From 76cb841cb886eef6b3bee341a2266c76578724ad Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 6 May 2024 03:02:30 +0200 Subject: Adding upstream version 4.19.249. Signed-off-by: Daniel Baumann --- sound/Kconfig | 114 + sound/Makefile | 19 + sound/ac97/Kconfig | 18 + sound/ac97/Makefile | 8 + sound/ac97/ac97_core.h | 16 + sound/ac97/bus.c | 562 ++ sound/ac97/codec.c | 15 + sound/ac97/snd_ac97_compat.c | 123 + sound/ac97_bus.c | 113 + sound/aoa/Kconfig | 17 + sound/aoa/Makefile | 4 + sound/aoa/aoa-gpio.h | 83 + sound/aoa/aoa.h | 129 + sound/aoa/codecs/Kconfig | 24 + sound/aoa/codecs/Makefile | 8 + sound/aoa/codecs/onyx.c | 1061 +++ sound/aoa/codecs/onyx.h | 75 + sound/aoa/codecs/tas-basstreble.h | 135 + sound/aoa/codecs/tas-gain-table.h | 210 + sound/aoa/codecs/tas.c | 948 ++ sound/aoa/codecs/tas.h | 55 + sound/aoa/codecs/toonie.c | 151 + sound/aoa/core/Makefile | 6 + sound/aoa/core/alsa.c | 99 + sound/aoa/core/alsa.h | 16 + sound/aoa/core/core.c | 162 + sound/aoa/core/gpio-feature.c | 425 + sound/aoa/core/gpio-pmf.c | 253 + sound/aoa/fabrics/Kconfig | 11 + sound/aoa/fabrics/Makefile | 3 + sound/aoa/fabrics/layout.c | 1182 +++ sound/aoa/soundbus/Kconfig | 14 + sound/aoa/soundbus/Makefile | 3 + sound/aoa/soundbus/core.c | 193 + sound/aoa/soundbus/i2sbus/Makefile | 2 + sound/aoa/soundbus/i2sbus/control.c | 194 + sound/aoa/soundbus/i2sbus/core.c | 458 + sound/aoa/soundbus/i2sbus/i2sbus.h | 126 + sound/aoa/soundbus/i2sbus/interface.h | 187 + sound/aoa/soundbus/i2sbus/pcm.c | 1067 +++ sound/aoa/soundbus/soundbus.h | 202 + sound/aoa/soundbus/sysfs.c | 46 + sound/arm/Kconfig | 38 + sound/arm/Makefile | 14 + sound/arm/aaci.c | 1111 +++ sound/arm/aaci.h | 250 + sound/arm/pxa2xx-ac97-lib.c | 432 + sound/arm/pxa2xx-ac97.c | 311 + sound/arm/pxa2xx-pcm-lib.c | 225 + sound/atmel/Kconfig | 12 + sound/atmel/Makefile | 3 + sound/atmel/ac97c.c | 899 ++ sound/atmel/ac97c.h | 73 + sound/core/Kconfig | 187 + sound/core/Makefile | 53 + sound/core/compress_offload.c | 1208 +++ sound/core/control.c | 1992 +++++ sound/core/control_compat.c | 504 ++ sound/core/ctljack.c | 88 + sound/core/device.c | 254 + sound/core/hrtimer.c | 183 + sound/core/hwdep.c | 551 ++ sound/core/hwdep_compat.c | 78 + sound/core/info.c | 888 ++ sound/core/info_oss.c | 122 + sound/core/init.c | 1035 +++ sound/core/isadma.c | 114 + sound/core/jack.c | 418 + sound/core/memalloc.c | 290 + sound/core/memory.c | 90 + sound/core/misc.c | 162 + sound/core/oss/Makefile | 14 + sound/core/oss/copy.c | 92 + sound/core/oss/io.c | 141 + sound/core/oss/linear.c | 180 + sound/core/oss/mixer_oss.c | 1451 +++ sound/core/oss/mulaw.c | 346 + sound/core/oss/pcm_oss.c | 3229 +++++++ sound/core/oss/pcm_plugin.c | 799 ++ sound/core/oss/pcm_plugin.h | 183 + sound/core/oss/rate.c | 348 + sound/core/oss/route.c | 111 + sound/core/pcm.c | 1299 +++ sound/core/pcm_compat.c | 732 ++ sound/core/pcm_dmaengine.c | 381 + sound/core/pcm_drm_eld.c | 99 + sound/core/pcm_iec958.c | 126 + sound/core/pcm_lib.c | 2479 ++++++ sound/core/pcm_local.h | 70 + sound/core/pcm_memory.c | 457 + sound/core/pcm_misc.c | 606 ++ sound/core/pcm_native.c | 3755 ++++++++ sound/core/pcm_param_trace.h | 143 + sound/core/pcm_timer.c | 145 + sound/core/pcm_trace.h | 149 + sound/core/rawmidi.c | 1869 ++++ sound/core/rawmidi_compat.c | 178 + sound/core/seq/Kconfig | 62 + sound/core/seq/Makefile | 24 + sound/core/seq/oss/Makefile | 10 + sound/core/seq/oss/seq_oss.c | 318 + sound/core/seq/oss/seq_oss_device.h | 175 + sound/core/seq/oss/seq_oss_event.c | 460 + sound/core/seq/oss/seq_oss_event.h | 112 + sound/core/seq/oss/seq_oss_init.c | 517 ++ sound/core/seq/oss/seq_oss_ioctl.c | 191 + sound/core/seq/oss/seq_oss_midi.c | 709 ++ sound/core/seq/oss/seq_oss_midi.h | 48 + sound/core/seq/oss/seq_oss_readq.c | 263 + sound/core/seq/oss/seq_oss_readq.h | 58 + sound/core/seq/oss/seq_oss_rw.c | 216 + sound/core/seq/oss/seq_oss_synth.c | 676 ++ sound/core/seq/oss/seq_oss_synth.h | 52 + sound/core/seq/oss/seq_oss_timer.c | 277 + sound/core/seq/oss/seq_oss_timer.h | 70 + sound/core/seq/oss/seq_oss_writeq.c | 172 + sound/core/seq/oss/seq_oss_writeq.h | 50 + sound/core/seq/seq.c | 135 + sound/core/seq/seq_clientmgr.c | 2540 ++++++ sound/core/seq/seq_clientmgr.h | 106 + sound/core/seq/seq_compat.c | 136 + sound/core/seq/seq_dummy.c | 226 + sound/core/seq/seq_fifo.c | 299 + sound/core/seq/seq_fifo.h | 74 + sound/core/seq/seq_info.c | 75 + sound/core/seq/seq_info.h | 40 + sound/core/seq/seq_lock.c | 41 + sound/core/seq/seq_lock.h | 22 + sound/core/seq/seq_memory.c | 518 ++ sound/core/seq/seq_memory.h | 103 + sound/core/seq/seq_midi.c | 468 + sound/core/seq/seq_midi_emul.c | 730 ++ sound/core/seq/seq_midi_event.c | 472 + sound/core/seq/seq_ports.c | 729 ++ sound/core/seq/seq_ports.h | 142 + sound/core/seq/seq_prioq.c | 451 + sound/core/seq/seq_prioq.h | 60 + sound/core/seq/seq_queue.c | 807 ++ sound/core/seq/seq_queue.h | 112 + sound/core/seq/seq_system.c | 187 + sound/core/seq/seq_system.h | 46 + sound/core/seq/seq_timer.c | 507 ++ sound/core/seq/seq_timer.h | 149 + sound/core/seq/seq_virmidi.c | 531 ++ sound/core/seq_device.c | 316 + sound/core/sgbuf.c | 164 + sound/core/sound.c | 423 + sound/core/sound_oss.c | 259 + sound/core/timer.c | 2203 +++++ sound/core/timer_compat.c | 182 + sound/core/vmaster.c | 524 ++ sound/drivers/Kconfig | 238 + sound/drivers/Makefile | 26 + sound/drivers/aloop.c | 1292 +++ sound/drivers/dummy.c | 1229 +++ sound/drivers/ml403-ac97cr.c | 1335 +++ sound/drivers/mpu401/Makefile | 12 + sound/drivers/mpu401/mpu401.c | 287 + sound/drivers/mpu401/mpu401_uart.c | 619 ++ sound/drivers/mtpav.c | 786 ++ sound/drivers/mts64.c | 1071 +++ sound/drivers/opl3/Makefile | 15 + sound/drivers/opl3/opl3_drums.c | 224 + sound/drivers/opl3/opl3_lib.c | 540 ++ sound/drivers/opl3/opl3_midi.c | 870 ++ sound/drivers/opl3/opl3_oss.c | 263 + sound/drivers/opl3/opl3_seq.c | 290 + sound/drivers/opl3/opl3_synth.c | 622 ++ sound/drivers/opl3/opl3_voice.h | 59 + sound/drivers/opl4/Makefile | 12 + sound/drivers/opl4/opl4_lib.c | 265 + sound/drivers/opl4/opl4_local.h | 235 + sound/drivers/opl4/opl4_mixer.c | 95 + sound/drivers/opl4/opl4_proc.c | 129 + sound/drivers/opl4/opl4_seq.c | 210 + sound/drivers/opl4/opl4_synth.c | 631 ++ sound/drivers/opl4/yrw801.c | 961 ++ sound/drivers/pcm-indirect2.c | 573 ++ sound/drivers/pcm-indirect2.h | 140 + sound/drivers/pcsp/Makefile | 2 + sound/drivers/pcsp/pcsp.c | 239 + sound/drivers/pcsp/pcsp.h | 83 + sound/drivers/pcsp/pcsp_input.c | 117 + sound/drivers/pcsp/pcsp_input.h | 15 + sound/drivers/pcsp/pcsp_lib.c | 360 + sound/drivers/pcsp/pcsp_mixer.c | 164 + sound/drivers/portman2x4.c | 870 ++ sound/drivers/serial-u16550.c | 1044 +++ sound/drivers/virmidi.c | 199 + sound/drivers/vx/Makefile | 8 + sound/drivers/vx/vx_cmd.c | 109 + sound/drivers/vx/vx_cmd.h | 246 + sound/drivers/vx/vx_core.c | 817 ++ sound/drivers/vx/vx_hwdep.c | 121 + sound/drivers/vx/vx_mixer.c | 1005 +++ sound/drivers/vx/vx_pcm.c | 1261 +++ sound/drivers/vx/vx_uer.c | 309 + sound/firewire/Kconfig | 165 + sound/firewire/Makefile | 18 + sound/firewire/amdtp-am824.c | 398 + sound/firewire/amdtp-am824.h | 49 + sound/firewire/amdtp-stream-trace.h | 198 + sound/firewire/amdtp-stream.c | 1031 +++ sound/firewire/amdtp-stream.h | 247 + sound/firewire/bebob/Makefile | 6 + sound/firewire/bebob/bebob.c | 564 ++ sound/firewire/bebob/bebob.h | 262 + sound/firewire/bebob/bebob_command.c | 296 + sound/firewire/bebob/bebob_focusrite.c | 323 + sound/firewire/bebob/bebob_hwdep.c | 197 + sound/firewire/bebob/bebob_maudio.c | 797 ++ sound/firewire/bebob/bebob_midi.c | 174 + sound/firewire/bebob/bebob_pcm.c | 391 + sound/firewire/bebob/bebob_proc.c | 198 + sound/firewire/bebob/bebob_stream.c | 1004 +++ sound/firewire/bebob/bebob_terratec.c | 54 + sound/firewire/bebob/bebob_yamaha_terratec.c | 65 + sound/firewire/cmp.c | 404 + sound/firewire/cmp.h | 50 + sound/firewire/dice/Makefile | 4 + sound/firewire/dice/dice-alesis.c | 52 + sound/firewire/dice/dice-extension.c | 175 + sound/firewire/dice/dice-hwdep.c | 190 + sound/firewire/dice/dice-interface.h | 378 + sound/firewire/dice/dice-midi.c | 157 + sound/firewire/dice/dice-mytek.c | 46 + sound/firewire/dice/dice-pcm.c | 459 + sound/firewire/dice/dice-proc.c | 316 + sound/firewire/dice/dice-stream.c | 670 ++ sound/firewire/dice/dice-tcelectronic.c | 104 + sound/firewire/dice/dice-transaction.c | 370 + sound/firewire/dice/dice.c | 426 + sound/firewire/dice/dice.h | 231 + sound/firewire/digi00x/Makefile | 4 + sound/firewire/digi00x/amdtp-dot.c | 402 + sound/firewire/digi00x/digi00x-hwdep.c | 199 + sound/firewire/digi00x/digi00x-midi.c | 169 + sound/firewire/digi00x/digi00x-pcm.c | 370 + sound/firewire/digi00x/digi00x-proc.c | 99 + sound/firewire/digi00x/digi00x-stream.c | 422 + sound/firewire/digi00x/digi00x-transaction.c | 84 + sound/firewire/digi00x/digi00x.c | 230 + sound/firewire/digi00x/digi00x.h | 159 + sound/firewire/fcp.c | 401 + sound/firewire/fcp.h | 34 + sound/firewire/fireface/Makefile | 3 + sound/firewire/fireface/amdtp-ff.c | 155 + sound/firewire/fireface/ff-hwdep.c | 191 + sound/firewire/fireface/ff-midi.c | 129 + sound/firewire/fireface/ff-pcm.c | 401 + sound/firewire/fireface/ff-proc.c | 63 + sound/firewire/fireface/ff-protocol-ff400.c | 374 + sound/firewire/fireface/ff-stream.c | 282 + sound/firewire/fireface/ff-transaction.c | 295 + sound/firewire/fireface/ff.c | 209 + sound/firewire/fireface/ff.h | 146 + sound/firewire/fireworks/Makefile | 4 + sound/firewire/fireworks/fireworks.c | 427 + sound/firewire/fireworks/fireworks.h | 235 + sound/firewire/fireworks/fireworks_command.c | 372 + sound/firewire/fireworks/fireworks_hwdep.c | 331 + sound/firewire/fireworks/fireworks_midi.c | 174 + sound/firewire/fireworks/fireworks_pcm.c | 415 + sound/firewire/fireworks/fireworks_proc.c | 232 + sound/firewire/fireworks/fireworks_stream.c | 319 + sound/firewire/fireworks/fireworks_transaction.c | 325 + sound/firewire/isight.c | 743 ++ sound/firewire/iso-resources.c | 236 + sound/firewire/iso-resources.h | 39 + sound/firewire/lib.c | 104 + sound/firewire/lib.h | 29 + sound/firewire/motu/Makefile | 7 + sound/firewire/motu/amdtp-motu-trace.h | 123 + sound/firewire/motu/amdtp-motu.c | 429 + sound/firewire/motu/motu-hwdep.c | 198 + sound/firewire/motu/motu-midi.c | 169 + sound/firewire/motu/motu-pcm.c | 392 + sound/firewire/motu/motu-proc.c | 118 + sound/firewire/motu/motu-protocol-v2.c | 268 + sound/firewire/motu/motu-protocol-v3.c | 315 + sound/firewire/motu/motu-stream.c | 415 + sound/firewire/motu/motu-transaction.c | 137 + sound/firewire/motu/motu.c | 314 + sound/firewire/motu/motu.h | 168 + sound/firewire/oxfw/Makefile | 3 + sound/firewire/oxfw/oxfw-command.c | 159 + sound/firewire/oxfw/oxfw-hwdep.c | 190 + sound/firewire/oxfw/oxfw-midi.c | 187 + sound/firewire/oxfw/oxfw-pcm.c | 423 + sound/firewire/oxfw/oxfw-proc.c | 113 + sound/firewire/oxfw/oxfw-scs1x.c | 420 + sound/firewire/oxfw/oxfw-spkr.c | 319 + sound/firewire/oxfw/oxfw-stream.c | 730 ++ sound/firewire/oxfw/oxfw.c | 470 + sound/firewire/oxfw/oxfw.h | 141 + sound/firewire/packets-buffer.c | 77 + sound/firewire/packets-buffer.h | 27 + sound/firewire/tascam/Makefile | 4 + sound/firewire/tascam/amdtp-tascam.c | 185 + sound/firewire/tascam/tascam-hwdep.c | 189 + sound/firewire/tascam/tascam-midi.c | 136 + sound/firewire/tascam/tascam-pcm.c | 300 + sound/firewire/tascam/tascam-proc.c | 88 + sound/firewire/tascam/tascam-stream.c | 508 ++ sound/firewire/tascam/tascam-transaction.c | 400 + sound/firewire/tascam/tascam.c | 290 + sound/firewire/tascam/tascam.h | 180 + sound/hda/Kconfig | 30 + sound/hda/Makefile | 15 + sound/hda/array.c | 51 + sound/hda/ext/Makefile | 3 + sound/hda/ext/hdac_ext_bus.c | 258 + sound/hda/ext/hdac_ext_controller.c | 337 + sound/hda/ext/hdac_ext_stream.c | 551 ++ sound/hda/hda_bus_type.c | 96 + sound/hda/hdac_bus.c | 221 + sound/hda/hdac_component.c | 335 + sound/hda/hdac_controller.c | 620 ++ sound/hda/hdac_device.c | 1103 +++ sound/hda/hdac_i915.c | 160 + sound/hda/hdac_regmap.c | 531 ++ sound/hda/hdac_stream.c | 720 ++ sound/hda/hdac_sysfs.c | 464 + sound/hda/hdmi_chmap.c | 849 ++ sound/hda/local.h | 36 + sound/hda/trace.c | 6 + sound/hda/trace.h | 90 + sound/i2c/Makefile | 16 + sound/i2c/cs8427.c | 623 ++ sound/i2c/i2c.c | 340 + sound/i2c/other/Makefile | 16 + sound/i2c/other/ak4113.c | 656 ++ sound/i2c/other/ak4114.c | 641 ++ sound/i2c/other/ak4117.c | 547 ++ sound/i2c/other/ak4xxx-adda.c | 914 ++ sound/i2c/other/pt2258.c | 227 + sound/i2c/tea6330t.c | 370 + sound/isa/Kconfig | 471 + sound/isa/Makefile | 29 + sound/isa/ad1816a/Makefile | 9 + sound/isa/ad1816a/ad1816a.c | 303 + sound/isa/ad1816a/ad1816a_lib.c | 981 +++ sound/isa/ad1848/Makefile | 10 + sound/isa/ad1848/ad1848.c | 177 + sound/isa/adlib.c | 115 + sound/isa/als100.c | 379 + sound/isa/azt2320.c | 354 + sound/isa/cmi8328.c | 472 + sound/isa/cmi8330.c | 780 ++ sound/isa/cs423x/Makefile | 13 + sound/isa/cs423x/cs4231.c | 193 + sound/isa/cs423x/cs4236.c | 728 ++ sound/isa/cs423x/cs4236_lib.c | 1086 +++ sound/isa/es1688/Makefile | 11 + sound/isa/es1688/es1688.c | 368 + sound/isa/es1688/es1688_lib.c | 1031 +++ sound/isa/es18xx.c | 2441 +++++ sound/isa/galaxy/Makefile | 10 + sound/isa/galaxy/azt1605.c | 91 + sound/isa/galaxy/azt2316.c | 111 + sound/isa/galaxy/galaxy.c | 640 ++ sound/isa/gus/Makefile | 25 + sound/isa/gus/gus_dma.c | 251 + sound/isa/gus/gus_dram.c | 102 + sound/isa/gus/gus_io.c | 540 ++ sound/isa/gus/gus_irq.c | 149 + sound/isa/gus/gus_main.c | 467 + sound/isa/gus/gus_mem.c | 351 + sound/isa/gus/gus_mem_proc.c | 102 + sound/isa/gus/gus_mixer.c | 190 + sound/isa/gus/gus_pcm.c | 924 ++ sound/isa/gus/gus_reset.c | 411 + sound/isa/gus/gus_tables.h | 90 + sound/isa/gus/gus_timer.c | 203 + sound/isa/gus/gus_uart.c | 258 + sound/isa/gus/gus_volume.c | 218 + sound/isa/gus/gusclassic.c | 232 + sound/isa/gus/gusextreme.c | 361 + sound/isa/gus/gusmax.c | 373 + sound/isa/gus/interwave-stb.c | 2 + sound/isa/gus/interwave.c | 937 ++ sound/isa/msnd/Makefile | 10 + sound/isa/msnd/msnd.c | 705 ++ sound/isa/msnd/msnd.h | 308 + sound/isa/msnd/msnd_classic.c | 3 + sound/isa/msnd/msnd_classic.h | 129 + sound/isa/msnd/msnd_midi.c | 182 + sound/isa/msnd/msnd_pinnacle.c | 1243 +++ sound/isa/msnd/msnd_pinnacle.h | 181 + sound/isa/msnd/msnd_pinnacle_mixer.c | 338 + sound/isa/opl3sa2.c | 966 ++ sound/isa/opti9xx/Makefile | 16 + sound/isa/opti9xx/miro.c | 1669 ++++ sound/isa/opti9xx/opti92x-ad1848.c | 1209 +++ sound/isa/opti9xx/opti92x-cs4231.c | 2 + sound/isa/opti9xx/opti93x.c | 3 + sound/isa/sb/Makefile | 29 + sound/isa/sb/emu8000.c | 1173 +++ sound/isa/sb/emu8000_callback.c | 547 ++ sound/isa/sb/emu8000_local.h | 45 + sound/isa/sb/emu8000_patch.c | 304 + sound/isa/sb/emu8000_pcm.c | 709 ++ sound/isa/sb/emu8000_synth.c | 129 + sound/isa/sb/jazz16.c | 390 + sound/isa/sb/sb16.c | 697 ++ sound/isa/sb/sb16_csp.c | 1199 +++ sound/isa/sb/sb16_main.c | 902 ++ sound/isa/sb/sb8.c | 254 + sound/isa/sb/sb8_main.c | 623 ++ sound/isa/sb/sb8_midi.c | 279 + sound/isa/sb/sb_common.c | 307 + sound/isa/sb/sb_mixer.c | 961 ++ sound/isa/sb/sbawe.c | 2 + sound/isa/sc6000.c | 714 ++ sound/isa/sscape.c | 1357 +++ sound/isa/wavefront/Makefile | 9 + sound/isa/wavefront/wavefront.c | 682 ++ sound/isa/wavefront/wavefront_fx.c | 285 + sound/isa/wavefront/wavefront_midi.c | 575 ++ sound/isa/wavefront/wavefront_synth.c | 2216 +++++ sound/isa/wss/Makefile | 10 + sound/isa/wss/wss_lib.c | 2279 +++++ sound/last.c | 40 + sound/mips/Kconfig | 27 + sound/mips/Makefile | 10 + sound/mips/ad1843.c | 561 ++ sound/mips/hal2.c | 937 ++ sound/mips/hal2.h | 245 + sound/mips/sgio2audio.c | 966 ++ sound/oss/.gitignore | 3 + sound/oss/dmasound/Kconfig | 45 + sound/oss/dmasound/Makefile | 7 + sound/oss/dmasound/dmasound.h | 262 + sound/oss/dmasound/dmasound_atari.c | 1620 ++++ sound/oss/dmasound/dmasound_core.c | 1600 ++++ sound/oss/dmasound/dmasound_paula.c | 738 ++ sound/oss/dmasound/dmasound_q40.c | 638 ++ sound/parisc/Kconfig | 20 + sound/parisc/Makefile | 8 + sound/parisc/harmony.c | 1034 +++ sound/parisc/harmony.h | 155 + sound/pci/Kconfig | 913 ++ sound/pci/Makefile | 83 + sound/pci/ac97/Makefile | 10 + sound/pci/ac97/ac97_codec.c | 2943 +++++++ sound/pci/ac97/ac97_id.h | 66 + sound/pci/ac97/ac97_local.h | 41 + sound/pci/ac97/ac97_patch.c | 3947 +++++++++ sound/pci/ac97/ac97_patch.h | 95 + sound/pci/ac97/ac97_pcm.c | 752 ++ sound/pci/ac97/ac97_proc.c | 490 ++ sound/pci/ad1889.c | 1051 +++ sound/pci/ad1889.h | 190 + sound/pci/ak4531_codec.c | 488 + sound/pci/ali5451/Makefile | 9 + sound/pci/ali5451/ali5451.c | 2281 +++++ sound/pci/als300.c | 816 ++ sound/pci/als4000.c | 1037 +++ sound/pci/asihpi/Makefile | 5 + sound/pci/asihpi/asihpi.c | 3053 +++++++ sound/pci/asihpi/hpi.h | 1735 ++++ sound/pci/asihpi/hpi6000.c | 1812 ++++ sound/pci/asihpi/hpi6000.h | 70 + sound/pci/asihpi/hpi6205.c | 2230 +++++ sound/pci/asihpi/hpi6205.h | 103 + sound/pci/asihpi/hpi_internal.h | 1435 +++ sound/pci/asihpi/hpi_version.h | 33 + sound/pci/asihpi/hpicmn.c | 722 ++ sound/pci/asihpi/hpicmn.h | 82 + sound/pci/asihpi/hpidebug.c | 78 + sound/pci/asihpi/hpidebug.h | 102 + sound/pci/asihpi/hpidspcd.c | 142 + sound/pci/asihpi/hpidspcd.h | 106 + sound/pci/asihpi/hpifunc.c | 2869 ++++++ sound/pci/asihpi/hpimsginit.c | 131 + sound/pci/asihpi/hpimsginit.h | 46 + sound/pci/asihpi/hpimsgx.c | 809 ++ sound/pci/asihpi/hpimsgx.h | 36 + sound/pci/asihpi/hpioctl.c | 591 ++ sound/pci/asihpi/hpioctl.h | 38 + sound/pci/asihpi/hpios.c | 83 + sound/pci/asihpi/hpios.h | 165 + sound/pci/asihpi/hpipcida.h | 37 + sound/pci/atiixp.c | 1706 ++++ sound/pci/atiixp_modem.c | 1327 +++ sound/pci/au88x0/Makefile | 8 + sound/pci/au88x0/au8810.c | 17 + sound/pci/au88x0/au8810.h | 225 + sound/pci/au88x0/au8820.c | 15 + sound/pci/au88x0/au8820.h | 205 + sound/pci/au88x0/au8830.c | 18 + sound/pci/au88x0/au8830.h | 252 + sound/pci/au88x0/au88x0.c | 382 + sound/pci/au88x0/au88x0.h | 293 + sound/pci/au88x0/au88x0_a3d.c | 915 ++ sound/pci/au88x0/au88x0_a3d.h | 123 + sound/pci/au88x0/au88x0_a3ddata.c | 91 + sound/pci/au88x0/au88x0_core.c | 2867 ++++++ sound/pci/au88x0/au88x0_eq.c | 930 ++ sound/pci/au88x0/au88x0_eq.h | 44 + sound/pci/au88x0/au88x0_eqdata.c | 117 + sound/pci/au88x0/au88x0_game.c | 134 + sound/pci/au88x0/au88x0_mixer.c | 44 + sound/pci/au88x0/au88x0_mpu401.c | 112 + sound/pci/au88x0/au88x0_pcm.c | 704 ++ sound/pci/au88x0/au88x0_synth.c | 412 + sound/pci/au88x0/au88x0_wt.h | 66 + sound/pci/au88x0/au88x0_xtalk.c | 795 ++ sound/pci/au88x0/au88x0_xtalk.h | 61 + sound/pci/aw2/Makefile | 3 + sound/pci/aw2/aw2-alsa.c | 761 ++ sound/pci/aw2/aw2-saa7146.c | 461 + sound/pci/aw2/aw2-saa7146.h | 105 + sound/pci/aw2/aw2-tsl.c | 110 + sound/pci/aw2/saa7146.h | 168 + sound/pci/azt3328.c | 2762 ++++++ sound/pci/azt3328.h | 343 + sound/pci/bt87x.c | 994 +++ sound/pci/ca0106/Makefile | 4 + sound/pci/ca0106/ca0106.h | 742 ++ sound/pci/ca0106/ca0106_main.c | 1971 +++++ sound/pci/ca0106/ca0106_mixer.c | 932 ++ sound/pci/ca0106/ca0106_proc.c | 453 + sound/pci/ca0106/ca_midi.c | 316 + sound/pci/ca0106/ca_midi.h | 66 + sound/pci/cmipci.c | 3406 +++++++ sound/pci/cs4281.c | 2085 +++++ sound/pci/cs46xx/Makefile | 10 + sound/pci/cs46xx/cs46xx.c | 176 + sound/pci/cs46xx/cs46xx.h | 1749 ++++ sound/pci/cs46xx/cs46xx_dsp_scb_types.h | 1213 +++ sound/pci/cs46xx/cs46xx_dsp_spos.h | 234 + sound/pci/cs46xx/cs46xx_dsp_task_types.h | 252 + sound/pci/cs46xx/cs46xx_lib.c | 4052 +++++++++ sound/pci/cs46xx/cs46xx_lib.h | 210 + sound/pci/cs46xx/dsp_spos.c | 2056 +++++ sound/pci/cs46xx/dsp_spos.h | 231 + sound/pci/cs46xx/dsp_spos_scb_lib.c | 1800 ++++ sound/pci/cs5530.c | 298 + sound/pci/cs5535audio/Makefile | 10 + sound/pci/cs5535audio/cs5535audio.c | 411 + sound/pci/cs5535audio/cs5535audio.h | 140 + sound/pci/cs5535audio/cs5535audio_olpc.c | 192 + sound/pci/cs5535audio/cs5535audio_pcm.c | 454 + sound/pci/cs5535audio/cs5535audio_pm.c | 115 + sound/pci/ctxfi/Makefile | 5 + sound/pci/ctxfi/ct20k1reg.h | 634 ++ sound/pci/ctxfi/ct20k2reg.h | 89 + sound/pci/ctxfi/ctamixer.c | 488 + sound/pci/ctxfi/ctamixer.h | 99 + sound/pci/ctxfi/ctatc.c | 1753 ++++ sound/pci/ctxfi/ctatc.h | 160 + sound/pci/ctxfi/ctdaio.c | 759 ++ sound/pci/ctxfi/ctdaio.h | 125 + sound/pci/ctxfi/cthardware.c | 93 + sound/pci/ctxfi/cthardware.h | 217 + sound/pci/ctxfi/cthw20k1.c | 2287 +++++ sound/pci/ctxfi/cthw20k1.h | 26 + sound/pci/ctxfi/cthw20k2.c | 2353 +++++ sound/pci/ctxfi/cthw20k2.h | 26 + sound/pci/ctxfi/ctimap.c | 112 + sound/pci/ctxfi/ctimap.h | 40 + sound/pci/ctxfi/ctmixer.c | 1229 +++ sound/pci/ctxfi/ctmixer.h | 70 + sound/pci/ctxfi/ctpcm.c | 490 ++ sound/pci/ctxfi/ctpcm.h | 27 + sound/pci/ctxfi/ctresource.c | 293 + sound/pci/ctxfi/ctresource.h | 73 + sound/pci/ctxfi/ctsrc.c | 886 ++ sound/pci/ctxfi/ctsrc.h | 152 + sound/pci/ctxfi/cttimer.c | 442 + sound/pci/ctxfi/cttimer.h | 30 + sound/pci/ctxfi/ctvmem.c | 245 + sound/pci/ctxfi/ctvmem.h | 63 + sound/pci/ctxfi/xfi.c | 162 + sound/pci/echoaudio/Makefile | 35 + sound/pci/echoaudio/darla20.c | 101 + sound/pci/echoaudio/darla20_dsp.c | 129 + sound/pci/echoaudio/darla24.c | 108 + sound/pci/echoaudio/darla24_dsp.c | 163 + sound/pci/echoaudio/echo3g.c | 122 + sound/pci/echoaudio/echo3g_dsp.c | 131 + sound/pci/echoaudio/echoaudio.c | 2293 +++++ sound/pci/echoaudio/echoaudio.h | 562 ++ sound/pci/echoaudio/echoaudio_3g.c | 433 + sound/pci/echoaudio/echoaudio_dsp.c | 1161 +++ sound/pci/echoaudio/echoaudio_dsp.h | 698 ++ sound/pci/echoaudio/echoaudio_gml.c | 203 + sound/pci/echoaudio/gina20.c | 105 + sound/pci/echoaudio/gina20_dsp.c | 215 + sound/pci/echoaudio/gina24.c | 129 + sound/pci/echoaudio/gina24_dsp.c | 345 + sound/pci/echoaudio/indigo.c | 107 + sound/pci/echoaudio/indigo_dsp.c | 165 + sound/pci/echoaudio/indigo_express_dsp.c | 122 + sound/pci/echoaudio/indigodj.c | 107 + sound/pci/echoaudio/indigodj_dsp.c | 165 + sound/pci/echoaudio/indigodjx.c | 108 + sound/pci/echoaudio/indigodjx_dsp.c | 70 + sound/pci/echoaudio/indigoio.c | 108 + sound/pci/echoaudio/indigoio_dsp.c | 135 + sound/pci/echoaudio/indigoiox.c | 110 + sound/pci/echoaudio/indigoiox_dsp.c | 70 + sound/pci/echoaudio/layla20.c | 115 + sound/pci/echoaudio/layla20_dsp.c | 289 + sound/pci/echoaudio/layla24.c | 127 + sound/pci/echoaudio/layla24_dsp.c | 394 + sound/pci/echoaudio/mia.c | 121 + sound/pci/echoaudio/mia_dsp.c | 225 + sound/pci/echoaudio/midi.c | 326 + sound/pci/echoaudio/mona.c | 138 + sound/pci/echoaudio/mona_dsp.c | 426 + sound/pci/emu10k1/Makefile | 17 + sound/pci/emu10k1/emu10k1.c | 283 + sound/pci/emu10k1/emu10k1_callback.c | 550 ++ sound/pci/emu10k1/emu10k1_main.c | 2178 +++++ sound/pci/emu10k1/emu10k1_patch.c | 228 + sound/pci/emu10k1/emu10k1_synth.c | 119 + sound/pci/emu10k1/emu10k1_synth_local.h | 42 + sound/pci/emu10k1/emu10k1x.c | 1642 ++++ sound/pci/emu10k1/emufx.c | 2794 ++++++ sound/pci/emu10k1/emumixer.c | 2234 +++++ sound/pci/emu10k1/emumpu401.c | 399 + sound/pci/emu10k1/emupcm.c | 1867 ++++ sound/pci/emu10k1/emuproc.c | 656 ++ sound/pci/emu10k1/io.c | 580 ++ sound/pci/emu10k1/irq.c | 213 + sound/pci/emu10k1/memory.c | 636 ++ sound/pci/emu10k1/p16v.c | 909 ++ sound/pci/emu10k1/p16v.h | 299 + sound/pci/emu10k1/p17v.h | 158 + sound/pci/emu10k1/timer.c | 96 + sound/pci/emu10k1/tina2.h | 32 + sound/pci/emu10k1/voice.c | 168 + sound/pci/ens1370.c | 2476 ++++++ sound/pci/ens1371.c | 2 + sound/pci/es1938.c | 1883 ++++ sound/pci/es1968.c | 2934 +++++++ sound/pci/fm801.c | 1463 +++ sound/pci/hda/Kconfig | 231 + sound/pci/hda/Makefile | 50 + sound/pci/hda/ca0132_regs.h | 409 + sound/pci/hda/dell_wmi_helper.c | 48 + sound/pci/hda/hda_auto_parser.c | 1046 +++ sound/pci/hda/hda_auto_parser.h | 119 + sound/pci/hda/hda_beep.c | 339 + sound/pci/hda/hda_beep.h | 51 + sound/pci/hda/hda_bind.c | 316 + sound/pci/hda/hda_codec.c | 4032 +++++++++ sound/pci/hda/hda_codec.h | 537 ++ sound/pci/hda/hda_controller.c | 1420 +++ sound/pci/hda/hda_controller.h | 237 + sound/pci/hda/hda_controller_trace.h | 99 + sound/pci/hda/hda_eld.c | 780 ++ sound/pci/hda/hda_generic.c | 6146 +++++++++++++ sound/pci/hda/hda_generic.h | 364 + sound/pci/hda/hda_hwdep.c | 134 + sound/pci/hda/hda_intel.c | 2849 ++++++ sound/pci/hda/hda_intel.h | 47 + sound/pci/hda/hda_intel_trace.h | 54 + sound/pci/hda/hda_jack.c | 575 ++ sound/pci/hda/hda_jack.h | 95 + sound/pci/hda/hda_local.h | 741 ++ sound/pci/hda/hda_proc.c | 933 ++ sound/pci/hda/hda_sysfs.c | 798 ++ sound/pci/hda/hda_tegra.c | 586 ++ sound/pci/hda/hp_x360_helper.c | 95 + sound/pci/hda/patch_analog.c | 1196 +++ sound/pci/hda/patch_ca0110.c | 101 + sound/pci/hda/patch_ca0132.c | 7716 ++++++++++++++++ sound/pci/hda/patch_cirrus.c | 1256 +++ sound/pci/hda/patch_cmedia.c | 141 + sound/pci/hda/patch_conexant.c | 1138 +++ sound/pci/hda/patch_hdmi.c | 3970 +++++++++ sound/pci/hda/patch_realtek.c | 9277 ++++++++++++++++++++ sound/pci/hda/patch_si3054.c | 318 + sound/pci/hda/patch_sigmatel.c | 5177 +++++++++++ sound/pci/hda/patch_via.c | 1264 +++ sound/pci/hda/thinkpad_helper.c | 80 + sound/pci/ice1712/Makefile | 13 + sound/pci/ice1712/ak4xxx.c | 184 + sound/pci/ice1712/amp.c | 97 + sound/pci/ice1712/amp.h | 48 + sound/pci/ice1712/aureon.c | 2281 +++++ sound/pci/ice1712/aureon.h | 65 + sound/pci/ice1712/delta.c | 934 ++ sound/pci/ice1712/delta.h | 166 + sound/pci/ice1712/envy24ht.h | 220 + sound/pci/ice1712/ews.c | 1076 +++ sound/pci/ice1712/ews.h | 86 + sound/pci/ice1712/hoontech.c | 384 + sound/pci/ice1712/hoontech.h | 78 + sound/pci/ice1712/ice1712.c | 2874 ++++++ sound/pci/ice1712/ice1712.h | 539 ++ sound/pci/ice1712/ice1724.c | 2885 ++++++ sound/pci/ice1712/juli.c | 699 ++ sound/pci/ice1712/juli.h | 11 + sound/pci/ice1712/maya44.c | 762 ++ sound/pci/ice1712/maya44.h | 11 + sound/pci/ice1712/phase.c | 964 ++ sound/pci/ice1712/phase.h | 53 + sound/pci/ice1712/pontis.c | 829 ++ sound/pci/ice1712/pontis.h | 33 + sound/pci/ice1712/prodigy192.c | 806 ++ sound/pci/ice1712/prodigy192.h | 20 + sound/pci/ice1712/prodigy_hifi.c | 1283 +++ sound/pci/ice1712/prodigy_hifi.h | 38 + sound/pci/ice1712/psc724.c | 464 + sound/pci/ice1712/psc724.h | 14 + sound/pci/ice1712/quartet.c | 1105 +++ sound/pci/ice1712/quartet.h | 11 + sound/pci/ice1712/revo.c | 645 ++ sound/pci/ice1712/revo.h | 55 + sound/pci/ice1712/se.c | 766 ++ sound/pci/ice1712/se.h | 16 + sound/pci/ice1712/stac946x.h | 26 + sound/pci/ice1712/vt1720_mobo.c | 138 + sound/pci/ice1712/vt1720_mobo.h | 41 + sound/pci/ice1712/wm8766.c | 346 + sound/pci/ice1712/wm8766.h | 161 + sound/pci/ice1712/wm8776.c | 619 ++ sound/pci/ice1712/wm8776.h | 223 + sound/pci/ice1712/wtm.c | 646 ++ sound/pci/ice1712/wtm.h | 21 + sound/pci/intel8x0.c | 3356 +++++++ sound/pci/intel8x0m.c | 1332 +++ sound/pci/korg1212/Makefile | 9 + sound/pci/korg1212/korg1212.c | 2486 ++++++ sound/pci/lola/Makefile | 4 + sound/pci/lola/lola.c | 775 ++ sound/pci/lola/lola.h | 527 ++ sound/pci/lola/lola_clock.c | 323 + sound/pci/lola/lola_mixer.c | 896 ++ sound/pci/lola/lola_pcm.c | 710 ++ sound/pci/lola/lola_proc.c | 222 + sound/pci/lx6464es/Makefile | 2 + sound/pci/lx6464es/lx6464es.c | 1153 +++ sound/pci/lx6464es/lx6464es.h | 111 + sound/pci/lx6464es/lx_core.c | 1198 +++ sound/pci/lx6464es/lx_core.h | 237 + sound/pci/lx6464es/lx_defs.h | 376 + sound/pci/maestro3.c | 2794 ++++++ sound/pci/mixart/Makefile | 8 + sound/pci/mixart/mixart.c | 1421 +++ sound/pci/mixart/mixart.h | 220 + sound/pci/mixart/mixart_core.c | 601 ++ sound/pci/mixart/mixart_core.h | 571 ++ sound/pci/mixart/mixart_hwdep.c | 591 ++ sound/pci/mixart/mixart_hwdep.h | 155 + sound/pci/mixart/mixart_mixer.c | 1194 +++ sound/pci/mixart/mixart_mixer.h | 31 + sound/pci/nm256/Makefile | 9 + sound/pci/nm256/nm256.c | 1772 ++++ sound/pci/nm256/nm256_coef.c | 4608 ++++++++++ sound/pci/oxygen/Makefile | 11 + sound/pci/oxygen/ak4396.h | 45 + sound/pci/oxygen/cm9780.h | 64 + sound/pci/oxygen/cs2000.h | 84 + sound/pci/oxygen/cs4245.h | 111 + sound/pci/oxygen/cs4362a.h | 70 + sound/pci/oxygen/cs4398.h | 70 + sound/pci/oxygen/oxygen.c | 880 ++ sound/pci/oxygen/oxygen.h | 260 + sound/pci/oxygen/oxygen_io.c | 292 + sound/pci/oxygen/oxygen_lib.c | 834 ++ sound/pci/oxygen/oxygen_mixer.c | 1139 +++ sound/pci/oxygen/oxygen_pcm.c | 807 ++ sound/pci/oxygen/oxygen_regs.h | 459 + sound/pci/oxygen/pcm1796.h | 59 + sound/pci/oxygen/se6x.c | 160 + sound/pci/oxygen/virtuoso.c | 107 + sound/pci/oxygen/wm8766.h | 74 + sound/pci/oxygen/wm8776.h | 177 + sound/pci/oxygen/wm8785.h | 46 + sound/pci/oxygen/xonar.h | 55 + sound/pci/oxygen/xonar_cs43xx.c | 452 + sound/pci/oxygen/xonar_dg.c | 295 + sound/pci/oxygen/xonar_dg.h | 57 + sound/pci/oxygen/xonar_dg_mixer.c | 477 + sound/pci/oxygen/xonar_hdmi.c | 128 + sound/pci/oxygen/xonar_lib.c | 134 + sound/pci/oxygen/xonar_pcm179x.c | 1299 +++ sound/pci/oxygen/xonar_wm87x6.c | 1342 +++ sound/pci/pcxhr/Makefile | 2 + sound/pci/pcxhr/pcxhr.c | 1699 ++++ sound/pci/pcxhr/pcxhr.h | 212 + sound/pci/pcxhr/pcxhr_core.c | 1346 +++ sound/pci/pcxhr/pcxhr_core.h | 205 + sound/pci/pcxhr/pcxhr_hwdep.c | 422 + sound/pci/pcxhr/pcxhr_hwdep.h | 40 + sound/pci/pcxhr/pcxhr_mix22.c | 868 ++ sound/pci/pcxhr/pcxhr_mix22.h | 60 + sound/pci/pcxhr/pcxhr_mixer.c | 1259 +++ sound/pci/pcxhr/pcxhr_mixer.h | 29 + sound/pci/riptide/Makefile | 3 + sound/pci/riptide/riptide.c | 2217 +++++ sound/pci/rme32.c | 1996 +++++ sound/pci/rme96.c | 2550 ++++++ sound/pci/rme9652/Makefile | 14 + sound/pci/rme9652/hdsp.c | 5413 ++++++++++++ sound/pci/rme9652/hdspm.c | 7001 +++++++++++++++ sound/pci/rme9652/rme9652.c | 2655 ++++++ sound/pci/sis7019.c | 1480 ++++ sound/pci/sis7019.h | 342 + sound/pci/sonicvibes.c | 1548 ++++ sound/pci/trident/Makefile | 9 + sound/pci/trident/trident.c | 187 + sound/pci/trident/trident.h | 444 + sound/pci/trident/trident_main.c | 3958 +++++++++ sound/pci/trident/trident_memory.c | 314 + sound/pci/via82xx.c | 2650 ++++++ sound/pci/via82xx_modem.c | 1232 +++ sound/pci/vx222/Makefile | 8 + sound/pci/vx222/vx222.c | 292 + sound/pci/vx222/vx222.h | 116 + sound/pci/vx222/vx222_ops.c | 1037 +++ sound/pci/ymfpci/Makefile | 9 + sound/pci/ymfpci/ymfpci.c | 371 + sound/pci/ymfpci/ymfpci.h | 389 + sound/pci/ymfpci/ymfpci_main.c | 2458 ++++++ sound/pcmcia/Kconfig | 33 + sound/pcmcia/Makefile | 6 + sound/pcmcia/pdaudiocf/Makefile | 8 + sound/pcmcia/pdaudiocf/pdaudiocf.c | 303 + sound/pcmcia/pdaudiocf/pdaudiocf.h | 141 + sound/pcmcia/pdaudiocf/pdaudiocf_core.c | 296 + sound/pcmcia/pdaudiocf/pdaudiocf_irq.c | 326 + sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c | 307 + sound/pcmcia/vx/Makefile | 8 + sound/pcmcia/vx/vxp_mixer.c | 151 + sound/pcmcia/vx/vxp_ops.c | 612 ++ sound/pcmcia/vx/vxpocket.c | 376 + sound/pcmcia/vx/vxpocket.h | 92 + sound/ppc/Kconfig | 52 + sound/ppc/Makefile | 10 + sound/ppc/awacs.c | 1147 +++ sound/ppc/awacs.h | 205 + sound/ppc/beep.c | 285 + sound/ppc/burgundy.c | 732 ++ sound/ppc/burgundy.h | 114 + sound/ppc/daca.c | 282 + sound/ppc/keywest.c | 175 + sound/ppc/pmac.c | 1405 +++ sound/ppc/pmac.h | 210 + sound/ppc/powermac.c | 198 + sound/ppc/snd_ps3.c | 1164 +++ sound/ppc/snd_ps3.h | 136 + sound/ppc/snd_ps3_reg.h | 891 ++ sound/ppc/tumbler.c | 1491 ++++ sound/ppc/tumbler_volume.h | 251 + sound/sh/Kconfig | 31 + sound/sh/Makefile | 10 + sound/sh/aica.c | 667 ++ sound/sh/aica.h | 81 + sound/sh/sh_dac_audio.c | 445 + sound/soc/Kconfig | 86 + sound/soc/Makefile | 61 + sound/soc/adi/Kconfig | 21 + sound/soc/adi/Makefile | 5 + sound/soc/adi/axi-i2s.c | 277 + sound/soc/adi/axi-spdif.c | 270 + sound/soc/amd/Kconfig | 21 + sound/soc/amd/Makefile | 7 + sound/soc/amd/acp-da7219-max98357a.c | 411 + sound/soc/amd/acp-pcm-dma.c | 1434 +++ sound/soc/amd/acp-rt5645.c | 199 + sound/soc/amd/acp.h | 206 + sound/soc/amd/include/acp_2_2_d.h | 609 ++ sound/soc/amd/include/acp_2_2_enum.h | 1068 +++ sound/soc/amd/include/acp_2_2_sh_mask.h | 2292 +++++ sound/soc/atmel/Kconfig | 102 + sound/soc/atmel/Makefile | 26 + sound/soc/atmel/atmel-classd.c | 668 ++ sound/soc/atmel/atmel-classd.h | 121 + sound/soc/atmel/atmel-i2s.c | 751 ++ sound/soc/atmel/atmel-pcm-dma.c | 139 + sound/soc/atmel/atmel-pcm-pdc.c | 416 + sound/soc/atmel/atmel-pcm.h | 112 + sound/soc/atmel/atmel-pdmic.c | 735 ++ sound/soc/atmel/atmel-pdmic.h | 81 + sound/soc/atmel/atmel_ssc_dai.c | 1084 +++ sound/soc/atmel/atmel_ssc_dai.h | 125 + sound/soc/atmel/atmel_wm8904.c | 197 + sound/soc/atmel/sam9g20_wm8731.c | 226 + sound/soc/atmel/sam9x5_wm8731.c | 205 + sound/soc/atmel/tse850-pcm5142.c | 455 + sound/soc/au1x/Kconfig | 64 + sound/soc/au1x/Makefile | 24 + sound/soc/au1x/ac97c.c | 347 + sound/soc/au1x/db1000.c | 56 + sound/soc/au1x/db1200.c | 197 + sound/soc/au1x/dbdma2.c | 365 + sound/soc/au1x/dma.c | 334 + sound/soc/au1x/i2sc.c | 323 + sound/soc/au1x/psc-ac97.c | 504 ++ sound/soc/au1x/psc-i2s.c | 421 + sound/soc/au1x/psc.h | 42 + sound/soc/bcm/Kconfig | 18 + sound/soc/bcm/Makefile | 10 + sound/soc/bcm/bcm2835-i2s.c | 942 ++ sound/soc/bcm/cygnus-pcm.c | 860 ++ sound/soc/bcm/cygnus-ssp.c | 1398 +++ sound/soc/bcm/cygnus-ssp.h | 139 + sound/soc/cirrus/Kconfig | 60 + sound/soc/cirrus/Makefile | 18 + sound/soc/cirrus/edb93xx.c | 126 + sound/soc/cirrus/ep93xx-ac97.c | 450 + sound/soc/cirrus/ep93xx-i2s.c | 528 ++ sound/soc/cirrus/ep93xx-pcm.c | 70 + sound/soc/cirrus/ep93xx-pcm.h | 22 + sound/soc/cirrus/simone.c | 87 + sound/soc/cirrus/snappercl15.c | 136 + sound/soc/codecs/88pm860x-codec.c | 1425 +++ sound/soc/codecs/88pm860x-codec.h | 96 + sound/soc/codecs/Kconfig | 1313 +++ sound/soc/codecs/Makefile | 517 ++ sound/soc/codecs/ab8500-codec.c | 2586 ++++++ sound/soc/codecs/ab8500-codec.h | 592 ++ sound/soc/codecs/ac97.c | 154 + sound/soc/codecs/ad1836.c | 413 + sound/soc/codecs/ad1836.h | 51 + sound/soc/codecs/ad193x-i2c.c | 49 + sound/soc/codecs/ad193x-spi.c | 54 + sound/soc/codecs/ad193x.c | 454 + sound/soc/codecs/ad193x.h | 99 + sound/soc/codecs/ad1980.c | 330 + sound/soc/codecs/ad73311.c | 86 + sound/soc/codecs/ad73311.h | 88 + sound/soc/codecs/adau-utils.c | 61 + sound/soc/codecs/adau-utils.h | 8 + sound/soc/codecs/adau1373.c | 1520 ++++ sound/soc/codecs/adau1373.h | 30 + sound/soc/codecs/adau1701.c | 922 ++ sound/soc/codecs/adau1701.h | 17 + sound/soc/codecs/adau1761-i2c.c | 71 + sound/soc/codecs/adau1761-spi.c | 88 + sound/soc/codecs/adau1761.c | 817 ++ sound/soc/codecs/adau1761.h | 23 + sound/soc/codecs/adau1781-i2c.c | 67 + sound/soc/codecs/adau1781-spi.c | 84 + sound/soc/codecs/adau1781.c | 509 ++ sound/soc/codecs/adau1781.h | 23 + sound/soc/codecs/adau17x1.c | 1032 +++ sound/soc/codecs/adau17x1.h | 137 + sound/soc/codecs/adau1977-i2c.c | 51 + sound/soc/codecs/adau1977-spi.c | 68 + sound/soc/codecs/adau1977.c | 1006 +++ sound/soc/codecs/adau1977.h | 37 + sound/soc/codecs/adau7002.c | 94 + sound/soc/codecs/adav801.c | 45 + sound/soc/codecs/adav803.c | 42 + sound/soc/codecs/adav80x.c | 883 ++ sound/soc/codecs/adav80x.h | 42 + sound/soc/codecs/ads117x.c | 100 + sound/soc/codecs/ak4104.c | 356 + sound/soc/codecs/ak4458.c | 662 ++ sound/soc/codecs/ak4458.h | 86 + sound/soc/codecs/ak4535.c | 453 + sound/soc/codecs/ak4535.h | 37 + sound/soc/codecs/ak4554.c | 97 + sound/soc/codecs/ak4613.c | 700 ++ sound/soc/codecs/ak4641.c | 645 ++ sound/soc/codecs/ak4642.c | 708 ++ sound/soc/codecs/ak4671.c | 673 ++ sound/soc/codecs/ak4671.h | 151 + sound/soc/codecs/ak5386.c | 212 + sound/soc/codecs/ak5558.c | 416 + sound/soc/codecs/ak5558.h | 52 + sound/soc/codecs/alc5623.c | 1095 +++ sound/soc/codecs/alc5623.h | 161 + sound/soc/codecs/alc5632.c | 1192 +++ sound/soc/codecs/alc5632.h | 252 + sound/soc/codecs/arizona.c | 2863 ++++++ sound/soc/codecs/arizona.h | 357 + sound/soc/codecs/bd28623.c | 242 + sound/soc/codecs/bt-sco.c | 122 + sound/soc/codecs/cpcap.c | 1564 ++++ sound/soc/codecs/cq93vc.c | 168 + sound/soc/codecs/cs35l32.c | 586 ++ sound/soc/codecs/cs35l32.h | 93 + sound/soc/codecs/cs35l33.c | 1297 +++ sound/soc/codecs/cs35l33.h | 221 + sound/soc/codecs/cs35l34.c | 1242 +++ sound/soc/codecs/cs35l34.h | 269 + sound/soc/codecs/cs35l35.c | 1674 ++++ sound/soc/codecs/cs35l35.h | 300 + sound/soc/codecs/cs4265.c | 651 ++ sound/soc/codecs/cs4265.h | 64 + sound/soc/codecs/cs4270.c | 754 ++ sound/soc/codecs/cs4271-i2c.c | 54 + sound/soc/codecs/cs4271-spi.c | 47 + sound/soc/codecs/cs4271.c | 730 ++ sound/soc/codecs/cs4271.h | 12 + sound/soc/codecs/cs42l42.c | 1965 +++++ sound/soc/codecs/cs42l42.h | 777 ++ sound/soc/codecs/cs42l51-i2c.c | 51 + sound/soc/codecs/cs42l51.c | 575 ++ sound/soc/codecs/cs42l51.h | 168 + sound/soc/codecs/cs42l52.c | 1240 +++ sound/soc/codecs/cs42l52.h | 274 + sound/soc/codecs/cs42l56.c | 1367 +++ sound/soc/codecs/cs42l56.h | 177 + sound/soc/codecs/cs42l73.c | 1401 +++ sound/soc/codecs/cs42l73.h | 226 + sound/soc/codecs/cs42xx8-i2c.c | 63 + sound/soc/codecs/cs42xx8.c | 602 ++ sound/soc/codecs/cs42xx8.h | 239 + sound/soc/codecs/cs43130.c | 2707 ++++++ sound/soc/codecs/cs43130.h | 546 ++ sound/soc/codecs/cs4349.c | 395 + sound/soc/codecs/cs4349.h | 136 + sound/soc/codecs/cs47l24.c | 1355 +++ sound/soc/codecs/cs47l24.h | 23 + sound/soc/codecs/cs53l30.c | 1137 +++ sound/soc/codecs/cs53l30.h | 459 + sound/soc/codecs/cx20442.c | 447 + sound/soc/codecs/cx20442.h | 18 + sound/soc/codecs/da7210.c | 1361 +++ sound/soc/codecs/da7213.c | 1900 ++++ sound/soc/codecs/da7213.h | 537 ++ sound/soc/codecs/da7218.c | 3326 +++++++ sound/soc/codecs/da7218.h | 1415 +++ sound/soc/codecs/da7219-aad.c | 956 ++ sound/soc/codecs/da7219-aad.h | 219 + sound/soc/codecs/da7219.c | 2288 +++++ sound/soc/codecs/da7219.h | 840 ++ sound/soc/codecs/da732x.c | 1583 ++++ sound/soc/codecs/da732x.h | 130 + sound/soc/codecs/da732x_reg.h | 654 ++ sound/soc/codecs/da9055.c | 1545 ++++ sound/soc/codecs/dmic.c | 164 + sound/soc/codecs/es7134.c | 330 + sound/soc/codecs/es7241.c | 322 + sound/soc/codecs/es8316.c | 627 ++ sound/soc/codecs/es8316.h | 129 + sound/soc/codecs/es8328-i2c.c | 55 + sound/soc/codecs/es8328-spi.c | 42 + sound/soc/codecs/es8328.c | 887 ++ sound/soc/codecs/es8328.h | 290 + sound/soc/codecs/gtm601.c | 91 + sound/soc/codecs/hdac_hdmi.c | 2312 +++++ sound/soc/codecs/hdac_hdmi.h | 10 + sound/soc/codecs/hdmi-codec.c | 826 ++ sound/soc/codecs/ics43432.c | 74 + sound/soc/codecs/inno_rk3036.c | 492 ++ sound/soc/codecs/inno_rk3036.h | 124 + sound/soc/codecs/isabelle.c | 1158 +++ sound/soc/codecs/isabelle.h | 143 + sound/soc/codecs/jz4740.c | 368 + sound/soc/codecs/l3.c | 138 + sound/soc/codecs/lm4857.c | 154 + sound/soc/codecs/lm49453.c | 1470 ++++ sound/soc/codecs/lm49453.h | 380 + sound/soc/codecs/max9759.c | 208 + sound/soc/codecs/max9768.c | 228 + sound/soc/codecs/max98088.c | 1757 ++++ sound/soc/codecs/max98088.h | 206 + sound/soc/codecs/max98090.c | 2717 ++++++ sound/soc/codecs/max98090.h | 1551 ++++ sound/soc/codecs/max98095.c | 2173 +++++ sound/soc/codecs/max98095.h | 321 + sound/soc/codecs/max98357a.c | 150 + sound/soc/codecs/max98371.c | 438 + sound/soc/codecs/max98371.h | 66 + sound/soc/codecs/max98373.c | 973 ++ sound/soc/codecs/max98373.h | 213 + sound/soc/codecs/max9850.c | 360 + sound/soc/codecs/max9850.h | 38 + sound/soc/codecs/max98504.c | 383 + sound/soc/codecs/max98504.h | 59 + sound/soc/codecs/max9860.c | 745 ++ sound/soc/codecs/max9860.h | 154 + sound/soc/codecs/max9867.c | 557 ++ sound/soc/codecs/max9867.h | 82 + sound/soc/codecs/max9877.c | 177 + sound/soc/codecs/max9877.h | 35 + sound/soc/codecs/max98925.c | 652 ++ sound/soc/codecs/max98925.h | 832 ++ sound/soc/codecs/max98926.c | 604 ++ sound/soc/codecs/max98926.h | 848 ++ sound/soc/codecs/max98927.c | 966 ++ sound/soc/codecs/max98927.h | 275 + sound/soc/codecs/mc13783.c | 810 ++ sound/soc/codecs/mc13783.h | 28 + sound/soc/codecs/ml26124.c | 607 ++ sound/soc/codecs/ml26124.h | 184 + sound/soc/codecs/msm8916-wcd-analog.c | 1275 +++ sound/soc/codecs/msm8916-wcd-digital.c | 971 ++ sound/soc/codecs/mt6351.c | 1505 ++++ sound/soc/codecs/mt6351.h | 105 + sound/soc/codecs/nau8540.c | 887 ++ sound/soc/codecs/nau8540.h | 242 + sound/soc/codecs/nau8810.c | 874 ++ sound/soc/codecs/nau8810.h | 281 + sound/soc/codecs/nau8824.c | 1999 +++++ sound/soc/codecs/nau8824.h | 478 + sound/soc/codecs/nau8825.c | 2667 ++++++ sound/soc/codecs/nau8825.h | 487 + sound/soc/codecs/pcm1681.c | 347 + sound/soc/codecs/pcm1789-i2c.c | 62 + sound/soc/codecs/pcm1789.c | 273 + sound/soc/codecs/pcm1789.h | 17 + sound/soc/codecs/pcm179x-i2c.c | 67 + sound/soc/codecs/pcm179x-spi.c | 66 + sound/soc/codecs/pcm179x.c | 240 + sound/soc/codecs/pcm179x.h | 27 + sound/soc/codecs/pcm186x-i2c.c | 61 + sound/soc/codecs/pcm186x-spi.c | 61 + sound/soc/codecs/pcm186x.c | 712 ++ sound/soc/codecs/pcm186x.h | 219 + sound/soc/codecs/pcm3008.c | 168 + sound/soc/codecs/pcm3008.h | 22 + sound/soc/codecs/pcm3168a-i2c.c | 66 + sound/soc/codecs/pcm3168a-spi.c | 65 + sound/soc/codecs/pcm3168a.c | 773 ++ sound/soc/codecs/pcm3168a.h | 100 + sound/soc/codecs/pcm5102a.c | 66 + sound/soc/codecs/pcm512x-i2c.c | 94 + sound/soc/codecs/pcm512x-spi.c | 76 + sound/soc/codecs/pcm512x.c | 1606 ++++ sound/soc/codecs/pcm512x.h | 270 + sound/soc/codecs/rl6231.c | 244 + sound/soc/codecs/rl6231.h | 35 + sound/soc/codecs/rl6347a.c | 111 + sound/soc/codecs/rl6347a.h | 34 + sound/soc/codecs/rt1305.c | 1184 +++ sound/soc/codecs/rt1305.h | 276 + sound/soc/codecs/rt274.c | 1231 +++ sound/soc/codecs/rt274.h | 217 + sound/soc/codecs/rt286.c | 1280 +++ sound/soc/codecs/rt286.h | 205 + sound/soc/codecs/rt298.c | 1322 +++ sound/soc/codecs/rt298.h | 216 + sound/soc/codecs/rt5514-spi.c | 517 ++ sound/soc/codecs/rt5514-spi.h | 40 + sound/soc/codecs/rt5514.c | 1341 +++ sound/soc/codecs/rt5514.h | 289 + sound/soc/codecs/rt5616.c | 1422 +++ sound/soc/codecs/rt5616.h | 1819 ++++ sound/soc/codecs/rt5631.c | 1742 ++++ sound/soc/codecs/rt5631.h | 702 ++ sound/soc/codecs/rt5640.c | 2871 ++++++ sound/soc/codecs/rt5640.h | 2163 +++++ sound/soc/codecs/rt5645.c | 4151 +++++++++ sound/soc/codecs/rt5645.h | 2209 +++++ sound/soc/codecs/rt5651.c | 2234 +++++ sound/soc/codecs/rt5651.h | 2103 +++++ sound/soc/codecs/rt5659.c | 4393 +++++++++ sound/soc/codecs/rt5659.h | 1824 ++++ sound/soc/codecs/rt5660.c | 1350 +++ sound/soc/codecs/rt5660.h | 850 ++ sound/soc/codecs/rt5663.c | 3687 ++++++++ sound/soc/codecs/rt5663.h | 1131 +++ sound/soc/codecs/rt5665.c | 4983 +++++++++++ sound/soc/codecs/rt5665.h | 2008 +++++ sound/soc/codecs/rt5668.c | 2641 ++++++ sound/soc/codecs/rt5668.h | 1318 +++ sound/soc/codecs/rt5670-dsp.h | 54 + sound/soc/codecs/rt5670.c | 3250 +++++++ sound/soc/codecs/rt5670.h | 2019 +++++ sound/soc/codecs/rt5677-spi.c | 237 + sound/soc/codecs/rt5677-spi.h | 19 + sound/soc/codecs/rt5677.c | 5282 +++++++++++ sound/soc/codecs/rt5677.h | 1822 ++++ sound/soc/codecs/rt5682.c | 2694 ++++++ sound/soc/codecs/rt5682.h | 1324 +++ sound/soc/codecs/sgtl5000.c | 1806 ++++ sound/soc/codecs/sgtl5000.h | 407 + sound/soc/codecs/si476x.c | 273 + sound/soc/codecs/sigmadsp-i2c.c | 97 + sound/soc/codecs/sigmadsp-regmap.c | 60 + sound/soc/codecs/sigmadsp.c | 813 ++ sound/soc/codecs/sigmadsp.h | 66 + sound/soc/codecs/simple-amplifier.c | 122 + sound/soc/codecs/sirf-audio-codec.c | 581 ++ sound/soc/codecs/sirf-audio-codec.h | 125 + sound/soc/codecs/spdif_receiver.c | 90 + sound/soc/codecs/spdif_transmitter.c | 91 + sound/soc/codecs/ssm2305.c | 104 + sound/soc/codecs/ssm2518.c | 825 ++ sound/soc/codecs/ssm2518.h | 20 + sound/soc/codecs/ssm2602-i2c.c | 58 + sound/soc/codecs/ssm2602-spi.c | 40 + sound/soc/codecs/ssm2602.c | 659 ++ sound/soc/codecs/ssm2602.h | 139 + sound/soc/codecs/ssm4567.c | 512 ++ sound/soc/codecs/sta32x.c | 1156 +++ sound/soc/codecs/sta32x.h | 211 + sound/soc/codecs/sta350.c | 1279 +++ sound/soc/codecs/sta350.h | 238 + sound/soc/codecs/sta529.c | 391 + sound/soc/codecs/stac9766.c | 342 + sound/soc/codecs/sti-sas.c | 486 + sound/soc/codecs/tas2552.c | 780 ++ sound/soc/codecs/tas2552.h | 146 + sound/soc/codecs/tas5086.c | 1014 +++ sound/soc/codecs/tas571x.c | 928 ++ sound/soc/codecs/tas571x.h | 111 + sound/soc/codecs/tas5720.c | 656 ++ sound/soc/codecs/tas5720.h | 121 + sound/soc/codecs/tas6424.c | 776 ++ sound/soc/codecs/tas6424.h | 148 + sound/soc/codecs/tda7419.c | 654 ++ sound/soc/codecs/tfa9879.c | 323 + sound/soc/codecs/tfa9879.h | 197 + sound/soc/codecs/tlv320aic23-i2c.c | 60 + sound/soc/codecs/tlv320aic23-spi.c | 48 + sound/soc/codecs/tlv320aic23.c | 620 ++ sound/soc/codecs/tlv320aic23.h | 125 + sound/soc/codecs/tlv320aic26.c | 378 + sound/soc/codecs/tlv320aic26.h | 91 + sound/soc/codecs/tlv320aic31xx.c | 1500 ++++ sound/soc/codecs/tlv320aic31xx.h | 204 + sound/soc/codecs/tlv320aic32x4-i2c.c | 76 + sound/soc/codecs/tlv320aic32x4-spi.c | 78 + sound/soc/codecs/tlv320aic32x4.c | 1215 +++ sound/soc/codecs/tlv320aic32x4.h | 207 + sound/soc/codecs/tlv320aic3x.c | 1935 ++++ sound/soc/codecs/tlv320aic3x.h | 291 + sound/soc/codecs/tlv320dac33.c | 1592 ++++ sound/soc/codecs/tlv320dac33.h | 264 + sound/soc/codecs/tpa6130a2.c | 342 + sound/soc/codecs/tpa6130a2.h | 60 + sound/soc/codecs/ts3a227e.c | 400 + sound/soc/codecs/ts3a227e.h | 17 + sound/soc/codecs/tscs42xx.c | 1516 ++++ sound/soc/codecs/tscs42xx.h | 2701 ++++++ sound/soc/codecs/tscs454.c | 3497 ++++++++ sound/soc/codecs/tscs454.h | 2323 +++++ sound/soc/codecs/twl4030.c | 2230 +++++ sound/soc/codecs/twl6040.c | 1192 +++ sound/soc/codecs/twl6040.h | 44 + sound/soc/codecs/uda134x.c | 590 ++ sound/soc/codecs/uda134x.h | 33 + sound/soc/codecs/uda1380.c | 814 ++ sound/soc/codecs/uda1380.h | 75 + sound/soc/codecs/wl1273.c | 522 ++ sound/soc/codecs/wl1273.h | 30 + sound/soc/codecs/wm0010.c | 1005 +++ sound/soc/codecs/wm1250-ev1.c | 264 + sound/soc/codecs/wm2000.c | 949 ++ sound/soc/codecs/wm2000.h | 74 + sound/soc/codecs/wm2200.c | 2512 ++++++ sound/soc/codecs/wm2200.h | 3674 ++++++++ sound/soc/codecs/wm5100-tables.c | 1485 ++++ sound/soc/codecs/wm5100.c | 2728 ++++++ sound/soc/codecs/wm5100.h | 5315 +++++++++++ sound/soc/codecs/wm5102.c | 2164 +++++ sound/soc/codecs/wm5102.h | 23 + sound/soc/codecs/wm5110.c | 2530 ++++++ sound/soc/codecs/wm5110.h | 23 + sound/soc/codecs/wm8350.c | 1639 ++++ sound/soc/codecs/wm8350.h | 29 + sound/soc/codecs/wm8400.c | 1363 +++ sound/soc/codecs/wm8400.h | 59 + sound/soc/codecs/wm8510.c | 724 ++ sound/soc/codecs/wm8510.h | 102 + sound/soc/codecs/wm8523.c | 544 ++ sound/soc/codecs/wm8523.h | 157 + sound/soc/codecs/wm8524.c | 255 + sound/soc/codecs/wm8580.c | 1066 +++ sound/soc/codecs/wm8580.h | 35 + sound/soc/codecs/wm8711.c | 510 ++ sound/soc/codecs/wm8711.h | 39 + sound/soc/codecs/wm8727.c | 83 + sound/soc/codecs/wm8728.c | 351 + sound/soc/codecs/wm8728.h | 21 + sound/soc/codecs/wm8731.c | 847 ++ sound/soc/codecs/wm8731.h | 39 + sound/soc/codecs/wm8737.c | 738 ++ sound/soc/codecs/wm8737.h | 322 + sound/soc/codecs/wm8741.c | 693 ++ sound/soc/codecs/wm8741.h | 221 + sound/soc/codecs/wm8750.c | 859 ++ sound/soc/codecs/wm8750.h | 60 + sound/soc/codecs/wm8753.c | 1635 ++++ sound/soc/codecs/wm8753.h | 115 + sound/soc/codecs/wm8770.c | 715 ++ sound/soc/codecs/wm8770.h | 51 + sound/soc/codecs/wm8776.c | 569 ++ sound/soc/codecs/wm8776.h | 48 + sound/soc/codecs/wm8782.c | 90 + sound/soc/codecs/wm8804-i2c.c | 77 + sound/soc/codecs/wm8804-spi.c | 56 + sound/soc/codecs/wm8804.c | 730 ++ sound/soc/codecs/wm8804.h | 73 + sound/soc/codecs/wm8900.c | 1356 +++ sound/soc/codecs/wm8900.h | 55 + sound/soc/codecs/wm8903.c | 2230 +++++ sound/soc/codecs/wm8903.h | 1225 +++ sound/soc/codecs/wm8904.c | 2304 +++++ sound/soc/codecs/wm8904.h | 1592 ++++ sound/soc/codecs/wm8940.c | 796 ++ sound/soc/codecs/wm8940.h | 102 + sound/soc/codecs/wm8955.c | 1018 +++ sound/soc/codecs/wm8955.h | 486 + sound/soc/codecs/wm8958-dsp2.c | 1039 +++ sound/soc/codecs/wm8960.c | 1504 ++++ sound/soc/codecs/wm8960.h | 114 + sound/soc/codecs/wm8961.c | 990 +++ sound/soc/codecs/wm8961.h | 863 ++ sound/soc/codecs/wm8962.c | 3895 ++++++++ sound/soc/codecs/wm8962.h | 3784 ++++++++ sound/soc/codecs/wm8971.c | 719 ++ sound/soc/codecs/wm8971.h | 56 + sound/soc/codecs/wm8974.c | 743 ++ sound/soc/codecs/wm8974.h | 86 + sound/soc/codecs/wm8978.c | 1087 +++ sound/soc/codecs/wm8978.h | 85 + sound/soc/codecs/wm8983.c | 1115 +++ sound/soc/codecs/wm8983.h | 1029 +++ sound/soc/codecs/wm8985.c | 1250 +++ sound/soc/codecs/wm8985.h | 1083 +++ sound/soc/codecs/wm8988.c | 952 ++ sound/soc/codecs/wm8988.h | 57 + sound/soc/codecs/wm8990.c | 1361 +++ sound/soc/codecs/wm8990.h | 826 ++ sound/soc/codecs/wm8991.c | 1347 +++ sound/soc/codecs/wm8991.h | 819 ++ sound/soc/codecs/wm8993.c | 1759 ++++ sound/soc/codecs/wm8993.h | 2139 +++++ sound/soc/codecs/wm8994.c | 4543 ++++++++++ sound/soc/codecs/wm8994.h | 168 + sound/soc/codecs/wm8995.c | 2331 +++++ sound/soc/codecs/wm8995.h | 4266 +++++++++ sound/soc/codecs/wm8996.c | 3107 +++++++ sound/soc/codecs/wm8996.h | 3721 ++++++++ sound/soc/codecs/wm8997.c | 1216 +++ sound/soc/codecs/wm8997.h | 23 + sound/soc/codecs/wm8998.c | 1427 +++ sound/soc/codecs/wm8998.h | 23 + sound/soc/codecs/wm9081.c | 1388 +++ sound/soc/codecs/wm9081.h | 784 ++ sound/soc/codecs/wm9090.c | 643 ++ sound/soc/codecs/wm9090.h | 713 ++ sound/soc/codecs/wm9705.c | 405 + sound/soc/codecs/wm9712.c | 731 ++ sound/soc/codecs/wm9713.c | 1296 +++ sound/soc/codecs/wm9713.h | 45 + sound/soc/codecs/wm_adsp.c | 3880 ++++++++ sound/soc/codecs/wm_adsp.h | 171 + sound/soc/codecs/wm_hubs.c | 1313 +++ sound/soc/codecs/wm_hubs.h | 75 + sound/soc/codecs/wmfw.h | 172 + sound/soc/codecs/zx_aud96p22.c | 404 + sound/soc/davinci/Kconfig | 106 + sound/soc/davinci/Makefile | 16 + sound/soc/davinci/davinci-evm.c | 511 ++ sound/soc/davinci/davinci-i2s.c | 785 ++ sound/soc/davinci/davinci-i2s.h | 20 + sound/soc/davinci/davinci-mcasp.c | 2122 +++++ sound/soc/davinci/davinci-mcasp.h | 314 + sound/soc/davinci/davinci-vcif.c | 259 + sound/soc/davinci/edma-pcm.c | 59 + sound/soc/davinci/edma-pcm.h | 32 + sound/soc/dwc/Kconfig | 19 + sound/soc/dwc/Makefile | 5 + sound/soc/dwc/dwc-i2s.c | 761 ++ sound/soc/dwc/dwc-pcm.c | 282 + sound/soc/dwc/local.h | 135 + sound/soc/fsl/Kconfig | 293 + sound/soc/fsl/Makefile | 68 + sound/soc/fsl/efika-audio-fabric.c | 89 + sound/soc/fsl/eukrea-tlv320.c | 234 + sound/soc/fsl/fsl-asoc-card.c | 723 ++ sound/soc/fsl/fsl_asrc.c | 1043 +++ sound/soc/fsl/fsl_asrc.h | 465 + sound/soc/fsl/fsl_asrc_dma.c | 390 + sound/soc/fsl/fsl_dma.c | 981 +++ sound/soc/fsl/fsl_dma.h | 129 + sound/soc/fsl/fsl_esai.c | 1013 +++ sound/soc/fsl/fsl_esai.h | 351 + sound/soc/fsl/fsl_sai.c | 965 ++ sound/soc/fsl/fsl_sai.h | 153 + sound/soc/fsl/fsl_spdif.c | 1382 +++ sound/soc/fsl/fsl_spdif.h | 196 + sound/soc/fsl/fsl_ssi.c | 1712 ++++ sound/soc/fsl/fsl_ssi.h | 326 + sound/soc/fsl/fsl_ssi_dbg.c | 160 + sound/soc/fsl/fsl_utils.c | 88 + sound/soc/fsl/fsl_utils.h | 22 + sound/soc/fsl/imx-audmux.c | 378 + sound/soc/fsl/imx-audmux.h | 12 + sound/soc/fsl/imx-es8328.c | 231 + sound/soc/fsl/imx-mc13783.c | 168 + sound/soc/fsl/imx-pcm-dma.c | 57 + sound/soc/fsl/imx-pcm-fiq.c | 391 + sound/soc/fsl/imx-pcm.h | 71 + sound/soc/fsl/imx-sgtl5000.c | 209 + sound/soc/fsl/imx-spdif.c | 101 + sound/soc/fsl/imx-ssi.c | 661 ++ sound/soc/fsl/imx-ssi.h | 218 + sound/soc/fsl/mpc5200_dma.c | 514 ++ sound/soc/fsl/mpc5200_dma.h | 88 + sound/soc/fsl/mpc5200_psc_ac97.c | 350 + sound/soc/fsl/mpc5200_psc_i2s.c | 241 + sound/soc/fsl/mpc8610_hpcd.c | 432 + sound/soc/fsl/mx27vis-aic32x4.c | 234 + sound/soc/fsl/p1022_ds.c | 441 + sound/soc/fsl/p1022_rdk.c | 391 + sound/soc/fsl/pcm030-audio-fabric.c | 142 + sound/soc/fsl/phycore-ac97.c | 125 + sound/soc/fsl/wm1133-ev1.c | 292 + sound/soc/generic/Kconfig | 33 + sound/soc/generic/Makefile | 12 + sound/soc/generic/audio-graph-card.c | 385 + sound/soc/generic/audio-graph-scu-card.c | 412 + sound/soc/generic/simple-card-utils.c | 481 + sound/soc/generic/simple-card.c | 478 + sound/soc/generic/simple-scu-card.c | 312 + sound/soc/hisilicon/Kconfig | 5 + sound/soc/hisilicon/Makefile | 1 + sound/soc/hisilicon/hi6210-i2s.c | 620 ++ sound/soc/hisilicon/hi6210-i2s.h | 276 + sound/soc/img/Kconfig | 52 + sound/soc/img/Makefile | 8 + sound/soc/img/img-i2s-in.c | 625 ++ sound/soc/img/img-i2s-out.c | 631 ++ sound/soc/img/img-parallel-out.c | 333 + sound/soc/img/img-spdif-in.c | 894 ++ sound/soc/img/img-spdif-out.c | 488 + sound/soc/img/pistachio-internal-dac.c | 289 + sound/soc/intel/Kconfig | 129 + sound/soc/intel/Makefile | 12 + sound/soc/intel/atom/Makefile | 9 + sound/soc/intel/atom/sst-atom-controls.c | 1564 ++++ sound/soc/intel/atom/sst-atom-controls.h | 884 ++ sound/soc/intel/atom/sst-mfld-dsp.h | 533 ++ sound/soc/intel/atom/sst-mfld-platform-compress.c | 273 + sound/soc/intel/atom/sst-mfld-platform-pcm.c | 843 ++ sound/soc/intel/atom/sst-mfld-platform.h | 186 + sound/soc/intel/atom/sst/Makefile | 8 + sound/soc/intel/atom/sst/sst.c | 584 ++ sound/soc/intel/atom/sst/sst.h | 534 ++ sound/soc/intel/atom/sst/sst_acpi.c | 423 + sound/soc/intel/atom/sst/sst_drv_interface.c | 728 ++ sound/soc/intel/atom/sst/sst_ipc.c | 384 + sound/soc/intel/atom/sst/sst_loader.c | 462 + sound/soc/intel/atom/sst/sst_pci.c | 209 + sound/soc/intel/atom/sst/sst_pvt.c | 415 + sound/soc/intel/atom/sst/sst_stream.c | 473 + sound/soc/intel/baytrail/Makefile | 4 + sound/soc/intel/baytrail/sst-baytrail-dsp.c | 366 + sound/soc/intel/baytrail/sst-baytrail-ipc.c | 782 ++ sound/soc/intel/baytrail/sst-baytrail-ipc.h | 73 + sound/soc/intel/baytrail/sst-baytrail-pcm.c | 491 ++ sound/soc/intel/boards/Kconfig | 300 + sound/soc/intel/boards/Makefile | 48 + sound/soc/intel/boards/bdw-rt5677.c | 371 + sound/soc/intel/boards/broadwell.c | 287 + sound/soc/intel/boards/bxt_da7219_max98357a.c | 616 ++ sound/soc/intel/boards/bxt_rt298.c | 634 ++ sound/soc/intel/boards/byt-max98090.c | 188 + sound/soc/intel/boards/byt-rt5640.c | 230 + sound/soc/intel/boards/bytcht_da7213.c | 274 + sound/soc/intel/boards/bytcht_es8316.c | 301 + sound/soc/intel/boards/bytcht_nocodec.c | 200 + sound/soc/intel/boards/bytcr_rt5640.c | 1456 +++ sound/soc/intel/boards/bytcr_rt5651.c | 1090 +++ sound/soc/intel/boards/cht_bsw_max98090_ti.c | 519 ++ sound/soc/intel/boards/cht_bsw_nau8824.c | 282 + sound/soc/intel/boards/cht_bsw_rt5645.c | 701 ++ sound/soc/intel/boards/cht_bsw_rt5672.c | 457 + sound/soc/intel/boards/glk_rt5682_max98357a.c | 643 ++ sound/soc/intel/boards/haswell.c | 211 + sound/soc/intel/boards/kbl_da7219_max98357a.c | 615 ++ sound/soc/intel/boards/kbl_rt5663_max98927.c | 1054 +++ .../soc/intel/boards/kbl_rt5663_rt5514_max98927.c | 694 ++ sound/soc/intel/boards/skl_nau88l25_max98357a.c | 685 ++ sound/soc/intel/boards/skl_nau88l25_ssm4567.c | 742 ++ sound/soc/intel/boards/skl_rt286.c | 565 ++ sound/soc/intel/common/Makefile | 15 + sound/soc/intel/common/soc-acpi-intel-bxt-match.c | 59 + sound/soc/intel/common/soc-acpi-intel-byt-match.c | 196 + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 203 + sound/soc/intel/common/soc-acpi-intel-cnl-match.c | 32 + sound/soc/intel/common/soc-acpi-intel-glk-match.c | 41 + .../intel/common/soc-acpi-intel-hsw-bdw-match.c | 64 + sound/soc/intel/common/soc-acpi-intel-kbl-match.c | 91 + sound/soc/intel/common/soc-acpi-intel-skl-match.c | 47 + sound/soc/intel/common/sst-acpi.c | 244 + sound/soc/intel/common/sst-dsp-priv.h | 392 + sound/soc/intel/common/sst-dsp.c | 424 + sound/soc/intel/common/sst-dsp.h | 293 + sound/soc/intel/common/sst-firmware.c | 1282 +++ sound/soc/intel/common/sst-ipc.c | 329 + sound/soc/intel/common/sst-ipc.h | 96 + sound/soc/intel/haswell/Makefile | 4 + sound/soc/intel/haswell/sst-haswell-dsp.c | 714 ++ sound/soc/intel/haswell/sst-haswell-ipc.c | 2225 +++++ sound/soc/intel/haswell/sst-haswell-ipc.h | 536 ++ sound/soc/intel/haswell/sst-haswell-pcm.c | 1404 +++ sound/soc/intel/skylake/Makefile | 21 + sound/soc/intel/skylake/bxt-sst.c | 642 ++ sound/soc/intel/skylake/cnl-sst-dsp.c | 274 + sound/soc/intel/skylake/cnl-sst-dsp.h | 112 + sound/soc/intel/skylake/cnl-sst.c | 498 ++ sound/soc/intel/skylake/skl-debug.c | 263 + sound/soc/intel/skylake/skl-i2s.h | 95 + sound/soc/intel/skylake/skl-messages.c | 1388 +++ sound/soc/intel/skylake/skl-nhlt.c | 451 + sound/soc/intel/skylake/skl-nhlt.h | 128 + sound/soc/intel/skylake/skl-pcm.c | 1487 ++++ sound/soc/intel/skylake/skl-ssp-clk.c | 429 + sound/soc/intel/skylake/skl-ssp-clk.h | 117 + sound/soc/intel/skylake/skl-sst-cldma.c | 375 + sound/soc/intel/skylake/skl-sst-cldma.h | 251 + sound/soc/intel/skylake/skl-sst-dsp.c | 470 + sound/soc/intel/skylake/skl-sst-dsp.h | 263 + sound/soc/intel/skylake/skl-sst-ipc.c | 1033 +++ sound/soc/intel/skylake/skl-sst-ipc.h | 224 + sound/soc/intel/skylake/skl-sst-utils.c | 448 + sound/soc/intel/skylake/skl-sst.c | 611 ++ sound/soc/intel/skylake/skl-topology.c | 3761 ++++++++ sound/soc/intel/skylake/skl-topology.h | 520 ++ sound/soc/intel/skylake/skl.c | 1049 +++ sound/soc/intel/skylake/skl.h | 178 + sound/soc/jz4740/Kconfig | 29 + sound/soc/jz4740/Makefile | 11 + sound/soc/jz4740/jz4740-i2s.c | 567 ++ sound/soc/jz4740/jz4740-i2s.h | 16 + sound/soc/jz4740/qi_lb60.c | 112 + sound/soc/kirkwood/Kconfig | 17 + sound/soc/kirkwood/Makefile | 7 + sound/soc/kirkwood/armada-370-db.c | 148 + sound/soc/kirkwood/kirkwood-dma.c | 326 + sound/soc/kirkwood/kirkwood-i2s.c | 654 ++ sound/soc/kirkwood/kirkwood.h | 150 + sound/soc/mediatek/Kconfig | 107 + sound/soc/mediatek/Makefile | 5 + sound/soc/mediatek/common/Makefile | 4 + sound/soc/mediatek/common/mtk-afe-fe-dai.c | 372 + sound/soc/mediatek/common/mtk-afe-fe-dai.h | 37 + .../soc/mediatek/common/mtk-afe-platform-driver.c | 152 + .../soc/mediatek/common/mtk-afe-platform-driver.h | 28 + sound/soc/mediatek/common/mtk-base-afe.h | 118 + sound/soc/mediatek/mt2701/Makefile | 8 + sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c | 298 + sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h | 34 + sound/soc/mediatek/mt2701/mt2701-afe-common.h | 119 + sound/soc/mediatek/mt2701/mt2701-afe-pcm.c | 1506 ++++ sound/soc/mediatek/mt2701/mt2701-cs42448.c | 403 + sound/soc/mediatek/mt2701/mt2701-reg.h | 141 + sound/soc/mediatek/mt2701/mt2701-wm8960.c | 173 + sound/soc/mediatek/mt6797/Makefile | 14 + sound/soc/mediatek/mt6797/mt6797-afe-clk.c | 123 + sound/soc/mediatek/mt6797/mt6797-afe-clk.h | 17 + sound/soc/mediatek/mt6797/mt6797-afe-common.h | 59 + sound/soc/mediatek/mt6797/mt6797-afe-pcm.c | 937 ++ sound/soc/mediatek/mt6797/mt6797-dai-adda.c | 402 + sound/soc/mediatek/mt6797/mt6797-dai-hostless.c | 118 + sound/soc/mediatek/mt6797/mt6797-dai-pcm.c | 317 + sound/soc/mediatek/mt6797/mt6797-interconnection.h | 33 + sound/soc/mediatek/mt6797/mt6797-mt6351.c | 223 + sound/soc/mediatek/mt6797/mt6797-reg.h | 1015 +++ sound/soc/mediatek/mt8173/Makefile | 8 + sound/soc/mediatek/mt8173/mt8173-afe-common.h | 65 + sound/soc/mediatek/mt8173/mt8173-afe-pcm.c | 1222 +++ sound/soc/mediatek/mt8173/mt8173-max98090.c | 205 + sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c | 249 + sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c | 306 + sound/soc/mediatek/mt8173/mt8173-rt5650.c | 333 + sound/soc/meson/Kconfig | 65 + sound/soc/meson/Makefile | 21 + sound/soc/meson/axg-card.c | 671 ++ sound/soc/meson/axg-fifo.c | 349 + sound/soc/meson/axg-fifo.h | 80 + sound/soc/meson/axg-frddr.c | 141 + sound/soc/meson/axg-spdifout.c | 456 + sound/soc/meson/axg-tdm-formatter.c | 381 + sound/soc/meson/axg-tdm-formatter.h | 39 + sound/soc/meson/axg-tdm-interface.c | 562 ++ sound/soc/meson/axg-tdm.h | 78 + sound/soc/meson/axg-tdmin.c | 228 + sound/soc/meson/axg-tdmout.c | 258 + sound/soc/meson/axg-toddr.c | 199 + sound/soc/mxs/Kconfig | 21 + sound/soc/mxs/Makefile | 11 + sound/soc/mxs/mxs-pcm.c | 59 + sound/soc/mxs/mxs-pcm.h | 24 + sound/soc/mxs/mxs-saif.c | 872 ++ sound/soc/mxs/mxs-saif.h | 136 + sound/soc/mxs/mxs-sgtl5000.c | 205 + sound/soc/nuc900/Kconfig | 28 + sound/soc/nuc900/Makefile | 12 + sound/soc/nuc900/nuc900-ac97.c | 397 + sound/soc/nuc900/nuc900-audio.c | 75 + sound/soc/nuc900/nuc900-audio.h | 112 + sound/soc/nuc900/nuc900-pcm.c | 325 + sound/soc/omap/Kconfig | 129 + sound/soc/omap/Makefile | 32 + sound/soc/omap/am3517evm.c | 141 + sound/soc/omap/ams-delta.c | 594 ++ sound/soc/omap/mcbsp.c | 1104 +++ sound/soc/omap/mcbsp.h | 358 + sound/soc/omap/n810.c | 378 + sound/soc/omap/omap-abe-twl6040.c | 353 + sound/soc/omap/omap-dmic.c | 541 ++ sound/soc/omap/omap-dmic.h | 69 + sound/soc/omap/omap-hdmi-audio.c | 420 + sound/soc/omap/omap-mcbsp.c | 906 ++ sound/soc/omap/omap-mcbsp.h | 44 + sound/soc/omap/omap-mcpdm.c | 619 ++ sound/soc/omap/omap-mcpdm.h | 107 + sound/soc/omap/omap-twl4030.c | 353 + sound/soc/omap/omap3pandora.c | 315 + sound/soc/omap/osk5912.c | 187 + sound/soc/omap/rx51.c | 493 ++ sound/soc/omap/sdma-pcm.c | 74 + sound/soc/omap/sdma-pcm.h | 21 + sound/soc/pxa/Kconfig | 221 + sound/soc/pxa/Makefile | 54 + sound/soc/pxa/brownstone.c | 136 + sound/soc/pxa/corgi.c | 319 + sound/soc/pxa/e740_wm9705.c | 167 + sound/soc/pxa/e750_wm9705.c | 150 + sound/soc/pxa/e800_wm9712.c | 149 + sound/soc/pxa/em-x270.c | 93 + sound/soc/pxa/hx4700.c | 217 + sound/soc/pxa/imote2.c | 95 + sound/soc/pxa/magician.c | 432 + sound/soc/pxa/mioa701_wm9713.c | 209 + sound/soc/pxa/mmp-pcm.c | 255 + sound/soc/pxa/mmp-sspa.c | 483 + sound/soc/pxa/mmp-sspa.h | 92 + sound/soc/pxa/palm27x.c | 161 + sound/soc/pxa/poodle.c | 291 + sound/soc/pxa/pxa-ssp.c | 904 ++ sound/soc/pxa/pxa-ssp.h | 39 + sound/soc/pxa/pxa2xx-ac97.c | 300 + sound/soc/pxa/pxa2xx-i2s.c | 404 + sound/soc/pxa/pxa2xx-i2s.h | 15 + sound/soc/pxa/pxa2xx-pcm.c | 48 + sound/soc/pxa/raumfeld.c | 318 + sound/soc/pxa/spitz.c | 343 + sound/soc/pxa/tosa.c | 263 + sound/soc/pxa/ttc-dkb.c | 153 + sound/soc/pxa/z2.c | 220 + sound/soc/pxa/zylonite.c | 265 + sound/soc/qcom/Kconfig | 106 + sound/soc/qcom/Makefile | 27 + sound/soc/qcom/apq8016_sbc.c | 278 + sound/soc/qcom/apq8096.c | 100 + sound/soc/qcom/common.c | 121 + sound/soc/qcom/common.h | 11 + sound/soc/qcom/lpass-apq8016.c | 270 + sound/soc/qcom/lpass-cpu.c | 546 ++ sound/soc/qcom/lpass-ipq806x.c | 122 + sound/soc/qcom/lpass-lpaif-reg.h | 176 + sound/soc/qcom/lpass-platform.c | 608 ++ sound/soc/qcom/lpass.h | 105 + sound/soc/qcom/qdsp6/Makefile | 8 + sound/soc/qcom/qdsp6/q6adm.c | 634 ++ sound/soc/qcom/qdsp6/q6adm.h | 27 + sound/soc/qcom/qdsp6/q6afe-dai.c | 1456 +++ sound/soc/qcom/qdsp6/q6afe.c | 1504 ++++ sound/soc/qcom/qdsp6/q6afe.h | 211 + sound/soc/qcom/qdsp6/q6asm-dai.c | 607 ++ sound/soc/qcom/qdsp6/q6asm.c | 1387 +++ sound/soc/qcom/qdsp6/q6asm.h | 69 + sound/soc/qcom/qdsp6/q6core.c | 380 + sound/soc/qcom/qdsp6/q6core.h | 15 + sound/soc/qcom/qdsp6/q6dsp-common.c | 66 + sound/soc/qcom/qdsp6/q6dsp-common.h | 24 + sound/soc/qcom/qdsp6/q6dsp-errno.h | 51 + sound/soc/qcom/qdsp6/q6routing.c | 1045 +++ sound/soc/qcom/qdsp6/q6routing.h | 9 + sound/soc/qcom/sdm845.c | 286 + sound/soc/qcom/storm.c | 146 + sound/soc/rockchip/Kconfig | 78 + sound/soc/rockchip/Makefile | 20 + sound/soc/rockchip/rk3288_hdmi_analog.c | 300 + sound/soc/rockchip/rk3399_gru_sound.c | 576 ++ sound/soc/rockchip/rockchip_i2s.c | 736 ++ sound/soc/rockchip/rockchip_i2s.h | 249 + sound/soc/rockchip/rockchip_max98090.c | 251 + sound/soc/rockchip/rockchip_pcm.c | 47 + sound/soc/rockchip/rockchip_pcm.h | 14 + sound/soc/rockchip/rockchip_pdm.c | 520 ++ sound/soc/rockchip/rockchip_pdm.h | 83 + sound/soc/rockchip/rockchip_rt5645.c | 248 + sound/soc/rockchip/rockchip_spdif.c | 421 + sound/soc/rockchip/rockchip_spdif.h | 63 + sound/soc/samsung/Kconfig | 212 + sound/soc/samsung/Makefile | 66 + sound/soc/samsung/arndale_rt5631.c | 140 + sound/soc/samsung/bells.c | 477 + sound/soc/samsung/dma.h | 21 + sound/soc/samsung/dmaengine.c | 53 + sound/soc/samsung/h1940_uda1380.c | 252 + sound/soc/samsung/i2s-regs.h | 163 + sound/soc/samsung/i2s.c | 1589 ++++ sound/soc/samsung/i2s.h | 31 + sound/soc/samsung/idma.c | 432 + sound/soc/samsung/idma.h | 26 + sound/soc/samsung/jive_wm8750.c | 146 + sound/soc/samsung/littlemill.c | 348 + sound/soc/samsung/lowland.c | 205 + sound/soc/samsung/neo1973_wm8753.c | 395 + sound/soc/samsung/odroid.c | 226 + sound/soc/samsung/pcm.c | 607 ++ sound/soc/samsung/pcm.h | 17 + sound/soc/samsung/regs-i2s-v2.h | 115 + sound/soc/samsung/regs-iis.h | 69 + sound/soc/samsung/rx1950_uda1380.c | 273 + sound/soc/samsung/s3c-i2s-v2.c | 741 ++ sound/soc/samsung/s3c-i2s-v2.h | 114 + sound/soc/samsung/s3c2412-i2s.c | 208 + sound/soc/samsung/s3c2412-i2s.h | 27 + sound/soc/samsung/s3c24xx-i2s.c | 476 + sound/soc/samsung/s3c24xx-i2s.h | 35 + sound/soc/samsung/s3c24xx_simtec.c | 372 + sound/soc/samsung/s3c24xx_simtec.h | 22 + sound/soc/samsung/s3c24xx_simtec_hermes.c | 114 + sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c | 102 + sound/soc/samsung/s3c24xx_uda134x.c | 259 + sound/soc/samsung/smartq_wm8987.c | 229 + sound/soc/samsung/smdk_spdif.c | 223 + sound/soc/samsung/smdk_wm8580.c | 214 + sound/soc/samsung/smdk_wm8994.c | 203 + sound/soc/samsung/smdk_wm8994pcm.c | 142 + sound/soc/samsung/snow.c | 258 + sound/soc/samsung/spdif.c | 491 ++ sound/soc/samsung/spdif.h | 19 + sound/soc/samsung/speyside.c | 353 + sound/soc/samsung/tm2_wm5110.c | 660 ++ sound/soc/samsung/tobermory.c | 255 + sound/soc/sh/Kconfig | 68 + sound/soc/sh/Makefile | 24 + sound/soc/sh/dma-sh7760.c | 344 + sound/soc/sh/fsi.c | 2121 +++++ sound/soc/sh/hac.c | 346 + sound/soc/sh/migor.c | 203 + sound/soc/sh/rcar/Makefile | 3 + sound/soc/sh/rcar/adg.c | 653 ++ sound/soc/sh/rcar/cmd.c | 181 + sound/soc/sh/rcar/core.c | 1655 ++++ sound/soc/sh/rcar/ctu.c | 431 + sound/soc/sh/rcar/dma.c | 802 ++ sound/soc/sh/rcar/dvc.c | 383 + sound/soc/sh/rcar/gen.c | 421 + sound/soc/sh/rcar/mix.c | 341 + sound/soc/sh/rcar/rsnd.h | 818 ++ sound/soc/sh/rcar/src.c | 635 ++ sound/soc/sh/rcar/ssi.c | 1207 +++ sound/soc/sh/rcar/ssiu.c | 289 + sound/soc/sh/sh7760-ac97.c | 71 + sound/soc/sh/siu.h | 180 + sound/soc/sh/siu_dai.c | 799 ++ sound/soc/sh/siu_pcm.c | 602 ++ sound/soc/sh/ssi.c | 402 + sound/soc/sirf/Kconfig | 20 + sound/soc/sirf/Makefile | 8 + sound/soc/sirf/sirf-audio-port.c | 87 + sound/soc/sirf/sirf-audio.c | 156 + sound/soc/sirf/sirf-usp.c | 438 + sound/soc/sirf/sirf-usp.h | 293 + sound/soc/soc-ac97.c | 422 + sound/soc/soc-acpi.c | 108 + sound/soc/soc-compress.c | 993 +++ sound/soc/soc-core.c | 3886 ++++++++ sound/soc/soc-dapm.c | 4637 ++++++++++ sound/soc/soc-devres.c | 122 + sound/soc/soc-generic-dmaengine-pcm.c | 548 ++ sound/soc/soc-io.c | 202 + sound/soc/soc-jack.c | 510 ++ sound/soc/soc-ops.c | 1025 +++ sound/soc/soc-pcm.c | 3491 ++++++++ sound/soc/soc-topology.c | 2618 ++++++ sound/soc/soc-utils.c | 380 + sound/soc/spear/Kconfig | 9 + sound/soc/spear/Makefile | 9 + sound/soc/spear/spdif_in.c | 275 + sound/soc/spear/spdif_in_regs.h | 60 + sound/soc/spear/spdif_out.c | 364 + sound/soc/spear/spdif_out_regs.h | 79 + sound/soc/spear/spear_pcm.c | 55 + sound/soc/spear/spear_pcm.h | 24 + sound/soc/sti/Kconfig | 11 + sound/soc/sti/Makefile | 4 + sound/soc/sti/sti_uniperif.c | 513 ++ sound/soc/sti/uniperif.h | 1416 +++ sound/soc/sti/uniperif_player.c | 1148 +++ sound/soc/sti/uniperif_reader.c | 436 + sound/soc/stm/Kconfig | 44 + sound/soc/stm/Makefile | 18 + sound/soc/stm/stm32_adfsdm.c | 372 + sound/soc/stm/stm32_i2s.c | 945 ++ sound/soc/stm/stm32_sai.c | 211 + sound/soc/stm/stm32_sai.h | 279 + sound/soc/stm/stm32_sai_sub.c | 1301 +++ sound/soc/stm/stm32_spdifrx.c | 1016 +++ sound/soc/sunxi/Kconfig | 48 + sound/soc/sunxi/Makefile | 6 + sound/soc/sunxi/sun4i-codec.c | 1745 ++++ sound/soc/sunxi/sun4i-i2s.c | 1200 +++ sound/soc/sunxi/sun4i-spdif.c | 593 ++ sound/soc/sunxi/sun8i-codec-analog.c | 938 ++ sound/soc/sunxi/sun8i-codec.c | 629 ++ sound/soc/tegra/Kconfig | 137 + sound/soc/tegra/Makefile | 40 + sound/soc/tegra/tegra20_ac97.c | 451 + sound/soc/tegra/tegra20_ac97.h | 94 + sound/soc/tegra/tegra20_das.c | 230 + sound/soc/tegra/tegra20_das.h | 134 + sound/soc/tegra/tegra20_i2s.c | 463 + sound/soc/tegra/tegra20_i2s.h | 163 + sound/soc/tegra/tegra20_spdif.c | 375 + sound/soc/tegra/tegra20_spdif.h | 470 + sound/soc/tegra/tegra30_ahub.c | 755 ++ sound/soc/tegra/tegra30_ahub.h | 534 ++ sound/soc/tegra/tegra30_i2s.c | 586 ++ sound/soc/tegra/tegra30_i2s.h | 251 + sound/soc/tegra/tegra_alc5632.c | 263 + sound/soc/tegra/tegra_asoc_utils.c | 240 + sound/soc/tegra/tegra_asoc_utils.h | 53 + sound/soc/tegra/tegra_max98090.c | 305 + sound/soc/tegra/tegra_pcm.c | 86 + sound/soc/tegra/tegra_pcm.h | 42 + sound/soc/tegra/tegra_rt5640.c | 251 + sound/soc/tegra/tegra_rt5677.c | 336 + sound/soc/tegra/tegra_sgtl5000.c | 223 + sound/soc/tegra/tegra_wm8753.c | 217 + sound/soc/tegra/tegra_wm8903.c | 395 + sound/soc/tegra/tegra_wm9712.c | 176 + sound/soc/tegra/trimslice.c | 204 + sound/soc/txx9/Kconfig | 29 + sound/soc/txx9/Makefile | 12 + sound/soc/txx9/txx9aclc-ac97.c | 235 + sound/soc/txx9/txx9aclc-generic.c | 89 + sound/soc/txx9/txx9aclc.c | 435 + sound/soc/txx9/txx9aclc.h | 74 + sound/soc/uniphier/Kconfig | 50 + sound/soc/uniphier/Makefile | 11 + sound/soc/uniphier/aio-compress.c | 425 + sound/soc/uniphier/aio-core.c | 1251 +++ sound/soc/uniphier/aio-cpu.c | 713 ++ sound/soc/uniphier/aio-dma.c | 306 + sound/soc/uniphier/aio-ld11.c | 418 + sound/soc/uniphier/aio-pxs2.c | 320 + sound/soc/uniphier/aio-reg.h | 476 + sound/soc/uniphier/aio.h | 354 + sound/soc/uniphier/evea.c | 574 ++ sound/soc/ux500/Kconfig | 32 + sound/soc/ux500/Makefile | 11 + sound/soc/ux500/mop500.c | 169 + sound/soc/ux500/mop500_ab8500.c | 447 + sound/soc/ux500/mop500_ab8500.h | 22 + sound/soc/ux500/ux500_msp_dai.c | 860 ++ sound/soc/ux500/ux500_msp_dai.h | 71 + sound/soc/ux500/ux500_msp_i2s.c | 735 ++ sound/soc/ux500/ux500_msp_i2s.h | 502 ++ sound/soc/ux500/ux500_pcm.c | 172 + sound/soc/ux500/ux500_pcm.h | 24 + sound/soc/xtensa/Kconfig | 7 + sound/soc/xtensa/Makefile | 3 + sound/soc/xtensa/xtfpga-i2s.c | 658 ++ sound/soc/zte/Kconfig | 25 + sound/soc/zte/Makefile | 3 + sound/soc/zte/zx-i2s.c | 453 + sound/soc/zte/zx-spdif.c | 365 + sound/soc/zte/zx-tdm.c | 461 + sound/sound_core.c | 625 ++ sound/sparc/Kconfig | 42 + sound/sparc/Makefile | 13 + sound/sparc/amd7930.c | 1100 +++ sound/sparc/cs4231.c | 2116 +++++ sound/sparc/dbri.c | 2702 ++++++ sound/spi/Kconfig | 38 + sound/spi/Makefile | 6 + sound/spi/at73c213.c | 1146 +++ sound/spi/at73c213.h | 119 + sound/synth/Kconfig | 2 + sound/synth/Makefile | 13 + sound/synth/emux/Makefile | 14 + sound/synth/emux/emux.c | 165 + sound/synth/emux/emux_effect.c | 310 + sound/synth/emux/emux_hwdep.c | 156 + sound/synth/emux/emux_nrpn.c | 397 + sound/synth/emux/emux_oss.c | 503 ++ sound/synth/emux/emux_proc.c | 128 + sound/synth/emux/emux_seq.c | 416 + sound/synth/emux/emux_synth.c | 982 +++ sound/synth/emux/emux_voice.h | 100 + sound/synth/emux/soundfont.c | 1497 ++++ sound/synth/util_mem.c | 195 + sound/usb/6fire/Makefile | 3 + sound/usb/6fire/chip.c | 212 + sound/usb/6fire/chip.h | 31 + sound/usb/6fire/comm.c | 204 + sound/usb/6fire/comm.h | 43 + sound/usb/6fire/common.h | 29 + sound/usb/6fire/control.c | 621 ++ sound/usb/6fire/control.h | 57 + sound/usb/6fire/firmware.c | 421 + sound/usb/6fire/firmware.h | 27 + sound/usb/6fire/midi.c | 218 + sound/usb/6fire/midi.h | 41 + sound/usb/6fire/pcm.c | 709 ++ sound/usb/6fire/pcm.h | 75 + sound/usb/Kconfig | 166 + sound/usb/Makefile | 32 + sound/usb/bcd2000/Makefile | 3 + sound/usb/bcd2000/bcd2000.c | 468 + sound/usb/caiaq/Makefile | 4 + sound/usb/caiaq/audio.c | 903 ++ sound/usb/caiaq/audio.h | 8 + sound/usb/caiaq/control.c | 656 ++ sound/usb/caiaq/control.h | 7 + sound/usb/caiaq/device.c | 583 ++ sound/usb/caiaq/device.h | 138 + sound/usb/caiaq/input.c | 855 ++ sound/usb/caiaq/input.h | 9 + sound/usb/caiaq/midi.c | 175 + sound/usb/caiaq/midi.h | 10 + sound/usb/card.c | 920 ++ sound/usb/card.h | 176 + sound/usb/clock.c | 677 ++ sound/usb/clock.h | 12 + sound/usb/debug.h | 16 + sound/usb/endpoint.c | 1233 +++ sound/usb/endpoint.h | 37 + sound/usb/format.c | 640 ++ sound/usb/format.h | 14 + sound/usb/helper.c | 133 + sound/usb/helper.h | 37 + sound/usb/hiface/Makefile | 2 + sound/usb/hiface/chip.c | 297 + sound/usb/hiface/chip.h | 30 + sound/usb/hiface/pcm.c | 632 ++ sound/usb/hiface/pcm.h | 24 + sound/usb/line6/Kconfig | 43 + sound/usb/line6/Makefile | 19 + sound/usb/line6/capture.c | 299 + sound/usb/line6/capture.h | 29 + sound/usb/line6/driver.c | 907 ++ sound/usb/line6/driver.h | 224 + sound/usb/line6/midi.c | 297 + sound/usb/line6/midi.h | 51 + sound/usb/line6/midibuf.c | 252 + sound/usb/line6/midibuf.h | 35 + sound/usb/line6/pcm.c | 620 ++ sound/usb/line6/pcm.h | 199 + sound/usb/line6/playback.c | 444 + sound/usb/line6/playback.h | 35 + sound/usb/line6/pod.c | 584 ++ sound/usb/line6/podhd.c | 513 ++ sound/usb/line6/toneport.c | 585 ++ sound/usb/line6/variax.c | 302 + sound/usb/midi.c | 2527 ++++++ sound/usb/midi.h | 63 + sound/usb/misc/Makefile | 2 + sound/usb/misc/ua101.c | 1386 +++ sound/usb/mixer.c | 3677 ++++++++ sound/usb/mixer.h | 129 + sound/usb/mixer_maps.c | 649 ++ sound/usb/mixer_quirks.c | 2009 +++++ sound/usb/mixer_quirks.h | 22 + sound/usb/mixer_scarlett.c | 1002 +++ sound/usb/mixer_scarlett.h | 7 + sound/usb/mixer_us16x08.c | 1424 +++ sound/usb/mixer_us16x08.h | 122 + sound/usb/pcm.c | 1861 ++++ sound/usb/pcm.h | 18 + sound/usb/power.c | 106 + sound/usb/power.h | 37 + sound/usb/proc.c | 178 + sound/usb/proc.h | 9 + sound/usb/quirks-table.h | 3619 ++++++++ sound/usb/quirks.c | 1576 ++++ sound/usb/quirks.h | 51 + sound/usb/stream.c | 1214 +++ sound/usb/stream.h | 13 + sound/usb/usbaudio.h | 135 + sound/usb/usx2y/Makefile | 6 + sound/usb/usx2y/us122l.c | 772 ++ sound/usb/usx2y/us122l.h | 34 + sound/usb/usx2y/usX2Yhwdep.c | 262 + sound/usb/usx2y/usX2Yhwdep.h | 7 + sound/usb/usx2y/usb_stream.c | 768 ++ sound/usb/usx2y/usb_stream.h | 43 + sound/usb/usx2y/usbus428ctldefs.h | 104 + sound/usb/usx2y/usbusx2y.c | 468 + sound/usb/usx2y/usbusx2y.h | 89 + sound/usb/usx2y/usbusx2yaudio.c | 1020 +++ sound/usb/usx2y/usx2y.h | 51 + sound/usb/usx2y/usx2yhwdeppcm.c | 761 ++ sound/usb/usx2y/usx2yhwdeppcm.h | 23 + sound/usb/validate.c | 332 + sound/x86/Kconfig | 17 + sound/x86/Makefile | 4 + sound/x86/intel_hdmi_audio.c | 1955 +++++ sound/x86/intel_hdmi_audio.h | 146 + sound/x86/intel_hdmi_lpe_audio.h | 328 + sound/xen/Kconfig | 10 + sound/xen/Makefile | 9 + sound/xen/xen_snd_front.c | 397 + sound/xen/xen_snd_front.h | 54 + sound/xen/xen_snd_front_alsa.c | 822 ++ sound/xen/xen_snd_front_alsa.h | 23 + sound/xen/xen_snd_front_cfg.c | 519 ++ sound/xen/xen_snd_front_cfg.h | 46 + sound/xen/xen_snd_front_evtchnl.c | 494 ++ sound/xen/xen_snd_front_evtchnl.h | 95 + sound/xen/xen_snd_front_shbuf.c | 194 + sound/xen/xen_snd_front_shbuf.h | 36 + 2006 files changed, 1079376 insertions(+) create mode 100644 sound/Kconfig create mode 100644 sound/Makefile create mode 100644 sound/ac97/Kconfig create mode 100644 sound/ac97/Makefile create mode 100644 sound/ac97/ac97_core.h create mode 100644 sound/ac97/bus.c create mode 100644 sound/ac97/codec.c create mode 100644 sound/ac97/snd_ac97_compat.c create mode 100644 sound/ac97_bus.c create mode 100644 sound/aoa/Kconfig create mode 100644 sound/aoa/Makefile create mode 100644 sound/aoa/aoa-gpio.h create mode 100644 sound/aoa/aoa.h create mode 100644 sound/aoa/codecs/Kconfig create mode 100644 sound/aoa/codecs/Makefile create mode 100644 sound/aoa/codecs/onyx.c create mode 100644 sound/aoa/codecs/onyx.h create mode 100644 sound/aoa/codecs/tas-basstreble.h create mode 100644 sound/aoa/codecs/tas-gain-table.h create mode 100644 sound/aoa/codecs/tas.c create mode 100644 sound/aoa/codecs/tas.h create mode 100644 sound/aoa/codecs/toonie.c create mode 100644 sound/aoa/core/Makefile create mode 100644 sound/aoa/core/alsa.c create mode 100644 sound/aoa/core/alsa.h create mode 100644 sound/aoa/core/core.c create mode 100644 sound/aoa/core/gpio-feature.c create mode 100644 sound/aoa/core/gpio-pmf.c create mode 100644 sound/aoa/fabrics/Kconfig create mode 100644 sound/aoa/fabrics/Makefile create mode 100644 sound/aoa/fabrics/layout.c create mode 100644 sound/aoa/soundbus/Kconfig create mode 100644 sound/aoa/soundbus/Makefile create mode 100644 sound/aoa/soundbus/core.c create mode 100644 sound/aoa/soundbus/i2sbus/Makefile create mode 100644 sound/aoa/soundbus/i2sbus/control.c create mode 100644 sound/aoa/soundbus/i2sbus/core.c create mode 100644 sound/aoa/soundbus/i2sbus/i2sbus.h create mode 100644 sound/aoa/soundbus/i2sbus/interface.h create mode 100644 sound/aoa/soundbus/i2sbus/pcm.c create mode 100644 sound/aoa/soundbus/soundbus.h create mode 100644 sound/aoa/soundbus/sysfs.c create mode 100644 sound/arm/Kconfig create mode 100644 sound/arm/Makefile create mode 100644 sound/arm/aaci.c create mode 100644 sound/arm/aaci.h create mode 100644 sound/arm/pxa2xx-ac97-lib.c create mode 100644 sound/arm/pxa2xx-ac97.c create mode 100644 sound/arm/pxa2xx-pcm-lib.c create mode 100644 sound/atmel/Kconfig create mode 100644 sound/atmel/Makefile create mode 100644 sound/atmel/ac97c.c create mode 100644 sound/atmel/ac97c.h create mode 100644 sound/core/Kconfig create mode 100644 sound/core/Makefile create mode 100644 sound/core/compress_offload.c create mode 100644 sound/core/control.c create mode 100644 sound/core/control_compat.c create mode 100644 sound/core/ctljack.c create mode 100644 sound/core/device.c create mode 100644 sound/core/hrtimer.c create mode 100644 sound/core/hwdep.c create mode 100644 sound/core/hwdep_compat.c create mode 100644 sound/core/info.c create mode 100644 sound/core/info_oss.c create mode 100644 sound/core/init.c create mode 100644 sound/core/isadma.c create mode 100644 sound/core/jack.c create mode 100644 sound/core/memalloc.c create mode 100644 sound/core/memory.c create mode 100644 sound/core/misc.c create mode 100644 sound/core/oss/Makefile create mode 100644 sound/core/oss/copy.c create mode 100644 sound/core/oss/io.c create mode 100644 sound/core/oss/linear.c create mode 100644 sound/core/oss/mixer_oss.c create mode 100644 sound/core/oss/mulaw.c create mode 100644 sound/core/oss/pcm_oss.c create mode 100644 sound/core/oss/pcm_plugin.c create mode 100644 sound/core/oss/pcm_plugin.h create mode 100644 sound/core/oss/rate.c create mode 100644 sound/core/oss/route.c create mode 100644 sound/core/pcm.c create mode 100644 sound/core/pcm_compat.c create mode 100644 sound/core/pcm_dmaengine.c create mode 100644 sound/core/pcm_drm_eld.c create mode 100644 sound/core/pcm_iec958.c create mode 100644 sound/core/pcm_lib.c create mode 100644 sound/core/pcm_local.h create mode 100644 sound/core/pcm_memory.c create mode 100644 sound/core/pcm_misc.c create mode 100644 sound/core/pcm_native.c create mode 100644 sound/core/pcm_param_trace.h create mode 100644 sound/core/pcm_timer.c create mode 100644 sound/core/pcm_trace.h create mode 100644 sound/core/rawmidi.c create mode 100644 sound/core/rawmidi_compat.c create mode 100644 sound/core/seq/Kconfig create mode 100644 sound/core/seq/Makefile create mode 100644 sound/core/seq/oss/Makefile create mode 100644 sound/core/seq/oss/seq_oss.c create mode 100644 sound/core/seq/oss/seq_oss_device.h create mode 100644 sound/core/seq/oss/seq_oss_event.c create mode 100644 sound/core/seq/oss/seq_oss_event.h create mode 100644 sound/core/seq/oss/seq_oss_init.c create mode 100644 sound/core/seq/oss/seq_oss_ioctl.c create mode 100644 sound/core/seq/oss/seq_oss_midi.c create mode 100644 sound/core/seq/oss/seq_oss_midi.h create mode 100644 sound/core/seq/oss/seq_oss_readq.c create mode 100644 sound/core/seq/oss/seq_oss_readq.h create mode 100644 sound/core/seq/oss/seq_oss_rw.c create mode 100644 sound/core/seq/oss/seq_oss_synth.c create mode 100644 sound/core/seq/oss/seq_oss_synth.h create mode 100644 sound/core/seq/oss/seq_oss_timer.c create mode 100644 sound/core/seq/oss/seq_oss_timer.h create mode 100644 sound/core/seq/oss/seq_oss_writeq.c create mode 100644 sound/core/seq/oss/seq_oss_writeq.h create mode 100644 sound/core/seq/seq.c create mode 100644 sound/core/seq/seq_clientmgr.c create mode 100644 sound/core/seq/seq_clientmgr.h create mode 100644 sound/core/seq/seq_compat.c create mode 100644 sound/core/seq/seq_dummy.c create mode 100644 sound/core/seq/seq_fifo.c create mode 100644 sound/core/seq/seq_fifo.h create mode 100644 sound/core/seq/seq_info.c create mode 100644 sound/core/seq/seq_info.h create mode 100644 sound/core/seq/seq_lock.c create mode 100644 sound/core/seq/seq_lock.h create mode 100644 sound/core/seq/seq_memory.c create mode 100644 sound/core/seq/seq_memory.h create mode 100644 sound/core/seq/seq_midi.c create mode 100644 sound/core/seq/seq_midi_emul.c create mode 100644 sound/core/seq/seq_midi_event.c create mode 100644 sound/core/seq/seq_ports.c create mode 100644 sound/core/seq/seq_ports.h create mode 100644 sound/core/seq/seq_prioq.c create mode 100644 sound/core/seq/seq_prioq.h create mode 100644 sound/core/seq/seq_queue.c create mode 100644 sound/core/seq/seq_queue.h create mode 100644 sound/core/seq/seq_system.c create mode 100644 sound/core/seq/seq_system.h create mode 100644 sound/core/seq/seq_timer.c create mode 100644 sound/core/seq/seq_timer.h create mode 100644 sound/core/seq/seq_virmidi.c create mode 100644 sound/core/seq_device.c create mode 100644 sound/core/sgbuf.c create mode 100644 sound/core/sound.c create mode 100644 sound/core/sound_oss.c create mode 100644 sound/core/timer.c create mode 100644 sound/core/timer_compat.c create mode 100644 sound/core/vmaster.c create mode 100644 sound/drivers/Kconfig create mode 100644 sound/drivers/Makefile create mode 100644 sound/drivers/aloop.c create mode 100644 sound/drivers/dummy.c create mode 100644 sound/drivers/ml403-ac97cr.c create mode 100644 sound/drivers/mpu401/Makefile create mode 100644 sound/drivers/mpu401/mpu401.c create mode 100644 sound/drivers/mpu401/mpu401_uart.c create mode 100644 sound/drivers/mtpav.c create mode 100644 sound/drivers/mts64.c create mode 100644 sound/drivers/opl3/Makefile create mode 100644 sound/drivers/opl3/opl3_drums.c create mode 100644 sound/drivers/opl3/opl3_lib.c create mode 100644 sound/drivers/opl3/opl3_midi.c create mode 100644 sound/drivers/opl3/opl3_oss.c create mode 100644 sound/drivers/opl3/opl3_seq.c create mode 100644 sound/drivers/opl3/opl3_synth.c create mode 100644 sound/drivers/opl3/opl3_voice.h create mode 100644 sound/drivers/opl4/Makefile create mode 100644 sound/drivers/opl4/opl4_lib.c create mode 100644 sound/drivers/opl4/opl4_local.h create mode 100644 sound/drivers/opl4/opl4_mixer.c create mode 100644 sound/drivers/opl4/opl4_proc.c create mode 100644 sound/drivers/opl4/opl4_seq.c create mode 100644 sound/drivers/opl4/opl4_synth.c create mode 100644 sound/drivers/opl4/yrw801.c create mode 100644 sound/drivers/pcm-indirect2.c create mode 100644 sound/drivers/pcm-indirect2.h create mode 100644 sound/drivers/pcsp/Makefile create mode 100644 sound/drivers/pcsp/pcsp.c create mode 100644 sound/drivers/pcsp/pcsp.h create mode 100644 sound/drivers/pcsp/pcsp_input.c create mode 100644 sound/drivers/pcsp/pcsp_input.h create mode 100644 sound/drivers/pcsp/pcsp_lib.c create mode 100644 sound/drivers/pcsp/pcsp_mixer.c create mode 100644 sound/drivers/portman2x4.c create mode 100644 sound/drivers/serial-u16550.c create mode 100644 sound/drivers/virmidi.c create mode 100644 sound/drivers/vx/Makefile create mode 100644 sound/drivers/vx/vx_cmd.c create mode 100644 sound/drivers/vx/vx_cmd.h create mode 100644 sound/drivers/vx/vx_core.c create mode 100644 sound/drivers/vx/vx_hwdep.c create mode 100644 sound/drivers/vx/vx_mixer.c create mode 100644 sound/drivers/vx/vx_pcm.c create mode 100644 sound/drivers/vx/vx_uer.c create mode 100644 sound/firewire/Kconfig create mode 100644 sound/firewire/Makefile create mode 100644 sound/firewire/amdtp-am824.c create mode 100644 sound/firewire/amdtp-am824.h create mode 100644 sound/firewire/amdtp-stream-trace.h create mode 100644 sound/firewire/amdtp-stream.c create mode 100644 sound/firewire/amdtp-stream.h create mode 100644 sound/firewire/bebob/Makefile create mode 100644 sound/firewire/bebob/bebob.c create mode 100644 sound/firewire/bebob/bebob.h create mode 100644 sound/firewire/bebob/bebob_command.c create mode 100644 sound/firewire/bebob/bebob_focusrite.c create mode 100644 sound/firewire/bebob/bebob_hwdep.c create mode 100644 sound/firewire/bebob/bebob_maudio.c create mode 100644 sound/firewire/bebob/bebob_midi.c create mode 100644 sound/firewire/bebob/bebob_pcm.c create mode 100644 sound/firewire/bebob/bebob_proc.c create mode 100644 sound/firewire/bebob/bebob_stream.c create mode 100644 sound/firewire/bebob/bebob_terratec.c create mode 100644 sound/firewire/bebob/bebob_yamaha_terratec.c create mode 100644 sound/firewire/cmp.c create mode 100644 sound/firewire/cmp.h create mode 100644 sound/firewire/dice/Makefile create mode 100644 sound/firewire/dice/dice-alesis.c create mode 100644 sound/firewire/dice/dice-extension.c create mode 100644 sound/firewire/dice/dice-hwdep.c create mode 100644 sound/firewire/dice/dice-interface.h create mode 100644 sound/firewire/dice/dice-midi.c create mode 100644 sound/firewire/dice/dice-mytek.c create mode 100644 sound/firewire/dice/dice-pcm.c create mode 100644 sound/firewire/dice/dice-proc.c create mode 100644 sound/firewire/dice/dice-stream.c create mode 100644 sound/firewire/dice/dice-tcelectronic.c create mode 100644 sound/firewire/dice/dice-transaction.c create mode 100644 sound/firewire/dice/dice.c create mode 100644 sound/firewire/dice/dice.h create mode 100644 sound/firewire/digi00x/Makefile create mode 100644 sound/firewire/digi00x/amdtp-dot.c create mode 100644 sound/firewire/digi00x/digi00x-hwdep.c create mode 100644 sound/firewire/digi00x/digi00x-midi.c create mode 100644 sound/firewire/digi00x/digi00x-pcm.c create mode 100644 sound/firewire/digi00x/digi00x-proc.c create mode 100644 sound/firewire/digi00x/digi00x-stream.c create mode 100644 sound/firewire/digi00x/digi00x-transaction.c create mode 100644 sound/firewire/digi00x/digi00x.c create mode 100644 sound/firewire/digi00x/digi00x.h create mode 100644 sound/firewire/fcp.c create mode 100644 sound/firewire/fcp.h create mode 100644 sound/firewire/fireface/Makefile create mode 100644 sound/firewire/fireface/amdtp-ff.c create mode 100644 sound/firewire/fireface/ff-hwdep.c create mode 100644 sound/firewire/fireface/ff-midi.c create mode 100644 sound/firewire/fireface/ff-pcm.c create mode 100644 sound/firewire/fireface/ff-proc.c create mode 100644 sound/firewire/fireface/ff-protocol-ff400.c create mode 100644 sound/firewire/fireface/ff-stream.c create mode 100644 sound/firewire/fireface/ff-transaction.c create mode 100644 sound/firewire/fireface/ff.c create mode 100644 sound/firewire/fireface/ff.h create mode 100644 sound/firewire/fireworks/Makefile create mode 100644 sound/firewire/fireworks/fireworks.c create mode 100644 sound/firewire/fireworks/fireworks.h create mode 100644 sound/firewire/fireworks/fireworks_command.c create mode 100644 sound/firewire/fireworks/fireworks_hwdep.c create mode 100644 sound/firewire/fireworks/fireworks_midi.c create mode 100644 sound/firewire/fireworks/fireworks_pcm.c create mode 100644 sound/firewire/fireworks/fireworks_proc.c create mode 100644 sound/firewire/fireworks/fireworks_stream.c create mode 100644 sound/firewire/fireworks/fireworks_transaction.c create mode 100644 sound/firewire/isight.c create mode 100644 sound/firewire/iso-resources.c create mode 100644 sound/firewire/iso-resources.h create mode 100644 sound/firewire/lib.c create mode 100644 sound/firewire/lib.h create mode 100644 sound/firewire/motu/Makefile create mode 100644 sound/firewire/motu/amdtp-motu-trace.h create mode 100644 sound/firewire/motu/amdtp-motu.c create mode 100644 sound/firewire/motu/motu-hwdep.c create mode 100644 sound/firewire/motu/motu-midi.c create mode 100644 sound/firewire/motu/motu-pcm.c create mode 100644 sound/firewire/motu/motu-proc.c create mode 100644 sound/firewire/motu/motu-protocol-v2.c create mode 100644 sound/firewire/motu/motu-protocol-v3.c create mode 100644 sound/firewire/motu/motu-stream.c create mode 100644 sound/firewire/motu/motu-transaction.c create mode 100644 sound/firewire/motu/motu.c create mode 100644 sound/firewire/motu/motu.h create mode 100644 sound/firewire/oxfw/Makefile create mode 100644 sound/firewire/oxfw/oxfw-command.c create mode 100644 sound/firewire/oxfw/oxfw-hwdep.c create mode 100644 sound/firewire/oxfw/oxfw-midi.c create mode 100644 sound/firewire/oxfw/oxfw-pcm.c create mode 100644 sound/firewire/oxfw/oxfw-proc.c create mode 100644 sound/firewire/oxfw/oxfw-scs1x.c create mode 100644 sound/firewire/oxfw/oxfw-spkr.c create mode 100644 sound/firewire/oxfw/oxfw-stream.c create mode 100644 sound/firewire/oxfw/oxfw.c create mode 100644 sound/firewire/oxfw/oxfw.h create mode 100644 sound/firewire/packets-buffer.c create mode 100644 sound/firewire/packets-buffer.h create mode 100644 sound/firewire/tascam/Makefile create mode 100644 sound/firewire/tascam/amdtp-tascam.c create mode 100644 sound/firewire/tascam/tascam-hwdep.c create mode 100644 sound/firewire/tascam/tascam-midi.c create mode 100644 sound/firewire/tascam/tascam-pcm.c create mode 100644 sound/firewire/tascam/tascam-proc.c create mode 100644 sound/firewire/tascam/tascam-stream.c create mode 100644 sound/firewire/tascam/tascam-transaction.c create mode 100644 sound/firewire/tascam/tascam.c create mode 100644 sound/firewire/tascam/tascam.h create mode 100644 sound/hda/Kconfig create mode 100644 sound/hda/Makefile create mode 100644 sound/hda/array.c create mode 100644 sound/hda/ext/Makefile create mode 100644 sound/hda/ext/hdac_ext_bus.c create mode 100644 sound/hda/ext/hdac_ext_controller.c create mode 100644 sound/hda/ext/hdac_ext_stream.c create mode 100644 sound/hda/hda_bus_type.c create mode 100644 sound/hda/hdac_bus.c create mode 100644 sound/hda/hdac_component.c create mode 100644 sound/hda/hdac_controller.c create mode 100644 sound/hda/hdac_device.c create mode 100644 sound/hda/hdac_i915.c create mode 100644 sound/hda/hdac_regmap.c create mode 100644 sound/hda/hdac_stream.c create mode 100644 sound/hda/hdac_sysfs.c create mode 100644 sound/hda/hdmi_chmap.c create mode 100644 sound/hda/local.h create mode 100644 sound/hda/trace.c create mode 100644 sound/hda/trace.h create mode 100644 sound/i2c/Makefile create mode 100644 sound/i2c/cs8427.c create mode 100644 sound/i2c/i2c.c create mode 100644 sound/i2c/other/Makefile create mode 100644 sound/i2c/other/ak4113.c create mode 100644 sound/i2c/other/ak4114.c create mode 100644 sound/i2c/other/ak4117.c create mode 100644 sound/i2c/other/ak4xxx-adda.c create mode 100644 sound/i2c/other/pt2258.c create mode 100644 sound/i2c/tea6330t.c create mode 100644 sound/isa/Kconfig create mode 100644 sound/isa/Makefile create mode 100644 sound/isa/ad1816a/Makefile create mode 100644 sound/isa/ad1816a/ad1816a.c create mode 100644 sound/isa/ad1816a/ad1816a_lib.c create mode 100644 sound/isa/ad1848/Makefile create mode 100644 sound/isa/ad1848/ad1848.c create mode 100644 sound/isa/adlib.c create mode 100644 sound/isa/als100.c create mode 100644 sound/isa/azt2320.c create mode 100644 sound/isa/cmi8328.c create mode 100644 sound/isa/cmi8330.c create mode 100644 sound/isa/cs423x/Makefile create mode 100644 sound/isa/cs423x/cs4231.c create mode 100644 sound/isa/cs423x/cs4236.c create mode 100644 sound/isa/cs423x/cs4236_lib.c create mode 100644 sound/isa/es1688/Makefile create mode 100644 sound/isa/es1688/es1688.c create mode 100644 sound/isa/es1688/es1688_lib.c create mode 100644 sound/isa/es18xx.c create mode 100644 sound/isa/galaxy/Makefile create mode 100644 sound/isa/galaxy/azt1605.c create mode 100644 sound/isa/galaxy/azt2316.c create mode 100644 sound/isa/galaxy/galaxy.c create mode 100644 sound/isa/gus/Makefile create mode 100644 sound/isa/gus/gus_dma.c create mode 100644 sound/isa/gus/gus_dram.c create mode 100644 sound/isa/gus/gus_io.c create mode 100644 sound/isa/gus/gus_irq.c create mode 100644 sound/isa/gus/gus_main.c create mode 100644 sound/isa/gus/gus_mem.c create mode 100644 sound/isa/gus/gus_mem_proc.c create mode 100644 sound/isa/gus/gus_mixer.c create mode 100644 sound/isa/gus/gus_pcm.c create mode 100644 sound/isa/gus/gus_reset.c create mode 100644 sound/isa/gus/gus_tables.h create mode 100644 sound/isa/gus/gus_timer.c create mode 100644 sound/isa/gus/gus_uart.c create mode 100644 sound/isa/gus/gus_volume.c create mode 100644 sound/isa/gus/gusclassic.c create mode 100644 sound/isa/gus/gusextreme.c create mode 100644 sound/isa/gus/gusmax.c create mode 100644 sound/isa/gus/interwave-stb.c create mode 100644 sound/isa/gus/interwave.c create mode 100644 sound/isa/msnd/Makefile create mode 100644 sound/isa/msnd/msnd.c create mode 100644 sound/isa/msnd/msnd.h create mode 100644 sound/isa/msnd/msnd_classic.c create mode 100644 sound/isa/msnd/msnd_classic.h create mode 100644 sound/isa/msnd/msnd_midi.c create mode 100644 sound/isa/msnd/msnd_pinnacle.c create mode 100644 sound/isa/msnd/msnd_pinnacle.h create mode 100644 sound/isa/msnd/msnd_pinnacle_mixer.c create mode 100644 sound/isa/opl3sa2.c create mode 100644 sound/isa/opti9xx/Makefile create mode 100644 sound/isa/opti9xx/miro.c create mode 100644 sound/isa/opti9xx/opti92x-ad1848.c create mode 100644 sound/isa/opti9xx/opti92x-cs4231.c create mode 100644 sound/isa/opti9xx/opti93x.c create mode 100644 sound/isa/sb/Makefile create mode 100644 sound/isa/sb/emu8000.c create mode 100644 sound/isa/sb/emu8000_callback.c create mode 100644 sound/isa/sb/emu8000_local.h create mode 100644 sound/isa/sb/emu8000_patch.c create mode 100644 sound/isa/sb/emu8000_pcm.c create mode 100644 sound/isa/sb/emu8000_synth.c create mode 100644 sound/isa/sb/jazz16.c create mode 100644 sound/isa/sb/sb16.c create mode 100644 sound/isa/sb/sb16_csp.c create mode 100644 sound/isa/sb/sb16_main.c create mode 100644 sound/isa/sb/sb8.c create mode 100644 sound/isa/sb/sb8_main.c create mode 100644 sound/isa/sb/sb8_midi.c create mode 100644 sound/isa/sb/sb_common.c create mode 100644 sound/isa/sb/sb_mixer.c create mode 100644 sound/isa/sb/sbawe.c create mode 100644 sound/isa/sc6000.c create mode 100644 sound/isa/sscape.c create mode 100644 sound/isa/wavefront/Makefile create mode 100644 sound/isa/wavefront/wavefront.c create mode 100644 sound/isa/wavefront/wavefront_fx.c create mode 100644 sound/isa/wavefront/wavefront_midi.c create mode 100644 sound/isa/wavefront/wavefront_synth.c create mode 100644 sound/isa/wss/Makefile create mode 100644 sound/isa/wss/wss_lib.c create mode 100644 sound/last.c create mode 100644 sound/mips/Kconfig create mode 100644 sound/mips/Makefile create mode 100644 sound/mips/ad1843.c create mode 100644 sound/mips/hal2.c create mode 100644 sound/mips/hal2.h create mode 100644 sound/mips/sgio2audio.c create mode 100644 sound/oss/.gitignore create mode 100644 sound/oss/dmasound/Kconfig create mode 100644 sound/oss/dmasound/Makefile create mode 100644 sound/oss/dmasound/dmasound.h create mode 100644 sound/oss/dmasound/dmasound_atari.c create mode 100644 sound/oss/dmasound/dmasound_core.c create mode 100644 sound/oss/dmasound/dmasound_paula.c create mode 100644 sound/oss/dmasound/dmasound_q40.c create mode 100644 sound/parisc/Kconfig create mode 100644 sound/parisc/Makefile create mode 100644 sound/parisc/harmony.c create mode 100644 sound/parisc/harmony.h create mode 100644 sound/pci/Kconfig create mode 100644 sound/pci/Makefile create mode 100644 sound/pci/ac97/Makefile create mode 100644 sound/pci/ac97/ac97_codec.c create mode 100644 sound/pci/ac97/ac97_id.h create mode 100644 sound/pci/ac97/ac97_local.h create mode 100644 sound/pci/ac97/ac97_patch.c create mode 100644 sound/pci/ac97/ac97_patch.h create mode 100644 sound/pci/ac97/ac97_pcm.c create mode 100644 sound/pci/ac97/ac97_proc.c create mode 100644 sound/pci/ad1889.c create mode 100644 sound/pci/ad1889.h create mode 100644 sound/pci/ak4531_codec.c create mode 100644 sound/pci/ali5451/Makefile create mode 100644 sound/pci/ali5451/ali5451.c create mode 100644 sound/pci/als300.c create mode 100644 sound/pci/als4000.c create mode 100644 sound/pci/asihpi/Makefile create mode 100644 sound/pci/asihpi/asihpi.c create mode 100644 sound/pci/asihpi/hpi.h create mode 100644 sound/pci/asihpi/hpi6000.c create mode 100644 sound/pci/asihpi/hpi6000.h create mode 100644 sound/pci/asihpi/hpi6205.c create mode 100644 sound/pci/asihpi/hpi6205.h create mode 100644 sound/pci/asihpi/hpi_internal.h create mode 100644 sound/pci/asihpi/hpi_version.h create mode 100644 sound/pci/asihpi/hpicmn.c create mode 100644 sound/pci/asihpi/hpicmn.h create mode 100644 sound/pci/asihpi/hpidebug.c create mode 100644 sound/pci/asihpi/hpidebug.h create mode 100644 sound/pci/asihpi/hpidspcd.c create mode 100644 sound/pci/asihpi/hpidspcd.h create mode 100644 sound/pci/asihpi/hpifunc.c create mode 100644 sound/pci/asihpi/hpimsginit.c create mode 100644 sound/pci/asihpi/hpimsginit.h create mode 100644 sound/pci/asihpi/hpimsgx.c create mode 100644 sound/pci/asihpi/hpimsgx.h create mode 100644 sound/pci/asihpi/hpioctl.c create mode 100644 sound/pci/asihpi/hpioctl.h create mode 100644 sound/pci/asihpi/hpios.c create mode 100644 sound/pci/asihpi/hpios.h create mode 100644 sound/pci/asihpi/hpipcida.h create mode 100644 sound/pci/atiixp.c create mode 100644 sound/pci/atiixp_modem.c create mode 100644 sound/pci/au88x0/Makefile create mode 100644 sound/pci/au88x0/au8810.c create mode 100644 sound/pci/au88x0/au8810.h create mode 100644 sound/pci/au88x0/au8820.c create mode 100644 sound/pci/au88x0/au8820.h create mode 100644 sound/pci/au88x0/au8830.c create mode 100644 sound/pci/au88x0/au8830.h create mode 100644 sound/pci/au88x0/au88x0.c create mode 100644 sound/pci/au88x0/au88x0.h create mode 100644 sound/pci/au88x0/au88x0_a3d.c create mode 100644 sound/pci/au88x0/au88x0_a3d.h create mode 100644 sound/pci/au88x0/au88x0_a3ddata.c create mode 100644 sound/pci/au88x0/au88x0_core.c create mode 100644 sound/pci/au88x0/au88x0_eq.c create mode 100644 sound/pci/au88x0/au88x0_eq.h create mode 100644 sound/pci/au88x0/au88x0_eqdata.c create mode 100644 sound/pci/au88x0/au88x0_game.c create mode 100644 sound/pci/au88x0/au88x0_mixer.c create mode 100644 sound/pci/au88x0/au88x0_mpu401.c create mode 100644 sound/pci/au88x0/au88x0_pcm.c create mode 100644 sound/pci/au88x0/au88x0_synth.c create mode 100644 sound/pci/au88x0/au88x0_wt.h create mode 100644 sound/pci/au88x0/au88x0_xtalk.c create mode 100644 sound/pci/au88x0/au88x0_xtalk.h create mode 100644 sound/pci/aw2/Makefile create mode 100644 sound/pci/aw2/aw2-alsa.c create mode 100644 sound/pci/aw2/aw2-saa7146.c create mode 100644 sound/pci/aw2/aw2-saa7146.h create mode 100644 sound/pci/aw2/aw2-tsl.c create mode 100644 sound/pci/aw2/saa7146.h create mode 100644 sound/pci/azt3328.c create mode 100644 sound/pci/azt3328.h create mode 100644 sound/pci/bt87x.c create mode 100644 sound/pci/ca0106/Makefile create mode 100644 sound/pci/ca0106/ca0106.h create mode 100644 sound/pci/ca0106/ca0106_main.c create mode 100644 sound/pci/ca0106/ca0106_mixer.c create mode 100644 sound/pci/ca0106/ca0106_proc.c create mode 100644 sound/pci/ca0106/ca_midi.c create mode 100644 sound/pci/ca0106/ca_midi.h create mode 100644 sound/pci/cmipci.c create mode 100644 sound/pci/cs4281.c create mode 100644 sound/pci/cs46xx/Makefile create mode 100644 sound/pci/cs46xx/cs46xx.c create mode 100644 sound/pci/cs46xx/cs46xx.h create mode 100644 sound/pci/cs46xx/cs46xx_dsp_scb_types.h create mode 100644 sound/pci/cs46xx/cs46xx_dsp_spos.h create mode 100644 sound/pci/cs46xx/cs46xx_dsp_task_types.h create mode 100644 sound/pci/cs46xx/cs46xx_lib.c create mode 100644 sound/pci/cs46xx/cs46xx_lib.h create mode 100644 sound/pci/cs46xx/dsp_spos.c create mode 100644 sound/pci/cs46xx/dsp_spos.h create mode 100644 sound/pci/cs46xx/dsp_spos_scb_lib.c create mode 100644 sound/pci/cs5530.c create mode 100644 sound/pci/cs5535audio/Makefile create mode 100644 sound/pci/cs5535audio/cs5535audio.c create mode 100644 sound/pci/cs5535audio/cs5535audio.h create mode 100644 sound/pci/cs5535audio/cs5535audio_olpc.c create mode 100644 sound/pci/cs5535audio/cs5535audio_pcm.c create mode 100644 sound/pci/cs5535audio/cs5535audio_pm.c create mode 100644 sound/pci/ctxfi/Makefile create mode 100644 sound/pci/ctxfi/ct20k1reg.h create mode 100644 sound/pci/ctxfi/ct20k2reg.h create mode 100644 sound/pci/ctxfi/ctamixer.c create mode 100644 sound/pci/ctxfi/ctamixer.h create mode 100644 sound/pci/ctxfi/ctatc.c create mode 100644 sound/pci/ctxfi/ctatc.h create mode 100644 sound/pci/ctxfi/ctdaio.c create mode 100644 sound/pci/ctxfi/ctdaio.h create mode 100644 sound/pci/ctxfi/cthardware.c create mode 100644 sound/pci/ctxfi/cthardware.h create mode 100644 sound/pci/ctxfi/cthw20k1.c create mode 100644 sound/pci/ctxfi/cthw20k1.h create mode 100644 sound/pci/ctxfi/cthw20k2.c create mode 100644 sound/pci/ctxfi/cthw20k2.h create mode 100644 sound/pci/ctxfi/ctimap.c create mode 100644 sound/pci/ctxfi/ctimap.h create mode 100644 sound/pci/ctxfi/ctmixer.c create mode 100644 sound/pci/ctxfi/ctmixer.h create mode 100644 sound/pci/ctxfi/ctpcm.c create mode 100644 sound/pci/ctxfi/ctpcm.h create mode 100644 sound/pci/ctxfi/ctresource.c create mode 100644 sound/pci/ctxfi/ctresource.h create mode 100644 sound/pci/ctxfi/ctsrc.c create mode 100644 sound/pci/ctxfi/ctsrc.h create mode 100644 sound/pci/ctxfi/cttimer.c create mode 100644 sound/pci/ctxfi/cttimer.h create mode 100644 sound/pci/ctxfi/ctvmem.c create mode 100644 sound/pci/ctxfi/ctvmem.h create mode 100644 sound/pci/ctxfi/xfi.c create mode 100644 sound/pci/echoaudio/Makefile create mode 100644 sound/pci/echoaudio/darla20.c create mode 100644 sound/pci/echoaudio/darla20_dsp.c create mode 100644 sound/pci/echoaudio/darla24.c create mode 100644 sound/pci/echoaudio/darla24_dsp.c create mode 100644 sound/pci/echoaudio/echo3g.c create mode 100644 sound/pci/echoaudio/echo3g_dsp.c create mode 100644 sound/pci/echoaudio/echoaudio.c create mode 100644 sound/pci/echoaudio/echoaudio.h create mode 100644 sound/pci/echoaudio/echoaudio_3g.c create mode 100644 sound/pci/echoaudio/echoaudio_dsp.c create mode 100644 sound/pci/echoaudio/echoaudio_dsp.h create mode 100644 sound/pci/echoaudio/echoaudio_gml.c create mode 100644 sound/pci/echoaudio/gina20.c create mode 100644 sound/pci/echoaudio/gina20_dsp.c create mode 100644 sound/pci/echoaudio/gina24.c create mode 100644 sound/pci/echoaudio/gina24_dsp.c create mode 100644 sound/pci/echoaudio/indigo.c create mode 100644 sound/pci/echoaudio/indigo_dsp.c create mode 100644 sound/pci/echoaudio/indigo_express_dsp.c create mode 100644 sound/pci/echoaudio/indigodj.c create mode 100644 sound/pci/echoaudio/indigodj_dsp.c create mode 100644 sound/pci/echoaudio/indigodjx.c create mode 100644 sound/pci/echoaudio/indigodjx_dsp.c create mode 100644 sound/pci/echoaudio/indigoio.c create mode 100644 sound/pci/echoaudio/indigoio_dsp.c create mode 100644 sound/pci/echoaudio/indigoiox.c create mode 100644 sound/pci/echoaudio/indigoiox_dsp.c create mode 100644 sound/pci/echoaudio/layla20.c create mode 100644 sound/pci/echoaudio/layla20_dsp.c create mode 100644 sound/pci/echoaudio/layla24.c create mode 100644 sound/pci/echoaudio/layla24_dsp.c create mode 100644 sound/pci/echoaudio/mia.c create mode 100644 sound/pci/echoaudio/mia_dsp.c create mode 100644 sound/pci/echoaudio/midi.c create mode 100644 sound/pci/echoaudio/mona.c create mode 100644 sound/pci/echoaudio/mona_dsp.c create mode 100644 sound/pci/emu10k1/Makefile create mode 100644 sound/pci/emu10k1/emu10k1.c create mode 100644 sound/pci/emu10k1/emu10k1_callback.c create mode 100644 sound/pci/emu10k1/emu10k1_main.c create mode 100644 sound/pci/emu10k1/emu10k1_patch.c create mode 100644 sound/pci/emu10k1/emu10k1_synth.c create mode 100644 sound/pci/emu10k1/emu10k1_synth_local.h create mode 100644 sound/pci/emu10k1/emu10k1x.c create mode 100644 sound/pci/emu10k1/emufx.c create mode 100644 sound/pci/emu10k1/emumixer.c create mode 100644 sound/pci/emu10k1/emumpu401.c create mode 100644 sound/pci/emu10k1/emupcm.c create mode 100644 sound/pci/emu10k1/emuproc.c create mode 100644 sound/pci/emu10k1/io.c create mode 100644 sound/pci/emu10k1/irq.c create mode 100644 sound/pci/emu10k1/memory.c create mode 100644 sound/pci/emu10k1/p16v.c create mode 100644 sound/pci/emu10k1/p16v.h create mode 100644 sound/pci/emu10k1/p17v.h create mode 100644 sound/pci/emu10k1/timer.c create mode 100644 sound/pci/emu10k1/tina2.h create mode 100644 sound/pci/emu10k1/voice.c create mode 100644 sound/pci/ens1370.c create mode 100644 sound/pci/ens1371.c create mode 100644 sound/pci/es1938.c create mode 100644 sound/pci/es1968.c create mode 100644 sound/pci/fm801.c create mode 100644 sound/pci/hda/Kconfig create mode 100644 sound/pci/hda/Makefile create mode 100644 sound/pci/hda/ca0132_regs.h create mode 100644 sound/pci/hda/dell_wmi_helper.c create mode 100644 sound/pci/hda/hda_auto_parser.c create mode 100644 sound/pci/hda/hda_auto_parser.h create mode 100644 sound/pci/hda/hda_beep.c create mode 100644 sound/pci/hda/hda_beep.h create mode 100644 sound/pci/hda/hda_bind.c create mode 100644 sound/pci/hda/hda_codec.c create mode 100644 sound/pci/hda/hda_codec.h create mode 100644 sound/pci/hda/hda_controller.c create mode 100644 sound/pci/hda/hda_controller.h create mode 100644 sound/pci/hda/hda_controller_trace.h create mode 100644 sound/pci/hda/hda_eld.c create mode 100644 sound/pci/hda/hda_generic.c create mode 100644 sound/pci/hda/hda_generic.h create mode 100644 sound/pci/hda/hda_hwdep.c create mode 100644 sound/pci/hda/hda_intel.c create mode 100644 sound/pci/hda/hda_intel.h create mode 100644 sound/pci/hda/hda_intel_trace.h create mode 100644 sound/pci/hda/hda_jack.c create mode 100644 sound/pci/hda/hda_jack.h create mode 100644 sound/pci/hda/hda_local.h create mode 100644 sound/pci/hda/hda_proc.c create mode 100644 sound/pci/hda/hda_sysfs.c create mode 100644 sound/pci/hda/hda_tegra.c create mode 100644 sound/pci/hda/hp_x360_helper.c create mode 100644 sound/pci/hda/patch_analog.c create mode 100644 sound/pci/hda/patch_ca0110.c create mode 100644 sound/pci/hda/patch_ca0132.c create mode 100644 sound/pci/hda/patch_cirrus.c create mode 100644 sound/pci/hda/patch_cmedia.c create mode 100644 sound/pci/hda/patch_conexant.c create mode 100644 sound/pci/hda/patch_hdmi.c create mode 100644 sound/pci/hda/patch_realtek.c create mode 100644 sound/pci/hda/patch_si3054.c create mode 100644 sound/pci/hda/patch_sigmatel.c create mode 100644 sound/pci/hda/patch_via.c create mode 100644 sound/pci/hda/thinkpad_helper.c create mode 100644 sound/pci/ice1712/Makefile create mode 100644 sound/pci/ice1712/ak4xxx.c create mode 100644 sound/pci/ice1712/amp.c create mode 100644 sound/pci/ice1712/amp.h create mode 100644 sound/pci/ice1712/aureon.c create mode 100644 sound/pci/ice1712/aureon.h create mode 100644 sound/pci/ice1712/delta.c create mode 100644 sound/pci/ice1712/delta.h create mode 100644 sound/pci/ice1712/envy24ht.h create mode 100644 sound/pci/ice1712/ews.c create mode 100644 sound/pci/ice1712/ews.h create mode 100644 sound/pci/ice1712/hoontech.c create mode 100644 sound/pci/ice1712/hoontech.h create mode 100644 sound/pci/ice1712/ice1712.c create mode 100644 sound/pci/ice1712/ice1712.h create mode 100644 sound/pci/ice1712/ice1724.c create mode 100644 sound/pci/ice1712/juli.c create mode 100644 sound/pci/ice1712/juli.h create mode 100644 sound/pci/ice1712/maya44.c create mode 100644 sound/pci/ice1712/maya44.h create mode 100644 sound/pci/ice1712/phase.c create mode 100644 sound/pci/ice1712/phase.h create mode 100644 sound/pci/ice1712/pontis.c create mode 100644 sound/pci/ice1712/pontis.h create mode 100644 sound/pci/ice1712/prodigy192.c create mode 100644 sound/pci/ice1712/prodigy192.h create mode 100644 sound/pci/ice1712/prodigy_hifi.c create mode 100644 sound/pci/ice1712/prodigy_hifi.h create mode 100644 sound/pci/ice1712/psc724.c create mode 100644 sound/pci/ice1712/psc724.h create mode 100644 sound/pci/ice1712/quartet.c create mode 100644 sound/pci/ice1712/quartet.h create mode 100644 sound/pci/ice1712/revo.c create mode 100644 sound/pci/ice1712/revo.h create mode 100644 sound/pci/ice1712/se.c create mode 100644 sound/pci/ice1712/se.h create mode 100644 sound/pci/ice1712/stac946x.h create mode 100644 sound/pci/ice1712/vt1720_mobo.c create mode 100644 sound/pci/ice1712/vt1720_mobo.h create mode 100644 sound/pci/ice1712/wm8766.c create mode 100644 sound/pci/ice1712/wm8766.h create mode 100644 sound/pci/ice1712/wm8776.c create mode 100644 sound/pci/ice1712/wm8776.h create mode 100644 sound/pci/ice1712/wtm.c create mode 100644 sound/pci/ice1712/wtm.h create mode 100644 sound/pci/intel8x0.c create mode 100644 sound/pci/intel8x0m.c create mode 100644 sound/pci/korg1212/Makefile create mode 100644 sound/pci/korg1212/korg1212.c create mode 100644 sound/pci/lola/Makefile create mode 100644 sound/pci/lola/lola.c create mode 100644 sound/pci/lola/lola.h create mode 100644 sound/pci/lola/lola_clock.c create mode 100644 sound/pci/lola/lola_mixer.c create mode 100644 sound/pci/lola/lola_pcm.c create mode 100644 sound/pci/lola/lola_proc.c create mode 100644 sound/pci/lx6464es/Makefile create mode 100644 sound/pci/lx6464es/lx6464es.c create mode 100644 sound/pci/lx6464es/lx6464es.h create mode 100644 sound/pci/lx6464es/lx_core.c create mode 100644 sound/pci/lx6464es/lx_core.h create mode 100644 sound/pci/lx6464es/lx_defs.h create mode 100644 sound/pci/maestro3.c create mode 100644 sound/pci/mixart/Makefile create mode 100644 sound/pci/mixart/mixart.c create mode 100644 sound/pci/mixart/mixart.h create mode 100644 sound/pci/mixart/mixart_core.c create mode 100644 sound/pci/mixart/mixart_core.h create mode 100644 sound/pci/mixart/mixart_hwdep.c create mode 100644 sound/pci/mixart/mixart_hwdep.h create mode 100644 sound/pci/mixart/mixart_mixer.c create mode 100644 sound/pci/mixart/mixart_mixer.h create mode 100644 sound/pci/nm256/Makefile create mode 100644 sound/pci/nm256/nm256.c create mode 100644 sound/pci/nm256/nm256_coef.c create mode 100644 sound/pci/oxygen/Makefile create mode 100644 sound/pci/oxygen/ak4396.h create mode 100644 sound/pci/oxygen/cm9780.h create mode 100644 sound/pci/oxygen/cs2000.h create mode 100644 sound/pci/oxygen/cs4245.h create mode 100644 sound/pci/oxygen/cs4362a.h create mode 100644 sound/pci/oxygen/cs4398.h create mode 100644 sound/pci/oxygen/oxygen.c create mode 100644 sound/pci/oxygen/oxygen.h create mode 100644 sound/pci/oxygen/oxygen_io.c create mode 100644 sound/pci/oxygen/oxygen_lib.c create mode 100644 sound/pci/oxygen/oxygen_mixer.c create mode 100644 sound/pci/oxygen/oxygen_pcm.c create mode 100644 sound/pci/oxygen/oxygen_regs.h create mode 100644 sound/pci/oxygen/pcm1796.h create mode 100644 sound/pci/oxygen/se6x.c create mode 100644 sound/pci/oxygen/virtuoso.c create mode 100644 sound/pci/oxygen/wm8766.h create mode 100644 sound/pci/oxygen/wm8776.h create mode 100644 sound/pci/oxygen/wm8785.h create mode 100644 sound/pci/oxygen/xonar.h create mode 100644 sound/pci/oxygen/xonar_cs43xx.c create mode 100644 sound/pci/oxygen/xonar_dg.c create mode 100644 sound/pci/oxygen/xonar_dg.h create mode 100644 sound/pci/oxygen/xonar_dg_mixer.c create mode 100644 sound/pci/oxygen/xonar_hdmi.c create mode 100644 sound/pci/oxygen/xonar_lib.c create mode 100644 sound/pci/oxygen/xonar_pcm179x.c create mode 100644 sound/pci/oxygen/xonar_wm87x6.c create mode 100644 sound/pci/pcxhr/Makefile create mode 100644 sound/pci/pcxhr/pcxhr.c create mode 100644 sound/pci/pcxhr/pcxhr.h create mode 100644 sound/pci/pcxhr/pcxhr_core.c create mode 100644 sound/pci/pcxhr/pcxhr_core.h create mode 100644 sound/pci/pcxhr/pcxhr_hwdep.c create mode 100644 sound/pci/pcxhr/pcxhr_hwdep.h create mode 100644 sound/pci/pcxhr/pcxhr_mix22.c create mode 100644 sound/pci/pcxhr/pcxhr_mix22.h create mode 100644 sound/pci/pcxhr/pcxhr_mixer.c create mode 100644 sound/pci/pcxhr/pcxhr_mixer.h create mode 100644 sound/pci/riptide/Makefile create mode 100644 sound/pci/riptide/riptide.c create mode 100644 sound/pci/rme32.c create mode 100644 sound/pci/rme96.c create mode 100644 sound/pci/rme9652/Makefile create mode 100644 sound/pci/rme9652/hdsp.c create mode 100644 sound/pci/rme9652/hdspm.c create mode 100644 sound/pci/rme9652/rme9652.c create mode 100644 sound/pci/sis7019.c create mode 100644 sound/pci/sis7019.h create mode 100644 sound/pci/sonicvibes.c create mode 100644 sound/pci/trident/Makefile create mode 100644 sound/pci/trident/trident.c create mode 100644 sound/pci/trident/trident.h create mode 100644 sound/pci/trident/trident_main.c create mode 100644 sound/pci/trident/trident_memory.c create mode 100644 sound/pci/via82xx.c create mode 100644 sound/pci/via82xx_modem.c create mode 100644 sound/pci/vx222/Makefile create mode 100644 sound/pci/vx222/vx222.c create mode 100644 sound/pci/vx222/vx222.h create mode 100644 sound/pci/vx222/vx222_ops.c create mode 100644 sound/pci/ymfpci/Makefile create mode 100644 sound/pci/ymfpci/ymfpci.c create mode 100644 sound/pci/ymfpci/ymfpci.h create mode 100644 sound/pci/ymfpci/ymfpci_main.c create mode 100644 sound/pcmcia/Kconfig create mode 100644 sound/pcmcia/Makefile create mode 100644 sound/pcmcia/pdaudiocf/Makefile create mode 100644 sound/pcmcia/pdaudiocf/pdaudiocf.c create mode 100644 sound/pcmcia/pdaudiocf/pdaudiocf.h create mode 100644 sound/pcmcia/pdaudiocf/pdaudiocf_core.c create mode 100644 sound/pcmcia/pdaudiocf/pdaudiocf_irq.c create mode 100644 sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c create mode 100644 sound/pcmcia/vx/Makefile create mode 100644 sound/pcmcia/vx/vxp_mixer.c create mode 100644 sound/pcmcia/vx/vxp_ops.c create mode 100644 sound/pcmcia/vx/vxpocket.c create mode 100644 sound/pcmcia/vx/vxpocket.h create mode 100644 sound/ppc/Kconfig create mode 100644 sound/ppc/Makefile create mode 100644 sound/ppc/awacs.c create mode 100644 sound/ppc/awacs.h create mode 100644 sound/ppc/beep.c create mode 100644 sound/ppc/burgundy.c create mode 100644 sound/ppc/burgundy.h create mode 100644 sound/ppc/daca.c create mode 100644 sound/ppc/keywest.c create mode 100644 sound/ppc/pmac.c create mode 100644 sound/ppc/pmac.h create mode 100644 sound/ppc/powermac.c create mode 100644 sound/ppc/snd_ps3.c create mode 100644 sound/ppc/snd_ps3.h create mode 100644 sound/ppc/snd_ps3_reg.h create mode 100644 sound/ppc/tumbler.c create mode 100644 sound/ppc/tumbler_volume.h create mode 100644 sound/sh/Kconfig create mode 100644 sound/sh/Makefile create mode 100644 sound/sh/aica.c create mode 100644 sound/sh/aica.h create mode 100644 sound/sh/sh_dac_audio.c create mode 100644 sound/soc/Kconfig create mode 100644 sound/soc/Makefile create mode 100644 sound/soc/adi/Kconfig create mode 100644 sound/soc/adi/Makefile create mode 100644 sound/soc/adi/axi-i2s.c create mode 100644 sound/soc/adi/axi-spdif.c create mode 100644 sound/soc/amd/Kconfig create mode 100644 sound/soc/amd/Makefile create mode 100644 sound/soc/amd/acp-da7219-max98357a.c create mode 100644 sound/soc/amd/acp-pcm-dma.c create mode 100644 sound/soc/amd/acp-rt5645.c create mode 100644 sound/soc/amd/acp.h create mode 100644 sound/soc/amd/include/acp_2_2_d.h create mode 100644 sound/soc/amd/include/acp_2_2_enum.h create mode 100644 sound/soc/amd/include/acp_2_2_sh_mask.h create mode 100644 sound/soc/atmel/Kconfig create mode 100644 sound/soc/atmel/Makefile create mode 100644 sound/soc/atmel/atmel-classd.c create mode 100644 sound/soc/atmel/atmel-classd.h create mode 100644 sound/soc/atmel/atmel-i2s.c create mode 100644 sound/soc/atmel/atmel-pcm-dma.c create mode 100644 sound/soc/atmel/atmel-pcm-pdc.c create mode 100644 sound/soc/atmel/atmel-pcm.h create mode 100644 sound/soc/atmel/atmel-pdmic.c create mode 100644 sound/soc/atmel/atmel-pdmic.h create mode 100644 sound/soc/atmel/atmel_ssc_dai.c create mode 100644 sound/soc/atmel/atmel_ssc_dai.h create mode 100644 sound/soc/atmel/atmel_wm8904.c create mode 100644 sound/soc/atmel/sam9g20_wm8731.c create mode 100644 sound/soc/atmel/sam9x5_wm8731.c create mode 100644 sound/soc/atmel/tse850-pcm5142.c create mode 100644 sound/soc/au1x/Kconfig create mode 100644 sound/soc/au1x/Makefile create mode 100644 sound/soc/au1x/ac97c.c create mode 100644 sound/soc/au1x/db1000.c create mode 100644 sound/soc/au1x/db1200.c create mode 100644 sound/soc/au1x/dbdma2.c create mode 100644 sound/soc/au1x/dma.c create mode 100644 sound/soc/au1x/i2sc.c create mode 100644 sound/soc/au1x/psc-ac97.c create mode 100644 sound/soc/au1x/psc-i2s.c create mode 100644 sound/soc/au1x/psc.h create mode 100644 sound/soc/bcm/Kconfig create mode 100644 sound/soc/bcm/Makefile create mode 100644 sound/soc/bcm/bcm2835-i2s.c create mode 100644 sound/soc/bcm/cygnus-pcm.c create mode 100644 sound/soc/bcm/cygnus-ssp.c create mode 100644 sound/soc/bcm/cygnus-ssp.h create mode 100644 sound/soc/cirrus/Kconfig create mode 100644 sound/soc/cirrus/Makefile create mode 100644 sound/soc/cirrus/edb93xx.c create mode 100644 sound/soc/cirrus/ep93xx-ac97.c create mode 100644 sound/soc/cirrus/ep93xx-i2s.c create mode 100644 sound/soc/cirrus/ep93xx-pcm.c create mode 100644 sound/soc/cirrus/ep93xx-pcm.h create mode 100644 sound/soc/cirrus/simone.c create mode 100644 sound/soc/cirrus/snappercl15.c create mode 100644 sound/soc/codecs/88pm860x-codec.c create mode 100644 sound/soc/codecs/88pm860x-codec.h create mode 100644 sound/soc/codecs/Kconfig create mode 100644 sound/soc/codecs/Makefile create mode 100644 sound/soc/codecs/ab8500-codec.c create mode 100644 sound/soc/codecs/ab8500-codec.h create mode 100644 sound/soc/codecs/ac97.c create mode 100644 sound/soc/codecs/ad1836.c create mode 100644 sound/soc/codecs/ad1836.h create mode 100644 sound/soc/codecs/ad193x-i2c.c create mode 100644 sound/soc/codecs/ad193x-spi.c create mode 100644 sound/soc/codecs/ad193x.c create mode 100644 sound/soc/codecs/ad193x.h create mode 100644 sound/soc/codecs/ad1980.c create mode 100644 sound/soc/codecs/ad73311.c create mode 100644 sound/soc/codecs/ad73311.h create mode 100644 sound/soc/codecs/adau-utils.c create mode 100644 sound/soc/codecs/adau-utils.h create mode 100644 sound/soc/codecs/adau1373.c create mode 100644 sound/soc/codecs/adau1373.h create mode 100644 sound/soc/codecs/adau1701.c create mode 100644 sound/soc/codecs/adau1701.h create mode 100644 sound/soc/codecs/adau1761-i2c.c create mode 100644 sound/soc/codecs/adau1761-spi.c create mode 100644 sound/soc/codecs/adau1761.c create mode 100644 sound/soc/codecs/adau1761.h create mode 100644 sound/soc/codecs/adau1781-i2c.c create mode 100644 sound/soc/codecs/adau1781-spi.c create mode 100644 sound/soc/codecs/adau1781.c create mode 100644 sound/soc/codecs/adau1781.h create mode 100644 sound/soc/codecs/adau17x1.c create mode 100644 sound/soc/codecs/adau17x1.h create mode 100644 sound/soc/codecs/adau1977-i2c.c create mode 100644 sound/soc/codecs/adau1977-spi.c create mode 100644 sound/soc/codecs/adau1977.c create mode 100644 sound/soc/codecs/adau1977.h create mode 100644 sound/soc/codecs/adau7002.c create mode 100644 sound/soc/codecs/adav801.c create mode 100644 sound/soc/codecs/adav803.c create mode 100644 sound/soc/codecs/adav80x.c create mode 100644 sound/soc/codecs/adav80x.h create mode 100644 sound/soc/codecs/ads117x.c create mode 100644 sound/soc/codecs/ak4104.c create mode 100644 sound/soc/codecs/ak4458.c create mode 100644 sound/soc/codecs/ak4458.h create mode 100644 sound/soc/codecs/ak4535.c create mode 100644 sound/soc/codecs/ak4535.h create mode 100644 sound/soc/codecs/ak4554.c create mode 100644 sound/soc/codecs/ak4613.c create mode 100644 sound/soc/codecs/ak4641.c create mode 100644 sound/soc/codecs/ak4642.c create mode 100644 sound/soc/codecs/ak4671.c create mode 100644 sound/soc/codecs/ak4671.h create mode 100644 sound/soc/codecs/ak5386.c create mode 100644 sound/soc/codecs/ak5558.c create mode 100644 sound/soc/codecs/ak5558.h create mode 100644 sound/soc/codecs/alc5623.c create mode 100644 sound/soc/codecs/alc5623.h create mode 100644 sound/soc/codecs/alc5632.c create mode 100644 sound/soc/codecs/alc5632.h create mode 100644 sound/soc/codecs/arizona.c create mode 100644 sound/soc/codecs/arizona.h create mode 100644 sound/soc/codecs/bd28623.c create mode 100644 sound/soc/codecs/bt-sco.c create mode 100644 sound/soc/codecs/cpcap.c create mode 100644 sound/soc/codecs/cq93vc.c create mode 100644 sound/soc/codecs/cs35l32.c create mode 100644 sound/soc/codecs/cs35l32.h create mode 100644 sound/soc/codecs/cs35l33.c create mode 100644 sound/soc/codecs/cs35l33.h create mode 100644 sound/soc/codecs/cs35l34.c create mode 100644 sound/soc/codecs/cs35l34.h create mode 100644 sound/soc/codecs/cs35l35.c create mode 100644 sound/soc/codecs/cs35l35.h create mode 100644 sound/soc/codecs/cs4265.c create mode 100644 sound/soc/codecs/cs4265.h create mode 100644 sound/soc/codecs/cs4270.c create mode 100644 sound/soc/codecs/cs4271-i2c.c create mode 100644 sound/soc/codecs/cs4271-spi.c create mode 100644 sound/soc/codecs/cs4271.c create mode 100644 sound/soc/codecs/cs4271.h create mode 100644 sound/soc/codecs/cs42l42.c create mode 100644 sound/soc/codecs/cs42l42.h create mode 100644 sound/soc/codecs/cs42l51-i2c.c create mode 100644 sound/soc/codecs/cs42l51.c create mode 100644 sound/soc/codecs/cs42l51.h create mode 100644 sound/soc/codecs/cs42l52.c create mode 100644 sound/soc/codecs/cs42l52.h create mode 100644 sound/soc/codecs/cs42l56.c create mode 100644 sound/soc/codecs/cs42l56.h create mode 100644 sound/soc/codecs/cs42l73.c create mode 100644 sound/soc/codecs/cs42l73.h create mode 100644 sound/soc/codecs/cs42xx8-i2c.c create mode 100644 sound/soc/codecs/cs42xx8.c create mode 100644 sound/soc/codecs/cs42xx8.h create mode 100644 sound/soc/codecs/cs43130.c create mode 100644 sound/soc/codecs/cs43130.h create mode 100644 sound/soc/codecs/cs4349.c create mode 100644 sound/soc/codecs/cs4349.h create mode 100644 sound/soc/codecs/cs47l24.c create mode 100644 sound/soc/codecs/cs47l24.h create mode 100644 sound/soc/codecs/cs53l30.c create mode 100644 sound/soc/codecs/cs53l30.h create mode 100644 sound/soc/codecs/cx20442.c create mode 100644 sound/soc/codecs/cx20442.h create mode 100644 sound/soc/codecs/da7210.c create mode 100644 sound/soc/codecs/da7213.c create mode 100644 sound/soc/codecs/da7213.h create mode 100644 sound/soc/codecs/da7218.c create mode 100644 sound/soc/codecs/da7218.h create mode 100644 sound/soc/codecs/da7219-aad.c create mode 100644 sound/soc/codecs/da7219-aad.h create mode 100644 sound/soc/codecs/da7219.c create mode 100644 sound/soc/codecs/da7219.h create mode 100644 sound/soc/codecs/da732x.c create mode 100644 sound/soc/codecs/da732x.h create mode 100644 sound/soc/codecs/da732x_reg.h create mode 100644 sound/soc/codecs/da9055.c create mode 100644 sound/soc/codecs/dmic.c create mode 100644 sound/soc/codecs/es7134.c create mode 100644 sound/soc/codecs/es7241.c create mode 100644 sound/soc/codecs/es8316.c create mode 100644 sound/soc/codecs/es8316.h create mode 100644 sound/soc/codecs/es8328-i2c.c create mode 100644 sound/soc/codecs/es8328-spi.c create mode 100644 sound/soc/codecs/es8328.c create mode 100644 sound/soc/codecs/es8328.h create mode 100644 sound/soc/codecs/gtm601.c create mode 100644 sound/soc/codecs/hdac_hdmi.c create mode 100644 sound/soc/codecs/hdac_hdmi.h create mode 100644 sound/soc/codecs/hdmi-codec.c create mode 100644 sound/soc/codecs/ics43432.c create mode 100644 sound/soc/codecs/inno_rk3036.c create mode 100644 sound/soc/codecs/inno_rk3036.h create mode 100644 sound/soc/codecs/isabelle.c create mode 100644 sound/soc/codecs/isabelle.h create mode 100644 sound/soc/codecs/jz4740.c create mode 100644 sound/soc/codecs/l3.c create mode 100644 sound/soc/codecs/lm4857.c create mode 100644 sound/soc/codecs/lm49453.c create mode 100644 sound/soc/codecs/lm49453.h create mode 100644 sound/soc/codecs/max9759.c create mode 100644 sound/soc/codecs/max9768.c create mode 100644 sound/soc/codecs/max98088.c create mode 100644 sound/soc/codecs/max98088.h create mode 100644 sound/soc/codecs/max98090.c create mode 100644 sound/soc/codecs/max98090.h create mode 100644 sound/soc/codecs/max98095.c create mode 100644 sound/soc/codecs/max98095.h create mode 100644 sound/soc/codecs/max98357a.c create mode 100644 sound/soc/codecs/max98371.c create mode 100644 sound/soc/codecs/max98371.h create mode 100644 sound/soc/codecs/max98373.c create mode 100644 sound/soc/codecs/max98373.h create mode 100644 sound/soc/codecs/max9850.c create mode 100644 sound/soc/codecs/max9850.h create mode 100644 sound/soc/codecs/max98504.c create mode 100644 sound/soc/codecs/max98504.h create mode 100644 sound/soc/codecs/max9860.c create mode 100644 sound/soc/codecs/max9860.h create mode 100644 sound/soc/codecs/max9867.c create mode 100644 sound/soc/codecs/max9867.h create mode 100644 sound/soc/codecs/max9877.c create mode 100644 sound/soc/codecs/max9877.h create mode 100644 sound/soc/codecs/max98925.c create mode 100644 sound/soc/codecs/max98925.h create mode 100644 sound/soc/codecs/max98926.c create mode 100644 sound/soc/codecs/max98926.h create mode 100644 sound/soc/codecs/max98927.c create mode 100644 sound/soc/codecs/max98927.h create mode 100644 sound/soc/codecs/mc13783.c create mode 100644 sound/soc/codecs/mc13783.h create mode 100644 sound/soc/codecs/ml26124.c create mode 100644 sound/soc/codecs/ml26124.h create mode 100644 sound/soc/codecs/msm8916-wcd-analog.c create mode 100644 sound/soc/codecs/msm8916-wcd-digital.c create mode 100644 sound/soc/codecs/mt6351.c create mode 100644 sound/soc/codecs/mt6351.h create mode 100644 sound/soc/codecs/nau8540.c create mode 100644 sound/soc/codecs/nau8540.h create mode 100644 sound/soc/codecs/nau8810.c create mode 100644 sound/soc/codecs/nau8810.h create mode 100644 sound/soc/codecs/nau8824.c create mode 100644 sound/soc/codecs/nau8824.h create mode 100644 sound/soc/codecs/nau8825.c create mode 100644 sound/soc/codecs/nau8825.h create mode 100644 sound/soc/codecs/pcm1681.c create mode 100644 sound/soc/codecs/pcm1789-i2c.c create mode 100644 sound/soc/codecs/pcm1789.c create mode 100644 sound/soc/codecs/pcm1789.h create mode 100644 sound/soc/codecs/pcm179x-i2c.c create mode 100644 sound/soc/codecs/pcm179x-spi.c create mode 100644 sound/soc/codecs/pcm179x.c create mode 100644 sound/soc/codecs/pcm179x.h create mode 100644 sound/soc/codecs/pcm186x-i2c.c create mode 100644 sound/soc/codecs/pcm186x-spi.c create mode 100644 sound/soc/codecs/pcm186x.c create mode 100644 sound/soc/codecs/pcm186x.h create mode 100644 sound/soc/codecs/pcm3008.c create mode 100644 sound/soc/codecs/pcm3008.h create mode 100644 sound/soc/codecs/pcm3168a-i2c.c create mode 100644 sound/soc/codecs/pcm3168a-spi.c create mode 100644 sound/soc/codecs/pcm3168a.c create mode 100644 sound/soc/codecs/pcm3168a.h create mode 100644 sound/soc/codecs/pcm5102a.c create mode 100644 sound/soc/codecs/pcm512x-i2c.c create mode 100644 sound/soc/codecs/pcm512x-spi.c create mode 100644 sound/soc/codecs/pcm512x.c create mode 100644 sound/soc/codecs/pcm512x.h create mode 100644 sound/soc/codecs/rl6231.c create mode 100644 sound/soc/codecs/rl6231.h create mode 100644 sound/soc/codecs/rl6347a.c create mode 100644 sound/soc/codecs/rl6347a.h create mode 100644 sound/soc/codecs/rt1305.c create mode 100644 sound/soc/codecs/rt1305.h create mode 100644 sound/soc/codecs/rt274.c create mode 100644 sound/soc/codecs/rt274.h create mode 100644 sound/soc/codecs/rt286.c create mode 100644 sound/soc/codecs/rt286.h create mode 100644 sound/soc/codecs/rt298.c create mode 100644 sound/soc/codecs/rt298.h create mode 100644 sound/soc/codecs/rt5514-spi.c create mode 100644 sound/soc/codecs/rt5514-spi.h create mode 100644 sound/soc/codecs/rt5514.c create mode 100644 sound/soc/codecs/rt5514.h create mode 100644 sound/soc/codecs/rt5616.c create mode 100644 sound/soc/codecs/rt5616.h create mode 100644 sound/soc/codecs/rt5631.c create mode 100644 sound/soc/codecs/rt5631.h create mode 100644 sound/soc/codecs/rt5640.c create mode 100644 sound/soc/codecs/rt5640.h create mode 100644 sound/soc/codecs/rt5645.c create mode 100644 sound/soc/codecs/rt5645.h create mode 100644 sound/soc/codecs/rt5651.c create mode 100644 sound/soc/codecs/rt5651.h create mode 100644 sound/soc/codecs/rt5659.c create mode 100644 sound/soc/codecs/rt5659.h create mode 100644 sound/soc/codecs/rt5660.c create mode 100644 sound/soc/codecs/rt5660.h create mode 100644 sound/soc/codecs/rt5663.c create mode 100644 sound/soc/codecs/rt5663.h create mode 100644 sound/soc/codecs/rt5665.c create mode 100644 sound/soc/codecs/rt5665.h create mode 100644 sound/soc/codecs/rt5668.c create mode 100644 sound/soc/codecs/rt5668.h create mode 100644 sound/soc/codecs/rt5670-dsp.h create mode 100644 sound/soc/codecs/rt5670.c create mode 100644 sound/soc/codecs/rt5670.h create mode 100644 sound/soc/codecs/rt5677-spi.c create mode 100644 sound/soc/codecs/rt5677-spi.h create mode 100644 sound/soc/codecs/rt5677.c create mode 100644 sound/soc/codecs/rt5677.h create mode 100644 sound/soc/codecs/rt5682.c create mode 100644 sound/soc/codecs/rt5682.h create mode 100644 sound/soc/codecs/sgtl5000.c create mode 100644 sound/soc/codecs/sgtl5000.h create mode 100644 sound/soc/codecs/si476x.c create mode 100644 sound/soc/codecs/sigmadsp-i2c.c create mode 100644 sound/soc/codecs/sigmadsp-regmap.c create mode 100644 sound/soc/codecs/sigmadsp.c create mode 100644 sound/soc/codecs/sigmadsp.h create mode 100644 sound/soc/codecs/simple-amplifier.c create mode 100644 sound/soc/codecs/sirf-audio-codec.c create mode 100644 sound/soc/codecs/sirf-audio-codec.h create mode 100644 sound/soc/codecs/spdif_receiver.c create mode 100644 sound/soc/codecs/spdif_transmitter.c create mode 100644 sound/soc/codecs/ssm2305.c create mode 100644 sound/soc/codecs/ssm2518.c create mode 100644 sound/soc/codecs/ssm2518.h create mode 100644 sound/soc/codecs/ssm2602-i2c.c create mode 100644 sound/soc/codecs/ssm2602-spi.c create mode 100644 sound/soc/codecs/ssm2602.c create mode 100644 sound/soc/codecs/ssm2602.h create mode 100644 sound/soc/codecs/ssm4567.c create mode 100644 sound/soc/codecs/sta32x.c create mode 100644 sound/soc/codecs/sta32x.h create mode 100644 sound/soc/codecs/sta350.c create mode 100644 sound/soc/codecs/sta350.h create mode 100644 sound/soc/codecs/sta529.c create mode 100644 sound/soc/codecs/stac9766.c create mode 100644 sound/soc/codecs/sti-sas.c create mode 100644 sound/soc/codecs/tas2552.c create mode 100644 sound/soc/codecs/tas2552.h create mode 100644 sound/soc/codecs/tas5086.c create mode 100644 sound/soc/codecs/tas571x.c create mode 100644 sound/soc/codecs/tas571x.h create mode 100644 sound/soc/codecs/tas5720.c create mode 100644 sound/soc/codecs/tas5720.h create mode 100644 sound/soc/codecs/tas6424.c create mode 100644 sound/soc/codecs/tas6424.h create mode 100644 sound/soc/codecs/tda7419.c create mode 100644 sound/soc/codecs/tfa9879.c create mode 100644 sound/soc/codecs/tfa9879.h create mode 100644 sound/soc/codecs/tlv320aic23-i2c.c create mode 100644 sound/soc/codecs/tlv320aic23-spi.c create mode 100644 sound/soc/codecs/tlv320aic23.c create mode 100644 sound/soc/codecs/tlv320aic23.h create mode 100644 sound/soc/codecs/tlv320aic26.c create mode 100644 sound/soc/codecs/tlv320aic26.h create mode 100644 sound/soc/codecs/tlv320aic31xx.c create mode 100644 sound/soc/codecs/tlv320aic31xx.h create mode 100644 sound/soc/codecs/tlv320aic32x4-i2c.c create mode 100644 sound/soc/codecs/tlv320aic32x4-spi.c create mode 100644 sound/soc/codecs/tlv320aic32x4.c create mode 100644 sound/soc/codecs/tlv320aic32x4.h create mode 100644 sound/soc/codecs/tlv320aic3x.c create mode 100644 sound/soc/codecs/tlv320aic3x.h create mode 100644 sound/soc/codecs/tlv320dac33.c create mode 100644 sound/soc/codecs/tlv320dac33.h create mode 100644 sound/soc/codecs/tpa6130a2.c create mode 100644 sound/soc/codecs/tpa6130a2.h create mode 100644 sound/soc/codecs/ts3a227e.c create mode 100644 sound/soc/codecs/ts3a227e.h create mode 100644 sound/soc/codecs/tscs42xx.c create mode 100644 sound/soc/codecs/tscs42xx.h create mode 100644 sound/soc/codecs/tscs454.c create mode 100644 sound/soc/codecs/tscs454.h create mode 100644 sound/soc/codecs/twl4030.c create mode 100644 sound/soc/codecs/twl6040.c create mode 100644 sound/soc/codecs/twl6040.h create mode 100644 sound/soc/codecs/uda134x.c create mode 100644 sound/soc/codecs/uda134x.h create mode 100644 sound/soc/codecs/uda1380.c create mode 100644 sound/soc/codecs/uda1380.h create mode 100644 sound/soc/codecs/wl1273.c create mode 100644 sound/soc/codecs/wl1273.h create mode 100644 sound/soc/codecs/wm0010.c create mode 100644 sound/soc/codecs/wm1250-ev1.c create mode 100644 sound/soc/codecs/wm2000.c create mode 100644 sound/soc/codecs/wm2000.h create mode 100644 sound/soc/codecs/wm2200.c create mode 100644 sound/soc/codecs/wm2200.h create mode 100644 sound/soc/codecs/wm5100-tables.c create mode 100644 sound/soc/codecs/wm5100.c create mode 100644 sound/soc/codecs/wm5100.h create mode 100644 sound/soc/codecs/wm5102.c create mode 100644 sound/soc/codecs/wm5102.h create mode 100644 sound/soc/codecs/wm5110.c create mode 100644 sound/soc/codecs/wm5110.h create mode 100644 sound/soc/codecs/wm8350.c create mode 100644 sound/soc/codecs/wm8350.h create mode 100644 sound/soc/codecs/wm8400.c create mode 100644 sound/soc/codecs/wm8400.h create mode 100644 sound/soc/codecs/wm8510.c create mode 100644 sound/soc/codecs/wm8510.h create mode 100644 sound/soc/codecs/wm8523.c create mode 100644 sound/soc/codecs/wm8523.h create mode 100644 sound/soc/codecs/wm8524.c create mode 100644 sound/soc/codecs/wm8580.c create mode 100644 sound/soc/codecs/wm8580.h create mode 100644 sound/soc/codecs/wm8711.c create mode 100644 sound/soc/codecs/wm8711.h create mode 100644 sound/soc/codecs/wm8727.c create mode 100644 sound/soc/codecs/wm8728.c create mode 100644 sound/soc/codecs/wm8728.h create mode 100644 sound/soc/codecs/wm8731.c create mode 100644 sound/soc/codecs/wm8731.h create mode 100644 sound/soc/codecs/wm8737.c create mode 100644 sound/soc/codecs/wm8737.h create mode 100644 sound/soc/codecs/wm8741.c create mode 100644 sound/soc/codecs/wm8741.h create mode 100644 sound/soc/codecs/wm8750.c create mode 100644 sound/soc/codecs/wm8750.h create mode 100644 sound/soc/codecs/wm8753.c create mode 100644 sound/soc/codecs/wm8753.h create mode 100644 sound/soc/codecs/wm8770.c create mode 100644 sound/soc/codecs/wm8770.h create mode 100644 sound/soc/codecs/wm8776.c create mode 100644 sound/soc/codecs/wm8776.h create mode 100644 sound/soc/codecs/wm8782.c create mode 100644 sound/soc/codecs/wm8804-i2c.c create mode 100644 sound/soc/codecs/wm8804-spi.c create mode 100644 sound/soc/codecs/wm8804.c create mode 100644 sound/soc/codecs/wm8804.h create mode 100644 sound/soc/codecs/wm8900.c create mode 100644 sound/soc/codecs/wm8900.h create mode 100644 sound/soc/codecs/wm8903.c create mode 100644 sound/soc/codecs/wm8903.h create mode 100644 sound/soc/codecs/wm8904.c create mode 100644 sound/soc/codecs/wm8904.h create mode 100644 sound/soc/codecs/wm8940.c create mode 100644 sound/soc/codecs/wm8940.h create mode 100644 sound/soc/codecs/wm8955.c create mode 100644 sound/soc/codecs/wm8955.h create mode 100644 sound/soc/codecs/wm8958-dsp2.c create mode 100644 sound/soc/codecs/wm8960.c create mode 100644 sound/soc/codecs/wm8960.h create mode 100644 sound/soc/codecs/wm8961.c create mode 100644 sound/soc/codecs/wm8961.h create mode 100644 sound/soc/codecs/wm8962.c create mode 100644 sound/soc/codecs/wm8962.h create mode 100644 sound/soc/codecs/wm8971.c create mode 100644 sound/soc/codecs/wm8971.h create mode 100644 sound/soc/codecs/wm8974.c create mode 100644 sound/soc/codecs/wm8974.h create mode 100644 sound/soc/codecs/wm8978.c create mode 100644 sound/soc/codecs/wm8978.h create mode 100644 sound/soc/codecs/wm8983.c create mode 100644 sound/soc/codecs/wm8983.h create mode 100644 sound/soc/codecs/wm8985.c create mode 100644 sound/soc/codecs/wm8985.h create mode 100644 sound/soc/codecs/wm8988.c create mode 100644 sound/soc/codecs/wm8988.h create mode 100644 sound/soc/codecs/wm8990.c create mode 100644 sound/soc/codecs/wm8990.h create mode 100644 sound/soc/codecs/wm8991.c create mode 100644 sound/soc/codecs/wm8991.h create mode 100644 sound/soc/codecs/wm8993.c create mode 100644 sound/soc/codecs/wm8993.h create mode 100644 sound/soc/codecs/wm8994.c create mode 100644 sound/soc/codecs/wm8994.h create mode 100644 sound/soc/codecs/wm8995.c create mode 100644 sound/soc/codecs/wm8995.h create mode 100644 sound/soc/codecs/wm8996.c create mode 100644 sound/soc/codecs/wm8996.h create mode 100644 sound/soc/codecs/wm8997.c create mode 100644 sound/soc/codecs/wm8997.h create mode 100644 sound/soc/codecs/wm8998.c create mode 100644 sound/soc/codecs/wm8998.h create mode 100644 sound/soc/codecs/wm9081.c create mode 100644 sound/soc/codecs/wm9081.h create mode 100644 sound/soc/codecs/wm9090.c create mode 100644 sound/soc/codecs/wm9090.h create mode 100644 sound/soc/codecs/wm9705.c create mode 100644 sound/soc/codecs/wm9712.c create mode 100644 sound/soc/codecs/wm9713.c create mode 100644 sound/soc/codecs/wm9713.h create mode 100644 sound/soc/codecs/wm_adsp.c create mode 100644 sound/soc/codecs/wm_adsp.h create mode 100644 sound/soc/codecs/wm_hubs.c create mode 100644 sound/soc/codecs/wm_hubs.h create mode 100644 sound/soc/codecs/wmfw.h create mode 100644 sound/soc/codecs/zx_aud96p22.c create mode 100644 sound/soc/davinci/Kconfig create mode 100644 sound/soc/davinci/Makefile create mode 100644 sound/soc/davinci/davinci-evm.c create mode 100644 sound/soc/davinci/davinci-i2s.c create mode 100644 sound/soc/davinci/davinci-i2s.h create mode 100644 sound/soc/davinci/davinci-mcasp.c create mode 100644 sound/soc/davinci/davinci-mcasp.h create mode 100644 sound/soc/davinci/davinci-vcif.c create mode 100644 sound/soc/davinci/edma-pcm.c create mode 100644 sound/soc/davinci/edma-pcm.h create mode 100644 sound/soc/dwc/Kconfig create mode 100644 sound/soc/dwc/Makefile create mode 100644 sound/soc/dwc/dwc-i2s.c create mode 100644 sound/soc/dwc/dwc-pcm.c create mode 100644 sound/soc/dwc/local.h create mode 100644 sound/soc/fsl/Kconfig create mode 100644 sound/soc/fsl/Makefile create mode 100644 sound/soc/fsl/efika-audio-fabric.c create mode 100644 sound/soc/fsl/eukrea-tlv320.c create mode 100644 sound/soc/fsl/fsl-asoc-card.c create mode 100644 sound/soc/fsl/fsl_asrc.c create mode 100644 sound/soc/fsl/fsl_asrc.h create mode 100644 sound/soc/fsl/fsl_asrc_dma.c create mode 100644 sound/soc/fsl/fsl_dma.c create mode 100644 sound/soc/fsl/fsl_dma.h create mode 100644 sound/soc/fsl/fsl_esai.c create mode 100644 sound/soc/fsl/fsl_esai.h create mode 100644 sound/soc/fsl/fsl_sai.c create mode 100644 sound/soc/fsl/fsl_sai.h create mode 100644 sound/soc/fsl/fsl_spdif.c create mode 100644 sound/soc/fsl/fsl_spdif.h create mode 100644 sound/soc/fsl/fsl_ssi.c create mode 100644 sound/soc/fsl/fsl_ssi.h create mode 100644 sound/soc/fsl/fsl_ssi_dbg.c create mode 100644 sound/soc/fsl/fsl_utils.c create mode 100644 sound/soc/fsl/fsl_utils.h create mode 100644 sound/soc/fsl/imx-audmux.c create mode 100644 sound/soc/fsl/imx-audmux.h create mode 100644 sound/soc/fsl/imx-es8328.c create mode 100644 sound/soc/fsl/imx-mc13783.c create mode 100644 sound/soc/fsl/imx-pcm-dma.c create mode 100644 sound/soc/fsl/imx-pcm-fiq.c create mode 100644 sound/soc/fsl/imx-pcm.h create mode 100644 sound/soc/fsl/imx-sgtl5000.c create mode 100644 sound/soc/fsl/imx-spdif.c create mode 100644 sound/soc/fsl/imx-ssi.c create mode 100644 sound/soc/fsl/imx-ssi.h create mode 100644 sound/soc/fsl/mpc5200_dma.c create mode 100644 sound/soc/fsl/mpc5200_dma.h create mode 100644 sound/soc/fsl/mpc5200_psc_ac97.c create mode 100644 sound/soc/fsl/mpc5200_psc_i2s.c create mode 100644 sound/soc/fsl/mpc8610_hpcd.c create mode 100644 sound/soc/fsl/mx27vis-aic32x4.c create mode 100644 sound/soc/fsl/p1022_ds.c create mode 100644 sound/soc/fsl/p1022_rdk.c create mode 100644 sound/soc/fsl/pcm030-audio-fabric.c create mode 100644 sound/soc/fsl/phycore-ac97.c create mode 100644 sound/soc/fsl/wm1133-ev1.c create mode 100644 sound/soc/generic/Kconfig create mode 100644 sound/soc/generic/Makefile create mode 100644 sound/soc/generic/audio-graph-card.c create mode 100644 sound/soc/generic/audio-graph-scu-card.c create mode 100644 sound/soc/generic/simple-card-utils.c create mode 100644 sound/soc/generic/simple-card.c create mode 100644 sound/soc/generic/simple-scu-card.c create mode 100644 sound/soc/hisilicon/Kconfig create mode 100644 sound/soc/hisilicon/Makefile create mode 100644 sound/soc/hisilicon/hi6210-i2s.c create mode 100644 sound/soc/hisilicon/hi6210-i2s.h create mode 100644 sound/soc/img/Kconfig create mode 100644 sound/soc/img/Makefile create mode 100644 sound/soc/img/img-i2s-in.c create mode 100644 sound/soc/img/img-i2s-out.c create mode 100644 sound/soc/img/img-parallel-out.c create mode 100644 sound/soc/img/img-spdif-in.c create mode 100644 sound/soc/img/img-spdif-out.c create mode 100644 sound/soc/img/pistachio-internal-dac.c create mode 100644 sound/soc/intel/Kconfig create mode 100644 sound/soc/intel/Makefile create mode 100644 sound/soc/intel/atom/Makefile create mode 100644 sound/soc/intel/atom/sst-atom-controls.c create mode 100644 sound/soc/intel/atom/sst-atom-controls.h create mode 100644 sound/soc/intel/atom/sst-mfld-dsp.h create mode 100644 sound/soc/intel/atom/sst-mfld-platform-compress.c create mode 100644 sound/soc/intel/atom/sst-mfld-platform-pcm.c create mode 100644 sound/soc/intel/atom/sst-mfld-platform.h create mode 100644 sound/soc/intel/atom/sst/Makefile create mode 100644 sound/soc/intel/atom/sst/sst.c create mode 100644 sound/soc/intel/atom/sst/sst.h create mode 100644 sound/soc/intel/atom/sst/sst_acpi.c create mode 100644 sound/soc/intel/atom/sst/sst_drv_interface.c create mode 100644 sound/soc/intel/atom/sst/sst_ipc.c create mode 100644 sound/soc/intel/atom/sst/sst_loader.c create mode 100644 sound/soc/intel/atom/sst/sst_pci.c create mode 100644 sound/soc/intel/atom/sst/sst_pvt.c create mode 100644 sound/soc/intel/atom/sst/sst_stream.c create mode 100644 sound/soc/intel/baytrail/Makefile create mode 100644 sound/soc/intel/baytrail/sst-baytrail-dsp.c create mode 100644 sound/soc/intel/baytrail/sst-baytrail-ipc.c create mode 100644 sound/soc/intel/baytrail/sst-baytrail-ipc.h create mode 100644 sound/soc/intel/baytrail/sst-baytrail-pcm.c create mode 100644 sound/soc/intel/boards/Kconfig create mode 100644 sound/soc/intel/boards/Makefile create mode 100644 sound/soc/intel/boards/bdw-rt5677.c create mode 100644 sound/soc/intel/boards/broadwell.c create mode 100644 sound/soc/intel/boards/bxt_da7219_max98357a.c create mode 100644 sound/soc/intel/boards/bxt_rt298.c create mode 100644 sound/soc/intel/boards/byt-max98090.c create mode 100644 sound/soc/intel/boards/byt-rt5640.c create mode 100644 sound/soc/intel/boards/bytcht_da7213.c create mode 100644 sound/soc/intel/boards/bytcht_es8316.c create mode 100644 sound/soc/intel/boards/bytcht_nocodec.c create mode 100644 sound/soc/intel/boards/bytcr_rt5640.c create mode 100644 sound/soc/intel/boards/bytcr_rt5651.c create mode 100644 sound/soc/intel/boards/cht_bsw_max98090_ti.c create mode 100644 sound/soc/intel/boards/cht_bsw_nau8824.c create mode 100644 sound/soc/intel/boards/cht_bsw_rt5645.c create mode 100644 sound/soc/intel/boards/cht_bsw_rt5672.c create mode 100644 sound/soc/intel/boards/glk_rt5682_max98357a.c create mode 100644 sound/soc/intel/boards/haswell.c create mode 100644 sound/soc/intel/boards/kbl_da7219_max98357a.c create mode 100644 sound/soc/intel/boards/kbl_rt5663_max98927.c create mode 100644 sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c create mode 100644 sound/soc/intel/boards/skl_nau88l25_max98357a.c create mode 100644 sound/soc/intel/boards/skl_nau88l25_ssm4567.c create mode 100644 sound/soc/intel/boards/skl_rt286.c create mode 100644 sound/soc/intel/common/Makefile create mode 100644 sound/soc/intel/common/soc-acpi-intel-bxt-match.c create mode 100644 sound/soc/intel/common/soc-acpi-intel-byt-match.c create mode 100644 sound/soc/intel/common/soc-acpi-intel-cht-match.c create mode 100644 sound/soc/intel/common/soc-acpi-intel-cnl-match.c create mode 100644 sound/soc/intel/common/soc-acpi-intel-glk-match.c create mode 100644 sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c create mode 100644 sound/soc/intel/common/soc-acpi-intel-kbl-match.c create mode 100644 sound/soc/intel/common/soc-acpi-intel-skl-match.c create mode 100644 sound/soc/intel/common/sst-acpi.c create mode 100644 sound/soc/intel/common/sst-dsp-priv.h create mode 100644 sound/soc/intel/common/sst-dsp.c create mode 100644 sound/soc/intel/common/sst-dsp.h create mode 100644 sound/soc/intel/common/sst-firmware.c create mode 100644 sound/soc/intel/common/sst-ipc.c create mode 100644 sound/soc/intel/common/sst-ipc.h create mode 100644 sound/soc/intel/haswell/Makefile create mode 100644 sound/soc/intel/haswell/sst-haswell-dsp.c create mode 100644 sound/soc/intel/haswell/sst-haswell-ipc.c create mode 100644 sound/soc/intel/haswell/sst-haswell-ipc.h create mode 100644 sound/soc/intel/haswell/sst-haswell-pcm.c create mode 100644 sound/soc/intel/skylake/Makefile create mode 100644 sound/soc/intel/skylake/bxt-sst.c create mode 100644 sound/soc/intel/skylake/cnl-sst-dsp.c create mode 100644 sound/soc/intel/skylake/cnl-sst-dsp.h create mode 100644 sound/soc/intel/skylake/cnl-sst.c create mode 100644 sound/soc/intel/skylake/skl-debug.c create mode 100644 sound/soc/intel/skylake/skl-i2s.h create mode 100644 sound/soc/intel/skylake/skl-messages.c create mode 100644 sound/soc/intel/skylake/skl-nhlt.c create mode 100644 sound/soc/intel/skylake/skl-nhlt.h create mode 100644 sound/soc/intel/skylake/skl-pcm.c create mode 100644 sound/soc/intel/skylake/skl-ssp-clk.c create mode 100644 sound/soc/intel/skylake/skl-ssp-clk.h create mode 100644 sound/soc/intel/skylake/skl-sst-cldma.c create mode 100644 sound/soc/intel/skylake/skl-sst-cldma.h create mode 100644 sound/soc/intel/skylake/skl-sst-dsp.c create mode 100644 sound/soc/intel/skylake/skl-sst-dsp.h create mode 100644 sound/soc/intel/skylake/skl-sst-ipc.c create mode 100644 sound/soc/intel/skylake/skl-sst-ipc.h create mode 100644 sound/soc/intel/skylake/skl-sst-utils.c create mode 100644 sound/soc/intel/skylake/skl-sst.c create mode 100644 sound/soc/intel/skylake/skl-topology.c create mode 100644 sound/soc/intel/skylake/skl-topology.h create mode 100644 sound/soc/intel/skylake/skl.c create mode 100644 sound/soc/intel/skylake/skl.h create mode 100644 sound/soc/jz4740/Kconfig create mode 100644 sound/soc/jz4740/Makefile create mode 100644 sound/soc/jz4740/jz4740-i2s.c create mode 100644 sound/soc/jz4740/jz4740-i2s.h create mode 100644 sound/soc/jz4740/qi_lb60.c create mode 100644 sound/soc/kirkwood/Kconfig create mode 100644 sound/soc/kirkwood/Makefile create mode 100644 sound/soc/kirkwood/armada-370-db.c create mode 100644 sound/soc/kirkwood/kirkwood-dma.c create mode 100644 sound/soc/kirkwood/kirkwood-i2s.c create mode 100644 sound/soc/kirkwood/kirkwood.h create mode 100644 sound/soc/mediatek/Kconfig create mode 100644 sound/soc/mediatek/Makefile create mode 100644 sound/soc/mediatek/common/Makefile create mode 100644 sound/soc/mediatek/common/mtk-afe-fe-dai.c create mode 100644 sound/soc/mediatek/common/mtk-afe-fe-dai.h create mode 100644 sound/soc/mediatek/common/mtk-afe-platform-driver.c create mode 100644 sound/soc/mediatek/common/mtk-afe-platform-driver.h create mode 100644 sound/soc/mediatek/common/mtk-base-afe.h create mode 100644 sound/soc/mediatek/mt2701/Makefile create mode 100644 sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c create mode 100644 sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h create mode 100644 sound/soc/mediatek/mt2701/mt2701-afe-common.h create mode 100644 sound/soc/mediatek/mt2701/mt2701-afe-pcm.c create mode 100644 sound/soc/mediatek/mt2701/mt2701-cs42448.c create mode 100644 sound/soc/mediatek/mt2701/mt2701-reg.h create mode 100644 sound/soc/mediatek/mt2701/mt2701-wm8960.c create mode 100644 sound/soc/mediatek/mt6797/Makefile create mode 100644 sound/soc/mediatek/mt6797/mt6797-afe-clk.c create mode 100644 sound/soc/mediatek/mt6797/mt6797-afe-clk.h create mode 100644 sound/soc/mediatek/mt6797/mt6797-afe-common.h create mode 100644 sound/soc/mediatek/mt6797/mt6797-afe-pcm.c create mode 100644 sound/soc/mediatek/mt6797/mt6797-dai-adda.c create mode 100644 sound/soc/mediatek/mt6797/mt6797-dai-hostless.c create mode 100644 sound/soc/mediatek/mt6797/mt6797-dai-pcm.c create mode 100644 sound/soc/mediatek/mt6797/mt6797-interconnection.h create mode 100644 sound/soc/mediatek/mt6797/mt6797-mt6351.c create mode 100644 sound/soc/mediatek/mt6797/mt6797-reg.h create mode 100644 sound/soc/mediatek/mt8173/Makefile create mode 100644 sound/soc/mediatek/mt8173/mt8173-afe-common.h create mode 100644 sound/soc/mediatek/mt8173/mt8173-afe-pcm.c create mode 100644 sound/soc/mediatek/mt8173/mt8173-max98090.c create mode 100644 sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c create mode 100644 sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c create mode 100644 sound/soc/mediatek/mt8173/mt8173-rt5650.c create mode 100644 sound/soc/meson/Kconfig create mode 100644 sound/soc/meson/Makefile create mode 100644 sound/soc/meson/axg-card.c create mode 100644 sound/soc/meson/axg-fifo.c create mode 100644 sound/soc/meson/axg-fifo.h create mode 100644 sound/soc/meson/axg-frddr.c create mode 100644 sound/soc/meson/axg-spdifout.c create mode 100644 sound/soc/meson/axg-tdm-formatter.c create mode 100644 sound/soc/meson/axg-tdm-formatter.h create mode 100644 sound/soc/meson/axg-tdm-interface.c create mode 100644 sound/soc/meson/axg-tdm.h create mode 100644 sound/soc/meson/axg-tdmin.c create mode 100644 sound/soc/meson/axg-tdmout.c create mode 100644 sound/soc/meson/axg-toddr.c create mode 100644 sound/soc/mxs/Kconfig create mode 100644 sound/soc/mxs/Makefile create mode 100644 sound/soc/mxs/mxs-pcm.c create mode 100644 sound/soc/mxs/mxs-pcm.h create mode 100644 sound/soc/mxs/mxs-saif.c create mode 100644 sound/soc/mxs/mxs-saif.h create mode 100644 sound/soc/mxs/mxs-sgtl5000.c create mode 100644 sound/soc/nuc900/Kconfig create mode 100644 sound/soc/nuc900/Makefile create mode 100644 sound/soc/nuc900/nuc900-ac97.c create mode 100644 sound/soc/nuc900/nuc900-audio.c create mode 100644 sound/soc/nuc900/nuc900-audio.h create mode 100644 sound/soc/nuc900/nuc900-pcm.c create mode 100644 sound/soc/omap/Kconfig create mode 100644 sound/soc/omap/Makefile create mode 100644 sound/soc/omap/am3517evm.c create mode 100644 sound/soc/omap/ams-delta.c create mode 100644 sound/soc/omap/mcbsp.c create mode 100644 sound/soc/omap/mcbsp.h create mode 100644 sound/soc/omap/n810.c create mode 100644 sound/soc/omap/omap-abe-twl6040.c create mode 100644 sound/soc/omap/omap-dmic.c create mode 100644 sound/soc/omap/omap-dmic.h create mode 100644 sound/soc/omap/omap-hdmi-audio.c create mode 100644 sound/soc/omap/omap-mcbsp.c create mode 100644 sound/soc/omap/omap-mcbsp.h create mode 100644 sound/soc/omap/omap-mcpdm.c create mode 100644 sound/soc/omap/omap-mcpdm.h create mode 100644 sound/soc/omap/omap-twl4030.c create mode 100644 sound/soc/omap/omap3pandora.c create mode 100644 sound/soc/omap/osk5912.c create mode 100644 sound/soc/omap/rx51.c create mode 100644 sound/soc/omap/sdma-pcm.c create mode 100644 sound/soc/omap/sdma-pcm.h create mode 100644 sound/soc/pxa/Kconfig create mode 100644 sound/soc/pxa/Makefile create mode 100644 sound/soc/pxa/brownstone.c create mode 100644 sound/soc/pxa/corgi.c create mode 100644 sound/soc/pxa/e740_wm9705.c create mode 100644 sound/soc/pxa/e750_wm9705.c create mode 100644 sound/soc/pxa/e800_wm9712.c create mode 100644 sound/soc/pxa/em-x270.c create mode 100644 sound/soc/pxa/hx4700.c create mode 100644 sound/soc/pxa/imote2.c create mode 100644 sound/soc/pxa/magician.c create mode 100644 sound/soc/pxa/mioa701_wm9713.c create mode 100644 sound/soc/pxa/mmp-pcm.c create mode 100644 sound/soc/pxa/mmp-sspa.c create mode 100644 sound/soc/pxa/mmp-sspa.h create mode 100644 sound/soc/pxa/palm27x.c create mode 100644 sound/soc/pxa/poodle.c create mode 100644 sound/soc/pxa/pxa-ssp.c create mode 100644 sound/soc/pxa/pxa-ssp.h create mode 100644 sound/soc/pxa/pxa2xx-ac97.c create mode 100644 sound/soc/pxa/pxa2xx-i2s.c create mode 100644 sound/soc/pxa/pxa2xx-i2s.h create mode 100644 sound/soc/pxa/pxa2xx-pcm.c create mode 100644 sound/soc/pxa/raumfeld.c create mode 100644 sound/soc/pxa/spitz.c create mode 100644 sound/soc/pxa/tosa.c create mode 100644 sound/soc/pxa/ttc-dkb.c create mode 100644 sound/soc/pxa/z2.c create mode 100644 sound/soc/pxa/zylonite.c create mode 100644 sound/soc/qcom/Kconfig create mode 100644 sound/soc/qcom/Makefile create mode 100644 sound/soc/qcom/apq8016_sbc.c create mode 100644 sound/soc/qcom/apq8096.c create mode 100644 sound/soc/qcom/common.c create mode 100644 sound/soc/qcom/common.h create mode 100644 sound/soc/qcom/lpass-apq8016.c create mode 100644 sound/soc/qcom/lpass-cpu.c create mode 100644 sound/soc/qcom/lpass-ipq806x.c create mode 100644 sound/soc/qcom/lpass-lpaif-reg.h create mode 100644 sound/soc/qcom/lpass-platform.c create mode 100644 sound/soc/qcom/lpass.h create mode 100644 sound/soc/qcom/qdsp6/Makefile create mode 100644 sound/soc/qcom/qdsp6/q6adm.c create mode 100644 sound/soc/qcom/qdsp6/q6adm.h create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c create mode 100644 sound/soc/qcom/qdsp6/q6afe.c create mode 100644 sound/soc/qcom/qdsp6/q6afe.h create mode 100644 sound/soc/qcom/qdsp6/q6asm-dai.c create mode 100644 sound/soc/qcom/qdsp6/q6asm.c create mode 100644 sound/soc/qcom/qdsp6/q6asm.h create mode 100644 sound/soc/qcom/qdsp6/q6core.c create mode 100644 sound/soc/qcom/qdsp6/q6core.h create mode 100644 sound/soc/qcom/qdsp6/q6dsp-common.c create mode 100644 sound/soc/qcom/qdsp6/q6dsp-common.h create mode 100644 sound/soc/qcom/qdsp6/q6dsp-errno.h create mode 100644 sound/soc/qcom/qdsp6/q6routing.c create mode 100644 sound/soc/qcom/qdsp6/q6routing.h create mode 100644 sound/soc/qcom/sdm845.c create mode 100644 sound/soc/qcom/storm.c create mode 100644 sound/soc/rockchip/Kconfig create mode 100644 sound/soc/rockchip/Makefile create mode 100644 sound/soc/rockchip/rk3288_hdmi_analog.c create mode 100644 sound/soc/rockchip/rk3399_gru_sound.c create mode 100644 sound/soc/rockchip/rockchip_i2s.c create mode 100644 sound/soc/rockchip/rockchip_i2s.h create mode 100644 sound/soc/rockchip/rockchip_max98090.c create mode 100644 sound/soc/rockchip/rockchip_pcm.c create mode 100644 sound/soc/rockchip/rockchip_pcm.h create mode 100644 sound/soc/rockchip/rockchip_pdm.c create mode 100644 sound/soc/rockchip/rockchip_pdm.h create mode 100644 sound/soc/rockchip/rockchip_rt5645.c create mode 100644 sound/soc/rockchip/rockchip_spdif.c create mode 100644 sound/soc/rockchip/rockchip_spdif.h create mode 100644 sound/soc/samsung/Kconfig create mode 100644 sound/soc/samsung/Makefile create mode 100644 sound/soc/samsung/arndale_rt5631.c create mode 100644 sound/soc/samsung/bells.c create mode 100644 sound/soc/samsung/dma.h create mode 100644 sound/soc/samsung/dmaengine.c create mode 100644 sound/soc/samsung/h1940_uda1380.c create mode 100644 sound/soc/samsung/i2s-regs.h create mode 100644 sound/soc/samsung/i2s.c create mode 100644 sound/soc/samsung/i2s.h create mode 100644 sound/soc/samsung/idma.c create mode 100644 sound/soc/samsung/idma.h create mode 100644 sound/soc/samsung/jive_wm8750.c create mode 100644 sound/soc/samsung/littlemill.c create mode 100644 sound/soc/samsung/lowland.c create mode 100644 sound/soc/samsung/neo1973_wm8753.c create mode 100644 sound/soc/samsung/odroid.c create mode 100644 sound/soc/samsung/pcm.c create mode 100644 sound/soc/samsung/pcm.h create mode 100644 sound/soc/samsung/regs-i2s-v2.h create mode 100644 sound/soc/samsung/regs-iis.h create mode 100644 sound/soc/samsung/rx1950_uda1380.c create mode 100644 sound/soc/samsung/s3c-i2s-v2.c create mode 100644 sound/soc/samsung/s3c-i2s-v2.h create mode 100644 sound/soc/samsung/s3c2412-i2s.c create mode 100644 sound/soc/samsung/s3c2412-i2s.h create mode 100644 sound/soc/samsung/s3c24xx-i2s.c create mode 100644 sound/soc/samsung/s3c24xx-i2s.h create mode 100644 sound/soc/samsung/s3c24xx_simtec.c create mode 100644 sound/soc/samsung/s3c24xx_simtec.h create mode 100644 sound/soc/samsung/s3c24xx_simtec_hermes.c create mode 100644 sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c create mode 100644 sound/soc/samsung/s3c24xx_uda134x.c create mode 100644 sound/soc/samsung/smartq_wm8987.c create mode 100644 sound/soc/samsung/smdk_spdif.c create mode 100644 sound/soc/samsung/smdk_wm8580.c create mode 100644 sound/soc/samsung/smdk_wm8994.c create mode 100644 sound/soc/samsung/smdk_wm8994pcm.c create mode 100644 sound/soc/samsung/snow.c create mode 100644 sound/soc/samsung/spdif.c create mode 100644 sound/soc/samsung/spdif.h create mode 100644 sound/soc/samsung/speyside.c create mode 100644 sound/soc/samsung/tm2_wm5110.c create mode 100644 sound/soc/samsung/tobermory.c create mode 100644 sound/soc/sh/Kconfig create mode 100644 sound/soc/sh/Makefile create mode 100644 sound/soc/sh/dma-sh7760.c create mode 100644 sound/soc/sh/fsi.c create mode 100644 sound/soc/sh/hac.c create mode 100644 sound/soc/sh/migor.c create mode 100644 sound/soc/sh/rcar/Makefile create mode 100644 sound/soc/sh/rcar/adg.c create mode 100644 sound/soc/sh/rcar/cmd.c create mode 100644 sound/soc/sh/rcar/core.c create mode 100644 sound/soc/sh/rcar/ctu.c create mode 100644 sound/soc/sh/rcar/dma.c create mode 100644 sound/soc/sh/rcar/dvc.c create mode 100644 sound/soc/sh/rcar/gen.c create mode 100644 sound/soc/sh/rcar/mix.c create mode 100644 sound/soc/sh/rcar/rsnd.h create mode 100644 sound/soc/sh/rcar/src.c create mode 100644 sound/soc/sh/rcar/ssi.c create mode 100644 sound/soc/sh/rcar/ssiu.c create mode 100644 sound/soc/sh/sh7760-ac97.c create mode 100644 sound/soc/sh/siu.h create mode 100644 sound/soc/sh/siu_dai.c create mode 100644 sound/soc/sh/siu_pcm.c create mode 100644 sound/soc/sh/ssi.c create mode 100644 sound/soc/sirf/Kconfig create mode 100644 sound/soc/sirf/Makefile create mode 100644 sound/soc/sirf/sirf-audio-port.c create mode 100644 sound/soc/sirf/sirf-audio.c create mode 100644 sound/soc/sirf/sirf-usp.c create mode 100644 sound/soc/sirf/sirf-usp.h create mode 100644 sound/soc/soc-ac97.c create mode 100644 sound/soc/soc-acpi.c create mode 100644 sound/soc/soc-compress.c create mode 100644 sound/soc/soc-core.c create mode 100644 sound/soc/soc-dapm.c create mode 100644 sound/soc/soc-devres.c create mode 100644 sound/soc/soc-generic-dmaengine-pcm.c create mode 100644 sound/soc/soc-io.c create mode 100644 sound/soc/soc-jack.c create mode 100644 sound/soc/soc-ops.c create mode 100644 sound/soc/soc-pcm.c create mode 100644 sound/soc/soc-topology.c create mode 100644 sound/soc/soc-utils.c create mode 100644 sound/soc/spear/Kconfig create mode 100644 sound/soc/spear/Makefile create mode 100644 sound/soc/spear/spdif_in.c create mode 100644 sound/soc/spear/spdif_in_regs.h create mode 100644 sound/soc/spear/spdif_out.c create mode 100644 sound/soc/spear/spdif_out_regs.h create mode 100644 sound/soc/spear/spear_pcm.c create mode 100644 sound/soc/spear/spear_pcm.h create mode 100644 sound/soc/sti/Kconfig create mode 100644 sound/soc/sti/Makefile create mode 100644 sound/soc/sti/sti_uniperif.c create mode 100644 sound/soc/sti/uniperif.h create mode 100644 sound/soc/sti/uniperif_player.c create mode 100644 sound/soc/sti/uniperif_reader.c create mode 100644 sound/soc/stm/Kconfig create mode 100644 sound/soc/stm/Makefile create mode 100644 sound/soc/stm/stm32_adfsdm.c create mode 100644 sound/soc/stm/stm32_i2s.c create mode 100644 sound/soc/stm/stm32_sai.c create mode 100644 sound/soc/stm/stm32_sai.h create mode 100644 sound/soc/stm/stm32_sai_sub.c create mode 100644 sound/soc/stm/stm32_spdifrx.c create mode 100644 sound/soc/sunxi/Kconfig create mode 100644 sound/soc/sunxi/Makefile create mode 100644 sound/soc/sunxi/sun4i-codec.c create mode 100644 sound/soc/sunxi/sun4i-i2s.c create mode 100644 sound/soc/sunxi/sun4i-spdif.c create mode 100644 sound/soc/sunxi/sun8i-codec-analog.c create mode 100644 sound/soc/sunxi/sun8i-codec.c create mode 100644 sound/soc/tegra/Kconfig create mode 100644 sound/soc/tegra/Makefile create mode 100644 sound/soc/tegra/tegra20_ac97.c create mode 100644 sound/soc/tegra/tegra20_ac97.h create mode 100644 sound/soc/tegra/tegra20_das.c create mode 100644 sound/soc/tegra/tegra20_das.h create mode 100644 sound/soc/tegra/tegra20_i2s.c create mode 100644 sound/soc/tegra/tegra20_i2s.h create mode 100644 sound/soc/tegra/tegra20_spdif.c create mode 100644 sound/soc/tegra/tegra20_spdif.h create mode 100644 sound/soc/tegra/tegra30_ahub.c create mode 100644 sound/soc/tegra/tegra30_ahub.h create mode 100644 sound/soc/tegra/tegra30_i2s.c create mode 100644 sound/soc/tegra/tegra30_i2s.h create mode 100644 sound/soc/tegra/tegra_alc5632.c create mode 100644 sound/soc/tegra/tegra_asoc_utils.c create mode 100644 sound/soc/tegra/tegra_asoc_utils.h create mode 100644 sound/soc/tegra/tegra_max98090.c create mode 100644 sound/soc/tegra/tegra_pcm.c create mode 100644 sound/soc/tegra/tegra_pcm.h create mode 100644 sound/soc/tegra/tegra_rt5640.c create mode 100644 sound/soc/tegra/tegra_rt5677.c create mode 100644 sound/soc/tegra/tegra_sgtl5000.c create mode 100644 sound/soc/tegra/tegra_wm8753.c create mode 100644 sound/soc/tegra/tegra_wm8903.c create mode 100644 sound/soc/tegra/tegra_wm9712.c create mode 100644 sound/soc/tegra/trimslice.c create mode 100644 sound/soc/txx9/Kconfig create mode 100644 sound/soc/txx9/Makefile create mode 100644 sound/soc/txx9/txx9aclc-ac97.c create mode 100644 sound/soc/txx9/txx9aclc-generic.c create mode 100644 sound/soc/txx9/txx9aclc.c create mode 100644 sound/soc/txx9/txx9aclc.h create mode 100644 sound/soc/uniphier/Kconfig create mode 100644 sound/soc/uniphier/Makefile create mode 100644 sound/soc/uniphier/aio-compress.c create mode 100644 sound/soc/uniphier/aio-core.c create mode 100644 sound/soc/uniphier/aio-cpu.c create mode 100644 sound/soc/uniphier/aio-dma.c create mode 100644 sound/soc/uniphier/aio-ld11.c create mode 100644 sound/soc/uniphier/aio-pxs2.c create mode 100644 sound/soc/uniphier/aio-reg.h create mode 100644 sound/soc/uniphier/aio.h create mode 100644 sound/soc/uniphier/evea.c create mode 100644 sound/soc/ux500/Kconfig create mode 100644 sound/soc/ux500/Makefile create mode 100644 sound/soc/ux500/mop500.c create mode 100644 sound/soc/ux500/mop500_ab8500.c create mode 100644 sound/soc/ux500/mop500_ab8500.h create mode 100644 sound/soc/ux500/ux500_msp_dai.c create mode 100644 sound/soc/ux500/ux500_msp_dai.h create mode 100644 sound/soc/ux500/ux500_msp_i2s.c create mode 100644 sound/soc/ux500/ux500_msp_i2s.h create mode 100644 sound/soc/ux500/ux500_pcm.c create mode 100644 sound/soc/ux500/ux500_pcm.h create mode 100644 sound/soc/xtensa/Kconfig create mode 100644 sound/soc/xtensa/Makefile create mode 100644 sound/soc/xtensa/xtfpga-i2s.c create mode 100644 sound/soc/zte/Kconfig create mode 100644 sound/soc/zte/Makefile create mode 100644 sound/soc/zte/zx-i2s.c create mode 100644 sound/soc/zte/zx-spdif.c create mode 100644 sound/soc/zte/zx-tdm.c create mode 100644 sound/sound_core.c create mode 100644 sound/sparc/Kconfig create mode 100644 sound/sparc/Makefile create mode 100644 sound/sparc/amd7930.c create mode 100644 sound/sparc/cs4231.c create mode 100644 sound/sparc/dbri.c create mode 100644 sound/spi/Kconfig create mode 100644 sound/spi/Makefile create mode 100644 sound/spi/at73c213.c create mode 100644 sound/spi/at73c213.h create mode 100644 sound/synth/Kconfig create mode 100644 sound/synth/Makefile create mode 100644 sound/synth/emux/Makefile create mode 100644 sound/synth/emux/emux.c create mode 100644 sound/synth/emux/emux_effect.c create mode 100644 sound/synth/emux/emux_hwdep.c create mode 100644 sound/synth/emux/emux_nrpn.c create mode 100644 sound/synth/emux/emux_oss.c create mode 100644 sound/synth/emux/emux_proc.c create mode 100644 sound/synth/emux/emux_seq.c create mode 100644 sound/synth/emux/emux_synth.c create mode 100644 sound/synth/emux/emux_voice.h create mode 100644 sound/synth/emux/soundfont.c create mode 100644 sound/synth/util_mem.c create mode 100644 sound/usb/6fire/Makefile create mode 100644 sound/usb/6fire/chip.c create mode 100644 sound/usb/6fire/chip.h create mode 100644 sound/usb/6fire/comm.c create mode 100644 sound/usb/6fire/comm.h create mode 100644 sound/usb/6fire/common.h create mode 100644 sound/usb/6fire/control.c create mode 100644 sound/usb/6fire/control.h create mode 100644 sound/usb/6fire/firmware.c create mode 100644 sound/usb/6fire/firmware.h create mode 100644 sound/usb/6fire/midi.c create mode 100644 sound/usb/6fire/midi.h create mode 100644 sound/usb/6fire/pcm.c create mode 100644 sound/usb/6fire/pcm.h create mode 100644 sound/usb/Kconfig create mode 100644 sound/usb/Makefile create mode 100644 sound/usb/bcd2000/Makefile create mode 100644 sound/usb/bcd2000/bcd2000.c create mode 100644 sound/usb/caiaq/Makefile create mode 100644 sound/usb/caiaq/audio.c create mode 100644 sound/usb/caiaq/audio.h create mode 100644 sound/usb/caiaq/control.c create mode 100644 sound/usb/caiaq/control.h create mode 100644 sound/usb/caiaq/device.c create mode 100644 sound/usb/caiaq/device.h create mode 100644 sound/usb/caiaq/input.c create mode 100644 sound/usb/caiaq/input.h create mode 100644 sound/usb/caiaq/midi.c create mode 100644 sound/usb/caiaq/midi.h create mode 100644 sound/usb/card.c create mode 100644 sound/usb/card.h create mode 100644 sound/usb/clock.c create mode 100644 sound/usb/clock.h create mode 100644 sound/usb/debug.h create mode 100644 sound/usb/endpoint.c create mode 100644 sound/usb/endpoint.h create mode 100644 sound/usb/format.c create mode 100644 sound/usb/format.h create mode 100644 sound/usb/helper.c create mode 100644 sound/usb/helper.h create mode 100644 sound/usb/hiface/Makefile create mode 100644 sound/usb/hiface/chip.c create mode 100644 sound/usb/hiface/chip.h create mode 100644 sound/usb/hiface/pcm.c create mode 100644 sound/usb/hiface/pcm.h create mode 100644 sound/usb/line6/Kconfig create mode 100644 sound/usb/line6/Makefile create mode 100644 sound/usb/line6/capture.c create mode 100644 sound/usb/line6/capture.h create mode 100644 sound/usb/line6/driver.c create mode 100644 sound/usb/line6/driver.h create mode 100644 sound/usb/line6/midi.c create mode 100644 sound/usb/line6/midi.h create mode 100644 sound/usb/line6/midibuf.c create mode 100644 sound/usb/line6/midibuf.h create mode 100644 sound/usb/line6/pcm.c create mode 100644 sound/usb/line6/pcm.h create mode 100644 sound/usb/line6/playback.c create mode 100644 sound/usb/line6/playback.h create mode 100644 sound/usb/line6/pod.c create mode 100644 sound/usb/line6/podhd.c create mode 100644 sound/usb/line6/toneport.c create mode 100644 sound/usb/line6/variax.c create mode 100644 sound/usb/midi.c create mode 100644 sound/usb/midi.h create mode 100644 sound/usb/misc/Makefile create mode 100644 sound/usb/misc/ua101.c create mode 100644 sound/usb/mixer.c create mode 100644 sound/usb/mixer.h create mode 100644 sound/usb/mixer_maps.c create mode 100644 sound/usb/mixer_quirks.c create mode 100644 sound/usb/mixer_quirks.h create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h create mode 100644 sound/usb/mixer_us16x08.c create mode 100644 sound/usb/mixer_us16x08.h create mode 100644 sound/usb/pcm.c create mode 100644 sound/usb/pcm.h create mode 100644 sound/usb/power.c create mode 100644 sound/usb/power.h create mode 100644 sound/usb/proc.c create mode 100644 sound/usb/proc.h create mode 100644 sound/usb/quirks-table.h create mode 100644 sound/usb/quirks.c create mode 100644 sound/usb/quirks.h create mode 100644 sound/usb/stream.c create mode 100644 sound/usb/stream.h create mode 100644 sound/usb/usbaudio.h create mode 100644 sound/usb/usx2y/Makefile create mode 100644 sound/usb/usx2y/us122l.c create mode 100644 sound/usb/usx2y/us122l.h create mode 100644 sound/usb/usx2y/usX2Yhwdep.c create mode 100644 sound/usb/usx2y/usX2Yhwdep.h create mode 100644 sound/usb/usx2y/usb_stream.c create mode 100644 sound/usb/usx2y/usb_stream.h create mode 100644 sound/usb/usx2y/usbus428ctldefs.h create mode 100644 sound/usb/usx2y/usbusx2y.c create mode 100644 sound/usb/usx2y/usbusx2y.h create mode 100644 sound/usb/usx2y/usbusx2yaudio.c create mode 100644 sound/usb/usx2y/usx2y.h create mode 100644 sound/usb/usx2y/usx2yhwdeppcm.c create mode 100644 sound/usb/usx2y/usx2yhwdeppcm.h create mode 100644 sound/usb/validate.c create mode 100644 sound/x86/Kconfig create mode 100644 sound/x86/Makefile create mode 100644 sound/x86/intel_hdmi_audio.c create mode 100644 sound/x86/intel_hdmi_audio.h create mode 100644 sound/x86/intel_hdmi_lpe_audio.h create mode 100644 sound/xen/Kconfig create mode 100644 sound/xen/Makefile create mode 100644 sound/xen/xen_snd_front.c create mode 100644 sound/xen/xen_snd_front.h create mode 100644 sound/xen/xen_snd_front_alsa.c create mode 100644 sound/xen/xen_snd_front_alsa.h create mode 100644 sound/xen/xen_snd_front_cfg.c create mode 100644 sound/xen/xen_snd_front_cfg.h create mode 100644 sound/xen/xen_snd_front_evtchnl.c create mode 100644 sound/xen/xen_snd_front_evtchnl.h create mode 100644 sound/xen/xen_snd_front_shbuf.c create mode 100644 sound/xen/xen_snd_front_shbuf.h (limited to 'sound') diff --git a/sound/Kconfig b/sound/Kconfig new file mode 100644 index 000000000..1140e9988 --- /dev/null +++ b/sound/Kconfig @@ -0,0 +1,114 @@ +menuconfig SOUND + tristate "Sound card support" + depends on HAS_IOMEM + help + If you have a sound card in your computer, i.e. if it can say more + than an occasional beep, say Y. + +if SOUND + +config SOUND_OSS_CORE + bool + default n + +config SOUND_OSS_CORE_PRECLAIM + bool "Preclaim OSS device numbers" + depends on SOUND_OSS_CORE + default y + help + With this option enabled, the kernel will claim all OSS device + numbers if any OSS support (native or emulation) is enabled + whether the respective module is loaded or not and try to load the + appropriate module using sound-slot/service-* and char-major-* + module aliases when one of the device numbers is opened. With + this option disabled, kernel will only claim actually in-use + device numbers and opening a missing device will generate only the + standard char-major-* aliases. + + The only visible difference is use of additional module aliases + and whether OSS sound devices appear multiple times in + /proc/devices. sound-slot/service-* module aliases are scheduled + to be removed (ie. PRECLAIM won't be available) and this option is + to make the transition easier. This option can be overridden + during boot using the kernel parameter soundcore.preclaim_oss. + + Disabling this allows alternative OSS implementations. + + If unsure, say Y. + +source "sound/oss/dmasound/Kconfig" + +if !UML + +menuconfig SND + tristate "Advanced Linux Sound Architecture" + help + Say 'Y' or 'M' to enable ALSA (Advanced Linux Sound Architecture), + the new base sound system. + + For more information, see + +if SND + +source "sound/core/Kconfig" + +source "sound/drivers/Kconfig" + +source "sound/isa/Kconfig" + +source "sound/pci/Kconfig" + +source "sound/hda/Kconfig" + +source "sound/ppc/Kconfig" + +source "sound/ac97/Kconfig" + +source "sound/aoa/Kconfig" + +source "sound/arm/Kconfig" + +source "sound/atmel/Kconfig" + +source "sound/spi/Kconfig" + +source "sound/mips/Kconfig" + +source "sound/sh/Kconfig" + +# the following will depend on the order of config. +# here assuming USB is defined before ALSA +source "sound/usb/Kconfig" + +source "sound/firewire/Kconfig" + +# the following will depend on the order of config. +# here assuming PCMCIA is defined before ALSA +source "sound/pcmcia/Kconfig" + +source "sound/sparc/Kconfig" + +source "sound/parisc/Kconfig" + +source "sound/soc/Kconfig" + +source "sound/x86/Kconfig" + +source "sound/synth/Kconfig" + +source "sound/xen/Kconfig" + +endif # SND + +endif # !UML + +endif # SOUND + +# AC97_BUS is used from both sound and ucb1400 +config AC97_BUS + tristate + help + This is used to avoid config and link hard dependencies between the + sound subsystem and other function drivers completely unrelated to + sound although they're sharing the AC97 bus. Concerned drivers + should "select" this. diff --git a/sound/Makefile b/sound/Makefile new file mode 100644 index 000000000..797ecdcd3 --- /dev/null +++ b/sound/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for the Linux sound card driver +# + +obj-$(CONFIG_SOUND) += soundcore.o +obj-$(CONFIG_DMASOUND) += oss/dmasound/ +obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ + firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/ +obj-$(CONFIG_SND_AOA) += aoa/ + +# This one must be compilable even if sound is configured out +obj-$(CONFIG_AC97_BUS) += ac97_bus.o +obj-$(CONFIG_AC97_BUS_NEW) += ac97/ + +ifeq ($(CONFIG_SND),y) + obj-y += last.o +endif + +soundcore-objs := sound_core.o diff --git a/sound/ac97/Kconfig b/sound/ac97/Kconfig new file mode 100644 index 000000000..baa5f8ef8 --- /dev/null +++ b/sound/ac97/Kconfig @@ -0,0 +1,18 @@ +# +# AC97 configuration +# + + +config AC97_BUS_NEW + tristate + help + This is the new AC97 bus type, successor of AC97_BUS. The ported + drivers which benefit from the AC97 automatic probing should "select" + this instead of the AC97_BUS. + Say Y here if you want to have AC97 devices, which are sound oriented + devices around an AC-Link. + +config AC97_BUS_COMPAT + bool + depends on AC97_BUS_NEW + depends on !AC97_BUS diff --git a/sound/ac97/Makefile b/sound/ac97/Makefile new file mode 100644 index 000000000..f9c2640bf --- /dev/null +++ b/sound/ac97/Makefile @@ -0,0 +1,8 @@ +# +# make for AC97 bus drivers +# + +obj-$(CONFIG_AC97_BUS_NEW) += ac97.o + +ac97-y += bus.o codec.o +ac97-$(CONFIG_AC97_BUS_COMPAT) += snd_ac97_compat.o diff --git a/sound/ac97/ac97_core.h b/sound/ac97/ac97_core.h new file mode 100644 index 000000000..08441a4fd --- /dev/null +++ b/sound/ac97/ac97_core.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2016 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +unsigned int snd_ac97_bus_scan_one(struct ac97_controller *ac97, + unsigned int codec_num); + +static inline bool ac97_ids_match(unsigned int id1, unsigned int id2, + unsigned int mask) +{ + return (id1 & mask) == (id2 & mask); +} diff --git a/sound/ac97/bus.c b/sound/ac97/bus.c new file mode 100644 index 000000000..d8b227e6d --- /dev/null +++ b/sound/ac97/bus.c @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2016 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ac97_core.h" + +/* + * Protects ac97_controllers and each ac97_controller structure. + */ +static DEFINE_MUTEX(ac97_controllers_mutex); +static DEFINE_IDR(ac97_adapter_idr); +static LIST_HEAD(ac97_controllers); + +static struct bus_type ac97_bus_type; + +static inline struct ac97_controller* +to_ac97_controller(struct device *ac97_adapter) +{ + return container_of(ac97_adapter, struct ac97_controller, adap); +} + +static int ac97_unbound_ctrl_write(struct ac97_controller *adrv, int slot, + unsigned short reg, unsigned short val) +{ + return -ENODEV; +} + +static int ac97_unbound_ctrl_read(struct ac97_controller *adrv, int slot, + unsigned short reg) +{ + return -ENODEV; +} + +static const struct ac97_controller_ops ac97_unbound_ctrl_ops = { + .write = ac97_unbound_ctrl_write, + .read = ac97_unbound_ctrl_read, +}; + +static struct ac97_controller ac97_unbound_ctrl = { + .ops = &ac97_unbound_ctrl_ops, +}; + +static struct ac97_codec_device * +ac97_codec_find(struct ac97_controller *ac97_ctrl, unsigned int codec_num) +{ + if (codec_num >= AC97_BUS_MAX_CODECS) + return ERR_PTR(-EINVAL); + + return ac97_ctrl->codecs[codec_num]; +} + +static struct device_node * +ac97_of_get_child_device(struct ac97_controller *ac97_ctrl, int idx, + unsigned int vendor_id) +{ + struct device_node *node; + u32 reg; + char compat[] = "ac97,0000,0000"; + + snprintf(compat, sizeof(compat), "ac97,%04x,%04x", + vendor_id >> 16, vendor_id & 0xffff); + + for_each_child_of_node(ac97_ctrl->parent->of_node, node) { + if ((idx != of_property_read_u32(node, "reg", ®)) || + !of_device_is_compatible(node, compat)) + continue; + return node; + } + + return NULL; +} + +static void ac97_codec_release(struct device *dev) +{ + struct ac97_codec_device *adev; + struct ac97_controller *ac97_ctrl; + + adev = to_ac97_device(dev); + ac97_ctrl = adev->ac97_ctrl; + ac97_ctrl->codecs[adev->num] = NULL; + of_node_put(dev->of_node); + kfree(adev); +} + +static int ac97_codec_add(struct ac97_controller *ac97_ctrl, int idx, + unsigned int vendor_id) +{ + struct ac97_codec_device *codec; + int ret; + + codec = kzalloc(sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + ac97_ctrl->codecs[idx] = codec; + codec->vendor_id = vendor_id; + codec->dev.release = ac97_codec_release; + codec->dev.bus = &ac97_bus_type; + codec->dev.parent = &ac97_ctrl->adap; + codec->num = idx; + codec->ac97_ctrl = ac97_ctrl; + + device_initialize(&codec->dev); + dev_set_name(&codec->dev, "%s:%u", dev_name(ac97_ctrl->parent), idx); + codec->dev.of_node = ac97_of_get_child_device(ac97_ctrl, idx, + vendor_id); + + ret = device_add(&codec->dev); + if (ret) { + put_device(&codec->dev); + return ret; + } + + return 0; +} + +unsigned int snd_ac97_bus_scan_one(struct ac97_controller *adrv, + unsigned int codec_num) +{ + unsigned short vid1, vid2; + int ret; + + ret = adrv->ops->read(adrv, codec_num, AC97_VENDOR_ID1); + vid1 = (ret & 0xffff); + if (ret < 0) + return 0; + + ret = adrv->ops->read(adrv, codec_num, AC97_VENDOR_ID2); + vid2 = (ret & 0xffff); + if (ret < 0) + return 0; + + dev_dbg(&adrv->adap, "%s(codec_num=%u): vendor_id=0x%08x\n", + __func__, codec_num, AC97_ID(vid1, vid2)); + return AC97_ID(vid1, vid2); +} + +static int ac97_bus_scan(struct ac97_controller *ac97_ctrl) +{ + int ret, i; + unsigned int vendor_id; + + for (i = 0; i < AC97_BUS_MAX_CODECS; i++) { + if (ac97_codec_find(ac97_ctrl, i)) + continue; + if (!(ac97_ctrl->slots_available & BIT(i))) + continue; + vendor_id = snd_ac97_bus_scan_one(ac97_ctrl, i); + if (!vendor_id) + continue; + + ret = ac97_codec_add(ac97_ctrl, i, vendor_id); + if (ret < 0) + return ret; + } + return 0; +} + +static int ac97_bus_reset(struct ac97_controller *ac97_ctrl) +{ + ac97_ctrl->ops->reset(ac97_ctrl); + + return 0; +} + +/** + * snd_ac97_codec_driver_register - register an AC97 codec driver + * @dev: AC97 driver codec to register + * + * Register an AC97 codec driver to the ac97 bus driver, aka. the AC97 digital + * controller. + * + * Returns 0 on success or error code + */ +int snd_ac97_codec_driver_register(struct ac97_codec_driver *drv) +{ + drv->driver.bus = &ac97_bus_type; + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(snd_ac97_codec_driver_register); + +/** + * snd_ac97_codec_driver_unregister - unregister an AC97 codec driver + * @dev: AC97 codec driver to unregister + * + * Unregister a previously registered ac97 codec driver. + */ +void snd_ac97_codec_driver_unregister(struct ac97_codec_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(snd_ac97_codec_driver_unregister); + +/** + * snd_ac97_codec_get_platdata - get platform_data + * @adev: the ac97 codec device + * + * For legacy platforms, in order to have platform_data in codec drivers + * available, while ac97 device are auto-created upon probe, this retrieves the + * platdata which was setup on ac97 controller registration. + * + * Returns the platform data pointer + */ +void *snd_ac97_codec_get_platdata(const struct ac97_codec_device *adev) +{ + struct ac97_controller *ac97_ctrl = adev->ac97_ctrl; + + return ac97_ctrl->codecs_pdata[adev->num]; +} +EXPORT_SYMBOL_GPL(snd_ac97_codec_get_platdata); + +static void ac97_ctrl_codecs_unregister(struct ac97_controller *ac97_ctrl) +{ + int i; + + for (i = 0; i < AC97_BUS_MAX_CODECS; i++) + if (ac97_ctrl->codecs[i]) { + ac97_ctrl->codecs[i]->ac97_ctrl = &ac97_unbound_ctrl; + device_unregister(&ac97_ctrl->codecs[i]->dev); + } +} + +static ssize_t cold_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + struct ac97_controller *ac97_ctrl; + + mutex_lock(&ac97_controllers_mutex); + ac97_ctrl = to_ac97_controller(dev); + ac97_ctrl->ops->reset(ac97_ctrl); + mutex_unlock(&ac97_controllers_mutex); + return len; +} +static DEVICE_ATTR_WO(cold_reset); + +static ssize_t warm_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + struct ac97_controller *ac97_ctrl; + + if (!dev) + return -ENODEV; + + mutex_lock(&ac97_controllers_mutex); + ac97_ctrl = to_ac97_controller(dev); + ac97_ctrl->ops->warm_reset(ac97_ctrl); + mutex_unlock(&ac97_controllers_mutex); + return len; +} +static DEVICE_ATTR_WO(warm_reset); + +static struct attribute *ac97_controller_device_attrs[] = { + &dev_attr_cold_reset.attr, + &dev_attr_warm_reset.attr, + NULL +}; + +static struct attribute_group ac97_adapter_attr_group = { + .name = "ac97_operations", + .attrs = ac97_controller_device_attrs, +}; + +static const struct attribute_group *ac97_adapter_groups[] = { + &ac97_adapter_attr_group, + NULL, +}; + +static void ac97_del_adapter(struct ac97_controller *ac97_ctrl) +{ + mutex_lock(&ac97_controllers_mutex); + ac97_ctrl_codecs_unregister(ac97_ctrl); + list_del(&ac97_ctrl->controllers); + mutex_unlock(&ac97_controllers_mutex); + + device_unregister(&ac97_ctrl->adap); +} + +static void ac97_adapter_release(struct device *dev) +{ + struct ac97_controller *ac97_ctrl; + + ac97_ctrl = to_ac97_controller(dev); + idr_remove(&ac97_adapter_idr, ac97_ctrl->nr); + dev_dbg(&ac97_ctrl->adap, "adapter unregistered by %s\n", + dev_name(ac97_ctrl->parent)); +} + +static const struct device_type ac97_adapter_type = { + .groups = ac97_adapter_groups, + .release = ac97_adapter_release, +}; + +static int ac97_add_adapter(struct ac97_controller *ac97_ctrl) +{ + int ret; + + mutex_lock(&ac97_controllers_mutex); + ret = idr_alloc(&ac97_adapter_idr, ac97_ctrl, 0, 0, GFP_KERNEL); + ac97_ctrl->nr = ret; + if (ret >= 0) { + dev_set_name(&ac97_ctrl->adap, "ac97-%d", ret); + ac97_ctrl->adap.type = &ac97_adapter_type; + ac97_ctrl->adap.parent = ac97_ctrl->parent; + ret = device_register(&ac97_ctrl->adap); + if (ret) + put_device(&ac97_ctrl->adap); + } + if (!ret) + list_add(&ac97_ctrl->controllers, &ac97_controllers); + mutex_unlock(&ac97_controllers_mutex); + + if (!ret) + dev_dbg(&ac97_ctrl->adap, "adapter registered by %s\n", + dev_name(ac97_ctrl->parent)); + return ret; +} + +/** + * snd_ac97_controller_register - register an ac97 controller + * @ops: the ac97 bus operations + * @dev: the device providing the ac97 DC function + * @slots_available: mask of the ac97 codecs that can be scanned and probed + * bit0 => codec 0, bit1 => codec 1 ... bit 3 => codec 3 + * + * Register a digital controller which can control up to 4 ac97 codecs. This is + * the controller side of the AC97 AC-link, while the slave side are the codecs. + * + * Returns a valid controller upon success, negative pointer value upon error + */ +struct ac97_controller *snd_ac97_controller_register( + const struct ac97_controller_ops *ops, struct device *dev, + unsigned short slots_available, void **codecs_pdata) +{ + struct ac97_controller *ac97_ctrl; + int ret, i; + + ac97_ctrl = kzalloc(sizeof(*ac97_ctrl), GFP_KERNEL); + if (!ac97_ctrl) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < AC97_BUS_MAX_CODECS && codecs_pdata; i++) + ac97_ctrl->codecs_pdata[i] = codecs_pdata[i]; + + ac97_ctrl->ops = ops; + ac97_ctrl->slots_available = slots_available; + ac97_ctrl->parent = dev; + ret = ac97_add_adapter(ac97_ctrl); + + if (ret) + goto err; + ac97_bus_reset(ac97_ctrl); + ac97_bus_scan(ac97_ctrl); + + return ac97_ctrl; +err: + kfree(ac97_ctrl); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(snd_ac97_controller_register); + +/** + * snd_ac97_controller_unregister - unregister an ac97 controller + * @ac97_ctrl: the device previously provided to ac97_controller_register() + * + */ +void snd_ac97_controller_unregister(struct ac97_controller *ac97_ctrl) +{ + ac97_del_adapter(ac97_ctrl); +} +EXPORT_SYMBOL_GPL(snd_ac97_controller_unregister); + +#ifdef CONFIG_PM +static int ac97_pm_runtime_suspend(struct device *dev) +{ + struct ac97_codec_device *codec = to_ac97_device(dev); + int ret = pm_generic_runtime_suspend(dev); + + if (ret == 0 && dev->driver) { + if (pm_runtime_is_irq_safe(dev)) + clk_disable(codec->clk); + else + clk_disable_unprepare(codec->clk); + } + + return ret; +} + +static int ac97_pm_runtime_resume(struct device *dev) +{ + struct ac97_codec_device *codec = to_ac97_device(dev); + int ret; + + if (dev->driver) { + if (pm_runtime_is_irq_safe(dev)) + ret = clk_enable(codec->clk); + else + ret = clk_prepare_enable(codec->clk); + if (ret) + return ret; + } + + return pm_generic_runtime_resume(dev); +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops ac97_pm = { + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, + SET_RUNTIME_PM_OPS( + ac97_pm_runtime_suspend, + ac97_pm_runtime_resume, + NULL) +}; + +static int ac97_get_enable_clk(struct ac97_codec_device *adev) +{ + int ret; + + adev->clk = clk_get(&adev->dev, "ac97_clk"); + if (IS_ERR(adev->clk)) + return PTR_ERR(adev->clk); + + ret = clk_prepare_enable(adev->clk); + if (ret) + clk_put(adev->clk); + + return ret; +} + +static void ac97_put_disable_clk(struct ac97_codec_device *adev) +{ + clk_disable_unprepare(adev->clk); + clk_put(adev->clk); +} + +static ssize_t vendor_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ac97_codec_device *codec = to_ac97_device(dev); + + return sprintf(buf, "%08x", codec->vendor_id); +} +DEVICE_ATTR_RO(vendor_id); + +static struct attribute *ac97_dev_attrs[] = { + &dev_attr_vendor_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ac97_dev); + +static int ac97_bus_match(struct device *dev, struct device_driver *drv) +{ + struct ac97_codec_device *adev = to_ac97_device(dev); + struct ac97_codec_driver *adrv = to_ac97_driver(drv); + const struct ac97_id *id = adrv->id_table; + int i = 0; + + if (adev->vendor_id == 0x0 || adev->vendor_id == 0xffffffff) + return false; + + do { + if (ac97_ids_match(id[i].id, adev->vendor_id, id[i].mask)) + return true; + } while (id[i++].id); + + return false; +} + +static int ac97_bus_probe(struct device *dev) +{ + struct ac97_codec_device *adev = to_ac97_device(dev); + struct ac97_codec_driver *adrv = to_ac97_driver(dev->driver); + int ret; + + ret = ac97_get_enable_clk(adev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + ret = adrv->probe(adev); + if (ret == 0) + return 0; + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + ac97_put_disable_clk(adev); + + return ret; +} + +static int ac97_bus_remove(struct device *dev) +{ + struct ac97_codec_device *adev = to_ac97_device(dev); + struct ac97_codec_driver *adrv = to_ac97_driver(dev->driver); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + ret = adrv->remove(adev); + pm_runtime_put_noidle(dev); + if (ret == 0) + ac97_put_disable_clk(adev); + + pm_runtime_disable(dev); + + return ret; +} + +static struct bus_type ac97_bus_type = { + .name = "ac97bus", + .dev_groups = ac97_dev_groups, + .match = ac97_bus_match, + .pm = &ac97_pm, + .probe = ac97_bus_probe, + .remove = ac97_bus_remove, +}; + +static int __init ac97_bus_init(void) +{ + return bus_register(&ac97_bus_type); +} +subsys_initcall(ac97_bus_init); + +static void __exit ac97_bus_exit(void) +{ + bus_unregister(&ac97_bus_type); +} +module_exit(ac97_bus_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Jarzmik "); diff --git a/sound/ac97/codec.c b/sound/ac97/codec.c new file mode 100644 index 000000000..a835f0374 --- /dev/null +++ b/sound/ac97/codec.c @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2016 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include /* For compat_ac97_* */ + diff --git a/sound/ac97/snd_ac97_compat.c b/sound/ac97/snd_ac97_compat.c new file mode 100644 index 000000000..8bab44f74 --- /dev/null +++ b/sound/ac97/snd_ac97_compat.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include "ac97_core.h" + +static void compat_ac97_release(struct device *dev) +{ + kfree(to_ac97_t(dev)); +} + +static void compat_ac97_reset(struct snd_ac97 *ac97) +{ + struct ac97_codec_device *adev = to_ac97_device(ac97->private_data); + struct ac97_controller *actrl = adev->ac97_ctrl; + + if (actrl->ops->reset) + actrl->ops->reset(actrl); +} + +static void compat_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct ac97_codec_device *adev = to_ac97_device(ac97->private_data); + struct ac97_controller *actrl = adev->ac97_ctrl; + + if (actrl->ops->warm_reset) + actrl->ops->warm_reset(actrl); +} + +static void compat_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct ac97_codec_device *adev = to_ac97_device(ac97->private_data); + struct ac97_controller *actrl = adev->ac97_ctrl; + + actrl->ops->write(actrl, ac97->num, reg, val); +} + +static unsigned short compat_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct ac97_codec_device *adev = to_ac97_device(ac97->private_data); + struct ac97_controller *actrl = adev->ac97_ctrl; + + return actrl->ops->read(actrl, ac97->num, reg); +} + +static struct snd_ac97_bus_ops compat_snd_ac97_bus_ops = { + .reset = compat_ac97_reset, + .warm_reset = compat_ac97_warm_reset, + .write = compat_ac97_write, + .read = compat_ac97_read, +}; + +static struct snd_ac97_bus compat_soc_ac97_bus = { + .ops = &compat_snd_ac97_bus_ops, +}; + +struct snd_ac97 *snd_ac97_compat_alloc(struct ac97_codec_device *adev) +{ + struct snd_ac97 *ac97; + int ret; + + ac97 = kzalloc(sizeof(struct snd_ac97), GFP_KERNEL); + if (ac97 == NULL) + return ERR_PTR(-ENOMEM); + + ac97->private_data = adev; + ac97->bus = &compat_soc_ac97_bus; + + ac97->dev.parent = &adev->dev; + ac97->dev.release = compat_ac97_release; + dev_set_name(&ac97->dev, "%s-compat", dev_name(&adev->dev)); + ret = device_register(&ac97->dev); + if (ret) { + put_device(&ac97->dev); + return ERR_PTR(ret); + } + + return ac97; +} +EXPORT_SYMBOL_GPL(snd_ac97_compat_alloc); + +void snd_ac97_compat_release(struct snd_ac97 *ac97) +{ + device_unregister(&ac97->dev); +} +EXPORT_SYMBOL_GPL(snd_ac97_compat_release); + +int snd_ac97_reset(struct snd_ac97 *ac97, bool try_warm, unsigned int id, + unsigned int id_mask) +{ + struct ac97_codec_device *adev = to_ac97_device(ac97->private_data); + struct ac97_controller *actrl = adev->ac97_ctrl; + unsigned int scanned; + + if (try_warm) { + compat_ac97_warm_reset(ac97); + scanned = snd_ac97_bus_scan_one(actrl, adev->num); + if (ac97_ids_match(scanned, adev->vendor_id, id_mask)) + return 1; + } + + compat_ac97_reset(ac97); + compat_ac97_warm_reset(ac97); + scanned = snd_ac97_bus_scan_one(actrl, adev->num); + if (ac97_ids_match(scanned, adev->vendor_id, id_mask)) + return 0; + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(snd_ac97_reset); diff --git a/sound/ac97_bus.c b/sound/ac97_bus.c new file mode 100644 index 000000000..52e4bc54c --- /dev/null +++ b/sound/ac97_bus.c @@ -0,0 +1,113 @@ +/* + * Linux driver model AC97 bus interface + * + * Author: Nicolas Pitre + * Created: Jan 14, 2005 + * Copyright: (C) MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +/* + * snd_ac97_check_id() - Reads and checks the vendor ID of the device + * @ac97: The AC97 device to check + * @id: The ID to compare to + * @id_mask: Mask that is applied to the device ID before comparing to @id + * + * If @id is 0 this function returns true if the read device vendor ID is + * a valid ID. If @id is non 0 this functions returns true if @id + * matches the read vendor ID. Otherwise the function returns false. + */ +static bool snd_ac97_check_id(struct snd_ac97 *ac97, unsigned int id, + unsigned int id_mask) +{ + ac97->id = ac97->bus->ops->read(ac97, AC97_VENDOR_ID1) << 16; + ac97->id |= ac97->bus->ops->read(ac97, AC97_VENDOR_ID2); + + if (ac97->id == 0x0 || ac97->id == 0xffffffff) + return false; + + if (id != 0 && id != (ac97->id & id_mask)) + return false; + + return true; +} + +/** + * snd_ac97_reset() - Reset AC'97 device + * @ac97: The AC'97 device to reset + * @try_warm: Try a warm reset first + * @id: Expected device vendor ID + * @id_mask: Mask that is applied to the device ID before comparing to @id + * + * This function resets the AC'97 device. If @try_warm is true the function + * first performs a warm reset. If the warm reset is successful the function + * returns 1. Otherwise or if @try_warm is false the function issues cold reset + * followed by a warm reset. If this is successful the function returns 0, + * otherwise a negative error code. If @id is 0 any valid device ID will be + * accepted, otherwise only the ID that matches @id and @id_mask is accepted. + */ +int snd_ac97_reset(struct snd_ac97 *ac97, bool try_warm, unsigned int id, + unsigned int id_mask) +{ + struct snd_ac97_bus_ops *ops = ac97->bus->ops; + + if (try_warm && ops->warm_reset) { + ops->warm_reset(ac97); + if (snd_ac97_check_id(ac97, id, id_mask)) + return 1; + } + + if (ops->reset) + ops->reset(ac97); + if (ops->warm_reset) + ops->warm_reset(ac97); + + if (snd_ac97_check_id(ac97, id, id_mask)) + return 0; + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(snd_ac97_reset); + +/* + * Let drivers decide whether they want to support given codec from their + * probe method. Drivers have direct access to the struct snd_ac97 + * structure and may decide based on the id field amongst other things. + */ +static int ac97_bus_match(struct device *dev, struct device_driver *drv) +{ + return 1; +} + +struct bus_type ac97_bus_type = { + .name = "ac97", + .match = ac97_bus_match, +}; + +static int __init ac97_bus_init(void) +{ + return bus_register(&ac97_bus_type); +} + +subsys_initcall(ac97_bus_init); + +static void __exit ac97_bus_exit(void) +{ + bus_unregister(&ac97_bus_type); +} + +module_exit(ac97_bus_exit); + +EXPORT_SYMBOL(ac97_bus_type); + +MODULE_LICENSE("GPL"); diff --git a/sound/aoa/Kconfig b/sound/aoa/Kconfig new file mode 100644 index 000000000..c081e18b9 --- /dev/null +++ b/sound/aoa/Kconfig @@ -0,0 +1,17 @@ +menuconfig SND_AOA + tristate "Apple Onboard Audio driver" + depends on PPC_PMAC + select SND_PCM + ---help--- + This option enables the new driver for the various + Apple Onboard Audio components. + +if SND_AOA + +source "sound/aoa/fabrics/Kconfig" + +source "sound/aoa/codecs/Kconfig" + +source "sound/aoa/soundbus/Kconfig" + +endif # SND_AOA diff --git a/sound/aoa/Makefile b/sound/aoa/Makefile new file mode 100644 index 000000000..a8c037f90 --- /dev/null +++ b/sound/aoa/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_SND_AOA) += core/ +obj-$(CONFIG_SND_AOA_SOUNDBUS) += soundbus/ +obj-$(CONFIG_SND_AOA) += fabrics/ +obj-$(CONFIG_SND_AOA) += codecs/ diff --git a/sound/aoa/aoa-gpio.h b/sound/aoa/aoa-gpio.h new file mode 100644 index 000000000..6065b0344 --- /dev/null +++ b/sound/aoa/aoa-gpio.h @@ -0,0 +1,83 @@ +/* + * Apple Onboard Audio GPIO definitions + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#ifndef __AOA_GPIO_H +#define __AOA_GPIO_H +#include +#include +#include + +typedef void (*notify_func_t)(void *data); + +enum notify_type { + AOA_NOTIFY_HEADPHONE, + AOA_NOTIFY_LINE_IN, + AOA_NOTIFY_LINE_OUT, +}; + +struct gpio_runtime; +struct gpio_methods { + /* for initialisation/de-initialisation of the GPIO layer */ + void (*init)(struct gpio_runtime *rt); + void (*exit)(struct gpio_runtime *rt); + + /* turn off headphone, speakers, lineout */ + void (*all_amps_off)(struct gpio_runtime *rt); + /* turn headphone, speakers, lineout back to previous setting */ + void (*all_amps_restore)(struct gpio_runtime *rt); + + void (*set_headphone)(struct gpio_runtime *rt, int on); + void (*set_speakers)(struct gpio_runtime *rt, int on); + void (*set_lineout)(struct gpio_runtime *rt, int on); + void (*set_master)(struct gpio_runtime *rt, int on); + + int (*get_headphone)(struct gpio_runtime *rt); + int (*get_speakers)(struct gpio_runtime *rt); + int (*get_lineout)(struct gpio_runtime *rt); + int (*get_master)(struct gpio_runtime *rt); + + void (*set_hw_reset)(struct gpio_runtime *rt, int on); + + /* use this to be notified of any events. The notification + * function is passed the data, and is called in process + * context by the use of schedule_work. + * The interface for it is that setting a function to NULL + * removes it, and they return 0 if the operation succeeded, + * and -EBUSY if the notification is already assigned by + * someone else. */ + int (*set_notify)(struct gpio_runtime *rt, + enum notify_type type, + notify_func_t notify, + void *data); + /* returns 0 if not plugged in, 1 if plugged in + * or a negative error code */ + int (*get_detect)(struct gpio_runtime *rt, + enum notify_type type); +}; + +struct gpio_notification { + struct delayed_work work; + notify_func_t notify; + void *data; + void *gpio_private; + struct mutex mutex; +}; + +struct gpio_runtime { + /* to be assigned by fabric */ + struct device_node *node; + /* since everyone needs this pointer anyway... */ + struct gpio_methods *methods; + /* to be used by the gpio implementation */ + int implementation_private; + struct gpio_notification headphone_notify; + struct gpio_notification line_in_notify; + struct gpio_notification line_out_notify; +}; + +#endif /* __AOA_GPIO_H */ diff --git a/sound/aoa/aoa.h b/sound/aoa/aoa.h new file mode 100644 index 000000000..34c668f27 --- /dev/null +++ b/sound/aoa/aoa.h @@ -0,0 +1,129 @@ +/* + * Apple Onboard Audio definitions + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#ifndef __AOA_H +#define __AOA_H +#include +#include +#include +#include +#include +#include "aoa-gpio.h" +#include "soundbus/soundbus.h" + +#define MAX_CODEC_NAME_LEN 32 + +struct aoa_codec { + char name[MAX_CODEC_NAME_LEN]; + + struct module *owner; + + /* called when the fabric wants to init this codec. + * Do alsa card manipulations from here. */ + int (*init)(struct aoa_codec *codec); + + /* called when the fabric is done with the codec. + * The alsa card will be cleaned up so don't bother. */ + void (*exit)(struct aoa_codec *codec); + + /* May be NULL, but can be used by the fabric. + * Refcounting is the codec driver's responsibility */ + struct device_node *node; + + /* assigned by fabric before init() is called, points + * to the soundbus device. Cannot be NULL. */ + struct soundbus_dev *soundbus_dev; + + /* assigned by the fabric before init() is called, points + * to the fabric's gpio runtime record for the relevant + * device. */ + struct gpio_runtime *gpio; + + /* assigned by the fabric before init() is called, contains + * a codec specific bitmask of what outputs and inputs are + * actually connected */ + u32 connected; + + /* data the fabric can associate with this structure */ + void *fabric_data; + + /* private! */ + struct list_head list; + struct aoa_fabric *fabric; +}; + +/* return 0 on success */ +extern int +aoa_codec_register(struct aoa_codec *codec); +extern void +aoa_codec_unregister(struct aoa_codec *codec); + +#define MAX_LAYOUT_NAME_LEN 32 + +struct aoa_fabric { + char name[MAX_LAYOUT_NAME_LEN]; + + struct module *owner; + + /* once codecs register, they are passed here after. + * They are of course not initialised, since the + * fabric is responsible for initialising some fields + * in the codec structure! */ + int (*found_codec)(struct aoa_codec *codec); + /* called for each codec when it is removed, + * also in the case that aoa_fabric_unregister + * is called and all codecs are removed + * from this fabric. + * Also called if found_codec returned 0 but + * the codec couldn't initialise. */ + void (*remove_codec)(struct aoa_codec *codec); + /* If found_codec returned 0, and the codec + * could be initialised, this is called. */ + void (*attached_codec)(struct aoa_codec *codec); +}; + +/* return 0 on success, -EEXIST if another fabric is + * registered, -EALREADY if the same fabric is registered. + * Passing NULL can be used to test for the presence + * of another fabric, if -EALREADY is returned there is + * no other fabric present. + * In the case that the function returns -EALREADY + * and the fabric passed is not NULL, all codecs + * that are not assigned yet are passed to the fabric + * again for reconsideration. */ +extern int +aoa_fabric_register(struct aoa_fabric *fabric, struct device *dev); + +/* it is vital to call this when the fabric exits! + * When calling, the remove_codec will be called + * for all codecs, unless it is NULL. */ +extern void +aoa_fabric_unregister(struct aoa_fabric *fabric); + +/* if for some reason you want to get rid of a codec + * before the fabric is removed, use this. + * Note that remove_codec is called for it! */ +extern void +aoa_fabric_unlink_codec(struct aoa_codec *codec); + +/* alsa help methods */ +struct aoa_card { + struct snd_card *alsa_card; +}; + +extern int aoa_snd_device_new(enum snd_device_type type, + void * device_data, struct snd_device_ops * ops); +extern struct snd_card *aoa_get_card(void); +extern int aoa_snd_ctl_add(struct snd_kcontrol* control); + +/* GPIO stuff */ +extern struct gpio_methods *pmf_gpio_methods; +extern struct gpio_methods *ftr_gpio_methods; +/* extern struct gpio_methods *map_gpio_methods; */ + +#endif /* __AOA_H */ diff --git a/sound/aoa/codecs/Kconfig b/sound/aoa/codecs/Kconfig new file mode 100644 index 000000000..0c68e3283 --- /dev/null +++ b/sound/aoa/codecs/Kconfig @@ -0,0 +1,24 @@ +config SND_AOA_ONYX + tristate "support Onyx chip" + select I2C + select I2C_POWERMAC + ---help--- + This option enables support for the Onyx (pcm3052) + codec chip found in the latest Apple machines + (most of those with digital audio output). + +config SND_AOA_TAS + tristate "support TAS chips" + select I2C + select I2C_POWERMAC + ---help--- + This option enables support for the tas chips + found in a lot of Apple Machines, especially + iBooks and PowerBooks without digital. + +config SND_AOA_TOONIE + tristate "support Toonie chip" + ---help--- + This option enables support for the toonie codec + found in the Mac Mini. If you have a Mac Mini and + want to hear sound, select this option. diff --git a/sound/aoa/codecs/Makefile b/sound/aoa/codecs/Makefile new file mode 100644 index 000000000..95f4c3849 --- /dev/null +++ b/sound/aoa/codecs/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-aoa-codec-onyx-objs := onyx.o +snd-aoa-codec-tas-objs := tas.o +snd-aoa-codec-toonie-objs := toonie.o + +obj-$(CONFIG_SND_AOA_ONYX) += snd-aoa-codec-onyx.o +obj-$(CONFIG_SND_AOA_TAS) += snd-aoa-codec-tas.o +obj-$(CONFIG_SND_AOA_TOONIE) += snd-aoa-codec-toonie.o diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c new file mode 100644 index 000000000..6224fd3bb --- /dev/null +++ b/sound/aoa/codecs/onyx.c @@ -0,0 +1,1061 @@ +/* + * Apple Onboard Audio driver for Onyx codec + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + * + * + * This is a driver for the pcm3052 codec chip (codenamed Onyx) + * that is present in newer Apple hardware (with digital output). + * + * The Onyx codec has the following connections (listed by the bit + * to be used in aoa_codec.connected): + * 0: analog output + * 1: digital output + * 2: line input + * 3: microphone input + * Note that even though I know of no machine that has for example + * the digital output connected but not the analog, I have handled + * all the different cases in the code so that this driver may serve + * as a good example of what to do. + * + * NOTE: This driver assumes that there's at most one chip to be + * used with one alsa card, in form of creating all kinds + * of mixer elements without regard for their existence. + * But snd-aoa assumes that there's at most one card, so + * this means you can only have one onyx on a system. This + * should probably be fixed by changing the assumption of + * having just a single card on a system, and making the + * 'card' pointer accessible to anyone who needs it instead + * of hiding it in the aoa_snd_* functions... + * + */ +#include +#include +#include +MODULE_AUTHOR("Johannes Berg "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa"); + +#include "onyx.h" +#include "../aoa.h" +#include "../soundbus/soundbus.h" + + +#define PFX "snd-aoa-codec-onyx: " + +struct onyx { + /* cache registers 65 to 80, they are write-only! */ + u8 cache[16]; + struct i2c_client *i2c; + struct aoa_codec codec; + u32 initialised:1, + spdif_locked:1, + analog_locked:1, + original_mute:2; + int open_count; + struct codec_info *codec_info; + + /* mutex serializes concurrent access to the device + * and this structure. + */ + struct mutex mutex; +}; +#define codec_to_onyx(c) container_of(c, struct onyx, codec) + +/* both return 0 if all ok, else on error */ +static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value) +{ + s32 v; + + if (reg != ONYX_REG_CONTROL) { + *value = onyx->cache[reg-FIRSTREGISTER]; + return 0; + } + v = i2c_smbus_read_byte_data(onyx->i2c, reg); + if (v < 0) { + *value = 0; + return -1; + } + *value = (u8)v; + onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value; + return 0; +} + +static int onyx_write_register(struct onyx *onyx, u8 reg, u8 value) +{ + int result; + + result = i2c_smbus_write_byte_data(onyx->i2c, reg, value); + if (!result) + onyx->cache[reg-FIRSTREGISTER] = value; + return result; +} + +/* alsa stuff */ + +static int onyx_dev_register(struct snd_device *dev) +{ + return 0; +} + +static struct snd_device_ops ops = { + .dev_register = onyx_dev_register, +}; + +/* this is necessary because most alsa mixer programs + * can't properly handle the negative range */ +#define VOLUME_RANGE_SHIFT 128 + +static int onyx_snd_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 = -128 + VOLUME_RANGE_SHIFT; + uinfo->value.integer.max = -1 + VOLUME_RANGE_SHIFT; + return 0; +} + +static int onyx_snd_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + s8 l, r; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r); + mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = l + VOLUME_RANGE_SHIFT; + ucontrol->value.integer.value[1] = r + VOLUME_RANGE_SHIFT; + + return 0; +} + +static int onyx_snd_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + s8 l, r; + + if (ucontrol->value.integer.value[0] < -128 + VOLUME_RANGE_SHIFT || + ucontrol->value.integer.value[0] > -1 + VOLUME_RANGE_SHIFT) + return -EINVAL; + if (ucontrol->value.integer.value[1] < -128 + VOLUME_RANGE_SHIFT || + ucontrol->value.integer.value[1] > -1 + VOLUME_RANGE_SHIFT) + return -EINVAL; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l); + onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r); + + if (l + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[0] && + r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) { + mutex_unlock(&onyx->mutex); + return 0; + } + + onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, + ucontrol->value.integer.value[0] + - VOLUME_RANGE_SHIFT); + onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, + ucontrol->value.integer.value[1] + - VOLUME_RANGE_SHIFT); + mutex_unlock(&onyx->mutex); + + return 1; +} + +static const struct snd_kcontrol_new volume_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = onyx_snd_vol_info, + .get = onyx_snd_vol_get, + .put = onyx_snd_vol_put, +}; + +/* like above, this is necessary because a lot + * of alsa mixer programs don't handle ranges + * that don't start at 0 properly. + * even alsamixer is one of them... */ +#define INPUTGAIN_RANGE_SHIFT (-3) + +static int onyx_snd_inputgain_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 = 3 + INPUTGAIN_RANGE_SHIFT; + uinfo->value.integer.max = 28 + INPUTGAIN_RANGE_SHIFT; + return 0; +} + +static int onyx_snd_inputgain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 ig; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &ig); + mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = + (ig & ONYX_ADC_PGA_GAIN_MASK) + INPUTGAIN_RANGE_SHIFT; + + return 0; +} + +static int onyx_snd_inputgain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 v, n; + + if (ucontrol->value.integer.value[0] < 3 + INPUTGAIN_RANGE_SHIFT || + ucontrol->value.integer.value[0] > 28 + INPUTGAIN_RANGE_SHIFT) + return -EINVAL; + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v); + n = v; + n &= ~ONYX_ADC_PGA_GAIN_MASK; + n |= (ucontrol->value.integer.value[0] - INPUTGAIN_RANGE_SHIFT) + & ONYX_ADC_PGA_GAIN_MASK; + onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, n); + mutex_unlock(&onyx->mutex); + + return n != v; +} + +static const struct snd_kcontrol_new inputgain_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = onyx_snd_inputgain_info, + .get = onyx_snd_inputgain_get, + .put = onyx_snd_inputgain_put, +}; + +static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "Line-In", "Microphone" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int onyx_snd_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + s8 v; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v); + mutex_unlock(&onyx->mutex); + + ucontrol->value.enumerated.item[0] = !!(v&ONYX_ADC_INPUT_MIC); + + return 0; +} + +static void onyx_set_capture_source(struct onyx *onyx, int mic) +{ + s8 v; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v); + v &= ~ONYX_ADC_INPUT_MIC; + if (mic) + v |= ONYX_ADC_INPUT_MIC; + onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, v); + mutex_unlock(&onyx->mutex); +} + +static int onyx_snd_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + onyx_set_capture_source(snd_kcontrol_chip(kcontrol), + ucontrol->value.enumerated.item[0]); + return 1; +} + +static const struct snd_kcontrol_new capture_source_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* If we name this 'Input Source', it properly shows up in + * alsamixer as a selection, * but it's shown under the + * 'Playback' category. + * If I name it 'Capture Source', it shows up in strange + * ways (two bools of which one can be selected at a + * time) but at least it's shown in the 'Capture' + * category. + * I was told that this was due to backward compatibility, + * but I don't understand then why the mangling is *not* + * done when I name it "Input Source"..... + */ + .name = "Capture Source", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = onyx_snd_capture_source_info, + .get = onyx_snd_capture_source_get, + .put = onyx_snd_capture_source_put, +}; + +#define onyx_snd_mute_info snd_ctl_boolean_stereo_info + +static int onyx_snd_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 c; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &c); + mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = !(c & ONYX_MUTE_LEFT); + ucontrol->value.integer.value[1] = !(c & ONYX_MUTE_RIGHT); + + return 0; +} + +static int onyx_snd_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 v = 0, c = 0; + int err = -EBUSY; + + mutex_lock(&onyx->mutex); + if (onyx->analog_locked) + goto out_unlock; + + onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); + c = v; + c &= ~(ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT); + if (!ucontrol->value.integer.value[0]) + c |= ONYX_MUTE_LEFT; + if (!ucontrol->value.integer.value[1]) + c |= ONYX_MUTE_RIGHT; + err = onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, c); + + out_unlock: + mutex_unlock(&onyx->mutex); + + return !err ? (v != c) : err; +} + +static const struct snd_kcontrol_new mute_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = onyx_snd_mute_info, + .get = onyx_snd_mute_get, + .put = onyx_snd_mute_put, +}; + + +#define onyx_snd_single_bit_info snd_ctl_boolean_mono_info + +#define FLAG_POLARITY_INVERT 1 +#define FLAG_SPDIFLOCK 2 + +static int onyx_snd_single_bit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 c; + long int pv = kcontrol->private_value; + u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT; + u8 address = (pv >> 8) & 0xff; + u8 mask = pv & 0xff; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, address, &c); + mutex_unlock(&onyx->mutex); + + ucontrol->value.integer.value[0] = !!(c & mask) ^ polarity; + + return 0; +} + +static int onyx_snd_single_bit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 v = 0, c = 0; + int err; + long int pv = kcontrol->private_value; + u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT; + u8 spdiflock = (pv >> 16) & FLAG_SPDIFLOCK; + u8 address = (pv >> 8) & 0xff; + u8 mask = pv & 0xff; + + mutex_lock(&onyx->mutex); + if (spdiflock && onyx->spdif_locked) { + /* even if alsamixer doesn't care.. */ + err = -EBUSY; + goto out_unlock; + } + onyx_read_register(onyx, address, &v); + c = v; + c &= ~(mask); + if (!!ucontrol->value.integer.value[0] ^ polarity) + c |= mask; + err = onyx_write_register(onyx, address, c); + + out_unlock: + mutex_unlock(&onyx->mutex); + + return !err ? (v != c) : err; +} + +#define SINGLE_BIT(n, type, description, address, mask, flags) \ +static struct snd_kcontrol_new n##_control = { \ + .iface = SNDRV_CTL_ELEM_IFACE_##type, \ + .name = description, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = onyx_snd_single_bit_info, \ + .get = onyx_snd_single_bit_get, \ + .put = onyx_snd_single_bit_put, \ + .private_value = (flags << 16) | (address << 8) | mask \ +} + +SINGLE_BIT(spdif, + MIXER, + SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + ONYX_REG_DIG_INFO4, + ONYX_SPDIF_ENABLE, + FLAG_SPDIFLOCK); +SINGLE_BIT(ovr1, + MIXER, + "Oversampling Rate", + ONYX_REG_DAC_CONTROL, + ONYX_OVR1, + 0); +SINGLE_BIT(flt0, + MIXER, + "Fast Digital Filter Rolloff", + ONYX_REG_DAC_FILTER, + ONYX_ROLLOFF_FAST, + FLAG_POLARITY_INVERT); +SINGLE_BIT(hpf, + MIXER, + "Highpass Filter", + ONYX_REG_ADC_HPF_BYPASS, + ONYX_HPF_DISABLE, + FLAG_POLARITY_INVERT); +SINGLE_BIT(dm12, + MIXER, + "Digital De-Emphasis", + ONYX_REG_DAC_DEEMPH, + ONYX_DIGDEEMPH_CTRL, + 0); + +static int onyx_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 onyx_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* datasheet page 30, all others are 0 */ + ucontrol->value.iec958.status[0] = 0x3e; + ucontrol->value.iec958.status[1] = 0xff; + + ucontrol->value.iec958.status[3] = 0x3f; + ucontrol->value.iec958.status[4] = 0x0f; + + return 0; +} + +static const struct snd_kcontrol_new onyx_spdif_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK), + .info = onyx_spdif_info, + .get = onyx_spdif_mask_get, +}; + +static int onyx_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 v; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v); + ucontrol->value.iec958.status[0] = v & 0x3e; + + onyx_read_register(onyx, ONYX_REG_DIG_INFO2, &v); + ucontrol->value.iec958.status[1] = v; + + onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v); + ucontrol->value.iec958.status[3] = v & 0x3f; + + onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); + ucontrol->value.iec958.status[4] = v & 0x0f; + mutex_unlock(&onyx->mutex); + + return 0; +} + +static int onyx_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct onyx *onyx = snd_kcontrol_chip(kcontrol); + u8 v; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v); + v = (v & ~0x3e) | (ucontrol->value.iec958.status[0] & 0x3e); + onyx_write_register(onyx, ONYX_REG_DIG_INFO1, v); + + v = ucontrol->value.iec958.status[1]; + onyx_write_register(onyx, ONYX_REG_DIG_INFO2, v); + + onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v); + v = (v & ~0x3f) | (ucontrol->value.iec958.status[3] & 0x3f); + onyx_write_register(onyx, ONYX_REG_DIG_INFO3, v); + + onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); + v = (v & ~0x0f) | (ucontrol->value.iec958.status[4] & 0x0f); + onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v); + mutex_unlock(&onyx->mutex); + + return 1; +} + +static const struct snd_kcontrol_new onyx_spdif_ctrl = { + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = onyx_spdif_info, + .get = onyx_spdif_get, + .put = onyx_spdif_put, +}; + +/* our registers */ + +static u8 register_map[] = { + ONYX_REG_DAC_ATTEN_LEFT, + ONYX_REG_DAC_ATTEN_RIGHT, + ONYX_REG_CONTROL, + ONYX_REG_DAC_CONTROL, + ONYX_REG_DAC_DEEMPH, + ONYX_REG_DAC_FILTER, + ONYX_REG_DAC_OUTPHASE, + ONYX_REG_ADC_CONTROL, + ONYX_REG_ADC_HPF_BYPASS, + ONYX_REG_DIG_INFO1, + ONYX_REG_DIG_INFO2, + ONYX_REG_DIG_INFO3, + ONYX_REG_DIG_INFO4 +}; + +static u8 initial_values[ARRAY_SIZE(register_map)] = { + 0x80, 0x80, /* muted */ + ONYX_MRST | ONYX_SRST, /* but handled specially! */ + ONYX_MUTE_LEFT | ONYX_MUTE_RIGHT, + 0, /* no deemphasis */ + ONYX_DAC_FILTER_ALWAYS, + ONYX_OUTPHASE_INVERTED, + (-1 /*dB*/ + 8) & 0xF, /* line in selected, -1 dB gain*/ + ONYX_ADC_HPF_ALWAYS, + (1<<2), /* pcm audio */ + 2, /* category: pcm coder */ + 0, /* sampling frequency 44.1 kHz, clock accuracy level II */ + 1 /* 24 bit depth */ +}; + +/* reset registers of chip, either to initial or to previous values */ +static int onyx_register_init(struct onyx *onyx) +{ + int i; + u8 val; + u8 regs[sizeof(initial_values)]; + + if (!onyx->initialised) { + memcpy(regs, initial_values, sizeof(initial_values)); + if (onyx_read_register(onyx, ONYX_REG_CONTROL, &val)) + return -1; + val &= ~ONYX_SILICONVERSION; + val |= initial_values[3]; + regs[3] = val; + } else { + for (i=0; icache[register_map[i]-FIRSTREGISTER]; + } + + for (i=0; iinitialised = 1; + return 0; +} + +static struct transfer_info onyx_transfers[] = { + /* this is first so we can skip it if no input is present... + * No hardware exists with that, but it's here as an example + * of what to do :) */ + { + /* analog input */ + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE, + .rates = SNDRV_PCM_RATE_8000_96000, + .transfer_in = 1, + .must_be_clock_source = 0, + .tag = 0, + }, + { + /* if analog and digital are currently off, anything should go, + * so this entry describes everything we can do... */ + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE +#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE + | SNDRV_PCM_FMTBIT_COMPRESSED_16BE +#endif + , + .rates = SNDRV_PCM_RATE_8000_96000, + .tag = 0, + }, + { + /* analog output */ + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE, + .rates = SNDRV_PCM_RATE_8000_96000, + .transfer_in = 0, + .must_be_clock_source = 0, + .tag = 1, + }, + { + /* digital pcm output, also possible for analog out */ + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .transfer_in = 0, + .must_be_clock_source = 0, + .tag = 2, + }, +#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE + /* Once alsa gets supports for this kind of thing we can add it... */ + { + /* digital compressed output */ + .formats = SNDRV_PCM_FMTBIT_COMPRESSED_16BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .tag = 2, + }, +#endif + {} +}; + +static int onyx_usable(struct codec_info_item *cii, + struct transfer_info *ti, + struct transfer_info *out) +{ + u8 v; + struct onyx *onyx = cii->codec_data; + int spdif_enabled, analog_enabled; + + mutex_lock(&onyx->mutex); + onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); + spdif_enabled = !!(v & ONYX_SPDIF_ENABLE); + onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); + analog_enabled = + (v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT)) + != (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT); + mutex_unlock(&onyx->mutex); + + switch (ti->tag) { + case 0: return 1; + case 1: return analog_enabled; + case 2: return spdif_enabled; + } + return 1; +} + +static int onyx_prepare(struct codec_info_item *cii, + struct bus_info *bi, + struct snd_pcm_substream *substream) +{ + u8 v; + struct onyx *onyx = cii->codec_data; + int err = -EBUSY; + + mutex_lock(&onyx->mutex); + +#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE + if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) { + /* mute and lock analog output */ + onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); + if (onyx_write_register(onyx, + ONYX_REG_DAC_CONTROL, + v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT)) + goto out_unlock; + onyx->analog_locked = 1; + err = 0; + goto out_unlock; + } +#endif + switch (substream->runtime->rate) { + case 32000: + case 44100: + case 48000: + /* these rates are ok for all outputs */ + /* FIXME: program spdif channel control bits here so that + * userspace doesn't have to if it only plays pcm! */ + err = 0; + goto out_unlock; + default: + /* got some rate that the digital output can't do, + * so disable and lock it */ + onyx_read_register(cii->codec_data, ONYX_REG_DIG_INFO4, &v); + if (onyx_write_register(onyx, + ONYX_REG_DIG_INFO4, + v & ~ONYX_SPDIF_ENABLE)) + goto out_unlock; + onyx->spdif_locked = 1; + err = 0; + goto out_unlock; + } + + out_unlock: + mutex_unlock(&onyx->mutex); + + return err; +} + +static int onyx_open(struct codec_info_item *cii, + struct snd_pcm_substream *substream) +{ + struct onyx *onyx = cii->codec_data; + + mutex_lock(&onyx->mutex); + onyx->open_count++; + mutex_unlock(&onyx->mutex); + + return 0; +} + +static int onyx_close(struct codec_info_item *cii, + struct snd_pcm_substream *substream) +{ + struct onyx *onyx = cii->codec_data; + + mutex_lock(&onyx->mutex); + onyx->open_count--; + if (!onyx->open_count) + onyx->spdif_locked = onyx->analog_locked = 0; + mutex_unlock(&onyx->mutex); + + return 0; +} + +static int onyx_switch_clock(struct codec_info_item *cii, + enum clock_switch what) +{ + struct onyx *onyx = cii->codec_data; + + mutex_lock(&onyx->mutex); + /* this *MUST* be more elaborate later... */ + switch (what) { + case CLOCK_SWITCH_PREPARE_SLAVE: + onyx->codec.gpio->methods->all_amps_off(onyx->codec.gpio); + break; + case CLOCK_SWITCH_SLAVE: + onyx->codec.gpio->methods->all_amps_restore(onyx->codec.gpio); + break; + default: /* silence warning */ + break; + } + mutex_unlock(&onyx->mutex); + + return 0; +} + +#ifdef CONFIG_PM + +static int onyx_suspend(struct codec_info_item *cii, pm_message_t state) +{ + struct onyx *onyx = cii->codec_data; + u8 v; + int err = -ENXIO; + + mutex_lock(&onyx->mutex); + if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) + goto out_unlock; + onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV); + /* Apple does a sleep here but the datasheet says to do it on resume */ + err = 0; + out_unlock: + mutex_unlock(&onyx->mutex); + + return err; +} + +static int onyx_resume(struct codec_info_item *cii) +{ + struct onyx *onyx = cii->codec_data; + u8 v; + int err = -ENXIO; + + mutex_lock(&onyx->mutex); + + /* reset codec */ + onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); + msleep(1); + onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1); + msleep(1); + onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); + msleep(1); + + /* take codec out of suspend (if it still is after reset) */ + if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) + goto out_unlock; + onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV)); + /* FIXME: should divide by sample rate, but 8k is the lowest we go */ + msleep(2205000/8000); + /* reset all values */ + onyx_register_init(onyx); + err = 0; + out_unlock: + mutex_unlock(&onyx->mutex); + + return err; +} + +#endif /* CONFIG_PM */ + +static struct codec_info onyx_codec_info = { + .transfers = onyx_transfers, + .sysclock_factor = 256, + .bus_factor = 64, + .owner = THIS_MODULE, + .usable = onyx_usable, + .prepare = onyx_prepare, + .open = onyx_open, + .close = onyx_close, + .switch_clock = onyx_switch_clock, +#ifdef CONFIG_PM + .suspend = onyx_suspend, + .resume = onyx_resume, +#endif +}; + +static int onyx_init_codec(struct aoa_codec *codec) +{ + struct onyx *onyx = codec_to_onyx(codec); + struct snd_kcontrol *ctl; + struct codec_info *ci = &onyx_codec_info; + u8 v; + int err; + + if (!onyx->codec.gpio || !onyx->codec.gpio->methods) { + printk(KERN_ERR PFX "gpios not assigned!!\n"); + return -EINVAL; + } + + onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); + msleep(1); + onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1); + msleep(1); + onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0); + msleep(1); + + if (onyx_register_init(onyx)) { + printk(KERN_ERR PFX "failed to initialise onyx registers\n"); + return -ENODEV; + } + + if (aoa_snd_device_new(SNDRV_DEV_CODEC, onyx, &ops)) { + printk(KERN_ERR PFX "failed to create onyx snd device!\n"); + return -ENODEV; + } + + /* nothing connected? what a joke! */ + if ((onyx->codec.connected & 0xF) == 0) + return -ENOTCONN; + + /* if no inputs are present... */ + if ((onyx->codec.connected & 0xC) == 0) { + if (!onyx->codec_info) + onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL); + if (!onyx->codec_info) + return -ENOMEM; + ci = onyx->codec_info; + *ci = onyx_codec_info; + ci->transfers++; + } + + /* if no outputs are present... */ + if ((onyx->codec.connected & 3) == 0) { + if (!onyx->codec_info) + onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL); + if (!onyx->codec_info) + return -ENOMEM; + ci = onyx->codec_info; + /* this is fine as there have to be inputs + * if we end up in this part of the code */ + *ci = onyx_codec_info; + ci->transfers[1].formats = 0; + } + + if (onyx->codec.soundbus_dev->attach_codec(onyx->codec.soundbus_dev, + aoa_get_card(), + ci, onyx)) { + printk(KERN_ERR PFX "error creating onyx pcm\n"); + return -ENODEV; + } +#define ADDCTL(n) \ + do { \ + ctl = snd_ctl_new1(&n, onyx); \ + if (ctl) { \ + ctl->id.device = \ + onyx->codec.soundbus_dev->pcm->device; \ + err = aoa_snd_ctl_add(ctl); \ + if (err) \ + goto error; \ + } \ + } while (0) + + if (onyx->codec.soundbus_dev->pcm) { + /* give the user appropriate controls + * depending on what inputs are connected */ + if ((onyx->codec.connected & 0xC) == 0xC) + ADDCTL(capture_source_control); + else if (onyx->codec.connected & 4) + onyx_set_capture_source(onyx, 0); + else + onyx_set_capture_source(onyx, 1); + if (onyx->codec.connected & 0xC) + ADDCTL(inputgain_control); + + /* depending on what output is connected, + * give the user appropriate controls */ + if (onyx->codec.connected & 1) { + ADDCTL(volume_control); + ADDCTL(mute_control); + ADDCTL(ovr1_control); + ADDCTL(flt0_control); + ADDCTL(hpf_control); + ADDCTL(dm12_control); + /* spdif control defaults to off */ + } + if (onyx->codec.connected & 2) { + ADDCTL(onyx_spdif_mask); + ADDCTL(onyx_spdif_ctrl); + } + if ((onyx->codec.connected & 3) == 3) + ADDCTL(spdif_control); + /* if only S/PDIF is connected, enable it unconditionally */ + if ((onyx->codec.connected & 3) == 2) { + onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v); + v |= ONYX_SPDIF_ENABLE; + onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v); + } + } +#undef ADDCTL + printk(KERN_INFO PFX "attached to onyx codec via i2c\n"); + + return 0; + error: + onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx); + snd_device_free(aoa_get_card(), onyx); + return err; +} + +static void onyx_exit_codec(struct aoa_codec *codec) +{ + struct onyx *onyx = codec_to_onyx(codec); + + if (!onyx->codec.soundbus_dev) { + printk(KERN_ERR PFX "onyx_exit_codec called without soundbus_dev!\n"); + return; + } + onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx); +} + +static int onyx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *node = client->dev.of_node; + struct onyx *onyx; + u8 dummy; + + onyx = kzalloc(sizeof(struct onyx), GFP_KERNEL); + + if (!onyx) + return -ENOMEM; + + mutex_init(&onyx->mutex); + onyx->i2c = client; + i2c_set_clientdata(client, onyx); + + /* we try to read from register ONYX_REG_CONTROL + * to check if the codec is present */ + if (onyx_read_register(onyx, ONYX_REG_CONTROL, &dummy) != 0) { + printk(KERN_ERR PFX "failed to read control register\n"); + goto fail; + } + + strlcpy(onyx->codec.name, "onyx", MAX_CODEC_NAME_LEN); + onyx->codec.owner = THIS_MODULE; + onyx->codec.init = onyx_init_codec; + onyx->codec.exit = onyx_exit_codec; + onyx->codec.node = of_node_get(node); + + if (aoa_codec_register(&onyx->codec)) { + goto fail; + } + printk(KERN_DEBUG PFX "created and attached onyx instance\n"); + return 0; + fail: + kfree(onyx); + return -ENODEV; +} + +static int onyx_i2c_remove(struct i2c_client *client) +{ + struct onyx *onyx = i2c_get_clientdata(client); + + aoa_codec_unregister(&onyx->codec); + of_node_put(onyx->codec.node); + kfree(onyx->codec_info); + kfree(onyx); + return 0; +} + +static const struct i2c_device_id onyx_i2c_id[] = { + { "MAC,pcm3052", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c,onyx_i2c_id); + +static struct i2c_driver onyx_driver = { + .driver = { + .name = "aoa_codec_onyx", + }, + .probe = onyx_i2c_probe, + .remove = onyx_i2c_remove, + .id_table = onyx_i2c_id, +}; + +module_i2c_driver(onyx_driver); diff --git a/sound/aoa/codecs/onyx.h b/sound/aoa/codecs/onyx.h new file mode 100644 index 000000000..ffd20254f --- /dev/null +++ b/sound/aoa/codecs/onyx.h @@ -0,0 +1,75 @@ +/* + * Apple Onboard Audio driver for Onyx codec (header) + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ +#ifndef __SND_AOA_CODEC_ONYX_H +#define __SND_AOA_CODEC_ONYX_H +#include +#include +#include +#include + +/* PCM3052 register definitions */ + +/* the attenuation registers take values from + * -1 (0dB) to -127 (-63.0 dB) or others (muted) */ +#define ONYX_REG_DAC_ATTEN_LEFT 65 +#define FIRSTREGISTER ONYX_REG_DAC_ATTEN_LEFT +#define ONYX_REG_DAC_ATTEN_RIGHT 66 + +#define ONYX_REG_CONTROL 67 +# define ONYX_MRST (1<<7) +# define ONYX_SRST (1<<6) +# define ONYX_ADPSV (1<<5) +# define ONYX_DAPSV (1<<4) +# define ONYX_SILICONVERSION (1<<0) +/* all others reserved */ + +#define ONYX_REG_DAC_CONTROL 68 +# define ONYX_OVR1 (1<<6) +# define ONYX_MUTE_RIGHT (1<<1) +# define ONYX_MUTE_LEFT (1<<0) + +#define ONYX_REG_DAC_DEEMPH 69 +# define ONYX_DIGDEEMPH_SHIFT 5 +# define ONYX_DIGDEEMPH_MASK (3<= 50) + result += tas3004_bass_diff_to_treble[idx-50]; + return result; +} diff --git a/sound/aoa/codecs/tas-gain-table.h b/sound/aoa/codecs/tas-gain-table.h new file mode 100644 index 000000000..77b8e7dc5 --- /dev/null +++ b/sound/aoa/codecs/tas-gain-table.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + This is the program used to generate below table. + +#include +#include +int main() { + int dB2; + printf("/" "* This file is only included exactly once!\n"); + printf(" *\n"); + printf(" * If they'd only tell us that generating this table was\n"); + printf(" * as easy as calculating\n"); + printf(" * hwvalue = 1048576.0*exp(0.057564628*dB*2)\n"); + printf(" * :) *" "/\n"); + printf("static int tas_gaintable[] = {\n"); + printf(" 0x000000, /" "* -infinity dB *" "/\n"); + for (dB2=-140;dB2<=36;dB2++) + printf(" 0x%.6x, /" "* %-02.1f dB *" "/\n", (int)(1048576.0*exp(0.057564628*dB2)), dB2/2.0); + printf("};\n\n"); +} + +*/ + +/* This file is only included exactly once! + * + * If they'd only tell us that generating this table was + * as easy as calculating + * hwvalue = 1048576.0*exp(0.057564628*dB*2) + * :) */ +static int tas_gaintable[] = { + 0x000000, /* -infinity dB */ + 0x00014b, /* -70.0 dB */ + 0x00015f, /* -69.5 dB */ + 0x000174, /* -69.0 dB */ + 0x00018a, /* -68.5 dB */ + 0x0001a1, /* -68.0 dB */ + 0x0001ba, /* -67.5 dB */ + 0x0001d4, /* -67.0 dB */ + 0x0001f0, /* -66.5 dB */ + 0x00020d, /* -66.0 dB */ + 0x00022c, /* -65.5 dB */ + 0x00024d, /* -65.0 dB */ + 0x000270, /* -64.5 dB */ + 0x000295, /* -64.0 dB */ + 0x0002bc, /* -63.5 dB */ + 0x0002e6, /* -63.0 dB */ + 0x000312, /* -62.5 dB */ + 0x000340, /* -62.0 dB */ + 0x000372, /* -61.5 dB */ + 0x0003a6, /* -61.0 dB */ + 0x0003dd, /* -60.5 dB */ + 0x000418, /* -60.0 dB */ + 0x000456, /* -59.5 dB */ + 0x000498, /* -59.0 dB */ + 0x0004de, /* -58.5 dB */ + 0x000528, /* -58.0 dB */ + 0x000576, /* -57.5 dB */ + 0x0005c9, /* -57.0 dB */ + 0x000620, /* -56.5 dB */ + 0x00067d, /* -56.0 dB */ + 0x0006e0, /* -55.5 dB */ + 0x000748, /* -55.0 dB */ + 0x0007b7, /* -54.5 dB */ + 0x00082c, /* -54.0 dB */ + 0x0008a8, /* -53.5 dB */ + 0x00092b, /* -53.0 dB */ + 0x0009b6, /* -52.5 dB */ + 0x000a49, /* -52.0 dB */ + 0x000ae5, /* -51.5 dB */ + 0x000b8b, /* -51.0 dB */ + 0x000c3a, /* -50.5 dB */ + 0x000cf3, /* -50.0 dB */ + 0x000db8, /* -49.5 dB */ + 0x000e88, /* -49.0 dB */ + 0x000f64, /* -48.5 dB */ + 0x00104e, /* -48.0 dB */ + 0x001145, /* -47.5 dB */ + 0x00124b, /* -47.0 dB */ + 0x001361, /* -46.5 dB */ + 0x001487, /* -46.0 dB */ + 0x0015be, /* -45.5 dB */ + 0x001708, /* -45.0 dB */ + 0x001865, /* -44.5 dB */ + 0x0019d8, /* -44.0 dB */ + 0x001b60, /* -43.5 dB */ + 0x001cff, /* -43.0 dB */ + 0x001eb7, /* -42.5 dB */ + 0x002089, /* -42.0 dB */ + 0x002276, /* -41.5 dB */ + 0x002481, /* -41.0 dB */ + 0x0026ab, /* -40.5 dB */ + 0x0028f5, /* -40.0 dB */ + 0x002b63, /* -39.5 dB */ + 0x002df5, /* -39.0 dB */ + 0x0030ae, /* -38.5 dB */ + 0x003390, /* -38.0 dB */ + 0x00369e, /* -37.5 dB */ + 0x0039db, /* -37.0 dB */ + 0x003d49, /* -36.5 dB */ + 0x0040ea, /* -36.0 dB */ + 0x0044c3, /* -35.5 dB */ + 0x0048d6, /* -35.0 dB */ + 0x004d27, /* -34.5 dB */ + 0x0051b9, /* -34.0 dB */ + 0x005691, /* -33.5 dB */ + 0x005bb2, /* -33.0 dB */ + 0x006121, /* -32.5 dB */ + 0x0066e3, /* -32.0 dB */ + 0x006cfb, /* -31.5 dB */ + 0x007370, /* -31.0 dB */ + 0x007a48, /* -30.5 dB */ + 0x008186, /* -30.0 dB */ + 0x008933, /* -29.5 dB */ + 0x009154, /* -29.0 dB */ + 0x0099f1, /* -28.5 dB */ + 0x00a310, /* -28.0 dB */ + 0x00acba, /* -27.5 dB */ + 0x00b6f6, /* -27.0 dB */ + 0x00c1cd, /* -26.5 dB */ + 0x00cd49, /* -26.0 dB */ + 0x00d973, /* -25.5 dB */ + 0x00e655, /* -25.0 dB */ + 0x00f3fb, /* -24.5 dB */ + 0x010270, /* -24.0 dB */ + 0x0111c0, /* -23.5 dB */ + 0x0121f9, /* -23.0 dB */ + 0x013328, /* -22.5 dB */ + 0x01455b, /* -22.0 dB */ + 0x0158a2, /* -21.5 dB */ + 0x016d0e, /* -21.0 dB */ + 0x0182af, /* -20.5 dB */ + 0x019999, /* -20.0 dB */ + 0x01b1de, /* -19.5 dB */ + 0x01cb94, /* -19.0 dB */ + 0x01e6cf, /* -18.5 dB */ + 0x0203a7, /* -18.0 dB */ + 0x022235, /* -17.5 dB */ + 0x024293, /* -17.0 dB */ + 0x0264db, /* -16.5 dB */ + 0x02892c, /* -16.0 dB */ + 0x02afa3, /* -15.5 dB */ + 0x02d862, /* -15.0 dB */ + 0x03038a, /* -14.5 dB */ + 0x033142, /* -14.0 dB */ + 0x0361af, /* -13.5 dB */ + 0x0394fa, /* -13.0 dB */ + 0x03cb50, /* -12.5 dB */ + 0x0404de, /* -12.0 dB */ + 0x0441d5, /* -11.5 dB */ + 0x048268, /* -11.0 dB */ + 0x04c6d0, /* -10.5 dB */ + 0x050f44, /* -10.0 dB */ + 0x055c04, /* -9.5 dB */ + 0x05ad50, /* -9.0 dB */ + 0x06036e, /* -8.5 dB */ + 0x065ea5, /* -8.0 dB */ + 0x06bf44, /* -7.5 dB */ + 0x07259d, /* -7.0 dB */ + 0x079207, /* -6.5 dB */ + 0x0804dc, /* -6.0 dB */ + 0x087e80, /* -5.5 dB */ + 0x08ff59, /* -5.0 dB */ + 0x0987d5, /* -4.5 dB */ + 0x0a1866, /* -4.0 dB */ + 0x0ab189, /* -3.5 dB */ + 0x0b53be, /* -3.0 dB */ + 0x0bff91, /* -2.5 dB */ + 0x0cb591, /* -2.0 dB */ + 0x0d765a, /* -1.5 dB */ + 0x0e4290, /* -1.0 dB */ + 0x0f1adf, /* -0.5 dB */ + 0x100000, /* 0.0 dB */ + 0x10f2b4, /* 0.5 dB */ + 0x11f3c9, /* 1.0 dB */ + 0x13041a, /* 1.5 dB */ + 0x14248e, /* 2.0 dB */ + 0x15561a, /* 2.5 dB */ + 0x1699c0, /* 3.0 dB */ + 0x17f094, /* 3.5 dB */ + 0x195bb8, /* 4.0 dB */ + 0x1adc61, /* 4.5 dB */ + 0x1c73d5, /* 5.0 dB */ + 0x1e236d, /* 5.5 dB */ + 0x1fec98, /* 6.0 dB */ + 0x21d0d9, /* 6.5 dB */ + 0x23d1cd, /* 7.0 dB */ + 0x25f125, /* 7.5 dB */ + 0x2830af, /* 8.0 dB */ + 0x2a9254, /* 8.5 dB */ + 0x2d1818, /* 9.0 dB */ + 0x2fc420, /* 9.5 dB */ + 0x3298b0, /* 10.0 dB */ + 0x35982f, /* 10.5 dB */ + 0x38c528, /* 11.0 dB */ + 0x3c224c, /* 11.5 dB */ + 0x3fb278, /* 12.0 dB */ + 0x4378b0, /* 12.5 dB */ + 0x477829, /* 13.0 dB */ + 0x4bb446, /* 13.5 dB */ + 0x5030a1, /* 14.0 dB */ + 0x54f106, /* 14.5 dB */ + 0x59f980, /* 15.0 dB */ + 0x5f4e52, /* 15.5 dB */ + 0x64f403, /* 16.0 dB */ + 0x6aef5e, /* 16.5 dB */ + 0x714575, /* 17.0 dB */ + 0x77fbaa, /* 17.5 dB */ + 0x7f17af, /* 18.0 dB */ +}; + diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c new file mode 100644 index 000000000..15c05755d --- /dev/null +++ b/sound/aoa/codecs/tas.c @@ -0,0 +1,948 @@ +/* + * Apple Onboard Audio driver for tas codec + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + * + * Open questions: + * - How to distinguish between 3004 and versions? + * + * FIXMEs: + * - This codec driver doesn't honour the 'connected' + * property of the aoa_codec struct, hence if + * it is used in machines where not everything is + * connected it will display wrong mixer elements. + * - Driver assumes that the microphone is always + * monaureal and connected to the right channel of + * the input. This should also be a codec-dependent + * flag, maybe the codec should have 3 different + * bits for the three different possibilities how + * it can be hooked up... + * But as long as I don't see any hardware hooked + * up that way... + * - As Apple notes in their code, the tas3004 seems + * to delay the right channel by one sample. You can + * see this when for example recording stereo in + * audacity, or recording the tas output via cable + * on another machine (use a sinus generator or so). + * I tried programming the BiQuads but couldn't + * make the delay work, maybe someone can read the + * datasheet and fix it. The relevant Apple comment + * is in AppleTAS3004Audio.cpp lines 1637 ff. Note + * that their comment describing how they program + * the filters sucks... + * + * Other things: + * - this should actually register *two* aoa_codec + * structs since it has two inputs. Then it must + * use the prepare callback to forbid running the + * secondary output on a different clock. + * Also, whatever bus knows how to do this must + * provide two soundbus_dev devices and the fabric + * must be able to link them correctly. + * + * I don't even know if Apple ever uses the second + * port on the tas3004 though, I don't think their + * i2s controllers can even do it. OTOH, they all + * derive the clocks from common clocks, so it + * might just be possible. The framework allows the + * codec to refine the transfer_info items in the + * usable callback, so we can simply remove the + * rates the second instance is not using when it + * actually is in use. + * Maybe we'll need to make the sound busses have + * a 'clock group id' value so the codec can + * determine if the two outputs can be driven at + * the same time. But that is likely overkill, up + * to the fabric to not link them up incorrectly, + * and up to the hardware designer to not wire + * them up in some weird unusable way. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Johannes Berg "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("tas codec driver for snd-aoa"); + +#include "tas.h" +#include "tas-gain-table.h" +#include "tas-basstreble.h" +#include "../aoa.h" +#include "../soundbus/soundbus.h" + +#define PFX "snd-aoa-codec-tas: " + + +struct tas { + struct aoa_codec codec; + struct i2c_client *i2c; + u32 mute_l:1, mute_r:1 , + controls_created:1 , + drc_enabled:1, + hw_enabled:1; + u8 cached_volume_l, cached_volume_r; + u8 mixer_l[3], mixer_r[3]; + u8 bass, treble; + u8 acr; + int drc_range; + /* protects hardware access against concurrency from + * userspace when hitting controls and during + * codec init/suspend/resume */ + struct mutex mtx; +}; + +static int tas_reset_init(struct tas *tas); + +static struct tas *codec_to_tas(struct aoa_codec *codec) +{ + return container_of(codec, struct tas, codec); +} + +static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data) +{ + if (len == 1) + return i2c_smbus_write_byte_data(tas->i2c, reg, *data); + else + return i2c_smbus_write_i2c_block_data(tas->i2c, reg, len, data); +} + +static void tas3004_set_drc(struct tas *tas) +{ + unsigned char val[6]; + + if (tas->drc_enabled) + val[0] = 0x50; /* 3:1 above threshold */ + else + val[0] = 0x51; /* disabled */ + val[1] = 0x02; /* 1:1 below threshold */ + if (tas->drc_range > 0xef) + val[2] = 0xef; + else if (tas->drc_range < 0) + val[2] = 0x00; + else + val[2] = tas->drc_range; + val[3] = 0xb0; + val[4] = 0x60; + val[5] = 0xa0; + + tas_write_reg(tas, TAS_REG_DRC, 6, val); +} + +static void tas_set_treble(struct tas *tas) +{ + u8 tmp; + + tmp = tas3004_treble(tas->treble); + tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp); +} + +static void tas_set_bass(struct tas *tas) +{ + u8 tmp; + + tmp = tas3004_bass(tas->bass); + tas_write_reg(tas, TAS_REG_BASS, 1, &tmp); +} + +static void tas_set_volume(struct tas *tas) +{ + u8 block[6]; + int tmp; + u8 left, right; + + left = tas->cached_volume_l; + right = tas->cached_volume_r; + + if (left > 177) left = 177; + if (right > 177) right = 177; + + if (tas->mute_l) left = 0; + if (tas->mute_r) right = 0; + + /* analysing the volume and mixer tables shows + * that they are similar enough when we shift + * the mixer table down by 4 bits. The error + * is miniscule, in just one item the error + * is 1, at a value of 0x07f17b (mixer table + * value is 0x07f17a) */ + tmp = tas_gaintable[left]; + block[0] = tmp>>20; + block[1] = tmp>>12; + block[2] = tmp>>4; + tmp = tas_gaintable[right]; + block[3] = tmp>>20; + block[4] = tmp>>12; + block[5] = tmp>>4; + tas_write_reg(tas, TAS_REG_VOL, 6, block); +} + +static void tas_set_mixer(struct tas *tas) +{ + u8 block[9]; + int tmp, i; + u8 val; + + for (i=0;i<3;i++) { + val = tas->mixer_l[i]; + if (val > 177) val = 177; + tmp = tas_gaintable[val]; + block[3*i+0] = tmp>>16; + block[3*i+1] = tmp>>8; + block[3*i+2] = tmp; + } + tas_write_reg(tas, TAS_REG_LMIX, 9, block); + + for (i=0;i<3;i++) { + val = tas->mixer_r[i]; + if (val > 177) val = 177; + tmp = tas_gaintable[val]; + block[3*i+0] = tmp>>16; + block[3*i+1] = tmp>>8; + block[3*i+2] = tmp; + } + tas_write_reg(tas, TAS_REG_RMIX, 9, block); +} + +/* alsa stuff */ + +static int tas_dev_register(struct snd_device *dev) +{ + return 0; +} + +static struct snd_device_ops ops = { + .dev_register = tas_dev_register, +}; + +static int tas_snd_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 = 177; + return 0; +} + +static int tas_snd_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + ucontrol->value.integer.value[0] = tas->cached_volume_l; + ucontrol->value.integer.value[1] = tas->cached_volume_r; + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_snd_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] < 0 || + ucontrol->value.integer.value[0] > 177) + return -EINVAL; + if (ucontrol->value.integer.value[1] < 0 || + ucontrol->value.integer.value[1] > 177) + return -EINVAL; + + mutex_lock(&tas->mtx); + if (tas->cached_volume_l == ucontrol->value.integer.value[0] + && tas->cached_volume_r == ucontrol->value.integer.value[1]) { + mutex_unlock(&tas->mtx); + return 0; + } + + tas->cached_volume_l = ucontrol->value.integer.value[0]; + tas->cached_volume_r = ucontrol->value.integer.value[1]; + if (tas->hw_enabled) + tas_set_volume(tas); + mutex_unlock(&tas->mtx); + return 1; +} + +static const struct snd_kcontrol_new volume_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tas_snd_vol_info, + .get = tas_snd_vol_get, + .put = tas_snd_vol_put, +}; + +#define tas_snd_mute_info snd_ctl_boolean_stereo_info + +static int tas_snd_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + ucontrol->value.integer.value[0] = !tas->mute_l; + ucontrol->value.integer.value[1] = !tas->mute_r; + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_snd_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + if (tas->mute_l == !ucontrol->value.integer.value[0] + && tas->mute_r == !ucontrol->value.integer.value[1]) { + mutex_unlock(&tas->mtx); + return 0; + } + + tas->mute_l = !ucontrol->value.integer.value[0]; + tas->mute_r = !ucontrol->value.integer.value[1]; + if (tas->hw_enabled) + tas_set_volume(tas); + mutex_unlock(&tas->mtx); + return 1; +} + +static const struct snd_kcontrol_new mute_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tas_snd_mute_info, + .get = tas_snd_mute_get, + .put = tas_snd_mute_put, +}; + +static int tas_snd_mixer_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 = 177; + return 0; +} + +static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + + mutex_lock(&tas->mtx); + ucontrol->value.integer.value[0] = tas->mixer_l[idx]; + ucontrol->value.integer.value[1] = tas->mixer_r[idx]; + mutex_unlock(&tas->mtx); + + return 0; +} + +static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + int idx = kcontrol->private_value; + + mutex_lock(&tas->mtx); + if (tas->mixer_l[idx] == ucontrol->value.integer.value[0] + && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) { + mutex_unlock(&tas->mtx); + return 0; + } + + tas->mixer_l[idx] = ucontrol->value.integer.value[0]; + tas->mixer_r[idx] = ucontrol->value.integer.value[1]; + + if (tas->hw_enabled) + tas_set_mixer(tas); + mutex_unlock(&tas->mtx); + return 1; +} + +#define MIXER_CONTROL(n,descr,idx) \ +static struct snd_kcontrol_new n##_control = { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = descr " Playback Volume", \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = tas_snd_mixer_info, \ + .get = tas_snd_mixer_get, \ + .put = tas_snd_mixer_put, \ + .private_value = idx, \ +} + +MIXER_CONTROL(pcm1, "PCM", 0); +MIXER_CONTROL(monitor, "Monitor", 2); + +static int tas_snd_drc_range_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 = TAS3004_DRC_MAX; + return 0; +} + +static int tas_snd_drc_range_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + ucontrol->value.integer.value[0] = tas->drc_range; + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_snd_drc_range_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] < 0 || + ucontrol->value.integer.value[0] > TAS3004_DRC_MAX) + return -EINVAL; + + mutex_lock(&tas->mtx); + if (tas->drc_range == ucontrol->value.integer.value[0]) { + mutex_unlock(&tas->mtx); + return 0; + } + + tas->drc_range = ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas3004_set_drc(tas); + mutex_unlock(&tas->mtx); + return 1; +} + +static const struct snd_kcontrol_new drc_range_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Range", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tas_snd_drc_range_info, + .get = tas_snd_drc_range_get, + .put = tas_snd_drc_range_put, +}; + +#define tas_snd_drc_switch_info snd_ctl_boolean_mono_info + +static int tas_snd_drc_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + ucontrol->value.integer.value[0] = tas->drc_enabled; + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_snd_drc_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + if (tas->drc_enabled == ucontrol->value.integer.value[0]) { + mutex_unlock(&tas->mtx); + return 0; + } + + tas->drc_enabled = !!ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas3004_set_drc(tas); + mutex_unlock(&tas->mtx); + return 1; +} + +static const struct snd_kcontrol_new drc_switch_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Range Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tas_snd_drc_switch_info, + .get = tas_snd_drc_switch_get, + .put = tas_snd_drc_switch_put, +}; + +static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "Line-In", "Microphone" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B); + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + int oldacr; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + mutex_lock(&tas->mtx); + oldacr = tas->acr; + + /* + * Despite what the data sheet says in one place, the + * TAS_ACR_B_MONAUREAL bit forces mono output even when + * input A (line in) is selected. + */ + tas->acr &= ~(TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL); + if (ucontrol->value.enumerated.item[0]) + tas->acr |= TAS_ACR_INPUT_B | TAS_ACR_B_MONAUREAL | + TAS_ACR_B_MON_SEL_RIGHT; + if (oldacr == tas->acr) { + mutex_unlock(&tas->mtx); + return 0; + } + if (tas->hw_enabled) + tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr); + mutex_unlock(&tas->mtx); + return 1; +} + +static const struct snd_kcontrol_new capture_source_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* If we name this 'Input Source', it properly shows up in + * alsamixer as a selection, * but it's shown under the + * 'Playback' category. + * If I name it 'Capture Source', it shows up in strange + * ways (two bools of which one can be selected at a + * time) but at least it's shown in the 'Capture' + * category. + * I was told that this was due to backward compatibility, + * but I don't understand then why the mangling is *not* + * done when I name it "Input Source"..... + */ + .name = "Capture Source", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tas_snd_capture_source_info, + .get = tas_snd_capture_source_get, + .put = tas_snd_capture_source_put, +}; + +static int tas_snd_treble_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = TAS3004_TREBLE_MIN; + uinfo->value.integer.max = TAS3004_TREBLE_MAX; + return 0; +} + +static int tas_snd_treble_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + ucontrol->value.integer.value[0] = tas->treble; + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_snd_treble_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] < TAS3004_TREBLE_MIN || + ucontrol->value.integer.value[0] > TAS3004_TREBLE_MAX) + return -EINVAL; + mutex_lock(&tas->mtx); + if (tas->treble == ucontrol->value.integer.value[0]) { + mutex_unlock(&tas->mtx); + return 0; + } + + tas->treble = ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas_set_treble(tas); + mutex_unlock(&tas->mtx); + return 1; +} + +static const struct snd_kcontrol_new treble_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Treble", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tas_snd_treble_info, + .get = tas_snd_treble_get, + .put = tas_snd_treble_put, +}; + +static int tas_snd_bass_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 = TAS3004_BASS_MIN; + uinfo->value.integer.max = TAS3004_BASS_MAX; + return 0; +} + +static int tas_snd_bass_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + mutex_lock(&tas->mtx); + ucontrol->value.integer.value[0] = tas->bass; + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_snd_bass_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tas *tas = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] < TAS3004_BASS_MIN || + ucontrol->value.integer.value[0] > TAS3004_BASS_MAX) + return -EINVAL; + mutex_lock(&tas->mtx); + if (tas->bass == ucontrol->value.integer.value[0]) { + mutex_unlock(&tas->mtx); + return 0; + } + + tas->bass = ucontrol->value.integer.value[0]; + if (tas->hw_enabled) + tas_set_bass(tas); + mutex_unlock(&tas->mtx); + return 1; +} + +static const struct snd_kcontrol_new bass_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Bass", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = tas_snd_bass_info, + .get = tas_snd_bass_get, + .put = tas_snd_bass_put, +}; + +static struct transfer_info tas_transfers[] = { + { + /* input */ + .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .transfer_in = 1, + }, + { + /* output */ + .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_BE, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .transfer_in = 0, + }, + {} +}; + +static int tas_usable(struct codec_info_item *cii, + struct transfer_info *ti, + struct transfer_info *out) +{ + return 1; +} + +static int tas_reset_init(struct tas *tas) +{ + u8 tmp; + + tas->codec.gpio->methods->all_amps_off(tas->codec.gpio); + msleep(5); + tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0); + msleep(5); + tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 1); + msleep(20); + tas->codec.gpio->methods->set_hw_reset(tas->codec.gpio, 0); + msleep(10); + tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio); + + tmp = TAS_MCS_SCLK64 | TAS_MCS_SPORT_MODE_I2S | TAS_MCS_SPORT_WL_24BIT; + if (tas_write_reg(tas, TAS_REG_MCS, 1, &tmp)) + goto outerr; + + tas->acr |= TAS_ACR_ANALOG_PDOWN; + if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr)) + goto outerr; + + tmp = 0; + if (tas_write_reg(tas, TAS_REG_MCS2, 1, &tmp)) + goto outerr; + + tas3004_set_drc(tas); + + /* Set treble & bass to 0dB */ + tas->treble = TAS3004_TREBLE_ZERO; + tas->bass = TAS3004_BASS_ZERO; + tas_set_treble(tas); + tas_set_bass(tas); + + tas->acr &= ~TAS_ACR_ANALOG_PDOWN; + if (tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr)) + goto outerr; + + return 0; + outerr: + return -ENODEV; +} + +static int tas_switch_clock(struct codec_info_item *cii, enum clock_switch clock) +{ + struct tas *tas = cii->codec_data; + + switch(clock) { + case CLOCK_SWITCH_PREPARE_SLAVE: + /* Clocks are going away, mute mute mute */ + tas->codec.gpio->methods->all_amps_off(tas->codec.gpio); + tas->hw_enabled = 0; + break; + case CLOCK_SWITCH_SLAVE: + /* Clocks are back, re-init the codec */ + mutex_lock(&tas->mtx); + tas_reset_init(tas); + tas_set_volume(tas); + tas_set_mixer(tas); + tas->hw_enabled = 1; + tas->codec.gpio->methods->all_amps_restore(tas->codec.gpio); + mutex_unlock(&tas->mtx); + break; + default: + /* doesn't happen as of now */ + return -EINVAL; + } + return 0; +} + +#ifdef CONFIG_PM +/* we are controlled via i2c and assume that is always up + * If that wasn't the case, we'd have to suspend once + * our i2c device is suspended, and then take note of that! */ +static int tas_suspend(struct tas *tas) +{ + mutex_lock(&tas->mtx); + tas->hw_enabled = 0; + tas->acr |= TAS_ACR_ANALOG_PDOWN; + tas_write_reg(tas, TAS_REG_ACR, 1, &tas->acr); + mutex_unlock(&tas->mtx); + return 0; +} + +static int tas_resume(struct tas *tas) +{ + /* reset codec */ + mutex_lock(&tas->mtx); + tas_reset_init(tas); + tas_set_volume(tas); + tas_set_mixer(tas); + tas->hw_enabled = 1; + mutex_unlock(&tas->mtx); + return 0; +} + +static int _tas_suspend(struct codec_info_item *cii, pm_message_t state) +{ + return tas_suspend(cii->codec_data); +} + +static int _tas_resume(struct codec_info_item *cii) +{ + return tas_resume(cii->codec_data); +} +#else /* CONFIG_PM */ +#define _tas_suspend NULL +#define _tas_resume NULL +#endif /* CONFIG_PM */ + +static struct codec_info tas_codec_info = { + .transfers = tas_transfers, + /* in theory, we can drive it at 512 too... + * but so far the framework doesn't allow + * for that and I don't see much point in it. */ + .sysclock_factor = 256, + /* same here, could be 32 for just one 16 bit format */ + .bus_factor = 64, + .owner = THIS_MODULE, + .usable = tas_usable, + .switch_clock = tas_switch_clock, + .suspend = _tas_suspend, + .resume = _tas_resume, +}; + +static int tas_init_codec(struct aoa_codec *codec) +{ + struct tas *tas = codec_to_tas(codec); + int err; + + if (!tas->codec.gpio || !tas->codec.gpio->methods) { + printk(KERN_ERR PFX "gpios not assigned!!\n"); + return -EINVAL; + } + + mutex_lock(&tas->mtx); + if (tas_reset_init(tas)) { + printk(KERN_ERR PFX "tas failed to initialise\n"); + mutex_unlock(&tas->mtx); + return -ENXIO; + } + tas->hw_enabled = 1; + mutex_unlock(&tas->mtx); + + if (tas->codec.soundbus_dev->attach_codec(tas->codec.soundbus_dev, + aoa_get_card(), + &tas_codec_info, tas)) { + printk(KERN_ERR PFX "error attaching tas to soundbus\n"); + return -ENODEV; + } + + if (aoa_snd_device_new(SNDRV_DEV_CODEC, tas, &ops)) { + printk(KERN_ERR PFX "failed to create tas snd device!\n"); + return -ENODEV; + } + err = aoa_snd_ctl_add(snd_ctl_new1(&volume_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&mute_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&pcm1_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&monitor_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&capture_source_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&drc_range_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&drc_switch_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&treble_control, tas)); + if (err) + goto error; + + err = aoa_snd_ctl_add(snd_ctl_new1(&bass_control, tas)); + if (err) + goto error; + + return 0; + error: + tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas); + snd_device_free(aoa_get_card(), tas); + return err; +} + +static void tas_exit_codec(struct aoa_codec *codec) +{ + struct tas *tas = codec_to_tas(codec); + + if (!tas->codec.soundbus_dev) + return; + tas->codec.soundbus_dev->detach_codec(tas->codec.soundbus_dev, tas); +} + + +static int tas_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *node = client->dev.of_node; + struct tas *tas; + + tas = kzalloc(sizeof(struct tas), GFP_KERNEL); + + if (!tas) + return -ENOMEM; + + mutex_init(&tas->mtx); + tas->i2c = client; + i2c_set_clientdata(client, tas); + + /* seems that half is a saner default */ + tas->drc_range = TAS3004_DRC_MAX / 2; + + strlcpy(tas->codec.name, "tas", MAX_CODEC_NAME_LEN); + tas->codec.owner = THIS_MODULE; + tas->codec.init = tas_init_codec; + tas->codec.exit = tas_exit_codec; + tas->codec.node = of_node_get(node); + + if (aoa_codec_register(&tas->codec)) { + goto fail; + } + printk(KERN_DEBUG + "snd-aoa-codec-tas: tas found, addr 0x%02x on %pOF\n", + (unsigned int)client->addr, node); + return 0; + fail: + mutex_destroy(&tas->mtx); + kfree(tas); + return -EINVAL; +} + +static int tas_i2c_remove(struct i2c_client *client) +{ + struct tas *tas = i2c_get_clientdata(client); + u8 tmp = TAS_ACR_ANALOG_PDOWN; + + aoa_codec_unregister(&tas->codec); + of_node_put(tas->codec.node); + + /* power down codec chip */ + tas_write_reg(tas, TAS_REG_ACR, 1, &tmp); + + mutex_destroy(&tas->mtx); + kfree(tas); + return 0; +} + +static const struct i2c_device_id tas_i2c_id[] = { + { "MAC,tas3004", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c,tas_i2c_id); + +static struct i2c_driver tas_driver = { + .driver = { + .name = "aoa_codec_tas", + }, + .probe = tas_i2c_probe, + .remove = tas_i2c_remove, + .id_table = tas_i2c_id, +}; + +module_i2c_driver(tas_driver); diff --git a/sound/aoa/codecs/tas.h b/sound/aoa/codecs/tas.h new file mode 100644 index 000000000..ae177e346 --- /dev/null +++ b/sound/aoa/codecs/tas.h @@ -0,0 +1,55 @@ +/* + * Apple Onboard Audio driver for tas codec (header) + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ +#ifndef __SND_AOA_CODECTASH +#define __SND_AOA_CODECTASH + +#define TAS_REG_MCS 0x01 /* main control */ +# define TAS_MCS_FASTLOAD (1<<7) +# define TAS_MCS_SCLK64 (1<<6) +# define TAS_MCS_SPORT_MODE_MASK (3<<4) +# define TAS_MCS_SPORT_MODE_I2S (2<<4) +# define TAS_MCS_SPORT_MODE_RJ (1<<4) +# define TAS_MCS_SPORT_MODE_LJ (0<<4) +# define TAS_MCS_SPORT_WL_MASK (3<<0) +# define TAS_MCS_SPORT_WL_16BIT (0<<0) +# define TAS_MCS_SPORT_WL_18BIT (1<<0) +# define TAS_MCS_SPORT_WL_20BIT (2<<0) +# define TAS_MCS_SPORT_WL_24BIT (3<<0) + +#define TAS_REG_DRC 0x02 +#define TAS_REG_VOL 0x04 +#define TAS_REG_TREBLE 0x05 +#define TAS_REG_BASS 0x06 +#define TAS_REG_LMIX 0x07 +#define TAS_REG_RMIX 0x08 + +#define TAS_REG_ACR 0x40 /* analog control */ +# define TAS_ACR_B_MONAUREAL (1<<7) +# define TAS_ACR_B_MON_SEL_RIGHT (1<<6) +# define TAS_ACR_DEEMPH_MASK (3<<2) +# define TAS_ACR_DEEMPH_OFF (0<<2) +# define TAS_ACR_DEEMPH_48KHz (1<<2) +# define TAS_ACR_DEEMPH_44KHz (2<<2) +# define TAS_ACR_INPUT_B (1<<1) +# define TAS_ACR_ANALOG_PDOWN (1<<0) + +#define TAS_REG_MCS2 0x43 /* main control 2 */ +# define TAS_MCS2_ALLPASS (1<<1) + +#define TAS_REG_LEFT_BIQUAD6 0x10 +#define TAS_REG_RIGHT_BIQUAD6 0x19 + +#define TAS_REG_LEFT_LOUDNESS 0x21 +#define TAS_REG_RIGHT_LOUDNESS 0x22 +#define TAS_REG_LEFT_LOUDNESS_GAIN 0x23 +#define TAS_REG_RIGHT_LOUDNESS_GAIN 0x24 + +#define TAS3001_DRC_MAX 0x5f +#define TAS3004_DRC_MAX 0xef + +#endif /* __SND_AOA_CODECTASH */ diff --git a/sound/aoa/codecs/toonie.c b/sound/aoa/codecs/toonie.c new file mode 100644 index 000000000..7e8c3417c --- /dev/null +++ b/sound/aoa/codecs/toonie.c @@ -0,0 +1,151 @@ +/* + * Apple Onboard Audio driver for Toonie codec + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + * + * + * This is a driver for the toonie codec chip. This chip is present + * on the Mac Mini and is nothing but a DAC. + */ +#include +#include +#include +MODULE_AUTHOR("Johannes Berg "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("toonie codec driver for snd-aoa"); + +#include "../aoa.h" +#include "../soundbus/soundbus.h" + + +#define PFX "snd-aoa-codec-toonie: " + +struct toonie { + struct aoa_codec codec; +}; +#define codec_to_toonie(c) container_of(c, struct toonie, codec) + +static int toonie_dev_register(struct snd_device *dev) +{ + return 0; +} + +static struct snd_device_ops ops = { + .dev_register = toonie_dev_register, +}; + +static struct transfer_info toonie_transfers[] = { + /* This thing *only* has analog output, + * the rates are taken from Info.plist + * from Darwin. */ + { + .formats = SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + }, + {} +}; + +static int toonie_usable(struct codec_info_item *cii, + struct transfer_info *ti, + struct transfer_info *out) +{ + return 1; +} + +#ifdef CONFIG_PM +static int toonie_suspend(struct codec_info_item *cii, pm_message_t state) +{ + /* can we turn it off somehow? */ + return 0; +} + +static int toonie_resume(struct codec_info_item *cii) +{ + return 0; +} +#endif /* CONFIG_PM */ + +static struct codec_info toonie_codec_info = { + .transfers = toonie_transfers, + .sysclock_factor = 256, + .bus_factor = 64, + .owner = THIS_MODULE, + .usable = toonie_usable, +#ifdef CONFIG_PM + .suspend = toonie_suspend, + .resume = toonie_resume, +#endif +}; + +static int toonie_init_codec(struct aoa_codec *codec) +{ + struct toonie *toonie = codec_to_toonie(codec); + + /* nothing connected? what a joke! */ + if (toonie->codec.connected != 1) + return -ENOTCONN; + + if (aoa_snd_device_new(SNDRV_DEV_CODEC, toonie, &ops)) { + printk(KERN_ERR PFX "failed to create toonie snd device!\n"); + return -ENODEV; + } + + if (toonie->codec.soundbus_dev->attach_codec(toonie->codec.soundbus_dev, + aoa_get_card(), + &toonie_codec_info, toonie)) { + printk(KERN_ERR PFX "error creating toonie pcm\n"); + snd_device_free(aoa_get_card(), toonie); + return -ENODEV; + } + + return 0; +} + +static void toonie_exit_codec(struct aoa_codec *codec) +{ + struct toonie *toonie = codec_to_toonie(codec); + + if (!toonie->codec.soundbus_dev) { + printk(KERN_ERR PFX "toonie_exit_codec called without soundbus_dev!\n"); + return; + } + toonie->codec.soundbus_dev->detach_codec(toonie->codec.soundbus_dev, toonie); +} + +static struct toonie *toonie; + +static int __init toonie_init(void) +{ + toonie = kzalloc(sizeof(struct toonie), GFP_KERNEL); + + if (!toonie) + return -ENOMEM; + + strlcpy(toonie->codec.name, "toonie", sizeof(toonie->codec.name)); + toonie->codec.owner = THIS_MODULE; + toonie->codec.init = toonie_init_codec; + toonie->codec.exit = toonie_exit_codec; + + if (aoa_codec_register(&toonie->codec)) { + kfree(toonie); + return -EINVAL; + } + + return 0; +} + +static void __exit toonie_exit(void) +{ + aoa_codec_unregister(&toonie->codec); + kfree(toonie); +} + +module_init(toonie_init); +module_exit(toonie_exit); diff --git a/sound/aoa/core/Makefile b/sound/aoa/core/Makefile new file mode 100644 index 000000000..056d69683 --- /dev/null +++ b/sound/aoa/core/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_SND_AOA) += snd-aoa.o +snd-aoa-objs := core.o \ + alsa.o \ + gpio-pmf.o \ + gpio-feature.o diff --git a/sound/aoa/core/alsa.c b/sound/aoa/core/alsa.c new file mode 100644 index 000000000..4a7e4e6b7 --- /dev/null +++ b/sound/aoa/core/alsa.c @@ -0,0 +1,99 @@ +/* + * Apple Onboard Audio Alsa helpers + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ +#include +#include "alsa.h" + +static int index = -1; +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "index for AOA sound card."); + +static struct aoa_card *aoa_card; + +int aoa_alsa_init(char *name, struct module *mod, struct device *dev) +{ + struct snd_card *alsa_card; + int err; + + if (aoa_card) + /* cannot be EEXIST due to usage in aoa_fabric_register */ + return -EBUSY; + + err = snd_card_new(dev, index, name, mod, sizeof(struct aoa_card), + &alsa_card); + if (err < 0) + return err; + aoa_card = alsa_card->private_data; + aoa_card->alsa_card = alsa_card; + strlcpy(alsa_card->driver, "AppleOnbdAudio", sizeof(alsa_card->driver)); + strlcpy(alsa_card->shortname, name, sizeof(alsa_card->shortname)); + strlcpy(alsa_card->longname, name, sizeof(alsa_card->longname)); + strlcpy(alsa_card->mixername, name, sizeof(alsa_card->mixername)); + err = snd_card_register(aoa_card->alsa_card); + if (err < 0) { + printk(KERN_ERR "snd-aoa: couldn't register alsa card\n"); + snd_card_free(aoa_card->alsa_card); + aoa_card = NULL; + return err; + } + return 0; +} + +struct snd_card *aoa_get_card(void) +{ + if (aoa_card) + return aoa_card->alsa_card; + return NULL; +} +EXPORT_SYMBOL_GPL(aoa_get_card); + +void aoa_alsa_cleanup(void) +{ + if (aoa_card) { + snd_card_free(aoa_card->alsa_card); + aoa_card = NULL; + } +} + +int aoa_snd_device_new(enum snd_device_type type, + void * device_data, struct snd_device_ops * ops) +{ + struct snd_card *card = aoa_get_card(); + int err; + + if (!card) return -ENOMEM; + + err = snd_device_new(card, type, device_data, ops); + if (err) { + printk(KERN_ERR "snd-aoa: failed to create snd device (%d)\n", err); + return err; + } + err = snd_device_register(card, device_data); + if (err) { + printk(KERN_ERR "snd-aoa: failed to register " + "snd device (%d)\n", err); + printk(KERN_ERR "snd-aoa: have you forgotten the " + "dev_register callback?\n"); + snd_device_free(card, device_data); + } + return err; +} +EXPORT_SYMBOL_GPL(aoa_snd_device_new); + +int aoa_snd_ctl_add(struct snd_kcontrol* control) +{ + int err; + + if (!aoa_card) return -ENODEV; + + err = snd_ctl_add(aoa_card->alsa_card, control); + if (err) + printk(KERN_ERR "snd-aoa: failed to add alsa control (%d)\n", + err); + return err; +} +EXPORT_SYMBOL_GPL(aoa_snd_ctl_add); diff --git a/sound/aoa/core/alsa.h b/sound/aoa/core/alsa.h new file mode 100644 index 000000000..9669e4489 --- /dev/null +++ b/sound/aoa/core/alsa.h @@ -0,0 +1,16 @@ +/* + * Apple Onboard Audio Alsa private helpers + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#ifndef __SND_AOA_ALSA_H +#define __SND_AOA_ALSA_H +#include "../aoa.h" + +extern int aoa_alsa_init(char *name, struct module *mod, struct device *dev); +extern void aoa_alsa_cleanup(void); + +#endif /* __SND_AOA_ALSA_H */ diff --git a/sound/aoa/core/core.c b/sound/aoa/core/core.c new file mode 100644 index 000000000..10bec6c61 --- /dev/null +++ b/sound/aoa/core/core.c @@ -0,0 +1,162 @@ +/* + * Apple Onboard Audio driver core + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#include +#include +#include +#include "../aoa.h" +#include "alsa.h" + +MODULE_DESCRIPTION("Apple Onboard Audio Sound Driver"); +MODULE_AUTHOR("Johannes Berg "); +MODULE_LICENSE("GPL"); + +/* We allow only one fabric. This simplifies things, + * and more don't really make that much sense */ +static struct aoa_fabric *fabric; +static LIST_HEAD(codec_list); + +static int attach_codec_to_fabric(struct aoa_codec *c) +{ + int err; + + if (!try_module_get(c->owner)) + return -EBUSY; + /* found_codec has to be assigned */ + err = -ENOENT; + if (fabric->found_codec) + err = fabric->found_codec(c); + if (err) { + module_put(c->owner); + printk(KERN_ERR "snd-aoa: fabric didn't like codec %s\n", + c->name); + return err; + } + c->fabric = fabric; + + err = 0; + if (c->init) + err = c->init(c); + if (err) { + printk(KERN_ERR "snd-aoa: codec %s didn't init\n", c->name); + c->fabric = NULL; + if (fabric->remove_codec) + fabric->remove_codec(c); + module_put(c->owner); + return err; + } + if (fabric->attached_codec) + fabric->attached_codec(c); + return 0; +} + +int aoa_codec_register(struct aoa_codec *codec) +{ + int err = 0; + + /* if there's a fabric already, we can tell if we + * will want to have this codec, so propagate error + * through. Otherwise, this will happen later... */ + if (fabric) + err = attach_codec_to_fabric(codec); + if (!err) + list_add(&codec->list, &codec_list); + return err; +} +EXPORT_SYMBOL_GPL(aoa_codec_register); + +void aoa_codec_unregister(struct aoa_codec *codec) +{ + list_del(&codec->list); + if (codec->fabric && codec->exit) + codec->exit(codec); + if (fabric && fabric->remove_codec) + fabric->remove_codec(codec); + codec->fabric = NULL; + module_put(codec->owner); +} +EXPORT_SYMBOL_GPL(aoa_codec_unregister); + +int aoa_fabric_register(struct aoa_fabric *new_fabric, struct device *dev) +{ + struct aoa_codec *c; + int err; + + /* allow querying for presence of fabric + * (i.e. do this test first!) */ + if (new_fabric == fabric) { + err = -EALREADY; + goto attach; + } + if (fabric) + return -EEXIST; + if (!new_fabric) + return -EINVAL; + + err = aoa_alsa_init(new_fabric->name, new_fabric->owner, dev); + if (err) + return err; + + fabric = new_fabric; + + attach: + list_for_each_entry(c, &codec_list, list) { + if (c->fabric != fabric) + attach_codec_to_fabric(c); + } + return err; +} +EXPORT_SYMBOL_GPL(aoa_fabric_register); + +void aoa_fabric_unregister(struct aoa_fabric *old_fabric) +{ + struct aoa_codec *c; + + if (fabric != old_fabric) + return; + + list_for_each_entry(c, &codec_list, list) { + if (c->fabric) + aoa_fabric_unlink_codec(c); + } + + aoa_alsa_cleanup(); + + fabric = NULL; +} +EXPORT_SYMBOL_GPL(aoa_fabric_unregister); + +void aoa_fabric_unlink_codec(struct aoa_codec *codec) +{ + if (!codec->fabric) { + printk(KERN_ERR "snd-aoa: fabric unassigned " + "in aoa_fabric_unlink_codec\n"); + dump_stack(); + return; + } + if (codec->exit) + codec->exit(codec); + if (codec->fabric->remove_codec) + codec->fabric->remove_codec(codec); + codec->fabric = NULL; + module_put(codec->owner); +} +EXPORT_SYMBOL_GPL(aoa_fabric_unlink_codec); + +static int __init aoa_init(void) +{ + return 0; +} + +static void __exit aoa_exit(void) +{ + aoa_alsa_cleanup(); +} + +module_init(aoa_init); +module_exit(aoa_exit); diff --git a/sound/aoa/core/gpio-feature.c b/sound/aoa/core/gpio-feature.c new file mode 100644 index 000000000..65557421f --- /dev/null +++ b/sound/aoa/core/gpio-feature.c @@ -0,0 +1,425 @@ +/* + * Apple Onboard Audio feature call GPIO control + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + * + * This file contains the GPIO control routines for + * direct (through feature calls) access to the GPIO + * registers. + */ + +#include +#include +#include +#include "../aoa.h" + +/* TODO: these are lots of global variables + * that aren't used on most machines... + * Move them into a dynamically allocated + * structure and use that. + */ + +/* these are the GPIO numbers (register addresses as offsets into + * the GPIO space) */ +static int headphone_mute_gpio; +static int master_mute_gpio; +static int amp_mute_gpio; +static int lineout_mute_gpio; +static int hw_reset_gpio; +static int lineout_detect_gpio; +static int headphone_detect_gpio; +static int linein_detect_gpio; + +/* see the SWITCH_GPIO macro */ +static int headphone_mute_gpio_activestate; +static int master_mute_gpio_activestate; +static int amp_mute_gpio_activestate; +static int lineout_mute_gpio_activestate; +static int hw_reset_gpio_activestate; +static int lineout_detect_gpio_activestate; +static int headphone_detect_gpio_activestate; +static int linein_detect_gpio_activestate; + +/* node pointers that we save when getting the GPIO number + * to get the interrupt later */ +static struct device_node *lineout_detect_node; +static struct device_node *linein_detect_node; +static struct device_node *headphone_detect_node; + +static int lineout_detect_irq; +static int linein_detect_irq; +static int headphone_detect_irq; + +static struct device_node *get_gpio(char *name, + char *altname, + int *gpioptr, + int *gpioactiveptr) +{ + struct device_node *np, *gpio; + const u32 *reg; + const char *audio_gpio; + + *gpioptr = -1; + + /* check if we can get it the easy way ... */ + np = of_find_node_by_name(NULL, name); + if (!np) { + /* some machines have only gpioX/extint-gpioX nodes, + * and an audio-gpio property saying what it is ... + * So what we have to do is enumerate all children + * of the gpio node and check them all. */ + gpio = of_find_node_by_name(NULL, "gpio"); + if (!gpio) + return NULL; + while ((np = of_get_next_child(gpio, np))) { + audio_gpio = of_get_property(np, "audio-gpio", NULL); + if (!audio_gpio) + continue; + if (strcmp(audio_gpio, name) == 0) + break; + if (altname && (strcmp(audio_gpio, altname) == 0)) + break; + } + /* still not found, assume not there */ + if (!np) + return NULL; + } + + reg = of_get_property(np, "reg", NULL); + if (!reg) { + of_node_put(np); + return NULL; + } + + *gpioptr = *reg; + + /* this is a hack, usually the GPIOs 'reg' property + * should have the offset based from the GPIO space + * which is at 0x50, but apparently not always... */ + if (*gpioptr < 0x50) + *gpioptr += 0x50; + + reg = of_get_property(np, "audio-gpio-active-state", NULL); + if (!reg) + /* Apple seems to default to 1, but + * that doesn't seem right at least on most + * machines. So until proven that the opposite + * is necessary, we default to 0 + * (which, incidentally, snd-powermac also does...) */ + *gpioactiveptr = 0; + else + *gpioactiveptr = *reg; + + return np; +} + +static void get_irq(struct device_node * np, int *irqptr) +{ + if (np) + *irqptr = irq_of_parse_and_map(np, 0); + else + *irqptr = 0; +} + +/* 0x4 is outenable, 0x1 is out, thus 4 or 5 */ +#define SWITCH_GPIO(name, v, on) \ + (((v)&~1) | ((on)? \ + (name##_gpio_activestate==0?4:5): \ + (name##_gpio_activestate==0?5:4))) + +#define FTR_GPIO(name, bit) \ +static void ftr_gpio_set_##name(struct gpio_runtime *rt, int on)\ +{ \ + int v; \ + \ + if (unlikely(!rt)) return; \ + \ + if (name##_mute_gpio < 0) \ + return; \ + \ + v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, \ + name##_mute_gpio, \ + 0); \ + \ + /* muted = !on... */ \ + v = SWITCH_GPIO(name##_mute, v, !on); \ + \ + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, \ + name##_mute_gpio, v); \ + \ + rt->implementation_private &= ~(1<implementation_private |= (!!on << bit); \ +} \ +static int ftr_gpio_get_##name(struct gpio_runtime *rt) \ +{ \ + if (unlikely(!rt)) return 0; \ + return (rt->implementation_private>>bit)&1; \ +} + +FTR_GPIO(headphone, 0); +FTR_GPIO(amp, 1); +FTR_GPIO(lineout, 2); +FTR_GPIO(master, 3); + +static void ftr_gpio_set_hw_reset(struct gpio_runtime *rt, int on) +{ + int v; + + if (unlikely(!rt)) return; + if (hw_reset_gpio < 0) + return; + + v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, + hw_reset_gpio, 0); + v = SWITCH_GPIO(hw_reset, v, on); + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, + hw_reset_gpio, v); +} + +static struct gpio_methods methods; + +static void ftr_gpio_all_amps_off(struct gpio_runtime *rt) +{ + int saved; + + if (unlikely(!rt)) return; + saved = rt->implementation_private; + ftr_gpio_set_headphone(rt, 0); + ftr_gpio_set_amp(rt, 0); + ftr_gpio_set_lineout(rt, 0); + if (methods.set_master) + ftr_gpio_set_master(rt, 0); + rt->implementation_private = saved; +} + +static void ftr_gpio_all_amps_restore(struct gpio_runtime *rt) +{ + int s; + + if (unlikely(!rt)) return; + s = rt->implementation_private; + ftr_gpio_set_headphone(rt, (s>>0)&1); + ftr_gpio_set_amp(rt, (s>>1)&1); + ftr_gpio_set_lineout(rt, (s>>2)&1); + if (methods.set_master) + ftr_gpio_set_master(rt, (s>>3)&1); +} + +static void ftr_handle_notify(struct work_struct *work) +{ + struct gpio_notification *notif = + container_of(work, struct gpio_notification, work.work); + + mutex_lock(¬if->mutex); + if (notif->notify) + notif->notify(notif->data); + mutex_unlock(¬if->mutex); +} + +static void gpio_enable_dual_edge(int gpio) +{ + int v; + + if (gpio == -1) + return; + v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0); + v |= 0x80; /* enable dual edge */ + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio, v); +} + +static void ftr_gpio_init(struct gpio_runtime *rt) +{ + get_gpio("headphone-mute", NULL, + &headphone_mute_gpio, + &headphone_mute_gpio_activestate); + get_gpio("amp-mute", NULL, + &_mute_gpio, + &_mute_gpio_activestate); + get_gpio("lineout-mute", NULL, + &lineout_mute_gpio, + &lineout_mute_gpio_activestate); + get_gpio("hw-reset", "audio-hw-reset", + &hw_reset_gpio, + &hw_reset_gpio_activestate); + if (get_gpio("master-mute", NULL, + &master_mute_gpio, + &master_mute_gpio_activestate)) { + methods.set_master = ftr_gpio_set_master; + methods.get_master = ftr_gpio_get_master; + } + + headphone_detect_node = get_gpio("headphone-detect", NULL, + &headphone_detect_gpio, + &headphone_detect_gpio_activestate); + /* go Apple, and thanks for giving these different names + * across the board... */ + lineout_detect_node = get_gpio("lineout-detect", "line-output-detect", + &lineout_detect_gpio, + &lineout_detect_gpio_activestate); + linein_detect_node = get_gpio("linein-detect", "line-input-detect", + &linein_detect_gpio, + &linein_detect_gpio_activestate); + + gpio_enable_dual_edge(headphone_detect_gpio); + gpio_enable_dual_edge(lineout_detect_gpio); + gpio_enable_dual_edge(linein_detect_gpio); + + get_irq(headphone_detect_node, &headphone_detect_irq); + get_irq(lineout_detect_node, &lineout_detect_irq); + get_irq(linein_detect_node, &linein_detect_irq); + + ftr_gpio_all_amps_off(rt); + rt->implementation_private = 0; + INIT_DELAYED_WORK(&rt->headphone_notify.work, ftr_handle_notify); + INIT_DELAYED_WORK(&rt->line_in_notify.work, ftr_handle_notify); + INIT_DELAYED_WORK(&rt->line_out_notify.work, ftr_handle_notify); + mutex_init(&rt->headphone_notify.mutex); + mutex_init(&rt->line_in_notify.mutex); + mutex_init(&rt->line_out_notify.mutex); +} + +static void ftr_gpio_exit(struct gpio_runtime *rt) +{ + ftr_gpio_all_amps_off(rt); + rt->implementation_private = 0; + if (rt->headphone_notify.notify) + free_irq(headphone_detect_irq, &rt->headphone_notify); + if (rt->line_in_notify.gpio_private) + free_irq(linein_detect_irq, &rt->line_in_notify); + if (rt->line_out_notify.gpio_private) + free_irq(lineout_detect_irq, &rt->line_out_notify); + cancel_delayed_work_sync(&rt->headphone_notify.work); + cancel_delayed_work_sync(&rt->line_in_notify.work); + cancel_delayed_work_sync(&rt->line_out_notify.work); + mutex_destroy(&rt->headphone_notify.mutex); + mutex_destroy(&rt->line_in_notify.mutex); + mutex_destroy(&rt->line_out_notify.mutex); +} + +static irqreturn_t ftr_handle_notify_irq(int xx, void *data) +{ + struct gpio_notification *notif = data; + + schedule_delayed_work(¬if->work, 0); + + return IRQ_HANDLED; +} + +static int ftr_set_notify(struct gpio_runtime *rt, + enum notify_type type, + notify_func_t notify, + void *data) +{ + struct gpio_notification *notif; + notify_func_t old; + int irq; + char *name; + int err = -EBUSY; + + switch (type) { + case AOA_NOTIFY_HEADPHONE: + notif = &rt->headphone_notify; + name = "headphone-detect"; + irq = headphone_detect_irq; + break; + case AOA_NOTIFY_LINE_IN: + notif = &rt->line_in_notify; + name = "linein-detect"; + irq = linein_detect_irq; + break; + case AOA_NOTIFY_LINE_OUT: + notif = &rt->line_out_notify; + name = "lineout-detect"; + irq = lineout_detect_irq; + break; + default: + return -EINVAL; + } + + if (!irq) + return -ENODEV; + + mutex_lock(¬if->mutex); + + old = notif->notify; + + if (!old && !notify) { + err = 0; + goto out_unlock; + } + + if (old && notify) { + if (old == notify && notif->data == data) + err = 0; + goto out_unlock; + } + + if (old && !notify) + free_irq(irq, notif); + + if (!old && notify) { + err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif); + if (err) + goto out_unlock; + } + + notif->notify = notify; + notif->data = data; + + err = 0; + out_unlock: + mutex_unlock(¬if->mutex); + return err; +} + +static int ftr_get_detect(struct gpio_runtime *rt, + enum notify_type type) +{ + int gpio, ret, active; + + switch (type) { + case AOA_NOTIFY_HEADPHONE: + gpio = headphone_detect_gpio; + active = headphone_detect_gpio_activestate; + break; + case AOA_NOTIFY_LINE_IN: + gpio = linein_detect_gpio; + active = linein_detect_gpio_activestate; + break; + case AOA_NOTIFY_LINE_OUT: + gpio = lineout_detect_gpio; + active = lineout_detect_gpio_activestate; + break; + default: + return -EINVAL; + } + + if (gpio == -1) + return -ENODEV; + + ret = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0); + if (ret < 0) + return ret; + return ((ret >> 1) & 1) == active; +} + +static struct gpio_methods methods = { + .init = ftr_gpio_init, + .exit = ftr_gpio_exit, + .all_amps_off = ftr_gpio_all_amps_off, + .all_amps_restore = ftr_gpio_all_amps_restore, + .set_headphone = ftr_gpio_set_headphone, + .set_speakers = ftr_gpio_set_amp, + .set_lineout = ftr_gpio_set_lineout, + .set_hw_reset = ftr_gpio_set_hw_reset, + .get_headphone = ftr_gpio_get_headphone, + .get_speakers = ftr_gpio_get_amp, + .get_lineout = ftr_gpio_get_lineout, + .set_notify = ftr_set_notify, + .get_detect = ftr_get_detect, +}; + +struct gpio_methods *ftr_gpio_methods = &methods; +EXPORT_SYMBOL_GPL(ftr_gpio_methods); diff --git a/sound/aoa/core/gpio-pmf.c b/sound/aoa/core/gpio-pmf.c new file mode 100644 index 000000000..c8d8a1a6f --- /dev/null +++ b/sound/aoa/core/gpio-pmf.c @@ -0,0 +1,253 @@ +/* + * Apple Onboard Audio pmf GPIOs + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#include +#include +#include +#include "../aoa.h" + +#define PMF_GPIO(name, bit) \ +static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\ +{ \ + struct pmf_args args = { .count = 1, .u[0].v = !on }; \ + int rc; \ + \ + if (unlikely(!rt)) return; \ + rc = pmf_call_function(rt->node, #name "-mute", &args); \ + if (rc && rc != -ENODEV) \ + printk(KERN_WARNING "pmf_gpio_set_" #name \ + " failed, rc: %d\n", rc); \ + rt->implementation_private &= ~(1<implementation_private |= (!!on << bit); \ +} \ +static int pmf_gpio_get_##name(struct gpio_runtime *rt) \ +{ \ + if (unlikely(!rt)) return 0; \ + return (rt->implementation_private>>bit)&1; \ +} + +PMF_GPIO(headphone, 0); +PMF_GPIO(amp, 1); +PMF_GPIO(lineout, 2); + +static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on) +{ + struct pmf_args args = { .count = 1, .u[0].v = !!on }; + int rc; + + if (unlikely(!rt)) return; + rc = pmf_call_function(rt->node, "hw-reset", &args); + if (rc) + printk(KERN_WARNING "pmf_gpio_set_hw_reset" + " failed, rc: %d\n", rc); +} + +static void pmf_gpio_all_amps_off(struct gpio_runtime *rt) +{ + int saved; + + if (unlikely(!rt)) return; + saved = rt->implementation_private; + pmf_gpio_set_headphone(rt, 0); + pmf_gpio_set_amp(rt, 0); + pmf_gpio_set_lineout(rt, 0); + rt->implementation_private = saved; +} + +static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt) +{ + int s; + + if (unlikely(!rt)) return; + s = rt->implementation_private; + pmf_gpio_set_headphone(rt, (s>>0)&1); + pmf_gpio_set_amp(rt, (s>>1)&1); + pmf_gpio_set_lineout(rt, (s>>2)&1); +} + +static void pmf_handle_notify(struct work_struct *work) +{ + struct gpio_notification *notif = + container_of(work, struct gpio_notification, work.work); + + mutex_lock(¬if->mutex); + if (notif->notify) + notif->notify(notif->data); + mutex_unlock(¬if->mutex); +} + +static void pmf_gpio_init(struct gpio_runtime *rt) +{ + pmf_gpio_all_amps_off(rt); + rt->implementation_private = 0; + INIT_DELAYED_WORK(&rt->headphone_notify.work, pmf_handle_notify); + INIT_DELAYED_WORK(&rt->line_in_notify.work, pmf_handle_notify); + INIT_DELAYED_WORK(&rt->line_out_notify.work, pmf_handle_notify); + mutex_init(&rt->headphone_notify.mutex); + mutex_init(&rt->line_in_notify.mutex); + mutex_init(&rt->line_out_notify.mutex); +} + +static void pmf_gpio_exit(struct gpio_runtime *rt) +{ + pmf_gpio_all_amps_off(rt); + rt->implementation_private = 0; + + if (rt->headphone_notify.gpio_private) + pmf_unregister_irq_client(rt->headphone_notify.gpio_private); + if (rt->line_in_notify.gpio_private) + pmf_unregister_irq_client(rt->line_in_notify.gpio_private); + if (rt->line_out_notify.gpio_private) + pmf_unregister_irq_client(rt->line_out_notify.gpio_private); + + /* make sure no work is pending before freeing + * all things */ + cancel_delayed_work_sync(&rt->headphone_notify.work); + cancel_delayed_work_sync(&rt->line_in_notify.work); + cancel_delayed_work_sync(&rt->line_out_notify.work); + + mutex_destroy(&rt->headphone_notify.mutex); + mutex_destroy(&rt->line_in_notify.mutex); + mutex_destroy(&rt->line_out_notify.mutex); + + kfree(rt->headphone_notify.gpio_private); + kfree(rt->line_in_notify.gpio_private); + kfree(rt->line_out_notify.gpio_private); +} + +static void pmf_handle_notify_irq(void *data) +{ + struct gpio_notification *notif = data; + + schedule_delayed_work(¬if->work, 0); +} + +static int pmf_set_notify(struct gpio_runtime *rt, + enum notify_type type, + notify_func_t notify, + void *data) +{ + struct gpio_notification *notif; + notify_func_t old; + struct pmf_irq_client *irq_client; + char *name; + int err = -EBUSY; + + switch (type) { + case AOA_NOTIFY_HEADPHONE: + notif = &rt->headphone_notify; + name = "headphone-detect"; + break; + case AOA_NOTIFY_LINE_IN: + notif = &rt->line_in_notify; + name = "linein-detect"; + break; + case AOA_NOTIFY_LINE_OUT: + notif = &rt->line_out_notify; + name = "lineout-detect"; + break; + default: + return -EINVAL; + } + + mutex_lock(¬if->mutex); + + old = notif->notify; + + if (!old && !notify) { + err = 0; + goto out_unlock; + } + + if (old && notify) { + if (old == notify && notif->data == data) + err = 0; + goto out_unlock; + } + + if (old && !notify) { + irq_client = notif->gpio_private; + pmf_unregister_irq_client(irq_client); + kfree(irq_client); + notif->gpio_private = NULL; + } + if (!old && notify) { + irq_client = kzalloc(sizeof(struct pmf_irq_client), + GFP_KERNEL); + if (!irq_client) { + err = -ENOMEM; + goto out_unlock; + } + irq_client->data = notif; + irq_client->handler = pmf_handle_notify_irq; + irq_client->owner = THIS_MODULE; + err = pmf_register_irq_client(rt->node, + name, + irq_client); + if (err) { + printk(KERN_ERR "snd-aoa: gpio layer failed to" + " register %s irq (%d)\n", name, err); + kfree(irq_client); + goto out_unlock; + } + notif->gpio_private = irq_client; + } + notif->notify = notify; + notif->data = data; + + err = 0; + out_unlock: + mutex_unlock(¬if->mutex); + return err; +} + +static int pmf_get_detect(struct gpio_runtime *rt, + enum notify_type type) +{ + char *name; + int err = -EBUSY, ret; + struct pmf_args args = { .count = 1, .u[0].p = &ret }; + + switch (type) { + case AOA_NOTIFY_HEADPHONE: + name = "headphone-detect"; + break; + case AOA_NOTIFY_LINE_IN: + name = "linein-detect"; + break; + case AOA_NOTIFY_LINE_OUT: + name = "lineout-detect"; + break; + default: + return -EINVAL; + } + + err = pmf_call_function(rt->node, name, &args); + if (err) + return err; + return ret; +} + +static struct gpio_methods methods = { + .init = pmf_gpio_init, + .exit = pmf_gpio_exit, + .all_amps_off = pmf_gpio_all_amps_off, + .all_amps_restore = pmf_gpio_all_amps_restore, + .set_headphone = pmf_gpio_set_headphone, + .set_speakers = pmf_gpio_set_amp, + .set_lineout = pmf_gpio_set_lineout, + .set_hw_reset = pmf_gpio_set_hw_reset, + .get_headphone = pmf_gpio_get_headphone, + .get_speakers = pmf_gpio_get_amp, + .get_lineout = pmf_gpio_get_lineout, + .set_notify = pmf_set_notify, + .get_detect = pmf_get_detect, +}; + +struct gpio_methods *pmf_gpio_methods = &methods; +EXPORT_SYMBOL_GPL(pmf_gpio_methods); diff --git a/sound/aoa/fabrics/Kconfig b/sound/aoa/fabrics/Kconfig new file mode 100644 index 000000000..3ca475a88 --- /dev/null +++ b/sound/aoa/fabrics/Kconfig @@ -0,0 +1,11 @@ +config SND_AOA_FABRIC_LAYOUT + tristate "layout-id fabric" + select SND_AOA_SOUNDBUS + select SND_AOA_SOUNDBUS_I2S + ---help--- + This enables the layout-id fabric for the Apple Onboard + Audio driver, the module holding it all together + based on the device-tree's layout-id property. + + If you are unsure and have a later Apple machine, + compile it as a module. diff --git a/sound/aoa/fabrics/Makefile b/sound/aoa/fabrics/Makefile new file mode 100644 index 000000000..da37c10ec --- /dev/null +++ b/sound/aoa/fabrics/Makefile @@ -0,0 +1,3 @@ +snd-aoa-fabric-layout-objs += layout.o + +obj-$(CONFIG_SND_AOA_FABRIC_LAYOUT) += snd-aoa-fabric-layout.o diff --git a/sound/aoa/fabrics/layout.c b/sound/aoa/fabrics/layout.c new file mode 100644 index 000000000..1eddf8fa1 --- /dev/null +++ b/sound/aoa/fabrics/layout.c @@ -0,0 +1,1182 @@ +/* + * Apple Onboard Audio driver -- layout/machine id fabric + * + * Copyright 2006-2008 Johannes Berg + * + * GPL v2, can be found in COPYING. + * + * + * This fabric module looks for sound codecs based on the + * layout-id or device-id property in the device tree. + */ +#include +#include +#include +#include +#include "../aoa.h" +#include "../soundbus/soundbus.h" + +MODULE_AUTHOR("Johannes Berg "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa"); + +#define MAX_CODECS_PER_BUS 2 + +/* These are the connections the layout fabric + * knows about. It doesn't really care about the + * input ones, but I thought I'd separate them + * to give them proper names. The thing is that + * Apple usually will distinguish the active output + * by GPIOs, while the active input is set directly + * on the codec. Hence we here tell the codec what + * we think is connected. This information is hard- + * coded below ... */ +#define CC_SPEAKERS (1<<0) +#define CC_HEADPHONE (1<<1) +#define CC_LINEOUT (1<<2) +#define CC_DIGITALOUT (1<<3) +#define CC_LINEIN (1<<4) +#define CC_MICROPHONE (1<<5) +#define CC_DIGITALIN (1<<6) +/* pretty bogus but users complain... + * This is a flag saying that the LINEOUT + * should be renamed to HEADPHONE. + * be careful with input detection! */ +#define CC_LINEOUT_LABELLED_HEADPHONE (1<<7) + +struct codec_connection { + /* CC_ flags from above */ + int connected; + /* codec dependent bit to be set in the aoa_codec.connected field. + * This intentionally doesn't have any generic flags because the + * fabric has to know the codec anyway and all codecs might have + * different connectors */ + int codec_bit; +}; + +struct codec_connect_info { + char *name; + struct codec_connection *connections; +}; + +#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF (1<<0) + +struct layout { + unsigned int layout_id, device_id; + struct codec_connect_info codecs[MAX_CODECS_PER_BUS]; + int flags; + + /* if busname is not assigned, we use 'Master' below, + * so that our layout table doesn't need to be filled + * too much. + * We only assign these two if we expect to find more + * than one soundbus, i.e. on those machines with + * multiple layout-ids */ + char *busname; + int pcmid; +}; + +MODULE_ALIAS("sound-layout-36"); +MODULE_ALIAS("sound-layout-41"); +MODULE_ALIAS("sound-layout-45"); +MODULE_ALIAS("sound-layout-47"); +MODULE_ALIAS("sound-layout-48"); +MODULE_ALIAS("sound-layout-49"); +MODULE_ALIAS("sound-layout-50"); +MODULE_ALIAS("sound-layout-51"); +MODULE_ALIAS("sound-layout-56"); +MODULE_ALIAS("sound-layout-57"); +MODULE_ALIAS("sound-layout-58"); +MODULE_ALIAS("sound-layout-60"); +MODULE_ALIAS("sound-layout-61"); +MODULE_ALIAS("sound-layout-62"); +MODULE_ALIAS("sound-layout-64"); +MODULE_ALIAS("sound-layout-65"); +MODULE_ALIAS("sound-layout-66"); +MODULE_ALIAS("sound-layout-67"); +MODULE_ALIAS("sound-layout-68"); +MODULE_ALIAS("sound-layout-69"); +MODULE_ALIAS("sound-layout-70"); +MODULE_ALIAS("sound-layout-72"); +MODULE_ALIAS("sound-layout-76"); +MODULE_ALIAS("sound-layout-80"); +MODULE_ALIAS("sound-layout-82"); +MODULE_ALIAS("sound-layout-84"); +MODULE_ALIAS("sound-layout-86"); +MODULE_ALIAS("sound-layout-90"); +MODULE_ALIAS("sound-layout-92"); +MODULE_ALIAS("sound-layout-94"); +MODULE_ALIAS("sound-layout-96"); +MODULE_ALIAS("sound-layout-98"); +MODULE_ALIAS("sound-layout-100"); + +MODULE_ALIAS("aoa-device-id-14"); +MODULE_ALIAS("aoa-device-id-22"); +MODULE_ALIAS("aoa-device-id-31"); +MODULE_ALIAS("aoa-device-id-35"); +MODULE_ALIAS("aoa-device-id-44"); + +/* onyx with all but microphone connected */ +static struct codec_connection onyx_connections_nomic[] = { + { + .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, + .codec_bit = 0, + }, + { + .connected = CC_DIGITALOUT, + .codec_bit = 1, + }, + { + .connected = CC_LINEIN, + .codec_bit = 2, + }, + {} /* terminate array by .connected == 0 */ +}; + +/* onyx on machines without headphone */ +static struct codec_connection onyx_connections_noheadphones[] = { + { + .connected = CC_SPEAKERS | CC_LINEOUT | + CC_LINEOUT_LABELLED_HEADPHONE, + .codec_bit = 0, + }, + { + .connected = CC_DIGITALOUT, + .codec_bit = 1, + }, + /* FIXME: are these correct? probably not for all the machines + * below ... If not this will need separating. */ + { + .connected = CC_LINEIN, + .codec_bit = 2, + }, + { + .connected = CC_MICROPHONE, + .codec_bit = 3, + }, + {} /* terminate array by .connected == 0 */ +}; + +/* onyx on machines with real line-out */ +static struct codec_connection onyx_connections_reallineout[] = { + { + .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE, + .codec_bit = 0, + }, + { + .connected = CC_DIGITALOUT, + .codec_bit = 1, + }, + { + .connected = CC_LINEIN, + .codec_bit = 2, + }, + {} /* terminate array by .connected == 0 */ +}; + +/* tas on machines without line out */ +static struct codec_connection tas_connections_nolineout[] = { + { + .connected = CC_SPEAKERS | CC_HEADPHONE, + .codec_bit = 0, + }, + { + .connected = CC_LINEIN, + .codec_bit = 2, + }, + { + .connected = CC_MICROPHONE, + .codec_bit = 3, + }, + {} /* terminate array by .connected == 0 */ +}; + +/* tas on machines with neither line out nor line in */ +static struct codec_connection tas_connections_noline[] = { + { + .connected = CC_SPEAKERS | CC_HEADPHONE, + .codec_bit = 0, + }, + { + .connected = CC_MICROPHONE, + .codec_bit = 3, + }, + {} /* terminate array by .connected == 0 */ +}; + +/* tas on machines without microphone */ +static struct codec_connection tas_connections_nomic[] = { + { + .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, + .codec_bit = 0, + }, + { + .connected = CC_LINEIN, + .codec_bit = 2, + }, + {} /* terminate array by .connected == 0 */ +}; + +/* tas on machines with everything connected */ +static struct codec_connection tas_connections_all[] = { + { + .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, + .codec_bit = 0, + }, + { + .connected = CC_LINEIN, + .codec_bit = 2, + }, + { + .connected = CC_MICROPHONE, + .codec_bit = 3, + }, + {} /* terminate array by .connected == 0 */ +}; + +static struct codec_connection toonie_connections[] = { + { + .connected = CC_SPEAKERS | CC_HEADPHONE, + .codec_bit = 0, + }, + {} /* terminate array by .connected == 0 */ +}; + +static struct codec_connection topaz_input[] = { + { + .connected = CC_DIGITALIN, + .codec_bit = 0, + }, + {} /* terminate array by .connected == 0 */ +}; + +static struct codec_connection topaz_output[] = { + { + .connected = CC_DIGITALOUT, + .codec_bit = 1, + }, + {} /* terminate array by .connected == 0 */ +}; + +static struct codec_connection topaz_inout[] = { + { + .connected = CC_DIGITALIN, + .codec_bit = 0, + }, + { + .connected = CC_DIGITALOUT, + .codec_bit = 1, + }, + {} /* terminate array by .connected == 0 */ +}; + +static struct layout layouts[] = { + /* last PowerBooks (15" Oct 2005) */ + { .layout_id = 82, + .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + .codecs[1] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + /* PowerMac9,1 */ + { .layout_id = 60, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_reallineout, + }, + }, + /* PowerMac9,1 */ + { .layout_id = 61, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + /* PowerBook5,7 */ + { .layout_id = 64, + .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + }, + /* PowerBook5,7 */ + { .layout_id = 65, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + /* PowerBook5,9 [17" Oct 2005] */ + { .layout_id = 84, + .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + .codecs[1] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + /* PowerMac8,1 */ + { .layout_id = 45, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + .codecs[1] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + /* Quad PowerMac (analog in, analog/digital out) */ + { .layout_id = 68, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_nomic, + }, + }, + /* Quad PowerMac (digital in) */ + { .layout_id = 69, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + .busname = "digital in", .pcmid = 1 }, + /* Early 2005 PowerBook (PowerBook 5,6) */ + { .layout_id = 70, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_nolineout, + }, + }, + /* PowerBook 5,4 */ + { .layout_id = 51, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_nolineout, + }, + }, + /* PowerBook6,1 */ + { .device_id = 31, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_nolineout, + }, + }, + /* PowerBook6,5 */ + { .device_id = 44, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_all, + }, + }, + /* PowerBook6,7 */ + { .layout_id = 80, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_noline, + }, + }, + /* PowerBook6,8 */ + { .layout_id = 72, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_nolineout, + }, + }, + /* PowerMac8,2 */ + { .layout_id = 86, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_nomic, + }, + .codecs[1] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + /* PowerBook6,7 */ + { .layout_id = 92, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_nolineout, + }, + }, + /* PowerMac10,1 (Mac Mini) */ + { .layout_id = 58, + .codecs[0] = { + .name = "toonie", + .connections = toonie_connections, + }, + }, + { + .layout_id = 96, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + }, + /* unknown, untested, but this comes from Apple */ + { .layout_id = 41, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_all, + }, + }, + { .layout_id = 36, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_nomic, + }, + .codecs[1] = { + .name = "topaz", + .connections = topaz_inout, + }, + }, + { .layout_id = 47, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + }, + { .layout_id = 48, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + { .layout_id = 49, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_nomic, + }, + }, + { .layout_id = 50, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + { .layout_id = 56, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + }, + { .layout_id = 57, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + { .layout_id = 62, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + .codecs[1] = { + .name = "topaz", + .connections = topaz_output, + }, + }, + { .layout_id = 66, + .codecs[0] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + }, + { .layout_id = 67, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + }, + { .layout_id = 76, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_nomic, + }, + .codecs[1] = { + .name = "topaz", + .connections = topaz_inout, + }, + }, + { .layout_id = 90, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_noline, + }, + }, + { .layout_id = 94, + .codecs[0] = { + .name = "onyx", + /* but it has an external mic?? how to select? */ + .connections = onyx_connections_noheadphones, + }, + }, + { .layout_id = 98, + .codecs[0] = { + .name = "toonie", + .connections = toonie_connections, + }, + }, + { .layout_id = 100, + .codecs[0] = { + .name = "topaz", + .connections = topaz_input, + }, + .codecs[1] = { + .name = "onyx", + .connections = onyx_connections_noheadphones, + }, + }, + /* PowerMac3,4 */ + { .device_id = 14, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_noline, + }, + }, + /* PowerMac3,6 */ + { .device_id = 22, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_all, + }, + }, + /* PowerBook5,2 */ + { .device_id = 35, + .codecs[0] = { + .name = "tas", + .connections = tas_connections_all, + }, + }, + {} +}; + +static struct layout *find_layout_by_id(unsigned int id) +{ + struct layout *l; + + l = layouts; + while (l->codecs[0].name) { + if (l->layout_id == id) + return l; + l++; + } + return NULL; +} + +static struct layout *find_layout_by_device(unsigned int id) +{ + struct layout *l; + + l = layouts; + while (l->codecs[0].name) { + if (l->device_id == id) + return l; + l++; + } + return NULL; +} + +static void use_layout(struct layout *l) +{ + int i; + + for (i=0; icodecs[i].name) { + request_module("snd-aoa-codec-%s", l->codecs[i].name); + } + } + /* now we wait for the codecs to call us back */ +} + +struct layout_dev; + +struct layout_dev_ptr { + struct layout_dev *ptr; +}; + +struct layout_dev { + struct list_head list; + struct soundbus_dev *sdev; + struct device_node *sound; + struct aoa_codec *codecs[MAX_CODECS_PER_BUS]; + struct layout *layout; + struct gpio_runtime gpio; + + /* we need these for headphone/lineout detection */ + struct snd_kcontrol *headphone_ctrl; + struct snd_kcontrol *lineout_ctrl; + struct snd_kcontrol *speaker_ctrl; + struct snd_kcontrol *master_ctrl; + struct snd_kcontrol *headphone_detected_ctrl; + struct snd_kcontrol *lineout_detected_ctrl; + + struct layout_dev_ptr selfptr_headphone; + struct layout_dev_ptr selfptr_lineout; + + u32 have_lineout_detect:1, + have_headphone_detect:1, + switch_on_headphone:1, + switch_on_lineout:1; +}; + +static LIST_HEAD(layouts_list); +static int layouts_list_items; +/* this can go away but only if we allow multiple cards, + * make the fabric handle all the card stuff, etc... */ +static struct layout_dev *layout_device; + +#define control_info snd_ctl_boolean_mono_info + +#define AMP_CONTROL(n, description) \ +static int n##_control_get(struct snd_kcontrol *kcontrol, \ + struct snd_ctl_elem_value *ucontrol) \ +{ \ + struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol); \ + if (gpio->methods && gpio->methods->get_##n) \ + ucontrol->value.integer.value[0] = \ + gpio->methods->get_##n(gpio); \ + return 0; \ +} \ +static int n##_control_put(struct snd_kcontrol *kcontrol, \ + struct snd_ctl_elem_value *ucontrol) \ +{ \ + struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol); \ + if (gpio->methods && gpio->methods->set_##n) \ + gpio->methods->set_##n(gpio, \ + !!ucontrol->value.integer.value[0]); \ + return 1; \ +} \ +static struct snd_kcontrol_new n##_ctl = { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = description, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .info = control_info, \ + .get = n##_control_get, \ + .put = n##_control_put, \ +} + +AMP_CONTROL(headphone, "Headphone Switch"); +AMP_CONTROL(speakers, "Speakers Switch"); +AMP_CONTROL(lineout, "Line-Out Switch"); +AMP_CONTROL(master, "Master Switch"); + +static int detect_choice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); + + switch (kcontrol->private_value) { + case 0: + ucontrol->value.integer.value[0] = ldev->switch_on_headphone; + break; + case 1: + ucontrol->value.integer.value[0] = ldev->switch_on_lineout; + break; + default: + return -ENODEV; + } + return 0; +} + +static int detect_choice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); + + switch (kcontrol->private_value) { + case 0: + ldev->switch_on_headphone = !!ucontrol->value.integer.value[0]; + break; + case 1: + ldev->switch_on_lineout = !!ucontrol->value.integer.value[0]; + break; + default: + return -ENODEV; + } + return 1; +} + +static const struct snd_kcontrol_new headphone_detect_choice = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Detect Autoswitch", + .info = control_info, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .get = detect_choice_get, + .put = detect_choice_put, + .private_value = 0, +}; + +static const struct snd_kcontrol_new lineout_detect_choice = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line-Out Detect Autoswitch", + .info = control_info, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .get = detect_choice_get, + .put = detect_choice_put, + .private_value = 1, +}; + +static int detected_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); + int v; + + switch (kcontrol->private_value) { + case 0: + v = ldev->gpio.methods->get_detect(&ldev->gpio, + AOA_NOTIFY_HEADPHONE); + break; + case 1: + v = ldev->gpio.methods->get_detect(&ldev->gpio, + AOA_NOTIFY_LINE_OUT); + break; + default: + return -ENODEV; + } + ucontrol->value.integer.value[0] = v; + return 0; +} + +static const struct snd_kcontrol_new headphone_detected = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Detected", + .info = control_info, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .get = detected_get, + .private_value = 0, +}; + +static const struct snd_kcontrol_new lineout_detected = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line-Out Detected", + .info = control_info, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .get = detected_get, + .private_value = 1, +}; + +static int check_codec(struct aoa_codec *codec, + struct layout_dev *ldev, + struct codec_connect_info *cci) +{ + const u32 *ref; + char propname[32]; + struct codec_connection *cc; + + /* if the codec has a 'codec' node, we require a reference */ + if (codec->node && (strcmp(codec->node->name, "codec") == 0)) { + snprintf(propname, sizeof(propname), + "platform-%s-codec-ref", codec->name); + ref = of_get_property(ldev->sound, propname, NULL); + if (!ref) { + printk(KERN_INFO "snd-aoa-fabric-layout: " + "required property %s not present\n", propname); + return -ENODEV; + } + if (*ref != codec->node->phandle) { + printk(KERN_INFO "snd-aoa-fabric-layout: " + "%s doesn't match!\n", propname); + return -ENODEV; + } + } else { + if (layouts_list_items != 1) { + printk(KERN_INFO "snd-aoa-fabric-layout: " + "more than one soundbus, but no references.\n"); + return -ENODEV; + } + } + codec->soundbus_dev = ldev->sdev; + codec->gpio = &ldev->gpio; + + cc = cci->connections; + if (!cc) + return -EINVAL; + + printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n"); + + codec->connected = 0; + codec->fabric_data = cc; + + while (cc->connected) { + codec->connected |= 1<codec_bit; + cc++; + } + + return 0; +} + +static int layout_found_codec(struct aoa_codec *codec) +{ + struct layout_dev *ldev; + int i; + + list_for_each_entry(ldev, &layouts_list, list) { + for (i=0; ilayout->codecs[i].name) + continue; + if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) { + if (check_codec(codec, + ldev, + &ldev->layout->codecs[i]) == 0) + return 0; + } + } + } + return -ENODEV; +} + +static void layout_remove_codec(struct aoa_codec *codec) +{ + int i; + /* here remove the codec from the layout dev's + * codec reference */ + + codec->soundbus_dev = NULL; + codec->gpio = NULL; + for (i=0; iptr; + if (data == &ldev->selfptr_headphone) { + v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE); + detected = ldev->headphone_detected_ctrl; + update = ldev->switch_on_headphone; + if (update) { + ldev->gpio.methods->set_speakers(&ldev->gpio, !v); + ldev->gpio.methods->set_headphone(&ldev->gpio, v); + ldev->gpio.methods->set_lineout(&ldev->gpio, 0); + } + } else if (data == &ldev->selfptr_lineout) { + v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT); + detected = ldev->lineout_detected_ctrl; + update = ldev->switch_on_lineout; + if (update) { + ldev->gpio.methods->set_speakers(&ldev->gpio, !v); + ldev->gpio.methods->set_headphone(&ldev->gpio, 0); + ldev->gpio.methods->set_lineout(&ldev->gpio, v); + } + } else + return; + + if (detected) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id); + if (update) { + c = ldev->headphone_ctrl; + if (c) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); + c = ldev->speaker_ctrl; + if (c) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); + c = ldev->lineout_ctrl; + if (c) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); + } +} + +static void layout_attached_codec(struct aoa_codec *codec) +{ + struct codec_connection *cc; + struct snd_kcontrol *ctl; + int headphones, lineout; + struct layout_dev *ldev = layout_device; + + /* need to add this codec to our codec array! */ + + cc = codec->fabric_data; + + headphones = codec->gpio->methods->get_detect(codec->gpio, + AOA_NOTIFY_HEADPHONE); + lineout = codec->gpio->methods->get_detect(codec->gpio, + AOA_NOTIFY_LINE_OUT); + + if (codec->gpio->methods->set_master) { + ctl = snd_ctl_new1(&master_ctl, codec->gpio); + ldev->master_ctrl = ctl; + aoa_snd_ctl_add(ctl); + } + while (cc->connected) { + if (cc->connected & CC_SPEAKERS) { + if (headphones <= 0 && lineout <= 0) + ldev->gpio.methods->set_speakers(codec->gpio, 1); + ctl = snd_ctl_new1(&speakers_ctl, codec->gpio); + ldev->speaker_ctrl = ctl; + aoa_snd_ctl_add(ctl); + } + if (cc->connected & CC_HEADPHONE) { + if (headphones == 1) + ldev->gpio.methods->set_headphone(codec->gpio, 1); + ctl = snd_ctl_new1(&headphone_ctl, codec->gpio); + ldev->headphone_ctrl = ctl; + aoa_snd_ctl_add(ctl); + ldev->have_headphone_detect = + !ldev->gpio.methods + ->set_notify(&ldev->gpio, + AOA_NOTIFY_HEADPHONE, + layout_notify, + &ldev->selfptr_headphone); + if (ldev->have_headphone_detect) { + ctl = snd_ctl_new1(&headphone_detect_choice, + ldev); + aoa_snd_ctl_add(ctl); + ctl = snd_ctl_new1(&headphone_detected, + ldev); + ldev->headphone_detected_ctrl = ctl; + aoa_snd_ctl_add(ctl); + } + } + if (cc->connected & CC_LINEOUT) { + if (lineout == 1) + ldev->gpio.methods->set_lineout(codec->gpio, 1); + ctl = snd_ctl_new1(&lineout_ctl, codec->gpio); + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strlcpy(ctl->id.name, + "Headphone Switch", sizeof(ctl->id.name)); + ldev->lineout_ctrl = ctl; + aoa_snd_ctl_add(ctl); + ldev->have_lineout_detect = + !ldev->gpio.methods + ->set_notify(&ldev->gpio, + AOA_NOTIFY_LINE_OUT, + layout_notify, + &ldev->selfptr_lineout); + if (ldev->have_lineout_detect) { + ctl = snd_ctl_new1(&lineout_detect_choice, + ldev); + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strlcpy(ctl->id.name, + "Headphone Detect Autoswitch", + sizeof(ctl->id.name)); + aoa_snd_ctl_add(ctl); + ctl = snd_ctl_new1(&lineout_detected, + ldev); + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strlcpy(ctl->id.name, + "Headphone Detected", + sizeof(ctl->id.name)); + ldev->lineout_detected_ctrl = ctl; + aoa_snd_ctl_add(ctl); + } + } + cc++; + } + /* now update initial state */ + if (ldev->have_headphone_detect) + layout_notify(&ldev->selfptr_headphone); + if (ldev->have_lineout_detect) + layout_notify(&ldev->selfptr_lineout); +} + +static struct aoa_fabric layout_fabric = { + .name = "SoundByLayout", + .owner = THIS_MODULE, + .found_codec = layout_found_codec, + .remove_codec = layout_remove_codec, + .attached_codec = layout_attached_codec, +}; + +static int aoa_fabric_layout_probe(struct soundbus_dev *sdev) +{ + struct device_node *sound = NULL; + const unsigned int *id; + struct layout *layout = NULL; + struct layout_dev *ldev = NULL; + int err; + + /* hm, currently we can only have one ... */ + if (layout_device) + return -ENODEV; + + /* by breaking out we keep a reference */ + while ((sound = of_get_next_child(sdev->ofdev.dev.of_node, sound))) { + if (sound->type && strcasecmp(sound->type, "soundchip") == 0) + break; + } + if (!sound) + return -ENODEV; + + id = of_get_property(sound, "layout-id", NULL); + if (id) { + layout = find_layout_by_id(*id); + } else { + id = of_get_property(sound, "device-id", NULL); + if (id) + layout = find_layout_by_device(*id); + } + + if (!layout) { + printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n"); + goto outnodev; + } + + ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL); + if (!ldev) + goto outnodev; + + layout_device = ldev; + ldev->sdev = sdev; + ldev->sound = sound; + ldev->layout = layout; + ldev->gpio.node = sound->parent; + switch (layout->layout_id) { + case 0: /* anything with device_id, not layout_id */ + case 41: /* that unknown machine no one seems to have */ + case 51: /* PowerBook5,4 */ + case 58: /* Mac Mini */ + ldev->gpio.methods = ftr_gpio_methods; + printk(KERN_DEBUG + "snd-aoa-fabric-layout: Using direct GPIOs\n"); + break; + default: + ldev->gpio.methods = pmf_gpio_methods; + printk(KERN_DEBUG + "snd-aoa-fabric-layout: Using PMF GPIOs\n"); + } + ldev->selfptr_headphone.ptr = ldev; + ldev->selfptr_lineout.ptr = ldev; + dev_set_drvdata(&sdev->ofdev.dev, ldev); + list_add(&ldev->list, &layouts_list); + layouts_list_items++; + + /* assign these before registering ourselves, so + * callbacks that are done during registration + * already have the values */ + sdev->pcmid = ldev->layout->pcmid; + if (ldev->layout->busname) { + sdev->pcmname = ldev->layout->busname; + } else { + sdev->pcmname = "Master"; + } + + ldev->gpio.methods->init(&ldev->gpio); + + err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev); + if (err && err != -EALREADY) { + printk(KERN_INFO "snd-aoa-fabric-layout: can't use," + " another fabric is active!\n"); + goto outlistdel; + } + + use_layout(layout); + ldev->switch_on_headphone = 1; + ldev->switch_on_lineout = 1; + return 0; + outlistdel: + /* we won't be using these then... */ + ldev->gpio.methods->exit(&ldev->gpio); + /* reset if we didn't use it */ + sdev->pcmname = NULL; + sdev->pcmid = -1; + list_del(&ldev->list); + layouts_list_items--; + kfree(ldev); + outnodev: + of_node_put(sound); + layout_device = NULL; + return -ENODEV; +} + +static int aoa_fabric_layout_remove(struct soundbus_dev *sdev) +{ + struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev); + int i; + + for (i=0; icodecs[i]) { + aoa_fabric_unlink_codec(ldev->codecs[i]); + } + ldev->codecs[i] = NULL; + } + list_del(&ldev->list); + layouts_list_items--; + of_node_put(ldev->sound); + + ldev->gpio.methods->set_notify(&ldev->gpio, + AOA_NOTIFY_HEADPHONE, + NULL, + NULL); + ldev->gpio.methods->set_notify(&ldev->gpio, + AOA_NOTIFY_LINE_OUT, + NULL, + NULL); + + ldev->gpio.methods->exit(&ldev->gpio); + layout_device = NULL; + kfree(ldev); + sdev->pcmid = -1; + sdev->pcmname = NULL; + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int aoa_fabric_layout_suspend(struct device *dev) +{ + struct layout_dev *ldev = dev_get_drvdata(dev); + + if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off) + ldev->gpio.methods->all_amps_off(&ldev->gpio); + + return 0; +} + +static int aoa_fabric_layout_resume(struct device *dev) +{ + struct layout_dev *ldev = dev_get_drvdata(dev); + + if (ldev->gpio.methods && ldev->gpio.methods->all_amps_restore) + ldev->gpio.methods->all_amps_restore(&ldev->gpio); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(aoa_fabric_layout_pm_ops, + aoa_fabric_layout_suspend, aoa_fabric_layout_resume); + +#endif + +static struct soundbus_driver aoa_soundbus_driver = { + .name = "snd_aoa_soundbus_drv", + .owner = THIS_MODULE, + .probe = aoa_fabric_layout_probe, + .remove = aoa_fabric_layout_remove, + .driver = { + .owner = THIS_MODULE, +#ifdef CONFIG_PM_SLEEP + .pm = &aoa_fabric_layout_pm_ops, +#endif + } +}; + +static int __init aoa_fabric_layout_init(void) +{ + return soundbus_register_driver(&aoa_soundbus_driver); +} + +static void __exit aoa_fabric_layout_exit(void) +{ + soundbus_unregister_driver(&aoa_soundbus_driver); + aoa_fabric_unregister(&layout_fabric); +} + +module_init(aoa_fabric_layout_init); +module_exit(aoa_fabric_layout_exit); diff --git a/sound/aoa/soundbus/Kconfig b/sound/aoa/soundbus/Kconfig new file mode 100644 index 000000000..839d1137b --- /dev/null +++ b/sound/aoa/soundbus/Kconfig @@ -0,0 +1,14 @@ +config SND_AOA_SOUNDBUS + tristate "Apple Soundbus support" + select SND_PCM + ---help--- + This option enables the generic driver for the soundbus + support on Apple machines. + + It is required for the sound bus implementations. + +config SND_AOA_SOUNDBUS_I2S + tristate "I2S bus support" + depends on SND_AOA_SOUNDBUS && PCI + ---help--- + This option enables support for Apple I2S busses. diff --git a/sound/aoa/soundbus/Makefile b/sound/aoa/soundbus/Makefile new file mode 100644 index 000000000..0e61f5aa0 --- /dev/null +++ b/sound/aoa/soundbus/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SND_AOA_SOUNDBUS) += snd-aoa-soundbus.o +snd-aoa-soundbus-objs := core.o sysfs.o +obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += i2sbus/ diff --git a/sound/aoa/soundbus/core.c b/sound/aoa/soundbus/core.c new file mode 100644 index 000000000..70bcaa7f9 --- /dev/null +++ b/sound/aoa/soundbus/core.c @@ -0,0 +1,193 @@ +/* + * soundbus + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#include +#include "soundbus.h" + +MODULE_AUTHOR("Johannes Berg "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Apple Soundbus"); + +struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev) +{ + struct device *tmp; + + if (!dev) + return NULL; + tmp = get_device(&dev->ofdev.dev); + if (tmp) + return to_soundbus_device(tmp); + else + return NULL; +} +EXPORT_SYMBOL_GPL(soundbus_dev_get); + +void soundbus_dev_put(struct soundbus_dev *dev) +{ + if (dev) + put_device(&dev->ofdev.dev); +} +EXPORT_SYMBOL_GPL(soundbus_dev_put); + +static int soundbus_probe(struct device *dev) +{ + int error = -ENODEV; + struct soundbus_driver *drv; + struct soundbus_dev *soundbus_dev; + + drv = to_soundbus_driver(dev->driver); + soundbus_dev = to_soundbus_device(dev); + + if (!drv->probe) + return error; + + soundbus_dev_get(soundbus_dev); + + error = drv->probe(soundbus_dev); + if (error) + soundbus_dev_put(soundbus_dev); + + return error; +} + + +static int soundbus_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct soundbus_dev * soundbus_dev; + struct platform_device * of; + const char *compat; + int retval = 0; + int cplen, seen = 0; + + if (!dev) + return -ENODEV; + + soundbus_dev = to_soundbus_device(dev); + if (!soundbus_dev) + return -ENODEV; + + of = &soundbus_dev->ofdev; + + /* stuff we want to pass to /sbin/hotplug */ + retval = add_uevent_var(env, "OF_NAME=%s", of->dev.of_node->name); + if (retval) + return retval; + + retval = add_uevent_var(env, "OF_TYPE=%s", of->dev.of_node->type); + if (retval) + return retval; + + /* Since the compatible field can contain pretty much anything + * it's not really legal to split it out with commas. We split it + * up using a number of environment variables instead. */ + + compat = of_get_property(of->dev.of_node, "compatible", &cplen); + while (compat && cplen > 0) { + int tmp = env->buflen; + retval = add_uevent_var(env, "OF_COMPATIBLE_%d=%s", seen, compat); + if (retval) + return retval; + compat += env->buflen - tmp; + cplen -= env->buflen - tmp; + seen += 1; + } + + retval = add_uevent_var(env, "OF_COMPATIBLE_N=%d", seen); + if (retval) + return retval; + retval = add_uevent_var(env, "MODALIAS=%s", soundbus_dev->modalias); + + return retval; +} + +static int soundbus_device_remove(struct device *dev) +{ + struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); + struct soundbus_driver * drv = to_soundbus_driver(dev->driver); + + if (dev->driver && drv->remove) + drv->remove(soundbus_dev); + soundbus_dev_put(soundbus_dev); + + return 0; +} + +static void soundbus_device_shutdown(struct device *dev) +{ + struct soundbus_dev * soundbus_dev = to_soundbus_device(dev); + struct soundbus_driver * drv = to_soundbus_driver(dev->driver); + + if (dev->driver && drv->shutdown) + drv->shutdown(soundbus_dev); +} + +/* soundbus_dev_attrs is declared in sysfs.c */ +ATTRIBUTE_GROUPS(soundbus_dev); +static struct bus_type soundbus_bus_type = { + .name = "aoa-soundbus", + .probe = soundbus_probe, + .uevent = soundbus_uevent, + .remove = soundbus_device_remove, + .shutdown = soundbus_device_shutdown, + .dev_groups = soundbus_dev_groups, +}; + +int soundbus_add_one(struct soundbus_dev *dev) +{ + static int devcount; + + /* sanity checks */ + if (!dev->attach_codec || + !dev->ofdev.dev.of_node || + dev->pcmname || + dev->pcmid != -1) { + printk(KERN_ERR "soundbus: adding device failed sanity check!\n"); + return -EINVAL; + } + + dev_set_name(&dev->ofdev.dev, "soundbus:%x", ++devcount); + dev->ofdev.dev.bus = &soundbus_bus_type; + return of_device_register(&dev->ofdev); +} +EXPORT_SYMBOL_GPL(soundbus_add_one); + +void soundbus_remove_one(struct soundbus_dev *dev) +{ + of_device_unregister(&dev->ofdev); +} +EXPORT_SYMBOL_GPL(soundbus_remove_one); + +int soundbus_register_driver(struct soundbus_driver *drv) +{ + /* initialize common driver fields */ + drv->driver.name = drv->name; + drv->driver.bus = &soundbus_bus_type; + + /* register with core */ + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(soundbus_register_driver); + +void soundbus_unregister_driver(struct soundbus_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(soundbus_unregister_driver); + +static int __init soundbus_init(void) +{ + return bus_register(&soundbus_bus_type); +} + +static void __exit soundbus_exit(void) +{ + bus_unregister(&soundbus_bus_type); +} + +subsys_initcall(soundbus_init); +module_exit(soundbus_exit); diff --git a/sound/aoa/soundbus/i2sbus/Makefile b/sound/aoa/soundbus/i2sbus/Makefile new file mode 100644 index 000000000..1b949b2a4 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_SND_AOA_SOUNDBUS_I2S) += snd-aoa-i2sbus.o +snd-aoa-i2sbus-objs := core.o pcm.o control.o diff --git a/sound/aoa/soundbus/i2sbus/control.c b/sound/aoa/soundbus/i2sbus/control.c new file mode 100644 index 000000000..f4495decc --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/control.c @@ -0,0 +1,194 @@ +/* + * i2sbus driver -- bus control routines + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "i2sbus.h" + +int i2sbus_control_init(struct macio_dev* dev, struct i2sbus_control **c) +{ + *c = kzalloc(sizeof(struct i2sbus_control), GFP_KERNEL); + if (!*c) + return -ENOMEM; + + INIT_LIST_HEAD(&(*c)->list); + + (*c)->macio = dev->bus->chip; + return 0; +} + +void i2sbus_control_destroy(struct i2sbus_control *c) +{ + kfree(c); +} + +/* this is serialised externally */ +int i2sbus_control_add_dev(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev) +{ + struct device_node *np; + + np = i2sdev->sound.ofdev.dev.of_node; + i2sdev->enable = pmf_find_function(np, "enable"); + i2sdev->cell_enable = pmf_find_function(np, "cell-enable"); + i2sdev->clock_enable = pmf_find_function(np, "clock-enable"); + i2sdev->cell_disable = pmf_find_function(np, "cell-disable"); + i2sdev->clock_disable = pmf_find_function(np, "clock-disable"); + + /* if the bus number is not 0 or 1 we absolutely need to use + * the platform functions -- there's nothing in Darwin that + * would allow seeing a system behind what the FCRs are then, + * and I don't want to go parsing a bunch of platform functions + * by hand to try finding a system... */ + if (i2sdev->bus_number != 0 && i2sdev->bus_number != 1 && + (!i2sdev->enable || + !i2sdev->cell_enable || !i2sdev->clock_enable || + !i2sdev->cell_disable || !i2sdev->clock_disable)) { + pmf_put_function(i2sdev->enable); + pmf_put_function(i2sdev->cell_enable); + pmf_put_function(i2sdev->clock_enable); + pmf_put_function(i2sdev->cell_disable); + pmf_put_function(i2sdev->clock_disable); + return -ENODEV; + } + + list_add(&i2sdev->item, &c->list); + + return 0; +} + +void i2sbus_control_remove_dev(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev) +{ + /* this is serialised externally */ + list_del(&i2sdev->item); + if (list_empty(&c->list)) + i2sbus_control_destroy(c); +} + +int i2sbus_control_enable(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev) +{ + struct pmf_args args = { .count = 0 }; + struct macio_chip *macio = c->macio; + + if (i2sdev->enable) + return pmf_call_one(i2sdev->enable, &args); + + if (macio == NULL || macio->base == NULL) + return -ENODEV; + + switch (i2sdev->bus_number) { + case 0: + /* these need to be locked or done through + * newly created feature calls! */ + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE); + break; + case 1: + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_ENABLE); + break; + default: + return -ENODEV; + } + return 0; +} + +int i2sbus_control_cell(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev, + int enable) +{ + struct pmf_args args = { .count = 0 }; + struct macio_chip *macio = c->macio; + + switch (enable) { + case 0: + if (i2sdev->cell_disable) + return pmf_call_one(i2sdev->cell_disable, &args); + break; + case 1: + if (i2sdev->cell_enable) + return pmf_call_one(i2sdev->cell_enable, &args); + break; + default: + printk(KERN_ERR "i2sbus: INVALID CELL ENABLE VALUE\n"); + return -ENODEV; + } + + if (macio == NULL || macio->base == NULL) + return -ENODEV; + + switch (i2sdev->bus_number) { + case 0: + if (enable) + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE); + else + MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE); + break; + case 1: + if (enable) + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE); + else + MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CELL_ENABLE); + break; + default: + return -ENODEV; + } + return 0; +} + +int i2sbus_control_clock(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev, + int enable) +{ + struct pmf_args args = { .count = 0 }; + struct macio_chip *macio = c->macio; + + switch (enable) { + case 0: + if (i2sdev->clock_disable) + return pmf_call_one(i2sdev->clock_disable, &args); + break; + case 1: + if (i2sdev->clock_enable) + return pmf_call_one(i2sdev->clock_enable, &args); + break; + default: + printk(KERN_ERR "i2sbus: INVALID CLOCK ENABLE VALUE\n"); + return -ENODEV; + } + + if (macio == NULL || macio->base == NULL) + return -ENODEV; + + switch (i2sdev->bus_number) { + case 0: + if (enable) + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT); + else + MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT); + break; + case 1: + if (enable) + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT); + else + MACIO_BIC(KEYLARGO_FCR1, KL1_I2S1_CLK_ENABLE_BIT); + break; + default: + return -ENODEV; + } + return 0; +} diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c new file mode 100644 index 000000000..000b58522 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -0,0 +1,458 @@ +/* + * i2sbus driver + * + * Copyright 2006-2008 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "../soundbus.h" +#include "i2sbus.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Johannes Berg "); +MODULE_DESCRIPTION("Apple Soundbus: I2S support"); + +static int force; +module_param(force, int, 0444); +MODULE_PARM_DESC(force, "Force loading i2sbus even when" + " no layout-id property is present"); + +static const struct of_device_id i2sbus_match[] = { + { .name = "i2s" }, + { } +}; + +MODULE_DEVICE_TABLE(of, i2sbus_match); + +static int alloc_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, + struct dbdma_command_mem *r, + int numcmds) +{ + /* one more for rounding, one for branch back, one for stop command */ + r->size = (numcmds + 3) * sizeof(struct dbdma_cmd); + /* We use the PCI APIs for now until the generic one gets fixed + * enough or until we get some macio-specific versions + */ + r->space = dma_zalloc_coherent(&macio_get_pci_dev(i2sdev->macio)->dev, + r->size, &r->bus_addr, GFP_KERNEL); + if (!r->space) + return -ENOMEM; + + r->cmds = (void*)DBDMA_ALIGN(r->space); + r->bus_cmd_start = r->bus_addr + + (dma_addr_t)((char*)r->cmds - (char*)r->space); + + return 0; +} + +static void free_dbdma_descriptor_ring(struct i2sbus_dev *i2sdev, + struct dbdma_command_mem *r) +{ + if (!r->space) return; + + dma_free_coherent(&macio_get_pci_dev(i2sdev->macio)->dev, + r->size, r->space, r->bus_addr); +} + +static void i2sbus_release_dev(struct device *dev) +{ + struct i2sbus_dev *i2sdev; + int i; + + i2sdev = container_of(dev, struct i2sbus_dev, sound.ofdev.dev); + iounmap(i2sdev->intfregs); + iounmap(i2sdev->out.dbdma); + iounmap(i2sdev->in.dbdma); + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) + release_and_free_resource(i2sdev->allocated_resource[i]); + free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring); + free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring); + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) + free_irq(i2sdev->interrupts[i], i2sdev); + i2sbus_control_remove_dev(i2sdev->control, i2sdev); + mutex_destroy(&i2sdev->lock); + kfree(i2sdev); +} + +static irqreturn_t i2sbus_bus_intr(int irq, void *devid) +{ + struct i2sbus_dev *dev = devid; + u32 intreg; + + spin_lock(&dev->low_lock); + intreg = in_le32(&dev->intfregs->intr_ctl); + + /* acknowledge interrupt reasons */ + out_le32(&dev->intfregs->intr_ctl, intreg); + + spin_unlock(&dev->low_lock); + + return IRQ_HANDLED; +} + + +/* + * XXX FIXME: We test the layout_id's here to get the proper way of + * mapping in various registers, thanks to bugs in Apple device-trees. + * We could instead key off the machine model and the name of the i2s + * node (i2s-a). This we'll do when we move it all to macio_asic.c + * and have that export items for each sub-node too. + */ +static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index, + int layout, struct resource *res) +{ + struct device_node *parent; + int pindex, rc = -ENXIO; + const u32 *reg; + + /* Machines with layout 76 and 36 (K2 based) have a weird device + * tree what we need to special case. + * Normal machines just fetch the resource from the i2s-X node. + * Darwin further divides normal machines into old and new layouts + * with a subtely different code path but that doesn't seem necessary + * in practice, they just bloated it. In addition, even on our K2 + * case the i2s-modem node, if we ever want to handle it, uses the + * normal layout + */ + if (layout != 76 && layout != 36) + return of_address_to_resource(np, index, res); + + parent = of_get_parent(np); + pindex = (index == aoa_resource_i2smmio) ? 0 : 1; + rc = of_address_to_resource(parent, pindex, res); + if (rc) + goto bail; + reg = of_get_property(np, "reg", NULL); + if (reg == NULL) { + rc = -ENXIO; + goto bail; + } + res->start += reg[index * 2]; + res->end = res->start + reg[index * 2 + 1] - 1; + bail: + of_node_put(parent); + return rc; +} + +/* FIXME: look at device node refcounting */ +static int i2sbus_add_dev(struct macio_dev *macio, + struct i2sbus_control *control, + struct device_node *np) +{ + struct i2sbus_dev *dev; + struct device_node *child = NULL, *sound = NULL; + struct resource *r; + int i, layout = 0, rlen, ok = force; + static const char *rnames[] = { "i2sbus: %s (control)", + "i2sbus: %s (tx)", + "i2sbus: %s (rx)" }; + static irq_handler_t ints[] = { + i2sbus_bus_intr, + i2sbus_tx_intr, + i2sbus_rx_intr + }; + + if (strlen(np->name) != 5) + return 0; + if (strncmp(np->name, "i2s-", 4)) + return 0; + + dev = kzalloc(sizeof(struct i2sbus_dev), GFP_KERNEL); + if (!dev) + return 0; + + i = 0; + while ((child = of_get_next_child(np, child))) { + if (strcmp(child->name, "sound") == 0) { + i++; + sound = child; + } + } + if (i == 1) { + const u32 *id = of_get_property(sound, "layout-id", NULL); + + if (id) { + layout = *id; + snprintf(dev->sound.modalias, 32, + "sound-layout-%d", layout); + ok = 1; + } else { + id = of_get_property(sound, "device-id", NULL); + /* + * We probably cannot handle all device-id machines, + * so restrict to those we do handle for now. + */ + if (id && (*id == 22 || *id == 14 || *id == 35 || + *id == 31 || *id == 44)) { + snprintf(dev->sound.modalias, 32, + "aoa-device-id-%d", *id); + ok = 1; + layout = -1; + } + } + } + /* for the time being, until we can handle non-layout-id + * things in some fabric, refuse to attach if there is no + * layout-id property or we haven't been forced to attach. + * When there are two i2s busses and only one has a layout-id, + * then this depends on the order, but that isn't important + * either as the second one in that case is just a modem. */ + if (!ok) { + kfree(dev); + return -ENODEV; + } + + mutex_init(&dev->lock); + spin_lock_init(&dev->low_lock); + dev->sound.ofdev.archdata.dma_mask = macio->ofdev.archdata.dma_mask; + dev->sound.ofdev.dev.of_node = np; + dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.archdata.dma_mask; + dev->sound.ofdev.dev.parent = &macio->ofdev.dev; + dev->sound.ofdev.dev.release = i2sbus_release_dev; + dev->sound.attach_codec = i2sbus_attach_codec; + dev->sound.detach_codec = i2sbus_detach_codec; + dev->sound.pcmid = -1; + dev->macio = macio; + dev->control = control; + dev->bus_number = np->name[4] - 'a'; + INIT_LIST_HEAD(&dev->sound.codec_list); + + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + dev->interrupts[i] = -1; + snprintf(dev->rnames[i], sizeof(dev->rnames[i]), + rnames[i], np->name); + } + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + int irq = irq_of_parse_and_map(np, i); + if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = irq; + } + + + /* Resource handling is problematic as some device-trees contain + * useless crap (ugh ugh ugh). We work around that here by calling + * specific functions for calculating the appropriate resources. + * + * This will all be moved to macio_asic.c at one point + */ + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + if (i2sbus_get_and_fixup_rsrc(np,i,layout,&dev->resources[i])) + goto err; + /* If only we could use our resource dev->resources[i]... + * but request_resource doesn't know about parents and + * contained resources... + */ + dev->allocated_resource[i] = + request_mem_region(dev->resources[i].start, + resource_size(&dev->resources[i]), + dev->rnames[i]); + if (!dev->allocated_resource[i]) { + printk(KERN_ERR "i2sbus: failed to claim resource %d!\n", i); + goto err; + } + } + + r = &dev->resources[aoa_resource_i2smmio]; + rlen = resource_size(r); + if (rlen < sizeof(struct i2s_interface_regs)) + goto err; + dev->intfregs = ioremap(r->start, rlen); + + r = &dev->resources[aoa_resource_txdbdma]; + rlen = resource_size(r); + if (rlen < sizeof(struct dbdma_regs)) + goto err; + dev->out.dbdma = ioremap(r->start, rlen); + + r = &dev->resources[aoa_resource_rxdbdma]; + rlen = resource_size(r); + if (rlen < sizeof(struct dbdma_regs)) + goto err; + dev->in.dbdma = ioremap(r->start, rlen); + + if (!dev->intfregs || !dev->out.dbdma || !dev->in.dbdma) + goto err; + + if (alloc_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring, + MAX_DBDMA_COMMANDS)) + goto err; + if (alloc_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring, + MAX_DBDMA_COMMANDS)) + goto err; + + if (i2sbus_control_add_dev(dev->control, dev)) { + printk(KERN_ERR "i2sbus: control layer didn't like bus\n"); + goto err; + } + + if (soundbus_add_one(&dev->sound)) { + printk(KERN_DEBUG "i2sbus: device registration error!\n"); + goto err; + } + + /* enable this cell */ + i2sbus_control_cell(dev->control, dev, 1); + i2sbus_control_enable(dev->control, dev); + i2sbus_control_clock(dev->control, dev, 1); + + return 1; + err: + for (i=0;i<3;i++) + if (dev->interrupts[i] != -1) + free_irq(dev->interrupts[i], dev); + free_dbdma_descriptor_ring(dev, &dev->out.dbdma_ring); + free_dbdma_descriptor_ring(dev, &dev->in.dbdma_ring); + iounmap(dev->intfregs); + iounmap(dev->out.dbdma); + iounmap(dev->in.dbdma); + for (i=0;i<3;i++) + release_and_free_resource(dev->allocated_resource[i]); + mutex_destroy(&dev->lock); + kfree(dev); + return 0; +} + +static int i2sbus_probe(struct macio_dev* dev, const struct of_device_id *match) +{ + struct device_node *np = NULL; + int got = 0, err; + struct i2sbus_control *control = NULL; + + err = i2sbus_control_init(dev, &control); + if (err) + return err; + if (!control) { + printk(KERN_ERR "i2sbus_control_init API breakage\n"); + return -ENODEV; + } + + while ((np = of_get_next_child(dev->ofdev.dev.of_node, np))) { + if (of_device_is_compatible(np, "i2sbus") || + of_device_is_compatible(np, "i2s-modem")) { + got += i2sbus_add_dev(dev, control, np); + } + } + + if (!got) { + /* found none, clean up */ + i2sbus_control_destroy(control); + return -ENODEV; + } + + dev_set_drvdata(&dev->ofdev.dev, control); + + return 0; +} + +static int i2sbus_remove(struct macio_dev* dev) +{ + struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev); + struct i2sbus_dev *i2sdev, *tmp; + + list_for_each_entry_safe(i2sdev, tmp, &control->list, item) + soundbus_remove_one(&i2sdev->sound); + + return 0; +} + +#ifdef CONFIG_PM +static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state) +{ + struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev); + struct codec_info_item *cii; + struct i2sbus_dev* i2sdev; + int err, ret = 0; + + list_for_each_entry(i2sdev, &control->list, item) { + /* Notify Alsa */ + /* Suspend PCM streams */ + snd_pcm_suspend_all(i2sdev->sound.pcm); + + /* Notify codecs */ + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + err = 0; + if (cii->codec->suspend) + err = cii->codec->suspend(cii, state); + if (err) + ret = err; + } + + /* wait until streams are stopped */ + i2sbus_wait_for_stop_both(i2sdev); + } + + return ret; +} + +static int i2sbus_resume(struct macio_dev* dev) +{ + struct i2sbus_control *control = dev_get_drvdata(&dev->ofdev.dev); + struct codec_info_item *cii; + struct i2sbus_dev* i2sdev; + int err, ret = 0; + + list_for_each_entry(i2sdev, &control->list, item) { + /* reset i2s bus format etc. */ + i2sbus_pcm_prepare_both(i2sdev); + + /* Notify codecs so they can re-initialize */ + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + err = 0; + if (cii->codec->resume) + err = cii->codec->resume(cii); + if (err) + ret = err; + } + } + + return ret; +} +#endif /* CONFIG_PM */ + +static int i2sbus_shutdown(struct macio_dev* dev) +{ + return 0; +} + +static struct macio_driver i2sbus_drv = { + .driver = { + .name = "soundbus-i2s", + .owner = THIS_MODULE, + .of_match_table = i2sbus_match, + }, + .probe = i2sbus_probe, + .remove = i2sbus_remove, +#ifdef CONFIG_PM + .suspend = i2sbus_suspend, + .resume = i2sbus_resume, +#endif + .shutdown = i2sbus_shutdown, +}; + +static int __init soundbus_i2sbus_init(void) +{ + return macio_register_driver(&i2sbus_drv); +} + +static void __exit soundbus_i2sbus_exit(void) +{ + macio_unregister_driver(&i2sbus_drv); +} + +module_init(soundbus_i2sbus_init); +module_exit(soundbus_i2sbus_exit); diff --git a/sound/aoa/soundbus/i2sbus/i2sbus.h b/sound/aoa/soundbus/i2sbus/i2sbus.h new file mode 100644 index 000000000..befefd99e --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/i2sbus.h @@ -0,0 +1,126 @@ +/* + * i2sbus driver -- private definitions + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ +#ifndef __I2SBUS_H +#define __I2SBUS_H +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "interface.h" +#include "../soundbus.h" + +struct i2sbus_control { + struct list_head list; + struct macio_chip *macio; +}; + +#define MAX_DBDMA_COMMANDS 32 + +struct dbdma_command_mem { + dma_addr_t bus_addr; + dma_addr_t bus_cmd_start; + struct dbdma_cmd *cmds; + void *space; + int size; + u32 running:1; + u32 stopping:1; +}; + +struct pcm_info { + u32 created:1, /* has this direction been created with alsa? */ + active:1; /* is this stream active? */ + /* runtime information */ + struct snd_pcm_substream *substream; + int current_period; + u32 frame_count; + struct dbdma_command_mem dbdma_ring; + volatile struct dbdma_regs __iomem *dbdma; + struct completion *stop_completion; +}; + +enum { + aoa_resource_i2smmio = 0, + aoa_resource_txdbdma, + aoa_resource_rxdbdma, +}; + +struct i2sbus_dev { + struct soundbus_dev sound; + struct macio_dev *macio; + struct i2sbus_control *control; + volatile struct i2s_interface_regs __iomem *intfregs; + + struct resource resources[3]; + struct resource *allocated_resource[3]; + int interrupts[3]; + char rnames[3][32]; + + /* info about currently active substreams */ + struct pcm_info out, in; + snd_pcm_format_t format; + unsigned int rate; + + /* list for a single controller */ + struct list_head item; + /* number of bus on controller */ + int bus_number; + /* for use by control layer */ + struct pmf_function *enable, + *cell_enable, + *cell_disable, + *clock_enable, + *clock_disable; + + /* locks */ + /* spinlock for low-level interrupt locking */ + spinlock_t low_lock; + /* mutex for high-level consistency */ + struct mutex lock; +}; + +#define soundbus_dev_to_i2sbus_dev(sdev) \ + container_of(sdev, struct i2sbus_dev, sound) + +/* pcm specific functions */ +extern int +i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card, + struct codec_info *ci, void *data); +extern void +i2sbus_detach_codec(struct soundbus_dev *dev, void *data); +extern irqreturn_t +i2sbus_tx_intr(int irq, void *devid); +extern irqreturn_t +i2sbus_rx_intr(int irq, void *devid); + +extern void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev); +extern void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev); + +/* control specific functions */ +extern int i2sbus_control_init(struct macio_dev* dev, + struct i2sbus_control **c); +extern void i2sbus_control_destroy(struct i2sbus_control *c); +extern int i2sbus_control_add_dev(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev); +extern void i2sbus_control_remove_dev(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev); +extern int i2sbus_control_enable(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev); +extern int i2sbus_control_cell(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev, + int enable); +extern int i2sbus_control_clock(struct i2sbus_control *c, + struct i2sbus_dev *i2sdev, + int enable); +#endif /* __I2SBUS_H */ diff --git a/sound/aoa/soundbus/i2sbus/interface.h b/sound/aoa/soundbus/i2sbus/interface.h new file mode 100644 index 000000000..c6b5f5452 --- /dev/null +++ b/sound/aoa/soundbus/i2sbus/interface.h @@ -0,0 +1,187 @@ +/* + * i2sbus driver -- interface register definitions + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ +#ifndef __I2SBUS_INTERFACE_H +#define __I2SBUS_INTERFACE_H + +/* i2s bus control registers, at least what we know about them */ + +#define __PAD(m,n) u8 __pad##m[n] +#define _PAD(line, n) __PAD(line, n) +#define PAD(n) _PAD(__LINE__, (n)) +struct i2s_interface_regs { + __le32 intr_ctl; /* 0x00 */ + PAD(12); + __le32 serial_format; /* 0x10 */ + PAD(12); + __le32 codec_msg_out; /* 0x20 */ + PAD(12); + __le32 codec_msg_in; /* 0x30 */ + PAD(12); + __le32 frame_count; /* 0x40 */ + PAD(12); + __le32 frame_match; /* 0x50 */ + PAD(12); + __le32 data_word_sizes; /* 0x60 */ + PAD(12); + __le32 peak_level_sel; /* 0x70 */ + PAD(12); + __le32 peak_level_in0; /* 0x80 */ + PAD(12); + __le32 peak_level_in1; /* 0x90 */ + PAD(12); + /* total size: 0x100 bytes */ +} __attribute__((__packed__)); + +/* interrupt register is just a bitfield with + * interrupt enable and pending bits */ +#define I2S_REG_INTR_CTL 0x00 +# define I2S_INT_FRAME_COUNT (1<<31) +# define I2S_PENDING_FRAME_COUNT (1<<30) +# define I2S_INT_MESSAGE_FLAG (1<<29) +# define I2S_PENDING_MESSAGE_FLAG (1<<28) +# define I2S_INT_NEW_PEAK (1<<27) +# define I2S_PENDING_NEW_PEAK (1<<26) +# define I2S_INT_CLOCKS_STOPPED (1<<25) +# define I2S_PENDING_CLOCKS_STOPPED (1<<24) +# define I2S_INT_EXTERNAL_SYNC_ERROR (1<<23) +# define I2S_PENDING_EXTERNAL_SYNC_ERROR (1<<22) +# define I2S_INT_EXTERNAL_SYNC_OK (1<<21) +# define I2S_PENDING_EXTERNAL_SYNC_OK (1<<20) +# define I2S_INT_NEW_SAMPLE_RATE (1<<19) +# define I2S_PENDING_NEW_SAMPLE_RATE (1<<18) +# define I2S_INT_STATUS_FLAG (1<<17) +# define I2S_PENDING_STATUS_FLAG (1<<16) + +/* serial format register is more interesting :) + * It contains: + * - clock source + * - MClk divisor + * - SClk divisor + * - SClk master flag + * - serial format (sony, i2s 64x, i2s 32x, dav, silabs) + * - external sample frequency interrupt (don't understand) + * - external sample frequency + */ +#define I2S_REG_SERIAL_FORMAT 0x10 +/* clock source. You get either 18.432, 45.1584 or 49.1520 MHz */ +# define I2S_SF_CLOCK_SOURCE_SHIFT 30 +# define I2S_SF_CLOCK_SOURCE_MASK (3< + * + * GPL v2, can be found in COPYING. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../soundbus.h" +#include "i2sbus.h" + +static inline void get_pcm_info(struct i2sbus_dev *i2sdev, int in, + struct pcm_info **pi, struct pcm_info **other) +{ + if (in) { + if (pi) + *pi = &i2sdev->in; + if (other) + *other = &i2sdev->out; + } else { + if (pi) + *pi = &i2sdev->out; + if (other) + *other = &i2sdev->in; + } +} + +static int clock_and_divisors(int mclk, int sclk, int rate, int *out) +{ + /* sclk must be derived from mclk! */ + if (mclk % sclk) + return -1; + /* derive sclk register value */ + if (i2s_sf_sclkdiv(mclk / sclk, out)) + return -1; + + if (I2S_CLOCK_SPEED_18MHz % (rate * mclk) == 0) { + if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_18MHz / (rate * mclk), out)) { + *out |= I2S_SF_CLOCK_SOURCE_18MHz; + return 0; + } + } + if (I2S_CLOCK_SPEED_45MHz % (rate * mclk) == 0) { + if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_45MHz / (rate * mclk), out)) { + *out |= I2S_SF_CLOCK_SOURCE_45MHz; + return 0; + } + } + if (I2S_CLOCK_SPEED_49MHz % (rate * mclk) == 0) { + if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_49MHz / (rate * mclk), out)) { + *out |= I2S_SF_CLOCK_SOURCE_49MHz; + return 0; + } + } + return -1; +} + +#define CHECK_RATE(rate) \ + do { if (rates & SNDRV_PCM_RATE_ ##rate) { \ + int dummy; \ + if (clock_and_divisors(sysclock_factor, \ + bus_factor, rate, &dummy)) \ + rates &= ~SNDRV_PCM_RATE_ ##rate; \ + } } while (0) + +static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in) +{ + struct pcm_info *pi, *other; + struct soundbus_dev *sdev; + int masks_inited = 0, err; + struct codec_info_item *cii, *rev; + struct snd_pcm_hardware *hw; + u64 formats = 0; + unsigned int rates = 0; + struct transfer_info v; + int result = 0; + int bus_factor = 0, sysclock_factor = 0; + int found_this; + + mutex_lock(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, &other); + + hw = &pi->substream->runtime->hw; + sdev = &i2sdev->sound; + + if (pi->active) { + /* alsa messed up */ + result = -EBUSY; + goto out_unlock; + } + + /* we now need to assign the hw */ + list_for_each_entry(cii, &sdev->codec_list, list) { + struct transfer_info *ti = cii->codec->transfers; + bus_factor = cii->codec->bus_factor; + sysclock_factor = cii->codec->sysclock_factor; + while (ti->formats && ti->rates) { + v = *ti; + if (ti->transfer_in == in + && cii->codec->usable(cii, ti, &v)) { + if (masks_inited) { + formats &= v.formats; + rates &= v.rates; + } else { + formats = v.formats; + rates = v.rates; + masks_inited = 1; + } + } + ti++; + } + } + if (!masks_inited || !bus_factor || !sysclock_factor) { + result = -ENODEV; + goto out_unlock; + } + /* bus dependent stuff */ + hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_JOINT_DUPLEX; + + CHECK_RATE(5512); + CHECK_RATE(8000); + CHECK_RATE(11025); + CHECK_RATE(16000); + CHECK_RATE(22050); + CHECK_RATE(32000); + CHECK_RATE(44100); + CHECK_RATE(48000); + CHECK_RATE(64000); + CHECK_RATE(88200); + CHECK_RATE(96000); + CHECK_RATE(176400); + CHECK_RATE(192000); + hw->rates = rates; + + /* well. the codec might want 24 bits only, and we'll + * ever only transfer 24 bits, but they are top-aligned! + * So for alsa, we claim that we're doing full 32 bit + * while in reality we'll ignore the lower 8 bits of + * that when doing playback (they're transferred as 0 + * as far as I know, no codecs we have are 32-bit capable + * so I can't really test) and when doing recording we'll + * always have those lower 8 bits recorded as 0 */ + if (formats & SNDRV_PCM_FMTBIT_S24_BE) + formats |= SNDRV_PCM_FMTBIT_S32_BE; + if (formats & SNDRV_PCM_FMTBIT_U24_BE) + formats |= SNDRV_PCM_FMTBIT_U32_BE; + /* now mask off what we can support. I suppose we could + * also support S24_3LE and some similar formats, but I + * doubt there's a codec that would be able to use that, + * so we don't support it here. */ + hw->formats = formats & (SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U16_BE | + SNDRV_PCM_FMTBIT_S32_BE | + SNDRV_PCM_FMTBIT_U32_BE); + + /* we need to set the highest and lowest rate possible. + * These are the highest and lowest rates alsa can + * support properly in its bitfield. + * Below, we'll use that to restrict to the rate + * currently in use (if any). */ + hw->rate_min = 5512; + hw->rate_max = 192000; + /* if the other stream is active, then we can only + * support what it is currently using. + * FIXME: I lied. This comment is wrong. We can support + * anything that works with the same serial format, ie. + * when recording 24 bit sound we can well play 16 bit + * sound at the same time iff using the same transfer mode. + */ + if (other->active) { + /* FIXME: is this guaranteed by the alsa api? */ + hw->formats &= pcm_format_to_bits(i2sdev->format); + /* see above, restrict rates to the one we already have */ + hw->rate_min = i2sdev->rate; + hw->rate_max = i2sdev->rate; + } + + hw->channels_min = 2; + hw->channels_max = 2; + /* these are somewhat arbitrary */ + hw->buffer_bytes_max = 131072; + hw->period_bytes_min = 256; + hw->period_bytes_max = 16384; + hw->periods_min = 3; + hw->periods_max = MAX_DBDMA_COMMANDS; + err = snd_pcm_hw_constraint_integer(pi->substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) { + result = err; + goto out_unlock; + } + list_for_each_entry(cii, &sdev->codec_list, list) { + if (cii->codec->open) { + err = cii->codec->open(cii, pi->substream); + if (err) { + result = err; + /* unwind */ + found_this = 0; + list_for_each_entry_reverse(rev, + &sdev->codec_list, list) { + if (found_this && rev->codec->close) { + rev->codec->close(rev, + pi->substream); + } + if (rev == cii) + found_this = 1; + } + goto out_unlock; + } + } + } + + out_unlock: + mutex_unlock(&i2sdev->lock); + return result; +} + +#undef CHECK_RATE + +static int i2sbus_pcm_close(struct i2sbus_dev *i2sdev, int in) +{ + struct codec_info_item *cii; + struct pcm_info *pi; + int err = 0, tmp; + + mutex_lock(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, NULL); + + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + if (cii->codec->close) { + tmp = cii->codec->close(cii, pi->substream); + if (tmp) + err = tmp; + } + } + + pi->substream = NULL; + pi->active = 0; + mutex_unlock(&i2sdev->lock); + return err; +} + +static void i2sbus_wait_for_stop(struct i2sbus_dev *i2sdev, + struct pcm_info *pi) +{ + unsigned long flags; + struct completion done; + long timeout; + + spin_lock_irqsave(&i2sdev->low_lock, flags); + if (pi->dbdma_ring.stopping) { + init_completion(&done); + pi->stop_completion = &done; + spin_unlock_irqrestore(&i2sdev->low_lock, flags); + timeout = wait_for_completion_timeout(&done, HZ); + spin_lock_irqsave(&i2sdev->low_lock, flags); + pi->stop_completion = NULL; + if (timeout == 0) { + /* timeout expired, stop dbdma forcefully */ + printk(KERN_ERR "i2sbus_wait_for_stop: timed out\n"); + /* make sure RUN, PAUSE and S0 bits are cleared */ + out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); + pi->dbdma_ring.stopping = 0; + timeout = 10; + while (in_le32(&pi->dbdma->status) & ACTIVE) { + if (--timeout <= 0) + break; + udelay(1); + } + } + } + spin_unlock_irqrestore(&i2sdev->low_lock, flags); +} + +#ifdef CONFIG_PM +void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev) +{ + struct pcm_info *pi; + + get_pcm_info(i2sdev, 0, &pi, NULL); + i2sbus_wait_for_stop(i2sdev, pi); + get_pcm_info(i2sdev, 1, &pi, NULL); + i2sbus_wait_for_stop(i2sdev, pi); +} +#endif + +static int i2sbus_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); +} + +static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + struct pcm_info *pi; + + get_pcm_info(i2sdev, in, &pi, NULL); + if (pi->dbdma_ring.stopping) + i2sbus_wait_for_stop(i2sdev, pi); + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream) +{ + return i2sbus_hw_free(substream, 0); +} + +static int i2sbus_record_hw_free(struct snd_pcm_substream *substream) +{ + return i2sbus_hw_free(substream, 1); +} + +static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) +{ + /* whee. Hard work now. The user has selected a bitrate + * and bit format, so now we have to program our + * I2S controller appropriately. */ + struct snd_pcm_runtime *runtime; + struct dbdma_cmd *command; + int i, periodsize, nperiods; + dma_addr_t offset; + struct bus_info bi; + struct codec_info_item *cii; + int sfr = 0; /* serial format register */ + int dws = 0; /* data word sizes reg */ + int input_16bit; + struct pcm_info *pi, *other; + int cnt; + int result = 0; + unsigned int cmd, stopaddr; + + mutex_lock(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, &other); + + if (pi->dbdma_ring.running) { + result = -EBUSY; + goto out_unlock; + } + if (pi->dbdma_ring.stopping) + i2sbus_wait_for_stop(i2sdev, pi); + + if (!pi->substream || !pi->substream->runtime) { + result = -EINVAL; + goto out_unlock; + } + + runtime = pi->substream->runtime; + pi->active = 1; + if (other->active && + ((i2sdev->format != runtime->format) + || (i2sdev->rate != runtime->rate))) { + result = -EINVAL; + goto out_unlock; + } + + i2sdev->format = runtime->format; + i2sdev->rate = runtime->rate; + + periodsize = snd_pcm_lib_period_bytes(pi->substream); + nperiods = pi->substream->runtime->periods; + pi->current_period = 0; + + /* generate dbdma command ring first */ + command = pi->dbdma_ring.cmds; + memset(command, 0, (nperiods + 2) * sizeof(struct dbdma_cmd)); + + /* commands to DMA to/from the ring */ + /* + * For input, we need to do a graceful stop; if we abort + * the DMA, we end up with leftover bytes that corrupt + * the next recording. To do this we set the S0 status + * bit and wait for the DMA controller to stop. Each + * command has a branch condition to + * make it branch to a stop command if S0 is set. + * On input we also need to wait for the S7 bit to be + * set before turning off the DMA controller. + * In fact we do the graceful stop for output as well. + */ + offset = runtime->dma_addr; + cmd = (in? INPUT_MORE: OUTPUT_MORE) | BR_IFSET | INTR_ALWAYS; + stopaddr = pi->dbdma_ring.bus_cmd_start + + (nperiods + 1) * sizeof(struct dbdma_cmd); + for (i = 0; i < nperiods; i++, command++, offset += periodsize) { + command->command = cpu_to_le16(cmd); + command->cmd_dep = cpu_to_le32(stopaddr); + command->phy_addr = cpu_to_le32(offset); + command->req_count = cpu_to_le16(periodsize); + } + + /* branch back to beginning of ring */ + command->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS); + command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start); + command++; + + /* set stop command */ + command->command = cpu_to_le16(DBDMA_STOP); + + /* ok, let's set the serial format and stuff */ + switch (runtime->format) { + /* 16 bit formats */ + case SNDRV_PCM_FORMAT_S16_BE: + case SNDRV_PCM_FORMAT_U16_BE: + /* FIXME: if we add different bus factors we need to + * do more here!! */ + bi.bus_factor = 0; + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + bi.bus_factor = cii->codec->bus_factor; + break; + } + if (!bi.bus_factor) { + result = -ENODEV; + goto out_unlock; + } + input_16bit = 1; + break; + case SNDRV_PCM_FORMAT_S32_BE: + case SNDRV_PCM_FORMAT_U32_BE: + /* force 64x bus speed, otherwise the data cannot be + * transferred quickly enough! */ + bi.bus_factor = 64; + input_16bit = 0; + break; + default: + result = -EINVAL; + goto out_unlock; + } + /* we assume all sysclocks are the same! */ + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + bi.sysclock_factor = cii->codec->sysclock_factor; + break; + } + + if (clock_and_divisors(bi.sysclock_factor, + bi.bus_factor, + runtime->rate, + &sfr) < 0) { + result = -EINVAL; + goto out_unlock; + } + switch (bi.bus_factor) { + case 32: + sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X; + break; + case 64: + sfr |= I2S_SF_SERIAL_FORMAT_I2S_64X; + break; + } + /* FIXME: THIS ASSUMES MASTER ALL THE TIME */ + sfr |= I2S_SF_SCLK_MASTER; + + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { + int err = 0; + if (cii->codec->prepare) + err = cii->codec->prepare(cii, &bi, pi->substream); + if (err) { + result = err; + goto out_unlock; + } + } + /* codecs are fine with it, so set our clocks */ + if (input_16bit) + dws = (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) | + (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) | + I2S_DWS_DATA_IN_16BIT | I2S_DWS_DATA_OUT_16BIT; + else + dws = (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) | + (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) | + I2S_DWS_DATA_IN_24BIT | I2S_DWS_DATA_OUT_24BIT; + + /* early exit if already programmed correctly */ + /* not locking these is fine since we touch them only in this function */ + if (in_le32(&i2sdev->intfregs->serial_format) == sfr + && in_le32(&i2sdev->intfregs->data_word_sizes) == dws) + goto out_unlock; + + /* let's notify the codecs about clocks going away. + * For now we only do mastering on the i2s cell... */ + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) + if (cii->codec->switch_clock) + cii->codec->switch_clock(cii, CLOCK_SWITCH_PREPARE_SLAVE); + + i2sbus_control_enable(i2sdev->control, i2sdev); + i2sbus_control_cell(i2sdev->control, i2sdev, 1); + + out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED); + + i2sbus_control_clock(i2sdev->control, i2sdev, 0); + + msleep(1); + + /* wait for clock stopped. This can apparently take a while... */ + cnt = 100; + while (cnt-- && + !(in_le32(&i2sdev->intfregs->intr_ctl) & I2S_PENDING_CLOCKS_STOPPED)) { + msleep(5); + } + out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED); + + /* not locking these is fine since we touch them only in this function */ + out_le32(&i2sdev->intfregs->serial_format, sfr); + out_le32(&i2sdev->intfregs->data_word_sizes, dws); + + i2sbus_control_enable(i2sdev->control, i2sdev); + i2sbus_control_cell(i2sdev->control, i2sdev, 1); + i2sbus_control_clock(i2sdev->control, i2sdev, 1); + msleep(1); + + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) + if (cii->codec->switch_clock) + cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE); + + out_unlock: + mutex_unlock(&i2sdev->lock); + return result; +} + +#ifdef CONFIG_PM +void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev) +{ + i2sbus_pcm_prepare(i2sdev, 0); + i2sbus_pcm_prepare(i2sdev, 1); +} +#endif + +static int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd) +{ + struct codec_info_item *cii; + struct pcm_info *pi; + int result = 0; + unsigned long flags; + + spin_lock_irqsave(&i2sdev->low_lock, flags); + + get_pcm_info(i2sdev, in, &pi, NULL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (pi->dbdma_ring.running) { + result = -EALREADY; + goto out_unlock; + } + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) + if (cii->codec->start) + cii->codec->start(cii, pi->substream); + pi->dbdma_ring.running = 1; + + if (pi->dbdma_ring.stopping) { + /* Clear the S0 bit, then see if we stopped yet */ + out_le32(&pi->dbdma->control, 1 << 16); + if (in_le32(&pi->dbdma->status) & ACTIVE) { + /* possible race here? */ + udelay(10); + if (in_le32(&pi->dbdma->status) & ACTIVE) { + pi->dbdma_ring.stopping = 0; + goto out_unlock; /* keep running */ + } + } + } + + /* make sure RUN, PAUSE and S0 bits are cleared */ + out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); + + /* set branch condition select register */ + out_le32(&pi->dbdma->br_sel, (1 << 16) | 1); + + /* write dma command buffer address to the dbdma chip */ + out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start); + + /* initialize the frame count and current period */ + pi->current_period = 0; + pi->frame_count = in_le32(&i2sdev->intfregs->frame_count); + + /* set the DMA controller running */ + out_le32(&pi->dbdma->control, (RUN << 16) | RUN); + + /* off you go! */ + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!pi->dbdma_ring.running) { + result = -EALREADY; + goto out_unlock; + } + pi->dbdma_ring.running = 0; + + /* Set the S0 bit to make the DMA branch to the stop cmd */ + out_le32(&pi->dbdma->control, (1 << 16) | 1); + pi->dbdma_ring.stopping = 1; + + list_for_each_entry(cii, &i2sdev->sound.codec_list, list) + if (cii->codec->stop) + cii->codec->stop(cii, pi->substream); + break; + default: + result = -EINVAL; + goto out_unlock; + } + + out_unlock: + spin_unlock_irqrestore(&i2sdev->low_lock, flags); + return result; +} + +static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in) +{ + struct pcm_info *pi; + u32 fc; + + get_pcm_info(i2sdev, in, &pi, NULL); + + fc = in_le32(&i2sdev->intfregs->frame_count); + fc = fc - pi->frame_count; + + if (fc >= pi->substream->runtime->buffer_size) + fc %= pi->substream->runtime->buffer_size; + return fc; +} + +static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in) +{ + struct pcm_info *pi; + u32 fc, nframes; + u32 status; + int timeout, i; + int dma_stopped = 0; + struct snd_pcm_runtime *runtime; + + spin_lock(&i2sdev->low_lock); + get_pcm_info(i2sdev, in, &pi, NULL); + if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping) + goto out_unlock; + + i = pi->current_period; + runtime = pi->substream->runtime; + while (pi->dbdma_ring.cmds[i].xfer_status) { + if (le16_to_cpu(pi->dbdma_ring.cmds[i].xfer_status) & BT) + /* + * BT is the branch taken bit. If it took a branch + * it is because we set the S0 bit to make it + * branch to the stop command. + */ + dma_stopped = 1; + pi->dbdma_ring.cmds[i].xfer_status = 0; + + if (++i >= runtime->periods) { + i = 0; + pi->frame_count += runtime->buffer_size; + } + pi->current_period = i; + + /* + * Check the frame count. The DMA tends to get a bit + * ahead of the frame counter, which confuses the core. + */ + fc = in_le32(&i2sdev->intfregs->frame_count); + nframes = i * runtime->period_size; + if (fc < pi->frame_count + nframes) + pi->frame_count = fc - nframes; + } + + if (dma_stopped) { + timeout = 1000; + for (;;) { + status = in_le32(&pi->dbdma->status); + if (!(status & ACTIVE) && (!in || (status & 0x80))) + break; + if (--timeout <= 0) { + printk(KERN_ERR "i2sbus: timed out " + "waiting for DMA to stop!\n"); + break; + } + udelay(1); + } + + /* Turn off DMA controller, clear S0 bit */ + out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); + + pi->dbdma_ring.stopping = 0; + if (pi->stop_completion) + complete(pi->stop_completion); + } + + if (!pi->dbdma_ring.running) + goto out_unlock; + spin_unlock(&i2sdev->low_lock); + /* may call _trigger again, hence needs to be unlocked */ + snd_pcm_period_elapsed(pi->substream); + return; + + out_unlock: + spin_unlock(&i2sdev->low_lock); +} + +irqreturn_t i2sbus_tx_intr(int irq, void *devid) +{ + handle_interrupt((struct i2sbus_dev *)devid, 0); + return IRQ_HANDLED; +} + +irqreturn_t i2sbus_rx_intr(int irq, void *devid) +{ + handle_interrupt((struct i2sbus_dev *)devid, 1); + return IRQ_HANDLED; +} + +static int i2sbus_playback_open(struct snd_pcm_substream *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + i2sdev->out.substream = substream; + return i2sbus_pcm_open(i2sdev, 0); +} + +static int i2sbus_playback_close(struct snd_pcm_substream *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + int err; + + if (!i2sdev) + return -EINVAL; + if (i2sdev->out.substream != substream) + return -EINVAL; + err = i2sbus_pcm_close(i2sdev, 0); + if (!err) + i2sdev->out.substream = NULL; + return err; +} + +static int i2sbus_playback_prepare(struct snd_pcm_substream *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + if (i2sdev->out.substream != substream) + return -EINVAL; + return i2sbus_pcm_prepare(i2sdev, 0); +} + +static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + if (i2sdev->out.substream != substream) + return -EINVAL; + return i2sbus_pcm_trigger(i2sdev, 0, cmd); +} + +static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream + *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + if (i2sdev->out.substream != substream) + return 0; + return i2sbus_pcm_pointer(i2sdev, 0); +} + +static const struct snd_pcm_ops i2sbus_playback_ops = { + .open = i2sbus_playback_open, + .close = i2sbus_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = i2sbus_hw_params, + .hw_free = i2sbus_playback_hw_free, + .prepare = i2sbus_playback_prepare, + .trigger = i2sbus_playback_trigger, + .pointer = i2sbus_playback_pointer, +}; + +static int i2sbus_record_open(struct snd_pcm_substream *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + i2sdev->in.substream = substream; + return i2sbus_pcm_open(i2sdev, 1); +} + +static int i2sbus_record_close(struct snd_pcm_substream *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + int err; + + if (!i2sdev) + return -EINVAL; + if (i2sdev->in.substream != substream) + return -EINVAL; + err = i2sbus_pcm_close(i2sdev, 1); + if (!err) + i2sdev->in.substream = NULL; + return err; +} + +static int i2sbus_record_prepare(struct snd_pcm_substream *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + if (i2sdev->in.substream != substream) + return -EINVAL; + return i2sbus_pcm_prepare(i2sdev, 1); +} + +static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + if (i2sdev->in.substream != substream) + return -EINVAL; + return i2sbus_pcm_trigger(i2sdev, 1, cmd); +} + +static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream + *substream) +{ + struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); + + if (!i2sdev) + return -EINVAL; + if (i2sdev->in.substream != substream) + return 0; + return i2sbus_pcm_pointer(i2sdev, 1); +} + +static const struct snd_pcm_ops i2sbus_record_ops = { + .open = i2sbus_record_open, + .close = i2sbus_record_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = i2sbus_hw_params, + .hw_free = i2sbus_record_hw_free, + .prepare = i2sbus_record_prepare, + .trigger = i2sbus_record_trigger, + .pointer = i2sbus_record_pointer, +}; + +static void i2sbus_private_free(struct snd_pcm *pcm) +{ + struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm); + struct codec_info_item *p, *tmp; + + i2sdev->sound.pcm = NULL; + i2sdev->out.created = 0; + i2sdev->in.created = 0; + list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) { + printk(KERN_ERR "i2sbus: a codec didn't unregister!\n"); + list_del(&p->list); + module_put(p->codec->owner); + kfree(p); + } + soundbus_dev_put(&i2sdev->sound); + module_put(THIS_MODULE); +} + +int +i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card, + struct codec_info *ci, void *data) +{ + int err, in = 0, out = 0; + struct transfer_info *tmp; + struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev); + struct codec_info_item *cii; + + if (!dev->pcmname || dev->pcmid == -1) { + printk(KERN_ERR "i2sbus: pcm name and id must be set!\n"); + return -EINVAL; + } + + list_for_each_entry(cii, &dev->codec_list, list) { + if (cii->codec_data == data) + return -EALREADY; + } + + if (!ci->transfers || !ci->transfers->formats + || !ci->transfers->rates || !ci->usable) + return -EINVAL; + + /* we currently code the i2s transfer on the clock, and support only + * 32 and 64 */ + if (ci->bus_factor != 32 && ci->bus_factor != 64) + return -EINVAL; + + /* If you want to fix this, you need to keep track of what transport infos + * are to be used, which codecs they belong to, and then fix all the + * sysclock/busclock stuff above to depend on which is usable */ + list_for_each_entry(cii, &dev->codec_list, list) { + if (cii->codec->sysclock_factor != ci->sysclock_factor) { + printk(KERN_DEBUG + "cannot yet handle multiple different sysclocks!\n"); + return -EINVAL; + } + if (cii->codec->bus_factor != ci->bus_factor) { + printk(KERN_DEBUG + "cannot yet handle multiple different bus clocks!\n"); + return -EINVAL; + } + } + + tmp = ci->transfers; + while (tmp->formats && tmp->rates) { + if (tmp->transfer_in) + in = 1; + else + out = 1; + tmp++; + } + + cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL); + if (!cii) { + printk(KERN_DEBUG "i2sbus: failed to allocate cii\n"); + return -ENOMEM; + } + + /* use the private data to point to the codec info */ + cii->sdev = soundbus_dev_get(dev); + cii->codec = ci; + cii->codec_data = data; + + if (!cii->sdev) { + printk(KERN_DEBUG + "i2sbus: failed to get soundbus dev reference\n"); + err = -ENODEV; + goto out_free_cii; + } + + if (!try_module_get(THIS_MODULE)) { + printk(KERN_DEBUG "i2sbus: failed to get module reference!\n"); + err = -EBUSY; + goto out_put_sdev; + } + + if (!try_module_get(ci->owner)) { + printk(KERN_DEBUG + "i2sbus: failed to get module reference to codec owner!\n"); + err = -EBUSY; + goto out_put_this_module; + } + + if (!dev->pcm) { + err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0, + &dev->pcm); + if (err) { + printk(KERN_DEBUG "i2sbus: failed to create pcm\n"); + goto out_put_ci_module; + } + } + + /* ALSA yet again sucks. + * If it is ever fixed, remove this line. See below. */ + out = in = 1; + + if (!i2sdev->out.created && out) { + if (dev->pcm->card != card) { + /* eh? */ + printk(KERN_ERR + "Can't attach same bus to different cards!\n"); + err = -EINVAL; + goto out_put_ci_module; + } + err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (err) + goto out_put_ci_module; + snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, + &i2sbus_playback_ops); + dev->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].dev.parent = + &dev->ofdev.dev; + i2sdev->out.created = 1; + } + + if (!i2sdev->in.created && in) { + if (dev->pcm->card != card) { + printk(KERN_ERR + "Can't attach same bus to different cards!\n"); + err = -EINVAL; + goto out_put_ci_module; + } + err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (err) + goto out_put_ci_module; + snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, + &i2sbus_record_ops); + dev->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].dev.parent = + &dev->ofdev.dev; + i2sdev->in.created = 1; + } + + /* so we have to register the pcm after adding any substream + * to it because alsa doesn't create the devices for the + * substreams when we add them later. + * Therefore, force in and out on both busses (above) and + * register the pcm now instead of just after creating it. + */ + err = snd_device_register(card, dev->pcm); + if (err) { + printk(KERN_ERR "i2sbus: error registering new pcm\n"); + goto out_put_ci_module; + } + /* no errors any more, so let's add this to our list */ + list_add(&cii->list, &dev->codec_list); + + dev->pcm->private_data = i2sdev; + dev->pcm->private_free = i2sbus_private_free; + + /* well, we really should support scatter/gather DMA */ + snd_pcm_lib_preallocate_pages_for_all( + dev->pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)), + 64 * 1024, 64 * 1024); + + return 0; + out_put_ci_module: + module_put(ci->owner); + out_put_this_module: + module_put(THIS_MODULE); + out_put_sdev: + soundbus_dev_put(dev); + out_free_cii: + kfree(cii); + return err; +} + +void i2sbus_detach_codec(struct soundbus_dev *dev, void *data) +{ + struct codec_info_item *cii = NULL, *i; + + list_for_each_entry(i, &dev->codec_list, list) { + if (i->codec_data == data) { + cii = i; + break; + } + } + if (cii) { + list_del(&cii->list); + module_put(cii->codec->owner); + kfree(cii); + } + /* no more codecs, but still a pcm? */ + if (list_empty(&dev->codec_list) && dev->pcm) { + /* the actual cleanup is done by the callback above! */ + snd_device_free(dev->pcm->card, dev->pcm); + } +} diff --git a/sound/aoa/soundbus/soundbus.h b/sound/aoa/soundbus/soundbus.h new file mode 100644 index 000000000..ae4022438 --- /dev/null +++ b/sound/aoa/soundbus/soundbus.h @@ -0,0 +1,202 @@ +/* + * soundbus generic definitions + * + * Copyright 2006 Johannes Berg + * + * GPL v2, can be found in COPYING. + */ +#ifndef __SOUNDBUS_H +#define __SOUNDBUS_H + +#include +#include +#include + + +/* When switching from master to slave or the other way around, + * you don't want to have the codec chip acting as clock source + * while the bus still is. + * More importantly, while switch from slave to master, you need + * to turn off the chip's master function first, but then there's + * no clock for a while and other chips might reset, so we notify + * their drivers after having switched. + * The constants here are codec-point of view, so when we switch + * the soundbus to master we tell the codec we're going to switch + * and give it CLOCK_SWITCH_PREPARE_SLAVE! + */ +enum clock_switch { + CLOCK_SWITCH_PREPARE_SLAVE, + CLOCK_SWITCH_PREPARE_MASTER, + CLOCK_SWITCH_SLAVE, + CLOCK_SWITCH_MASTER, + CLOCK_SWITCH_NOTIFY, +}; + +/* information on a transfer the codec can take */ +struct transfer_info { + u64 formats; /* SNDRV_PCM_FMTBIT_* */ + unsigned int rates; /* SNDRV_PCM_RATE_* */ + /* flags */ + u32 transfer_in:1, /* input = 1, output = 0 */ + must_be_clock_source:1; + /* for codecs to distinguish among their TIs */ + int tag; +}; + +struct codec_info_item { + struct codec_info *codec; + void *codec_data; + struct soundbus_dev *sdev; + /* internal, to be used by the soundbus provider */ + struct list_head list; +}; + +/* for prepare, where the codecs need to know + * what we're going to drive the bus with */ +struct bus_info { + /* see below */ + int sysclock_factor; + int bus_factor; +}; + +/* information on the codec itself, plus function pointers */ +struct codec_info { + /* the module this lives in */ + struct module *owner; + + /* supported transfer possibilities, array terminated by + * formats or rates being 0. */ + struct transfer_info *transfers; + + /* Master clock speed factor + * to be used (master clock speed = sysclock_factor * sampling freq) + * Unused if the soundbus provider has no such notion. + */ + int sysclock_factor; + + /* Bus factor, bus clock speed = bus_factor * sampling freq) + * Unused if the soundbus provider has no such notion. + */ + int bus_factor; + + /* operations */ + /* clock switching, see above */ + int (*switch_clock)(struct codec_info_item *cii, + enum clock_switch clock); + + /* called for each transfer_info when the user + * opens the pcm device to determine what the + * hardware can support at this point in time. + * That can depend on other user-switchable controls. + * Return 1 if usable, 0 if not. + * out points to another instance of a transfer_info + * which is initialised to the values in *ti, and + * it's format and rate values can be modified by + * the callback if it is necessary to further restrict + * the formats that can be used at the moment, for + * example when one codec has multiple logical codec + * info structs for multiple inputs. + */ + int (*usable)(struct codec_info_item *cii, + struct transfer_info *ti, + struct transfer_info *out); + + /* called when pcm stream is opened, probably not implemented + * most of the time since it isn't too useful */ + int (*open)(struct codec_info_item *cii, + struct snd_pcm_substream *substream); + + /* called when the pcm stream is closed, at this point + * the user choices can all be unlocked (see below) */ + int (*close)(struct codec_info_item *cii, + struct snd_pcm_substream *substream); + + /* if the codec must forbid some user choices because + * they are not valid with the substream/transfer info, + * it must do so here. Example: no digital output for + * incompatible framerate, say 8KHz, on Onyx. + * If the selected stuff in the substream is NOT + * compatible, you have to reject this call! */ + int (*prepare)(struct codec_info_item *cii, + struct bus_info *bi, + struct snd_pcm_substream *substream); + + /* start() is called before data is pushed to the codec. + * Note that start() must be atomic! */ + int (*start)(struct codec_info_item *cii, + struct snd_pcm_substream *substream); + + /* stop() is called after data is no longer pushed to the codec. + * Note that stop() must be atomic! */ + int (*stop)(struct codec_info_item *cii, + struct snd_pcm_substream *substream); + + int (*suspend)(struct codec_info_item *cii, pm_message_t state); + int (*resume)(struct codec_info_item *cii); +}; + +/* information on a soundbus device */ +struct soundbus_dev { + /* the bus it belongs to */ + struct list_head onbuslist; + + /* the of device it represents */ + struct platform_device ofdev; + + /* what modules go by */ + char modalias[32]; + + /* These fields must be before attach_codec can be called. + * They should be set by the owner of the alsa card object + * that is needed, and whoever sets them must make sure + * that they are unique within that alsa card object. */ + char *pcmname; + int pcmid; + + /* this is assigned by the soundbus provider in attach_codec */ + struct snd_pcm *pcm; + + /* operations */ + /* attach a codec to this soundbus, give the alsa + * card object the PCMs for this soundbus should be in. + * The 'data' pointer must be unique, it is used as the + * key for detach_codec(). */ + int (*attach_codec)(struct soundbus_dev *dev, struct snd_card *card, + struct codec_info *ci, void *data); + void (*detach_codec)(struct soundbus_dev *dev, void *data); + /* TODO: suspend/resume */ + + /* private for the soundbus provider */ + struct list_head codec_list; + u32 have_out:1, have_in:1; +}; +#define to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev.dev) +#define of_to_soundbus_device(d) container_of(d, struct soundbus_dev, ofdev) + +extern int soundbus_add_one(struct soundbus_dev *dev); +extern void soundbus_remove_one(struct soundbus_dev *dev); + +extern struct soundbus_dev *soundbus_dev_get(struct soundbus_dev *dev); +extern void soundbus_dev_put(struct soundbus_dev *dev); + +struct soundbus_driver { + char *name; + struct module *owner; + + /* we don't implement any matching at all */ + + int (*probe)(struct soundbus_dev* dev); + int (*remove)(struct soundbus_dev* dev); + + int (*shutdown)(struct soundbus_dev* dev); + + struct device_driver driver; +}; +#define to_soundbus_driver(drv) container_of(drv,struct soundbus_driver, driver) + +extern int soundbus_register_driver(struct soundbus_driver *drv); +extern void soundbus_unregister_driver(struct soundbus_driver *drv); + +extern struct attribute *soundbus_dev_attrs[]; + +#endif /* __SOUNDBUS_H */ diff --git a/sound/aoa/soundbus/sysfs.c b/sound/aoa/soundbus/sysfs.c new file mode 100644 index 000000000..81da020bd --- /dev/null +++ b/sound/aoa/soundbus/sysfs.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +/* FIX UP */ +#include "soundbus.h" + +#define soundbus_config_of_attr(field, format_string) \ +static ssize_t \ +field##_show (struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct soundbus_dev *mdev = to_soundbus_device (dev); \ + return sprintf (buf, format_string, mdev->ofdev.dev.of_node->field); \ +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct soundbus_dev *sdev = to_soundbus_device(dev); + struct platform_device *of = &sdev->ofdev; + int length; + + if (*sdev->modalias) { + strlcpy(buf, sdev->modalias, sizeof(sdev->modalias) + 1); + strcat(buf, "\n"); + length = strlen(buf); + } else { + length = sprintf(buf, "of:N%sT%s\n", + of->dev.of_node->name, of->dev.of_node->type); + } + + return length; +} +static DEVICE_ATTR_RO(modalias); + +soundbus_config_of_attr (name, "%s\n"); +static DEVICE_ATTR_RO(name); +soundbus_config_of_attr (type, "%s\n"); +static DEVICE_ATTR_RO(type); + +struct attribute *soundbus_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_type.attr, + &dev_attr_modalias.attr, + NULL, +}; diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig new file mode 100644 index 000000000..5fbd47a91 --- /dev/null +++ b/sound/arm/Kconfig @@ -0,0 +1,38 @@ +# ALSA ARM drivers + +menuconfig SND_ARM + bool "ARM sound devices" + depends on ARM + default y + help + Support for sound devices specific to ARM architectures. + Drivers that are implemented on ASoC can be found in + "ALSA for SoC audio support" section. + +if SND_ARM + +config SND_ARMAACI + tristate "ARM PrimeCell PL041 AC Link support" + depends on ARM_AMBA + select SND_PCM + select SND_AC97_CODEC + +config SND_PXA2XX_AC97 + tristate "AC97 driver for the Intel PXA2xx chip" + depends on ARCH_PXA + select SND_AC97_CODEC + select SND_PXA2XX_LIB + select SND_PXA2XX_LIB_AC97 + help + Say Y or M if you want to support any AC97 codec attached to + the PXA2xx AC97 interface. + +endif # SND_ARM + +config SND_PXA2XX_LIB + tristate + select SND_AC97_CODEC if SND_PXA2XX_LIB_AC97 + select SND_DMAENGINE_PCM + +config SND_PXA2XX_LIB_AC97 + bool diff --git a/sound/arm/Makefile b/sound/arm/Makefile new file mode 100644 index 000000000..34c769489 --- /dev/null +++ b/sound/arm/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# + +obj-$(CONFIG_SND_ARMAACI) += snd-aaci.o +snd-aaci-objs := aaci.o + +obj-$(CONFIG_SND_PXA2XX_LIB) += snd-pxa2xx-lib.o +snd-pxa2xx-lib-y := pxa2xx-pcm-lib.o +snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o + +obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o +snd-pxa2xx-ac97-objs := pxa2xx-ac97.o diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c new file mode 100644 index 000000000..0114ffed5 --- /dev/null +++ b/sound/arm/aaci.c @@ -0,0 +1,1111 @@ +/* + * linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver + * + * Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Documentation: ARM DDI 0173B + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "aaci.h" + +#define DRIVER_NAME "aaci-pl041" + +#define FRAME_PERIOD_US 21 + +/* + * PM support is not complete. Turn it off. + */ +#undef CONFIG_PM + +static void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97) +{ + u32 v, maincr = aaci->maincr | MAINCR_SCRA(ac97->num); + + /* + * Ensure that the slot 1/2 RX registers are empty. + */ + v = readl(aaci->base + AACI_SLFR); + if (v & SLFR_2RXV) + readl(aaci->base + AACI_SL2RX); + if (v & SLFR_1RXV) + readl(aaci->base + AACI_SL1RX); + + if (maincr != readl(aaci->base + AACI_MAINCR)) { + writel(maincr, aaci->base + AACI_MAINCR); + readl(aaci->base + AACI_MAINCR); + udelay(1); + } +} + +/* + * P29: + * The recommended use of programming the external codec through slot 1 + * and slot 2 data is to use the channels during setup routines and the + * slot register at any other time. The data written into slot 1, slot 2 + * and slot 12 registers is transmitted only when their corresponding + * SI1TxEn, SI2TxEn and SI12TxEn bits are set in the AACI_MAINCR + * register. + */ +static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct aaci *aaci = ac97->private_data; + int timeout; + u32 v; + + if (ac97->num >= 4) + return; + + mutex_lock(&aaci->ac97_sem); + + aaci_ac97_select_codec(aaci, ac97); + + /* + * P54: You must ensure that AACI_SL2TX is always written + * to, if required, before data is written to AACI_SL1TX. + */ + writel(val << 4, aaci->base + AACI_SL2TX); + writel(reg << 12, aaci->base + AACI_SL1TX); + + /* Initially, wait one frame period */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for it to be sent */ + timeout = FRAME_PERIOD_US * 8; + do { + udelay(1); + v = readl(aaci->base + AACI_SLFR); + } while ((v & (SLFR_1TXB|SLFR_2TXB)) && --timeout); + + if (v & (SLFR_1TXB|SLFR_2TXB)) + dev_err(&aaci->dev->dev, + "timeout waiting for write to complete\n"); + + mutex_unlock(&aaci->ac97_sem); +} + +/* + * Read an AC'97 register. + */ +static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct aaci *aaci = ac97->private_data; + int timeout, retries = 10; + u32 v; + + if (ac97->num >= 4) + return ~0; + + mutex_lock(&aaci->ac97_sem); + + aaci_ac97_select_codec(aaci, ac97); + + /* + * Write the register address to slot 1. + */ + writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX); + + /* Initially, wait one frame period */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for it to be sent */ + timeout = FRAME_PERIOD_US * 8; + do { + udelay(1); + v = readl(aaci->base + AACI_SLFR); + } while ((v & SLFR_1TXB) && --timeout); + + if (v & SLFR_1TXB) { + dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n"); + v = ~0; + goto out; + } + + /* Now wait for the response frame */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for data */ + timeout = FRAME_PERIOD_US * 8; + do { + udelay(1); + cond_resched(); + v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV); + } while ((v != (SLFR_1RXV|SLFR_2RXV)) && --timeout); + + if (v != (SLFR_1RXV|SLFR_2RXV)) { + dev_err(&aaci->dev->dev, "timeout on RX valid\n"); + v = ~0; + goto out; + } + + do { + v = readl(aaci->base + AACI_SL1RX) >> 12; + if (v == reg) { + v = readl(aaci->base + AACI_SL2RX) >> 4; + break; + } else if (--retries) { + dev_warn(&aaci->dev->dev, + "ac97 read back fail. retry\n"); + continue; + } else { + dev_warn(&aaci->dev->dev, + "wrong ac97 register read back (%x != %x)\n", + v, reg); + v = ~0; + } + } while (retries); + out: + mutex_unlock(&aaci->ac97_sem); + return v; +} + +static inline void +aaci_chan_wait_ready(struct aaci_runtime *aacirun, unsigned long mask) +{ + u32 val; + int timeout = 5000; + + do { + udelay(1); + val = readl(aacirun->base + AACI_SR); + } while (val & mask && timeout--); +} + + + +/* + * Interrupt support. + */ +static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) +{ + if (mask & ISR_ORINTR) { + dev_warn(&aaci->dev->dev, "RX overrun on chan %d\n", channel); + writel(ICLR_RXOEC1 << channel, aaci->base + AACI_INTCLR); + } + + if (mask & ISR_RXTOINTR) { + dev_warn(&aaci->dev->dev, "RX timeout on chan %d\n", channel); + writel(ICLR_RXTOFEC1 << channel, aaci->base + AACI_INTCLR); + } + + if (mask & ISR_RXINTR) { + struct aaci_runtime *aacirun = &aaci->capture; + bool period_elapsed = false; + void *ptr; + + if (!aacirun->substream || !aacirun->start) { + dev_warn(&aaci->dev->dev, "RX interrupt???\n"); + writel(0, aacirun->base + AACI_IE); + return; + } + + spin_lock(&aacirun->lock); + + ptr = aacirun->ptr; + do { + unsigned int len = aacirun->fifo_bytes; + u32 val; + + if (aacirun->bytes <= 0) { + aacirun->bytes += aacirun->period; + period_elapsed = true; + } + if (!(aacirun->cr & CR_EN)) + break; + + val = readl(aacirun->base + AACI_SR); + if (!(val & SR_RXHF)) + break; + if (!(val & SR_RXFF)) + len >>= 1; + + aacirun->bytes -= len; + + /* reading 16 bytes at a time */ + for( ; len > 0; len -= 16) { + asm( + "ldmia %1, {r0, r1, r2, r3}\n\t" + "stmia %0!, {r0, r1, r2, r3}" + : "+r" (ptr) + : "r" (aacirun->fifo) + : "r0", "r1", "r2", "r3", "cc"); + + if (ptr >= aacirun->end) + ptr = aacirun->start; + } + } while(1); + + aacirun->ptr = ptr; + + spin_unlock(&aacirun->lock); + + if (period_elapsed) + snd_pcm_period_elapsed(aacirun->substream); + } + + if (mask & ISR_URINTR) { + dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel); + writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR); + } + + if (mask & ISR_TXINTR) { + struct aaci_runtime *aacirun = &aaci->playback; + bool period_elapsed = false; + void *ptr; + + if (!aacirun->substream || !aacirun->start) { + dev_warn(&aaci->dev->dev, "TX interrupt???\n"); + writel(0, aacirun->base + AACI_IE); + return; + } + + spin_lock(&aacirun->lock); + + ptr = aacirun->ptr; + do { + unsigned int len = aacirun->fifo_bytes; + u32 val; + + if (aacirun->bytes <= 0) { + aacirun->bytes += aacirun->period; + period_elapsed = true; + } + if (!(aacirun->cr & CR_EN)) + break; + + val = readl(aacirun->base + AACI_SR); + if (!(val & SR_TXHE)) + break; + if (!(val & SR_TXFE)) + len >>= 1; + + aacirun->bytes -= len; + + /* writing 16 bytes at a time */ + for ( ; len > 0; len -= 16) { + asm( + "ldmia %0!, {r0, r1, r2, r3}\n\t" + "stmia %1, {r0, r1, r2, r3}" + : "+r" (ptr) + : "r" (aacirun->fifo) + : "r0", "r1", "r2", "r3", "cc"); + + if (ptr >= aacirun->end) + ptr = aacirun->start; + } + } while (1); + + aacirun->ptr = ptr; + + spin_unlock(&aacirun->lock); + + if (period_elapsed) + snd_pcm_period_elapsed(aacirun->substream); + } +} + +static irqreturn_t aaci_irq(int irq, void *devid) +{ + struct aaci *aaci = devid; + u32 mask; + int i; + + mask = readl(aaci->base + AACI_ALLINTS); + if (mask) { + u32 m = mask; + for (i = 0; i < 4; i++, m >>= 7) { + if (m & 0x7f) { + aaci_fifo_irq(aaci, i, m); + } + } + } + + return mask ? IRQ_HANDLED : IRQ_NONE; +} + + + +/* + * ALSA support. + */ +static const struct snd_pcm_hardware aaci_hw_info = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME, + + /* + * ALSA doesn't support 18-bit or 20-bit packed into 32-bit + * words. It also doesn't support 12-bit at all. + */ + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + /* rates are setup from the AC'97 codec */ + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 256, + .period_bytes_max = PAGE_SIZE, + .periods_min = 4, + .periods_max = PAGE_SIZE / 16, +}; + +/* + * We can support two and four channel audio. Unfortunately + * six channel audio requires a non-standard channel ordering: + * 2 -> FL(3), FR(4) + * 4 -> FL(3), FR(4), SL(7), SR(8) + * 6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required) + * FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual) + * This requires an ALSA configuration file to correct. + */ +static int aaci_rule_channels(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_rule *rule) +{ + static unsigned int channel_list[] = { 2, 4, 6 }; + struct aaci *aaci = rule->private; + unsigned int mask = 1 << 0, slots; + + /* pcms[0] is the our 5.1 PCM instance. */ + slots = aaci->ac97_bus->pcms[0].r[0].slots; + if (slots & (1 << AC97_SLOT_PCM_SLEFT)) { + mask |= 1 << 1; + if (slots & (1 << AC97_SLOT_LFE)) + mask |= 1 << 2; + } + + return snd_interval_list(hw_param_interval(p, rule->var), + ARRAY_SIZE(channel_list), channel_list, mask); +} + +static int aaci_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aaci *aaci = substream->private_data; + struct aaci_runtime *aacirun; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aacirun = &aaci->playback; + } else { + aacirun = &aaci->capture; + } + + aacirun->substream = substream; + runtime->private_data = aacirun; + runtime->hw = aaci_hw_info; + runtime->hw.rates = aacirun->pcm->rates; + snd_pcm_limit_hw_rates(runtime); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.channels_max = 6; + + /* Add rule describing channel dependency. */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + aaci_rule_channels, aaci, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) + return ret; + + if (aacirun->pcm->r[1].slots) + snd_ac97_pcm_double_rate_rules(runtime); + } + + /* + * ALSA wants the byte-size of the FIFOs. As we only support + * 16-bit samples, this is twice the FIFO depth irrespective + * of whether it's in compact mode or not. + */ + runtime->hw.fifo_size = aaci->fifo_depth * 2; + + mutex_lock(&aaci->irq_lock); + if (!aaci->users++) { + ret = request_irq(aaci->dev->irq[0], aaci_irq, + IRQF_SHARED, DRIVER_NAME, aaci); + if (ret != 0) + aaci->users--; + } + mutex_unlock(&aaci->irq_lock); + + return ret; +} + + +/* + * Common ALSA stuff + */ +static int aaci_pcm_close(struct snd_pcm_substream *substream) +{ + struct aaci *aaci = substream->private_data; + struct aaci_runtime *aacirun = substream->runtime->private_data; + + WARN_ON(aacirun->cr & CR_EN); + + aacirun->substream = NULL; + + mutex_lock(&aaci->irq_lock); + if (!--aaci->users) + free_irq(aaci->dev->irq[0], aaci); + mutex_unlock(&aaci->irq_lock); + + return 0; +} + +static int aaci_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct aaci_runtime *aacirun = substream->runtime->private_data; + + /* + * This must not be called with the device enabled. + */ + WARN_ON(aacirun->cr & CR_EN); + + if (aacirun->pcm_open) + snd_ac97_pcm_close(aacirun->pcm); + aacirun->pcm_open = 0; + + /* + * Clear out the DMA and any allocated buffers. + */ + snd_pcm_lib_free_pages(substream); + + return 0; +} + +/* Channel to slot mask */ +static const u32 channels_to_slotmask[] = { + [2] = CR_SL3 | CR_SL4, + [4] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8, + [6] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8 | CR_SL6 | CR_SL9, +}; + +static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct aaci_runtime *aacirun = substream->runtime->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + int dbl = rate > 48000; + int err; + + aaci_pcm_hw_free(substream); + if (aacirun->pcm_open) { + snd_ac97_pcm_close(aacirun->pcm); + aacirun->pcm_open = 0; + } + + /* channels is already limited to 2, 4, or 6 by aaci_rule_channels */ + if (dbl && channels != 2) + return -EINVAL; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(params)); + if (err >= 0) { + struct aaci *aaci = substream->private_data; + + err = snd_ac97_pcm_open(aacirun->pcm, rate, channels, + aacirun->pcm->r[dbl].slots); + + aacirun->pcm_open = err == 0; + aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16; + aacirun->cr |= channels_to_slotmask[channels + dbl * 2]; + + /* + * fifo_bytes is the number of bytes we transfer to/from + * the FIFO, including padding. So that's x4. As we're + * in compact mode, the FIFO is half the size. + */ + aacirun->fifo_bytes = aaci->fifo_depth * 4 / 2; + } + + return err; +} + +static int aaci_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aaci_runtime *aacirun = runtime->private_data; + + aacirun->period = snd_pcm_lib_period_bytes(substream); + aacirun->start = runtime->dma_area; + aacirun->end = aacirun->start + snd_pcm_lib_buffer_bytes(substream); + aacirun->ptr = aacirun->start; + aacirun->bytes = aacirun->period; + + return 0; +} + +static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aaci_runtime *aacirun = runtime->private_data; + ssize_t bytes = aacirun->ptr - aacirun->start; + + return bytes_to_frames(runtime, bytes); +} + + +/* + * Playback specific ALSA stuff + */ +static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun) +{ + u32 ie; + + ie = readl(aacirun->base + AACI_IE); + ie &= ~(IE_URIE|IE_TXIE); + writel(ie, aacirun->base + AACI_IE); + aacirun->cr &= ~CR_EN; + aaci_chan_wait_ready(aacirun, SR_TXB); + writel(aacirun->cr, aacirun->base + AACI_TXCR); +} + +static void aaci_pcm_playback_start(struct aaci_runtime *aacirun) +{ + u32 ie; + + aaci_chan_wait_ready(aacirun, SR_TXB); + aacirun->cr |= CR_EN; + + ie = readl(aacirun->base + AACI_IE); + ie |= IE_URIE | IE_TXIE; + writel(ie, aacirun->base + AACI_IE); + writel(aacirun->cr, aacirun->base + AACI_TXCR); +} + +static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct aaci_runtime *aacirun = substream->runtime->private_data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&aacirun->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aaci_pcm_playback_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + aaci_pcm_playback_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_STOP: + aaci_pcm_playback_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + aaci_pcm_playback_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + default: + ret = -EINVAL; + } + + spin_unlock_irqrestore(&aacirun->lock, flags); + + return ret; +} + +static const struct snd_pcm_ops aaci_playback_ops = { + .open = aaci_pcm_open, + .close = aaci_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = aaci_pcm_hw_params, + .hw_free = aaci_pcm_hw_free, + .prepare = aaci_pcm_prepare, + .trigger = aaci_pcm_playback_trigger, + .pointer = aaci_pcm_pointer, +}; + +static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun) +{ + u32 ie; + + aaci_chan_wait_ready(aacirun, SR_RXB); + + ie = readl(aacirun->base + AACI_IE); + ie &= ~(IE_ORIE | IE_RXIE); + writel(ie, aacirun->base+AACI_IE); + + aacirun->cr &= ~CR_EN; + + writel(aacirun->cr, aacirun->base + AACI_RXCR); +} + +static void aaci_pcm_capture_start(struct aaci_runtime *aacirun) +{ + u32 ie; + + aaci_chan_wait_ready(aacirun, SR_RXB); + +#ifdef DEBUG + /* RX Timeout value: bits 28:17 in RXCR */ + aacirun->cr |= 0xf << 17; +#endif + + aacirun->cr |= CR_EN; + writel(aacirun->cr, aacirun->base + AACI_RXCR); + + ie = readl(aacirun->base + AACI_IE); + ie |= IE_ORIE |IE_RXIE; // overrun and rx interrupt -- half full + writel(ie, aacirun->base + AACI_IE); +} + +static int aaci_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct aaci_runtime *aacirun = substream->runtime->private_data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&aacirun->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aaci_pcm_capture_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + aaci_pcm_capture_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_STOP: + aaci_pcm_capture_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + aaci_pcm_capture_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + default: + ret = -EINVAL; + } + + spin_unlock_irqrestore(&aacirun->lock, flags); + + return ret; +} + +static int aaci_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aaci *aaci = substream->private_data; + + aaci_pcm_prepare(substream); + + /* allow changing of sample rate */ + aaci_ac97_write(aaci->ac97, AC97_EXTENDED_STATUS, 0x0001); /* VRA */ + aaci_ac97_write(aaci->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate); + aaci_ac97_write(aaci->ac97, AC97_PCM_MIC_ADC_RATE, runtime->rate); + + /* Record select: Mic: 0, Aux: 3, Line: 4 */ + aaci_ac97_write(aaci->ac97, AC97_REC_SEL, 0x0404); + + return 0; +} + +static const struct snd_pcm_ops aaci_capture_ops = { + .open = aaci_pcm_open, + .close = aaci_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = aaci_pcm_hw_params, + .hw_free = aaci_pcm_hw_free, + .prepare = aaci_pcm_capture_prepare, + .trigger = aaci_pcm_capture_trigger, + .pointer = aaci_pcm_pointer, +}; + +/* + * Power Management. + */ +#ifdef CONFIG_PM +static int aaci_do_suspend(struct snd_card *card) +{ + struct aaci *aaci = card->private_data; + snd_power_change_state(card, SNDRV_CTL_POWER_D3cold); + snd_pcm_suspend_all(aaci->pcm); + return 0; +} + +static int aaci_do_resume(struct snd_card *card) +{ + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int aaci_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + return card ? aaci_do_suspend(card) : 0; +} + +static int aaci_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + return card ? aaci_do_resume(card) : 0; +} + +static SIMPLE_DEV_PM_OPS(aaci_dev_pm_ops, aaci_suspend, aaci_resume); +#define AACI_DEV_PM_OPS (&aaci_dev_pm_ops) +#else +#define AACI_DEV_PM_OPS NULL +#endif + + +static const struct ac97_pcm ac97_defs[] = { + [0] = { /* Front PCM */ + .exclusive = 1, + .r = { + [0] = { + .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), + }, + [1] = { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) | + (1 << AC97_SLOT_PCM_LEFT_0) | + (1 << AC97_SLOT_PCM_RIGHT_0), + }, + }, + }, + [1] = { /* PCM in */ + .stream = 1, + .exclusive = 1, + .r = { + [0] = { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT), + }, + }, + }, + [2] = { /* Mic in */ + .stream = 1, + .exclusive = 1, + .r = { + [0] = { + .slots = (1 << AC97_SLOT_MIC), + }, + }, + } +}; + +static struct snd_ac97_bus_ops aaci_bus_ops = { + .write = aaci_ac97_write, + .read = aaci_ac97_read, +}; + +static int aaci_probe_ac97(struct aaci *aaci) +{ + struct snd_ac97_template ac97_template; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + int ret; + + /* + * Assert AACIRESET for 2us + */ + writel(0, aaci->base + AACI_RESET); + udelay(2); + writel(RESET_NRST, aaci->base + AACI_RESET); + + /* + * Give the AC'97 codec more than enough time + * to wake up. (42us = ~2 frames at 48kHz.) + */ + udelay(FRAME_PERIOD_US * 2); + + ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus); + if (ret) + goto out; + + ac97_bus->clock = 48000; + aaci->ac97_bus = ac97_bus; + + memset(&ac97_template, 0, sizeof(struct snd_ac97_template)); + ac97_template.private_data = aaci; + ac97_template.num = 0; + ac97_template.scaps = AC97_SCAP_SKIP_MODEM; + + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97); + if (ret) + goto out; + aaci->ac97 = ac97; + + /* + * Disable AC97 PC Beep input on audio codecs. + */ + if (ac97_is_audio(ac97)) + snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x801e); + + ret = snd_ac97_pcm_assign(ac97_bus, ARRAY_SIZE(ac97_defs), ac97_defs); + if (ret) + goto out; + + aaci->playback.pcm = &ac97_bus->pcms[0]; + aaci->capture.pcm = &ac97_bus->pcms[1]; + + out: + return ret; +} + +static void aaci_free_card(struct snd_card *card) +{ + struct aaci *aaci = card->private_data; + + iounmap(aaci->base); +} + +static struct aaci *aaci_init_card(struct amba_device *dev) +{ + struct aaci *aaci; + struct snd_card *card; + int err; + + err = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(struct aaci), &card); + if (err < 0) + return NULL; + + card->private_free = aaci_free_card; + + strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strlcpy(card->shortname, "ARM AC'97 Interface", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s PL%03x rev%u at 0x%08llx, irq %d", + card->shortname, amba_part(dev), amba_rev(dev), + (unsigned long long)dev->res.start, dev->irq[0]); + + aaci = card->private_data; + mutex_init(&aaci->ac97_sem); + mutex_init(&aaci->irq_lock); + aaci->card = card; + aaci->dev = dev; + + /* Set MAINCR to allow slot 1 and 2 data IO */ + aaci->maincr = MAINCR_IE | MAINCR_SL1RXEN | MAINCR_SL1TXEN | + MAINCR_SL2RXEN | MAINCR_SL2TXEN; + + return aaci; +} + +static int aaci_init_pcm(struct aaci *aaci) +{ + struct snd_pcm *pcm; + int ret; + + ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 1, &pcm); + if (ret == 0) { + aaci->pcm = pcm; + pcm->private_data = aaci; + pcm->info_flags = 0; + + strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name)); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aaci_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aaci_capture_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + NULL, 0, 64 * 1024); + } + + return ret; +} + +static unsigned int aaci_size_fifo(struct aaci *aaci) +{ + struct aaci_runtime *aacirun = &aaci->playback; + int i; + + /* + * Enable the channel, but don't assign it to any slots, so + * it won't empty onto the AC'97 link. + */ + writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR); + + for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++) + writel(0, aacirun->fifo); + + writel(0, aacirun->base + AACI_TXCR); + + /* + * Re-initialise the AACI after the FIFO depth test, to + * ensure that the FIFOs are empty. Unfortunately, merely + * disabling the channel doesn't clear the FIFO. + */ + writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR); + readl(aaci->base + AACI_MAINCR); + udelay(1); + writel(aaci->maincr, aaci->base + AACI_MAINCR); + + /* + * If we hit 4096 entries, we failed. Go back to the specified + * fifo depth. + */ + if (i == 4096) + i = 8; + + return i; +} + +static int aaci_probe(struct amba_device *dev, + const struct amba_id *id) +{ + struct aaci *aaci; + int ret, i; + + ret = amba_request_regions(dev, NULL); + if (ret) + return ret; + + aaci = aaci_init_card(dev); + if (!aaci) { + ret = -ENOMEM; + goto out; + } + + aaci->base = ioremap(dev->res.start, resource_size(&dev->res)); + if (!aaci->base) { + ret = -ENOMEM; + goto out; + } + + /* + * Playback uses AACI channel 0 + */ + spin_lock_init(&aaci->playback.lock); + aaci->playback.base = aaci->base + AACI_CSCH1; + aaci->playback.fifo = aaci->base + AACI_DR1; + + /* + * Capture uses AACI channel 0 + */ + spin_lock_init(&aaci->capture.lock); + aaci->capture.base = aaci->base + AACI_CSCH1; + aaci->capture.fifo = aaci->base + AACI_DR1; + + for (i = 0; i < 4; i++) { + void __iomem *base = aaci->base + i * 0x14; + + writel(0, base + AACI_IE); + writel(0, base + AACI_TXCR); + writel(0, base + AACI_RXCR); + } + + writel(0x1fff, aaci->base + AACI_INTCLR); + writel(aaci->maincr, aaci->base + AACI_MAINCR); + /* + * Fix: ac97 read back fail errors by reading + * from any arbitrary aaci register. + */ + readl(aaci->base + AACI_CSCH1); + ret = aaci_probe_ac97(aaci); + if (ret) + goto out; + + /* + * Size the FIFOs (must be multiple of 16). + * This is the number of entries in the FIFO. + */ + aaci->fifo_depth = aaci_size_fifo(aaci); + if (aaci->fifo_depth & 15) { + printk(KERN_WARNING "AACI: FIFO depth %d not supported\n", + aaci->fifo_depth); + ret = -ENODEV; + goto out; + } + + ret = aaci_init_pcm(aaci); + if (ret) + goto out; + + ret = snd_card_register(aaci->card); + if (ret == 0) { + dev_info(&dev->dev, "%s\n", aaci->card->longname); + dev_info(&dev->dev, "FIFO %u entries\n", aaci->fifo_depth); + amba_set_drvdata(dev, aaci->card); + return ret; + } + + out: + if (aaci) + snd_card_free(aaci->card); + amba_release_regions(dev); + return ret; +} + +static int aaci_remove(struct amba_device *dev) +{ + struct snd_card *card = amba_get_drvdata(dev); + + if (card) { + struct aaci *aaci = card->private_data; + writel(0, aaci->base + AACI_MAINCR); + + snd_card_free(card); + amba_release_regions(dev); + } + + return 0; +} + +static struct amba_id aaci_ids[] = { + { + .id = 0x00041041, + .mask = 0x000fffff, + }, + { 0, 0 }, +}; + +MODULE_DEVICE_TABLE(amba, aaci_ids); + +static struct amba_driver aaci_driver = { + .drv = { + .name = DRIVER_NAME, + .pm = AACI_DEV_PM_OPS, + }, + .probe = aaci_probe, + .remove = aaci_remove, + .id_table = aaci_ids, +}; + +module_amba_driver(aaci_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ARM PrimeCell PL041 Advanced Audio CODEC Interface driver"); diff --git a/sound/arm/aaci.h b/sound/arm/aaci.h new file mode 100644 index 000000000..5791bd5bd --- /dev/null +++ b/sound/arm/aaci.h @@ -0,0 +1,250 @@ +/* + * linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver + * + * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef AACI_H +#define AACI_H + +/* + * Control and status register offsets + * P39. + */ +#define AACI_CSCH1 0x000 +#define AACI_CSCH2 0x014 +#define AACI_CSCH3 0x028 +#define AACI_CSCH4 0x03c + +#define AACI_RXCR 0x000 /* 29 bits Control Rx FIFO */ +#define AACI_TXCR 0x004 /* 17 bits Control Tx FIFO */ +#define AACI_SR 0x008 /* 12 bits Status */ +#define AACI_ISR 0x00c /* 7 bits Int Status */ +#define AACI_IE 0x010 /* 7 bits Int Enable */ + +/* + * Other registers + */ +#define AACI_SL1RX 0x050 +#define AACI_SL1TX 0x054 +#define AACI_SL2RX 0x058 +#define AACI_SL2TX 0x05c +#define AACI_SL12RX 0x060 +#define AACI_SL12TX 0x064 +#define AACI_SLFR 0x068 /* slot flags */ +#define AACI_SLISTAT 0x06c /* slot interrupt status */ +#define AACI_SLIEN 0x070 /* slot interrupt enable */ +#define AACI_INTCLR 0x074 /* interrupt clear */ +#define AACI_MAINCR 0x078 /* main control */ +#define AACI_RESET 0x07c /* reset control */ +#define AACI_SYNC 0x080 /* sync control */ +#define AACI_ALLINTS 0x084 /* all fifo interrupt status */ +#define AACI_MAINFR 0x088 /* main flag register */ +#define AACI_DR1 0x090 /* data read/written fifo 1 */ +#define AACI_DR2 0x0b0 /* data read/written fifo 2 */ +#define AACI_DR3 0x0d0 /* data read/written fifo 3 */ +#define AACI_DR4 0x0f0 /* data read/written fifo 4 */ + +/* + * TX/RX fifo control register (CR). P48 + */ +#define CR_FEN (1 << 16) /* fifo enable */ +#define CR_COMPACT (1 << 15) /* compact mode */ +#define CR_SZ16 (0 << 13) /* 16 bits */ +#define CR_SZ18 (1 << 13) /* 18 bits */ +#define CR_SZ20 (2 << 13) /* 20 bits */ +#define CR_SZ12 (3 << 13) /* 12 bits */ +#define CR_SL12 (1 << 12) +#define CR_SL11 (1 << 11) +#define CR_SL10 (1 << 10) +#define CR_SL9 (1 << 9) +#define CR_SL8 (1 << 8) +#define CR_SL7 (1 << 7) +#define CR_SL6 (1 << 6) +#define CR_SL5 (1 << 5) +#define CR_SL4 (1 << 4) +#define CR_SL3 (1 << 3) +#define CR_SL2 (1 << 2) +#define CR_SL1 (1 << 1) +#define CR_EN (1 << 0) /* transmit enable */ + +/* + * status register bits. P49 + */ +#define SR_RXTOFE (1 << 11) /* rx timeout fifo empty */ +#define SR_TXTO (1 << 10) /* rx timeout fifo nonempty */ +#define SR_TXU (1 << 9) /* tx underrun */ +#define SR_RXO (1 << 8) /* rx overrun */ +#define SR_TXB (1 << 7) /* tx busy */ +#define SR_RXB (1 << 6) /* rx busy */ +#define SR_TXFF (1 << 5) /* tx fifo full */ +#define SR_RXFF (1 << 4) /* rx fifo full */ +#define SR_TXHE (1 << 3) /* tx fifo half empty */ +#define SR_RXHF (1 << 2) /* rx fifo half full */ +#define SR_TXFE (1 << 1) /* tx fifo empty */ +#define SR_RXFE (1 << 0) /* rx fifo empty */ + +/* + * interrupt status register bits. + */ +#define ISR_RXTOFEINTR (1 << 6) /* rx fifo empty */ +#define ISR_URINTR (1 << 5) /* tx underflow */ +#define ISR_ORINTR (1 << 4) /* rx overflow */ +#define ISR_RXINTR (1 << 3) /* rx fifo */ +#define ISR_TXINTR (1 << 2) /* tx fifo intr */ +#define ISR_RXTOINTR (1 << 1) /* tx timeout */ +#define ISR_TXCINTR (1 << 0) /* tx complete */ + +/* + * interrupt enable register bits. + */ +#define IE_RXTOIE (1 << 6) +#define IE_URIE (1 << 5) +#define IE_ORIE (1 << 4) +#define IE_RXIE (1 << 3) +#define IE_TXIE (1 << 2) +#define IE_RXTIE (1 << 1) +#define IE_TXCIE (1 << 0) + +/* + * interrupt status. P51 + */ +#define ISR_RXTOFE (1 << 6) /* rx timeout fifo empty */ +#define ISR_UR (1 << 5) /* tx fifo underrun */ +#define ISR_OR (1 << 4) /* rx fifo overrun */ +#define ISR_RX (1 << 3) /* rx interrupt status */ +#define ISR_TX (1 << 2) /* tx interrupt status */ +#define ISR_RXTO (1 << 1) /* rx timeout */ +#define ISR_TXC (1 << 0) /* tx complete */ + +/* + * interrupt enable. P52 + */ +#define IE_RXTOFE (1 << 6) /* rx timeout fifo empty */ +#define IE_UR (1 << 5) /* tx fifo underrun */ +#define IE_OR (1 << 4) /* rx fifo overrun */ +#define IE_RX (1 << 3) /* rx interrupt status */ +#define IE_TX (1 << 2) /* tx interrupt status */ +#define IE_RXTO (1 << 1) /* rx timeout */ +#define IE_TXC (1 << 0) /* tx complete */ + +/* + * slot flag register bits. P56 + */ +#define SLFR_RWIS (1 << 13) /* raw wake-up interrupt status */ +#define SLFR_RGPIOINTR (1 << 12) /* raw gpio interrupt */ +#define SLFR_12TXE (1 << 11) /* slot 12 tx empty */ +#define SLFR_12RXV (1 << 10) /* slot 12 rx valid */ +#define SLFR_2TXE (1 << 9) /* slot 2 tx empty */ +#define SLFR_2RXV (1 << 8) /* slot 2 rx valid */ +#define SLFR_1TXE (1 << 7) /* slot 1 tx empty */ +#define SLFR_1RXV (1 << 6) /* slot 1 rx valid */ +#define SLFR_12TXB (1 << 5) /* slot 12 tx busy */ +#define SLFR_12RXB (1 << 4) /* slot 12 rx busy */ +#define SLFR_2TXB (1 << 3) /* slot 2 tx busy */ +#define SLFR_2RXB (1 << 2) /* slot 2 rx busy */ +#define SLFR_1TXB (1 << 1) /* slot 1 tx busy */ +#define SLFR_1RXB (1 << 0) /* slot 1 rx busy */ + +/* + * Interrupt clear register. + */ +#define ICLR_RXTOFEC4 (1 << 12) +#define ICLR_RXTOFEC3 (1 << 11) +#define ICLR_RXTOFEC2 (1 << 10) +#define ICLR_RXTOFEC1 (1 << 9) +#define ICLR_TXUEC4 (1 << 8) +#define ICLR_TXUEC3 (1 << 7) +#define ICLR_TXUEC2 (1 << 6) +#define ICLR_TXUEC1 (1 << 5) +#define ICLR_RXOEC4 (1 << 4) +#define ICLR_RXOEC3 (1 << 3) +#define ICLR_RXOEC2 (1 << 2) +#define ICLR_RXOEC1 (1 << 1) +#define ICLR_WISC (1 << 0) + +/* + * Main control register bits. P62 + */ +#define MAINCR_SCRA(x) ((x) << 10) /* secondary codec reg access */ +#define MAINCR_DMAEN (1 << 9) /* dma enable */ +#define MAINCR_SL12TXEN (1 << 8) /* slot 12 transmit enable */ +#define MAINCR_SL12RXEN (1 << 7) /* slot 12 receive enable */ +#define MAINCR_SL2TXEN (1 << 6) /* slot 2 transmit enable */ +#define MAINCR_SL2RXEN (1 << 5) /* slot 2 receive enable */ +#define MAINCR_SL1TXEN (1 << 4) /* slot 1 transmit enable */ +#define MAINCR_SL1RXEN (1 << 3) /* slot 1 receive enable */ +#define MAINCR_LPM (1 << 2) /* low power mode */ +#define MAINCR_LOOPBK (1 << 1) /* loopback */ +#define MAINCR_IE (1 << 0) /* aaci interface enable */ + +/* + * Reset register bits. P65 + */ +#define RESET_NRST (1 << 0) + +/* + * Sync register bits. P65 + */ +#define SYNC_FORCE (1 << 0) + +/* + * Main flag register bits. P66 + */ +#define MAINFR_TXB (1 << 1) /* transmit busy */ +#define MAINFR_RXB (1 << 0) /* receive busy */ + + + +struct aaci_runtime { + void __iomem *base; + void __iomem *fifo; + spinlock_t lock; + + struct ac97_pcm *pcm; + int pcm_open; + + u32 cr; + struct snd_pcm_substream *substream; + + unsigned int period; /* byte size of a "period" */ + + /* + * PIO support + */ + void *start; + void *end; + void *ptr; + int bytes; + unsigned int fifo_bytes; +}; + +struct aaci { + struct amba_device *dev; + struct snd_card *card; + void __iomem *base; + unsigned int fifo_depth; + unsigned int users; + struct mutex irq_lock; + + /* AC'97 */ + struct mutex ac97_sem; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + + u32 maincr; + + struct aaci_runtime playback; + struct aaci_runtime capture; + + struct snd_pcm *pcm; +}; + +#define ACSTREAM_FRONT 0 +#define ACSTREAM_SURROUND 1 +#define ACSTREAM_LFE 2 + +#endif diff --git a/sound/arm/pxa2xx-ac97-lib.c b/sound/arm/pxa2xx-ac97-lib.c new file mode 100644 index 000000000..8eafd3d3d --- /dev/null +++ b/sound/arm/pxa2xx-ac97-lib.c @@ -0,0 +1,432 @@ +/* + * Based on sound/arm/pxa2xx-ac97.c and sound/soc/pxa/pxa2xx-ac97.c + * which contain: + * + * Author: Nicolas Pitre + * Created: Dec 02, 2004 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static DEFINE_MUTEX(car_mutex); +static DECLARE_WAIT_QUEUE_HEAD(gsr_wq); +static volatile long gsr_bits; +static struct clk *ac97_clk; +static struct clk *ac97conf_clk; +static int reset_gpio; + +extern void pxa27x_configure_ac97reset(int reset_gpio, bool to_gpio); + +/* + * Beware PXA27x bugs: + * + * o Slot 12 read from modem space will hang controller. + * o CDONE, SDONE interrupt fails after any slot 12 IO. + * + * We therefore have an hybrid approach for waiting on SDONE (interrupt or + * 1 jiffy timeout if interrupt never comes). + */ + +int pxa2xx_ac97_read(int slot, unsigned short reg) +{ + int val = -ENODEV; + volatile u32 *reg_addr; + + if (slot > 0) + return -ENODEV; + + mutex_lock(&car_mutex); + + /* set up primary or secondary codec space */ + if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS) + reg_addr = slot ? &SMC_REG_BASE : &PMC_REG_BASE; + else + reg_addr = slot ? &SAC_REG_BASE : &PAC_REG_BASE; + reg_addr += (reg >> 1); + + /* start read access across the ac97 link */ + GSR = GSR_CDONE | GSR_SDONE; + gsr_bits = 0; + val = (*reg_addr & 0xffff); + if (reg == AC97_GPIO_STATUS) + goto out; + if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1) <= 0 && + !((GSR | gsr_bits) & GSR_SDONE)) { + printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)\n", + __func__, reg, GSR | gsr_bits); + val = -ETIMEDOUT; + goto out; + } + + /* valid data now */ + GSR = GSR_CDONE | GSR_SDONE; + gsr_bits = 0; + val = (*reg_addr & 0xffff); + /* but we've just started another cycle... */ + wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1); + +out: mutex_unlock(&car_mutex); + return val; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_read); + +int pxa2xx_ac97_write(int slot, unsigned short reg, unsigned short val) +{ + volatile u32 *reg_addr; + int ret = 0; + + mutex_lock(&car_mutex); + + /* set up primary or secondary codec space */ + if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS) + reg_addr = slot ? &SMC_REG_BASE : &PMC_REG_BASE; + else + reg_addr = slot ? &SAC_REG_BASE : &PAC_REG_BASE; + reg_addr += (reg >> 1); + + GSR = GSR_CDONE | GSR_SDONE; + gsr_bits = 0; + *reg_addr = val; + if (wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_CDONE, 1) <= 0 && + !((GSR | gsr_bits) & GSR_CDONE)) { + printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)\n", + __func__, reg, GSR | gsr_bits); + ret = -EIO; + } + + mutex_unlock(&car_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_write); + +#ifdef CONFIG_PXA25x +static inline void pxa_ac97_warm_pxa25x(void) +{ + gsr_bits = 0; + + GCR |= GCR_WARM_RST; +} + +static inline void pxa_ac97_cold_pxa25x(void) +{ + GCR &= GCR_COLD_RST; /* clear everything but nCRST */ + GCR &= ~GCR_COLD_RST; /* then assert nCRST */ + + gsr_bits = 0; + + GCR = GCR_COLD_RST; +} +#endif + +#ifdef CONFIG_PXA27x +static inline void pxa_ac97_warm_pxa27x(void) +{ + gsr_bits = 0; + + /* warm reset broken on Bulverde, so manually keep AC97 reset high */ + pxa27x_configure_ac97reset(reset_gpio, true); + udelay(10); + GCR |= GCR_WARM_RST; + pxa27x_configure_ac97reset(reset_gpio, false); + udelay(500); +} + +static inline void pxa_ac97_cold_pxa27x(void) +{ + GCR &= GCR_COLD_RST; /* clear everything but nCRST */ + GCR &= ~GCR_COLD_RST; /* then assert nCRST */ + + gsr_bits = 0; + + /* PXA27x Developers Manual section 13.5.2.2.1 */ + clk_prepare_enable(ac97conf_clk); + udelay(5); + clk_disable_unprepare(ac97conf_clk); + GCR = GCR_COLD_RST | GCR_WARM_RST; +} +#endif + +#ifdef CONFIG_PXA3xx +static inline void pxa_ac97_warm_pxa3xx(void) +{ + gsr_bits = 0; + + /* Can't use interrupts */ + GCR |= GCR_WARM_RST; +} + +static inline void pxa_ac97_cold_pxa3xx(void) +{ + /* Hold CLKBPB for 100us */ + GCR = 0; + GCR = GCR_CLKBPB; + udelay(100); + GCR = 0; + + GCR &= GCR_COLD_RST; /* clear everything but nCRST */ + GCR &= ~GCR_COLD_RST; /* then assert nCRST */ + + gsr_bits = 0; + + /* Can't use interrupts on PXA3xx */ + GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); + + GCR = GCR_WARM_RST | GCR_COLD_RST; +} +#endif + +bool pxa2xx_ac97_try_warm_reset(void) +{ + unsigned long gsr; + unsigned int timeout = 100; + +#ifdef CONFIG_PXA25x + if (cpu_is_pxa25x()) + pxa_ac97_warm_pxa25x(); + else +#endif +#ifdef CONFIG_PXA27x + if (cpu_is_pxa27x()) + pxa_ac97_warm_pxa27x(); + else +#endif +#ifdef CONFIG_PXA3xx + if (cpu_is_pxa3xx()) + pxa_ac97_warm_pxa3xx(); + else +#endif + snd_BUG(); + + while (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--) + mdelay(1); + + gsr = GSR | gsr_bits; + if (!(gsr & (GSR_PCR | GSR_SCR))) { + printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n", + __func__, gsr); + + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_warm_reset); + +bool pxa2xx_ac97_try_cold_reset(void) +{ + unsigned long gsr; + unsigned int timeout = 1000; + +#ifdef CONFIG_PXA25x + if (cpu_is_pxa25x()) + pxa_ac97_cold_pxa25x(); + else +#endif +#ifdef CONFIG_PXA27x + if (cpu_is_pxa27x()) + pxa_ac97_cold_pxa27x(); + else +#endif +#ifdef CONFIG_PXA3xx + if (cpu_is_pxa3xx()) + pxa_ac97_cold_pxa3xx(); + else +#endif + snd_BUG(); + + while (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--) + mdelay(1); + + gsr = GSR | gsr_bits; + if (!(gsr & (GSR_PCR | GSR_SCR))) { + printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n", + __func__, gsr); + + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_cold_reset); + + +void pxa2xx_ac97_finish_reset(void) +{ + GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); + GCR |= GCR_SDONE_IE|GCR_CDONE_IE; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_finish_reset); + +static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id) +{ + long status; + + status = GSR; + if (status) { + GSR = status; + gsr_bits |= status; + wake_up(&gsr_wq); + + /* Although we don't use those we still need to clear them + since they tend to spuriously trigger when MMC is used + (hardware bug? go figure)... */ + if (cpu_is_pxa27x()) { + MISR = MISR_EOC; + PISR = PISR_EOC; + MCSR = MCSR_EOC; + } + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +#ifdef CONFIG_PM +int pxa2xx_ac97_hw_suspend(void) +{ + GCR |= GCR_ACLINK_OFF; + clk_disable_unprepare(ac97_clk); + return 0; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_suspend); + +int pxa2xx_ac97_hw_resume(void) +{ + clk_prepare_enable(ac97_clk); + return 0; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_resume); +#endif + +int pxa2xx_ac97_hw_probe(struct platform_device *dev) +{ + int ret; + pxa2xx_audio_ops_t *pdata = dev->dev.platform_data; + + if (pdata) { + switch (pdata->reset_gpio) { + case 95: + case 113: + reset_gpio = pdata->reset_gpio; + break; + case 0: + reset_gpio = 113; + break; + case -1: + break; + default: + dev_err(&dev->dev, "Invalid reset GPIO %d\n", + pdata->reset_gpio); + } + } else if (!pdata && dev->dev.of_node) { + pdata = devm_kzalloc(&dev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + pdata->reset_gpio = of_get_named_gpio(dev->dev.of_node, + "reset-gpios", 0); + if (pdata->reset_gpio == -ENOENT) + pdata->reset_gpio = -1; + else if (pdata->reset_gpio < 0) + return pdata->reset_gpio; + reset_gpio = pdata->reset_gpio; + } else { + if (cpu_is_pxa27x()) + reset_gpio = 113; + } + + if (cpu_is_pxa27x()) { + /* + * This gpio is needed for a work-around to a bug in the ac97 + * controller during warm reset. The direction and level is set + * here so that it is an output driven high when switching from + * AC97_nRESET alt function to generic gpio. + */ + ret = gpio_request_one(reset_gpio, GPIOF_OUT_INIT_HIGH, + "pxa27x ac97 reset"); + if (ret < 0) { + pr_err("%s: gpio_request_one() failed: %d\n", + __func__, ret); + goto err_conf; + } + pxa27x_configure_ac97reset(reset_gpio, false); + + ac97conf_clk = clk_get(&dev->dev, "AC97CONFCLK"); + if (IS_ERR(ac97conf_clk)) { + ret = PTR_ERR(ac97conf_clk); + ac97conf_clk = NULL; + goto err_conf; + } + } + + ac97_clk = clk_get(&dev->dev, "AC97CLK"); + if (IS_ERR(ac97_clk)) { + ret = PTR_ERR(ac97_clk); + ac97_clk = NULL; + goto err_clk; + } + + ret = clk_prepare_enable(ac97_clk); + if (ret) + goto err_clk2; + + ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, 0, "AC97", NULL); + if (ret < 0) + goto err_irq; + + return 0; + +err_irq: + GCR |= GCR_ACLINK_OFF; +err_clk2: + clk_put(ac97_clk); + ac97_clk = NULL; +err_clk: + if (ac97conf_clk) { + clk_put(ac97conf_clk); + ac97conf_clk = NULL; + } +err_conf: + return ret; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_probe); + +void pxa2xx_ac97_hw_remove(struct platform_device *dev) +{ + if (cpu_is_pxa27x()) + gpio_free(reset_gpio); + GCR |= GCR_ACLINK_OFF; + free_irq(IRQ_AC97, NULL); + if (ac97conf_clk) { + clk_put(ac97conf_clk); + ac97conf_clk = NULL; + } + clk_disable_unprepare(ac97_clk); + clk_put(ac97_clk); + ac97_clk = NULL; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_remove); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("Intel/Marvell PXA sound library"); +MODULE_LICENSE("GPL"); + diff --git a/sound/arm/pxa2xx-ac97.c b/sound/arm/pxa2xx-ac97.c new file mode 100644 index 000000000..1f7267226 --- /dev/null +++ b/sound/arm/pxa2xx-ac97.c @@ -0,0 +1,311 @@ +/* + * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip. + * + * Author: Nicolas Pitre + * Created: Dec 02, 2004 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +static void pxa2xx_ac97_legacy_reset(struct snd_ac97 *ac97) +{ + if (!pxa2xx_ac97_try_cold_reset()) + pxa2xx_ac97_try_warm_reset(); + + pxa2xx_ac97_finish_reset(); +} + +static unsigned short pxa2xx_ac97_legacy_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int ret; + + ret = pxa2xx_ac97_read(ac97->num, reg); + if (ret < 0) + return 0; + else + return (unsigned short)(ret & 0xffff); +} + +static void pxa2xx_ac97_legacy_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int __always_unused ret; + + ret = pxa2xx_ac97_write(ac97->num, reg, val); +} + +static struct snd_ac97_bus_ops pxa2xx_ac97_ops = { + .read = pxa2xx_ac97_legacy_read, + .write = pxa2xx_ac97_legacy_write, + .reset = pxa2xx_ac97_legacy_reset, +}; + +static struct snd_pcm *pxa2xx_ac97_pcm; +static struct snd_ac97 *pxa2xx_ac97_ac97; + +static int pxa2xx_ac97_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + pxa2xx_audio_ops_t *platform_ops; + int ret, i; + + ret = pxa2xx_pcm_open(substream); + if (ret) + return ret; + + runtime->hw.channels_min = 2; + runtime->hw.channels_max = 2; + + i = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_RATES_FRONT_DAC : AC97_RATES_ADC; + runtime->hw.rates = pxa2xx_ac97_ac97->rates[i]; + snd_pcm_limit_hw_rates(runtime); + + platform_ops = substream->pcm->card->dev->platform_data; + if (platform_ops && platform_ops->startup) { + ret = platform_ops->startup(substream, platform_ops->priv); + if (ret < 0) + pxa2xx_pcm_close(substream); + } + + return ret; +} + +static int pxa2xx_ac97_pcm_close(struct snd_pcm_substream *substream) +{ + pxa2xx_audio_ops_t *platform_ops; + + platform_ops = substream->pcm->card->dev->platform_data; + if (platform_ops && platform_ops->shutdown) + platform_ops->shutdown(substream, platform_ops->priv); + + return 0; +} + +static int pxa2xx_ac97_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE; + int ret; + + ret = pxa2xx_pcm_prepare(substream); + if (ret < 0) + return ret; + + return snd_ac97_set_rate(pxa2xx_ac97_ac97, reg, runtime->rate); +} + +#ifdef CONFIG_PM_SLEEP + +static int pxa2xx_ac97_do_suspend(struct snd_card *card) +{ + pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3cold); + snd_pcm_suspend_all(pxa2xx_ac97_pcm); + snd_ac97_suspend(pxa2xx_ac97_ac97); + if (platform_ops && platform_ops->suspend) + platform_ops->suspend(platform_ops->priv); + + return pxa2xx_ac97_hw_suspend(); +} + +static int pxa2xx_ac97_do_resume(struct snd_card *card) +{ + pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data; + int rc; + + rc = pxa2xx_ac97_hw_resume(); + if (rc) + return rc; + + if (platform_ops && platform_ops->resume) + platform_ops->resume(platform_ops->priv); + snd_ac97_resume(pxa2xx_ac97_ac97); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} + +static int pxa2xx_ac97_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int ret = 0; + + if (card) + ret = pxa2xx_ac97_do_suspend(card); + + return ret; +} + +static int pxa2xx_ac97_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int ret = 0; + + if (card) + ret = pxa2xx_ac97_do_resume(card); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(pxa2xx_ac97_pm_ops, pxa2xx_ac97_suspend, pxa2xx_ac97_resume); +#endif + +static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = { + .open = pxa2xx_ac97_pcm_open, + .close = pxa2xx_ac97_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pxa2xx_pcm_hw_params, + .hw_free = pxa2xx_pcm_hw_free, + .prepare = pxa2xx_ac97_pcm_prepare, + .trigger = pxa2xx_pcm_trigger, + .pointer = pxa2xx_pcm_pointer, + .mmap = pxa2xx_pcm_mmap, +}; + + +static int pxa2xx_ac97_pcm_new(struct snd_card *card) +{ + struct snd_pcm *pcm; + int stream, ret; + + ret = snd_pcm_new(card, "PXA2xx-PCM", 0, 1, 1, &pcm); + if (ret) + goto out; + + pcm->private_free = pxa2xx_pcm_free_dma_buffers; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + goto out; + + stream = SNDRV_PCM_STREAM_PLAYBACK; + snd_pcm_set_ops(pcm, stream, &pxa2xx_ac97_pcm_ops); + ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream); + if (ret) + goto out; + + stream = SNDRV_PCM_STREAM_CAPTURE; + snd_pcm_set_ops(pcm, stream, &pxa2xx_ac97_pcm_ops); + ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, stream); + if (ret) + goto out; + + pxa2xx_ac97_pcm = pcm; + ret = 0; + + out: + return ret; +} + +static int pxa2xx_ac97_probe(struct platform_device *dev) +{ + struct snd_card *card; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97_template ac97_template; + int ret; + pxa2xx_audio_ops_t *pdata = dev->dev.platform_data; + + if (dev->id >= 0) { + dev_err(&dev->dev, "PXA2xx has only one AC97 port.\n"); + ret = -ENXIO; + goto err_dev; + } + + ret = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (ret < 0) + goto err; + + strlcpy(card->driver, dev->dev.driver->name, sizeof(card->driver)); + + ret = pxa2xx_ac97_pcm_new(card); + if (ret) + goto err; + + ret = pxa2xx_ac97_hw_probe(dev); + if (ret) + goto err; + + ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus); + if (ret) + goto err_remove; + memset(&ac97_template, 0, sizeof(ac97_template)); + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97); + if (ret) + goto err_remove; + + snprintf(card->shortname, sizeof(card->shortname), + "%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97)); + snprintf(card->longname, sizeof(card->longname), + "%s (%s)", dev->dev.driver->name, card->mixername); + + if (pdata && pdata->codec_pdata[0]) + snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]); + ret = snd_card_register(card); + if (ret == 0) { + platform_set_drvdata(dev, card); + return 0; + } + +err_remove: + pxa2xx_ac97_hw_remove(dev); +err: + if (card) + snd_card_free(card); +err_dev: + return ret; +} + +static int pxa2xx_ac97_remove(struct platform_device *dev) +{ + struct snd_card *card = platform_get_drvdata(dev); + + if (card) { + snd_card_free(card); + pxa2xx_ac97_hw_remove(dev); + } + + return 0; +} + +static struct platform_driver pxa2xx_ac97_driver = { + .probe = pxa2xx_ac97_probe, + .remove = pxa2xx_ac97_remove, + .driver = { + .name = "pxa2xx-ac97", +#ifdef CONFIG_PM_SLEEP + .pm = &pxa2xx_ac97_pm_ops, +#endif + }, +}; + +module_platform_driver(pxa2xx_ac97_driver); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa2xx-ac97"); diff --git a/sound/arm/pxa2xx-pcm-lib.c b/sound/arm/pxa2xx-pcm-lib.c new file mode 100644 index 000000000..7931789d4 --- /dev/null +++ b/sound/arm/pxa2xx-pcm-lib.c @@ -0,0 +1,225 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static const struct snd_pcm_hardware pxa2xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 32, + .period_bytes_max = 8192 - 32, + .periods_min = 1, + .periods_max = 256, + .buffer_bytes_max = 128 * 1024, + .fifo_size = 32, +}; + +int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_params; + struct dma_slave_config config; + int ret; + + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (!dma_params) + return 0; + + ret = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (ret) + return ret; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream), + &config); + + ret = dmaengine_slave_config(chan, &config); + if (ret) + return ret; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} +EXPORT_SYMBOL(pxa2xx_pcm_hw_params); + +int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} +EXPORT_SYMBOL(pxa2xx_pcm_hw_free); + +int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_dmaengine_pcm_trigger(substream, cmd); +} +EXPORT_SYMBOL(pxa2xx_pcm_trigger); + +snd_pcm_uframes_t +pxa2xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} +EXPORT_SYMBOL(pxa2xx_pcm_pointer); + +int pxa2xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} +EXPORT_SYMBOL(pxa2xx_pcm_prepare); + +int pxa2xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dmaengine_dai_dma_data *dma_params; + int ret; + + runtime->hw = pxa2xx_pcm_hardware; + + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (!dma_params) + return 0; + + /* + * For mysterious reasons (and despite what the manual says) + * playback samples are lost if the DMA count is not a multiple + * of the DMA burst size. Let's add a rule to enforce that. + */ + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return snd_dmaengine_pcm_open( + substream, dma_request_slave_channel(rtd->cpu_dai->dev, + dma_params->chan_name)); +} +EXPORT_SYMBOL(pxa2xx_pcm_open); + +int pxa2xx_pcm_close(struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_close_release_chan(substream); +} +EXPORT_SYMBOL(pxa2xx_pcm_close); + +int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} +EXPORT_SYMBOL(pxa2xx_pcm_mmap); + +int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = pxa2xx_pcm_hardware.buffer_bytes_max; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_wc(pcm->card->dev, size, &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} +EXPORT_SYMBOL(pxa2xx_pcm_preallocate_dma_buffer); + +void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_wc(pcm->card->dev, buf->bytes, buf->area, buf->addr); + buf->area = NULL; + } +} +EXPORT_SYMBOL(pxa2xx_pcm_free_dma_buffers); + +int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = pxa2xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_new); + +const struct snd_pcm_ops pxa2xx_pcm_ops = { + .open = pxa2xx_pcm_open, + .close = pxa2xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pxa2xx_pcm_hw_params, + .hw_free = pxa2xx_pcm_hw_free, + .prepare = pxa2xx_pcm_prepare, + .trigger = pxa2xx_pcm_trigger, + .pointer = pxa2xx_pcm_pointer, + .mmap = pxa2xx_pcm_mmap, +}; +EXPORT_SYMBOL(pxa2xx_pcm_ops); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("Intel PXA2xx sound library"); +MODULE_LICENSE("GPL"); diff --git a/sound/atmel/Kconfig b/sound/atmel/Kconfig new file mode 100644 index 000000000..d789cbcb9 --- /dev/null +++ b/sound/atmel/Kconfig @@ -0,0 +1,12 @@ +menu "Atmel devices (AT91)" + depends on ARCH_AT91 + +config SND_ATMEL_AC97C + tristate "Atmel AC97 Controller (AC97C) driver" + select SND_PCM + select SND_AC97_CODEC + depends on ARCH_AT91 + help + ALSA sound driver for the Atmel AC97 controller. + +endmenu diff --git a/sound/atmel/Makefile b/sound/atmel/Makefile new file mode 100644 index 000000000..d4009d143 --- /dev/null +++ b/sound/atmel/Makefile @@ -0,0 +1,3 @@ +snd-atmel-ac97c-objs := ac97c.o + +obj-$(CONFIG_SND_ATMEL_AC97C) += snd-atmel-ac97c.o diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c new file mode 100644 index 000000000..380025887 --- /dev/null +++ b/sound/atmel/ac97c.c @@ -0,0 +1,899 @@ +/* + * Driver for Atmel AC97C + * + * Copyright (C) 2005-2009 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ac97c.h" + +/* Serialize access to opened variable */ +static DEFINE_MUTEX(opened_mutex); + +struct atmel_ac97c { + struct clk *pclk; + struct platform_device *pdev; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_ac97 *ac97; + struct snd_ac97_bus *ac97_bus; + + u64 cur_format; + unsigned int cur_rate; + int playback_period, capture_period; + /* Serialize access to opened variable */ + spinlock_t lock; + void __iomem *regs; + int irq; + int opened; + struct gpio_desc *reset_pin; +}; + +#define get_chip(card) ((struct atmel_ac97c *)(card)->private_data) + +#define ac97c_writel(chip, reg, val) \ + __raw_writel((val), (chip)->regs + AC97C_##reg) +#define ac97c_readl(chip, reg) \ + __raw_readl((chip)->regs + AC97C_##reg) + +static const struct snd_pcm_hardware atmel_ac97c_hw = { + .info = (SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_JOINT_DUPLEX + | SNDRV_PCM_INFO_RESUME + | SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_BE + | SNDRV_PCM_FMTBIT_S16_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 2 * 2 * 64 * 2048, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 6, + .periods_max = 64, +}; + +static int atmel_ac97c_playback_open(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + mutex_lock(&opened_mutex); + chip->opened++; + runtime->hw = atmel_ac97c_hw; + if (chip->cur_rate) { + runtime->hw.rate_min = chip->cur_rate; + runtime->hw.rate_max = chip->cur_rate; + } + if (chip->cur_format) + runtime->hw.formats = pcm_format_to_bits(chip->cur_format); + mutex_unlock(&opened_mutex); + chip->playback_substream = substream; + return 0; +} + +static int atmel_ac97c_capture_open(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + mutex_lock(&opened_mutex); + chip->opened++; + runtime->hw = atmel_ac97c_hw; + if (chip->cur_rate) { + runtime->hw.rate_min = chip->cur_rate; + runtime->hw.rate_max = chip->cur_rate; + } + if (chip->cur_format) + runtime->hw.formats = pcm_format_to_bits(chip->cur_format); + mutex_unlock(&opened_mutex); + chip->capture_substream = substream; + return 0; +} + +static int atmel_ac97c_playback_close(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + + mutex_lock(&opened_mutex); + chip->opened--; + if (!chip->opened) { + chip->cur_rate = 0; + chip->cur_format = 0; + } + mutex_unlock(&opened_mutex); + + chip->playback_substream = NULL; + + return 0; +} + +static int atmel_ac97c_capture_close(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + + mutex_lock(&opened_mutex); + chip->opened--; + if (!chip->opened) { + chip->cur_rate = 0; + chip->cur_format = 0; + } + mutex_unlock(&opened_mutex); + + chip->capture_substream = NULL; + + return 0; +} + +static int atmel_ac97c_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + int retval; + + retval = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (retval < 0) + return retval; + + /* Set restrictions to params. */ + mutex_lock(&opened_mutex); + chip->cur_rate = params_rate(hw_params); + chip->cur_format = params_format(hw_params); + mutex_unlock(&opened_mutex); + + return retval; +} + +static int atmel_ac97c_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + int retval; + + retval = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (retval < 0) + return retval; + + /* Set restrictions to params. */ + mutex_lock(&opened_mutex); + chip->cur_rate = params_rate(hw_params); + chip->cur_format = params_format(hw_params); + mutex_unlock(&opened_mutex); + + return retval; +} + +static int atmel_ac97c_playback_prepare(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int block_size = frames_to_bytes(runtime, runtime->period_size); + unsigned long word = ac97c_readl(chip, OCA); + int retval; + + chip->playback_period = 0; + word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT)); + + /* assign channels to AC97C channel A */ + switch (runtime->channels) { + case 1: + word |= AC97C_CH_ASSIGN(PCM_LEFT, A); + break; + case 2: + word |= AC97C_CH_ASSIGN(PCM_LEFT, A) + | AC97C_CH_ASSIGN(PCM_RIGHT, A); + break; + default: + /* TODO: support more than two channels */ + return -EINVAL; + } + ac97c_writel(chip, OCA, word); + + /* configure sample format and size */ + word = ac97c_readl(chip, CAMR); + if (chip->opened <= 1) + word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + else + word |= AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S16_BE: /* fall through */ + word &= ~(AC97C_CMR_CEM_LITTLE); + break; + default: + word = ac97c_readl(chip, OCA); + word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT)); + ac97c_writel(chip, OCA, word); + return -EINVAL; + } + + /* Enable underrun interrupt on channel A */ + word |= AC97C_CSR_UNRUN; + + ac97c_writel(chip, CAMR, word); + + /* Enable channel A event interrupt */ + word = ac97c_readl(chip, IMR); + word |= AC97C_SR_CAEVT; + ac97c_writel(chip, IER, word); + + /* set variable rate if needed */ + if (runtime->rate != 48000) { + word = ac97c_readl(chip, MR); + word |= AC97C_MR_VRA; + ac97c_writel(chip, MR, word); + } else { + word = ac97c_readl(chip, MR); + word &= ~(AC97C_MR_VRA); + ac97c_writel(chip, MR, word); + } + + retval = snd_ac97_set_rate(chip->ac97, AC97_PCM_FRONT_DAC_RATE, + runtime->rate); + if (retval) + dev_dbg(&chip->pdev->dev, "could not set rate %d Hz\n", + runtime->rate); + + /* Initialize and start the PDC */ + writel(runtime->dma_addr, chip->regs + ATMEL_PDC_TPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_TCR); + writel(runtime->dma_addr + block_size, chip->regs + ATMEL_PDC_TNPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_TNCR); + + return retval; +} + +static int atmel_ac97c_capture_prepare(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int block_size = frames_to_bytes(runtime, runtime->period_size); + unsigned long word = ac97c_readl(chip, ICA); + int retval; + + chip->capture_period = 0; + word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT)); + + /* assign channels to AC97C channel A */ + switch (runtime->channels) { + case 1: + word |= AC97C_CH_ASSIGN(PCM_LEFT, A); + break; + case 2: + word |= AC97C_CH_ASSIGN(PCM_LEFT, A) + | AC97C_CH_ASSIGN(PCM_RIGHT, A); + break; + default: + /* TODO: support more than two channels */ + return -EINVAL; + } + ac97c_writel(chip, ICA, word); + + /* configure sample format and size */ + word = ac97c_readl(chip, CAMR); + if (chip->opened <= 1) + word = AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + else + word |= AC97C_CMR_DMAEN | AC97C_CMR_SIZE_16; + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S16_BE: /* fall through */ + word &= ~(AC97C_CMR_CEM_LITTLE); + break; + default: + word = ac97c_readl(chip, ICA); + word &= ~(AC97C_CH_MASK(PCM_LEFT) | AC97C_CH_MASK(PCM_RIGHT)); + ac97c_writel(chip, ICA, word); + return -EINVAL; + } + + /* Enable overrun interrupt on channel A */ + word |= AC97C_CSR_OVRUN; + + ac97c_writel(chip, CAMR, word); + + /* Enable channel A event interrupt */ + word = ac97c_readl(chip, IMR); + word |= AC97C_SR_CAEVT; + ac97c_writel(chip, IER, word); + + /* set variable rate if needed */ + if (runtime->rate != 48000) { + word = ac97c_readl(chip, MR); + word |= AC97C_MR_VRA; + ac97c_writel(chip, MR, word); + } else { + word = ac97c_readl(chip, MR); + word &= ~(AC97C_MR_VRA); + ac97c_writel(chip, MR, word); + } + + retval = snd_ac97_set_rate(chip->ac97, AC97_PCM_LR_ADC_RATE, + runtime->rate); + if (retval) + dev_dbg(&chip->pdev->dev, "could not set rate %d Hz\n", + runtime->rate); + + /* Initialize and start the PDC */ + writel(runtime->dma_addr, chip->regs + ATMEL_PDC_RPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_RCR); + writel(runtime->dma_addr + block_size, chip->regs + ATMEL_PDC_RNPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_RNCR); + + return retval; +} + +static int +atmel_ac97c_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + unsigned long camr, ptcr = 0; + + camr = ac97c_readl(chip, CAMR); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */ + case SNDRV_PCM_TRIGGER_RESUME: /* fall through */ + case SNDRV_PCM_TRIGGER_START: + ptcr = ATMEL_PDC_TXTEN; + camr |= AC97C_CMR_CENA | AC97C_CSR_ENDTX; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */ + case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */ + case SNDRV_PCM_TRIGGER_STOP: + ptcr |= ATMEL_PDC_TXTDIS; + if (chip->opened <= 1) + camr &= ~AC97C_CMR_CENA; + break; + default: + return -EINVAL; + } + + ac97c_writel(chip, CAMR, camr); + writel(ptcr, chip->regs + ATMEL_PDC_PTCR); + return 0; +} + +static int +atmel_ac97c_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + unsigned long camr, ptcr = 0; + + camr = ac97c_readl(chip, CAMR); + ptcr = readl(chip->regs + ATMEL_PDC_PTSR); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */ + case SNDRV_PCM_TRIGGER_RESUME: /* fall through */ + case SNDRV_PCM_TRIGGER_START: + ptcr = ATMEL_PDC_RXTEN; + camr |= AC97C_CMR_CENA | AC97C_CSR_ENDRX; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */ + case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */ + case SNDRV_PCM_TRIGGER_STOP: + ptcr |= ATMEL_PDC_RXTDIS; + if (chip->opened <= 1) + camr &= ~AC97C_CMR_CENA; + break; + default: + return -EINVAL; + } + + ac97c_writel(chip, CAMR, camr); + writel(ptcr, chip->regs + ATMEL_PDC_PTCR); + return 0; +} + +static snd_pcm_uframes_t +atmel_ac97c_playback_pointer(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t frames; + unsigned long bytes; + + bytes = readl(chip->regs + ATMEL_PDC_TPR); + bytes -= runtime->dma_addr; + + frames = bytes_to_frames(runtime, bytes); + if (frames >= runtime->buffer_size) + frames -= runtime->buffer_size; + return frames; +} + +static snd_pcm_uframes_t +atmel_ac97c_capture_pointer(struct snd_pcm_substream *substream) +{ + struct atmel_ac97c *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t frames; + unsigned long bytes; + + bytes = readl(chip->regs + ATMEL_PDC_RPR); + bytes -= runtime->dma_addr; + + frames = bytes_to_frames(runtime, bytes); + if (frames >= runtime->buffer_size) + frames -= runtime->buffer_size; + return frames; +} + +static const struct snd_pcm_ops atmel_ac97_playback_ops = { + .open = atmel_ac97c_playback_open, + .close = atmel_ac97c_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = atmel_ac97c_playback_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .prepare = atmel_ac97c_playback_prepare, + .trigger = atmel_ac97c_playback_trigger, + .pointer = atmel_ac97c_playback_pointer, +}; + +static const struct snd_pcm_ops atmel_ac97_capture_ops = { + .open = atmel_ac97c_capture_open, + .close = atmel_ac97c_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = atmel_ac97c_capture_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .prepare = atmel_ac97c_capture_prepare, + .trigger = atmel_ac97c_capture_trigger, + .pointer = atmel_ac97c_capture_pointer, +}; + +static irqreturn_t atmel_ac97c_interrupt(int irq, void *dev) +{ + struct atmel_ac97c *chip = (struct atmel_ac97c *)dev; + irqreturn_t retval = IRQ_NONE; + u32 sr = ac97c_readl(chip, SR); + u32 casr = ac97c_readl(chip, CASR); + u32 cosr = ac97c_readl(chip, COSR); + u32 camr = ac97c_readl(chip, CAMR); + + if (sr & AC97C_SR_CAEVT) { + struct snd_pcm_runtime *runtime; + int offset, next_period, block_size; + dev_dbg(&chip->pdev->dev, "channel A event%s%s%s%s%s%s\n", + casr & AC97C_CSR_OVRUN ? " OVRUN" : "", + casr & AC97C_CSR_RXRDY ? " RXRDY" : "", + casr & AC97C_CSR_UNRUN ? " UNRUN" : "", + casr & AC97C_CSR_TXEMPTY ? " TXEMPTY" : "", + casr & AC97C_CSR_TXRDY ? " TXRDY" : "", + !casr ? " NONE" : ""); + if ((casr & camr) & AC97C_CSR_ENDTX) { + runtime = chip->playback_substream->runtime; + block_size = frames_to_bytes(runtime, runtime->period_size); + chip->playback_period++; + + if (chip->playback_period == runtime->periods) + chip->playback_period = 0; + next_period = chip->playback_period + 1; + if (next_period == runtime->periods) + next_period = 0; + + offset = block_size * next_period; + + writel(runtime->dma_addr + offset, chip->regs + ATMEL_PDC_TNPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_TNCR); + + snd_pcm_period_elapsed(chip->playback_substream); + } + if ((casr & camr) & AC97C_CSR_ENDRX) { + runtime = chip->capture_substream->runtime; + block_size = frames_to_bytes(runtime, runtime->period_size); + chip->capture_period++; + + if (chip->capture_period == runtime->periods) + chip->capture_period = 0; + next_period = chip->capture_period + 1; + if (next_period == runtime->periods) + next_period = 0; + + offset = block_size * next_period; + + writel(runtime->dma_addr + offset, chip->regs + ATMEL_PDC_RNPR); + writel(block_size / 2, chip->regs + ATMEL_PDC_RNCR); + snd_pcm_period_elapsed(chip->capture_substream); + } + retval = IRQ_HANDLED; + } + + if (sr & AC97C_SR_COEVT) { + dev_info(&chip->pdev->dev, "codec channel event%s%s%s%s%s\n", + cosr & AC97C_CSR_OVRUN ? " OVRUN" : "", + cosr & AC97C_CSR_RXRDY ? " RXRDY" : "", + cosr & AC97C_CSR_TXEMPTY ? " TXEMPTY" : "", + cosr & AC97C_CSR_TXRDY ? " TXRDY" : "", + !cosr ? " NONE" : ""); + retval = IRQ_HANDLED; + } + + if (retval == IRQ_NONE) { + dev_err(&chip->pdev->dev, "spurious interrupt sr 0x%08x " + "casr 0x%08x cosr 0x%08x\n", sr, casr, cosr); + } + + return retval; +} + +static const struct ac97_pcm at91_ac97_pcm_defs[] = { + /* Playback */ + { + .exclusive = 1, + .r = { { + .slots = ((1 << AC97_SLOT_PCM_LEFT) + | (1 << AC97_SLOT_PCM_RIGHT)), + } }, + }, + /* PCM in */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = ((1 << AC97_SLOT_PCM_LEFT) + | (1 << AC97_SLOT_PCM_RIGHT)), + } } + }, + /* Mic in */ + { + .stream = 1, + .exclusive = 1, + .r = { { + .slots = (1<ac97_bus, + ARRAY_SIZE(at91_ac97_pcm_defs), + at91_ac97_pcm_defs); + if (retval) + return retval; + + retval = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm); + if (retval) + return retval; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &atmel_ac97_capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &atmel_ac97_playback_ops); + + retval = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pdev->dev, hw.periods_min * hw.period_bytes_min, + hw.buffer_bytes_max); + if (retval) + return retval; + + pcm->private_data = chip; + pcm->info_flags = 0; + strcpy(pcm->name, chip->card->shortname); + chip->pcm = pcm; + + return 0; +} + +static int atmel_ac97c_mixer_new(struct atmel_ac97c *chip) +{ + struct snd_ac97_template template; + memset(&template, 0, sizeof(template)); + template.private_data = chip; + return snd_ac97_mixer(chip->ac97_bus, &template, &chip->ac97); +} + +static void atmel_ac97c_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct atmel_ac97c *chip = get_chip(ac97); + unsigned long word; + int timeout = 40; + + word = (reg & 0x7f) << 16 | val; + + do { + if (ac97c_readl(chip, COSR) & AC97C_CSR_TXRDY) { + ac97c_writel(chip, COTHR, word); + return; + } + udelay(1); + } while (--timeout); + + dev_dbg(&chip->pdev->dev, "codec write timeout\n"); +} + +static unsigned short atmel_ac97c_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct atmel_ac97c *chip = get_chip(ac97); + unsigned long word; + int timeout = 40; + int write = 10; + + word = (0x80 | (reg & 0x7f)) << 16; + + if ((ac97c_readl(chip, COSR) & AC97C_CSR_RXRDY) != 0) + ac97c_readl(chip, CORHR); + +retry_write: + timeout = 40; + + do { + if ((ac97c_readl(chip, COSR) & AC97C_CSR_TXRDY) != 0) { + ac97c_writel(chip, COTHR, word); + goto read_reg; + } + udelay(10); + } while (--timeout); + + if (!--write) + goto timed_out; + goto retry_write; + +read_reg: + do { + if ((ac97c_readl(chip, COSR) & AC97C_CSR_RXRDY) != 0) { + unsigned short val = ac97c_readl(chip, CORHR); + return val; + } + udelay(10); + } while (--timeout); + + if (!--write) + goto timed_out; + goto retry_write; + +timed_out: + dev_dbg(&chip->pdev->dev, "codec read timeout\n"); + return 0xffff; +} + +static void atmel_ac97c_reset(struct atmel_ac97c *chip) +{ + ac97c_writel(chip, MR, 0); + ac97c_writel(chip, MR, AC97C_MR_ENA); + ac97c_writel(chip, CAMR, 0); + ac97c_writel(chip, COMR, 0); + + if (!IS_ERR(chip->reset_pin)) { + gpiod_set_value(chip->reset_pin, 0); + /* AC97 v2.2 specifications says minimum 1 us. */ + udelay(2); + gpiod_set_value(chip->reset_pin, 1); + } else { + ac97c_writel(chip, MR, AC97C_MR_WRST | AC97C_MR_ENA); + udelay(2); + ac97c_writel(chip, MR, AC97C_MR_ENA); + } +} + +static const struct of_device_id atmel_ac97c_dt_ids[] = { + { .compatible = "atmel,at91sam9263-ac97c", }, + { } +}; +MODULE_DEVICE_TABLE(of, atmel_ac97c_dt_ids); + +static int atmel_ac97c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_card *card; + struct atmel_ac97c *chip; + struct resource *regs; + struct clk *pclk; + static struct snd_ac97_bus_ops ops = { + .write = atmel_ac97c_write, + .read = atmel_ac97c_read, + }; + int retval; + int irq; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_dbg(&pdev->dev, "no memory resource\n"); + return -ENXIO; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_dbg(&pdev->dev, "could not get irq: %d\n", irq); + return irq; + } + + pclk = clk_get(&pdev->dev, "ac97_clk"); + if (IS_ERR(pclk)) { + dev_dbg(&pdev->dev, "no peripheral clock\n"); + return PTR_ERR(pclk); + } + retval = clk_prepare_enable(pclk); + if (retval) + goto err_prepare_enable; + + retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct atmel_ac97c), &card); + if (retval) { + dev_dbg(&pdev->dev, "could not create sound card device\n"); + goto err_snd_card_new; + } + + chip = get_chip(card); + + retval = request_irq(irq, atmel_ac97c_interrupt, 0, "AC97C", chip); + if (retval) { + dev_dbg(&pdev->dev, "unable to request irq %d\n", irq); + goto err_request_irq; + } + chip->irq = irq; + + spin_lock_init(&chip->lock); + + strcpy(card->driver, "Atmel AC97C"); + strcpy(card->shortname, "Atmel AC97C"); + sprintf(card->longname, "Atmel AC97 controller"); + + chip->card = card; + chip->pclk = pclk; + chip->pdev = pdev; + chip->regs = ioremap(regs->start, resource_size(regs)); + + if (!chip->regs) { + dev_dbg(&pdev->dev, "could not remap register memory\n"); + retval = -ENOMEM; + goto err_ioremap; + } + + chip->reset_pin = devm_gpiod_get_index(dev, "ac97", 2, GPIOD_OUT_HIGH); + if (IS_ERR(chip->reset_pin)) + dev_dbg(dev, "reset pin not available\n"); + + atmel_ac97c_reset(chip); + + /* Enable overrun interrupt from codec channel */ + ac97c_writel(chip, COMR, AC97C_CSR_OVRUN); + ac97c_writel(chip, IER, ac97c_readl(chip, IMR) | AC97C_SR_COEVT); + + retval = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (retval) { + dev_dbg(&pdev->dev, "could not register on ac97 bus\n"); + goto err_ac97_bus; + } + + retval = atmel_ac97c_mixer_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 mixer\n"); + goto err_ac97_bus; + } + + retval = atmel_ac97c_pcm_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 pcm device\n"); + goto err_ac97_bus; + } + + retval = snd_card_register(card); + if (retval) { + dev_dbg(&pdev->dev, "could not register sound card\n"); + goto err_ac97_bus; + } + + platform_set_drvdata(pdev, card); + + dev_info(&pdev->dev, "Atmel AC97 controller at 0x%p, irq = %d\n", + chip->regs, irq); + + return 0; + +err_ac97_bus: + iounmap(chip->regs); +err_ioremap: + free_irq(irq, chip); +err_request_irq: + snd_card_free(card); +err_snd_card_new: + clk_disable_unprepare(pclk); +err_prepare_enable: + clk_put(pclk); + return retval; +} + +#ifdef CONFIG_PM_SLEEP +static int atmel_ac97c_suspend(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct atmel_ac97c *chip = card->private_data; + + clk_disable_unprepare(chip->pclk); + return 0; +} + +static int atmel_ac97c_resume(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct atmel_ac97c *chip = card->private_data; + int ret = clk_prepare_enable(chip->pclk); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(atmel_ac97c_pm, atmel_ac97c_suspend, atmel_ac97c_resume); +#define ATMEL_AC97C_PM_OPS &atmel_ac97c_pm +#else +#define ATMEL_AC97C_PM_OPS NULL +#endif + +static int atmel_ac97c_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + struct atmel_ac97c *chip = get_chip(card); + + ac97c_writel(chip, CAMR, 0); + ac97c_writel(chip, COMR, 0); + ac97c_writel(chip, MR, 0); + + clk_disable_unprepare(chip->pclk); + clk_put(chip->pclk); + iounmap(chip->regs); + free_irq(chip->irq, chip); + + snd_card_free(card); + + return 0; +} + +static struct platform_driver atmel_ac97c_driver = { + .probe = atmel_ac97c_probe, + .remove = atmel_ac97c_remove, + .driver = { + .name = "atmel_ac97c", + .pm = ATMEL_AC97C_PM_OPS, + .of_match_table = atmel_ac97c_dt_ids, + }, +}; +module_platform_driver(atmel_ac97c_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for Atmel AC97 controller"); +MODULE_AUTHOR("Hans-Christian Egtvedt "); diff --git a/sound/atmel/ac97c.h b/sound/atmel/ac97c.h new file mode 100644 index 000000000..ecbba5021 --- /dev/null +++ b/sound/atmel/ac97c.h @@ -0,0 +1,73 @@ +/* + * Register definitions for Atmel AC97C + * + * Copyright (C) 2005-2009 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ +#ifndef __SOUND_ATMEL_AC97C_H +#define __SOUND_ATMEL_AC97C_H + +#define AC97C_MR 0x08 +#define AC97C_ICA 0x10 +#define AC97C_OCA 0x14 +#define AC97C_CARHR 0x20 +#define AC97C_CATHR 0x24 +#define AC97C_CASR 0x28 +#define AC97C_CAMR 0x2c +#define AC97C_CORHR 0x40 +#define AC97C_COTHR 0x44 +#define AC97C_COSR 0x48 +#define AC97C_COMR 0x4c +#define AC97C_SR 0x50 +#define AC97C_IER 0x54 +#define AC97C_IDR 0x58 +#define AC97C_IMR 0x5c +#define AC97C_VERSION 0xfc + +#define AC97C_CATPR PDC_TPR +#define AC97C_CATCR PDC_TCR +#define AC97C_CATNPR PDC_TNPR +#define AC97C_CATNCR PDC_TNCR +#define AC97C_CARPR PDC_RPR +#define AC97C_CARCR PDC_RCR +#define AC97C_CARNPR PDC_RNPR +#define AC97C_CARNCR PDC_RNCR +#define AC97C_PTCR PDC_PTCR + +#define AC97C_MR_ENA (1 << 0) +#define AC97C_MR_WRST (1 << 1) +#define AC97C_MR_VRA (1 << 2) + +#define AC97C_CSR_TXRDY (1 << 0) +#define AC97C_CSR_TXEMPTY (1 << 1) +#define AC97C_CSR_UNRUN (1 << 2) +#define AC97C_CSR_RXRDY (1 << 4) +#define AC97C_CSR_OVRUN (1 << 5) +#define AC97C_CSR_ENDTX (1 << 10) +#define AC97C_CSR_ENDRX (1 << 14) + +#define AC97C_CMR_SIZE_20 (0 << 16) +#define AC97C_CMR_SIZE_18 (1 << 16) +#define AC97C_CMR_SIZE_16 (2 << 16) +#define AC97C_CMR_SIZE_10 (3 << 16) +#define AC97C_CMR_CEM_LITTLE (1 << 18) +#define AC97C_CMR_CEM_BIG (0 << 18) +#define AC97C_CMR_CENA (1 << 21) +#define AC97C_CMR_DMAEN (1 << 22) + +#define AC97C_SR_CAEVT (1 << 3) +#define AC97C_SR_COEVT (1 << 2) +#define AC97C_SR_WKUP (1 << 1) +#define AC97C_SR_SOF (1 << 0) + +#define AC97C_CH_MASK(slot) \ + (0x7 << (3 * (AC97_SLOT_##slot - 3))) +#define AC97C_CH_ASSIGN(slot, channel) \ + (AC97C_CHANNEL_##channel << (3 * (AC97_SLOT_##slot - 3))) +#define AC97C_CHANNEL_NONE 0x0 +#define AC97C_CHANNEL_A 0x1 + +#endif /* __SOUND_ATMEL_AC97C_H */ diff --git a/sound/core/Kconfig b/sound/core/Kconfig new file mode 100644 index 000000000..63b3ef9c8 --- /dev/null +++ b/sound/core/Kconfig @@ -0,0 +1,187 @@ +# ALSA soundcard-configuration +config SND_TIMER + tristate + +config SND_PCM + tristate + select SND_TIMER if SND_PCM_TIMER + +config SND_PCM_ELD + bool + +config SND_PCM_IEC958 + bool + +config SND_DMAENGINE_PCM + tristate + +config SND_HWDEP + tristate + +config SND_SEQ_DEVICE + tristate + +config SND_RAWMIDI + tristate + select SND_SEQ_DEVICE if SND_SEQUENCER != n + +config SND_COMPRESS_OFFLOAD + tristate + +config SND_JACK + bool + +# enable input device support in jack layer +config SND_JACK_INPUT_DEV + bool + depends on SND_JACK + default y if INPUT=y || INPUT=SND + +config SND_OSSEMUL + bool "Enable OSS Emulation" + select SOUND_OSS_CORE + help + This option enables the build of OSS emulation layer. + +config SND_MIXER_OSS + tristate "OSS Mixer API" + depends on SND_OSSEMUL + help + To enable OSS mixer API emulation (/dev/mixer*), say Y here + and read . + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-mixer-oss. + +config SND_PCM_OSS + tristate "OSS PCM (digital audio) API" + depends on SND_OSSEMUL + select SND_PCM + help + To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y + here and read . + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-pcm-oss. + +config SND_PCM_OSS_PLUGINS + bool "OSS PCM (digital audio) API - Include plugin system" + depends on SND_PCM_OSS + default y + help + If you disable this option, the ALSA's OSS PCM API will not + support conversion of channels, formats and rates. It will + behave like most of new OSS/Free drivers in 2.4/2.6 kernels. + +config SND_PCM_TIMER + bool "PCM timer interface" if EXPERT + default y + help + If you disable this option, pcm timer will be unavailable, so + those stubs that use pcm timer (e.g. dmix, dsnoop & co) may work + incorrectlly. + + For some embedded devices, we may disable it to reduce memory + footprint, about 20KB on x86_64 platform. + +config SND_HRTIMER + tristate "HR-timer backend support" + depends on HIGH_RES_TIMERS + select SND_TIMER + help + Say Y here to enable HR-timer backend for ALSA timer. ALSA uses + the hrtimer as a precise timing source. The ALSA sequencer code + also can use this timing source. + + To compile this driver as a module, choose M here: the module + will be called snd-hrtimer. + +config SND_DYNAMIC_MINORS + bool "Dynamic device file minor numbers" + help + If you say Y here, the minor numbers of ALSA device files in + /dev/snd/ are allocated dynamically. This allows you to have + more than 8 sound cards, but requires a dynamic device file + system like udev. + + If you are unsure about this, say N here. + +config SND_MAX_CARDS + int "Max number of sound cards" + range 4 256 + default 32 + depends on SND_DYNAMIC_MINORS + help + Specify the max number of sound cards that can be assigned + on a single machine. + +config SND_SUPPORT_OLD_API + bool "Support old ALSA API" + default y + help + Say Y here to support the obsolete ALSA PCM API (ver.0.9.0 rc3 + or older). + +config SND_PROC_FS + bool "Sound Proc FS Support" if EXPERT + depends on PROC_FS + default y + help + Say 'N' to disable Sound proc FS, which may reduce code size about + 9KB on x86_64 platform. + If unsure say Y. + +config SND_VERBOSE_PROCFS + bool "Verbose procfs contents" + depends on SND_PROC_FS + default y + help + Say Y here to include code for verbose procfs contents (provides + useful information to developers when a problem occurs). On the + other side, it makes the ALSA subsystem larger. + +config SND_VERBOSE_PRINTK + bool "Verbose printk" + help + Say Y here to enable verbose log messages. These messages + will help to identify source file and position containing + printed messages. + + You don't need this unless you're debugging ALSA. + +config SND_DEBUG + bool "Debug" + help + Say Y here to enable ALSA debug code. + +config SND_DEBUG_VERBOSE + bool "More verbose debug" + depends on SND_DEBUG + help + Say Y here to enable extra-verbose debugging messages. + + Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages. + So, say Y only if you are ready to be annoyed. + +config SND_PCM_XRUN_DEBUG + bool "Enable PCM ring buffer overrun/underrun debugging" + default n + depends on SND_DEBUG && SND_VERBOSE_PROCFS + help + Say Y to enable the PCM ring buffer overrun/underrun debugging. + It is usually not required, but if you have trouble with + sound clicking when system is loaded, it may help to determine + the process or driver which causes the scheduling gaps. + +config SND_VMASTER + bool + +config SND_DMA_SGBUF + def_bool y + depends on X86 + +source "sound/core/seq/Kconfig" diff --git a/sound/core/Makefile b/sound/core/Makefile new file mode 100644 index 000000000..d123587c0 --- /dev/null +++ b/sound/core/Makefile @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 1999,2001 by Jaroslav Kysela +# + +snd-y := sound.o init.o memory.o control.o misc.o device.o +ifneq ($(CONFIG_SND_PROC_FS),) +snd-y += info.o +snd-$(CONFIG_SND_OSSEMUL) += info_oss.o +endif +ifneq ($(CONFIG_M68K),y) +snd-$(CONFIG_ISA_DMA_API) += isadma.o +endif +snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o +snd-$(CONFIG_SND_VMASTER) += vmaster.o +snd-$(CONFIG_SND_JACK) += ctljack.o jack.o + +snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_misc.o \ + pcm_memory.o memalloc.o +snd-pcm-$(CONFIG_SND_PCM_TIMER) += pcm_timer.o +snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o +snd-pcm-$(CONFIG_SND_PCM_ELD) += pcm_drm_eld.o +snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o + +# for trace-points +CFLAGS_pcm_lib.o := -I$(src) +CFLAGS_pcm_native.o := -I$(src) + +snd-pcm-dmaengine-objs := pcm_dmaengine.o + +snd-rawmidi-objs := rawmidi.o +snd-timer-objs := timer.o +snd-hrtimer-objs := hrtimer.o +snd-rtctimer-objs := rtctimer.o +snd-hwdep-objs := hwdep.o +snd-seq-device-objs := seq_device.o + +snd-compress-objs := compress_offload.o + +obj-$(CONFIG_SND) += snd.o +obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o +obj-$(CONFIG_SND_TIMER) += snd-timer.o +obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o +obj-$(CONFIG_SND_PCM) += snd-pcm.o +obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o +obj-$(CONFIG_SND_SEQ_DEVICE) += snd-seq-device.o +obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o + +obj-$(CONFIG_SND_OSSEMUL) += oss/ +obj-$(CONFIG_SND_SEQUENCER) += seq/ + +obj-$(CONFIG_SND_COMPRESS_OFFLOAD) += snd-compress.o diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c new file mode 100644 index 000000000..68f016e49 --- /dev/null +++ b/sound/core/compress_offload.c @@ -0,0 +1,1208 @@ +/* + * compress_core.c - compress offload core + * + * Copyright (C) 2011 Intel Corporation + * Authors: Vinod Koul + * Pierre-Louis Bossart + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ +#define FORMAT(fmt) "%s: %d: " fmt, __func__, __LINE__ +#define pr_fmt(fmt) KBUILD_MODNAME ": " FORMAT(fmt) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* struct snd_compr_codec_caps overflows the ioctl bit size for some + * architectures, so we need to disable the relevant ioctls. + */ +#if _IOC_SIZEBITS < 14 +#define COMPR_CODEC_CAPS_OVERFLOW +#endif + +/* TODO: + * - add substream support for multiple devices in case of + * SND_DYNAMIC_MINORS is not used + * - Multiple node representation + * driver should be able to register multiple nodes + */ + +static DEFINE_MUTEX(device_mutex); + +struct snd_compr_file { + unsigned long caps; + struct snd_compr_stream stream; +}; + +static void error_delayed_work(struct work_struct *work); + +/* + * a note on stream states used: + * we use following states in the compressed core + * SNDRV_PCM_STATE_OPEN: When stream has been opened. + * SNDRV_PCM_STATE_SETUP: When stream has been initialized. This is done by + * calling SNDRV_COMPRESS_SET_PARAMS. Running streams will come to this + * state at stop by calling SNDRV_COMPRESS_STOP, or at end of drain. + * SNDRV_PCM_STATE_PREPARED: When a stream has been written to (for + * playback only). User after setting up stream writes the data buffer + * before starting the stream. + * SNDRV_PCM_STATE_RUNNING: When stream has been started and is + * decoding/encoding and rendering/capturing data. + * SNDRV_PCM_STATE_DRAINING: When stream is draining current data. This is done + * by calling SNDRV_COMPRESS_DRAIN. + * SNDRV_PCM_STATE_PAUSED: When stream is paused. This is done by calling + * SNDRV_COMPRESS_PAUSE. It can be stopped or resumed by calling + * SNDRV_COMPRESS_STOP or SNDRV_COMPRESS_RESUME respectively. + */ +static int snd_compr_open(struct inode *inode, struct file *f) +{ + struct snd_compr *compr; + struct snd_compr_file *data; + struct snd_compr_runtime *runtime; + enum snd_compr_direction dirn; + int maj = imajor(inode); + int ret; + + if ((f->f_flags & O_ACCMODE) == O_WRONLY) + dirn = SND_COMPRESS_PLAYBACK; + else if ((f->f_flags & O_ACCMODE) == O_RDONLY) + dirn = SND_COMPRESS_CAPTURE; + else + return -EINVAL; + + if (maj == snd_major) + compr = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_COMPRESS); + else + return -EBADFD; + + if (compr == NULL) { + pr_err("no device data!!!\n"); + return -ENODEV; + } + + if (dirn != compr->direction) { + pr_err("this device doesn't support this direction\n"); + snd_card_unref(compr->card); + return -EINVAL; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + snd_card_unref(compr->card); + return -ENOMEM; + } + + INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); + + data->stream.ops = compr->ops; + data->stream.direction = dirn; + data->stream.private_data = compr->private_data; + data->stream.device = compr; + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) { + kfree(data); + snd_card_unref(compr->card); + return -ENOMEM; + } + runtime->state = SNDRV_PCM_STATE_OPEN; + init_waitqueue_head(&runtime->sleep); + data->stream.runtime = runtime; + f->private_data = (void *)data; + mutex_lock(&compr->lock); + ret = compr->ops->open(&data->stream); + mutex_unlock(&compr->lock); + if (ret) { + kfree(runtime); + kfree(data); + } + snd_card_unref(compr->card); + return ret; +} + +static int snd_compr_free(struct inode *inode, struct file *f) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_runtime *runtime = data->stream.runtime; + + cancel_delayed_work_sync(&data->stream.error_work); + + switch (runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_PAUSED: + data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP); + break; + default: + break; + } + + data->stream.ops->free(&data->stream); + kfree(data->stream.runtime->buffer); + kfree(data->stream.runtime); + kfree(data); + return 0; +} + +static int snd_compr_update_tstamp(struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + if (!stream->ops->pointer) + return -ENOTSUPP; + stream->ops->pointer(stream, tstamp); + pr_debug("dsp consumed till %d total %d bytes\n", + tstamp->byte_offset, tstamp->copied_total); + if (stream->direction == SND_COMPRESS_PLAYBACK) + stream->runtime->total_bytes_transferred = tstamp->copied_total; + else + stream->runtime->total_bytes_available = tstamp->copied_total; + return 0; +} + +static size_t snd_compr_calc_avail(struct snd_compr_stream *stream, + struct snd_compr_avail *avail) +{ + memset(avail, 0, sizeof(*avail)); + snd_compr_update_tstamp(stream, &avail->tstamp); + /* Still need to return avail even if tstamp can't be filled in */ + + if (stream->runtime->total_bytes_available == 0 && + stream->runtime->state == SNDRV_PCM_STATE_SETUP && + stream->direction == SND_COMPRESS_PLAYBACK) { + pr_debug("detected init and someone forgot to do a write\n"); + return stream->runtime->buffer_size; + } + pr_debug("app wrote %lld, DSP consumed %lld\n", + stream->runtime->total_bytes_available, + stream->runtime->total_bytes_transferred); + if (stream->runtime->total_bytes_available == + stream->runtime->total_bytes_transferred) { + if (stream->direction == SND_COMPRESS_PLAYBACK) { + pr_debug("both pointers are same, returning full avail\n"); + return stream->runtime->buffer_size; + } else { + pr_debug("both pointers are same, returning no avail\n"); + return 0; + } + } + + avail->avail = stream->runtime->total_bytes_available - + stream->runtime->total_bytes_transferred; + if (stream->direction == SND_COMPRESS_PLAYBACK) + avail->avail = stream->runtime->buffer_size - avail->avail; + + pr_debug("ret avail as %lld\n", avail->avail); + return avail->avail; +} + +static inline size_t snd_compr_get_avail(struct snd_compr_stream *stream) +{ + struct snd_compr_avail avail; + + return snd_compr_calc_avail(stream, &avail); +} + +static int +snd_compr_ioctl_avail(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_avail ioctl_avail; + size_t avail; + + avail = snd_compr_calc_avail(stream, &ioctl_avail); + ioctl_avail.avail = avail; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + return -EBADFD; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + + if (copy_to_user((__u64 __user *)arg, + &ioctl_avail, sizeof(ioctl_avail))) + return -EFAULT; + return 0; +} + +static int snd_compr_write_data(struct snd_compr_stream *stream, + const char __user *buf, size_t count) +{ + void *dstn; + size_t copy; + struct snd_compr_runtime *runtime = stream->runtime; + /* 64-bit Modulus */ + u64 app_pointer = div64_u64(runtime->total_bytes_available, + runtime->buffer_size); + app_pointer = runtime->total_bytes_available - + (app_pointer * runtime->buffer_size); + + dstn = runtime->buffer + app_pointer; + pr_debug("copying %ld at %lld\n", + (unsigned long)count, app_pointer); + if (count < runtime->buffer_size - app_pointer) { + if (copy_from_user(dstn, buf, count)) + return -EFAULT; + } else { + copy = runtime->buffer_size - app_pointer; + if (copy_from_user(dstn, buf, copy)) + return -EFAULT; + if (copy_from_user(runtime->buffer, buf + copy, count - copy)) + return -EFAULT; + } + /* if DSP cares, let it know data has been written */ + if (stream->ops->ack) + stream->ops->ack(stream, count); + return count; +} + +static ssize_t snd_compr_write(struct file *f, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + size_t avail; + int retval; + + if (snd_BUG_ON(!data)) + return -EFAULT; + + stream = &data->stream; + mutex_lock(&stream->device->lock); + /* write is allowed when stream is running or has been steup */ + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_RUNNING: + break; + default: + mutex_unlock(&stream->device->lock); + return -EBADFD; + } + + avail = snd_compr_get_avail(stream); + pr_debug("avail returned %ld\n", (unsigned long)avail); + /* calculate how much we can write to buffer */ + if (avail > count) + avail = count; + + if (stream->ops->copy) { + char __user* cbuf = (char __user*)buf; + retval = stream->ops->copy(stream, cbuf, avail); + } else { + retval = snd_compr_write_data(stream, buf, avail); + } + if (retval > 0) + stream->runtime->total_bytes_available += retval; + + /* while initiating the stream, write should be called before START + * call, so in setup move state */ + if (stream->runtime->state == SNDRV_PCM_STATE_SETUP) { + stream->runtime->state = SNDRV_PCM_STATE_PREPARED; + pr_debug("stream prepared, Houston we are good to go\n"); + } + + mutex_unlock(&stream->device->lock); + return retval; +} + + +static ssize_t snd_compr_read(struct file *f, char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + size_t avail; + int retval; + + if (snd_BUG_ON(!data)) + return -EFAULT; + + stream = &data->stream; + mutex_lock(&stream->device->lock); + + /* read is allowed when stream is running, paused, draining and setup + * (yes setup is state which we transition to after stop, so if user + * wants to read data after stop we allow that) + */ + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + case SNDRV_PCM_STATE_DISCONNECTED: + retval = -EBADFD; + goto out; + case SNDRV_PCM_STATE_XRUN: + retval = -EPIPE; + goto out; + } + + avail = snd_compr_get_avail(stream); + pr_debug("avail returned %ld\n", (unsigned long)avail); + /* calculate how much we can read from buffer */ + if (avail > count) + avail = count; + + if (stream->ops->copy) { + retval = stream->ops->copy(stream, buf, avail); + } else { + retval = -ENXIO; + goto out; + } + if (retval > 0) + stream->runtime->total_bytes_transferred += retval; + +out: + mutex_unlock(&stream->device->lock); + return retval; +} + +static int snd_compr_mmap(struct file *f, struct vm_area_struct *vma) +{ + return -ENXIO; +} + +static __poll_t snd_compr_get_poll(struct snd_compr_stream *stream) +{ + if (stream->direction == SND_COMPRESS_PLAYBACK) + return EPOLLOUT | EPOLLWRNORM; + else + return EPOLLIN | EPOLLRDNORM; +} + +static __poll_t snd_compr_poll(struct file *f, poll_table *wait) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + size_t avail; + __poll_t retval = 0; + + if (snd_BUG_ON(!data)) + return EPOLLERR; + + stream = &data->stream; + + mutex_lock(&stream->device->lock); + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_XRUN: + retval = snd_compr_get_poll(stream) | EPOLLERR; + goto out; + default: + break; + } + + poll_wait(f, &stream->runtime->sleep, wait); + + avail = snd_compr_get_avail(stream); + pr_debug("avail is %ld\n", (unsigned long)avail); + /* check if we have at least one fragment to fill */ + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_DRAINING: + /* stream has been woken up after drain is complete + * draining done so set stream state to stopped + */ + retval = snd_compr_get_poll(stream); + stream->runtime->state = SNDRV_PCM_STATE_SETUP; + break; + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + if (avail >= stream->runtime->fragment_size) + retval = snd_compr_get_poll(stream); + break; + default: + retval = snd_compr_get_poll(stream) | EPOLLERR; + break; + } +out: + mutex_unlock(&stream->device->lock); + return retval; +} + +static int +snd_compr_get_caps(struct snd_compr_stream *stream, unsigned long arg) +{ + int retval; + struct snd_compr_caps caps; + + if (!stream->ops->get_caps) + return -ENXIO; + + memset(&caps, 0, sizeof(caps)); + retval = stream->ops->get_caps(stream, &caps); + if (retval) + goto out; + if (copy_to_user((void __user *)arg, &caps, sizeof(caps))) + retval = -EFAULT; +out: + return retval; +} + +#ifndef COMPR_CODEC_CAPS_OVERFLOW +static int +snd_compr_get_codec_caps(struct snd_compr_stream *stream, unsigned long arg) +{ + int retval; + struct snd_compr_codec_caps *caps; + + if (!stream->ops->get_codec_caps) + return -ENXIO; + + caps = kzalloc(sizeof(*caps), GFP_KERNEL); + if (!caps) + return -ENOMEM; + + retval = stream->ops->get_codec_caps(stream, caps); + if (retval) + goto out; + if (copy_to_user((void __user *)arg, caps, sizeof(*caps))) + retval = -EFAULT; + +out: + kfree(caps); + return retval; +} +#endif /* !COMPR_CODEC_CAPS_OVERFLOW */ + +/* revisit this with snd_pcm_preallocate_xxx */ +static int snd_compr_allocate_buffer(struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + unsigned int buffer_size; + void *buffer; + + buffer_size = params->buffer.fragment_size * params->buffer.fragments; + if (stream->ops->copy) { + buffer = NULL; + /* if copy is defined the driver will be required to copy + * the data from core + */ + } else { + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + } + stream->runtime->fragment_size = params->buffer.fragment_size; + stream->runtime->fragments = params->buffer.fragments; + stream->runtime->buffer = buffer; + stream->runtime->buffer_size = buffer_size; + return 0; +} + +static int snd_compress_check_input(struct snd_compr_params *params) +{ + /* first let's check the buffer parameter's */ + if (params->buffer.fragment_size == 0 || + params->buffer.fragments > U32_MAX / params->buffer.fragment_size || + params->buffer.fragments == 0) + return -EINVAL; + + /* now codec parameters */ + if (params->codec.id == 0 || params->codec.id > SND_AUDIOCODEC_MAX) + return -EINVAL; + + if (params->codec.ch_in == 0 || params->codec.ch_out == 0) + return -EINVAL; + + return 0; +} + +static int +snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_params *params; + int retval; + + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + /* + * we should allow parameter change only when stream has been + * opened not in other cases + */ + params = memdup_user((void __user *)arg, sizeof(*params)); + if (IS_ERR(params)) + return PTR_ERR(params); + + retval = snd_compress_check_input(params); + if (retval) + goto out; + + retval = snd_compr_allocate_buffer(stream, params); + if (retval) { + retval = -ENOMEM; + goto out; + } + + retval = stream->ops->set_params(stream, params); + if (retval) + goto out; + + stream->metadata_set = false; + stream->next_track = false; + + stream->runtime->state = SNDRV_PCM_STATE_SETUP; + } else { + return -EPERM; + } +out: + kfree(params); + return retval; +} + +static int +snd_compr_get_params(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_codec *params; + int retval; + + if (!stream->ops->get_params) + return -EBADFD; + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + retval = stream->ops->get_params(stream, params); + if (retval) + goto out; + if (copy_to_user((char __user *)arg, params, sizeof(*params))) + retval = -EFAULT; + +out: + kfree(params); + return retval; +} + +static int +snd_compr_get_metadata(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_metadata metadata; + int retval; + + if (!stream->ops->get_metadata) + return -ENXIO; + + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) + return -EFAULT; + + retval = stream->ops->get_metadata(stream, &metadata); + if (retval != 0) + return retval; + + if (copy_to_user((void __user *)arg, &metadata, sizeof(metadata))) + return -EFAULT; + + return 0; +} + +static int +snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_metadata metadata; + int retval; + + if (!stream->ops->set_metadata) + return -ENXIO; + /* + * we should allow parameter change only when stream has been + * opened not in other cases + */ + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) + return -EFAULT; + + retval = stream->ops->set_metadata(stream, &metadata); + stream->metadata_set = true; + + return retval; +} + +static inline int +snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_tstamp tstamp = {0}; + int ret; + + ret = snd_compr_update_tstamp(stream, &tstamp); + if (ret == 0) + ret = copy_to_user((struct snd_compr_tstamp __user *)arg, + &tstamp, sizeof(tstamp)) ? -EFAULT : 0; + return ret; +} + +static int snd_compr_pause(struct snd_compr_stream *stream) +{ + int retval; + + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING) + return -EPERM; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_PAUSED; + return retval; +} + +static int snd_compr_resume(struct snd_compr_stream *stream) +{ + int retval; + + if (stream->runtime->state != SNDRV_PCM_STATE_PAUSED) + return -EPERM; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_RELEASE); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_RUNNING; + return retval; +} + +static int snd_compr_start(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_SETUP: + if (stream->direction != SND_COMPRESS_CAPTURE) + return -EPERM; + break; + case SNDRV_PCM_STATE_PREPARED: + break; + default: + return -EPERM; + } + + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_START); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_RUNNING; + return retval; +} + +static int snd_compr_stop(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + return -EPERM; + default: + break; + } + + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + if (!retval) { + /* clear flags and stop any drain wait */ + stream->partial_drain = false; + stream->metadata_set = false; + snd_compr_drain_notify(stream); + stream->runtime->total_bytes_available = 0; + stream->runtime->total_bytes_transferred = 0; + } + return retval; +} + +static void error_delayed_work(struct work_struct *work) +{ + struct snd_compr_stream *stream; + + stream = container_of(work, struct snd_compr_stream, error_work.work); + + mutex_lock(&stream->device->lock); + + stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + wake_up(&stream->runtime->sleep); + + mutex_unlock(&stream->device->lock); +} + +/* + * snd_compr_stop_error: Report a fatal error on a stream + * @stream: pointer to stream + * @state: state to transition the stream to + * + * Stop the stream and set its state. + * + * Should be called with compressed device lock held. + */ +int snd_compr_stop_error(struct snd_compr_stream *stream, + snd_pcm_state_t state) +{ + if (stream->runtime->state == state) + return 0; + + stream->runtime->state = state; + + pr_debug("Changing state to: %d\n", state); + + queue_delayed_work(system_power_efficient_wq, &stream->error_work, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_compr_stop_error); + +static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) +{ + int ret; + + /* + * We are called with lock held. So drop the lock while we wait for + * drain complete notification from the driver + * + * It is expected that driver will notify the drain completion and then + * stream will be moved to SETUP state, even if draining resulted in an + * error. We can trigger next track after this. + */ + stream->runtime->state = SNDRV_PCM_STATE_DRAINING; + mutex_unlock(&stream->device->lock); + + /* we wait for drain to complete here, drain can return when + * interruption occurred, wait returned error or success. + * For the first two cases we don't do anything different here and + * return after waking up + */ + + ret = wait_event_interruptible(stream->runtime->sleep, + (stream->runtime->state != SNDRV_PCM_STATE_DRAINING)); + if (ret == -ERESTARTSYS) + pr_debug("wait aborted by a signal\n"); + else if (ret) + pr_debug("wait for drain failed with %d\n", ret); + + + wake_up(&stream->runtime->sleep); + mutex_lock(&stream->device->lock); + + return ret; +} + +static int snd_compr_drain(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + return -EPERM; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_DRAIN); + if (retval) { + pr_debug("SND_COMPR_TRIGGER_DRAIN failed %d\n", retval); + wake_up(&stream->runtime->sleep); + return retval; + } + + return snd_compress_wait_for_drain(stream); +} + +static int snd_compr_next_track(struct snd_compr_stream *stream) +{ + int retval; + + /* only a running stream can transition to next track */ + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING) + return -EPERM; + + /* next track doesn't have any meaning for capture streams */ + if (stream->direction == SND_COMPRESS_CAPTURE) + return -EPERM; + + /* you can signal next track if this is intended to be a gapless stream + * and current track metadata is set + */ + if (stream->metadata_set == false) + return -EPERM; + + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_NEXT_TRACK); + if (retval != 0) + return retval; + stream->metadata_set = false; + stream->next_track = true; + return 0; +} + +static int snd_compr_partial_drain(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + return -EPERM; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + + /* partial drain doesn't have any meaning for capture streams */ + if (stream->direction == SND_COMPRESS_CAPTURE) + return -EPERM; + + /* stream can be drained only when next track has been signalled */ + if (stream->next_track == false) + return -EPERM; + + stream->partial_drain = true; + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_PARTIAL_DRAIN); + if (retval) { + pr_debug("Partial drain returned failure\n"); + wake_up(&stream->runtime->sleep); + return retval; + } + + stream->next_track = false; + return snd_compress_wait_for_drain(stream); +} + +static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + int retval = -ENOTTY; + + if (snd_BUG_ON(!data)) + return -EFAULT; + + stream = &data->stream; + + mutex_lock(&stream->device->lock); + switch (_IOC_NR(cmd)) { + case _IOC_NR(SNDRV_COMPRESS_IOCTL_VERSION): + retval = put_user(SNDRV_COMPRESS_VERSION, + (int __user *)arg) ? -EFAULT : 0; + break; + case _IOC_NR(SNDRV_COMPRESS_GET_CAPS): + retval = snd_compr_get_caps(stream, arg); + break; +#ifndef COMPR_CODEC_CAPS_OVERFLOW + case _IOC_NR(SNDRV_COMPRESS_GET_CODEC_CAPS): + retval = snd_compr_get_codec_caps(stream, arg); + break; +#endif + case _IOC_NR(SNDRV_COMPRESS_SET_PARAMS): + retval = snd_compr_set_params(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_GET_PARAMS): + retval = snd_compr_get_params(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_SET_METADATA): + retval = snd_compr_set_metadata(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_GET_METADATA): + retval = snd_compr_get_metadata(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_TSTAMP): + retval = snd_compr_tstamp(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_AVAIL): + retval = snd_compr_ioctl_avail(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_PAUSE): + retval = snd_compr_pause(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_RESUME): + retval = snd_compr_resume(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_START): + retval = snd_compr_start(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_STOP): + retval = snd_compr_stop(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_DRAIN): + retval = snd_compr_drain(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_PARTIAL_DRAIN): + retval = snd_compr_partial_drain(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_NEXT_TRACK): + retval = snd_compr_next_track(stream); + break; + + } + mutex_unlock(&stream->device->lock); + return retval; +} + +/* support of 32bit userspace on 64bit platforms */ +#ifdef CONFIG_COMPAT +static long snd_compr_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return snd_compr_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static const struct file_operations snd_compr_file_ops = { + .owner = THIS_MODULE, + .open = snd_compr_open, + .release = snd_compr_free, + .write = snd_compr_write, + .read = snd_compr_read, + .unlocked_ioctl = snd_compr_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = snd_compr_ioctl_compat, +#endif + .mmap = snd_compr_mmap, + .poll = snd_compr_poll, +}; + +static int snd_compress_dev_register(struct snd_device *device) +{ + int ret = -EINVAL; + struct snd_compr *compr; + + if (snd_BUG_ON(!device || !device->device_data)) + return -EBADFD; + compr = device->device_data; + + pr_debug("reg device %s, direction %d\n", compr->name, + compr->direction); + /* register compressed device */ + ret = snd_register_device(SNDRV_DEVICE_TYPE_COMPRESS, + compr->card, compr->device, + &snd_compr_file_ops, compr, &compr->dev); + if (ret < 0) { + pr_err("snd_register_device failed %d\n", ret); + return ret; + } + return ret; + +} + +static int snd_compress_dev_disconnect(struct snd_device *device) +{ + struct snd_compr *compr; + + compr = device->device_data; + snd_unregister_device(&compr->dev); + return 0; +} + +#ifdef CONFIG_SND_VERBOSE_PROCFS +static void snd_compress_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_compr *compr = (struct snd_compr *)entry->private_data; + + snd_iprintf(buffer, "card: %d\n", compr->card->number); + snd_iprintf(buffer, "device: %d\n", compr->device); + snd_iprintf(buffer, "stream: %s\n", + compr->direction == SND_COMPRESS_PLAYBACK + ? "PLAYBACK" : "CAPTURE"); + snd_iprintf(buffer, "id: %s\n", compr->id); +} + +static int snd_compress_proc_init(struct snd_compr *compr) +{ + struct snd_info_entry *entry; + char name[16]; + + sprintf(name, "compr%i", compr->device); + entry = snd_info_create_card_entry(compr->card, name, + compr->card->proc_root); + if (!entry) + return -ENOMEM; + entry->mode = S_IFDIR | 0555; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + compr->proc_root = entry; + + entry = snd_info_create_card_entry(compr->card, "info", + compr->proc_root); + if (entry) { + snd_info_set_text_ops(entry, compr, + snd_compress_proc_info_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + compr->proc_info_entry = entry; + + return 0; +} + +static void snd_compress_proc_done(struct snd_compr *compr) +{ + snd_info_free_entry(compr->proc_info_entry); + compr->proc_info_entry = NULL; + snd_info_free_entry(compr->proc_root); + compr->proc_root = NULL; +} + +static inline void snd_compress_set_id(struct snd_compr *compr, const char *id) +{ + strlcpy(compr->id, id, sizeof(compr->id)); +} +#else +static inline int snd_compress_proc_init(struct snd_compr *compr) +{ + return 0; +} + +static inline void snd_compress_proc_done(struct snd_compr *compr) +{ +} + +static inline void snd_compress_set_id(struct snd_compr *compr, const char *id) +{ +} +#endif + +static int snd_compress_dev_free(struct snd_device *device) +{ + struct snd_compr *compr; + + compr = device->device_data; + snd_compress_proc_done(compr); + put_device(&compr->dev); + return 0; +} + +/* + * snd_compress_new: create new compress device + * @card: sound card pointer + * @device: device number + * @dirn: device direction, should be of type enum snd_compr_direction + * @compr: compress device pointer + */ +int snd_compress_new(struct snd_card *card, int device, + int dirn, const char *id, struct snd_compr *compr) +{ + static struct snd_device_ops ops = { + .dev_free = snd_compress_dev_free, + .dev_register = snd_compress_dev_register, + .dev_disconnect = snd_compress_dev_disconnect, + }; + int ret; + + compr->card = card; + compr->device = device; + compr->direction = dirn; + + snd_compress_set_id(compr, id); + + snd_device_initialize(&compr->dev, card); + dev_set_name(&compr->dev, "comprC%iD%i", card->number, device); + + ret = snd_device_new(card, SNDRV_DEV_COMPRESS, compr, &ops); + if (ret == 0) + snd_compress_proc_init(compr); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_compress_new); + +static int snd_compress_add_device(struct snd_compr *device) +{ + int ret; + + if (!device->card) + return -EINVAL; + + /* register the card */ + ret = snd_card_register(device->card); + if (ret) + goto out; + return 0; + +out: + pr_err("failed with %d\n", ret); + return ret; + +} + +static int snd_compress_remove_device(struct snd_compr *device) +{ + return snd_card_free(device->card); +} + +/** + * snd_compress_register - register compressed device + * + * @device: compressed device to register + */ +int snd_compress_register(struct snd_compr *device) +{ + int retval; + + if (device->name == NULL || device->ops == NULL) + return -EINVAL; + + pr_debug("Registering compressed device %s\n", device->name); + if (snd_BUG_ON(!device->ops->open)) + return -EINVAL; + if (snd_BUG_ON(!device->ops->free)) + return -EINVAL; + if (snd_BUG_ON(!device->ops->set_params)) + return -EINVAL; + if (snd_BUG_ON(!device->ops->trigger)) + return -EINVAL; + + mutex_init(&device->lock); + + /* register a compressed card */ + mutex_lock(&device_mutex); + retval = snd_compress_add_device(device); + mutex_unlock(&device_mutex); + return retval; +} +EXPORT_SYMBOL_GPL(snd_compress_register); + +int snd_compress_deregister(struct snd_compr *device) +{ + pr_debug("Removing compressed device %s\n", device->name); + mutex_lock(&device_mutex); + snd_compress_remove_device(device); + mutex_unlock(&device_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_compress_deregister); + +MODULE_DESCRIPTION("ALSA Compressed offload framework"); +MODULE_AUTHOR("Vinod Koul "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/core/control.c b/sound/core/control.c new file mode 100644 index 000000000..9cec0e8c9 --- /dev/null +++ b/sound/core/control.c @@ -0,0 +1,1992 @@ +/* + * Routines for driver control interface + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* max number of user-defined controls */ +#define MAX_USER_CONTROLS 32 +#define MAX_CONTROL_COUNT 1028 + +struct snd_kctl_ioctl { + struct list_head list; /* list of all ioctls */ + snd_kctl_ioctl_func_t fioctl; +}; + +static DECLARE_RWSEM(snd_ioctl_rwsem); +static LIST_HEAD(snd_control_ioctls); +#ifdef CONFIG_COMPAT +static LIST_HEAD(snd_control_compat_ioctls); +#endif + +static int snd_ctl_open(struct inode *inode, struct file *file) +{ + unsigned long flags; + struct snd_card *card; + struct snd_ctl_file *ctl; + int i, err; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); + if (!card) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(card, file); + if (err < 0) { + err = -ENODEV; + goto __error1; + } + if (!try_module_get(card->module)) { + err = -EFAULT; + goto __error2; + } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (ctl == NULL) { + err = -ENOMEM; + goto __error; + } + INIT_LIST_HEAD(&ctl->events); + init_waitqueue_head(&ctl->change_sleep); + spin_lock_init(&ctl->read_lock); + ctl->card = card; + for (i = 0; i < SND_CTL_SUBDEV_ITEMS; i++) + ctl->preferred_subdevice[i] = -1; + ctl->pid = get_pid(task_pid(current)); + file->private_data = ctl; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_add_tail(&ctl->list, &card->ctl_files); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + snd_card_unref(card); + return 0; + + __error: + module_put(card->module); + __error2: + snd_card_file_remove(card, file); + __error1: + if (card) + snd_card_unref(card); + return err; +} + +static void snd_ctl_empty_read_queue(struct snd_ctl_file * ctl) +{ + unsigned long flags; + struct snd_kctl_event *cread; + + spin_lock_irqsave(&ctl->read_lock, flags); + while (!list_empty(&ctl->events)) { + cread = snd_kctl_event(ctl->events.next); + list_del(&cread->list); + kfree(cread); + } + spin_unlock_irqrestore(&ctl->read_lock, flags); +} + +static int snd_ctl_release(struct inode *inode, struct file *file) +{ + unsigned long flags; + struct snd_card *card; + struct snd_ctl_file *ctl; + struct snd_kcontrol *control; + unsigned int idx; + + ctl = file->private_data; + file->private_data = NULL; + card = ctl->card; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_del(&ctl->list); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + down_write(&card->controls_rwsem); + list_for_each_entry(control, &card->controls, list) + for (idx = 0; idx < control->count; idx++) + if (control->vd[idx].owner == ctl) + control->vd[idx].owner = NULL; + up_write(&card->controls_rwsem); + snd_ctl_empty_read_queue(ctl); + put_pid(ctl->pid); + kfree(ctl); + module_put(card->module); + snd_card_file_remove(card, file); + return 0; +} + +/** + * snd_ctl_notify - Send notification to user-space for a control change + * @card: the card to send notification + * @mask: the event mask, SNDRV_CTL_EVENT_* + * @id: the ctl element id to send notification + * + * This function adds an event record with the given id and mask, appends + * to the list and wakes up the user-space for notification. This can be + * called in the atomic context. + */ +void snd_ctl_notify(struct snd_card *card, unsigned int mask, + struct snd_ctl_elem_id *id) +{ + unsigned long flags; + struct snd_ctl_file *ctl; + struct snd_kctl_event *ev; + + if (snd_BUG_ON(!card || !id)) + return; + if (card->shutdown) + return; + read_lock(&card->ctl_files_rwlock); +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + card->mixer_oss_change_count++; +#endif + list_for_each_entry(ctl, &card->ctl_files, list) { + if (!ctl->subscribed) + continue; + spin_lock_irqsave(&ctl->read_lock, flags); + list_for_each_entry(ev, &ctl->events, list) { + if (ev->id.numid == id->numid) { + ev->mask |= mask; + goto _found; + } + } + ev = kzalloc(sizeof(*ev), GFP_ATOMIC); + if (ev) { + ev->id = *id; + ev->mask = mask; + list_add_tail(&ev->list, &ctl->events); + } else { + dev_err(card->dev, "No memory available to allocate event\n"); + } + _found: + wake_up(&ctl->change_sleep); + spin_unlock_irqrestore(&ctl->read_lock, flags); + kill_fasync(&ctl->fasync, SIGIO, POLL_IN); + } + read_unlock(&card->ctl_files_rwlock); +} +EXPORT_SYMBOL(snd_ctl_notify); + +/** + * snd_ctl_new - create a new control instance with some elements + * @kctl: the pointer to store new control instance + * @count: the number of elements in this control + * @access: the default access flags for elements in this control + * @file: given when locking these elements + * + * Allocates a memory object for a new control instance. The instance has + * elements as many as the given number (@count). Each element has given + * access permissions (@access). Each element is locked when @file is given. + * + * Return: 0 on success, error code on failure + */ +static int snd_ctl_new(struct snd_kcontrol **kctl, unsigned int count, + unsigned int access, struct snd_ctl_file *file) +{ + unsigned int size; + unsigned int idx; + + if (count == 0 || count > MAX_CONTROL_COUNT) + return -EINVAL; + + size = sizeof(struct snd_kcontrol); + size += sizeof(struct snd_kcontrol_volatile) * count; + + *kctl = kzalloc(size, GFP_KERNEL); + if (!*kctl) + return -ENOMEM; + + for (idx = 0; idx < count; idx++) { + (*kctl)->vd[idx].access = access; + (*kctl)->vd[idx].owner = file; + } + (*kctl)->count = count; + + return 0; +} + +/** + * snd_ctl_new1 - create a control instance from the template + * @ncontrol: the initialization record + * @private_data: the private data to set + * + * Allocates a new struct snd_kcontrol instance and initialize from the given + * template. When the access field of ncontrol is 0, it's assumed as + * READWRITE access. When the count field is 0, it's assumes as one. + * + * Return: The pointer of the newly generated instance, or %NULL on failure. + */ +struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, + void *private_data) +{ + struct snd_kcontrol *kctl; + unsigned int count; + unsigned int access; + int err; + + if (snd_BUG_ON(!ncontrol || !ncontrol->info)) + return NULL; + + count = ncontrol->count; + if (count == 0) + count = 1; + + access = ncontrol->access; + if (access == 0) + access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE | + SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + + err = snd_ctl_new(&kctl, count, access, NULL); + if (err < 0) + return NULL; + + /* The 'numid' member is decided when calling snd_ctl_add(). */ + kctl->id.iface = ncontrol->iface; + kctl->id.device = ncontrol->device; + kctl->id.subdevice = ncontrol->subdevice; + if (ncontrol->name) { + strlcpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name)); + if (strcmp(ncontrol->name, kctl->id.name) != 0) + pr_warn("ALSA: Control name '%s' truncated to '%s'\n", + ncontrol->name, kctl->id.name); + } + kctl->id.index = ncontrol->index; + + kctl->info = ncontrol->info; + kctl->get = ncontrol->get; + kctl->put = ncontrol->put; + kctl->tlv.p = ncontrol->tlv.p; + + kctl->private_value = ncontrol->private_value; + kctl->private_data = private_data; + + return kctl; +} +EXPORT_SYMBOL(snd_ctl_new1); + +/** + * snd_ctl_free_one - release the control instance + * @kcontrol: the control instance + * + * Releases the control instance created via snd_ctl_new() + * or snd_ctl_new1(). + * Don't call this after the control was added to the card. + */ +void snd_ctl_free_one(struct snd_kcontrol *kcontrol) +{ + if (kcontrol) { + if (kcontrol->private_free) + kcontrol->private_free(kcontrol); + kfree(kcontrol); + } +} +EXPORT_SYMBOL(snd_ctl_free_one); + +static bool snd_ctl_remove_numid_conflict(struct snd_card *card, + unsigned int count) +{ + struct snd_kcontrol *kctl; + + /* Make sure that the ids assigned to the control do not wrap around */ + if (card->last_numid >= UINT_MAX - count) + card->last_numid = 0; + + list_for_each_entry(kctl, &card->controls, list) { + if (kctl->id.numid < card->last_numid + 1 + count && + kctl->id.numid + kctl->count > card->last_numid + 1) { + card->last_numid = kctl->id.numid + kctl->count - 1; + return true; + } + } + return false; +} + +static int snd_ctl_find_hole(struct snd_card *card, unsigned int count) +{ + unsigned int iter = 100000; + + while (snd_ctl_remove_numid_conflict(card, count)) { + if (--iter == 0) { + /* this situation is very unlikely */ + dev_err(card->dev, "unable to allocate new control numid\n"); + return -ENOMEM; + } + } + return 0; +} + +/* add a new kcontrol object; call with card->controls_rwsem locked */ +static int __snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) +{ + struct snd_ctl_elem_id id; + unsigned int idx; + unsigned int count; + + id = kcontrol->id; + if (id.index > UINT_MAX - kcontrol->count) + return -EINVAL; + + if (snd_ctl_find_id(card, &id)) { + dev_err(card->dev, + "control %i:%i:%i:%s:%i is already present\n", + id.iface, id.device, id.subdevice, id.name, id.index); + return -EBUSY; + } + + if (snd_ctl_find_hole(card, kcontrol->count) < 0) + return -ENOMEM; + + list_add_tail(&kcontrol->list, &card->controls); + card->controls_count += kcontrol->count; + kcontrol->id.numid = card->last_numid + 1; + card->last_numid += kcontrol->count; + + id = kcontrol->id; + count = kcontrol->count; + for (idx = 0; idx < count; idx++, id.index++, id.numid++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); + + return 0; +} + +/** + * snd_ctl_add - add the control instance to the card + * @card: the card instance + * @kcontrol: the control instance to add + * + * Adds the control instance created via snd_ctl_new() or + * snd_ctl_new1() to the given card. Assigns also an unique + * numid used for fast search. + * + * It frees automatically the control which cannot be added. + * + * Return: Zero if successful, or a negative error code on failure. + * + */ +int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) +{ + int err = -EINVAL; + + if (! kcontrol) + return err; + if (snd_BUG_ON(!card || !kcontrol->info)) + goto error; + + down_write(&card->controls_rwsem); + err = __snd_ctl_add(card, kcontrol); + up_write(&card->controls_rwsem); + if (err < 0) + goto error; + return 0; + + error: + snd_ctl_free_one(kcontrol); + return err; +} +EXPORT_SYMBOL(snd_ctl_add); + +/** + * snd_ctl_replace - replace the control instance of the card + * @card: the card instance + * @kcontrol: the control instance to replace + * @add_on_replace: add the control if not already added + * + * Replaces the given control. If the given control does not exist + * and the add_on_replace flag is set, the control is added. If the + * control exists, it is destroyed first. + * + * It frees automatically the control which cannot be added or replaced. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, + bool add_on_replace) +{ + struct snd_ctl_elem_id id; + unsigned int count; + unsigned int idx; + struct snd_kcontrol *old; + int ret; + + if (!kcontrol) + return -EINVAL; + if (snd_BUG_ON(!card || !kcontrol->info)) { + ret = -EINVAL; + goto error; + } + id = kcontrol->id; + down_write(&card->controls_rwsem); + old = snd_ctl_find_id(card, &id); + if (!old) { + if (add_on_replace) + goto add; + up_write(&card->controls_rwsem); + ret = -EINVAL; + goto error; + } + ret = snd_ctl_remove(card, old); + if (ret < 0) { + up_write(&card->controls_rwsem); + goto error; + } +add: + if (snd_ctl_find_hole(card, kcontrol->count) < 0) { + up_write(&card->controls_rwsem); + ret = -ENOMEM; + goto error; + } + list_add_tail(&kcontrol->list, &card->controls); + card->controls_count += kcontrol->count; + kcontrol->id.numid = card->last_numid + 1; + card->last_numid += kcontrol->count; + id = kcontrol->id; + count = kcontrol->count; + up_write(&card->controls_rwsem); + for (idx = 0; idx < count; idx++, id.index++, id.numid++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); + return 0; + +error: + snd_ctl_free_one(kcontrol); + return ret; +} +EXPORT_SYMBOL(snd_ctl_replace); + +/** + * snd_ctl_remove - remove the control from the card and release it + * @card: the card instance + * @kcontrol: the control instance to remove + * + * Removes the control from the card and then releases the instance. + * You don't need to call snd_ctl_free_one(). You must be in + * the write lock - down_write(&card->controls_rwsem). + * + * Return: 0 if successful, or a negative error code on failure. + */ +int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol) +{ + struct snd_ctl_elem_id id; + unsigned int idx; + + if (snd_BUG_ON(!card || !kcontrol)) + return -EINVAL; + list_del(&kcontrol->list); + card->controls_count -= kcontrol->count; + id = kcontrol->id; + for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id); + snd_ctl_free_one(kcontrol); + return 0; +} +EXPORT_SYMBOL(snd_ctl_remove); + +/** + * snd_ctl_remove_id - remove the control of the given id and release it + * @card: the card instance + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Return: 0 if successful, or a negative error code on failure. + */ +int snd_ctl_remove_id(struct snd_card *card, struct snd_ctl_elem_id *id) +{ + struct snd_kcontrol *kctl; + int ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + ret = snd_ctl_remove(card, kctl); + up_write(&card->controls_rwsem); + return ret; +} +EXPORT_SYMBOL(snd_ctl_remove_id); + +/** + * snd_ctl_remove_user_ctl - remove and release the unlocked user control + * @file: active control handle + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Return: 0 if successful, or a negative error code on failure. + */ +static int snd_ctl_remove_user_ctl(struct snd_ctl_file * file, + struct snd_ctl_elem_id *id) +{ + struct snd_card *card = file->card; + struct snd_kcontrol *kctl; + int idx, ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + ret = -ENOENT; + goto error; + } + if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_USER)) { + ret = -EINVAL; + goto error; + } + for (idx = 0; idx < kctl->count; idx++) + if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) { + ret = -EBUSY; + goto error; + } + ret = snd_ctl_remove(card, kctl); + if (ret < 0) + goto error; + card->user_ctl_count--; +error: + up_write(&card->controls_rwsem); + return ret; +} + +/** + * snd_ctl_activate_id - activate/inactivate the control of the given id + * @card: the card instance + * @id: the control id to activate/inactivate + * @active: non-zero to activate + * + * Finds the control instance with the given id, and activate or + * inactivate the control together with notification, if changed. + * The given ID data is filled with full information. + * + * Return: 0 if unchanged, 1 if changed, or a negative error code on failure. + */ +int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id, + int active) +{ + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + int ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + ret = -ENOENT; + goto unlock; + } + index_offset = snd_ctl_get_ioff(kctl, id); + vd = &kctl->vd[index_offset]; + ret = 0; + if (active) { + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) + goto unlock; + vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } else { + if (vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) + goto unlock; + vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } + snd_ctl_build_ioff(id, kctl, index_offset); + ret = 1; + unlock: + up_write(&card->controls_rwsem); + if (ret > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, id); + return ret; +} +EXPORT_SYMBOL_GPL(snd_ctl_activate_id); + +/** + * snd_ctl_rename_id - replace the id of a control on the card + * @card: the card instance + * @src_id: the old id + * @dst_id: the new id + * + * Finds the control with the old id from the card, and replaces the + * id with the new one. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, + struct snd_ctl_elem_id *dst_id) +{ + struct snd_kcontrol *kctl; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, src_id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + kctl->id = *dst_id; + kctl->id.numid = card->last_numid + 1; + card->last_numid += kctl->count; + up_write(&card->controls_rwsem); + return 0; +} +EXPORT_SYMBOL(snd_ctl_rename_id); + +/** + * snd_ctl_find_numid - find the control instance with the given number-id + * @card: the card instance + * @numid: the number-id to search + * + * Finds the control instance with the given number-id from the card. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + * + * Return: The pointer of the instance if found, or %NULL if not. + * + */ +struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid) +{ + struct snd_kcontrol *kctl; + + if (snd_BUG_ON(!card || !numid)) + return NULL; + list_for_each_entry(kctl, &card->controls, list) { + if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid) + return kctl; + } + return NULL; +} +EXPORT_SYMBOL(snd_ctl_find_numid); + +/** + * snd_ctl_find_id - find the control instance with the given id + * @card: the card instance + * @id: the id to search + * + * Finds the control instance with the given id from the card. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + * + * Return: The pointer of the instance if found, or %NULL if not. + * + */ +struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card, + struct snd_ctl_elem_id *id) +{ + struct snd_kcontrol *kctl; + + if (snd_BUG_ON(!card || !id)) + return NULL; + if (id->numid != 0) + return snd_ctl_find_numid(card, id->numid); + list_for_each_entry(kctl, &card->controls, list) { + if (kctl->id.iface != id->iface) + continue; + if (kctl->id.device != id->device) + continue; + if (kctl->id.subdevice != id->subdevice) + continue; + if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name))) + continue; + if (kctl->id.index > id->index) + continue; + if (kctl->id.index + kctl->count <= id->index) + continue; + return kctl; + } + return NULL; +} +EXPORT_SYMBOL(snd_ctl_find_id); + +static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, + unsigned int cmd, void __user *arg) +{ + struct snd_ctl_card_info *info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + down_read(&snd_ioctl_rwsem); + info->card = card->number; + strlcpy(info->id, card->id, sizeof(info->id)); + strlcpy(info->driver, card->driver, sizeof(info->driver)); + strlcpy(info->name, card->shortname, sizeof(info->name)); + strlcpy(info->longname, card->longname, sizeof(info->longname)); + strlcpy(info->mixername, card->mixername, sizeof(info->mixername)); + strlcpy(info->components, card->components, sizeof(info->components)); + up_read(&snd_ioctl_rwsem); + if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) { + kfree(info); + return -EFAULT; + } + kfree(info); + return 0; +} + +static int snd_ctl_elem_list(struct snd_card *card, + struct snd_ctl_elem_list __user *_list) +{ + struct snd_ctl_elem_list list; + struct snd_kcontrol *kctl; + struct snd_ctl_elem_id id; + unsigned int offset, space, jidx; + int err = 0; + + if (copy_from_user(&list, _list, sizeof(list))) + return -EFAULT; + offset = list.offset; + space = list.space; + + down_read(&card->controls_rwsem); + list.count = card->controls_count; + list.used = 0; + if (space > 0) { + list_for_each_entry(kctl, &card->controls, list) { + if (offset >= kctl->count) { + offset -= kctl->count; + continue; + } + for (jidx = offset; jidx < kctl->count; jidx++) { + snd_ctl_build_ioff(&id, kctl, jidx); + if (copy_to_user(list.pids + list.used, &id, + sizeof(id))) { + err = -EFAULT; + goto out; + } + list.used++; + if (!--space) + goto out; + } + offset = 0; + } + } + out: + up_read(&card->controls_rwsem); + if (!err && copy_to_user(_list, &list, sizeof(list))) + err = -EFAULT; + return err; +} + +static bool validate_element_member_dimension(struct snd_ctl_elem_info *info) +{ + unsigned int members; + unsigned int i; + + if (info->dimen.d[0] == 0) + return true; + + members = 1; + for (i = 0; i < ARRAY_SIZE(info->dimen.d); ++i) { + if (info->dimen.d[i] == 0) + break; + members *= info->dimen.d[i]; + + /* + * info->count should be validated in advance, to guarantee + * calculation soundness. + */ + if (members > info->count) + return false; + } + + for (++i; i < ARRAY_SIZE(info->dimen.d); ++i) { + if (info->dimen.d[i] > 0) + return false; + } + + return members == info->count; +} + +static int snd_ctl_elem_info(struct snd_ctl_file *ctl, + struct snd_ctl_elem_info *info) +{ + struct snd_card *card = ctl->card; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + int result; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &info->id); + if (kctl == NULL) { + up_read(&card->controls_rwsem); + return -ENOENT; + } +#ifdef CONFIG_SND_DEBUG + info->access = 0; +#endif + result = kctl->info(kctl, info); + if (result >= 0) { + snd_BUG_ON(info->access); + index_offset = snd_ctl_get_ioff(kctl, &info->id); + vd = &kctl->vd[index_offset]; + snd_ctl_build_ioff(&info->id, kctl, index_offset); + info->access = vd->access; + if (vd->owner) { + info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK; + if (vd->owner == ctl) + info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER; + info->owner = pid_vnr(vd->owner->pid); + } else { + info->owner = -1; + } + } + up_read(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl, + struct snd_ctl_elem_info __user *_info) +{ + struct snd_ctl_elem_info info; + int result; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + result = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0); + if (result < 0) + return result; + result = snd_ctl_elem_info(ctl, &info); + if (result < 0) + return result; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return result; +} + +static int snd_ctl_elem_read(struct snd_card *card, + struct snd_ctl_elem_value *control) +{ + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + + kctl = snd_ctl_find_id(card, &control->id); + if (kctl == NULL) + return -ENOENT; + + index_offset = snd_ctl_get_ioff(kctl, &control->id); + vd = &kctl->vd[index_offset]; + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_READ) || kctl->get == NULL) + return -EPERM; + + snd_ctl_build_ioff(&control->id, kctl, index_offset); + return kctl->get(kctl, control); +} + +static int snd_ctl_elem_read_user(struct snd_card *card, + struct snd_ctl_elem_value __user *_control) +{ + struct snd_ctl_elem_value *control; + int result; + + control = memdup_user(_control, sizeof(*control)); + if (IS_ERR(control)) + return PTR_ERR(control); + + result = snd_power_wait(card, SNDRV_CTL_POWER_D0); + if (result < 0) + goto error; + + down_read(&card->controls_rwsem); + result = snd_ctl_elem_read(card, control); + up_read(&card->controls_rwsem); + if (result < 0) + goto error; + + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + error: + kfree(control); + return result; +} + +static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, + struct snd_ctl_elem_value *control) +{ + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + int result; + + kctl = snd_ctl_find_id(card, &control->id); + if (kctl == NULL) + return -ENOENT; + + index_offset = snd_ctl_get_ioff(kctl, &control->id); + vd = &kctl->vd[index_offset]; + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || kctl->put == NULL || + (file && vd->owner && vd->owner != file)) { + return -EPERM; + } + + snd_ctl_build_ioff(&control->id, kctl, index_offset); + result = kctl->put(kctl, control); + if (result < 0) + return result; + + if (result > 0) { + struct snd_ctl_elem_id id = control->id; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &id); + } + + return 0; +} + +static int snd_ctl_elem_write_user(struct snd_ctl_file *file, + struct snd_ctl_elem_value __user *_control) +{ + struct snd_ctl_elem_value *control; + struct snd_card *card; + int result; + + control = memdup_user(_control, sizeof(*control)); + if (IS_ERR(control)) + return PTR_ERR(control); + + card = file->card; + result = snd_power_wait(card, SNDRV_CTL_POWER_D0); + if (result < 0) + goto error; + + down_write(&card->controls_rwsem); + result = snd_ctl_elem_write(card, file, control); + up_write(&card->controls_rwsem); + if (result < 0) + goto error; + + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + error: + kfree(control); + return result; +} + +static int snd_ctl_elem_lock(struct snd_ctl_file *file, + struct snd_ctl_elem_id __user *_id) +{ + struct snd_card *card = file->card; + struct snd_ctl_elem_id id; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner != NULL) + result = -EBUSY; + else { + vd->owner = file; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_unlock(struct snd_ctl_file *file, + struct snd_ctl_elem_id __user *_id) +{ + struct snd_card *card = file->card; + struct snd_ctl_elem_id id; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner == NULL) + result = -EINVAL; + else if (vd->owner != file) + result = -EPERM; + else { + vd->owner = NULL; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +struct user_element { + struct snd_ctl_elem_info info; + struct snd_card *card; + char *elem_data; /* element data */ + unsigned long elem_data_size; /* size of element data in bytes */ + void *tlv_data; /* TLV data */ + unsigned long tlv_data_size; /* TLV data size */ + void *priv_data; /* private data (like strings for enumerated type) */ +}; + +static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct user_element *ue = kcontrol->private_data; + unsigned int offset; + + offset = snd_ctl_get_ioff(kcontrol, &uinfo->id); + *uinfo = ue->info; + snd_ctl_build_ioff(&uinfo->id, kcontrol, offset); + + return 0; +} + +static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct user_element *ue = kcontrol->private_data; + const char *names; + unsigned int item; + unsigned int offset; + + item = uinfo->value.enumerated.item; + + offset = snd_ctl_get_ioff(kcontrol, &uinfo->id); + *uinfo = ue->info; + snd_ctl_build_ioff(&uinfo->id, kcontrol, offset); + + item = min(item, uinfo->value.enumerated.items - 1); + uinfo->value.enumerated.item = item; + + names = ue->priv_data; + for (; item > 0; --item) + names += strlen(names) + 1; + strcpy(uinfo->value.enumerated.name, names); + + return 0; +} + +static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct user_element *ue = kcontrol->private_data; + unsigned int size = ue->elem_data_size; + char *src = ue->elem_data + + snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; + + memcpy(&ucontrol->value, src, size); + return 0; +} + +static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + struct user_element *ue = kcontrol->private_data; + unsigned int size = ue->elem_data_size; + char *dst = ue->elem_data + + snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; + + change = memcmp(&ucontrol->value, dst, size) != 0; + if (change) + memcpy(dst, &ucontrol->value, size); + return change; +} + +static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, + unsigned int size) +{ + struct user_element *ue = kctl->private_data; + unsigned int *container; + struct snd_ctl_elem_id id; + unsigned int mask = 0; + int i; + int change; + + if (size > 1024 * 128) /* sane value */ + return -EINVAL; + + container = vmemdup_user(buf, size); + if (IS_ERR(container)) + return PTR_ERR(container); + + change = ue->tlv_data_size != size; + if (!change) + change = memcmp(ue->tlv_data, container, size) != 0; + if (!change) { + kvfree(container); + return 0; + } + + if (ue->tlv_data == NULL) { + /* Now TLV data is available. */ + for (i = 0; i < kctl->count; ++i) + kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + mask = SNDRV_CTL_EVENT_MASK_INFO; + } + + kvfree(ue->tlv_data); + ue->tlv_data = container; + ue->tlv_data_size = size; + + mask |= SNDRV_CTL_EVENT_MASK_TLV; + for (i = 0; i < kctl->count; ++i) { + snd_ctl_build_ioff(&id, kctl, i); + snd_ctl_notify(ue->card, mask, &id); + } + + return change; +} + +static int read_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, + unsigned int size) +{ + struct user_element *ue = kctl->private_data; + + if (ue->tlv_data_size == 0 || ue->tlv_data == NULL) + return -ENXIO; + + if (size < ue->tlv_data_size) + return -ENOSPC; + + if (copy_to_user(buf, ue->tlv_data, ue->tlv_data_size)) + return -EFAULT; + + return 0; +} + +static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kctl, int op_flag, + unsigned int size, unsigned int __user *buf) +{ + if (op_flag == SNDRV_CTL_TLV_OP_WRITE) + return replace_user_tlv(kctl, buf, size); + else + return read_user_tlv(kctl, buf, size); +} + +static int snd_ctl_elem_init_enum_names(struct user_element *ue) +{ + char *names, *p; + size_t buf_len, name_len; + unsigned int i; + const uintptr_t user_ptrval = ue->info.value.enumerated.names_ptr; + + if (ue->info.value.enumerated.names_length > 64 * 1024) + return -EINVAL; + + names = vmemdup_user((const void __user *)user_ptrval, + ue->info.value.enumerated.names_length); + if (IS_ERR(names)) + return PTR_ERR(names); + + /* check that there are enough valid names */ + buf_len = ue->info.value.enumerated.names_length; + p = names; + for (i = 0; i < ue->info.value.enumerated.items; ++i) { + name_len = strnlen(p, buf_len); + if (name_len == 0 || name_len >= 64 || name_len == buf_len) { + kvfree(names); + return -EINVAL; + } + p += name_len + 1; + buf_len -= name_len + 1; + } + + ue->priv_data = names; + ue->info.value.enumerated.names_ptr = 0; + + return 0; +} + +static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) +{ + struct user_element *ue = kcontrol->private_data; + + kvfree(ue->tlv_data); + kvfree(ue->priv_data); + kfree(ue); +} + +static int snd_ctl_elem_add(struct snd_ctl_file *file, + struct snd_ctl_elem_info *info, int replace) +{ + /* The capacity of struct snd_ctl_elem_value.value.*/ + static const unsigned int value_sizes[] = { + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long), + [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long), + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int), + [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char), + [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958), + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), + }; + static const unsigned int max_value_counts[] = { + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128, + [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128, + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128, + [SNDRV_CTL_ELEM_TYPE_BYTES] = 512, + [SNDRV_CTL_ELEM_TYPE_IEC958] = 1, + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64, + }; + struct snd_card *card = file->card; + struct snd_kcontrol *kctl; + unsigned int count; + unsigned int access; + long private_size; + struct user_element *ue; + unsigned int offset; + int err; + + if (!*info->id.name) + return -EINVAL; + if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name)) + return -EINVAL; + + /* Delete a control to replace them if needed. */ + if (replace) { + info->id.numid = 0; + err = snd_ctl_remove_user_ctl(file, &info->id); + if (err) + return err; + } + + /* + * The number of userspace controls are counted control by control, + * not element by element. + */ + if (card->user_ctl_count + 1 > MAX_USER_CONTROLS) + return -ENOMEM; + + /* Check the number of elements for this userspace control. */ + count = info->owner; + if (count == 0) + count = 1; + + /* Arrange access permissions if needed. */ + access = info->access; + if (access == 0) + access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE | + SNDRV_CTL_ELEM_ACCESS_TLV_WRITE); + + /* In initial state, nothing is available as TLV container. */ + if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) + access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + access |= SNDRV_CTL_ELEM_ACCESS_USER; + + /* + * Check information and calculate the size of data specific to + * this userspace control. + */ + if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || + info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) + return -EINVAL; + if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED && + info->value.enumerated.items == 0) + return -EINVAL; + if (info->count < 1 || + info->count > max_value_counts[info->type]) + return -EINVAL; + if (!validate_element_member_dimension(info)) + return -EINVAL; + private_size = value_sizes[info->type] * info->count; + + /* + * Keep memory object for this userspace control. After passing this + * code block, the instance should be freed by snd_ctl_free_one(). + * + * Note that these elements in this control are locked. + */ + err = snd_ctl_new(&kctl, count, access, file); + if (err < 0) + return err; + memcpy(&kctl->id, &info->id, sizeof(kctl->id)); + kctl->private_data = kzalloc(sizeof(struct user_element) + private_size * count, + GFP_KERNEL); + if (kctl->private_data == NULL) { + kfree(kctl); + return -ENOMEM; + } + kctl->private_free = snd_ctl_elem_user_free; + + /* Set private data for this userspace control. */ + ue = (struct user_element *)kctl->private_data; + ue->card = card; + ue->info = *info; + ue->info.access = 0; + ue->elem_data = (char *)ue + sizeof(*ue); + ue->elem_data_size = private_size; + if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) { + err = snd_ctl_elem_init_enum_names(ue); + if (err < 0) { + snd_ctl_free_one(kctl); + return err; + } + } + + /* Set callback functions. */ + if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) + kctl->info = snd_ctl_elem_user_enum_info; + else + kctl->info = snd_ctl_elem_user_info; + if (access & SNDRV_CTL_ELEM_ACCESS_READ) + kctl->get = snd_ctl_elem_user_get; + if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) + kctl->put = snd_ctl_elem_user_put; + if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) + kctl->tlv.c = snd_ctl_elem_user_tlv; + + /* This function manage to free the instance on failure. */ + down_write(&card->controls_rwsem); + err = __snd_ctl_add(card, kctl); + if (err < 0) { + snd_ctl_free_one(kctl); + goto unlock; + } + offset = snd_ctl_get_ioff(kctl, &info->id); + snd_ctl_build_ioff(&info->id, kctl, offset); + /* + * Here we cannot fill any field for the number of elements added by + * this operation because there're no specific fields. The usage of + * 'owner' field for this purpose may cause any bugs to userspace + * applications because the field originally means PID of a process + * which locks the element. + */ + + card->user_ctl_count++; + + unlock: + up_write(&card->controls_rwsem); + return err; +} + +static int snd_ctl_elem_add_user(struct snd_ctl_file *file, + struct snd_ctl_elem_info __user *_info, int replace) +{ + struct snd_ctl_elem_info info; + int err; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + err = snd_ctl_elem_add(file, &info, replace); + if (err < 0) + return err; + if (copy_to_user(_info, &info, sizeof(info))) { + snd_ctl_remove_user_ctl(file, &info.id); + return -EFAULT; + } + + return 0; +} + +static int snd_ctl_elem_remove(struct snd_ctl_file *file, + struct snd_ctl_elem_id __user *_id) +{ + struct snd_ctl_elem_id id; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + return snd_ctl_remove_user_ctl(file, &id); +} + +static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr) +{ + int subscribe; + if (get_user(subscribe, ptr)) + return -EFAULT; + if (subscribe < 0) { + subscribe = file->subscribed; + if (put_user(subscribe, ptr)) + return -EFAULT; + return 0; + } + if (subscribe) { + file->subscribed = 1; + return 0; + } else if (file->subscribed) { + snd_ctl_empty_read_queue(file); + file->subscribed = 0; + } + return 0; +} + +static int call_tlv_handler(struct snd_ctl_file *file, int op_flag, + struct snd_kcontrol *kctl, + struct snd_ctl_elem_id *id, + unsigned int __user *buf, unsigned int size) +{ + static const struct { + int op; + int perm; + } pairs[] = { + {SNDRV_CTL_TLV_OP_READ, SNDRV_CTL_ELEM_ACCESS_TLV_READ}, + {SNDRV_CTL_TLV_OP_WRITE, SNDRV_CTL_ELEM_ACCESS_TLV_WRITE}, + {SNDRV_CTL_TLV_OP_CMD, SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND}, + }; + struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; + int i; + + /* Check support of the request for this element. */ + for (i = 0; i < ARRAY_SIZE(pairs); ++i) { + if (op_flag == pairs[i].op && (vd->access & pairs[i].perm)) + break; + } + if (i == ARRAY_SIZE(pairs)) + return -ENXIO; + + if (kctl->tlv.c == NULL) + return -ENXIO; + + /* Write and command operations are not allowed for locked element. */ + if (op_flag != SNDRV_CTL_TLV_OP_READ && + vd->owner != NULL && vd->owner != file) + return -EPERM; + + return kctl->tlv.c(kctl, op_flag, size, buf); +} + +static int read_tlv_buf(struct snd_kcontrol *kctl, struct snd_ctl_elem_id *id, + unsigned int __user *buf, unsigned int size) +{ + struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; + unsigned int len; + + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)) + return -ENXIO; + + if (kctl->tlv.p == NULL) + return -ENXIO; + + len = sizeof(unsigned int) * 2 + kctl->tlv.p[1]; + if (size < len) + return -ENOMEM; + + if (copy_to_user(buf, kctl->tlv.p, len)) + return -EFAULT; + + return 0; +} + +static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, + struct snd_ctl_tlv __user *buf, + int op_flag) +{ + struct snd_ctl_tlv header; + unsigned int __user *container; + unsigned int container_size; + struct snd_kcontrol *kctl; + struct snd_ctl_elem_id id; + struct snd_kcontrol_volatile *vd; + + if (copy_from_user(&header, buf, sizeof(header))) + return -EFAULT; + + /* In design of control core, numerical ID starts at 1. */ + if (header.numid == 0) + return -EINVAL; + + /* At least, container should include type and length fields. */ + if (header.length < sizeof(unsigned int) * 2) + return -EINVAL; + container_size = header.length; + container = buf->tlv; + + kctl = snd_ctl_find_numid(file->card, header.numid); + if (kctl == NULL) + return -ENOENT; + + /* Calculate index of the element in this set. */ + id = kctl->id; + snd_ctl_build_ioff(&id, kctl, header.numid - id.numid); + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + + if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + return call_tlv_handler(file, op_flag, kctl, &id, container, + container_size); + } else { + if (op_flag == SNDRV_CTL_TLV_OP_READ) { + return read_tlv_buf(kctl, &id, container, + container_size); + } + } + + /* Not supported. */ + return -ENXIO; +} + +static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_ctl_file *ctl; + struct snd_card *card; + struct snd_kctl_ioctl *p; + void __user *argp = (void __user *)arg; + int __user *ip = argp; + int err; + + ctl = file->private_data; + card = ctl->card; + if (snd_BUG_ON(!card)) + return -ENXIO; + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list(card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: + return snd_ctl_elem_info_user(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_READ: + return snd_ctl_elem_read_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE: + return snd_ctl_elem_write_user(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_LOCK: + return snd_ctl_elem_lock(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_UNLOCK: + return snd_ctl_elem_unlock(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_ADD: + return snd_ctl_elem_add_user(ctl, argp, 0); + case SNDRV_CTL_IOCTL_ELEM_REPLACE: + return snd_ctl_elem_add_user(ctl, argp, 1); + case SNDRV_CTL_IOCTL_ELEM_REMOVE: + return snd_ctl_elem_remove(ctl, argp); + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + return snd_ctl_subscribe_events(ctl, ip); + case SNDRV_CTL_IOCTL_TLV_READ: + down_read(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ); + up_read(&ctl->card->controls_rwsem); + return err; + case SNDRV_CTL_IOCTL_TLV_WRITE: + down_write(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE); + up_write(&ctl->card->controls_rwsem); + return err; + case SNDRV_CTL_IOCTL_TLV_COMMAND: + down_write(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD); + up_write(&ctl->card->controls_rwsem); + return err; + case SNDRV_CTL_IOCTL_POWER: + return -ENOPROTOOPT; + case SNDRV_CTL_IOCTL_POWER_STATE: +#ifdef CONFIG_PM + return put_user(card->power_state, ip) ? -EFAULT : 0; +#else + return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0; +#endif + } + down_read(&snd_ioctl_rwsem); + list_for_each_entry(p, &snd_control_ioctls, list) { + err = p->fioctl(card, ctl, cmd, arg); + if (err != -ENOIOCTLCMD) { + up_read(&snd_ioctl_rwsem); + return err; + } + } + up_read(&snd_ioctl_rwsem); + dev_dbg(card->dev, "unknown ioctl = 0x%x\n", cmd); + return -ENOTTY; +} + +static ssize_t snd_ctl_read(struct file *file, char __user *buffer, + size_t count, loff_t * offset) +{ + struct snd_ctl_file *ctl; + int err = 0; + ssize_t result = 0; + + ctl = file->private_data; + if (snd_BUG_ON(!ctl || !ctl->card)) + return -ENXIO; + if (!ctl->subscribed) + return -EBADFD; + if (count < sizeof(struct snd_ctl_event)) + return -EINVAL; + spin_lock_irq(&ctl->read_lock); + while (count >= sizeof(struct snd_ctl_event)) { + struct snd_ctl_event ev; + struct snd_kctl_event *kev; + while (list_empty(&ctl->events)) { + wait_queue_entry_t wait; + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + err = -EAGAIN; + goto __end_lock; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&ctl->change_sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&ctl->read_lock); + schedule(); + remove_wait_queue(&ctl->change_sleep, &wait); + if (ctl->card->shutdown) + return -ENODEV; + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&ctl->read_lock); + } + kev = snd_kctl_event(ctl->events.next); + ev.type = SNDRV_CTL_EVENT_ELEM; + ev.data.elem.mask = kev->mask; + ev.data.elem.id = kev->id; + list_del(&kev->list); + spin_unlock_irq(&ctl->read_lock); + kfree(kev); + if (copy_to_user(buffer, &ev, sizeof(struct snd_ctl_event))) { + err = -EFAULT; + goto __end; + } + spin_lock_irq(&ctl->read_lock); + buffer += sizeof(struct snd_ctl_event); + count -= sizeof(struct snd_ctl_event); + result += sizeof(struct snd_ctl_event); + } + __end_lock: + spin_unlock_irq(&ctl->read_lock); + __end: + return result > 0 ? result : err; +} + +static __poll_t snd_ctl_poll(struct file *file, poll_table * wait) +{ + __poll_t mask; + struct snd_ctl_file *ctl; + + ctl = file->private_data; + if (!ctl->subscribed) + return 0; + poll_wait(file, &ctl->change_sleep, wait); + + mask = 0; + if (!list_empty(&ctl->events)) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} + +/* + * register the device-specific control-ioctls. + * called from each device manager like pcm.c, hwdep.c, etc. + */ +static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists) +{ + struct snd_kctl_ioctl *pn; + + pn = kzalloc(sizeof(struct snd_kctl_ioctl), GFP_KERNEL); + if (pn == NULL) + return -ENOMEM; + pn->fioctl = fcn; + down_write(&snd_ioctl_rwsem); + list_add_tail(&pn->list, lists); + up_write(&snd_ioctl_rwsem); + return 0; +} + +/** + * snd_ctl_register_ioctl - register the device-specific control-ioctls + * @fcn: ioctl callback function + * + * called from each device manager like pcm.c, hwdep.c, etc. + */ +int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls); +} +EXPORT_SYMBOL(snd_ctl_register_ioctl); + +#ifdef CONFIG_COMPAT +/** + * snd_ctl_register_ioctl_compat - register the device-specific 32bit compat + * control-ioctls + * @fcn: ioctl callback function + */ +int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls); +} +EXPORT_SYMBOL(snd_ctl_register_ioctl_compat); +#endif + +/* + * de-register the device-specific control-ioctls. + */ +static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, + struct list_head *lists) +{ + struct snd_kctl_ioctl *p; + + if (snd_BUG_ON(!fcn)) + return -EINVAL; + down_write(&snd_ioctl_rwsem); + list_for_each_entry(p, lists, list) { + if (p->fioctl == fcn) { + list_del(&p->list); + up_write(&snd_ioctl_rwsem); + kfree(p); + return 0; + } + } + up_write(&snd_ioctl_rwsem); + snd_BUG(); + return -EINVAL; +} + +/** + * snd_ctl_unregister_ioctl - de-register the device-specific control-ioctls + * @fcn: ioctl callback function to unregister + */ +int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls); +} +EXPORT_SYMBOL(snd_ctl_unregister_ioctl); + +#ifdef CONFIG_COMPAT +/** + * snd_ctl_unregister_ioctl - de-register the device-specific compat 32bit + * control-ioctls + * @fcn: ioctl callback function to unregister + */ +int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls); +} +EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat); +#endif + +static int snd_ctl_fasync(int fd, struct file * file, int on) +{ + struct snd_ctl_file *ctl; + + ctl = file->private_data; + return fasync_helper(fd, file, on, &ctl->fasync); +} + +/* return the preferred subdevice number if already assigned; + * otherwise return -1 + */ +int snd_ctl_get_preferred_subdevice(struct snd_card *card, int type) +{ + struct snd_ctl_file *kctl; + int subdevice = -1; + + read_lock(&card->ctl_files_rwlock); + list_for_each_entry(kctl, &card->ctl_files, list) { + if (kctl->pid == task_pid(current)) { + subdevice = kctl->preferred_subdevice[type]; + if (subdevice != -1) + break; + } + } + read_unlock(&card->ctl_files_rwlock); + return subdevice; +} +EXPORT_SYMBOL_GPL(snd_ctl_get_preferred_subdevice); + +/* + * ioctl32 compat + */ +#ifdef CONFIG_COMPAT +#include "control_compat.c" +#else +#define snd_ctl_ioctl_compat NULL +#endif + +/* + * INIT PART + */ + +static const struct file_operations snd_ctl_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_ctl_read, + .open = snd_ctl_open, + .release = snd_ctl_release, + .llseek = no_llseek, + .poll = snd_ctl_poll, + .unlocked_ioctl = snd_ctl_ioctl, + .compat_ioctl = snd_ctl_ioctl_compat, + .fasync = snd_ctl_fasync, +}; + +/* + * registration of the control device + */ +static int snd_ctl_dev_register(struct snd_device *device) +{ + struct snd_card *card = device->device_data; + + return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, + &snd_ctl_f_ops, card, &card->ctl_dev); +} + +/* + * disconnection of the control device + */ +static int snd_ctl_dev_disconnect(struct snd_device *device) +{ + struct snd_card *card = device->device_data; + struct snd_ctl_file *ctl; + + read_lock(&card->ctl_files_rwlock); + list_for_each_entry(ctl, &card->ctl_files, list) { + wake_up(&ctl->change_sleep); + kill_fasync(&ctl->fasync, SIGIO, POLL_ERR); + } + read_unlock(&card->ctl_files_rwlock); + + return snd_unregister_device(&card->ctl_dev); +} + +/* + * free all controls + */ +static int snd_ctl_dev_free(struct snd_device *device) +{ + struct snd_card *card = device->device_data; + struct snd_kcontrol *control; + + down_write(&card->controls_rwsem); + while (!list_empty(&card->controls)) { + control = snd_kcontrol(card->controls.next); + snd_ctl_remove(card, control); + } + up_write(&card->controls_rwsem); + put_device(&card->ctl_dev); + return 0; +} + +/* + * create control core: + * called from init.c + */ +int snd_ctl_create(struct snd_card *card) +{ + static struct snd_device_ops ops = { + .dev_free = snd_ctl_dev_free, + .dev_register = snd_ctl_dev_register, + .dev_disconnect = snd_ctl_dev_disconnect, + }; + int err; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS)) + return -ENXIO; + + snd_device_initialize(&card->ctl_dev, card); + dev_set_name(&card->ctl_dev, "controlC%d", card->number); + + err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); + if (err < 0) + put_device(&card->ctl_dev); + return err; +} + +/* + * Frequently used control callbacks/helpers + */ + +/** + * snd_ctl_boolean_mono_info - Helper function for a standard boolean info + * callback with a mono channel + * @kcontrol: the kcontrol instance + * @uinfo: info to store + * + * This is a function that can be used as info callback for a standard + * boolean control with a single mono channel. + */ +int snd_ctl_boolean_mono_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; +} +EXPORT_SYMBOL(snd_ctl_boolean_mono_info); + +/** + * snd_ctl_boolean_stereo_info - Helper function for a standard boolean info + * callback with stereo two channels + * @kcontrol: the kcontrol instance + * @uinfo: info to store + * + * This is a function that can be used as info callback for a standard + * boolean control with stereo two channels. + */ +int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} +EXPORT_SYMBOL(snd_ctl_boolean_stereo_info); + +/** + * snd_ctl_enum_info - fills the info structure for an enumerated control + * @info: the structure to be filled + * @channels: the number of the control's channels; often one + * @items: the number of control values; also the size of @names + * @names: an array containing the names of all control values + * + * Sets all required fields in @info to their appropriate values. + * If the control's accessibility is not the default (readable and writable), + * the caller has to fill @info->access. + * + * Return: Zero. + */ +int snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels, + unsigned int items, const char *const names[]) +{ + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = channels; + info->value.enumerated.items = items; + if (!items) + return 0; + if (info->value.enumerated.item >= items) + info->value.enumerated.item = items - 1; + WARN(strlen(names[info->value.enumerated.item]) >= sizeof(info->value.enumerated.name), + "ALSA: too long item name '%s'\n", + names[info->value.enumerated.item]); + strlcpy(info->value.enumerated.name, + names[info->value.enumerated.item], + sizeof(info->value.enumerated.name)); + return 0; +} +EXPORT_SYMBOL(snd_ctl_enum_info); diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c new file mode 100644 index 000000000..3fc216644 --- /dev/null +++ b/sound/core/control_compat.c @@ -0,0 +1,504 @@ +/* + * compat ioctls for control API + * + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* this file included from control.c */ + +#include +#include + +struct snd_ctl_elem_list32 { + u32 offset; + u32 space; + u32 used; + u32 count; + u32 pids; + unsigned char reserved[50]; +} /* don't set packed attribute here */; + +static int snd_ctl_elem_list_compat(struct snd_card *card, + struct snd_ctl_elem_list32 __user *data32) +{ + struct snd_ctl_elem_list __user *data; + compat_caddr_t ptr; + int err; + + data = compat_alloc_user_space(sizeof(*data)); + + /* offset, space, used, count */ + if (copy_in_user(data, data32, 4 * sizeof(u32))) + return -EFAULT; + /* pids */ + if (get_user(ptr, &data32->pids) || + put_user(compat_ptr(ptr), &data->pids)) + return -EFAULT; + err = snd_ctl_elem_list(card, data); + if (err < 0) + return err; + /* copy the result */ + if (copy_in_user(data32, data, 4 * sizeof(u32))) + return -EFAULT; + return 0; +} + +/* + * control element info + * it uses union, so the things are not easy.. + */ + +struct snd_ctl_elem_info32 { + struct snd_ctl_elem_id id; // the size of struct is same + s32 type; + u32 access; + u32 count; + s32 owner; + union { + struct { + s32 min; + s32 max; + s32 step; + } integer; + struct { + u64 min; + u64 max; + u64 step; + } integer64; + struct { + u32 items; + u32 item; + char name[64]; + u64 names_ptr; + u32 names_length; + } enumerated; + unsigned char reserved[128]; + } value; + unsigned char reserved[64]; +} __attribute__((packed)); + +static int snd_ctl_elem_info_compat(struct snd_ctl_file *ctl, + struct snd_ctl_elem_info32 __user *data32) +{ + struct snd_ctl_elem_info *data; + int err; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (! data) + return -ENOMEM; + + err = -EFAULT; + /* copy id */ + if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) + goto error; + /* we need to copy the item index. + * hope this doesn't break anything.. + */ + if (get_user(data->value.enumerated.item, &data32->value.enumerated.item)) + goto error; + + err = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0); + if (err < 0) + goto error; + err = snd_ctl_elem_info(ctl, data); + if (err < 0) + goto error; + /* restore info to 32bit */ + err = -EFAULT; + /* id, type, access, count */ + if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) || + copy_to_user(&data32->type, &data->type, 3 * sizeof(u32))) + goto error; + if (put_user(data->owner, &data32->owner)) + goto error; + switch (data->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + if (put_user(data->value.integer.min, &data32->value.integer.min) || + put_user(data->value.integer.max, &data32->value.integer.max) || + put_user(data->value.integer.step, &data32->value.integer.step)) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + if (copy_to_user(&data32->value.integer64, + &data->value.integer64, + sizeof(data->value.integer64))) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + if (copy_to_user(&data32->value.enumerated, + &data->value.enumerated, + sizeof(data->value.enumerated))) + goto error; + break; + default: + break; + } + err = 0; + error: + kfree(data); + return err; +} + +/* read / write */ +struct snd_ctl_elem_value32 { + struct snd_ctl_elem_id id; + unsigned int indirect; /* bit-field causes misalignment */ + union { + s32 integer[128]; + unsigned char data[512]; +#ifndef CONFIG_X86_64 + s64 integer64[64]; +#endif + } value; + unsigned char reserved[128]; +}; + +#ifdef CONFIG_X86_X32 +/* x32 has a different alignment for 64bit values from ia32 */ +struct snd_ctl_elem_value_x32 { + struct snd_ctl_elem_id id; + unsigned int indirect; /* bit-field causes misalignment */ + union { + s32 integer[128]; + unsigned char data[512]; + s64 integer64[64]; + } value; + unsigned char reserved[128]; +}; +#endif /* CONFIG_X86_X32 */ + +/* get the value type and count of the control */ +static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, + int *countp) +{ + struct snd_kcontrol *kctl; + struct snd_ctl_elem_info *info; + int err; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (! kctl) { + up_read(&card->controls_rwsem); + return -ENOENT; + } + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (info == NULL) { + up_read(&card->controls_rwsem); + return -ENOMEM; + } + info->id = *id; + err = kctl->info(kctl, info); + up_read(&card->controls_rwsem); + if (err >= 0) { + err = info->type; + *countp = info->count; + } + kfree(info); + return err; +} + +static int get_elem_size(int type, int count) +{ + switch (type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + return sizeof(s64) * count; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + return sizeof(int) * count; + case SNDRV_CTL_ELEM_TYPE_BYTES: + return 512; + case SNDRV_CTL_ELEM_TYPE_IEC958: + return sizeof(struct snd_aes_iec958); + default: + return -1; + } +} + +static int copy_ctl_value_from_user(struct snd_card *card, + struct snd_ctl_elem_value *data, + void __user *userdata, + void __user *valuep, + int *typep, int *countp) +{ + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, type, size; + int uninitialized_var(count); + unsigned int indirect; + + if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) + return -EFAULT; + if (get_user(indirect, &data32->indirect)) + return -EFAULT; + if (indirect) + return -EINVAL; + type = get_ctl_type(card, &data->id, &count); + if (type < 0) + return type; + + if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; + if (get_user(val, &intp[i])) + return -EFAULT; + data->value.integer.value[i] = val; + } + } else { + size = get_elem_size(type, count); + if (size < 0) { + dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; + } + if (copy_from_user(data->value.bytes.data, valuep, size)) + return -EFAULT; + } + + *typep = type; + *countp = count; + return 0; +} + +/* restore the value to 32bit */ +static int copy_ctl_value_to_user(void __user *userdata, + void __user *valuep, + struct snd_ctl_elem_value *data, + int type, int count) +{ + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, size; + + if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; + val = data->value.integer.value[i]; + if (put_user(val, &intp[i])) + return -EFAULT; + } + } else { + size = get_elem_size(type, count); + if (copy_to_user(valuep, data->value.bytes.data, size)) + return -EFAULT; + } + if (copy_to_user(&data32->id, &data->id, sizeof(data32->id))) + return -EFAULT; + return 0; +} + +static int ctl_elem_read_user(struct snd_card *card, + void __user *userdata, void __user *valuep) +{ + struct snd_ctl_elem_value *data; + int err, type, count; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + err = copy_ctl_value_from_user(card, data, userdata, valuep, + &type, &count); + if (err < 0) + goto error; + + err = snd_power_wait(card, SNDRV_CTL_POWER_D0); + if (err < 0) + goto error; + err = snd_ctl_elem_read(card, data); + if (err < 0) + goto error; + err = copy_ctl_value_to_user(userdata, valuep, data, type, count); + error: + kfree(data); + return err; +} + +static int ctl_elem_write_user(struct snd_ctl_file *file, + void __user *userdata, void __user *valuep) +{ + struct snd_ctl_elem_value *data; + struct snd_card *card = file->card; + int err, type, count; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + err = copy_ctl_value_from_user(card, data, userdata, valuep, + &type, &count); + if (err < 0) + goto error; + + err = snd_power_wait(card, SNDRV_CTL_POWER_D0); + if (err < 0) + goto error; + err = snd_ctl_elem_write(card, file, data); + if (err < 0) + goto error; + err = copy_ctl_value_to_user(userdata, valuep, data, type, count); + error: + kfree(data); + return err; +} + +static int snd_ctl_elem_read_user_compat(struct snd_card *card, + struct snd_ctl_elem_value32 __user *data32) +{ + return ctl_elem_read_user(card, data32, &data32->value); +} + +static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file, + struct snd_ctl_elem_value32 __user *data32) +{ + return ctl_elem_write_user(file, data32, &data32->value); +} + +#ifdef CONFIG_X86_X32 +static int snd_ctl_elem_read_user_x32(struct snd_card *card, + struct snd_ctl_elem_value_x32 __user *data32) +{ + return ctl_elem_read_user(card, data32, &data32->value); +} + +static int snd_ctl_elem_write_user_x32(struct snd_ctl_file *file, + struct snd_ctl_elem_value_x32 __user *data32) +{ + return ctl_elem_write_user(file, data32, &data32->value); +} +#endif /* CONFIG_X86_X32 */ + +/* add or replace a user control */ +static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, + struct snd_ctl_elem_info32 __user *data32, + int replace) +{ + struct snd_ctl_elem_info *data; + int err; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (! data) + return -ENOMEM; + + err = -EFAULT; + /* id, type, access, count */ \ + if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) || + copy_from_user(&data->type, &data32->type, 3 * sizeof(u32))) + goto error; + if (get_user(data->owner, &data32->owner)) + goto error; + switch (data->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + if (get_user(data->value.integer.min, &data32->value.integer.min) || + get_user(data->value.integer.max, &data32->value.integer.max) || + get_user(data->value.integer.step, &data32->value.integer.step)) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + if (copy_from_user(&data->value.integer64, + &data32->value.integer64, + sizeof(data->value.integer64))) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + if (copy_from_user(&data->value.enumerated, + &data32->value.enumerated, + sizeof(data->value.enumerated))) + goto error; + data->value.enumerated.names_ptr = + (uintptr_t)compat_ptr(data->value.enumerated.names_ptr); + break; + default: + break; + } + err = snd_ctl_elem_add(file, data, replace); + error: + kfree(data); + return err; +} + +enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), + SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct snd_ctl_elem_info32), + SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct snd_ctl_elem_value32), + SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32), + SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32), + SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32), +#ifdef CONFIG_X86_X32 + SNDRV_CTL_IOCTL_ELEM_READ_X32 = _IOWR('U', 0x12, struct snd_ctl_elem_value_x32), + SNDRV_CTL_IOCTL_ELEM_WRITE_X32 = _IOWR('U', 0x13, struct snd_ctl_elem_value_x32), +#endif /* CONFIG_X86_X32 */ +}; + +static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_ctl_file *ctl; + struct snd_kctl_ioctl *p; + void __user *argp = compat_ptr(arg); + int err; + + ctl = file->private_data; + if (snd_BUG_ON(!ctl || !ctl->card)) + return -ENXIO; + + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: + case SNDRV_CTL_IOCTL_ELEM_LOCK: + case SNDRV_CTL_IOCTL_ELEM_UNLOCK: + case SNDRV_CTL_IOCTL_ELEM_REMOVE: + case SNDRV_CTL_IOCTL_TLV_READ: + case SNDRV_CTL_IOCTL_TLV_WRITE: + case SNDRV_CTL_IOCTL_TLV_COMMAND: + return snd_ctl_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_CTL_IOCTL_ELEM_LIST32: + return snd_ctl_elem_list_compat(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO32: + return snd_ctl_elem_info_compat(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_READ32: + return snd_ctl_elem_read_user_compat(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE32: + return snd_ctl_elem_write_user_compat(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_ADD32: + return snd_ctl_elem_add_compat(ctl, argp, 0); + case SNDRV_CTL_IOCTL_ELEM_REPLACE32: + return snd_ctl_elem_add_compat(ctl, argp, 1); +#ifdef CONFIG_X86_X32 + case SNDRV_CTL_IOCTL_ELEM_READ_X32: + return snd_ctl_elem_read_user_x32(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE_X32: + return snd_ctl_elem_write_user_x32(ctl, argp); +#endif /* CONFIG_X86_X32 */ + } + + down_read(&snd_ioctl_rwsem); + list_for_each_entry(p, &snd_control_compat_ioctls, list) { + if (p->fioctl) { + err = p->fioctl(ctl->card, ctl, cmd, arg); + if (err != -ENOIOCTLCMD) { + up_read(&snd_ioctl_rwsem); + return err; + } + } + } + up_read(&snd_ioctl_rwsem); + return -ENOIOCTLCMD; +} diff --git a/sound/core/ctljack.c b/sound/core/ctljack.c new file mode 100644 index 000000000..0249d5e6a --- /dev/null +++ b/sound/core/ctljack.c @@ -0,0 +1,88 @@ +/* + * Helper functions for jack-detection kcontrols + * + * Copyright (c) 2011 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include +#include +#include +#include + +#define jack_detect_kctl_info snd_ctl_boolean_mono_info + +static int jack_detect_kctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +static const struct snd_kcontrol_new jack_detect_kctl = { + /* name is filled later */ + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = jack_detect_kctl_info, + .get = jack_detect_kctl_get, +}; + +static int get_available_index(struct snd_card *card, const char *name) +{ + struct snd_ctl_elem_id sid; + + memset(&sid, 0, sizeof(sid)); + + sid.index = 0; + sid.iface = SNDRV_CTL_ELEM_IFACE_CARD; + strlcpy(sid.name, name, sizeof(sid.name)); + + while (snd_ctl_find_id(card, &sid)) { + sid.index++; + /* reset numid; otherwise snd_ctl_find_id() hits this again */ + sid.numid = 0; + } + + return sid.index; +} + +static void jack_kctl_name_gen(char *name, const char *src_name, int size) +{ + size_t count = strlen(src_name); + bool need_cat = true; + + /* remove redundant " Jack" from src_name */ + if (count >= 5) + need_cat = strncmp(&src_name[count - 5], " Jack", 5) ? true : false; + + snprintf(name, size, need_cat ? "%s Jack" : "%s", src_name); + +} + +struct snd_kcontrol * +snd_kctl_jack_new(const char *name, struct snd_card *card) +{ + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1(&jack_detect_kctl, NULL); + if (!kctl) + return NULL; + + jack_kctl_name_gen(kctl->id.name, name, sizeof(kctl->id.name)); + kctl->id.index = get_available_index(card, kctl->id.name); + kctl->private_value = 0; + return kctl; +} + +void snd_kctl_jack_report(struct snd_card *card, + struct snd_kcontrol *kctl, bool status) +{ + if (kctl->private_value == status) + return; + kctl->private_value = status; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); +} diff --git a/sound/core/device.c b/sound/core/device.c new file mode 100644 index 000000000..535102d56 --- /dev/null +++ b/sound/core/device.c @@ -0,0 +1,254 @@ +/* + * Device management routines + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +/** + * snd_device_new - create an ALSA device component + * @card: the card instance + * @type: the device type, SNDRV_DEV_XXX + * @device_data: the data pointer of this device + * @ops: the operator table + * + * Creates a new device component for the given data pointer. + * The device will be assigned to the card and managed together + * by the card. + * + * The data pointer plays a role as the identifier, too, so the + * pointer address must be unique and unchanged. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_device_new(struct snd_card *card, enum snd_device_type type, + void *device_data, struct snd_device_ops *ops) +{ + struct snd_device *dev; + struct list_head *p; + + if (snd_BUG_ON(!card || !device_data || !ops)) + return -ENXIO; + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + INIT_LIST_HEAD(&dev->list); + dev->card = card; + dev->type = type; + dev->state = SNDRV_DEV_BUILD; + dev->device_data = device_data; + dev->ops = ops; + + /* insert the entry in an incrementally sorted list */ + list_for_each_prev(p, &card->devices) { + struct snd_device *pdev = list_entry(p, struct snd_device, list); + if ((unsigned int)pdev->type <= (unsigned int)type) + break; + } + + list_add(&dev->list, p); + return 0; +} +EXPORT_SYMBOL(snd_device_new); + +static void __snd_device_disconnect(struct snd_device *dev) +{ + if (dev->state == SNDRV_DEV_REGISTERED) { + if (dev->ops->dev_disconnect && + dev->ops->dev_disconnect(dev)) + dev_err(dev->card->dev, "device disconnect failure\n"); + dev->state = SNDRV_DEV_DISCONNECTED; + } +} + +static void __snd_device_free(struct snd_device *dev) +{ + /* unlink */ + list_del(&dev->list); + + __snd_device_disconnect(dev); + if (dev->ops->dev_free) { + if (dev->ops->dev_free(dev)) + dev_err(dev->card->dev, "device free failure\n"); + } + kfree(dev); +} + +static struct snd_device *look_for_dev(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + list_for_each_entry(dev, &card->devices, list) + if (dev->device_data == device_data) + return dev; + + return NULL; +} + +/** + * snd_device_disconnect - disconnect the device + * @card: the card instance + * @device_data: the data pointer to disconnect + * + * Turns the device into the disconnection state, invoking + * dev_disconnect callback, if the device was already registered. + * + * Usually called from snd_card_disconnect(). + * + * Return: Zero if successful, or a negative error code on failure or if the + * device not found. + */ +void snd_device_disconnect(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card || !device_data)) + return; + dev = look_for_dev(card, device_data); + if (dev) + __snd_device_disconnect(dev); + else + dev_dbg(card->dev, "device disconnect %p (from %pS), not found\n", + device_data, __builtin_return_address(0)); +} +EXPORT_SYMBOL_GPL(snd_device_disconnect); + +/** + * snd_device_free - release the device from the card + * @card: the card instance + * @device_data: the data pointer to release + * + * Removes the device from the list on the card and invokes the + * callbacks, dev_disconnect and dev_free, corresponding to the state. + * Then release the device. + */ +void snd_device_free(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card || !device_data)) + return; + dev = look_for_dev(card, device_data); + if (dev) + __snd_device_free(dev); + else + dev_dbg(card->dev, "device free %p (from %pS), not found\n", + device_data, __builtin_return_address(0)); +} +EXPORT_SYMBOL(snd_device_free); + +static int __snd_device_register(struct snd_device *dev) +{ + if (dev->state == SNDRV_DEV_BUILD) { + if (dev->ops->dev_register) { + int err = dev->ops->dev_register(dev); + if (err < 0) + return err; + } + dev->state = SNDRV_DEV_REGISTERED; + } + return 0; +} + +/** + * snd_device_register - register the device + * @card: the card instance + * @device_data: the data pointer to register + * + * Registers the device which was already created via + * snd_device_new(). Usually this is called from snd_card_register(), + * but it can be called later if any new devices are created after + * invocation of snd_card_register(). + * + * Return: Zero if successful, or a negative error code on failure or if the + * device not found. + */ +int snd_device_register(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card || !device_data)) + return -ENXIO; + dev = look_for_dev(card, device_data); + if (dev) + return __snd_device_register(dev); + snd_BUG(); + return -ENXIO; +} +EXPORT_SYMBOL(snd_device_register); + +/* + * register all the devices on the card. + * called from init.c + */ +int snd_device_register_all(struct snd_card *card) +{ + struct snd_device *dev; + int err; + + if (snd_BUG_ON(!card)) + return -ENXIO; + list_for_each_entry(dev, &card->devices, list) { + err = __snd_device_register(dev); + if (err < 0) + return err; + } + return 0; +} + +/* + * disconnect all the devices on the card. + * called from init.c + */ +void snd_device_disconnect_all(struct snd_card *card) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card)) + return; + list_for_each_entry_reverse(dev, &card->devices, list) + __snd_device_disconnect(dev); +} + +/* + * release all the devices on the card. + * called from init.c + */ +void snd_device_free_all(struct snd_card *card) +{ + struct snd_device *dev, *next; + + if (snd_BUG_ON(!card)) + return; + list_for_each_entry_safe_reverse(dev, next, &card->devices, list) { + /* exception: free ctl and lowlevel stuff later */ + if (dev->type == SNDRV_DEV_CONTROL || + dev->type == SNDRV_DEV_LOWLEVEL) + continue; + __snd_device_free(dev); + } + + /* free all */ + list_for_each_entry_safe_reverse(dev, next, &card->devices, list) + __snd_device_free(dev); +} diff --git a/sound/core/hrtimer.c b/sound/core/hrtimer.c new file mode 100644 index 000000000..18cb6f476 --- /dev/null +++ b/sound/core/hrtimer.c @@ -0,0 +1,183 @@ +/* + * ALSA timer back-end using hrtimer + * Copyright (C) 2008 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("ALSA hrtimer backend"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER)); + +#define NANO_SEC 1000000000UL /* 10^9 in sec */ +static unsigned int resolution; + +struct snd_hrtimer { + struct snd_timer *timer; + struct hrtimer hrt; + bool in_callback; +}; + +static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) +{ + struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt); + struct snd_timer *t = stime->timer; + ktime_t delta; + unsigned long ticks; + enum hrtimer_restart ret = HRTIMER_NORESTART; + + spin_lock(&t->lock); + if (!t->running) + goto out; /* fast path */ + stime->in_callback = true; + ticks = t->sticks; + spin_unlock(&t->lock); + + /* calculate the drift */ + delta = ktime_sub(hrt->base->get_time(), hrtimer_get_expires(hrt)); + if (delta > 0) + ticks += ktime_divns(delta, ticks * resolution); + + snd_timer_interrupt(stime->timer, ticks); + + spin_lock(&t->lock); + if (t->running) { + hrtimer_add_expires_ns(hrt, t->sticks * resolution); + ret = HRTIMER_RESTART; + } + + stime->in_callback = false; + out: + spin_unlock(&t->lock); + return ret; +} + +static int snd_hrtimer_open(struct snd_timer *t) +{ + struct snd_hrtimer *stime; + + stime = kzalloc(sizeof(*stime), GFP_KERNEL); + if (!stime) + return -ENOMEM; + hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + stime->timer = t; + stime->hrt.function = snd_hrtimer_callback; + t->private_data = stime; + return 0; +} + +static int snd_hrtimer_close(struct snd_timer *t) +{ + struct snd_hrtimer *stime = t->private_data; + + if (stime) { + spin_lock_irq(&t->lock); + t->running = 0; /* just to be sure */ + stime->in_callback = 1; /* skip start/stop */ + spin_unlock_irq(&t->lock); + + hrtimer_cancel(&stime->hrt); + kfree(stime); + t->private_data = NULL; + } + return 0; +} + +static int snd_hrtimer_start(struct snd_timer *t) +{ + struct snd_hrtimer *stime = t->private_data; + + if (stime->in_callback) + return 0; + hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution), + HRTIMER_MODE_REL); + return 0; +} + +static int snd_hrtimer_stop(struct snd_timer *t) +{ + struct snd_hrtimer *stime = t->private_data; + + if (stime->in_callback) + return 0; + hrtimer_try_to_cancel(&stime->hrt); + return 0; +} + +static const struct snd_timer_hardware hrtimer_hw __initconst = { + .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_TASKLET, + .open = snd_hrtimer_open, + .close = snd_hrtimer_close, + .start = snd_hrtimer_start, + .stop = snd_hrtimer_stop, +}; + +/* + * entry functions + */ + +static struct snd_timer *mytimer; + +static int __init snd_hrtimer_init(void) +{ + struct snd_timer *timer; + int err; + + resolution = hrtimer_resolution; + + /* Create a new timer and set up the fields */ + err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER, + &timer); + if (err < 0) + return err; + + timer->module = THIS_MODULE; + strcpy(timer->name, "HR timer"); + timer->hw = hrtimer_hw; + timer->hw.resolution = resolution; + timer->hw.ticks = NANO_SEC / resolution; + timer->max_instances = 100; /* lower the limit */ + + err = snd_timer_global_register(timer); + if (err < 0) { + snd_timer_global_free(timer); + return err; + } + mytimer = timer; /* remember this */ + + return 0; +} + +static void __exit snd_hrtimer_exit(void) +{ + if (mytimer) { + snd_timer_global_free(mytimer); + mytimer = NULL; + } +} + +module_init(snd_hrtimer_init); +module_exit(snd_hrtimer_exit); diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c new file mode 100644 index 000000000..600ab2eb1 --- /dev/null +++ b/sound/core/hwdep.c @@ -0,0 +1,551 @@ +/* + * Hardware dependent layer + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Hardware dependent layer"); +MODULE_LICENSE("GPL"); + +static LIST_HEAD(snd_hwdep_devices); +static DEFINE_MUTEX(register_mutex); + +static int snd_hwdep_dev_free(struct snd_device *device); +static int snd_hwdep_dev_register(struct snd_device *device); +static int snd_hwdep_dev_disconnect(struct snd_device *device); + + +static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device) +{ + struct snd_hwdep *hwdep; + + list_for_each_entry(hwdep, &snd_hwdep_devices, list) + if (hwdep->card == card && hwdep->device == device) + return hwdep; + return NULL; +} + +static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.llseek) + return hw->ops.llseek(hw, file, offset, orig); + return -ENXIO; +} + +static ssize_t snd_hwdep_read(struct file * file, char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.read) + return hw->ops.read(hw, buf, count, offset); + return -ENXIO; +} + +static ssize_t snd_hwdep_write(struct file * file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.write) + return hw->ops.write(hw, buf, count, offset); + return -ENXIO; +} + +static int snd_hwdep_open(struct inode *inode, struct file * file) +{ + int major = imajor(inode); + struct snd_hwdep *hw; + int err; + wait_queue_entry_t wait; + + if (major == snd_major) { + hw = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_HWDEP); +#ifdef CONFIG_SND_OSSEMUL + } else if (major == SOUND_MAJOR) { + hw = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_DMFM); +#endif + } else + return -ENXIO; + if (hw == NULL) + return -ENODEV; + + if (!try_module_get(hw->card->module)) { + snd_card_unref(hw->card); + return -EFAULT; + } + + init_waitqueue_entry(&wait, current); + add_wait_queue(&hw->open_wait, &wait); + mutex_lock(&hw->open_mutex); + while (1) { + if (hw->exclusive && hw->used > 0) { + err = -EBUSY; + break; + } + if (!hw->ops.open) { + err = 0; + break; + } + err = hw->ops.open(hw, file); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&hw->open_mutex); + schedule(); + mutex_lock(&hw->open_mutex); + if (hw->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&hw->open_wait, &wait); + if (err >= 0) { + err = snd_card_file_add(hw->card, file); + if (err >= 0) { + file->private_data = hw; + hw->used++; + } else { + if (hw->ops.release) + hw->ops.release(hw, file); + } + } + mutex_unlock(&hw->open_mutex); + if (err < 0) + module_put(hw->card->module); + snd_card_unref(hw->card); + return err; +} + +static int snd_hwdep_release(struct inode *inode, struct file * file) +{ + int err = 0; + struct snd_hwdep *hw = file->private_data; + struct module *mod = hw->card->module; + + mutex_lock(&hw->open_mutex); + if (hw->ops.release) + err = hw->ops.release(hw, file); + if (hw->used > 0) + hw->used--; + mutex_unlock(&hw->open_mutex); + wake_up(&hw->open_wait); + + snd_card_file_remove(hw->card, file); + module_put(mod); + return err; +} + +static __poll_t snd_hwdep_poll(struct file * file, poll_table * wait) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.poll) + return hw->ops.poll(hw, file, wait); + return 0; +} + +static int snd_hwdep_info(struct snd_hwdep *hw, + struct snd_hwdep_info __user *_info) +{ + struct snd_hwdep_info info; + + memset(&info, 0, sizeof(info)); + info.card = hw->card->number; + strlcpy(info.id, hw->id, sizeof(info.id)); + strlcpy(info.name, hw->name, sizeof(info.name)); + info.iface = hw->iface; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_hwdep_dsp_status(struct snd_hwdep *hw, + struct snd_hwdep_dsp_status __user *_info) +{ + struct snd_hwdep_dsp_status info; + int err; + + if (! hw->ops.dsp_status) + return -ENXIO; + memset(&info, 0, sizeof(info)); + info.dsp_loaded = hw->dsp_loaded; + if ((err = hw->ops.dsp_status(hw, &info)) < 0) + return err; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_hwdep_dsp_load(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image __user *_info) +{ + struct snd_hwdep_dsp_image info; + int err; + + if (! hw->ops.dsp_load) + return -ENXIO; + memset(&info, 0, sizeof(info)); + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + if (info.index >= 32) + return -EINVAL; + /* check whether the dsp was already loaded */ + if (hw->dsp_loaded & (1u << info.index)) + return -EBUSY; + err = hw->ops.dsp_load(hw, &info); + if (err < 0) + return err; + hw->dsp_loaded |= (1u << info.index); + return 0; +} + +static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, + unsigned long arg) +{ + struct snd_hwdep *hw = file->private_data; + void __user *argp = (void __user *)arg; + switch (cmd) { + case SNDRV_HWDEP_IOCTL_PVERSION: + return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp); + case SNDRV_HWDEP_IOCTL_INFO: + return snd_hwdep_info(hw, argp); + case SNDRV_HWDEP_IOCTL_DSP_STATUS: + return snd_hwdep_dsp_status(hw, argp); + case SNDRV_HWDEP_IOCTL_DSP_LOAD: + return snd_hwdep_dsp_load(hw, argp); + } + if (hw->ops.ioctl) + return hw->ops.ioctl(hw, file, cmd, arg); + return -ENOTTY; +} + +static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.mmap) + return hw->ops.mmap(hw, file, vma); + return -ENXIO; +} + +static int snd_hwdep_control_ioctl(struct snd_card *card, + struct snd_ctl_file * control, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)arg)) + return -EFAULT; + mutex_lock(®ister_mutex); + + if (device < 0) + device = 0; + else if (device < SNDRV_MINOR_HWDEPS) + device++; + else + device = SNDRV_MINOR_HWDEPS; + + while (device < SNDRV_MINOR_HWDEPS) { + if (snd_hwdep_search(card, device)) + break; + device++; + } + if (device >= SNDRV_MINOR_HWDEPS) + device = -1; + mutex_unlock(®ister_mutex); + if (put_user(device, (int __user *)arg)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_HWDEP_INFO: + { + struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg; + int device, err; + struct snd_hwdep *hwdep; + + if (get_user(device, &info->device)) + return -EFAULT; + mutex_lock(®ister_mutex); + hwdep = snd_hwdep_search(card, device); + if (hwdep) + err = snd_hwdep_info(hwdep, info); + else + err = -ENXIO; + mutex_unlock(®ister_mutex); + return err; + } + } + return -ENOIOCTLCMD; +} + +#ifdef CONFIG_COMPAT +#include "hwdep_compat.c" +#else +#define snd_hwdep_ioctl_compat NULL +#endif + +/* + + */ + +static const struct file_operations snd_hwdep_f_ops = +{ + .owner = THIS_MODULE, + .llseek = snd_hwdep_llseek, + .read = snd_hwdep_read, + .write = snd_hwdep_write, + .open = snd_hwdep_open, + .release = snd_hwdep_release, + .poll = snd_hwdep_poll, + .unlocked_ioctl = snd_hwdep_ioctl, + .compat_ioctl = snd_hwdep_ioctl_compat, + .mmap = snd_hwdep_mmap, +}; + +static void release_hwdep_device(struct device *dev) +{ + kfree(container_of(dev, struct snd_hwdep, dev)); +} + +/** + * snd_hwdep_new - create a new hwdep instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero-based) + * @rhwdep: the pointer to store the new hwdep instance + * + * Creates a new hwdep instance with the given index on the card. + * The callbacks (hwdep->ops) must be set on the returned instance + * after this call manually by the caller. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_hwdep_new(struct snd_card *card, char *id, int device, + struct snd_hwdep **rhwdep) +{ + struct snd_hwdep *hwdep; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_hwdep_dev_free, + .dev_register = snd_hwdep_dev_register, + .dev_disconnect = snd_hwdep_dev_disconnect, + }; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (rhwdep) + *rhwdep = NULL; + hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL); + if (!hwdep) + return -ENOMEM; + + init_waitqueue_head(&hwdep->open_wait); + mutex_init(&hwdep->open_mutex); + hwdep->card = card; + hwdep->device = device; + if (id) + strlcpy(hwdep->id, id, sizeof(hwdep->id)); + + snd_device_initialize(&hwdep->dev, card); + hwdep->dev.release = release_hwdep_device; + dev_set_name(&hwdep->dev, "hwC%iD%i", card->number, device); +#ifdef CONFIG_SND_OSSEMUL + hwdep->oss_type = -1; +#endif + + err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops); + if (err < 0) { + put_device(&hwdep->dev); + return err; + } + + if (rhwdep) + *rhwdep = hwdep; + return 0; +} +EXPORT_SYMBOL(snd_hwdep_new); + +static int snd_hwdep_dev_free(struct snd_device *device) +{ + struct snd_hwdep *hwdep = device->device_data; + if (!hwdep) + return 0; + if (hwdep->private_free) + hwdep->private_free(hwdep); + put_device(&hwdep->dev); + return 0; +} + +static int snd_hwdep_dev_register(struct snd_device *device) +{ + struct snd_hwdep *hwdep = device->device_data; + struct snd_card *card = hwdep->card; + int err; + + mutex_lock(®ister_mutex); + if (snd_hwdep_search(card, hwdep->device)) { + mutex_unlock(®ister_mutex); + return -EBUSY; + } + list_add_tail(&hwdep->list, &snd_hwdep_devices); + err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP, + hwdep->card, hwdep->device, + &snd_hwdep_f_ops, hwdep, &hwdep->dev); + if (err < 0) { + dev_err(&hwdep->dev, "unable to register\n"); + list_del(&hwdep->list); + mutex_unlock(®ister_mutex); + return err; + } + +#ifdef CONFIG_SND_OSSEMUL + hwdep->ossreg = 0; + if (hwdep->oss_type >= 0) { + if (hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM && + hwdep->device) + dev_warn(&hwdep->dev, + "only hwdep device 0 can be registered as OSS direct FM device!\n"); + else if (snd_register_oss_device(hwdep->oss_type, + card, hwdep->device, + &snd_hwdep_f_ops, hwdep) < 0) + dev_warn(&hwdep->dev, + "unable to register OSS compatibility device\n"); + else + hwdep->ossreg = 1; + } +#endif + mutex_unlock(®ister_mutex); + return 0; +} + +static int snd_hwdep_dev_disconnect(struct snd_device *device) +{ + struct snd_hwdep *hwdep = device->device_data; + + if (snd_BUG_ON(!hwdep)) + return -ENXIO; + mutex_lock(®ister_mutex); + if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) { + mutex_unlock(®ister_mutex); + return -EINVAL; + } + mutex_lock(&hwdep->open_mutex); + wake_up(&hwdep->open_wait); +#ifdef CONFIG_SND_OSSEMUL + if (hwdep->ossreg) + snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device); +#endif + snd_unregister_device(&hwdep->dev); + list_del_init(&hwdep->list); + mutex_unlock(&hwdep->open_mutex); + mutex_unlock(®ister_mutex); + return 0; +} + +#ifdef CONFIG_SND_PROC_FS +/* + * Info interface + */ + +static void snd_hwdep_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_hwdep *hwdep; + + mutex_lock(®ister_mutex); + list_for_each_entry(hwdep, &snd_hwdep_devices, list) + snd_iprintf(buffer, "%02i-%02i: %s\n", + hwdep->card->number, hwdep->device, hwdep->name); + mutex_unlock(®ister_mutex); +} + +static struct snd_info_entry *snd_hwdep_proc_entry; + +static void __init snd_hwdep_proc_init(void) +{ + struct snd_info_entry *entry; + + if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) { + entry->c.text.read = snd_hwdep_proc_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_hwdep_proc_entry = entry; +} + +static void __exit snd_hwdep_proc_done(void) +{ + snd_info_free_entry(snd_hwdep_proc_entry); +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_hwdep_proc_init() +#define snd_hwdep_proc_done() +#endif /* CONFIG_SND_PROC_FS */ + + +/* + * ENTRY functions + */ + +static int __init alsa_hwdep_init(void) +{ + snd_hwdep_proc_init(); + snd_ctl_register_ioctl(snd_hwdep_control_ioctl); + snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl); + return 0; +} + +static void __exit alsa_hwdep_exit(void) +{ + snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl); + snd_hwdep_proc_done(); +} + +module_init(alsa_hwdep_init) +module_exit(alsa_hwdep_exit) diff --git a/sound/core/hwdep_compat.c b/sound/core/hwdep_compat.c new file mode 100644 index 000000000..3827c0cee --- /dev/null +++ b/sound/core/hwdep_compat.c @@ -0,0 +1,78 @@ +/* + * 32bit -> 64bit ioctl wrapper for hwdep API + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file is included from hwdep.c */ + +#include + +struct snd_hwdep_dsp_image32 { + u32 index; + unsigned char name[64]; + u32 image; /* pointer */ + u32 length; + u32 driver_data; +} /* don't set packed attribute here */; + +static int snd_hwdep_dsp_load_compat(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image32 __user *src) +{ + struct snd_hwdep_dsp_image __user *dst; + compat_caddr_t ptr; + u32 val; + + dst = compat_alloc_user_space(sizeof(*dst)); + + /* index and name */ + if (copy_in_user(dst, src, 4 + 64)) + return -EFAULT; + if (get_user(ptr, &src->image) || + put_user(compat_ptr(ptr), &dst->image)) + return -EFAULT; + if (get_user(val, &src->length) || + put_user(val, &dst->length)) + return -EFAULT; + if (get_user(val, &src->driver_data) || + put_user(val, &dst->driver_data)) + return -EFAULT; + + return snd_hwdep_dsp_load(hw, dst); +} + +enum { + SNDRV_HWDEP_IOCTL_DSP_LOAD32 = _IOW('H', 0x03, struct snd_hwdep_dsp_image32) +}; + +static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd, + unsigned long arg) +{ + struct snd_hwdep *hw = file->private_data; + void __user *argp = compat_ptr(arg); + switch (cmd) { + case SNDRV_HWDEP_IOCTL_PVERSION: + case SNDRV_HWDEP_IOCTL_INFO: + case SNDRV_HWDEP_IOCTL_DSP_STATUS: + return snd_hwdep_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_HWDEP_IOCTL_DSP_LOAD32: + return snd_hwdep_dsp_load_compat(hw, argp); + } + if (hw->ops.ioctl_compat) + return hw->ops.ioctl_compat(hw, file, cmd, arg); + return -ENOIOCTLCMD; +} diff --git a/sound/core/info.c b/sound/core/info.c new file mode 100644 index 000000000..3fa833679 --- /dev/null +++ b/sound/core/info.c @@ -0,0 +1,888 @@ +/* + * Information interface for ALSA driver + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int snd_info_check_reserved_words(const char *str) +{ + static char *reserved[] = + { + "version", + "meminfo", + "memdebug", + "detect", + "devices", + "oss", + "cards", + "timers", + "synth", + "pcm", + "seq", + NULL + }; + char **xstr = reserved; + + while (*xstr) { + if (!strcmp(*xstr, str)) + return 0; + xstr++; + } + if (!strncmp(str, "card", 4)) + return 0; + return 1; +} + +static DEFINE_MUTEX(info_mutex); + +struct snd_info_private_data { + struct snd_info_buffer *rbuffer; + struct snd_info_buffer *wbuffer; + struct snd_info_entry *entry; + void *file_private_data; +}; + +static int snd_info_version_init(void); +static void snd_info_disconnect(struct snd_info_entry *entry); + +/* + + */ + +static struct snd_info_entry *snd_proc_root; +struct snd_info_entry *snd_seq_root; +EXPORT_SYMBOL(snd_seq_root); + +#ifdef CONFIG_SND_OSSEMUL +struct snd_info_entry *snd_oss_root; +#endif + +static int alloc_info_private(struct snd_info_entry *entry, + struct snd_info_private_data **ret) +{ + struct snd_info_private_data *data; + + if (!entry || !entry->p) + return -ENODEV; + if (!try_module_get(entry->module)) + return -EFAULT; + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + module_put(entry->module); + return -ENOMEM; + } + data->entry = entry; + *ret = data; + return 0; +} + +static bool valid_pos(loff_t pos, size_t count) +{ + if (pos < 0 || (long) pos != pos || (ssize_t) count < 0) + return false; + if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos) + return false; + return true; +} + +/* + * file ops for binary proc files + */ +static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig) +{ + struct snd_info_private_data *data; + struct snd_info_entry *entry; + loff_t ret = -EINVAL, size; + + data = file->private_data; + entry = data->entry; + mutex_lock(&entry->access); + if (entry->c.ops->llseek) { + offset = entry->c.ops->llseek(entry, + data->file_private_data, + file, offset, orig); + goto out; + } + + size = entry->size; + switch (orig) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += file->f_pos; + break; + case SEEK_END: + if (!size) + goto out; + offset += size; + break; + default: + goto out; + } + if (offset < 0) + goto out; + if (size && offset > size) + offset = size; + file->f_pos = offset; + ret = offset; + out: + mutex_unlock(&entry->access); + return ret; +} + +static ssize_t snd_info_entry_read(struct file *file, char __user *buffer, + size_t count, loff_t * offset) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + size_t size; + loff_t pos; + + pos = *offset; + if (!valid_pos(pos, count)) + return -EIO; + if (pos >= entry->size) + return 0; + size = entry->size - pos; + size = min(count, size); + size = entry->c.ops->read(entry, data->file_private_data, + file, buffer, size, pos); + if ((ssize_t) size > 0) + *offset = pos + size; + return size; +} + +static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer, + size_t count, loff_t * offset) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + ssize_t size = 0; + loff_t pos; + + pos = *offset; + if (!valid_pos(pos, count)) + return -EIO; + if (count > 0) { + size_t maxsize = entry->size - pos; + count = min(count, maxsize); + size = entry->c.ops->write(entry, data->file_private_data, + file, buffer, count, pos); + } + if (size > 0) + *offset = pos + size; + return size; +} + +static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + __poll_t mask = 0; + + if (entry->c.ops->poll) + return entry->c.ops->poll(entry, + data->file_private_data, + file, wait); + if (entry->c.ops->read) + mask |= EPOLLIN | EPOLLRDNORM; + if (entry->c.ops->write) + mask |= EPOLLOUT | EPOLLWRNORM; + return mask; +} + +static long snd_info_entry_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + + if (!entry->c.ops->ioctl) + return -ENOTTY; + return entry->c.ops->ioctl(entry, data->file_private_data, + file, cmd, arg); +} + +static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct inode *inode = file_inode(file); + struct snd_info_private_data *data; + struct snd_info_entry *entry; + + data = file->private_data; + if (data == NULL) + return 0; + entry = data->entry; + if (!entry->c.ops->mmap) + return -ENXIO; + return entry->c.ops->mmap(entry, data->file_private_data, + inode, file, vma); +} + +static int snd_info_entry_open(struct inode *inode, struct file *file) +{ + struct snd_info_entry *entry = PDE_DATA(inode); + struct snd_info_private_data *data; + int mode, err; + + mutex_lock(&info_mutex); + err = alloc_info_private(entry, &data); + if (err < 0) + goto unlock; + + mode = file->f_flags & O_ACCMODE; + if (((mode == O_RDONLY || mode == O_RDWR) && !entry->c.ops->read) || + ((mode == O_WRONLY || mode == O_RDWR) && !entry->c.ops->write)) { + err = -ENODEV; + goto error; + } + + if (entry->c.ops->open) { + err = entry->c.ops->open(entry, mode, &data->file_private_data); + if (err < 0) + goto error; + } + + file->private_data = data; + mutex_unlock(&info_mutex); + return 0; + + error: + kfree(data); + module_put(entry->module); + unlock: + mutex_unlock(&info_mutex); + return err; +} + +static int snd_info_entry_release(struct inode *inode, struct file *file) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + + if (entry->c.ops->release) + entry->c.ops->release(entry, file->f_flags & O_ACCMODE, + data->file_private_data); + module_put(entry->module); + kfree(data); + return 0; +} + +static const struct file_operations snd_info_entry_operations = +{ + .owner = THIS_MODULE, + .llseek = snd_info_entry_llseek, + .read = snd_info_entry_read, + .write = snd_info_entry_write, + .poll = snd_info_entry_poll, + .unlocked_ioctl = snd_info_entry_ioctl, + .mmap = snd_info_entry_mmap, + .open = snd_info_entry_open, + .release = snd_info_entry_release, +}; + +/* + * file ops for text proc files + */ +static ssize_t snd_info_text_entry_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *offset) +{ + struct seq_file *m = file->private_data; + struct snd_info_private_data *data = m->private; + struct snd_info_entry *entry = data->entry; + struct snd_info_buffer *buf; + loff_t pos; + size_t next; + int err = 0; + + if (!entry->c.text.write) + return -EIO; + pos = *offset; + if (!valid_pos(pos, count)) + return -EIO; + next = pos + count; + /* don't handle too large text inputs */ + if (next > 16 * 1024) + return -EIO; + mutex_lock(&entry->access); + buf = data->wbuffer; + if (!buf) { + data->wbuffer = buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto error; + } + } + if (next > buf->len) { + char *nbuf = kvzalloc(PAGE_ALIGN(next), GFP_KERNEL); + if (!nbuf) { + err = -ENOMEM; + goto error; + } + kvfree(buf->buffer); + buf->buffer = nbuf; + buf->len = PAGE_ALIGN(next); + } + if (copy_from_user(buf->buffer + pos, buffer, count)) { + err = -EFAULT; + goto error; + } + buf->size = next; + error: + mutex_unlock(&entry->access); + if (err < 0) + return err; + *offset = next; + return count; +} + +static int snd_info_seq_show(struct seq_file *seq, void *p) +{ + struct snd_info_private_data *data = seq->private; + struct snd_info_entry *entry = data->entry; + + if (!entry->c.text.read) { + return -EIO; + } else { + data->rbuffer->buffer = (char *)seq; /* XXX hack! */ + entry->c.text.read(entry, data->rbuffer); + } + return 0; +} + +static int snd_info_text_entry_open(struct inode *inode, struct file *file) +{ + struct snd_info_entry *entry = PDE_DATA(inode); + struct snd_info_private_data *data; + int err; + + mutex_lock(&info_mutex); + err = alloc_info_private(entry, &data); + if (err < 0) + goto unlock; + + data->rbuffer = kzalloc(sizeof(*data->rbuffer), GFP_KERNEL); + if (!data->rbuffer) { + err = -ENOMEM; + goto error; + } + if (entry->size) + err = single_open_size(file, snd_info_seq_show, data, + entry->size); + else + err = single_open(file, snd_info_seq_show, data); + if (err < 0) + goto error; + mutex_unlock(&info_mutex); + return 0; + + error: + kfree(data->rbuffer); + kfree(data); + module_put(entry->module); + unlock: + mutex_unlock(&info_mutex); + return err; +} + +static int snd_info_text_entry_release(struct inode *inode, struct file *file) +{ + struct seq_file *m = file->private_data; + struct snd_info_private_data *data = m->private; + struct snd_info_entry *entry = data->entry; + + if (data->wbuffer && entry->c.text.write) + entry->c.text.write(entry, data->wbuffer); + + single_release(inode, file); + kfree(data->rbuffer); + if (data->wbuffer) { + kvfree(data->wbuffer->buffer); + kfree(data->wbuffer); + } + + module_put(entry->module); + kfree(data); + return 0; +} + +static const struct file_operations snd_info_text_entry_ops = +{ + .owner = THIS_MODULE, + .open = snd_info_text_entry_open, + .release = snd_info_text_entry_release, + .write = snd_info_text_entry_write, + .llseek = seq_lseek, + .read = seq_read, +}; + +static struct snd_info_entry *create_subdir(struct module *mod, + const char *name) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(mod, name, NULL); + if (!entry) + return NULL; + entry->mode = S_IFDIR | 0555; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return NULL; + } + return entry; +} + +static struct snd_info_entry * +snd_info_create_entry(const char *name, struct snd_info_entry *parent); + +int __init snd_info_init(void) +{ + snd_proc_root = snd_info_create_entry("asound", NULL); + if (!snd_proc_root) + return -ENOMEM; + snd_proc_root->mode = S_IFDIR | 0555; + snd_proc_root->p = proc_mkdir("asound", NULL); + if (!snd_proc_root->p) + goto error; +#ifdef CONFIG_SND_OSSEMUL + snd_oss_root = create_subdir(THIS_MODULE, "oss"); + if (!snd_oss_root) + goto error; +#endif +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + snd_seq_root = create_subdir(THIS_MODULE, "seq"); + if (!snd_seq_root) + goto error; +#endif + if (snd_info_version_init() < 0 || + snd_minor_info_init() < 0 || + snd_minor_info_oss_init() < 0 || + snd_card_info_init() < 0 || + snd_info_minor_register() < 0) + goto error; + return 0; + + error: + snd_info_free_entry(snd_proc_root); + return -ENOMEM; +} + +int __exit snd_info_done(void) +{ + snd_info_free_entry(snd_proc_root); + return 0; +} + +/* + * create a card proc file + * called from init.c + */ +int snd_info_card_create(struct snd_card *card) +{ + char str[8]; + struct snd_info_entry *entry; + + if (snd_BUG_ON(!card)) + return -ENXIO; + + sprintf(str, "card%i", card->number); + entry = create_subdir(card->module, str); + if (!entry) + return -ENOMEM; + card->proc_root = entry; + return 0; +} + +/* register all pending info entries */ +static int snd_info_register_recursive(struct snd_info_entry *entry) +{ + struct snd_info_entry *p; + int err; + + if (!entry->p) { + err = snd_info_register(entry); + if (err < 0) + return err; + } + + list_for_each_entry(p, &entry->children, list) { + err = snd_info_register_recursive(p); + if (err < 0) + return err; + } + + return 0; +} + +/* + * register the card proc file + * called from init.c + * can be called multiple times for reinitialization + */ +int snd_info_card_register(struct snd_card *card) +{ + struct proc_dir_entry *p; + int err; + + if (snd_BUG_ON(!card)) + return -ENXIO; + + err = snd_info_register_recursive(card->proc_root); + if (err < 0) + return err; + + if (!strcmp(card->id, card->proc_root->name)) + return 0; + + if (card->proc_root_link) + return 0; + p = proc_symlink(card->id, snd_proc_root->p, card->proc_root->name); + if (!p) + return -ENOMEM; + card->proc_root_link = p; + return 0; +} + +/* + * called on card->id change + */ +void snd_info_card_id_change(struct snd_card *card) +{ + mutex_lock(&info_mutex); + if (card->proc_root_link) { + proc_remove(card->proc_root_link); + card->proc_root_link = NULL; + } + if (strcmp(card->id, card->proc_root->name)) + card->proc_root_link = proc_symlink(card->id, + snd_proc_root->p, + card->proc_root->name); + mutex_unlock(&info_mutex); +} + +/* + * de-register the card proc file + * called from init.c + */ +void snd_info_card_disconnect(struct snd_card *card) +{ + if (!card) + return; + mutex_lock(&info_mutex); + proc_remove(card->proc_root_link); + card->proc_root_link = NULL; + if (card->proc_root) + snd_info_disconnect(card->proc_root); + mutex_unlock(&info_mutex); +} + +/* + * release the card proc file resources + * called from init.c + */ +int snd_info_card_free(struct snd_card *card) +{ + if (!card) + return 0; + snd_info_free_entry(card->proc_root); + card->proc_root = NULL; + return 0; +} + + +/** + * snd_info_get_line - read one line from the procfs buffer + * @buffer: the procfs buffer + * @line: the buffer to store + * @len: the max. buffer size + * + * Reads one line from the buffer and stores the string. + * + * Return: Zero if successful, or 1 if error or EOF. + */ +int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len) +{ + int c = -1; + + if (snd_BUG_ON(!buffer)) + return 1; + if (!buffer->buffer) + return 1; + if (len <= 0 || buffer->stop || buffer->error) + return 1; + while (!buffer->stop) { + c = buffer->buffer[buffer->curr++]; + if (buffer->curr >= buffer->size) + buffer->stop = 1; + if (c == '\n') + break; + if (len > 1) { + len--; + *line++ = c; + } + } + *line = '\0'; + return 0; +} +EXPORT_SYMBOL(snd_info_get_line); + +/** + * snd_info_get_str - parse a string token + * @dest: the buffer to store the string token + * @src: the original string + * @len: the max. length of token - 1 + * + * Parses the original string and copy a token to the given + * string buffer. + * + * Return: The updated pointer of the original string so that + * it can be used for the next call. + */ +const char *snd_info_get_str(char *dest, const char *src, int len) +{ + int c; + + while (*src == ' ' || *src == '\t') + src++; + if (*src == '"' || *src == '\'') { + c = *src++; + while (--len > 0 && *src && *src != c) { + *dest++ = *src++; + } + if (*src == c) + src++; + } else { + while (--len > 0 && *src && *src != ' ' && *src != '\t') { + *dest++ = *src++; + } + } + *dest = 0; + while (*src == ' ' || *src == '\t') + src++; + return src; +} +EXPORT_SYMBOL(snd_info_get_str); + +/* + * snd_info_create_entry - create an info entry + * @name: the proc file name + * @parent: the parent directory + * + * Creates an info entry with the given file name and initializes as + * the default state. + * + * Usually called from other functions such as + * snd_info_create_card_entry(). + * + * Return: The pointer of the new instance, or %NULL on failure. + */ +static struct snd_info_entry * +snd_info_create_entry(const char *name, struct snd_info_entry *parent) +{ + struct snd_info_entry *entry; + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry == NULL) + return NULL; + entry->name = kstrdup(name, GFP_KERNEL); + if (entry->name == NULL) { + kfree(entry); + return NULL; + } + entry->mode = S_IFREG | 0444; + entry->content = SNDRV_INFO_CONTENT_TEXT; + mutex_init(&entry->access); + INIT_LIST_HEAD(&entry->children); + INIT_LIST_HEAD(&entry->list); + entry->parent = parent; + if (parent) { + mutex_lock(&parent->access); + list_add_tail(&entry->list, &parent->children); + mutex_unlock(&parent->access); + } + return entry; +} + +/** + * snd_info_create_module_entry - create an info entry for the given module + * @module: the module pointer + * @name: the file name + * @parent: the parent directory + * + * Creates a new info entry and assigns it to the given module. + * + * Return: The pointer of the new instance, or %NULL on failure. + */ +struct snd_info_entry *snd_info_create_module_entry(struct module * module, + const char *name, + struct snd_info_entry *parent) +{ + struct snd_info_entry *entry = snd_info_create_entry(name, parent); + if (entry) + entry->module = module; + return entry; +} +EXPORT_SYMBOL(snd_info_create_module_entry); + +/** + * snd_info_create_card_entry - create an info entry for the given card + * @card: the card instance + * @name: the file name + * @parent: the parent directory + * + * Creates a new info entry and assigns it to the given card. + * + * Return: The pointer of the new instance, or %NULL on failure. + */ +struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card, + const char *name, + struct snd_info_entry * parent) +{ + struct snd_info_entry *entry = snd_info_create_entry(name, parent); + if (entry) { + entry->module = card->module; + entry->card = card; + } + return entry; +} +EXPORT_SYMBOL(snd_info_create_card_entry); + +static void snd_info_disconnect(struct snd_info_entry *entry) +{ + struct snd_info_entry *p; + + if (!entry->p) + return; + list_for_each_entry(p, &entry->children, list) + snd_info_disconnect(p); + proc_remove(entry->p); + entry->p = NULL; +} + +/** + * snd_info_free_entry - release the info entry + * @entry: the info entry + * + * Releases the info entry. + */ +void snd_info_free_entry(struct snd_info_entry * entry) +{ + struct snd_info_entry *p, *n; + + if (!entry) + return; + if (entry->p) { + mutex_lock(&info_mutex); + snd_info_disconnect(entry); + mutex_unlock(&info_mutex); + } + + /* free all children at first */ + list_for_each_entry_safe(p, n, &entry->children, list) + snd_info_free_entry(p); + + p = entry->parent; + if (p) { + mutex_lock(&p->access); + list_del(&entry->list); + mutex_unlock(&p->access); + } + kfree(entry->name); + if (entry->private_free) + entry->private_free(entry); + kfree(entry); +} +EXPORT_SYMBOL(snd_info_free_entry); + +/** + * snd_info_register - register the info entry + * @entry: the info entry + * + * Registers the proc info entry. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_info_register(struct snd_info_entry * entry) +{ + struct proc_dir_entry *root, *p = NULL; + + if (snd_BUG_ON(!entry)) + return -ENXIO; + root = entry->parent == NULL ? snd_proc_root->p : entry->parent->p; + mutex_lock(&info_mutex); + if (S_ISDIR(entry->mode)) { + p = proc_mkdir_mode(entry->name, entry->mode, root); + if (!p) { + mutex_unlock(&info_mutex); + return -ENOMEM; + } + } else { + const struct file_operations *ops; + if (entry->content == SNDRV_INFO_CONTENT_DATA) + ops = &snd_info_entry_operations; + else + ops = &snd_info_text_entry_ops; + p = proc_create_data(entry->name, entry->mode, root, + ops, entry); + if (!p) { + mutex_unlock(&info_mutex); + return -ENOMEM; + } + proc_set_size(p, entry->size); + } + entry->p = p; + mutex_unlock(&info_mutex); + return 0; +} +EXPORT_SYMBOL(snd_info_register); + +/* + + */ + +static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, + "Advanced Linux Sound Architecture Driver Version k%s.\n", + init_utsname()->release); +} + +static int __init snd_info_version_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL); + if (entry == NULL) + return -ENOMEM; + entry->c.text.read = snd_info_version_read; + return snd_info_register(entry); /* freed in error path */ +} diff --git a/sound/core/info_oss.c b/sound/core/info_oss.c new file mode 100644 index 000000000..f479374b6 --- /dev/null +++ b/sound/core/info_oss.c @@ -0,0 +1,122 @@ +/* + * Information interface for ALSA driver + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * OSS compatible part + */ + +static DEFINE_MUTEX(strings); +static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT]; + +int snd_oss_info_register(int dev, int num, char *string) +{ + char *x; + + if (snd_BUG_ON(dev < 0 || dev >= SNDRV_OSS_INFO_DEV_COUNT)) + return -ENXIO; + if (snd_BUG_ON(num < 0 || num >= SNDRV_CARDS)) + return -ENXIO; + mutex_lock(&strings); + if (string == NULL) { + if ((x = snd_sndstat_strings[num][dev]) != NULL) { + kfree(x); + x = NULL; + } + } else { + x = kstrdup(string, GFP_KERNEL); + if (x == NULL) { + mutex_unlock(&strings); + return -ENOMEM; + } + } + snd_sndstat_strings[num][dev] = x; + mutex_unlock(&strings); + return 0; +} +EXPORT_SYMBOL(snd_oss_info_register); + +static int snd_sndstat_show_strings(struct snd_info_buffer *buf, char *id, int dev) +{ + int idx, ok = -1; + char *str; + + snd_iprintf(buf, "\n%s:", id); + mutex_lock(&strings); + for (idx = 0; idx < SNDRV_CARDS; idx++) { + str = snd_sndstat_strings[idx][dev]; + if (str) { + if (ok < 0) { + snd_iprintf(buf, "\n"); + ok++; + } + snd_iprintf(buf, "%i: %s\n", idx, str); + } + } + mutex_unlock(&strings); + if (ok < 0) + snd_iprintf(buf, " NOT ENABLED IN CONFIG\n"); + return ok; +} + +static void snd_sndstat_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA emulation code)\n"); + snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n", + init_utsname()->sysname, + init_utsname()->nodename, + init_utsname()->release, + init_utsname()->version, + init_utsname()->machine); + snd_iprintf(buffer, "Config options: 0\n"); + snd_iprintf(buffer, "\nInstalled drivers: \n"); + snd_iprintf(buffer, "Type 10: ALSA emulation\n"); + snd_iprintf(buffer, "\nCard config: \n"); + snd_card_info_read_oss(buffer); + snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO); + snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH); + snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI); + snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS); + snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS); +} + +int __init snd_info_minor_register(void) +{ + struct snd_info_entry *entry; + + memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings)); + entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", + snd_oss_root); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_sndstat_proc_read; + return snd_info_register(entry); /* freed in error path */ +} diff --git a/sound/core/init.c b/sound/core/init.c new file mode 100644 index 000000000..3eafa1500 --- /dev/null +++ b/sound/core/init.c @@ -0,0 +1,1035 @@ +/* + * Initialization routines + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* monitor files for graceful shutdown (hotplug) */ +struct snd_monitor_file { + struct file *file; + const struct file_operations *disconnected_f_op; + struct list_head shutdown_list; /* still need to shutdown */ + struct list_head list; /* link of monitor files */ +}; + +static DEFINE_SPINLOCK(shutdown_lock); +static LIST_HEAD(shutdown_files); + +static const struct file_operations snd_shutdown_f_ops; + +/* locked for registering/using */ +static DECLARE_BITMAP(snd_cards_lock, SNDRV_CARDS); +struct snd_card *snd_cards[SNDRV_CARDS]; +EXPORT_SYMBOL(snd_cards); + +static DEFINE_MUTEX(snd_card_mutex); + +static char *slots[SNDRV_CARDS]; +module_param_array(slots, charp, NULL, 0444); +MODULE_PARM_DESC(slots, "Module names assigned to the slots."); + +/* return non-zero if the given index is reserved for the given + * module via slots option + */ +static int module_slot_match(struct module *module, int idx) +{ + int match = 1; +#ifdef MODULE + const char *s1, *s2; + + if (!module || !*module->name || !slots[idx]) + return 0; + + s1 = module->name; + s2 = slots[idx]; + if (*s2 == '!') { + match = 0; /* negative match */ + s2++; + } + /* compare module name strings + * hyphens are handled as equivalent with underscore + */ + for (;;) { + char c1 = *s1++; + char c2 = *s2++; + if (c1 == '-') + c1 = '_'; + if (c2 == '-') + c2 = '_'; + if (c1 != c2) + return !match; + if (!c1) + break; + } +#endif /* MODULE */ + return match; +} + +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) +int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag); +EXPORT_SYMBOL(snd_mixer_oss_notify_callback); +#endif + +#ifdef CONFIG_SND_PROC_FS +static void snd_card_id_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, "%s\n", entry->card->id); +} + +static int init_info_for_card(struct snd_card *card) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(card, "id", card->proc_root); + if (!entry) { + dev_dbg(card->dev, "unable to create card entry\n"); + return -ENOMEM; + } + entry->c.text.read = snd_card_id_read; + card->proc_id = entry; + + return snd_info_card_register(card); +} +#else /* !CONFIG_SND_PROC_FS */ +#define init_info_for_card(card) +#endif + +static int check_empty_slot(struct module *module, int slot) +{ + return !slots[slot] || !*slots[slot]; +} + +/* return an empty slot number (>= 0) found in the given bitmask @mask. + * @mask == -1 == 0xffffffff means: take any free slot up to 32 + * when no slot is available, return the original @mask as is. + */ +static int get_slot_from_bitmask(int mask, int (*check)(struct module *, int), + struct module *module) +{ + int slot; + + for (slot = 0; slot < SNDRV_CARDS; slot++) { + if (slot < 32 && !(mask & (1U << slot))) + continue; + if (!test_bit(slot, snd_cards_lock)) { + if (check(module, slot)) + return slot; /* found */ + } + } + return mask; /* unchanged */ +} + +/* the default release callback set in snd_device_initialize() below; + * this is just NOP for now, as almost all jobs are already done in + * dev_free callback of snd_device chain instead. + */ +static void default_release(struct device *dev) +{ +} + +/** + * snd_device_initialize - Initialize struct device for sound devices + * @dev: device to initialize + * @card: card to assign, optional + */ +void snd_device_initialize(struct device *dev, struct snd_card *card) +{ + device_initialize(dev); + if (card) + dev->parent = &card->card_dev; + dev->class = sound_class; + dev->release = default_release; +} +EXPORT_SYMBOL_GPL(snd_device_initialize); + +static int snd_card_do_free(struct snd_card *card); +static const struct attribute_group card_dev_attr_group; + +static void release_card_device(struct device *dev) +{ + snd_card_do_free(dev_to_snd_card(dev)); +} + +/** + * snd_card_new - create and initialize a soundcard structure + * @parent: the parent device object + * @idx: card index (address) [0 ... (SNDRV_CARDS-1)] + * @xid: card identification (ASCII string) + * @module: top level module for locking + * @extra_size: allocate this extra size after the main soundcard structure + * @card_ret: the pointer to store the created card instance + * + * Creates and initializes a soundcard structure. + * + * The function allocates snd_card instance via kzalloc with the given + * space for the driver to use freely. The allocated struct is stored + * in the given card_ret pointer. + * + * Return: Zero if successful or a negative error code. + */ +int snd_card_new(struct device *parent, int idx, const char *xid, + struct module *module, int extra_size, + struct snd_card **card_ret) +{ + struct snd_card *card; + int err; + + if (snd_BUG_ON(!card_ret)) + return -EINVAL; + *card_ret = NULL; + + if (extra_size < 0) + extra_size = 0; + card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); + if (!card) + return -ENOMEM; + if (extra_size > 0) + card->private_data = (char *)card + sizeof(struct snd_card); + if (xid) + strlcpy(card->id, xid, sizeof(card->id)); + err = 0; + mutex_lock(&snd_card_mutex); + if (idx < 0) /* first check the matching module-name slot */ + idx = get_slot_from_bitmask(idx, module_slot_match, module); + if (idx < 0) /* if not matched, assign an empty slot */ + idx = get_slot_from_bitmask(idx, check_empty_slot, module); + if (idx < 0) + err = -ENODEV; + else if (idx < snd_ecards_limit) { + if (test_bit(idx, snd_cards_lock)) + err = -EBUSY; /* invalid */ + } else if (idx >= SNDRV_CARDS) + err = -ENODEV; + if (err < 0) { + mutex_unlock(&snd_card_mutex); + dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n", + idx, snd_ecards_limit - 1, err); + kfree(card); + return err; + } + set_bit(idx, snd_cards_lock); /* lock it */ + if (idx >= snd_ecards_limit) + snd_ecards_limit = idx + 1; /* increase the limit */ + mutex_unlock(&snd_card_mutex); + card->dev = parent; + card->number = idx; + card->module = module; + INIT_LIST_HEAD(&card->devices); + init_rwsem(&card->controls_rwsem); + rwlock_init(&card->ctl_files_rwlock); + INIT_LIST_HEAD(&card->controls); + INIT_LIST_HEAD(&card->ctl_files); + spin_lock_init(&card->files_lock); + INIT_LIST_HEAD(&card->files_list); +#ifdef CONFIG_PM + init_waitqueue_head(&card->power_sleep); +#endif + init_waitqueue_head(&card->remove_sleep); + + device_initialize(&card->card_dev); + card->card_dev.parent = parent; + card->card_dev.class = sound_class; + card->card_dev.release = release_card_device; + card->card_dev.groups = card->dev_groups; + card->dev_groups[0] = &card_dev_attr_group; + err = kobject_set_name(&card->card_dev.kobj, "card%d", idx); + if (err < 0) + goto __error; + + snprintf(card->irq_descr, sizeof(card->irq_descr), "%s:%s", + dev_driver_string(card->dev), dev_name(&card->card_dev)); + + /* the control interface cannot be accessed from the user space until */ + /* snd_cards_bitmask and snd_cards are set with snd_card_register */ + err = snd_ctl_create(card); + if (err < 0) { + dev_err(parent, "unable to register control minors\n"); + goto __error; + } + err = snd_info_card_create(card); + if (err < 0) { + dev_err(parent, "unable to create card info\n"); + goto __error_ctl; + } + *card_ret = card; + return 0; + + __error_ctl: + snd_device_free_all(card); + __error: + put_device(&card->card_dev); + return err; +} +EXPORT_SYMBOL(snd_card_new); + +/* return non-zero if a card is already locked */ +int snd_card_locked(int card) +{ + int locked; + + mutex_lock(&snd_card_mutex); + locked = test_bit(card, snd_cards_lock); + mutex_unlock(&snd_card_mutex); + return locked; +} + +static loff_t snd_disconnect_llseek(struct file *file, loff_t offset, int orig) +{ + return -ENODEV; +} + +static ssize_t snd_disconnect_read(struct file *file, char __user *buf, + size_t count, loff_t *offset) +{ + return -ENODEV; +} + +static ssize_t snd_disconnect_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + return -ENODEV; +} + +static int snd_disconnect_release(struct inode *inode, struct file *file) +{ + struct snd_monitor_file *df = NULL, *_df; + + spin_lock(&shutdown_lock); + list_for_each_entry(_df, &shutdown_files, shutdown_list) { + if (_df->file == file) { + df = _df; + list_del_init(&df->shutdown_list); + break; + } + } + spin_unlock(&shutdown_lock); + + if (likely(df)) { + if ((file->f_flags & FASYNC) && df->disconnected_f_op->fasync) + df->disconnected_f_op->fasync(-1, file, 0); + return df->disconnected_f_op->release(inode, file); + } + + panic("%s(%p, %p) failed!", __func__, inode, file); +} + +static __poll_t snd_disconnect_poll(struct file * file, poll_table * wait) +{ + return EPOLLERR | EPOLLNVAL; +} + +static long snd_disconnect_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return -ENODEV; +} + +static int snd_disconnect_mmap(struct file *file, struct vm_area_struct *vma) +{ + return -ENODEV; +} + +static int snd_disconnect_fasync(int fd, struct file *file, int on) +{ + return -ENODEV; +} + +static const struct file_operations snd_shutdown_f_ops = +{ + .owner = THIS_MODULE, + .llseek = snd_disconnect_llseek, + .read = snd_disconnect_read, + .write = snd_disconnect_write, + .release = snd_disconnect_release, + .poll = snd_disconnect_poll, + .unlocked_ioctl = snd_disconnect_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = snd_disconnect_ioctl, +#endif + .mmap = snd_disconnect_mmap, + .fasync = snd_disconnect_fasync +}; + +/** + * snd_card_disconnect - disconnect all APIs from the file-operations (user space) + * @card: soundcard structure + * + * Disconnects all APIs from the file-operations (user space). + * + * Return: Zero, otherwise a negative error code. + * + * Note: The current implementation replaces all active file->f_op with special + * dummy file operations (they do nothing except release). + */ +int snd_card_disconnect(struct snd_card *card) +{ + struct snd_monitor_file *mfile; + + if (!card) + return -EINVAL; + + spin_lock(&card->files_lock); + if (card->shutdown) { + spin_unlock(&card->files_lock); + return 0; + } + card->shutdown = 1; + + /* replace file->f_op with special dummy operations */ + list_for_each_entry(mfile, &card->files_list, list) { + /* it's critical part, use endless loop */ + /* we have no room to fail */ + mfile->disconnected_f_op = mfile->file->f_op; + + spin_lock(&shutdown_lock); + list_add(&mfile->shutdown_list, &shutdown_files); + spin_unlock(&shutdown_lock); + + mfile->file->f_op = &snd_shutdown_f_ops; + fops_get(mfile->file->f_op); + } + spin_unlock(&card->files_lock); + + /* notify all connected devices about disconnection */ + /* at this point, they cannot respond to any calls except release() */ + +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); +#endif + + /* notify all devices that we are disconnected */ + snd_device_disconnect_all(card); + + snd_info_card_disconnect(card); + if (card->registered) { + device_del(&card->card_dev); + card->registered = false; + } + + /* disable fops (user space) operations for ALSA API */ + mutex_lock(&snd_card_mutex); + snd_cards[card->number] = NULL; + clear_bit(card->number, snd_cards_lock); + mutex_unlock(&snd_card_mutex); + +#ifdef CONFIG_PM + wake_up(&card->power_sleep); +#endif + return 0; +} +EXPORT_SYMBOL(snd_card_disconnect); + +/** + * snd_card_disconnect_sync - disconnect card and wait until files get closed + * @card: card object to disconnect + * + * This calls snd_card_disconnect() for disconnecting all belonging components + * and waits until all pending files get closed. + * It assures that all accesses from user-space finished so that the driver + * can release its resources gracefully. + */ +void snd_card_disconnect_sync(struct snd_card *card) +{ + int err; + + err = snd_card_disconnect(card); + if (err < 0) { + dev_err(card->dev, + "snd_card_disconnect error (%d), skipping sync\n", + err); + return; + } + + spin_lock_irq(&card->files_lock); + wait_event_lock_irq(card->remove_sleep, + list_empty(&card->files_list), + card->files_lock); + spin_unlock_irq(&card->files_lock); +} +EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); + +static int snd_card_do_free(struct snd_card *card) +{ +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); +#endif + snd_device_free_all(card); + if (card->private_free) + card->private_free(card); + snd_info_free_entry(card->proc_id); + if (snd_info_card_free(card) < 0) { + dev_warn(card->dev, "unable to free card info\n"); + /* Not fatal error */ + } + if (card->release_completion) + complete(card->release_completion); + kfree(card); + return 0; +} + +/** + * snd_card_free_when_closed - Disconnect the card, free it later eventually + * @card: soundcard structure + * + * Unlike snd_card_free(), this function doesn't try to release the card + * resource immediately, but tries to disconnect at first. When the card + * is still in use, the function returns before freeing the resources. + * The card resources will be freed when the refcount gets to zero. + */ +int snd_card_free_when_closed(struct snd_card *card) +{ + int ret = snd_card_disconnect(card); + if (ret) + return ret; + put_device(&card->card_dev); + return 0; +} +EXPORT_SYMBOL(snd_card_free_when_closed); + +/** + * snd_card_free - frees given soundcard structure + * @card: soundcard structure + * + * This function releases the soundcard structure and the all assigned + * devices automatically. That is, you don't have to release the devices + * by yourself. + * + * This function waits until the all resources are properly released. + * + * Return: Zero. Frees all associated devices and frees the control + * interface associated to given soundcard. + */ +int snd_card_free(struct snd_card *card) +{ + struct completion released; + int ret; + + init_completion(&released); + card->release_completion = &released; + ret = snd_card_free_when_closed(card); + if (ret) + return ret; + /* wait, until all devices are ready for the free operation */ + wait_for_completion(&released); + return 0; +} +EXPORT_SYMBOL(snd_card_free); + +/* retrieve the last word of shortname or longname */ +static const char *retrieve_id_from_card_name(const char *name) +{ + const char *spos = name; + + while (*name) { + if (isspace(*name) && isalnum(name[1])) + spos = name + 1; + name++; + } + return spos; +} + +/* return true if the given id string doesn't conflict any other card ids */ +static bool card_id_ok(struct snd_card *card, const char *id) +{ + int i; + if (!snd_info_check_reserved_words(id)) + return false; + for (i = 0; i < snd_ecards_limit; i++) { + if (snd_cards[i] && snd_cards[i] != card && + !strcmp(snd_cards[i]->id, id)) + return false; + } + return true; +} + +/* copy to card->id only with valid letters from nid */ +static void copy_valid_id_string(struct snd_card *card, const char *src, + const char *nid) +{ + char *id = card->id; + + while (*nid && !isalnum(*nid)) + nid++; + if (isdigit(*nid)) + *id++ = isalpha(*src) ? *src : 'D'; + while (*nid && (size_t)(id - card->id) < sizeof(card->id) - 1) { + if (isalnum(*nid)) + *id++ = *nid; + nid++; + } + *id = 0; +} + +/* Set card->id from the given string + * If the string conflicts with other ids, add a suffix to make it unique. + */ +static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, + const char *nid) +{ + int len, loops; + bool is_default = false; + char *id; + + copy_valid_id_string(card, src, nid); + id = card->id; + + again: + /* use "Default" for obviously invalid strings + * ("card" conflicts with proc directories) + */ + if (!*id || !strncmp(id, "card", 4)) { + strcpy(id, "Default"); + is_default = true; + } + + len = strlen(id); + for (loops = 0; loops < SNDRV_CARDS; loops++) { + char *spos; + char sfxstr[5]; /* "_012" */ + int sfxlen; + + if (card_id_ok(card, id)) + return; /* OK */ + + /* Add _XYZ suffix */ + sprintf(sfxstr, "_%X", loops + 1); + sfxlen = strlen(sfxstr); + if (len + sfxlen >= sizeof(card->id)) + spos = id + sizeof(card->id) - sfxlen - 1; + else + spos = id + len; + strcpy(spos, sfxstr); + } + /* fallback to the default id */ + if (!is_default) { + *id = 0; + goto again; + } + /* last resort... */ + dev_err(card->dev, "unable to set card id (%s)\n", id); + if (card->proc_root->name) + strlcpy(card->id, card->proc_root->name, sizeof(card->id)); +} + +/** + * snd_card_set_id - set card identification name + * @card: soundcard structure + * @nid: new identification string + * + * This function sets the card identification and checks for name + * collisions. + */ +void snd_card_set_id(struct snd_card *card, const char *nid) +{ + /* check if user specified own card->id */ + if (card->id[0] != '\0') + return; + mutex_lock(&snd_card_mutex); + snd_card_set_id_no_lock(card, nid, nid); + mutex_unlock(&snd_card_mutex); +} +EXPORT_SYMBOL(snd_card_set_id); + +static ssize_t +card_id_show_attr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = container_of(dev, struct snd_card, card_dev); + return scnprintf(buf, PAGE_SIZE, "%s\n", card->id); +} + +static ssize_t +card_id_store_attr(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_card *card = container_of(dev, struct snd_card, card_dev); + char buf1[sizeof(card->id)]; + size_t copy = count > sizeof(card->id) - 1 ? + sizeof(card->id) - 1 : count; + size_t idx; + int c; + + for (idx = 0; idx < copy; idx++) { + c = buf[idx]; + if (!isalnum(c) && c != '_' && c != '-') + return -EINVAL; + } + memcpy(buf1, buf, copy); + buf1[copy] = '\0'; + mutex_lock(&snd_card_mutex); + if (!card_id_ok(NULL, buf1)) { + mutex_unlock(&snd_card_mutex); + return -EEXIST; + } + strcpy(card->id, buf1); + snd_info_card_id_change(card); + mutex_unlock(&snd_card_mutex); + + return count; +} + +static DEVICE_ATTR(id, 0644, card_id_show_attr, card_id_store_attr); + +static ssize_t +card_number_show_attr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = container_of(dev, struct snd_card, card_dev); + return scnprintf(buf, PAGE_SIZE, "%i\n", card->number); +} + +static DEVICE_ATTR(number, 0444, card_number_show_attr, NULL); + +static struct attribute *card_dev_attrs[] = { + &dev_attr_id.attr, + &dev_attr_number.attr, + NULL +}; + +static const struct attribute_group card_dev_attr_group = { + .attrs = card_dev_attrs, +}; + +/** + * snd_card_add_dev_attr - Append a new sysfs attribute group to card + * @card: card instance + * @group: attribute group to append + */ +int snd_card_add_dev_attr(struct snd_card *card, + const struct attribute_group *group) +{ + int i; + + /* loop for (arraysize-1) here to keep NULL at the last entry */ + for (i = 0; i < ARRAY_SIZE(card->dev_groups) - 1; i++) { + if (!card->dev_groups[i]) { + card->dev_groups[i] = group; + return 0; + } + } + + dev_err(card->dev, "Too many groups assigned\n"); + return -ENOSPC; +} +EXPORT_SYMBOL_GPL(snd_card_add_dev_attr); + +/** + * snd_card_register - register the soundcard + * @card: soundcard structure + * + * This function registers all the devices assigned to the soundcard. + * Until calling this, the ALSA control interface is blocked from the + * external accesses. Thus, you should call this function at the end + * of the initialization of the card. + * + * Return: Zero otherwise a negative error code if the registration failed. + */ +int snd_card_register(struct snd_card *card) +{ + int err; + + if (snd_BUG_ON(!card)) + return -EINVAL; + + if (!card->registered) { + err = device_add(&card->card_dev); + if (err < 0) + return err; + card->registered = true; + } + + if ((err = snd_device_register_all(card)) < 0) + return err; + mutex_lock(&snd_card_mutex); + if (snd_cards[card->number]) { + /* already registered */ + mutex_unlock(&snd_card_mutex); + return snd_info_card_register(card); /* register pending info */ + } + if (*card->id) { + /* make a unique id name from the given string */ + char tmpid[sizeof(card->id)]; + memcpy(tmpid, card->id, sizeof(card->id)); + snd_card_set_id_no_lock(card, tmpid, tmpid); + } else { + /* create an id from either shortname or longname */ + const char *src; + src = *card->shortname ? card->shortname : card->longname; + snd_card_set_id_no_lock(card, src, + retrieve_id_from_card_name(src)); + } + snd_cards[card->number] = card; + mutex_unlock(&snd_card_mutex); + init_info_for_card(card); +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); +#endif + return 0; +} +EXPORT_SYMBOL(snd_card_register); + +#ifdef CONFIG_SND_PROC_FS +static void snd_card_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int idx, count; + struct snd_card *card; + + for (idx = count = 0; idx < SNDRV_CARDS; idx++) { + mutex_lock(&snd_card_mutex); + if ((card = snd_cards[idx]) != NULL) { + count++; + snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n", + idx, + card->id, + card->driver, + card->shortname); + snd_iprintf(buffer, " %s\n", + card->longname); + } + mutex_unlock(&snd_card_mutex); + } + if (!count) + snd_iprintf(buffer, "--- no soundcards ---\n"); +} + +#ifdef CONFIG_SND_OSSEMUL +void snd_card_info_read_oss(struct snd_info_buffer *buffer) +{ + int idx, count; + struct snd_card *card; + + for (idx = count = 0; idx < SNDRV_CARDS; idx++) { + mutex_lock(&snd_card_mutex); + if ((card = snd_cards[idx]) != NULL) { + count++; + snd_iprintf(buffer, "%s\n", card->longname); + } + mutex_unlock(&snd_card_mutex); + } + if (!count) { + snd_iprintf(buffer, "--- no soundcards ---\n"); + } +} + +#endif + +#ifdef MODULE +static void snd_card_module_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int idx; + struct snd_card *card; + + for (idx = 0; idx < SNDRV_CARDS; idx++) { + mutex_lock(&snd_card_mutex); + if ((card = snd_cards[idx]) != NULL) + snd_iprintf(buffer, "%2i %s\n", + idx, card->module->name); + mutex_unlock(&snd_card_mutex); + } +} +#endif + +int __init snd_card_info_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL); + if (! entry) + return -ENOMEM; + entry->c.text.read = snd_card_info_read; + if (snd_info_register(entry) < 0) + return -ENOMEM; /* freed in error path */ + +#ifdef MODULE + entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_card_module_info_read; + if (snd_info_register(entry) < 0) + return -ENOMEM; /* freed in error path */ +#endif + + return 0; +} +#endif /* CONFIG_SND_PROC_FS */ + +/** + * snd_component_add - add a component string + * @card: soundcard structure + * @component: the component id string + * + * This function adds the component id string to the supported list. + * The component can be referred from the alsa-lib. + * + * Return: Zero otherwise a negative error code. + */ + +int snd_component_add(struct snd_card *card, const char *component) +{ + char *ptr; + int len = strlen(component); + + ptr = strstr(card->components, component); + if (ptr != NULL) { + if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ + return 1; + } + if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { + snd_BUG(); + return -ENOMEM; + } + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); + return 0; +} +EXPORT_SYMBOL(snd_component_add); + +/** + * snd_card_file_add - add the file to the file list of the card + * @card: soundcard structure + * @file: file pointer + * + * This function adds the file to the file linked-list of the card. + * This linked-list is used to keep tracking the connection state, + * and to avoid the release of busy resources by hotplug. + * + * Return: zero or a negative error code. + */ +int snd_card_file_add(struct snd_card *card, struct file *file) +{ + struct snd_monitor_file *mfile; + + mfile = kmalloc(sizeof(*mfile), GFP_KERNEL); + if (mfile == NULL) + return -ENOMEM; + mfile->file = file; + mfile->disconnected_f_op = NULL; + INIT_LIST_HEAD(&mfile->shutdown_list); + spin_lock(&card->files_lock); + if (card->shutdown) { + spin_unlock(&card->files_lock); + kfree(mfile); + return -ENODEV; + } + list_add(&mfile->list, &card->files_list); + get_device(&card->card_dev); + spin_unlock(&card->files_lock); + return 0; +} +EXPORT_SYMBOL(snd_card_file_add); + +/** + * snd_card_file_remove - remove the file from the file list + * @card: soundcard structure + * @file: file pointer + * + * This function removes the file formerly added to the card via + * snd_card_file_add() function. + * If all files are removed and snd_card_free_when_closed() was + * called beforehand, it processes the pending release of + * resources. + * + * Return: Zero or a negative error code. + */ +int snd_card_file_remove(struct snd_card *card, struct file *file) +{ + struct snd_monitor_file *mfile, *found = NULL; + + spin_lock(&card->files_lock); + list_for_each_entry(mfile, &card->files_list, list) { + if (mfile->file == file) { + list_del(&mfile->list); + spin_lock(&shutdown_lock); + list_del(&mfile->shutdown_list); + spin_unlock(&shutdown_lock); + if (mfile->disconnected_f_op) + fops_put(mfile->disconnected_f_op); + found = mfile; + break; + } + } + if (list_empty(&card->files_list)) + wake_up_all(&card->remove_sleep); + spin_unlock(&card->files_lock); + if (!found) { + dev_err(card->dev, "card file remove problem (%p)\n", file); + return -ENOENT; + } + kfree(found); + put_device(&card->card_dev); + return 0; +} +EXPORT_SYMBOL(snd_card_file_remove); + +#ifdef CONFIG_PM +/** + * snd_power_wait - wait until the power-state is changed. + * @card: soundcard structure + * @power_state: expected power state + * + * Waits until the power-state is changed. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_power_wait(struct snd_card *card, unsigned int power_state) +{ + wait_queue_entry_t wait; + int result = 0; + + /* fastpath */ + if (snd_power_get_state(card) == power_state) + return 0; + init_waitqueue_entry(&wait, current); + add_wait_queue(&card->power_sleep, &wait); + while (1) { + if (card->shutdown) { + result = -ENODEV; + break; + } + if (snd_power_get_state(card) == power_state) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(30 * HZ); + } + remove_wait_queue(&card->power_sleep, &wait); + return result; +} +EXPORT_SYMBOL(snd_power_wait); +#endif /* CONFIG_PM */ diff --git a/sound/core/isadma.c b/sound/core/isadma.c new file mode 100644 index 000000000..7a8515abb --- /dev/null +++ b/sound/core/isadma.c @@ -0,0 +1,114 @@ +/* + * ISA DMA support functions + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Defining following add some delay. Maybe this helps for some broken + * ISA DMA controllers. + */ + +#undef HAVE_REALLY_SLOW_DMA_CONTROLLER + +#include +#include +#include + +/** + * snd_dma_program - program an ISA DMA transfer + * @dma: the dma number + * @addr: the physical address of the buffer + * @size: the DMA transfer size + * @mode: the DMA transfer mode, DMA_MODE_XXX + * + * Programs an ISA DMA transfer for the given buffer. + */ +void snd_dma_program(unsigned long dma, + unsigned long addr, unsigned int size, + unsigned short mode) +{ + unsigned long flags; + + flags = claim_dma_lock(); + disable_dma(dma); + clear_dma_ff(dma); + set_dma_mode(dma, mode); + set_dma_addr(dma, addr); + set_dma_count(dma, size); + if (!(mode & DMA_MODE_NO_ENABLE)) + enable_dma(dma); + release_dma_lock(flags); +} +EXPORT_SYMBOL(snd_dma_program); + +/** + * snd_dma_disable - stop the ISA DMA transfer + * @dma: the dma number + * + * Stops the ISA DMA transfer. + */ +void snd_dma_disable(unsigned long dma) +{ + unsigned long flags; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + disable_dma(dma); + release_dma_lock(flags); +} +EXPORT_SYMBOL(snd_dma_disable); + +/** + * snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes + * @dma: the dma number + * @size: the dma transfer size + * + * Return: The current pointer in DMA transfer buffer in bytes. + */ +unsigned int snd_dma_pointer(unsigned long dma, unsigned int size) +{ + unsigned long flags; + unsigned int result, result1; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + if (!isa_dma_bridge_buggy) + disable_dma(dma); + result = get_dma_residue(dma); + /* + * HACK - read the counter again and choose higher value in order to + * avoid reading during counter lower byte roll over if the + * isa_dma_bridge_buggy is set. + */ + result1 = get_dma_residue(dma); + if (!isa_dma_bridge_buggy) + enable_dma(dma); + release_dma_lock(flags); + if (unlikely(result < result1)) + result = result1; +#ifdef CONFIG_SND_DEBUG + if (result > size) + pr_err("ALSA: pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size); +#endif + if (result >= size || result == 0) + return 0; + else + return size - result; +} +EXPORT_SYMBOL(snd_dma_pointer); diff --git a/sound/core/jack.c b/sound/core/jack.c new file mode 100644 index 000000000..074b15fcb --- /dev/null +++ b/sound/core/jack.c @@ -0,0 +1,418 @@ +/* + * Jack abstraction layer + * + * Copyright 2008 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +struct snd_jack_kctl { + struct snd_kcontrol *kctl; + struct list_head list; /* list of controls belong to the same jack */ + unsigned int mask_bits; /* only masked status bits are reported via kctl */ +}; + +#ifdef CONFIG_SND_JACK_INPUT_DEV +static int jack_switch_types[SND_JACK_SWITCH_TYPES] = { + SW_HEADPHONE_INSERT, + SW_MICROPHONE_INSERT, + SW_LINEOUT_INSERT, + SW_JACK_PHYSICAL_INSERT, + SW_VIDEOOUT_INSERT, + SW_LINEIN_INSERT, +}; +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + +static int snd_jack_dev_disconnect(struct snd_device *device) +{ +#ifdef CONFIG_SND_JACK_INPUT_DEV + struct snd_jack *jack = device->device_data; + + mutex_lock(&jack->input_dev_lock); + if (!jack->input_dev) { + mutex_unlock(&jack->input_dev_lock); + return 0; + } + + /* If the input device is registered with the input subsystem + * then we need to use a different deallocator. */ + if (jack->registered) + input_unregister_device(jack->input_dev); + else + input_free_device(jack->input_dev); + jack->input_dev = NULL; + mutex_unlock(&jack->input_dev_lock); +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + return 0; +} + +static int snd_jack_dev_free(struct snd_device *device) +{ + struct snd_jack *jack = device->device_data; + struct snd_card *card = device->card; + struct snd_jack_kctl *jack_kctl, *tmp_jack_kctl; + + down_write(&card->controls_rwsem); + list_for_each_entry_safe(jack_kctl, tmp_jack_kctl, &jack->kctl_list, list) { + list_del_init(&jack_kctl->list); + snd_ctl_remove(card, jack_kctl->kctl); + } + up_write(&card->controls_rwsem); + + if (jack->private_free) + jack->private_free(jack); + + snd_jack_dev_disconnect(device); + + kfree(jack->id); + kfree(jack); + + return 0; +} + +#ifdef CONFIG_SND_JACK_INPUT_DEV +static int snd_jack_dev_register(struct snd_device *device) +{ + struct snd_jack *jack = device->device_data; + struct snd_card *card = device->card; + int err, i; + + snprintf(jack->name, sizeof(jack->name), "%s %s", + card->shortname, jack->id); + + mutex_lock(&jack->input_dev_lock); + if (!jack->input_dev) { + mutex_unlock(&jack->input_dev_lock); + return 0; + } + + jack->input_dev->name = jack->name; + + /* Default to the sound card device. */ + if (!jack->input_dev->dev.parent) + jack->input_dev->dev.parent = snd_card_get_device_link(card); + + /* Add capabilities for any keys that are enabled */ + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = SND_JACK_BTN_0 >> i; + + if (!(jack->type & testbit)) + continue; + + if (!jack->key[i]) + jack->key[i] = BTN_0 + i; + + input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); + } + + err = input_register_device(jack->input_dev); + if (err == 0) + jack->registered = 1; + + mutex_unlock(&jack->input_dev_lock); + return err; +} +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + +static void snd_jack_kctl_private_free(struct snd_kcontrol *kctl) +{ + struct snd_jack_kctl *jack_kctl; + + jack_kctl = kctl->private_data; + if (jack_kctl) { + list_del(&jack_kctl->list); + kfree(jack_kctl); + } +} + +static void snd_jack_kctl_add(struct snd_jack *jack, struct snd_jack_kctl *jack_kctl) +{ + list_add_tail(&jack_kctl->list, &jack->kctl_list); +} + +static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const char *name, unsigned int mask) +{ + struct snd_kcontrol *kctl; + struct snd_jack_kctl *jack_kctl; + int err; + + kctl = snd_kctl_jack_new(name, card); + if (!kctl) + return NULL; + + err = snd_ctl_add(card, kctl); + if (err < 0) + return NULL; + + jack_kctl = kzalloc(sizeof(*jack_kctl), GFP_KERNEL); + + if (!jack_kctl) + goto error; + + jack_kctl->kctl = kctl; + jack_kctl->mask_bits = mask; + + kctl->private_data = jack_kctl; + kctl->private_free = snd_jack_kctl_private_free; + + return jack_kctl; +error: + snd_ctl_free_one(kctl); + return NULL; +} + +/** + * snd_jack_add_new_kctl - Create a new snd_jack_kctl and add it to jack + * @jack: the jack instance which the kctl will attaching to + * @name: the name for the snd_kcontrol object + * @mask: a bitmask of enum snd_jack_type values that can be detected + * by this snd_jack_kctl object. + * + * Creates a new snd_kcontrol object and adds it to the jack kctl_list. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_jack_add_new_kctl(struct snd_jack *jack, const char * name, int mask) +{ + struct snd_jack_kctl *jack_kctl; + + jack_kctl = snd_jack_kctl_new(jack->card, name, mask); + if (!jack_kctl) + return -ENOMEM; + + snd_jack_kctl_add(jack, jack_kctl); + return 0; +} +EXPORT_SYMBOL(snd_jack_add_new_kctl); + +/** + * snd_jack_new - Create a new jack + * @card: the card instance + * @id: an identifying string for this jack + * @type: a bitmask of enum snd_jack_type values that can be detected by + * this jack + * @jjack: Used to provide the allocated jack object to the caller. + * @initial_kctl: if true, create a kcontrol and add it to the jack list. + * @phantom_jack: Don't create a input device for phantom jacks. + * + * Creates a new jack object. + * + * Return: Zero if successful, or a negative error code on failure. + * On success @jjack will be initialised. + */ +int snd_jack_new(struct snd_card *card, const char *id, int type, + struct snd_jack **jjack, bool initial_kctl, bool phantom_jack) +{ + struct snd_jack *jack; + struct snd_jack_kctl *jack_kctl = NULL; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_jack_dev_free, +#ifdef CONFIG_SND_JACK_INPUT_DEV + .dev_register = snd_jack_dev_register, + .dev_disconnect = snd_jack_dev_disconnect, +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + }; + + if (initial_kctl) { + jack_kctl = snd_jack_kctl_new(card, id, type); + if (!jack_kctl) + return -ENOMEM; + } + + jack = kzalloc(sizeof(struct snd_jack), GFP_KERNEL); + if (jack == NULL) + return -ENOMEM; + + jack->id = kstrdup(id, GFP_KERNEL); + if (jack->id == NULL) { + kfree(jack); + return -ENOMEM; + } + +#ifdef CONFIG_SND_JACK_INPUT_DEV + mutex_init(&jack->input_dev_lock); + + /* don't create input device for phantom jack */ + if (!phantom_jack) { + int i; + + jack->input_dev = input_allocate_device(); + if (jack->input_dev == NULL) { + err = -ENOMEM; + goto fail_input; + } + + jack->input_dev->phys = "ALSA"; + + jack->type = type; + + for (i = 0; i < SND_JACK_SWITCH_TYPES; i++) + if (type & (1 << i)) + input_set_capability(jack->input_dev, EV_SW, + jack_switch_types[i]); + + } +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + + err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops); + if (err < 0) + goto fail_input; + + jack->card = card; + INIT_LIST_HEAD(&jack->kctl_list); + + if (initial_kctl) + snd_jack_kctl_add(jack, jack_kctl); + + *jjack = jack; + + return 0; + +fail_input: +#ifdef CONFIG_SND_JACK_INPUT_DEV + input_free_device(jack->input_dev); +#endif + kfree(jack->id); + kfree(jack); + return err; +} +EXPORT_SYMBOL(snd_jack_new); + +#ifdef CONFIG_SND_JACK_INPUT_DEV +/** + * snd_jack_set_parent - Set the parent device for a jack + * + * @jack: The jack to configure + * @parent: The device to set as parent for the jack. + * + * Set the parent for the jack devices in the device tree. This + * function is only valid prior to registration of the jack. If no + * parent is configured then the parent device will be the sound card. + */ +void snd_jack_set_parent(struct snd_jack *jack, struct device *parent) +{ + WARN_ON(jack->registered); + mutex_lock(&jack->input_dev_lock); + if (!jack->input_dev) { + mutex_unlock(&jack->input_dev_lock); + return; + } + + jack->input_dev->dev.parent = parent; + mutex_unlock(&jack->input_dev_lock); +} +EXPORT_SYMBOL(snd_jack_set_parent); + +/** + * snd_jack_set_key - Set a key mapping on a jack + * + * @jack: The jack to configure + * @type: Jack report type for this key + * @keytype: Input layer key type to be reported + * + * Map a SND_JACK_BTN_* button type to an input layer key, allowing + * reporting of keys on accessories via the jack abstraction. If no + * mapping is provided but keys are enabled in the jack type then + * BTN_n numeric buttons will be reported. + * + * If jacks are not reporting via the input API this call will have no + * effect. + * + * Note that this is intended to be use by simple devices with small + * numbers of keys that can be reported. It is also possible to + * access the input device directly - devices with complex input + * capabilities on accessories should consider doing this rather than + * using this abstraction. + * + * This function may only be called prior to registration of the jack. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type, + int keytype) +{ + int key = fls(SND_JACK_BTN_0) - fls(type); + + WARN_ON(jack->registered); + + if (!keytype || key >= ARRAY_SIZE(jack->key)) + return -EINVAL; + + jack->type |= type; + jack->key[key] = keytype; + return 0; +} +EXPORT_SYMBOL(snd_jack_set_key); +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + +/** + * snd_jack_report - Report the current status of a jack + * Note: This function uses mutexes and should be called from a + * context which can sleep (such as a workqueue). + * + * @jack: The jack to report status for + * @status: The current status of the jack + */ +void snd_jack_report(struct snd_jack *jack, int status) +{ + struct snd_jack_kctl *jack_kctl; +#ifdef CONFIG_SND_JACK_INPUT_DEV + int i; +#endif + + if (!jack) + return; + + list_for_each_entry(jack_kctl, &jack->kctl_list, list) + snd_kctl_jack_report(jack->card, jack_kctl->kctl, + status & jack_kctl->mask_bits); + +#ifdef CONFIG_SND_JACK_INPUT_DEV + mutex_lock(&jack->input_dev_lock); + if (!jack->input_dev) { + mutex_unlock(&jack->input_dev_lock); + return; + } + + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = SND_JACK_BTN_0 >> i; + + if (jack->type & testbit) + input_report_key(jack->input_dev, jack->key[i], + status & testbit); + } + + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) { + int testbit = 1 << i; + if (jack->type & testbit) + input_report_switch(jack->input_dev, + jack_switch_types[i], + status & testbit); + } + + input_sync(jack->input_dev); + mutex_unlock(&jack->input_dev_lock); +#endif /* CONFIG_SND_JACK_INPUT_DEV */ +} +EXPORT_SYMBOL(snd_jack_report); diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c new file mode 100644 index 000000000..753d5fc4b --- /dev/null +++ b/sound/core/memalloc.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Takashi Iwai + * + * Generic memory allocators + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +/* + * + * Generic memory allocators + * + */ + +/** + * snd_malloc_pages - allocate pages with the given size + * @size: the size to allocate in bytes + * @gfp_flags: the allocation conditions, GFP_XXX + * + * Allocates the physically contiguous pages with the given size. + * + * Return: The pointer of the buffer, or %NULL if no enough memory. + */ +void *snd_malloc_pages(size_t size, gfp_t gfp_flags) +{ + int pg; + + if (WARN_ON(!size)) + return NULL; + if (WARN_ON(!gfp_flags)) + return NULL; + gfp_flags |= __GFP_COMP; /* compound page lets parts be mapped */ + pg = get_order(size); + return (void *) __get_free_pages(gfp_flags, pg); +} +EXPORT_SYMBOL(snd_malloc_pages); + +/** + * snd_free_pages - release the pages + * @ptr: the buffer pointer to release + * @size: the allocated buffer size + * + * Releases the buffer allocated via snd_malloc_pages(). + */ +void snd_free_pages(void *ptr, size_t size) +{ + int pg; + + if (ptr == NULL) + return; + pg = get_order(size); + free_pages((unsigned long) ptr, pg); +} +EXPORT_SYMBOL(snd_free_pages); + +/* + * + * Bus-specific memory allocators + * + */ + +#ifdef CONFIG_HAS_DMA +/* allocate the coherent DMA pages */ +static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma) +{ + int pg; + gfp_t gfp_flags; + + if (WARN_ON(!dma)) + return NULL; + pg = get_order(size); + gfp_flags = GFP_KERNEL + | __GFP_COMP /* compound page lets parts be mapped */ + | __GFP_NORETRY /* don't trigger OOM-killer */ + | __GFP_NOWARN; /* no stack trace print - this call is non-critical */ + return dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags); +} + +/* free the coherent DMA pages */ +static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr, + dma_addr_t dma) +{ + int pg; + + if (ptr == NULL) + return; + pg = get_order(size); + dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma); +} + +#ifdef CONFIG_GENERIC_ALLOCATOR +/** + * snd_malloc_dev_iram - allocate memory from on-chip internal ram + * @dmab: buffer allocation record to store the allocated data + * @size: number of bytes to allocate from the iram + * + * This function requires iram phandle provided via of_node + */ +static void snd_malloc_dev_iram(struct snd_dma_buffer *dmab, size_t size) +{ + struct device *dev = dmab->dev.dev; + struct gen_pool *pool = NULL; + + dmab->area = NULL; + dmab->addr = 0; + + if (dev->of_node) + pool = of_gen_pool_get(dev->of_node, "iram", 0); + + if (!pool) + return; + + /* Assign the pool into private_data field */ + dmab->private_data = pool; + + dmab->area = gen_pool_dma_alloc(pool, size, &dmab->addr); +} + +/** + * snd_free_dev_iram - free allocated specific memory from on-chip internal ram + * @dmab: buffer allocation record to store the allocated data + */ +static void snd_free_dev_iram(struct snd_dma_buffer *dmab) +{ + struct gen_pool *pool = dmab->private_data; + + if (pool && dmab->area) + gen_pool_free(pool, (unsigned long)dmab->area, dmab->bytes); +} +#endif /* CONFIG_GENERIC_ALLOCATOR */ +#endif /* CONFIG_HAS_DMA */ + +/* + * + * ALSA generic memory management + * + */ + + +/** + * snd_dma_alloc_pages - allocate the buffer area according to the given type + * @type: the DMA buffer type + * @device: the device pointer + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. + * + * Return: Zero if the buffer with the given size is allocated successfully, + * otherwise a negative value on error. + */ +int snd_dma_alloc_pages(int type, struct device *device, size_t size, + struct snd_dma_buffer *dmab) +{ + if (WARN_ON(!size)) + return -ENXIO; + if (WARN_ON(!dmab)) + return -ENXIO; + + dmab->dev.type = type; + dmab->dev.dev = device; + dmab->bytes = 0; + switch (type) { + case SNDRV_DMA_TYPE_CONTINUOUS: + dmab->area = snd_malloc_pages(size, + (__force gfp_t)(unsigned long)device); + dmab->addr = 0; + break; +#ifdef CONFIG_HAS_DMA +#ifdef CONFIG_GENERIC_ALLOCATOR + case SNDRV_DMA_TYPE_DEV_IRAM: + snd_malloc_dev_iram(dmab, size); + if (dmab->area) + break; + /* Internal memory might have limited size and no enough space, + * so if we fail to malloc, try to fetch memory traditionally. + */ + dmab->dev.type = SNDRV_DMA_TYPE_DEV; +#endif /* CONFIG_GENERIC_ALLOCATOR */ + case SNDRV_DMA_TYPE_DEV: + dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr); + break; +#endif +#ifdef CONFIG_SND_DMA_SGBUF + case SNDRV_DMA_TYPE_DEV_SG: + snd_malloc_sgbuf_pages(device, size, dmab, NULL); + break; +#endif + default: + pr_err("snd-malloc: invalid device type %d\n", type); + dmab->area = NULL; + dmab->addr = 0; + return -ENXIO; + } + if (! dmab->area) + return -ENOMEM; + dmab->bytes = size; + return 0; +} +EXPORT_SYMBOL(snd_dma_alloc_pages); + +/** + * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback + * @type: the DMA buffer type + * @device: the device pointer + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. When no space is left, this function reduces the size and + * tries to allocate again. The size actually allocated is stored in + * res_size argument. + * + * Return: Zero if the buffer with the given size is allocated successfully, + * otherwise a negative value on error. + */ +int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size, + struct snd_dma_buffer *dmab) +{ + int err; + + while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) { + if (err != -ENOMEM) + return err; + if (size <= PAGE_SIZE) + return -ENOMEM; + size >>= 1; + size = PAGE_SIZE << get_order(size); + } + if (! dmab->area) + return -ENOMEM; + return 0; +} +EXPORT_SYMBOL(snd_dma_alloc_pages_fallback); + + +/** + * snd_dma_free_pages - release the allocated buffer + * @dmab: the buffer allocation record to release + * + * Releases the allocated buffer via snd_dma_alloc_pages(). + */ +void snd_dma_free_pages(struct snd_dma_buffer *dmab) +{ + switch (dmab->dev.type) { + case SNDRV_DMA_TYPE_CONTINUOUS: + snd_free_pages(dmab->area, dmab->bytes); + break; +#ifdef CONFIG_HAS_DMA +#ifdef CONFIG_GENERIC_ALLOCATOR + case SNDRV_DMA_TYPE_DEV_IRAM: + snd_free_dev_iram(dmab); + break; +#endif /* CONFIG_GENERIC_ALLOCATOR */ + case SNDRV_DMA_TYPE_DEV: + snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr); + break; +#endif +#ifdef CONFIG_SND_DMA_SGBUF + case SNDRV_DMA_TYPE_DEV_SG: + snd_free_sgbuf_pages(dmab); + break; +#endif + default: + pr_err("snd-malloc: invalid device type %d\n", dmab->dev.type); + } +} +EXPORT_SYMBOL(snd_dma_free_pages); diff --git a/sound/core/memory.c b/sound/core/memory.c new file mode 100644 index 000000000..19c9ea90d --- /dev/null +++ b/sound/core/memory.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) by Jaroslav Kysela + * + * Misc memory accessors + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +/** + * copy_to_user_fromio - copy data from mmio-space to user-space + * @dst: the destination pointer on user-space + * @src: the source pointer on mmio + * @count: the data size to copy in bytes + * + * Copies the data from mmio-space to user-space. + * + * Return: Zero if successful, or non-zero on failure. + */ +int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count) +{ +#if defined(__i386__) || defined(CONFIG_SPARC32) + return copy_to_user(dst, (const void __force*)src, count) ? -EFAULT : 0; +#else + char buf[256]; + while (count) { + size_t c = count; + if (c > sizeof(buf)) + c = sizeof(buf); + memcpy_fromio(buf, (void __iomem *)src, c); + if (copy_to_user(dst, buf, c)) + return -EFAULT; + count -= c; + dst += c; + src += c; + } + return 0; +#endif +} +EXPORT_SYMBOL(copy_to_user_fromio); + +/** + * copy_from_user_toio - copy data from user-space to mmio-space + * @dst: the destination pointer on mmio-space + * @src: the source pointer on user-space + * @count: the data size to copy in bytes + * + * Copies the data from user-space to mmio-space. + * + * Return: Zero if successful, or non-zero on failure. + */ +int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count) +{ +#if defined(__i386__) || defined(CONFIG_SPARC32) + return copy_from_user((void __force *)dst, src, count) ? -EFAULT : 0; +#else + char buf[256]; + while (count) { + size_t c = count; + if (c > sizeof(buf)) + c = sizeof(buf); + if (copy_from_user(buf, src, c)) + return -EFAULT; + memcpy_toio(dst, buf, c); + count -= c; + dst += c; + src += c; + } + return 0; +#endif +} +EXPORT_SYMBOL(copy_from_user_toio); diff --git a/sound/core/misc.c b/sound/core/misc.c new file mode 100644 index 000000000..0f818d593 --- /dev/null +++ b/sound/core/misc.c @@ -0,0 +1,162 @@ +/* + * Misc and compatibility things + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SND_DEBUG + +#ifdef CONFIG_SND_DEBUG_VERBOSE +#define DEFAULT_DEBUG_LEVEL 2 +#else +#define DEFAULT_DEBUG_LEVEL 1 +#endif + +static int debug = DEFAULT_DEBUG_LEVEL; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0 = disable)"); + +#endif /* CONFIG_SND_DEBUG */ + +void release_and_free_resource(struct resource *res) +{ + if (res) { + release_resource(res); + kfree(res); + } +} +EXPORT_SYMBOL(release_and_free_resource); + +#ifdef CONFIG_SND_VERBOSE_PRINTK +/* strip the leading path if the given path is absolute */ +static const char *sanity_file_name(const char *path) +{ + if (*path == '/') + return strrchr(path, '/') + 1; + else + return path; +} +#endif + +#if defined(CONFIG_SND_DEBUG) || defined(CONFIG_SND_VERBOSE_PRINTK) +void __snd_printk(unsigned int level, const char *path, int line, + const char *format, ...) +{ + va_list args; +#ifdef CONFIG_SND_VERBOSE_PRINTK + int kern_level; + struct va_format vaf; + char verbose_fmt[] = KERN_DEFAULT "ALSA %s:%d %pV"; + bool level_found = false; +#endif + +#ifdef CONFIG_SND_DEBUG + if (debug < level) + return; +#endif + + va_start(args, format); +#ifdef CONFIG_SND_VERBOSE_PRINTK + vaf.fmt = format; + vaf.va = &args; + + while ((kern_level = printk_get_level(vaf.fmt)) != 0) { + const char *end_of_header = printk_skip_level(vaf.fmt); + + /* Ignore KERN_CONT. We print filename:line for each piece. */ + if (kern_level >= '0' && kern_level <= '7') { + memcpy(verbose_fmt, vaf.fmt, end_of_header - vaf.fmt); + level_found = true; + } + + vaf.fmt = end_of_header; + } + + if (!level_found && level) + memcpy(verbose_fmt, KERN_DEBUG, sizeof(KERN_DEBUG) - 1); + + printk(verbose_fmt, sanity_file_name(path), line, &vaf); +#else + vprintk(format, args); +#endif + va_end(args); +} +EXPORT_SYMBOL_GPL(__snd_printk); +#endif + +#ifdef CONFIG_PCI +#include +/** + * snd_pci_quirk_lookup_id - look up a PCI SSID quirk list + * @vendor: PCI SSV id + * @device: PCI SSD id + * @list: quirk list, terminated by a null entry + * + * Look through the given quirk list and finds a matching entry + * with the same PCI SSID. When subdevice is 0, all subdevice + * values may match. + * + * Returns the matched entry pointer, or NULL if nothing matched. + */ +const struct snd_pci_quirk * +snd_pci_quirk_lookup_id(u16 vendor, u16 device, + const struct snd_pci_quirk *list) +{ + const struct snd_pci_quirk *q; + + for (q = list; q->subvendor; q++) { + if (q->subvendor != vendor) + continue; + if (!q->subdevice || + (device & q->subdevice_mask) == q->subdevice) + return q; + } + return NULL; +} +EXPORT_SYMBOL(snd_pci_quirk_lookup_id); + +/** + * snd_pci_quirk_lookup - look up a PCI SSID quirk list + * @pci: pci_dev handle + * @list: quirk list, terminated by a null entry + * + * Look through the given quirk list and finds a matching entry + * with the same PCI SSID. When subdevice is 0, all subdevice + * values may match. + * + * Returns the matched entry pointer, or NULL if nothing matched. + */ +const struct snd_pci_quirk * +snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list) +{ + if (!pci) + return NULL; + return snd_pci_quirk_lookup_id(pci->subsystem_vendor, + pci->subsystem_device, + list); +} +EXPORT_SYMBOL(snd_pci_quirk_lookup); +#endif diff --git a/sound/core/oss/Makefile b/sound/core/oss/Makefile new file mode 100644 index 000000000..ae25edcc3 --- /dev/null +++ b/sound/core/oss/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela +# + +snd-mixer-oss-objs := mixer_oss.o + +snd-pcm-oss-y := pcm_oss.o +snd-pcm-oss-$(CONFIG_SND_PCM_OSS_PLUGINS) += pcm_plugin.o \ + io.o copy.o linear.o mulaw.o route.o rate.o + +obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o +obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o diff --git a/sound/core/oss/copy.c b/sound/core/oss/copy.c new file mode 100644 index 000000000..05b58d4fc --- /dev/null +++ b/sound/core/oss/copy.c @@ -0,0 +1,92 @@ +/* + * Linear conversion Plug-In + * Copyright (c) 2000 by Abramo Bagnara + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#include +#include +#include +#include "pcm_plugin.h" + +static snd_pcm_sframes_t copy_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + unsigned int channel; + unsigned int nchannels; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; + nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; channel++) { + if (snd_BUG_ON(src_channels->area.first % 8 || + src_channels->area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels->area.first % 8 || + dst_channels->area.step % 8)) + return -ENXIO; + if (!src_channels->enabled) { + if (dst_channels->wanted) + snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format); + dst_channels->enabled = 0; + continue; + } + dst_channels->enabled = 1; + snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format); + src_channels++; + dst_channels++; + } + return frames; +} + +int snd_pcm_plugin_build_copy(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct snd_pcm_plugin *plugin; + int width; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->format != dst_format->format)) + return -ENXIO; + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + + width = snd_pcm_format_physical_width(src_format->format); + if (snd_BUG_ON(width <= 0)) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format, + 0, &plugin); + if (err < 0) + return err; + plugin->transfer = copy_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/io.c b/sound/core/oss/io.c new file mode 100644 index 000000000..d870b2d93 --- /dev/null +++ b/sound/core/oss/io.c @@ -0,0 +1,141 @@ +/* + * PCM I/O Plug-In Interface + * Copyright (c) 1999 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#include +#include +#include +#include +#include "pcm_plugin.h" + +#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1) +#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count) +#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1) +#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count) + +/* + * Basic io plugin + */ + +static snd_pcm_sframes_t io_playback_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (snd_BUG_ON(!src_channels)) + return -ENXIO; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + return pcm_write(plugin->plug, src_channels->area.addr, frames); + } else { + int channel, channels = plugin->dst_format.channels; + void **bufs = (void**)plugin->extra_data; + if (snd_BUG_ON(!bufs)) + return -ENXIO; + for (channel = 0; channel < channels; channel++) { + if (src_channels[channel].enabled) + bufs[channel] = src_channels[channel].area.addr; + else + bufs[channel] = NULL; + } + return pcm_writev(plugin->plug, bufs, frames); + } +} + +static snd_pcm_sframes_t io_capture_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (snd_BUG_ON(!dst_channels)) + return -ENXIO; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + return pcm_read(plugin->plug, dst_channels->area.addr, frames); + } else { + int channel, channels = plugin->dst_format.channels; + void **bufs = (void**)plugin->extra_data; + if (snd_BUG_ON(!bufs)) + return -ENXIO; + for (channel = 0; channel < channels; channel++) { + if (dst_channels[channel].enabled) + bufs[channel] = dst_channels[channel].area.addr; + else + bufs[channel] = NULL; + } + return pcm_readv(plugin->plug, bufs, frames); + } + return 0; +} + +static snd_pcm_sframes_t io_src_channels(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels) +{ + int err; + unsigned int channel; + struct snd_pcm_plugin_channel *v; + err = snd_pcm_plugin_client_channels(plugin, frames, &v); + if (err < 0) + return err; + *channels = v; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v) + v->wanted = 1; + } + return frames; +} + +int snd_pcm_plugin_build_io(struct snd_pcm_substream *plug, + struct snd_pcm_hw_params *params, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct snd_pcm_plugin_format format; + struct snd_pcm_plugin *plugin; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + if (snd_BUG_ON(!plug || !params)) + return -ENXIO; + format.format = params_format(params); + format.rate = params_rate(params); + format.channels = params_channels(params); + err = snd_pcm_plugin_build(plug, "I/O io", + &format, &format, + sizeof(void *) * format.channels, + &plugin); + if (err < 0) + return err; + plugin->access = params_access(params); + if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) { + plugin->transfer = io_playback_transfer; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) + plugin->client_channels = io_src_channels; + } else { + plugin->transfer = io_capture_transfer; + } + + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/linear.c b/sound/core/oss/linear.c new file mode 100644 index 000000000..797d838a2 --- /dev/null +++ b/sound/core/oss/linear.c @@ -0,0 +1,180 @@ +/* + * Linear conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela , + * Abramo Bagnara + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#include +#include +#include +#include "pcm_plugin.h" + +/* + * Basic linear conversion plugin + */ + +struct linear_priv { + int cvt_endian; /* need endian conversion? */ + unsigned int src_ofs; /* byte offset in source format */ + unsigned int dst_ofs; /* byte soffset in destination format */ + unsigned int copy_ofs; /* byte offset in temporary u32 data */ + unsigned int dst_bytes; /* byte size of destination format */ + unsigned int copy_bytes; /* bytes to copy per conversion */ + unsigned int flip; /* MSB flip for signeness, done after endian conv */ +}; + +static inline void do_convert(struct linear_priv *data, + unsigned char *dst, unsigned char *src) +{ + unsigned int tmp = 0; + unsigned char *p = (unsigned char *)&tmp; + + memcpy(p + data->copy_ofs, src + data->src_ofs, data->copy_bytes); + if (data->cvt_endian) + tmp = swab32(tmp); + tmp ^= data->flip; + memcpy(dst, p + data->dst_ofs, data->dst_bytes); +} + +static void convert(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct linear_priv *data = (struct linear_priv *)plugin->extra_data; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + do_convert(data, dst, src); + src += src_step; + dst += dst_step; + } + } +} + +static snd_pcm_sframes_t linear_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + if (snd_BUG_ON(src_channels[channel].area.first % 8 || + src_channels[channel].area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels[channel].area.first % 8 || + dst_channels[channel].area.step % 8)) + return -ENXIO; + } + } +#endif + if (frames > dst_channels[0].frames) + frames = dst_channels[0].frames; + convert(plugin, src_channels, dst_channels, frames); + return frames; +} + +static void init_data(struct linear_priv *data, + snd_pcm_format_t src_format, snd_pcm_format_t dst_format) +{ + int src_le, dst_le, src_bytes, dst_bytes; + + src_bytes = snd_pcm_format_width(src_format) / 8; + dst_bytes = snd_pcm_format_width(dst_format) / 8; + src_le = snd_pcm_format_little_endian(src_format) > 0; + dst_le = snd_pcm_format_little_endian(dst_format) > 0; + + data->dst_bytes = dst_bytes; + data->cvt_endian = src_le != dst_le; + data->copy_bytes = src_bytes < dst_bytes ? src_bytes : dst_bytes; + if (src_le) { + data->copy_ofs = 4 - data->copy_bytes; + data->src_ofs = src_bytes - data->copy_bytes; + } else + data->src_ofs = snd_pcm_format_physical_width(src_format) / 8 - + src_bytes; + if (dst_le) + data->dst_ofs = 4 - data->dst_bytes; + else + data->dst_ofs = snd_pcm_format_physical_width(dst_format) / 8 - + dst_bytes; + if (snd_pcm_format_signed(src_format) != + snd_pcm_format_signed(dst_format)) { + if (dst_le) + data->flip = (__force u32)cpu_to_le32(0x80000000); + else + data->flip = (__force u32)cpu_to_be32(0x80000000); + } +} + +int snd_pcm_plugin_build_linear(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct linear_priv *data; + struct snd_pcm_plugin *plugin; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + if (snd_BUG_ON(!snd_pcm_format_linear(src_format->format) || + !snd_pcm_format_linear(dst_format->format))) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "linear format conversion", + src_format, dst_format, + sizeof(struct linear_priv), &plugin); + if (err < 0) + return err; + data = (struct linear_priv *)plugin->extra_data; + init_data(data, src_format->format, dst_format->format); + plugin->transfer = linear_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c new file mode 100644 index 000000000..cb27bbe75 --- /dev/null +++ b/sound/core/oss/mixer_oss.c @@ -0,0 +1,1451 @@ +/* + * OSS emulation layer for the mixer interface + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OSS_ALSAEMULVER _SIOR ('M', 249, int) + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Mixer OSS emulation for ALSA."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MIXER); + +static int snd_mixer_oss_open(struct inode *inode, struct file *file) +{ + struct snd_card *card; + struct snd_mixer_oss_file *fmixer; + int err; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + card = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIXER); + if (card == NULL) + return -ENODEV; + if (card->mixer_oss == NULL) { + snd_card_unref(card); + return -ENODEV; + } + err = snd_card_file_add(card, file); + if (err < 0) { + snd_card_unref(card); + return err; + } + fmixer = kzalloc(sizeof(*fmixer), GFP_KERNEL); + if (fmixer == NULL) { + snd_card_file_remove(card, file); + snd_card_unref(card); + return -ENOMEM; + } + fmixer->card = card; + fmixer->mixer = card->mixer_oss; + file->private_data = fmixer; + if (!try_module_get(card->module)) { + kfree(fmixer); + snd_card_file_remove(card, file); + snd_card_unref(card); + return -EFAULT; + } + snd_card_unref(card); + return 0; +} + +static int snd_mixer_oss_release(struct inode *inode, struct file *file) +{ + struct snd_mixer_oss_file *fmixer; + + if (file->private_data) { + fmixer = file->private_data; + module_put(fmixer->card->module); + snd_card_file_remove(fmixer->card, file); + kfree(fmixer); + } + return 0; +} + +static int snd_mixer_oss_info(struct snd_mixer_oss_file *fmixer, + mixer_info __user *_info) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + struct mixer_info info; + + memset(&info, 0, sizeof(info)); + strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); + strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); + info.modify_counter = card->mixer_oss_change_count; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_mixer_oss_info_obsolete(struct snd_mixer_oss_file *fmixer, + _old_mixer_info __user *_info) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + _old_mixer_info info; + + memset(&info, 0, sizeof(info)); + strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); + strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_mixer_oss_caps(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + if (mixer->get_recsrc && mixer->put_recsrc) + result |= SOUND_CAP_EXCL_INPUT; + return result; +} + +static int snd_mixer_oss_devmask(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, chn; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_volume || pslot->put_recsrc) + result |= 1 << chn; + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_stereodevs(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, chn; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_volume && pslot->stereo) + result |= 1 << chn; + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_recmask(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ + result = mixer->mask_recsrc; + } else { + struct snd_mixer_oss_slot *pslot; + int chn; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_recsrc) + result |= 1 << chn; + } + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_get_recsrc(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ + unsigned int index; + result = mixer->get_recsrc(fmixer, &index); + if (result < 0) + goto unlock; + result = 1 << index; + } else { + struct snd_mixer_oss_slot *pslot; + int chn; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->get_recsrc) { + int active = 0; + pslot->get_recsrc(fmixer, pslot, &active); + if (active) + result |= 1 << chn; + } + } + } + mixer->oss_recsrc = result; + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsrc) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int chn, active; + unsigned int index; + int result = 0; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + if (mixer->get_recsrc && mixer->put_recsrc) { /* exclusive input */ + if (recsrc & ~mixer->oss_recsrc) + recsrc &= ~mixer->oss_recsrc; + mixer->put_recsrc(fmixer, ffz(~recsrc)); + mixer->get_recsrc(fmixer, &index); + result = 1 << index; + } + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_recsrc) { + active = (recsrc & (1 << chn)) ? 1 : 0; + pslot->put_recsrc(fmixer, pslot, active); + } + } + if (! result) { + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->get_recsrc) { + active = 0; + pslot->get_recsrc(fmixer, pslot, &active); + if (active) + result |= 1 << chn; + } + } + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_get_volume(struct snd_mixer_oss_file *fmixer, int slot) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, left, right; + + if (mixer == NULL || slot > 30) + return -EIO; + mutex_lock(&mixer->reg_mutex); + pslot = &mixer->slots[slot]; + left = pslot->volume[0]; + right = pslot->volume[1]; + if (pslot->get_volume) + result = pslot->get_volume(fmixer, pslot, &left, &right); + if (!pslot->stereo) + right = left; + if (snd_BUG_ON(left < 0 || left > 100)) { + result = -EIO; + goto unlock; + } + if (snd_BUG_ON(right < 0 || right > 100)) { + result = -EIO; + goto unlock; + } + if (result >= 0) { + pslot->volume[0] = left; + pslot->volume[1] = right; + result = (left & 0xff) | ((right & 0xff) << 8); + } + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_set_volume(struct snd_mixer_oss_file *fmixer, + int slot, int volume) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, left = volume & 0xff, right = (volume >> 8) & 0xff; + + if (mixer == NULL || slot > 30) + return -EIO; + mutex_lock(&mixer->reg_mutex); + pslot = &mixer->slots[slot]; + if (left > 100) + left = 100; + if (right > 100) + right = 100; + if (!pslot->stereo) + right = left; + if (pslot->put_volume) + result = pslot->put_volume(fmixer, pslot, left, right); + if (result < 0) + goto unlock; + pslot->volume[0] = left; + pslot->volume[1] = right; + result = (left & 0xff) | ((right & 0xff) << 8); + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_ioctl1(struct snd_mixer_oss_file *fmixer, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int tmp; + + if (snd_BUG_ON(!fmixer)) + return -ENXIO; + if (((cmd >> 8) & 0xff) == 'M') { + switch (cmd) { + case SOUND_MIXER_INFO: + return snd_mixer_oss_info(fmixer, argp); + case SOUND_OLD_MIXER_INFO: + return snd_mixer_oss_info_obsolete(fmixer, argp); + case SOUND_MIXER_WRITE_RECSRC: + if (get_user(tmp, p)) + return -EFAULT; + tmp = snd_mixer_oss_set_recsrc(fmixer, tmp); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case OSS_GETVERSION: + return put_user(SNDRV_OSS_VERSION, p); + case OSS_ALSAEMULVER: + return put_user(1, p); + case SOUND_MIXER_READ_DEVMASK: + tmp = snd_mixer_oss_devmask(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_STEREODEVS: + tmp = snd_mixer_oss_stereodevs(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_RECMASK: + tmp = snd_mixer_oss_recmask(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_CAPS: + tmp = snd_mixer_oss_caps(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_RECSRC: + tmp = snd_mixer_oss_get_recsrc(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } + } + if (cmd & SIOC_IN) { + if (get_user(tmp, p)) + return -EFAULT; + tmp = snd_mixer_oss_set_volume(fmixer, cmd & 0xff, tmp); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } else if (cmd & SIOC_OUT) { + tmp = snd_mixer_oss_get_volume(fmixer, cmd & 0xff); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } + return -ENXIO; +} + +static long snd_mixer_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return snd_mixer_oss_ioctl1(file->private_data, cmd, arg); +} + +int snd_mixer_oss_ioctl_card(struct snd_card *card, unsigned int cmd, unsigned long arg) +{ + struct snd_mixer_oss_file fmixer; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (card->mixer_oss == NULL) + return -ENXIO; + memset(&fmixer, 0, sizeof(fmixer)); + fmixer.card = card; + fmixer.mixer = card->mixer_oss; + return snd_mixer_oss_ioctl1(&fmixer, cmd, arg); +} +EXPORT_SYMBOL(snd_mixer_oss_ioctl_card); + +#ifdef CONFIG_COMPAT +/* all compatible */ +static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return snd_mixer_oss_ioctl1(file->private_data, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define snd_mixer_oss_ioctl_compat NULL +#endif + +/* + * REGISTRATION PART + */ + +static const struct file_operations snd_mixer_oss_f_ops = +{ + .owner = THIS_MODULE, + .open = snd_mixer_oss_open, + .release = snd_mixer_oss_release, + .llseek = no_llseek, + .unlocked_ioctl = snd_mixer_oss_ioctl, + .compat_ioctl = snd_mixer_oss_ioctl_compat, +}; + +/* + * utilities + */ + +static long snd_mixer_oss_conv(long val, long omin, long omax, long nmin, long nmax) +{ + long orange = omax - omin, nrange = nmax - nmin; + + if (orange == 0) + return 0; + return ((nrange * (val - omin)) + (orange / 2)) / orange + nmin; +} + +/* convert from alsa native to oss values (0-100) */ +static long snd_mixer_oss_conv1(long val, long min, long max, int *old) +{ + if (val == snd_mixer_oss_conv(*old, 0, 100, min, max)) + return *old; + return snd_mixer_oss_conv(val, min, max, 0, 100); +} + +/* convert from oss to alsa native values */ +static long snd_mixer_oss_conv2(long val, long min, long max) +{ + return snd_mixer_oss_conv(val, 0, 100, min, max); +} + +#if 0 +static void snd_mixer_oss_recsrce_set(struct snd_card *card, int slot) +{ + struct snd_mixer_oss *mixer = card->mixer_oss; + if (mixer) + mixer->mask_recsrc |= 1 << slot; +} + +static int snd_mixer_oss_recsrce_get(struct snd_card *card, int slot) +{ + struct snd_mixer_oss *mixer = card->mixer_oss; + if (mixer && (mixer->mask_recsrc & (1 << slot))) + return 1; + return 0; +} +#endif + +#define SNDRV_MIXER_OSS_SIGNATURE 0x65999250 + +#define SNDRV_MIXER_OSS_ITEM_GLOBAL 0 +#define SNDRV_MIXER_OSS_ITEM_GSWITCH 1 +#define SNDRV_MIXER_OSS_ITEM_GROUTE 2 +#define SNDRV_MIXER_OSS_ITEM_GVOLUME 3 +#define SNDRV_MIXER_OSS_ITEM_PSWITCH 4 +#define SNDRV_MIXER_OSS_ITEM_PROUTE 5 +#define SNDRV_MIXER_OSS_ITEM_PVOLUME 6 +#define SNDRV_MIXER_OSS_ITEM_CSWITCH 7 +#define SNDRV_MIXER_OSS_ITEM_CROUTE 8 +#define SNDRV_MIXER_OSS_ITEM_CVOLUME 9 +#define SNDRV_MIXER_OSS_ITEM_CAPTURE 10 + +#define SNDRV_MIXER_OSS_ITEM_COUNT 11 + +#define SNDRV_MIXER_OSS_PRESENT_GLOBAL (1<<0) +#define SNDRV_MIXER_OSS_PRESENT_GSWITCH (1<<1) +#define SNDRV_MIXER_OSS_PRESENT_GROUTE (1<<2) +#define SNDRV_MIXER_OSS_PRESENT_GVOLUME (1<<3) +#define SNDRV_MIXER_OSS_PRESENT_PSWITCH (1<<4) +#define SNDRV_MIXER_OSS_PRESENT_PROUTE (1<<5) +#define SNDRV_MIXER_OSS_PRESENT_PVOLUME (1<<6) +#define SNDRV_MIXER_OSS_PRESENT_CSWITCH (1<<7) +#define SNDRV_MIXER_OSS_PRESENT_CROUTE (1<<8) +#define SNDRV_MIXER_OSS_PRESENT_CVOLUME (1<<9) +#define SNDRV_MIXER_OSS_PRESENT_CAPTURE (1<<10) + +struct slot { + unsigned int signature; + unsigned int present; + unsigned int channels; + unsigned int numid[SNDRV_MIXER_OSS_ITEM_COUNT]; + unsigned int capture_item; + struct snd_mixer_oss_assign_table *assigned; + unsigned int allocated: 1; +}; + +#define ID_UNKNOWN ((unsigned int)-1) + +static struct snd_kcontrol *snd_mixer_oss_test_id(struct snd_mixer_oss *mixer, const char *name, int index) +{ + struct snd_card *card = mixer->card; + struct snd_ctl_elem_id id; + + memset(&id, 0, sizeof(id)); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strlcpy(id.name, name, sizeof(id.name)); + id.index = index; + return snd_ctl_find_id(card, &id); +} + +static void snd_mixer_oss_get_volume1_vol(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int *left, int *right) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (kctl->get(kctl, uctl)) + goto __unalloc; + if (uinfo->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN && + uinfo->value.integer.min == 0 && uinfo->value.integer.max == 1) + goto __unalloc; + *left = snd_mixer_oss_conv1(uctl->value.integer.value[0], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[0]); + if (uinfo->count > 1) + *right = snd_mixer_oss_conv1(uctl->value.integer.value[1], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[1]); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static void snd_mixer_oss_get_volume1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int *left, int *right, + int route) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (kctl->get(kctl, uctl)) + goto __unalloc; + if (!uctl->value.integer.value[0]) { + *left = 0; + if (uinfo->count == 1) + *right = 0; + } + if (uinfo->count > 1 && !uctl->value.integer.value[route ? 3 : 1]) + *right = 0; + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static int snd_mixer_oss_get_volume1(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int *left, int *right) +{ + struct slot *slot = pslot->private_data; + + *left = *right = 100; + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); + } + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } + return 0; +} + +static void snd_mixer_oss_put_volume1_vol(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int left, int right) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + int res; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (uinfo->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN && + uinfo->value.integer.min == 0 && uinfo->value.integer.max == 1) + goto __unalloc; + uctl->value.integer.value[0] = snd_mixer_oss_conv2(left, uinfo->value.integer.min, uinfo->value.integer.max); + if (uinfo->count > 1) + uctl->value.integer.value[1] = snd_mixer_oss_conv2(right, uinfo->value.integer.min, uinfo->value.integer.max); + if ((res = kctl->put(kctl, uctl)) < 0) + goto __unalloc; + if (res > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static void snd_mixer_oss_put_volume1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int left, int right, + int route) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + int res; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (uinfo->count > 1) { + uctl->value.integer.value[0] = left > 0 ? 1 : 0; + uctl->value.integer.value[route ? 3 : 1] = right > 0 ? 1 : 0; + if (route) { + uctl->value.integer.value[1] = + uctl->value.integer.value[2] = 0; + } + } else { + uctl->value.integer.value[0] = (left > 0 || right > 0) ? 1 : 0; + } + if ((res = kctl->put(kctl, uctl)) < 0) + goto __unalloc; + if (res > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static int snd_mixer_oss_put_volume1(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int left, int right) +{ + struct slot *slot = pslot->private_data; + + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME) + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, + slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); + } + if (left || right) { + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_CROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], left, right, 1); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } else { + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } + } + return 0; +} + +static int snd_mixer_oss_get_recsrc1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int *active) +{ + struct slot *slot = pslot->private_data; + int left, right; + + left = right = 1; + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], &left, &right, 0); + *active = (left || right) ? 1 : 0; + return 0; +} + +static int snd_mixer_oss_get_recsrc1_route(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int *active) +{ + struct slot *slot = pslot->private_data; + int left, right; + + left = right = 1; + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], &left, &right, 1); + *active = (left || right) ? 1 : 0; + return 0; +} + +static int snd_mixer_oss_put_recsrc1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int active) +{ + struct slot *slot = pslot->private_data; + + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], active, active, 0); + return 0; +} + +static int snd_mixer_oss_put_recsrc1_route(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int active) +{ + struct slot *slot = pslot->private_data; + + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], active, active, 1); + return 0; +} + +static int snd_mixer_oss_get_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned int *active_index) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_kcontrol *kctl; + struct snd_mixer_oss_slot *pslot; + struct slot *slot; + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + int err, idx; + + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) { + err = -ENOMEM; + goto __free_only; + } + down_read(&card->controls_rwsem); + kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); + if (! kctl) { + err = -ENOENT; + goto __unlock; + } + if ((err = kctl->info(kctl, uinfo)) < 0) + goto __unlock; + if ((err = kctl->get(kctl, uctl)) < 0) + goto __unlock; + for (idx = 0; idx < 32; idx++) { + if (!(mixer->mask_recsrc & (1 << idx))) + continue; + pslot = &mixer->slots[idx]; + slot = pslot->private_data; + if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) + continue; + if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) + continue; + if (slot->capture_item == uctl->value.enumerated.item[0]) { + *active_index = idx; + break; + } + } + err = 0; + __unlock: + up_read(&card->controls_rwsem); + __free_only: + kfree(uctl); + kfree(uinfo); + return err; +} + +static int snd_mixer_oss_put_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned int active_index) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_kcontrol *kctl; + struct snd_mixer_oss_slot *pslot; + struct slot *slot = NULL; + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + int err; + unsigned int idx; + + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) { + err = -ENOMEM; + goto __free_only; + } + down_read(&card->controls_rwsem); + kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); + if (! kctl) { + err = -ENOENT; + goto __unlock; + } + if ((err = kctl->info(kctl, uinfo)) < 0) + goto __unlock; + for (idx = 0; idx < 32; idx++) { + if (!(mixer->mask_recsrc & (1 << idx))) + continue; + pslot = &mixer->slots[idx]; + slot = pslot->private_data; + if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) + continue; + if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) + continue; + if (idx == active_index) + break; + slot = NULL; + } + if (! slot) + goto __unlock; + for (idx = 0; idx < uinfo->count; idx++) + uctl->value.enumerated.item[idx] = slot->capture_item; + err = kctl->put(kctl, uctl); + if (err > 0) + snd_ctl_notify(fmixer->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + err = 0; + __unlock: + up_read(&card->controls_rwsem); + __free_only: + kfree(uctl); + kfree(uinfo); + return err; +} + +struct snd_mixer_oss_assign_table { + int oss_id; + const char *name; + int index; +}; + +static int snd_mixer_oss_build_test(struct snd_mixer_oss *mixer, struct slot *slot, const char *name, int index, int item) +{ + struct snd_ctl_elem_info *info; + struct snd_kcontrol *kcontrol; + struct snd_card *card = mixer->card; + int err; + + down_read(&card->controls_rwsem); + kcontrol = snd_mixer_oss_test_id(mixer, name, index); + if (kcontrol == NULL) { + up_read(&card->controls_rwsem); + return 0; + } + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) { + up_read(&card->controls_rwsem); + return -ENOMEM; + } + if ((err = kcontrol->info(kcontrol, info)) < 0) { + up_read(&card->controls_rwsem); + kfree(info); + return err; + } + slot->numid[item] = kcontrol->id.numid; + up_read(&card->controls_rwsem); + if (info->count > slot->channels) + slot->channels = info->count; + slot->present |= 1 << item; + kfree(info); + return 0; +} + +static void snd_mixer_oss_slot_free(struct snd_mixer_oss_slot *chn) +{ + struct slot *p = chn->private_data; + if (p) { + if (p->allocated && p->assigned) { + kfree(p->assigned->name); + kfree(p->assigned); + } + kfree(p); + } +} + +static void mixer_slot_clear(struct snd_mixer_oss_slot *rslot) +{ + int idx = rslot->number; /* remember this */ + if (rslot->private_free) + rslot->private_free(rslot); + memset(rslot, 0, sizeof(*rslot)); + rslot->number = idx; +} + +/* In a separate function to keep gcc 3.2 happy - do NOT merge this in + snd_mixer_oss_build_input! */ +static int snd_mixer_oss_build_test_all(struct snd_mixer_oss *mixer, + struct snd_mixer_oss_assign_table *ptr, + struct slot *slot) +{ + char str[64]; + int err; + + err = snd_mixer_oss_build_test(mixer, slot, ptr->name, ptr->index, + SNDRV_MIXER_OSS_ITEM_GLOBAL); + if (err) + return err; + sprintf(str, "%s Switch", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GSWITCH); + if (err) + return err; + sprintf(str, "%s Route", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GROUTE); + if (err) + return err; + sprintf(str, "%s Volume", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GVOLUME); + if (err) + return err; + sprintf(str, "%s Playback Switch", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PSWITCH); + if (err) + return err; + sprintf(str, "%s Playback Route", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PROUTE); + if (err) + return err; + sprintf(str, "%s Playback Volume", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PVOLUME); + if (err) + return err; + sprintf(str, "%s Capture Switch", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CSWITCH); + if (err) + return err; + sprintf(str, "%s Capture Route", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CROUTE); + if (err) + return err; + sprintf(str, "%s Capture Volume", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CVOLUME); + if (err) + return err; + + return 0; +} + +/* + * build an OSS mixer element. + * ptr_allocated means the entry is dynamically allocated (change via proc file). + * when replace_old = 1, the old entry is replaced with the new one. + */ +static int snd_mixer_oss_build_input(struct snd_mixer_oss *mixer, struct snd_mixer_oss_assign_table *ptr, int ptr_allocated, int replace_old) +{ + struct slot slot; + struct slot *pslot; + struct snd_kcontrol *kctl; + struct snd_mixer_oss_slot *rslot; + char str[64]; + + /* check if already assigned */ + if (mixer->slots[ptr->oss_id].get_volume && ! replace_old) + return 0; + + memset(&slot, 0, sizeof(slot)); + memset(slot.numid, 0xff, sizeof(slot.numid)); /* ID_UNKNOWN */ + if (snd_mixer_oss_build_test_all(mixer, ptr, &slot)) + return 0; + down_read(&mixer->card->controls_rwsem); + if (ptr->index == 0 && (kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0)) != NULL) { + struct snd_ctl_elem_info *uinfo; + + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + if (! uinfo) { + up_read(&mixer->card->controls_rwsem); + return -ENOMEM; + } + + if (kctl->info(kctl, uinfo)) { + up_read(&mixer->card->controls_rwsem); + kfree(uinfo); + return 0; + } + strcpy(str, ptr->name); + if (!strcmp(str, "Master")) + strcpy(str, "Mix"); + if (!strcmp(str, "Master Mono")) + strcpy(str, "Mix Mono"); + slot.capture_item = 0; + if (!strcmp(uinfo->value.enumerated.name, str)) { + slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; + } else { + for (slot.capture_item = 1; slot.capture_item < uinfo->value.enumerated.items; slot.capture_item++) { + uinfo->value.enumerated.item = slot.capture_item; + if (kctl->info(kctl, uinfo)) { + up_read(&mixer->card->controls_rwsem); + kfree(uinfo); + return 0; + } + if (!strcmp(uinfo->value.enumerated.name, str)) { + slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; + break; + } + } + } + kfree(uinfo); + } + up_read(&mixer->card->controls_rwsem); + if (slot.present != 0) { + pslot = kmalloc(sizeof(slot), GFP_KERNEL); + if (! pslot) + return -ENOMEM; + *pslot = slot; + pslot->signature = SNDRV_MIXER_OSS_SIGNATURE; + pslot->assigned = ptr; + pslot->allocated = ptr_allocated; + rslot = &mixer->slots[ptr->oss_id]; + mixer_slot_clear(rslot); + rslot->stereo = slot.channels > 1 ? 1 : 0; + rslot->get_volume = snd_mixer_oss_get_volume1; + rslot->put_volume = snd_mixer_oss_put_volume1; + /* note: ES18xx have both Capture Source and XX Capture Volume !!! */ + if (slot.present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) { + rslot->get_recsrc = snd_mixer_oss_get_recsrc1_sw; + rslot->put_recsrc = snd_mixer_oss_put_recsrc1_sw; + } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CROUTE) { + rslot->get_recsrc = snd_mixer_oss_get_recsrc1_route; + rslot->put_recsrc = snd_mixer_oss_put_recsrc1_route; + } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CAPTURE) { + mixer->mask_recsrc |= 1 << ptr->oss_id; + } + rslot->private_data = pslot; + rslot->private_free = snd_mixer_oss_slot_free; + return 1; + } + return 0; +} + +#ifdef CONFIG_SND_PROC_FS +/* + */ +#define MIXER_VOL(name) [SOUND_MIXER_##name] = #name +static char *oss_mixer_names[SNDRV_OSS_MAX_MIXERS] = { + MIXER_VOL(VOLUME), + MIXER_VOL(BASS), + MIXER_VOL(TREBLE), + MIXER_VOL(SYNTH), + MIXER_VOL(PCM), + MIXER_VOL(SPEAKER), + MIXER_VOL(LINE), + MIXER_VOL(MIC), + MIXER_VOL(CD), + MIXER_VOL(IMIX), + MIXER_VOL(ALTPCM), + MIXER_VOL(RECLEV), + MIXER_VOL(IGAIN), + MIXER_VOL(OGAIN), + MIXER_VOL(LINE1), + MIXER_VOL(LINE2), + MIXER_VOL(LINE3), + MIXER_VOL(DIGITAL1), + MIXER_VOL(DIGITAL2), + MIXER_VOL(DIGITAL3), + MIXER_VOL(PHONEIN), + MIXER_VOL(PHONEOUT), + MIXER_VOL(VIDEO), + MIXER_VOL(RADIO), + MIXER_VOL(MONITOR), +}; + +/* + * /proc interface + */ + +static void snd_mixer_oss_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_mixer_oss *mixer = entry->private_data; + int i; + + mutex_lock(&mixer->reg_mutex); + for (i = 0; i < SNDRV_OSS_MAX_MIXERS; i++) { + struct slot *p; + + if (! oss_mixer_names[i]) + continue; + p = (struct slot *)mixer->slots[i].private_data; + snd_iprintf(buffer, "%s ", oss_mixer_names[i]); + if (p && p->assigned) + snd_iprintf(buffer, "\"%s\" %d\n", + p->assigned->name, + p->assigned->index); + else + snd_iprintf(buffer, "\"\" 0\n"); + } + mutex_unlock(&mixer->reg_mutex); +} + +static void snd_mixer_oss_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_mixer_oss *mixer = entry->private_data; + char line[128], str[32], idxstr[16]; + const char *cptr; + unsigned int idx; + int ch; + struct snd_mixer_oss_assign_table *tbl; + struct slot *slot; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + cptr = snd_info_get_str(str, line, sizeof(str)); + for (ch = 0; ch < SNDRV_OSS_MAX_MIXERS; ch++) + if (oss_mixer_names[ch] && strcmp(oss_mixer_names[ch], str) == 0) + break; + if (ch >= SNDRV_OSS_MAX_MIXERS) { + pr_err("ALSA: mixer_oss: invalid OSS volume '%s'\n", + str); + continue; + } + cptr = snd_info_get_str(str, cptr, sizeof(str)); + if (! *str) { + /* remove the entry */ + mutex_lock(&mixer->reg_mutex); + mixer_slot_clear(&mixer->slots[ch]); + mutex_unlock(&mixer->reg_mutex); + continue; + } + snd_info_get_str(idxstr, cptr, sizeof(idxstr)); + idx = simple_strtoul(idxstr, NULL, 10); + if (idx >= 0x4000) { /* too big */ + pr_err("ALSA: mixer_oss: invalid index %d\n", idx); + continue; + } + mutex_lock(&mixer->reg_mutex); + slot = (struct slot *)mixer->slots[ch].private_data; + if (slot && slot->assigned && + slot->assigned->index == idx && ! strcmp(slot->assigned->name, str)) + /* not changed */ + goto __unlock; + tbl = kmalloc(sizeof(*tbl), GFP_KERNEL); + if (!tbl) + goto __unlock; + tbl->oss_id = ch; + tbl->name = kstrdup(str, GFP_KERNEL); + if (! tbl->name) { + kfree(tbl); + goto __unlock; + } + tbl->index = idx; + if (snd_mixer_oss_build_input(mixer, tbl, 1, 1) <= 0) { + kfree(tbl->name); + kfree(tbl); + } + __unlock: + mutex_unlock(&mixer->reg_mutex); + } +} + +static void snd_mixer_oss_proc_init(struct snd_mixer_oss *mixer) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(mixer->card, "oss_mixer", + mixer->card->proc_root); + if (! entry) + return; + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->mode = S_IFREG | 0644; + entry->c.text.read = snd_mixer_oss_proc_read; + entry->c.text.write = snd_mixer_oss_proc_write; + entry->private_data = mixer; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + mixer->proc_entry = entry; +} + +static void snd_mixer_oss_proc_done(struct snd_mixer_oss *mixer) +{ + snd_info_free_entry(mixer->proc_entry); + mixer->proc_entry = NULL; +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_mixer_oss_proc_init(mix) +#define snd_mixer_oss_proc_done(mix) +#endif /* CONFIG_SND_PROC_FS */ + +static void snd_mixer_oss_build(struct snd_mixer_oss *mixer) +{ + static struct snd_mixer_oss_assign_table table[] = { + { SOUND_MIXER_VOLUME, "Master", 0 }, + { SOUND_MIXER_VOLUME, "Front", 0 }, /* fallback */ + { SOUND_MIXER_BASS, "Tone Control - Bass", 0 }, + { SOUND_MIXER_TREBLE, "Tone Control - Treble", 0 }, + { SOUND_MIXER_SYNTH, "Synth", 0 }, + { SOUND_MIXER_SYNTH, "FM", 0 }, /* fallback */ + { SOUND_MIXER_SYNTH, "Music", 0 }, /* fallback */ + { SOUND_MIXER_PCM, "PCM", 0 }, + { SOUND_MIXER_SPEAKER, "Beep", 0 }, + { SOUND_MIXER_SPEAKER, "PC Speaker", 0 }, /* fallback */ + { SOUND_MIXER_SPEAKER, "Speaker", 0 }, /* fallback */ + { SOUND_MIXER_LINE, "Line", 0 }, + { SOUND_MIXER_MIC, "Mic", 0 }, + { SOUND_MIXER_CD, "CD", 0 }, + { SOUND_MIXER_IMIX, "Monitor Mix", 0 }, + { SOUND_MIXER_ALTPCM, "PCM", 1 }, + { SOUND_MIXER_ALTPCM, "Headphone", 0 }, /* fallback */ + { SOUND_MIXER_ALTPCM, "Wave", 0 }, /* fallback */ + { SOUND_MIXER_RECLEV, "-- nothing --", 0 }, + { SOUND_MIXER_IGAIN, "Capture", 0 }, + { SOUND_MIXER_OGAIN, "Playback", 0 }, + { SOUND_MIXER_LINE1, "Aux", 0 }, + { SOUND_MIXER_LINE2, "Aux", 1 }, + { SOUND_MIXER_LINE3, "Aux", 2 }, + { SOUND_MIXER_DIGITAL1, "Digital", 0 }, + { SOUND_MIXER_DIGITAL1, "IEC958", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL1, "IEC958 Optical", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL1, "IEC958 Coaxial", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL2, "Digital", 1 }, + { SOUND_MIXER_DIGITAL3, "Digital", 2 }, + { SOUND_MIXER_PHONEIN, "Phone", 0 }, + { SOUND_MIXER_PHONEOUT, "Master Mono", 0 }, + { SOUND_MIXER_PHONEOUT, "Speaker", 0 }, /*fallback*/ + { SOUND_MIXER_PHONEOUT, "Mono", 0 }, /*fallback*/ + { SOUND_MIXER_PHONEOUT, "Phone", 0 }, /* fallback */ + { SOUND_MIXER_VIDEO, "Video", 0 }, + { SOUND_MIXER_RADIO, "Radio", 0 }, + { SOUND_MIXER_MONITOR, "Monitor", 0 } + }; + unsigned int idx; + + for (idx = 0; idx < ARRAY_SIZE(table); idx++) + snd_mixer_oss_build_input(mixer, &table[idx], 0, 0); + if (mixer->mask_recsrc) { + mixer->get_recsrc = snd_mixer_oss_get_recsrc2; + mixer->put_recsrc = snd_mixer_oss_put_recsrc2; + } +} + +/* + * + */ + +static int snd_mixer_oss_free1(void *private) +{ + struct snd_mixer_oss *mixer = private; + struct snd_card *card; + int idx; + + if (!mixer) + return 0; + card = mixer->card; + if (snd_BUG_ON(mixer != card->mixer_oss)) + return -ENXIO; + card->mixer_oss = NULL; + for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) { + struct snd_mixer_oss_slot *chn = &mixer->slots[idx]; + if (chn->private_free) + chn->private_free(chn); + } + kfree(mixer); + return 0; +} + +static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) +{ + struct snd_mixer_oss *mixer; + + if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) { + int idx, err; + + mixer = kcalloc(2, sizeof(*mixer), GFP_KERNEL); + if (mixer == NULL) + return -ENOMEM; + mutex_init(&mixer->reg_mutex); + if ((err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, + card, 0, + &snd_mixer_oss_f_ops, card)) < 0) { + dev_err(card->dev, + "unable to register OSS mixer device %i:%i\n", + card->number, 0); + kfree(mixer); + return err; + } + mixer->oss_dev_alloc = 1; + mixer->card = card; + if (*card->mixername) + strlcpy(mixer->name, card->mixername, sizeof(mixer->name)); + else + snprintf(mixer->name, sizeof(mixer->name), + "mixer%i", card->number); +#ifdef SNDRV_OSS_INFO_DEV_MIXERS + snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIXERS, + card->number, + mixer->name); +#endif + for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) + mixer->slots[idx].number = idx; + card->mixer_oss = mixer; + snd_mixer_oss_build(mixer); + snd_mixer_oss_proc_init(mixer); + } else { + mixer = card->mixer_oss; + if (mixer == NULL) + return 0; + if (mixer->oss_dev_alloc) { +#ifdef SNDRV_OSS_INFO_DEV_MIXERS + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIXERS, mixer->card->number); +#endif + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0); + mixer->oss_dev_alloc = 0; + } + if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) + return 0; + snd_mixer_oss_proc_done(mixer); + return snd_mixer_oss_free1(mixer); + } + return 0; +} + +static int __init alsa_mixer_oss_init(void) +{ + int idx; + + snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler; + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (snd_cards[idx]) + snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_REGISTER); + } + return 0; +} + +static void __exit alsa_mixer_oss_exit(void) +{ + int idx; + + snd_mixer_oss_notify_callback = NULL; + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (snd_cards[idx]) + snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_FREE); + } +} + +module_init(alsa_mixer_oss_init) +module_exit(alsa_mixer_oss_exit) diff --git a/sound/core/oss/mulaw.c b/sound/core/oss/mulaw.c new file mode 100644 index 000000000..fe27034f2 --- /dev/null +++ b/sound/core/oss/mulaw.c @@ -0,0 +1,346 @@ +/* + * Mu-Law conversion Plug-In Interface + * Copyright (c) 1999 by Jaroslav Kysela + * Uros Bizjak + * + * Based on reference implementation by Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#include +#include +#include +#include "pcm_plugin.h" + +#define SIGN_BIT (0x80) /* Sign bit for a u-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of u-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +#define BIAS (0x84) /* Bias for linear code. */ + +/* + * linear2ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +static unsigned char linear2ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + /* Get the sign and the magnitude of the value. */ + if (pcm_val < 0) { + pcm_val = BIAS - pcm_val; + mask = 0x7F; + } else { + pcm_val += BIAS; + mask = 0xFF; + } + if (pcm_val > 0x7FFF) + pcm_val = 0x7FFF; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF); + return uval ^ mask; +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +static int ulaw2linear(unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +/* + * Basic Mu-Law plugin + */ + +typedef void (*mulaw_f)(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames); + +struct mulaw_priv { + mulaw_f func; + int cvt_endian; /* need endian conversion? */ + unsigned int native_ofs; /* byte offset in native format */ + unsigned int copy_ofs; /* byte offset in s16 format */ + unsigned int native_bytes; /* byte size of the native format */ + unsigned int copy_bytes; /* bytes to copy per conversion */ + u16 flip; /* MSB flip for signedness, done after endian conversion */ +}; + +static inline void cvt_s16_to_native(struct mulaw_priv *data, + unsigned char *dst, u16 sample) +{ + sample ^= data->flip; + if (data->cvt_endian) + sample = swab16(sample); + if (data->native_bytes > data->copy_bytes) + memset(dst, 0, data->native_bytes); + memcpy(dst + data->native_ofs, (char *)&sample + data->copy_ofs, + data->copy_bytes); +} + +static void mulaw_decode(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + signed short sample = ulaw2linear(*src); + cvt_s16_to_native(data, dst, sample); + src += src_step; + dst += dst_step; + } + } +} + +static inline signed short cvt_native_to_s16(struct mulaw_priv *data, + unsigned char *src) +{ + u16 sample = 0; + memcpy((char *)&sample + data->copy_ofs, src + data->native_ofs, + data->copy_bytes); + if (data->cvt_endian) + sample = swab16(sample); + sample ^= data->flip; + return (signed short)sample; +} + +static void mulaw_encode(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + signed short sample = cvt_native_to_s16(data, src); + *dst = linear2ulaw(sample); + src += src_step; + dst += dst_step; + } + } +} + +static snd_pcm_sframes_t mulaw_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct mulaw_priv *data; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + if (snd_BUG_ON(src_channels[channel].area.first % 8 || + src_channels[channel].area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels[channel].area.first % 8 || + dst_channels[channel].area.step % 8)) + return -ENXIO; + } + } +#endif + if (frames > dst_channels[0].frames) + frames = dst_channels[0].frames; + data = (struct mulaw_priv *)plugin->extra_data; + data->func(plugin, src_channels, dst_channels, frames); + return frames; +} + +static void init_data(struct mulaw_priv *data, snd_pcm_format_t format) +{ +#ifdef SNDRV_LITTLE_ENDIAN + data->cvt_endian = snd_pcm_format_big_endian(format) > 0; +#else + data->cvt_endian = snd_pcm_format_little_endian(format) > 0; +#endif + if (!snd_pcm_format_signed(format)) + data->flip = 0x8000; + data->native_bytes = snd_pcm_format_physical_width(format) / 8; + data->copy_bytes = data->native_bytes < 2 ? 1 : 2; + if (snd_pcm_format_little_endian(format)) { + data->native_ofs = data->native_bytes - data->copy_bytes; + data->copy_ofs = 2 - data->copy_bytes; + } else { + /* S24 in 4bytes need an 1 byte offset */ + data->native_ofs = data->native_bytes - + snd_pcm_format_width(format) / 8; + } +} + +int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct mulaw_priv *data; + struct snd_pcm_plugin *plugin; + struct snd_pcm_plugin_format *format; + mulaw_f func; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + + if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) { + format = src_format; + func = mulaw_encode; + } + else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) { + format = dst_format; + func = mulaw_decode; + } + else { + snd_BUG(); + return -EINVAL; + } + if (!snd_pcm_format_linear(format->format)) + return -EINVAL; + + err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion", + src_format, dst_format, + sizeof(struct mulaw_priv), &plugin); + if (err < 0) + return err; + data = (struct mulaw_priv *)plugin->extra_data; + data->func = func; + init_data(data, format->format); + plugin->transfer = mulaw_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c new file mode 100644 index 000000000..c85fa8528 --- /dev/null +++ b/sound/core/oss/pcm_oss.c @@ -0,0 +1,3229 @@ +/* + * Digital Audio (PCM) abstract layer / OSS compatible + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#if 0 +#define PLUGIN_DEBUG +#endif +#if 0 +#define OSS_DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcm_plugin.h" +#include +#include +#include +#include + +#define OSS_ALSAEMULVER _SIOR ('M', 249, int) + +static int dsp_map[SNDRV_CARDS]; +static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +static bool nonblock_open = 1; + +MODULE_AUTHOR("Jaroslav Kysela , Abramo Bagnara "); +MODULE_DESCRIPTION("PCM OSS emulation for ALSA."); +MODULE_LICENSE("GPL"); +module_param_array(dsp_map, int, NULL, 0444); +MODULE_PARM_DESC(dsp_map, "PCM device number assigned to 1st OSS device."); +module_param_array(adsp_map, int, NULL, 0444); +MODULE_PARM_DESC(adsp_map, "PCM device number assigned to 2nd OSS device."); +module_param(nonblock_open, bool, 0644); +MODULE_PARM_DESC(nonblock_open, "Don't block opening busy PCM devices."); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM1); + +static int snd_pcm_oss_get_rate(struct snd_pcm_oss_file *pcm_oss_file); +static int snd_pcm_oss_get_channels(struct snd_pcm_oss_file *pcm_oss_file); +static int snd_pcm_oss_get_format(struct snd_pcm_oss_file *pcm_oss_file); + +/* + * helper functions to process hw_params + */ +static int snd_interval_refine_min(struct snd_interval *i, unsigned int min, int openmin) +{ + int changed = 0; + if (i->min < min) { + i->min = min; + i->openmin = openmin; + changed = 1; + } else if (i->min == min && !i->openmin && openmin) { + i->openmin = 1; + changed = 1; + } + if (i->integer) { + if (i->openmin) { + i->min++; + i->openmin = 0; + } + } + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} + +static int snd_interval_refine_max(struct snd_interval *i, unsigned int max, int openmax) +{ + int changed = 0; + if (i->max > max) { + i->max = max; + i->openmax = openmax; + changed = 1; + } else if (i->max == max && !i->openmax && openmax) { + i->openmax = 1; + changed = 1; + } + if (i->integer) { + if (i->openmax) { + i->max--; + i->openmax = 0; + } + } + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} + +static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) +{ + struct snd_interval t; + t.empty = 0; + t.min = t.max = val; + t.openmin = t.openmax = 0; + t.integer = 1; + return snd_interval_refine(i, &t); +} + +/** + * snd_pcm_hw_param_value_min + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Return the minimum value for field PAR. + */ +static unsigned int +snd_pcm_hw_param_value_min(const struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + if (dir) + *dir = 0; + return snd_mask_min(hw_param_mask_c(params, var)); + } + if (hw_is_interval(var)) { + const struct snd_interval *i = hw_param_interval_c(params, var); + if (dir) + *dir = i->openmin; + return snd_interval_min(i); + } + return -EINVAL; +} + +/** + * snd_pcm_hw_param_value_max + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Return the maximum value for field PAR. + */ +static int +snd_pcm_hw_param_value_max(const struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + if (dir) + *dir = 0; + return snd_mask_max(hw_param_mask_c(params, var)); + } + if (hw_is_interval(var)) { + const struct snd_interval *i = hw_param_interval_c(params, var); + if (dir) + *dir = - (int) i->openmax; + return snd_interval_max(i); + } + return -EINVAL; +} + +static int _snd_pcm_hw_param_mask(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, + const struct snd_mask *val) +{ + int changed; + changed = snd_mask_refine(hw_param_mask(params, var), val); + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +static int snd_pcm_hw_param_mask(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, + const struct snd_mask *val) +{ + int changed = _snd_pcm_hw_param_mask(params, var, val); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return 0; +} + +static int _snd_pcm_hw_param_min(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + int open = 0; + if (dir) { + if (dir > 0) { + open = 1; + } else if (dir < 0) { + if (val > 0) { + open = 1; + val--; + } + } + } + if (hw_is_mask(var)) + changed = snd_mask_refine_min(hw_param_mask(params, var), + val + !!open); + else if (hw_is_interval(var)) + changed = snd_interval_refine_min(hw_param_interval(params, var), + val, open); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_min + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @val: minimal value + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS remove from PAR all + * values < VAL. Reduce configuration space accordingly. + * Return new minimum or -EINVAL if the configuration space is empty + */ +static int snd_pcm_hw_param_min(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int *dir) +{ + int changed = _snd_pcm_hw_param_min(params, var, val, dir ? *dir : 0); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value_min(params, var, dir); +} + +static int _snd_pcm_hw_param_max(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + int open = 0; + if (dir) { + if (dir < 0) { + open = 1; + } else if (dir > 0) { + open = 1; + val++; + } + } + if (hw_is_mask(var)) { + if (val == 0 && open) { + snd_mask_none(hw_param_mask(params, var)); + changed = -EINVAL; + } else + changed = snd_mask_refine_max(hw_param_mask(params, var), + val - !!open); + } else if (hw_is_interval(var)) + changed = snd_interval_refine_max(hw_param_interval(params, var), + val, open); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_max + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @val: maximal value + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS remove from PAR all + * values >= VAL + 1. Reduce configuration space accordingly. + * Return new maximum or -EINVAL if the configuration space is empty + */ +static int snd_pcm_hw_param_max(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int *dir) +{ + int changed = _snd_pcm_hw_param_max(params, var, val, dir ? *dir : 0); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value_max(params, var, dir); +} + +static int boundary_sub(int a, int adir, + int b, int bdir, + int *c, int *cdir) +{ + adir = adir < 0 ? -1 : (adir > 0 ? 1 : 0); + bdir = bdir < 0 ? -1 : (bdir > 0 ? 1 : 0); + *c = a - b; + *cdir = adir - bdir; + if (*cdir == -2) { + (*c)--; + } else if (*cdir == 2) { + (*c)++; + } + return 0; +} + +static int boundary_lt(unsigned int a, int adir, + unsigned int b, int bdir) +{ + if (adir < 0) { + a--; + adir = 1; + } else if (adir > 0) + adir = 1; + if (bdir < 0) { + b--; + bdir = 1; + } else if (bdir > 0) + bdir = 1; + return a < b || (a == b && adir < bdir); +} + +/* Return 1 if min is nearer to best than max */ +static int boundary_nearer(int min, int mindir, + int best, int bestdir, + int max, int maxdir) +{ + int dmin, dmindir; + int dmax, dmaxdir; + boundary_sub(best, bestdir, min, mindir, &dmin, &dmindir); + boundary_sub(max, maxdir, best, bestdir, &dmax, &dmaxdir); + return boundary_lt(dmin, dmindir, dmax, dmaxdir); +} + +/** + * snd_pcm_hw_param_near + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @best: value to set + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS set PAR to the available value + * nearest to VAL. Reduce configuration space accordingly. + * This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS, + * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT. + * Return the value found. + */ +static int snd_pcm_hw_param_near(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int best, + int *dir) +{ + struct snd_pcm_hw_params *save = NULL; + int v; + unsigned int saved_min; + int last = 0; + int min, max; + int mindir, maxdir; + int valdir = dir ? *dir : 0; + /* FIXME */ + if (best > INT_MAX) + best = INT_MAX; + min = max = best; + mindir = maxdir = valdir; + if (maxdir > 0) + maxdir = 0; + else if (maxdir == 0) + maxdir = -1; + else { + maxdir = 1; + max--; + } + save = kmalloc(sizeof(*save), GFP_KERNEL); + if (save == NULL) + return -ENOMEM; + *save = *params; + saved_min = min; + min = snd_pcm_hw_param_min(pcm, params, var, min, &mindir); + if (min >= 0) { + struct snd_pcm_hw_params *params1; + if (max < 0) + goto _end; + if ((unsigned int)min == saved_min && mindir == valdir) + goto _end; + params1 = kmalloc(sizeof(*params1), GFP_KERNEL); + if (params1 == NULL) { + kfree(save); + return -ENOMEM; + } + *params1 = *save; + max = snd_pcm_hw_param_max(pcm, params1, var, max, &maxdir); + if (max < 0) { + kfree(params1); + goto _end; + } + if (boundary_nearer(max, maxdir, best, valdir, min, mindir)) { + *params = *params1; + last = 1; + } + kfree(params1); + } else { + *params = *save; + max = snd_pcm_hw_param_max(pcm, params, var, max, &maxdir); + if (max < 0) { + kfree(save); + return max; + } + last = 1; + } + _end: + kfree(save); + if (last) + v = snd_pcm_hw_param_last(pcm, params, var, dir); + else + v = snd_pcm_hw_param_first(pcm, params, var, dir); + return v; +} + +static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + if (hw_is_mask(var)) { + struct snd_mask *m = hw_param_mask(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_mask_none(m); + } else { + if (dir > 0) + val++; + else if (dir < 0) + val--; + changed = snd_mask_refine_set(hw_param_mask(params, var), val); + } + } else if (hw_is_interval(var)) { + struct snd_interval *i = hw_param_interval(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_interval_none(i); + } else if (dir == 0) + changed = snd_interval_refine_set(i, val); + else { + struct snd_interval t; + t.openmin = 1; + t.openmax = 1; + t.empty = 0; + t.integer = 0; + if (dir < 0) { + t.min = val - 1; + t.max = val; + } else { + t.min = val; + t.max = val+1; + } + changed = snd_interval_refine(i, &t); + } + } else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_set + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @val: value to set + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS remove from PAR all + * values != VAL. Reduce configuration space accordingly. + * Return VAL or -EINVAL if the configuration space is empty + */ +static int snd_pcm_hw_param_set(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed = _snd_pcm_hw_param_set(params, var, val, dir); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value(params, var, NULL); +} + +static int _snd_pcm_hw_param_setinteger(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + int changed; + changed = snd_interval_setinteger(hw_param_interval(params, var)); + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/* + * plugin + */ + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS +static int snd_pcm_oss_plugin_clear(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_plugin *plugin, *next; + + plugin = runtime->oss.plugin_first; + while (plugin) { + next = plugin->next; + snd_pcm_plugin_free(plugin); + plugin = next; + } + runtime->oss.plugin_first = runtime->oss.plugin_last = NULL; + return 0; +} + +static int snd_pcm_plugin_insert(struct snd_pcm_plugin *plugin) +{ + struct snd_pcm_runtime *runtime = plugin->plug->runtime; + plugin->next = runtime->oss.plugin_first; + plugin->prev = NULL; + if (runtime->oss.plugin_first) { + runtime->oss.plugin_first->prev = plugin; + runtime->oss.plugin_first = plugin; + } else { + runtime->oss.plugin_last = + runtime->oss.plugin_first = plugin; + } + return 0; +} + +int snd_pcm_plugin_append(struct snd_pcm_plugin *plugin) +{ + struct snd_pcm_runtime *runtime = plugin->plug->runtime; + plugin->next = NULL; + plugin->prev = runtime->oss.plugin_last; + if (runtime->oss.plugin_last) { + runtime->oss.plugin_last->next = plugin; + runtime->oss.plugin_last = plugin; + } else { + runtime->oss.plugin_last = + runtime->oss.plugin_first = plugin; + } + return 0; +} +#endif /* CONFIG_SND_PCM_OSS_PLUGINS */ + +static long snd_pcm_oss_bytes(struct snd_pcm_substream *substream, long frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + long buffer_size = snd_pcm_lib_buffer_bytes(substream); + long bytes = frames_to_bytes(runtime, frames); + if (buffer_size == runtime->oss.buffer_bytes) + return bytes; +#if BITS_PER_LONG >= 64 + return runtime->oss.buffer_bytes * bytes / buffer_size; +#else + { + u64 bsize = (u64)runtime->oss.buffer_bytes * (u64)bytes; + return div_u64(bsize, buffer_size); + } +#endif +} + +static long snd_pcm_alsa_frames(struct snd_pcm_substream *substream, long bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + long buffer_size = snd_pcm_lib_buffer_bytes(substream); + if (buffer_size == runtime->oss.buffer_bytes) + return bytes_to_frames(runtime, bytes); + return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes); +} + +static inline +snd_pcm_uframes_t get_hw_ptr_period(struct snd_pcm_runtime *runtime) +{ + return runtime->hw_ptr_interrupt; +} + +/* define extended formats in the recent OSS versions (if any) */ +/* linear formats */ +#define AFMT_S32_LE 0x00001000 +#define AFMT_S32_BE 0x00002000 +#define AFMT_S24_LE 0x00008000 +#define AFMT_S24_BE 0x00010000 +#define AFMT_S24_PACKED 0x00040000 + +/* other supported formats */ +#define AFMT_FLOAT 0x00004000 +#define AFMT_SPDIF_RAW 0x00020000 + +/* unsupported formats */ +#define AFMT_AC3 0x00000400 +#define AFMT_VORBIS 0x00000800 + +static snd_pcm_format_t snd_pcm_oss_format_from(int format) +{ + switch (format) { + case AFMT_MU_LAW: return SNDRV_PCM_FORMAT_MU_LAW; + case AFMT_A_LAW: return SNDRV_PCM_FORMAT_A_LAW; + case AFMT_IMA_ADPCM: return SNDRV_PCM_FORMAT_IMA_ADPCM; + case AFMT_U8: return SNDRV_PCM_FORMAT_U8; + case AFMT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE; + case AFMT_S16_BE: return SNDRV_PCM_FORMAT_S16_BE; + case AFMT_S8: return SNDRV_PCM_FORMAT_S8; + case AFMT_U16_LE: return SNDRV_PCM_FORMAT_U16_LE; + case AFMT_U16_BE: return SNDRV_PCM_FORMAT_U16_BE; + case AFMT_MPEG: return SNDRV_PCM_FORMAT_MPEG; + case AFMT_S32_LE: return SNDRV_PCM_FORMAT_S32_LE; + case AFMT_S32_BE: return SNDRV_PCM_FORMAT_S32_BE; + case AFMT_S24_LE: return SNDRV_PCM_FORMAT_S24_LE; + case AFMT_S24_BE: return SNDRV_PCM_FORMAT_S24_BE; + case AFMT_S24_PACKED: return SNDRV_PCM_FORMAT_S24_3LE; + case AFMT_FLOAT: return SNDRV_PCM_FORMAT_FLOAT; + case AFMT_SPDIF_RAW: return SNDRV_PCM_FORMAT_IEC958_SUBFRAME; + default: return SNDRV_PCM_FORMAT_U8; + } +} + +static int snd_pcm_oss_format_to(snd_pcm_format_t format) +{ + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW; + case SNDRV_PCM_FORMAT_A_LAW: return AFMT_A_LAW; + case SNDRV_PCM_FORMAT_IMA_ADPCM: return AFMT_IMA_ADPCM; + case SNDRV_PCM_FORMAT_U8: return AFMT_U8; + case SNDRV_PCM_FORMAT_S16_LE: return AFMT_S16_LE; + case SNDRV_PCM_FORMAT_S16_BE: return AFMT_S16_BE; + case SNDRV_PCM_FORMAT_S8: return AFMT_S8; + case SNDRV_PCM_FORMAT_U16_LE: return AFMT_U16_LE; + case SNDRV_PCM_FORMAT_U16_BE: return AFMT_U16_BE; + case SNDRV_PCM_FORMAT_MPEG: return AFMT_MPEG; + case SNDRV_PCM_FORMAT_S32_LE: return AFMT_S32_LE; + case SNDRV_PCM_FORMAT_S32_BE: return AFMT_S32_BE; + case SNDRV_PCM_FORMAT_S24_LE: return AFMT_S24_LE; + case SNDRV_PCM_FORMAT_S24_BE: return AFMT_S24_BE; + case SNDRV_PCM_FORMAT_S24_3LE: return AFMT_S24_PACKED; + case SNDRV_PCM_FORMAT_FLOAT: return AFMT_FLOAT; + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME: return AFMT_SPDIF_RAW; + default: return -EINVAL; + } +} + +static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *oss_params, + struct snd_pcm_hw_params *slave_params) +{ + ssize_t s; + ssize_t oss_buffer_size; + ssize_t oss_period_size, oss_periods; + ssize_t min_period_size, max_period_size; + struct snd_pcm_runtime *runtime = substream->runtime; + size_t oss_frame_size; + + oss_frame_size = snd_pcm_format_physical_width(params_format(oss_params)) * + params_channels(oss_params) / 8; + + oss_buffer_size = snd_pcm_hw_param_value_max(slave_params, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + NULL); + if (oss_buffer_size <= 0) + return -EINVAL; + oss_buffer_size = snd_pcm_plug_client_size(substream, + oss_buffer_size * oss_frame_size); + if (oss_buffer_size <= 0) + return -EINVAL; + oss_buffer_size = rounddown_pow_of_two(oss_buffer_size); + if (atomic_read(&substream->mmap_count)) { + if (oss_buffer_size > runtime->oss.mmap_bytes) + oss_buffer_size = runtime->oss.mmap_bytes; + } + + if (substream->oss.setup.period_size > 16) + oss_period_size = substream->oss.setup.period_size; + else if (runtime->oss.fragshift) { + oss_period_size = 1 << runtime->oss.fragshift; + if (oss_period_size > oss_buffer_size / 2) + oss_period_size = oss_buffer_size / 2; + } else { + int sd; + size_t bytes_per_sec = params_rate(oss_params) * snd_pcm_format_physical_width(params_format(oss_params)) * params_channels(oss_params) / 8; + + oss_period_size = oss_buffer_size; + do { + oss_period_size /= 2; + } while (oss_period_size > bytes_per_sec); + if (runtime->oss.subdivision == 0) { + sd = 4; + if (oss_period_size / sd > 4096) + sd *= 2; + if (oss_period_size / sd < 4096) + sd = 1; + } else + sd = runtime->oss.subdivision; + oss_period_size /= sd; + if (oss_period_size < 16) + oss_period_size = 16; + } + + min_period_size = snd_pcm_plug_client_size(substream, + snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); + if (min_period_size > 0) { + min_period_size *= oss_frame_size; + min_period_size = roundup_pow_of_two(min_period_size); + if (oss_period_size < min_period_size) + oss_period_size = min_period_size; + } + + max_period_size = snd_pcm_plug_client_size(substream, + snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); + if (max_period_size > 0) { + max_period_size *= oss_frame_size; + max_period_size = rounddown_pow_of_two(max_period_size); + if (oss_period_size > max_period_size) + oss_period_size = max_period_size; + } + + oss_periods = oss_buffer_size / oss_period_size; + + if (substream->oss.setup.periods > 1) + oss_periods = substream->oss.setup.periods; + + s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); + if (s > 0 && runtime->oss.maxfrags && s > runtime->oss.maxfrags) + s = runtime->oss.maxfrags; + if (oss_periods > s) + oss_periods = s; + + s = snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); + if (s < 2) + s = 2; + if (oss_periods < s) + oss_periods = s; + + while (oss_period_size * oss_periods > oss_buffer_size) + oss_period_size /= 2; + + if (oss_period_size < 16) + return -EINVAL; + + /* don't allocate too large period; 1MB period must be enough */ + if (oss_period_size > 1024 * 1024) + return -ENOMEM; + + runtime->oss.period_bytes = oss_period_size; + runtime->oss.period_frames = 1; + runtime->oss.periods = oss_periods; + return 0; +} + +static int choose_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, unsigned int best_rate) +{ + const struct snd_interval *it; + struct snd_pcm_hw_params *save; + unsigned int rate, prev; + + save = kmalloc(sizeof(*save), GFP_KERNEL); + if (save == NULL) + return -ENOMEM; + *save = *params; + it = hw_param_interval_c(save, SNDRV_PCM_HW_PARAM_RATE); + + /* try multiples of the best rate */ + rate = best_rate; + for (;;) { + if (it->max < rate || (it->max == rate && it->openmax)) + break; + if (it->min < rate || (it->min == rate && !it->openmin)) { + int ret; + ret = snd_pcm_hw_param_set(substream, params, + SNDRV_PCM_HW_PARAM_RATE, + rate, 0); + if (ret == (int)rate) { + kfree(save); + return rate; + } + *params = *save; + } + prev = rate; + rate += best_rate; + if (rate <= prev) + break; + } + + /* not found, use the nearest rate */ + kfree(save); + return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL); +} + +/* parameter locking: returns immediately if tried during streaming */ +static int lock_params(struct snd_pcm_runtime *runtime) +{ + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + if (atomic_read(&runtime->oss.rw_ref)) { + mutex_unlock(&runtime->oss.params_lock); + return -EBUSY; + } + return 0; +} + +static void unlock_params(struct snd_pcm_runtime *runtime) +{ + mutex_unlock(&runtime->oss.params_lock); +} + +/* call with params_lock held */ +static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hw_params *params, *sparams; + struct snd_pcm_sw_params *sw_params; + ssize_t oss_buffer_size, oss_period_size; + size_t oss_frame_size; + int err; + int direct; + snd_pcm_format_t format, sformat; + int n; + const struct snd_mask *sformat_mask; + struct snd_mask mask; + + if (!runtime->oss.params) + return 0; + sw_params = kzalloc(sizeof(*sw_params), GFP_KERNEL); + params = kmalloc(sizeof(*params), GFP_KERNEL); + sparams = kmalloc(sizeof(*sparams), GFP_KERNEL); + if (!sw_params || !params || !sparams) { + err = -ENOMEM; + goto failure; + } + + if (atomic_read(&substream->mmap_count)) + direct = 1; + else + direct = substream->oss.setup.direct; + + _snd_pcm_hw_params_any(sparams); + _snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS); + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&substream->mmap_count)) + snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { + snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) + snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { + pcm_dbg(substream->pcm, "No usable accesses\n"); + err = -EINVAL; + goto failure; + } + + err = choose_rate(substream, sparams, runtime->oss.rate); + if (err < 0) + goto failure; + err = snd_pcm_hw_param_near(substream, sparams, + SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, NULL); + if (err < 0) + goto failure; + + format = snd_pcm_oss_format_from(runtime->oss.format); + + sformat_mask = hw_param_mask_c(sparams, SNDRV_PCM_HW_PARAM_FORMAT); + if (direct) + sformat = format; + else + sformat = snd_pcm_plug_slave_format(format, sformat_mask); + + if ((__force int)sformat < 0 || + !snd_mask_test(sformat_mask, (__force int)sformat)) { + for (sformat = (__force snd_pcm_format_t)0; + (__force int)sformat <= (__force int)SNDRV_PCM_FORMAT_LAST; + sformat = (__force snd_pcm_format_t)((__force int)sformat + 1)) { + if (snd_mask_test(sformat_mask, (__force int)sformat) && + snd_pcm_oss_format_to(sformat) >= 0) + break; + } + if ((__force int)sformat > (__force int)SNDRV_PCM_FORMAT_LAST) { + pcm_dbg(substream->pcm, "Cannot find a format!!!\n"); + err = -EINVAL; + goto failure; + } + } + err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); + if (err < 0) + goto failure; + + if (direct) { + memcpy(params, sparams, sizeof(*params)); + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, + (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, + (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, + runtime->oss.rate, 0); + pdprintf("client: access = %i, format = %i, channels = %i, rate = %i\n", + params_access(params), params_format(params), + params_channels(params), params_rate(params)); + } + pdprintf("slave: access = %i, format = %i, channels = %i, rate = %i\n", + params_access(sparams), params_format(sparams), + params_channels(sparams), params_rate(sparams)); + + oss_frame_size = snd_pcm_format_physical_width(params_format(params)) * + params_channels(params) / 8; + + err = snd_pcm_oss_period_size(substream, params, sparams); + if (err < 0) + goto failure; + + n = snd_pcm_plug_slave_size(substream, runtime->oss.period_bytes / oss_frame_size); + err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, n, NULL); + if (err < 0) + goto failure; + + err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIODS, + runtime->oss.periods, NULL); + if (err < 0) + goto failure; + + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, sparams); + if (err < 0) { + pcm_dbg(substream->pcm, "HW_PARAMS failed: %i\n", err); + goto failure; + } + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + snd_pcm_oss_plugin_clear(substream); + if (!direct) { + /* add necessary plugins */ + snd_pcm_oss_plugin_clear(substream); + if ((err = snd_pcm_plug_format_plugins(substream, + params, + sparams)) < 0) { + pcm_dbg(substream->pcm, + "snd_pcm_plug_format_plugins failed: %i\n", err); + snd_pcm_oss_plugin_clear(substream); + goto failure; + } + if (runtime->oss.plugin_first) { + struct snd_pcm_plugin *plugin; + if ((err = snd_pcm_plugin_build_io(substream, sparams, &plugin)) < 0) { + pcm_dbg(substream->pcm, + "snd_pcm_plugin_build_io failed: %i\n", err); + snd_pcm_oss_plugin_clear(substream); + goto failure; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_plugin_append(plugin); + } else { + err = snd_pcm_plugin_insert(plugin); + } + if (err < 0) { + snd_pcm_oss_plugin_clear(substream); + goto failure; + } + } + } +#endif + + if (runtime->oss.trigger) { + sw_params->start_threshold = 1; + } else { + sw_params->start_threshold = runtime->boundary; + } + if (atomic_read(&substream->mmap_count) || + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + sw_params->stop_threshold = runtime->boundary; + else + sw_params->stop_threshold = runtime->buffer_size; + sw_params->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; + sw_params->period_step = 1; + sw_params->avail_min = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + 1 : runtime->period_size; + if (atomic_read(&substream->mmap_count) || + substream->oss.setup.nosilence) { + sw_params->silence_threshold = 0; + sw_params->silence_size = 0; + } else { + snd_pcm_uframes_t frames; + frames = runtime->period_size + 16; + if (frames > runtime->buffer_size) + frames = runtime->buffer_size; + sw_params->silence_threshold = frames; + sw_params->silence_size = frames; + } + + if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_SW_PARAMS, sw_params)) < 0) { + pcm_dbg(substream->pcm, "SW_PARAMS failed: %i\n", err); + goto failure; + } + + runtime->oss.periods = params_periods(sparams); + oss_period_size = snd_pcm_plug_client_size(substream, params_period_size(sparams)); + if (oss_period_size < 0) { + err = -EINVAL; + goto failure; + } +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + if (runtime->oss.plugin_first) { + err = snd_pcm_plug_alloc(substream, oss_period_size); + if (err < 0) + goto failure; + } +#endif + oss_period_size = array_size(oss_period_size, oss_frame_size); + oss_buffer_size = array_size(oss_period_size, runtime->oss.periods); + if (oss_buffer_size <= 0) { + err = -EINVAL; + goto failure; + } + + runtime->oss.period_bytes = oss_period_size; + runtime->oss.buffer_bytes = oss_buffer_size; + + pdprintf("oss: period bytes = %i, buffer bytes = %i\n", + runtime->oss.period_bytes, + runtime->oss.buffer_bytes); + pdprintf("slave: period_size = %i, buffer_size = %i\n", + params_period_size(sparams), + params_buffer_size(sparams)); + + runtime->oss.format = snd_pcm_oss_format_to(params_format(params)); + runtime->oss.channels = params_channels(params); + runtime->oss.rate = params_rate(params); + + kvfree(runtime->oss.buffer); + runtime->oss.buffer = kvzalloc(runtime->oss.period_bytes, GFP_KERNEL); + if (!runtime->oss.buffer) { + err = -ENOMEM; + goto failure; + } + + runtime->oss.params = 0; + runtime->oss.prepare = 1; + runtime->oss.buffer_used = 0; + if (runtime->dma_area) + snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes)); + + runtime->oss.period_frames = snd_pcm_alsa_frames(substream, oss_period_size); + + err = 0; +failure: + kfree(sw_params); + kfree(params); + kfree(sparams); + return err; +} + +/* this one takes the lock by itself */ +static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream, + bool trylock) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (trylock) { + if (!(mutex_trylock(&runtime->oss.params_lock))) + return -EAGAIN; + } else if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + + err = snd_pcm_oss_change_params_locked(substream); + mutex_unlock(&runtime->oss.params_lock); + return err; +} + +static int snd_pcm_oss_get_active_substream(struct snd_pcm_oss_file *pcm_oss_file, struct snd_pcm_substream **r_substream) +{ + int idx, err; + struct snd_pcm_substream *asubstream = NULL, *substream; + + for (idx = 0; idx < 2; idx++) { + substream = pcm_oss_file->streams[idx]; + if (substream == NULL) + continue; + if (asubstream == NULL) + asubstream = substream; + if (substream->runtime->oss.params) { + err = snd_pcm_oss_change_params(substream, false); + if (err < 0) + return err; + } + } + if (!asubstream) + return -EIO; + if (r_substream) + *r_substream = asubstream; + return 0; +} + +/* call with params_lock held */ +/* NOTE: this always call PREPARE unconditionally no matter whether + * runtime->oss.prepare is set or not + */ +static int snd_pcm_oss_prepare(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); + if (err < 0) { + pcm_dbg(substream->pcm, + "snd_pcm_oss_prepare: SNDRV_PCM_IOCTL_PREPARE failed\n"); + return err; + } + runtime->oss.prepare = 0; + runtime->oss.prev_hw_ptr_period = 0; + runtime->oss.period_ptr = 0; + runtime->oss.buffer_used = 0; + + return 0; +} + +static int snd_pcm_oss_make_ready(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int err; + + runtime = substream->runtime; + if (runtime->oss.params) { + err = snd_pcm_oss_change_params(substream, false); + if (err < 0) + return err; + } + if (runtime->oss.prepare) { + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + err = snd_pcm_oss_prepare(substream); + mutex_unlock(&runtime->oss.params_lock); + if (err < 0) + return err; + } + return 0; +} + +/* call with params_lock held */ +static int snd_pcm_oss_make_ready_locked(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int err; + + runtime = substream->runtime; + if (runtime->oss.params) { + err = snd_pcm_oss_change_params_locked(substream); + if (err < 0) + return err; + } + if (runtime->oss.prepare) { + err = snd_pcm_oss_prepare(substream); + if (err < 0) + return err; + } + return 0; +} + +static int snd_pcm_oss_capture_position_fixup(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay) +{ + struct snd_pcm_runtime *runtime; + snd_pcm_uframes_t frames; + int err = 0; + + while (1) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, delay); + if (err < 0) + break; + runtime = substream->runtime; + if (*delay <= (snd_pcm_sframes_t)runtime->buffer_size) + break; + /* in case of overrun, skip whole periods like OSS/Linux driver does */ + /* until avail(delay) <= buffer_size */ + frames = (*delay - runtime->buffer_size) + runtime->period_size - 1; + frames /= runtime->period_size; + frames *= runtime->period_size; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_FORWARD, &frames); + if (err < 0) + break; + } + return err; +} + +snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: write: recovering from %s\n", + runtime->status->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = __snd_pcm_lib_xfer(substream, (void *)ptr, true, + frames, in_kernel); + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + /* test, if we can't store new data, because the stream */ + /* has not been started */ + if (runtime->status->state == SNDRV_PCM_STATE_PREPARED) + return -EAGAIN; + } + return ret; +} + +snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *ptr, snd_pcm_uframes_t frames, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t delay; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: read: recovering from %s\n", + runtime->status->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + if (ret < 0) + break; + } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) { + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = snd_pcm_oss_capture_position_fixup(substream, &delay); + if (ret < 0) + break; + ret = __snd_pcm_lib_xfer(substream, (void *)ptr, true, + frames, in_kernel); + if (ret == -EPIPE) { + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) { + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + if (ret < 0) + break; + } + continue; + } + if (ret != -ESTRPIPE) + break; + } + return ret; +} + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS +snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: writev: recovering from %s\n", + runtime->status->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = snd_pcm_kernel_writev(substream, bufs, frames); + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + + /* test, if we can't store new data, because the stream */ + /* has not been started */ + if (runtime->status->state == SNDRV_PCM_STATE_PREPARED) + return -EAGAIN; + } + return ret; +} + +snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: readv: recovering from %s\n", + runtime->status->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + if (ret < 0) + break; + } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) { + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = snd_pcm_kernel_readv(substream, bufs, frames); + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + } + return ret; +} +#endif /* CONFIG_SND_PCM_OSS_PLUGINS */ + +static ssize_t snd_pcm_oss_write2(struct snd_pcm_substream *substream, const char *buf, size_t bytes, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t frames, frames1; +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + if (runtime->oss.plugin_first) { + struct snd_pcm_plugin_channel *channels; + size_t oss_frame_bytes = (runtime->oss.plugin_first->src_width * runtime->oss.plugin_first->src_format.channels) / 8; + if (!in_kernel) { + if (copy_from_user(runtime->oss.buffer, (const char __force __user *)buf, bytes)) + return -EFAULT; + buf = runtime->oss.buffer; + } + frames = bytes / oss_frame_bytes; + frames1 = snd_pcm_plug_client_channels_buf(substream, (char *)buf, frames, &channels); + if (frames1 < 0) + return frames1; + frames1 = snd_pcm_plug_write_transfer(substream, channels, frames1); + if (frames1 <= 0) + return frames1; + bytes = frames1 * oss_frame_bytes; + } else +#endif + { + frames = bytes_to_frames(runtime, bytes); + frames1 = snd_pcm_oss_write3(substream, buf, frames, in_kernel); + if (frames1 <= 0) + return frames1; + bytes = frames_to_bytes(runtime, frames1); + } + return bytes; +} + +static ssize_t snd_pcm_oss_write1(struct snd_pcm_substream *substream, const char __user *buf, size_t bytes) +{ + size_t xfer = 0; + ssize_t tmp = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (atomic_read(&substream->mmap_count)) + return -ENXIO; + + atomic_inc(&runtime->oss.rw_ref); + while (bytes > 0) { + if (mutex_lock_interruptible(&runtime->oss.params_lock)) { + tmp = -ERESTARTSYS; + break; + } + tmp = snd_pcm_oss_make_ready_locked(substream); + if (tmp < 0) + goto err; + if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) { + tmp = bytes; + if (tmp + runtime->oss.buffer_used > runtime->oss.period_bytes) + tmp = runtime->oss.period_bytes - runtime->oss.buffer_used; + if (tmp > 0) { + if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp)) { + tmp = -EFAULT; + goto err; + } + } + runtime->oss.buffer_used += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + if (substream->oss.setup.partialfrag || + runtime->oss.buffer_used == runtime->oss.period_bytes) { + tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr, + runtime->oss.buffer_used - runtime->oss.period_ptr, 1); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + runtime->oss.period_ptr += tmp; + runtime->oss.period_ptr %= runtime->oss.period_bytes; + if (runtime->oss.period_ptr == 0 || + runtime->oss.period_ptr == runtime->oss.buffer_used) + runtime->oss.buffer_used = 0; + else if ((substream->f_flags & O_NONBLOCK) != 0) { + tmp = -EAGAIN; + goto err; + } + } + } else { + tmp = snd_pcm_oss_write2(substream, + (const char __force *)buf, + runtime->oss.period_bytes, 0); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + if ((substream->f_flags & O_NONBLOCK) != 0 && + tmp != runtime->oss.period_bytes) + tmp = -EAGAIN; + } + err: + mutex_unlock(&runtime->oss.params_lock); + if (tmp < 0) + break; + if (signal_pending(current)) { + tmp = -ERESTARTSYS; + break; + } + tmp = 0; + } + atomic_dec(&runtime->oss.rw_ref); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; +} + +static ssize_t snd_pcm_oss_read2(struct snd_pcm_substream *substream, char *buf, size_t bytes, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t frames, frames1; +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + char __user *final_dst = (char __force __user *)buf; + if (runtime->oss.plugin_first) { + struct snd_pcm_plugin_channel *channels; + size_t oss_frame_bytes = (runtime->oss.plugin_last->dst_width * runtime->oss.plugin_last->dst_format.channels) / 8; + if (!in_kernel) + buf = runtime->oss.buffer; + frames = bytes / oss_frame_bytes; + frames1 = snd_pcm_plug_client_channels_buf(substream, buf, frames, &channels); + if (frames1 < 0) + return frames1; + frames1 = snd_pcm_plug_read_transfer(substream, channels, frames1); + if (frames1 <= 0) + return frames1; + bytes = frames1 * oss_frame_bytes; + if (!in_kernel && copy_to_user(final_dst, buf, bytes)) + return -EFAULT; + } else +#endif + { + frames = bytes_to_frames(runtime, bytes); + frames1 = snd_pcm_oss_read3(substream, buf, frames, in_kernel); + if (frames1 <= 0) + return frames1; + bytes = frames_to_bytes(runtime, frames1); + } + return bytes; +} + +static ssize_t snd_pcm_oss_read1(struct snd_pcm_substream *substream, char __user *buf, size_t bytes) +{ + size_t xfer = 0; + ssize_t tmp = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (atomic_read(&substream->mmap_count)) + return -ENXIO; + + atomic_inc(&runtime->oss.rw_ref); + while (bytes > 0) { + if (mutex_lock_interruptible(&runtime->oss.params_lock)) { + tmp = -ERESTARTSYS; + break; + } + tmp = snd_pcm_oss_make_ready_locked(substream); + if (tmp < 0) + goto err; + if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) { + if (runtime->oss.buffer_used == 0) { + tmp = snd_pcm_oss_read2(substream, runtime->oss.buffer, runtime->oss.period_bytes, 1); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + runtime->oss.period_ptr = tmp; + runtime->oss.buffer_used = tmp; + } + tmp = bytes; + if ((size_t) tmp > runtime->oss.buffer_used) + tmp = runtime->oss.buffer_used; + if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp)) { + tmp = -EFAULT; + goto err; + } + buf += tmp; + bytes -= tmp; + xfer += tmp; + runtime->oss.buffer_used -= tmp; + } else { + tmp = snd_pcm_oss_read2(substream, (char __force *)buf, + runtime->oss.period_bytes, 0); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + } + err: + mutex_unlock(&runtime->oss.params_lock); + if (tmp < 0) + break; + if (signal_pending(current)) { + tmp = -ERESTARTSYS; + break; + } + tmp = 0; + } + atomic_dec(&runtime->oss.rw_ref); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; +} + +static int snd_pcm_oss_reset(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + int i; + + for (i = 0; i < 2; i++) { + substream = pcm_oss_file->streams[i]; + if (!substream) + continue; + runtime = substream->runtime; + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + mutex_lock(&runtime->oss.params_lock); + runtime->oss.prepare = 1; + runtime->oss.buffer_used = 0; + runtime->oss.prev_hw_ptr_period = 0; + runtime->oss.period_ptr = 0; + mutex_unlock(&runtime->oss.params_lock); + } + return 0; +} + +static int snd_pcm_oss_post(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream != NULL) { + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL); + } + /* note: all errors from the start action are ignored */ + /* OSS apps do not know, how to handle them */ + return 0; +} + +static int snd_pcm_oss_sync1(struct snd_pcm_substream *substream, size_t size) +{ + struct snd_pcm_runtime *runtime; + ssize_t result = 0; + snd_pcm_state_t state; + long res; + wait_queue_entry_t wait; + + runtime = substream->runtime; + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "sync1: size = %li\n", size); +#endif + while (1) { + result = snd_pcm_oss_write2(substream, runtime->oss.buffer, size, 1); + if (result > 0) { + runtime->oss.buffer_used = 0; + result = 0; + break; + } + if (result != 0 && result != -EAGAIN) + break; + result = 0; + set_current_state(TASK_INTERRUPTIBLE); + snd_pcm_stream_lock_irq(substream); + state = runtime->status->state; + snd_pcm_stream_unlock_irq(substream); + if (state != SNDRV_PCM_STATE_RUNNING) { + set_current_state(TASK_RUNNING); + break; + } + res = schedule_timeout(10 * HZ); + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + if (res == 0) { + pcm_err(substream->pcm, + "OSS sync error - DMA timeout\n"); + result = -EIO; + break; + } + } + remove_wait_queue(&runtime->sleep, &wait); + return result; +} + +static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file) +{ + int err = 0; + unsigned int saved_f_flags; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_format_t format; + unsigned long width; + size_t size; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream != NULL) { + runtime = substream->runtime; + if (atomic_read(&substream->mmap_count)) + goto __direct; + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + atomic_inc(&runtime->oss.rw_ref); + if (mutex_lock_interruptible(&runtime->oss.params_lock)) { + atomic_dec(&runtime->oss.rw_ref); + return -ERESTARTSYS; + } + format = snd_pcm_oss_format_from(runtime->oss.format); + width = snd_pcm_format_physical_width(format); + if (runtime->oss.buffer_used > 0) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "sync: buffer_used\n"); +#endif + size = (8 * (runtime->oss.period_bytes - runtime->oss.buffer_used) + 7) / width; + snd_pcm_format_set_silence(format, + runtime->oss.buffer + runtime->oss.buffer_used, + size); + err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes); + if (err < 0) + goto unlock; + } else if (runtime->oss.period_ptr > 0) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "sync: period_ptr\n"); +#endif + size = runtime->oss.period_bytes - runtime->oss.period_ptr; + snd_pcm_format_set_silence(format, + runtime->oss.buffer, + size * 8 / width); + err = snd_pcm_oss_sync1(substream, size); + if (err < 0) + goto unlock; + } + /* + * The ALSA's period might be a bit large than OSS one. + * Fill the remain portion of ALSA period with zeros. + */ + size = runtime->control->appl_ptr % runtime->period_size; + if (size > 0) { + size = runtime->period_size - size; + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) + snd_pcm_lib_write(substream, NULL, size); + else if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + snd_pcm_lib_writev(substream, NULL, size); + } +unlock: + mutex_unlock(&runtime->oss.params_lock); + atomic_dec(&runtime->oss.rw_ref); + if (err < 0) + return err; + /* + * finish sync: drain the buffer + */ + __direct: + saved_f_flags = substream->f_flags; + substream->f_flags &= ~O_NONBLOCK; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + substream->f_flags = saved_f_flags; + if (err < 0) + return err; + mutex_lock(&runtime->oss.params_lock); + runtime->oss.prepare = 1; + mutex_unlock(&runtime->oss.params_lock); + } + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream != NULL) { + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + runtime = substream->runtime; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + if (err < 0) + return err; + mutex_lock(&runtime->oss.params_lock); + runtime->oss.buffer_used = 0; + runtime->oss.prepare = 1; + mutex_unlock(&runtime->oss.params_lock); + } + return 0; +} + +static int snd_pcm_oss_set_rate(struct snd_pcm_oss_file *pcm_oss_file, int rate) +{ + int idx; + + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + int err; + + if (substream == NULL) + continue; + runtime = substream->runtime; + if (rate < 1000) + rate = 1000; + else if (rate > 192000) + rate = 192000; + err = lock_params(runtime); + if (err < 0) + return err; + if (runtime->oss.rate != rate) { + runtime->oss.params = 1; + runtime->oss.rate = rate; + } + unlock_params(runtime); + } + return snd_pcm_oss_get_rate(pcm_oss_file); +} + +static int snd_pcm_oss_get_rate(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.rate; +} + +static int snd_pcm_oss_set_channels(struct snd_pcm_oss_file *pcm_oss_file, unsigned int channels) +{ + int idx; + if (channels < 1) + channels = 1; + if (channels > 128) + return -EINVAL; + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + int err; + + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + if (runtime->oss.channels != channels) { + runtime->oss.params = 1; + runtime->oss.channels = channels; + } + unlock_params(runtime); + } + return snd_pcm_oss_get_channels(pcm_oss_file); +} + +static int snd_pcm_oss_get_channels(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.channels; +} + +static int snd_pcm_oss_get_block_size(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.period_bytes; +} + +static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + int direct; + struct snd_pcm_hw_params *params; + unsigned int formats = 0; + const struct snd_mask *format_mask; + int fmt; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + if (atomic_read(&substream->mmap_count)) + direct = 1; + else + direct = substream->oss.setup.direct; + if (!direct) + return AFMT_MU_LAW | AFMT_U8 | + AFMT_S16_LE | AFMT_S16_BE | + AFMT_S8 | AFMT_U16_LE | + AFMT_U16_BE | + AFMT_S32_LE | AFMT_S32_BE | + AFMT_S24_LE | AFMT_S24_BE | + AFMT_S24_PACKED; + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + _snd_pcm_hw_params_any(params); + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto error; + format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(format_mask, fmt)) { + int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); + if (f >= 0) + formats |= f; + } + } + + error: + kfree(params); + return err < 0 ? err : formats; +} + +static int snd_pcm_oss_set_format(struct snd_pcm_oss_file *pcm_oss_file, int format) +{ + int formats, idx; + int err; + + if (format != AFMT_QUERY) { + formats = snd_pcm_oss_get_formats(pcm_oss_file); + if (formats < 0) + return formats; + if (!(formats & format)) + format = AFMT_U8; + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + if (runtime->oss.format != format) { + runtime->oss.params = 1; + runtime->oss.format = format; + } + unlock_params(runtime); + } + } + return snd_pcm_oss_get_format(pcm_oss_file); +} + +static int snd_pcm_oss_get_format(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.format; +} + +static int snd_pcm_oss_set_subdivide1(struct snd_pcm_substream *substream, int subdivide) +{ + struct snd_pcm_runtime *runtime; + + runtime = substream->runtime; + if (subdivide == 0) { + subdivide = runtime->oss.subdivision; + if (subdivide == 0) + subdivide = 1; + return subdivide; + } + if (runtime->oss.subdivision || runtime->oss.fragshift) + return -EINVAL; + if (subdivide != 1 && subdivide != 2 && subdivide != 4 && + subdivide != 8 && subdivide != 16) + return -EINVAL; + runtime->oss.subdivision = subdivide; + runtime->oss.params = 1; + return subdivide; +} + +static int snd_pcm_oss_set_subdivide(struct snd_pcm_oss_file *pcm_oss_file, int subdivide) +{ + int err = -EINVAL, idx; + + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + err = snd_pcm_oss_set_subdivide1(substream, subdivide); + unlock_params(runtime); + if (err < 0) + return err; + } + return err; +} + +static int snd_pcm_oss_set_fragment1(struct snd_pcm_substream *substream, unsigned int val) +{ + struct snd_pcm_runtime *runtime; + int fragshift; + + runtime = substream->runtime; + if (runtime->oss.subdivision || runtime->oss.fragshift) + return -EINVAL; + fragshift = val & 0xffff; + if (fragshift >= 25) /* should be large enough */ + return -EINVAL; + runtime->oss.fragshift = fragshift; + runtime->oss.maxfrags = (val >> 16) & 0xffff; + if (runtime->oss.fragshift < 4) /* < 16 */ + runtime->oss.fragshift = 4; + if (runtime->oss.maxfrags < 2) + runtime->oss.maxfrags = 2; + runtime->oss.params = 1; + return 0; +} + +static int snd_pcm_oss_set_fragment(struct snd_pcm_oss_file *pcm_oss_file, unsigned int val) +{ + int err = -EINVAL, idx; + + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + err = snd_pcm_oss_set_fragment1(substream, val); + unlock_params(runtime); + if (err < 0) + return err; + } + return err; +} + +static int snd_pcm_oss_nonblock(struct file * file) +{ + spin_lock(&file->f_lock); + file->f_flags |= O_NONBLOCK; + spin_unlock(&file->f_lock); + return 0; +} + +static int snd_pcm_oss_get_caps1(struct snd_pcm_substream *substream, int res) +{ + + if (substream == NULL) { + res &= ~DSP_CAP_DUPLEX; + return res; + } +#ifdef DSP_CAP_MULTI + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + if (substream->pstr->substream_count > 1) + res |= DSP_CAP_MULTI; +#endif + /* DSP_CAP_REALTIME is set all times: */ + /* all ALSA drivers can return actual pointer in ring buffer */ +#if defined(DSP_CAP_REALTIME) && 0 + { + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->info & (SNDRV_PCM_INFO_BLOCK_TRANSFER|SNDRV_PCM_INFO_BATCH)) + res &= ~DSP_CAP_REALTIME; + } +#endif + return res; +} + +static int snd_pcm_oss_get_caps(struct snd_pcm_oss_file *pcm_oss_file) +{ + int result, idx; + + result = DSP_CAP_TRIGGER | DSP_CAP_MMAP | DSP_CAP_DUPLEX | DSP_CAP_REALTIME; + for (idx = 0; idx < 2; idx++) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + result = snd_pcm_oss_get_caps1(substream, result); + } + result |= 0x0001; /* revision - same as SB AWE 64 */ + return result; +} + +static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream, + snd_pcm_uframes_t hw_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr; + appl_ptr = hw_ptr + runtime->buffer_size; + appl_ptr %= runtime->boundary; + runtime->control->appl_ptr = appl_ptr; +} + +static int snd_pcm_oss_set_trigger(struct snd_pcm_oss_file *pcm_oss_file, int trigger) +{ + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL; + int err, cmd; + +#ifdef OSS_DEBUG + pr_debug("pcm_oss: trigger = 0x%x\n", trigger); +#endif + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (psubstream) { + if ((err = snd_pcm_oss_make_ready(psubstream)) < 0) + return err; + } + if (csubstream) { + if ((err = snd_pcm_oss_make_ready(csubstream)) < 0) + return err; + } + if (psubstream) { + runtime = psubstream->runtime; + cmd = 0; + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + if (trigger & PCM_ENABLE_OUTPUT) { + if (runtime->oss.trigger) + goto _skip1; + if (atomic_read(&psubstream->mmap_count)) + snd_pcm_oss_simulate_fill(psubstream, + get_hw_ptr_period(runtime)); + runtime->oss.trigger = 1; + runtime->start_threshold = 1; + cmd = SNDRV_PCM_IOCTL_START; + } else { + if (!runtime->oss.trigger) + goto _skip1; + runtime->oss.trigger = 0; + runtime->start_threshold = runtime->boundary; + cmd = SNDRV_PCM_IOCTL_DROP; + runtime->oss.prepare = 1; + } + _skip1: + mutex_unlock(&runtime->oss.params_lock); + if (cmd) { + err = snd_pcm_kernel_ioctl(psubstream, cmd, NULL); + if (err < 0) + return err; + } + } + if (csubstream) { + runtime = csubstream->runtime; + cmd = 0; + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + if (trigger & PCM_ENABLE_INPUT) { + if (runtime->oss.trigger) + goto _skip2; + runtime->oss.trigger = 1; + runtime->start_threshold = 1; + cmd = SNDRV_PCM_IOCTL_START; + } else { + if (!runtime->oss.trigger) + goto _skip2; + runtime->oss.trigger = 0; + runtime->start_threshold = runtime->boundary; + cmd = SNDRV_PCM_IOCTL_DROP; + runtime->oss.prepare = 1; + } + _skip2: + mutex_unlock(&runtime->oss.params_lock); + if (cmd) { + err = snd_pcm_kernel_ioctl(csubstream, cmd, NULL); + if (err < 0) + return err; + } + } + return 0; +} + +static int snd_pcm_oss_get_trigger(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL; + int result = 0; + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (psubstream && psubstream->runtime && psubstream->runtime->oss.trigger) + result |= PCM_ENABLE_OUTPUT; + if (csubstream && csubstream->runtime && csubstream->runtime->oss.trigger) + result |= PCM_ENABLE_INPUT; + return result; +} + +static int snd_pcm_oss_get_odelay(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t delay; + int err; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + return -EINVAL; + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + runtime = substream->runtime; + if (runtime->oss.params || runtime->oss.prepare) + return 0; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); + if (err == -EPIPE) + delay = 0; /* hack for broken OSS applications */ + else if (err < 0) + return err; + return snd_pcm_oss_bytes(substream, delay); +} + +static int snd_pcm_oss_get_ptr(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct count_info __user * _info) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t delay; + int fixup; + struct count_info info; + int err; + + if (_info == NULL) + return -EFAULT; + substream = pcm_oss_file->streams[stream]; + if (substream == NULL) + return -EINVAL; + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + runtime = substream->runtime; + if (runtime->oss.params || runtime->oss.prepare) { + memset(&info, 0, sizeof(info)); + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); + if (err == -EPIPE || err == -ESTRPIPE || (! err && delay < 0)) { + err = 0; + delay = 0; + fixup = 0; + } else { + fixup = runtime->oss.buffer_used; + } + } else { + err = snd_pcm_oss_capture_position_fixup(substream, &delay); + fixup = -runtime->oss.buffer_used; + } + if (err < 0) + return err; + info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size); + if (atomic_read(&substream->mmap_count)) { + snd_pcm_sframes_t n; + delay = get_hw_ptr_period(runtime); + n = delay - runtime->oss.prev_hw_ptr_period; + if (n < 0) + n += runtime->boundary; + info.blocks = n / runtime->period_size; + runtime->oss.prev_hw_ptr_period = delay; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_oss_simulate_fill(substream, delay); + info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX; + } else { + delay = snd_pcm_oss_bytes(substream, delay); + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (substream->oss.setup.buggyptr) + info.blocks = (runtime->oss.buffer_bytes - delay - fixup) / runtime->oss.period_bytes; + else + info.blocks = (delay + fixup) / runtime->oss.period_bytes; + info.bytes = (runtime->oss.bytes - delay) & INT_MAX; + } else { + delay += fixup; + info.blocks = delay / runtime->oss.period_bytes; + info.bytes = (runtime->oss.bytes + delay) & INT_MAX; + } + } + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_pcm_oss_get_space(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct audio_buf_info __user *_info) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t avail; + int fixup; + struct audio_buf_info info; + int err; + + if (_info == NULL) + return -EFAULT; + substream = pcm_oss_file->streams[stream]; + if (substream == NULL) + return -EINVAL; + runtime = substream->runtime; + + if (runtime->oss.params && + (err = snd_pcm_oss_change_params(substream, false)) < 0) + return err; + + info.fragsize = runtime->oss.period_bytes; + info.fragstotal = runtime->periods; + if (runtime->oss.prepare) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + info.bytes = runtime->oss.period_bytes * runtime->oss.periods; + info.fragments = runtime->oss.periods; + } else { + info.bytes = 0; + info.fragments = 0; + } + } else { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &avail); + if (err == -EPIPE || err == -ESTRPIPE || (! err && avail < 0)) { + avail = runtime->buffer_size; + err = 0; + fixup = 0; + } else { + avail = runtime->buffer_size - avail; + fixup = -runtime->oss.buffer_used; + } + } else { + err = snd_pcm_oss_capture_position_fixup(substream, &avail); + fixup = runtime->oss.buffer_used; + } + if (err < 0) + return err; + info.bytes = snd_pcm_oss_bytes(substream, avail) + fixup; + info.fragments = info.bytes / runtime->oss.period_bytes; + } + +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: space: bytes = %i, fragments = %i, fragstotal = %i, fragsize = %i\n", + info.bytes, info.fragments, info.fragstotal, info.fragsize); +#endif + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_pcm_oss_get_mapbuf(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct buffmem_desc __user * _info) +{ + // it won't be probably implemented + // pr_debug("TODO: snd_pcm_oss_get_mapbuf\n"); + return -EINVAL; +} + +static const char *strip_task_path(const char *path) +{ + const char *ptr, *ptrl = NULL; + for (ptr = path; *ptr; ptr++) { + if (*ptr == '/') + ptrl = ptr + 1; + } + return ptrl; +} + +static void snd_pcm_oss_look_for_setup(struct snd_pcm *pcm, int stream, + const char *task_name, + struct snd_pcm_oss_setup *rsetup) +{ + struct snd_pcm_oss_setup *setup; + + mutex_lock(&pcm->streams[stream].oss.setup_mutex); + do { + for (setup = pcm->streams[stream].oss.setup_list; setup; + setup = setup->next) { + if (!strcmp(setup->task_name, task_name)) + goto out; + } + } while ((task_name = strip_task_path(task_name)) != NULL); + out: + if (setup) + *rsetup = *setup; + mutex_unlock(&pcm->streams[stream].oss.setup_mutex); +} + +static void snd_pcm_oss_release_substream(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + runtime = substream->runtime; + kvfree(runtime->oss.buffer); + runtime->oss.buffer = NULL; +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + snd_pcm_oss_plugin_clear(substream); +#endif + substream->oss.oss = 0; +} + +static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream, + struct snd_pcm_oss_setup *setup, + int minor) +{ + struct snd_pcm_runtime *runtime; + + substream->oss.oss = 1; + substream->oss.setup = *setup; + if (setup->nonblock) + substream->f_flags |= O_NONBLOCK; + else if (setup->block) + substream->f_flags &= ~O_NONBLOCK; + runtime = substream->runtime; + runtime->oss.params = 1; + runtime->oss.trigger = 1; + runtime->oss.rate = 8000; + mutex_init(&runtime->oss.params_lock); + switch (SNDRV_MINOR_OSS_DEVICE(minor)) { + case SNDRV_MINOR_OSS_PCM_8: + runtime->oss.format = AFMT_U8; + break; + case SNDRV_MINOR_OSS_PCM_16: + runtime->oss.format = AFMT_S16_LE; + break; + default: + runtime->oss.format = AFMT_MU_LAW; + } + runtime->oss.channels = 1; + runtime->oss.fragshift = 0; + runtime->oss.maxfrags = 0; + runtime->oss.subdivision = 0; + substream->pcm_release = snd_pcm_oss_release_substream; + atomic_set(&runtime->oss.rw_ref, 0); +} + +static int snd_pcm_oss_release_file(struct snd_pcm_oss_file *pcm_oss_file) +{ + int cidx; + if (!pcm_oss_file) + return 0; + for (cidx = 0; cidx < 2; ++cidx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[cidx]; + if (substream) + snd_pcm_release_substream(substream); + } + kfree(pcm_oss_file); + return 0; +} + +static int snd_pcm_oss_open_file(struct file *file, + struct snd_pcm *pcm, + struct snd_pcm_oss_file **rpcm_oss_file, + int minor, + struct snd_pcm_oss_setup *setup) +{ + int idx, err; + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream; + fmode_t f_mode = file->f_mode; + + if (rpcm_oss_file) + *rpcm_oss_file = NULL; + + pcm_oss_file = kzalloc(sizeof(*pcm_oss_file), GFP_KERNEL); + if (pcm_oss_file == NULL) + return -ENOMEM; + + if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) && + (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)) + f_mode = FMODE_WRITE; + + file->f_flags &= ~O_APPEND; + for (idx = 0; idx < 2; idx++) { + if (setup[idx].disable) + continue; + if (! pcm->streams[idx].substream_count) + continue; /* no matching substream */ + if (idx == SNDRV_PCM_STREAM_PLAYBACK) { + if (! (f_mode & FMODE_WRITE)) + continue; + } else { + if (! (f_mode & FMODE_READ)) + continue; + } + err = snd_pcm_open_substream(pcm, idx, file, &substream); + if (err < 0) { + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + + pcm_oss_file->streams[idx] = substream; + substream->file = pcm_oss_file; + snd_pcm_oss_init_substream(substream, &setup[idx], minor); + } + + if (!pcm_oss_file->streams[0] && !pcm_oss_file->streams[1]) { + snd_pcm_oss_release_file(pcm_oss_file); + return -EINVAL; + } + + file->private_data = pcm_oss_file; + if (rpcm_oss_file) + *rpcm_oss_file = pcm_oss_file; + return 0; +} + + +static int snd_task_name(struct task_struct *task, char *name, size_t size) +{ + unsigned int idx; + + if (snd_BUG_ON(!task || !name || size < 2)) + return -EINVAL; + for (idx = 0; idx < sizeof(task->comm) && idx + 1 < size; idx++) + name[idx] = task->comm[idx]; + name[idx] = '\0'; + return 0; +} + +static int snd_pcm_oss_open(struct inode *inode, struct file *file) +{ + int err; + char task_name[32]; + struct snd_pcm *pcm; + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_oss_setup setup[2]; + int nonblock; + wait_queue_entry_t wait; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + pcm = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM); + if (pcm == NULL) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(pcm->card, file); + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { + err = -EFAULT; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { + err = -EFAULT; + goto __error; + } + memset(setup, 0, sizeof(setup)); + if (file->f_mode & FMODE_WRITE) + snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, + task_name, &setup[0]); + if (file->f_mode & FMODE_READ) + snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, + task_name, &setup[1]); + + nonblock = !!(file->f_flags & O_NONBLOCK); + if (!nonblock) + nonblock = nonblock_open; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&pcm->open_wait, &wait); + mutex_lock(&pcm->open_mutex); + while (1) { + err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file, + iminor(inode), setup); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (nonblock) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&pcm->open_mutex); + schedule(); + mutex_lock(&pcm->open_mutex); + if (pcm->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&pcm->open_wait, &wait); + mutex_unlock(&pcm->open_mutex); + if (err < 0) + goto __error; + snd_card_unref(pcm->card); + return err; + + __error: + module_put(pcm->card->module); + __error2: + snd_card_file_remove(pcm->card, file); + __error1: + if (pcm) + snd_card_unref(pcm->card); + return err; +} + +static int snd_pcm_oss_release(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + struct snd_pcm_oss_file *pcm_oss_file; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (snd_BUG_ON(!substream)) + return -ENXIO; + pcm = substream->pcm; + if (!pcm->card->shutdown) + snd_pcm_oss_sync(pcm_oss_file); + mutex_lock(&pcm->open_mutex); + snd_pcm_oss_release_file(pcm_oss_file); + mutex_unlock(&pcm->open_mutex); + wake_up(&pcm->open_wait); + module_put(pcm->card->module); + snd_card_file_remove(pcm->card, file); + return 0; +} + +static long snd_pcm_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_pcm_oss_file *pcm_oss_file; + int __user *p = (int __user *)arg; + int res; + + pcm_oss_file = file->private_data; + if (cmd == OSS_GETVERSION) + return put_user(SNDRV_OSS_VERSION, p); + if (cmd == OSS_ALSAEMULVER) + return put_user(1, p); +#if IS_REACHABLE(CONFIG_SND_MIXER_OSS) + if (((cmd >> 8) & 0xff) == 'M') { /* mixer ioctl - for OSS compatibility */ + struct snd_pcm_substream *substream; + int idx; + for (idx = 0; idx < 2; ++idx) { + substream = pcm_oss_file->streams[idx]; + if (substream != NULL) + break; + } + if (snd_BUG_ON(idx >= 2)) + return -ENXIO; + return snd_mixer_oss_ioctl_card(substream->pcm->card, cmd, arg); + } +#endif + if (((cmd >> 8) & 0xff) != 'P') + return -EINVAL; +#ifdef OSS_DEBUG + pr_debug("pcm_oss: ioctl = 0x%x\n", cmd); +#endif + switch (cmd) { + case SNDCTL_DSP_RESET: + return snd_pcm_oss_reset(pcm_oss_file); + case SNDCTL_DSP_SYNC: + return snd_pcm_oss_sync(pcm_oss_file); + case SNDCTL_DSP_SPEED: + if (get_user(res, p)) + return -EFAULT; + if ((res = snd_pcm_oss_set_rate(pcm_oss_file, res))<0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_RATE: + res = snd_pcm_oss_get_rate(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_STEREO: + if (get_user(res, p)) + return -EFAULT; + res = res > 0 ? 2 : 1; + if ((res = snd_pcm_oss_set_channels(pcm_oss_file, res)) < 0) + return res; + return put_user(--res, p); + case SNDCTL_DSP_GETBLKSIZE: + res = snd_pcm_oss_get_block_size(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETFMT: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_format(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_BITS: + res = snd_pcm_oss_get_format(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_CHANNELS: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_channels(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_CHANNELS: + res = snd_pcm_oss_get_channels(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + return -EIO; + case SNDCTL_DSP_POST: + return snd_pcm_oss_post(pcm_oss_file); + case SNDCTL_DSP_SUBDIVIDE: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_subdivide(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(res, p)) + return -EFAULT; + return snd_pcm_oss_set_fragment(pcm_oss_file, res); + case SNDCTL_DSP_GETFMTS: + res = snd_pcm_oss_get_formats(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: + return snd_pcm_oss_get_space(pcm_oss_file, + cmd == SNDCTL_DSP_GETISPACE ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct audio_buf_info __user *) arg); + case SNDCTL_DSP_NONBLOCK: + return snd_pcm_oss_nonblock(file); + case SNDCTL_DSP_GETCAPS: + res = snd_pcm_oss_get_caps(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_GETTRIGGER: + res = snd_pcm_oss_get_trigger(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETTRIGGER: + if (get_user(res, p)) + return -EFAULT; + return snd_pcm_oss_set_trigger(pcm_oss_file, res); + case SNDCTL_DSP_GETIPTR: + case SNDCTL_DSP_GETOPTR: + return snd_pcm_oss_get_ptr(pcm_oss_file, + cmd == SNDCTL_DSP_GETIPTR ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct count_info __user *) arg); + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + return snd_pcm_oss_get_mapbuf(pcm_oss_file, + cmd == SNDCTL_DSP_MAPINBUF ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct buffmem_desc __user *) arg); + case SNDCTL_DSP_SETSYNCRO: + /* stop DMA now.. */ + return 0; + case SNDCTL_DSP_SETDUPLEX: + if (snd_pcm_oss_get_caps(pcm_oss_file) & DSP_CAP_DUPLEX) + return 0; + return -EIO; + case SNDCTL_DSP_GETODELAY: + res = snd_pcm_oss_get_odelay(pcm_oss_file); + if (res < 0) { + /* it's for sure, some broken apps don't check for error codes */ + put_user(0, p); + return res; + } + return put_user(res, p); + case SNDCTL_DSP_PROFILE: + return 0; /* silently ignore */ + default: + pr_debug("pcm_oss: unknown command = 0x%x\n", cmd); + } + return -EINVAL; +} + +#ifdef CONFIG_COMPAT +/* all compatible */ +static long snd_pcm_oss_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return snd_pcm_oss_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define snd_pcm_oss_ioctl_compat NULL +#endif + +static ssize_t snd_pcm_oss_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream == NULL) + return -ENXIO; + substream->f_flags = file->f_flags & O_NONBLOCK; +#ifndef OSS_DEBUG + return snd_pcm_oss_read1(substream, buf, count); +#else + { + ssize_t res = snd_pcm_oss_read1(substream, buf, count); + pcm_dbg(substream->pcm, + "pcm_oss: read %li bytes (returned %li bytes)\n", + (long)count, (long)res); + return res; + } +#endif +} + +static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream; + long result; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + return -ENXIO; + substream->f_flags = file->f_flags & O_NONBLOCK; + result = snd_pcm_oss_write1(substream, buf, count); +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "pcm_oss: write %li bytes (wrote %li bytes)\n", + (long)count, (long)result); +#endif + return result; +} + +static int snd_pcm_oss_playback_ready(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (atomic_read(&substream->mmap_count)) + return runtime->oss.prev_hw_ptr_period != + get_hw_ptr_period(runtime); + else + return snd_pcm_playback_avail(runtime) >= + runtime->oss.period_frames; +} + +static int snd_pcm_oss_capture_ready(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (atomic_read(&substream->mmap_count)) + return runtime->oss.prev_hw_ptr_period != + get_hw_ptr_period(runtime); + else + return snd_pcm_capture_avail(runtime) >= + runtime->oss.period_frames; +} + +static __poll_t snd_pcm_oss_poll(struct file *file, poll_table * wait) +{ + struct snd_pcm_oss_file *pcm_oss_file; + __poll_t mask; + struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL; + + pcm_oss_file = file->private_data; + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + + mask = 0; + if (psubstream != NULL) { + struct snd_pcm_runtime *runtime = psubstream->runtime; + poll_wait(file, &runtime->sleep, wait); + snd_pcm_stream_lock_irq(psubstream); + if (runtime->status->state != SNDRV_PCM_STATE_DRAINING && + (runtime->status->state != SNDRV_PCM_STATE_RUNNING || + snd_pcm_oss_playback_ready(psubstream))) + mask |= EPOLLOUT | EPOLLWRNORM; + snd_pcm_stream_unlock_irq(psubstream); + } + if (csubstream != NULL) { + struct snd_pcm_runtime *runtime = csubstream->runtime; + snd_pcm_state_t ostate; + poll_wait(file, &runtime->sleep, wait); + snd_pcm_stream_lock_irq(csubstream); + if ((ostate = runtime->status->state) != SNDRV_PCM_STATE_RUNNING || + snd_pcm_oss_capture_ready(csubstream)) + mask |= EPOLLIN | EPOLLRDNORM; + snd_pcm_stream_unlock_irq(csubstream); + if (ostate != SNDRV_PCM_STATE_RUNNING && runtime->oss.trigger) { + struct snd_pcm_oss_file ofile; + memset(&ofile, 0, sizeof(ofile)); + ofile.streams[SNDRV_PCM_STREAM_CAPTURE] = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + runtime->oss.trigger = 0; + snd_pcm_oss_set_trigger(&ofile, PCM_ENABLE_INPUT); + } + } + + return mask; +} + +static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area) +{ + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime; + int err; + +#ifdef OSS_DEBUG + pr_debug("pcm_oss: mmap begin\n"); +#endif + pcm_oss_file = file->private_data; + switch ((area->vm_flags & (VM_READ | VM_WRITE))) { + case VM_READ | VM_WRITE: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream) + break; + /* Fall through */ + case VM_READ: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + break; + case VM_WRITE: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + break; + default: + return -EINVAL; + } + /* set VM_READ access as well to fix memset() routines that do + reads before writes (to improve performance) */ + area->vm_flags |= VM_READ; + if (substream == NULL) + return -ENXIO; + runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP_VALID)) + return -EIO; + if (runtime->info & SNDRV_PCM_INFO_INTERLEAVED) + runtime->access = SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; + else + return -EIO; + + if (runtime->oss.params) { + /* use mutex_trylock() for params_lock for avoiding a deadlock + * between mmap_sem and params_lock taken by + * copy_from/to_user() in snd_pcm_oss_write/read() + */ + err = snd_pcm_oss_change_params(substream, true); + if (err < 0) + return err; + } +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + if (runtime->oss.plugin_first != NULL) + return -EIO; +#endif + + if (area->vm_pgoff != 0) + return -EINVAL; + + err = snd_pcm_mmap_data(substream, file, area); + if (err < 0) + return err; + runtime->oss.mmap_bytes = area->vm_end - area->vm_start; + runtime->silence_threshold = 0; + runtime->silence_size = 0; +#ifdef OSS_DEBUG + pr_debug("pcm_oss: mmap ok, bytes = 0x%x\n", + runtime->oss.mmap_bytes); +#endif + /* In mmap mode we never stop */ + runtime->stop_threshold = runtime->boundary; + + return 0; +} + +#ifdef CONFIG_SND_VERBOSE_PROCFS +/* + * /proc interface + */ + +static void snd_pcm_oss_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + struct snd_pcm_oss_setup *setup = pstr->oss.setup_list; + mutex_lock(&pstr->oss.setup_mutex); + while (setup) { + snd_iprintf(buffer, "%s %u %u%s%s%s%s%s%s\n", + setup->task_name, + setup->periods, + setup->period_size, + setup->disable ? " disable" : "", + setup->direct ? " direct" : "", + setup->block ? " block" : "", + setup->nonblock ? " non-block" : "", + setup->partialfrag ? " partial-frag" : "", + setup->nosilence ? " no-silence" : ""); + setup = setup->next; + } + mutex_unlock(&pstr->oss.setup_mutex); +} + +static void snd_pcm_oss_proc_free_setup_list(struct snd_pcm_str * pstr) +{ + struct snd_pcm_oss_setup *setup, *setupn; + + for (setup = pstr->oss.setup_list, pstr->oss.setup_list = NULL; + setup; setup = setupn) { + setupn = setup->next; + kfree(setup->task_name); + kfree(setup); + } + pstr->oss.setup_list = NULL; +} + +static void snd_pcm_oss_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + char line[128], str[32], task_name[32]; + const char *ptr; + int idx1; + struct snd_pcm_oss_setup *setup, *setup1, template; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + mutex_lock(&pstr->oss.setup_mutex); + memset(&template, 0, sizeof(template)); + ptr = snd_info_get_str(task_name, line, sizeof(task_name)); + if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) { + snd_pcm_oss_proc_free_setup_list(pstr); + mutex_unlock(&pstr->oss.setup_mutex); + continue; + } + for (setup = pstr->oss.setup_list; setup; setup = setup->next) { + if (!strcmp(setup->task_name, task_name)) { + template = *setup; + break; + } + } + ptr = snd_info_get_str(str, ptr, sizeof(str)); + template.periods = simple_strtoul(str, NULL, 10); + ptr = snd_info_get_str(str, ptr, sizeof(str)); + template.period_size = simple_strtoul(str, NULL, 10); + for (idx1 = 31; idx1 >= 0; idx1--) + if (template.period_size & (1 << idx1)) + break; + for (idx1--; idx1 >= 0; idx1--) + template.period_size &= ~(1 << idx1); + do { + ptr = snd_info_get_str(str, ptr, sizeof(str)); + if (!strcmp(str, "disable")) { + template.disable = 1; + } else if (!strcmp(str, "direct")) { + template.direct = 1; + } else if (!strcmp(str, "block")) { + template.block = 1; + } else if (!strcmp(str, "non-block")) { + template.nonblock = 1; + } else if (!strcmp(str, "partial-frag")) { + template.partialfrag = 1; + } else if (!strcmp(str, "no-silence")) { + template.nosilence = 1; + } else if (!strcmp(str, "buggy-ptr")) { + template.buggyptr = 1; + } + } while (*str); + if (setup == NULL) { + setup = kmalloc(sizeof(*setup), GFP_KERNEL); + if (! setup) { + buffer->error = -ENOMEM; + mutex_unlock(&pstr->oss.setup_mutex); + return; + } + if (pstr->oss.setup_list == NULL) + pstr->oss.setup_list = setup; + else { + for (setup1 = pstr->oss.setup_list; + setup1->next; setup1 = setup1->next); + setup1->next = setup; + } + template.task_name = kstrdup(task_name, GFP_KERNEL); + if (! template.task_name) { + kfree(setup); + buffer->error = -ENOMEM; + mutex_unlock(&pstr->oss.setup_mutex); + return; + } + } + *setup = template; + mutex_unlock(&pstr->oss.setup_mutex); + } +} + +static void snd_pcm_oss_proc_init(struct snd_pcm *pcm) +{ + int stream; + for (stream = 0; stream < 2; ++stream) { + struct snd_info_entry *entry; + struct snd_pcm_str *pstr = &pcm->streams[stream]; + if (pstr->substream_count == 0) + continue; + if ((entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->mode = S_IFREG | 0644; + entry->c.text.read = snd_pcm_oss_proc_read; + entry->c.text.write = snd_pcm_oss_proc_write; + entry->private_data = pstr; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + pstr->oss.proc_entry = entry; + } +} + +static void snd_pcm_oss_proc_done(struct snd_pcm *pcm) +{ + int stream; + for (stream = 0; stream < 2; ++stream) { + struct snd_pcm_str *pstr = &pcm->streams[stream]; + snd_info_free_entry(pstr->oss.proc_entry); + pstr->oss.proc_entry = NULL; + snd_pcm_oss_proc_free_setup_list(pstr); + } +} +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +#define snd_pcm_oss_proc_init(pcm) +#define snd_pcm_oss_proc_done(pcm) +#endif /* CONFIG_SND_VERBOSE_PROCFS */ + +/* + * ENTRY functions + */ + +static const struct file_operations snd_pcm_oss_f_reg = +{ + .owner = THIS_MODULE, + .read = snd_pcm_oss_read, + .write = snd_pcm_oss_write, + .open = snd_pcm_oss_open, + .release = snd_pcm_oss_release, + .llseek = no_llseek, + .poll = snd_pcm_oss_poll, + .unlocked_ioctl = snd_pcm_oss_ioctl, + .compat_ioctl = snd_pcm_oss_ioctl_compat, + .mmap = snd_pcm_oss_mmap, +}; + +static void register_oss_dsp(struct snd_pcm *pcm, int index) +{ + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, index, &snd_pcm_oss_f_reg, + pcm) < 0) { + pcm_err(pcm, "unable to register OSS PCM device %i:%i\n", + pcm->card->number, pcm->device); + } +} + +static int snd_pcm_oss_register_minor(struct snd_pcm *pcm) +{ + pcm->oss.reg = 0; + if (dsp_map[pcm->card->number] == (int)pcm->device) { + char name[128]; + int duplex; + register_oss_dsp(pcm, 0); + duplex = (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count > 0 && + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count && + !(pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)); + sprintf(name, "%s%s", pcm->name, duplex ? " (DUPLEX)" : ""); +#ifdef SNDRV_OSS_INFO_DEV_AUDIO + snd_oss_info_register(SNDRV_OSS_INFO_DEV_AUDIO, + pcm->card->number, + name); +#endif + pcm->oss.reg++; + pcm->oss.reg_mask |= 1; + } + if (adsp_map[pcm->card->number] == (int)pcm->device) { + register_oss_dsp(pcm, 1); + pcm->oss.reg++; + pcm->oss.reg_mask |= 2; + } + + if (pcm->oss.reg) + snd_pcm_oss_proc_init(pcm); + + return 0; +} + +static int snd_pcm_oss_disconnect_minor(struct snd_pcm *pcm) +{ + if (pcm->oss.reg) { + if (pcm->oss.reg_mask & 1) { + pcm->oss.reg_mask &= ~1; + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, 0); + } + if (pcm->oss.reg_mask & 2) { + pcm->oss.reg_mask &= ~2; + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, 1); + } + if (dsp_map[pcm->card->number] == (int)pcm->device) { +#ifdef SNDRV_OSS_INFO_DEV_AUDIO + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_AUDIO, pcm->card->number); +#endif + } + pcm->oss.reg = 0; + } + return 0; +} + +static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm) +{ + snd_pcm_oss_disconnect_minor(pcm); + snd_pcm_oss_proc_done(pcm); + return 0; +} + +static struct snd_pcm_notify snd_pcm_oss_notify = +{ + .n_register = snd_pcm_oss_register_minor, + .n_disconnect = snd_pcm_oss_disconnect_minor, + .n_unregister = snd_pcm_oss_unregister_minor, +}; + +static int __init alsa_pcm_oss_init(void) +{ + int i; + int err; + + /* check device map table */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (dsp_map[i] < 0 || dsp_map[i] >= SNDRV_PCM_DEVICES) { + pr_err("ALSA: pcm_oss: invalid dsp_map[%d] = %d\n", + i, dsp_map[i]); + dsp_map[i] = 0; + } + if (adsp_map[i] < 0 || adsp_map[i] >= SNDRV_PCM_DEVICES) { + pr_err("ALSA: pcm_oss: invalid adsp_map[%d] = %d\n", + i, adsp_map[i]); + adsp_map[i] = 1; + } + } + if ((err = snd_pcm_notify(&snd_pcm_oss_notify, 0)) < 0) + return err; + return 0; +} + +static void __exit alsa_pcm_oss_exit(void) +{ + snd_pcm_notify(&snd_pcm_oss_notify, 1); +} + +module_init(alsa_pcm_oss_init) +module_exit(alsa_pcm_oss_exit) diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c new file mode 100644 index 000000000..8b7bbabee --- /dev/null +++ b/sound/core/oss/pcm_plugin.c @@ -0,0 +1,799 @@ +/* + * PCM Plug-In shared (kernel/library) code + * Copyright (c) 1999 by Jaroslav Kysela + * Copyright (c) 2000 by Abramo Bagnara + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#if 0 +#define PLUGIN_DEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include "pcm_plugin.h" + +#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first) +#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last) + +/* + * because some cards might have rates "very close", we ignore + * all "resampling" requests within +-5% + */ +static int rate_match(unsigned int src_rate, unsigned int dst_rate) +{ + unsigned int low = (src_rate * 95) / 100; + unsigned int high = (src_rate * 105) / 100; + return dst_rate >= low && dst_rate <= high; +} + +static int snd_pcm_plugin_alloc(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames) +{ + struct snd_pcm_plugin_format *format; + ssize_t width; + size_t size; + unsigned int channel; + struct snd_pcm_plugin_channel *c; + + if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) { + format = &plugin->src_format; + } else { + format = &plugin->dst_format; + } + if ((width = snd_pcm_format_physical_width(format->format)) < 0) + return width; + size = array3_size(frames, format->channels, width); + /* check for too large period size once again */ + if (size > 1024 * 1024) + return -ENOMEM; + if (snd_BUG_ON(size % 8)) + return -ENXIO; + size /= 8; + if (plugin->buf_frames < frames) { + kvfree(plugin->buf); + plugin->buf = kvzalloc(size, GFP_KERNEL); + plugin->buf_frames = frames; + } + if (!plugin->buf) { + plugin->buf_frames = 0; + return -ENOMEM; + } + c = plugin->buf_channels; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + for (channel = 0; channel < format->channels; channel++, c++) { + c->frames = frames; + c->enabled = 1; + c->wanted = 0; + c->area.addr = plugin->buf; + c->area.first = channel * width; + c->area.step = format->channels * width; + } + } else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { + if (snd_BUG_ON(size % format->channels)) + return -EINVAL; + size /= format->channels; + for (channel = 0; channel < format->channels; channel++, c++) { + c->frames = frames; + c->enabled = 1; + c->wanted = 0; + c->area.addr = plugin->buf + (channel * size); + c->area.first = 0; + c->area.step = width; + } + } else + return -EINVAL; + return 0; +} + +int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames) +{ + int err; + if (snd_BUG_ON(!snd_pcm_plug_first(plug))) + return -ENXIO; + if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) { + struct snd_pcm_plugin *plugin = snd_pcm_plug_first(plug); + while (plugin->next) { + if (plugin->dst_frames) + frames = plugin->dst_frames(plugin, frames); + if ((snd_pcm_sframes_t)frames <= 0) + return -ENXIO; + plugin = plugin->next; + err = snd_pcm_plugin_alloc(plugin, frames); + if (err < 0) + return err; + } + } else { + struct snd_pcm_plugin *plugin = snd_pcm_plug_last(plug); + while (plugin->prev) { + if (plugin->src_frames) + frames = plugin->src_frames(plugin, frames); + if ((snd_pcm_sframes_t)frames <= 0) + return -ENXIO; + plugin = plugin->prev; + err = snd_pcm_plugin_alloc(plugin, frames); + if (err < 0) + return err; + } + } + return 0; +} + + +snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels) +{ + *channels = plugin->buf_channels; + return frames; +} + +int snd_pcm_plugin_build(struct snd_pcm_substream *plug, + const char *name, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + size_t extra, + struct snd_pcm_plugin **ret) +{ + struct snd_pcm_plugin *plugin; + unsigned int channels; + + if (snd_BUG_ON(!plug)) + return -ENXIO; + if (snd_BUG_ON(!src_format || !dst_format)) + return -ENXIO; + plugin = kzalloc(sizeof(*plugin) + extra, GFP_KERNEL); + if (plugin == NULL) + return -ENOMEM; + plugin->name = name; + plugin->plug = plug; + plugin->stream = snd_pcm_plug_stream(plug); + plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + plugin->src_format = *src_format; + plugin->src_width = snd_pcm_format_physical_width(src_format->format); + snd_BUG_ON(plugin->src_width <= 0); + plugin->dst_format = *dst_format; + plugin->dst_width = snd_pcm_format_physical_width(dst_format->format); + snd_BUG_ON(plugin->dst_width <= 0); + if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) + channels = src_format->channels; + else + channels = dst_format->channels; + plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL); + if (plugin->buf_channels == NULL) { + snd_pcm_plugin_free(plugin); + return -ENOMEM; + } + plugin->client_channels = snd_pcm_plugin_client_channels; + *ret = plugin; + return 0; +} + +int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin) +{ + if (! plugin) + return 0; + if (plugin->private_free) + plugin->private_free(plugin); + kfree(plugin->buf_channels); + kvfree(plugin->buf); + kfree(plugin); + return 0; +} + +static snd_pcm_sframes_t plug_client_size(struct snd_pcm_substream *plug, + snd_pcm_uframes_t drv_frames, + bool check_size) +{ + struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next; + int stream; + + if (snd_BUG_ON(!plug)) + return -ENXIO; + if (drv_frames == 0) + return 0; + stream = snd_pcm_plug_stream(plug); + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + plugin = snd_pcm_plug_last(plug); + while (plugin && drv_frames > 0) { + plugin_prev = plugin->prev; + if (plugin->src_frames) + drv_frames = plugin->src_frames(plugin, drv_frames); + if (check_size && plugin->buf_frames && + drv_frames > plugin->buf_frames) + drv_frames = plugin->buf_frames; + plugin = plugin_prev; + } + } else if (stream == SNDRV_PCM_STREAM_CAPTURE) { + plugin = snd_pcm_plug_first(plug); + while (plugin && drv_frames > 0) { + plugin_next = plugin->next; + if (check_size && plugin->buf_frames && + drv_frames > plugin->buf_frames) + drv_frames = plugin->buf_frames; + if (plugin->dst_frames) + drv_frames = plugin->dst_frames(plugin, drv_frames); + plugin = plugin_next; + } + } else + snd_BUG(); + return drv_frames; +} + +static snd_pcm_sframes_t plug_slave_size(struct snd_pcm_substream *plug, + snd_pcm_uframes_t clt_frames, + bool check_size) +{ + struct snd_pcm_plugin *plugin, *plugin_prev, *plugin_next; + snd_pcm_sframes_t frames; + int stream; + + if (snd_BUG_ON(!plug)) + return -ENXIO; + if (clt_frames == 0) + return 0; + frames = clt_frames; + stream = snd_pcm_plug_stream(plug); + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + plugin = snd_pcm_plug_first(plug); + while (plugin && frames > 0) { + plugin_next = plugin->next; + if (check_size && plugin->buf_frames && + frames > plugin->buf_frames) + frames = plugin->buf_frames; + if (plugin->dst_frames) { + frames = plugin->dst_frames(plugin, frames); + if (frames < 0) + return frames; + } + plugin = plugin_next; + } + } else if (stream == SNDRV_PCM_STREAM_CAPTURE) { + plugin = snd_pcm_plug_last(plug); + while (plugin) { + plugin_prev = plugin->prev; + if (plugin->src_frames) { + frames = plugin->src_frames(plugin, frames); + if (frames < 0) + return frames; + } + if (check_size && plugin->buf_frames && + frames > plugin->buf_frames) + frames = plugin->buf_frames; + plugin = plugin_prev; + } + } else + snd_BUG(); + return frames; +} + +snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *plug, + snd_pcm_uframes_t drv_frames) +{ + return plug_client_size(plug, drv_frames, false); +} + +snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *plug, + snd_pcm_uframes_t clt_frames) +{ + return plug_slave_size(plug, clt_frames, false); +} + +static int snd_pcm_plug_formats(const struct snd_mask *mask, + snd_pcm_format_t format) +{ + struct snd_mask formats = *mask; + u64 linfmts = (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_U24_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE | + SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); + snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & lower_32_bits(linfmts)) + formats.bits[0] |= lower_32_bits(linfmts); + if (formats.bits[1] & upper_32_bits(linfmts)) + formats.bits[1] |= upper_32_bits(linfmts); + return snd_mask_test(&formats, (__force int)format); +} + +static snd_pcm_format_t preferred_formats[] = { + SNDRV_PCM_FORMAT_S16_LE, + SNDRV_PCM_FORMAT_S16_BE, + SNDRV_PCM_FORMAT_U16_LE, + SNDRV_PCM_FORMAT_U16_BE, + SNDRV_PCM_FORMAT_S24_3LE, + SNDRV_PCM_FORMAT_S24_3BE, + SNDRV_PCM_FORMAT_U24_3LE, + SNDRV_PCM_FORMAT_U24_3BE, + SNDRV_PCM_FORMAT_S24_LE, + SNDRV_PCM_FORMAT_S24_BE, + SNDRV_PCM_FORMAT_U24_LE, + SNDRV_PCM_FORMAT_U24_BE, + SNDRV_PCM_FORMAT_S32_LE, + SNDRV_PCM_FORMAT_S32_BE, + SNDRV_PCM_FORMAT_U32_LE, + SNDRV_PCM_FORMAT_U32_BE, + SNDRV_PCM_FORMAT_S8, + SNDRV_PCM_FORMAT_U8 +}; + +snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + const struct snd_mask *format_mask) +{ + int i; + + if (snd_mask_test(format_mask, (__force int)format)) + return format; + if (!snd_pcm_plug_formats(format_mask, format)) + return (__force snd_pcm_format_t)-EINVAL; + if (snd_pcm_format_linear(format)) { + unsigned int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format) > 0; + int big = snd_pcm_format_big_endian(format) > 0; + unsigned int badness, best = -1; + snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; + for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { + snd_pcm_format_t f = preferred_formats[i]; + unsigned int w; + if (!snd_mask_test(format_mask, (__force int)f)) + continue; + w = snd_pcm_format_width(f); + if (w >= width) + badness = w - width; + else + badness = width - w + 32; + badness += snd_pcm_format_unsigned(f) != unsignd; + badness += snd_pcm_format_big_endian(f) != big; + if (badness < best) { + best_format = f; + best = badness; + } + } + if ((__force int)best_format >= 0) + return best_format; + else + return (__force snd_pcm_format_t)-EINVAL; + } else { + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + snd_pcm_format_t format1 = preferred_formats[i]; + if (snd_mask_test(format_mask, (__force int)format1)) + return format1; + } + /* fall through */ + default: + return (__force snd_pcm_format_t)-EINVAL; + } + } +} + +int snd_pcm_plug_format_plugins(struct snd_pcm_substream *plug, + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_params *slave_params) +{ + struct snd_pcm_plugin_format tmpformat; + struct snd_pcm_plugin_format dstformat; + struct snd_pcm_plugin_format srcformat; + snd_pcm_access_t src_access, dst_access; + struct snd_pcm_plugin *plugin = NULL; + int err; + int stream = snd_pcm_plug_stream(plug); + int slave_interleaved = (params_channels(slave_params) == 1 || + params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED); + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + dstformat.format = params_format(slave_params); + dstformat.rate = params_rate(slave_params); + dstformat.channels = params_channels(slave_params); + srcformat.format = params_format(params); + srcformat.rate = params_rate(params); + srcformat.channels = params_channels(params); + src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED : + SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + break; + case SNDRV_PCM_STREAM_CAPTURE: + dstformat.format = params_format(params); + dstformat.rate = params_rate(params); + dstformat.channels = params_channels(params); + srcformat.format = params_format(slave_params); + srcformat.rate = params_rate(slave_params); + srcformat.channels = params_channels(slave_params); + src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED : + SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + break; + default: + snd_BUG(); + return -EINVAL; + } + tmpformat = srcformat; + + pdprintf("srcformat: format=%i, rate=%i, channels=%i\n", + srcformat.format, + srcformat.rate, + srcformat.channels); + pdprintf("dstformat: format=%i, rate=%i, channels=%i\n", + dstformat.format, + dstformat.rate, + dstformat.channels); + + /* Format change (linearization) */ + if (! rate_match(srcformat.rate, dstformat.rate) && + ! snd_pcm_format_linear(srcformat.format)) { + if (srcformat.format != SNDRV_PCM_FORMAT_MU_LAW) + return -EINVAL; + tmpformat.format = SNDRV_PCM_FORMAT_S16; + err = snd_pcm_plugin_build_mulaw(plug, + &srcformat, &tmpformat, + &plugin); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* channels reduction */ + if (srcformat.channels > dstformat.channels) { + tmpformat.channels = dstformat.channels; + err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin); + pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* rate resampling */ + if (!rate_match(srcformat.rate, dstformat.rate)) { + if (srcformat.format != SNDRV_PCM_FORMAT_S16) { + /* convert to S16 for resampling */ + tmpformat.format = SNDRV_PCM_FORMAT_S16; + err = snd_pcm_plugin_build_linear(plug, + &srcformat, &tmpformat, + &plugin); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + tmpformat.rate = dstformat.rate; + err = snd_pcm_plugin_build_rate(plug, + &srcformat, &tmpformat, + &plugin); + pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* format change */ + if (srcformat.format != dstformat.format) { + tmpformat.format = dstformat.format; + if (srcformat.format == SNDRV_PCM_FORMAT_MU_LAW || + tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) { + err = snd_pcm_plugin_build_mulaw(plug, + &srcformat, &tmpformat, + &plugin); + } + else if (snd_pcm_format_linear(srcformat.format) && + snd_pcm_format_linear(tmpformat.format)) { + err = snd_pcm_plugin_build_linear(plug, + &srcformat, &tmpformat, + &plugin); + } + else + return -EINVAL; + pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* channels extension */ + if (srcformat.channels < dstformat.channels) { + tmpformat.channels = dstformat.channels; + err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin); + pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* de-interleave */ + if (src_access != dst_access) { + err = snd_pcm_plugin_build_copy(plug, + &srcformat, + &tmpformat, + &plugin); + pdprintf("interleave change (copy: returns %i)\n", err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + } + + return 0; +} + +snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *plug, + char *buf, + snd_pcm_uframes_t count, + struct snd_pcm_plugin_channel **channels) +{ + struct snd_pcm_plugin *plugin; + struct snd_pcm_plugin_channel *v; + struct snd_pcm_plugin_format *format; + int width, nchannels, channel; + int stream = snd_pcm_plug_stream(plug); + + if (snd_BUG_ON(!buf)) + return -ENXIO; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + plugin = snd_pcm_plug_first(plug); + format = &plugin->src_format; + } else { + plugin = snd_pcm_plug_last(plug); + format = &plugin->dst_format; + } + v = plugin->buf_channels; + *channels = v; + if ((width = snd_pcm_format_physical_width(format->format)) < 0) + return width; + nchannels = format->channels; + if (snd_BUG_ON(plugin->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED && + format->channels > 1)) + return -ENXIO; + for (channel = 0; channel < nchannels; channel++, v++) { + v->frames = count; + v->enabled = 1; + v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE); + v->area.addr = buf; + v->area.first = channel * width; + v->area.step = nchannels * width; + } + return count; +} + +snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *src_channels, snd_pcm_uframes_t size) +{ + struct snd_pcm_plugin *plugin, *next; + struct snd_pcm_plugin_channel *dst_channels; + int err; + snd_pcm_sframes_t frames = size; + + plugin = snd_pcm_plug_first(plug); + while (plugin) { + if (frames <= 0) + return frames; + if ((next = plugin->next) != NULL) { + snd_pcm_sframes_t frames1 = frames; + if (plugin->dst_frames) { + frames1 = plugin->dst_frames(plugin, frames); + if (frames1 <= 0) + return frames1; + } + if ((err = next->client_channels(next, frames1, &dst_channels)) < 0) { + return err; + } + if (err != frames1) { + frames = err; + if (plugin->src_frames) { + frames = plugin->src_frames(plugin, frames1); + if (frames <= 0) + return frames; + } + } + } else + dst_channels = NULL; + pdprintf("write plugin: %s, %li\n", plugin->name, frames); + if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0) + return frames; + src_channels = dst_channels; + plugin = next; + } + return plug_client_size(plug, frames, true); +} + +snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *dst_channels_final, snd_pcm_uframes_t size) +{ + struct snd_pcm_plugin *plugin, *next; + struct snd_pcm_plugin_channel *src_channels, *dst_channels; + snd_pcm_sframes_t frames = size; + int err; + + frames = plug_slave_size(plug, frames, true); + if (frames < 0) + return frames; + + src_channels = NULL; + plugin = snd_pcm_plug_first(plug); + while (plugin && frames > 0) { + if ((next = plugin->next) != NULL) { + if ((err = plugin->client_channels(plugin, frames, &dst_channels)) < 0) { + return err; + } + frames = err; + } else { + dst_channels = dst_channels_final; + } + pdprintf("read plugin: %s, %li\n", plugin->name, frames); + if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0) + return frames; + plugin = next; + src_channels = dst_channels; + } + return frames; +} + +int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_area, size_t dst_offset, + size_t samples, snd_pcm_format_t format) +{ + /* FIXME: sub byte resolution and odd dst_offset */ + unsigned char *dst; + unsigned int dst_step; + int width; + const unsigned char *silence; + if (!dst_area->addr) + return 0; + dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8; + width = snd_pcm_format_physical_width(format); + if (width <= 0) + return -EINVAL; + if (dst_area->step == (unsigned int) width && width >= 8) + return snd_pcm_format_set_silence(format, dst, samples); + silence = snd_pcm_format_silence_64(format); + if (! silence) + return -EINVAL; + dst_step = dst_area->step / 8; + if (width == 4) { + /* Ima ADPCM */ + int dstbit = dst_area->first % 8; + int dstbit_step = dst_area->step % 8; + while (samples-- > 0) { + if (dstbit) + *dst &= 0xf0; + else + *dst &= 0x0f; + dst += dst_step; + dstbit += dstbit_step; + if (dstbit == 8) { + dst++; + dstbit = 0; + } + } + } else { + width /= 8; + while (samples-- > 0) { + memcpy(dst, silence, width); + dst += dst_step; + } + } + return 0; +} + +int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_area, size_t src_offset, + const struct snd_pcm_channel_area *dst_area, size_t dst_offset, + size_t samples, snd_pcm_format_t format) +{ + /* FIXME: sub byte resolution and odd dst_offset */ + char *src, *dst; + int width; + int src_step, dst_step; + src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8; + if (!src_area->addr) + return snd_pcm_area_silence(dst_area, dst_offset, samples, format); + dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8; + if (!dst_area->addr) + return 0; + width = snd_pcm_format_physical_width(format); + if (width <= 0) + return -EINVAL; + if (src_area->step == (unsigned int) width && + dst_area->step == (unsigned int) width && width >= 8) { + size_t bytes = samples * width / 8; + memcpy(dst, src, bytes); + return 0; + } + src_step = src_area->step / 8; + dst_step = dst_area->step / 8; + if (width == 4) { + /* Ima ADPCM */ + int srcbit = src_area->first % 8; + int srcbit_step = src_area->step % 8; + int dstbit = dst_area->first % 8; + int dstbit_step = dst_area->step % 8; + while (samples-- > 0) { + unsigned char srcval; + if (srcbit) + srcval = *src & 0x0f; + else + srcval = (*src & 0xf0) >> 4; + if (dstbit) + *dst = (*dst & 0xf0) | srcval; + else + *dst = (*dst & 0x0f) | (srcval << 4); + src += src_step; + srcbit += srcbit_step; + if (srcbit == 8) { + src++; + srcbit = 0; + } + dst += dst_step; + dstbit += dstbit_step; + if (dstbit == 8) { + dst++; + dstbit = 0; + } + } + } else { + width /= 8; + while (samples-- > 0) { + memcpy(dst, src, width); + src += src_step; + dst += dst_step; + } + } + return 0; +} diff --git a/sound/core/oss/pcm_plugin.h b/sound/core/oss/pcm_plugin.h new file mode 100644 index 000000000..c9cd29d86 --- /dev/null +++ b/sound/core/oss/pcm_plugin.h @@ -0,0 +1,183 @@ +#ifndef __PCM_PLUGIN_H +#define __PCM_PLUGIN_H + +/* + * Digital Audio (Plugin interface) abstract layer + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + +#define snd_pcm_plug_stream(plug) ((plug)->stream) + +enum snd_pcm_plugin_action { + INIT = 0, + PREPARE = 1, +}; + +struct snd_pcm_channel_area { + void *addr; /* base address of channel samples */ + unsigned int first; /* offset to first sample in bits */ + unsigned int step; /* samples distance in bits */ +}; + +struct snd_pcm_plugin_channel { + void *aptr; /* pointer to the allocated area */ + struct snd_pcm_channel_area area; + snd_pcm_uframes_t frames; /* allocated frames */ + unsigned int enabled:1; /* channel need to be processed */ + unsigned int wanted:1; /* channel is wanted */ +}; + +struct snd_pcm_plugin_format { + snd_pcm_format_t format; + unsigned int rate; + unsigned int channels; +}; + +struct snd_pcm_plugin { + const char *name; /* plug-in name */ + int stream; + struct snd_pcm_plugin_format src_format; /* source format */ + struct snd_pcm_plugin_format dst_format; /* destination format */ + int src_width; /* sample width in bits */ + int dst_width; /* sample width in bits */ + snd_pcm_access_t access; + snd_pcm_sframes_t (*src_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t dst_frames); + snd_pcm_sframes_t (*dst_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t src_frames); + snd_pcm_sframes_t (*client_channels)(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels); + snd_pcm_sframes_t (*transfer)(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames); + int (*action)(struct snd_pcm_plugin *plugin, + enum snd_pcm_plugin_action action, + unsigned long data); + struct snd_pcm_plugin *prev; + struct snd_pcm_plugin *next; + struct snd_pcm_substream *plug; + void *private_data; + void (*private_free)(struct snd_pcm_plugin *plugin); + char *buf; + snd_pcm_uframes_t buf_frames; + struct snd_pcm_plugin_channel *buf_channels; + char extra_data[0]; +}; + +int snd_pcm_plugin_build(struct snd_pcm_substream *handle, + const char *name, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + size_t extra, + struct snd_pcm_plugin **ret); +int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin); +int snd_pcm_plugin_clear(struct snd_pcm_plugin **first); +int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames); +snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size); +snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size); + +#define FULL ROUTE_PLUGIN_RESOLUTION +#define HALF ROUTE_PLUGIN_RESOLUTION / 2 + +int snd_pcm_plugin_build_io(struct snd_pcm_substream *handle, + struct snd_pcm_hw_params *params, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_linear(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_rate(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_route(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_copy(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); + +int snd_pcm_plug_format_plugins(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_params *slave_params); + +snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + const struct snd_mask *format_mask); + +int snd_pcm_plugin_append(struct snd_pcm_plugin *plugin); + +snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_channel *src_channels, + snd_pcm_uframes_t size); +snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_channel *dst_channels_final, + snd_pcm_uframes_t size); + +snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *handle, + char *buf, snd_pcm_uframes_t count, + struct snd_pcm_plugin_channel **channels); + +snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels); + +int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_channel, + size_t dst_offset, + size_t samples, snd_pcm_format_t format); +int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_channel, + size_t src_offset, + const struct snd_pcm_channel_area *dst_channel, + size_t dst_offset, + size_t samples, snd_pcm_format_t format); + +void *snd_pcm_plug_buf_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t size); +void snd_pcm_plug_buf_unlock(struct snd_pcm_substream *plug, void *ptr); +snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, + const char *ptr, snd_pcm_uframes_t size, + int in_kernel); +snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, + char *ptr, snd_pcm_uframes_t size, int in_kernel); +snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, + void **bufs, snd_pcm_uframes_t frames); +snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream, + void **bufs, snd_pcm_uframes_t frames); + +#else + +static inline snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size) { return drv_size; } +static inline snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size) { return clt_size; } +static inline int snd_pcm_plug_slave_format(int format, const struct snd_mask *format_mask) { return format; } + +#endif + +#ifdef PLUGIN_DEBUG +#define pdprintf(fmt, args...) printk(KERN_DEBUG "plugin: " fmt, ##args) +#else +#define pdprintf(fmt, args...) +#endif + +#endif /* __PCM_PLUGIN_H */ diff --git a/sound/core/oss/rate.c b/sound/core/oss/rate.c new file mode 100644 index 000000000..2fa9299a4 --- /dev/null +++ b/sound/core/oss/rate.c @@ -0,0 +1,348 @@ +/* + * Rate conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#include +#include +#include +#include "pcm_plugin.h" + +#define SHIFT 11 +#define BITS (1<extra_data; + data->pos = 0; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + data->channels[channel].last_S1 = 0; + data->channels[channel].last_S2 = 0; + } +} + +static void resample_expand(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + int src_frames, int dst_frames) +{ + unsigned int pos = 0; + signed int val; + signed short S1, S2; + signed short *src, *dst; + unsigned int channel; + int src_step, dst_step; + int src_frames1, dst_frames1; + struct rate_priv *data = (struct rate_priv *)plugin->extra_data; + struct rate_channel *rchannels = data->channels; + + for (channel = 0; channel < plugin->src_format.channels; channel++) { + pos = data->pos; + S1 = rchannels->last_S1; + S2 = rchannels->last_S2; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = (signed short *)src_channels[channel].area.addr + + src_channels[channel].area.first / 8 / 2; + dst = (signed short *)dst_channels[channel].area.addr + + dst_channels[channel].area.first / 8 / 2; + src_step = src_channels[channel].area.step / 8 / 2; + dst_step = dst_channels[channel].area.step / 8 / 2; + src_frames1 = src_frames; + dst_frames1 = dst_frames; + while (dst_frames1-- > 0) { + if (pos & ~R_MASK) { + pos &= R_MASK; + S1 = S2; + if (src_frames1-- > 0) { + S2 = *src; + src += src_step; + } + } + val = S1 + ((S2 - S1) * (signed int)pos) / BITS; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + *dst = val; + dst += dst_step; + pos += data->pitch; + } + rchannels->last_S1 = S1; + rchannels->last_S2 = S2; + rchannels++; + } + data->pos = pos; +} + +static void resample_shrink(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + int src_frames, int dst_frames) +{ + unsigned int pos = 0; + signed int val; + signed short S1, S2; + signed short *src, *dst; + unsigned int channel; + int src_step, dst_step; + int src_frames1, dst_frames1; + struct rate_priv *data = (struct rate_priv *)plugin->extra_data; + struct rate_channel *rchannels = data->channels; + + for (channel = 0; channel < plugin->src_format.channels; ++channel) { + pos = data->pos; + S1 = rchannels->last_S1; + S2 = rchannels->last_S2; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = (signed short *)src_channels[channel].area.addr + + src_channels[channel].area.first / 8 / 2; + dst = (signed short *)dst_channels[channel].area.addr + + dst_channels[channel].area.first / 8 / 2; + src_step = src_channels[channel].area.step / 8 / 2; + dst_step = dst_channels[channel].area.step / 8 / 2; + src_frames1 = src_frames; + dst_frames1 = dst_frames; + while (dst_frames1 > 0) { + S1 = S2; + if (src_frames1-- > 0) { + S2 = *src; + src += src_step; + } + if (pos & ~R_MASK) { + pos &= R_MASK; + val = S1 + ((S2 - S1) * (signed int)pos) / BITS; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + *dst = val; + dst += dst_step; + dst_frames1--; + } + pos += data->pitch; + } + rchannels->last_S1 = S1; + rchannels->last_S2 = S2; + rchannels++; + } + data->pos = pos; +} + +static snd_pcm_sframes_t rate_src_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames) +{ + struct rate_priv *data; + snd_pcm_sframes_t res; + + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (frames == 0) + return 0; + data = (struct rate_priv *)plugin->extra_data; + if (plugin->src_format.rate < plugin->dst_format.rate) { + res = (((frames * data->pitch) + (BITS/2)) >> SHIFT); + } else { + res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch); + } + if (data->old_src_frames > 0) { + snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames; + while (data->old_src_frames < frames1) { + frames1 >>= 1; + res1 <<= 1; + } + while (data->old_src_frames > frames1) { + frames1 <<= 1; + res1 >>= 1; + } + if (data->old_src_frames == frames1) + return res1; + } + data->old_src_frames = frames; + data->old_dst_frames = res; + return res; +} + +static snd_pcm_sframes_t rate_dst_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames) +{ + struct rate_priv *data; + snd_pcm_sframes_t res; + + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (frames == 0) + return 0; + data = (struct rate_priv *)plugin->extra_data; + if (plugin->src_format.rate < plugin->dst_format.rate) { + res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch); + } else { + res = (((frames * data->pitch) + (BITS/2)) >> SHIFT); + } + if (data->old_dst_frames > 0) { + snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames; + while (data->old_dst_frames < frames1) { + frames1 >>= 1; + res1 <<= 1; + } + while (data->old_dst_frames > frames1) { + frames1 <<= 1; + res1 >>= 1; + } + if (data->old_dst_frames == frames1) + return res1; + } + data->old_dst_frames = frames; + data->old_src_frames = res; + return res; +} + +static snd_pcm_sframes_t rate_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + snd_pcm_uframes_t dst_frames; + struct rate_priv *data; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + if (snd_BUG_ON(src_channels[channel].area.first % 8 || + src_channels[channel].area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels[channel].area.first % 8 || + dst_channels[channel].area.step % 8)) + return -ENXIO; + } + } +#endif + + dst_frames = rate_dst_frames(plugin, frames); + if (dst_frames > dst_channels[0].frames) + dst_frames = dst_channels[0].frames; + data = (struct rate_priv *)plugin->extra_data; + data->func(plugin, src_channels, dst_channels, frames, dst_frames); + return dst_frames; +} + +static int rate_action(struct snd_pcm_plugin *plugin, + enum snd_pcm_plugin_action action, + unsigned long udata) +{ + if (snd_BUG_ON(!plugin)) + return -ENXIO; + switch (action) { + case INIT: + case PREPARE: + rate_init(plugin); + break; + default: + break; + } + return 0; /* silenty ignore other actions */ +} + +int snd_pcm_plugin_build_rate(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct rate_priv *data; + struct snd_pcm_plugin *plugin; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels <= 0)) + return -ENXIO; + if (snd_BUG_ON(src_format->format != SNDRV_PCM_FORMAT_S16)) + return -ENXIO; + if (snd_BUG_ON(dst_format->format != SNDRV_PCM_FORMAT_S16)) + return -ENXIO; + if (snd_BUG_ON(src_format->rate == dst_format->rate)) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "rate conversion", + src_format, dst_format, + sizeof(struct rate_priv) + + src_format->channels * sizeof(struct rate_channel), + &plugin); + if (err < 0) + return err; + data = (struct rate_priv *)plugin->extra_data; + if (src_format->rate < dst_format->rate) { + data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate; + data->func = resample_expand; + } else { + data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate; + data->func = resample_shrink; + } + data->pos = 0; + rate_init(plugin); + data->old_src_frames = data->old_dst_frames = 0; + plugin->transfer = rate_transfer; + plugin->src_frames = rate_src_frames; + plugin->dst_frames = rate_dst_frames; + plugin->action = rate_action; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/route.c b/sound/core/oss/route.c new file mode 100644 index 000000000..72dea0419 --- /dev/null +++ b/sound/core/oss/route.c @@ -0,0 +1,111 @@ +/* + * Route Plug-In + * Copyright (c) 2000 by Abramo Bagnara + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#include +#include +#include +#include "pcm_plugin.h" + +static void zero_areas(struct snd_pcm_plugin_channel *dvp, int ndsts, + snd_pcm_uframes_t frames, snd_pcm_format_t format) +{ + int dst = 0; + for (; dst < ndsts; ++dst) { + if (dvp->wanted) + snd_pcm_area_silence(&dvp->area, 0, frames, format); + dvp->enabled = 0; + dvp++; + } +} + +static inline void copy_area(const struct snd_pcm_plugin_channel *src_channel, + struct snd_pcm_plugin_channel *dst_channel, + snd_pcm_uframes_t frames, snd_pcm_format_t format) +{ + dst_channel->enabled = 1; + snd_pcm_area_copy(&src_channel->area, 0, &dst_channel->area, 0, frames, format); +} + +static snd_pcm_sframes_t route_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + int nsrcs, ndsts, dst; + struct snd_pcm_plugin_channel *dvp; + snd_pcm_format_t format; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; + if (frames > dst_channels[0].frames) + frames = dst_channels[0].frames; + + nsrcs = plugin->src_format.channels; + ndsts = plugin->dst_format.channels; + + format = plugin->dst_format.format; + dvp = dst_channels; + if (nsrcs <= 1) { + /* expand to all channels */ + for (dst = 0; dst < ndsts; ++dst) { + copy_area(src_channels, dvp, frames, format); + dvp++; + } + return frames; + } + + for (dst = 0; dst < ndsts && dst < nsrcs; ++dst) { + copy_area(src_channels, dvp, frames, format); + dvp++; + src_channels++; + } + if (dst < ndsts) + zero_areas(dvp, ndsts - dst, frames, format); + return frames; +} + +int snd_pcm_plugin_build_route(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + struct snd_pcm_plugin *plugin; + int err; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->format != dst_format->format)) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "route conversion", + src_format, dst_format, 0, &plugin); + if (err < 0) + return err; + + plugin->transfer = route_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/pcm.c b/sound/core/pcm.c new file mode 100644 index 000000000..8eed6244b --- /dev/null +++ b/sound/core/pcm.c @@ -0,0 +1,1299 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcm_local.h" + +MODULE_AUTHOR("Jaroslav Kysela , Abramo Bagnara "); +MODULE_DESCRIPTION("Midlevel PCM code for ALSA."); +MODULE_LICENSE("GPL"); + +static LIST_HEAD(snd_pcm_devices); +static DEFINE_MUTEX(register_mutex); +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +static LIST_HEAD(snd_pcm_notify_list); +#endif + +static int snd_pcm_free(struct snd_pcm *pcm); +static int snd_pcm_dev_free(struct snd_device *device); +static int snd_pcm_dev_register(struct snd_device *device); +static int snd_pcm_dev_disconnect(struct snd_device *device); + +static struct snd_pcm *snd_pcm_get(struct snd_card *card, int device) +{ + struct snd_pcm *pcm; + + list_for_each_entry(pcm, &snd_pcm_devices, list) { + if (pcm->card == card && pcm->device == device) + return pcm; + } + return NULL; +} + +static int snd_pcm_next(struct snd_card *card, int device) +{ + struct snd_pcm *pcm; + + list_for_each_entry(pcm, &snd_pcm_devices, list) { + if (pcm->card == card && pcm->device > device) + return pcm->device; + else if (pcm->card->number > card->number) + return -1; + } + return -1; +} + +static int snd_pcm_add(struct snd_pcm *newpcm) +{ + struct snd_pcm *pcm; + + if (newpcm->internal) + return 0; + + list_for_each_entry(pcm, &snd_pcm_devices, list) { + if (pcm->card == newpcm->card && pcm->device == newpcm->device) + return -EBUSY; + if (pcm->card->number > newpcm->card->number || + (pcm->card == newpcm->card && + pcm->device > newpcm->device)) { + list_add(&newpcm->list, pcm->list.prev); + return 0; + } + } + list_add_tail(&newpcm->list, &snd_pcm_devices); + return 0; +} + +static int snd_pcm_control_ioctl(struct snd_card *card, + struct snd_ctl_file *control, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)arg)) + return -EFAULT; + mutex_lock(®ister_mutex); + device = snd_pcm_next(card, device); + mutex_unlock(®ister_mutex); + if (put_user(device, (int __user *)arg)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_PCM_INFO: + { + struct snd_pcm_info __user *info; + unsigned int device, subdevice; + int stream; + struct snd_pcm *pcm; + struct snd_pcm_str *pstr; + struct snd_pcm_substream *substream; + int err; + + info = (struct snd_pcm_info __user *)arg; + if (get_user(device, &info->device)) + return -EFAULT; + if (get_user(stream, &info->stream)) + return -EFAULT; + if (stream < 0 || stream > 1) + return -EINVAL; + stream = array_index_nospec(stream, 2); + if (get_user(subdevice, &info->subdevice)) + return -EFAULT; + mutex_lock(®ister_mutex); + pcm = snd_pcm_get(card, device); + if (pcm == NULL) { + err = -ENXIO; + goto _error; + } + pstr = &pcm->streams[stream]; + if (pstr->substream_count == 0) { + err = -ENOENT; + goto _error; + } + if (subdevice >= pstr->substream_count) { + err = -ENXIO; + goto _error; + } + for (substream = pstr->substream; substream; + substream = substream->next) + if (substream->number == (int)subdevice) + break; + if (substream == NULL) { + err = -ENXIO; + goto _error; + } + mutex_lock(&pcm->open_mutex); + err = snd_pcm_info_user(substream, info); + mutex_unlock(&pcm->open_mutex); + _error: + mutex_unlock(®ister_mutex); + return err; + } + case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE: + { + int val; + + if (get_user(val, (int __user *)arg)) + return -EFAULT; + control->preferred_subdevice[SND_CTL_SUBDEV_PCM] = val; + return 0; + } + } + return -ENOIOCTLCMD; +} + +#define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v + +static char *snd_pcm_format_names[] = { + FORMAT(S8), + FORMAT(U8), + FORMAT(S16_LE), + FORMAT(S16_BE), + FORMAT(U16_LE), + FORMAT(U16_BE), + FORMAT(S24_LE), + FORMAT(S24_BE), + FORMAT(U24_LE), + FORMAT(U24_BE), + FORMAT(S32_LE), + FORMAT(S32_BE), + FORMAT(U32_LE), + FORMAT(U32_BE), + FORMAT(FLOAT_LE), + FORMAT(FLOAT_BE), + FORMAT(FLOAT64_LE), + FORMAT(FLOAT64_BE), + FORMAT(IEC958_SUBFRAME_LE), + FORMAT(IEC958_SUBFRAME_BE), + FORMAT(MU_LAW), + FORMAT(A_LAW), + FORMAT(IMA_ADPCM), + FORMAT(MPEG), + FORMAT(GSM), + FORMAT(SPECIAL), + FORMAT(S24_3LE), + FORMAT(S24_3BE), + FORMAT(U24_3LE), + FORMAT(U24_3BE), + FORMAT(S20_3LE), + FORMAT(S20_3BE), + FORMAT(U20_3LE), + FORMAT(U20_3BE), + FORMAT(S18_3LE), + FORMAT(S18_3BE), + FORMAT(U18_3LE), + FORMAT(U18_3BE), + FORMAT(G723_24), + FORMAT(G723_24_1B), + FORMAT(G723_40), + FORMAT(G723_40_1B), + FORMAT(DSD_U8), + FORMAT(DSD_U16_LE), + FORMAT(DSD_U32_LE), + FORMAT(DSD_U16_BE), + FORMAT(DSD_U32_BE), +}; + +/** + * snd_pcm_format_name - Return a name string for the given PCM format + * @format: PCM format + */ +const char *snd_pcm_format_name(snd_pcm_format_t format) +{ + if ((__force unsigned int)format >= ARRAY_SIZE(snd_pcm_format_names)) + return "Unknown"; + return snd_pcm_format_names[(__force unsigned int)format]; +} +EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +#ifdef CONFIG_SND_VERBOSE_PROCFS + +#define STATE(v) [SNDRV_PCM_STATE_##v] = #v +#define STREAM(v) [SNDRV_PCM_STREAM_##v] = #v +#define READY(v) [SNDRV_PCM_READY_##v] = #v +#define XRUN(v) [SNDRV_PCM_XRUN_##v] = #v +#define SILENCE(v) [SNDRV_PCM_SILENCE_##v] = #v +#define TSTAMP(v) [SNDRV_PCM_TSTAMP_##v] = #v +#define ACCESS(v) [SNDRV_PCM_ACCESS_##v] = #v +#define START(v) [SNDRV_PCM_START_##v] = #v +#define SUBFORMAT(v) [SNDRV_PCM_SUBFORMAT_##v] = #v + +static char *snd_pcm_stream_names[] = { + STREAM(PLAYBACK), + STREAM(CAPTURE), +}; + +static char *snd_pcm_state_names[] = { + STATE(OPEN), + STATE(SETUP), + STATE(PREPARED), + STATE(RUNNING), + STATE(XRUN), + STATE(DRAINING), + STATE(PAUSED), + STATE(SUSPENDED), +}; + +static char *snd_pcm_access_names[] = { + ACCESS(MMAP_INTERLEAVED), + ACCESS(MMAP_NONINTERLEAVED), + ACCESS(MMAP_COMPLEX), + ACCESS(RW_INTERLEAVED), + ACCESS(RW_NONINTERLEAVED), +}; + +static char *snd_pcm_subformat_names[] = { + SUBFORMAT(STD), +}; + +static char *snd_pcm_tstamp_mode_names[] = { + TSTAMP(NONE), + TSTAMP(ENABLE), +}; + +static const char *snd_pcm_stream_name(int stream) +{ + return snd_pcm_stream_names[stream]; +} + +static const char *snd_pcm_access_name(snd_pcm_access_t access) +{ + return snd_pcm_access_names[(__force int)access]; +} + +static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) +{ + return snd_pcm_subformat_names[(__force int)subformat]; +} + +static const char *snd_pcm_tstamp_mode_name(int mode) +{ + return snd_pcm_tstamp_mode_names[mode]; +} + +static const char *snd_pcm_state_name(snd_pcm_state_t state) +{ + return snd_pcm_state_names[(__force int)state]; +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#include + +static const char *snd_pcm_oss_format_name(int format) +{ + switch (format) { + case AFMT_MU_LAW: + return "MU_LAW"; + case AFMT_A_LAW: + return "A_LAW"; + case AFMT_IMA_ADPCM: + return "IMA_ADPCM"; + case AFMT_U8: + return "U8"; + case AFMT_S16_LE: + return "S16_LE"; + case AFMT_S16_BE: + return "S16_BE"; + case AFMT_S8: + return "S8"; + case AFMT_U16_LE: + return "U16_LE"; + case AFMT_U16_BE: + return "U16_BE"; + case AFMT_MPEG: + return "MPEG"; + default: + return "unknown"; + } +} +#endif + +static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_info *info; + int err; + + if (! substream) + return; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return; + + err = snd_pcm_info(substream, info); + if (err < 0) { + snd_iprintf(buffer, "error %d\n", err); + kfree(info); + return; + } + snd_iprintf(buffer, "card: %d\n", info->card); + snd_iprintf(buffer, "device: %d\n", info->device); + snd_iprintf(buffer, "subdevice: %d\n", info->subdevice); + snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream)); + snd_iprintf(buffer, "id: %s\n", info->id); + snd_iprintf(buffer, "name: %s\n", info->name); + snd_iprintf(buffer, "subname: %s\n", info->subname); + snd_iprintf(buffer, "class: %d\n", info->dev_class); + snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass); + snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count); + snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail); + kfree(info); +} + +static void snd_pcm_stream_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_pcm_proc_info_read(((struct snd_pcm_str *)entry->private_data)->substream, + buffer); +} + +static void snd_pcm_substream_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_pcm_proc_info_read(entry->private_data, buffer); +} + +static void snd_pcm_substream_proc_hw_params_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct snd_pcm_runtime *runtime; + + mutex_lock(&substream->pcm->open_mutex); + runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + goto unlock; + } + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_iprintf(buffer, "no setup\n"); + goto unlock; + } + snd_iprintf(buffer, "access: %s\n", snd_pcm_access_name(runtime->access)); + snd_iprintf(buffer, "format: %s\n", snd_pcm_format_name(runtime->format)); + snd_iprintf(buffer, "subformat: %s\n", snd_pcm_subformat_name(runtime->subformat)); + snd_iprintf(buffer, "channels: %u\n", runtime->channels); + snd_iprintf(buffer, "rate: %u (%u/%u)\n", runtime->rate, runtime->rate_num, runtime->rate_den); + snd_iprintf(buffer, "period_size: %lu\n", runtime->period_size); + snd_iprintf(buffer, "buffer_size: %lu\n", runtime->buffer_size); +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + if (substream->oss.oss) { + snd_iprintf(buffer, "OSS format: %s\n", snd_pcm_oss_format_name(runtime->oss.format)); + snd_iprintf(buffer, "OSS channels: %u\n", runtime->oss.channels); + snd_iprintf(buffer, "OSS rate: %u\n", runtime->oss.rate); + snd_iprintf(buffer, "OSS period bytes: %lu\n", (unsigned long)runtime->oss.period_bytes); + snd_iprintf(buffer, "OSS periods: %u\n", runtime->oss.periods); + snd_iprintf(buffer, "OSS period frames: %lu\n", (unsigned long)runtime->oss.period_frames); + } +#endif + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +static void snd_pcm_substream_proc_sw_params_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct snd_pcm_runtime *runtime; + + mutex_lock(&substream->pcm->open_mutex); + runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + goto unlock; + } + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_iprintf(buffer, "no setup\n"); + goto unlock; + } + snd_iprintf(buffer, "tstamp_mode: %s\n", snd_pcm_tstamp_mode_name(runtime->tstamp_mode)); + snd_iprintf(buffer, "period_step: %u\n", runtime->period_step); + snd_iprintf(buffer, "avail_min: %lu\n", runtime->control->avail_min); + snd_iprintf(buffer, "start_threshold: %lu\n", runtime->start_threshold); + snd_iprintf(buffer, "stop_threshold: %lu\n", runtime->stop_threshold); + snd_iprintf(buffer, "silence_threshold: %lu\n", runtime->silence_threshold); + snd_iprintf(buffer, "silence_size: %lu\n", runtime->silence_size); + snd_iprintf(buffer, "boundary: %lu\n", runtime->boundary); + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +static void snd_pcm_substream_proc_status_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct snd_pcm_runtime *runtime; + struct snd_pcm_status status; + int err; + + mutex_lock(&substream->pcm->open_mutex); + runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + goto unlock; + } + memset(&status, 0, sizeof(status)); + err = snd_pcm_status(substream, &status); + if (err < 0) { + snd_iprintf(buffer, "error %d\n", err); + goto unlock; + } + snd_iprintf(buffer, "state: %s\n", snd_pcm_state_name(status.state)); + snd_iprintf(buffer, "owner_pid : %d\n", pid_vnr(substream->pid)); + snd_iprintf(buffer, "trigger_time: %ld.%09ld\n", + status.trigger_tstamp.tv_sec, status.trigger_tstamp.tv_nsec); + snd_iprintf(buffer, "tstamp : %ld.%09ld\n", + status.tstamp.tv_sec, status.tstamp.tv_nsec); + snd_iprintf(buffer, "delay : %ld\n", status.delay); + snd_iprintf(buffer, "avail : %ld\n", status.avail); + snd_iprintf(buffer, "avail_max : %ld\n", status.avail_max); + snd_iprintf(buffer, "-----\n"); + snd_iprintf(buffer, "hw_ptr : %ld\n", runtime->status->hw_ptr); + snd_iprintf(buffer, "appl_ptr : %ld\n", runtime->control->appl_ptr); + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG +static void snd_pcm_xrun_injection_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + + snd_pcm_stop_xrun(substream); +} + +static void snd_pcm_xrun_debug_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + snd_iprintf(buffer, "%d\n", pstr->xrun_debug); +} + +static void snd_pcm_xrun_debug_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + char line[64]; + if (!snd_info_get_line(buffer, line, sizeof(line))) + pstr->xrun_debug = simple_strtoul(line, NULL, 10); +} +#endif + +static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) +{ + struct snd_pcm *pcm = pstr->pcm; + struct snd_info_entry *entry; + char name[16]; + + sprintf(name, "pcm%i%c", pcm->device, + pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); + entry = snd_info_create_card_entry(pcm->card, name, + pcm->card->proc_root); + if (!entry) + return -ENOMEM; + entry->mode = S_IFDIR | 0555; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + pstr->proc_root = entry; + entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root); + if (entry) { + snd_info_set_text_ops(entry, pstr, snd_pcm_stream_proc_info_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + pstr->proc_info_entry = entry; + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + entry = snd_info_create_card_entry(pcm->card, "xrun_debug", + pstr->proc_root); + if (entry) { + entry->c.text.read = snd_pcm_xrun_debug_read; + entry->c.text.write = snd_pcm_xrun_debug_write; + entry->mode |= 0200; + entry->private_data = pstr; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + pstr->proc_xrun_debug_entry = entry; +#endif + return 0; +} + +static int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) +{ +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + snd_info_free_entry(pstr->proc_xrun_debug_entry); + pstr->proc_xrun_debug_entry = NULL; +#endif + snd_info_free_entry(pstr->proc_info_entry); + pstr->proc_info_entry = NULL; + snd_info_free_entry(pstr->proc_root); + pstr->proc_root = NULL; + return 0; +} + +static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) +{ + struct snd_info_entry *entry; + struct snd_card *card; + char name[16]; + + card = substream->pcm->card; + + sprintf(name, "sub%i", substream->number); + entry = snd_info_create_card_entry(card, name, + substream->pstr->proc_root); + if (!entry) + return -ENOMEM; + entry->mode = S_IFDIR | 0555; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + substream->proc_root = entry; + entry = snd_info_create_card_entry(card, "info", substream->proc_root); + if (entry) { + snd_info_set_text_ops(entry, substream, + snd_pcm_substream_proc_info_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_info_entry = entry; + entry = snd_info_create_card_entry(card, "hw_params", + substream->proc_root); + if (entry) { + snd_info_set_text_ops(entry, substream, + snd_pcm_substream_proc_hw_params_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_hw_params_entry = entry; + entry = snd_info_create_card_entry(card, "sw_params", + substream->proc_root); + if (entry) { + snd_info_set_text_ops(entry, substream, + snd_pcm_substream_proc_sw_params_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_sw_params_entry = entry; + entry = snd_info_create_card_entry(card, "status", + substream->proc_root); + if (entry) { + snd_info_set_text_ops(entry, substream, + snd_pcm_substream_proc_status_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_status_entry = entry; + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + entry = snd_info_create_card_entry(card, "xrun_injection", + substream->proc_root); + if (entry) { + entry->private_data = substream; + entry->c.text.read = NULL; + entry->c.text.write = snd_pcm_xrun_injection_write; + entry->mode = S_IFREG | 0200; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_xrun_injection_entry = entry; +#endif /* CONFIG_SND_PCM_XRUN_DEBUG */ + + return 0; +} + +static int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream) +{ + snd_info_free_entry(substream->proc_info_entry); + substream->proc_info_entry = NULL; + snd_info_free_entry(substream->proc_hw_params_entry); + substream->proc_hw_params_entry = NULL; + snd_info_free_entry(substream->proc_sw_params_entry); + substream->proc_sw_params_entry = NULL; + snd_info_free_entry(substream->proc_status_entry); + substream->proc_status_entry = NULL; +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + snd_info_free_entry(substream->proc_xrun_injection_entry); + substream->proc_xrun_injection_entry = NULL; +#endif + snd_info_free_entry(substream->proc_root); + substream->proc_root = NULL; + return 0; +} +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +static inline int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { return 0; } +static inline int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) { return 0; } +static inline int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) { return 0; } +static inline int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream) { return 0; } +#endif /* CONFIG_SND_VERBOSE_PROCFS */ + +static const struct attribute_group *pcm_dev_attr_groups[]; + +/** + * snd_pcm_new_stream - create a new PCM stream + * @pcm: the pcm instance + * @stream: the stream direction, SNDRV_PCM_STREAM_XXX + * @substream_count: the number of substreams + * + * Creates a new stream for the pcm. + * The corresponding stream on the pcm must have been empty before + * calling this, i.e. zero must be given to the argument of + * snd_pcm_new(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count) +{ + int idx, err; + struct snd_pcm_str *pstr = &pcm->streams[stream]; + struct snd_pcm_substream *substream, *prev; + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + mutex_init(&pstr->oss.setup_mutex); +#endif + pstr->stream = stream; + pstr->pcm = pcm; + pstr->substream_count = substream_count; + if (!substream_count) + return 0; + + snd_device_initialize(&pstr->dev, pcm->card); + pstr->dev.groups = pcm_dev_attr_groups; + dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device, + stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); + + if (!pcm->internal) { + err = snd_pcm_stream_proc_init(pstr); + if (err < 0) { + pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n"); + return err; + } + } + prev = NULL; + for (idx = 0, prev = NULL; idx < substream_count; idx++) { + substream = kzalloc(sizeof(*substream), GFP_KERNEL); + if (!substream) + return -ENOMEM; + substream->pcm = pcm; + substream->pstr = pstr; + substream->number = idx; + substream->stream = stream; + sprintf(substream->name, "subdevice #%i", idx); + substream->buffer_bytes_max = UINT_MAX; + if (prev == NULL) + pstr->substream = substream; + else + prev->next = substream; + + if (!pcm->internal) { + err = snd_pcm_substream_proc_init(substream); + if (err < 0) { + pcm_err(pcm, + "Error in snd_pcm_stream_proc_init\n"); + if (prev == NULL) + pstr->substream = NULL; + else + prev->next = NULL; + kfree(substream); + return err; + } + } + substream->group = &substream->self_group; + spin_lock_init(&substream->self_group.lock); + mutex_init(&substream->self_group.mutex); + INIT_LIST_HEAD(&substream->self_group.substreams); + list_add_tail(&substream->link_list, &substream->self_group.substreams); + atomic_set(&substream->mmap_count, 0); + prev = substream; + } + return 0; +} +EXPORT_SYMBOL(snd_pcm_new_stream); + +static int _snd_pcm_new(struct snd_card *card, const char *id, int device, + int playback_count, int capture_count, bool internal, + struct snd_pcm **rpcm) +{ + struct snd_pcm *pcm; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_pcm_dev_free, + .dev_register = snd_pcm_dev_register, + .dev_disconnect = snd_pcm_dev_disconnect, + }; + static struct snd_device_ops internal_ops = { + .dev_free = snd_pcm_dev_free, + }; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (rpcm) + *rpcm = NULL; + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + pcm->card = card; + pcm->device = device; + pcm->internal = internal; + mutex_init(&pcm->open_mutex); + init_waitqueue_head(&pcm->open_wait); + INIT_LIST_HEAD(&pcm->list); + if (id) + strlcpy(pcm->id, id, sizeof(pcm->id)); + + err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, + playback_count); + if (err < 0) + goto free_pcm; + + err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count); + if (err < 0) + goto free_pcm; + + err = snd_device_new(card, SNDRV_DEV_PCM, pcm, + internal ? &internal_ops : &ops); + if (err < 0) + goto free_pcm; + + if (rpcm) + *rpcm = pcm; + return 0; + +free_pcm: + snd_pcm_free(pcm); + return err; +} + +/** + * snd_pcm_new - create a new PCM instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero based) + * @playback_count: the number of substreams for playback + * @capture_count: the number of substreams for capture + * @rpcm: the pointer to store the new pcm instance + * + * Creates a new PCM instance. + * + * The pcm operators have to be set afterwards to the new instance + * via snd_pcm_set_ops(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_new(struct snd_card *card, const char *id, int device, + int playback_count, int capture_count, struct snd_pcm **rpcm) +{ + return _snd_pcm_new(card, id, device, playback_count, capture_count, + false, rpcm); +} +EXPORT_SYMBOL(snd_pcm_new); + +/** + * snd_pcm_new_internal - create a new internal PCM instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero based - shared with normal PCMs) + * @playback_count: the number of substreams for playback + * @capture_count: the number of substreams for capture + * @rpcm: the pointer to store the new pcm instance + * + * Creates a new internal PCM instance with no userspace device or procfs + * entries. This is used by ASoC Back End PCMs in order to create a PCM that + * will only be used internally by kernel drivers. i.e. it cannot be opened + * by userspace. It provides existing ASoC components drivers with a substream + * and access to any private data. + * + * The pcm operators have to be set afterwards to the new instance + * via snd_pcm_set_ops(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_new_internal(struct snd_card *card, const char *id, int device, + int playback_count, int capture_count, + struct snd_pcm **rpcm) +{ + return _snd_pcm_new(card, id, device, playback_count, capture_count, + true, rpcm); +} +EXPORT_SYMBOL(snd_pcm_new_internal); + +static void free_chmap(struct snd_pcm_str *pstr) +{ + if (pstr->chmap_kctl) { + struct snd_card *card = pstr->pcm->card; + + down_write(&card->controls_rwsem); + snd_ctl_remove(card, pstr->chmap_kctl); + up_write(&card->controls_rwsem); + pstr->chmap_kctl = NULL; + } +} + +static void snd_pcm_free_stream(struct snd_pcm_str * pstr) +{ + struct snd_pcm_substream *substream, *substream_next; +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + struct snd_pcm_oss_setup *setup, *setupn; +#endif + substream = pstr->substream; + while (substream) { + substream_next = substream->next; + snd_pcm_timer_done(substream); + snd_pcm_substream_proc_done(substream); + kfree(substream); + substream = substream_next; + } + snd_pcm_stream_proc_done(pstr); +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + for (setup = pstr->oss.setup_list; setup; setup = setupn) { + setupn = setup->next; + kfree(setup->task_name); + kfree(setup); + } +#endif + free_chmap(pstr); + if (pstr->substream_count) + put_device(&pstr->dev); +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#define pcm_call_notify(pcm, call) \ + do { \ + struct snd_pcm_notify *_notify; \ + list_for_each_entry(_notify, &snd_pcm_notify_list, list) \ + _notify->call(pcm); \ + } while (0) +#else +#define pcm_call_notify(pcm, call) do {} while (0) +#endif + +static int snd_pcm_free(struct snd_pcm *pcm) +{ + if (!pcm) + return 0; + if (!pcm->internal) + pcm_call_notify(pcm, n_unregister); + if (pcm->private_free) + pcm->private_free(pcm); + snd_pcm_lib_preallocate_free_for_all(pcm); + snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]); + snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_CAPTURE]); + kfree(pcm); + return 0; +} + +static int snd_pcm_dev_free(struct snd_device *device) +{ + struct snd_pcm *pcm = device->device_data; + return snd_pcm_free(pcm); +} + +int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, + struct file *file, + struct snd_pcm_substream **rsubstream) +{ + struct snd_pcm_str * pstr; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct snd_card *card; + int prefer_subdevice; + size_t size; + + if (snd_BUG_ON(!pcm || !rsubstream)) + return -ENXIO; + if (snd_BUG_ON(stream != SNDRV_PCM_STREAM_PLAYBACK && + stream != SNDRV_PCM_STREAM_CAPTURE)) + return -EINVAL; + *rsubstream = NULL; + pstr = &pcm->streams[stream]; + if (pstr->substream == NULL || pstr->substream_count == 0) + return -ENODEV; + + card = pcm->card; + prefer_subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_PCM); + + if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) { + int opposite = !stream; + + for (substream = pcm->streams[opposite].substream; substream; + substream = substream->next) { + if (SUBSTREAM_BUSY(substream)) + return -EAGAIN; + } + } + + if (file->f_flags & O_APPEND) { + if (prefer_subdevice < 0) { + if (pstr->substream_count > 1) + return -EINVAL; /* must be unique */ + substream = pstr->substream; + } else { + for (substream = pstr->substream; substream; + substream = substream->next) + if (substream->number == prefer_subdevice) + break; + } + if (! substream) + return -ENODEV; + if (! SUBSTREAM_BUSY(substream)) + return -EBADFD; + substream->ref_count++; + *rsubstream = substream; + return 0; + } + + for (substream = pstr->substream; substream; substream = substream->next) { + if (!SUBSTREAM_BUSY(substream) && + (prefer_subdevice == -1 || + substream->number == prefer_subdevice)) + break; + } + if (substream == NULL) + return -EAGAIN; + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (runtime == NULL) + return -ENOMEM; + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); + runtime->status = snd_malloc_pages(size, GFP_KERNEL); + if (runtime->status == NULL) { + kfree(runtime); + return -ENOMEM; + } + memset((void*)runtime->status, 0, size); + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); + runtime->control = snd_malloc_pages(size, GFP_KERNEL); + if (runtime->control == NULL) { + snd_free_pages((void*)runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); + kfree(runtime); + return -ENOMEM; + } + memset((void*)runtime->control, 0, size); + + init_waitqueue_head(&runtime->sleep); + init_waitqueue_head(&runtime->tsleep); + + runtime->status->state = SNDRV_PCM_STATE_OPEN; + mutex_init(&runtime->buffer_mutex); + atomic_set(&runtime->buffer_accessing, 0); + + substream->runtime = runtime; + substream->private_data = pcm->private_data; + substream->ref_count = 1; + substream->f_flags = file->f_flags; + substream->pid = get_pid(task_pid(current)); + pstr->substream_opened++; + *rsubstream = substream; + return 0; +} + +void snd_pcm_detach_substream(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return; + runtime = substream->runtime; + if (runtime->private_free != NULL) + runtime->private_free(runtime); + snd_free_pages((void*)runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); + snd_free_pages((void*)runtime->control, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control))); + kfree(runtime->hw_constraints.rules); + /* Avoid concurrent access to runtime via PCM timer interface */ + if (substream->timer) + spin_lock_irq(&substream->timer->lock); + substream->runtime = NULL; + if (substream->timer) + spin_unlock_irq(&substream->timer->lock); + mutex_destroy(&runtime->buffer_mutex); + kfree(runtime); + put_pid(substream->pid); + substream->pid = NULL; + substream->pstr->substream_opened--; +} + +static ssize_t show_pcm_class(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_pcm_str *pstr = container_of(dev, struct snd_pcm_str, dev); + struct snd_pcm *pcm = pstr->pcm; + const char *str; + static const char *strs[SNDRV_PCM_CLASS_LAST + 1] = { + [SNDRV_PCM_CLASS_GENERIC] = "generic", + [SNDRV_PCM_CLASS_MULTI] = "multi", + [SNDRV_PCM_CLASS_MODEM] = "modem", + [SNDRV_PCM_CLASS_DIGITIZER] = "digitizer", + }; + + if (pcm->dev_class > SNDRV_PCM_CLASS_LAST) + str = "none"; + else + str = strs[pcm->dev_class]; + return snprintf(buf, PAGE_SIZE, "%s\n", str); +} + +static DEVICE_ATTR(pcm_class, 0444, show_pcm_class, NULL); +static struct attribute *pcm_dev_attrs[] = { + &dev_attr_pcm_class.attr, + NULL +}; + +static const struct attribute_group pcm_dev_attr_group = { + .attrs = pcm_dev_attrs, +}; + +static const struct attribute_group *pcm_dev_attr_groups[] = { + &pcm_dev_attr_group, + NULL +}; + +static int snd_pcm_dev_register(struct snd_device *device) +{ + int cidx, err; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm; + + if (snd_BUG_ON(!device || !device->device_data)) + return -ENXIO; + pcm = device->device_data; + + mutex_lock(®ister_mutex); + err = snd_pcm_add(pcm); + if (err) + goto unlock; + for (cidx = 0; cidx < 2; cidx++) { + int devtype = -1; + if (pcm->streams[cidx].substream == NULL) + continue; + switch (cidx) { + case SNDRV_PCM_STREAM_PLAYBACK: + devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK; + break; + case SNDRV_PCM_STREAM_CAPTURE: + devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE; + break; + } + /* register pcm */ + err = snd_register_device(devtype, pcm->card, pcm->device, + &snd_pcm_f_ops[cidx], pcm, + &pcm->streams[cidx].dev); + if (err < 0) { + list_del_init(&pcm->list); + goto unlock; + } + + for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) + snd_pcm_timer_init(substream); + } + + pcm_call_notify(pcm, n_register); + + unlock: + mutex_unlock(®ister_mutex); + return err; +} + +static int snd_pcm_dev_disconnect(struct snd_device *device) +{ + struct snd_pcm *pcm = device->device_data; + struct snd_pcm_substream *substream; + int cidx; + + mutex_lock(®ister_mutex); + mutex_lock(&pcm->open_mutex); + wake_up(&pcm->open_wait); + list_del_init(&pcm->list); + for (cidx = 0; cidx < 2; cidx++) { + for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) { + snd_pcm_stream_lock_irq(substream); + if (substream->runtime) { + if (snd_pcm_running(substream)) + snd_pcm_stop(substream, + SNDRV_PCM_STATE_DISCONNECTED); + /* to be sure, set the state unconditionally */ + substream->runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED; + wake_up(&substream->runtime->sleep); + wake_up(&substream->runtime->tsleep); + } + snd_pcm_stream_unlock_irq(substream); + } + } + + pcm_call_notify(pcm, n_disconnect); + for (cidx = 0; cidx < 2; cidx++) { + snd_unregister_device(&pcm->streams[cidx].dev); + free_chmap(&pcm->streams[cidx]); + } + mutex_unlock(&pcm->open_mutex); + mutex_unlock(®ister_mutex); + return 0; +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +/** + * snd_pcm_notify - Add/remove the notify list + * @notify: PCM notify list + * @nfree: 0 = register, 1 = unregister + * + * This adds the given notifier to the global list so that the callback is + * called for each registered PCM devices. This exists only for PCM OSS + * emulation, so far. + */ +int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree) +{ + struct snd_pcm *pcm; + + if (snd_BUG_ON(!notify || + !notify->n_register || + !notify->n_unregister || + !notify->n_disconnect)) + return -EINVAL; + mutex_lock(®ister_mutex); + if (nfree) { + list_del(¬ify->list); + list_for_each_entry(pcm, &snd_pcm_devices, list) + notify->n_unregister(pcm); + } else { + list_add_tail(¬ify->list, &snd_pcm_notify_list); + list_for_each_entry(pcm, &snd_pcm_devices, list) + notify->n_register(pcm); + } + mutex_unlock(®ister_mutex); + return 0; +} +EXPORT_SYMBOL(snd_pcm_notify); +#endif /* CONFIG_SND_PCM_OSS */ + +#ifdef CONFIG_SND_PROC_FS +/* + * Info interface + */ + +static void snd_pcm_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm *pcm; + + mutex_lock(®ister_mutex); + list_for_each_entry(pcm, &snd_pcm_devices, list) { + snd_iprintf(buffer, "%02i-%02i: %s : %s", + pcm->card->number, pcm->device, pcm->id, pcm->name); + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + snd_iprintf(buffer, " : playback %i", + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count); + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) + snd_iprintf(buffer, " : capture %i", + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count); + snd_iprintf(buffer, "\n"); + } + mutex_unlock(®ister_mutex); +} + +static struct snd_info_entry *snd_pcm_proc_entry; + +static void snd_pcm_proc_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL); + if (entry) { + snd_info_set_text_ops(entry, NULL, snd_pcm_proc_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_pcm_proc_entry = entry; +} + +static void snd_pcm_proc_done(void) +{ + snd_info_free_entry(snd_pcm_proc_entry); +} + +#else /* !CONFIG_SND_PROC_FS */ +#define snd_pcm_proc_init() +#define snd_pcm_proc_done() +#endif /* CONFIG_SND_PROC_FS */ + + +/* + * ENTRY functions + */ + +static int __init alsa_pcm_init(void) +{ + snd_ctl_register_ioctl(snd_pcm_control_ioctl); + snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl); + snd_pcm_proc_init(); + return 0; +} + +static void __exit alsa_pcm_exit(void) +{ + snd_ctl_unregister_ioctl(snd_pcm_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_pcm_control_ioctl); + snd_pcm_proc_done(); +} + +module_init(alsa_pcm_init) +module_exit(alsa_pcm_exit) diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c new file mode 100644 index 000000000..946ab080a --- /dev/null +++ b/sound/core/pcm_compat.c @@ -0,0 +1,732 @@ +/* + * 32bit -> 64bit ioctl wrapper for PCM API + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from pcm_native.c */ + +#include +#include + +static int snd_pcm_ioctl_delay_compat(struct snd_pcm_substream *substream, + s32 __user *src) +{ + snd_pcm_sframes_t delay; + int err; + + err = snd_pcm_delay(substream, &delay); + if (err) + return err; + if (put_user(delay, src)) + return -EFAULT; + return 0; +} + +static int snd_pcm_ioctl_rewind_compat(struct snd_pcm_substream *substream, + u32 __user *src) +{ + snd_pcm_uframes_t frames; + int err; + + if (get_user(frames, src)) + return -EFAULT; + err = snd_pcm_rewind(substream, frames); + if (put_user(err, src)) + return -EFAULT; + return err < 0 ? err : 0; +} + +static int snd_pcm_ioctl_forward_compat(struct snd_pcm_substream *substream, + u32 __user *src) +{ + snd_pcm_uframes_t frames; + int err; + + if (get_user(frames, src)) + return -EFAULT; + err = snd_pcm_forward(substream, frames); + if (put_user(err, src)) + return -EFAULT; + return err < 0 ? err : 0; +} + +struct snd_pcm_hw_params32 { + u32 flags; + struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */ + struct snd_mask mres[5]; /* reserved masks */ + struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1]; + struct snd_interval ires[9]; /* reserved intervals */ + u32 rmask; + u32 cmask; + u32 info; + u32 msbits; + u32 rate_num; + u32 rate_den; + u32 fifo_size; + unsigned char reserved[64]; +}; + +struct snd_pcm_sw_params32 { + s32 tstamp_mode; + u32 period_step; + u32 sleep_min; + u32 avail_min; + u32 xfer_align; + u32 start_threshold; + u32 stop_threshold; + u32 silence_threshold; + u32 silence_size; + u32 boundary; + u32 proto; + u32 tstamp_type; + unsigned char reserved[56]; +}; + +/* recalcuate the boundary within 32bit */ +static snd_pcm_uframes_t recalculate_boundary(struct snd_pcm_runtime *runtime) +{ + snd_pcm_uframes_t boundary; + + if (! runtime->buffer_size) + return 0; + boundary = runtime->buffer_size; + while (boundary * 2 <= 0x7fffffffUL - runtime->buffer_size) + boundary *= 2; + return boundary; +} + +static int snd_pcm_ioctl_sw_params_compat(struct snd_pcm_substream *substream, + struct snd_pcm_sw_params32 __user *src) +{ + struct snd_pcm_sw_params params; + snd_pcm_uframes_t boundary; + int err; + + memset(¶ms, 0, sizeof(params)); + if (get_user(params.tstamp_mode, &src->tstamp_mode) || + get_user(params.period_step, &src->period_step) || + get_user(params.sleep_min, &src->sleep_min) || + get_user(params.avail_min, &src->avail_min) || + get_user(params.xfer_align, &src->xfer_align) || + get_user(params.start_threshold, &src->start_threshold) || + get_user(params.stop_threshold, &src->stop_threshold) || + get_user(params.silence_threshold, &src->silence_threshold) || + get_user(params.silence_size, &src->silence_size) || + get_user(params.tstamp_type, &src->tstamp_type) || + get_user(params.proto, &src->proto)) + return -EFAULT; + /* + * Check silent_size parameter. Since we have 64bit boundary, + * silence_size must be compared with the 32bit boundary. + */ + boundary = recalculate_boundary(substream->runtime); + if (boundary && params.silence_size >= boundary) + params.silence_size = substream->runtime->boundary; + err = snd_pcm_sw_params(substream, ¶ms); + if (err < 0) + return err; + if (boundary && put_user(boundary, &src->boundary)) + return -EFAULT; + return err; +} + +struct snd_pcm_channel_info32 { + u32 channel; + u32 offset; + u32 first; + u32 step; +}; + +static int snd_pcm_ioctl_channel_info_compat(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info32 __user *src) +{ + struct snd_pcm_channel_info info; + int err; + + if (get_user(info.channel, &src->channel) || + get_user(info.offset, &src->offset) || + get_user(info.first, &src->first) || + get_user(info.step, &src->step)) + return -EFAULT; + err = snd_pcm_channel_info(substream, &info); + if (err < 0) + return err; + if (put_user(info.channel, &src->channel) || + put_user(info.offset, &src->offset) || + put_user(info.first, &src->first) || + put_user(info.step, &src->step)) + return -EFAULT; + return err; +} + +#ifdef CONFIG_X86_X32 +/* X32 ABI has the same struct as x86-64 for snd_pcm_channel_info */ +static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info __user *src); +#define snd_pcm_ioctl_channel_info_x32(s, p) \ + snd_pcm_channel_info_user(s, p) +#endif /* CONFIG_X86_X32 */ + +struct snd_pcm_status32 { + s32 state; + struct compat_timespec trigger_tstamp; + struct compat_timespec tstamp; + u32 appl_ptr; + u32 hw_ptr; + s32 delay; + u32 avail; + u32 avail_max; + u32 overrange; + s32 suspended_state; + u32 audio_tstamp_data; + struct compat_timespec audio_tstamp; + struct compat_timespec driver_tstamp; + u32 audio_tstamp_accuracy; + unsigned char reserved[52-2*sizeof(struct compat_timespec)]; +} __attribute__((packed)); + + +static int snd_pcm_status_user_compat(struct snd_pcm_substream *substream, + struct snd_pcm_status32 __user *src, + bool ext) +{ + struct snd_pcm_status status; + int err; + + memset(&status, 0, sizeof(status)); + /* + * with extension, parameters are read/write, + * get audio_tstamp_data from user, + * ignore rest of status structure + */ + if (ext && get_user(status.audio_tstamp_data, + (u32 __user *)(&src->audio_tstamp_data))) + return -EFAULT; + err = snd_pcm_status(substream, &status); + if (err < 0) + return err; + + if (clear_user(src, sizeof(*src))) + return -EFAULT; + if (put_user(status.state, &src->state) || + compat_put_timespec(&status.trigger_tstamp, &src->trigger_tstamp) || + compat_put_timespec(&status.tstamp, &src->tstamp) || + put_user(status.appl_ptr, &src->appl_ptr) || + put_user(status.hw_ptr, &src->hw_ptr) || + put_user(status.delay, &src->delay) || + put_user(status.avail, &src->avail) || + put_user(status.avail_max, &src->avail_max) || + put_user(status.overrange, &src->overrange) || + put_user(status.suspended_state, &src->suspended_state) || + put_user(status.audio_tstamp_data, &src->audio_tstamp_data) || + compat_put_timespec(&status.audio_tstamp, &src->audio_tstamp) || + compat_put_timespec(&status.driver_tstamp, &src->driver_tstamp) || + put_user(status.audio_tstamp_accuracy, &src->audio_tstamp_accuracy)) + return -EFAULT; + + return err; +} + +#ifdef CONFIG_X86_X32 +/* X32 ABI has 64bit timespec and 64bit alignment */ +struct snd_pcm_status_x32 { + s32 state; + u32 rsvd; /* alignment */ + struct timespec trigger_tstamp; + struct timespec tstamp; + u32 appl_ptr; + u32 hw_ptr; + s32 delay; + u32 avail; + u32 avail_max; + u32 overrange; + s32 suspended_state; + u32 audio_tstamp_data; + struct timespec audio_tstamp; + struct timespec driver_tstamp; + u32 audio_tstamp_accuracy; + unsigned char reserved[52-2*sizeof(struct timespec)]; +} __packed; + +#define put_timespec(src, dst) copy_to_user(dst, src, sizeof(*dst)) + +static int snd_pcm_status_user_x32(struct snd_pcm_substream *substream, + struct snd_pcm_status_x32 __user *src, + bool ext) +{ + struct snd_pcm_status status; + int err; + + memset(&status, 0, sizeof(status)); + /* + * with extension, parameters are read/write, + * get audio_tstamp_data from user, + * ignore rest of status structure + */ + if (ext && get_user(status.audio_tstamp_data, + (u32 __user *)(&src->audio_tstamp_data))) + return -EFAULT; + err = snd_pcm_status(substream, &status); + if (err < 0) + return err; + + if (clear_user(src, sizeof(*src))) + return -EFAULT; + if (put_user(status.state, &src->state) || + put_timespec(&status.trigger_tstamp, &src->trigger_tstamp) || + put_timespec(&status.tstamp, &src->tstamp) || + put_user(status.appl_ptr, &src->appl_ptr) || + put_user(status.hw_ptr, &src->hw_ptr) || + put_user(status.delay, &src->delay) || + put_user(status.avail, &src->avail) || + put_user(status.avail_max, &src->avail_max) || + put_user(status.overrange, &src->overrange) || + put_user(status.suspended_state, &src->suspended_state) || + put_user(status.audio_tstamp_data, &src->audio_tstamp_data) || + put_timespec(&status.audio_tstamp, &src->audio_tstamp) || + put_timespec(&status.driver_tstamp, &src->driver_tstamp) || + put_user(status.audio_tstamp_accuracy, &src->audio_tstamp_accuracy)) + return -EFAULT; + + return err; +} +#endif /* CONFIG_X86_X32 */ + +/* both for HW_PARAMS and HW_REFINE */ +static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, + int refine, + struct snd_pcm_hw_params32 __user *data32) +{ + struct snd_pcm_hw_params *data; + struct snd_pcm_runtime *runtime; + int err; + + if (! (runtime = substream->runtime)) + return -ENOTTY; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* only fifo_size (RO from userspace) is different, so just copy all */ + if (copy_from_user(data, data32, sizeof(*data32))) { + err = -EFAULT; + goto error; + } + + if (refine) + err = snd_pcm_hw_refine(substream, data); + else + err = snd_pcm_hw_params(substream, data); + if (err < 0) + goto error; + if (copy_to_user(data32, data, sizeof(*data32)) || + put_user(data->fifo_size, &data32->fifo_size)) { + err = -EFAULT; + goto error; + } + + if (! refine) { + unsigned int new_boundary = recalculate_boundary(runtime); + if (new_boundary) + runtime->boundary = new_boundary; + } + error: + kfree(data); + return err; +} + + +/* + */ +struct snd_xferi32 { + s32 result; + u32 buf; + u32 frames; +}; + +static int snd_pcm_ioctl_xferi_compat(struct snd_pcm_substream *substream, + int dir, struct snd_xferi32 __user *data32) +{ + compat_caddr_t buf; + u32 frames; + int err; + + if (! substream->runtime) + return -ENOTTY; + if (substream->stream != dir) + return -EINVAL; + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + if (get_user(buf, &data32->buf) || + get_user(frames, &data32->frames)) + return -EFAULT; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_lib_write(substream, compat_ptr(buf), frames); + else + err = snd_pcm_lib_read(substream, compat_ptr(buf), frames); + if (err < 0) + return err; + /* copy the result */ + if (put_user(err, &data32->result)) + return -EFAULT; + return 0; +} + + +/* snd_xfern needs remapping of bufs */ +struct snd_xfern32 { + s32 result; + u32 bufs; /* this is void **; */ + u32 frames; +}; + +/* + * xfern ioctl nees to copy (up to) 128 pointers on stack. + * although we may pass the copied pointers through f_op->ioctl, but the ioctl + * handler there expands again the same 128 pointers on stack, so it is better + * to handle the function (calling pcm_readv/writev) directly in this handler. + */ +static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream, + int dir, struct snd_xfern32 __user *data32) +{ + compat_caddr_t buf; + compat_caddr_t __user *bufptr; + u32 frames; + void __user **bufs; + int err, ch, i; + + if (! substream->runtime) + return -ENOTTY; + if (substream->stream != dir) + return -EINVAL; + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + if ((ch = substream->runtime->channels) > 128) + return -EINVAL; + if (get_user(buf, &data32->bufs) || + get_user(frames, &data32->frames)) + return -EFAULT; + bufptr = compat_ptr(buf); + bufs = kmalloc_array(ch, sizeof(void __user *), GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < ch; i++) { + u32 ptr; + if (get_user(ptr, bufptr)) { + kfree(bufs); + return -EFAULT; + } + bufs[i] = compat_ptr(ptr); + bufptr++; + } + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_lib_writev(substream, bufs, frames); + else + err = snd_pcm_lib_readv(substream, bufs, frames); + if (err >= 0) { + if (put_user(err, &data32->result)) + err = -EFAULT; + } + kfree(bufs); + return err; +} + + +struct snd_pcm_mmap_status32 { + s32 state; + s32 pad1; + u32 hw_ptr; + struct compat_timespec tstamp; + s32 suspended_state; + struct compat_timespec audio_tstamp; +} __attribute__((packed)); + +struct snd_pcm_mmap_control32 { + u32 appl_ptr; + u32 avail_min; +}; + +struct snd_pcm_sync_ptr32 { + u32 flags; + union { + struct snd_pcm_mmap_status32 status; + unsigned char reserved[64]; + } s; + union { + struct snd_pcm_mmap_control32 control; + unsigned char reserved[64]; + } c; +} __attribute__((packed)); + +static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream, + struct snd_pcm_sync_ptr32 __user *src) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + volatile struct snd_pcm_mmap_status *status; + volatile struct snd_pcm_mmap_control *control; + u32 sflags; + struct snd_pcm_mmap_control scontrol; + struct snd_pcm_mmap_status sstatus; + snd_pcm_uframes_t boundary; + int err; + + if (snd_BUG_ON(!runtime)) + return -EINVAL; + + if (get_user(sflags, &src->flags) || + get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + get_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + status = runtime->status; + control = runtime->control; + boundary = recalculate_boundary(runtime); + if (! boundary) + boundary = 0x7fffffff; + snd_pcm_stream_lock_irq(substream); + /* FIXME: we should consider the boundary for the sync from app */ + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + control->appl_ptr = scontrol.appl_ptr; + else + scontrol.appl_ptr = control->appl_ptr % boundary; + if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = scontrol.avail_min; + else + scontrol.avail_min = control->avail_min; + sstatus.state = status->state; + sstatus.hw_ptr = status->hw_ptr % boundary; + sstatus.tstamp = status->tstamp; + sstatus.suspended_state = status->suspended_state; + sstatus.audio_tstamp = status->audio_tstamp; + snd_pcm_stream_unlock_irq(substream); + if (put_user(sstatus.state, &src->s.status.state) || + put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || + compat_put_timespec(&sstatus.tstamp, &src->s.status.tstamp) || + put_user(sstatus.suspended_state, &src->s.status.suspended_state) || + compat_put_timespec(&sstatus.audio_tstamp, + &src->s.status.audio_tstamp) || + put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + put_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + + return 0; +} + +#ifdef CONFIG_X86_X32 +/* X32 ABI has 64bit timespec and 64bit alignment */ +struct snd_pcm_mmap_status_x32 { + s32 state; + s32 pad1; + u32 hw_ptr; + u32 pad2; /* alignment */ + struct timespec tstamp; + s32 suspended_state; + s32 pad3; + struct timespec audio_tstamp; +} __packed; + +struct snd_pcm_mmap_control_x32 { + u32 appl_ptr; + u32 avail_min; +}; + +struct snd_pcm_sync_ptr_x32 { + u32 flags; + u32 rsvd; /* alignment */ + union { + struct snd_pcm_mmap_status_x32 status; + unsigned char reserved[64]; + } s; + union { + struct snd_pcm_mmap_control_x32 control; + unsigned char reserved[64]; + } c; +} __packed; + +static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream, + struct snd_pcm_sync_ptr_x32 __user *src) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + volatile struct snd_pcm_mmap_status *status; + volatile struct snd_pcm_mmap_control *control; + u32 sflags; + struct snd_pcm_mmap_control scontrol; + struct snd_pcm_mmap_status sstatus; + snd_pcm_uframes_t boundary; + int err; + + if (snd_BUG_ON(!runtime)) + return -EINVAL; + + if (get_user(sflags, &src->flags) || + get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + get_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + status = runtime->status; + control = runtime->control; + boundary = recalculate_boundary(runtime); + if (!boundary) + boundary = 0x7fffffff; + snd_pcm_stream_lock_irq(substream); + /* FIXME: we should consider the boundary for the sync from app */ + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + control->appl_ptr = scontrol.appl_ptr; + else + scontrol.appl_ptr = control->appl_ptr % boundary; + if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = scontrol.avail_min; + else + scontrol.avail_min = control->avail_min; + sstatus.state = status->state; + sstatus.hw_ptr = status->hw_ptr % boundary; + sstatus.tstamp = status->tstamp; + sstatus.suspended_state = status->suspended_state; + sstatus.audio_tstamp = status->audio_tstamp; + snd_pcm_stream_unlock_irq(substream); + if (put_user(sstatus.state, &src->s.status.state) || + put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || + put_timespec(&sstatus.tstamp, &src->s.status.tstamp) || + put_user(sstatus.suspended_state, &src->s.status.suspended_state) || + put_timespec(&sstatus.audio_tstamp, &src->s.status.audio_tstamp) || + put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + put_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + + return 0; +} +#endif /* CONFIG_X86_X32 */ + +/* + */ +enum { + SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct snd_pcm_hw_params32), + SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct snd_pcm_hw_params32), + SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct snd_pcm_sw_params32), + SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct snd_pcm_status32), + SNDRV_PCM_IOCTL_STATUS_EXT32 = _IOWR('A', 0x24, struct snd_pcm_status32), + SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32), + SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct snd_pcm_channel_info32), + SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32), + SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32), + SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct snd_xferi32), + SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct snd_xferi32), + SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct snd_xfern32), + SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct snd_xfern32), + SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct snd_pcm_sync_ptr32), +#ifdef CONFIG_X86_X32 + SNDRV_PCM_IOCTL_CHANNEL_INFO_X32 = _IOR('A', 0x32, struct snd_pcm_channel_info), + SNDRV_PCM_IOCTL_STATUS_X32 = _IOR('A', 0x20, struct snd_pcm_status_x32), + SNDRV_PCM_IOCTL_STATUS_EXT_X32 = _IOWR('A', 0x24, struct snd_pcm_status_x32), + SNDRV_PCM_IOCTL_SYNC_PTR_X32 = _IOWR('A', 0x23, struct snd_pcm_sync_ptr_x32), +#endif /* CONFIG_X86_X32 */ +}; + +static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + void __user *argp = compat_ptr(arg); + + pcm_file = file->private_data; + if (! pcm_file) + return -ENOTTY; + substream = pcm_file->substream; + if (! substream) + return -ENOTTY; + + /* + * When PCM is used on 32bit mode, we need to disable + * mmap of PCM status/control records because of the size + * incompatibility. + */ + pcm_file->no_compat_mmap = 1; + + switch (cmd) { + case SNDRV_PCM_IOCTL_PVERSION: + case SNDRV_PCM_IOCTL_INFO: + case SNDRV_PCM_IOCTL_TSTAMP: + case SNDRV_PCM_IOCTL_TTSTAMP: + case SNDRV_PCM_IOCTL_USER_PVERSION: + case SNDRV_PCM_IOCTL_HWSYNC: + case SNDRV_PCM_IOCTL_PREPARE: + case SNDRV_PCM_IOCTL_RESET: + case SNDRV_PCM_IOCTL_START: + case SNDRV_PCM_IOCTL_DROP: + case SNDRV_PCM_IOCTL_DRAIN: + case SNDRV_PCM_IOCTL_PAUSE: + case SNDRV_PCM_IOCTL_HW_FREE: + case SNDRV_PCM_IOCTL_RESUME: + case SNDRV_PCM_IOCTL_XRUN: + case SNDRV_PCM_IOCTL_LINK: + case SNDRV_PCM_IOCTL_UNLINK: + return snd_pcm_common_ioctl(file, substream, cmd, argp); + case SNDRV_PCM_IOCTL_HW_REFINE32: + return snd_pcm_ioctl_hw_params_compat(substream, 1, argp); + case SNDRV_PCM_IOCTL_HW_PARAMS32: + return snd_pcm_ioctl_hw_params_compat(substream, 0, argp); + case SNDRV_PCM_IOCTL_SW_PARAMS32: + return snd_pcm_ioctl_sw_params_compat(substream, argp); + case SNDRV_PCM_IOCTL_STATUS32: + return snd_pcm_status_user_compat(substream, argp, false); + case SNDRV_PCM_IOCTL_STATUS_EXT32: + return snd_pcm_status_user_compat(substream, argp, true); + case SNDRV_PCM_IOCTL_SYNC_PTR32: + return snd_pcm_ioctl_sync_ptr_compat(substream, argp); + case SNDRV_PCM_IOCTL_CHANNEL_INFO32: + return snd_pcm_ioctl_channel_info_compat(substream, argp); + case SNDRV_PCM_IOCTL_WRITEI_FRAMES32: + return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp); + case SNDRV_PCM_IOCTL_READI_FRAMES32: + return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp); + case SNDRV_PCM_IOCTL_WRITEN_FRAMES32: + return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp); + case SNDRV_PCM_IOCTL_READN_FRAMES32: + return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp); + case SNDRV_PCM_IOCTL_DELAY32: + return snd_pcm_ioctl_delay_compat(substream, argp); + case SNDRV_PCM_IOCTL_REWIND32: + return snd_pcm_ioctl_rewind_compat(substream, argp); + case SNDRV_PCM_IOCTL_FORWARD32: + return snd_pcm_ioctl_forward_compat(substream, argp); +#ifdef CONFIG_X86_X32 + case SNDRV_PCM_IOCTL_STATUS_X32: + return snd_pcm_status_user_x32(substream, argp, false); + case SNDRV_PCM_IOCTL_STATUS_EXT_X32: + return snd_pcm_status_user_x32(substream, argp, true); + case SNDRV_PCM_IOCTL_SYNC_PTR_X32: + return snd_pcm_ioctl_sync_ptr_x32(substream, argp); + case SNDRV_PCM_IOCTL_CHANNEL_INFO_X32: + return snd_pcm_ioctl_channel_info_x32(substream, argp); +#endif /* CONFIG_X86_X32 */ + } + + return -ENOIOCTLCMD; +} diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c new file mode 100644 index 000000000..8eb58c709 --- /dev/null +++ b/sound/core/pcm_dmaengine.c @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2012, Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Based on: + * imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer + * mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc. + * ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek + * Copyright (C) 2006 Applied Data Systems + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +struct dmaengine_pcm_runtime_data { + struct dma_chan *dma_chan; + dma_cookie_t cookie; + + unsigned int pos; +}; + +static inline struct dmaengine_pcm_runtime_data *substream_to_prtd( + const struct snd_pcm_substream *substream) +{ + return substream->runtime->private_data; +} + +struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + return prtd->dma_chan; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan); + +/** + * snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config + * @substream: PCM substream + * @params: hw_params + * @slave_config: DMA slave config + * + * This function can be used to initialize a dma_slave_config from a substream + * and hw_params in a dmaengine based PCM driver implementation. + */ +int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream, + const struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config) +{ + enum dma_slave_buswidth buswidth; + int bits; + + bits = params_physical_width(params); + if (bits < 8 || bits > 64) + return -EINVAL; + else if (bits == 8) + buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + else if (bits == 16) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else if (bits == 24) + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + else if (bits <= 32) + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + slave_config->direction = DMA_MEM_TO_DEV; + slave_config->dst_addr_width = buswidth; + } else { + slave_config->direction = DMA_DEV_TO_MEM; + slave_config->src_addr_width = buswidth; + } + + slave_config->device_fc = false; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config); + +/** + * snd_dmaengine_pcm_set_config_from_dai_data() - Initializes a dma slave config + * using DAI DMA data. + * @substream: PCM substream + * @dma_data: DAI DMA data + * @slave_config: DMA slave configuration + * + * Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width and + * slave_id fields of the DMA slave config from the same fields of the DAI DMA + * data struct. The src and dst fields will be initialized depending on the + * direction of the substream. If the substream is a playback stream the dst + * fields will be initialized, if it is a capture stream the src fields will be + * initialized. The {dst,src}_addr_width field will only be initialized if the + * SND_DMAENGINE_PCM_DAI_FLAG_PACK flag is set or if the addr_width field of + * the DAI DMA data struct is not equal to DMA_SLAVE_BUSWIDTH_UNDEFINED. If + * both conditions are met the latter takes priority. + */ +void snd_dmaengine_pcm_set_config_from_dai_data( + const struct snd_pcm_substream *substream, + const struct snd_dmaengine_dai_dma_data *dma_data, + struct dma_slave_config *slave_config) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + slave_config->dst_addr = dma_data->addr; + slave_config->dst_maxburst = dma_data->maxburst; + if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK) + slave_config->dst_addr_width = + DMA_SLAVE_BUSWIDTH_UNDEFINED; + if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED) + slave_config->dst_addr_width = dma_data->addr_width; + } else { + slave_config->src_addr = dma_data->addr; + slave_config->src_maxburst = dma_data->maxburst; + if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK) + slave_config->src_addr_width = + DMA_SLAVE_BUSWIDTH_UNDEFINED; + if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED) + slave_config->src_addr_width = dma_data->addr_width; + } + + slave_config->slave_id = dma_data->slave_id; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data); + +static void dmaengine_pcm_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + prtd->pos += snd_pcm_lib_period_bytes(substream); + if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream)) + prtd->pos = 0; + + snd_pcm_period_elapsed(substream); +} + +static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + struct dma_chan *chan = prtd->dma_chan; + struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction direction; + unsigned long flags = DMA_CTRL_ACK; + + direction = snd_pcm_substream_to_dma_direction(substream); + + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + prtd->pos = 0; + desc = dmaengine_prep_dma_cyclic(chan, + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), direction, flags); + + if (!desc) + return -ENOMEM; + + desc->callback = dmaengine_pcm_dma_complete; + desc->callback_param = substream; + prtd->cookie = dmaengine_submit(desc); + + return 0; +} + +/** + * snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation + * @substream: PCM substream + * @cmd: Trigger command + * + * Returns 0 on success, a negative error code otherwise. + * + * This function can be used as the PCM trigger callback for dmaengine based PCM + * driver implementations. + */ +int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = dmaengine_pcm_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dmaengine_resume(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (runtime->info & SNDRV_PCM_INFO_PAUSE) + dmaengine_pause(prtd->dma_chan); + else + dmaengine_terminate_async(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_pause(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_STOP: + dmaengine_terminate_async(prtd->dma_chan); + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger); + +/** + * snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation + * @substream: PCM substream + * + * This function is deprecated and should not be used by new drivers, as its + * results may be unreliable. + */ +snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + return bytes_to_frames(substream->runtime, prtd->pos); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue); + +/** + * snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation + * @substream: PCM substream + * + * This function can be used as the PCM pointer callback for dmaengine based PCM + * driver implementations. + */ +snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + struct dma_tx_state state; + enum dma_status status; + unsigned int buf_size; + unsigned int pos = 0; + + status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state); + if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) { + buf_size = snd_pcm_lib_buffer_bytes(substream); + if (state.residue > 0 && state.residue <= buf_size) + pos = buf_size - state.residue; + } + + return bytes_to_frames(substream->runtime, pos); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer); + +/** + * snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM + * @filter_fn: Filter function used to request the DMA channel + * @filter_data: Data passed to the DMA filter function + * + * Returns NULL or the requested DMA channel. + * + * This function request a DMA channel for usage with dmaengine PCM. + */ +struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn, + void *filter_data) +{ + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_CYCLIC, mask); + + return dma_request_channel(mask, filter_fn, filter_data); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel); + +/** + * snd_dmaengine_pcm_open - Open a dmaengine based PCM substream + * @substream: PCM substream + * @chan: DMA channel to use for data transfers + * + * Returns 0 on success, a negative error code otherwise. + * + * The function should usually be called from the pcm open callback. Note that + * this function will use private_data field of the substream's runtime. So it + * is not available to your pcm driver implementation. + */ +int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream, + struct dma_chan *chan) +{ + struct dmaengine_pcm_runtime_data *prtd; + int ret; + + if (!chan) + return -ENXIO; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + return -ENOMEM; + + prtd->dma_chan = chan; + + substream->runtime->private_data = prtd; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open); + +/** + * snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel + * @substream: PCM substream + * @filter_fn: Filter function used to request the DMA channel + * @filter_data: Data passed to the DMA filter function + * + * Returns 0 on success, a negative error code otherwise. + * + * This function will request a DMA channel using the passed filter function and + * data. The function should usually be called from the pcm open callback. Note + * that this function will use private_data field of the substream's runtime. So + * it is not available to your pcm driver implementation. + */ +int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream, + dma_filter_fn filter_fn, void *filter_data) +{ + return snd_dmaengine_pcm_open(substream, + snd_dmaengine_pcm_request_channel(filter_fn, filter_data)); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan); + +/** + * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream + * @substream: PCM substream + */ +int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + dmaengine_synchronize(prtd->dma_chan); + kfree(prtd); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close); + +/** + * snd_dmaengine_pcm_release_chan_close - Close a dmaengine based PCM substream and release channel + * @substream: PCM substream + * + * Releases the DMA channel associated with the PCM substream. + */ +int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + dmaengine_synchronize(prtd->dma_chan); + dma_release_channel(prtd->dma_chan); + kfree(prtd); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan); + +MODULE_LICENSE("GPL"); diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c new file mode 100644 index 000000000..9881d0877 --- /dev/null +++ b/sound/core/pcm_drm_eld.c @@ -0,0 +1,99 @@ +/* + * PCM DRM helpers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include + +static const unsigned int eld_rates[] = { + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +static unsigned int sad_max_channels(const u8 *sad) +{ + return 1 + (sad[0] & 7); +} + +static int eld_limit_rates(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, rule->var); + const struct snd_interval *c; + unsigned int rate_mask = 7, i; + const u8 *sad, *eld = rule->private; + + sad = drm_eld_sad(eld); + if (sad) { + c = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + + for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) { + unsigned max_channels = sad_max_channels(sad); + + /* + * Exclude SADs which do not include the + * requested number of channels. + */ + if (c->min <= max_channels) + rate_mask |= sad[1]; + } + } + + return snd_interval_list(r, ARRAY_SIZE(eld_rates), eld_rates, + rate_mask); +} + +static int eld_limit_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *c = hw_param_interval(params, rule->var); + const struct snd_interval *r; + struct snd_interval t = { .min = 1, .max = 2, .integer = 1, }; + unsigned int i; + const u8 *sad, *eld = rule->private; + + sad = drm_eld_sad(eld); + if (sad) { + unsigned int rate_mask = 0; + + /* Convert the rate interval to a mask */ + r = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) + if (r->min <= eld_rates[i] && r->max >= eld_rates[i]) + rate_mask |= BIT(i); + + for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) + if (rate_mask & sad[1]) + t.max = max(t.max, sad_max_channels(sad)); + } + + return snd_interval_refine(c, &t); +} + +int snd_pcm_hw_constraint_eld(struct snd_pcm_runtime *runtime, void *eld) +{ + int ret; + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + eld_limit_rates, eld, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + eld_limit_channels, eld, + SNDRV_PCM_HW_PARAM_RATE, -1); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_pcm_hw_constraint_eld); diff --git a/sound/core/pcm_iec958.c b/sound/core/pcm_iec958.c new file mode 100644 index 000000000..5e6aed64f --- /dev/null +++ b/sound/core/pcm_iec958.c @@ -0,0 +1,126 @@ +/* + * PCM DRM helpers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include + +static int create_iec958_consumer(uint rate, uint sample_width, + u8 *cs, size_t len) +{ + unsigned int fs, ws; + + if (len < 4) + return -EINVAL; + + switch (rate) { + case 32000: + fs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + fs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + fs = IEC958_AES3_CON_FS_48000; + break; + case 88200: + fs = IEC958_AES3_CON_FS_88200; + break; + case 96000: + fs = IEC958_AES3_CON_FS_96000; + break; + case 176400: + fs = IEC958_AES3_CON_FS_176400; + break; + case 192000: + fs = IEC958_AES3_CON_FS_192000; + break; + default: + return -EINVAL; + } + + if (len > 4) { + switch (sample_width) { + case 16: + ws = IEC958_AES4_CON_WORDLEN_20_16; + break; + case 18: + ws = IEC958_AES4_CON_WORDLEN_22_18; + break; + case 20: + ws = IEC958_AES4_CON_WORDLEN_20_16 | + IEC958_AES4_CON_MAX_WORDLEN_24; + break; + case 24: + case 32: /* Assume 24-bit width for 32-bit samples. */ + ws = IEC958_AES4_CON_WORDLEN_24_20 | + IEC958_AES4_CON_MAX_WORDLEN_24; + break; + + default: + return -EINVAL; + } + } + + memset(cs, 0, len); + + cs[0] = IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS_NONE; + cs[1] = IEC958_AES1_CON_GENERAL; + cs[2] = IEC958_AES2_CON_SOURCE_UNSPEC | IEC958_AES2_CON_CHANNEL_UNSPEC; + cs[3] = IEC958_AES3_CON_CLOCK_1000PPM | fs; + + if (len > 4) + cs[4] = ws; + + return len; +} + +/** + * snd_pcm_create_iec958_consumer - create consumer format IEC958 channel status + * @runtime: pcm runtime structure with ->rate filled in + * @cs: channel status buffer, at least four bytes + * @len: length of channel status buffer + * + * Create the consumer format channel status data in @cs of maximum size + * @len corresponding to the parameters of the PCM runtime @runtime. + * + * Drivers may wish to tweak the contents of the buffer after creation. + * + * Returns: length of buffer, or negative error code if something failed. + */ +int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs, + size_t len) +{ + return create_iec958_consumer(runtime->rate, + snd_pcm_format_width(runtime->format), + cs, len); +} +EXPORT_SYMBOL(snd_pcm_create_iec958_consumer); + +/** + * snd_pcm_create_iec958_consumer_hw_params - create IEC958 channel status + * @hw_params: the hw_params instance for extracting rate and sample format + * @cs: channel status buffer, at least four bytes + * @len: length of channel status buffer + * + * Create the consumer format channel status data in @cs of maximum size + * @len corresponding to the parameters of the PCM runtime @runtime. + * + * Drivers may wish to tweak the contents of the buffer after creation. + * + * Returns: length of buffer, or negative error code if something failed. + */ +int snd_pcm_create_iec958_consumer_hw_params(struct snd_pcm_hw_params *params, + u8 *cs, size_t len) +{ + return create_iec958_consumer(params_rate(params), params_width(params), + cs, len); +} +EXPORT_SYMBOL(snd_pcm_create_iec958_consumer_hw_params); diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c new file mode 100644 index 000000000..c376471cf --- /dev/null +++ b/sound/core/pcm_lib.c @@ -0,0 +1,2479 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela + * Abramo Bagnara + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcm_local.h" + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG +#define CREATE_TRACE_POINTS +#include "pcm_trace.h" +#else +#define trace_hwptr(substream, pos, in_interrupt) +#define trace_xrun(substream) +#define trace_hw_ptr_error(substream, reason) +#define trace_applptr(substream, prev, curr) +#endif + +static int fill_silence_frames(struct snd_pcm_substream *substream, + snd_pcm_uframes_t off, snd_pcm_uframes_t frames); + +/* + * fill ring buffer with silence + * runtime->silence_start: starting pointer to silence area + * runtime->silence_filled: size filled with silence + * runtime->silence_threshold: threshold from application + * runtime->silence_size: maximal size from application + * + * when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately + */ +void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t frames, ofs, transfer; + int err; + + if (runtime->silence_size < runtime->boundary) { + snd_pcm_sframes_t noise_dist, n; + snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr); + if (runtime->silence_start != appl_ptr) { + n = appl_ptr - runtime->silence_start; + if (n < 0) + n += runtime->boundary; + if ((snd_pcm_uframes_t)n < runtime->silence_filled) + runtime->silence_filled -= n; + else + runtime->silence_filled = 0; + runtime->silence_start = appl_ptr; + } + if (runtime->silence_filled >= runtime->buffer_size) + return; + noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled; + if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold) + return; + frames = runtime->silence_threshold - noise_dist; + if (frames > runtime->silence_size) + frames = runtime->silence_size; + } else { + if (new_hw_ptr == ULONG_MAX) { /* initialization */ + snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime); + if (avail > runtime->buffer_size) + avail = runtime->buffer_size; + runtime->silence_filled = avail > 0 ? avail : 0; + runtime->silence_start = (runtime->status->hw_ptr + + runtime->silence_filled) % + runtime->boundary; + } else { + ofs = runtime->status->hw_ptr; + frames = new_hw_ptr - ofs; + if ((snd_pcm_sframes_t)frames < 0) + frames += runtime->boundary; + runtime->silence_filled -= frames; + if ((snd_pcm_sframes_t)runtime->silence_filled < 0) { + runtime->silence_filled = 0; + runtime->silence_start = new_hw_ptr; + } else { + runtime->silence_start = ofs; + } + } + frames = runtime->buffer_size - runtime->silence_filled; + } + if (snd_BUG_ON(frames > runtime->buffer_size)) + return; + if (frames == 0) + return; + ofs = runtime->silence_start % runtime->buffer_size; + while (frames > 0) { + transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames; + err = fill_silence_frames(substream, ofs, transfer); + snd_BUG_ON(err < 0); + runtime->silence_filled += transfer; + frames -= transfer; + ofs = 0; + } +} + +#ifdef CONFIG_SND_DEBUG +void snd_pcm_debug_name(struct snd_pcm_substream *substream, + char *name, size_t len) +{ + snprintf(name, len, "pcmC%dD%d%c:%d", + substream->pcm->card->number, + substream->pcm->device, + substream->stream ? 'c' : 'p', + substream->number); +} +EXPORT_SYMBOL(snd_pcm_debug_name); +#endif + +#define XRUN_DEBUG_BASIC (1<<0) +#define XRUN_DEBUG_STACK (1<<1) /* dump also stack */ +#define XRUN_DEBUG_JIFFIESCHECK (1<<2) /* do jiffies check */ + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + +#define xrun_debug(substream, mask) \ + ((substream)->pstr->xrun_debug & (mask)) +#else +#define xrun_debug(substream, mask) 0 +#endif + +#define dump_stack_on_xrun(substream) do { \ + if (xrun_debug(substream, XRUN_DEBUG_STACK)) \ + dump_stack(); \ + } while (0) + +/* call with stream lock held */ +void __snd_pcm_xrun(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + trace_xrun(substream); + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) + snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { + char name[16]; + snd_pcm_debug_name(substream, name, sizeof(name)); + pcm_warn(substream->pcm, "XRUN: %s\n", name); + dump_stack_on_xrun(substream); + } +} + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG +#define hw_ptr_error(substream, in_interrupt, reason, fmt, args...) \ + do { \ + trace_hw_ptr_error(substream, reason); \ + if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { \ + pr_err_ratelimited("ALSA: PCM: [%c] " reason ": " fmt, \ + (in_interrupt) ? 'Q' : 'P', ##args); \ + dump_stack_on_xrun(substream); \ + } \ + } while (0) + +#else /* ! CONFIG_SND_PCM_XRUN_DEBUG */ + +#define hw_ptr_error(substream, fmt, args...) do { } while (0) + +#endif + +int snd_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime) +{ + snd_pcm_uframes_t avail; + + avail = snd_pcm_avail(substream); + if (avail > runtime->avail_max) + runtime->avail_max = avail; + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) { + if (avail >= runtime->buffer_size) { + snd_pcm_drain_done(substream); + return -EPIPE; + } + } else { + if (avail >= runtime->stop_threshold) { + __snd_pcm_xrun(substream); + return -EPIPE; + } + } + if (runtime->twake) { + if (avail >= runtime->twake) + wake_up(&runtime->tsleep); + } else if (avail >= runtime->control->avail_min) + wake_up(&runtime->sleep); + return 0; +} + +static void update_audio_tstamp(struct snd_pcm_substream *substream, + struct timespec *curr_tstamp, + struct timespec *audio_tstamp) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + u64 audio_frames, audio_nsecs; + struct timespec driver_tstamp; + + if (runtime->tstamp_mode != SNDRV_PCM_TSTAMP_ENABLE) + return; + + if (!(substream->ops->get_time_info) || + (runtime->audio_tstamp_report.actual_type == + SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) { + + /* + * provide audio timestamp derived from pointer position + * add delay only if requested + */ + + audio_frames = runtime->hw_ptr_wrap + runtime->status->hw_ptr; + + if (runtime->audio_tstamp_config.report_delay) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_frames -= runtime->delay; + else + audio_frames += runtime->delay; + } + audio_nsecs = div_u64(audio_frames * 1000000000LL, + runtime->rate); + *audio_tstamp = ns_to_timespec(audio_nsecs); + } + if (!timespec_equal(&runtime->status->audio_tstamp, audio_tstamp)) { + runtime->status->audio_tstamp = *audio_tstamp; + runtime->status->tstamp = *curr_tstamp; + } + + /* + * re-take a driver timestamp to let apps detect if the reference tstamp + * read by low-level hardware was provided with a delay + */ + snd_pcm_gettime(substream->runtime, (struct timespec *)&driver_tstamp); + runtime->driver_tstamp = driver_tstamp; +} + +static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, + unsigned int in_interrupt) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t pos; + snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base; + snd_pcm_sframes_t hdelta, delta; + unsigned long jdelta; + unsigned long curr_jiffies; + struct timespec curr_tstamp; + struct timespec audio_tstamp; + int crossed_boundary = 0; + + old_hw_ptr = runtime->status->hw_ptr; + + /* + * group pointer, time and jiffies reads to allow for more + * accurate correlations/corrections. + * The values are stored at the end of this routine after + * corrections for hw_ptr position + */ + pos = substream->ops->pointer(substream); + curr_jiffies = jiffies; + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { + if ((substream->ops->get_time_info) && + (runtime->audio_tstamp_config.type_requested != SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) { + substream->ops->get_time_info(substream, &curr_tstamp, + &audio_tstamp, + &runtime->audio_tstamp_config, + &runtime->audio_tstamp_report); + + /* re-test in case tstamp type is not supported in hardware and was demoted to DEFAULT */ + if (runtime->audio_tstamp_report.actual_type == SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT) + snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp); + } else + snd_pcm_gettime(runtime, (struct timespec *)&curr_tstamp); + } + + if (pos == SNDRV_PCM_POS_XRUN) { + __snd_pcm_xrun(substream); + return -EPIPE; + } + if (pos >= runtime->buffer_size) { + if (printk_ratelimit()) { + char name[16]; + snd_pcm_debug_name(substream, name, sizeof(name)); + pcm_err(substream->pcm, + "invalid position: %s, pos = %ld, buffer size = %ld, period size = %ld\n", + name, pos, runtime->buffer_size, + runtime->period_size); + } + pos = 0; + } + pos -= pos % runtime->min_align; + trace_hwptr(substream, pos, in_interrupt); + hw_base = runtime->hw_ptr_base; + new_hw_ptr = hw_base + pos; + if (in_interrupt) { + /* we know that one period was processed */ + /* delta = "expected next hw_ptr" for in_interrupt != 0 */ + delta = runtime->hw_ptr_interrupt + runtime->period_size; + if (delta > new_hw_ptr) { + /* check for double acknowledged interrupts */ + hdelta = curr_jiffies - runtime->hw_ptr_jiffies; + if (hdelta > runtime->hw_ptr_buffer_jiffies/2 + 1) { + hw_base += runtime->buffer_size; + if (hw_base >= runtime->boundary) { + hw_base = 0; + crossed_boundary++; + } + new_hw_ptr = hw_base + pos; + goto __delta; + } + } + } + /* new_hw_ptr might be lower than old_hw_ptr in case when */ + /* pointer crosses the end of the ring buffer */ + if (new_hw_ptr < old_hw_ptr) { + hw_base += runtime->buffer_size; + if (hw_base >= runtime->boundary) { + hw_base = 0; + crossed_boundary++; + } + new_hw_ptr = hw_base + pos; + } + __delta: + delta = new_hw_ptr - old_hw_ptr; + if (delta < 0) + delta += runtime->boundary; + + if (runtime->no_period_wakeup) { + snd_pcm_sframes_t xrun_threshold; + /* + * Without regular period interrupts, we have to check + * the elapsed time to detect xruns. + */ + jdelta = curr_jiffies - runtime->hw_ptr_jiffies; + if (jdelta < runtime->hw_ptr_buffer_jiffies / 2) + goto no_delta_check; + hdelta = jdelta - delta * HZ / runtime->rate; + xrun_threshold = runtime->hw_ptr_buffer_jiffies / 2 + 1; + while (hdelta > xrun_threshold) { + delta += runtime->buffer_size; + hw_base += runtime->buffer_size; + if (hw_base >= runtime->boundary) { + hw_base = 0; + crossed_boundary++; + } + new_hw_ptr = hw_base + pos; + hdelta -= runtime->hw_ptr_buffer_jiffies; + } + goto no_delta_check; + } + + /* something must be really wrong */ + if (delta >= runtime->buffer_size + runtime->period_size) { + hw_ptr_error(substream, in_interrupt, "Unexpected hw_ptr", + "(stream=%i, pos=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n", + substream->stream, (long)pos, + (long)new_hw_ptr, (long)old_hw_ptr); + return 0; + } + + /* Do jiffies check only in xrun_debug mode */ + if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK)) + goto no_jiffies_check; + + /* Skip the jiffies check for hardwares with BATCH flag. + * Such hardware usually just increases the position at each IRQ, + * thus it can't give any strange position. + */ + if (runtime->hw.info & SNDRV_PCM_INFO_BATCH) + goto no_jiffies_check; + hdelta = delta; + if (hdelta < runtime->delay) + goto no_jiffies_check; + hdelta -= runtime->delay; + jdelta = curr_jiffies - runtime->hw_ptr_jiffies; + if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) { + delta = jdelta / + (((runtime->period_size * HZ) / runtime->rate) + + HZ/100); + /* move new_hw_ptr according jiffies not pos variable */ + new_hw_ptr = old_hw_ptr; + hw_base = delta; + /* use loop to avoid checks for delta overflows */ + /* the delta value is small or zero in most cases */ + while (delta > 0) { + new_hw_ptr += runtime->period_size; + if (new_hw_ptr >= runtime->boundary) { + new_hw_ptr -= runtime->boundary; + crossed_boundary--; + } + delta--; + } + /* align hw_base to buffer_size */ + hw_ptr_error(substream, in_interrupt, "hw_ptr skipping", + "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n", + (long)pos, (long)hdelta, + (long)runtime->period_size, jdelta, + ((hdelta * HZ) / runtime->rate), hw_base, + (unsigned long)old_hw_ptr, + (unsigned long)new_hw_ptr); + /* reset values to proper state */ + delta = 0; + hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size); + } + no_jiffies_check: + if (delta > runtime->period_size + runtime->period_size / 2) { + hw_ptr_error(substream, in_interrupt, + "Lost interrupts?", + "(stream=%i, delta=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n", + substream->stream, (long)delta, + (long)new_hw_ptr, + (long)old_hw_ptr); + } + + no_delta_check: + if (runtime->status->hw_ptr == new_hw_ptr) { + runtime->hw_ptr_jiffies = curr_jiffies; + update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp); + return 0; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, new_hw_ptr); + + if (in_interrupt) { + delta = new_hw_ptr - runtime->hw_ptr_interrupt; + if (delta < 0) + delta += runtime->boundary; + delta -= (snd_pcm_uframes_t)delta % runtime->period_size; + runtime->hw_ptr_interrupt += delta; + if (runtime->hw_ptr_interrupt >= runtime->boundary) + runtime->hw_ptr_interrupt -= runtime->boundary; + } + runtime->hw_ptr_base = hw_base; + runtime->status->hw_ptr = new_hw_ptr; + runtime->hw_ptr_jiffies = curr_jiffies; + if (crossed_boundary) { + snd_BUG_ON(crossed_boundary != 1); + runtime->hw_ptr_wrap += runtime->boundary; + } + + update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp); + + return snd_pcm_update_state(substream, runtime); +} + +/* CAUTION: call it with irq disabled */ +int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) +{ + return snd_pcm_update_hw_ptr0(substream, 0); +} + +/** + * snd_pcm_set_ops - set the PCM operators + * @pcm: the pcm instance + * @direction: stream direction, SNDRV_PCM_STREAM_XXX + * @ops: the operator table + * + * Sets the given PCM operators to the pcm instance. + */ +void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, + const struct snd_pcm_ops *ops) +{ + struct snd_pcm_str *stream = &pcm->streams[direction]; + struct snd_pcm_substream *substream; + + for (substream = stream->substream; substream != NULL; substream = substream->next) + substream->ops = ops; +} +EXPORT_SYMBOL(snd_pcm_set_ops); + +/** + * snd_pcm_sync - set the PCM sync id + * @substream: the pcm substream + * + * Sets the PCM sync identifier for the card. + */ +void snd_pcm_set_sync(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->sync.id32[0] = substream->pcm->card->number; + runtime->sync.id32[1] = -1; + runtime->sync.id32[2] = -1; + runtime->sync.id32[3] = -1; +} +EXPORT_SYMBOL(snd_pcm_set_sync); + +/* + * Standard ioctl routine + */ + +static inline unsigned int div32(unsigned int a, unsigned int b, + unsigned int *r) +{ + if (b == 0) { + *r = 0; + return UINT_MAX; + } + *r = a % b; + return a / b; +} + +static inline unsigned int div_down(unsigned int a, unsigned int b) +{ + if (b == 0) + return UINT_MAX; + return a / b; +} + +static inline unsigned int div_up(unsigned int a, unsigned int b) +{ + unsigned int r; + unsigned int q; + if (b == 0) + return UINT_MAX; + q = div32(a, b, &r); + if (r) + ++q; + return q; +} + +static inline unsigned int mul(unsigned int a, unsigned int b) +{ + if (a == 0) + return 0; + if (div_down(UINT_MAX, a) < b) + return UINT_MAX; + return a * b; +} + +static inline unsigned int muldiv32(unsigned int a, unsigned int b, + unsigned int c, unsigned int *r) +{ + u_int64_t n = (u_int64_t) a * b; + if (c == 0) { + *r = 0; + return UINT_MAX; + } + n = div_u64_rem(n, c, r); + if (n >= UINT_MAX) { + *r = 0; + return UINT_MAX; + } + return n; +} + +/** + * snd_interval_refine - refine the interval value of configurator + * @i: the interval value to refine + * @v: the interval value to refer to + * + * Refines the interval value with the reference value. + * The interval is changed to the range satisfying both intervals. + * The interval status (min, max, integer, etc.) are evaluated. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_refine(struct snd_interval *i, const struct snd_interval *v) +{ + int changed = 0; + if (snd_BUG_ON(snd_interval_empty(i))) + return -EINVAL; + if (i->min < v->min) { + i->min = v->min; + i->openmin = v->openmin; + changed = 1; + } else if (i->min == v->min && !i->openmin && v->openmin) { + i->openmin = 1; + changed = 1; + } + if (i->max > v->max) { + i->max = v->max; + i->openmax = v->openmax; + changed = 1; + } else if (i->max == v->max && !i->openmax && v->openmax) { + i->openmax = 1; + changed = 1; + } + if (!i->integer && v->integer) { + i->integer = 1; + changed = 1; + } + if (i->integer) { + if (i->openmin) { + i->min++; + i->openmin = 0; + } + if (i->openmax) { + i->max--; + i->openmax = 0; + } + } else if (!i->openmin && !i->openmax && i->min == i->max) + i->integer = 1; + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} +EXPORT_SYMBOL(snd_interval_refine); + +static int snd_interval_refine_first(struct snd_interval *i) +{ + const unsigned int last_max = i->max; + + if (snd_BUG_ON(snd_interval_empty(i))) + return -EINVAL; + if (snd_interval_single(i)) + return 0; + i->max = i->min; + if (i->openmin) + i->max++; + /* only exclude max value if also excluded before refine */ + i->openmax = (i->openmax && i->max >= last_max); + return 1; +} + +static int snd_interval_refine_last(struct snd_interval *i) +{ + const unsigned int last_min = i->min; + + if (snd_BUG_ON(snd_interval_empty(i))) + return -EINVAL; + if (snd_interval_single(i)) + return 0; + i->min = i->max; + if (i->openmax) + i->min--; + /* only exclude min value if also excluded before refine */ + i->openmin = (i->openmin && i->min <= last_min); + return 1; +} + +void snd_interval_mul(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *c) +{ + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = mul(a->min, b->min); + c->openmin = (a->openmin || b->openmin); + c->max = mul(a->max, b->max); + c->openmax = (a->openmax || b->openmax); + c->integer = (a->integer && b->integer); +} + +/** + * snd_interval_div - refine the interval value with division + * @a: dividend + * @b: divisor + * @c: quotient + * + * c = a / b + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_div(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = div32(a->min, b->max, &r); + c->openmin = (r || a->openmin || b->openmax); + if (b->min > 0) { + c->max = div32(a->max, b->min, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmin); + } else { + c->max = UINT_MAX; + c->openmax = 0; + } + c->integer = 0; +} + +/** + * snd_interval_muldivk - refine the interval value + * @a: dividend 1 + * @b: dividend 2 + * @k: divisor (as integer) + * @c: result + * + * c = a * b / k + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_muldivk(const struct snd_interval *a, const struct snd_interval *b, + unsigned int k, struct snd_interval *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = muldiv32(a->min, b->min, k, &r); + c->openmin = (r || a->openmin || b->openmin); + c->max = muldiv32(a->max, b->max, k, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmax); + c->integer = 0; +} + +/** + * snd_interval_mulkdiv - refine the interval value + * @a: dividend 1 + * @k: dividend 2 (as integer) + * @b: divisor + * @c: result + * + * c = a * k / b + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k, + const struct snd_interval *b, struct snd_interval *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = muldiv32(a->min, k, b->max, &r); + c->openmin = (r || a->openmin || b->openmax); + if (b->min > 0) { + c->max = muldiv32(a->max, k, b->min, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmin); + } else { + c->max = UINT_MAX; + c->openmax = 0; + } + c->integer = 0; +} + +/* ---- */ + + +/** + * snd_interval_ratnum - refine the interval value + * @i: interval to refine + * @rats_count: number of ratnum_t + * @rats: ratnum_t array + * @nump: pointer to store the resultant numerator + * @denp: pointer to store the resultant denominator + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_ratnum(struct snd_interval *i, + unsigned int rats_count, const struct snd_ratnum *rats, + unsigned int *nump, unsigned int *denp) +{ + unsigned int best_num, best_den; + int best_diff; + unsigned int k; + struct snd_interval t; + int err; + unsigned int result_num, result_den; + int result_diff; + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num = rats[k].num; + unsigned int den; + unsigned int q = i->min; + int diff; + if (q == 0) + q = 1; + den = div_up(num, q); + if (den < rats[k].den_min) + continue; + if (den > rats[k].den_max) + den = rats[k].den_max; + else { + unsigned int r; + r = (den - rats[k].den_min) % rats[k].den_step; + if (r != 0) + den -= r; + } + diff = num - q * den; + if (diff < 0) + diff = -diff; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.min = div_down(best_num, best_den); + t.openmin = !!(best_num % best_den); + + result_num = best_num; + result_diff = best_diff; + result_den = best_den; + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num = rats[k].num; + unsigned int den; + unsigned int q = i->max; + int diff; + if (q == 0) { + i->empty = 1; + return -EINVAL; + } + den = div_down(num, q); + if (den > rats[k].den_max) + continue; + if (den < rats[k].den_min) + den = rats[k].den_min; + else { + unsigned int r; + r = (den - rats[k].den_min) % rats[k].den_step; + if (r != 0) + den += rats[k].den_step - r; + } + diff = q * den - num; + if (diff < 0) + diff = -diff; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.max = div_up(best_num, best_den); + t.openmax = !!(best_num % best_den); + t.integer = 0; + err = snd_interval_refine(i, &t); + if (err < 0) + return err; + + if (snd_interval_single(i)) { + if (best_diff * result_den < result_diff * best_den) { + result_num = best_num; + result_den = best_den; + } + if (nump) + *nump = result_num; + if (denp) + *denp = result_den; + } + return err; +} +EXPORT_SYMBOL(snd_interval_ratnum); + +/** + * snd_interval_ratden - refine the interval value + * @i: interval to refine + * @rats_count: number of struct ratden + * @rats: struct ratden array + * @nump: pointer to store the resultant numerator + * @denp: pointer to store the resultant denominator + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +static int snd_interval_ratden(struct snd_interval *i, + unsigned int rats_count, + const struct snd_ratden *rats, + unsigned int *nump, unsigned int *denp) +{ + unsigned int best_num, best_diff, best_den; + unsigned int k; + struct snd_interval t; + int err; + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num; + unsigned int den = rats[k].den; + unsigned int q = i->min; + int diff; + num = mul(q, den); + if (num > rats[k].num_max) + continue; + if (num < rats[k].num_min) + num = rats[k].num_max; + else { + unsigned int r; + r = (num - rats[k].num_min) % rats[k].num_step; + if (r != 0) + num += rats[k].num_step - r; + } + diff = num - q * den; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.min = div_down(best_num, best_den); + t.openmin = !!(best_num % best_den); + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num; + unsigned int den = rats[k].den; + unsigned int q = i->max; + int diff; + num = mul(q, den); + if (num < rats[k].num_min) + continue; + if (num > rats[k].num_max) + num = rats[k].num_max; + else { + unsigned int r; + r = (num - rats[k].num_min) % rats[k].num_step; + if (r != 0) + num -= r; + } + diff = q * den - num; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.max = div_up(best_num, best_den); + t.openmax = !!(best_num % best_den); + t.integer = 0; + err = snd_interval_refine(i, &t); + if (err < 0) + return err; + + if (snd_interval_single(i)) { + if (nump) + *nump = best_num; + if (denp) + *denp = best_den; + } + return err; +} + +/** + * snd_interval_list - refine the interval value from the list + * @i: the interval value to refine + * @count: the number of elements in the list + * @list: the value list + * @mask: the bit-mask to evaluate + * + * Refines the interval value from the list. + * When mask is non-zero, only the elements corresponding to bit 1 are + * evaluated. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_list(struct snd_interval *i, unsigned int count, + const unsigned int *list, unsigned int mask) +{ + unsigned int k; + struct snd_interval list_range; + + if (!count) { + i->empty = 1; + return -EINVAL; + } + snd_interval_any(&list_range); + list_range.min = UINT_MAX; + list_range.max = 0; + for (k = 0; k < count; k++) { + if (mask && !(mask & (1 << k))) + continue; + if (!snd_interval_test(i, list[k])) + continue; + list_range.min = min(list_range.min, list[k]); + list_range.max = max(list_range.max, list[k]); + } + return snd_interval_refine(i, &list_range); +} +EXPORT_SYMBOL(snd_interval_list); + +/** + * snd_interval_ranges - refine the interval value from the list of ranges + * @i: the interval value to refine + * @count: the number of elements in the list of ranges + * @ranges: the ranges list + * @mask: the bit-mask to evaluate + * + * Refines the interval value from the list of ranges. + * When mask is non-zero, only the elements corresponding to bit 1 are + * evaluated. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_ranges(struct snd_interval *i, unsigned int count, + const struct snd_interval *ranges, unsigned int mask) +{ + unsigned int k; + struct snd_interval range_union; + struct snd_interval range; + + if (!count) { + snd_interval_none(i); + return -EINVAL; + } + snd_interval_any(&range_union); + range_union.min = UINT_MAX; + range_union.max = 0; + for (k = 0; k < count; k++) { + if (mask && !(mask & (1 << k))) + continue; + snd_interval_copy(&range, &ranges[k]); + if (snd_interval_refine(&range, i) < 0) + continue; + if (snd_interval_empty(&range)) + continue; + + if (range.min < range_union.min) { + range_union.min = range.min; + range_union.openmin = 1; + } + if (range.min == range_union.min && !range.openmin) + range_union.openmin = 0; + if (range.max > range_union.max) { + range_union.max = range.max; + range_union.openmax = 1; + } + if (range.max == range_union.max && !range.openmax) + range_union.openmax = 0; + } + return snd_interval_refine(i, &range_union); +} +EXPORT_SYMBOL(snd_interval_ranges); + +static int snd_interval_step(struct snd_interval *i, unsigned int step) +{ + unsigned int n; + int changed = 0; + n = i->min % step; + if (n != 0 || i->openmin) { + i->min += step - n; + i->openmin = 0; + changed = 1; + } + n = i->max % step; + if (n != 0 || i->openmax) { + i->max -= n; + i->openmax = 0; + changed = 1; + } + if (snd_interval_checkempty(i)) { + i->empty = 1; + return -EINVAL; + } + return changed; +} + +/* Info constraints helpers */ + +/** + * snd_pcm_hw_rule_add - add the hw-constraint rule + * @runtime: the pcm runtime instance + * @cond: condition bits + * @var: the variable to evaluate + * @func: the evaluation function + * @private: the private data pointer passed to function + * @dep: the dependent variables + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond, + int var, + snd_pcm_hw_rule_func_t func, void *private, + int dep, ...) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_pcm_hw_rule *c; + unsigned int k; + va_list args; + va_start(args, dep); + if (constrs->rules_num >= constrs->rules_all) { + struct snd_pcm_hw_rule *new; + unsigned int new_rules = constrs->rules_all + 16; + new = krealloc(constrs->rules, new_rules * sizeof(*c), + GFP_KERNEL); + if (!new) { + va_end(args); + return -ENOMEM; + } + constrs->rules = new; + constrs->rules_all = new_rules; + } + c = &constrs->rules[constrs->rules_num]; + c->cond = cond; + c->func = func; + c->var = var; + c->private = private; + k = 0; + while (1) { + if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps))) { + va_end(args); + return -EINVAL; + } + c->deps[k++] = dep; + if (dep < 0) + break; + dep = va_arg(args, int); + } + constrs->rules_num++; + va_end(args); + return 0; +} +EXPORT_SYMBOL(snd_pcm_hw_rule_add); + +/** + * snd_pcm_hw_constraint_mask - apply the given bitmap mask constraint + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the mask + * @mask: the bitmap mask + * + * Apply the constraint of the given bitmap mask to a 32-bit mask parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var, + u_int32_t mask) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_mask *maskp = constrs_mask(constrs, var); + *maskp->bits &= mask; + memset(maskp->bits + 1, 0, (SNDRV_MASK_MAX-32) / 8); /* clear rest */ + if (*maskp->bits == 0) + return -EINVAL; + return 0; +} + +/** + * snd_pcm_hw_constraint_mask64 - apply the given bitmap mask constraint + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the mask + * @mask: the 64bit bitmap mask + * + * Apply the constraint of the given bitmap mask to a 64-bit mask parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_mask64(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var, + u_int64_t mask) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_mask *maskp = constrs_mask(constrs, var); + maskp->bits[0] &= (u_int32_t)mask; + maskp->bits[1] &= (u_int32_t)(mask >> 32); + memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */ + if (! maskp->bits[0] && ! maskp->bits[1]) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_mask64); + +/** + * snd_pcm_hw_constraint_integer - apply an integer constraint to an interval + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the integer constraint + * + * Apply the constraint of integer to an interval parameter. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_pcm_hw_constraint_integer(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + return snd_interval_setinteger(constrs_interval(constrs, var)); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_integer); + +/** + * snd_pcm_hw_constraint_minmax - apply a min/max range constraint to an interval + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the range + * @min: the minimal value + * @max: the maximal value + * + * Apply the min/max range constraint to an interval parameter. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_pcm_hw_constraint_minmax(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var, + unsigned int min, unsigned int max) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_interval t; + t.min = min; + t.max = max; + t.openmin = t.openmax = 0; + t.integer = 0; + return snd_interval_refine(constrs_interval(constrs, var), &t); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_minmax); + +static int snd_pcm_hw_rule_list(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_hw_constraint_list *list = rule->private; + return snd_interval_list(hw_param_interval(params, rule->var), list->count, list->list, list->mask); +} + + +/** + * snd_pcm_hw_constraint_list - apply a list of constraints to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the list constraint + * @l: list + * + * Apply the list of constraints to an interval parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_list(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_list *l) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_list, (void *)l, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_list); + +static int snd_pcm_hw_rule_ranges(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_hw_constraint_ranges *r = rule->private; + return snd_interval_ranges(hw_param_interval(params, rule->var), + r->count, r->ranges, r->mask); +} + + +/** + * snd_pcm_hw_constraint_ranges - apply list of range constraints to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the list of range constraints + * @r: ranges + * + * Apply the list of range constraints to an interval parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_ranges(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_ranges *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ranges, (void *)r, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_ranges); + +static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_pcm_hw_constraint_ratnums *r = rule->private; + unsigned int num = 0, den = 0; + int err; + err = snd_interval_ratnum(hw_param_interval(params, rule->var), + r->nrats, r->rats, &num, &den); + if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + return err; +} + +/** + * snd_pcm_hw_constraint_ratnums - apply ratnums constraint to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the ratnums constraint + * @r: struct snd_ratnums constriants + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_ratnums *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ratnums, (void *)r, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums); + +static int snd_pcm_hw_rule_ratdens(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_pcm_hw_constraint_ratdens *r = rule->private; + unsigned int num = 0, den = 0; + int err = snd_interval_ratden(hw_param_interval(params, rule->var), + r->nrats, r->rats, &num, &den); + if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + return err; +} + +/** + * snd_pcm_hw_constraint_ratdens - apply ratdens constraint to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the ratdens constraint + * @r: struct snd_ratdens constriants + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_ratdens(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_ratdens *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ratdens, (void *)r, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_ratdens); + +static int snd_pcm_hw_rule_msbits(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int l = (unsigned long) rule->private; + int width = l & 0xffff; + unsigned int msbits = l >> 16; + const struct snd_interval *i = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + + if (!snd_interval_single(i)) + return 0; + + if ((snd_interval_value(i) == width) || + (width == 0 && snd_interval_value(i) > msbits)) + params->msbits = min_not_zero(params->msbits, msbits); + + return 0; +} + +/** + * snd_pcm_hw_constraint_msbits - add a hw constraint msbits rule + * @runtime: PCM runtime instance + * @cond: condition bits + * @width: sample bits width + * @msbits: msbits width + * + * This constraint will set the number of most significant bits (msbits) if a + * sample format with the specified width has been select. If width is set to 0 + * the msbits will be set for any sample format with a width larger than the + * specified msbits. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_msbits(struct snd_pcm_runtime *runtime, + unsigned int cond, + unsigned int width, + unsigned int msbits) +{ + unsigned long l = (msbits << 16) | width; + return snd_pcm_hw_rule_add(runtime, cond, -1, + snd_pcm_hw_rule_msbits, + (void*) l, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_msbits); + +static int snd_pcm_hw_rule_step(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned long step = (unsigned long) rule->private; + return snd_interval_step(hw_param_interval(params, rule->var), step); +} + +/** + * snd_pcm_hw_constraint_step - add a hw constraint step rule + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the step constraint + * @step: step size + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_step(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + unsigned long step) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_step, (void *) step, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_step); + +static int snd_pcm_hw_rule_pow2(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + static unsigned int pow2_sizes[] = { + 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, + 1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15, + 1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23, + 1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30 + }; + return snd_interval_list(hw_param_interval(params, rule->var), + ARRAY_SIZE(pow2_sizes), pow2_sizes, 0); +} + +/** + * snd_pcm_hw_constraint_pow2 - add a hw constraint power-of-2 rule + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the power-of-2 constraint + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_pow2(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_pow2, NULL, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2); + +static int snd_pcm_hw_rule_noresample_func(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int base_rate = (unsigned int)(uintptr_t)rule->private; + struct snd_interval *rate; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + return snd_interval_list(rate, 1, &base_rate, 0); +} + +/** + * snd_pcm_hw_rule_noresample - add a rule to allow disabling hw resampling + * @runtime: PCM runtime instance + * @base_rate: the rate at which the hardware does not resample + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_rule_noresample(struct snd_pcm_runtime *runtime, + unsigned int base_rate) +{ + return snd_pcm_hw_rule_add(runtime, SNDRV_PCM_HW_PARAMS_NORESAMPLE, + SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_noresample_func, + (void *)(uintptr_t)base_rate, + SNDRV_PCM_HW_PARAM_RATE, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_rule_noresample); + +static void _snd_pcm_hw_param_any(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + if (hw_is_mask(var)) { + snd_mask_any(hw_param_mask(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + return; + } + if (hw_is_interval(var)) { + snd_interval_any(hw_param_interval(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + return; + } + snd_BUG(); +} + +void _snd_pcm_hw_params_any(struct snd_pcm_hw_params *params) +{ + unsigned int k; + memset(params, 0, sizeof(*params)); + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) + _snd_pcm_hw_param_any(params, k); + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) + _snd_pcm_hw_param_any(params, k); + params->info = ~0U; +} +EXPORT_SYMBOL(_snd_pcm_hw_params_any); + +/** + * snd_pcm_hw_param_value - return @params field @var value + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or %NULL + * + * Return: The value for field @var if it's fixed in configuration space + * defined by @params. -%EINVAL otherwise. + */ +int snd_pcm_hw_param_value(const struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + const struct snd_mask *mask = hw_param_mask_c(params, var); + if (!snd_mask_single(mask)) + return -EINVAL; + if (dir) + *dir = 0; + return snd_mask_value(mask); + } + if (hw_is_interval(var)) { + const struct snd_interval *i = hw_param_interval_c(params, var); + if (!snd_interval_single(i)) + return -EINVAL; + if (dir) + *dir = i->openmin; + return snd_interval_value(i); + } + return -EINVAL; +} +EXPORT_SYMBOL(snd_pcm_hw_param_value); + +void _snd_pcm_hw_param_setempty(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + if (hw_is_mask(var)) { + snd_mask_none(hw_param_mask(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } else if (hw_is_interval(var)) { + snd_interval_none(hw_param_interval(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } else { + snd_BUG(); + } +} +EXPORT_SYMBOL(_snd_pcm_hw_param_setempty); + +static int _snd_pcm_hw_param_first(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + int changed; + if (hw_is_mask(var)) + changed = snd_mask_refine_first(hw_param_mask(params, var)); + else if (hw_is_interval(var)) + changed = snd_interval_refine_first(hw_param_interval(params, var)); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + + +/** + * snd_pcm_hw_param_first - refine config space and return minimum value + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or %NULL + * + * Inside configuration space defined by @params remove from @var all + * values > minimum. Reduce configuration space accordingly. + * + * Return: The minimum, or a negative error code on failure. + */ +int snd_pcm_hw_param_first(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + int changed = _snd_pcm_hw_param_first(params, var); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value(params, var, dir); +} +EXPORT_SYMBOL(snd_pcm_hw_param_first); + +static int _snd_pcm_hw_param_last(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + int changed; + if (hw_is_mask(var)) + changed = snd_mask_refine_last(hw_param_mask(params, var)); + else if (hw_is_interval(var)) + changed = snd_interval_refine_last(hw_param_interval(params, var)); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + + +/** + * snd_pcm_hw_param_last - refine config space and return maximum value + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or %NULL + * + * Inside configuration space defined by @params remove from @var all + * values < maximum. Reduce configuration space accordingly. + * + * Return: The maximum, or a negative error code on failure. + */ +int snd_pcm_hw_param_last(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + int changed = _snd_pcm_hw_param_last(params, var); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value(params, var, dir); +} +EXPORT_SYMBOL(snd_pcm_hw_param_last); + +static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + snd_pcm_stream_lock_irqsave(substream, flags); + if (snd_pcm_running(substream) && + snd_pcm_update_hw_ptr(substream) >= 0) + runtime->status->hw_ptr %= runtime->buffer_size; + else { + runtime->status->hw_ptr = 0; + runtime->hw_ptr_wrap = 0; + } + snd_pcm_stream_unlock_irqrestore(substream, flags); + return 0; +} + +static int snd_pcm_lib_ioctl_channel_info(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_channel_info *info = arg; + struct snd_pcm_runtime *runtime = substream->runtime; + int width; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { + info->offset = -1; + return 0; + } + width = snd_pcm_format_physical_width(runtime->format); + if (width < 0) + return width; + info->offset = 0; + switch (runtime->access) { + case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED: + case SNDRV_PCM_ACCESS_RW_INTERLEAVED: + info->first = info->channel * width; + info->step = runtime->channels * width; + break; + case SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED: + case SNDRV_PCM_ACCESS_RW_NONINTERLEAVED: + { + size_t size = runtime->dma_bytes / runtime->channels; + info->first = info->channel * size * 8; + info->step = width; + break; + } + default: + snd_BUG(); + break; + } + return 0; +} + +static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_hw_params *params = arg; + snd_pcm_format_t format; + int channels; + ssize_t frame_size; + + params->fifo_size = substream->runtime->hw.fifo_size; + if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_FIFO_IN_FRAMES)) { + format = params_format(params); + channels = params_channels(params); + frame_size = snd_pcm_format_size(format, channels); + if (frame_size > 0) + params->fifo_size /= frame_size; + } + return 0; +} + +/** + * snd_pcm_lib_ioctl - a generic PCM ioctl callback + * @substream: the pcm substream instance + * @cmd: ioctl command + * @arg: ioctl argument + * + * Processes the generic ioctl commands for PCM. + * Can be passed as the ioctl callback for PCM ops. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + return snd_pcm_lib_ioctl_reset(substream, arg); + case SNDRV_PCM_IOCTL1_CHANNEL_INFO: + return snd_pcm_lib_ioctl_channel_info(substream, arg); + case SNDRV_PCM_IOCTL1_FIFO_SIZE: + return snd_pcm_lib_ioctl_fifo_size(substream, arg); + } + return -ENXIO; +} +EXPORT_SYMBOL(snd_pcm_lib_ioctl); + +/** + * snd_pcm_period_elapsed - update the pcm status for the next period + * @substream: the pcm substream instance + * + * This function is called from the interrupt handler when the + * PCM has processed the period size. It will update the current + * pointer, wake up sleepers, etc. + * + * Even if more than one periods have elapsed since the last call, you + * have to call this only once. + */ +void snd_pcm_period_elapsed(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + unsigned long flags; + + if (snd_BUG_ON(!substream)) + return; + + snd_pcm_stream_lock_irqsave(substream, flags); + if (PCM_RUNTIME_CHECK(substream)) + goto _unlock; + runtime = substream->runtime; + + if (!snd_pcm_running(substream) || + snd_pcm_update_hw_ptr0(substream, 1) < 0) + goto _end; + +#ifdef CONFIG_SND_PCM_TIMER + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); +#endif + _end: + kill_fasync(&runtime->fasync, SIGIO, POLL_IN); + _unlock: + snd_pcm_stream_unlock_irqrestore(substream, flags); +} +EXPORT_SYMBOL(snd_pcm_period_elapsed); + +/* + * Wait until avail_min data becomes available + * Returns a negative error code if any error occurs during operation. + * The available space is stored on availp. When err = 0 and avail = 0 + * on the capture stream, it indicates the stream is in DRAINING state. + */ +static int wait_for_avail(struct snd_pcm_substream *substream, + snd_pcm_uframes_t *availp) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + wait_queue_entry_t wait; + int err = 0; + snd_pcm_uframes_t avail = 0; + long wait_time, tout; + + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&runtime->tsleep, &wait); + + if (runtime->no_period_wakeup) + wait_time = MAX_SCHEDULE_TIMEOUT; + else { + /* use wait time from substream if available */ + if (substream->wait_time) { + wait_time = substream->wait_time; + } else { + wait_time = 10; + + if (runtime->rate) { + long t = runtime->period_size * 2 / + runtime->rate; + wait_time = max(t, wait_time); + } + wait_time = msecs_to_jiffies(wait_time * 1000); + } + } + + for (;;) { + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + + /* + * We need to check if space became available already + * (and thus the wakeup happened already) first to close + * the race of space already having become available. + * This check must happen after been added to the waitqueue + * and having current state be INTERRUPTIBLE. + */ + avail = snd_pcm_avail(substream); + if (avail >= runtime->twake) + break; + snd_pcm_stream_unlock_irq(substream); + + tout = schedule_timeout(wait_time); + + snd_pcm_stream_lock_irq(substream); + set_current_state(TASK_INTERRUPTIBLE); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_SUSPENDED: + err = -ESTRPIPE; + goto _endloop; + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + goto _endloop; + case SNDRV_PCM_STATE_DRAINING: + if (is_playback) + err = -EPIPE; + else + avail = 0; /* indicate draining */ + goto _endloop; + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_DISCONNECTED: + err = -EBADFD; + goto _endloop; + case SNDRV_PCM_STATE_PAUSED: + continue; + } + if (!tout) { + pcm_dbg(substream->pcm, + "%s write error (DMA or IRQ trouble?)\n", + is_playback ? "playback" : "capture"); + err = -EIO; + break; + } + } + _endloop: + set_current_state(TASK_RUNNING); + remove_wait_queue(&runtime->tsleep, &wait); + *availp = avail; + return err; +} + +typedef int (*pcm_transfer_f)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes); + +typedef int (*pcm_copy_f)(struct snd_pcm_substream *, snd_pcm_uframes_t, void *, + snd_pcm_uframes_t, snd_pcm_uframes_t, pcm_transfer_f); + +/* calculate the target DMA-buffer position to be written/read */ +static void *get_dma_ptr(struct snd_pcm_runtime *runtime, + int channel, unsigned long hwoff) +{ + return runtime->dma_area + hwoff + + channel * (runtime->dma_bytes / runtime->channels); +} + +/* default copy_user ops for write; used for both interleaved and non- modes */ +static int default_write_copy(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + if (copy_from_user(get_dma_ptr(substream->runtime, channel, hwoff), + (void __user *)buf, bytes)) + return -EFAULT; + return 0; +} + +/* default copy_kernel ops for write */ +static int default_write_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + memcpy(get_dma_ptr(substream->runtime, channel, hwoff), buf, bytes); + return 0; +} + +/* fill silence instead of copy data; called as a transfer helper + * from __snd_pcm_lib_write() or directly from noninterleaved_copy() when + * a NULL buffer is passed + */ +static int fill_silence(struct snd_pcm_substream *substream, int channel, + unsigned long hwoff, void *buf, unsigned long bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + if (substream->ops->fill_silence) + return substream->ops->fill_silence(substream, channel, + hwoff, bytes); + + snd_pcm_format_set_silence(runtime->format, + get_dma_ptr(runtime, channel, hwoff), + bytes_to_samples(runtime, bytes)); + return 0; +} + +/* default copy_user ops for read; used for both interleaved and non- modes */ +static int default_read_copy(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + if (copy_to_user((void __user *)buf, + get_dma_ptr(substream->runtime, channel, hwoff), + bytes)) + return -EFAULT; + return 0; +} + +/* default copy_kernel ops for read */ +static int default_read_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + memcpy(buf, get_dma_ptr(substream->runtime, channel, hwoff), bytes); + return 0; +} + +/* call transfer function with the converted pointers and sizes; + * for interleaved mode, it's one shot for all samples + */ +static int interleaved_copy(struct snd_pcm_substream *substream, + snd_pcm_uframes_t hwoff, void *data, + snd_pcm_uframes_t off, + snd_pcm_uframes_t frames, + pcm_transfer_f transfer) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* convert to bytes */ + hwoff = frames_to_bytes(runtime, hwoff); + off = frames_to_bytes(runtime, off); + frames = frames_to_bytes(runtime, frames); + return transfer(substream, 0, hwoff, data + off, frames); +} + +/* call transfer function with the converted pointers and sizes for each + * non-interleaved channel; when buffer is NULL, silencing instead of copying + */ +static int noninterleaved_copy(struct snd_pcm_substream *substream, + snd_pcm_uframes_t hwoff, void *data, + snd_pcm_uframes_t off, + snd_pcm_uframes_t frames, + pcm_transfer_f transfer) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int channels = runtime->channels; + void **bufs = data; + int c, err; + + /* convert to bytes; note that it's not frames_to_bytes() here. + * in non-interleaved mode, we copy for each channel, thus + * each copy is n_samples bytes x channels = whole frames. + */ + off = samples_to_bytes(runtime, off); + frames = samples_to_bytes(runtime, frames); + hwoff = samples_to_bytes(runtime, hwoff); + for (c = 0; c < channels; ++c, ++bufs) { + if (!data || !*bufs) + err = fill_silence(substream, c, hwoff, NULL, frames); + else + err = transfer(substream, c, hwoff, *bufs + off, + frames); + if (err < 0) + return err; + } + return 0; +} + +/* fill silence on the given buffer position; + * called from snd_pcm_playback_silence() + */ +static int fill_silence_frames(struct snd_pcm_substream *substream, + snd_pcm_uframes_t off, snd_pcm_uframes_t frames) +{ + if (substream->runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + substream->runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) + return interleaved_copy(substream, off, NULL, 0, frames, + fill_silence); + else + return noninterleaved_copy(substream, off, NULL, 0, frames, + fill_silence); +} + +/* sanity-check for read/write methods */ +static int pcm_sanity_check(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (snd_BUG_ON(!substream->ops->copy_user && !runtime->dma_area)) + return -EINVAL; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + return 0; +} + +static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +{ + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PAUSED: + return 0; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + case SNDRV_PCM_STATE_SUSPENDED: + return -ESTRPIPE; + default: + return -EBADFD; + } +} + +/* update to the given appl_ptr and call ack callback if needed; + * when an error is returned, take back to the original value + */ +int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t appl_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t old_appl_ptr = runtime->control->appl_ptr; + int ret; + + if (old_appl_ptr == appl_ptr) + return 0; + + runtime->control->appl_ptr = appl_ptr; + if (substream->ops->ack) { + ret = substream->ops->ack(substream); + if (ret < 0) { + runtime->control->appl_ptr = old_appl_ptr; + return ret; + } + } + + trace_applptr(substream, old_appl_ptr, appl_ptr); + + return 0; +} + +/* the common loop for read/write data */ +snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, + void *data, bool interleaved, + snd_pcm_uframes_t size, bool in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t xfer = 0; + snd_pcm_uframes_t offset = 0; + snd_pcm_uframes_t avail; + pcm_copy_f writer; + pcm_transfer_f transfer; + bool nonblock; + bool is_playback; + int err; + + err = pcm_sanity_check(substream); + if (err < 0) + return err; + + is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (interleaved) { + if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED && + runtime->channels > 1) + return -EINVAL; + writer = interleaved_copy; + } else { + if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + return -EINVAL; + writer = noninterleaved_copy; + } + + if (!data) { + if (is_playback) + transfer = fill_silence; + else + return -EINVAL; + } else if (in_kernel) { + if (substream->ops->copy_kernel) + transfer = substream->ops->copy_kernel; + else + transfer = is_playback ? + default_write_copy_kernel : default_read_copy_kernel; + } else { + if (substream->ops->copy_user) + transfer = (pcm_transfer_f)substream->ops->copy_user; + else + transfer = is_playback ? + default_write_copy : default_read_copy; + } + + if (size == 0) + return 0; + + nonblock = !!(substream->f_flags & O_NONBLOCK); + + snd_pcm_stream_lock_irq(substream); + err = pcm_accessible_state(runtime); + if (err < 0) + goto _end_unlock; + + if (!is_playback && + runtime->status->state == SNDRV_PCM_STATE_PREPARED && + size >= runtime->start_threshold) { + err = snd_pcm_start(substream); + if (err < 0) + goto _end_unlock; + } + + runtime->twake = runtime->control->avail_min ? : 1; + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) + snd_pcm_update_hw_ptr(substream); + avail = snd_pcm_avail(substream); + while (size > 0) { + snd_pcm_uframes_t frames, appl_ptr, appl_ofs; + snd_pcm_uframes_t cont; + if (!avail) { + if (!is_playback && + runtime->status->state == SNDRV_PCM_STATE_DRAINING) { + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + goto _end_unlock; + } + if (nonblock) { + err = -EAGAIN; + goto _end_unlock; + } + runtime->twake = min_t(snd_pcm_uframes_t, size, + runtime->control->avail_min ? : 1); + err = wait_for_avail(substream, &avail); + if (err < 0) + goto _end_unlock; + if (!avail) + continue; /* draining */ + } + frames = size > avail ? avail : size; + appl_ptr = READ_ONCE(runtime->control->appl_ptr); + appl_ofs = appl_ptr % runtime->buffer_size; + cont = runtime->buffer_size - appl_ofs; + if (frames > cont) + frames = cont; + if (snd_BUG_ON(!frames)) { + runtime->twake = 0; + snd_pcm_stream_unlock_irq(substream); + return -EINVAL; + } + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) { + err = -EBUSY; + goto _end_unlock; + } + snd_pcm_stream_unlock_irq(substream); + err = writer(substream, appl_ofs, data, offset, frames, + transfer); + snd_pcm_stream_lock_irq(substream); + atomic_dec(&runtime->buffer_accessing); + if (err < 0) + goto _end_unlock; + err = pcm_accessible_state(runtime); + if (err < 0) + goto _end_unlock; + appl_ptr += frames; + if (appl_ptr >= runtime->boundary) + appl_ptr -= runtime->boundary; + err = pcm_lib_apply_appl_ptr(substream, appl_ptr); + if (err < 0) + goto _end_unlock; + + offset += frames; + size -= frames; + xfer += frames; + avail -= frames; + if (is_playback && + runtime->status->state == SNDRV_PCM_STATE_PREPARED && + snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) { + err = snd_pcm_start(substream); + if (err < 0) + goto _end_unlock; + } + } + _end_unlock: + runtime->twake = 0; + if (xfer > 0 && err >= 0) + snd_pcm_update_state(substream, runtime); + snd_pcm_stream_unlock_irq(substream); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : err; +} +EXPORT_SYMBOL(__snd_pcm_lib_xfer); + +/* + * standard channel mapping helpers + */ + +/* default channel maps for multi-channel playbacks, up to 8 channels */ +const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .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_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { .channels = 8, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, + { } +}; +EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps); + +/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */ +const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .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 } }, + { .channels = 8, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, + { } +}; +EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps); + +static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch) +{ + if (ch > info->max_channels) + return false; + return !info->channel_mask || (info->channel_mask & (1U << ch)); +} + +static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 0; + uinfo->count = info->max_channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SNDRV_CHMAP_LAST; + return 0; +} + +/* get callback for channel map ctl element + * stores the channel position firstly matching with the current channels + */ +static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct snd_pcm_substream *substream; + const struct snd_pcm_chmap_elem *map; + + if (!info->chmap) + return -EINVAL; + substream = snd_pcm_chmap_substream(info, idx); + if (!substream) + return -ENODEV; + memset(ucontrol->value.integer.value, 0, + sizeof(ucontrol->value.integer.value)); + if (!substream->runtime) + return 0; /* no channels set */ + for (map = info->chmap; map->channels; map++) { + int i; + if (map->channels == substream->runtime->channels && + valid_chmap_channels(info, map->channels)) { + for (i = 0; i < map->channels; i++) + ucontrol->value.integer.value[i] = map->map[i]; + return 0; + } + } + return -EINVAL; +} + +/* tlv callback for channel map ctl element + * expands the pre-defined channel maps in a form of TLV + */ +static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + const struct snd_pcm_chmap_elem *map; + unsigned int __user *dst; + int c, count = 0; + + if (!info->chmap) + return -EINVAL; + if (size < 8) + return -ENOMEM; + if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) + return -EFAULT; + size -= 8; + dst = tlv + 2; + for (map = info->chmap; map->channels; map++) { + int chs_bytes = map->channels * 4; + if (!valid_chmap_channels(info, map->channels)) + continue; + if (size < 8) + return -ENOMEM; + if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) || + put_user(chs_bytes, dst + 1)) + return -EFAULT; + dst += 2; + size -= 8; + count += 8; + if (size < chs_bytes) + return -ENOMEM; + size -= chs_bytes; + count += chs_bytes; + for (c = 0; c < map->channels; c++) { + if (put_user(map->map[c], dst)) + return -EFAULT; + dst++; + } + } + if (put_user(count, tlv + 1)) + return -EFAULT; + return 0; +} + +static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + info->pcm->streams[info->stream].chmap_kctl = NULL; + kfree(info); +} + +/** + * snd_pcm_add_chmap_ctls - create channel-mapping control elements + * @pcm: the assigned PCM instance + * @stream: stream direction + * @chmap: channel map elements (for query) + * @max_channels: the max number of channels for the stream + * @private_value: the value passed to each kcontrol's private_value field + * @info_ret: store struct snd_pcm_chmap instance if non-NULL + * + * Create channel-mapping control elements assigned to the given PCM stream(s). + * Return: Zero if successful, or a negative error value. + */ +int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, + const struct snd_pcm_chmap_elem *chmap, + int max_channels, + unsigned long private_value, + struct snd_pcm_chmap **info_ret) +{ + struct snd_pcm_chmap *info; + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, + .info = pcm_chmap_ctl_info, + .get = pcm_chmap_ctl_get, + .tlv.c = pcm_chmap_ctl_tlv, + }; + int err; + + if (WARN_ON(pcm->streams[stream].chmap_kctl)) + return -EBUSY; + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->pcm = pcm; + info->stream = stream; + info->chmap = chmap; + info->max_channels = max_channels; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + knew.name = "Playback Channel Map"; + else + knew.name = "Capture Channel Map"; + knew.device = pcm->device; + knew.count = pcm->streams[stream].substream_count; + knew.private_value = private_value; + info->kctl = snd_ctl_new1(&knew, info); + if (!info->kctl) { + kfree(info); + return -ENOMEM; + } + info->kctl->private_free = pcm_chmap_ctl_private_free; + err = snd_ctl_add(pcm->card, info->kctl); + if (err < 0) + return err; + pcm->streams[stream].chmap_kctl = info->kctl; + if (info_ret) + *info_ret = info; + return 0; +} +EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls); diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h new file mode 100644 index 000000000..c51561296 --- /dev/null +++ b/sound/core/pcm_local.h @@ -0,0 +1,70 @@ +/* + * pcm_local.h - a local header file for snd-pcm module. + * + * Copyright (c) Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __SOUND_CORE_PCM_LOCAL_H +#define __SOUND_CORE_PCM_LOCAL_H + +extern const struct snd_pcm_hw_constraint_list snd_pcm_known_rates; + +void snd_interval_mul(const struct snd_interval *a, + const struct snd_interval *b, struct snd_interval *c); +void snd_interval_div(const struct snd_interval *a, + const struct snd_interval *b, struct snd_interval *c); +void snd_interval_muldivk(const struct snd_interval *a, + const struct snd_interval *b, + unsigned int k, struct snd_interval *c); +void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k, + const struct snd_interval *b, struct snd_interval *c); + +int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream); +int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream); + +int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, + snd_pcm_hw_param_t var, u_int32_t mask); + +int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t appl_ptr); +int snd_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime); +int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream); + +void snd_pcm_playback_silence(struct snd_pcm_substream *substream, + snd_pcm_uframes_t new_hw_ptr); + +static inline snd_pcm_uframes_t +snd_pcm_avail(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return snd_pcm_playback_avail(substream->runtime); + else + return snd_pcm_capture_avail(substream->runtime); +} + +static inline snd_pcm_uframes_t +snd_pcm_hw_avail(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return snd_pcm_playback_hw_avail(substream->runtime); + else + return snd_pcm_capture_hw_avail(substream->runtime); +} + +#ifdef CONFIG_SND_PCM_TIMER +void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream); +void snd_pcm_timer_init(struct snd_pcm_substream *substream); +void snd_pcm_timer_done(struct snd_pcm_substream *substream); +#else +static inline void +snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream) {} +static inline void snd_pcm_timer_init(struct snd_pcm_substream *substream) {} +static inline void snd_pcm_timer_done(struct snd_pcm_substream *substream) {} +#endif + +void __snd_pcm_xrun(struct snd_pcm_substream *substream); + +#endif /* __SOUND_CORE_PCM_LOCAL_H */ diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c new file mode 100644 index 000000000..48e5f0091 --- /dev/null +++ b/sound/core/pcm_memory.c @@ -0,0 +1,457 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int preallocate_dma = 1; +module_param(preallocate_dma, int, 0444); +MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized."); + +static int maximum_substreams = 4; +module_param(maximum_substreams, int, 0444); +MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory."); + +static const size_t snd_minimum_buffer = 16384; + + +/* + * try to allocate as the large pages as possible. + * stores the resultant memory size in *res_size. + * + * the minimum size is snd_minimum_buffer. it should be power of 2. + */ +static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size) +{ + struct snd_dma_buffer *dmab = &substream->dma_buffer; + size_t orig_size = size; + int err; + + do { + if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev, + size, dmab)) < 0) { + if (err != -ENOMEM) + return err; /* fatal error */ + } else + return 0; + size >>= 1; + } while (size >= snd_minimum_buffer); + dmab->bytes = 0; /* tell error */ + pr_warn("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n", + substream->pcm->card->number, substream->pcm->device, + substream->stream ? 'c' : 'p', substream->number, + substream->pcm->name, orig_size); + return 0; +} + +/* + * release the preallocated buffer if not yet done. + */ +static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream) +{ + if (substream->dma_buffer.area == NULL) + return; + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; +} + +/** + * snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream. + * @substream: the pcm substream instance + * + * Releases the pre-allocated buffer of the given substream. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_preallocate_dma_free(substream); +#ifdef CONFIG_SND_VERBOSE_PROCFS + snd_info_free_entry(substream->proc_prealloc_max_entry); + substream->proc_prealloc_max_entry = NULL; + snd_info_free_entry(substream->proc_prealloc_entry); + substream->proc_prealloc_entry = NULL; +#endif + return 0; +} + +/** + * snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm + * @pcm: the pcm instance + * + * Releases all the pre-allocated buffers on the given pcm. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int stream; + + for (stream = 0; stream < 2; stream++) + for (substream = pcm->streams[stream].substream; substream; substream = substream->next) + snd_pcm_lib_preallocate_free(substream); + return 0; +} +EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all); + +#ifdef CONFIG_SND_VERBOSE_PROCFS +/* + * read callback for prealloc proc file + * + * prints the current allocated size in kB. + */ +static void snd_pcm_lib_preallocate_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024); +} + +/* + * read callback for prealloc_max proc file + * + * prints the maximum allowed size in kB. + */ +static void snd_pcm_lib_preallocate_max_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_max / 1024); +} + +/* + * write callback for prealloc proc file + * + * accepts the preallocation size in kB. + */ +static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + char line[64], str[64]; + size_t size; + struct snd_dma_buffer new_dmab; + + mutex_lock(&substream->pcm->open_mutex); + if (substream->runtime) { + buffer->error = -EBUSY; + goto unlock; + } + if (!snd_info_get_line(buffer, line, sizeof(line))) { + snd_info_get_str(str, line, sizeof(str)); + size = simple_strtoul(str, NULL, 10) * 1024; + if ((size != 0 && size < 8192) || size > substream->dma_max) { + buffer->error = -EINVAL; + goto unlock; + } + if (substream->dma_buffer.bytes == size) + goto unlock; + memset(&new_dmab, 0, sizeof(new_dmab)); + new_dmab.dev = substream->dma_buffer.dev; + if (size > 0) { + if (snd_dma_alloc_pages(substream->dma_buffer.dev.type, + substream->dma_buffer.dev.dev, + size, &new_dmab) < 0) { + buffer->error = -ENOMEM; + goto unlock; + } + substream->buffer_bytes_max = size; + } else { + substream->buffer_bytes_max = UINT_MAX; + } + if (substream->dma_buffer.area) + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer = new_dmab; + } else { + buffer->error = -EINVAL; + } + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +static inline void preallocate_info_init(struct snd_pcm_substream *substream) +{ + struct snd_info_entry *entry; + + if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) { + entry->c.text.read = snd_pcm_lib_preallocate_proc_read; + entry->c.text.write = snd_pcm_lib_preallocate_proc_write; + entry->mode |= 0200; + entry->private_data = substream; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_prealloc_entry = entry; + if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", substream->proc_root)) != NULL) { + entry->c.text.read = snd_pcm_lib_preallocate_max_proc_read; + entry->private_data = substream; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_prealloc_max_entry = entry; +} + +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +#define preallocate_info_init(s) +#endif /* CONFIG_SND_VERBOSE_PROCFS */ + +/* + * pre-allocate the buffer and create a proc file for the substream + */ +static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream, + size_t size, size_t max) +{ + + if (size > 0 && preallocate_dma && substream->number < maximum_substreams) + preallocate_pcm_pages(substream, size); + + if (substream->dma_buffer.bytes > 0) + substream->buffer_bytes_max = substream->dma_buffer.bytes; + substream->dma_max = max; + preallocate_info_init(substream); + return 0; +} + + +/** + * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type + * @substream: the pcm substream instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation for the given DMA buffer type. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream, + int type, struct device *data, + size_t size, size_t max) +{ + substream->dma_buffer.dev.type = type; + substream->dma_buffer.dev.dev = data; + return snd_pcm_lib_preallocate_pages1(substream, size, max); +} +EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages); + +/** + * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continuous memory type (all substreams) + * @pcm: the pcm instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation to all substreams of the given pcm for the + * specified DMA type. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, + int type, void *data, + size_t size, size_t max) +{ + struct snd_pcm_substream *substream; + int stream, err; + + for (stream = 0; stream < 2; stream++) + for (substream = pcm->streams[stream].substream; substream; substream = substream->next) + if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0) + return err; + return 0; +} +EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all); + +#ifdef CONFIG_SND_DMA_SGBUF +/** + * snd_pcm_sgbuf_ops_page - get the page struct at the given offset + * @substream: the pcm substream instance + * @offset: the buffer offset + * + * Used as the page callback of PCM ops. + * + * Return: The page struct at the given buffer offset. %NULL on failure. + */ +struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigned long offset) +{ + struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream); + + unsigned int idx = offset >> PAGE_SHIFT; + if (idx >= (unsigned int)sgbuf->pages) + return NULL; + return sgbuf->page_table[idx]; +} +EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page); +#endif /* CONFIG_SND_DMA_SGBUF */ + +/** + * snd_pcm_lib_malloc_pages - allocate the DMA buffer + * @substream: the substream to allocate the DMA buffer to + * @size: the requested buffer size in bytes + * + * Allocates the DMA buffer on the BUS type given earlier to + * snd_pcm_lib_preallocate_xxx_pages(). + * + * Return: 1 if the buffer is changed, 0 if not changed, or a negative + * code on failure. + */ +int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) +{ + struct snd_pcm_runtime *runtime; + struct snd_dma_buffer *dmab = NULL; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + if (snd_BUG_ON(substream->dma_buffer.dev.type == + SNDRV_DMA_TYPE_UNKNOWN)) + return -EINVAL; + runtime = substream->runtime; + + if (runtime->dma_buffer_p) { + /* perphaps, we might free the large DMA memory region + to save some space here, but the actual solution + costs us less time */ + if (runtime->dma_buffer_p->bytes >= size) { + runtime->dma_bytes = size; + return 0; /* ok, do not change */ + } + snd_pcm_lib_free_pages(substream); + } + if (substream->dma_buffer.area != NULL && + substream->dma_buffer.bytes >= size) { + dmab = &substream->dma_buffer; /* use the pre-allocated buffer */ + } else { + dmab = kzalloc(sizeof(*dmab), GFP_KERNEL); + if (! dmab) + return -ENOMEM; + dmab->dev = substream->dma_buffer.dev; + if (snd_dma_alloc_pages(substream->dma_buffer.dev.type, + substream->dma_buffer.dev.dev, + size, dmab) < 0) { + kfree(dmab); + return -ENOMEM; + } + } + snd_pcm_set_runtime_buffer(substream, dmab); + runtime->dma_bytes = size; + return 1; /* area was changed */ +} +EXPORT_SYMBOL(snd_pcm_lib_malloc_pages); + +/** + * snd_pcm_lib_free_pages - release the allocated DMA buffer. + * @substream: the substream to release the DMA buffer + * + * Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + runtime = substream->runtime; + if (runtime->dma_area == NULL) + return 0; + if (runtime->dma_buffer_p != &substream->dma_buffer) { + /* it's a newly allocated buffer. release it now. */ + snd_dma_free_pages(runtime->dma_buffer_p); + kfree(runtime->dma_buffer_p); + } + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} +EXPORT_SYMBOL(snd_pcm_lib_free_pages); + +int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream, + size_t size, gfp_t gfp_flags) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + runtime = substream->runtime; + if (runtime->dma_area) { + if (runtime->dma_bytes >= size) + return 0; /* already large enough */ + vfree(runtime->dma_area); + } + runtime->dma_area = __vmalloc(size, gfp_flags, PAGE_KERNEL); + if (!runtime->dma_area) + return -ENOMEM; + runtime->dma_bytes = size; + return 1; +} +EXPORT_SYMBOL(_snd_pcm_lib_alloc_vmalloc_buffer); + +/** + * snd_pcm_lib_free_vmalloc_buffer - free vmalloc buffer + * @substream: the substream with a buffer allocated by + * snd_pcm_lib_alloc_vmalloc_buffer() + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + runtime = substream->runtime; + vfree(runtime->dma_area); + runtime->dma_area = NULL; + return 0; +} +EXPORT_SYMBOL(snd_pcm_lib_free_vmalloc_buffer); + +/** + * snd_pcm_lib_get_vmalloc_page - map vmalloc buffer offset to page struct + * @substream: the substream with a buffer allocated by + * snd_pcm_lib_alloc_vmalloc_buffer() + * @offset: offset in the buffer + * + * This function is to be used as the page callback in the PCM ops. + * + * Return: The page struct, or %NULL on failure. + */ +struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + return vmalloc_to_page(substream->runtime->dma_area + offset); +} +EXPORT_SYMBOL(snd_pcm_lib_get_vmalloc_page); diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c new file mode 100644 index 000000000..0956be39b --- /dev/null +++ b/sound/core/pcm_misc.c @@ -0,0 +1,606 @@ +/* + * PCM Interface - misc routines + * Copyright (c) 1998 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + */ + +#include +#include +#include +#include + +#include "pcm_local.h" + +#define SND_PCM_FORMAT_UNKNOWN (-1) + +/* NOTE: "signed" prefix must be given below since the default char is + * unsigned on some architectures! + */ +struct pcm_format_data { + unsigned char width; /* bit width */ + unsigned char phys; /* physical bit width */ + signed char le; /* 0 = big-endian, 1 = little-endian, -1 = others */ + signed char signd; /* 0 = unsigned, 1 = signed, -1 = others */ + unsigned char silence[8]; /* silence data to fill */ +}; + +/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ +#define INT __force int + +static struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U8] = { + .width = 8, .phys = 8, .le = -1, .signd = 0, + .silence = { 0x80 }, + }, + [SNDRV_PCM_FORMAT_S16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 0, + .silence = { 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 0, + .silence = { 0x80, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S24_LE] = { + .width = 24, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S24_BE] = { + .width = 24, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U24_LE] = { + .width = 24, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U24_BE] = { + .width = 24, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x00, 0x80, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x80, 0x00, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_FLOAT_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT64_LE] = { + .width = 64, .phys = 64, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT64_BE] = { + .width = 64, .phys = 64, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_MU_LAW] = { + .width = 8, .phys = 8, .le = -1, .signd = -1, + .silence = { 0x7f }, + }, + [SNDRV_PCM_FORMAT_A_LAW] = { + .width = 8, .phys = 8, .le = -1, .signd = -1, + .silence = { 0x55 }, + }, + [SNDRV_PCM_FORMAT_IMA_ADPCM] = { + .width = 4, .phys = 4, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_G723_24] = { + .width = 3, .phys = 3, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_G723_40] = { + .width = 5, .phys = 5, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_DSD_U8] = { + .width = 8, .phys = 8, .le = 1, .signd = 0, + .silence = { 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 0, + .silence = { 0x69, 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x69, 0x69, 0x69, 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 0, + .silence = { 0x69, 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x69, 0x69, 0x69, 0x69 }, + }, + /* FIXME: the following two formats are not defined properly yet */ + [SNDRV_PCM_FORMAT_MPEG] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_GSM] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_S20_LE] = { + .width = 20, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S20_BE] = { + .width = 20, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U20_LE] = { + .width = 20, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x08, 0x00 }, + }, + [SNDRV_PCM_FORMAT_U20_BE] = { + .width = 20, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x00, 0x08, 0x00, 0x00 }, + }, + /* FIXME: the following format is not defined properly yet */ + [SNDRV_PCM_FORMAT_SPECIAL] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_S24_3LE] = { + .width = 24, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S24_3BE] = { + .width = 24, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U24_3LE] = { + .width = 24, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U24_3BE] = { + .width = 24, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x80, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S20_3LE] = { + .width = 20, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S20_3BE] = { + .width = 20, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U20_3LE] = { + .width = 20, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x08 }, + }, + [SNDRV_PCM_FORMAT_U20_3BE] = { + .width = 20, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x08, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S18_3LE] = { + .width = 18, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S18_3BE] = { + .width = 18, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U18_3LE] = { + .width = 18, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x02 }, + }, + [SNDRV_PCM_FORMAT_U18_3BE] = { + .width = 18, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x02, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_G723_24_1B] = { + .width = 3, .phys = 8, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_G723_40_1B] = { + .width = 5, .phys = 8, .le = -1, .signd = -1, + .silence = {}, + }, +}; + + +/** + * snd_pcm_format_signed - Check the PCM format is signed linear + * @format: the format to check + * + * Return: 1 if the given PCM format is signed linear, 0 if unsigned + * linear, and a negative error code for non-linear formats. + */ +int snd_pcm_format_signed(snd_pcm_format_t format) +{ + int val; + if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[(INT)format].signd) < 0) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_signed); + +/** + * snd_pcm_format_unsigned - Check the PCM format is unsigned linear + * @format: the format to check + * + * Return: 1 if the given PCM format is unsigned linear, 0 if signed + * linear, and a negative error code for non-linear formats. + */ +int snd_pcm_format_unsigned(snd_pcm_format_t format) +{ + int val; + + val = snd_pcm_format_signed(format); + if (val < 0) + return val; + return !val; +} +EXPORT_SYMBOL(snd_pcm_format_unsigned); + +/** + * snd_pcm_format_linear - Check the PCM format is linear + * @format: the format to check + * + * Return: 1 if the given PCM format is linear, 0 if not. + */ +int snd_pcm_format_linear(snd_pcm_format_t format) +{ + return snd_pcm_format_signed(format) >= 0; +} +EXPORT_SYMBOL(snd_pcm_format_linear); + +/** + * snd_pcm_format_little_endian - Check the PCM format is little-endian + * @format: the format to check + * + * Return: 1 if the given PCM format is little-endian, 0 if + * big-endian, or a negative error code if endian not specified. + */ +int snd_pcm_format_little_endian(snd_pcm_format_t format) +{ + int val; + if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[(INT)format].le) < 0) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_little_endian); + +/** + * snd_pcm_format_big_endian - Check the PCM format is big-endian + * @format: the format to check + * + * Return: 1 if the given PCM format is big-endian, 0 if + * little-endian, or a negative error code if endian not specified. + */ +int snd_pcm_format_big_endian(snd_pcm_format_t format) +{ + int val; + + val = snd_pcm_format_little_endian(format); + if (val < 0) + return val; + return !val; +} +EXPORT_SYMBOL(snd_pcm_format_big_endian); + +/** + * snd_pcm_format_width - return the bit-width of the format + * @format: the format to check + * + * Return: The bit-width of the format, or a negative error code + * if unknown format. + */ +int snd_pcm_format_width(snd_pcm_format_t format) +{ + int val; + if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[(INT)format].width) == 0) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_width); + +/** + * snd_pcm_format_physical_width - return the physical bit-width of the format + * @format: the format to check + * + * Return: The physical bit-width of the format, or a negative error code + * if unknown format. + */ +int snd_pcm_format_physical_width(snd_pcm_format_t format) +{ + int val; + if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[(INT)format].phys) == 0) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_physical_width); + +/** + * snd_pcm_format_size - return the byte size of samples on the given format + * @format: the format to check + * @samples: sampling rate + * + * Return: The byte size of the given samples for the format, or a + * negative error code if unknown format. + */ +ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) +{ + int phys_width = snd_pcm_format_physical_width(format); + if (phys_width < 0) + return -EINVAL; + return samples * phys_width / 8; +} +EXPORT_SYMBOL(snd_pcm_format_size); + +/** + * snd_pcm_format_silence_64 - return the silent data in 8 bytes array + * @format: the format to check + * + * Return: The format pattern to fill or %NULL if error. + */ +const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) +{ + if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST) + return NULL; + if (! pcm_formats[(INT)format].phys) + return NULL; + return pcm_formats[(INT)format].silence; +} +EXPORT_SYMBOL(snd_pcm_format_silence_64); + +/** + * snd_pcm_format_set_silence - set the silence data on the buffer + * @format: the PCM format + * @data: the buffer pointer + * @samples: the number of samples to set silence + * + * Sets the silence data on the buffer for the given samples. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples) +{ + int width; + unsigned char *dst, *pat; + + if ((INT)format < 0 || (INT)format > (INT)SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if (samples == 0) + return 0; + width = pcm_formats[(INT)format].phys; /* physical width */ + pat = pcm_formats[(INT)format].silence; + if (!width || !pat) + return -EINVAL; + /* signed or 1 byte data */ + if (pcm_formats[(INT)format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; + } + /* non-zero samples, fill using a loop */ + width /= 8; + dst = data; +#if 0 + while (samples--) { + memcpy(dst, pat, width); + dst += width; + } +#else + /* a bit optimization for constant width */ + switch (width) { + case 2: + while (samples--) { + memcpy(dst, pat, 2); + dst += 2; + } + break; + case 3: + while (samples--) { + memcpy(dst, pat, 3); + dst += 3; + } + break; + case 4: + while (samples--) { + memcpy(dst, pat, 4); + dst += 4; + } + break; + case 8: + while (samples--) { + memcpy(dst, pat, 8); + dst += 8; + } + break; + } +#endif + return 0; +} +EXPORT_SYMBOL(snd_pcm_format_set_silence); + +/** + * snd_pcm_limit_hw_rates - determine rate_min/rate_max fields + * @runtime: the runtime instance + * + * Determines the rate_min and rate_max fields from the rates bits of + * the given runtime->hw. + * + * Return: Zero if successful. + */ +int snd_pcm_limit_hw_rates(struct snd_pcm_runtime *runtime) +{ + int i; + for (i = 0; i < (int)snd_pcm_known_rates.count; i++) { + if (runtime->hw.rates & (1 << i)) { + runtime->hw.rate_min = snd_pcm_known_rates.list[i]; + break; + } + } + for (i = (int)snd_pcm_known_rates.count - 1; i >= 0; i--) { + if (runtime->hw.rates & (1 << i)) { + runtime->hw.rate_max = snd_pcm_known_rates.list[i]; + break; + } + } + return 0; +} +EXPORT_SYMBOL(snd_pcm_limit_hw_rates); + +/** + * snd_pcm_rate_to_rate_bit - converts sample rate to SNDRV_PCM_RATE_xxx bit + * @rate: the sample rate to convert + * + * Return: The SNDRV_PCM_RATE_xxx flag that corresponds to the given rate, or + * SNDRV_PCM_RATE_KNOT for an unknown rate. + */ +unsigned int snd_pcm_rate_to_rate_bit(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < snd_pcm_known_rates.count; i++) + if (snd_pcm_known_rates.list[i] == rate) + return 1u << i; + return SNDRV_PCM_RATE_KNOT; +} +EXPORT_SYMBOL(snd_pcm_rate_to_rate_bit); + +/** + * snd_pcm_rate_bit_to_rate - converts SNDRV_PCM_RATE_xxx bit to sample rate + * @rate_bit: the rate bit to convert + * + * Return: The sample rate that corresponds to the given SNDRV_PCM_RATE_xxx flag + * or 0 for an unknown rate bit. + */ +unsigned int snd_pcm_rate_bit_to_rate(unsigned int rate_bit) +{ + unsigned int i; + + for (i = 0; i < snd_pcm_known_rates.count; i++) + if ((1u << i) == rate_bit) + return snd_pcm_known_rates.list[i]; + return 0; +} +EXPORT_SYMBOL(snd_pcm_rate_bit_to_rate); + +static unsigned int snd_pcm_rate_mask_sanitize(unsigned int rates) +{ + if (rates & SNDRV_PCM_RATE_CONTINUOUS) + return SNDRV_PCM_RATE_CONTINUOUS; + else if (rates & SNDRV_PCM_RATE_KNOT) + return SNDRV_PCM_RATE_KNOT; + return rates; +} + +/** + * snd_pcm_rate_mask_intersect - computes the intersection between two rate masks + * @rates_a: The first rate mask + * @rates_b: The second rate mask + * + * This function computes the rates that are supported by both rate masks passed + * to the function. It will take care of the special handling of + * SNDRV_PCM_RATE_CONTINUOUS and SNDRV_PCM_RATE_KNOT. + * + * Return: A rate mask containing the rates that are supported by both rates_a + * and rates_b. + */ +unsigned int snd_pcm_rate_mask_intersect(unsigned int rates_a, + unsigned int rates_b) +{ + rates_a = snd_pcm_rate_mask_sanitize(rates_a); + rates_b = snd_pcm_rate_mask_sanitize(rates_b); + + if (rates_a & SNDRV_PCM_RATE_CONTINUOUS) + return rates_b; + else if (rates_b & SNDRV_PCM_RATE_CONTINUOUS) + return rates_a; + else if (rates_a & SNDRV_PCM_RATE_KNOT) + return rates_b; + else if (rates_b & SNDRV_PCM_RATE_KNOT) + return rates_a; + return rates_a & rates_b; +} +EXPORT_SYMBOL_GPL(snd_pcm_rate_mask_intersect); + +/** + * snd_pcm_rate_range_to_bits - converts rate range to SNDRV_PCM_RATE_xxx bit + * @rate_min: the minimum sample rate + * @rate_max: the maximum sample rate + * + * This function has an implicit assumption: the rates in the given range have + * only the pre-defined rates like 44100 or 16000. + * + * Return: The SNDRV_PCM_RATE_xxx flag that corresponds to the given rate range, + * or SNDRV_PCM_RATE_KNOT for an unknown range. + */ +unsigned int snd_pcm_rate_range_to_bits(unsigned int rate_min, + unsigned int rate_max) +{ + unsigned int rates = 0; + int i; + + for (i = 0; i < snd_pcm_known_rates.count; i++) { + if (snd_pcm_known_rates.list[i] >= rate_min + && snd_pcm_known_rates.list[i] <= rate_max) + rates |= 1 << i; + } + + if (!rates) + rates = SNDRV_PCM_RATE_KNOT; + + return rates; +} +EXPORT_SYMBOL_GPL(snd_pcm_rate_range_to_bits); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c new file mode 100644 index 000000000..9862b60bf --- /dev/null +++ b/sound/core/pcm_native.c @@ -0,0 +1,3755 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcm_local.h" + +#ifdef CONFIG_SND_DEBUG +#define CREATE_TRACE_POINTS +#include "pcm_param_trace.h" +#else +#define trace_hw_mask_param_enabled() 0 +#define trace_hw_interval_param_enabled() 0 +#define trace_hw_mask_param(substream, type, index, prev, curr) +#define trace_hw_interval_param(substream, type, index, prev, curr) +#endif + +/* + * Compatibility + */ + +struct snd_pcm_hw_params_old { + unsigned int flags; + unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - + SNDRV_PCM_HW_PARAM_ACCESS + 1]; + struct snd_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME - + SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1]; + unsigned int rmask; + unsigned int cmask; + unsigned int info; + unsigned int msbits; + unsigned int rate_num; + unsigned int rate_den; + snd_pcm_uframes_t fifo_size; + unsigned char reserved[64]; +}; + +#ifdef CONFIG_SND_SUPPORT_OLD_API +#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct snd_pcm_hw_params_old) +#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct snd_pcm_hw_params_old) + +static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams); +static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams); +#endif +static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream); + +/* + * + */ + +static DEFINE_RWLOCK(snd_pcm_link_rwlock); +static DECLARE_RWSEM(snd_pcm_link_rwsem); + +/* Writer in rwsem may block readers even during its waiting in queue, + * and this may lead to a deadlock when the code path takes read sem + * twice (e.g. one in snd_pcm_action_nonatomic() and another in + * snd_pcm_stream_lock()). As a (suboptimal) workaround, let writer to + * sleep until all the readers are completed without blocking by writer. + */ +static inline void down_write_nonfifo(struct rw_semaphore *lock) +{ + while (!down_write_trylock(lock)) + msleep(1); +} + +#define PCM_LOCK_DEFAULT 0 +#define PCM_LOCK_IRQ 1 +#define PCM_LOCK_IRQSAVE 2 + +static unsigned long __snd_pcm_stream_lock_mode(struct snd_pcm_substream *substream, + unsigned int mode) +{ + unsigned long flags = 0; + if (substream->pcm->nonatomic) { + down_read_nested(&snd_pcm_link_rwsem, SINGLE_DEPTH_NESTING); + mutex_lock(&substream->self_group.mutex); + } else { + switch (mode) { + case PCM_LOCK_DEFAULT: + read_lock(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQ: + read_lock_irq(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQSAVE: + read_lock_irqsave(&snd_pcm_link_rwlock, flags); + break; + } + spin_lock(&substream->self_group.lock); + } + return flags; +} + +static void __snd_pcm_stream_unlock_mode(struct snd_pcm_substream *substream, + unsigned int mode, unsigned long flags) +{ + if (substream->pcm->nonatomic) { + mutex_unlock(&substream->self_group.mutex); + up_read(&snd_pcm_link_rwsem); + } else { + spin_unlock(&substream->self_group.lock); + + switch (mode) { + case PCM_LOCK_DEFAULT: + read_unlock(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQ: + read_unlock_irq(&snd_pcm_link_rwlock); + break; + case PCM_LOCK_IRQSAVE: + read_unlock_irqrestore(&snd_pcm_link_rwlock, flags); + break; + } + } +} + +/** + * snd_pcm_stream_lock - Lock the PCM stream + * @substream: PCM substream + * + * This locks the PCM stream's spinlock or mutex depending on the nonatomic + * flag of the given substream. This also takes the global link rw lock + * (or rw sem), too, for avoiding the race with linked streams. + */ +void snd_pcm_stream_lock(struct snd_pcm_substream *substream) +{ + __snd_pcm_stream_lock_mode(substream, PCM_LOCK_DEFAULT); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_lock); + +/** + * snd_pcm_stream_lock - Unlock the PCM stream + * @substream: PCM substream + * + * This unlocks the PCM stream that has been locked via snd_pcm_stream_lock(). + */ +void snd_pcm_stream_unlock(struct snd_pcm_substream *substream) +{ + __snd_pcm_stream_unlock_mode(substream, PCM_LOCK_DEFAULT, 0); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock); + +/** + * snd_pcm_stream_lock_irq - Lock the PCM stream + * @substream: PCM substream + * + * This locks the PCM stream like snd_pcm_stream_lock() and disables the local + * IRQ (only when nonatomic is false). In nonatomic case, this is identical + * as snd_pcm_stream_lock(). + */ +void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream) +{ + __snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQ); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq); + +/** + * snd_pcm_stream_unlock_irq - Unlock the PCM stream + * @substream: PCM substream + * + * This is a counter-part of snd_pcm_stream_lock_irq(). + */ +void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream) +{ + __snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQ, 0); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irq); + +unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream) +{ + return __snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQSAVE); +} +EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave); + +/** + * snd_pcm_stream_unlock_irqrestore - Unlock the PCM stream + * @substream: PCM substream + * @flags: irq flags + * + * This is a counter-part of snd_pcm_stream_lock_irqsave(). + */ +void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream, + unsigned long flags) +{ + __snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQSAVE, flags); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore); + +int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info) +{ + struct snd_pcm *pcm = substream->pcm; + struct snd_pcm_str *pstr = substream->pstr; + + memset(info, 0, sizeof(*info)); + info->card = pcm->card->number; + info->device = pcm->device; + info->stream = substream->stream; + info->subdevice = substream->number; + strlcpy(info->id, pcm->id, sizeof(info->id)); + strlcpy(info->name, pcm->name, sizeof(info->name)); + info->dev_class = pcm->dev_class; + info->dev_subclass = pcm->dev_subclass; + info->subdevices_count = pstr->substream_count; + info->subdevices_avail = pstr->substream_count - pstr->substream_opened; + strlcpy(info->subname, substream->name, sizeof(info->subname)); + + return 0; +} + +int snd_pcm_info_user(struct snd_pcm_substream *substream, + struct snd_pcm_info __user * _info) +{ + struct snd_pcm_info *info; + int err; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + err = snd_pcm_info(substream, info); + if (err >= 0) { + if (copy_to_user(_info, info, sizeof(*info))) + err = -EFAULT; + } + kfree(info); + return err; +} + +static bool hw_support_mmap(struct snd_pcm_substream *substream) +{ + if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_MMAP)) + return false; + /* architecture supports dma_mmap_coherent()? */ +#if defined(CONFIG_ARCH_NO_COHERENT_DMA_MMAP) || !defined(CONFIG_HAS_DMA) + if (!substream->ops->mmap && + substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) + return false; +#endif + return true; +} + +static int constrain_mask_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_hw_constraints *constrs = + &substream->runtime->hw_constraints; + struct snd_mask *m; + unsigned int k; + struct snd_mask old_mask; + int changed; + + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) { + m = hw_param_mask(params, k); + if (snd_mask_empty(m)) + return -EINVAL; + + /* This parameter is not requested to change by a caller. */ + if (!(params->rmask & (1 << k))) + continue; + + if (trace_hw_mask_param_enabled()) + old_mask = *m; + + changed = snd_mask_refine(m, constrs_mask(constrs, k)); + if (changed < 0) + return changed; + if (changed == 0) + continue; + + /* Set corresponding flag so that the caller gets it. */ + trace_hw_mask_param(substream, k, 0, &old_mask, m); + params->cmask |= 1 << k; + } + + return 0; +} + +static int constrain_interval_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_hw_constraints *constrs = + &substream->runtime->hw_constraints; + struct snd_interval *i; + unsigned int k; + struct snd_interval old_interval; + int changed; + + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) { + i = hw_param_interval(params, k); + if (snd_interval_empty(i)) + return -EINVAL; + + /* This parameter is not requested to change by a caller. */ + if (!(params->rmask & (1 << k))) + continue; + + if (trace_hw_interval_param_enabled()) + old_interval = *i; + + changed = snd_interval_refine(i, constrs_interval(constrs, k)); + if (changed < 0) + return changed; + if (changed == 0) + continue; + + /* Set corresponding flag so that the caller gets it. */ + trace_hw_interval_param(substream, k, 0, &old_interval, i); + params->cmask |= 1 << k; + } + + return 0; +} + +static int constrain_params_by_rules(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_hw_constraints *constrs = + &substream->runtime->hw_constraints; + unsigned int k; + unsigned int *rstamps; + unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1]; + unsigned int stamp; + struct snd_pcm_hw_rule *r; + unsigned int d; + struct snd_mask old_mask; + struct snd_interval old_interval; + bool again; + int changed, err = 0; + + /* + * Each application of rule has own sequence number. + * + * Each member of 'rstamps' array represents the sequence number of + * recent application of corresponding rule. + */ + rstamps = kcalloc(constrs->rules_num, sizeof(unsigned int), GFP_KERNEL); + if (!rstamps) + return -ENOMEM; + + /* + * Each member of 'vstamps' array represents the sequence number of + * recent application of rule in which corresponding parameters were + * changed. + * + * In initial state, elements corresponding to parameters requested by + * a caller is 1. For unrequested parameters, corresponding members + * have 0 so that the parameters are never changed anymore. + */ + for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) + vstamps[k] = (params->rmask & (1 << k)) ? 1 : 0; + + /* Due to the above design, actual sequence number starts at 2. */ + stamp = 2; +retry: + /* Apply all rules in order. */ + again = false; + for (k = 0; k < constrs->rules_num; k++) { + r = &constrs->rules[k]; + + /* + * Check condition bits of this rule. When the rule has + * some condition bits, parameter without the bits is + * never processed. SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP + * is an example of the condition bits. + */ + if (r->cond && !(r->cond & params->flags)) + continue; + + /* + * The 'deps' array includes maximum three dependencies + * to SNDRV_PCM_HW_PARAM_XXXs for this rule. The fourth + * member of this array is a sentinel and should be + * negative value. + * + * This rule should be processed in this time when dependent + * parameters were changed at former applications of the other + * rules. + */ + for (d = 0; r->deps[d] >= 0; d++) { + if (vstamps[r->deps[d]] > rstamps[k]) + break; + } + if (r->deps[d] < 0) + continue; + + if (trace_hw_mask_param_enabled()) { + if (hw_is_mask(r->var)) + old_mask = *hw_param_mask(params, r->var); + } + if (trace_hw_interval_param_enabled()) { + if (hw_is_interval(r->var)) + old_interval = *hw_param_interval(params, r->var); + } + + changed = r->func(params, r); + if (changed < 0) { + err = changed; + goto out; + } + + /* + * When the parameter is changed, notify it to the caller + * by corresponding returned bit, then preparing for next + * iteration. + */ + if (changed && r->var >= 0) { + if (hw_is_mask(r->var)) { + trace_hw_mask_param(substream, r->var, + k + 1, &old_mask, + hw_param_mask(params, r->var)); + } + if (hw_is_interval(r->var)) { + trace_hw_interval_param(substream, r->var, + k + 1, &old_interval, + hw_param_interval(params, r->var)); + } + + params->cmask |= (1 << r->var); + vstamps[r->var] = stamp; + again = true; + } + + rstamps[k] = stamp++; + } + + /* Iterate to evaluate all rules till no parameters are changed. */ + if (again) + goto retry; + + out: + kfree(rstamps); + return err; +} + +static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + const struct snd_interval *i; + const struct snd_mask *m; + int err; + + if (!params->msbits) { + i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + if (snd_interval_single(i)) + params->msbits = snd_interval_value(i); + } + + if (!params->rate_den) { + i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + if (snd_interval_single(i)) { + params->rate_num = snd_interval_value(i); + params->rate_den = 1; + } + } + + if (!params->fifo_size) { + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (snd_mask_single(m) && snd_interval_single(i)) { + err = substream->ops->ioctl(substream, + SNDRV_PCM_IOCTL1_FIFO_SIZE, params); + if (err < 0) + return err; + } + } + + if (!params->info) { + params->info = substream->runtime->hw.info; + params->info &= ~(SNDRV_PCM_INFO_FIFO_IN_FRAMES | + SNDRV_PCM_INFO_DRAIN_TRIGGER); + if (!hw_support_mmap(substream)) + params->info &= ~(SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID); + } + + return 0; +} + +int snd_pcm_hw_refine(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int err; + + params->info = 0; + params->fifo_size = 0; + if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_SAMPLE_BITS)) + params->msbits = 0; + if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_RATE)) { + params->rate_num = 0; + params->rate_den = 0; + } + + err = constrain_mask_params(substream, params); + if (err < 0) + return err; + + err = constrain_interval_params(substream, params); + if (err < 0) + return err; + + err = constrain_params_by_rules(substream, params); + if (err < 0) + return err; + + params->rmask = 0; + + return 0; +} +EXPORT_SYMBOL(snd_pcm_hw_refine); + +static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + struct snd_pcm_hw_params *params; + int err; + + params = memdup_user(_params, sizeof(*params)); + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto end; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + goto end; + + if (copy_to_user(_params, params, sizeof(*params))) + err = -EFAULT; +end: + kfree(params); + return err; +} + +static int period_to_usecs(struct snd_pcm_runtime *runtime) +{ + int usecs; + + if (! runtime->rate) + return -1; /* invalid */ + + /* take 75% of period time as the deadline */ + usecs = (750000 / runtime->rate) * runtime->period_size; + usecs += ((750000 % runtime->rate) * runtime->period_size) / + runtime->rate; + + return usecs; +} + +static void snd_pcm_set_state(struct snd_pcm_substream *substream, int state) +{ + snd_pcm_stream_lock_irq(substream); + if (substream->runtime->status->state != SNDRV_PCM_STATE_DISCONNECTED) + substream->runtime->status->state = state; + snd_pcm_stream_unlock_irq(substream); +} + +static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream, + int event) +{ +#ifdef CONFIG_SND_PCM_TIMER + if (substream->timer) + snd_timer_notify(substream->timer, event, + &substream->runtime->trigger_tstamp); +#endif +} + +/** + * snd_pcm_hw_param_choose - choose a configuration defined by @params + * @pcm: PCM instance + * @params: the hw_params instance + * + * Choose one configuration from configuration space defined by @params. + * The configuration chosen is that obtained fixing in this order: + * first access, first format, first subformat, min channels, + * min rate, min period time, max buffer size, min tick time + * + * Return: Zero if successful, or a negative error code on failure. + */ +static int snd_pcm_hw_params_choose(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params) +{ + static const int vars[] = { + SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + SNDRV_PCM_HW_PARAM_TICK_TIME, + -1 + }; + const int *v; + struct snd_mask old_mask; + struct snd_interval old_interval; + int changed; + + for (v = vars; *v != -1; v++) { + /* Keep old parameter to trace. */ + if (trace_hw_mask_param_enabled()) { + if (hw_is_mask(*v)) + old_mask = *hw_param_mask(params, *v); + } + if (trace_hw_interval_param_enabled()) { + if (hw_is_interval(*v)) + old_interval = *hw_param_interval(params, *v); + } + if (*v != SNDRV_PCM_HW_PARAM_BUFFER_SIZE) + changed = snd_pcm_hw_param_first(pcm, params, *v, NULL); + else + changed = snd_pcm_hw_param_last(pcm, params, *v, NULL); + if (changed < 0) + return changed; + if (changed == 0) + continue; + + /* Trace the changed parameter. */ + if (hw_is_mask(*v)) { + trace_hw_mask_param(pcm, *v, 0, &old_mask, + hw_param_mask(params, *v)); + } + if (hw_is_interval(*v)) { + trace_hw_interval_param(pcm, *v, 0, &old_interval, + hw_param_interval(params, *v)); + } + } + + return 0; +} + +/* acquire buffer_mutex; if it's in r/w operation, return -EBUSY, otherwise + * block the further r/w operations + */ +static int snd_pcm_buffer_access_lock(struct snd_pcm_runtime *runtime) +{ + if (!atomic_dec_unless_positive(&runtime->buffer_accessing)) + return -EBUSY; + mutex_lock(&runtime->buffer_mutex); + return 0; /* keep buffer_mutex, unlocked by below */ +} + +/* release buffer_mutex and clear r/w access flag */ +static void snd_pcm_buffer_access_unlock(struct snd_pcm_runtime *runtime) +{ + mutex_unlock(&runtime->buffer_mutex); + atomic_inc(&runtime->buffer_accessing); +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#define is_oss_stream(substream) ((substream)->oss.oss) +#else +#define is_oss_stream(substream) false +#endif + +static int snd_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err, usecs; + unsigned int bits; + snd_pcm_uframes_t frames; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + err = snd_pcm_buffer_access_lock(runtime); + if (err < 0) + return err; + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + if (!is_oss_stream(substream) && + atomic_read(&substream->mmap_count)) + err = -EBADFD; + break; + default: + err = -EBADFD; + break; + } + snd_pcm_stream_unlock_irq(substream); + if (err) + goto unlock; + + params->rmask = ~0U; + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto _error; + + err = snd_pcm_hw_params_choose(substream, params); + if (err < 0) + goto _error; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + goto _error; + + if (substream->ops->hw_params != NULL) { + err = substream->ops->hw_params(substream, params); + if (err < 0) + goto _error; + } + + runtime->access = params_access(params); + runtime->format = params_format(params); + runtime->subformat = params_subformat(params); + runtime->channels = params_channels(params); + runtime->rate = params_rate(params); + runtime->period_size = params_period_size(params); + runtime->periods = params_periods(params); + runtime->buffer_size = params_buffer_size(params); + runtime->info = params->info; + runtime->rate_num = params->rate_num; + runtime->rate_den = params->rate_den; + runtime->no_period_wakeup = + (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) && + (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP); + + bits = snd_pcm_format_physical_width(runtime->format); + runtime->sample_bits = bits; + bits *= runtime->channels; + runtime->frame_bits = bits; + frames = 1; + while (bits % 8 != 0) { + bits *= 2; + frames *= 2; + } + runtime->byte_align = bits / 8; + runtime->min_align = frames; + + /* Default sw params */ + runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; + runtime->period_step = 1; + runtime->control->avail_min = runtime->period_size; + runtime->start_threshold = 1; + runtime->stop_threshold = runtime->buffer_size; + runtime->silence_threshold = 0; + runtime->silence_size = 0; + runtime->boundary = runtime->buffer_size; + while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size) + runtime->boundary *= 2; + + /* clear the buffer for avoiding possible kernel info leaks */ + if (runtime->dma_area && !substream->ops->copy_user) { + size_t size = runtime->dma_bytes; + + if (runtime->info & SNDRV_PCM_INFO_MMAP) + size = PAGE_ALIGN(size); + memset(runtime->dma_area, 0, size); + } + + snd_pcm_timer_resolution_change(substream); + snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP); + + if (pm_qos_request_active(&substream->latency_pm_qos_req)) + pm_qos_remove_request(&substream->latency_pm_qos_req); + if ((usecs = period_to_usecs(runtime)) >= 0) + pm_qos_add_request(&substream->latency_pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, usecs); + err = 0; + _error: + if (err) { + /* hardware might be unusable from this time, + * so we force application to retry to set + * the correct hardware parameter settings + */ + snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); + if (substream->ops->hw_free != NULL) + substream->ops->hw_free(substream); + } + unlock: + snd_pcm_buffer_access_unlock(runtime); + return err; +} + +static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + struct snd_pcm_hw_params *params; + int err; + + params = memdup_user(_params, sizeof(*params)); + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_params(substream, params); + if (err < 0) + goto end; + + if (copy_to_user(_params, params, sizeof(*params))) + err = -EFAULT; +end: + kfree(params); + return err; +} + +static int snd_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int result = 0; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + result = snd_pcm_buffer_access_lock(runtime); + if (result < 0) + return result; + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + if (atomic_read(&substream->mmap_count)) + result = -EBADFD; + break; + default: + result = -EBADFD; + break; + } + snd_pcm_stream_unlock_irq(substream); + if (result) + goto unlock; + if (substream->ops->hw_free) + result = substream->ops->hw_free(substream); + snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); + pm_qos_remove_request(&substream->latency_pm_qos_req); + unlock: + snd_pcm_buffer_access_unlock(runtime); + return result; +} + +static int snd_pcm_sw_params(struct snd_pcm_substream *substream, + struct snd_pcm_sw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + snd_pcm_stream_lock_irq(substream); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + + if (params->tstamp_mode < 0 || + params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST) + return -EINVAL; + if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12) && + params->tstamp_type > SNDRV_PCM_TSTAMP_TYPE_LAST) + return -EINVAL; + if (params->avail_min == 0) + return -EINVAL; + if (params->silence_size >= runtime->boundary) { + if (params->silence_threshold != 0) + return -EINVAL; + } else { + if (params->silence_size > params->silence_threshold) + return -EINVAL; + if (params->silence_threshold > runtime->buffer_size) + return -EINVAL; + } + err = 0; + snd_pcm_stream_lock_irq(substream); + runtime->tstamp_mode = params->tstamp_mode; + if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12)) + runtime->tstamp_type = params->tstamp_type; + runtime->period_step = params->period_step; + runtime->control->avail_min = params->avail_min; + runtime->start_threshold = params->start_threshold; + runtime->stop_threshold = params->stop_threshold; + runtime->silence_threshold = params->silence_threshold; + runtime->silence_size = params->silence_size; + params->boundary = runtime->boundary; + if (snd_pcm_running(substream)) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + err = snd_pcm_update_state(substream, runtime); + } + snd_pcm_stream_unlock_irq(substream); + return err; +} + +static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream, + struct snd_pcm_sw_params __user * _params) +{ + struct snd_pcm_sw_params params; + int err; + if (copy_from_user(¶ms, _params, sizeof(params))) + return -EFAULT; + err = snd_pcm_sw_params(substream, ¶ms); + if (copy_to_user(_params, ¶ms, sizeof(params))) + return -EFAULT; + return err; +} + +static inline snd_pcm_uframes_t +snd_pcm_calc_delay(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = snd_pcm_playback_hw_avail(substream->runtime); + else + delay = snd_pcm_capture_avail(substream->runtime); + return delay + substream->runtime->delay; +} + +int snd_pcm_status(struct snd_pcm_substream *substream, + struct snd_pcm_status *status) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_stream_lock_irq(substream); + + snd_pcm_unpack_audio_tstamp_config(status->audio_tstamp_data, + &runtime->audio_tstamp_config); + + /* backwards compatible behavior */ + if (runtime->audio_tstamp_config.type_requested == + SNDRV_PCM_AUDIO_TSTAMP_TYPE_COMPAT) { + if (runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK) + runtime->audio_tstamp_config.type_requested = + SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK; + else + runtime->audio_tstamp_config.type_requested = + SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT; + runtime->audio_tstamp_report.valid = 0; + } else + runtime->audio_tstamp_report.valid = 1; + + status->state = runtime->status->state; + status->suspended_state = runtime->status->suspended_state; + if (status->state == SNDRV_PCM_STATE_OPEN) + goto _end; + status->trigger_tstamp = runtime->trigger_tstamp; + if (snd_pcm_running(substream)) { + snd_pcm_update_hw_ptr(substream); + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { + status->tstamp = runtime->status->tstamp; + status->driver_tstamp = runtime->driver_tstamp; + status->audio_tstamp = + runtime->status->audio_tstamp; + if (runtime->audio_tstamp_report.valid == 1) + /* backwards compatibility, no report provided in COMPAT mode */ + snd_pcm_pack_audio_tstamp_report(&status->audio_tstamp_data, + &status->audio_tstamp_accuracy, + &runtime->audio_tstamp_report); + + goto _tstamp_end; + } + } else { + /* get tstamp only in fallback mode and only if enabled */ + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) + snd_pcm_gettime(runtime, &status->tstamp); + } + _tstamp_end: + status->appl_ptr = runtime->control->appl_ptr; + status->hw_ptr = runtime->status->hw_ptr; + status->avail = snd_pcm_avail(substream); + status->delay = snd_pcm_running(substream) ? + snd_pcm_calc_delay(substream) : 0; + status->avail_max = runtime->avail_max; + status->overrange = runtime->overrange; + runtime->avail_max = 0; + runtime->overrange = 0; + _end: + snd_pcm_stream_unlock_irq(substream); + return 0; +} + +static int snd_pcm_status_user(struct snd_pcm_substream *substream, + struct snd_pcm_status __user * _status, + bool ext) +{ + struct snd_pcm_status status; + int res; + + memset(&status, 0, sizeof(status)); + /* + * with extension, parameters are read/write, + * get audio_tstamp_data from user, + * ignore rest of status structure + */ + if (ext && get_user(status.audio_tstamp_data, + (u32 __user *)(&_status->audio_tstamp_data))) + return -EFAULT; + res = snd_pcm_status(substream, &status); + if (res < 0) + return res; + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +static int snd_pcm_channel_info(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info * info) +{ + struct snd_pcm_runtime *runtime; + unsigned int channel; + + channel = info->channel; + runtime = substream->runtime; + snd_pcm_stream_lock_irq(substream); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + if (channel >= runtime->channels) + return -EINVAL; + memset(info, 0, sizeof(*info)); + info->channel = channel; + return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info); +} + +static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info __user * _info) +{ + struct snd_pcm_channel_info info; + int res; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + res = snd_pcm_channel_info(substream, &info); + if (res < 0) + return res; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->trigger_master == NULL) + return; + if (runtime->trigger_master == substream) { + if (!runtime->trigger_tstamp_latched) + snd_pcm_gettime(runtime, &runtime->trigger_tstamp); + } else { + snd_pcm_trigger_tstamp(runtime->trigger_master); + runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp; + } + runtime->trigger_master = NULL; +} + +struct action_ops { + int (*pre_action)(struct snd_pcm_substream *substream, int state); + int (*do_action)(struct snd_pcm_substream *substream, int state); + void (*undo_action)(struct snd_pcm_substream *substream, int state); + void (*post_action)(struct snd_pcm_substream *substream, int state); +}; + +/* + * this functions is core for handling of linked stream + * Note: the stream state might be changed also on failure + * Note2: call with calling stream lock + link lock + */ +static int snd_pcm_action_group(const struct action_ops *ops, + struct snd_pcm_substream *substream, + int state, int stream_lock) +{ + struct snd_pcm_substream *s = NULL; + struct snd_pcm_substream *s1; + int res = 0, depth = 1; + + snd_pcm_group_for_each_entry(s, substream) { + if (s != substream) { + if (!stream_lock) + mutex_lock_nested(&s->runtime->buffer_mutex, depth); + else if (s->pcm->nonatomic) + mutex_lock_nested(&s->self_group.mutex, depth); + else + spin_lock_nested(&s->self_group.lock, depth); + depth++; + } + res = ops->pre_action(s, state); + if (res < 0) + goto _unlock; + } + snd_pcm_group_for_each_entry(s, substream) { + res = ops->do_action(s, state); + if (res < 0) { + if (ops->undo_action) { + snd_pcm_group_for_each_entry(s1, substream) { + if (s1 == s) /* failed stream */ + break; + ops->undo_action(s1, state); + } + } + s = NULL; /* unlock all */ + goto _unlock; + } + } + snd_pcm_group_for_each_entry(s, substream) { + ops->post_action(s, state); + } + _unlock: + /* unlock streams */ + snd_pcm_group_for_each_entry(s1, substream) { + if (s1 != substream) { + if (!stream_lock) + mutex_unlock(&s1->runtime->buffer_mutex); + else if (s1->pcm->nonatomic) + mutex_unlock(&s1->self_group.mutex); + else + spin_unlock(&s1->self_group.lock); + } + if (s1 == s) /* end */ + break; + } + return res; +} + +/* + * Note: call with stream lock + */ +static int snd_pcm_action_single(const struct action_ops *ops, + struct snd_pcm_substream *substream, + int state) +{ + int res; + + res = ops->pre_action(substream, state); + if (res < 0) + return res; + res = ops->do_action(substream, state); + if (res == 0) + ops->post_action(substream, state); + else if (ops->undo_action) + ops->undo_action(substream, state); + return res; +} + +/* + * Note: call with stream lock + */ +static int snd_pcm_action(const struct action_ops *ops, + struct snd_pcm_substream *substream, + int state) +{ + int res; + + if (!snd_pcm_stream_linked(substream)) + return snd_pcm_action_single(ops, substream, state); + + if (substream->pcm->nonatomic) { + if (!mutex_trylock(&substream->group->mutex)) { + mutex_unlock(&substream->self_group.mutex); + mutex_lock(&substream->group->mutex); + mutex_lock(&substream->self_group.mutex); + } + res = snd_pcm_action_group(ops, substream, state, 1); + mutex_unlock(&substream->group->mutex); + } else { + if (!spin_trylock(&substream->group->lock)) { + spin_unlock(&substream->self_group.lock); + spin_lock(&substream->group->lock); + spin_lock(&substream->self_group.lock); + } + res = snd_pcm_action_group(ops, substream, state, 1); + spin_unlock(&substream->group->lock); + } + return res; +} + +/* + * Note: don't use any locks before + */ +static int snd_pcm_action_lock_irq(const struct action_ops *ops, + struct snd_pcm_substream *substream, + int state) +{ + int res; + + snd_pcm_stream_lock_irq(substream); + res = snd_pcm_action(ops, substream, state); + snd_pcm_stream_unlock_irq(substream); + return res; +} + +/* + */ +static int snd_pcm_action_nonatomic(const struct action_ops *ops, + struct snd_pcm_substream *substream, + int state) +{ + int res; + + down_read(&snd_pcm_link_rwsem); + res = snd_pcm_buffer_access_lock(substream->runtime); + if (res < 0) + goto unlock; + if (snd_pcm_stream_linked(substream)) + res = snd_pcm_action_group(ops, substream, state, 0); + else + res = snd_pcm_action_single(ops, substream, state); + snd_pcm_buffer_access_unlock(substream->runtime); + unlock: + up_read(&snd_pcm_link_rwsem); + return res; +} + +/* + * start callbacks + */ +static int snd_pcm_pre_start(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->status->state != SNDRV_PCM_STATE_PREPARED) + return -EBADFD; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !snd_pcm_playback_data(substream)) + return -EPIPE; + runtime->trigger_tstamp_latched = false; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state) +{ + if (substream->runtime->trigger_master != substream) + return 0; + return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); +} + +static void snd_pcm_undo_start(struct snd_pcm_substream *substream, int state) +{ + if (substream->runtime->trigger_master == substream) + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); +} + +static void snd_pcm_post_start(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + runtime->hw_ptr_jiffies = jiffies; + runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / + runtime->rate; + runtime->status->state = state; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART); +} + +static const struct action_ops snd_pcm_action_start = { + .pre_action = snd_pcm_pre_start, + .do_action = snd_pcm_do_start, + .undo_action = snd_pcm_undo_start, + .post_action = snd_pcm_post_start +}; + +/** + * snd_pcm_start - start all linked streams + * @substream: the PCM substream instance + * + * Return: Zero if successful, or a negative error code. + * The stream lock must be acquired before calling this function. + */ +int snd_pcm_start(struct snd_pcm_substream *substream) +{ + return snd_pcm_action(&snd_pcm_action_start, substream, + SNDRV_PCM_STATE_RUNNING); +} + +/* take the stream lock and start the streams */ +static int snd_pcm_start_lock_irq(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, + SNDRV_PCM_STATE_RUNNING); +} + +/* + * stop callbacks + */ +static int snd_pcm_pre_stop(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_stop(struct snd_pcm_substream *substream, int state) +{ + if (substream->runtime->trigger_master == substream && + snd_pcm_running(substream)) + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); + return 0; /* unconditonally stop all substreams */ +} + +static void snd_pcm_post_stop(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->status->state != state) { + snd_pcm_trigger_tstamp(substream); + runtime->status->state = state; + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP); + } + wake_up(&runtime->sleep); + wake_up(&runtime->tsleep); +} + +static const struct action_ops snd_pcm_action_stop = { + .pre_action = snd_pcm_pre_stop, + .do_action = snd_pcm_do_stop, + .post_action = snd_pcm_post_stop +}; + +/** + * snd_pcm_stop - try to stop all running streams in the substream group + * @substream: the PCM substream instance + * @state: PCM state after stopping the stream + * + * The state of each stream is then changed to the given state unconditionally. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_pcm_stop(struct snd_pcm_substream *substream, snd_pcm_state_t state) +{ + return snd_pcm_action(&snd_pcm_action_stop, substream, state); +} +EXPORT_SYMBOL(snd_pcm_stop); + +/** + * snd_pcm_drain_done - stop the DMA only when the given stream is playback + * @substream: the PCM substream + * + * After stopping, the state is changed to SETUP. + * Unlike snd_pcm_stop(), this affects only the given stream. + * + * Return: Zero if succesful, or a negative error code. + */ +int snd_pcm_drain_done(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_single(&snd_pcm_action_stop, substream, + SNDRV_PCM_STATE_SETUP); +} + +/** + * snd_pcm_stop_xrun - stop the running streams as XRUN + * @substream: the PCM substream instance + * + * This stops the given running substream (and all linked substreams) as XRUN. + * Unlike snd_pcm_stop(), this function takes the substream lock by itself. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_pcm_stop_xrun(struct snd_pcm_substream *substream) +{ + unsigned long flags; + + snd_pcm_stream_lock_irqsave(substream, flags); + if (substream->runtime && snd_pcm_running(substream)) + __snd_pcm_xrun(substream); + snd_pcm_stream_unlock_irqrestore(substream, flags); + return 0; +} +EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); + +/* + * pause callbacks + */ +static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, int push) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_PAUSE)) + return -ENOSYS; + if (push) { + if (runtime->status->state != SNDRV_PCM_STATE_RUNNING) + return -EBADFD; + } else if (runtime->status->state != SNDRV_PCM_STATE_PAUSED) + return -EBADFD; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_pause(struct snd_pcm_substream *substream, int push) +{ + if (substream->runtime->trigger_master != substream) + return 0; + /* some drivers might use hw_ptr to recover from the pause - + update the hw_ptr now */ + if (push) + snd_pcm_update_hw_ptr(substream); + /* The jiffies check in snd_pcm_update_hw_ptr*() is done by + * a delta between the current jiffies, this gives a large enough + * delta, effectively to skip the check once. + */ + substream->runtime->hw_ptr_jiffies = jiffies - HZ * 1000; + return substream->ops->trigger(substream, + push ? SNDRV_PCM_TRIGGER_PAUSE_PUSH : + SNDRV_PCM_TRIGGER_PAUSE_RELEASE); +} + +static void snd_pcm_undo_pause(struct snd_pcm_substream *substream, int push) +{ + if (substream->runtime->trigger_master == substream) + substream->ops->trigger(substream, + push ? SNDRV_PCM_TRIGGER_PAUSE_RELEASE : + SNDRV_PCM_TRIGGER_PAUSE_PUSH); +} + +static void snd_pcm_post_pause(struct snd_pcm_substream *substream, int push) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + if (push) { + runtime->status->state = SNDRV_PCM_STATE_PAUSED; + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MPAUSE); + wake_up(&runtime->sleep); + wake_up(&runtime->tsleep); + } else { + runtime->status->state = SNDRV_PCM_STATE_RUNNING; + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MCONTINUE); + } +} + +static const struct action_ops snd_pcm_action_pause = { + .pre_action = snd_pcm_pre_pause, + .do_action = snd_pcm_do_pause, + .undo_action = snd_pcm_undo_pause, + .post_action = snd_pcm_post_pause +}; + +/* + * Push/release the pause for all linked streams. + */ +static int snd_pcm_pause(struct snd_pcm_substream *substream, int push) +{ + return snd_pcm_action(&snd_pcm_action_pause, substream, push); +} + +#ifdef CONFIG_PM +/* suspend */ + +static int snd_pcm_pre_suspend(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->status->state) { + case SNDRV_PCM_STATE_SUSPENDED: + return -EBUSY; + /* unresumable PCM state; return -EBUSY for skipping suspend */ + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_DISCONNECTED: + return -EBUSY; + } + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_suspend(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->trigger_master != substream) + return 0; + if (! snd_pcm_running(substream)) + return 0; + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND); + return 0; /* suspend unconditionally */ +} + +static void snd_pcm_post_suspend(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + runtime->status->suspended_state = runtime->status->state; + runtime->status->state = SNDRV_PCM_STATE_SUSPENDED; + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSUSPEND); + wake_up(&runtime->sleep); + wake_up(&runtime->tsleep); +} + +static const struct action_ops snd_pcm_action_suspend = { + .pre_action = snd_pcm_pre_suspend, + .do_action = snd_pcm_do_suspend, + .post_action = snd_pcm_post_suspend +}; + +/** + * snd_pcm_suspend - trigger SUSPEND to all linked streams + * @substream: the PCM substream + * + * After this call, all streams are changed to SUSPENDED state. + * + * Return: Zero if successful (or @substream is %NULL), or a negative error + * code. + */ +int snd_pcm_suspend(struct snd_pcm_substream *substream) +{ + int err; + unsigned long flags; + + if (! substream) + return 0; + + snd_pcm_stream_lock_irqsave(substream, flags); + err = snd_pcm_action(&snd_pcm_action_suspend, substream, 0); + snd_pcm_stream_unlock_irqrestore(substream, flags); + return err; +} +EXPORT_SYMBOL(snd_pcm_suspend); + +/** + * snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm + * @pcm: the PCM instance + * + * After this call, all streams are changed to SUSPENDED state. + * + * Return: Zero if successful (or @pcm is %NULL), or a negative error code. + */ +int snd_pcm_suspend_all(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int stream, err = 0; + + if (! pcm) + return 0; + + for (stream = 0; stream < 2; stream++) { + for (substream = pcm->streams[stream].substream; + substream; substream = substream->next) { + /* FIXME: the open/close code should lock this as well */ + if (substream->runtime == NULL) + continue; + + /* + * Skip BE dai link PCM's that are internal and may + * not have their substream ops set. + */ + if (!substream->ops) + continue; + + err = snd_pcm_suspend(substream); + if (err < 0 && err != -EBUSY) + return err; + } + } + return 0; +} +EXPORT_SYMBOL(snd_pcm_suspend_all); + +/* resume */ + +static int snd_pcm_pre_resume(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_RESUME)) + return -ENOSYS; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_resume(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->trigger_master != substream) + return 0; + /* DMA not running previously? */ + if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING && + (runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING || + substream->stream != SNDRV_PCM_STREAM_PLAYBACK)) + return 0; + return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME); +} + +static void snd_pcm_undo_resume(struct snd_pcm_substream *substream, int state) +{ + if (substream->runtime->trigger_master == substream && + snd_pcm_running(substream)) + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND); +} + +static void snd_pcm_post_resume(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + runtime->status->state = runtime->status->suspended_state; + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME); +} + +static const struct action_ops snd_pcm_action_resume = { + .pre_action = snd_pcm_pre_resume, + .do_action = snd_pcm_do_resume, + .undo_action = snd_pcm_undo_resume, + .post_action = snd_pcm_post_resume +}; + +static int snd_pcm_resume(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, 0); +} + +#else + +static int snd_pcm_resume(struct snd_pcm_substream *substream) +{ + return -ENOSYS; +} + +#endif /* CONFIG_PM */ + +/* + * xrun ioctl + * + * Change the RUNNING stream(s) to XRUN state. + */ +static int snd_pcm_xrun(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int result; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_XRUN: + result = 0; /* already there */ + break; + case SNDRV_PCM_STATE_RUNNING: + __snd_pcm_xrun(substream); + result = 0; + break; + default: + result = -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + return result; +} + +/* + * reset ioctl + */ +static int snd_pcm_pre_reset(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->status->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + case SNDRV_PCM_STATE_SUSPENDED: + return 0; + default: + return -EBADFD; + } +} + +static int snd_pcm_do_reset(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL); + if (err < 0) + return err; + snd_pcm_stream_lock_irq(substream); + runtime->hw_ptr_base = 0; + runtime->hw_ptr_interrupt = runtime->status->hw_ptr - + runtime->status->hw_ptr % runtime->period_size; + runtime->silence_start = runtime->status->hw_ptr; + runtime->silence_filled = 0; + snd_pcm_stream_unlock_irq(substream); + return 0; +} + +static void snd_pcm_post_reset(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_stream_lock_irq(substream); + runtime->control->appl_ptr = runtime->status->hw_ptr; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + snd_pcm_stream_unlock_irq(substream); +} + +static const struct action_ops snd_pcm_action_reset = { + .pre_action = snd_pcm_pre_reset, + .do_action = snd_pcm_do_reset, + .post_action = snd_pcm_post_reset +}; + +static int snd_pcm_reset(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, 0); +} + +/* + * prepare ioctl + */ +/* we use the second argument for updating f_flags */ +static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, + int f_flags) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN || + runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + if (snd_pcm_running(substream)) + return -EBUSY; + substream->f_flags = f_flags; + return 0; +} + +static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, int state) +{ + int err; + err = substream->ops->prepare(substream); + if (err < 0) + return err; + return snd_pcm_do_reset(substream, 0); +} + +static void snd_pcm_post_prepare(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + runtime->control->appl_ptr = runtime->status->hw_ptr; + snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); +} + +static const struct action_ops snd_pcm_action_prepare = { + .pre_action = snd_pcm_pre_prepare, + .do_action = snd_pcm_do_prepare, + .post_action = snd_pcm_post_prepare +}; + +/** + * snd_pcm_prepare - prepare the PCM substream to be triggerable + * @substream: the PCM substream instance + * @file: file to refer f_flags + * + * Return: Zero if successful, or a negative error code. + */ +static int snd_pcm_prepare(struct snd_pcm_substream *substream, + struct file *file) +{ + int f_flags; + + if (file) + f_flags = file->f_flags; + else + f_flags = substream->f_flags; + + snd_pcm_stream_lock_irq(substream); + switch (substream->runtime->status->state) { + case SNDRV_PCM_STATE_PAUSED: + snd_pcm_pause(substream, 0); + /* fallthru */ + case SNDRV_PCM_STATE_SUSPENDED: + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + } + snd_pcm_stream_unlock_irq(substream); + + return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, + substream, f_flags); +} + +/* + * drain ioctl + */ + +static int snd_pcm_pre_drain_init(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->status->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_DISCONNECTED: + case SNDRV_PCM_STATE_SUSPENDED: + return -EBADFD; + } + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, int state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + /* start playback stream if possible */ + if (! snd_pcm_playback_empty(substream)) { + snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING); + snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING); + } else { + runtime->status->state = SNDRV_PCM_STATE_SETUP; + } + break; + case SNDRV_PCM_STATE_RUNNING: + runtime->status->state = SNDRV_PCM_STATE_DRAINING; + break; + case SNDRV_PCM_STATE_XRUN: + runtime->status->state = SNDRV_PCM_STATE_SETUP; + break; + default: + break; + } + } else { + /* stop running stream */ + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) { + int new_state = snd_pcm_capture_avail(runtime) > 0 ? + SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; + snd_pcm_do_stop(substream, new_state); + snd_pcm_post_stop(substream, new_state); + } + } + + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING && + runtime->trigger_master == substream && + (runtime->hw.info & SNDRV_PCM_INFO_DRAIN_TRIGGER)) + return substream->ops->trigger(substream, + SNDRV_PCM_TRIGGER_DRAIN); + + return 0; +} + +static void snd_pcm_post_drain_init(struct snd_pcm_substream *substream, int state) +{ +} + +static const struct action_ops snd_pcm_action_drain_init = { + .pre_action = snd_pcm_pre_drain_init, + .do_action = snd_pcm_do_drain_init, + .post_action = snd_pcm_post_drain_init +}; + +static int snd_pcm_drop(struct snd_pcm_substream *substream); + +/* + * Drain the stream(s). + * When the substream is linked, sync until the draining of all playback streams + * is finished. + * After this call, all streams are supposed to be either SETUP or DRAINING + * (capture only) state. + */ +static int snd_pcm_drain(struct snd_pcm_substream *substream, + struct file *file) +{ + struct snd_card *card; + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *s; + wait_queue_entry_t wait; + int result = 0; + int nonblock = 0; + + card = substream->pcm->card; + runtime = substream->runtime; + + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + if (file) { + if (file->f_flags & O_NONBLOCK) + nonblock = 1; + } else if (substream->f_flags & O_NONBLOCK) + nonblock = 1; + + down_read(&snd_pcm_link_rwsem); + snd_pcm_stream_lock_irq(substream); + /* resume pause */ + if (runtime->status->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, 0); + + /* pre-start/stop - all running streams are changed to DRAINING state */ + result = snd_pcm_action(&snd_pcm_action_drain_init, substream, 0); + if (result < 0) + goto unlock; + /* in non-blocking, we don't wait in ioctl but let caller poll */ + if (nonblock) { + result = -EAGAIN; + goto unlock; + } + + for (;;) { + long tout; + struct snd_pcm_runtime *to_check; + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + /* find a substream to drain */ + to_check = NULL; + snd_pcm_group_for_each_entry(s, substream) { + if (s->stream != SNDRV_PCM_STREAM_PLAYBACK) + continue; + runtime = s->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) { + to_check = runtime; + break; + } + } + if (!to_check) + break; /* all drained */ + init_waitqueue_entry(&wait, current); + add_wait_queue(&to_check->sleep, &wait); + snd_pcm_stream_unlock_irq(substream); + up_read(&snd_pcm_link_rwsem); + if (runtime->no_period_wakeup) + tout = MAX_SCHEDULE_TIMEOUT; + else { + tout = 10; + if (runtime->rate) { + long t = runtime->period_size * 2 / runtime->rate; + tout = max(t, tout); + } + tout = msecs_to_jiffies(tout * 1000); + } + tout = schedule_timeout_interruptible(tout); + down_read(&snd_pcm_link_rwsem); + snd_pcm_stream_lock_irq(substream); + remove_wait_queue(&to_check->sleep, &wait); + if (card->shutdown) { + result = -ENODEV; + break; + } + if (tout == 0) { + if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) + result = -ESTRPIPE; + else { + dev_dbg(substream->pcm->card->dev, + "playback drain error (DMA or IRQ trouble?)\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + result = -EIO; + } + break; + } + } + + unlock: + snd_pcm_stream_unlock_irq(substream); + up_read(&snd_pcm_link_rwsem); + + return result; +} + +/* + * drop ioctl + * + * Immediately put all linked substreams into SETUP state. + */ +static int snd_pcm_drop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int result = 0; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + + if (runtime->status->state == SNDRV_PCM_STATE_OPEN || + runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + + snd_pcm_stream_lock_irq(substream); + /* resume pause */ + if (runtime->status->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, 0); + + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + snd_pcm_stream_unlock_irq(substream); + + return result; +} + + +static bool is_pcm_file(struct file *file) +{ + struct inode *inode = file_inode(file); + unsigned int minor; + + if (!S_ISCHR(inode->i_mode) || imajor(inode) != snd_major) + return false; + minor = iminor(inode); + return snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK) || + snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE); +} + +/* + * PCM link handling + */ +static int snd_pcm_link(struct snd_pcm_substream *substream, int fd) +{ + int res = 0; + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream1; + struct snd_pcm_group *group; + struct fd f = fdget(fd); + + if (!f.file) + return -EBADFD; + if (!is_pcm_file(f.file)) { + res = -EBADFD; + goto _badf; + } + pcm_file = f.file->private_data; + substream1 = pcm_file->substream; + if (substream == substream1) { + res = -EINVAL; + goto _badf; + } + + group = kmalloc(sizeof(*group), GFP_KERNEL); + if (!group) { + res = -ENOMEM; + goto _nolock; + } + down_write_nonfifo(&snd_pcm_link_rwsem); + write_lock_irq(&snd_pcm_link_rwlock); + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN || + substream->runtime->status->state != substream1->runtime->status->state || + substream->pcm->nonatomic != substream1->pcm->nonatomic) { + res = -EBADFD; + goto _end; + } + if (snd_pcm_stream_linked(substream1)) { + res = -EALREADY; + goto _end; + } + if (!snd_pcm_stream_linked(substream)) { + substream->group = group; + group = NULL; + spin_lock_init(&substream->group->lock); + mutex_init(&substream->group->mutex); + INIT_LIST_HEAD(&substream->group->substreams); + list_add_tail(&substream->link_list, &substream->group->substreams); + substream->group->count = 1; + } + list_add_tail(&substream1->link_list, &substream->group->substreams); + substream->group->count++; + substream1->group = substream->group; + _end: + write_unlock_irq(&snd_pcm_link_rwlock); + up_write(&snd_pcm_link_rwsem); + _nolock: + snd_card_unref(substream1->pcm->card); + kfree(group); + _badf: + fdput(f); + return res; +} + +static void relink_to_local(struct snd_pcm_substream *substream) +{ + substream->group = &substream->self_group; + INIT_LIST_HEAD(&substream->self_group.substreams); + list_add_tail(&substream->link_list, &substream->self_group.substreams); +} + +static int snd_pcm_unlink(struct snd_pcm_substream *substream) +{ + struct snd_pcm_substream *s; + int res = 0; + + down_write_nonfifo(&snd_pcm_link_rwsem); + write_lock_irq(&snd_pcm_link_rwlock); + if (!snd_pcm_stream_linked(substream)) { + res = -EALREADY; + goto _end; + } + list_del(&substream->link_list); + substream->group->count--; + if (substream->group->count == 1) { /* detach the last stream, too */ + snd_pcm_group_for_each_entry(s, substream) { + relink_to_local(s); + break; + } + kfree(substream->group); + } + relink_to_local(substream); + _end: + write_unlock_irq(&snd_pcm_link_rwlock); + up_write(&snd_pcm_link_rwsem); + return res; +} + +/* + * hw configurator + */ +static int snd_pcm_hw_rule_mul(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_mul(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_div(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_div(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_muldivk(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_muldivk(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), + (unsigned long) rule->private, &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_mulkdiv(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]), + (unsigned long) rule->private, + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int k; + const struct snd_interval *i = + hw_param_interval_c(params, rule->deps[0]); + struct snd_mask m; + struct snd_mask *mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_any(&m); + for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) { + int bits; + if (! snd_mask_test(mask, k)) + continue; + bits = snd_pcm_format_physical_width(k); + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) + snd_mask_reset(&m, k); + } + return snd_mask_refine(mask, &m); +} + +static int snd_pcm_hw_rule_sample_bits(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + unsigned int k; + t.min = UINT_MAX; + t.max = 0; + t.openmin = 0; + t.openmax = 0; + for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) { + int bits; + if (! snd_mask_test(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), k)) + continue; + bits = snd_pcm_format_physical_width(k); + if (bits <= 0) + continue; /* ignore invalid formats */ + if (t.min > (unsigned)bits) + t.min = bits; + if (t.max < (unsigned)bits) + t.max = bits; + } + t.integer = 1; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12 +#error "Change this table" +#endif + +static const unsigned int rates[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, + 48000, 64000, 88200, 96000, 176400, 192000 +}; + +const struct snd_pcm_hw_constraint_list snd_pcm_known_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, +}; + +static int snd_pcm_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_hardware *hw = rule->private; + return snd_interval_list(hw_param_interval(params, rule->var), + snd_pcm_known_rates.count, + snd_pcm_known_rates.list, hw->rates); +} + +static int snd_pcm_hw_rule_buffer_bytes_max(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + struct snd_pcm_substream *substream = rule->private; + t.min = 0; + t.max = substream->buffer_bytes_max; + t.openmin = 0; + t.openmax = 0; + t.integer = 1; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + int k, err; + + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) { + snd_mask_any(constrs_mask(constrs, k)); + } + + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) { + snd_interval_any(constrs_interval(constrs, k)); + } + + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS)); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + snd_pcm_hw_rule_format, NULL, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + snd_pcm_hw_rule_sample_bits, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mul, NULL, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_TIME, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_BUFFER_TIME, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_muldivk, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_mul, NULL, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_muldivk, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_TIME, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + snd_pcm_hw_rule_muldivk, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + snd_pcm_hw_rule_muldivk, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_TIME, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + return 0; +} + +int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + int err; + unsigned int mask = 0; + + if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_RW_INTERLEAVED; + if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + if (hw_support_mmap(substream)) { + if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; + if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED; + if (hw->info & SNDRV_PCM_INFO_COMPLEX) + mask |= 1 << SNDRV_PCM_ACCESS_MMAP_COMPLEX; + } + err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT, 1 << SNDRV_PCM_SUBFORMAT_STD); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + hw->channels_min, hw->channels_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + hw->rate_min, hw->rate_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + hw->period_bytes_min, hw->period_bytes_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS, + hw->periods_min, hw->periods_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + hw->period_bytes_min, hw->buffer_bytes_max); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + snd_pcm_hw_rule_buffer_bytes_max, substream, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1); + if (err < 0) + return err; + + /* FIXME: remove */ + if (runtime->dma_bytes) { + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, runtime->dma_bytes); + if (err < 0) + return err; + } + + if (!(hw->rates & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))) { + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_rate, hw, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + } + + /* FIXME: this belong to lowlevel */ + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + + return 0; +} + +static void pcm_release_private(struct snd_pcm_substream *substream) +{ + if (snd_pcm_stream_linked(substream)) + snd_pcm_unlink(substream); +} + +void snd_pcm_release_substream(struct snd_pcm_substream *substream) +{ + substream->ref_count--; + if (substream->ref_count > 0) + return; + + snd_pcm_drop(substream); + if (substream->hw_opened) { + if (substream->ops->hw_free && + substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + substream->ops->hw_free(substream); + substream->ops->close(substream); + substream->hw_opened = 0; + } + if (pm_qos_request_active(&substream->latency_pm_qos_req)) + pm_qos_remove_request(&substream->latency_pm_qos_req); + if (substream->pcm_release) { + substream->pcm_release(substream); + substream->pcm_release = NULL; + } + snd_pcm_detach_substream(substream); +} +EXPORT_SYMBOL(snd_pcm_release_substream); + +int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, + struct file *file, + struct snd_pcm_substream **rsubstream) +{ + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_attach_substream(pcm, stream, file, &substream); + if (err < 0) + return err; + if (substream->ref_count > 1) { + *rsubstream = substream; + return 0; + } + + err = snd_pcm_hw_constraints_init(substream); + if (err < 0) { + pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n"); + goto error; + } + + if ((err = substream->ops->open(substream)) < 0) + goto error; + + substream->hw_opened = 1; + + err = snd_pcm_hw_constraints_complete(substream); + if (err < 0) { + pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n"); + goto error; + } + + *rsubstream = substream; + return 0; + + error: + snd_pcm_release_substream(substream); + return err; +} +EXPORT_SYMBOL(snd_pcm_open_substream); + +static int snd_pcm_open_file(struct file *file, + struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_open_substream(pcm, stream, file, &substream); + if (err < 0) + return err; + + pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); + if (pcm_file == NULL) { + snd_pcm_release_substream(substream); + return -ENOMEM; + } + pcm_file->substream = substream; + if (substream->ref_count == 1) { + substream->file = pcm_file; + substream->pcm_release = pcm_release_private; + } + file->private_data = pcm_file; + + return 0; +} + +static int snd_pcm_playback_open(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + int err = nonseekable_open(inode, file); + if (err < 0) + return err; + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); + if (pcm) + snd_card_unref(pcm->card); + return err; +} + +static int snd_pcm_capture_open(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + int err = nonseekable_open(inode, file); + if (err < 0) + return err; + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); + if (pcm) + snd_card_unref(pcm->card); + return err; +} + +static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) +{ + int err; + wait_queue_entry_t wait; + + if (pcm == NULL) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(pcm->card, file); + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { + err = -EFAULT; + goto __error2; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&pcm->open_wait, &wait); + mutex_lock(&pcm->open_mutex); + while (1) { + err = snd_pcm_open_file(file, pcm, stream); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&pcm->open_mutex); + schedule(); + mutex_lock(&pcm->open_mutex); + if (pcm->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&pcm->open_wait, &wait); + mutex_unlock(&pcm->open_mutex); + if (err < 0) + goto __error; + return err; + + __error: + module_put(pcm->card->module); + __error2: + snd_card_file_remove(pcm->card, file); + __error1: + return err; +} + +static int snd_pcm_release(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + struct snd_pcm_file *pcm_file; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (snd_BUG_ON(!substream)) + return -ENXIO; + pcm = substream->pcm; + mutex_lock(&pcm->open_mutex); + snd_pcm_release_substream(substream); + kfree(pcm_file); + mutex_unlock(&pcm->open_mutex); + wake_up(&pcm->open_wait); + module_put(pcm->card->module); + snd_card_file_remove(pcm->card, file); + return 0; +} + +/* check and update PCM state; return 0 or a negative error + * call this inside PCM lock + */ +static int do_pcm_hwsync(struct snd_pcm_substream *substream) +{ + switch (substream->runtime->status->state) { + case SNDRV_PCM_STATE_DRAINING: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EBADFD; + /* Fall through */ + case SNDRV_PCM_STATE_RUNNING: + return snd_pcm_update_hw_ptr(substream); + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + return 0; + case SNDRV_PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + return -EBADFD; + } +} + +/* increase the appl_ptr; returns the processed frames or a negative error */ +static snd_pcm_sframes_t forward_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames, + snd_pcm_sframes_t avail) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + int ret; + + if (avail <= 0) + return 0; + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + appl_ptr = runtime->control->appl_ptr + frames; + if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary) + appl_ptr -= runtime->boundary; + ret = pcm_lib_apply_appl_ptr(substream, appl_ptr); + return ret < 0 ? ret : frames; +} + +/* decrease the appl_ptr; returns the processed frames or zero for error */ +static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames, + snd_pcm_sframes_t avail) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + int ret; + + if (avail <= 0) + return 0; + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + appl_ptr = runtime->control->appl_ptr - frames; + if (appl_ptr < 0) + appl_ptr += runtime->boundary; + ret = pcm_lib_apply_appl_ptr(substream, appl_ptr); + /* NOTE: we return zero for errors because PulseAudio gets depressed + * upon receiving an error from rewind ioctl and stops processing + * any longer. Returning zero means that no rewind is done, so + * it's not absolutely wrong to answer like that. + */ + return ret < 0 ? 0 : frames; +} + +static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames) +{ + snd_pcm_sframes_t ret; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + ret = do_pcm_hwsync(substream); + if (!ret) + ret = rewind_appl_ptr(substream, frames, + snd_pcm_hw_avail(substream)); + snd_pcm_stream_unlock_irq(substream); + return ret; +} + +static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames) +{ + snd_pcm_sframes_t ret; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + ret = do_pcm_hwsync(substream); + if (!ret) + ret = forward_appl_ptr(substream, frames, + snd_pcm_avail(substream)); + snd_pcm_stream_unlock_irq(substream); + return ret; +} + +static int snd_pcm_hwsync(struct snd_pcm_substream *substream) +{ + int err; + + snd_pcm_stream_lock_irq(substream); + err = do_pcm_hwsync(substream); + snd_pcm_stream_unlock_irq(substream); + return err; +} + +static int snd_pcm_delay(struct snd_pcm_substream *substream, + snd_pcm_sframes_t *delay) +{ + int err; + snd_pcm_sframes_t n = 0; + + snd_pcm_stream_lock_irq(substream); + err = do_pcm_hwsync(substream); + if (!err) + n = snd_pcm_calc_delay(substream); + snd_pcm_stream_unlock_irq(substream); + if (!err) + *delay = n; + return err; +} + +static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, + struct snd_pcm_sync_ptr __user *_sync_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_sync_ptr sync_ptr; + volatile struct snd_pcm_mmap_status *status; + volatile struct snd_pcm_mmap_control *control; + int err; + + memset(&sync_ptr, 0, sizeof(sync_ptr)); + if (get_user(sync_ptr.flags, (unsigned __user *)&(_sync_ptr->flags))) + return -EFAULT; + if (copy_from_user(&sync_ptr.c.control, &(_sync_ptr->c.control), sizeof(struct snd_pcm_mmap_control))) + return -EFAULT; + status = runtime->status; + control = runtime->control; + if (sync_ptr.flags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + snd_pcm_stream_lock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) { + err = pcm_lib_apply_appl_ptr(substream, + sync_ptr.c.control.appl_ptr); + if (err < 0) { + snd_pcm_stream_unlock_irq(substream); + return err; + } + } else { + sync_ptr.c.control.appl_ptr = control->appl_ptr; + } + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = sync_ptr.c.control.avail_min; + else + sync_ptr.c.control.avail_min = control->avail_min; + sync_ptr.s.status.state = status->state; + sync_ptr.s.status.hw_ptr = status->hw_ptr; + sync_ptr.s.status.tstamp = status->tstamp; + sync_ptr.s.status.suspended_state = status->suspended_state; + sync_ptr.s.status.audio_tstamp = status->audio_tstamp; + snd_pcm_stream_unlock_irq(substream); + if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr))) + return -EFAULT; + return 0; +} + +static int snd_pcm_tstamp(struct snd_pcm_substream *substream, int __user *_arg) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int arg; + + if (get_user(arg, _arg)) + return -EFAULT; + if (arg < 0 || arg > SNDRV_PCM_TSTAMP_TYPE_LAST) + return -EINVAL; + runtime->tstamp_type = arg; + return 0; +} + +static int snd_pcm_xferi_frames_ioctl(struct snd_pcm_substream *substream, + struct snd_xferi __user *_xferi) +{ + struct snd_xferi xferi; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t result; + + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (put_user(0, &_xferi->result)) + return -EFAULT; + if (copy_from_user(&xferi, _xferi, sizeof(xferi))) + return -EFAULT; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames); + else + result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames); + __put_user(result, &_xferi->result); + return result < 0 ? result : 0; +} + +static int snd_pcm_xfern_frames_ioctl(struct snd_pcm_substream *substream, + struct snd_xfern __user *_xfern) +{ + struct snd_xfern xfern; + struct snd_pcm_runtime *runtime = substream->runtime; + void *bufs; + snd_pcm_sframes_t result; + + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (runtime->channels > 128) + return -EINVAL; + if (put_user(0, &_xfern->result)) + return -EFAULT; + if (copy_from_user(&xfern, _xfern, sizeof(xfern))) + return -EFAULT; + + bufs = memdup_user(xfern.bufs, sizeof(void *) * runtime->channels); + if (IS_ERR(bufs)) + return PTR_ERR(bufs); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + result = snd_pcm_lib_writev(substream, bufs, xfern.frames); + else + result = snd_pcm_lib_readv(substream, bufs, xfern.frames); + kfree(bufs); + __put_user(result, &_xfern->result); + return result < 0 ? result : 0; +} + +static int snd_pcm_rewind_ioctl(struct snd_pcm_substream *substream, + snd_pcm_uframes_t __user *_frames) +{ + snd_pcm_uframes_t frames; + snd_pcm_sframes_t result; + + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_rewind(substream, frames); + __put_user(result, _frames); + return result < 0 ? result : 0; +} + +static int snd_pcm_forward_ioctl(struct snd_pcm_substream *substream, + snd_pcm_uframes_t __user *_frames) +{ + snd_pcm_uframes_t frames; + snd_pcm_sframes_t result; + + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_forward(substream, frames); + __put_user(result, _frames); + return result < 0 ? result : 0; +} + +static int snd_pcm_common_ioctl(struct file *file, + struct snd_pcm_substream *substream, + unsigned int cmd, void __user *arg) +{ + struct snd_pcm_file *pcm_file = file->private_data; + int res; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + + res = snd_power_wait(substream->pcm->card, SNDRV_CTL_POWER_D0); + if (res < 0) + return res; + + switch (cmd) { + case SNDRV_PCM_IOCTL_PVERSION: + return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0; + case SNDRV_PCM_IOCTL_INFO: + return snd_pcm_info_user(substream, arg); + case SNDRV_PCM_IOCTL_TSTAMP: /* just for compatibility */ + return 0; + case SNDRV_PCM_IOCTL_TTSTAMP: + return snd_pcm_tstamp(substream, arg); + case SNDRV_PCM_IOCTL_USER_PVERSION: + if (get_user(pcm_file->user_pversion, + (unsigned int __user *)arg)) + return -EFAULT; + return 0; + case SNDRV_PCM_IOCTL_HW_REFINE: + return snd_pcm_hw_refine_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_PARAMS: + return snd_pcm_hw_params_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_FREE: + return snd_pcm_hw_free(substream); + case SNDRV_PCM_IOCTL_SW_PARAMS: + return snd_pcm_sw_params_user(substream, arg); + case SNDRV_PCM_IOCTL_STATUS: + return snd_pcm_status_user(substream, arg, false); + case SNDRV_PCM_IOCTL_STATUS_EXT: + return snd_pcm_status_user(substream, arg, true); + case SNDRV_PCM_IOCTL_CHANNEL_INFO: + return snd_pcm_channel_info_user(substream, arg); + case SNDRV_PCM_IOCTL_PREPARE: + return snd_pcm_prepare(substream, file); + case SNDRV_PCM_IOCTL_RESET: + return snd_pcm_reset(substream); + case SNDRV_PCM_IOCTL_START: + return snd_pcm_start_lock_irq(substream); + case SNDRV_PCM_IOCTL_LINK: + return snd_pcm_link(substream, (int)(unsigned long) arg); + case SNDRV_PCM_IOCTL_UNLINK: + return snd_pcm_unlink(substream); + case SNDRV_PCM_IOCTL_RESUME: + return snd_pcm_resume(substream); + case SNDRV_PCM_IOCTL_XRUN: + return snd_pcm_xrun(substream); + case SNDRV_PCM_IOCTL_HWSYNC: + return snd_pcm_hwsync(substream); + case SNDRV_PCM_IOCTL_DELAY: + { + snd_pcm_sframes_t delay; + snd_pcm_sframes_t __user *res = arg; + int err; + + err = snd_pcm_delay(substream, &delay); + if (err) + return err; + if (put_user(delay, res)) + return -EFAULT; + return 0; + } + case SNDRV_PCM_IOCTL_SYNC_PTR: + return snd_pcm_sync_ptr(substream, arg); +#ifdef CONFIG_SND_SUPPORT_OLD_API + case SNDRV_PCM_IOCTL_HW_REFINE_OLD: + return snd_pcm_hw_refine_old_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_PARAMS_OLD: + return snd_pcm_hw_params_old_user(substream, arg); +#endif + case SNDRV_PCM_IOCTL_DRAIN: + return snd_pcm_drain(substream, file); + case SNDRV_PCM_IOCTL_DROP: + return snd_pcm_drop(substream); + case SNDRV_PCM_IOCTL_PAUSE: + return snd_pcm_action_lock_irq(&snd_pcm_action_pause, + substream, + (int)(unsigned long)arg); + case SNDRV_PCM_IOCTL_WRITEI_FRAMES: + case SNDRV_PCM_IOCTL_READI_FRAMES: + return snd_pcm_xferi_frames_ioctl(substream, arg); + case SNDRV_PCM_IOCTL_WRITEN_FRAMES: + case SNDRV_PCM_IOCTL_READN_FRAMES: + return snd_pcm_xfern_frames_ioctl(substream, arg); + case SNDRV_PCM_IOCTL_REWIND: + return snd_pcm_rewind_ioctl(substream, arg); + case SNDRV_PCM_IOCTL_FORWARD: + return snd_pcm_forward_ioctl(substream, arg); + } + pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd); + return -ENOTTY; +} + +static long snd_pcm_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_pcm_file *pcm_file; + + pcm_file = file->private_data; + + if (((cmd >> 8) & 0xff) != 'A') + return -ENOTTY; + + return snd_pcm_common_ioctl(file, pcm_file->substream, cmd, + (void __user *)arg); +} + +/** + * snd_pcm_kernel_ioctl - Execute PCM ioctl in the kernel-space + * @substream: PCM substream + * @cmd: IOCTL cmd + * @arg: IOCTL argument + * + * The function is provided primarily for OSS layer and USB gadget drivers, + * and it allows only the limited set of ioctls (hw_params, sw_params, + * prepare, start, drain, drop, forward). + */ +int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + snd_pcm_uframes_t *frames = arg; + snd_pcm_sframes_t result; + + switch (cmd) { + case SNDRV_PCM_IOCTL_FORWARD: + { + /* provided only for OSS; capture-only and no value returned */ + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + result = snd_pcm_forward(substream, *frames); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_HW_PARAMS: + return snd_pcm_hw_params(substream, arg); + case SNDRV_PCM_IOCTL_SW_PARAMS: + return snd_pcm_sw_params(substream, arg); + case SNDRV_PCM_IOCTL_PREPARE: + return snd_pcm_prepare(substream, NULL); + case SNDRV_PCM_IOCTL_START: + return snd_pcm_start_lock_irq(substream); + case SNDRV_PCM_IOCTL_DRAIN: + return snd_pcm_drain(substream, NULL); + case SNDRV_PCM_IOCTL_DROP: + return snd_pcm_drop(substream); + case SNDRV_PCM_IOCTL_DELAY: + return snd_pcm_delay(substream, frames); + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(snd_pcm_kernel_ioctl); + +static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count, + loff_t * offset) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!frame_aligned(runtime, count)) + return -EINVAL; + count = bytes_to_frames(runtime, count); + result = snd_pcm_lib_read(substream, buf, count); + if (result > 0) + result = frames_to_bytes(runtime, result); + return result; +} + +static ssize_t snd_pcm_write(struct file *file, const char __user *buf, + size_t count, loff_t * offset) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!frame_aligned(runtime, count)) + return -EINVAL; + count = bytes_to_frames(runtime, count); + result = snd_pcm_lib_write(substream, buf, count); + if (result > 0) + result = frames_to_bytes(runtime, result); + return result; +} + +static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + unsigned long i; + void __user **bufs; + snd_pcm_uframes_t frames; + + pcm_file = iocb->ki_filp->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!iter_is_iovec(to)) + return -EINVAL; + if (to->nr_segs > 1024 || to->nr_segs != runtime->channels) + return -EINVAL; + if (!frame_aligned(runtime, to->iov->iov_len)) + return -EINVAL; + frames = bytes_to_samples(runtime, to->iov->iov_len); + bufs = kmalloc_array(to->nr_segs, sizeof(void *), GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < to->nr_segs; ++i) + bufs[i] = to->iov[i].iov_base; + result = snd_pcm_lib_readv(substream, bufs, frames); + if (result > 0) + result = frames_to_bytes(runtime, result); + kfree(bufs); + return result; +} + +static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + unsigned long i; + void __user **bufs; + snd_pcm_uframes_t frames; + + pcm_file = iocb->ki_filp->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!iter_is_iovec(from)) + return -EINVAL; + if (from->nr_segs > 128 || from->nr_segs != runtime->channels || + !frame_aligned(runtime, from->iov->iov_len)) + return -EINVAL; + frames = bytes_to_samples(runtime, from->iov->iov_len); + bufs = kmalloc_array(from->nr_segs, sizeof(void *), GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < from->nr_segs; ++i) + bufs[i] = from->iov[i].iov_base; + result = snd_pcm_lib_writev(substream, bufs, frames); + if (result > 0) + result = frames_to_bytes(runtime, result); + kfree(bufs); + return result; +} + +static __poll_t snd_pcm_poll(struct file *file, poll_table *wait) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + __poll_t mask, ok; + snd_pcm_uframes_t avail; + + pcm_file = file->private_data; + + substream = pcm_file->substream; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ok = EPOLLOUT | EPOLLWRNORM; + else + ok = EPOLLIN | EPOLLRDNORM; + if (PCM_RUNTIME_CHECK(substream)) + return ok | EPOLLERR; + + runtime = substream->runtime; + poll_wait(file, &runtime->sleep, wait); + + mask = 0; + snd_pcm_stream_lock_irq(substream); + avail = snd_pcm_avail(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + if (avail >= runtime->control->avail_min) + mask = ok; + break; + case SNDRV_PCM_STATE_DRAINING: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + mask = ok; + if (!avail) + mask |= EPOLLERR; + } + break; + default: + mask = ok | EPOLLERR; + break; + } + snd_pcm_stream_unlock_irq(substream); + return mask; +} + +/* + * mmap support + */ + +/* + * Only on coherent architectures, we can mmap the status and the control records + * for effcient data transfer. On others, we have to use HWSYNC ioctl... + */ +#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_ALPHA) +/* + * mmap status record + */ +static vm_fault_t snd_pcm_mmap_status_fault(struct vm_fault *vmf) +{ + struct snd_pcm_substream *substream = vmf->vma->vm_private_data; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; + vmf->page = virt_to_page(runtime->status); + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct snd_pcm_vm_ops_status = +{ + .fault = snd_pcm_mmap_status_fault, +}; + +static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + long size; + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + size = area->vm_end - area->vm_start; + if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))) + return -EINVAL; + area->vm_ops = &snd_pcm_vm_ops_status; + area->vm_private_data = substream; + area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + return 0; +} + +/* + * mmap control record + */ +static vm_fault_t snd_pcm_mmap_control_fault(struct vm_fault *vmf) +{ + struct snd_pcm_substream *substream = vmf->vma->vm_private_data; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; + vmf->page = virt_to_page(runtime->control); + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct snd_pcm_vm_ops_control = +{ + .fault = snd_pcm_mmap_control_fault, +}; + +static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + long size; + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + size = area->vm_end - area->vm_start; + if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control))) + return -EINVAL; + area->vm_ops = &snd_pcm_vm_ops_control; + area->vm_private_data = substream; + area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + return 0; +} + +static bool pcm_status_mmap_allowed(struct snd_pcm_file *pcm_file) +{ + if (pcm_file->no_compat_mmap) + return false; + /* See pcm_control_mmap_allowed() below. + * Since older alsa-lib requires both status and control mmaps to be + * coupled, we have to disable the status mmap for old alsa-lib, too. + */ + if (pcm_file->user_pversion < SNDRV_PROTOCOL_VERSION(2, 0, 14) && + (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR)) + return false; + return true; +} + +static bool pcm_control_mmap_allowed(struct snd_pcm_file *pcm_file) +{ + if (pcm_file->no_compat_mmap) + return false; + /* Disallow the control mmap when SYNC_APPLPTR flag is set; + * it enforces the user-space to fall back to snd_pcm_sync_ptr(), + * thus it effectively assures the manual update of appl_ptr. + */ + if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR) + return false; + return true; +} + +#else /* ! coherent mmap */ +/* + * don't support mmap for status and control records. + */ +#define pcm_status_mmap_allowed(pcm_file) false +#define pcm_control_mmap_allowed(pcm_file) false + +static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + return -ENXIO; +} +static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + return -ENXIO; +} +#endif /* coherent mmap */ + +static inline struct page * +snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs) +{ + void *vaddr = substream->runtime->dma_area + ofs; + return virt_to_page(vaddr); +} + +/* + * fault callback for mmapping a RAM page + */ +static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf) +{ + struct snd_pcm_substream *substream = vmf->vma->vm_private_data; + struct snd_pcm_runtime *runtime; + unsigned long offset; + struct page * page; + size_t dma_bytes; + + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; + offset = vmf->pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) + return VM_FAULT_SIGBUS; + if (substream->ops->page) + page = substream->ops->page(substream, offset); + else + page = snd_pcm_default_page_ops(substream, offset); + if (!page) + return VM_FAULT_SIGBUS; + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct snd_pcm_vm_ops_data = { + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, +}; + +static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = { + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, + .fault = snd_pcm_mmap_data_fault, +}; + +/* + * mmap the DMA buffer on RAM + */ + +/** + * snd_pcm_lib_default_mmap - Default PCM data mmap function + * @substream: PCM substream + * @area: VMA + * + * This is the default mmap handler for PCM data. When mmap pcm_ops is NULL, + * this function is invoked implicitly. + */ +int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; +#ifdef CONFIG_GENERIC_ALLOCATOR + if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_IRAM) { + area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); + return remap_pfn_range(area, area->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + area->vm_end - area->vm_start, area->vm_page_prot); + } +#endif /* CONFIG_GENERIC_ALLOCATOR */ +#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */ + if (IS_ENABLED(CONFIG_HAS_DMA) && !substream->ops->page && + substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) + return dma_mmap_coherent(substream->dma_buffer.dev.dev, + area, + substream->runtime->dma_area, + substream->runtime->dma_addr, + substream->runtime->dma_bytes); +#endif /* CONFIG_X86 */ + /* mmap with fault handler */ + area->vm_ops = &snd_pcm_vm_ops_data_fault; + return 0; +} +EXPORT_SYMBOL_GPL(snd_pcm_lib_default_mmap); + +/* + * mmap the DMA buffer on I/O memory area + */ +#if SNDRV_PCM_INFO_MMAP_IOMEM +/** + * snd_pcm_lib_mmap_iomem - Default PCM data mmap function for I/O mem + * @substream: PCM substream + * @area: VMA + * + * When your hardware uses the iomapped pages as the hardware buffer and + * wants to mmap it, pass this function as mmap pcm_ops. Note that this + * is supposed to work only on limited architectures. + */ +int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + area->vm_page_prot = pgprot_noncached(area->vm_page_prot); + return vm_iomap_memory(area, runtime->dma_addr, runtime->dma_bytes); +} +EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem); +#endif /* SNDRV_PCM_INFO_MMAP */ + +/* + * mmap DMA buffer + */ +int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + struct snd_pcm_runtime *runtime; + long size; + unsigned long offset; + size_t dma_bytes; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!(area->vm_flags & (VM_WRITE|VM_READ))) + return -EINVAL; + } else { + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + } + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) + return -ENXIO; + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + return -EINVAL; + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if ((size_t)size > dma_bytes) + return -EINVAL; + if (offset > dma_bytes - size) + return -EINVAL; + + area->vm_ops = &snd_pcm_vm_ops_data; + area->vm_private_data = substream; + if (substream->ops->mmap) + err = substream->ops->mmap(substream, area); + else + err = snd_pcm_lib_default_mmap(substream, area); + if (!err) + atomic_inc(&substream->mmap_count); + return err; +} +EXPORT_SYMBOL(snd_pcm_mmap_data); + +static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area) +{ + struct snd_pcm_file * pcm_file; + struct snd_pcm_substream *substream; + unsigned long offset; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + + offset = area->vm_pgoff << PAGE_SHIFT; + switch (offset) { + case SNDRV_PCM_MMAP_OFFSET_STATUS: + if (!pcm_status_mmap_allowed(pcm_file)) + return -ENXIO; + return snd_pcm_mmap_status(substream, file, area); + case SNDRV_PCM_MMAP_OFFSET_CONTROL: + if (!pcm_control_mmap_allowed(pcm_file)) + return -ENXIO; + return snd_pcm_mmap_control(substream, file, area); + default: + return snd_pcm_mmap_data(substream, file, area); + } + return 0; +} + +static int snd_pcm_fasync(int fd, struct file * file, int on) +{ + struct snd_pcm_file * pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + return fasync_helper(fd, file, on, &runtime->fasync); +} + +/* + * ioctl32 compat + */ +#ifdef CONFIG_COMPAT +#include "pcm_compat.c" +#else +#define snd_pcm_ioctl_compat NULL +#endif + +/* + * To be removed helpers to keep binary compatibility + */ + +#ifdef CONFIG_SND_SUPPORT_OLD_API +#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5)) +#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5)) + +static void snd_pcm_hw_convert_from_old_params(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_params_old *oparams) +{ + unsigned int i; + + memset(params, 0, sizeof(*params)); + params->flags = oparams->flags; + for (i = 0; i < ARRAY_SIZE(oparams->masks); i++) + params->masks[i].bits[0] = oparams->masks[i]; + memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals)); + params->rmask = __OLD_TO_NEW_MASK(oparams->rmask); + params->cmask = __OLD_TO_NEW_MASK(oparams->cmask); + params->info = oparams->info; + params->msbits = oparams->msbits; + params->rate_num = oparams->rate_num; + params->rate_den = oparams->rate_den; + params->fifo_size = oparams->fifo_size; +} + +static void snd_pcm_hw_convert_to_old_params(struct snd_pcm_hw_params_old *oparams, + struct snd_pcm_hw_params *params) +{ + unsigned int i; + + memset(oparams, 0, sizeof(*oparams)); + oparams->flags = params->flags; + for (i = 0; i < ARRAY_SIZE(oparams->masks); i++) + oparams->masks[i] = params->masks[i].bits[0]; + memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals)); + oparams->rmask = __NEW_TO_OLD_MASK(params->rmask); + oparams->cmask = __NEW_TO_OLD_MASK(params->cmask); + oparams->info = params->info; + oparams->msbits = params->msbits; + oparams->rate_num = params->rate_num; + oparams->rate_den = params->rate_den; + oparams->fifo_size = params->fifo_size; +} + +static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams) +{ + struct snd_pcm_hw_params *params; + struct snd_pcm_hw_params_old *oparams = NULL; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + oparams = memdup_user(_oparams, sizeof(*oparams)); + if (IS_ERR(oparams)) { + err = PTR_ERR(oparams); + goto out; + } + snd_pcm_hw_convert_from_old_params(params, oparams); + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto out_old; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + goto out_old; + + snd_pcm_hw_convert_to_old_params(oparams, params); + if (copy_to_user(_oparams, oparams, sizeof(*oparams))) + err = -EFAULT; +out_old: + kfree(oparams); +out: + kfree(params); + return err; +} + +static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams) +{ + struct snd_pcm_hw_params *params; + struct snd_pcm_hw_params_old *oparams = NULL; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + oparams = memdup_user(_oparams, sizeof(*oparams)); + if (IS_ERR(oparams)) { + err = PTR_ERR(oparams); + goto out; + } + + snd_pcm_hw_convert_from_old_params(params, oparams); + err = snd_pcm_hw_params(substream, params); + if (err < 0) + goto out_old; + + snd_pcm_hw_convert_to_old_params(oparams, params); + if (copy_to_user(_oparams, oparams, sizeof(*oparams))) + err = -EFAULT; +out_old: + kfree(oparams); +out: + kfree(params); + return err; +} +#endif /* CONFIG_SND_SUPPORT_OLD_API */ + +#ifndef CONFIG_MMU +static unsigned long snd_pcm_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct snd_pcm_file *pcm_file = file->private_data; + struct snd_pcm_substream *substream = pcm_file->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long offset = pgoff << PAGE_SHIFT; + + switch (offset) { + case SNDRV_PCM_MMAP_OFFSET_STATUS: + return (unsigned long)runtime->status; + case SNDRV_PCM_MMAP_OFFSET_CONTROL: + return (unsigned long)runtime->control; + default: + return (unsigned long)runtime->dma_area + offset; + } +} +#else +# define snd_pcm_get_unmapped_area NULL +#endif + +/* + * Register section + */ + +const struct file_operations snd_pcm_f_ops[2] = { + { + .owner = THIS_MODULE, + .write = snd_pcm_write, + .write_iter = snd_pcm_writev, + .open = snd_pcm_playback_open, + .release = snd_pcm_release, + .llseek = no_llseek, + .poll = snd_pcm_poll, + .unlocked_ioctl = snd_pcm_ioctl, + .compat_ioctl = snd_pcm_ioctl_compat, + .mmap = snd_pcm_mmap, + .fasync = snd_pcm_fasync, + .get_unmapped_area = snd_pcm_get_unmapped_area, + }, + { + .owner = THIS_MODULE, + .read = snd_pcm_read, + .read_iter = snd_pcm_readv, + .open = snd_pcm_capture_open, + .release = snd_pcm_release, + .llseek = no_llseek, + .poll = snd_pcm_poll, + .unlocked_ioctl = snd_pcm_ioctl, + .compat_ioctl = snd_pcm_ioctl_compat, + .mmap = snd_pcm_mmap, + .fasync = snd_pcm_fasync, + .get_unmapped_area = snd_pcm_get_unmapped_area, + } +}; diff --git a/sound/core/pcm_param_trace.h b/sound/core/pcm_param_trace.h new file mode 100644 index 000000000..08abba313 --- /dev/null +++ b/sound/core/pcm_param_trace.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_pcm + +#if !defined(_PCM_PARAMS_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _PCM_PARAMS_TRACE_H + +#include + +#define HW_PARAM_ENTRY(param) {SNDRV_PCM_HW_PARAM_##param, #param} +#define hw_param_labels \ + HW_PARAM_ENTRY(ACCESS), \ + HW_PARAM_ENTRY(FORMAT), \ + HW_PARAM_ENTRY(SUBFORMAT), \ + HW_PARAM_ENTRY(SAMPLE_BITS), \ + HW_PARAM_ENTRY(FRAME_BITS), \ + HW_PARAM_ENTRY(CHANNELS), \ + HW_PARAM_ENTRY(RATE), \ + HW_PARAM_ENTRY(PERIOD_TIME), \ + HW_PARAM_ENTRY(PERIOD_SIZE), \ + HW_PARAM_ENTRY(PERIOD_BYTES), \ + HW_PARAM_ENTRY(PERIODS), \ + HW_PARAM_ENTRY(BUFFER_TIME), \ + HW_PARAM_ENTRY(BUFFER_SIZE), \ + HW_PARAM_ENTRY(BUFFER_BYTES), \ + HW_PARAM_ENTRY(TICK_TIME) + +TRACE_EVENT(hw_mask_param, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_hw_param_t type, int index, const struct snd_mask *prev, const struct snd_mask *curr), + TP_ARGS(substream, type, index, prev, curr), + TP_STRUCT__entry( + __field(int, card) + __field(int, device) + __field(int, subdevice) + __field(int, direction) + __field(snd_pcm_hw_param_t, type) + __field(int, index) + __field(int, total) + __array(__u32, prev_bits, 8) + __array(__u32, curr_bits, 8) + ), + TP_fast_assign( + __entry->card = substream->pcm->card->number; + __entry->device = substream->pcm->device; + __entry->subdevice = substream->number; + __entry->direction = substream->stream; + __entry->type = type; + __entry->index = index; + __entry->total = substream->runtime->hw_constraints.rules_num; + memcpy(__entry->prev_bits, prev->bits, sizeof(__u32) * 8); + memcpy(__entry->curr_bits, curr->bits, sizeof(__u32) * 8); + ), + TP_printk("pcmC%dD%d%s:%d %03d/%03d %s %08x%08x%08x%08x %08x%08x%08x%08x", + __entry->card, + __entry->device, + __entry->direction ? "c" : "p", + __entry->subdevice, + __entry->index, + __entry->total, + __print_symbolic(__entry->type, hw_param_labels), + __entry->prev_bits[3], __entry->prev_bits[2], + __entry->prev_bits[1], __entry->prev_bits[0], + __entry->curr_bits[3], __entry->curr_bits[2], + __entry->curr_bits[1], __entry->curr_bits[0] + ) +); + +TRACE_EVENT(hw_interval_param, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_hw_param_t type, int index, const struct snd_interval *prev, const struct snd_interval *curr), + TP_ARGS(substream, type, index, prev, curr), + TP_STRUCT__entry( + __field(int, card) + __field(int, device) + __field(int, subdevice) + __field(int, direction) + __field(snd_pcm_hw_param_t, type) + __field(int, index) + __field(int, total) + __field(unsigned int, prev_min) + __field(unsigned int, prev_max) + __field(unsigned int, prev_openmin) + __field(unsigned int, prev_openmax) + __field(unsigned int, prev_integer) + __field(unsigned int, prev_empty) + __field(unsigned int, curr_min) + __field(unsigned int, curr_max) + __field(unsigned int, curr_openmin) + __field(unsigned int, curr_openmax) + __field(unsigned int, curr_integer) + __field(unsigned int, curr_empty) + ), + TP_fast_assign( + __entry->card = substream->pcm->card->number; + __entry->device = substream->pcm->device; + __entry->subdevice = substream->number; + __entry->direction = substream->stream; + __entry->type = type; + __entry->index = index; + __entry->total = substream->runtime->hw_constraints.rules_num; + __entry->prev_min = prev->min; + __entry->prev_max = prev->max; + __entry->prev_openmin = prev->openmin; + __entry->prev_openmax = prev->openmax; + __entry->prev_integer = prev->integer; + __entry->prev_empty = prev->empty; + __entry->curr_min = curr->min; + __entry->curr_max = curr->max; + __entry->curr_openmin = curr->openmin; + __entry->curr_openmax = curr->openmax; + __entry->curr_integer = curr->integer; + __entry->curr_empty = curr->empty; + ), + TP_printk("pcmC%dD%d%s:%d %03d/%03d %s %d %d %s%u %u%s %d %d %s%u %u%s", + __entry->card, + __entry->device, + __entry->direction ? "c" : "p", + __entry->subdevice, + __entry->index, + __entry->total, + __print_symbolic(__entry->type, hw_param_labels), + __entry->prev_empty, + __entry->prev_integer, + __entry->prev_openmin ? "(" : "[", + __entry->prev_min, + __entry->prev_max, + __entry->prev_openmax ? ")" : "]", + __entry->curr_empty, + __entry->curr_integer, + __entry->curr_openmin ? "(" : "[", + __entry->curr_min, + __entry->curr_max, + __entry->curr_openmax ? ")" : "]" + ) +); + +#endif /* _PCM_PARAMS_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE pcm_param_trace +#include diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c new file mode 100644 index 000000000..11389f13d --- /dev/null +++ b/sound/core/pcm_timer.c @@ -0,0 +1,145 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include "pcm_local.h" + +/* + * Timer functions + */ + +void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream) +{ + unsigned long rate, mult, fsize, l, post; + struct snd_pcm_runtime *runtime = substream->runtime; + + mult = 1000000000; + rate = runtime->rate; + if (snd_BUG_ON(!rate)) + return; + l = gcd(mult, rate); + mult /= l; + rate /= l; + fsize = runtime->period_size; + if (snd_BUG_ON(!fsize)) + return; + l = gcd(rate, fsize); + rate /= l; + fsize /= l; + post = 1; + while ((mult * fsize) / fsize != mult) { + mult /= 2; + post *= 2; + } + if (rate == 0) { + pcm_err(substream->pcm, + "pcm timer resolution out of range (rate = %u, period_size = %lu)\n", + runtime->rate, runtime->period_size); + runtime->timer_resolution = -1; + return; + } + runtime->timer_resolution = (mult * fsize / rate) * post; +} + +static unsigned long snd_pcm_timer_resolution(struct snd_timer * timer) +{ + struct snd_pcm_substream *substream; + + substream = timer->private_data; + return substream->runtime ? substream->runtime->timer_resolution : 0; +} + +static int snd_pcm_timer_start(struct snd_timer * timer) +{ + struct snd_pcm_substream *substream; + + substream = snd_timer_chip(timer); + substream->timer_running = 1; + return 0; +} + +static int snd_pcm_timer_stop(struct snd_timer * timer) +{ + struct snd_pcm_substream *substream; + + substream = snd_timer_chip(timer); + substream->timer_running = 0; + return 0; +} + +static struct snd_timer_hardware snd_pcm_timer = +{ + .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE, + .resolution = 0, + .ticks = 1, + .c_resolution = snd_pcm_timer_resolution, + .start = snd_pcm_timer_start, + .stop = snd_pcm_timer_stop, +}; + +/* + * Init functions + */ + +static void snd_pcm_timer_free(struct snd_timer *timer) +{ + struct snd_pcm_substream *substream = timer->private_data; + substream->timer = NULL; +} + +void snd_pcm_timer_init(struct snd_pcm_substream *substream) +{ + struct snd_timer_id tid; + struct snd_timer *timer; + + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.dev_class = SNDRV_TIMER_CLASS_PCM; + tid.card = substream->pcm->card->number; + tid.device = substream->pcm->device; + tid.subdevice = (substream->number << 1) | (substream->stream & 1); + if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0) + return; + sprintf(timer->name, "PCM %s %i-%i-%i", + substream->stream == SNDRV_PCM_STREAM_CAPTURE ? + "capture" : "playback", + tid.card, tid.device, tid.subdevice); + timer->hw = snd_pcm_timer; + if (snd_device_register(timer->card, timer) < 0) { + snd_device_free(timer->card, timer); + return; + } + timer->private_data = substream; + timer->private_free = snd_pcm_timer_free; + substream->timer = timer; +} + +void snd_pcm_timer_done(struct snd_pcm_substream *substream) +{ + if (substream->timer) { + snd_device_free(substream->pcm->card, substream->timer); + substream->timer = NULL; + } +} diff --git a/sound/core/pcm_trace.h b/sound/core/pcm_trace.h new file mode 100644 index 000000000..f18da2050 --- /dev/null +++ b/sound/core/pcm_trace.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_pcm +#define TRACE_INCLUDE_FILE pcm_trace + +#if !defined(_PCM_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _PCM_TRACE_H + +#include + +TRACE_EVENT(hwptr, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_uframes_t pos, bool irq), + TP_ARGS(substream, pos, irq), + TP_STRUCT__entry( + __field( bool, in_interrupt ) + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __field( snd_pcm_uframes_t, pos ) + __field( snd_pcm_uframes_t, period_size ) + __field( snd_pcm_uframes_t, buffer_size ) + __field( snd_pcm_uframes_t, old_hw_ptr ) + __field( snd_pcm_uframes_t, hw_ptr_base ) + ), + TP_fast_assign( + __entry->in_interrupt = (irq); + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __entry->pos = (pos); + __entry->period_size = (substream)->runtime->period_size; + __entry->buffer_size = (substream)->runtime->buffer_size; + __entry->old_hw_ptr = (substream)->runtime->status->hw_ptr; + __entry->hw_ptr_base = (substream)->runtime->hw_ptr_base; + ), + TP_printk("pcmC%dD%d%s/sub%d: %s: pos=%lu, old=%lu, base=%lu, period=%lu, buf=%lu", + __entry->card, __entry->device, + __entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? "p" : "c", + __entry->number, + __entry->in_interrupt ? "IRQ" : "POS", + (unsigned long)__entry->pos, + (unsigned long)__entry->old_hw_ptr, + (unsigned long)__entry->hw_ptr_base, + (unsigned long)__entry->period_size, + (unsigned long)__entry->buffer_size) +); + +TRACE_EVENT(xrun, + TP_PROTO(struct snd_pcm_substream *substream), + TP_ARGS(substream), + TP_STRUCT__entry( + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __field( snd_pcm_uframes_t, period_size ) + __field( snd_pcm_uframes_t, buffer_size ) + __field( snd_pcm_uframes_t, old_hw_ptr ) + __field( snd_pcm_uframes_t, hw_ptr_base ) + ), + TP_fast_assign( + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __entry->period_size = (substream)->runtime->period_size; + __entry->buffer_size = (substream)->runtime->buffer_size; + __entry->old_hw_ptr = (substream)->runtime->status->hw_ptr; + __entry->hw_ptr_base = (substream)->runtime->hw_ptr_base; + ), + TP_printk("pcmC%dD%d%s/sub%d: XRUN: old=%lu, base=%lu, period=%lu, buf=%lu", + __entry->card, __entry->device, + __entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? "p" : "c", + __entry->number, + (unsigned long)__entry->old_hw_ptr, + (unsigned long)__entry->hw_ptr_base, + (unsigned long)__entry->period_size, + (unsigned long)__entry->buffer_size) +); + +TRACE_EVENT(hw_ptr_error, + TP_PROTO(struct snd_pcm_substream *substream, const char *why), + TP_ARGS(substream, why), + TP_STRUCT__entry( + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __field( const char *, reason ) + ), + TP_fast_assign( + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __entry->reason = (why); + ), + TP_printk("pcmC%dD%d%s/sub%d: ERROR: %s", + __entry->card, __entry->device, + __entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? "p" : "c", + __entry->number, __entry->reason) +); + +TRACE_EVENT(applptr, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_uframes_t prev, snd_pcm_uframes_t curr), + TP_ARGS(substream, prev, curr), + TP_STRUCT__entry( + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __field( snd_pcm_uframes_t, prev ) + __field( snd_pcm_uframes_t, curr ) + __field( snd_pcm_uframes_t, avail ) + __field( snd_pcm_uframes_t, period_size ) + __field( snd_pcm_uframes_t, buffer_size ) + ), + TP_fast_assign( + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __entry->prev = (prev); + __entry->curr = (curr); + __entry->avail = (substream)->stream ? snd_pcm_capture_avail(substream->runtime) : snd_pcm_playback_avail(substream->runtime); + __entry->period_size = (substream)->runtime->period_size; + __entry->buffer_size = (substream)->runtime->buffer_size; + ), + TP_printk("pcmC%dD%d%s/sub%d: prev=%lu, curr=%lu, avail=%lu, period=%lu, buf=%lu", + __entry->card, + __entry->device, + __entry->stream ? "c" : "p", + __entry->number, + __entry->prev, + __entry->curr, + __entry->avail, + __entry->period_size, + __entry->buffer_size + ) +); + +#endif /* _PCM_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c new file mode 100644 index 000000000..f4f855d7a --- /dev/null +++ b/sound/core/rawmidi.c @@ -0,0 +1,1869 @@ +/* + * Abstract layer for MIDI v1.0 stream + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA."); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_SND_OSSEMUL +static int midi_map[SNDRV_CARDS]; +static int amidi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +module_param_array(midi_map, int, NULL, 0444); +MODULE_PARM_DESC(midi_map, "Raw MIDI device number assigned to 1st OSS device."); +module_param_array(amidi_map, int, NULL, 0444); +MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); +#endif /* CONFIG_SND_OSSEMUL */ + +static int snd_rawmidi_free(struct snd_rawmidi *rawmidi); +static int snd_rawmidi_dev_free(struct snd_device *device); +static int snd_rawmidi_dev_register(struct snd_device *device); +static int snd_rawmidi_dev_disconnect(struct snd_device *device); + +static LIST_HEAD(snd_rawmidi_devices); +static DEFINE_MUTEX(register_mutex); + +#define rmidi_err(rmidi, fmt, args...) \ + dev_err(&(rmidi)->dev, fmt, ##args) +#define rmidi_warn(rmidi, fmt, args...) \ + dev_warn(&(rmidi)->dev, fmt, ##args) +#define rmidi_dbg(rmidi, fmt, args...) \ + dev_dbg(&(rmidi)->dev, fmt, ##args) + +static struct snd_rawmidi *snd_rawmidi_search(struct snd_card *card, int device) +{ + struct snd_rawmidi *rawmidi; + + list_for_each_entry(rawmidi, &snd_rawmidi_devices, list) + if (rawmidi->card == card && rawmidi->device == device) + return rawmidi; + return NULL; +} + +static inline unsigned short snd_rawmidi_file_flags(struct file *file) +{ + switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) { + case FMODE_WRITE: + return SNDRV_RAWMIDI_LFLG_OUTPUT; + case FMODE_READ: + return SNDRV_RAWMIDI_LFLG_INPUT; + default: + return SNDRV_RAWMIDI_LFLG_OPEN; + } +} + +static inline bool __snd_rawmidi_ready(struct snd_rawmidi_runtime *runtime) +{ + return runtime->avail >= runtime->avail_min; +} + +static bool snd_rawmidi_ready(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + unsigned long flags; + bool ready; + + spin_lock_irqsave(&runtime->lock, flags); + ready = __snd_rawmidi_ready(runtime); + spin_unlock_irqrestore(&runtime->lock, flags); + return ready; +} + +static inline int snd_rawmidi_ready_append(struct snd_rawmidi_substream *substream, + size_t count) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + return runtime->avail >= runtime->avail_min && + (!substream->append || runtime->avail >= count); +} + +static void snd_rawmidi_input_event_work(struct work_struct *work) +{ + struct snd_rawmidi_runtime *runtime = + container_of(work, struct snd_rawmidi_runtime, event_work); + + if (runtime->event) + runtime->event(runtime->substream); +} + +/* buffer refcount management: call with runtime->lock held */ +static inline void snd_rawmidi_buffer_ref(struct snd_rawmidi_runtime *runtime) +{ + runtime->buffer_ref++; +} + +static inline void snd_rawmidi_buffer_unref(struct snd_rawmidi_runtime *runtime) +{ + runtime->buffer_ref--; +} + +static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime; + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) + return -ENOMEM; + runtime->substream = substream; + spin_lock_init(&runtime->lock); + init_waitqueue_head(&runtime->sleep); + INIT_WORK(&runtime->event_work, snd_rawmidi_input_event_work); + runtime->event = NULL; + runtime->buffer_size = PAGE_SIZE; + runtime->avail_min = 1; + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + runtime->avail = 0; + else + runtime->avail = runtime->buffer_size; + runtime->buffer = kvzalloc(runtime->buffer_size, GFP_KERNEL); + if (!runtime->buffer) { + kfree(runtime); + return -ENOMEM; + } + runtime->appl_ptr = runtime->hw_ptr = 0; + substream->runtime = runtime; + return 0; +} + +static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + kvfree(runtime->buffer); + kfree(runtime); + substream->runtime = NULL; + return 0; +} + +static inline void snd_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (!substream->opened) + return; + substream->ops->trigger(substream, up); +} + +static void snd_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (!substream->opened) + return; + substream->ops->trigger(substream, up); + if (!up) + cancel_work_sync(&substream->runtime->event_work); +} + +static void __reset_runtime_ptrs(struct snd_rawmidi_runtime *runtime, + bool is_input) +{ + runtime->drain = 0; + runtime->appl_ptr = runtime->hw_ptr = 0; + runtime->avail = is_input ? 0 : runtime->buffer_size; +} + +static void reset_runtime_ptrs(struct snd_rawmidi_runtime *runtime, + bool is_input) +{ + unsigned long flags; + + spin_lock_irqsave(&runtime->lock, flags); + __reset_runtime_ptrs(runtime, is_input); + spin_unlock_irqrestore(&runtime->lock, flags); +} + +int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream) +{ + snd_rawmidi_output_trigger(substream, 0); + reset_runtime_ptrs(substream->runtime, false); + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_drop_output); + +int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) +{ + int err; + long timeout; + struct snd_rawmidi_runtime *runtime = substream->runtime; + + err = 0; + runtime->drain = 1; + timeout = wait_event_interruptible_timeout(runtime->sleep, + (runtime->avail >= runtime->buffer_size), + 10*HZ); + if (signal_pending(current)) + err = -ERESTARTSYS; + if (runtime->avail < runtime->buffer_size && !timeout) { + rmidi_warn(substream->rmidi, + "rawmidi drain error (avail = %li, buffer_size = %li)\n", + (long)runtime->avail, (long)runtime->buffer_size); + err = -EIO; + } + runtime->drain = 0; + if (err != -ERESTARTSYS) { + /* we need wait a while to make sure that Tx FIFOs are empty */ + if (substream->ops->drain) + substream->ops->drain(substream); + else + msleep(50); + snd_rawmidi_drop_output(substream); + } + return err; +} +EXPORT_SYMBOL(snd_rawmidi_drain_output); + +int snd_rawmidi_drain_input(struct snd_rawmidi_substream *substream) +{ + snd_rawmidi_input_trigger(substream, 0); + reset_runtime_ptrs(substream->runtime, true); + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_drain_input); + +/* look for an available substream for the given stream direction; + * if a specific subdevice is given, try to assign it + */ +static int assign_substream(struct snd_rawmidi *rmidi, int subdevice, + int stream, int mode, + struct snd_rawmidi_substream **sub_ret) +{ + struct snd_rawmidi_substream *substream; + struct snd_rawmidi_str *s = &rmidi->streams[stream]; + static unsigned int info_flags[2] = { + [SNDRV_RAWMIDI_STREAM_OUTPUT] = SNDRV_RAWMIDI_INFO_OUTPUT, + [SNDRV_RAWMIDI_STREAM_INPUT] = SNDRV_RAWMIDI_INFO_INPUT, + }; + + if (!(rmidi->info_flags & info_flags[stream])) + return -ENXIO; + if (subdevice >= 0 && subdevice >= s->substream_count) + return -ENODEV; + + list_for_each_entry(substream, &s->substreams, list) { + if (substream->opened) { + if (stream == SNDRV_RAWMIDI_STREAM_INPUT || + !(mode & SNDRV_RAWMIDI_LFLG_APPEND) || + !substream->append) + continue; + } + if (subdevice < 0 || subdevice == substream->number) { + *sub_ret = substream; + return 0; + } + } + return -EAGAIN; +} + +/* open and do ref-counting for the given substream */ +static int open_substream(struct snd_rawmidi *rmidi, + struct snd_rawmidi_substream *substream, + int mode) +{ + int err; + + if (substream->use_count == 0) { + err = snd_rawmidi_runtime_create(substream); + if (err < 0) + return err; + err = substream->ops->open(substream); + if (err < 0) { + snd_rawmidi_runtime_free(substream); + return err; + } + substream->opened = 1; + substream->active_sensing = 0; + if (mode & SNDRV_RAWMIDI_LFLG_APPEND) + substream->append = 1; + substream->pid = get_pid(task_pid(current)); + rmidi->streams[substream->stream].substream_opened++; + } + substream->use_count++; + return 0; +} + +static void close_substream(struct snd_rawmidi *rmidi, + struct snd_rawmidi_substream *substream, + int cleanup); + +static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, + struct snd_rawmidi_file *rfile) +{ + struct snd_rawmidi_substream *sinput = NULL, *soutput = NULL; + int err; + + rfile->input = rfile->output = NULL; + if (mode & SNDRV_RAWMIDI_LFLG_INPUT) { + err = assign_substream(rmidi, subdevice, + SNDRV_RAWMIDI_STREAM_INPUT, + mode, &sinput); + if (err < 0) + return err; + } + if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) { + err = assign_substream(rmidi, subdevice, + SNDRV_RAWMIDI_STREAM_OUTPUT, + mode, &soutput); + if (err < 0) + return err; + } + + if (sinput) { + err = open_substream(rmidi, sinput, mode); + if (err < 0) + return err; + } + if (soutput) { + err = open_substream(rmidi, soutput, mode); + if (err < 0) { + if (sinput) + close_substream(rmidi, sinput, 0); + return err; + } + } + + rfile->rmidi = rmidi; + rfile->input = sinput; + rfile->output = soutput; + return 0; +} + +/* called from sound/core/seq/seq_midi.c */ +int snd_rawmidi_kernel_open(struct snd_card *card, int device, int subdevice, + int mode, struct snd_rawmidi_file *rfile) +{ + struct snd_rawmidi *rmidi; + int err = 0; + + if (snd_BUG_ON(!rfile)) + return -EINVAL; + + mutex_lock(®ister_mutex); + rmidi = snd_rawmidi_search(card, device); + if (!rmidi) + err = -ENODEV; + else if (!try_module_get(rmidi->card->module)) + err = -ENXIO; + mutex_unlock(®ister_mutex); + if (err < 0) + return err; + + mutex_lock(&rmidi->open_mutex); + err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); + mutex_unlock(&rmidi->open_mutex); + if (err < 0) + module_put(rmidi->card->module); + return err; +} +EXPORT_SYMBOL(snd_rawmidi_kernel_open); + +static int snd_rawmidi_open(struct inode *inode, struct file *file) +{ + int maj = imajor(inode); + struct snd_card *card; + int subdevice; + unsigned short fflags; + int err; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_file *rawmidi_file = NULL; + wait_queue_entry_t wait; + + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + if (maj == snd_major) { + rmidi = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_RAWMIDI); +#ifdef CONFIG_SND_OSSEMUL + } else if (maj == SOUND_MAJOR) { + rmidi = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIDI); +#endif + } else + return -ENXIO; + + if (rmidi == NULL) + return -ENODEV; + + if (!try_module_get(rmidi->card->module)) { + snd_card_unref(rmidi->card); + return -ENXIO; + } + + mutex_lock(&rmidi->open_mutex); + card = rmidi->card; + err = snd_card_file_add(card, file); + if (err < 0) + goto __error_card; + fflags = snd_rawmidi_file_flags(file); + if ((file->f_flags & O_APPEND) || maj == SOUND_MAJOR) /* OSS emul? */ + fflags |= SNDRV_RAWMIDI_LFLG_APPEND; + rawmidi_file = kmalloc(sizeof(*rawmidi_file), GFP_KERNEL); + if (rawmidi_file == NULL) { + err = -ENOMEM; + goto __error; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&rmidi->open_wait, &wait); + while (1) { + subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_RAWMIDI); + err = rawmidi_open_priv(rmidi, subdevice, fflags, rawmidi_file); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&rmidi->open_mutex); + schedule(); + mutex_lock(&rmidi->open_mutex); + if (rmidi->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&rmidi->open_wait, &wait); + if (err < 0) { + kfree(rawmidi_file); + goto __error; + } +#ifdef CONFIG_SND_OSSEMUL + if (rawmidi_file->input && rawmidi_file->input->runtime) + rawmidi_file->input->runtime->oss = (maj == SOUND_MAJOR); + if (rawmidi_file->output && rawmidi_file->output->runtime) + rawmidi_file->output->runtime->oss = (maj == SOUND_MAJOR); +#endif + file->private_data = rawmidi_file; + mutex_unlock(&rmidi->open_mutex); + snd_card_unref(rmidi->card); + return 0; + + __error: + snd_card_file_remove(card, file); + __error_card: + mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + snd_card_unref(rmidi->card); + return err; +} + +static void close_substream(struct snd_rawmidi *rmidi, + struct snd_rawmidi_substream *substream, + int cleanup) +{ + if (--substream->use_count) + return; + + if (cleanup) { + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + snd_rawmidi_input_trigger(substream, 0); + else { + if (substream->active_sensing) { + unsigned char buf = 0xfe; + /* sending single active sensing message + * to shut the device up + */ + snd_rawmidi_kernel_write(substream, &buf, 1); + } + if (snd_rawmidi_drain_output(substream) == -ERESTARTSYS) + snd_rawmidi_output_trigger(substream, 0); + } + } + substream->ops->close(substream); + if (substream->runtime->private_free) + substream->runtime->private_free(substream); + snd_rawmidi_runtime_free(substream); + substream->opened = 0; + substream->append = 0; + put_pid(substream->pid); + substream->pid = NULL; + rmidi->streams[substream->stream].substream_opened--; +} + +static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) +{ + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; + mutex_lock(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; + } + if (rfile->output) { + close_substream(rmidi, rfile->output, 1); + rfile->output = NULL; + } + rfile->rmidi = NULL; + mutex_unlock(&rmidi->open_mutex); + wake_up(&rmidi->open_wait); +} + +/* called from sound/core/seq/seq_midi.c */ +int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) +{ + struct snd_rawmidi *rmidi; + + if (snd_BUG_ON(!rfile)) + return -ENXIO; + + rmidi = rfile->rmidi; + rawmidi_release_priv(rfile); + module_put(rmidi->card->module); + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_kernel_release); + +static int snd_rawmidi_release(struct inode *inode, struct file *file) +{ + struct snd_rawmidi_file *rfile; + struct snd_rawmidi *rmidi; + struct module *module; + + rfile = file->private_data; + rmidi = rfile->rmidi; + rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); + module_put(module); + return 0; +} + +static int snd_rawmidi_info(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_info *info) +{ + struct snd_rawmidi *rmidi; + + if (substream == NULL) + return -ENODEV; + rmidi = substream->rmidi; + memset(info, 0, sizeof(*info)); + info->card = rmidi->card->number; + info->device = rmidi->device; + info->subdevice = substream->number; + info->stream = substream->stream; + info->flags = rmidi->info_flags; + strcpy(info->id, rmidi->id); + strcpy(info->name, rmidi->name); + strcpy(info->subname, substream->name); + info->subdevices_count = substream->pstr->substream_count; + info->subdevices_avail = (substream->pstr->substream_count - + substream->pstr->substream_opened); + return 0; +} + +static int snd_rawmidi_info_user(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_info __user *_info) +{ + struct snd_rawmidi_info info; + int err; + + err = snd_rawmidi_info(substream, &info); + if (err < 0) + return err; + if (copy_to_user(_info, &info, sizeof(struct snd_rawmidi_info))) + return -EFAULT; + return 0; +} + +static int __snd_rawmidi_info_select(struct snd_card *card, + struct snd_rawmidi_info *info) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *pstr; + struct snd_rawmidi_substream *substream; + + rmidi = snd_rawmidi_search(card, info->device); + if (!rmidi) + return -ENXIO; + if (info->stream < 0 || info->stream > 1) + return -EINVAL; + info->stream = array_index_nospec(info->stream, 2); + pstr = &rmidi->streams[info->stream]; + if (pstr->substream_count == 0) + return -ENOENT; + if (info->subdevice >= pstr->substream_count) + return -ENXIO; + list_for_each_entry(substream, &pstr->substreams, list) { + if ((unsigned int)substream->number == info->subdevice) + return snd_rawmidi_info(substream, info); + } + return -ENXIO; +} + +int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info) +{ + int ret; + + mutex_lock(®ister_mutex); + ret = __snd_rawmidi_info_select(card, info); + mutex_unlock(®ister_mutex); + return ret; +} +EXPORT_SYMBOL(snd_rawmidi_info_select); + +static int snd_rawmidi_info_select_user(struct snd_card *card, + struct snd_rawmidi_info __user *_info) +{ + int err; + struct snd_rawmidi_info info; + + if (get_user(info.device, &_info->device)) + return -EFAULT; + if (get_user(info.stream, &_info->stream)) + return -EFAULT; + if (get_user(info.subdevice, &_info->subdevice)) + return -EFAULT; + err = snd_rawmidi_info_select(card, &info); + if (err < 0) + return err; + if (copy_to_user(_info, &info, sizeof(struct snd_rawmidi_info))) + return -EFAULT; + return 0; +} + +static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime, + struct snd_rawmidi_params *params, + bool is_input) +{ + char *newbuf, *oldbuf; + + if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) + return -EINVAL; + if (params->avail_min < 1 || params->avail_min > params->buffer_size) + return -EINVAL; + if (params->buffer_size != runtime->buffer_size) { + newbuf = kvzalloc(params->buffer_size, GFP_KERNEL); + if (!newbuf) + return -ENOMEM; + spin_lock_irq(&runtime->lock); + if (runtime->buffer_ref) { + spin_unlock_irq(&runtime->lock); + kvfree(newbuf); + return -EBUSY; + } + oldbuf = runtime->buffer; + runtime->buffer = newbuf; + runtime->buffer_size = params->buffer_size; + __reset_runtime_ptrs(runtime, is_input); + spin_unlock_irq(&runtime->lock); + kvfree(oldbuf); + } + runtime->avail_min = params->avail_min; + return 0; +} + +int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params) +{ + if (substream->append && substream->use_count > 1) + return -EBUSY; + snd_rawmidi_drain_output(substream); + substream->active_sensing = !params->no_active_sensing; + return resize_runtime_buffer(substream->runtime, params, false); +} +EXPORT_SYMBOL(snd_rawmidi_output_params); + +int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params) +{ + snd_rawmidi_drain_input(substream); + return resize_runtime_buffer(substream->runtime, params, true); +} +EXPORT_SYMBOL(snd_rawmidi_input_params); + +static int snd_rawmidi_output_status(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_status *status) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + memset(status, 0, sizeof(*status)); + status->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + spin_lock_irq(&runtime->lock); + status->avail = runtime->avail; + spin_unlock_irq(&runtime->lock); + return 0; +} + +static int snd_rawmidi_input_status(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_status *status) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + memset(status, 0, sizeof(*status)); + status->stream = SNDRV_RAWMIDI_STREAM_INPUT; + spin_lock_irq(&runtime->lock); + status->avail = runtime->avail; + status->xruns = runtime->xruns; + runtime->xruns = 0; + spin_unlock_irq(&runtime->lock); + return 0; +} + +static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_rawmidi_file *rfile; + void __user *argp = (void __user *)arg; + + rfile = file->private_data; + if (((cmd >> 8) & 0xff) != 'W') + return -ENOTTY; + switch (cmd) { + case SNDRV_RAWMIDI_IOCTL_PVERSION: + return put_user(SNDRV_RAWMIDI_VERSION, (int __user *)argp) ? -EFAULT : 0; + case SNDRV_RAWMIDI_IOCTL_INFO: + { + int stream; + struct snd_rawmidi_info __user *info = argp; + + if (get_user(stream, &info->stream)) + return -EFAULT; + switch (stream) { + case SNDRV_RAWMIDI_STREAM_INPUT: + return snd_rawmidi_info_user(rfile->input, info); + case SNDRV_RAWMIDI_STREAM_OUTPUT: + return snd_rawmidi_info_user(rfile->output, info); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_PARAMS: + { + struct snd_rawmidi_params params; + + if (copy_from_user(¶ms, argp, sizeof(struct snd_rawmidi_params))) + return -EFAULT; + switch (params.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_output_params(rfile->output, ¶ms); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + return snd_rawmidi_input_params(rfile->input, ¶ms); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_STATUS: + { + int err = 0; + struct snd_rawmidi_status status; + + if (copy_from_user(&status, argp, sizeof(struct snd_rawmidi_status))) + return -EFAULT; + switch (status.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + err = snd_rawmidi_output_status(rfile->output, &status); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + err = snd_rawmidi_input_status(rfile->input, &status); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + if (copy_to_user(argp, &status, sizeof(struct snd_rawmidi_status))) + return -EFAULT; + return 0; + } + case SNDRV_RAWMIDI_IOCTL_DROP: + { + int val; + + if (get_user(val, (int __user *) argp)) + return -EFAULT; + switch (val) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_drop_output(rfile->output); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_DRAIN: + { + int val; + + if (get_user(val, (int __user *) argp)) + return -EFAULT; + switch (val) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_drain_output(rfile->output); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + return snd_rawmidi_drain_input(rfile->input); + default: + return -EINVAL; + } + } + default: + rmidi_dbg(rfile->rmidi, + "rawmidi: unknown command = 0x%x\n", cmd); + } + return -ENOTTY; +} + +static int snd_rawmidi_control_ioctl(struct snd_card *card, + struct snd_ctl_file *control, + unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)argp)) + return -EFAULT; + if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */ + device = SNDRV_RAWMIDI_DEVICES - 1; + mutex_lock(®ister_mutex); + device = device < 0 ? 0 : device + 1; + while (device < SNDRV_RAWMIDI_DEVICES) { + if (snd_rawmidi_search(card, device)) + break; + device++; + } + if (device == SNDRV_RAWMIDI_DEVICES) + device = -1; + mutex_unlock(®ister_mutex); + if (put_user(device, (int __user *)argp)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: + { + int val; + + if (get_user(val, (int __user *)argp)) + return -EFAULT; + control->preferred_subdevice[SND_CTL_SUBDEV_RAWMIDI] = val; + return 0; + } + case SNDRV_CTL_IOCTL_RAWMIDI_INFO: + return snd_rawmidi_info_select_user(card, argp); + } + return -ENOIOCTLCMD; +} + +/** + * snd_rawmidi_receive - receive the input data from the device + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: the data size to read + * + * Reads the data from the internal buffer. + * + * Return: The size of read data, or a negative error code on failure. + */ +int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, + const unsigned char *buffer, int count) +{ + unsigned long flags; + int result = 0, count1; + struct snd_rawmidi_runtime *runtime = substream->runtime; + + if (!substream->opened) + return -EBADFD; + if (runtime->buffer == NULL) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_receive: input is not active!!!\n"); + return -EINVAL; + } + spin_lock_irqsave(&runtime->lock, flags); + if (count == 1) { /* special case, faster code */ + substream->bytes++; + if (runtime->avail < runtime->buffer_size) { + runtime->buffer[runtime->hw_ptr++] = buffer[0]; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail++; + result++; + } else { + runtime->xruns++; + } + } else { + substream->bytes += count; + count1 = runtime->buffer_size - runtime->hw_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) + count1 = runtime->buffer_size - runtime->avail; + memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1); + runtime->hw_ptr += count1; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail += count1; + count -= count1; + result += count1; + if (count > 0) { + buffer += count1; + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) { + count1 = runtime->buffer_size - runtime->avail; + runtime->xruns += count - count1; + } + if (count1 > 0) { + memcpy(runtime->buffer, buffer, count1); + runtime->hw_ptr = count1; + runtime->avail += count1; + result += count1; + } + } + } + if (result > 0) { + if (runtime->event) + schedule_work(&runtime->event_work); + else if (__snd_rawmidi_ready(runtime)) + wake_up(&runtime->sleep); + } + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_receive); + +static long snd_rawmidi_kernel_read1(struct snd_rawmidi_substream *substream, + unsigned char __user *userbuf, + unsigned char *kernelbuf, long count) +{ + unsigned long flags; + long result = 0, count1; + struct snd_rawmidi_runtime *runtime = substream->runtime; + unsigned long appl_ptr; + int err = 0; + + spin_lock_irqsave(&runtime->lock, flags); + snd_rawmidi_buffer_ref(runtime); + while (count > 0 && runtime->avail) { + count1 = runtime->buffer_size - runtime->appl_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)runtime->avail) + count1 = runtime->avail; + + /* update runtime->appl_ptr before unlocking for userbuf */ + appl_ptr = runtime->appl_ptr; + runtime->appl_ptr += count1; + runtime->appl_ptr %= runtime->buffer_size; + runtime->avail -= count1; + + if (kernelbuf) + memcpy(kernelbuf + result, runtime->buffer + appl_ptr, count1); + if (userbuf) { + spin_unlock_irqrestore(&runtime->lock, flags); + if (copy_to_user(userbuf + result, + runtime->buffer + appl_ptr, count1)) + err = -EFAULT; + spin_lock_irqsave(&runtime->lock, flags); + if (err) + goto out; + } + result += count1; + count -= count1; + } + out: + snd_rawmidi_buffer_unref(runtime); + spin_unlock_irqrestore(&runtime->lock, flags); + return result > 0 ? result : err; +} + +long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, + unsigned char *buf, long count) +{ + snd_rawmidi_input_trigger(substream, 1); + return snd_rawmidi_kernel_read1(substream, NULL/*userbuf*/, buf, count); +} +EXPORT_SYMBOL(snd_rawmidi_kernel_read); + +static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + long result; + int count1; + struct snd_rawmidi_file *rfile; + struct snd_rawmidi_substream *substream; + struct snd_rawmidi_runtime *runtime; + + rfile = file->private_data; + substream = rfile->input; + if (substream == NULL) + return -EIO; + runtime = substream->runtime; + snd_rawmidi_input_trigger(substream, 1); + result = 0; + while (count > 0) { + spin_lock_irq(&runtime->lock); + while (!__snd_rawmidi_ready(runtime)) { + wait_queue_entry_t wait; + + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + spin_unlock_irq(&runtime->lock); + return result > 0 ? result : -EAGAIN; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&runtime->lock); + schedule(); + remove_wait_queue(&runtime->sleep, &wait); + if (rfile->rmidi->card->shutdown) + return -ENODEV; + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + spin_lock_irq(&runtime->lock); + if (!runtime->avail) { + spin_unlock_irq(&runtime->lock); + return result > 0 ? result : -EIO; + } + } + spin_unlock_irq(&runtime->lock); + count1 = snd_rawmidi_kernel_read1(substream, + (unsigned char __user *)buf, + NULL/*kernelbuf*/, + count); + if (count1 < 0) + return result > 0 ? result : count1; + result += count1; + buf += count1; + count -= count1; + } + return result; +} + +/** + * snd_rawmidi_transmit_empty - check whether the output buffer is empty + * @substream: the rawmidi substream + * + * Return: 1 if the internal output buffer is empty, 0 if not. + */ +int snd_rawmidi_transmit_empty(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + int result; + unsigned long flags; + + if (runtime->buffer == NULL) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_transmit_empty: output is not active!!!\n"); + return 1; + } + spin_lock_irqsave(&runtime->lock, flags); + result = runtime->avail >= runtime->buffer_size; + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit_empty); + +/** + * __snd_rawmidi_transmit_peek - copy data from the internal buffer + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: data size to transfer + * + * This is a variant of snd_rawmidi_transmit_peek() without spinlock. + */ +int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, + unsigned char *buffer, int count) +{ + int result, count1; + struct snd_rawmidi_runtime *runtime = substream->runtime; + + if (runtime->buffer == NULL) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_transmit_peek: output is not active!!!\n"); + return -EINVAL; + } + result = 0; + if (runtime->avail >= runtime->buffer_size) { + /* warning: lowlevel layer MUST trigger down the hardware */ + goto __skip; + } + if (count == 1) { /* special case, faster code */ + *buffer = runtime->buffer[runtime->hw_ptr]; + result++; + } else { + count1 = runtime->buffer_size - runtime->hw_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) + count1 = runtime->buffer_size - runtime->avail; + memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1); + count -= count1; + result += count1; + if (count > 0) { + if (count > (int)(runtime->buffer_size - runtime->avail - count1)) + count = runtime->buffer_size - runtime->avail - count1; + memcpy(buffer + count1, runtime->buffer, count); + result += count; + } + } + __skip: + return result; +} +EXPORT_SYMBOL(__snd_rawmidi_transmit_peek); + +/** + * snd_rawmidi_transmit_peek - copy data from the internal buffer + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: data size to transfer + * + * Copies data from the internal output buffer to the given buffer. + * + * Call this in the interrupt handler when the midi output is ready, + * and call snd_rawmidi_transmit_ack() after the transmission is + * finished. + * + * Return: The size of copied data, or a negative error code on failure. + */ +int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, + unsigned char *buffer, int count) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + int result; + unsigned long flags; + + spin_lock_irqsave(&runtime->lock, flags); + result = __snd_rawmidi_transmit_peek(substream, buffer, count); + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit_peek); + +/** + * __snd_rawmidi_transmit_ack - acknowledge the transmission + * @substream: the rawmidi substream + * @count: the transferred count + * + * This is a variant of __snd_rawmidi_transmit_ack() without spinlock. + */ +int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + if (runtime->buffer == NULL) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_transmit_ack: output is not active!!!\n"); + return -EINVAL; + } + snd_BUG_ON(runtime->avail + count > runtime->buffer_size); + runtime->hw_ptr += count; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail += count; + substream->bytes += count; + if (count > 0) { + if (runtime->drain || __snd_rawmidi_ready(runtime)) + wake_up(&runtime->sleep); + } + return count; +} +EXPORT_SYMBOL(__snd_rawmidi_transmit_ack); + +/** + * snd_rawmidi_transmit_ack - acknowledge the transmission + * @substream: the rawmidi substream + * @count: the transferred count + * + * Advances the hardware pointer for the internal output buffer with + * the given size and updates the condition. + * Call after the transmission is finished. + * + * Return: The advanced size if successful, or a negative error code on failure. + */ +int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + int result; + unsigned long flags; + + spin_lock_irqsave(&runtime->lock, flags); + result = __snd_rawmidi_transmit_ack(substream, count); + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit_ack); + +/** + * snd_rawmidi_transmit - copy from the buffer to the device + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: the data size to transfer + * + * Copies data from the buffer to the device and advances the pointer. + * + * Return: The copied size if successful, or a negative error code on failure. + */ +int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, + unsigned char *buffer, int count) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + int result; + unsigned long flags; + + spin_lock_irqsave(&runtime->lock, flags); + if (!substream->opened) + result = -EBADFD; + else { + count = __snd_rawmidi_transmit_peek(substream, buffer, count); + if (count <= 0) + result = count; + else + result = __snd_rawmidi_transmit_ack(substream, count); + } + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit); + +static long snd_rawmidi_kernel_write1(struct snd_rawmidi_substream *substream, + const unsigned char __user *userbuf, + const unsigned char *kernelbuf, + long count) +{ + unsigned long flags; + long count1, result; + struct snd_rawmidi_runtime *runtime = substream->runtime; + unsigned long appl_ptr; + + if (!kernelbuf && !userbuf) + return -EINVAL; + if (snd_BUG_ON(!runtime->buffer)) + return -EINVAL; + + result = 0; + spin_lock_irqsave(&runtime->lock, flags); + if (substream->append) { + if ((long)runtime->avail < count) { + spin_unlock_irqrestore(&runtime->lock, flags); + return -EAGAIN; + } + } + snd_rawmidi_buffer_ref(runtime); + while (count > 0 && runtime->avail > 0) { + count1 = runtime->buffer_size - runtime->appl_ptr; + if (count1 > count) + count1 = count; + if (count1 > (long)runtime->avail) + count1 = runtime->avail; + + /* update runtime->appl_ptr before unlocking for userbuf */ + appl_ptr = runtime->appl_ptr; + runtime->appl_ptr += count1; + runtime->appl_ptr %= runtime->buffer_size; + runtime->avail -= count1; + + if (kernelbuf) + memcpy(runtime->buffer + appl_ptr, + kernelbuf + result, count1); + else if (userbuf) { + spin_unlock_irqrestore(&runtime->lock, flags); + if (copy_from_user(runtime->buffer + appl_ptr, + userbuf + result, count1)) { + spin_lock_irqsave(&runtime->lock, flags); + result = result > 0 ? result : -EFAULT; + goto __end; + } + spin_lock_irqsave(&runtime->lock, flags); + } + result += count1; + count -= count1; + } + __end: + count1 = runtime->avail < runtime->buffer_size; + snd_rawmidi_buffer_unref(runtime); + spin_unlock_irqrestore(&runtime->lock, flags); + if (count1) + snd_rawmidi_output_trigger(substream, 1); + return result; +} + +long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, + const unsigned char *buf, long count) +{ + return snd_rawmidi_kernel_write1(substream, NULL, buf, count); +} +EXPORT_SYMBOL(snd_rawmidi_kernel_write); + +static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + long result, timeout; + int count1; + struct snd_rawmidi_file *rfile; + struct snd_rawmidi_runtime *runtime; + struct snd_rawmidi_substream *substream; + + rfile = file->private_data; + substream = rfile->output; + runtime = substream->runtime; + /* we cannot put an atomic message to our buffer */ + if (substream->append && count > runtime->buffer_size) + return -EIO; + result = 0; + while (count > 0) { + spin_lock_irq(&runtime->lock); + while (!snd_rawmidi_ready_append(substream, count)) { + wait_queue_entry_t wait; + + if (file->f_flags & O_NONBLOCK) { + spin_unlock_irq(&runtime->lock); + return result > 0 ? result : -EAGAIN; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&runtime->lock); + timeout = schedule_timeout(30 * HZ); + remove_wait_queue(&runtime->sleep, &wait); + if (rfile->rmidi->card->shutdown) + return -ENODEV; + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + spin_lock_irq(&runtime->lock); + if (!runtime->avail && !timeout) { + spin_unlock_irq(&runtime->lock); + return result > 0 ? result : -EIO; + } + } + spin_unlock_irq(&runtime->lock); + count1 = snd_rawmidi_kernel_write1(substream, buf, NULL, count); + if (count1 < 0) + return result > 0 ? result : count1; + result += count1; + buf += count1; + if ((size_t)count1 < count && (file->f_flags & O_NONBLOCK)) + break; + count -= count1; + } + if (file->f_flags & O_DSYNC) { + spin_lock_irq(&runtime->lock); + while (runtime->avail != runtime->buffer_size) { + wait_queue_entry_t wait; + unsigned int last_avail = runtime->avail; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&runtime->lock); + timeout = schedule_timeout(30 * HZ); + remove_wait_queue(&runtime->sleep, &wait); + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + if (runtime->avail == last_avail && !timeout) + return result > 0 ? result : -EIO; + spin_lock_irq(&runtime->lock); + } + spin_unlock_irq(&runtime->lock); + } + return result; +} + +static __poll_t snd_rawmidi_poll(struct file *file, poll_table *wait) +{ + struct snd_rawmidi_file *rfile; + struct snd_rawmidi_runtime *runtime; + __poll_t mask; + + rfile = file->private_data; + if (rfile->input != NULL) { + runtime = rfile->input->runtime; + snd_rawmidi_input_trigger(rfile->input, 1); + poll_wait(file, &runtime->sleep, wait); + } + if (rfile->output != NULL) { + runtime = rfile->output->runtime; + poll_wait(file, &runtime->sleep, wait); + } + mask = 0; + if (rfile->input != NULL) { + if (snd_rawmidi_ready(rfile->input)) + mask |= EPOLLIN | EPOLLRDNORM; + } + if (rfile->output != NULL) { + if (snd_rawmidi_ready(rfile->output)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + return mask; +} + +/* + */ +#ifdef CONFIG_COMPAT +#include "rawmidi_compat.c" +#else +#define snd_rawmidi_ioctl_compat NULL +#endif + +/* + */ + +static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream; + struct snd_rawmidi_runtime *runtime; + unsigned long buffer_size, avail, xruns; + + rmidi = entry->private_data; + snd_iprintf(buffer, "%s\n\n", rmidi->name); + mutex_lock(&rmidi->open_mutex); + if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) { + list_for_each_entry(substream, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams, + list) { + snd_iprintf(buffer, + "Output %d\n" + " Tx bytes : %lu\n", + substream->number, + (unsigned long) substream->bytes); + if (substream->opened) { + snd_iprintf(buffer, + " Owner PID : %d\n", + pid_vnr(substream->pid)); + runtime = substream->runtime; + spin_lock_irq(&runtime->lock); + buffer_size = runtime->buffer_size; + avail = runtime->avail; + spin_unlock_irq(&runtime->lock); + snd_iprintf(buffer, + " Mode : %s\n" + " Buffer size : %lu\n" + " Avail : %lu\n", + runtime->oss ? "OSS compatible" : "native", + buffer_size, avail); + } + } + } + if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT) { + list_for_each_entry(substream, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams, + list) { + snd_iprintf(buffer, + "Input %d\n" + " Rx bytes : %lu\n", + substream->number, + (unsigned long) substream->bytes); + if (substream->opened) { + snd_iprintf(buffer, + " Owner PID : %d\n", + pid_vnr(substream->pid)); + runtime = substream->runtime; + spin_lock_irq(&runtime->lock); + buffer_size = runtime->buffer_size; + avail = runtime->avail; + xruns = runtime->xruns; + spin_unlock_irq(&runtime->lock); + snd_iprintf(buffer, + " Buffer size : %lu\n" + " Avail : %lu\n" + " Overruns : %lu\n", + buffer_size, avail, xruns); + } + } + } + mutex_unlock(&rmidi->open_mutex); +} + +/* + * Register functions + */ + +static const struct file_operations snd_rawmidi_f_ops = { + .owner = THIS_MODULE, + .read = snd_rawmidi_read, + .write = snd_rawmidi_write, + .open = snd_rawmidi_open, + .release = snd_rawmidi_release, + .llseek = no_llseek, + .poll = snd_rawmidi_poll, + .unlocked_ioctl = snd_rawmidi_ioctl, + .compat_ioctl = snd_rawmidi_ioctl_compat, +}; + +static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, + struct snd_rawmidi_str *stream, + int direction, + int count) +{ + struct snd_rawmidi_substream *substream; + int idx; + + for (idx = 0; idx < count; idx++) { + substream = kzalloc(sizeof(*substream), GFP_KERNEL); + if (!substream) + return -ENOMEM; + substream->stream = direction; + substream->number = idx; + substream->rmidi = rmidi; + substream->pstr = stream; + list_add_tail(&substream->list, &stream->substreams); + stream->substream_count++; + } + return 0; +} + +static void release_rawmidi_device(struct device *dev) +{ + kfree(container_of(dev, struct snd_rawmidi, dev)); +} + +/** + * snd_rawmidi_new - create a rawmidi instance + * @card: the card instance + * @id: the id string + * @device: the device index + * @output_count: the number of output streams + * @input_count: the number of input streams + * @rrawmidi: the pointer to store the new rawmidi instance + * + * Creates a new rawmidi instance. + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_rawmidi_new(struct snd_card *card, char *id, int device, + int output_count, int input_count, + struct snd_rawmidi **rrawmidi) +{ + struct snd_rawmidi *rmidi; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_rawmidi_dev_free, + .dev_register = snd_rawmidi_dev_register, + .dev_disconnect = snd_rawmidi_dev_disconnect, + }; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (rrawmidi) + *rrawmidi = NULL; + rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); + if (!rmidi) + return -ENOMEM; + rmidi->card = card; + rmidi->device = device; + mutex_init(&rmidi->open_mutex); + init_waitqueue_head(&rmidi->open_wait); + INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams); + INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams); + + if (id != NULL) + strlcpy(rmidi->id, id, sizeof(rmidi->id)); + + snd_device_initialize(&rmidi->dev, card); + rmidi->dev.release = release_rawmidi_device; + dev_set_name(&rmidi->dev, "midiC%iD%i", card->number, device); + + err = snd_rawmidi_alloc_substreams(rmidi, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], + SNDRV_RAWMIDI_STREAM_INPUT, + input_count); + if (err < 0) + goto error; + err = snd_rawmidi_alloc_substreams(rmidi, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], + SNDRV_RAWMIDI_STREAM_OUTPUT, + output_count); + if (err < 0) + goto error; + err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops); + if (err < 0) + goto error; + + if (rrawmidi) + *rrawmidi = rmidi; + return 0; + + error: + snd_rawmidi_free(rmidi); + return err; +} +EXPORT_SYMBOL(snd_rawmidi_new); + +static void snd_rawmidi_free_substreams(struct snd_rawmidi_str *stream) +{ + struct snd_rawmidi_substream *substream; + + while (!list_empty(&stream->substreams)) { + substream = list_entry(stream->substreams.next, struct snd_rawmidi_substream, list); + list_del(&substream->list); + kfree(substream); + } +} + +static int snd_rawmidi_free(struct snd_rawmidi *rmidi) +{ + if (!rmidi) + return 0; + + snd_info_free_entry(rmidi->proc_entry); + rmidi->proc_entry = NULL; + mutex_lock(®ister_mutex); + if (rmidi->ops && rmidi->ops->dev_unregister) + rmidi->ops->dev_unregister(rmidi); + mutex_unlock(®ister_mutex); + + snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]); + snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]); + if (rmidi->private_free) + rmidi->private_free(rmidi); + put_device(&rmidi->dev); + return 0; +} + +static int snd_rawmidi_dev_free(struct snd_device *device) +{ + struct snd_rawmidi *rmidi = device->device_data; + + return snd_rawmidi_free(rmidi); +} + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +static void snd_rawmidi_dev_seq_free(struct snd_seq_device *device) +{ + struct snd_rawmidi *rmidi = device->private_data; + + rmidi->seq_dev = NULL; +} +#endif + +static int snd_rawmidi_dev_register(struct snd_device *device) +{ + int err; + struct snd_info_entry *entry; + char name[16]; + struct snd_rawmidi *rmidi = device->device_data; + + if (rmidi->device >= SNDRV_RAWMIDI_DEVICES) + return -ENOMEM; + err = 0; + mutex_lock(®ister_mutex); + if (snd_rawmidi_search(rmidi->card, rmidi->device)) + err = -EBUSY; + else + list_add_tail(&rmidi->list, &snd_rawmidi_devices); + mutex_unlock(®ister_mutex); + if (err < 0) + return err; + + err = snd_register_device(SNDRV_DEVICE_TYPE_RAWMIDI, + rmidi->card, rmidi->device, + &snd_rawmidi_f_ops, rmidi, &rmidi->dev); + if (err < 0) { + rmidi_err(rmidi, "unable to register\n"); + goto error; + } + if (rmidi->ops && rmidi->ops->dev_register) { + err = rmidi->ops->dev_register(rmidi); + if (err < 0) + goto error_unregister; + } +#ifdef CONFIG_SND_OSSEMUL + rmidi->ossreg = 0; + if ((int)rmidi->device == midi_map[rmidi->card->number]) { + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, + rmidi->card, 0, &snd_rawmidi_f_ops, + rmidi) < 0) { + rmidi_err(rmidi, + "unable to register OSS rawmidi device %i:%i\n", + rmidi->card->number, 0); + } else { + rmidi->ossreg++; +#ifdef SNDRV_OSS_INFO_DEV_MIDI + snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number, rmidi->name); +#endif + } + } + if ((int)rmidi->device == amidi_map[rmidi->card->number]) { + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, + rmidi->card, 1, &snd_rawmidi_f_ops, + rmidi) < 0) { + rmidi_err(rmidi, + "unable to register OSS rawmidi device %i:%i\n", + rmidi->card->number, 1); + } else { + rmidi->ossreg++; + } + } +#endif /* CONFIG_SND_OSSEMUL */ + sprintf(name, "midi%d", rmidi->device); + entry = snd_info_create_card_entry(rmidi->card, name, rmidi->card->proc_root); + if (entry) { + entry->private_data = rmidi; + entry->c.text.read = snd_rawmidi_proc_info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + rmidi->proc_entry = entry; +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (!rmidi->ops || !rmidi->ops->dev_register) { /* own registration mechanism */ + if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) { + rmidi->seq_dev->private_data = rmidi; + rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free; + sprintf(rmidi->seq_dev->name, "MIDI %d-%d", rmidi->card->number, rmidi->device); + snd_device_register(rmidi->card, rmidi->seq_dev); + } + } +#endif + return 0; + + error_unregister: + snd_unregister_device(&rmidi->dev); + error: + mutex_lock(®ister_mutex); + list_del(&rmidi->list); + mutex_unlock(®ister_mutex); + return err; +} + +static int snd_rawmidi_dev_disconnect(struct snd_device *device) +{ + struct snd_rawmidi *rmidi = device->device_data; + int dir; + + mutex_lock(®ister_mutex); + mutex_lock(&rmidi->open_mutex); + wake_up(&rmidi->open_wait); + list_del_init(&rmidi->list); + for (dir = 0; dir < 2; dir++) { + struct snd_rawmidi_substream *s; + + list_for_each_entry(s, &rmidi->streams[dir].substreams, list) { + if (s->runtime) + wake_up(&s->runtime->sleep); + } + } + +#ifdef CONFIG_SND_OSSEMUL + if (rmidi->ossreg) { + if ((int)rmidi->device == midi_map[rmidi->card->number]) { + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 0); +#ifdef SNDRV_OSS_INFO_DEV_MIDI + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number); +#endif + } + if ((int)rmidi->device == amidi_map[rmidi->card->number]) + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 1); + rmidi->ossreg = 0; + } +#endif /* CONFIG_SND_OSSEMUL */ + snd_unregister_device(&rmidi->dev); + mutex_unlock(&rmidi->open_mutex); + mutex_unlock(®ister_mutex); + return 0; +} + +/** + * snd_rawmidi_set_ops - set the rawmidi operators + * @rmidi: the rawmidi instance + * @stream: the stream direction, SNDRV_RAWMIDI_STREAM_XXX + * @ops: the operator table + * + * Sets the rawmidi operators for the given stream direction. + */ +void snd_rawmidi_set_ops(struct snd_rawmidi *rmidi, int stream, + const struct snd_rawmidi_ops *ops) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &rmidi->streams[stream].substreams, list) + substream->ops = ops; +} +EXPORT_SYMBOL(snd_rawmidi_set_ops); + +/* + * ENTRY functions + */ + +static int __init alsa_rawmidi_init(void) +{ + + snd_ctl_register_ioctl(snd_rawmidi_control_ioctl); + snd_ctl_register_ioctl_compat(snd_rawmidi_control_ioctl); +#ifdef CONFIG_SND_OSSEMUL + { int i; + /* check device map table */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (midi_map[i] < 0 || midi_map[i] >= SNDRV_RAWMIDI_DEVICES) { + pr_err("ALSA: rawmidi: invalid midi_map[%d] = %d\n", + i, midi_map[i]); + midi_map[i] = 0; + } + if (amidi_map[i] < 0 || amidi_map[i] >= SNDRV_RAWMIDI_DEVICES) { + pr_err("ALSA: rawmidi: invalid amidi_map[%d] = %d\n", + i, amidi_map[i]); + amidi_map[i] = 1; + } + } + } +#endif /* CONFIG_SND_OSSEMUL */ + return 0; +} + +static void __exit alsa_rawmidi_exit(void) +{ + snd_ctl_unregister_ioctl(snd_rawmidi_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_rawmidi_control_ioctl); +} + +module_init(alsa_rawmidi_init) +module_exit(alsa_rawmidi_exit) diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c new file mode 100644 index 000000000..e30e30ba6 --- /dev/null +++ b/sound/core/rawmidi_compat.c @@ -0,0 +1,178 @@ +/* + * 32bit -> 64bit ioctl wrapper for raw MIDI API + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from rawmidi.c */ + +#include + +struct snd_rawmidi_params32 { + s32 stream; + u32 buffer_size; + u32 avail_min; + unsigned int no_active_sensing; /* avoid bit-field */ + unsigned char reserved[16]; +} __attribute__((packed)); + +static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile, + struct snd_rawmidi_params32 __user *src) +{ + struct snd_rawmidi_params params; + unsigned int val; + + if (get_user(params.stream, &src->stream) || + get_user(params.buffer_size, &src->buffer_size) || + get_user(params.avail_min, &src->avail_min) || + get_user(val, &src->no_active_sensing)) + return -EFAULT; + params.no_active_sensing = val; + switch (params.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (!rfile->output) + return -EINVAL; + return snd_rawmidi_output_params(rfile->output, ¶ms); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (!rfile->input) + return -EINVAL; + return snd_rawmidi_input_params(rfile->input, ¶ms); + } + return -EINVAL; +} + +struct snd_rawmidi_status32 { + s32 stream; + struct compat_timespec tstamp; + u32 avail; + u32 xruns; + unsigned char reserved[16]; +} __attribute__((packed)); + +static int snd_rawmidi_ioctl_status_compat(struct snd_rawmidi_file *rfile, + struct snd_rawmidi_status32 __user *src) +{ + int err; + struct snd_rawmidi_status status; + + if (get_user(status.stream, &src->stream)) + return -EFAULT; + + switch (status.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (!rfile->output) + return -EINVAL; + err = snd_rawmidi_output_status(rfile->output, &status); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + if (!rfile->input) + return -EINVAL; + err = snd_rawmidi_input_status(rfile->input, &status); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + + if (compat_put_timespec(&status.tstamp, &src->tstamp) || + put_user(status.avail, &src->avail) || + put_user(status.xruns, &src->xruns)) + return -EFAULT; + + return 0; +} + +#ifdef CONFIG_X86_X32 +/* X32 ABI has 64bit timespec and 64bit alignment */ +struct snd_rawmidi_status_x32 { + s32 stream; + u32 rsvd; /* alignment */ + struct timespec tstamp; + u32 avail; + u32 xruns; + unsigned char reserved[16]; +} __attribute__((packed)); + +#define put_timespec(src, dst) copy_to_user(dst, src, sizeof(*dst)) + +static int snd_rawmidi_ioctl_status_x32(struct snd_rawmidi_file *rfile, + struct snd_rawmidi_status_x32 __user *src) +{ + int err; + struct snd_rawmidi_status status; + + if (get_user(status.stream, &src->stream)) + return -EFAULT; + + switch (status.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (!rfile->output) + return -EINVAL; + err = snd_rawmidi_output_status(rfile->output, &status); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + if (!rfile->input) + return -EINVAL; + err = snd_rawmidi_input_status(rfile->input, &status); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + + if (put_timespec(&status.tstamp, &src->tstamp) || + put_user(status.avail, &src->avail) || + put_user(status.xruns, &src->xruns)) + return -EFAULT; + + return 0; +} +#endif /* CONFIG_X86_X32 */ + +enum { + SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct snd_rawmidi_params32), + SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct snd_rawmidi_status32), +#ifdef CONFIG_X86_X32 + SNDRV_RAWMIDI_IOCTL_STATUS_X32 = _IOWR('W', 0x20, struct snd_rawmidi_status_x32), +#endif /* CONFIG_X86_X32 */ +}; + +static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_rawmidi_file *rfile; + void __user *argp = compat_ptr(arg); + + rfile = file->private_data; + switch (cmd) { + case SNDRV_RAWMIDI_IOCTL_PVERSION: + case SNDRV_RAWMIDI_IOCTL_INFO: + case SNDRV_RAWMIDI_IOCTL_DROP: + case SNDRV_RAWMIDI_IOCTL_DRAIN: + return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_RAWMIDI_IOCTL_PARAMS32: + return snd_rawmidi_ioctl_params_compat(rfile, argp); + case SNDRV_RAWMIDI_IOCTL_STATUS32: + return snd_rawmidi_ioctl_status_compat(rfile, argp); +#ifdef CONFIG_X86_X32 + case SNDRV_RAWMIDI_IOCTL_STATUS_X32: + return snd_rawmidi_ioctl_status_x32(rfile, argp); +#endif /* CONFIG_X86_X32 */ + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig new file mode 100644 index 000000000..45c1336c6 --- /dev/null +++ b/sound/core/seq/Kconfig @@ -0,0 +1,62 @@ +config SND_SEQUENCER + tristate "Sequencer support" + select SND_TIMER + select SND_SEQ_DEVICE + help + Say Y or M to enable MIDI sequencer and router support. This + feature allows routing and enqueueing of MIDI events. Events + can be processed at a given time. + + Many programs require this feature, so you should enable it + unless you know what you're doing. + +if SND_SEQUENCER + +config SND_SEQ_DUMMY + tristate "Sequencer dummy client" + help + Say Y here to enable the dummy sequencer client. This client + is a simple MIDI-through client: all normal input events are + redirected to the output port immediately. + + You don't need this unless you want to connect many MIDI + devices or applications together. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-dummy. + +config SND_SEQUENCER_OSS + tristate "OSS Sequencer API" + depends on SND_OSSEMUL + select SND_SEQ_MIDI_EVENT + help + Say Y here to enable OSS sequencer emulation (both + /dev/sequencer and /dev/music interfaces). + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-oss. + +config SND_SEQ_HRTIMER_DEFAULT + bool "Use HR-timer as default sequencer timer" + depends on SND_HRTIMER + default y + help + Say Y here to use the HR-timer backend as the default sequencer + timer. + +config SND_SEQ_MIDI_EVENT + tristate + +config SND_SEQ_MIDI + def_tristate SND_RAWMIDI + select SND_SEQ_MIDI_EVENT + +config SND_SEQ_MIDI_EMUL + tristate + +config SND_SEQ_VIRMIDI + tristate + +endif # SND_SEQUENCER diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile new file mode 100644 index 000000000..3a2177a7e --- /dev/null +++ b/sound/core/seq/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela +# + +snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \ + seq_fifo.o seq_prioq.o seq_timer.o \ + seq_system.o seq_ports.o +snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o +snd-seq-midi-objs := seq_midi.o +snd-seq-midi-emul-objs := seq_midi_emul.o +snd-seq-midi-event-objs := seq_midi_event.o +snd-seq-dummy-objs := seq_dummy.o +snd-seq-virmidi-objs := seq_virmidi.o + +obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o +obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/ + +obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o +obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o +obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o +obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o +obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o diff --git a/sound/core/seq/oss/Makefile b/sound/core/seq/oss/Makefile new file mode 100644 index 000000000..4ea4e3eea --- /dev/null +++ b/sound/core/seq/oss/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela +# + +snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \ + seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \ + seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o + +obj-$(CONFIG_SND_SEQUENCER_OSS) += snd-seq-oss.o diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c new file mode 100644 index 000000000..f4a9d9972 --- /dev/null +++ b/sound/core/seq/oss/seq_oss.c @@ -0,0 +1,318 @@ +/* + * OSS compatible sequencer driver + * + * registration of device and proc + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include "seq_oss_device.h" +#include "seq_oss_synth.h" + +/* + * module option + */ +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("OSS-compatible sequencer module"); +MODULE_LICENSE("GPL"); +/* Takashi says this is really only for sound-service-0-, but this is OK. */ +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC); + + +/* + * prototypes + */ +static int register_device(void); +static void unregister_device(void); +#ifdef CONFIG_SND_PROC_FS +static int register_proc(void); +static void unregister_proc(void); +#else +static inline int register_proc(void) { return 0; } +static inline void unregister_proc(void) {} +#endif + +static int odev_open(struct inode *inode, struct file *file); +static int odev_release(struct inode *inode, struct file *file); +static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset); +static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset); +static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static __poll_t odev_poll(struct file *file, poll_table * wait); + + +/* + * module interface + */ + +static struct snd_seq_driver seq_oss_synth_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_oss_synth_probe, + .remove = snd_seq_oss_synth_remove, + }, + .id = SNDRV_SEQ_DEV_ID_OSS, + .argsize = sizeof(struct snd_seq_oss_reg), +}; + +static int __init alsa_seq_oss_init(void) +{ + int rc; + + if ((rc = register_device()) < 0) + goto error; + if ((rc = register_proc()) < 0) { + unregister_device(); + goto error; + } + if ((rc = snd_seq_oss_create_client()) < 0) { + unregister_proc(); + unregister_device(); + goto error; + } + + rc = snd_seq_driver_register(&seq_oss_synth_driver); + if (rc < 0) { + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); + goto error; + } + + /* success */ + snd_seq_oss_synth_init(); + + error: + return rc; +} + +static void __exit alsa_seq_oss_exit(void) +{ + snd_seq_driver_unregister(&seq_oss_synth_driver); + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); +} + +module_init(alsa_seq_oss_init) +module_exit(alsa_seq_oss_exit) + +/* + * ALSA minor device interface + */ + +static DEFINE_MUTEX(register_mutex); + +static int +odev_open(struct inode *inode, struct file *file) +{ + int level, rc; + + if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC) + level = SNDRV_SEQ_OSS_MODE_MUSIC; + else + level = SNDRV_SEQ_OSS_MODE_SYNTH; + + mutex_lock(®ister_mutex); + rc = snd_seq_oss_open(file, level); + mutex_unlock(®ister_mutex); + + return rc; +} + +static int +odev_release(struct inode *inode, struct file *file) +{ + struct seq_oss_devinfo *dp; + + if ((dp = file->private_data) == NULL) + return 0; + + mutex_lock(®ister_mutex); + snd_seq_oss_release(dp); + mutex_unlock(®ister_mutex); + + return 0; +} + +static ssize_t +odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + struct seq_oss_devinfo *dp; + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return -ENXIO; + return snd_seq_oss_read(dp, buf, count); +} + + +static ssize_t +odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + struct seq_oss_devinfo *dp; + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return -ENXIO; + return snd_seq_oss_write(dp, buf, count, file); +} + +static long +odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct seq_oss_devinfo *dp; + long rc; + + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return -ENXIO; + + if (cmd != SNDCTL_SEQ_SYNC && + mutex_lock_interruptible(®ister_mutex)) + return -ERESTARTSYS; + rc = snd_seq_oss_ioctl(dp, cmd, arg); + if (cmd != SNDCTL_SEQ_SYNC) + mutex_unlock(®ister_mutex); + return rc; +} + +#ifdef CONFIG_COMPAT +static long odev_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return odev_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define odev_ioctl_compat NULL +#endif + +static __poll_t +odev_poll(struct file *file, poll_table * wait) +{ + struct seq_oss_devinfo *dp; + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return EPOLLERR; + return snd_seq_oss_poll(dp, file, wait); +} + +/* + * registration of sequencer minor device + */ + +static const struct file_operations seq_oss_f_ops = +{ + .owner = THIS_MODULE, + .read = odev_read, + .write = odev_write, + .open = odev_open, + .release = odev_release, + .poll = odev_poll, + .unlocked_ioctl = odev_ioctl, + .compat_ioctl = odev_ioctl_compat, + .llseek = noop_llseek, +}; + +static int __init +register_device(void) +{ + int rc; + + mutex_lock(®ister_mutex); + if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, + NULL, 0, + &seq_oss_f_ops, NULL)) < 0) { + pr_err("ALSA: seq_oss: can't register device seq\n"); + mutex_unlock(®ister_mutex); + return rc; + } + if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, + NULL, 0, + &seq_oss_f_ops, NULL)) < 0) { + pr_err("ALSA: seq_oss: can't register device music\n"); + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0); + mutex_unlock(®ister_mutex); + return rc; + } + mutex_unlock(®ister_mutex); + return 0; +} + +static void +unregister_device(void) +{ + mutex_lock(®ister_mutex); + if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0) + pr_err("ALSA: seq_oss: error unregister device music\n"); + if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0) + pr_err("ALSA: seq_oss: error unregister device seq\n"); + mutex_unlock(®ister_mutex); +} + +/* + * /proc interface + */ + +#ifdef CONFIG_SND_PROC_FS + +static struct snd_info_entry *info_entry; + +static void +info_read(struct snd_info_entry *entry, struct snd_info_buffer *buf) +{ + mutex_lock(®ister_mutex); + snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR); + snd_seq_oss_system_info_read(buf); + snd_seq_oss_synth_info_read(buf); + snd_seq_oss_midi_info_read(buf); + mutex_unlock(®ister_mutex); +} + + +static int __init +register_proc(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root); + if (entry == NULL) + return -ENOMEM; + + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = NULL; + entry->c.text.read = info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + info_entry = entry; + return 0; +} + +static void +unregister_proc(void) +{ + snd_info_free_entry(info_entry); + info_entry = NULL; +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h new file mode 100644 index 000000000..2d0e9eaf1 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_device.h @@ -0,0 +1,175 @@ +/* + * OSS compatible sequencer driver + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_DEVICE_H +#define __SEQ_OSS_DEVICE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* max. applications */ +#define SNDRV_SEQ_OSS_MAX_CLIENTS 16 +#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS 16 +#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS 32 + +/* version */ +#define SNDRV_SEQ_OSS_MAJOR_VERSION 0 +#define SNDRV_SEQ_OSS_MINOR_VERSION 1 +#define SNDRV_SEQ_OSS_TINY_VERSION 8 +#define SNDRV_SEQ_OSS_VERSION_STR "0.1.8" + +/* device and proc interface name */ +#define SNDRV_SEQ_OSS_PROCNAME "oss" + + +/* + * type definitions + */ + +typedef unsigned int reltime_t; +typedef unsigned int abstime_t; + + +/* + * synthesizer channel information + */ +struct seq_oss_chinfo { + int note, vel; +}; + +/* + * synthesizer information + */ +struct seq_oss_synthinfo { + struct snd_seq_oss_arg arg; + struct seq_oss_chinfo *ch; + struct seq_oss_synth_sysex *sysex; + int nr_voices; + int opened; + int is_midi; + int midi_mapped; +}; + + +/* + * sequencer client information + */ + +struct seq_oss_devinfo { + + int index; /* application index */ + int cseq; /* sequencer client number */ + int port; /* sequencer port number */ + int queue; /* sequencer queue number */ + + struct snd_seq_addr addr; /* address of this device */ + + int seq_mode; /* sequencer mode */ + int file_mode; /* file access */ + + /* midi device table */ + int max_mididev; + + /* synth device table */ + int max_synthdev; + struct seq_oss_synthinfo synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS]; + int synth_opened; + + /* output queue */ + struct seq_oss_writeq *writeq; + + /* midi input queue */ + struct seq_oss_readq *readq; + + /* timer */ + struct seq_oss_timer *timer; +}; + + +/* + * function prototypes + */ + +/* create/delete OSS sequencer client */ +int snd_seq_oss_create_client(void); +int snd_seq_oss_delete_client(void); + +/* device file interface */ +int snd_seq_oss_open(struct file *file, int level); +void snd_seq_oss_release(struct seq_oss_devinfo *dp); +int snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long arg); +int snd_seq_oss_read(struct seq_oss_devinfo *dev, char __user *buf, int count); +int snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt); +__poll_t snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait); + +void snd_seq_oss_reset(struct seq_oss_devinfo *dp); + +/* */ +void snd_seq_oss_process_queue(struct seq_oss_devinfo *dp, abstime_t time); + + +/* proc interface */ +void snd_seq_oss_system_info_read(struct snd_info_buffer *buf); +void snd_seq_oss_midi_info_read(struct snd_info_buffer *buf); +void snd_seq_oss_synth_info_read(struct snd_info_buffer *buf); +void snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf); + +/* file mode macros */ +#define is_read_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_READ) +#define is_write_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_WRITE) +#define is_nonblock_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK) + +/* dispatch event */ +static inline int +snd_seq_oss_dispatch(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int atomic, int hop) +{ + return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop); +} + +/* ioctl */ +static inline int +snd_seq_oss_control(struct seq_oss_devinfo *dp, unsigned int type, void *arg) +{ + return snd_seq_kernel_client_ctl(dp->cseq, type, arg); +} + +/* fill the addresses in header */ +static inline void +snd_seq_oss_fill_addr(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, + int dest_client, int dest_port) +{ + ev->queue = dp->queue; + ev->source = dp->addr; + ev->dest.client = dest_client; + ev->dest.port = dest_port; +} + + +/* misc. functions for proc interface */ +char *enabled_str(int bool); + +#endif /* __SEQ_OSS_DEVICE_H */ diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c new file mode 100644 index 000000000..86ca584c2 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_event.c @@ -0,0 +1,460 @@ +/* + * OSS compatible sequencer driver + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include + + +/* + * prototypes + */ +static int extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev); +static int chn_voice_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int chn_common_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int timing_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int local_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev); +static int note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev); +static int note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev); +static int set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev); +static int set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev); +static int set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev); + + +/* + * convert an OSS event to ALSA event + * return 0 : enqueued + * non-zero : invalid - ignored + */ + +int +snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + switch (q->s.code) { + case SEQ_EXTENDED: + return extended_event(dp, q, ev); + + case EV_CHN_VOICE: + return chn_voice_event(dp, q, ev); + + case EV_CHN_COMMON: + return chn_common_event(dp, q, ev); + + case EV_TIMING: + return timing_event(dp, q, ev); + + case EV_SEQ_LOCAL: + return local_event(dp, q, ev); + + case EV_SYSEX: + return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev); + + case SEQ_MIDIPUTC: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + /* put a midi byte */ + if (! is_write_mode(dp->file_mode)) + break; + if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE)) + break; + if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE) + return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev); + break; + + case SEQ_ECHO: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return set_echo_event(dp, q, ev); + + case SEQ_PRIVATE: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev); + + default: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return old_event(dp, q, ev); + } + return -EINVAL; +} + +/* old type events: mode1 only */ +static int +old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + switch (q->s.code) { + case SEQ_NOTEOFF: + return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev); + + case SEQ_NOTEON: + return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev); + + case SEQ_WAIT: + /* skip */ + break; + + case SEQ_PGMCHANGE: + return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE, + q->n.chn, 0, q->n.note, ev); + + case SEQ_SYNCTIMER: + return snd_seq_oss_timer_reset(dp->timer); + } + + return -EINVAL; +} + +/* 8bytes extended event: mode1 only */ +static int +extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + int val; + + switch (q->e.cmd) { + case SEQ_NOTEOFF: + return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev); + + case SEQ_NOTEON: + return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev); + + case SEQ_PGMCHANGE: + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE, + q->e.chn, 0, q->e.p1, ev); + + case SEQ_AFTERTOUCH: + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS, + q->e.chn, 0, q->e.p1, ev); + + case SEQ_BALANCE: + /* convert -128:127 to 0:127 */ + val = (char)q->e.p1; + val = (val + 128) / 2; + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER, + q->e.chn, CTL_PAN, val, ev); + + case SEQ_CONTROLLER: + val = ((short)q->e.p3 << 8) | (short)q->e.p2; + switch (q->e.p1) { + case CTRL_PITCH_BENDER: /* SEQ1 V2 control */ + /* -0x2000:0x1fff */ + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_PITCHBEND, + q->e.chn, 0, val, ev); + case CTRL_PITCH_BENDER_RANGE: + /* conversion: 100/semitone -> 128/semitone */ + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_REGPARAM, + q->e.chn, 0, val*128/100, ev); + default: + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_CONTROL14, + q->e.chn, q->e.p1, val, ev); + } + + case SEQ_VOLMODE: + return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev); + + } + return -EINVAL; +} + +/* channel voice events: mode1 and 2 */ +static int +chn_voice_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + if (q->v.chn >= 32) + return -EINVAL; + switch (q->v.cmd) { + case MIDI_NOTEON: + return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev); + + case MIDI_NOTEOFF: + return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev); + + case MIDI_KEY_PRESSURE: + return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS, + q->v.chn, q->v.note, q->v.parm, ev); + + } + return -EINVAL; +} + +/* channel common events: mode1 and 2 */ +static int +chn_common_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + if (q->l.chn >= 32) + return -EINVAL; + switch (q->l.cmd) { + case MIDI_PGM_CHANGE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE, + q->l.chn, 0, q->l.p1, ev); + + case MIDI_CTL_CHANGE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER, + q->l.chn, q->l.p1, q->l.val, ev); + + case MIDI_PITCH_BEND: + /* conversion: 0:0x3fff -> -0x2000:0x1fff */ + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND, + q->l.chn, 0, q->l.val - 8192, ev); + + case MIDI_CHN_PRESSURE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS, + q->l.chn, 0, q->l.val, ev); + } + return -EINVAL; +} + +/* timer events: mode1 and mode2 */ +static int +timing_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + switch (q->t.cmd) { + case TMR_ECHO: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return set_echo_event(dp, q, ev); + else { + union evrec tmp; + memset(&tmp, 0, sizeof(tmp)); + /* XXX: only for little-endian! */ + tmp.echo = (q->t.time << 8) | SEQ_ECHO; + return set_echo_event(dp, &tmp, ev); + } + + case TMR_STOP: + if (dp->seq_mode) + return snd_seq_oss_timer_stop(dp->timer); + return 0; + + case TMR_CONTINUE: + if (dp->seq_mode) + return snd_seq_oss_timer_continue(dp->timer); + return 0; + + case TMR_TEMPO: + if (dp->seq_mode) + return snd_seq_oss_timer_tempo(dp->timer, q->t.time); + return 0; + } + + return -EINVAL; +} + +/* local events: mode1 and 2 */ +static int +local_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + return -EINVAL; +} + +/* + * process note-on event for OSS synth + * three different modes are available: + * - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode) + * Accept note 255 as volume change. + * - SNDRV_SEQ_OSS_PASS_EVENTS + * Pass all events to lowlevel driver anyway + * - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000) + * Use key-pressure if note >= 128 + */ +static int +note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info) + return -ENXIO; + + switch (info->arg.event_passing) { + case SNDRV_SEQ_OSS_PROCESS_EVENTS: + if (! info->ch || ch < 0 || ch >= info->nr_voices) { + /* pass directly */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + + ch = array_index_nospec(ch, info->nr_voices); + if (note == 255 && info->ch[ch].note >= 0) { + /* volume control */ + int type; + //if (! vel) + /* set volume to zero -- note off */ + // type = SNDRV_SEQ_EVENT_NOTEOFF; + //else + if (info->ch[ch].vel) + /* sample already started -- volume change */ + type = SNDRV_SEQ_EVENT_KEYPRESS; + else + /* sample not started -- start now */ + type = SNDRV_SEQ_EVENT_NOTEON; + info->ch[ch].vel = vel; + return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev); + } else if (note >= 128) + return -EINVAL; /* invalid */ + + if (note != info->ch[ch].note && info->ch[ch].note >= 0) + /* note changed - note off at beginning */ + set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev); + /* set current status */ + info->ch[ch].note = note; + info->ch[ch].vel = vel; + if (vel) /* non-zero velocity - start the note now */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + return -EINVAL; + + case SNDRV_SEQ_OSS_PASS_EVENTS: + /* pass the event anyway */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + + case SNDRV_SEQ_OSS_PROCESS_KEYPRESS: + if (note >= 128) /* key pressure: shifted by 128 */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev); + else /* normal note-on event */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + return -EINVAL; +} + +/* + * process note-off event for OSS synth + */ +static int +note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info) + return -ENXIO; + + switch (info->arg.event_passing) { + case SNDRV_SEQ_OSS_PROCESS_EVENTS: + if (! info->ch || ch < 0 || ch >= info->nr_voices) { + /* pass directly */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + + ch = array_index_nospec(ch, info->nr_voices); + if (info->ch[ch].note >= 0) { + note = info->ch[ch].note; + info->ch[ch].vel = 0; + info->ch[ch].note = -1; + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev); + } + return -EINVAL; /* invalid */ + + case SNDRV_SEQ_OSS_PASS_EVENTS: + case SNDRV_SEQ_OSS_PROCESS_KEYPRESS: + /* pass the event anyway */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev); + + } + return -EINVAL; +} + +/* + * create a note event + */ +static int +set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev) +{ + if (!snd_seq_oss_synth_info(dp, dev)) + return -ENXIO; + + ev->type = type; + snd_seq_oss_synth_addr(dp, dev, ev); + ev->data.note.channel = ch; + ev->data.note.note = note; + ev->data.note.velocity = vel; + + return 0; +} + +/* + * create a control event + */ +static int +set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev) +{ + if (!snd_seq_oss_synth_info(dp, dev)) + return -ENXIO; + + ev->type = type; + snd_seq_oss_synth_addr(dp, dev, ev); + ev->data.control.channel = ch; + ev->data.control.param = param; + ev->data.control.value = val; + + return 0; +} + +/* + * create an echo event + */ +static int +set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev) +{ + ev->type = SNDRV_SEQ_EVENT_ECHO; + /* echo back to itself */ + snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port); + memcpy(&ev->data, rec, LONG_EVENT_SIZE); + return 0; +} + +/* + * event input callback from ALSA sequencer: + * the echo event is processed here. + */ +int +snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data; + union evrec *rec; + + if (ev->type != SNDRV_SEQ_EVENT_ECHO) + return snd_seq_oss_midi_input(ev, direct, private_data); + + if (ev->source.client != dp->cseq) + return 0; /* ignored */ + + rec = (union evrec*)&ev->data; + if (rec->s.code == SEQ_SYNCTIMER) { + /* sync echo back */ + snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time); + + } else { + /* echo back event */ + if (dp->readq == NULL) + return 0; + snd_seq_oss_readq_put_event(dp->readq, rec); + } + return 0; +} + diff --git a/sound/core/seq/oss/seq_oss_event.h b/sound/core/seq/oss/seq_oss_event.h new file mode 100644 index 000000000..9a4d9adb7 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_event.h @@ -0,0 +1,112 @@ +/* + * OSS compatible sequencer driver + * + * seq_oss_event.h - OSS event queue record + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_EVENT_H +#define __SEQ_OSS_EVENT_H + +#include "seq_oss_device.h" + +#define SHORT_EVENT_SIZE 4 +#define LONG_EVENT_SIZE 8 + +/* short event (4bytes) */ +struct evrec_short { + unsigned char code; + unsigned char parm1; + unsigned char dev; + unsigned char parm2; +}; + +/* short note events (4bytes) */ +struct evrec_note { + unsigned char code; + unsigned char chn; + unsigned char note; + unsigned char vel; +}; + +/* long timer events (8bytes) */ +struct evrec_timer { + unsigned char code; + unsigned char cmd; + unsigned char dummy1, dummy2; + unsigned int time; +}; + +/* long extended events (8bytes) */ +struct evrec_extended { + unsigned char code; + unsigned char cmd; + unsigned char dev; + unsigned char chn; + unsigned char p1, p2, p3, p4; +}; + +/* long channel events (8bytes) */ +struct evrec_long { + unsigned char code; + unsigned char dev; + unsigned char cmd; + unsigned char chn; + unsigned char p1, p2; + unsigned short val; +}; + +/* channel voice events (8bytes) */ +struct evrec_voice { + unsigned char code; + unsigned char dev; + unsigned char cmd; + unsigned char chn; + unsigned char note, parm; + unsigned short dummy; +}; + +/* sysex events (8bytes) */ +struct evrec_sysex { + unsigned char code; + unsigned char dev; + unsigned char buf[6]; +}; + +/* event record */ +union evrec { + struct evrec_short s; + struct evrec_note n; + struct evrec_long l; + struct evrec_voice v; + struct evrec_timer t; + struct evrec_extended e; + struct evrec_sysex x; + unsigned int echo; + unsigned char c[LONG_EVENT_SIZE]; +}; + +#define ev_is_long(ev) ((ev)->s.code >= 128) +#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE) + +int snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev); +int snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *q); +int snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop); + + +#endif /* __SEQ_OSS_EVENT_H */ diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c new file mode 100644 index 000000000..92c96a95a --- /dev/null +++ b/sound/core/seq/oss/seq_oss_init.c @@ -0,0 +1,517 @@ +/* + * OSS compatible sequencer driver + * + * open/close and reset interface + * + * Copyright (C) 1998-1999 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_writeq.h" +#include "seq_oss_readq.h" +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include +#include +#include +#include +#include + +/* + * common variables + */ +static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN; +module_param(maxqlen, int, 0444); +MODULE_PARM_DESC(maxqlen, "maximum queue length"); + +static int system_client = -1; /* ALSA sequencer client number */ +static int system_port = -1; + +static int num_clients; +static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS]; + + +/* + * prototypes + */ +static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop); +static int translate_mode(struct file *file); +static int create_port(struct seq_oss_devinfo *dp); +static int delete_port(struct seq_oss_devinfo *dp); +static int alloc_seq_queue(struct seq_oss_devinfo *dp); +static int delete_seq_queue(int queue); +static void free_devinfo(void *private); + +#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec) + + +/* call snd_seq_oss_midi_lookup_ports() asynchronously */ +static void async_call_lookup_ports(struct work_struct *work) +{ + snd_seq_oss_midi_lookup_ports(system_client); +} + +static DECLARE_WORK(async_lookup_work, async_call_lookup_ports); + +/* + * create sequencer client for OSS sequencer + */ +int __init +snd_seq_oss_create_client(void) +{ + int rc; + struct snd_seq_port_info *port; + struct snd_seq_port_callback port_callback; + + port = kmalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + rc = -ENOMEM; + goto __error; + } + + /* create ALSA client */ + rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, + "OSS sequencer"); + if (rc < 0) + goto __error; + + system_client = rc; + + /* create annoucement receiver port */ + memset(port, 0, sizeof(*port)); + strcpy(port->name, "Receiver"); + port->addr.client = system_client; + port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */ + port->type = 0; + + memset(&port_callback, 0, sizeof(port_callback)); + /* don't set port_callback.owner here. otherwise the module counter + * is incremented and we can no longer release the module.. + */ + port_callback.event_input = receive_announce; + port->kernel = &port_callback; + + call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port); + if ((system_port = port->addr.port) >= 0) { + struct snd_seq_port_subscribe subs; + + memset(&subs, 0, sizeof(subs)); + subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM; + subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + subs.dest.client = system_client; + subs.dest.port = system_port; + call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs); + } + rc = 0; + + /* look up midi devices */ + schedule_work(&async_lookup_work); + + __error: + kfree(port); + return rc; +} + + +/* + * receive annoucement from system port, and check the midi device + */ +static int +receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop) +{ + struct snd_seq_port_info pinfo; + + if (atomic) + return 0; /* it must not happen */ + + switch (ev->type) { + case SNDRV_SEQ_EVENT_PORT_START: + case SNDRV_SEQ_EVENT_PORT_CHANGE: + if (ev->data.addr.client == system_client) + break; /* ignore myself */ + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.addr = ev->data.addr; + if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0) + snd_seq_oss_midi_check_new_port(&pinfo); + break; + + case SNDRV_SEQ_EVENT_PORT_EXIT: + if (ev->data.addr.client == system_client) + break; /* ignore myself */ + snd_seq_oss_midi_check_exit_port(ev->data.addr.client, + ev->data.addr.port); + break; + } + return 0; +} + + +/* + * delete OSS sequencer client + */ +int +snd_seq_oss_delete_client(void) +{ + cancel_work_sync(&async_lookup_work); + if (system_client >= 0) + snd_seq_delete_kernel_client(system_client); + + snd_seq_oss_midi_clear_all(); + + return 0; +} + + +/* + * open sequencer device + */ +int +snd_seq_oss_open(struct file *file, int level) +{ + int i, rc; + struct seq_oss_devinfo *dp; + + dp = kzalloc(sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + dp->cseq = system_client; + dp->port = -1; + dp->queue = -1; + + for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) { + if (client_table[i] == NULL) + break; + } + + dp->index = i; + if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) { + pr_debug("ALSA: seq_oss: too many applications\n"); + rc = -ENOMEM; + goto _error; + } + + /* look up synth and midi devices */ + snd_seq_oss_synth_setup(dp); + snd_seq_oss_midi_setup(dp); + + if (dp->synth_opened == 0 && dp->max_mididev == 0) { + /* pr_err("ALSA: seq_oss: no device found\n"); */ + rc = -ENODEV; + goto _error; + } + + /* create port */ + rc = create_port(dp); + if (rc < 0) { + pr_err("ALSA: seq_oss: can't create port\n"); + goto _error; + } + + /* allocate queue */ + rc = alloc_seq_queue(dp); + if (rc < 0) + goto _error; + + /* set address */ + dp->addr.client = dp->cseq; + dp->addr.port = dp->port; + /*dp->addr.queue = dp->queue;*/ + /*dp->addr.channel = 0;*/ + + dp->seq_mode = level; + + /* set up file mode */ + dp->file_mode = translate_mode(file); + + /* initialize read queue */ + if (is_read_mode(dp->file_mode)) { + dp->readq = snd_seq_oss_readq_new(dp, maxqlen); + if (!dp->readq) { + rc = -ENOMEM; + goto _error; + } + } + + /* initialize write queue */ + if (is_write_mode(dp->file_mode)) { + dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen); + if (!dp->writeq) { + rc = -ENOMEM; + goto _error; + } + } + + /* initialize timer */ + dp->timer = snd_seq_oss_timer_new(dp); + if (!dp->timer) { + pr_err("ALSA: seq_oss: can't alloc timer\n"); + rc = -ENOMEM; + goto _error; + } + + /* set private data pointer */ + file->private_data = dp; + + /* set up for mode2 */ + if (level == SNDRV_SEQ_OSS_MODE_MUSIC) + snd_seq_oss_synth_setup_midi(dp); + else if (is_read_mode(dp->file_mode)) + snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ); + + client_table[dp->index] = dp; + num_clients++; + + return 0; + + _error: + snd_seq_oss_synth_cleanup(dp); + snd_seq_oss_midi_cleanup(dp); + delete_seq_queue(dp->queue); + delete_port(dp); + + return rc; +} + +/* + * translate file flags to private mode + */ +static int +translate_mode(struct file *file) +{ + int file_mode = 0; + if ((file->f_flags & O_ACCMODE) != O_RDONLY) + file_mode |= SNDRV_SEQ_OSS_FILE_WRITE; + if ((file->f_flags & O_ACCMODE) != O_WRONLY) + file_mode |= SNDRV_SEQ_OSS_FILE_READ; + if (file->f_flags & O_NONBLOCK) + file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK; + return file_mode; +} + + +/* + * create sequencer port + */ +static int +create_port(struct seq_oss_devinfo *dp) +{ + int rc; + struct snd_seq_port_info port; + struct snd_seq_port_callback callback; + + memset(&port, 0, sizeof(port)); + port.addr.client = dp->cseq; + sprintf(port.name, "Sequencer-%d", dp->index); + port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */ + port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC; + port.midi_channels = 128; + port.synth_voices = 128; + + memset(&callback, 0, sizeof(callback)); + callback.owner = THIS_MODULE; + callback.private_data = dp; + callback.event_input = snd_seq_oss_event_input; + callback.private_free = free_devinfo; + port.kernel = &callback; + + rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port); + if (rc < 0) + return rc; + + dp->port = port.addr.port; + + return 0; +} + +/* + * delete ALSA port + */ +static int +delete_port(struct seq_oss_devinfo *dp) +{ + if (dp->port < 0) { + kfree(dp); + return 0; + } + + return snd_seq_event_port_detach(dp->cseq, dp->port); +} + +/* + * allocate a queue + */ +static int +alloc_seq_queue(struct seq_oss_devinfo *dp) +{ + struct snd_seq_queue_info qinfo; + int rc; + + memset(&qinfo, 0, sizeof(qinfo)); + qinfo.owner = system_client; + qinfo.locked = 1; + strcpy(qinfo.name, "OSS Sequencer Emulation"); + if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0) + return rc; + dp->queue = qinfo.queue; + return 0; +} + +/* + * release queue + */ +static int +delete_seq_queue(int queue) +{ + struct snd_seq_queue_info qinfo; + int rc; + + if (queue < 0) + return 0; + memset(&qinfo, 0, sizeof(qinfo)); + qinfo.queue = queue; + rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo); + if (rc < 0) + pr_err("ALSA: seq_oss: unable to delete queue %d (%d)\n", queue, rc); + return rc; +} + + +/* + * free device informations - private_free callback of port + */ +static void +free_devinfo(void *private) +{ + struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private; + + snd_seq_oss_timer_delete(dp->timer); + + snd_seq_oss_writeq_delete(dp->writeq); + + snd_seq_oss_readq_delete(dp->readq); + + kfree(dp); +} + + +/* + * close sequencer device + */ +void +snd_seq_oss_release(struct seq_oss_devinfo *dp) +{ + int queue; + + client_table[dp->index] = NULL; + num_clients--; + + snd_seq_oss_reset(dp); + + snd_seq_oss_synth_cleanup(dp); + snd_seq_oss_midi_cleanup(dp); + + /* clear slot */ + queue = dp->queue; + if (dp->port >= 0) + delete_port(dp); + delete_seq_queue(queue); +} + + +/* + * reset sequencer devices + */ +void +snd_seq_oss_reset(struct seq_oss_devinfo *dp) +{ + int i; + + /* reset all synth devices */ + for (i = 0; i < dp->max_synthdev; i++) + snd_seq_oss_synth_reset(dp, i); + + /* reset all midi devices */ + if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) { + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_reset(dp, i); + } + + /* remove queues */ + if (dp->readq) + snd_seq_oss_readq_clear(dp->readq); + if (dp->writeq) + snd_seq_oss_writeq_clear(dp->writeq); + + /* reset timer */ + snd_seq_oss_timer_stop(dp->timer); +} + +#ifdef CONFIG_SND_PROC_FS +/* + * misc. functions for proc interface + */ +char * +enabled_str(int bool) +{ + return bool ? "enabled" : "disabled"; +} + +static char * +filemode_str(int val) +{ + static char *str[] = { + "none", "read", "write", "read/write", + }; + return str[val & SNDRV_SEQ_OSS_FILE_ACMODE]; +} + + +/* + * proc interface + */ +void +snd_seq_oss_system_info_read(struct snd_info_buffer *buf) +{ + int i; + struct seq_oss_devinfo *dp; + + snd_iprintf(buf, "ALSA client number %d\n", system_client); + snd_iprintf(buf, "ALSA receiver port %d\n", system_port); + + snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients); + for (i = 0; i < num_clients; i++) { + snd_iprintf(buf, "\nApplication %d: ", i); + if ((dp = client_table[i]) == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue); + snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n", + (dp->seq_mode ? "music" : "synth"), + filemode_str(dp->file_mode)); + if (dp->seq_mode) + snd_iprintf(buf, " timer tempo = %d, timebase = %d\n", + dp->timer->oss_tempo, dp->timer->oss_timebase); + snd_iprintf(buf, " max queue length %d\n", maxqlen); + if (is_read_mode(dp->file_mode) && dp->readq) + snd_seq_oss_readq_info_read(dp->readq, buf); + } +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_ioctl.c b/sound/core/seq/oss/seq_oss_ioctl.c new file mode 100644 index 000000000..7d72e3d48 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_ioctl.c @@ -0,0 +1,191 @@ +/* + * OSS compatible sequencer driver + * + * OSS compatible i/o control + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include "seq_oss_timer.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_event.h" + +static int snd_seq_oss_synth_info_user(struct seq_oss_devinfo *dp, void __user *arg) +{ + struct synth_info info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0) + return -EINVAL; + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_seq_oss_midi_info_user(struct seq_oss_devinfo *dp, void __user *arg) +{ + struct midi_info info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0) + return -EINVAL; + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_seq_oss_oob_user(struct seq_oss_devinfo *dp, void __user *arg) +{ + unsigned char ev[8]; + struct snd_seq_event tmpev; + + if (copy_from_user(ev, arg, 8)) + return -EFAULT; + memset(&tmpev, 0, sizeof(tmpev)); + snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.client, dp->addr.port); + tmpev.time.tick = 0; + if (! snd_seq_oss_process_event(dp, (union evrec *)ev, &tmpev)) { + snd_seq_oss_dispatch(dp, &tmpev, 0, 0); + } + return 0; +} + +int +snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long carg) +{ + int dev, val; + void __user *arg = (void __user *)carg; + int __user *p = arg; + + switch (cmd) { + case SNDCTL_TMR_TIMEBASE: + case SNDCTL_TMR_TEMPO: + case SNDCTL_TMR_START: + case SNDCTL_TMR_STOP: + case SNDCTL_TMR_CONTINUE: + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SOURCE: + case SNDCTL_TMR_SELECT: + case SNDCTL_SEQ_CTRLRATE: + return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg); + + case SNDCTL_SEQ_PANIC: + snd_seq_oss_reset(dp); + return -EINVAL; + + case SNDCTL_SEQ_SYNC: + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return 0; + while (snd_seq_oss_writeq_sync(dp->writeq)) + ; + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; + + case SNDCTL_SEQ_RESET: + snd_seq_oss_reset(dp); + return 0; + + case SNDCTL_SEQ_TESTMIDI: + if (get_user(dev, p)) + return -EFAULT; + return snd_seq_oss_midi_open(dp, dev, dp->file_mode); + + case SNDCTL_SEQ_GETINCOUNT: + if (dp->readq == NULL || ! is_read_mode(dp->file_mode)) + return 0; + return put_user(dp->readq->qlen, p) ? -EFAULT : 0; + + case SNDCTL_SEQ_GETOUTCOUNT: + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return 0; + return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0; + + case SNDCTL_SEQ_GETTIME: + return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0; + + case SNDCTL_SEQ_RESETSAMPLES: + if (get_user(dev, p)) + return -EFAULT; + return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + + case SNDCTL_SEQ_NRSYNTHS: + return put_user(dp->max_synthdev, p) ? -EFAULT : 0; + + case SNDCTL_SEQ_NRMIDIS: + return put_user(dp->max_mididev, p) ? -EFAULT : 0; + + case SNDCTL_SYNTH_MEMAVL: + if (get_user(dev, p)) + return -EFAULT; + val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + return put_user(val, p) ? -EFAULT : 0; + + case SNDCTL_FM_4OP_ENABLE: + if (get_user(dev, p)) + return -EFAULT; + snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + return 0; + + case SNDCTL_SYNTH_INFO: + case SNDCTL_SYNTH_ID: + return snd_seq_oss_synth_info_user(dp, arg); + + case SNDCTL_SEQ_OUTOFBAND: + return snd_seq_oss_oob_user(dp, arg); + + case SNDCTL_MIDI_INFO: + return snd_seq_oss_midi_info_user(dp, arg); + + case SNDCTL_SEQ_THRESHOLD: + if (! is_write_mode(dp->file_mode)) + return 0; + if (get_user(val, p)) + return -EFAULT; + if (val < 1) + val = 1; + if (val >= dp->writeq->maxlen) + val = dp->writeq->maxlen - 1; + snd_seq_oss_writeq_set_output(dp->writeq, val); + return 0; + + case SNDCTL_MIDI_PRETIME: + if (dp->readq == NULL || !is_read_mode(dp->file_mode)) + return 0; + if (get_user(val, p)) + return -EFAULT; + if (val <= 0) + val = -1; + else + val = (HZ * val) / 10; + dp->readq->pre_event_timeout = val; + return put_user(val, p) ? -EFAULT : 0; + + default: + if (! is_write_mode(dp->file_mode)) + return -EIO; + return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg); + } + return 0; +} + diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c new file mode 100644 index 000000000..b7bef25b3 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_midi.c @@ -0,0 +1,709 @@ +/* + * OSS compatible sequencer driver + * + * MIDI device handlers + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include "seq_oss_midi.h" +#include "seq_oss_readq.h" +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include +#include "../seq_lock.h" +#include +#include +#include + + +/* + * constants + */ +#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30 + +/* + * definition of midi device record + */ +struct seq_oss_midi { + int seq_device; /* device number */ + int client; /* sequencer client number */ + int port; /* sequencer port number */ + unsigned int flags; /* port capability */ + int opened; /* flag for opening */ + unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME]; + struct snd_midi_event *coder; /* MIDI event coder */ + struct seq_oss_devinfo *devinfo; /* assigned OSSseq device */ + snd_use_lock_t use_lock; +}; + + +/* + * midi device table + */ +static int max_midi_devs; +static struct seq_oss_midi *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS]; + +static DEFINE_SPINLOCK(register_lock); + +/* + * prototypes + */ +static struct seq_oss_midi *get_mdev(int dev); +static struct seq_oss_midi *get_mididev(struct seq_oss_devinfo *dp, int dev); +static int send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev); +static int send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev); + +/* + * look up the existing ports + * this looks a very exhausting job. + */ +int +snd_seq_oss_midi_lookup_ports(int client) +{ + struct snd_seq_client_info *clinfo; + struct snd_seq_port_info *pinfo; + + clinfo = kzalloc(sizeof(*clinfo), GFP_KERNEL); + pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); + if (! clinfo || ! pinfo) { + kfree(clinfo); + kfree(pinfo); + return -ENOMEM; + } + clinfo->client = -1; + while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) { + if (clinfo->client == client) + continue; /* ignore myself */ + pinfo->addr.client = clinfo->client; + pinfo->addr.port = -1; + while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0) + snd_seq_oss_midi_check_new_port(pinfo); + } + kfree(clinfo); + kfree(pinfo); + return 0; +} + + +/* + */ +static struct seq_oss_midi * +get_mdev(int dev) +{ + struct seq_oss_midi *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + mdev = midi_devs[dev]; + if (mdev) + snd_use_lock_use(&mdev->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return mdev; +} + +/* + * look for the identical slot + */ +static struct seq_oss_midi * +find_slot(int client, int port) +{ + int i; + struct seq_oss_midi *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + mdev = midi_devs[i]; + if (mdev && mdev->client == client && mdev->port == port) { + /* found! */ + snd_use_lock_use(&mdev->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return mdev; + } + } + spin_unlock_irqrestore(®ister_lock, flags); + return NULL; +} + + +#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE) +#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ) +/* + * register a new port if it doesn't exist yet + */ +int +snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo) +{ + int i; + struct seq_oss_midi *mdev; + unsigned long flags; + + /* the port must include generic midi */ + if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC)) + return 0; + /* either read or write subscribable */ + if ((pinfo->capability & PERM_WRITE) != PERM_WRITE && + (pinfo->capability & PERM_READ) != PERM_READ) + return 0; + + /* + * look for the identical slot + */ + if ((mdev = find_slot(pinfo->addr.client, pinfo->addr.port)) != NULL) { + /* already exists */ + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + /* + * allocate midi info record + */ + mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + /* copy the port information */ + mdev->client = pinfo->addr.client; + mdev->port = pinfo->addr.port; + mdev->flags = pinfo->capability; + mdev->opened = 0; + snd_use_lock_init(&mdev->use_lock); + + /* copy and truncate the name of synth device */ + strlcpy(mdev->name, pinfo->name, sizeof(mdev->name)); + + /* create MIDI coder */ + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) { + pr_err("ALSA: seq_oss: can't malloc midi coder\n"); + kfree(mdev); + return -ENOMEM; + } + /* OSS sequencer adds running status to all sequences */ + snd_midi_event_no_status(mdev->coder, 1); + + /* + * look for en empty slot + */ + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + if (midi_devs[i] == NULL) + break; + } + if (i >= max_midi_devs) { + if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) { + spin_unlock_irqrestore(®ister_lock, flags); + snd_midi_event_free(mdev->coder); + kfree(mdev); + return -ENOMEM; + } + max_midi_devs++; + } + mdev->seq_device = i; + midi_devs[mdev->seq_device] = mdev; + spin_unlock_irqrestore(®ister_lock, flags); + + return 0; +} + +/* + * release the midi device if it was registered + */ +int +snd_seq_oss_midi_check_exit_port(int client, int port) +{ + struct seq_oss_midi *mdev; + unsigned long flags; + int index; + + if ((mdev = find_slot(client, port)) != NULL) { + spin_lock_irqsave(®ister_lock, flags); + midi_devs[mdev->seq_device] = NULL; + spin_unlock_irqrestore(®ister_lock, flags); + snd_use_lock_free(&mdev->use_lock); + snd_use_lock_sync(&mdev->use_lock); + snd_midi_event_free(mdev->coder); + kfree(mdev); + } + spin_lock_irqsave(®ister_lock, flags); + for (index = max_midi_devs - 1; index >= 0; index--) { + if (midi_devs[index]) + break; + } + max_midi_devs = index + 1; + spin_unlock_irqrestore(®ister_lock, flags); + return 0; +} + + +/* + * release the midi device if it was registered + */ +void +snd_seq_oss_midi_clear_all(void) +{ + int i; + struct seq_oss_midi *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + if ((mdev = midi_devs[i]) != NULL) { + snd_midi_event_free(mdev->coder); + kfree(mdev); + midi_devs[i] = NULL; + } + } + max_midi_devs = 0; + spin_unlock_irqrestore(®ister_lock, flags); +} + + +/* + * set up midi tables + */ +void +snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp) +{ + dp->max_mididev = max_midi_devs; +} + +/* + * clean up midi tables + */ +void +snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp) +{ + int i; + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_close(dp, i); + dp->max_mididev = 0; +} + + +/* + * open all midi devices. ignore errors. + */ +void +snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode) +{ + int i; + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_open(dp, i, file_mode); +} + + +/* + * get the midi device information + */ +static struct seq_oss_midi * +get_mididev(struct seq_oss_devinfo *dp, int dev) +{ + if (dev < 0 || dev >= dp->max_mididev) + return NULL; + dev = array_index_nospec(dev, dp->max_mididev); + return get_mdev(dev); +} + + +/* + * open the midi device if not opened yet + */ +int +snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int fmode) +{ + int perm; + struct seq_oss_midi *mdev; + struct snd_seq_port_subscribe subs; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENODEV; + + /* already used? */ + if (mdev->opened && mdev->devinfo != dp) { + snd_use_lock_free(&mdev->use_lock); + return -EBUSY; + } + + perm = 0; + if (is_write_mode(fmode)) + perm |= PERM_WRITE; + if (is_read_mode(fmode)) + perm |= PERM_READ; + perm &= mdev->flags; + if (perm == 0) { + snd_use_lock_free(&mdev->use_lock); + return -ENXIO; + } + + /* already opened? */ + if ((mdev->opened & perm) == perm) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + perm &= ~mdev->opened; + + memset(&subs, 0, sizeof(subs)); + + if (perm & PERM_WRITE) { + subs.sender = dp->addr; + subs.dest.client = mdev->client; + subs.dest.port = mdev->port; + if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0) + mdev->opened |= PERM_WRITE; + } + if (perm & PERM_READ) { + subs.sender.client = mdev->client; + subs.sender.port = mdev->port; + subs.dest = dp->addr; + subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP; + subs.queue = dp->queue; /* queue for timestamps */ + if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0) + mdev->opened |= PERM_READ; + } + + if (! mdev->opened) { + snd_use_lock_free(&mdev->use_lock); + return -ENXIO; + } + + mdev->devinfo = dp; + snd_use_lock_free(&mdev->use_lock); + return 0; +} + +/* + * close the midi device if already opened + */ +int +snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_midi *mdev; + struct snd_seq_port_subscribe subs; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENODEV; + if (! mdev->opened || mdev->devinfo != dp) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + memset(&subs, 0, sizeof(subs)); + if (mdev->opened & PERM_WRITE) { + subs.sender = dp->addr; + subs.dest.client = mdev->client; + subs.dest.port = mdev->port; + snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs); + } + if (mdev->opened & PERM_READ) { + subs.sender.client = mdev->client; + subs.sender.port = mdev->port; + subs.dest = dp->addr; + snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs); + } + + mdev->opened = 0; + mdev->devinfo = NULL; + + snd_use_lock_free(&mdev->use_lock); + return 0; +} + +/* + * change seq capability flags to file mode flags + */ +int +snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_midi *mdev; + int mode; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return 0; + + mode = 0; + if (mdev->opened & PERM_WRITE) + mode |= SNDRV_SEQ_OSS_FILE_WRITE; + if (mdev->opened & PERM_READ) + mode |= SNDRV_SEQ_OSS_FILE_READ; + + snd_use_lock_free(&mdev->use_lock); + return mode; +} + +/* + * reset the midi device and close it: + * so far, only close the device. + */ +void +snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_midi *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return; + if (! mdev->opened) { + snd_use_lock_free(&mdev->use_lock); + return; + } + + if (mdev->opened & PERM_WRITE) { + struct snd_seq_event ev; + int c; + + memset(&ev, 0, sizeof(ev)); + ev.dest.client = mdev->client; + ev.dest.port = mdev->port; + ev.queue = dp->queue; + ev.source.port = dp->port; + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) { + ev.type = SNDRV_SEQ_EVENT_SENSING; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + } + for (c = 0; c < 16; c++) { + ev.type = SNDRV_SEQ_EVENT_CONTROLLER; + ev.data.control.channel = c; + ev.data.control.param = MIDI_CTL_ALL_NOTES_OFF; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + ev.data.control.param = + MIDI_CTL_RESET_CONTROLLERS; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + ev.type = SNDRV_SEQ_EVENT_PITCHBEND; + ev.data.control.value = 0; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + } + } + } + // snd_seq_oss_midi_close(dp, dev); + snd_use_lock_free(&mdev->use_lock); +} + + +/* + * get client/port of the specified MIDI device + */ +void +snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr) +{ + struct seq_oss_midi *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return; + addr->client = mdev->client; + addr->port = mdev->port; + snd_use_lock_free(&mdev->use_lock); +} + + +/* + * input callback - this can be atomic + */ +int +snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private_data) +{ + struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data; + struct seq_oss_midi *mdev; + int rc; + + if (dp->readq == NULL) + return 0; + if ((mdev = find_slot(ev->source.client, ev->source.port)) == NULL) + return 0; + if (! (mdev->opened & PERM_READ)) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + rc = send_synth_event(dp, ev, mdev->seq_device); + else + rc = send_midi_event(dp, ev, mdev); + + snd_use_lock_free(&mdev->use_lock); + return rc; +} + +/* + * convert ALSA sequencer event to OSS synth event + */ +static int +send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev) +{ + union evrec ossev; + + memset(&ossev, 0, sizeof(ossev)); + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + ossev.v.cmd = MIDI_NOTEON; break; + case SNDRV_SEQ_EVENT_NOTEOFF: + ossev.v.cmd = MIDI_NOTEOFF; break; + case SNDRV_SEQ_EVENT_KEYPRESS: + ossev.v.cmd = MIDI_KEY_PRESSURE; break; + case SNDRV_SEQ_EVENT_CONTROLLER: + ossev.l.cmd = MIDI_CTL_CHANGE; break; + case SNDRV_SEQ_EVENT_PGMCHANGE: + ossev.l.cmd = MIDI_PGM_CHANGE; break; + case SNDRV_SEQ_EVENT_CHANPRESS: + ossev.l.cmd = MIDI_CHN_PRESSURE; break; + case SNDRV_SEQ_EVENT_PITCHBEND: + ossev.l.cmd = MIDI_PITCH_BEND; break; + default: + return 0; /* not supported */ + } + + ossev.v.dev = dev; + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + case SNDRV_SEQ_EVENT_NOTEOFF: + case SNDRV_SEQ_EVENT_KEYPRESS: + ossev.v.code = EV_CHN_VOICE; + ossev.v.note = ev->data.note.note; + ossev.v.parm = ev->data.note.velocity; + ossev.v.chn = ev->data.note.channel; + break; + case SNDRV_SEQ_EVENT_CONTROLLER: + case SNDRV_SEQ_EVENT_PGMCHANGE: + case SNDRV_SEQ_EVENT_CHANPRESS: + ossev.l.code = EV_CHN_COMMON; + ossev.l.p1 = ev->data.control.param; + ossev.l.val = ev->data.control.value; + ossev.l.chn = ev->data.control.channel; + break; + case SNDRV_SEQ_EVENT_PITCHBEND: + ossev.l.code = EV_CHN_COMMON; + ossev.l.val = ev->data.control.value + 8192; + ossev.l.chn = ev->data.control.channel; + break; + } + + snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode); + snd_seq_oss_readq_put_event(dp->readq, &ossev); + + return 0; +} + +/* + * decode event and send MIDI bytes to read queue + */ +static int +send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev) +{ + char msg[32]; + int len; + + snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode); + if (!dp->timer->running) + len = snd_seq_oss_timer_start(dp->timer); + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { + snd_seq_oss_readq_sysex(dp->readq, mdev->seq_device, ev); + snd_midi_event_reset_decode(mdev->coder); + } else { + len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev); + if (len > 0) + snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len); + } + + return 0; +} + + +/* + * dump midi data + * return 0 : enqueued + * non-zero : invalid - ignored + */ +int +snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c, struct snd_seq_event *ev) +{ + struct seq_oss_midi *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENODEV; + if (snd_midi_event_encode_byte(mdev->coder, c, ev)) { + snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port); + snd_use_lock_free(&mdev->use_lock); + return 0; + } + snd_use_lock_free(&mdev->use_lock); + return -EINVAL; +} + +/* + * create OSS compatible midi_info record + */ +int +snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf) +{ + struct seq_oss_midi *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENXIO; + inf->device = dev; + inf->dev_type = 0; /* FIXME: ?? */ + inf->capabilities = 0; /* FIXME: ?? */ + strlcpy(inf->name, mdev->name, sizeof(inf->name)); + snd_use_lock_free(&mdev->use_lock); + return 0; +} + + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ +static char * +capmode_str(int val) +{ + val &= PERM_READ|PERM_WRITE; + if (val == (PERM_READ|PERM_WRITE)) + return "read/write"; + else if (val == PERM_READ) + return "read"; + else if (val == PERM_WRITE) + return "write"; + else + return "none"; +} + +void +snd_seq_oss_midi_info_read(struct snd_info_buffer *buf) +{ + int i; + struct seq_oss_midi *mdev; + + snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs); + for (i = 0; i < max_midi_devs; i++) { + snd_iprintf(buf, "\nmidi %d: ", i); + mdev = get_mdev(i); + if (mdev == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name, + mdev->client, mdev->port); + snd_iprintf(buf, " capability %s / opened %s\n", + capmode_str(mdev->flags), + capmode_str(mdev->opened)); + snd_use_lock_free(&mdev->use_lock); + } +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_midi.h b/sound/core/seq/oss/seq_oss_midi.h new file mode 100644 index 000000000..84eb866ba --- /dev/null +++ b/sound/core/seq/oss/seq_oss_midi.h @@ -0,0 +1,48 @@ +/* + * OSS compatible sequencer driver + * + * midi device information + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_MIDI_H +#define __SEQ_OSS_MIDI_H + +#include "seq_oss_device.h" +#include + +int snd_seq_oss_midi_lookup_ports(int client); +int snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo); +int snd_seq_oss_midi_check_exit_port(int client, int port); +void snd_seq_oss_midi_clear_all(void); + +void snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp); +void snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp); + +int snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int file_mode); +void snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode); +int snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev); +void snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev); +int snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c, + struct snd_seq_event *ev); +int snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private); +int snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev); +int snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf); +void snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr); + +#endif diff --git a/sound/core/seq/oss/seq_oss_readq.c b/sound/core/seq/oss/seq_oss_readq.c new file mode 100644 index 000000000..06b21226b --- /dev/null +++ b/sound/core/seq/oss/seq_oss_readq.c @@ -0,0 +1,263 @@ +/* + * OSS compatible sequencer driver + * + * seq_oss_readq.c - MIDI input queue + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_readq.h" +#include "seq_oss_event.h" +#include +#include "../seq_lock.h" +#include +#include + +/* + * constants + */ +//#define SNDRV_SEQ_OSS_MAX_TIMEOUT (unsigned long)(-1) +#define SNDRV_SEQ_OSS_MAX_TIMEOUT (HZ * 3600) + + +/* + * prototypes + */ + + +/* + * create a read queue + */ +struct seq_oss_readq * +snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen) +{ + struct seq_oss_readq *q; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + + q->q = kcalloc(maxlen, sizeof(union evrec), GFP_KERNEL); + if (!q->q) { + kfree(q); + return NULL; + } + + q->maxlen = maxlen; + q->qlen = 0; + q->head = q->tail = 0; + init_waitqueue_head(&q->midi_sleep); + spin_lock_init(&q->lock); + q->pre_event_timeout = SNDRV_SEQ_OSS_MAX_TIMEOUT; + q->input_time = (unsigned long)-1; + + return q; +} + +/* + * delete the read queue + */ +void +snd_seq_oss_readq_delete(struct seq_oss_readq *q) +{ + if (q) { + kfree(q->q); + kfree(q); + } +} + +/* + * reset the read queue + */ +void +snd_seq_oss_readq_clear(struct seq_oss_readq *q) +{ + if (q->qlen) { + q->qlen = 0; + q->head = q->tail = 0; + } + /* if someone sleeping, wake'em up */ + wake_up(&q->midi_sleep); + q->input_time = (unsigned long)-1; +} + +/* + * put a midi byte + */ +int +snd_seq_oss_readq_puts(struct seq_oss_readq *q, int dev, unsigned char *data, int len) +{ + union evrec rec; + int result; + + memset(&rec, 0, sizeof(rec)); + rec.c[0] = SEQ_MIDIPUTC; + rec.c[2] = dev; + + while (len-- > 0) { + rec.c[1] = *data++; + result = snd_seq_oss_readq_put_event(q, &rec); + if (result < 0) + return result; + } + return 0; +} + +/* + * put MIDI sysex bytes; the event buffer may be chained, thus it has + * to be expanded via snd_seq_dump_var_event(). + */ +struct readq_sysex_ctx { + struct seq_oss_readq *readq; + int dev; +}; + +static int readq_dump_sysex(void *ptr, void *buf, int count) +{ + struct readq_sysex_ctx *ctx = ptr; + + return snd_seq_oss_readq_puts(ctx->readq, ctx->dev, buf, count); +} + +int snd_seq_oss_readq_sysex(struct seq_oss_readq *q, int dev, + struct snd_seq_event *ev) +{ + struct readq_sysex_ctx ctx = { + .readq = q, + .dev = dev + }; + + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + return 0; + return snd_seq_dump_var_event(ev, readq_dump_sysex, &ctx); +} + +/* + * copy an event to input queue: + * return zero if enqueued + */ +int +snd_seq_oss_readq_put_event(struct seq_oss_readq *q, union evrec *ev) +{ + unsigned long flags; + + spin_lock_irqsave(&q->lock, flags); + if (q->qlen >= q->maxlen - 1) { + spin_unlock_irqrestore(&q->lock, flags); + return -ENOMEM; + } + + memcpy(&q->q[q->tail], ev, sizeof(*ev)); + q->tail = (q->tail + 1) % q->maxlen; + q->qlen++; + + /* wake up sleeper */ + wake_up(&q->midi_sleep); + + spin_unlock_irqrestore(&q->lock, flags); + + return 0; +} + + +/* + * pop queue + * caller must hold lock + */ +int +snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec) +{ + if (q->qlen == 0) + return -EAGAIN; + memcpy(rec, &q->q[q->head], sizeof(*rec)); + return 0; +} + +/* + * sleep until ready + */ +void +snd_seq_oss_readq_wait(struct seq_oss_readq *q) +{ + wait_event_interruptible_timeout(q->midi_sleep, + (q->qlen > 0 || q->head == q->tail), + q->pre_event_timeout); +} + +/* + * drain one record + * caller must hold lock + */ +void +snd_seq_oss_readq_free(struct seq_oss_readq *q) +{ + if (q->qlen > 0) { + q->head = (q->head + 1) % q->maxlen; + q->qlen--; + } +} + +/* + * polling/select: + * return non-zero if readq is not empty. + */ +unsigned int +snd_seq_oss_readq_poll(struct seq_oss_readq *q, struct file *file, poll_table *wait) +{ + poll_wait(file, &q->midi_sleep, wait); + return q->qlen; +} + +/* + * put a timestamp + */ +int +snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *q, unsigned long curt, int seq_mode) +{ + if (curt != q->input_time) { + union evrec rec; + memset(&rec, 0, sizeof(rec)); + switch (seq_mode) { + case SNDRV_SEQ_OSS_MODE_SYNTH: + rec.echo = (curt << 8) | SEQ_WAIT; + snd_seq_oss_readq_put_event(q, &rec); + break; + case SNDRV_SEQ_OSS_MODE_MUSIC: + rec.t.code = EV_TIMING; + rec.t.cmd = TMR_WAIT_ABS; + rec.t.time = curt; + snd_seq_oss_readq_put_event(q, &rec); + break; + } + q->input_time = curt; + } + return 0; +} + + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ +void +snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf) +{ + snd_iprintf(buf, " read queue [%s] length = %d : tick = %ld\n", + (waitqueue_active(&q->midi_sleep) ? "sleeping":"running"), + q->qlen, q->input_time); +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_readq.h b/sound/core/seq/oss/seq_oss_readq.h new file mode 100644 index 000000000..8d033ca2d --- /dev/null +++ b/sound/core/seq/oss/seq_oss_readq.h @@ -0,0 +1,58 @@ +/* + * OSS compatible sequencer driver + * read fifo queue + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_READQ_H +#define __SEQ_OSS_READQ_H + +#include "seq_oss_device.h" + + +/* + * definition of read queue + */ +struct seq_oss_readq { + union evrec *q; + int qlen; + int maxlen; + int head, tail; + unsigned long pre_event_timeout; + unsigned long input_time; + wait_queue_head_t midi_sleep; + spinlock_t lock; +}; + +struct seq_oss_readq *snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen); +void snd_seq_oss_readq_delete(struct seq_oss_readq *q); +void snd_seq_oss_readq_clear(struct seq_oss_readq *readq); +unsigned int snd_seq_oss_readq_poll(struct seq_oss_readq *readq, struct file *file, poll_table *wait); +int snd_seq_oss_readq_puts(struct seq_oss_readq *readq, int dev, unsigned char *data, int len); +int snd_seq_oss_readq_sysex(struct seq_oss_readq *q, int dev, + struct snd_seq_event *ev); +int snd_seq_oss_readq_put_event(struct seq_oss_readq *readq, union evrec *ev); +int snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *readq, unsigned long curt, int seq_mode); +int snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec); +void snd_seq_oss_readq_wait(struct seq_oss_readq *q); +void snd_seq_oss_readq_free(struct seq_oss_readq *q); + +#define snd_seq_oss_readq_lock(q, flags) spin_lock_irqsave(&(q)->lock, flags) +#define snd_seq_oss_readq_unlock(q, flags) spin_unlock_irqrestore(&(q)->lock, flags) + +#endif diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c new file mode 100644 index 000000000..05fbb564b --- /dev/null +++ b/sound/core/seq/oss/seq_oss_rw.c @@ -0,0 +1,216 @@ +/* + * OSS compatible sequencer driver + * + * read/write/select interface to device file + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include "seq_oss_synth.h" +#include +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include "../seq_clientmgr.h" + + +/* + * protoypes + */ +static int insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt); + + +/* + * read interface + */ + +int +snd_seq_oss_read(struct seq_oss_devinfo *dp, char __user *buf, int count) +{ + struct seq_oss_readq *readq = dp->readq; + int result = 0, err = 0; + int ev_len; + union evrec rec; + unsigned long flags; + + if (readq == NULL || ! is_read_mode(dp->file_mode)) + return -ENXIO; + + while (count >= SHORT_EVENT_SIZE) { + snd_seq_oss_readq_lock(readq, flags); + err = snd_seq_oss_readq_pick(readq, &rec); + if (err == -EAGAIN && + !is_nonblock_mode(dp->file_mode) && result == 0) { + snd_seq_oss_readq_unlock(readq, flags); + snd_seq_oss_readq_wait(readq); + snd_seq_oss_readq_lock(readq, flags); + if (signal_pending(current)) + err = -ERESTARTSYS; + else + err = snd_seq_oss_readq_pick(readq, &rec); + } + if (err < 0) { + snd_seq_oss_readq_unlock(readq, flags); + break; + } + ev_len = ev_length(&rec); + if (ev_len < count) { + snd_seq_oss_readq_unlock(readq, flags); + break; + } + snd_seq_oss_readq_free(readq); + snd_seq_oss_readq_unlock(readq, flags); + if (copy_to_user(buf, &rec, ev_len)) { + err = -EFAULT; + break; + } + result += ev_len; + buf += ev_len; + count -= ev_len; + } + return result > 0 ? result : err; +} + + +/* + * write interface + */ + +int +snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt) +{ + int result = 0, err = 0; + int ev_size, fmt; + union evrec rec; + + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return -ENXIO; + + while (count >= SHORT_EVENT_SIZE) { + if (copy_from_user(&rec, buf, SHORT_EVENT_SIZE)) { + err = -EFAULT; + break; + } + if (rec.s.code == SEQ_FULLSIZE) { + /* load patch */ + if (result > 0) { + err = -EINVAL; + break; + } + fmt = (*(unsigned short *)rec.c) & 0xffff; + /* FIXME the return value isn't correct */ + return snd_seq_oss_synth_load_patch(dp, rec.s.dev, + fmt, buf, 0, count); + } + if (ev_is_long(&rec)) { + /* extended code */ + if (rec.s.code == SEQ_EXTENDED && + dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + err = -EINVAL; + break; + } + ev_size = LONG_EVENT_SIZE; + if (count < ev_size) + break; + /* copy the reset 4 bytes */ + if (copy_from_user(rec.c + SHORT_EVENT_SIZE, + buf + SHORT_EVENT_SIZE, + LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) { + err = -EFAULT; + break; + } + } else { + /* old-type code */ + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + err = -EINVAL; + break; + } + ev_size = SHORT_EVENT_SIZE; + } + + /* insert queue */ + if ((err = insert_queue(dp, &rec, opt)) < 0) + break; + + result += ev_size; + buf += ev_size; + count -= ev_size; + } + return result > 0 ? result : err; +} + + +/* + * insert event record to write queue + * return: 0 = OK, non-zero = NG + */ +static int +insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt) +{ + int rc = 0; + struct snd_seq_event event; + + /* if this is a timing event, process the current time */ + if (snd_seq_oss_process_timer_event(dp->timer, rec)) + return 0; /* no need to insert queue */ + + /* parse this event */ + memset(&event, 0, sizeof(event)); + /* set dummy -- to be sure */ + event.type = SNDRV_SEQ_EVENT_NOTEOFF; + snd_seq_oss_fill_addr(dp, &event, dp->addr.client, dp->addr.port); + + if (snd_seq_oss_process_event(dp, rec, &event)) + return 0; /* invalid event - no need to insert queue */ + + event.time.tick = snd_seq_oss_timer_cur_tick(dp->timer); + if (dp->timer->realtime || !dp->timer->running) { + snd_seq_oss_dispatch(dp, &event, 0, 0); + } else { + if (is_nonblock_mode(dp->file_mode)) + rc = snd_seq_kernel_client_enqueue(dp->cseq, &event, 0, 0); + else + rc = snd_seq_kernel_client_enqueue_blocking(dp->cseq, &event, opt, 0, 0); + } + return rc; +} + + +/* + * select / poll + */ + +__poll_t +snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait) +{ + __poll_t mask = 0; + + /* input */ + if (dp->readq && is_read_mode(dp->file_mode)) { + if (snd_seq_oss_readq_poll(dp->readq, file, wait)) + mask |= EPOLLIN | EPOLLRDNORM; + } + + /* output */ + if (dp->writeq && is_write_mode(dp->file_mode)) { + if (snd_seq_kernel_client_write_poll(dp->cseq, file, wait)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + return mask; +} diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c new file mode 100644 index 000000000..247b68790 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_synth.c @@ -0,0 +1,676 @@ +/* + * OSS compatible sequencer driver + * + * synth device handlers + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "../seq_lock.h" +#include +#include +#include +#include + +/* + * constants + */ +#define SNDRV_SEQ_OSS_MAX_SYNTH_NAME 30 +#define MAX_SYSEX_BUFLEN 128 + + +/* + * definition of synth info records + */ + +/* sysex buffer */ +struct seq_oss_synth_sysex { + int len; + int skip; + unsigned char buf[MAX_SYSEX_BUFLEN]; +}; + +/* synth info */ +struct seq_oss_synth { + int seq_device; + + /* for synth_info */ + int synth_type; + int synth_subtype; + int nr_voices; + + char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME]; + struct snd_seq_oss_callback oper; + + int opened; + + void *private_data; + snd_use_lock_t use_lock; +}; + + +/* + * device table + */ +static int max_synth_devs; +static struct seq_oss_synth *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS]; +static struct seq_oss_synth midi_synth_dev = { + .seq_device = -1, + .synth_type = SYNTH_TYPE_MIDI, + .synth_subtype = 0, + .nr_voices = 16, + .name = "MIDI", +}; + +static DEFINE_SPINLOCK(register_lock); + +/* + * prototypes + */ +static struct seq_oss_synth *get_synthdev(struct seq_oss_devinfo *dp, int dev); +static void reset_channels(struct seq_oss_synthinfo *info); + +/* + * global initialization + */ +void __init +snd_seq_oss_synth_init(void) +{ + snd_use_lock_init(&midi_synth_dev.use_lock); +} + +/* + * registration of the synth device + */ +int +snd_seq_oss_synth_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + int i; + struct seq_oss_synth *rec; + struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev); + unsigned long flags; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return -ENOMEM; + rec->seq_device = -1; + rec->synth_type = reg->type; + rec->synth_subtype = reg->subtype; + rec->nr_voices = reg->nvoices; + rec->oper = reg->oper; + rec->private_data = reg->private_data; + rec->opened = 0; + snd_use_lock_init(&rec->use_lock); + + /* copy and truncate the name of synth device */ + strlcpy(rec->name, dev->name, sizeof(rec->name)); + + /* registration */ + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_synth_devs; i++) { + if (synth_devs[i] == NULL) + break; + } + if (i >= max_synth_devs) { + if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) { + spin_unlock_irqrestore(®ister_lock, flags); + pr_err("ALSA: seq_oss: no more synth slot\n"); + kfree(rec); + return -ENOMEM; + } + max_synth_devs++; + } + rec->seq_device = i; + synth_devs[i] = rec; + spin_unlock_irqrestore(®ister_lock, flags); + dev->driver_data = rec; +#ifdef SNDRV_OSS_INFO_DEV_SYNTH + if (i < SNDRV_CARDS) + snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name); +#endif + return 0; +} + + +int +snd_seq_oss_synth_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + int index; + struct seq_oss_synth *rec = dev->driver_data; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (index = 0; index < max_synth_devs; index++) { + if (synth_devs[index] == rec) + break; + } + if (index >= max_synth_devs) { + spin_unlock_irqrestore(®ister_lock, flags); + pr_err("ALSA: seq_oss: can't unregister synth\n"); + return -EINVAL; + } + synth_devs[index] = NULL; + if (index == max_synth_devs - 1) { + for (index--; index >= 0; index--) { + if (synth_devs[index]) + break; + } + max_synth_devs = index + 1; + } + spin_unlock_irqrestore(®ister_lock, flags); +#ifdef SNDRV_OSS_INFO_DEV_SYNTH + if (rec->seq_device < SNDRV_CARDS) + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device); +#endif + + snd_use_lock_sync(&rec->use_lock); + kfree(rec); + + return 0; +} + + +/* + */ +static struct seq_oss_synth * +get_sdev(int dev) +{ + struct seq_oss_synth *rec; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + rec = synth_devs[dev]; + if (rec) + snd_use_lock_use(&rec->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return rec; +} + + +/* + * set up synth tables + */ + +void +snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp) +{ + int i; + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + + dp->max_synthdev = max_synth_devs; + dp->synth_opened = 0; + memset(dp->synths, 0, sizeof(dp->synths)); + for (i = 0; i < dp->max_synthdev; i++) { + rec = get_sdev(i); + if (rec == NULL) + continue; + if (rec->oper.open == NULL || rec->oper.close == NULL) { + snd_use_lock_free(&rec->use_lock); + continue; + } + info = &dp->synths[i]; + info->arg.app_index = dp->port; + info->arg.file_mode = dp->file_mode; + info->arg.seq_mode = dp->seq_mode; + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) + info->arg.event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + else + info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS; + info->opened = 0; + if (!try_module_get(rec->oper.owner)) { + snd_use_lock_free(&rec->use_lock); + continue; + } + if (rec->oper.open(&info->arg, rec->private_data) < 0) { + module_put(rec->oper.owner); + snd_use_lock_free(&rec->use_lock); + continue; + } + info->nr_voices = rec->nr_voices; + if (info->nr_voices > 0) { + info->ch = kcalloc(info->nr_voices, sizeof(struct seq_oss_chinfo), GFP_KERNEL); + if (!info->ch) { + rec->oper.close(&info->arg); + module_put(rec->oper.owner); + snd_use_lock_free(&rec->use_lock); + continue; + } + reset_channels(info); + } + info->opened++; + rec->opened++; + dp->synth_opened++; + snd_use_lock_free(&rec->use_lock); + } +} + + +/* + * set up synth tables for MIDI emulation - /dev/music mode only + */ + +void +snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp) +{ + int i; + + if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) + return; + + for (i = 0; i < dp->max_mididev; i++) { + struct seq_oss_synthinfo *info; + info = &dp->synths[dp->max_synthdev]; + if (snd_seq_oss_midi_open(dp, i, dp->file_mode) < 0) + continue; + info->arg.app_index = dp->port; + info->arg.file_mode = dp->file_mode; + info->arg.seq_mode = dp->seq_mode; + info->arg.private_data = info; + info->is_midi = 1; + info->midi_mapped = i; + info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS; + snd_seq_oss_midi_get_addr(dp, i, &info->arg.addr); + info->opened = 1; + midi_synth_dev.opened++; + dp->max_synthdev++; + if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) + break; + } +} + + +/* + * clean up synth tables + */ + +void +snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp) +{ + int i; + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + + if (snd_BUG_ON(dp->max_synthdev > SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)) + return; + for (i = 0; i < dp->max_synthdev; i++) { + info = &dp->synths[i]; + if (! info->opened) + continue; + if (info->is_midi) { + if (midi_synth_dev.opened > 0) { + snd_seq_oss_midi_close(dp, info->midi_mapped); + midi_synth_dev.opened--; + } + } else { + rec = get_sdev(i); + if (rec == NULL) + continue; + if (rec->opened > 0) { + rec->oper.close(&info->arg); + module_put(rec->oper.owner); + rec->opened = 0; + } + snd_use_lock_free(&rec->use_lock); + } + kfree(info->sysex); + info->sysex = NULL; + kfree(info->ch); + info->ch = NULL; + } + dp->synth_opened = 0; + dp->max_synthdev = 0; +} + +static struct seq_oss_synthinfo * +get_synthinfo_nospec(struct seq_oss_devinfo *dp, int dev) +{ + if (dev < 0 || dev >= dp->max_synthdev) + return NULL; + dev = array_index_nospec(dev, SNDRV_SEQ_OSS_MAX_SYNTH_DEVS); + return &dp->synths[dev]; +} + +/* + * return synth device information pointer + */ +static struct seq_oss_synth * +get_synthdev(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info = get_synthinfo_nospec(dp, dev); + + if (!info) + return NULL; + if (!info->opened) + return NULL; + if (info->is_midi) { + rec = &midi_synth_dev; + snd_use_lock_use(&rec->use_lock); + } else { + rec = get_sdev(dev); + if (!rec) + return NULL; + } + if (! rec->opened) { + snd_use_lock_free(&rec->use_lock); + return NULL; + } + return rec; +} + + +/* + * reset note and velocity on each channel. + */ +static void +reset_channels(struct seq_oss_synthinfo *info) +{ + int i; + if (info->ch == NULL || ! info->nr_voices) + return; + for (i = 0; i < info->nr_voices; i++) { + info->ch[i].note = -1; + info->ch[i].vel = 0; + } +} + + +/* + * reset synth device: + * call reset callback. if no callback is defined, send a heartbeat + * event to the corresponding port. + */ +void +snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + + info = get_synthinfo_nospec(dp, dev); + if (!info || !info->opened) + return; + if (info->sysex) + info->sysex->len = 0; /* reset sysex */ + reset_channels(info); + if (info->is_midi) { + if (midi_synth_dev.opened <= 0) + return; + snd_seq_oss_midi_reset(dp, info->midi_mapped); + /* reopen the device */ + snd_seq_oss_midi_close(dp, dev); + if (snd_seq_oss_midi_open(dp, info->midi_mapped, + dp->file_mode) < 0) { + midi_synth_dev.opened--; + info->opened = 0; + kfree(info->sysex); + info->sysex = NULL; + kfree(info->ch); + info->ch = NULL; + } + return; + } + + rec = get_sdev(dev); + if (rec == NULL) + return; + if (rec->oper.reset) { + rec->oper.reset(&info->arg); + } else { + struct snd_seq_event ev; + memset(&ev, 0, sizeof(ev)); + snd_seq_oss_fill_addr(dp, &ev, info->arg.addr.client, + info->arg.addr.port); + ev.type = SNDRV_SEQ_EVENT_RESET; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + } + snd_use_lock_free(&rec->use_lock); +} + + +/* + * load a patch record: + * call load_patch callback function + */ +int +snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt, + const char __user *buf, int p, int c) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + int rc; + + info = get_synthinfo_nospec(dp, dev); + if (!info) + return -ENXIO; + + if (info->is_midi) + return 0; + if ((rec = get_synthdev(dp, dev)) == NULL) + return -ENXIO; + + if (rec->oper.load_patch == NULL) + rc = -ENXIO; + else + rc = rec->oper.load_patch(&info->arg, fmt, buf, p, c); + snd_use_lock_free(&rec->use_lock); + return rc; +} + +/* + * check if the device is valid synth device and return the synth info + */ +struct seq_oss_synthinfo * +snd_seq_oss_synth_info(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_synth *rec; + + rec = get_synthdev(dp, dev); + if (rec) { + snd_use_lock_free(&rec->use_lock); + return get_synthinfo_nospec(dp, dev); + } + return NULL; +} + + +/* + * receive OSS 6 byte sysex packet: + * the full sysex message will be sent if it reaches to the end of data + * (0xff). + */ +int +snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, struct snd_seq_event *ev) +{ + int i, send; + unsigned char *dest; + struct seq_oss_synth_sysex *sysex; + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info) + return -ENXIO; + + sysex = info->sysex; + if (sysex == NULL) { + sysex = kzalloc(sizeof(*sysex), GFP_KERNEL); + if (sysex == NULL) + return -ENOMEM; + info->sysex = sysex; + } + + send = 0; + dest = sysex->buf + sysex->len; + /* copy 6 byte packet to the buffer */ + for (i = 0; i < 6; i++) { + if (buf[i] == 0xff) { + send = 1; + break; + } + dest[i] = buf[i]; + sysex->len++; + if (sysex->len >= MAX_SYSEX_BUFLEN) { + sysex->len = 0; + sysex->skip = 1; + break; + } + } + + if (sysex->len && send) { + if (sysex->skip) { + sysex->skip = 0; + sysex->len = 0; + return -EINVAL; /* skip */ + } + /* copy the data to event record and send it */ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + if (snd_seq_oss_synth_addr(dp, dev, ev)) + return -EINVAL; + ev->data.ext.len = sysex->len; + ev->data.ext.ptr = sysex->buf; + sysex->len = 0; + return 0; + } + + return -EINVAL; /* skip */ +} + +/* + * fill the event source/destination addresses + */ +int +snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info = snd_seq_oss_synth_info(dp, dev); + + if (!info) + return -EINVAL; + snd_seq_oss_fill_addr(dp, ev, info->arg.addr.client, + info->arg.addr.port); + return 0; +} + + +/* + * OSS compatible ioctl + */ +int +snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd, unsigned long addr) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + int rc; + + info = get_synthinfo_nospec(dp, dev); + if (!info || info->is_midi) + return -ENXIO; + if ((rec = get_synthdev(dp, dev)) == NULL) + return -ENXIO; + if (rec->oper.ioctl == NULL) + rc = -ENXIO; + else + rc = rec->oper.ioctl(&info->arg, cmd, addr); + snd_use_lock_free(&rec->use_lock); + return rc; +} + + +/* + * send OSS raw events - SEQ_PRIVATE and SEQ_VOLUME + */ +int +snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev, unsigned char *data, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info || info->is_midi) + return -ENXIO; + ev->type = SNDRV_SEQ_EVENT_OSS; + memcpy(ev->data.raw8.d, data, 8); + return snd_seq_oss_synth_addr(dp, dev, ev); +} + + +/* + * create OSS compatible synth_info record + */ +int +snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info = get_synthinfo_nospec(dp, dev); + + if (!info) + return -ENXIO; + + if (info->is_midi) { + struct midi_info minf; + if (snd_seq_oss_midi_make_info(dp, info->midi_mapped, &minf)) + return -ENXIO; + inf->synth_type = SYNTH_TYPE_MIDI; + inf->synth_subtype = 0; + inf->nr_voices = 16; + inf->device = dev; + strlcpy(inf->name, minf.name, sizeof(inf->name)); + } else { + if ((rec = get_synthdev(dp, dev)) == NULL) + return -ENXIO; + inf->synth_type = rec->synth_type; + inf->synth_subtype = rec->synth_subtype; + inf->nr_voices = rec->nr_voices; + inf->device = dev; + strlcpy(inf->name, rec->name, sizeof(inf->name)); + snd_use_lock_free(&rec->use_lock); + } + return 0; +} + + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ +void +snd_seq_oss_synth_info_read(struct snd_info_buffer *buf) +{ + int i; + struct seq_oss_synth *rec; + + snd_iprintf(buf, "\nNumber of synth devices: %d\n", max_synth_devs); + for (i = 0; i < max_synth_devs; i++) { + snd_iprintf(buf, "\nsynth %d: ", i); + rec = get_sdev(i); + if (rec == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "[%s]\n", rec->name); + snd_iprintf(buf, " type 0x%x : subtype 0x%x : voices %d\n", + rec->synth_type, rec->synth_subtype, + rec->nr_voices); + snd_iprintf(buf, " capabilities : ioctl %s / load_patch %s\n", + enabled_str((long)rec->oper.ioctl), + enabled_str((long)rec->oper.load_patch)); + snd_use_lock_free(&rec->use_lock); + } +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_synth.h b/sound/core/seq/oss/seq_oss_synth.h new file mode 100644 index 000000000..a63f9e229 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_synth.h @@ -0,0 +1,52 @@ +/* + * OSS compatible sequencer driver + * + * synth device information + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_SYNTH_H +#define __SEQ_OSS_SYNTH_H + +#include "seq_oss_device.h" +#include +#include + +void snd_seq_oss_synth_init(void); +int snd_seq_oss_synth_probe(struct device *dev); +int snd_seq_oss_synth_remove(struct device *dev); +void snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp); +void snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp); +void snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp); + +void snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev); +int snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt, + const char __user *buf, int p, int c); +struct seq_oss_synthinfo *snd_seq_oss_synth_info(struct seq_oss_devinfo *dp, + int dev); +int snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, + struct snd_seq_event *ev); +int snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev); +int snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd, + unsigned long addr); +int snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev, + unsigned char *data, struct snd_seq_event *ev); + +int snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf); + +#endif diff --git a/sound/core/seq/oss/seq_oss_timer.c b/sound/core/seq/oss/seq_oss_timer.c new file mode 100644 index 000000000..ba127c225 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_timer.c @@ -0,0 +1,277 @@ +/* + * OSS compatible sequencer driver + * + * Timer control routines + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include +#include + +/* + */ +#define MIN_OSS_TEMPO 8 +#define MAX_OSS_TEMPO 360 +#define MIN_OSS_TIMEBASE 1 +#define MAX_OSS_TIMEBASE 1000 + +/* + */ +static void calc_alsa_tempo(struct seq_oss_timer *timer); +static int send_timer_event(struct seq_oss_devinfo *dp, int type, int value); + + +/* + * create and register a new timer. + * if queue is not started yet, start it. + */ +struct seq_oss_timer * +snd_seq_oss_timer_new(struct seq_oss_devinfo *dp) +{ + struct seq_oss_timer *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (rec == NULL) + return NULL; + + rec->dp = dp; + rec->cur_tick = 0; + rec->realtime = 0; + rec->running = 0; + rec->oss_tempo = 60; + rec->oss_timebase = 100; + calc_alsa_tempo(rec); + + return rec; +} + + +/* + * delete timer. + * if no more timer exists, stop the queue. + */ +void +snd_seq_oss_timer_delete(struct seq_oss_timer *rec) +{ + if (rec) { + snd_seq_oss_timer_stop(rec); + kfree(rec); + } +} + + +/* + * process one timing event + * return 1 : event proceseed -- skip this event + * 0 : not a timer event -- enqueue this event + */ +int +snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *ev) +{ + abstime_t parm = ev->t.time; + + if (ev->t.code == EV_TIMING) { + switch (ev->t.cmd) { + case TMR_WAIT_REL: + parm += rec->cur_tick; + rec->realtime = 0; + /* fall through and continue to next */ + case TMR_WAIT_ABS: + if (parm == 0) { + rec->realtime = 1; + } else if (parm >= rec->cur_tick) { + rec->realtime = 0; + rec->cur_tick = parm; + } + return 1; /* skip this event */ + + case TMR_START: + snd_seq_oss_timer_start(rec); + return 1; + + } + } else if (ev->s.code == SEQ_WAIT) { + /* time = from 1 to 3 bytes */ + parm = (ev->echo >> 8) & 0xffffff; + if (parm > rec->cur_tick) { + /* set next event time */ + rec->cur_tick = parm; + rec->realtime = 0; + } + return 1; + } + + return 0; +} + + +/* + * convert tempo units + */ +static void +calc_alsa_tempo(struct seq_oss_timer *timer) +{ + timer->tempo = (60 * 1000000) / timer->oss_tempo; + timer->ppq = timer->oss_timebase; +} + + +/* + * dispatch a timer event + */ +static int +send_timer_event(struct seq_oss_devinfo *dp, int type, int value) +{ + struct snd_seq_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = type; + ev.source.client = dp->cseq; + ev.source.port = 0; + ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM; + ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + ev.queue = dp->queue; + ev.data.queue.queue = dp->queue; + ev.data.queue.param.value = value; + return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0); +} + +/* + * set queue tempo and start queue + */ +int +snd_seq_oss_timer_start(struct seq_oss_timer *timer) +{ + struct seq_oss_devinfo *dp = timer->dp; + struct snd_seq_queue_tempo tmprec; + + if (timer->running) + snd_seq_oss_timer_stop(timer); + + memset(&tmprec, 0, sizeof(tmprec)); + tmprec.queue = dp->queue; + tmprec.ppq = timer->ppq; + tmprec.tempo = timer->tempo; + snd_seq_set_queue_tempo(dp->cseq, &tmprec); + + send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0); + timer->running = 1; + timer->cur_tick = 0; + return 0; +} + + +/* + * stop queue + */ +int +snd_seq_oss_timer_stop(struct seq_oss_timer *timer) +{ + if (! timer->running) + return 0; + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0); + timer->running = 0; + return 0; +} + + +/* + * continue queue + */ +int +snd_seq_oss_timer_continue(struct seq_oss_timer *timer) +{ + if (timer->running) + return 0; + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0); + timer->running = 1; + return 0; +} + + +/* + * change queue tempo + */ +int +snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value) +{ + if (value < MIN_OSS_TEMPO) + value = MIN_OSS_TEMPO; + else if (value > MAX_OSS_TEMPO) + value = MAX_OSS_TEMPO; + timer->oss_tempo = value; + calc_alsa_tempo(timer); + if (timer->running) + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo); + return 0; +} + + +/* + * ioctls + */ +int +snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg) +{ + int value; + + if (cmd == SNDCTL_SEQ_CTRLRATE) { + /* if *arg == 0, just return the current rate */ + if (get_user(value, arg)) + return -EFAULT; + if (value) + return -EINVAL; + value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60; + return put_user(value, arg) ? -EFAULT : 0; + } + + if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) + return 0; + + switch (cmd) { + case SNDCTL_TMR_START: + return snd_seq_oss_timer_start(timer); + case SNDCTL_TMR_STOP: + return snd_seq_oss_timer_stop(timer); + case SNDCTL_TMR_CONTINUE: + return snd_seq_oss_timer_continue(timer); + case SNDCTL_TMR_TEMPO: + if (get_user(value, arg)) + return -EFAULT; + return snd_seq_oss_timer_tempo(timer, value); + case SNDCTL_TMR_TIMEBASE: + if (get_user(value, arg)) + return -EFAULT; + if (value < MIN_OSS_TIMEBASE) + value = MIN_OSS_TIMEBASE; + else if (value > MAX_OSS_TIMEBASE) + value = MAX_OSS_TIMEBASE; + timer->oss_timebase = value; + calc_alsa_tempo(timer); + return 0; + + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SELECT: + case SNDCTL_TMR_SOURCE: + /* not supported */ + return 0; + } + return 0; +} diff --git a/sound/core/seq/oss/seq_oss_timer.h b/sound/core/seq/oss/seq_oss_timer.h new file mode 100644 index 000000000..b995bd68a --- /dev/null +++ b/sound/core/seq/oss/seq_oss_timer.h @@ -0,0 +1,70 @@ +/* + * OSS compatible sequencer driver + * timer handling routines + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_TIMER_H +#define __SEQ_OSS_TIMER_H + +#include "seq_oss_device.h" + +/* + * timer information definition + */ +struct seq_oss_timer { + struct seq_oss_devinfo *dp; + reltime_t cur_tick; + int realtime; + int running; + int tempo, ppq; /* ALSA queue */ + int oss_tempo, oss_timebase; +}; + + +struct seq_oss_timer *snd_seq_oss_timer_new(struct seq_oss_devinfo *dp); +void snd_seq_oss_timer_delete(struct seq_oss_timer *dp); + +int snd_seq_oss_timer_start(struct seq_oss_timer *timer); +int snd_seq_oss_timer_stop(struct seq_oss_timer *timer); +int snd_seq_oss_timer_continue(struct seq_oss_timer *timer); +int snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value); +#define snd_seq_oss_timer_reset snd_seq_oss_timer_start + +int snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg); + +/* + * get current processed time + */ +static inline abstime_t +snd_seq_oss_timer_cur_tick(struct seq_oss_timer *timer) +{ + return timer->cur_tick; +} + + +/* + * is realtime event? + */ +static inline int +snd_seq_oss_timer_is_realtime(struct seq_oss_timer *timer) +{ + return timer->realtime; +} + +#endif diff --git a/sound/core/seq/oss/seq_oss_writeq.c b/sound/core/seq/oss/seq_oss_writeq.c new file mode 100644 index 000000000..5e04f4df1 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_writeq.c @@ -0,0 +1,172 @@ +/* + * OSS compatible sequencer driver + * + * seq_oss_writeq.c - write queue and sync + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_writeq.h" +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include +#include "../seq_lock.h" +#include "../seq_clientmgr.h" +#include +#include +#include + + +/* + * create a write queue record + */ +struct seq_oss_writeq * +snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen) +{ + struct seq_oss_writeq *q; + struct snd_seq_client_pool pool; + + if ((q = kzalloc(sizeof(*q), GFP_KERNEL)) == NULL) + return NULL; + q->dp = dp; + q->maxlen = maxlen; + spin_lock_init(&q->sync_lock); + q->sync_event_put = 0; + q->sync_time = 0; + init_waitqueue_head(&q->sync_sleep); + + memset(&pool, 0, sizeof(pool)); + pool.client = dp->cseq; + pool.output_pool = maxlen; + pool.output_room = maxlen / 2; + + snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool); + + return q; +} + +/* + * delete the write queue + */ +void +snd_seq_oss_writeq_delete(struct seq_oss_writeq *q) +{ + if (q) { + snd_seq_oss_writeq_clear(q); /* to be sure */ + kfree(q); + } +} + + +/* + * reset the write queue + */ +void +snd_seq_oss_writeq_clear(struct seq_oss_writeq *q) +{ + struct snd_seq_remove_events reset; + + memset(&reset, 0, sizeof(reset)); + reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */ + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset); + + /* wake up sleepers if any */ + snd_seq_oss_writeq_wakeup(q, 0); +} + +/* + * wait until the write buffer has enough room + */ +int +snd_seq_oss_writeq_sync(struct seq_oss_writeq *q) +{ + struct seq_oss_devinfo *dp = q->dp; + abstime_t time; + + time = snd_seq_oss_timer_cur_tick(dp->timer); + if (q->sync_time >= time) + return 0; /* already finished */ + + if (! q->sync_event_put) { + struct snd_seq_event ev; + union evrec *rec; + + /* put echoback event */ + memset(&ev, 0, sizeof(ev)); + ev.flags = 0; + ev.type = SNDRV_SEQ_EVENT_ECHO; + ev.time.tick = time; + /* echo back to itself */ + snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port); + rec = (union evrec *)&ev.data; + rec->t.code = SEQ_SYNCTIMER; + rec->t.time = time; + q->sync_event_put = 1; + snd_seq_kernel_client_enqueue_blocking(dp->cseq, &ev, NULL, 0, 0); + } + + wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ); + if (signal_pending(current)) + /* interrupted - return 0 to finish sync */ + q->sync_event_put = 0; + if (! q->sync_event_put || q->sync_time >= time) + return 0; + return 1; +} + +/* + * wake up sync - echo event was catched + */ +void +snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time) +{ + unsigned long flags; + + spin_lock_irqsave(&q->sync_lock, flags); + q->sync_time = time; + q->sync_event_put = 0; + wake_up(&q->sync_sleep); + spin_unlock_irqrestore(&q->sync_lock, flags); +} + + +/* + * return the unused pool size + */ +int +snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q) +{ + struct snd_seq_client_pool pool; + pool.client = q->dp->cseq; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool); + return pool.output_free; +} + + +/* + * set output threshold size from ioctl + */ +void +snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int val) +{ + struct snd_seq_client_pool pool; + pool.client = q->dp->cseq; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool); + pool.output_room = val; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool); +} + diff --git a/sound/core/seq/oss/seq_oss_writeq.h b/sound/core/seq/oss/seq_oss_writeq.h new file mode 100644 index 000000000..c469d2967 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_writeq.h @@ -0,0 +1,50 @@ +/* + * OSS compatible sequencer driver + * write priority queue + * + * Copyright (C) 1998,99 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_WRITEQ_H +#define __SEQ_OSS_WRITEQ_H + +#include "seq_oss_device.h" + + +struct seq_oss_writeq { + struct seq_oss_devinfo *dp; + int maxlen; + abstime_t sync_time; + int sync_event_put; + wait_queue_head_t sync_sleep; + spinlock_t sync_lock; +}; + + +/* + * seq_oss_writeq.c + */ +struct seq_oss_writeq *snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen); +void snd_seq_oss_writeq_delete(struct seq_oss_writeq *q); +void snd_seq_oss_writeq_clear(struct seq_oss_writeq *q); +int snd_seq_oss_writeq_sync(struct seq_oss_writeq *q); +void snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time); +int snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q); +void snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int size); + + +#endif diff --git a/sound/core/seq/seq.c b/sound/core/seq/seq.c new file mode 100644 index 000000000..7de98d71f --- /dev/null +++ b/sound/core/seq/seq.c @@ -0,0 +1,135 @@ +/* + * ALSA sequencer main module + * Copyright (c) 1998-1999 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include "seq_clientmgr.h" +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_lock.h" +#include "seq_timer.h" +#include "seq_system.h" +#include "seq_info.h" +#include +#include + +#if defined(CONFIG_SND_SEQ_DUMMY_MODULE) +int seq_client_load[15] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 14] = -1}; +#else +int seq_client_load[15] = {[0 ... 14] = -1}; +#endif +int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL; +int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE; +int seq_default_timer_card = -1; +int seq_default_timer_device = +#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT + SNDRV_TIMER_GLOBAL_HRTIMER +#else + SNDRV_TIMER_GLOBAL_SYSTEM +#endif + ; +int seq_default_timer_subdevice = 0; +int seq_default_timer_resolution = 0; /* Hz */ + +MODULE_AUTHOR("Frank van de Pol , Jaroslav Kysela "); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer."); +MODULE_LICENSE("GPL"); + +module_param_array(seq_client_load, int, NULL, 0444); +MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod."); +module_param(seq_default_timer_class, int, 0644); +MODULE_PARM_DESC(seq_default_timer_class, "The default timer class."); +module_param(seq_default_timer_sclass, int, 0644); +MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class."); +module_param(seq_default_timer_card, int, 0644); +MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number."); +module_param(seq_default_timer_device, int, 0644); +MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number."); +module_param(seq_default_timer_subdevice, int, 0644); +MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number."); +module_param(seq_default_timer_resolution, int, 0644); +MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz."); + +MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_SEQUENCER); +MODULE_ALIAS("devname:snd/seq"); + +/* + * INIT PART + */ + +static int __init alsa_seq_init(void) +{ + int err; + + err = client_init_data(); + if (err < 0) + goto error; + + /* register sequencer device */ + err = snd_sequencer_device_init(); + if (err < 0) + goto error; + + /* register proc interface */ + err = snd_seq_info_init(); + if (err < 0) + goto error_device; + + /* register our internal client */ + err = snd_seq_system_client_init(); + if (err < 0) + goto error_info; + + snd_seq_autoload_init(); + return 0; + + error_info: + snd_seq_info_done(); + error_device: + snd_sequencer_device_done(); + error: + return err; +} + +static void __exit alsa_seq_exit(void) +{ + /* unregister our internal client */ + snd_seq_system_client_done(); + + /* unregister proc interface */ + snd_seq_info_done(); + + /* delete timing queues */ + snd_seq_queues_delete(); + + /* unregister sequencer device */ + snd_sequencer_device_done(); + + snd_seq_autoload_exit(); +} + +module_init(alsa_seq_init) +module_exit(alsa_seq_exit) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c new file mode 100644 index 000000000..aaf9c419c --- /dev/null +++ b/sound/core/seq/seq_clientmgr.c @@ -0,0 +1,2540 @@ +/* + * ALSA sequencer Client Manager + * Copyright (c) 1998-2001 by Frank van de Pol + * Jaroslav Kysela + * Takashi Iwai + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include "seq_clientmgr.h" +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_timer.h" +#include "seq_info.h" +#include "seq_system.h" +#include +#ifdef CONFIG_COMPAT +#include +#endif + +/* Client Manager + + * this module handles the connections of userland and kernel clients + * + */ + +/* + * There are four ranges of client numbers (last two shared): + * 0..15: global clients + * 16..127: statically allocated client numbers for cards 0..27 + * 128..191: dynamically allocated client numbers for cards 28..31 + * 128..191: dynamically allocated client numbers for applications + */ + +/* number of kernel non-card clients */ +#define SNDRV_SEQ_GLOBAL_CLIENTS 16 +/* clients per cards, for static clients */ +#define SNDRV_SEQ_CLIENTS_PER_CARD 4 +/* dynamically allocated client numbers (both kernel drivers and user space) */ +#define SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN 128 + +#define SNDRV_SEQ_LFLG_INPUT 0x0001 +#define SNDRV_SEQ_LFLG_OUTPUT 0x0002 +#define SNDRV_SEQ_LFLG_OPEN (SNDRV_SEQ_LFLG_INPUT|SNDRV_SEQ_LFLG_OUTPUT) + +static DEFINE_SPINLOCK(clients_lock); +static DEFINE_MUTEX(register_mutex); + +/* + * client table + */ +static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; +static struct snd_seq_usage client_usage; + +/* + * prototypes + */ +static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop); +static int snd_seq_deliver_single_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int filter, int atomic, int hop); + +/* + */ +static inline unsigned short snd_seq_file_flags(struct file *file) +{ + switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) { + case FMODE_WRITE: + return SNDRV_SEQ_LFLG_OUTPUT; + case FMODE_READ: + return SNDRV_SEQ_LFLG_INPUT; + default: + return SNDRV_SEQ_LFLG_OPEN; + } +} + +static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) +{ + return snd_seq_total_cells(client->pool) > 0; +} + +/* return pointer to client structure for specified id */ +static struct snd_seq_client *clientptr(int clientid) +{ + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } + return clienttab[clientid]; +} + +struct snd_seq_client *snd_seq_client_use_ptr(int clientid) +{ + unsigned long flags; + struct snd_seq_client *client; + + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } + spin_lock_irqsave(&clients_lock, flags); + client = clientptr(clientid); + if (client) + goto __lock; + if (clienttablock[clientid]) { + spin_unlock_irqrestore(&clients_lock, flags); + return NULL; + } + spin_unlock_irqrestore(&clients_lock, flags); +#ifdef CONFIG_MODULES + if (!in_interrupt()) { + static char client_requested[SNDRV_SEQ_GLOBAL_CLIENTS]; + static char card_requested[SNDRV_CARDS]; + if (clientid < SNDRV_SEQ_GLOBAL_CLIENTS) { + int idx; + + if (!client_requested[clientid]) { + client_requested[clientid] = 1; + for (idx = 0; idx < 15; idx++) { + if (seq_client_load[idx] < 0) + break; + if (seq_client_load[idx] == clientid) { + request_module("snd-seq-client-%i", + clientid); + break; + } + } + } + } else if (clientid < SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) { + int card = (clientid - SNDRV_SEQ_GLOBAL_CLIENTS) / + SNDRV_SEQ_CLIENTS_PER_CARD; + if (card < snd_ecards_limit) { + if (! card_requested[card]) { + card_requested[card] = 1; + snd_request_card(card); + } + snd_seq_device_load_drivers(); + } + } + spin_lock_irqsave(&clients_lock, flags); + client = clientptr(clientid); + if (client) + goto __lock; + spin_unlock_irqrestore(&clients_lock, flags); + } +#endif + return NULL; + + __lock: + snd_use_lock_use(&client->use_lock); + spin_unlock_irqrestore(&clients_lock, flags); + return client; +} + +static void usage_alloc(struct snd_seq_usage *res, int num) +{ + res->cur += num; + if (res->cur > res->peak) + res->peak = res->cur; +} + +static void usage_free(struct snd_seq_usage *res, int num) +{ + res->cur -= num; +} + +/* initialise data structures */ +int __init client_init_data(void) +{ + /* zap out the client table */ + memset(&clienttablock, 0, sizeof(clienttablock)); + memset(&clienttab, 0, sizeof(clienttab)); + return 0; +} + + +static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) +{ + unsigned long flags; + int c; + struct snd_seq_client *client; + + /* init client data */ + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (client == NULL) + return NULL; + client->pool = snd_seq_pool_new(poolsize); + if (client->pool == NULL) { + kfree(client); + return NULL; + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); + rwlock_init(&client->ports_lock); + mutex_init(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + mutex_init(&client->ioctl_mutex); + + /* find free slot in the client table */ + spin_lock_irqsave(&clients_lock, flags); + if (client_index < 0) { + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { + if (clienttab[c] || clienttablock[c]) + continue; + clienttab[client->number = c] = client; + spin_unlock_irqrestore(&clients_lock, flags); + return client; + } + } else { + if (clienttab[client_index] == NULL && !clienttablock[client_index]) { + clienttab[client->number = client_index] = client; + spin_unlock_irqrestore(&clients_lock, flags); + return client; + } + } + spin_unlock_irqrestore(&clients_lock, flags); + snd_seq_pool_delete(&client->pool); + kfree(client); + return NULL; /* no free slot found or busy, return failure code */ +} + + +static int seq_free_client1(struct snd_seq_client *client) +{ + unsigned long flags; + + if (!client) + return 0; + spin_lock_irqsave(&clients_lock, flags); + clienttablock[client->number] = 1; + clienttab[client->number] = NULL; + spin_unlock_irqrestore(&clients_lock, flags); + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); + snd_use_lock_sync(&client->use_lock); + snd_seq_queue_client_termination(client->number); + if (client->pool) + snd_seq_pool_delete(&client->pool); + spin_lock_irqsave(&clients_lock, flags); + clienttablock[client->number] = 0; + spin_unlock_irqrestore(&clients_lock, flags); + return 0; +} + + +static void seq_free_client(struct snd_seq_client * client) +{ + mutex_lock(®ister_mutex); + switch (client->type) { + case NO_CLIENT: + pr_warn("ALSA: seq: Trying to free unused client %d\n", + client->number); + break; + case USER_CLIENT: + case KERNEL_CLIENT: + seq_free_client1(client); + usage_free(&client_usage, 1); + break; + + default: + pr_err("ALSA: seq: Trying to free client %d with undefined type = %d\n", + client->number, client->type); + } + mutex_unlock(®ister_mutex); + + snd_seq_system_client_ev_client_exit(client->number); +} + + + +/* -------------------------------------------------------- */ + +/* create a user client */ +static int snd_seq_open(struct inode *inode, struct file *file) +{ + int c, mode; /* client id */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; + int err; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + mutex_lock(®ister_mutex); + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); + if (!client) { + mutex_unlock(®ister_mutex); + return -ENOMEM; /* failure code */ + } + + mode = snd_seq_file_flags(file); + if (mode & SNDRV_SEQ_LFLG_INPUT) + client->accept_input = 1; + if (mode & SNDRV_SEQ_LFLG_OUTPUT) + client->accept_output = 1; + + user = &client->data.user; + user->fifo = NULL; + user->fifo_pool_size = 0; + + if (mode & SNDRV_SEQ_LFLG_INPUT) { + user->fifo_pool_size = SNDRV_SEQ_DEFAULT_CLIENT_EVENTS; + user->fifo = snd_seq_fifo_new(user->fifo_pool_size); + if (user->fifo == NULL) { + seq_free_client1(client); + kfree(client); + mutex_unlock(®ister_mutex); + return -ENOMEM; + } + } + + usage_alloc(&client_usage, 1); + client->type = USER_CLIENT; + mutex_unlock(®ister_mutex); + + c = client->number; + file->private_data = client; + + /* fill client data */ + user->file = file; + sprintf(client->name, "Client-%d", c); + client->data.user.owner = get_pid(task_pid(current)); + + /* make others aware this new client */ + snd_seq_system_client_ev_client_start(c); + + return 0; +} + +/* delete a user client */ +static int snd_seq_release(struct inode *inode, struct file *file) +{ + struct snd_seq_client *client = file->private_data; + + if (client) { + seq_free_client(client); + if (client->data.user.fifo) + snd_seq_fifo_delete(&client->data.user.fifo); + put_pid(client->data.user.owner); + kfree(client); + } + + return 0; +} + + +/* handle client read() */ +/* possible error values: + * -ENXIO invalid client or file open mode + * -ENOSPC FIFO overflow (the flag is cleared after this error report) + * -EINVAL no enough user-space buffer to write the whole event + * -EFAULT seg. fault during copy to user space + */ +static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + struct snd_seq_client *client = file->private_data; + struct snd_seq_fifo *fifo; + int err; + long result = 0; + struct snd_seq_event_cell *cell; + + if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT)) + return -ENXIO; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return -ENXIO; + + if (!client->accept_input || (fifo = client->data.user.fifo) == NULL) + return -ENXIO; + + if (atomic_read(&fifo->overflow) > 0) { + /* buffer overflow is detected */ + snd_seq_fifo_clear(fifo); + /* return error code */ + return -ENOSPC; + } + + cell = NULL; + err = 0; + snd_seq_fifo_lock(fifo); + + /* while data available in queue */ + while (count >= sizeof(struct snd_seq_event)) { + int nonblock; + + nonblock = (file->f_flags & O_NONBLOCK) || result > 0; + if ((err = snd_seq_fifo_cell_out(fifo, &cell, nonblock)) < 0) { + break; + } + if (snd_seq_ev_is_variable(&cell->event)) { + struct snd_seq_event tmpev; + tmpev = cell->event; + tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; + if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) { + err = -EFAULT; + break; + } + count -= sizeof(struct snd_seq_event); + buf += sizeof(struct snd_seq_event); + err = snd_seq_expand_var_event(&cell->event, count, + (char __force *)buf, 0, + sizeof(struct snd_seq_event)); + if (err < 0) + break; + result += err; + count -= err; + buf += err; + } else { + if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) { + err = -EFAULT; + break; + } + count -= sizeof(struct snd_seq_event); + buf += sizeof(struct snd_seq_event); + } + snd_seq_cell_free(cell); + cell = NULL; /* to be sure */ + result += sizeof(struct snd_seq_event); + } + + if (err < 0) { + if (cell) + snd_seq_fifo_cell_putback(fifo, cell); + if (err == -EAGAIN && result > 0) + err = 0; + } + snd_seq_fifo_unlock(fifo); + + return (err < 0) ? err : result; +} + + +/* + * check access permission to the port + */ +static int check_port_perm(struct snd_seq_client_port *port, unsigned int flags) +{ + if ((port->capability & flags) != flags) + return 0; + return flags; +} + +/* + * check if the destination client is available, and return the pointer + * if filter is non-zero, client filter bitmap is tested. + */ +static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event, + int filter) +{ + struct snd_seq_client *dest; + + dest = snd_seq_client_use_ptr(event->dest.client); + if (dest == NULL) + return NULL; + if (! dest->accept_input) + goto __not_avail; + if ((dest->filter & SNDRV_SEQ_FILTER_USE_EVENT) && + ! test_bit(event->type, dest->event_filter)) + goto __not_avail; + if (filter && !(dest->filter & filter)) + goto __not_avail; + + return dest; /* ok - accessible */ +__not_avail: + snd_seq_client_unlock(dest); + return NULL; +} + + +/* + * Return the error event. + * + * If the receiver client is a user client, the original event is + * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If + * the original event is also variable length, the external data is + * copied after the event record. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. + */ +static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop) +{ + struct snd_seq_event bounce_ev; + int result; + + if (client == NULL || + ! (client->filter & SNDRV_SEQ_FILTER_BOUNCE) || + ! client->accept_input) + return 0; /* ignored */ + + /* set up quoted error */ + memset(&bounce_ev, 0, sizeof(bounce_ev)); + bounce_ev.type = SNDRV_SEQ_EVENT_KERNEL_ERROR; + bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + bounce_ev.queue = SNDRV_SEQ_QUEUE_DIRECT; + bounce_ev.source.client = SNDRV_SEQ_CLIENT_SYSTEM; + bounce_ev.source.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + bounce_ev.dest.client = client->number; + bounce_ev.dest.port = event->source.port; + bounce_ev.data.quote.origin = event->dest; + bounce_ev.data.quote.event = event; + bounce_ev.data.quote.value = -err; /* use positive value */ + result = snd_seq_deliver_single_event(NULL, &bounce_ev, 0, atomic, hop + 1); + if (result < 0) { + client->event_lost++; + return result; + } + + return result; +} + + +/* + * rewrite the time-stamp of the event record with the curren time + * of the given queue. + * return non-zero if updated. + */ +static int update_timestamp_of_queue(struct snd_seq_event *event, + int queue, int real_time) +{ + struct snd_seq_queue *q; + + q = queueptr(queue); + if (! q) + return 0; + event->queue = queue; + event->flags &= ~SNDRV_SEQ_TIME_STAMP_MASK; + if (real_time) { + event->time.time = snd_seq_timer_get_cur_time(q->timer, true); + event->flags |= SNDRV_SEQ_TIME_STAMP_REAL; + } else { + event->time.tick = snd_seq_timer_get_cur_tick(q->timer); + event->flags |= SNDRV_SEQ_TIME_STAMP_TICK; + } + queuefree(q); + return 1; +} + + +/* + * deliver an event to the specified destination. + * if filter is non-zero, client filter bitmap is tested. + * + * RETURN VALUE: 0 : if succeeded + * <0 : error + */ +static int snd_seq_deliver_single_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int filter, int atomic, int hop) +{ + struct snd_seq_client *dest = NULL; + struct snd_seq_client_port *dest_port = NULL; + int result = -ENOENT; + int direct; + + direct = snd_seq_ev_is_direct(event); + + dest = get_event_dest_client(event, filter); + if (dest == NULL) + goto __skip; + dest_port = snd_seq_port_use_ptr(dest, event->dest.port); + if (dest_port == NULL) + goto __skip; + + /* check permission */ + if (! check_port_perm(dest_port, SNDRV_SEQ_PORT_CAP_WRITE)) { + result = -EPERM; + goto __skip; + } + + if (dest_port->timestamping) + update_timestamp_of_queue(event, dest_port->time_queue, + dest_port->time_real); + + switch (dest->type) { + case USER_CLIENT: + if (dest->data.user.fifo) + result = snd_seq_fifo_event_in(dest->data.user.fifo, event); + break; + + case KERNEL_CLIENT: + if (dest_port->event_input == NULL) + break; + result = dest_port->event_input(event, direct, + dest_port->private_data, + atomic, hop); + break; + default: + break; + } + + __skip: + if (dest_port) + snd_seq_port_unlock(dest_port); + if (dest) + snd_seq_client_unlock(dest); + + if (result < 0 && !direct) { + result = bounce_error_event(client, event, result, atomic, hop); + } + return result; +} + + +/* + * send the event to all subscribers: + */ +static int deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_subscribers *subs; + int err, result = 0, num_ev = 0; + struct snd_seq_event event_saved; + struct snd_seq_client_port *src_port; + struct snd_seq_port_subs_info *grp; + + src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port == NULL) + return -EINVAL; /* invalid source port */ + /* save original event record */ + event_saved = *event; + grp = &src_port->c_src; + + /* lock list */ + if (atomic) + read_lock(&grp->list_lock); + else + down_read_nested(&grp->list_mutex, hop); + list_for_each_entry(subs, &grp->list_head, src_list) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; + event->dest = subs->info.dest; + if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) + /* convert time according to flag with subscription */ + update_timestamp_of_queue(event, subs->info.queue, + subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL); + err = snd_seq_deliver_single_event(client, event, + 0, atomic, hop); + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } + num_ev++; + /* restore original event record */ + *event = event_saved; + } + if (atomic) + read_unlock(&grp->list_lock); + else + up_read(&grp->list_mutex); + *event = event_saved; /* restore */ + snd_seq_port_unlock(src_port); + return (result < 0) ? result : num_ev; +} + + +#ifdef SUPPORT_BROADCAST +/* + * broadcast to all ports: + */ +static int port_broadcast_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int atomic, int hop) +{ + int num_ev = 0, err, result = 0; + struct snd_seq_client *dest_client; + struct snd_seq_client_port *port; + + dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST); + if (dest_client == NULL) + return 0; /* no matching destination */ + + read_lock(&dest_client->ports_lock); + list_for_each_entry(port, &dest_client->ports_list_head, list) { + event->dest.port = port->addr.port; + /* pass NULL as source client to avoid error bounce */ + err = snd_seq_deliver_single_event(NULL, event, + SNDRV_SEQ_FILTER_BROADCAST, + atomic, hop); + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } + num_ev++; + } + read_unlock(&dest_client->ports_lock); + snd_seq_client_unlock(dest_client); + event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */ + return (result < 0) ? result : num_ev; +} + +/* + * send the event to all clients: + * if destination port is also ADDRESS_BROADCAST, deliver to all ports. + */ +static int broadcast_event(struct snd_seq_client *client, + struct snd_seq_event *event, int atomic, int hop) +{ + int err, result = 0, num_ev = 0; + int dest; + struct snd_seq_addr addr; + + addr = event->dest; /* save */ + + for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) { + /* don't send to itself */ + if (dest == client->number) + continue; + event->dest.client = dest; + event->dest.port = addr.port; + if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST) + err = port_broadcast_event(client, event, atomic, hop); + else + /* pass NULL as source client to avoid error bounce */ + err = snd_seq_deliver_single_event(NULL, event, + SNDRV_SEQ_FILTER_BROADCAST, + atomic, hop); + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } + num_ev += err; + } + event->dest = addr; /* restore */ + return (result < 0) ? result : num_ev; +} + + +/* multicast - not supported yet */ +static int multicast_event(struct snd_seq_client *client, struct snd_seq_event *event, + int atomic, int hop) +{ + pr_debug("ALSA: seq: multicast not supported yet.\n"); + return 0; /* ignored */ +} +#endif /* SUPPORT_BROADCAST */ + + +/* deliver an event to the destination port(s). + * if the event is to subscribers or broadcast, the event is dispatched + * to multiple targets. + * + * RETURN VALUE: n > 0 : the number of delivered events. + * n == 0 : the event was not passed to any client. + * n < 0 : error - event was not processed. + */ +static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_event *event, + int atomic, int hop) +{ + int result; + + hop++; + if (hop >= SNDRV_SEQ_MAX_HOPS) { + pr_debug("ALSA: seq: too long delivery path (%d:%d->%d:%d)\n", + event->source.client, event->source.port, + event->dest.client, event->dest.port); + return -EMLINK; + } + + if (snd_seq_ev_is_variable(event) && + snd_BUG_ON(atomic && (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR))) + return -EINVAL; + + if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS || + event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) + result = deliver_to_subscribers(client, event, atomic, hop); +#ifdef SUPPORT_BROADCAST + else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST || + event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST) + result = broadcast_event(client, event, atomic, hop); + else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS) + result = multicast_event(client, event, atomic, hop); + else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST) + result = port_broadcast_event(client, event, atomic, hop); +#endif + else + result = snd_seq_deliver_single_event(client, event, 0, atomic, hop); + + return result; +} + +/* + * dispatch an event cell: + * This function is called only from queue check routines in timer + * interrupts or after enqueued. + * The event cell shall be released or re-queued in this function. + * + * RETURN VALUE: n > 0 : the number of delivered events. + * n == 0 : the event was not passed to any client. + * n < 0 : error - event was not processed. + */ +int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop) +{ + struct snd_seq_client *client; + int result; + + if (snd_BUG_ON(!cell)) + return -EINVAL; + + client = snd_seq_client_use_ptr(cell->event.source.client); + if (client == NULL) { + snd_seq_cell_free(cell); /* release this cell */ + return -EINVAL; + } + + if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) { + /* NOTE event: + * the event cell is re-used as a NOTE-OFF event and + * enqueued again. + */ + struct snd_seq_event tmpev, *ev; + + /* reserve this event to enqueue note-off later */ + tmpev = cell->event; + tmpev.type = SNDRV_SEQ_EVENT_NOTEON; + result = snd_seq_deliver_event(client, &tmpev, atomic, hop); + + /* + * This was originally a note event. We now re-use the + * cell for the note-off event. + */ + + ev = &cell->event; + ev->type = SNDRV_SEQ_EVENT_NOTEOFF; + ev->flags |= SNDRV_SEQ_PRIORITY_HIGH; + + /* add the duration time */ + switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + ev->time.tick += ev->data.note.duration; + break; + case SNDRV_SEQ_TIME_STAMP_REAL: + /* unit for duration is ms */ + ev->time.time.tv_nsec += 1000000 * (ev->data.note.duration % 1000); + ev->time.time.tv_sec += ev->data.note.duration / 1000 + + ev->time.time.tv_nsec / 1000000000; + ev->time.time.tv_nsec %= 1000000000; + break; + } + ev->data.note.velocity = ev->data.note.off_velocity; + + /* Now queue this cell as the note off event */ + if (snd_seq_enqueue_event(cell, atomic, hop) < 0) + snd_seq_cell_free(cell); /* release this cell */ + + } else { + /* Normal events: + * event cell is freed after processing the event + */ + + result = snd_seq_deliver_event(client, &cell->event, atomic, hop); + snd_seq_cell_free(cell); + } + + snd_seq_client_unlock(client); + return result; +} + + +/* Allocate a cell from client pool and enqueue it to queue: + * if pool is empty and blocking is TRUE, sleep until a new cell is + * available. + */ +static int snd_seq_client_enqueue_event(struct snd_seq_client *client, + struct snd_seq_event *event, + struct file *file, int blocking, + int atomic, int hop, + struct mutex *mutexp) +{ + struct snd_seq_event_cell *cell; + int err; + + /* special queue values - force direct passing */ + if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + event->queue = SNDRV_SEQ_QUEUE_DIRECT; + } else +#ifdef SUPPORT_BROADCAST + if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) { + event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST; + event->queue = SNDRV_SEQ_QUEUE_DIRECT; + } +#endif + if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + /* check presence of source port */ + struct snd_seq_client_port *src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port == NULL) + return -EINVAL; + snd_seq_port_unlock(src_port); + } + + /* direct event processing without enqueued */ + if (snd_seq_ev_is_direct(event)) { + if (event->type == SNDRV_SEQ_EVENT_NOTE) + return -EINVAL; /* this event must be enqueued! */ + return snd_seq_deliver_event(client, event, atomic, hop); + } + + /* Not direct, normal queuing */ + if (snd_seq_queue_is_used(event->queue, client->number) <= 0) + return -EINVAL; /* invalid queue */ + if (! snd_seq_write_pool_allocated(client)) + return -ENXIO; /* queue is not allocated */ + + /* allocate an event cell */ + err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic, + file, mutexp); + if (err < 0) + return err; + + /* we got a cell. enqueue it. */ + if ((err = snd_seq_enqueue_event(cell, atomic, hop)) < 0) { + snd_seq_cell_free(cell); + return err; + } + + return 0; +} + + +/* + * check validity of event type and data length. + * return non-zero if invalid. + */ +static int check_event_type_and_length(struct snd_seq_event *ev) +{ + switch (snd_seq_ev_length_type(ev)) { + case SNDRV_SEQ_EVENT_LENGTH_FIXED: + if (snd_seq_ev_is_variable_type(ev)) + return -EINVAL; + break; + case SNDRV_SEQ_EVENT_LENGTH_VARIABLE: + if (! snd_seq_ev_is_variable_type(ev) || + (ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK) >= SNDRV_SEQ_MAX_EVENT_LEN) + return -EINVAL; + break; + case SNDRV_SEQ_EVENT_LENGTH_VARUSR: + if (! snd_seq_ev_is_direct(ev)) + return -EINVAL; + break; + } + return 0; +} + + +/* handle write() */ +/* possible error values: + * -ENXIO invalid client or file open mode + * -ENOMEM malloc failed + * -EFAULT seg. fault during copy from user space + * -EINVAL invalid event + * -EAGAIN no space in output pool + * -EINTR interrupts while sleep + * -EMLINK too many hops + * others depends on return value from driver callback + */ +static ssize_t snd_seq_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_seq_client *client = file->private_data; + int written = 0, len; + int err, handled; + struct snd_seq_event event; + + if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT)) + return -ENXIO; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return -ENXIO; + + if (!client->accept_output || client->pool == NULL) + return -ENXIO; + + repeat: + handled = 0; + /* allocate the pool now if the pool is not allocated yet */ + mutex_lock(&client->ioctl_mutex); + if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) { + err = snd_seq_pool_init(client->pool); + if (err < 0) + goto out; + } + + /* only process whole events */ + err = -EINVAL; + while (count >= sizeof(struct snd_seq_event)) { + /* Read in the event header from the user */ + len = sizeof(event); + if (copy_from_user(&event, buf, len)) { + err = -EFAULT; + break; + } + event.source.client = client->number; /* fill in client number */ + /* Check for extension data length */ + if (check_event_type_and_length(&event)) { + err = -EINVAL; + break; + } + + /* check for special events */ + if (event.type == SNDRV_SEQ_EVENT_NONE) + goto __skip_event; + else if (snd_seq_ev_is_reserved(&event)) { + err = -EINVAL; + break; + } + + if (snd_seq_ev_is_variable(&event)) { + int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK; + if ((size_t)(extlen + len) > count) { + /* back out, will get an error this time or next */ + err = -EINVAL; + break; + } + /* set user space pointer */ + event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; + event.data.ext.ptr = (char __force *)buf + + sizeof(struct snd_seq_event); + len += extlen; /* increment data length */ + } else { +#ifdef CONFIG_COMPAT + if (client->convert32 && snd_seq_ev_is_varusr(&event)) { + void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]); + event.data.ext.ptr = ptr; + } +#endif + } + + /* ok, enqueue it */ + err = snd_seq_client_enqueue_event(client, &event, file, + !(file->f_flags & O_NONBLOCK), + 0, 0, &client->ioctl_mutex); + if (err < 0) + break; + handled++; + + __skip_event: + /* Update pointers and counts */ + count -= len; + buf += len; + written += len; + + /* let's have a coffee break if too many events are queued */ + if (++handled >= 200) { + mutex_unlock(&client->ioctl_mutex); + goto repeat; + } + } + + out: + mutex_unlock(&client->ioctl_mutex); + return written ? written : err; +} + + +/* + * handle polling + */ +static __poll_t snd_seq_poll(struct file *file, poll_table * wait) +{ + struct snd_seq_client *client = file->private_data; + __poll_t mask = 0; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return EPOLLERR; + + if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) && + client->data.user.fifo) { + + /* check if data is available in the outqueue */ + if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait)) + mask |= EPOLLIN | EPOLLRDNORM; + } + + if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) { + + /* check if data is available in the pool */ + if (!snd_seq_write_pool_allocated(client) || + snd_seq_pool_poll_wait(client->pool, file, wait)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + + return mask; +} + + +/*-----------------------------------------------------*/ + +static int snd_seq_ioctl_pversion(struct snd_seq_client *client, void *arg) +{ + int *pversion = arg; + + *pversion = SNDRV_SEQ_VERSION; + return 0; +} + +static int snd_seq_ioctl_client_id(struct snd_seq_client *client, void *arg) +{ + int *client_id = arg; + + *client_id = client->number; + return 0; +} + +/* SYSTEM_INFO ioctl() */ +static int snd_seq_ioctl_system_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_system_info *info = arg; + + memset(info, 0, sizeof(*info)); + /* fill the info fields */ + info->queues = SNDRV_SEQ_MAX_QUEUES; + info->clients = SNDRV_SEQ_MAX_CLIENTS; + info->ports = SNDRV_SEQ_MAX_PORTS; + info->channels = 256; /* fixed limit */ + info->cur_clients = client_usage.cur; + info->cur_queues = snd_seq_queue_get_cur_queues(); + + return 0; +} + + +/* RUNNING_MODE ioctl() */ +static int snd_seq_ioctl_running_mode(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_running_info *info = arg; + struct snd_seq_client *cptr; + int err = 0; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(info->client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + +#ifdef SNDRV_BIG_ENDIAN + if (!info->big_endian) { + err = -EINVAL; + goto __err; + } +#else + if (info->big_endian) { + err = -EINVAL; + goto __err; + } + +#endif + if (info->cpu_mode > sizeof(long)) { + err = -EINVAL; + goto __err; + } + cptr->convert32 = (info->cpu_mode < sizeof(long)); + __err: + snd_seq_client_unlock(cptr); + return err; +} + +/* CLIENT_INFO ioctl() */ +static void get_client_info(struct snd_seq_client *cptr, + struct snd_seq_client_info *info) +{ + info->client = cptr->number; + + /* fill the info fields */ + info->type = cptr->type; + strcpy(info->name, cptr->name); + info->filter = cptr->filter; + info->event_lost = cptr->event_lost; + memcpy(info->event_filter, cptr->event_filter, 32); + info->num_ports = cptr->num_ports; + + if (cptr->type == USER_CLIENT) + info->pid = pid_vnr(cptr->data.user.owner); + else + info->pid = -1; + + if (cptr->type == KERNEL_CLIENT) + info->card = cptr->data.kernel.card ? cptr->data.kernel.card->number : -1; + else + info->card = -1; + + memset(info->reserved, 0, sizeof(info->reserved)); +} + +static int snd_seq_ioctl_get_client_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *client_info = arg; + struct snd_seq_client *cptr; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(client_info->client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + + get_client_info(cptr, client_info); + snd_seq_client_unlock(cptr); + + return 0; +} + + +/* CLIENT_INFO ioctl() */ +static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *client_info = arg; + + /* it is not allowed to set the info fields for an another client */ + if (client->number != client_info->client) + return -EPERM; + /* also client type must be set now */ + if (client->type != client_info->type) + return -EINVAL; + + /* fill the info fields */ + if (client_info->name[0]) + strscpy(client->name, client_info->name, sizeof(client->name)); + + client->filter = client_info->filter; + client->event_lost = client_info->event_lost; + memcpy(client->event_filter, client_info->event_filter, 32); + + return 0; +} + + +/* + * CREATE PORT ioctl() + */ +static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client_port *port; + struct snd_seq_port_callback *callback; + int port_idx; + + /* it is not allowed to create the port for an another client */ + if (info->addr.client != client->number) + return -EPERM; + + port = snd_seq_create_port(client, (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info->addr.port : -1); + if (port == NULL) + return -ENOMEM; + + if (client->type == USER_CLIENT && info->kernel) { + port_idx = port->addr.port; + snd_seq_port_unlock(port); + snd_seq_delete_port(client, port_idx); + return -EINVAL; + } + if (client->type == KERNEL_CLIENT) { + if ((callback = info->kernel) != NULL) { + if (callback->owner) + port->owner = callback->owner; + port->private_data = callback->private_data; + port->private_free = callback->private_free; + port->event_input = callback->event_input; + port->c_src.open = callback->subscribe; + port->c_src.close = callback->unsubscribe; + port->c_dest.open = callback->use; + port->c_dest.close = callback->unuse; + } + } + + info->addr = port->addr; + + snd_seq_set_port_info(port, info); + snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port); + snd_seq_port_unlock(port); + + return 0; +} + +/* + * DELETE PORT ioctl() + */ +static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + int err; + + /* it is not allowed to remove the port for an another client */ + if (info->addr.client != client->number) + return -EPERM; + + err = snd_seq_delete_port(client, info->addr.port); + if (err >= 0) + snd_seq_system_client_ev_port_exit(client->number, info->addr.port); + return err; +} + + +/* + * GET_PORT_INFO ioctl() (on any client) + */ +static int snd_seq_ioctl_get_port_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client *cptr; + struct snd_seq_client_port *port; + + cptr = snd_seq_client_use_ptr(info->addr.client); + if (cptr == NULL) + return -ENXIO; + + port = snd_seq_port_use_ptr(cptr, info->addr.port); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; /* don't change */ + } + + /* get port info */ + snd_seq_get_port_info(port, info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + return 0; +} + + +/* + * SET_PORT_INFO ioctl() (only ports on this/own client) + */ +static int snd_seq_ioctl_set_port_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client_port *port; + + if (info->addr.client != client->number) /* only set our own ports ! */ + return -EPERM; + port = snd_seq_port_use_ptr(client, info->addr.port); + if (port) { + snd_seq_set_port_info(port, info); + snd_seq_port_unlock(port); + } + return 0; +} + + +/* + * port subscription (connection) + */ +#define PERM_RD (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ) +#define PERM_WR (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE) + +static int check_subscription_permission(struct snd_seq_client *client, + struct snd_seq_client_port *sport, + struct snd_seq_client_port *dport, + struct snd_seq_port_subscribe *subs) +{ + if (client->number != subs->sender.client && + client->number != subs->dest.client) { + /* connection by third client - check export permission */ + if (check_port_perm(sport, SNDRV_SEQ_PORT_CAP_NO_EXPORT)) + return -EPERM; + if (check_port_perm(dport, SNDRV_SEQ_PORT_CAP_NO_EXPORT)) + return -EPERM; + } + + /* check read permission */ + /* if sender or receiver is the subscribing client itself, + * no permission check is necessary + */ + if (client->number != subs->sender.client) { + if (! check_port_perm(sport, PERM_RD)) + return -EPERM; + } + /* check write permission */ + if (client->number != subs->dest.client) { + if (! check_port_perm(dport, PERM_WR)) + return -EPERM; + } + return 0; +} + +/* + * send an subscription notify event to user client: + * client must be user client. + */ +int snd_seq_client_notify_subscription(int client, int port, + struct snd_seq_port_subscribe *info, + int evtype) +{ + struct snd_seq_event event; + + memset(&event, 0, sizeof(event)); + event.type = evtype; + event.data.connect.dest = info->dest; + event.data.connect.sender = info->sender; + + return snd_seq_system_notify(client, port, &event); /* non-atomic */ +} + + +/* + * add to port's subscription list IOCTL interface + */ +static int snd_seq_ioctl_subscribe_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result = -EINVAL; + struct snd_seq_client *receiver = NULL, *sender = NULL; + struct snd_seq_client_port *sport = NULL, *dport = NULL; + + if ((receiver = snd_seq_client_use_ptr(subs->dest.client)) == NULL) + goto __end; + if ((sender = snd_seq_client_use_ptr(subs->sender.client)) == NULL) + goto __end; + if ((sport = snd_seq_port_use_ptr(sender, subs->sender.port)) == NULL) + goto __end; + if ((dport = snd_seq_port_use_ptr(receiver, subs->dest.port)) == NULL) + goto __end; + + result = check_subscription_permission(client, sport, dport, subs); + if (result < 0) + goto __end; + + /* connect them */ + result = snd_seq_port_connect(client, sender, sport, receiver, dport, subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (dport) + snd_seq_port_unlock(dport); + if (sender) + snd_seq_client_unlock(sender); + if (receiver) + snd_seq_client_unlock(receiver); + return result; +} + + +/* + * remove from port's subscription list + */ +static int snd_seq_ioctl_unsubscribe_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result = -ENXIO; + struct snd_seq_client *receiver = NULL, *sender = NULL; + struct snd_seq_client_port *sport = NULL, *dport = NULL; + + if ((receiver = snd_seq_client_use_ptr(subs->dest.client)) == NULL) + goto __end; + if ((sender = snd_seq_client_use_ptr(subs->sender.client)) == NULL) + goto __end; + if ((sport = snd_seq_port_use_ptr(sender, subs->sender.port)) == NULL) + goto __end; + if ((dport = snd_seq_port_use_ptr(receiver, subs->dest.port)) == NULL) + goto __end; + + result = check_subscription_permission(client, sport, dport, subs); + if (result < 0) + goto __end; + + result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (dport) + snd_seq_port_unlock(dport); + if (sender) + snd_seq_client_unlock(sender); + if (receiver) + snd_seq_client_unlock(receiver); + return result; +} + + +/* CREATE_QUEUE ioctl() */ +static int snd_seq_ioctl_create_queue(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = snd_seq_queue_alloc(client->number, info->locked, info->flags); + if (IS_ERR(q)) + return PTR_ERR(q); + + info->queue = q->queue; + info->locked = q->locked; + info->owner = q->owner; + + /* set queue name */ + if (!info->name[0]) + snprintf(info->name, sizeof(info->name), "Queue-%d", q->queue); + strscpy(q->name, info->name, sizeof(q->name)); + snd_use_lock_free(&q->use_lock); + + return 0; +} + +/* DELETE_QUEUE ioctl() */ +static int snd_seq_ioctl_delete_queue(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_queue_info *info = arg; + + return snd_seq_queue_delete(client->number, info->queue); +} + +/* GET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_get_queue_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = queueptr(info->queue); + if (q == NULL) + return -EINVAL; + + memset(info, 0, sizeof(*info)); + info->queue = q->queue; + info->owner = q->owner; + info->locked = q->locked; + strlcpy(info->name, q->name, sizeof(info->name)); + queuefree(q); + + return 0; +} + +/* SET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_set_queue_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + if (info->owner != client->number) + return -EINVAL; + + /* change owner/locked permission */ + if (snd_seq_queue_check_access(info->queue, client->number)) { + if (snd_seq_queue_set_owner(info->queue, client->number, info->locked) < 0) + return -EPERM; + if (info->locked) + snd_seq_queue_use(info->queue, client->number, 1); + } else { + return -EPERM; + } + + q = queueptr(info->queue); + if (! q) + return -EINVAL; + if (q->owner != client->number) { + queuefree(q); + return -EPERM; + } + strscpy(q->name, info->name, sizeof(q->name)); + queuefree(q); + + return 0; +} + +/* GET_NAMED_QUEUE ioctl() */ +static int snd_seq_ioctl_get_named_queue(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = snd_seq_queue_find_name(info->name); + if (q == NULL) + return -EINVAL; + info->queue = q->queue; + info->owner = q->owner; + info->locked = q->locked; + queuefree(q); + + return 0; +} + +/* GET_QUEUE_STATUS ioctl() */ +static int snd_seq_ioctl_get_queue_status(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_status *status = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(status->queue); + if (queue == NULL) + return -EINVAL; + memset(status, 0, sizeof(*status)); + status->queue = queue->queue; + + tmr = queue->timer; + status->events = queue->tickq->cells + queue->timeq->cells; + + status->time = snd_seq_timer_get_cur_time(tmr, true); + status->tick = snd_seq_timer_get_cur_tick(tmr); + + status->running = tmr->running; + + status->flags = queue->flags; + queuefree(queue); + + return 0; +} + + +/* GET_QUEUE_TEMPO ioctl() */ +static int snd_seq_ioctl_get_queue_tempo(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_tempo *tempo = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(tempo->queue); + if (queue == NULL) + return -EINVAL; + memset(tempo, 0, sizeof(*tempo)); + tempo->queue = queue->queue; + + tmr = queue->timer; + + tempo->tempo = tmr->tempo; + tempo->ppq = tmr->ppq; + tempo->skew_value = tmr->skew; + tempo->skew_base = tmr->skew_base; + queuefree(queue); + + return 0; +} + + +/* SET_QUEUE_TEMPO ioctl() */ +int snd_seq_set_queue_tempo(int client, struct snd_seq_queue_tempo *tempo) +{ + if (!snd_seq_queue_check_access(tempo->queue, client)) + return -EPERM; + return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo); +} +EXPORT_SYMBOL(snd_seq_set_queue_tempo); + +static int snd_seq_ioctl_set_queue_tempo(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_tempo *tempo = arg; + int result; + + result = snd_seq_set_queue_tempo(client->number, tempo); + return result < 0 ? result : 0; +} + + +/* GET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_get_queue_timer(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_timer *timer = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(timer->queue); + if (queue == NULL) + return -EINVAL; + + mutex_lock(&queue->timer_mutex); + tmr = queue->timer; + memset(timer, 0, sizeof(*timer)); + timer->queue = queue->queue; + + timer->type = tmr->type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + timer->u.alsa.id = tmr->alsa_id; + timer->u.alsa.resolution = tmr->preferred_resolution; + } + mutex_unlock(&queue->timer_mutex); + queuefree(queue); + + return 0; +} + + +/* SET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_set_queue_timer(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_timer *timer = arg; + int result = 0; + + if (timer->type != SNDRV_SEQ_TIMER_ALSA) + return -EINVAL; + + if (snd_seq_queue_check_access(timer->queue, client->number)) { + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + + q = queueptr(timer->queue); + if (q == NULL) + return -ENXIO; + mutex_lock(&q->timer_mutex); + tmr = q->timer; + snd_seq_queue_timer_close(timer->queue); + tmr->type = timer->type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + tmr->alsa_id = timer->u.alsa.id; + tmr->preferred_resolution = timer->u.alsa.resolution; + } + result = snd_seq_queue_timer_open(timer->queue); + mutex_unlock(&q->timer_mutex); + queuefree(q); + } else { + return -EPERM; + } + + return result; +} + + +/* GET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_get_queue_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_client *info = arg; + int used; + + used = snd_seq_queue_is_used(info->queue, client->number); + if (used < 0) + return -EINVAL; + info->used = used; + info->client = client->number; + + return 0; +} + + +/* SET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_set_queue_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_client *info = arg; + int err; + + if (info->used >= 0) { + err = snd_seq_queue_use(info->queue, client->number, info->used); + if (err < 0) + return err; + } + + return snd_seq_ioctl_get_queue_client(client, arg); +} + + +/* GET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_get_client_pool(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_pool *info = arg; + struct snd_seq_client *cptr; + + cptr = snd_seq_client_use_ptr(info->client); + if (cptr == NULL) + return -ENOENT; + memset(info, 0, sizeof(*info)); + info->client = cptr->number; + info->output_pool = cptr->pool->size; + info->output_room = cptr->pool->room; + info->output_free = info->output_pool; + info->output_free = snd_seq_unused_cells(cptr->pool); + if (cptr->type == USER_CLIENT) { + info->input_pool = cptr->data.user.fifo_pool_size; + info->input_free = info->input_pool; + info->input_free = snd_seq_fifo_unused_cells(cptr->data.user.fifo); + } else { + info->input_pool = 0; + info->input_free = 0; + } + snd_seq_client_unlock(cptr); + + return 0; +} + +/* SET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_set_client_pool(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_pool *info = arg; + int rc; + + if (client->number != info->client) + return -EINVAL; /* can't change other clients */ + + if (info->output_pool >= 1 && info->output_pool <= SNDRV_SEQ_MAX_EVENTS && + (! snd_seq_write_pool_allocated(client) || + info->output_pool != client->pool->size)) { + if (snd_seq_write_pool_allocated(client)) { + /* is the pool in use? */ + if (atomic_read(&client->pool->counter)) + return -EBUSY; + /* remove all existing cells */ + snd_seq_pool_mark_closing(client->pool); + snd_seq_pool_done(client->pool); + } + client->pool->size = info->output_pool; + rc = snd_seq_pool_init(client->pool); + if (rc < 0) + return rc; + } + if (client->type == USER_CLIENT && client->data.user.fifo != NULL && + info->input_pool >= 1 && + info->input_pool <= SNDRV_SEQ_MAX_CLIENT_EVENTS && + info->input_pool != client->data.user.fifo_pool_size) { + /* change pool size */ + rc = snd_seq_fifo_resize(client->data.user.fifo, info->input_pool); + if (rc < 0) + return rc; + client->data.user.fifo_pool_size = info->input_pool; + } + if (info->output_room >= 1 && + info->output_room <= client->pool->size) { + client->pool->room = info->output_room; + } + + return snd_seq_ioctl_get_client_pool(client, arg); +} + + +/* REMOVE_EVENTS ioctl() */ +static int snd_seq_ioctl_remove_events(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_remove_events *info = arg; + + /* + * Input mostly not implemented XXX. + */ + if (info->remove_mode & SNDRV_SEQ_REMOVE_INPUT) { + /* + * No restrictions so for a user client we can clear + * the whole fifo + */ + if (client->type == USER_CLIENT && client->data.user.fifo) + snd_seq_fifo_clear(client->data.user.fifo); + } + + if (info->remove_mode & SNDRV_SEQ_REMOVE_OUTPUT) + snd_seq_queue_remove_cells(client->number, info); + + return 0; +} + + +/* + * get subscription info + */ +static int snd_seq_ioctl_get_subscription(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result; + struct snd_seq_client *sender = NULL; + struct snd_seq_client_port *sport = NULL; + + result = -EINVAL; + if ((sender = snd_seq_client_use_ptr(subs->sender.client)) == NULL) + goto __end; + if ((sport = snd_seq_port_use_ptr(sender, subs->sender.port)) == NULL) + goto __end; + result = snd_seq_port_get_subscription(&sport->c_src, &subs->dest, + subs); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (sender) + snd_seq_client_unlock(sender); + + return result; +} + + +/* + * get subscription info - check only its presence + */ +static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_query_subs *subs = arg; + int result = -ENXIO; + struct snd_seq_client *cptr = NULL; + struct snd_seq_client_port *port = NULL; + struct snd_seq_port_subs_info *group; + struct list_head *p; + int i; + + if ((cptr = snd_seq_client_use_ptr(subs->root.client)) == NULL) + goto __end; + if ((port = snd_seq_port_use_ptr(cptr, subs->root.port)) == NULL) + goto __end; + + switch (subs->type) { + case SNDRV_SEQ_QUERY_SUBS_READ: + group = &port->c_src; + break; + case SNDRV_SEQ_QUERY_SUBS_WRITE: + group = &port->c_dest; + break; + default: + goto __end; + } + + down_read(&group->list_mutex); + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; + result = -ENOENT; + list_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { + s = list_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { + s = list_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; + subs->queue = s->info.queue; + result = 0; + break; + } + } + up_read(&group->list_mutex); + + __end: + if (port) + snd_seq_port_unlock(port); + if (cptr) + snd_seq_client_unlock(cptr); + + return result; +} + + +/* + * query next client + */ +static int snd_seq_ioctl_query_next_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *info = arg; + struct snd_seq_client *cptr = NULL; + + /* search for next client */ + if (info->client < INT_MAX) + info->client++; + if (info->client < 0) + info->client = 0; + for (; info->client < SNDRV_SEQ_MAX_CLIENTS; info->client++) { + cptr = snd_seq_client_use_ptr(info->client); + if (cptr) + break; /* found */ + } + if (cptr == NULL) + return -ENOENT; + + get_client_info(cptr, info); + snd_seq_client_unlock(cptr); + + return 0; +} + +/* + * query next port + */ +static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client *cptr; + struct snd_seq_client_port *port = NULL; + + cptr = snd_seq_client_use_ptr(info->addr.client); + if (cptr == NULL) + return -ENXIO; + + /* search for next port */ + info->addr.port++; + port = snd_seq_port_query_nearest(cptr, info); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; + } + + /* get port info */ + info->addr = port->addr; + snd_seq_get_port_info(port, info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + return 0; +} + +/* -------------------------------------------------------- */ + +static const struct ioctl_handler { + unsigned int cmd; + int (*func)(struct snd_seq_client *client, void *arg); +} ioctl_handlers[] = { + { SNDRV_SEQ_IOCTL_PVERSION, snd_seq_ioctl_pversion }, + { SNDRV_SEQ_IOCTL_CLIENT_ID, snd_seq_ioctl_client_id }, + { SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info }, + { SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, snd_seq_ioctl_get_client_info }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, snd_seq_ioctl_set_client_info }, + { SNDRV_SEQ_IOCTL_CREATE_PORT, snd_seq_ioctl_create_port }, + { SNDRV_SEQ_IOCTL_DELETE_PORT, snd_seq_ioctl_delete_port }, + { SNDRV_SEQ_IOCTL_GET_PORT_INFO, snd_seq_ioctl_get_port_info }, + { SNDRV_SEQ_IOCTL_SET_PORT_INFO, snd_seq_ioctl_set_port_info }, + { SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, snd_seq_ioctl_subscribe_port }, + { SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, snd_seq_ioctl_unsubscribe_port }, + { SNDRV_SEQ_IOCTL_CREATE_QUEUE, snd_seq_ioctl_create_queue }, + { SNDRV_SEQ_IOCTL_DELETE_QUEUE, snd_seq_ioctl_delete_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, snd_seq_ioctl_get_queue_info }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, snd_seq_ioctl_set_queue_info }, + { SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, snd_seq_ioctl_get_named_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, snd_seq_ioctl_get_queue_status }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, snd_seq_ioctl_get_queue_tempo }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, snd_seq_ioctl_set_queue_tempo }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, snd_seq_ioctl_get_queue_timer }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, snd_seq_ioctl_set_queue_timer }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, snd_seq_ioctl_get_queue_client }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, snd_seq_ioctl_set_queue_client }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, snd_seq_ioctl_get_client_pool }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, snd_seq_ioctl_set_client_pool }, + { SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, snd_seq_ioctl_get_subscription }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, snd_seq_ioctl_query_next_client }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, snd_seq_ioctl_query_next_port }, + { SNDRV_SEQ_IOCTL_REMOVE_EVENTS, snd_seq_ioctl_remove_events }, + { SNDRV_SEQ_IOCTL_QUERY_SUBS, snd_seq_ioctl_query_subs }, + { 0, NULL }, +}; + +static long snd_seq_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_seq_client *client = file->private_data; + /* To use kernel stack for ioctl data. */ + union { + int pversion; + int client_id; + struct snd_seq_system_info system_info; + struct snd_seq_running_info running_info; + struct snd_seq_client_info client_info; + struct snd_seq_port_info port_info; + struct snd_seq_port_subscribe port_subscribe; + struct snd_seq_queue_info queue_info; + struct snd_seq_queue_status queue_status; + struct snd_seq_queue_tempo tempo; + struct snd_seq_queue_timer queue_timer; + struct snd_seq_queue_client queue_client; + struct snd_seq_client_pool client_pool; + struct snd_seq_remove_events remove_events; + struct snd_seq_query_subs query_subs; + } buf; + const struct ioctl_handler *handler; + unsigned long size; + int err; + + if (snd_BUG_ON(!client)) + return -ENXIO; + + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { + if (handler->cmd == cmd) + break; + } + if (handler->cmd == 0) + return -ENOTTY; + + memset(&buf, 0, sizeof(buf)); + + /* + * All of ioctl commands for ALSA sequencer get an argument of size + * within 13 bits. We can safely pick up the size from the command. + */ + size = _IOC_SIZE(handler->cmd); + if (handler->cmd & IOC_IN) { + if (copy_from_user(&buf, (const void __user *)arg, size)) + return -EFAULT; + } + + mutex_lock(&client->ioctl_mutex); + err = handler->func(client, &buf); + mutex_unlock(&client->ioctl_mutex); + if (err >= 0) { + /* Some commands includes a bug in 'dir' field. */ + if (handler->cmd == SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT || + handler->cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_POOL || + (handler->cmd & IOC_OUT)) + if (copy_to_user((void __user *)arg, &buf, size)) + return -EFAULT; + } + + return err; +} + +#ifdef CONFIG_COMPAT +#include "seq_compat.c" +#else +#define snd_seq_ioctl_compat NULL +#endif + +/* -------------------------------------------------------- */ + + +/* exported to kernel modules */ +int snd_seq_create_kernel_client(struct snd_card *card, int client_index, + const char *name_fmt, ...) +{ + struct snd_seq_client *client; + va_list args; + + if (snd_BUG_ON(in_interrupt())) + return -EBUSY; + + if (card && client_index >= SNDRV_SEQ_CLIENTS_PER_CARD) + return -EINVAL; + if (card == NULL && client_index >= SNDRV_SEQ_GLOBAL_CLIENTS) + return -EINVAL; + + mutex_lock(®ister_mutex); + + if (card) { + client_index += SNDRV_SEQ_GLOBAL_CLIENTS + + card->number * SNDRV_SEQ_CLIENTS_PER_CARD; + if (client_index >= SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) + client_index = -1; + } + + /* empty write queue as default */ + client = seq_create_client1(client_index, 0); + if (client == NULL) { + mutex_unlock(®ister_mutex); + return -EBUSY; /* failure code */ + } + usage_alloc(&client_usage, 1); + + client->accept_input = 1; + client->accept_output = 1; + client->data.kernel.card = card; + + va_start(args, name_fmt); + vsnprintf(client->name, sizeof(client->name), name_fmt, args); + va_end(args); + + client->type = KERNEL_CLIENT; + mutex_unlock(®ister_mutex); + + /* make others aware this new client */ + snd_seq_system_client_ev_client_start(client->number); + + /* return client number to caller */ + return client->number; +} +EXPORT_SYMBOL(snd_seq_create_kernel_client); + +/* exported to kernel modules */ +int snd_seq_delete_kernel_client(int client) +{ + struct snd_seq_client *ptr; + + if (snd_BUG_ON(in_interrupt())) + return -EBUSY; + + ptr = clientptr(client); + if (ptr == NULL) + return -EINVAL; + + seq_free_client(ptr); + kfree(ptr); + return 0; +} +EXPORT_SYMBOL(snd_seq_delete_kernel_client); + +/* skeleton to enqueue event, called from snd_seq_kernel_client_enqueue + * and snd_seq_kernel_client_enqueue_blocking + */ +static int kernel_client_enqueue(int client, struct snd_seq_event *ev, + struct file *file, int blocking, + int atomic, int hop) +{ + struct snd_seq_client *cptr; + int result; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* ignore this */ + if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return -EINVAL; /* quoted events can't be enqueued */ + + /* fill in client number */ + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (! cptr->accept_output) + result = -EPERM; + else /* send it */ + result = snd_seq_client_enqueue_event(cptr, ev, file, blocking, + atomic, hop, NULL); + + snd_seq_client_unlock(cptr); + return result; +} + +/* + * exported, called by kernel clients to enqueue events (w/o blocking) + * + * RETURN VALUE: zero if succeed, negative if error + */ +int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event * ev, + int atomic, int hop) +{ + return kernel_client_enqueue(client, ev, NULL, 0, atomic, hop); +} +EXPORT_SYMBOL(snd_seq_kernel_client_enqueue); + +/* + * exported, called by kernel clients to enqueue events (with blocking) + * + * RETURN VALUE: zero if succeed, negative if error + */ +int snd_seq_kernel_client_enqueue_blocking(int client, struct snd_seq_event * ev, + struct file *file, + int atomic, int hop) +{ + return kernel_client_enqueue(client, ev, file, 1, atomic, hop); +} +EXPORT_SYMBOL(snd_seq_kernel_client_enqueue_blocking); + +/* + * exported, called by kernel clients to dispatch events directly to other + * clients, bypassing the queues. Event time-stamp will be updated. + * + * RETURN VALUE: negative = delivery failed, + * zero, or positive: the number of delivered events + */ +int snd_seq_kernel_client_dispatch(int client, struct snd_seq_event * ev, + int atomic, int hop) +{ + struct snd_seq_client *cptr; + int result; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + + /* fill in client number */ + ev->queue = SNDRV_SEQ_QUEUE_DIRECT; + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (!cptr->accept_output) + result = -EPERM; + else + result = snd_seq_deliver_event(cptr, ev, atomic, hop); + + snd_seq_client_unlock(cptr); + return result; +} +EXPORT_SYMBOL(snd_seq_kernel_client_dispatch); + +/** + * snd_seq_kernel_client_ctl - operate a command for a client with data in + * kernel space. + * @clientid: A numerical ID for a client. + * @cmd: An ioctl(2) command for ALSA sequencer operation. + * @arg: A pointer to data in kernel space. + * + * Against its name, both kernel/application client can be handled by this + * kernel API. A pointer of 'arg' argument should be in kernel space. + * + * Return: 0 at success. Negative error code at failure. + */ +int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg) +{ + const struct ioctl_handler *handler; + struct snd_seq_client *client; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { + if (handler->cmd == cmd) + return handler->func(client, arg); + } + + pr_debug("ALSA: seq unknown ioctl() 0x%x (type='%c', number=0x%02x)\n", + cmd, _IOC_TYPE(cmd), _IOC_NR(cmd)); + return -ENOTTY; +} +EXPORT_SYMBOL(snd_seq_kernel_client_ctl); + +/* exported (for OSS emulator) */ +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait) +{ + struct snd_seq_client *client; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + + if (! snd_seq_write_pool_allocated(client)) + return 1; + if (snd_seq_pool_poll_wait(client->pool, file, wait)) + return 1; + return 0; +} +EXPORT_SYMBOL(snd_seq_kernel_client_write_poll); + +/*---------------------------------------------------------------------------*/ + +#ifdef CONFIG_SND_PROC_FS +/* + * /proc interface + */ +static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) +{ + struct list_head *p; + struct snd_seq_subscribers *s; + int count = 0; + + down_read(&group->list_mutex); + if (list_empty(&group->list_head)) { + up_read(&group->list_mutex); + return; + } + snd_iprintf(buffer, msg); + list_for_each(p, &group->list_head) { + if (is_src) + s = list_entry(p, struct snd_seq_subscribers, src_list); + else + s = list_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", + is_src ? s->info.dest.client : s->info.sender.client, + is_src ? s->info.dest.port : s->info.sender.port); + if (s->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) + snd_iprintf(buffer, "[%c:%d]", ((s->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 'r' : 't'), s->info.queue); + if (group->exclusive) + snd_iprintf(buffer, "[ex]"); + } + up_read(&group->list_mutex); + snd_iprintf(buffer, "\n"); +} + +#define FLAG_PERM_RD(perm) ((perm) & SNDRV_SEQ_PORT_CAP_READ ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_READ ? 'R' : 'r') : '-') +#define FLAG_PERM_WR(perm) ((perm) & SNDRV_SEQ_PORT_CAP_WRITE ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_WRITE ? 'W' : 'w') : '-') +#define FLAG_PERM_EX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_NO_EXPORT ? '-' : 'e') + +#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-') + +static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, + struct snd_seq_client *client) +{ + struct snd_seq_client_port *p; + + mutex_lock(&client->ports_mutex); + list_for_each_entry(p, &client->ports_list_head, list) { + snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n", + p->addr.port, p->name, + FLAG_PERM_RD(p->capability), + FLAG_PERM_WR(p->capability), + FLAG_PERM_EX(p->capability), + FLAG_PERM_DUPLEX(p->capability)); + snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: "); + snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: "); + } + mutex_unlock(&client->ports_mutex); +} + + +/* exported to seq_info.c */ +void snd_seq_info_clients_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int c; + struct snd_seq_client *client; + + snd_iprintf(buffer, "Client info\n"); + snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur); + snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak); + snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS); + snd_iprintf(buffer, "\n"); + + /* list the client table */ + for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) { + client = snd_seq_client_use_ptr(c); + if (client == NULL) + continue; + if (client->type == NO_CLIENT) { + snd_seq_client_unlock(client); + continue; + } + + snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n", + c, client->name, + client->type == USER_CLIENT ? "User" : "Kernel"); + snd_seq_info_dump_ports(buffer, client); + if (snd_seq_write_pool_allocated(client)) { + snd_iprintf(buffer, " Output pool :\n"); + snd_seq_info_pool(buffer, client->pool, " "); + } + if (client->type == USER_CLIENT && client->data.user.fifo && + client->data.user.fifo->pool) { + snd_iprintf(buffer, " Input pool :\n"); + snd_seq_info_pool(buffer, client->data.user.fifo->pool, " "); + } + snd_seq_client_unlock(client); + } +} +#endif /* CONFIG_SND_PROC_FS */ + +/*---------------------------------------------------------------------------*/ + + +/* + * REGISTRATION PART + */ + +static const struct file_operations snd_seq_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_seq_read, + .write = snd_seq_write, + .open = snd_seq_open, + .release = snd_seq_release, + .llseek = no_llseek, + .poll = snd_seq_poll, + .unlocked_ioctl = snd_seq_ioctl, + .compat_ioctl = snd_seq_ioctl_compat, +}; + +static struct device seq_dev; + +/* + * register sequencer device + */ +int __init snd_sequencer_device_init(void) +{ + int err; + + snd_device_initialize(&seq_dev, NULL); + dev_set_name(&seq_dev, "seq"); + + mutex_lock(®ister_mutex); + err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0, + &snd_seq_f_ops, NULL, &seq_dev); + mutex_unlock(®ister_mutex); + if (err < 0) { + put_device(&seq_dev); + return err; + } + + return 0; +} + + + +/* + * unregister sequencer device + */ +void snd_sequencer_device_done(void) +{ + snd_unregister_device(&seq_dev); + put_device(&seq_dev); +} diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h new file mode 100644 index 000000000..0611e1e0e --- /dev/null +++ b/sound/core/seq/seq_clientmgr.h @@ -0,0 +1,106 @@ +/* + * ALSA sequencer Client Manager + * Copyright (c) 1998-1999 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_CLIENTMGR_H +#define __SND_SEQ_CLIENTMGR_H + +#include +#include +#include "seq_fifo.h" +#include "seq_ports.h" +#include "seq_lock.h" + + +/* client manager */ + +struct snd_seq_user_client { + struct file *file; /* file struct of client */ + /* ... */ + struct pid *owner; + + /* fifo */ + struct snd_seq_fifo *fifo; /* queue for incoming events */ + int fifo_pool_size; +}; + +struct snd_seq_kernel_client { + /* ... */ + struct snd_card *card; +}; + + +struct snd_seq_client { + snd_seq_client_type_t type; + unsigned int accept_input: 1, + accept_output: 1; + char name[64]; /* client name */ + int number; /* client number */ + unsigned int filter; /* filter flags */ + DECLARE_BITMAP(event_filter, 256); + snd_use_lock_t use_lock; + int event_lost; + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; + rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ + + /* output pool */ + struct snd_seq_pool *pool; /* memory pool for this client */ + + union { + struct snd_seq_user_client user; + struct snd_seq_kernel_client kernel; + } data; +}; + +/* usage statistics */ +struct snd_seq_usage { + int cur; + int peak; +}; + + +int client_init_data(void); +int snd_sequencer_device_init(void); +void snd_sequencer_device_done(void); + +/* get locked pointer to client */ +struct snd_seq_client *snd_seq_client_use_ptr(int clientid); + +/* unlock pointer to client */ +#define snd_seq_client_unlock(client) snd_use_lock_free(&(client)->use_lock) + +/* dispatch event to client(s) */ +int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop); + +/* exported to other modules */ +int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev, int atomic, int hop); +int snd_seq_kernel_client_enqueue_blocking(int client, struct snd_seq_event * ev, + struct file *file, int atomic, int hop); +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait); +int snd_seq_client_notify_subscription(int client, int port, + struct snd_seq_port_subscribe *info, int evtype); + +extern int seq_client_load[15]; + +#endif diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c new file mode 100644 index 000000000..8c3507216 --- /dev/null +++ b/sound/core/seq/seq_compat.c @@ -0,0 +1,136 @@ +/* + * 32bit -> 64bit ioctl wrapper for sequencer API + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from seq.c */ + +#include +#include + +struct snd_seq_port_info32 { + struct snd_seq_addr addr; /* client/port numbers */ + char name[64]; /* port name */ + + u32 capability; /* port capability bits */ + u32 type; /* port type bits */ + s32 midi_channels; /* channels per MIDI port */ + s32 midi_voices; /* voices per MIDI port */ + s32 synth_voices; /* voices per SYNTH port */ + + s32 read_use; /* R/O: subscribers for output (from this port) */ + s32 write_use; /* R/O: subscribers for input (to this port) */ + + u32 kernel; /* reserved for kernel use (must be NULL) */ + u32 flags; /* misc. conditioning */ + unsigned char time_queue; /* queue # for timestamping */ + char reserved[59]; /* for future use */ +}; + +static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd, + struct snd_seq_port_info32 __user *data32) +{ + int err = -EFAULT; + struct snd_seq_port_info *data; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (copy_from_user(data, data32, sizeof(*data32)) || + get_user(data->flags, &data32->flags) || + get_user(data->time_queue, &data32->time_queue)) + goto error; + data->kernel = NULL; + + err = snd_seq_kernel_client_ctl(client->number, cmd, data); + if (err < 0) + goto error; + + if (copy_to_user(data32, data, sizeof(*data32)) || + put_user(data->flags, &data32->flags) || + put_user(data->time_queue, &data32->time_queue)) + err = -EFAULT; + + error: + kfree(data); + return err; +} + + + +/* + */ + +enum { + SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct snd_seq_port_info32), +}; + +static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_seq_client *client = file->private_data; + void __user *argp = compat_ptr(arg); + + if (snd_BUG_ON(!client)) + return -ENXIO; + + switch (cmd) { + case SNDRV_SEQ_IOCTL_PVERSION: + case SNDRV_SEQ_IOCTL_CLIENT_ID: + case SNDRV_SEQ_IOCTL_SYSTEM_INFO: + case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_CREATE_QUEUE: + case SNDRV_SEQ_IOCTL_DELETE_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_REMOVE_EVENTS: + case SNDRV_SEQ_IOCTL_QUERY_SUBS: + case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION: + case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT: + case SNDRV_SEQ_IOCTL_RUNNING_MODE: + return snd_seq_ioctl(file, cmd, arg); + case SNDRV_SEQ_IOCTL_CREATE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp); + case SNDRV_SEQ_IOCTL_DELETE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp); + case SNDRV_SEQ_IOCTL_GET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_SET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp); + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c new file mode 100644 index 000000000..d3a2ec4f0 --- /dev/null +++ b/sound/core/seq/seq_dummy.c @@ -0,0 +1,226 @@ +/* + * ALSA sequencer MIDI-through client + * Copyright (c) 1999-2000 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "seq_clientmgr.h" +#include +#include + +/* + + Sequencer MIDI-through client + + This gives a simple midi-through client. All the normal input events + are redirected to output port immediately. + The routing can be done via aconnect program in alsa-utils. + + Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY). + If you want to auto-load this module, you may add the following alias + in your /etc/conf.modules file. + + alias snd-seq-client-62 snd-seq-dummy + + The module is loaded on demand for client 62, or /proc/asound/seq/ + is accessed. If you don't need this module to be loaded, alias + snd-seq-client-62 as "off". This will help modprobe. + + The number of ports to be created can be specified via the module + parameter "ports". For example, to create four ports, add the + following option in a configuration file under /etc/modprobe.d/: + + option snd-seq-dummy ports=4 + + The model option "duplex=1" enables duplex operation to the port. + In duplex mode, a pair of ports are created instead of single port, + and events are tunneled between pair-ports. For example, input to + port A is sent to output port of another port B and vice versa. + In duplex mode, each port has DUPLEX capability. + + */ + + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("ALSA sequencer MIDI-through client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY)); + +static int ports = 1; +static bool duplex; + +module_param(ports, int, 0444); +MODULE_PARM_DESC(ports, "number of ports to be created"); +module_param(duplex, bool, 0444); +MODULE_PARM_DESC(duplex, "create DUPLEX ports"); + +struct snd_seq_dummy_port { + int client; + int port; + int duplex; + int connect; +}; + +static int my_client = -1; + +/* + * event input callback - just redirect events to subscribers + */ +static int +dummy_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct snd_seq_dummy_port *p; + struct snd_seq_event tmpev; + + p = private_data; + if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM || + ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return 0; /* ignore system messages */ + tmpev = *ev; + if (p->duplex) + tmpev.source.port = p->connect; + else + tmpev.source.port = p->port; + tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop); +} + +/* + * free_private callback + */ +static void +dummy_free(void *private_data) +{ + kfree(private_data); +} + +/* + * create a port + */ +static struct snd_seq_dummy_port __init * +create_port(int idx, int type) +{ + struct snd_seq_port_info pinfo; + struct snd_seq_port_callback pcb; + struct snd_seq_dummy_port *rec; + + if ((rec = kzalloc(sizeof(*rec), GFP_KERNEL)) == NULL) + return NULL; + + rec->client = my_client; + rec->duplex = duplex; + rec->connect = 0; + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.addr.client = my_client; + if (duplex) + sprintf(pinfo.name, "Midi Through Port-%d:%c", idx, + (type ? 'B' : 'A')); + else + sprintf(pinfo.name, "Midi Through Port-%d", idx); + pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if (duplex) + pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_SOFTWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + memset(&pcb, 0, sizeof(pcb)); + pcb.owner = THIS_MODULE; + pcb.event_input = dummy_input; + pcb.private_free = dummy_free; + pcb.private_data = rec; + pinfo.kernel = &pcb; + if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) { + kfree(rec); + return NULL; + } + rec->port = pinfo.addr.port; + return rec; +} + +/* + * register client and create ports + */ +static int __init +register_client(void) +{ + struct snd_seq_dummy_port *rec1, *rec2; + int i; + + if (ports < 1) { + pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports); + return -EINVAL; + } + + /* create client */ + my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, + "Midi Through"); + if (my_client < 0) + return my_client; + + /* create ports */ + for (i = 0; i < ports; i++) { + rec1 = create_port(i, 0); + if (rec1 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + if (duplex) { + rec2 = create_port(i, 1); + if (rec2 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + rec1->connect = rec2->port; + rec2->connect = rec1->port; + } + } + + return 0; +} + +/* + * delete client if exists + */ +static void __exit +delete_client(void) +{ + if (my_client >= 0) + snd_seq_delete_kernel_client(my_client); +} + +/* + * Init part + */ + +static int __init alsa_seq_dummy_init(void) +{ + return register_client(); +} + +static void __exit alsa_seq_dummy_exit(void) +{ + delete_client(); +} + +module_init(alsa_seq_dummy_init) +module_exit(alsa_seq_dummy_exit) diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c new file mode 100644 index 000000000..6a2473270 --- /dev/null +++ b/sound/core/seq/seq_fifo.c @@ -0,0 +1,299 @@ +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#include "seq_fifo.h" +#include "seq_lock.h" + + +/* FIFO */ + +/* create new fifo */ +struct snd_seq_fifo *snd_seq_fifo_new(int poolsize) +{ + struct snd_seq_fifo *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + f->pool = snd_seq_pool_new(poolsize); + if (f->pool == NULL) { + kfree(f); + return NULL; + } + if (snd_seq_pool_init(f->pool) < 0) { + snd_seq_pool_delete(&f->pool); + kfree(f); + return NULL; + } + + spin_lock_init(&f->lock); + snd_use_lock_init(&f->use_lock); + init_waitqueue_head(&f->input_sleep); + atomic_set(&f->overflow, 0); + + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +void snd_seq_fifo_delete(struct snd_seq_fifo **fifo) +{ + struct snd_seq_fifo *f; + + if (snd_BUG_ON(!fifo)) + return; + f = *fifo; + if (snd_BUG_ON(!f)) + return; + *fifo = NULL; + + if (f->pool) + snd_seq_pool_mark_closing(f->pool); + + snd_seq_fifo_clear(f); + + /* wake up clients if any */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + /* release resources...*/ + /*....................*/ + + if (f->pool) { + snd_seq_pool_done(f->pool); + snd_seq_pool_delete(&f->pool); + } + + kfree(f); +} + +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f); + +/* clear queue */ +void snd_seq_fifo_clear(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + + /* clear overflow flag */ + atomic_set(&f->overflow, 0); + + snd_use_lock_sync(&f->use_lock); + spin_lock_irqsave(&f->lock, flags); + /* drain the fifo */ + while ((cell = fifo_cell_out(f)) != NULL) { + snd_seq_cell_free(cell); + } + spin_unlock_irqrestore(&f->lock, flags); +} + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(struct snd_seq_fifo *f, + struct snd_seq_event *event) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + int err; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + snd_use_lock_use(&f->use_lock); + err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL, NULL); /* always non-blocking */ + if (err < 0) { + if ((err == -ENOMEM) || (err == -EAGAIN)) + atomic_inc(&f->overflow); + snd_use_lock_free(&f->use_lock); + return err; + } + + /* append new cells to fifo */ + spin_lock_irqsave(&f->lock, flags); + if (f->tail != NULL) + f->tail->next = cell; + f->tail = cell; + if (f->head == NULL) + f->head = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + + /* wakeup client */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + snd_use_lock_free(&f->use_lock); + + return 0; /* success */ + +} + +/* dequeue cell from fifo */ +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + + if ((cell = f->head) != NULL) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + return cell; +} + +/* dequeue cell from fifo and copy on user space */ +int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, + struct snd_seq_event_cell **cellp, int nonblock) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + wait_queue_entry_t wait; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + *cellp = NULL; + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&f->lock, flags); + while ((cell = fifo_cell_out(f)) == NULL) { + if (nonblock) { + /* non-blocking - return immediately */ + spin_unlock_irqrestore(&f->lock, flags); + return -EAGAIN; + } + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&f->input_sleep, &wait); + spin_unlock_irq(&f->lock); + schedule(); + spin_lock_irq(&f->lock); + remove_wait_queue(&f->input_sleep, &wait); + if (signal_pending(current)) { + spin_unlock_irqrestore(&f->lock, flags); + return -ERESTARTSYS; + } + } + spin_unlock_irqrestore(&f->lock, flags); + *cellp = cell; + + return 0; +} + + +void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, + struct snd_seq_event_cell *cell) +{ + unsigned long flags; + + if (cell) { + spin_lock_irqsave(&f->lock, flags); + cell->next = f->head; + f->head = cell; + if (!f->tail) + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + } +} + + +/* polling; return non-zero if queue is available */ +int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, + poll_table *wait) +{ + poll_wait(file, &f->input_sleep, wait); + return (f->cells > 0); +} + +/* change the size of pool; all old events are removed */ +int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize) +{ + unsigned long flags; + struct snd_seq_pool *newpool, *oldpool; + struct snd_seq_event_cell *cell, *next, *oldhead; + + if (snd_BUG_ON(!f || !f->pool)) + return -EINVAL; + + /* allocate new pool */ + newpool = snd_seq_pool_new(poolsize); + if (newpool == NULL) + return -ENOMEM; + if (snd_seq_pool_init(newpool) < 0) { + snd_seq_pool_delete(&newpool); + return -ENOMEM; + } + + spin_lock_irqsave(&f->lock, flags); + /* remember old pool */ + oldpool = f->pool; + oldhead = f->head; + /* exchange pools */ + f->pool = newpool; + f->head = NULL; + f->tail = NULL; + f->cells = 0; + /* NOTE: overflow flag is not cleared */ + spin_unlock_irqrestore(&f->lock, flags); + + /* close the old pool and wait until all users are gone */ + snd_seq_pool_mark_closing(oldpool); + snd_use_lock_sync(&f->use_lock); + + /* release cells in old pool */ + for (cell = oldhead; cell; cell = next) { + next = cell->next; + snd_seq_cell_free(cell); + } + snd_seq_pool_delete(&oldpool); + + return 0; +} + +/* get the number of unused cells safely */ +int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f) +{ + unsigned long flags; + int cells; + + if (!f) + return 0; + + snd_use_lock_use(&f->use_lock); + spin_lock_irqsave(&f->lock, flags); + cells = snd_seq_unused_cells(f->pool); + spin_unlock_irqrestore(&f->lock, flags); + snd_use_lock_free(&f->use_lock); + return cells; +} diff --git a/sound/core/seq/seq_fifo.h b/sound/core/seq/seq_fifo.h new file mode 100644 index 000000000..5d38a0d7f --- /dev/null +++ b/sound/core/seq/seq_fifo.h @@ -0,0 +1,74 @@ +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_FIFO_H +#define __SND_SEQ_FIFO_H + +#include "seq_memory.h" +#include "seq_lock.h" + + +/* === FIFO === */ + +struct snd_seq_fifo { + struct snd_seq_pool *pool; /* FIFO pool */ + struct snd_seq_event_cell *head; /* pointer to head of fifo */ + struct snd_seq_event_cell *tail; /* pointer to tail of fifo */ + int cells; + spinlock_t lock; + snd_use_lock_t use_lock; + wait_queue_head_t input_sleep; + atomic_t overflow; + +}; + +/* create new fifo (constructor) */ +struct snd_seq_fifo *snd_seq_fifo_new(int poolsize); + +/* delete fifo (destructor) */ +void snd_seq_fifo_delete(struct snd_seq_fifo **f); + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(struct snd_seq_fifo *f, struct snd_seq_event *event); + +/* lock fifo from release */ +#define snd_seq_fifo_lock(fifo) snd_use_lock_use(&(fifo)->use_lock) +#define snd_seq_fifo_unlock(fifo) snd_use_lock_free(&(fifo)->use_lock) + +/* get a cell from fifo - fifo should be locked */ +int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, struct snd_seq_event_cell **cellp, int nonblock); + +/* free dequeued cell - fifo should be locked */ +void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, struct snd_seq_event_cell *cell); + +/* clean up queue */ +void snd_seq_fifo_clear(struct snd_seq_fifo *f); + +/* polling */ +int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, poll_table *wait); + +/* resize pool in fifo */ +int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize); + +/* get the number of unused cells safely */ +int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f); + +#endif diff --git a/sound/core/seq/seq_info.c b/sound/core/seq/seq_info.c new file mode 100644 index 000000000..b27fedd43 --- /dev/null +++ b/sound/core/seq/seq_info.c @@ -0,0 +1,75 @@ +/* + * ALSA sequencer /proc interface + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#include "seq_info.h" +#include "seq_clientmgr.h" +#include "seq_timer.h" + +static struct snd_info_entry *queues_entry; +static struct snd_info_entry *clients_entry; +static struct snd_info_entry *timer_entry; + + +static struct snd_info_entry * __init +create_info_entry(char *name, void (*read)(struct snd_info_entry *, + struct snd_info_buffer *)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root); + if (entry == NULL) + return NULL; + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->c.text.read = read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return NULL; + } + return entry; +} + +void snd_seq_info_done(void) +{ + snd_info_free_entry(queues_entry); + snd_info_free_entry(clients_entry); + snd_info_free_entry(timer_entry); +} + +/* create all our /proc entries */ +int __init snd_seq_info_init(void) +{ + queues_entry = create_info_entry("queues", + snd_seq_info_queues_read); + clients_entry = create_info_entry("clients", + snd_seq_info_clients_read); + timer_entry = create_info_entry("timer", snd_seq_info_timer_read); + if (!queues_entry || !clients_entry || !timer_entry) + goto error; + return 0; + + error: + snd_seq_info_done(); + return -ENOMEM; +} diff --git a/sound/core/seq/seq_info.h b/sound/core/seq/seq_info.h new file mode 100644 index 000000000..2cdf8f6e6 --- /dev/null +++ b/sound/core/seq/seq_info.h @@ -0,0 +1,40 @@ +/* + * ALSA sequencer /proc info + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_INFO_H +#define __SND_SEQ_INFO_H + +#include +#include + +void snd_seq_info_clients_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +void snd_seq_info_timer_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +void snd_seq_info_queues_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); + + +#ifdef CONFIG_SND_PROC_FS +int snd_seq_info_init(void); +void snd_seq_info_done(void); +#else +static inline int snd_seq_info_init(void) { return 0; } +static inline void snd_seq_info_done(void) {} +#endif + +#endif diff --git a/sound/core/seq/seq_lock.c b/sound/core/seq/seq_lock.c new file mode 100644 index 000000000..cda64b489 --- /dev/null +++ b/sound/core/seq/seq_lock.c @@ -0,0 +1,41 @@ +/* + * Do sleep inside a spin-lock + * Copyright (c) 1999 by Takashi Iwai + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include "seq_lock.h" + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line) +{ + int warn_count = 5 * HZ; + + if (atomic_read(lockp) < 0) { + pr_warn("ALSA: seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line); + return; + } + while (atomic_read(lockp) > 0) { + if (warn_count-- == 0) + pr_warn("ALSA: seq_lock: waiting [%d left] in %s:%d\n", atomic_read(lockp), file, line); + schedule_timeout_uninterruptible(1); + } +} +EXPORT_SYMBOL(snd_use_lock_sync_helper); diff --git a/sound/core/seq/seq_lock.h b/sound/core/seq/seq_lock.h new file mode 100644 index 000000000..a973860eb --- /dev/null +++ b/sound/core/seq/seq_lock.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SND_SEQ_LOCK_H +#define __SND_SEQ_LOCK_H + +#include + +typedef atomic_t snd_use_lock_t; + +/* initialize lock */ +#define snd_use_lock_init(lockp) atomic_set(lockp, 0) + +/* increment lock */ +#define snd_use_lock_use(lockp) atomic_inc(lockp) + +/* release lock */ +#define snd_use_lock_free(lockp) atomic_dec(lockp) + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line); +#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__) + +#endif /* __SND_SEQ_LOCK_H */ diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c new file mode 100644 index 000000000..5b0388202 --- /dev/null +++ b/sound/core/seq/seq_memory.c @@ -0,0 +1,518 @@ +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol + * Jaroslav Kysela + * 2000 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_info.h" +#include "seq_lock.h" + +static inline int snd_seq_pool_available(struct snd_seq_pool *pool) +{ + return pool->total_elements - atomic_read(&pool->counter); +} + +static inline int snd_seq_output_ok(struct snd_seq_pool *pool) +{ + return snd_seq_pool_available(pool) >= pool->room; +} + +/* + * Variable length event: + * The event like sysex uses variable length type. + * The external data may be stored in three different formats. + * 1) kernel space + * This is the normal case. + * ext.data.len = length + * ext.data.ptr = buffer pointer + * 2) user space + * When an event is generated via read(), the external data is + * kept in user space until expanded. + * ext.data.len = length | SNDRV_SEQ_EXT_USRPTR + * ext.data.ptr = userspace pointer + * 3) chained cells + * When the variable length event is enqueued (in prioq or fifo), + * the external data is decomposed to several cells. + * ext.data.len = length | SNDRV_SEQ_EXT_CHAINED + * ext.data.ptr = the additiona cell head + * -> cell.next -> cell.next -> .. + */ + +/* + * exported: + * call dump function to expand external data. + */ + +static int get_var_len(const struct snd_seq_event *event) +{ + if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + return -EINVAL; + + return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; +} + +int snd_seq_dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data) +{ + int len, err; + struct snd_seq_event_cell *cell; + + if ((len = get_var_len(event)) <= 0) + return len; + + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + char buf[32]; + char __user *curptr = (char __force __user *)event->data.ext.ptr; + while (len > 0) { + int size = sizeof(buf); + if (len < size) + size = len; + if (copy_from_user(buf, curptr, size)) + return -EFAULT; + err = func(private_data, buf, size); + if (err < 0) + return err; + curptr += size; + len -= size; + } + return 0; + } + if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) + return func(private_data, event->data.ext.ptr, len); + + cell = (struct snd_seq_event_cell *)event->data.ext.ptr; + for (; len > 0 && cell; cell = cell->next) { + int size = sizeof(struct snd_seq_event); + if (len < size) + size = len; + err = func(private_data, &cell->event, size); + if (err < 0) + return err; + len -= size; + } + return 0; +} +EXPORT_SYMBOL(snd_seq_dump_var_event); + + +/* + * exported: + * expand the variable length event to linear buffer space. + */ + +static int seq_copy_in_kernel(char **bufptr, const void *src, int size) +{ + memcpy(*bufptr, src, size); + *bufptr += size; + return 0; +} + +static int seq_copy_in_user(char __user **bufptr, const void *src, int size) +{ + if (copy_to_user(*bufptr, src, size)) + return -EFAULT; + *bufptr += size; + return 0; +} + +int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, + int in_kernel, int size_aligned) +{ + int len, newlen; + int err; + + if ((len = get_var_len(event)) < 0) + return len; + newlen = len; + if (size_aligned > 0) + newlen = roundup(len, size_aligned); + if (count < newlen) + return -EAGAIN; + + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + if (! in_kernel) + return -EINVAL; + if (copy_from_user(buf, (void __force __user *)event->data.ext.ptr, len)) + return -EFAULT; + return newlen; + } + err = snd_seq_dump_var_event(event, + in_kernel ? (snd_seq_dump_func_t)seq_copy_in_kernel : + (snd_seq_dump_func_t)seq_copy_in_user, + &buf); + return err < 0 ? err : newlen; +} +EXPORT_SYMBOL(snd_seq_expand_var_event); + +/* + * release this cell, free extended data if available + */ + +static inline void free_cell(struct snd_seq_pool *pool, + struct snd_seq_event_cell *cell) +{ + cell->next = pool->free; + pool->free = cell; + atomic_dec(&pool->counter); +} + +void snd_seq_cell_free(struct snd_seq_event_cell * cell) +{ + unsigned long flags; + struct snd_seq_pool *pool; + + if (snd_BUG_ON(!cell)) + return; + pool = cell->pool; + if (snd_BUG_ON(!pool)) + return; + + spin_lock_irqsave(&pool->lock, flags); + free_cell(pool, cell); + if (snd_seq_ev_is_variable(&cell->event)) { + if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) { + struct snd_seq_event_cell *curp, *nextptr; + curp = cell->event.data.ext.ptr; + for (; curp; curp = nextptr) { + nextptr = curp->next; + curp->next = pool->free; + free_cell(pool, curp); + } + } + } + if (waitqueue_active(&pool->output_sleep)) { + /* has enough space now? */ + if (snd_seq_output_ok(pool)) + wake_up(&pool->output_sleep); + } + spin_unlock_irqrestore(&pool->lock, flags); +} + + +/* + * allocate an event cell. + */ +static int snd_seq_cell_alloc(struct snd_seq_pool *pool, + struct snd_seq_event_cell **cellp, + int nonblock, struct file *file, + struct mutex *mutexp) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + int err = -EAGAIN; + wait_queue_entry_t wait; + + if (pool == NULL) + return -EINVAL; + + *cellp = NULL; + + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&pool->lock, flags); + if (pool->ptr == NULL) { /* not initialized */ + pr_debug("ALSA: seq: pool is not initialized\n"); + err = -EINVAL; + goto __error; + } + while (pool->free == NULL && ! nonblock && ! pool->closing) { + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&pool->output_sleep, &wait); + spin_unlock_irq(&pool->lock); + if (mutexp) + mutex_unlock(mutexp); + schedule(); + if (mutexp) + mutex_lock(mutexp); + spin_lock_irq(&pool->lock); + remove_wait_queue(&pool->output_sleep, &wait); + /* interrupted? */ + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto __error; + } + } + if (pool->closing) { /* closing.. */ + err = -ENOMEM; + goto __error; + } + + cell = pool->free; + if (cell) { + int used; + pool->free = cell->next; + atomic_inc(&pool->counter); + used = atomic_read(&pool->counter); + if (pool->max_used < used) + pool->max_used = used; + pool->event_alloc_success++; + /* clear cell pointers */ + cell->next = NULL; + err = 0; + } else + pool->event_alloc_failures++; + *cellp = cell; + +__error: + spin_unlock_irqrestore(&pool->lock, flags); + return err; +} + + +/* + * duplicate the event to a cell. + * if the event has external data, the data is decomposed to additional + * cells. + */ +int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, + struct snd_seq_event_cell **cellp, int nonblock, + struct file *file, struct mutex *mutexp) +{ + int ncells, err; + unsigned int extlen; + struct snd_seq_event_cell *cell; + + *cellp = NULL; + + ncells = 0; + extlen = 0; + if (snd_seq_ev_is_variable(event)) { + extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; + ncells = (extlen + sizeof(struct snd_seq_event) - 1) / sizeof(struct snd_seq_event); + } + if (ncells >= pool->total_elements) + return -ENOMEM; + + err = snd_seq_cell_alloc(pool, &cell, nonblock, file, mutexp); + if (err < 0) + return err; + + /* copy the event */ + cell->event = *event; + + /* decompose */ + if (snd_seq_ev_is_variable(event)) { + int len = extlen; + int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED; + int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR; + struct snd_seq_event_cell *src, *tmp, *tail; + char *buf; + + cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED; + cell->event.data.ext.ptr = NULL; + + src = (struct snd_seq_event_cell *)event->data.ext.ptr; + buf = (char *)event->data.ext.ptr; + tail = NULL; + + while (ncells-- > 0) { + int size = sizeof(struct snd_seq_event); + if (len < size) + size = len; + err = snd_seq_cell_alloc(pool, &tmp, nonblock, file, + mutexp); + if (err < 0) + goto __error; + if (cell->event.data.ext.ptr == NULL) + cell->event.data.ext.ptr = tmp; + if (tail) + tail->next = tmp; + tail = tmp; + /* copy chunk */ + if (is_chained && src) { + tmp->event = src->event; + src = src->next; + } else if (is_usrptr) { + if (copy_from_user(&tmp->event, (char __force __user *)buf, size)) { + err = -EFAULT; + goto __error; + } + } else { + memcpy(&tmp->event, buf, size); + } + buf += size; + len -= size; + } + } + + *cellp = cell; + return 0; + +__error: + snd_seq_cell_free(cell); + return err; +} + + +/* poll wait */ +int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, + poll_table *wait) +{ + poll_wait(file, &pool->output_sleep, wait); + return snd_seq_output_ok(pool); +} + + +/* allocate room specified number of events */ +int snd_seq_pool_init(struct snd_seq_pool *pool) +{ + int cell; + struct snd_seq_event_cell *cellptr; + unsigned long flags; + + if (snd_BUG_ON(!pool)) + return -EINVAL; + + cellptr = vmalloc(array_size(sizeof(struct snd_seq_event_cell), + pool->size)); + if (!cellptr) + return -ENOMEM; + + /* add new cells to the free cell list */ + spin_lock_irqsave(&pool->lock, flags); + if (pool->ptr) { + spin_unlock_irqrestore(&pool->lock, flags); + vfree(cellptr); + return 0; + } + + pool->ptr = cellptr; + pool->free = NULL; + + for (cell = 0; cell < pool->size; cell++) { + cellptr = pool->ptr + cell; + cellptr->pool = pool; + cellptr->next = pool->free; + pool->free = cellptr; + } + pool->room = (pool->size + 1) / 2; + + /* init statistics */ + pool->max_used = 0; + pool->total_elements = pool->size; + spin_unlock_irqrestore(&pool->lock, flags); + return 0; +} + +/* refuse the further insertion to the pool */ +void snd_seq_pool_mark_closing(struct snd_seq_pool *pool) +{ + unsigned long flags; + + if (snd_BUG_ON(!pool)) + return; + spin_lock_irqsave(&pool->lock, flags); + pool->closing = 1; + spin_unlock_irqrestore(&pool->lock, flags); +} + +/* remove events */ +int snd_seq_pool_done(struct snd_seq_pool *pool) +{ + unsigned long flags; + struct snd_seq_event_cell *ptr; + + if (snd_BUG_ON(!pool)) + return -EINVAL; + + /* wait for closing all threads */ + if (waitqueue_active(&pool->output_sleep)) + wake_up(&pool->output_sleep); + + while (atomic_read(&pool->counter) > 0) + schedule_timeout_uninterruptible(1); + + /* release all resources */ + spin_lock_irqsave(&pool->lock, flags); + ptr = pool->ptr; + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + spin_unlock_irqrestore(&pool->lock, flags); + + vfree(ptr); + + spin_lock_irqsave(&pool->lock, flags); + pool->closing = 0; + spin_unlock_irqrestore(&pool->lock, flags); + + return 0; +} + + +/* init new memory pool */ +struct snd_seq_pool *snd_seq_pool_new(int poolsize) +{ + struct snd_seq_pool *pool; + + /* create pool block */ + pool = kzalloc(sizeof(*pool), GFP_KERNEL); + if (!pool) + return NULL; + spin_lock_init(&pool->lock); + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + atomic_set(&pool->counter, 0); + pool->closing = 0; + init_waitqueue_head(&pool->output_sleep); + + pool->size = poolsize; + + /* init statistics */ + pool->max_used = 0; + return pool; +} + +/* remove memory pool */ +int snd_seq_pool_delete(struct snd_seq_pool **ppool) +{ + struct snd_seq_pool *pool = *ppool; + + *ppool = NULL; + if (pool == NULL) + return 0; + snd_seq_pool_mark_closing(pool); + snd_seq_pool_done(pool); + kfree(pool); + return 0; +} + +/* exported to seq_clientmgr.c */ +void snd_seq_info_pool(struct snd_info_buffer *buffer, + struct snd_seq_pool *pool, char *space) +{ + if (pool == NULL) + return; + snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements); + snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter)); + snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used); + snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success); + snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures); +} diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h new file mode 100644 index 000000000..1292fe91f --- /dev/null +++ b/sound/core/seq/seq_memory.h @@ -0,0 +1,103 @@ +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_MEMORYMGR_H +#define __SND_SEQ_MEMORYMGR_H + +#include +#include + +struct snd_info_buffer; + +/* container for sequencer event (internal use) */ +struct snd_seq_event_cell { + struct snd_seq_event event; + struct snd_seq_pool *pool; /* used pool */ + struct snd_seq_event_cell *next; /* next cell */ +}; + +/* design note: the pool is a contiguous block of memory, if we dynamicly + want to add additional cells to the pool be better store this in another + pool as we need to know the base address of the pool when releasing + memory. */ + +struct snd_seq_pool { + struct snd_seq_event_cell *ptr; /* pointer to first event chunk */ + struct snd_seq_event_cell *free; /* pointer to the head of the free list */ + + int total_elements; /* pool size actually allocated */ + atomic_t counter; /* cells free */ + + int size; /* pool size to be allocated */ + int room; /* watermark for sleep/wakeup */ + + int closing; + + /* statistics */ + int max_used; + int event_alloc_nopool; + int event_alloc_failures; + int event_alloc_success; + + /* Write locking */ + wait_queue_head_t output_sleep; + + /* Pool lock */ + spinlock_t lock; +}; + +void snd_seq_cell_free(struct snd_seq_event_cell *cell); + +int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, + struct snd_seq_event_cell **cellp, int nonblock, + struct file *file, struct mutex *mutexp); + +/* return number of unused (free) cells */ +static inline int snd_seq_unused_cells(struct snd_seq_pool *pool) +{ + return pool ? pool->total_elements - atomic_read(&pool->counter) : 0; +} + +/* return total number of allocated cells */ +static inline int snd_seq_total_cells(struct snd_seq_pool *pool) +{ + return pool ? pool->total_elements : 0; +} + +/* init pool - allocate events */ +int snd_seq_pool_init(struct snd_seq_pool *pool); + +/* done pool - free events */ +void snd_seq_pool_mark_closing(struct snd_seq_pool *pool); +int snd_seq_pool_done(struct snd_seq_pool *pool); + +/* create pool */ +struct snd_seq_pool *snd_seq_pool_new(int poolsize); + +/* remove pool */ +int snd_seq_pool_delete(struct snd_seq_pool **pool); + +/* polling */ +int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, poll_table *wait); + +void snd_seq_info_pool(struct snd_info_buffer *buffer, + struct snd_seq_pool *pool, char *space); + +#endif diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c new file mode 100644 index 000000000..9e0dabd3c --- /dev/null +++ b/sound/core/seq/seq_midi.c @@ -0,0 +1,468 @@ +/* + * Generic MIDI synth driver for ALSA sequencer + * Copyright (c) 1998 by Frank van de Pol + * Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* +Possible options for midisynth module: + - automatic opening of midi ports on first received event or subscription + (close will be performed when client leaves) +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Frank van de Pol , Jaroslav Kysela "); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth."); +MODULE_LICENSE("GPL"); +static int output_buffer_size = PAGE_SIZE; +module_param(output_buffer_size, int, 0644); +MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes."); +static int input_buffer_size = PAGE_SIZE; +module_param(input_buffer_size, int, 0644); +MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes."); + +/* data for this midi synth driver */ +struct seq_midisynth { + struct snd_card *card; + int device; + int subdevice; + struct snd_rawmidi_file input_rfile; + struct snd_rawmidi_file output_rfile; + int seq_client; + int seq_port; + struct snd_midi_event *parser; +}; + +struct seq_midisynth_client { + int seq_client; + int num_ports; + int ports_per_device[SNDRV_RAWMIDI_DEVICES]; + struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES]; +}; + +static struct seq_midisynth_client *synths[SNDRV_CARDS]; +static DEFINE_MUTEX(register_mutex); + +/* handle rawmidi input event (MIDI v1.0 stream) */ +static void snd_midi_input_event(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime; + struct seq_midisynth *msynth; + struct snd_seq_event ev; + char buf[16], *pbuf; + long res; + + if (substream == NULL) + return; + runtime = substream->runtime; + msynth = runtime->private_data; + if (msynth == NULL) + return; + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); + if (res <= 0) + continue; + if (msynth->parser == NULL) + continue; + pbuf = buf; + while (res-- > 0) { + if (!snd_midi_event_encode_byte(msynth->parser, + *pbuf++, &ev)) + continue; + ev.source.port = msynth->seq_port; + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0); + /* clear event and reset header */ + memset(&ev, 0, sizeof(ev)); + } + } +} + +static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) +{ + struct snd_rawmidi_runtime *runtime; + int tmp; + + if (snd_BUG_ON(!substream || !buf)) + return -EINVAL; + runtime = substream->runtime; + if ((tmp = runtime->avail) < count) { + if (printk_ratelimit()) + pr_err("ALSA: seq_midi: MIDI output buffer overrun\n"); + return -ENOMEM; + } + if (snd_rawmidi_kernel_write(substream, buf, count) < count) + return -EINVAL; + return 0; +} + +static int event_process_midi(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct seq_midisynth *msynth = private_data; + unsigned char msg[10]; /* buffer for constructing midi messages */ + struct snd_rawmidi_substream *substream; + int len; + + if (snd_BUG_ON(!msynth)) + return -EINVAL; + substream = msynth->output_rfile.output; + if (substream == NULL) + return -ENODEV; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { /* special case, to save space */ + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + /* invalid event */ + pr_debug("ALSA: seq_midi: invalid sysex event flags = 0x%x\n", ev->flags); + return 0; + } + snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream); + snd_midi_event_reset_decode(msynth->parser); + } else { + if (msynth->parser == NULL) + return -EIO; + len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev); + if (len < 0) + return 0; + if (dump_midi(substream, msg, len) < 0) + snd_midi_event_reset_decode(msynth->parser); + } + return 0; +} + + +static int snd_seq_midisynth_new(struct seq_midisynth *msynth, + struct snd_card *card, + int device, + int subdevice) +{ + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0) + return -ENOMEM; + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; + return 0; +} + +/* open associated midi device for input */ +static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_runtime *runtime; + struct snd_rawmidi_params params; + + /* open midi port */ + if ((err = snd_rawmidi_kernel_open(msynth->card, msynth->device, + msynth->subdevice, + SNDRV_RAWMIDI_LFLG_INPUT, + &msynth->input_rfile)) < 0) { + pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); + return err; + } + runtime = msynth->input_rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; + if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms)) < 0) { + snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; +} + +/* close associated midi device for input */ +static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + + if (snd_BUG_ON(!msynth->input_rfile.input)) + return -EINVAL; + err = snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; +} + +/* open associated midi device for output */ +static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_params params; + + /* open midi port */ + if ((err = snd_rawmidi_kernel_open(msynth->card, msynth->device, + msynth->subdevice, + SNDRV_RAWMIDI_LFLG_OUTPUT, + &msynth->output_rfile)) < 0) { + pr_debug("ALSA: seq_midi: midi output open failed!!!\n"); + return err; + } + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = output_buffer_size; + params.no_active_sensing = 1; + if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, ¶ms)) < 0) { + snd_rawmidi_kernel_release(&msynth->output_rfile); + return err; + } + snd_midi_event_reset_decode(msynth->parser); + return 0; +} + +/* close associated midi device for output */ +static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct seq_midisynth *msynth = private_data; + + if (snd_BUG_ON(!msynth->output_rfile.output)) + return -EINVAL; + snd_rawmidi_drain_output(msynth->output_rfile.output); + return snd_rawmidi_kernel_release(&msynth->output_rfile); +} + +/* delete given midi synth port */ +static void snd_seq_midisynth_delete(struct seq_midisynth *msynth) +{ + if (msynth == NULL) + return; + + if (msynth->seq_client > 0) { + /* delete port */ + snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port); + } + + snd_midi_event_free(msynth->parser); +} + +/* register new midi synth port */ +static int +snd_seq_midisynth_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct seq_midisynth_client *client; + struct seq_midisynth *msynth, *ms; + struct snd_seq_port_info *port; + struct snd_rawmidi_info *info; + struct snd_rawmidi *rmidi = dev->private_data; + int newclient = 0; + unsigned int p, ports; + struct snd_seq_port_callback pcallbacks; + struct snd_card *card = dev->card; + int device = dev->device; + unsigned int input_count = 0, output_count = 0; + + if (snd_BUG_ON(!card || device < 0 || device >= SNDRV_RAWMIDI_DEVICES)) + return -EINVAL; + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + info->device = device; + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + info->subdevice = 0; + if (snd_rawmidi_info_select(card, info) >= 0) + output_count = info->subdevices_count; + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + if (snd_rawmidi_info_select(card, info) >= 0) { + input_count = info->subdevices_count; + } + ports = output_count; + if (ports < input_count) + ports = input_count; + if (ports == 0) { + kfree(info); + return -ENODEV; + } + if (ports > (256 / SNDRV_RAWMIDI_DEVICES)) + ports = 256 / SNDRV_RAWMIDI_DEVICES; + + mutex_lock(®ister_mutex); + client = synths[card->number]; + if (client == NULL) { + newclient = 1; + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (client == NULL) { + mutex_unlock(®ister_mutex); + kfree(info); + return -ENOMEM; + } + client->seq_client = + snd_seq_create_kernel_client( + card, 0, "%s", card->shortname[0] ? + (const char *)card->shortname : "External MIDI"); + if (client->seq_client < 0) { + kfree(client); + mutex_unlock(®ister_mutex); + kfree(info); + return -ENOMEM; + } + } + + msynth = kcalloc(ports, sizeof(struct seq_midisynth), GFP_KERNEL); + port = kmalloc(sizeof(*port), GFP_KERNEL); + if (msynth == NULL || port == NULL) + goto __nomem; + + for (p = 0; p < ports; p++) { + ms = &msynth[p]; + + if (snd_seq_midisynth_new(ms, card, device, p) < 0) + goto __nomem; + + /* declare port */ + memset(port, 0, sizeof(*port)); + port->addr.client = client->seq_client; + port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + memset(info, 0, sizeof(*info)); + info->device = device; + if (p < output_count) + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + else + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + info->subdevice = p; + if (snd_rawmidi_info_select(card, info) >= 0) + strcpy(port->name, info->subname); + if (! port->name[0]) { + if (info->name[0]) { + if (ports > 1) + snprintf(port->name, sizeof(port->name), "%s-%u", info->name, p); + else + snprintf(port->name, sizeof(port->name), "%s", info->name); + } else { + /* last resort */ + if (ports > 1) + sprintf(port->name, "MIDI %d-%d-%u", card->number, device, p); + else + sprintf(port->name, "MIDI %d-%d", card->number, device); + } + } + if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count) + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count) + port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) && + info->flags & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_HARDWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = ms; + pcallbacks.subscribe = midisynth_subscribe; + pcallbacks.unsubscribe = midisynth_unsubscribe; + pcallbacks.use = midisynth_use; + pcallbacks.unuse = midisynth_unuse; + pcallbacks.event_input = event_process_midi; + port->kernel = &pcallbacks; + if (rmidi->ops && rmidi->ops->get_port_info) + rmidi->ops->get_port_info(rmidi, p, port); + if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0) + goto __nomem; + ms->seq_client = client->seq_client; + ms->seq_port = port->addr.port; + } + client->ports_per_device[device] = ports; + client->ports[device] = msynth; + client->num_ports++; + if (newclient) + synths[card->number] = client; + mutex_unlock(®ister_mutex); + kfree(info); + kfree(port); + return 0; /* success */ + + __nomem: + if (msynth != NULL) { + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + } + if (newclient) { + snd_seq_delete_kernel_client(client->seq_client); + kfree(client); + } + kfree(info); + kfree(port); + mutex_unlock(®ister_mutex); + return -ENOMEM; +} + +/* release midi synth port */ +static int +snd_seq_midisynth_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct seq_midisynth_client *client; + struct seq_midisynth *msynth; + struct snd_card *card = dev->card; + int device = dev->device, p, ports; + + mutex_lock(®ister_mutex); + client = synths[card->number]; + if (client == NULL || client->ports[device] == NULL) { + mutex_unlock(®ister_mutex); + return -ENODEV; + } + ports = client->ports_per_device[device]; + client->ports_per_device[device] = 0; + msynth = client->ports[device]; + client->ports[device] = NULL; + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + client->num_ports--; + if (client->num_ports <= 0) { + snd_seq_delete_kernel_client(client->seq_client); + synths[card->number] = NULL; + kfree(client); + } + mutex_unlock(®ister_mutex); + return 0; +} + +static struct snd_seq_driver seq_midisynth_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_midisynth_probe, + .remove = snd_seq_midisynth_remove, + }, + .id = SNDRV_SEQ_DEV_ID_MIDISYNTH, + .argsize = 0, +}; + +module_snd_seq_driver(seq_midisynth_driver); diff --git a/sound/core/seq/seq_midi_emul.c b/sound/core/seq/seq_midi_emul.c new file mode 100644 index 000000000..c1975dd31 --- /dev/null +++ b/sound/core/seq/seq_midi_emul.c @@ -0,0 +1,730 @@ +/* + * GM/GS/XG midi module. + * + * Copyright (C) 1999 Steve Ratcliffe + * + * Based on awe_wave.c by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * This module is used to keep track of the current midi state. + * It can be used for drivers that are required to emulate midi when + * the hardware doesn't. + * + * It was written for a AWE64 driver, but there should be no AWE specific + * code in here. If there is it should be reported as a bug. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation."); +MODULE_LICENSE("GPL"); + +/* Prototypes for static functions */ +static void note_off(struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + int note, int vel); +static void do_control(struct snd_midi_op *ops, void *private, + struct snd_midi_channel_set *chset, + struct snd_midi_channel *chan, + int control, int value); +static void rpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); +static void nrpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); +static void sysex(struct snd_midi_op *ops, void *private, unsigned char *sysex, + int len, struct snd_midi_channel_set *chset); +static void all_sounds_off(struct snd_midi_op *ops, void *private, + struct snd_midi_channel *chan); +static void all_notes_off(struct snd_midi_op *ops, void *private, + struct snd_midi_channel *chan); +static void snd_midi_reset_controllers(struct snd_midi_channel *chan); +static void reset_all_channels(struct snd_midi_channel_set *chset); + + +/* + * Process an event in a driver independent way. This means dealing + * with RPN, NRPN, SysEx etc that are defined for common midi applications + * such as GM, GS and XG. + * There modes that this module will run in are: + * Generic MIDI - no interpretation at all, it will just save current values + * of controllers etc. + * GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN, + * SysEx will be interpreded as defined in General Midi. + * GS - You can use all gs_ prefixed elements of chan. Codes for GS will be + * interpreted. + * XG - You can use all xg_ prefixed elements of chan. Codes for XG will + * be interpreted. + */ +void +snd_midi_process_event(struct snd_midi_op *ops, + struct snd_seq_event *ev, + struct snd_midi_channel_set *chanset) +{ + struct snd_midi_channel *chan; + void *drv; + int dest_channel = 0; + + if (ev == NULL || chanset == NULL) { + pr_debug("ALSA: seq_midi_emul: ev or chanbase NULL (snd_midi_process_event)\n"); + return; + } + if (chanset->channels == NULL) + return; + + if (snd_seq_ev_is_channel_type(ev)) { + dest_channel = ev->data.note.channel; + if (dest_channel >= chanset->max_channels) { + pr_debug("ALSA: seq_midi_emul: dest channel is %d, max is %d\n", + dest_channel, chanset->max_channels); + return; + } + } + + chan = chanset->channels + dest_channel; + drv = chanset->private_data; + + /* EVENT_NOTE should be processed before queued */ + if (ev->type == SNDRV_SEQ_EVENT_NOTE) + return; + + /* Make sure that we don't have a note on that should really be + * a note off */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0) + ev->type = SNDRV_SEQ_EVENT_NOTEOFF; + + /* Make sure the note is within array range */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON || + ev->type == SNDRV_SEQ_EVENT_NOTEOFF || + ev->type == SNDRV_SEQ_EVENT_KEYPRESS) { + if (ev->data.note.note >= 128) + return; + } + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) { + if (ops->note_off) + ops->note_off(drv, ev->data.note.note, 0, chan); + } + chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON; + if (ops->note_on) + ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_NOTEOFF: + if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON)) + break; + if (ops->note_off) + note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity); + break; + case SNDRV_SEQ_EVENT_KEYPRESS: + if (ops->key_press) + ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_CONTROLLER: + do_control(ops, drv, chanset, chan, + ev->data.control.param, ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_PGMCHANGE: + chan->midi_program = ev->data.control.value; + break; + case SNDRV_SEQ_EVENT_PITCHBEND: + chan->midi_pitchbend = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_PITCHBEND, chan); + break; + case SNDRV_SEQ_EVENT_CHANPRESS: + chan->midi_pressure = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan); + break; + case SNDRV_SEQ_EVENT_CONTROL14: + /* Best guess is that this is any of the 14 bit controller values */ + if (ev->data.control.param < 32) { + /* set low part first */ + chan->control[ev->data.control.param + 32] = + ev->data.control.value & 0x7f; + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ((ev->data.control.value>>7) & 0x7f)); + } else + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_NONREGPARAM: + /* Break it back into its controller values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + nrpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_REGPARAM: + /* Break it back into its controller values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + rpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_SYSEX: + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + unsigned char sysexbuf[64]; + int len; + len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0); + if (len > 0) + sysex(ops, drv, sysexbuf, len, chanset); + } + break; + case SNDRV_SEQ_EVENT_SONGPOS: + case SNDRV_SEQ_EVENT_SONGSEL: + case SNDRV_SEQ_EVENT_CLOCK: + case SNDRV_SEQ_EVENT_START: + case SNDRV_SEQ_EVENT_CONTINUE: + case SNDRV_SEQ_EVENT_STOP: + case SNDRV_SEQ_EVENT_QFRAME: + case SNDRV_SEQ_EVENT_TEMPO: + case SNDRV_SEQ_EVENT_TIMESIGN: + case SNDRV_SEQ_EVENT_KEYSIGN: + goto not_yet; + case SNDRV_SEQ_EVENT_SENSING: + break; + case SNDRV_SEQ_EVENT_CLIENT_START: + case SNDRV_SEQ_EVENT_CLIENT_EXIT: + case SNDRV_SEQ_EVENT_CLIENT_CHANGE: + case SNDRV_SEQ_EVENT_PORT_START: + case SNDRV_SEQ_EVENT_PORT_EXIT: + case SNDRV_SEQ_EVENT_PORT_CHANGE: + case SNDRV_SEQ_EVENT_ECHO: + not_yet: + default: + /*pr_debug("ALSA: seq_midi_emul: Unimplemented event %d\n", ev->type);*/ + break; + } +} +EXPORT_SYMBOL(snd_midi_process_event); + + +/* + * release note + */ +static void +note_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + int note, int vel) +{ + if (chan->gm_hold) { + /* Hold this note until pedal is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) { + /* Mark this note as release; it will be turned off when sostenuto + * is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else { + chan->note[note] = 0; + if (ops->note_off) + ops->note_off(drv, note, vel, chan); + } +} + +/* + * Do all driver independent operations for this controller and pass + * events that need to take place immediately to the driver. + */ +static void +do_control(struct snd_midi_op *ops, void *drv, struct snd_midi_channel_set *chset, + struct snd_midi_channel *chan, int control, int value) +{ + int i; + + if (control >= ARRAY_SIZE(chan->control)) + return; + + /* Switches */ + if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) { + /* These are all switches; either off or on so set to 0 or 127 */ + value = (value >= 64)? 127: 0; + } + chan->control[control] = value; + + switch (control) { + case MIDI_CTL_SUSTAIN: + if (value == 0) { + /* Sustain has been released, turn off held notes */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + break; + case MIDI_CTL_PORTAMENTO: + break; + case MIDI_CTL_SOSTENUTO: + if (value) { + /* Mark each note that is currently held down */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_ON) + chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO; + } + } else { + /* release all notes that were held */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) { + chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO; + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + } + break; + case MIDI_CTL_MSB_DATA_ENTRY: + chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0; + /* fall through */ + case MIDI_CTL_LSB_DATA_ENTRY: + if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED) + rpn(ops, drv, chan, chset); + else + nrpn(ops, drv, chan, chset); + break; + case MIDI_CTL_REGIST_PARM_NUM_LSB: + case MIDI_CTL_REGIST_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + break; + case MIDI_CTL_NONREG_PARM_NUM_LSB: + case MIDI_CTL_NONREG_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + break; + + case MIDI_CTL_ALL_SOUNDS_OFF: + all_sounds_off(ops, drv, chan); + break; + + case MIDI_CTL_ALL_NOTES_OFF: + all_notes_off(ops, drv, chan); + break; + + case MIDI_CTL_MSB_BANK: + if (chset->midi_mode == SNDRV_MIDI_MODE_XG) { + if (value == 127) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } + break; + case MIDI_CTL_LSB_BANK: + break; + + case MIDI_CTL_RESET_CONTROLLERS: + snd_midi_reset_controllers(chan); + break; + + case MIDI_CTL_SOFT_PEDAL: + case MIDI_CTL_LEGATO_FOOTSWITCH: + case MIDI_CTL_HOLD2: + case MIDI_CTL_SC1_SOUND_VARIATION: + case MIDI_CTL_SC2_TIMBRE: + case MIDI_CTL_SC3_RELEASE_TIME: + case MIDI_CTL_SC4_ATTACK_TIME: + case MIDI_CTL_SC5_BRIGHTNESS: + case MIDI_CTL_E1_REVERB_DEPTH: + case MIDI_CTL_E2_TREMOLO_DEPTH: + case MIDI_CTL_E3_CHORUS_DEPTH: + case MIDI_CTL_E4_DETUNE_DEPTH: + case MIDI_CTL_E5_PHASER_DEPTH: + goto notyet; + notyet: + default: + if (ops->control) + ops->control(drv, control, chan); + break; + } +} + + +/* + * initialize the MIDI status + */ +void +snd_midi_channel_set_clear(struct snd_midi_channel_set *chset) +{ + int i; + + chset->midi_mode = SNDRV_MIDI_MODE_GM; + chset->gs_master_volume = 127; + + for (i = 0; i < chset->max_channels; i++) { + struct snd_midi_channel *chan = chset->channels + i; + memset(chan->note, 0, sizeof(chan->note)); + + chan->midi_aftertouch = 0; + chan->midi_pressure = 0; + chan->midi_program = 0; + chan->midi_pitchbend = 0; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (i == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} +EXPORT_SYMBOL(snd_midi_channel_set_clear); + +/* + * Process a rpn message. + */ +static void +rpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + int type; + int val; + + if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) { + type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) | + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]; + val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) | + chan->control[MIDI_CTL_LSB_DATA_ENTRY]; + + switch (type) { + case 0x0000: /* Pitch bend sensitivity */ + /* MSB only / 1 semitone per 128 */ + chan->gm_rpn_pitch_bend_range = val; + break; + + case 0x0001: /* fine tuning: */ + /* MSB/LSB, 8192=center, 100/8192 cent step */ + chan->gm_rpn_fine_tuning = val - 8192; + break; + + case 0x0002: /* coarse tuning */ + /* MSB only / 8192=center, 1 semitone per 128 */ + chan->gm_rpn_coarse_tuning = val - 8192; + break; + + case 0x7F7F: /* "lock-in" RPN */ + /* ignored */ + break; + } + } + /* should call nrpn or rpn callback here.. */ +} + +/* + * Process an nrpn message. + */ +static void +nrpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + /* parse XG NRPNs here if possible */ + if (ops->nrpn) + ops->nrpn(drv, chan, chset); +} + + +/* + * convert channel parameter in GS sysex + */ +static int +get_channel(unsigned char cmd) +{ + int p = cmd & 0x0f; + if (p == 0) + p = 9; + else if (p < 10) + p--; + return p; +} + + +/* + * Process a sysex message. + */ +static void +sysex(struct snd_midi_op *ops, void *private, unsigned char *buf, int len, + struct snd_midi_channel_set *chset) +{ + /* GM on */ + static unsigned char gm_on_macro[] = { + 0x7e,0x7f,0x09,0x01, + }; + /* XG on */ + static unsigned char xg_on_macro[] = { + 0x43,0x10,0x4c,0x00,0x00,0x7e,0x00, + }; + /* GS prefix + * drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off + * reverb mode: XX=0x01, YY=0x30, ZZ=0-7 + * chorus mode: XX=0x01, YY=0x38, ZZ=0-7 + * master vol: XX=0x00, YY=0x04, ZZ=0-127 + */ + static unsigned char gs_pfx_macro[] = { + 0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/ + }; + + int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED; + + if (len <= 0 || buf[0] != 0xf0) + return; + /* skip first byte */ + buf++; + len--; + + /* GM on */ + if (len >= (int)sizeof(gm_on_macro) && + memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) { + chset->midi_mode = SNDRV_MIDI_MODE_GM; + reset_all_channels(chset); + parsed = SNDRV_MIDI_SYSEX_GM_ON; + } + } + + /* GS macros */ + else if (len >= 8 && + memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) + chset->midi_mode = SNDRV_MIDI_MODE_GS; + + if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) { + /* GS reset */ + parsed = SNDRV_MIDI_SYSEX_GS_RESET; + reset_all_channels(chset); + } + + else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) { + /* drum pattern */ + int p = get_channel(buf[5]); + if (p < chset->max_channels) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + if (buf[7]) + chset->channels[p].drum_channel = 1; + else + chset->channels[p].drum_channel = 0; + } + + } else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) { + /* program */ + int p = get_channel(buf[5]); + if (p < chset->max_channels && + ! chset->channels[p].drum_channel) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + chset->channels[p].midi_program = buf[7]; + } + + } else if (buf[5] == 0x01 && buf[6] == 0x30) { + /* reverb mode */ + parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE; + chset->gs_reverb_mode = buf[7]; + + } else if (buf[5] == 0x01 && buf[6] == 0x38) { + /* chorus mode */ + parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE; + chset->gs_chorus_mode = buf[7]; + + } else if (buf[5] == 0x00 && buf[6] == 0x04) { + /* master volume */ + parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME; + chset->gs_master_volume = buf[7]; + + } + } + + /* XG on */ + else if (len >= (int)sizeof(xg_on_macro) && + memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) { + int i; + chset->midi_mode = SNDRV_MIDI_MODE_XG; + parsed = SNDRV_MIDI_SYSEX_XG_ON; + /* reset CC#0 for drums */ + for (i = 0; i < chset->max_channels; i++) { + if (chset->channels[i].drum_channel) + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127; + else + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0; + } + } + + if (ops->sysex) + ops->sysex(private, buf - 1, len + 1, parsed, chset); +} + +/* + * all sound off + */ +static void +all_sounds_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan) +{ + int n; + + if (! ops->note_terminate) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n]) { + ops->note_terminate(drv, n, chan); + chan->note[n] = 0; + } + } +} + +/* + * all notes off + */ +static void +all_notes_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan) +{ + int n; + + if (! ops->note_off) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n] == SNDRV_MIDI_NOTE_ON) + note_off(ops, drv, chan, n, 0); + } +} + +/* + * Initialise a single midi channel control block. + */ +static void snd_midi_channel_init(struct snd_midi_channel *p, int n) +{ + if (p == NULL) + return; + + memset(p, 0, sizeof(struct snd_midi_channel)); + p->private = NULL; + p->number = n; + + snd_midi_reset_controllers(p); + p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + p->gm_rpn_fine_tuning = 0; + p->gm_rpn_coarse_tuning = 0; + + if (n == 9) + p->drum_channel = 1; /* Default ch 10 as drums */ +} + +/* + * Allocate and initialise a set of midi channel control blocks. + */ +static struct snd_midi_channel *snd_midi_channel_init_set(int n) +{ + struct snd_midi_channel *chan; + int i; + + chan = kmalloc_array(n, sizeof(struct snd_midi_channel), GFP_KERNEL); + if (chan) { + for (i = 0; i < n; i++) + snd_midi_channel_init(chan+i, i); + } + + return chan; +} + +/* + * reset all midi channels + */ +static void +reset_all_channels(struct snd_midi_channel_set *chset) +{ + int ch; + for (ch = 0; ch < chset->max_channels; ch++) { + struct snd_midi_channel *chan = chset->channels + ch; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (ch == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} + + +/* + * Allocate and initialise a midi channel set. + */ +struct snd_midi_channel_set *snd_midi_channel_alloc_set(int n) +{ + struct snd_midi_channel_set *chset; + + chset = kmalloc(sizeof(*chset), GFP_KERNEL); + if (chset) { + chset->channels = snd_midi_channel_init_set(n); + chset->private_data = NULL; + chset->max_channels = n; + } + return chset; +} +EXPORT_SYMBOL(snd_midi_channel_alloc_set); + +/* + * Reset the midi controllers on a particular channel to default values. + */ +static void snd_midi_reset_controllers(struct snd_midi_channel *chan) +{ + memset(chan->control, 0, sizeof(chan->control)); + chan->gm_volume = 127; + chan->gm_expression = 127; + chan->gm_pan = 64; +} + + +/* + * Free a midi channel set. + */ +void snd_midi_channel_free_set(struct snd_midi_channel_set *chset) +{ + if (chset == NULL) + return; + kfree(chset->channels); + kfree(chset); +} +EXPORT_SYMBOL(snd_midi_channel_free_set); diff --git a/sound/core/seq/seq_midi_event.c b/sound/core/seq/seq_midi_event.c new file mode 100644 index 000000000..b11419537 --- /dev/null +++ b/sound/core/seq/seq_midi_event.c @@ -0,0 +1,472 @@ +/* + * MIDI byte <-> sequencer event coder + * + * Copyright (C) 1998,99 Takashi Iwai , + * Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai , Jaroslav Kysela "); +MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder"); +MODULE_LICENSE("GPL"); + +/* event type, index into status_event[] */ +/* from 0 to 6 are normal commands (note off, on, etc.) for 0x9?-0xe? */ +#define ST_INVALID 7 +#define ST_SPECIAL 8 +#define ST_SYSEX ST_SPECIAL +/* from 8 to 15 are events for 0xf0-0xf7 */ + + +/* + * prototypes + */ +static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void note_decode(struct snd_seq_event *ev, unsigned char *buf); +static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf); +static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf); +static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf); +static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf); + +/* + * event list + */ +static struct status_event_list { + int event; + int qlen; + void (*encode)(struct snd_midi_event *dev, struct snd_seq_event *ev); + void (*decode)(struct snd_seq_event *ev, unsigned char *buf); +} status_event[] = { + /* 0x80 - 0xef */ + {SNDRV_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_NOTEON, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode}, + {SNDRV_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode}, + /* invalid */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, + /* 0xf0 - 0xff */ + {SNDRV_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */ + {SNDRV_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */ + {SNDRV_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */ + {SNDRV_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf4 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf5 */ + {SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf7 */ + {SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf9 */ + {SNDRV_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */ + {SNDRV_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */ + {SNDRV_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xfd */ + {SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */ + {SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */ +}; + +static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, int len, + struct snd_seq_event *ev); +static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, int count, + struct snd_seq_event *ev); + +static struct extra_event_list { + int event; + int (*decode)(struct snd_midi_event *dev, unsigned char *buf, int len, + struct snd_seq_event *ev); +} extra_event[] = { + {SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14}, + {SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn}, + {SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn}, +}; + +/* + * new/delete record + */ + +int snd_midi_event_new(int bufsize, struct snd_midi_event **rdev) +{ + struct snd_midi_event *dev; + + *rdev = NULL; + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + if (bufsize > 0) { + dev->buf = kmalloc(bufsize, GFP_KERNEL); + if (dev->buf == NULL) { + kfree(dev); + return -ENOMEM; + } + } + dev->bufsize = bufsize; + dev->lastcmd = 0xff; + dev->type = ST_INVALID; + spin_lock_init(&dev->lock); + *rdev = dev; + return 0; +} +EXPORT_SYMBOL(snd_midi_event_new); + +void snd_midi_event_free(struct snd_midi_event *dev) +{ + if (dev != NULL) { + kfree(dev->buf); + kfree(dev); + } +} +EXPORT_SYMBOL(snd_midi_event_free); + +/* + * initialize record + */ +static inline void reset_encode(struct snd_midi_event *dev) +{ + dev->read = 0; + dev->qlen = 0; + dev->type = ST_INVALID; +} + +void snd_midi_event_reset_encode(struct snd_midi_event *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + reset_encode(dev); + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL(snd_midi_event_reset_encode); + +void snd_midi_event_reset_decode(struct snd_midi_event *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->lastcmd = 0xff; + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL(snd_midi_event_reset_decode); + +void snd_midi_event_no_status(struct snd_midi_event *dev, int on) +{ + dev->nostat = on ? 1 : 0; +} +EXPORT_SYMBOL(snd_midi_event_no_status); + +/* + * read one byte and encode to sequencer event: + * return true if MIDI bytes are encoded to an event + * false data is not finished + */ +bool snd_midi_event_encode_byte(struct snd_midi_event *dev, unsigned char c, + struct snd_seq_event *ev) +{ + bool rc = false; + unsigned long flags; + + if (c >= MIDI_CMD_COMMON_CLOCK) { + /* real-time event */ + ev->type = status_event[ST_SPECIAL + c - 0xf0].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + return ev->type != SNDRV_SEQ_EVENT_NONE; + } + + spin_lock_irqsave(&dev->lock, flags); + if ((c & 0x80) && + (c != MIDI_CMD_COMMON_SYSEX_END || dev->type != ST_SYSEX)) { + /* new command */ + dev->buf[0] = c; + if ((c & 0xf0) == 0xf0) /* system messages */ + dev->type = (c & 0x0f) + ST_SPECIAL; + else + dev->type = (c >> 4) & 0x07; + dev->read = 1; + dev->qlen = status_event[dev->type].qlen; + } else { + if (dev->qlen > 0) { + /* rest of command */ + dev->buf[dev->read++] = c; + if (dev->type != ST_SYSEX) + dev->qlen--; + } else { + /* running status */ + dev->buf[1] = c; + dev->qlen = status_event[dev->type].qlen - 1; + dev->read = 2; + } + } + if (dev->qlen == 0) { + ev->type = status_event[dev->type].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (status_event[dev->type].encode) /* set data values */ + status_event[dev->type].encode(dev, ev); + if (dev->type >= ST_SPECIAL) + dev->type = ST_INVALID; + rc = true; + } else if (dev->type == ST_SYSEX) { + if (c == MIDI_CMD_COMMON_SYSEX_END || + dev->read >= dev->bufsize) { + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->data.ext.len = dev->read; + ev->data.ext.ptr = dev->buf; + if (c != MIDI_CMD_COMMON_SYSEX_END) + dev->read = 0; /* continue to parse */ + else + reset_encode(dev); /* all parsed */ + rc = true; + } + } + + spin_unlock_irqrestore(&dev->lock, flags); + return rc; +} +EXPORT_SYMBOL(snd_midi_event_encode_byte); + +/* encode note event */ +static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.note.channel = dev->buf[0] & 0x0f; + ev->data.note.note = dev->buf[1]; + ev->data.note.velocity = dev->buf[2]; +} + +/* encode one parameter controls */ +static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = dev->buf[1]; +} + +/* encode pitch wheel change */ +static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192; +} + +/* encode midi control change */ +static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.param = dev->buf[1]; + ev->data.control.value = dev->buf[2]; +} + +/* encode one parameter value*/ +static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.value = dev->buf[1]; +} + +/* encode song position */ +static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1]; +} + +/* + * decode from a sequencer event to midi bytes + * return the size of decoded midi events + */ +long snd_midi_event_decode(struct snd_midi_event *dev, unsigned char *buf, long count, + struct snd_seq_event *ev) +{ + unsigned int cmd, type; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return -ENOENT; + + for (type = 0; type < ARRAY_SIZE(status_event); type++) { + if (ev->type == status_event[type].event) + goto __found; + } + for (type = 0; type < ARRAY_SIZE(extra_event); type++) { + if (ev->type == extra_event[type].event) + return extra_event[type].decode(dev, buf, count, ev); + } + return -ENOENT; + + __found: + if (type >= ST_SPECIAL) + cmd = 0xf0 + (type - ST_SPECIAL); + else + /* data.note.channel and data.control.channel is identical */ + cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f); + + + if (cmd == MIDI_CMD_COMMON_SYSEX) { + snd_midi_event_reset_decode(dev); + return snd_seq_expand_var_event(ev, count, buf, 1, 0); + } else { + int qlen; + unsigned char xbuf[4]; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) { + dev->lastcmd = cmd; + spin_unlock_irqrestore(&dev->lock, flags); + xbuf[0] = cmd; + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 1); + qlen = status_event[type].qlen + 1; + } else { + spin_unlock_irqrestore(&dev->lock, flags); + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 0); + qlen = status_event[type].qlen; + } + if (count < qlen) + return -ENOMEM; + memcpy(buf, xbuf, qlen); + return qlen; + } +} +EXPORT_SYMBOL(snd_midi_event_decode); + + +/* decode note event */ +static void note_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.note.note & 0x7f; + buf[1] = ev->data.note.velocity & 0x7f; +} + +/* decode one parameter controls */ +static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; +} + +/* decode pitch wheel change */ +static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + int value = ev->data.control.value + 8192; + buf[0] = value & 0x7f; + buf[1] = (value >> 7) & 0x7f; +} + +/* decode midi control change */ +static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.param & 0x7f; + buf[1] = ev->data.control.value & 0x7f; +} + +/* decode song position */ +static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; + buf[1] = (ev->data.control.value >> 7) & 0x7f; +} + +/* decode 14bit control */ +static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, + int count, struct snd_seq_event *ev) +{ + unsigned char cmd; + int idx = 0; + + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + if (ev->data.control.param < 0x20) { + if (count < 4) + return -ENOMEM; + if (dev->nostat && count < 6) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 5) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param; + buf[idx++] = (ev->data.control.value >> 7) & 0x7f; + if (dev->nostat) + buf[idx++] = cmd; + buf[idx++] = ev->data.control.param + 0x20; + buf[idx++] = ev->data.control.value & 0x7f; + } else { + if (count < 2) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 3) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param & 0x7f; + buf[idx++] = ev->data.control.value & 0x7f; + } + return idx; +} + +/* decode reg/nonreg param */ +static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, + int count, struct snd_seq_event *ev) +{ + unsigned char cmd; + char *cbytes; + static char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB, + MIDI_CTL_NONREG_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + static char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB, + MIDI_CTL_REGIST_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + unsigned char bytes[4]; + int idx = 0, i; + + if (count < 8) + return -ENOMEM; + if (dev->nostat && count < 12) + return -ENOMEM; + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + bytes[0] = (ev->data.control.param & 0x3f80) >> 7; + bytes[1] = ev->data.control.param & 0x007f; + bytes[2] = (ev->data.control.value & 0x3f80) >> 7; + bytes[3] = ev->data.control.value & 0x007f; + if (cmd != dev->lastcmd && !dev->nostat) { + if (count < 9) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn; + for (i = 0; i < 4; i++) { + if (dev->nostat) + buf[idx++] = dev->lastcmd = cmd; + buf[idx++] = cbytes[i]; + buf[idx++] = bytes[i]; + } + return idx; +} diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c new file mode 100644 index 000000000..efecb8b8e --- /dev/null +++ b/sound/core/seq/seq_ports.c @@ -0,0 +1,729 @@ +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol + * Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include "seq_system.h" +#include "seq_ports.h" +#include "seq_clientmgr.h" + +/* + + registration of client ports + + */ + + +/* + +NOTE: the current implementation of the port structure as a linked list is +not optimal for clients that have many ports. For sending messages to all +subscribers of a port we first need to find the address of the port +structure, which means we have to traverse the list. A direct access table +(array) would be better, but big preallocated arrays waste memory. + +Possible actions: + +1) leave it this way, a client does normaly does not have more than a few +ports + +2) replace the linked list of ports by a array of pointers which is +dynamicly kmalloced. When a port is added or deleted we can simply allocate +a new array, copy the corresponding pointers, and delete the old one. We +then only need a pointer to this array, and an integer that tells us how +much elements are in array. + +*/ + +/* return pointer to port structure - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + int num) +{ + struct snd_seq_client_port *port; + + if (client == NULL) + return NULL; + read_lock(&client->ports_lock); + list_for_each_entry(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ + snd_use_lock_use(&port->use_lock); + read_unlock(&client->ports_lock); + return port; + } + } + read_unlock(&client->ports_lock); + return NULL; /* not found */ +} + + +/* search for the next port - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, + struct snd_seq_port_info *pinfo) +{ + int num; + struct snd_seq_client_port *port, *found; + + num = pinfo->addr.port; + found = NULL; + read_lock(&client->ports_lock); + list_for_each_entry(port, &client->ports_list_head, list) { + if (port->addr.port < num) + continue; + if (port->addr.port == num) { + found = port; + break; + } + if (found == NULL || port->addr.port < found->addr.port) + found = port; + } + if (found) { + if (found->closing) + found = NULL; + else + snd_use_lock_use(&found->use_lock); + } + read_unlock(&client->ports_lock); + return found; +} + + +/* initialize snd_seq_port_subs_info */ +static void port_subs_info_init(struct snd_seq_port_subs_info *grp) +{ + INIT_LIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; + rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +} + + +/* create a port, port number is returned (-1 on failure); + * the caller needs to unref the port via snd_seq_port_unlock() appropriately + */ +struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, + int port) +{ + unsigned long flags; + struct snd_seq_client_port *new_port, *p; + int num = -1; + + /* sanity check */ + if (snd_BUG_ON(!client)) + return NULL; + + if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) { + pr_warn("ALSA: seq: too many ports for client %d\n", client->number); + return NULL; + } + + /* create a new port */ + new_port = kzalloc(sizeof(*new_port), GFP_KERNEL); + if (!new_port) + return NULL; /* failure, out of memory */ + /* init port data */ + new_port->addr.client = client->number; + new_port->addr.port = -1; + new_port->owner = THIS_MODULE; + sprintf(new_port->name, "port-%d", num); + snd_use_lock_init(&new_port->use_lock); + port_subs_info_init(&new_port->c_src); + port_subs_info_init(&new_port->c_dest); + snd_use_lock_use(&new_port->use_lock); + + num = port >= 0 ? port : 0; + mutex_lock(&client->ports_mutex); + write_lock_irqsave(&client->ports_lock, flags); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port > num) + break; + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } + /* insert the new port */ + list_add_tail(&new_port->list, &p->list); + client->num_ports++; + new_port->addr.port = num; /* store the port number in the port */ + sprintf(new_port->name, "port-%d", num); + write_unlock_irqrestore(&client->ports_lock, flags); + mutex_unlock(&client->ports_mutex); + + return new_port; +} + +/* */ +static int subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, int send_ack); +static int unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, int send_ack); + + +static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr, + struct snd_seq_client **cp) +{ + struct snd_seq_client_port *p; + *cp = snd_seq_client_use_ptr(addr->client); + if (*cp) { + p = snd_seq_port_use_ptr(*cp, addr->port); + if (! p) { + snd_seq_client_unlock(*cp); + *cp = NULL; + } + return p; + } + return NULL; +} + +static void delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack); + +static inline struct snd_seq_subscribers * +get_subscriber(struct list_head *p, bool is_src) +{ + if (is_src) + return list_entry(p, struct snd_seq_subscribers, src_list); + else + return list_entry(p, struct snd_seq_subscribers, dest_list); +} + +/* + * remove all subscribers on the list + * this is called from port_delete, for each src and dest list. + */ +static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + int is_src) +{ + struct list_head *p, *n; + + list_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + struct snd_seq_client *c; + struct snd_seq_client_port *aport; + + subs = get_subscriber(p, is_src); + if (is_src) + aport = get_client_port(&subs->info.dest, &c); + else + aport = get_client_port(&subs->info.sender, &c); + delete_and_unsubscribe_port(client, port, subs, is_src, false); + + if (!aport) { + /* looks like the connected port is being deleted. + * we decrease the counter, and when both ports are deleted + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) + kfree(subs); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); + kfree(subs); + snd_seq_port_unlock(aport); + snd_seq_client_unlock(c); + } +} + +/* delete port data */ +static int port_delete(struct snd_seq_client *client, + struct snd_seq_client_port *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); + clear_subscriber_list(client, port, &port->c_dest, false); + + if (port->private_free) + port->private_free(port->private_data); + + snd_BUG_ON(port->c_src.count != 0); + snd_BUG_ON(port->c_dest.count != 0); + + kfree(port); + return 0; +} + + +/* delete a port with the given port id */ +int snd_seq_delete_port(struct snd_seq_client *client, int port) +{ + unsigned long flags; + struct snd_seq_client_port *found = NULL, *p; + + mutex_lock(&client->ports_mutex); + write_lock_irqsave(&client->ports_lock, flags); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ + list_del(&p->list); + client->num_ports--; + found = p; + break; + } + } + write_unlock_irqrestore(&client->ports_lock, flags); + mutex_unlock(&client->ports_mutex); + if (found) + return port_delete(client, found); + else + return -ENOENT; +} + +/* delete the all ports belonging to the given client */ +int snd_seq_delete_all_ports(struct snd_seq_client *client) +{ + unsigned long flags; + struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; + + /* move the port list to deleted_list, and + * clear the port list in the client data. + */ + mutex_lock(&client->ports_mutex); + write_lock_irqsave(&client->ports_lock, flags); + if (! list_empty(&client->ports_list_head)) { + list_add(&deleted_list, &client->ports_list_head); + list_del_init(&client->ports_list_head); + } else { + INIT_LIST_HEAD(&deleted_list); + } + client->num_ports = 0; + write_unlock_irqrestore(&client->ports_lock, flags); + + /* remove each port in deleted_list */ + list_for_each_entry_safe(port, tmp, &deleted_list, list) { + list_del(&port->list); + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } + mutex_unlock(&client->ports_mutex); + return 0; +} + +/* set port info fields */ +int snd_seq_set_port_info(struct snd_seq_client_port * port, + struct snd_seq_port_info * info) +{ + if (snd_BUG_ON(!port || !info)) + return -EINVAL; + + /* set port name */ + if (info->name[0]) + strlcpy(port->name, info->name, sizeof(port->name)); + + /* set capabilities */ + port->capability = info->capability; + + /* get port type */ + port->type = info->type; + + /* information about supported channels/voices */ + port->midi_channels = info->midi_channels; + port->midi_voices = info->midi_voices; + port->synth_voices = info->synth_voices; + + /* timestamping */ + port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0; + port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; + port->time_queue = info->time_queue; + + return 0; +} + +/* get port info fields */ +int snd_seq_get_port_info(struct snd_seq_client_port * port, + struct snd_seq_port_info * info) +{ + if (snd_BUG_ON(!port || !info)) + return -EINVAL; + + /* get port name */ + strlcpy(info->name, port->name, sizeof(info->name)); + + /* get capabilities */ + info->capability = port->capability; + + /* get port type */ + info->type = port->type; + + /* information about supported channels/voices */ + info->midi_channels = port->midi_channels; + info->midi_voices = port->midi_voices; + info->synth_voices = port->synth_voices; + + /* get subscriber counts */ + info->read_use = port->c_src.count; + info->write_use = port->c_dest.count; + + /* timestamping */ + info->flags = 0; + if (port->timestamping) { + info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP; + if (port->time_real) + info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL; + info->time_queue = port->time_queue; + } + + return 0; +} + + + +/* + * call callback functions (if any): + * the callbacks are invoked only when the first (for connection) or + * the last subscription (for disconnection) is done. Second or later + * subscription results in increment of counter, but no callback is + * invoked. + * This feature is useful if these callbacks are associated with + * initialization or termination of devices (see seq_midi.c). + */ + +static int subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, + int send_ack) +{ + int err = 0; + + if (!try_module_get(port->owner)) + return -EFAULT; + grp->count++; + if (grp->open && grp->count == 1) { + err = grp->open(port->private_data, info); + if (err < 0) { + module_put(port->owner); + grp->count--; + } + } + if (err >= 0 && send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + + return err; +} + +static int unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, + int send_ack) +{ + int err = 0; + + if (! grp->count) + return -EINVAL; + grp->count--; + if (grp->close && grp->count == 0) + err = grp->close(port->private_data, info); + if (send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + module_put(port->owner); + return err; +} + + + +/* check if both addresses are identical */ +static inline int addr_match(struct snd_seq_addr *r, struct snd_seq_addr *s) +{ + return (r->client == s->client) && (r->port == s->port); +} + +/* check the two subscribe info match */ +/* if flags is zero, checks only sender and destination addresses */ +static int match_subs_info(struct snd_seq_port_subscribe *r, + struct snd_seq_port_subscribe *s) +{ + if (addr_match(&r->sender, &s->sender) && + addr_match(&r->dest, &s->dest)) { + if (r->flags && r->flags == s->flags) + return r->queue == s->queue; + else if (! r->flags) + return 1; + } + return 0; +} + +static int check_and_subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool exclusive, bool ack) +{ + struct snd_seq_port_subs_info *grp; + struct list_head *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + err = -EBUSY; + down_write(&grp->list_mutex); + if (exclusive) { + if (!list_empty(&grp->list_head)) + goto __error; + } else { + if (grp->exclusive) + goto __error; + /* check whether already exists */ + list_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + goto __error; + } + } + + err = subscribe_port(client, port, grp, &subs->info, ack); + if (err < 0) { + grp->exclusive = 0; + goto __error; + } + + /* add to list */ + write_lock_irq(&grp->list_lock); + if (is_src) + list_add_tail(&subs->src_list, &grp->list_head); + else + list_add_tail(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + write_unlock_irq(&grp->list_lock); + err = 0; + + __error: + up_write(&grp->list_mutex); + return err; +} + +/* called with grp->list_mutex held */ +static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack) +{ + struct snd_seq_port_subs_info *grp; + struct list_head *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; + write_lock_irq(&grp->list_lock); + empty = list_empty(list); + if (!empty) + list_del_init(list); + grp->exclusive = 0; + write_unlock_irq(&grp->list_lock); + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +} + +static void delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack) +{ + struct snd_seq_port_subs_info *grp; + + grp = is_src ? &port->c_src : &port->c_dest; + down_write(&grp->list_mutex); + __delete_and_unsubscribe_port(client, port, subs, is_src, ack); + up_write(&grp->list_mutex); +} + +/* connect two ports */ +int snd_seq_port_connect(struct snd_seq_client *connector, + struct snd_seq_client *src_client, + struct snd_seq_client_port *src_port, + struct snd_seq_client *dest_client, + struct snd_seq_client_port *dest_port, + struct snd_seq_port_subscribe *info) +{ + struct snd_seq_subscribers *subs; + bool exclusive; + int err; + + subs = kzalloc(sizeof(*subs), GFP_KERNEL); + if (!subs) + return -ENOMEM; + + subs->info = *info; + atomic_set(&subs->ref_count, 0); + INIT_LIST_HEAD(&subs->src_list); + INIT_LIST_HEAD(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + + err = check_and_subscribe_port(src_client, src_port, subs, true, + exclusive, + connector->number != src_client->number); + if (err < 0) + goto error; + err = check_and_subscribe_port(dest_client, dest_port, subs, false, + exclusive, + connector->number != dest_client->number); + if (err < 0) + goto error_dest; + + return 0; + + error_dest: + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: + kfree(subs); + return err; +} + +/* remove the connection */ +int snd_seq_port_disconnect(struct snd_seq_client *connector, + struct snd_seq_client *src_client, + struct snd_seq_client_port *src_port, + struct snd_seq_client *dest_client, + struct snd_seq_client_port *dest_port, + struct snd_seq_port_subscribe *info) +{ + struct snd_seq_port_subs_info *dest = &dest_port->c_dest; + struct snd_seq_subscribers *subs; + int err = -ENOENT; + + /* always start from deleting the dest port for avoiding concurrent + * deletions + */ + down_write(&dest->list_mutex); + /* look for the connection */ + list_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, + connector->number != dest_client->number); + err = 0; + break; + } + } + up_write(&dest->list_mutex); + if (err < 0) + return err; + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + kfree(subs); + return 0; +} + + +/* get matched subscriber */ +int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + struct snd_seq_addr *dest_addr, + struct snd_seq_port_subscribe *subs) +{ + struct snd_seq_subscribers *s; + int err = -ENOENT; + + down_read(&src_grp->list_mutex); + list_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; + break; + } + } + up_read(&src_grp->list_mutex); + return err; +} + +/* + * Attach a device driver that wants to receive events from the + * sequencer. Returns the new port number on success. + * A driver that wants to receive the events converted to midi, will + * use snd_seq_midisynth_register_port(). + */ +/* exported */ +int snd_seq_event_port_attach(int client, + struct snd_seq_port_callback *pcbp, + int cap, int type, int midi_channels, + int midi_voices, char *portname) +{ + struct snd_seq_port_info portinfo; + int ret; + + /* Set up the port */ + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + strlcpy(portinfo.name, portname ? portname : "Unnamed port", + sizeof(portinfo.name)); + + portinfo.capability = cap; + portinfo.type = type; + portinfo.kernel = pcbp; + portinfo.midi_channels = midi_channels; + portinfo.midi_voices = midi_voices; + + /* Create it */ + ret = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + &portinfo); + + if (ret >= 0) + ret = portinfo.addr.port; + + return ret; +} +EXPORT_SYMBOL(snd_seq_event_port_attach); + +/* + * Detach the driver from a port. + */ +/* exported */ +int snd_seq_event_port_detach(int client, int port) +{ + struct snd_seq_port_info portinfo; + int err; + + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + portinfo.addr.port = port; + err = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_DELETE_PORT, + &portinfo); + + return err; +} +EXPORT_SYMBOL(snd_seq_event_port_detach); diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h new file mode 100644 index 000000000..06003b366 --- /dev/null +++ b/sound/core/seq/seq_ports.h @@ -0,0 +1,142 @@ +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_PORTS_H +#define __SND_SEQ_PORTS_H + +#include +#include "seq_lock.h" + +/* list of 'exported' ports */ + +/* Client ports that are not exported are still accessible, but are + anonymous ports. + + If a port supports SUBSCRIPTION, that port can send events to all + subscribersto a special address, with address + (queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all + recipients that are registered in the subscription list. A typical + application for these SUBSCRIPTION events is handling of incoming MIDI + data. The port doesn't 'know' what other clients are interested in this + message. If for instance a MIDI recording application would like to receive + the events from that port, it will first have to subscribe with that port. + +*/ + +struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ + struct list_head src_list; /* link of sources */ + struct list_head dest_list; /* link of destinations */ + atomic_t ref_count; +}; + +struct snd_seq_port_subs_info { + struct list_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; + rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); +}; + +struct snd_seq_client_port { + + struct snd_seq_addr addr; /* client/port number */ + struct module *owner; /* owner of this port */ + char name[64]; /* port name */ + struct list_head list; /* port list */ + snd_use_lock_t use_lock; + + /* subscribers */ + struct snd_seq_port_subs_info c_src; /* read (sender) list */ + struct snd_seq_port_subs_info c_dest; /* write (dest) list */ + + int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop); + void (*private_free)(void *private_data); + void *private_data; + unsigned int closing : 1; + unsigned int timestamping: 1; + unsigned int time_real: 1; + int time_queue; + + /* capability, inport, output, sync */ + unsigned int capability; /* port capability bits */ + unsigned int type; /* port type bits */ + + /* supported channels */ + int midi_channels; + int midi_voices; + int synth_voices; + +}; + +struct snd_seq_client; + +/* return pointer to port structure and lock port */ +struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, int num); + +/* search for next port - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, + struct snd_seq_port_info *pinfo); + +/* unlock the port */ +#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock) + +/* create a port, port number is returned (-1 on failure) */ +struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, int port_index); + +/* delete a port */ +int snd_seq_delete_port(struct snd_seq_client *client, int port); + +/* delete all ports */ +int snd_seq_delete_all_ports(struct snd_seq_client *client); + +/* set port info fields */ +int snd_seq_set_port_info(struct snd_seq_client_port *port, + struct snd_seq_port_info *info); + +/* get port info fields */ +int snd_seq_get_port_info(struct snd_seq_client_port *port, + struct snd_seq_port_info *info); + +/* add subscriber to subscription list */ +int snd_seq_port_connect(struct snd_seq_client *caller, + struct snd_seq_client *s, struct snd_seq_client_port *sp, + struct snd_seq_client *d, struct snd_seq_client_port *dp, + struct snd_seq_port_subscribe *info); + +/* remove subscriber from subscription list */ +int snd_seq_port_disconnect(struct snd_seq_client *caller, + struct snd_seq_client *s, struct snd_seq_client_port *sp, + struct snd_seq_client *d, struct snd_seq_client_port *dp, + struct snd_seq_port_subscribe *info); + +/* subscribe port */ +int snd_seq_port_subscribe(struct snd_seq_client_port *port, + struct snd_seq_port_subscribe *info); + +/* get matched subscriber */ +int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + struct snd_seq_addr *dest_addr, + struct snd_seq_port_subscribe *subs); + +#endif diff --git a/sound/core/seq/seq_prioq.c b/sound/core/seq/seq_prioq.c new file mode 100644 index 000000000..2bc6759e4 --- /dev/null +++ b/sound/core/seq/seq_prioq.c @@ -0,0 +1,451 @@ +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998-1999 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include "seq_timer.h" +#include "seq_prioq.h" + + +/* Implementation is a simple linked list for now... + + This priority queue orders the events on timestamp. For events with an + equeal timestamp the queue behaves as a FIFO. + + * + * +-------+ + * Head --> | first | + * +-------+ + * |next + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * Tail --> | last | + * +-------+ + * + + */ + + + +/* create new prioq (constructor) */ +struct snd_seq_prioq *snd_seq_prioq_new(void) +{ + struct snd_seq_prioq *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + spin_lock_init(&f->lock); + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +/* delete prioq (destructor) */ +void snd_seq_prioq_delete(struct snd_seq_prioq **fifo) +{ + struct snd_seq_prioq *f = *fifo; + *fifo = NULL; + + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_delete() called with NULL prioq\n"); + return; + } + + /* release resources...*/ + /*....................*/ + + if (f->cells > 0) { + /* drain prioQ */ + while (f->cells > 0) + snd_seq_cell_free(snd_seq_prioq_cell_out(f, NULL)); + } + + kfree(f); +} + + + + +/* compare timestamp between events */ +/* return 1 if a >= b; 0 */ +static inline int compare_timestamp(struct snd_seq_event *a, + struct snd_seq_event *b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick)); + } else { + /* compare real time */ + return (snd_seq_compare_real_time(&a->time.time, &b->time.time)); + } +} + +/* compare timestamp between events */ +/* return negative if a < b; + * zero if a = b; + * positive if a > b; + */ +static inline int compare_timestamp_rel(struct snd_seq_event *a, + struct snd_seq_event *b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + if (a->time.tick > b->time.tick) + return 1; + else if (a->time.tick == b->time.tick) + return 0; + else + return -1; + } else { + /* compare real time */ + if (a->time.time.tv_sec > b->time.time.tv_sec) + return 1; + else if (a->time.time.tv_sec == b->time.time.tv_sec) { + if (a->time.time.tv_nsec > b->time.time.tv_nsec) + return 1; + else if (a->time.time.tv_nsec == b->time.time.tv_nsec) + return 0; + else + return -1; + } else + return -1; + } +} + +/* enqueue cell to prioq */ +int snd_seq_prioq_cell_in(struct snd_seq_prioq * f, + struct snd_seq_event_cell * cell) +{ + struct snd_seq_event_cell *cur, *prev; + unsigned long flags; + int count; + int prior; + + if (snd_BUG_ON(!f || !cell)) + return -EINVAL; + + /* check flags */ + prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK); + + spin_lock_irqsave(&f->lock, flags); + + /* check if this element needs to inserted at the end (ie. ordered + data is inserted) This will be very likeley if a sequencer + application or midi file player is feeding us (sequential) data */ + if (f->tail && !prior) { + if (compare_timestamp(&cell->event, &f->tail->event)) { + /* add new cell to tail of the fifo */ + f->tail->next = cell; + f->tail = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; + } + } + /* traverse list of elements to find the place where the new cell is + to be inserted... Note that this is a order n process ! */ + + prev = NULL; /* previous cell */ + cur = f->head; /* cursor */ + + count = 10000; /* FIXME: enough big, isn't it? */ + while (cur != NULL) { + /* compare timestamps */ + int rel = compare_timestamp_rel(&cell->event, &cur->event); + if (rel < 0) + /* new cell has earlier schedule time, */ + break; + else if (rel == 0 && prior) + /* equal schedule time and prior to others */ + break; + /* new cell has equal or larger schedule time, */ + /* move cursor to next cell */ + prev = cur; + cur = cur->next; + if (! --count) { + spin_unlock_irqrestore(&f->lock, flags); + pr_err("ALSA: seq: cannot find a pointer.. infinite loop?\n"); + return -EINVAL; + } + } + + /* insert it before cursor */ + if (prev != NULL) + prev->next = cell; + cell->next = cur; + + if (f->head == cur) /* this is the first cell, set head to it */ + f->head = cell; + if (cur == NULL) /* reached end of the list */ + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; +} + +/* return 1 if the current time >= event timestamp */ +static int event_is_ready(struct snd_seq_event *ev, void *current_time) +{ + if ((ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) + return snd_seq_compare_tick_time(current_time, &ev->time.tick); + else + return snd_seq_compare_real_time(current_time, &ev->time.time); +} + +/* dequeue cell from prioq */ +struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f, + void *current_time) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return NULL; + } + spin_lock_irqsave(&f->lock, flags); + + cell = f->head; + if (cell && current_time && !event_is_ready(&cell->event, current_time)) + cell = NULL; + if (cell) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + spin_unlock_irqrestore(&f->lock, flags); + return cell; +} + +/* return number of events available in prioq */ +int snd_seq_prioq_avail(struct snd_seq_prioq * f) +{ + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return 0; + } + return f->cells; +} + +static inline int prioq_match(struct snd_seq_event_cell *cell, + int client, int timestamp) +{ + if (cell->event.source.client == client || + cell->event.dest.client == client) + return 1; + if (!timestamp) + return 0; + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + if (cell->event.time.tick) + return 1; + break; + case SNDRV_SEQ_TIME_STAMP_REAL: + if (cell->event.time.time.tv_sec || + cell->event.time.time.tv_nsec) + return 1; + break; + } + return 0; +} + +/* remove cells for left client */ +void snd_seq_prioq_leave(struct snd_seq_prioq * f, int client, int timestamp) +{ + register struct snd_seq_event_cell *cell, *next; + unsigned long flags; + struct snd_seq_event_cell *prev = NULL; + struct snd_seq_event_cell *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + while (cell) { + next = cell->next; + if (prioq_match(cell, client, timestamp)) { + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + freeprev = cell; + } else { +#if 0 + pr_debug("ALSA: seq: type = %i, source = %i, dest = %i, " + "client = %i\n", + cell->event.type, + cell->event.source.client, + cell->event.dest.client, + client); +#endif + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + +static int prioq_remove_match(struct snd_seq_remove_events *info, + struct snd_seq_event *ev) +{ + int res; + + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) { + if (ev->dest.client != info->dest.client || + ev->dest.port != info->dest.port) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) { + if (! snd_seq_ev_is_channel_type(ev)) + return 0; + /* data.note.channel and data.control.channel are identical */ + if (ev->data.note.channel != info->channel) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (!res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) { + if (ev->type != info->type) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) { + /* Do not remove off events */ + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEOFF: + /* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */ + return 0; + default: + break; + } + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) { + if (info->tag != ev->tag) + return 0; + } + + return 1; +} + +/* remove cells matching remove criteria */ +void snd_seq_prioq_remove_events(struct snd_seq_prioq * f, int client, + struct snd_seq_remove_events *info) +{ + struct snd_seq_event_cell *cell, *next; + unsigned long flags; + struct snd_seq_event_cell *prev = NULL; + struct snd_seq_event_cell *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + + while (cell) { + next = cell->next; + if (cell->event.source.client == client && + prioq_remove_match(info, &cell->event)) { + + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + + freeprev = cell; + } else { + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + + diff --git a/sound/core/seq/seq_prioq.h b/sound/core/seq/seq_prioq.h new file mode 100644 index 000000000..2c315ca10 --- /dev/null +++ b/sound/core/seq/seq_prioq.h @@ -0,0 +1,60 @@ +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_PRIOQ_H +#define __SND_SEQ_PRIOQ_H + +#include "seq_memory.h" + + +/* === PRIOQ === */ + +struct snd_seq_prioq { + struct snd_seq_event_cell *head; /* pointer to head of prioq */ + struct snd_seq_event_cell *tail; /* pointer to tail of prioq */ + int cells; + spinlock_t lock; +}; + + +/* create new prioq (constructor) */ +struct snd_seq_prioq *snd_seq_prioq_new(void); + +/* delete prioq (destructor) */ +void snd_seq_prioq_delete(struct snd_seq_prioq **fifo); + +/* enqueue cell to prioq */ +int snd_seq_prioq_cell_in(struct snd_seq_prioq *f, struct snd_seq_event_cell *cell); + +/* dequeue cell from prioq */ +struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f, + void *current_time); + +/* return number of events available in prioq */ +int snd_seq_prioq_avail(struct snd_seq_prioq *f); + +/* client left queue */ +void snd_seq_prioq_leave(struct snd_seq_prioq *f, int client, int timestamp); + +/* Remove events */ +void snd_seq_prioq_remove_events(struct snd_seq_prioq *f, int client, + struct snd_seq_remove_events *info); + +#endif diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c new file mode 100644 index 000000000..a23ba648d --- /dev/null +++ b/sound/core/seq/seq_queue.c @@ -0,0 +1,807 @@ +/* + * ALSA sequencer Timing queue handling + * Copyright (c) 1998-1999 by Frank van de Pol + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * MAJOR CHANGES + * Nov. 13, 1999 Takashi Iwai + * - Queues are allocated dynamically via ioctl. + * - When owner client is deleted, all owned queues are deleted, too. + * - Owner of unlocked queue is kept unmodified even if it is + * manipulated by other clients. + * - Owner field in SET_QUEUE_OWNER ioctl must be identical with the + * caller client. i.e. Changing owner to a third client is not + * allowed. + * + * Aug. 30, 2000 Takashi Iwai + * - Queues are managed in static array again, but with better way. + * The API itself is identical. + * - The queue is locked when struct snd_seq_queue pointer is returned via + * queueptr(). This pointer *MUST* be released afterward by + * queuefree(ptr). + * - Addition of experimental sync support. + */ + +#include +#include +#include + +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_clientmgr.h" +#include "seq_fifo.h" +#include "seq_timer.h" +#include "seq_info.h" + +/* list of allocated queues */ +static struct snd_seq_queue *queue_list[SNDRV_SEQ_MAX_QUEUES]; +static DEFINE_SPINLOCK(queue_list_lock); +/* number of queues allocated */ +static int num_queues; + +int snd_seq_queue_get_cur_queues(void) +{ + return num_queues; +} + +/*----------------------------------------------------------------*/ + +/* assign queue id and insert to list */ +static int queue_list_add(struct snd_seq_queue *q) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (! queue_list[i]) { + queue_list[i] = q; + q->queue = i; + num_queues++; + spin_unlock_irqrestore(&queue_list_lock, flags); + return i; + } + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return -1; +} + +static struct snd_seq_queue *queue_list_remove(int id, int client) +{ + struct snd_seq_queue *q; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[id]; + if (q) { + spin_lock(&q->owner_lock); + if (q->owner == client) { + /* found */ + q->klocked = 1; + spin_unlock(&q->owner_lock); + queue_list[id] = NULL; + num_queues--; + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; + } + spin_unlock(&q->owner_lock); + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return NULL; +} + +/*----------------------------------------------------------------*/ + +/* create new queue (constructor) */ +static struct snd_seq_queue *queue_new(int owner, int locked) +{ + struct snd_seq_queue *q; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + + spin_lock_init(&q->owner_lock); + spin_lock_init(&q->check_lock); + mutex_init(&q->timer_mutex); + snd_use_lock_init(&q->use_lock); + q->queue = -1; + + q->tickq = snd_seq_prioq_new(); + q->timeq = snd_seq_prioq_new(); + q->timer = snd_seq_timer_new(); + if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) { + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + kfree(q); + return NULL; + } + + q->owner = owner; + q->locked = locked; + q->klocked = 0; + + return q; +} + +/* delete queue (destructor) */ +static void queue_delete(struct snd_seq_queue *q) +{ + /* stop and release the timer */ + mutex_lock(&q->timer_mutex); + snd_seq_timer_stop(q->timer); + snd_seq_timer_close(q); + mutex_unlock(&q->timer_mutex); + /* wait until access free */ + snd_use_lock_sync(&q->use_lock); + /* release resources... */ + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + + kfree(q); +} + + +/*----------------------------------------------------------------*/ + +/* delete all existing queues */ +void snd_seq_queues_delete(void) +{ + int i; + + /* clear list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (queue_list[i]) + queue_delete(queue_list[i]); + } +} + +static void queue_use(struct snd_seq_queue *queue, int client, int use); + +/* allocate a new queue - + * return pointer to new queue or ERR_PTR(-errno) for error + * The new queue's use_lock is set to 1. It is the caller's responsibility to + * call snd_use_lock_free(&q->use_lock). + */ +struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int info_flags) +{ + struct snd_seq_queue *q; + + q = queue_new(client, locked); + if (q == NULL) + return ERR_PTR(-ENOMEM); + q->info_flags = info_flags; + queue_use(q, client, 1); + snd_use_lock_use(&q->use_lock); + if (queue_list_add(q) < 0) { + snd_use_lock_free(&q->use_lock); + queue_delete(q); + return ERR_PTR(-ENOMEM); + } + return q; +} + +/* delete a queue - queue must be owned by the client */ +int snd_seq_queue_delete(int client, int queueid) +{ + struct snd_seq_queue *q; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return -EINVAL; + q = queue_list_remove(queueid, client); + if (q == NULL) + return -EINVAL; + queue_delete(q); + + return 0; +} + + +/* return pointer to queue structure for specified id */ +struct snd_seq_queue *queueptr(int queueid) +{ + struct snd_seq_queue *q; + unsigned long flags; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return NULL; + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[queueid]; + if (q) + snd_use_lock_use(&q->use_lock); + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; +} + +/* return the (first) queue matching with the specified name */ +struct snd_seq_queue *snd_seq_queue_find_name(char *name) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) != NULL) { + if (strncmp(q->name, name, sizeof(q->name)) == 0) + return q; + queuefree(q); + } + } + return NULL; +} + + +/* -------------------------------------------------------- */ + +#define MAX_CELL_PROCESSES_IN_QUEUE 1000 + +void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop) +{ + unsigned long flags; + struct snd_seq_event_cell *cell; + snd_seq_tick_time_t cur_tick; + snd_seq_real_time_t cur_time; + int processed = 0; + + if (q == NULL) + return; + + /* make this function non-reentrant */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_blocked) { + q->check_again = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + return; /* other thread is already checking queues */ + } + q->check_blocked = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + + __again: + /* Process tick queue... */ + cur_tick = snd_seq_timer_get_cur_tick(q->timer); + for (;;) { + cell = snd_seq_prioq_cell_out(q->tickq, &cur_tick); + if (!cell) + break; + snd_seq_dispatch_event(cell, atomic, hop); + if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE) + goto out; /* the rest processed at the next batch */ + } + + /* Process time queue... */ + cur_time = snd_seq_timer_get_cur_time(q->timer, false); + for (;;) { + cell = snd_seq_prioq_cell_out(q->timeq, &cur_time); + if (!cell) + break; + snd_seq_dispatch_event(cell, atomic, hop); + if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE) + goto out; /* the rest processed at the next batch */ + } + + out: + /* free lock */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_again) { + q->check_again = 0; + if (processed < MAX_CELL_PROCESSES_IN_QUEUE) { + spin_unlock_irqrestore(&q->check_lock, flags); + goto __again; + } + } + q->check_blocked = 0; + spin_unlock_irqrestore(&q->check_lock, flags); +} + + +/* enqueue a event to singe queue */ +int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop) +{ + int dest, err; + struct snd_seq_queue *q; + + if (snd_BUG_ON(!cell)) + return -EINVAL; + dest = cell->event.queue; /* destination queue */ + q = queueptr(dest); + if (q == NULL) + return -EINVAL; + /* handle relative time stamps, convert them into absolute */ + if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) { + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + cell->event.time.tick += q->timer->tick.cur_tick; + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + snd_seq_inc_real_time(&cell->event.time.time, + &q->timer->cur_time); + break; + } + cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK; + cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS; + } + /* enqueue event in the real-time or midi queue */ + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + err = snd_seq_prioq_cell_in(q->tickq, cell); + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + default: + err = snd_seq_prioq_cell_in(q->timeq, cell); + break; + } + + if (err < 0) { + queuefree(q); /* unlock */ + return err; + } + + /* trigger dispatching */ + snd_seq_check_queue(q, atomic, hop); + + queuefree(q); /* unlock */ + + return 0; +} + + +/*----------------------------------------------------------------*/ + +static inline int check_access(struct snd_seq_queue *q, int client) +{ + return (q->owner == client) || (!q->locked && !q->klocked); +} + +/* check if the client has permission to modify queue parameters. + * if it does, lock the queue + */ +static int queue_access_lock(struct snd_seq_queue *q, int client) +{ + unsigned long flags; + int access_ok; + + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + if (access_ok) + q->klocked = 1; + spin_unlock_irqrestore(&q->owner_lock, flags); + return access_ok; +} + +/* unlock the queue */ +static inline void queue_access_unlock(struct snd_seq_queue *q) +{ + unsigned long flags; + + spin_lock_irqsave(&q->owner_lock, flags); + q->klocked = 0; + spin_unlock_irqrestore(&q->owner_lock, flags); +} + +/* exported - only checking permission */ +int snd_seq_queue_check_access(int queueid, int client) +{ + struct snd_seq_queue *q = queueptr(queueid); + int access_ok; + unsigned long flags; + + if (! q) + return 0; + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + spin_unlock_irqrestore(&q->owner_lock, flags); + queuefree(q); + return access_ok; +} + +/*----------------------------------------------------------------*/ + +/* + * change queue's owner and permission + */ +int snd_seq_queue_set_owner(int queueid, int client, int locked) +{ + struct snd_seq_queue *q = queueptr(queueid); + unsigned long flags; + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + spin_lock_irqsave(&q->owner_lock, flags); + q->locked = locked ? 1 : 0; + q->owner = client; + spin_unlock_irqrestore(&q->owner_lock, flags); + queue_access_unlock(q); + queuefree(q); + + return 0; +} + + +/*----------------------------------------------------------------*/ + +/* open timer - + * q->use mutex should be down before calling this function to avoid + * confliction with snd_seq_queue_use() + */ +int snd_seq_queue_timer_open(int queueid) +{ + int result = 0; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + tmr = queue->timer; + if ((result = snd_seq_timer_open(queue)) < 0) { + snd_seq_timer_defaults(tmr); + result = snd_seq_timer_open(queue); + } + queuefree(queue); + return result; +} + +/* close timer - + * q->use mutex should be down before calling this function + */ +int snd_seq_queue_timer_close(int queueid) +{ + struct snd_seq_queue *queue; + int result = 0; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + snd_seq_timer_close(queue); + queuefree(queue); + return result; +} + +/* change queue tempo and ppq */ +int snd_seq_queue_timer_set_tempo(int queueid, int client, + struct snd_seq_queue_tempo *info) +{ + struct snd_seq_queue *q = queueptr(queueid); + int result; + + if (q == NULL) + return -EINVAL; + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq); + if (result >= 0 && info->skew_base > 0) + result = snd_seq_timer_set_skew(q->timer, info->skew_value, + info->skew_base); + queue_access_unlock(q); + queuefree(q); + return result; +} + +/* use or unuse this queue */ +static void queue_use(struct snd_seq_queue *queue, int client, int use) +{ + if (use) { + if (!test_and_set_bit(client, queue->clients_bitmap)) + queue->clients++; + } else { + if (test_and_clear_bit(client, queue->clients_bitmap)) + queue->clients--; + } + if (queue->clients) { + if (use && queue->clients == 1) + snd_seq_timer_defaults(queue->timer); + snd_seq_timer_open(queue); + } else { + snd_seq_timer_close(queue); + } +} + +/* use or unuse this queue - + * if it is the first client, starts the timer. + * if it is not longer used by any clients, stop the timer. + */ +int snd_seq_queue_use(int queueid, int client, int use) +{ + struct snd_seq_queue *queue; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + mutex_lock(&queue->timer_mutex); + queue_use(queue, client, use); + mutex_unlock(&queue->timer_mutex); + queuefree(queue); + return 0; +} + +/* + * check if queue is used by the client + * return negative value if the queue is invalid. + * return 0 if not used, 1 if used. + */ +int snd_seq_queue_is_used(int queueid, int client) +{ + struct snd_seq_queue *q; + int result; + + q = queueptr(queueid); + if (q == NULL) + return -EINVAL; /* invalid queue */ + result = test_bit(client, q->clients_bitmap) ? 1 : 0; + queuefree(q); + return result; +} + + +/*----------------------------------------------------------------*/ + +/* notification that client has left the system - + * stop the timer on all queues owned by this client + */ +void snd_seq_queue_client_termination(int client) +{ + unsigned long flags; + int i; + struct snd_seq_queue *q; + bool matched; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + spin_lock_irqsave(&q->owner_lock, flags); + matched = (q->owner == client); + if (matched) + q->klocked = 1; + spin_unlock_irqrestore(&q->owner_lock, flags); + if (matched) { + if (q->timer->running) + snd_seq_timer_stop(q->timer); + snd_seq_timer_reset(q->timer); + } + queuefree(q); + } +} + +/* final stage notification - + * remove cells for no longer exist client (for non-owned queue) + * or delete this queue (for owned queue) + */ +void snd_seq_queue_client_leave(int client) +{ + int i; + struct snd_seq_queue *q; + + /* delete own queues from queue list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queue_list_remove(i, client)) != NULL) + queue_delete(q); + } + + /* remove cells from existing queues - + * they are not owned by this client + */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + if (test_bit(client, q->clients_bitmap)) { + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + snd_seq_queue_use(q->queue, client, 0); + } + queuefree(q); + } +} + + + +/*----------------------------------------------------------------*/ + +/* remove cells from all queues */ +void snd_seq_queue_client_leave_cells(int client) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + queuefree(q); + } +} + +/* remove cells based on flush criteria */ +void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + if (test_bit(client, q->clients_bitmap) && + (! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) || + q->queue == info->queue)) { + snd_seq_prioq_remove_events(q->tickq, client, info); + snd_seq_prioq_remove_events(q->timeq, client, info); + } + queuefree(q); + } +} + +/*----------------------------------------------------------------*/ + +/* + * send events to all subscribed ports + */ +static void queue_broadcast_event(struct snd_seq_queue *q, struct snd_seq_event *ev, + int atomic, int hop) +{ + struct snd_seq_event sev; + + sev = *ev; + + sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS; + sev.time.tick = q->timer->tick.cur_tick; + sev.queue = q->queue; + sev.data.queue.queue = q->queue; + + /* broadcast events from Timer port */ + sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM; + sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop); +} + +/* + * process a received queue-control event. + * this function is exported for seq_sync.c. + */ +static void snd_seq_queue_process_event(struct snd_seq_queue *q, + struct snd_seq_event *ev, + int atomic, int hop) +{ + switch (ev->type) { + case SNDRV_SEQ_EVENT_START: + snd_seq_prioq_leave(q->tickq, ev->source.client, 1); + snd_seq_prioq_leave(q->timeq, ev->source.client, 1); + if (! snd_seq_timer_start(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_CONTINUE: + if (! snd_seq_timer_continue(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_STOP: + snd_seq_timer_stop(q->timer); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_TEMPO: + snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_SETPOS_TICK: + if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + + case SNDRV_SEQ_EVENT_SETPOS_TIME: + if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + case SNDRV_SEQ_EVENT_QUEUE_SKEW: + if (snd_seq_timer_set_skew(q->timer, + ev->data.queue.param.skew.value, + ev->data.queue.param.skew.base) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + } +} + + +/* + * Queue control via timer control port: + * this function is exported as a callback of timer port. + */ +int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop) +{ + struct snd_seq_queue *q; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + q = queueptr(ev->data.queue.queue); + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, ev->source.client)) { + queuefree(q); + return -EPERM; + } + + snd_seq_queue_process_event(q, ev, atomic, hop); + + queue_access_unlock(q); + queuefree(q); + return 0; +} + + +/*----------------------------------------------------------------*/ + +#ifdef CONFIG_SND_PROC_FS +/* exported to seq_info.c */ +void snd_seq_info_queues_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int i, bpm; + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + bool locked; + int owner; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + + tmr = q->timer; + if (tmr->tempo) + bpm = 60000000 / tmr->tempo; + else + bpm = 0; + + spin_lock_irq(&q->owner_lock); + locked = q->locked; + owner = q->owner; + spin_unlock_irq(&q->owner_lock); + + snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name); + snd_iprintf(buffer, "owned by client : %d\n", owner); + snd_iprintf(buffer, "lock status : %s\n", locked ? "Locked" : "Free"); + snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq)); + snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq)); + snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped"); + snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq); + snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo); + snd_iprintf(buffer, "current BPM : %d\n", bpm); + snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec); + snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick); + snd_iprintf(buffer, "\n"); + queuefree(q); + } +} +#endif /* CONFIG_SND_PROC_FS */ + diff --git a/sound/core/seq/seq_queue.h b/sound/core/seq/seq_queue.h new file mode 100644 index 000000000..6b634cfb4 --- /dev/null +++ b/sound/core/seq/seq_queue.h @@ -0,0 +1,112 @@ +/* + * ALSA sequencer Queue handling + * Copyright (c) 1998-1999 by Frank van de Pol + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_QUEUE_H +#define __SND_SEQ_QUEUE_H + +#include "seq_memory.h" +#include "seq_prioq.h" +#include "seq_timer.h" +#include "seq_lock.h" +#include +#include +#include + +#define SEQ_QUEUE_NO_OWNER (-1) + +struct snd_seq_queue { + int queue; /* queue number */ + + char name[64]; /* name of this queue */ + + struct snd_seq_prioq *tickq; /* midi tick event queue */ + struct snd_seq_prioq *timeq; /* real-time event queue */ + + struct snd_seq_timer *timer; /* time keeper for this queue */ + int owner; /* client that 'owns' the timer */ + bool locked; /* timer is only accesibble by owner if set */ + bool klocked; /* kernel lock (after START) */ + bool check_again; /* concurrent access happened during check */ + bool check_blocked; /* queue being checked */ + + unsigned int flags; /* status flags */ + unsigned int info_flags; /* info for sync */ + + spinlock_t owner_lock; + spinlock_t check_lock; + + /* clients which uses this queue (bitmap) */ + DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS); + unsigned int clients; /* users of this queue */ + struct mutex timer_mutex; + + snd_use_lock_t use_lock; +}; + + +/* get the number of current queues */ +int snd_seq_queue_get_cur_queues(void); + +/* delete queues */ +void snd_seq_queues_delete(void); + + +/* create new queue (constructor) */ +struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int flags); + +/* delete queue (destructor) */ +int snd_seq_queue_delete(int client, int queueid); + +/* notification that client has left the system */ +void snd_seq_queue_client_termination(int client); + +/* final stage */ +void snd_seq_queue_client_leave(int client); + +/* enqueue a event received from one the clients */ +int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop); + +/* Remove events */ +void snd_seq_queue_client_leave_cells(int client); +void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info); + +/* return pointer to queue structure for specified id */ +struct snd_seq_queue *queueptr(int queueid); +/* unlock */ +#define queuefree(q) snd_use_lock_free(&(q)->use_lock) + +/* return the (first) queue matching with the specified name */ +struct snd_seq_queue *snd_seq_queue_find_name(char *name); + +/* check single queue and dispatch events */ +void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop); + +/* access to queue's parameters */ +int snd_seq_queue_check_access(int queueid, int client); +int snd_seq_queue_timer_set_tempo(int queueid, int client, struct snd_seq_queue_tempo *info); +int snd_seq_queue_set_owner(int queueid, int client, int locked); +int snd_seq_queue_set_locked(int queueid, int client, int locked); +int snd_seq_queue_timer_open(int queueid); +int snd_seq_queue_timer_close(int queueid); +int snd_seq_queue_use(int queueid, int client, int use); +int snd_seq_queue_is_used(int queueid, int client); + +int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop); + +#endif diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c new file mode 100644 index 000000000..ce1f1e472 --- /dev/null +++ b/sound/core/seq/seq_system.c @@ -0,0 +1,187 @@ +/* + * ALSA sequencer System services Client + * Copyright (c) 1998-1999 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "seq_system.h" +#include "seq_timer.h" +#include "seq_queue.h" + +/* internal client that provide system services, access to timer etc. */ + +/* + * Port "Timer" + * - send tempo /start/stop etc. events to this port to manipulate the + * queue's timer. The queue address is specified in + * data.queue.queue. + * - this port supports subscription. The received timer events are + * broadcasted to all subscribed clients. The modified tempo + * value is stored on data.queue.value. + * The modifier client/port is not send. + * + * Port "Announce" + * - does not receive message + * - supports supscription. For each client or port attaching to or + * detaching from the system an announcement is send to the subscribed + * clients. + * + * Idea: the subscription mechanism might also work handy for distributing + * synchronisation and timing information. In this case we would ideally have + * a list of subscribers for each type of sync (time, tick), for each timing + * queue. + * + * NOTE: the queue to be started, stopped, etc. must be specified + * in data.queue.addr.queue field. queue is used only for + * scheduling, and no longer referred as affected queue. + * They are used only for timer broadcast (see above). + * -- iwai + */ + + +/* client id of our system client */ +static int sysclient = -1; + +/* port id numbers for this client */ +static int announce_port = -1; + + + +/* fill standard header data, source port & channel are filled in */ +static int setheader(struct snd_seq_event * ev, int client, int port) +{ + if (announce_port < 0) + return -ENODEV; + + memset(ev, 0, sizeof(struct snd_seq_event)); + + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + + /* fill data */ + /*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/ + ev->data.addr.client = client; + ev->data.addr.port = port; + + return 0; +} + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type) +{ + struct snd_seq_event ev; + + if (setheader(&ev, client, port) < 0) + return; + ev.type = type; + snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); +} + +/* entry points for broadcasting system events */ +int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev) +{ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = client; + ev->dest.port = port; + return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0); +} + +/* call-back handler for timer events */ +static int event_input_timer(struct snd_seq_event * ev, int direct, void *private_data, int atomic, int hop) +{ + return snd_seq_control_queue(ev, atomic, hop); +} + +/* register our internal client */ +int __init snd_seq_system_client_init(void) +{ + struct snd_seq_port_callback pcallbacks; + struct snd_seq_port_info *port; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.event_input = event_input_timer; + + /* register client */ + sysclient = snd_seq_create_kernel_client(NULL, 0, "System"); + + /* register timer */ + strcpy(port->name, "Timer"); + port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */ + port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */ + port->kernel = &pcallbacks; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + if (err < 0) + goto error_port; + + /* register announcement port */ + strcpy(port->name, "Announce"); + port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */ + port->kernel = NULL; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + if (err < 0) + goto error_port; + announce_port = port->addr.port; + + kfree(port); + return 0; + + error_port: + snd_seq_system_client_done(); + kfree(port); + return err; +} + + +/* unregister our internal client */ +void snd_seq_system_client_done(void) +{ + int oldsysclient = sysclient; + + if (oldsysclient >= 0) { + sysclient = -1; + announce_port = -1; + snd_seq_delete_kernel_client(oldsysclient); + } +} diff --git a/sound/core/seq/seq_system.h b/sound/core/seq/seq_system.h new file mode 100644 index 000000000..cf2cfa234 --- /dev/null +++ b/sound/core/seq/seq_system.h @@ -0,0 +1,46 @@ +/* + * ALSA sequencer System Client + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_SYSTEM_H +#define __SND_SEQ_SYSTEM_H + +#include + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type); + +#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START) +#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT) +#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE) +#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START) +#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT) +#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE) + +int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev); + +/* register our internal client */ +int snd_seq_system_client_init(void); + +/* unregister our internal client */ +void snd_seq_system_client_done(void); + + +#endif diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c new file mode 100644 index 000000000..3da44a4f9 --- /dev/null +++ b/sound/core/seq/seq_timer.c @@ -0,0 +1,507 @@ +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol + * Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include "seq_timer.h" +#include "seq_queue.h" +#include "seq_info.h" + +/* allowed sequencer timer frequencies, in Hz */ +#define MIN_FREQUENCY 10 +#define MAX_FREQUENCY 6250 +#define DEFAULT_FREQUENCY 1000 + +#define SKEW_BASE 0x10000 /* 16bit shift */ + +static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr) +{ + if (tmr->tempo < 1000000) + tmr->tick.resolution = (tmr->tempo * 1000) / tmr->ppq; + else { + /* might overflow.. */ + unsigned int s; + s = tmr->tempo % tmr->ppq; + s = (s * 1000) / tmr->ppq; + tmr->tick.resolution = (tmr->tempo / tmr->ppq) * 1000; + tmr->tick.resolution += s; + } + if (tmr->tick.resolution <= 0) + tmr->tick.resolution = 1; + snd_seq_timer_update_tick(&tmr->tick, 0); +} + +/* create new timer (constructor) */ +struct snd_seq_timer *snd_seq_timer_new(void) +{ + struct snd_seq_timer *tmr; + + tmr = kzalloc(sizeof(*tmr), GFP_KERNEL); + if (!tmr) + return NULL; + spin_lock_init(&tmr->lock); + + /* reset setup to defaults */ + snd_seq_timer_defaults(tmr); + + /* reset time */ + snd_seq_timer_reset(tmr); + + return tmr; +} + +/* delete timer (destructor) */ +void snd_seq_timer_delete(struct snd_seq_timer **tmr) +{ + struct snd_seq_timer *t = *tmr; + *tmr = NULL; + + if (t == NULL) { + pr_debug("ALSA: seq: snd_seq_timer_delete() called with NULL timer\n"); + return; + } + t->running = 0; + + /* reset time */ + snd_seq_timer_stop(t); + snd_seq_timer_reset(t); + + kfree(t); +} + +void snd_seq_timer_defaults(struct snd_seq_timer * tmr) +{ + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + /* setup defaults */ + tmr->ppq = 96; /* 96 PPQ */ + tmr->tempo = 500000; /* 120 BPM */ + snd_seq_timer_set_tick_resolution(tmr); + tmr->running = 0; + + tmr->type = SNDRV_SEQ_TIMER_ALSA; + tmr->alsa_id.dev_class = seq_default_timer_class; + tmr->alsa_id.dev_sclass = seq_default_timer_sclass; + tmr->alsa_id.card = seq_default_timer_card; + tmr->alsa_id.device = seq_default_timer_device; + tmr->alsa_id.subdevice = seq_default_timer_subdevice; + tmr->preferred_resolution = seq_default_timer_resolution; + + tmr->skew = tmr->skew_base = SKEW_BASE; + spin_unlock_irqrestore(&tmr->lock, flags); +} + +static void seq_timer_reset(struct snd_seq_timer *tmr) +{ + /* reset time & songposition */ + tmr->cur_time.tv_sec = 0; + tmr->cur_time.tv_nsec = 0; + + tmr->tick.cur_tick = 0; + tmr->tick.fraction = 0; +} + +void snd_seq_timer_reset(struct snd_seq_timer *tmr) +{ + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + seq_timer_reset(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); +} + + +/* called by timer interrupt routine. the period time since previous invocation is passed */ +static void snd_seq_timer_interrupt(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + unsigned long flags; + struct snd_seq_queue *q = timeri->callback_data; + struct snd_seq_timer *tmr; + + if (q == NULL) + return; + tmr = q->timer; + if (tmr == NULL) + return; + spin_lock_irqsave(&tmr->lock, flags); + if (!tmr->running) { + spin_unlock_irqrestore(&tmr->lock, flags); + return; + } + + resolution *= ticks; + if (tmr->skew != tmr->skew_base) { + /* FIXME: assuming skew_base = 0x10000 */ + resolution = (resolution >> 16) * tmr->skew + + (((resolution & 0xffff) * tmr->skew) >> 16); + } + + /* update timer */ + snd_seq_inc_time_nsec(&tmr->cur_time, resolution); + + /* calculate current tick */ + snd_seq_timer_update_tick(&tmr->tick, resolution); + + /* register actual time of this timer update */ + ktime_get_ts64(&tmr->last_update); + + spin_unlock_irqrestore(&tmr->lock, flags); + + /* check queues and dispatch events */ + snd_seq_check_queue(q, 1, 0); +} + +/* set current tempo */ +int snd_seq_timer_set_tempo(struct snd_seq_timer * tmr, int tempo) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tempo <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if ((unsigned int)tempo != tmr->tempo) { + tmr->tempo = tempo; + snd_seq_timer_set_tick_resolution(tmr); + } + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current tempo and ppq in a shot */ +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq) +{ + int changed; + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tempo <= 0 || ppq <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if (tmr->running && (ppq != tmr->ppq)) { + /* refuse to change ppq on running timers */ + /* because it will upset the song position (ticks) */ + spin_unlock_irqrestore(&tmr->lock, flags); + pr_debug("ALSA: seq: cannot change ppq of a running timer\n"); + return -EBUSY; + } + changed = (tempo != tmr->tempo) || (ppq != tmr->ppq); + tmr->tempo = tempo; + tmr->ppq = ppq; + if (changed) + snd_seq_timer_set_tick_resolution(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current tick position */ +int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, + snd_seq_tick_time_t position) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + spin_lock_irqsave(&tmr->lock, flags); + tmr->tick.cur_tick = position; + tmr->tick.fraction = 0; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current real-time position */ +int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, + snd_seq_real_time_t position) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + snd_seq_sanity_real_time(&position); + spin_lock_irqsave(&tmr->lock, flags); + tmr->cur_time = position; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set timer skew */ +int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, + unsigned int base) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + /* FIXME */ + if (base != SKEW_BASE) { + pr_debug("ALSA: seq: invalid skew base 0x%x\n", base); + return -EINVAL; + } + spin_lock_irqsave(&tmr->lock, flags); + tmr->skew = skew; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +int snd_seq_timer_open(struct snd_seq_queue *q) +{ + struct snd_timer_instance *t; + struct snd_seq_timer *tmr; + char str[32]; + int err; + + tmr = q->timer; + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tmr->timeri) + return -EBUSY; + sprintf(str, "sequencer queue %i", q->queue); + if (tmr->type != SNDRV_SEQ_TIMER_ALSA) /* standard ALSA timer */ + return -EINVAL; + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) + tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue); + if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) { + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL || + tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) { + struct snd_timer_id tid; + memset(&tid, 0, sizeof(tid)); + tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL; + tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + tid.card = -1; + tid.device = SNDRV_TIMER_GLOBAL_SYSTEM; + err = snd_timer_open(&t, str, &tid, q->queue); + } + } + if (err < 0) { + pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err); + return err; + } + t->callback = snd_seq_timer_interrupt; + t->callback_data = q; + t->flags |= SNDRV_TIMER_IFLG_AUTO; + spin_lock_irq(&tmr->lock); + tmr->timeri = t; + spin_unlock_irq(&tmr->lock); + return 0; +} + +int snd_seq_timer_close(struct snd_seq_queue *q) +{ + struct snd_seq_timer *tmr; + struct snd_timer_instance *t; + + tmr = q->timer; + if (snd_BUG_ON(!tmr)) + return -EINVAL; + spin_lock_irq(&tmr->lock); + t = tmr->timeri; + tmr->timeri = NULL; + spin_unlock_irq(&tmr->lock); + if (t) + snd_timer_close(t); + return 0; +} + +static int seq_timer_stop(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (!tmr->running) + return 0; + tmr->running = 0; + snd_timer_pause(tmr->timeri); + return 0; +} + +int snd_seq_timer_stop(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_stop(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +static int initialize_timer(struct snd_seq_timer *tmr) +{ + struct snd_timer *t; + unsigned long freq; + + t = tmr->timeri->timer; + if (!t) + return -EINVAL; + + freq = tmr->preferred_resolution; + if (!freq) + freq = DEFAULT_FREQUENCY; + else if (freq < MIN_FREQUENCY) + freq = MIN_FREQUENCY; + else if (freq > MAX_FREQUENCY) + freq = MAX_FREQUENCY; + + tmr->ticks = 1; + if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE)) { + unsigned long r = snd_timer_resolution(tmr->timeri); + if (r) { + tmr->ticks = (unsigned int)(1000000000uL / (r * freq)); + if (! tmr->ticks) + tmr->ticks = 1; + } + } + tmr->initialized = 1; + return 0; +} + +static int seq_timer_start(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + seq_timer_stop(tmr); + seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + ktime_get_ts64(&tmr->last_update); + return 0; +} + +int snd_seq_timer_start(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_start(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +static int seq_timer_continue(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + return -EBUSY; + if (! tmr->initialized) { + seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + } + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + ktime_get_ts64(&tmr->last_update); + return 0; +} + +int snd_seq_timer_continue(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_continue(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +/* return current 'real' time. use timeofday() to get better granularity. */ +snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr, + bool adjust_ktime) +{ + snd_seq_real_time_t cur_time; + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + cur_time = tmr->cur_time; + if (adjust_ktime && tmr->running) { + struct timespec64 tm; + + ktime_get_ts64(&tm); + tm = timespec64_sub(tm, tmr->last_update); + cur_time.tv_nsec += tm.tv_nsec; + cur_time.tv_sec += tm.tv_sec; + snd_seq_sanity_real_time(&cur_time); + } + spin_unlock_irqrestore(&tmr->lock, flags); + return cur_time; +} + +/* TODO: use interpolation on tick queue (will only be useful for very + high PPQ values) */ +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr) +{ + snd_seq_tick_time_t cur_tick; + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + cur_tick = tmr->tick.cur_tick; + spin_unlock_irqrestore(&tmr->lock, flags); + return cur_tick; +} + + +#ifdef CONFIG_SND_PROC_FS +/* exported to seq_info.c */ +void snd_seq_info_timer_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int idx; + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + struct snd_timer_instance *ti; + unsigned long resolution; + + for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) { + q = queueptr(idx); + if (q == NULL) + continue; + mutex_lock(&q->timer_mutex); + tmr = q->timer; + if (!tmr) + goto unlock; + ti = tmr->timeri; + if (!ti) + goto unlock; + snd_iprintf(buffer, "Timer for queue %i : %s\n", q->queue, ti->timer->name); + resolution = snd_timer_resolution(ti) * tmr->ticks; + snd_iprintf(buffer, " Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000); + snd_iprintf(buffer, " Skew : %u / %u\n", tmr->skew, tmr->skew_base); +unlock: + mutex_unlock(&q->timer_mutex); + queuefree(q); + } +} +#endif /* CONFIG_SND_PROC_FS */ + diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h new file mode 100644 index 000000000..44f52f596 --- /dev/null +++ b/sound/core/seq/seq_timer.h @@ -0,0 +1,149 @@ +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_TIMER_H +#define __SND_SEQ_TIMER_H + +#include +#include + +struct snd_seq_timer_tick { + snd_seq_tick_time_t cur_tick; /* current tick */ + unsigned long resolution; /* time per tick in nsec */ + unsigned long fraction; /* current time per tick in nsec */ +}; + +struct snd_seq_timer { + /* ... tempo / offset / running state */ + + unsigned int running:1, /* running state of queue */ + initialized:1; /* timer is initialized */ + + unsigned int tempo; /* current tempo, us/tick */ + int ppq; /* time resolution, ticks/quarter */ + + snd_seq_real_time_t cur_time; /* current time */ + struct snd_seq_timer_tick tick; /* current tick */ + int tick_updated; + + int type; /* timer type */ + struct snd_timer_id alsa_id; /* ALSA's timer ID */ + struct snd_timer_instance *timeri; /* timer instance */ + unsigned int ticks; + unsigned long preferred_resolution; /* timer resolution, ticks/sec */ + + unsigned int skew; + unsigned int skew_base; + + struct timespec64 last_update; /* time of last clock update, used for interpolation */ + + spinlock_t lock; +}; + + +/* create new timer (constructor) */ +struct snd_seq_timer *snd_seq_timer_new(void); + +/* delete timer (destructor) */ +void snd_seq_timer_delete(struct snd_seq_timer **tmr); + +/* */ +static inline void snd_seq_timer_update_tick(struct snd_seq_timer_tick *tick, + unsigned long resolution) +{ + if (tick->resolution > 0) { + tick->fraction += resolution; + tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution); + tick->fraction %= tick->resolution; + } +} + + +/* compare timestamp between events */ +/* return 1 if a >= b; otherwise return 0 */ +static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b) +{ + /* compare ticks */ + return (*a >= *b); +} + +static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b) +{ + /* compare real time */ + if (a->tv_sec > b->tv_sec) + return 1; + if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec)) + return 1; + return 0; +} + + +static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm) +{ + while (tm->tv_nsec >= 1000000000) { + /* roll-over */ + tm->tv_nsec -= 1000000000; + tm->tv_sec++; + } +} + + +/* increment timestamp */ +static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc) +{ + tm->tv_sec += inc->tv_sec; + tm->tv_nsec += inc->tv_nsec; + snd_seq_sanity_real_time(tm); +} + +static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec) +{ + tm->tv_nsec += nsec; + snd_seq_sanity_real_time(tm); +} + +/* called by timer isr */ +struct snd_seq_queue; +int snd_seq_timer_open(struct snd_seq_queue *q); +int snd_seq_timer_close(struct snd_seq_queue *q); +int snd_seq_timer_midi_open(struct snd_seq_queue *q); +int snd_seq_timer_midi_close(struct snd_seq_queue *q); +void snd_seq_timer_defaults(struct snd_seq_timer *tmr); +void snd_seq_timer_reset(struct snd_seq_timer *tmr); +int snd_seq_timer_stop(struct snd_seq_timer *tmr); +int snd_seq_timer_start(struct snd_seq_timer *tmr); +int snd_seq_timer_continue(struct snd_seq_timer *tmr); +int snd_seq_timer_set_tempo(struct snd_seq_timer *tmr, int tempo); +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq); +int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, snd_seq_tick_time_t position); +int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, snd_seq_real_time_t position); +int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, unsigned int base); +snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr, + bool adjust_ktime); +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr); + +extern int seq_default_timer_class; +extern int seq_default_timer_sclass; +extern int seq_default_timer_card; +extern int seq_default_timer_device; +extern int seq_default_timer_subdevice; +extern int seq_default_timer_resolution; + +#endif diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c new file mode 100644 index 000000000..af9af89a4 --- /dev/null +++ b/sound/core/seq/seq_virmidi.c @@ -0,0 +1,531 @@ +/* + * Virtual Raw MIDI client on Sequencer + * + * Copyright (c) 2000 by Takashi Iwai , + * Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Virtual Raw MIDI client + * + * The virtual rawmidi client is a sequencer client which associate + * a rawmidi device file. The created rawmidi device file can be + * accessed as a normal raw midi, but its MIDI source and destination + * are arbitrary. For example, a user-client software synth connected + * to this port can be used as a normal midi device as well. + * + * The virtual rawmidi device accepts also multiple opens. Each file + * has its own input buffer, so that no conflict would occur. The drain + * of input/output buffer acts only to the local buffer. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer"); +MODULE_LICENSE("GPL"); + +/* + * initialize an event record + */ +static void snd_virmidi_init_event(struct snd_virmidi *vmidi, + struct snd_seq_event *ev) +{ + memset(ev, 0, sizeof(*ev)); + ev->source.port = vmidi->port; + switch (vmidi->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + /* FIXME: source and destination are same - not good.. */ + ev->dest.client = vmidi->client; + ev->dest.port = vmidi->port; + break; + } + ev->type = SNDRV_SEQ_EVENT_NONE; +} + +/* + * decode input event and put to read buffer of each opened file + */ +static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + struct snd_seq_event *ev, + bool atomic) +{ + struct snd_virmidi *vmidi; + unsigned char msg[4]; + int len; + + if (atomic) + read_lock(&rdev->filelist_lock); + else + down_read(&rdev->filelist_sem); + list_for_each_entry(vmidi, &rdev->filelist, list) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + continue; + snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)snd_rawmidi_receive, vmidi->substream); + snd_midi_event_reset_decode(vmidi->parser); + } else { + len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev); + if (len > 0) + snd_rawmidi_receive(vmidi->substream, msg, len); + } + } + if (atomic) + read_unlock(&rdev->filelist_lock); + else + up_read(&rdev->filelist_sem); + + return 0; +} + +/* + * event handler of virmidi port + */ +static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!(rdev->flags & SNDRV_VIRMIDI_USE)) + return 0; /* ignored */ + return snd_virmidi_dev_receive_event(rdev, ev, atomic); +} + +/* + * trigger rawmidi stream for input + */ +static void snd_virmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, !!up); +} + +/* process rawmidi bytes and send events; + * we need no lock here for vmidi->event since it's handled only in this work + */ +static void snd_vmidi_output_work(struct work_struct *work) +{ + struct snd_virmidi *vmidi; + struct snd_rawmidi_substream *substream; + unsigned char input; + int ret; + + vmidi = container_of(work, struct snd_virmidi, output_work); + substream = vmidi->substream; + + /* discard the outputs in dispatch mode unless subscribed */ + if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH && + !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) { + char buf[32]; + while (snd_rawmidi_transmit(substream, buf, sizeof(buf)) > 0) + ; /* ignored */ + return; + } + + while (READ_ONCE(vmidi->trigger)) { + if (snd_rawmidi_transmit(substream, &input, 1) != 1) + break; + if (!snd_midi_event_encode_byte(vmidi->parser, input, + &vmidi->event)) + continue; + if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { + ret = snd_seq_kernel_client_dispatch(vmidi->client, + &vmidi->event, + false, 0); + vmidi->event.type = SNDRV_SEQ_EVENT_NONE; + if (ret < 0) + break; + } + /* rawmidi input might be huge, allow to have a break */ + cond_resched(); + } +} + +/* + * trigger rawmidi stream for output + */ +static void snd_virmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, !!up); + if (up) + queue_work(system_highpri_wq, &vmidi->output_work); +} + +/* + * open rawmidi handle for input + */ +static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_virmidi *vmidi; + + vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(0, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + runtime->private_data = vmidi; + down_write(&rdev->filelist_sem); + write_lock_irq(&rdev->filelist_lock); + list_add_tail(&vmidi->list, &rdev->filelist); + write_unlock_irq(&rdev->filelist_lock); + up_write(&rdev->filelist_sem); + vmidi->rdev = rdev; + return 0; +} + +/* + * open rawmidi handle for output + */ +static int snd_virmidi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_virmidi *vmidi; + + vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + snd_virmidi_init_event(vmidi, &vmidi->event); + vmidi->rdev = rdev; + INIT_WORK(&vmidi->output_work, snd_vmidi_output_work); + runtime->private_data = vmidi; + return 0; +} + +/* + * close rawmidi handle for input + */ +static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_virmidi *vmidi = substream->runtime->private_data; + + down_write(&rdev->filelist_sem); + write_lock_irq(&rdev->filelist_lock); + list_del(&vmidi->list); + write_unlock_irq(&rdev->filelist_lock); + up_write(&rdev->filelist_sem); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * close rawmidi handle for output + */ +static int snd_virmidi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, false); /* to be sure */ + cancel_work_sync(&vmidi->output_work); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * subscribe callback - allow output to rawmidi device + */ +static int snd_virmidi_subscribe(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE; + return 0; +} + +/* + * unsubscribe callback - disallow output to rawmidi device + */ +static int snd_virmidi_unsubscribe(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE; + module_put(rdev->card->module); + return 0; +} + + +/* + * use callback - allow input to rawmidi device + */ +static int snd_virmidi_use(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_USE; + return 0; +} + +/* + * unuse callback - disallow input to rawmidi device + */ +static int snd_virmidi_unuse(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_USE; + module_put(rdev->card->module); + return 0; +} + + +/* + * Register functions + */ + +static const struct snd_rawmidi_ops snd_virmidi_input_ops = { + .open = snd_virmidi_input_open, + .close = snd_virmidi_input_close, + .trigger = snd_virmidi_input_trigger, +}; + +static const struct snd_rawmidi_ops snd_virmidi_output_ops = { + .open = snd_virmidi_output_open, + .close = snd_virmidi_output_close, + .trigger = snd_virmidi_output_trigger, +}; + +/* + * create a sequencer client and a port + */ +static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev) +{ + int client; + struct snd_seq_port_callback pcallbacks; + struct snd_seq_port_info *pinfo; + int err; + + if (rdev->client >= 0) + return 0; + + pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); + if (!pinfo) { + err = -ENOMEM; + goto __error; + } + + client = snd_seq_create_kernel_client(rdev->card, rdev->device, + "%s %d-%d", rdev->rmidi->name, + rdev->card->number, + rdev->device); + if (client < 0) { + err = client; + goto __error; + } + rdev->client = client; + + /* create a port */ + pinfo->addr.client = client; + sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device); + /* set all capabilities */ + pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_SOFTWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + pinfo->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = rdev; + pcallbacks.subscribe = snd_virmidi_subscribe; + pcallbacks.unsubscribe = snd_virmidi_unsubscribe; + pcallbacks.use = snd_virmidi_use; + pcallbacks.unuse = snd_virmidi_unuse; + pcallbacks.event_input = snd_virmidi_event_input; + pinfo->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, pinfo); + if (err < 0) { + snd_seq_delete_kernel_client(client); + rdev->client = -1; + goto __error; + } + + rdev->port = pinfo->addr.port; + err = 0; /* success */ + + __error: + kfree(pinfo); + return err; +} + + +/* + * release the sequencer client + */ +static void snd_virmidi_dev_detach_seq(struct snd_virmidi_dev *rdev) +{ + if (rdev->client >= 0) { + snd_seq_delete_kernel_client(rdev->client); + rdev->client = -1; + } +} + +/* + * register the device + */ +static int snd_virmidi_dev_register(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + int err; + + switch (rdev->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + err = snd_virmidi_dev_attach_seq(rdev); + if (err < 0) + return err; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + if (rdev->client == 0) + return -EINVAL; + /* should check presence of port more strictly.. */ + break; + default: + pr_err("ALSA: seq_virmidi: seq_mode is not set: %d\n", rdev->seq_mode); + return -EINVAL; + } + return 0; +} + + +/* + * unregister the device + */ +static int snd_virmidi_dev_unregister(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + + if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH) + snd_virmidi_dev_detach_seq(rdev); + return 0; +} + +/* + * + */ +static const struct snd_rawmidi_global_ops snd_virmidi_global_ops = { + .dev_register = snd_virmidi_dev_register, + .dev_unregister = snd_virmidi_dev_unregister, +}; + +/* + * free device + */ +static void snd_virmidi_free(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + kfree(rdev); +} + +/* + * create a new device + * + */ +/* exported */ +int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmidi) +{ + struct snd_rawmidi *rmidi; + struct snd_virmidi_dev *rdev; + int err; + + *rrmidi = NULL; + if ((err = snd_rawmidi_new(card, "VirMidi", device, + 16, /* may be configurable */ + 16, /* may be configurable */ + &rmidi)) < 0) + return err; + strcpy(rmidi->name, rmidi->id); + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); + if (rdev == NULL) { + snd_device_free(card, rmidi); + return -ENOMEM; + } + rdev->card = card; + rdev->rmidi = rmidi; + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); + rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; + rmidi->private_free = snd_virmidi_free; + rmidi->ops = &snd_virmidi_global_ops; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + *rrmidi = rmidi; + return 0; +} +EXPORT_SYMBOL(snd_virmidi_new); diff --git a/sound/core/seq_device.c b/sound/core/seq_device.c new file mode 100644 index 000000000..5d16b2079 --- /dev/null +++ b/sound/core/seq_device.c @@ -0,0 +1,316 @@ +/* + * ALSA sequencer device management + * Copyright (c) 1999 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + *---------------------------------------------------------------- + * + * This device handler separates the card driver module from sequencer + * stuff (sequencer core, synth drivers, etc), so that user can avoid + * to spend unnecessary resources e.g. if he needs only listening to + * MP3s. + * + * The card (or lowlevel) driver creates a sequencer device entry + * via snd_seq_device_new(). This is an entry pointer to communicate + * with the sequencer device "driver", which is involved with the + * actual part to communicate with the sequencer core. + * Each sequencer device entry has an id string and the corresponding + * driver with the same id is loaded when required. For example, + * lowlevel codes to access emu8000 chip on sbawe card are included in + * emu8000-synth module. To activate this module, the hardware + * resources like i/o port are passed via snd_seq_device argument. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("ALSA sequencer device management"); +MODULE_LICENSE("GPL"); + +/* + * bus definition + */ +static int snd_seq_bus_match(struct device *dev, struct device_driver *drv) +{ + struct snd_seq_device *sdev = to_seq_dev(dev); + struct snd_seq_driver *sdrv = to_seq_drv(drv); + + return strcmp(sdrv->id, sdev->id) == 0 && + sdrv->argsize == sdev->argsize; +} + +static struct bus_type snd_seq_bus_type = { + .name = "snd_seq", + .match = snd_seq_bus_match, +}; + +/* + * proc interface -- just for compatibility + */ +#ifdef CONFIG_SND_PROC_FS +static struct snd_info_entry *info_entry; + +static int print_dev_info(struct device *dev, void *data) +{ + struct snd_seq_device *sdev = to_seq_dev(dev); + struct snd_info_buffer *buffer = data; + + snd_iprintf(buffer, "snd-%s,%s,%d\n", sdev->id, + dev->driver ? "loaded" : "empty", + dev->driver ? 1 : 0); + return 0; +} + +static void snd_seq_device_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + bus_for_each_dev(&snd_seq_bus_type, NULL, buffer, print_dev_info); +} +#endif + +/* + * load all registered drivers (called from seq_clientmgr.c) + */ + +#ifdef CONFIG_MODULES +/* flag to block auto-loading */ +static atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */ + +static int request_seq_drv(struct device *dev, void *data) +{ + struct snd_seq_device *sdev = to_seq_dev(dev); + + if (!dev->driver) + request_module("snd-%s", sdev->id); + return 0; +} + +static void autoload_drivers(struct work_struct *work) +{ + /* avoid reentrance */ + if (atomic_inc_return(&snd_seq_in_init) == 1) + bus_for_each_dev(&snd_seq_bus_type, NULL, NULL, + request_seq_drv); + atomic_dec(&snd_seq_in_init); +} + +static DECLARE_WORK(autoload_work, autoload_drivers); + +static void queue_autoload_drivers(void) +{ + schedule_work(&autoload_work); +} + +void snd_seq_autoload_init(void) +{ + atomic_dec(&snd_seq_in_init); +#ifdef CONFIG_SND_SEQUENCER_MODULE + /* initial autoload only when snd-seq is a module */ + queue_autoload_drivers(); +#endif +} +EXPORT_SYMBOL(snd_seq_autoload_init); + +void snd_seq_autoload_exit(void) +{ + atomic_inc(&snd_seq_in_init); +} +EXPORT_SYMBOL(snd_seq_autoload_exit); + +void snd_seq_device_load_drivers(void) +{ + queue_autoload_drivers(); + flush_work(&autoload_work); +} +EXPORT_SYMBOL(snd_seq_device_load_drivers); +#define cancel_autoload_drivers() cancel_work_sync(&autoload_work) +#else +#define queue_autoload_drivers() /* NOP */ +#define cancel_autoload_drivers() /* NOP */ +#endif + +/* + * device management + */ +static int snd_seq_device_dev_free(struct snd_device *device) +{ + struct snd_seq_device *dev = device->device_data; + + cancel_autoload_drivers(); + if (dev->private_free) + dev->private_free(dev); + put_device(&dev->dev); + return 0; +} + +static int snd_seq_device_dev_register(struct snd_device *device) +{ + struct snd_seq_device *dev = device->device_data; + int err; + + err = device_add(&dev->dev); + if (err < 0) + return err; + if (!dev->dev.driver) + queue_autoload_drivers(); + return 0; +} + +static int snd_seq_device_dev_disconnect(struct snd_device *device) +{ + struct snd_seq_device *dev = device->device_data; + + device_del(&dev->dev); + return 0; +} + +static void snd_seq_dev_release(struct device *dev) +{ + kfree(to_seq_dev(dev)); +} + +/* + * register a sequencer device + * card = card info + * device = device number (if any) + * id = id of driver + * result = return pointer (NULL allowed if unnecessary) + */ +int snd_seq_device_new(struct snd_card *card, int device, const char *id, + int argsize, struct snd_seq_device **result) +{ + struct snd_seq_device *dev; + int err; + static struct snd_device_ops dops = { + .dev_free = snd_seq_device_dev_free, + .dev_register = snd_seq_device_dev_register, + .dev_disconnect = snd_seq_device_dev_disconnect, + }; + + if (result) + *result = NULL; + + if (snd_BUG_ON(!id)) + return -EINVAL; + + dev = kzalloc(sizeof(*dev) + argsize, GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* set up device info */ + dev->card = card; + dev->device = device; + dev->id = id; + dev->argsize = argsize; + + device_initialize(&dev->dev); + dev->dev.parent = &card->card_dev; + dev->dev.bus = &snd_seq_bus_type; + dev->dev.release = snd_seq_dev_release; + dev_set_name(&dev->dev, "%s-%d-%d", dev->id, card->number, device); + + /* add this device to the list */ + err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops); + if (err < 0) { + put_device(&dev->dev); + return err; + } + + if (result) + *result = dev; + + return 0; +} +EXPORT_SYMBOL(snd_seq_device_new); + +/* + * driver registration + */ +int __snd_seq_driver_register(struct snd_seq_driver *drv, struct module *mod) +{ + if (WARN_ON(!drv->driver.name || !drv->id)) + return -EINVAL; + drv->driver.bus = &snd_seq_bus_type; + drv->driver.owner = mod; + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__snd_seq_driver_register); + +void snd_seq_driver_unregister(struct snd_seq_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(snd_seq_driver_unregister); + +/* + * module part + */ + +static int __init seq_dev_proc_init(void) +{ +#ifdef CONFIG_SND_PROC_FS + info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", + snd_seq_root); + if (info_entry == NULL) + return -ENOMEM; + info_entry->content = SNDRV_INFO_CONTENT_TEXT; + info_entry->c.text.read = snd_seq_device_info; + if (snd_info_register(info_entry) < 0) { + snd_info_free_entry(info_entry); + return -ENOMEM; + } +#endif + return 0; +} + +static int __init alsa_seq_device_init(void) +{ + int err; + + err = bus_register(&snd_seq_bus_type); + if (err < 0) + return err; + err = seq_dev_proc_init(); + if (err < 0) + bus_unregister(&snd_seq_bus_type); + return err; +} + +static void __exit alsa_seq_device_exit(void) +{ +#ifdef CONFIG_MODULES + cancel_work_sync(&autoload_work); +#endif +#ifdef CONFIG_SND_PROC_FS + snd_info_free_entry(info_entry); +#endif + bus_unregister(&snd_seq_bus_type); +} + +subsys_initcall(alsa_seq_device_init) +module_exit(alsa_seq_device_exit) diff --git a/sound/core/sgbuf.c b/sound/core/sgbuf.c new file mode 100644 index 000000000..84fffabdd --- /dev/null +++ b/sound/core/sgbuf.c @@ -0,0 +1,164 @@ +/* + * Scatter-Gather buffer + * + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + + +/* table entries are align to 32 */ +#define SGBUF_TBL_ALIGN 32 +#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN) + +int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab) +{ + struct snd_sg_buf *sgbuf = dmab->private_data; + struct snd_dma_buffer tmpb; + int i; + + if (! sgbuf) + return -EINVAL; + + vunmap(dmab->area); + dmab->area = NULL; + + tmpb.dev.type = SNDRV_DMA_TYPE_DEV; + tmpb.dev.dev = sgbuf->dev; + for (i = 0; i < sgbuf->pages; i++) { + if (!(sgbuf->table[i].addr & ~PAGE_MASK)) + continue; /* continuous pages */ + tmpb.area = sgbuf->table[i].buf; + tmpb.addr = sgbuf->table[i].addr & PAGE_MASK; + tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT; + snd_dma_free_pages(&tmpb); + } + + kfree(sgbuf->table); + kfree(sgbuf->page_table); + kfree(sgbuf); + dmab->private_data = NULL; + + return 0; +} + +#define MAX_ALLOC_PAGES 32 + +void *snd_malloc_sgbuf_pages(struct device *device, + size_t size, struct snd_dma_buffer *dmab, + size_t *res_size) +{ + struct snd_sg_buf *sgbuf; + unsigned int i, pages, chunk, maxpages; + struct snd_dma_buffer tmpb; + struct snd_sg_page *table; + struct page **pgtable; + + dmab->area = NULL; + dmab->addr = 0; + dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL); + if (! sgbuf) + return NULL; + sgbuf->dev = device; + pages = snd_sgbuf_aligned_pages(size); + sgbuf->tblsize = sgbuf_align_table(pages); + table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL); + if (!table) + goto _failed; + sgbuf->table = table; + pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL); + if (!pgtable) + goto _failed; + sgbuf->page_table = pgtable; + + /* allocate pages */ + maxpages = MAX_ALLOC_PAGES; + while (pages > 0) { + chunk = pages; + /* don't be too eager to take a huge chunk */ + if (chunk > maxpages) + chunk = maxpages; + chunk <<= PAGE_SHIFT; + if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, device, + chunk, &tmpb) < 0) { + if (!sgbuf->pages) + goto _failed; + if (!res_size) + goto _failed; + size = sgbuf->pages * PAGE_SIZE; + break; + } + chunk = tmpb.bytes >> PAGE_SHIFT; + for (i = 0; i < chunk; i++) { + table->buf = tmpb.area; + table->addr = tmpb.addr; + if (!i) + table->addr |= chunk; /* mark head */ + table++; + *pgtable++ = virt_to_page(tmpb.area); + tmpb.area += PAGE_SIZE; + tmpb.addr += PAGE_SIZE; + } + sgbuf->pages += chunk; + pages -= chunk; + if (chunk < maxpages) + maxpages = chunk; + } + + sgbuf->size = size; + dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL); + if (! dmab->area) + goto _failed; + if (res_size) + *res_size = sgbuf->size; + return dmab->area; + + _failed: + snd_free_sgbuf_pages(dmab); /* free the table */ + return NULL; +} + +/* + * compute the max chunk size with continuous pages on sg-buffer + */ +unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, + unsigned int ofs, unsigned int size) +{ + struct snd_sg_buf *sg = dmab->private_data; + unsigned int start, end, pg; + + start = ofs >> PAGE_SHIFT; + end = (ofs + size - 1) >> PAGE_SHIFT; + /* check page continuity */ + pg = sg->table[start].addr >> PAGE_SHIFT; + for (;;) { + start++; + if (start > end) + break; + pg++; + if ((sg->table[start].addr >> PAGE_SHIFT) != pg) + return (start << PAGE_SHIFT) - ofs; + } + /* ok, all on continuous pages */ + return size; +} +EXPORT_SYMBOL(snd_sgbuf_get_chunk_size); diff --git a/sound/core/sound.c b/sound/core/sound.c new file mode 100644 index 000000000..b30f027eb --- /dev/null +++ b/sound/core/sound.c @@ -0,0 +1,423 @@ +/* + * Advanced Linux Sound Architecture + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int major = CONFIG_SND_MAJOR; +int snd_major; +EXPORT_SYMBOL(snd_major); + +static int cards_limit = 1; + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards."); +MODULE_LICENSE("GPL"); +module_param(major, int, 0444); +MODULE_PARM_DESC(major, "Major # for sound driver."); +module_param(cards_limit, int, 0444); +MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards."); +MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR); + +/* this one holds the actual max. card number currently available. + * as default, it's identical with cards_limit option. when more + * modules are loaded manually, this limit number increases, too. + */ +int snd_ecards_limit; +EXPORT_SYMBOL(snd_ecards_limit); + +static struct snd_minor *snd_minors[SNDRV_OS_MINORS]; +static DEFINE_MUTEX(sound_mutex); + +#ifdef CONFIG_MODULES + +/** + * snd_request_card - try to load the card module + * @card: the card number + * + * Tries to load the module "snd-card-X" for the given card number + * via request_module. Returns immediately if already loaded. + */ +void snd_request_card(int card) +{ + if (snd_card_locked(card)) + return; + if (card < 0 || card >= cards_limit) + return; + request_module("snd-card-%i", card); +} +EXPORT_SYMBOL(snd_request_card); + +static void snd_request_other(int minor) +{ + char *str; + + switch (minor) { + case SNDRV_MINOR_SEQUENCER: str = "snd-seq"; break; + case SNDRV_MINOR_TIMER: str = "snd-timer"; break; + default: return; + } + request_module(str); +} + +#endif /* modular kernel */ + +/** + * snd_lookup_minor_data - get user data of a registered device + * @minor: the minor number + * @type: device type (SNDRV_DEVICE_TYPE_XXX) + * + * Checks that a minor device with the specified type is registered, and returns + * its user data pointer. + * + * This function increments the reference counter of the card instance + * if an associated instance with the given minor number and type is found. + * The caller must call snd_card_unref() appropriately later. + * + * Return: The user data pointer if the specified device is found. %NULL + * otherwise. + */ +void *snd_lookup_minor_data(unsigned int minor, int type) +{ + struct snd_minor *mreg; + void *private_data; + + if (minor >= ARRAY_SIZE(snd_minors)) + return NULL; + mutex_lock(&sound_mutex); + mreg = snd_minors[minor]; + if (mreg && mreg->type == type) { + private_data = mreg->private_data; + if (private_data && mreg->card_ptr) + get_device(&mreg->card_ptr->card_dev); + } else + private_data = NULL; + mutex_unlock(&sound_mutex); + return private_data; +} +EXPORT_SYMBOL(snd_lookup_minor_data); + +#ifdef CONFIG_MODULES +static struct snd_minor *autoload_device(unsigned int minor) +{ + int dev; + mutex_unlock(&sound_mutex); /* release lock temporarily */ + dev = SNDRV_MINOR_DEVICE(minor); + if (dev == SNDRV_MINOR_CONTROL) { + /* /dev/aloadC? */ + int card = SNDRV_MINOR_CARD(minor); + if (snd_cards[card] == NULL) + snd_request_card(card); + } else if (dev == SNDRV_MINOR_GLOBAL) { + /* /dev/aloadSEQ */ + snd_request_other(minor); + } + mutex_lock(&sound_mutex); /* reacuire lock */ + return snd_minors[minor]; +} +#else /* !CONFIG_MODULES */ +#define autoload_device(minor) NULL +#endif /* CONFIG_MODULES */ + +static int snd_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct snd_minor *mptr = NULL; + const struct file_operations *new_fops; + int err = 0; + + if (minor >= ARRAY_SIZE(snd_minors)) + return -ENODEV; + mutex_lock(&sound_mutex); + mptr = snd_minors[minor]; + if (mptr == NULL) { + mptr = autoload_device(minor); + if (!mptr) { + mutex_unlock(&sound_mutex); + return -ENODEV; + } + } + new_fops = fops_get(mptr->f_ops); + mutex_unlock(&sound_mutex); + if (!new_fops) + return -ENODEV; + replace_fops(file, new_fops); + + if (file->f_op->open) + err = file->f_op->open(inode, file); + return err; +} + +static const struct file_operations snd_fops = +{ + .owner = THIS_MODULE, + .open = snd_open, + .llseek = noop_llseek, +}; + +#ifdef CONFIG_SND_DYNAMIC_MINORS +static int snd_find_free_minor(int type, struct snd_card *card, int dev) +{ + int minor; + + /* static minors for module auto loading */ + if (type == SNDRV_DEVICE_TYPE_SEQUENCER) + return SNDRV_MINOR_SEQUENCER; + if (type == SNDRV_DEVICE_TYPE_TIMER) + return SNDRV_MINOR_TIMER; + + for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + /* skip static minors still used for module auto loading */ + if (SNDRV_MINOR_DEVICE(minor) == SNDRV_MINOR_CONTROL) + continue; + if (minor == SNDRV_MINOR_SEQUENCER || + minor == SNDRV_MINOR_TIMER) + continue; + if (!snd_minors[minor]) + return minor; + } + return -EBUSY; +} +#else +static int snd_find_free_minor(int type, struct snd_card *card, int dev) +{ + int minor; + + switch (type) { + case SNDRV_DEVICE_TYPE_SEQUENCER: + case SNDRV_DEVICE_TYPE_TIMER: + minor = type; + break; + case SNDRV_DEVICE_TYPE_CONTROL: + if (snd_BUG_ON(!card)) + return -EINVAL; + minor = SNDRV_MINOR(card->number, type); + break; + case SNDRV_DEVICE_TYPE_HWDEP: + case SNDRV_DEVICE_TYPE_RAWMIDI: + case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: + case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + case SNDRV_DEVICE_TYPE_COMPRESS: + if (snd_BUG_ON(!card)) + return -EINVAL; + minor = SNDRV_MINOR(card->number, type + dev); + break; + default: + return -EINVAL; + } + if (snd_BUG_ON(minor < 0 || minor >= SNDRV_OS_MINORS)) + return -EINVAL; + if (snd_minors[minor]) + return -EBUSY; + return minor; +} +#endif + +/** + * snd_register_device - Register the ALSA device file for the card + * @type: the device type, SNDRV_DEVICE_TYPE_XXX + * @card: the card instance + * @dev: the device index + * @f_ops: the file operations + * @private_data: user pointer for f_ops->open() + * @device: the device to register + * + * Registers an ALSA device file for the given card. + * The operators have to be set in reg parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_register_device(int type, struct snd_card *card, int dev, + const struct file_operations *f_ops, + void *private_data, struct device *device) +{ + int minor; + int err = 0; + struct snd_minor *preg; + + if (snd_BUG_ON(!device)) + return -EINVAL; + + preg = kmalloc(sizeof *preg, GFP_KERNEL); + if (preg == NULL) + return -ENOMEM; + preg->type = type; + preg->card = card ? card->number : -1; + preg->device = dev; + preg->f_ops = f_ops; + preg->private_data = private_data; + preg->card_ptr = card; + mutex_lock(&sound_mutex); + minor = snd_find_free_minor(type, card, dev); + if (minor < 0) { + err = minor; + goto error; + } + + preg->dev = device; + device->devt = MKDEV(major, minor); + err = device_add(device); + if (err < 0) + goto error; + + snd_minors[minor] = preg; + error: + mutex_unlock(&sound_mutex); + if (err < 0) + kfree(preg); + return err; +} +EXPORT_SYMBOL(snd_register_device); + +/** + * snd_unregister_device - unregister the device on the given card + * @dev: the device instance + * + * Unregisters the device file already registered via + * snd_register_device(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_unregister_device(struct device *dev) +{ + int minor; + struct snd_minor *preg; + + mutex_lock(&sound_mutex); + for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + preg = snd_minors[minor]; + if (preg && preg->dev == dev) { + snd_minors[minor] = NULL; + device_del(dev); + kfree(preg); + break; + } + } + mutex_unlock(&sound_mutex); + if (minor >= ARRAY_SIZE(snd_minors)) + return -ENOENT; + return 0; +} +EXPORT_SYMBOL(snd_unregister_device); + +#ifdef CONFIG_SND_PROC_FS +/* + * INFO PART + */ +static const char *snd_device_type_name(int type) +{ + switch (type) { + case SNDRV_DEVICE_TYPE_CONTROL: + return "control"; + case SNDRV_DEVICE_TYPE_HWDEP: + return "hardware dependent"; + case SNDRV_DEVICE_TYPE_RAWMIDI: + return "raw midi"; + case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: + return "digital audio playback"; + case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + return "digital audio capture"; + case SNDRV_DEVICE_TYPE_SEQUENCER: + return "sequencer"; + case SNDRV_DEVICE_TYPE_TIMER: + return "timer"; + default: + return "?"; + } +} + +static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + int minor; + struct snd_minor *mptr; + + mutex_lock(&sound_mutex); + for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) { + if (!(mptr = snd_minors[minor])) + continue; + if (mptr->card >= 0) { + if (mptr->device >= 0) + snd_iprintf(buffer, "%3i: [%2i-%2i]: %s\n", + minor, mptr->card, mptr->device, + snd_device_type_name(mptr->type)); + else + snd_iprintf(buffer, "%3i: [%2i] : %s\n", + minor, mptr->card, + snd_device_type_name(mptr->type)); + } else + snd_iprintf(buffer, "%3i: : %s\n", minor, + snd_device_type_name(mptr->type)); + } + mutex_unlock(&sound_mutex); +} + +int __init snd_minor_info_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_minor_info_read; + return snd_info_register(entry); /* freed in error path */ +} +#endif /* CONFIG_SND_PROC_FS */ + +/* + * INIT PART + */ + +static int __init alsa_sound_init(void) +{ + snd_major = major; + snd_ecards_limit = cards_limit; + if (register_chrdev(major, "alsa", &snd_fops)) { + pr_err("ALSA core: unable to register native major device number %d\n", major); + return -EIO; + } + if (snd_info_init() < 0) { + unregister_chrdev(major, "alsa"); + return -ENOMEM; + } +#ifndef MODULE + pr_info("Advanced Linux Sound Architecture Driver Initialized.\n"); +#endif + return 0; +} + +static void __exit alsa_sound_exit(void) +{ + snd_info_done(); + unregister_chrdev(major, "alsa"); +} + +subsys_initcall(alsa_sound_init); +module_exit(alsa_sound_exit); diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c new file mode 100644 index 000000000..0a5c66229 --- /dev/null +++ b/sound/core/sound_oss.c @@ -0,0 +1,259 @@ +/* + * Advanced Linux Sound Architecture + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SNDRV_OSS_MINORS 256 + +static struct snd_minor *snd_oss_minors[SNDRV_OSS_MINORS]; +static DEFINE_MUTEX(sound_oss_mutex); + +/* NOTE: This function increments the refcount of the associated card like + * snd_lookup_minor_data(); the caller must call snd_card_unref() appropriately + */ +void *snd_lookup_oss_minor_data(unsigned int minor, int type) +{ + struct snd_minor *mreg; + void *private_data; + + if (minor >= ARRAY_SIZE(snd_oss_minors)) + return NULL; + mutex_lock(&sound_oss_mutex); + mreg = snd_oss_minors[minor]; + if (mreg && mreg->type == type) { + private_data = mreg->private_data; + if (private_data && mreg->card_ptr) + get_device(&mreg->card_ptr->card_dev); + } else + private_data = NULL; + mutex_unlock(&sound_oss_mutex); + return private_data; +} +EXPORT_SYMBOL(snd_lookup_oss_minor_data); + +static int snd_oss_kernel_minor(int type, struct snd_card *card, int dev) +{ + int minor; + + switch (type) { + case SNDRV_OSS_DEVICE_TYPE_MIXER: + if (snd_BUG_ON(!card || dev < 0 || dev > 1)) + return -EINVAL; + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIXER1 : SNDRV_MINOR_OSS_MIXER)); + break; + case SNDRV_OSS_DEVICE_TYPE_SEQUENCER: + minor = SNDRV_MINOR_OSS_SEQUENCER; + break; + case SNDRV_OSS_DEVICE_TYPE_MUSIC: + minor = SNDRV_MINOR_OSS_MUSIC; + break; + case SNDRV_OSS_DEVICE_TYPE_PCM: + if (snd_BUG_ON(!card || dev < 0 || dev > 1)) + return -EINVAL; + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 : SNDRV_MINOR_OSS_PCM)); + break; + case SNDRV_OSS_DEVICE_TYPE_MIDI: + if (snd_BUG_ON(!card || dev < 0 || dev > 1)) + return -EINVAL; + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIDI1 : SNDRV_MINOR_OSS_MIDI)); + break; + case SNDRV_OSS_DEVICE_TYPE_DMFM: + minor = SNDRV_MINOR_OSS(card->number, SNDRV_MINOR_OSS_DMFM); + break; + case SNDRV_OSS_DEVICE_TYPE_SNDSTAT: + minor = SNDRV_MINOR_OSS_SNDSTAT; + break; + default: + return -EINVAL; + } + if (minor < 0 || minor >= SNDRV_OSS_MINORS) + return -EINVAL; + return minor; +} + +int snd_register_oss_device(int type, struct snd_card *card, int dev, + const struct file_operations *f_ops, void *private_data) +{ + int minor = snd_oss_kernel_minor(type, card, dev); + int minor_unit; + struct snd_minor *preg; + int cidx = SNDRV_MINOR_OSS_CARD(minor); + int track2 = -1; + int register1 = -1, register2 = -1; + struct device *carddev = snd_card_get_device_link(card); + + if (card && card->number >= SNDRV_MINOR_OSS_DEVICES) + return 0; /* ignore silently */ + if (minor < 0) + return minor; + preg = kmalloc(sizeof(struct snd_minor), GFP_KERNEL); + if (preg == NULL) + return -ENOMEM; + preg->type = type; + preg->card = card ? card->number : -1; + preg->device = dev; + preg->f_ops = f_ops; + preg->private_data = private_data; + preg->card_ptr = card; + mutex_lock(&sound_oss_mutex); + snd_oss_minors[minor] = preg; + minor_unit = SNDRV_MINOR_OSS_DEVICE(minor); + switch (minor_unit) { + case SNDRV_MINOR_OSS_PCM: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO); + break; + case SNDRV_MINOR_OSS_MIDI: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI); + break; + case SNDRV_MINOR_OSS_MIDI1: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1); + break; + } + register1 = register_sound_special_device(f_ops, minor, carddev); + if (register1 != minor) + goto __end; + if (track2 >= 0) { + register2 = register_sound_special_device(f_ops, track2, + carddev); + if (register2 != track2) + goto __end; + snd_oss_minors[track2] = preg; + } + mutex_unlock(&sound_oss_mutex); + return 0; + + __end: + if (register2 >= 0) + unregister_sound_special(register2); + if (register1 >= 0) + unregister_sound_special(register1); + snd_oss_minors[minor] = NULL; + mutex_unlock(&sound_oss_mutex); + kfree(preg); + return -EBUSY; +} +EXPORT_SYMBOL(snd_register_oss_device); + +int snd_unregister_oss_device(int type, struct snd_card *card, int dev) +{ + int minor = snd_oss_kernel_minor(type, card, dev); + int cidx = SNDRV_MINOR_OSS_CARD(minor); + int track2 = -1; + struct snd_minor *mptr; + + if (card && card->number >= SNDRV_MINOR_OSS_DEVICES) + return 0; + if (minor < 0) + return minor; + mutex_lock(&sound_oss_mutex); + mptr = snd_oss_minors[minor]; + if (mptr == NULL) { + mutex_unlock(&sound_oss_mutex); + return -ENOENT; + } + unregister_sound_special(minor); + switch (SNDRV_MINOR_OSS_DEVICE(minor)) { + case SNDRV_MINOR_OSS_PCM: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO); + break; + case SNDRV_MINOR_OSS_MIDI: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI); + break; + case SNDRV_MINOR_OSS_MIDI1: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1); + break; + } + if (track2 >= 0) { + unregister_sound_special(track2); + snd_oss_minors[track2] = NULL; + } + snd_oss_minors[minor] = NULL; + mutex_unlock(&sound_oss_mutex); + kfree(mptr); + return 0; +} +EXPORT_SYMBOL(snd_unregister_oss_device); + +/* + * INFO PART + */ + +#ifdef CONFIG_SND_PROC_FS +static const char *snd_oss_device_type_name(int type) +{ + switch (type) { + case SNDRV_OSS_DEVICE_TYPE_MIXER: + return "mixer"; + case SNDRV_OSS_DEVICE_TYPE_SEQUENCER: + case SNDRV_OSS_DEVICE_TYPE_MUSIC: + return "sequencer"; + case SNDRV_OSS_DEVICE_TYPE_PCM: + return "digital audio"; + case SNDRV_OSS_DEVICE_TYPE_MIDI: + return "raw midi"; + case SNDRV_OSS_DEVICE_TYPE_DMFM: + return "hardware dependent"; + default: + return "?"; + } +} + +static void snd_minor_info_oss_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int minor; + struct snd_minor *mptr; + + mutex_lock(&sound_oss_mutex); + for (minor = 0; minor < SNDRV_OSS_MINORS; ++minor) { + if (!(mptr = snd_oss_minors[minor])) + continue; + if (mptr->card >= 0) + snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", minor, + mptr->card, mptr->device, + snd_oss_device_type_name(mptr->type)); + else + snd_iprintf(buffer, "%3i: : %s\n", minor, + snd_oss_device_type_name(mptr->type)); + } + mutex_unlock(&sound_oss_mutex); +} + + +int __init snd_minor_info_oss_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_minor_info_oss_read; + return snd_info_register(entry); /* freed in error path */ +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/timer.c b/sound/core/timer.c new file mode 100644 index 000000000..4920ec4f4 --- /dev/null +++ b/sound/core/timer.c @@ -0,0 +1,2203 @@ +/* + * Timers abstract layer + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* internal flags */ +#define SNDRV_TIMER_IFLG_PAUSED 0x00010000 + +#if IS_ENABLED(CONFIG_SND_HRTIMER) +#define DEFAULT_TIMER_LIMIT 4 +#else +#define DEFAULT_TIMER_LIMIT 1 +#endif + +static int timer_limit = DEFAULT_TIMER_LIMIT; +static int timer_tstamp_monotonic = 1; +MODULE_AUTHOR("Jaroslav Kysela , Takashi Iwai "); +MODULE_DESCRIPTION("ALSA timer interface"); +MODULE_LICENSE("GPL"); +module_param(timer_limit, int, 0444); +MODULE_PARM_DESC(timer_limit, "Maximum global timers in system."); +module_param(timer_tstamp_monotonic, int, 0444); +MODULE_PARM_DESC(timer_tstamp_monotonic, "Use posix monotonic clock source for timestamps (default)."); + +MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_TIMER); +MODULE_ALIAS("devname:snd/timer"); + +struct snd_timer_user { + struct snd_timer_instance *timeri; + int tread; /* enhanced read with timestamps and events */ + unsigned long ticks; + unsigned long overrun; + int qhead; + int qtail; + int qused; + int queue_size; + bool disconnected; + struct snd_timer_read *queue; + struct snd_timer_tread *tqueue; + spinlock_t qlock; + unsigned long last_resolution; + unsigned int filter; + struct timespec tstamp; /* trigger tstamp */ + wait_queue_head_t qchange_sleep; + struct fasync_struct *fasync; + struct mutex ioctl_lock; +}; + +/* list of timers */ +static LIST_HEAD(snd_timer_list); + +/* list of slave instances */ +static LIST_HEAD(snd_timer_slave_list); + +/* lock for slave active lists */ +static DEFINE_SPINLOCK(slave_active_lock); + +#define MAX_SLAVE_INSTANCES 1000 +static int num_slaves; + +static DEFINE_MUTEX(register_mutex); + +static int snd_timer_free(struct snd_timer *timer); +static int snd_timer_dev_free(struct snd_device *device); +static int snd_timer_dev_register(struct snd_device *device); +static int snd_timer_dev_disconnect(struct snd_device *device); + +static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_left); + +/* + * create a timer instance with the given owner string. + * when timer is not NULL, increments the module counter + */ +static struct snd_timer_instance *snd_timer_instance_new(char *owner, + struct snd_timer *timer) +{ + struct snd_timer_instance *timeri; + timeri = kzalloc(sizeof(*timeri), GFP_KERNEL); + if (timeri == NULL) + return NULL; + timeri->owner = kstrdup(owner, GFP_KERNEL); + if (! timeri->owner) { + kfree(timeri); + return NULL; + } + INIT_LIST_HEAD(&timeri->open_list); + INIT_LIST_HEAD(&timeri->active_list); + INIT_LIST_HEAD(&timeri->ack_list); + INIT_LIST_HEAD(&timeri->slave_list_head); + INIT_LIST_HEAD(&timeri->slave_active_head); + + timeri->timer = timer; + if (timer && !try_module_get(timer->module)) { + kfree(timeri->owner); + kfree(timeri); + return NULL; + } + + return timeri; +} + +/* + * find a timer instance from the given timer id + */ +static struct snd_timer *snd_timer_find(struct snd_timer_id *tid) +{ + struct snd_timer *timer = NULL; + + list_for_each_entry(timer, &snd_timer_list, device_list) { + if (timer->tmr_class != tid->dev_class) + continue; + if ((timer->tmr_class == SNDRV_TIMER_CLASS_CARD || + timer->tmr_class == SNDRV_TIMER_CLASS_PCM) && + (timer->card == NULL || + timer->card->number != tid->card)) + continue; + if (timer->tmr_device != tid->device) + continue; + if (timer->tmr_subdevice != tid->subdevice) + continue; + return timer; + } + return NULL; +} + +#ifdef CONFIG_MODULES + +static void snd_timer_request(struct snd_timer_id *tid) +{ + switch (tid->dev_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + if (tid->device < timer_limit) + request_module("snd-timer-%i", tid->device); + break; + case SNDRV_TIMER_CLASS_CARD: + case SNDRV_TIMER_CLASS_PCM: + if (tid->card < snd_ecards_limit) + request_module("snd-card-%i", tid->card); + break; + default: + break; + } +} + +#endif + +/* + * look for a master instance matching with the slave id of the given slave. + * when found, relink the open_link of the slave. + * + * call this with register_mutex down. + */ +static int snd_timer_check_slave(struct snd_timer_instance *slave) +{ + struct snd_timer *timer; + struct snd_timer_instance *master; + + /* FIXME: it's really dumb to look up all entries.. */ + list_for_each_entry(timer, &snd_timer_list, device_list) { + list_for_each_entry(master, &timer->open_list_head, open_list) { + if (slave->slave_class == master->slave_class && + slave->slave_id == master->slave_id) { + if (master->timer->num_instances >= + master->timer->max_instances) + return -EBUSY; + list_move_tail(&slave->open_list, + &master->slave_list_head); + master->timer->num_instances++; + spin_lock_irq(&slave_active_lock); + slave->master = master; + slave->timer = master->timer; + spin_unlock_irq(&slave_active_lock); + return 0; + } + } + } + return 0; +} + +/* + * look for slave instances matching with the slave id of the given master. + * when found, relink the open_link of slaves. + * + * call this with register_mutex down. + */ +static int snd_timer_check_master(struct snd_timer_instance *master) +{ + struct snd_timer_instance *slave, *tmp; + + /* check all pending slaves */ + list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) { + if (slave->slave_class == master->slave_class && + slave->slave_id == master->slave_id) { + if (master->timer->num_instances >= + master->timer->max_instances) + return -EBUSY; + list_move_tail(&slave->open_list, &master->slave_list_head); + master->timer->num_instances++; + spin_lock_irq(&slave_active_lock); + spin_lock(&master->timer->lock); + slave->master = master; + slave->timer = master->timer; + if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) + list_add_tail(&slave->active_list, + &master->slave_active_head); + spin_unlock(&master->timer->lock); + spin_unlock_irq(&slave_active_lock); + } + } + return 0; +} + +static int snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put); + +/* + * open a timer instance + * when opening a master, the slave id must be here given. + */ +int snd_timer_open(struct snd_timer_instance **ti, + char *owner, struct snd_timer_id *tid, + unsigned int slave_id) +{ + struct snd_timer *timer; + struct snd_timer_instance *timeri = NULL; + struct device *card_dev_to_put = NULL; + int err; + + mutex_lock(®ister_mutex); + if (tid->dev_class == SNDRV_TIMER_CLASS_SLAVE) { + /* open a slave instance */ + if (tid->dev_sclass <= SNDRV_TIMER_SCLASS_NONE || + tid->dev_sclass > SNDRV_TIMER_SCLASS_OSS_SEQUENCER) { + pr_debug("ALSA: timer: invalid slave class %i\n", + tid->dev_sclass); + err = -EINVAL; + goto unlock; + } + if (num_slaves >= MAX_SLAVE_INSTANCES) { + err = -EBUSY; + goto unlock; + } + timeri = snd_timer_instance_new(owner, NULL); + if (!timeri) { + err = -ENOMEM; + goto unlock; + } + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = tid->device; + timeri->flags |= SNDRV_TIMER_IFLG_SLAVE; + list_add_tail(&timeri->open_list, &snd_timer_slave_list); + num_slaves++; + err = snd_timer_check_slave(timeri); + if (err < 0) { + snd_timer_close_locked(timeri, &card_dev_to_put); + timeri = NULL; + } + goto unlock; + } + + /* open a master instance */ + timer = snd_timer_find(tid); +#ifdef CONFIG_MODULES + if (!timer) { + mutex_unlock(®ister_mutex); + snd_timer_request(tid); + mutex_lock(®ister_mutex); + timer = snd_timer_find(tid); + } +#endif + if (!timer) { + err = -ENODEV; + goto unlock; + } + if (!list_empty(&timer->open_list_head)) { + struct snd_timer_instance *t = + list_entry(timer->open_list_head.next, + struct snd_timer_instance, open_list); + if (t->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) { + err = -EBUSY; + goto unlock; + } + } + if (timer->num_instances >= timer->max_instances) { + err = -EBUSY; + goto unlock; + } + timeri = snd_timer_instance_new(owner, timer); + if (!timeri) { + err = -ENOMEM; + goto unlock; + } + /* take a card refcount for safe disconnection */ + if (timer->card) + get_device(&timer->card->card_dev); + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = slave_id; + + if (list_empty(&timer->open_list_head) && timer->hw.open) { + err = timer->hw.open(timer); + if (err) { + kfree(timeri->owner); + kfree(timeri); + timeri = NULL; + + if (timer->card) + card_dev_to_put = &timer->card->card_dev; + module_put(timer->module); + goto unlock; + } + } + + list_add_tail(&timeri->open_list, &timer->open_list_head); + timer->num_instances++; + err = snd_timer_check_master(timeri); + if (err < 0) { + snd_timer_close_locked(timeri, &card_dev_to_put); + timeri = NULL; + } + + unlock: + mutex_unlock(®ister_mutex); + /* put_device() is called after unlock for avoiding deadlock */ + if (card_dev_to_put) + put_device(card_dev_to_put); + *ti = timeri; + return err; +} +EXPORT_SYMBOL(snd_timer_open); + +/* + * close a timer instance + * call this with register_mutex down. + */ +static int snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put) +{ + struct snd_timer *timer = NULL; + struct snd_timer_instance *slave, *tmp; + + list_del(&timeri->open_list); + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + num_slaves--; + + /* force to stop the timer */ + snd_timer_stop(timeri); + + timer = timeri->timer; + if (timer) { + timer->num_instances--; + /* wait, until the active callback is finished */ + spin_lock_irq(&timer->lock); + while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) { + spin_unlock_irq(&timer->lock); + udelay(10); + spin_lock_irq(&timer->lock); + } + spin_unlock_irq(&timer->lock); + + /* remove slave links */ + spin_lock_irq(&slave_active_lock); + spin_lock(&timer->lock); + list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head, + open_list) { + list_move_tail(&slave->open_list, &snd_timer_slave_list); + timer->num_instances--; + slave->master = NULL; + slave->timer = NULL; + list_del_init(&slave->ack_list); + list_del_init(&slave->active_list); + } + spin_unlock(&timer->lock); + spin_unlock_irq(&slave_active_lock); + + /* slave doesn't need to release timer resources below */ + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + timer = NULL; + } + + if (timeri->private_free) + timeri->private_free(timeri); + kfree(timeri->owner); + kfree(timeri); + + if (timer) { + if (list_empty(&timer->open_list_head) && timer->hw.close) + timer->hw.close(timer); + /* release a card refcount for safe disconnection */ + if (timer->card) + *card_devp_to_put = &timer->card->card_dev; + module_put(timer->module); + } + + return 0; +} + +/* + * close a timer instance + */ +int snd_timer_close(struct snd_timer_instance *timeri) +{ + struct device *card_dev_to_put = NULL; + int err; + + if (snd_BUG_ON(!timeri)) + return -ENXIO; + + mutex_lock(®ister_mutex); + err = snd_timer_close_locked(timeri, &card_dev_to_put); + mutex_unlock(®ister_mutex); + /* put_device() is called after unlock for avoiding deadlock */ + if (card_dev_to_put) + put_device(card_dev_to_put); + return err; +} +EXPORT_SYMBOL(snd_timer_close); + +static unsigned long snd_timer_hw_resolution(struct snd_timer *timer) +{ + if (timer->hw.c_resolution) + return timer->hw.c_resolution(timer); + else + return timer->hw.resolution; +} + +unsigned long snd_timer_resolution(struct snd_timer_instance *timeri) +{ + struct snd_timer * timer; + unsigned long ret = 0; + unsigned long flags; + + if (timeri == NULL) + return 0; + timer = timeri->timer; + if (timer) { + spin_lock_irqsave(&timer->lock, flags); + ret = snd_timer_hw_resolution(timer); + spin_unlock_irqrestore(&timer->lock, flags); + } + return ret; +} +EXPORT_SYMBOL(snd_timer_resolution); + +static void snd_timer_notify1(struct snd_timer_instance *ti, int event) +{ + struct snd_timer *timer = ti->timer; + unsigned long resolution = 0; + struct snd_timer_instance *ts; + struct timespec tstamp; + + if (timer_tstamp_monotonic) + ktime_get_ts(&tstamp); + else + getnstimeofday(&tstamp); + if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_START || + event > SNDRV_TIMER_EVENT_PAUSE)) + return; + if (timer && + (event == SNDRV_TIMER_EVENT_START || + event == SNDRV_TIMER_EVENT_CONTINUE)) + resolution = snd_timer_hw_resolution(timer); + if (ti->ccallback) + ti->ccallback(ti, event, &tstamp, resolution); + if (ti->flags & SNDRV_TIMER_IFLG_SLAVE) + return; + if (timer == NULL) + return; + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + return; + event += 10; /* convert to SNDRV_TIMER_EVENT_MXXX */ + list_for_each_entry(ts, &ti->slave_active_head, active_list) + if (ts->ccallback) + ts->ccallback(ts, event, &tstamp, resolution); +} + +/* start/continue a master timer */ +static int snd_timer_start1(struct snd_timer_instance *timeri, + bool start, unsigned long ticks) +{ + struct snd_timer *timer; + int result; + unsigned long flags; + + timer = timeri->timer; + if (!timer) + return -EINVAL; + + spin_lock_irqsave(&timer->lock, flags); + if (timer->card && timer->card->shutdown) { + result = -ENODEV; + goto unlock; + } + if (timeri->flags & (SNDRV_TIMER_IFLG_RUNNING | + SNDRV_TIMER_IFLG_START)) { + result = -EBUSY; + goto unlock; + } + + if (start) + timeri->ticks = timeri->cticks = ticks; + else if (!timeri->cticks) + timeri->cticks = 1; + timeri->pticks = 0; + + list_move_tail(&timeri->active_list, &timer->active_list_head); + if (timer->running) { + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + goto __start_now; + timer->flags |= SNDRV_TIMER_FLG_RESCHED; + timeri->flags |= SNDRV_TIMER_IFLG_START; + result = 1; /* delayed start */ + } else { + if (start) + timer->sticks = ticks; + timer->hw.start(timer); + __start_now: + timer->running++; + timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; + result = 0; + } + snd_timer_notify1(timeri, start ? SNDRV_TIMER_EVENT_START : + SNDRV_TIMER_EVENT_CONTINUE); + unlock: + spin_unlock_irqrestore(&timer->lock, flags); + return result; +} + +/* start/continue a slave timer */ +static int snd_timer_start_slave(struct snd_timer_instance *timeri, + bool start) +{ + unsigned long flags; + + spin_lock_irqsave(&slave_active_lock, flags); + if (timeri->flags & SNDRV_TIMER_IFLG_RUNNING) { + spin_unlock_irqrestore(&slave_active_lock, flags); + return -EBUSY; + } + timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; + if (timeri->master && timeri->timer) { + spin_lock(&timeri->timer->lock); + list_add_tail(&timeri->active_list, + &timeri->master->slave_active_head); + snd_timer_notify1(timeri, start ? SNDRV_TIMER_EVENT_START : + SNDRV_TIMER_EVENT_CONTINUE); + spin_unlock(&timeri->timer->lock); + } + spin_unlock_irqrestore(&slave_active_lock, flags); + return 1; /* delayed start */ +} + +/* stop/pause a master timer */ +static int snd_timer_stop1(struct snd_timer_instance *timeri, bool stop) +{ + struct snd_timer *timer; + int result = 0; + unsigned long flags; + + timer = timeri->timer; + if (!timer) + return -EINVAL; + spin_lock_irqsave(&timer->lock, flags); + list_del_init(&timeri->ack_list); + list_del_init(&timeri->active_list); + if (!(timeri->flags & (SNDRV_TIMER_IFLG_RUNNING | + SNDRV_TIMER_IFLG_START))) { + result = -EBUSY; + goto unlock; + } + if (timer->card && timer->card->shutdown) + goto unlock; + if (stop) { + timeri->cticks = timeri->ticks; + timeri->pticks = 0; + } + if ((timeri->flags & SNDRV_TIMER_IFLG_RUNNING) && + !(--timer->running)) { + timer->hw.stop(timer); + if (timer->flags & SNDRV_TIMER_FLG_RESCHED) { + timer->flags &= ~SNDRV_TIMER_FLG_RESCHED; + snd_timer_reschedule(timer, 0); + if (timer->flags & SNDRV_TIMER_FLG_CHANGE) { + timer->flags &= ~SNDRV_TIMER_FLG_CHANGE; + timer->hw.start(timer); + } + } + } + timeri->flags &= ~(SNDRV_TIMER_IFLG_RUNNING | SNDRV_TIMER_IFLG_START); + if (stop) + timeri->flags &= ~SNDRV_TIMER_IFLG_PAUSED; + else + timeri->flags |= SNDRV_TIMER_IFLG_PAUSED; + snd_timer_notify1(timeri, stop ? SNDRV_TIMER_EVENT_STOP : + SNDRV_TIMER_EVENT_PAUSE); + unlock: + spin_unlock_irqrestore(&timer->lock, flags); + return result; +} + +/* stop/pause a slave timer */ +static int snd_timer_stop_slave(struct snd_timer_instance *timeri, bool stop) +{ + unsigned long flags; + bool running; + + spin_lock_irqsave(&slave_active_lock, flags); + running = timeri->flags & SNDRV_TIMER_IFLG_RUNNING; + timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING; + if (timeri->timer) { + spin_lock(&timeri->timer->lock); + list_del_init(&timeri->ack_list); + list_del_init(&timeri->active_list); + if (running) + snd_timer_notify1(timeri, stop ? SNDRV_TIMER_EVENT_STOP : + SNDRV_TIMER_EVENT_PAUSE); + spin_unlock(&timeri->timer->lock); + } + spin_unlock_irqrestore(&slave_active_lock, flags); + return running ? 0 : -EBUSY; +} + +/* + * start the timer instance + */ +int snd_timer_start(struct snd_timer_instance *timeri, unsigned int ticks) +{ + if (timeri == NULL || ticks < 1) + return -EINVAL; + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_start_slave(timeri, true); + else + return snd_timer_start1(timeri, true, ticks); +} +EXPORT_SYMBOL(snd_timer_start); + +/* + * stop the timer instance. + * + * do not call this from the timer callback! + */ +int snd_timer_stop(struct snd_timer_instance *timeri) +{ + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_stop_slave(timeri, true); + else + return snd_timer_stop1(timeri, true); +} +EXPORT_SYMBOL(snd_timer_stop); + +/* + * start again.. the tick is kept. + */ +int snd_timer_continue(struct snd_timer_instance *timeri) +{ + /* timer can continue only after pause */ + if (!(timeri->flags & SNDRV_TIMER_IFLG_PAUSED)) + return -EINVAL; + + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_start_slave(timeri, false); + else + return snd_timer_start1(timeri, false, 0); +} +EXPORT_SYMBOL(snd_timer_continue); + +/* + * pause.. remember the ticks left + */ +int snd_timer_pause(struct snd_timer_instance * timeri) +{ + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_stop_slave(timeri, false); + else + return snd_timer_stop1(timeri, false); +} +EXPORT_SYMBOL(snd_timer_pause); + +/* + * reschedule the timer + * + * start pending instances and check the scheduling ticks. + * when the scheduling ticks is changed set CHANGE flag to reprogram the timer. + */ +static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_left) +{ + struct snd_timer_instance *ti; + unsigned long ticks = ~0UL; + + list_for_each_entry(ti, &timer->active_list_head, active_list) { + if (ti->flags & SNDRV_TIMER_IFLG_START) { + ti->flags &= ~SNDRV_TIMER_IFLG_START; + ti->flags |= SNDRV_TIMER_IFLG_RUNNING; + timer->running++; + } + if (ti->flags & SNDRV_TIMER_IFLG_RUNNING) { + if (ticks > ti->cticks) + ticks = ti->cticks; + } + } + if (ticks == ~0UL) { + timer->flags &= ~SNDRV_TIMER_FLG_RESCHED; + return; + } + if (ticks > timer->hw.ticks) + ticks = timer->hw.ticks; + if (ticks_left != ticks) + timer->flags |= SNDRV_TIMER_FLG_CHANGE; + timer->sticks = ticks; +} + +/* + * timer tasklet + * + */ +static void snd_timer_tasklet(unsigned long arg) +{ + struct snd_timer *timer = (struct snd_timer *) arg; + struct snd_timer_instance *ti; + struct list_head *p; + unsigned long resolution, ticks; + unsigned long flags; + + if (timer->card && timer->card->shutdown) + return; + + spin_lock_irqsave(&timer->lock, flags); + /* now process all callbacks */ + while (!list_empty(&timer->sack_list_head)) { + p = timer->sack_list_head.next; /* get first item */ + ti = list_entry(p, struct snd_timer_instance, ack_list); + + /* remove from ack_list and make empty */ + list_del_init(p); + + ticks = ti->pticks; + ti->pticks = 0; + resolution = ti->resolution; + + ti->flags |= SNDRV_TIMER_IFLG_CALLBACK; + spin_unlock(&timer->lock); + if (ti->callback) + ti->callback(ti, resolution, ticks); + spin_lock(&timer->lock); + ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK; + } + spin_unlock_irqrestore(&timer->lock, flags); +} + +/* + * timer interrupt + * + * ticks_left is usually equal to timer->sticks. + * + */ +void snd_timer_interrupt(struct snd_timer * timer, unsigned long ticks_left) +{ + struct snd_timer_instance *ti, *ts, *tmp; + unsigned long resolution, ticks; + struct list_head *p, *ack_list_head; + unsigned long flags; + int use_tasklet = 0; + + if (timer == NULL) + return; + + if (timer->card && timer->card->shutdown) + return; + + spin_lock_irqsave(&timer->lock, flags); + + /* remember the current resolution */ + resolution = snd_timer_hw_resolution(timer); + + /* loop for all active instances + * Here we cannot use list_for_each_entry because the active_list of a + * processed instance is relinked to done_list_head before the callback + * is called. + */ + list_for_each_entry_safe(ti, tmp, &timer->active_list_head, + active_list) { + if (!(ti->flags & SNDRV_TIMER_IFLG_RUNNING)) + continue; + ti->pticks += ticks_left; + ti->resolution = resolution; + if (ti->cticks < ticks_left) + ti->cticks = 0; + else + ti->cticks -= ticks_left; + if (ti->cticks) /* not expired */ + continue; + if (ti->flags & SNDRV_TIMER_IFLG_AUTO) { + ti->cticks = ti->ticks; + } else { + ti->flags &= ~SNDRV_TIMER_IFLG_RUNNING; + --timer->running; + list_del_init(&ti->active_list); + } + if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) || + (ti->flags & SNDRV_TIMER_IFLG_FAST)) + ack_list_head = &timer->ack_list_head; + else + ack_list_head = &timer->sack_list_head; + if (list_empty(&ti->ack_list)) + list_add_tail(&ti->ack_list, ack_list_head); + list_for_each_entry(ts, &ti->slave_active_head, active_list) { + ts->pticks = ti->pticks; + ts->resolution = resolution; + if (list_empty(&ts->ack_list)) + list_add_tail(&ts->ack_list, ack_list_head); + } + } + if (timer->flags & SNDRV_TIMER_FLG_RESCHED) + snd_timer_reschedule(timer, timer->sticks); + if (timer->running) { + if (timer->hw.flags & SNDRV_TIMER_HW_STOP) { + timer->hw.stop(timer); + timer->flags |= SNDRV_TIMER_FLG_CHANGE; + } + if (!(timer->hw.flags & SNDRV_TIMER_HW_AUTO) || + (timer->flags & SNDRV_TIMER_FLG_CHANGE)) { + /* restart timer */ + timer->flags &= ~SNDRV_TIMER_FLG_CHANGE; + timer->hw.start(timer); + } + } else { + timer->hw.stop(timer); + } + + /* now process all fast callbacks */ + while (!list_empty(&timer->ack_list_head)) { + p = timer->ack_list_head.next; /* get first item */ + ti = list_entry(p, struct snd_timer_instance, ack_list); + + /* remove from ack_list and make empty */ + list_del_init(p); + + ticks = ti->pticks; + ti->pticks = 0; + + ti->flags |= SNDRV_TIMER_IFLG_CALLBACK; + spin_unlock(&timer->lock); + if (ti->callback) + ti->callback(ti, resolution, ticks); + spin_lock(&timer->lock); + ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK; + } + + /* do we have any slow callbacks? */ + use_tasklet = !list_empty(&timer->sack_list_head); + spin_unlock_irqrestore(&timer->lock, flags); + + if (use_tasklet) + tasklet_schedule(&timer->task_queue); +} +EXPORT_SYMBOL(snd_timer_interrupt); + +/* + + */ + +int snd_timer_new(struct snd_card *card, char *id, struct snd_timer_id *tid, + struct snd_timer **rtimer) +{ + struct snd_timer *timer; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_timer_dev_free, + .dev_register = snd_timer_dev_register, + .dev_disconnect = snd_timer_dev_disconnect, + }; + + if (snd_BUG_ON(!tid)) + return -EINVAL; + if (tid->dev_class == SNDRV_TIMER_CLASS_CARD || + tid->dev_class == SNDRV_TIMER_CLASS_PCM) { + if (WARN_ON(!card)) + return -EINVAL; + } + if (rtimer) + *rtimer = NULL; + timer = kzalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) + return -ENOMEM; + timer->tmr_class = tid->dev_class; + timer->card = card; + timer->tmr_device = tid->device; + timer->tmr_subdevice = tid->subdevice; + if (id) + strlcpy(timer->id, id, sizeof(timer->id)); + timer->sticks = 1; + INIT_LIST_HEAD(&timer->device_list); + INIT_LIST_HEAD(&timer->open_list_head); + INIT_LIST_HEAD(&timer->active_list_head); + INIT_LIST_HEAD(&timer->ack_list_head); + INIT_LIST_HEAD(&timer->sack_list_head); + spin_lock_init(&timer->lock); + tasklet_init(&timer->task_queue, snd_timer_tasklet, + (unsigned long)timer); + timer->max_instances = 1000; /* default limit per timer */ + if (card != NULL) { + timer->module = card->module; + err = snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops); + if (err < 0) { + snd_timer_free(timer); + return err; + } + } + if (rtimer) + *rtimer = timer; + return 0; +} +EXPORT_SYMBOL(snd_timer_new); + +static int snd_timer_free(struct snd_timer *timer) +{ + if (!timer) + return 0; + + mutex_lock(®ister_mutex); + if (! list_empty(&timer->open_list_head)) { + struct list_head *p, *n; + struct snd_timer_instance *ti; + pr_warn("ALSA: timer %p is busy?\n", timer); + list_for_each_safe(p, n, &timer->open_list_head) { + list_del_init(p); + ti = list_entry(p, struct snd_timer_instance, open_list); + ti->timer = NULL; + } + } + list_del(&timer->device_list); + mutex_unlock(®ister_mutex); + + if (timer->private_free) + timer->private_free(timer); + kfree(timer); + return 0; +} + +static int snd_timer_dev_free(struct snd_device *device) +{ + struct snd_timer *timer = device->device_data; + return snd_timer_free(timer); +} + +static int snd_timer_dev_register(struct snd_device *dev) +{ + struct snd_timer *timer = dev->device_data; + struct snd_timer *timer1; + + if (snd_BUG_ON(!timer || !timer->hw.start || !timer->hw.stop)) + return -ENXIO; + if (!(timer->hw.flags & SNDRV_TIMER_HW_SLAVE) && + !timer->hw.resolution && timer->hw.c_resolution == NULL) + return -EINVAL; + + mutex_lock(®ister_mutex); + list_for_each_entry(timer1, &snd_timer_list, device_list) { + if (timer1->tmr_class > timer->tmr_class) + break; + if (timer1->tmr_class < timer->tmr_class) + continue; + if (timer1->card && timer->card) { + if (timer1->card->number > timer->card->number) + break; + if (timer1->card->number < timer->card->number) + continue; + } + if (timer1->tmr_device > timer->tmr_device) + break; + if (timer1->tmr_device < timer->tmr_device) + continue; + if (timer1->tmr_subdevice > timer->tmr_subdevice) + break; + if (timer1->tmr_subdevice < timer->tmr_subdevice) + continue; + /* conflicts.. */ + mutex_unlock(®ister_mutex); + return -EBUSY; + } + list_add_tail(&timer->device_list, &timer1->device_list); + mutex_unlock(®ister_mutex); + return 0; +} + +static int snd_timer_dev_disconnect(struct snd_device *device) +{ + struct snd_timer *timer = device->device_data; + struct snd_timer_instance *ti; + + mutex_lock(®ister_mutex); + list_del_init(&timer->device_list); + /* wake up pending sleepers */ + list_for_each_entry(ti, &timer->open_list_head, open_list) { + if (ti->disconnect) + ti->disconnect(ti); + } + mutex_unlock(®ister_mutex); + return 0; +} + +void snd_timer_notify(struct snd_timer *timer, int event, struct timespec *tstamp) +{ + unsigned long flags; + unsigned long resolution = 0; + struct snd_timer_instance *ti, *ts; + + if (timer->card && timer->card->shutdown) + return; + if (! (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)) + return; + if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_MSTART || + event > SNDRV_TIMER_EVENT_MRESUME)) + return; + spin_lock_irqsave(&timer->lock, flags); + if (event == SNDRV_TIMER_EVENT_MSTART || + event == SNDRV_TIMER_EVENT_MCONTINUE || + event == SNDRV_TIMER_EVENT_MRESUME) + resolution = snd_timer_hw_resolution(timer); + list_for_each_entry(ti, &timer->active_list_head, active_list) { + if (ti->ccallback) + ti->ccallback(ti, event, tstamp, resolution); + list_for_each_entry(ts, &ti->slave_active_head, active_list) + if (ts->ccallback) + ts->ccallback(ts, event, tstamp, resolution); + } + spin_unlock_irqrestore(&timer->lock, flags); +} +EXPORT_SYMBOL(snd_timer_notify); + +/* + * exported functions for global timers + */ +int snd_timer_global_new(char *id, int device, struct snd_timer **rtimer) +{ + struct snd_timer_id tid; + + tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = -1; + tid.device = device; + tid.subdevice = 0; + return snd_timer_new(NULL, id, &tid, rtimer); +} +EXPORT_SYMBOL(snd_timer_global_new); + +int snd_timer_global_free(struct snd_timer *timer) +{ + return snd_timer_free(timer); +} +EXPORT_SYMBOL(snd_timer_global_free); + +int snd_timer_global_register(struct snd_timer *timer) +{ + struct snd_device dev; + + memset(&dev, 0, sizeof(dev)); + dev.device_data = timer; + return snd_timer_dev_register(&dev); +} +EXPORT_SYMBOL(snd_timer_global_register); + +/* + * System timer + */ + +struct snd_timer_system_private { + struct timer_list tlist; + struct snd_timer *snd_timer; + unsigned long last_expires; + unsigned long last_jiffies; + unsigned long correction; +}; + +static void snd_timer_s_function(struct timer_list *t) +{ + struct snd_timer_system_private *priv = from_timer(priv, t, + tlist); + struct snd_timer *timer = priv->snd_timer; + unsigned long jiff = jiffies; + if (time_after(jiff, priv->last_expires)) + priv->correction += (long)jiff - (long)priv->last_expires; + snd_timer_interrupt(timer, (long)jiff - (long)priv->last_jiffies); +} + +static int snd_timer_s_start(struct snd_timer * timer) +{ + struct snd_timer_system_private *priv; + unsigned long njiff; + + priv = (struct snd_timer_system_private *) timer->private_data; + njiff = (priv->last_jiffies = jiffies); + if (priv->correction > timer->sticks - 1) { + priv->correction -= timer->sticks - 1; + njiff++; + } else { + njiff += timer->sticks - priv->correction; + priv->correction = 0; + } + priv->last_expires = njiff; + mod_timer(&priv->tlist, njiff); + return 0; +} + +static int snd_timer_s_stop(struct snd_timer * timer) +{ + struct snd_timer_system_private *priv; + unsigned long jiff; + + priv = (struct snd_timer_system_private *) timer->private_data; + del_timer(&priv->tlist); + jiff = jiffies; + if (time_before(jiff, priv->last_expires)) + timer->sticks = priv->last_expires - jiff; + else + timer->sticks = 1; + priv->correction = 0; + return 0; +} + +static int snd_timer_s_close(struct snd_timer *timer) +{ + struct snd_timer_system_private *priv; + + priv = (struct snd_timer_system_private *)timer->private_data; + del_timer_sync(&priv->tlist); + return 0; +} + +static struct snd_timer_hardware snd_timer_system = +{ + .flags = SNDRV_TIMER_HW_FIRST | SNDRV_TIMER_HW_TASKLET, + .resolution = 1000000000L / HZ, + .ticks = 10000000L, + .close = snd_timer_s_close, + .start = snd_timer_s_start, + .stop = snd_timer_s_stop +}; + +static void snd_timer_free_system(struct snd_timer *timer) +{ + kfree(timer->private_data); +} + +static int snd_timer_register_system(void) +{ + struct snd_timer *timer; + struct snd_timer_system_private *priv; + int err; + + err = snd_timer_global_new("system", SNDRV_TIMER_GLOBAL_SYSTEM, &timer); + if (err < 0) + return err; + strcpy(timer->name, "system timer"); + timer->hw = snd_timer_system; + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + snd_timer_free(timer); + return -ENOMEM; + } + priv->snd_timer = timer; + timer_setup(&priv->tlist, snd_timer_s_function, 0); + timer->private_data = priv; + timer->private_free = snd_timer_free_system; + return snd_timer_global_register(timer); +} + +#ifdef CONFIG_SND_PROC_FS +/* + * Info interface + */ + +static void snd_timer_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_timer *timer; + struct snd_timer_instance *ti; + + mutex_lock(®ister_mutex); + list_for_each_entry(timer, &snd_timer_list, device_list) { + if (timer->card && timer->card->shutdown) + continue; + switch (timer->tmr_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + snd_iprintf(buffer, "G%i: ", timer->tmr_device); + break; + case SNDRV_TIMER_CLASS_CARD: + snd_iprintf(buffer, "C%i-%i: ", + timer->card->number, timer->tmr_device); + break; + case SNDRV_TIMER_CLASS_PCM: + snd_iprintf(buffer, "P%i-%i-%i: ", timer->card->number, + timer->tmr_device, timer->tmr_subdevice); + break; + default: + snd_iprintf(buffer, "?%i-%i-%i-%i: ", timer->tmr_class, + timer->card ? timer->card->number : -1, + timer->tmr_device, timer->tmr_subdevice); + } + snd_iprintf(buffer, "%s :", timer->name); + if (timer->hw.resolution) + snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", + timer->hw.resolution / 1000, + timer->hw.resolution % 1000, + timer->hw.ticks); + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + snd_iprintf(buffer, " SLAVE"); + snd_iprintf(buffer, "\n"); + list_for_each_entry(ti, &timer->open_list_head, open_list) + snd_iprintf(buffer, " Client %s : %s\n", + ti->owner ? ti->owner : "unknown", + ti->flags & (SNDRV_TIMER_IFLG_START | + SNDRV_TIMER_IFLG_RUNNING) + ? "running" : "stopped"); + } + mutex_unlock(®ister_mutex); +} + +static struct snd_info_entry *snd_timer_proc_entry; + +static void __init snd_timer_proc_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "timers", NULL); + if (entry != NULL) { + entry->c.text.read = snd_timer_proc_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_timer_proc_entry = entry; +} + +static void __exit snd_timer_proc_done(void) +{ + snd_info_free_entry(snd_timer_proc_entry); +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_timer_proc_init() +#define snd_timer_proc_done() +#endif + +/* + * USER SPACE interface + */ + +static void snd_timer_user_interrupt(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + struct snd_timer_user *tu = timeri->callback_data; + struct snd_timer_read *r; + int prev; + + spin_lock(&tu->qlock); + if (tu->qused > 0) { + prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1; + r = &tu->queue[prev]; + if (r->resolution == resolution) { + r->ticks += ticks; + goto __wake; + } + } + if (tu->qused >= tu->queue_size) { + tu->overrun++; + } else { + r = &tu->queue[tu->qtail++]; + tu->qtail %= tu->queue_size; + r->resolution = resolution; + r->ticks = ticks; + tu->qused++; + } + __wake: + spin_unlock(&tu->qlock); + kill_fasync(&tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_append_to_tqueue(struct snd_timer_user *tu, + struct snd_timer_tread *tread) +{ + if (tu->qused >= tu->queue_size) { + tu->overrun++; + } else { + memcpy(&tu->tqueue[tu->qtail++], tread, sizeof(*tread)); + tu->qtail %= tu->queue_size; + tu->qused++; + } +} + +static void snd_timer_user_ccallback(struct snd_timer_instance *timeri, + int event, + struct timespec *tstamp, + unsigned long resolution) +{ + struct snd_timer_user *tu = timeri->callback_data; + struct snd_timer_tread r1; + unsigned long flags; + + if (event >= SNDRV_TIMER_EVENT_START && + event <= SNDRV_TIMER_EVENT_PAUSE) + tu->tstamp = *tstamp; + if ((tu->filter & (1 << event)) == 0 || !tu->tread) + return; + memset(&r1, 0, sizeof(r1)); + r1.event = event; + r1.tstamp = *tstamp; + r1.val = resolution; + spin_lock_irqsave(&tu->qlock, flags); + snd_timer_user_append_to_tqueue(tu, &r1); + spin_unlock_irqrestore(&tu->qlock, flags); + kill_fasync(&tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_disconnect(struct snd_timer_instance *timeri) +{ + struct snd_timer_user *tu = timeri->callback_data; + + tu->disconnected = true; + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + struct snd_timer_user *tu = timeri->callback_data; + struct snd_timer_tread *r, r1; + struct timespec tstamp; + int prev, append = 0; + + memset(&r1, 0, sizeof(r1)); + memset(&tstamp, 0, sizeof(tstamp)); + spin_lock(&tu->qlock); + if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) | + (1 << SNDRV_TIMER_EVENT_TICK))) == 0) { + spin_unlock(&tu->qlock); + return; + } + if (tu->last_resolution != resolution || ticks > 0) { + if (timer_tstamp_monotonic) + ktime_get_ts(&tstamp); + else + getnstimeofday(&tstamp); + } + if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && + tu->last_resolution != resolution) { + r1.event = SNDRV_TIMER_EVENT_RESOLUTION; + r1.tstamp = tstamp; + r1.val = resolution; + snd_timer_user_append_to_tqueue(tu, &r1); + tu->last_resolution = resolution; + append++; + } + if ((tu->filter & (1 << SNDRV_TIMER_EVENT_TICK)) == 0) + goto __wake; + if (ticks == 0) + goto __wake; + if (tu->qused > 0) { + prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1; + r = &tu->tqueue[prev]; + if (r->event == SNDRV_TIMER_EVENT_TICK) { + r->tstamp = tstamp; + r->val += ticks; + append++; + goto __wake; + } + } + r1.event = SNDRV_TIMER_EVENT_TICK; + r1.tstamp = tstamp; + r1.val = ticks; + snd_timer_user_append_to_tqueue(tu, &r1); + append++; + __wake: + spin_unlock(&tu->qlock); + if (append == 0) + return; + kill_fasync(&tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static int realloc_user_queue(struct snd_timer_user *tu, int size) +{ + struct snd_timer_read *queue = NULL; + struct snd_timer_tread *tqueue = NULL; + + if (tu->tread) { + tqueue = kcalloc(size, sizeof(*tqueue), GFP_KERNEL); + if (!tqueue) + return -ENOMEM; + } else { + queue = kcalloc(size, sizeof(*queue), GFP_KERNEL); + if (!queue) + return -ENOMEM; + } + + spin_lock_irq(&tu->qlock); + kfree(tu->queue); + kfree(tu->tqueue); + tu->queue_size = size; + tu->queue = queue; + tu->tqueue = tqueue; + tu->qhead = tu->qtail = tu->qused = 0; + spin_unlock_irq(&tu->qlock); + + return 0; +} + +static int snd_timer_user_open(struct inode *inode, struct file *file) +{ + struct snd_timer_user *tu; + int err; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + tu = kzalloc(sizeof(*tu), GFP_KERNEL); + if (tu == NULL) + return -ENOMEM; + spin_lock_init(&tu->qlock); + init_waitqueue_head(&tu->qchange_sleep); + mutex_init(&tu->ioctl_lock); + tu->ticks = 1; + if (realloc_user_queue(tu, 128) < 0) { + kfree(tu); + return -ENOMEM; + } + file->private_data = tu; + return 0; +} + +static int snd_timer_user_release(struct inode *inode, struct file *file) +{ + struct snd_timer_user *tu; + + if (file->private_data) { + tu = file->private_data; + file->private_data = NULL; + mutex_lock(&tu->ioctl_lock); + if (tu->timeri) + snd_timer_close(tu->timeri); + mutex_unlock(&tu->ioctl_lock); + kfree(tu->queue); + kfree(tu->tqueue); + kfree(tu); + } + return 0; +} + +static void snd_timer_user_zero_id(struct snd_timer_id *id) +{ + id->dev_class = SNDRV_TIMER_CLASS_NONE; + id->dev_sclass = SNDRV_TIMER_SCLASS_NONE; + id->card = -1; + id->device = -1; + id->subdevice = -1; +} + +static void snd_timer_user_copy_id(struct snd_timer_id *id, struct snd_timer *timer) +{ + id->dev_class = timer->tmr_class; + id->dev_sclass = SNDRV_TIMER_SCLASS_NONE; + id->card = timer->card ? timer->card->number : -1; + id->device = timer->tmr_device; + id->subdevice = timer->tmr_subdevice; +} + +static int snd_timer_user_next_device(struct snd_timer_id __user *_tid) +{ + struct snd_timer_id id; + struct snd_timer *timer; + struct list_head *p; + + if (copy_from_user(&id, _tid, sizeof(id))) + return -EFAULT; + mutex_lock(®ister_mutex); + if (id.dev_class < 0) { /* first item */ + if (list_empty(&snd_timer_list)) + snd_timer_user_zero_id(&id); + else { + timer = list_entry(snd_timer_list.next, + struct snd_timer, device_list); + snd_timer_user_copy_id(&id, timer); + } + } else { + switch (id.dev_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + id.device = id.device < 0 ? 0 : id.device + 1; + list_for_each(p, &snd_timer_list) { + timer = list_entry(p, struct snd_timer, device_list); + if (timer->tmr_class > SNDRV_TIMER_CLASS_GLOBAL) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_device >= id.device) { + snd_timer_user_copy_id(&id, timer); + break; + } + } + if (p == &snd_timer_list) + snd_timer_user_zero_id(&id); + break; + case SNDRV_TIMER_CLASS_CARD: + case SNDRV_TIMER_CLASS_PCM: + if (id.card < 0) { + id.card = 0; + } else { + if (id.device < 0) { + id.device = 0; + } else { + if (id.subdevice < 0) + id.subdevice = 0; + else if (id.subdevice < INT_MAX) + id.subdevice++; + } + } + list_for_each(p, &snd_timer_list) { + timer = list_entry(p, struct snd_timer, device_list); + if (timer->tmr_class > id.dev_class) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_class < id.dev_class) + continue; + if (timer->card->number > id.card) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->card->number < id.card) + continue; + if (timer->tmr_device > id.device) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_device < id.device) + continue; + if (timer->tmr_subdevice > id.subdevice) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_subdevice < id.subdevice) + continue; + snd_timer_user_copy_id(&id, timer); + break; + } + if (p == &snd_timer_list) + snd_timer_user_zero_id(&id); + break; + default: + snd_timer_user_zero_id(&id); + } + } + mutex_unlock(®ister_mutex); + if (copy_to_user(_tid, &id, sizeof(*_tid))) + return -EFAULT; + return 0; +} + +static int snd_timer_user_ginfo(struct file *file, + struct snd_timer_ginfo __user *_ginfo) +{ + struct snd_timer_ginfo *ginfo; + struct snd_timer_id tid; + struct snd_timer *t; + struct list_head *p; + int err = 0; + + ginfo = memdup_user(_ginfo, sizeof(*ginfo)); + if (IS_ERR(ginfo)) + return PTR_ERR(ginfo); + + tid = ginfo->tid; + memset(ginfo, 0, sizeof(*ginfo)); + ginfo->tid = tid; + mutex_lock(®ister_mutex); + t = snd_timer_find(&tid); + if (t != NULL) { + ginfo->card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + ginfo->flags |= SNDRV_TIMER_FLG_SLAVE; + strlcpy(ginfo->id, t->id, sizeof(ginfo->id)); + strlcpy(ginfo->name, t->name, sizeof(ginfo->name)); + ginfo->resolution = t->hw.resolution; + if (t->hw.resolution_min > 0) { + ginfo->resolution_min = t->hw.resolution_min; + ginfo->resolution_max = t->hw.resolution_max; + } + list_for_each(p, &t->open_list_head) { + ginfo->clients++; + } + } else { + err = -ENODEV; + } + mutex_unlock(®ister_mutex); + if (err >= 0 && copy_to_user(_ginfo, ginfo, sizeof(*ginfo))) + err = -EFAULT; + kfree(ginfo); + return err; +} + +static int timer_set_gparams(struct snd_timer_gparams *gparams) +{ + struct snd_timer *t; + int err; + + mutex_lock(®ister_mutex); + t = snd_timer_find(&gparams->tid); + if (!t) { + err = -ENODEV; + goto _error; + } + if (!list_empty(&t->open_list_head)) { + err = -EBUSY; + goto _error; + } + if (!t->hw.set_period) { + err = -ENOSYS; + goto _error; + } + err = t->hw.set_period(t, gparams->period_num, gparams->period_den); +_error: + mutex_unlock(®ister_mutex); + return err; +} + +static int snd_timer_user_gparams(struct file *file, + struct snd_timer_gparams __user *_gparams) +{ + struct snd_timer_gparams gparams; + + if (copy_from_user(&gparams, _gparams, sizeof(gparams))) + return -EFAULT; + return timer_set_gparams(&gparams); +} + +static int snd_timer_user_gstatus(struct file *file, + struct snd_timer_gstatus __user *_gstatus) +{ + struct snd_timer_gstatus gstatus; + struct snd_timer_id tid; + struct snd_timer *t; + int err = 0; + + if (copy_from_user(&gstatus, _gstatus, sizeof(gstatus))) + return -EFAULT; + tid = gstatus.tid; + memset(&gstatus, 0, sizeof(gstatus)); + gstatus.tid = tid; + mutex_lock(®ister_mutex); + t = snd_timer_find(&tid); + if (t != NULL) { + spin_lock_irq(&t->lock); + gstatus.resolution = snd_timer_hw_resolution(t); + if (t->hw.precise_resolution) { + t->hw.precise_resolution(t, &gstatus.resolution_num, + &gstatus.resolution_den); + } else { + gstatus.resolution_num = gstatus.resolution; + gstatus.resolution_den = 1000000000uL; + } + spin_unlock_irq(&t->lock); + } else { + err = -ENODEV; + } + mutex_unlock(®ister_mutex); + if (err >= 0 && copy_to_user(_gstatus, &gstatus, sizeof(gstatus))) + err = -EFAULT; + return err; +} + +static int snd_timer_user_tselect(struct file *file, + struct snd_timer_select __user *_tselect) +{ + struct snd_timer_user *tu; + struct snd_timer_select tselect; + char str[32]; + int err = 0; + + tu = file->private_data; + if (tu->timeri) { + snd_timer_close(tu->timeri); + tu->timeri = NULL; + } + if (copy_from_user(&tselect, _tselect, sizeof(tselect))) { + err = -EFAULT; + goto __err; + } + sprintf(str, "application %i", current->pid); + if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE) + tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION; + err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid); + if (err < 0) + goto __err; + + tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST; + tu->timeri->callback = tu->tread + ? snd_timer_user_tinterrupt : snd_timer_user_interrupt; + tu->timeri->ccallback = snd_timer_user_ccallback; + tu->timeri->callback_data = (void *)tu; + tu->timeri->disconnect = snd_timer_user_disconnect; + + __err: + return err; +} + +static int snd_timer_user_info(struct file *file, + struct snd_timer_info __user *_info) +{ + struct snd_timer_user *tu; + struct snd_timer_info *info; + struct snd_timer *t; + int err = 0; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + t = tu->timeri->timer; + if (!t) + return -EBADFD; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + info->card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + info->flags |= SNDRV_TIMER_FLG_SLAVE; + strlcpy(info->id, t->id, sizeof(info->id)); + strlcpy(info->name, t->name, sizeof(info->name)); + info->resolution = t->hw.resolution; + if (copy_to_user(_info, info, sizeof(*_info))) + err = -EFAULT; + kfree(info); + return err; +} + +static int snd_timer_user_params(struct file *file, + struct snd_timer_params __user *_params) +{ + struct snd_timer_user *tu; + struct snd_timer_params params; + struct snd_timer *t; + int err; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + t = tu->timeri->timer; + if (!t) + return -EBADFD; + if (copy_from_user(¶ms, _params, sizeof(params))) + return -EFAULT; + if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE)) { + u64 resolution; + + if (params.ticks < 1) { + err = -EINVAL; + goto _end; + } + + /* Don't allow resolution less than 1ms */ + resolution = snd_timer_resolution(tu->timeri); + resolution *= params.ticks; + if (resolution < 1000000) { + err = -EINVAL; + goto _end; + } + } + if (params.queue_size > 0 && + (params.queue_size < 32 || params.queue_size > 1024)) { + err = -EINVAL; + goto _end; + } + if (params.filter & ~((1<timeri); + spin_lock_irq(&t->lock); + tu->timeri->flags &= ~(SNDRV_TIMER_IFLG_AUTO| + SNDRV_TIMER_IFLG_EXCLUSIVE| + SNDRV_TIMER_IFLG_EARLY_EVENT); + if (params.flags & SNDRV_TIMER_PSFLG_AUTO) + tu->timeri->flags |= SNDRV_TIMER_IFLG_AUTO; + if (params.flags & SNDRV_TIMER_PSFLG_EXCLUSIVE) + tu->timeri->flags |= SNDRV_TIMER_IFLG_EXCLUSIVE; + if (params.flags & SNDRV_TIMER_PSFLG_EARLY_EVENT) + tu->timeri->flags |= SNDRV_TIMER_IFLG_EARLY_EVENT; + spin_unlock_irq(&t->lock); + if (params.queue_size > 0 && + (unsigned int)tu->queue_size != params.queue_size) { + err = realloc_user_queue(tu, params.queue_size); + if (err < 0) + goto _end; + } + spin_lock_irq(&tu->qlock); + tu->qhead = tu->qtail = tu->qused = 0; + if (tu->timeri->flags & SNDRV_TIMER_IFLG_EARLY_EVENT) { + if (tu->tread) { + struct snd_timer_tread tread; + memset(&tread, 0, sizeof(tread)); + tread.event = SNDRV_TIMER_EVENT_EARLY; + tread.tstamp.tv_sec = 0; + tread.tstamp.tv_nsec = 0; + tread.val = 0; + snd_timer_user_append_to_tqueue(tu, &tread); + } else { + struct snd_timer_read *r = &tu->queue[0]; + r->resolution = 0; + r->ticks = 0; + tu->qused++; + tu->qtail++; + } + } + tu->filter = params.filter; + tu->ticks = params.ticks; + spin_unlock_irq(&tu->qlock); + err = 0; + _end: + if (copy_to_user(_params, ¶ms, sizeof(params))) + return -EFAULT; + return err; +} + +static int snd_timer_user_status(struct file *file, + struct snd_timer_status __user *_status) +{ + struct snd_timer_user *tu; + struct snd_timer_status status; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + memset(&status, 0, sizeof(status)); + status.tstamp = tu->tstamp; + status.resolution = snd_timer_resolution(tu->timeri); + status.lost = tu->timeri->lost; + status.overrun = tu->overrun; + spin_lock_irq(&tu->qlock); + status.queue = tu->qused; + spin_unlock_irq(&tu->qlock); + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +static int snd_timer_user_start(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + snd_timer_stop(tu->timeri); + tu->timeri->lost = 0; + tu->last_resolution = 0; + return (err = snd_timer_start(tu->timeri, tu->ticks)) < 0 ? err : 0; +} + +static int snd_timer_user_stop(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + return (err = snd_timer_stop(tu->timeri)) < 0 ? err : 0; +} + +static int snd_timer_user_continue(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + /* start timer instead of continue if it's not used before */ + if (!(tu->timeri->flags & SNDRV_TIMER_IFLG_PAUSED)) + return snd_timer_user_start(file); + tu->timeri->lost = 0; + return (err = snd_timer_continue(tu->timeri)) < 0 ? err : 0; +} + +static int snd_timer_user_pause(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + return (err = snd_timer_pause(tu->timeri)) < 0 ? err : 0; +} + +enum { + SNDRV_TIMER_IOCTL_START_OLD = _IO('T', 0x20), + SNDRV_TIMER_IOCTL_STOP_OLD = _IO('T', 0x21), + SNDRV_TIMER_IOCTL_CONTINUE_OLD = _IO('T', 0x22), + SNDRV_TIMER_IOCTL_PAUSE_OLD = _IO('T', 0x23), +}; + +static long __snd_timer_user_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_timer_user *tu; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + tu = file->private_data; + switch (cmd) { + case SNDRV_TIMER_IOCTL_PVERSION: + return put_user(SNDRV_TIMER_VERSION, p) ? -EFAULT : 0; + case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + return snd_timer_user_next_device(argp); + case SNDRV_TIMER_IOCTL_TREAD: + { + int xarg, old_tread; + + if (tu->timeri) /* too late */ + return -EBUSY; + if (get_user(xarg, p)) + return -EFAULT; + old_tread = tu->tread; + tu->tread = xarg ? 1 : 0; + if (tu->tread != old_tread && + realloc_user_queue(tu, tu->queue_size) < 0) { + tu->tread = old_tread; + return -ENOMEM; + } + return 0; + } + case SNDRV_TIMER_IOCTL_GINFO: + return snd_timer_user_ginfo(file, argp); + case SNDRV_TIMER_IOCTL_GPARAMS: + return snd_timer_user_gparams(file, argp); + case SNDRV_TIMER_IOCTL_GSTATUS: + return snd_timer_user_gstatus(file, argp); + case SNDRV_TIMER_IOCTL_SELECT: + return snd_timer_user_tselect(file, argp); + case SNDRV_TIMER_IOCTL_INFO: + return snd_timer_user_info(file, argp); + case SNDRV_TIMER_IOCTL_PARAMS: + return snd_timer_user_params(file, argp); + case SNDRV_TIMER_IOCTL_STATUS: + return snd_timer_user_status(file, argp); + case SNDRV_TIMER_IOCTL_START: + case SNDRV_TIMER_IOCTL_START_OLD: + return snd_timer_user_start(file); + case SNDRV_TIMER_IOCTL_STOP: + case SNDRV_TIMER_IOCTL_STOP_OLD: + return snd_timer_user_stop(file); + case SNDRV_TIMER_IOCTL_CONTINUE: + case SNDRV_TIMER_IOCTL_CONTINUE_OLD: + return snd_timer_user_continue(file); + case SNDRV_TIMER_IOCTL_PAUSE: + case SNDRV_TIMER_IOCTL_PAUSE_OLD: + return snd_timer_user_pause(file); + } + return -ENOTTY; +} + +static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_timer_user *tu = file->private_data; + long ret; + + mutex_lock(&tu->ioctl_lock); + ret = __snd_timer_user_ioctl(file, cmd, arg); + mutex_unlock(&tu->ioctl_lock); + return ret; +} + +static int snd_timer_user_fasync(int fd, struct file * file, int on) +{ + struct snd_timer_user *tu; + + tu = file->private_data; + return fasync_helper(fd, file, on, &tu->fasync); +} + +static ssize_t snd_timer_user_read(struct file *file, char __user *buffer, + size_t count, loff_t *offset) +{ + struct snd_timer_user *tu; + long result = 0, unit; + int qhead; + int err = 0; + + tu = file->private_data; + unit = tu->tread ? sizeof(struct snd_timer_tread) : sizeof(struct snd_timer_read); + mutex_lock(&tu->ioctl_lock); + spin_lock_irq(&tu->qlock); + while ((long)count - result >= unit) { + while (!tu->qused) { + wait_queue_entry_t wait; + + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + err = -EAGAIN; + goto _error; + } + + set_current_state(TASK_INTERRUPTIBLE); + init_waitqueue_entry(&wait, current); + add_wait_queue(&tu->qchange_sleep, &wait); + + spin_unlock_irq(&tu->qlock); + mutex_unlock(&tu->ioctl_lock); + schedule(); + mutex_lock(&tu->ioctl_lock); + spin_lock_irq(&tu->qlock); + + remove_wait_queue(&tu->qchange_sleep, &wait); + + if (tu->disconnected) { + err = -ENODEV; + goto _error; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto _error; + } + } + + qhead = tu->qhead++; + tu->qhead %= tu->queue_size; + tu->qused--; + spin_unlock_irq(&tu->qlock); + + if (tu->tread) { + if (copy_to_user(buffer, &tu->tqueue[qhead], + sizeof(struct snd_timer_tread))) + err = -EFAULT; + } else { + if (copy_to_user(buffer, &tu->queue[qhead], + sizeof(struct snd_timer_read))) + err = -EFAULT; + } + + spin_lock_irq(&tu->qlock); + if (err < 0) + goto _error; + result += unit; + buffer += unit; + } + _error: + spin_unlock_irq(&tu->qlock); + mutex_unlock(&tu->ioctl_lock); + return result > 0 ? result : err; +} + +static __poll_t snd_timer_user_poll(struct file *file, poll_table * wait) +{ + __poll_t mask; + struct snd_timer_user *tu; + + tu = file->private_data; + + poll_wait(file, &tu->qchange_sleep, wait); + + mask = 0; + spin_lock_irq(&tu->qlock); + if (tu->qused) + mask |= EPOLLIN | EPOLLRDNORM; + if (tu->disconnected) + mask |= EPOLLERR; + spin_unlock_irq(&tu->qlock); + + return mask; +} + +#ifdef CONFIG_COMPAT +#include "timer_compat.c" +#else +#define snd_timer_user_ioctl_compat NULL +#endif + +static const struct file_operations snd_timer_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_timer_user_read, + .open = snd_timer_user_open, + .release = snd_timer_user_release, + .llseek = no_llseek, + .poll = snd_timer_user_poll, + .unlocked_ioctl = snd_timer_user_ioctl, + .compat_ioctl = snd_timer_user_ioctl_compat, + .fasync = snd_timer_user_fasync, +}; + +/* unregister the system timer */ +static void snd_timer_free_all(void) +{ + struct snd_timer *timer, *n; + + list_for_each_entry_safe(timer, n, &snd_timer_list, device_list) + snd_timer_free(timer); +} + +static struct device timer_dev; + +/* + * ENTRY functions + */ + +static int __init alsa_timer_init(void) +{ + int err; + + snd_device_initialize(&timer_dev, NULL); + dev_set_name(&timer_dev, "timer"); + +#ifdef SNDRV_OSS_INFO_DEV_TIMERS + snd_oss_info_register(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1, + "system timer"); +#endif + + err = snd_timer_register_system(); + if (err < 0) { + pr_err("ALSA: unable to register system timer (%i)\n", err); + goto put_timer; + } + + err = snd_register_device(SNDRV_DEVICE_TYPE_TIMER, NULL, 0, + &snd_timer_f_ops, NULL, &timer_dev); + if (err < 0) { + pr_err("ALSA: unable to register timer device (%i)\n", err); + snd_timer_free_all(); + goto put_timer; + } + + snd_timer_proc_init(); + return 0; + +put_timer: + put_device(&timer_dev); + return err; +} + +static void __exit alsa_timer_exit(void) +{ + snd_unregister_device(&timer_dev); + snd_timer_free_all(); + put_device(&timer_dev); + snd_timer_proc_done(); +#ifdef SNDRV_OSS_INFO_DEV_TIMERS + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1); +#endif +} + +module_init(alsa_timer_init) +module_exit(alsa_timer_exit) diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c new file mode 100644 index 000000000..e00f7e399 --- /dev/null +++ b/sound/core/timer_compat.c @@ -0,0 +1,182 @@ +/* + * 32bit -> 64bit ioctl wrapper for timer API + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from timer.c */ + +#include + +/* + * ILP32/LP64 has different size for 'long' type. Additionally, the size + * of storage alignment differs depending on architectures. Here, '__packed' + * qualifier is used so that the size of this structure is multiple of 4 and + * it fits to any architectures with 32 bit storage alignment. + */ +struct snd_timer_gparams32 { + struct snd_timer_id tid; + u32 period_num; + u32 period_den; + unsigned char reserved[32]; +} __packed; + +struct snd_timer_info32 { + u32 flags; + s32 card; + unsigned char id[64]; + unsigned char name[80]; + u32 reserved0; + u32 resolution; + unsigned char reserved[64]; +}; + +static int snd_timer_user_gparams_compat(struct file *file, + struct snd_timer_gparams32 __user *user) +{ + struct snd_timer_gparams gparams; + + if (copy_from_user(&gparams.tid, &user->tid, sizeof(gparams.tid)) || + get_user(gparams.period_num, &user->period_num) || + get_user(gparams.period_den, &user->period_den)) + return -EFAULT; + + return timer_set_gparams(&gparams); +} + +static int snd_timer_user_info_compat(struct file *file, + struct snd_timer_info32 __user *_info) +{ + struct snd_timer_user *tu; + struct snd_timer_info32 info; + struct snd_timer *t; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + t = tu->timeri->timer; + if (!t) + return -EBADFD; + memset(&info, 0, sizeof(info)); + info.card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + info.flags |= SNDRV_TIMER_FLG_SLAVE; + strlcpy(info.id, t->id, sizeof(info.id)); + strlcpy(info.name, t->name, sizeof(info.name)); + info.resolution = t->hw.resolution; + if (copy_to_user(_info, &info, sizeof(*_info))) + return -EFAULT; + return 0; +} + +struct snd_timer_status32 { + struct compat_timespec tstamp; + u32 resolution; + u32 lost; + u32 overrun; + u32 queue; + unsigned char reserved[64]; +}; + +static int snd_timer_user_status_compat(struct file *file, + struct snd_timer_status32 __user *_status) +{ + struct snd_timer_user *tu; + struct snd_timer_status32 status; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + memset(&status, 0, sizeof(status)); + status.tstamp.tv_sec = tu->tstamp.tv_sec; + status.tstamp.tv_nsec = tu->tstamp.tv_nsec; + status.resolution = snd_timer_resolution(tu->timeri); + status.lost = tu->timeri->lost; + status.overrun = tu->overrun; + spin_lock_irq(&tu->qlock); + status.queue = tu->qused; + spin_unlock_irq(&tu->qlock); + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +#ifdef CONFIG_X86_X32 +/* X32 ABI has the same struct as x86-64 */ +#define snd_timer_user_status_x32(file, s) \ + snd_timer_user_status(file, s) +#endif /* CONFIG_X86_X32 */ + +/* + */ + +enum { + SNDRV_TIMER_IOCTL_GPARAMS32 = _IOW('T', 0x04, struct snd_timer_gparams32), + SNDRV_TIMER_IOCTL_INFO32 = _IOR('T', 0x11, struct snd_timer_info32), + SNDRV_TIMER_IOCTL_STATUS32 = _IOW('T', 0x14, struct snd_timer_status32), +#ifdef CONFIG_X86_X32 + SNDRV_TIMER_IOCTL_STATUS_X32 = _IOW('T', 0x14, struct snd_timer_status), +#endif /* CONFIG_X86_X32 */ +}; + +static long __snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = compat_ptr(arg); + + switch (cmd) { + case SNDRV_TIMER_IOCTL_PVERSION: + case SNDRV_TIMER_IOCTL_TREAD: + case SNDRV_TIMER_IOCTL_GINFO: + case SNDRV_TIMER_IOCTL_GSTATUS: + case SNDRV_TIMER_IOCTL_SELECT: + case SNDRV_TIMER_IOCTL_PARAMS: + case SNDRV_TIMER_IOCTL_START: + case SNDRV_TIMER_IOCTL_START_OLD: + case SNDRV_TIMER_IOCTL_STOP: + case SNDRV_TIMER_IOCTL_STOP_OLD: + case SNDRV_TIMER_IOCTL_CONTINUE: + case SNDRV_TIMER_IOCTL_CONTINUE_OLD: + case SNDRV_TIMER_IOCTL_PAUSE: + case SNDRV_TIMER_IOCTL_PAUSE_OLD: + case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + return __snd_timer_user_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_TIMER_IOCTL_GPARAMS32: + return snd_timer_user_gparams_compat(file, argp); + case SNDRV_TIMER_IOCTL_INFO32: + return snd_timer_user_info_compat(file, argp); + case SNDRV_TIMER_IOCTL_STATUS32: + return snd_timer_user_status_compat(file, argp); +#ifdef CONFIG_X86_X32 + case SNDRV_TIMER_IOCTL_STATUS_X32: + return snd_timer_user_status_x32(file, argp); +#endif /* CONFIG_X86_X32 */ + } + return -ENOIOCTLCMD; +} + +static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_timer_user *tu = file->private_data; + long ret; + + mutex_lock(&tu->ioctl_lock); + ret = __snd_timer_user_ioctl_compat(file, cmd, arg); + mutex_unlock(&tu->ioctl_lock); + return ret; +} diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c new file mode 100644 index 000000000..fd99d8abe --- /dev/null +++ b/sound/core/vmaster.c @@ -0,0 +1,524 @@ +/* + * Virtual master and slave controls + * + * Copyright (c) 2008 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include + +/* + * a subset of information returned via ctl info callback + */ +struct link_ctl_info { + snd_ctl_elem_type_t type; /* value type */ + int count; /* item count */ + int min_val, max_val; /* min, max values */ +}; + +/* + * link master - this contains a list of slave controls that are + * identical types, i.e. info returns the same value type and value + * ranges, but may have different number of counts. + * + * The master control is so far only mono volume/switch for simplicity. + * The same value will be applied to all slaves. + */ +struct link_master { + struct list_head slaves; + struct link_ctl_info info; + int val; /* the master value */ + unsigned int tlv[4]; + void (*hook)(void *private_data, int); + void *hook_private_data; +}; + +/* + * link slave - this contains a slave control element + * + * It fakes the control callbacsk with additional attenuation by the + * master control. A slave may have either one or two channels. + */ + +struct link_slave { + struct list_head list; + struct link_master *master; + struct link_ctl_info info; + int vals[2]; /* current values */ + unsigned int flags; + struct snd_kcontrol *kctl; /* original kcontrol pointer */ + struct snd_kcontrol slave; /* the copy of original control entry */ +}; + +static int slave_update(struct link_slave *slave) +{ + struct snd_ctl_elem_value *uctl; + int err, ch; + + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (!uctl) + return -ENOMEM; + uctl->id = slave->slave.id; + err = slave->slave.get(&slave->slave, uctl); + if (err < 0) + goto error; + for (ch = 0; ch < slave->info.count; ch++) + slave->vals[ch] = uctl->value.integer.value[ch]; + error: + kfree(uctl); + return err < 0 ? err : 0; +} + +/* get the slave ctl info and save the initial values */ +static int slave_init(struct link_slave *slave) +{ + struct snd_ctl_elem_info *uinfo; + int err; + + if (slave->info.count) { + /* already initialized */ + if (slave->flags & SND_CTL_SLAVE_NEED_UPDATE) + return slave_update(slave); + return 0; + } + + uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL); + if (!uinfo) + return -ENOMEM; + uinfo->id = slave->slave.id; + err = slave->slave.info(&slave->slave, uinfo); + if (err < 0) { + kfree(uinfo); + return err; + } + slave->info.type = uinfo->type; + slave->info.count = uinfo->count; + if (slave->info.count > 2 || + (slave->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER && + slave->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) { + pr_err("ALSA: vmaster: invalid slave element\n"); + kfree(uinfo); + return -EINVAL; + } + slave->info.min_val = uinfo->value.integer.min; + slave->info.max_val = uinfo->value.integer.max; + kfree(uinfo); + + return slave_update(slave); +} + +/* initialize master volume */ +static int master_init(struct link_master *master) +{ + struct link_slave *slave; + + if (master->info.count) + return 0; /* already initialized */ + + list_for_each_entry(slave, &master->slaves, list) { + int err = slave_init(slave); + if (err < 0) + return err; + master->info = slave->info; + master->info.count = 1; /* always mono */ + /* set full volume as default (= no attenuation) */ + master->val = master->info.max_val; + if (master->hook) + master->hook(master->hook_private_data, master->val); + return 1; + } + return -ENOENT; +} + +static int slave_get_val(struct link_slave *slave, + struct snd_ctl_elem_value *ucontrol) +{ + int err, ch; + + err = slave_init(slave); + if (err < 0) + return err; + for (ch = 0; ch < slave->info.count; ch++) + ucontrol->value.integer.value[ch] = slave->vals[ch]; + return 0; +} + +static int slave_put_val(struct link_slave *slave, + struct snd_ctl_elem_value *ucontrol) +{ + int err, ch, vol; + + err = master_init(slave->master); + if (err < 0) + return err; + + switch (slave->info.type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + for (ch = 0; ch < slave->info.count; ch++) + ucontrol->value.integer.value[ch] &= + !!slave->master->val; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + for (ch = 0; ch < slave->info.count; ch++) { + /* max master volume is supposed to be 0 dB */ + vol = ucontrol->value.integer.value[ch]; + vol += slave->master->val - slave->master->info.max_val; + if (vol < slave->info.min_val) + vol = slave->info.min_val; + else if (vol > slave->info.max_val) + vol = slave->info.max_val; + ucontrol->value.integer.value[ch] = vol; + } + break; + } + return slave->slave.put(&slave->slave, ucontrol); +} + +/* + * ctl callbacks for slaves + */ +static int slave_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct link_slave *slave = snd_kcontrol_chip(kcontrol); + return slave->slave.info(&slave->slave, uinfo); +} + +static int slave_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_slave *slave = snd_kcontrol_chip(kcontrol); + return slave_get_val(slave, ucontrol); +} + +static int slave_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_slave *slave = snd_kcontrol_chip(kcontrol); + int err, ch, changed = 0; + + err = slave_init(slave); + if (err < 0) + return err; + for (ch = 0; ch < slave->info.count; ch++) { + if (slave->vals[ch] != ucontrol->value.integer.value[ch]) { + changed = 1; + slave->vals[ch] = ucontrol->value.integer.value[ch]; + } + } + if (!changed) + return 0; + err = slave_put_val(slave, ucontrol); + if (err < 0) + return err; + return 1; +} + +static int slave_tlv_cmd(struct snd_kcontrol *kcontrol, + int op_flag, unsigned int size, + unsigned int __user *tlv) +{ + struct link_slave *slave = snd_kcontrol_chip(kcontrol); + /* FIXME: this assumes that the max volume is 0 dB */ + return slave->slave.tlv.c(&slave->slave, op_flag, size, tlv); +} + +static void slave_free(struct snd_kcontrol *kcontrol) +{ + struct link_slave *slave = snd_kcontrol_chip(kcontrol); + if (slave->slave.private_free) + slave->slave.private_free(&slave->slave); + if (slave->master) + list_del(&slave->list); + kfree(slave); +} + +/* + * Add a slave control to the group with the given master control + * + * All slaves must be the same type (returning the same information + * via info callback). The function doesn't check it, so it's your + * responsibility. + * + * Also, some additional limitations: + * - at most two channels + * - logarithmic volume control (dB level), no linear volume + * - master can only attenuate the volume, no gain + */ +int _snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave, + unsigned int flags) +{ + struct link_master *master_link = snd_kcontrol_chip(master); + struct link_slave *srec; + + srec = kzalloc(struct_size(srec, slave.vd, slave->count), + GFP_KERNEL); + if (!srec) + return -ENOMEM; + srec->kctl = slave; + srec->slave = *slave; + memcpy(srec->slave.vd, slave->vd, slave->count * sizeof(*slave->vd)); + srec->master = master_link; + srec->flags = flags; + + /* override callbacks */ + slave->info = slave_info; + slave->get = slave_get; + slave->put = slave_put; + if (slave->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) + slave->tlv.c = slave_tlv_cmd; + slave->private_data = srec; + slave->private_free = slave_free; + + list_add_tail(&srec->list, &master_link->slaves); + return 0; +} +EXPORT_SYMBOL(_snd_ctl_add_slave); + +/* + * ctl callbacks for master controls + */ +static int master_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int ret; + + ret = master_init(master); + if (ret < 0) + return ret; + uinfo->type = master->info.type; + uinfo->count = master->info.count; + uinfo->value.integer.min = master->info.min_val; + uinfo->value.integer.max = master->info.max_val; + return 0; +} + +static int master_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int err = master_init(master); + if (err < 0) + return err; + ucontrol->value.integer.value[0] = master->val; + return 0; +} + +static int sync_slaves(struct link_master *master, int old_val, int new_val) +{ + struct link_slave *slave; + struct snd_ctl_elem_value *uval; + + uval = kmalloc(sizeof(*uval), GFP_KERNEL); + if (!uval) + return -ENOMEM; + list_for_each_entry(slave, &master->slaves, list) { + master->val = old_val; + uval->id = slave->slave.id; + slave_get_val(slave, uval); + master->val = new_val; + slave_put_val(slave, uval); + } + kfree(uval); + return 0; +} + +static int master_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int err, new_val, old_val; + bool first_init; + + err = master_init(master); + if (err < 0) + return err; + first_init = err; + old_val = master->val; + new_val = ucontrol->value.integer.value[0]; + if (new_val == old_val) + return 0; + + err = sync_slaves(master, old_val, new_val); + if (err < 0) + return err; + if (master->hook && !first_init) + master->hook(master->hook_private_data, master->val); + return 1; +} + +static void master_free(struct snd_kcontrol *kcontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + struct link_slave *slave, *n; + + /* free all slave links and retore the original slave kctls */ + list_for_each_entry_safe(slave, n, &master->slaves, list) { + struct snd_kcontrol *sctl = slave->kctl; + struct list_head olist = sctl->list; + memcpy(sctl, &slave->slave, sizeof(*sctl)); + memcpy(sctl->vd, slave->slave.vd, + sctl->count * sizeof(*sctl->vd)); + sctl->list = olist; /* keep the current linked-list */ + kfree(slave); + } + kfree(master); +} + + +/** + * snd_ctl_make_virtual_master - Create a virtual master control + * @name: name string of the control element to create + * @tlv: optional TLV int array for dB information + * + * Creates a virtual master control with the given name string. + * + * After creating a vmaster element, you can add the slave controls + * via snd_ctl_add_slave() or snd_ctl_add_slave_uncached(). + * + * The optional argument @tlv can be used to specify the TLV information + * for dB scale of the master control. It should be a single element + * with #SNDRV_CTL_TLVT_DB_SCALE, #SNDRV_CTL_TLV_DB_MINMAX or + * #SNDRV_CTL_TLVT_DB_MINMAX_MUTE type, and should be the max 0dB. + * + * Return: The created control element, or %NULL for errors (ENOMEM). + */ +struct snd_kcontrol *snd_ctl_make_virtual_master(char *name, + const unsigned int *tlv) +{ + struct link_master *master; + struct snd_kcontrol *kctl; + struct snd_kcontrol_new knew; + + memset(&knew, 0, sizeof(knew)); + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.name = name; + knew.info = master_info; + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return NULL; + INIT_LIST_HEAD(&master->slaves); + + kctl = snd_ctl_new1(&knew, master); + if (!kctl) { + kfree(master); + return NULL; + } + /* override some callbacks */ + kctl->info = master_info; + kctl->get = master_get; + kctl->put = master_put; + kctl->private_free = master_free; + + /* additional (constant) TLV read */ + if (tlv) { + unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE]; + if (type == SNDRV_CTL_TLVT_DB_SCALE || + type == SNDRV_CTL_TLVT_DB_MINMAX || + type == SNDRV_CTL_TLVT_DB_MINMAX_MUTE) { + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + memcpy(master->tlv, tlv, sizeof(master->tlv)); + kctl->tlv.p = master->tlv; + } + } + + return kctl; +} +EXPORT_SYMBOL(snd_ctl_make_virtual_master); + +/** + * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control + * @kcontrol: vmaster kctl element + * @hook: the hook function + * @private_data: the private_data pointer to be saved + * + * Adds the given hook to the vmaster control element so that it's called + * at each time when the value is changed. + * + * Return: Zero. + */ +int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, + void (*hook)(void *private_data, int), + void *private_data) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + master->hook = hook; + master->hook_private_data = private_data; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ctl_add_vmaster_hook); + +/** + * snd_ctl_sync_vmaster - Sync the vmaster slaves and hook + * @kcontrol: vmaster kctl element + * @hook_only: sync only the hook + * + * Forcibly call the put callback of each slave and call the hook function + * to synchronize with the current value of the given vmaster element. + * NOP when NULL is passed to @kcontrol. + */ +void snd_ctl_sync_vmaster(struct snd_kcontrol *kcontrol, bool hook_only) +{ + struct link_master *master; + bool first_init = false; + + if (!kcontrol) + return; + master = snd_kcontrol_chip(kcontrol); + if (!hook_only) { + int err = master_init(master); + if (err < 0) + return; + first_init = err; + err = sync_slaves(master, master->val, master->val); + if (err < 0) + return; + } + + if (master->hook && !first_init) + master->hook(master->hook_private_data, master->val); +} +EXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster); + +/** + * snd_ctl_apply_vmaster_slaves - Apply function to each vmaster slave + * @kctl: vmaster kctl element + * @func: function to apply + * @arg: optional function argument + * + * Apply the function @func to each slave kctl of the given vmaster kctl. + * Returns 0 if successful, or a negative error code. + */ +int snd_ctl_apply_vmaster_slaves(struct snd_kcontrol *kctl, + int (*func)(struct snd_kcontrol *vslave, + struct snd_kcontrol *slave, + void *arg), + void *arg) +{ + struct link_master *master; + struct link_slave *slave; + int err; + + master = snd_kcontrol_chip(kctl); + err = master_init(master); + if (err < 0) + return err; + list_for_each_entry(slave, &master->slaves, list) { + err = func(slave->kctl, &slave->slave, arg); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_ctl_apply_vmaster_slaves); diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig new file mode 100644 index 000000000..648a12da4 --- /dev/null +++ b/sound/drivers/Kconfig @@ -0,0 +1,238 @@ +config SND_MPU401_UART + tristate + select SND_RAWMIDI + +config SND_OPL3_LIB + tristate + select SND_TIMER + select SND_HWDEP + select SND_SEQ_DEVICE if SND_SEQUENCER != n + +config SND_OPL4_LIB + tristate + select SND_TIMER + select SND_HWDEP + select SND_SEQ_DEVICE if SND_SEQUENCER != n + +# select SEQ stuff to min(SND_SEQUENCER,SND_XXX) +config SND_OPL3_LIB_SEQ + def_tristate SND_SEQUENCER && SND_OPL3_LIB + select SND_SEQ_MIDI_EMUL + select SND_SEQ_MIDI_EVENT + +config SND_OPL4_LIB_SEQ + def_tristate SND_SEQUENCER && SND_OPL4_LIB + select SND_SEQ_MIDI_EMUL + select SND_SEQ_MIDI_EVENT + +config SND_VX_LIB + tristate + select FW_LOADER + select SND_HWDEP + select SND_PCM + +config SND_AC97_CODEC + tristate + select SND_PCM + select AC97_BUS + select SND_VMASTER + +menuconfig SND_DRIVERS + bool "Generic sound devices" + default y + help + Support for generic sound devices. + +if SND_DRIVERS + +config SND_PCSP + tristate "PC-Speaker support (READ HELP!)" + depends on PCSPKR_PLATFORM && X86 && HIGH_RES_TIMERS + depends on INPUT + select SND_PCM + help + If you don't have a sound card in your computer, you can include a + driver for the PC speaker which allows it to act like a primitive + sound card. + This driver also replaces the pcspkr driver for beeps. + + You can compile this as a module which will be called snd-pcsp. + + WARNING: if you already have a soundcard, enabling this + driver may lead to a problem. Namely, it may get loaded + before the other sound driver of yours, making the + pc-speaker a default sound device. Which is likely not + what you want. To make this driver play nicely with other + sound driver, you can add this in a configuration file under + /etc/modprobe.d/ directory: + options snd-pcsp index=2 + + You don't need this driver if you only want your pc-speaker to beep. + You don't need this driver if you have a tablet piezo beeper + in your PC instead of the real speaker. + + Say N if you have a sound card. + Say M if you don't. + Say Y only if you really know what you do. + +config SND_DUMMY + tristate "Dummy (/dev/null) soundcard" + select SND_PCM + help + Say Y here to include the dummy driver. This driver does + nothing, but emulates various mixer controls and PCM devices. + + You don't need this unless you're testing the hardware support + of programs using the ALSA API. + + To compile this driver as a module, choose M here: the module + will be called snd-dummy. + +config SND_ALOOP + tristate "Generic loopback driver (PCM)" + select SND_PCM + help + Say 'Y' or 'M' to include support for the PCM loopback device. + This module returns played samples back to the user space using + the standard ALSA PCM device. The devices are routed 0->1 and + 1->0, where first number is the playback PCM device and second + number is the capture device. Module creates two PCM devices and + configured number of substreams (see the pcm_substreams module + parameter). + + The loopback device allows time sychronization with an external + timing source using the time shift universal control (+-20% + of system time). + + To compile this driver as a module, choose M here: the module + will be called snd-aloop. + +config SND_VIRMIDI + tristate "Virtual MIDI soundcard" + depends on SND_SEQUENCER + select SND_TIMER + select SND_RAWMIDI + select SND_SEQ_VIRMIDI + select SND_SEQ_MIDI_EVENT + help + Say Y here to include the virtual MIDI driver. This driver + allows to connect applications using raw MIDI devices to + sequencer clients. + + If you don't know what MIDI is, say N here. + + To compile this driver as a module, choose M here: the module + will be called snd-virmidi. + +config SND_MTPAV + tristate "MOTU MidiTimePiece AV multiport MIDI" + select SND_RAWMIDI + help + To use a MOTU MidiTimePiece AV multiport MIDI adapter + connected to the parallel port, say Y here and make sure that + the standard parallel port driver isn't used for the port. + + To compile this driver as a module, choose M here: the module + will be called snd-mtpav. + +config SND_MTS64 + tristate "ESI Miditerminal 4140 driver" + depends on PARPORT + select SND_RAWMIDI + help + The ESI Miditerminal 4140 is a 4 In 4 Out MIDI Interface with + additional SMPTE Timecode capabilities for the parallel port. + + Say 'Y' to include support for this device. + + To compile this driver as a module, chose 'M' here: the module + will be called snd-mts64. + +config SND_SERIAL_U16550 + tristate "UART16550 serial MIDI driver" + select SND_RAWMIDI + help + To include support for MIDI serial port interfaces, say Y here + and read . + This driver works with serial UARTs 16550 and better. + + This driver accesses the serial port hardware directly, so + make sure that the standard serial driver isn't used or + deactivated with setserial before loading this driver. + + To compile this driver as a module, choose M here: the module + will be called snd-serial-u16550. + +config SND_MPU401 + tristate "Generic MPU-401 UART driver" + select SND_MPU401_UART + help + Say Y here to include support for MIDI ports compatible with + the Roland MPU-401 interface in UART mode. + + To compile this driver as a module, choose M here: the module + will be called snd-mpu401. + +config SND_PORTMAN2X4 + tristate "Portman 2x4 driver" + depends on PARPORT + select SND_RAWMIDI + help + Say Y here to include support for Midiman Portman 2x4 parallel + port MIDI device. + + To compile this driver as a module, choose M here: the module + will be called snd-portman2x4. + +config SND_ML403_AC97CR + tristate "Xilinx ML403 AC97 Controller Reference" + depends on XILINX_VIRTEX + select SND_AC97_CODEC + help + Say Y here to include support for the + opb_ac97_controller_ref_v1_00_a ip core found in Xilinx's ML403 + reference design. + + To compile this driver as a module, choose M here: the module + will be called snd-ml403_ac97cr. + +config SND_AC97_POWER_SAVE + bool "AC97 Power-Saving Mode" + depends on SND_AC97_CODEC + default n + help + Say Y here to enable the aggressive power-saving support of + AC97 codecs. In this mode, the power-mode is dynamically + controlled at each open/close. + + The mode is activated by passing 'power_save=X' to the + snd-ac97-codec driver module, where 'X' is the time-out + value, a nonnegative integer that specifies how many + seconds of idle time the driver must count before it may + put the AC97 into power-save mode; a value of 0 (zero) + disables the use of this power-save mode. + + After the snd-ac97-codec driver module has been loaded, + the 'power_save' parameter can be set via sysfs as follows: + + echo 10 > /sys/module/snd_ac97_codec/parameters/power_save + + In this case, the time-out is set to 10 seconds; setting + the time-out to 1 second (the minimum activation value) + isn't recommended because many applications try to reopen + the device frequently. A value of 10 seconds would be a + good choice for normal operations. + + See Documentation/sound/designs/powersave.rst for more details. + +config SND_AC97_POWER_SAVE_DEFAULT + int "Default time-out for AC97 power-save mode" + depends on SND_AC97_POWER_SAVE + default 0 + help + The default time-out value in seconds for AC97 automatic + power-save mode. 0 means to disable the power-save mode. + + See SND_AC97_POWER_SAVE for more details. + +endif # SND_DRIVERS diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile new file mode 100644 index 000000000..615558a28 --- /dev/null +++ b/sound/drivers/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-dummy-objs := dummy.o +snd-aloop-objs := aloop.o +snd-mtpav-objs := mtpav.o +snd-mts64-objs := mts64.o +snd-portman2x4-objs := portman2x4.o +snd-serial-u16550-objs := serial-u16550.o +snd-virmidi-objs := virmidi.o +snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_DUMMY) += snd-dummy.o +obj-$(CONFIG_SND_ALOOP) += snd-aloop.o +obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o +obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o +obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o +obj-$(CONFIG_SND_MTS64) += snd-mts64.o +obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o +obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o + +obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c new file mode 100644 index 000000000..3c65e52b0 --- /dev/null +++ b/sound/drivers/aloop.c @@ -0,0 +1,1292 @@ +/* + * Loopback soundcard + * + * Original code: + * Copyright (c) by Jaroslav Kysela + * + * More accurate positioning and full-duplex support: + * Copyright (c) Ahmet İnan + * + * Major (almost complete) rewrite: + * Copyright (c) by Takashi Iwai + * + * A next major update in 2010 (separate timers for playback and capture): + * Copyright (c) Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("A loopback soundcard"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ALSA,Loopback soundcard}}"); + +#define MAX_PCM_SUBSTREAMS 8 + +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] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; +static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8}; +static int pcm_notify[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for loopback soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for loopback soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this loopback soundcard."); +module_param_array(pcm_substreams, int, NULL, 0444); +MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver."); +module_param_array(pcm_notify, int, NULL, 0444); +MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes."); + +#define NO_PITCH 100000 + +struct loopback_pcm; + +struct loopback_cable { + spinlock_t lock; + struct loopback_pcm *streams[2]; + struct snd_pcm_hardware hw; + /* flags */ + unsigned int valid; + unsigned int running; + unsigned int pause; +}; + +struct loopback_setup { + unsigned int notify: 1; + unsigned int rate_shift; + unsigned int format; + unsigned int rate; + unsigned int channels; + struct snd_ctl_elem_id active_id; + struct snd_ctl_elem_id format_id; + struct snd_ctl_elem_id rate_id; + struct snd_ctl_elem_id channels_id; +}; + +struct loopback { + struct snd_card *card; + struct mutex cable_lock; + struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2]; + struct snd_pcm *pcm[2]; + struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2]; +}; + +struct loopback_pcm { + struct loopback *loopback; + struct snd_pcm_substream *substream; + struct loopback_cable *cable; + unsigned int pcm_buffer_size; + unsigned int buf_pos; /* position in buffer */ + unsigned int silent_size; + /* PCM parameters */ + unsigned int pcm_period_size; + unsigned int pcm_bps; /* bytes per second */ + unsigned int pcm_salign; /* bytes per sample * channels */ + unsigned int pcm_rate_shift; /* rate shift value */ + /* flags */ + unsigned int period_update_pending :1; + /* timer stuff */ + unsigned int irq_pos; /* fractional IRQ position */ + unsigned int period_size_frac; + unsigned int last_drift; + unsigned long last_jiffies; + struct timer_list timer; +}; + +static struct platform_device *devices[SNDRV_CARDS]; + +static inline unsigned int byte_pos(struct loopback_pcm *dpcm, unsigned int x) +{ + if (dpcm->pcm_rate_shift == NO_PITCH) { + x /= HZ; + } else { + x = div_u64(NO_PITCH * (unsigned long long)x, + HZ * (unsigned long long)dpcm->pcm_rate_shift); + } + return x - (x % dpcm->pcm_salign); +} + +static inline unsigned int frac_pos(struct loopback_pcm *dpcm, unsigned int x) +{ + if (dpcm->pcm_rate_shift == NO_PITCH) { /* no pitch */ + return x * HZ; + } else { + x = div_u64(dpcm->pcm_rate_shift * (unsigned long long)x * HZ, + NO_PITCH); + } + return x; +} + +static inline struct loopback_setup *get_setup(struct loopback_pcm *dpcm) +{ + int device = dpcm->substream->pstr->pcm->device; + + if (dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + device ^= 1; + return &dpcm->loopback->setup[dpcm->substream->number][device]; +} + +static inline unsigned int get_notify(struct loopback_pcm *dpcm) +{ + return get_setup(dpcm)->notify; +} + +static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm) +{ + return get_setup(dpcm)->rate_shift; +} + +/* call in cable->lock */ +static void loopback_timer_start(struct loopback_pcm *dpcm) +{ + unsigned long tick; + unsigned int rate_shift = get_rate_shift(dpcm); + + if (rate_shift != dpcm->pcm_rate_shift) { + dpcm->pcm_rate_shift = rate_shift; + dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size); + } + if (dpcm->period_size_frac <= dpcm->irq_pos) { + dpcm->irq_pos %= dpcm->period_size_frac; + dpcm->period_update_pending = 1; + } + tick = dpcm->period_size_frac - dpcm->irq_pos; + tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps; + mod_timer(&dpcm->timer, jiffies + tick); +} + +/* call in cable->lock */ +static inline void loopback_timer_stop(struct loopback_pcm *dpcm) +{ + del_timer(&dpcm->timer); + dpcm->timer.expires = 0; +} + +static inline void loopback_timer_stop_sync(struct loopback_pcm *dpcm) +{ + del_timer_sync(&dpcm->timer); +} + +#define CABLE_VALID_PLAYBACK (1 << SNDRV_PCM_STREAM_PLAYBACK) +#define CABLE_VALID_CAPTURE (1 << SNDRV_PCM_STREAM_CAPTURE) +#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE) + +static int loopback_check_format(struct loopback_cable *cable, int stream) +{ + struct snd_pcm_runtime *runtime, *cruntime; + struct loopback_setup *setup; + struct snd_card *card; + int check; + + if (cable->valid != CABLE_VALID_BOTH) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + goto __notify; + return 0; + } + runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]-> + substream->runtime; + cruntime = cable->streams[SNDRV_PCM_STREAM_CAPTURE]-> + substream->runtime; + check = runtime->format != cruntime->format || + runtime->rate != cruntime->rate || + runtime->channels != cruntime->channels; + if (!check) + return 0; + if (stream == SNDRV_PCM_STREAM_CAPTURE) { + return -EIO; + } else { + snd_pcm_stop(cable->streams[SNDRV_PCM_STREAM_CAPTURE]-> + substream, SNDRV_PCM_STATE_DRAINING); + __notify: + runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]-> + substream->runtime; + setup = get_setup(cable->streams[SNDRV_PCM_STREAM_PLAYBACK]); + card = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->loopback->card; + if (setup->format != runtime->format) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->format_id); + setup->format = runtime->format; + } + if (setup->rate != runtime->rate) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->rate_id); + setup->rate = runtime->rate; + } + if (setup->channels != runtime->channels) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->channels_id); + setup->channels = runtime->channels; + } + } + return 0; +} + +static void loopback_active_notify(struct loopback_pcm *dpcm) +{ + snd_ctl_notify(dpcm->loopback->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &get_setup(dpcm)->active_id); +} + +static int loopback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + int err, stream = 1 << substream->stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = loopback_check_format(cable, substream->stream); + if (err < 0) + return err; + dpcm->last_jiffies = jiffies; + dpcm->pcm_rate_shift = 0; + dpcm->last_drift = 0; + spin_lock(&cable->lock); + cable->running |= stream; + cable->pause &= ~stream; + loopback_timer_start(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_STOP: + spin_lock(&cable->lock); + cable->running &= ~stream; + cable->pause &= ~stream; + loopback_timer_stop(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + spin_lock(&cable->lock); + cable->pause |= stream; + loopback_timer_stop(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + spin_lock(&cable->lock); + dpcm->last_jiffies = jiffies; + cable->pause &= ~stream; + loopback_timer_start(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + default: + return -EINVAL; + } + return 0; +} + +static void params_change(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + + cable->hw.formats = pcm_format_to_bits(runtime->format); + cable->hw.rate_min = runtime->rate; + cable->hw.rate_max = runtime->rate; + cable->hw.channels_min = runtime->channels; + cable->hw.channels_max = runtime->channels; +} + +static int loopback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + int bps, salign; + + loopback_timer_stop_sync(dpcm); + + salign = (snd_pcm_format_width(runtime->format) * + runtime->channels) / 8; + bps = salign * runtime->rate; + if (bps <= 0 || salign <= 0) + return -EINVAL; + + dpcm->buf_pos = 0; + dpcm->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* clear capture buffer */ + dpcm->silent_size = dpcm->pcm_buffer_size; + snd_pcm_format_set_silence(runtime->format, runtime->dma_area, + runtime->buffer_size * runtime->channels); + } + + dpcm->irq_pos = 0; + dpcm->period_update_pending = 0; + dpcm->pcm_bps = bps; + dpcm->pcm_salign = salign; + dpcm->pcm_period_size = frames_to_bytes(runtime, runtime->period_size); + + mutex_lock(&dpcm->loopback->cable_lock); + if (!(cable->valid & ~(1 << substream->stream)) || + (get_setup(dpcm)->notify && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) + params_change(substream); + cable->valid |= 1 << substream->stream; + mutex_unlock(&dpcm->loopback->cable_lock); + + return 0; +} + +static void clear_capture_buf(struct loopback_pcm *dpcm, unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = dpcm->substream->runtime; + char *dst = runtime->dma_area; + unsigned int dst_off = dpcm->buf_pos; + + if (dpcm->silent_size >= dpcm->pcm_buffer_size) + return; + if (dpcm->silent_size + bytes > dpcm->pcm_buffer_size) + bytes = dpcm->pcm_buffer_size - dpcm->silent_size; + + for (;;) { + unsigned int size = bytes; + if (dst_off + size > dpcm->pcm_buffer_size) + size = dpcm->pcm_buffer_size - dst_off; + snd_pcm_format_set_silence(runtime->format, dst + dst_off, + bytes_to_frames(runtime, size) * + runtime->channels); + dpcm->silent_size += size; + bytes -= size; + if (!bytes) + break; + dst_off = 0; + } +} + +static void copy_play_buf(struct loopback_pcm *play, + struct loopback_pcm *capt, + unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = play->substream->runtime; + char *src = runtime->dma_area; + char *dst = capt->substream->runtime->dma_area; + unsigned int src_off = play->buf_pos; + unsigned int dst_off = capt->buf_pos; + unsigned int clear_bytes = 0; + + /* check if playback is draining, trim the capture copy size + * when our pointer is at the end of playback ring buffer */ + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING && + snd_pcm_playback_hw_avail(runtime) < runtime->buffer_size) { + snd_pcm_uframes_t appl_ptr, appl_ptr1, diff; + appl_ptr = appl_ptr1 = runtime->control->appl_ptr; + appl_ptr1 -= appl_ptr1 % runtime->buffer_size; + appl_ptr1 += play->buf_pos / play->pcm_salign; + if (appl_ptr < appl_ptr1) + appl_ptr1 -= runtime->buffer_size; + diff = (appl_ptr - appl_ptr1) * play->pcm_salign; + if (diff < bytes) { + clear_bytes = bytes - diff; + bytes = diff; + } + } + + for (;;) { + unsigned int size = bytes; + if (src_off + size > play->pcm_buffer_size) + size = play->pcm_buffer_size - src_off; + if (dst_off + size > capt->pcm_buffer_size) + size = capt->pcm_buffer_size - dst_off; + memcpy(dst + dst_off, src + src_off, size); + capt->silent_size = 0; + bytes -= size; + if (!bytes) + break; + src_off = (src_off + size) % play->pcm_buffer_size; + dst_off = (dst_off + size) % capt->pcm_buffer_size; + } + + if (clear_bytes > 0) { + clear_capture_buf(capt, clear_bytes); + capt->silent_size = 0; + } +} + +static inline unsigned int bytepos_delta(struct loopback_pcm *dpcm, + unsigned int jiffies_delta) +{ + unsigned long last_pos; + unsigned int delta; + + last_pos = byte_pos(dpcm, dpcm->irq_pos); + dpcm->irq_pos += jiffies_delta * dpcm->pcm_bps; + delta = byte_pos(dpcm, dpcm->irq_pos) - last_pos; + if (delta >= dpcm->last_drift) + delta -= dpcm->last_drift; + dpcm->last_drift = 0; + if (dpcm->irq_pos >= dpcm->period_size_frac) { + dpcm->irq_pos %= dpcm->period_size_frac; + dpcm->period_update_pending = 1; + } + return delta; +} + +static inline void bytepos_finish(struct loopback_pcm *dpcm, + unsigned int delta) +{ + dpcm->buf_pos += delta; + dpcm->buf_pos %= dpcm->pcm_buffer_size; +} + +/* call in cable->lock */ +static unsigned int loopback_pos_update(struct loopback_cable *cable) +{ + struct loopback_pcm *dpcm_play = + cable->streams[SNDRV_PCM_STREAM_PLAYBACK]; + struct loopback_pcm *dpcm_capt = + cable->streams[SNDRV_PCM_STREAM_CAPTURE]; + unsigned long delta_play = 0, delta_capt = 0; + unsigned int running, count1, count2; + + running = cable->running ^ cable->pause; + if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) { + delta_play = jiffies - dpcm_play->last_jiffies; + dpcm_play->last_jiffies += delta_play; + } + + if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) { + delta_capt = jiffies - dpcm_capt->last_jiffies; + dpcm_capt->last_jiffies += delta_capt; + } + + if (delta_play == 0 && delta_capt == 0) + goto unlock; + + if (delta_play > delta_capt) { + count1 = bytepos_delta(dpcm_play, delta_play - delta_capt); + bytepos_finish(dpcm_play, count1); + delta_play = delta_capt; + } else if (delta_play < delta_capt) { + count1 = bytepos_delta(dpcm_capt, delta_capt - delta_play); + clear_capture_buf(dpcm_capt, count1); + bytepos_finish(dpcm_capt, count1); + delta_capt = delta_play; + } + + if (delta_play == 0 && delta_capt == 0) + goto unlock; + + /* note delta_capt == delta_play at this moment */ + count1 = bytepos_delta(dpcm_play, delta_play); + count2 = bytepos_delta(dpcm_capt, delta_capt); + if (count1 < count2) { + dpcm_capt->last_drift = count2 - count1; + count1 = count2; + } else if (count1 > count2) { + dpcm_play->last_drift = count1 - count2; + } + copy_play_buf(dpcm_play, dpcm_capt, count1); + bytepos_finish(dpcm_play, count1); + bytepos_finish(dpcm_capt, count1); + unlock: + return running; +} + +static void loopback_timer_function(struct timer_list *t) +{ + struct loopback_pcm *dpcm = from_timer(dpcm, t, timer); + unsigned long flags; + + spin_lock_irqsave(&dpcm->cable->lock, flags); + if (loopback_pos_update(dpcm->cable) & (1 << dpcm->substream->stream)) { + loopback_timer_start(dpcm); + if (dpcm->period_update_pending) { + dpcm->period_update_pending = 0; + spin_unlock_irqrestore(&dpcm->cable->lock, flags); + /* need to unlock before calling below */ + snd_pcm_period_elapsed(dpcm->substream); + return; + } + } + spin_unlock_irqrestore(&dpcm->cable->lock, flags); +} + +static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + snd_pcm_uframes_t pos; + + spin_lock(&dpcm->cable->lock); + loopback_pos_update(dpcm->cable); + pos = dpcm->buf_pos; + spin_unlock(&dpcm->cable->lock); + return bytes_to_frames(runtime, pos); +} + +static const struct snd_pcm_hardware loopback_pcm_hardware = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | + SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 32, + .buffer_bytes_max = 2 * 1024 * 1024, + .period_bytes_min = 64, + /* note check overflow in frac_pos() using pcm_rate_shift before + changing period_bytes_max value */ + .period_bytes_max = 1024 * 1024, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void loopback_runtime_free(struct snd_pcm_runtime *runtime) +{ + struct loopback_pcm *dpcm = runtime->private_data; + kfree(dpcm); +} + +static int loopback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int loopback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + + mutex_lock(&dpcm->loopback->cable_lock); + cable->valid &= ~(1 << substream->stream); + mutex_unlock(&dpcm->loopback->cable_lock); + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static unsigned int get_cable_index(struct snd_pcm_substream *substream) +{ + if (!substream->pcm->device) + return substream->stream; + else + return !substream->stream; +} + +static int rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct loopback_pcm *dpcm = rule->private; + struct loopback_cable *cable = dpcm->cable; + struct snd_mask m; + + snd_mask_none(&m); + mutex_lock(&dpcm->loopback->cable_lock); + m.bits[0] = (u_int32_t)cable->hw.formats; + m.bits[1] = (u_int32_t)(cable->hw.formats >> 32); + mutex_unlock(&dpcm->loopback->cable_lock); + return snd_mask_refine(hw_param_mask(params, rule->var), &m); +} + +static int rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct loopback_pcm *dpcm = rule->private; + struct loopback_cable *cable = dpcm->cable; + struct snd_interval t; + + mutex_lock(&dpcm->loopback->cable_lock); + t.min = cable->hw.rate_min; + t.max = cable->hw.rate_max; + mutex_unlock(&dpcm->loopback->cable_lock); + t.openmin = t.openmax = 0; + t.integer = 0; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct loopback_pcm *dpcm = rule->private; + struct loopback_cable *cable = dpcm->cable; + struct snd_interval t; + + mutex_lock(&dpcm->loopback->cable_lock); + t.min = cable->hw.channels_min; + t.max = cable->hw.channels_max; + mutex_unlock(&dpcm->loopback->cable_lock); + t.openmin = t.openmax = 0; + t.integer = 0; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static void free_cable(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + int dev = get_cable_index(substream); + struct loopback_cable *cable; + + cable = loopback->cables[substream->number][dev]; + if (!cable) + return; + if (cable->streams[!substream->stream]) { + /* other stream is still alive */ + spin_lock_irq(&cable->lock); + cable->streams[substream->stream] = NULL; + spin_unlock_irq(&cable->lock); + } else { + /* free the cable */ + loopback->cables[substream->number][dev] = NULL; + kfree(cable); + } +} + +static int loopback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback *loopback = substream->private_data; + struct loopback_pcm *dpcm; + struct loopback_cable *cable = NULL; + int err = 0; + int dev = get_cable_index(substream); + + mutex_lock(&loopback->cable_lock); + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) { + err = -ENOMEM; + goto unlock; + } + dpcm->loopback = loopback; + dpcm->substream = substream; + timer_setup(&dpcm->timer, loopback_timer_function, 0); + + cable = loopback->cables[substream->number][dev]; + if (!cable) { + cable = kzalloc(sizeof(*cable), GFP_KERNEL); + if (!cable) { + err = -ENOMEM; + goto unlock; + } + spin_lock_init(&cable->lock); + cable->hw = loopback_pcm_hardware; + loopback->cables[substream->number][dev] = cable; + } + dpcm->cable = cable; + + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + /* use dynamic rules based on actual runtime->hw values */ + /* note that the default rules created in the PCM midlevel code */ + /* are cached -> they do not reflect the actual state */ + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + rule_format, dpcm, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (err < 0) + goto unlock; + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + rule_rate, dpcm, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto unlock; + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + rule_channels, dpcm, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto unlock; + + runtime->private_data = dpcm; + runtime->private_free = loopback_runtime_free; + if (get_notify(dpcm)) + runtime->hw = loopback_pcm_hardware; + else + runtime->hw = cable->hw; + + spin_lock_irq(&cable->lock); + cable->streams[substream->stream] = dpcm; + spin_unlock_irq(&cable->lock); + + unlock: + if (err < 0) { + free_cable(substream); + kfree(dpcm); + } + mutex_unlock(&loopback->cable_lock); + return err; +} + +static int loopback_close(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + struct loopback_pcm *dpcm = substream->runtime->private_data; + + loopback_timer_stop_sync(dpcm); + mutex_lock(&loopback->cable_lock); + free_cable(substream); + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static const struct snd_pcm_ops loopback_pcm_ops = { + .open = loopback_open, + .close = loopback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = loopback_hw_params, + .hw_free = loopback_hw_free, + .prepare = loopback_prepare, + .trigger = loopback_trigger, + .pointer = loopback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static int loopback_pcm_new(struct loopback *loopback, + int device, int substreams) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(loopback->card, "Loopback PCM", device, + substreams, substreams, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops); + + pcm->private_data = loopback; + pcm->info_flags = 0; + strcpy(pcm->name, "Loopback PCM"); + + loopback->pcm[device] = pcm; + return 0; +} + +static int loopback_rate_shift_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 = 80000; + uinfo->value.integer.max = 120000; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_rate_shift_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate_shift; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static int loopback_rate_shift_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + val = ucontrol->value.integer.value[0]; + if (val < 80000) + val = 80000; + if (val > 120000) + val = 120000; + mutex_lock(&loopback->cable_lock); + if (val != loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate_shift) { + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate_shift = val; + change = 1; + } + mutex_unlock(&loopback->cable_lock); + return change; +} + +static int loopback_notify_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].notify; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static int loopback_notify_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + val = ucontrol->value.integer.value[0] ? 1 : 0; + mutex_lock(&loopback->cable_lock); + if (val != loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].notify) { + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].notify = val; + change = 1; + } + mutex_unlock(&loopback->cable_lock); + return change; +} + +static int loopback_active_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + struct loopback_cable *cable; + + unsigned int val = 0; + + mutex_lock(&loopback->cable_lock); + cable = loopback->cables[kcontrol->id.subdevice][kcontrol->id.device ^ 1]; + if (cable != NULL) { + unsigned int running = cable->running ^ cable->pause; + + val = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ? 1 : 0; + } + mutex_unlock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int loopback_format_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 = SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].format; + return 0; +} + +static int loopback_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; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static int loopback_channels_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 = 1024; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].channels; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static struct snd_kcontrol_new loopback_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Rate Shift 100000", + .info = loopback_rate_shift_info, + .get = loopback_rate_shift_get, + .put = loopback_rate_shift_put, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Notify", + .info = snd_ctl_boolean_mono_info, + .get = loopback_notify_get, + .put = loopback_notify_put, +}, +#define ACTIVE_IDX 2 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Active", + .info = snd_ctl_boolean_mono_info, + .get = loopback_active_get, +}, +#define FORMAT_IDX 3 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Format", + .info = loopback_format_info, + .get = loopback_format_get +}, +#define RATE_IDX 4 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Rate", + .info = loopback_rate_info, + .get = loopback_rate_get +}, +#define CHANNELS_IDX 5 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Channels", + .info = loopback_channels_info, + .get = loopback_channels_get +} +}; + +static int loopback_mixer_new(struct loopback *loopback, int notify) +{ + struct snd_card *card = loopback->card; + struct snd_pcm *pcm; + struct snd_kcontrol *kctl; + struct loopback_setup *setup; + int err, dev, substr, substr_count, idx; + + strcpy(card->mixername, "Loopback Mixer"); + for (dev = 0; dev < 2; dev++) { + pcm = loopback->pcm[dev]; + substr_count = + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count; + for (substr = 0; substr < substr_count; substr++) { + setup = &loopback->setup[substr][dev]; + setup->notify = notify; + setup->rate_shift = NO_PITCH; + setup->format = SNDRV_PCM_FORMAT_S16_LE; + setup->rate = 48000; + setup->channels = 2; + for (idx = 0; idx < ARRAY_SIZE(loopback_controls); + idx++) { + kctl = snd_ctl_new1(&loopback_controls[idx], + loopback); + if (!kctl) + return -ENOMEM; + kctl->id.device = dev; + kctl->id.subdevice = substr; + + /* Add the control before copying the id so that + * the numid field of the id is set in the copy. + */ + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + + switch (idx) { + case ACTIVE_IDX: + setup->active_id = kctl->id; + break; + case FORMAT_IDX: + setup->format_id = kctl->id; + break; + case RATE_IDX: + setup->rate_id = kctl->id; + break; + case CHANNELS_IDX: + setup->channels_id = kctl->id; + break; + default: + break; + } + } + } + } + return 0; +} + +static void print_dpcm_info(struct snd_info_buffer *buffer, + struct loopback_pcm *dpcm, + const char *id) +{ + snd_iprintf(buffer, " %s\n", id); + if (dpcm == NULL) { + snd_iprintf(buffer, " inactive\n"); + return; + } + snd_iprintf(buffer, " buffer_size:\t%u\n", dpcm->pcm_buffer_size); + snd_iprintf(buffer, " buffer_pos:\t\t%u\n", dpcm->buf_pos); + snd_iprintf(buffer, " silent_size:\t%u\n", dpcm->silent_size); + snd_iprintf(buffer, " period_size:\t%u\n", dpcm->pcm_period_size); + snd_iprintf(buffer, " bytes_per_sec:\t%u\n", dpcm->pcm_bps); + snd_iprintf(buffer, " sample_align:\t%u\n", dpcm->pcm_salign); + snd_iprintf(buffer, " rate_shift:\t\t%u\n", dpcm->pcm_rate_shift); + snd_iprintf(buffer, " update_pending:\t%u\n", + dpcm->period_update_pending); + snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos); + snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac); + snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n", + dpcm->last_jiffies, jiffies); + snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires); +} + +static void print_substream_info(struct snd_info_buffer *buffer, + struct loopback *loopback, + int sub, + int num) +{ + struct loopback_cable *cable = loopback->cables[sub][num]; + + snd_iprintf(buffer, "Cable %i substream %i:\n", num, sub); + if (cable == NULL) { + snd_iprintf(buffer, " inactive\n"); + return; + } + snd_iprintf(buffer, " valid: %u\n", cable->valid); + snd_iprintf(buffer, " running: %u\n", cable->running); + snd_iprintf(buffer, " pause: %u\n", cable->pause); + print_dpcm_info(buffer, cable->streams[0], "Playback"); + print_dpcm_info(buffer, cable->streams[1], "Capture"); +} + +static void print_cable_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct loopback *loopback = entry->private_data; + int sub, num; + + mutex_lock(&loopback->cable_lock); + num = entry->name[strlen(entry->name)-1]; + num = num == '0' ? 0 : 1; + for (sub = 0; sub < MAX_PCM_SUBSTREAMS; sub++) + print_substream_info(buffer, loopback, sub, num); + mutex_unlock(&loopback->cable_lock); +} + +static int loopback_proc_new(struct loopback *loopback, int cidx) +{ + char name[32]; + struct snd_info_entry *entry; + int err; + + snprintf(name, sizeof(name), "cable#%d", cidx); + err = snd_card_proc_new(loopback->card, name, &entry); + if (err < 0) + return err; + + snd_info_set_text_ops(entry, loopback, print_cable_info); + return 0; +} + +static int loopback_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct loopback *loopback; + int dev = devptr->id; + int err; + + err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct loopback), &card); + if (err < 0) + return err; + loopback = card->private_data; + + if (pcm_substreams[dev] < 1) + pcm_substreams[dev] = 1; + if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) + pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; + + loopback->card = card; + mutex_init(&loopback->cable_lock); + + err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]); + if (err < 0) + goto __nodev; + err = loopback_pcm_new(loopback, 1, pcm_substreams[dev]); + if (err < 0) + goto __nodev; + err = loopback_mixer_new(loopback, pcm_notify[dev] ? 1 : 0); + if (err < 0) + goto __nodev; + loopback_proc_new(loopback, 0); + loopback_proc_new(loopback, 1); + strcpy(card->driver, "Loopback"); + strcpy(card->shortname, "Loopback"); + sprintf(card->longname, "Loopback %i", dev + 1); + err = snd_card_register(card); + if (!err) { + platform_set_drvdata(devptr, card); + return 0; + } + __nodev: + snd_card_free(card); + return err; +} + +static int loopback_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int loopback_suspend(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct loopback *loopback = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + snd_pcm_suspend_all(loopback->pcm[0]); + snd_pcm_suspend_all(loopback->pcm[1]); + return 0; +} + +static int loopback_resume(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(loopback_pm, loopback_suspend, loopback_resume); +#define LOOPBACK_PM_OPS &loopback_pm +#else +#define LOOPBACK_PM_OPS NULL +#endif + +#define SND_LOOPBACK_DRIVER "snd_aloop" + +static struct platform_driver loopback_driver = { + .probe = loopback_probe, + .remove = loopback_remove, + .driver = { + .name = SND_LOOPBACK_DRIVER, + .pm = LOOPBACK_PM_OPS, + }, +}; + +static void loopback_unregister_all(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(devices); ++i) + platform_device_unregister(devices[i]); + platform_driver_unregister(&loopback_driver); +} + +static int __init alsa_card_loopback_init(void) +{ + int i, err, cards; + + err = platform_driver_register(&loopback_driver); + if (err < 0) + return err; + + + cards = 0; + for (i = 0; i < SNDRV_CARDS; i++) { + struct platform_device *device; + if (!enable[i]) + continue; + device = platform_device_register_simple(SND_LOOPBACK_DRIVER, + i, NULL, 0); + if (IS_ERR(device)) + continue; + if (!platform_get_drvdata(device)) { + platform_device_unregister(device); + continue; + } + devices[i] = device; + cards++; + } + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "aloop: No loopback enabled\n"); +#endif + loopback_unregister_all(); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_loopback_exit(void) +{ + loopback_unregister_all(); +} + +module_init(alsa_card_loopback_init) +module_exit(alsa_card_loopback_exit) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c new file mode 100644 index 000000000..b78cc8d86 --- /dev/null +++ b/sound/drivers/dummy.c @@ -0,0 +1,1229 @@ +/* + * Dummy soundcard + * Copyright (c) by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Dummy soundcard (/dev/null)"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ALSA,Dummy soundcard}}"); + +#define MAX_PCM_DEVICES 4 +#define MAX_PCM_SUBSTREAMS 128 +#define MAX_MIDI_DEVICES 2 + +/* defaults */ +#define MAX_BUFFER_SIZE (64*1024) +#define MIN_PERIOD_SIZE 64 +#define MAX_PERIOD_SIZE MAX_BUFFER_SIZE +#define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE) +#define USE_RATE SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000 +#define USE_RATE_MIN 5500 +#define USE_RATE_MAX 48000 +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define USE_PERIODS_MIN 1 +#define USE_PERIODS_MAX 1024 + +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] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; +static char *model[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = NULL}; +static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8}; +//static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +#ifdef CONFIG_HIGH_RES_TIMERS +static bool hrtimer = 1; +#endif +static bool fake_buffer = 1; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for dummy soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for dummy soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this dummy soundcard."); +module_param_array(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Soundcard model."); +module_param_array(pcm_devs, int, NULL, 0444); +MODULE_PARM_DESC(pcm_devs, "PCM devices # (0-4) for dummy driver."); +module_param_array(pcm_substreams, int, NULL, 0444); +MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-128) for dummy driver."); +//module_param_array(midi_devs, int, NULL, 0444); +//MODULE_PARM_DESC(midi_devs, "MIDI devices # (0-2) for dummy driver."); +module_param(fake_buffer, bool, 0444); +MODULE_PARM_DESC(fake_buffer, "Fake buffer allocations."); +#ifdef CONFIG_HIGH_RES_TIMERS +module_param(hrtimer, bool, 0644); +MODULE_PARM_DESC(hrtimer, "Use hrtimer as the timer source."); +#endif + +static struct platform_device *devices[SNDRV_CARDS]; + +#define MIXER_ADDR_MASTER 0 +#define MIXER_ADDR_LINE 1 +#define MIXER_ADDR_MIC 2 +#define MIXER_ADDR_SYNTH 3 +#define MIXER_ADDR_CD 4 +#define MIXER_ADDR_LAST 4 + +struct dummy_timer_ops { + int (*create)(struct snd_pcm_substream *); + void (*free)(struct snd_pcm_substream *); + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); +}; + +#define get_dummy_ops(substream) \ + (*(const struct dummy_timer_ops **)(substream)->runtime->private_data) + +struct dummy_model { + const char *name; + int (*playback_constraints)(struct snd_pcm_runtime *runtime); + int (*capture_constraints)(struct snd_pcm_runtime *runtime); + u64 formats; + size_t buffer_bytes_max; + size_t period_bytes_min; + size_t period_bytes_max; + unsigned int periods_min; + unsigned int periods_max; + unsigned int rates; + unsigned int rate_min; + unsigned int rate_max; + unsigned int channels_min; + unsigned int channels_max; +}; + +struct snd_dummy { + struct snd_card *card; + struct dummy_model *model; + struct snd_pcm *pcm; + struct snd_pcm_hardware pcm_hw; + spinlock_t mixer_lock; + int mixer_volume[MIXER_ADDR_LAST+1][2]; + int capture_source[MIXER_ADDR_LAST+1][2]; + int iobox; + struct snd_kcontrol *cd_volume_ctl; + struct snd_kcontrol *cd_switch_ctl; +}; + +/* + * card models + */ + +static int emu10k1_playback_constraints(struct snd_pcm_runtime *runtime) +{ + int err; + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX); + if (err < 0) + return err; + return 0; +} + +static struct dummy_model model_emu10k1 = { + .name = "emu10k1", + .playback_constraints = emu10k1_playback_constraints, + .buffer_bytes_max = 128 * 1024, +}; + +static struct dummy_model model_rme9652 = { + .name = "rme9652", + .buffer_bytes_max = 26 * 64 * 1024, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 26, + .channels_max = 26, + .periods_min = 2, + .periods_max = 2, +}; + +static struct dummy_model model_ice1712 = { + .name = "ice1712", + .buffer_bytes_max = 256 * 1024, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 10, + .channels_max = 10, + .periods_min = 1, + .periods_max = 1024, +}; + +static struct dummy_model model_uda1341 = { + .name = "uda1341", + .buffer_bytes_max = 16380, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .periods_min = 2, + .periods_max = 255, +}; + +static struct dummy_model model_ac97 = { + .name = "ac97", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, +}; + +static struct dummy_model model_ca0106 = { + .name = "ca0106", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .buffer_bytes_max = ((65536-64)*8), + .period_bytes_max = (65536-64), + .periods_min = 2, + .periods_max = 8, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_192000, + .rate_min = 48000, + .rate_max = 192000, +}; + +static struct dummy_model *dummy_models[] = { + &model_emu10k1, + &model_rme9652, + &model_ice1712, + &model_uda1341, + &model_ac97, + &model_ca0106, + NULL +}; + +/* + * system timer interface + */ + +struct dummy_systimer_pcm { + /* ops must be the first item */ + const struct dummy_timer_ops *timer_ops; + spinlock_t lock; + struct timer_list timer; + unsigned long base_time; + unsigned int frac_pos; /* fractional sample position (based HZ) */ + unsigned int frac_period_rest; + unsigned int frac_buffer_size; /* buffer_size * HZ */ + unsigned int frac_period_size; /* period_size * HZ */ + unsigned int rate; + int elapsed; + struct snd_pcm_substream *substream; +}; + +static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm) +{ + mod_timer(&dpcm->timer, jiffies + + (dpcm->frac_period_rest + dpcm->rate - 1) / dpcm->rate); +} + +static void dummy_systimer_update(struct dummy_systimer_pcm *dpcm) +{ + unsigned long delta; + + delta = jiffies - dpcm->base_time; + if (!delta) + return; + dpcm->base_time += delta; + delta *= dpcm->rate; + dpcm->frac_pos += delta; + while (dpcm->frac_pos >= dpcm->frac_buffer_size) + dpcm->frac_pos -= dpcm->frac_buffer_size; + while (dpcm->frac_period_rest <= delta) { + dpcm->elapsed++; + dpcm->frac_period_rest += dpcm->frac_period_size; + } + dpcm->frac_period_rest -= delta; +} + +static int dummy_systimer_start(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); + dpcm->base_time = jiffies; + dummy_systimer_rearm(dpcm); + spin_unlock(&dpcm->lock); + return 0; +} + +static int dummy_systimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); + del_timer(&dpcm->timer); + spin_unlock(&dpcm->lock); + return 0; +} + +static int dummy_systimer_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_systimer_pcm *dpcm = runtime->private_data; + + dpcm->frac_pos = 0; + dpcm->rate = runtime->rate; + dpcm->frac_buffer_size = runtime->buffer_size * HZ; + dpcm->frac_period_size = runtime->period_size * HZ; + dpcm->frac_period_rest = dpcm->frac_period_size; + dpcm->elapsed = 0; + + return 0; +} + +static void dummy_systimer_callback(struct timer_list *t) +{ + struct dummy_systimer_pcm *dpcm = from_timer(dpcm, t, timer); + unsigned long flags; + int elapsed = 0; + + spin_lock_irqsave(&dpcm->lock, flags); + dummy_systimer_update(dpcm); + dummy_systimer_rearm(dpcm); + elapsed = dpcm->elapsed; + dpcm->elapsed = 0; + spin_unlock_irqrestore(&dpcm->lock, flags); + if (elapsed) + snd_pcm_period_elapsed(dpcm->substream); +} + +static snd_pcm_uframes_t +dummy_systimer_pointer(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + snd_pcm_uframes_t pos; + + spin_lock(&dpcm->lock); + dummy_systimer_update(dpcm); + pos = dpcm->frac_pos / HZ; + spin_unlock(&dpcm->lock); + return pos; +} + +static int dummy_systimer_create(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + timer_setup(&dpcm->timer, dummy_systimer_callback, 0); + spin_lock_init(&dpcm->lock); + dpcm->substream = substream; + return 0; +} + +static void dummy_systimer_free(struct snd_pcm_substream *substream) +{ + kfree(substream->runtime->private_data); +} + +static const struct dummy_timer_ops dummy_systimer_ops = { + .create = dummy_systimer_create, + .free = dummy_systimer_free, + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, + .pointer = dummy_systimer_pointer, +}; + +#ifdef CONFIG_HIGH_RES_TIMERS +/* + * hrtimer interface + */ + +struct dummy_hrtimer_pcm { + /* ops must be the first item */ + const struct dummy_timer_ops *timer_ops; + ktime_t base_time; + ktime_t period_time; + atomic_t running; + struct hrtimer timer; + struct snd_pcm_substream *substream; +}; + +static enum hrtimer_restart dummy_hrtimer_callback(struct hrtimer *timer) +{ + struct dummy_hrtimer_pcm *dpcm; + + dpcm = container_of(timer, struct dummy_hrtimer_pcm, timer); + if (!atomic_read(&dpcm->running)) + return HRTIMER_NORESTART; + /* + * In cases of XRUN and draining, this calls .trigger to stop PCM + * substream. + */ + snd_pcm_period_elapsed(dpcm->substream); + if (!atomic_read(&dpcm->running)) + return HRTIMER_NORESTART; + + hrtimer_forward_now(timer, dpcm->period_time); + return HRTIMER_RESTART; +} + +static int dummy_hrtimer_start(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + + dpcm->base_time = hrtimer_cb_get_time(&dpcm->timer); + hrtimer_start(&dpcm->timer, dpcm->period_time, HRTIMER_MODE_REL_SOFT); + atomic_set(&dpcm->running, 1); + return 0; +} + +static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + + atomic_set(&dpcm->running, 0); + if (!hrtimer_callback_running(&dpcm->timer)) + hrtimer_cancel(&dpcm->timer); + return 0; +} + +static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) +{ + hrtimer_cancel(&dpcm->timer); +} + +static snd_pcm_uframes_t +dummy_hrtimer_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_hrtimer_pcm *dpcm = runtime->private_data; + u64 delta; + u32 pos; + + delta = ktime_us_delta(hrtimer_cb_get_time(&dpcm->timer), + dpcm->base_time); + delta = div_u64(delta * runtime->rate + 999999, 1000000); + div_u64_rem(delta, runtime->buffer_size, &pos); + return pos; +} + +static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_hrtimer_pcm *dpcm = runtime->private_data; + unsigned int period, rate; + long sec; + unsigned long nsecs; + + dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; + period %= rate; + nsecs = div_u64((u64)period * 1000000000UL + rate - 1, rate); + dpcm->period_time = ktime_set(sec, nsecs); + + return 0; +} + +static int dummy_hrtimer_create(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + hrtimer_init(&dpcm->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + dpcm->timer.function = dummy_hrtimer_callback; + dpcm->substream = substream; + atomic_set(&dpcm->running, 0); + return 0; +} + +static void dummy_hrtimer_free(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + dummy_hrtimer_sync(dpcm); + kfree(dpcm); +} + +static const struct dummy_timer_ops dummy_hrtimer_ops = { + .create = dummy_hrtimer_create, + .free = dummy_hrtimer_free, + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, + .pointer = dummy_hrtimer_pointer, +}; + +#endif /* CONFIG_HIGH_RES_TIMERS */ + +/* + * PCM interface + */ + +static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return get_dummy_ops(substream)->start(substream); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + return get_dummy_ops(substream)->stop(substream); + } + return -EINVAL; +} + +static int dummy_pcm_prepare(struct snd_pcm_substream *substream) +{ + return get_dummy_ops(substream)->prepare(substream); +} + +static snd_pcm_uframes_t dummy_pcm_pointer(struct snd_pcm_substream *substream) +{ + return get_dummy_ops(substream)->pointer(substream); +} + +static const struct snd_pcm_hardware dummy_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +static int dummy_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + if (fake_buffer) { + /* runtime->dma_bytes has to be set manually to allow mmap */ + substream->runtime->dma_bytes = params_buffer_bytes(hw_params); + return 0; + } + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int dummy_pcm_hw_free(struct snd_pcm_substream *substream) +{ + if (fake_buffer) + return 0; + return snd_pcm_lib_free_pages(substream); +} + +static int dummy_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_dummy *dummy = snd_pcm_substream_chip(substream); + struct dummy_model *model = dummy->model; + struct snd_pcm_runtime *runtime = substream->runtime; + const struct dummy_timer_ops *ops; + int err; + + ops = &dummy_systimer_ops; +#ifdef CONFIG_HIGH_RES_TIMERS + if (hrtimer) + ops = &dummy_hrtimer_ops; +#endif + + err = ops->create(substream); + if (err < 0) + return err; + get_dummy_ops(substream) = ops; + + runtime->hw = dummy->pcm_hw; + if (substream->pcm->device & 1) { + runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; + runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; + } + if (substream->pcm->device & 2) + runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID); + + if (model == NULL) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (model->playback_constraints) + err = model->playback_constraints(substream->runtime); + } else { + if (model->capture_constraints) + err = model->capture_constraints(substream->runtime); + } + if (err < 0) { + get_dummy_ops(substream)->free(substream); + return err; + } + return 0; +} + +static int dummy_pcm_close(struct snd_pcm_substream *substream) +{ + get_dummy_ops(substream)->free(substream); + return 0; +} + +/* + * dummy buffer handling + */ + +static void *dummy_page[2]; + +static void free_fake_buffer(void) +{ + if (fake_buffer) { + int i; + for (i = 0; i < 2; i++) + if (dummy_page[i]) { + free_page((unsigned long)dummy_page[i]); + dummy_page[i] = NULL; + } + } +} + +static int alloc_fake_buffer(void) +{ + int i; + + if (!fake_buffer) + return 0; + for (i = 0; i < 2; i++) { + dummy_page[i] = (void *)get_zeroed_page(GFP_KERNEL); + if (!dummy_page[i]) { + free_fake_buffer(); + return -ENOMEM; + } + } + return 0; +} + +static int dummy_pcm_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + void __user *dst, unsigned long bytes) +{ + return 0; /* do nothing */ +} + +static int dummy_pcm_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + void *dst, unsigned long bytes) +{ + return 0; /* do nothing */ +} + +static int dummy_pcm_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long bytes) +{ + return 0; /* do nothing */ +} + +static struct page *dummy_pcm_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + return virt_to_page(dummy_page[substream->stream]); /* the same page */ +} + +static struct snd_pcm_ops dummy_pcm_ops = { + .open = dummy_pcm_open, + .close = dummy_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dummy_pcm_hw_params, + .hw_free = dummy_pcm_hw_free, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, + .pointer = dummy_pcm_pointer, +}; + +static struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .open = dummy_pcm_open, + .close = dummy_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dummy_pcm_hw_params, + .hw_free = dummy_pcm_hw_free, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, + .pointer = dummy_pcm_pointer, + .copy_user = dummy_pcm_copy, + .copy_kernel = dummy_pcm_copy_kernel, + .fill_silence = dummy_pcm_silence, + .page = dummy_pcm_page, +}; + +static int snd_card_dummy_pcm(struct snd_dummy *dummy, int device, + int substreams) +{ + struct snd_pcm *pcm; + struct snd_pcm_ops *ops; + int err; + + err = snd_pcm_new(dummy->card, "Dummy PCM", device, + substreams, substreams, &pcm); + if (err < 0) + return err; + dummy->pcm = pcm; + if (fake_buffer) + ops = &dummy_pcm_ops_no_buf; + else + ops = &dummy_pcm_ops; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops); + pcm->private_data = dummy; + pcm->info_flags = 0; + strcpy(pcm->name, "Dummy PCM"); + if (!fake_buffer) { + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 0, 64*1024); + } + return 0; +} + +/* + * mixer interface + */ + +#define DUMMY_VOLUME(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .name = xname, .index = xindex, \ + .info = snd_dummy_volume_info, \ + .get = snd_dummy_volume_get, .put = snd_dummy_volume_put, \ + .private_value = addr, \ + .tlv = { .p = db_scale_dummy } } + +static int snd_dummy_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 = -50; + uinfo->value.integer.max = 100; + return 0; +} + +static int snd_dummy_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value; + + spin_lock_irq(&dummy->mixer_lock); + ucontrol->value.integer.value[0] = dummy->mixer_volume[addr][0]; + ucontrol->value.integer.value[1] = dummy->mixer_volume[addr][1]; + spin_unlock_irq(&dummy->mixer_lock); + return 0; +} + +static int snd_dummy_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + int change, addr = kcontrol->private_value; + int left, right; + + left = ucontrol->value.integer.value[0]; + if (left < -50) + left = -50; + if (left > 100) + left = 100; + right = ucontrol->value.integer.value[1]; + if (right < -50) + right = -50; + if (right > 100) + right = 100; + spin_lock_irq(&dummy->mixer_lock); + change = dummy->mixer_volume[addr][0] != left || + dummy->mixer_volume[addr][1] != right; + dummy->mixer_volume[addr][0] = left; + dummy->mixer_volume[addr][1] = right; + spin_unlock_irq(&dummy->mixer_lock); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_dummy, -4500, 30, 0); + +#define DUMMY_CAPSRC(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_dummy_capsrc_info, \ + .get = snd_dummy_capsrc_get, .put = snd_dummy_capsrc_put, \ + .private_value = addr } + +#define snd_dummy_capsrc_info snd_ctl_boolean_stereo_info + +static int snd_dummy_capsrc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value; + + spin_lock_irq(&dummy->mixer_lock); + ucontrol->value.integer.value[0] = dummy->capture_source[addr][0]; + ucontrol->value.integer.value[1] = dummy->capture_source[addr][1]; + spin_unlock_irq(&dummy->mixer_lock); + return 0; +} + +static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + int change, addr = kcontrol->private_value; + int left, right; + + left = ucontrol->value.integer.value[0] & 1; + right = ucontrol->value.integer.value[1] & 1; + spin_lock_irq(&dummy->mixer_lock); + change = dummy->capture_source[addr][0] != left && + dummy->capture_source[addr][1] != right; + dummy->capture_source[addr][0] = left; + dummy->capture_source[addr][1] = right; + spin_unlock_irq(&dummy->mixer_lock); + return change; +} + +static int snd_dummy_iobox_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const names[] = { "None", "CD Player" }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int snd_dummy_iobox_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + + value->value.enumerated.item[0] = dummy->iobox; + return 0; +} + +static int snd_dummy_iobox_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + int changed; + + if (value->value.enumerated.item[0] > 1) + return -EINVAL; + + changed = value->value.enumerated.item[0] != dummy->iobox; + if (changed) { + dummy->iobox = value->value.enumerated.item[0]; + + if (dummy->iobox) { + dummy->cd_volume_ctl->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + dummy->cd_switch_ctl->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } else { + dummy->cd_volume_ctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_INACTIVE; + dummy->cd_switch_ctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } + + snd_ctl_notify(dummy->card, SNDRV_CTL_EVENT_MASK_INFO, + &dummy->cd_volume_ctl->id); + snd_ctl_notify(dummy->card, SNDRV_CTL_EVENT_MASK_INFO, + &dummy->cd_switch_ctl->id); + } + + return changed; +} + +static struct snd_kcontrol_new snd_dummy_controls[] = { +DUMMY_VOLUME("Master Volume", 0, MIXER_ADDR_MASTER), +DUMMY_CAPSRC("Master Capture Switch", 0, MIXER_ADDR_MASTER), +DUMMY_VOLUME("Synth Volume", 0, MIXER_ADDR_SYNTH), +DUMMY_CAPSRC("Synth Capture Switch", 0, MIXER_ADDR_SYNTH), +DUMMY_VOLUME("Line Volume", 0, MIXER_ADDR_LINE), +DUMMY_CAPSRC("Line Capture Switch", 0, MIXER_ADDR_LINE), +DUMMY_VOLUME("Mic Volume", 0, MIXER_ADDR_MIC), +DUMMY_CAPSRC("Mic Capture Switch", 0, MIXER_ADDR_MIC), +DUMMY_VOLUME("CD Volume", 0, MIXER_ADDR_CD), +DUMMY_CAPSRC("CD Capture Switch", 0, MIXER_ADDR_CD), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "External I/O Box", + .info = snd_dummy_iobox_info, + .get = snd_dummy_iobox_get, + .put = snd_dummy_iobox_put, +}, +}; + +static int snd_card_dummy_new_mixer(struct snd_dummy *dummy) +{ + struct snd_card *card = dummy->card; + struct snd_kcontrol *kcontrol; + unsigned int idx; + int err; + + spin_lock_init(&dummy->mixer_lock); + strcpy(card->mixername, "Dummy Mixer"); + dummy->iobox = 1; + + for (idx = 0; idx < ARRAY_SIZE(snd_dummy_controls); idx++) { + kcontrol = snd_ctl_new1(&snd_dummy_controls[idx], dummy); + err = snd_ctl_add(card, kcontrol); + if (err < 0) + return err; + if (!strcmp(kcontrol->id.name, "CD Volume")) + dummy->cd_volume_ctl = kcontrol; + else if (!strcmp(kcontrol->id.name, "CD Capture Switch")) + dummy->cd_switch_ctl = kcontrol; + + } + return 0; +} + +#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_PROC_FS) +/* + * proc interface + */ +static void print_formats(struct snd_dummy *dummy, + struct snd_info_buffer *buffer) +{ + int i; + + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + if (dummy->pcm_hw.formats & (1ULL << i)) + snd_iprintf(buffer, " %s", snd_pcm_format_name(i)); + } +} + +static void print_rates(struct snd_dummy *dummy, + struct snd_info_buffer *buffer) +{ + static int rates[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, + 64000, 88200, 96000, 176400, 192000, + }; + int i; + + if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_CONTINUOUS) + snd_iprintf(buffer, " continuous"); + if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_KNOT) + snd_iprintf(buffer, " knot"); + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (dummy->pcm_hw.rates & (1 << i)) + snd_iprintf(buffer, " %d", rates[i]); +} + +#define get_dummy_int_ptr(dummy, ofs) \ + (unsigned int *)((char *)&((dummy)->pcm_hw) + (ofs)) +#define get_dummy_ll_ptr(dummy, ofs) \ + (unsigned long long *)((char *)&((dummy)->pcm_hw) + (ofs)) + +struct dummy_hw_field { + const char *name; + const char *format; + unsigned int offset; + unsigned int size; +}; +#define FIELD_ENTRY(item, fmt) { \ + .name = #item, \ + .format = fmt, \ + .offset = offsetof(struct snd_pcm_hardware, item), \ + .size = sizeof(dummy_pcm_hardware.item) } + +static struct dummy_hw_field fields[] = { + FIELD_ENTRY(formats, "%#llx"), + FIELD_ENTRY(rates, "%#x"), + FIELD_ENTRY(rate_min, "%d"), + FIELD_ENTRY(rate_max, "%d"), + FIELD_ENTRY(channels_min, "%d"), + FIELD_ENTRY(channels_max, "%d"), + FIELD_ENTRY(buffer_bytes_max, "%ld"), + FIELD_ENTRY(period_bytes_min, "%ld"), + FIELD_ENTRY(period_bytes_max, "%ld"), + FIELD_ENTRY(periods_min, "%d"), + FIELD_ENTRY(periods_max, "%d"), +}; + +static void dummy_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_dummy *dummy = entry->private_data; + int i; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + snd_iprintf(buffer, "%s ", fields[i].name); + if (fields[i].size == sizeof(int)) + snd_iprintf(buffer, fields[i].format, + *get_dummy_int_ptr(dummy, fields[i].offset)); + else + snd_iprintf(buffer, fields[i].format, + *get_dummy_ll_ptr(dummy, fields[i].offset)); + if (!strcmp(fields[i].name, "formats")) + print_formats(dummy, buffer); + else if (!strcmp(fields[i].name, "rates")) + print_rates(dummy, buffer); + snd_iprintf(buffer, "\n"); + } +} + +static void dummy_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_dummy *dummy = entry->private_data; + char line[64]; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + char item[20]; + const char *ptr; + unsigned long long val; + int i; + + ptr = snd_info_get_str(item, line, sizeof(item)); + for (i = 0; i < ARRAY_SIZE(fields); i++) { + if (!strcmp(item, fields[i].name)) + break; + } + if (i >= ARRAY_SIZE(fields)) + continue; + snd_info_get_str(item, ptr, sizeof(item)); + if (kstrtoull(item, 0, &val)) + continue; + if (fields[i].size == sizeof(int)) + *get_dummy_int_ptr(dummy, fields[i].offset) = val; + else + *get_dummy_ll_ptr(dummy, fields[i].offset) = val; + } +} + +static void dummy_proc_init(struct snd_dummy *chip) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "dummy_pcm", &entry)) { + snd_info_set_text_ops(entry, chip, dummy_proc_read); + entry->c.text.write = dummy_proc_write; + entry->mode |= 0200; + entry->private_data = chip; + } +} +#else +#define dummy_proc_init(x) +#endif /* CONFIG_SND_DEBUG && CONFIG_SND_PROC_FS */ + +static int snd_dummy_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_dummy *dummy; + struct dummy_model *m = NULL, **mdl; + int idx, err; + int dev = devptr->id; + + err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_dummy), &card); + if (err < 0) + return err; + dummy = card->private_data; + dummy->card = card; + for (mdl = dummy_models; *mdl && model[dev]; mdl++) { + if (strcmp(model[dev], (*mdl)->name) == 0) { + printk(KERN_INFO + "snd-dummy: Using model '%s' for card %i\n", + (*mdl)->name, card->number); + m = dummy->model = *mdl; + break; + } + } + for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) { + if (pcm_substreams[dev] < 1) + pcm_substreams[dev] = 1; + if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) + pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; + err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]); + if (err < 0) + goto __nodev; + } + + dummy->pcm_hw = dummy_pcm_hardware; + if (m) { + if (m->formats) + dummy->pcm_hw.formats = m->formats; + if (m->buffer_bytes_max) + dummy->pcm_hw.buffer_bytes_max = m->buffer_bytes_max; + if (m->period_bytes_min) + dummy->pcm_hw.period_bytes_min = m->period_bytes_min; + if (m->period_bytes_max) + dummy->pcm_hw.period_bytes_max = m->period_bytes_max; + if (m->periods_min) + dummy->pcm_hw.periods_min = m->periods_min; + if (m->periods_max) + dummy->pcm_hw.periods_max = m->periods_max; + if (m->rates) + dummy->pcm_hw.rates = m->rates; + if (m->rate_min) + dummy->pcm_hw.rate_min = m->rate_min; + if (m->rate_max) + dummy->pcm_hw.rate_max = m->rate_max; + if (m->channels_min) + dummy->pcm_hw.channels_min = m->channels_min; + if (m->channels_max) + dummy->pcm_hw.channels_max = m->channels_max; + } + + err = snd_card_dummy_new_mixer(dummy); + if (err < 0) + goto __nodev; + strcpy(card->driver, "Dummy"); + strcpy(card->shortname, "Dummy"); + sprintf(card->longname, "Dummy %i", dev + 1); + + dummy_proc_init(dummy); + + err = snd_card_register(card); + if (err == 0) { + platform_set_drvdata(devptr, card); + return 0; + } + __nodev: + snd_card_free(card); + return err; +} + +static int snd_dummy_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int snd_dummy_suspend(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct snd_dummy *dummy = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(dummy->pcm); + return 0; +} + +static int snd_dummy_resume(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_dummy_pm, snd_dummy_suspend, snd_dummy_resume); +#define SND_DUMMY_PM_OPS &snd_dummy_pm +#else +#define SND_DUMMY_PM_OPS NULL +#endif + +#define SND_DUMMY_DRIVER "snd_dummy" + +static struct platform_driver snd_dummy_driver = { + .probe = snd_dummy_probe, + .remove = snd_dummy_remove, + .driver = { + .name = SND_DUMMY_DRIVER, + .pm = SND_DUMMY_PM_OPS, + }, +}; + +static void snd_dummy_unregister_all(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(devices); ++i) + platform_device_unregister(devices[i]); + platform_driver_unregister(&snd_dummy_driver); + free_fake_buffer(); +} + +static int __init alsa_card_dummy_init(void) +{ + int i, cards, err; + + err = platform_driver_register(&snd_dummy_driver); + if (err < 0) + return err; + + err = alloc_fake_buffer(); + if (err < 0) { + platform_driver_unregister(&snd_dummy_driver); + return err; + } + + cards = 0; + for (i = 0; i < SNDRV_CARDS; i++) { + struct platform_device *device; + if (! enable[i]) + continue; + device = platform_device_register_simple(SND_DUMMY_DRIVER, + i, NULL, 0); + if (IS_ERR(device)) + continue; + if (!platform_get_drvdata(device)) { + platform_device_unregister(device); + continue; + } + devices[i] = device; + cards++; + } + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "Dummy soundcard not found or device busy\n"); +#endif + snd_dummy_unregister_all(); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_dummy_exit(void) +{ + snd_dummy_unregister_all(); +} + +module_init(alsa_card_dummy_init) +module_exit(alsa_card_dummy_exit) diff --git a/sound/drivers/ml403-ac97cr.c b/sound/drivers/ml403-ac97cr.c new file mode 100644 index 000000000..18fd12996 --- /dev/null +++ b/sound/drivers/ml403-ac97cr.c @@ -0,0 +1,1335 @@ +/* + * ALSA driver for Xilinx ML403 AC97 Controller Reference + * IP: opb_ac97_controller_ref_v1_00_a (EDK 8.1i) + * IP: opb_ac97_controller_ref_v1_00_a (EDK 9.1i) + * + * Copyright (c) by 2007 Joachim Foerster + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Some notes / status of this driver: + * + * - Don't wonder about some strange implementations of things - especially the + * (heavy) shadowing of codec registers, with which I tried to reduce read + * accesses to a minimum, because after a variable amount of accesses, the AC97 + * controller doesn't raise the register access finished bit anymore ... + * + * - Playback support seems to be pretty stable - no issues here. + * - Capture support "works" now, too. Overruns don't happen any longer so often. + * But there might still be some ... + */ + +#include +#include + +#include + +#include +#include +#include +#include + +/* HZ */ +#include +/* jiffies, time_*() */ +#include +/* schedule_timeout*() */ +#include +/* spin_lock*() */ +#include +/* struct mutex, mutex_init(), mutex_*lock() */ +#include + +/* snd_printk(), snd_printd() */ +#include +#include +#include +#include +#include + +#include "pcm-indirect2.h" + + +#define SND_ML403_AC97CR_DRIVER "ml403-ac97cr" + +MODULE_AUTHOR("Joachim Foerster "); +MODULE_DESCRIPTION("Xilinx ML403 AC97 Controller Reference"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Xilinx,ML403 AC97 Controller Reference}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ML403 AC97 Controller Reference."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ML403 AC97 Controller Reference."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this ML403 AC97 Controller Reference."); + +/* Special feature options */ +/*#define CODEC_WRITE_CHECK_RAF*/ /* don't return after a write to a codec + * register, while RAF bit is not set + */ +/* Debug options for code which may be removed completely in a final version */ +#ifdef CONFIG_SND_DEBUG +/*#define CODEC_STAT*/ /* turn on some minimal "statistics" + * about codec register usage + */ +#define SND_PCM_INDIRECT2_STAT /* turn on some "statistics" about the + * process of copying bytes from the + * intermediate buffer to the hardware + * fifo and the other way round + */ +#endif + +/* Definition of a "level/facility dependent" printk(); may be removed + * completely in a final version + */ +#undef PDEBUG +#ifdef CONFIG_SND_DEBUG +/* "facilities" for PDEBUG */ +#define UNKNOWN (1<<0) +#define CODEC_SUCCESS (1<<1) +#define CODEC_FAKE (1<<2) +#define INIT_INFO (1<<3) +#define INIT_FAILURE (1<<4) +#define WORK_INFO (1<<5) +#define WORK_FAILURE (1<<6) + +#define PDEBUG_FACILITIES (UNKNOWN | INIT_FAILURE | WORK_FAILURE) + +#define PDEBUG(fac, fmt, args...) do { \ + if (fac & PDEBUG_FACILITIES) \ + snd_printd(KERN_DEBUG SND_ML403_AC97CR_DRIVER ": " \ + fmt, ##args); \ + } while (0) +#else +#define PDEBUG(fac, fmt, args...) /* nothing */ +#endif + + + +/* Defines for "waits"/timeouts (portions of HZ=250 on arch/ppc by default) */ +#define CODEC_TIMEOUT_ON_INIT 5 /* timeout for checking for codec + * readiness (after insmod) + */ +#ifndef CODEC_WRITE_CHECK_RAF +#define CODEC_WAIT_AFTER_WRITE 100 /* general, static wait after a write + * access to a codec register, may be + * 0 to completely remove wait + */ +#else +#define CODEC_TIMEOUT_AFTER_WRITE 5 /* timeout after a write access to a + * codec register, if RAF bit is used + */ +#endif +#define CODEC_TIMEOUT_AFTER_READ 5 /* timeout after a read access to a + * codec register (checking RAF bit) + */ + +/* Infrastructure for codec register shadowing */ +#define LM4550_REG_OK (1<<0) /* register exists */ +#define LM4550_REG_DONEREAD (1<<1) /* read register once, value should be + * the same currently in the register + */ +#define LM4550_REG_NOSAVE (1<<2) /* values written to this register will + * not be saved in the register + */ +#define LM4550_REG_NOSHADOW (1<<3) /* don't do register shadowing, use plain + * hardware access + */ +#define LM4550_REG_READONLY (1<<4) /* register is read only */ +#define LM4550_REG_FAKEPROBE (1<<5) /* fake write _and_ read actions during + * probe() correctly + */ +#define LM4550_REG_FAKEREAD (1<<6) /* fake read access, always return + * default value + */ +#define LM4550_REG_ALLFAKE (LM4550_REG_FAKEREAD | LM4550_REG_FAKEPROBE) + +struct lm4550_reg { + u16 value; + u16 flag; + u16 wmask; + u16 def; +}; + +struct lm4550_reg lm4550_regfile[64] = { + [AC97_RESET / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_NOSAVE \ + | LM4550_REG_FAKEREAD, + .def = 0x0D50}, + [AC97_MASTER / 2] = {.flag = LM4550_REG_OK + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8000}, + [AC97_HEADPHONE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8000}, + [AC97_MASTER_MONO / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x801F, + .def = 0x8000}, + [AC97_PC_BEEP / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x801E, + .def = 0x0}, + [AC97_PHONE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x801F, + .def = 0x8008}, + [AC97_MIC / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x805F, + .def = 0x8008}, + [AC97_LINE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_CD / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_VIDEO / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_AUX / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_PCM / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8008}, + [AC97_REC_SEL / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x707, + .def = 0x0}, + [AC97_REC_GAIN / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x8F0F, + .def = 0x8000}, + [AC97_GENERAL_PURPOSE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .def = 0x0, + .wmask = 0xA380}, + [AC97_3D_CONTROL / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEREAD \ + | LM4550_REG_READONLY, + .def = 0x0101}, + [AC97_POWERDOWN / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_NOSHADOW \ + | LM4550_REG_NOSAVE, + .wmask = 0xFF00}, + /* may not write ones to + * REF/ANL/DAC/ADC bits + * FIXME: Is this ok? + */ + [AC97_EXTENDED_ID / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEREAD \ + | LM4550_REG_READONLY, + .def = 0x0201}, /* primary codec */ + [AC97_EXTENDED_STATUS / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_NOSHADOW \ + | LM4550_REG_NOSAVE, + .wmask = 0x1}, + [AC97_PCM_FRONT_DAC_RATE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .def = 0xBB80, + .wmask = 0xFFFF}, + [AC97_PCM_LR_ADC_RATE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .def = 0xBB80, + .wmask = 0xFFFF}, + [AC97_VENDOR_ID1 / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_READONLY \ + | LM4550_REG_FAKEREAD, + .def = 0x4E53}, + [AC97_VENDOR_ID2 / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_READONLY \ + | LM4550_REG_FAKEREAD, + .def = 0x4350} +}; + +#define LM4550_RF_OK(reg) (lm4550_regfile[reg / 2].flag & LM4550_REG_OK) + +static void lm4550_regfile_init(void) +{ + int i; + for (i = 0; i < 64; i++) + if (lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE) + lm4550_regfile[i].value = lm4550_regfile[i].def; +} + +static void lm4550_regfile_write_values_after_init(struct snd_ac97 *ac97) +{ + int i; + for (i = 0; i < 64; i++) + if ((lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE) && + (lm4550_regfile[i].value != lm4550_regfile[i].def)) { + PDEBUG(CODEC_FAKE, "lm4550_regfile_write_values_after_" + "init(): reg=0x%x value=0x%x / %d is different " + "from def=0x%x / %d\n", + i, lm4550_regfile[i].value, + lm4550_regfile[i].value, lm4550_regfile[i].def, + lm4550_regfile[i].def); + snd_ac97_write(ac97, i * 2, lm4550_regfile[i].value); + lm4550_regfile[i].flag |= LM4550_REG_DONEREAD; + } +} + + +/* direct registers */ +#define CR_REG(ml403_ac97cr, x) ((ml403_ac97cr)->port + CR_REG_##x) + +#define CR_REG_PLAYFIFO 0x00 +#define CR_PLAYDATA(a) ((a) & 0xFFFF) + +#define CR_REG_RECFIFO 0x04 +#define CR_RECDATA(a) ((a) & 0xFFFF) + +#define CR_REG_STATUS 0x08 +#define CR_RECOVER (1<<7) +#define CR_PLAYUNDER (1<<6) +#define CR_CODECREADY (1<<5) +#define CR_RAF (1<<4) +#define CR_RECEMPTY (1<<3) +#define CR_RECFULL (1<<2) +#define CR_PLAYHALF (1<<1) +#define CR_PLAYFULL (1<<0) + +#define CR_REG_RESETFIFO 0x0C +#define CR_RECRESET (1<<1) +#define CR_PLAYRESET (1<<0) + +#define CR_REG_CODEC_ADDR 0x10 +/* UG082 says: + * #define CR_CODEC_ADDR(a) ((a) << 1) + * #define CR_CODEC_READ (1<<0) + * #define CR_CODEC_WRITE (0<<0) + */ +/* RefDesign example says: */ +#define CR_CODEC_ADDR(a) ((a) << 0) +#define CR_CODEC_READ (1<<7) +#define CR_CODEC_WRITE (0<<7) + +#define CR_REG_CODEC_DATAREAD 0x14 +#define CR_CODEC_DATAREAD(v) ((v) & 0xFFFF) + +#define CR_REG_CODEC_DATAWRITE 0x18 +#define CR_CODEC_DATAWRITE(v) ((v) & 0xFFFF) + +#define CR_FIFO_SIZE 32 + +struct snd_ml403_ac97cr { + /* lock for access to (controller) registers */ + spinlock_t reg_lock; + /* mutex for the whole sequence of accesses to (controller) registers + * which affect codec registers + */ + struct mutex cdc_mutex; + + int irq; /* for playback */ + int enable_irq; /* for playback */ + + int capture_irq; + int enable_capture_irq; + + struct resource *res_port; + void *port; + + struct snd_ac97 *ac97; + int ac97_fake; +#ifdef CODEC_STAT + int ac97_read; + int ac97_write; +#endif + + struct platform_device *pfdev; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct snd_pcm_indirect2 ind_rec; /* for playback */ + struct snd_pcm_indirect2 capture_ind2_rec; +}; + +static const struct snd_pcm_hardware snd_ml403_ac97cr_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .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 = CR_FIFO_SIZE/2, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = (128*1024)/(CR_FIFO_SIZE/2), + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_ml403_ac97cr_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .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 = CR_FIFO_SIZE/2, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = (128*1024)/(CR_FIFO_SIZE/2), + .fifo_size = 0, +}; + +static size_t +snd_ml403_ac97cr_playback_ind2_zero(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int copied_words = 0; + u32 full = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + spin_lock(&ml403_ac97cr->reg_lock); + while ((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_PLAYFULL)) != CR_PLAYFULL) { + out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), 0); + copied_words++; + } + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_playback_ind2_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + size_t bytes) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + u16 *src; + int copied_words = 0; + u32 full = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + src = (u16 *)(substream->runtime->dma_area + rec->sw_data); + + spin_lock(&ml403_ac97cr->reg_lock); + while (((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_PLAYFULL)) != CR_PLAYFULL) && (bytes > 1)) { + out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), + CR_PLAYDATA(src[copied_words])); + copied_words++; + bytes = bytes - 2; + } + if (full != CR_PLAYFULL) + rec->hw_ready = 1; + else + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_capture_ind2_null(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int copied_words = 0; + u32 empty = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + spin_lock(&ml403_ac97cr->reg_lock); + while ((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RECEMPTY)) != CR_RECEMPTY) { + volatile u32 trash; + + trash = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, RECFIFO))); + /* Hmmmm, really necessary? Don't want call to in_be32() + * to be optimised away! + */ + trash++; + copied_words++; + } + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_capture_ind2_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, size_t bytes) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + u16 *dst; + int copied_words = 0; + u32 empty = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + dst = (u16 *)(substream->runtime->dma_area + rec->sw_data); + + spin_lock(&ml403_ac97cr->reg_lock); + while (((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RECEMPTY)) != CR_RECEMPTY) && (bytes > 1)) { + dst[copied_words] = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, + RECFIFO))); + copied_words++; + bytes = bytes - 2; + } + if (empty != CR_RECEMPTY) + rec->hw_ready = 1; + else + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static snd_pcm_uframes_t +snd_ml403_ac97cr_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_indirect2 *ind2_rec = NULL; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + if (substream == ml403_ac97cr->playback_substream) + ind2_rec = &ml403_ac97cr->ind_rec; + if (substream == ml403_ac97cr->capture_substream) + ind2_rec = &ml403_ac97cr->capture_ind2_rec; + + if (ind2_rec != NULL) + return snd_pcm_indirect2_pointer(substream, ind2_rec); + return (snd_pcm_uframes_t) 0; +} + +static int +snd_ml403_ac97cr_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + PDEBUG(WORK_INFO, "trigger(playback): START\n"); + ml403_ac97cr->ind_rec.hw_ready = 1; + + /* clear play FIFO */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_PLAYRESET); + + /* enable play irq */ + ml403_ac97cr->enable_irq = 1; + enable_irq(ml403_ac97cr->irq); + break; + case SNDRV_PCM_TRIGGER_STOP: + PDEBUG(WORK_INFO, "trigger(playback): STOP\n"); + ml403_ac97cr->ind_rec.hw_ready = 0; +#ifdef SND_PCM_INDIRECT2_STAT + snd_pcm_indirect2_stat(substream, &ml403_ac97cr->ind_rec); +#endif + /* disable play irq */ + disable_irq_nosync(ml403_ac97cr->irq); + ml403_ac97cr->enable_irq = 0; + break; + default: + err = -EINVAL; + break; + } + PDEBUG(WORK_INFO, "trigger(playback): (done)\n"); + return err; +} + +static int +snd_ml403_ac97cr_pcm_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + PDEBUG(WORK_INFO, "trigger(capture): START\n"); + ml403_ac97cr->capture_ind2_rec.hw_ready = 0; + + /* clear record FIFO */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_RECRESET); + + /* enable record irq */ + ml403_ac97cr->enable_capture_irq = 1; + enable_irq(ml403_ac97cr->capture_irq); + break; + case SNDRV_PCM_TRIGGER_STOP: + PDEBUG(WORK_INFO, "trigger(capture): STOP\n"); + ml403_ac97cr->capture_ind2_rec.hw_ready = 0; +#ifdef SND_PCM_INDIRECT2_STAT + snd_pcm_indirect2_stat(substream, + &ml403_ac97cr->capture_ind2_rec); +#endif + /* disable capture irq */ + disable_irq_nosync(ml403_ac97cr->capture_irq); + ml403_ac97cr->enable_capture_irq = 0; + break; + default: + err = -EINVAL; + break; + } + PDEBUG(WORK_INFO, "trigger(capture): (done)\n"); + return err; +} + +static int +snd_ml403_ac97cr_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, + "prepare(): period_bytes=%d, minperiod_bytes=%d\n", + snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2); + + /* set sampling rate */ + snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_FRONT_DAC_RATE, + runtime->rate); + PDEBUG(WORK_INFO, "prepare(): rate=%d\n", runtime->rate); + + /* init struct for intermediate buffer */ + memset(&ml403_ac97cr->ind_rec, 0, + sizeof(struct snd_pcm_indirect2)); + ml403_ac97cr->ind_rec.hw_buffer_size = CR_FIFO_SIZE; + ml403_ac97cr->ind_rec.sw_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + ml403_ac97cr->ind_rec.min_periods = -1; + ml403_ac97cr->ind_rec.min_multiple = + snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2); + PDEBUG(WORK_INFO, "prepare(): hw_buffer_size=%d, " + "sw_buffer_size=%d, min_multiple=%d\n", + CR_FIFO_SIZE, ml403_ac97cr->ind_rec.sw_buffer_size, + ml403_ac97cr->ind_rec.min_multiple); + return 0; +} + +static int +snd_ml403_ac97cr_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, + "prepare(capture): period_bytes=%d, minperiod_bytes=%d\n", + snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2); + + /* set sampling rate */ + snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_LR_ADC_RATE, + runtime->rate); + PDEBUG(WORK_INFO, "prepare(capture): rate=%d\n", runtime->rate); + + /* init struct for intermediate buffer */ + memset(&ml403_ac97cr->capture_ind2_rec, 0, + sizeof(struct snd_pcm_indirect2)); + ml403_ac97cr->capture_ind2_rec.hw_buffer_size = CR_FIFO_SIZE; + ml403_ac97cr->capture_ind2_rec.sw_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + ml403_ac97cr->capture_ind2_rec.min_multiple = + snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2); + PDEBUG(WORK_INFO, "prepare(capture): hw_buffer_size=%d, " + "sw_buffer_size=%d, min_multiple=%d\n", CR_FIFO_SIZE, + ml403_ac97cr->capture_ind2_rec.sw_buffer_size, + ml403_ac97cr->capture_ind2_rec.min_multiple); + return 0; +} + +static int snd_ml403_ac97cr_hw_free(struct snd_pcm_substream *substream) +{ + PDEBUG(WORK_INFO, "hw_free()\n"); + return snd_pcm_lib_free_pages(substream); +} + +static int +snd_ml403_ac97cr_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + PDEBUG(WORK_INFO, "hw_params(): desired buffer bytes=%d, desired " + "period bytes=%d\n", + params_buffer_bytes(hw_params), params_period_bytes(hw_params)); + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_ml403_ac97cr_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, "open(playback)\n"); + ml403_ac97cr->playback_substream = substream; + runtime->hw = snd_ml403_ac97cr_playback; + + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + CR_FIFO_SIZE / 2); + return 0; +} + +static int snd_ml403_ac97cr_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, "open(capture)\n"); + ml403_ac97cr->capture_substream = substream; + runtime->hw = snd_ml403_ac97cr_capture; + + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + CR_FIFO_SIZE / 2); + return 0; +} + +static int snd_ml403_ac97cr_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + PDEBUG(WORK_INFO, "close(playback)\n"); + ml403_ac97cr->playback_substream = NULL; + return 0; +} + +static int snd_ml403_ac97cr_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + PDEBUG(WORK_INFO, "close(capture)\n"); + ml403_ac97cr->capture_substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_ml403_ac97cr_playback_ops = { + .open = snd_ml403_ac97cr_playback_open, + .close = snd_ml403_ac97cr_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ml403_ac97cr_hw_params, + .hw_free = snd_ml403_ac97cr_hw_free, + .prepare = snd_ml403_ac97cr_pcm_playback_prepare, + .trigger = snd_ml403_ac97cr_pcm_playback_trigger, + .pointer = snd_ml403_ac97cr_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_ml403_ac97cr_capture_ops = { + .open = snd_ml403_ac97cr_capture_open, + .close = snd_ml403_ac97cr_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ml403_ac97cr_hw_params, + .hw_free = snd_ml403_ac97cr_hw_free, + .prepare = snd_ml403_ac97cr_pcm_capture_prepare, + .trigger = snd_ml403_ac97cr_pcm_capture_trigger, + .pointer = snd_ml403_ac97cr_pcm_pointer, +}; + +static irqreturn_t snd_ml403_ac97cr_irq(int irq, void *dev_id) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct platform_device *pfdev; + int cmp_irq; + + ml403_ac97cr = (struct snd_ml403_ac97cr *)dev_id; + if (ml403_ac97cr == NULL) + return IRQ_NONE; + + pfdev = ml403_ac97cr->pfdev; + + /* playback interrupt */ + cmp_irq = platform_get_irq(pfdev, 0); + if (irq == cmp_irq) { + if (ml403_ac97cr->enable_irq) + snd_pcm_indirect2_playback_interrupt( + ml403_ac97cr->playback_substream, + &ml403_ac97cr->ind_rec, + snd_ml403_ac97cr_playback_ind2_copy, + snd_ml403_ac97cr_playback_ind2_zero); + else + goto __disable_irq; + } else { + /* record interrupt */ + cmp_irq = platform_get_irq(pfdev, 1); + if (irq == cmp_irq) { + if (ml403_ac97cr->enable_capture_irq) + snd_pcm_indirect2_capture_interrupt( + ml403_ac97cr->capture_substream, + &ml403_ac97cr->capture_ind2_rec, + snd_ml403_ac97cr_capture_ind2_copy, + snd_ml403_ac97cr_capture_ind2_null); + else + goto __disable_irq; + } else + return IRQ_NONE; + } + return IRQ_HANDLED; + +__disable_irq: + PDEBUG(INIT_INFO, "irq(): irq %d is meant to be disabled! So, now try " + "to disable it _really_!\n", irq); + disable_irq_nosync(irq); + return IRQ_HANDLED; +} + +static unsigned short +snd_ml403_ac97cr_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; +#ifdef CODEC_STAT + u32 stat; + u32 rafaccess = 0; +#endif + unsigned long end_time; + u16 value = 0; + + if (!LM4550_RF_OK(reg)) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "access to unknown/unused codec register 0x%x " + "ignored!\n", reg); + return 0; + } + /* check if we can fake/answer this access from our shadow register */ + if ((lm4550_regfile[reg / 2].flag & + (LM4550_REG_DONEREAD | LM4550_REG_ALLFAKE)) && + !(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) { + if (lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEREAD) { + PDEBUG(CODEC_FAKE, "codec_read(): faking read from " + "reg=0x%x, val=0x%x / %d\n", + reg, lm4550_regfile[reg / 2].def, + lm4550_regfile[reg / 2].def); + return lm4550_regfile[reg / 2].def; + } else if ((lm4550_regfile[reg / 2].flag & + LM4550_REG_FAKEPROBE) && + ml403_ac97cr->ac97_fake) { + PDEBUG(CODEC_FAKE, "codec_read(): faking read from " + "reg=0x%x, val=0x%x / %d (probe)\n", + reg, lm4550_regfile[reg / 2].value, + lm4550_regfile[reg / 2].value); + return lm4550_regfile[reg / 2].value; + } else { +#ifdef CODEC_STAT + PDEBUG(CODEC_FAKE, "codec_read(): read access " + "answered by shadow register 0x%x (value=0x%x " + "/ %d) (cw=%d cr=%d)\n", + reg, lm4550_regfile[reg / 2].value, + lm4550_regfile[reg / 2].value, + ml403_ac97cr->ac97_write, + ml403_ac97cr->ac97_read); +#else + PDEBUG(CODEC_FAKE, "codec_read(): read access " + "answered by shadow register 0x%x (value=0x%x " + "/ %d)\n", + reg, lm4550_regfile[reg / 2].value, + lm4550_regfile[reg / 2].value); +#endif + return lm4550_regfile[reg / 2].value; + } + } + /* if we are here, we _have_ to access the codec really, no faking */ + if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0) + return 0; +#ifdef CODEC_STAT + ml403_ac97cr->ac97_read++; +#endif + spin_lock(&ml403_ac97cr->reg_lock); + out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR), + CR_CODEC_ADDR(reg) | CR_CODEC_READ); + spin_unlock(&ml403_ac97cr->reg_lock); + end_time = jiffies + (HZ / CODEC_TIMEOUT_AFTER_READ); + do { + spin_lock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + rafaccess++; + stat = in_be32(CR_REG(ml403_ac97cr, STATUS)); + if ((stat & CR_RAF) == CR_RAF) { + value = CR_CODEC_DATAREAD( + in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + PDEBUG(CODEC_SUCCESS, "codec_read(): (done) reg=0x%x, " + "value=0x%x / %d (STATUS=0x%x)\n", + reg, value, value, stat); +#else + if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RAF) == CR_RAF) { + value = CR_CODEC_DATAREAD( + in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + PDEBUG(CODEC_SUCCESS, "codec_read(): (done) " + "reg=0x%x, value=0x%x / %d\n", + reg, value, value); +#endif + lm4550_regfile[reg / 2].value = value; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + spin_unlock(&ml403_ac97cr->reg_lock); + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return value; + } + spin_unlock(&ml403_ac97cr->reg_lock); + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); + /* read the DATAREAD register anyway, see comment below */ + spin_lock(&ml403_ac97cr->reg_lock); + value = + CR_CODEC_DATAREAD(in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + spin_unlock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec read! " + "(reg=0x%x, last STATUS=0x%x, DATAREAD=0x%x / %d, %d) " + "(cw=%d, cr=%d)\n", + reg, stat, value, value, rafaccess, + ml403_ac97cr->ac97_write, ml403_ac97cr->ac97_read); +#else + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec read! " + "(reg=0x%x, DATAREAD=0x%x / %d)\n", + reg, value, value); +#endif + /* BUG: This is PURE speculation! But after _most_ read timeouts the + * value in the register is ok! + */ + lm4550_regfile[reg / 2].value = value; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return value; +} + +static void +snd_ml403_ac97cr_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; + +#ifdef CODEC_STAT + u32 stat; + u32 rafaccess = 0; +#endif +#ifdef CODEC_WRITE_CHECK_RAF + unsigned long end_time; +#endif + + if (!LM4550_RF_OK(reg)) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "access to unknown/unused codec register 0x%x " + "ignored!\n", reg); + return; + } + if (lm4550_regfile[reg / 2].flag & LM4550_REG_READONLY) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "write access to read only codec register 0x%x " + "ignored!\n", reg); + return; + } + if ((val & lm4550_regfile[reg / 2].wmask) != val) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "write access to codec register 0x%x " + "with bad value 0x%x / %d!\n", + reg, val, val); + val = val & lm4550_regfile[reg / 2].wmask; + } + if (((lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEPROBE) && + ml403_ac97cr->ac97_fake) && + !(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) { + PDEBUG(CODEC_FAKE, "codec_write(): faking write to reg=0x%x, " + "val=0x%x / %d\n", reg, val, val); + lm4550_regfile[reg / 2].value = (val & + lm4550_regfile[reg / 2].wmask); + return; + } + if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0) + return; +#ifdef CODEC_STAT + ml403_ac97cr->ac97_write++; +#endif + spin_lock(&ml403_ac97cr->reg_lock); + out_be32(CR_REG(ml403_ac97cr, CODEC_DATAWRITE), + CR_CODEC_DATAWRITE(val)); + out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR), + CR_CODEC_ADDR(reg) | CR_CODEC_WRITE); + spin_unlock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_WRITE_CHECK_RAF + /* check CR_CODEC_RAF bit to see if write access to register is done; + * loop until bit is set or timeout happens + */ + end_time = jiffies + HZ / CODEC_TIMEOUT_AFTER_WRITE; + do { + spin_lock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + rafaccess++; + stat = in_be32(CR_REG(ml403_ac97cr, STATUS)) + if ((stat & CR_RAF) == CR_RAF) { +#else + if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RAF) == CR_RAF) { +#endif + PDEBUG(CODEC_SUCCESS, "codec_write(): (done) " + "reg=0x%x, value=%d / 0x%x\n", + reg, val, val); + if (!(lm4550_regfile[reg / 2].flag & + LM4550_REG_NOSHADOW) && + !(lm4550_regfile[reg / 2].flag & + LM4550_REG_NOSAVE)) + lm4550_regfile[reg / 2].value = val; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + spin_unlock(&ml403_ac97cr->reg_lock); + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return; + } + spin_unlock(&ml403_ac97cr->reg_lock); + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); +#ifdef CODEC_STAT + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec write " + "(reg=0x%x, val=0x%x / %d, last STATUS=0x%x, %d) " + "(cw=%d, cr=%d)\n", + reg, val, val, stat, rafaccess, ml403_ac97cr->ac97_write, + ml403_ac97cr->ac97_read); +#else + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec write (reg=0x%x, val=0x%x / %d)\n", + reg, val, val); +#endif +#else /* CODEC_WRITE_CHECK_RAF */ +#if CODEC_WAIT_AFTER_WRITE > 0 + /* officially, in AC97 spec there is no possibility for a AC97 + * controller to determine, if write access is done or not - so: How + * is Xilinx able to provide a RAF bit for write access? + * => very strange, thus just don't check RAF bit (compare with + * Xilinx's example app in EDK 8.1i) and wait + */ + schedule_timeout_uninterruptible(HZ / CODEC_WAIT_AFTER_WRITE); +#endif + PDEBUG(CODEC_SUCCESS, "codec_write(): (done) " + "reg=0x%x, value=%d / 0x%x (no RAF check)\n", + reg, val, val); +#endif + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return; +} + +static int +snd_ml403_ac97cr_chip_init(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + unsigned long end_time; + PDEBUG(INIT_INFO, "chip_init():\n"); + end_time = jiffies + HZ / CODEC_TIMEOUT_ON_INIT; + do { + if (in_be32(CR_REG(ml403_ac97cr, STATUS)) & CR_CODECREADY) { + /* clear both hardware FIFOs */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), + CR_RECRESET | CR_PLAYRESET); + PDEBUG(INIT_INFO, "chip_init(): (done)\n"); + return 0; + } + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "timeout while waiting for codec, " + "not ready!\n"); + return -EBUSY; +} + +static int snd_ml403_ac97cr_free(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + PDEBUG(INIT_INFO, "free():\n"); + /* irq release */ + if (ml403_ac97cr->irq >= 0) + free_irq(ml403_ac97cr->irq, ml403_ac97cr); + if (ml403_ac97cr->capture_irq >= 0) + free_irq(ml403_ac97cr->capture_irq, ml403_ac97cr); + /* give back "port" */ + iounmap(ml403_ac97cr->port); + kfree(ml403_ac97cr); + PDEBUG(INIT_INFO, "free(): (done)\n"); + return 0; +} + +static int snd_ml403_ac97cr_dev_free(struct snd_device *snddev) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = snddev->device_data; + PDEBUG(INIT_INFO, "dev_free():\n"); + return snd_ml403_ac97cr_free(ml403_ac97cr); +} + +static int +snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev, + struct snd_ml403_ac97cr **rml403_ac97cr) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_ml403_ac97cr_dev_free, + }; + struct resource *resource; + int irq; + + *rml403_ac97cr = NULL; + ml403_ac97cr = kzalloc(sizeof(*ml403_ac97cr), GFP_KERNEL); + if (ml403_ac97cr == NULL) + return -ENOMEM; + spin_lock_init(&ml403_ac97cr->reg_lock); + mutex_init(&ml403_ac97cr->cdc_mutex); + ml403_ac97cr->card = card; + ml403_ac97cr->pfdev = pfdev; + ml403_ac97cr->irq = -1; + ml403_ac97cr->enable_irq = 0; + ml403_ac97cr->capture_irq = -1; + ml403_ac97cr->enable_capture_irq = 0; + ml403_ac97cr->port = NULL; + ml403_ac97cr->res_port = NULL; + + PDEBUG(INIT_INFO, "Trying to reserve resources now ...\n"); + resource = platform_get_resource(pfdev, IORESOURCE_MEM, 0); + /* get "port" */ + ml403_ac97cr->port = ioremap_nocache(resource->start, + (resource->end) - + (resource->start) + 1); + if (ml403_ac97cr->port == NULL) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to remap memory region (%pR)\n", + resource); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "remap controller memory region to " + "0x%x done\n", (unsigned int)ml403_ac97cr->port); + /* get irq */ + irq = platform_get_irq(pfdev, 0); + if (request_irq(irq, snd_ml403_ac97cr_irq, 0, + dev_name(&pfdev->dev), (void *)ml403_ac97cr)) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to grab IRQ %d\n", + irq); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + ml403_ac97cr->irq = irq; + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "request (playback) irq %d done\n", + ml403_ac97cr->irq); + irq = platform_get_irq(pfdev, 1); + if (request_irq(irq, snd_ml403_ac97cr_irq, 0, + dev_name(&pfdev->dev), (void *)ml403_ac97cr)) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to grab IRQ %d\n", + irq); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + ml403_ac97cr->capture_irq = irq; + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "request (capture) irq %d done\n", + ml403_ac97cr->capture_irq); + + err = snd_ml403_ac97cr_chip_init(ml403_ac97cr); + if (err < 0) { + snd_ml403_ac97cr_free(ml403_ac97cr); + return err; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ml403_ac97cr, &ops); + if (err < 0) { + PDEBUG(INIT_FAILURE, "probe(): snd_device_new() failed!\n"); + snd_ml403_ac97cr_free(ml403_ac97cr); + return err; + } + + *rml403_ac97cr = ml403_ac97cr; + return 0; +} + +static void snd_ml403_ac97cr_mixer_free(struct snd_ac97 *ac97) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; + PDEBUG(INIT_INFO, "mixer_free():\n"); + ml403_ac97cr->ac97 = NULL; + PDEBUG(INIT_INFO, "mixer_free(): (done)\n"); +} + +static int +snd_ml403_ac97cr_mixer(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; + int err; + static struct snd_ac97_bus_ops ops = { + .write = snd_ml403_ac97cr_codec_write, + .read = snd_ml403_ac97cr_codec_read, + }; + PDEBUG(INIT_INFO, "mixer():\n"); + err = snd_ac97_bus(ml403_ac97cr->card, 0, &ops, NULL, &bus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ml403_ac97cr->ac97_fake = 1; + lm4550_regfile_init(); +#ifdef CODEC_STAT + ml403_ac97cr->ac97_read = 0; + ml403_ac97cr->ac97_write = 0; +#endif + ac97.private_data = ml403_ac97cr; + ac97.private_free = snd_ml403_ac97cr_mixer_free; + ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM | + AC97_SCAP_NO_SPDIF; + err = snd_ac97_mixer(bus, &ac97, &ml403_ac97cr->ac97); + ml403_ac97cr->ac97_fake = 0; + lm4550_regfile_write_values_after_init(ml403_ac97cr->ac97); + PDEBUG(INIT_INFO, "mixer(): (done) snd_ac97_mixer()=%d\n", err); + return err; +} + +static int +snd_ml403_ac97cr_pcm(struct snd_ml403_ac97cr *ml403_ac97cr, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ml403_ac97cr->card, "ML403AC97CR/1", device, 1, 1, + &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_ml403_ac97cr_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_ml403_ac97cr_capture_ops); + pcm->private_data = ml403_ac97cr; + pcm->info_flags = 0; + strcpy(pcm->name, "ML403AC97CR DAC/ADC"); + ml403_ac97cr->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64 * 1024, + 128 * 1024); + return 0; +} + +static int snd_ml403_ac97cr_probe(struct platform_device *pfdev) +{ + struct snd_card *card; + struct snd_ml403_ac97cr *ml403_ac97cr = NULL; + int err; + int dev = pfdev->id; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) + return -ENOENT; + + err = snd_card_new(&pfdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) + return err; + err = snd_ml403_ac97cr_create(card, pfdev, &ml403_ac97cr); + if (err < 0) { + PDEBUG(INIT_FAILURE, "probe(): create failed!\n"); + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): create done\n"); + card->private_data = ml403_ac97cr; + err = snd_ml403_ac97cr_mixer(ml403_ac97cr); + if (err < 0) { + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): mixer done\n"); + err = snd_ml403_ac97cr_pcm(ml403_ac97cr, 0); + if (err < 0) { + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): PCM done\n"); + strcpy(card->driver, SND_ML403_AC97CR_DRIVER); + strcpy(card->shortname, "ML403 AC97 Controller Reference"); + sprintf(card->longname, "%s %s at 0x%lx, irq %i & %i, device %i", + card->shortname, card->driver, + (unsigned long)ml403_ac97cr->port, ml403_ac97cr->irq, + ml403_ac97cr->capture_irq, dev + 1); + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + platform_set_drvdata(pfdev, card); + PDEBUG(INIT_INFO, "probe(): (done)\n"); + return 0; +} + +static int snd_ml403_ac97cr_remove(struct platform_device *pfdev) +{ + snd_card_free(platform_get_drvdata(pfdev)); + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:" SND_ML403_AC97CR_DRIVER); + +static struct platform_driver snd_ml403_ac97cr_driver = { + .probe = snd_ml403_ac97cr_probe, + .remove = snd_ml403_ac97cr_remove, + .driver = { + .name = SND_ML403_AC97CR_DRIVER, + }, +}; + +module_platform_driver(snd_ml403_ac97cr_driver); diff --git a/sound/drivers/mpu401/Makefile b/sound/drivers/mpu401/Makefile new file mode 100644 index 000000000..918f83f34 --- /dev/null +++ b/sound/drivers/mpu401/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-mpu401-objs := mpu401.o +snd-mpu401-uart-objs := mpu401_uart.o + +obj-$(CONFIG_SND_MPU401_UART) += snd-mpu401-uart.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_MPU401) += snd-mpu401.o diff --git a/sound/drivers/mpu401/mpu401.c b/sound/drivers/mpu401/mpu401.c new file mode 100644 index 000000000..b6715764c --- /dev/null +++ b/sound/drivers/mpu401/mpu401.c @@ -0,0 +1,287 @@ +/* + * Driver for generic MPU-401 boards (UART mode only) + * Copyright (c) by Jaroslav Kysela + * Copyright (c) 2004 by Castet Matthieu + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("MPU-401 UART"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* exclude the first card */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +#ifdef CONFIG_PNP +static bool pnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* MPU-401 port number */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* MPU-401 IRQ */ +static bool uart_enter[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for MPU-401 device."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for MPU-401 device."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable MPU-401 device."); +#ifdef CONFIG_PNP +module_param_array(pnp, bool, NULL, 0444); +MODULE_PARM_DESC(pnp, "PnP detection for MPU-401 device."); +#endif +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for MPU-401 device."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for MPU-401 device."); +module_param_array(uart_enter, bool, NULL, 0444); +MODULE_PARM_DESC(uart_enter, "Issue UART_ENTER command at open."); + +static struct platform_device *platform_devices[SNDRV_CARDS]; +static int pnp_registered; +static unsigned int snd_mpu401_devices; + +static int snd_mpu401_create(struct device *devptr, int dev, + struct snd_card **rcard) +{ + struct snd_card *card; + int err; + + if (!uart_enter[dev]) + snd_printk(KERN_ERR "the uart_enter option is obsolete; remove it\n"); + + *rcard = NULL; + err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) + return err; + strcpy(card->driver, "MPU-401 UART"); + strcpy(card->shortname, card->driver); + sprintf(card->longname, "%s at %#lx, ", card->shortname, port[dev]); + if (irq[dev] >= 0) { + sprintf(card->longname + strlen(card->longname), "irq %d", irq[dev]); + } else { + strcat(card->longname, "polled"); + } + + err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, port[dev], 0, + irq[dev], NULL); + if (err < 0) { + printk(KERN_ERR "MPU401 not detected at 0x%lx\n", port[dev]); + goto _err; + } + + *rcard = card; + return 0; + + _err: + snd_card_free(card); + return err; +} + +static int snd_mpu401_probe(struct platform_device *devptr) +{ + int dev = devptr->id; + int err; + struct snd_card *card; + + if (port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR "specify port\n"); + return -EINVAL; + } + if (irq[dev] == SNDRV_AUTO_IRQ) { + snd_printk(KERN_ERR "specify or disable IRQ\n"); + return -EINVAL; + } + err = snd_mpu401_create(&devptr->dev, dev, &card); + if (err < 0) + return err; + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + platform_set_drvdata(devptr, card); + return 0; +} + +static int snd_mpu401_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +#define SND_MPU401_DRIVER "snd_mpu401" + +static struct platform_driver snd_mpu401_driver = { + .probe = snd_mpu401_probe, + .remove = snd_mpu401_remove, + .driver = { + .name = SND_MPU401_DRIVER, + }, +}; + + +#ifdef CONFIG_PNP + +#define IO_EXTENT 2 + +static const struct pnp_device_id snd_mpu401_pnpids[] = { + { .id = "PNPb006" }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp, snd_mpu401_pnpids); + +static int snd_mpu401_pnp(int dev, struct pnp_dev *device, + const struct pnp_device_id *id) +{ + if (!pnp_port_valid(device, 0) || + pnp_port_flags(device, 0) & IORESOURCE_DISABLED) { + snd_printk(KERN_ERR "no PnP port\n"); + return -ENODEV; + } + if (pnp_port_len(device, 0) < IO_EXTENT) { + snd_printk(KERN_ERR "PnP port length is %llu, expected %d\n", + (unsigned long long)pnp_port_len(device, 0), + IO_EXTENT); + return -ENODEV; + } + port[dev] = pnp_port_start(device, 0); + + if (!pnp_irq_valid(device, 0) || + pnp_irq_flags(device, 0) & IORESOURCE_DISABLED) { + snd_printk(KERN_WARNING "no PnP irq, using polling\n"); + irq[dev] = -1; + } else { + irq[dev] = pnp_irq(device, 0); + } + return 0; +} + +static int snd_mpu401_pnp_probe(struct pnp_dev *pnp_dev, + const struct pnp_device_id *id) +{ + static int dev; + struct snd_card *card; + int err; + + for ( ; dev < SNDRV_CARDS; ++dev) { + if (!enable[dev] || !pnp[dev]) + continue; + err = snd_mpu401_pnp(dev, pnp_dev, id); + if (err < 0) + return err; + err = snd_mpu401_create(&pnp_dev->dev, dev, &card); + if (err < 0) + return err; + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + pnp_set_drvdata(pnp_dev, card); + snd_mpu401_devices++; + ++dev; + return 0; + } + return -ENODEV; +} + +static void snd_mpu401_pnp_remove(struct pnp_dev *dev) +{ + struct snd_card *card = (struct snd_card *) pnp_get_drvdata(dev); + + snd_card_disconnect(card); + snd_card_free_when_closed(card); +} + +static struct pnp_driver snd_mpu401_pnp_driver = { + .name = "mpu401", + .id_table = snd_mpu401_pnpids, + .probe = snd_mpu401_pnp_probe, + .remove = snd_mpu401_pnp_remove, +}; +#else +static struct pnp_driver snd_mpu401_pnp_driver; +#endif + +static void snd_mpu401_unregister_all(void) +{ + int i; + + if (pnp_registered) + pnp_unregister_driver(&snd_mpu401_pnp_driver); + for (i = 0; i < ARRAY_SIZE(platform_devices); ++i) + platform_device_unregister(platform_devices[i]); + platform_driver_unregister(&snd_mpu401_driver); +} + +static int __init alsa_card_mpu401_init(void) +{ + int i, err; + + if ((err = platform_driver_register(&snd_mpu401_driver)) < 0) + return err; + + for (i = 0; i < SNDRV_CARDS; i++) { + struct platform_device *device; + if (! enable[i]) + continue; +#ifdef CONFIG_PNP + if (pnp[i]) + continue; +#endif + device = platform_device_register_simple(SND_MPU401_DRIVER, + i, NULL, 0); + if (IS_ERR(device)) + continue; + if (!platform_get_drvdata(device)) { + platform_device_unregister(device); + continue; + } + platform_devices[i] = device; + snd_mpu401_devices++; + } + err = pnp_register_driver(&snd_mpu401_pnp_driver); + if (!err) + pnp_registered = 1; + + if (!snd_mpu401_devices) { +#ifdef MODULE + printk(KERN_ERR "MPU-401 device not found or device busy\n"); +#endif + snd_mpu401_unregister_all(); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_mpu401_exit(void) +{ + snd_mpu401_unregister_all(); +} + +module_init(alsa_card_mpu401_init) +module_exit(alsa_card_mpu401_exit) diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c new file mode 100644 index 000000000..dae26e856 --- /dev/null +++ b/sound/drivers/mpu401/mpu401_uart.c @@ -0,0 +1,619 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of MPU-401 in UART mode + * + * MPU-401 supports UART mode which is not capable generate transmit + * interrupts thus output is done via polling. Without interrupt, + * input is done also via polling. Do not expect good performance. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * 13-03-2003: + * Added support for different kind of hardware I/O. Build in choices + * are port and mmio. For other kind of I/O, set mpu->read and + * mpu->write to your own I/O functions. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Routines for control of MPU-401 in UART mode"); +MODULE_LICENSE("GPL"); + +static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu); +static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu); + +/* + + */ + +#define snd_mpu401_input_avail(mpu) \ + (!(mpu->read(mpu, MPU401C(mpu)) & MPU401_RX_EMPTY)) +#define snd_mpu401_output_ready(mpu) \ + (!(mpu->read(mpu, MPU401C(mpu)) & MPU401_TX_FULL)) + +/* Build in lowlevel io */ +static void mpu401_write_port(struct snd_mpu401 *mpu, unsigned char data, + unsigned long addr) +{ + outb(data, addr); +} + +static unsigned char mpu401_read_port(struct snd_mpu401 *mpu, + unsigned long addr) +{ + return inb(addr); +} + +static void mpu401_write_mmio(struct snd_mpu401 *mpu, unsigned char data, + unsigned long addr) +{ + writeb(data, (void __iomem *)addr); +} + +static unsigned char mpu401_read_mmio(struct snd_mpu401 *mpu, + unsigned long addr) +{ + return readb((void __iomem *)addr); +} +/* */ + +static void snd_mpu401_uart_clear_rx(struct snd_mpu401 *mpu) +{ + int timeout = 100000; + for (; timeout > 0 && snd_mpu401_input_avail(mpu); timeout--) + mpu->read(mpu, MPU401D(mpu)); +#ifdef CONFIG_SND_DEBUG + if (timeout <= 0) + snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n", + mpu->read(mpu, MPU401C(mpu))); +#endif +} + +static void uart_interrupt_tx(struct snd_mpu401 *mpu) +{ + unsigned long flags; + + if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) && + test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) { + spin_lock_irqsave(&mpu->output_lock, flags); + snd_mpu401_uart_output_write(mpu); + spin_unlock_irqrestore(&mpu->output_lock, flags); + } +} + +static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu) +{ + unsigned long flags; + + if (mpu->info_flags & MPU401_INFO_INPUT) { + spin_lock_irqsave(&mpu->input_lock, flags); + if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) + snd_mpu401_uart_input_read(mpu); + else + snd_mpu401_uart_clear_rx(mpu); + spin_unlock_irqrestore(&mpu->input_lock, flags); + } + if (! (mpu->info_flags & MPU401_INFO_TX_IRQ)) + /* ok. for better Tx performance try do some output + when input is done */ + uart_interrupt_tx(mpu); +} + +/** + * snd_mpu401_uart_interrupt - generic MPU401-UART interrupt handler + * @irq: the irq number + * @dev_id: mpu401 instance + * + * Processes the interrupt for MPU401-UART i/o. + * + * Return: %IRQ_HANDLED if the interrupt was handled. %IRQ_NONE otherwise. + */ +irqreturn_t snd_mpu401_uart_interrupt(int irq, void *dev_id) +{ + struct snd_mpu401 *mpu = dev_id; + + if (!mpu) + return IRQ_NONE; + _snd_mpu401_uart_interrupt(mpu); + return IRQ_HANDLED; +} + +EXPORT_SYMBOL(snd_mpu401_uart_interrupt); + +/** + * snd_mpu401_uart_interrupt_tx - generic MPU401-UART transmit irq handler + * @irq: the irq number + * @dev_id: mpu401 instance + * + * Processes the interrupt for MPU401-UART output. + * + * Return: %IRQ_HANDLED if the interrupt was handled. %IRQ_NONE otherwise. + */ +irqreturn_t snd_mpu401_uart_interrupt_tx(int irq, void *dev_id) +{ + struct snd_mpu401 *mpu = dev_id; + + if (!mpu) + return IRQ_NONE; + uart_interrupt_tx(mpu); + return IRQ_HANDLED; +} + +EXPORT_SYMBOL(snd_mpu401_uart_interrupt_tx); + +/* + * timer callback + * reprogram the timer and call the interrupt job + */ +static void snd_mpu401_uart_timer(struct timer_list *t) +{ + struct snd_mpu401 *mpu = from_timer(mpu, t, timer); + unsigned long flags; + + spin_lock_irqsave(&mpu->timer_lock, flags); + /*mpu->mode |= MPU401_MODE_TIMER;*/ + mod_timer(&mpu->timer, 1 + jiffies); + spin_unlock_irqrestore(&mpu->timer_lock, flags); + if (mpu->rmidi) + _snd_mpu401_uart_interrupt(mpu); +} + +/* + * initialize the timer callback if not programmed yet + */ +static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input) +{ + unsigned long flags; + + spin_lock_irqsave (&mpu->timer_lock, flags); + if (mpu->timer_invoked == 0) { + timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); + mod_timer(&mpu->timer, 1 + jiffies); + } + mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER : + MPU401_MODE_OUTPUT_TIMER; + spin_unlock_irqrestore (&mpu->timer_lock, flags); +} + +/* + * remove the timer callback if still active + */ +static void snd_mpu401_uart_remove_timer (struct snd_mpu401 *mpu, int input) +{ + unsigned long flags; + + spin_lock_irqsave (&mpu->timer_lock, flags); + if (mpu->timer_invoked) { + mpu->timer_invoked &= input ? ~MPU401_MODE_INPUT_TIMER : + ~MPU401_MODE_OUTPUT_TIMER; + if (! mpu->timer_invoked) + del_timer(&mpu->timer); + } + spin_unlock_irqrestore (&mpu->timer_lock, flags); +} + +/* + * send a UART command + * return zero if successful, non-zero for some errors + */ + +static int snd_mpu401_uart_cmd(struct snd_mpu401 * mpu, unsigned char cmd, + int ack) +{ + unsigned long flags; + int timeout, ok; + + spin_lock_irqsave(&mpu->input_lock, flags); + if (mpu->hardware != MPU401_HW_TRID4DWAVE) { + mpu->write(mpu, 0x00, MPU401D(mpu)); + /*snd_mpu401_uart_clear_rx(mpu);*/ + } + /* ok. standard MPU-401 initialization */ + if (mpu->hardware != MPU401_HW_SB) { + for (timeout = 1000; timeout > 0 && + !snd_mpu401_output_ready(mpu); timeout--) + udelay(10); +#ifdef CONFIG_SND_DEBUG + if (!timeout) + snd_printk(KERN_ERR "cmd: tx timeout (status = 0x%x)\n", + mpu->read(mpu, MPU401C(mpu))); +#endif + } + mpu->write(mpu, cmd, MPU401C(mpu)); + if (ack && !(mpu->info_flags & MPU401_INFO_NO_ACK)) { + ok = 0; + timeout = 10000; + while (!ok && timeout-- > 0) { + if (snd_mpu401_input_avail(mpu)) { + if (mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK) + ok = 1; + } + } + if (!ok && mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK) + ok = 1; + } else + ok = 1; + spin_unlock_irqrestore(&mpu->input_lock, flags); + if (!ok) { + snd_printk(KERN_ERR "cmd: 0x%x failed at 0x%lx " + "(status = 0x%x, data = 0x%x)\n", cmd, mpu->port, + mpu->read(mpu, MPU401C(mpu)), + mpu->read(mpu, MPU401D(mpu))); + return 1; + } + return 0; +} + +static int snd_mpu401_do_reset(struct snd_mpu401 *mpu) +{ + if (snd_mpu401_uart_cmd(mpu, MPU401_RESET, 1)) + return -EIO; + if (snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 0)) + return -EIO; + return 0; +} + +/* + * input/output open/close - protected by open_mutex in rawmidi.c + */ +static int snd_mpu401_uart_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_mpu401 *mpu; + int err; + + mpu = substream->rmidi->private_data; + if (mpu->open_input && (err = mpu->open_input(mpu)) < 0) + return err; + if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode)) { + if (snd_mpu401_do_reset(mpu) < 0) + goto error_out; + } + mpu->substream_input = substream; + set_bit(MPU401_MODE_BIT_INPUT, &mpu->mode); + return 0; + +error_out: + if (mpu->open_input && mpu->close_input) + mpu->close_input(mpu); + return -EIO; +} + +static int snd_mpu401_uart_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_mpu401 *mpu; + int err; + + mpu = substream->rmidi->private_data; + if (mpu->open_output && (err = mpu->open_output(mpu)) < 0) + return err; + if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) { + if (snd_mpu401_do_reset(mpu) < 0) + goto error_out; + } + mpu->substream_output = substream; + set_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode); + return 0; + +error_out: + if (mpu->open_output && mpu->close_output) + mpu->close_output(mpu); + return -EIO; +} + +static int snd_mpu401_uart_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_mpu401 *mpu; + int err = 0; + + mpu = substream->rmidi->private_data; + clear_bit(MPU401_MODE_BIT_INPUT, &mpu->mode); + mpu->substream_input = NULL; + if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode)) + err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0); + if (mpu->close_input) + mpu->close_input(mpu); + if (err) + return -EIO; + return 0; +} + +static int snd_mpu401_uart_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_mpu401 *mpu; + int err = 0; + + mpu = substream->rmidi->private_data; + clear_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode); + mpu->substream_output = NULL; + if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) + err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0); + if (mpu->close_output) + mpu->close_output(mpu); + if (err) + return -EIO; + return 0; +} + +/* + * trigger input callback + */ +static void +snd_mpu401_uart_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_mpu401 *mpu; + int max = 64; + + mpu = substream->rmidi->private_data; + if (up) { + if (! test_and_set_bit(MPU401_MODE_BIT_INPUT_TRIGGER, + &mpu->mode)) { + /* first time - flush FIFO */ + while (max-- > 0) + mpu->read(mpu, MPU401D(mpu)); + if (mpu->info_flags & MPU401_INFO_USE_TIMER) + snd_mpu401_uart_add_timer(mpu, 1); + } + + /* read data in advance */ + spin_lock_irqsave(&mpu->input_lock, flags); + snd_mpu401_uart_input_read(mpu); + spin_unlock_irqrestore(&mpu->input_lock, flags); + } else { + if (mpu->info_flags & MPU401_INFO_USE_TIMER) + snd_mpu401_uart_remove_timer(mpu, 1); + clear_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode); + } + +} + +/* + * transfer input pending data + * call with input_lock spinlock held + */ +static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu) +{ + int max = 128; + unsigned char byte; + + while (max-- > 0) { + if (! snd_mpu401_input_avail(mpu)) + break; /* input not available */ + byte = mpu->read(mpu, MPU401D(mpu)); + if (test_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode)) + snd_rawmidi_receive(mpu->substream_input, &byte, 1); + } +} + +/* + * Tx FIFO sizes: + * CS4237B - 16 bytes + * AudioDrive ES1688 - 12 bytes + * S3 SonicVibes - 8 bytes + * SoundBlaster AWE 64 - 2 bytes (ugly hardware) + */ + +/* + * write output pending bytes + * call with output_lock spinlock held + */ +static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu) +{ + unsigned char byte; + int max = 256; + + do { + if (snd_rawmidi_transmit_peek(mpu->substream_output, + &byte, 1) == 1) { + /* + * Try twice because there is hardware that insists on + * setting the output busy bit after each write. + */ + if (!snd_mpu401_output_ready(mpu) && + !snd_mpu401_output_ready(mpu)) + break; /* Tx FIFO full - try again later */ + mpu->write(mpu, byte, MPU401D(mpu)); + snd_rawmidi_transmit_ack(mpu->substream_output, 1); + } else { + snd_mpu401_uart_remove_timer (mpu, 0); + break; /* no other data - leave the tx loop */ + } + } while (--max > 0); +} + +/* + * output trigger callback + */ +static void +snd_mpu401_uart_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_mpu401 *mpu; + + mpu = substream->rmidi->private_data; + if (up) { + set_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode); + + /* try to add the timer at each output trigger, + * since the output timer might have been removed in + * snd_mpu401_uart_output_write(). + */ + if (! (mpu->info_flags & MPU401_INFO_TX_IRQ)) + snd_mpu401_uart_add_timer(mpu, 0); + + /* output pending data */ + spin_lock_irqsave(&mpu->output_lock, flags); + snd_mpu401_uart_output_write(mpu); + spin_unlock_irqrestore(&mpu->output_lock, flags); + } else { + if (! (mpu->info_flags & MPU401_INFO_TX_IRQ)) + snd_mpu401_uart_remove_timer(mpu, 0); + clear_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode); + } +} + +/* + + */ + +static const struct snd_rawmidi_ops snd_mpu401_uart_output = +{ + .open = snd_mpu401_uart_output_open, + .close = snd_mpu401_uart_output_close, + .trigger = snd_mpu401_uart_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_mpu401_uart_input = +{ + .open = snd_mpu401_uart_input_open, + .close = snd_mpu401_uart_input_close, + .trigger = snd_mpu401_uart_input_trigger, +}; + +static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi) +{ + struct snd_mpu401 *mpu = rmidi->private_data; + if (mpu->irq >= 0) + free_irq(mpu->irq, (void *) mpu); + release_and_free_resource(mpu->res); + kfree(mpu); +} + +/** + * snd_mpu401_uart_new - create an MPU401-UART instance + * @card: the card instance + * @device: the device index, zero-based + * @hardware: the hardware type, MPU401_HW_XXXX + * @port: the base address of MPU401 port + * @info_flags: bitflags MPU401_INFO_XXX + * @irq: the ISA irq number, -1 if not to be allocated + * @rrawmidi: the pointer to store the new rawmidi instance + * + * Creates a new MPU-401 instance. + * + * Note that the rawmidi instance is returned on the rrawmidi argument, + * not the mpu401 instance itself. To access to the mpu401 instance, + * cast from rawmidi->private_data (with struct snd_mpu401 magic-cast). + * + * Return: Zero if successful, or a negative error code. + */ +int snd_mpu401_uart_new(struct snd_card *card, int device, + unsigned short hardware, + unsigned long port, + unsigned int info_flags, + int irq, + struct snd_rawmidi ** rrawmidi) +{ + struct snd_mpu401 *mpu; + struct snd_rawmidi *rmidi; + int in_enable, out_enable; + int err; + + if (rrawmidi) + *rrawmidi = NULL; + if (! (info_flags & (MPU401_INFO_INPUT | MPU401_INFO_OUTPUT))) + info_flags |= MPU401_INFO_INPUT | MPU401_INFO_OUTPUT; + in_enable = (info_flags & MPU401_INFO_INPUT) ? 1 : 0; + out_enable = (info_flags & MPU401_INFO_OUTPUT) ? 1 : 0; + if ((err = snd_rawmidi_new(card, "MPU-401U", device, + out_enable, in_enable, &rmidi)) < 0) + return err; + mpu = kzalloc(sizeof(*mpu), GFP_KERNEL); + if (!mpu) { + err = -ENOMEM; + goto free_device; + } + rmidi->private_data = mpu; + rmidi->private_free = snd_mpu401_uart_free; + spin_lock_init(&mpu->input_lock); + spin_lock_init(&mpu->output_lock); + spin_lock_init(&mpu->timer_lock); + mpu->hardware = hardware; + mpu->irq = -1; + if (! (info_flags & MPU401_INFO_INTEGRATED)) { + int res_size = hardware == MPU401_HW_PC98II ? 4 : 2; + mpu->res = request_region(port, res_size, "MPU401 UART"); + if (!mpu->res) { + snd_printk(KERN_ERR "mpu401_uart: " + "unable to grab port 0x%lx size %d\n", + port, res_size); + err = -EBUSY; + goto free_device; + } + } + if (info_flags & MPU401_INFO_MMIO) { + mpu->write = mpu401_write_mmio; + mpu->read = mpu401_read_mmio; + } else { + mpu->write = mpu401_write_port; + mpu->read = mpu401_read_port; + } + mpu->port = port; + if (hardware == MPU401_HW_PC98II) + mpu->cport = port + 2; + else + mpu->cport = port + 1; + if (irq >= 0) { + if (request_irq(irq, snd_mpu401_uart_interrupt, 0, + "MPU401 UART", (void *) mpu)) { + snd_printk(KERN_ERR "mpu401_uart: " + "unable to grab IRQ %d\n", irq); + err = -EBUSY; + goto free_device; + } + } + if (irq < 0 && !(info_flags & MPU401_INFO_IRQ_HOOK)) + info_flags |= MPU401_INFO_USE_TIMER; + mpu->info_flags = info_flags; + mpu->irq = irq; + if (card->shortname[0]) + snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI", + card->shortname); + else + sprintf(rmidi->name, "MPU-401 MIDI %d-%d",card->number, device); + if (out_enable) { + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_mpu401_uart_output); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + } + if (in_enable) { + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_mpu401_uart_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + if (out_enable) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + } + mpu->rmidi = rmidi; + if (rrawmidi) + *rrawmidi = rmidi; + return 0; +free_device: + snd_device_free(card, rmidi); + return err; +} + +EXPORT_SYMBOL(snd_mpu401_uart_new); diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c new file mode 100644 index 000000000..547662e02 --- /dev/null +++ b/sound/drivers/mtpav.c @@ -0,0 +1,786 @@ +/* + * MOTU Midi Timepiece ALSA Main routines + * Copyright by Michael T. Mayers (c) Jan 09, 2000 + * mail: michael@tweakoz.com + * Thanks to John Galbraith + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * This driver is for the 'Mark Of The Unicorn' (MOTU) + * MidiTimePiece AV multiport MIDI interface + * + * IOPORTS + * ------- + * 8 MIDI Ins and 8 MIDI outs + * Video Sync In (BNC), Word Sync Out (BNC), + * ADAT Sync Out (DB9) + * SMPTE in/out (1/4") + * 2 programmable pedal/footswitch inputs and 4 programmable MIDI controller knobs. + * Macintosh RS422 serial port + * RS422 "network" port for ganging multiple MTP's + * PC Parallel Port ( which this driver currently uses ) + * + * MISC FEATURES + * ------------- + * Hardware MIDI routing, merging, and filtering + * MIDI Synchronization to Video, ADAT, SMPTE and other Clock sources + * 128 'scene' memories, recallable from MIDI program change + * + * + * ChangeLog + * Jun 11 2001 Takashi Iwai + * - Recoded & debugged + * - Added timer interrupt for midi outputs + * - hwports is between 1 and 8, which specifies the number of hardware ports. + * The three global ports, computer, adat and broadcast ports, are created + * always after h/w and remote ports. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * globals + */ +MODULE_AUTHOR("Michael T. Mayers"); +MODULE_DESCRIPTION("MOTU MidiTimePiece AV multiport MIDI"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{MOTU,MidiTimePiece AV multiport MIDI}}"); + +// io resources +#define MTPAV_IOBASE 0x378 +#define MTPAV_IRQ 7 +#define MTPAV_MAX_PORTS 8 + +static int index = SNDRV_DEFAULT_IDX1; +static char *id = SNDRV_DEFAULT_STR1; +static long port = MTPAV_IOBASE; /* 0x378, 0x278 */ +static int irq = MTPAV_IRQ; /* 7, 5 */ +static int hwports = MTPAV_MAX_PORTS; /* use hardware ports 1-8 */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for MotuMTPAV MIDI."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for MotuMTPAV MIDI."); +module_param_hw(port, long, ioport, 0444); +MODULE_PARM_DESC(port, "Parallel port # for MotuMTPAV MIDI."); +module_param_hw(irq, int, irq, 0444); +MODULE_PARM_DESC(irq, "Parallel IRQ # for MotuMTPAV MIDI."); +module_param(hwports, int, 0444); +MODULE_PARM_DESC(hwports, "Hardware ports # for MotuMTPAV MIDI."); + +static struct platform_device *device; + +/* + * defines + */ +//#define USE_FAKE_MTP // don't actually read/write to MTP device (for debugging without an actual unit) (does not work yet) + +// parallel port usage masks +#define SIGS_BYTE 0x08 +#define SIGS_RFD 0x80 +#define SIGS_IRQ 0x40 +#define SIGS_IN0 0x10 +#define SIGS_IN1 0x20 + +#define SIGC_WRITE 0x04 +#define SIGC_READ 0x08 +#define SIGC_INTEN 0x10 + +#define DREG 0 +#define SREG 1 +#define CREG 2 + +// +#define MTPAV_MODE_INPUT_OPENED 0x01 +#define MTPAV_MODE_OUTPUT_OPENED 0x02 +#define MTPAV_MODE_INPUT_TRIGGERED 0x04 +#define MTPAV_MODE_OUTPUT_TRIGGERED 0x08 + +#define NUMPORTS (0x12+1) + + +/* + */ + +struct mtpav_port { + u8 number; + u8 hwport; + u8 mode; + u8 running_status; + struct snd_rawmidi_substream *input; + struct snd_rawmidi_substream *output; +}; + +struct mtpav { + struct snd_card *card; + unsigned long port; + struct resource *res_port; + int irq; /* interrupt (for inputs) */ + spinlock_t spinlock; + int share_irq; /* number of accesses to input interrupts */ + int istimer; /* number of accesses to timer interrupts */ + struct timer_list timer; /* timer interrupts for outputs */ + struct snd_rawmidi *rmidi; + int num_ports; /* number of hw ports (1-8) */ + struct mtpav_port ports[NUMPORTS]; /* all ports including computer, adat and bc */ + + u32 inmidiport; /* selected input midi port */ + u32 inmidistate; /* during midi command 0xf5 */ + + u32 outmidihwport; /* selected output midi hw port */ +}; + + +/* + * possible hardware ports (selected by 0xf5 port message) + * 0x00 all ports + * 0x01 .. 0x08 this MTP's ports 1..8 + * 0x09 .. 0x10 networked MTP's ports (9..16) + * 0x11 networked MTP's computer port + * 0x63 to ADAT + * + * mappig: + * subdevice 0 - (X-1) ports + * X - (2*X-1) networked ports + * X computer + * X+1 ADAT + * X+2 all ports + * + * where X = chip->num_ports + */ + +#define MTPAV_PIDX_COMPUTER 0 +#define MTPAV_PIDX_ADAT 1 +#define MTPAV_PIDX_BROADCAST 2 + + +static int translate_subdevice_to_hwport(struct mtpav *chip, int subdev) +{ + if (subdev < 0) + return 0x01; /* invalid - use port 0 as default */ + else if (subdev < chip->num_ports) + return subdev + 1; /* single mtp port */ + else if (subdev < chip->num_ports * 2) + return subdev - chip->num_ports + 0x09; /* remote port */ + else if (subdev == chip->num_ports * 2 + MTPAV_PIDX_COMPUTER) + return 0x11; /* computer port */ + else if (subdev == chip->num_ports + MTPAV_PIDX_ADAT) + return 0x63; /* ADAT */ + return 0; /* all ports */ +} + +static int translate_hwport_to_subdevice(struct mtpav *chip, int hwport) +{ + int p; + if (hwport <= 0x00) /* all ports */ + return chip->num_ports + MTPAV_PIDX_BROADCAST; + else if (hwport <= 0x08) { /* single port */ + p = hwport - 1; + if (p >= chip->num_ports) + p = 0; + return p; + } else if (hwport <= 0x10) { /* remote port */ + p = hwport - 0x09 + chip->num_ports; + if (p >= chip->num_ports * 2) + p = chip->num_ports; + return p; + } else if (hwport == 0x11) /* computer port */ + return chip->num_ports + MTPAV_PIDX_COMPUTER; + else /* ADAT */ + return chip->num_ports + MTPAV_PIDX_ADAT; +} + + +/* + */ + +static u8 snd_mtpav_getreg(struct mtpav *chip, u16 reg) +{ + u8 rval = 0; + + if (reg == SREG) { + rval = inb(chip->port + SREG); + rval = (rval & 0xf8); + } else if (reg == CREG) { + rval = inb(chip->port + CREG); + rval = (rval & 0x1c); + } + + return rval; +} + +/* + */ + +static inline void snd_mtpav_mputreg(struct mtpav *chip, u16 reg, u8 val) +{ + if (reg == DREG || reg == CREG) + outb(val, chip->port + reg); +} + +/* + */ + +static void snd_mtpav_wait_rfdhi(struct mtpav *chip) +{ + int counts = 10000; + u8 sbyte; + + sbyte = snd_mtpav_getreg(chip, SREG); + while (!(sbyte & SIGS_RFD) && counts--) { + sbyte = snd_mtpav_getreg(chip, SREG); + udelay(10); + } +} + +static void snd_mtpav_send_byte(struct mtpav *chip, u8 byte) +{ + u8 tcbyt; + u8 clrwrite; + u8 setwrite; + + snd_mtpav_wait_rfdhi(chip); + + ///////////////// + + tcbyt = snd_mtpav_getreg(chip, CREG); + clrwrite = tcbyt & (SIGC_WRITE ^ 0xff); + setwrite = tcbyt | SIGC_WRITE; + + snd_mtpav_mputreg(chip, DREG, byte); + snd_mtpav_mputreg(chip, CREG, clrwrite); // clear write bit + + snd_mtpav_mputreg(chip, CREG, setwrite); // set write bit + +} + + +/* + */ + +/* call this with spin lock held */ +static void snd_mtpav_output_port_write(struct mtpav *mtp_card, + struct mtpav_port *portp, + struct snd_rawmidi_substream *substream) +{ + u8 outbyte; + + // Get the outbyte first, so we can emulate running status if + // necessary + if (snd_rawmidi_transmit(substream, &outbyte, 1) != 1) + return; + + // send port change command if necessary + + if (portp->hwport != mtp_card->outmidihwport) { + mtp_card->outmidihwport = portp->hwport; + + snd_mtpav_send_byte(mtp_card, 0xf5); + snd_mtpav_send_byte(mtp_card, portp->hwport); + /* + snd_printk(KERN_DEBUG "new outport: 0x%x\n", + (unsigned int) portp->hwport); + */ + if (!(outbyte & 0x80) && portp->running_status) + snd_mtpav_send_byte(mtp_card, portp->running_status); + } + + // send data + + do { + if (outbyte & 0x80) + portp->running_status = outbyte; + + snd_mtpav_send_byte(mtp_card, outbyte); + } while (snd_rawmidi_transmit(substream, &outbyte, 1) == 1); +} + +static void snd_mtpav_output_write(struct snd_rawmidi_substream *substream) +{ + struct mtpav *mtp_card = substream->rmidi->private_data; + struct mtpav_port *portp = &mtp_card->ports[substream->number]; + unsigned long flags; + + spin_lock_irqsave(&mtp_card->spinlock, flags); + snd_mtpav_output_port_write(mtp_card, portp, substream); + spin_unlock_irqrestore(&mtp_card->spinlock, flags); +} + + +/* + * mtpav control + */ + +static void snd_mtpav_portscan(struct mtpav *chip) // put mtp into smart routing mode +{ + u8 p; + + for (p = 0; p < 8; p++) { + snd_mtpav_send_byte(chip, 0xf5); + snd_mtpav_send_byte(chip, p); + snd_mtpav_send_byte(chip, 0xfe); + } +} + +/* + */ + +static int snd_mtpav_input_open(struct snd_rawmidi_substream *substream) +{ + struct mtpav *mtp_card = substream->rmidi->private_data; + struct mtpav_port *portp = &mtp_card->ports[substream->number]; + unsigned long flags; + + spin_lock_irqsave(&mtp_card->spinlock, flags); + portp->mode |= MTPAV_MODE_INPUT_OPENED; + portp->input = substream; + if (mtp_card->share_irq++ == 0) + snd_mtpav_mputreg(mtp_card, CREG, (SIGC_INTEN | SIGC_WRITE)); // enable pport interrupts + spin_unlock_irqrestore(&mtp_card->spinlock, flags); + return 0; +} + +/* + */ + +static int snd_mtpav_input_close(struct snd_rawmidi_substream *substream) +{ + struct mtpav *mtp_card = substream->rmidi->private_data; + struct mtpav_port *portp = &mtp_card->ports[substream->number]; + unsigned long flags; + + spin_lock_irqsave(&mtp_card->spinlock, flags); + portp->mode &= ~MTPAV_MODE_INPUT_OPENED; + portp->input = NULL; + if (--mtp_card->share_irq == 0) + snd_mtpav_mputreg(mtp_card, CREG, 0); // disable pport interrupts + spin_unlock_irqrestore(&mtp_card->spinlock, flags); + return 0; +} + +/* + */ + +static void snd_mtpav_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct mtpav *mtp_card = substream->rmidi->private_data; + struct mtpav_port *portp = &mtp_card->ports[substream->number]; + unsigned long flags; + + spin_lock_irqsave(&mtp_card->spinlock, flags); + if (up) + portp->mode |= MTPAV_MODE_INPUT_TRIGGERED; + else + portp->mode &= ~MTPAV_MODE_INPUT_TRIGGERED; + spin_unlock_irqrestore(&mtp_card->spinlock, flags); + +} + + +/* + * timer interrupt for outputs + */ + +static void snd_mtpav_output_timer(struct timer_list *t) +{ + unsigned long flags; + struct mtpav *chip = from_timer(chip, t, timer); + int p; + + spin_lock_irqsave(&chip->spinlock, flags); + /* reprogram timer */ + mod_timer(&chip->timer, 1 + jiffies); + /* process each port */ + for (p = 0; p <= chip->num_ports * 2 + MTPAV_PIDX_BROADCAST; p++) { + struct mtpav_port *portp = &chip->ports[p]; + if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED) && portp->output) + snd_mtpav_output_port_write(chip, portp, portp->output); + } + spin_unlock_irqrestore(&chip->spinlock, flags); +} + +/* spinlock held! */ +static void snd_mtpav_add_output_timer(struct mtpav *chip) +{ + mod_timer(&chip->timer, 1 + jiffies); +} + +/* spinlock held! */ +static void snd_mtpav_remove_output_timer(struct mtpav *chip) +{ + del_timer(&chip->timer); +} + +/* + */ + +static int snd_mtpav_output_open(struct snd_rawmidi_substream *substream) +{ + struct mtpav *mtp_card = substream->rmidi->private_data; + struct mtpav_port *portp = &mtp_card->ports[substream->number]; + unsigned long flags; + + spin_lock_irqsave(&mtp_card->spinlock, flags); + portp->mode |= MTPAV_MODE_OUTPUT_OPENED; + portp->output = substream; + spin_unlock_irqrestore(&mtp_card->spinlock, flags); + return 0; +}; + +/* + */ + +static int snd_mtpav_output_close(struct snd_rawmidi_substream *substream) +{ + struct mtpav *mtp_card = substream->rmidi->private_data; + struct mtpav_port *portp = &mtp_card->ports[substream->number]; + unsigned long flags; + + spin_lock_irqsave(&mtp_card->spinlock, flags); + portp->mode &= ~MTPAV_MODE_OUTPUT_OPENED; + portp->output = NULL; + spin_unlock_irqrestore(&mtp_card->spinlock, flags); + return 0; +}; + +/* + */ + +static void snd_mtpav_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct mtpav *mtp_card = substream->rmidi->private_data; + struct mtpav_port *portp = &mtp_card->ports[substream->number]; + unsigned long flags; + + spin_lock_irqsave(&mtp_card->spinlock, flags); + if (up) { + if (! (portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) { + if (mtp_card->istimer++ == 0) + snd_mtpav_add_output_timer(mtp_card); + portp->mode |= MTPAV_MODE_OUTPUT_TRIGGERED; + } + } else { + portp->mode &= ~MTPAV_MODE_OUTPUT_TRIGGERED; + if (--mtp_card->istimer == 0) + snd_mtpav_remove_output_timer(mtp_card); + } + spin_unlock_irqrestore(&mtp_card->spinlock, flags); + + if (up) + snd_mtpav_output_write(substream); +} + +/* + * midi interrupt for inputs + */ + +static void snd_mtpav_inmidi_process(struct mtpav *mcrd, u8 inbyte) +{ + struct mtpav_port *portp; + + if ((int)mcrd->inmidiport > mcrd->num_ports * 2 + MTPAV_PIDX_BROADCAST) + return; + + portp = &mcrd->ports[mcrd->inmidiport]; + if (portp->mode & MTPAV_MODE_INPUT_TRIGGERED) + snd_rawmidi_receive(portp->input, &inbyte, 1); +} + +static void snd_mtpav_inmidi_h(struct mtpav *mcrd, u8 inbyte) +{ + if (inbyte >= 0xf8) { + /* real-time midi code */ + snd_mtpav_inmidi_process(mcrd, inbyte); + return; + } + + if (mcrd->inmidistate == 0) { // awaiting command + if (inbyte == 0xf5) // MTP port # + mcrd->inmidistate = 1; + else + snd_mtpav_inmidi_process(mcrd, inbyte); + } else if (mcrd->inmidistate) { + mcrd->inmidiport = translate_hwport_to_subdevice(mcrd, inbyte); + mcrd->inmidistate = 0; + } +} + +static void snd_mtpav_read_bytes(struct mtpav *mcrd) +{ + u8 clrread, setread; + u8 mtp_read_byte; + u8 sr, cbyt; + int i; + + u8 sbyt = snd_mtpav_getreg(mcrd, SREG); + + /* printk(KERN_DEBUG "snd_mtpav_read_bytes() sbyt: 0x%x\n", sbyt); */ + + if (!(sbyt & SIGS_BYTE)) + return; + + cbyt = snd_mtpav_getreg(mcrd, CREG); + clrread = cbyt & (SIGC_READ ^ 0xff); + setread = cbyt | SIGC_READ; + + do { + + mtp_read_byte = 0; + for (i = 0; i < 4; i++) { + snd_mtpav_mputreg(mcrd, CREG, setread); + sr = snd_mtpav_getreg(mcrd, SREG); + snd_mtpav_mputreg(mcrd, CREG, clrread); + + sr &= SIGS_IN0 | SIGS_IN1; + sr >>= 4; + mtp_read_byte |= sr << (i * 2); + } + + snd_mtpav_inmidi_h(mcrd, mtp_read_byte); + + sbyt = snd_mtpav_getreg(mcrd, SREG); + + } while (sbyt & SIGS_BYTE); +} + +static irqreturn_t snd_mtpav_irqh(int irq, void *dev_id) +{ + struct mtpav *mcard = dev_id; + + spin_lock(&mcard->spinlock); + snd_mtpav_read_bytes(mcard); + spin_unlock(&mcard->spinlock); + return IRQ_HANDLED; +} + +/* + * get ISA resources + */ +static int snd_mtpav_get_ISA(struct mtpav *mcard) +{ + if ((mcard->res_port = request_region(port, 3, "MotuMTPAV MIDI")) == NULL) { + snd_printk(KERN_ERR "MTVAP port 0x%lx is busy\n", port); + return -EBUSY; + } + mcard->port = port; + if (request_irq(irq, snd_mtpav_irqh, 0, "MOTU MTPAV", mcard)) { + snd_printk(KERN_ERR "MTVAP IRQ %d busy\n", irq); + return -EBUSY; + } + mcard->irq = irq; + return 0; +} + + +/* + */ + +static const struct snd_rawmidi_ops snd_mtpav_output = { + .open = snd_mtpav_output_open, + .close = snd_mtpav_output_close, + .trigger = snd_mtpav_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_mtpav_input = { + .open = snd_mtpav_input_open, + .close = snd_mtpav_input_close, + .trigger = snd_mtpav_input_trigger, +}; + + +/* + * get RAWMIDI resources + */ + +static void snd_mtpav_set_name(struct mtpav *chip, + struct snd_rawmidi_substream *substream) +{ + if (substream->number >= 0 && substream->number < chip->num_ports) + sprintf(substream->name, "MTP direct %d", (substream->number % chip->num_ports) + 1); + else if (substream->number >= 8 && substream->number < chip->num_ports * 2) + sprintf(substream->name, "MTP remote %d", (substream->number % chip->num_ports) + 1); + else if (substream->number == chip->num_ports * 2) + strcpy(substream->name, "MTP computer"); + else if (substream->number == chip->num_ports * 2 + 1) + strcpy(substream->name, "MTP ADAT"); + else + strcpy(substream->name, "MTP broadcast"); +} + +static int snd_mtpav_get_RAWMIDI(struct mtpav *mcard) +{ + int rval; + struct snd_rawmidi *rawmidi; + struct snd_rawmidi_substream *substream; + struct list_head *list; + + if (hwports < 1) + hwports = 1; + else if (hwports > 8) + hwports = 8; + mcard->num_ports = hwports; + + if ((rval = snd_rawmidi_new(mcard->card, "MotuMIDI", 0, + mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1, + mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1, + &mcard->rmidi)) < 0) + return rval; + rawmidi = mcard->rmidi; + rawmidi->private_data = mcard; + + list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) { + substream = list_entry(list, struct snd_rawmidi_substream, list); + snd_mtpav_set_name(mcard, substream); + substream->ops = &snd_mtpav_input; + } + list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) { + substream = list_entry(list, struct snd_rawmidi_substream, list); + snd_mtpav_set_name(mcard, substream); + substream->ops = &snd_mtpav_output; + mcard->ports[substream->number].hwport = translate_subdevice_to_hwport(mcard, substream->number); + } + rawmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + sprintf(rawmidi->name, "MTP AV MIDI"); + return 0; +} + +/* + */ + +static void snd_mtpav_free(struct snd_card *card) +{ + struct mtpav *crd = card->private_data; + unsigned long flags; + + spin_lock_irqsave(&crd->spinlock, flags); + if (crd->istimer > 0) + snd_mtpav_remove_output_timer(crd); + spin_unlock_irqrestore(&crd->spinlock, flags); + if (crd->irq >= 0) + free_irq(crd->irq, (void *)crd); + release_and_free_resource(crd->res_port); +} + +/* + */ +static int snd_mtpav_probe(struct platform_device *dev) +{ + struct snd_card *card; + int err; + struct mtpav *mtp_card; + + err = snd_card_new(&dev->dev, index, id, THIS_MODULE, + sizeof(*mtp_card), &card); + if (err < 0) + return err; + + mtp_card = card->private_data; + spin_lock_init(&mtp_card->spinlock); + mtp_card->card = card; + mtp_card->irq = -1; + mtp_card->share_irq = 0; + mtp_card->inmidistate = 0; + mtp_card->outmidihwport = 0xffffffff; + timer_setup(&mtp_card->timer, snd_mtpav_output_timer, 0); + + card->private_free = snd_mtpav_free; + + err = snd_mtpav_get_RAWMIDI(mtp_card); + if (err < 0) + goto __error; + + mtp_card->inmidiport = mtp_card->num_ports + MTPAV_PIDX_BROADCAST; + + err = snd_mtpav_get_ISA(mtp_card); + if (err < 0) + goto __error; + + strcpy(card->driver, "MTPAV"); + strcpy(card->shortname, "MTPAV on parallel port"); + snprintf(card->longname, sizeof(card->longname), + "MTPAV on parallel port at 0x%lx", port); + + snd_mtpav_portscan(mtp_card); + + err = snd_card_register(mtp_card->card); + if (err < 0) + goto __error; + + platform_set_drvdata(dev, card); + printk(KERN_INFO "Motu MidiTimePiece on parallel port irq: %d ioport: 0x%lx\n", irq, port); + return 0; + + __error: + snd_card_free(card); + return err; +} + +static int snd_mtpav_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +#define SND_MTPAV_DRIVER "snd_mtpav" + +static struct platform_driver snd_mtpav_driver = { + .probe = snd_mtpav_probe, + .remove = snd_mtpav_remove, + .driver = { + .name = SND_MTPAV_DRIVER, + }, +}; + +static int __init alsa_card_mtpav_init(void) +{ + int err; + + if ((err = platform_driver_register(&snd_mtpav_driver)) < 0) + return err; + + device = platform_device_register_simple(SND_MTPAV_DRIVER, -1, NULL, 0); + if (!IS_ERR(device)) { + if (platform_get_drvdata(device)) + return 0; + platform_device_unregister(device); + err = -ENODEV; + } else + err = PTR_ERR(device); + platform_driver_unregister(&snd_mtpav_driver); + return err; +} + +static void __exit alsa_card_mtpav_exit(void) +{ + platform_device_unregister(device); + platform_driver_unregister(&snd_mtpav_driver); +} + +module_init(alsa_card_mtpav_init) +module_exit(alsa_card_mtpav_exit) diff --git a/sound/drivers/mts64.c b/sound/drivers/mts64.c new file mode 100644 index 000000000..b68e71ca7 --- /dev/null +++ b/sound/drivers/mts64.c @@ -0,0 +1,1071 @@ +/* + * ALSA Driver for Ego Systems Inc. (ESI) Miditerminal 4140 + * Copyright (c) 2006 by Matthias König + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CARD_NAME "Miditerminal 4140" +#define DRIVER_NAME "MTS64" +#define PLATFORM_DRIVER "snd_mts64" + +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 struct platform_device *platform_devices[SNDRV_CARDS]; +static int device_count; + +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_AUTHOR("Matthias Koenig "); +MODULE_DESCRIPTION("ESI Miditerminal 4140"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ESI,Miditerminal 4140}}"); + +/********************************************************************* + * Chip specific + *********************************************************************/ +#define MTS64_NUM_INPUT_PORTS 5 +#define MTS64_NUM_OUTPUT_PORTS 4 +#define MTS64_SMPTE_SUBSTREAM 4 + +struct mts64 { + spinlock_t lock; + struct snd_card *card; + struct snd_rawmidi *rmidi; + struct pardevice *pardev; + int open_count; + int current_midi_output_port; + int current_midi_input_port; + u8 mode[MTS64_NUM_INPUT_PORTS]; + struct snd_rawmidi_substream *midi_input_substream[MTS64_NUM_INPUT_PORTS]; + int smpte_switch; + u8 time[4]; /* [0]=hh, [1]=mm, [2]=ss, [3]=ff */ + u8 fps; +}; + +static int snd_mts64_free(struct mts64 *mts) +{ + kfree(mts); + return 0; +} + +static int snd_mts64_create(struct snd_card *card, + struct pardevice *pardev, + struct mts64 **rchip) +{ + struct mts64 *mts; + + *rchip = NULL; + + mts = kzalloc(sizeof(struct mts64), GFP_KERNEL); + if (mts == NULL) + return -ENOMEM; + + /* Init chip specific data */ + spin_lock_init(&mts->lock); + mts->card = card; + mts->pardev = pardev; + mts->current_midi_output_port = -1; + mts->current_midi_input_port = -1; + + *rchip = mts; + + return 0; +} + +/********************************************************************* + * HW register related constants + *********************************************************************/ + +/* Status Bits */ +#define MTS64_STAT_BSY 0x80 +#define MTS64_STAT_BIT_SET 0x20 /* readout process, bit is set */ +#define MTS64_STAT_PORT 0x10 /* read byte is a port number */ + +/* Control Bits */ +#define MTS64_CTL_READOUT 0x08 /* enable readout */ +#define MTS64_CTL_WRITE_CMD 0x06 +#define MTS64_CTL_WRITE_DATA 0x02 +#define MTS64_CTL_STROBE 0x01 + +/* Command */ +#define MTS64_CMD_RESET 0xfe +#define MTS64_CMD_PROBE 0x8f /* Used in probing procedure */ +#define MTS64_CMD_SMPTE_SET_TIME 0xe8 +#define MTS64_CMD_SMPTE_SET_FPS 0xee +#define MTS64_CMD_SMPTE_STOP 0xef +#define MTS64_CMD_SMPTE_FPS_24 0xe3 +#define MTS64_CMD_SMPTE_FPS_25 0xe2 +#define MTS64_CMD_SMPTE_FPS_2997 0xe4 +#define MTS64_CMD_SMPTE_FPS_30D 0xe1 +#define MTS64_CMD_SMPTE_FPS_30 0xe0 +#define MTS64_CMD_COM_OPEN 0xf8 /* setting the communication mode */ +#define MTS64_CMD_COM_CLOSE1 0xff /* clearing communication mode */ +#define MTS64_CMD_COM_CLOSE2 0xf5 + +/********************************************************************* + * Hardware specific functions + *********************************************************************/ +static void mts64_enable_readout(struct parport *p); +static void mts64_disable_readout(struct parport *p); +static int mts64_device_ready(struct parport *p); +static int mts64_device_init(struct parport *p); +static int mts64_device_open(struct mts64 *mts); +static int mts64_device_close(struct mts64 *mts); +static u8 mts64_map_midi_input(u8 c); +static int mts64_probe(struct parport *p); +static u16 mts64_read(struct parport *p); +static u8 mts64_read_char(struct parport *p); +static void mts64_smpte_start(struct parport *p, + u8 hours, u8 minutes, + u8 seconds, u8 frames, + u8 idx); +static void mts64_smpte_stop(struct parport *p); +static void mts64_write_command(struct parport *p, u8 c); +static void mts64_write_data(struct parport *p, u8 c); +static void mts64_write_midi(struct mts64 *mts, u8 c, int midiport); + + +/* Enables the readout procedure + * + * Before we can read a midi byte from the device, we have to set + * bit 3 of control port. + */ +static void mts64_enable_readout(struct parport *p) +{ + u8 c; + + c = parport_read_control(p); + c |= MTS64_CTL_READOUT; + parport_write_control(p, c); +} + +/* Disables readout + * + * Readout is disabled by clearing bit 3 of control + */ +static void mts64_disable_readout(struct parport *p) +{ + u8 c; + + c = parport_read_control(p); + c &= ~MTS64_CTL_READOUT; + parport_write_control(p, c); +} + +/* waits for device ready + * + * Checks if BUSY (Bit 7 of status) is clear + * 1 device ready + * 0 failure + */ +static int mts64_device_ready(struct parport *p) +{ + int i; + u8 c; + + for (i = 0; i < 0xffff; ++i) { + c = parport_read_status(p); + c &= MTS64_STAT_BSY; + if (c != 0) + return 1; + } + + return 0; +} + +/* Init device (LED blinking startup magic) + * + * Returns: + * 0 init ok + * -EIO failure + */ +static int mts64_device_init(struct parport *p) +{ + int i; + + mts64_write_command(p, MTS64_CMD_RESET); + + for (i = 0; i < 64; ++i) { + msleep(100); + + if (mts64_probe(p) == 0) { + /* success */ + mts64_disable_readout(p); + return 0; + } + } + mts64_disable_readout(p); + + return -EIO; +} + +/* + * Opens the device (set communication mode) + */ +static int mts64_device_open(struct mts64 *mts) +{ + int i; + struct parport *p = mts->pardev->port; + + for (i = 0; i < 5; ++i) + mts64_write_command(p, MTS64_CMD_COM_OPEN); + + return 0; +} + +/* + * Close device (clear communication mode) + */ +static int mts64_device_close(struct mts64 *mts) +{ + int i; + struct parport *p = mts->pardev->port; + + for (i = 0; i < 5; ++i) { + mts64_write_command(p, MTS64_CMD_COM_CLOSE1); + mts64_write_command(p, MTS64_CMD_COM_CLOSE2); + } + + return 0; +} + +/* map hardware port to substream number + * + * When reading a byte from the device, the device tells us + * on what port the byte is. This HW port has to be mapped to + * the midiport (substream number). + * substream 0-3 are Midiports 1-4 + * substream 4 is SMPTE Timecode + * The mapping is done by the table: + * HW | 0 | 1 | 2 | 3 | 4 + * SW | 0 | 1 | 4 | 2 | 3 + */ +static u8 mts64_map_midi_input(u8 c) +{ + static u8 map[] = { 0, 1, 4, 2, 3 }; + + return map[c]; +} + + +/* Probe parport for device + * + * Do we have a Miditerminal 4140 on parport? + * Returns: + * 0 device found + * -ENODEV no device + */ +static int mts64_probe(struct parport *p) +{ + u8 c; + + mts64_smpte_stop(p); + mts64_write_command(p, MTS64_CMD_PROBE); + + msleep(50); + + c = mts64_read(p); + + c &= 0x00ff; + if (c != MTS64_CMD_PROBE) + return -ENODEV; + else + return 0; + +} + +/* Read byte incl. status from device + * + * Returns: + * data in lower 8 bits and status in upper 8 bits + */ +static u16 mts64_read(struct parport *p) +{ + u8 data, status; + + mts64_device_ready(p); + mts64_enable_readout(p); + status = parport_read_status(p); + data = mts64_read_char(p); + mts64_disable_readout(p); + + return (status << 8) | data; +} + +/* Read a byte from device + * + * Note, that readout mode has to be enabled. + * readout procedure is as follows: + * - Write number of the Bit to read to DATA + * - Read STATUS + * - Bit 5 of STATUS indicates if Bit is set + * + * Returns: + * Byte read from device + */ +static u8 mts64_read_char(struct parport *p) +{ + u8 c = 0; + u8 status; + u8 i; + + for (i = 0; i < 8; ++i) { + parport_write_data(p, i); + c >>= 1; + status = parport_read_status(p); + if (status & MTS64_STAT_BIT_SET) + c |= 0x80; + } + + return c; +} + +/* Starts SMPTE Timecode generation + * + * The device creates SMPTE Timecode by hardware. + * 0 24 fps + * 1 25 fps + * 2 29.97 fps + * 3 30 fps (Drop-frame) + * 4 30 fps + */ +static void mts64_smpte_start(struct parport *p, + u8 hours, u8 minutes, + u8 seconds, u8 frames, + u8 idx) +{ + static u8 fps[5] = { MTS64_CMD_SMPTE_FPS_24, + MTS64_CMD_SMPTE_FPS_25, + MTS64_CMD_SMPTE_FPS_2997, + MTS64_CMD_SMPTE_FPS_30D, + MTS64_CMD_SMPTE_FPS_30 }; + + mts64_write_command(p, MTS64_CMD_SMPTE_SET_TIME); + mts64_write_command(p, frames); + mts64_write_command(p, seconds); + mts64_write_command(p, minutes); + mts64_write_command(p, hours); + + mts64_write_command(p, MTS64_CMD_SMPTE_SET_FPS); + mts64_write_command(p, fps[idx]); +} + +/* Stops SMPTE Timecode generation + */ +static void mts64_smpte_stop(struct parport *p) +{ + mts64_write_command(p, MTS64_CMD_SMPTE_STOP); +} + +/* Write a command byte to device + */ +static void mts64_write_command(struct parport *p, u8 c) +{ + mts64_device_ready(p); + + parport_write_data(p, c); + + parport_write_control(p, MTS64_CTL_WRITE_CMD); + parport_write_control(p, MTS64_CTL_WRITE_CMD | MTS64_CTL_STROBE); + parport_write_control(p, MTS64_CTL_WRITE_CMD); +} + +/* Write a data byte to device + */ +static void mts64_write_data(struct parport *p, u8 c) +{ + mts64_device_ready(p); + + parport_write_data(p, c); + + parport_write_control(p, MTS64_CTL_WRITE_DATA); + parport_write_control(p, MTS64_CTL_WRITE_DATA | MTS64_CTL_STROBE); + parport_write_control(p, MTS64_CTL_WRITE_DATA); +} + +/* Write a MIDI byte to midiport + * + * midiport ranges from 0-3 and maps to Ports 1-4 + * assumptions: communication mode is on + */ +static void mts64_write_midi(struct mts64 *mts, u8 c, + int midiport) +{ + struct parport *p = mts->pardev->port; + + /* check current midiport */ + if (mts->current_midi_output_port != midiport) + mts64_write_command(p, midiport); + + /* write midi byte */ + mts64_write_data(p, c); +} + +/********************************************************************* + * Control elements + *********************************************************************/ + +/* SMPTE Switch */ +#define snd_mts64_ctl_smpte_switch_info snd_ctl_boolean_mono_info + +static int snd_mts64_ctl_smpte_switch_get(struct snd_kcontrol* kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + + spin_lock_irq(&mts->lock); + uctl->value.integer.value[0] = mts->smpte_switch; + spin_unlock_irq(&mts->lock); + + return 0; +} + +/* smpte_switch is not accessed from IRQ handler, so we just need + to protect the HW access */ +static int snd_mts64_ctl_smpte_switch_put(struct snd_kcontrol* kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int changed = 0; + int val = !!uctl->value.integer.value[0]; + + spin_lock_irq(&mts->lock); + if (mts->smpte_switch == val) + goto __out; + + changed = 1; + mts->smpte_switch = val; + if (mts->smpte_switch) { + mts64_smpte_start(mts->pardev->port, + mts->time[0], mts->time[1], + mts->time[2], mts->time[3], + mts->fps); + } else { + mts64_smpte_stop(mts->pardev->port); + } +__out: + spin_unlock_irq(&mts->lock); + return changed; +} + +static struct snd_kcontrol_new mts64_ctl_smpte_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Playback Switch", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_mts64_ctl_smpte_switch_info, + .get = snd_mts64_ctl_smpte_switch_get, + .put = snd_mts64_ctl_smpte_switch_put +}; + +/* Time */ +static int snd_mts64_ctl_smpte_time_h_info(struct snd_kcontrol *kctl, + 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 = 23; + return 0; +} + +static int snd_mts64_ctl_smpte_time_f_info(struct snd_kcontrol *kctl, + 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 = 99; + return 0; +} + +static int snd_mts64_ctl_smpte_time_info(struct snd_kcontrol *kctl, + 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 = 59; + return 0; +} + +static int snd_mts64_ctl_smpte_time_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int idx = kctl->private_value; + + spin_lock_irq(&mts->lock); + uctl->value.integer.value[0] = mts->time[idx]; + spin_unlock_irq(&mts->lock); + + return 0; +} + +static int snd_mts64_ctl_smpte_time_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int idx = kctl->private_value; + unsigned int time = uctl->value.integer.value[0] % 60; + int changed = 0; + + spin_lock_irq(&mts->lock); + if (mts->time[idx] != time) { + changed = 1; + mts->time[idx] = time; + } + spin_unlock_irq(&mts->lock); + + return changed; +} + +static struct snd_kcontrol_new mts64_ctl_smpte_time_hours = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Hours", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_mts64_ctl_smpte_time_h_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +static struct snd_kcontrol_new mts64_ctl_smpte_time_minutes = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Minutes", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 1, + .info = snd_mts64_ctl_smpte_time_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +static struct snd_kcontrol_new mts64_ctl_smpte_time_seconds = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Seconds", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 2, + .info = snd_mts64_ctl_smpte_time_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +static struct snd_kcontrol_new mts64_ctl_smpte_time_frames = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Frames", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 3, + .info = snd_mts64_ctl_smpte_time_f_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +/* FPS */ +static int snd_mts64_ctl_smpte_fps_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[5] = { + "24", "25", "29.97", "30D", "30" + }; + + return snd_ctl_enum_info(uinfo, 1, 5, texts); +} + +static int snd_mts64_ctl_smpte_fps_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + + spin_lock_irq(&mts->lock); + uctl->value.enumerated.item[0] = mts->fps; + spin_unlock_irq(&mts->lock); + + return 0; +} + +static int snd_mts64_ctl_smpte_fps_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int changed = 0; + + if (uctl->value.enumerated.item[0] >= 5) + return -EINVAL; + spin_lock_irq(&mts->lock); + if (mts->fps != uctl->value.enumerated.item[0]) { + changed = 1; + mts->fps = uctl->value.enumerated.item[0]; + } + spin_unlock_irq(&mts->lock); + + return changed; +} + +static struct snd_kcontrol_new mts64_ctl_smpte_fps = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Fps", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_mts64_ctl_smpte_fps_info, + .get = snd_mts64_ctl_smpte_fps_get, + .put = snd_mts64_ctl_smpte_fps_put +}; + + +static int snd_mts64_ctl_create(struct snd_card *card, + struct mts64 *mts) +{ + int err, i; + static struct snd_kcontrol_new *control[] = { + &mts64_ctl_smpte_switch, + &mts64_ctl_smpte_time_hours, + &mts64_ctl_smpte_time_minutes, + &mts64_ctl_smpte_time_seconds, + &mts64_ctl_smpte_time_frames, + &mts64_ctl_smpte_fps, + NULL }; + + for (i = 0; control[i]; ++i) { + err = snd_ctl_add(card, snd_ctl_new1(control[i], mts)); + if (err < 0) { + snd_printd("Cannot create control: %s\n", + control[i]->name); + return err; + } + } + + return 0; +} + +/********************************************************************* + * Rawmidi + *********************************************************************/ +#define MTS64_MODE_INPUT_TRIGGERED 0x01 + +static int snd_mts64_rawmidi_open(struct snd_rawmidi_substream *substream) +{ + struct mts64 *mts = substream->rmidi->private_data; + + if (mts->open_count == 0) { + /* We don't need a spinlock here, because this is just called + if the device has not been opened before. + So there aren't any IRQs from the device */ + mts64_device_open(mts); + + msleep(50); + } + ++(mts->open_count); + + return 0; +} + +static int snd_mts64_rawmidi_close(struct snd_rawmidi_substream *substream) +{ + struct mts64 *mts = substream->rmidi->private_data; + unsigned long flags; + + --(mts->open_count); + if (mts->open_count == 0) { + /* We need the spinlock_irqsave here because we can still + have IRQs at this point */ + spin_lock_irqsave(&mts->lock, flags); + mts64_device_close(mts); + spin_unlock_irqrestore(&mts->lock, flags); + + msleep(500); + + } else if (mts->open_count < 0) + mts->open_count = 0; + + return 0; +} + +static void snd_mts64_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct mts64 *mts = substream->rmidi->private_data; + u8 data; + unsigned long flags; + + spin_lock_irqsave(&mts->lock, flags); + while (snd_rawmidi_transmit_peek(substream, &data, 1) == 1) { + mts64_write_midi(mts, data, substream->number+1); + snd_rawmidi_transmit_ack(substream, 1); + } + spin_unlock_irqrestore(&mts->lock, flags); +} + +static void snd_mts64_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct mts64 *mts = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&mts->lock, flags); + if (up) + mts->mode[substream->number] |= MTS64_MODE_INPUT_TRIGGERED; + else + mts->mode[substream->number] &= ~MTS64_MODE_INPUT_TRIGGERED; + + spin_unlock_irqrestore(&mts->lock, flags); +} + +static const struct snd_rawmidi_ops snd_mts64_rawmidi_output_ops = { + .open = snd_mts64_rawmidi_open, + .close = snd_mts64_rawmidi_close, + .trigger = snd_mts64_rawmidi_output_trigger +}; + +static const struct snd_rawmidi_ops snd_mts64_rawmidi_input_ops = { + .open = snd_mts64_rawmidi_open, + .close = snd_mts64_rawmidi_close, + .trigger = snd_mts64_rawmidi_input_trigger +}; + +/* Create and initialize the rawmidi component */ +static int snd_mts64_rawmidi_create(struct snd_card *card) +{ + struct mts64 *mts = card->private_data; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream; + struct list_head *list; + int err; + + err = snd_rawmidi_new(card, CARD_NAME, 0, + MTS64_NUM_OUTPUT_PORTS, + MTS64_NUM_INPUT_PORTS, + &rmidi); + if (err < 0) + return err; + + rmidi->private_data = mts; + strcpy(rmidi->name, CARD_NAME); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + mts->rmidi = rmidi; + + /* register rawmidi ops */ + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_mts64_rawmidi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_mts64_rawmidi_input_ops); + + /* name substreams */ + /* output */ + list_for_each(list, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) { + substream = list_entry(list, struct snd_rawmidi_substream, list); + sprintf(substream->name, + "Miditerminal %d", substream->number+1); + } + /* input */ + list_for_each(list, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) { + substream = list_entry(list, struct snd_rawmidi_substream, list); + mts->midi_input_substream[substream->number] = substream; + switch(substream->number) { + case MTS64_SMPTE_SUBSTREAM: + strcpy(substream->name, "Miditerminal SMPTE"); + break; + default: + sprintf(substream->name, + "Miditerminal %d", substream->number+1); + } + } + + /* controls */ + err = snd_mts64_ctl_create(card, mts); + + return err; +} + +/********************************************************************* + * parport stuff + *********************************************************************/ +static void snd_mts64_interrupt(void *private) +{ + struct mts64 *mts = ((struct snd_card*)private)->private_data; + u16 ret; + u8 status, data; + struct snd_rawmidi_substream *substream; + + spin_lock(&mts->lock); + ret = mts64_read(mts->pardev->port); + data = ret & 0x00ff; + status = ret >> 8; + + if (status & MTS64_STAT_PORT) { + mts->current_midi_input_port = mts64_map_midi_input(data); + } else { + if (mts->current_midi_input_port == -1) + goto __out; + substream = mts->midi_input_substream[mts->current_midi_input_port]; + if (mts->mode[substream->number] & MTS64_MODE_INPUT_TRIGGERED) + snd_rawmidi_receive(substream, &data, 1); + } +__out: + spin_unlock(&mts->lock); +} + +static void snd_mts64_attach(struct parport *p) +{ + struct platform_device *device; + + device = platform_device_alloc(PLATFORM_DRIVER, device_count); + if (!device) + return; + + /* Temporary assignment to forward the parport */ + platform_set_drvdata(device, p); + + if (platform_device_add(device) < 0) { + platform_device_put(device); + return; + } + + /* Since we dont get the return value of probe + * We need to check if device probing succeeded or not */ + if (!platform_get_drvdata(device)) { + platform_device_unregister(device); + return; + } + + /* register device in global table */ + platform_devices[device_count] = device; + device_count++; +} + +static void snd_mts64_detach(struct parport *p) +{ + /* nothing to do here */ +} + +static int snd_mts64_dev_probe(struct pardevice *pardev) +{ + if (strcmp(pardev->name, DRIVER_NAME)) + return -ENODEV; + + return 0; +} + +static struct parport_driver mts64_parport_driver = { + .name = "mts64", + .probe = snd_mts64_dev_probe, + .match_port = snd_mts64_attach, + .detach = snd_mts64_detach, + .devmodel = true, +}; + +/********************************************************************* + * platform stuff + *********************************************************************/ +static void snd_mts64_card_private_free(struct snd_card *card) +{ + struct mts64 *mts = card->private_data; + struct pardevice *pardev = mts->pardev; + + if (pardev) { + parport_release(pardev); + parport_unregister_device(pardev); + } + + snd_mts64_free(mts); +} + +static int snd_mts64_probe(struct platform_device *pdev) +{ + struct pardevice *pardev; + struct parport *p; + int dev = pdev->id; + struct snd_card *card = NULL; + struct mts64 *mts = NULL; + int err; + struct pardev_cb mts64_cb = { + .preempt = NULL, + .wakeup = NULL, + .irq_func = snd_mts64_interrupt, /* ISR */ + .flags = PARPORT_DEV_EXCL, /* flags */ + }; + + p = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) + return -ENOENT; + + err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + snd_printd("Cannot create card\n"); + return err; + } + strcpy(card->driver, DRIVER_NAME); + strcpy(card->shortname, "ESI " CARD_NAME); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, p->base, p->irq); + + mts64_cb.private = card; /* private */ + pardev = parport_register_dev_model(p, /* port */ + DRIVER_NAME, /* name */ + &mts64_cb, /* callbacks */ + pdev->id); /* device number */ + if (!pardev) { + snd_printd("Cannot register pardevice\n"); + err = -EIO; + goto __err; + } + + /* claim parport */ + if (parport_claim(pardev)) { + snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base); + err = -EIO; + goto free_pardev; + } + + if ((err = snd_mts64_create(card, pardev, &mts)) < 0) { + snd_printd("Cannot create main component\n"); + goto release_pardev; + } + card->private_data = mts; + card->private_free = snd_mts64_card_private_free; + + err = mts64_probe(p); + if (err) { + err = -EIO; + goto __err; + } + + if ((err = snd_mts64_rawmidi_create(card)) < 0) { + snd_printd("Creating Rawmidi component failed\n"); + goto __err; + } + + /* init device */ + if ((err = mts64_device_init(p)) < 0) + goto __err; + + platform_set_drvdata(pdev, card); + + /* At this point card will be usable */ + if ((err = snd_card_register(card)) < 0) { + snd_printd("Cannot register card\n"); + goto __err; + } + + snd_printk(KERN_INFO "ESI Miditerminal 4140 on 0x%lx\n", p->base); + return 0; + +release_pardev: + parport_release(pardev); +free_pardev: + parport_unregister_device(pardev); +__err: + snd_card_free(card); + return err; +} + +static int snd_mts64_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + + if (card) + snd_card_free(card); + + return 0; +} + +static struct platform_driver snd_mts64_driver = { + .probe = snd_mts64_probe, + .remove = snd_mts64_remove, + .driver = { + .name = PLATFORM_DRIVER, + } +}; + +/********************************************************************* + * module init stuff + *********************************************************************/ +static void snd_mts64_unregister_all(void) +{ + int i; + + for (i = 0; i < SNDRV_CARDS; ++i) { + if (platform_devices[i]) { + platform_device_unregister(platform_devices[i]); + platform_devices[i] = NULL; + } + } + platform_driver_unregister(&snd_mts64_driver); + parport_unregister_driver(&mts64_parport_driver); +} + +static int __init snd_mts64_module_init(void) +{ + int err; + + if ((err = platform_driver_register(&snd_mts64_driver)) < 0) + return err; + + if (parport_register_driver(&mts64_parport_driver) != 0) { + platform_driver_unregister(&snd_mts64_driver); + return -EIO; + } + + if (device_count == 0) { + snd_mts64_unregister_all(); + return -ENODEV; + } + + return 0; +} + +static void __exit snd_mts64_module_exit(void) +{ + snd_mts64_unregister_all(); +} + +module_init(snd_mts64_module_init); +module_exit(snd_mts64_module_exit); diff --git a/sound/drivers/opl3/Makefile b/sound/drivers/opl3/Makefile new file mode 100644 index 000000000..83bca9f1f --- /dev/null +++ b/sound/drivers/opl3/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-opl3-lib-objs := opl3_lib.o opl3_synth.o +snd-opl3-synth-y := opl3_seq.o opl3_midi.o opl3_drums.o +ifneq ($(CONFIG_SND_SEQUENCER_OSS),) +snd-opl3-synth-y += opl3_oss.o +endif + +obj-$(CONFIG_SND_OPL3_LIB) += snd-opl3-lib.o +obj-$(CONFIG_SND_OPL4_LIB) += snd-opl3-lib.o +obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-opl3-synth.o diff --git a/sound/drivers/opl3/opl3_drums.c b/sound/drivers/opl3/opl3_drums.c new file mode 100644 index 000000000..149298229 --- /dev/null +++ b/sound/drivers/opl3/opl3_drums.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) by Uros Bizjak + * + * OPL2/OPL3/OPL4 FM routines for internal percussion channels + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "opl3_voice.h" + +static char snd_opl3_drum_table[47] = +{ + OPL3_BASSDRUM_ON, OPL3_BASSDRUM_ON, OPL3_HIHAT_ON, /* 35 - 37 */ + OPL3_SNAREDRUM_ON, OPL3_HIHAT_ON, OPL3_SNAREDRUM_ON, /* 38 - 40 */ + OPL3_BASSDRUM_ON, OPL3_HIHAT_ON, OPL3_BASSDRUM_ON, /* 41 - 43 */ + OPL3_HIHAT_ON, OPL3_TOMTOM_ON, OPL3_HIHAT_ON, /* 44 - 46 */ + OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_CYMBAL_ON, /* 47 - 49 */ + + OPL3_TOMTOM_ON, OPL3_CYMBAL_ON, OPL3_CYMBAL_ON, /* 50 - 52 */ + OPL3_CYMBAL_ON, OPL3_CYMBAL_ON, OPL3_CYMBAL_ON, /* 53 - 55 */ + OPL3_HIHAT_ON, OPL3_CYMBAL_ON, OPL3_TOMTOM_ON, /* 56 - 58 */ + OPL3_CYMBAL_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 59 - 61 */ + OPL3_HIHAT_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 62 - 64 */ + + OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 65 - 67 */ + OPL3_TOMTOM_ON, OPL3_HIHAT_ON, OPL3_HIHAT_ON, /* 68 - 70 */ + OPL3_HIHAT_ON, OPL3_HIHAT_ON, OPL3_TOMTOM_ON, /* 71 - 73 */ + OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 74 - 76 */ + OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 77 - 79 */ + OPL3_CYMBAL_ON, OPL3_CYMBAL_ON /* 80 - 81 */ +}; + +struct snd_opl3_drum_voice { + int voice; + int op; + unsigned char am_vib; + unsigned char ksl_level; + unsigned char attack_decay; + unsigned char sustain_release; + unsigned char feedback_connection; + unsigned char wave_select; +}; + +struct snd_opl3_drum_note { + int voice; + unsigned char fnum; + unsigned char octave_f; + unsigned char feedback_connection; +}; + +static struct snd_opl3_drum_voice bass_op0 = {6, 0, 0x00, 0x32, 0xf8, 0x66, 0x30, 0x00}; +static struct snd_opl3_drum_voice bass_op1 = {6, 1, 0x00, 0x03, 0xf6, 0x57, 0x30, 0x00}; +static struct snd_opl3_drum_note bass_note = {6, 0x90, 0x09}; + +static struct snd_opl3_drum_voice hihat = {7, 0, 0x00, 0x03, 0xf0, 0x06, 0x20, 0x00}; + +static struct snd_opl3_drum_voice snare = {7, 1, 0x00, 0x03, 0xf0, 0x07, 0x20, 0x02}; +static struct snd_opl3_drum_note snare_note = {7, 0xf4, 0x0d}; + +static struct snd_opl3_drum_voice tomtom = {8, 0, 0x02, 0x03, 0xf0, 0x06, 0x10, 0x00}; +static struct snd_opl3_drum_note tomtom_note = {8, 0xf4, 0x09}; + +static struct snd_opl3_drum_voice cymbal = {8, 1, 0x04, 0x03, 0xf0, 0x06, 0x10, 0x00}; + +/* + * set drum voice characteristics + */ +static void snd_opl3_drum_voice_set(struct snd_opl3 *opl3, + struct snd_opl3_drum_voice *data) +{ + unsigned char op_offset = snd_opl3_regmap[data->voice][data->op]; + unsigned char voice_offset = data->voice; + unsigned short opl3_reg; + + /* Set OPL3 AM_VIB register */ + opl3_reg = OPL3_LEFT | (OPL3_REG_AM_VIB + op_offset); + opl3->command(opl3, opl3_reg, data->am_vib); + + /* Set OPL3 KSL_LEVEL register */ + opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset); + opl3->command(opl3, opl3_reg, data->ksl_level); + + /* Set OPL3 ATTACK_DECAY register */ + opl3_reg = OPL3_LEFT | (OPL3_REG_ATTACK_DECAY + op_offset); + opl3->command(opl3, opl3_reg, data->attack_decay); + + /* Set OPL3 SUSTAIN_RELEASE register */ + opl3_reg = OPL3_LEFT | (OPL3_REG_SUSTAIN_RELEASE + op_offset); + opl3->command(opl3, opl3_reg, data->sustain_release); + + /* Set OPL3 FEEDBACK_CONNECTION register */ + opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset); + opl3->command(opl3, opl3_reg, data->feedback_connection); + + /* Select waveform */ + opl3_reg = OPL3_LEFT | (OPL3_REG_WAVE_SELECT + op_offset); + opl3->command(opl3, opl3_reg, data->wave_select); +} + +/* + * Set drum voice pitch + */ +static void snd_opl3_drum_note_set(struct snd_opl3 *opl3, + struct snd_opl3_drum_note *data) +{ + unsigned char voice_offset = data->voice; + unsigned short opl3_reg; + + /* Set OPL3 FNUM_LOW register */ + opl3_reg = OPL3_LEFT | (OPL3_REG_FNUM_LOW + voice_offset); + opl3->command(opl3, opl3_reg, data->fnum); + + /* Set OPL3 KEYON_BLOCK register */ + opl3_reg = OPL3_LEFT | (OPL3_REG_KEYON_BLOCK + voice_offset); + opl3->command(opl3, opl3_reg, data->octave_f); +} + +/* + * Set drum voice volume and position + */ +static void snd_opl3_drum_vol_set(struct snd_opl3 *opl3, + struct snd_opl3_drum_voice *data, + int vel, struct snd_midi_channel *chan) +{ + unsigned char op_offset = snd_opl3_regmap[data->voice][data->op]; + unsigned char voice_offset = data->voice; + unsigned char reg_val; + unsigned short opl3_reg; + + /* Set OPL3 KSL_LEVEL register */ + reg_val = data->ksl_level; + snd_opl3_calc_volume(®_val, vel, chan); + opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set OPL3 FEEDBACK_CONNECTION register */ + /* Set output voice connection */ + reg_val = data->feedback_connection | OPL3_STEREO_BITS; + if (chan->gm_pan < 43) + reg_val &= ~OPL3_VOICE_TO_RIGHT; + if (chan->gm_pan > 85) + reg_val &= ~OPL3_VOICE_TO_LEFT; + opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset); + opl3->command(opl3, opl3_reg, reg_val); +} + +/* + * Loads drum voices at init time + */ +void snd_opl3_load_drums(struct snd_opl3 *opl3) +{ + snd_opl3_drum_voice_set(opl3, &bass_op0); + snd_opl3_drum_voice_set(opl3, &bass_op1); + snd_opl3_drum_note_set(opl3, &bass_note); + + snd_opl3_drum_voice_set(opl3, &hihat); + + snd_opl3_drum_voice_set(opl3, &snare); + snd_opl3_drum_note_set(opl3, &snare_note); + + snd_opl3_drum_voice_set(opl3, &tomtom); + snd_opl3_drum_note_set(opl3, &tomtom_note); + + snd_opl3_drum_voice_set(opl3, &cymbal); +} + +/* + * Switch drum voice on or off + */ +void snd_opl3_drum_switch(struct snd_opl3 *opl3, int note, int vel, int on_off, + struct snd_midi_channel *chan) +{ + unsigned char drum_mask; + struct snd_opl3_drum_voice *drum_voice; + + if (!(opl3->drum_reg & OPL3_PERCUSSION_ENABLE)) + return; + + if ((note < 35) || (note > 81)) + return; + drum_mask = snd_opl3_drum_table[note - 35]; + + if (on_off) { + switch (drum_mask) { + case OPL3_BASSDRUM_ON: + drum_voice = &bass_op1; + break; + case OPL3_HIHAT_ON: + drum_voice = &hihat; + break; + case OPL3_SNAREDRUM_ON: + drum_voice = &snare; + break; + case OPL3_TOMTOM_ON: + drum_voice = &tomtom; + break; + case OPL3_CYMBAL_ON: + drum_voice = &cymbal; + break; + default: + drum_voice = &tomtom; + } + + snd_opl3_drum_vol_set(opl3, drum_voice, vel, chan); + opl3->drum_reg |= drum_mask; + } else { + opl3->drum_reg &= ~drum_mask; + } + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, + opl3->drum_reg); +} diff --git a/sound/drivers/opl3/opl3_lib.c b/sound/drivers/opl3/opl3_lib.c new file mode 100644 index 000000000..cf86c36c7 --- /dev/null +++ b/sound/drivers/opl3/opl3_lib.c @@ -0,0 +1,540 @@ +/* + * Copyright (c) by Jaroslav Kysela , + * Hannu Savolainen 1993-1996, + * Rob Hooft + * + * Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips) + * + * Most if code is ported from OSS/Lite. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "opl3_voice.h" + +MODULE_AUTHOR("Jaroslav Kysela , Hannu Savolainen 1993-1996, Rob Hooft"); +MODULE_DESCRIPTION("Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)"); +MODULE_LICENSE("GPL"); + +static void snd_opl2_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val) +{ + unsigned long flags; + unsigned long port; + + /* + * The original 2-OP synth requires a quite long delay + * after writing to a register. + */ + + port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port; + + spin_lock_irqsave(&opl3->reg_lock, flags); + + outb((unsigned char) cmd, port); + udelay(10); + + outb((unsigned char) val, port + 1); + udelay(30); + + spin_unlock_irqrestore(&opl3->reg_lock, flags); +} + +static void snd_opl3_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val) +{ + unsigned long flags; + unsigned long port; + + /* + * The OPL-3 survives with just two INBs + * after writing to a register. + */ + + port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port; + + spin_lock_irqsave(&opl3->reg_lock, flags); + + outb((unsigned char) cmd, port); + inb(opl3->l_port); + inb(opl3->l_port); + + outb((unsigned char) val, port + 1); + inb(opl3->l_port); + inb(opl3->l_port); + + spin_unlock_irqrestore(&opl3->reg_lock, flags); +} + +static int snd_opl3_detect(struct snd_opl3 * opl3) +{ + /* + * This function returns 1 if the FM chip is present at the given I/O port + * The detection algorithm plays with the timer built in the FM chip and + * looks for a change in the status register. + * + * Note! The timers of the FM chip are not connected to AdLib (and compatible) + * boards. + * + * Note2! The chip is initialized if detected. + */ + + unsigned char stat1, stat2, signature; + + /* Reset timers 1 and 2 */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK); + /* Reset the IRQ of the FM chip */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET); + signature = stat1 = inb(opl3->l_port); /* Status register */ + if ((stat1 & 0xe0) != 0x00) { /* Should be 0x00 */ + snd_printd("OPL3: stat1 = 0x%x\n", stat1); + return -ENODEV; + } + /* Set timer1 to 0xff */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 0xff); + /* Unmask and start timer 1 */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER2_MASK | OPL3_TIMER1_START); + /* Now we have to delay at least 80us */ + udelay(200); + /* Read status after timers have expired */ + stat2 = inb(opl3->l_port); + /* Stop the timers */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK); + /* Reset the IRQ of the FM chip */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET); + if ((stat2 & 0xe0) != 0xc0) { /* There is no YM3812 */ + snd_printd("OPL3: stat2 = 0x%x\n", stat2); + return -ENODEV; + } + + /* If the toplevel code knows exactly the type of chip, don't try + to detect it. */ + if (opl3->hardware != OPL3_HW_AUTO) + return 0; + + /* There is a FM chip on this address. Detect the type (OPL2 to OPL4) */ + if (signature == 0x06) { /* OPL2 */ + opl3->hardware = OPL3_HW_OPL2; + } else { + /* + * If we had an OPL4 chip, opl3->hardware would have been set + * by the OPL4 driver; so we can assume OPL3 here. + */ + if (snd_BUG_ON(!opl3->r_port)) + return -ENODEV; + opl3->hardware = OPL3_HW_OPL3; + } + return 0; +} + +/* + * AdLib timers + */ + +/* + * Timer 1 - 80us + */ + +static int snd_opl3_timer1_start(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + unsigned int ticks; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + spin_lock_irqsave(&opl3->timer_lock, flags); + ticks = timer->sticks; + tmp = (opl3->timer_enable | OPL3_TIMER1_START) & ~OPL3_TIMER1_MASK; + opl3->timer_enable = tmp; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 256 - ticks); /* timer 1 count */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* enable timer 1 IRQ */ + spin_unlock_irqrestore(&opl3->timer_lock, flags); + return 0; +} + +static int snd_opl3_timer1_stop(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + spin_lock_irqsave(&opl3->timer_lock, flags); + tmp = (opl3->timer_enable | OPL3_TIMER1_MASK) & ~OPL3_TIMER1_START; + opl3->timer_enable = tmp; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* disable timer #1 */ + spin_unlock_irqrestore(&opl3->timer_lock, flags); + return 0; +} + +/* + * Timer 2 - 320us + */ + +static int snd_opl3_timer2_start(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + unsigned int ticks; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + spin_lock_irqsave(&opl3->timer_lock, flags); + ticks = timer->sticks; + tmp = (opl3->timer_enable | OPL3_TIMER2_START) & ~OPL3_TIMER2_MASK; + opl3->timer_enable = tmp; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER2, 256 - ticks); /* timer 1 count */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* enable timer 1 IRQ */ + spin_unlock_irqrestore(&opl3->timer_lock, flags); + return 0; +} + +static int snd_opl3_timer2_stop(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + spin_lock_irqsave(&opl3->timer_lock, flags); + tmp = (opl3->timer_enable | OPL3_TIMER2_MASK) & ~OPL3_TIMER2_START; + opl3->timer_enable = tmp; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* disable timer #1 */ + spin_unlock_irqrestore(&opl3->timer_lock, flags); + return 0; +} + +/* + + */ + +static struct snd_timer_hardware snd_opl3_timer1 = +{ + .flags = SNDRV_TIMER_HW_STOP, + .resolution = 80000, + .ticks = 256, + .start = snd_opl3_timer1_start, + .stop = snd_opl3_timer1_stop, +}; + +static struct snd_timer_hardware snd_opl3_timer2 = +{ + .flags = SNDRV_TIMER_HW_STOP, + .resolution = 320000, + .ticks = 256, + .start = snd_opl3_timer2_start, + .stop = snd_opl3_timer2_stop, +}; + +static int snd_opl3_timer1_init(struct snd_opl3 * opl3, int timer_no) +{ + 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 = opl3->card->number; + tid.device = timer_no; + tid.subdevice = 0; + if ((err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer)) >= 0) { + strcpy(timer->name, "AdLib timer #1"); + timer->private_data = opl3; + timer->hw = snd_opl3_timer1; + } + opl3->timer1 = timer; + return err; +} + +static int snd_opl3_timer2_init(struct snd_opl3 * opl3, int timer_no) +{ + 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 = opl3->card->number; + tid.device = timer_no; + tid.subdevice = 0; + if ((err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer)) >= 0) { + strcpy(timer->name, "AdLib timer #2"); + timer->private_data = opl3; + timer->hw = snd_opl3_timer2; + } + opl3->timer2 = timer; + return err; +} + +/* + + */ + +void snd_opl3_interrupt(struct snd_hwdep * hw) +{ + unsigned char status; + struct snd_opl3 *opl3; + struct snd_timer *timer; + + if (hw == NULL) + return; + + opl3 = hw->private_data; + status = inb(opl3->l_port); +#if 0 + snd_printk(KERN_DEBUG "AdLib IRQ status = 0x%x\n", status); +#endif + if (!(status & 0x80)) + return; + + if (status & 0x40) { + timer = opl3->timer1; + snd_timer_interrupt(timer, timer->sticks); + } + if (status & 0x20) { + timer = opl3->timer2; + snd_timer_interrupt(timer, timer->sticks); + } +} + +EXPORT_SYMBOL(snd_opl3_interrupt); + +/* + + */ + +static int snd_opl3_free(struct snd_opl3 *opl3) +{ + if (snd_BUG_ON(!opl3)) + return -ENXIO; + if (opl3->private_free) + opl3->private_free(opl3); + snd_opl3_clear_patches(opl3); + release_and_free_resource(opl3->res_l_port); + release_and_free_resource(opl3->res_r_port); + kfree(opl3); + return 0; +} + +static int snd_opl3_dev_free(struct snd_device *device) +{ + struct snd_opl3 *opl3 = device->device_data; + return snd_opl3_free(opl3); +} + +int snd_opl3_new(struct snd_card *card, + unsigned short hardware, + struct snd_opl3 **ropl3) +{ + static struct snd_device_ops ops = { + .dev_free = snd_opl3_dev_free, + }; + struct snd_opl3 *opl3; + int err; + + *ropl3 = NULL; + opl3 = kzalloc(sizeof(*opl3), GFP_KERNEL); + if (!opl3) + return -ENOMEM; + + opl3->card = card; + opl3->hardware = hardware; + spin_lock_init(&opl3->reg_lock); + spin_lock_init(&opl3->timer_lock); + + if ((err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops)) < 0) { + snd_opl3_free(opl3); + return err; + } + + *ropl3 = opl3; + return 0; +} + +EXPORT_SYMBOL(snd_opl3_new); + +int snd_opl3_init(struct snd_opl3 *opl3) +{ + if (! opl3->command) { + printk(KERN_ERR "snd_opl3_init: command not defined!\n"); + return -EINVAL; + } + + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT); + /* Melodic mode */ + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00); + + switch (opl3->hardware & OPL3_HW_MASK) { + case OPL3_HW_OPL2: + opl3->max_voices = MAX_OPL2_VOICES; + break; + case OPL3_HW_OPL3: + case OPL3_HW_OPL4: + opl3->max_voices = MAX_OPL3_VOICES; + /* Enter OPL3 mode */ + opl3->command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_OPL3_ENABLE); + } + return 0; +} + +EXPORT_SYMBOL(snd_opl3_init); + +int snd_opl3_create(struct snd_card *card, + unsigned long l_port, + unsigned long r_port, + unsigned short hardware, + int integrated, + struct snd_opl3 ** ropl3) +{ + struct snd_opl3 *opl3; + int err; + + *ropl3 = NULL; + if ((err = snd_opl3_new(card, hardware, &opl3)) < 0) + return err; + if (! integrated) { + if ((opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)")) == NULL) { + snd_printk(KERN_ERR "opl3: can't grab left port 0x%lx\n", l_port); + snd_device_free(card, opl3); + return -EBUSY; + } + if (r_port != 0 && + (opl3->res_r_port = request_region(r_port, 2, "OPL2/3 (right)")) == NULL) { + snd_printk(KERN_ERR "opl3: can't grab right port 0x%lx\n", r_port); + snd_device_free(card, opl3); + return -EBUSY; + } + } + opl3->l_port = l_port; + opl3->r_port = r_port; + + switch (opl3->hardware) { + /* some hardware doesn't support timers */ + case OPL3_HW_OPL3_SV: + case OPL3_HW_OPL3_CS: + case OPL3_HW_OPL3_FM801: + opl3->command = &snd_opl3_command; + break; + default: + opl3->command = &snd_opl2_command; + if ((err = snd_opl3_detect(opl3)) < 0) { + snd_printd("OPL2/3 chip not detected at 0x%lx/0x%lx\n", + opl3->l_port, opl3->r_port); + snd_device_free(card, opl3); + return err; + } + /* detect routine returns correct hardware type */ + switch (opl3->hardware & OPL3_HW_MASK) { + case OPL3_HW_OPL3: + case OPL3_HW_OPL4: + opl3->command = &snd_opl3_command; + } + } + + snd_opl3_init(opl3); + + *ropl3 = opl3; + return 0; +} + +EXPORT_SYMBOL(snd_opl3_create); + +int snd_opl3_timer_new(struct snd_opl3 * opl3, int timer1_dev, int timer2_dev) +{ + int err; + + if (timer1_dev >= 0) + if ((err = snd_opl3_timer1_init(opl3, timer1_dev)) < 0) + return err; + if (timer2_dev >= 0) { + if ((err = snd_opl3_timer2_init(opl3, timer2_dev)) < 0) { + snd_device_free(opl3->card, opl3->timer1); + opl3->timer1 = NULL; + return err; + } + } + return 0; +} + +EXPORT_SYMBOL(snd_opl3_timer_new); + +int snd_opl3_hwdep_new(struct snd_opl3 * opl3, + int device, int seq_device, + struct snd_hwdep ** rhwdep) +{ + struct snd_hwdep *hw; + struct snd_card *card = opl3->card; + int err; + + if (rhwdep) + *rhwdep = NULL; + + /* create hardware dependent device (direct FM) */ + + if ((err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw)) < 0) { + snd_device_free(card, opl3); + return err; + } + hw->private_data = opl3; + hw->exclusive = 1; +#ifdef CONFIG_SND_OSSEMUL + if (device == 0) + hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM; +#endif + strcpy(hw->name, hw->id); + switch (opl3->hardware & OPL3_HW_MASK) { + case OPL3_HW_OPL2: + strcpy(hw->name, "OPL2 FM"); + hw->iface = SNDRV_HWDEP_IFACE_OPL2; + break; + case OPL3_HW_OPL3: + strcpy(hw->name, "OPL3 FM"); + hw->iface = SNDRV_HWDEP_IFACE_OPL3; + break; + case OPL3_HW_OPL4: + strcpy(hw->name, "OPL4 FM"); + hw->iface = SNDRV_HWDEP_IFACE_OPL4; + break; + } + + /* operators - only ioctl */ + hw->ops.open = snd_opl3_open; + hw->ops.ioctl = snd_opl3_ioctl; + hw->ops.write = snd_opl3_write; + hw->ops.release = snd_opl3_release; + + opl3->hwdep = hw; + opl3->seq_dev_num = seq_device; +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (snd_seq_device_new(card, seq_device, SNDRV_SEQ_DEV_ID_OPL3, + sizeof(struct snd_opl3 *), &opl3->seq_dev) >= 0) { + strcpy(opl3->seq_dev->name, hw->name); + *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(opl3->seq_dev) = opl3; + } +#endif + if (rhwdep) + *rhwdep = hw; + return 0; +} + +EXPORT_SYMBOL(snd_opl3_hwdep_new); diff --git a/sound/drivers/opl3/opl3_midi.c b/sound/drivers/opl3/opl3_midi.c new file mode 100644 index 000000000..4e77b1dca --- /dev/null +++ b/sound/drivers/opl3/opl3_midi.c @@ -0,0 +1,870 @@ +/* + * Copyright (c) by Uros Bizjak + * + * Midi synth routines for OPL2/OPL3/OPL4 FM + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#undef DEBUG_ALLOC +#undef DEBUG_MIDI + +#include "opl3_voice.h" +#include + +static void snd_opl3_note_off_unsafe(void *p, int note, int vel, + struct snd_midi_channel *chan); +/* + * The next table looks magical, but it certainly is not. Its values have + * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception + * for i=0. This log-table converts a linear volume-scaling (0..127) to a + * logarithmic scaling as present in the FM-synthesizer chips. so : Volume + * 64 = 0 db = relative volume 0 and: Volume 32 = -6 db = relative + * volume -8 it was implemented as a table because it is only 128 bytes and + * it saves a lot of log() calculations. (Rob Hooft ) + */ + +static char opl3_volume_table[128] = +{ + -63, -48, -40, -35, -32, -29, -27, -26, + -24, -23, -21, -20, -19, -18, -18, -17, + -16, -15, -15, -14, -13, -13, -12, -12, + -11, -11, -10, -10, -10, -9, -9, -8, + -8, -8, -7, -7, -7, -6, -6, -6, + -5, -5, -5, -5, -4, -4, -4, -4, + -3, -3, -3, -3, -2, -2, -2, -2, + -2, -1, -1, -1, -1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 4, + 4, 4, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8 +}; + +void snd_opl3_calc_volume(unsigned char *volbyte, int vel, + struct snd_midi_channel *chan) +{ + int oldvol, newvol, n; + int volume; + + volume = (vel * chan->gm_volume * chan->gm_expression) / (127*127); + if (volume > 127) + volume = 127; + + oldvol = OPL3_TOTAL_LEVEL_MASK - (*volbyte & OPL3_TOTAL_LEVEL_MASK); + + newvol = opl3_volume_table[volume] + oldvol; + if (newvol > OPL3_TOTAL_LEVEL_MASK) + newvol = OPL3_TOTAL_LEVEL_MASK; + else if (newvol < 0) + newvol = 0; + + n = OPL3_TOTAL_LEVEL_MASK - (newvol & OPL3_TOTAL_LEVEL_MASK); + + *volbyte = (*volbyte & OPL3_KSL_MASK) | (n & OPL3_TOTAL_LEVEL_MASK); +} + +/* + * Converts the note frequency to block and fnum values for the FM chip + */ +static short opl3_note_table[16] = +{ + 305, 323, /* for pitch bending, -2 semitones */ + 343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647, + 686, 726 /* for pitch bending, +2 semitones */ +}; + +static void snd_opl3_calc_pitch(unsigned char *fnum, unsigned char *blocknum, + int note, struct snd_midi_channel *chan) +{ + int block = ((note / 12) & 0x07) - 1; + int idx = (note % 12) + 2; + int freq; + + if (chan->midi_pitchbend) { + int pitchbend = chan->midi_pitchbend; + int segment; + + if (pitchbend < -0x2000) + pitchbend = -0x2000; + if (pitchbend > 0x1FFF) + pitchbend = 0x1FFF; + + segment = pitchbend / 0x1000; + freq = opl3_note_table[idx+segment]; + freq += ((opl3_note_table[idx+segment+1] - freq) * + (pitchbend % 0x1000)) / 0x1000; + } else { + freq = opl3_note_table[idx]; + } + + *fnum = (unsigned char) freq; + *blocknum = ((freq >> 8) & OPL3_FNUM_HIGH_MASK) | + ((block << 2) & OPL3_BLOCKNUM_MASK); +} + + +#ifdef DEBUG_ALLOC +static void debug_alloc(struct snd_opl3 *opl3, char *s, int voice) { + int i; + char *str = "x.24"; + + printk(KERN_DEBUG "time %.5i: %s [%.2i]: ", opl3->use_time, s, voice); + for (i = 0; i < opl3->max_voices; i++) + printk(KERN_CONT "%c", *(str + opl3->voices[i].state + 1)); + printk(KERN_CONT "\n"); +} +#endif + +/* + * Get a FM voice (channel) to play a note on. + */ +static int opl3_get_voice(struct snd_opl3 *opl3, int instr_4op, + struct snd_midi_channel *chan) { + int chan_4op_1; /* first voice for 4op instrument */ + int chan_4op_2; /* second voice for 4op instrument */ + + struct snd_opl3_voice *vp, *vp2; + unsigned int voice_time; + int i; + +#ifdef DEBUG_ALLOC + char *alloc_type[3] = { "FREE ", "CHEAP ", "EXPENSIVE" }; +#endif + + /* This is our "allocation cost" table */ + enum { + FREE = 0, CHEAP, EXPENSIVE, END + }; + + /* Keeps track of what we are finding */ + struct best { + unsigned int time; + int voice; + } best[END]; + struct best *bp; + + for (i = 0; i < END; i++) { + best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */ + best[i].voice = -1; + } + + /* Look through all the channels for the most suitable. */ + for (i = 0; i < opl3->max_voices; i++) { + vp = &opl3->voices[i]; + + if (vp->state == SNDRV_OPL3_ST_NOT_AVAIL) + /* skip unavailable channels, allocated by + drum voices or by bounded 4op voices) */ + continue; + + voice_time = vp->time; + bp = best; + + chan_4op_1 = ((i < 3) || (i > 8 && i < 12)); + chan_4op_2 = ((i > 2 && i < 6) || (i > 11 && i < 15)); + if (instr_4op) { + /* allocate 4op voice */ + /* skip channels unavailable to 4op instrument */ + if (!chan_4op_1) + continue; + + if (vp->state) + /* kill one voice, CHEAP */ + bp++; + /* get state of bounded 2op channel + to be allocated for 4op instrument */ + vp2 = &opl3->voices[i + 3]; + if (vp2->state == SNDRV_OPL3_ST_ON_2OP) { + /* kill two voices, EXPENSIVE */ + bp++; + voice_time = (voice_time > vp->time) ? + voice_time : vp->time; + } + } else { + /* allocate 2op voice */ + if ((chan_4op_1) || (chan_4op_2)) + /* use bounded channels for 2op, CHEAP */ + bp++; + else if (vp->state) + /* kill one voice on 2op channel, CHEAP */ + bp++; + /* raise kill cost to EXPENSIVE for all channels */ + if (vp->state) + bp++; + } + if (voice_time < bp->time) { + bp->time = voice_time; + bp->voice = i; + } + } + + for (i = 0; i < END; i++) { + if (best[i].voice >= 0) { +#ifdef DEBUG_ALLOC + printk(KERN_DEBUG "%s %iop allocation on voice %i\n", + alloc_type[i], instr_4op ? 4 : 2, + best[i].voice); +#endif + return best[i].voice; + } + } + /* not found */ + return -1; +} + +/* ------------------------------ */ + +/* + * System timer interrupt function + */ +void snd_opl3_timer_func(struct timer_list *t) +{ + + struct snd_opl3 *opl3 = from_timer(opl3, t, tlist); + unsigned long flags; + int again = 0; + int i; + + spin_lock_irqsave(&opl3->voice_lock, flags); + for (i = 0; i < opl3->max_voices; i++) { + struct snd_opl3_voice *vp = &opl3->voices[i]; + if (vp->state > 0 && vp->note_off_check) { + if (vp->note_off == jiffies) + snd_opl3_note_off_unsafe(opl3, vp->note, 0, + vp->chan); + else + again++; + } + } + spin_unlock_irqrestore(&opl3->voice_lock, flags); + + spin_lock_irqsave(&opl3->sys_timer_lock, flags); + if (again) + mod_timer(&opl3->tlist, jiffies + 1); /* invoke again */ + else + opl3->sys_timer_status = 0; + spin_unlock_irqrestore(&opl3->sys_timer_lock, flags); +} + +/* + * Start system timer + */ +static void snd_opl3_start_timer(struct snd_opl3 *opl3) +{ + unsigned long flags; + spin_lock_irqsave(&opl3->sys_timer_lock, flags); + if (! opl3->sys_timer_status) { + mod_timer(&opl3->tlist, jiffies + 1); + opl3->sys_timer_status = 1; + } + spin_unlock_irqrestore(&opl3->sys_timer_lock, flags); +} + +/* ------------------------------ */ + + +static int snd_opl3_oss_map[MAX_OPL3_VOICES] = { + 0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, 3, 4 ,5, 12, 13, 14 +}; + +/* + * Start a note. + */ +void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan) +{ + struct snd_opl3 *opl3; + int instr_4op; + + int voice; + struct snd_opl3_voice *vp, *vp2; + unsigned short connect_mask; + unsigned char connection; + unsigned char vol_op[4]; + + int extra_prg = 0; + + unsigned short reg_side; + unsigned char op_offset; + unsigned char voice_offset; + unsigned short opl3_reg; + unsigned char reg_val; + unsigned char prg, bank; + + int key = note; + unsigned char fnum, blocknum; + int i; + + struct fm_patch *patch; + struct fm_instrument *fm; + unsigned long flags; + + opl3 = p; + +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "Note on, ch %i, inst %i, note %i, vel %i\n", + chan->number, chan->midi_program, note, vel); +#endif + + /* in SYNTH mode, application takes care of voices */ + /* in SEQ mode, drum voice numbers are notes on drum channel */ + if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) { + if (chan->drum_channel) { + /* percussion instruments are located in bank 128 */ + bank = 128; + prg = note; + } else { + bank = chan->gm_bank_select; + prg = chan->midi_program; + } + } else { + /* Prepare for OSS mode */ + if (chan->number >= MAX_OPL3_VOICES) + return; + + /* OSS instruments are located in bank 127 */ + bank = 127; + prg = chan->midi_program; + } + + spin_lock_irqsave(&opl3->voice_lock, flags); + + if (use_internal_drums) { + snd_opl3_drum_switch(opl3, note, vel, 1, chan); + spin_unlock_irqrestore(&opl3->voice_lock, flags); + return; + } + + __extra_prg: + patch = snd_opl3_find_patch(opl3, prg, bank, 0); + if (!patch) { + spin_unlock_irqrestore(&opl3->voice_lock, flags); + return; + } + + fm = &patch->inst; + switch (patch->type) { + case FM_PATCH_OPL2: + instr_4op = 0; + break; + case FM_PATCH_OPL3: + if (opl3->hardware >= OPL3_HW_OPL3) { + instr_4op = 1; + break; + } + /* fall through */ + default: + spin_unlock_irqrestore(&opl3->voice_lock, flags); + return; + } +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " --> OPL%i instrument: %s\n", + instr_4op ? 3 : 2, patch->name); +#endif + /* in SYNTH mode, application takes care of voices */ + /* in SEQ mode, allocate voice on free OPL3 channel */ + if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) { + voice = opl3_get_voice(opl3, instr_4op, chan); + } else { + /* remap OSS voice */ + voice = snd_opl3_oss_map[chan->number]; + } + + if (voice < 0) { + spin_unlock_irqrestore(&opl3->voice_lock, flags); + return; + } + + if (voice < MAX_OPL2_VOICES) { + /* Left register block for voices 0 .. 8 */ + reg_side = OPL3_LEFT; + voice_offset = voice; + connect_mask = (OPL3_LEFT_4OP_0 << voice_offset) & 0x07; + } else { + /* Right register block for voices 9 .. 17 */ + reg_side = OPL3_RIGHT; + voice_offset = voice - MAX_OPL2_VOICES; + connect_mask = (OPL3_RIGHT_4OP_0 << voice_offset) & 0x38; + } + + /* kill voice on channel */ + vp = &opl3->voices[voice]; + if (vp->state > 0) { + opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset); + reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT; + opl3->command(opl3, opl3_reg, reg_val); + } + if (instr_4op) { + vp2 = &opl3->voices[voice + 3]; + if (vp2->state > 0) { + opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + + voice_offset + 3); + reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT; + opl3->command(opl3, opl3_reg, reg_val); + } + } + + /* set connection register */ + if (instr_4op) { + if ((opl3->connection_reg ^ connect_mask) & connect_mask) { + opl3->connection_reg |= connect_mask; + /* set connection bit */ + opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT; + opl3->command(opl3, opl3_reg, opl3->connection_reg); + } + } else { + if ((opl3->connection_reg ^ ~connect_mask) & connect_mask) { + opl3->connection_reg &= ~connect_mask; + /* clear connection bit */ + opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT; + opl3->command(opl3, opl3_reg, opl3->connection_reg); + } + } + +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " --> setting OPL3 connection: 0x%x\n", + opl3->connection_reg); +#endif + /* + * calculate volume depending on connection + * between FM operators (see include/opl3.h) + */ + for (i = 0; i < (instr_4op ? 4 : 2); i++) + vol_op[i] = fm->op[i].ksl_level; + + connection = fm->feedback_connection[0] & 0x01; + if (instr_4op) { + connection <<= 1; + connection |= fm->feedback_connection[1] & 0x01; + + snd_opl3_calc_volume(&vol_op[3], vel, chan); + switch (connection) { + case 0x03: + snd_opl3_calc_volume(&vol_op[2], vel, chan); + /* fallthru */ + case 0x02: + snd_opl3_calc_volume(&vol_op[0], vel, chan); + break; + case 0x01: + snd_opl3_calc_volume(&vol_op[1], vel, chan); + } + } else { + snd_opl3_calc_volume(&vol_op[1], vel, chan); + if (connection) + snd_opl3_calc_volume(&vol_op[0], vel, chan); + } + + /* Program the FM voice characteristics */ + for (i = 0; i < (instr_4op ? 4 : 2); i++) { +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " --> programming operator %i\n", i); +#endif + op_offset = snd_opl3_regmap[voice_offset][i]; + + /* Set OPL3 AM_VIB register of requested voice/operator */ + reg_val = fm->op[i].am_vib; + opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set OPL3 KSL_LEVEL register of requested voice/operator */ + reg_val = vol_op[i]; + opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set OPL3 ATTACK_DECAY register of requested voice/operator */ + reg_val = fm->op[i].attack_decay; + opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */ + reg_val = fm->op[i].sustain_release; + opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Select waveform */ + reg_val = fm->op[i].wave_select; + opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + } + + /* Set operator feedback and 2op inter-operator connection */ + reg_val = fm->feedback_connection[0]; + /* Set output voice connection */ + reg_val |= OPL3_STEREO_BITS; + if (chan->gm_pan < 43) + reg_val &= ~OPL3_VOICE_TO_RIGHT; + if (chan->gm_pan > 85) + reg_val &= ~OPL3_VOICE_TO_LEFT; + opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset); + opl3->command(opl3, opl3_reg, reg_val); + + if (instr_4op) { + /* Set 4op inter-operator connection */ + reg_val = fm->feedback_connection[1] & OPL3_CONNECTION_BIT; + /* Set output voice connection */ + reg_val |= OPL3_STEREO_BITS; + if (chan->gm_pan < 43) + reg_val &= ~OPL3_VOICE_TO_RIGHT; + if (chan->gm_pan > 85) + reg_val &= ~OPL3_VOICE_TO_LEFT; + opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + + voice_offset + 3); + opl3->command(opl3, opl3_reg, reg_val); + } + + /* + * Special treatment of percussion notes for fm: + * Requested pitch is really program, and pitch for + * device is whatever was specified in the patch library. + */ + if (fm->fix_key) + note = fm->fix_key; + /* + * use transpose if defined in patch library + */ + if (fm->trnsps) + note += (fm->trnsps - 64); + + snd_opl3_calc_pitch(&fnum, &blocknum, note, chan); + + /* Set OPL3 FNUM_LOW register of requested voice */ + opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset); + opl3->command(opl3, opl3_reg, fnum); + + opl3->voices[voice].keyon_reg = blocknum; + + /* Set output sound flag */ + blocknum |= OPL3_KEYON_BIT; + +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " --> trigger voice %i\n", voice); +#endif + /* Set OPL3 KEYON_BLOCK register of requested voice */ + opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset); + opl3->command(opl3, opl3_reg, blocknum); + + /* kill note after fixed duration (in centiseconds) */ + if (fm->fix_dur) { + opl3->voices[voice].note_off = jiffies + + (fm->fix_dur * HZ) / 100; + snd_opl3_start_timer(opl3); + opl3->voices[voice].note_off_check = 1; + } else + opl3->voices[voice].note_off_check = 0; + + /* get extra pgm, but avoid possible loops */ + extra_prg = (extra_prg) ? 0 : fm->modes; + + /* do the bookkeeping */ + vp->time = opl3->use_time++; + vp->note = key; + vp->chan = chan; + + if (instr_4op) { + vp->state = SNDRV_OPL3_ST_ON_4OP; + + vp2 = &opl3->voices[voice + 3]; + vp2->time = opl3->use_time++; + vp2->note = key; + vp2->chan = chan; + vp2->state = SNDRV_OPL3_ST_NOT_AVAIL; + } else { + if (vp->state == SNDRV_OPL3_ST_ON_4OP) { + /* 4op killed by 2op, release bounded voice */ + vp2 = &opl3->voices[voice + 3]; + vp2->time = opl3->use_time++; + vp2->state = SNDRV_OPL3_ST_OFF; + } + vp->state = SNDRV_OPL3_ST_ON_2OP; + } + +#ifdef DEBUG_ALLOC + debug_alloc(opl3, "note on ", voice); +#endif + + /* allocate extra program if specified in patch library */ + if (extra_prg) { + if (extra_prg > 128) { + bank = 128; + /* percussions start at 35 */ + prg = extra_prg - 128 + 35 - 1; + } else { + bank = 0; + prg = extra_prg - 1; + } +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " *** allocating extra program\n"); +#endif + goto __extra_prg; + } + spin_unlock_irqrestore(&opl3->voice_lock, flags); +} + +static void snd_opl3_kill_voice(struct snd_opl3 *opl3, int voice) +{ + unsigned short reg_side; + unsigned char voice_offset; + unsigned short opl3_reg; + + struct snd_opl3_voice *vp, *vp2; + + if (snd_BUG_ON(voice >= MAX_OPL3_VOICES)) + return; + + vp = &opl3->voices[voice]; + if (voice < MAX_OPL2_VOICES) { + /* Left register block for voices 0 .. 8 */ + reg_side = OPL3_LEFT; + voice_offset = voice; + } else { + /* Right register block for voices 9 .. 17 */ + reg_side = OPL3_RIGHT; + voice_offset = voice - MAX_OPL2_VOICES; + } + + /* kill voice */ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " --> kill voice %i\n", voice); +#endif + opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset); + /* clear Key ON bit */ + opl3->command(opl3, opl3_reg, vp->keyon_reg); + + /* do the bookkeeping */ + vp->time = opl3->use_time++; + + if (vp->state == SNDRV_OPL3_ST_ON_4OP) { + vp2 = &opl3->voices[voice + 3]; + + vp2->time = opl3->use_time++; + vp2->state = SNDRV_OPL3_ST_OFF; + } + vp->state = SNDRV_OPL3_ST_OFF; +#ifdef DEBUG_ALLOC + debug_alloc(opl3, "note off", voice); +#endif + +} + +/* + * Release a note in response to a midi note off. + */ +static void snd_opl3_note_off_unsafe(void *p, int note, int vel, + struct snd_midi_channel *chan) +{ + struct snd_opl3 *opl3; + + int voice; + struct snd_opl3_voice *vp; + + opl3 = p; + +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "Note off, ch %i, inst %i, note %i\n", + chan->number, chan->midi_program, note); +#endif + + if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) { + if (chan->drum_channel && use_internal_drums) { + snd_opl3_drum_switch(opl3, note, vel, 0, chan); + return; + } + /* this loop will hopefully kill all extra voices, because + they are grouped by the same channel and note values */ + for (voice = 0; voice < opl3->max_voices; voice++) { + vp = &opl3->voices[voice]; + if (vp->state > 0 && vp->chan == chan && vp->note == note) { + snd_opl3_kill_voice(opl3, voice); + } + } + } else { + /* remap OSS voices */ + if (chan->number < MAX_OPL3_VOICES) { + voice = snd_opl3_oss_map[chan->number]; + snd_opl3_kill_voice(opl3, voice); + } + } +} + +void snd_opl3_note_off(void *p, int note, int vel, + struct snd_midi_channel *chan) +{ + struct snd_opl3 *opl3 = p; + unsigned long flags; + + spin_lock_irqsave(&opl3->voice_lock, flags); + snd_opl3_note_off_unsafe(p, note, vel, chan); + spin_unlock_irqrestore(&opl3->voice_lock, flags); +} + +/* + * key pressure change + */ +void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "Key pressure, ch#: %i, inst#: %i\n", + chan->number, chan->midi_program); +#endif +} + +/* + * terminate note + */ +void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "Terminate note, ch#: %i, inst#: %i\n", + chan->number, chan->midi_program); +#endif +} + +static void snd_opl3_update_pitch(struct snd_opl3 *opl3, int voice) +{ + unsigned short reg_side; + unsigned char voice_offset; + unsigned short opl3_reg; + + unsigned char fnum, blocknum; + + struct snd_opl3_voice *vp; + + if (snd_BUG_ON(voice >= MAX_OPL3_VOICES)) + return; + + vp = &opl3->voices[voice]; + if (vp->chan == NULL) + return; /* not allocated? */ + + if (voice < MAX_OPL2_VOICES) { + /* Left register block for voices 0 .. 8 */ + reg_side = OPL3_LEFT; + voice_offset = voice; + } else { + /* Right register block for voices 9 .. 17 */ + reg_side = OPL3_RIGHT; + voice_offset = voice - MAX_OPL2_VOICES; + } + + snd_opl3_calc_pitch(&fnum, &blocknum, vp->note, vp->chan); + + /* Set OPL3 FNUM_LOW register of requested voice */ + opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset); + opl3->command(opl3, opl3_reg, fnum); + + vp->keyon_reg = blocknum; + + /* Set output sound flag */ + blocknum |= OPL3_KEYON_BIT; + + /* Set OPL3 KEYON_BLOCK register of requested voice */ + opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset); + opl3->command(opl3, opl3_reg, blocknum); + + vp->time = opl3->use_time++; +} + +/* + * Update voice pitch controller + */ +static void snd_opl3_pitch_ctrl(struct snd_opl3 *opl3, struct snd_midi_channel *chan) +{ + int voice; + struct snd_opl3_voice *vp; + + unsigned long flags; + + spin_lock_irqsave(&opl3->voice_lock, flags); + + if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) { + for (voice = 0; voice < opl3->max_voices; voice++) { + vp = &opl3->voices[voice]; + if (vp->state > 0 && vp->chan == chan) { + snd_opl3_update_pitch(opl3, voice); + } + } + } else { + /* remap OSS voices */ + if (chan->number < MAX_OPL3_VOICES) { + voice = snd_opl3_oss_map[chan->number]; + snd_opl3_update_pitch(opl3, voice); + } + } + spin_unlock_irqrestore(&opl3->voice_lock, flags); +} + +/* + * Deal with a controller type event. This includes all types of + * control events, not just the midi controllers + */ +void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan) +{ + struct snd_opl3 *opl3; + + opl3 = p; +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "Controller, TYPE = %i, ch#: %i, inst#: %i\n", + type, chan->number, chan->midi_program); +#endif + + switch (type) { + case MIDI_CTL_MSB_MODWHEEL: + if (chan->control[MIDI_CTL_MSB_MODWHEEL] > 63) + opl3->drum_reg |= OPL3_VIBRATO_DEPTH; + else + opl3->drum_reg &= ~OPL3_VIBRATO_DEPTH; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, + opl3->drum_reg); + break; + case MIDI_CTL_E2_TREMOLO_DEPTH: + if (chan->control[MIDI_CTL_E2_TREMOLO_DEPTH] > 63) + opl3->drum_reg |= OPL3_TREMOLO_DEPTH; + else + opl3->drum_reg &= ~OPL3_TREMOLO_DEPTH; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, + opl3->drum_reg); + break; + case MIDI_CTL_PITCHBEND: + snd_opl3_pitch_ctrl(opl3, chan); + break; + } +} + +/* + * NRPN events + */ +void snd_opl3_nrpn(void *p, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "NRPN, ch#: %i, inst#: %i\n", + chan->number, chan->midi_program); +#endif +} + +/* + * receive sysex + */ +void snd_opl3_sysex(void *p, unsigned char *buf, int len, + int parsed, struct snd_midi_channel_set *chset) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "SYSEX\n"); +#endif +} diff --git a/sound/drivers/opl3/opl3_oss.c b/sound/drivers/opl3/opl3_oss.c new file mode 100644 index 000000000..869220ced --- /dev/null +++ b/sound/drivers/opl3/opl3_oss.c @@ -0,0 +1,263 @@ +/* + * Interface for OSS sequencer emulation + * + * Copyright (C) 2000 Uros Bizjak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include "opl3_voice.h" + +static int snd_opl3_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure); +static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg); +static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg); +static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, const char __user *buf, int offs, int count); +static int snd_opl3_reset_seq_oss(struct snd_seq_oss_arg *arg); + +/* operators */ + +static struct snd_seq_oss_callback oss_callback = { + .owner = THIS_MODULE, + .open = snd_opl3_open_seq_oss, + .close = snd_opl3_close_seq_oss, + .ioctl = snd_opl3_ioctl_seq_oss, + .load_patch = snd_opl3_load_patch_seq_oss, + .reset = snd_opl3_reset_seq_oss, +}; + +static int snd_opl3_oss_event_input(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct snd_opl3 *opl3 = private_data; + + if (ev->type != SNDRV_SEQ_EVENT_OSS) + snd_midi_process_event(&opl3_ops, ev, opl3->oss_chset); + return 0; +} + +/* ------------------------------ */ + +static void snd_opl3_oss_free_port(void *private_data) +{ + struct snd_opl3 *opl3 = private_data; + + snd_midi_channel_free_set(opl3->oss_chset); +} + +static int snd_opl3_oss_create_port(struct snd_opl3 * opl3) +{ + struct snd_seq_port_callback callbacks; + char name[32]; + int voices, opl_ver; + + voices = (opl3->hardware < OPL3_HW_OPL3) ? + MAX_OPL2_VOICES : MAX_OPL3_VOICES; + opl3->oss_chset = snd_midi_channel_alloc_set(voices); + if (opl3->oss_chset == NULL) + return -ENOMEM; + opl3->oss_chset->private_data = opl3; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.owner = THIS_MODULE; + callbacks.event_input = snd_opl3_oss_event_input; + callbacks.private_free = snd_opl3_oss_free_port; + callbacks.private_data = opl3; + + opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8; + sprintf(name, "OPL%i OSS Port", opl_ver); + + opl3->oss_chset->client = opl3->seq_client; + opl3->oss_chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks, + SNDRV_SEQ_PORT_CAP_WRITE, + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_MIDI_GM | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER, + voices, voices, + name); + if (opl3->oss_chset->port < 0) { + int port; + port = opl3->oss_chset->port; + snd_midi_channel_free_set(opl3->oss_chset); + return port; + } + return 0; +} + +/* ------------------------------ */ + +/* register OSS synth */ +void snd_opl3_init_seq_oss(struct snd_opl3 *opl3, char *name) +{ + struct snd_seq_oss_reg *arg; + struct snd_seq_device *dev; + + if (snd_seq_device_new(opl3->card, 0, SNDRV_SEQ_DEV_ID_OSS, + sizeof(struct snd_seq_oss_reg), &dev) < 0) + return; + + opl3->oss_seq_dev = dev; + strlcpy(dev->name, name, sizeof(dev->name)); + arg = SNDRV_SEQ_DEVICE_ARGPTR(dev); + arg->type = SYNTH_TYPE_FM; + if (opl3->hardware < OPL3_HW_OPL3) { + arg->subtype = FM_TYPE_ADLIB; + arg->nvoices = MAX_OPL2_VOICES; + } else { + arg->subtype = FM_TYPE_OPL3; + arg->nvoices = MAX_OPL3_VOICES; + } + arg->oper = oss_callback; + arg->private_data = opl3; + + if (snd_opl3_oss_create_port(opl3)) { + /* register to OSS synth table */ + snd_device_register(opl3->card, dev); + } +} + +/* unregister */ +void snd_opl3_free_seq_oss(struct snd_opl3 *opl3) +{ + if (opl3->oss_seq_dev) { + /* The instance should have been released in prior */ + opl3->oss_seq_dev = NULL; + } +} + +/* ------------------------------ */ + +/* open OSS sequencer */ +static int snd_opl3_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) +{ + struct snd_opl3 *opl3 = closure; + int err; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + + if ((err = snd_opl3_synth_setup(opl3)) < 0) + return err; + + /* fill the argument data */ + arg->private_data = opl3; + arg->addr.client = opl3->oss_chset->client; + arg->addr.port = opl3->oss_chset->port; + + if ((err = snd_opl3_synth_use_inc(opl3)) < 0) + return err; + + opl3->synth_mode = SNDRV_OPL3_MODE_SYNTH; + return 0; +} + +/* close OSS sequencer */ +static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg) +{ + struct snd_opl3 *opl3; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + opl3 = arg->private_data; + + snd_opl3_synth_cleanup(opl3); + + snd_opl3_synth_use_dec(opl3); + return 0; +} + +/* load patch */ + +/* from sound_config.h */ +#define SBFM_MAXINSTR 256 + +static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, + const char __user *buf, int offs, int count) +{ + struct snd_opl3 *opl3; + struct sbi_instrument sbi; + char name[32]; + int err, type; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + opl3 = arg->private_data; + + if (format == FM_PATCH) + type = FM_PATCH_OPL2; + else if (format == OPL3_PATCH) + type = FM_PATCH_OPL3; + else + return -EINVAL; + + if (count < (int)sizeof(sbi)) { + snd_printk(KERN_ERR "FM Error: Patch record too short\n"); + return -EINVAL; + } + if (copy_from_user(&sbi, buf, sizeof(sbi))) + return -EFAULT; + + if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) { + snd_printk(KERN_ERR "FM Error: Invalid instrument number %d\n", + sbi.channel); + return -EINVAL; + } + + memset(name, 0, sizeof(name)); + sprintf(name, "Chan%d", sbi.channel); + + err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL, + sbi.operators); + if (err < 0) + return err; + + return sizeof(sbi); +} + +/* ioctl */ +static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, + unsigned long ioarg) +{ + if (snd_BUG_ON(!arg)) + return -ENXIO; + switch (cmd) { + case SNDCTL_FM_LOAD_INSTR: + snd_printk(KERN_ERR "OPL3: " + "Obsolete ioctl(SNDCTL_FM_LOAD_INSTR) used. " + "Fix the program.\n"); + return -EINVAL; + + case SNDCTL_SYNTH_MEMAVL: + return 0x7fffffff; + + case SNDCTL_FM_4OP_ENABLE: + // handled automatically by OPL instrument type + return 0; + + default: + return -EINVAL; + } + return 0; +} + +/* reset device */ +static int snd_opl3_reset_seq_oss(struct snd_seq_oss_arg *arg) +{ + if (snd_BUG_ON(!arg)) + return -ENXIO; + + return 0; +} diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c new file mode 100644 index 000000000..5f881c4cd --- /dev/null +++ b/sound/drivers/opl3/opl3_seq.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) by Uros Bizjak + * + * Midi Sequencer interface routines for OPL2/OPL3/OPL4 FM + * + * OPL2/3 FM instrument loader: + * alsa-tools/seq/sbiload/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "opl3_voice.h" +#include +#include +#include +#include + +MODULE_AUTHOR("Uros Bizjak "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth"); + +bool use_internal_drums = 0; +module_param(use_internal_drums, bool, 0444); +MODULE_PARM_DESC(use_internal_drums, "Enable internal OPL2/3 drums."); + +int snd_opl3_synth_use_inc(struct snd_opl3 * opl3) +{ + if (!try_module_get(opl3->card->module)) + return -EFAULT; + return 0; + +} + +void snd_opl3_synth_use_dec(struct snd_opl3 * opl3) +{ + module_put(opl3->card->module); +} + +int snd_opl3_synth_setup(struct snd_opl3 * opl3) +{ + int idx; + struct snd_hwdep *hwdep = opl3->hwdep; + + mutex_lock(&hwdep->open_mutex); + if (hwdep->used) { + mutex_unlock(&hwdep->open_mutex); + return -EBUSY; + } + hwdep->used++; + mutex_unlock(&hwdep->open_mutex); + + snd_opl3_reset(opl3); + + for (idx = 0; idx < MAX_OPL3_VOICES; idx++) { + opl3->voices[idx].state = SNDRV_OPL3_ST_OFF; + opl3->voices[idx].time = 0; + opl3->voices[idx].keyon_reg = 0x00; + } + opl3->use_time = 0; + opl3->connection_reg = 0x00; + if (opl3->hardware >= OPL3_HW_OPL3) { + /* Clear 4-op connections */ + opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, + opl3->connection_reg); + opl3->max_voices = MAX_OPL3_VOICES; + } + return 0; +} + +void snd_opl3_synth_cleanup(struct snd_opl3 * opl3) +{ + unsigned long flags; + struct snd_hwdep *hwdep; + + /* Stop system timer */ + spin_lock_irqsave(&opl3->sys_timer_lock, flags); + if (opl3->sys_timer_status) { + del_timer(&opl3->tlist); + opl3->sys_timer_status = 0; + } + spin_unlock_irqrestore(&opl3->sys_timer_lock, flags); + + snd_opl3_reset(opl3); + hwdep = opl3->hwdep; + mutex_lock(&hwdep->open_mutex); + hwdep->used--; + mutex_unlock(&hwdep->open_mutex); + wake_up(&hwdep->open_wait); +} + +static int snd_opl3_synth_use(void *private_data, struct snd_seq_port_subscribe * info) +{ + struct snd_opl3 *opl3 = private_data; + int err; + + if ((err = snd_opl3_synth_setup(opl3)) < 0) + return err; + + if (use_internal_drums) { + /* Percussion mode */ + opl3->voices[6].state = opl3->voices[7].state = + opl3->voices[8].state = SNDRV_OPL3_ST_NOT_AVAIL; + snd_opl3_load_drums(opl3); + opl3->drum_reg = OPL3_PERCUSSION_ENABLE; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, opl3->drum_reg); + } else { + opl3->drum_reg = 0x00; + } + + if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) { + if ((err = snd_opl3_synth_use_inc(opl3)) < 0) + return err; + } + opl3->synth_mode = SNDRV_OPL3_MODE_SEQ; + return 0; +} + +static int snd_opl3_synth_unuse(void *private_data, struct snd_seq_port_subscribe * info) +{ + struct snd_opl3 *opl3 = private_data; + + snd_opl3_synth_cleanup(opl3); + + if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) + snd_opl3_synth_use_dec(opl3); + return 0; +} + +/* + * MIDI emulation operators + */ +struct snd_midi_op opl3_ops = { + .note_on = snd_opl3_note_on, + .note_off = snd_opl3_note_off, + .key_press = snd_opl3_key_press, + .note_terminate = snd_opl3_terminate_note, + .control = snd_opl3_control, + .nrpn = snd_opl3_nrpn, + .sysex = snd_opl3_sysex, +}; + +static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct, + void *private_data, int atomic, int hop) +{ + struct snd_opl3 *opl3 = private_data; + + snd_midi_process_event(&opl3_ops, ev, opl3->chset); + return 0; +} + +/* ------------------------------ */ + +static void snd_opl3_synth_free_port(void *private_data) +{ + struct snd_opl3 *opl3 = private_data; + + snd_midi_channel_free_set(opl3->chset); +} + +static int snd_opl3_synth_create_port(struct snd_opl3 * opl3) +{ + struct snd_seq_port_callback callbacks; + char name[32]; + int voices, opl_ver; + + voices = (opl3->hardware < OPL3_HW_OPL3) ? + MAX_OPL2_VOICES : MAX_OPL3_VOICES; + opl3->chset = snd_midi_channel_alloc_set(16); + if (opl3->chset == NULL) + return -ENOMEM; + opl3->chset->private_data = opl3; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.owner = THIS_MODULE; + callbacks.use = snd_opl3_synth_use; + callbacks.unuse = snd_opl3_synth_unuse; + callbacks.event_input = snd_opl3_synth_event_input; + callbacks.private_free = snd_opl3_synth_free_port; + callbacks.private_data = opl3; + + opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8; + sprintf(name, "OPL%i FM Port", opl_ver); + + opl3->chset->client = opl3->seq_client; + opl3->chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks, + SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE, + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_MIDI_GM | + SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER, + 16, voices, + name); + if (opl3->chset->port < 0) { + int port; + port = opl3->chset->port; + snd_midi_channel_free_set(opl3->chset); + return port; + } + return 0; +} + +/* ------------------------------ */ + +static int snd_opl3_seq_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_opl3 *opl3; + int client, err; + char name[32]; + int opl_ver; + + opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (opl3 == NULL) + return -EINVAL; + + spin_lock_init(&opl3->voice_lock); + + opl3->seq_client = -1; + + /* allocate new client */ + opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8; + sprintf(name, "OPL%i FM synth", opl_ver); + client = opl3->seq_client = + snd_seq_create_kernel_client(opl3->card, opl3->seq_dev_num, + name); + if (client < 0) + return client; + + if ((err = snd_opl3_synth_create_port(opl3)) < 0) { + snd_seq_delete_kernel_client(client); + opl3->seq_client = -1; + return err; + } + + /* setup system timer */ + timer_setup(&opl3->tlist, snd_opl3_timer_func, 0); + spin_lock_init(&opl3->sys_timer_lock); + opl3->sys_timer_status = 0; + +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + snd_opl3_init_seq_oss(opl3, name); +#endif + return 0; +} + +static int snd_opl3_seq_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_opl3 *opl3; + + opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (opl3 == NULL) + return -EINVAL; + +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + snd_opl3_free_seq_oss(opl3); +#endif + if (opl3->seq_client >= 0) { + snd_seq_delete_kernel_client(opl3->seq_client); + opl3->seq_client = -1; + } + return 0; +} + +static struct snd_seq_driver opl3_seq_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_opl3_seq_probe, + .remove = snd_opl3_seq_remove, + }, + .id = SNDRV_SEQ_DEV_ID_OPL3, + .argsize = sizeof(struct snd_opl3 *), +}; + +module_snd_seq_driver(opl3_seq_driver); diff --git a/sound/drivers/opl3/opl3_synth.c b/sound/drivers/opl3/opl3_synth.c new file mode 100644 index 000000000..1a87f8ea0 --- /dev/null +++ b/sound/drivers/opl3/opl3_synth.c @@ -0,0 +1,622 @@ +/* + * Copyright (c) by Uros Bizjak + * + * Routines for OPL2/OPL3/OPL4 control + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include "opl3_voice.h" + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +#define OPL3_SUPPORT_SYNTH +#endif + +/* + * There is 18 possible 2 OP voices + * (9 in the left and 9 in the right). + * The first OP is the modulator and 2nd is the carrier. + * + * The first three voices in the both sides may be connected + * with another voice to a 4 OP voice. For example voice 0 + * can be connected with voice 3. The operators of voice 3 are + * used as operators 3 and 4 of the new 4 OP voice. + * In this case the 2 OP voice number 0 is the 'first half' and + * voice 3 is the second. + */ + + +/* + * Register offset table for OPL2/3 voices, + * OPL2 / one OPL3 register array side only + */ + +char snd_opl3_regmap[MAX_OPL2_VOICES][4] = +{ +/* OP1 OP2 OP3 OP4 */ +/* ------------------------ */ + { 0x00, 0x03, 0x08, 0x0b }, + { 0x01, 0x04, 0x09, 0x0c }, + { 0x02, 0x05, 0x0a, 0x0d }, + + { 0x08, 0x0b, 0x00, 0x00 }, + { 0x09, 0x0c, 0x00, 0x00 }, + { 0x0a, 0x0d, 0x00, 0x00 }, + + { 0x10, 0x13, 0x00, 0x00 }, /* used by percussive voices */ + { 0x11, 0x14, 0x00, 0x00 }, /* if the percussive mode */ + { 0x12, 0x15, 0x00, 0x00 } /* is selected (only left reg block) */ +}; + +EXPORT_SYMBOL(snd_opl3_regmap); + +/* + * prototypes + */ +static int snd_opl3_play_note(struct snd_opl3 * opl3, struct snd_dm_fm_note * note); +static int snd_opl3_set_voice(struct snd_opl3 * opl3, struct snd_dm_fm_voice * voice); +static int snd_opl3_set_params(struct snd_opl3 * opl3, struct snd_dm_fm_params * params); +static int snd_opl3_set_mode(struct snd_opl3 * opl3, int mode); +static int snd_opl3_set_connection(struct snd_opl3 * opl3, int connection); + +/* ------------------------------ */ + +/* + * open the device exclusively + */ +int snd_opl3_open(struct snd_hwdep * hw, struct file *file) +{ + return 0; +} + +/* + * ioctl for hwdep device: + */ +int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_opl3 *opl3 = hw->private_data; + void __user *argp = (void __user *)arg; + + if (snd_BUG_ON(!opl3)) + return -EINVAL; + + switch (cmd) { + /* get information */ + case SNDRV_DM_FM_IOCTL_INFO: + { + struct snd_dm_fm_info info; + + memset(&info, 0, sizeof(info)); + + info.fm_mode = opl3->fm_mode; + info.rhythm = opl3->rhythm; + if (copy_to_user(argp, &info, sizeof(struct snd_dm_fm_info))) + return -EFAULT; + return 0; + } + + case SNDRV_DM_FM_IOCTL_RESET: +#ifdef CONFIG_SND_OSSEMUL + case SNDRV_DM_FM_OSS_IOCTL_RESET: +#endif + snd_opl3_reset(opl3); + return 0; + + case SNDRV_DM_FM_IOCTL_PLAY_NOTE: +#ifdef CONFIG_SND_OSSEMUL + case SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE: +#endif + { + struct snd_dm_fm_note note; + if (copy_from_user(¬e, argp, sizeof(struct snd_dm_fm_note))) + return -EFAULT; + return snd_opl3_play_note(opl3, ¬e); + } + + case SNDRV_DM_FM_IOCTL_SET_VOICE: +#ifdef CONFIG_SND_OSSEMUL + case SNDRV_DM_FM_OSS_IOCTL_SET_VOICE: +#endif + { + struct snd_dm_fm_voice voice; + if (copy_from_user(&voice, argp, sizeof(struct snd_dm_fm_voice))) + return -EFAULT; + return snd_opl3_set_voice(opl3, &voice); + } + + case SNDRV_DM_FM_IOCTL_SET_PARAMS: +#ifdef CONFIG_SND_OSSEMUL + case SNDRV_DM_FM_OSS_IOCTL_SET_PARAMS: +#endif + { + struct snd_dm_fm_params params; + if (copy_from_user(¶ms, argp, sizeof(struct snd_dm_fm_params))) + return -EFAULT; + return snd_opl3_set_params(opl3, ¶ms); + } + + case SNDRV_DM_FM_IOCTL_SET_MODE: +#ifdef CONFIG_SND_OSSEMUL + case SNDRV_DM_FM_OSS_IOCTL_SET_MODE: +#endif + return snd_opl3_set_mode(opl3, (int) arg); + + case SNDRV_DM_FM_IOCTL_SET_CONNECTION: +#ifdef CONFIG_SND_OSSEMUL + case SNDRV_DM_FM_OSS_IOCTL_SET_OPL: +#endif + return snd_opl3_set_connection(opl3, (int) arg); + +#ifdef OPL3_SUPPORT_SYNTH + case SNDRV_DM_FM_IOCTL_CLEAR_PATCHES: + snd_opl3_clear_patches(opl3); + return 0; +#endif + +#ifdef CONFIG_SND_DEBUG + default: + snd_printk(KERN_WARNING "unknown IOCTL: 0x%x\n", cmd); +#endif + } + return -ENOTTY; +} + +/* + * close the device + */ +int snd_opl3_release(struct snd_hwdep * hw, struct file *file) +{ + struct snd_opl3 *opl3 = hw->private_data; + + snd_opl3_reset(opl3); + return 0; +} + +#ifdef OPL3_SUPPORT_SYNTH +/* + * write the device - load patches + */ +long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count, + loff_t *offset) +{ + struct snd_opl3 *opl3 = hw->private_data; + long result = 0; + int err = 0; + struct sbi_patch inst; + + while (count >= sizeof(inst)) { + unsigned char type; + if (copy_from_user(&inst, buf, sizeof(inst))) + return -EFAULT; + if (!memcmp(inst.key, FM_KEY_SBI, 4) || + !memcmp(inst.key, FM_KEY_2OP, 4)) + type = FM_PATCH_OPL2; + else if (!memcmp(inst.key, FM_KEY_4OP, 4)) + type = FM_PATCH_OPL3; + else /* invalid type */ + break; + err = snd_opl3_load_patch(opl3, inst.prog, inst.bank, type, + inst.name, inst.extension, + inst.data); + if (err < 0) + break; + result += sizeof(inst); + count -= sizeof(inst); + } + return result > 0 ? result : err; +} + + +/* + * Patch management + */ + +/* offsets for SBI params */ +#define AM_VIB 0 +#define KSL_LEVEL 2 +#define ATTACK_DECAY 4 +#define SUSTAIN_RELEASE 6 +#define WAVE_SELECT 8 + +/* offset for SBI instrument */ +#define CONNECTION 10 +#define OFFSET_4OP 11 + +/* + * load a patch, obviously. + * + * loaded on the given program and bank numbers with the given type + * (FM_PATCH_OPLx). + * data is the pointer of SBI record _without_ header (key and name). + * name is the name string of the patch. + * ext is the extension data of 7 bytes long (stored in name of SBI + * data up to offset 25), or NULL to skip. + * return 0 if successful or a negative error code. + */ +int snd_opl3_load_patch(struct snd_opl3 *opl3, + int prog, int bank, int type, + const char *name, + const unsigned char *ext, + const unsigned char *data) +{ + struct fm_patch *patch; + int i; + + patch = snd_opl3_find_patch(opl3, prog, bank, 1); + if (!patch) + return -ENOMEM; + + patch->type = type; + + for (i = 0; i < 2; i++) { + patch->inst.op[i].am_vib = data[AM_VIB + i]; + patch->inst.op[i].ksl_level = data[KSL_LEVEL + i]; + patch->inst.op[i].attack_decay = data[ATTACK_DECAY + i]; + patch->inst.op[i].sustain_release = data[SUSTAIN_RELEASE + i]; + patch->inst.op[i].wave_select = data[WAVE_SELECT + i]; + } + patch->inst.feedback_connection[0] = data[CONNECTION]; + + if (type == FM_PATCH_OPL3) { + for (i = 0; i < 2; i++) { + patch->inst.op[i+2].am_vib = + data[OFFSET_4OP + AM_VIB + i]; + patch->inst.op[i+2].ksl_level = + data[OFFSET_4OP + KSL_LEVEL + i]; + patch->inst.op[i+2].attack_decay = + data[OFFSET_4OP + ATTACK_DECAY + i]; + patch->inst.op[i+2].sustain_release = + data[OFFSET_4OP + SUSTAIN_RELEASE + i]; + patch->inst.op[i+2].wave_select = + data[OFFSET_4OP + WAVE_SELECT + i]; + } + patch->inst.feedback_connection[1] = + data[OFFSET_4OP + CONNECTION]; + } + + if (ext) { + patch->inst.echo_delay = ext[0]; + patch->inst.echo_atten = ext[1]; + patch->inst.chorus_spread = ext[2]; + patch->inst.trnsps = ext[3]; + patch->inst.fix_dur = ext[4]; + patch->inst.modes = ext[5]; + patch->inst.fix_key = ext[6]; + } + + if (name) + strlcpy(patch->name, name, sizeof(patch->name)); + + return 0; +} +EXPORT_SYMBOL(snd_opl3_load_patch); + +/* + * find a patch with the given program and bank numbers, returns its pointer + * if no matching patch is found and create_patch is set, it creates a + * new patch object. + */ +struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank, + int create_patch) +{ + /* pretty dumb hash key */ + unsigned int key = (prog + bank) % OPL3_PATCH_HASH_SIZE; + struct fm_patch *patch; + + for (patch = opl3->patch_table[key]; patch; patch = patch->next) { + if (patch->prog == prog && patch->bank == bank) + return patch; + } + if (!create_patch) + return NULL; + + patch = kzalloc(sizeof(*patch), GFP_KERNEL); + if (!patch) + return NULL; + patch->prog = prog; + patch->bank = bank; + patch->next = opl3->patch_table[key]; + opl3->patch_table[key] = patch; + return patch; +} +EXPORT_SYMBOL(snd_opl3_find_patch); + +/* + * Clear all patches of the given OPL3 instance + */ +void snd_opl3_clear_patches(struct snd_opl3 *opl3) +{ + int i; + for (i = 0; i < OPL3_PATCH_HASH_SIZE; i++) { + struct fm_patch *patch, *next; + for (patch = opl3->patch_table[i]; patch; patch = next) { + next = patch->next; + kfree(patch); + } + } + memset(opl3->patch_table, 0, sizeof(opl3->patch_table)); +} +#endif /* OPL3_SUPPORT_SYNTH */ + +/* ------------------------------ */ + +void snd_opl3_reset(struct snd_opl3 * opl3) +{ + unsigned short opl3_reg; + + unsigned short reg_side; + unsigned char voice_offset; + + int max_voices, i; + + max_voices = (opl3->hardware < OPL3_HW_OPL3) ? + MAX_OPL2_VOICES : MAX_OPL3_VOICES; + + for (i = 0; i < max_voices; i++) { + /* Get register array side and offset of voice */ + if (i < MAX_OPL2_VOICES) { + /* Left register block for voices 0 .. 8 */ + reg_side = OPL3_LEFT; + voice_offset = i; + } else { + /* Right register block for voices 9 .. 17 */ + reg_side = OPL3_RIGHT; + voice_offset = i - MAX_OPL2_VOICES; + } + opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][0]); + opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 1 volume */ + opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][1]); + opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 2 volume */ + + opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset); + opl3->command(opl3, opl3_reg, 0x00); /* Note off */ + } + + opl3->max_voices = MAX_OPL2_VOICES; + opl3->fm_mode = SNDRV_DM_FM_MODE_OPL2; + + opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT); + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00); /* Melodic mode */ + opl3->rhythm = 0; +} + +EXPORT_SYMBOL(snd_opl3_reset); + +static int snd_opl3_play_note(struct snd_opl3 * opl3, struct snd_dm_fm_note * note) +{ + unsigned short reg_side; + unsigned char voice_offset; + + unsigned short opl3_reg; + unsigned char reg_val; + + /* Voices 0 - 8 in OPL2 mode */ + /* Voices 0 - 17 in OPL3 mode */ + if (note->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ? + MAX_OPL3_VOICES : MAX_OPL2_VOICES)) + return -EINVAL; + + /* Get register array side and offset of voice */ + if (note->voice < MAX_OPL2_VOICES) { + /* Left register block for voices 0 .. 8 */ + reg_side = OPL3_LEFT; + voice_offset = note->voice; + } else { + /* Right register block for voices 9 .. 17 */ + reg_side = OPL3_RIGHT; + voice_offset = note->voice - MAX_OPL2_VOICES; + } + + /* Set lower 8 bits of note frequency */ + reg_val = (unsigned char) note->fnum; + opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset); + opl3->command(opl3, opl3_reg, reg_val); + + reg_val = 0x00; + /* Set output sound flag */ + if (note->key_on) + reg_val |= OPL3_KEYON_BIT; + /* Set octave */ + reg_val |= (note->octave << 2) & OPL3_BLOCKNUM_MASK; + /* Set higher 2 bits of note frequency */ + reg_val |= (unsigned char) (note->fnum >> 8) & OPL3_FNUM_HIGH_MASK; + + /* Set OPL3 KEYON_BLOCK register of requested voice */ + opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset); + opl3->command(opl3, opl3_reg, reg_val); + + return 0; +} + + +static int snd_opl3_set_voice(struct snd_opl3 * opl3, struct snd_dm_fm_voice * voice) +{ + unsigned short reg_side; + unsigned char op_offset; + unsigned char voice_offset, voice_op; + + unsigned short opl3_reg; + unsigned char reg_val; + + /* Only operators 1 and 2 */ + if (voice->op > 1) + return -EINVAL; + /* Voices 0 - 8 in OPL2 mode */ + /* Voices 0 - 17 in OPL3 mode */ + if (voice->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ? + MAX_OPL3_VOICES : MAX_OPL2_VOICES)) + return -EINVAL; + + /* Get register array side and offset of voice */ + if (voice->voice < MAX_OPL2_VOICES) { + /* Left register block for voices 0 .. 8 */ + reg_side = OPL3_LEFT; + voice_offset = voice->voice; + } else { + /* Right register block for voices 9 .. 17 */ + reg_side = OPL3_RIGHT; + voice_offset = voice->voice - MAX_OPL2_VOICES; + } + /* Get register offset of operator */ + voice_offset = array_index_nospec(voice_offset, MAX_OPL2_VOICES); + voice_op = array_index_nospec(voice->op, 4); + op_offset = snd_opl3_regmap[voice_offset][voice_op]; + + reg_val = 0x00; + /* Set amplitude modulation (tremolo) effect */ + if (voice->am) + reg_val |= OPL3_TREMOLO_ON; + /* Set vibrato effect */ + if (voice->vibrato) + reg_val |= OPL3_VIBRATO_ON; + /* Set sustaining sound phase */ + if (voice->do_sustain) + reg_val |= OPL3_SUSTAIN_ON; + /* Set keyboard scaling bit */ + if (voice->kbd_scale) + reg_val |= OPL3_KSR; + /* Set harmonic or frequency multiplier */ + reg_val |= voice->harmonic & OPL3_MULTIPLE_MASK; + + /* Set OPL3 AM_VIB register of requested voice/operator */ + opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set decreasing volume of higher notes */ + reg_val = (voice->scale_level << 6) & OPL3_KSL_MASK; + /* Set output volume */ + reg_val |= ~voice->volume & OPL3_TOTAL_LEVEL_MASK; + + /* Set OPL3 KSL_LEVEL register of requested voice/operator */ + opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set attack phase level */ + reg_val = (voice->attack << 4) & OPL3_ATTACK_MASK; + /* Set decay phase level */ + reg_val |= voice->decay & OPL3_DECAY_MASK; + + /* Set OPL3 ATTACK_DECAY register of requested voice/operator */ + opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set sustain phase level */ + reg_val = (voice->sustain << 4) & OPL3_SUSTAIN_MASK; + /* Set release phase level */ + reg_val |= voice->release & OPL3_RELEASE_MASK; + + /* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */ + opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Set inter-operator feedback */ + reg_val = (voice->feedback << 1) & OPL3_FEEDBACK_MASK; + /* Set inter-operator connection */ + if (voice->connection) + reg_val |= OPL3_CONNECTION_BIT; + /* OPL-3 only */ + if (opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) { + if (voice->left) + reg_val |= OPL3_VOICE_TO_LEFT; + if (voice->right) + reg_val |= OPL3_VOICE_TO_RIGHT; + } + /* Feedback/connection bits are applicable to voice */ + opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset); + opl3->command(opl3, opl3_reg, reg_val); + + /* Select waveform */ + reg_val = voice->waveform & OPL3_WAVE_SELECT_MASK; + opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset); + opl3->command(opl3, opl3_reg, reg_val); + + return 0; +} + +static int snd_opl3_set_params(struct snd_opl3 * opl3, struct snd_dm_fm_params * params) +{ + unsigned char reg_val; + + reg_val = 0x00; + /* Set keyboard split method */ + if (params->kbd_split) + reg_val |= OPL3_KEYBOARD_SPLIT; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_KBD_SPLIT, reg_val); + + reg_val = 0x00; + /* Set amplitude modulation (tremolo) depth */ + if (params->am_depth) + reg_val |= OPL3_TREMOLO_DEPTH; + /* Set vibrato depth */ + if (params->vib_depth) + reg_val |= OPL3_VIBRATO_DEPTH; + /* Set percussion mode */ + if (params->rhythm) { + reg_val |= OPL3_PERCUSSION_ENABLE; + opl3->rhythm = 1; + } else { + opl3->rhythm = 0; + } + /* Play percussion instruments */ + if (params->bass) + reg_val |= OPL3_BASSDRUM_ON; + if (params->snare) + reg_val |= OPL3_SNAREDRUM_ON; + if (params->tomtom) + reg_val |= OPL3_TOMTOM_ON; + if (params->cymbal) + reg_val |= OPL3_CYMBAL_ON; + if (params->hihat) + reg_val |= OPL3_HIHAT_ON; + + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, reg_val); + return 0; +} + +static int snd_opl3_set_mode(struct snd_opl3 * opl3, int mode) +{ + if ((mode == SNDRV_DM_FM_MODE_OPL3) && (opl3->hardware < OPL3_HW_OPL3)) + return -EINVAL; + + opl3->fm_mode = mode; + if (opl3->hardware >= OPL3_HW_OPL3) + opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, 0x00); /* Clear 4-op connections */ + + return 0; +} + +static int snd_opl3_set_connection(struct snd_opl3 * opl3, int connection) +{ + unsigned char reg_val; + + /* OPL-3 only */ + if (opl3->fm_mode != SNDRV_DM_FM_MODE_OPL3) + return -EINVAL; + + reg_val = connection & (OPL3_RIGHT_4OP_0 | OPL3_RIGHT_4OP_1 | OPL3_RIGHT_4OP_2 | + OPL3_LEFT_4OP_0 | OPL3_LEFT_4OP_1 | OPL3_LEFT_4OP_2); + /* Set 4-op connections */ + opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, reg_val); + + return 0; +} + diff --git a/sound/drivers/opl3/opl3_voice.h b/sound/drivers/opl3/opl3_voice.h new file mode 100644 index 000000000..4e4ecc217 --- /dev/null +++ b/sound/drivers/opl3/opl3_voice.h @@ -0,0 +1,59 @@ +#ifndef __OPL3_VOICE_H +#define __OPL3_VOICE_H + +/* + * Copyright (c) 2000 Uros Bizjak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +/* Prototypes for opl3_seq.c */ +int snd_opl3_synth_use_inc(struct snd_opl3 * opl3); +void snd_opl3_synth_use_dec(struct snd_opl3 * opl3); +int snd_opl3_synth_setup(struct snd_opl3 * opl3); +void snd_opl3_synth_cleanup(struct snd_opl3 * opl3); + +/* Prototypes for opl3_midi.c */ +void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_opl3_note_off(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan); +void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan); +void snd_opl3_nrpn(void *p, struct snd_midi_channel *chan, struct snd_midi_channel_set *chset); +void snd_opl3_sysex(void *p, unsigned char *buf, int len, int parsed, struct snd_midi_channel_set *chset); + +void snd_opl3_calc_volume(unsigned char *reg, int vel, struct snd_midi_channel *chan); +void snd_opl3_timer_func(struct timer_list *t); + +/* Prototypes for opl3_drums.c */ +void snd_opl3_load_drums(struct snd_opl3 *opl3); +void snd_opl3_drum_switch(struct snd_opl3 *opl3, int note, int vel, int on_off, struct snd_midi_channel *chan); + +/* Prototypes for opl3_oss.c */ +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) +void snd_opl3_init_seq_oss(struct snd_opl3 *opl3, char *name); +void snd_opl3_free_seq_oss(struct snd_opl3 *opl3); +#else +#define snd_opl3_init_seq_oss(opl3, name) /* NOP */ +#define snd_opl3_free_seq_oss(opl3) /* NOP */ +#endif + +extern char snd_opl3_regmap[MAX_OPL2_VOICES][4]; +extern bool use_internal_drums; +extern struct snd_midi_op opl3_ops; + +#endif diff --git a/sound/drivers/opl4/Makefile b/sound/drivers/opl4/Makefile new file mode 100644 index 000000000..6e86a4092 --- /dev/null +++ b/sound/drivers/opl4/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-opl4-lib-objs := opl4_lib.o opl4_mixer.o +snd-opl4-lib-$(CONFIG_SND_PROC_FS) += opl4_proc.o +snd-opl4-synth-objs := opl4_seq.o opl4_synth.o yrw801.o + +obj-$(CONFIG_SND_OPL4_LIB) += snd-opl4-lib.o +obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-opl4-synth.o diff --git a/sound/drivers/opl4/opl4_lib.c b/sound/drivers/opl4/opl4_lib.c new file mode 100644 index 000000000..819d2dce2 --- /dev/null +++ b/sound/drivers/opl4/opl4_lib.c @@ -0,0 +1,265 @@ +/* + * Functions for accessing OPL4 devices + * Copyright (c) 2003 by Clemens Ladisch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "opl4_local.h" +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("OPL4 driver"); +MODULE_LICENSE("GPL"); + +static inline void snd_opl4_wait(struct snd_opl4 *opl4) +{ + int timeout = 10; + while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0) + ; +} + +void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value) +{ + snd_opl4_wait(opl4); + outb(reg, opl4->pcm_port); + + snd_opl4_wait(opl4); + outb(value, opl4->pcm_port + 1); +} + +EXPORT_SYMBOL(snd_opl4_write); + +u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg) +{ + snd_opl4_wait(opl4); + outb(reg, opl4->pcm_port); + + snd_opl4_wait(opl4); + return inb(opl4->pcm_port + 1); +} + +EXPORT_SYMBOL(snd_opl4_read); + +void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size) +{ + unsigned long flags; + u8 memcfg; + + spin_lock_irqsave(&opl4->reg_lock, flags); + + memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); + snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); + + snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); + snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); + snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); + + snd_opl4_wait(opl4); + outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); + snd_opl4_wait(opl4); + insb(opl4->pcm_port + 1, buf, size); + + snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); + + spin_unlock_irqrestore(&opl4->reg_lock, flags); +} + +EXPORT_SYMBOL(snd_opl4_read_memory); + +void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size) +{ + unsigned long flags; + u8 memcfg; + + spin_lock_irqsave(&opl4->reg_lock, flags); + + memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); + snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT); + + snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16); + snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8); + snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset); + + snd_opl4_wait(opl4); + outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port); + snd_opl4_wait(opl4); + outsb(opl4->pcm_port + 1, buf, size); + + snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg); + + spin_unlock_irqrestore(&opl4->reg_lock, flags); +} + +EXPORT_SYMBOL(snd_opl4_write_memory); + +static void snd_opl4_enable_opl4(struct snd_opl4 *opl4) +{ + outb(OPL3_REG_MODE, opl4->fm_port + 2); + inb(opl4->fm_port); + inb(opl4->fm_port); + outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3); + inb(opl4->fm_port); + inb(opl4->fm_port); +} + +static int snd_opl4_detect(struct snd_opl4 *opl4) +{ + u8 id1, id2; + + snd_opl4_enable_opl4(opl4); + + id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION); + snd_printdd("OPL4[02]=%02x\n", id1); + switch (id1 & OPL4_DEVICE_ID_MASK) { + case 0x20: + opl4->hardware = OPL3_HW_OPL4; + break; + case 0x40: + opl4->hardware = OPL3_HW_OPL4_ML; + break; + default: + return -ENODEV; + } + + snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00); + snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff); + id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM); + id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM); + snd_printdd("OPL4 id1=%02x id2=%02x\n", id1, id2); + if (id1 != 0x00 || id2 != 0xff) + return -ENODEV; + + snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f); + snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f); + snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00); + return 0; +} + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +static void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev) +{ + struct snd_opl4 *opl4 = seq_dev->private_data; + opl4->seq_dev = NULL; +} + +static int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device) +{ + opl4->seq_dev_num = seq_device; + if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4, + sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) { + strcpy(opl4->seq_dev->name, "OPL4 Wavetable"); + *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4; + opl4->seq_dev->private_data = opl4; + opl4->seq_dev->private_free = snd_opl4_seq_dev_free; + } + return 0; +} +#endif + +static void snd_opl4_free(struct snd_opl4 *opl4) +{ + snd_opl4_free_proc(opl4); + release_and_free_resource(opl4->res_fm_port); + release_and_free_resource(opl4->res_pcm_port); + kfree(opl4); +} + +static int snd_opl4_dev_free(struct snd_device *device) +{ + struct snd_opl4 *opl4 = device->device_data; + snd_opl4_free(opl4); + return 0; +} + +int snd_opl4_create(struct snd_card *card, + unsigned long fm_port, unsigned long pcm_port, + int seq_device, + struct snd_opl3 **ropl3, struct snd_opl4 **ropl4) +{ + struct snd_opl4 *opl4; + struct snd_opl3 *opl3; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_opl4_dev_free + }; + + if (ropl3) + *ropl3 = NULL; + if (ropl4) + *ropl4 = NULL; + + opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL); + if (!opl4) + return -ENOMEM; + + opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM"); + opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX"); + if (!opl4->res_fm_port || !opl4->res_pcm_port) { + snd_printk(KERN_ERR "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port); + snd_opl4_free(opl4); + return -EBUSY; + } + + opl4->card = card; + opl4->fm_port = fm_port; + opl4->pcm_port = pcm_port; + spin_lock_init(&opl4->reg_lock); + mutex_init(&opl4->access_mutex); + + err = snd_opl4_detect(opl4); + if (err < 0) { + snd_opl4_free(opl4); + snd_printd("OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port); + return err; + } + + err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops); + if (err < 0) { + snd_opl4_free(opl4); + return err; + } + + err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3); + if (err < 0) { + snd_device_free(card, opl4); + return err; + } + + /* opl3 initialization disabled opl4, so reenable */ + snd_opl4_enable_opl4(opl4); + + snd_opl4_create_mixer(opl4); + snd_opl4_create_proc(opl4); + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + opl4->seq_client = -1; + if (opl4->hardware < OPL3_HW_OPL4_ML) + snd_opl4_create_seq_dev(opl4, seq_device); +#endif + + if (ropl3) + *ropl3 = opl3; + if (ropl4) + *ropl4 = opl4; + return 0; +} + +EXPORT_SYMBOL(snd_opl4_create); diff --git a/sound/drivers/opl4/opl4_local.h b/sound/drivers/opl4/opl4_local.h new file mode 100644 index 000000000..a16b4677c --- /dev/null +++ b/sound/drivers/opl4/opl4_local.h @@ -0,0 +1,235 @@ +/* + * Local definitions for the OPL4 driver + * + * Copyright (c) 2003 by Clemens Ladisch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed and/or modified under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __OPL4_LOCAL_H +#define __OPL4_LOCAL_H + +#include + +/* + * Register numbers + */ + +#define OPL4_REG_TEST0 0x00 +#define OPL4_REG_TEST1 0x01 + +#define OPL4_REG_MEMORY_CONFIGURATION 0x02 +#define OPL4_MODE_BIT 0x01 +#define OPL4_MTYPE_BIT 0x02 +#define OPL4_TONE_HEADER_MASK 0x1c +#define OPL4_DEVICE_ID_MASK 0xe0 + +#define OPL4_REG_MEMORY_ADDRESS_HIGH 0x03 +#define OPL4_REG_MEMORY_ADDRESS_MID 0x04 +#define OPL4_REG_MEMORY_ADDRESS_LOW 0x05 +#define OPL4_REG_MEMORY_DATA 0x06 + +/* + * Offsets to the register banks for voices. To get the + * register number just add the voice number to the bank offset. + * + * Wave Table Number low bits (0x08 to 0x1F) + */ +#define OPL4_REG_TONE_NUMBER 0x08 + +/* Wave Table Number high bit, F-Number low bits (0x20 to 0x37) */ +#define OPL4_REG_F_NUMBER 0x20 +#define OPL4_TONE_NUMBER_BIT8 0x01 +#define OPL4_F_NUMBER_LOW_MASK 0xfe + +/* F-Number high bits, Octave, Pseudo-Reverb (0x38 to 0x4F) */ +#define OPL4_REG_OCTAVE 0x38 +#define OPL4_F_NUMBER_HIGH_MASK 0x07 +#define OPL4_BLOCK_MASK 0xf0 +#define OPL4_PSEUDO_REVERB_BIT 0x08 + +/* Total Level, Level Direct (0x50 to 0x67) */ +#define OPL4_REG_LEVEL 0x50 +#define OPL4_TOTAL_LEVEL_MASK 0xfe +#define OPL4_LEVEL_DIRECT_BIT 0x01 + +/* Key On, Damp, LFO RST, CH, Panpot (0x68 to 0x7F) */ +#define OPL4_REG_MISC 0x68 +#define OPL4_KEY_ON_BIT 0x80 +#define OPL4_DAMP_BIT 0x40 +#define OPL4_LFO_RESET_BIT 0x20 +#define OPL4_OUTPUT_CHANNEL_BIT 0x10 +#define OPL4_PAN_POT_MASK 0x0f + +/* LFO, VIB (0x80 to 0x97) */ +#define OPL4_REG_LFO_VIBRATO 0x80 +#define OPL4_LFO_FREQUENCY_MASK 0x38 +#define OPL4_VIBRATO_DEPTH_MASK 0x07 +#define OPL4_CHORUS_SEND_MASK 0xc0 /* ML only */ + +/* Attack / Decay 1 rate (0x98 to 0xAF) */ +#define OPL4_REG_ATTACK_DECAY1 0x98 +#define OPL4_ATTACK_RATE_MASK 0xf0 +#define OPL4_DECAY1_RATE_MASK 0x0f + +/* Decay level / 2 rate (0xB0 to 0xC7) */ +#define OPL4_REG_LEVEL_DECAY2 0xb0 +#define OPL4_DECAY_LEVEL_MASK 0xf0 +#define OPL4_DECAY2_RATE_MASK 0x0f + +/* Release rate / Rate correction (0xC8 to 0xDF) */ +#define OPL4_REG_RELEASE_CORRECTION 0xc8 +#define OPL4_RELEASE_RATE_MASK 0x0f +#define OPL4_RATE_INTERPOLATION_MASK 0xf0 + +/* AM (0xE0 to 0xF7) */ +#define OPL4_REG_TREMOLO 0xe0 +#define OPL4_TREMOLO_DEPTH_MASK 0x07 +#define OPL4_REVERB_SEND_MASK 0xe0 /* ML only */ + +/* Mixer */ +#define OPL4_REG_MIX_CONTROL_FM 0xf8 +#define OPL4_REG_MIX_CONTROL_PCM 0xf9 +#define OPL4_MIX_LEFT_MASK 0x07 +#define OPL4_MIX_RIGHT_MASK 0x38 + +#define OPL4_REG_ATC 0xfa +#define OPL4_ATC_BIT 0x01 /* ???, ML only */ + +/* bits in the OPL3 Status register */ +#define OPL4_STATUS_BUSY 0x01 +#define OPL4_STATUS_LOAD 0x02 + + +#define OPL4_MAX_VOICES 24 + +#define SNDRV_SEQ_DEV_ID_OPL4 "opl4-synth" + + +struct opl4_sound { + u16 tone; + s16 pitch_offset; + u8 key_scaling; + s8 panpot; + u8 vibrato; + u8 tone_attenuate; + u8 volume_factor; + u8 reg_lfo_vibrato; + u8 reg_attack_decay1; + u8 reg_level_decay2; + u8 reg_release_correction; + u8 reg_tremolo; +}; + +struct opl4_region { + u8 key_min, key_max; + struct opl4_sound sound; +}; + +struct opl4_region_ptr { + int count; + const struct opl4_region *regions; +}; + +struct opl4_voice { + struct list_head list; + int number; + struct snd_midi_channel *chan; + int note; + int velocity; + const struct opl4_sound *sound; + u8 level_direct; + u8 reg_f_number; + u8 reg_misc; + u8 reg_lfo_vibrato; +}; + +struct snd_opl4 { + unsigned long fm_port; + unsigned long pcm_port; + struct resource *res_fm_port; + struct resource *res_pcm_port; + unsigned short hardware; + spinlock_t reg_lock; + struct snd_card *card; + +#ifdef CONFIG_SND_PROC_FS + struct snd_info_entry *proc_entry; + int memory_access; +#endif + struct mutex access_mutex; + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + int used; + + int seq_dev_num; + int seq_client; + struct snd_seq_device *seq_dev; + + struct snd_midi_channel_set *chset; + struct opl4_voice voices[OPL4_MAX_VOICES]; + struct list_head off_voices; + struct list_head on_voices; +#endif +}; + +/* opl4_lib.c */ +void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value); +u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg); +void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size); +void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size); + +/* opl4_mixer.c */ +int snd_opl4_create_mixer(struct snd_opl4 *opl4); + +#ifdef CONFIG_SND_PROC_FS +/* opl4_proc.c */ +int snd_opl4_create_proc(struct snd_opl4 *opl4); +void snd_opl4_free_proc(struct snd_opl4 *opl4); +#else +static inline int snd_opl4_create_proc(struct snd_opl4 *opl4) { return 0; } +static inline void snd_opl4_free_proc(struct snd_opl4 *opl4) {} +#endif + +/* opl4_seq.c */ +extern int volume_boost; + +/* opl4_synth.c */ +void snd_opl4_synth_reset(struct snd_opl4 *opl4); +void snd_opl4_synth_shutdown(struct snd_opl4 *opl4); +void snd_opl4_note_on(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_opl4_note_off(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_opl4_terminate_note(void *p, int note, struct snd_midi_channel *chan); +void snd_opl4_control(void *p, int type, struct snd_midi_channel *chan); +void snd_opl4_sysex(void *p, unsigned char *buf, int len, int parsed, struct snd_midi_channel_set *chset); + +/* yrw801.c */ +int snd_yrw801_detect(struct snd_opl4 *opl4); +extern const struct opl4_region_ptr snd_yrw801_regions[]; + +#endif /* __OPL4_LOCAL_H */ diff --git a/sound/drivers/opl4/opl4_mixer.c b/sound/drivers/opl4/opl4_mixer.c new file mode 100644 index 000000000..04079de4c --- /dev/null +++ b/sound/drivers/opl4/opl4_mixer.c @@ -0,0 +1,95 @@ +/* + * OPL4 mixer functions + * Copyright (c) 2003 by Clemens Ladisch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "opl4_local.h" +#include + +static int snd_opl4_ctl_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 = 7; + return 0; +} + +static int snd_opl4_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_opl4 *opl4 = snd_kcontrol_chip(kcontrol); + unsigned long flags; + u8 reg = kcontrol->private_value; + u8 value; + + spin_lock_irqsave(&opl4->reg_lock, flags); + value = snd_opl4_read(opl4, reg); + spin_unlock_irqrestore(&opl4->reg_lock, flags); + ucontrol->value.integer.value[0] = 7 - (value & 7); + ucontrol->value.integer.value[1] = 7 - ((value >> 3) & 7); + return 0; +} + +static int snd_opl4_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_opl4 *opl4 = snd_kcontrol_chip(kcontrol); + unsigned long flags; + u8 reg = kcontrol->private_value; + u8 value, old_value; + + value = (7 - (ucontrol->value.integer.value[0] & 7)) | + ((7 - (ucontrol->value.integer.value[1] & 7)) << 3); + spin_lock_irqsave(&opl4->reg_lock, flags); + old_value = snd_opl4_read(opl4, reg); + snd_opl4_write(opl4, reg, value); + spin_unlock_irqrestore(&opl4->reg_lock, flags); + return value != old_value; +} + +static struct snd_kcontrol_new snd_opl4_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "FM Playback Volume", + .info = snd_opl4_ctl_info, + .get = snd_opl4_ctl_get, + .put = snd_opl4_ctl_put, + .private_value = OPL4_REG_MIX_CONTROL_FM + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Wavetable Playback Volume", + .info = snd_opl4_ctl_info, + .get = snd_opl4_ctl_get, + .put = snd_opl4_ctl_put, + .private_value = OPL4_REG_MIX_CONTROL_PCM + } +}; + +int snd_opl4_create_mixer(struct snd_opl4 *opl4) +{ + struct snd_card *card = opl4->card; + int i, err; + + strcat(card->mixername, ",OPL4"); + + for (i = 0; i < 2; ++i) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_opl4_controls[i], opl4)); + if (err < 0) + return err; + } + return 0; +} diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c new file mode 100644 index 000000000..16b24091d --- /dev/null +++ b/sound/drivers/opl4/opl4_proc.c @@ -0,0 +1,129 @@ +/* + * Functions for the OPL4 proc file + * Copyright (c) 2003 by Clemens Ladisch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "opl4_local.h" +#include +#include +#include + +static int snd_opl4_mem_proc_open(struct snd_info_entry *entry, + unsigned short mode, void **file_private_data) +{ + struct snd_opl4 *opl4 = entry->private_data; + + mutex_lock(&opl4->access_mutex); + if (opl4->memory_access) { + mutex_unlock(&opl4->access_mutex); + return -EBUSY; + } + opl4->memory_access++; + mutex_unlock(&opl4->access_mutex); + return 0; +} + +static int snd_opl4_mem_proc_release(struct snd_info_entry *entry, + unsigned short mode, void *file_private_data) +{ + struct snd_opl4 *opl4 = entry->private_data; + + mutex_lock(&opl4->access_mutex); + opl4->memory_access--; + mutex_unlock(&opl4->access_mutex); + return 0; +} + +static ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *_buf, + size_t count, loff_t pos) +{ + struct snd_opl4 *opl4 = entry->private_data; + char* buf; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + snd_opl4_read_memory(opl4, buf, pos, count); + if (copy_to_user(_buf, buf, count)) { + vfree(buf); + return -EFAULT; + } + vfree(buf); + return count; +} + +static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, + const char __user *_buf, + size_t count, loff_t pos) +{ + struct snd_opl4 *opl4 = entry->private_data; + char *buf; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + if (copy_from_user(buf, _buf, count)) { + vfree(buf); + return -EFAULT; + } + snd_opl4_write_memory(opl4, buf, pos, count); + vfree(buf); + return count; +} + +static struct snd_info_entry_ops snd_opl4_mem_proc_ops = { + .open = snd_opl4_mem_proc_open, + .release = snd_opl4_mem_proc_release, + .read = snd_opl4_mem_proc_read, + .write = snd_opl4_mem_proc_write, +}; + +int snd_opl4_create_proc(struct snd_opl4 *opl4) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(opl4->card, "opl4-mem", opl4->card->proc_root); + if (entry) { + if (opl4->hardware < OPL3_HW_OPL4_ML) { + /* OPL4 can access 4 MB external ROM/SRAM */ + entry->mode |= 0200; + entry->size = 4 * 1024 * 1024; + } else { + /* OPL4-ML has 1 MB internal ROM */ + entry->size = 1 * 1024 * 1024; + } + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->c.ops = &snd_opl4_mem_proc_ops; + entry->module = THIS_MODULE; + entry->private_data = opl4; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + opl4->proc_entry = entry; + return 0; +} + +void snd_opl4_free_proc(struct snd_opl4 *opl4) +{ + snd_info_free_entry(opl4->proc_entry); +} diff --git a/sound/drivers/opl4/opl4_seq.c b/sound/drivers/opl4/opl4_seq.c new file mode 100644 index 000000000..03d6202f4 --- /dev/null +++ b/sound/drivers/opl4/opl4_seq.c @@ -0,0 +1,210 @@ +/* + * OPL4 sequencer functions + * + * Copyright (c) 2003 by Clemens Ladisch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed and/or modified under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opl4_local.h" +#include +#include +#include +#include + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("OPL4 wavetable synth driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +int volume_boost = 8; + +module_param(volume_boost, int, 0644); +MODULE_PARM_DESC(volume_boost, "Additional volume for OPL4 wavetable sounds."); + +static int snd_opl4_seq_use_inc(struct snd_opl4 *opl4) +{ + if (!try_module_get(opl4->card->module)) + return -EFAULT; + return 0; +} + +static void snd_opl4_seq_use_dec(struct snd_opl4 *opl4) +{ + module_put(opl4->card->module); +} + +static int snd_opl4_seq_use(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct snd_opl4 *opl4 = private_data; + int err; + + mutex_lock(&opl4->access_mutex); + + if (opl4->used) { + mutex_unlock(&opl4->access_mutex); + return -EBUSY; + } + opl4->used++; + + if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) { + err = snd_opl4_seq_use_inc(opl4); + if (err < 0) { + mutex_unlock(&opl4->access_mutex); + return err; + } + } + + mutex_unlock(&opl4->access_mutex); + + snd_opl4_synth_reset(opl4); + return 0; +} + +static int snd_opl4_seq_unuse(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct snd_opl4 *opl4 = private_data; + + snd_opl4_synth_shutdown(opl4); + + mutex_lock(&opl4->access_mutex); + opl4->used--; + mutex_unlock(&opl4->access_mutex); + + if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) + snd_opl4_seq_use_dec(opl4); + return 0; +} + +static struct snd_midi_op opl4_ops = { + .note_on = snd_opl4_note_on, + .note_off = snd_opl4_note_off, + .note_terminate = snd_opl4_terminate_note, + .control = snd_opl4_control, + .sysex = snd_opl4_sysex, +}; + +static int snd_opl4_seq_event_input(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct snd_opl4 *opl4 = private_data; + + snd_midi_process_event(&opl4_ops, ev, opl4->chset); + return 0; +} + +static void snd_opl4_seq_free_port(void *private_data) +{ + struct snd_opl4 *opl4 = private_data; + + snd_midi_channel_free_set(opl4->chset); +} + +static int snd_opl4_seq_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_opl4 *opl4; + int client; + struct snd_seq_port_callback pcallbacks; + + opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (!opl4) + return -EINVAL; + + if (snd_yrw801_detect(opl4) < 0) + return -ENODEV; + + opl4->chset = snd_midi_channel_alloc_set(16); + if (!opl4->chset) + return -ENOMEM; + opl4->chset->private_data = opl4; + + /* allocate new client */ + client = snd_seq_create_kernel_client(opl4->card, opl4->seq_dev_num, + "OPL4 Wavetable"); + if (client < 0) { + snd_midi_channel_free_set(opl4->chset); + return client; + } + opl4->seq_client = client; + opl4->chset->client = client; + + /* create new port */ + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.use = snd_opl4_seq_use; + pcallbacks.unuse = snd_opl4_seq_unuse; + pcallbacks.event_input = snd_opl4_seq_event_input; + pcallbacks.private_free = snd_opl4_seq_free_port; + pcallbacks.private_data = opl4; + + opl4->chset->port = snd_seq_event_port_attach(client, &pcallbacks, + SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE, + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_MIDI_GM | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER, + 16, 24, + "OPL4 Wavetable Port"); + if (opl4->chset->port < 0) { + int err = opl4->chset->port; + snd_midi_channel_free_set(opl4->chset); + snd_seq_delete_kernel_client(client); + opl4->seq_client = -1; + return err; + } + return 0; +} + +static int snd_opl4_seq_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_opl4 *opl4; + + opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (!opl4) + return -EINVAL; + + if (opl4->seq_client >= 0) { + snd_seq_delete_kernel_client(opl4->seq_client); + opl4->seq_client = -1; + } + return 0; +} + +static struct snd_seq_driver opl4_seq_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_opl4_seq_probe, + .remove = snd_opl4_seq_remove, + }, + .id = SNDRV_SEQ_DEV_ID_OPL4, + .argsize = sizeof(struct snd_opl4 *), +}; + +module_snd_seq_driver(opl4_seq_driver); diff --git a/sound/drivers/opl4/opl4_synth.c b/sound/drivers/opl4/opl4_synth.c new file mode 100644 index 000000000..7bc1e58c9 --- /dev/null +++ b/sound/drivers/opl4/opl4_synth.c @@ -0,0 +1,631 @@ +/* + * OPL4 MIDI synthesizer functions + * + * Copyright (c) 2003 by Clemens Ladisch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed and/or modified under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opl4_local.h" +#include +#include +#include + +/* GM2 controllers */ +#ifndef MIDI_CTL_RELEASE_TIME +#define MIDI_CTL_RELEASE_TIME 0x48 +#define MIDI_CTL_ATTACK_TIME 0x49 +#define MIDI_CTL_DECAY_TIME 0x4b +#define MIDI_CTL_VIBRATO_RATE 0x4c +#define MIDI_CTL_VIBRATO_DEPTH 0x4d +#define MIDI_CTL_VIBRATO_DELAY 0x4e +#endif + +/* + * This table maps 100/128 cents to F_NUMBER. + */ +static const s16 snd_opl4_pitch_map[0x600] = { + 0x000,0x000,0x001,0x001,0x002,0x002,0x003,0x003, + 0x004,0x004,0x005,0x005,0x006,0x006,0x006,0x007, + 0x007,0x008,0x008,0x009,0x009,0x00a,0x00a,0x00b, + 0x00b,0x00c,0x00c,0x00d,0x00d,0x00d,0x00e,0x00e, + 0x00f,0x00f,0x010,0x010,0x011,0x011,0x012,0x012, + 0x013,0x013,0x014,0x014,0x015,0x015,0x015,0x016, + 0x016,0x017,0x017,0x018,0x018,0x019,0x019,0x01a, + 0x01a,0x01b,0x01b,0x01c,0x01c,0x01d,0x01d,0x01e, + 0x01e,0x01e,0x01f,0x01f,0x020,0x020,0x021,0x021, + 0x022,0x022,0x023,0x023,0x024,0x024,0x025,0x025, + 0x026,0x026,0x027,0x027,0x028,0x028,0x029,0x029, + 0x029,0x02a,0x02a,0x02b,0x02b,0x02c,0x02c,0x02d, + 0x02d,0x02e,0x02e,0x02f,0x02f,0x030,0x030,0x031, + 0x031,0x032,0x032,0x033,0x033,0x034,0x034,0x035, + 0x035,0x036,0x036,0x037,0x037,0x038,0x038,0x038, + 0x039,0x039,0x03a,0x03a,0x03b,0x03b,0x03c,0x03c, + 0x03d,0x03d,0x03e,0x03e,0x03f,0x03f,0x040,0x040, + 0x041,0x041,0x042,0x042,0x043,0x043,0x044,0x044, + 0x045,0x045,0x046,0x046,0x047,0x047,0x048,0x048, + 0x049,0x049,0x04a,0x04a,0x04b,0x04b,0x04c,0x04c, + 0x04d,0x04d,0x04e,0x04e,0x04f,0x04f,0x050,0x050, + 0x051,0x051,0x052,0x052,0x053,0x053,0x054,0x054, + 0x055,0x055,0x056,0x056,0x057,0x057,0x058,0x058, + 0x059,0x059,0x05a,0x05a,0x05b,0x05b,0x05c,0x05c, + 0x05d,0x05d,0x05e,0x05e,0x05f,0x05f,0x060,0x060, + 0x061,0x061,0x062,0x062,0x063,0x063,0x064,0x064, + 0x065,0x065,0x066,0x066,0x067,0x067,0x068,0x068, + 0x069,0x069,0x06a,0x06a,0x06b,0x06b,0x06c,0x06c, + 0x06d,0x06d,0x06e,0x06e,0x06f,0x06f,0x070,0x071, + 0x071,0x072,0x072,0x073,0x073,0x074,0x074,0x075, + 0x075,0x076,0x076,0x077,0x077,0x078,0x078,0x079, + 0x079,0x07a,0x07a,0x07b,0x07b,0x07c,0x07c,0x07d, + 0x07d,0x07e,0x07e,0x07f,0x07f,0x080,0x081,0x081, + 0x082,0x082,0x083,0x083,0x084,0x084,0x085,0x085, + 0x086,0x086,0x087,0x087,0x088,0x088,0x089,0x089, + 0x08a,0x08a,0x08b,0x08b,0x08c,0x08d,0x08d,0x08e, + 0x08e,0x08f,0x08f,0x090,0x090,0x091,0x091,0x092, + 0x092,0x093,0x093,0x094,0x094,0x095,0x096,0x096, + 0x097,0x097,0x098,0x098,0x099,0x099,0x09a,0x09a, + 0x09b,0x09b,0x09c,0x09c,0x09d,0x09d,0x09e,0x09f, + 0x09f,0x0a0,0x0a0,0x0a1,0x0a1,0x0a2,0x0a2,0x0a3, + 0x0a3,0x0a4,0x0a4,0x0a5,0x0a6,0x0a6,0x0a7,0x0a7, + 0x0a8,0x0a8,0x0a9,0x0a9,0x0aa,0x0aa,0x0ab,0x0ab, + 0x0ac,0x0ad,0x0ad,0x0ae,0x0ae,0x0af,0x0af,0x0b0, + 0x0b0,0x0b1,0x0b1,0x0b2,0x0b2,0x0b3,0x0b4,0x0b4, + 0x0b5,0x0b5,0x0b6,0x0b6,0x0b7,0x0b7,0x0b8,0x0b8, + 0x0b9,0x0ba,0x0ba,0x0bb,0x0bb,0x0bc,0x0bc,0x0bd, + 0x0bd,0x0be,0x0be,0x0bf,0x0c0,0x0c0,0x0c1,0x0c1, + 0x0c2,0x0c2,0x0c3,0x0c3,0x0c4,0x0c4,0x0c5,0x0c6, + 0x0c6,0x0c7,0x0c7,0x0c8,0x0c8,0x0c9,0x0c9,0x0ca, + 0x0cb,0x0cb,0x0cc,0x0cc,0x0cd,0x0cd,0x0ce,0x0ce, + 0x0cf,0x0d0,0x0d0,0x0d1,0x0d1,0x0d2,0x0d2,0x0d3, + 0x0d3,0x0d4,0x0d5,0x0d5,0x0d6,0x0d6,0x0d7,0x0d7, + 0x0d8,0x0d8,0x0d9,0x0da,0x0da,0x0db,0x0db,0x0dc, + 0x0dc,0x0dd,0x0de,0x0de,0x0df,0x0df,0x0e0,0x0e0, + 0x0e1,0x0e1,0x0e2,0x0e3,0x0e3,0x0e4,0x0e4,0x0e5, + 0x0e5,0x0e6,0x0e7,0x0e7,0x0e8,0x0e8,0x0e9,0x0e9, + 0x0ea,0x0eb,0x0eb,0x0ec,0x0ec,0x0ed,0x0ed,0x0ee, + 0x0ef,0x0ef,0x0f0,0x0f0,0x0f1,0x0f1,0x0f2,0x0f3, + 0x0f3,0x0f4,0x0f4,0x0f5,0x0f5,0x0f6,0x0f7,0x0f7, + 0x0f8,0x0f8,0x0f9,0x0f9,0x0fa,0x0fb,0x0fb,0x0fc, + 0x0fc,0x0fd,0x0fd,0x0fe,0x0ff,0x0ff,0x100,0x100, + 0x101,0x101,0x102,0x103,0x103,0x104,0x104,0x105, + 0x106,0x106,0x107,0x107,0x108,0x108,0x109,0x10a, + 0x10a,0x10b,0x10b,0x10c,0x10c,0x10d,0x10e,0x10e, + 0x10f,0x10f,0x110,0x111,0x111,0x112,0x112,0x113, + 0x114,0x114,0x115,0x115,0x116,0x116,0x117,0x118, + 0x118,0x119,0x119,0x11a,0x11b,0x11b,0x11c,0x11c, + 0x11d,0x11e,0x11e,0x11f,0x11f,0x120,0x120,0x121, + 0x122,0x122,0x123,0x123,0x124,0x125,0x125,0x126, + 0x126,0x127,0x128,0x128,0x129,0x129,0x12a,0x12b, + 0x12b,0x12c,0x12c,0x12d,0x12e,0x12e,0x12f,0x12f, + 0x130,0x131,0x131,0x132,0x132,0x133,0x134,0x134, + 0x135,0x135,0x136,0x137,0x137,0x138,0x138,0x139, + 0x13a,0x13a,0x13b,0x13b,0x13c,0x13d,0x13d,0x13e, + 0x13e,0x13f,0x140,0x140,0x141,0x141,0x142,0x143, + 0x143,0x144,0x144,0x145,0x146,0x146,0x147,0x148, + 0x148,0x149,0x149,0x14a,0x14b,0x14b,0x14c,0x14c, + 0x14d,0x14e,0x14e,0x14f,0x14f,0x150,0x151,0x151, + 0x152,0x153,0x153,0x154,0x154,0x155,0x156,0x156, + 0x157,0x157,0x158,0x159,0x159,0x15a,0x15b,0x15b, + 0x15c,0x15c,0x15d,0x15e,0x15e,0x15f,0x160,0x160, + 0x161,0x161,0x162,0x163,0x163,0x164,0x165,0x165, + 0x166,0x166,0x167,0x168,0x168,0x169,0x16a,0x16a, + 0x16b,0x16b,0x16c,0x16d,0x16d,0x16e,0x16f,0x16f, + 0x170,0x170,0x171,0x172,0x172,0x173,0x174,0x174, + 0x175,0x175,0x176,0x177,0x177,0x178,0x179,0x179, + 0x17a,0x17a,0x17b,0x17c,0x17c,0x17d,0x17e,0x17e, + 0x17f,0x180,0x180,0x181,0x181,0x182,0x183,0x183, + 0x184,0x185,0x185,0x186,0x187,0x187,0x188,0x188, + 0x189,0x18a,0x18a,0x18b,0x18c,0x18c,0x18d,0x18e, + 0x18e,0x18f,0x190,0x190,0x191,0x191,0x192,0x193, + 0x193,0x194,0x195,0x195,0x196,0x197,0x197,0x198, + 0x199,0x199,0x19a,0x19a,0x19b,0x19c,0x19c,0x19d, + 0x19e,0x19e,0x19f,0x1a0,0x1a0,0x1a1,0x1a2,0x1a2, + 0x1a3,0x1a4,0x1a4,0x1a5,0x1a6,0x1a6,0x1a7,0x1a8, + 0x1a8,0x1a9,0x1a9,0x1aa,0x1ab,0x1ab,0x1ac,0x1ad, + 0x1ad,0x1ae,0x1af,0x1af,0x1b0,0x1b1,0x1b1,0x1b2, + 0x1b3,0x1b3,0x1b4,0x1b5,0x1b5,0x1b6,0x1b7,0x1b7, + 0x1b8,0x1b9,0x1b9,0x1ba,0x1bb,0x1bb,0x1bc,0x1bd, + 0x1bd,0x1be,0x1bf,0x1bf,0x1c0,0x1c1,0x1c1,0x1c2, + 0x1c3,0x1c3,0x1c4,0x1c5,0x1c5,0x1c6,0x1c7,0x1c7, + 0x1c8,0x1c9,0x1c9,0x1ca,0x1cb,0x1cb,0x1cc,0x1cd, + 0x1cd,0x1ce,0x1cf,0x1cf,0x1d0,0x1d1,0x1d1,0x1d2, + 0x1d3,0x1d3,0x1d4,0x1d5,0x1d5,0x1d6,0x1d7,0x1d7, + 0x1d8,0x1d9,0x1d9,0x1da,0x1db,0x1db,0x1dc,0x1dd, + 0x1dd,0x1de,0x1df,0x1df,0x1e0,0x1e1,0x1e1,0x1e2, + 0x1e3,0x1e4,0x1e4,0x1e5,0x1e6,0x1e6,0x1e7,0x1e8, + 0x1e8,0x1e9,0x1ea,0x1ea,0x1eb,0x1ec,0x1ec,0x1ed, + 0x1ee,0x1ee,0x1ef,0x1f0,0x1f0,0x1f1,0x1f2,0x1f3, + 0x1f3,0x1f4,0x1f5,0x1f5,0x1f6,0x1f7,0x1f7,0x1f8, + 0x1f9,0x1f9,0x1fa,0x1fb,0x1fb,0x1fc,0x1fd,0x1fe, + 0x1fe,0x1ff,0x200,0x200,0x201,0x202,0x202,0x203, + 0x204,0x205,0x205,0x206,0x207,0x207,0x208,0x209, + 0x209,0x20a,0x20b,0x20b,0x20c,0x20d,0x20e,0x20e, + 0x20f,0x210,0x210,0x211,0x212,0x212,0x213,0x214, + 0x215,0x215,0x216,0x217,0x217,0x218,0x219,0x21a, + 0x21a,0x21b,0x21c,0x21c,0x21d,0x21e,0x21e,0x21f, + 0x220,0x221,0x221,0x222,0x223,0x223,0x224,0x225, + 0x226,0x226,0x227,0x228,0x228,0x229,0x22a,0x22b, + 0x22b,0x22c,0x22d,0x22d,0x22e,0x22f,0x230,0x230, + 0x231,0x232,0x232,0x233,0x234,0x235,0x235,0x236, + 0x237,0x237,0x238,0x239,0x23a,0x23a,0x23b,0x23c, + 0x23c,0x23d,0x23e,0x23f,0x23f,0x240,0x241,0x241, + 0x242,0x243,0x244,0x244,0x245,0x246,0x247,0x247, + 0x248,0x249,0x249,0x24a,0x24b,0x24c,0x24c,0x24d, + 0x24e,0x24f,0x24f,0x250,0x251,0x251,0x252,0x253, + 0x254,0x254,0x255,0x256,0x257,0x257,0x258,0x259, + 0x259,0x25a,0x25b,0x25c,0x25c,0x25d,0x25e,0x25f, + 0x25f,0x260,0x261,0x262,0x262,0x263,0x264,0x265, + 0x265,0x266,0x267,0x267,0x268,0x269,0x26a,0x26a, + 0x26b,0x26c,0x26d,0x26d,0x26e,0x26f,0x270,0x270, + 0x271,0x272,0x273,0x273,0x274,0x275,0x276,0x276, + 0x277,0x278,0x279,0x279,0x27a,0x27b,0x27c,0x27c, + 0x27d,0x27e,0x27f,0x27f,0x280,0x281,0x282,0x282, + 0x283,0x284,0x285,0x285,0x286,0x287,0x288,0x288, + 0x289,0x28a,0x28b,0x28b,0x28c,0x28d,0x28e,0x28e, + 0x28f,0x290,0x291,0x291,0x292,0x293,0x294,0x294, + 0x295,0x296,0x297,0x298,0x298,0x299,0x29a,0x29b, + 0x29b,0x29c,0x29d,0x29e,0x29e,0x29f,0x2a0,0x2a1, + 0x2a1,0x2a2,0x2a3,0x2a4,0x2a5,0x2a5,0x2a6,0x2a7, + 0x2a8,0x2a8,0x2a9,0x2aa,0x2ab,0x2ab,0x2ac,0x2ad, + 0x2ae,0x2af,0x2af,0x2b0,0x2b1,0x2b2,0x2b2,0x2b3, + 0x2b4,0x2b5,0x2b5,0x2b6,0x2b7,0x2b8,0x2b9,0x2b9, + 0x2ba,0x2bb,0x2bc,0x2bc,0x2bd,0x2be,0x2bf,0x2c0, + 0x2c0,0x2c1,0x2c2,0x2c3,0x2c4,0x2c4,0x2c5,0x2c6, + 0x2c7,0x2c7,0x2c8,0x2c9,0x2ca,0x2cb,0x2cb,0x2cc, + 0x2cd,0x2ce,0x2ce,0x2cf,0x2d0,0x2d1,0x2d2,0x2d2, + 0x2d3,0x2d4,0x2d5,0x2d6,0x2d6,0x2d7,0x2d8,0x2d9, + 0x2da,0x2da,0x2db,0x2dc,0x2dd,0x2dd,0x2de,0x2df, + 0x2e0,0x2e1,0x2e1,0x2e2,0x2e3,0x2e4,0x2e5,0x2e5, + 0x2e6,0x2e7,0x2e8,0x2e9,0x2e9,0x2ea,0x2eb,0x2ec, + 0x2ed,0x2ed,0x2ee,0x2ef,0x2f0,0x2f1,0x2f1,0x2f2, + 0x2f3,0x2f4,0x2f5,0x2f5,0x2f6,0x2f7,0x2f8,0x2f9, + 0x2f9,0x2fa,0x2fb,0x2fc,0x2fd,0x2fd,0x2fe,0x2ff, + 0x300,0x301,0x302,0x302,0x303,0x304,0x305,0x306, + 0x306,0x307,0x308,0x309,0x30a,0x30a,0x30b,0x30c, + 0x30d,0x30e,0x30f,0x30f,0x310,0x311,0x312,0x313, + 0x313,0x314,0x315,0x316,0x317,0x318,0x318,0x319, + 0x31a,0x31b,0x31c,0x31c,0x31d,0x31e,0x31f,0x320, + 0x321,0x321,0x322,0x323,0x324,0x325,0x326,0x326, + 0x327,0x328,0x329,0x32a,0x32a,0x32b,0x32c,0x32d, + 0x32e,0x32f,0x32f,0x330,0x331,0x332,0x333,0x334, + 0x334,0x335,0x336,0x337,0x338,0x339,0x339,0x33a, + 0x33b,0x33c,0x33d,0x33e,0x33e,0x33f,0x340,0x341, + 0x342,0x343,0x343,0x344,0x345,0x346,0x347,0x348, + 0x349,0x349,0x34a,0x34b,0x34c,0x34d,0x34e,0x34e, + 0x34f,0x350,0x351,0x352,0x353,0x353,0x354,0x355, + 0x356,0x357,0x358,0x359,0x359,0x35a,0x35b,0x35c, + 0x35d,0x35e,0x35f,0x35f,0x360,0x361,0x362,0x363, + 0x364,0x364,0x365,0x366,0x367,0x368,0x369,0x36a, + 0x36a,0x36b,0x36c,0x36d,0x36e,0x36f,0x370,0x370, + 0x371,0x372,0x373,0x374,0x375,0x376,0x377,0x377, + 0x378,0x379,0x37a,0x37b,0x37c,0x37d,0x37d,0x37e, + 0x37f,0x380,0x381,0x382,0x383,0x383,0x384,0x385, + 0x386,0x387,0x388,0x389,0x38a,0x38a,0x38b,0x38c, + 0x38d,0x38e,0x38f,0x390,0x391,0x391,0x392,0x393, + 0x394,0x395,0x396,0x397,0x398,0x398,0x399,0x39a, + 0x39b,0x39c,0x39d,0x39e,0x39f,0x39f,0x3a0,0x3a1, + 0x3a2,0x3a3,0x3a4,0x3a5,0x3a6,0x3a7,0x3a7,0x3a8, + 0x3a9,0x3aa,0x3ab,0x3ac,0x3ad,0x3ae,0x3ae,0x3af, + 0x3b0,0x3b1,0x3b2,0x3b3,0x3b4,0x3b5,0x3b6,0x3b6, + 0x3b7,0x3b8,0x3b9,0x3ba,0x3bb,0x3bc,0x3bd,0x3be, + 0x3bf,0x3bf,0x3c0,0x3c1,0x3c2,0x3c3,0x3c4,0x3c5, + 0x3c6,0x3c7,0x3c7,0x3c8,0x3c9,0x3ca,0x3cb,0x3cc, + 0x3cd,0x3ce,0x3cf,0x3d0,0x3d1,0x3d1,0x3d2,0x3d3, + 0x3d4,0x3d5,0x3d6,0x3d7,0x3d8,0x3d9,0x3da,0x3da, + 0x3db,0x3dc,0x3dd,0x3de,0x3df,0x3e0,0x3e1,0x3e2, + 0x3e3,0x3e4,0x3e4,0x3e5,0x3e6,0x3e7,0x3e8,0x3e9, + 0x3ea,0x3eb,0x3ec,0x3ed,0x3ee,0x3ef,0x3ef,0x3f0, + 0x3f1,0x3f2,0x3f3,0x3f4,0x3f5,0x3f6,0x3f7,0x3f8, + 0x3f9,0x3fa,0x3fa,0x3fb,0x3fc,0x3fd,0x3fe,0x3ff +}; + +/* + * Attenuation according to GM recommendations, in -0.375 dB units. + * table[v] = 40 * log(v / 127) / -0.375 + */ +static unsigned char snd_opl4_volume_table[128] = { + 255,224,192,173,160,150,141,134, + 128,122,117,113,109,105,102, 99, + 96, 93, 90, 88, 85, 83, 81, 79, + 77, 75, 73, 71, 70, 68, 67, 65, + 64, 62, 61, 59, 58, 57, 56, 54, + 53, 52, 51, 50, 49, 48, 47, 46, + 45, 44, 43, 42, 41, 40, 39, 39, + 38, 37, 36, 35, 34, 34, 33, 32, + 31, 31, 30, 29, 29, 28, 27, 27, + 26, 25, 25, 24, 24, 23, 22, 22, + 21, 21, 20, 19, 19, 18, 18, 17, + 17, 16, 16, 15, 15, 14, 14, 13, + 13, 12, 12, 11, 11, 10, 10, 9, + 9, 9, 8, 8, 7, 7, 6, 6, + 6, 5, 5, 4, 4, 4, 3, 3, + 2, 2, 2, 1, 1, 0, 0, 0 +}; + +/* + * Initializes all voices. + */ +void snd_opl4_synth_reset(struct snd_opl4 *opl4) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&opl4->reg_lock, flags); + for (i = 0; i < OPL4_MAX_VOICES; i++) + snd_opl4_write(opl4, OPL4_REG_MISC + i, OPL4_DAMP_BIT); + spin_unlock_irqrestore(&opl4->reg_lock, flags); + + INIT_LIST_HEAD(&opl4->off_voices); + INIT_LIST_HEAD(&opl4->on_voices); + memset(opl4->voices, 0, sizeof(opl4->voices)); + for (i = 0; i < OPL4_MAX_VOICES; i++) { + opl4->voices[i].number = i; + list_add_tail(&opl4->voices[i].list, &opl4->off_voices); + } + + snd_midi_channel_set_clear(opl4->chset); +} + +/* + * Shuts down all voices. + */ +void snd_opl4_synth_shutdown(struct snd_opl4 *opl4) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&opl4->reg_lock, flags); + for (i = 0; i < OPL4_MAX_VOICES; i++) + snd_opl4_write(opl4, OPL4_REG_MISC + i, + opl4->voices[i].reg_misc & ~OPL4_KEY_ON_BIT); + spin_unlock_irqrestore(&opl4->reg_lock, flags); +} + +/* + * Executes the callback for all voices playing the specified note. + */ +static void snd_opl4_do_for_note(struct snd_opl4 *opl4, int note, struct snd_midi_channel *chan, + void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice)) +{ + int i; + unsigned long flags; + struct opl4_voice *voice; + + spin_lock_irqsave(&opl4->reg_lock, flags); + for (i = 0; i < OPL4_MAX_VOICES; i++) { + voice = &opl4->voices[i]; + if (voice->chan == chan && voice->note == note) { + func(opl4, voice); + } + } + spin_unlock_irqrestore(&opl4->reg_lock, flags); +} + +/* + * Executes the callback for all voices of to the specified channel. + */ +static void snd_opl4_do_for_channel(struct snd_opl4 *opl4, + struct snd_midi_channel *chan, + void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice)) +{ + int i; + unsigned long flags; + struct opl4_voice *voice; + + spin_lock_irqsave(&opl4->reg_lock, flags); + for (i = 0; i < OPL4_MAX_VOICES; i++) { + voice = &opl4->voices[i]; + if (voice->chan == chan) { + func(opl4, voice); + } + } + spin_unlock_irqrestore(&opl4->reg_lock, flags); +} + +/* + * Executes the callback for all active voices. + */ +static void snd_opl4_do_for_all(struct snd_opl4 *opl4, + void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice)) +{ + int i; + unsigned long flags; + struct opl4_voice *voice; + + spin_lock_irqsave(&opl4->reg_lock, flags); + for (i = 0; i < OPL4_MAX_VOICES; i++) { + voice = &opl4->voices[i]; + if (voice->chan) + func(opl4, voice); + } + spin_unlock_irqrestore(&opl4->reg_lock, flags); +} + +static void snd_opl4_update_volume(struct snd_opl4 *opl4, struct opl4_voice *voice) +{ + int att; + + att = voice->sound->tone_attenuate; + att += snd_opl4_volume_table[opl4->chset->gs_master_volume & 0x7f]; + att += snd_opl4_volume_table[voice->chan->gm_volume & 0x7f]; + att += snd_opl4_volume_table[voice->chan->gm_expression & 0x7f]; + att += snd_opl4_volume_table[voice->velocity]; + att = 0x7f - (0x7f - att) * (voice->sound->volume_factor) / 0xfe - volume_boost; + if (att < 0) + att = 0; + else if (att > 0x7e) + att = 0x7e; + snd_opl4_write(opl4, OPL4_REG_LEVEL + voice->number, + (att << 1) | voice->level_direct); + voice->level_direct = 0; +} + +static void snd_opl4_update_pan(struct snd_opl4 *opl4, struct opl4_voice *voice) +{ + int pan = voice->sound->panpot; + + if (!voice->chan->drum_channel) + pan += (voice->chan->control[MIDI_CTL_MSB_PAN] - 0x40) >> 3; + if (pan < -7) + pan = -7; + else if (pan > 7) + pan = 7; + voice->reg_misc = (voice->reg_misc & ~OPL4_PAN_POT_MASK) + | (pan & OPL4_PAN_POT_MASK); + snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc); +} + +static void snd_opl4_update_vibrato_depth(struct snd_opl4 *opl4, + struct opl4_voice *voice) +{ + int depth; + + if (voice->chan->drum_channel) + return; + depth = (7 - voice->sound->vibrato) + * (voice->chan->control[MIDI_CTL_VIBRATO_DEPTH] & 0x7f); + depth = (depth >> 7) + voice->sound->vibrato; + voice->reg_lfo_vibrato &= ~OPL4_VIBRATO_DEPTH_MASK; + voice->reg_lfo_vibrato |= depth & OPL4_VIBRATO_DEPTH_MASK; + snd_opl4_write(opl4, OPL4_REG_LFO_VIBRATO + voice->number, + voice->reg_lfo_vibrato); +} + +static void snd_opl4_update_pitch(struct snd_opl4 *opl4, + struct opl4_voice *voice) +{ + struct snd_midi_channel *chan = voice->chan; + int note, pitch, octave; + + note = chan->drum_channel ? 60 : voice->note; + /* + * pitch is in 100/128 cents, so 0x80 is one semitone and + * 0x600 is one octave. + */ + pitch = ((note - 60) << 7) * voice->sound->key_scaling / 100 + (60 << 7); + pitch += voice->sound->pitch_offset; + if (!chan->drum_channel) + pitch += chan->gm_rpn_coarse_tuning; + pitch += chan->gm_rpn_fine_tuning >> 7; + pitch += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 0x2000; + if (pitch < 0) + pitch = 0; + else if (pitch >= 0x6000) + pitch = 0x5fff; + octave = pitch / 0x600 - 8; + pitch = snd_opl4_pitch_map[pitch % 0x600]; + + snd_opl4_write(opl4, OPL4_REG_OCTAVE + voice->number, + (octave << 4) | ((pitch >> 7) & OPL4_F_NUMBER_HIGH_MASK)); + voice->reg_f_number = (voice->reg_f_number & OPL4_TONE_NUMBER_BIT8) + | ((pitch << 1) & OPL4_F_NUMBER_LOW_MASK); + snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice->number, voice->reg_f_number); +} + +static void snd_opl4_update_tone_parameters(struct snd_opl4 *opl4, + struct opl4_voice *voice) +{ + snd_opl4_write(opl4, OPL4_REG_ATTACK_DECAY1 + voice->number, + voice->sound->reg_attack_decay1); + snd_opl4_write(opl4, OPL4_REG_LEVEL_DECAY2 + voice->number, + voice->sound->reg_level_decay2); + snd_opl4_write(opl4, OPL4_REG_RELEASE_CORRECTION + voice->number, + voice->sound->reg_release_correction); + snd_opl4_write(opl4, OPL4_REG_TREMOLO + voice->number, + voice->sound->reg_tremolo); +} + +/* allocate one voice */ +static struct opl4_voice *snd_opl4_get_voice(struct snd_opl4 *opl4) +{ + /* first, try to get the oldest key-off voice */ + if (!list_empty(&opl4->off_voices)) + return list_entry(opl4->off_voices.next, struct opl4_voice, list); + /* then get the oldest key-on voice */ + snd_BUG_ON(list_empty(&opl4->on_voices)); + return list_entry(opl4->on_voices.next, struct opl4_voice, list); +} + +static void snd_opl4_wait_for_wave_headers(struct snd_opl4 *opl4) +{ + int timeout = 200; + + while ((inb(opl4->fm_port) & OPL4_STATUS_LOAD) && --timeout > 0) + udelay(10); +} + +void snd_opl4_note_on(void *private_data, int note, int vel, struct snd_midi_channel *chan) +{ + struct snd_opl4 *opl4 = private_data; + const struct opl4_region_ptr *regions; + struct opl4_voice *voice[2]; + const struct opl4_sound *sound[2]; + int voices = 0, i; + unsigned long flags; + + /* determine the number of voices and voice parameters */ + i = chan->drum_channel ? 0x80 : (chan->midi_program & 0x7f); + regions = &snd_yrw801_regions[i]; + for (i = 0; i < regions->count; i++) { + if (note >= regions->regions[i].key_min && + note <= regions->regions[i].key_max) { + sound[voices] = ®ions->regions[i].sound; + if (++voices >= 2) + break; + } + } + + /* allocate and initialize the needed voices */ + spin_lock_irqsave(&opl4->reg_lock, flags); + for (i = 0; i < voices; i++) { + voice[i] = snd_opl4_get_voice(opl4); + list_move_tail(&voice[i]->list, &opl4->on_voices); + voice[i]->chan = chan; + voice[i]->note = note; + voice[i]->velocity = vel & 0x7f; + voice[i]->sound = sound[i]; + } + + /* set tone number (triggers header loading) */ + for (i = 0; i < voices; i++) { + voice[i]->reg_f_number = + (sound[i]->tone >> 8) & OPL4_TONE_NUMBER_BIT8; + snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice[i]->number, + voice[i]->reg_f_number); + snd_opl4_write(opl4, OPL4_REG_TONE_NUMBER + voice[i]->number, + sound[i]->tone & 0xff); + } + + /* set parameters which can be set while loading */ + for (i = 0; i < voices; i++) { + voice[i]->reg_misc = OPL4_LFO_RESET_BIT; + snd_opl4_update_pan(opl4, voice[i]); + snd_opl4_update_pitch(opl4, voice[i]); + voice[i]->level_direct = OPL4_LEVEL_DIRECT_BIT; + snd_opl4_update_volume(opl4, voice[i]); + } + spin_unlock_irqrestore(&opl4->reg_lock, flags); + + /* wait for completion of loading */ + snd_opl4_wait_for_wave_headers(opl4); + + /* set remaining parameters */ + spin_lock_irqsave(&opl4->reg_lock, flags); + for (i = 0; i < voices; i++) { + snd_opl4_update_tone_parameters(opl4, voice[i]); + voice[i]->reg_lfo_vibrato = voice[i]->sound->reg_lfo_vibrato; + snd_opl4_update_vibrato_depth(opl4, voice[i]); + } + + /* finally, switch on all voices */ + for (i = 0; i < voices; i++) { + voice[i]->reg_misc = + (voice[i]->reg_misc & 0x1f) | OPL4_KEY_ON_BIT; + snd_opl4_write(opl4, OPL4_REG_MISC + voice[i]->number, + voice[i]->reg_misc); + } + spin_unlock_irqrestore(&opl4->reg_lock, flags); +} + +static void snd_opl4_voice_off(struct snd_opl4 *opl4, struct opl4_voice *voice) +{ + list_move_tail(&voice->list, &opl4->off_voices); + + voice->reg_misc &= ~OPL4_KEY_ON_BIT; + snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc); +} + +void snd_opl4_note_off(void *private_data, int note, int vel, struct snd_midi_channel *chan) +{ + struct snd_opl4 *opl4 = private_data; + + snd_opl4_do_for_note(opl4, note, chan, snd_opl4_voice_off); +} + +static void snd_opl4_terminate_voice(struct snd_opl4 *opl4, struct opl4_voice *voice) +{ + list_move_tail(&voice->list, &opl4->off_voices); + + voice->reg_misc = (voice->reg_misc & ~OPL4_KEY_ON_BIT) | OPL4_DAMP_BIT; + snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc); +} + +void snd_opl4_terminate_note(void *private_data, int note, struct snd_midi_channel *chan) +{ + struct snd_opl4 *opl4 = private_data; + + snd_opl4_do_for_note(opl4, note, chan, snd_opl4_terminate_voice); +} + +void snd_opl4_control(void *private_data, int type, struct snd_midi_channel *chan) +{ + struct snd_opl4 *opl4 = private_data; + + switch (type) { + case MIDI_CTL_MSB_MODWHEEL: + chan->control[MIDI_CTL_VIBRATO_DEPTH] = chan->control[MIDI_CTL_MSB_MODWHEEL]; + snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth); + break; + case MIDI_CTL_MSB_MAIN_VOLUME: + snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume); + break; + case MIDI_CTL_MSB_PAN: + snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pan); + break; + case MIDI_CTL_MSB_EXPRESSION: + snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume); + break; + case MIDI_CTL_VIBRATO_RATE: + /* not yet supported */ + break; + case MIDI_CTL_VIBRATO_DEPTH: + snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth); + break; + case MIDI_CTL_VIBRATO_DELAY: + /* not yet supported */ + break; + case MIDI_CTL_E1_REVERB_DEPTH: + /* + * Each OPL4 voice has a bit called "Pseudo-Reverb", but + * IMHO _not_ using it enhances the listening experience. + */ + break; + case MIDI_CTL_PITCHBEND: + snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pitch); + break; + } +} + +void snd_opl4_sysex(void *private_data, unsigned char *buf, int len, + int parsed, struct snd_midi_channel_set *chset) +{ + struct snd_opl4 *opl4 = private_data; + + if (parsed == SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME) + snd_opl4_do_for_all(opl4, snd_opl4_update_volume); +} diff --git a/sound/drivers/opl4/yrw801.c b/sound/drivers/opl4/yrw801.c new file mode 100644 index 000000000..6c335492d --- /dev/null +++ b/sound/drivers/opl4/yrw801.c @@ -0,0 +1,961 @@ +/* + * Information about the Yamaha YRW801 wavetable ROM chip + * + * Copyright (c) 2003 by Clemens Ladisch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed and/or modified under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opl4_local.h" + +int snd_yrw801_detect(struct snd_opl4 *opl4) +{ + char buf[15]; + + snd_opl4_read_memory(opl4, buf, 0x001200, 15); + if (memcmp(buf, "CopyrightYAMAHA", 15)) + return -ENODEV; + snd_opl4_read_memory(opl4, buf, 0x1ffffe, 2); + if (buf[0] != 0x01) + return -ENODEV; + snd_printdd("YRW801 ROM version %02x.%02x\n", buf[0], buf[1]); + return 0; +} + +/* + * The instrument definitions are stored statically because, in practice, the + * OPL4 is always coupled with a YRW801. Dynamic instrument loading would be + * required if downloading sample data to external SRAM was actually supported + * by this driver. + */ + +static const struct opl4_region regions_00[] = { /* Acoustic Grand Piano */ + {0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}}, + {0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xc8,0x20,0xf2,0x14,0x18,0x0}}, + {0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xc8,0x20,0xf3,0x14,0x18,0x0}}, + {0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}} +}; +static const struct opl4_region regions_01[] = { /* Bright Acoustic Piano */ + {0x14, 0x2d, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}}, + {0x2e, 0x33, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x34, 0x39, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x3a, 0x3f, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x40, 0x45, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x46, 0x4b, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x4c, 0x52, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}}, + {0x53, 0x58, {0x133,2081,100, 0,0,0x07,0xc8,0x20,0xf2,0x14,0x18,0x0}}, + {0x59, 0x5e, {0x134,1444,100, 0,0,0x0a,0xc8,0x20,0xf3,0x14,0x18,0x0}}, + {0x5f, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}} +}; +static const struct opl4_region regions_02[] = { /* Electric Grand Piano */ + {0x14, 0x2d, {0x12c,7476,100, 1,0,0x00,0xae,0x20,0xf2,0x13,0x07,0x0}}, + {0x2e, 0x33, {0x12d,6818,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}}, + {0x34, 0x39, {0x12e,5901,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}}, + {0x3a, 0x3f, {0x12f,5292,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}}, + {0x40, 0x45, {0x130,4262,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}}, + {0x46, 0x4b, {0x131,3627,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}}, + {0x4c, 0x52, {0x132,3118,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}}, + {0x53, 0x58, {0x133,2083,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x17,0x0}}, + {0x59, 0x5e, {0x134,1446,100, 1,0,0x00,0xae,0x20,0xf3,0x14,0x17,0x0}}, + {0x5f, 0x6d, {0x135,1917,100, 1,0,0x00,0xae,0x20,0xf4,0x15,0x07,0x0}}, + {0x00, 0x7f, {0x06c,6375,100,-1,0,0x00,0xc2,0x28,0xf4,0x23,0x18,0x0}} +}; +static const struct opl4_region regions_03[] = { /* Honky-Tonk Piano */ + {0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}}, + {0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xb4,0x20,0xf2,0x14,0x18,0x0}}, + {0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xb4,0x20,0xf3,0x14,0x18,0x0}}, + {0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}}, + {0x14, 0x27, {0x12c,7486,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}}, + {0x28, 0x2d, {0x12d,6803,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x2e, 0x33, {0x12e,5912,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x34, 0x39, {0x12f,5275,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x3a, 0x3f, {0x130,4274,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x40, 0x45, {0x131,3611,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x46, 0x4b, {0x132,3129,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}}, + {0x4c, 0x52, {0x133,2074,100, 0,0,0x07,0xb4,0x20,0xf2,0x14,0x18,0x0}}, + {0x53, 0x58, {0x134,1457,100, 0,0,0x01,0xb4,0x20,0xf3,0x14,0x18,0x0}}, + {0x59, 0x6d, {0x135,1903,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}} +}; +static const struct opl4_region regions_04[] = { /* Electric Piano 1 */ + {0x15, 0x6c, {0x00b,6570,100, 0,0,0x00,0x28,0x38,0xf0,0x00,0x0c,0x0}}, + {0x00, 0x7f, {0x06c,6375,100, 0,2,0x00,0xb0,0x22,0xf4,0x23,0x19,0x0}} +}; +static const struct opl4_region regions_05[] = { /* Electric Piano 2 */ + {0x14, 0x27, {0x12c,7476,100, 0,3,0x00,0xa2,0x1b,0xf2,0x13,0x08,0x0}}, + {0x28, 0x2d, {0x12d,6818,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}}, + {0x2e, 0x33, {0x12e,5901,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}}, + {0x34, 0x39, {0x12f,5292,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}}, + {0x3a, 0x3f, {0x130,4262,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}}, + {0x40, 0x45, {0x131,3627,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}}, + {0x46, 0x4b, {0x132,3118,100, 0,3,0x04,0xa2,0x1b,0xf2,0x14,0x08,0x0}}, + {0x4c, 0x52, {0x133,2083,100, 0,3,0x03,0xa2,0x1b,0xf2,0x14,0x18,0x0}}, + {0x53, 0x58, {0x134,1446,100, 0,3,0x07,0xa2,0x1b,0xf3,0x14,0x18,0x0}}, + {0x59, 0x6d, {0x135,1917,100, 0,3,0x00,0xa2,0x1b,0xf4,0x15,0x08,0x0}}, + {0x14, 0x2d, {0x12c,7472,100, 0,0,0x00,0xa2,0x18,0xf2,0x13,0x08,0x0}}, + {0x2e, 0x33, {0x12d,6814,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}}, + {0x34, 0x39, {0x12e,5897,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}}, + {0x3a, 0x3f, {0x12f,5288,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}}, + {0x40, 0x45, {0x130,4258,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}}, + {0x46, 0x4b, {0x131,3623,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}}, + {0x4c, 0x52, {0x132,3114,100, 0,0,0x04,0xa2,0x18,0xf2,0x14,0x08,0x0}}, + {0x53, 0x58, {0x133,2079,100, 0,0,0x07,0xa2,0x18,0xf2,0x14,0x18,0x0}}, + {0x59, 0x5e, {0x134,1442,100, 0,0,0x0a,0xa2,0x18,0xf3,0x14,0x18,0x0}}, + {0x5f, 0x6d, {0x135,1913,100, 0,0,0x00,0xa2,0x18,0xf4,0x15,0x08,0x0}} +}; +static const struct opl4_region regions_06[] = { /* Harpsichord */ + {0x15, 0x39, {0x080,5158,100, 0,0,0x00,0xb2,0x20,0xf5,0x24,0x19,0x0}}, + {0x3a, 0x3f, {0x081,4408,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}}, + {0x40, 0x45, {0x082,3622,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}}, + {0x46, 0x4d, {0x083,2843,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x19,0x0}}, + {0x4e, 0x6c, {0x084,1307,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x29,0x0}} +}; +static const struct opl4_region regions_07[] = { /* Clavinet */ + {0x15, 0x51, {0x027,5009,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x2b,0x0}}, + {0x52, 0x6c, {0x028,3495,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x3b,0x0}} +}; +static const struct opl4_region regions_08[] = { /* Celesta */ + {0x15, 0x6c, {0x02b,3267,100, 0,0,0x00,0xdc,0x20,0xf4,0x15,0x07,0x3}} +}; +static const struct opl4_region regions_09[] = { /* Glockenspiel */ + {0x15, 0x78, {0x0f3, 285,100, 0,0,0x00,0xc2,0x28,0xf6,0x25,0x25,0x0}} +}; +static const struct opl4_region regions_0a[] = { /* Music Box */ + {0x15, 0x6c, {0x0f3,3362,100, 0,0,0x00,0xb6,0x20,0xa6,0x25,0x25,0x0}}, + {0x15, 0x6c, {0x101,4773,100, 0,0,0x00,0xaa,0x20,0xd4,0x14,0x16,0x0}} +}; +static const struct opl4_region regions_0b[] = { /* Vibraphone */ + {0x15, 0x6c, {0x101,4778,100, 0,0,0x00,0xc0,0x28,0xf4,0x14,0x16,0x4}} +}; +static const struct opl4_region regions_0c[] = { /* Marimba */ + {0x15, 0x3f, {0x0f4,4778,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}}, + {0x40, 0x4c, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}}, + {0x4d, 0x5a, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x08,0x0}}, + {0x5b, 0x7f, {0x0f5,3218,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x18,0x0}} +}; +static const struct opl4_region regions_0d[] = { /* Xylophone */ + {0x00, 0x7f, {0x136,1729,100, 0,0,0x00,0xd2,0x38,0xf0,0x06,0x36,0x0}} +}; +static const struct opl4_region regions_0e[] = { /* Tubular Bell */ + {0x01, 0x7f, {0x0ff,3999,100, 0,1,0x00,0x90,0x21,0xf4,0xa3,0x25,0x1}} +}; +static const struct opl4_region regions_0f[] = { /* Dulcimer */ + {0x00, 0x7f, {0x03f,4236,100, 0,1,0x00,0xbc,0x29,0xf5,0x16,0x07,0x0}}, + {0x00, 0x7f, {0x040,4236,100, 0,2,0x0e,0x94,0x2a,0xf5,0x16,0x07,0x0}} +}; +static const struct opl4_region regions_10[] = { /* Drawbar Organ */ + {0x01, 0x7f, {0x08e,4394,100, 0,2,0x14,0xc2,0x3a,0xf0,0x00,0x0a,0x0}} +}; +static const struct opl4_region regions_11[] = { /* Percussive Organ */ + {0x15, 0x3b, {0x08c,6062,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}}, + {0x3c, 0x6c, {0x08d,2984,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_12[] = { /* Rock Organ */ + {0x15, 0x30, {0x128,6574,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}}, + {0x31, 0x3c, {0x129,5040,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}}, + {0x3d, 0x48, {0x12a,3498,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}}, + {0x49, 0x54, {0x12b,1957,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}}, + {0x55, 0x6c, {0x127, 423,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}} +}; +static const struct opl4_region regions_13[] = { /* Church Organ */ + {0x15, 0x29, {0x087,7466,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}}, + {0x2a, 0x30, {0x088,6456,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}}, + {0x31, 0x38, {0x089,5428,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}}, + {0x39, 0x41, {0x08a,4408,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}}, + {0x42, 0x6c, {0x08b,3406,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_14[] = { /* Reed Organ */ + {0x00, 0x53, {0x0ac,5570,100, 0,0,0x06,0xc0,0x38,0xf0,0x00,0x09,0x1}}, + {0x54, 0x7f, {0x0ad,2497,100, 0,0,0x00,0xc0,0x38,0xf0,0x00,0x09,0x1}} +}; +static const struct opl4_region regions_15[] = { /* Accordion */ + {0x15, 0x4c, {0x006,4261,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}}, + {0x4d, 0x6c, {0x007,1530,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}}, + {0x15, 0x6c, {0x070,4391,100, 0,3,0x00,0x8a,0x23,0xa0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_16[] = { /* Harmonica */ + {0x15, 0x6c, {0x070,4408,100, 0,0,0x00,0xae,0x30,0xa0,0x00,0x09,0x2}} +}; +static const struct opl4_region regions_17[] = { /* Tango Accordion */ + {0x00, 0x53, {0x0ac,5573,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}}, + {0x54, 0x7f, {0x0ad,2500,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}}, + {0x15, 0x6c, {0x041,8479,100, 0,2,0x00,0x6a,0x3a,0x75,0x20,0x0a,0x0}} +}; +static const struct opl4_region regions_18[] = { /* Nylon Guitar */ + {0x15, 0x2f, {0x0b3,6964,100, 0,0,0x05,0xca,0x28,0xf5,0x34,0x09,0x0}}, + {0x30, 0x36, {0x0b7,5567,100, 0,0,0x0c,0xca,0x28,0xf5,0x34,0x09,0x0}}, + {0x37, 0x3c, {0x0b5,4653,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}}, + {0x3d, 0x43, {0x0b4,3892,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x09,0x0}}, + {0x44, 0x60, {0x0b6,2723,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x19,0x0}} +}; +static const struct opl4_region regions_19[] = { /* Steel Guitar */ + {0x15, 0x31, {0x00c,6937,100, 0,0,0x00,0xbc,0x28,0xf0,0x04,0x19,0x0}}, + {0x32, 0x38, {0x00d,5410,100, 0,0,0x00,0xbc,0x28,0xf0,0x05,0x09,0x0}}, + {0x39, 0x47, {0x00e,4379,100, 0,0,0x00,0xbc,0x28,0xf5,0x94,0x09,0x0}}, + {0x48, 0x6c, {0x00f,2843,100, 0,0,0x00,0xbc,0x28,0xf6,0x95,0x09,0x0}} +}; +static const struct opl4_region regions_1a[] = { /* Jazz Guitar */ + {0x15, 0x31, {0x05a,6832,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}}, + {0x32, 0x3f, {0x05b,4897,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}}, + {0x40, 0x6c, {0x05c,3218,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}} +}; +static const struct opl4_region regions_1b[] = { /* Clean Guitar */ + {0x15, 0x2c, {0x061,7053,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}}, + {0x2d, 0x31, {0x060,6434,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}}, + {0x32, 0x38, {0x063,5764,100, 0,1,0x00,0xbe,0x29,0xf5,0x55,0x0a,0x0}}, + {0x39, 0x3f, {0x062,4627,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x0a,0x0}}, + {0x40, 0x44, {0x065,3963,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}}, + {0x45, 0x4b, {0x064,3313,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}}, + {0x4c, 0x54, {0x066,2462,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x2a,0x0}}, + {0x55, 0x6c, {0x067,1307,100, 0,1,0x00,0xb4,0x29,0xf6,0x56,0x0a,0x0}} +}; +static const struct opl4_region regions_1c[] = { /* Muted Guitar */ + {0x01, 0x7f, {0x068,4408,100, 0,0,0x00,0xcc,0x28,0xf6,0x15,0x09,0x0}} +}; +static const struct opl4_region regions_1d[] = { /* Overdriven Guitar */ + {0x00, 0x40, {0x0a5,6589,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}}, + {0x41, 0x7f, {0x0a6,5428,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}} +}; +static const struct opl4_region regions_1e[] = { /* Distortion Guitar */ + {0x15, 0x2a, {0x051,6928,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x2b, 0x2e, {0x052,6433,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x2f, 0x32, {0x053,5944,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x33, 0x36, {0x054,5391,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x37, 0x3a, {0x055,4897,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x3b, 0x3e, {0x056,4408,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x3f, 0x42, {0x057,3892,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x43, 0x46, {0x058,3361,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}, + {0x47, 0x6c, {0x059,2784,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}} +}; +static const struct opl4_region regions_1f[] = { /* Guitar Harmonics */ + {0x15, 0x44, {0x05e,5499,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}}, + {0x45, 0x49, {0x05d,4850,100, 0,0,0x00,0xe2,0x28,0xf4,0x24,0x09,0x0}}, + {0x4a, 0x6c, {0x05f,4259,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}} +}; +static const struct opl4_region regions_20[] = { /* Acoustic Bass */ + {0x15, 0x30, {0x004,8053,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}}, + {0x31, 0x6c, {0x005,4754,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}} +}; +static const struct opl4_region regions_21[] = { /* Fingered Bass */ + {0x01, 0x20, {0x04a,8762,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}}, + {0x21, 0x25, {0x04b,8114,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}}, + {0x26, 0x2a, {0x04c,7475,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}}, + {0x2b, 0x7f, {0x04d,6841,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}} +}; +static const struct opl4_region regions_22[] = { /* Picked Bass */ + {0x15, 0x23, {0x04f,7954,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x0a,0x0}}, + {0x24, 0x2a, {0x050,7318,100, 0,0,0x05,0xcc,0x18,0xf3,0x90,0x1a,0x0}}, + {0x2b, 0x2f, {0x06b,6654,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x2a,0x0}}, + {0x30, 0x47, {0x069,6031,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}}, + {0x48, 0x6c, {0x06a,5393,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}} +}; +static const struct opl4_region regions_23[] = { /* Fretless Bass */ + {0x01, 0x7f, {0x04e,5297,100, 0,0,0x00,0xd2,0x10,0xf3,0x63,0x19,0x0}} +}; +static const struct opl4_region regions_24[] = { /* Slap Bass 1 */ + {0x15, 0x6c, {0x0a3,7606,100, 0,1,0x00,0xde,0x19,0xf5,0x32,0x1a,0x0}} +}; +static const struct opl4_region regions_25[] = { /* Slap Bass 2 */ + {0x01, 0x7f, {0x0a2,6694,100, 0,0,0x00,0xda,0x20,0xb0,0x02,0x09,0x0}} +}; +static const struct opl4_region regions_26[] = { /* Synth Bass 1 */ + {0x15, 0x6c, {0x0be,7466,100, 0,1,0x00,0xb8,0x39,0xf4,0x14,0x09,0x0}} +}; +static const struct opl4_region regions_27[] = { /* Synth Bass 2 */ + {0x00, 0x7f, {0x117,8103,100, 0,1,0x00,0xca,0x39,0xf3,0x50,0x08,0x0}} +}; +static const struct opl4_region regions_28[] = { /* Violin */ + {0x15, 0x3a, {0x105,5158,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x3b, 0x3f, {0x102,4754,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x40, 0x41, {0x106,4132,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x42, 0x44, {0x107,4033,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x45, 0x47, {0x108,3580,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x48, 0x4a, {0x10a,2957,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x4b, 0x4c, {0x10b,2724,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x4d, 0x4e, {0x10c,2530,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x4f, 0x51, {0x10d,2166,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}, + {0x52, 0x6c, {0x109,1825,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}} +}; +static const struct opl4_region regions_29[] = { /* Viola */ + {0x15, 0x32, {0x103,5780,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}}, + {0x33, 0x35, {0x104,5534,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}}, + {0x36, 0x38, {0x105,5158,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}}, + {0x39, 0x3d, {0x102,4754,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}}, + {0x3e, 0x3f, {0x106,4132,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}}, + {0x40, 0x42, {0x107,4033,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}}, + {0x43, 0x45, {0x108,3580,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}}, + {0x46, 0x48, {0x10a,2957,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}}, + {0x49, 0x4a, {0x10b,2724,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}}, + {0x4b, 0x4c, {0x10c,2530,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}}, + {0x4d, 0x4f, {0x10d,2166,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}}, + {0x50, 0x6c, {0x109,1825,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}} +}; +static const struct opl4_region regions_2a[] = { /* Cello */ + {0x15, 0x2d, {0x112,6545,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}}, + {0x2e, 0x37, {0x113,5764,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}}, + {0x38, 0x3e, {0x115,4378,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}}, + {0x3f, 0x44, {0x116,3998,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}}, + {0x45, 0x6c, {0x114,3218,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}} +}; +static const struct opl4_region regions_2b[] = { /* Contrabass */ + {0x15, 0x29, {0x110,7713,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}}, + {0x2a, 0x6c, {0x111,6162,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_2c[] = { /* Tremolo Strings */ + {0x15, 0x3b, {0x0b0,4810,100, 0,0,0x0a,0xde,0x38,0xf0,0x00,0x07,0x6}}, + {0x3c, 0x41, {0x035,4035,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}}, + {0x42, 0x47, {0x033,3129,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}}, + {0x48, 0x52, {0x034,2625,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}}, + {0x53, 0x6c, {0x0af, 936,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x07,0x6}} +}; +static const struct opl4_region regions_2d[] = { /* Pizzicato Strings */ + {0x15, 0x32, {0x0b8,6186,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}}, + {0x33, 0x3b, {0x0b9,5031,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}}, + {0x3c, 0x42, {0x0bb,4146,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}}, + {0x43, 0x48, {0x0ba,3245,100, 0,0,0x00,0xc2,0x28,0xf0,0x00,0x05,0x0}}, + {0x49, 0x6c, {0x0bc,2352,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}} +}; +static const struct opl4_region regions_2e[] = { /* Harp */ + {0x15, 0x46, {0x07e,3740,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}}, + {0x47, 0x6c, {0x07f,2319,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}} +}; +static const struct opl4_region regions_2f[] = { /* Timpani */ + {0x15, 0x6c, {0x100,6570,100, 0,0,0x00,0xf8,0x28,0xf0,0x05,0x16,0x0}} +}; +static const struct opl4_region regions_30[] = { /* Strings */ + {0x15, 0x3b, {0x13c,4806,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}}, + {0x3c, 0x41, {0x13e,4035,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}}, + {0x42, 0x47, {0x13d,3122,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}}, + {0x48, 0x52, {0x13f,2629,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}}, + {0x53, 0x6c, {0x140, 950,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}} +}; +static const struct opl4_region regions_31[] = { /* Slow Strings */ + {0x15, 0x3b, {0x0b0,4810,100, 0,1,0x0a,0xbe,0x19,0xf0,0x00,0x07,0x0}}, + {0x3c, 0x41, {0x035,4035,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}}, + {0x42, 0x47, {0x033,3129,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}}, + {0x48, 0x52, {0x034,2625,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}}, + {0x53, 0x6c, {0x0af, 936,100, 0,1,0x00,0xbe,0x19,0xf0,0x00,0x07,0x0}} +}; +static const struct opl4_region regions_32[] = { /* Synth Strings 1 */ + {0x05, 0x71, {0x002,6045,100,-2,0,0x00,0xa6,0x20,0x93,0x22,0x06,0x0}}, + {0x15, 0x6c, {0x0ae,3261,100, 2,0,0x00,0xc6,0x20,0x70,0x01,0x06,0x0}} +}; +static const struct opl4_region regions_33[] = { /* Synth Strings 2 */ + {0x15, 0x6c, {0x002,4513,100, 5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}}, + {0x15, 0x6c, {0x002,4501,100,-5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}} +}; +static const struct opl4_region regions_34[] = { /* Choir Aahs */ + {0x15, 0x3a, {0x018,5010,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}}, + {0x3b, 0x40, {0x019,4370,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}}, + {0x41, 0x47, {0x01a,3478,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}}, + {0x48, 0x6c, {0x01b,2197,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}} +}; +static const struct opl4_region regions_35[] = { /* Voice Oohs */ + {0x15, 0x6c, {0x029,3596,100, 0,0,0x00,0xe6,0x20,0xf7,0x20,0x08,0x0}} +}; +static const struct opl4_region regions_36[] = { /* Synth Voice */ + {0x15, 0x6c, {0x02a,3482,100, 0,1,0x00,0xc2,0x19,0x85,0x21,0x07,0x0}} +}; +static const struct opl4_region regions_37[] = { /* Orchestra Hit */ + {0x15, 0x6c, {0x049,4394,100, 0,0,0x00,0xfe,0x30,0x80,0x05,0x05,0x0}} +}; +static const struct opl4_region regions_38[] = { /* Trumpet */ + {0x15, 0x3c, {0x0f6,4706,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}, + {0x3d, 0x43, {0x0f8,3894,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}, + {0x44, 0x48, {0x0f7,3118,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}, + {0x49, 0x4e, {0x0fa,2322,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}, + {0x4f, 0x55, {0x0f9,1634,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}, + {0x56, 0x6c, {0x0fb, 786,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}} +}; +static const struct opl4_region regions_39[] = { /* Trombone */ + {0x15, 0x3a, {0x0f0,5053,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}}, + {0x3b, 0x3f, {0x0f1,4290,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}}, + {0x40, 0x6c, {0x0f2,3580,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_3a[] = { /* Tuba */ + {0x15, 0x2d, {0x085,7096,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}}, + {0x2e, 0x6c, {0x086,6014,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}} +}; +static const struct opl4_region regions_3b[] = { /* Muted Trumpet */ + {0x15, 0x45, {0x0b1,4135,100, 0,0,0x00,0xcc,0x28,0xf3,0x10,0x0a,0x1}}, + {0x46, 0x6c, {0x0b2,2599,100, 0,0,0x00,0xcc,0x28,0x83,0x10,0x0a,0x1}} +}; +static const struct opl4_region regions_3c[] = { /* French Horns */ + {0x15, 0x49, {0x07c,3624,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}}, + {0x4a, 0x6c, {0x07d,2664,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_3d[] = { /* Brass Section */ + {0x15, 0x42, {0x0fc,4375,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}}, + {0x43, 0x6c, {0x0fd,2854,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}} +}; +static const struct opl4_region regions_3e[] = { /* Synth Brass 1 */ + {0x01, 0x27, {0x0d3,9094,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x28, 0x2d, {0x0da,8335,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x2e, 0x33, {0x0d4,7558,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x34, 0x39, {0x0db,6785,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x3a, 0x3f, {0x0d5,6042,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x40, 0x45, {0x0dc,5257,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x46, 0x4b, {0x0d6,4493,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x4c, 0x51, {0x0dd,3741,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x52, 0x57, {0x0d7,3012,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x58, 0x5d, {0x0de,2167,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x5e, 0x63, {0x0d8,1421,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x64, 0x7f, {0x0d9,-115,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}}, + {0x01, 0x27, {0x118,9103,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x28, 0x2d, {0x119,8340,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x2e, 0x33, {0x11a,7565,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x34, 0x39, {0x11b,6804,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x3a, 0x3f, {0x11c,6042,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x40, 0x45, {0x11d,5277,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x46, 0x4b, {0x11e,4520,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x4c, 0x51, {0x11f,3741,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x52, 0x57, {0x120,3012,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x58, 0x5d, {0x121,2166,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x5e, 0x64, {0x122,1421,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}, + {0x65, 0x7f, {0x123,-115,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}} +}; +static const struct opl4_region regions_3f[] = { /* Synth Brass 2 */ + {0x01, 0x27, {0x118,9113,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x28, 0x2d, {0x119,8350,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x2e, 0x33, {0x11a,7575,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x34, 0x39, {0x11b,6814,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x3a, 0x3f, {0x11c,6052,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x40, 0x45, {0x11d,5287,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x46, 0x4b, {0x11e,4530,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x4c, 0x51, {0x11f,3751,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x52, 0x57, {0x120,3022,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x58, 0x5d, {0x121,2176,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x5e, 0x64, {0x122,1431,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x65, 0x7f, {0x123,-105,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}}, + {0x00, 0x7f, {0x124,4034,100,-3,2,0x00,0xea,0x22,0x85,0x23,0x08,0x0}} +}; +static const struct opl4_region regions_40[] = { /* Soprano Sax */ + {0x15, 0x3f, {0x0e3,4228,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}}, + {0x40, 0x45, {0x0e4,3495,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}}, + {0x46, 0x4b, {0x0e5,2660,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}}, + {0x4c, 0x51, {0x0e6,2002,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}}, + {0x52, 0x59, {0x0e7,1186,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}}, + {0x59, 0x6c, {0x0e8,1730,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}} +}; +static const struct opl4_region regions_41[] = { /* Alto Sax */ + {0x15, 0x32, {0x092,6204,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x33, 0x35, {0x096,5812,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x36, 0x3a, {0x099,5318,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x3b, 0x3b, {0x08f,5076,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x3c, 0x3e, {0x093,4706,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x3f, 0x41, {0x097,4321,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x42, 0x44, {0x09a,3893,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x45, 0x47, {0x090,3497,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x48, 0x4a, {0x094,3119,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x4b, 0x4d, {0x098,2726,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x4e, 0x50, {0x09b,2393,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x51, 0x53, {0x091,2088,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}, + {0x54, 0x6c, {0x095,1732,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}} +}; +static const struct opl4_region regions_42[] = { /* Tenor Sax */ + {0x24, 0x30, {0x0e9,6301,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}, + {0x31, 0x34, {0x0ea,5781,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}, + {0x35, 0x3a, {0x0eb,5053,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}, + {0x3b, 0x41, {0x0ed,4165,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}, + {0x42, 0x47, {0x0ec,3218,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}, + {0x48, 0x51, {0x0ee,2462,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}, + {0x52, 0x6c, {0x0ef,1421,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}} +}; +static const struct opl4_region regions_43[] = { /* Baritone Sax */ + {0x15, 0x2d, {0x0df,6714,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}}, + {0x2e, 0x34, {0x0e1,5552,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}}, + {0x35, 0x39, {0x0e2,5178,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}}, + {0x3a, 0x6c, {0x0e0,4437,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}} +}; +static const struct opl4_region regions_44[] = { /* Oboe */ + {0x15, 0x3c, {0x042,4493,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}}, + {0x3d, 0x43, {0x044,3702,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}}, + {0x44, 0x49, {0x043,2956,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}}, + {0x4a, 0x4f, {0x046,2166,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}}, + {0x50, 0x55, {0x045,1420,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}}, + {0x56, 0x6c, {0x047, 630,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}} +}; +static const struct opl4_region regions_45[] = { /* English Horn */ + {0x15, 0x38, {0x03c,5098,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}}, + {0x39, 0x3e, {0x03b,4291,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}}, + {0x3f, 0x6c, {0x03d,3540,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_46[] = { /* Bassoon */ + {0x15, 0x22, {0x038,7833,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}}, + {0x23, 0x2e, {0x03a,7070,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}}, + {0x2f, 0x6c, {0x039,6302,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}} +}; +static const struct opl4_region regions_47[] = { /* Clarinet */ + {0x15, 0x3b, {0x09e,5900,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}}, + {0x3c, 0x41, {0x0a0,5158,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}}, + {0x42, 0x4a, {0x09f,4260,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}}, + {0x4b, 0x6c, {0x0a1,2957,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}} +}; +static const struct opl4_region regions_48[] = { /* Piccolo */ + {0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}, + {0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}, + {0x4e, 0x53, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}, + {0x54, 0x5f, {0x074,2085,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}, + {0x60, 0x6c, {0x075,1421,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}} +}; +static const struct opl4_region regions_49[] = { /* Flute */ + {0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}}, + {0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}}, + {0x4e, 0x6c, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}} +}; +static const struct opl4_region regions_4a[] = { /* Recorder */ + {0x15, 0x6f, {0x0bd,4897,100, 0,0,0x00,0xec,0x30,0x70,0x00,0x09,0x1}} +}; +static const struct opl4_region regions_4b[] = { /* Pan Flute */ + {0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x09,0x3}} +}; +static const struct opl4_region regions_4c[] = { /* Bottle Blow */ + {0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xc8,0x38,0xf0,0x00,0x09,0x1}}, + {0x01, 0x7f, {0x125,7372,100, 0,0,0x1e,0x80,0x00,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_4d[] = { /* Shakuhachi */ + {0x00, 0x7f, {0x0ab,4548,100, 0,0,0x00,0xd6,0x30,0xf0,0x00,0x0a,0x3}}, + {0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xa2,0x28,0x70,0x00,0x09,0x2}} +}; +static const struct opl4_region regions_4e[] = { /* Whistle */ + {0x00, 0x7f, {0x0aa,1731,100, 0,4,0x00,0xd2,0x2c,0x70,0x00,0x0a,0x0}} +}; +static const struct opl4_region regions_4f[] = { /* Ocarina */ + {0x00, 0x7f, {0x0aa,1731,100, 0,1,0x00,0xce,0x29,0x90,0x00,0x0a,0x1}} +}; +static const struct opl4_region regions_50[] = { /* Square Lead */ + {0x01, 0x2a, {0x0cc,9853,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}}, + {0x2b, 0x36, {0x0cd,6785,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}}, + {0x37, 0x42, {0x0ca,5248,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}}, + {0x43, 0x4e, {0x0cf,3713,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}}, + {0x4f, 0x5a, {0x0ce,2176,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}}, + {0x5b, 0x7f, {0x0cb, 640,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}}, + {0x01, 0x2a, {0x0cc,9844,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}, + {0x2b, 0x36, {0x0cd,6776,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}, + {0x37, 0x42, {0x0ca,5239,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}, + {0x43, 0x4e, {0x0cf,3704,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}, + {0x4f, 0x5a, {0x0ce,2167,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}, + {0x5b, 0x7f, {0x0cb, 631,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}} +}; +static const struct opl4_region regions_51[] = { /* Sawtooth Lead */ + {0x01, 0x27, {0x118,9108,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x28, 0x2d, {0x119,8345,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x2e, 0x33, {0x11a,7570,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x34, 0x39, {0x11b,6809,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x3a, 0x3f, {0x11c,6047,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x40, 0x45, {0x11d,5282,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x46, 0x4b, {0x11e,4525,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x4c, 0x51, {0x11f,3746,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x52, 0x57, {0x120,3017,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x58, 0x5d, {0x121,2171,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x5e, 0x66, {0x122,1426,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x67, 0x7f, {0x123,-110,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x01, 0x27, {0x118,9098,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x28, 0x2d, {0x119,8335,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x2e, 0x33, {0x11a,7560,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x34, 0x39, {0x11b,6799,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x3a, 0x3f, {0x11c,6037,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x40, 0x45, {0x11d,5272,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x46, 0x4b, {0x11e,4515,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x4c, 0x51, {0x11f,3736,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x52, 0x57, {0x120,3007,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x58, 0x5d, {0x121,2161,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x5e, 0x66, {0x122,1416,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}, + {0x67, 0x7f, {0x123,-120,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}} +}; +static const struct opl4_region regions_52[] = { /* Calliope Lead */ + {0x00, 0x7f, {0x0aa,1731,100, 0,0,0x00,0xc2,0x28,0x90,0x00,0x0a,0x2}}, + {0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xb6,0x28,0xb0,0x00,0x09,0x2}} +}; +static const struct opl4_region regions_53[] = { /* Chiffer Lead */ + {0x00, 0x7f, {0x13a,3665,100, 0,2,0x00,0xcc,0x2a,0xf0,0x10,0x09,0x1}}, + {0x01, 0x7f, {0x0fe,3660,100, 0,0,0x00,0xbe,0x28,0xf3,0x10,0x17,0x0}} +}; +static const struct opl4_region regions_54[] = { /* Charang Lead */ + {0x00, 0x40, {0x0a5,6594,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}}, + {0x41, 0x7f, {0x0a6,5433,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}}, + {0x01, 0x27, {0x118,9098,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x28, 0x2d, {0x119,8335,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x2e, 0x33, {0x11a,7560,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x34, 0x39, {0x11b,6799,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x3a, 0x3f, {0x11c,6037,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x40, 0x45, {0x11d,5272,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x46, 0x4b, {0x11e,4515,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x4c, 0x51, {0x11f,3736,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x52, 0x57, {0x120,3007,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x58, 0x5d, {0x121,2161,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x5e, 0x66, {0x122,1416,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}, + {0x67, 0x7f, {0x123,-120,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}} +}; +static const struct opl4_region regions_55[] = { /* Voice Lead */ + {0x00, 0x7f, {0x0aa,1739,100, 0,6,0x00,0x8c,0x2e,0x90,0x00,0x0a,0x0}}, + {0x15, 0x6c, {0x02a,3474,100, 0,1,0x00,0xd8,0x29,0xf0,0x05,0x0a,0x0}} +}; +static const struct opl4_region regions_56[] = { /* 5ths Lead */ + {0x01, 0x27, {0x118,8468,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x28, 0x2d, {0x119,7705,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x2e, 0x33, {0x11a,6930,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x34, 0x39, {0x11b,6169,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x3a, 0x3f, {0x11c,5407,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x40, 0x45, {0x11d,4642,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x46, 0x4b, {0x11e,3885,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x4c, 0x51, {0x11f,3106,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x52, 0x57, {0x120,2377,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x58, 0x5d, {0x121,1531,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x5e, 0x64, {0x122, 786,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x65, 0x7f, {0x123,-750,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}}, + {0x05, 0x71, {0x002,4503,100, 0,1,0x00,0xb8,0x31,0xb3,0x20,0x0b,0x0}} +}; +static const struct opl4_region regions_57[] = { /* Bass & Lead */ + {0x00, 0x7f, {0x117,8109,100, 0,1,0x00,0xbc,0x29,0xf3,0x50,0x08,0x0}}, + {0x01, 0x27, {0x118,9097,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x28, 0x2d, {0x119,8334,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x2e, 0x33, {0x11a,7559,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x34, 0x39, {0x11b,6798,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x3a, 0x3f, {0x11c,6036,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x40, 0x45, {0x11d,5271,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x46, 0x4b, {0x11e,4514,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x4c, 0x51, {0x11f,3735,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x52, 0x57, {0x120,3006,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x58, 0x5d, {0x121,2160,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x5e, 0x66, {0x122,1415,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}, + {0x67, 0x7f, {0x123,-121,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}} +}; +static const struct opl4_region regions_58[] = { /* New Age Pad */ + {0x15, 0x6c, {0x002,4501,100, 0,4,0x00,0xa4,0x24,0x80,0x01,0x05,0x0}}, + {0x15, 0x6c, {0x0f3,4253,100, 0,3,0x00,0x8c,0x23,0xa2,0x14,0x06,0x1}} +}; +static const struct opl4_region regions_59[] = { /* Warm Pad */ + {0x15, 0x6c, {0x04e,5306,100, 2,2,0x00,0x92,0x2a,0x34,0x23,0x05,0x2}}, + {0x15, 0x6c, {0x029,3575,100,-2,2,0x00,0xbe,0x22,0x31,0x23,0x06,0x0}} +}; +static const struct opl4_region regions_5a[] = { /* Polysynth Pad */ + {0x01, 0x27, {0x118,9111,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x28, 0x2d, {0x119,8348,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x2e, 0x33, {0x11a,7573,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x34, 0x39, {0x11b,6812,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x3a, 0x3f, {0x11c,6050,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x40, 0x45, {0x11d,5285,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x46, 0x4b, {0x11e,4528,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x4c, 0x51, {0x11f,3749,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x52, 0x57, {0x120,3020,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x58, 0x5d, {0x121,2174,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x5e, 0x66, {0x122,1429,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x67, 0x7f, {0x123,-107,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}}, + {0x00, 0x7f, {0x124,4024,100, 0,2,0x00,0xae,0x22,0xe5,0x20,0x08,0x0}} +}; +static const struct opl4_region regions_5b[] = { /* Choir Pad */ + {0x15, 0x3a, {0x018,5010,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}}, + {0x3b, 0x40, {0x019,4370,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}}, + {0x41, 0x47, {0x01a,3478,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}}, + {0x48, 0x6c, {0x01b,2197,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}}, + {0x15, 0x6c, {0x02a,3482,100, 0,4,0x00,0x98,0x24,0x65,0x21,0x06,0x0}} +}; +static const struct opl4_region regions_5c[] = { /* Bowed Pad */ + {0x15, 0x6c, {0x101,4790,100,-1,1,0x00,0xbe,0x19,0x44,0x14,0x16,0x0}}, + {0x00, 0x7f, {0x0aa,1720,100, 1,1,0x00,0x94,0x19,0x40,0x00,0x06,0x0}} +}; +static const struct opl4_region regions_5d[] = { /* Metallic Pad */ + {0x15, 0x31, {0x00c,6943,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}}, + {0x32, 0x38, {0x00d,5416,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}}, + {0x39, 0x47, {0x00e,4385,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}}, + {0x48, 0x6c, {0x00f,2849,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}}, + {0x00, 0x7f, {0x03f,4224,100, 0,1,0x00,0x9c,0x31,0x65,0x16,0x07,0x0}} +}; +static const struct opl4_region regions_5e[] = { /* Halo Pad */ + {0x00, 0x7f, {0x124,4038,100, 0,2,0x00,0xa6,0x1a,0x85,0x23,0x08,0x0}}, + {0x15, 0x6c, {0x02a,3471,100, 0,3,0x00,0xc0,0x1b,0xc0,0x05,0x06,0x0}} +}; +static const struct opl4_region regions_5f[] = { /* Sweep Pad */ + {0x01, 0x27, {0x0d3,9100,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x28, 0x2d, {0x0da,8341,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x2e, 0x33, {0x0d4,7564,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x34, 0x39, {0x0db,6791,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x3a, 0x3f, {0x0d5,6048,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x40, 0x45, {0x0dc,5263,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x46, 0x4b, {0x0d6,4499,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x4c, 0x51, {0x0dd,3747,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x52, 0x57, {0x0d7,3018,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x58, 0x5d, {0x0de,2173,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x5e, 0x63, {0x0d8,1427,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x64, 0x7f, {0x0d9,-109,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}}, + {0x01, 0x27, {0x0d3,9088,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x28, 0x2d, {0x0da,8329,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x2e, 0x33, {0x0d4,7552,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x34, 0x39, {0x0db,6779,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x3a, 0x3f, {0x0d5,6036,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x40, 0x45, {0x0dc,5251,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x46, 0x4b, {0x0d6,4487,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x4c, 0x51, {0x0dd,3735,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x52, 0x57, {0x0d7,3006,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x58, 0x5d, {0x0de,2161,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x5e, 0x63, {0x0d8,1415,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}, + {0x64, 0x7f, {0x0d9,-121,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}} +}; +static const struct opl4_region regions_60[] = { /* Ice Rain */ + {0x01, 0x7f, {0x04e,9345,100, 0,2,0x00,0xcc,0x22,0xa3,0x63,0x17,0x0}}, + {0x00, 0x7f, {0x143,5586, 20, 0,2,0x00,0x6e,0x2a,0xf0,0x05,0x05,0x0}} +}; +static const struct opl4_region regions_61[] = { /* Soundtrack */ + {0x15, 0x6c, {0x002,4501,100, 0,2,0x00,0xb6,0x2a,0x60,0x01,0x05,0x0}}, + {0x15, 0x6c, {0x0f3,1160,100, 0,5,0x00,0xa8,0x2d,0x52,0x14,0x06,0x2}} +}; +static const struct opl4_region regions_62[] = { /* Crystal */ + {0x15, 0x6c, {0x0f3,1826,100, 0,3,0x00,0xb8,0x33,0xf6,0x25,0x25,0x0}}, + {0x15, 0x2c, {0x06d,7454,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}}, + {0x2d, 0x36, {0x06e,5925,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}}, + {0x37, 0x6c, {0x06f,4403,100, 0,3,0x09,0xac,0x3b,0x85,0x24,0x06,0x0}} +}; +static const struct opl4_region regions_63[] = { /* Atmosphere */ + {0x05, 0x71, {0x002,4509,100, 0,2,0x00,0xc8,0x32,0x73,0x22,0x06,0x1}}, + {0x15, 0x2f, {0x0b3,6964,100, 0,2,0x05,0xc2,0x32,0xf5,0x34,0x07,0x2}}, + {0x30, 0x36, {0x0b7,5567,100, 0,2,0x0c,0xc2,0x32,0xf5,0x34,0x07,0x2}}, + {0x37, 0x3c, {0x0b5,4653,100, 0,2,0x00,0xc2,0x32,0xf6,0x34,0x07,0x2}}, + {0x3d, 0x43, {0x0b4,3892,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x07,0x2}}, + {0x44, 0x60, {0x0b6,2723,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x17,0x2}} +}; +static const struct opl4_region regions_64[] = { /* Brightness */ + {0x00, 0x7f, {0x137,5285,100, 0,2,0x00,0xbe,0x2a,0xa5,0x18,0x08,0x0}}, + {0x15, 0x6c, {0x02a,3481,100, 0,1,0x00,0xc8,0x29,0x80,0x05,0x05,0x0}} +}; +static const struct opl4_region regions_65[] = { /* Goblins */ + {0x15, 0x6c, {0x002,4501,100,-1,2,0x00,0xca,0x2a,0x40,0x01,0x05,0x0}}, + {0x15, 0x6c, {0x009,9679, 20, 1,4,0x00,0x3c,0x0c,0x22,0x11,0x06,0x0}} +}; +static const struct opl4_region regions_66[] = { /* Echoes */ + {0x15, 0x6c, {0x02a,3487,100, 0,3,0x00,0xae,0x2b,0xf5,0x21,0x06,0x0}}, + {0x00, 0x7f, {0x124,4027,100, 0,3,0x00,0xae,0x2b,0x85,0x23,0x07,0x0}} +}; +static const struct opl4_region regions_67[] = { /* Sci-Fi */ + {0x15, 0x31, {0x00c,6940,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}}, + {0x32, 0x38, {0x00d,5413,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}}, + {0x39, 0x47, {0x00e,4382,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}}, + {0x48, 0x6c, {0x00f,2846,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}}, + {0x15, 0x6c, {0x002,4498,100, 0,2,0x00,0xd4,0x22,0x80,0x01,0x05,0x0}} +}; +static const struct opl4_region regions_68[] = { /* Sitar */ + {0x00, 0x7f, {0x10f,4408,100, 0,2,0x00,0xc4,0x32,0xf4,0x15,0x16,0x1}} +}; +static const struct opl4_region regions_69[] = { /* Banjo */ + {0x15, 0x34, {0x013,5685,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}, + {0x35, 0x38, {0x014,5009,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}, + {0x39, 0x3c, {0x012,4520,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}, + {0x3d, 0x44, {0x015,3622,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}, + {0x45, 0x4c, {0x017,2661,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}, + {0x4d, 0x6d, {0x016,1632,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}} +}; +static const struct opl4_region regions_6a[] = { /* Shamisen */ + {0x15, 0x6c, {0x10e,3273,100, 0,0,0x00,0xc0,0x28,0xf7,0x76,0x08,0x0}} +}; +static const struct opl4_region regions_6b[] = { /* Koto */ + {0x00, 0x7f, {0x0a9,4033,100, 0,0,0x00,0xc6,0x20,0xf0,0x06,0x07,0x0}} +}; +static const struct opl4_region regions_6c[] = { /* Kalimba */ + {0x00, 0x7f, {0x137,3749,100, 0,0,0x00,0xce,0x38,0xf5,0x18,0x08,0x0}} +}; +static const struct opl4_region regions_6d[] = { /* Bagpipe */ + {0x15, 0x39, {0x0a4,7683,100, 0,4,0x00,0xc0,0x1c,0xf0,0x00,0x09,0x0}}, + {0x15, 0x39, {0x0a7,7680,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}}, + {0x3a, 0x6c, {0x0a8,3697,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_6e[] = { /* Fiddle */ + {0x15, 0x3a, {0x105,5158,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x3b, 0x3f, {0x102,4754,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x40, 0x41, {0x106,4132,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x42, 0x44, {0x107,4033,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x45, 0x47, {0x108,3580,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x48, 0x4a, {0x10a,2957,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x4b, 0x4c, {0x10b,2724,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x4d, 0x4e, {0x10c,2530,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x4f, 0x51, {0x10d,2166,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}, + {0x52, 0x6c, {0x109,1825,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}} +}; +static const struct opl4_region regions_6f[] = { /* Shanai */ + {0x15, 0x6c, {0x041,6946,100, 0,1,0x00,0xc4,0x31,0x95,0x20,0x09,0x0}} +}; +static const struct opl4_region regions_70[] = { /* Tinkle Bell */ + {0x15, 0x73, {0x0f3,1821,100, 0,3,0x00,0xc8,0x3b,0xd6,0x25,0x25,0x0}}, + {0x00, 0x7f, {0x137,5669,100, 0,3,0x00,0x66,0x3b,0xf5,0x18,0x08,0x0}} +}; +static const struct opl4_region regions_71[] = { /* Agogo */ + {0x15, 0x74, {0x00b,2474,100, 0,0,0x00,0xd2,0x38,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_72[] = { /* Steel Drums */ + {0x01, 0x7f, {0x0fe,3670,100, 0,0,0x00,0xca,0x38,0xf3,0x06,0x17,0x1}}, + {0x15, 0x6c, {0x100,9602,100, 0,0,0x00,0x54,0x38,0xb0,0x05,0x16,0x1}} +}; +static const struct opl4_region regions_73[] = { /* Woodblock */ + {0x15, 0x6c, {0x02c,2963, 50, 0,0,0x07,0xd4,0x00,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_74[] = { /* Taiko Drum */ + {0x13, 0x6c, {0x03e,1194, 50, 0,0,0x00,0xaa,0x38,0xf0,0x04,0x04,0x0}} +}; +static const struct opl4_region regions_75[] = { /* Melodic Tom */ + {0x15, 0x6c, {0x0c7,6418, 50, 0,0,0x00,0xe4,0x38,0xf0,0x05,0x01,0x0}} +}; +static const struct opl4_region regions_76[] = { /* Synth Drum */ + {0x15, 0x6c, {0x026,3898, 50, 0,0,0x00,0xd0,0x38,0xf0,0x04,0x04,0x0}} +}; +static const struct opl4_region regions_77[] = { /* Reverse Cymbal */ + {0x15, 0x6c, {0x031,4138, 50, 0,0,0x00,0xfe,0x38,0x3a,0xf0,0x09,0x0}} +}; +static const struct opl4_region regions_78[] = { /* Guitar Fret Noise */ + {0x15, 0x6c, {0x138,5266,100, 0,0,0x00,0xa0,0x38,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_79[] = { /* Breath Noise */ + {0x01, 0x7f, {0x125,4269,100, 0,0,0x1e,0xd0,0x38,0xf0,0x00,0x09,0x0}} +}; +static const struct opl4_region regions_7a[] = { /* Seashore */ + {0x15, 0x6c, {0x008,2965, 20,-2,0,0x00,0xfe,0x00,0x20,0x03,0x04,0x0}}, + {0x01, 0x7f, {0x037,4394, 20, 2,0,0x14,0xfe,0x00,0x20,0x04,0x05,0x0}} +}; +static const struct opl4_region regions_7b[] = { /* Bird Tweet */ + {0x15, 0x6c, {0x009,8078, 5,-4,7,0x00,0xc2,0x0f,0x22,0x12,0x07,0x0}}, + {0x15, 0x6c, {0x009,3583, 5, 4,5,0x00,0xae,0x15,0x72,0x12,0x07,0x0}} +}; +static const struct opl4_region regions_7c[] = { /* Telephone Ring */ + {0x15, 0x6c, {0x003,3602, 10, 0,0,0x00,0xce,0x00,0xf0,0x00,0x0f,0x0}} +}; +static const struct opl4_region regions_7d[] = { /* Helicopter */ + {0x0c, 0x7f, {0x001,2965, 10,-2,0,0x00,0xe0,0x08,0x30,0x01,0x07,0x0}}, + {0x01, 0x7f, {0x037,4394, 10, 2,0,0x44,0x76,0x00,0x30,0x01,0x07,0x0}} +}; +static const struct opl4_region regions_7e[] = { /* Applause */ + {0x15, 0x6c, {0x036,8273, 20,-6,7,0x00,0xc4,0x0f,0x70,0x01,0x05,0x0}}, + {0x15, 0x6c, {0x036,8115, 5, 6,7,0x00,0xc6,0x07,0x70,0x01,0x05,0x0}} +}; +static const struct opl4_region regions_7f[] = { /* Gun Shot */ + {0x15, 0x6c, {0x139,2858, 20, 0,0,0x00,0xbe,0x38,0xf0,0x03,0x00,0x0}} +}; +static const struct opl4_region regions_drums[] = { + {0x18, 0x18, {0x0cb,6397,100, 3,0,0x00,0xf4,0x38,0xc9,0x1c,0x0c,0x0}}, + {0x19, 0x19, {0x0c4,3714,100, 0,0,0x00,0xe0,0x00,0x97,0x19,0x09,0x0}}, + {0x1a, 0x1a, {0x0c4,3519,100, 0,0,0x00,0xea,0x00,0x61,0x01,0x07,0x0}}, + {0x1b, 0x1b, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0xf7,0x19,0x09,0x0}}, + {0x1c, 0x1c, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0x81,0x01,0x07,0x0}}, + {0x1e, 0x1e, {0x0c3,4783,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x1f, 0x1f, {0x0d1,4042,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}}, + {0x20, 0x20, {0x0d2,5943,100, 0,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}}, + {0x21, 0x21, {0x011,3842,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}}, + {0x23, 0x23, {0x011,4098,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}}, + {0x24, 0x24, {0x011,4370,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x06,0x0}}, + {0x25, 0x25, {0x0d2,4404,100, 0,0,0x00,0xd6,0x00,0xf0,0x00,0x06,0x0}}, + {0x26, 0x26, {0x0d1,4298,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}}, + {0x27, 0x27, {0x00a,4403,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}, + {0x28, 0x28, {0x0d1,4554,100, 0,0,0x00,0xdc,0x00,0xf0,0x07,0x07,0x0}}, + {0x29, 0x29, {0x0c8,4242,100,-4,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}}, + {0x2a, 0x2a, {0x079,6160,100, 2,0,0x00,0xe0,0x00,0xf5,0x19,0x09,0x0}}, + {0x2b, 0x2b, {0x0c8,4626,100,-3,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}}, + {0x2c, 0x2c, {0x07b,6039,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}, + {0x2d, 0x2d, {0x0c8,5394,100,-2,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}}, + {0x2e, 0x2e, {0x07a,5690,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x05,0x0}}, + {0x2f, 0x2f, {0x0c7,5185,100, 2,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}}, + {0x30, 0x30, {0x0c7,5650,100, 3,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}}, + {0x31, 0x31, {0x031,4395,100, 2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}}, + {0x32, 0x32, {0x0c7,6162,100, 4,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}}, + {0x33, 0x33, {0x02e,4391,100,-2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}}, + {0x34, 0x34, {0x07a,3009,100,-2,0,0x00,0xea,0x00,0xf2,0x15,0x05,0x0}}, + {0x35, 0x35, {0x021,4522,100,-3,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}}, + {0x36, 0x36, {0x025,5163,100, 1,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}}, + {0x37, 0x37, {0x031,5287,100,-1,0,0x00,0xea,0x00,0xf5,0x16,0x06,0x0}}, + {0x38, 0x38, {0x01d,4395,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}}, + {0x39, 0x39, {0x031,4647,100,-2,0,0x00,0xea,0x00,0xf4,0x16,0x06,0x0}}, + {0x3a, 0x3a, {0x09d,4426,100,-4,0,0x00,0xe0,0x00,0xf4,0x17,0x07,0x0}}, + {0x3b, 0x3b, {0x02e,4659,100,-2,0,0x00,0xea,0x00,0xf0,0x06,0x06,0x0}}, + {0x3c, 0x3c, {0x01c,4769,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x3d, 0x3d, {0x01c,4611,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x3e, 0x3e, {0x01e,4402,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x3f, 0x3f, {0x01f,4387,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x40, 0x40, {0x01f,3983,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x41, 0x41, {0x09c,4526,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x42, 0x42, {0x09c,4016,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x43, 0x43, {0x00b,4739,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x44, 0x44, {0x00b,4179,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x45, 0x45, {0x02f,4787,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}, + {0x46, 0x46, {0x030,4665,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}, + {0x47, 0x47, {0x144,4519,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}}, + {0x48, 0x48, {0x144,4111,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}}, + {0x49, 0x49, {0x024,6408,100, 3,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}}, + {0x4a, 0x4a, {0x024,4144,100, 3,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}}, + {0x4b, 0x4b, {0x020,4001,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}}, + {0x4c, 0x4c, {0x02c,4402,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x4d, 0x4d, {0x02c,3612,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x4e, 0x4e, {0x022,4129,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x4f, 0x4f, {0x023,4147,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}}, + {0x50, 0x50, {0x032,4412,100,-4,0,0x00,0xd6,0x00,0xf0,0x08,0x09,0x0}}, + {0x51, 0x51, {0x032,4385,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}, + {0x52, 0x52, {0x02f,5935,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}} +}; + +#define REGION(num) { ARRAY_SIZE(regions ## num), regions ## num } +const struct opl4_region_ptr snd_yrw801_regions[0x81] = { + REGION(_00), REGION(_01), REGION(_02), REGION(_03), + REGION(_04), REGION(_05), REGION(_06), REGION(_07), + REGION(_08), REGION(_09), REGION(_0a), REGION(_0b), + REGION(_0c), REGION(_0d), REGION(_0e), REGION(_0f), + REGION(_10), REGION(_11), REGION(_12), REGION(_13), + REGION(_14), REGION(_15), REGION(_16), REGION(_17), + REGION(_18), REGION(_19), REGION(_1a), REGION(_1b), + REGION(_1c), REGION(_1d), REGION(_1e), REGION(_1f), + REGION(_20), REGION(_21), REGION(_22), REGION(_23), + REGION(_24), REGION(_25), REGION(_26), REGION(_27), + REGION(_28), REGION(_29), REGION(_2a), REGION(_2b), + REGION(_2c), REGION(_2d), REGION(_2e), REGION(_2f), + REGION(_30), REGION(_31), REGION(_32), REGION(_33), + REGION(_34), REGION(_35), REGION(_36), REGION(_37), + REGION(_38), REGION(_39), REGION(_3a), REGION(_3b), + REGION(_3c), REGION(_3d), REGION(_3e), REGION(_3f), + REGION(_40), REGION(_41), REGION(_42), REGION(_43), + REGION(_44), REGION(_45), REGION(_46), REGION(_47), + REGION(_48), REGION(_49), REGION(_4a), REGION(_4b), + REGION(_4c), REGION(_4d), REGION(_4e), REGION(_4f), + REGION(_50), REGION(_51), REGION(_52), REGION(_53), + REGION(_54), REGION(_55), REGION(_56), REGION(_57), + REGION(_58), REGION(_59), REGION(_5a), REGION(_5b), + REGION(_5c), REGION(_5d), REGION(_5e), REGION(_5f), + REGION(_60), REGION(_61), REGION(_62), REGION(_63), + REGION(_64), REGION(_65), REGION(_66), REGION(_67), + REGION(_68), REGION(_69), REGION(_6a), REGION(_6b), + REGION(_6c), REGION(_6d), REGION(_6e), REGION(_6f), + REGION(_70), REGION(_71), REGION(_72), REGION(_73), + REGION(_74), REGION(_75), REGION(_76), REGION(_77), + REGION(_78), REGION(_79), REGION(_7a), REGION(_7b), + REGION(_7c), REGION(_7d), REGION(_7e), REGION(_7f), + REGION(_drums) +}; diff --git a/sound/drivers/pcm-indirect2.c b/sound/drivers/pcm-indirect2.c new file mode 100644 index 000000000..d16bc14a0 --- /dev/null +++ b/sound/drivers/pcm-indirect2.c @@ -0,0 +1,573 @@ +/* + * Helper functions for indirect PCM data transfer to a simple FIFO in + * hardware (small, no possibility to read "hardware io position", + * updating position done by interrupt, ...) + * + * Copyright (c) by 2007 Joachim Foerster + * + * Based on "pcm-indirect.h" (alsa-driver-1.0.13) by + * + * Copyright (c) by Takashi Iwai + * Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* snd_printk/d() */ +#include +/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t + * snd_pcm_period_elapsed() */ +#include + +#include "pcm-indirect2.h" + +#ifdef SND_PCM_INDIRECT2_STAT +/* jiffies */ +#include + +void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int i; + int j; + int k; + int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ; + + snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, " + "irq_occurred: %d\n", + rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured); + snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n", + rec->min_multiple); + snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, " + "firstzerotime: %lu\n", + rec->firstbytetime, rec->lastbytetime, rec->firstzerotime); + snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) " + "length: %d s\n", + rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate); + snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => " + "rate: %d Bytes/s = %d Frames/s|Hz\n", + seconds, rec->bytes2hw / seconds, + rec->bytes2hw / 2 / 2 / seconds); + snd_printk(KERN_DEBUG + "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n", + rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) / + runtime->rate, + rec->zeros2hw / (rec->hw_buffer_size / 2), + (rec->hw_buffer_size / 2)); + snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n", + rec->pointer_calls, rec->lastdifftime); + snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io, + rec->sw_data); + snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n"); + k = 0; + for (j = 0; j < 8; j++) { + for (i = j * 8; i < (j + 1) * 8; i++) + if (rec->byte_sizes[i] != 0) { + snd_printk(KERN_DEBUG "%u: %u", + i, rec->byte_sizes[i]); + k++; + } + if (((k % 8) == 0) && (k != 0)) { + snd_printk(KERN_DEBUG "\n"); + k = 0; + } + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n"); + for (j = 0; j < 8; j++) { + k = 0; + for (i = j * 8; i < (j + 1) * 8; i++) + if (rec->zero_sizes[i] != 0) + snd_printk(KERN_DEBUG "%u: %u", + i, rec->zero_sizes[i]); + else + k++; + if (!k) + snd_printk(KERN_DEBUG "\n"); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: min_adds[]:\n"); + for (j = 0; j < 8; j++) { + if (rec->min_adds[j] != 0) + snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n"); + for (j = 0; j < 8; j++) { + if (rec->mul_adds[j] != 0) + snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG + "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n", + rec->zero_times_saved, rec->zero_times_notsaved); + /* snd_printk(KERN_DEBUG "STAT: zero_times[]\n"); + i = 0; + for (j = 0; j < 3750; j++) { + if (rec->zero_times[j] != 0) { + snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]); + i++; + } + if (((i % 8) == 0) && (i != 0)) + snd_printk(KERN_DEBUG "\n"); + } + snd_printk(KERN_DEBUG "\n"); */ + return; +} +#endif + +/* + * _internal_ helper function for playback/capture transfer function + */ +static void +snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + int isplay, int iscopy, + unsigned int bytes) +{ + if (rec->min_periods >= 0) { + if (iscopy) { + rec->sw_io += bytes; + if (rec->sw_io >= rec->sw_buffer_size) + rec->sw_io -= rec->sw_buffer_size; + } else if (isplay) { + /* If application does not write data in multiples of + * a period, move sw_data to the next correctly aligned + * position, so that sw_io can converge to it (in the + * next step). + */ + if (!rec->check_alignment) { + if (rec->bytes2hw % + snd_pcm_lib_period_bytes(substream)) { + unsigned bytes2hw_aligned = + (1 + + (rec->bytes2hw / + snd_pcm_lib_period_bytes + (substream))) * + snd_pcm_lib_period_bytes + (substream); + rec->sw_data = + bytes2hw_aligned % + rec->sw_buffer_size; +#ifdef SND_PCM_INDIRECT2_STAT + snd_printk(KERN_DEBUG + "STAT: @re-align: aligned " + "bytes2hw to next period " + "size boundary: %d " + "(instead of %d)\n", + bytes2hw_aligned, + rec->bytes2hw); + snd_printk(KERN_DEBUG + "STAT: @re-align: sw_data " + "moves to: %d\n", + rec->sw_data); +#endif + } + rec->check_alignment = 1; + } + /* We are at the end and are copying zeros into the + * fifo. + * Now, we have to make sure that sw_io is increased + * until the position of sw_data: Filling the fifo with + * the first zeros means, the last bytes were played. + */ + if (rec->sw_io != rec->sw_data) { + unsigned int diff; + if (rec->sw_data > rec->sw_io) + diff = rec->sw_data - rec->sw_io; + else + diff = (rec->sw_buffer_size - + rec->sw_io) + + rec->sw_data; + if (bytes >= diff) + rec->sw_io = rec->sw_data; + else { + rec->sw_io += bytes; + if (rec->sw_io >= rec->sw_buffer_size) + rec->sw_io -= + rec->sw_buffer_size; + } + } + } + rec->min_period_count += bytes; + if (rec->min_period_count >= (rec->hw_buffer_size / 2)) { + rec->min_periods += (rec->min_period_count / + (rec->hw_buffer_size / 2)); +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_period_count / + (rec->hw_buffer_size / 2)) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) min_adds " + "at once - too big to save!\n", + (rec->min_period_count / + (rec->hw_buffer_size / 2))); + else + rec->min_adds[(rec->min_period_count / + (rec->hw_buffer_size / 2))]++; +#endif + rec->min_period_count = (rec->min_period_count % + (rec->hw_buffer_size / 2)); + } + } else if (isplay && iscopy) + rec->min_periods = 0; +} + +/* + * helper function for playback/capture pointer callback + */ +snd_pcm_uframes_t +snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->pointer_calls++; +#endif + return bytes_to_frames(substream->runtime, rec->sw_io); +} + +/* + * _internal_ helper function for playback interrupt callback + */ +static void +snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + + /* runtime->control->appl_ptr: position where ALSA will write next time + * rec->appl_ptr: position where ALSA was last time + * diff: obviously ALSA wrote that much bytes into the intermediate + * buffer since we checked last time + */ + snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr; + + if (diff) { +#ifdef SND_PCM_INDIRECT2_STAT + rec->lastdifftime = jiffies; +#endif + if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2)) + diff += runtime->boundary; + /* number of bytes "added" by ALSA increases the number of + * bytes which are ready to "be transferred to HW"/"played" + * Then, set rec->appl_ptr to not count bytes twice next time. + */ + rec->sw_ready += (int)frames_to_bytes(runtime, diff); + rec->appl_ptr = appl_ptr; + } + if (rec->hw_ready && (rec->sw_ready <= 0)) { + unsigned int bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstzerotime == 0) { + rec->firstzerotime = jiffies; + snd_printk(KERN_DEBUG + "STAT: @firstzerotime: mul_elapsed: %d, " + "min_period_count: %d\n", + rec->mul_elapsed, rec->min_period_count); + snd_printk(KERN_DEBUG + "STAT: @firstzerotime: sw_io: %d, " + "sw_data: %d, appl_ptr: %u\n", + rec->sw_io, rec->sw_data, + (unsigned int)appl_ptr); + } + if ((jiffies - rec->firstzerotime) < 3750) { + rec->zero_times[(jiffies - rec->firstzerotime)]++; + rec->zero_times_saved++; + } else + rec->zero_times_notsaved++; +#endif + bytes = zero(substream, rec); + +#ifdef SND_PCM_INDIRECT2_STAT + rec->zeros2hw += bytes; + if (bytes < 64) + rec->zero_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: %d zero Bytes copied to hardware at " + "once - too big to save!\n", + bytes); +#endif + snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0, + bytes); + return; + } + while (rec->hw_ready && (rec->sw_ready > 0)) { + /* sw_to_end: max. number of bytes that can be read/take from + * the current position (sw_data) in _one_ step + */ + unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data; + + /* bytes: number of bytes we have available (for reading) */ + unsigned int bytes = rec->sw_ready; + + if (sw_to_end < bytes) + bytes = sw_to_end; + if (!bytes) + break; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstbytetime == 0) + rec->firstbytetime = jiffies; + rec->lastbytetime = jiffies; +#endif + /* copy bytes from intermediate buffer position sw_data to the + * HW and return number of bytes actually written + * Furthermore, set hw_ready to 0, if the fifo isn't empty + * now => more could be transferred to fifo + */ + bytes = copy(substream, rec, bytes); + rec->bytes2hw += bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (bytes < 64) + rec->byte_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: %d Bytes copied to hardware at once " + "- too big to save!\n", + bytes); +#endif + /* increase sw_data by the number of actually written bytes + * (= number of taken bytes from intermediate buffer) + */ + rec->sw_data += bytes; + if (rec->sw_data == rec->sw_buffer_size) + rec->sw_data = 0; + /* now sw_data is the position where ALSA is going to write + * in the intermediate buffer next time = position we are going + * to read from next time + */ + + snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1, + bytes); + + /* we read bytes from intermediate buffer, so we need to say + * that the number of bytes ready for transfer are decreased + * now + */ + rec->sw_ready -= bytes; + } + return; +} + +/* + * helper function for playback interrupt routine + */ +void +snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->irq_occured++; +#endif + /* hardware played some bytes, so there is room again (in fifo) */ + rec->hw_ready = 1; + + /* don't call ack() now, instead call transfer() function directly + * (normally called by ack() ) + */ + snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero); + + if (rec->min_periods >= rec->min_multiple) { +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_periods / rec->min_multiple) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) mul_adds - too big " + "to save!\n", + (rec->min_periods / rec->min_multiple)); + else + rec->mul_adds[(rec->min_periods / + rec->min_multiple)]++; + rec->mul_elapsed_real += (rec->min_periods / + rec->min_multiple); + rec->mul_elapsed++; +#endif + rec->min_periods = (rec->min_periods % rec->min_multiple); + snd_pcm_period_elapsed(substream); + } +} + +/* + * _internal_ helper function for capture interrupt callback + */ +static void +snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr; + + if (diff) { +#ifdef SND_PCM_INDIRECT2_STAT + rec->lastdifftime = jiffies; +#endif + if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2)) + diff += runtime->boundary; + rec->sw_ready -= frames_to_bytes(runtime, diff); + rec->appl_ptr = appl_ptr; + } + /* if hardware has something, but the intermediate buffer is full + * => skip contents of buffer + */ + if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) { + unsigned int bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstzerotime == 0) { + rec->firstzerotime = jiffies; + snd_printk(KERN_DEBUG "STAT: (capture) " + "@firstzerotime: mul_elapsed: %d, " + "min_period_count: %d\n", + rec->mul_elapsed, rec->min_period_count); + snd_printk(KERN_DEBUG "STAT: (capture) " + "@firstzerotime: sw_io: %d, sw_data: %d, " + "appl_ptr: %u\n", + rec->sw_io, rec->sw_data, + (unsigned int)appl_ptr); + } + if ((jiffies - rec->firstzerotime) < 3750) { + rec->zero_times[(jiffies - rec->firstzerotime)]++; + rec->zero_times_saved++; + } else + rec->zero_times_notsaved++; +#endif + bytes = null(substream, rec); + +#ifdef SND_PCM_INDIRECT2_STAT + rec->zeros2hw += bytes; + if (bytes < 64) + rec->zero_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: (capture) %d zero Bytes copied to " + "hardware at once - too big to save!\n", + bytes); +#endif + snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0, + bytes); + /* report an overrun */ + rec->sw_io = SNDRV_PCM_POS_XRUN; + return; + } + while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) { + /* sw_to_end: max. number of bytes that we can write to the + * intermediate buffer (until it's end) + */ + size_t sw_to_end = rec->sw_buffer_size - rec->sw_data; + + /* bytes: max. number of bytes, which may be copied to the + * intermediate buffer without overflow (in _one_ step) + */ + size_t bytes = rec->sw_buffer_size - rec->sw_ready; + + /* limit number of bytes (for transfer) by available room in + * the intermediate buffer + */ + if (sw_to_end < bytes) + bytes = sw_to_end; + if (!bytes) + break; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstbytetime == 0) + rec->firstbytetime = jiffies; + rec->lastbytetime = jiffies; +#endif + /* copy bytes from the intermediate buffer (position sw_data) + * to the HW at most and return number of bytes actually copied + * from HW + * Furthermore, set hw_ready to 0, if the fifo is empty now. + */ + bytes = copy(substream, rec, bytes); + rec->bytes2hw += bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (bytes < 64) + rec->byte_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: (capture) %d Bytes copied to " + "hardware at once - too big to save!\n", + bytes); +#endif + /* increase sw_data by the number of actually copied bytes from + * HW + */ + rec->sw_data += bytes; + if (rec->sw_data == rec->sw_buffer_size) + rec->sw_data = 0; + + snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1, + bytes); + + /* number of bytes in the intermediate buffer, which haven't + * been fetched by ALSA yet. + */ + rec->sw_ready += bytes; + } + return; +} + +/* + * helper function for capture interrupt routine + */ +void +snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->irq_occured++; +#endif + /* hardware recorded some bytes, so there is something to read from the + * record fifo: + */ + rec->hw_ready = 1; + + /* don't call ack() now, instead call transfer() function directly + * (normally called by ack() ) + */ + snd_pcm_indirect2_capture_transfer(substream, rec, copy, null); + + if (rec->min_periods >= rec->min_multiple) { + +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_periods / rec->min_multiple) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) mul_adds - " + "too big to save!\n", + (rec->min_periods / rec->min_multiple)); + else + rec->mul_adds[(rec->min_periods / + rec->min_multiple)]++; + rec->mul_elapsed_real += (rec->min_periods / + rec->min_multiple); + rec->mul_elapsed++; +#endif + rec->min_periods = (rec->min_periods % rec->min_multiple); + snd_pcm_period_elapsed(substream); + } +} diff --git a/sound/drivers/pcm-indirect2.h b/sound/drivers/pcm-indirect2.h new file mode 100644 index 000000000..2ea6e460f --- /dev/null +++ b/sound/drivers/pcm-indirect2.h @@ -0,0 +1,140 @@ +/* + * Helper functions for indirect PCM data transfer to a simple FIFO in + * hardware (small, no possibility to read "hardware io position", + * updating position done by interrupt, ...) + * + * Copyright (c) by 2007 Joachim Foerster + * + * Based on "pcm-indirect.h" (alsa-driver-1.0.13) by + * + * Copyright (c) by Takashi Iwai + * Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SOUND_PCM_INDIRECT2_H +#define __SOUND_PCM_INDIRECT2_H + +/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t */ +#include + +/* Debug options for code which may be removed completely in a final version */ +#ifdef CONFIG_SND_DEBUG +#define SND_PCM_INDIRECT2_STAT /* turn on some "statistics" about the + * process of copying bytes from the + * intermediate buffer to the hardware + * fifo and the other way round + */ +#endif + +struct snd_pcm_indirect2 { + unsigned int hw_buffer_size; /* Byte size of hardware buffer */ + int hw_ready; /* playback: 1 = hw fifo has room left, + * 0 = hw fifo is full + */ + unsigned int min_multiple; + int min_periods; /* counts number of min. periods until + * min_multiple is reached + */ + int min_period_count; /* counts bytes to count number of + * min. periods + */ + + unsigned int sw_buffer_size; /* Byte size of software buffer */ + + /* sw_data: position in intermediate buffer, where we will read (or + * write) from/to next time (to transfer data to/from HW) + */ + unsigned int sw_data; /* Offset to next dst (or src) in sw + * ring buffer + */ + /* easiest case (playback): + * sw_data is nearly the same as ~ runtime->control->appl_ptr, with the + * exception that sw_data is "behind" by the number if bytes ALSA wrote + * to the intermediate buffer last time. + * A call to ack() callback synchronizes both indirectly. + */ + + /* We have no real sw_io pointer here. Usually sw_io is pointing to the + * current playback/capture position _inside_ the hardware. Devices + * with plain FIFOs often have no possibility to publish this position. + * So we say: if sw_data is updated, that means bytes were copied to + * the hardware, we increase sw_io by that amount, because there have + * to be as much bytes which were played. So sw_io will stay behind + * sw_data all the time and has to converge to sw_data at the end of + * playback. + */ + unsigned int sw_io; /* Current software pointer in bytes */ + + /* sw_ready: number of bytes ALSA copied to the intermediate buffer, so + * it represents the number of bytes which wait for transfer to the HW + */ + int sw_ready; /* Bytes ready to be transferred to/from hw */ + + /* appl_ptr: last known position of ALSA (where ALSA is going to write + * next time into the intermediate buffer + */ + snd_pcm_uframes_t appl_ptr; /* Last seen appl_ptr */ + + unsigned int bytes2hw; + int check_alignment; + +#ifdef SND_PCM_INDIRECT2_STAT + unsigned int zeros2hw; + unsigned int mul_elapsed; + unsigned int mul_elapsed_real; + unsigned long firstbytetime; + unsigned long lastbytetime; + unsigned long firstzerotime; + unsigned int byte_sizes[64]; + unsigned int zero_sizes[64]; + unsigned int min_adds[8]; + unsigned int mul_adds[8]; + unsigned int zero_times[3750]; /* = 15s */ + unsigned int zero_times_saved; + unsigned int zero_times_notsaved; + unsigned int irq_occured; + unsigned int pointer_calls; + unsigned int lastdifftime; +#endif +}; + +typedef size_t (*snd_pcm_indirect2_copy_t) (struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + size_t bytes); +typedef size_t (*snd_pcm_indirect2_zero_t) (struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec); + +#ifdef SND_PCM_INDIRECT2_STAT +void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec); +#endif + +snd_pcm_uframes_t +snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec); +void +snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero); +void +snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null); + +#endif /* __SOUND_PCM_INDIRECT2_H */ diff --git a/sound/drivers/pcsp/Makefile b/sound/drivers/pcsp/Makefile new file mode 100644 index 000000000..b19555b44 --- /dev/null +++ b/sound/drivers/pcsp/Makefile @@ -0,0 +1,2 @@ +snd-pcsp-objs := pcsp.o pcsp_lib.o pcsp_mixer.o pcsp_input.o +obj-$(CONFIG_SND_PCSP) += snd-pcsp.o diff --git a/sound/drivers/pcsp/pcsp.c b/sound/drivers/pcsp/pcsp.c new file mode 100644 index 000000000..0dd3f46eb --- /dev/null +++ b/sound/drivers/pcsp/pcsp.c @@ -0,0 +1,239 @@ +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 1997-2001 David Woodhouse + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcsp_input.h" +#include "pcsp.h" + +MODULE_AUTHOR("Stas Sergeev "); +MODULE_DESCRIPTION("PC-Speaker driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}"); +MODULE_ALIAS("platform:pcspkr"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ +static bool nopcm; /* Disable PCM capability of the driver */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for pcsp soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for pcsp soundcard."); +module_param(enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable PC-Speaker sound."); +module_param(nopcm, bool, 0444); +MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain."); + +struct snd_pcsp pcsp_chip; + +static int snd_pcsp_create(struct snd_card *card) +{ + static struct snd_device_ops ops = { }; + unsigned int resolution = hrtimer_resolution; + int err, div, min_div, order; + + if (!nopcm) { + if (resolution > PCSP_MAX_PERIOD_NS) { + printk(KERN_ERR "PCSP: Timer resolution is not sufficient " + "(%unS)\n", resolution); + printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI " + "enabled.\n"); + printk(KERN_ERR "PCSP: Turned into nopcm mode.\n"); + nopcm = 1; + } + } + + if (loops_per_jiffy >= PCSP_MIN_LPJ && resolution <= PCSP_MIN_PERIOD_NS) + min_div = MIN_DIV; + else + min_div = MAX_DIV; +#if PCSP_DEBUG + printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%u\n", + loops_per_jiffy, min_div, resolution); +#endif + + div = MAX_DIV / min_div; + order = fls(div) - 1; + + pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE); + pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE); + pcsp_chip.playback_ptr = 0; + pcsp_chip.period_ptr = 0; + atomic_set(&pcsp_chip.timer_active, 0); + pcsp_chip.enable = 1; + pcsp_chip.pcspkr = 1; + + spin_lock_init(&pcsp_chip.substream_lock); + + pcsp_chip.card = card; + pcsp_chip.port = 0x61; + pcsp_chip.irq = -1; + pcsp_chip.dma = -1; + + /* Register device */ + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops); + if (err < 0) + return err; + + return 0; +} + +static int snd_card_pcsp_probe(int devnum, struct device *dev) +{ + struct snd_card *card; + int err; + + if (devnum != 0) + return -EINVAL; + + hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pcsp_chip.timer.function = pcsp_do_timer; + + err = snd_card_new(dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + err = snd_pcsp_create(card); + if (err < 0) + goto free_card; + + if (!nopcm) { + err = snd_pcsp_new_pcm(&pcsp_chip); + if (err < 0) + goto free_card; + } + err = snd_pcsp_new_mixer(&pcsp_chip, nopcm); + if (err < 0) + goto free_card; + + strcpy(card->driver, "PC-Speaker"); + strcpy(card->shortname, "pcsp"); + sprintf(card->longname, "Internal PC-Speaker at port 0x%x", + pcsp_chip.port); + + err = snd_card_register(card); + if (err < 0) + goto free_card; + + return 0; + +free_card: + snd_card_free(card); + return err; +} + +static int alsa_card_pcsp_init(struct device *dev) +{ + int err; + + err = snd_card_pcsp_probe(0, dev); + if (err) { + printk(KERN_ERR "PC-Speaker initialization failed.\n"); + return err; + } + + /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */ + if (debug_pagealloc_enabled()) { + printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, " + "which may make the sound noisy.\n"); + } + + return 0; +} + +static void alsa_card_pcsp_exit(struct snd_pcsp *chip) +{ + snd_card_free(chip->card); +} + +static int pcsp_probe(struct platform_device *dev) +{ + int err; + + err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev); + if (err < 0) + return err; + + err = alsa_card_pcsp_init(&dev->dev); + if (err < 0) { + pcspkr_input_remove(pcsp_chip.input_dev); + return err; + } + + platform_set_drvdata(dev, &pcsp_chip); + return 0; +} + +static int pcsp_remove(struct platform_device *dev) +{ + struct snd_pcsp *chip = platform_get_drvdata(dev); + pcspkr_input_remove(chip->input_dev); + alsa_card_pcsp_exit(chip); + return 0; +} + +static void pcsp_stop_beep(struct snd_pcsp *chip) +{ + pcsp_sync_stop(chip); + pcspkr_stop_sound(); +} + +#ifdef CONFIG_PM_SLEEP +static int pcsp_suspend(struct device *dev) +{ + struct snd_pcsp *chip = dev_get_drvdata(dev); + pcsp_stop_beep(chip); + snd_pcm_suspend_all(chip->pcm); + return 0; +} + +static SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL); +#define PCSP_PM_OPS &pcsp_pm +#else +#define PCSP_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static void pcsp_shutdown(struct platform_device *dev) +{ + struct snd_pcsp *chip = platform_get_drvdata(dev); + pcsp_stop_beep(chip); +} + +static struct platform_driver pcsp_platform_driver = { + .driver = { + .name = "pcspkr", + .pm = PCSP_PM_OPS, + }, + .probe = pcsp_probe, + .remove = pcsp_remove, + .shutdown = pcsp_shutdown, +}; + +static int __init pcsp_init(void) +{ + if (!enable) + return -ENODEV; + return platform_driver_register(&pcsp_platform_driver); +} + +static void __exit pcsp_exit(void) +{ + platform_driver_unregister(&pcsp_platform_driver); +} + +module_init(pcsp_init); +module_exit(pcsp_exit); diff --git a/sound/drivers/pcsp/pcsp.h b/sound/drivers/pcsp/pcsp.h new file mode 100644 index 000000000..036ad3c99 --- /dev/null +++ b/sound/drivers/pcsp/pcsp.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 1993-1997 Michael Beck + * Copyright (C) 1997-2001 David Woodhouse + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#ifndef __PCSP_H__ +#define __PCSP_H__ + +#include +#include +#include + +#define PCSP_SOUND_VERSION 0x400 /* read 4.00 */ +#define PCSP_DEBUG 0 + +/* default timer freq for PC-Speaker: 18643 Hz */ +#define DIV_18KHZ 64 +#define MAX_DIV DIV_18KHZ +#define CALC_DIV(d) (MAX_DIV >> (d)) +#define CUR_DIV() CALC_DIV(chip->treble) +#define PCSP_MAX_TREBLE 1 + +/* unfortunately, with hrtimers 37KHz does not work very well :( */ +#define PCSP_DEFAULT_TREBLE 0 +#define MIN_DIV (MAX_DIV >> PCSP_MAX_TREBLE) + +/* wild guess */ +#define PCSP_MIN_LPJ 1000000 +#define PCSP_DEFAULT_SDIV (DIV_18KHZ >> 1) +#define PCSP_DEFAULT_SRATE (PIT_TICK_RATE / PCSP_DEFAULT_SDIV) +#define PCSP_INDEX_INC() (1 << (PCSP_MAX_TREBLE - chip->treble)) +#define PCSP_CALC_RATE(i) (PIT_TICK_RATE / CALC_DIV(i)) +#define PCSP_RATE() PCSP_CALC_RATE(chip->treble) +#define PCSP_MIN_RATE__1 MAX_DIV/PIT_TICK_RATE +#define PCSP_MAX_RATE__1 MIN_DIV/PIT_TICK_RATE +#define PCSP_MAX_PERIOD_NS (1000000000ULL * PCSP_MIN_RATE__1) +#define PCSP_MIN_PERIOD_NS (1000000000ULL * PCSP_MAX_RATE__1) +#define PCSP_CALC_NS(div) ({ \ + u64 __val = 1000000000ULL * (div); \ + do_div(__val, PIT_TICK_RATE); \ + __val; \ +}) +#define PCSP_PERIOD_NS() PCSP_CALC_NS(CUR_DIV()) + +#define PCSP_MAX_PERIOD_SIZE (64*1024) +#define PCSP_MAX_PERIODS 512 +#define PCSP_BUFFER_SIZE (128*1024) + +struct snd_pcsp { + struct snd_card *card; + struct snd_pcm *pcm; + struct input_dev *input_dev; + struct hrtimer timer; + unsigned short port, irq, dma; + spinlock_t substream_lock; + struct snd_pcm_substream *playback_substream; + unsigned int fmt_size; + unsigned int is_signed; + size_t playback_ptr; + size_t period_ptr; + atomic_t timer_active; + int thalf; + u64 ns_rem; + unsigned char val61; + int enable; + int max_treble; + int treble; + int pcspkr; +}; + +extern struct snd_pcsp pcsp_chip; + +extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle); +extern void pcsp_sync_stop(struct snd_pcsp *chip); + +extern int snd_pcsp_new_pcm(struct snd_pcsp *chip); +extern int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm); + +#endif diff --git a/sound/drivers/pcsp/pcsp_input.c b/sound/drivers/pcsp/pcsp_input.c new file mode 100644 index 000000000..bfc258119 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_input.c @@ -0,0 +1,117 @@ +/* + * PC Speaker beeper driver for Linux + * + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 1992 Orest Zborowski + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation + */ + +#include +#include +#include +#include "pcsp.h" +#include "pcsp_input.h" + +static void pcspkr_do_sound(unsigned int count) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&i8253_lock, flags); + + if (count) { + /* set command for counter 2, 2 byte write */ + outb_p(0xB6, 0x43); + /* select desired HZ */ + outb_p(count & 0xff, 0x42); + outb((count >> 8) & 0xff, 0x42); + /* enable counter 2 */ + outb_p(inb_p(0x61) | 3, 0x61); + } else { + /* disable counter 2 */ + outb(inb_p(0x61) & 0xFC, 0x61); + } + + raw_spin_unlock_irqrestore(&i8253_lock, flags); +} + +void pcspkr_stop_sound(void) +{ + pcspkr_do_sound(0); +} + +static int pcspkr_input_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + unsigned int count = 0; + + if (atomic_read(&pcsp_chip.timer_active) || !pcsp_chip.pcspkr) + return 0; + + switch (type) { + case EV_SND: + switch (code) { + case SND_BELL: + if (value) + value = 1000; + case SND_TONE: + break; + default: + return -1; + } + break; + + default: + return -1; + } + + if (value > 20 && value < 32767) + count = PIT_TICK_RATE / value; + + pcspkr_do_sound(count); + + return 0; +} + +int pcspkr_input_init(struct input_dev **rdev, struct device *dev) +{ + int err; + + struct input_dev *input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "PC Speaker"; + input_dev->phys = "isa0061/input0"; + input_dev->id.bustype = BUS_ISA; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = dev; + + input_dev->evbit[0] = BIT(EV_SND); + input_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE); + input_dev->event = pcspkr_input_event; + + err = input_register_device(input_dev); + if (err) { + input_free_device(input_dev); + return err; + } + + *rdev = input_dev; + return 0; +} + +int pcspkr_input_remove(struct input_dev *dev) +{ + pcspkr_stop_sound(); + input_unregister_device(dev); /* this also does kfree() */ + + return 0; +} diff --git a/sound/drivers/pcsp/pcsp_input.h b/sound/drivers/pcsp/pcsp_input.h new file mode 100644 index 000000000..e80079b38 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_input.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#ifndef __PCSP_INPUT_H__ +#define __PCSP_INPUT_H__ + +int pcspkr_input_init(struct input_dev **rdev, struct device *dev); +int pcspkr_input_remove(struct input_dev *dev); +void pcspkr_stop_sound(void); + +#endif diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c new file mode 100644 index 000000000..8f0f05bbc --- /dev/null +++ b/sound/drivers/pcsp/pcsp_lib.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 1993-1997 Michael Beck + * Copyright (C) 1997-2001 David Woodhouse + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#include +#include +#include +#include +#include +#include +#include "pcsp.h" + +static bool nforce_wa; +module_param(nforce_wa, bool, 0444); +MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround " + "(expect bad sound)"); + +#define DMIX_WANTS_S16 1 + +/* + * Call snd_pcm_period_elapsed in a tasklet + * This avoids spinlock messes and long-running irq contexts + */ +static void pcsp_call_pcm_elapsed(unsigned long priv) +{ + if (atomic_read(&pcsp_chip.timer_active)) { + struct snd_pcm_substream *substream; + substream = pcsp_chip.playback_substream; + if (substream) + snd_pcm_period_elapsed(substream); + } +} + +static DECLARE_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0); + +/* write the port and returns the next expire time in ns; + * called at the trigger-start and in hrtimer callback + */ +static u64 pcsp_timer_update(struct snd_pcsp *chip) +{ + unsigned char timer_cnt, val; + u64 ns; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned long flags; + + if (chip->thalf) { + outb(chip->val61, 0x61); + chip->thalf = 0; + return chip->ns_rem; + } + + substream = chip->playback_substream; + if (!substream) + return 0; + + runtime = substream->runtime; + /* assume it is mono! */ + val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1]; + if (chip->is_signed) + val ^= 0x80; + timer_cnt = val * CUR_DIV() / 256; + + if (timer_cnt && chip->enable) { + raw_spin_lock_irqsave(&i8253_lock, flags); + if (!nforce_wa) { + outb_p(chip->val61, 0x61); + outb_p(timer_cnt, 0x42); + outb(chip->val61 ^ 1, 0x61); + } else { + outb(chip->val61 ^ 2, 0x61); + chip->thalf = 1; + } + raw_spin_unlock_irqrestore(&i8253_lock, flags); + } + + chip->ns_rem = PCSP_PERIOD_NS(); + ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); + chip->ns_rem -= ns; + return ns; +} + +static void pcsp_pointer_update(struct snd_pcsp *chip) +{ + struct snd_pcm_substream *substream; + size_t period_bytes, buffer_bytes; + int periods_elapsed; + unsigned long flags; + + /* update the playback position */ + substream = chip->playback_substream; + if (!substream) + return; + + period_bytes = snd_pcm_lib_period_bytes(substream); + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + + spin_lock_irqsave(&chip->substream_lock, flags); + chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size; + periods_elapsed = chip->playback_ptr - chip->period_ptr; + if (periods_elapsed < 0) { +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? " + "(%zi %zi %zi)\n", + chip->playback_ptr, period_bytes, buffer_bytes); +#endif + periods_elapsed += buffer_bytes; + } + periods_elapsed /= period_bytes; + /* wrap the pointer _before_ calling snd_pcm_period_elapsed(), + * or ALSA will BUG on us. */ + chip->playback_ptr %= buffer_bytes; + + if (periods_elapsed) { + chip->period_ptr += periods_elapsed * period_bytes; + chip->period_ptr %= buffer_bytes; + } + spin_unlock_irqrestore(&chip->substream_lock, flags); + + if (periods_elapsed) + tasklet_schedule(&pcsp_pcm_tasklet); +} + +enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) +{ + struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); + int pointer_update; + u64 ns; + + if (!atomic_read(&chip->timer_active) || !chip->playback_substream) + return HRTIMER_NORESTART; + + pointer_update = !chip->thalf; + ns = pcsp_timer_update(chip); + if (!ns) { + printk(KERN_WARNING "PCSP: unexpected stop\n"); + return HRTIMER_NORESTART; + } + + if (pointer_update) + pcsp_pointer_update(chip); + + hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns)); + + return HRTIMER_RESTART; +} + +static int pcsp_start_playing(struct snd_pcsp *chip) +{ +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: start_playing called\n"); +#endif + if (atomic_read(&chip->timer_active)) { + printk(KERN_ERR "PCSP: Timer already active\n"); + return -EIO; + } + + raw_spin_lock(&i8253_lock); + chip->val61 = inb(0x61) | 0x03; + outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */ + raw_spin_unlock(&i8253_lock); + atomic_set(&chip->timer_active, 1); + chip->thalf = 0; + + hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL); + return 0; +} + +static void pcsp_stop_playing(struct snd_pcsp *chip) +{ +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: stop_playing called\n"); +#endif + if (!atomic_read(&chip->timer_active)) + return; + + atomic_set(&chip->timer_active, 0); + raw_spin_lock(&i8253_lock); + /* restore the timer */ + outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */ + outb(chip->val61 & 0xFC, 0x61); + raw_spin_unlock(&i8253_lock); +} + +/* + * Force to stop and sync the stream + */ +void pcsp_sync_stop(struct snd_pcsp *chip) +{ + local_irq_disable(); + pcsp_stop_playing(chip); + local_irq_enable(); + hrtimer_cancel(&chip->timer); + tasklet_kill(&pcsp_pcm_tasklet); +} + +static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: close called\n"); +#endif + pcsp_sync_stop(chip); + chip->playback_substream = NULL; + return 0; +} + +static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + int err; + pcsp_sync_stop(chip); + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + return 0; +} + +static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: hw_free called\n"); +#endif + pcsp_sync_stop(chip); + return snd_pcm_lib_free_pages(substream); +} + +static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + pcsp_sync_stop(chip); + chip->playback_ptr = 0; + chip->period_ptr = 0; + chip->fmt_size = + snd_pcm_format_physical_width(substream->runtime->format) >> 3; + chip->is_signed = snd_pcm_format_signed(substream->runtime->format); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: prepare called, " + "size=%zi psize=%zi f=%zi f1=%i fsize=%i\n", + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + snd_pcm_lib_buffer_bytes(substream) / + snd_pcm_lib_period_bytes(substream), + substream->runtime->periods, + chip->fmt_size); +#endif + return 0; +} + +static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: trigger called\n"); +#endif + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return pcsp_start_playing(chip); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + pcsp_stop_playing(chip); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream + *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + unsigned int pos; + spin_lock(&chip->substream_lock); + pos = chip->playback_ptr; + spin_unlock(&chip->substream_lock); + return bytes_to_frames(substream->runtime, pos); +} + +static const struct snd_pcm_hardware snd_pcsp_playback = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_HALF_DUPLEX | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 +#if DMIX_WANTS_S16 + | SNDRV_PCM_FMTBIT_S16_LE +#endif + ), + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = PCSP_DEFAULT_SRATE, + .rate_max = PCSP_DEFAULT_SRATE, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = PCSP_BUFFER_SIZE, + .period_bytes_min = 64, + .period_bytes_max = PCSP_MAX_PERIOD_SIZE, + .periods_min = 2, + .periods_max = PCSP_MAX_PERIODS, + .fifo_size = 0, +}; + +static int snd_pcsp_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: open called\n"); +#endif + if (atomic_read(&chip->timer_active)) { + printk(KERN_ERR "PCSP: still active!!\n"); + return -EBUSY; + } + runtime->hw = snd_pcsp_playback; + chip->playback_substream = substream; + return 0; +} + +static const struct snd_pcm_ops snd_pcsp_playback_ops = { + .open = snd_pcsp_playback_open, + .close = snd_pcsp_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_pcsp_playback_hw_params, + .hw_free = snd_pcsp_playback_hw_free, + .prepare = snd_pcsp_playback_prepare, + .trigger = snd_pcsp_trigger, + .pointer = snd_pcsp_playback_pointer, +}; + +int snd_pcsp_new_pcm(struct snd_pcsp *chip) +{ + int err; + + err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_pcsp_playback_ops); + + chip->pcm->private_data = chip; + chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + strcpy(chip->pcm->name, "pcsp"); + + snd_pcm_lib_preallocate_pages_for_all(chip->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), PCSP_BUFFER_SIZE, + PCSP_BUFFER_SIZE); + + return 0; +} diff --git a/sound/drivers/pcsp/pcsp_mixer.c b/sound/drivers/pcsp/pcsp_mixer.c new file mode 100644 index 000000000..be2990451 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_mixer.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PC-Speaker driver for Linux + * + * Mixer implementation. + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#include +#include +#include "pcsp.h" + + +static int pcsp_enable_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 pcsp_enable_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->enable; + return 0; +} + +static int pcsp_enable_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int enab = ucontrol->value.integer.value[0]; + if (enab != chip->enable) { + chip->enable = enab; + changed = 1; + } + return changed; +} + +static int pcsp_treble_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = chip->max_treble + 1; + if (uinfo->value.enumerated.item > chip->max_treble) + uinfo->value.enumerated.item = chip->max_treble; + sprintf(uinfo->value.enumerated.name, "%lu", + (unsigned long)PCSP_CALC_RATE(uinfo->value.enumerated.item)); + return 0; +} + +static int pcsp_treble_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->treble; + return 0; +} + +static int pcsp_treble_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int treble = ucontrol->value.enumerated.item[0]; + if (treble != chip->treble) { + chip->treble = treble; +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: rate set to %li\n", PCSP_RATE()); +#endif + changed = 1; + } + return changed; +} + +static int pcsp_pcspkr_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 pcsp_pcspkr_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->pcspkr; + return 0; +} + +static int pcsp_pcspkr_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int spkr = ucontrol->value.integer.value[0]; + if (spkr != chip->pcspkr) { + chip->pcspkr = spkr; + changed = 1; + } + return changed; +} + +#define PCSP_MIXER_CONTROL(ctl_type, ctl_name) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = ctl_name, \ + .info = pcsp_##ctl_type##_info, \ + .get = pcsp_##ctl_type##_get, \ + .put = pcsp_##ctl_type##_put, \ +} + +static struct snd_kcontrol_new snd_pcsp_controls_pcm[] = { + PCSP_MIXER_CONTROL(enable, "Master Playback Switch"), + PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"), +}; + +static struct snd_kcontrol_new snd_pcsp_controls_spkr[] = { + PCSP_MIXER_CONTROL(pcspkr, "Beep Playback Switch"), +}; + +static int snd_pcsp_ctls_add(struct snd_pcsp *chip, + struct snd_kcontrol_new *ctls, int num) +{ + int i, err; + struct snd_card *card = chip->card; + for (i = 0; i < num; i++) { + err = snd_ctl_add(card, snd_ctl_new1(ctls + i, chip)); + if (err < 0) + return err; + } + return 0; +} + +int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm) +{ + int err; + struct snd_card *card = chip->card; + + if (!nopcm) { + err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_pcm, + ARRAY_SIZE(snd_pcsp_controls_pcm)); + if (err < 0) + return err; + } + err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_spkr, + ARRAY_SIZE(snd_pcsp_controls_spkr)); + if (err < 0) + return err; + + strcpy(card->mixername, "PC-Speaker"); + + return 0; +} diff --git a/sound/drivers/portman2x4.c b/sound/drivers/portman2x4.c new file mode 100644 index 000000000..3cdf0a88d --- /dev/null +++ b/sound/drivers/portman2x4.c @@ -0,0 +1,870 @@ +/* + * Driver for Midiman Portman2x4 parallel port midi interface + * + * Copyright (c) by Levent Guendogdu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * ChangeLog + * Jan 24 2007 Matthias Koenig + * - cleanup and rewrite + * Sep 30 2004 Tobias Gehrig + * - source code cleanup + * Sep 03 2004 Tobias Gehrig + * - fixed compilation problem with alsa 1.0.6a (removed MODULE_CLASSES, + * MODULE_PARM_SYNTAX and changed MODULE_DEVICES to + * MODULE_SUPPORTED_DEVICE) + * Mar 24 2004 Tobias Gehrig + * - added 2.6 kernel support + * Mar 18 2004 Tobias Gehrig + * - added parport_unregister_driver to the startup routine if the driver fails to detect a portman + * - added support for all 4 output ports in portman_putmidi + * Mar 17 2004 Tobias Gehrig + * - added checks for opened input device in interrupt handler + * Feb 20 2004 Tobias Gehrig + * - ported from alsa 0.5 to 1.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CARD_NAME "Portman 2x4" +#define DRIVER_NAME "portman" +#define PLATFORM_DRIVER "snd_portman2x4" + +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 struct platform_device *platform_devices[SNDRV_CARDS]; +static int device_count; + +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_AUTHOR("Levent Guendogdu, Tobias Gehrig, Matthias Koenig"); +MODULE_DESCRIPTION("Midiman Portman2x4"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Midiman,Portman2x4}}"); + +/********************************************************************* + * Chip specific + *********************************************************************/ +#define PORTMAN_NUM_INPUT_PORTS 2 +#define PORTMAN_NUM_OUTPUT_PORTS 4 + +struct portman { + spinlock_t reg_lock; + struct snd_card *card; + struct snd_rawmidi *rmidi; + struct pardevice *pardev; + int open_count; + int mode[PORTMAN_NUM_INPUT_PORTS]; + struct snd_rawmidi_substream *midi_input[PORTMAN_NUM_INPUT_PORTS]; +}; + +static int portman_free(struct portman *pm) +{ + kfree(pm); + return 0; +} + +static int portman_create(struct snd_card *card, + struct pardevice *pardev, + struct portman **rchip) +{ + struct portman *pm; + + *rchip = NULL; + + pm = kzalloc(sizeof(struct portman), GFP_KERNEL); + if (pm == NULL) + return -ENOMEM; + + /* Init chip specific data */ + spin_lock_init(&pm->reg_lock); + pm->card = card; + pm->pardev = pardev; + + *rchip = pm; + + return 0; +} + +/********************************************************************* + * HW related constants + *********************************************************************/ + +/* Standard PC parallel port status register equates. */ +#define PP_STAT_BSY 0x80 /* Busy status. Inverted. */ +#define PP_STAT_ACK 0x40 /* Acknowledge. Non-Inverted. */ +#define PP_STAT_POUT 0x20 /* Paper Out. Non-Inverted. */ +#define PP_STAT_SEL 0x10 /* Select. Non-Inverted. */ +#define PP_STAT_ERR 0x08 /* Error. Non-Inverted. */ + +/* Standard PC parallel port command register equates. */ +#define PP_CMD_IEN 0x10 /* IRQ Enable. Non-Inverted. */ +#define PP_CMD_SELI 0x08 /* Select Input. Inverted. */ +#define PP_CMD_INIT 0x04 /* Init Printer. Non-Inverted. */ +#define PP_CMD_FEED 0x02 /* Auto Feed. Inverted. */ +#define PP_CMD_STB 0x01 /* Strobe. Inverted. */ + +/* Parallel Port Command Register as implemented by PCP2x4. */ +#define INT_EN PP_CMD_IEN /* Interrupt enable. */ +#define STROBE PP_CMD_STB /* Command strobe. */ + +/* The parallel port command register field (b1..b3) selects the + * various "registers" within the PC/P 2x4. These are the internal + * address of these "registers" that must be written to the parallel + * port command register. + */ +#define RXDATA0 (0 << 1) /* PCP RxData channel 0. */ +#define RXDATA1 (1 << 1) /* PCP RxData channel 1. */ +#define GEN_CTL (2 << 1) /* PCP General Control Register. */ +#define SYNC_CTL (3 << 1) /* PCP Sync Control Register. */ +#define TXDATA0 (4 << 1) /* PCP TxData channel 0. */ +#define TXDATA1 (5 << 1) /* PCP TxData channel 1. */ +#define TXDATA2 (6 << 1) /* PCP TxData channel 2. */ +#define TXDATA3 (7 << 1) /* PCP TxData channel 3. */ + +/* Parallel Port Status Register as implemented by PCP2x4. */ +#define ESTB PP_STAT_POUT /* Echoed strobe. */ +#define INT_REQ PP_STAT_ACK /* Input data int request. */ +#define BUSY PP_STAT_ERR /* Interface Busy. */ + +/* Parallel Port Status Register BUSY and SELECT lines are multiplexed + * between several functions. Depending on which 2x4 "register" is + * currently selected (b1..b3), the BUSY and SELECT lines are + * assigned as follows: + * + * SELECT LINE: A3 A2 A1 + * -------- + */ +#define RXAVAIL PP_STAT_SEL /* Rx Available, channel 0. 0 0 0 */ +// RXAVAIL1 PP_STAT_SEL /* Rx Available, channel 1. 0 0 1 */ +#define SYNC_STAT PP_STAT_SEL /* Reserved - Sync Status. 0 1 0 */ +// /* Reserved. 0 1 1 */ +#define TXEMPTY PP_STAT_SEL /* Tx Empty, channel 0. 1 0 0 */ +// TXEMPTY1 PP_STAT_SEL /* Tx Empty, channel 1. 1 0 1 */ +// TXEMPTY2 PP_STAT_SEL /* Tx Empty, channel 2. 1 1 0 */ +// TXEMPTY3 PP_STAT_SEL /* Tx Empty, channel 3. 1 1 1 */ + +/* BUSY LINE: A3 A2 A1 + * -------- + */ +#define RXDATA PP_STAT_BSY /* Rx Input Data, channel 0. 0 0 0 */ +// RXDATA1 PP_STAT_BSY /* Rx Input Data, channel 1. 0 0 1 */ +#define SYNC_DATA PP_STAT_BSY /* Reserved - Sync Data. 0 1 0 */ + /* Reserved. 0 1 1 */ +#define DATA_ECHO PP_STAT_BSY /* Parallel Port Data Echo. 1 0 0 */ +#define A0_ECHO PP_STAT_BSY /* Address 0 Echo. 1 0 1 */ +#define A1_ECHO PP_STAT_BSY /* Address 1 Echo. 1 1 0 */ +#define A2_ECHO PP_STAT_BSY /* Address 2 Echo. 1 1 1 */ + +#define PORTMAN2X4_MODE_INPUT_TRIGGERED 0x01 + +/********************************************************************* + * Hardware specific functions + *********************************************************************/ +static inline void portman_write_command(struct portman *pm, u8 value) +{ + parport_write_control(pm->pardev->port, value); +} + +static inline u8 portman_read_command(struct portman *pm) +{ + return parport_read_control(pm->pardev->port); +} + +static inline u8 portman_read_status(struct portman *pm) +{ + return parport_read_status(pm->pardev->port); +} + +static inline u8 portman_read_data(struct portman *pm) +{ + return parport_read_data(pm->pardev->port); +} + +static inline void portman_write_data(struct portman *pm, u8 value) +{ + parport_write_data(pm->pardev->port, value); +} + +static void portman_write_midi(struct portman *pm, + int port, u8 mididata) +{ + int command = ((port + 4) << 1); + + /* Get entering data byte and port number in BL and BH respectively. + * Set up Tx Channel address field for use with PP Cmd Register. + * Store address field in BH register. + * Inputs: AH = Output port number (0..3). + * AL = Data byte. + * command = TXDATA0 | INT_EN; + * Align port num with address field (b1...b3), + * set address for TXDatax, Strobe=0 + */ + command |= INT_EN; + + /* Disable interrupts so that the process is not interrupted, then + * write the address associated with the current Tx channel to the + * PP Command Reg. Do not set the Strobe signal yet. + */ + + do { + portman_write_command(pm, command); + + /* While the address lines settle, write parallel output data to + * PP Data Reg. This has no effect until Strobe signal is asserted. + */ + + portman_write_data(pm, mididata); + + /* If PCP channel's TxEmpty is set (TxEmpty is read through the PP + * Status Register), then go write data. Else go back and wait. + */ + } while ((portman_read_status(pm) & TXEMPTY) != TXEMPTY); + + /* TxEmpty is set. Maintain PC/P destination address and assert + * Strobe through the PP Command Reg. This will Strobe data into + * the PC/P transmitter and set the PC/P BUSY signal. + */ + + portman_write_command(pm, command | STROBE); + + /* Wait for strobe line to settle and echo back through hardware. + * Once it has echoed back, assume that the address and data lines + * have settled! + */ + + while ((portman_read_status(pm) & ESTB) == 0) + cpu_relax(); + + /* Release strobe and immediately re-allow interrupts. */ + portman_write_command(pm, command); + + while ((portman_read_status(pm) & ESTB) == ESTB) + cpu_relax(); + + /* PC/P BUSY is now set. We must wait until BUSY resets itself. + * We'll reenable ints while we're waiting. + */ + + while ((portman_read_status(pm) & BUSY) == BUSY) + cpu_relax(); + + /* Data sent. */ +} + + +/* + * Read MIDI byte from port + * Attempt to read input byte from specified hardware input port (0..). + * Return -1 if no data + */ +static int portman_read_midi(struct portman *pm, int port) +{ + unsigned char midi_data = 0; + unsigned char cmdout; /* Saved address+IE bit. */ + + /* Make sure clocking edge is down before starting... */ + portman_write_data(pm, 0); /* Make sure edge is down. */ + + /* Set destination address to PCP. */ + cmdout = (port << 1) | INT_EN; /* Address + IE + No Strobe. */ + portman_write_command(pm, cmdout); + + while ((portman_read_status(pm) & ESTB) == ESTB) + cpu_relax(); /* Wait for strobe echo. */ + + /* After the address lines settle, check multiplexed RxAvail signal. + * If data is available, read it. + */ + if ((portman_read_status(pm) & RXAVAIL) == 0) + return -1; /* No data. */ + + /* Set the Strobe signal to enable the Rx clocking circuitry. */ + portman_write_command(pm, cmdout | STROBE); /* Write address+IE+Strobe. */ + + while ((portman_read_status(pm) & ESTB) == 0) + cpu_relax(); /* Wait for strobe echo. */ + + /* The first data bit (msb) is already sitting on the input line. */ + midi_data = (portman_read_status(pm) & 128); + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + + /* Data bit 6. */ + portman_write_data(pm, 0); /* Cause falling edge while data settles. */ + midi_data |= (portman_read_status(pm) >> 1) & 64; + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + + /* Data bit 5. */ + portman_write_data(pm, 0); /* Cause falling edge while data settles. */ + midi_data |= (portman_read_status(pm) >> 2) & 32; + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + + /* Data bit 4. */ + portman_write_data(pm, 0); /* Cause falling edge while data settles. */ + midi_data |= (portman_read_status(pm) >> 3) & 16; + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + + /* Data bit 3. */ + portman_write_data(pm, 0); /* Cause falling edge while data settles. */ + midi_data |= (portman_read_status(pm) >> 4) & 8; + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + + /* Data bit 2. */ + portman_write_data(pm, 0); /* Cause falling edge while data settles. */ + midi_data |= (portman_read_status(pm) >> 5) & 4; + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + + /* Data bit 1. */ + portman_write_data(pm, 0); /* Cause falling edge while data settles. */ + midi_data |= (portman_read_status(pm) >> 6) & 2; + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + + /* Data bit 0. */ + portman_write_data(pm, 0); /* Cause falling edge while data settles. */ + midi_data |= (portman_read_status(pm) >> 7) & 1; + portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */ + portman_write_data(pm, 0); /* Return data clock low. */ + + + /* De-assert Strobe and return data. */ + portman_write_command(pm, cmdout); /* Output saved address+IE. */ + + /* Wait for strobe echo. */ + while ((portman_read_status(pm) & ESTB) == ESTB) + cpu_relax(); + + return (midi_data & 255); /* Shift back and return value. */ +} + +/* + * Checks if any input data on the given channel is available + * Checks RxAvail + */ +static int portman_data_avail(struct portman *pm, int channel) +{ + int command = INT_EN; + switch (channel) { + case 0: + command |= RXDATA0; + break; + case 1: + command |= RXDATA1; + break; + } + /* Write hardware (assumme STROBE=0) */ + portman_write_command(pm, command); + /* Check multiplexed RxAvail signal */ + if ((portman_read_status(pm) & RXAVAIL) == RXAVAIL) + return 1; /* Data available */ + + /* No Data available */ + return 0; +} + + +/* + * Flushes any input + */ +static void portman_flush_input(struct portman *pm, unsigned char port) +{ + /* Local variable for counting things */ + unsigned int i = 0; + unsigned char command = 0; + + switch (port) { + case 0: + command = RXDATA0; + break; + case 1: + command = RXDATA1; + break; + default: + snd_printk(KERN_WARNING + "portman_flush_input() Won't flush port %i\n", + port); + return; + } + + /* Set address for specified channel in port and allow to settle. */ + portman_write_command(pm, command); + + /* Assert the Strobe and wait for echo back. */ + portman_write_command(pm, command | STROBE); + + /* Wait for ESTB */ + while ((portman_read_status(pm) & ESTB) == 0) + cpu_relax(); + + /* Output clock cycles to the Rx circuitry. */ + portman_write_data(pm, 0); + + /* Flush 250 bits... */ + for (i = 0; i < 250; i++) { + portman_write_data(pm, 1); + portman_write_data(pm, 0); + } + + /* Deassert the Strobe signal of the port and wait for it to settle. */ + portman_write_command(pm, command | INT_EN); + + /* Wait for settling */ + while ((portman_read_status(pm) & ESTB) == ESTB) + cpu_relax(); +} + +static int portman_probe(struct parport *p) +{ + /* Initialize the parallel port data register. Will set Rx clocks + * low in case we happen to be addressing the Rx ports at this time. + */ + /* 1 */ + parport_write_data(p, 0); + + /* Initialize the parallel port command register, thus initializing + * hardware handshake lines to midi box: + * + * Strobe = 0 + * Interrupt Enable = 0 + */ + /* 2 */ + parport_write_control(p, 0); + + /* Check if Portman PC/P 2x4 is out there. */ + /* 3 */ + parport_write_control(p, RXDATA0); /* Write Strobe=0 to command reg. */ + + /* Check for ESTB to be clear */ + /* 4 */ + if ((parport_read_status(p) & ESTB) == ESTB) + return 1; /* CODE 1 - Strobe Failure. */ + + /* Set for RXDATA0 where no damage will be done. */ + /* 5 */ + parport_write_control(p, RXDATA0 + STROBE); /* Write Strobe=1 to command reg. */ + + /* 6 */ + if ((parport_read_status(p) & ESTB) != ESTB) + return 1; /* CODE 1 - Strobe Failure. */ + + /* 7 */ + parport_write_control(p, 0); /* Reset Strobe=0. */ + + /* Check if Tx circuitry is functioning properly. If initialized + * unit TxEmpty is false, send out char and see if if goes true. + */ + /* 8 */ + parport_write_control(p, TXDATA0); /* Tx channel 0, strobe off. */ + + /* If PCP channel's TxEmpty is set (TxEmpty is read through the PP + * Status Register), then go write data. Else go back and wait. + */ + /* 9 */ + if ((parport_read_status(p) & TXEMPTY) == 0) + return 2; + + /* Return OK status. */ + return 0; +} + +static int portman_device_init(struct portman *pm) +{ + portman_flush_input(pm, 0); + portman_flush_input(pm, 1); + + return 0; +} + +/********************************************************************* + * Rawmidi + *********************************************************************/ +static int snd_portman_midi_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int snd_portman_midi_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void snd_portman_midi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct portman *pm = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&pm->reg_lock, flags); + if (up) + pm->mode[substream->number] |= PORTMAN2X4_MODE_INPUT_TRIGGERED; + else + pm->mode[substream->number] &= ~PORTMAN2X4_MODE_INPUT_TRIGGERED; + spin_unlock_irqrestore(&pm->reg_lock, flags); +} + +static void snd_portman_midi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct portman *pm = substream->rmidi->private_data; + unsigned long flags; + unsigned char byte; + + spin_lock_irqsave(&pm->reg_lock, flags); + if (up) { + while ((snd_rawmidi_transmit(substream, &byte, 1) == 1)) + portman_write_midi(pm, substream->number, byte); + } + spin_unlock_irqrestore(&pm->reg_lock, flags); +} + +static const struct snd_rawmidi_ops snd_portman_midi_output = { + .open = snd_portman_midi_open, + .close = snd_portman_midi_close, + .trigger = snd_portman_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_portman_midi_input = { + .open = snd_portman_midi_open, + .close = snd_portman_midi_close, + .trigger = snd_portman_midi_input_trigger, +}; + +/* Create and initialize the rawmidi component */ +static int snd_portman_rawmidi_create(struct snd_card *card) +{ + struct portman *pm = card->private_data; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream; + int err; + + err = snd_rawmidi_new(card, CARD_NAME, 0, + PORTMAN_NUM_OUTPUT_PORTS, + PORTMAN_NUM_INPUT_PORTS, + &rmidi); + if (err < 0) + return err; + + rmidi->private_data = pm; + strcpy(rmidi->name, CARD_NAME); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + pm->rmidi = rmidi; + + /* register rawmidi ops */ + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_portman_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_portman_midi_input); + + /* name substreams */ + /* output */ + list_for_each_entry(substream, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams, + list) { + sprintf(substream->name, + "Portman2x4 %d", substream->number+1); + } + /* input */ + list_for_each_entry(substream, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams, + list) { + pm->midi_input[substream->number] = substream; + sprintf(substream->name, + "Portman2x4 %d", substream->number+1); + } + + return err; +} + +/********************************************************************* + * parport stuff + *********************************************************************/ +static void snd_portman_interrupt(void *userdata) +{ + unsigned char midivalue = 0; + struct portman *pm = ((struct snd_card*)userdata)->private_data; + + spin_lock(&pm->reg_lock); + + /* While any input data is waiting */ + while ((portman_read_status(pm) & INT_REQ) == INT_REQ) { + /* If data available on channel 0, + read it and stuff it into the queue. */ + if (portman_data_avail(pm, 0)) { + /* Read Midi */ + midivalue = portman_read_midi(pm, 0); + /* put midi into queue... */ + if (pm->mode[0] & PORTMAN2X4_MODE_INPUT_TRIGGERED) + snd_rawmidi_receive(pm->midi_input[0], + &midivalue, 1); + + } + /* If data available on channel 1, + read it and stuff it into the queue. */ + if (portman_data_avail(pm, 1)) { + /* Read Midi */ + midivalue = portman_read_midi(pm, 1); + /* put midi into queue... */ + if (pm->mode[1] & PORTMAN2X4_MODE_INPUT_TRIGGERED) + snd_rawmidi_receive(pm->midi_input[1], + &midivalue, 1); + } + + } + + spin_unlock(&pm->reg_lock); +} + +static void snd_portman_attach(struct parport *p) +{ + struct platform_device *device; + + device = platform_device_alloc(PLATFORM_DRIVER, device_count); + if (!device) + return; + + /* Temporary assignment to forward the parport */ + platform_set_drvdata(device, p); + + if (platform_device_add(device) < 0) { + platform_device_put(device); + return; + } + + /* Since we dont get the return value of probe + * We need to check if device probing succeeded or not */ + if (!platform_get_drvdata(device)) { + platform_device_unregister(device); + return; + } + + /* register device in global table */ + platform_devices[device_count] = device; + device_count++; +} + +static void snd_portman_detach(struct parport *p) +{ + /* nothing to do here */ +} + +static int snd_portman_dev_probe(struct pardevice *pardev) +{ + if (strcmp(pardev->name, DRIVER_NAME)) + return -ENODEV; + + return 0; +} + +static struct parport_driver portman_parport_driver = { + .name = "portman2x4", + .probe = snd_portman_dev_probe, + .match_port = snd_portman_attach, + .detach = snd_portman_detach, + .devmodel = true, +}; + +/********************************************************************* + * platform stuff + *********************************************************************/ +static void snd_portman_card_private_free(struct snd_card *card) +{ + struct portman *pm = card->private_data; + struct pardevice *pardev = pm->pardev; + + if (pardev) { + parport_release(pardev); + parport_unregister_device(pardev); + } + + portman_free(pm); +} + +static int snd_portman_probe(struct platform_device *pdev) +{ + struct pardevice *pardev; + struct parport *p; + int dev = pdev->id; + struct snd_card *card = NULL; + struct portman *pm = NULL; + int err; + struct pardev_cb portman_cb = { + .preempt = NULL, + .wakeup = NULL, + .irq_func = snd_portman_interrupt, /* ISR */ + .flags = PARPORT_DEV_EXCL, /* flags */ + }; + + p = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) + return -ENOENT; + + err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + snd_printd("Cannot create card\n"); + return err; + } + strcpy(card->driver, DRIVER_NAME); + strcpy(card->shortname, CARD_NAME); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, p->base, p->irq); + + portman_cb.private = card; /* private */ + pardev = parport_register_dev_model(p, /* port */ + DRIVER_NAME, /* name */ + &portman_cb, /* callbacks */ + pdev->id); /* device number */ + if (pardev == NULL) { + snd_printd("Cannot register pardevice\n"); + err = -EIO; + goto __err; + } + + /* claim parport */ + if (parport_claim(pardev)) { + snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base); + err = -EIO; + goto free_pardev; + } + + if ((err = portman_create(card, pardev, &pm)) < 0) { + snd_printd("Cannot create main component\n"); + goto release_pardev; + } + card->private_data = pm; + card->private_free = snd_portman_card_private_free; + + err = portman_probe(p); + if (err) { + err = -EIO; + goto __err; + } + + if ((err = snd_portman_rawmidi_create(card)) < 0) { + snd_printd("Creating Rawmidi component failed\n"); + goto __err; + } + + /* init device */ + if ((err = portman_device_init(pm)) < 0) + goto __err; + + platform_set_drvdata(pdev, card); + + /* At this point card will be usable */ + if ((err = snd_card_register(card)) < 0) { + snd_printd("Cannot register card\n"); + goto __err; + } + + snd_printk(KERN_INFO "Portman 2x4 on 0x%lx\n", p->base); + return 0; + +release_pardev: + parport_release(pardev); +free_pardev: + parport_unregister_device(pardev); +__err: + snd_card_free(card); + return err; +} + +static int snd_portman_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + + if (card) + snd_card_free(card); + + return 0; +} + + +static struct platform_driver snd_portman_driver = { + .probe = snd_portman_probe, + .remove = snd_portman_remove, + .driver = { + .name = PLATFORM_DRIVER, + } +}; + +/********************************************************************* + * module init stuff + *********************************************************************/ +static void snd_portman_unregister_all(void) +{ + int i; + + for (i = 0; i < SNDRV_CARDS; ++i) { + if (platform_devices[i]) { + platform_device_unregister(platform_devices[i]); + platform_devices[i] = NULL; + } + } + platform_driver_unregister(&snd_portman_driver); + parport_unregister_driver(&portman_parport_driver); +} + +static int __init snd_portman_module_init(void) +{ + int err; + + if ((err = platform_driver_register(&snd_portman_driver)) < 0) + return err; + + if (parport_register_driver(&portman_parport_driver) != 0) { + platform_driver_unregister(&snd_portman_driver); + return -EIO; + } + + if (device_count == 0) { + snd_portman_unregister_all(); + return -ENODEV; + } + + return 0; +} + +static void __exit snd_portman_module_exit(void) +{ + snd_portman_unregister_all(); +} + +module_init(snd_portman_module_init); +module_exit(snd_portman_module_exit); diff --git a/sound/drivers/serial-u16550.c b/sound/drivers/serial-u16550.c new file mode 100644 index 000000000..0a67b8b9f --- /dev/null +++ b/sound/drivers/serial-u16550.c @@ -0,0 +1,1044 @@ +/* + * serial.c + * Copyright (c) by Jaroslav Kysela , + * Isaku Yamahata , + * George Hansper , + * Hannu Savolainen + * + * This code is based on the code from ALSA 0.5.9, but heavily rewritten. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Sat Mar 31 17:27:57 PST 2001 tim.mann@compaq.com + * Added support for the Midiator MS-124T and for the MS-124W in + * Single Addressed (S/A) or Multiple Burst (M/B) mode, with + * power derived either parasitically from the serial port or + * from a separate power supply. + * + * More documentation can be found in serial-u16550.txt. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_DESCRIPTION("MIDI serial u16550"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ALSA, MIDI serial u16550}}"); + +#define SNDRV_SERIAL_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */ +#define SNDRV_SERIAL_MS124T 1 /* Midiator MS-124T */ +#define SNDRV_SERIAL_MS124W_SA 2 /* Midiator MS-124W in S/A mode */ +#define SNDRV_SERIAL_MS124W_MB 3 /* Midiator MS-124W in M/B mode */ +#define SNDRV_SERIAL_GENERIC 4 /* Generic Interface */ +#define SNDRV_SERIAL_MAX_ADAPTOR SNDRV_SERIAL_GENERIC +static char *adaptor_names[] = { + "Soundcanvas", + "MS-124T", + "MS-124W S/A", + "MS-124W M/B", + "Generic" +}; + +#define SNDRV_SERIAL_NORMALBUFF 0 /* Normal blocking buffer operation */ +#define SNDRV_SERIAL_DROPBUFF 1 /* Non-blocking discard operation */ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x3f8,0x2f8,0x3e8,0x2e8 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,4,5,7,9,10,11,14,15 */ +static int speed[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 38400}; /* 9600,19200,38400,57600,115200 */ +static int base[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 115200}; /* baud base */ +static int outs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */ +static int ins[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */ +static int adaptor[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = SNDRV_SERIAL_SOUNDCANVAS}; +static bool droponfull[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS -1)] = SNDRV_SERIAL_NORMALBUFF }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Serial MIDI."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Serial MIDI."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable UART16550A chip."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for UART16550A chip."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for UART16550A chip."); +module_param_array(speed, int, NULL, 0444); +MODULE_PARM_DESC(speed, "Speed in bauds."); +module_param_array(base, int, NULL, 0444); +MODULE_PARM_DESC(base, "Base for divisor in bauds."); +module_param_array(outs, int, NULL, 0444); +MODULE_PARM_DESC(outs, "Number of MIDI outputs."); +module_param_array(ins, int, NULL, 0444); +MODULE_PARM_DESC(ins, "Number of MIDI inputs."); +module_param_array(droponfull, bool, NULL, 0444); +MODULE_PARM_DESC(droponfull, "Flag to enable drop-on-full buffer mode"); + +module_param_array(adaptor, int, NULL, 0444); +MODULE_PARM_DESC(adaptor, "Type of adaptor."); + +/*#define SNDRV_SERIAL_MS124W_MB_NOCOMBO 1*/ /* Address outs as 0-3 instead of bitmap */ + +#define SNDRV_SERIAL_MAX_OUTS 16 /* max 64, min 16 */ +#define SNDRV_SERIAL_MAX_INS 16 /* max 64, min 16 */ + +#define TX_BUFF_SIZE (1<<15) /* Must be 2^n */ +#define TX_BUFF_MASK (TX_BUFF_SIZE - 1) + +#define SERIAL_MODE_NOT_OPENED (0) +#define SERIAL_MODE_INPUT_OPEN (1 << 0) +#define SERIAL_MODE_OUTPUT_OPEN (1 << 1) +#define SERIAL_MODE_INPUT_TRIGGERED (1 << 2) +#define SERIAL_MODE_OUTPUT_TRIGGERED (1 << 3) + +struct snd_uart16550 { + struct snd_card *card; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_output[SNDRV_SERIAL_MAX_OUTS]; + struct snd_rawmidi_substream *midi_input[SNDRV_SERIAL_MAX_INS]; + + int filemode; /* open status of file */ + + spinlock_t open_lock; + + int irq; + + unsigned long base; + struct resource *res_base; + + unsigned int speed; + unsigned int speed_base; + unsigned char divisor; + + unsigned char old_divisor_lsb; + unsigned char old_divisor_msb; + unsigned char old_line_ctrl_reg; + + /* parameter for using of write loop */ + short int fifo_limit; /* used in uart16550 */ + short int fifo_count; /* used in uart16550 */ + + /* type of adaptor */ + int adaptor; + + /* inputs */ + int prev_in; + unsigned char rstatus; + + /* outputs */ + int prev_out; + unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS]; + + /* write buffer and its writing/reading position */ + unsigned char tx_buff[TX_BUFF_SIZE]; + int buff_in_count; + int buff_in; + int buff_out; + int drop_on_full; + + /* wait timer */ + unsigned int timer_running:1; + struct timer_list buffer_timer; + +}; + +static struct platform_device *devices[SNDRV_CARDS]; + +static inline void snd_uart16550_add_timer(struct snd_uart16550 *uart) +{ + if (!uart->timer_running) { + /* timer 38600bps * 10bit * 16byte */ + mod_timer(&uart->buffer_timer, jiffies + (HZ + 255) / 256); + uart->timer_running = 1; + } +} + +static inline void snd_uart16550_del_timer(struct snd_uart16550 *uart) +{ + if (uart->timer_running) { + del_timer(&uart->buffer_timer); + uart->timer_running = 0; + } +} + +/* This macro is only used in snd_uart16550_io_loop */ +static inline void snd_uart16550_buffer_output(struct snd_uart16550 *uart) +{ + unsigned short buff_out = uart->buff_out; + if (uart->buff_in_count > 0) { + outb(uart->tx_buff[buff_out], uart->base + UART_TX); + uart->fifo_count++; + buff_out++; + buff_out &= TX_BUFF_MASK; + uart->buff_out = buff_out; + uart->buff_in_count--; + } +} + +/* This loop should be called with interrupts disabled + * We don't want to interrupt this, + * as we're already handling an interrupt + */ +static void snd_uart16550_io_loop(struct snd_uart16550 * uart) +{ + unsigned char c, status; + int substream; + + /* recall previous stream */ + substream = uart->prev_in; + + /* Read Loop */ + while ((status = inb(uart->base + UART_LSR)) & UART_LSR_DR) { + /* while receive data ready */ + c = inb(uart->base + UART_RX); + + /* keep track of last status byte */ + if (c & 0x80) + uart->rstatus = c; + + /* handle stream switch */ + if (uart->adaptor == SNDRV_SERIAL_GENERIC) { + if (uart->rstatus == 0xf5) { + if (c <= SNDRV_SERIAL_MAX_INS && c > 0) + substream = c - 1; + if (c != 0xf5) + /* prevent future bytes from being + interpreted as streams */ + uart->rstatus = 0; + } else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) + && uart->midi_input[substream]) + snd_rawmidi_receive(uart->midi_input[substream], + &c, 1); + } else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) && + uart->midi_input[substream]) + snd_rawmidi_receive(uart->midi_input[substream], &c, 1); + + if (status & UART_LSR_OE) + snd_printk(KERN_WARNING + "%s: Overrun on device at 0x%lx\n", + uart->rmidi->name, uart->base); + } + + /* remember the last stream */ + uart->prev_in = substream; + + /* no need of check SERIAL_MODE_OUTPUT_OPEN because if not, + buffer is never filled. */ + /* Check write status */ + if (status & UART_LSR_THRE) + uart->fifo_count = 0; + if (uart->adaptor == SNDRV_SERIAL_MS124W_SA + || uart->adaptor == SNDRV_SERIAL_GENERIC) { + /* Can't use FIFO, must send only when CTS is true */ + status = inb(uart->base + UART_MSR); + while (uart->fifo_count == 0 && (status & UART_MSR_CTS) && + uart->buff_in_count > 0) { + snd_uart16550_buffer_output(uart); + status = inb(uart->base + UART_MSR); + } + } else { + /* Write loop */ + while (uart->fifo_count < uart->fifo_limit /* Can we write ? */ + && uart->buff_in_count > 0) /* Do we want to? */ + snd_uart16550_buffer_output(uart); + } + if (uart->irq < 0 && uart->buff_in_count > 0) + snd_uart16550_add_timer(uart); +} + +/* NOTES ON SERVICING INTERUPTS + * --------------------------- + * After receiving a interrupt, it is important to indicate to the UART that + * this has been done. + * For a Rx interrupt, this is done by reading the received byte. + * For a Tx interrupt this is done by either: + * a) Writing a byte + * b) Reading the IIR + * It is particularly important to read the IIR if a Tx interrupt is received + * when there is no data in tx_buff[], as in this case there no other + * indication that the interrupt has been serviced, and it remains outstanding + * indefinitely. This has the curious side effect that and no further interrupts + * will be generated from this device AT ALL!!. + * It is also desirable to clear outstanding interrupts when the device is + * opened/closed. + * + * + * Note that some devices need OUT2 to be set before they will generate + * interrupts at all. (Possibly tied to an internal pull-up on CTS?) + */ +static irqreturn_t snd_uart16550_interrupt(int irq, void *dev_id) +{ + struct snd_uart16550 *uart; + + uart = dev_id; + spin_lock(&uart->open_lock); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) { + spin_unlock(&uart->open_lock); + return IRQ_NONE; + } + /* indicate to the UART that the interrupt has been serviced */ + inb(uart->base + UART_IIR); + snd_uart16550_io_loop(uart); + spin_unlock(&uart->open_lock); + return IRQ_HANDLED; +} + +/* When the polling mode, this function calls snd_uart16550_io_loop. */ +static void snd_uart16550_buffer_timer(struct timer_list *t) +{ + unsigned long flags; + struct snd_uart16550 *uart; + + uart = from_timer(uart, t, buffer_timer); + spin_lock_irqsave(&uart->open_lock, flags); + snd_uart16550_del_timer(uart); + snd_uart16550_io_loop(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +/* + * this method probes, if an uart sits on given port + * return 0 if found + * return negative error if not found + */ +static int snd_uart16550_detect(struct snd_uart16550 *uart) +{ + unsigned long io_base = uart->base; + int ok; + unsigned char c; + + /* Do some vague tests for the presence of the uart */ + if (io_base == 0 || io_base == SNDRV_AUTO_PORT) { + return -ENODEV; /* Not configured */ + } + + uart->res_base = request_region(io_base, 8, "Serial MIDI"); + if (uart->res_base == NULL) { + snd_printk(KERN_ERR "u16550: can't grab port 0x%lx\n", io_base); + return -EBUSY; + } + + /* uart detected unless one of the following tests should fail */ + ok = 1; + /* 8 data-bits, 1 stop-bit, parity off, DLAB = 0 */ + outb(UART_LCR_WLEN8, io_base + UART_LCR); /* Line Control Register */ + c = inb(io_base + UART_IER); + /* The top four bits of the IER should always == 0 */ + if ((c & 0xf0) != 0) + ok = 0; /* failed */ + + outb(0xaa, io_base + UART_SCR); + /* Write arbitrary data into the scratch reg */ + c = inb(io_base + UART_SCR); + /* If it comes back, it's OK */ + if (c != 0xaa) + ok = 0; /* failed */ + + outb(0x55, io_base + UART_SCR); + /* Write arbitrary data into the scratch reg */ + c = inb(io_base + UART_SCR); + /* If it comes back, it's OK */ + if (c != 0x55) + ok = 0; /* failed */ + + return ok; +} + +static void snd_uart16550_do_open(struct snd_uart16550 * uart) +{ + char byte; + + /* Initialize basic variables */ + uart->buff_in_count = 0; + uart->buff_in = 0; + uart->buff_out = 0; + uart->fifo_limit = 1; + uart->fifo_count = 0; + uart->timer_running = 0; + + outb(UART_FCR_ENABLE_FIFO /* Enable FIFO's (if available) */ + | UART_FCR_CLEAR_RCVR /* Clear receiver FIFO */ + | UART_FCR_CLEAR_XMIT /* Clear transmitter FIFO */ + | UART_FCR_TRIGGER_4 /* Set FIFO trigger at 4-bytes */ + /* NOTE: interrupt generated after T=(time)4-bytes + * if less than UART_FCR_TRIGGER bytes received + */ + ,uart->base + UART_FCR); /* FIFO Control Register */ + + if ((inb(uart->base + UART_IIR) & 0xf0) == 0xc0) + uart->fifo_limit = 16; + if (uart->divisor != 0) { + uart->old_line_ctrl_reg = inb(uart->base + UART_LCR); + outb(UART_LCR_DLAB /* Divisor latch access bit */ + ,uart->base + UART_LCR); /* Line Control Register */ + uart->old_divisor_lsb = inb(uart->base + UART_DLL); + uart->old_divisor_msb = inb(uart->base + UART_DLM); + + outb(uart->divisor + ,uart->base + UART_DLL); /* Divisor Latch Low */ + outb(0 + ,uart->base + UART_DLM); /* Divisor Latch High */ + /* DLAB is reset to 0 in next outb() */ + } + /* Set serial parameters (parity off, etc) */ + outb(UART_LCR_WLEN8 /* 8 data-bits */ + | 0 /* 1 stop-bit */ + | 0 /* parity off */ + | 0 /* DLAB = 0 */ + ,uart->base + UART_LCR); /* Line Control Register */ + + switch (uart->adaptor) { + default: + outb(UART_MCR_RTS /* Set Request-To-Send line active */ + | UART_MCR_DTR /* Set Data-Terminal-Ready line active */ + | UART_MCR_OUT2 /* Set OUT2 - not always required, but when + * it is, it is ESSENTIAL for enabling interrupts + */ + ,uart->base + UART_MCR); /* Modem Control Register */ + break; + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* MS-124W can draw power from RTS and DTR if they + are in opposite states. */ + outb(UART_MCR_RTS | (0&UART_MCR_DTR) | UART_MCR_OUT2, + uart->base + UART_MCR); + break; + case SNDRV_SERIAL_MS124T: + /* MS-124T can draw power from RTS and/or DTR (preferably + both) if they are both asserted. */ + outb(UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2, + uart->base + UART_MCR); + break; + } + + if (uart->irq < 0) { + byte = (0 & UART_IER_RDI) /* Disable Receiver data interrupt */ + |(0 & UART_IER_THRI) /* Disable Transmitter holding register empty interrupt */ + ; + } else if (uart->adaptor == SNDRV_SERIAL_MS124W_SA) { + byte = UART_IER_RDI /* Enable Receiver data interrupt */ + | UART_IER_MSI /* Enable Modem status interrupt */ + ; + } else if (uart->adaptor == SNDRV_SERIAL_GENERIC) { + byte = UART_IER_RDI /* Enable Receiver data interrupt */ + | UART_IER_MSI /* Enable Modem status interrupt */ + | UART_IER_THRI /* Enable Transmitter holding register empty interrupt */ + ; + } else { + byte = UART_IER_RDI /* Enable Receiver data interrupt */ + | UART_IER_THRI /* Enable Transmitter holding register empty interrupt */ + ; + } + outb(byte, uart->base + UART_IER); /* Interrupt enable Register */ + + inb(uart->base + UART_LSR); /* Clear any pre-existing overrun indication */ + inb(uart->base + UART_IIR); /* Clear any pre-existing transmit interrupt */ + inb(uart->base + UART_RX); /* Clear any pre-existing receive interrupt */ +} + +static void snd_uart16550_do_close(struct snd_uart16550 * uart) +{ + if (uart->irq < 0) + snd_uart16550_del_timer(uart); + + /* NOTE: may need to disable interrupts before de-registering out handler. + * For now, the consequences are harmless. + */ + + outb((0 & UART_IER_RDI) /* Disable Receiver data interrupt */ + |(0 & UART_IER_THRI) /* Disable Transmitter holding register empty interrupt */ + ,uart->base + UART_IER); /* Interrupt enable Register */ + + switch (uart->adaptor) { + default: + outb((0 & UART_MCR_RTS) /* Deactivate Request-To-Send line */ + |(0 & UART_MCR_DTR) /* Deactivate Data-Terminal-Ready line */ + |(0 & UART_MCR_OUT2) /* Deactivate OUT2 */ + ,uart->base + UART_MCR); /* Modem Control Register */ + break; + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* MS-124W can draw power from RTS and DTR if they + are in opposite states; leave it powered. */ + outb(UART_MCR_RTS | (0&UART_MCR_DTR) | (0&UART_MCR_OUT2), + uart->base + UART_MCR); + break; + case SNDRV_SERIAL_MS124T: + /* MS-124T can draw power from RTS and/or DTR (preferably + both) if they are both asserted; leave it powered. */ + outb(UART_MCR_RTS | UART_MCR_DTR | (0&UART_MCR_OUT2), + uart->base + UART_MCR); + break; + } + + inb(uart->base + UART_IIR); /* Clear any outstanding interrupts */ + + /* Restore old divisor */ + if (uart->divisor != 0) { + outb(UART_LCR_DLAB /* Divisor latch access bit */ + ,uart->base + UART_LCR); /* Line Control Register */ + outb(uart->old_divisor_lsb + ,uart->base + UART_DLL); /* Divisor Latch Low */ + outb(uart->old_divisor_msb + ,uart->base + UART_DLM); /* Divisor Latch High */ + /* Restore old LCR (data bits, stop bits, parity, DLAB) */ + outb(uart->old_line_ctrl_reg + ,uart->base + UART_LCR); /* Line Control Register */ + } +} + +static int snd_uart16550_input_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_open(uart); + uart->filemode |= SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = substream; + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +} + +static int snd_uart16550_input_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + uart->filemode &= ~SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_close(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +} + +static void snd_uart16550_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (up) + uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED; + else + uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED; + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +static int snd_uart16550_output_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_open(uart); + uart->filemode |= SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = substream; + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +}; + +static int snd_uart16550_output_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_close(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +}; + +static inline int snd_uart16550_buffer_can_write(struct snd_uart16550 *uart, + int Num) +{ + if (uart->buff_in_count + Num < TX_BUFF_SIZE) + return 1; + else + return 0; +} + +static inline int snd_uart16550_write_buffer(struct snd_uart16550 *uart, + unsigned char byte) +{ + unsigned short buff_in = uart->buff_in; + if (uart->buff_in_count < TX_BUFF_SIZE) { + uart->tx_buff[buff_in] = byte; + buff_in++; + buff_in &= TX_BUFF_MASK; + uart->buff_in = buff_in; + uart->buff_in_count++; + if (uart->irq < 0) /* polling mode */ + snd_uart16550_add_timer(uart); + return 1; + } else + return 0; +} + +static int snd_uart16550_output_byte(struct snd_uart16550 *uart, + struct snd_rawmidi_substream *substream, + unsigned char midi_byte) +{ + if (uart->buff_in_count == 0 /* Buffer empty? */ + && ((uart->adaptor != SNDRV_SERIAL_MS124W_SA && + uart->adaptor != SNDRV_SERIAL_GENERIC) || + (uart->fifo_count == 0 /* FIFO empty? */ + && (inb(uart->base + UART_MSR) & UART_MSR_CTS)))) { /* CTS? */ + + /* Tx Buffer Empty - try to write immediately */ + if ((inb(uart->base + UART_LSR) & UART_LSR_THRE) != 0) { + /* Transmitter holding register (and Tx FIFO) empty */ + uart->fifo_count = 1; + outb(midi_byte, uart->base + UART_TX); + } else { + if (uart->fifo_count < uart->fifo_limit) { + uart->fifo_count++; + outb(midi_byte, uart->base + UART_TX); + } else { + /* Cannot write (buffer empty) - + * put char in buffer */ + snd_uart16550_write_buffer(uart, midi_byte); + } + } + } else { + if (!snd_uart16550_write_buffer(uart, midi_byte)) { + snd_printk(KERN_WARNING + "%s: Buffer overrun on device at 0x%lx\n", + uart->rmidi->name, uart->base); + return 0; + } + } + + return 1; +} + +static void snd_uart16550_output_write(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + unsigned char midi_byte, addr_byte; + struct snd_uart16550 *uart = substream->rmidi->private_data; + char first; + static unsigned long lasttime = 0; + + /* Interrupts are disabled during the updating of the tx_buff, + * since it is 'bad' to have two processes updating the same + * variables (ie buff_in & buff_out) + */ + + spin_lock_irqsave(&uart->open_lock, flags); + + if (uart->irq < 0) /* polling */ + snd_uart16550_io_loop(uart); + + if (uart->adaptor == SNDRV_SERIAL_MS124W_MB) { + while (1) { + /* buffer full? */ + /* in this mode we need two bytes of space */ + if (uart->buff_in_count > TX_BUFF_SIZE - 2) + break; + if (snd_rawmidi_transmit(substream, &midi_byte, 1) != 1) + break; +#ifdef SNDRV_SERIAL_MS124W_MB_NOCOMBO + /* select exactly one of the four ports */ + addr_byte = (1 << (substream->number + 4)) | 0x08; +#else + /* select any combination of the four ports */ + addr_byte = (substream->number << 4) | 0x08; + /* ...except none */ + if (addr_byte == 0x08) + addr_byte = 0xf8; +#endif + snd_uart16550_output_byte(uart, substream, addr_byte); + /* send midi byte */ + snd_uart16550_output_byte(uart, substream, midi_byte); + } + } else { + first = 0; + while (snd_rawmidi_transmit_peek(substream, &midi_byte, 1) == 1) { + /* Also send F5 after 3 seconds with no data + * to handle device disconnect */ + if (first == 0 && + (uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS || + uart->adaptor == SNDRV_SERIAL_GENERIC) && + (uart->prev_out != substream->number || + time_after(jiffies, lasttime + 3*HZ))) { + + if (snd_uart16550_buffer_can_write(uart, 3)) { + /* Roland Soundcanvas part selection */ + /* If this substream of the data is + * different previous substream + * in this uart, send the change part + * event + */ + uart->prev_out = substream->number; + /* change part */ + snd_uart16550_output_byte(uart, substream, + 0xf5); + /* data */ + snd_uart16550_output_byte(uart, substream, + uart->prev_out + 1); + /* If midi_byte is a data byte, + * send the previous status byte */ + if (midi_byte < 0x80 && + uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS) + snd_uart16550_output_byte(uart, substream, uart->prev_status[uart->prev_out]); + } else if (!uart->drop_on_full) + break; + + } + + /* send midi byte */ + if (!snd_uart16550_output_byte(uart, substream, midi_byte) && + !uart->drop_on_full ) + break; + + if (midi_byte >= 0x80 && midi_byte < 0xf0) + uart->prev_status[uart->prev_out] = midi_byte; + first = 1; + + snd_rawmidi_transmit_ack( substream, 1 ); + } + lasttime = jiffies; + } + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +static void snd_uart16550_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (up) + uart->filemode |= SERIAL_MODE_OUTPUT_TRIGGERED; + else + uart->filemode &= ~SERIAL_MODE_OUTPUT_TRIGGERED; + spin_unlock_irqrestore(&uart->open_lock, flags); + if (up) + snd_uart16550_output_write(substream); +} + +static const struct snd_rawmidi_ops snd_uart16550_output = +{ + .open = snd_uart16550_output_open, + .close = snd_uart16550_output_close, + .trigger = snd_uart16550_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_uart16550_input = +{ + .open = snd_uart16550_input_open, + .close = snd_uart16550_input_close, + .trigger = snd_uart16550_input_trigger, +}; + +static int snd_uart16550_free(struct snd_uart16550 *uart) +{ + if (uart->irq >= 0) + free_irq(uart->irq, uart); + release_and_free_resource(uart->res_base); + kfree(uart); + return 0; +}; + +static int snd_uart16550_dev_free(struct snd_device *device) +{ + struct snd_uart16550 *uart = device->device_data; + return snd_uart16550_free(uart); +} + +static int snd_uart16550_create(struct snd_card *card, + unsigned long iobase, + int irq, + unsigned int speed, + unsigned int base, + int adaptor, + int droponfull, + struct snd_uart16550 **ruart) +{ + static struct snd_device_ops ops = { + .dev_free = snd_uart16550_dev_free, + }; + struct snd_uart16550 *uart; + int err; + + + if ((uart = kzalloc(sizeof(*uart), GFP_KERNEL)) == NULL) + return -ENOMEM; + uart->adaptor = adaptor; + uart->card = card; + spin_lock_init(&uart->open_lock); + uart->irq = -1; + uart->base = iobase; + uart->drop_on_full = droponfull; + + if ((err = snd_uart16550_detect(uart)) <= 0) { + printk(KERN_ERR "no UART detected at 0x%lx\n", iobase); + snd_uart16550_free(uart); + return -ENODEV; + } + + if (irq >= 0 && irq != SNDRV_AUTO_IRQ) { + if (request_irq(irq, snd_uart16550_interrupt, + 0, "Serial MIDI", uart)) { + snd_printk(KERN_WARNING + "irq %d busy. Using Polling.\n", irq); + } else { + uart->irq = irq; + } + } + uart->divisor = base / speed; + uart->speed = base / (unsigned int)uart->divisor; + uart->speed_base = base; + uart->prev_out = -1; + uart->prev_in = 0; + uart->rstatus = 0; + memset(uart->prev_status, 0x80, sizeof(unsigned char) * SNDRV_SERIAL_MAX_OUTS); + timer_setup(&uart->buffer_timer, snd_uart16550_buffer_timer, 0); + uart->timer_running = 0; + + /* Register device */ + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, uart, &ops)) < 0) { + snd_uart16550_free(uart); + return err; + } + + switch (uart->adaptor) { + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* MS-124W can draw power from RTS and DTR if they + are in opposite states. */ + outb(UART_MCR_RTS | (0&UART_MCR_DTR), uart->base + UART_MCR); + break; + case SNDRV_SERIAL_MS124T: + /* MS-124T can draw power from RTS and/or DTR (preferably + both) if they are asserted. */ + outb(UART_MCR_RTS | UART_MCR_DTR, uart->base + UART_MCR); + break; + default: + break; + } + + if (ruart) + *ruart = uart; + + return 0; +} + +static void snd_uart16550_substreams(struct snd_rawmidi_str *stream) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + sprintf(substream->name, "Serial MIDI %d", substream->number + 1); + } +} + +static int snd_uart16550_rmidi(struct snd_uart16550 *uart, int device, + int outs, int ins, + struct snd_rawmidi **rmidi) +{ + struct snd_rawmidi *rrawmidi; + int err; + + err = snd_rawmidi_new(uart->card, "UART Serial MIDI", device, + outs, ins, &rrawmidi); + if (err < 0) + return err; + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_uart16550_input); + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_uart16550_output); + strcpy(rrawmidi->name, "Serial MIDI"); + snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]); + snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]); + rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rrawmidi->private_data = uart; + if (rmidi) + *rmidi = rrawmidi; + return 0; +} + +static int snd_serial_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_uart16550 *uart; + int err; + int dev = devptr->id; + + switch (adaptor[dev]) { + case SNDRV_SERIAL_SOUNDCANVAS: + ins[dev] = 1; + break; + case SNDRV_SERIAL_MS124T: + case SNDRV_SERIAL_MS124W_SA: + outs[dev] = 1; + ins[dev] = 1; + break; + case SNDRV_SERIAL_MS124W_MB: + outs[dev] = 16; + ins[dev] = 1; + break; + case SNDRV_SERIAL_GENERIC: + break; + default: + snd_printk(KERN_ERR + "Adaptor type is out of range 0-%d (%d)\n", + SNDRV_SERIAL_MAX_ADAPTOR, adaptor[dev]); + return -ENODEV; + } + + if (outs[dev] < 1 || outs[dev] > SNDRV_SERIAL_MAX_OUTS) { + snd_printk(KERN_ERR + "Count of outputs is out of range 1-%d (%d)\n", + SNDRV_SERIAL_MAX_OUTS, outs[dev]); + return -ENODEV; + } + + if (ins[dev] < 1 || ins[dev] > SNDRV_SERIAL_MAX_INS) { + snd_printk(KERN_ERR + "Count of inputs is out of range 1-%d (%d)\n", + SNDRV_SERIAL_MAX_INS, ins[dev]); + return -ENODEV; + } + + err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) + return err; + + strcpy(card->driver, "Serial"); + strcpy(card->shortname, "Serial MIDI (UART16550A)"); + + if ((err = snd_uart16550_create(card, + port[dev], + irq[dev], + speed[dev], + base[dev], + adaptor[dev], + droponfull[dev], + &uart)) < 0) + goto _err; + + err = snd_uart16550_rmidi(uart, 0, outs[dev], ins[dev], &uart->rmidi); + if (err < 0) + goto _err; + + sprintf(card->longname, "%s [%s] at %#lx, irq %d", + card->shortname, + adaptor_names[uart->adaptor], + uart->base, + uart->irq); + + if ((err = snd_card_register(card)) < 0) + goto _err; + + platform_set_drvdata(devptr, card); + return 0; + + _err: + snd_card_free(card); + return err; +} + +static int snd_serial_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +#define SND_SERIAL_DRIVER "snd_serial_u16550" + +static struct platform_driver snd_serial_driver = { + .probe = snd_serial_probe, + .remove = snd_serial_remove, + .driver = { + .name = SND_SERIAL_DRIVER, + }, +}; + +static void snd_serial_unregister_all(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(devices); ++i) + platform_device_unregister(devices[i]); + platform_driver_unregister(&snd_serial_driver); +} + +static int __init alsa_card_serial_init(void) +{ + int i, cards, err; + + if ((err = platform_driver_register(&snd_serial_driver)) < 0) + return err; + + cards = 0; + for (i = 0; i < SNDRV_CARDS; i++) { + struct platform_device *device; + if (! enable[i]) + continue; + device = platform_device_register_simple(SND_SERIAL_DRIVER, + i, NULL, 0); + if (IS_ERR(device)) + continue; + if (!platform_get_drvdata(device)) { + platform_device_unregister(device); + continue; + } + devices[i] = device; + cards++; + } + if (! cards) { +#ifdef MODULE + printk(KERN_ERR "serial midi soundcard not found or device busy\n"); +#endif + snd_serial_unregister_all(); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_serial_exit(void) +{ + snd_serial_unregister_all(); +} + +module_init(alsa_card_serial_init) +module_exit(alsa_card_serial_exit) diff --git a/sound/drivers/virmidi.c b/sound/drivers/virmidi.c new file mode 100644 index 000000000..33ef13a72 --- /dev/null +++ b/sound/drivers/virmidi.c @@ -0,0 +1,199 @@ +/* + * Dummy soundcard for virtual rawmidi devices + * + * Copyright (c) 2000 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * VIRTUAL RAW MIDI DEVICE CARDS + * + * This dummy card contains up to 4 virtual rawmidi devices. + * They are not real rawmidi devices but just associated with sequencer + * clients, so that any input/output sources can be connected as a raw + * MIDI device arbitrary. + * Also, multiple access is allowed to a single rawmidi device. + * + * Typical usage is like following: + * - Load snd-virmidi module. + * # modprobe snd-virmidi index=2 + * Then, sequencer clients 72:0 to 75:0 will be created, which are + * mapped from /dev/snd/midiC1D0 to /dev/snd/midiC1D3, respectively. + * + * - Connect input/output via aconnect. + * % aconnect 64:0 72:0 # keyboard input redirection 64:0 -> 72:0 + * % aconnect 72:0 65:0 # output device redirection 72:0 -> 65:0 + * + * - Run application using a midi device (eg. /dev/snd/midiC1D0) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* hack: OSS defines midi_devs, so undefine it (versioned symbols) */ +#undef midi_devs + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("Dummy soundcard for virtual rawmidi devices"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual rawmidi device}}"); + +#define MAX_MIDI_DEVICES 4 + +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] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; +static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for virmidi soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for virmidi soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this soundcard."); +module_param_array(midi_devs, int, NULL, 0444); +MODULE_PARM_DESC(midi_devs, "MIDI devices # (1-4)"); + +struct snd_card_virmidi { + struct snd_card *card; + struct snd_rawmidi *midi[MAX_MIDI_DEVICES]; +}; + +static struct platform_device *devices[SNDRV_CARDS]; + + +static int snd_virmidi_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_card_virmidi *vmidi; + int idx, err; + int dev = devptr->id; + + err = snd_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_virmidi), &card); + if (err < 0) + return err; + vmidi = card->private_data; + vmidi->card = card; + + if (midi_devs[dev] > MAX_MIDI_DEVICES) { + snd_printk(KERN_WARNING + "too much midi devices for virmidi %d: force to use %d\n", + dev, MAX_MIDI_DEVICES); + midi_devs[dev] = MAX_MIDI_DEVICES; + } + for (idx = 0; idx < midi_devs[dev]; idx++) { + struct snd_rawmidi *rmidi; + struct snd_virmidi_dev *rdev; + + err = snd_virmidi_new(card, idx, &rmidi); + if (err < 0) + goto __nodev; + rdev = rmidi->private_data; + vmidi->midi[idx] = rmidi; + strcpy(rmidi->name, "Virtual Raw MIDI"); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + } + + strcpy(card->driver, "VirMIDI"); + strcpy(card->shortname, "VirMIDI"); + sprintf(card->longname, "Virtual MIDI Card %i", dev + 1); + + err = snd_card_register(card); + if (!err) { + platform_set_drvdata(devptr, card); + return 0; + } +__nodev: + snd_card_free(card); + return err; +} + +static int snd_virmidi_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +#define SND_VIRMIDI_DRIVER "snd_virmidi" + +static struct platform_driver snd_virmidi_driver = { + .probe = snd_virmidi_probe, + .remove = snd_virmidi_remove, + .driver = { + .name = SND_VIRMIDI_DRIVER, + }, +}; + +static void snd_virmidi_unregister_all(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(devices); ++i) + platform_device_unregister(devices[i]); + platform_driver_unregister(&snd_virmidi_driver); +} + +static int __init alsa_card_virmidi_init(void) +{ + int i, cards, err; + + err = platform_driver_register(&snd_virmidi_driver); + if (err < 0) + return err; + + cards = 0; + for (i = 0; i < SNDRV_CARDS; i++) { + struct platform_device *device; + + if (!enable[i]) + continue; + device = platform_device_register_simple(SND_VIRMIDI_DRIVER, + i, NULL, 0); + if (IS_ERR(device)) + continue; + if (!platform_get_drvdata(device)) { + platform_device_unregister(device); + continue; + } + devices[i] = device; + cards++; + } + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "Card-VirMIDI soundcard not found or device busy\n"); +#endif + snd_virmidi_unregister_all(); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_virmidi_exit(void) +{ + snd_virmidi_unregister_all(); +} + +module_init(alsa_card_virmidi_init) +module_exit(alsa_card_virmidi_exit) diff --git a/sound/drivers/vx/Makefile b/sound/drivers/vx/Makefile new file mode 100644 index 000000000..9a168a3c1 --- /dev/null +++ b/sound/drivers/vx/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-vx-lib-objs := vx_core.o vx_hwdep.o vx_pcm.o vx_mixer.o vx_cmd.o vx_uer.o + +obj-$(CONFIG_SND_VX_LIB) += snd-vx-lib.o diff --git a/sound/drivers/vx/vx_cmd.c b/sound/drivers/vx/vx_cmd.c new file mode 100644 index 000000000..23f4857f0 --- /dev/null +++ b/sound/drivers/vx/vx_cmd.c @@ -0,0 +1,109 @@ +/* + * Driver for Digigram VX soundcards + * + * DSP commands + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include "vx_cmd.h" + +/* + * Array of DSP commands + */ +static struct vx_cmd_info vx_dsp_cmds[] = { +[CMD_VERSION] = { 0x010000, 2, RMH_SSIZE_FIXED, 1 }, +[CMD_SUPPORTED] = { 0x020000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_TEST_IT] = { 0x040000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_SEND_IRQA] = { 0x070001, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_IBL] = { 0x080000, 1, RMH_SSIZE_FIXED, 4 }, +[CMD_ASYNC] = { 0x0A0000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_RES_PIPE] = { 0x400000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_FREE_PIPE] = { 0x410000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_CONF_PIPE] = { 0x42A101, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_ABORT_CONF_PIPE] = { 0x42A100, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_PARAM_OUTPUT_PIPE] = { 0x43A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_STOP_PIPE] = { 0x470004, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_PIPE_STATE] = { 0x480000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_PIPE_SPL_COUNT] = { 0x49A000, 2, RMH_SSIZE_FIXED, 2 }, +[CMD_CAN_START_PIPE] = { 0x4b0000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_SIZE_HBUFFER] = { 0x4C0000, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_START_STREAM] = { 0x80A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_START_ONE_STREAM] = { 0x800000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_PAUSE_STREAM] = { 0x81A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_PAUSE_ONE_STREAM] = { 0x810000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM_OUT_LEVEL_ADJUST] = { 0x828000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_STOP_STREAM] = { 0x830000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_FORMAT_STREAM_OUT] = { 0x868000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_FORMAT_STREAM_IN] = { 0x878800, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_STREAM_STATE] = { 0x890001, 2, RMH_SSIZE_FIXED, 1 }, +[CMD_DROP_BYTES_AWAY] = { 0x8A8000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_REMAINING_BYTES] = { 0x8D0800, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_CONNECT_AUDIO] = { 0xC10000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_AUDIO_LEVEL_ADJUST] = { 0xC2A000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_AUDIO_VU_PIC_METER] = { 0xC3A003, 2, RMH_SSIZE_FIXED, 1 }, +[CMD_GET_AUDIO_LEVELS] = { 0xC4A000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_NOTIFY_EVENT] = { 0x4D0000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_INFO_NOTIFIED] = { 0x0B0000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_ACCESS_IO_FCT] = { 0x098000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_STATUS_R_BUFFERS] = { 0x440000, 1, RMH_SSIZE_ARG, 0 }, +[CMD_UPDATE_R_BUFFERS] = { 0x848000, 4, RMH_SSIZE_FIXED, 0 }, +[CMD_LOAD_EFFECT_CONTEXT] = { 0x0c8000, 3, RMH_SSIZE_FIXED, 1 }, +[CMD_EFFECT_ONE_PIPE] = { 0x458000, 0, RMH_SSIZE_FIXED, 0 }, +[CMD_MODIFY_CLOCK] = { 0x0d0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM1_OUT_SET_N_LEVELS] ={ 0x858000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_PURGE_STREAM_DCMDS] = { 0x8b8000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_NOTIFY_PIPE_TIME] = { 0x4e0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_LOAD_EFFECT_CONTEXT_PACKET] = { 0x0c8000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_RELIC_R_BUFFER] = { 0x8e0800, 1, RMH_SSIZE_FIXED, 1 }, +[CMD_RESYNC_AUDIO_INPUTS] = { 0x0e0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_NOTIFY_STREAM_TIME] = { 0x8f0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM_SAMPLE_COUNT] = { 0x900000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_CONFIG_TIME_CODE] = { 0x050000, 2, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_TIME_CODE] = { 0x060000, 1, RMH_SSIZE_FIXED, 5 }, +[CMD_MANAGE_SIGNAL] = { 0x0f0000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_PARAMETER_STREAM_OUT] = { 0x91A000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_READ_BOARD_FREQ] = { 0x030000, 1, RMH_SSIZE_FIXED, 2 }, +[CMD_GET_STREAM_LEVELS] = { 0x8c0000, 1, RMH_SSIZE_FIXED, 3 }, +[CMD_PURGE_PIPE_DCMDS] = { 0x4f8000, 3, RMH_SSIZE_FIXED, 0 }, +// [CMD_SET_STREAM_OUT_EFFECTS] = { 0x888000, 34, RMH_SSIZE_FIXED, 0 }, +// [CMD_GET_STREAM_OUT_EFFECTS] = { 0x928000, 2, RMH_SSIZE_FIXED, 32 }, +[CMD_CONNECT_MONITORING] = { 0xC00000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_STREAM2_OUT_SET_N_LEVELS] = { 0x938000, 3, RMH_SSIZE_FIXED, 0 }, +[CMD_CANCEL_R_BUFFERS] = { 0x948000, 4, RMH_SSIZE_FIXED, 0 }, +[CMD_NOTIFY_END_OF_BUFFER] = { 0x950000, 1, RMH_SSIZE_FIXED, 0 }, +[CMD_GET_STREAM_VU_METER] = { 0x95A000, 2, RMH_SSIZE_ARG, 0 }, +}; + +/** + * vx_init_rmh - initialize the RMH instance + * @rmh: the rmh pointer to be initialized + * @cmd: the rmh command to be set + */ +void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd) +{ + if (snd_BUG_ON(cmd >= CMD_LAST_INDEX)) + return; + rmh->LgCmd = vx_dsp_cmds[cmd].length; + rmh->LgStat = vx_dsp_cmds[cmd].st_length; + rmh->DspStat = vx_dsp_cmds[cmd].st_type; + rmh->Cmd[0] = vx_dsp_cmds[cmd].opcode; +} + diff --git a/sound/drivers/vx/vx_cmd.h b/sound/drivers/vx/vx_cmd.h new file mode 100644 index 000000000..a85248ba3 --- /dev/null +++ b/sound/drivers/vx/vx_cmd.h @@ -0,0 +1,246 @@ +/* + * Driver for Digigram VX soundcards + * + * Definitions of DSP commands + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __VX_CMD_H +#define __VX_CMD_H + +enum { + CMD_VERSION, + CMD_SUPPORTED, + CMD_TEST_IT, + CMD_SEND_IRQA, + CMD_IBL, + CMD_ASYNC, + CMD_RES_PIPE, + CMD_FREE_PIPE, + CMD_CONF_PIPE, + CMD_ABORT_CONF_PIPE, + CMD_PARAM_OUTPUT_PIPE, + CMD_STOP_PIPE, + CMD_PIPE_STATE, + CMD_PIPE_SPL_COUNT, + CMD_CAN_START_PIPE, + CMD_SIZE_HBUFFER, + CMD_START_STREAM, + CMD_START_ONE_STREAM, + CMD_PAUSE_STREAM, + CMD_PAUSE_ONE_STREAM, + CMD_STREAM_OUT_LEVEL_ADJUST, + CMD_STOP_STREAM, + CMD_FORMAT_STREAM_OUT, + CMD_FORMAT_STREAM_IN, + CMD_GET_STREAM_STATE, + CMD_DROP_BYTES_AWAY, + CMD_GET_REMAINING_BYTES, + CMD_CONNECT_AUDIO, + CMD_AUDIO_LEVEL_ADJUST, + CMD_AUDIO_VU_PIC_METER, + CMD_GET_AUDIO_LEVELS, + CMD_GET_NOTIFY_EVENT, + CMD_INFO_NOTIFIED, + CMD_ACCESS_IO_FCT, + CMD_STATUS_R_BUFFERS, + CMD_UPDATE_R_BUFFERS, + CMD_LOAD_EFFECT_CONTEXT, + CMD_EFFECT_ONE_PIPE, + CMD_MODIFY_CLOCK, + CMD_STREAM1_OUT_SET_N_LEVELS, + CMD_PURGE_STREAM_DCMDS, + CMD_NOTIFY_PIPE_TIME, + CMD_LOAD_EFFECT_CONTEXT_PACKET, + CMD_RELIC_R_BUFFER, + CMD_RESYNC_AUDIO_INPUTS, + CMD_NOTIFY_STREAM_TIME, + CMD_STREAM_SAMPLE_COUNT, + CMD_CONFIG_TIME_CODE, + CMD_GET_TIME_CODE, + CMD_MANAGE_SIGNAL, + CMD_PARAMETER_STREAM_OUT, + CMD_READ_BOARD_FREQ, + CMD_GET_STREAM_LEVELS, + CMD_PURGE_PIPE_DCMDS, + // CMD_SET_STREAM_OUT_EFFECTS, + // CMD_GET_STREAM_OUT_EFFECTS, + CMD_CONNECT_MONITORING, + CMD_STREAM2_OUT_SET_N_LEVELS, + CMD_CANCEL_R_BUFFERS, + CMD_NOTIFY_END_OF_BUFFER, + CMD_GET_STREAM_VU_METER, + CMD_LAST_INDEX +}; + +struct vx_cmd_info { + unsigned int opcode; /* command word */ + int length; /* command length (in words) */ + int st_type; /* status type (RMH_SSIZE_XXX) */ + int st_length; /* fixed length */ +}; + +/* Family and code op of some DSP requests. */ +#define CODE_OP_PIPE_TIME 0x004e0000 +#define CODE_OP_START_STREAM 0x00800000 +#define CODE_OP_PAUSE_STREAM 0x00810000 +#define CODE_OP_OUT_STREAM_LEVEL 0x00820000 +#define CODE_OP_UPDATE_R_BUFFERS 0x00840000 +#define CODE_OP_OUT_STREAM1_LEVEL_CURVE 0x00850000 +#define CODE_OP_OUT_STREAM2_LEVEL_CURVE 0x00930000 +#define CODE_OP_OUT_STREAM_FORMAT 0x00860000 +#define CODE_OP_STREAM_TIME 0x008f0000 +#define CODE_OP_OUT_STREAM_EXTRAPARAMETER 0x00910000 +#define CODE_OP_OUT_AUDIO_LEVEL 0x00c20000 + +#define NOTIFY_LAST_COMMAND 0x00400000 + +/* Values for a user delay */ +#define DC_DIFFERED_DELAY (1<Cmd[0] |= COMMAND_RECORD_MASK; + rmh->Cmd[0] |= (((u32)param1 & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD; + + if (param2) + rmh->Cmd[0] |= ((u32)param2 & MASK_FIRST_FIELD) & MASK_DSP_WORD; + +} + +/** + * vx_set_stream_cmd_params - fill first command word for stream commands + * @rmh: the rmh to be modified + * @is_capture: 0 = playback, 1 = capture operation + * @pipe: the pipe index (zero-based) + */ +static inline void vx_set_stream_cmd_params(struct vx_rmh *rmh, int is_capture, int pipe) +{ + if (is_capture) + rmh->Cmd[0] |= COMMAND_RECORD_MASK; + rmh->Cmd[0] |= (((u32)pipe & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD; +} + +#endif /* __VX_CMD_H */ diff --git a/sound/drivers/vx/vx_core.c b/sound/drivers/vx/vx_core.c new file mode 100644 index 000000000..04368dd59 --- /dev/null +++ b/sound/drivers/vx/vx_core.c @@ -0,0 +1,817 @@ +/* + * Driver for Digigram VX soundcards + * + * Hardware core part + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vx_cmd.h" + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("Common routines for Digigram VX drivers"); +MODULE_LICENSE("GPL"); + + +/* + * vx_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. + */ +int snd_vx_check_reg_bit(struct vx_core *chip, int reg, int mask, int bit, int time) +{ + unsigned long end_time = jiffies + (time * HZ + 999) / 1000; + static char *reg_names[VX_REG_MAX] = { + "ICR", "CVR", "ISR", "IVR", "RXH", "RXM", "RXL", + "DMA", "CDSP", "RFREQ", "RUER/V2", "DATA", "MEMIRQ", + "ACQ", "BIT0", "BIT1", "MIC0", "MIC1", "MIC2", + "MIC3", "INTCSR", "CNTRL", "GPIOC", + "LOFREQ", "HIFREQ", "CSUER", "RUER" + }; + + do { + if ((snd_vx_inb(chip, reg) & mask) == bit) + return 0; + //msleep(10); + } while (time_after_eq(end_time, jiffies)); + snd_printd(KERN_DEBUG "vx_check_reg_bit: timeout, reg=%s, mask=0x%x, val=0x%x\n", reg_names[reg], mask, snd_vx_inb(chip, reg)); + return -EIO; +} + +EXPORT_SYMBOL(snd_vx_check_reg_bit); + +/* + * vx_send_irq_dsp - set command irq bit + * @num: the requested IRQ type, IRQ_XXX + * + * this triggers the specified IRQ request + * returns 0 if successful, or a negative error code. + * + */ +static int vx_send_irq_dsp(struct vx_core *chip, int num) +{ + int nirq; + + /* wait for Hc = 0 */ + if (snd_vx_check_reg_bit(chip, VX_CVR, CVR_HC, 0, 200) < 0) + return -EIO; + + nirq = num; + if (vx_has_new_dsp(chip)) + nirq += VXP_IRQ_OFFSET; + vx_outb(chip, CVR, (nirq >> 1) | CVR_HC); + return 0; +} + + +/* + * vx_reset_chk - reset CHK bit on ISR + * + * returns 0 if successful, or a negative error code. + */ +static int vx_reset_chk(struct vx_core *chip) +{ + /* Reset irq CHK */ + if (vx_send_irq_dsp(chip, IRQ_RESET_CHK) < 0) + return -EIO; + /* Wait until CHK = 0 */ + if (vx_check_isr(chip, ISR_CHK, 0, 200) < 0) + return -EIO; + return 0; +} + +/* + * vx_transfer_end - terminate message transfer + * @cmd: IRQ message to send (IRQ_MESS_XXX_END) + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * NB: call with mutex held! + */ +static int vx_transfer_end(struct vx_core *chip, int cmd) +{ + int err; + + if ((err = vx_reset_chk(chip)) < 0) + return err; + + /* irq MESS_READ/WRITE_END */ + if ((err = vx_send_irq_dsp(chip, cmd)) < 0) + return err; + + /* Wait CHK = 1 */ + if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0) + return err; + + /* If error, Read RX */ + if ((err = vx_inb(chip, ISR)) & ISR_ERR) { + if ((err = vx_wait_for_rx_full(chip)) < 0) { + snd_printd(KERN_DEBUG "transfer_end: error in rx_full\n"); + return err; + } + err = vx_inb(chip, RXH) << 16; + err |= vx_inb(chip, RXM) << 8; + err |= vx_inb(chip, RXL); + snd_printd(KERN_DEBUG "transfer_end: error = 0x%x\n", err); + return -(VX_ERR_MASK | err); + } + return 0; +} + +/* + * vx_read_status - return the status rmh + * @rmh: rmh record to store the status + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * NB: call with mutex held! + */ +static int vx_read_status(struct vx_core *chip, struct vx_rmh *rmh) +{ + int i, err, val, size; + + /* no read necessary? */ + if (rmh->DspStat == RMH_SSIZE_FIXED && rmh->LgStat == 0) + return 0; + + /* Wait for RX full (with timeout protection) + * The first word of status is in RX + */ + err = vx_wait_for_rx_full(chip); + if (err < 0) + return err; + + /* Read RX */ + val = vx_inb(chip, RXH) << 16; + val |= vx_inb(chip, RXM) << 8; + val |= vx_inb(chip, RXL); + + /* If status given by DSP, let's decode its size */ + switch (rmh->DspStat) { + case RMH_SSIZE_ARG: + size = val & 0xff; + rmh->Stat[0] = val & 0xffff00; + rmh->LgStat = size + 1; + break; + case RMH_SSIZE_MASK: + /* Let's count the arg numbers from a mask */ + rmh->Stat[0] = val; + size = 0; + while (val) { + if (val & 0x01) + size++; + val >>= 1; + } + rmh->LgStat = size + 1; + break; + default: + /* else retrieve the status length given by the driver */ + size = rmh->LgStat; + rmh->Stat[0] = val; /* Val is the status 1st word */ + size--; /* hence adjust remaining length */ + break; + } + + if (size < 1) + return 0; + if (snd_BUG_ON(size >= SIZE_MAX_STATUS)) + return -EINVAL; + + for (i = 1; i <= size; i++) { + /* trigger an irq MESS_WRITE_NEXT */ + err = vx_send_irq_dsp(chip, IRQ_MESS_WRITE_NEXT); + if (err < 0) + return err; + /* Wait for RX full (with timeout protection) */ + err = vx_wait_for_rx_full(chip); + if (err < 0) + return err; + rmh->Stat[i] = vx_inb(chip, RXH) << 16; + rmh->Stat[i] |= vx_inb(chip, RXM) << 8; + rmh->Stat[i] |= vx_inb(chip, RXL); + } + + return vx_transfer_end(chip, IRQ_MESS_WRITE_END); +} + + +#define MASK_MORE_THAN_1_WORD_COMMAND 0x00008000 +#define MASK_1_WORD_COMMAND 0x00ff7fff + +/* + * vx_send_msg_nolock - send a DSP message and read back the status + * @rmh: the rmh record to send and receive + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * + * this function doesn't call mutex lock at all. + */ +int vx_send_msg_nolock(struct vx_core *chip, struct vx_rmh *rmh) +{ + int i, err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + if ((err = vx_reset_chk(chip)) < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: vx_reset_chk error\n"); + return err; + } + +#if 0 + printk(KERN_DEBUG "rmh: cmd = 0x%06x, length = %d, stype = %d\n", + rmh->Cmd[0], rmh->LgCmd, rmh->DspStat); + if (rmh->LgCmd > 1) { + printk(KERN_DEBUG " "); + for (i = 1; i < rmh->LgCmd; i++) + printk(KERN_CONT "0x%06x ", rmh->Cmd[i]); + printk(KERN_CONT "\n"); + } +#endif + /* Check bit M is set according to length of the command */ + if (rmh->LgCmd > 1) + rmh->Cmd[0] |= MASK_MORE_THAN_1_WORD_COMMAND; + else + rmh->Cmd[0] &= MASK_1_WORD_COMMAND; + + /* Wait for TX empty */ + if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: wait tx empty error\n"); + return err; + } + + /* Write Cmd[0] */ + vx_outb(chip, TXH, (rmh->Cmd[0] >> 16) & 0xff); + vx_outb(chip, TXM, (rmh->Cmd[0] >> 8) & 0xff); + vx_outb(chip, TXL, rmh->Cmd[0] & 0xff); + + /* Trigger irq MESSAGE */ + if ((err = vx_send_irq_dsp(chip, IRQ_MESSAGE)) < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: send IRQ_MESSAGE error\n"); + return err; + } + + /* Wait for CHK = 1 */ + if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0) + return err; + + /* If error, get error value from RX */ + if (vx_inb(chip, ISR) & ISR_ERR) { + if ((err = vx_wait_for_rx_full(chip)) < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: rx_full read error\n"); + return err; + } + err = vx_inb(chip, RXH) << 16; + err |= vx_inb(chip, RXM) << 8; + err |= vx_inb(chip, RXL); + snd_printd(KERN_DEBUG "msg got error = 0x%x at cmd[0]\n", err); + err = -(VX_ERR_MASK | err); + return err; + } + + /* Send the other words */ + if (rmh->LgCmd > 1) { + for (i = 1; i < rmh->LgCmd; i++) { + /* Wait for TX ready */ + if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: tx_ready error\n"); + return err; + } + + /* Write Cmd[i] */ + vx_outb(chip, TXH, (rmh->Cmd[i] >> 16) & 0xff); + vx_outb(chip, TXM, (rmh->Cmd[i] >> 8) & 0xff); + vx_outb(chip, TXL, rmh->Cmd[i] & 0xff); + + /* Trigger irq MESS_READ_NEXT */ + if ((err = vx_send_irq_dsp(chip, IRQ_MESS_READ_NEXT)) < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: IRQ_READ_NEXT error\n"); + return err; + } + } + /* Wait for TX empty */ + if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: TX_READY error\n"); + return err; + } + /* End of transfer */ + err = vx_transfer_end(chip, IRQ_MESS_READ_END); + if (err < 0) + return err; + } + + return vx_read_status(chip, rmh); +} + + +/* + * vx_send_msg - send a DSP message with mutex + * @rmh: the rmh record to send and receive + * + * returns 0 if successful, or a negative error code. + * see vx_send_msg_nolock(). + */ +int vx_send_msg(struct vx_core *chip, struct vx_rmh *rmh) +{ + int err; + + mutex_lock(&chip->lock); + err = vx_send_msg_nolock(chip, rmh); + mutex_unlock(&chip->lock); + return err; +} + + +/* + * vx_send_rih_nolock - send an RIH to xilinx + * @cmd: the command to send + * + * returns 0 if successful, or a negative error code. + * the error code can be VX-specific, retrieved via vx_get_error(). + * + * this function doesn't call mutex at all. + * + * unlike RMH, no command is sent to DSP. + */ +int vx_send_rih_nolock(struct vx_core *chip, int cmd) +{ + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + +#if 0 + printk(KERN_DEBUG "send_rih: cmd = 0x%x\n", cmd); +#endif + if ((err = vx_reset_chk(chip)) < 0) + return err; + /* send the IRQ */ + if ((err = vx_send_irq_dsp(chip, cmd)) < 0) + return err; + /* Wait CHK = 1 */ + if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0) + return err; + /* If error, read RX */ + if (vx_inb(chip, ISR) & ISR_ERR) { + if ((err = vx_wait_for_rx_full(chip)) < 0) + return err; + err = vx_inb(chip, RXH) << 16; + err |= vx_inb(chip, RXM) << 8; + err |= vx_inb(chip, RXL); + return -(VX_ERR_MASK | err); + } + return 0; +} + + +/* + * vx_send_rih - send an RIH with mutex + * @cmd: the command to send + * + * see vx_send_rih_nolock(). + */ +int vx_send_rih(struct vx_core *chip, int cmd) +{ + int err; + + mutex_lock(&chip->lock); + err = vx_send_rih_nolock(chip, cmd); + mutex_unlock(&chip->lock); + return err; +} + +#define END_OF_RESET_WAIT_TIME 500 /* us */ + +/** + * snd_vx_boot_xilinx - boot up the xilinx interface + * @chip: VX core instance + * @boot: the boot record to load + */ +int snd_vx_load_boot_image(struct vx_core *chip, const struct firmware *boot) +{ + unsigned int i; + int no_fillup = vx_has_new_dsp(chip); + + /* check the length of boot image */ + if (boot->size <= 0) + return -EINVAL; + if (boot->size % 3) + return -EINVAL; +#if 0 + { + /* more strict check */ + unsigned int c = ((u32)boot->data[0] << 16) | ((u32)boot->data[1] << 8) | boot->data[2]; + if (boot->size != (c + 2) * 3) + return -EINVAL; + } +#endif + + /* reset dsp */ + vx_reset_dsp(chip); + + udelay(END_OF_RESET_WAIT_TIME); /* another wait? */ + + /* download boot strap */ + for (i = 0; i < 0x600; i += 3) { + if (i >= boot->size) { + if (no_fillup) + break; + if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) { + snd_printk(KERN_ERR "dsp boot failed at %d\n", i); + return -EIO; + } + vx_outb(chip, TXH, 0); + vx_outb(chip, TXM, 0); + vx_outb(chip, TXL, 0); + } else { + const unsigned char *image = boot->data + i; + if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) { + snd_printk(KERN_ERR "dsp boot failed at %d\n", i); + return -EIO; + } + vx_outb(chip, TXH, image[0]); + vx_outb(chip, TXM, image[1]); + vx_outb(chip, TXL, image[2]); + } + } + return 0; +} + +EXPORT_SYMBOL(snd_vx_load_boot_image); + +/* + * vx_test_irq_src - query the source of interrupts + * + * called from irq handler only + */ +static int vx_test_irq_src(struct vx_core *chip, unsigned int *ret) +{ + int err; + + vx_init_rmh(&chip->irq_rmh, CMD_TEST_IT); + mutex_lock(&chip->lock); + err = vx_send_msg_nolock(chip, &chip->irq_rmh); + if (err < 0) + *ret = 0; + else + *ret = chip->irq_rmh.Stat[0]; + mutex_unlock(&chip->lock); + return err; +} + + +/* + * snd_vx_threaded_irq_handler - threaded irq handler + */ +irqreturn_t snd_vx_threaded_irq_handler(int irq, void *dev) +{ + struct vx_core *chip = dev; + unsigned int events; + + if (chip->chip_status & VX_STAT_IS_STALE) + return IRQ_HANDLED; + + if (vx_test_irq_src(chip, &events) < 0) + return IRQ_HANDLED; + +#if 0 + if (events & 0x000800) + printk(KERN_ERR "DSP Stream underrun ! IRQ events = 0x%x\n", events); +#endif + // printk(KERN_DEBUG "IRQ events = 0x%x\n", events); + + /* We must prevent any application using this DSP + * and block any further request until the application + * either unregisters or reloads the DSP + */ + if (events & FATAL_DSP_ERROR) { + snd_printk(KERN_ERR "vx_core: fatal DSP error!!\n"); + return IRQ_HANDLED; + } + + /* The start on time code conditions are filled (ie the time code + * received by the board is equal to one of those given to it). + */ + if (events & TIME_CODE_EVENT_PENDING) + ; /* so far, nothing to do yet */ + + /* The frequency has changed on the board (UER mode). */ + if (events & FREQUENCY_CHANGE_EVENT_PENDING) + vx_change_frequency(chip); + + /* update the pcm streams */ + vx_pcm_update_intr(chip, events); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(snd_vx_threaded_irq_handler); + +/** + * snd_vx_irq_handler - interrupt handler + * @irq: irq number + * @dev: VX core instance + */ +irqreturn_t snd_vx_irq_handler(int irq, void *dev) +{ + struct vx_core *chip = dev; + + if (! (chip->chip_status & VX_STAT_CHIP_INIT) || + (chip->chip_status & VX_STAT_IS_STALE)) + return IRQ_NONE; + if (! vx_test_and_ack(chip)) + return IRQ_WAKE_THREAD; + return IRQ_NONE; +} + +EXPORT_SYMBOL(snd_vx_irq_handler); + +/* + */ +static void vx_reset_board(struct vx_core *chip, int cold_reset) +{ + if (snd_BUG_ON(!chip->ops->reset_board)) + return; + + /* current source, later sync'ed with target */ + chip->audio_source = VX_AUDIO_SRC_LINE; + if (cold_reset) { + chip->audio_source_target = chip->audio_source; + chip->clock_source = INTERNAL_QUARTZ; + chip->clock_mode = VX_CLOCK_MODE_AUTO; + chip->freq = 48000; + chip->uer_detected = VX_UER_MODE_NOT_PRESENT; + chip->uer_bits = SNDRV_PCM_DEFAULT_CON_SPDIF; + } + + chip->ops->reset_board(chip, cold_reset); + + vx_reset_codec(chip, cold_reset); + + vx_set_internal_clock(chip, chip->freq); + + /* Reset the DSP */ + vx_reset_dsp(chip); + + if (vx_is_pcmcia(chip)) { + /* Acknowledge any pending IRQ and reset the MEMIRQ flag. */ + vx_test_and_ack(chip); + vx_validate_irq(chip, 1); + } + + /* init CBits */ + vx_set_iec958_status(chip, chip->uer_bits); +} + + +/* + * proc interface + */ + +static void vx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct vx_core *chip = entry->private_data; + static char *audio_src_vxp[] = { "Line", "Mic", "Digital" }; + static char *audio_src_vx2[] = { "Analog", "Analog", "Digital" }; + static char *clock_mode[] = { "Auto", "Internal", "External" }; + static char *clock_src[] = { "Internal", "External" }; + static char *uer_type[] = { "Consumer", "Professional", "Not Present" }; + + snd_iprintf(buffer, "%s\n", chip->card->longname); + snd_iprintf(buffer, "Xilinx Firmware: %s\n", + chip->chip_status & VX_STAT_XILINX_LOADED ? "Loaded" : "No"); + snd_iprintf(buffer, "Device Initialized: %s\n", + chip->chip_status & VX_STAT_DEVICE_INIT ? "Yes" : "No"); + snd_iprintf(buffer, "DSP audio info:"); + if (chip->audio_info & VX_AUDIO_INFO_REAL_TIME) + snd_iprintf(buffer, " realtime"); + if (chip->audio_info & VX_AUDIO_INFO_OFFLINE) + snd_iprintf(buffer, " offline"); + if (chip->audio_info & VX_AUDIO_INFO_MPEG1) + snd_iprintf(buffer, " mpeg1"); + if (chip->audio_info & VX_AUDIO_INFO_MPEG2) + snd_iprintf(buffer, " mpeg2"); + if (chip->audio_info & VX_AUDIO_INFO_LINEAR_8) + snd_iprintf(buffer, " linear8"); + if (chip->audio_info & VX_AUDIO_INFO_LINEAR_16) + snd_iprintf(buffer, " linear16"); + if (chip->audio_info & VX_AUDIO_INFO_LINEAR_24) + snd_iprintf(buffer, " linear24"); + snd_iprintf(buffer, "\n"); + snd_iprintf(buffer, "Input Source: %s\n", vx_is_pcmcia(chip) ? + audio_src_vxp[chip->audio_source] : + audio_src_vx2[chip->audio_source]); + snd_iprintf(buffer, "Clock Mode: %s\n", clock_mode[chip->clock_mode]); + snd_iprintf(buffer, "Clock Source: %s\n", clock_src[chip->clock_source]); + snd_iprintf(buffer, "Frequency: %d\n", chip->freq); + snd_iprintf(buffer, "Detected Frequency: %d\n", chip->freq_detected); + snd_iprintf(buffer, "Detected UER type: %s\n", uer_type[chip->uer_detected]); + snd_iprintf(buffer, "Min/Max/Cur IBL: %d/%d/%d (granularity=%d)\n", + chip->ibl.min_size, chip->ibl.max_size, chip->ibl.size, + chip->ibl.granularity); +} + +static void vx_proc_init(struct vx_core *chip) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "vx-status", &entry)) + snd_info_set_text_ops(entry, chip, vx_proc_read); +} + + +/** + * snd_vx_dsp_boot - load the DSP boot + * @chip: VX core instance + * @boot: firmware data + */ +int snd_vx_dsp_boot(struct vx_core *chip, const struct firmware *boot) +{ + int err; + int cold_reset = !(chip->chip_status & VX_STAT_DEVICE_INIT); + + vx_reset_board(chip, cold_reset); + vx_validate_irq(chip, 0); + + if ((err = snd_vx_load_boot_image(chip, boot)) < 0) + return err; + msleep(10); + + return 0; +} + +EXPORT_SYMBOL(snd_vx_dsp_boot); + +/** + * snd_vx_dsp_load - load the DSP image + * @chip: VX core instance + * @dsp: firmware data + */ +int snd_vx_dsp_load(struct vx_core *chip, const struct firmware *dsp) +{ + unsigned int i; + int err; + unsigned int csum = 0; + const unsigned char *image, *cptr; + + if (dsp->size % 3) + return -EINVAL; + + vx_toggle_dac_mute(chip, 1); + + /* Transfert data buffer from PC to DSP */ + for (i = 0; i < dsp->size; i += 3) { + image = dsp->data + i; + /* Wait DSP ready for a new read */ + if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) { + printk(KERN_ERR + "dsp loading error at position %d\n", i); + return err; + } + cptr = image; + csum ^= *cptr; + csum = (csum >> 24) | (csum << 8); + vx_outb(chip, TXH, *cptr++); + csum ^= *cptr; + csum = (csum >> 24) | (csum << 8); + vx_outb(chip, TXM, *cptr++); + csum ^= *cptr; + csum = (csum >> 24) | (csum << 8); + vx_outb(chip, TXL, *cptr++); + } + snd_printdd(KERN_DEBUG "checksum = 0x%08x\n", csum); + + msleep(200); + + if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0) + return err; + + vx_toggle_dac_mute(chip, 0); + + vx_test_and_ack(chip); + vx_validate_irq(chip, 1); + + return 0; +} + +EXPORT_SYMBOL(snd_vx_dsp_load); + +#ifdef CONFIG_PM +/* + * suspend + */ +int snd_vx_suspend(struct vx_core *chip) +{ + unsigned int i; + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + chip->chip_status |= VX_STAT_IN_SUSPEND; + for (i = 0; i < chip->hw->num_codecs; i++) + snd_pcm_suspend_all(chip->pcm[i]); + + return 0; +} + +EXPORT_SYMBOL(snd_vx_suspend); + +/* + * resume + */ +int snd_vx_resume(struct vx_core *chip) +{ + int i, err; + + chip->chip_status &= ~VX_STAT_CHIP_INIT; + + for (i = 0; i < 4; i++) { + if (! chip->firmware[i]) + continue; + err = chip->ops->load_dsp(chip, i, chip->firmware[i]); + if (err < 0) { + snd_printk(KERN_ERR "vx: firmware resume error at DSP %d\n", i); + return -EIO; + } + } + + chip->chip_status |= VX_STAT_CHIP_INIT; + chip->chip_status &= ~VX_STAT_IN_SUSPEND; + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + return 0; +} + +EXPORT_SYMBOL(snd_vx_resume); +#endif + +/** + * snd_vx_create - constructor for struct vx_core + * @card: card instance + * @hw: hardware specific record + * @ops: VX ops pointer + * @extra_size: extra byte size to allocate appending to chip + * + * this function allocates the instance and prepare for the hardware + * initialization. + * + * return the instance pointer if successful, NULL in error. + */ +struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw, + struct snd_vx_ops *ops, + int extra_size) +{ + struct vx_core *chip; + + if (snd_BUG_ON(!card || !hw || !ops)) + return NULL; + + chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL); + if (! chip) + return NULL; + mutex_init(&chip->lock); + chip->irq = -1; + chip->hw = hw; + chip->type = hw->type; + chip->ops = ops; + mutex_init(&chip->mixer_mutex); + + chip->card = card; + card->private_data = chip; + strcpy(card->driver, hw->name); + sprintf(card->shortname, "Digigram %s", hw->name); + + vx_proc_init(chip); + + return chip; +} + +EXPORT_SYMBOL(snd_vx_create); diff --git a/sound/drivers/vx/vx_hwdep.c b/sound/drivers/vx/vx_hwdep.c new file mode 100644 index 000000000..3014b8636 --- /dev/null +++ b/sound/drivers/vx/vx_hwdep.c @@ -0,0 +1,121 @@ +/* + * Driver for Digigram VX soundcards + * + * DSP firmware management + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_FIRMWARE("vx/bx_1_vxp.b56"); +MODULE_FIRMWARE("vx/bx_1_vp4.b56"); +MODULE_FIRMWARE("vx/x1_1_vx2.xlx"); +MODULE_FIRMWARE("vx/x1_2_v22.xlx"); +MODULE_FIRMWARE("vx/x1_1_vxp.xlx"); +MODULE_FIRMWARE("vx/x1_1_vp4.xlx"); +MODULE_FIRMWARE("vx/bd56002.boot"); +MODULE_FIRMWARE("vx/bd563v2.boot"); +MODULE_FIRMWARE("vx/bd563s3.boot"); +MODULE_FIRMWARE("vx/l_1_vx2.d56"); +MODULE_FIRMWARE("vx/l_1_v22.d56"); +MODULE_FIRMWARE("vx/l_1_vxp.d56"); +MODULE_FIRMWARE("vx/l_1_vp4.d56"); + +int snd_vx_setup_firmware(struct vx_core *chip) +{ + static char *fw_files[VX_TYPE_NUMS][4] = { + [VX_TYPE_BOARD] = { + NULL, "x1_1_vx2.xlx", "bd56002.boot", "l_1_vx2.d56", + }, + [VX_TYPE_V2] = { + NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56", + }, + [VX_TYPE_MIC] = { + NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56", + }, + [VX_TYPE_VXPOCKET] = { + "bx_1_vxp.b56", "x1_1_vxp.xlx", "bd563s3.boot", "l_1_vxp.d56" + }, + [VX_TYPE_VXP440] = { + "bx_1_vp4.b56", "x1_1_vp4.xlx", "bd563s3.boot", "l_1_vp4.d56" + }, + }; + + int i, err; + + for (i = 0; i < 4; i++) { + char path[32]; + const struct firmware *fw; + if (! fw_files[chip->type][i]) + continue; + sprintf(path, "vx/%s", fw_files[chip->type][i]); + if (request_firmware(&fw, path, chip->dev)) { + snd_printk(KERN_ERR "vx: can't load firmware %s\n", path); + return -ENOENT; + } + err = chip->ops->load_dsp(chip, i, fw); + if (err < 0) { + release_firmware(fw); + return err; + } + if (i == 1) + chip->chip_status |= VX_STAT_XILINX_LOADED; +#ifdef CONFIG_PM + chip->firmware[i] = fw; +#else + release_firmware(fw); +#endif + } + + /* ok, we reached to the last one */ + /* create the devices if not built yet */ + if ((err = snd_vx_pcm_new(chip)) < 0) + return err; + + if ((err = snd_vx_mixer_new(chip)) < 0) + return err; + + if (chip->ops->add_controls) + if ((err = chip->ops->add_controls(chip)) < 0) + return err; + + chip->chip_status |= VX_STAT_DEVICE_INIT; + chip->chip_status |= VX_STAT_CHIP_INIT; + + return snd_card_register(chip->card); +} + +/* exported */ +void snd_vx_free_firmware(struct vx_core *chip) +{ +#ifdef CONFIG_PM + int i; + for (i = 0; i < 4; i++) + release_firmware(chip->firmware[i]); +#endif +} + +EXPORT_SYMBOL(snd_vx_setup_firmware); +EXPORT_SYMBOL(snd_vx_free_firmware); diff --git a/sound/drivers/vx/vx_mixer.c b/sound/drivers/vx/vx_mixer.c new file mode 100644 index 000000000..98a41ac40 --- /dev/null +++ b/sound/drivers/vx/vx_mixer.c @@ -0,0 +1,1005 @@ +/* + * Driver for Digigram VX soundcards + * + * Common mixer part + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "vx_cmd.h" + + +/* + * write a codec data (24bit) + */ +static void vx_write_codec_reg(struct vx_core *chip, int codec, unsigned int data) +{ + if (snd_BUG_ON(!chip->ops->write_codec)) + return; + + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + mutex_lock(&chip->lock); + chip->ops->write_codec(chip, codec, data); + mutex_unlock(&chip->lock); +} + +/* + * Data type used to access the Codec + */ +union vx_codec_data { + u32 l; +#ifdef SNDRV_BIG_ENDIAN + struct w { + u16 h; + u16 l; + } w; + struct b { + u8 hh; + u8 mh; + u8 ml; + u8 ll; + } b; +#else /* LITTLE_ENDIAN */ + struct w { + u16 l; + u16 h; + } w; + struct b { + u8 ll; + u8 ml; + u8 mh; + u8 hh; + } b; +#endif +}; + +#define SET_CDC_DATA_SEL(di,s) ((di).b.mh = (u8) (s)) +#define SET_CDC_DATA_REG(di,r) ((di).b.ml = (u8) (r)) +#define SET_CDC_DATA_VAL(di,d) ((di).b.ll = (u8) (d)) +#define SET_CDC_DATA_INIT(di) ((di).l = 0L, SET_CDC_DATA_SEL(di,XX_CODEC_SELECTOR)) + +/* + * set up codec register and write the value + * @codec: the codec id, 0 or 1 + * @reg: register index + * @val: data value + */ +static void vx_set_codec_reg(struct vx_core *chip, int codec, int reg, int val) +{ + union vx_codec_data data; + /* DAC control register */ + SET_CDC_DATA_INIT(data); + SET_CDC_DATA_REG(data, reg); + SET_CDC_DATA_VAL(data, val); + vx_write_codec_reg(chip, codec, data.l); +} + + +/* + * vx_set_analog_output_level - set the output attenuation level + * @codec: the output codec, 0 or 1. (1 for VXP440 only) + * @left: left output level, 0 = mute + * @right: right output level + */ +static void vx_set_analog_output_level(struct vx_core *chip, int codec, int left, int right) +{ + left = chip->hw->output_level_max - left; + right = chip->hw->output_level_max - right; + + if (chip->ops->akm_write) { + chip->ops->akm_write(chip, XX_CODEC_LEVEL_LEFT_REGISTER, left); + chip->ops->akm_write(chip, XX_CODEC_LEVEL_RIGHT_REGISTER, right); + } else { + /* convert to attenuation level: 0 = 0dB (max), 0xe3 = -113.5 dB (min) */ + vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_LEFT_REGISTER, left); + vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_RIGHT_REGISTER, right); + } +} + + +/* + * vx_toggle_dac_mute - mute/unmute DAC + * @mute: 0 = unmute, 1 = mute + */ + +#define DAC_ATTEN_MIN 0x08 +#define DAC_ATTEN_MAX 0x38 + +void vx_toggle_dac_mute(struct vx_core *chip, int mute) +{ + unsigned int i; + for (i = 0; i < chip->hw->num_codecs; i++) { + if (chip->ops->akm_write) + chip->ops->akm_write(chip, XX_CODEC_DAC_CONTROL_REGISTER, mute); /* XXX */ + else + vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, + mute ? DAC_ATTEN_MAX : DAC_ATTEN_MIN); + } +} + +/* + * vx_reset_codec - reset and initialize the codecs + */ +void vx_reset_codec(struct vx_core *chip, int cold_reset) +{ + unsigned int i; + int port = chip->type >= VX_TYPE_VXPOCKET ? 0x75 : 0x65; + + chip->ops->reset_codec(chip); + + /* AKM codecs should be initialized in reset_codec callback */ + if (! chip->ops->akm_write) { + /* initialize old codecs */ + for (i = 0; i < chip->hw->num_codecs; i++) { + /* DAC control register (change level when zero crossing + mute) */ + vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, DAC_ATTEN_MAX); + /* ADC control register */ + vx_set_codec_reg(chip, i, XX_CODEC_ADC_CONTROL_REGISTER, 0x00); + /* Port mode register */ + vx_set_codec_reg(chip, i, XX_CODEC_PORT_MODE_REGISTER, port); + /* Clock control register */ + vx_set_codec_reg(chip, i, XX_CODEC_CLOCK_CONTROL_REGISTER, 0x00); + } + } + + /* mute analog output */ + for (i = 0; i < chip->hw->num_codecs; i++) { + chip->output_level[i][0] = 0; + chip->output_level[i][1] = 0; + vx_set_analog_output_level(chip, i, 0, 0); + } +} + +/* + * change the audio input source + * @src: the target source (VX_AUDIO_SRC_XXX) + */ +static void vx_change_audio_source(struct vx_core *chip, int src) +{ + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + mutex_lock(&chip->lock); + chip->ops->change_audio_source(chip, src); + mutex_unlock(&chip->lock); +} + + +/* + * change the audio source if necessary and possible + * returns 1 if the source is actually changed. + */ +int vx_sync_audio_source(struct vx_core *chip) +{ + if (chip->audio_source_target == chip->audio_source || + chip->pcm_running) + return 0; + vx_change_audio_source(chip, chip->audio_source_target); + chip->audio_source = chip->audio_source_target; + return 1; +} + + +/* + * audio level, mute, monitoring + */ +struct vx_audio_level { + unsigned int has_level: 1; + unsigned int has_monitor_level: 1; + unsigned int has_mute: 1; + unsigned int has_monitor_mute: 1; + unsigned int mute; + unsigned int monitor_mute; + short level; + short monitor_level; +}; + +static int vx_adjust_audio_level(struct vx_core *chip, int audio, int capture, + struct vx_audio_level *info) +{ + struct vx_rmh rmh; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + vx_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST); + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + /* Add Audio IO mask */ + rmh.Cmd[1] = 1 << audio; + rmh.Cmd[2] = 0; + if (info->has_level) { + rmh.Cmd[0] |= VALID_AUDIO_IO_DIGITAL_LEVEL; + rmh.Cmd[2] |= info->level; + } + if (info->has_monitor_level) { + rmh.Cmd[0] |= VALID_AUDIO_IO_MONITORING_LEVEL; + rmh.Cmd[2] |= ((unsigned int)info->monitor_level << 10); + } + if (info->has_mute) { + rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_LEVEL; + if (info->mute) + rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_LEVEL; + } + if (info->has_monitor_mute) { + /* validate flag for M2 at least to unmute it */ + rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_MONITORING_1 | VALID_AUDIO_IO_MUTE_MONITORING_2; + if (info->monitor_mute) + rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_MONITORING_1; + } + + return vx_send_msg(chip, &rmh); +} + + +#if 0 // not used +static int vx_read_audio_level(struct vx_core *chip, int audio, int capture, + struct vx_audio_level *info) +{ + int err; + struct vx_rmh rmh; + + memset(info, 0, sizeof(*info)); + vx_init_rmh(&rmh, CMD_GET_AUDIO_LEVELS); + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + /* Add Audio IO mask */ + rmh.Cmd[1] = 1 << audio; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + info.level = rmh.Stat[0] & MASK_DSP_WORD_LEVEL; + info.monitor_level = (rmh.Stat[0] >> 10) & MASK_DSP_WORD_LEVEL; + info.mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_LEVEL) ? 1 : 0; + info.monitor_mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_MONITORING_1) ? 1 : 0; + return 0; +} +#endif // not used + +/* + * set the monitoring level and mute state of the given audio + * no more static, because must be called from vx_pcm to demute monitoring + */ +int vx_set_monitor_level(struct vx_core *chip, int audio, int level, int active) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_monitor_level = 1; + info.monitor_level = level; + info.has_monitor_mute = 1; + info.monitor_mute = !active; + chip->audio_monitor[audio] = level; + chip->audio_monitor_active[audio] = active; + return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */ +} + + +/* + * set the mute status of the given audio + */ +static int vx_set_audio_switch(struct vx_core *chip, int audio, int active) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_mute = 1; + info.mute = !active; + chip->audio_active[audio] = active; + return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */ +} + +/* + * set the mute status of the given audio + */ +static int vx_set_audio_gain(struct vx_core *chip, int audio, int capture, int level) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_level = 1; + info.level = level; + chip->audio_gain[capture][audio] = level; + return vx_adjust_audio_level(chip, audio, capture, &info); +} + +/* + * reset all audio levels + */ +static void vx_reset_audio_levels(struct vx_core *chip) +{ + unsigned int i, c; + struct vx_audio_level info; + + memset(chip->audio_gain, 0, sizeof(chip->audio_gain)); + memset(chip->audio_active, 0, sizeof(chip->audio_active)); + memset(chip->audio_monitor, 0, sizeof(chip->audio_monitor)); + memset(chip->audio_monitor_active, 0, sizeof(chip->audio_monitor_active)); + + for (c = 0; c < 2; c++) { + for (i = 0; i < chip->hw->num_ins * 2; i++) { + memset(&info, 0, sizeof(info)); + if (c == 0) { + info.has_monitor_level = 1; + info.has_mute = 1; + info.has_monitor_mute = 1; + } + info.has_level = 1; + info.level = CVAL_0DB; /* default: 0dB */ + vx_adjust_audio_level(chip, i, c, &info); + chip->audio_gain[c][i] = CVAL_0DB; + chip->audio_monitor[i] = CVAL_0DB; + } + } +} + + +/* + * VU, peak meter record + */ + +#define VU_METER_CHANNELS 2 + +struct vx_vu_meter { + int saturated; + int vu_level; + int peak_level; +}; + +/* + * get the VU and peak meter values + * @audio: the audio index + * @capture: 0 = playback, 1 = capture operation + * @info: the array of vx_vu_meter records (size = 2). + */ +static int vx_get_audio_vu_meter(struct vx_core *chip, int audio, int capture, struct vx_vu_meter *info) +{ + struct vx_rmh rmh; + int i, err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + vx_init_rmh(&rmh, CMD_AUDIO_VU_PIC_METER); + rmh.LgStat += 2 * VU_METER_CHANNELS; + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + + /* Add Audio IO mask */ + rmh.Cmd[1] = 0; + for (i = 0; i < VU_METER_CHANNELS; i++) + rmh.Cmd[1] |= 1 << (audio + i); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + /* Read response */ + for (i = 0; i < 2 * VU_METER_CHANNELS; i +=2) { + info->saturated = (rmh.Stat[0] & (1 << (audio + i))) ? 1 : 0; + info->vu_level = rmh.Stat[i + 1]; + info->peak_level = rmh.Stat[i + 2]; + info++; + } + return 0; +} + + +/* + * control API entries + */ + +/* + * output level control + */ +static int vx_output_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = chip->hw->output_level_max; + return 0; +} + +static int vx_output_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->id.index; + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->output_level[codec][0]; + ucontrol->value.integer.value[1] = chip->output_level[codec][1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_output_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->id.index; + unsigned int val[2], vmax; + + vmax = chip->hw->output_level_max; + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > vmax || val[1] > vmax) + return -EINVAL; + mutex_lock(&chip->mixer_mutex); + if (val[0] != chip->output_level[codec][0] || + val[1] != chip->output_level[codec][1]) { + vx_set_analog_output_level(chip, codec, val[0], val[1]); + chip->output_level[codec][0] = val[0]; + chip->output_level[codec][1] = val[1]; + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_output_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Volume", + .info = vx_output_level_info, + .get = vx_output_level_get, + .put = vx_output_level_put, + /* tlv will be filled later */ +}; + +/* + * audio source select + */ +static int vx_audio_src_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts_mic[3] = { + "Digital", "Line", "Mic" + }; + static const char * const texts_vx2[2] = { + "Digital", "Analog" + }; + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (chip->type >= VX_TYPE_VXPOCKET) + return snd_ctl_enum_info(uinfo, 1, 3, texts_mic); + else + return snd_ctl_enum_info(uinfo, 1, 2, texts_vx2); +} + +static int vx_audio_src_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->audio_source_target; + return 0; +} + +static int vx_audio_src_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (chip->type >= VX_TYPE_VXPOCKET) { + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + } else { + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + } + mutex_lock(&chip->mixer_mutex); + if (chip->audio_source_target != ucontrol->value.enumerated.item[0]) { + chip->audio_source_target = ucontrol->value.enumerated.item[0]; + vx_sync_audio_source(chip); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_audio_src = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = vx_audio_src_info, + .get = vx_audio_src_get, + .put = vx_audio_src_put, +}; + +/* + * clock mode selection + */ +static int vx_clock_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Auto", "Internal", "External" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int vx_clock_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->clock_mode; + return 0; +} + +static int vx_clock_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + mutex_lock(&chip->mixer_mutex); + if (chip->clock_mode != ucontrol->value.enumerated.item[0]) { + chip->clock_mode = ucontrol->value.enumerated.item[0]; + vx_set_clock(chip, chip->freq); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_clock_mode = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Mode", + .info = vx_clock_mode_info, + .get = vx_clock_mode_get, + .put = vx_clock_mode_put, +}; + +/* + * Audio Gain + */ +static int vx_audio_gain_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 = CVAL_MAX; + return 0; +} + +static int vx_audio_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_gain[capture][audio]; + ucontrol->value.integer.value[1] = chip->audio_gain[capture][audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_gain_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + unsigned int val[2]; + + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > CVAL_MAX || val[1] > CVAL_MAX) + return -EINVAL; + mutex_lock(&chip->mixer_mutex); + if (val[0] != chip->audio_gain[capture][audio] || + val[1] != chip->audio_gain[capture][audio+1]) { + vx_set_audio_gain(chip, audio, capture, val[0]); + vx_set_audio_gain(chip, audio+1, capture, val[1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_monitor_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor[audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_monitor_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + unsigned int val[2]; + + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > CVAL_MAX || val[1] > CVAL_MAX) + return -EINVAL; + + mutex_lock(&chip->mixer_mutex); + if (val[0] != chip->audio_monitor[audio] || + val[1] != chip->audio_monitor[audio+1]) { + vx_set_monitor_level(chip, audio, val[0], + chip->audio_monitor_active[audio]); + vx_set_monitor_level(chip, audio+1, val[1], + chip->audio_monitor_active[audio+1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +#define vx_audio_sw_info snd_ctl_boolean_stereo_info + +static int vx_audio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_active[audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + if (ucontrol->value.integer.value[0] != chip->audio_active[audio] || + ucontrol->value.integer.value[1] != chip->audio_active[audio+1]) { + vx_set_audio_switch(chip, audio, + !!ucontrol->value.integer.value[0]); + vx_set_audio_switch(chip, audio+1, + !!ucontrol->value.integer.value[1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_monitor_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor_active[audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + if (ucontrol->value.integer.value[0] != chip->audio_monitor_active[audio] || + ucontrol->value.integer.value[1] != chip->audio_monitor_active[audio+1]) { + vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], + !!ucontrol->value.integer.value[0]); + vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], + !!ucontrol->value.integer.value[1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_audio_gain, -10975, 25, 0); + +static const struct snd_kcontrol_new vx_control_audio_gain = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + /* name will be filled later */ + .info = vx_audio_gain_info, + .get = vx_audio_gain_get, + .put = vx_audio_gain_put, + .tlv = { .p = db_scale_audio_gain }, +}; +static const struct snd_kcontrol_new vx_control_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = vx_audio_sw_info, + .get = vx_audio_sw_get, + .put = vx_audio_sw_put +}; +static const struct snd_kcontrol_new vx_control_monitor_gain = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Volume", + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = vx_audio_gain_info, /* shared */ + .get = vx_audio_monitor_get, + .put = vx_audio_monitor_put, + .tlv = { .p = db_scale_audio_gain }, +}; +static const struct snd_kcontrol_new vx_control_monitor_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Switch", + .info = vx_audio_sw_info, /* shared */ + .get = vx_monitor_sw_get, + .put = vx_monitor_sw_put +}; + + +/* + * IEC958 status bits + */ +static int vx_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 vx_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.iec958.status[0] = (chip->uer_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (chip->uer_bits >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (chip->uer_bits >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (chip->uer_bits >> 24) & 0xff; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_iec958_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 vx_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + 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); + mutex_lock(&chip->mixer_mutex); + if (chip->uer_bits != val) { + chip->uer_bits = val; + vx_set_iec958_status(chip, val); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_iec958_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = vx_iec958_info, /* shared */ + .get = vx_iec958_mask_get, +}; + +static const struct snd_kcontrol_new vx_control_iec958 = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = vx_iec958_info, + .get = vx_iec958_get, + .put = vx_iec958_put +}; + + +/* + * VU meter + */ + +#define METER_MAX 0xff +#define METER_SHIFT 16 + +static int vx_vu_meter_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 = METER_MAX; + return 0; +} + +static int vx_vu_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + vx_get_audio_vu_meter(chip, audio, capture, meter); + ucontrol->value.integer.value[0] = meter[0].vu_level >> METER_SHIFT; + ucontrol->value.integer.value[1] = meter[1].vu_level >> METER_SHIFT; + return 0; +} + +static int vx_peak_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + vx_get_audio_vu_meter(chip, audio, capture, meter); + ucontrol->value.integer.value[0] = meter[0].peak_level >> METER_SHIFT; + ucontrol->value.integer.value[1] = meter[1].peak_level >> METER_SHIFT; + return 0; +} + +#define vx_saturation_info snd_ctl_boolean_stereo_info + +static int vx_saturation_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + + vx_get_audio_vu_meter(chip, audio, 1, meter); /* capture only */ + ucontrol->value.integer.value[0] = meter[0].saturated; + ucontrol->value.integer.value[1] = meter[1].saturated; + return 0; +} + +static const struct snd_kcontrol_new vx_control_vu_meter = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + /* name will be filled later */ + .info = vx_vu_meter_info, + .get = vx_vu_meter_get, +}; + +static const struct snd_kcontrol_new vx_control_peak_meter = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + /* name will be filled later */ + .info = vx_vu_meter_info, /* shared */ + .get = vx_peak_meter_get, +}; + +static const struct snd_kcontrol_new vx_control_saturation = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Saturation", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = vx_saturation_info, + .get = vx_saturation_get, +}; + + + +/* + * + */ + +int snd_vx_mixer_new(struct vx_core *chip) +{ + unsigned int i, c; + int err; + struct snd_kcontrol_new temp; + struct snd_card *card = chip->card; + char name[32]; + + strcpy(card->mixername, card->driver); + + /* output level controls */ + for (i = 0; i < chip->hw->num_outs; i++) { + temp = vx_control_output_level; + temp.index = i; + temp.tlv.p = chip->hw->output_level_db_scale; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + } + + /* PCM volumes, switches, monitoring */ + for (i = 0; i < chip->hw->num_outs; i++) { + int val = i * 2; + temp = vx_control_audio_gain; + temp.index = i; + temp.name = "PCM Playback Volume"; + temp.private_value = val; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + temp = vx_control_output_switch; + temp.index = i; + temp.private_value = val; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + temp = vx_control_monitor_gain; + temp.index = i; + temp.private_value = val; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + temp = vx_control_monitor_switch; + temp.index = i; + temp.private_value = val; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + } + for (i = 0; i < chip->hw->num_outs; i++) { + temp = vx_control_audio_gain; + temp.index = i; + temp.name = "PCM Capture Volume"; + temp.private_value = (i * 2) | (1 << 8); + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + } + + /* Audio source */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_audio_src, chip))) < 0) + return err; + /* clock mode */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_clock_mode, chip))) < 0) + return err; + /* IEC958 controls */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958_mask, chip))) < 0) + return err; + if ((err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958, chip))) < 0) + return err; + /* VU, peak, saturation meters */ + for (c = 0; c < 2; c++) { + static char *dir[2] = { "Output", "Input" }; + for (i = 0; i < chip->hw->num_ins; i++) { + int val = (i * 2) | (c << 8); + if (c == 1) { + temp = vx_control_saturation; + temp.index = i; + temp.private_value = val; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + } + sprintf(name, "%s VU Meter", dir[c]); + temp = vx_control_vu_meter; + temp.index = i; + temp.name = name; + temp.private_value = val; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + sprintf(name, "%s Peak Meter", dir[c]); + temp = vx_control_peak_meter; + temp.index = i; + temp.name = name; + temp.private_value = val; + if ((err = snd_ctl_add(card, snd_ctl_new1(&temp, chip))) < 0) + return err; + } + } + vx_reset_audio_levels(chip); + return 0; +} diff --git a/sound/drivers/vx/vx_pcm.c b/sound/drivers/vx/vx_pcm.c new file mode 100644 index 000000000..ba80f459b --- /dev/null +++ b/sound/drivers/vx/vx_pcm.c @@ -0,0 +1,1261 @@ +/* + * Driver for Digigram VX soundcards + * + * PCM part + * + * Copyright (c) 2002,2003 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * STRATEGY + * for playback, we send series of "chunks", which size is equal with the + * IBL size, typically 126 samples. at each end of chunk, the end-of-buffer + * interrupt is notified, and the interrupt handler will feed the next chunk. + * + * the current position is calculated from the sample count RMH. + * pipe->transferred is the counter of data which has been already transferred. + * if this counter reaches to the period size, snd_pcm_period_elapsed() will + * be issued. + * + * for capture, the situation is much easier. + * to get a low latency response, we'll check the capture streams at each + * interrupt (capture stream has no EOB notification). if the pending + * data is accumulated to the period size, snd_pcm_period_elapsed() is + * called and the pointer is updated. + * + * the current point of read buffer is kept in pipe->hw_ptr. note that + * this is in bytes. + * + * + * TODO + * - linked trigger for full-duplex mode. + * - scheduled action on the stream. + */ + +#include +#include +#include +#include +#include +#include +#include "vx_cmd.h" + + +/* + * read three pending pcm bytes via inb() + */ +static void vx_pcm_read_per_bytes(struct vx_core *chip, struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe) +{ + int offset = pipe->hw_ptr; + unsigned char *buf = (unsigned char *)(runtime->dma_area + offset); + *buf++ = vx_inb(chip, RXH); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + buf = (unsigned char *)runtime->dma_area; + } + *buf++ = vx_inb(chip, RXM); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + buf = (unsigned char *)runtime->dma_area; + } + *buf++ = vx_inb(chip, RXL); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + buf = (unsigned char *)runtime->dma_area; + } + pipe->hw_ptr = offset; +} + +/* + * vx_set_pcx_time - convert from the PC time to the RMH status time. + * @pc_time: the pointer for the PC-time to set + * @dsp_time: the pointer for RMH status time array + */ +static void vx_set_pcx_time(struct vx_core *chip, pcx_time_t *pc_time, + unsigned int *dsp_time) +{ + dsp_time[0] = (unsigned int)((*pc_time) >> 24) & PCX_TIME_HI_MASK; + dsp_time[1] = (unsigned int)(*pc_time) & MASK_DSP_WORD; +} + +/* + * vx_set_differed_time - set the differed time if specified + * @rmh: the rmh record to modify + * @pipe: the pipe to be checked + * + * if the pipe is programmed with the differed time, set the DSP time + * on the rmh and changes its command length. + * + * returns the increase of the command length. + */ +static int vx_set_differed_time(struct vx_core *chip, struct vx_rmh *rmh, + struct vx_pipe *pipe) +{ + /* Update The length added to the RMH command by the timestamp */ + if (! (pipe->differed_type & DC_DIFFERED_DELAY)) + return 0; + + /* Set the T bit */ + rmh->Cmd[0] |= DSP_DIFFERED_COMMAND_MASK; + + /* Time stamp is the 1st following parameter */ + vx_set_pcx_time(chip, &pipe->pcx_time, &rmh->Cmd[1]); + + /* Add the flags to a notified differed command */ + if (pipe->differed_type & DC_NOTIFY_DELAY) + rmh->Cmd[1] |= NOTIFY_MASK_TIME_HIGH ; + + /* Add the flags to a multiple differed command */ + if (pipe->differed_type & DC_MULTIPLE_DELAY) + rmh->Cmd[1] |= MULTIPLE_MASK_TIME_HIGH; + + /* Add the flags to a stream-time differed command */ + if (pipe->differed_type & DC_STREAM_TIME_DELAY) + rmh->Cmd[1] |= STREAM_MASK_TIME_HIGH; + + rmh->LgCmd += 2; + return 2; +} + +/* + * vx_set_stream_format - send the stream format command + * @pipe: the affected pipe + * @data: format bitmask + */ +static int vx_set_stream_format(struct vx_core *chip, struct vx_pipe *pipe, + unsigned int data) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, pipe->is_capture ? + CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT); + rmh.Cmd[0] |= pipe->number << FIELD_SIZE; + + /* Command might be longer since we may have to add a timestamp */ + vx_set_differed_time(chip, &rmh, pipe); + + rmh.Cmd[rmh.LgCmd] = (data & 0xFFFFFF00) >> 8; + rmh.Cmd[rmh.LgCmd + 1] = (data & 0xFF) << 16 /*| (datal & 0xFFFF00) >> 8*/; + rmh.LgCmd += 2; + + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_set_format - set the format of a pipe + * @pipe: the affected pipe + * @runtime: pcm runtime instance to be referred + * + * returns 0 if successful, or a negative error code. + */ +static int vx_set_format(struct vx_core *chip, struct vx_pipe *pipe, + struct snd_pcm_runtime *runtime) +{ + unsigned int header = HEADER_FMT_BASE; + + if (runtime->channels == 1) + header |= HEADER_FMT_MONO; + if (snd_pcm_format_little_endian(runtime->format)) + header |= HEADER_FMT_INTEL; + if (runtime->rate < 32000 && runtime->rate > 11025) + header |= HEADER_FMT_UPTO32; + else if (runtime->rate <= 11025) + header |= HEADER_FMT_UPTO11; + + switch (snd_pcm_format_physical_width(runtime->format)) { + // case 8: break; + case 16: header |= HEADER_FMT_16BITS; break; + case 24: header |= HEADER_FMT_24BITS; break; + default : + snd_BUG(); + return -EINVAL; + } + + return vx_set_stream_format(chip, pipe, header); +} + +/* + * set / query the IBL size + */ +static int vx_set_ibl(struct vx_core *chip, struct vx_ibl_info *info) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_IBL); + rmh.Cmd[0] |= info->size & 0x03ffff; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + info->size = rmh.Stat[0]; + info->max_size = rmh.Stat[1]; + info->min_size = rmh.Stat[2]; + info->granularity = rmh.Stat[3]; + snd_printdd(KERN_DEBUG "vx_set_ibl: size = %d, max = %d, min = %d, gran = %d\n", + info->size, info->max_size, info->min_size, info->granularity); + return 0; +} + + +/* + * vx_get_pipe_state - get the state of a pipe + * @pipe: the pipe to be checked + * @state: the pointer for the returned state + * + * checks the state of a given pipe, and stores the state (1 = running, + * 0 = paused) on the given pointer. + * + * called from trigger callback only + */ +static int vx_get_pipe_state(struct vx_core *chip, struct vx_pipe *pipe, int *state) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_PIPE_STATE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (! err) + *state = (rmh.Stat[0] & (1 << pipe->number)) ? 1 : 0; + return err; +} + + +/* + * vx_query_hbuffer_size - query available h-buffer size in bytes + * @pipe: the pipe to be checked + * + * return the available size on h-buffer in bytes, + * or a negative error code. + * + * NOTE: calling this function always switches to the stream mode. + * you'll need to disconnect the host to get back to the + * normal mode. + */ +static int vx_query_hbuffer_size(struct vx_core *chip, struct vx_pipe *pipe) +{ + int result; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_SIZE_HBUFFER); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + if (pipe->is_capture) + rmh.Cmd[0] |= 0x00000001; + result = vx_send_msg(chip, &rmh); + if (! result) + result = rmh.Stat[0] & 0xffff; + return result; +} + + +/* + * vx_pipe_can_start - query whether a pipe is ready for start + * @pipe: the pipe to be checked + * + * return 1 if ready, 0 if not ready, and negative value on error. + * + * called from trigger callback only + */ +static int vx_pipe_can_start(struct vx_core *chip, struct vx_pipe *pipe) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_CAN_START_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + rmh.Cmd[0] |= 1; + + err = vx_send_msg(chip, &rmh); + if (! err) { + if (rmh.Stat[0]) + err = 1; + } + return err; +} + +/* + * vx_conf_pipe - tell the pipe to stand by and wait for IRQA. + * @pipe: the pipe to be configured + */ +static int vx_conf_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_CONF_PIPE); + if (pipe->is_capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + rmh.Cmd[1] = 1 << pipe->number; + return vx_send_msg(chip, &rmh); +} + +/* + * vx_send_irqa - trigger IRQA + */ +static int vx_send_irqa(struct vx_core *chip) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_SEND_IRQA); + return vx_send_msg(chip, &rmh); +} + + +#define MAX_WAIT_FOR_DSP 250 +/* + * vx boards do not support inter-card sync, besides + * only 126 samples require to be prepared before a pipe can start + */ +#define CAN_START_DELAY 2 /* wait 2ms only before asking if the pipe is ready*/ +#define WAIT_STATE_DELAY 2 /* wait 2ms after irqA was requested and check if the pipe state toggled*/ + +/* + * vx_toggle_pipe - start / pause a pipe + * @pipe: the pipe to be triggered + * @state: start = 1, pause = 0 + * + * called from trigger callback only + * + */ +static int vx_toggle_pipe(struct vx_core *chip, struct vx_pipe *pipe, int state) +{ + int err, i, cur_state; + + /* Check the pipe is not already in the requested state */ + if (vx_get_pipe_state(chip, pipe, &cur_state) < 0) + return -EBADFD; + if (state == cur_state) + return 0; + + /* If a start is requested, ask the DSP to get prepared + * and wait for a positive acknowledge (when there are + * enough sound buffer for this pipe) + */ + if (state) { + for (i = 0 ; i < MAX_WAIT_FOR_DSP; i++) { + err = vx_pipe_can_start(chip, pipe); + if (err > 0) + break; + /* Wait for a few, before asking again + * to avoid flooding the DSP with our requests + */ + mdelay(1); + } + } + + if ((err = vx_conf_pipe(chip, pipe)) < 0) + return err; + + if ((err = vx_send_irqa(chip)) < 0) + return err; + + /* If it completes successfully, wait for the pipes + * reaching the expected state before returning + * Check one pipe only (since they are synchronous) + */ + for (i = 0; i < MAX_WAIT_FOR_DSP; i++) { + err = vx_get_pipe_state(chip, pipe, &cur_state); + if (err < 0 || cur_state == state) + break; + err = -EIO; + mdelay(1); + } + return err < 0 ? -EIO : 0; +} + + +/* + * vx_stop_pipe - stop a pipe + * @pipe: the pipe to be stopped + * + * called from trigger callback only + */ +static int vx_stop_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + vx_init_rmh(&rmh, CMD_STOP_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_alloc_pipe - allocate a pipe and initialize the pipe instance + * @capture: 0 = playback, 1 = capture operation + * @audioid: the audio id to be assigned + * @num_audio: number of audio channels + * @pipep: the returned pipe instance + * + * return 0 on success, or a negative error code. + */ +static int vx_alloc_pipe(struct vx_core *chip, int capture, + int audioid, int num_audio, + struct vx_pipe **pipep) +{ + int err; + struct vx_pipe *pipe; + struct vx_rmh rmh; + int data_mode; + + *pipep = NULL; + vx_init_rmh(&rmh, CMD_RES_PIPE); + vx_set_pipe_cmd_params(&rmh, capture, audioid, num_audio); +#if 0 // NYI + if (underrun_skip_sound) + rmh.Cmd[0] |= BIT_SKIP_SOUND; +#endif // NYI + data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; + if (! capture && data_mode) + rmh.Cmd[0] |= BIT_DATA_MODE; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + + /* initialize the pipe record */ + pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); + if (! pipe) { + /* release the pipe */ + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, capture, audioid, 0); + vx_send_msg(chip, &rmh); + return -ENOMEM; + } + + /* the pipe index should be identical with the audio index */ + pipe->number = audioid; + pipe->is_capture = capture; + pipe->channels = num_audio; + pipe->differed_type = 0; + pipe->pcx_time = 0; + pipe->data_mode = data_mode; + *pipep = pipe; + + return 0; +} + + +/* + * vx_free_pipe - release a pipe + * @pipe: pipe to be released + */ +static int vx_free_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + vx_send_msg(chip, &rmh); + + kfree(pipe); + return 0; +} + + +/* + * vx_start_stream - start the stream + * + * called from trigger callback only + */ +static int vx_start_stream(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_START_ONE_STREAM); + vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); + vx_set_differed_time(chip, &rmh, pipe); + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_stop_stream - stop the stream + * + * called from trigger callback only + */ +static int vx_stop_stream(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_STOP_STREAM); + vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); + return vx_send_msg(chip, &rmh); +} + + +/* + * playback hw information + */ + +static const struct snd_pcm_hardware vx_pcm_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ + /*SNDRV_PCM_INFO_RESUME*/), + .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 126, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = VX_MAX_PERIODS, + .fifo_size = 126, +}; + + +/* + * vx_pcm_playback_open - open callback for playback + */ +static int vx_pcm_playback_open(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe = NULL; + unsigned int audio; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + audio = subs->pcm->device * 2; + if (snd_BUG_ON(audio >= chip->audio_outs)) + return -EINVAL; + + /* playback pipe may have been already allocated for monitoring */ + pipe = chip->playback_pipes[audio]; + if (! pipe) { + /* not allocated yet */ + err = vx_alloc_pipe(chip, 0, audio, 2, &pipe); /* stereo playback */ + if (err < 0) + return err; + chip->playback_pipes[audio] = pipe; + } + /* open for playback */ + pipe->references++; + + pipe->substream = subs; + chip->playback_pipes[audio] = pipe; + + runtime->hw = vx_pcm_playback_hw; + runtime->hw.period_bytes_min = chip->ibl.size; + runtime->private_data = pipe; + + /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); + + return 0; +} + +/* + * vx_pcm_playback_close - close callback for playback + */ +static int vx_pcm_playback_close(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + + if (! subs->runtime->private_data) + return -EINVAL; + + pipe = subs->runtime->private_data; + + if (--pipe->references == 0) { + chip->playback_pipes[pipe->number] = NULL; + vx_free_pipe(chip, pipe); + } + + return 0; + +} + + +/* + * vx_notify_end_of_buffer - send "end-of-buffer" notifier at the given pipe + * @pipe: the pipe to notify + * + * NB: call with a certain lock. + */ +static int vx_notify_end_of_buffer(struct vx_core *chip, struct vx_pipe *pipe) +{ + int err; + struct vx_rmh rmh; /* use a temporary rmh here */ + + /* Toggle Dsp Host Interface into Message mode */ + vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); + vx_init_rmh(&rmh, CMD_NOTIFY_END_OF_BUFFER); + vx_set_stream_cmd_params(&rmh, 0, pipe->number); + err = vx_send_msg_nolock(chip, &rmh); + if (err < 0) + return err; + /* Toggle Dsp Host Interface back to sound transfer mode */ + vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); + return 0; +} + +/* + * vx_pcm_playback_transfer_chunk - transfer a single chunk + * @subs: substream + * @pipe: the pipe to transfer + * @size: chunk size in bytes + * + * transfer a single buffer chunk. EOB notificaton is added after that. + * called from the interrupt handler, too. + * + * return 0 if ok. + */ +static int vx_pcm_playback_transfer_chunk(struct vx_core *chip, + struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe, int size) +{ + int space, err = 0; + + space = vx_query_hbuffer_size(chip, pipe); + if (space < 0) { + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + snd_printd("error hbuffer\n"); + return space; + } + if (space < size) { + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + snd_printd("no enough hbuffer space %d\n", space); + return -EIO; /* XRUN */ + } + + /* we don't need irqsave here, because this function + * is called from either trigger callback or irq handler + */ + mutex_lock(&chip->lock); + vx_pseudo_dma_write(chip, runtime, pipe, size); + err = vx_notify_end_of_buffer(chip, pipe); + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT); + mutex_unlock(&chip->lock); + return err; +} + +/* + * update the position of the given pipe. + * pipe->position is updated and wrapped within the buffer size. + * pipe->transferred is updated, too, but the size is not wrapped, + * so that the caller can check the total transferred size later + * (to call snd_pcm_period_elapsed). + */ +static int vx_update_pipe_position(struct vx_core *chip, + struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + int err, update; + u64 count; + + vx_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + + count = ((u64)(rmh.Stat[0] & 0xfffff) << 24) | (u64)rmh.Stat[1]; + update = (int)(count - pipe->cur_count); + pipe->cur_count = count; + pipe->position += update; + if (pipe->position >= (int)runtime->buffer_size) + pipe->position %= runtime->buffer_size; + pipe->transferred += update; + return 0; +} + +/* + * transfer the pending playback buffer data to DSP + * called from interrupt handler + */ +static void vx_pcm_playback_transfer(struct vx_core *chip, + struct snd_pcm_substream *subs, + struct vx_pipe *pipe, int nchunks) +{ + int i, err; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE)) + return; + for (i = 0; i < nchunks; i++) { + if ((err = vx_pcm_playback_transfer_chunk(chip, runtime, pipe, + chip->ibl.size)) < 0) + return; + } +} + +/* + * update the playback position and call snd_pcm_period_elapsed() if necessary + * called from interrupt handler + */ +static void vx_pcm_playback_update(struct vx_core *chip, + struct snd_pcm_substream *subs, + struct vx_pipe *pipe) +{ + int err; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (pipe->running && ! (chip->chip_status & VX_STAT_IS_STALE)) { + if ((err = vx_update_pipe_position(chip, runtime, pipe)) < 0) + return; + if (pipe->transferred >= (int)runtime->period_size) { + pipe->transferred %= runtime->period_size; + snd_pcm_period_elapsed(subs); + } + } +} + +/* + * vx_pcm_playback_trigger - trigger callback for playback + */ +static int vx_pcm_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe = subs->runtime->private_data; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (! pipe->is_capture) + vx_pcm_playback_transfer(chip, subs, pipe, 2); + err = vx_start_stream(chip, pipe); + if (err < 0) { + pr_debug("vx: cannot start stream\n"); + return err; + } + err = vx_toggle_pipe(chip, pipe, 1); + if (err < 0) { + pr_debug("vx: cannot start pipe\n"); + vx_stop_stream(chip, pipe); + return err; + } + chip->pcm_running++; + pipe->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + vx_toggle_pipe(chip, pipe, 0); + vx_stop_pipe(chip, pipe); + vx_stop_stream(chip, pipe); + chip->pcm_running--; + pipe->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if ((err = vx_toggle_pipe(chip, pipe, 0)) < 0) + return err; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((err = vx_toggle_pipe(chip, pipe, 1)) < 0) + return err; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * vx_pcm_playback_pointer - pointer callback for playback + */ +static snd_pcm_uframes_t vx_pcm_playback_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + return pipe->position; +} + +/* + * vx_pcm_hw_params - hw_params callback for playback and capture + */ +static int vx_pcm_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_32_buffer + (subs, params_buffer_bytes(hw_params)); +} + +/* + * vx_pcm_hw_free - hw_free callback for playback and capture + */ +static int vx_pcm_hw_free(struct snd_pcm_substream *subs) +{ + return snd_pcm_lib_free_vmalloc_buffer(subs); +} + +/* + * vx_pcm_prepare - prepare callback for playback and capture + */ +static int vx_pcm_prepare(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + int err, data_mode; + // int max_size, nchunks; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; + if (data_mode != pipe->data_mode && ! pipe->is_capture) { + /* IEC958 status (raw-mode) was changed */ + /* we reopen the pipe */ + struct vx_rmh rmh; + snd_printdd(KERN_DEBUG "reopen the pipe with data_mode = %d\n", data_mode); + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, 0, pipe->number, 0); + if ((err = vx_send_msg(chip, &rmh)) < 0) + return err; + vx_init_rmh(&rmh, CMD_RES_PIPE); + vx_set_pipe_cmd_params(&rmh, 0, pipe->number, pipe->channels); + if (data_mode) + rmh.Cmd[0] |= BIT_DATA_MODE; + if ((err = vx_send_msg(chip, &rmh)) < 0) + return err; + pipe->data_mode = data_mode; + } + + if (chip->pcm_running && chip->freq != runtime->rate) { + snd_printk(KERN_ERR "vx: cannot set different clock %d " + "from the current %d\n", runtime->rate, chip->freq); + return -EINVAL; + } + vx_set_clock(chip, runtime->rate); + + if ((err = vx_set_format(chip, pipe, runtime)) < 0) + return err; + + if (vx_is_pcmcia(chip)) { + pipe->align = 2; /* 16bit word */ + } else { + pipe->align = 4; /* 32bit word */ + } + + pipe->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size); + pipe->period_bytes = frames_to_bytes(runtime, runtime->period_size); + pipe->hw_ptr = 0; + + /* set the timestamp */ + vx_update_pipe_position(chip, runtime, pipe); + /* clear again */ + pipe->transferred = 0; + pipe->position = 0; + + pipe->prepared = 1; + + return 0; +} + + +/* + * operators for PCM playback + */ +static const struct snd_pcm_ops vx_pcm_playback_ops = { + .open = vx_pcm_playback_open, + .close = vx_pcm_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = vx_pcm_hw_params, + .hw_free = vx_pcm_hw_free, + .prepare = vx_pcm_prepare, + .trigger = vx_pcm_trigger, + .pointer = vx_pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + + +/* + * playback hw information + */ + +static const struct snd_pcm_hardware vx_pcm_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ + /*SNDRV_PCM_INFO_RESUME*/), + .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 126, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = VX_MAX_PERIODS, + .fifo_size = 126, +}; + + +/* + * vx_pcm_capture_open - open callback for capture + */ +static int vx_pcm_capture_open(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + struct vx_pipe *pipe_out_monitoring = NULL; + unsigned int audio; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + audio = subs->pcm->device * 2; + if (snd_BUG_ON(audio >= chip->audio_ins)) + return -EINVAL; + err = vx_alloc_pipe(chip, 1, audio, 2, &pipe); + if (err < 0) + return err; + pipe->substream = subs; + chip->capture_pipes[audio] = pipe; + + /* check if monitoring is needed */ + if (chip->audio_monitor_active[audio]) { + pipe_out_monitoring = chip->playback_pipes[audio]; + if (! pipe_out_monitoring) { + /* allocate a pipe */ + err = vx_alloc_pipe(chip, 0, audio, 2, &pipe_out_monitoring); + if (err < 0) + return err; + chip->playback_pipes[audio] = pipe_out_monitoring; + } + pipe_out_monitoring->references++; + /* + if an output pipe is available, it's audios still may need to be + unmuted. hence we'll have to call a mixer entry point. + */ + vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], + chip->audio_monitor_active[audio]); + /* assuming stereo */ + vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], + chip->audio_monitor_active[audio+1]); + } + + pipe->monitoring_pipe = pipe_out_monitoring; /* default value NULL */ + + runtime->hw = vx_pcm_capture_hw; + runtime->hw.period_bytes_min = chip->ibl.size; + runtime->private_data = pipe; + + /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); + + return 0; +} + +/* + * vx_pcm_capture_close - close callback for capture + */ +static int vx_pcm_capture_close(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + struct vx_pipe *pipe_out_monitoring; + + if (! subs->runtime->private_data) + return -EINVAL; + pipe = subs->runtime->private_data; + chip->capture_pipes[pipe->number] = NULL; + + pipe_out_monitoring = pipe->monitoring_pipe; + + /* + if an output pipe is attached to this input, + check if it needs to be released. + */ + if (pipe_out_monitoring) { + if (--pipe_out_monitoring->references == 0) { + vx_free_pipe(chip, pipe_out_monitoring); + chip->playback_pipes[pipe->number] = NULL; + pipe->monitoring_pipe = NULL; + } + } + + vx_free_pipe(chip, pipe); + return 0; +} + + + +#define DMA_READ_ALIGN 6 /* hardware alignment for read */ + +/* + * vx_pcm_capture_update - update the capture buffer + */ +static void vx_pcm_capture_update(struct vx_core *chip, struct snd_pcm_substream *subs, + struct vx_pipe *pipe) +{ + int size, space, count; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (!pipe->running || (chip->chip_status & VX_STAT_IS_STALE)) + return; + + size = runtime->buffer_size - snd_pcm_capture_avail(runtime); + if (! size) + return; + size = frames_to_bytes(runtime, size); + space = vx_query_hbuffer_size(chip, pipe); + if (space < 0) + goto _error; + if (size > space) + size = space; + size = (size / 3) * 3; /* align to 3 bytes */ + if (size < DMA_READ_ALIGN) + goto _error; + + /* keep the last 6 bytes, they will be read after disconnection */ + count = size - DMA_READ_ALIGN; + /* read bytes until the current pointer reaches to the aligned position + * for word-transfer + */ + while (count > 0) { + if ((pipe->hw_ptr % pipe->align) == 0) + break; + if (vx_wait_for_rx_full(chip) < 0) + goto _error; + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + if (count > 0) { + /* ok, let's accelerate! */ + int align = pipe->align * 3; + space = (count / align) * align; + if (space > 0) { + vx_pseudo_dma_read(chip, runtime, pipe, space); + count -= space; + } + } + /* read the rest of bytes */ + while (count > 0) { + if (vx_wait_for_rx_full(chip) < 0) + goto _error; + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + /* read the last pending 6 bytes */ + count = DMA_READ_ALIGN; + while (count > 0) { + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + /* update the position */ + pipe->transferred += size; + if (pipe->transferred >= pipe->period_bytes) { + pipe->transferred %= pipe->period_bytes; + snd_pcm_period_elapsed(subs); + } + return; + + _error: + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + return; +} + +/* + * vx_pcm_capture_pointer - pointer callback for capture + */ +static snd_pcm_uframes_t vx_pcm_capture_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + return bytes_to_frames(runtime, pipe->hw_ptr); +} + +/* + * operators for PCM capture + */ +static const struct snd_pcm_ops vx_pcm_capture_ops = { + .open = vx_pcm_capture_open, + .close = vx_pcm_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = vx_pcm_hw_params, + .hw_free = vx_pcm_hw_free, + .prepare = vx_pcm_prepare, + .trigger = vx_pcm_trigger, + .pointer = vx_pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + + +/* + * interrupt handler for pcm streams + */ +void vx_pcm_update_intr(struct vx_core *chip, unsigned int events) +{ + unsigned int i; + struct vx_pipe *pipe; + +#define EVENT_MASK (END_OF_BUFFER_EVENTS_PENDING|ASYNC_EVENTS_PENDING) + + if (events & EVENT_MASK) { + vx_init_rmh(&chip->irq_rmh, CMD_ASYNC); + if (events & ASYNC_EVENTS_PENDING) + chip->irq_rmh.Cmd[0] |= 0x00000001; /* SEL_ASYNC_EVENTS */ + if (events & END_OF_BUFFER_EVENTS_PENDING) + chip->irq_rmh.Cmd[0] |= 0x00000002; /* SEL_END_OF_BUF_EVENTS */ + + if (vx_send_msg(chip, &chip->irq_rmh) < 0) { + snd_printdd(KERN_ERR "msg send error!!\n"); + return; + } + + i = 1; + while (i < chip->irq_rmh.LgStat) { + int p, buf, capture, eob; + p = chip->irq_rmh.Stat[i] & MASK_FIRST_FIELD; + capture = (chip->irq_rmh.Stat[i] & 0x400000) ? 1 : 0; + eob = (chip->irq_rmh.Stat[i] & 0x800000) ? 1 : 0; + i++; + if (events & ASYNC_EVENTS_PENDING) + i++; + buf = 1; /* force to transfer */ + if (events & END_OF_BUFFER_EVENTS_PENDING) { + if (eob) + buf = chip->irq_rmh.Stat[i]; + i++; + } + if (capture) + continue; + if (snd_BUG_ON(p < 0 || p >= chip->audio_outs)) + continue; + pipe = chip->playback_pipes[p]; + if (pipe && pipe->substream) { + vx_pcm_playback_update(chip, pipe->substream, pipe); + vx_pcm_playback_transfer(chip, pipe->substream, pipe, buf); + } + } + } + + /* update the capture pcm pointers as frequently as possible */ + for (i = 0; i < chip->audio_ins; i++) { + pipe = chip->capture_pipes[i]; + if (pipe && pipe->substream) + vx_pcm_capture_update(chip, pipe->substream, pipe); + } +} + + +/* + * vx_init_audio_io - check the available audio i/o and allocate pipe arrays + */ +static int vx_init_audio_io(struct vx_core *chip) +{ + struct vx_rmh rmh; + int preferred; + + vx_init_rmh(&rmh, CMD_SUPPORTED); + if (vx_send_msg(chip, &rmh) < 0) { + snd_printk(KERN_ERR "vx: cannot get the supported audio data\n"); + return -ENXIO; + } + + chip->audio_outs = rmh.Stat[0] & MASK_FIRST_FIELD; + chip->audio_ins = (rmh.Stat[0] >> (FIELD_SIZE*2)) & MASK_FIRST_FIELD; + chip->audio_info = rmh.Stat[1]; + + /* allocate pipes */ + chip->playback_pipes = kcalloc(chip->audio_outs, sizeof(struct vx_pipe *), GFP_KERNEL); + if (!chip->playback_pipes) + return -ENOMEM; + chip->capture_pipes = kcalloc(chip->audio_ins, sizeof(struct vx_pipe *), GFP_KERNEL); + if (!chip->capture_pipes) { + kfree(chip->playback_pipes); + return -ENOMEM; + } + + preferred = chip->ibl.size; + chip->ibl.size = 0; + vx_set_ibl(chip, &chip->ibl); /* query the info */ + if (preferred > 0) { + chip->ibl.size = ((preferred + chip->ibl.granularity - 1) / + chip->ibl.granularity) * chip->ibl.granularity; + if (chip->ibl.size > chip->ibl.max_size) + chip->ibl.size = chip->ibl.max_size; + } else + chip->ibl.size = chip->ibl.min_size; /* set to the minimum */ + vx_set_ibl(chip, &chip->ibl); + + return 0; +} + + +/* + * free callback for pcm + */ +static void snd_vx_pcm_free(struct snd_pcm *pcm) +{ + struct vx_core *chip = pcm->private_data; + chip->pcm[pcm->device] = NULL; + kfree(chip->playback_pipes); + chip->playback_pipes = NULL; + kfree(chip->capture_pipes); + chip->capture_pipes = NULL; +} + +/* + * snd_vx_pcm_new - create and initialize a pcm + */ +int snd_vx_pcm_new(struct vx_core *chip) +{ + struct snd_pcm *pcm; + unsigned int i; + int err; + + if ((err = vx_init_audio_io(chip)) < 0) + return err; + + for (i = 0; i < chip->hw->num_codecs; i++) { + unsigned int outs, ins; + outs = chip->audio_outs > i * 2 ? 1 : 0; + ins = chip->audio_ins > i * 2 ? 1 : 0; + if (! outs && ! ins) + break; + err = snd_pcm_new(chip->card, "VX PCM", i, + outs, ins, &pcm); + if (err < 0) + return err; + if (outs) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops); + if (ins) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops); + + pcm->private_data = chip; + pcm->private_free = snd_vx_pcm_free; + pcm->info_flags = 0; + pcm->nonatomic = true; + strcpy(pcm->name, chip->card->shortname); + chip->pcm[i] = pcm; + } + + return 0; +} diff --git a/sound/drivers/vx/vx_uer.c b/sound/drivers/vx/vx_uer.c new file mode 100644 index 000000000..ef0b40c0a --- /dev/null +++ b/sound/drivers/vx/vx_uer.c @@ -0,0 +1,309 @@ +/* + * Driver for Digigram VX soundcards + * + * IEC958 stuff + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include "vx_cmd.h" + + +/* + * vx_modify_board_clock - tell the board that its clock has been modified + * @sync: DSP needs to resynchronize its FIFO + */ +static int vx_modify_board_clock(struct vx_core *chip, int sync) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_MODIFY_CLOCK); + /* Ask the DSP to resynchronize its FIFO. */ + if (sync) + rmh.Cmd[0] |= CMD_MODIFY_CLOCK_S_BIT; + return vx_send_msg(chip, &rmh); +} + +/* + * vx_modify_board_inputs - resync audio inputs + */ +static int vx_modify_board_inputs(struct vx_core *chip) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS); + rmh.Cmd[0] |= 1 << 0; /* reference: AUDIO 0 */ + return vx_send_msg(chip, &rmh); +} + +/* + * vx_read_one_cbit - read one bit from UER config + * @index: the bit index + * returns 0 or 1. + */ +static int vx_read_one_cbit(struct vx_core *chip, int index) +{ + int val; + + mutex_lock(&chip->lock); + if (chip->type >= VX_TYPE_VXPOCKET) { + vx_outb(chip, CSUER, 1); /* read */ + vx_outb(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK); + val = (vx_inb(chip, RUER) >> 7) & 0x01; + } else { + vx_outl(chip, CSUER, 1); /* read */ + vx_outl(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK); + val = (vx_inl(chip, RUER) >> 7) & 0x01; + } + mutex_unlock(&chip->lock); + return val; +} + +/* + * vx_write_one_cbit - write one bit to UER config + * @index: the bit index + * @val: bit value, 0 or 1 + */ +static void vx_write_one_cbit(struct vx_core *chip, int index, int val) +{ + val = !!val; /* 0 or 1 */ + mutex_lock(&chip->lock); + if (vx_is_pcmcia(chip)) { + vx_outb(chip, CSUER, 0); /* write */ + vx_outb(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK)); + } else { + vx_outl(chip, CSUER, 0); /* write */ + vx_outl(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK)); + } + mutex_unlock(&chip->lock); +} + +/* + * vx_read_uer_status - read the current UER status + * @mode: pointer to store the UER mode, VX_UER_MODE_XXX + * + * returns the frequency of UER, or 0 if not sync, + * or a negative error code. + */ +static int vx_read_uer_status(struct vx_core *chip, unsigned int *mode) +{ + int val, freq; + + /* Default values */ + freq = 0; + + /* Read UER status */ + if (vx_is_pcmcia(chip)) + val = vx_inb(chip, CSUER); + else + val = vx_inl(chip, CSUER); + if (val < 0) + return val; + /* If clock is present, read frequency */ + if (val & VX_SUER_CLOCK_PRESENT_MASK) { + switch (val & VX_SUER_FREQ_MASK) { + case VX_SUER_FREQ_32KHz_MASK: + freq = 32000; + break; + case VX_SUER_FREQ_44KHz_MASK: + freq = 44100; + break; + case VX_SUER_FREQ_48KHz_MASK: + freq = 48000; + break; + } + } + if (val & VX_SUER_DATA_PRESENT_MASK) + /* bit 0 corresponds to consumer/professional bit */ + *mode = vx_read_one_cbit(chip, 0) ? + VX_UER_MODE_PROFESSIONAL : VX_UER_MODE_CONSUMER; + else + *mode = VX_UER_MODE_NOT_PRESENT; + + return freq; +} + + +/* + * compute the sample clock value from frequency + * + * The formula is as follows: + * + * HexFreq = (dword) ((double) ((double) 28224000 / (double) Frequency)) + * switch ( HexFreq & 0x00000F00 ) + * case 0x00000100: ; + * case 0x00000200: + * case 0x00000300: HexFreq -= 0x00000201 ; + * case 0x00000400: + * case 0x00000500: + * case 0x00000600: + * case 0x00000700: HexFreq = (dword) (((double) 28224000 / (double) (Frequency*2)) - 1) + * default : HexFreq = (dword) ((double) 28224000 / (double) (Frequency*4)) - 0x000001FF + */ + +static int vx_calc_clock_from_freq(struct vx_core *chip, int freq) +{ + int hexfreq; + + if (snd_BUG_ON(freq <= 0)) + return 0; + + hexfreq = (28224000 * 10) / freq; + hexfreq = (hexfreq + 5) / 10; + + /* max freq = 55125 Hz */ + if (snd_BUG_ON(hexfreq <= 0x00000200)) + return 0; + + if (hexfreq <= 0x03ff) + return hexfreq - 0x00000201; + if (hexfreq <= 0x07ff) + return (hexfreq / 2) - 1; + if (hexfreq <= 0x0fff) + return (hexfreq / 4) + 0x000001ff; + + return 0x5fe; /* min freq = 6893 Hz */ +} + + +/* + * vx_change_clock_source - change the clock source + * @source: the new source + */ +static void vx_change_clock_source(struct vx_core *chip, int source) +{ + /* we mute DAC to prevent clicks */ + vx_toggle_dac_mute(chip, 1); + mutex_lock(&chip->lock); + chip->ops->set_clock_source(chip, source); + chip->clock_source = source; + mutex_unlock(&chip->lock); + /* unmute */ + vx_toggle_dac_mute(chip, 0); +} + + +/* + * set the internal clock + */ +void vx_set_internal_clock(struct vx_core *chip, unsigned int freq) +{ + int clock; + + /* Get real clock value */ + clock = vx_calc_clock_from_freq(chip, freq); + snd_printdd(KERN_DEBUG "set internal clock to 0x%x from freq %d\n", clock, freq); + mutex_lock(&chip->lock); + if (vx_is_pcmcia(chip)) { + vx_outb(chip, HIFREQ, (clock >> 8) & 0x0f); + vx_outb(chip, LOFREQ, clock & 0xff); + } else { + vx_outl(chip, HIFREQ, (clock >> 8) & 0x0f); + vx_outl(chip, LOFREQ, clock & 0xff); + } + mutex_unlock(&chip->lock); +} + + +/* + * set the iec958 status bits + * @bits: 32-bit status bits + */ +void vx_set_iec958_status(struct vx_core *chip, unsigned int bits) +{ + int i; + + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + for (i = 0; i < 32; i++) + vx_write_one_cbit(chip, i, bits & (1 << i)); +} + + +/* + * vx_set_clock - change the clock and audio source if necessary + */ +int vx_set_clock(struct vx_core *chip, unsigned int freq) +{ + int src_changed = 0; + + if (chip->chip_status & VX_STAT_IS_STALE) + return 0; + + /* change the audio source if possible */ + vx_sync_audio_source(chip); + + if (chip->clock_mode == VX_CLOCK_MODE_EXTERNAL || + (chip->clock_mode == VX_CLOCK_MODE_AUTO && + chip->audio_source == VX_AUDIO_SRC_DIGITAL)) { + if (chip->clock_source != UER_SYNC) { + vx_change_clock_source(chip, UER_SYNC); + mdelay(6); + src_changed = 1; + } + } else if (chip->clock_mode == VX_CLOCK_MODE_INTERNAL || + (chip->clock_mode == VX_CLOCK_MODE_AUTO && + chip->audio_source != VX_AUDIO_SRC_DIGITAL)) { + if (chip->clock_source != INTERNAL_QUARTZ) { + vx_change_clock_source(chip, INTERNAL_QUARTZ); + src_changed = 1; + } + if (chip->freq == freq) + return 0; + vx_set_internal_clock(chip, freq); + if (src_changed) + vx_modify_board_inputs(chip); + } + if (chip->freq == freq) + return 0; + chip->freq = freq; + vx_modify_board_clock(chip, 1); + return 0; +} + + +/* + * vx_change_frequency - called from interrupt handler + */ +int vx_change_frequency(struct vx_core *chip) +{ + int freq; + + if (chip->chip_status & VX_STAT_IS_STALE) + return 0; + + if (chip->clock_source == INTERNAL_QUARTZ) + return 0; + /* + * Read the real UER board frequency + */ + freq = vx_read_uer_status(chip, &chip->uer_detected); + if (freq < 0) + return freq; + /* + * The frequency computed by the DSP is good and + * is different from the previous computed. + */ + if (freq == 48000 || freq == 44100 || freq == 32000) + chip->freq_detected = freq; + + return 0; +} diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig new file mode 100644 index 000000000..f7b26b1d7 --- /dev/null +++ b/sound/firewire/Kconfig @@ -0,0 +1,165 @@ +menuconfig SND_FIREWIRE + bool "FireWire sound devices" + depends on FIREWIRE + default y + help + Support for IEEE-1394/FireWire/iLink sound devices. + +if SND_FIREWIRE && FIREWIRE + +config SND_FIREWIRE_LIB + tristate + select SND_PCM + select SND_RAWMIDI + +config SND_DICE + tristate "DICE-based DACs support" + select SND_HWDEP + select SND_FIREWIRE_LIB + help + Say Y here to include support for many DACs based on the DICE + chip family (DICE-II/Jr/Mini) which TC Applied Technologies produces. + + To compile this driver as a module, choose M here: the module + will be called snd-dice. + +config SND_OXFW + tristate "Oxford Semiconductor FW970/971 chipset support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based on + Oxford Semiconductor FW970/971 chipset. + * Griffin Firewave + * LaCie Firewire Speakers + * Behringer F-Control Audio 202 + * Mackie(Loud) Onyx-i series (former models) + * Mackie(Loud) Onyx 1640i (former model) + * Mackie(Loud) Onyx Satellite + * Mackie(Loud) Tapco Link.Firewire + * Mackie(Loud) d.2 pro/d.4 pro (built-in FireWire card with OXFW971 ASIC) + * Mackie(Loud) U.420/U.420d + * TASCAM FireOne + * Stanton Controllers & Systems 1 Deck/Mixer + * APOGEE duet FireWire + + To compile this driver as a module, choose M here: the module + will be called snd-oxfw. + +config SND_ISIGHT + tristate "Apple iSight microphone" + select SND_FIREWIRE_LIB + help + Say Y here to include support for the front and rear microphones + of the Apple iSight web camera. + + To compile this driver as a module, choose M here: the module + will be called snd-isight. + +config SND_FIREWORKS + tristate "Echo Fireworks board module support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based + on Echo Digital Audio Fireworks board: + * Mackie Onyx 400F/1200F + * Echo AudioFire12/8(until 2009 July) + * Echo AudioFire2/4/Pre8/8(since 2009 July) + * Echo Fireworks 8/HDMI + * Gibson Robot Interface Pack/GoldTop + + To compile this driver as a module, choose M here: the module + will be called snd-fireworks. + +config SND_BEBOB + tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based + on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware: + * Edirol FA-66/FA-101 + * PreSonus FIREBOX/FIREPOD/FP10/Inspire1394 + * BridgeCo RDAudio1/Audio5 + * Mackie Onyx 1220/1620/1640 (FireWire I/O Card) + * Mackie d.2 (optional FireWire card with DM1000 ASIC) + * Stanton FinalScratch 2 (ScratchAmp) + * Tascam IF-FW/DM + * Behringer XENIX UFX 1204/1604 + * Behringer Digital Mixer X32 series (X-UF Card) + * Behringer FCA610/1616 + * Apogee Rosetta 200/400 (X-FireWire card) + * Apogee DA/AD/DD-16X (X-FireWire card) + * Apogee Ensemble + * ESI QuataFire 610 + * AcousticReality eARMasterOne + * CME MatrixKFW + * Phonic Helix Board 12 MkII/18 MkII/24 MkII + * Phonic Helix Board 12 Universal/18 Universal/24 Universal + * Lynx Aurora 8/16 (LT-FW) + * ICON FireXon + * PrismSound Orpheus/ADA-8XR + * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW + * TerraTec EWS MIC2/EWS MIC8 + * TerraTec Aureon 7.1 FireWire + * Yamaha GO44/GO46 + * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO + * M-Audio FireWire410/AudioPhile/Solo + * M-Audio Ozonic/NRV10/ProfireLightBridge + * M-Audio FireWire 1814/ProjectMix IO + * Digidesign Mbox 2 Pro + * ToneWeal FW66 + + To compile this driver as a module, choose M here: the module + will be called snd-bebob. + +config SND_FIREWIRE_DIGI00X + tristate "Digidesign Digi 002/003 family support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for Digidesign Digi 002/003 family. + * Digi 002 Console + * Digi 002 Rack + * Digi 003 Console + * Digi 003 Rack + * Digi 003 Rack+ + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-digi00x. + +config SND_FIREWIRE_TASCAM + tristate "TASCAM FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for TASCAM. + * FW-1884 + * FW-1082 + * FW-1804 + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-tascam. + +config SND_FIREWIRE_MOTU + tristate "Mark of the unicorn FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to enable support for FireWire devices which MOTU produced: + * 828mk2 + * 828mk3 + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-motu. + +config SND_FIREFACE + tristate "RME Fireface series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for RME fireface series. + * Fireface 400 + +endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile new file mode 100644 index 000000000..44a7b510b --- /dev/null +++ b/sound/firewire/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# To find a header included by define_trace.h. +CFLAGS_amdtp-stream.o := -I$(src) + +snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ + fcp.o cmp.o amdtp-stream.o amdtp-am824.o +snd-isight-objs := isight.o + +obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o +obj-$(CONFIG_SND_DICE) += dice/ +obj-$(CONFIG_SND_OXFW) += oxfw/ +obj-$(CONFIG_SND_ISIGHT) += snd-isight.o +obj-$(CONFIG_SND_FIREWORKS) += fireworks/ +obj-$(CONFIG_SND_BEBOB) += bebob/ +obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ +obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ +obj-$(CONFIG_SND_FIREFACE) += fireface/ diff --git a/sound/firewire/amdtp-am824.c b/sound/firewire/amdtp-am824.c new file mode 100644 index 000000000..d09da9dbf --- /dev/null +++ b/sound/firewire/amdtp-am824.c @@ -0,0 +1,398 @@ +/* + * AM824 format in Audio and Music Data Transmission Protocol (IEC 61883-6) + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include + +#include "amdtp-am824.h" + +#define CIP_FMT_AM 0x10 + +/* "Clock-based rate control mode" is just supported. */ +#define AMDTP_FDF_AM824 0x00 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +/* + * Several devices look only at the first eight data blocks. + * In any case, this is more than enough for the MIDI data rate. + */ +#define MAX_MIDI_RX_BLOCKS 8 + +struct amdtp_am824 { + struct snd_rawmidi_substream *midi[AM824_MAX_CHANNELS_FOR_MIDI * 8]; + int midi_fifo_limit; + int midi_fifo_used[AM824_MAX_CHANNELS_FOR_MIDI * 8]; + unsigned int pcm_channels; + unsigned int midi_ports; + + u8 pcm_positions[AM824_MAX_CHANNELS_FOR_PCM]; + u8 midi_position; + + unsigned int frame_multiplier; +}; + +/** + * amdtp_am824_set_parameters - set stream parameters + * @s: the AMDTP stream to configure + * @rate: the sample rate + * @pcm_channels: the number of PCM samples in each data block, to be encoded + * as AM824 multi-bit linear audio + * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) + * @double_pcm_frames: one data block transfers two PCM frames + * + * The parameters must be set before the stream is started, and must not be + * changed while the stream is running. + */ +int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports, + bool double_pcm_frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int midi_channels; + unsigned int i; + int err; + + if (amdtp_stream_running(s)) + return -EINVAL; + + if (pcm_channels > AM824_MAX_CHANNELS_FOR_PCM) + return -EINVAL; + + midi_channels = DIV_ROUND_UP(midi_ports, 8); + if (midi_channels > AM824_MAX_CHANNELS_FOR_MIDI) + return -EINVAL; + + if (WARN_ON(amdtp_stream_running(s)) || + WARN_ON(pcm_channels > AM824_MAX_CHANNELS_FOR_PCM) || + WARN_ON(midi_channels > AM824_MAX_CHANNELS_FOR_MIDI)) + return -EINVAL; + + err = amdtp_stream_set_parameters(s, rate, + pcm_channels + midi_channels); + if (err < 0) + return err; + + s->fdf = AMDTP_FDF_AM824 | s->sfc; + + p->pcm_channels = pcm_channels; + p->midi_ports = midi_ports; + + /* + * In IEC 61883-6, one data block represents one event. In ALSA, one + * event equals to one PCM frame. But Dice has a quirk at higher + * sampling rate to transfer two PCM frames in one data block. + */ + if (double_pcm_frames) + p->frame_multiplier = 2; + else + p->frame_multiplier = 1; + + /* init the position map for PCM and MIDI channels */ + for (i = 0; i < pcm_channels; i++) + p->pcm_positions[i] = i; + p->midi_position = p->pcm_channels; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + + return 0; +} +EXPORT_SYMBOL_GPL(amdtp_am824_set_parameters); + +/** + * amdtp_am824_set_pcm_position - set an index of data channel for a channel + * of PCM frame + * @s: the AMDTP stream + * @index: the index of data channel in an data block + * @position: the channel of PCM frame + */ +void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index, + unsigned int position) +{ + struct amdtp_am824 *p = s->protocol; + + if (index < p->pcm_channels) + p->pcm_positions[index] = position; +} +EXPORT_SYMBOL_GPL(amdtp_am824_set_pcm_position); + +/** + * amdtp_am824_set_midi_position - set a index of data channel for MIDI + * conformant data channel + * @s: the AMDTP stream + * @position: the index of data channel in an data block + */ +void amdtp_am824_set_midi_position(struct amdtp_stream *s, + unsigned int position) +{ + struct amdtp_am824 *p = s->protocol; + + p->midi_position = position; +} +EXPORT_SYMBOL_GPL(amdtp_am824_set_midi_position); + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[p->pcm_positions[c]] = + cpu_to_be32((*src >> 8) | 0x40000000); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = be32_to_cpu(buffer[p->pcm_positions[c]]) << 8; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int i, c, channels = p->pcm_channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) + buffer[p->pcm_positions[c]] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +/** + * amdtp_am824_add_pcm_hw_constraints - add hw constraints for PCM substream + * @s: the AMDTP stream for AM824 data block, must be initialized. + * @runtime: the PCM substream runtime + * + */ +int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + err = amdtp_stream_add_pcm_hw_constraints(s, runtime); + if (err < 0) + return err; + + /* AM824 in IEC 61883-6 can deliver 24bit data. */ + return snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); +} +EXPORT_SYMBOL_GPL(amdtp_am824_add_pcm_hw_constraints); + +/** + * amdtp_am824_midi_trigger - start/stop playback/capture with a MIDI device + * @s: the AMDTP stream + * @port: index of MIDI port + * @midi: the MIDI device to be started, or %NULL to stop the current device + * + * Call this function on a running isochronous stream to enable the actual + * transmission of MIDI data. This function should be called from the MIDI + * device's .trigger callback. + */ +void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_am824 *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi[port], midi); +} +EXPORT_SYMBOL_GPL(amdtp_am824_midi_trigger); + +/* + * To avoid sending MIDI bytes at too high a rate, assume that the receiving + * device has a FIFO, and track how much it is filled. This values increases + * by one whenever we send one byte in a packet, but the FIFO empties at + * a constant rate independent of our packet rate. One packet has syt_interval + * samples, so the number of bytes that empty out of the FIFO, per packet(!), + * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing + * fractional values, the values in midi_fifo_used[] are measured in bytes + * multiplied by the sample rate. + */ +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_am824 *p = s->protocol; + int used; + + used = p->midi_fifo_used[port]; + if (used == 0) /* common shortcut */ + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + p->midi_fifo_used[port] = used; + + return used < p->midi_fifo_limit; +} + +static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_am824 *p = s->protocol; + + p->midi_fifo_used[port] += amdtp_rate_table[s->sfc]; +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int f, port; + u8 *b; + + for (f = 0; f < frames; f++) { + b = (u8 *)&buffer[p->midi_position]; + + port = (s->data_block_counter + f) % 8; + if (f < MAX_MIDI_RX_BLOCKS && + midi_ratelimit_per_packet(s, port) && + p->midi[port] != NULL && + snd_rawmidi_transmit(p->midi[port], &b[1], 1) == 1) { + midi_rate_use_one_byte(s, port); + b[0] = 0x81; + } else { + b[0] = 0x80; + b[1] = 0; + } + b[2] = 0; + b[3] = 0; + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int f, port; + int len; + u8 *b; + + for (f = 0; f < frames; f++) { + port = (8 - s->tx_first_dbc + s->data_block_counter + f) % 8; + b = (u8 *)&buffer[p->midi_position]; + + len = b[0] - 0x80; + if ((1 <= len) && (len <= 3) && (p->midi[port])) + snd_rawmidi_receive(p->midi[port], b + 1, len); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks, unsigned int *syt) +{ + struct amdtp_am824 *p = s->protocol; + struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + write_pcm_s32(s, pcm, buffer, data_blocks); + pcm_frames = data_blocks * p->frame_multiplier; + } else { + write_pcm_silence(s, buffer, data_blocks); + pcm_frames = 0; + } + + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + return pcm_frames; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks, unsigned int *syt) +{ + struct amdtp_am824 *p = s->protocol; + struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + read_pcm_s32(s, pcm, buffer, data_blocks); + pcm_frames = data_blocks * p->frame_multiplier; + } else { + pcm_frames = 0; + } + + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + + return pcm_frames; +} + +/** + * amdtp_am824_init - initialize an AMDTP stream structure to handle AM824 + * data block + * @s: the AMDTP stream to initialize + * @unit: the target of the stream + * @dir: the direction of stream + * @flags: the packet transmission method to use + */ +int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + + if (dir == AMDTP_IN_STREAM) + process_data_blocks = process_tx_data_blocks; + else + process_data_blocks = process_rx_data_blocks; + + return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM, + process_data_blocks, + sizeof(struct amdtp_am824)); +} +EXPORT_SYMBOL_GPL(amdtp_am824_init); diff --git a/sound/firewire/amdtp-am824.h b/sound/firewire/amdtp-am824.h new file mode 100644 index 000000000..06d280783 --- /dev/null +++ b/sound/firewire/amdtp-am824.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED + +#include +#include + +#include "amdtp-stream.h" + +#define AM824_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32 + +#define AM824_OUT_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32 + +/* + * This module supports maximum 64 PCM channels for one PCM stream + * This is for our convenience. + */ +#define AM824_MAX_CHANNELS_FOR_PCM 64 + +/* + * AMDTP packet can include channels for MIDI conformant data. + * Each MIDI conformant data channel includes 8 MPX-MIDI data stream. + * Each MPX-MIDI data stream includes one data stream from/to MIDI ports. + * + * This module supports maximum 1 MIDI conformant data channels. + * Then this AMDTP packets can transfer maximum 8 MIDI data streams. + */ +#define AM824_MAX_CHANNELS_FOR_MIDI 1 + +int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports, + bool double_pcm_frames); + +void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index, + unsigned int position); + +void amdtp_am824_set_midi_position(struct amdtp_stream *s, + unsigned int position); + +int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); + +void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags); +#endif diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h new file mode 100644 index 000000000..ac20acf48 --- /dev/null +++ b/sound/firewire/amdtp-stream-trace.h @@ -0,0 +1,198 @@ +/* + * amdtp-stream-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2016 Takashi Sakamoto + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_lib + +#if !defined(_AMDTP_STREAM_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _AMDTP_STREAM_TRACE_H + +#include + +TRACE_EVENT(in_packet, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 *cip_header, unsigned int payload_length, unsigned int index), + TP_ARGS(s, cycles, cip_header, payload_length, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(u32, cip_header0) + __field(u32, cip_header1) + __field(unsigned int, payload_quadlets) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dest = fw_parent_device(s->unit)->card->node_id; + __entry->cip_header0 = cip_header[0]; + __entry->cip_header1 = cip_header[1]; + __entry->payload_quadlets = payload_length / 4; + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %08x %08x %03u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->cip_header0, + __entry->cip_header1, + __entry->payload_quadlets, + __entry->packet_index, + __entry->irq, + __entry->index) +); + +TRACE_EVENT(out_packet, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, __be32 *cip_header, unsigned int payload_length, unsigned int index), + TP_ARGS(s, cycles, cip_header, payload_length, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(u32, cip_header0) + __field(u32, cip_header1) + __field(unsigned int, payload_quadlets) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dest = fw_parent_device(s->unit)->node_id; + __entry->cip_header0 = be32_to_cpu(cip_header[0]); + __entry->cip_header1 = be32_to_cpu(cip_header[1]); + __entry->payload_quadlets = payload_length / 4; + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %08x %08x %03u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->cip_header0, + __entry->cip_header1, + __entry->payload_quadlets, + __entry->packet_index, + __entry->irq, + __entry->index) +); + +TRACE_EVENT(in_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_quadlets, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_quadlets, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dest = fw_parent_device(s->unit)->card->node_id; + __entry->payload_quadlets = payload_quadlets; + __entry->data_blocks = data_blocks, + __entry->data_block_counter = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + +TRACE_EVENT(out_packet_without_header, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_length, unsigned int data_blocks, unsigned int index), + TP_ARGS(s, cycles, payload_length, data_blocks, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dest = fw_parent_device(s->unit)->node_id; + __entry->payload_quadlets = payload_length / 4; + __entry->data_blocks = data_blocks, + __entry->data_block_counter = s->data_block_counter, + __entry->packet_index = s->packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-stream-trace +#include diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c new file mode 100644 index 000000000..293933f46 --- /dev/null +++ b/sound/firewire/amdtp-stream.c @@ -0,0 +1,1031 @@ +/* + * Audio and Music Data Transmission Protocol (IEC 61883-6) streams + * with Common Isochronous Packet (IEC 61883-1) headers + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "amdtp-stream.h" + +#define TICKS_PER_CYCLE 3072 +#define CYCLES_PER_SECOND 8000 +#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND) + +/* Always support Linux tracing subsystem. */ +#define CREATE_TRACE_POINTS +#include "amdtp-stream-trace.h" + +#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */ + +/* isochronous header parameters */ +#define ISO_DATA_LENGTH_SHIFT 16 +#define TAG_NO_CIP_HEADER 0 +#define TAG_CIP 1 + +/* common isochronous packet header parameters */ +#define CIP_EOH_SHIFT 31 +#define CIP_EOH (1u << CIP_EOH_SHIFT) +#define CIP_EOH_MASK 0x80000000 +#define CIP_SID_SHIFT 24 +#define CIP_SID_MASK 0x3f000000 +#define CIP_DBS_MASK 0x00ff0000 +#define CIP_DBS_SHIFT 16 +#define CIP_SPH_MASK 0x00000400 +#define CIP_SPH_SHIFT 10 +#define CIP_DBC_MASK 0x000000ff +#define CIP_FMT_SHIFT 24 +#define CIP_FMT_MASK 0x3f000000 +#define CIP_FDF_MASK 0x00ff0000 +#define CIP_FDF_SHIFT 16 +#define CIP_SYT_MASK 0x0000ffff +#define CIP_SYT_NO_INFO 0xffff + +/* Audio and Music transfer protocol specific parameters */ +#define CIP_FMT_AM 0x10 +#define AMDTP_FDF_NO_DATA 0xff + +/* TODO: make these configurable */ +#define INTERRUPT_INTERVAL 16 +#define QUEUE_LENGTH 48 + +#define IN_PACKET_HEADER_SIZE 4 +#define OUT_PACKET_HEADER_SIZE 0 + +static void pcm_period_tasklet(unsigned long data); + +/** + * amdtp_stream_init - initialize an AMDTP stream structure + * @s: the AMDTP stream to initialize + * @unit: the target of the stream + * @dir: the direction of stream + * @flags: the packet transmission method to use + * @fmt: the value of fmt field in CIP header + * @process_data_blocks: callback handler to process data blocks + * @protocol_size: the size to allocate newly for protocol + */ +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags, + unsigned int fmt, + amdtp_stream_process_data_blocks_t process_data_blocks, + unsigned int protocol_size) +{ + if (process_data_blocks == NULL) + return -EINVAL; + + s->protocol = kzalloc(protocol_size, GFP_KERNEL); + if (!s->protocol) + return -ENOMEM; + + s->unit = unit; + s->direction = dir; + s->flags = flags; + s->context = ERR_PTR(-1); + mutex_init(&s->mutex); + tasklet_init(&s->period_tasklet, pcm_period_tasklet, (unsigned long)s); + s->packet_index = 0; + + init_waitqueue_head(&s->callback_wait); + s->callbacked = false; + + s->fmt = fmt; + s->process_data_blocks = process_data_blocks; + + return 0; +} +EXPORT_SYMBOL(amdtp_stream_init); + +/** + * amdtp_stream_destroy - free stream resources + * @s: the AMDTP stream to destroy + */ +void amdtp_stream_destroy(struct amdtp_stream *s) +{ + /* Not initialized. */ + if (s->protocol == NULL) + return; + + WARN_ON(amdtp_stream_running(s)); + kfree(s->protocol); + mutex_destroy(&s->mutex); +} +EXPORT_SYMBOL(amdtp_stream_destroy); + +const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { + [CIP_SFC_32000] = 8, + [CIP_SFC_44100] = 8, + [CIP_SFC_48000] = 8, + [CIP_SFC_88200] = 16, + [CIP_SFC_96000] = 16, + [CIP_SFC_176400] = 32, + [CIP_SFC_192000] = 32, +}; +EXPORT_SYMBOL(amdtp_syt_intervals); + +const unsigned int amdtp_rate_table[CIP_SFC_COUNT] = { + [CIP_SFC_32000] = 32000, + [CIP_SFC_44100] = 44100, + [CIP_SFC_48000] = 48000, + [CIP_SFC_88200] = 88200, + [CIP_SFC_96000] = 96000, + [CIP_SFC_176400] = 176400, + [CIP_SFC_192000] = 192000, +}; +EXPORT_SYMBOL(amdtp_rate_table); + +/** + * amdtp_stream_add_pcm_hw_constraints - add hw constraints for PCM substream + * @s: the AMDTP stream, which must be initialized. + * @runtime: the PCM substream runtime + */ +int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + int err; + + hw->info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + /* SNDRV_PCM_INFO_BATCH */ + hw->periods_min = 2; + hw->periods_max = UINT_MAX; + + /* bytes for a frame */ + hw->period_bytes_min = 4 * hw->channels_max; + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; + + /* + * Currently firewire-lib processes 16 packets in one software + * interrupt callback. This equals to 2msec but actually the + * interval of the interrupts has a jitter. + * Additionally, even if adding a constraint to fit period size to + * 2msec, actual calculated frames per period doesn't equal to 2msec, + * depending on sampling rate. + * Anyway, the interval to call snd_pcm_period_elapsed() cannot 2msec. + * Here let us use 5msec for safe period interrupt. + */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5000, UINT_MAX); + if (err < 0) + goto end; + + /* Non-Blocking stream has no more constraints */ + if (!(s->flags & CIP_BLOCKING)) + goto end; + + /* + * One AMDTP packet can include some frames. In blocking mode, the + * number equals to SYT_INTERVAL. So the number is 8, 16 or 32, + * depending on its sampling rate. For accurate period interrupt, it's + * preferrable to align period/buffer sizes to current SYT_INTERVAL. + * + * TODO: These constraints can be improved with proper rules. + * Currently apply LCM of SYT_INTERVALs. + */ + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); + if (err < 0) + goto end; + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); +end: + return err; +} +EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints); + +/** + * amdtp_stream_set_parameters - set stream parameters + * @s: the AMDTP stream to configure + * @rate: the sample rate + * @data_block_quadlets: the size of a data block in quadlet unit + * + * The parameters must be set before the stream is started, and must not be + * changed while the stream is running. + */ +int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int data_block_quadlets) +{ + unsigned int sfc; + + for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == ARRAY_SIZE(amdtp_rate_table)) + return -EINVAL; + + s->sfc = sfc; + s->data_block_quadlets = data_block_quadlets; + s->syt_interval = amdtp_syt_intervals[sfc]; + + /* default buffering in the device */ + s->transfer_delay = TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; + if (s->flags & CIP_BLOCKING) + /* additional buffering needed to adjust for no-data packets */ + s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate; + + return 0; +} +EXPORT_SYMBOL(amdtp_stream_set_parameters); + +/** + * amdtp_stream_get_max_payload - get the stream's packet size + * @s: the AMDTP stream + * + * This function must not be called before the stream has been configured + * with amdtp_stream_set_parameters(). + */ +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) +{ + unsigned int multiplier = 1; + unsigned int header_size = 0; + + if (s->flags & CIP_JUMBO_PAYLOAD) + multiplier = 5; + if (!(s->flags & CIP_NO_HEADER)) + header_size = 8; + + return header_size + + s->syt_interval * s->data_block_quadlets * 4 * multiplier; +} +EXPORT_SYMBOL(amdtp_stream_get_max_payload); + +/** + * amdtp_stream_pcm_prepare - prepare PCM device for running + * @s: the AMDTP stream + * + * This function should be called from the PCM device's .prepare callback. + */ +void amdtp_stream_pcm_prepare(struct amdtp_stream *s) +{ + tasklet_kill(&s->period_tasklet); + s->pcm_buffer_pointer = 0; + s->pcm_period_pointer = 0; +} +EXPORT_SYMBOL(amdtp_stream_pcm_prepare); + +static unsigned int calculate_data_blocks(struct amdtp_stream *s, + unsigned int syt) +{ + unsigned int phase, data_blocks; + + /* Blocking mode. */ + if (s->flags & CIP_BLOCKING) { + /* This module generate empty packet for 'no data'. */ + if (syt == CIP_SYT_NO_INFO) + data_blocks = 0; + else + data_blocks = s->syt_interval; + /* Non-blocking mode. */ + } else { + if (!cip_sfc_is_base_44100(s->sfc)) { + /* Sample_rate / 8000 is an integer, and precomputed. */ + data_blocks = s->data_block_state; + } else { + phase = s->data_block_state; + + /* + * This calculates the number of data blocks per packet so that + * 1) the overall rate is correct and exactly synchronized to + * the bus clock, and + * 2) packets with a rounded-up number of blocks occur as early + * as possible in the sequence (to prevent underruns of the + * device's buffer). + */ + if (s->sfc == CIP_SFC_44100) + /* 6 6 5 6 5 6 5 ... */ + data_blocks = 5 + ((phase & 1) ^ + (phase == 0 || phase >= 40)); + else + /* 12 11 11 11 11 ... or 23 22 22 22 22 ... */ + data_blocks = 11 * (s->sfc >> 1) + (phase == 0); + if (++phase >= (80 >> (s->sfc >> 1))) + phase = 0; + s->data_block_state = phase; + } + } + + return data_blocks; +} + +static unsigned int calculate_syt(struct amdtp_stream *s, + unsigned int cycle) +{ + unsigned int syt_offset, phase, index, syt; + + if (s->last_syt_offset < TICKS_PER_CYCLE) { + if (!cip_sfc_is_base_44100(s->sfc)) + syt_offset = s->last_syt_offset + s->syt_offset_state; + else { + /* + * The time, in ticks, of the n'th SYT_INTERVAL sample is: + * n * SYT_INTERVAL * 24576000 / sample_rate + * Modulo TICKS_PER_CYCLE, the difference between successive + * elements is about 1386.23. Rounding the results of this + * formula to the SYT precision results in a sequence of + * differences that begins with: + * 1386 1386 1387 1386 1386 1386 1387 1386 1386 1386 1387 ... + * This code generates _exactly_ the same sequence. + */ + phase = s->syt_offset_state; + index = phase % 13; + syt_offset = s->last_syt_offset; + syt_offset += 1386 + ((index && !(index & 3)) || + phase == 146); + if (++phase >= 147) + phase = 0; + s->syt_offset_state = phase; + } + } else + syt_offset = s->last_syt_offset - TICKS_PER_CYCLE; + s->last_syt_offset = syt_offset; + + if (syt_offset < TICKS_PER_CYCLE) { + syt_offset += s->transfer_delay; + syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12; + syt += syt_offset % TICKS_PER_CYCLE; + + return syt & CIP_SYT_MASK; + } else { + return CIP_SYT_NO_INFO; + } +} + +static void update_pcm_pointers(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + unsigned int frames) +{ + unsigned int ptr; + + ptr = s->pcm_buffer_pointer + frames; + if (ptr >= pcm->runtime->buffer_size) + ptr -= pcm->runtime->buffer_size; + WRITE_ONCE(s->pcm_buffer_pointer, ptr); + + s->pcm_period_pointer += frames; + if (s->pcm_period_pointer >= pcm->runtime->period_size) { + s->pcm_period_pointer -= pcm->runtime->period_size; + tasklet_hi_schedule(&s->period_tasklet); + } +} + +static void pcm_period_tasklet(unsigned long data) +{ + struct amdtp_stream *s = (void *)data; + struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); + + if (pcm) + snd_pcm_period_elapsed(pcm); +} + +static int queue_packet(struct amdtp_stream *s, unsigned int header_length, + unsigned int payload_length) +{ + struct fw_iso_packet p = {0}; + int err = 0; + + if (IS_ERR(s->context)) + goto end; + + p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); + p.tag = s->tag; + p.header_length = header_length; + if (payload_length > 0) + p.payload_length = payload_length; + else + p.skip = true; + err = fw_iso_context_queue(s->context, &p, &s->buffer.iso_buffer, + s->buffer.packets[s->packet_index].offset); + if (err < 0) { + dev_err(&s->unit->device, "queueing error: %d\n", err); + goto end; + } + + if (++s->packet_index >= QUEUE_LENGTH) + s->packet_index = 0; +end: + return err; +} + +static inline int queue_out_packet(struct amdtp_stream *s, + unsigned int payload_length) +{ + return queue_packet(s, OUT_PACKET_HEADER_SIZE, payload_length); +} + +static inline int queue_in_packet(struct amdtp_stream *s) +{ + return queue_packet(s, IN_PACKET_HEADER_SIZE, s->max_payload_length); +} + +static int handle_out_packet(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int syt; + unsigned int data_blocks; + unsigned int pcm_frames; + struct snd_pcm_substream *pcm; + + buffer = s->buffer.packets[s->packet_index].buffer; + syt = calculate_syt(s, cycle); + data_blocks = calculate_data_blocks(s, syt); + pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); + + if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; + + buffer[0] = cpu_to_be32(READ_ONCE(s->source_node_id_field) | + (s->data_block_quadlets << CIP_DBS_SHIFT) | + ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | + s->data_block_counter); + buffer[1] = cpu_to_be32(CIP_EOH | + ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | + ((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | + (syt & CIP_SYT_MASK)); + + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; + payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; + + trace_out_packet(s, cycle, buffer, payload_length, index); + + if (queue_out_packet(s, payload_length) < 0) + return -EIO; + + pcm = READ_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + /* No need to return the number of handled data blocks. */ + return 0; +} + +static int handle_out_packet_without_header(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int syt; + unsigned int data_blocks; + unsigned int pcm_frames; + struct snd_pcm_substream *pcm; + + buffer = s->buffer.packets[s->packet_index].buffer; + syt = calculate_syt(s, cycle); + data_blocks = calculate_data_blocks(s, syt); + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + payload_length = data_blocks * 4 * s->data_block_quadlets; + + trace_out_packet_without_header(s, cycle, payload_length, data_blocks, + index); + + if (queue_out_packet(s, payload_length) < 0) + return -EIO; + + pcm = READ_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + /* No need to return the number of handled data blocks. */ + return 0; +} + +static int handle_in_packet(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + u32 cip_header[2]; + unsigned int sph, fmt, fdf, syt; + unsigned int data_block_quadlets, data_block_counter, dbc_interval; + unsigned int data_blocks; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + bool lost; + + buffer = s->buffer.packets[s->packet_index].buffer; + cip_header[0] = be32_to_cpu(buffer[0]); + cip_header[1] = be32_to_cpu(buffer[1]); + + trace_in_packet(s, cycle, cip_header, payload_length, index); + + /* + * This module supports 'Two-quadlet CIP header with SYT field'. + * For convenience, also check FMT field is AM824 or not. + */ + if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) && + (!(s->flags & CIP_HEADER_WITHOUT_EOH))) { + dev_info_ratelimited(&s->unit->device, + "Invalid CIP header for AMDTP: %08X:%08X\n", + cip_header[0], cip_header[1]); + data_blocks = 0; + pcm_frames = 0; + goto end; + } + + /* Check valid protocol or not. */ + sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT; + fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; + if (sph != s->sph || fmt != s->fmt) { + dev_info_ratelimited(&s->unit->device, + "Detect unexpected protocol: %08x %08x\n", + cip_header[0], cip_header[1]); + data_blocks = 0; + pcm_frames = 0; + goto end; + } + + /* Calculate data blocks */ + fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT; + if (payload_length < 12 || + (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) { + data_blocks = 0; + } else { + data_block_quadlets = + (cip_header[0] & CIP_DBS_MASK) >> CIP_DBS_SHIFT; + /* avoid division by zero */ + if (data_block_quadlets == 0) { + dev_err(&s->unit->device, + "Detect invalid value in dbs field: %08X\n", + cip_header[0]); + return -EPROTO; + } + if (s->flags & CIP_WRONG_DBS) + data_block_quadlets = s->data_block_quadlets; + + data_blocks = (payload_length / 4 - 2) / + data_block_quadlets; + } + + /* Check data block counter continuity */ + data_block_counter = cip_header[0] & CIP_DBC_MASK; + if (data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) && + s->data_block_counter != UINT_MAX) + data_block_counter = s->data_block_counter; + + if (((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && + data_block_counter == s->tx_first_dbc) || + s->data_block_counter == UINT_MAX) { + lost = false; + } else if (!(s->flags & CIP_DBC_IS_END_EVENT)) { + lost = data_block_counter != s->data_block_counter; + } else { + if (data_blocks > 0 && s->tx_dbc_interval > 0) + dbc_interval = s->tx_dbc_interval; + else + dbc_interval = data_blocks; + + lost = data_block_counter != + ((s->data_block_counter + dbc_interval) & 0xff); + } + + if (lost) { + dev_err(&s->unit->device, + "Detect discontinuity of CIP: %02X %02X\n", + s->data_block_counter, data_block_counter); + return -EIO; + } + + syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK; + pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); + + if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = data_block_counter; + else + s->data_block_counter = + (data_block_counter + data_blocks) & 0xff; +end: + if (queue_in_packet(s) < 0) + return -EIO; + + pcm = READ_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + return 0; +} + +static int handle_in_packet_without_header(struct amdtp_stream *s, + unsigned int payload_length, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int payload_quadlets; + unsigned int data_blocks; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + buffer = s->buffer.packets[s->packet_index].buffer; + payload_quadlets = payload_length / 4; + data_blocks = payload_quadlets / s->data_block_quadlets; + + trace_in_packet_without_header(s, cycle, payload_quadlets, data_blocks, + index); + + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + if (queue_in_packet(s) < 0) + return -EIO; + + pcm = READ_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + return 0; +} + +/* + * In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On + * the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent + * it. Thus, via Linux firewire subsystem, we can get the 3 bits for second. + */ +static inline u32 compute_cycle_count(u32 tstamp) +{ + return (((tstamp >> 13) & 0x07) * 8000) + (tstamp & 0x1fff); +} + +static inline u32 increment_cycle_count(u32 cycle, unsigned int addend) +{ + cycle += addend; + if (cycle >= 8 * CYCLES_PER_SECOND) + cycle -= 8 * CYCLES_PER_SECOND; + return cycle; +} + +static inline u32 decrement_cycle_count(u32 cycle, unsigned int subtrahend) +{ + if (cycle < subtrahend) + cycle += 8 * CYCLES_PER_SECOND; + return cycle - subtrahend; +} + +static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, + size_t header_length, void *header, + void *private_data) +{ + struct amdtp_stream *s = private_data; + unsigned int i, packets = header_length / 4; + u32 cycle; + + if (s->packet_index < 0) + return; + + cycle = compute_cycle_count(tstamp); + + /* Align to actual cycle count for the last packet. */ + cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets); + + for (i = 0; i < packets; ++i) { + cycle = increment_cycle_count(cycle, 1); + if (s->handle_packet(s, 0, cycle, i) < 0) { + s->packet_index = -1; + if (in_interrupt()) + amdtp_stream_pcm_abort(s); + WRITE_ONCE(s->pcm_buffer_pointer, SNDRV_PCM_POS_XRUN); + return; + } + } + + fw_iso_context_queue_flush(s->context); +} + +static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, + size_t header_length, void *header, + void *private_data) +{ + struct amdtp_stream *s = private_data; + unsigned int i, packets; + unsigned int payload_length, max_payload_length; + __be32 *headers = header; + u32 cycle; + + if (s->packet_index < 0) + return; + + /* The number of packets in buffer */ + packets = header_length / IN_PACKET_HEADER_SIZE; + + cycle = compute_cycle_count(tstamp); + + /* Align to actual cycle count for the last packet. */ + cycle = decrement_cycle_count(cycle, packets); + + /* For buffer-over-run prevention. */ + max_payload_length = s->max_payload_length; + + for (i = 0; i < packets; i++) { + cycle = increment_cycle_count(cycle, 1); + + /* The number of bytes in this packet */ + payload_length = + (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT); + if (payload_length > max_payload_length) { + dev_err(&s->unit->device, + "Detect jumbo payload: %04x %04x\n", + payload_length, max_payload_length); + break; + } + + if (s->handle_packet(s, payload_length, cycle, i) < 0) + break; + } + + /* Queueing error or detecting invalid payload. */ + if (i < packets) { + s->packet_index = -1; + if (in_interrupt()) + amdtp_stream_pcm_abort(s); + WRITE_ONCE(s->pcm_buffer_pointer, SNDRV_PCM_POS_XRUN); + return; + } + + fw_iso_context_queue_flush(s->context); +} + +/* this is executed one time */ +static void amdtp_stream_first_callback(struct fw_iso_context *context, + u32 tstamp, size_t header_length, + void *header, void *private_data) +{ + struct amdtp_stream *s = private_data; + u32 cycle; + unsigned int packets; + + /* + * For in-stream, first packet has come. + * For out-stream, prepared to transmit first packet + */ + s->callbacked = true; + wake_up(&s->callback_wait); + + cycle = compute_cycle_count(tstamp); + + if (s->direction == AMDTP_IN_STREAM) { + packets = header_length / IN_PACKET_HEADER_SIZE; + cycle = decrement_cycle_count(cycle, packets); + context->callback.sc = in_stream_callback; + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_in_packet_without_header; + else + s->handle_packet = handle_in_packet; + } else { + packets = header_length / 4; + cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets); + context->callback.sc = out_stream_callback; + if (s->flags & CIP_NO_HEADER) + s->handle_packet = handle_out_packet_without_header; + else + s->handle_packet = handle_out_packet; + } + + s->start_cycle = cycle; + + context->callback.sc(context, tstamp, header_length, header, s); +} + +/** + * amdtp_stream_start - start transferring packets + * @s: the AMDTP stream to start + * @channel: the isochronous channel on the bus + * @speed: firewire speed code + * + * The stream cannot be started until it has been configured with + * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI + * device can be started. + */ +int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) +{ + static const struct { + unsigned int data_block; + unsigned int syt_offset; + } initial_state[] = { + [CIP_SFC_32000] = { 4, 3072 }, + [CIP_SFC_48000] = { 6, 1024 }, + [CIP_SFC_96000] = { 12, 1024 }, + [CIP_SFC_192000] = { 24, 1024 }, + [CIP_SFC_44100] = { 0, 67 }, + [CIP_SFC_88200] = { 0, 67 }, + [CIP_SFC_176400] = { 0, 67 }, + }; + unsigned int header_size; + enum dma_data_direction dir; + int type, tag, err; + + mutex_lock(&s->mutex); + + if (WARN_ON(amdtp_stream_running(s) || + (s->data_block_quadlets < 1))) { + err = -EBADFD; + goto err_unlock; + } + + if (s->direction == AMDTP_IN_STREAM) + s->data_block_counter = UINT_MAX; + else + s->data_block_counter = 0; + s->data_block_state = initial_state[s->sfc].data_block; + s->syt_offset_state = initial_state[s->sfc].syt_offset; + s->last_syt_offset = TICKS_PER_CYCLE; + + /* initialize packet buffer */ + if (s->direction == AMDTP_IN_STREAM) { + dir = DMA_FROM_DEVICE; + type = FW_ISO_CONTEXT_RECEIVE; + header_size = IN_PACKET_HEADER_SIZE; + } else { + dir = DMA_TO_DEVICE; + type = FW_ISO_CONTEXT_TRANSMIT; + header_size = OUT_PACKET_HEADER_SIZE; + } + err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH, + amdtp_stream_get_max_payload(s), dir); + if (err < 0) + goto err_unlock; + + s->context = fw_iso_context_create(fw_parent_device(s->unit)->card, + type, channel, speed, header_size, + amdtp_stream_first_callback, s); + if (IS_ERR(s->context)) { + err = PTR_ERR(s->context); + if (err == -EBUSY) + dev_err(&s->unit->device, + "no free stream on this controller\n"); + goto err_buffer; + } + + amdtp_stream_update(s); + + if (s->direction == AMDTP_IN_STREAM) + s->max_payload_length = amdtp_stream_get_max_payload(s); + + if (s->flags & CIP_NO_HEADER) + s->tag = TAG_NO_CIP_HEADER; + else + s->tag = TAG_CIP; + + s->packet_index = 0; + do { + if (s->direction == AMDTP_IN_STREAM) + err = queue_in_packet(s); + else + err = queue_out_packet(s, 0); + if (err < 0) + goto err_context; + } while (s->packet_index > 0); + + /* NOTE: TAG1 matches CIP. This just affects in stream. */ + tag = FW_ISO_CONTEXT_MATCH_TAG1; + if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER)) + tag |= FW_ISO_CONTEXT_MATCH_TAG0; + + s->callbacked = false; + err = fw_iso_context_start(s->context, -1, 0, tag); + if (err < 0) + goto err_context; + + mutex_unlock(&s->mutex); + + return 0; + +err_context: + fw_iso_context_destroy(s->context); + s->context = ERR_PTR(-1); +err_buffer: + iso_packets_buffer_destroy(&s->buffer, s->unit); +err_unlock: + mutex_unlock(&s->mutex); + + return err; +} +EXPORT_SYMBOL(amdtp_stream_start); + +/** + * amdtp_stream_pcm_pointer - get the PCM buffer position + * @s: the AMDTP stream that transports the PCM data + * + * Returns the current buffer position, in frames. + */ +unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s) +{ + /* + * This function is called in software IRQ context of period_tasklet or + * process context. + * + * When the software IRQ context was scheduled by software IRQ context + * of IR/IT contexts, queued packets were already handled. Therefore, + * no need to flush the queue in buffer anymore. + * + * When the process context reach here, some packets will be already + * queued in the buffer. These packets should be handled immediately + * to keep better granularity of PCM pointer. + * + * Later, the process context will sometimes schedules software IRQ + * context of the period_tasklet. Then, no need to flush the queue by + * the same reason as described for IR/IT contexts. + */ + if (!in_interrupt() && amdtp_stream_running(s)) + fw_iso_context_flush_completions(s->context); + + return READ_ONCE(s->pcm_buffer_pointer); +} +EXPORT_SYMBOL(amdtp_stream_pcm_pointer); + +/** + * amdtp_stream_pcm_ack - acknowledge queued PCM frames + * @s: the AMDTP stream that transfers the PCM frames + * + * Returns zero always. + */ +int amdtp_stream_pcm_ack(struct amdtp_stream *s) +{ + /* + * Process isochronous packets for recent isochronous cycle to handle + * queued PCM frames. + */ + if (amdtp_stream_running(s)) + fw_iso_context_flush_completions(s->context); + + return 0; +} +EXPORT_SYMBOL(amdtp_stream_pcm_ack); + +/** + * amdtp_stream_update - update the stream after a bus reset + * @s: the AMDTP stream + */ +void amdtp_stream_update(struct amdtp_stream *s) +{ + /* Precomputing. */ + WRITE_ONCE(s->source_node_id_field, + (fw_parent_device(s->unit)->card->node_id << CIP_SID_SHIFT) & CIP_SID_MASK); +} +EXPORT_SYMBOL(amdtp_stream_update); + +/** + * amdtp_stream_stop - stop sending packets + * @s: the AMDTP stream to stop + * + * All PCM and MIDI devices of the stream must be stopped before the stream + * itself can be stopped. + */ +void amdtp_stream_stop(struct amdtp_stream *s) +{ + mutex_lock(&s->mutex); + + if (!amdtp_stream_running(s)) { + mutex_unlock(&s->mutex); + return; + } + + tasklet_kill(&s->period_tasklet); + fw_iso_context_stop(s->context); + fw_iso_context_destroy(s->context); + s->context = ERR_PTR(-1); + iso_packets_buffer_destroy(&s->buffer, s->unit); + + s->callbacked = false; + + mutex_unlock(&s->mutex); +} +EXPORT_SYMBOL(amdtp_stream_stop); + +/** + * amdtp_stream_pcm_abort - abort the running PCM device + * @s: the AMDTP stream about to be stopped + * + * If the isochronous stream needs to be stopped asynchronously, call this + * function first to stop the PCM device. + */ +void amdtp_stream_pcm_abort(struct amdtp_stream *s) +{ + struct snd_pcm_substream *pcm; + + pcm = READ_ONCE(s->pcm); + if (pcm) + snd_pcm_stop_xrun(pcm); +} +EXPORT_SYMBOL(amdtp_stream_pcm_abort); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h new file mode 100644 index 000000000..e45de3eec --- /dev/null +++ b/sound/firewire/amdtp-stream.h @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_H_INCLUDED + +#include +#include +#include +#include +#include +#include "packets-buffer.h" + +/** + * enum cip_flags - describes details of the streaming protocol + * @CIP_NONBLOCKING: In non-blocking mode, each packet contains + * sample_rate/8000 samples, with rounding up or down to adjust + * for clock skew and left-over fractional samples. This should + * be used if supported by the device. + * @CIP_BLOCKING: In blocking mode, each packet contains either zero or + * SYT_INTERVAL samples, with these two types alternating so that + * the overall sample rate comes out right. + * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. + * @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end + * of event in the packet. Out of IEC 61883. + * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. + * The value of data_block_quadlets is used instead of reported value. + * @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is + * skipped for detecting discontinuity. + * @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty + * packet is wrong but the others are correct. + * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an + * packet is larger than IEC 61883-6 defines. Current implementation + * allows 5 times as large as IEC 61883-6 defines. + * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include + * valid EOH. + * @CIP_NO_HEADERS: a lack of headers in packets + */ +enum cip_flags { + CIP_NONBLOCKING = 0x00, + CIP_BLOCKING = 0x01, + CIP_EMPTY_WITH_TAG0 = 0x02, + CIP_DBC_IS_END_EVENT = 0x04, + CIP_WRONG_DBS = 0x08, + CIP_SKIP_DBC_ZERO_CHECK = 0x10, + CIP_EMPTY_HAS_WRONG_DBC = 0x20, + CIP_JUMBO_PAYLOAD = 0x40, + CIP_HEADER_WITHOUT_EOH = 0x80, + CIP_NO_HEADER = 0x100, +}; + +/** + * enum cip_sfc - supported Sampling Frequency Codes (SFCs) + * @CIP_SFC_32000: 32,000 data blocks + * @CIP_SFC_44100: 44,100 data blocks + * @CIP_SFC_48000: 48,000 data blocks + * @CIP_SFC_88200: 88,200 data blocks + * @CIP_SFC_96000: 96,000 data blocks + * @CIP_SFC_176400: 176,400 data blocks + * @CIP_SFC_192000: 192,000 data blocks + * @CIP_SFC_COUNT: the number of supported SFCs + * + * These values are used to show nominal Sampling Frequency Code in + * Format Dependent Field (FDF) of AMDTP packet header. In IEC 61883-6:2002, + * this code means the number of events per second. Actually the code + * represents the number of data blocks transferred per second in an AMDTP + * stream. + * + * In IEC 61883-6:2005, some extensions were added to support more types of + * data such as 'One Bit LInear Audio', therefore the meaning of SFC became + * different depending on the types. + * + * Currently our implementation is compatible with IEC 61883-6:2002. + */ +enum cip_sfc { + CIP_SFC_32000 = 0, + CIP_SFC_44100 = 1, + CIP_SFC_48000 = 2, + CIP_SFC_88200 = 3, + CIP_SFC_96000 = 4, + CIP_SFC_176400 = 5, + CIP_SFC_192000 = 6, + CIP_SFC_COUNT +}; + +struct fw_unit; +struct fw_iso_context; +struct snd_pcm_substream; +struct snd_pcm_runtime; + +enum amdtp_stream_direction { + AMDTP_OUT_STREAM = 0, + AMDTP_IN_STREAM +}; + +struct amdtp_stream; +typedef unsigned int (*amdtp_stream_process_data_blocks_t)( + struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt); +struct amdtp_stream { + struct fw_unit *unit; + enum cip_flags flags; + enum amdtp_stream_direction direction; + struct mutex mutex; + + /* For packet processing. */ + struct fw_iso_context *context; + struct iso_packets_buffer buffer; + int packet_index; + int tag; + int (*handle_packet)(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index); + unsigned int max_payload_length; + + /* For CIP headers. */ + unsigned int source_node_id_field; + unsigned int data_block_quadlets; + unsigned int data_block_counter; + unsigned int sph; + unsigned int fmt; + unsigned int fdf; + /* quirk: fixed interval of dbc between previos/current packets. */ + unsigned int tx_dbc_interval; + /* quirk: indicate the value of dbc field in a first packet. */ + unsigned int tx_first_dbc; + + /* Internal flags. */ + enum cip_sfc sfc; + unsigned int syt_interval; + unsigned int transfer_delay; + unsigned int data_block_state; + unsigned int last_syt_offset; + unsigned int syt_offset_state; + + /* For a PCM substream processing. */ + struct snd_pcm_substream *pcm; + struct tasklet_struct period_tasklet; + snd_pcm_uframes_t pcm_buffer_pointer; + unsigned int pcm_period_pointer; + + /* To wait for first packet. */ + bool callbacked; + wait_queue_head_t callback_wait; + u32 start_cycle; + + /* For backends to process data blocks. */ + void *protocol; + amdtp_stream_process_data_blocks_t process_data_blocks; +}; + +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags, + unsigned int fmt, + amdtp_stream_process_data_blocks_t process_data_blocks, + unsigned int protocol_size); +void amdtp_stream_destroy(struct amdtp_stream *s); + +int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int data_block_quadlets); +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s); + +int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed); +void amdtp_stream_update(struct amdtp_stream *s); +void amdtp_stream_stop(struct amdtp_stream *s); + +int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); + +void amdtp_stream_pcm_prepare(struct amdtp_stream *s); +unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s); +int amdtp_stream_pcm_ack(struct amdtp_stream *s); +void amdtp_stream_pcm_abort(struct amdtp_stream *s); + +extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT]; +extern const unsigned int amdtp_rate_table[CIP_SFC_COUNT]; + +/** + * amdtp_stream_running - check stream is running or not + * @s: the AMDTP stream + * + * If this function returns true, the stream is running. + */ +static inline bool amdtp_stream_running(struct amdtp_stream *s) +{ + return !IS_ERR(s->context); +} + +/** + * amdtp_streaming_error - check for streaming error + * @s: the AMDTP stream + * + * If this function returns true, the stream's packet queue has stopped due to + * an asynchronous error. + */ +static inline bool amdtp_streaming_error(struct amdtp_stream *s) +{ + return s->packet_index < 0; +} + +/** + * amdtp_stream_pcm_running - check PCM substream is running or not + * @s: the AMDTP stream + * + * If this function returns true, PCM substream in the AMDTP stream is running. + */ +static inline bool amdtp_stream_pcm_running(struct amdtp_stream *s) +{ + return !!s->pcm; +} + +/** + * amdtp_stream_pcm_trigger - start/stop playback from a PCM device + * @s: the AMDTP stream + * @pcm: the PCM device to be started, or %NULL to stop the current device + * + * Call this function on a running isochronous stream to enable the actual + * transmission of PCM data. This function should be called from the PCM + * device's .trigger callback. + */ +static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s, + struct snd_pcm_substream *pcm) +{ + WRITE_ONCE(s->pcm, pcm); +} + +static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc) +{ + return sfc & 1; +} + +/** + * amdtp_stream_wait_callback - sleep till callbacked or timeout + * @s: the AMDTP stream + * @timeout: msec till timeout + * + * If this function return false, the AMDTP stream should be stopped. + */ +static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s, + unsigned int timeout) +{ + return wait_event_timeout(s->callback_wait, + s->callbacked == true, + msecs_to_jiffies(timeout)) > 0; +} + +#endif diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile new file mode 100644 index 000000000..14bc84c51 --- /dev/null +++ b/sound/firewire/bebob/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ + bebob_pcm.o bebob_hwdep.o bebob_terratec.o \ + bebob_yamaha_terratec.o bebob_focusrite.o bebob_maudio.o \ + bebob.o +obj-$(CONFIG_SND_BEBOB) += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c new file mode 100644 index 000000000..eac3ff24e --- /dev/null +++ b/sound/firewire/bebob/bebob.c @@ -0,0 +1,564 @@ +/* + * bebob.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire + * devices with DM1000/DM1100/DM1500 chipset. It gives common way for host + * system to handle BeBoB based devices. + */ + +#include "bebob.h" + +MODULE_DESCRIPTION("BridgeCo BeBoB driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable BeBoB sound card"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +/* Offsets from information register. */ +#define INFO_OFFSET_BEBOB_VERSION 0x08 +#define INFO_OFFSET_GUID 0x10 +#define INFO_OFFSET_HW_MODEL_ID 0x18 +#define INFO_OFFSET_HW_MODEL_REVISION 0x1c + +#define VEN_EDIROL 0x000040ab +#define VEN_PRESONUS 0x00000a92 +#define VEN_BRIDGECO 0x000007f5 +#define VEN_MACKIE1 0x0000000f +#define VEN_MACKIE2 0x00000ff2 +#define VEN_STANTON 0x00001260 +#define VEN_TASCAM 0x0000022e +#define VEN_BEHRINGER 0x00001564 +#define VEN_APOGEE 0x000003db +#define VEN_ESI 0x00000f1b +#define VEN_ACOUSTIC 0x00000002 +#define VEN_CME 0x0000000a +#define VEN_PHONIC 0x00001496 +#define VEN_LYNX 0x000019e5 +#define VEN_ICON 0x00001a9e +#define VEN_PRISMSOUND 0x00001198 +#define VEN_TERRATEC 0x00000aac +#define VEN_YAMAHA 0x0000a0de +#define VEN_FOCUSRITE 0x0000130e +#define VEN_MAUDIO1 0x00000d6c +#define VEN_MAUDIO2 0x000007f5 +#define VEN_DIGIDESIGN 0x00a07e +#define OUI_SHOUYO 0x002327 + +#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000 +#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 +#define MODEL_MAUDIO_FW1814 0x00010071 +#define MODEL_MAUDIO_PROJECTMIX 0x00010091 + +static int +name_device(struct snd_bebob *bebob) +{ + struct fw_device *fw_dev = fw_parent_device(bebob->unit); + char vendor[24] = {0}; + char model[32] = {0}; + u32 hw_id; + u32 data[2] = {0}; + u32 revision; + u32 version; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(bebob->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + /* get hardware id */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID, + &hw_id); + if (err < 0) + goto end; + + /* get hardware revision */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION, + &revision); + if (err < 0) + goto end; + + /* get GUID */ + err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID, + data, sizeof(data)); + if (err < 0) + goto end; + + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_BEBOB_VERSION, + &version); + if (err < 0) + goto end; + bebob->version = version; + + strcpy(bebob->card->driver, "BeBoB"); + strcpy(bebob->card->shortname, model); + strcpy(bebob->card->mixername, model); + snprintf(bebob->card->longname, sizeof(bebob->card->longname), + "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d", + vendor, model, hw_id, revision, + data[0], data[1], dev_name(&bebob->unit->device), + 100 << fw_dev->max_speed); +end: + return err; +} + +static void bebob_free(struct snd_bebob *bebob) +{ + snd_bebob_stream_destroy_duplex(bebob); + fw_unit_put(bebob->unit); + + kfree(bebob->maudio_special_quirk); + + mutex_destroy(&bebob->mutex); + kfree(bebob); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void +bebob_card_free(struct snd_card *card) +{ + struct snd_bebob *bebob = card->private_data; + + mutex_lock(&devices_mutex); + clear_bit(bebob->card_index, devices_used); + mutex_unlock(&devices_mutex); + + bebob_free(card->private_data); +} + +static const struct snd_bebob_spec * +get_saffire_spec(struct fw_unit *unit) +{ + char name[24] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return NULL; + + if (strcmp(name, "SaffireLE") == 0) + return &saffire_le_spec; + else + return &saffire_spec; +} + +static bool +check_audiophile_booted(struct fw_unit *unit) +{ + char name[28] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return false; + + return strncmp(name, "FW Audiophile Bootloader", 24) != 0; +} + +static void +do_registration(struct work_struct *work) +{ + struct snd_bebob *bebob = + container_of(work, struct snd_bebob, dwork.work); + unsigned int card_index; + int err; + + if (bebob->registered) + return; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; card_index++) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return; + } + + err = snd_card_new(&bebob->unit->device, index[card_index], + id[card_index], THIS_MODULE, 0, &bebob->card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return; + } + + err = name_device(bebob); + if (err < 0) + goto error; + + if (bebob->spec == &maudio_special_spec) { + if (bebob->entry->model_id == MODEL_MAUDIO_FW1814) + err = snd_bebob_maudio_special_discover(bebob, true); + else + err = snd_bebob_maudio_special_discover(bebob, false); + } else { + err = snd_bebob_stream_discover(bebob); + } + if (err < 0) + goto error; + + err = snd_bebob_stream_init_duplex(bebob); + if (err < 0) + goto error; + + snd_bebob_proc_init(bebob); + + if (bebob->midi_input_ports > 0 || bebob->midi_output_ports > 0) { + err = snd_bebob_create_midi_devices(bebob); + if (err < 0) + goto error; + } + + err = snd_bebob_create_pcm_devices(bebob); + if (err < 0) + goto error; + + err = snd_bebob_create_hwdep_device(bebob); + if (err < 0) + goto error; + + err = snd_card_register(bebob->card); + if (err < 0) + goto error; + + set_bit(card_index, devices_used); + mutex_unlock(&devices_mutex); + + /* + * After registered, bebob instance can be released corresponding to + * releasing the sound card instance. + */ + bebob->card->private_free = bebob_card_free; + bebob->card->private_data = bebob; + bebob->registered = true; + + return; +error: + mutex_unlock(&devices_mutex); + snd_bebob_stream_destroy_duplex(bebob); + kfree(bebob->maudio_special_quirk); + bebob->maudio_special_quirk = NULL; + snd_card_free(bebob->card); + dev_info(&bebob->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int +bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) +{ + struct snd_bebob *bebob; + const struct snd_bebob_spec *spec; + + if (entry->vendor_id == VEN_FOCUSRITE && + entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH) + spec = get_saffire_spec(unit); + else if (entry->vendor_id == VEN_MAUDIO1 && + entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH && + !check_audiophile_booted(unit)) + spec = NULL; + else + spec = (const struct snd_bebob_spec *)entry->driver_data; + + if (spec == NULL) { + if (entry->vendor_id == VEN_MAUDIO1 || + entry->vendor_id == VEN_MAUDIO2) + return snd_bebob_maudio_load_firmware(unit); + else + return -ENODEV; + } + + /* Allocate this independent of sound card instance. */ + bebob = kzalloc(sizeof(struct snd_bebob), GFP_KERNEL); + if (bebob == NULL) + return -ENOMEM; + + bebob->unit = fw_unit_get(unit); + bebob->entry = entry; + bebob->spec = spec; + dev_set_drvdata(&unit->device, bebob); + + mutex_init(&bebob->mutex); + spin_lock_init(&bebob->lock); + init_waitqueue_head(&bebob->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&bebob->dwork, do_registration); + + if (entry->vendor_id != VEN_MAUDIO1 || + (entry->model_id != MODEL_MAUDIO_FW1814 && + entry->model_id != MODEL_MAUDIO_PROJECTMIX)) { + snd_fw_schedule_registration(unit, &bebob->dwork); + } else { + /* + * This is a workaround. This bus reset seems to have an effect + * to make devices correctly handling transactions. Without + * this, the devices have gap_count mismatch. This causes much + * failure of transaction. + * + * Just after registration, user-land application receive + * signals from dbus and starts I/Os. To avoid I/Os till the + * future bus reset, registration is done in next update(). + */ + fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card, + false, true); + } + + return 0; +} + +/* + * This driver doesn't update streams in bus reset handler. + * + * DM1000/ DM1100/DM1500 chipsets with BeBoB firmware transfer packets with + * discontinued counter at bus reset. This discontinuity is immediately + * detected in packet streaming layer, then it sets XRUN to PCM substream. + * + * ALSA PCM applications can know the XRUN by getting -EPIPE from PCM operation. + * Then, they can recover the PCM substream by executing ioctl(2) with + * SNDRV_PCM_IOCTL_PREPARE. 'struct snd_pcm_ops.prepare' is called and drivers + * restart packet streaming. + * + * The above processing may be executed before this bus-reset handler is + * executed. When this handler updates streams with current isochronous + * channels, the streams already have the current ones. + */ +static void +bebob_update(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + + /* Postpone a workqueue for deferred registration. */ + if (!bebob->registered) + snd_fw_schedule_registration(unit, &bebob->dwork); + else + fcp_bus_reset(bebob->unit); +} + +static void bebob_remove(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&bebob->dwork); + + if (bebob->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(bebob->card); + } else { + /* Don't forget this case. */ + bebob_free(bebob); + } +} + +static const struct snd_bebob_rate_spec normal_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate +}; +static const struct snd_bebob_spec spec_normal = { + .clock = NULL, + .rate = &normal_rate_spec, + .meter = NULL +}; + +static const struct ieee1394_device_id bebob_id_table[] = { + /* Edirol, FA-66 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal), + /* Edirol, FA-101 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal), + /* Presonus, FIREBOX */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal), + /* PreSonus, FIREPOD/FP10 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal), + /* PreSonus, Inspire1394 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal), + /* BridgeCo, RDAudio1 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal), + /* BridgeCo, Audio5 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal), + /* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE2, 0x00010065, &spec_normal), + // Mackie, d.2 (optional Firewire card with DM1000). + SND_BEBOB_DEV_ENTRY(VEN_MACKIE1, 0x00010067, &spec_normal), + /* Stanton, ScratchAmp */ + SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal), + /* Tascam, IF-FW DM */ + SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal), + /* Behringer, XENIX UFX 1204 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal), + /* Behringer, XENIX UFX 1604 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal), + /* Behringer, Digital Mixer X32 series (X-UF Card) */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal), + /* Behringer, F-Control Audio 1616 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x001616, &spec_normal), + /* Behringer, F-Control Audio 610 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x000610, &spec_normal), + /* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */ + /* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal), + /* Apogee Electronics, Ensemble */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x01eeee, &spec_normal), + /* ESI, Quatafire610 */ + SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal), + /* AcousticReality, eARMasterOne */ + SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, &spec_normal), + /* CME, MatrixKFW */ + SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal), + /* Phonic, Helix Board 12 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal), + /* Phonic, Helix Board 18 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal), + /* Phonic, Helix Board 24 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal), + /* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal), + /* Lynx, Aurora 8/16 (LT-FW) */ + SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal), + /* ICON, FireXon */ + SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal), + /* PrismSound, Orpheus */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal), + /* PrismSound, ADA-8XR */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal), + /* TerraTec Electronic GmbH, PHASE 88 Rack FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec), + /* TerraTec Electronic GmbH, PHASE 24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &yamaha_terratec_spec), + /* TerraTec Electronic GmbH, Phase X24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &yamaha_terratec_spec), + /* TerraTec Electronic GmbH, EWS MIC2/MIC8 */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal), + /* Terratec Electronic GmbH, Aureon 7.1 Firewire */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal), + /* Yamaha, GO44 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_terratec_spec), + /* YAMAHA, GO46 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_terratec_spec), + /* Focusrite, SaffirePro 26 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec), + /* Focusrite, SaffirePro 10 I/O */ + { + // The combination of vendor_id and model_id is the same as the + // same as the one of Liquid Saffire 56. + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VEN_FOCUSRITE, + .model_id = 0x000006, + .specifier_id = 0x00a02d, + .version = 0x010001, + .driver_data = (kernel_ulong_t)&saffirepro_10_spec, + }, + /* Focusrite, Saffire(no label and LE) */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, + &saffire_spec), + /* M-Audio, Firewire 410 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, NULL), /* bootloader */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec), + /* M-Audio, Firewire Audiophile */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH, + &maudio_audiophile_spec), + /* M-Audio, Firewire Solo */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, &maudio_solo_spec), + /* M-Audio, Ozonic */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, &maudio_ozonic_spec), + /* M-Audio NRV10 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec), + /* M-Audio, ProFireLightbridge */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal), + /* Firewire 1814 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, NULL), /* bootloader */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814, + &maudio_special_spec), + /* M-Audio ProjectMix */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX, + &maudio_special_spec), + /* Digidesign Mbox 2 Pro */ + SND_BEBOB_DEV_ENTRY(VEN_DIGIDESIGN, 0x0000a9, &spec_normal), + // Toneweal FW66. + SND_BEBOB_DEV_ENTRY(OUI_SHOUYO, 0x020002, &spec_normal), + /* IDs are unknown but able to be supported */ + /* Apogee, Mini-ME Firewire */ + /* Apogee, Mini-DAC Firewire */ + /* Cakawalk, Sonar Power Studio 66 */ + /* CME, UF400e */ + /* ESI, Quotafire XL */ + /* Infrasonic, DewX */ + /* Infrasonic, Windy6 */ + /* Mackie, Digital X Bus x.200 */ + /* Mackie, Digital X Bus x.400 */ + /* Phonic, HB 12 */ + /* Phonic, HB 24 */ + /* Phonic, HB 18 */ + /* Phonic, FireFly 202 */ + /* Phonic, FireFly 302 */ + /* Rolf Spuler, Firewire Guitar */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, bebob_id_table); + +static struct fw_driver bebob_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-bebob", + .bus = &fw_bus_type, + }, + .probe = bebob_probe, + .update = bebob_update, + .remove = bebob_remove, + .id_table = bebob_id_table, +}; + +static int __init +snd_bebob_init(void) +{ + return driver_register(&bebob_driver.driver); +} + +static void __exit +snd_bebob_exit(void) +{ + driver_unregister(&bebob_driver.driver); +} + +module_init(snd_bebob_init); +module_exit(snd_bebob_exit); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h new file mode 100644 index 000000000..df1b1e94c --- /dev/null +++ b/sound/firewire/bebob/bebob.h @@ -0,0 +1,262 @@ +/* + * bebob.h - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_BEBOB_H_INCLUDED +#define SOUND_BEBOB_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lib.h" +#include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-am824.h" +#include "../cmp.h" + +/* basic register addresses on DM1000/DM1100/DM1500 */ +#define BEBOB_ADDR_REG_INFO 0xffffc8020000ULL +#define BEBOB_ADDR_REG_REQ 0xffffc8021000ULL + +struct snd_bebob; + +#define SND_BEBOB_STRM_FMT_ENTRIES 7 +struct snd_bebob_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +/* this is a lookup table for index of stream formations */ +extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES]; + +/* device specific operations */ +enum snd_bebob_clock_type { + SND_BEBOB_CLOCK_TYPE_INTERNAL = 0, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, + SND_BEBOB_CLOCK_TYPE_SYT, +}; +struct snd_bebob_clock_spec { + unsigned int num; + const char *const *labels; + const enum snd_bebob_clock_type *types; + int (*get)(struct snd_bebob *bebob, unsigned int *id); +}; +struct snd_bebob_rate_spec { + int (*get)(struct snd_bebob *bebob, unsigned int *rate); + int (*set)(struct snd_bebob *bebob, unsigned int rate); +}; +struct snd_bebob_meter_spec { + unsigned int num; + const char *const *labels; + int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size); +}; +struct snd_bebob_spec { + const struct snd_bebob_clock_spec *clock; + const struct snd_bebob_rate_spec *rate; + const struct snd_bebob_meter_spec *meter; +}; + +struct snd_bebob { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + const struct ieee1394_device_id *entry; + const struct snd_bebob_spec *spec; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + bool connected; + + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + unsigned int substreams_counter; + + struct snd_bebob_stream_formation + tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob_stream_formation + rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + + int sync_input_plug; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* for M-Audio special devices */ + void *maudio_special_quirk; + + /* For BeBoB version quirk. */ + unsigned int version; +}; + +static inline int +snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size) +{ + return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + buf, size, 0); +} + +static inline int +snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf) +{ + return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + (void *)buf, sizeof(u32), 0); +} + +/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 1394TA) */ +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num); +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num); + +/* + * AVC command extensions, AV/C Unit and Subunit, Revision 17 + * (Nov 2003, BridgeCo) + */ +#define AVC_BRIDGECO_ADDR_BYTES 6 +enum avc_bridgeco_plug_dir { + AVC_BRIDGECO_PLUG_DIR_IN = 0x00, + AVC_BRIDGECO_PLUG_DIR_OUT = 0x01 +}; +enum avc_bridgeco_plug_mode { + AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00, + AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01, + AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02 +}; +enum avc_bridgeco_plug_unit { + AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00, + AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01, + AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02 +}; +enum avc_bridgeco_plug_type { + AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00, + AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01, + AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02, + AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03, + AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04, + AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05, + AVC_BRIDGECO_PLUG_TYPE_ADDITION = 0x06 +}; +static inline void +avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + enum avc_bridgeco_plug_unit unit, + unsigned int pid) +{ + buf[0] = 0xff; /* Unit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT; + buf[3] = unit; + buf[4] = 0xff & pid; + buf[5] = 0xff; /* reserved */ +} +static inline void +avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + unsigned int pid) +{ + buf[0] = 0x60; /* Music subunit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT; + buf[3] = 0xff & pid; + buf[4] = 0xff; /* reserved */ + buf[5] = 0xff; /* reserved */ +} +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len); +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type); +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type); +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 input[7]); +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid); + +/* for AMDTP streaming */ +int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate); +int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate); +int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob, + enum snd_bebob_clock_type *src); +int snd_bebob_stream_discover(struct snd_bebob *bebob); +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob); +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate); +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob); + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob); +int snd_bebob_stream_lock_try(struct snd_bebob *bebob); +void snd_bebob_stream_lock_release(struct snd_bebob *bebob); + +void snd_bebob_proc_init(struct snd_bebob *bebob); + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob); + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob); + +int snd_bebob_create_hwdep_device(struct snd_bebob *bebob); + +/* model specific operations */ +extern const struct snd_bebob_spec phase88_rack_spec; +extern const struct snd_bebob_spec yamaha_terratec_spec; +extern const struct snd_bebob_spec saffirepro_26_spec; +extern const struct snd_bebob_spec saffirepro_10_spec; +extern const struct snd_bebob_spec saffire_le_spec; +extern const struct snd_bebob_spec saffire_spec; +extern const struct snd_bebob_spec maudio_fw410_spec; +extern const struct snd_bebob_spec maudio_audiophile_spec; +extern const struct snd_bebob_spec maudio_solo_spec; +extern const struct snd_bebob_spec maudio_ozonic_spec; +extern const struct snd_bebob_spec maudio_nrv10_spec; +extern const struct snd_bebob_spec maudio_special_spec; +int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814); +int snd_bebob_maudio_load_firmware(struct fw_unit *unit); + +#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor, \ + .model_id = model, \ + .driver_data = (kernel_ulong_t)data \ +} + +#endif diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c new file mode 100644 index 000000000..f9b4225dd --- /dev/null +++ b/sound/firewire/bebob/bebob_command.c @@ -0,0 +1,296 @@ +/* + * bebob_command.c - driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff & num; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err < 0) + ; + else if (err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else + err = 0; + + kfree(buf); + return err; +} + +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(8)); + if (err < 0) + ; + else if (err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *num = buf[7]; + err = 0; +end: + kfree(buf); + return err; +} + +static inline void +avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr) +{ + buf[1] = addr[0]; + memcpy(buf + 4, addr + 1, 5); +} + +static inline void +avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr, + unsigned int itype) +{ + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */ + buf[3] = 0xc0; /* BridgeCo extension */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[9] = itype; /* info type */ +} + +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug type'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00); + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + ; + else if (err < 11) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[10]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len) +{ + int err; + + /* Info type is 'channel position'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03); + + err = fcp_avc_transaction(unit, buf, 12, buf, 256, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + ; + else if (err < 11) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* Pick up specific data. */ + memmove(buf, buf + 10, err - 10); + err = 0; +end: + return err; +} + +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type) +{ + u8 *buf; + int err; + + /* section info includes charactors but this module don't need it */ + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'section info'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07); + buf[10] = 0xff & ++id; /* section id */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9) | BIT(10)); + if (err < 0) + ; + else if (err < 12) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[11]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7]) +{ + int err; + u8 *buf; + + buf = kzalloc(18, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug input'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05); + + err = fcp_avc_transaction(unit, buf, 16, buf, 16, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) + ; + else if (err < 16) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + memcpy(input, buf + 10, 5); + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid) +{ + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */ + buf[3] = 0xc1; /* Bridgeco extension - List Request */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[10] = 0xff & eid; /* Entry ID */ + + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(10)); + if (err < 0) + ; + else if (err < 12) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + else if (buf[10] != eid) + err = -EIO; + if (err < 0) + goto end; + + /* Pick up 'stream format info'. */ + memmove(buf, buf + 11, err - 11); + *len = err - 11; + err = 0; +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c new file mode 100644 index 000000000..62d989edd --- /dev/null +++ b/sound/firewire/bebob/bebob_focusrite.c @@ -0,0 +1,323 @@ +/* + * bebob_focusrite.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define ANA_IN "Analog In" +#define DIG_IN "Digital In" +#define ANA_OUT "Analog Out" +#define DIG_OUT "Digital Out" +#define STM_IN "Stream In" + +#define SAFFIRE_ADDRESS_BASE 0x000100000000ULL + +#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8 +#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174 + +/* whether sync to external device or not */ +#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c +#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432 +#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164 + +#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIRE_CLOCK_SOURCE_SPDIF 1 + +/* clock sources as returned from register of Saffire Pro 10 and 26 */ +#define SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK 0x000000ff +#define SAFFIREPRO_CLOCK_SOURCE_DETECT_MASK 0x0000ff00 +#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIREPRO_CLOCK_SOURCE_SKIP 1 /* never used on hardware */ +#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 /* not used on s.pro. 10 */ +#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 /* not used on s.pro. 10 */ +#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5 +#define SAFFIREPRO_CLOCK_SOURCE_COUNT 6 + +/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */ +#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4 + +/* saffirepro has its own parameter for sampling frequency */ +#define SAFFIREPRO_RATE_NOREBOOT 0x01cc +/* index is the value for this register */ +static const unsigned int rates[] = { + [0] = 0, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000 +}; + +/* saffire(no label)/saffire LE has metering */ +#define SAFFIRE_OFFSET_METER 0x0100 +#define SAFFIRE_LE_OFFSET_METER 0x0168 + +static inline int +saffire_read_block(struct snd_bebob *bebob, u64 offset, + u32 *buf, unsigned int size) +{ + unsigned int i; + int err; + __be32 *tmp = (__be32 *)buf; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + tmp, size, 0); + if (err < 0) + goto end; + + for (i = 0; i < size / sizeof(u32); i++) + buf[i] = be32_to_cpu(tmp[i]); +end: + return err; +} + +static inline int +saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value) +{ + int err; + __be32 tmp; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &tmp, sizeof(__be32), 0); + if (err < 0) + goto end; + + *value = be32_to_cpu(tmp); +end: + return err; +} + +static inline int +saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value) +{ + __be32 data = cpu_to_be32(value); + + return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &data, sizeof(__be32), 0); +} + +static const enum snd_bebob_clock_type saffirepro_10_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ +}; +static const enum snd_bebob_clock_type saffirepro_26_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT1 */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT2 */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ +}; +/* Value maps between registers and labels for SaffirePro 10/26. */ +static const signed char saffirepro_clk_maps[][SAFFIREPRO_CLOCK_SOURCE_COUNT] = { + /* SaffirePro 10 */ + [0] = { + [SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0, + [SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1, + [SAFFIREPRO_CLOCK_SOURCE_ADAT1] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_ADAT2] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 2, + }, + /* SaffirePro 26 */ + [1] = { + [SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0, + [SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1, + [SAFFIREPRO_CLOCK_SOURCE_ADAT1] = 2, + [SAFFIREPRO_CLOCK_SOURCE_ADAT2] = 3, + [SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 4, + } +}; + +static int +saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate) +{ + u32 id; + int err; + + err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id); + if (err < 0) + goto end; + if (id >= ARRAY_SIZE(rates)) + err = -EIO; + else + *rate = rates[id]; +end: + return err; +} +static int +saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate) +{ + u32 id; + + for (id = 0; id < ARRAY_SIZE(rates); id++) { + if (rates[id] == rate) + break; + } + if (id == ARRAY_SIZE(rates)) + return -EINVAL; + + return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id); +} + +/* + * query hardware for current clock source, return our internally + * used clock index in *id, depending on hardware. + */ +static int +saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; /* clock source read from hw register */ + const signed char *map; + + err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value); + if (err < 0) + goto end; + + /* depending on hardware, use a different mapping */ + if (bebob->spec->clock->types == saffirepro_10_clk_src_types) + map = saffirepro_clk_maps[0]; + else + map = saffirepro_clk_maps[1]; + + /* In a case that this driver cannot handle the value of register. */ + value &= SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK; + if (value >= SAFFIREPRO_CLOCK_SOURCE_COUNT || map[value] < 0) { + err = -EIO; + goto end; + } + + *id = (unsigned int)map[value]; +end: + return err; +} + +const struct snd_bebob_spec saffire_le_spec; +static const enum snd_bebob_clock_type saffire_both_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, +}; +static int +saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value); + if (err >= 0) + *id = 0xff & value; + + return err; +}; +static const char *const saffire_le_meter_labels[] = { + ANA_IN, ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + STM_IN, STM_IN +}; +static const char *const saffire_meter_labels[] = { + ANA_IN, ANA_IN, + STM_IN, STM_IN, STM_IN, STM_IN, STM_IN, +}; +static int +saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + const struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int channels; + u64 offset; + int err; + + if (spec->labels == saffire_le_meter_labels) + offset = SAFFIRE_LE_OFFSET_METER; + else + offset = SAFFIRE_OFFSET_METER; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EIO; + + err = saffire_read_block(bebob, offset, buf, size); + if (err >= 0 && spec->labels == saffire_le_meter_labels) { + swap(buf[1], buf[3]); + swap(buf[2], buf[3]); + swap(buf[3], buf[4]); + + swap(buf[7], buf[10]); + swap(buf[8], buf[10]); + swap(buf[9], buf[11]); + swap(buf[11], buf[12]); + + swap(buf[15], buf[16]); + } + + return err; +} + +static const struct snd_bebob_rate_spec saffirepro_both_rate_spec = { + .get = &saffirepro_both_clk_freq_get, + .set = &saffirepro_both_clk_freq_set, +}; +/* Saffire Pro 26 I/O */ +static const struct snd_bebob_clock_spec saffirepro_26_clk_spec = { + .num = ARRAY_SIZE(saffirepro_26_clk_src_types), + .types = saffirepro_26_clk_src_types, + .get = &saffirepro_both_clk_src_get, +}; +const struct snd_bebob_spec saffirepro_26_spec = { + .clock = &saffirepro_26_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; +/* Saffire Pro 10 I/O */ +static const struct snd_bebob_clock_spec saffirepro_10_clk_spec = { + .num = ARRAY_SIZE(saffirepro_10_clk_src_types), + .types = saffirepro_10_clk_src_types, + .get = &saffirepro_both_clk_src_get, +}; +const struct snd_bebob_spec saffirepro_10_spec = { + .clock = &saffirepro_10_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; + +static const struct snd_bebob_rate_spec saffire_both_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static const struct snd_bebob_clock_spec saffire_both_clk_spec = { + .num = ARRAY_SIZE(saffire_both_clk_src_types), + .types = saffire_both_clk_src_types, + .get = &saffire_both_clk_src_get, +}; +/* Saffire LE */ +static const struct snd_bebob_meter_spec saffire_le_meter_spec = { + .num = ARRAY_SIZE(saffire_le_meter_labels), + .labels = saffire_le_meter_labels, + .get = &saffire_meter_get, +}; +const struct snd_bebob_spec saffire_le_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_le_meter_spec +}; +/* Saffire */ +static const struct snd_bebob_meter_spec saffire_meter_spec = { + .num = ARRAY_SIZE(saffire_meter_labels), + .labels = saffire_meter_labels, + .get = &saffire_meter_get, +}; +const struct snd_bebob_spec saffire_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_meter_spec +}; diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c new file mode 100644 index 000000000..a04b58809 --- /dev/null +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -0,0 +1,197 @@ +/* + * bebob_hwdep.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node infomation + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "bebob.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_bebob *bebob = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&bebob->lock); + + while (!bebob->dev_lock_changed) { + prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&bebob->lock); + schedule(); + finish_wait(&bebob->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&bebob->lock); + } + + memset(&event, 0, sizeof(event)); + count = min_t(long, count, sizeof(event.lock_status)); + if (bebob->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (bebob->dev_lock_count > 0); + bebob->dev_lock_changed = false; + } + + spin_unlock_irq(&bebob->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_bebob *bebob = hwdep->private_data; + __poll_t events; + + poll_wait(file, &bebob->hwdep_wait, wait); + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&bebob->lock); + + return events; +} + +static int +hwdep_get_info(struct snd_bebob *bebob, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(bebob->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_BEBOB; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int +hwdep_lock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == 0) { + bebob->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == -1) { + bebob->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_bebob *bebob = hwdep->private_data; + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_count == -1) + bebob->dev_lock_count = 0; + spin_unlock_irq(&bebob->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_bebob *bebob = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(bebob, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(bebob); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(bebob); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_bebob_create_hwdep_device(struct snd_bebob *bebob) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "BeBoB"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB; + hwdep->ops = ops; + hwdep->private_data = bebob; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c new file mode 100644 index 000000000..c266997ad --- /dev/null +++ b/sound/firewire/bebob/bebob_maudio.c @@ -0,0 +1,797 @@ +/* + * bebob_maudio.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" +#include + +/* + * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to + * download firmware blob. To enable these devices, drivers should upload + * firmware blob and send a command to initialize configuration to factory + * settings when completing uploading. Then these devices generate bus reset + * and are recognized as new devices with the firmware. + * + * But with firmware version 5058 or later, the firmware is stored to flash + * memory in the device and drivers can tell bootloader to load the firmware + * by sending a cue. This cue must be sent one time. + * + * For streaming, both of output and input streams are needed for Firewire 410 + * and Ozonic. The single stream is OK for the other devices even if the clock + * source is not SYT-Match (I note no devices use SYT-Match). + * + * Without streaming, the devices except for Firewire Audiophile can mix any + * input and output. For this reason, Audiophile cannot be used as standalone + * mixer. + * + * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed + * when receiving any commands which the firmware can't understand. These + * devices utilize completely different system to control. It is some + * write-transaction directly into a certain address. All of addresses for mixer + * functionality is between 0xffc700700000 to 0xffc70070009c. + */ + +/* Offset from information register */ +#define INFO_OFFSET_SW_DATE 0x20 + +/* Bootloader Protocol Version 1 */ +#define MAUDIO_BOOTLOADER_CUE1 0x00000001 +/* + * Initializing configuration to factory settings (= 0x1101), (swapped in line), + * Command code is zero (= 0x00), + * the number of operands is zero (= 0x00)(at least significant byte) + */ +#define MAUDIO_BOOTLOADER_CUE2 0x01110000 +/* padding */ +#define MAUDIO_BOOTLOADER_CUE3 0x00000000 + +#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL + +#define METER_OFFSET 0x00600000 + +/* some device has sync info after metering data */ +#define METER_SIZE_SPECIAL 84 /* with sync info */ +#define METER_SIZE_FW410 76 /* with sync info */ +#define METER_SIZE_AUDIOPHILE 60 /* with sync info */ +#define METER_SIZE_SOLO 52 /* with sync info */ +#define METER_SIZE_OZONIC 48 +#define METER_SIZE_NRV10 80 + +/* labels for metering */ +#define ANA_IN "Analog In" +#define ANA_OUT "Analog Out" +#define DIG_IN "Digital In" +#define SPDIF_IN "S/PDIF In" +#define ADAT_IN "ADAT In" +#define DIG_OUT "Digital Out" +#define SPDIF_OUT "S/PDIF Out" +#define ADAT_OUT "ADAT Out" +#define STRM_IN "Stream In" +#define AUX_OUT "Aux Out" +#define HP_OUT "HP Out" +/* for NRV */ +#define UNKNOWN_METER "Unknown" + +struct special_params { + bool is1814; + unsigned int clk_src; + unsigned int dig_in_fmt; + unsigned int dig_out_fmt; + unsigned int clk_lock; + struct snd_ctl_elem_id *ctl_id_sync; +}; + +/* + * For some M-Audio devices, this module just send cue to load firmware. After + * loading, the device generates bus reset and newly detected. + * + * If we make any transactions to load firmware, the operation may failed. + */ +int snd_bebob_maudio_load_firmware(struct fw_unit *unit) +{ + struct fw_device *device = fw_parent_device(unit); + int err, rcode; + u64 date; + __le32 *cues; + + /* check date of software used to build */ + err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE, + &date, sizeof(u64)); + if (err < 0) + return err; + /* + * firmware version 5058 or later has date later than "20070401", but + * 'date' is not null-terminated. + */ + if (date < 0x3230303730343031LL) { + dev_err(&unit->device, + "Use firmware version 5058 or later\n"); + return -ENXIO; + } + + cues = kmalloc_array(3, sizeof(*cues), GFP_KERNEL); + if (!cues) + return -ENOMEM; + + cues[0] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE1); + cues[1] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE2); + cues[2] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE3); + + rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST, + device->node_id, device->generation, + device->max_speed, BEBOB_ADDR_REG_REQ, + cues, 3 * sizeof(*cues)); + kfree(cues); + if (rcode != RCODE_COMPLETE) { + dev_err(&unit->device, + "Failed to send a cue to load firmware\n"); + err = -EIO; + } + + return err; +} + +static inline int +get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) +{ + return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET, + buf, size, 0); +} + +static int +check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync) +{ + int err; + u8 *buf; + + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, buf, size); + if (err < 0) + goto end; + + /* if synced, this value is the same as SFC of FDF in CIP header */ + *sync = (buf[size - 2] != 0xff); +end: + kfree(buf); + return err; +} + +/* + * dig_fmt: 0x00:S/PDIF, 0x01:ADAT + * clk_lock: 0x00:unlock, 0x01:lock + */ +static int +avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src, + unsigned int dig_in_fmt, unsigned int dig_out_fmt, + unsigned int clk_lock) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + u8 *buf; + + if (amdtp_stream_running(&bebob->rx_stream) || + amdtp_stream_running(&bebob->tx_stream)) + return -EBUSY; + + buf = kmalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x00; /* vendor dependent */ + buf[3] = 0x04; /* company ID high */ + buf[4] = 0x00; /* company ID middle */ + buf[5] = 0x04; /* company ID low */ + buf[6] = 0xff & clk_src; /* clock source */ + buf[7] = 0xff & dig_in_fmt; /* input digital format */ + buf[8] = 0xff & dig_out_fmt; /* output digital format */ + buf[9] = 0xff & clk_lock; /* lock these settings */ + buf[10] = 0x00; /* padding */ + buf[11] = 0x00; /* padding */ + + err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(8) | + BIT(9)); + if ((err > 0) && (err < 10)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + params->clk_src = buf[6]; + params->dig_in_fmt = buf[7]; + params->dig_out_fmt = buf[8]; + params->clk_lock = buf[9]; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); + + err = 0; +end: + kfree(buf); + return err; +} +static void +special_stream_formation_set(struct snd_bebob *bebob) +{ + static const unsigned int ch_table[2][2][3] = { + /* AMDTP_OUT_STREAM */ + { { 6, 6, 4 }, /* SPDIF */ + { 12, 8, 4 } }, /* ADAT */ + /* AMDTP_IN_STREAM */ + { { 10, 10, 2 }, /* SPDIF */ + { 16, 12, 2 } } /* ADAT */ + }; + struct special_params *params = bebob->maudio_special_quirk; + unsigned int i, max; + + max = SND_BEBOB_STRM_FMT_ENTRIES - 1; + if (!params->is1814) + max -= 2; + + for (i = 0; i < max; i++) { + bebob->tx_stream_formations[i + 1].pcm = + ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2]; + bebob->tx_stream_formations[i + 1].midi = 1; + + bebob->rx_stream_formations[i + 1].pcm = + ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2]; + bebob->rx_stream_formations[i + 1].midi = 1; + } +} + +static int add_special_controls(struct snd_bebob *bebob); +int +snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814) +{ + struct special_params *params; + int err; + + params = kzalloc(sizeof(struct special_params), GFP_KERNEL); + if (params == NULL) + return -ENOMEM; + + mutex_lock(&bebob->mutex); + + bebob->maudio_special_quirk = (void *)params; + params->is1814 = is1814; + + /* initialize these parameters because driver is not allowed to ask */ + bebob->rx_stream.context = ERR_PTR(-1); + bebob->tx_stream.context = ERR_PTR(-1); + err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to initialize clock params: %d\n", err); + goto end; + } + + err = add_special_controls(bebob); + if (err < 0) + goto end; + + special_stream_formation_set(bebob); + + if (params->is1814) { + bebob->midi_input_ports = 1; + bebob->midi_output_ports = 1; + } else { + bebob->midi_input_ports = 2; + bebob->midi_output_ports = 2; + } +end: + mutex_unlock(&bebob->mutex); + return err; +} + +/* Input plug shows actual rate. Output plug is needless for this purpose. */ +static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate) +{ + int err, trials; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + + return err; +} +static int special_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* + * Just after changing sampling rate for output, a followed command + * for input is easy to fail. This is a workaround fot this issue. + */ + msleep(100); + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); +end: + return err; +} + +/* Clock source control for special firmware */ +static const enum snd_bebob_clock_type special_clk_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, /* With digital mute */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* SPDIF/ADAT */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ + SND_BEBOB_CLOCK_TYPE_INTERNAL, +}; +static int special_clk_get(struct snd_bebob *bebob, unsigned int *id) +{ + struct special_params *params = bebob->maudio_special_quirk; + *id = params->clk_src; + return 0; +} +static int special_clk_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + static const char *const special_clk_labels[] = { + "Internal with Digital Mute", + "Digital", + "Word Clock", + "Internal" + }; + return snd_ctl_enum_info(einf, 1, ARRAY_SIZE(special_clk_types), + special_clk_labels); +} +static int special_clk_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + uval->value.enumerated.item[0] = params->clk_src; + return 0; +} +static int special_clk_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + int err, id; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_clk_types)) + return -EINVAL; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, id, + params->dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + mutex_unlock(&bebob->mutex); + + if (err >= 0) + err = 1; + + return err; +} +static const struct snd_kcontrol_new special_clk_ctl = { + .name = "Clock Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_clk_ctl_info, + .get = special_clk_ctl_get, + .put = special_clk_ctl_put +}; + +/* Clock synchronization control for special firmware */ +static int special_sync_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + einf->count = 1; + einf->value.integer.min = 0; + einf->value.integer.max = 1; + + return 0; +} +static int special_sync_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + int err; + bool synced = 0; + + err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced); + if (err >= 0) + uval->value.integer.value[0] = synced; + + return 0; +} +static const struct snd_kcontrol_new special_sync_ctl = { + .name = "Sync Status", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = special_sync_ctl_info, + .get = special_sync_ctl_get, +}; + +/* Digital input interface control for special firmware */ +static const char *const special_dig_in_iface_labels[] = { + "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical" +}; +static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + return snd_ctl_enum_info(einf, 1, + ARRAY_SIZE(special_dig_in_iface_labels), + special_dig_in_iface_labels); +} +static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int dig_in_iface; + int err, val; + + mutex_lock(&bebob->mutex); + + err = avc_audio_get_selector(bebob->unit, 0x00, 0x04, + &dig_in_iface); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get digital input interface: %d\n", err); + goto end; + } + + /* encoded id for user value */ + val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01); + + /* for ADAT Optical */ + if (val > 2) + val = 2; + + uval->value.enumerated.item[0] = val; +end: + mutex_unlock(&bebob->mutex); + return err; +} +static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id, dig_in_fmt, dig_in_iface; + int err; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_dig_in_iface_labels)) + return -EINVAL; + + /* decode user value */ + dig_in_fmt = (id >> 1) & 0x01; + dig_in_iface = id & 0x01; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + if (err < 0) + goto end; + + /* For ADAT, optical interface is only available. */ + if (params->dig_in_fmt > 0) { + err = 1; + goto end; + } + + /* For S/PDIF, optical/coaxial interfaces are selectable. */ + err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface); + if (err < 0) + dev_err(&bebob->unit->device, + "fail to set digital input interface: %d\n", err); + err = 1; +end: + special_stream_formation_set(bebob); + mutex_unlock(&bebob->mutex); + return err; +} +static const struct snd_kcontrol_new special_dig_in_iface_ctl = { + .name = "Digital Input Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_in_iface_ctl_info, + .get = special_dig_in_iface_ctl_get, + .put = special_dig_in_iface_ctl_set +}; + +/* Digital output interface control for special firmware */ +static const char *const special_dig_out_iface_labels[] = { + "S/PDIF Optical and Coaxial", "ADAT Optical" +}; +static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + return snd_ctl_enum_info(einf, 1, + ARRAY_SIZE(special_dig_out_iface_labels), + special_dig_out_iface_labels); +} +static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + mutex_lock(&bebob->mutex); + uval->value.enumerated.item[0] = params->dig_out_fmt; + mutex_unlock(&bebob->mutex); + return 0; +} +static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id; + int err; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_dig_out_iface_labels)) + return -EINVAL; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + params->dig_in_fmt, + id, params->clk_lock); + if (err >= 0) { + special_stream_formation_set(bebob); + err = 1; + } + + mutex_unlock(&bebob->mutex); + return err; +} +static const struct snd_kcontrol_new special_dig_out_iface_ctl = { + .name = "Digital Output Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_out_iface_ctl_info, + .get = special_dig_out_iface_ctl_get, + .put = special_dig_out_iface_ctl_set +}; + +static int add_special_controls(struct snd_bebob *bebob) +{ + struct snd_kcontrol *kctl; + struct special_params *params = bebob->maudio_special_quirk; + int err; + + kctl = snd_ctl_new1(&special_clk_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_sync_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + params->ctl_id_sync = &kctl->id; + + kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); +end: + return err; +} + +/* Hardware metering for special firmware */ +static const char *const special_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + SPDIF_IN, + ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN, + ANA_OUT, ANA_OUT, + SPDIF_OUT, + ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT, + HP_OUT, HP_OUT, + AUX_OUT +}; +static int +special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size) +{ + __be16 *buf; + unsigned int i, c, channels; + int err; + + channels = ARRAY_SIZE(special_meter_labels) * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + /* omit last 4 bytes because it's clock info. */ + buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4); + if (err < 0) + goto end; + + /* Its format is u16 and some channels are unknown. */ + i = 0; + for (c = 2; c < channels + 2; c++) + target[i++] = be16_to_cpu(buf[c]) << 16; +end: + kfree(buf); + return err; +} + +/* last 4 bytes are omitted because it's clock info. */ +static const char *const fw410_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT +}; +static const char *const audiophile_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT, AUX_OUT, +}; +static const char *const solo_meter_labels[] = { + ANA_IN, DIG_IN, + STRM_IN, STRM_IN, + ANA_OUT, DIG_OUT +}; + +/* no clock info */ +static const char *const ozonic_meter_labels[] = { + ANA_IN, ANA_IN, + STRM_IN, STRM_IN, + ANA_OUT, ANA_OUT +}; +/* TODO: need testers. these positions are based on authour's assumption */ +static const char *const nrv10_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + DIG_IN +}; +static int +normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + const struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int c, channels; + int err; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + err = get_meter(bebob, (void *)buf, size); + if (err < 0) + goto end; + + for (c = 0; c < channels; c++) + be32_to_cpus(&buf[c]); + + /* swap stream channels because inverted */ + if (spec->labels == solo_meter_labels) { + swap(buf[4], buf[6]); + swap(buf[5], buf[7]); + } +end: + return err; +} + +/* for special customized devices */ +static const struct snd_bebob_rate_spec special_rate_spec = { + .get = &special_get_rate, + .set = &special_set_rate, +}; +static const struct snd_bebob_clock_spec special_clk_spec = { + .num = ARRAY_SIZE(special_clk_types), + .types = special_clk_types, + .get = &special_clk_get, +}; +static const struct snd_bebob_meter_spec special_meter_spec = { + .num = ARRAY_SIZE(special_meter_labels), + .labels = special_meter_labels, + .get = &special_meter_get +}; +const struct snd_bebob_spec maudio_special_spec = { + .clock = &special_clk_spec, + .rate = &special_rate_spec, + .meter = &special_meter_spec +}; + +/* Firewire 410 specification */ +static const struct snd_bebob_rate_spec usual_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static const struct snd_bebob_meter_spec fw410_meter_spec = { + .num = ARRAY_SIZE(fw410_meter_labels), + .labels = fw410_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_fw410_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &fw410_meter_spec +}; + +/* Firewire Audiophile specification */ +static const struct snd_bebob_meter_spec audiophile_meter_spec = { + .num = ARRAY_SIZE(audiophile_meter_labels), + .labels = audiophile_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_audiophile_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &audiophile_meter_spec +}; + +/* Firewire Solo specification */ +static const struct snd_bebob_meter_spec solo_meter_spec = { + .num = ARRAY_SIZE(solo_meter_labels), + .labels = solo_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_solo_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &solo_meter_spec +}; + +/* Ozonic specification */ +static const struct snd_bebob_meter_spec ozonic_meter_spec = { + .num = ARRAY_SIZE(ozonic_meter_labels), + .labels = ozonic_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_ozonic_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &ozonic_meter_spec +}; + +/* NRV10 specification */ +static const struct snd_bebob_meter_spec nrv10_meter_spec = { + .num = ARRAY_SIZE(nrv10_meter_labels), + .labels = nrv10_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_nrv10_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &nrv10_meter_spec +}; diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c new file mode 100644 index 000000000..3befa3eca --- /dev/null +++ b/sound/firewire/bebob/bebob_midi.c @@ -0,0 +1,174 @@ +/* + * bebob_midi.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "bebob.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + + mutex_lock(&bebob->mutex); + bebob->substreams_counter++; + err = snd_bebob_stream_start_duplex(bebob, 0); + mutex_unlock(&bebob->mutex); + if (err < 0) + snd_bebob_stream_lock_release(bebob); +end: + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + + mutex_lock(&bebob->mutex); + bebob->substreams_counter++; + err = snd_bebob_stream_start_duplex(bebob, 0); + mutex_unlock(&bebob->mutex); + if (err < 0) + snd_bebob_stream_lock_release(bebob); +end: + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + mutex_lock(&bebob->mutex); + bebob->substreams_counter--; + snd_bebob_stream_stop_duplex(bebob); + mutex_unlock(&bebob->mutex); + + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + mutex_lock(&bebob->mutex); + bebob->substreams_counter--; + snd_bebob_stream_stop_duplex(bebob); + mutex_unlock(&bebob->mutex); + + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&bebob->tx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&bebob->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&bebob->rx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&bebob->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static void set_midi_substream_names(struct snd_bebob *bebob, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + bebob->card->shortname, subs->number + 1); + } +} + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0, + bebob->midi_output_ports, bebob->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", bebob->card->shortname); + rmidi->private_data = bebob; + + if (bebob->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(bebob, str); + } + + if (bebob->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(bebob, str); + } + + if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c new file mode 100644 index 000000000..ea9b86450 --- /dev/null +++ b/sound/firewire/bebob/bebob_pcm.c @@ -0,0 +1,391 @@ +/* + * bebob_pcm.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(c, formations[i].pcm)) + continue; + + t.min = min(t.min, snd_bebob_rate_table[i]); + t.max = max(t.max, snd_bebob_rate_table[i]); + + } + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, snd_bebob_rate_table[i])) + continue; + + t.min = min(t.min, formations[i].pcm); + t.max = max(t.max, formations[i].pcm); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels_and_rates(struct snd_pcm_hardware *hw, + struct snd_bebob_stream_formation *formations) +{ + unsigned int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->channels_min = min(hw->channels_min, formations[i].pcm); + hw->channels_max = max(hw->channels_max, formations[i].pcm); + + hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]); + hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]); + } +} + +static int +pcm_init_hw_params(struct snd_bebob *bebob, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + struct snd_bebob_stream_formation *formations; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; + s = &bebob->tx_stream; + formations = bebob->tx_stream_formations; + } else { + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; + s = &bebob->rx_stream; + formations = bebob->rx_stream_formations; + } + + limit_channels_and_rates(&runtime->hw, formations); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formations, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formations, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_am824_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int +pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + const struct snd_bebob_rate_spec *spec = bebob->spec->rate; + unsigned int sampling_rate; + enum snd_bebob_clock_type src; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + + err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto err_locked; + + err = snd_bebob_stream_get_clock_src(bebob, &src); + if (err < 0) + goto err_locked; + + /* + * When source of clock is internal or any PCM stream are running, + * the available sampling rate is limited at current sampling rate. + */ + if (src == SND_BEBOB_CLOCK_TYPE_EXTERNAL || + amdtp_stream_pcm_running(&bebob->tx_stream) || + amdtp_stream_pcm_running(&bebob->rx_stream)) { + err = spec->get(bebob, &sampling_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto err_locked; + } + + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_bebob_stream_lock_release(bebob); + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static int +pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&bebob->mutex); + bebob->substreams_counter++; + mutex_unlock(&bebob->mutex); + } + + return 0; +} +static int +pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&bebob->mutex); + bebob->substreams_counter++; + mutex_unlock(&bebob->mutex); + } + + return 0; +} + +static int +pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) { + mutex_lock(&bebob->mutex); + bebob->substreams_counter--; + mutex_unlock(&bebob->mutex); + } + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int +pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) { + mutex_lock(&bebob->mutex); + bebob->substreams_counter--; + mutex_unlock(&bebob->mutex); + } + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int +pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->tx_stream); + + return err; +} +static int +pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->rx_stream); + + return err; +} + +static int +pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int +pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->tx_stream); +} +static snd_pcm_uframes_t +pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + return amdtp_stream_pcm_ack(&bebob->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + return amdtp_stream_pcm_ack(&bebob->rx_stream); +} + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = bebob; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", bebob->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c new file mode 100644 index 000000000..8096891af --- /dev/null +++ b/sound/firewire/bebob/bebob_proc.c @@ -0,0 +1,198 @@ +/* + * bebob_proc.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +/* contents of information register */ +struct hw_info { + u64 manufacturer; + u32 protocol_ver; + u32 bld_ver; + u32 guid[2]; + u32 model_id; + u32 model_rev; + u64 fw_date; + u64 fw_time; + u32 fw_id; + u32 fw_ver; + u32 base_addr; + u32 max_size; + u64 bld_date; + u64 bld_time; +/* may not used in product + u64 dbg_date; + u64 dbg_time; + u32 dbg_id; + u32 dbg_version; +*/ +} __packed; + +static void +proc_read_hw_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct hw_info *info; + + info = kzalloc(sizeof(struct hw_info), GFP_KERNEL); + if (info == NULL) + return; + + if (snd_bebob_read_block(bebob->unit, 0, + info, sizeof(struct hw_info)) < 0) + goto end; + + snd_iprintf(buffer, "Manufacturer:\t%.8s\n", + (char *)&info->manufacturer); + snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver); + snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver); + snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n", + info->guid[0], info->guid[1]); + snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id); + snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev); + snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date); + snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time); + snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id); + snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver); + snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr); + snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size); + snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date); + snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time); + +end: + kfree(info); +} + +static void +proc_read_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + const struct snd_bebob_meter_spec *spec = bebob->spec->meter; + u32 *buf; + unsigned int i, c, channels, size; + + if (spec == NULL) + return; + + channels = spec->num * 2; + size = channels * sizeof(u32); + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return; + + if (spec->get(bebob, buf, size) < 0) + goto end; + + for (i = 0, c = 1; i < channels; i++) { + snd_iprintf(buffer, "%s %d:\t%d\n", + spec->labels[i / 2], c++, buf[i]); + if ((i + 1 < channels - 1) && + (strcmp(spec->labels[i / 2], + spec->labels[(i + 1) / 2]) != 0)) + c = 1; + } +end: + kfree(buf); +} + +static void +proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_stream_formation *formation; + unsigned int i; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->tx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->rx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } +} + +static void +proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const clk_labels[] = { + "Internal", + "External", + "SYT-Match", + }; + struct snd_bebob *bebob = entry->private_data; + const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; + const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + enum snd_bebob_clock_type src; + unsigned int rate; + + if (rate_spec->get(bebob, &rate) >= 0) + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (snd_bebob_stream_get_clock_src(bebob, &src) >= 0) { + if (clk_spec) + snd_iprintf(buffer, "Clock Source: %s\n", + clk_labels[src]); + else + snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n", + clk_labels[src], bebob->sync_input_plug); + } +} + +static void +add_node(struct snd_bebob *bebob, struct snd_info_entry *root, const char *name, + void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(bebob->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, bebob, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_bebob_proc_init(struct snd_bebob *bebob) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(bebob->card, "firewire", + bebob->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(bebob, root, "clock", proc_read_clock); + add_node(bebob, root, "firmware", proc_read_hw_info); + add_node(bebob, root, "formation", proc_read_formation); + + if (bebob->spec->meter != NULL) + add_node(bebob, root, "meter", proc_read_meters); +} diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c new file mode 100644 index 000000000..be2c056eb --- /dev/null +++ b/sound/firewire/bebob/bebob_stream.c @@ -0,0 +1,1004 @@ +/* + * bebob_stream.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define CALLBACK_TIMEOUT 2000 +#define FW_ISO_RESOURCE_DELAY 1000 + +/* + * NOTE; + * For BeBoB streams, Both of input and output CMP connection are important. + * + * For most devices, each CMP connection starts to transmit/receive a + * corresponding stream. But for a few devices, both of CMP connection needs + * to start transmitting stream. An example is 'M-Audio Firewire 410'. + */ + +/* 128 is an arbitrary length but it seems to be enough */ +#define FORMAT_MAXIMUM_LENGTH 128 + +const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000, +}; + +/* + * See: Table 51: Extended Stream Format Info ‘Sampling Frequency’ + * in Additional AVC commands (Nov 2003, BridgeCo) + */ +static const unsigned int bridgeco_freq_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x06, + [6] = 0x07, +}; + +static int +get_formation_index(unsigned int rate, unsigned int *index) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) { + if (snd_bebob_rate_table[i] == rate) { + *index = i; + return 0; + } + } + return -EINVAL; +} + +int +snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate) +{ + unsigned int tx_rate, rx_rate, trials; + int err; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &tx_rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + *curr_rate = rx_rate; + if (rx_rate == tx_rate) + goto end; + + /* synchronize receive stream rate to transmit stream rate */ + err = avc_general_set_sig_fmt(bebob->unit, rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); +end: + return err; +} + +int +snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + /* + * Some devices need a bit time for transition. + * 300msec is got by some experiments. + */ + msleep(300); +end: + return err; +} + +int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob, + enum snd_bebob_clock_type *src) +{ + const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; + unsigned int id; + enum avc_bridgeco_plug_type type; + int err = 0; + + /* 1.The device has its own operation to switch source of clock */ + if (clk_spec) { + err = clk_spec->get(bebob, &id); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get clock source: %d\n", err); + goto end; + } + + if (id >= clk_spec->num) { + dev_err(&bebob->unit->device, + "clock source %d out of range 0..%d\n", + id, clk_spec->num - 1); + err = -EIO; + goto end; + } + + *src = clk_spec->types[id]; + goto end; + } + + /* + * 2.The device don't support to switch source of clock then assumed + * to use internal clock always + */ + if (bebob->sync_input_plug < 0) { + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + + /* + * 3.The device supports to switch source of clock by an usual way. + * Let's check input for 'Music Sub Unit Sync Input' plug. + */ + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + bebob->sync_input_plug); + err = avc_bridgeco_get_plug_input(bebob->unit, addr, input); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get an input for MSU in plug %d: %d\n", + bebob->sync_input_plug, err); + goto end; + } + + /* + * If there are no input plugs, all of fields are 0xff. + * Here check the first field. This field is used for direction. + */ + if (input[0] == 0xff) { + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + + /* The source from any output plugs is for one purpose only. */ + if (input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) { + /* + * In BeBoB architecture, the source from music subunit may + * bypass from oPCR[0]. This means that this source gives + * synchronization to IEEE 1394 cycle start packet. + */ + if (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT && + input[2] == 0x0c) { + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + /* The source from any input units is for several purposes. */ + } else if (input[1] == AVC_BRIDGECO_PLUG_MODE_UNIT) { + if (input[2] == AVC_BRIDGECO_PLUG_UNIT_ISOC) { + if (input[3] == 0x00) { + /* + * This source comes from iPCR[0]. This means + * that presentation timestamp calculated by + * SYT series of the received packets. In + * short, this driver is the master of + * synchronization. + */ + *src = SND_BEBOB_CLOCK_TYPE_SYT; + goto end; + } else { + /* + * This source comes from iPCR[1-29]. This + * means that the synchronization stream is not + * the Audio/MIDI compound stream. + */ + *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; + goto end; + } + } else if (input[2] == AVC_BRIDGECO_PLUG_UNIT_EXT) { + /* Check type of this plug. */ + avc_bridgeco_fill_unit_addr(addr, + AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_EXT, + input[3]); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, + &type); + if (err < 0) + goto end; + + if (type == AVC_BRIDGECO_PLUG_TYPE_DIG) { + /* + * SPDIF/ADAT or sometimes (not always) word + * clock. + */ + *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { + /* Often word clock. */ + *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_ADDITION) { + /* + * Not standard. + * Mostly, additional internal clock. + */ + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + } + } + + /* Not supported. */ + err = -EIO; +end: + return err; +} + +static int map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + unsigned int sec, sections, ch, channels; + unsigned int pcm, midi, location; + unsigned int stm_pos, sec_loc, pos; + u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type; + enum avc_bridgeco_plug_dir dir; + int err; + + /* + * The length of return value of this command cannot be expected. Here + * use the maximum length of FCP. + */ + buf = kzalloc(256, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (s == &bebob->tx_stream) + dir = AVC_BRIDGECO_PLUG_DIR_OUT; + else + dir = AVC_BRIDGECO_PLUG_DIR_IN; + + avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get channel position for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out", + err); + goto end; + } + pos = 0; + + /* positions in I/O buffer */ + pcm = 0; + midi = 0; + + /* the number of sections in AMDTP packet */ + sections = buf[pos++]; + + for (sec = 0; sec < sections; sec++) { + /* type of this section */ + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_section_type(bebob->unit, addr, + sec, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get section type for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + err); + goto end; + } + /* NoType */ + if (type == 0xff) { + err = -ENOSYS; + goto end; + } + + /* the number of channels in this section */ + channels = buf[pos++]; + + for (ch = 0; ch < channels; ch++) { + /* position of this channel in AMDTP packet */ + stm_pos = buf[pos++] - 1; + /* location of this channel in this section */ + sec_loc = buf[pos++] - 1; + + /* + * Basically the number of location is within the + * number of channels in this section. But some models + * of M-Audio don't follow this. Its location for MIDI + * is the position of MIDI channels in AMDTP packet. + */ + if (sec_loc >= channels) + sec_loc = ch; + + switch (type) { + /* for MIDI conformant data channel */ + case 0x0a: + /* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */ + if ((midi > 0) && (stm_pos != midi)) { + err = -ENOSYS; + goto end; + } + amdtp_am824_set_midi_position(s, stm_pos); + midi = stm_pos; + break; + /* for PCM data channel */ + case 0x01: /* Headphone */ + case 0x02: /* Microphone */ + case 0x03: /* Line */ + case 0x04: /* SPDIF */ + case 0x05: /* ADAT */ + case 0x06: /* TDIF */ + case 0x07: /* MADI */ + /* for undefined/changeable signal */ + case 0x08: /* Analog */ + case 0x09: /* Digital */ + default: + location = pcm + sec_loc; + if (location >= AM824_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + amdtp_am824_set_pcm_position(s, location, + stm_pos); + break; + } + } + + if (type != 0x0a) + pcm += channels; + else + midi += channels; + } +end: + kfree(buf); + return err; +} + +static int +init_both_connections(struct snd_bebob *bebob) +{ + int err; + + err = cmp_connection_init(&bebob->in_conn, + bebob->unit, CMP_INPUT, 0); + if (err < 0) + goto end; + + err = cmp_connection_init(&bebob->out_conn, + bebob->unit, CMP_OUTPUT, 0); + if (err < 0) + cmp_connection_destroy(&bebob->in_conn); +end: + return err; +} + +static int +check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &bebob->tx_stream) + conn = &bebob->out_conn; + else + conn = &bebob->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&bebob->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +static int +make_both_connections(struct snd_bebob *bebob, unsigned int rate) +{ + int index, pcm_channels, midi_channels, err = 0; + + if (bebob->connected) + goto end; + + /* confirm params for both streams */ + err = get_formation_index(rate, &index); + if (err < 0) + goto end; + pcm_channels = bebob->tx_stream_formations[index].pcm; + midi_channels = bebob->tx_stream_formations[index].midi; + err = amdtp_am824_set_parameters(&bebob->tx_stream, rate, + pcm_channels, midi_channels * 8, + false); + if (err < 0) + goto end; + + pcm_channels = bebob->rx_stream_formations[index].pcm; + midi_channels = bebob->rx_stream_formations[index].midi; + err = amdtp_am824_set_parameters(&bebob->rx_stream, rate, + pcm_channels, midi_channels * 8, + false); + if (err < 0) + goto end; + + /* establish connections for both streams */ + err = cmp_connection_establish(&bebob->out_conn, + amdtp_stream_get_max_payload(&bebob->tx_stream)); + if (err < 0) + goto end; + err = cmp_connection_establish(&bebob->in_conn, + amdtp_stream_get_max_payload(&bebob->rx_stream)); + if (err < 0) { + cmp_connection_break(&bebob->out_conn); + goto end; + } + + bebob->connected = true; +end: + return err; +} + +static void +break_both_connections(struct snd_bebob *bebob) +{ + cmp_connection_break(&bebob->in_conn); + cmp_connection_break(&bebob->out_conn); + + bebob->connected = false; + + /* These models seems to be in transition state for a longer time. */ + if (bebob->maudio_special_quirk != NULL) + msleep(200); +} + +static void +destroy_both_connections(struct snd_bebob *bebob) +{ + cmp_connection_destroy(&bebob->in_conn); + cmp_connection_destroy(&bebob->out_conn); +} + +static int +start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream, + unsigned int rate) +{ + struct cmp_connection *conn; + int err = 0; + + if (stream == &bebob->rx_stream) + conn = &bebob->in_conn; + else + conn = &bebob->out_conn; + + /* channel mapping */ + if (bebob->maudio_special_quirk == NULL) { + err = map_data_channels(bebob, stream); + if (err < 0) + goto end; + } + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); +end: + return err; +} + +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) +{ + int err; + + err = init_both_connections(bebob); + if (err < 0) + goto end; + + err = amdtp_am824_init(&bebob->tx_stream, bebob->unit, + AMDTP_IN_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + destroy_both_connections(bebob); + goto end; + } + + /* + * BeBoB v3 transfers packets with these qurks: + * - In the beginning of streaming, the value of dbc is incremented + * even if no data blocks are transferred. + * - The value of dbc is reset suddenly. + */ + if (bebob->version > 2) + bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC | + CIP_SKIP_DBC_ZERO_CHECK; + + /* + * At high sampling rate, M-Audio special firmware transmits empty + * packet with the value of dbc incremented by 8 but the others are + * valid to IEC 61883-1. + */ + if (bebob->maudio_special_quirk) + bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC; + + err = amdtp_am824_init(&bebob->rx_stream, bebob->unit, + AMDTP_OUT_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + amdtp_stream_destroy(&bebob->rx_stream); + destroy_both_connections(bebob); + } +end: + return err; +} + +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate) +{ + const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; + unsigned int curr_rate; + int err = 0; + + /* Need no substreams */ + if (bebob->substreams_counter == 0) + goto end; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(bebob, &bebob->rx_stream); + if (err < 0) + goto end; + + /* + * packet queueing error or detecting discontinuity + * + * At bus reset, connections should not be broken here. So streams need + * to be re-started. This is a reason to use SKIP_INIT_DBC_CHECK flag. + */ + if (amdtp_streaming_error(&bebob->rx_stream)) + amdtp_stream_stop(&bebob->rx_stream); + if (amdtp_streaming_error(&bebob->tx_stream)) + amdtp_stream_stop(&bebob->tx_stream); + if (!amdtp_stream_running(&bebob->rx_stream) && + !amdtp_stream_running(&bebob->tx_stream)) + break_both_connections(bebob); + + /* stop streams if rate is different */ + err = rate_spec->get(bebob, &curr_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto end; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(&bebob->rx_stream); + amdtp_stream_stop(&bebob->tx_stream); + break_both_connections(bebob); + } + + /* master should be always running */ + if (!amdtp_stream_running(&bebob->rx_stream)) { + /* + * NOTE: + * If establishing connections at first, Yamaha GO46 + * (and maybe Terratec X24) don't generate sound. + * + * For firmware customized by M-Audio, refer to next NOTE. + */ + if (bebob->maudio_special_quirk == NULL) { + err = rate_spec->set(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to set sampling rate: %d\n", + err); + goto end; + } + } + + err = make_both_connections(bebob, rate); + if (err < 0) + goto end; + + err = start_stream(bebob, &bebob->rx_stream, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP master stream:%d\n", err); + break_both_connections(bebob); + goto end; + } + + /* + * NOTE: + * The firmware customized by M-Audio uses these commands to + * start transmitting stream. This is not usual way. + */ + if (bebob->maudio_special_quirk != NULL) { + err = rate_spec->set(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to ensure sampling rate: %d\n", + err); + amdtp_stream_stop(&bebob->rx_stream); + break_both_connections(bebob); + goto end; + } + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(&bebob->rx_stream, + CALLBACK_TIMEOUT)) { + amdtp_stream_stop(&bebob->rx_stream); + break_both_connections(bebob); + err = -ETIMEDOUT; + goto end; + } + } + + /* start slave if needed */ + if (!amdtp_stream_running(&bebob->tx_stream)) { + err = start_stream(bebob, &bebob->tx_stream, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP slave stream:%d\n", err); + amdtp_stream_stop(&bebob->rx_stream); + break_both_connections(bebob); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(&bebob->tx_stream, + CALLBACK_TIMEOUT)) { + amdtp_stream_stop(&bebob->tx_stream); + amdtp_stream_stop(&bebob->rx_stream); + break_both_connections(bebob); + err = -ETIMEDOUT; + } + } +end: + return err; +} + +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) +{ + if (bebob->substreams_counter == 0) { + amdtp_stream_pcm_abort(&bebob->rx_stream); + amdtp_stream_stop(&bebob->rx_stream); + + amdtp_stream_pcm_abort(&bebob->tx_stream); + amdtp_stream_stop(&bebob->tx_stream); + + break_both_connections(bebob); + } +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) +{ + amdtp_stream_destroy(&bebob->rx_stream); + amdtp_stream_destroy(&bebob->tx_stream); + + destroy_both_connections(bebob); +} + +/* + * See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’ + * in Additional AVC commands (Nov 2003, BridgeCo) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +static int +parse_stream_formation(u8 *buf, unsigned int len, + struct snd_bebob_stream_formation *formation) +{ + unsigned int i, e, channels, format; + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((buf[0] != 0x90) || (buf[1] != 0x40)) + return -ENOSYS; + + /* check sampling rate */ + for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) { + if (buf[2] == bridgeco_freq_table[i]) + break; + } + if (i == ARRAY_SIZE(bridgeco_freq_table)) + return -ENOSYS; + + /* Avoid double count by different entries for the same rate. */ + memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation)); + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* IEC 60958 Conformant, currently handled as MBLA */ + case 0x00: + /* Multi bit linear audio */ + case 0x06: /* Raw */ + formation[i].pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation[i].midi += channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi bit linear audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation[i].pcm > AM824_MAX_CHANNELS_FOR_PCM || + formation[i].midi > AM824_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; + + return 0; +} + +static int +fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir, + unsigned short pid) +{ + u8 *buf; + struct snd_bebob_stream_formation *formations; + unsigned int len, eid; + u8 addr[AVC_BRIDGECO_ADDR_BYTES]; + int err; + + buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_BRIDGECO_PLUG_DIR_IN) + formations = bebob->rx_stream_formations; + else + formations = bebob->tx_stream_formations; + + for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) { + len = FORMAT_MAXIMUM_LENGTH; + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, pid); + err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, + &len, eid); + /* No entries remained. */ + if (err == -EINVAL && eid > 0) { + err = 0; + break; + } else if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + + err = parse_stream_formation(buf, len, formations); + if (err < 0) + break; + } + + kfree(buf); + return err; +} + +static int +seek_msu_sync_input_plug(struct snd_bebob *bebob) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + unsigned int i; + enum avc_bridgeco_plug_type type; + int err; + + /* Get the number of Music Sub Unit for both direction. */ + err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for MSU in/out plugs: %d\n", + err); + goto end; + } + + /* seek destination plugs for 'MSU sync input' */ + bebob->sync_input_plug = -1; + for (i = 0; i < plugs[0]; i++) { + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for MSU in plug %d: %d\n", + i, err); + goto end; + } + + if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { + bebob->sync_input_plug = i; + break; + } + } +end: + return err; +} + +int snd_bebob_stream_discover(struct snd_bebob *bebob) +{ + const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + enum avc_bridgeco_plug_type type; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } + + /* + * This module supports at least one isoc input plug and one isoc + * output plug. + */ + if ((plugs[0] == 0) || (plugs[1] == 0)) { + err = -ENOSYS; + goto end; + } + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc in plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc out plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* count external input plugs for MIDI */ + bebob->midi_input_ports = 0; + for (i = 0; i < plugs[2]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external in plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_input_ports++; + } + } + + /* count external output plugs for MIDI */ + bebob->midi_output_ports = 0; + for (i = 0; i < plugs[3]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external out plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_output_ports++; + } + } + + /* for check source of clock later */ + if (!clk_spec) + err = seek_msu_sync_input_plug(bebob); +end: + return err; +} + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob) +{ + bebob->dev_lock_changed = true; + wake_up(&bebob->hwdep_wait); +} + +int snd_bebob_stream_lock_try(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + /* user land lock this */ + if (bebob->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (bebob->dev_lock_count++ == 0) + snd_bebob_stream_lock_changed(bebob); + err = 0; +end: + spin_unlock_irq(&bebob->lock); + return err; +} + +void snd_bebob_stream_lock_release(struct snd_bebob *bebob) +{ + spin_lock_irq(&bebob->lock); + + if (WARN_ON(bebob->dev_lock_count <= 0)) + goto end; + if (--bebob->dev_lock_count == 0) + snd_bebob_stream_lock_changed(bebob); +end: + spin_unlock_irq(&bebob->lock); +} diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c new file mode 100644 index 000000000..9770c2127 --- /dev/null +++ b/sound/firewire/bebob/bebob_terratec.c @@ -0,0 +1,54 @@ +/* + * bebob_terratec.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static const enum snd_bebob_clock_type phase88_rack_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ +}; +static int +phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + unsigned int enable_ext, enable_word; + int err; + + err = avc_audio_get_selector(bebob->unit, 0, 9, &enable_ext); + if (err < 0) + goto end; + err = avc_audio_get_selector(bebob->unit, 0, 8, &enable_word); + if (err < 0) + goto end; + + if (enable_ext == 0) + *id = 0; + else if (enable_word == 0) + *id = 1; + else + *id = 2; +end: + return err; +} + +static const struct snd_bebob_rate_spec phase_series_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; + +/* PHASE 88 Rack FW */ +static const struct snd_bebob_clock_spec phase88_rack_clk = { + .num = ARRAY_SIZE(phase88_rack_clk_src_types), + .types = phase88_rack_clk_src_types, + .get = &phase88_rack_clk_src_get, +}; +const struct snd_bebob_spec phase88_rack_spec = { + .clock = &phase88_rack_clk, + .rate = &phase_series_rate_spec, + .meter = NULL +}; diff --git a/sound/firewire/bebob/bebob_yamaha_terratec.c b/sound/firewire/bebob/bebob_yamaha_terratec.c new file mode 100644 index 000000000..8bd78fef3 --- /dev/null +++ b/sound/firewire/bebob/bebob_yamaha_terratec.c @@ -0,0 +1,65 @@ +/* + * bebob_yamaha.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +/* + * NOTE: + * Yamaha GO44 is not designed to be used as stand-alone mixer. So any streams + * must be accompanied. If changing the state, a LED on the device starts to + * blink and its sync status is false. In this state, the device sounds nothing + * even if streaming. To start streaming at the current sampling rate is only + * way to recover this state. GO46 is better for stand-alone mixer. + * + * Both of them have a capability to change its sampling rate up to 192.0kHz. + * At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out. + * But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use + * 'Extended Stream Format Information Command - Single Request' in 'Additional + * AVC commands' defined by BridgeCo. + * This ALSA driver don't do this because a bit tiresome. Then isochronous + * streaming with many asynchronous transactions brings sounds with noises. + * Unfortunately current 'ffado-mixer' generated many asynchronous transaction + * to observe device's state, mainly check cmp connection and signal format. I + * recommend users to close ffado-mixer at 192.0kHz if mixer is needless. + * + * Terratec PHASE 24 FW and PHASE X24 FW are internally the same as + * Yamaha GO 44 and GO 46. Yamaha and Terratec had cooperated for these models. + */ + +static const enum snd_bebob_clock_type clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ +}; +static int +clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + + err = avc_audio_get_selector(bebob->unit, 0, 4, id); + if (err < 0) + return err; + + if (*id >= ARRAY_SIZE(clk_src_types)) + return -EIO; + + return 0; +} +static const struct snd_bebob_clock_spec clock_spec = { + .num = ARRAY_SIZE(clk_src_types), + .types = clk_src_types, + .get = &clk_src_get, +}; +static const struct snd_bebob_rate_spec rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +const struct snd_bebob_spec yamaha_terratec_spec = { + .clock = &clock_spec, + .rate = &rate_spec, + .meter = NULL +}; diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c new file mode 100644 index 000000000..ae3bc1940 --- /dev/null +++ b/sound/firewire/cmp.c @@ -0,0 +1,404 @@ +/* + * Connection Management Procedures (IEC 61883-1) helper functions + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include "lib.h" +#include "iso-resources.h" +#include "cmp.h" + +/* MPR common fields */ +#define MPR_SPEED_MASK 0xc0000000 +#define MPR_SPEED_SHIFT 30 +#define MPR_XSPEED_MASK 0x00000060 +#define MPR_XSPEED_SHIFT 5 +#define MPR_PLUGS_MASK 0x0000001f + +/* PCR common fields */ +#define PCR_ONLINE 0x80000000 +#define PCR_BCAST_CONN 0x40000000 +#define PCR_P2P_CONN_MASK 0x3f000000 +#define PCR_P2P_CONN_SHIFT 24 +#define PCR_CHANNEL_MASK 0x003f0000 +#define PCR_CHANNEL_SHIFT 16 + +/* oPCR specific fields */ +#define OPCR_XSPEED_MASK 0x00C00000 +#define OPCR_XSPEED_SHIFT 22 +#define OPCR_SPEED_MASK 0x0000C000 +#define OPCR_SPEED_SHIFT 14 +#define OPCR_OVERHEAD_ID_MASK 0x00003C00 +#define OPCR_OVERHEAD_ID_SHIFT 10 + +enum bus_reset_handling { + ABORT_ON_BUS_RESET, + SUCCEED_ON_BUS_RESET, +}; + +static __printf(2, 3) +void cmp_error(struct cmp_connection *c, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + dev_err(&c->resources.unit->device, "%cPCR%u: %pV", + (c->direction == CMP_INPUT) ? 'i' : 'o', + c->pcr_index, &(struct va_format){ fmt, &va }); + va_end(va); +} + +static u64 mpr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IMPR; + else + return CSR_REGISTER_BASE + CSR_OMPR; +} + +static u64 pcr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index); + else + return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index); +} + +static int pcr_modify(struct cmp_connection *c, + __be32 (*modify)(struct cmp_connection *c, __be32 old), + int (*check)(struct cmp_connection *c, __be32 pcr), + enum bus_reset_handling bus_reset_handling) +{ + __be32 old_arg, buffer[2]; + int err; + + buffer[0] = c->last_pcr_value; + for (;;) { + old_arg = buffer[0]; + buffer[1] = modify(c, buffer[0]); + + err = snd_fw_transaction( + c->resources.unit, TCODE_LOCK_COMPARE_SWAP, + pcr_address(c), buffer, 8, + FW_FIXED_GENERATION | c->resources.generation); + + if (err < 0) { + if (err == -EAGAIN && + bus_reset_handling == SUCCEED_ON_BUS_RESET) + err = 0; + return err; + } + + if (buffer[0] == old_arg) /* success? */ + break; + + if (check) { + err = check(c, buffer[0]); + if (err < 0) + return err; + } + } + c->last_pcr_value = buffer[1]; + + return 0; +} + + +/** + * cmp_connection_init - initializes a connection manager + * @c: the connection manager to initialize + * @unit: a unit of the target device + * @direction: input or output + * @pcr_index: the index of the iPCR/oPCR on the target device + */ +int cmp_connection_init(struct cmp_connection *c, + struct fw_unit *unit, + enum cmp_direction direction, + unsigned int pcr_index) +{ + __be32 mpr_be; + u32 mpr; + int err; + + c->direction = direction; + err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + mpr_address(c), &mpr_be, 4, 0); + if (err < 0) + return err; + mpr = be32_to_cpu(mpr_be); + + if (pcr_index >= (mpr & MPR_PLUGS_MASK)) + return -EINVAL; + + err = fw_iso_resources_init(&c->resources, unit); + if (err < 0) + return err; + + c->connected = false; + mutex_init(&c->mutex); + c->last_pcr_value = cpu_to_be32(0x80000000); + c->pcr_index = pcr_index; + c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT; + if (c->max_speed == SCODE_BETA) + c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT; + + return 0; +} +EXPORT_SYMBOL(cmp_connection_init); + +/** + * cmp_connection_check_used - check connection is already esablished or not + * @c: the connection manager to be checked + * @used: the pointer to store the result of checking the connection + */ +int cmp_connection_check_used(struct cmp_connection *c, bool *used) +{ + __be32 pcr; + int err; + + err = snd_fw_transaction( + c->resources.unit, TCODE_READ_QUADLET_REQUEST, + pcr_address(c), &pcr, 4, 0); + if (err >= 0) + *used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK)); + + return err; +} +EXPORT_SYMBOL(cmp_connection_check_used); + +/** + * cmp_connection_destroy - free connection manager resources + * @c: the connection manager + */ +void cmp_connection_destroy(struct cmp_connection *c) +{ + WARN_ON(c->connected); + mutex_destroy(&c->mutex); + fw_iso_resources_destroy(&c->resources); +} +EXPORT_SYMBOL(cmp_connection_destroy); + + +static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) +{ + ipcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + PCR_CHANNEL_MASK); + ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); + + return ipcr; +} + +static int get_overhead_id(struct cmp_connection *c) +{ + int id; + + /* + * apply "oPCR overhead ID encoding" + * the encoding table can convert up to 512. + * here the value over 512 is converted as the same way as 512. + */ + for (id = 1; id < 16; id++) { + if (c->resources.bandwidth_overhead < (id << 5)) + break; + } + if (id == 16) + id = 0; + + return id; +} + +static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr) +{ + unsigned int spd, xspd; + + /* generate speed and extended speed field value */ + if (c->speed > SCODE_400) { + spd = SCODE_800; + xspd = c->speed - SCODE_800; + } else { + spd = c->speed; + xspd = 0; + } + + opcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + OPCR_XSPEED_MASK | + PCR_CHANNEL_MASK | + OPCR_SPEED_MASK | + OPCR_OVERHEAD_ID_MASK); + opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT); + opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); + opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT); + opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT); + + return opcr; +} + +static int pcr_set_check(struct cmp_connection *c, __be32 pcr) +{ + if (pcr & cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK)) { + cmp_error(c, "plug is already in use\n"); + return -EBUSY; + } + if (!(pcr & cpu_to_be32(PCR_ONLINE))) { + cmp_error(c, "plug is not on-line\n"); + return -ECONNREFUSED; + } + + return 0; +} + +/** + * cmp_connection_establish - establish a connection to the target + * @c: the connection manager + * @max_payload_bytes: the amount of data (including CIP headers) per packet + * + * This function establishes a point-to-point connection from the local + * computer to the target by allocating isochronous resources (channel and + * bandwidth) and setting the target's input/output plug control register. + * When this function succeeds, the caller is responsible for starting + * transmitting packets. + */ +int cmp_connection_establish(struct cmp_connection *c, + unsigned int max_payload_bytes) +{ + int err; + + if (WARN_ON(c->connected)) + return -EISCONN; + + c->speed = min(c->max_speed, + fw_parent_device(c->resources.unit)->max_speed); + + mutex_lock(&c->mutex); + +retry_after_bus_reset: + err = fw_iso_resources_allocate(&c->resources, + max_payload_bytes, c->speed); + if (err < 0) + goto err_mutex; + + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + + if (err == -EAGAIN) { + fw_iso_resources_free(&c->resources); + goto retry_after_bus_reset; + } + if (err < 0) + goto err_resources; + + c->connected = true; + + mutex_unlock(&c->mutex); + + return 0; + +err_resources: + fw_iso_resources_free(&c->resources); +err_mutex: + mutex_unlock(&c->mutex); + + return err; +} +EXPORT_SYMBOL(cmp_connection_establish); + +/** + * cmp_connection_update - update the connection after a bus reset + * @c: the connection manager + * + * This function must be called from the driver's .update handler to + * reestablish any connection that might have been active. + * + * Returns zero on success, or a negative error code. On an error, the + * connection is broken and the caller must stop transmitting iso packets. + */ +int cmp_connection_update(struct cmp_connection *c) +{ + int err; + + mutex_lock(&c->mutex); + + if (!c->connected) { + mutex_unlock(&c->mutex); + return 0; + } + + err = fw_iso_resources_update(&c->resources); + if (err < 0) + goto err_unconnect; + + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + + if (err < 0) + goto err_resources; + + mutex_unlock(&c->mutex); + + return 0; + +err_resources: + fw_iso_resources_free(&c->resources); +err_unconnect: + c->connected = false; + mutex_unlock(&c->mutex); + + return err; +} +EXPORT_SYMBOL(cmp_connection_update); + +static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr) +{ + return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK); +} + +/** + * cmp_connection_break - break the connection to the target + * @c: the connection manager + * + * This function deactives the connection in the target's input/output plug + * control register, and frees the isochronous resources of the connection. + * Before calling this function, the caller should cease transmitting packets. + */ +void cmp_connection_break(struct cmp_connection *c) +{ + int err; + + mutex_lock(&c->mutex); + + if (!c->connected) { + mutex_unlock(&c->mutex); + return; + } + + err = pcr_modify(c, pcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); + if (err < 0) + cmp_error(c, "plug is still connected\n"); + + fw_iso_resources_free(&c->resources); + + c->connected = false; + + mutex_unlock(&c->mutex); +} +EXPORT_SYMBOL(cmp_connection_break); diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h new file mode 100644 index 000000000..b60b415ca --- /dev/null +++ b/sound/firewire/cmp.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_CMP_H_INCLUDED +#define SOUND_FIREWIRE_CMP_H_INCLUDED + +#include +#include +#include "iso-resources.h" + +struct fw_unit; + +enum cmp_direction { + CMP_INPUT = 0, + CMP_OUTPUT, +}; + +/** + * struct cmp_connection - manages an isochronous connection to a device + * @speed: the connection's actual speed + * + * This structure manages (using CMP) an isochronous stream between the local + * computer and a device's input plug (iPCR) and output plug (oPCR). + * + * There is no corresponding oPCR created on the local computer, so it is not + * possible to overlay connections on top of this one. + */ +struct cmp_connection { + int speed; + /* private: */ + bool connected; + struct mutex mutex; + struct fw_iso_resources resources; + __be32 last_pcr_value; + unsigned int pcr_index; + unsigned int max_speed; + enum cmp_direction direction; +}; + +int cmp_connection_init(struct cmp_connection *connection, + struct fw_unit *unit, + enum cmp_direction direction, + unsigned int pcr_index); +int cmp_connection_check_used(struct cmp_connection *connection, bool *used); +void cmp_connection_destroy(struct cmp_connection *connection); + +int cmp_connection_establish(struct cmp_connection *connection, + unsigned int max_payload); +int cmp_connection_update(struct cmp_connection *connection); +void cmp_connection_break(struct cmp_connection *connection); + +#endif diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile new file mode 100644 index 000000000..37062a233 --- /dev/null +++ b/sound/firewire/dice/Makefile @@ -0,0 +1,4 @@ +snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \ + dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o \ + dice-alesis.o dice-extension.o dice-mytek.o +obj-$(CONFIG_SND_DICE) += snd-dice.o diff --git a/sound/firewire/dice/dice-alesis.c b/sound/firewire/dice/dice-alesis.c new file mode 100644 index 000000000..39a4ef8c8 --- /dev/null +++ b/sound/firewire/dice/dice-alesis.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-alesis.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +static const unsigned int +alesis_io14_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = { + {6, 6, 4}, /* Tx0 = Analog + S/PDIF. */ + {8, 4, 0}, /* Tx1 = ADAT1. */ +}; + +static const unsigned int +alesis_io26_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = { + {10, 10, 4}, /* Tx0 = Analog + S/PDIF. */ + {16, 4, 0}, /* Tx1 = ADAT1 + ADAT2 (available at low rate). */ +}; + +int snd_dice_detect_alesis_formats(struct snd_dice *dice) +{ + __be32 reg; + u32 data; + int i; + int err; + + err = snd_dice_transaction_read_tx(dice, TX_NUMBER_AUDIO, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (data == 4 || data == 6) { + memcpy(dice->tx_pcm_chs, alesis_io14_tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * + sizeof(unsigned int)); + } else { + memcpy(dice->tx_pcm_chs, alesis_io26_tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * + sizeof(unsigned int)); + } + + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + dice->rx_pcm_chs[0][i] = 8; + + dice->tx_midi_ports[0] = 1; + dice->rx_midi_ports[0] = 1; + + return 0; +} diff --git a/sound/firewire/dice/dice-extension.c b/sound/firewire/dice/dice-extension.c new file mode 100644 index 000000000..02f4a8318 --- /dev/null +++ b/sound/firewire/dice/dice-extension.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-extension.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +/* For TCD2210/2220, TCAT defines extension of application protocol. */ + +#define DICE_EXT_APP_SPACE 0xffffe0200000uLL + +#define DICE_EXT_APP_CAPS_OFFSET 0x00 +#define DICE_EXT_APP_CAPS_SIZE 0x04 +#define DICE_EXT_APP_CMD_OFFSET 0x08 +#define DICE_EXT_APP_CMD_SIZE 0x0c +#define DICE_EXT_APP_MIXER_OFFSET 0x10 +#define DICE_EXT_APP_MIXER_SIZE 0x14 +#define DICE_EXT_APP_PEAK_OFFSET 0x18 +#define DICE_EXT_APP_PEAK_SIZE 0x1c +#define DICE_EXT_APP_ROUTER_OFFSET 0x20 +#define DICE_EXT_APP_ROUTER_SIZE 0x24 +#define DICE_EXT_APP_STREAM_OFFSET 0x28 +#define DICE_EXT_APP_STREAM_SIZE 0x2c +#define DICE_EXT_APP_CURRENT_OFFSET 0x30 +#define DICE_EXT_APP_CURRENT_SIZE 0x34 +#define DICE_EXT_APP_STANDALONE_OFFSET 0x38 +#define DICE_EXT_APP_STANDALONE_SIZE 0x3c +#define DICE_EXT_APP_APPLICATION_OFFSET 0x40 +#define DICE_EXT_APP_APPLICATION_SIZE 0x44 + +#define EXT_APP_STREAM_TX_NUMBER 0x0000 +#define EXT_APP_STREAM_RX_NUMBER 0x0004 +#define EXT_APP_STREAM_ENTRIES 0x0008 +#define EXT_APP_STREAM_ENTRY_SIZE 0x010c +#define EXT_APP_NUMBER_AUDIO 0x0000 +#define EXT_APP_NUMBER_MIDI 0x0004 +#define EXT_APP_NAMES 0x0008 +#define EXT_APP_NAMES_SIZE 256 +#define EXT_APP_AC3 0x0108 + +#define EXT_APP_CONFIG_LOW_ROUTER 0x0000 +#define EXT_APP_CONFIG_LOW_STREAM 0x1000 +#define EXT_APP_CONFIG_MIDDLE_ROUTER 0x2000 +#define EXT_APP_CONFIG_MIDDLE_STREAM 0x3000 +#define EXT_APP_CONFIG_HIGH_ROUTER 0x4000 +#define EXT_APP_CONFIG_HIGH_STREAM 0x5000 + +static inline int read_transaction(struct snd_dice *dice, u64 section_addr, + u32 offset, void *buf, size_t len) +{ + return snd_fw_transaction(dice->unit, + len == 4 ? TCODE_READ_QUADLET_REQUEST : + TCODE_READ_BLOCK_REQUEST, + section_addr + offset, buf, len, 0); +} + +static int read_stream_entries(struct snd_dice *dice, u64 section_addr, + u32 base_offset, unsigned int stream_count, + unsigned int mode, + unsigned int pcm_channels[MAX_STREAMS][3], + unsigned int midi_ports[MAX_STREAMS]) +{ + u32 entry_offset; + __be32 reg[2]; + int err; + int i; + + for (i = 0; i < stream_count; ++i) { + entry_offset = base_offset + i * EXT_APP_STREAM_ENTRY_SIZE; + err = read_transaction(dice, section_addr, + entry_offset + EXT_APP_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + pcm_channels[i][mode] = be32_to_cpu(reg[0]); + midi_ports[i] = max(midi_ports[i], be32_to_cpu(reg[1])); + } + + return 0; +} + +static int detect_stream_formats(struct snd_dice *dice, u64 section_addr) +{ + u32 base_offset; + __be32 reg[2]; + unsigned int stream_count; + int mode; + int err = 0; + + for (mode = 0; mode < SND_DICE_RATE_MODE_COUNT; ++mode) { + unsigned int cap; + + /* + * Some models report stream formats at highest mode, however + * they don't support the mode. Check clock capabilities. + */ + if (mode == 2) { + cap = CLOCK_CAP_RATE_176400 | CLOCK_CAP_RATE_192000; + } else if (mode == 1) { + cap = CLOCK_CAP_RATE_88200 | CLOCK_CAP_RATE_96000; + } else { + cap = CLOCK_CAP_RATE_32000 | CLOCK_CAP_RATE_44100 | + CLOCK_CAP_RATE_48000; + } + if (!(cap & dice->clock_caps)) + continue; + + base_offset = 0x2000 * mode + 0x1000; + + err = read_transaction(dice, section_addr, + base_offset + EXT_APP_STREAM_TX_NUMBER, + ®, sizeof(reg)); + if (err < 0) + break; + + base_offset += EXT_APP_STREAM_ENTRIES; + stream_count = be32_to_cpu(reg[0]); + err = read_stream_entries(dice, section_addr, base_offset, + stream_count, mode, + dice->tx_pcm_chs, + dice->tx_midi_ports); + if (err < 0) + break; + + base_offset += stream_count * EXT_APP_STREAM_ENTRY_SIZE; + stream_count = be32_to_cpu(reg[1]); + err = read_stream_entries(dice, section_addr, base_offset, + stream_count, + mode, dice->rx_pcm_chs, + dice->rx_midi_ports); + if (err < 0) + break; + } + + return err; +} + +int snd_dice_detect_extension_formats(struct snd_dice *dice) +{ + __be32 *pointers; + unsigned int i; + u64 section_addr; + int err; + + pointers = kmalloc_array(9, sizeof(__be32) * 2, GFP_KERNEL); + if (pointers == NULL) + return -ENOMEM; + + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_EXT_APP_SPACE, pointers, + 9 * sizeof(__be32) * 2, 0); + if (err < 0) + goto end; + + /* Check two of them for offset have the same value or not. */ + for (i = 0; i < 9; ++i) { + int j; + + for (j = i + 1; j < 9; ++j) { + if (pointers[i * 2] == pointers[j * 2]) { + // Fallback to limited functionality. + err = -ENXIO; + goto end; + } + } + } + + section_addr = DICE_EXT_APP_SPACE + be32_to_cpu(pointers[12]) * 4; + err = detect_stream_formats(dice, section_addr); +end: + kfree(pointers); + return err; +} diff --git a/sound/firewire/dice/dice-hwdep.c b/sound/firewire/dice/dice-hwdep.c new file mode 100644 index 000000000..6498bf690 --- /dev/null +++ b/sound/firewire/dice/dice-hwdep.c @@ -0,0 +1,190 @@ +/* + * dice_hwdep.c - a part of driver for DICE based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "dice.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, + long count, loff_t *offset) +{ + struct snd_dice *dice = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&dice->lock); + + while (!dice->dev_lock_changed && dice->notification_bits == 0) { + prepare_to_wait(&dice->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&dice->lock); + schedule(); + finish_wait(&dice->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&dice->lock); + } + + memset(&event, 0, sizeof(event)); + if (dice->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = dice->dev_lock_count > 0; + dice->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.dice_notification.type = + SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION; + event.dice_notification.notification = dice->notification_bits; + dice->notification_bits = 0; + + count = min_t(long, count, sizeof(event.dice_notification)); + } + + spin_unlock_irq(&dice->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_dice *dice = hwdep->private_data; + __poll_t events; + + poll_wait(file, &dice->hwdep_wait, wait); + + spin_lock_irq(&dice->lock); + if (dice->dev_lock_changed || dice->notification_bits != 0) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&dice->lock); + + return events; +} + +static int hwdep_get_info(struct snd_dice *dice, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(dice->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_DICE; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count == 0) { + dice->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&dice->lock); + + return err; +} + +static int hwdep_unlock(struct snd_dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count == -1) { + dice->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&dice->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_dice *dice = hwdep->private_data; + + spin_lock_irq(&dice->lock); + if (dice->dev_lock_count == -1) + dice->dev_lock_count = 0; + spin_unlock_irq(&dice->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_dice *dice = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(dice, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(dice); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(dice); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_dice_create_hwdep(struct snd_dice *dice) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(dice->card, "DICE", 0, &hwdep); + if (err < 0) + return err; + strcpy(hwdep->name, "DICE"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_DICE; + hwdep->ops = ops; + hwdep->private_data = dice; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/dice/dice-interface.h b/sound/firewire/dice/dice-interface.h new file mode 100644 index 000000000..9cad3d608 --- /dev/null +++ b/sound/firewire/dice/dice-interface.h @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED +#define SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED + +/* + * DICE device interface definitions + */ + +/* + * Generally, all registers can be read like memory, i.e., with quadlet read or + * block read transactions with at least quadlet-aligned offset and length. + * Writes are not allowed except where noted; quadlet-sized registers must be + * written with a quadlet write transaction. + * + * All values are in big endian. The DICE firmware runs on a little-endian CPU + * and just byte-swaps _all_ quadlets on the bus, so values without endianness + * (e.g. strings) get scrambled and must be byte-swapped again by the driver. + */ + +/* + * Streaming is handled by the "DICE driver" interface. Its registers are + * located in this private address space. + */ +#define DICE_PRIVATE_SPACE 0xffffe0000000uLL + +/* + * The registers are organized in several sections, which are organized + * separately to allow them to be extended individually. Whether a register is + * supported can be detected by checking its offset against its section's size. + * + * The section offset values are relative to DICE_PRIVATE_SPACE; the offset/ + * size values are measured in quadlets. Read-only. + */ +#define DICE_GLOBAL_OFFSET 0x00 +#define DICE_GLOBAL_SIZE 0x04 +#define DICE_TX_OFFSET 0x08 +#define DICE_TX_SIZE 0x0c +#define DICE_RX_OFFSET 0x10 +#define DICE_RX_SIZE 0x14 +#define DICE_EXT_SYNC_OFFSET 0x18 +#define DICE_EXT_SYNC_SIZE 0x1c +#define DICE_UNUSED2_OFFSET 0x20 +#define DICE_UNUSED2_SIZE 0x24 + +/* + * Global settings. + */ + +/* + * Stores the full 64-bit address (node ID and offset in the node's address + * space) where the device will send notifications. Must be changed with + * a compare/swap transaction by the owner. This register is automatically + * cleared on a bus reset. + */ +#define GLOBAL_OWNER 0x000 +#define OWNER_NO_OWNER 0xffff000000000000uLL +#define OWNER_NODE_SHIFT 48 + +/* + * A bitmask with asynchronous events; read-only. When any event(s) happen, + * the bits of previous events are cleared, and the value of this register is + * also written to the address stored in the owner register. + */ +#define GLOBAL_NOTIFICATION 0x008 +/* Some registers in the Rx/Tx sections may have changed. */ +#define NOTIFY_RX_CFG_CHG 0x00000001 +#define NOTIFY_TX_CFG_CHG 0x00000002 +/* Lock status of the current clock source may have changed. */ +#define NOTIFY_LOCK_CHG 0x00000010 +/* Write to the clock select register has been finished. */ +#define NOTIFY_CLOCK_ACCEPTED 0x00000020 +/* Lock status of some clock source has changed. */ +#define NOTIFY_EXT_STATUS 0x00000040 +/* Other bits may be used for device-specific events. */ + +/* + * A name that can be customized for each device; read/write. Padded with zero + * bytes. Quadlets are byte-swapped. The encoding is whatever the host driver + * happens to be using. + */ +#define GLOBAL_NICK_NAME 0x00c +#define NICK_NAME_SIZE 64 + +/* + * The current sample rate and clock source; read/write. Whether a clock + * source or sample rate is supported is device-specific; the internal clock + * source is always available. Low/mid/high = up to 48/96/192 kHz. This + * register can be changed even while streams are running. + */ +#define GLOBAL_CLOCK_SELECT 0x04c +#define CLOCK_SOURCE_MASK 0x000000ff +#define CLOCK_SOURCE_AES1 0x00000000 +#define CLOCK_SOURCE_AES2 0x00000001 +#define CLOCK_SOURCE_AES3 0x00000002 +#define CLOCK_SOURCE_AES4 0x00000003 +#define CLOCK_SOURCE_AES_ANY 0x00000004 +#define CLOCK_SOURCE_ADAT 0x00000005 +#define CLOCK_SOURCE_TDIF 0x00000006 +#define CLOCK_SOURCE_WC 0x00000007 +#define CLOCK_SOURCE_ARX1 0x00000008 +#define CLOCK_SOURCE_ARX2 0x00000009 +#define CLOCK_SOURCE_ARX3 0x0000000a +#define CLOCK_SOURCE_ARX4 0x0000000b +#define CLOCK_SOURCE_INTERNAL 0x0000000c +#define CLOCK_RATE_MASK 0x0000ff00 +#define CLOCK_RATE_32000 0x00000000 +#define CLOCK_RATE_44100 0x00000100 +#define CLOCK_RATE_48000 0x00000200 +#define CLOCK_RATE_88200 0x00000300 +#define CLOCK_RATE_96000 0x00000400 +#define CLOCK_RATE_176400 0x00000500 +#define CLOCK_RATE_192000 0x00000600 +#define CLOCK_RATE_ANY_LOW 0x00000700 +#define CLOCK_RATE_ANY_MID 0x00000800 +#define CLOCK_RATE_ANY_HIGH 0x00000900 +#define CLOCK_RATE_NONE 0x00000a00 +#define CLOCK_RATE_SHIFT 8 + +/* + * Enable streaming; read/write. Writing a non-zero value (re)starts all + * streams that have a valid iso channel set; zero stops all streams. The + * streams' parameters must be configured before starting. This register is + * automatically cleared on a bus reset. + */ +#define GLOBAL_ENABLE 0x050 + +/* + * Status of the sample clock; read-only. + */ +#define GLOBAL_STATUS 0x054 +/* The current clock source is locked. */ +#define STATUS_SOURCE_LOCKED 0x00000001 +/* The actual sample rate; CLOCK_RATE_32000-_192000 or _NONE. */ +#define STATUS_NOMINAL_RATE_MASK 0x0000ff00 + +/* + * Status of all clock sources; read-only. + */ +#define GLOBAL_EXTENDED_STATUS 0x058 +/* + * The _LOCKED bits always show the current status; any change generates + * a notification. + */ +#define EXT_STATUS_AES1_LOCKED 0x00000001 +#define EXT_STATUS_AES2_LOCKED 0x00000002 +#define EXT_STATUS_AES3_LOCKED 0x00000004 +#define EXT_STATUS_AES4_LOCKED 0x00000008 +#define EXT_STATUS_ADAT_LOCKED 0x00000010 +#define EXT_STATUS_TDIF_LOCKED 0x00000020 +#define EXT_STATUS_ARX1_LOCKED 0x00000040 +#define EXT_STATUS_ARX2_LOCKED 0x00000080 +#define EXT_STATUS_ARX3_LOCKED 0x00000100 +#define EXT_STATUS_ARX4_LOCKED 0x00000200 +#define EXT_STATUS_WC_LOCKED 0x00000400 +/* + * The _SLIP bits do not generate notifications; a set bit indicates that an + * error occurred since the last time when this register was read with + * a quadlet read transaction. + */ +#define EXT_STATUS_AES1_SLIP 0x00010000 +#define EXT_STATUS_AES2_SLIP 0x00020000 +#define EXT_STATUS_AES3_SLIP 0x00040000 +#define EXT_STATUS_AES4_SLIP 0x00080000 +#define EXT_STATUS_ADAT_SLIP 0x00100000 +#define EXT_STATUS_TDIF_SLIP 0x00200000 +#define EXT_STATUS_ARX1_SLIP 0x00400000 +#define EXT_STATUS_ARX2_SLIP 0x00800000 +#define EXT_STATUS_ARX3_SLIP 0x01000000 +#define EXT_STATUS_ARX4_SLIP 0x02000000 +#define EXT_STATUS_WC_SLIP 0x04000000 + +/* + * The measured rate of the current clock source, in Hz; read-only. + */ +#define GLOBAL_SAMPLE_RATE 0x05c + +/* + * Some old firmware versions do not have the following global registers. + * Windows drivers produced by TCAT lost backward compatibility in its + * early release because they can handle firmware only which supports the + * following registers. + */ + +/* + * The version of the DICE driver specification that this device conforms to; + * read-only. + */ +#define GLOBAL_VERSION 0x060 + +/* + * Supported sample rates and clock sources; read-only. + */ +#define GLOBAL_CLOCK_CAPABILITIES 0x064 +#define CLOCK_CAP_RATE_32000 0x00000001 +#define CLOCK_CAP_RATE_44100 0x00000002 +#define CLOCK_CAP_RATE_48000 0x00000004 +#define CLOCK_CAP_RATE_88200 0x00000008 +#define CLOCK_CAP_RATE_96000 0x00000010 +#define CLOCK_CAP_RATE_176400 0x00000020 +#define CLOCK_CAP_RATE_192000 0x00000040 +#define CLOCK_CAP_SOURCE_AES1 0x00010000 +#define CLOCK_CAP_SOURCE_AES2 0x00020000 +#define CLOCK_CAP_SOURCE_AES3 0x00040000 +#define CLOCK_CAP_SOURCE_AES4 0x00080000 +#define CLOCK_CAP_SOURCE_AES_ANY 0x00100000 +#define CLOCK_CAP_SOURCE_ADAT 0x00200000 +#define CLOCK_CAP_SOURCE_TDIF 0x00400000 +#define CLOCK_CAP_SOURCE_WC 0x00800000 +#define CLOCK_CAP_SOURCE_ARX1 0x01000000 +#define CLOCK_CAP_SOURCE_ARX2 0x02000000 +#define CLOCK_CAP_SOURCE_ARX3 0x04000000 +#define CLOCK_CAP_SOURCE_ARX4 0x08000000 +#define CLOCK_CAP_SOURCE_INTERNAL 0x10000000 + +/* + * Names of all clock sources; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. Unused clock sources are included. + */ +#define GLOBAL_CLOCK_SOURCE_NAMES 0x068 +#define CLOCK_SOURCE_NAMES_SIZE 256 + +/* + * Capture stream settings. This section includes the number/size registers + * and the registers of all streams. + */ + +/* + * The number of supported capture streams; read-only. + */ +#define TX_NUMBER 0x000 + +/* + * The size of one stream's register block, in quadlets; read-only. The + * registers of the first stream follow immediately afterwards; the registers + * of the following streams are offset by this register's value. + */ +#define TX_SIZE 0x004 + +/* + * The isochronous channel number on which packets are sent, or -1 if the + * stream is not to be used; read/write. + */ +#define TX_ISOCHRONOUS 0x008 + +/* + * The number of audio channels; read-only. There will be one quadlet per + * channel; the first channel is the first quadlet in a data block. + */ +#define TX_NUMBER_AUDIO 0x00c + +/* + * The number of MIDI ports, 0-8; read-only. If > 0, there will be one + * additional quadlet in each data block, following the audio quadlets. + */ +#define TX_NUMBER_MIDI 0x010 + +/* + * The speed at which the packets are sent, SCODE_100-_400; read/write. + * SCODE_800 is only available in Dice III. + */ +#define TX_SPEED 0x014 + +/* + * Names of all audio channels; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. + */ +#define TX_NAMES 0x018 +#define TX_NAMES_SIZE 256 + +/* + * Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio + * channel. + */ +#define TX_AC3_CAPABILITIES 0x118 + +/* + * Send audio data with IEC60958 label; read/write. Bitmask with one bit per + * audio channel. This register can be changed even while the stream is + * running. + */ +#define TX_AC3_ENABLE 0x11c + +/* + * Playback stream settings. This section includes the number/size registers + * and the registers of all streams. + */ + +/* + * The number of supported playback streams; read-only. + */ +#define RX_NUMBER 0x000 + +/* + * The size of one stream's register block, in quadlets; read-only. The + * registers of the first stream follow immediately afterwards; the registers + * of the following streams are offset by this register's value. + */ +#define RX_SIZE 0x004 + +/* + * The isochronous channel number on which packets are received, or -1 if the + * stream is not to be used; read/write. + */ +#define RX_ISOCHRONOUS 0x008 + +/* + * Index of first quadlet to be interpreted; read/write. If > 0, that many + * quadlets at the beginning of each data block will be ignored, and all the + * audio and MIDI quadlets will follow. + */ +#define RX_SEQ_START 0x00c + +/* + * The number of audio channels; read-only. There will be one quadlet per + * channel. + */ +#define RX_NUMBER_AUDIO 0x010 + +/* + * The number of MIDI ports, 0-8; read-only. If > 0, there will be one + * additional quadlet in each data block, following the audio quadlets. + */ +#define RX_NUMBER_MIDI 0x014 + +/* + * Names of all audio channels; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. + */ +#define RX_NAMES 0x018 +#define RX_NAMES_SIZE 256 + +/* + * Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio + * channel. + */ +#define RX_AC3_CAPABILITIES 0x118 + +/* + * Receive audio data with IEC60958 label; read/write. Bitmask with one bit + * per audio channel. This register can be changed even while the stream is + * running. + */ +#define RX_AC3_ENABLE 0x11c + +/* + * Extended synchronization information. + * This section can be read completely with a block read request. + */ + +/* + * Current clock source; read-only. + */ +#define EXT_SYNC_CLOCK_SOURCE 0x000 + +/* + * Clock source is locked (boolean); read-only. + */ +#define EXT_SYNC_LOCKED 0x004 + +/* + * Current sample rate (CLOCK_RATE_* >> CLOCK_RATE_SHIFT), _32000-_192000 or + * _NONE; read-only. + */ +#define EXT_SYNC_RATE 0x008 + +/* + * ADAT user data bits; read-only. + */ +#define EXT_SYNC_ADAT_USER_DATA 0x00c +/* The data bits, if available. */ +#define ADAT_USER_DATA_MASK 0x0f +/* The data bits are not available. */ +#define ADAT_USER_DATA_NO_DATA 0x10 + +#endif diff --git a/sound/firewire/dice/dice-midi.c b/sound/firewire/dice/dice-midi.c new file mode 100644 index 000000000..84eca8a51 --- /dev/null +++ b/sound/firewire/dice/dice-midi.c @@ -0,0 +1,157 @@ +/* + * dice_midi.c - a part of driver for Dice based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "dice.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + int err; + + err = snd_dice_stream_lock_try(dice); + if (err < 0) + return err; + + mutex_lock(&dice->mutex); + + dice->substreams_counter++; + err = snd_dice_stream_start_duplex(dice, 0); + + mutex_unlock(&dice->mutex); + + if (err < 0) + snd_dice_stream_lock_release(dice); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + + mutex_lock(&dice->mutex); + + dice->substreams_counter--; + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + snd_dice_stream_lock_release(dice); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dice *dice = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dice->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&dice->tx_stream[0], + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&dice->tx_stream[0], + substrm->number, NULL); + + spin_unlock_irqrestore(&dice->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dice *dice = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dice->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&dice->rx_stream[0], + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&dice->rx_stream[0], + substrm->number, NULL); + + spin_unlock_irqrestore(&dice->lock, flags); +} + +static void set_midi_substream_names(struct snd_dice *dice, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", dice->card->shortname, subs->number + 1); + } +} + +int snd_dice_create_midi(struct snd_dice *dice) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + unsigned int midi_in_ports, midi_out_ports; + int i; + int err; + + midi_in_ports = 0; + midi_out_ports = 0; + for (i = 0; i < MAX_STREAMS; ++i) { + midi_in_ports = max(midi_in_ports, dice->tx_midi_ports[i]); + midi_out_ports = max(midi_out_ports, dice->rx_midi_ports[i]); + } + + if (midi_in_ports + midi_out_ports == 0) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(dice->card, dice->card->driver, 0, + midi_out_ports, midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", dice->card->shortname); + rmidi->private_data = dice; + + if (midi_in_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(dice, str); + } + + if (midi_out_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(dice, str); + } + + if ((midi_out_ports > 0) && (midi_in_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/dice/dice-mytek.c b/sound/firewire/dice/dice-mytek.c new file mode 100644 index 000000000..eb7d5492d --- /dev/null +++ b/sound/firewire/dice/dice-mytek.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-mytek.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Melvin Vermeeren + */ + +#include "dice.h" + +struct dice_mytek_spec { + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; +}; + +static const struct dice_mytek_spec stereo_192_dsd_dac = { + /* AES, TOSLINK, SPDIF, ADAT inputs on device */ + .tx_pcm_chs = {{8, 8, 8}, {0, 0, 0} }, + /* PCM 44.1-192, native DSD64/DSD128 to device */ + .rx_pcm_chs = {{4, 4, 4}, {0, 0, 0} } +}; + +/* + * Mytek has a few other firewire-capable devices, though newer models appear + * to lack the port more often than not. As I don't have access to any of them + * they are missing here. An example is the Mytek 8x192 ADDA, which is DICE. + */ + +int snd_dice_detect_mytek_formats(struct snd_dice *dice) +{ + int i; + const struct dice_mytek_spec *dev; + + dev = &stereo_192_dsd_dac; + + memcpy(dice->tx_pcm_chs, dev->tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + memcpy(dice->rx_pcm_chs, dev->rx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + + for (i = 0; i < MAX_STREAMS; ++i) { + dice->tx_midi_ports[i] = 0; + dice->rx_midi_ports[i] = 0; + } + + return 0; +} diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c new file mode 100644 index 000000000..bb3ef5ff3 --- /dev/null +++ b/sound/firewire/dice/dice-pcm.c @@ -0,0 +1,459 @@ +/* + * dice_pcm.c - a part of driver for DICE based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "dice.h" + +static int dice_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data; + unsigned int index = substream->pcm->device; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int *pcm_channels; + enum snd_dice_rate_mode mode; + unsigned int i, rate; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int dice_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data; + unsigned int index = substream->pcm->device; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int *pcm_channels; + enum snd_dice_rate_mode mode; + unsigned int i, rate; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + + if (!snd_interval_test(r, rate)) + continue; + + channels.min = min(channels.min, pcm_channels[mode]); + channels.max = max(channels.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &channels); +} + +static int limit_channels_and_rates(struct snd_dice *dice, + struct snd_pcm_runtime *runtime, + enum amdtp_stream_direction dir, + unsigned int index) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int *pcm_channels; + unsigned int i; + + if (dir == AMDTP_IN_STREAM) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + enum snd_dice_rate_mode mode; + unsigned int rate, channels; + + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + + channels = pcm_channels[mode]; + if (channels == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); + } + + snd_pcm_limit_hw_rates(runtime); + + return 0; +} + +static int init_hw_info(struct snd_dice *dice, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int index = substream->pcm->device; + enum amdtp_stream_direction dir; + struct amdtp_stream *stream; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = AM824_IN_PCM_FORMAT_BITS; + dir = AMDTP_IN_STREAM; + stream = &dice->tx_stream[index]; + } else { + hw->formats = AM824_OUT_PCM_FORMAT_BITS; + dir = AMDTP_OUT_STREAM; + stream = &dice->rx_stream[index]; + } + + err = limit_channels_and_rates(dice, substream->runtime, dir, + index); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + dice_rate_constraint, substream, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dice_channels_constraint, substream, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_am824_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + unsigned int source; + bool internal; + int err; + + err = snd_dice_stream_lock_try(dice); + if (err < 0) + goto end; + + err = init_hw_info(dice, substream); + if (err < 0) + goto err_locked; + + err = snd_dice_transaction_get_clock_source(dice, &source); + if (err < 0) + goto err_locked; + switch (source) { + case CLOCK_SOURCE_AES1: + case CLOCK_SOURCE_AES2: + case CLOCK_SOURCE_AES3: + case CLOCK_SOURCE_AES4: + case CLOCK_SOURCE_AES_ANY: + case CLOCK_SOURCE_ADAT: + case CLOCK_SOURCE_TDIF: + case CLOCK_SOURCE_WC: + internal = false; + break; + default: + internal = true; + break; + } + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + if (!internal || + amdtp_stream_pcm_running(&dice->tx_stream[0]) || + amdtp_stream_pcm_running(&dice->tx_stream[1]) || + amdtp_stream_pcm_running(&dice->rx_stream[0]) || + amdtp_stream_pcm_running(&dice->rx_stream[1])) { + unsigned int rate; + + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_dice_stream_lock_release(dice); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + + snd_dice_stream_lock_release(dice); + + return 0; +} + +static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_dice *dice = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dice->mutex); + dice->substreams_counter++; + mutex_unlock(&dice->mutex); + } + + return 0; +} +static int playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_dice *dice = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dice->mutex); + dice->substreams_counter++; + mutex_unlock(&dice->mutex); + } + + return 0; +} + +static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + + mutex_lock(&dice->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dice->substreams_counter--; + + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + + mutex_lock(&dice->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dice->substreams_counter--; + + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + int err; + + mutex_lock(&dice->mutex); + err = snd_dice_stream_start_duplex(dice, substream->runtime->rate); + mutex_unlock(&dice->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + int err; + + mutex_lock(&dice->mutex); + err = snd_dice_stream_start_duplex(dice, substream->runtime->rate); + mutex_unlock(&dice->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + + return amdtp_stream_pcm_pointer(stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + + return amdtp_stream_pcm_pointer(stream); +} + +static int capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + + return amdtp_stream_pcm_ack(stream); +} + +static int playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + + return amdtp_stream_pcm_ack(stream); +} + +int snd_dice_create_pcm(struct snd_dice *dice) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .ack = capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_hw_params, + .hw_free = playback_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .ack = playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + unsigned int capture, playback; + int i, j; + int err; + + for (i = 0; i < MAX_STREAMS; i++) { + capture = playback = 0; + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) { + if (dice->tx_pcm_chs[i][j] > 0) + capture = 1; + if (dice->rx_pcm_chs[i][j] > 0) + playback = 1; + } + + err = snd_pcm_new(dice->card, "DICE", i, playback, capture, + &pcm); + if (err < 0) + return err; + pcm->private_data = dice; + strcpy(pcm->name, dice->card->shortname); + + if (capture > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &capture_ops); + + if (playback > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &playback_ops); + } + + return 0; +} diff --git a/sound/firewire/dice/dice-proc.c b/sound/firewire/dice/dice-proc.c new file mode 100644 index 000000000..bb870fc73 --- /dev/null +++ b/sound/firewire/dice/dice-proc.c @@ -0,0 +1,316 @@ +/* + * dice_proc.c - a part of driver for Dice based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "dice.h" + +static int dice_proc_read_mem(struct snd_dice *dice, void *buffer, + unsigned int offset_q, unsigned int quadlets) +{ + unsigned int i; + int err; + + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE + 4 * offset_q, + buffer, 4 * quadlets, 0); + if (err < 0) + return err; + + for (i = 0; i < quadlets; ++i) + be32_to_cpus(&((u32 *)buffer)[i]); + + return 0; +} + +static const char *str_from_array(const char *const strs[], unsigned int count, + unsigned int i) +{ + if (i < count) + return strs[i]; + + return "(unknown)"; +} + +static void dice_proc_fixup_string(char *s, unsigned int size) +{ + unsigned int i; + + for (i = 0; i < size; i += 4) + cpu_to_le32s((u32 *)(s + i)); + + for (i = 0; i < size - 2; ++i) { + if (s[i] == '\0') + return; + if (s[i] == '\\' && s[i + 1] == '\\') { + s[i + 2] = '\0'; + return; + } + } + s[size - 1] = '\0'; +} + +static void dice_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const section_names[5] = { + "global", "tx", "rx", "ext_sync", "unused2" + }; + static const char *const clock_sources[] = { + "aes1", "aes2", "aes3", "aes4", "aes", "adat", "tdif", + "wc", "arx1", "arx2", "arx3", "arx4", "internal" + }; + static const char *const rates[] = { + "32000", "44100", "48000", "88200", "96000", "176400", "192000", + "any low", "any mid", "any high", "none" + }; + struct snd_dice *dice = entry->private_data; + u32 sections[ARRAY_SIZE(section_names) * 2]; + struct { + u32 number; + u32 size; + } tx_rx_header; + union { + struct { + u32 owner_hi, owner_lo; + u32 notification; + char nick_name[NICK_NAME_SIZE]; + u32 clock_select; + u32 enable; + u32 status; + u32 extended_status; + u32 sample_rate; + u32 version; + u32 clock_caps; + char clock_source_names[CLOCK_SOURCE_NAMES_SIZE]; + } global; + struct { + u32 iso; + u32 number_audio; + u32 number_midi; + u32 speed; + char names[TX_NAMES_SIZE]; + u32 ac3_caps; + u32 ac3_enable; + } tx; + struct { + u32 iso; + u32 seq_start; + u32 number_audio; + u32 number_midi; + char names[RX_NAMES_SIZE]; + u32 ac3_caps; + u32 ac3_enable; + } rx; + struct { + u32 clock_source; + u32 locked; + u32 rate; + u32 adat_user_data; + } ext_sync; + } buf; + unsigned int quadlets, stream, i; + + if (dice_proc_read_mem(dice, sections, 0, ARRAY_SIZE(sections)) < 0) + return; + snd_iprintf(buffer, "sections:\n"); + for (i = 0; i < ARRAY_SIZE(section_names); ++i) + snd_iprintf(buffer, " %s: offset %u, size %u\n", + section_names[i], + sections[i * 2], sections[i * 2 + 1]); + + quadlets = min_t(u32, sections[1], sizeof(buf.global) / 4); + if (dice_proc_read_mem(dice, &buf.global, sections[0], quadlets) < 0) + return; + snd_iprintf(buffer, "global:\n"); + snd_iprintf(buffer, " owner: %04x:%04x%08x\n", + buf.global.owner_hi >> 16, + buf.global.owner_hi & 0xffff, buf.global.owner_lo); + snd_iprintf(buffer, " notification: %08x\n", buf.global.notification); + dice_proc_fixup_string(buf.global.nick_name, NICK_NAME_SIZE); + snd_iprintf(buffer, " nick name: %s\n", buf.global.nick_name); + snd_iprintf(buffer, " clock select: %s %s\n", + str_from_array(clock_sources, ARRAY_SIZE(clock_sources), + buf.global.clock_select & CLOCK_SOURCE_MASK), + str_from_array(rates, ARRAY_SIZE(rates), + (buf.global.clock_select & CLOCK_RATE_MASK) + >> CLOCK_RATE_SHIFT)); + snd_iprintf(buffer, " enable: %u\n", buf.global.enable); + snd_iprintf(buffer, " status: %slocked %s\n", + buf.global.status & STATUS_SOURCE_LOCKED ? "" : "un", + str_from_array(rates, ARRAY_SIZE(rates), + (buf.global.status & + STATUS_NOMINAL_RATE_MASK) + >> CLOCK_RATE_SHIFT)); + snd_iprintf(buffer, " ext status: %08x\n", buf.global.extended_status); + snd_iprintf(buffer, " sample rate: %u\n", buf.global.sample_rate); + if (quadlets >= 90) { + snd_iprintf(buffer, " version: %u.%u.%u.%u\n", + (buf.global.version >> 24) & 0xff, + (buf.global.version >> 16) & 0xff, + (buf.global.version >> 8) & 0xff, + (buf.global.version >> 0) & 0xff); + snd_iprintf(buffer, " clock caps:"); + for (i = 0; i <= 6; ++i) + if (buf.global.clock_caps & (1 << i)) + snd_iprintf(buffer, " %s", rates[i]); + for (i = 0; i <= 12; ++i) + if (buf.global.clock_caps & (1 << (16 + i))) + snd_iprintf(buffer, " %s", clock_sources[i]); + snd_iprintf(buffer, "\n"); + dice_proc_fixup_string(buf.global.clock_source_names, + CLOCK_SOURCE_NAMES_SIZE); + snd_iprintf(buffer, " clock source names: %s\n", + buf.global.clock_source_names); + } + + if (dice_proc_read_mem(dice, &tx_rx_header, sections[2], 2) < 0) + return; + quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.tx) / 4); + for (stream = 0; stream < tx_rx_header.number; ++stream) { + if (dice_proc_read_mem(dice, &buf.tx, sections[2] + 2 + + stream * tx_rx_header.size, + quadlets) < 0) + break; + snd_iprintf(buffer, "tx %u:\n", stream); + snd_iprintf(buffer, " iso channel: %d\n", (int)buf.tx.iso); + snd_iprintf(buffer, " audio channels: %u\n", + buf.tx.number_audio); + snd_iprintf(buffer, " midi ports: %u\n", buf.tx.number_midi); + snd_iprintf(buffer, " speed: S%u\n", 100u << buf.tx.speed); + if (quadlets >= 68) { + dice_proc_fixup_string(buf.tx.names, TX_NAMES_SIZE); + snd_iprintf(buffer, " names: %s\n", buf.tx.names); + } + if (quadlets >= 70) { + snd_iprintf(buffer, " ac3 caps: %08x\n", + buf.tx.ac3_caps); + snd_iprintf(buffer, " ac3 enable: %08x\n", + buf.tx.ac3_enable); + } + } + + if (dice_proc_read_mem(dice, &tx_rx_header, sections[4], 2) < 0) + return; + quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.rx) / 4); + for (stream = 0; stream < tx_rx_header.number; ++stream) { + if (dice_proc_read_mem(dice, &buf.rx, sections[4] + 2 + + stream * tx_rx_header.size, + quadlets) < 0) + break; + snd_iprintf(buffer, "rx %u:\n", stream); + snd_iprintf(buffer, " iso channel: %d\n", (int)buf.rx.iso); + snd_iprintf(buffer, " sequence start: %u\n", buf.rx.seq_start); + snd_iprintf(buffer, " audio channels: %u\n", + buf.rx.number_audio); + snd_iprintf(buffer, " midi ports: %u\n", buf.rx.number_midi); + if (quadlets >= 68) { + dice_proc_fixup_string(buf.rx.names, RX_NAMES_SIZE); + snd_iprintf(buffer, " names: %s\n", buf.rx.names); + } + if (quadlets >= 70) { + snd_iprintf(buffer, " ac3 caps: %08x\n", + buf.rx.ac3_caps); + snd_iprintf(buffer, " ac3 enable: %08x\n", + buf.rx.ac3_enable); + } + } + + quadlets = min_t(u32, sections[7], sizeof(buf.ext_sync) / 4); + if (quadlets >= 4) { + if (dice_proc_read_mem(dice, &buf.ext_sync, + sections[6], 4) < 0) + return; + snd_iprintf(buffer, "ext status:\n"); + snd_iprintf(buffer, " clock source: %s\n", + str_from_array(clock_sources, + ARRAY_SIZE(clock_sources), + buf.ext_sync.clock_source)); + snd_iprintf(buffer, " locked: %u\n", buf.ext_sync.locked); + snd_iprintf(buffer, " rate: %s\n", + str_from_array(rates, ARRAY_SIZE(rates), + buf.ext_sync.rate)); + snd_iprintf(buffer, " adat user data: "); + if (buf.ext_sync.adat_user_data & ADAT_USER_DATA_NO_DATA) + snd_iprintf(buffer, "-\n"); + else + snd_iprintf(buffer, "%x\n", + buf.ext_sync.adat_user_data); + } +} + +static void dice_proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const rate_labels[] = { + [SND_DICE_RATE_MODE_LOW] = "low", + [SND_DICE_RATE_MODE_MIDDLE] = "middle", + [SND_DICE_RATE_MODE_HIGH] = "high", + }; + struct snd_dice *dice = entry->private_data; + int i, j; + + snd_iprintf(buffer, "Output stream from unit:\n"); + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + snd_iprintf(buffer, "\t%s", rate_labels[i]); + snd_iprintf(buffer, "\tMIDI\n"); + for (i = 0; i < MAX_STREAMS; ++i) { + snd_iprintf(buffer, "Tx %u:", i); + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) + snd_iprintf(buffer, "\t%u", dice->tx_pcm_chs[i][j]); + snd_iprintf(buffer, "\t%u\n", dice->tx_midi_ports[i]); + } + + snd_iprintf(buffer, "Input stream to unit:\n"); + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + snd_iprintf(buffer, "\t%s", rate_labels[i]); + snd_iprintf(buffer, "\n"); + for (i = 0; i < MAX_STREAMS; ++i) { + snd_iprintf(buffer, "Rx %u:", i); + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) + snd_iprintf(buffer, "\t%u", dice->rx_pcm_chs[i][j]); + snd_iprintf(buffer, "\t%u\n", dice->rx_midi_ports[i]); + } +} + +static void add_node(struct snd_dice *dice, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *entry, + struct snd_info_buffer *buffer)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(dice->card, name, root); + if (!entry) + return; + + snd_info_set_text_ops(entry, dice, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_dice_create_proc(struct snd_dice *dice) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(dice->card, "firewire", + dice->card->proc_root); + if (!root) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(dice, root, "dice", dice_proc_read); + add_node(dice, root, "formation", dice_proc_read_formation); +} diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c new file mode 100644 index 000000000..c3c892c5c --- /dev/null +++ b/sound/firewire/dice/dice-stream.c @@ -0,0 +1,670 @@ +/* + * dice_stream.c - a part of driver for DICE based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "dice.h" + +#define CALLBACK_TIMEOUT 200 +#define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC) + +struct reg_params { + unsigned int count; + unsigned int size; +}; + +const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = { + /* mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* mode 1 */ + [3] = 88200, + [4] = 96000, + /* mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, + enum snd_dice_rate_mode *mode) +{ + /* Corresponding to each entry in snd_dice_rates. */ + static const enum snd_dice_rate_mode modes[] = { + [0] = SND_DICE_RATE_MODE_LOW, + [1] = SND_DICE_RATE_MODE_LOW, + [2] = SND_DICE_RATE_MODE_LOW, + [3] = SND_DICE_RATE_MODE_MIDDLE, + [4] = SND_DICE_RATE_MODE_MIDDLE, + [5] = SND_DICE_RATE_MODE_HIGH, + [6] = SND_DICE_RATE_MODE_HIGH, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) { + if (!(dice->clock_caps & BIT(i))) + continue; + if (snd_dice_rates[i] != rate) + continue; + + *mode = modes[i]; + return 0; + } + + return -EINVAL; +} + +/* + * This operation has an effect to synchronize GLOBAL_STATUS/GLOBAL_SAMPLE_RATE + * to GLOBAL_STATUS. Especially, just after powering on, these are different. + */ +static int ensure_phase_lock(struct snd_dice *dice, unsigned int rate) +{ + __be32 reg, nominal; + u32 data; + int i; + int err; + + err = snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, + ®, sizeof(reg)); + if (err < 0) + return err; + + data = be32_to_cpu(reg); + + data &= ~CLOCK_RATE_MASK; + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + if (snd_dice_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_dice_rates)) + return -EINVAL; + data |= i << CLOCK_RATE_SHIFT; + + if (completion_done(&dice->clock_accepted)) + reinit_completion(&dice->clock_accepted); + + reg = cpu_to_be32(data); + err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT, + ®, sizeof(reg)); + if (err < 0) + return err; + + if (wait_for_completion_timeout(&dice->clock_accepted, + msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) { + /* + * Old versions of Dice firmware transfer no notification when + * the same clock status as current one is set. In this case, + * just check current clock status. + */ + err = snd_dice_transaction_read_global(dice, GLOBAL_STATUS, + &nominal, sizeof(nominal)); + if (err < 0) + return err; + if (!(be32_to_cpu(nominal) & STATUS_SOURCE_LOCKED)) + return -ETIMEDOUT; + } + + return 0; +} + +static int get_register_params(struct snd_dice *dice, + struct reg_params *tx_params, + struct reg_params *rx_params) +{ + __be32 reg[2]; + int err; + + err = snd_dice_transaction_read_tx(dice, TX_NUMBER, reg, sizeof(reg)); + if (err < 0) + return err; + tx_params->count = + min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); + tx_params->size = be32_to_cpu(reg[1]) * 4; + + err = snd_dice_transaction_read_rx(dice, RX_NUMBER, reg, sizeof(reg)); + if (err < 0) + return err; + rx_params->count = + min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); + rx_params->size = be32_to_cpu(reg[1]) * 4; + + return 0; +} + +static void release_resources(struct snd_dice *dice) +{ + unsigned int i; + + for (i = 0; i < MAX_STREAMS; i++) { + if (amdtp_stream_running(&dice->tx_stream[i])) { + amdtp_stream_pcm_abort(&dice->tx_stream[i]); + amdtp_stream_stop(&dice->tx_stream[i]); + } + if (amdtp_stream_running(&dice->rx_stream[i])) { + amdtp_stream_pcm_abort(&dice->rx_stream[i]); + amdtp_stream_stop(&dice->rx_stream[i]); + } + + fw_iso_resources_free(&dice->tx_resources[i]); + fw_iso_resources_free(&dice->rx_resources[i]); + } +} + +static void stop_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, + struct reg_params *params) +{ + __be32 reg; + unsigned int i; + + for (i = 0; i < params->count; i++) { + reg = cpu_to_be32((u32)-1); + if (dir == AMDTP_IN_STREAM) { + snd_dice_transaction_write_tx(dice, + params->size * i + TX_ISOCHRONOUS, + ®, sizeof(reg)); + } else { + snd_dice_transaction_write_rx(dice, + params->size * i + RX_ISOCHRONOUS, + ®, sizeof(reg)); + } + } +} + +static int keep_resources(struct snd_dice *dice, + enum amdtp_stream_direction dir, unsigned int index, + unsigned int rate, unsigned int pcm_chs, + unsigned int midi_ports) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + bool double_pcm_frames; + unsigned int i; + int err; + + if (dir == AMDTP_IN_STREAM) { + stream = &dice->tx_stream[index]; + resources = &dice->tx_resources[index]; + } else { + stream = &dice->rx_stream[index]; + resources = &dice->rx_resources[index]; + } + + /* + * At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in + * one data block of AMDTP packet. Thus sampling transfer frequency is + * a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are + * transferred on AMDTP packets at 96 kHz. Two successive samples of a + * channel are stored consecutively in the packet. This quirk is called + * as 'Dual Wire'. + * For this quirk, blocking mode is required and PCM buffer size should + * be aligned to SYT_INTERVAL. + */ + double_pcm_frames = rate > 96000; + if (double_pcm_frames) { + rate /= 2; + pcm_chs *= 2; + } + + err = amdtp_am824_set_parameters(stream, rate, pcm_chs, midi_ports, + double_pcm_frames); + if (err < 0) + return err; + + if (double_pcm_frames) { + pcm_chs /= 2; + + for (i = 0; i < pcm_chs; i++) { + amdtp_am824_set_pcm_position(stream, i, i * 2); + amdtp_am824_set_pcm_position(stream, i + pcm_chs, + i * 2 + 1); + } + } + + return fw_iso_resources_allocate(resources, + amdtp_stream_get_max_payload(stream), + fw_parent_device(dice->unit)->max_speed); +} + +static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, + unsigned int rate, struct reg_params *params) +{ + __be32 reg[2]; + enum snd_dice_rate_mode mode; + unsigned int i, pcm_chs, midi_ports; + struct amdtp_stream *streams; + struct fw_iso_resources *resources; + struct fw_device *fw_dev = fw_parent_device(dice->unit); + int err = 0; + + if (dir == AMDTP_IN_STREAM) { + streams = dice->tx_stream; + resources = dice->tx_resources; + } else { + streams = dice->rx_stream; + resources = dice->rx_resources; + } + + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + + for (i = 0; i < params->count; i++) { + unsigned int pcm_cache; + unsigned int midi_cache; + + if (dir == AMDTP_IN_STREAM) { + pcm_cache = dice->tx_pcm_chs[i][mode]; + midi_cache = dice->tx_midi_ports[i]; + err = snd_dice_transaction_read_tx(dice, + params->size * i + TX_NUMBER_AUDIO, + reg, sizeof(reg)); + } else { + pcm_cache = dice->rx_pcm_chs[i][mode]; + midi_cache = dice->rx_midi_ports[i]; + err = snd_dice_transaction_read_rx(dice, + params->size * i + RX_NUMBER_AUDIO, + reg, sizeof(reg)); + } + if (err < 0) + return err; + pcm_chs = be32_to_cpu(reg[0]); + midi_ports = be32_to_cpu(reg[1]); + + /* These are important for developer of this driver. */ + if (pcm_chs != pcm_cache || midi_ports != midi_cache) { + dev_info(&dice->unit->device, + "cache mismatch: pcm: %u:%u, midi: %u:%u\n", + pcm_chs, pcm_cache, midi_ports, midi_cache); + return -EPROTO; + } + + err = keep_resources(dice, dir, i, rate, pcm_chs, midi_ports); + if (err < 0) + return err; + + reg[0] = cpu_to_be32(resources[i].channel); + if (dir == AMDTP_IN_STREAM) { + err = snd_dice_transaction_write_tx(dice, + params->size * i + TX_ISOCHRONOUS, + reg, sizeof(reg[0])); + } else { + err = snd_dice_transaction_write_rx(dice, + params->size * i + RX_ISOCHRONOUS, + reg, sizeof(reg[0])); + } + if (err < 0) + return err; + + if (dir == AMDTP_IN_STREAM) { + reg[0] = cpu_to_be32(fw_dev->max_speed); + err = snd_dice_transaction_write_tx(dice, + params->size * i + TX_SPEED, + reg, sizeof(reg[0])); + if (err < 0) + return err; + } + + err = amdtp_stream_start(&streams[i], resources[i].channel, + fw_dev->max_speed); + if (err < 0) + return err; + } + + return err; +} + +static int start_duplex_streams(struct snd_dice *dice, unsigned int rate) +{ + struct reg_params tx_params, rx_params; + int i; + int err; + + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + /* Stop transmission. */ + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); + snd_dice_transaction_clear_enable(dice); + release_resources(dice); + + err = ensure_phase_lock(dice, rate); + if (err < 0) { + dev_err(&dice->unit->device, "fail to ensure phase lock\n"); + return err; + } + + /* Likely to have changed stream formats. */ + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + /* Start both streams. */ + err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params); + if (err < 0) + goto error; + err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params); + if (err < 0) + goto error; + + err = snd_dice_transaction_set_enable(dice); + if (err < 0) { + dev_err(&dice->unit->device, "fail to enable interface\n"); + goto error; + } + + for (i = 0; i < MAX_STREAMS; i++) { + if ((i < tx_params.count && + !amdtp_stream_wait_callback(&dice->tx_stream[i], + CALLBACK_TIMEOUT)) || + (i < rx_params.count && + !amdtp_stream_wait_callback(&dice->rx_stream[i], + CALLBACK_TIMEOUT))) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); + snd_dice_transaction_clear_enable(dice); + release_resources(dice); + return err; +} + +/* + * MEMO: After this function, there're two states of streams: + * - None streams are running. + * - All streams are running. + */ +int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) +{ + unsigned int curr_rate; + unsigned int i; + enum snd_dice_rate_mode mode; + int err; + + if (dice->substreams_counter == 0) + return -EIO; + + /* Check sampling transmission frequency. */ + err = snd_dice_transaction_get_rate(dice, &curr_rate); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to get sampling rate\n"); + return err; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) + goto restart; + + /* Check error of packet streaming. */ + for (i = 0; i < MAX_STREAMS; ++i) { + if (amdtp_streaming_error(&dice->tx_stream[i])) + break; + if (amdtp_streaming_error(&dice->rx_stream[i])) + break; + } + if (i < MAX_STREAMS) + goto restart; + + /* Check required streams are running or not. */ + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + for (i = 0; i < MAX_STREAMS; ++i) { + if (dice->tx_pcm_chs[i][mode] > 0 && + !amdtp_stream_running(&dice->tx_stream[i])) + break; + if (dice->rx_pcm_chs[i][mode] > 0 && + !amdtp_stream_running(&dice->rx_stream[i])) + break; + } + if (i < MAX_STREAMS) + goto restart; + + return 0; +restart: + return start_duplex_streams(dice, rate); +} + +/* + * MEMO: After this function, there're two states of streams: + * - None streams are running. + * - All streams are running. + */ +void snd_dice_stream_stop_duplex(struct snd_dice *dice) +{ + struct reg_params tx_params, rx_params; + + if (dice->substreams_counter > 0) + return; + + snd_dice_transaction_clear_enable(dice); + + if (get_register_params(dice, &tx_params, &rx_params) == 0) { + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); + } + + release_resources(dice); +} + +static int init_stream(struct snd_dice *dice, enum amdtp_stream_direction dir, + unsigned int index) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + int err; + + if (dir == AMDTP_IN_STREAM) { + stream = &dice->tx_stream[index]; + resources = &dice->tx_resources[index]; + } else { + stream = &dice->rx_stream[index]; + resources = &dice->rx_resources[index]; + } + + err = fw_iso_resources_init(resources, dice->unit); + if (err < 0) + goto end; + resources->channels_mask = 0x00000000ffffffffuLL; + + err = amdtp_am824_init(stream, dice->unit, dir, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } +end: + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +static void destroy_stream(struct snd_dice *dice, + enum amdtp_stream_direction dir, + unsigned int index) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &dice->tx_stream[index]; + resources = &dice->tx_resources[index]; + } else { + stream = &dice->rx_stream[index]; + resources = &dice->rx_resources[index]; + } + + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); +} + +int snd_dice_stream_init_duplex(struct snd_dice *dice) +{ + int i, err; + + for (i = 0; i < MAX_STREAMS; i++) { + err = init_stream(dice, AMDTP_IN_STREAM, i); + if (err < 0) { + for (; i >= 0; i--) + destroy_stream(dice, AMDTP_IN_STREAM, i); + goto end; + } + } + + for (i = 0; i < MAX_STREAMS; i++) { + err = init_stream(dice, AMDTP_OUT_STREAM, i); + if (err < 0) { + for (; i >= 0; i--) + destroy_stream(dice, AMDTP_OUT_STREAM, i); + for (i = 0; i < MAX_STREAMS; i++) + destroy_stream(dice, AMDTP_IN_STREAM, i); + break; + } + } +end: + return err; +} + +void snd_dice_stream_destroy_duplex(struct snd_dice *dice) +{ + unsigned int i; + + for (i = 0; i < MAX_STREAMS; i++) { + destroy_stream(dice, AMDTP_IN_STREAM, i); + destroy_stream(dice, AMDTP_OUT_STREAM, i); + } +} + +void snd_dice_stream_update_duplex(struct snd_dice *dice) +{ + struct reg_params tx_params, rx_params; + + /* + * On a bus reset, the DICE firmware disables streaming and then goes + * off contemplating its own navel for hundreds of milliseconds before + * it can react to any of our attempts to reenable streaming. This + * means that we lose synchronization anyway, so we force our streams + * to stop so that the application can restart them in an orderly + * manner. + */ + dice->global_enabled = false; + + if (get_register_params(dice, &tx_params, &rx_params) == 0) { + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); + } +} + +int snd_dice_stream_detect_current_formats(struct snd_dice *dice) +{ + unsigned int rate; + enum snd_dice_rate_mode mode; + __be32 reg[2]; + struct reg_params tx_params, rx_params; + int i; + int err; + + /* If extended protocol is available, detect detail spec. */ + err = snd_dice_detect_extension_formats(dice); + if (err >= 0) + return err; + + /* + * Available stream format is restricted at current mode of sampling + * clock. + */ + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) + return err; + + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + + /* + * Just after owning the unit (GLOBAL_OWNER), the unit can return + * invalid stream formats. Selecting clock parameters have an effect + * for the unit to refine it. + */ + err = ensure_phase_lock(dice, rate); + if (err < 0) + return err; + + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + for (i = 0; i < tx_params.count; ++i) { + err = snd_dice_transaction_read_tx(dice, + tx_params.size * i + TX_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + dice->tx_pcm_chs[i][mode] = be32_to_cpu(reg[0]); + dice->tx_midi_ports[i] = max_t(unsigned int, + be32_to_cpu(reg[1]), dice->tx_midi_ports[i]); + } + for (i = 0; i < rx_params.count; ++i) { + err = snd_dice_transaction_read_rx(dice, + rx_params.size * i + RX_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + dice->rx_pcm_chs[i][mode] = be32_to_cpu(reg[0]); + dice->rx_midi_ports[i] = max_t(unsigned int, + be32_to_cpu(reg[1]), dice->rx_midi_ports[i]); + } + + return 0; +} + +static void dice_lock_changed(struct snd_dice *dice) +{ + dice->dev_lock_changed = true; + wake_up(&dice->hwdep_wait); +} + +int snd_dice_stream_lock_try(struct snd_dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (dice->dev_lock_count++ == 0) + dice_lock_changed(dice); + err = 0; +out: + spin_unlock_irq(&dice->lock); + return err; +} + +void snd_dice_stream_lock_release(struct snd_dice *dice) +{ + spin_lock_irq(&dice->lock); + + if (WARN_ON(dice->dev_lock_count <= 0)) + goto out; + + if (--dice->dev_lock_count == 0) + dice_lock_changed(dice); +out: + spin_unlock_irq(&dice->lock); +} diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c new file mode 100644 index 000000000..43a3bcb15 --- /dev/null +++ b/sound/firewire/dice/dice-tcelectronic.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-tc_electronic.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +struct dice_tc_spec { + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + bool has_midi; +}; + +static const struct dice_tc_spec desktop_konnekt6 = { + .tx_pcm_chs = {{6, 6, 2}, {0, 0, 0} }, + .rx_pcm_chs = {{6, 6, 4}, {0, 0, 0} }, + .has_midi = false, +}; + +static const struct dice_tc_spec impact_twin = { + .tx_pcm_chs = {{14, 10, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{14, 10, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_8 = { + .tx_pcm_chs = {{4, 4, 3}, {0, 0, 0} }, + .rx_pcm_chs = {{4, 4, 3}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_24d = { + .tx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_live = { + .tx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec studio_konnekt_48 = { + .tx_pcm_chs = {{16, 16, 8}, {16, 16, 7} }, + .rx_pcm_chs = {{16, 16, 8}, {14, 14, 7} }, + .has_midi = true, +}; + +static const struct dice_tc_spec digital_konnekt_x32 = { + .tx_pcm_chs = {{16, 16, 4}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 4}, {0, 0, 0} }, + .has_midi = false, +}; + +int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice) +{ + static const struct { + u32 model_id; + const struct dice_tc_spec *spec; + } *entry, entries[] = { + {0x00000020, &konnekt_24d}, + {0x00000021, &konnekt_8}, + {0x00000022, &studio_konnekt_48}, + {0x00000023, &konnekt_live}, + {0x00000024, &desktop_konnekt6}, + {0x00000027, &impact_twin}, + {0x00000030, &digital_konnekt_x32}, + }; + struct fw_csr_iterator it; + int key, val, model_id; + int i; + + model_id = 0; + fw_csr_iterator_init(&it, dice->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + if (key == CSR_MODEL) { + model_id = val; + break; + } + } + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = entries + i; + if (entry->model_id == model_id) + break; + } + if (i == ARRAY_SIZE(entries)) + return -ENODEV; + + memcpy(dice->tx_pcm_chs, entry->spec->tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + memcpy(dice->rx_pcm_chs, entry->spec->rx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + + if (entry->spec->has_midi) { + dice->tx_midi_ports[0] = 1; + dice->rx_midi_ports[0] = 1; + } + + return 0; +} diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c new file mode 100644 index 000000000..b7e138b5a --- /dev/null +++ b/sound/firewire/dice/dice-transaction.c @@ -0,0 +1,370 @@ +/* + * dice_transaction.c - a part of driver for Dice based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "dice.h" + +static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type, + u64 offset) +{ + switch (type) { + case SND_DICE_ADDR_TYPE_TX: + offset += dice->tx_offset; + break; + case SND_DICE_ADDR_TYPE_RX: + offset += dice->rx_offset; + break; + case SND_DICE_ADDR_TYPE_SYNC: + offset += dice->sync_offset; + break; + case SND_DICE_ADDR_TYPE_RSRV: + offset += dice->rsrv_offset; + break; + case SND_DICE_ADDR_TYPE_GLOBAL: + default: + offset += dice->global_offset; + break; + } + offset += DICE_PRIVATE_SPACE; + return offset; +} + +int snd_dice_transaction_write(struct snd_dice *dice, + enum snd_dice_addr_type type, + unsigned int offset, void *buf, unsigned int len) +{ + return snd_fw_transaction(dice->unit, + (len == 4) ? TCODE_WRITE_QUADLET_REQUEST : + TCODE_WRITE_BLOCK_REQUEST, + get_subaddr(dice, type, offset), buf, len, 0); +} + +int snd_dice_transaction_read(struct snd_dice *dice, + enum snd_dice_addr_type type, unsigned int offset, + void *buf, unsigned int len) +{ + return snd_fw_transaction(dice->unit, + (len == 4) ? TCODE_READ_QUADLET_REQUEST : + TCODE_READ_BLOCK_REQUEST, + get_subaddr(dice, type, offset), buf, len, 0); +} + +static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info) +{ + return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, + info, 4); +} + +int snd_dice_transaction_get_clock_source(struct snd_dice *dice, + unsigned int *source) +{ + __be32 info; + int err; + + err = get_clock_info(dice, &info); + if (err >= 0) + *source = be32_to_cpu(info) & CLOCK_SOURCE_MASK; + + return err; +} + +int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate) +{ + __be32 info; + unsigned int index; + int err; + + err = get_clock_info(dice, &info); + if (err < 0) + goto end; + + index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; + if (index >= SND_DICE_RATES_COUNT) { + err = -ENOSYS; + goto end; + } + + *rate = snd_dice_rates[index]; +end: + return err; +} + +int snd_dice_transaction_set_enable(struct snd_dice *dice) +{ + __be32 value; + int err = 0; + + if (dice->global_enabled) + goto end; + + value = cpu_to_be32(1); + err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_ENABLE), + &value, 4, + FW_FIXED_GENERATION | dice->owner_generation); + if (err < 0) + goto end; + + dice->global_enabled = true; +end: + return err; +} + +void snd_dice_transaction_clear_enable(struct snd_dice *dice) +{ + __be32 value; + + value = 0; + snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_ENABLE), + &value, 4, FW_QUIET | + FW_FIXED_GENERATION | dice->owner_generation); + + dice->global_enabled = false; +} + +static void dice_notification(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_dice *dice = callback_data; + u32 bits; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_TYPE_ERROR); + return; + } + if ((offset & 3) != 0) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + bits = be32_to_cpup(data); + + spin_lock_irqsave(&dice->lock, flags); + dice->notification_bits |= bits; + spin_unlock_irqrestore(&dice->lock, flags); + + fw_send_response(card, request, RCODE_COMPLETE); + + if (bits & NOTIFY_LOCK_CHG) + complete(&dice->clock_accepted); + wake_up(&dice->hwdep_wait); +} + +static int register_notification_address(struct snd_dice *dice, bool retry) +{ + struct fw_device *device = fw_parent_device(dice->unit); + __be64 *buffer; + unsigned int retries; + int err; + + retries = (retry) ? 3 : 0; + + buffer = kmalloc(2 * 8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + for (;;) { + buffer[0] = cpu_to_be64(OWNER_NO_OWNER); + buffer[1] = cpu_to_be64( + ((u64)device->card->node_id << OWNER_NODE_SHIFT) | + dice->notification_handler.offset); + + dice->owner_generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, + get_subaddr(dice, + SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_OWNER), + buffer, 2 * 8, + FW_FIXED_GENERATION | + dice->owner_generation); + if (err == 0) { + /* success */ + if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) + break; + /* The address seems to be already registered. */ + if (buffer[0] == buffer[1]) + break; + + dev_err(&dice->unit->device, + "device is already in use\n"); + err = -EBUSY; + } + if (err != -EAGAIN || retries-- > 0) + break; + + msleep(20); + } + + kfree(buffer); + + if (err < 0) + dice->owner_generation = -1; + + return err; +} + +static void unregister_notification_address(struct snd_dice *dice) +{ + struct fw_device *device = fw_parent_device(dice->unit); + __be64 *buffer; + + buffer = kmalloc(2 * 8, GFP_KERNEL); + if (buffer == NULL) + return; + + buffer[0] = cpu_to_be64( + ((u64)device->card->node_id << OWNER_NODE_SHIFT) | + dice->notification_handler.offset); + buffer[1] = cpu_to_be64(OWNER_NO_OWNER); + snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, + get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_OWNER), + buffer, 2 * 8, FW_QUIET | + FW_FIXED_GENERATION | dice->owner_generation); + + kfree(buffer); + + dice->owner_generation = -1; +} + +void snd_dice_transaction_destroy(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + + if (handler->callback_data == NULL) + return; + + unregister_notification_address(dice); + + fw_core_remove_address_handler(handler); + handler->callback_data = NULL; +} + +int snd_dice_transaction_reinit(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + + if (handler->callback_data == NULL) + return -EINVAL; + + return register_notification_address(dice, false); +} + +static int get_subaddrs(struct snd_dice *dice) +{ + static const int min_values[10] = { + 10, 0x60 / 4, + 10, 0x18 / 4, + 10, 0x18 / 4, + 0, 0, + 0, 0, + }; + __be32 *pointers; + __be32 version; + u32 data; + unsigned int i; + int err; + + pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32), + GFP_KERNEL); + if (pointers == NULL) + return -ENOMEM; + + /* + * Check that the sub address spaces exist and are located inside the + * private address space. The minimum values are chosen so that all + * minimally required registers are included. + */ + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE, pointers, + sizeof(__be32) * ARRAY_SIZE(min_values), 0); + if (err < 0) + goto end; + + for (i = 0; i < ARRAY_SIZE(min_values); ++i) { + data = be32_to_cpu(pointers[i]); + if (data < min_values[i] || data >= 0x40000) { + err = -ENODEV; + goto end; + } + } + + if (be32_to_cpu(pointers[1]) > 0x18) { + /* + * Check that the implemented DICE driver specification major + * version number matches. + */ + err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, + DICE_PRIVATE_SPACE + + be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, + &version, sizeof(version), 0); + if (err < 0) + goto end; + + if ((version & cpu_to_be32(0xff000000)) != + cpu_to_be32(0x01000000)) { + dev_err(&dice->unit->device, + "unknown DICE version: 0x%08x\n", + be32_to_cpu(version)); + err = -ENODEV; + goto end; + } + + /* Set up later. */ + dice->clock_caps = 1; + } + + dice->global_offset = be32_to_cpu(pointers[0]) * 4; + dice->tx_offset = be32_to_cpu(pointers[2]) * 4; + dice->rx_offset = be32_to_cpu(pointers[4]) * 4; + + /* Old firmware doesn't support these fields. */ + if (pointers[7]) + dice->sync_offset = be32_to_cpu(pointers[6]) * 4; + if (pointers[9]) + dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4; +end: + kfree(pointers); + return err; +} + +int snd_dice_transaction_init(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + int err; + + err = get_subaddrs(dice); + if (err < 0) + return err; + + /* Allocation callback in address space over host controller */ + handler->length = 4; + handler->address_callback = dice_notification; + handler->callback_data = dice; + err = fw_core_add_address_handler(handler, &fw_high_memory_region); + if (err < 0) { + handler->callback_data = NULL; + return err; + } + + /* Register the address space */ + err = register_notification_address(dice, true); + if (err < 0) { + fw_core_remove_address_handler(handler); + handler->callback_data = NULL; + } + + return err; +} diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c new file mode 100644 index 000000000..3d600f498 --- /dev/null +++ b/sound/firewire/dice/dice.c @@ -0,0 +1,426 @@ +/* + * TC Applied Technologies Digital Interface Communications Engine driver + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "dice.h" + +MODULE_DESCRIPTION("DICE driver"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); + +#define OUI_WEISS 0x001c6a +#define OUI_LOUD 0x000ff2 +#define OUI_FOCUSRITE 0x00130e +#define OUI_TCELECTRONIC 0x000166 +#define OUI_ALESIS 0x000595 +#define OUI_MAUDIO 0x000d6c +#define OUI_MYTEK 0x001ee8 +#define OUI_SSL 0x0050c2 // Actually ID reserved by IEEE. + +#define DICE_CATEGORY_ID 0x04 +#define WEISS_CATEGORY_ID 0x00 +#define LOUD_CATEGORY_ID 0x10 + +#define MODEL_ALESIS_IO_BOTH 0x000001 + +static int check_dice_category(struct fw_unit *unit) +{ + struct fw_device *device = fw_parent_device(unit); + struct fw_csr_iterator it; + int key, val, vendor = -1, model = -1; + unsigned int category; + + /* + * Check that GUID and unit directory are constructed according to DICE + * rules, i.e., that the specifier ID is the GUID's OUI, and that the + * GUID chip ID consists of the 8-bit category ID, the 10-bit product + * ID, and a 22-bit serial number. + */ + fw_csr_iterator_init(&it, unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_SPECIFIER_ID: + vendor = val; + break; + case CSR_MODEL: + model = val; + break; + } + } + + if (vendor == OUI_WEISS) + category = WEISS_CATEGORY_ID; + else if (vendor == OUI_LOUD) + category = LOUD_CATEGORY_ID; + else + category = DICE_CATEGORY_ID; + if (device->config_rom[3] != ((vendor << 8) | category) || + device->config_rom[4] >> 22 != model) + return -ENODEV; + + return 0; +} + +static int check_clock_caps(struct snd_dice *dice) +{ + __be32 value; + int err; + + /* some very old firmwares don't tell about their clock support */ + if (dice->clock_caps > 0) { + err = snd_dice_transaction_read_global(dice, + GLOBAL_CLOCK_CAPABILITIES, + &value, 4); + if (err < 0) + return err; + dice->clock_caps = be32_to_cpu(value); + } else { + /* this should be supported by any device */ + dice->clock_caps = CLOCK_CAP_RATE_44100 | + CLOCK_CAP_RATE_48000 | + CLOCK_CAP_SOURCE_ARX1 | + CLOCK_CAP_SOURCE_INTERNAL; + } + + return 0; +} + +static void dice_card_strings(struct snd_dice *dice) +{ + struct snd_card *card = dice->card; + struct fw_device *dev = fw_parent_device(dice->unit); + char vendor[32], model[32]; + unsigned int i; + int err; + + strcpy(card->driver, "DICE"); + + strcpy(card->shortname, "DICE"); + BUILD_BUG_ON(NICK_NAME_SIZE < sizeof(card->shortname)); + err = snd_dice_transaction_read_global(dice, GLOBAL_NICK_NAME, + card->shortname, + sizeof(card->shortname)); + if (err >= 0) { + /* DICE strings are returned in "always-wrong" endianness */ + BUILD_BUG_ON(sizeof(card->shortname) % 4 != 0); + for (i = 0; i < sizeof(card->shortname); i += 4) + swab32s((u32 *)&card->shortname[i]); + card->shortname[sizeof(card->shortname) - 1] = '\0'; + } + + strcpy(vendor, "?"); + fw_csr_string(dev->config_rom + 5, CSR_VENDOR, vendor, sizeof(vendor)); + strcpy(model, "?"); + fw_csr_string(dice->unit->directory, CSR_MODEL, model, sizeof(model)); + snprintf(card->longname, sizeof(card->longname), + "%s %s (serial %u) at %s, S%d", + vendor, model, dev->config_rom[4] & 0x3fffff, + dev_name(&dice->unit->device), 100 << dev->max_speed); + + strcpy(card->mixername, "DICE"); +} + +static void dice_free(struct snd_dice *dice) +{ + snd_dice_stream_destroy_duplex(dice); + snd_dice_transaction_destroy(dice); + fw_unit_put(dice->unit); + + mutex_destroy(&dice->mutex); + kfree(dice); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void dice_card_free(struct snd_card *card) +{ + dice_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_dice *dice = container_of(work, struct snd_dice, dwork.work); + int err; + + if (dice->registered) + return; + + err = snd_card_new(&dice->unit->device, -1, NULL, THIS_MODULE, 0, + &dice->card); + if (err < 0) + return; + + err = snd_dice_transaction_init(dice); + if (err < 0) + goto error; + + err = check_clock_caps(dice); + if (err < 0) + goto error; + + dice_card_strings(dice); + + err = dice->detect_formats(dice); + if (err < 0) + goto error; + + err = snd_dice_stream_init_duplex(dice); + if (err < 0) + goto error; + + snd_dice_create_proc(dice); + + err = snd_dice_create_pcm(dice); + if (err < 0) + goto error; + + err = snd_dice_create_midi(dice); + if (err < 0) + goto error; + + err = snd_dice_create_hwdep(dice); + if (err < 0) + goto error; + + err = snd_card_register(dice->card); + if (err < 0) + goto error; + + /* + * After registered, dice instance can be released corresponding to + * releasing the sound card instance. + */ + dice->card->private_free = dice_card_free; + dice->card->private_data = dice; + dice->registered = true; + + return; +error: + snd_dice_stream_destroy_duplex(dice); + snd_dice_transaction_destroy(dice); + snd_dice_stream_destroy_duplex(dice); + snd_card_free(dice->card); + dev_info(&dice->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int dice_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_dice *dice; + int err; + + if (!entry->driver_data && entry->vendor_id != OUI_SSL) { + err = check_dice_category(unit); + if (err < 0) + return -ENODEV; + } + + /* Allocate this independent of sound card instance. */ + dice = kzalloc(sizeof(struct snd_dice), GFP_KERNEL); + if (dice == NULL) + return -ENOMEM; + + dice->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, dice); + + if (!entry->driver_data) { + dice->detect_formats = snd_dice_stream_detect_current_formats; + } else { + dice->detect_formats = + (snd_dice_detect_formats_t)entry->driver_data; + } + + spin_lock_init(&dice->lock); + mutex_init(&dice->mutex); + init_completion(&dice->clock_accepted); + init_waitqueue_head(&dice->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&dice->dwork, do_registration); + snd_fw_schedule_registration(unit, &dice->dwork); + + return 0; +} + +static void dice_remove(struct fw_unit *unit) +{ + struct snd_dice *dice = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&dice->dwork); + + if (dice->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(dice->card); + } else { + /* Don't forget this case. */ + dice_free(dice); + } +} + +static void dice_bus_reset(struct fw_unit *unit) +{ + struct snd_dice *dice = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!dice->registered) + snd_fw_schedule_registration(unit, &dice->dwork); + + /* The handler address register becomes initialized. */ + snd_dice_transaction_reinit(dice); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (dice->registered) { + mutex_lock(&dice->mutex); + snd_dice_stream_update_duplex(dice); + mutex_unlock(&dice->mutex); + } +} + +#define DICE_INTERFACE 0x000001 + +static const struct ieee1394_device_id dice_id_table[] = { + /* M-Audio Profire 2626 has a different value in version field. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MAUDIO, + .model_id = 0x000010, + .driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats, + }, + /* M-Audio Profire 610 has a different value in version field. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MAUDIO, + .model_id = 0x000011, + .driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats, + }, + /* TC Electronic Konnekt 24D. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000020, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Konnekt 8. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000021, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Studio Konnekt 48. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000022, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Konnekt Live. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000023, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Desktop Konnekt 6. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000024, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Impact Twin. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000027, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Digital Konnekt x32. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000030, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* Alesis iO14/iO26. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_ALESIS, + .model_id = MODEL_ALESIS_IO_BOTH, + .driver_data = (kernel_ulong_t)snd_dice_detect_alesis_formats, + }, + /* Mytek Stereo 192 DSD-DAC. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MYTEK, + .model_id = 0x000002, + .driver_data = (kernel_ulong_t)snd_dice_detect_mytek_formats, + }, + // Solid State Logic, Duende Classic and Mini. + // NOTE: each field of GUID in config ROM is not compliant to standard + // DICE scheme. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_SSL, + .model_id = 0x000070, + }, + { + .match_flags = IEEE1394_MATCH_VERSION, + .version = DICE_INTERFACE, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, dice_id_table); + +static struct fw_driver dice_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = dice_probe, + .update = dice_bus_reset, + .remove = dice_remove, + .id_table = dice_id_table, +}; + +static int __init alsa_dice_init(void) +{ + return driver_register(&dice_driver.driver); +} + +static void __exit alsa_dice_exit(void) +{ + driver_unregister(&dice_driver.driver); +} + +module_init(alsa_dice_init); +module_exit(alsa_dice_exit); diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h new file mode 100644 index 000000000..83353a355 --- /dev/null +++ b/sound/firewire/dice/dice.h @@ -0,0 +1,231 @@ +/* + * dice.h - a part of driver for Dice based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_DICE_H_INCLUDED +#define SOUND_DICE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../amdtp-am824.h" +#include "../iso-resources.h" +#include "../lib.h" +#include "dice-interface.h" + +/* + * This module support maximum 2 pairs of tx/rx isochronous streams for + * our convinience. + * + * In documents for ASICs called with a name of 'DICE': + * - ASIC for DICE II: + * - Maximum 2 tx and 4 rx are supported. + * - A packet supports maximum 16 data channels. + * - TCD2210/2210-E (so-called 'Dice Mini'): + * - Maximum 2 tx and 2 rx are supported. + * - A packet supports maximum 16 data channels. + * - TCD2220/2220-E (so-called 'Dice Jr.') + * - 2 tx and 2 rx are supported. + * - A packet supports maximum 16 data channels. + * - TCD3070-CH (so-called 'Dice III') + * - Maximum 2 tx and 2 rx are supported. + * - A packet supports maximum 32 data channels. + * + * For the above, MIDI conformant data channel is just on the first isochronous + * stream. + */ +#define MAX_STREAMS 2 + +enum snd_dice_rate_mode { + SND_DICE_RATE_MODE_LOW = 0, + SND_DICE_RATE_MODE_MIDDLE, + SND_DICE_RATE_MODE_HIGH, + SND_DICE_RATE_MODE_COUNT, +}; + +struct snd_dice; +typedef int (*snd_dice_detect_formats_t)(struct snd_dice *dice); + +struct snd_dice { + struct snd_card *card; + struct fw_unit *unit; + spinlock_t lock; + struct mutex mutex; + + bool registered; + struct delayed_work dwork; + + /* Offsets for sub-addresses */ + unsigned int global_offset; + unsigned int rx_offset; + unsigned int tx_offset; + unsigned int sync_offset; + unsigned int rsrv_offset; + + unsigned int clock_caps; + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int tx_midi_ports[MAX_STREAMS]; + unsigned int rx_midi_ports[MAX_STREAMS]; + snd_dice_detect_formats_t detect_formats; + + struct fw_address_handler notification_handler; + int owner_generation; + u32 notification_bits; + + /* For uapi */ + int dev_lock_count; /* > 0 driver, < 0 userspace */ + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* For streaming */ + struct fw_iso_resources tx_resources[MAX_STREAMS]; + struct fw_iso_resources rx_resources[MAX_STREAMS]; + struct amdtp_stream tx_stream[MAX_STREAMS]; + struct amdtp_stream rx_stream[MAX_STREAMS]; + bool global_enabled; + struct completion clock_accepted; + unsigned int substreams_counter; +}; + +enum snd_dice_addr_type { + SND_DICE_ADDR_TYPE_PRIVATE, + SND_DICE_ADDR_TYPE_GLOBAL, + SND_DICE_ADDR_TYPE_TX, + SND_DICE_ADDR_TYPE_RX, + SND_DICE_ADDR_TYPE_SYNC, + SND_DICE_ADDR_TYPE_RSRV, +}; + +int snd_dice_transaction_write(struct snd_dice *dice, + enum snd_dice_addr_type type, + unsigned int offset, + void *buf, unsigned int len); +int snd_dice_transaction_read(struct snd_dice *dice, + enum snd_dice_addr_type type, unsigned int offset, + void *buf, unsigned int len); + +static inline int snd_dice_transaction_write_global(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, + SND_DICE_ADDR_TYPE_GLOBAL, offset, + buf, len); +} +static inline int snd_dice_transaction_read_global(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, + SND_DICE_ADDR_TYPE_GLOBAL, offset, + buf, len); +} +static inline int snd_dice_transaction_write_tx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_TX, offset, + buf, len); +} +static inline int snd_dice_transaction_read_tx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_TX, offset, + buf, len); +} +static inline int snd_dice_transaction_write_rx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_RX, offset, + buf, len); +} +static inline int snd_dice_transaction_read_rx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_RX, offset, + buf, len); +} +static inline int snd_dice_transaction_write_sync(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_SYNC, offset, + buf, len); +} +static inline int snd_dice_transaction_read_sync(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_SYNC, offset, + buf, len); +} + +int snd_dice_transaction_get_clock_source(struct snd_dice *dice, + unsigned int *source); +int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate); +int snd_dice_transaction_set_enable(struct snd_dice *dice); +void snd_dice_transaction_clear_enable(struct snd_dice *dice); +int snd_dice_transaction_init(struct snd_dice *dice); +int snd_dice_transaction_reinit(struct snd_dice *dice); +void snd_dice_transaction_destroy(struct snd_dice *dice); + +#define SND_DICE_RATES_COUNT 7 +extern const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT]; + +int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, + enum snd_dice_rate_mode *mode); +int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate); +void snd_dice_stream_stop_duplex(struct snd_dice *dice); +int snd_dice_stream_init_duplex(struct snd_dice *dice); +void snd_dice_stream_destroy_duplex(struct snd_dice *dice); +void snd_dice_stream_update_duplex(struct snd_dice *dice); +int snd_dice_stream_detect_current_formats(struct snd_dice *dice); + +int snd_dice_stream_lock_try(struct snd_dice *dice); +void snd_dice_stream_lock_release(struct snd_dice *dice); + +int snd_dice_create_pcm(struct snd_dice *dice); + +int snd_dice_create_hwdep(struct snd_dice *dice); + +void snd_dice_create_proc(struct snd_dice *dice); + +int snd_dice_create_midi(struct snd_dice *dice); + +int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice); +int snd_dice_detect_alesis_formats(struct snd_dice *dice); +int snd_dice_detect_extension_formats(struct snd_dice *dice); +int snd_dice_detect_mytek_formats(struct snd_dice *dice); + +#endif diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile new file mode 100644 index 000000000..1123e68c8 --- /dev/null +++ b/sound/firewire/digi00x/Makefile @@ -0,0 +1,4 @@ +snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \ + digi00x-pcm.o digi00x-hwdep.o \ + digi00x-transaction.o digi00x-midi.o digi00x.o +obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c new file mode 100644 index 000000000..4a884a335 --- /dev/null +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -0,0 +1,402 @@ +/* + * amdtp-dot.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * Copyright (C) 2012 Robin Gareus + * Copyright (C) 2012 Damien Zammit + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "digi00x.h" + +#define CIP_FMT_AM 0x10 + +/* 'Clock-based rate control mode' is just supported. */ +#define AMDTP_FDF_AM824 0x00 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +/* + * Several devices look only at the first eight data blocks. + * In any case, this is more than enough for the MIDI data rate. + */ +#define MAX_MIDI_RX_BLOCKS 8 + +/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */ +#define MAX_MIDI_PORTS 3 + +/* + * The double-oh-three algorithm was discovered by Robin Gareus and Damien + * Zammit in 2012, with reverse-engineering for Digi 003 Rack. + */ +struct dot_state { + u8 carry; + u8 idx; + unsigned int off; +}; + +struct amdtp_dot { + unsigned int pcm_channels; + struct dot_state state; + + struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS]; + int midi_fifo_used[MAX_MIDI_PORTS]; + int midi_fifo_limit; +}; + +/* + * double-oh-three look up table + * + * @param idx index byte (audio-sample data) 0x00..0xff + * @param off channel offset shift + * @return salt to XOR with given data + */ +#define BYTE_PER_SAMPLE (4) +#define MAGIC_DOT_BYTE (2) +#define MAGIC_BYTE_OFF(x) (((x) * BYTE_PER_SAMPLE) + MAGIC_DOT_BYTE) +static u8 dot_scrt(const u8 idx, const unsigned int off) +{ + /* + * the length of the added pattern only depends on the lower nibble + * of the last non-zero data + */ + static const u8 len[16] = {0, 1, 3, 5, 7, 9, 11, 13, 14, + 12, 10, 8, 6, 4, 2, 0}; + + /* + * the lower nibble of the salt. Interleaved sequence. + * this is walked backwards according to len[] + */ + static const u8 nib[15] = {0x8, 0x7, 0x9, 0x6, 0xa, 0x5, 0xb, 0x4, + 0xc, 0x3, 0xd, 0x2, 0xe, 0x1, 0xf}; + + /* circular list for the salt's hi nibble. */ + static const u8 hir[15] = {0x0, 0x6, 0xf, 0x8, 0x7, 0x5, 0x3, 0x4, + 0xc, 0xd, 0xe, 0x1, 0x2, 0xb, 0xa}; + + /* + * start offset for upper nibble mapping. + * note: 9 is /special/. In the case where the high nibble == 0x9, + * hir[] is not used and - coincidentally - the salt's hi nibble is + * 0x09 regardless of the offset. + */ + static const u8 hio[16] = {0, 11, 12, 6, 7, 5, 1, 4, + 3, 0x00, 14, 13, 8, 9, 10, 2}; + + const u8 ln = idx & 0xf; + const u8 hn = (idx >> 4) & 0xf; + const u8 hr = (hn == 0x9) ? 0x9 : hir[(hio[hn] + off) % 15]; + + if (len[ln] < off) + return 0x00; + + return ((nib[14 + off - len[ln]]) | (hr << 4)); +} + +static void dot_encode_step(struct dot_state *state, __be32 *const buffer) +{ + u8 * const data = (u8 *) buffer; + + if (data[MAGIC_DOT_BYTE] != 0x00) { + state->off = 0; + state->idx = data[MAGIC_DOT_BYTE] ^ state->carry; + } + data[MAGIC_DOT_BYTE] ^= state->carry; + state->carry = dot_scrt(state->idx, ++(state->off)); +} + +int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels) +{ + struct amdtp_dot *p = s->protocol; + int err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + /* + * A first data channel is for MIDI messages, the rest is Multi Bit + * Linear Audio data channel. + */ + err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1); + if (err < 0) + return err; + + s->fdf = AMDTP_FDF_AM824 | s->sfc; + + p->pcm_channels = pcm_channels; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + + return 0; +} + +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_dot *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + buffer++; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_be32((*src >> 8) | 0x40000000); + dot_encode_step(&p->state, &buffer[c]); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_dot *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + buffer++; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = be32_to_cpu(buffer[c]) << 8; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + buffer++; + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_dot *p = s->protocol; + int used; + + used = p->midi_fifo_used[port]; + if (used == 0) + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + p->midi_fifo_used[port] = used; + + return used < p->midi_fifo_limit; +} + +static inline void midi_use_bytes(struct amdtp_stream *s, + unsigned int port, unsigned int count) +{ + struct amdtp_dot *p = s->protocol; + + p->midi_fifo_used[port] += amdtp_rate_table[s->sfc] * count; +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int f, port; + int len; + u8 *b; + + for (f = 0; f < data_blocks; f++) { + port = (s->data_block_counter + f) % 8; + b = (u8 *)&buffer[0]; + + len = 0; + if (port < MAX_MIDI_PORTS && + midi_ratelimit_per_packet(s, port) && + p->midi[port] != NULL) + len = snd_rawmidi_transmit(p->midi[port], b + 1, 2); + + if (len > 0) { + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. + * - 0010b: physical MIDI port 2. + * - 1110b: console MIDI port. + */ + if (port == 2) + b[3] = 0xe0; + else if (port == 1) + b[3] = 0x20; + else + b[3] = 0x00; + b[3] |= len; + midi_use_bytes(s, port, len); + } else { + b[1] = 0; + b[2] = 0; + b[3] = 0; + } + b[0] = 0x80; + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int f, port, len; + u8 *b; + + for (f = 0; f < data_blocks; f++) { + b = (u8 *)&buffer[0]; + + len = b[3] & 0x0f; + if (len > 0) { + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. Use port 0. + * - 1110b: console MIDI port. Use port 2. + */ + if (b[3] >> 4 > 0) + port = 2; + else + port = 0; + + if (port < MAX_MIDI_PORTS && p->midi[port]) + snd_rawmidi_receive(p->midi[port], b + 1, len); + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* This protocol delivers 24 bit data in 32bit data channel. */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_dot *p = s->protocol; + + if (port < MAX_MIDI_PORTS) + WRITE_ONCE(p->midi[port], midi); +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = READ_ONCE(s->pcm); + if (pcm) { + read_pcm_s32(s, pcm, buffer, data_blocks); + pcm_frames = data_blocks; + } else { + pcm_frames = 0; + } + + read_midi_messages(s, buffer, data_blocks); + + return pcm_frames; +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = READ_ONCE(s->pcm); + if (pcm) { + write_pcm_s32(s, pcm, buffer, data_blocks); + pcm_frames = data_blocks; + } else { + write_pcm_silence(s, buffer, data_blocks); + pcm_frames = 0; + } + + write_midi_messages(s, buffer, data_blocks); + + return pcm_frames; +} + +int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + enum cip_flags flags; + + /* Use different mode between incoming/outgoing. */ + if (dir == AMDTP_IN_STREAM) { + flags = CIP_NONBLOCKING; + process_data_blocks = process_tx_data_blocks; + } else { + flags = CIP_BLOCKING; + process_data_blocks = process_rx_data_blocks; + } + + return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM, + process_data_blocks, sizeof(struct amdtp_dot)); +} + +void amdtp_dot_reset(struct amdtp_stream *s) +{ + struct amdtp_dot *p = s->protocol; + + p->state.carry = 0x00; + p->state.idx = 0x00; + p->state.off = 0; +} diff --git a/sound/firewire/digi00x/digi00x-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c new file mode 100644 index 000000000..426cd39e0 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-hwdep.c @@ -0,0 +1,199 @@ +/* + * digi00x-hwdep.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + * 4.get asynchronous messaging + */ + +#include "digi00x.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&dg00x->lock); + + while (!dg00x->dev_lock_changed && dg00x->msg == 0) { + prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&dg00x->lock); + schedule(); + finish_wait(&dg00x->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&dg00x->lock); + } + + memset(&event, 0, sizeof(event)); + if (dg00x->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (dg00x->dev_lock_count > 0); + dg00x->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.digi00x_message.type = + SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE; + event.digi00x_message.message = dg00x->msg; + dg00x->msg = 0; + + count = min_t(long, count, sizeof(event.digi00x_message)); + } + + spin_unlock_irq(&dg00x->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + __poll_t events; + + poll_wait(file, &dg00x->hwdep_wait, wait); + + spin_lock_irq(&dg00x->lock); + if (dg00x->dev_lock_changed || dg00x->msg) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&dg00x->lock); + + return events; +} + +static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(dg00x->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_DIGI00X; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + if (dg00x->dev_lock_count == 0) { + dg00x->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&dg00x->lock); + + return err; +} + +static int hwdep_unlock(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + if (dg00x->dev_lock_count == -1) { + dg00x->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&dg00x->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + + spin_lock_irq(&dg00x->lock); + if (dg00x->dev_lock_count == -1) + dg00x->dev_lock_count = 0; + spin_unlock_irq(&dg00x->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(dg00x, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(dg00x); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(dg00x); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(dg00x->card, "Digi00x", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Digi00x"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X; + hwdep->ops = ops; + hwdep->private_data = dg00x; + hwdep->exclusive = true; + + return err; +} diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c new file mode 100644 index 000000000..7ab3d0810 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -0,0 +1,169 @@ +/* + * digi00x-midi.h - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + int err; + + err = snd_dg00x_stream_lock_try(dg00x); + if (err < 0) + return err; + + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter++; + err = snd_dg00x_stream_start_duplex(dg00x, 0); + mutex_unlock(&dg00x->mutex); + if (err < 0) + snd_dg00x_stream_lock_release(dg00x); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter--; + snd_dg00x_stream_stop_duplex(dg00x); + mutex_unlock(&dg00x->mutex); + + snd_dg00x_stream_lock_release(dg00x); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; + unsigned long flags; + + if (substream->rmidi->device == 0) + port = substream->number; + else + port = 2; + + spin_lock_irqsave(&dg00x->lock, flags); + + if (up) + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream); + else + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL); + + spin_unlock_irqrestore(&dg00x->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; + unsigned long flags; + + if (substream->rmidi->device == 0) + port = substream->number; + else + port = 2; + + spin_lock_irqsave(&dg00x->lock, flags); + + if (up) + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream); + else + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL); + + spin_unlock_irqrestore(&dg00x->lock, flags); +} + +static void set_substream_names(struct snd_dg00x *dg00x, + struct snd_rawmidi *rmidi, bool is_console) +{ + struct snd_rawmidi_substream *subs; + struct snd_rawmidi_str *str; + int i; + + for (i = 0; i < 2; ++i) { + str = &rmidi->streams[i]; + + list_for_each_entry(subs, &str->substreams, list) { + if (!is_console) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + dg00x->card->shortname, + subs->number + 1); + } else { + snprintf(subs->name, sizeof(subs->name), + "%s control", + dg00x->card->shortname); + } + } + } +} + +static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports, + unsigned int in_ports, bool is_console) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + const char *label; + struct snd_rawmidi *rmidi; + int err; + + /* Add physical midi ports. */ + err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console, + out_ports, in_ports, &rmidi); + if (err < 0) + return err; + rmidi->private_data = dg00x; + + if (!is_console) + label = "%s control"; + else + label = "%s MIDI"; + snprintf(rmidi->name, sizeof(rmidi->name), label, + dg00x->card->shortname); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + set_substream_names(dg00x, rmidi, is_console); + + return 0; +} + +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +{ + int err; + + /* Add physical midi ports. */ + err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, + false); + if (err < 0) + return err; + + if (dg00x->is_console) + err = add_substream_pair(dg00x, 1, 1, true); + + return err; +} diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c new file mode 100644 index 000000000..fdcff0460 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-pcm.c @@ -0,0 +1,370 @@ +/* + * digi00x-pcm.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1, + }; + unsigned int i; + + for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { + if (!snd_interval_test(c, + snd_dg00x_stream_pcm_channels[i])) + continue; + + t.min = min(t.min, snd_dg00x_stream_rates[i]); + t.max = max(t.max, snd_dg00x_stream_rates[i]); + } + + return snd_interval_refine(r, &t); +} + +static int hw_rule_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); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1, + }; + unsigned int i; + + for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { + if (!snd_interval_test(r, snd_dg00x_stream_rates[i])) + continue; + + t.min = min(t.min, snd_dg00x_stream_pcm_channels[i]); + t.max = max(t.max, snd_dg00x_stream_pcm_channels[i]); + } + + return snd_interval_refine(c, &t); +} + +static int pcm_init_hw_params(struct snd_dg00x *dg00x, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *s; + int err; + + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &dg00x->tx_stream; + } else { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &dg00x->rx_stream; + } + + hw->channels_min = 10; + hw->channels_max = 18; + + hw->rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + snd_pcm_limit_hw_rates(runtime); + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return amdtp_dot_add_pcm_hw_constraints(s, substream->runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + enum snd_dg00x_clock clock; + bool detect; + unsigned int rate; + int err; + + err = snd_dg00x_stream_lock_try(dg00x); + if (err < 0) + goto end; + + err = pcm_init_hw_params(dg00x, substream); + if (err < 0) + goto err_locked; + + /* Check current clock source. */ + err = snd_dg00x_stream_get_clock(dg00x, &clock); + if (err < 0) + goto err_locked; + if (clock != SND_DG00X_CLOCK_INTERNAL) { + err = snd_dg00x_stream_check_external_clock(dg00x, &detect); + if (err < 0) + goto err_locked; + if (!detect) { + err = -EBUSY; + goto err_locked; + } + } + + if ((clock != SND_DG00X_CLOCK_INTERNAL) || + amdtp_stream_pcm_running(&dg00x->rx_stream) || + amdtp_stream_pcm_running(&dg00x->tx_stream)) { + err = snd_dg00x_stream_get_external_rate(dg00x, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_dg00x_stream_lock_release(dg00x); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + snd_dg00x_stream_lock_release(dg00x); + + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_dg00x *dg00x = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter++; + mutex_unlock(&dg00x->mutex); + } + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_dg00x *dg00x = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter++; + mutex_unlock(&dg00x->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + mutex_lock(&dg00x->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dg00x->substreams_counter--; + + snd_dg00x_stream_stop_duplex(dg00x); + + mutex_unlock(&dg00x->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + mutex_lock(&dg00x->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dg00x->substreams_counter--; + + snd_dg00x_stream_stop_duplex(dg00x); + + mutex_unlock(&dg00x->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&dg00x->mutex); + + err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&dg00x->tx_stream); + + mutex_unlock(&dg00x->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&dg00x->mutex); + + err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate); + if (err >= 0) { + amdtp_stream_pcm_prepare(&dg00x->rx_stream); + amdtp_dot_reset(&dg00x->rx_stream); + } + + mutex_unlock(&dg00x->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dg00x *dg00x = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&dg00x->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&dg00x->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dg00x *dg00x = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&dg00x->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&dg00x->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_dg00x *dg00x = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&dg00x->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_dg00x *dg00x = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&dg00x->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + return amdtp_stream_pcm_ack(&dg00x->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + return amdtp_stream_pcm_ack(&dg00x->rx_stream); +} + +int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(dg00x->card, dg00x->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = dg00x; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", dg00x->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + + return 0; +} diff --git a/sound/firewire/digi00x/digi00x-proc.c b/sound/firewire/digi00x/digi00x-proc.c new file mode 100644 index 000000000..6996d5a6f --- /dev/null +++ b/sound/firewire/digi00x/digi00x-proc.c @@ -0,0 +1,99 @@ +/* + * digi00x-proc.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +static int get_optical_iface_mode(struct snd_dg00x *dg00x, + enum snd_dg00x_optical_mode *mode) +{ + __be32 data; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_OPT_IFACE_MODE, + &data, sizeof(data), 0); + if (err >= 0) + *mode = be32_to_cpu(data) & 0x01; + + return err; +} + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buf) +{ + static const char *const source_name[] = { + [SND_DG00X_CLOCK_INTERNAL] = "internal", + [SND_DG00X_CLOCK_SPDIF] = "s/pdif", + [SND_DG00X_CLOCK_ADAT] = "adat", + [SND_DG00X_CLOCK_WORD] = "word clock", + }; + static const char *const optical_name[] = { + [SND_DG00X_OPT_IFACE_MODE_ADAT] = "adat", + [SND_DG00X_OPT_IFACE_MODE_SPDIF] = "s/pdif", + }; + struct snd_dg00x *dg00x = entry->private_data; + enum snd_dg00x_optical_mode mode; + unsigned int rate; + enum snd_dg00x_clock clock; + bool detect; + + if (get_optical_iface_mode(dg00x, &mode) < 0) + return; + if (snd_dg00x_stream_get_local_rate(dg00x, &rate) < 0) + return; + if (snd_dg00x_stream_get_clock(dg00x, &clock) < 0) + return; + + snd_iprintf(buf, "Optical mode: %s\n", optical_name[mode]); + snd_iprintf(buf, "Sampling Rate: %d\n", rate); + snd_iprintf(buf, "Clock Source: %s\n", source_name[clock]); + + if (clock == SND_DG00X_CLOCK_INTERNAL) + return; + + if (snd_dg00x_stream_check_external_clock(dg00x, &detect) < 0) + return; + snd_iprintf(buf, "External source: %s\n", detect ? "detected" : "not"); + if (!detect) + return; + + if (snd_dg00x_stream_get_external_rate(dg00x, &rate) >= 0) + snd_iprintf(buf, "External sampling rate: %d\n", rate); +} + +void snd_dg00x_proc_init(struct snd_dg00x *dg00x) +{ + struct snd_info_entry *root, *entry; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(dg00x->card, "firewire", + dg00x->card->proc_root); + if (root == NULL) + return; + + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + entry = snd_info_create_card_entry(dg00x->card, "clock", root); + if (entry == NULL) { + snd_info_free_entry(root); + return; + } + + snd_info_set_text_ops(entry, dg00x, proc_read_clock); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + snd_info_free_entry(root); + } +} diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c new file mode 100644 index 000000000..4d3b4ebbd --- /dev/null +++ b/sound/firewire/digi00x/digi00x-stream.c @@ -0,0 +1,422 @@ +/* + * digi00x-stream.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +#define CALLBACK_TIMEOUT 500 + +const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = { + [SND_DG00X_RATE_44100] = 44100, + [SND_DG00X_RATE_48000] = 48000, + [SND_DG00X_RATE_88200] = 88200, + [SND_DG00X_RATE_96000] = 96000, +}; + +/* Multi Bit Linear Audio data channels for each sampling transfer frequency. */ +const unsigned int +snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT] = { + /* Analog/ADAT/SPDIF */ + [SND_DG00X_RATE_44100] = (8 + 8 + 2), + [SND_DG00X_RATE_48000] = (8 + 8 + 2), + /* Analog/SPDIF */ + [SND_DG00X_RATE_88200] = (8 + 2), + [SND_DG00X_RATE_96000] = (8 + 2), +}; + +int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, unsigned int *rate) +{ + u32 data; + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = be32_to_cpu(reg) & 0x0f; + if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) + *rate = snd_dg00x_stream_rates[data]; + else + err = -EIO; + + return err; +} + +int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate) +{ + __be32 reg; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_dg00x_stream_rates); i++) { + if (rate == snd_dg00x_stream_rates[i]) + break; + } + if (i == ARRAY_SIZE(snd_dg00x_stream_rates)) + return -EINVAL; + + reg = cpu_to_be32(i); + return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, + ®, sizeof(reg), 0); +} + +int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x, + enum snd_dg00x_clock *clock) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_CLOCK_SOURCE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + *clock = be32_to_cpu(reg) & 0x0f; + if (*clock >= SND_DG00X_CLOCK_COUNT) + err = -EIO; + + return err; +} + +int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, bool *detect) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_DETECT_EXTERNAL, + ®, sizeof(reg), 0); + if (err >= 0) + *detect = be32_to_cpu(reg) > 0; + + return err; +} + +int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x, + unsigned int *rate) +{ + u32 data; + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_EXTERNAL_RATE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = be32_to_cpu(reg) & 0x0f; + if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) + *rate = snd_dg00x_stream_rates[data]; + /* This means desync. */ + else + err = -EBUSY; + + return err; +} + +static void finish_session(struct snd_dg00x *dg00x) +{ + __be32 data = cpu_to_be32(0x00000003); + + snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET, + &data, sizeof(data), 0); +} + +static int begin_session(struct snd_dg00x *dg00x) +{ + __be32 data; + u32 curr; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_STATE, + &data, sizeof(data), 0); + if (err < 0) + goto error; + curr = be32_to_cpu(data); + + if (curr == 0) + curr = 2; + + curr--; + while (curr > 0) { + data = cpu_to_be32(curr); + err = snd_fw_transaction(dg00x->unit, + TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + + DG00X_OFFSET_STREAMING_SET, + &data, sizeof(data), 0); + if (err < 0) + goto error; + + msleep(20); + curr--; + } + + return 0; +error: + finish_session(dg00x); + return err; +} + +static void release_resources(struct snd_dg00x *dg00x) +{ + __be32 data = 0; + + /* Unregister isochronous channels for both direction. */ + snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, + &data, sizeof(data), 0); + + /* Release isochronous resources. */ + fw_iso_resources_free(&dg00x->tx_resources); + fw_iso_resources_free(&dg00x->rx_resources); +} + +static int keep_resources(struct snd_dg00x *dg00x, unsigned int rate) +{ + unsigned int i; + __be32 data; + int err; + + /* Check sampling rate. */ + for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { + if (snd_dg00x_stream_rates[i] == rate) + break; + } + if (i == SND_DG00X_RATE_COUNT) + return -EINVAL; + + /* Keep resources for out-stream. */ + err = amdtp_dot_set_parameters(&dg00x->rx_stream, rate, + snd_dg00x_stream_pcm_channels[i]); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&dg00x->rx_resources, + amdtp_stream_get_max_payload(&dg00x->rx_stream), + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + return err; + + /* Keep resources for in-stream. */ + err = amdtp_dot_set_parameters(&dg00x->tx_stream, rate, + snd_dg00x_stream_pcm_channels[i]); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&dg00x->tx_resources, + amdtp_stream_get_max_payload(&dg00x->tx_stream), + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + goto error; + + /* Register isochronous channels for both direction. */ + data = cpu_to_be32((dg00x->tx_resources.channel << 16) | + dg00x->rx_resources.channel); + err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, + &data, sizeof(data), 0); + if (err < 0) + goto error; + + return 0; +error: + release_resources(dg00x); + return err; +} + +int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x) +{ + int err; + + /* For out-stream. */ + err = fw_iso_resources_init(&dg00x->rx_resources, dg00x->unit); + if (err < 0) + goto error; + err = amdtp_dot_init(&dg00x->rx_stream, dg00x->unit, AMDTP_OUT_STREAM); + if (err < 0) + goto error; + + /* For in-stream. */ + err = fw_iso_resources_init(&dg00x->tx_resources, dg00x->unit); + if (err < 0) + goto error; + err = amdtp_dot_init(&dg00x->tx_stream, dg00x->unit, AMDTP_IN_STREAM); + if (err < 0) + goto error; + + return 0; +error: + snd_dg00x_stream_destroy_duplex(dg00x); + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x) +{ + amdtp_stream_destroy(&dg00x->rx_stream); + fw_iso_resources_destroy(&dg00x->rx_resources); + + amdtp_stream_destroy(&dg00x->tx_stream); + fw_iso_resources_destroy(&dg00x->tx_resources); +} + +int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate) +{ + unsigned int curr_rate; + int err = 0; + + if (dg00x->substreams_counter == 0) + goto end; + + /* Check current sampling rate. */ + err = snd_dg00x_stream_get_local_rate(dg00x, &curr_rate); + if (err < 0) + goto error; + if (rate == 0) + rate = curr_rate; + if (curr_rate != rate || + amdtp_streaming_error(&dg00x->tx_stream) || + amdtp_streaming_error(&dg00x->rx_stream)) { + finish_session(dg00x); + + amdtp_stream_stop(&dg00x->tx_stream); + amdtp_stream_stop(&dg00x->rx_stream); + release_resources(dg00x); + } + + /* + * No packets are transmitted without receiving packets, reagardless of + * which source of clock is used. + */ + if (!amdtp_stream_running(&dg00x->rx_stream)) { + err = snd_dg00x_stream_set_local_rate(dg00x, rate); + if (err < 0) + goto error; + + err = keep_resources(dg00x, rate); + if (err < 0) + goto error; + + err = begin_session(dg00x); + if (err < 0) + goto error; + + err = amdtp_stream_start(&dg00x->rx_stream, + dg00x->rx_resources.channel, + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&dg00x->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + /* + * The value of SYT field in transmitted packets is always 0x0000. Thus, + * duplex streams with timestamp synchronization cannot be built. + */ + if (!amdtp_stream_running(&dg00x->tx_stream)) { + err = amdtp_stream_start(&dg00x->tx_stream, + dg00x->tx_resources.channel, + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&dg00x->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } +end: + return err; +error: + finish_session(dg00x); + + amdtp_stream_stop(&dg00x->tx_stream); + amdtp_stream_stop(&dg00x->rx_stream); + release_resources(dg00x); + + return err; +} + +void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x) +{ + if (dg00x->substreams_counter > 0) + return; + + amdtp_stream_stop(&dg00x->tx_stream); + amdtp_stream_stop(&dg00x->rx_stream); + finish_session(dg00x); + release_resources(dg00x); + + /* + * Just after finishing the session, the device may lost transmitting + * functionality for a short time. + */ + msleep(50); +} + +void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x) +{ + fw_iso_resources_update(&dg00x->tx_resources); + fw_iso_resources_update(&dg00x->rx_resources); + + amdtp_stream_update(&dg00x->tx_stream); + amdtp_stream_update(&dg00x->rx_stream); +} + +void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x) +{ + dg00x->dev_lock_changed = true; + wake_up(&dg00x->hwdep_wait); +} + +int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + /* user land lock this */ + if (dg00x->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (dg00x->dev_lock_count++ == 0) + snd_dg00x_stream_lock_changed(dg00x); + err = 0; +end: + spin_unlock_irq(&dg00x->lock); + return err; +} + +void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x) +{ + spin_lock_irq(&dg00x->lock); + + if (WARN_ON(dg00x->dev_lock_count <= 0)) + goto end; + if (--dg00x->dev_lock_count == 0) + snd_dg00x_stream_lock_changed(dg00x); +end: + spin_unlock_irq(&dg00x->lock); +} diff --git a/sound/firewire/digi00x/digi00x-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c new file mode 100644 index 000000000..af9bc8504 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-transaction.c @@ -0,0 +1,84 @@ +/* + * digi00x-transaction.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "digi00x.h" + +static void handle_unknown_message(struct snd_dg00x *dg00x, + unsigned long long offset, __be32 *buf) +{ + unsigned long flags; + + spin_lock_irqsave(&dg00x->lock, flags); + dg00x->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&dg00x->lock, flags); + + wake_up(&dg00x->hwdep_wait); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_dg00x *dg00x = callback_data; + __be32 *buf = (__be32 *)data; + + fw_send_response(card, request, RCODE_COMPLETE); + + if (offset == dg00x->async_handler.offset) + handle_unknown_message(dg00x, offset, buf); +} + +int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x) +{ + struct fw_device *device = fw_parent_device(dg00x->unit); + __be32 data[2]; + + /* Unknown. 4bytes. */ + data[0] = cpu_to_be32((device->card->node_id << 16) | + (dg00x->async_handler.offset >> 32)); + data[1] = cpu_to_be32(dg00x->async_handler.offset); + return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, + &data, sizeof(data), 0); +} + +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) +{ + if (dg00x->async_handler.callback_data == NULL) + return; + + fw_core_remove_address_handler(&dg00x->async_handler); + + dg00x->async_handler.callback_data = NULL; +} + +int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + dg00x->async_handler.length = 4; + dg00x->async_handler.address_callback = handle_message; + dg00x->async_handler.callback_data = dg00x; + + err = fw_core_add_address_handler(&dg00x->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_dg00x_transaction_reregister(dg00x); + if (err < 0) + snd_dg00x_transaction_unregister(dg00x); + + return err; +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c new file mode 100644 index 000000000..bf53e3427 --- /dev/null +++ b/sound/firewire/digi00x/digi00x.c @@ -0,0 +1,230 @@ +/* + * digi00x.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +MODULE_DESCRIPTION("Digidesign Digi 002/003 family Driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +#define VENDOR_DIGIDESIGN 0x00a07e +#define MODEL_CONSOLE 0x000001 +#define MODEL_RACK 0x000002 +#define SPEC_VERSION 0x000001 + +static int name_card(struct snd_dg00x *dg00x) +{ + struct fw_device *fw_dev = fw_parent_device(dg00x->unit); + char name[32] = {0}; + char *model; + int err; + + err = fw_csr_string(dg00x->unit->directory, CSR_MODEL, name, + sizeof(name)); + if (err < 0) + return err; + + model = skip_spaces(name); + + strcpy(dg00x->card->driver, "Digi00x"); + strcpy(dg00x->card->shortname, model); + strcpy(dg00x->card->mixername, model); + snprintf(dg00x->card->longname, sizeof(dg00x->card->longname), + "Digidesign %s, GUID %08x%08x at %s, S%d", model, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&dg00x->unit->device), 100 << fw_dev->max_speed); + + return 0; +} + +static void dg00x_free(struct snd_dg00x *dg00x) +{ + snd_dg00x_stream_destroy_duplex(dg00x); + snd_dg00x_transaction_unregister(dg00x); + + fw_unit_put(dg00x->unit); + + mutex_destroy(&dg00x->mutex); + kfree(dg00x); +} + +static void dg00x_card_free(struct snd_card *card) +{ + dg00x_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_dg00x *dg00x = + container_of(work, struct snd_dg00x, dwork.work); + int err; + + if (dg00x->registered) + return; + + err = snd_card_new(&dg00x->unit->device, -1, NULL, THIS_MODULE, 0, + &dg00x->card); + if (err < 0) + return; + + err = name_card(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_stream_init_duplex(dg00x); + if (err < 0) + goto error; + + snd_dg00x_proc_init(dg00x); + + err = snd_dg00x_create_pcm_devices(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_create_midi_devices(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_create_hwdep_device(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_transaction_register(dg00x); + if (err < 0) + goto error; + + err = snd_card_register(dg00x->card); + if (err < 0) + goto error; + + dg00x->card->private_free = dg00x_card_free; + dg00x->card->private_data = dg00x; + dg00x->registered = true; + + return; +error: + snd_dg00x_transaction_unregister(dg00x); + snd_dg00x_stream_destroy_duplex(dg00x); + snd_card_free(dg00x->card); + dev_info(&dg00x->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_dg00x_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_dg00x *dg00x; + + /* Allocate this independent of sound card instance. */ + dg00x = kzalloc(sizeof(struct snd_dg00x), GFP_KERNEL); + if (dg00x == NULL) + return -ENOMEM; + + dg00x->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, dg00x); + + mutex_init(&dg00x->mutex); + spin_lock_init(&dg00x->lock); + init_waitqueue_head(&dg00x->hwdep_wait); + + dg00x->is_console = entry->model_id == MODEL_CONSOLE; + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration); + snd_fw_schedule_registration(unit, &dg00x->dwork); + + return 0; +} + +static void snd_dg00x_update(struct fw_unit *unit) +{ + struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!dg00x->registered) + snd_fw_schedule_registration(unit, &dg00x->dwork); + + snd_dg00x_transaction_reregister(dg00x); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (dg00x->registered) { + mutex_lock(&dg00x->mutex); + snd_dg00x_stream_update_duplex(dg00x); + mutex_unlock(&dg00x->mutex); + } +} + +static void snd_dg00x_remove(struct fw_unit *unit) +{ + struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&dg00x->dwork); + + if (dg00x->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(dg00x->card); + } else { + /* Don't forget this case. */ + dg00x_free(dg00x); + } +} + +static const struct ieee1394_device_id snd_dg00x_id_table[] = { + /* Both of 002/003 use the same ID. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .version = SPEC_VERSION, + .model_id = MODEL_CONSOLE, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .version = SPEC_VERSION, + .model_id = MODEL_RACK, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_dg00x_id_table); + +static struct fw_driver dg00x_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-firewire-digi00x", + .bus = &fw_bus_type, + }, + .probe = snd_dg00x_probe, + .update = snd_dg00x_update, + .remove = snd_dg00x_remove, + .id_table = snd_dg00x_id_table, +}; + +static int __init snd_dg00x_init(void) +{ + return driver_register(&dg00x_driver.driver); +} + +static void __exit snd_dg00x_exit(void) +{ + driver_unregister(&dg00x_driver.driver); +} + +module_init(snd_dg00x_init); +module_exit(snd_dg00x_exit); diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h new file mode 100644 index 000000000..4dd1bbf2e --- /dev/null +++ b/sound/firewire/digi00x/digi00x.h @@ -0,0 +1,159 @@ +/* + * digi00x.h - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_DIGI00X_H_INCLUDED +#define SOUND_DIGI00X_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lib.h" +#include "../iso-resources.h" +#include "../amdtp-stream.h" + +struct snd_dg00x { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + struct amdtp_stream tx_stream; + struct fw_iso_resources tx_resources; + + struct amdtp_stream rx_stream; + struct fw_iso_resources rx_resources; + + unsigned int substreams_counter; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* For asynchronous messages. */ + struct fw_address_handler async_handler; + u32 msg; + + /* Console models have additional MIDI ports for control surface. */ + bool is_console; +}; + +#define DG00X_ADDR_BASE 0xffffe0000000ull + +#define DG00X_OFFSET_STREAMING_STATE 0x0000 +#define DG00X_OFFSET_STREAMING_SET 0x0004 +/* unknown but address in host space 0x0008 */ +/* For LSB of the address 0x000c */ +/* unknown 0x0010 */ +#define DG00X_OFFSET_MESSAGE_ADDR 0x0014 +/* For LSB of the address 0x0018 */ +/* unknown 0x001c */ +/* unknown 0x0020 */ +/* not used 0x0024--0x00ff */ +#define DG00X_OFFSET_ISOC_CHANNELS 0x0100 +/* unknown 0x0104 */ +/* unknown 0x0108 */ +/* unknown 0x010c */ +#define DG00X_OFFSET_LOCAL_RATE 0x0110 +#define DG00X_OFFSET_EXTERNAL_RATE 0x0114 +#define DG00X_OFFSET_CLOCK_SOURCE 0x0118 +#define DG00X_OFFSET_OPT_IFACE_MODE 0x011c +/* unknown 0x0120 */ +/* Mixer control on/off 0x0124 */ +/* unknown 0x0128 */ +#define DG00X_OFFSET_DETECT_EXTERNAL 0x012c +/* unknown 0x0138 */ +#define DG00X_OFFSET_MMC 0x0400 + +enum snd_dg00x_rate { + SND_DG00X_RATE_44100 = 0, + SND_DG00X_RATE_48000, + SND_DG00X_RATE_88200, + SND_DG00X_RATE_96000, + SND_DG00X_RATE_COUNT, +}; + +enum snd_dg00x_clock { + SND_DG00X_CLOCK_INTERNAL = 0, + SND_DG00X_CLOCK_SPDIF, + SND_DG00X_CLOCK_ADAT, + SND_DG00X_CLOCK_WORD, + SND_DG00X_CLOCK_COUNT, +}; + +enum snd_dg00x_optical_mode { + SND_DG00X_OPT_IFACE_MODE_ADAT = 0, + SND_DG00X_OPT_IFACE_MODE_SPDIF, + SND_DG00X_OPT_IFACE_MODE_COUNT, +}; + +#define DOT_MIDI_IN_PORTS 1 +#define DOT_MIDI_OUT_PORTS 2 + +int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); +int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels); +void amdtp_dot_reset(struct amdtp_stream *s); +int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int snd_dg00x_transaction_register(struct snd_dg00x *dg00x); +int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x); +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x); + +extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT]; +extern const unsigned int snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT]; +int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x, + unsigned int *rate); +int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, + unsigned int *rate); +int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate); +int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x, + enum snd_dg00x_clock *clock); +int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, + bool *detect); +int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x); +int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate); +void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x); +void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x); +void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x); + +void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x); +int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x); +void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x); + +void snd_dg00x_proc_init(struct snd_dg00x *dg00x); + +int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x); + +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x); + +int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x); +#endif diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c new file mode 100644 index 000000000..c8fbb5426 --- /dev/null +++ b/sound/firewire/fcp.c @@ -0,0 +1,401 @@ +/* + * Function Control Protocol (IEC 61883-1) helper functions + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fcp.h" +#include "lib.h" +#include "amdtp-stream.h" + +#define CTS_AVC 0x00 + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define FCP_TIMEOUT_MS 125 + +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + bool flag; + int err; + + flag = false; + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) { + flag = true; + break; + } + } + if (!flag) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used)*/ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_set_sig_fmt); + +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* Unit */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0xff; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-4] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* check sfc field and pick up rate */ + sfc = 0x07 & buf[5]; + if (sfc >= CIP_SFC_COUNT) { + err = -EAGAIN; /* also in transition */ + goto end; + } + + *rate = amdtp_rate_table[sfc]; + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_sig_fmt); + +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]) +{ + u8 *buf; + int err; + + /* extended subunit in spec.4.2 is not supported */ + if ((subunit_type == 0x1E) || (subunit_id == 5)) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + /* UNIT or Subunit, Functionblock */ + buf[1] = ((subunit_type & 0x1f) << 3) | (subunit_id & 0x7); + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xff & subfunction; + + err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + info[0] = buf[4]; + info[1] = buf[5]; + info[2] = buf[6]; + info[3] = buf[7]; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_plug_info); + +static DEFINE_SPINLOCK(transactions_lock); +static LIST_HEAD(transactions); + +enum fcp_state { + STATE_PENDING, + STATE_BUS_RESET, + STATE_COMPLETE, + STATE_DEFERRED, +}; + +struct fcp_transaction { + struct list_head list; + struct fw_unit *unit; + void *response_buffer; + unsigned int response_size; + unsigned int response_match_bytes; + enum fcp_state state; + wait_queue_head_t wait; + bool deferrable; +}; + +/** + * fcp_avc_transaction - send an AV/C command and wait for its response + * @unit: a unit on the target device + * @command: a buffer containing the command frame; must be DMA-able + * @command_size: the size of @command + * @response: a buffer for the response frame + * @response_size: the maximum size of @response + * @response_match_bytes: a bitmap specifying the bytes used to detect the + * correct response frame + * + * This function sends a FCP command frame to the target and waits for the + * corresponding response frame to be returned. + * + * Because it is possible for multiple FCP transactions to be active at the + * same time, the correct response frame is detected by the value of certain + * bytes. These bytes must be set in @response before calling this function, + * and the corresponding bits must be set in @response_match_bytes. + * + * @command and @response can point to the same buffer. + * + * Returns the actual size of the response frame, or a negative error code. + */ +int fcp_avc_transaction(struct fw_unit *unit, + const void *command, unsigned int command_size, + void *response, unsigned int response_size, + unsigned int response_match_bytes) +{ + struct fcp_transaction t; + int tcode, ret, tries = 0; + + t.unit = unit; + t.response_buffer = response; + t.response_size = response_size; + t.response_match_bytes = response_match_bytes; + t.state = STATE_PENDING; + init_waitqueue_head(&t.wait); + t.deferrable = (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03); + + spin_lock_irq(&transactions_lock); + list_add_tail(&t.list, &transactions); + spin_unlock_irq(&transactions_lock); + + for (;;) { + tcode = command_size == 4 ? TCODE_WRITE_QUADLET_REQUEST + : TCODE_WRITE_BLOCK_REQUEST; + ret = snd_fw_transaction(t.unit, tcode, + CSR_REGISTER_BASE + CSR_FCP_COMMAND, + (void *)command, command_size, 0); + if (ret < 0) + break; +deferred: + wait_event_timeout(t.wait, t.state != STATE_PENDING, + msecs_to_jiffies(FCP_TIMEOUT_MS)); + + if (t.state == STATE_DEFERRED) { + /* + * 'AV/C General Specification' define no time limit + * on command completion once an INTERIM response has + * been sent. but we promise to finish this function + * for a caller. Here we use FCP_TIMEOUT_MS for next + * interval. This is not in the specification. + */ + t.state = STATE_PENDING; + goto deferred; + } else if (t.state == STATE_COMPLETE) { + ret = t.response_size; + break; + } else if (t.state == STATE_BUS_RESET) { + msleep(ERROR_DELAY_MS); + } else if (++tries >= ERROR_RETRIES) { + dev_err(&t.unit->device, "FCP command timed out\n"); + ret = -EIO; + break; + } + } + + spin_lock_irq(&transactions_lock); + list_del(&t.list); + spin_unlock_irq(&transactions_lock); + + return ret; +} +EXPORT_SYMBOL(fcp_avc_transaction); + +/** + * fcp_bus_reset - inform the target handler about a bus reset + * @unit: the unit that might be used by fcp_avc_transaction() + * + * This function must be called from the driver's .update handler to inform + * the FCP transaction handler that a bus reset has happened. Any pending FCP + * transactions are retried. + */ +void fcp_bus_reset(struct fw_unit *unit) +{ + struct fcp_transaction *t; + + spin_lock_irq(&transactions_lock); + list_for_each_entry(t, &transactions, list) { + if (t->unit == unit && + (t->state == STATE_PENDING || + t->state == STATE_DEFERRED)) { + t->state = STATE_BUS_RESET; + wake_up(&t->wait); + } + } + spin_unlock_irq(&transactions_lock); +} +EXPORT_SYMBOL(fcp_bus_reset); + +/* checks whether the response matches the masked bytes in response_buffer */ +static bool is_matching_response(struct fcp_transaction *transaction, + const void *response, size_t length) +{ + const u8 *p1, *p2; + unsigned int mask, i; + + p1 = response; + p2 = transaction->response_buffer; + mask = transaction->response_match_bytes; + + for (i = 0; ; ++i) { + if ((mask & 1) && p1[i] != p2[i]) + return false; + mask >>= 1; + if (!mask) + return true; + if (--length == 0) + return false; + } +} + +static void fcp_response(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct fcp_transaction *t; + unsigned long flags; + + if (length < 1 || (*(const u8 *)data & 0xf0) != CTS_AVC) + return; + + spin_lock_irqsave(&transactions_lock, flags); + list_for_each_entry(t, &transactions, list) { + struct fw_device *device = fw_parent_device(t->unit); + if (device->card != card || + device->generation != generation) + continue; + smp_rmb(); /* node_id vs. generation */ + if (device->node_id != source) + continue; + + if (t->state == STATE_PENDING && + is_matching_response(t, data, length)) { + if (t->deferrable && *(const u8 *)data == 0x0f) { + t->state = STATE_DEFERRED; + } else { + t->state = STATE_COMPLETE; + t->response_size = min_t(unsigned int, length, + t->response_size); + memcpy(t->response_buffer, data, + t->response_size); + } + wake_up(&t->wait); + } + } + spin_unlock_irqrestore(&transactions_lock, flags); +} + +static struct fw_address_handler response_register_handler = { + .length = 0x200, + .address_callback = fcp_response, +}; + +static int __init fcp_module_init(void) +{ + static const struct fw_address_region response_register_region = { + .start = CSR_REGISTER_BASE + CSR_FCP_RESPONSE, + .end = CSR_REGISTER_BASE + CSR_FCP_END, + }; + + fw_core_add_address_handler(&response_register_handler, + &response_register_region); + + return 0; +} + +static void __exit fcp_module_exit(void) +{ + WARN_ON(!list_empty(&transactions)); + fw_core_remove_address_handler(&response_register_handler); +} + +module_init(fcp_module_init); +module_exit(fcp_module_exit); diff --git a/sound/firewire/fcp.h b/sound/firewire/fcp.h new file mode 100644 index 000000000..512f7c409 --- /dev/null +++ b/sound/firewire/fcp.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_FCP_H_INCLUDED +#define SOUND_FIREWIRE_FCP_H_INCLUDED + +#define AVC_PLUG_INFO_BUF_BYTES 4 + +struct fw_unit; + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +enum avc_general_plug_dir { + AVC_GENERAL_PLUG_DIR_IN = 0, + AVC_GENERAL_PLUG_DIR_OUT = 1, + AVC_GENERAL_PLUG_DIR_COUNT +}; +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]); + +int fcp_avc_transaction(struct fw_unit *unit, + const void *command, unsigned int command_size, + void *response, unsigned int response_size, + unsigned int response_match_bytes); +void fcp_bus_reset(struct fw_unit *unit); + +#endif diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile new file mode 100644 index 000000000..8f807284b --- /dev/null +++ b/sound/firewire/fireface/Makefile @@ -0,0 +1,3 @@ +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ + ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o +obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c new file mode 100644 index 000000000..77c7598b6 --- /dev/null +++ b/sound/firewire/fireface/amdtp-ff.c @@ -0,0 +1,155 @@ +/* + * amdtp-ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "ff.h" + +struct amdtp_ff { + unsigned int pcm_channels; +}; + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels) +{ + struct amdtp_ff *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + p->pcm_channels = pcm_channels; + data_channels = pcm_channels; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_le32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = le32_to_cpu(buffer[c]) & 0xffffff00; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + unsigned int i, c, channels = p->pcm_channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_le32(0x00000000); + buffer += s->data_block_quadlets; + } +} + +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + write_pcm_silence(s, (__le32 *)buffer, data_blocks); + pcm_frames = 0; + } + + return pcm_frames; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + pcm_frames = 0; + } + + return pcm_frames; +} + +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + + if (dir == AMDTP_IN_STREAM) + process_data_blocks = process_tx_data_blocks; + else + process_data_blocks = process_rx_data_blocks; + + return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0, + process_data_blocks, sizeof(struct amdtp_ff)); +} diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c new file mode 100644 index 000000000..336c0076e --- /dev/null +++ b/sound/firewire/fireface/ff-hwdep.c @@ -0,0 +1,191 @@ +/* + * ff-hwdep.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "ff.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_ff *ff = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&ff->lock); + + while (!ff->dev_lock_changed) { + prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&ff->lock); + schedule(); + finish_wait(&ff->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&ff->lock); + } + + memset(&event, 0, sizeof(event)); + if (ff->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (ff->dev_lock_count > 0); + ff->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&ff->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_ff *ff = hwdep->private_data; + __poll_t events; + + poll_wait(file, &ff->hwdep_wait, wait); + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&ff->lock); + + return events; +} + +static int hwdep_get_info(struct snd_ff *ff, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(ff->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREFACE; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == 0) { + ff->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_unlock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == -1) { + ff->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_ff *ff = hwdep->private_data; + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_count == -1) + ff->dev_lock_count = 0; + spin_unlock_irq(&ff->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_ff *ff = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(ff, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(ff); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(ff); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_ff_create_hwdep_devices(struct snd_ff *ff) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, ff->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE; + hwdep->ops = hwdep_ops; + hwdep->private_data = ff; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c new file mode 100644 index 000000000..6a49611ee --- /dev/null +++ b/sound/firewire/fireface/ff-midi.c @@ -0,0 +1,129 @@ +/* + * ff-midi.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + /* Initialize internal status. */ + ff->running_status[substream->number] = 0; + ff->rx_midi_error[substream->number] = false; + + WRITE_ONCE(ff->rx_midi_substreams[substream->number], substream); + + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + cancel_work_sync(&ff->rx_midi_work[substream->number]); + WRITE_ONCE(ff->rx_midi_substreams[substream->number], NULL); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up) + WRITE_ONCE(ff->tx_midi_substreams[substream->number], + substream); + else + WRITE_ONCE(ff->tx_midi_substreams[substream->number], NULL); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up || !ff->rx_midi_error[substream->number]) + schedule_work(&ff->rx_midi_work[substream->number]); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static void set_midi_substream_names(struct snd_rawmidi_str *stream, + const char *const name) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + snprintf(substream->name, sizeof(substream->name), + "%s MIDI %d", name, substream->number + 1); + } +} + +int snd_ff_create_midi_devices(struct snd_ff *ff) +{ + static const struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + int err; + + err = snd_rawmidi_new(ff->card, ff->card->driver, 0, + ff->spec->midi_out_ports, ff->spec->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", ff->card->shortname); + rmidi->private_data = ff; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c new file mode 100644 index 000000000..bf47f9ec8 --- /dev/null +++ b/sound/firewire/fireface/ff-pcm.c @@ -0,0 +1,401 @@ +/* + * ff-pcm.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static inline unsigned int get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, amdtp_rate_table[i]); + t.max = max(t.max, amdtp_rate_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, amdtp_rate_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, + const unsigned int *pcm_channels) +{ + unsigned int mode; + unsigned int rate, channels; + int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + + channels = pcm_channels[mode]; + if (pcm_channels[mode] == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); + + rate = amdtp_rate_table[i]; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->rate_min = min(hw->rate_min, rate); + hw->rate_max = max(hw->rate_max, rate); + } +} + +static int pcm_init_hw_params(struct snd_ff *ff, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + const unsigned int *pcm_channels; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->tx_stream; + pcm_channels = ff->spec->pcm_capture_channels; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->rx_stream; + pcm_channels = ff->spec->pcm_playback_channels; + } + + limit_channels_and_rates(&runtime->hw, pcm_channels); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return amdtp_ff_add_pcm_hw_constraints(s, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + unsigned int rate; + enum snd_ff_clock_src src; + int i, err; + + err = snd_ff_stream_lock_try(ff); + if (err < 0) + return err; + + err = pcm_init_hw_params(ff, substream); + if (err < 0) + goto release_lock; + + err = ff->spec->protocol->get_clock(ff, &rate, &src); + if (err < 0) + goto release_lock; + + if (src != SND_FF_CLOCK_SRC_INTERNAL) { + for (i = 0; i < CIP_SFC_COUNT; ++i) { + if (amdtp_rate_table[i] == rate) + break; + } + /* + * The unit is configured at sampling frequency which packet + * streaming engine can't support. + */ + if (i >= CIP_SFC_COUNT) { + err = -EIO; + goto release_lock; + } + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } else { + if (amdtp_stream_pcm_running(&ff->rx_stream) || + amdtp_stream_pcm_running(&ff->tx_stream)) { + rate = amdtp_rate_table[ff->rx_stream.sfc]; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + } + + snd_pcm_set_sync(substream); + + return 0; + +release_lock: + snd_ff_stream_lock_release(ff); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + snd_ff_stream_lock_release(ff); + + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->tx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->rx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + return amdtp_stream_pcm_ack(&ff->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + return amdtp_stream_pcm_ack(&ff->rx_stream); +} + +int snd_ff_create_pcm_devices(struct snd_ff *ff) +{ + static const struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = ff; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", ff->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + + return 0; +} diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c new file mode 100644 index 000000000..40ccbfd8e --- /dev/null +++ b/sound/firewire/fireface/ff-proc.c @@ -0,0 +1,63 @@ +/* + * ff-proc.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./ff.h" + +static void proc_dump_clock_config(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_clock_config(ff, buffer); +} + +static void proc_dump_sync_status(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_sync_status(ff, buffer); +} + +static void add_node(struct snd_ff *ff, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(ff->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, ff, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_ff_proc_init(struct snd_ff *ff) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(ff->card, "firewire", + ff->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(ff, root, "clock-config", proc_dump_clock_config); + add_node(ff, root, "sync-status", proc_dump_sync_status); +} diff --git a/sound/firewire/fireface/ff-protocol-ff400.c b/sound/firewire/fireface/ff-protocol-ff400.c new file mode 100644 index 000000000..4d191172f --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-ff400.c @@ -0,0 +1,374 @@ +/* + * ff-protocol-ff400.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "ff.h" + +#define FF400_STF 0x000080100500ull +#define FF400_RX_PACKET_FORMAT 0x000080100504ull +#define FF400_ISOC_COMM_START 0x000080100508ull +#define FF400_TX_PACKET_FORMAT 0x00008010050cull +#define FF400_ISOC_COMM_STOP 0x000080100510ull +#define FF400_SYNC_STATUS 0x0000801c0000ull +#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */ +#define FF400_CLOCK_CONFIG 0x0000801c0004ull + +#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull +#define FF400_MIDI_RX_PORT_0 0x000080180000ull +#define FF400_MIDI_RX_PORT_1 0x000080190000ull + +static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_CLOCK_CONFIG, ®, sizeof(reg), 0); + if (err < 0) + return err; + data = le32_to_cpu(reg); + + /* Calculate sampling rate. */ + switch ((data >> 1) & 0x03) { + case 0x01: + *rate = 32000; + break; + case 0x00: + *rate = 44100; + break; + case 0x03: + *rate = 48000; + break; + case 0x02: + default: + return -EIO; + } + + if (data & 0x08) + *rate *= 2; + else if (data & 0x10) + *rate *= 4; + + /* Calculate source of clock. */ + if (data & 0x01) { + *src = SND_FF_CLOCK_SRC_INTERNAL; + } else { + /* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */ + switch ((data >> 10) & 0x07) { + case 0x03: + *src = SND_FF_CLOCK_SRC_SPDIF; + break; + case 0x04: + *src = SND_FF_CLOCK_SRC_WORD; + break; + case 0x05: + *src = SND_FF_CLOCK_SRC_LTC; + break; + case 0x00: + default: + *src = SND_FF_CLOCK_SRC_ADAT; + break; + } + } + + return 0; +} + +static int ff400_begin_session(struct snd_ff *ff, unsigned int rate) +{ + __le32 reg; + int i, err; + + /* Check whether the given value is supported or not. */ + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + if (i == CIP_SFC_COUNT) + return -EINVAL; + + /* Set the number of data blocks transferred in a second. */ + reg = cpu_to_le32(rate); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_STF, ®, sizeof(reg), 0); + if (err < 0) + return err; + + msleep(100); + + /* + * Set isochronous channel and the number of quadlets of received + * packets. + */ + reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) | + ff->rx_resources.channel); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* + * Set isochronous channel and the number of quadlets of transmitted + * packet. + */ + /* TODO: investigate the purpose of this 0x80. */ + reg = cpu_to_le32((0x80 << 24) | + (ff->tx_resources.channel << 5) | + (ff->tx_stream.data_block_quadlets)); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Allow to transmit packets. */ + reg = cpu_to_le32(0x00000001); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_START, ®, sizeof(reg), 0); +} + +static void ff400_finish_session(struct snd_ff *ff) +{ + __le32 reg; + + reg = cpu_to_le32(0x80000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0); +} + +static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable) +{ + __le32 *reg; + int i; + int err; + + reg = kcalloc(18, sizeof(__le32), GFP_KERNEL); + if (reg == NULL) + return -ENOMEM; + + if (!enable) { + /* + * Each quadlet is corresponding to data channels in a data + * blocks in reverse order. Precisely, quadlets for available + * data channels should be enabled. Here, I take second best + * to fetch PCM frames from all of data channels regardless of + * stf. + */ + for (i = 0; i < 18; ++i) + reg[i] = cpu_to_le32(0x00000001); + } + + err = snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST, + FF400_FETCH_PCM_FRAMES, reg, + sizeof(__le32) * 18, 0); + kfree(reg); + return err; +} + +static void ff400_dump_sync_status(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "External source detection:\n"); + + snd_iprintf(buffer, "Word Clock:"); + if ((data >> 24) & 0x20) { + if ((data >> 24) & 0x40) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "S/PDIF:"); + if ((data >> 16) & 0x10) { + if ((data >> 16) & 0x04) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "ADAT:"); + if ((data >> 8) & 0x04) { + if ((data >> 8) & 0x10) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "\nUsed external source:\n"); + + if (((data >> 22) & 0x07) == 0x07) { + snd_iprintf(buffer, "None\n"); + } else { + switch ((data >> 22) & 0x07) { + case 0x00: + snd_iprintf(buffer, "ADAT:"); + break; + case 0x03: + snd_iprintf(buffer, "S/PDIF:"); + break; + case 0x04: + snd_iprintf(buffer, "Word:"); + break; + case 0x07: + snd_iprintf(buffer, "Nothing:"); + break; + case 0x01: + case 0x02: + case 0x05: + case 0x06: + default: + snd_iprintf(buffer, "unknown:"); + break; + } + + if ((data >> 25) & 0x07) { + switch ((data >> 25) & 0x07) { + case 0x01: + snd_iprintf(buffer, "32000\n"); + break; + case 0x02: + snd_iprintf(buffer, "44100\n"); + break; + case 0x03: + snd_iprintf(buffer, "48000\n"); + break; + case 0x04: + snd_iprintf(buffer, "64000\n"); + break; + case 0x05: + snd_iprintf(buffer, "88200\n"); + break; + case 0x06: + snd_iprintf(buffer, "96000\n"); + break; + case 0x07: + snd_iprintf(buffer, "128000\n"); + break; + case 0x08: + snd_iprintf(buffer, "176400\n"); + break; + case 0x09: + snd_iprintf(buffer, "192000\n"); + break; + case 0x00: + snd_iprintf(buffer, "unknown\n"); + break; + } + } + } + + snd_iprintf(buffer, "Multiplied:"); + snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250); +} + +static void ff400_dump_clock_config(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + unsigned int rate; + const char *src; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST, + FF400_CLOCK_CONFIG, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n", + (data & 0x20) ? "Professional" : "Consumer", + (data & 0x40) ? "on" : "off"); + + snd_iprintf(buffer, "Optical output interface format: %s\n", + ((data >> 8) & 0x01) ? "S/PDIF" : "ADAT"); + + snd_iprintf(buffer, "Word output single speed: %s\n", + ((data >> 8) & 0x20) ? "on" : "off"); + + snd_iprintf(buffer, "S/PDIF input interface: %s\n", + ((data >> 8) & 0x02) ? "Optical" : "Coaxial"); + + switch ((data >> 1) & 0x03) { + case 0x01: + rate = 32000; + break; + case 0x00: + rate = 44100; + break; + case 0x03: + rate = 48000; + break; + case 0x02: + default: + return; + } + + if (data & 0x08) + rate *= 2; + else if (data & 0x10) + rate *= 4; + + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (data & 0x01) { + src = "Internal"; + } else { + switch ((data >> 10) & 0x07) { + case 0x00: + src = "ADAT"; + break; + case 0x03: + src = "S/PDIF"; + break; + case 0x04: + src = "Word"; + break; + case 0x05: + src = "LTC"; + break; + default: + return; + } + } + + snd_iprintf(buffer, "Sync to clock source: %s\n", src); +} + +const struct snd_ff_protocol snd_ff_protocol_ff400 = { + .get_clock = ff400_get_clock, + .begin_session = ff400_begin_session, + .finish_session = ff400_finish_session, + .switch_fetching_mode = ff400_switch_fetching_mode, + + .dump_sync_status = ff400_dump_sync_status, + .dump_clock_config = ff400_dump_clock_config, + + .midi_high_addr_reg = FF400_MIDI_HIGH_ADDR, + .midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0, + .midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1, +}; diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c new file mode 100644 index 000000000..788809221 --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -0,0 +1,282 @@ +/* + * ff-stream.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define CALLBACK_TIMEOUT_MS 200 + +static int get_rate_mode(unsigned int rate, unsigned int *mode) +{ + int i; + + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + + if (i == CIP_SFC_COUNT) + return -EINVAL; + + *mode = ((int)i - 1) / 2; + + return 0; +} + +/* + * Fireface 400 manages isochronous channel number in 3 bit field. Therefore, + * we can allocate between 0 and 7 channel. + */ +static int keep_resources(struct snd_ff *ff, unsigned int rate) +{ + int mode; + int err; + + err = get_rate_mode(rate, &mode); + if (err < 0) + return err; + + /* Keep resources for in-stream. */ + err = amdtp_ff_set_parameters(&ff->tx_stream, rate, + ff->spec->pcm_capture_channels[mode]); + if (err < 0) + return err; + ff->tx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->tx_resources, + amdtp_stream_get_max_payload(&ff->tx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + return err; + + /* Keep resources for out-stream. */ + err = amdtp_ff_set_parameters(&ff->rx_stream, rate, + ff->spec->pcm_playback_channels[mode]); + if (err < 0) + return err; + ff->rx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->rx_resources, + amdtp_stream_get_max_payload(&ff->rx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + fw_iso_resources_free(&ff->tx_resources); + + return err; +} + +static void release_resources(struct snd_ff *ff) +{ + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); +} + +static inline void finish_session(struct snd_ff *ff) +{ + ff->spec->protocol->finish_session(ff); + ff->spec->protocol->switch_fetching_mode(ff, false); +} + +static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + int err; + struct fw_iso_resources *resources; + struct amdtp_stream *stream; + + if (dir == AMDTP_IN_STREAM) { + resources = &ff->tx_resources; + stream = &ff->tx_stream; + } else { + resources = &ff->rx_resources; + stream = &ff->rx_stream; + } + + err = fw_iso_resources_init(resources, ff->unit); + if (err < 0) + return err; + + err = amdtp_ff_init(stream, ff->unit, dir); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + if (dir == AMDTP_IN_STREAM) { + amdtp_stream_destroy(&ff->tx_stream); + fw_iso_resources_destroy(&ff->tx_resources); + } else { + amdtp_stream_destroy(&ff->rx_stream); + fw_iso_resources_destroy(&ff->rx_resources); + } +} + +int snd_ff_stream_init_duplex(struct snd_ff *ff) +{ + int err; + + err = init_stream(ff, AMDTP_OUT_STREAM); + if (err < 0) + goto end; + + err = init_stream(ff, AMDTP_IN_STREAM); + if (err < 0) + destroy_stream(ff, AMDTP_OUT_STREAM); +end: + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_ff_stream_destroy_duplex(struct snd_ff *ff) +{ + destroy_stream(ff, AMDTP_IN_STREAM); + destroy_stream(ff, AMDTP_OUT_STREAM); +} + +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) +{ + unsigned int curr_rate; + enum snd_ff_clock_src src; + int err; + + if (ff->substreams_counter == 0) + return 0; + + err = ff->spec->protocol->get_clock(ff, &curr_rate, &src); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&ff->tx_stream) || + amdtp_streaming_error(&ff->rx_stream)) { + finish_session(ff); + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + release_resources(ff); + } + + /* + * Regardless of current source of clock signal, drivers transfer some + * packets. Then, the device transfers packets. + */ + if (!amdtp_stream_running(&ff->rx_stream)) { + err = keep_resources(ff, rate); + if (err < 0) + goto error; + + err = ff->spec->protocol->begin_session(ff, rate); + if (err < 0) + goto error; + + err = amdtp_stream_start(&ff->rx_stream, + ff->rx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->rx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + + err = ff->spec->protocol->switch_fetching_mode(ff, true); + if (err < 0) + goto error; + } + + if (!amdtp_stream_running(&ff->tx_stream)) { + err = amdtp_stream_start(&ff->tx_stream, + ff->tx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->tx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + finish_session(ff); + release_resources(ff); + + return err; +} + +void snd_ff_stream_stop_duplex(struct snd_ff *ff) +{ + if (ff->substreams_counter > 0) + return; + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + finish_session(ff); + release_resources(ff); +} + +void snd_ff_stream_update_duplex(struct snd_ff *ff) +{ + /* The device discontinue to transfer packets. */ + amdtp_stream_pcm_abort(&ff->tx_stream); + amdtp_stream_stop(&ff->tx_stream); + + amdtp_stream_pcm_abort(&ff->rx_stream); + amdtp_stream_stop(&ff->rx_stream); + + fw_iso_resources_update(&ff->tx_resources); + fw_iso_resources_update(&ff->rx_resources); +} + +void snd_ff_stream_lock_changed(struct snd_ff *ff) +{ + ff->dev_lock_changed = true; + wake_up(&ff->hwdep_wait); +} + +int snd_ff_stream_lock_try(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + /* user land lock this */ + if (ff->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (ff->dev_lock_count++ == 0) + snd_ff_stream_lock_changed(ff); + err = 0; +end: + spin_unlock_irq(&ff->lock); + return err; +} + +void snd_ff_stream_lock_release(struct snd_ff *ff) +{ + spin_lock_irq(&ff->lock); + + if (WARN_ON(ff->dev_lock_count <= 0)) + goto end; + if (--ff->dev_lock_count == 0) + snd_ff_stream_lock_changed(ff); +end: + spin_unlock_irq(&ff->lock); +} diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c new file mode 100644 index 000000000..47280ae50 --- /dev/null +++ b/sound/firewire/fireface/ff-transaction.c @@ -0,0 +1,295 @@ +/* + * ff-transaction.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port, + int rcode) +{ + struct snd_rawmidi_substream *substream = + READ_ONCE(ff->rx_midi_substreams[port]); + + if (rcode_is_permanent_error(rcode)) { + ff->rx_midi_error[port] = true; + return; + } + + if (rcode != RCODE_COMPLETE) { + /* Transfer the message again, immediately. */ + ff->next_ktime[port] = 0; + schedule_work(&ff->rx_midi_work[port]); + return; + } + + snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]); + ff->rx_bytes[port] = 0; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&ff->rx_midi_work[port]); +} + +static void finish_transmit_midi0_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[0]); + finish_transmit_midi_msg(ff, 0, rcode); +} + +static void finish_transmit_midi1_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[1]); + finish_transmit_midi_msg(ff, 1, rcode); +} + +static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port, + unsigned int index, u8 byte) +{ + ff->msg_buf[port][index] = cpu_to_le32(byte); +} + +static void transmit_midi_msg(struct snd_ff *ff, unsigned int port) +{ + struct snd_rawmidi_substream *substream = + READ_ONCE(ff->rx_midi_substreams[port]); + u8 *buf = (u8 *)ff->msg_buf[port]; + int i, len; + + struct fw_device *fw_dev = fw_parent_device(ff->unit); + unsigned long long addr; + int generation; + fw_transaction_callback_t callback; + + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port]) + return; + + /* Do it in next chance. */ + if (ktime_after(ff->next_ktime[port], ktime_get())) { + schedule_work(&ff->rx_midi_work[port]); + return; + } + + len = snd_rawmidi_transmit_peek(substream, buf, + SND_FF_MAXIMIM_MIDI_QUADS); + if (len <= 0) + return; + + for (i = len - 1; i >= 0; i--) + fill_midi_buf(ff, port, i, buf[i]); + + if (port == 0) { + addr = ff->spec->protocol->midi_rx_port_0_reg; + callback = finish_transmit_midi0_msg; + } else { + addr = ff->spec->protocol->midi_rx_port_1_reg; + callback = finish_transmit_midi1_msg; + } + + /* Set interval to next transaction. */ + ff->next_ktime[port] = ktime_add_ns(ktime_get(), + len * 8 * (NSEC_PER_SEC / 31250)); + ff->rx_bytes[port] = len; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = fw_dev->generation; + smp_rmb(); + fw_send_request(fw_dev->card, &ff->transactions[port], + TCODE_WRITE_BLOCK_REQUEST, + fw_dev->node_id, generation, fw_dev->max_speed, + addr, &ff->msg_buf[port], len * 4, + callback, &ff->transactions[port]); +} + +static void transmit_midi0_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]); + + transmit_midi_msg(ff, 0); +} + +static void transmit_midi1_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]); + + transmit_midi_msg(ff, 1); +} + +static void handle_midi_msg(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_ff *ff = callback_data; + __le32 *buf = data; + u32 quad; + u8 byte; + unsigned int index; + struct snd_rawmidi_substream *substream; + int i; + + fw_send_response(card, request, RCODE_COMPLETE); + + for (i = 0; i < length / 4; i++) { + quad = le32_to_cpu(buf[i]); + + /* Message in first port. */ + /* + * This value may represent the index of this unit when the same + * units are on the same IEEE 1394 bus. This driver doesn't use + * it. + */ + index = (quad >> 8) & 0xff; + if (index > 0) { + substream = READ_ONCE(ff->tx_midi_substreams[0]); + if (substream != NULL) { + byte = quad & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + + /* Message in second port. */ + index = (quad >> 24) & 0xff; + if (index > 0) { + substream = READ_ONCE(ff->tx_midi_substreams[1]); + if (substream != NULL) { + byte = (quad >> 16) & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + } +} + +static int allocate_own_address(struct snd_ff *ff, int i) +{ + struct fw_address_region midi_msg_region; + int err; + + ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4; + ff->async_handler.address_callback = handle_midi_msg; + ff->async_handler.callback_data = ff; + + midi_msg_region.start = 0x000100000000ull * i; + midi_msg_region.end = midi_msg_region.start + ff->async_handler.length; + + err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region); + if (err >= 0) { + /* Controllers are allowed to register this region. */ + if (ff->async_handler.offset & 0x0000ffffffff) { + fw_core_remove_address_handler(&ff->async_handler); + err = -EAGAIN; + } + } + + return err; +} + +/* + * The configuration to start asynchronous transactions for MIDI messages is in + * 0x'0000'8010'051c. This register includes the other options, thus this driver + * doesn't touch it and leaves the decision to userspace. The userspace MUST add + * 0x04000000 to write transactions to the register to receive any MIDI + * messages. + * + * Here, I just describe MIDI-related offsets of the register, in little-endian + * order. + * + * Controllers are allowed to register higher 4 bytes of address to receive + * the transactions. The register is 0x'0000'8010'03f4. On the other hand, the + * controllers are not allowed to register lower 4 bytes of the address. They + * are forced to select from 4 options by writing corresponding bits to + * 0x'0000'8010'051c. + * + * The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes + * of address to which the device transferrs the transactions. + * - 6th: 0x'....'....'0000'0180 + * - 5th: 0x'....'....'0000'0100 + * - 4th: 0x'....'....'0000'0080 + * - 3rd: 0x'....'....'0000'0000 + * + * This driver configure 0x'....'....'0000'0000 for units to receive MIDI + * messages. 3rd bit of the register should be configured, however this driver + * deligates this task to user space applications due to a restriction that + * this register is write-only and the other bits have own effects. + * + * The 1st and 2nd bits in LSB of this register are used to cancel transferring + * asynchronous transactions. These two bits have the same effect. + * - 1st/2nd: cancel transferring + */ +int snd_ff_transaction_reregister(struct snd_ff *ff) +{ + struct fw_card *fw_card = fw_parent_device(ff->unit)->card; + u32 addr; + __le32 reg; + + /* + * Controllers are allowed to register its node ID and upper 2 byte of + * local address to listen asynchronous transactions. + */ + addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32); + reg = cpu_to_le32(addr); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); +} + +int snd_ff_transaction_register(struct snd_ff *ff) +{ + int i, err; + + /* + * Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should + * be zero due to device specification. + */ + for (i = 0; i < 0xffff; i++) { + err = allocate_own_address(ff, i); + if (err != -EBUSY && err != -EAGAIN) + break; + } + if (err < 0) + return err; + + err = snd_ff_transaction_reregister(ff); + if (err < 0) + return err; + + INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg); + INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg); + + return 0; +} + +void snd_ff_transaction_unregister(struct snd_ff *ff) +{ + __le32 reg; + + if (ff->async_handler.callback_data == NULL) + return; + ff->async_handler.callback_data = NULL; + + /* Release higher 4 bytes of address. */ + reg = cpu_to_le32(0x00000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&ff->async_handler); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c new file mode 100644 index 000000000..4974bc798 --- /dev/null +++ b/sound/firewire/fireface/ff.c @@ -0,0 +1,209 @@ +/* + * ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define OUI_RME 0x000a35 + +MODULE_DESCRIPTION("RME Fireface series Driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_ff *ff) +{ + struct fw_device *fw_dev = fw_parent_device(ff->unit); + + strcpy(ff->card->driver, "Fireface"); + strcpy(ff->card->shortname, ff->spec->name); + strcpy(ff->card->mixername, ff->spec->name); + snprintf(ff->card->longname, sizeof(ff->card->longname), + "RME %s, GUID %08x%08x at %s, S%d", ff->spec->name, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&ff->unit->device), 100 << fw_dev->max_speed); +} + +static void ff_free(struct snd_ff *ff) +{ + snd_ff_stream_destroy_duplex(ff); + snd_ff_transaction_unregister(ff); + + fw_unit_put(ff->unit); + + mutex_destroy(&ff->mutex); + kfree(ff); +} + +static void ff_card_free(struct snd_card *card) +{ + ff_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work); + int err; + + if (ff->registered) + return; + + err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0, + &ff->card); + if (err < 0) + return; + + err = snd_ff_transaction_register(ff); + if (err < 0) + goto error; + + name_card(ff); + + err = snd_ff_stream_init_duplex(ff); + if (err < 0) + goto error; + + snd_ff_proc_init(ff); + + err = snd_ff_create_midi_devices(ff); + if (err < 0) + goto error; + + err = snd_ff_create_pcm_devices(ff); + if (err < 0) + goto error; + + err = snd_ff_create_hwdep_devices(ff); + if (err < 0) + goto error; + + err = snd_card_register(ff->card); + if (err < 0) + goto error; + + ff->card->private_free = ff_card_free; + ff->card->private_data = ff; + ff->registered = true; + + return; +error: + snd_ff_transaction_unregister(ff); + snd_ff_stream_destroy_duplex(ff); + snd_card_free(ff->card); + dev_info(&ff->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_ff_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_ff *ff; + + ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL); + if (ff == NULL) + return -ENOMEM; + + /* initialize myself */ + ff->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, ff); + + mutex_init(&ff->mutex); + spin_lock_init(&ff->lock); + init_waitqueue_head(&ff->hwdep_wait); + + ff->spec = (const struct snd_ff_spec *)entry->driver_data; + + /* Register this sound card later. */ + INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); + snd_fw_schedule_registration(unit, &ff->dwork); + + return 0; +} + +static void snd_ff_update(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!ff->registered) + snd_fw_schedule_registration(unit, &ff->dwork); + + snd_ff_transaction_reregister(ff); + + if (ff->registered) + snd_ff_stream_update_duplex(ff); +} + +static void snd_ff_remove(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_work_sync(&ff->dwork.work); + + if (ff->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(ff->card); + } else { + /* Don't forget this case. */ + ff_free(ff); + } +} + +static const struct snd_ff_spec spec_ff400 = { + .name = "Fireface400", + .pcm_capture_channels = {18, 14, 10}, + .pcm_playback_channels = {18, 14, 10}, + .midi_in_ports = 2, + .midi_out_ports = 2, + .protocol = &snd_ff_protocol_ff400, +}; + +static const struct ieee1394_device_id snd_ff_id_table[] = { + /* Fireface 400 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_RME, + .specifier_id = 0x000a35, + .version = 0x000002, + .model_id = 0x101800, + .driver_data = (kernel_ulong_t)&spec_ff400, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); + +static struct fw_driver ff_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireface", + .bus = &fw_bus_type, + }, + .probe = snd_ff_probe, + .update = snd_ff_update, + .remove = snd_ff_remove, + .id_table = snd_ff_id_table, +}; + +static int __init snd_ff_init(void) +{ + return driver_register(&ff_driver.driver); +} + +static void __exit snd_ff_exit(void) +{ + driver_unregister(&ff_driver.driver); +} + +module_init(snd_ff_init); +module_exit(snd_ff_exit); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h new file mode 100644 index 000000000..64df44beb --- /dev/null +++ b/sound/firewire/fireface/ff.h @@ -0,0 +1,146 @@ +/* + * ff.h - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREFACE_H_INCLUDED +#define SOUND_FIREFACE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +#define SND_FF_STREAM_MODES 3 + +#define SND_FF_MAXIMIM_MIDI_QUADS 9 +#define SND_FF_IN_MIDI_PORTS 2 +#define SND_FF_OUT_MIDI_PORTS 2 + +struct snd_ff_protocol; +struct snd_ff_spec { + const char *const name; + + const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES]; + const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES]; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; + + const struct snd_ff_protocol *protocol; +}; + +struct snd_ff { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + const struct snd_ff_spec *spec; + + /* To handle MIDI tx. */ + struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS]; + struct fw_address_handler async_handler; + + /* TO handle MIDI rx. */ + struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS]; + u8 running_status[SND_FF_OUT_MIDI_PORTS]; + __le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS]; + struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS]; + struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS]; + ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS]; + bool rx_midi_error[SND_FF_OUT_MIDI_PORTS]; + unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS]; + + unsigned int substreams_counter; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; +}; + +enum snd_ff_clock_src { + SND_FF_CLOCK_SRC_INTERNAL, + SND_FF_CLOCK_SRC_SPDIF, + SND_FF_CLOCK_SRC_ADAT, + SND_FF_CLOCK_SRC_WORD, + SND_FF_CLOCK_SRC_LTC, + /* TODO: perhaps ADAT2 and TCO exists. */ +}; + +struct snd_ff_protocol { + int (*get_clock)(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src); + int (*begin_session)(struct snd_ff *ff, unsigned int rate); + void (*finish_session)(struct snd_ff *ff); + int (*switch_fetching_mode)(struct snd_ff *ff, bool enable); + + void (*dump_sync_status)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + void (*dump_clock_config)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + + u64 midi_high_addr_reg; + u64 midi_rx_port_0_reg; + u64 midi_rx_port_1_reg; +}; + +extern const struct snd_ff_protocol snd_ff_protocol_ff400; + +int snd_ff_transaction_register(struct snd_ff *ff); +int snd_ff_transaction_reregister(struct snd_ff *ff); +void snd_ff_transaction_unregister(struct snd_ff *ff); + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels); +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); + +int snd_ff_stream_init_duplex(struct snd_ff *ff); +void snd_ff_stream_destroy_duplex(struct snd_ff *ff); +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate); +void snd_ff_stream_stop_duplex(struct snd_ff *ff); +void snd_ff_stream_update_duplex(struct snd_ff *ff); + +void snd_ff_stream_lock_changed(struct snd_ff *ff); +int snd_ff_stream_lock_try(struct snd_ff *ff); +void snd_ff_stream_lock_release(struct snd_ff *ff); + +void snd_ff_proc_init(struct snd_ff *ff); + +int snd_ff_create_midi_devices(struct snd_ff *ff); + +int snd_ff_create_pcm_devices(struct snd_ff *ff); + +int snd_ff_create_hwdep_devices(struct snd_ff *ff); + +#endif diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile new file mode 100644 index 000000000..15ef7f75a --- /dev/null +++ b/sound/firewire/fireworks/Makefile @@ -0,0 +1,4 @@ +snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ + fireworks_stream.o fireworks_proc.o fireworks_midi.o \ + fireworks_pcm.o fireworks_hwdep.o fireworks.o +obj-$(CONFIG_SND_FIREWORKS) += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c new file mode 100644 index 000000000..f2d073365 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.c @@ -0,0 +1,427 @@ +/* + * fireworks.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * Fireworks is a board module which Echo Audio produced. This module consists + * of three chipsets: + * - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6 + * - DSP or/and FPGA for signal processing + * - Flash Memory to store firmwares + */ + +#include "fireworks.h" + +MODULE_DESCRIPTION("Echo Fireworks driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +unsigned int snd_efw_resp_buf_size = 1024; +bool snd_efw_resp_buf_debug = false; + +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 Fireworks sound card"); +module_param_named(resp_buf_size, snd_efw_resp_buf_size, uint, 0444); +MODULE_PARM_DESC(resp_buf_size, + "response buffer size (max 4096, default 1024)"); +module_param_named(resp_buf_debug, snd_efw_resp_buf_debug, bool, 0444); +MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +#define VENDOR_LOUD 0x000ff2 +#define MODEL_MACKIE_400F 0x00400f +#define MODEL_MACKIE_1200F 0x01200f + +#define VENDOR_ECHO 0x001486 +#define MODEL_ECHO_AUDIOFIRE_12 0x00af12 +#define MODEL_ECHO_AUDIOFIRE_12HD 0x0af12d +#define MODEL_ECHO_AUDIOFIRE_12_APPLE 0x0af12a +/* This is applied for AudioFire8 (until 2009 July) */ +#define MODEL_ECHO_AUDIOFIRE_8 0x000af8 +#define MODEL_ECHO_AUDIOFIRE_2 0x000af2 +#define MODEL_ECHO_AUDIOFIRE_4 0x000af4 +/* AudioFire9 is applied for AudioFire8(since 2009 July) and AudioFirePre8 */ +#define MODEL_ECHO_AUDIOFIRE_9 0x000af9 +/* unknown as product */ +#define MODEL_ECHO_FIREWORKS_8 0x0000f8 +#define MODEL_ECHO_FIREWORKS_HDMI 0x00afd1 + +#define VENDOR_GIBSON 0x00075b +/* for Robot Interface Pack of Dark Fire, Dusk Tiger, Les Paul Standard 2010 */ +#define MODEL_GIBSON_RIP 0x00afb2 +/* unknown as product */ +#define MODEL_GIBSON_GOLDTOP 0x00afb9 + +/* part of hardware capability flags */ +#define FLAG_RESP_ADDR_CHANGABLE 0 + +static int +get_hardware_info(struct snd_efw *efw) +{ + struct fw_device *fw_dev = fw_parent_device(efw->unit); + struct snd_efw_hwinfo *hwinfo; + char version[12] = {0}; + int err; + + hwinfo = kzalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return -ENOMEM; + + err = snd_efw_command_get_hwinfo(efw, hwinfo); + if (err < 0) + goto end; + + /* firmware version for communication chipset */ + snprintf(version, sizeof(version), "%u.%u", + (hwinfo->arm_version >> 24) & 0xff, + (hwinfo->arm_version >> 16) & 0xff); + efw->firmware_version = hwinfo->arm_version; + + strcpy(efw->card->driver, "Fireworks"); + strcpy(efw->card->shortname, hwinfo->model_name); + strcpy(efw->card->mixername, hwinfo->model_name); + snprintf(efw->card->longname, sizeof(efw->card->longname), + "%s %s v%s, GUID %08x%08x at %s, S%d", + hwinfo->vendor_name, hwinfo->model_name, version, + hwinfo->guid_hi, hwinfo->guid_lo, + dev_name(&efw->unit->device), 100 << fw_dev->max_speed); + + if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE)) + efw->resp_addr_changable = true; + + efw->supported_sampling_rate = 0; + if ((hwinfo->min_sample_rate <= 22050) + && (22050 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050; + if ((hwinfo->min_sample_rate <= 32000) + && (32000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000; + if ((hwinfo->min_sample_rate <= 44100) + && (44100 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100; + if ((hwinfo->min_sample_rate <= 48000) + && (48000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000; + if ((hwinfo->min_sample_rate <= 88200) + && (88200 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200; + if ((hwinfo->min_sample_rate <= 96000) + && (96000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000; + if ((hwinfo->min_sample_rate <= 176400) + && (176400 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400; + if ((hwinfo->min_sample_rate <= 192000) + && (192000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000; + + /* the number of MIDI ports, not of MIDI conformant data channels */ + if (hwinfo->midi_out_ports > SND_EFW_MAX_MIDI_OUT_PORTS || + hwinfo->midi_in_ports > SND_EFW_MAX_MIDI_IN_PORTS) { + err = -EIO; + goto end; + } + efw->midi_out_ports = hwinfo->midi_out_ports; + efw->midi_in_ports = hwinfo->midi_in_ports; + + if (hwinfo->amdtp_tx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels; + efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x; + efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x; + efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels; + efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x; + efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x; + + /* Hardware metering. */ + if (hwinfo->phys_in_grp_count > HWINFO_MAX_CAPS_GROUPS || + hwinfo->phys_out_grp_count > HWINFO_MAX_CAPS_GROUPS) { + err = -EIO; + goto end; + } + efw->phys_in = hwinfo->phys_in; + efw->phys_out = hwinfo->phys_out; + efw->phys_in_grp_count = hwinfo->phys_in_grp_count; + efw->phys_out_grp_count = hwinfo->phys_out_grp_count; + memcpy(&efw->phys_in_grps, hwinfo->phys_in_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count); + memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count); + + /* AudioFire8 (since 2009) and AudioFirePre8 */ + if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_9) + efw->is_af9 = true; + /* These models uses the same firmware. */ + if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_2 || + hwinfo->type == MODEL_ECHO_AUDIOFIRE_4 || + hwinfo->type == MODEL_ECHO_AUDIOFIRE_9 || + hwinfo->type == MODEL_GIBSON_RIP || + hwinfo->type == MODEL_GIBSON_GOLDTOP) + efw->is_fireworks3 = true; +end: + kfree(hwinfo); + return err; +} + +static void efw_free(struct snd_efw *efw) +{ + snd_efw_stream_destroy_duplex(efw); + snd_efw_transaction_remove_instance(efw); + fw_unit_put(efw->unit); + + kfree(efw->resp_buf); + + mutex_destroy(&efw->mutex); + kfree(efw); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void +efw_card_free(struct snd_card *card) +{ + struct snd_efw *efw = card->private_data; + + if (efw->card_index >= 0) { + mutex_lock(&devices_mutex); + clear_bit(efw->card_index, devices_used); + mutex_unlock(&devices_mutex); + } + + efw_free(card->private_data); +} + +static void +do_registration(struct work_struct *work) +{ + struct snd_efw *efw = container_of(work, struct snd_efw, dwork.work); + unsigned int card_index; + int err; + + if (efw->registered) + return; + + mutex_lock(&devices_mutex); + + /* check registered cards */ + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return; + } + + err = snd_card_new(&efw->unit->device, index[card_index], + id[card_index], THIS_MODULE, 0, &efw->card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return; + } + + /* prepare response buffer */ + snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size, + SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U); + efw->resp_buf = kzalloc(snd_efw_resp_buf_size, GFP_KERNEL); + if (efw->resp_buf == NULL) { + err = -ENOMEM; + goto error; + } + efw->pull_ptr = efw->push_ptr = efw->resp_buf; + snd_efw_transaction_add_instance(efw); + + err = get_hardware_info(efw); + if (err < 0) + goto error; + + err = snd_efw_stream_init_duplex(efw); + if (err < 0) + goto error; + + snd_efw_proc_init(efw); + + if (efw->midi_out_ports || efw->midi_in_ports) { + err = snd_efw_create_midi_devices(efw); + if (err < 0) + goto error; + } + + err = snd_efw_create_pcm_devices(efw); + if (err < 0) + goto error; + + err = snd_efw_create_hwdep_device(efw); + if (err < 0) + goto error; + + err = snd_card_register(efw->card); + if (err < 0) + goto error; + + set_bit(card_index, devices_used); + mutex_unlock(&devices_mutex); + + /* + * After registered, efw instance can be released corresponding to + * releasing the sound card instance. + */ + efw->card->private_free = efw_card_free; + efw->card->private_data = efw; + efw->registered = true; + + return; +error: + mutex_unlock(&devices_mutex); + snd_efw_transaction_remove_instance(efw); + snd_efw_stream_destroy_duplex(efw); + snd_card_free(efw->card); + kfree(efw->resp_buf); + efw->resp_buf = NULL; + dev_info(&efw->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int +efw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) +{ + struct snd_efw *efw; + + efw = kzalloc(sizeof(struct snd_efw), GFP_KERNEL); + if (efw == NULL) + return -ENOMEM; + + efw->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, efw); + + mutex_init(&efw->mutex); + spin_lock_init(&efw->lock); + init_waitqueue_head(&efw->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&efw->dwork, do_registration); + snd_fw_schedule_registration(unit, &efw->dwork); + + return 0; +} + +static void efw_update(struct fw_unit *unit) +{ + struct snd_efw *efw = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!efw->registered) + snd_fw_schedule_registration(unit, &efw->dwork); + + snd_efw_transaction_bus_reset(efw->unit); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (efw->registered) { + mutex_lock(&efw->mutex); + snd_efw_stream_update_duplex(efw); + mutex_unlock(&efw->mutex); + } +} + +static void efw_remove(struct fw_unit *unit) +{ + struct snd_efw *efw = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&efw->dwork); + + if (efw->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(efw->card); + } else { + /* Don't forget this case. */ + efw_free(efw); + } +} + +static const struct ieee1394_device_id efw_id_table[] = { + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_400F), + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_1200F), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12HD), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12_APPLE), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_2), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_4), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_9), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_HDMI), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_RIP), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_GOLDTOP), + {} +}; +MODULE_DEVICE_TABLE(ieee1394, efw_id_table); + +static struct fw_driver efw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireworks", + .bus = &fw_bus_type, + }, + .probe = efw_probe, + .update = efw_update, + .remove = efw_remove, + .id_table = efw_id_table, +}; + +static int __init snd_efw_init(void) +{ + int err; + + err = snd_efw_transaction_register(); + if (err < 0) + goto end; + + err = driver_register(&efw_driver.driver); + if (err < 0) + snd_efw_transaction_unregister(); + +end: + return err; +} + +static void __exit snd_efw_exit(void) +{ + snd_efw_transaction_unregister(); + driver_unregister(&efw_driver.driver); +} + +module_init(snd_efw_init); +module_exit(snd_efw_exit); diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h new file mode 100644 index 000000000..9b19c7f05 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.h @@ -0,0 +1,235 @@ +/* + * fireworks.h - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#ifndef SOUND_FIREWORKS_H_INCLUDED +#define SOUND_FIREWORKS_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-am824.h" +#include "../cmp.h" +#include "../lib.h" + +#define SND_EFW_MAX_MIDI_OUT_PORTS 2 +#define SND_EFW_MAX_MIDI_IN_PORTS 2 + +#define SND_EFW_MULTIPLIER_MODES 3 +#define HWINFO_NAME_SIZE_BYTES 32 +#define HWINFO_MAX_CAPS_GROUPS 8 + +/* + * This should be greater than maximum bytes for EFW response content. + * Currently response against command for isochronous channel mapping is + * confirmed to be the maximum one. But for flexibility, use maximum data + * payload for asynchronous primary packets at S100 (Cable base rate) in + * IEEE Std 1394-1995. + */ +#define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U + +extern unsigned int snd_efw_resp_buf_size; +extern bool snd_efw_resp_buf_debug; + +struct snd_efw_phys_grp { + u8 type; /* see enum snd_efw_grp_type */ + u8 count; +} __packed; + +struct snd_efw { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + /* for transaction */ + u32 seqnum; + bool resp_addr_changable; + + /* for quirks */ + bool is_af9; + bool is_fireworks3; + u32 firmware_version; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; + + unsigned int supported_sampling_rate; + unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES]; + unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES]; + + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + unsigned int capture_substreams; + unsigned int playback_substreams; + + /* hardware metering parameters */ + unsigned int phys_out; + unsigned int phys_in; + unsigned int phys_out_grp_count; + unsigned int phys_in_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* response queue */ + u8 *resp_buf; + u8 *pull_ptr; + u8 *push_ptr; +}; + +int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size); +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size); +int snd_efw_transaction_register(void); +void snd_efw_transaction_unregister(void); +void snd_efw_transaction_bus_reset(struct fw_unit *unit); +void snd_efw_transaction_add_instance(struct snd_efw *efw); +void snd_efw_transaction_remove_instance(struct snd_efw *efw); + +struct snd_efw_hwinfo { + u32 flags; + u32 guid_hi; + u32 guid_lo; + u32 type; + u32 version; + char vendor_name[HWINFO_NAME_SIZE_BYTES]; + char model_name[HWINFO_NAME_SIZE_BYTES]; + u32 supported_clocks; + u32 amdtp_rx_pcm_channels; + u32 amdtp_tx_pcm_channels; + u32 phys_out; + u32 phys_in; + u32 phys_out_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 phys_in_grp_count; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 midi_out_ports; + u32 midi_in_ports; + u32 max_sample_rate; + u32 min_sample_rate; + u32 dsp_version; + u32 arm_version; + u32 mixer_playback_channels; + u32 mixer_capture_channels; + u32 fpga_version; + u32 amdtp_rx_pcm_channels_2x; + u32 amdtp_tx_pcm_channels_2x; + u32 amdtp_rx_pcm_channels_4x; + u32 amdtp_tx_pcm_channels_4x; + u32 reserved[16]; +} __packed; +enum snd_efw_grp_type { + SND_EFW_CH_TYPE_ANALOG = 0, + SND_EFW_CH_TYPE_SPDIF = 1, + SND_EFW_CH_TYPE_ADAT = 2, + SND_EFW_CH_TYPE_SPDIF_OR_ADAT = 3, + SND_EFW_CH_TYPE_ANALOG_MIRRORING = 4, + SND_EFW_CH_TYPE_HEADPHONES = 5, + SND_EFW_CH_TYPE_I2S = 6, + SND_EFW_CH_TYPE_GUITAR = 7, + SND_EFW_CH_TYPE_PIEZO_GUITAR = 8, + SND_EFW_CH_TYPE_GUITAR_STRING = 9, + SND_EFW_CH_TYPE_DUMMY +}; +struct snd_efw_phys_meters { + u32 status; /* guitar state/midi signal/clock input detect */ + u32 reserved0; + u32 reserved1; + u32 reserved2; + u32 reserved3; + u32 out_meters; + u32 in_meters; + u32 reserved4; + u32 reserved5; + u32 values[0]; +} __packed; +enum snd_efw_clock_source { + SND_EFW_CLOCK_SOURCE_INTERNAL = 0, + SND_EFW_CLOCK_SOURCE_SYTMATCH = 1, + SND_EFW_CLOCK_SOURCE_WORDCLOCK = 2, + SND_EFW_CLOCK_SOURCE_SPDIF = 3, + SND_EFW_CLOCK_SOURCE_ADAT_1 = 4, + SND_EFW_CLOCK_SOURCE_ADAT_2 = 5, + SND_EFW_CLOCK_SOURCE_CONTINUOUS = 6 /* internal variable clock */ +}; +enum snd_efw_transport_mode { + SND_EFW_TRANSPORT_MODE_WINDOWS = 0, + SND_EFW_TRANSPORT_MODE_IEC61883 = 1, +}; +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low); +int snd_efw_command_set_tx_mode(struct snd_efw *efw, + enum snd_efw_transport_mode mode); +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo); +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len); +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source); +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate); +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate); + +int snd_efw_stream_init_duplex(struct snd_efw *efw); +int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate); +void snd_efw_stream_stop_duplex(struct snd_efw *efw); +void snd_efw_stream_update_duplex(struct snd_efw *efw); +void snd_efw_stream_destroy_duplex(struct snd_efw *efw); +void snd_efw_stream_lock_changed(struct snd_efw *efw); +int snd_efw_stream_lock_try(struct snd_efw *efw); +void snd_efw_stream_lock_release(struct snd_efw *efw); + +void snd_efw_proc_init(struct snd_efw *efw); + +int snd_efw_create_midi_devices(struct snd_efw *efw); + +int snd_efw_create_pcm_devices(struct snd_efw *efw); +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode); + +int snd_efw_create_hwdep_device(struct snd_efw *efw); + +#define SND_EFW_DEV_ENTRY(vendor, model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor,\ + .model_id = model \ +} + +#endif diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c new file mode 100644 index 000000000..94bab0476 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_command.c @@ -0,0 +1,372 @@ +/* + * fireworks_command.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./fireworks.h" + +/* + * This driver uses transaction version 1 or later to use extended hardware + * information. Then too old devices are not available. + * + * Each commands are not required to have continuous sequence numbers. This + * number is just used to match command and response. + * + * This module support a part of commands. Please see FFADO if you want to see + * whole commands. But there are some commands which FFADO don't implement. + * + * Fireworks also supports AV/C general commands and AV/C Stream Format + * Information commands. But this module don't use them. + */ + +#define KERNEL_SEQNUM_MIN (SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 2) +#define KERNEL_SEQNUM_MAX ((u32)~0) + +/* for clock source and sampling rate */ +struct efc_clock { + u32 source; + u32 sampling_rate; + u32 index; +}; + +/* command categories */ +enum efc_category { + EFC_CAT_HWINFO = 0, + EFC_CAT_TRANSPORT = 2, + EFC_CAT_HWCTL = 3, +}; + +/* hardware info category commands */ +enum efc_cmd_hwinfo { + EFC_CMD_HWINFO_GET_CAPS = 0, + EFC_CMD_HWINFO_GET_POLLED = 1, + EFC_CMD_HWINFO_SET_RESP_ADDR = 2 +}; + +enum efc_cmd_transport { + EFC_CMD_TRANSPORT_SET_TX_MODE = 0 +}; + +/* hardware control category commands */ +enum efc_cmd_hwctl { + EFC_CMD_HWCTL_SET_CLOCK = 0, + EFC_CMD_HWCTL_GET_CLOCK = 1, + EFC_CMD_HWCTL_IDENTIFY = 5 +}; + +/* return values in response */ +enum efr_status { + EFR_STATUS_OK = 0, + EFR_STATUS_BAD = 1, + EFR_STATUS_BAD_COMMAND = 2, + EFR_STATUS_COMM_ERR = 3, + EFR_STATUS_BAD_QUAD_COUNT = 4, + EFR_STATUS_UNSUPPORTED = 5, + EFR_STATUS_1394_TIMEOUT = 6, + EFR_STATUS_DSP_TIMEOUT = 7, + EFR_STATUS_BAD_RATE = 8, + EFR_STATUS_BAD_CLOCK = 9, + EFR_STATUS_BAD_CHANNEL = 10, + EFR_STATUS_BAD_PAN = 11, + EFR_STATUS_FLASH_BUSY = 12, + EFR_STATUS_BAD_MIRROR = 13, + EFR_STATUS_BAD_LED = 14, + EFR_STATUS_BAD_PARAMETER = 15, + EFR_STATUS_INCOMPLETE = 0x80000000 +}; + +static const char *const efr_status_names[] = { + [EFR_STATUS_OK] = "OK", + [EFR_STATUS_BAD] = "bad", + [EFR_STATUS_BAD_COMMAND] = "bad command", + [EFR_STATUS_COMM_ERR] = "comm err", + [EFR_STATUS_BAD_QUAD_COUNT] = "bad quad count", + [EFR_STATUS_UNSUPPORTED] = "unsupported", + [EFR_STATUS_1394_TIMEOUT] = "1394 timeout", + [EFR_STATUS_DSP_TIMEOUT] = "DSP timeout", + [EFR_STATUS_BAD_RATE] = "bad rate", + [EFR_STATUS_BAD_CLOCK] = "bad clock", + [EFR_STATUS_BAD_CHANNEL] = "bad channel", + [EFR_STATUS_BAD_PAN] = "bad pan", + [EFR_STATUS_FLASH_BUSY] = "flash busy", + [EFR_STATUS_BAD_MIRROR] = "bad mirror", + [EFR_STATUS_BAD_LED] = "bad LED", + [EFR_STATUS_BAD_PARAMETER] = "bad parameter", + [EFR_STATUS_BAD_PARAMETER + 1] = "incomplete" +}; + +static int +efw_transaction(struct snd_efw *efw, unsigned int category, + unsigned int command, + const __be32 *params, unsigned int param_bytes, + const __be32 *resp, unsigned int resp_bytes) +{ + struct snd_efw_transaction *header; + __be32 *buf; + u32 seqnum; + unsigned int buf_bytes, cmd_bytes; + int err; + + /* calculate buffer size*/ + buf_bytes = sizeof(struct snd_efw_transaction) + + max(param_bytes, resp_bytes); + + /* keep buffer */ + buf = kzalloc(buf_bytes, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* to keep consistency of sequence number */ + spin_lock(&efw->lock); + if ((efw->seqnum < KERNEL_SEQNUM_MIN) || + (efw->seqnum >= KERNEL_SEQNUM_MAX - 2)) + efw->seqnum = KERNEL_SEQNUM_MIN; + else + efw->seqnum += 2; + seqnum = efw->seqnum; + spin_unlock(&efw->lock); + + /* fill transaction header fields */ + cmd_bytes = sizeof(struct snd_efw_transaction) + param_bytes; + header = (struct snd_efw_transaction *)buf; + header->length = cpu_to_be32(cmd_bytes / sizeof(__be32)); + header->version = cpu_to_be32(1); + header->seqnum = cpu_to_be32(seqnum); + header->category = cpu_to_be32(category); + header->command = cpu_to_be32(command); + header->status = 0; + + /* fill transaction command parameters */ + memcpy(header->params, params, param_bytes); + + err = snd_efw_transaction_run(efw->unit, buf, cmd_bytes, + buf, buf_bytes); + if (err < 0) + goto end; + + /* check transaction header fields */ + if ((be32_to_cpu(header->version) < 1) || + (be32_to_cpu(header->category) != category) || + (be32_to_cpu(header->command) != command) || + (be32_to_cpu(header->status) != EFR_STATUS_OK)) { + dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n", + be32_to_cpu(header->category), + be32_to_cpu(header->command), + efr_status_names[be32_to_cpu(header->status)]); + err = -EIO; + goto end; + } + + if (resp == NULL) + goto end; + + /* fill transaction response parameters */ + memset((void *)resp, 0, resp_bytes); + resp_bytes = min_t(unsigned int, resp_bytes, + be32_to_cpu(header->length) * sizeof(__be32) - + sizeof(struct snd_efw_transaction)); + memcpy((void *)resp, &buf[6], resp_bytes); +end: + kfree(buf); + return err; +} + +/* + * The address in host system for transaction response is changable when the + * device supports. struct hwinfo.flags includes its flag. The default is + * MEMORY_SPACE_EFW_RESPONSE. + */ +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low) +{ + __be32 addr[2]; + + addr[0] = cpu_to_be32(addr_high); + addr[1] = cpu_to_be32(addr_low); + + if (!efw->resp_addr_changable) + return -ENOSYS; + + return efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWINFO_SET_RESP_ADDR, + addr, sizeof(addr), NULL, 0); +} + +/* + * This is for timestamp processing. In Windows mode, all 32bit fields of second + * CIP header in AMDTP transmit packet is used for 'presentation timestamp'. In + * 'no data' packet the value of this field is 0x90ffffff. + */ +int snd_efw_command_set_tx_mode(struct snd_efw *efw, + enum snd_efw_transport_mode mode) +{ + __be32 param = cpu_to_be32(mode); + return efw_transaction(efw, EFC_CAT_TRANSPORT, + EFC_CMD_TRANSPORT_SET_TX_MODE, + ¶m, sizeof(param), NULL, 0); +} + +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_CAPS, + NULL, 0, (__be32 *)hwinfo, sizeof(*hwinfo)); + if (err < 0) + goto end; + + be32_to_cpus(&hwinfo->flags); + be32_to_cpus(&hwinfo->guid_hi); + be32_to_cpus(&hwinfo->guid_lo); + be32_to_cpus(&hwinfo->type); + be32_to_cpus(&hwinfo->version); + be32_to_cpus(&hwinfo->supported_clocks); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels); + be32_to_cpus(&hwinfo->phys_out); + be32_to_cpus(&hwinfo->phys_in); + be32_to_cpus(&hwinfo->phys_out_grp_count); + be32_to_cpus(&hwinfo->phys_in_grp_count); + be32_to_cpus(&hwinfo->midi_out_ports); + be32_to_cpus(&hwinfo->midi_in_ports); + be32_to_cpus(&hwinfo->max_sample_rate); + be32_to_cpus(&hwinfo->min_sample_rate); + be32_to_cpus(&hwinfo->dsp_version); + be32_to_cpus(&hwinfo->arm_version); + be32_to_cpus(&hwinfo->mixer_playback_channels); + be32_to_cpus(&hwinfo->mixer_capture_channels); + be32_to_cpus(&hwinfo->fpga_version); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_4x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_4x); + + /* ensure terminated */ + hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; + hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; +end: + return err; +} + +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len) +{ + u32 *buf = (u32 *)meters; + unsigned int i; + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_POLLED, + NULL, 0, (__be32 *)meters, len); + if (err >= 0) + for (i = 0; i < len / sizeof(u32); i++) + be32_to_cpus(&buf[i]); + + return err; +} + +static int +command_get_clock(struct snd_efw *efw, struct efc_clock *clock) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_GET_CLOCK, + NULL, 0, + (__be32 *)clock, sizeof(struct efc_clock)); + if (err >= 0) { + be32_to_cpus(&clock->source); + be32_to_cpus(&clock->sampling_rate); + be32_to_cpus(&clock->index); + } + + return err; +} + +/* give UINT_MAX if set nothing */ +static int +command_set_clock(struct snd_efw *efw, + unsigned int source, unsigned int rate) +{ + struct efc_clock clock = {0}; + int err; + + /* check arguments */ + if ((source == UINT_MAX) && (rate == UINT_MAX)) { + err = -EINVAL; + goto end; + } + + /* get current status */ + err = command_get_clock(efw, &clock); + if (err < 0) + goto end; + + /* no need */ + if ((clock.source == source) && (clock.sampling_rate == rate)) + goto end; + + /* set params */ + if ((source != UINT_MAX) && (clock.source != source)) + clock.source = source; + if ((rate != UINT_MAX) && (clock.sampling_rate != rate)) + clock.sampling_rate = rate; + clock.index = 0; + + cpu_to_be32s(&clock.source); + cpu_to_be32s(&clock.sampling_rate); + cpu_to_be32s(&clock.index); + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_SET_CLOCK, + (__be32 *)&clock, sizeof(struct efc_clock), + NULL, 0); + if (err < 0) + goto end; + + /* + * With firmware version 5.8, just after changing clock state, these + * parameters are not immediately retrieved by get command. In my + * trial, there needs to be 100msec to get changed parameters. + */ + msleep(150); +end: + return err; +} + +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *source = clock.source; + + return err; +} + +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *rate = clock.sampling_rate; + + return err; +} + +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate) +{ + return command_set_clock(efw, UINT_MAX, rate); +} + diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c new file mode 100644 index 000000000..e9209f44c --- /dev/null +++ b/sound/firewire/fireworks/fireworks_hwdep.c @@ -0,0 +1,331 @@ +/* + * fireworks_hwdep.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * 4.transmit command of EFW transaction + * 5.receive response of EFW transaction + * + */ + +#include "fireworks.h" + +static long +hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained, + loff_t *offset) +{ + unsigned int length, till_end, type; + struct snd_efw_transaction *t; + u8 *pull_ptr; + long count = 0; + + if (remained < sizeof(type) + sizeof(struct snd_efw_transaction)) + return -ENOSPC; + + /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */ + type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE; + if (copy_to_user(buf, &type, sizeof(type))) + return -EFAULT; + count += sizeof(type); + remained -= sizeof(type); + buf += sizeof(type); + + /* write into buffer as many responses as possible */ + spin_lock_irq(&efw->lock); + + /* + * When another task reaches here during this task's access to user + * space, it picks up current position in buffer and can read the same + * series of responses. + */ + pull_ptr = efw->pull_ptr; + + while (efw->push_ptr != pull_ptr) { + t = (struct snd_efw_transaction *)(pull_ptr); + length = be32_to_cpu(t->length) * sizeof(__be32); + + /* confirm enough space for this response */ + if (remained < length) + break; + + /* copy from ring buffer to user buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(pull_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + spin_unlock_irq(&efw->lock); + + if (copy_to_user(buf, pull_ptr, till_end)) + return -EFAULT; + + spin_lock_irq(&efw->lock); + + pull_ptr += till_end; + if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size) + pull_ptr -= snd_efw_resp_buf_size; + + length -= till_end; + buf += till_end; + count += till_end; + remained -= till_end; + } + } + + /* + * All of tasks can read from the buffer nearly simultaneously, but the + * last position for each task is different depending on the length of + * given buffer. Here, for simplicity, a position of buffer is set by + * the latest task. It's better for a listening application to allow one + * thread to read from the buffer. Unless, each task can read different + * sequence of responses depending on variation of buffer length. + */ + efw->pull_ptr = pull_ptr; + + spin_unlock_irq(&efw->lock); + + return count; +} + +static long +hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count, + loff_t *offset) +{ + union snd_firewire_event event = { + .lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, + }; + + spin_lock_irq(&efw->lock); + + event.lock_status.status = (efw->dev_lock_count > 0); + efw->dev_lock_changed = false; + + spin_unlock_irq(&efw->lock); + + count = min_t(long, count, sizeof(event.lock_status)); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + DEFINE_WAIT(wait); + bool dev_lock_changed; + bool queued; + + spin_lock_irq(&efw->lock); + + dev_lock_changed = efw->dev_lock_changed; + queued = efw->push_ptr != efw->pull_ptr; + + while (!dev_lock_changed && !queued) { + prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&efw->lock); + schedule(); + finish_wait(&efw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&efw->lock); + dev_lock_changed = efw->dev_lock_changed; + queued = efw->push_ptr != efw->pull_ptr; + } + + spin_unlock_irq(&efw->lock); + + if (dev_lock_changed) + count = hwdep_read_locked(efw, buf, count, offset); + else if (queued) + count = hwdep_read_resp_buf(efw, buf, count, offset); + + return count; +} + +static long +hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + u32 seqnum; + u8 *buf; + + if (count < sizeof(struct snd_efw_transaction) || + SND_EFW_RESPONSE_MAXIMUM_BYTES < count) + return -EINVAL; + + buf = memdup_user(data, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* check seqnum is not for kernel-land */ + seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) { + count = -EINVAL; + goto end; + } + + if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0) + count = -EIO; +end: + kfree(buf); + return count; +} + +static __poll_t +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_efw *efw = hwdep->private_data; + __poll_t events; + + poll_wait(file, &efw->hwdep_wait, wait); + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&efw->lock); + + return events | EPOLLOUT; +} + +static int +hwdep_get_info(struct snd_efw *efw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(efw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int +hwdep_lock(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == 0) { + efw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == -1) { + efw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_efw *efw = hwdep->private_data; + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_count == -1) + efw->dev_lock_count = 0; + spin_unlock_irq(&efw->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_efw *efw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(efw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(efw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(efw); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_efw_create_hwdep_device(struct snd_efw *efw) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .write = hwdep_write, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "Fireworks"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; + hwdep->ops = ops; + hwdep->private_data = efw; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c new file mode 100644 index 000000000..f5da2cd4c --- /dev/null +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -0,0 +1,174 @@ +/* + * fireworks_midi.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "fireworks.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end; + + mutex_lock(&efw->mutex); + efw->capture_substreams++; + err = snd_efw_stream_start_duplex(efw, 0); + mutex_unlock(&efw->mutex); + if (err < 0) + snd_efw_stream_lock_release(efw); + +end: + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end; + + mutex_lock(&efw->mutex); + efw->playback_substreams++; + err = snd_efw_stream_start_duplex(efw, 0); + mutex_unlock(&efw->mutex); + if (err < 0) + snd_efw_stream_lock_release(efw); +end: + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + mutex_lock(&efw->mutex); + efw->capture_substreams--; + snd_efw_stream_stop_duplex(efw); + mutex_unlock(&efw->mutex); + + snd_efw_stream_lock_release(efw); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + mutex_lock(&efw->mutex); + efw->playback_substreams--; + snd_efw_stream_stop_duplex(efw); + mutex_unlock(&efw->mutex); + + snd_efw_stream_lock_release(efw); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&efw->tx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&efw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&efw->rx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&efw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static void set_midi_substream_names(struct snd_efw *efw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", efw->card->shortname, subs->number + 1); + } +} + +int snd_efw_create_midi_devices(struct snd_efw *efw) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(efw->card, efw->card->driver, 0, + efw->midi_out_ports, efw->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", efw->card->shortname); + rmidi->private_data = efw; + + if (efw->midi_in_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(efw, str); + } + + if (efw->midi_out_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(efw, str); + } + + if ((efw->midi_out_ports > 0) && (efw->midi_in_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c new file mode 100644 index 000000000..aed566d82 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -0,0 +1,415 @@ +/* + * fireworks_pcm.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "./fireworks.h" + +/* + * NOTE: + * Fireworks changes its AMDTP channels for PCM data according to its sampling + * rate. There are three modes. Here _XX is either _rx or _tx. + * 0: 32.0- 48.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels applied + * 1: 88.2- 96.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_2x applied + * 2: 176.4-192.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_4x applied + * + * The number of PCM channels for analog input and output are always fixed but + * the number of PCM channels for digital input and output are differed. + * + * Additionally, according to "AudioFire Owner's Manual Version 2.2", in some + * model, the number of PCM channels for digital input has more restriction + * depending on which digital interface is selected. + * - S/PDIF coaxial and optical : use input 1-2 + * - ADAT optical at 32.0-48.0 kHz : use input 1-8 + * - ADAT optical at 88.2-96.0 kHz : use input 1-4 (S/MUX format) + * + * The data in AMDTP channels for blank PCM channels are zero. + */ +static const unsigned int freq_table[] = { + /* multiplier mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* multiplier mode 1 */ + [3] = 88200, + [4] = 96000, + /* multiplier mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +static inline unsigned int +get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + if (freq_table[i] == sampling_rate) { + *mode = get_multiplier_mode_with_index(i); + return 0; + } + } + + return -EINVAL; +} + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, freq_table[i]); + t.max = max(t.max, freq_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, freq_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels(struct snd_pcm_hardware *hw, unsigned int *pcm_channels) +{ + unsigned int i, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (pcm_channels[mode] == 0) + continue; + + hw->channels_min = min(hw->channels_min, pcm_channels[mode]); + hw->channels_max = max(hw->channels_max, pcm_channels[mode]); + } +} + +static int +pcm_init_hw_params(struct snd_efw *efw, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + unsigned int *pcm_channels; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; + s = &efw->tx_stream; + pcm_channels = efw->pcm_capture_channels; + } else { + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; + s = &efw->rx_stream; + pcm_channels = efw->pcm_playback_channels; + } + + /* limit rates */ + runtime->hw.rates = efw->supported_sampling_rate, + snd_pcm_limit_hw_rates(runtime); + + limit_channels(&runtime->hw, pcm_channels); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_am824_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + unsigned int sampling_rate; + enum snd_efw_clock_source clock_source; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end; + + err = pcm_init_hw_params(efw, substream); + if (err < 0) + goto err_locked; + + err = snd_efw_command_get_clock_source(efw, &clock_source); + if (err < 0) + goto err_locked; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) || + amdtp_stream_pcm_running(&efw->tx_stream) || + amdtp_stream_pcm_running(&efw->rx_stream)) { + err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_efw_stream_lock_release(efw); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + snd_efw_stream_lock_release(efw); + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&efw->mutex); + efw->capture_substreams++; + mutex_unlock(&efw->mutex); + } + + return 0; +} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&efw->mutex); + efw->playback_substreams++; + mutex_unlock(&efw->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) { + mutex_lock(&efw->mutex); + efw->capture_substreams--; + mutex_unlock(&efw->mutex); + } + + snd_efw_stream_stop_duplex(efw); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) { + mutex_lock(&efw->mutex); + efw->playback_substreams--; + mutex_unlock(&efw->mutex); + } + + snd_efw_stream_stop_duplex(efw); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_efw_stream_start_duplex(efw, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->tx_stream); + + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_efw_stream_start_duplex(efw, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->rx_stream); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&efw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&efw->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + return amdtp_stream_pcm_ack(&efw->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + return amdtp_stream_pcm_ack(&efw->rx_stream); +} + +int snd_efw_create_pcm_devices(struct snd_efw *efw) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(efw->card, efw->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = efw; + snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c new file mode 100644 index 000000000..779ecec5a --- /dev/null +++ b/sound/firewire/fireworks/fireworks_proc.c @@ -0,0 +1,232 @@ +/* + * fireworks_proc.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./fireworks.h" + +static inline const char* +get_phys_name(struct snd_efw_phys_grp *grp, bool input) +{ + static const char *const ch_type[] = { + "Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT", "Mirroring", + "Headphones", "I2S", "Guitar", "Pirzo Guitar", "Guitar String", + }; + + if (grp->type < ARRAY_SIZE(ch_type)) + return ch_type[grp->type]; + else if (input) + return "Input"; + else + return "Output"; +} + +static void +proc_read_hwinfo(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned short i; + struct snd_efw_hwinfo *hwinfo; + + hwinfo = kmalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return; + + if (snd_efw_command_get_hwinfo(efw, hwinfo) < 0) + goto end; + + snd_iprintf(buffer, "guid_hi: 0x%X\n", hwinfo->guid_hi); + snd_iprintf(buffer, "guid_lo: 0x%X\n", hwinfo->guid_lo); + snd_iprintf(buffer, "type: 0x%X\n", hwinfo->type); + snd_iprintf(buffer, "version: 0x%X\n", hwinfo->version); + snd_iprintf(buffer, "vendor_name: %s\n", hwinfo->vendor_name); + snd_iprintf(buffer, "model_name: %s\n", hwinfo->model_name); + + snd_iprintf(buffer, "dsp_version: 0x%X\n", hwinfo->dsp_version); + snd_iprintf(buffer, "arm_version: 0x%X\n", hwinfo->arm_version); + snd_iprintf(buffer, "fpga_version: 0x%X\n", hwinfo->fpga_version); + + snd_iprintf(buffer, "flags: 0x%X\n", hwinfo->flags); + + snd_iprintf(buffer, "max_sample_rate: 0x%X\n", hwinfo->max_sample_rate); + snd_iprintf(buffer, "min_sample_rate: 0x%X\n", hwinfo->min_sample_rate); + snd_iprintf(buffer, "supported_clock: 0x%X\n", + hwinfo->supported_clocks); + + snd_iprintf(buffer, "phys out: 0x%X\n", hwinfo->phys_out); + snd_iprintf(buffer, "phys in: 0x%X\n", hwinfo->phys_in); + + snd_iprintf(buffer, "phys in grps: 0x%X\n", + hwinfo->phys_in_grp_count); + for (i = 0; i < hwinfo->phys_in_grp_count; i++) { + snd_iprintf(buffer, + "phys in grp[%d]: type 0x%X, count 0x%X\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "phys out grps: 0x%X\n", + hwinfo->phys_out_grp_count); + for (i = 0; i < hwinfo->phys_out_grp_count; i++) { + snd_iprintf(buffer, + "phys out grps[%d]: type 0x%X, count 0x%X\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "amdtp rx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels); + snd_iprintf(buffer, "amdtp tx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels); + snd_iprintf(buffer, "amdtp rx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp tx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp rx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_4x); + snd_iprintf(buffer, "amdtp tx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_4x); + + snd_iprintf(buffer, "midi out ports: 0x%X\n", hwinfo->midi_out_ports); + snd_iprintf(buffer, "midi in ports: 0x%X\n", hwinfo->midi_in_ports); + + snd_iprintf(buffer, "mixer playback channels: 0x%X\n", + hwinfo->mixer_playback_channels); + snd_iprintf(buffer, "mixer capture channels: 0x%X\n", + hwinfo->mixer_capture_channels); +end: + kfree(hwinfo); +} + +static void +proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + enum snd_efw_clock_source clock_source; + unsigned int sampling_rate; + + if (snd_efw_command_get_clock_source(efw, &clock_source) < 0) + return; + + if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0) + return; + + snd_iprintf(buffer, "Clock Source: %d\n", clock_source); + snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate); +} + +/* + * NOTE: + * dB = 20 * log10(linear / 0x01000000) + * -144.0 dB when linear is 0 + */ +static void +proc_read_phys_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + struct snd_efw_phys_meters *meters; + unsigned int g, c, m, max, size; + const char *name; + u32 *linear; + int err; + + size = sizeof(struct snd_efw_phys_meters) + + (efw->phys_in + efw->phys_out) * sizeof(u32); + meters = kzalloc(size, GFP_KERNEL); + if (meters == NULL) + return; + + err = snd_efw_command_get_phys_meters(efw, meters, size); + if (err < 0) + goto end; + + snd_iprintf(buffer, "Physical Meters:\n"); + + m = 0; + max = min(efw->phys_out, meters->out_meters); + linear = meters->values; + snd_iprintf(buffer, " %d Outputs:\n", max); + for (g = 0; g < efw->phys_out_grp_count; g++) { + name = get_phys_name(&efw->phys_out_grps[g], false); + for (c = 0; c < efw->phys_out_grps[g].count; c++) { + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } + } + + m = 0; + max = min(efw->phys_in, meters->in_meters); + linear = meters->values + meters->out_meters; + snd_iprintf(buffer, " %d Inputs:\n", max); + for (g = 0; g < efw->phys_in_grp_count; g++) { + name = get_phys_name(&efw->phys_in_grps[g], true); + for (c = 0; c < efw->phys_in_grps[g].count; c++) + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } +end: + kfree(meters); +} + +static void +proc_read_queues_state(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned int consumed; + + if (efw->pull_ptr > efw->push_ptr) + consumed = snd_efw_resp_buf_size - + (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr); + + snd_iprintf(buffer, "%d/%d\n", + consumed, snd_efw_resp_buf_size); +} + +static void +add_node(struct snd_efw *efw, struct snd_info_entry *root, const char *name, + void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(efw->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, efw, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_efw_proc_init(struct snd_efw *efw) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(efw->card, "firewire", + efw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(efw, root, "clock", proc_read_clock); + add_node(efw, root, "firmware", proc_read_hwinfo); + add_node(efw, root, "meters", proc_read_phys_meters); + add_node(efw, root, "queues", proc_read_queues_state); +} diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c new file mode 100644 index 000000000..827161bc2 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -0,0 +1,319 @@ +/* + * fireworks_stream.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "./fireworks.h" + +#define CALLBACK_TIMEOUT 100 + +static int +init_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &efw->tx_stream) { + conn = &efw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &efw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, efw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); + } +end: + return err; +} + +static void +stop_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream); + + if (stream == &efw->tx_stream) + cmp_connection_break(&efw->out_conn); + else + cmp_connection_break(&efw->in_conn); +} + +static int +start_stream(struct snd_efw *efw, struct amdtp_stream *stream, + unsigned int sampling_rate) +{ + struct cmp_connection *conn; + unsigned int mode, pcm_channels, midi_ports; + int err; + + err = snd_efw_get_multiplier_mode(sampling_rate, &mode); + if (err < 0) + goto end; + if (stream == &efw->tx_stream) { + conn = &efw->out_conn; + pcm_channels = efw->pcm_capture_channels[mode]; + midi_ports = efw->midi_out_ports; + } else { + conn = &efw->in_conn; + pcm_channels = efw->pcm_playback_channels[mode]; + midi_ports = efw->midi_in_ports; + } + + err = amdtp_am824_set_parameters(stream, sampling_rate, + pcm_channels, midi_ports, false); + if (err < 0) + goto end; + + /* establish connection via CMP */ + err = cmp_connection_establish(conn, + amdtp_stream_get_max_payload(stream)); + if (err < 0) + goto end; + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); + if (err < 0) { + stop_stream(efw, stream); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + stop_stream(efw, stream); + err = -ETIMEDOUT; + } +end: + return err; +} + +/* + * This function should be called before starting the stream or after stopping + * the streams. + */ +static void +destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (stream == &efw->tx_stream) + conn = &efw->out_conn; + else + conn = &efw->in_conn; + + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); +} + +static int +check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &efw->tx_stream) + conn = &efw->out_conn; + else + conn = &efw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&efw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +int snd_efw_stream_init_duplex(struct snd_efw *efw) +{ + int err; + + err = init_stream(efw, &efw->tx_stream); + if (err < 0) + goto end; + /* Fireworks transmits NODATA packets with TAG0. */ + efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; + /* Fireworks has its own meaning for dbc. */ + efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; + /* Fireworks reset dbc at bus reset. */ + efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK; + /* + * But Recent firmwares starts packets with non-zero dbc. + * Driver version 5.7.6 installs firmware version 5.7.3. + */ + if (efw->is_fireworks3 && + (efw->firmware_version == 0x5070000 || + efw->firmware_version == 0x5070300 || + efw->firmware_version == 0x5080000)) + efw->tx_stream.tx_first_dbc = 0x02; + /* AudioFire9 always reports wrong dbs. */ + if (efw->is_af9) + efw->tx_stream.flags |= CIP_WRONG_DBS; + /* Firmware version 5.5 reports fixed interval for dbc. */ + if (efw->firmware_version == 0x5050000) + efw->tx_stream.tx_dbc_interval = 8; + + err = init_stream(efw, &efw->rx_stream); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + goto end; + } + + /* set IEC61883 compliant mode (actually not fully compliant...) */ + err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + destroy_stream(efw, &efw->rx_stream); + } +end: + return err; +} + +int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate) +{ + unsigned int curr_rate; + int err = 0; + + /* Need no substreams */ + if (efw->playback_substreams == 0 && efw->capture_substreams == 0) + goto end; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(efw, &efw->rx_stream); + if (err < 0) + goto end; + + /* packet queueing error */ + if (amdtp_streaming_error(&efw->tx_stream)) + stop_stream(efw, &efw->tx_stream); + if (amdtp_streaming_error(&efw->rx_stream)) + stop_stream(efw, &efw->rx_stream); + + /* stop streams if rate is different */ + err = snd_efw_command_get_sampling_rate(efw, &curr_rate); + if (err < 0) + goto end; + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + stop_stream(efw, &efw->tx_stream); + stop_stream(efw, &efw->rx_stream); + } + + /* master should be always running */ + if (!amdtp_stream_running(&efw->rx_stream)) { + err = snd_efw_command_set_sampling_rate(efw, rate); + if (err < 0) + goto end; + + err = start_stream(efw, &efw->rx_stream, rate); + if (err < 0) { + dev_err(&efw->unit->device, + "fail to start AMDTP master stream:%d\n", err); + goto end; + } + } + + /* start slave if needed */ + if (efw->capture_substreams > 0 && + !amdtp_stream_running(&efw->tx_stream)) { + err = start_stream(efw, &efw->tx_stream, rate); + if (err < 0) { + dev_err(&efw->unit->device, + "fail to start AMDTP slave stream:%d\n", err); + stop_stream(efw, &efw->rx_stream); + } + } +end: + return err; +} + +void snd_efw_stream_stop_duplex(struct snd_efw *efw) +{ + if (efw->capture_substreams == 0) { + stop_stream(efw, &efw->tx_stream); + + if (efw->playback_substreams == 0) + stop_stream(efw, &efw->rx_stream); + } +} + +void snd_efw_stream_update_duplex(struct snd_efw *efw) +{ + if (cmp_connection_update(&efw->out_conn) < 0 || + cmp_connection_update(&efw->in_conn) < 0) { + stop_stream(efw, &efw->rx_stream); + stop_stream(efw, &efw->tx_stream); + } else { + amdtp_stream_update(&efw->rx_stream); + amdtp_stream_update(&efw->tx_stream); + } +} + +void snd_efw_stream_destroy_duplex(struct snd_efw *efw) +{ + destroy_stream(efw, &efw->rx_stream); + destroy_stream(efw, &efw->tx_stream); +} + +void snd_efw_stream_lock_changed(struct snd_efw *efw) +{ + efw->dev_lock_changed = true; + wake_up(&efw->hwdep_wait); +} + +int snd_efw_stream_lock_try(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + /* user land lock this */ + if (efw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (efw->dev_lock_count++ == 0) + snd_efw_stream_lock_changed(efw); + err = 0; +end: + spin_unlock_irq(&efw->lock); + return err; +} + +void snd_efw_stream_lock_release(struct snd_efw *efw) +{ + spin_lock_irq(&efw->lock); + + if (WARN_ON(efw->dev_lock_count <= 0)) + goto end; + if (--efw->dev_lock_count == 0) + snd_efw_stream_lock_changed(efw); +end: + spin_unlock_irq(&efw->lock); +} diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c new file mode 100644 index 000000000..58674a711 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_transaction.c @@ -0,0 +1,325 @@ +/* + * fireworks_transaction.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * Fireworks have its own transaction. The transaction can be delivered by AV/C + * Vendor Specific command frame or usual asynchronous transaction. At least, + * Windows driver and firmware version 5.5 or later don't use AV/C command. + * + * Transaction substance: + * At first, 6 data exist. Following to the data, parameters for each command + * exist. All of the parameters are 32 bit aligned to big endian. + * data[0]: Length of transaction substance + * data[1]: Transaction version + * data[2]: Sequence number. This is incremented by the device + * data[3]: Transaction category + * data[4]: Transaction command + * data[5]: Return value in response. + * data[6-]: Parameters + * + * Transaction address: + * command: 0xecc000000000 + * response: 0xecc080000000 (default) + * + * I note that the address for response can be changed by command. But this + * module uses the default address. + */ +#include "./fireworks.h" + +#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000ULL +#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000ULL + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define EFC_TIMEOUT_MS 125 + +static DEFINE_SPINLOCK(instances_lock); +static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +static DEFINE_SPINLOCK(transaction_queues_lock); +static LIST_HEAD(transaction_queues); + +enum transaction_queue_state { + STATE_PENDING, + STATE_BUS_RESET, + STATE_COMPLETE +}; + +struct transaction_queue { + struct list_head list; + struct fw_unit *unit; + void *buf; + unsigned int size; + u32 seqnum; + enum transaction_queue_state state; + wait_queue_head_t wait; +}; + +int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size) +{ + return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST, + MEMORY_SPACE_EFW_COMMAND, + (void *)cmd, size, 0); +} + +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size) +{ + struct transaction_queue t; + unsigned int tries; + int ret; + + t.unit = unit; + t.buf = resp; + t.size = resp_size; + t.seqnum = be32_to_cpu(((struct snd_efw_transaction *)cmd)->seqnum) + 1; + t.state = STATE_PENDING; + init_waitqueue_head(&t.wait); + + spin_lock_irq(&transaction_queues_lock); + list_add_tail(&t.list, &transaction_queues); + spin_unlock_irq(&transaction_queues_lock); + + tries = 0; + do { + ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size); + if (ret < 0) + break; + + wait_event_timeout(t.wait, t.state != STATE_PENDING, + msecs_to_jiffies(EFC_TIMEOUT_MS)); + + if (t.state == STATE_COMPLETE) { + ret = t.size; + break; + } else if (t.state == STATE_BUS_RESET) { + msleep(ERROR_DELAY_MS); + } else if (++tries >= ERROR_RETRIES) { + dev_err(&t.unit->device, "EFW transaction timed out\n"); + ret = -EIO; + break; + } + } while (1); + + spin_lock_irq(&transaction_queues_lock); + list_del(&t.list); + spin_unlock_irq(&transaction_queues_lock); + + return ret; +} + +static void +copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode) +{ + size_t capacity, till_end; + struct snd_efw_transaction *t; + + t = (struct snd_efw_transaction *)data; + length = min_t(size_t, be32_to_cpu(t->length) * sizeof(u32), length); + + spin_lock(&efw->lock); + + if (efw->push_ptr < efw->pull_ptr) + capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + capacity = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->pull_ptr); + + /* confirm enough space for this response */ + if (capacity < length) { + *rcode = RCODE_CONFLICT_ERROR; + goto end; + } + + /* copy to ring buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + memcpy(efw->push_ptr, data, till_end); + + efw->push_ptr += till_end; + if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size) + efw->push_ptr -= snd_efw_resp_buf_size; + + length -= till_end; + data += till_end; + } + + /* for hwdep */ + wake_up(&efw->hwdep_wait); + + *rcode = RCODE_COMPLETE; +end: + spin_unlock_irq(&efw->lock); +} + +static void +handle_resp_for_user(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode) +{ + struct fw_device *device; + struct snd_efw *efw; + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + efw = instances[i]; + if (efw == NULL) + continue; + device = fw_parent_device(efw->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node id vs. generation */ + if (device->node_id != source) + continue; + + break; + } + if (i == SNDRV_CARDS) + goto end; + + copy_resp_to_buf(efw, data, length, rcode); +end: + spin_unlock(&instances_lock); +} + +static void +handle_resp_for_kernel(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode, u32 seqnum) +{ + struct fw_device *device; + struct transaction_queue *t; + unsigned long flags; + + spin_lock_irqsave(&transaction_queues_lock, flags); + list_for_each_entry(t, &transaction_queues, list) { + device = fw_parent_device(t->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node_id vs. generation */ + if (device->node_id != source) + continue; + + if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) { + t->state = STATE_COMPLETE; + t->size = min_t(unsigned int, length, t->size); + memcpy(t->buf, data, t->size); + wake_up(&t->wait); + *rcode = RCODE_COMPLETE; + } + } + spin_unlock_irqrestore(&transaction_queues_lock, flags); +} + +static void +efw_response(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + int rcode, dummy; + u32 seqnum; + + rcode = RCODE_TYPE_ERROR; + if (length < sizeof(struct snd_efw_transaction)) { + rcode = RCODE_DATA_ERROR; + goto end; + } else if (offset != MEMORY_SPACE_EFW_RESPONSE) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + + seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) { + handle_resp_for_kernel(card, generation, source, + data, length, &rcode, seqnum); + if (snd_efw_resp_buf_debug) + handle_resp_for_user(card, generation, source, + data, length, &dummy); + } else { + handle_resp_for_user(card, generation, source, + data, length, &rcode); + } +end: + fw_send_response(card, request, rcode); +} + +void snd_efw_transaction_add_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != NULL) + continue; + instances[i] = efw; + break; + } + + spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_remove_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != efw) + continue; + instances[i] = NULL; + } + + spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_bus_reset(struct fw_unit *unit) +{ + struct transaction_queue *t; + + spin_lock_irq(&transaction_queues_lock); + list_for_each_entry(t, &transaction_queues, list) { + if ((t->unit == unit) && + (t->state == STATE_PENDING)) { + t->state = STATE_BUS_RESET; + wake_up(&t->wait); + } + } + spin_unlock_irq(&transaction_queues_lock); +} + +static struct fw_address_handler resp_register_handler = { + .length = SND_EFW_RESPONSE_MAXIMUM_BYTES, + .address_callback = efw_response +}; + +int snd_efw_transaction_register(void) +{ + static const struct fw_address_region resp_register_region = { + .start = MEMORY_SPACE_EFW_RESPONSE, + .end = MEMORY_SPACE_EFW_RESPONSE + + SND_EFW_RESPONSE_MAXIMUM_BYTES + }; + return fw_core_add_address_handler(&resp_register_handler, + &resp_register_region); +} + +void snd_efw_transaction_unregister(void) +{ + WARN_ON(!list_empty(&transaction_queues)); + fw_core_remove_address_handler(&resp_register_handler); +} diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c new file mode 100644 index 000000000..0717ab9e4 --- /dev/null +++ b/sound/firewire/isight.c @@ -0,0 +1,743 @@ +/* + * Apple iSight audio driver + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib.h" +#include "iso-resources.h" +#include "packets-buffer.h" + +#define OUI_APPLE 0x000a27 +#define MODEL_APPLE_ISIGHT 0x000008 +#define SW_ISIGHT_AUDIO 0x000010 + +#define REG_AUDIO_ENABLE 0x000 +#define AUDIO_ENABLE 0x80000000 +#define REG_DEF_AUDIO_GAIN 0x204 +#define REG_GAIN_RAW_START 0x210 +#define REG_GAIN_RAW_END 0x214 +#define REG_GAIN_DB_START 0x218 +#define REG_GAIN_DB_END 0x21c +#define REG_SAMPLE_RATE_INQUIRY 0x280 +#define REG_ISO_TX_CONFIG 0x300 +#define SPEED_SHIFT 16 +#define REG_SAMPLE_RATE 0x400 +#define RATE_48000 0x80000000 +#define REG_GAIN 0x500 +#define REG_MUTE 0x504 + +#define MAX_FRAMES_PER_PACKET 475 + +#define QUEUE_LENGTH 20 + +struct isight { + struct snd_card *card; + struct fw_unit *unit; + struct fw_device *device; + u64 audio_base; + struct snd_pcm_substream *pcm; + struct mutex mutex; + struct iso_packets_buffer buffer; + struct fw_iso_resources resources; + struct fw_iso_context *context; + bool pcm_active; + bool pcm_running; + bool first_packet; + int packet_index; + u32 total_samples; + unsigned int buffer_pointer; + unsigned int period_counter; + s32 gain_min, gain_max; + unsigned int gain_tlv[4]; +}; + +struct audio_payload { + __be32 sample_count; + __be32 signature; + __be32 sample_total; + __be32 reserved; + __be16 samples[2 * MAX_FRAMES_PER_PACKET]; +}; + +MODULE_DESCRIPTION("iSight audio driver"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); + +static struct fw_iso_packet audio_packet = { + .payload_length = sizeof(struct audio_payload), + .interrupt = 1, + .header_length = 4, +}; + +static void isight_update_pointers(struct isight *isight, unsigned int count) +{ + struct snd_pcm_runtime *runtime = isight->pcm->runtime; + unsigned int ptr; + + smp_wmb(); /* update buffer data before buffer pointer */ + + ptr = isight->buffer_pointer; + ptr += count; + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + WRITE_ONCE(isight->buffer_pointer, ptr); + + isight->period_counter += count; + if (isight->period_counter >= runtime->period_size) { + isight->period_counter -= runtime->period_size; + snd_pcm_period_elapsed(isight->pcm); + } +} + +static void isight_samples(struct isight *isight, + const __be16 *samples, unsigned int count) +{ + struct snd_pcm_runtime *runtime; + unsigned int count1; + + if (!READ_ONCE(isight->pcm_running)) + return; + + runtime = isight->pcm->runtime; + if (isight->buffer_pointer + count <= runtime->buffer_size) { + memcpy(runtime->dma_area + isight->buffer_pointer * 4, + samples, count * 4); + } else { + count1 = runtime->buffer_size - isight->buffer_pointer; + memcpy(runtime->dma_area + isight->buffer_pointer * 4, + samples, count1 * 4); + samples += count1 * 2; + memcpy(runtime->dma_area, samples, (count - count1) * 4); + } + + isight_update_pointers(isight, count); +} + +static void isight_pcm_abort(struct isight *isight) +{ + if (READ_ONCE(isight->pcm_active)) + snd_pcm_stop_xrun(isight->pcm); +} + +static void isight_dropped_samples(struct isight *isight, unsigned int total) +{ + struct snd_pcm_runtime *runtime; + u32 dropped; + unsigned int count1; + + if (!READ_ONCE(isight->pcm_running)) + return; + + runtime = isight->pcm->runtime; + dropped = total - isight->total_samples; + if (dropped < runtime->buffer_size) { + if (isight->buffer_pointer + dropped <= runtime->buffer_size) { + memset(runtime->dma_area + isight->buffer_pointer * 4, + 0, dropped * 4); + } else { + count1 = runtime->buffer_size - isight->buffer_pointer; + memset(runtime->dma_area + isight->buffer_pointer * 4, + 0, count1 * 4); + memset(runtime->dma_area, 0, (dropped - count1) * 4); + } + isight_update_pointers(isight, dropped); + } else { + isight_pcm_abort(isight); + } +} + +static void isight_packet(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, void *data) +{ + struct isight *isight = data; + const struct audio_payload *payload; + unsigned int index, length, count, total; + int err; + + if (isight->packet_index < 0) + return; + index = isight->packet_index; + payload = isight->buffer.packets[index].buffer; + length = be32_to_cpup(header) >> 16; + + if (likely(length >= 16 && + payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) { + count = be32_to_cpu(payload->sample_count); + if (likely(count <= (length - 16) / 4)) { + total = be32_to_cpu(payload->sample_total); + if (unlikely(total != isight->total_samples)) { + if (!isight->first_packet) + isight_dropped_samples(isight, total); + isight->first_packet = false; + isight->total_samples = total; + } + + isight_samples(isight, payload->samples, count); + isight->total_samples += count; + } + } + + err = fw_iso_context_queue(isight->context, &audio_packet, + &isight->buffer.iso_buffer, + isight->buffer.packets[index].offset); + if (err < 0) { + dev_err(&isight->unit->device, "queueing error: %d\n", err); + isight_pcm_abort(isight); + isight->packet_index = -1; + return; + } + fw_iso_context_queue_flush(isight->context); + + if (++index >= QUEUE_LENGTH) + index = 0; + isight->packet_index = index; +} + +static int isight_connect(struct isight *isight) +{ + int ch, err; + __be32 value; + +retry_after_bus_reset: + ch = fw_iso_resources_allocate(&isight->resources, + sizeof(struct audio_payload), + isight->device->max_speed); + if (ch < 0) { + err = ch; + goto error; + } + + value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT)); + err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_ISO_TX_CONFIG, + &value, 4, FW_FIXED_GENERATION | + isight->resources.generation); + if (err == -EAGAIN) { + fw_iso_resources_free(&isight->resources); + goto retry_after_bus_reset; + } else if (err < 0) { + goto err_resources; + } + + return 0; + +err_resources: + fw_iso_resources_free(&isight->resources); +error: + return err; +} + +static int isight_open(struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4 * 1024 * 1024, + .period_bytes_min = MAX_FRAMES_PER_PACKET * 4, + .period_bytes_max = 1024 * 1024, + .periods_min = 2, + .periods_max = UINT_MAX, + }; + struct isight *isight = substream->private_data; + + substream->runtime->hw = hardware; + + return iso_packets_buffer_init(&isight->buffer, isight->unit, + QUEUE_LENGTH, + sizeof(struct audio_payload), + DMA_FROM_DEVICE); +} + +static int isight_close(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + iso_packets_buffer_destroy(&isight->buffer, isight->unit); + + return 0; +} + +static int isight_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct isight *isight = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + WRITE_ONCE(isight->pcm_active, true); + + return 0; +} + +static int reg_read(struct isight *isight, int offset, __be32 *value) +{ + return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + offset, value, 4, 0); +} + +static int reg_write(struct isight *isight, int offset, __be32 value) +{ + return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + offset, &value, 4, 0); +} + +static void isight_stop_streaming(struct isight *isight) +{ + __be32 value; + + if (!isight->context) + return; + + fw_iso_context_stop(isight->context); + fw_iso_context_destroy(isight->context); + isight->context = NULL; + fw_iso_resources_free(&isight->resources); + value = 0; + snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_AUDIO_ENABLE, + &value, 4, FW_QUIET); +} + +static int isight_hw_free(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + WRITE_ONCE(isight->pcm_active, false); + + mutex_lock(&isight->mutex); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int isight_start_streaming(struct isight *isight) +{ + unsigned int i; + int err; + + if (isight->context) { + if (isight->packet_index < 0) + isight_stop_streaming(isight); + else + return 0; + } + + err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000)); + if (err < 0) + goto error; + + err = isight_connect(isight); + if (err < 0) + goto error; + + err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE)); + if (err < 0) + goto err_resources; + + isight->context = fw_iso_context_create(isight->device->card, + FW_ISO_CONTEXT_RECEIVE, + isight->resources.channel, + isight->device->max_speed, + 4, isight_packet, isight); + if (IS_ERR(isight->context)) { + err = PTR_ERR(isight->context); + isight->context = NULL; + goto err_resources; + } + + for (i = 0; i < QUEUE_LENGTH; ++i) { + err = fw_iso_context_queue(isight->context, &audio_packet, + &isight->buffer.iso_buffer, + isight->buffer.packets[i].offset); + if (err < 0) + goto err_context; + } + + isight->first_packet = true; + isight->packet_index = 0; + + err = fw_iso_context_start(isight->context, -1, 0, + FW_ISO_CONTEXT_MATCH_ALL_TAGS/*?*/); + if (err < 0) + goto err_context; + + return 0; + +err_context: + fw_iso_context_destroy(isight->context); + isight->context = NULL; +err_resources: + fw_iso_resources_free(&isight->resources); + reg_write(isight, REG_AUDIO_ENABLE, 0); +error: + return err; +} + +static int isight_prepare(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + int err; + + isight->buffer_pointer = 0; + isight->period_counter = 0; + + mutex_lock(&isight->mutex); + err = isight_start_streaming(isight); + mutex_unlock(&isight->mutex); + + return err; +} + +static int isight_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct isight *isight = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + WRITE_ONCE(isight->pcm_running, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + WRITE_ONCE(isight->pcm_running, false); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + return READ_ONCE(isight->buffer_pointer); +} + +static int isight_create_pcm(struct isight *isight) +{ + static const struct snd_pcm_ops ops = { + .open = isight_open, + .close = isight_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = isight_hw_params, + .hw_free = isight_hw_free, + .prepare = isight_prepare, + .trigger = isight_trigger, + .pointer = isight_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(isight->card, "iSight", 0, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = isight; + strcpy(pcm->name, "iSight"); + isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + isight->pcm->ops = &ops; + + return 0; +} + +static int isight_gain_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + struct isight *isight = ctl->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = isight->gain_min; + info->value.integer.max = isight->gain_max; + + return 0; +} + +static int isight_gain_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 gain; + int err; + + err = reg_read(isight, REG_GAIN, &gain); + if (err < 0) + return err; + + value->value.integer.value[0] = (s32)be32_to_cpu(gain); + + return 0; +} + +static int isight_gain_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + + if (value->value.integer.value[0] < isight->gain_min || + value->value.integer.value[0] > isight->gain_max) + return -EINVAL; + + return reg_write(isight, REG_GAIN, + cpu_to_be32(value->value.integer.value[0])); +} + +static int isight_mute_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 mute; + int err; + + err = reg_read(isight, REG_MUTE, &mute); + if (err < 0) + return err; + + value->value.integer.value[0] = !mute; + + return 0; +} + +static int isight_mute_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + + return reg_write(isight, REG_MUTE, + (__force __be32)!value->value.integer.value[0]); +} + +static int isight_create_mixer(struct isight *isight) +{ + static const struct snd_kcontrol_new gain_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = isight_gain_info, + .get = isight_gain_get, + .put = isight_gain_put, + }; + static const struct snd_kcontrol_new mute_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Switch", + .info = snd_ctl_boolean_mono_info, + .get = isight_mute_get, + .put = isight_mute_put, + }; + __be32 value; + struct snd_kcontrol *ctl; + int err; + + err = reg_read(isight, REG_GAIN_RAW_START, &value); + if (err < 0) + return err; + isight->gain_min = be32_to_cpu(value); + + err = reg_read(isight, REG_GAIN_RAW_END, &value); + if (err < 0) + return err; + isight->gain_max = be32_to_cpu(value); + + isight->gain_tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_MINMAX; + isight->gain_tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); + + err = reg_read(isight, REG_GAIN_DB_START, &value); + if (err < 0) + return err; + isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN] = + (s32)be32_to_cpu(value) * 100; + + err = reg_read(isight, REG_GAIN_DB_END, &value); + if (err < 0) + return err; + isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX] = + (s32)be32_to_cpu(value) * 100; + + ctl = snd_ctl_new1(&gain_control, isight); + if (ctl) + ctl->tlv.p = isight->gain_tlv; + err = snd_ctl_add(isight->card, ctl); + if (err < 0) + return err; + + err = snd_ctl_add(isight->card, snd_ctl_new1(&mute_control, isight)); + if (err < 0) + return err; + + return 0; +} + +static void isight_card_free(struct snd_card *card) +{ + struct isight *isight = card->private_data; + + fw_iso_resources_destroy(&isight->resources); + fw_unit_put(isight->unit); + mutex_destroy(&isight->mutex); +} + +static u64 get_unit_base(struct fw_unit *unit) +{ + struct fw_csr_iterator i; + int key, value; + + fw_csr_iterator_init(&i, unit->directory); + while (fw_csr_iterator_next(&i, &key, &value)) + if (key == CSR_OFFSET) + return CSR_REGISTER_BASE + value * 4; + return 0; +} + +static int isight_probe(struct fw_unit *unit, + const struct ieee1394_device_id *id) +{ + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct isight *isight; + int err; + + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(*isight), &card); + if (err < 0) + return err; + + isight = card->private_data; + isight->card = card; + mutex_init(&isight->mutex); + isight->unit = fw_unit_get(unit); + isight->device = fw_dev; + isight->audio_base = get_unit_base(unit); + if (!isight->audio_base) { + dev_err(&unit->device, "audio unit base not found\n"); + err = -ENXIO; + goto error; + } + fw_iso_resources_init(&isight->resources, unit); + + card->private_free = isight_card_free; + + strcpy(card->driver, "iSight"); + strcpy(card->shortname, "Apple iSight"); + snprintf(card->longname, sizeof(card->longname), + "Apple iSight (GUID %08x%08x) at %s, S%d", + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, "iSight"); + + err = isight_create_pcm(isight); + if (err < 0) + goto error; + + err = isight_create_mixer(isight); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, isight); + + return 0; +error: + snd_card_free(card); + + mutex_destroy(&isight->mutex); + fw_unit_put(isight->unit); + + return err; +} + +static void isight_bus_reset(struct fw_unit *unit) +{ + struct isight *isight = dev_get_drvdata(&unit->device); + + if (fw_iso_resources_update(&isight->resources) < 0) { + isight_pcm_abort(isight); + + mutex_lock(&isight->mutex); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + } +} + +static void isight_remove(struct fw_unit *unit) +{ + struct isight *isight = dev_get_drvdata(&unit->device); + + isight_pcm_abort(isight); + + snd_card_disconnect(isight->card); + + mutex_lock(&isight->mutex); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + + snd_card_free_when_closed(isight->card); +} + +static const struct ieee1394_device_id isight_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .specifier_id = OUI_APPLE, + .version = SW_ISIGHT_AUDIO, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, isight_id_table); + +static struct fw_driver isight_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = isight_probe, + .update = isight_bus_reset, + .remove = isight_remove, + .id_table = isight_id_table, +}; + +static int __init alsa_isight_init(void) +{ + return driver_register(&isight_driver.driver); +} + +static void __exit alsa_isight_exit(void) +{ + driver_unregister(&isight_driver.driver); +} + +module_init(alsa_isight_init); +module_exit(alsa_isight_exit); diff --git a/sound/firewire/iso-resources.c b/sound/firewire/iso-resources.c new file mode 100644 index 000000000..066b5df66 --- /dev/null +++ b/sound/firewire/iso-resources.c @@ -0,0 +1,236 @@ +/* + * isochronous resources helper functions + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "iso-resources.h" + +/** + * fw_iso_resources_init - initializes a &struct fw_iso_resources + * @r: the resource manager to initialize + * @unit: the device unit for which the resources will be needed + * + * If the device does not support all channel numbers, change @r->channels_mask + * after calling this function. + */ +int fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit) +{ + r->channels_mask = ~0uLL; + r->unit = unit; + mutex_init(&r->mutex); + r->allocated = false; + + return 0; +} +EXPORT_SYMBOL(fw_iso_resources_init); + +/** + * fw_iso_resources_destroy - destroy a resource manager + * @r: the resource manager that is no longer needed + */ +void fw_iso_resources_destroy(struct fw_iso_resources *r) +{ + WARN_ON(r->allocated); + mutex_destroy(&r->mutex); +} +EXPORT_SYMBOL(fw_iso_resources_destroy); + +static unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed) +{ + unsigned int bytes, s400_bytes; + + /* iso packets have three header quadlets and quadlet-aligned payload */ + bytes = 3 * 4 + ALIGN(max_payload_bytes, 4); + + /* convert to bandwidth units (quadlets at S1600 = bytes at S400) */ + if (speed <= SCODE_400) + s400_bytes = bytes * (1 << (SCODE_400 - speed)); + else + s400_bytes = DIV_ROUND_UP(bytes, 1 << (speed - SCODE_400)); + + return s400_bytes; +} + +static int current_bandwidth_overhead(struct fw_card *card) +{ + /* + * Under the usual pessimistic assumption (cable length 4.5 m), the + * isochronous overhead for N cables is 1.797 µs + N * 0.494 µs, or + * 88.3 + N * 24.3 in bandwidth units. + * + * The calculation below tries to deduce N from the current gap count. + * If the gap count has been optimized by measuring the actual packet + * transmission time, this derived overhead should be near the actual + * overhead as well. + */ + return card->gap_count < 63 ? card->gap_count * 97 / 10 + 89 : 512; +} + +static int wait_isoch_resource_delay_after_bus_reset(struct fw_card *card) +{ + for (;;) { + s64 delay = (card->reset_jiffies + HZ) - get_jiffies_64(); + if (delay <= 0) + return 0; + if (schedule_timeout_interruptible(delay) > 0) + return -ERESTARTSYS; + } +} + +/** + * fw_iso_resources_allocate - allocate isochronous channel and bandwidth + * @r: the resource manager + * @max_payload_bytes: the amount of data (including CIP headers) per packet + * @speed: the speed (e.g., SCODE_400) at which the packets will be sent + * + * This function allocates one isochronous channel and enough bandwidth for the + * specified packet size. + * + * Returns the channel number that the caller must use for streaming, or + * a negative error code. Due to potentionally long delays, this function is + * interruptible and can return -ERESTARTSYS. On success, the caller is + * responsible for calling fw_iso_resources_update() on bus resets, and + * fw_iso_resources_free() when the resources are not longer needed. + */ +int fw_iso_resources_allocate(struct fw_iso_resources *r, + unsigned int max_payload_bytes, int speed) +{ + struct fw_card *card = fw_parent_device(r->unit)->card; + int bandwidth, channel, err; + + if (WARN_ON(r->allocated)) + return -EBADFD; + + r->bandwidth = packet_bandwidth(max_payload_bytes, speed); + +retry_after_bus_reset: + spin_lock_irq(&card->lock); + r->generation = card->generation; + r->bandwidth_overhead = current_bandwidth_overhead(card); + spin_unlock_irq(&card->lock); + + err = wait_isoch_resource_delay_after_bus_reset(card); + if (err < 0) + return err; + + mutex_lock(&r->mutex); + + bandwidth = r->bandwidth + r->bandwidth_overhead; + fw_iso_resource_manage(card, r->generation, r->channels_mask, + &channel, &bandwidth, true); + if (channel == -EAGAIN) { + mutex_unlock(&r->mutex); + goto retry_after_bus_reset; + } + if (channel >= 0) { + r->channel = channel; + r->allocated = true; + } else { + if (channel == -EBUSY) + dev_err(&r->unit->device, + "isochronous resources exhausted\n"); + else + dev_err(&r->unit->device, + "isochronous resource allocation failed\n"); + } + + mutex_unlock(&r->mutex); + + return channel; +} +EXPORT_SYMBOL(fw_iso_resources_allocate); + +/** + * fw_iso_resources_update - update resource allocations after a bus reset + * @r: the resource manager + * + * This function must be called from the driver's .update handler to reallocate + * any resources that were allocated before the bus reset. It is safe to call + * this function if no resources are currently allocated. + * + * Returns a negative error code on failure. If this happens, the caller must + * stop streaming. + */ +int fw_iso_resources_update(struct fw_iso_resources *r) +{ + struct fw_card *card = fw_parent_device(r->unit)->card; + int bandwidth, channel; + + mutex_lock(&r->mutex); + + if (!r->allocated) { + mutex_unlock(&r->mutex); + return 0; + } + + spin_lock_irq(&card->lock); + r->generation = card->generation; + r->bandwidth_overhead = current_bandwidth_overhead(card); + spin_unlock_irq(&card->lock); + + bandwidth = r->bandwidth + r->bandwidth_overhead; + + fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, + &channel, &bandwidth, true); + /* + * When another bus reset happens, pretend that the allocation + * succeeded; we will try again for the new generation later. + */ + if (channel < 0 && channel != -EAGAIN) { + r->allocated = false; + if (channel == -EBUSY) + dev_err(&r->unit->device, + "isochronous resources exhausted\n"); + else + dev_err(&r->unit->device, + "isochronous resource allocation failed\n"); + } + + mutex_unlock(&r->mutex); + + return channel; +} +EXPORT_SYMBOL(fw_iso_resources_update); + +/** + * fw_iso_resources_free - frees allocated resources + * @r: the resource manager + * + * This function deallocates the channel and bandwidth, if allocated. + */ +void fw_iso_resources_free(struct fw_iso_resources *r) +{ + struct fw_card *card; + int bandwidth, channel; + + /* Not initialized. */ + if (r->unit == NULL) + return; + card = fw_parent_device(r->unit)->card; + + mutex_lock(&r->mutex); + + if (r->allocated) { + bandwidth = r->bandwidth + r->bandwidth_overhead; + fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, + &channel, &bandwidth, false); + if (channel < 0) + dev_err(&r->unit->device, + "isochronous resource deallocation failed\n"); + + r->allocated = false; + } + + mutex_unlock(&r->mutex); +} +EXPORT_SYMBOL(fw_iso_resources_free); diff --git a/sound/firewire/iso-resources.h b/sound/firewire/iso-resources.h new file mode 100644 index 000000000..34f85e9e8 --- /dev/null +++ b/sound/firewire/iso-resources.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED +#define SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED + +#include +#include + +struct fw_unit; + +/** + * struct fw_iso_resources - manages channel/bandwidth allocation + * @channels_mask: if the device does not support all channel numbers, set this + * bit mask to something else than the default (all ones) + * + * This structure manages (de)allocation of isochronous resources (channel and + * bandwidth) for one isochronous stream. + */ +struct fw_iso_resources { + u64 channels_mask; + /* private: */ + struct fw_unit *unit; + struct mutex mutex; + unsigned int channel; + unsigned int bandwidth; /* in bandwidth units, without overhead */ + unsigned int bandwidth_overhead; + int generation; /* in which allocation is valid */ + bool allocated; +}; + +int fw_iso_resources_init(struct fw_iso_resources *r, + struct fw_unit *unit); +void fw_iso_resources_destroy(struct fw_iso_resources *r); + +int fw_iso_resources_allocate(struct fw_iso_resources *r, + unsigned int max_payload_bytes, int speed); +int fw_iso_resources_update(struct fw_iso_resources *r); +void fw_iso_resources_free(struct fw_iso_resources *r); + +#endif diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c new file mode 100644 index 000000000..39dfa7490 --- /dev/null +++ b/sound/firewire/lib.c @@ -0,0 +1,104 @@ +/* + * miscellaneous helper functions + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include "lib.h" + +#define ERROR_RETRY_DELAY_MS 20 + +/** + * snd_fw_transaction - send a request and wait for its completion + * @unit: the driver's unit on the target device + * @tcode: the transaction code + * @offset: the address in the target's address space + * @buffer: input/output data + * @length: length of @buffer + * @flags: use %FW_FIXED_GENERATION and add the generation value to attempt the + * request only in that generation; use %FW_QUIET to suppress error + * messages + * + * Submits an asynchronous request to the target device, and waits for the + * response. The node ID and the current generation are derived from @unit. + * On a bus reset or an error, the transaction is retried a few times. + * Returns zero on success, or a negative error code. + */ +int snd_fw_transaction(struct fw_unit *unit, int tcode, + u64 offset, void *buffer, size_t length, + unsigned int flags) +{ + struct fw_device *device = fw_parent_device(unit); + int generation, rcode, tries = 0; + + generation = flags & FW_GENERATION_MASK; + for (;;) { + if (!(flags & FW_FIXED_GENERATION)) { + generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + } + rcode = fw_run_transaction(device->card, tcode, + device->node_id, generation, + device->max_speed, offset, + buffer, length); + + if (rcode == RCODE_COMPLETE) + return 0; + + if (rcode == RCODE_GENERATION && (flags & FW_FIXED_GENERATION)) + return -EAGAIN; + + if (rcode_is_permanent_error(rcode) || ++tries >= 3) { + if (!(flags & FW_QUIET)) + dev_err(&unit->device, + "transaction failed: %s\n", + fw_rcode_string(rcode)); + return -EIO; + } + + msleep(ERROR_RETRY_DELAY_MS); + } +} +EXPORT_SYMBOL(snd_fw_transaction); + +#define PROBE_DELAY_MS (2 * MSEC_PER_SEC) + +/** + * snd_fw_schedule_registration - schedule work for sound card registration + * @unit: an instance for unit on IEEE 1394 bus + * @dwork: delayed work with callback function + * + * This function is not designed for general purposes. When new unit is + * connected to IEEE 1394 bus, the bus is under bus-reset state because of + * topological change. In this state, units tend to fail both of asynchronous + * and isochronous communication. To avoid this problem, this function is used + * to postpone sound card registration after the state. The callers must + * set up instance of delayed work in advance. + */ +void snd_fw_schedule_registration(struct fw_unit *unit, + struct delayed_work *dwork) +{ + u64 now, delay; + + now = get_jiffies_64(); + delay = fw_parent_device(unit)->card->reset_jiffies + + msecs_to_jiffies(PROBE_DELAY_MS); + + if (time_after64(delay, now)) + delay -= now; + else + delay = 0; + + mod_delayed_work(system_wq, dwork, delay); +} +EXPORT_SYMBOL(snd_fw_schedule_registration); + +MODULE_DESCRIPTION("FireWire audio helper functions"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h new file mode 100644 index 000000000..dc815dc39 --- /dev/null +++ b/sound/firewire/lib.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_LIB_H_INCLUDED +#define SOUND_FIREWIRE_LIB_H_INCLUDED + +#include +#include +#include +#include + +struct fw_unit; + +#define FW_GENERATION_MASK 0x00ff +#define FW_FIXED_GENERATION 0x0100 +#define FW_QUIET 0x0200 + +int snd_fw_transaction(struct fw_unit *unit, int tcode, + u64 offset, void *buffer, size_t length, + unsigned int flags); + +/* returns true if retrying the transaction would not make sense */ +static inline bool rcode_is_permanent_error(int rcode) +{ + return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR; +} + +void snd_fw_schedule_registration(struct fw_unit *unit, + struct delayed_work *dwork); + +#endif diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 000000000..7c502d351 --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS_amdtp-motu.o := -I$(src) + +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ + motu-protocol-v2.o motu-protocol-v3.o +obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu-trace.h b/sound/firewire/motu/amdtp-motu-trace.h new file mode 100644 index 000000000..cd0cbfa9f --- /dev/null +++ b/sound/firewire/motu/amdtp-motu-trace.h @@ -0,0 +1,123 @@ +/* + * amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2017 Takashi Sakamoto + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_motu + +#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SND_FIREWIRE_MOTU_TRACE_H + +#include + +static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); +static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); + +TRACE_EVENT(in_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(out_data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(in_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +TRACE_EVENT(out_data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-motu-trace +#include diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 000000000..6c9b743ea --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,429 @@ +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include "motu.h" + +#define CREATE_TRACE_POINTS +#include "amdtp-motu-trace.h" + +#define CIP_FMT_MOTU 0x02 +#define CIP_FMT_MOTU_TX_V3 0x22 +#define MOTU_FDF_AM824 0x22 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_ticks; + unsigned int next_accumulated; + unsigned int next_cycles; + unsigned int next_seconds; + + unsigned int pcm_chunks; + unsigned int pcm_byte_offset; + + struct snd_rawmidi_substream *midi; + unsigned int midi_ports; + unsigned int midi_flag_offset; + unsigned int midi_byte_offset; + + int midi_db_count; + unsigned int midi_db_interval; +}; + +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats) +{ + static const struct { + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + } params[] = { + [CIP_SFC_44100] = { 557, 123 }, + [CIP_SFC_48000] = { 512, 0 }, + [CIP_SFC_88200] = { 278, 282 }, + [CIP_SFC_96000] = { 256, 0 }, + [CIP_SFC_176400] = { 139, 141 }, + [CIP_SFC_192000] = { 128, 0 }, + }; + struct amdtp_motu *p = s->protocol; + unsigned int pcm_chunks, data_chunks, data_block_quadlets; + unsigned int delay; + unsigned int mode; + int i, err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) { + mode = i >> 1; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + pcm_chunks = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + data_chunks = formats->msg_chunks + pcm_chunks; + + /* + * Each data block includes SPH in its head. Data chunks follow with + * 3 byte alignment. Padding follows with zero to conform to quadlet + * alignment. + */ + data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4); + + err = amdtp_stream_set_parameters(s, rate, data_block_quadlets); + if (err < 0) + return err; + + p->pcm_chunks = pcm_chunks; + p->pcm_byte_offset = formats->pcm_byte_offset; + + p->midi_ports = midi_ports; + p->midi_flag_offset = formats->midi_flag_offset; + p->midi_byte_offset = formats->midi_byte_offset; + + p->midi_db_count = 0; + p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND; + + /* IEEE 1394 bus requires. */ + delay = 0x2e00; + + /* For no-data or empty packets to adjust PCM sampling frequency. */ + delay += 8000 * 3072 * s->syt_interval / rate; + + p->next_seconds = 0; + p->next_cycles = delay / 3072; + p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event; + p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event; + p->next_ticks = delay % 3072; + p->next_accumulated = 0; + + return 0; +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + u32 *dst; + + channels = p->pcm_chunks; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | + (byte[1] << 16) | + (byte[2] << 8); + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + const u32 *src; + + channels = p->pcm_chunks; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = (*src >> 24) & 0xff; + byte[1] = (*src >> 16) & 0xff; + byte[2] = (*src >> 8) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_chunks; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* TODO: how to set an constraint for exactly 24bit PCM sample? */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi, midi); +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi = READ_ONCE(p->midi); + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + + if (midi && p->midi_db_count == 0 && + snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) { + b[p->midi_flag_offset] = 0x01; + } else { + b[p->midi_byte_offset] = 0x00; + b[p->midi_flag_offset] = 0x00; + } + + buffer += s->data_block_quadlets; + + if (--p->midi_db_count < 0) + p->midi_db_count = p->midi_db_interval; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi; + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + midi = READ_ONCE(p->midi); + + if (midi && (b[p->midi_flag_offset] & 0x01)) + snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1); + + buffer += s->data_block_quadlets; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + for (i = 0; i < data_blocks; ++i) { + *frames = be32_to_cpu(*buffer); + buffer += data_block_quadlets; + frames++; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_message(u64 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + /* This is just for v2/v3 protocol. */ + for (i = 0; i < data_blocks; ++i) { + *frames = (be32_to_cpu(buffer[1]) << 16) | + (be32_to_cpu(buffer[2]) >> 16); + buffer += data_block_quadlets; + frames++; + } +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_motu *p = s->protocol; + struct snd_pcm_substream *pcm; + + trace_in_data_block_sph(s, data_blocks, buffer); + trace_in_data_block_message(s, data_blocks, buffer); + + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + + pcm = READ_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + read_pcm_s32(s, pcm->runtime, buffer, data_blocks); + + return data_blocks; +} + +static inline void compute_next_elapse_from_start(struct amdtp_motu *p) +{ + p->next_accumulated += p->remainder_ticks_per_event; + if (p->next_accumulated >= 441) { + p->next_accumulated -= 441; + p->next_ticks++; + } + + p->next_ticks += p->quotient_ticks_per_event; + if (p->next_ticks >= 3072) { + p->next_ticks -= 3072; + p->next_cycles++; + } + + if (p->next_cycles >= 8000) { + p->next_cycles -= 8000; + p->next_seconds++; + } + + if (p->next_seconds >= 128) + p->next_seconds -= 128; +} + +static void write_sph(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int next_cycles; + unsigned int i; + u32 sph; + + for (i = 0; i < data_blocks; i++) { + next_cycles = (s->start_cycle + p->next_cycles) % 8000; + sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff; + *buffer = cpu_to_be32(sph); + + compute_next_elapse_from_start(p); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; + struct snd_pcm_substream *pcm; + + /* Not used. */ + *syt = 0xffff; + + /* TODO: how to interact control messages between userspace? */ + + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + pcm = READ_ONCE(s->pcm); + if (pcm) + write_pcm_s32(s, pcm->runtime, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + write_sph(s, buffer, data_blocks); + + trace_out_data_block_sph(s, data_blocks, buffer); + trace_out_data_block_message(s, data_blocks, buffer); + + return data_blocks; +} + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + int fmt = CIP_FMT_MOTU; + int flags = CIP_BLOCKING; + int err; + + if (dir == AMDTP_IN_STREAM) { + process_data_blocks = process_tx_data_blocks; + + /* + * Units of version 3 transmits packets with invalid CIP header + * against IEC 61883-1. + */ + if (protocol == &snd_motu_protocol_v3) { + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK | + CIP_HEADER_WITHOUT_EOH; + fmt = CIP_FMT_MOTU_TX_V3; + } + } else { + process_data_blocks = process_rx_data_blocks; + flags |= CIP_DBC_IS_END_EVENT; + } + + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + + s->sph = 1; + s->fdf = MOTU_FDF_AM824; + + return 0; +} diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 000000000..5f772eab5 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,198 @@ +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed && motu->msg == 0) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + memset(&event, 0, sizeof(event)); + if (motu->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (motu->dev_lock_count > 0); + motu->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; + event.motu_notification.message = motu->msg; + motu->msg = 0; + + count = min_t(long, count, sizeof(event.motu_notification)); + } + + spin_unlock_irq(&motu->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + __poll_t events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed || motu->msg) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | EPOLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_MOTU; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_motu_create_hwdep_device(struct snd_motu *motu) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU; + hwdep->ops = ops; + hwdep->private_data = motu; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 000000000..e55cab6d7 --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,169 @@ +/* + * motu-midi.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "motu.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + motu->capture_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + motu->playback_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->capture_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->playback_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void set_midi_substream_names(struct snd_motu *motu, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(motu, str); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(motu, str); + + return 0; +} diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 000000000..ab69d7e6a --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,392 @@ +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (!snd_interval_test(c, pcm_channels)) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + channels.min = min(channels.min, pcm_channels); + channels.max = max(channels.max, pcm_channels); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + struct snd_motu_packet_format *formats) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, pcm_channels, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (pcm_channels == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels); + hw->channels_max = max(hw->channels_max, pcm_channels); + } + + snd_pcm_limit_hw_rates(runtime); +} + +static int init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + struct snd_motu_packet_format *formats; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + formats = &motu->tx_packet_formats; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->rx_stream; + formats = &motu->rx_packet_formats; + } + + limit_channels_and_rates(motu, runtime, formats); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_motu_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + enum snd_motu_clock_source src; + unsigned int rate; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + err = snd_motu_stream_cache_packet_formats(motu); + if (err < 0) + goto err_locked; + + err = init_hw_info(motu, substream); + if (err < 0) + goto err_locked; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + err = protocol->get_clock_source(motu, &src); + if (err < 0) + goto err_locked; + if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL || + amdtp_stream_pcm_running(&motu->tx_stream) || + amdtp_stream_pcm_running(&motu->rx_stream)) { + err = protocol->get_clock_rate(motu, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + mutex_unlock(&motu->mutex); + + return err; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + + return 0; +} + +static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->capture_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} +static int playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->playback_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} + +static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->capture_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->playback_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->rx_stream); +} + +static int capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_ack(&motu->tx_stream); +} + +static int playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_ack(&motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .ack = capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_hw_params, + .hw_free = playback_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .ack = playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + + return 0; +} diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 000000000..015b4ec27 --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,118 @@ +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PDIF on coaxial interface", + [SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface", + [SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int rate; + enum snd_motu_clock_source source; + + if (protocol->get_clock_rate(motu, &rate) < 0) + return; + if (protocol->get_clock_source(motu, &source) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]); +} + +static void proc_read_format(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int mode; + struct snd_motu_packet_format *formats; + int i; + + if (protocol->cache_packet_formats(motu) < 0) + return; + + snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->tx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } + + snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->rx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } +} + +static void add_node(struct snd_motu *motu, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(motu->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, motu, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(motu->card, "firewire", + motu->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(motu, root, "clock", proc_read_clock); + add_node(motu, root, "format", proc_read_format); +} diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c new file mode 100644 index 000000000..453fc29fa --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -0,0 +1,268 @@ +/* + * motu-protocol-v2.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define V2_CLOCK_STATUS_OFFSET 0x0b14 +#define V2_CLOCK_RATE_MASK 0x00000038 +#define V2_CLOCK_RATE_SHIFT 3 +#define V2_CLOCK_SRC_MASK 0x00000007 +#define V2_CLOCK_SRC_SHIFT 0 +#define V2_CLOCK_TRAVELER_FETCH_DISABLE 0x04000000 +#define V2_CLOCK_TRAVELER_FETCH_ENABLE 0x03000000 + +#define V2_IN_OUT_CONF_OFFSET 0x0c04 +#define V2_OPT_OUT_IFACE_MASK 0x00000c00 +#define V2_OPT_OUT_IFACE_SHIFT 10 +#define V2_OPT_IN_IFACE_MASK 0x00000300 +#define V2_OPT_IN_IFACE_SHIFT 8 +#define V2_OPT_IFACE_MODE_NONE 0 +#define V2_OPT_IFACE_MODE_ADAT 1 +#define V2_OPT_IFACE_MODE_SPDIF 2 + +static int v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = (be32_to_cpu(reg) & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT; + if (index >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[index]; + + return 0; +} + +static int v2_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~V2_CLOCK_RATE_MASK; + data |= i << V2_CLOCK_RATE_SHIFT; + + if (motu->spec == &snd_motu_spec_traveler) { + data &= ~V2_CLOCK_TRAVELER_FETCH_ENABLE; + data |= V2_CLOCK_TRAVELER_FETCH_DISABLE; + } + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK; + if (index > 5) + return -EIO; + + /* To check the configuration of optical interface. */ + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + switch (index) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 1: + if (be32_to_cpu(reg) & 0x00000200) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT; + break; + case 2: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 5: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB; + break; + default: + return -EIO; + } + + return 0; +} + +static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + __be32 reg; + u32 data; + int err = 0; + + if (motu->spec == &snd_motu_spec_traveler) { + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V2_CLOCK_TRAVELER_FETCH_DISABLE | + V2_CLOCK_TRAVELER_FETCH_ENABLE); + + if (enable) + data |= V2_CLOCK_TRAVELER_FETCH_ENABLE; + else + data |= V2_CLOCK_TRAVELER_FETCH_DISABLE; + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, + ®, sizeof(reg)); + } + + return err; +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + if (flags & SND_MOTU_SPEC_RX_SEPARETED_MAIN) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + + // Packets to v2 units include 2 chunks for phone 1/2, except + // for 176.4/192.0 kHz. + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + + if (flags & SND_MOTU_SPEC_HAS_AESEBU_IFACE) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + + /* + * All of v2 models have a pair of coaxial interfaces for digital in/out + * port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these + * ports. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + formats->fixed_part_pcm_chunks[0] = pcm_chunks[0]; + formats->fixed_part_pcm_chunks[1] = pcm_chunks[1]; + formats->fixed_part_pcm_chunks[2] = pcm_chunks[2]; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, + u32 data, u32 mask, u32 shift) +{ + unsigned char pcm_chunks[2] = {0, 0}; + + /* + * When optical interfaces are configured for S/PDIF (TOSLINK), + * the above PCM frames come from them, instead of coaxial + * interfaces. + */ + data = (data & mask) >> shift; + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && + data == V2_OPT_IFACE_MODE_ADAT) { + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + + /* At mode x4, no data chunks are supported in this part. */ + formats->differed_part_pcm_chunks[0] = pcm_chunks[0]; + formats->differed_part_pcm_chunks[1] = pcm_chunks[1]; +} + +static int v2_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags, + data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags, + data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT); + + motu->tx_packet_formats.pcm_byte_offset = 10; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v2 = { + .get_clock_rate = v2_get_clock_rate, + .set_clock_rate = v2_set_clock_rate, + .get_clock_source = v2_get_clock_source, + .switch_fetching_mode = v2_switch_fetching_mode, + .cache_packet_formats = v2_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c new file mode 100644 index 000000000..7cc80a05e --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -0,0 +1,315 @@ +/* + * motu-protocol-v3.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "motu.h" + +#define V3_CLOCK_STATUS_OFFSET 0x0b14 +#define V3_FETCH_PCM_FRAMES 0x02000000 +#define V3_CLOCK_RATE_MASK 0x0000ff00 +#define V3_CLOCK_RATE_SHIFT 8 +#define V3_CLOCK_SOURCE_MASK 0x000000ff + +#define V3_OPT_IFACE_MODE_OFFSET 0x0c94 +#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 +#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 +#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 +#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 +#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 +#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 +#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 +#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 + +static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; + if (data >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[data]; + + return 0; +} + +static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + bool need_to_wait; + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); + data |= i << V3_CLOCK_RATE_SHIFT; + + need_to_wait = data != be32_to_cpu(reg); + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + if (need_to_wait) { + /* Cost expensive. */ + if (msleep_interruptible(4000) > 0) + return -EINTR; + } + + return 0; +} + +static int v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + u32 data; + unsigned int val; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + val = data & V3_CLOCK_SOURCE_MASK; + if (val == 0x00) { + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + } else if (val == 0x01) { + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + } else if (val == 0x10) { + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + } else if (val == 0x18 || val == 0x19) { + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (val == 0x18) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_A) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; + } else { + if (data & V3_NO_ADAT_OPT_IN_IFACE_B) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B; + } + } else { + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + } + + return 0; +} + +static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return 0; + data = be32_to_cpu(reg); + + if (enable) + data |= V3_FETCH_PCM_FRAMES; + else + data &= ~V3_FETCH_PCM_FRAMES; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + if (flags & SND_MOTU_SPEC_RX_SEPARETED_MAIN) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + + // Packets to v3 units include 2 chunks for phone 1/2, except + // for 176.4/192.0 kHz. + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + + if (flags & SND_MOTU_SPEC_HAS_AESEBU_IFACE) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + + /* + * At least, packets have two data chunks for S/PDIF on coaxial + * interface. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* + * Fixed part consists of PCM chunks multiple of 4, with msg chunks. As + * a result, this part can includes empty data chunks. + */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, u32 data, + u32 a_enable_mask, u32 a_no_adat_mask, + u32 b_enable_mask, u32 b_no_adat_mask) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + int i; + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) { + if (data & a_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface A. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * A. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) { + if (data & b_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface B. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * B. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + for (i = 0; i < 3; ++i) { + if (pcm_chunks[i] > 0) + pcm_chunks[i] = round_up(pcm_chunks[i], 4); + + formats->differed_part_pcm_chunks[i] = pcm_chunks[i]; + } +} + +static int v3_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A, + V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A, + V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B); + + motu->tx_packet_formats.pcm_byte_offset = 10; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v3 = { + .get_clock_rate = v3_get_clock_rate, + .set_clock_rate = v3_set_clock_rate, + .get_clock_source = v3_get_clock_source, + .switch_fetching_mode = v3_switch_fetching_mode, + .cache_packet_formats = v3_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 000000000..483a8771d --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,415 @@ +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define ISOC_COMM_CONTROL_OFFSET 0x0b00 +#define ISOC_COMM_CONTROL_MASK 0xffff0000 +#define CHANGE_RX_ISOC_COMM_STATE 0x80000000 +#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000 +#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000 +#define RX_ISOC_COMM_CHANNEL_SHIFT 24 +#define CHANGE_TX_ISOC_COMM_STATE 0x00800000 +#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000 +#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000 +#define TX_ISOC_COMM_CHANNEL_SHIFT 16 + +#define PACKET_FORMAT_OFFSET 0x0b10 +#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080 +#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040 +#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f + +static int start_both_streams(struct snd_motu *motu, unsigned int rate) +{ + unsigned int midi_ports = 0; + __be32 reg; + u32 data; + int err; + + if ((motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q)) + midi_ports = 1; + + /* Set packet formation to our packet streaming engine. */ + err = amdtp_motu_set_parameters(&motu->rx_stream, rate, midi_ports, + &motu->rx_packet_formats); + if (err < 0) + return err; + + if ((motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q)) + midi_ports = 1; + else + midi_ports = 0; + + err = amdtp_motu_set_parameters(&motu->tx_stream, rate, midi_ports, + &motu->tx_packet_formats); + if (err < 0) + return err; + + /* Get isochronous resources on the bus. */ + err = fw_iso_resources_allocate(&motu->rx_resources, + amdtp_stream_get_max_payload(&motu->rx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + err = fw_iso_resources_allocate(&motu->tx_resources, + amdtp_stream_get_max_payload(&motu->tx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + /* Configure the unit to start isochronous communication. */ + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK; + + data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED | + (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) | + CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED | + (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT); + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +static void stop_both_streams(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = motu->spec->protocol->switch_fetching_mode(motu, false); + if (err < 0) + return; + + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return; + data = be32_to_cpu(reg); + + data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED); + data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE; + + reg = cpu_to_be32(data); + snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); +} + +static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + int err; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + err = amdtp_stream_start(stream, resources->channel, + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); + return -ETIMEDOUT; + } + + return 0; +} + +static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); +} + +int snd_motu_stream_cache_packet_formats(struct snd_motu *motu) +{ + int err; + + err = motu->spec->protocol->cache_packet_formats(motu); + if (err < 0) + return err; + + if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) { + motu->tx_packet_formats.midi_flag_offset = 4; + motu->tx_packet_formats.midi_byte_offset = 6; + } else if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q) { + motu->tx_packet_formats.midi_flag_offset = 8; + motu->tx_packet_formats.midi_byte_offset = 7; + } + + if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) { + motu->rx_packet_formats.midi_flag_offset = 4; + motu->rx_packet_formats.midi_byte_offset = 6; + } else if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q) { + motu->rx_packet_formats.midi_flag_offset = 8; + motu->rx_packet_formats.midi_byte_offset = 7; + } + + return 0; +} + +static int ensure_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS | + RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS| + TX_PACKET_TRANSMISSION_SPEED_MASK); + if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + data |= fw_parent_device(motu->unit)->max_speed; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate) +{ + const struct snd_motu_protocol *protocol = motu->spec->protocol; + unsigned int curr_rate; + int err = 0; + + if (motu->capture_substreams == 0 && motu->playback_substreams == 0) + return 0; + + /* Some packet queueing errors. */ + if (amdtp_streaming_error(&motu->rx_stream) || + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + err = snd_motu_stream_cache_packet_formats(motu); + if (err < 0) + return err; + + /* Stop stream if rate is different. */ + err = protocol->get_clock_rate(motu, &curr_rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to get sampling rate: %d\n", err); + return err; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + err = protocol->set_clock_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate: %d\n", err); + return err; + } + + err = ensure_packet_formats(motu); + if (err < 0) + return err; + + err = start_both_streams(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start isochronous comm: %d\n", err); + goto stop_streams; + } + + err = start_isoc_ctx(motu, &motu->rx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IT context: %d\n", err); + goto stop_streams; + } + + err = protocol->switch_fetching_mode(motu, true); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to enable frame fetching: %d\n", err); + goto stop_streams; + } + } + + if (!amdtp_stream_running(&motu->tx_stream) && + motu->capture_substreams > 0) { + err = start_isoc_ctx(motu, &motu->tx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IR context: %d", err); + amdtp_stream_stop(&motu->rx_stream); + goto stop_streams; + } + } + + return 0; + +stop_streams: + stop_both_streams(motu); + return err; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->capture_substreams == 0) { + if (amdtp_stream_running(&motu->tx_stream)) + stop_isoc_ctx(motu, &motu->tx_stream); + + if (motu->playback_substreams == 0) { + if (amdtp_stream_running(&motu->rx_stream)) + stop_isoc_ctx(motu, &motu->rx_stream); + stop_both_streams(motu); + } + } +} + +static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir) +{ + int err; + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + err = fw_iso_resources_init(resources, motu->unit); + if (err < 0) + return err; + + err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } + + return err; +} + +static void destroy_stream(struct snd_motu *motu, + enum amdtp_stream_direction dir) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + err = init_stream(motu, AMDTP_IN_STREAM); + if (err < 0) + return err; + + err = init_stream(motu, AMDTP_OUT_STREAM); + if (err < 0) + destroy_stream(motu, AMDTP_IN_STREAM); + + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_motu_stream_destroy_duplex(struct snd_motu *motu) +{ + destroy_stream(motu, AMDTP_IN_STREAM); + destroy_stream(motu, AMDTP_OUT_STREAM); + + motu->playback_substreams = 0; + motu->capture_substreams = 0; +} + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 000000000..7fc30091e --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,137 @@ +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + + +#include "motu.h" + +#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL +#define ASYNC_ADDR_HI 0x0b04 +#define ASYNC_ADDR_LO 0x0b08 + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_READ_QUADLET_REQUEST; + else + tcode = TCODE_READ_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_WRITE_QUADLET_REQUEST; + else + tcode = TCODE_WRITE_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_motu *motu = callback_data; + __be32 *buf = (__be32 *)data; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_COMPLETE); + return; + } + + if (offset != motu->async_handler.offset || length != 4) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + + fw_send_response(card, request, RCODE_COMPLETE); + + wake_up(&motu->hwdep_wait); +} + +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + if (motu->async_handler.callback_data == NULL) + return -EINVAL; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, + sizeof(data)); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, + sizeof(data)); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data)); + snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data)); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 000000000..e240fdfca --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,314 @@ +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define OUI_MOTU 0x0001f2 + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_MODEL: + version = val; + break; + } + } + + strcpy(motu->card->driver, "FW-MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(motu->card->mixername, motu->spec->name); + snprintf(motu->card->longname, sizeof(motu->card->longname), + "MOTU %s (version:%06x), GUID %08x%08x at %s, S%d", + motu->spec->name, version, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_free(struct snd_motu *motu) +{ + snd_motu_transaction_unregister(motu); + + snd_motu_stream_destroy_duplex(motu); + fw_unit_put(motu->unit); + + mutex_destroy(&motu->mutex); + kfree(motu); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void motu_card_free(struct snd_card *card) +{ + motu_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work); + int err; + + if (motu->registered) + return; + + err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0, + &motu->card); + if (err < 0) + return; + + name_card(motu); + + err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + + err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + + snd_motu_proc_init(motu); + + err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + + if ((motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q)) { + err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + } + + err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + + err = snd_card_register(motu->card); + if (err < 0) + goto error; + + /* + * After registered, motu instance can be released corresponding to + * releasing the sound card instance. + */ + motu->card->private_free = motu_card_free; + motu->card->private_data = motu; + motu->registered = true; + + return; +error: + snd_motu_transaction_unregister(motu); + snd_motu_stream_destroy_duplex(motu); + snd_card_free(motu->card); + dev_info(&motu->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_motu *motu; + + /* Allocate this independently of sound card instance. */ + motu = kzalloc(sizeof(struct snd_motu), GFP_KERNEL); + if (motu == NULL) + return -ENOMEM; + + motu->spec = (const struct snd_motu_spec *)entry->driver_data; + motu->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, motu); + + mutex_init(&motu->mutex); + spin_lock_init(&motu->lock); + init_waitqueue_head(&motu->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); + snd_fw_schedule_registration(unit, &motu->dwork); + + return 0; +} + +static void motu_remove(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&motu->dwork); + + if (motu->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); + } else { + /* Don't forget this case. */ + motu_free(motu); + } +} + +static void motu_bus_update(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!motu->registered) + snd_fw_schedule_registration(unit, &motu->dwork); + + /* The handler address register becomes initialized. */ + snd_motu_transaction_reregister(motu); +} + +static const struct snd_motu_spec motu_828mk2 = { + .name = "828mk2", + .protocol = &snd_motu_protocol_v2, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_RX_SEPARETED_MAIN | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_2ND_Q, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + +const struct snd_motu_spec snd_motu_spec_traveler = { + .name = "Traveler", + .protocol = &snd_motu_protocol_v2, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_HAS_AESEBU_IFACE | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_2ND_Q, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + +static const struct snd_motu_spec motu_828mk3 = { + .name = "828mk3", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_TX_REVERB_CHUNK | + SND_MOTU_SPEC_RX_SEPARETED_MAIN | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_OPT_IFACE_B | + SND_MOTU_SPEC_RX_MIDI_3RD_Q | + SND_MOTU_SPEC_TX_MIDI_3RD_Q, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + +static const struct snd_motu_spec motu_audio_express = { + .name = "AudioExpress", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_RX_SEPARETED_MAIN | + SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_3RD_Q, + .analog_in_ports = 2, + .analog_out_ports = 4, +}; + +static const struct snd_motu_spec motu_4pre = { + .name = "4pre", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_RX_SEPARETED_MAIN, + .analog_in_ports = 2, + .analog_out_ports = 2, +}; + +#define SND_MOTU_DEV_ENTRY(model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_SPECIFIER_ID | \ + IEEE1394_MATCH_VERSION, \ + .vendor_id = OUI_MOTU, \ + .specifier_id = OUI_MOTU, \ + .version = model, \ + .driver_data = (kernel_ulong_t)data, \ +} + +static const struct ieee1394_device_id motu_id_table[] = { + SND_MOTU_DEV_ENTRY(0x000003, &motu_828mk2), + SND_MOTU_DEV_ENTRY(0x000009, &snd_motu_spec_traveler), + SND_MOTU_DEV_ENTRY(0x000015, &motu_828mk3), /* FireWire only. */ + SND_MOTU_DEV_ENTRY(0x000035, &motu_828mk3), /* Hybrid. */ + SND_MOTU_DEV_ENTRY(0x000033, &motu_audio_express), + SND_MOTU_DEV_ENTRY(0x000045, &motu_4pre), + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_update, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 000000000..fd5327d30 --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,168 @@ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; + unsigned char pcm_byte_offset; + + unsigned char msg_chunks; + unsigned char fixed_part_pcm_chunks[3]; + unsigned char differed_part_pcm_chunks[3]; +}; + +struct snd_motu { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + /* Model dependent information. */ + const struct snd_motu_spec *spec; + + /* For packet streaming */ + struct snd_motu_packet_format tx_packet_formats; + struct snd_motu_packet_format rx_packet_formats; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + unsigned int capture_substreams; + unsigned int playback_substreams; + + /* For notification. */ + struct fw_address_handler async_handler; + u32 msg; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; +}; + +enum snd_motu_spec_flags { + SND_MOTU_SPEC_SUPPORT_CLOCK_X2 = 0x0001, + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 = 0x0002, + SND_MOTU_SPEC_TX_MICINST_CHUNK = 0x0004, + SND_MOTU_SPEC_TX_RETURN_CHUNK = 0x0008, + SND_MOTU_SPEC_TX_REVERB_CHUNK = 0x0010, + SND_MOTU_SPEC_HAS_AESEBU_IFACE = 0x0020, + SND_MOTU_SPEC_HAS_OPT_IFACE_A = 0x0040, + SND_MOTU_SPEC_HAS_OPT_IFACE_B = 0x0080, + SND_MOTU_SPEC_RX_MIDI_2ND_Q = 0x0100, + SND_MOTU_SPEC_RX_MIDI_3RD_Q = 0x0200, + SND_MOTU_SPEC_TX_MIDI_2ND_Q = 0x0400, + SND_MOTU_SPEC_TX_MIDI_3RD_Q = 0x0800, + SND_MOTU_SPEC_RX_SEPARETED_MAIN = 0x1000, +}; + +#define SND_MOTU_CLOCK_RATE_COUNT 6 +extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT]; + +enum snd_motu_clock_source { + SND_MOTU_CLOCK_SOURCE_INTERNAL, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX, + SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR, + SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC, + SND_MOTU_CLOCK_SOURCE_UNKNOWN, +}; + +struct snd_motu_protocol { + int (*get_clock_rate)(struct snd_motu *motu, unsigned int *rate); + int (*set_clock_rate)(struct snd_motu *motu, unsigned int rate); + int (*get_clock_source)(struct snd_motu *motu, + enum snd_motu_clock_source *source); + int (*switch_fetching_mode)(struct snd_motu *motu, bool enable); + int (*cache_packet_formats)(struct snd_motu *motu); +}; + +struct snd_motu_spec { + const char *const name; + enum snd_motu_spec_flags flags; + + unsigned char analog_in_ports; + unsigned char analog_out_ports; + + const struct snd_motu_protocol *const protocol; +}; + +extern const struct snd_motu_protocol snd_motu_protocol_v2; +extern const struct snd_motu_protocol snd_motu_protocol_v3; + +extern const struct snd_motu_spec snd_motu_spec_traveler; + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats); +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_cache_packet_formats(struct snd_motu *motu); +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu); + +void snd_motu_proc_init(struct snd_motu *motu); + +int snd_motu_create_pcm_devices(struct snd_motu *motu); + +int snd_motu_create_midi_devices(struct snd_motu *motu); + +int snd_motu_create_hwdep_device(struct snd_motu *motu); +#endif diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile new file mode 100644 index 000000000..b474da7c6 --- /dev/null +++ b/sound/firewire/oxfw/Makefile @@ -0,0 +1,3 @@ +snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-pcm.o oxfw-proc.o \ + oxfw-midi.o oxfw-hwdep.o oxfw-spkr.o oxfw-scs1x.o oxfw.o +obj-$(CONFIG_SND_OXFW) += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-command.c b/sound/firewire/oxfw/oxfw-command.c new file mode 100644 index 000000000..ac3e2e301 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-command.c @@ -0,0 +1,159 @@ +/* + * oxfw_command.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len) +{ + u8 *buf; + int err; + + buf = kmalloc(len + 10, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = 0xc0; /* SINGLE subfunction */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* UNIT */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* Support status in response */ + memcpy(buf + 10, format, len); + + /* do transaction and check buf[1-8] are the same against command */ + err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err < 0) + ; + else if (err < len + 10) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else + err = 0; + + kfree(buf); + + return err; +} + +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid) +{ + unsigned int subfunc; + int err; + + if (eid == 0xff) + subfunc = 0xc0; /* SINGLE */ + else + subfunc = 0xc1; /* LIST */ + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = subfunc; /* SINGLE or LIST */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* Unit */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* support status in response */ + buf[10] = 0xff & eid; /* entry ID for LIST subfunction */ + buf[11] = 0xff; /* padding */ + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) + ; + else if (err < 12) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + /* LIST subfunction has entry ID */ + else if ((subfunc == 0xc1) && (buf[10] != eid)) + err = -EIO; + if (err < 0) + goto end; + + /* keep just stream format information */ + if (subfunc == 0xc0) { + memmove(buf, buf + 10, err - 10); + *len = err - 10; + } else { + memmove(buf, buf + 11, err - 11); + *len = err - 11; + } + + err = 0; +end: + return err; +} + +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == CIP_SFC_COUNT) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x02; /* SPECIFIC INQUIRY */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} diff --git a/sound/firewire/oxfw/oxfw-hwdep.c b/sound/firewire/oxfw/oxfw-hwdep.c new file mode 100644 index 000000000..50a1c03b4 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-hwdep.c @@ -0,0 +1,190 @@ +/* + * oxfw_hwdep.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "oxfw.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&oxfw->lock); + + while (!oxfw->dev_lock_changed) { + prepare_to_wait(&oxfw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&oxfw->lock); + schedule(); + finish_wait(&oxfw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&oxfw->lock); + } + + memset(&event, 0, sizeof(event)); + if (oxfw->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (oxfw->dev_lock_count > 0); + oxfw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&oxfw->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + __poll_t events; + + poll_wait(file, &oxfw->hwdep_wait, wait); + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&oxfw->lock); + + return events; +} + +static int hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(oxfw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_OXFW; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == 0) { + oxfw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_unlock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == -1) { + oxfw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_count == -1) + oxfw->dev_lock_count = 0; + spin_unlock_irq(&oxfw->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(oxfw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(oxfw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(oxfw); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(oxfw->card, oxfw->card->driver, 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, oxfw->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW; + hwdep->ops = hwdep_ops; + hwdep->private_data = oxfw; + hwdep->exclusive = true; +end: + return err; +} diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c new file mode 100644 index 000000000..b7bbd77df --- /dev/null +++ b/sound/firewire/oxfw/oxfw-midi.c @@ -0,0 +1,187 @@ +/* + * oxfw_midi.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + + mutex_lock(&oxfw->mutex); + + oxfw->capture_substreams++; + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, 0, 0); + + mutex_unlock(&oxfw->mutex); + + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + + mutex_lock(&oxfw->mutex); + + oxfw->playback_substreams++; + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, 0, 0); + + mutex_unlock(&oxfw->mutex); + + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + oxfw->capture_substreams--; + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + oxfw->playback_substreams--; + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream); + + mutex_unlock(&oxfw->mutex); + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&oxfw->tx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&oxfw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&oxfw->rx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&oxfw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static void set_midi_substream_names(struct snd_oxfw *oxfw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + oxfw->card->shortname, subs->number + 1); + } +} + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + if (oxfw->midi_input_ports == 0 && oxfw->midi_output_ports == 0) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(oxfw->card, oxfw->card->driver, 0, + oxfw->midi_output_ports, oxfw->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + rmidi->private_data = oxfw; + + if (oxfw->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(oxfw, str); + } + + if (oxfw->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(oxfw, str); + } + + if ((oxfw->midi_output_ports > 0) && (oxfw->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c new file mode 100644 index 000000000..b3f6503dd --- /dev/null +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -0,0 +1,423 @@ +/* + * oxfw_pcm.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + u8 **formats = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + struct snd_oxfw_stream_formation formation; + int i, err; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + continue; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(c, formation.pcm)) + continue; + + t.min = min(t.min, formation.rate); + t.max = max(t.max, formation.rate); + + } + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + u8 **formats = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_oxfw_stream_formation formation; + int i, j, err; + unsigned int count, list[SND_OXFW_STREAM_FORMAT_ENTRIES] = {0}; + + count = 0; + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(r, formation.rate)) + continue; + if (list[count] == formation.pcm) + continue; + + for (j = 0; j < ARRAY_SIZE(list); j++) { + if (list[j] == formation.pcm) + break; + } + if (j == ARRAY_SIZE(list)) { + list[count] = formation.pcm; + if (++count == ARRAY_SIZE(list)) + break; + } + } + + return snd_interval_list(c, count, list, 0); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, u8 **formats) +{ + struct snd_oxfw_stream_formation formation; + int i, err; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + + hw->channels_min = min(hw->channels_min, formation.pcm); + hw->channels_max = max(hw->channels_max, formation.pcm); + + hw->rate_min = min(hw->rate_min, formation.rate); + hw->rate_max = max(hw->rate_max, formation.rate); + hw->rates |= snd_pcm_rate_to_rate_bit(formation.rate); + } +} + +static int init_hw_params(struct snd_oxfw *oxfw, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + u8 **formats; + struct amdtp_stream *stream; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; + stream = &oxfw->tx_stream; + formats = oxfw->tx_stream_formats; + } else { + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; + stream = &oxfw->rx_stream; + formats = oxfw->rx_stream_formats; + } + + limit_channels_and_rates(&runtime->hw, formats); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_am824_add_pcm_hw_constraints(stream, runtime); +end: + return err; +} + +static int limit_to_current_params(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = AVC_GENERAL_PLUG_DIR_OUT; + else + dir = AVC_GENERAL_PLUG_DIR_IN; + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + goto end; + + substream->runtime->hw.channels_min = formation.pcm; + substream->runtime->hw.channels_max = formation.pcm; + substream->runtime->hw.rate_min = formation.rate; + substream->runtime->hw.rate_max = formation.rate; +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + goto end; + + err = init_hw_params(oxfw, substream); + if (err < 0) + goto err_locked; + + /* + * When any PCM streams are already running, the available sampling + * rate is limited at current value. + */ + if (amdtp_stream_pcm_running(&oxfw->tx_stream) || + amdtp_stream_pcm_running(&oxfw->rx_stream)) { + err = limit_to_current_params(substream); + if (err < 0) + goto end; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_oxfw_stream_lock_release(oxfw); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&oxfw->mutex); + oxfw->capture_substreams++; + mutex_unlock(&oxfw->mutex); + } + + return 0; +} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&oxfw->mutex); + oxfw->playback_substreams++; + mutex_unlock(&oxfw->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + oxfw->capture_substreams--; + + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + oxfw->playback_substreams--; + + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream); + + mutex_unlock(&oxfw->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, + runtime->rate, runtime->channels); + mutex_unlock(&oxfw->mutex); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&oxfw->tx_stream); +end: + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, + runtime->rate, runtime->channels); + mutex_unlock(&oxfw->mutex); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&oxfw->rx_stream); +end: + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&oxfw->tx_stream, pcm); + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm); + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstm) +{ + struct snd_oxfw *oxfw = sbstm->private_data; + + return amdtp_stream_pcm_pointer(&oxfw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstm) +{ + struct snd_oxfw *oxfw = sbstm->private_data; + + return amdtp_stream_pcm_pointer(&oxfw->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + return amdtp_stream_pcm_ack(&oxfw->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + return amdtp_stream_pcm_ack(&oxfw->rx_stream); +} + +int snd_oxfw_create_pcm(struct snd_oxfw *oxfw) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + unsigned int cap = 0; + int err; + + if (oxfw->has_output) + cap = 1; + + err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, cap, &pcm); + if (err < 0) + return err; + + pcm->private_data = oxfw; + strcpy(pcm->name, oxfw->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + if (cap > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-proc.c b/sound/firewire/oxfw/oxfw-proc.c new file mode 100644 index 000000000..27dac071b --- /dev/null +++ b/sound/firewire/oxfw/oxfw-proc.c @@ -0,0 +1,113 @@ +/* + * oxfw_proc.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./oxfw.h" + +static void proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_oxfw *oxfw = entry->private_data; + struct snd_oxfw_stream_formation formation, curr; + u8 *format; + char flag; + int i, err; + + /* Show input. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_IN, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->rx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } + + if (!oxfw->has_output) + return; + + /* Show output. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_OUT, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } +} + +static void add_node(struct snd_oxfw *oxfw, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(oxfw->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, oxfw, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(oxfw->card, "firewire", + oxfw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(oxfw, root, "formation", proc_read_formation); +} diff --git a/sound/firewire/oxfw/oxfw-scs1x.c b/sound/firewire/oxfw/oxfw-scs1x.c new file mode 100644 index 000000000..f33497cdc --- /dev/null +++ b/sound/firewire/oxfw/oxfw-scs1x.c @@ -0,0 +1,420 @@ +/* + * oxfw-scs1x.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +#define HSS1394_ADDRESS 0xc007dedadadaULL +#define HSS1394_MAX_PACKET_SIZE 64 +#define HSS1394_TAG_USER_DATA 0x00 +#define HSS1394_TAG_CHANGE_ADDRESS 0xf1 + +struct fw_scs1x { + struct fw_address_handler hss_handler; + u8 input_escape_count; + struct snd_rawmidi_substream *input; + + /* For MIDI playback. */ + struct snd_rawmidi_substream *output; + bool output_idle; + u8 output_status; + u8 output_bytes; + bool output_escaped; + bool output_escape_high_nibble; + struct work_struct work; + wait_queue_head_t idle_wait; + u8 buffer[HSS1394_MAX_PACKET_SIZE]; + bool transaction_running; + struct fw_transaction transaction; + unsigned int transaction_bytes; + bool error; + struct fw_device *fw_dev; +}; + +static const u8 sysex_escape_prefix[] = { + 0xf0, /* SysEx begin */ + 0x00, 0x01, 0x60, /* Stanton DJ */ + 0x48, 0x53, 0x53, /* "HSS" */ +}; + +static void midi_input_escaped_byte(struct snd_rawmidi_substream *stream, + u8 byte) +{ + u8 nibbles[2]; + + nibbles[0] = byte >> 4; + nibbles[1] = byte & 0x0f; + snd_rawmidi_receive(stream, nibbles, 2); +} + +static void midi_input_byte(struct fw_scs1x *scs, + struct snd_rawmidi_substream *stream, u8 byte) +{ + const u8 eox = 0xf7; + + if (scs->input_escape_count > 0) { + midi_input_escaped_byte(stream, byte); + scs->input_escape_count--; + if (scs->input_escape_count == 0) + snd_rawmidi_receive(stream, &eox, sizeof(eox)); + } else if (byte == 0xf9) { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + midi_input_escaped_byte(stream, 0x00); + midi_input_escaped_byte(stream, 0xf9); + scs->input_escape_count = 3; + } else { + snd_rawmidi_receive(stream, &byte, 1); + } +} + +static void midi_input_packet(struct fw_scs1x *scs, + struct snd_rawmidi_substream *stream, + const u8 *data, unsigned int bytes) +{ + unsigned int i; + const u8 eox = 0xf7; + + if (data[0] == HSS1394_TAG_USER_DATA) { + for (i = 1; i < bytes; ++i) + midi_input_byte(scs, stream, data[i]); + } else { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + for (i = 0; i < bytes; ++i) + midi_input_escaped_byte(stream, data[i]); + snd_rawmidi_receive(stream, &eox, sizeof(eox)); + } +} + +static void handle_hss(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, int generation, + unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct fw_scs1x *scs = callback_data; + struct snd_rawmidi_substream *stream; + int rcode; + + if (offset != scs->hss_handler.offset) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + if (tcode != TCODE_WRITE_QUADLET_REQUEST && + tcode != TCODE_WRITE_BLOCK_REQUEST) { + rcode = RCODE_TYPE_ERROR; + goto end; + } + + if (length >= 1) { + stream = READ_ONCE(scs->input); + if (stream) + midi_input_packet(scs, stream, data, length); + } + + rcode = RCODE_COMPLETE; +end: + fw_send_response(card, request, rcode); +} + +static void scs_write_callback(struct fw_card *card, int rcode, + void *data, size_t length, void *callback_data) +{ + struct fw_scs1x *scs = callback_data; + + if (!rcode_is_permanent_error(rcode)) { + /* Don't retry for this data. */ + if (rcode == RCODE_COMPLETE) + scs->transaction_bytes = 0; + } else { + scs->error = true; + } + + scs->transaction_running = false; + schedule_work(&scs->work); +} + +static bool is_valid_running_status(u8 status) +{ + return status >= 0x80 && status <= 0xef; +} + +static bool is_one_byte_cmd(u8 status) +{ + return status == 0xf6 || + status >= 0xf8; +} + +static bool is_two_bytes_cmd(u8 status) +{ + return (status >= 0xc0 && status <= 0xdf) || + status == 0xf1 || + status == 0xf3; +} + +static bool is_three_bytes_cmd(u8 status) +{ + return (status >= 0x80 && status <= 0xbf) || + (status >= 0xe0 && status <= 0xef) || + status == 0xf2; +} + +static bool is_invalid_cmd(u8 status) +{ + return status == 0xf4 || + status == 0xf5 || + status == 0xf9 || + status == 0xfd; +} + +static void scs_output_work(struct work_struct *work) +{ + struct fw_scs1x *scs = container_of(work, struct fw_scs1x, work); + struct snd_rawmidi_substream *stream; + unsigned int i; + u8 byte; + int generation; + + if (scs->transaction_running) + return; + + stream = READ_ONCE(scs->output); + if (!stream || scs->error) { + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + + if (scs->transaction_bytes > 0) + goto retry; + + i = scs->output_bytes; + for (;;) { + if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { + scs->output_bytes = i; + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + /* + * Convert from real MIDI to what I think the device expects (no + * running status, one command per packet, unescaped SysExs). + */ + if (scs->output_escaped && byte < 0x80) { + if (scs->output_escape_high_nibble) { + if (i < HSS1394_MAX_PACKET_SIZE) { + scs->buffer[i] = byte << 4; + scs->output_escape_high_nibble = false; + } + } else { + scs->buffer[i++] |= byte & 0x0f; + scs->output_escape_high_nibble = true; + } + } else if (byte < 0x80) { + if (i == 1) { + if (!is_valid_running_status( + scs->output_status)) + continue; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = scs->output_status; + } + scs->buffer[i++] = byte; + if ((i == 3 && is_two_bytes_cmd(scs->output_status)) || + (i == 4 && is_three_bytes_cmd(scs->output_status))) + break; + if (i == 1 + ARRAY_SIZE(sysex_escape_prefix) && + !memcmp(scs->buffer + 1, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix))) { + scs->output_escaped = true; + scs->output_escape_high_nibble = true; + i = 0; + } + if (i >= HSS1394_MAX_PACKET_SIZE) + i = 1; + } else if (byte == 0xf7) { + if (scs->output_escaped) { + if (i >= 1 && scs->output_escape_high_nibble && + scs->buffer[0] != + HSS1394_TAG_CHANGE_ADDRESS) + break; + } else { + if (i > 1 && scs->output_status == 0xf0) { + scs->buffer[i++] = 0xf7; + break; + } + } + i = 1; + scs->output_escaped = false; + } else if (!is_invalid_cmd(byte) && byte < 0xf8) { + i = 1; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = byte; + scs->output_status = byte; + scs->output_escaped = false; + if (is_one_byte_cmd(byte)) + break; + } + } + scs->output_bytes = 1; + scs->output_escaped = false; + + scs->transaction_bytes = i; +retry: + scs->transaction_running = true; + generation = scs->fw_dev->generation; + smp_rmb(); /* node_id vs. generation */ + fw_send_request(scs->fw_dev->card, &scs->transaction, + TCODE_WRITE_BLOCK_REQUEST, scs->fw_dev->node_id, + generation, scs->fw_dev->max_speed, HSS1394_ADDRESS, + scs->buffer, scs->transaction_bytes, + scs_write_callback, scs); +} + +static int midi_capture_open(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + if (up) { + scs->input_escape_count = 0; + WRITE_ONCE(scs->input, stream); + } else { + WRITE_ONCE(scs->input, NULL); + } +} + +static int midi_playback_open(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + if (up) { + scs->output_status = 0; + scs->output_bytes = 1; + scs->output_escaped = false; + scs->output_idle = false; + scs->transaction_bytes = 0; + scs->error = false; + + WRITE_ONCE(scs->output, stream); + schedule_work(&scs->work); + } else { + WRITE_ONCE(scs->output, NULL); + } +} +static void midi_playback_drain(struct snd_rawmidi_substream *stream) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + wait_event(scs->idle_wait, scs->output_idle); +} + +static int register_address(struct snd_oxfw *oxfw) +{ + struct fw_scs1x *scs = oxfw->spec; + __be64 data; + + data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | + scs->hss_handler.offset); + return snd_fw_transaction(oxfw->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, &data, sizeof(data), 0); +} + +static void remove_scs1x(struct snd_rawmidi *rmidi) +{ + struct fw_scs1x *scs = rmidi->private_data; + + fw_core_remove_address_handler(&scs->hss_handler); +} + +void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw) +{ + register_address(oxfw); +} + +int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw) +{ + static const struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + .drain = midi_playback_drain, + }; + struct snd_rawmidi *rmidi; + struct fw_scs1x *scs; + int err; + + scs = kzalloc(sizeof(struct fw_scs1x), GFP_KERNEL); + if (scs == NULL) + return -ENOMEM; + scs->fw_dev = fw_parent_device(oxfw->unit); + oxfw->spec = scs; + + /* Allocate own handler for imcoming asynchronous transaction. */ + scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; + scs->hss_handler.address_callback = handle_hss; + scs->hss_handler.callback_data = scs; + err = fw_core_add_address_handler(&scs->hss_handler, + &fw_high_memory_region); + if (err < 0) + return err; + + err = register_address(oxfw); + if (err < 0) + goto err_allocated; + + /* Use unique name for backward compatibility to scs1x module. */ + err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 1, 1, &rmidi); + if (err < 0) + goto err_allocated; + rmidi->private_data = scs; + rmidi->private_free = remove_scs1x; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + INIT_WORK(&scs->work, scs_output_work); + init_waitqueue_head(&scs->idle_wait); + scs->output_idle = true; + + return 0; +err_allocated: + fw_core_remove_address_handler(&scs->hss_handler); + return err; +} diff --git a/sound/firewire/oxfw/oxfw-spkr.c b/sound/firewire/oxfw/oxfw-spkr.c new file mode 100644 index 000000000..cb905af06 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-spkr.c @@ -0,0 +1,319 @@ +/* + * oxfw-spkr.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +struct fw_spkr { + bool mute; + s16 volume[6]; + s16 volume_min; + s16 volume_max; + + unsigned int mixer_channels; + u8 mute_fb_id; + u8 volume_fb_id; +}; + +enum control_action { CTL_READ, CTL_WRITE }; +enum control_attribute { + CTL_MIN = 0x02, + CTL_MAX = 0x03, + CTL_CURRENT = 0x10, +}; + +static int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(11, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fb_id; /* function block ID */ + buf[5] = 0x10; /* control attribute: current */ + buf[6] = 0x02; /* selector length */ + buf[7] = 0x00; /* audio channel number */ + buf[8] = 0x01; /* control selector: mute */ + buf[9] = 0x01; /* control data length */ + if (action == CTL_READ) + buf[10] = 0xff; + else + buf[10] = *value ? 0x70 : 0x60; + + err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe); + if (err < 0) + goto error; + if (err < 11) { + dev_err(&unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&unit->device, "mute command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = buf[10] == 0x70; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value, + unsigned int channel, + enum control_attribute attribute, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(12, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fb_id; /* function block ID */ + buf[5] = attribute; /* control attribute */ + buf[6] = 0x02; /* selector length */ + buf[7] = channel; /* audio channel number */ + buf[8] = 0x02; /* control selector: volume */ + buf[9] = 0x02; /* control data length */ + if (action == CTL_READ) { + buf[10] = 0xff; + buf[11] = 0xff; + } else { + buf[10] = *value >> 8; + buf[11] = *value; + } + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe); + if (err < 0) + goto error; + if (err < 12) { + dev_err(&unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&unit->device, "volume command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = (buf[10] << 8) | buf[11]; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int spkr_mute_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + + value->value.integer.value[0] = !spkr->mute; + + return 0; +} + +static int spkr_mute_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + bool mute; + int err; + + mute = !value->value.integer.value[0]; + + if (mute == spkr->mute) + return 0; + + err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute, + CTL_WRITE); + if (err < 0) + return err; + spkr->mute = mute; + + return 1; +} + +static int spkr_volume_info(struct snd_kcontrol *control, + struct snd_ctl_elem_info *info) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = spkr->mixer_channels; + info->value.integer.min = spkr->volume_min; + info->value.integer.max = spkr->volume_max; + + return 0; +} + +static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; + +static int spkr_volume_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + unsigned int i; + + for (i = 0; i < spkr->mixer_channels; ++i) + value->value.integer.value[channel_map[i]] = spkr->volume[i]; + + return 0; +} + +static int spkr_volume_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + unsigned int i, changed_channels; + bool equal_values = true; + s16 volume; + int err; + + for (i = 0; i < spkr->mixer_channels; ++i) { + if (value->value.integer.value[i] < spkr->volume_min || + value->value.integer.value[i] > spkr->volume_max) + return -EINVAL; + if (value->value.integer.value[i] != + value->value.integer.value[0]) + equal_values = false; + } + + changed_channels = 0; + for (i = 0; i < spkr->mixer_channels; ++i) + if (value->value.integer.value[channel_map[i]] != + spkr->volume[i]) + changed_channels |= 1 << (i + 1); + + if (equal_values && changed_channels != 0) + changed_channels = 1 << 0; + + for (i = 0; i <= spkr->mixer_channels; ++i) { + volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; + if (changed_channels & (1 << i)) { + err = avc_audio_feature_volume(oxfw->unit, + spkr->volume_fb_id, &volume, + i, CTL_CURRENT, CTL_WRITE); + if (err < 0) + return err; + } + if (i > 0) + spkr->volume[i - 1] = volume; + } + + return changed_channels != 0; +} + +int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie) +{ + static const struct snd_kcontrol_new controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = spkr_mute_get, + .put = spkr_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = spkr_volume_info, + .get = spkr_volume_get, + .put = spkr_volume_put, + }, + }; + struct fw_spkr *spkr; + unsigned int i, first_ch; + int err; + + spkr = kzalloc(sizeof(struct fw_spkr), GFP_KERNEL); + if (spkr == NULL) + return -ENOMEM; + oxfw->spec = spkr; + + if (is_lacie) { + spkr->mixer_channels = 1; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x01; + } else { + spkr->mixer_channels = 6; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x02; + } + + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume_min, 0, CTL_MIN, CTL_READ); + if (err < 0) + return err; + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume_max, 0, CTL_MAX, CTL_READ); + if (err < 0) + return err; + + err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute, + CTL_READ); + if (err < 0) + return err; + + first_ch = spkr->mixer_channels == 1 ? 0 : 1; + for (i = 0; i < spkr->mixer_channels; ++i) { + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume[i], first_ch + i, + CTL_CURRENT, CTL_READ); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(controls); ++i) { + err = snd_ctl_add(oxfw->card, + snd_ctl_new1(&controls[i], oxfw)); + if (err < 0) + return err; + } + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c new file mode 100644 index 000000000..d9361f352 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -0,0 +1,730 @@ +/* + * oxfw_stream.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" +#include + +#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 +#define CALLBACK_TIMEOUT 200 + +/* + * According to datasheet of Oxford Semiconductor: + * OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O + * OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O + */ +static const unsigned int oxfw_rate_table[] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 192000, +}; + +/* + * See Table 5.7 – Sampling frequency for Multi-bit Audio + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static const unsigned int avc_stream_rate_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x07, +}; + +static int set_rate(struct snd_oxfw *oxfw, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (oxfw->has_output) + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); +end: + return err; +} + +static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s, + unsigned int rate, unsigned int pcm_channels) +{ + u8 **formats; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + unsigned int len; + int i, err; + + if (s == &oxfw->tx_stream) { + formats = oxfw->tx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + formats = oxfw->rx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_IN; + } + + /* Seek stream format for requirements. */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + return err; + + if ((formation.rate == rate) && (formation.pcm == pcm_channels)) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) + return -EINVAL; + + /* If assumed, just change rate. */ + if (oxfw->assumed) + return set_rate(oxfw, rate); + + /* Calculate format length. */ + len = 5 + formats[i][4] * 2; + + err = avc_stream_set_format(oxfw->unit, dir, 0, formats[i], len); + if (err < 0) + return err; + + /* Some requests just after changing format causes freezing. */ + msleep(100); + + return 0; +} + +static void stop_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream); + + if (stream == &oxfw->tx_stream) + cmp_connection_break(&oxfw->out_conn); + else + cmp_connection_break(&oxfw->in_conn); +} + +static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels) +{ + u8 **formats; + struct cmp_connection *conn; + struct snd_oxfw_stream_formation formation; + unsigned int i, midi_ports; + int err; + + if (stream == &oxfw->rx_stream) { + formats = oxfw->rx_stream_formats; + conn = &oxfw->in_conn; + } else { + formats = oxfw->tx_stream_formats; + conn = &oxfw->out_conn; + } + + /* Get stream format */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + goto end; + if (rate != formation.rate) + continue; + if (pcm_channels == 0 || pcm_channels == formation.pcm) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) { + err = -EINVAL; + goto end; + } + + pcm_channels = formation.pcm; + midi_ports = formation.midi * 8; + + /* The stream should have one pcm channels at least */ + if (pcm_channels == 0) { + err = -EINVAL; + goto end; + } + err = amdtp_am824_set_parameters(stream, rate, pcm_channels, midi_ports, + false); + if (err < 0) + goto end; + + err = cmp_connection_establish(conn, + amdtp_stream_get_max_payload(stream)); + if (err < 0) + goto end; + + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); + if (err < 0) { + cmp_connection_break(conn); + goto end; + } + + /* Wait first packet */ + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + stop_stream(oxfw, stream); + err = -ETIMEDOUT; + } +end: + return err; +} + +static int check_connection_used_by_others(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(stream)) { + dev_err(&oxfw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &oxfw->tx_stream) { + conn = &oxfw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &oxfw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, oxfw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_am824_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); + goto end; + } + + /* + * OXFW starts to transmit packets with non-zero dbc. + * OXFW postpone transferring packets till handling any asynchronous + * packets. As a result, next isochronous packet includes more data + * blocks than IEC 61883-6 defines. + */ + if (stream == &oxfw->tx_stream) { + oxfw->tx_stream.flags |= CIP_JUMBO_PAYLOAD; + if (oxfw->wrong_dbs) + oxfw->tx_stream.flags |= CIP_WRONG_DBS; + } +end: + return err; +} + +int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels) +{ + struct amdtp_stream *opposite; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + unsigned int substreams, opposite_substreams; + int err = 0; + + if (stream == &oxfw->tx_stream) { + substreams = oxfw->capture_substreams; + opposite = &oxfw->rx_stream; + opposite_substreams = oxfw->playback_substreams; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + substreams = oxfw->playback_substreams; + opposite_substreams = oxfw->capture_substreams; + + if (oxfw->has_output) + opposite = &oxfw->rx_stream; + else + opposite = NULL; + + dir = AVC_GENERAL_PLUG_DIR_IN; + } + + if (substreams == 0) + goto end; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(oxfw, stream); + if (err < 0) + goto end; + + /* packet queueing error */ + if (amdtp_streaming_error(stream)) + stop_stream(oxfw, stream); + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + goto end; + if (rate == 0) + rate = formation.rate; + if (pcm_channels == 0) + pcm_channels = formation.pcm; + + if ((formation.rate != rate) || (formation.pcm != pcm_channels)) { + if (opposite != NULL) { + err = check_connection_used_by_others(oxfw, opposite); + if (err < 0) + goto end; + stop_stream(oxfw, opposite); + } + stop_stream(oxfw, stream); + + err = set_stream_format(oxfw, stream, rate, pcm_channels); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to set stream format: %d\n", err); + goto end; + } + + /* Start opposite stream if needed. */ + if (opposite && !amdtp_stream_running(opposite) && + (opposite_substreams > 0)) { + err = start_stream(oxfw, opposite, rate, 0); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to restart stream: %d\n", err); + goto end; + } + } + } + + /* Start requested stream. */ + if (!amdtp_stream_running(stream)) { + err = start_stream(oxfw, stream, rate, pcm_channels); + if (err < 0) + dev_err(&oxfw->unit->device, + "fail to start stream: %d\n", err); + } +end: + return err; +} + +void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + if (((stream == &oxfw->tx_stream) && (oxfw->capture_substreams > 0)) || + ((stream == &oxfw->rx_stream) && (oxfw->playback_substreams > 0))) + return; + + stop_stream(oxfw, stream); +} + +/* + * This function should be called before starting the stream or after stopping + * the streams. + */ +void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); +} + +void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + if (cmp_connection_update(conn) < 0) + stop_stream(oxfw, stream); + else + amdtp_stream_update(stream); +} + +int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation) +{ + u8 *format; + unsigned int len; + int err; + + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + format = kmalloc(len, GFP_KERNEL); + if (format == NULL) + return -ENOMEM; + + err = avc_stream_get_format_single(oxfw->unit, dir, 0, format, &len); + if (err < 0) + goto end; + if (len < 3) { + err = -EIO; + goto end; + } + + err = snd_oxfw_stream_parse_format(format, formation); +end: + kfree(format); + return err; +} + +/* + * See Table 6.16 - AM824 Stream Format + * Figure 6.19 - format_information field for AM824 Compound + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation) +{ + unsigned int i, e, channels, type; + + memset(formation, 0, sizeof(struct snd_oxfw_stream_formation)); + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((format[0] != 0x90) || (format[1] != 0x40)) + return -ENOSYS; + + /* check the sampling rate */ + for (i = 0; i < ARRAY_SIZE(avc_stream_rate_table); i++) { + if (format[2] == avc_stream_rate_table[i]) + break; + } + if (i == ARRAY_SIZE(avc_stream_rate_table)) + return -ENOSYS; + + formation->rate = oxfw_rate_table[i]; + + for (e = 0; e < format[4]; e++) { + channels = format[5 + e * 2]; + type = format[6 + e * 2]; + + switch (type) { + /* IEC 60958 Conformant, currently handled as MBLA */ + case 0x00: + /* Multi Bit Linear Audio (Raw) */ + case 0x06: + formation->pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation->midi = channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi Bit Linear Audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* SMPTE Time-Code conformant */ + case 0x0e: + /* Sample Count */ + case 0x0f: + /* Anciliary Data */ + case 0x10: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation->pcm > AM824_MAX_CHANNELS_FOR_PCM || + formation->midi > AM824_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; + + return 0; +} + +static int +assume_stream_formats(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, + unsigned int pid, u8 *buf, unsigned int *len, + u8 **formats) +{ + struct snd_oxfw_stream_formation formation; + unsigned int i, eid; + int err; + + /* get format at current sampling rate */ + err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get current stream format for isoc %s plug %d:%d\n", + (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* parse and set stream format */ + eid = 0; + err = snd_oxfw_stream_parse_format(buf, &formation); + if (err < 0) + goto end; + + formats[eid] = kmemdup(buf, *len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + goto end; + } + + /* apply the format for each available sampling rate */ + for (i = 0; i < ARRAY_SIZE(oxfw_rate_table); i++) { + if (formation.rate == oxfw_rate_table[i]) + continue; + + err = avc_general_inquiry_sig_fmt(oxfw->unit, + oxfw_rate_table[i], + dir, pid); + if (err < 0) + continue; + + eid++; + formats[eid] = kmemdup(buf, *len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + goto end; + } + formats[eid][2] = avc_stream_rate_table[i]; + } + + err = 0; + oxfw->assumed = true; +end: + return err; +} + +static int fill_stream_formats(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + u8 *buf, **formats; + unsigned int len, eid = 0; + struct snd_oxfw_stream_formation dummy; + int err; + + buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_GENERAL_PLUG_DIR_OUT) + formats = oxfw->tx_stream_formats; + else + formats = oxfw->rx_stream_formats; + + /* get first entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, 0); + if (err == -ENOSYS) { + /* LIST subfunction is not implemented */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = assume_stream_formats(oxfw, dir, pid, buf, &len, + formats); + goto end; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* LIST subfunction is implemented */ + while (eid < SND_OXFW_STREAM_FORMAT_ENTRIES) { + /* The format is too short. */ + if (len < 3) { + err = -EIO; + break; + } + + /* parse and set stream format */ + err = snd_oxfw_stream_parse_format(buf, &dummy); + if (err < 0) + break; + + formats[eid] = kmemdup(buf, len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + break; + } + + /* get next entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, + buf, &len, ++eid); + /* No entries remained. */ + if (err == -EINVAL) { + err = 0; + break; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + } +end: + kfree(buf); + return err; +} + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES]; + struct snd_oxfw_stream_formation formation; + u8 *format; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } else if ((plugs[0] == 0) && (plugs[1] == 0)) { + err = -ENOSYS; + goto end; + } + + /* use oPCR[0] if exists */ + if (plugs[1] > 0) { + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format == NULL) + continue; + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + /* Add one MIDI port. */ + if (formation.midi > 0) + oxfw->midi_input_ports = 1; + } + + oxfw->has_output = true; + } + + /* use iPCR[0] if exists */ + if (plugs[0] > 0) { + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->rx_stream_formats[i]; + if (format == NULL) + continue; + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + /* Add one MIDI port. */ + if (formation.midi > 0) + oxfw->midi_output_ports = 1; + } + } +end: + return err; +} + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw) +{ + oxfw->dev_lock_changed = true; + wake_up(&oxfw->hwdep_wait); +} + +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + /* user land lock this */ + if (oxfw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (oxfw->dev_lock_count++ == 0) + snd_oxfw_stream_lock_changed(oxfw); + err = 0; +end: + spin_unlock_irq(&oxfw->lock); + return err; +} + +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw) +{ + spin_lock_irq(&oxfw->lock); + + if (WARN_ON(oxfw->dev_lock_count <= 0)) + goto end; + if (--oxfw->dev_lock_count == 0) + snd_oxfw_stream_lock_changed(oxfw); +end: + spin_unlock_irq(&oxfw->lock); +} diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c new file mode 100644 index 000000000..59c05c5dc --- /dev/null +++ b/sound/firewire/oxfw/oxfw.c @@ -0,0 +1,470 @@ +/* + * oxfw.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) +/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ + +#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) +#define OXFORD_HARDWARE_ID_OXFW970 0x39443841 +#define OXFORD_HARDWARE_ID_OXFW971 0x39373100 + +#define VENDOR_LOUD 0x000ff2 +#define VENDOR_GRIFFIN 0x001292 +#define VENDOR_BEHRINGER 0x001564 +#define VENDOR_LACIE 0x00d04b +#define VENDOR_TASCAM 0x00022e +#define OUI_STANTON 0x001260 +#define OUI_APOGEE 0x0003db + +#define MODEL_SATELLITE 0x00200f + +#define SPECIFIER_1394TA 0x00a02d +#define VERSION_AVC 0x010001 + +MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("snd-firewire-speakers"); +MODULE_ALIAS("snd-scs1x"); + +struct compat_info { + const char *driver_name; + const char *vendor_name; + const char *model_name; +}; + +static bool detect_loud_models(struct fw_unit *unit) +{ + const char *const models[] = { + "Onyxi", + "Onyx-i", + "Onyx 1640i", + "d.Pro", + "Mackie Onyx Satellite", + "Tapco LINK.firewire 4x6", + "U.420"}; + char model[32]; + int err; + + err = fw_csr_string(unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + return false; + + return match_string(models, ARRAY_SIZE(models), model) >= 0; +} + +static int name_card(struct snd_oxfw *oxfw) +{ + struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + const struct compat_info *info; + char vendor[24]; + char model[32]; + const char *d, *v, *m; + u32 firmware; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(oxfw->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST, + OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0); + if (err < 0) + goto end; + be32_to_cpus(&firmware); + + /* to apply card definitions */ + if (oxfw->entry->vendor_id == VENDOR_GRIFFIN || + oxfw->entry->vendor_id == VENDOR_LACIE) { + info = (const struct compat_info *)oxfw->entry->driver_data; + d = info->driver_name; + v = info->vendor_name; + m = info->model_name; + } else { + d = "OXFW"; + v = vendor; + m = model; + } + + strcpy(oxfw->card->driver, d); + strcpy(oxfw->card->mixername, m); + strcpy(oxfw->card->shortname, m); + + snprintf(oxfw->card->longname, sizeof(oxfw->card->longname), + "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d", + v, m, firmware >> 20, firmware & 0xffff, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed); +end: + return err; +} + +static void oxfw_free(struct snd_oxfw *oxfw) +{ + unsigned int i; + + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream); + + fw_unit_put(oxfw->unit); + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + kfree(oxfw->tx_stream_formats[i]); + kfree(oxfw->rx_stream_formats[i]); + } + + kfree(oxfw->spec); + mutex_destroy(&oxfw->mutex); + kfree(oxfw); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void oxfw_card_free(struct snd_card *card) +{ + oxfw_free(card->private_data); +} + +static int detect_quirks(struct snd_oxfw *oxfw) +{ + struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + struct fw_csr_iterator it; + int key, val; + int vendor, model; + + /* + * Add ALSA control elements for two models to keep compatibility to + * old firewire-speaker module. + */ + if (oxfw->entry->vendor_id == VENDOR_GRIFFIN) + return snd_oxfw_add_spkr(oxfw, false); + if (oxfw->entry->vendor_id == VENDOR_LACIE) + return snd_oxfw_add_spkr(oxfw, true); + + /* + * Stanton models supports asynchronous transactions for unique MIDI + * messages. + */ + if (oxfw->entry->vendor_id == OUI_STANTON) { + /* No physical MIDI ports. */ + oxfw->midi_input_ports = 0; + oxfw->midi_output_ports = 0; + + return snd_oxfw_scs1x_add(oxfw); + } + + /* + * TASCAM FireOne has physical control and requires a pair of additional + * MIDI ports. + */ + if (oxfw->entry->vendor_id == VENDOR_TASCAM) { + oxfw->midi_input_ports++; + oxfw->midi_output_ports++; + return 0; + } + + /* Seek from Root Directory of Config ROM. */ + vendor = model = 0; + fw_csr_iterator_init(&it, fw_dev->config_rom + 5); + while (fw_csr_iterator_next(&it, &key, &val)) { + if (key == CSR_VENDOR) + vendor = val; + else if (key == CSR_MODEL) + model = val; + } + + /* + * Mackie Onyx Satellite with base station has a quirk to report a wrong + * value in 'dbs' field of CIP header against its format information. + */ + if (vendor == VENDOR_LOUD && model == MODEL_SATELLITE) + oxfw->wrong_dbs = true; + + return 0; +} + +static void do_registration(struct work_struct *work) +{ + struct snd_oxfw *oxfw = container_of(work, struct snd_oxfw, dwork.work); + int i; + int err; + + if (oxfw->registered) + return; + + err = snd_card_new(&oxfw->unit->device, -1, NULL, THIS_MODULE, 0, + &oxfw->card); + if (err < 0) + return; + + err = name_card(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_stream_discover(oxfw); + if (err < 0) + goto error; + + err = detect_quirks(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream); + if (err < 0) + goto error; + if (oxfw->has_output) { + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->tx_stream); + if (err < 0) + goto error; + } + + err = snd_oxfw_create_pcm(oxfw); + if (err < 0) + goto error; + + snd_oxfw_proc_init(oxfw); + + err = snd_oxfw_create_midi(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_create_hwdep(oxfw); + if (err < 0) + goto error; + + err = snd_card_register(oxfw->card); + if (err < 0) + goto error; + + /* + * After registered, oxfw instance can be released corresponding to + * releasing the sound card instance. + */ + oxfw->card->private_free = oxfw_card_free; + oxfw->card->private_data = oxfw; + oxfw->registered = true; + + return; +error: + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; ++i) { + kfree(oxfw->tx_stream_formats[i]); + oxfw->tx_stream_formats[i] = NULL; + kfree(oxfw->rx_stream_formats[i]); + oxfw->rx_stream_formats[i] = NULL; + } + snd_card_free(oxfw->card); + kfree(oxfw->spec); + oxfw->spec = NULL; + dev_info(&oxfw->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int oxfw_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_oxfw *oxfw; + + if (entry->vendor_id == VENDOR_LOUD && !detect_loud_models(unit)) + return -ENODEV; + + /* Allocate this independent of sound card instance. */ + oxfw = kzalloc(sizeof(struct snd_oxfw), GFP_KERNEL); + if (oxfw == NULL) + return -ENOMEM; + + oxfw->entry = entry; + oxfw->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, oxfw); + + mutex_init(&oxfw->mutex); + spin_lock_init(&oxfw->lock); + init_waitqueue_head(&oxfw->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&oxfw->dwork, do_registration); + snd_fw_schedule_registration(unit, &oxfw->dwork); + + return 0; +} + +static void oxfw_bus_reset(struct fw_unit *unit) +{ + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + if (!oxfw->registered) + snd_fw_schedule_registration(unit, &oxfw->dwork); + + fcp_bus_reset(oxfw->unit); + + if (oxfw->registered) { + mutex_lock(&oxfw->mutex); + + snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + if (oxfw->entry->vendor_id == OUI_STANTON) + snd_oxfw_scs1x_update(oxfw); + } +} + +static void oxfw_remove(struct fw_unit *unit) +{ + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&oxfw->dwork); + + if (oxfw->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(oxfw->card); + } else { + /* Don't forget this case. */ + oxfw_free(oxfw); + } +} + +static const struct compat_info griffin_firewave = { + .driver_name = "FireWave", + .vendor_name = "Griffin", + .model_name = "FireWave", +}; + +static const struct compat_info lacie_speakers = { + .driver_name = "FWSpeakers", + .vendor_name = "LaCie", + .model_name = "FireWire Speakers", +}; + +static const struct ieee1394_device_id oxfw_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_GRIFFIN, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&griffin_firewave, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LACIE, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&lacie_speakers, + }, + /* Behringer,F-Control Audio 202 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_BEHRINGER, + .model_id = 0x00fc22, + }, + /* + * Any Mackie(Loud) models (name string/model id): + * Onyx-i series (former models): 0x081216 + * Mackie Onyx Satellite: 0x00200f + * Tapco LINK.firewire 4x6: 0x000460 + * d.2 pro/d.4 pro (built-in card): Unknown + * U.420: Unknown + * U.420d: Unknown + */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LOUD, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + }, + /* TASCAM, FireOne */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_TASCAM, + .model_id = 0x800007, + }, + /* Stanton, Stanton Controllers & Systems 1 Mixer (SCS.1m) */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = 0x001000, + }, + /* Stanton, Stanton Controllers & Systems 1 Deck (SCS.1d) */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = 0x002000, + }, + // APOGEE, duet FireWire + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_APOGEE, + .model_id = 0x01dddd, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table); + +static struct fw_driver oxfw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = oxfw_probe, + .update = oxfw_bus_reset, + .remove = oxfw_remove, + .id_table = oxfw_id_table, +}; + +static int __init snd_oxfw_init(void) +{ + return driver_register(&oxfw_driver.driver); +} + +static void __exit snd_oxfw_exit(void) +{ + driver_unregister(&oxfw_driver.driver); +} + +module_init(snd_oxfw_init); +module_exit(snd_oxfw_exit); diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h new file mode 100644 index 000000000..d54d4a9ac --- /dev/null +++ b/sound/firewire/oxfw/oxfw.h @@ -0,0 +1,141 @@ +/* + * oxfw.h - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lib.h" +#include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-am824.h" +#include "../cmp.h" + +/* This is an arbitrary number for convinience. */ +#define SND_OXFW_STREAM_FORMAT_ENTRIES 10 +struct snd_oxfw { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + bool wrong_dbs; + bool has_output; + u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; + u8 *rx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; + bool assumed; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int capture_substreams; + unsigned int playback_substreams; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + const struct ieee1394_device_id *entry; + void *spec; +}; + +/* + * AV/C Stream Format Information Specification 1.1 Working Draft + * (Apr 2005, 1394TA) + */ +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len); +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid); +static inline int +avc_stream_get_format_single(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, 0xff); +} +static inline int +avc_stream_get_format_list(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, eid); +} + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid); + +int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels); +void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); + +struct snd_oxfw_stream_formation { + unsigned int rate; + unsigned int pcm; + unsigned int midi; +}; +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation); +int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation); + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw); + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw); +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw); +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw); + +int snd_oxfw_create_pcm(struct snd_oxfw *oxfw); + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw); + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw); + +int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw); + +int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie); +int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw); +void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw); diff --git a/sound/firewire/packets-buffer.c b/sound/firewire/packets-buffer.c new file mode 100644 index 000000000..715cd99f2 --- /dev/null +++ b/sound/firewire/packets-buffer.c @@ -0,0 +1,77 @@ +/* + * helpers for managing a buffer for many packets + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include "packets-buffer.h" + +/** + * iso_packets_buffer_init - allocates the memory for packets + * @b: the buffer structure to initialize + * @unit: the device at the other end of the stream + * @count: the number of packets + * @packet_size: the (maximum) size of a packet, in bytes + * @direction: %DMA_TO_DEVICE or %DMA_FROM_DEVICE + */ +int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit, + unsigned int count, unsigned int packet_size, + enum dma_data_direction direction) +{ + unsigned int packets_per_page, pages; + unsigned int i, page_index, offset_in_page; + void *p; + int err; + + b->packets = kmalloc_array(count, sizeof(*b->packets), GFP_KERNEL); + if (!b->packets) { + err = -ENOMEM; + goto error; + } + + packet_size = L1_CACHE_ALIGN(packet_size); + packets_per_page = PAGE_SIZE / packet_size; + if (WARN_ON(!packets_per_page)) { + err = -EINVAL; + goto err_packets; + } + pages = DIV_ROUND_UP(count, packets_per_page); + + err = fw_iso_buffer_init(&b->iso_buffer, fw_parent_device(unit)->card, + pages, direction); + if (err < 0) + goto err_packets; + + for (i = 0; i < count; ++i) { + page_index = i / packets_per_page; + p = page_address(b->iso_buffer.pages[page_index]); + offset_in_page = (i % packets_per_page) * packet_size; + b->packets[i].buffer = p + offset_in_page; + b->packets[i].offset = page_index * PAGE_SIZE + offset_in_page; + } + + return 0; + +err_packets: + kfree(b->packets); +error: + return err; +} +EXPORT_SYMBOL(iso_packets_buffer_init); + +/** + * iso_packets_buffer_destroy - frees packet buffer resources + * @b: the buffer structure to free + * @unit: the device at the other end of the stream + */ +void iso_packets_buffer_destroy(struct iso_packets_buffer *b, + struct fw_unit *unit) +{ + fw_iso_buffer_destroy(&b->iso_buffer, fw_parent_device(unit)->card); + kfree(b->packets); +} +EXPORT_SYMBOL(iso_packets_buffer_destroy); diff --git a/sound/firewire/packets-buffer.h b/sound/firewire/packets-buffer.h new file mode 100644 index 000000000..99e963c27 --- /dev/null +++ b/sound/firewire/packets-buffer.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED +#define SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED + +#include +#include + +/** + * struct iso_packets_buffer - manages a buffer for many packets + * @iso_buffer: the memory containing the packets + * @packets: an array, with each element pointing to one packet + */ +struct iso_packets_buffer { + struct fw_iso_buffer iso_buffer; + struct { + void *buffer; + unsigned int offset; + } *packets; +}; + +int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit, + unsigned int count, unsigned int packet_size, + enum dma_data_direction direction); +void iso_packets_buffer_destroy(struct iso_packets_buffer *b, + struct fw_unit *unit); + +#endif diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile new file mode 100644 index 000000000..0fc955d5b --- /dev/null +++ b/sound/firewire/tascam/Makefile @@ -0,0 +1,4 @@ +snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ + tascam-pcm.o tascam-hwdep.o tascam-transaction.o \ + tascam-midi.o tascam.o +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c new file mode 100644 index 000000000..ab482423c --- /dev/null +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -0,0 +1,185 @@ +/* + * amdtp-tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "tascam.h" + +#define AMDTP_FMT_TSCM_TX 0x1e +#define AMDTP_FMT_TSCM_RX 0x3e + +struct amdtp_tscm { + unsigned int pcm_channels; +}; + +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + data_channels = p->pcm_channels; + + /* Packets in in-stream have extra 2 data channels. */ + if (s->direction == AMDTP_IN_STREAM) + data_channels += 2; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_be32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + /* The first data channel is for event counter. */ + buffer += 1; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = be32_to_cpu(buffer[c]); + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = 0x00000000; + buffer += s->data_block_quadlets; + } +} + +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* + * Our implementation allows this protocol to deliver 24 bit sample in + * 32bit data channel. + */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + + pcm = READ_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + read_pcm_s32(s, pcm, buffer, data_blocks); + + /* A place holder for control messages. */ + + return data_blocks; +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + + /* This field is not used. */ + *syt = 0x0000; + + pcm = READ_ONCE(s->pcm); + if (pcm) + write_pcm_s32(s, pcm, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + return data_blocks; +} + +int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + struct amdtp_tscm *p; + unsigned int fmt; + int err; + + if (dir == AMDTP_IN_STREAM) { + fmt = AMDTP_FMT_TSCM_TX; + process_data_blocks = process_tx_data_blocks; + } else { + fmt = AMDTP_FMT_TSCM_RX; + process_data_blocks = process_rx_data_blocks; + } + + err = amdtp_stream_init(s, unit, dir, + CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt, + process_data_blocks, sizeof(struct amdtp_tscm)); + if (err < 0) + return 0; + + /* Use fixed value for FDF field. */ + s->fdf = 0x00; + + /* This protocol uses fixed number of data channels for PCM samples. */ + p = s->protocol; + p->pcm_channels = pcm_channels; + + return 0; +} diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c new file mode 100644 index 000000000..4e4c1e902 --- /dev/null +++ b/sound/firewire/tascam/tascam-hwdep.c @@ -0,0 +1,189 @@ +/* + * tascam-hwdep.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "tascam.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_tscm *tscm = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event = { + .lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, + }; + + spin_lock_irq(&tscm->lock); + + while (!tscm->dev_lock_changed) { + prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&tscm->lock); + schedule(); + finish_wait(&tscm->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&tscm->lock); + } + + event.lock_status.status = (tscm->dev_lock_count > 0); + tscm->dev_lock_changed = false; + + spin_unlock_irq(&tscm->lock); + + count = min_t(long, count, sizeof(event.lock_status)); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_tscm *tscm = hwdep->private_data; + __poll_t events; + + poll_wait(file, &tscm->hwdep_wait, wait); + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&tscm->lock); + + return events; +} + +static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(tscm->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_TASCAM; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == 0) { + tscm->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int hwdep_unlock(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == -1) { + tscm->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_tscm *tscm = hwdep->private_data; + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_count == -1) + tscm->dev_lock_count = 0; + spin_unlock_irq(&tscm->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_tscm *tscm = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(tscm, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(tscm); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(tscm); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_tscm_create_hwdep_device(struct snd_tscm *tscm) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Tascam"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM; + hwdep->ops = ops; + hwdep->private_data = tscm; + hwdep->exclusive = true; + + return err; +} diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c new file mode 100644 index 000000000..4a741570d --- /dev/null +++ b/sound/firewire/tascam/tascam-midi.c @@ -0,0 +1,136 @@ +/* + * tascam-midi.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_tscm *tscm = substream->rmidi->private_data; + + snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]); + + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void midi_playback_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_tscm *tscm = substream->rmidi->private_data; + + snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]); +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_tscm *tscm = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&tscm->lock, flags); + + if (up) + tscm->tx_midi_substreams[substrm->number] = substrm; + else + tscm->tx_midi_substreams[substrm->number] = NULL; + + spin_unlock_irqrestore(&tscm->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_tscm *tscm = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&tscm->lock, flags); + + if (up) + snd_fw_async_midi_port_run(&tscm->out_ports[substrm->number], + substrm); + + spin_unlock_irqrestore(&tscm->lock, flags); +} + +int snd_tscm_create_midi_devices(struct snd_tscm *tscm) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .drain = midi_playback_drain, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + struct snd_rawmidi_substream *subs; + int err; + + err = snd_rawmidi_new(tscm->card, tscm->card->driver, 0, + tscm->spec->midi_playback_ports, + tscm->spec->midi_capture_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", tscm->card->shortname); + rmidi->private_data = tscm; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + /* Set port names for MIDI input. */ + list_for_each_entry(subs, &stream->substreams, list) { + /* TODO: support virtual MIDI ports. */ + if (subs->number < tscm->spec->midi_capture_ports) { + /* Hardware MIDI ports. */ + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + tscm->card->shortname, subs->number + 1); + } + } + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + /* Set port names for MIDI ourput. */ + list_for_each_entry(subs, &stream->substreams, list) { + if (subs->number < tscm->spec->midi_playback_ports) { + /* Hardware MIDI ports only. */ + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + tscm->card->shortname, subs->number + 1); + } + } + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c new file mode 100644 index 000000000..9e58633e2 --- /dev/null +++ b/sound/firewire/tascam/tascam-pcm.c @@ -0,0 +1,300 @@ +/* + * tascam-pcm.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +static int pcm_init_hw_params(struct snd_tscm *tscm, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + unsigned int pcm_channels; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + stream = &tscm->tx_stream; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + stream = &tscm->rx_stream; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + } + + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + runtime->hw.channels_min = runtime->hw.channels_max = pcm_channels; + + hw->rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + snd_pcm_limit_hw_rates(runtime); + + return amdtp_tscm_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + enum snd_tscm_clock clock; + unsigned int rate; + int err; + + err = snd_tscm_stream_lock_try(tscm); + if (err < 0) + goto end; + + err = pcm_init_hw_params(tscm, substream); + if (err < 0) + goto err_locked; + + err = snd_tscm_stream_get_clock(tscm, &clock); + if (err < 0) + goto err_locked; + + if (clock != SND_TSCM_CLOCK_INTERNAL || + amdtp_stream_pcm_running(&tscm->rx_stream) || + amdtp_stream_pcm_running(&tscm->tx_stream)) { + err = snd_tscm_stream_get_rate(tscm, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_tscm_stream_lock_release(tscm); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + snd_tscm_stream_lock_release(tscm); + + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_tscm *tscm = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&tscm->mutex); + tscm->substreams_counter++; + mutex_unlock(&tscm->mutex); + } + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_tscm *tscm = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&tscm->mutex); + tscm->substreams_counter++; + mutex_unlock(&tscm->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + mutex_lock(&tscm->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + tscm->substreams_counter--; + + snd_tscm_stream_stop_duplex(tscm); + + mutex_unlock(&tscm->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + mutex_lock(&tscm->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + tscm->substreams_counter--; + + snd_tscm_stream_stop_duplex(tscm); + + mutex_unlock(&tscm->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->tx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->rx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&tscm->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&tscm->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + return amdtp_stream_pcm_ack(&tscm->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + return amdtp_stream_pcm_ack(&tscm->rx_stream); +} + +int snd_tscm_create_pcm_devices(struct snd_tscm *tscm) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(tscm->card, tscm->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = tscm; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", tscm->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + + return 0; +} diff --git a/sound/firewire/tascam/tascam-proc.c b/sound/firewire/tascam/tascam-proc.c new file mode 100644 index 000000000..fee3bf32a --- /dev/null +++ b/sound/firewire/tascam/tascam-proc.c @@ -0,0 +1,88 @@ +/* + * tascam-proc.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./tascam.h" + +static void proc_read_firmware(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_tscm *tscm = entry->private_data; + __be32 data; + unsigned int reg, fpga, arm, hw; + int err; + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_REGISTER, + &data, sizeof(data), 0); + if (err < 0) + return; + reg = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_FPGA, + &data, sizeof(data), 0); + if (err < 0) + return; + fpga = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_ARM, + &data, sizeof(data), 0); + if (err < 0) + return; + arm = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_HW, + &data, sizeof(data), 0); + if (err < 0) + return; + hw = be32_to_cpu(data); + + snd_iprintf(buffer, "Register: %d (0x%08x)\n", reg & 0xffff, reg); + snd_iprintf(buffer, "FPGA: %d (0x%08x)\n", fpga & 0xffff, fpga); + snd_iprintf(buffer, "ARM: %d (0x%08x)\n", arm & 0xffff, arm); + snd_iprintf(buffer, "Hardware: %d (0x%08x)\n", hw >> 16, hw); +} + +static void add_node(struct snd_tscm *tscm, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(tscm->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, tscm, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_tscm_proc_init(struct snd_tscm *tscm) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(tscm->card, "firewire", + tscm->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(tscm, root, "firmware", proc_read_firmware); +} diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c new file mode 100644 index 000000000..a1308f12a --- /dev/null +++ b/sound/firewire/tascam/tascam-stream.c @@ -0,0 +1,508 @@ +/* + * tascam-stream.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include "tascam.h" + +#define CLOCK_STATUS_MASK 0xffff0000 +#define CLOCK_CONFIG_MASK 0x0000ffff + +#define CALLBACK_TIMEOUT 500 + +static int get_clock(struct snd_tscm *tscm, u32 *data) +{ + int trial = 0; + __be32 reg; + int err; + + while (trial++ < 5) { + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + *data = be32_to_cpu(reg); + if (*data & CLOCK_STATUS_MASK) + break; + + // In intermediate state after changing clock status. + msleep(50); + } + + // Still in the intermediate state. + if (trial >= 5) + return -EAGAIN; + + return 0; +} + +static int set_clock(struct snd_tscm *tscm, unsigned int rate, + enum snd_tscm_clock clock) +{ + u32 data; + __be32 reg; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + data &= CLOCK_CONFIG_MASK; + + if (rate > 0) { + data &= 0x000000ff; + /* Base rate. */ + if ((rate % 44100) == 0) { + data |= 0x00000100; + /* Multiplier. */ + if (rate / 44100 == 2) + data |= 0x00008000; + } else if ((rate % 48000) == 0) { + data |= 0x00000200; + /* Multiplier. */ + if (rate / 48000 == 2) + data |= 0x00008000; + } else { + return -EAGAIN; + } + } + + if (clock != INT_MAX) { + data &= 0x0000ff00; + data |= clock + 1; + } + + reg = cpu_to_be32(data); + + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + if (data & 0x00008000) + reg = cpu_to_be32(0x0000001a); + else + reg = cpu_to_be32(0x0000000d); + + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE, + ®, sizeof(reg), 0); +} + +int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate) +{ + u32 data; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + + data = (data & 0xff000000) >> 24; + + /* Check base rate. */ + if ((data & 0x0f) == 0x01) + *rate = 44100; + else if ((data & 0x0f) == 0x02) + *rate = 48000; + else + return -EAGAIN; + + /* Check multiplier. */ + if ((data & 0xf0) == 0x80) + *rate *= 2; + else if ((data & 0xf0) != 0x00) + return -EAGAIN; + + return err; +} + +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock) +{ + u32 data; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + + *clock = ((data & 0x00ff0000) >> 16) - 1; + if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT) + return -EIO; + + return 0; +} + +static int enable_data_channels(struct snd_tscm *tscm) +{ + __be32 reg; + u32 data; + unsigned int i; + int err; + + data = 0; + for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i) + data |= BIT(i); + if (tscm->spec->has_adat) + data |= 0x0000ff00; + if (tscm->spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = 0; + for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i) + data |= BIT(i); + if (tscm->spec->has_adat) + data |= 0x0000ff00; + if (tscm->spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS, + ®, sizeof(reg), 0); +} + +static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate) +{ + __be32 reg; + int err; + + /* Set an option for unknown purpose. */ + reg = cpu_to_be32(0x00200000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + err = enable_data_channels(tscm); + if (err < 0) + return err; + + return set_clock(tscm, rate, INT_MAX); +} + +static void finish_session(struct snd_tscm *tscm) +{ + __be32 reg; + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + +} + +static int begin_session(struct snd_tscm *tscm) +{ + __be32 reg; + int err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Set an option for unknown purpose. */ + reg = cpu_to_be32(0x00002000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Start multiplexing PCM samples on packets. */ + reg = cpu_to_be32(0x00000001); + return snd_fw_transaction(tscm->unit, + TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON, + ®, sizeof(reg), 0); +} + +static void release_resources(struct snd_tscm *tscm) +{ + __be32 reg; + + /* Unregister channels. */ + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); + + /* Release isochronous resources. */ + fw_iso_resources_free(&tscm->tx_resources); + fw_iso_resources_free(&tscm->rx_resources); +} + +static int keep_resources(struct snd_tscm *tscm, unsigned int rate) +{ + __be32 reg; + int err; + + /* Keep resources for in-stream. */ + err = amdtp_tscm_set_parameters(&tscm->tx_stream, rate); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&tscm->tx_resources, + amdtp_stream_get_max_payload(&tscm->tx_stream), + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + /* Keep resources for out-stream. */ + err = amdtp_tscm_set_parameters(&tscm->rx_stream, rate); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&tscm->rx_resources, + amdtp_stream_get_max_payload(&tscm->rx_stream), + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + return err; + + /* Register the isochronous channel for transmitting stream. */ + reg = cpu_to_be32(tscm->tx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + /* Unknown */ + reg = cpu_to_be32(0x00000002); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + /* Register the isochronous channel for receiving stream. */ + reg = cpu_to_be32(tscm->rx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + return 0; +error: + release_resources(tscm); + return err; +} + +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) +{ + unsigned int pcm_channels; + int err; + + /* For out-stream. */ + err = fw_iso_resources_init(&tscm->rx_resources, tscm->unit); + if (err < 0) + return err; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + err = amdtp_tscm_init(&tscm->rx_stream, tscm->unit, AMDTP_OUT_STREAM, + pcm_channels); + if (err < 0) + return err; + + /* For in-stream. */ + err = fw_iso_resources_init(&tscm->tx_resources, tscm->unit); + if (err < 0) + return err; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + err = amdtp_tscm_init(&tscm->tx_stream, tscm->unit, AMDTP_IN_STREAM, + pcm_channels); + if (err < 0) + amdtp_stream_destroy(&tscm->rx_stream); + + return err; +} + +/* At bus reset, streaming is stopped and some registers are clear. */ +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) +{ + amdtp_stream_pcm_abort(&tscm->tx_stream); + amdtp_stream_stop(&tscm->tx_stream); + + amdtp_stream_pcm_abort(&tscm->rx_stream); + amdtp_stream_stop(&tscm->rx_stream); +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) +{ + amdtp_stream_destroy(&tscm->rx_stream); + amdtp_stream_destroy(&tscm->tx_stream); + + fw_iso_resources_destroy(&tscm->rx_resources); + fw_iso_resources_destroy(&tscm->tx_resources); +} + +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) +{ + unsigned int curr_rate; + int err; + + if (tscm->substreams_counter == 0) + return 0; + + err = snd_tscm_stream_get_rate(tscm, &curr_rate); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&tscm->rx_stream) || + amdtp_streaming_error(&tscm->tx_stream)) { + finish_session(tscm); + + amdtp_stream_stop(&tscm->rx_stream); + amdtp_stream_stop(&tscm->tx_stream); + + release_resources(tscm); + } + + if (!amdtp_stream_running(&tscm->rx_stream)) { + err = keep_resources(tscm, rate); + if (err < 0) + goto error; + + err = set_stream_formats(tscm, rate); + if (err < 0) + goto error; + + err = begin_session(tscm); + if (err < 0) + goto error; + + err = amdtp_stream_start(&tscm->rx_stream, + tscm->rx_resources.channel, + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&tscm->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + if (!amdtp_stream_running(&tscm->tx_stream)) { + err = amdtp_stream_start(&tscm->tx_stream, + tscm->tx_resources.channel, + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&tscm->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&tscm->rx_stream); + amdtp_stream_stop(&tscm->tx_stream); + + finish_session(tscm); + release_resources(tscm); + + return err; +} + +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) +{ + if (tscm->substreams_counter > 0) + return; + + amdtp_stream_stop(&tscm->tx_stream); + amdtp_stream_stop(&tscm->rx_stream); + + finish_session(tscm); + release_resources(tscm); +} + +void snd_tscm_stream_lock_changed(struct snd_tscm *tscm) +{ + tscm->dev_lock_changed = true; + wake_up(&tscm->hwdep_wait); +} + +int snd_tscm_stream_lock_try(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + /* user land lock this */ + if (tscm->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (tscm->dev_lock_count++ == 0) + snd_tscm_stream_lock_changed(tscm); + err = 0; +end: + spin_unlock_irq(&tscm->lock); + return err; +} + +void snd_tscm_stream_lock_release(struct snd_tscm *tscm) +{ + spin_lock_irq(&tscm->lock); + + if (WARN_ON(tscm->dev_lock_count <= 0)) + goto end; + if (--tscm->dev_lock_count == 0) + snd_tscm_stream_lock_changed(tscm); +end: + spin_unlock_irq(&tscm->lock); +} diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c new file mode 100644 index 000000000..92f7e74d5 --- /dev/null +++ b/sound/firewire/tascam/tascam-transaction.c @@ -0,0 +1,400 @@ +/* + * tascam-transaction.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +/* + * When return minus value, given argument is not MIDI status. + * When return 0, given argument is a beginning of system exclusive. + * When return the others, given argument is MIDI data. + */ +static inline int calculate_message_bytes(u8 status) +{ + switch (status) { + case 0xf6: /* Tune request. */ + case 0xf8: /* Timing clock. */ + case 0xfa: /* Start. */ + case 0xfb: /* Continue. */ + case 0xfc: /* Stop. */ + case 0xfe: /* Active sensing. */ + case 0xff: /* System reset. */ + return 1; + case 0xf1: /* MIDI time code quarter frame. */ + case 0xf3: /* Song select. */ + return 2; + case 0xf2: /* Song position pointer. */ + return 3; + case 0xf0: /* Exclusive. */ + return 0; + case 0xf7: /* End of exclusive. */ + break; + case 0xf4: /* Undefined. */ + case 0xf5: /* Undefined. */ + case 0xf9: /* Undefined. */ + case 0xfd: /* Undefined. */ + break; + default: + switch (status & 0xf0) { + case 0x80: /* Note on. */ + case 0x90: /* Note off. */ + case 0xa0: /* Polyphonic key pressure. */ + case 0xb0: /* Control change and Mode change. */ + case 0xe0: /* Pitch bend change. */ + return 3; + case 0xc0: /* Program change. */ + case 0xd0: /* Channel pressure. */ + return 2; + default: + break; + } + break; + } + + return -EINVAL; +} + +static int fill_message(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + int i, len, consume; + u8 *label, *msg; + u8 status; + + /* The first byte is used for label, the rest for MIDI bytes. */ + label = port->buf; + msg = port->buf + 1; + + consume = snd_rawmidi_transmit_peek(substream, msg, 3); + if (consume == 0) + return 0; + + /* On exclusive message. */ + if (port->on_sysex) { + /* Seek the end of exclusives. */ + for (i = 0; i < consume; ++i) { + if (msg[i] == 0xf7) { + port->on_sysex = false; + break; + } + } + + /* At the end of exclusive message, use label 0x07. */ + if (!port->on_sysex) { + consume = i + 1; + *label = (substream->number << 4) | 0x07; + /* During exclusive message, use label 0x04. */ + } else if (consume == 3) { + *label = (substream->number << 4) | 0x04; + /* We need to fill whole 3 bytes. Go to next change. */ + } else { + return 0; + } + + len = consume; + } else { + /* The beginning of exclusives. */ + if (msg[0] == 0xf0) { + /* Transfer it in next chance in another condition. */ + port->on_sysex = true; + return 0; + } else { + /* On running-status. */ + if ((msg[0] & 0x80) != 0x80) + status = port->running_status; + else + status = msg[0]; + + /* Calculate consume bytes. */ + len = calculate_message_bytes(status); + if (len <= 0) + return 0; + + /* On running-status. */ + if ((msg[0] & 0x80) != 0x80) { + /* Enough MIDI bytes were not retrieved. */ + if (consume < len - 1) + return 0; + consume = len - 1; + + msg[2] = msg[1]; + msg[1] = msg[0]; + msg[0] = port->running_status; + } else { + /* Enough MIDI bytes were not retrieved. */ + if (consume < len) + return 0; + consume = len; + + port->running_status = msg[0]; + } + } + + *label = (substream->number << 4) | (msg[0] >> 4); + } + + if (len > 0 && len < 3) + memset(msg + len, 0, 3 - len); + + return consume; +} + +static void async_midi_port_callback(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_fw_async_midi_port *port = callback_data; + struct snd_rawmidi_substream *substream = READ_ONCE(port->substream); + + /* This port is closed. */ + if (substream == NULL) + return; + + if (rcode == RCODE_COMPLETE) + snd_rawmidi_transmit_ack(substream, port->consume_bytes); + else if (!rcode_is_permanent_error(rcode)) + /* To start next transaction immediately for recovery. */ + port->next_ktime = 0; + else + /* Don't continue processing. */ + port->error = true; + + port->idling = true; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&port->work); +} + +static void midi_port_work(struct work_struct *work) +{ + struct snd_fw_async_midi_port *port = + container_of(work, struct snd_fw_async_midi_port, work); + struct snd_rawmidi_substream *substream = READ_ONCE(port->substream); + int generation; + + /* Under transacting or error state. */ + if (!port->idling || port->error) + return; + + /* Nothing to do. */ + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + /* Do it in next chance. */ + if (ktime_after(port->next_ktime, ktime_get())) { + schedule_work(&port->work); + return; + } + + /* + * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). + * Later, snd_rawmidi_transmit_ack() is called. + */ + memset(port->buf, 0, 4); + port->consume_bytes = fill_message(port, substream); + if (port->consume_bytes <= 0) { + /* Do it in next chance, immediately. */ + if (port->consume_bytes == 0) { + port->next_ktime = 0; + schedule_work(&port->work); + } else { + /* Fatal error. */ + port->error = true; + } + return; + } + + /* Set interval to next transaction. */ + port->next_ktime = ktime_add_ns(ktime_get(), + port->consume_bytes * 8 * (NSEC_PER_SEC / 31250)); + + /* Start this transaction. */ + port->idling = false; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = port->parent->generation; + smp_rmb(); + + fw_send_request(port->parent->card, &port->transaction, + TCODE_WRITE_QUADLET_REQUEST, + port->parent->node_id, generation, + port->parent->max_speed, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, + port->buf, 4, async_midi_port_callback, + port); +} + +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port) +{ + port->idling = true; + port->error = false; + port->running_status = 0; + port->on_sysex = false; +} + +static void handle_midi_tx(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_tscm *tscm = callback_data; + u32 *buf = (u32 *)data; + unsigned int messages; + unsigned int i; + unsigned int port; + struct snd_rawmidi_substream *substream; + u8 *b; + int bytes; + + if (offset != tscm->async_handler.offset) + goto end; + + messages = length / 8; + for (i = 0; i < messages; i++) { + b = (u8 *)(buf + i * 2); + + port = b[0] >> 4; + /* TODO: support virtual MIDI ports. */ + if (port >= tscm->spec->midi_capture_ports) + goto end; + + /* Assume the message length. */ + bytes = calculate_message_bytes(b[1]); + /* On MIDI data or exclusives. */ + if (bytes <= 0) { + /* Seek the end of exclusives. */ + for (bytes = 1; bytes < 4; bytes++) { + if (b[bytes] == 0xf7) + break; + } + if (bytes == 4) + bytes = 3; + } + + substream = READ_ONCE(tscm->tx_midi_substreams[port]); + if (substream != NULL) + snd_rawmidi_receive(substream, b + 1, bytes); + } +end: + fw_send_response(card, request, RCODE_COMPLETE); +} + +int snd_tscm_transaction_register(struct snd_tscm *tscm) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + unsigned int i; + int err; + + /* + * Usually, two quadlets are transferred by one transaction. The first + * quadlet has MIDI messages, the rest includes timestamp. + * Sometimes, 8 set of the data is transferred by a block transaction. + */ + tscm->async_handler.length = 8 * 8; + tscm->async_handler.address_callback = handle_midi_tx; + tscm->async_handler.callback_data = tscm; + + err = fw_core_add_address_handler(&tscm->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_tscm_transaction_reregister(tscm); + if (err < 0) + goto error; + + for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { + tscm->out_ports[i].parent = fw_parent_device(tscm->unit); + tscm->out_ports[i].next_ktime = 0; + INIT_WORK(&tscm->out_ports[i].work, midi_port_work); + } + + return err; +error: + fw_core_remove_address_handler(&tscm->async_handler); + tscm->async_handler.callback_data = NULL; + return err; +} + +/* At bus reset, these registers are cleared. */ +int snd_tscm_transaction_reregister(struct snd_tscm *tscm) +{ + struct fw_device *device = fw_parent_device(tscm->unit); + __be32 reg; + int err; + + /* Register messaging address. Block transaction is not allowed. */ + reg = cpu_to_be32((device->card->node_id << 16) | + (tscm->async_handler.offset >> 32)); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(tscm->async_handler.offset); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Turn on messaging. */ + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Turn on FireWire LED. */ + reg = cpu_to_be32(0x0001008e); + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER, + ®, sizeof(reg), 0); +} + +void snd_tscm_transaction_unregister(struct snd_tscm *tscm) +{ + __be32 reg; + + if (tscm->async_handler.callback_data == NULL) + return; + + /* Turn off FireWire LED. */ + reg = cpu_to_be32(0x0000008e); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER, + ®, sizeof(reg), 0); + + /* Turn off messaging. */ + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, + ®, sizeof(reg), 0); + + /* Unregister the address. */ + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, + ®, sizeof(reg), 0); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&tscm->async_handler); + tscm->async_handler.callback_data = NULL; +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c new file mode 100644 index 000000000..1e61cdce2 --- /dev/null +++ b/sound/firewire/tascam/tascam.c @@ -0,0 +1,290 @@ +/* + * tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +MODULE_DESCRIPTION("TASCAM FireWire series Driver"); +MODULE_AUTHOR("Takashi Sakamoto "); +MODULE_LICENSE("GPL v2"); + +static const struct snd_tscm_spec model_specs[] = { + { + .name = "FW-1884", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 8, + .midi_capture_ports = 4, + .midi_playback_ports = 4, + }, + { + .name = "FW-1082", + .has_adat = false, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 2, + .midi_playback_ports = 2, + }, + { + .name = "FW-1804", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 2, + .midi_playback_ports = 4, + }, +}; + +static int identify_model(struct snd_tscm *tscm) +{ + struct fw_device *fw_dev = fw_parent_device(tscm->unit); + const u32 *config_rom = fw_dev->config_rom; + char model[9]; + unsigned int i; + u8 c; + + if (fw_dev->config_rom_length < 30) { + dev_err(&tscm->unit->device, + "Configuration ROM is too short.\n"); + return -ENODEV; + } + + /* Pick up model name from certain addresses. */ + for (i = 0; i < 8; i++) { + c = config_rom[28 + i / 4] >> (24 - 8 * (i % 4)); + if (c == '\0') + break; + model[i] = c; + } + model[i] = '\0'; + + for (i = 0; i < ARRAY_SIZE(model_specs); i++) { + if (strcmp(model, model_specs[i].name) == 0) { + tscm->spec = &model_specs[i]; + break; + } + } + if (tscm->spec == NULL) + return -ENODEV; + + strcpy(tscm->card->driver, "FW-TASCAM"); + strcpy(tscm->card->shortname, model); + strcpy(tscm->card->mixername, model); + snprintf(tscm->card->longname, sizeof(tscm->card->longname), + "TASCAM %s, GUID %08x%08x at %s, S%d", model, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&tscm->unit->device), 100 << fw_dev->max_speed); + + return 0; +} + +static void tscm_free(struct snd_tscm *tscm) +{ + snd_tscm_transaction_unregister(tscm); + snd_tscm_stream_destroy_duplex(tscm); + + fw_unit_put(tscm->unit); + + mutex_destroy(&tscm->mutex); + kfree(tscm); +} + +static void tscm_card_free(struct snd_card *card) +{ + tscm_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_tscm *tscm = container_of(work, struct snd_tscm, dwork.work); + int err; + + err = snd_card_new(&tscm->unit->device, -1, NULL, THIS_MODULE, 0, + &tscm->card); + if (err < 0) + return; + + err = identify_model(tscm); + if (err < 0) + goto error; + + err = snd_tscm_transaction_register(tscm); + if (err < 0) + goto error; + + err = snd_tscm_stream_init_duplex(tscm); + if (err < 0) + goto error; + + snd_tscm_proc_init(tscm); + + err = snd_tscm_create_pcm_devices(tscm); + if (err < 0) + goto error; + + err = snd_tscm_create_midi_devices(tscm); + if (err < 0) + goto error; + + err = snd_tscm_create_hwdep_device(tscm); + if (err < 0) + goto error; + + err = snd_card_register(tscm->card); + if (err < 0) + goto error; + + /* + * After registered, tscm instance can be released corresponding to + * releasing the sound card instance. + */ + tscm->card->private_free = tscm_card_free; + tscm->card->private_data = tscm; + tscm->registered = true; + + return; +error: + snd_tscm_transaction_unregister(tscm); + snd_tscm_stream_destroy_duplex(tscm); + snd_card_free(tscm->card); + dev_info(&tscm->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_tscm_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_tscm *tscm; + + /* Allocate this independent of sound card instance. */ + tscm = kzalloc(sizeof(struct snd_tscm), GFP_KERNEL); + if (tscm == NULL) + return -ENOMEM; + + /* initialize myself */ + tscm->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, tscm); + + mutex_init(&tscm->mutex); + spin_lock_init(&tscm->lock); + init_waitqueue_head(&tscm->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&tscm->dwork, do_registration); + snd_fw_schedule_registration(unit, &tscm->dwork); + + return 0; +} + +static void snd_tscm_update(struct fw_unit *unit) +{ + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!tscm->registered) + snd_fw_schedule_registration(unit, &tscm->dwork); + + snd_tscm_transaction_reregister(tscm); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (tscm->registered) { + mutex_lock(&tscm->mutex); + snd_tscm_stream_update_duplex(tscm); + mutex_unlock(&tscm->mutex); + } +} + +static void snd_tscm_remove(struct fw_unit *unit) +{ + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&tscm->dwork); + + if (tscm->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(tscm->card); + } else { + /* Don't forget this case. */ + tscm_free(tscm); + } +} + +static const struct ieee1394_device_id snd_tscm_id_table[] = { + // Tascam, FW-1884. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800000, + }, + // Tascam, FE-8 (.version = 0x800001) + // This kernel module doesn't support FE-8 because the most of features + // can be implemented in userspace without any specific support of this + // module. + // + // .version = 0x800002 is unknown. + // + // Tascam, FW-1082. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800003, + }, + // Tascam, FW-1804. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800004, + }, + /* FE-08 requires reverse-engineering because it just has faders. */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_tscm_id_table); + +static struct fw_driver tscm_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-firewire-tascam", + .bus = &fw_bus_type, + }, + .probe = snd_tscm_probe, + .update = snd_tscm_update, + .remove = snd_tscm_remove, + .id_table = snd_tscm_id_table, +}; + +static int __init snd_tscm_init(void) +{ + return driver_register(&tscm_driver.driver); +} + +static void __exit snd_tscm_exit(void) +{ + driver_unregister(&tscm_driver.driver); +} + +module_init(snd_tscm_init); +module_exit(snd_tscm_exit); diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h new file mode 100644 index 000000000..a5bd167eb --- /dev/null +++ b/sound/firewire/tascam/tascam.h @@ -0,0 +1,180 @@ +/* + * tascam.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_TASCAM_H_INCLUDED +#define SOUND_TASCAM_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +struct snd_tscm_spec { + const char *const name; + bool has_adat; + bool has_spdif; + unsigned int pcm_capture_analog_channels; + unsigned int pcm_playback_analog_channels; + unsigned int midi_capture_ports; + unsigned int midi_playback_ports; +}; + +#define TSCM_MIDI_IN_PORT_MAX 4 +#define TSCM_MIDI_OUT_PORT_MAX 4 + +struct snd_fw_async_midi_port { + struct fw_device *parent; + struct work_struct work; + bool idling; + ktime_t next_ktime; + bool error; + + struct fw_transaction transaction; + + u8 buf[4]; + u8 running_status; + bool on_sysex; + + struct snd_rawmidi_substream *substream; + int consume_bytes; +}; + +struct snd_tscm { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + const struct snd_tscm_spec *spec; + + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int substreams_counter; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* For MIDI message incoming transactions. */ + struct fw_address_handler async_handler; + struct snd_rawmidi_substream *tx_midi_substreams[TSCM_MIDI_IN_PORT_MAX]; + + /* For MIDI message outgoing transactions. */ + struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; +}; + +#define TSCM_ADDR_BASE 0xffff00000000ull + +#define TSCM_OFFSET_FIRMWARE_REGISTER 0x0000 +#define TSCM_OFFSET_FIRMWARE_FPGA 0x0004 +#define TSCM_OFFSET_FIRMWARE_ARM 0x0008 +#define TSCM_OFFSET_FIRMWARE_HW 0x000c + +#define TSCM_OFFSET_ISOC_TX_CH 0x0200 +#define TSCM_OFFSET_UNKNOWN 0x0204 +#define TSCM_OFFSET_START_STREAMING 0x0208 +#define TSCM_OFFSET_ISOC_RX_CH 0x020c +#define TSCM_OFFSET_ISOC_RX_ON 0x0210 /* Little conviction. */ +#define TSCM_OFFSET_TX_PCM_CHANNELS 0x0214 +#define TSCM_OFFSET_RX_PCM_CHANNELS 0x0218 +#define TSCM_OFFSET_MULTIPLEX_MODE 0x021c +#define TSCM_OFFSET_ISOC_TX_ON 0x0220 +/* Unknown 0x0224 */ +#define TSCM_OFFSET_CLOCK_STATUS 0x0228 +#define TSCM_OFFSET_SET_OPTION 0x022c + +#define TSCM_OFFSET_MIDI_TX_ON 0x0300 +#define TSCM_OFFSET_MIDI_TX_ADDR_HI 0x0304 +#define TSCM_OFFSET_MIDI_TX_ADDR_LO 0x0308 + +#define TSCM_OFFSET_LED_POWER 0x0404 + +#define TSCM_OFFSET_MIDI_RX_QUAD 0x4000 + +enum snd_tscm_clock { + SND_TSCM_CLOCK_INTERNAL = 0, + SND_TSCM_CLOCK_WORD = 1, + SND_TSCM_CLOCK_SPDIF = 2, + SND_TSCM_CLOCK_ADAT = 3, +}; + +int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels); +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate); +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); + +int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate); +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, + enum snd_tscm_clock *clock); +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm); +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm); + +void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); +int snd_tscm_stream_lock_try(struct snd_tscm *tscm); +void snd_tscm_stream_lock_release(struct snd_tscm *tscm); + +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port); + +static inline void +snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + if (!port->error) { + port->substream = substream; + schedule_work(&port->work); + } +} + +static inline void +snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) +{ + port->substream = NULL; + cancel_work_sync(&port->work); + port->error = false; +} + +int snd_tscm_transaction_register(struct snd_tscm *tscm); +int snd_tscm_transaction_reregister(struct snd_tscm *tscm); +void snd_tscm_transaction_unregister(struct snd_tscm *tscm); + +void snd_tscm_proc_init(struct snd_tscm *tscm); + +int snd_tscm_create_pcm_devices(struct snd_tscm *tscm); + +int snd_tscm_create_midi_devices(struct snd_tscm *tscm); + +int snd_tscm_create_hwdep_device(struct snd_tscm *tscm); + +#endif diff --git a/sound/hda/Kconfig b/sound/hda/Kconfig new file mode 100644 index 000000000..2d90e11b3 --- /dev/null +++ b/sound/hda/Kconfig @@ -0,0 +1,30 @@ +config SND_HDA_CORE + tristate + select REGMAP + +config SND_HDA_DSP_LOADER + bool + +config SND_HDA_COMPONENT + bool + +config SND_HDA_I915 + bool + select SND_HDA_COMPONENT + +config SND_HDA_EXT_CORE + tristate + select SND_HDA_CORE + +config SND_HDA_PREALLOC_SIZE + int "Pre-allocated buffer size for HD-audio driver" + range 0 32768 + default 64 + help + Specifies the default pre-allocated buffer-size in kB for the + HD-audio driver. A larger buffer (e.g. 2048) is preferred + for systems using PulseAudio. The default 64 is chosen just + for compatibility reasons. + + Note that the pre-allocation size can be changed dynamically + via a proc file (/proc/asound/card*/pcm*/sub*/prealloc), too. diff --git a/sound/hda/Makefile b/sound/hda/Makefile new file mode 100644 index 000000000..2160202e2 --- /dev/null +++ b/sound/hda/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-hda-core-objs := hda_bus_type.o hdac_bus.o hdac_device.o hdac_sysfs.o \ + hdac_regmap.o hdac_controller.o hdac_stream.o array.o hdmi_chmap.o + +snd-hda-core-objs += trace.o +CFLAGS_trace.o := -I$(src) + +# for sync with i915 gfx driver +snd-hda-core-$(CONFIG_SND_HDA_COMPONENT) += hdac_component.o +snd-hda-core-$(CONFIG_SND_HDA_I915) += hdac_i915.o + +obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o + +#extended hda +obj-$(CONFIG_SND_HDA_EXT_CORE) += ext/ diff --git a/sound/hda/array.c b/sound/hda/array.c new file mode 100644 index 000000000..5dfa610e4 --- /dev/null +++ b/sound/hda/array.c @@ -0,0 +1,51 @@ +/* + * generic arrays + */ + +#include +#include +#include + +/** + * snd_array_new - get a new element from the given array + * @array: the array object + * + * Get a new element from the given array. If it exceeds the + * pre-allocated array size, re-allocate the array. + * + * Returns NULL if allocation failed. + */ +void *snd_array_new(struct snd_array *array) +{ + if (snd_BUG_ON(!array->elem_size)) + return NULL; + if (array->used >= array->alloced) { + int num = array->alloced + array->alloc_align; + int oldsize = array->alloced * array->elem_size; + int size = (num + 1) * array->elem_size; + void *nlist; + if (snd_BUG_ON(num >= 4096)) + return NULL; + nlist = krealloc(array->list, size, GFP_KERNEL); + if (!nlist) + return NULL; + memset(nlist + oldsize, 0, size - oldsize); + array->list = nlist; + array->alloced = num; + } + return snd_array_elem(array, array->used++); +} +EXPORT_SYMBOL_GPL(snd_array_new); + +/** + * snd_array_free - free the given array elements + * @array: the array object + */ +void snd_array_free(struct snd_array *array) +{ + kfree(array->list); + array->used = 0; + array->alloced = 0; + array->list = NULL; +} +EXPORT_SYMBOL_GPL(snd_array_free); diff --git a/sound/hda/ext/Makefile b/sound/hda/ext/Makefile new file mode 100644 index 000000000..9b6f641c7 --- /dev/null +++ b/sound/hda/ext/Makefile @@ -0,0 +1,3 @@ +snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o hdac_ext_stream.o + +obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o diff --git a/sound/hda/ext/hdac_ext_bus.c b/sound/hda/ext/hdac_ext_bus.c new file mode 100644 index 000000000..08cc0ce3b --- /dev/null +++ b/sound/hda/ext/hdac_ext_bus.c @@ -0,0 +1,258 @@ +/* + * hdac-ext-bus.c - HD-audio extended core bus functions. + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include + +MODULE_DESCRIPTION("HDA extended core"); +MODULE_LICENSE("GPL v2"); + +static void hdac_ext_writel(u32 value, u32 __iomem *addr) +{ + writel(value, addr); +} + +static u32 hdac_ext_readl(u32 __iomem *addr) +{ + return readl(addr); +} + +static void hdac_ext_writew(u16 value, u16 __iomem *addr) +{ + writew(value, addr); +} + +static u16 hdac_ext_readw(u16 __iomem *addr) +{ + return readw(addr); +} + +static void hdac_ext_writeb(u8 value, u8 __iomem *addr) +{ + writeb(value, addr); +} + +static u8 hdac_ext_readb(u8 __iomem *addr) +{ + return readb(addr); +} + +static int hdac_ext_dma_alloc_pages(struct hdac_bus *bus, int type, + size_t size, struct snd_dma_buffer *buf) +{ + return snd_dma_alloc_pages(type, bus->dev, size, buf); +} + +static void hdac_ext_dma_free_pages(struct hdac_bus *bus, struct snd_dma_buffer *buf) +{ + snd_dma_free_pages(buf); +} + +static const struct hdac_io_ops hdac_ext_default_io = { + .reg_writel = hdac_ext_writel, + .reg_readl = hdac_ext_readl, + .reg_writew = hdac_ext_writew, + .reg_readw = hdac_ext_readw, + .reg_writeb = hdac_ext_writeb, + .reg_readb = hdac_ext_readb, + .dma_alloc_pages = hdac_ext_dma_alloc_pages, + .dma_free_pages = hdac_ext_dma_free_pages, +}; + +/** + * snd_hdac_ext_bus_init - initialize a HD-audio extended bus + * @ebus: the pointer to extended bus object + * @dev: device pointer + * @ops: bus verb operators + * @io_ops: lowlevel I/O operators, can be NULL. If NULL core will use + * default ops + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_ext_bus_init(struct hdac_bus *bus, struct device *dev, + const struct hdac_bus_ops *ops, + const struct hdac_io_ops *io_ops, + const struct hdac_ext_bus_ops *ext_ops) +{ + int ret; + static int idx; + + /* check if io ops are provided, if not load the defaults */ + if (io_ops == NULL) + io_ops = &hdac_ext_default_io; + + ret = snd_hdac_bus_init(bus, dev, ops, io_ops); + if (ret < 0) + return ret; + + bus->ext_ops = ext_ops; + INIT_LIST_HEAD(&bus->hlink_list); + bus->idx = idx++; + + mutex_init(&bus->lock); + bus->cmd_dma_state = true; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_init); + +/** + * snd_hdac_ext_bus_exit - clean up a HD-audio extended bus + * @ebus: the pointer to extended bus object + */ +void snd_hdac_ext_bus_exit(struct hdac_bus *bus) +{ + snd_hdac_bus_exit(bus); + WARN_ON(!list_empty(&bus->hlink_list)); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_exit); + +static void default_release(struct device *dev) +{ + snd_hdac_ext_bus_device_exit(container_of(dev, struct hdac_device, dev)); +} + +/** + * snd_hdac_ext_bus_device_init - initialize the HDA extended codec base device + * @ebus: hdac extended bus to attach to + * @addr: codec address + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_ext_bus_device_init(struct hdac_bus *bus, int addr, + struct hdac_device *hdev) +{ + char name[15]; + int ret; + + hdev->bus = bus; + + snprintf(name, sizeof(name), "ehdaudio%dD%d", bus->idx, addr); + + ret = snd_hdac_device_init(hdev, bus, name, addr); + if (ret < 0) { + dev_err(bus->dev, "device init failed for hdac device\n"); + return ret; + } + hdev->type = HDA_DEV_ASOC; + hdev->dev.release = default_release; + + ret = snd_hdac_device_register(hdev); + if (ret) { + dev_err(bus->dev, "failed to register hdac device\n"); + snd_hdac_ext_bus_device_exit(hdev); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_init); + +/** + * snd_hdac_ext_bus_device_exit - clean up a HD-audio extended codec base device + * @hdev: hdac device to clean up + */ +void snd_hdac_ext_bus_device_exit(struct hdac_device *hdev) +{ + snd_hdac_device_exit(hdev); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_exit); + +/** + * snd_hdac_ext_bus_device_remove - remove HD-audio extended codec base devices + * + * @ebus: HD-audio extended bus + */ +void snd_hdac_ext_bus_device_remove(struct hdac_bus *bus) +{ + struct hdac_device *codec, *__codec; + /* + * we need to remove all the codec devices objects created in the + * snd_hdac_ext_bus_device_init + */ + list_for_each_entry_safe(codec, __codec, &bus->codec_list, list) { + snd_hdac_device_unregister(codec); + put_device(&codec->dev); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_remove); +#define dev_to_hdac(dev) (container_of((dev), \ + struct hdac_device, dev)) + +static inline struct hdac_driver *get_hdrv(struct device *dev) +{ + struct hdac_driver *hdrv = drv_to_hdac_driver(dev->driver); + return hdrv; +} + +static inline struct hdac_device *get_hdev(struct device *dev) +{ + struct hdac_device *hdev = dev_to_hdac_dev(dev); + return hdev; +} + +static int hda_ext_drv_probe(struct device *dev) +{ + return (get_hdrv(dev))->probe(get_hdev(dev)); +} + +static int hdac_ext_drv_remove(struct device *dev) +{ + return (get_hdrv(dev))->remove(get_hdev(dev)); +} + +static void hdac_ext_drv_shutdown(struct device *dev) +{ + return (get_hdrv(dev))->shutdown(get_hdev(dev)); +} + +/** + * snd_hda_ext_driver_register - register a driver for ext hda devices + * + * @drv: ext hda driver structure + */ +int snd_hda_ext_driver_register(struct hdac_driver *drv) +{ + drv->type = HDA_DEV_ASOC; + drv->driver.bus = &snd_hda_bus_type; + /* we use default match */ + + if (drv->probe) + drv->driver.probe = hda_ext_drv_probe; + if (drv->remove) + drv->driver.remove = hdac_ext_drv_remove; + if (drv->shutdown) + drv->driver.shutdown = hdac_ext_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(snd_hda_ext_driver_register); + +/** + * snd_hda_ext_driver_unregister - unregister a driver for ext hda devices + * + * @drv: ext hda driver structure + */ +void snd_hda_ext_driver_unregister(struct hdac_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(snd_hda_ext_driver_unregister); diff --git a/sound/hda/ext/hdac_ext_controller.c b/sound/hda/ext/hdac_ext_controller.c new file mode 100644 index 000000000..b96abebcf --- /dev/null +++ b/sound/hda/ext/hdac_ext_controller.c @@ -0,0 +1,337 @@ +/* + * hdac-ext-controller.c - HD-audio extended controller functions. + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include + +/* + * maximum HDAC capablities we should parse to avoid endless looping: + * currently we have 4 extended caps, so this is future proof for now. + * extend when this limit is seen meeting in real HW + */ +#define HDAC_MAX_CAPS 10 + +/* + * processing pipe helpers - these helpers are useful for dealing with HDA + * new capability of processing pipelines + */ + +/** + * snd_hdac_ext_bus_ppcap_enable - enable/disable processing pipe capability + * @ebus: HD-audio extended core bus + * @enable: flag to turn on/off the capability + */ +void snd_hdac_ext_bus_ppcap_enable(struct hdac_bus *bus, bool enable) +{ + + if (!bus->ppcap) { + dev_err(bus->dev, "Address of PP capability is NULL"); + return; + } + + if (enable) + snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, + AZX_PPCTL_GPROCEN, AZX_PPCTL_GPROCEN); + else + snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, + AZX_PPCTL_GPROCEN, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_enable); + +/** + * snd_hdac_ext_bus_ppcap_int_enable - ppcap interrupt enable/disable + * @ebus: HD-audio extended core bus + * @enable: flag to enable/disable interrupt + */ +void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_bus *bus, bool enable) +{ + + if (!bus->ppcap) { + dev_err(bus->dev, "Address of PP capability is NULL\n"); + return; + } + + if (enable) + snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, + AZX_PPCTL_PIE, AZX_PPCTL_PIE); + else + snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, + AZX_PPCTL_PIE, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_int_enable); + +/* + * Multilink helpers - these helpers are useful for dealing with HDA + * new multilink capability + */ + +/** + * snd_hdac_ext_bus_get_ml_capabilities - get multilink capability + * @ebus: HD-audio extended core bus + * + * This will parse all links and read the mlink capabilities and add them + * in hlink_list of extended hdac bus + * Note: this will be freed on bus exit by driver + */ +int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus) +{ + int idx; + u32 link_count; + struct hdac_ext_link *hlink; + + link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count); + + for (idx = 0; idx < link_count; idx++) { + hlink = kzalloc(sizeof(*hlink), GFP_KERNEL); + if (!hlink) + return -ENOMEM; + hlink->index = idx; + hlink->bus = bus; + hlink->ml_addr = bus->mlcap + AZX_ML_BASE + + (AZX_ML_INTERVAL * idx); + hlink->lcaps = readl(hlink->ml_addr + AZX_REG_ML_LCAP); + hlink->lsdiid = readw(hlink->ml_addr + AZX_REG_ML_LSDIID); + + /* since link in On, update the ref */ + hlink->ref_count = 1; + + list_add_tail(&hlink->list, &bus->hlink_list); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities); + +/** + * snd_hdac_link_free_all- free hdac extended link objects + * + * @ebus: HD-audio ext core bus + */ + +void snd_hdac_link_free_all(struct hdac_bus *bus) +{ + struct hdac_ext_link *l; + + while (!list_empty(&bus->hlink_list)) { + l = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list); + list_del(&l->list); + kfree(l); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_link_free_all); + +/** + * snd_hdac_ext_bus_get_link_index - get link based on codec name + * @ebus: HD-audio extended core bus + * @codec_name: codec name + */ +struct hdac_ext_link *snd_hdac_ext_bus_get_link(struct hdac_bus *bus, + const char *codec_name) +{ + int i; + struct hdac_ext_link *hlink = NULL; + int bus_idx, addr; + + if (sscanf(codec_name, "ehdaudio%dD%d", &bus_idx, &addr) != 2) + return NULL; + if (bus->idx != bus_idx) + return NULL; + if (addr < 0 || addr > 31) + return NULL; + + list_for_each_entry(hlink, &bus->hlink_list, list) { + for (i = 0; i < HDA_MAX_CODECS; i++) { + if (hlink->lsdiid & (0x1 << addr)) + return hlink; + } + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_link); + +static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) +{ + int timeout; + u32 val; + int mask = (1 << AZX_MLCTL_CPA_SHIFT); + + udelay(3); + timeout = 150; + + do { + val = readl(link->ml_addr + AZX_REG_ML_LCTL); + if (enable) { + if (((val & mask) >> AZX_MLCTL_CPA_SHIFT)) + return 0; + } else { + if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT)) + return 0; + } + udelay(3); + } while (--timeout); + + return -EIO; +} + +/** + * snd_hdac_ext_bus_link_power_up -power up hda link + * @link: HD-audio extended link + */ +int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *link) +{ + snd_hdac_updatel(link->ml_addr, AZX_REG_ML_LCTL, + AZX_MLCTL_SPA, AZX_MLCTL_SPA); + + return check_hdac_link_power_active(link, true); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up); + +/** + * snd_hdac_ext_bus_link_power_down -power down hda link + * @link: HD-audio extended link + */ +int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *link) +{ + snd_hdac_updatel(link->ml_addr, AZX_REG_ML_LCTL, AZX_MLCTL_SPA, 0); + + return check_hdac_link_power_active(link, false); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down); + +/** + * snd_hdac_ext_bus_link_power_up_all -power up all hda link + * @ebus: HD-audio extended bus + */ +int snd_hdac_ext_bus_link_power_up_all(struct hdac_bus *bus) +{ + struct hdac_ext_link *hlink = NULL; + int ret; + + list_for_each_entry(hlink, &bus->hlink_list, list) { + snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL, + AZX_MLCTL_SPA, AZX_MLCTL_SPA); + ret = check_hdac_link_power_active(hlink, true); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up_all); + +/** + * snd_hdac_ext_bus_link_power_down_all -power down all hda link + * @ebus: HD-audio extended bus + */ +int snd_hdac_ext_bus_link_power_down_all(struct hdac_bus *bus) +{ + struct hdac_ext_link *hlink = NULL; + int ret; + + list_for_each_entry(hlink, &bus->hlink_list, list) { + snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL, + AZX_MLCTL_SPA, 0); + ret = check_hdac_link_power_active(hlink, false); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down_all); + +int snd_hdac_ext_bus_link_get(struct hdac_bus *bus, + struct hdac_ext_link *link) +{ + unsigned long codec_mask; + int ret = 0; + + mutex_lock(&bus->lock); + + /* + * if we move from 0 to 1, count will be 1 so power up this link + * as well, also check the dma status and trigger that + */ + if (++link->ref_count == 1) { + if (!bus->cmd_dma_state) { + snd_hdac_bus_init_cmd_io(bus); + bus->cmd_dma_state = true; + } + + ret = snd_hdac_ext_bus_link_power_up(link); + + /* + * wait for 521usec for codec to report status + * HDA spec section 4.3 - Codec Discovery + */ + udelay(521); + codec_mask = snd_hdac_chip_readw(bus, STATESTS); + dev_dbg(bus->dev, "codec_mask = 0x%lx\n", codec_mask); + snd_hdac_chip_writew(bus, STATESTS, codec_mask); + if (!bus->codec_mask) + bus->codec_mask = codec_mask; + } + + mutex_unlock(&bus->lock); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_get); + +int snd_hdac_ext_bus_link_put(struct hdac_bus *bus, + struct hdac_ext_link *link) +{ + int ret = 0; + struct hdac_ext_link *hlink; + bool link_up = false; + + mutex_lock(&bus->lock); + + /* + * if we move from 1 to 0, count will be 0 + * so power down this link as well + */ + if (--link->ref_count == 0) { + ret = snd_hdac_ext_bus_link_power_down(link); + + /* + * now check if all links are off, if so turn off + * cmd dma as well + */ + list_for_each_entry(hlink, &bus->hlink_list, list) { + if (hlink->ref_count) { + link_up = true; + break; + } + } + + if (!link_up) { + snd_hdac_bus_stop_cmd_io(bus); + bus->cmd_dma_state = false; + } + } + + mutex_unlock(&bus->lock); + return ret; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_put); diff --git a/sound/hda/ext/hdac_ext_stream.c b/sound/hda/ext/hdac_ext_stream.c new file mode 100644 index 000000000..a835558dd --- /dev/null +++ b/sound/hda/ext/hdac_ext_stream.c @@ -0,0 +1,551 @@ +/* + * hdac-ext-stream.c - HD-audio extended stream operations. + * + * Copyright (C) 2015 Intel Corp + * Author: Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include + +/** + * snd_hdac_ext_stream_init - initialize each stream (aka device) + * @bus: HD-audio core bus + * @stream: HD-audio ext core stream object to initialize + * @idx: stream index number + * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE) + * @tag: the tag id to assign + * + * initialize the stream, if ppcap is enabled then init those and then + * invoke hdac stream initialization routine + */ +void snd_hdac_ext_stream_init(struct hdac_bus *bus, + struct hdac_ext_stream *stream, + int idx, int direction, int tag) +{ + if (bus->ppcap) { + stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE + + AZX_PPHC_INTERVAL * idx; + + stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE + + AZX_PPLC_MULTI * bus->num_streams + + AZX_PPLC_INTERVAL * idx; + } + + if (bus->spbcap) { + stream->spib_addr = bus->spbcap + AZX_SPB_BASE + + AZX_SPB_INTERVAL * idx + + AZX_SPB_SPIB; + + stream->fifo_addr = bus->spbcap + AZX_SPB_BASE + + AZX_SPB_INTERVAL * idx + + AZX_SPB_MAXFIFO; + } + + if (bus->drsmcap) + stream->dpibr_addr = bus->drsmcap + AZX_DRSM_BASE + + AZX_DRSM_INTERVAL * idx; + + stream->decoupled = false; + snd_hdac_stream_init(bus, &stream->hstream, idx, direction, tag); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init); + +/** + * snd_hdac_ext_stream_init_all - create and initialize the stream objects + * for an extended hda bus + * @bus: HD-audio core bus + * @start_idx: start index for streams + * @num_stream: number of streams to initialize + * @dir: direction of streams + */ +int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + int num_stream, int dir) +{ + int stream_tag = 0; + int i, tag, idx = start_idx; + + for (i = 0; i < num_stream; i++) { + struct hdac_ext_stream *stream = + kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + tag = ++stream_tag; + snd_hdac_ext_stream_init(bus, stream, idx, dir, tag); + idx++; + } + + return 0; + +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); + +/** + * snd_hdac_stream_free_all - free hdac extended stream objects + * + * @bus: HD-audio core bus + */ +void snd_hdac_stream_free_all(struct hdac_bus *bus) +{ + struct hdac_stream *s, *_s; + struct hdac_ext_stream *stream; + + list_for_each_entry_safe(s, _s, &bus->stream_list, list) { + stream = stream_to_hdac_ext_stream(s); + snd_hdac_ext_stream_decouple(bus, stream, false); + list_del(&s->list); + kfree(stream); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all); + +/** + * snd_hdac_ext_stream_decouple - decouple the hdac stream + * @bus: HD-audio core bus + * @stream: HD-audio ext core stream object to initialize + * @decouple: flag to decouple + */ +void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, + struct hdac_ext_stream *stream, bool decouple) +{ + struct hdac_stream *hstream = &stream->hstream; + u32 val; + int mask = AZX_PPCTL_PROCEN(hstream->index); + + spin_lock_irq(&bus->reg_lock); + val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask; + + if (decouple && !val) + snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask); + else if (!decouple && val) + snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0); + + stream->decoupled = decouple; + spin_unlock_irq(&bus->reg_lock); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); + +/** + * snd_hdac_ext_linkstream_start - start a stream + * @stream: HD-audio ext core stream to start + */ +void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *stream) +{ + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, + AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_start); + +/** + * snd_hdac_ext_link_stream_clear - stop a stream DMA + * @stream: HD-audio ext core stream to stop + */ +void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *stream) +{ + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_clear); + +/** + * snd_hdac_ext_link_stream_reset - reset a stream + * @stream: HD-audio ext core stream to reset + */ +void snd_hdac_ext_link_stream_reset(struct hdac_ext_stream *stream) +{ + unsigned char val; + int timeout; + + snd_hdac_ext_link_stream_clear(stream); + + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, + AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST); + udelay(3); + timeout = 50; + do { + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) & + AZX_PPLCCTL_STRST; + if (val) + break; + udelay(3); + } while (--timeout); + val &= ~AZX_PPLCCTL_STRST; + writel(val, stream->pplc_addr + AZX_REG_PPLCCTL); + udelay(3); + + timeout = 50; + /* waiting for hardware to report that the stream is out of reset */ + do { + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST; + if (!val) + break; + udelay(3); + } while (--timeout); + +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_reset); + +/** + * snd_hdac_ext_link_stream_setup - set up the SD for streaming + * @stream: HD-audio ext core stream to set up + * @fmt: stream format + */ +int snd_hdac_ext_link_stream_setup(struct hdac_ext_stream *stream, int fmt) +{ + struct hdac_stream *hstream = &stream->hstream; + unsigned int val; + + /* make sure the run bit is zero for SD */ + snd_hdac_ext_link_stream_clear(stream); + /* program the stream_tag */ + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL); + val = (val & ~AZX_PPLCCTL_STRM_MASK) | + (hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT); + writel(val, stream->pplc_addr + AZX_REG_PPLCCTL); + + /* program the stream format */ + writew(fmt, stream->pplc_addr + AZX_REG_PPLCFMT); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_setup); + +/** + * snd_hdac_ext_link_set_stream_id - maps stream id to link output + * @link: HD-audio ext link to set up + * @stream: stream id + */ +void snd_hdac_ext_link_set_stream_id(struct hdac_ext_link *link, + int stream) +{ + snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 1 << stream); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_set_stream_id); + +/** + * snd_hdac_ext_link_clear_stream_id - maps stream id to link output + * @link: HD-audio ext link to set up + * @stream: stream id + */ +void snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link *link, + int stream) +{ + snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_clear_stream_id); + +static struct hdac_ext_stream * +hdac_ext_link_stream_assign(struct hdac_bus *bus, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *res = NULL; + struct hdac_stream *stream = NULL; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + + list_for_each_entry(stream, &bus->stream_list, list) { + struct hdac_ext_stream *hstream = container_of(stream, + struct hdac_ext_stream, + hstream); + if (stream->direction != substream->stream) + continue; + + /* check if decoupled stream and not in use is available */ + if (hstream->decoupled && !hstream->link_locked) { + res = hstream; + break; + } + + if (!hstream->link_locked) { + snd_hdac_ext_stream_decouple(bus, hstream, true); + res = hstream; + break; + } + } + if (res) { + spin_lock_irq(&bus->reg_lock); + res->link_locked = 1; + res->link_substream = substream; + spin_unlock_irq(&bus->reg_lock); + } + return res; +} + +static struct hdac_ext_stream * +hdac_ext_host_stream_assign(struct hdac_bus *bus, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *res = NULL; + struct hdac_stream *stream = NULL; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + + list_for_each_entry(stream, &bus->stream_list, list) { + struct hdac_ext_stream *hstream = container_of(stream, + struct hdac_ext_stream, + hstream); + if (stream->direction != substream->stream) + continue; + + if (!stream->opened) { + if (!hstream->decoupled) + snd_hdac_ext_stream_decouple(bus, hstream, true); + res = hstream; + break; + } + } + if (res) { + spin_lock_irq(&bus->reg_lock); + res->hstream.opened = 1; + res->hstream.running = 0; + res->hstream.substream = substream; + spin_unlock_irq(&bus->reg_lock); + } + + return res; +} + +/** + * snd_hdac_ext_stream_assign - assign a stream for the PCM + * @bus: HD-audio core bus + * @substream: PCM substream to assign + * @type: type of stream (coupled, host or link stream) + * + * This assigns the stream based on the type (coupled/host/link), for the + * given PCM substream, assigns it and returns the stream object + * + * coupled: Looks for an unused stream + * host: Looks for an unused decoupled host stream + * link: Looks for an unused decoupled link stream + * + * If no stream is free, returns NULL. The function tries to keep using + * the same stream object when it's used beforehand. when a stream is + * decoupled, it becomes a host stream and link stream. + */ +struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus, + struct snd_pcm_substream *substream, + int type) +{ + struct hdac_ext_stream *hstream = NULL; + struct hdac_stream *stream = NULL; + + switch (type) { + case HDAC_EXT_STREAM_TYPE_COUPLED: + stream = snd_hdac_stream_assign(bus, substream); + if (stream) + hstream = container_of(stream, + struct hdac_ext_stream, hstream); + return hstream; + + case HDAC_EXT_STREAM_TYPE_HOST: + return hdac_ext_host_stream_assign(bus, substream); + + case HDAC_EXT_STREAM_TYPE_LINK: + return hdac_ext_link_stream_assign(bus, substream); + + default: + return NULL; + } +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign); + +/** + * snd_hdac_ext_stream_release - release the assigned stream + * @stream: HD-audio ext core stream to release + * @type: type of stream (coupled, host or link stream) + * + * Release the stream that has been assigned by snd_hdac_ext_stream_assign(). + */ +void snd_hdac_ext_stream_release(struct hdac_ext_stream *stream, int type) +{ + struct hdac_bus *bus = stream->hstream.bus; + + switch (type) { + case HDAC_EXT_STREAM_TYPE_COUPLED: + snd_hdac_stream_release(&stream->hstream); + break; + + case HDAC_EXT_STREAM_TYPE_HOST: + if (stream->decoupled && !stream->link_locked) + snd_hdac_ext_stream_decouple(bus, stream, false); + snd_hdac_stream_release(&stream->hstream); + break; + + case HDAC_EXT_STREAM_TYPE_LINK: + if (stream->decoupled && !stream->hstream.opened) + snd_hdac_ext_stream_decouple(bus, stream, false); + spin_lock_irq(&bus->reg_lock); + stream->link_locked = 0; + stream->link_substream = NULL; + spin_unlock_irq(&bus->reg_lock); + break; + + default: + dev_dbg(bus->dev, "Invalid type %d\n", type); + } + +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release); + +/** + * snd_hdac_ext_stream_spbcap_enable - enable SPIB for a stream + * @bus: HD-audio core bus + * @enable: flag to enable/disable SPIB + * @index: stream index for which SPIB need to be enabled + */ +void snd_hdac_ext_stream_spbcap_enable(struct hdac_bus *bus, + bool enable, int index) +{ + u32 mask = 0; + + if (!bus->spbcap) { + dev_err(bus->dev, "Address of SPB capability is NULL\n"); + return; + } + + mask |= (1 << index); + + if (enable) + snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, mask); + else + snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_spbcap_enable); + +/** + * snd_hdac_ext_stream_set_spib - sets the spib value of a stream + * @bus: HD-audio core bus + * @stream: hdac_ext_stream + * @value: spib value to set + */ +int snd_hdac_ext_stream_set_spib(struct hdac_bus *bus, + struct hdac_ext_stream *stream, u32 value) +{ + + if (!bus->spbcap) { + dev_err(bus->dev, "Address of SPB capability is NULL\n"); + return -EINVAL; + } + + writel(value, stream->spib_addr); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_spib); + +/** + * snd_hdac_ext_stream_get_spbmaxfifo - gets the spib value of a stream + * @bus: HD-audio core bus + * @stream: hdac_ext_stream + * + * Return maxfifo for the stream + */ +int snd_hdac_ext_stream_get_spbmaxfifo(struct hdac_bus *bus, + struct hdac_ext_stream *stream) +{ + + if (!bus->spbcap) { + dev_err(bus->dev, "Address of SPB capability is NULL\n"); + return -EINVAL; + } + + return readl(stream->fifo_addr); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_get_spbmaxfifo); + + +/** + * snd_hdac_ext_stop_streams - stop all stream if running + * @bus: HD-audio core bus + */ +void snd_hdac_ext_stop_streams(struct hdac_bus *bus) +{ + struct hdac_stream *stream; + + if (bus->chip_init) { + list_for_each_entry(stream, &bus->stream_list, list) + snd_hdac_stream_stop(stream); + snd_hdac_bus_stop_chip(bus); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stop_streams); + +/** + * snd_hdac_ext_stream_drsm_enable - enable DMA resume for a stream + * @bus: HD-audio core bus + * @enable: flag to enable/disable DRSM + * @index: stream index for which DRSM need to be enabled + */ +void snd_hdac_ext_stream_drsm_enable(struct hdac_bus *bus, + bool enable, int index) +{ + u32 mask = 0; + + if (!bus->drsmcap) { + dev_err(bus->dev, "Address of DRSM capability is NULL\n"); + return; + } + + mask |= (1 << index); + + if (enable) + snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, mask); + else + snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_drsm_enable); + +/** + * snd_hdac_ext_stream_set_dpibr - sets the dpibr value of a stream + * @bus: HD-audio core bus + * @stream: hdac_ext_stream + * @value: dpib value to set + */ +int snd_hdac_ext_stream_set_dpibr(struct hdac_bus *bus, + struct hdac_ext_stream *stream, u32 value) +{ + + if (!bus->drsmcap) { + dev_err(bus->dev, "Address of DRSM capability is NULL\n"); + return -EINVAL; + } + + writel(value, stream->dpibr_addr); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_dpibr); + +/** + * snd_hdac_ext_stream_set_lpib - sets the lpib value of a stream + * @bus: HD-audio core bus + * @stream: hdac_ext_stream + * @value: lpib value to set + */ +int snd_hdac_ext_stream_set_lpib(struct hdac_ext_stream *stream, u32 value) +{ + snd_hdac_stream_writel(&stream->hstream, SD_LPIB, value); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_lpib); diff --git a/sound/hda/hda_bus_type.c b/sound/hda/hda_bus_type.c new file mode 100644 index 000000000..3060e2aee --- /dev/null +++ b/sound/hda/hda_bus_type.c @@ -0,0 +1,96 @@ +/* + * HD-audio bus + */ +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("HD-audio bus"); +MODULE_LICENSE("GPL"); + +/** + * hdac_get_device_id - gets the hdac device id entry + * @hdev: HD-audio core device + * @drv: HD-audio codec driver + * + * Compares the hdac device vendor_id and revision_id to the hdac_device + * driver id_table and returns the matching device id entry. + */ +const struct hda_device_id * +hdac_get_device_id(struct hdac_device *hdev, struct hdac_driver *drv) +{ + if (drv->id_table) { + const struct hda_device_id *id = drv->id_table; + + while (id->vendor_id) { + if (hdev->vendor_id == id->vendor_id && + (!id->rev_id || id->rev_id == hdev->revision_id)) + return id; + id++; + } + } + + return NULL; +} +EXPORT_SYMBOL_GPL(hdac_get_device_id); + +static int hdac_codec_match(struct hdac_device *dev, struct hdac_driver *drv) +{ + if (hdac_get_device_id(dev, drv)) + return 1; + else + return 0; +} + +static int hda_bus_match(struct device *dev, struct device_driver *drv) +{ + struct hdac_device *hdev = dev_to_hdac_dev(dev); + struct hdac_driver *hdrv = drv_to_hdac_driver(drv); + + if (hdev->type != hdrv->type) + return 0; + + /* + * if driver provided a match function use that otherwise we will + * use hdac_codec_match function + */ + if (hdrv->match) + return hdrv->match(hdev, hdrv); + else + return hdac_codec_match(hdev, hdrv); + return 1; +} + +static int hda_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + char modalias[32]; + + snd_hdac_codec_modalias(dev_to_hdac_dev(dev), modalias, + sizeof(modalias)); + if (add_uevent_var(env, "MODALIAS=%s", modalias)) + return -ENOMEM; + return 0; +} + +struct bus_type snd_hda_bus_type = { + .name = "hdaudio", + .match = hda_bus_match, + .uevent = hda_uevent, +}; +EXPORT_SYMBOL_GPL(snd_hda_bus_type); + +static int __init hda_bus_init(void) +{ + return bus_register(&snd_hda_bus_type); +} + +static void __exit hda_bus_exit(void) +{ + bus_unregister(&snd_hda_bus_type); +} + +subsys_initcall(hda_bus_init); +module_exit(hda_bus_exit); diff --git a/sound/hda/hdac_bus.c b/sound/hda/hdac_bus.c new file mode 100644 index 000000000..ab9236e4c --- /dev/null +++ b/sound/hda/hdac_bus.c @@ -0,0 +1,221 @@ +/* + * HD-audio core bus driver + */ + +#include +#include +#include +#include +#include +#include "trace.h" + +static void process_unsol_events(struct work_struct *work); + +static const struct hdac_bus_ops default_ops = { + .command = snd_hdac_bus_send_cmd, + .get_response = snd_hdac_bus_get_response, +}; + +/** + * snd_hdac_bus_init - initialize a HD-audio bas bus + * @bus: the pointer to bus object + * @ops: bus verb operators + * @io_ops: lowlevel I/O operators + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev, + const struct hdac_bus_ops *ops, + const struct hdac_io_ops *io_ops) +{ + memset(bus, 0, sizeof(*bus)); + bus->dev = dev; + if (ops) + bus->ops = ops; + else + bus->ops = &default_ops; + bus->io_ops = io_ops; + INIT_LIST_HEAD(&bus->stream_list); + INIT_LIST_HEAD(&bus->codec_list); + INIT_WORK(&bus->unsol_work, process_unsol_events); + spin_lock_init(&bus->reg_lock); + mutex_init(&bus->cmd_mutex); + bus->irq = -1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_init); + +/** + * snd_hdac_bus_exit - clean up a HD-audio bas bus + * @bus: the pointer to bus object + */ +void snd_hdac_bus_exit(struct hdac_bus *bus) +{ + WARN_ON(!list_empty(&bus->stream_list)); + WARN_ON(!list_empty(&bus->codec_list)); + cancel_work_sync(&bus->unsol_work); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_exit); + +/** + * snd_hdac_bus_exec_verb - execute a HD-audio verb on the given bus + * @bus: bus object + * @cmd: HD-audio encoded verb + * @res: pointer to store the response, NULL if performing asynchronously + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr, + unsigned int cmd, unsigned int *res) +{ + int err; + + mutex_lock(&bus->cmd_mutex); + err = snd_hdac_bus_exec_verb_unlocked(bus, addr, cmd, res); + mutex_unlock(&bus->cmd_mutex); + return err; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb); + +/** + * snd_hdac_bus_exec_verb_unlocked - unlocked version + * @bus: bus object + * @cmd: HD-audio encoded verb + * @res: pointer to store the response, NULL if performing asynchronously + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr, + unsigned int cmd, unsigned int *res) +{ + unsigned int tmp; + int err; + + if (cmd == ~0) + return -EINVAL; + + if (res) + *res = -1; + else if (bus->sync_write) + res = &tmp; + for (;;) { + trace_hda_send_cmd(bus, cmd); + err = bus->ops->command(bus, cmd); + if (err != -EAGAIN) + break; + /* process pending verbs */ + err = bus->ops->get_response(bus, addr, &tmp); + if (err) + break; + } + if (!err && res) { + err = bus->ops->get_response(bus, addr, res); + trace_hda_get_response(bus, addr, *res); + } + return err; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb_unlocked); + +/** + * snd_hdac_bus_queue_event - add an unsolicited event to queue + * @bus: the BUS + * @res: unsolicited event (lower 32bit of RIRB entry) + * @res_ex: codec addr and flags (upper 32bit or RIRB entry) + * + * Adds the given event to the queue. The events are processed in + * the workqueue asynchronously. Call this function in the interrupt + * hanlder when RIRB receives an unsolicited event. + */ +void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex) +{ + unsigned int wp; + + if (!bus) + return; + + trace_hda_unsol_event(bus, res, res_ex); + wp = (bus->unsol_wp + 1) % HDA_UNSOL_QUEUE_SIZE; + bus->unsol_wp = wp; + + wp <<= 1; + bus->unsol_queue[wp] = res; + bus->unsol_queue[wp + 1] = res_ex; + + schedule_work(&bus->unsol_work); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_queue_event); + +/* + * process queued unsolicited events + */ +static void process_unsol_events(struct work_struct *work) +{ + struct hdac_bus *bus = container_of(work, struct hdac_bus, unsol_work); + struct hdac_device *codec; + struct hdac_driver *drv; + unsigned int rp, caddr, res; + + spin_lock_irq(&bus->reg_lock); + while (bus->unsol_rp != bus->unsol_wp) { + rp = (bus->unsol_rp + 1) % HDA_UNSOL_QUEUE_SIZE; + bus->unsol_rp = rp; + rp <<= 1; + res = bus->unsol_queue[rp]; + caddr = bus->unsol_queue[rp + 1]; + if (!(caddr & (1 << 4))) /* no unsolicited event? */ + continue; + codec = bus->caddr_tbl[caddr & 0x0f]; + if (!codec || !codec->dev.driver) + continue; + spin_unlock_irq(&bus->reg_lock); + drv = drv_to_hdac_driver(codec->dev.driver); + if (drv->unsol_event) + drv->unsol_event(codec, res); + spin_lock_irq(&bus->reg_lock); + } + spin_unlock_irq(&bus->reg_lock); +} + +/** + * snd_hdac_bus_add_device - Add a codec to bus + * @bus: HDA core bus + * @codec: HDA core device to add + * + * Adds the given codec to the list in the bus. The caddr_tbl array + * and codec_powered bits are updated, as well. + * Returns zero if success, or a negative error code. + */ +int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec) +{ + if (bus->caddr_tbl[codec->addr]) { + dev_err(bus->dev, "address 0x%x is already occupied\n", + codec->addr); + return -EBUSY; + } + + list_add_tail(&codec->list, &bus->codec_list); + bus->caddr_tbl[codec->addr] = codec; + set_bit(codec->addr, &bus->codec_powered); + bus->num_codecs++; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_add_device); + +/** + * snd_hdac_bus_remove_device - Remove a codec from bus + * @bus: HDA core bus + * @codec: HDA core device to remove + */ +void snd_hdac_bus_remove_device(struct hdac_bus *bus, + struct hdac_device *codec) +{ + WARN_ON(bus != codec->bus); + if (list_empty(&codec->list)) + return; + list_del_init(&codec->list); + bus->caddr_tbl[codec->addr] = NULL; + clear_bit(codec->addr, &bus->codec_powered); + bus->num_codecs--; + flush_work(&bus->unsol_work); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_remove_device); diff --git a/sound/hda/hdac_component.c b/sound/hda/hdac_component.c new file mode 100644 index 000000000..6e46a9c73 --- /dev/null +++ b/sound/hda/hdac_component.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0 +// hdac_component.c - routines for sync between HD-A core and DRM driver + +#include +#include +#include +#include +#include +#include +#include +#include + +static void hdac_acomp_release(struct device *dev, void *res) +{ +} + +static struct drm_audio_component *hdac_get_acomp(struct device *dev) +{ + return devres_find(dev, hdac_acomp_release, NULL, NULL); +} + +/** + * snd_hdac_set_codec_wakeup - Enable / disable HDMI/DP codec wakeup + * @bus: HDA core bus + * @enable: enable or disable the wakeup + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with graphics driver. + * + * This function should be called during the chip reset, also called at + * resume for updating STATESTS register read. + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable) +{ + struct drm_audio_component *acomp = bus->audio_component; + + if (!acomp || !acomp->ops) + return -ENODEV; + + if (!acomp->ops->codec_wake_override) + return 0; + + dev_dbg(bus->dev, "%s codec wakeup\n", + enable ? "enable" : "disable"); + + acomp->ops->codec_wake_override(acomp->dev, enable); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_set_codec_wakeup); + +/** + * snd_hdac_display_power - Power up / down the power refcount + * @bus: HDA core bus + * @enable: power up or down + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with graphics driver. + * + * This function manages a refcount and calls the get_power() and + * put_power() ops accordingly, toggling the codec wakeup, too. + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_display_power(struct hdac_bus *bus, bool enable) +{ + struct drm_audio_component *acomp = bus->audio_component; + + if (!acomp || !acomp->ops) + return -ENODEV; + + dev_dbg(bus->dev, "display power %s\n", + enable ? "enable" : "disable"); + + if (enable) { + if (!bus->drm_power_refcount++) { + if (acomp->ops->get_power) + acomp->ops->get_power(acomp->dev); + snd_hdac_set_codec_wakeup(bus, true); + snd_hdac_set_codec_wakeup(bus, false); + } + } else { + WARN_ON(!bus->drm_power_refcount); + if (!--bus->drm_power_refcount) + if (acomp->ops->put_power) + acomp->ops->put_power(acomp->dev); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_display_power); + +/** + * snd_hdac_sync_audio_rate - Set N/CTS based on the sample rate + * @codec: HDA codec + * @nid: the pin widget NID + * @dev_id: device identifier + * @rate: the sample rate to set + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with graphics driver. + * + * This function sets N/CTS value based on the given sample rate. + * Returns zero for success, or a negative error code. + */ +int snd_hdac_sync_audio_rate(struct hdac_device *codec, hda_nid_t nid, + int dev_id, int rate) +{ + struct hdac_bus *bus = codec->bus; + struct drm_audio_component *acomp = bus->audio_component; + int port, pipe; + + if (!acomp || !acomp->ops || !acomp->ops->sync_audio_rate) + return -ENODEV; + port = nid; + if (acomp->audio_ops && acomp->audio_ops->pin2port) { + port = acomp->audio_ops->pin2port(codec, nid); + if (port < 0) + return -EINVAL; + } + pipe = dev_id; + return acomp->ops->sync_audio_rate(acomp->dev, port, pipe, rate); +} +EXPORT_SYMBOL_GPL(snd_hdac_sync_audio_rate); + +/** + * snd_hdac_acomp_get_eld - Get the audio state and ELD via component + * @codec: HDA codec + * @nid: the pin widget NID + * @dev_id: device identifier + * @audio_enabled: the pointer to store the current audio state + * @buffer: the buffer pointer to store ELD bytes + * @max_bytes: the max bytes to be stored on @buffer + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with graphics driver. + * + * This function queries the current state of the audio on the given + * digital port and fetches the ELD bytes onto the given buffer. + * It returns the number of bytes for the total ELD data, zero for + * invalid ELD, or a negative error code. + * + * The return size is the total bytes required for the whole ELD bytes, + * thus it may be over @max_bytes. If it's over @max_bytes, it implies + * that only a part of ELD bytes have been fetched. + */ +int snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid, int dev_id, + bool *audio_enabled, char *buffer, int max_bytes) +{ + struct hdac_bus *bus = codec->bus; + struct drm_audio_component *acomp = bus->audio_component; + int port, pipe; + + if (!acomp || !acomp->ops || !acomp->ops->get_eld) + return -ENODEV; + + port = nid; + if (acomp->audio_ops && acomp->audio_ops->pin2port) { + port = acomp->audio_ops->pin2port(codec, nid); + if (port < 0) + return -EINVAL; + } + pipe = dev_id; + return acomp->ops->get_eld(acomp->dev, port, pipe, audio_enabled, + buffer, max_bytes); +} +EXPORT_SYMBOL_GPL(snd_hdac_acomp_get_eld); + +static int hdac_component_master_bind(struct device *dev) +{ + struct drm_audio_component *acomp = hdac_get_acomp(dev); + int ret; + + if (WARN_ON(!acomp)) + return -EINVAL; + + ret = component_bind_all(dev, acomp); + if (ret < 0) + return ret; + + if (WARN_ON(!(acomp->dev && acomp->ops))) { + ret = -EINVAL; + goto out_unbind; + } + + /* pin the module to avoid dynamic unbinding, but only if given */ + if (!try_module_get(acomp->ops->owner)) { + ret = -ENODEV; + goto out_unbind; + } + + if (acomp->audio_ops && acomp->audio_ops->master_bind) { + ret = acomp->audio_ops->master_bind(dev, acomp); + if (ret < 0) + goto module_put; + } + + return 0; + + module_put: + module_put(acomp->ops->owner); +out_unbind: + component_unbind_all(dev, acomp); + + return ret; +} + +static void hdac_component_master_unbind(struct device *dev) +{ + struct drm_audio_component *acomp = hdac_get_acomp(dev); + + if (acomp->audio_ops && acomp->audio_ops->master_unbind) + acomp->audio_ops->master_unbind(dev, acomp); + module_put(acomp->ops->owner); + component_unbind_all(dev, acomp); + WARN_ON(acomp->ops || acomp->dev); +} + +static const struct component_master_ops hdac_component_master_ops = { + .bind = hdac_component_master_bind, + .unbind = hdac_component_master_unbind, +}; + +/** + * snd_hdac_acomp_register_notifier - Register audio component ops + * @bus: HDA core bus + * @aops: audio component ops + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with graphics driver. + * + * This function sets the given ops to be called by the graphics driver. + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_acomp_register_notifier(struct hdac_bus *bus, + const struct drm_audio_component_audio_ops *aops) +{ + if (!bus->audio_component) + return -ENODEV; + + bus->audio_component->audio_ops = aops; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_acomp_register_notifier); + +/** + * snd_hdac_acomp_init - Initialize audio component + * @bus: HDA core bus + * @match_master: match function for finding components + * @extra_size: Extra bytes to allocate + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with graphics driver. + * + * This function initializes and sets up the audio component to communicate + * with graphics driver. + * + * Unlike snd_hdac_i915_init(), this function doesn't synchronize with the + * binding with the DRM component. Each caller needs to sync via master_bind + * audio_ops. + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_acomp_init(struct hdac_bus *bus, + const struct drm_audio_component_audio_ops *aops, + int (*match_master)(struct device *, void *), + size_t extra_size) +{ + struct component_match *match = NULL; + struct device *dev = bus->dev; + struct drm_audio_component *acomp; + int ret; + + if (WARN_ON(hdac_get_acomp(dev))) + return -EBUSY; + + acomp = devres_alloc(hdac_acomp_release, sizeof(*acomp) + extra_size, + GFP_KERNEL); + if (!acomp) + return -ENOMEM; + acomp->audio_ops = aops; + bus->audio_component = acomp; + devres_add(dev, acomp); + + component_match_add(dev, &match, match_master, bus); + ret = component_master_add_with_match(dev, &hdac_component_master_ops, + match); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + bus->audio_component = NULL; + devres_destroy(dev, hdac_acomp_release, NULL, NULL); + dev_info(dev, "failed to add audio component master (%d)\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_hdac_acomp_init); + +/** + * snd_hdac_acomp_exit - Finalize audio component + * @bus: HDA core bus + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with graphics driver. + * + * This function releases the audio component that has been used. + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_acomp_exit(struct hdac_bus *bus) +{ + struct device *dev = bus->dev; + struct drm_audio_component *acomp = bus->audio_component; + + if (!acomp) + return 0; + + WARN_ON(bus->drm_power_refcount); + if (bus->drm_power_refcount > 0 && acomp->ops) + acomp->ops->put_power(acomp->dev); + + component_master_del(dev, &hdac_component_master_ops); + + bus->audio_component = NULL; + devres_destroy(dev, hdac_acomp_release, NULL, NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_acomp_exit); diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c new file mode 100644 index 000000000..a65e8c0c6 --- /dev/null +++ b/sound/hda/hdac_controller.c @@ -0,0 +1,620 @@ +/* + * HD-audio controller helpers + */ + +#include +#include +#include +#include +#include +#include + +/* clear CORB read pointer properly */ +static void azx_clear_corbrp(struct hdac_bus *bus) +{ + int timeout; + + for (timeout = 1000; timeout > 0; timeout--) { + if (snd_hdac_chip_readw(bus, CORBRP) & AZX_CORBRP_RST) + break; + udelay(1); + } + if (timeout <= 0) + dev_err(bus->dev, "CORB reset timeout#1, CORBRP = %d\n", + snd_hdac_chip_readw(bus, CORBRP)); + + snd_hdac_chip_writew(bus, CORBRP, 0); + for (timeout = 1000; timeout > 0; timeout--) { + if (snd_hdac_chip_readw(bus, CORBRP) == 0) + break; + udelay(1); + } + if (timeout <= 0) + dev_err(bus->dev, "CORB reset timeout#2, CORBRP = %d\n", + snd_hdac_chip_readw(bus, CORBRP)); +} + +/** + * snd_hdac_bus_init_cmd_io - set up CORB/RIRB buffers + * @bus: HD-audio core bus + */ +void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus) +{ + WARN_ON_ONCE(!bus->rb.area); + + spin_lock_irq(&bus->reg_lock); + /* CORB set up */ + bus->corb.addr = bus->rb.addr; + bus->corb.buf = (__le32 *)bus->rb.area; + snd_hdac_chip_writel(bus, CORBLBASE, (u32)bus->corb.addr); + snd_hdac_chip_writel(bus, CORBUBASE, upper_32_bits(bus->corb.addr)); + + /* set the corb size to 256 entries (ULI requires explicitly) */ + snd_hdac_chip_writeb(bus, CORBSIZE, 0x02); + /* set the corb write pointer to 0 */ + snd_hdac_chip_writew(bus, CORBWP, 0); + + /* reset the corb hw read pointer */ + snd_hdac_chip_writew(bus, CORBRP, AZX_CORBRP_RST); + if (!bus->corbrp_self_clear) + azx_clear_corbrp(bus); + + /* enable corb dma */ + snd_hdac_chip_writeb(bus, CORBCTL, AZX_CORBCTL_RUN); + + /* RIRB set up */ + bus->rirb.addr = bus->rb.addr + 2048; + bus->rirb.buf = (__le32 *)(bus->rb.area + 2048); + bus->rirb.wp = bus->rirb.rp = 0; + memset(bus->rirb.cmds, 0, sizeof(bus->rirb.cmds)); + snd_hdac_chip_writel(bus, RIRBLBASE, (u32)bus->rirb.addr); + snd_hdac_chip_writel(bus, RIRBUBASE, upper_32_bits(bus->rirb.addr)); + + /* set the rirb size to 256 entries (ULI requires explicitly) */ + snd_hdac_chip_writeb(bus, RIRBSIZE, 0x02); + /* reset the rirb hw write pointer */ + snd_hdac_chip_writew(bus, RIRBWP, AZX_RIRBWP_RST); + /* set N=1, get RIRB response interrupt for new entry */ + snd_hdac_chip_writew(bus, RINTCNT, 1); + /* enable rirb dma and response irq */ + snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN); + spin_unlock_irq(&bus->reg_lock); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_init_cmd_io); + +/* wait for cmd dmas till they are stopped */ +static void hdac_wait_for_cmd_dmas(struct hdac_bus *bus) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(100); + while ((snd_hdac_chip_readb(bus, RIRBCTL) & AZX_RBCTL_DMA_EN) + && time_before(jiffies, timeout)) + udelay(10); + + timeout = jiffies + msecs_to_jiffies(100); + while ((snd_hdac_chip_readb(bus, CORBCTL) & AZX_CORBCTL_RUN) + && time_before(jiffies, timeout)) + udelay(10); +} + +/** + * snd_hdac_bus_stop_cmd_io - clean up CORB/RIRB buffers + * @bus: HD-audio core bus + */ +void snd_hdac_bus_stop_cmd_io(struct hdac_bus *bus) +{ + spin_lock_irq(&bus->reg_lock); + /* disable ringbuffer DMAs */ + snd_hdac_chip_writeb(bus, RIRBCTL, 0); + snd_hdac_chip_writeb(bus, CORBCTL, 0); + spin_unlock_irq(&bus->reg_lock); + + hdac_wait_for_cmd_dmas(bus); + + spin_lock_irq(&bus->reg_lock); + /* disable unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, 0); + spin_unlock_irq(&bus->reg_lock); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_stop_cmd_io); + +static unsigned int azx_command_addr(u32 cmd) +{ + unsigned int addr = cmd >> 28; + + if (snd_BUG_ON(addr >= HDA_MAX_CODECS)) + addr = 0; + return addr; +} + +/** + * snd_hdac_bus_send_cmd - send a command verb via CORB + * @bus: HD-audio core bus + * @val: encoded verb value to send + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val) +{ + unsigned int addr = azx_command_addr(val); + unsigned int wp, rp; + + spin_lock_irq(&bus->reg_lock); + + bus->last_cmd[azx_command_addr(val)] = val; + + /* add command to corb */ + wp = snd_hdac_chip_readw(bus, CORBWP); + if (wp == 0xffff) { + /* something wrong, controller likely turned to D3 */ + spin_unlock_irq(&bus->reg_lock); + return -EIO; + } + wp++; + wp %= AZX_MAX_CORB_ENTRIES; + + rp = snd_hdac_chip_readw(bus, CORBRP); + if (wp == rp) { + /* oops, it's full */ + spin_unlock_irq(&bus->reg_lock); + return -EAGAIN; + } + + bus->rirb.cmds[addr]++; + bus->corb.buf[wp] = cpu_to_le32(val); + snd_hdac_chip_writew(bus, CORBWP, wp); + + spin_unlock_irq(&bus->reg_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_send_cmd); + +#define AZX_RIRB_EX_UNSOL_EV (1<<4) + +/** + * snd_hdac_bus_update_rirb - retrieve RIRB entries + * @bus: HD-audio core bus + * + * Usually called from interrupt handler. + */ +void snd_hdac_bus_update_rirb(struct hdac_bus *bus) +{ + unsigned int rp, wp; + unsigned int addr; + u32 res, res_ex; + + wp = snd_hdac_chip_readw(bus, RIRBWP); + if (wp == 0xffff) { + /* something wrong, controller likely turned to D3 */ + return; + } + + if (wp == bus->rirb.wp) + return; + bus->rirb.wp = wp; + + while (bus->rirb.rp != wp) { + bus->rirb.rp++; + bus->rirb.rp %= AZX_MAX_RIRB_ENTRIES; + + rp = bus->rirb.rp << 1; /* an RIRB entry is 8-bytes */ + res_ex = le32_to_cpu(bus->rirb.buf[rp + 1]); + res = le32_to_cpu(bus->rirb.buf[rp]); + addr = res_ex & 0xf; + if (addr >= HDA_MAX_CODECS) { + dev_err(bus->dev, + "spurious response %#x:%#x, rp = %d, wp = %d", + res, res_ex, bus->rirb.rp, wp); + snd_BUG(); + } else if (res_ex & AZX_RIRB_EX_UNSOL_EV) + snd_hdac_bus_queue_event(bus, res, res_ex); + else if (bus->rirb.cmds[addr]) { + bus->rirb.res[addr] = res; + bus->rirb.cmds[addr]--; + } else { + dev_err_ratelimited(bus->dev, + "spurious response %#x:%#x, last cmd=%#08x\n", + res, res_ex, bus->last_cmd[addr]); + } + } +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_update_rirb); + +/** + * snd_hdac_bus_get_response - receive a response via RIRB + * @bus: HD-audio core bus + * @addr: codec address + * @res: pointer to store the value, NULL when not needed + * + * Returns zero if a value is read, or a negative error code. + */ +int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr, + unsigned int *res) +{ + unsigned long timeout; + unsigned long loopcounter; + + timeout = jiffies + msecs_to_jiffies(1000); + + for (loopcounter = 0;; loopcounter++) { + spin_lock_irq(&bus->reg_lock); + if (!bus->rirb.cmds[addr]) { + if (res) + *res = bus->rirb.res[addr]; /* the last value */ + spin_unlock_irq(&bus->reg_lock); + return 0; + } + spin_unlock_irq(&bus->reg_lock); + if (time_after(jiffies, timeout)) + break; + if (loopcounter > 3000) + msleep(2); /* temporary workaround */ + else { + udelay(10); + cond_resched(); + } + } + + return -EIO; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response); + +#define HDAC_MAX_CAPS 10 +/** + * snd_hdac_bus_parse_capabilities - parse capability structure + * @bus: the pointer to bus object + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) +{ + unsigned int cur_cap; + unsigned int offset; + unsigned int counter = 0; + + offset = snd_hdac_chip_readw(bus, LLCH); + + /* Lets walk the linked capabilities list */ + do { + cur_cap = _snd_hdac_chip_readl(bus, offset); + + dev_dbg(bus->dev, "Capability version: 0x%x\n", + (cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF); + + dev_dbg(bus->dev, "HDA capability ID: 0x%x\n", + (cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF); + + if (cur_cap == -1) { + dev_dbg(bus->dev, "Invalid capability reg read\n"); + break; + } + + switch ((cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF) { + case AZX_ML_CAP_ID: + dev_dbg(bus->dev, "Found ML capability\n"); + bus->mlcap = bus->remap_addr + offset; + break; + + case AZX_GTS_CAP_ID: + dev_dbg(bus->dev, "Found GTS capability offset=%x\n", offset); + bus->gtscap = bus->remap_addr + offset; + break; + + case AZX_PP_CAP_ID: + /* PP capability found, the Audio DSP is present */ + dev_dbg(bus->dev, "Found PP capability offset=%x\n", offset); + bus->ppcap = bus->remap_addr + offset; + break; + + case AZX_SPB_CAP_ID: + /* SPIB capability found, handler function */ + dev_dbg(bus->dev, "Found SPB capability\n"); + bus->spbcap = bus->remap_addr + offset; + break; + + case AZX_DRSM_CAP_ID: + /* DMA resume capability found, handler function */ + dev_dbg(bus->dev, "Found DRSM capability\n"); + bus->drsmcap = bus->remap_addr + offset; + break; + + default: + dev_err(bus->dev, "Unknown capability %d\n", cur_cap); + cur_cap = 0; + break; + } + + counter++; + + if (counter > HDAC_MAX_CAPS) { + dev_err(bus->dev, "We exceeded HDAC capabilities!!!\n"); + break; + } + + /* read the offset of next capability */ + offset = cur_cap & AZX_CAP_HDR_NXT_PTR_MASK; + + } while (offset); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_parse_capabilities); + +/* + * Lowlevel interface + */ + +/** + * snd_hdac_bus_enter_link_reset - enter link reset + * @bus: HD-audio core bus + * + * Enter to the link reset state. + */ +void snd_hdac_bus_enter_link_reset(struct hdac_bus *bus) +{ + unsigned long timeout; + + /* reset controller */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, 0); + + timeout = jiffies + msecs_to_jiffies(100); + while ((snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET) && + time_before(jiffies, timeout)) + usleep_range(500, 1000); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_enter_link_reset); + +/** + * snd_hdac_bus_exit_link_reset - exit link reset + * @bus: HD-audio core bus + * + * Exit from the link reset state. + */ +void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus) +{ + unsigned long timeout; + + snd_hdac_chip_updateb(bus, GCTL, 0, AZX_GCTL_RESET); + + timeout = jiffies + msecs_to_jiffies(100); + while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout)) + usleep_range(500, 1000); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_exit_link_reset); + +/* reset codec link */ +int snd_hdac_bus_reset_link(struct hdac_bus *bus, bool full_reset) +{ + if (!full_reset) + goto skip_reset; + + /* clear STATESTS if not in reset */ + if (snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET) + snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK); + + /* reset controller */ + snd_hdac_bus_enter_link_reset(bus); + + /* delay for >= 100us for codec PLL to settle per spec + * Rev 0.9 section 5.5.1 + */ + usleep_range(500, 1000); + + /* Bring controller out of reset */ + snd_hdac_bus_exit_link_reset(bus); + + /* Brent Chartrand said to wait >= 540us for codecs to initialize */ + usleep_range(1000, 1200); + + skip_reset: + /* check to see if controller is ready */ + if (!snd_hdac_chip_readb(bus, GCTL)) { + dev_dbg(bus->dev, "controller not ready!\n"); + return -EBUSY; + } + + /* Accept unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, 0, AZX_GCTL_UNSOL); + + /* detect codecs */ + if (!bus->codec_mask) { + bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS); + dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_reset_link); + +/* enable interrupts */ +static void azx_int_enable(struct hdac_bus *bus) +{ + /* enable controller CIE and GIE */ + snd_hdac_chip_updatel(bus, INTCTL, 0, AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN); +} + +/* disable interrupts */ +static void azx_int_disable(struct hdac_bus *bus) +{ + struct hdac_stream *azx_dev; + + /* disable interrupts in stream descriptor */ + list_for_each_entry(azx_dev, &bus->stream_list, list) + snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); + + /* disable SIE for all streams */ + snd_hdac_chip_writeb(bus, INTCTL, 0); + + /* disable controller CIE and GIE */ + snd_hdac_chip_updatel(bus, INTCTL, AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN, 0); +} + +/* clear interrupts */ +static void azx_int_clear(struct hdac_bus *bus) +{ + struct hdac_stream *azx_dev; + + /* clear stream status */ + list_for_each_entry(azx_dev, &bus->stream_list, list) + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); + + /* clear STATESTS */ + snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK); + + /* clear rirb status */ + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); + + /* clear int status */ + snd_hdac_chip_writel(bus, INTSTS, AZX_INT_CTRL_EN | AZX_INT_ALL_STREAM); +} + +/** + * snd_hdac_bus_init_chip - reset and start the controller registers + * @bus: HD-audio core bus + * @full_reset: Do full reset + */ +bool snd_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) +{ + if (bus->chip_init) + return false; + + /* reset controller */ + snd_hdac_bus_reset_link(bus, full_reset); + + /* clear interrupts */ + azx_int_clear(bus); + + /* initialize the codec command I/O */ + snd_hdac_bus_init_cmd_io(bus); + + /* enable interrupts after CORB/RIRB buffers are initialized above */ + azx_int_enable(bus); + + /* program the position buffer */ + if (bus->use_posbuf && bus->posbuf.addr) { + snd_hdac_chip_writel(bus, DPLBASE, (u32)bus->posbuf.addr); + snd_hdac_chip_writel(bus, DPUBASE, upper_32_bits(bus->posbuf.addr)); + } + + bus->chip_init = true; + return true; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_init_chip); + +/** + * snd_hdac_bus_stop_chip - disable the whole IRQ and I/Os + * @bus: HD-audio core bus + */ +void snd_hdac_bus_stop_chip(struct hdac_bus *bus) +{ + if (!bus->chip_init) + return; + + /* disable interrupts */ + azx_int_disable(bus); + azx_int_clear(bus); + + /* disable CORB/RIRB */ + snd_hdac_bus_stop_cmd_io(bus); + + /* disable position buffer */ + if (bus->posbuf.addr) { + snd_hdac_chip_writel(bus, DPLBASE, 0); + snd_hdac_chip_writel(bus, DPUBASE, 0); + } + + bus->chip_init = false; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_stop_chip); + +/** + * snd_hdac_bus_handle_stream_irq - interrupt handler for streams + * @bus: HD-audio core bus + * @status: INTSTS register value + * @ask: callback to be called for woken streams + * + * Returns the bits of handled streams, or zero if no stream is handled. + */ +int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status, + void (*ack)(struct hdac_bus *, + struct hdac_stream *)) +{ + struct hdac_stream *azx_dev; + u8 sd_status; + int handled = 0; + + list_for_each_entry(azx_dev, &bus->stream_list, list) { + if (status & azx_dev->sd_int_sta_mask) { + sd_status = snd_hdac_stream_readb(azx_dev, SD_STS); + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); + handled |= 1 << azx_dev->index; + if (!azx_dev->substream || !azx_dev->running || + !(sd_status & SD_INT_COMPLETE)) + continue; + if (ack) + ack(bus, azx_dev); + } + } + return handled; +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_handle_stream_irq); + +/** + * snd_hdac_bus_alloc_stream_pages - allocate BDL and other buffers + * @bus: HD-audio core bus + * + * Call this after assigning the all streams. + * Returns zero for success, or a negative error code. + */ +int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) +{ + struct hdac_stream *s; + int num_streams = 0; + int err; + + list_for_each_entry(s, &bus->stream_list, list) { + /* allocate memory for the BDL for each stream */ + err = bus->io_ops->dma_alloc_pages(bus, SNDRV_DMA_TYPE_DEV, + BDL_SIZE, &s->bdl); + num_streams++; + if (err < 0) + return -ENOMEM; + } + + if (WARN_ON(!num_streams)) + return -EINVAL; + /* allocate memory for the position buffer */ + err = bus->io_ops->dma_alloc_pages(bus, SNDRV_DMA_TYPE_DEV, + num_streams * 8, &bus->posbuf); + if (err < 0) + return -ENOMEM; + list_for_each_entry(s, &bus->stream_list, list) + s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8); + + /* single page (at least 4096 bytes) must suffice for both ringbuffes */ + return bus->io_ops->dma_alloc_pages(bus, SNDRV_DMA_TYPE_DEV, + PAGE_SIZE, &bus->rb); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages); + +/** + * snd_hdac_bus_free_stream_pages - release BDL and other buffers + * @bus: HD-audio core bus + */ +void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus) +{ + struct hdac_stream *s; + + list_for_each_entry(s, &bus->stream_list, list) { + if (s->bdl.area) + bus->io_ops->dma_free_pages(bus, &s->bdl); + } + + if (bus->rb.area) + bus->io_ops->dma_free_pages(bus, &bus->rb); + if (bus->posbuf.area) + bus->io_ops->dma_free_pages(bus, &bus->posbuf); +} +EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages); diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c new file mode 100644 index 000000000..58b53a4bc --- /dev/null +++ b/sound/hda/hdac_device.c @@ -0,0 +1,1103 @@ +/* + * HD-audio codec core device + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "local.h" + +static void setup_fg_nodes(struct hdac_device *codec); +static int get_codec_vendor_name(struct hdac_device *codec); + +static void default_release(struct device *dev) +{ + snd_hdac_device_exit(container_of(dev, struct hdac_device, dev)); +} + +/** + * snd_hdac_device_init - initialize the HD-audio codec base device + * @codec: device to initialize + * @bus: but to attach + * @name: device name string + * @addr: codec address + * + * Returns zero for success or a negative error code. + * + * This function increments the runtime PM counter and marks it active. + * The caller needs to turn it off appropriately later. + * + * The caller needs to set the device's release op properly by itself. + */ +int snd_hdac_device_init(struct hdac_device *codec, struct hdac_bus *bus, + const char *name, unsigned int addr) +{ + struct device *dev; + hda_nid_t fg; + int err; + + dev = &codec->dev; + device_initialize(dev); + dev->parent = bus->dev; + dev->bus = &snd_hda_bus_type; + dev->release = default_release; + dev->groups = hdac_dev_attr_groups; + dev_set_name(dev, "%s", name); + device_enable_async_suspend(dev); + + codec->bus = bus; + codec->addr = addr; + codec->type = HDA_DEV_CORE; + pm_runtime_set_active(&codec->dev); + pm_runtime_get_noresume(&codec->dev); + atomic_set(&codec->in_pm, 0); + + err = snd_hdac_bus_add_device(bus, codec); + if (err < 0) + goto error; + + /* fill parameters */ + codec->vendor_id = snd_hdac_read_parm(codec, AC_NODE_ROOT, + AC_PAR_VENDOR_ID); + if (codec->vendor_id == -1) { + /* read again, hopefully the access method was corrected + * in the last read... + */ + codec->vendor_id = snd_hdac_read_parm(codec, AC_NODE_ROOT, + AC_PAR_VENDOR_ID); + } + + codec->subsystem_id = snd_hdac_read_parm(codec, AC_NODE_ROOT, + AC_PAR_SUBSYSTEM_ID); + codec->revision_id = snd_hdac_read_parm(codec, AC_NODE_ROOT, + AC_PAR_REV_ID); + + setup_fg_nodes(codec); + if (!codec->afg && !codec->mfg) { + dev_err(dev, "no AFG or MFG node found\n"); + err = -ENODEV; + goto error; + } + + fg = codec->afg ? codec->afg : codec->mfg; + + err = snd_hdac_refresh_widgets(codec, false); + if (err < 0) + goto error; + + codec->power_caps = snd_hdac_read_parm(codec, fg, AC_PAR_POWER_STATE); + /* reread ssid if not set by parameter */ + if (codec->subsystem_id == -1 || codec->subsystem_id == 0) + snd_hdac_read(codec, fg, AC_VERB_GET_SUBSYSTEM_ID, 0, + &codec->subsystem_id); + + err = get_codec_vendor_name(codec); + if (err < 0) + goto error; + + codec->chip_name = kasprintf(GFP_KERNEL, "ID %x", + codec->vendor_id & 0xffff); + if (!codec->chip_name) { + err = -ENOMEM; + goto error; + } + + return 0; + + error: + put_device(&codec->dev); + return err; +} +EXPORT_SYMBOL_GPL(snd_hdac_device_init); + +/** + * snd_hdac_device_exit - clean up the HD-audio codec base device + * @codec: device to clean up + */ +void snd_hdac_device_exit(struct hdac_device *codec) +{ + pm_runtime_put_noidle(&codec->dev); + /* keep balance of runtime PM child_count in parent device */ + pm_runtime_set_suspended(&codec->dev); + snd_hdac_bus_remove_device(codec->bus, codec); + kfree(codec->vendor_name); + kfree(codec->chip_name); +} +EXPORT_SYMBOL_GPL(snd_hdac_device_exit); + +/** + * snd_hdac_device_register - register the hd-audio codec base device + * codec: the device to register + */ +int snd_hdac_device_register(struct hdac_device *codec) +{ + int err; + + err = device_add(&codec->dev); + if (err < 0) + return err; + err = hda_widget_sysfs_init(codec); + if (err < 0) { + device_del(&codec->dev); + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_device_register); + +/** + * snd_hdac_device_unregister - unregister the hd-audio codec base device + * codec: the device to unregister + */ +void snd_hdac_device_unregister(struct hdac_device *codec) +{ + if (device_is_registered(&codec->dev)) { + hda_widget_sysfs_exit(codec); + device_del(&codec->dev); + snd_hdac_bus_remove_device(codec->bus, codec); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_device_unregister); + +/** + * snd_hdac_device_set_chip_name - set/update the codec name + * @codec: the HDAC device + * @name: name string to set + * + * Returns 0 if the name is set or updated, or a negative error code. + */ +int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name) +{ + char *newname; + + if (!name) + return 0; + newname = kstrdup(name, GFP_KERNEL); + if (!newname) + return -ENOMEM; + kfree(codec->chip_name); + codec->chip_name = newname; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_device_set_chip_name); + +/** + * snd_hdac_codec_modalias - give the module alias name + * @codec: HDAC device + * @buf: string buffer to store + * @size: string buffer size + * + * Returns the size of string, like snprintf(), or a negative error code. + */ +int snd_hdac_codec_modalias(struct hdac_device *codec, char *buf, size_t size) +{ + return snprintf(buf, size, "hdaudio:v%08Xr%08Xa%02X\n", + codec->vendor_id, codec->revision_id, codec->type); +} +EXPORT_SYMBOL_GPL(snd_hdac_codec_modalias); + +/** + * snd_hdac_make_cmd - compose a 32bit command word to be sent to the + * HD-audio controller + * @codec: the codec object + * @nid: NID to encode + * @verb: verb to encode + * @parm: parameter to encode + * + * Return an encoded command verb or -1 for error. + */ +unsigned int snd_hdac_make_cmd(struct hdac_device *codec, hda_nid_t nid, + unsigned int verb, unsigned int parm) +{ + u32 val, addr; + + addr = codec->addr; + if ((addr & ~0xf) || (nid & ~0x7f) || + (verb & ~0xfff) || (parm & ~0xffff)) { + dev_err(&codec->dev, "out of range cmd %x:%x:%x:%x\n", + addr, nid, verb, parm); + return -1; + } + + val = addr << 28; + val |= (u32)nid << 20; + val |= verb << 8; + val |= parm; + return val; +} +EXPORT_SYMBOL_GPL(snd_hdac_make_cmd); + +/** + * snd_hdac_exec_verb - execute an encoded verb + * @codec: the codec object + * @cmd: encoded verb to execute + * @flags: optional flags, pass zero for default + * @res: the pointer to store the result, NULL if running async + * + * Returns zero if successful, or a negative error code. + * + * This calls the exec_verb op when set in hdac_codec. If not, + * call the default snd_hdac_bus_exec_verb(). + */ +int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd, + unsigned int flags, unsigned int *res) +{ + if (codec->exec_verb) + return codec->exec_verb(codec, cmd, flags, res); + return snd_hdac_bus_exec_verb(codec->bus, codec->addr, cmd, res); +} +EXPORT_SYMBOL_GPL(snd_hdac_exec_verb); + + +/** + * snd_hdac_read - execute a verb + * @codec: the codec object + * @nid: NID to execute a verb + * @verb: verb to execute + * @parm: parameter for a verb + * @res: the pointer to store the result, NULL if running async + * + * Returns zero if successful, or a negative error code. + */ +int snd_hdac_read(struct hdac_device *codec, hda_nid_t nid, + unsigned int verb, unsigned int parm, unsigned int *res) +{ + unsigned int cmd = snd_hdac_make_cmd(codec, nid, verb, parm); + + return snd_hdac_exec_verb(codec, cmd, 0, res); +} +EXPORT_SYMBOL_GPL(snd_hdac_read); + +/** + * _snd_hdac_read_parm - read a parmeter + * + * This function returns zero or an error unlike snd_hdac_read_parm(). + */ +int _snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, int parm, + unsigned int *res) +{ + unsigned int cmd; + + cmd = snd_hdac_regmap_encode_verb(nid, AC_VERB_PARAMETERS) | parm; + return snd_hdac_regmap_read_raw(codec, cmd, res); +} +EXPORT_SYMBOL_GPL(_snd_hdac_read_parm); + +/** + * snd_hdac_read_parm_uncached - read a codec parameter without caching + * @codec: the codec object + * @nid: NID to read a parameter + * @parm: parameter to read + * + * Returns -1 for error. If you need to distinguish the error more + * strictly, use snd_hdac_read() directly. + */ +int snd_hdac_read_parm_uncached(struct hdac_device *codec, hda_nid_t nid, + int parm) +{ + unsigned int cmd, val; + + cmd = snd_hdac_regmap_encode_verb(nid, AC_VERB_PARAMETERS) | parm; + if (snd_hdac_regmap_read_raw_uncached(codec, cmd, &val) < 0) + return -1; + return val; +} +EXPORT_SYMBOL_GPL(snd_hdac_read_parm_uncached); + +/** + * snd_hdac_override_parm - override read-only parameters + * @codec: the codec object + * @nid: NID for the parameter + * @parm: the parameter to change + * @val: the parameter value to overwrite + */ +int snd_hdac_override_parm(struct hdac_device *codec, hda_nid_t nid, + unsigned int parm, unsigned int val) +{ + unsigned int verb = (AC_VERB_PARAMETERS << 8) | (nid << 20) | parm; + int err; + + if (!codec->regmap) + return -EINVAL; + + codec->caps_overwriting = true; + err = snd_hdac_regmap_write_raw(codec, verb, val); + codec->caps_overwriting = false; + return err; +} +EXPORT_SYMBOL_GPL(snd_hdac_override_parm); + +/** + * snd_hdac_get_sub_nodes - get start NID and number of subtree nodes + * @codec: the codec object + * @nid: NID to inspect + * @start_id: the pointer to store the starting NID + * + * Returns the number of subtree nodes or zero if not found. + * This function reads parameters always without caching. + */ +int snd_hdac_get_sub_nodes(struct hdac_device *codec, hda_nid_t nid, + hda_nid_t *start_id) +{ + unsigned int parm; + + parm = snd_hdac_read_parm_uncached(codec, nid, AC_PAR_NODE_COUNT); + if (parm == -1) { + *start_id = 0; + return 0; + } + *start_id = (parm >> 16) & 0x7fff; + return (int)(parm & 0x7fff); +} +EXPORT_SYMBOL_GPL(snd_hdac_get_sub_nodes); + +/* + * look for an AFG and MFG nodes + */ +static void setup_fg_nodes(struct hdac_device *codec) +{ + int i, total_nodes, function_id; + hda_nid_t nid; + + total_nodes = snd_hdac_get_sub_nodes(codec, AC_NODE_ROOT, &nid); + for (i = 0; i < total_nodes; i++, nid++) { + function_id = snd_hdac_read_parm(codec, nid, + AC_PAR_FUNCTION_TYPE); + switch (function_id & 0xff) { + case AC_GRP_AUDIO_FUNCTION: + codec->afg = nid; + codec->afg_function_id = function_id & 0xff; + codec->afg_unsol = (function_id >> 8) & 1; + break; + case AC_GRP_MODEM_FUNCTION: + codec->mfg = nid; + codec->mfg_function_id = function_id & 0xff; + codec->mfg_unsol = (function_id >> 8) & 1; + break; + default: + break; + } + } +} + +/** + * snd_hdac_refresh_widgets - Reset the widget start/end nodes + * @codec: the codec object + * @sysfs: re-initialize sysfs tree, too + */ +int snd_hdac_refresh_widgets(struct hdac_device *codec, bool sysfs) +{ + hda_nid_t start_nid; + int nums, err; + + nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid); + if (!start_nid || nums <= 0 || nums >= 0xff) { + dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n", + codec->afg); + return -EINVAL; + } + + if (sysfs) { + err = hda_widget_sysfs_reinit(codec, start_nid, nums); + if (err < 0) + return err; + } + + codec->num_nodes = nums; + codec->start_nid = start_nid; + codec->end_nid = start_nid + nums; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_refresh_widgets); + +/* return CONNLIST_LEN parameter of the given widget */ +static unsigned int get_num_conns(struct hdac_device *codec, hda_nid_t nid) +{ + unsigned int wcaps = get_wcaps(codec, nid); + unsigned int parm; + + if (!(wcaps & AC_WCAP_CONN_LIST) && + get_wcaps_type(wcaps) != AC_WID_VOL_KNB) + return 0; + + parm = snd_hdac_read_parm(codec, nid, AC_PAR_CONNLIST_LEN); + if (parm == -1) + parm = 0; + return parm; +} + +/** + * snd_hdac_get_connections - get a widget connection list + * @codec: the codec object + * @nid: NID + * @conn_list: the array to store the results, can be NULL + * @max_conns: the max size of the given array + * + * Returns the number of connected widgets, zero for no connection, or a + * negative error code. When the number of elements don't fit with the + * given array size, it returns -ENOSPC. + * + * When @conn_list is NULL, it just checks the number of connections. + */ +int snd_hdac_get_connections(struct hdac_device *codec, hda_nid_t nid, + hda_nid_t *conn_list, int max_conns) +{ + unsigned int parm; + int i, conn_len, conns, err; + unsigned int shift, num_elems, mask; + hda_nid_t prev_nid; + int null_count = 0; + + parm = get_num_conns(codec, nid); + if (!parm) + return 0; + + if (parm & AC_CLIST_LONG) { + /* long form */ + shift = 16; + num_elems = 2; + } else { + /* short form */ + shift = 8; + num_elems = 4; + } + conn_len = parm & AC_CLIST_LENGTH; + mask = (1 << (shift-1)) - 1; + + if (!conn_len) + return 0; /* no connection */ + + if (conn_len == 1) { + /* single connection */ + err = snd_hdac_read(codec, nid, AC_VERB_GET_CONNECT_LIST, 0, + &parm); + if (err < 0) + return err; + if (conn_list) + conn_list[0] = parm & mask; + return 1; + } + + /* multi connection */ + conns = 0; + prev_nid = 0; + for (i = 0; i < conn_len; i++) { + int range_val; + hda_nid_t val, n; + + if (i % num_elems == 0) { + err = snd_hdac_read(codec, nid, + AC_VERB_GET_CONNECT_LIST, i, + &parm); + if (err < 0) + return -EIO; + } + range_val = !!(parm & (1 << (shift-1))); /* ranges */ + val = parm & mask; + if (val == 0 && null_count++) { /* no second chance */ + dev_dbg(&codec->dev, + "invalid CONNECT_LIST verb %x[%i]:%x\n", + nid, i, parm); + return 0; + } + parm >>= shift; + if (range_val) { + /* ranges between the previous and this one */ + if (!prev_nid || prev_nid >= val) { + dev_warn(&codec->dev, + "invalid dep_range_val %x:%x\n", + prev_nid, val); + continue; + } + for (n = prev_nid + 1; n <= val; n++) { + if (conn_list) { + if (conns >= max_conns) + return -ENOSPC; + conn_list[conns] = n; + } + conns++; + } + } else { + if (conn_list) { + if (conns >= max_conns) + return -ENOSPC; + conn_list[conns] = val; + } + conns++; + } + prev_nid = val; + } + return conns; +} +EXPORT_SYMBOL_GPL(snd_hdac_get_connections); + +#ifdef CONFIG_PM +/** + * snd_hdac_power_up - power up the codec + * @codec: the codec object + * + * This function calls the runtime PM helper to power up the given codec. + * Unlike snd_hdac_power_up_pm(), you should call this only for the code + * path that isn't included in PM path. Otherwise it gets stuck. + * + * Returns zero if successful, or a negative error code. + */ +int snd_hdac_power_up(struct hdac_device *codec) +{ + return pm_runtime_get_sync(&codec->dev); +} +EXPORT_SYMBOL_GPL(snd_hdac_power_up); + +/** + * snd_hdac_power_down - power down the codec + * @codec: the codec object + * + * Returns zero if successful, or a negative error code. + */ +int snd_hdac_power_down(struct hdac_device *codec) +{ + struct device *dev = &codec->dev; + + pm_runtime_mark_last_busy(dev); + return pm_runtime_put_autosuspend(dev); +} +EXPORT_SYMBOL_GPL(snd_hdac_power_down); + +/** + * snd_hdac_power_up_pm - power up the codec + * @codec: the codec object + * + * This function can be called in a recursive code path like init code + * which may be called by PM suspend/resume again. OTOH, if a power-up + * call must wake up the sleeper (e.g. in a kctl callback), use + * snd_hdac_power_up() instead. + * + * Returns zero if successful, or a negative error code. + */ +int snd_hdac_power_up_pm(struct hdac_device *codec) +{ + if (!atomic_inc_not_zero(&codec->in_pm)) + return snd_hdac_power_up(codec); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_power_up_pm); + +/* like snd_hdac_power_up_pm(), but only increment the pm count when + * already powered up. Returns -1 if not powered up, 1 if incremented + * or 0 if unchanged. Only used in hdac_regmap.c + */ +int snd_hdac_keep_power_up(struct hdac_device *codec) +{ + if (!atomic_inc_not_zero(&codec->in_pm)) { + int ret = pm_runtime_get_if_in_use(&codec->dev); + if (!ret) + return -1; + if (ret < 0) + return 0; + } + return 1; +} + +/** + * snd_hdac_power_down_pm - power down the codec + * @codec: the codec object + * + * Like snd_hdac_power_up_pm(), this function is used in a recursive + * code path like init code which may be called by PM suspend/resume again. + * + * Returns zero if successful, or a negative error code. + */ +int snd_hdac_power_down_pm(struct hdac_device *codec) +{ + if (atomic_dec_if_positive(&codec->in_pm) < 0) + return snd_hdac_power_down(codec); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm); +#endif + +/** + * snd_hdac_link_power - Enable/disable the link power for a codec + * @codec: the codec object + * @bool: enable or disable the link power + */ +int snd_hdac_link_power(struct hdac_device *codec, bool enable) +{ + if (!codec->link_power_control) + return 0; + + if (codec->bus->ops->link_power) + return codec->bus->ops->link_power(codec->bus, enable); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_hdac_link_power); + +/* codec vendor labels */ +struct hda_vendor_id { + unsigned int id; + const char *name; +}; + +static struct hda_vendor_id hda_vendor_ids[] = { + { 0x1002, "ATI" }, + { 0x1013, "Cirrus Logic" }, + { 0x1057, "Motorola" }, + { 0x1095, "Silicon Image" }, + { 0x10de, "Nvidia" }, + { 0x10ec, "Realtek" }, + { 0x1102, "Creative" }, + { 0x1106, "VIA" }, + { 0x111d, "IDT" }, + { 0x11c1, "LSI" }, + { 0x11d4, "Analog Devices" }, + { 0x13f6, "C-Media" }, + { 0x14f1, "Conexant" }, + { 0x17e8, "Chrontel" }, + { 0x1854, "LG" }, + { 0x1aec, "Wolfson Microelectronics" }, + { 0x1af4, "QEMU" }, + { 0x434d, "C-Media" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +}; + +/* store the codec vendor name */ +static int get_codec_vendor_name(struct hdac_device *codec) +{ + const struct hda_vendor_id *c; + u16 vendor_id = codec->vendor_id >> 16; + + for (c = hda_vendor_ids; c->id; c++) { + if (c->id == vendor_id) { + codec->vendor_name = kstrdup(c->name, GFP_KERNEL); + return codec->vendor_name ? 0 : -ENOMEM; + } + } + + codec->vendor_name = kasprintf(GFP_KERNEL, "Generic %04x", vendor_id); + return codec->vendor_name ? 0 : -ENOMEM; +} + +/* + * stream formats + */ +struct hda_rate_tbl { + unsigned int hz; + unsigned int alsa_bits; + unsigned int hda_fmt; +}; + +/* rate = base * mult / div */ +#define HDA_RATE(base, mult, div) \ + (AC_FMT_BASE_##base##K | (((mult) - 1) << AC_FMT_MULT_SHIFT) | \ + (((div) - 1) << AC_FMT_DIV_SHIFT)) + +static struct hda_rate_tbl rate_bits[] = { + /* rate in Hz, ALSA rate bitmask, HDA format value */ + + /* autodetected value used in snd_hda_query_supported_pcm */ + { 8000, SNDRV_PCM_RATE_8000, HDA_RATE(48, 1, 6) }, + { 11025, SNDRV_PCM_RATE_11025, HDA_RATE(44, 1, 4) }, + { 16000, SNDRV_PCM_RATE_16000, HDA_RATE(48, 1, 3) }, + { 22050, SNDRV_PCM_RATE_22050, HDA_RATE(44, 1, 2) }, + { 32000, SNDRV_PCM_RATE_32000, HDA_RATE(48, 2, 3) }, + { 44100, SNDRV_PCM_RATE_44100, HDA_RATE(44, 1, 1) }, + { 48000, SNDRV_PCM_RATE_48000, HDA_RATE(48, 1, 1) }, + { 88200, SNDRV_PCM_RATE_88200, HDA_RATE(44, 2, 1) }, + { 96000, SNDRV_PCM_RATE_96000, HDA_RATE(48, 2, 1) }, + { 176400, SNDRV_PCM_RATE_176400, HDA_RATE(44, 4, 1) }, + { 192000, SNDRV_PCM_RATE_192000, HDA_RATE(48, 4, 1) }, +#define AC_PAR_PCM_RATE_BITS 11 + /* up to bits 10, 384kHZ isn't supported properly */ + + /* not autodetected value */ + { 9600, SNDRV_PCM_RATE_KNOT, HDA_RATE(48, 1, 5) }, + + { 0 } /* terminator */ +}; + +/** + * snd_hdac_calc_stream_format - calculate the format bitset + * @rate: the sample rate + * @channels: the number of channels + * @format: the PCM format (SNDRV_PCM_FORMAT_XXX) + * @maxbps: the max. bps + * @spdif_ctls: HD-audio SPDIF status bits (0 if irrelevant) + * + * Calculate the format bitset from the given rate, channels and th PCM format. + * + * Return zero if invalid. + */ +unsigned int snd_hdac_calc_stream_format(unsigned int rate, + unsigned int channels, + snd_pcm_format_t format, + unsigned int maxbps, + unsigned short spdif_ctls) +{ + int i; + unsigned int val = 0; + + for (i = 0; rate_bits[i].hz; i++) + if (rate_bits[i].hz == rate) { + val = rate_bits[i].hda_fmt; + break; + } + if (!rate_bits[i].hz) + return 0; + + if (channels == 0 || channels > 8) + return 0; + val |= channels - 1; + + switch (snd_pcm_format_width(format)) { + case 8: + val |= AC_FMT_BITS_8; + break; + case 16: + val |= AC_FMT_BITS_16; + break; + case 20: + case 24: + case 32: + if (maxbps >= 32 || format == SNDRV_PCM_FORMAT_FLOAT_LE) + val |= AC_FMT_BITS_32; + else if (maxbps >= 24) + val |= AC_FMT_BITS_24; + else + val |= AC_FMT_BITS_20; + break; + default: + return 0; + } + + if (spdif_ctls & AC_DIG1_NONAUDIO) + val |= AC_FMT_TYPE_NON_PCM; + + return val; +} +EXPORT_SYMBOL_GPL(snd_hdac_calc_stream_format); + +static unsigned int query_pcm_param(struct hdac_device *codec, hda_nid_t nid) +{ + unsigned int val = 0; + + if (nid != codec->afg && + (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) + val = snd_hdac_read_parm(codec, nid, AC_PAR_PCM); + if (!val || val == -1) + val = snd_hdac_read_parm(codec, codec->afg, AC_PAR_PCM); + if (!val || val == -1) + return 0; + return val; +} + +static unsigned int query_stream_param(struct hdac_device *codec, hda_nid_t nid) +{ + unsigned int streams = snd_hdac_read_parm(codec, nid, AC_PAR_STREAM); + + if (!streams || streams == -1) + streams = snd_hdac_read_parm(codec, codec->afg, AC_PAR_STREAM); + if (!streams || streams == -1) + return 0; + return streams; +} + +/** + * snd_hdac_query_supported_pcm - query the supported PCM rates and formats + * @codec: the codec object + * @nid: NID to query + * @ratesp: the pointer to store the detected rate bitflags + * @formatsp: the pointer to store the detected formats + * @bpsp: the pointer to store the detected format widths + * + * Queries the supported PCM rates and formats. The NULL @ratesp, @formatsp + * or @bsps argument is ignored. + * + * Returns 0 if successful, otherwise a negative error code. + */ +int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid, + u32 *ratesp, u64 *formatsp, unsigned int *bpsp) +{ + unsigned int i, val, wcaps; + + wcaps = get_wcaps(codec, nid); + val = query_pcm_param(codec, nid); + + if (ratesp) { + u32 rates = 0; + for (i = 0; i < AC_PAR_PCM_RATE_BITS; i++) { + if (val & (1 << i)) + rates |= rate_bits[i].alsa_bits; + } + if (rates == 0) { + dev_err(&codec->dev, + "rates == 0 (nid=0x%x, val=0x%x, ovrd=%i)\n", + nid, val, + (wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0); + return -EIO; + } + *ratesp = rates; + } + + if (formatsp || bpsp) { + u64 formats = 0; + unsigned int streams, bps; + + streams = query_stream_param(codec, nid); + if (!streams) + return -EIO; + + bps = 0; + if (streams & AC_SUPFMT_PCM) { + if (val & AC_SUPPCM_BITS_8) { + formats |= SNDRV_PCM_FMTBIT_U8; + bps = 8; + } + if (val & AC_SUPPCM_BITS_16) { + formats |= SNDRV_PCM_FMTBIT_S16_LE; + bps = 16; + } + if (wcaps & AC_WCAP_DIGITAL) { + if (val & AC_SUPPCM_BITS_32) + formats |= SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; + if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24)) + formats |= SNDRV_PCM_FMTBIT_S32_LE; + if (val & AC_SUPPCM_BITS_24) + bps = 24; + else if (val & AC_SUPPCM_BITS_20) + bps = 20; + } else if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24| + AC_SUPPCM_BITS_32)) { + formats |= SNDRV_PCM_FMTBIT_S32_LE; + if (val & AC_SUPPCM_BITS_32) + bps = 32; + else if (val & AC_SUPPCM_BITS_24) + bps = 24; + else if (val & AC_SUPPCM_BITS_20) + bps = 20; + } + } +#if 0 /* FIXME: CS4206 doesn't work, which is the only codec supporting float */ + if (streams & AC_SUPFMT_FLOAT32) { + formats |= SNDRV_PCM_FMTBIT_FLOAT_LE; + if (!bps) + bps = 32; + } +#endif + if (streams == AC_SUPFMT_AC3) { + /* should be exclusive */ + /* temporary hack: we have still no proper support + * for the direct AC3 stream... + */ + formats |= SNDRV_PCM_FMTBIT_U8; + bps = 8; + } + if (formats == 0) { + dev_err(&codec->dev, + "formats == 0 (nid=0x%x, val=0x%x, ovrd=%i, streams=0x%x)\n", + nid, val, + (wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0, + streams); + return -EIO; + } + if (formatsp) + *formatsp = formats; + if (bpsp) + *bpsp = bps; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_query_supported_pcm); + +/** + * snd_hdac_is_supported_format - Check the validity of the format + * @codec: the codec object + * @nid: NID to check + * @format: the HD-audio format value to check + * + * Check whether the given node supports the format value. + * + * Returns true if supported, false if not. + */ +bool snd_hdac_is_supported_format(struct hdac_device *codec, hda_nid_t nid, + unsigned int format) +{ + int i; + unsigned int val = 0, rate, stream; + + val = query_pcm_param(codec, nid); + if (!val) + return false; + + rate = format & 0xff00; + for (i = 0; i < AC_PAR_PCM_RATE_BITS; i++) + if (rate_bits[i].hda_fmt == rate) { + if (val & (1 << i)) + break; + return false; + } + if (i >= AC_PAR_PCM_RATE_BITS) + return false; + + stream = query_stream_param(codec, nid); + if (!stream) + return false; + + if (stream & AC_SUPFMT_PCM) { + switch (format & 0xf0) { + case 0x00: + if (!(val & AC_SUPPCM_BITS_8)) + return false; + break; + case 0x10: + if (!(val & AC_SUPPCM_BITS_16)) + return false; + break; + case 0x20: + if (!(val & AC_SUPPCM_BITS_20)) + return false; + break; + case 0x30: + if (!(val & AC_SUPPCM_BITS_24)) + return false; + break; + case 0x40: + if (!(val & AC_SUPPCM_BITS_32)) + return false; + break; + default: + return false; + } + } else { + /* FIXME: check for float32 and AC3? */ + } + + return true; +} +EXPORT_SYMBOL_GPL(snd_hdac_is_supported_format); + +static unsigned int codec_read(struct hdac_device *hdac, hda_nid_t nid, + int flags, unsigned int verb, unsigned int parm) +{ + unsigned int cmd = snd_hdac_make_cmd(hdac, nid, verb, parm); + unsigned int res; + + if (snd_hdac_exec_verb(hdac, cmd, flags, &res)) + return -1; + + return res; +} + +static int codec_write(struct hdac_device *hdac, hda_nid_t nid, + int flags, unsigned int verb, unsigned int parm) +{ + unsigned int cmd = snd_hdac_make_cmd(hdac, nid, verb, parm); + + return snd_hdac_exec_verb(hdac, cmd, flags, NULL); +} + +/** + * snd_hdac_codec_read - send a command and get the response + * @hdac: the HDAC device + * @nid: NID to send the command + * @flags: optional bit flags + * @verb: the verb to send + * @parm: the parameter for the verb + * + * Send a single command and read the corresponding response. + * + * Returns the obtained response value, or -1 for an error. + */ +int snd_hdac_codec_read(struct hdac_device *hdac, hda_nid_t nid, + int flags, unsigned int verb, unsigned int parm) +{ + return codec_read(hdac, nid, flags, verb, parm); +} +EXPORT_SYMBOL_GPL(snd_hdac_codec_read); + +/** + * snd_hdac_codec_write - send a single command without waiting for response + * @hdac: the HDAC device + * @nid: NID to send the command + * @flags: optional bit flags + * @verb: the verb to send + * @parm: the parameter for the verb + * + * Send a single command without waiting for response. + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_codec_write(struct hdac_device *hdac, hda_nid_t nid, + int flags, unsigned int verb, unsigned int parm) +{ + return codec_write(hdac, nid, flags, verb, parm); +} +EXPORT_SYMBOL_GPL(snd_hdac_codec_write); + +/** + * snd_hdac_check_power_state - check whether the actual power state matches + * with the target state + * + * @hdac: the HDAC device + * @nid: NID to send the command + * @target_state: target state to check for + * + * Return true if state matches, false if not + */ +bool snd_hdac_check_power_state(struct hdac_device *hdac, + hda_nid_t nid, unsigned int target_state) +{ + unsigned int state = codec_read(hdac, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + + if (state & AC_PWRST_ERROR) + return true; + state = (state >> 4) & 0x0f; + return (state == target_state); +} +EXPORT_SYMBOL_GPL(snd_hdac_check_power_state); +/** + * snd_hdac_sync_power_state - wait until actual power state matches + * with the target state + * + * @hdac: the HDAC device + * @nid: NID to send the command + * @target_state: target state to check for + * + * Return power state or PS_ERROR if codec rejects GET verb. + */ +unsigned int snd_hdac_sync_power_state(struct hdac_device *codec, + hda_nid_t nid, unsigned int power_state) +{ + unsigned long end_time = jiffies + msecs_to_jiffies(500); + unsigned int state, actual_state, count; + + for (count = 0; count < 500; count++) { + state = snd_hdac_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + if (state & AC_PWRST_ERROR) { + msleep(20); + break; + } + actual_state = (state >> 4) & 0x0f; + if (actual_state == power_state) + break; + if (time_after_eq(jiffies, end_time)) + break; + /* wait until the codec reachs to the target state */ + msleep(1); + } + return state; +} +EXPORT_SYMBOL_GPL(snd_hdac_sync_power_state); diff --git a/sound/hda/hdac_i915.c b/sound/hda/hdac_i915.c new file mode 100644 index 000000000..3847fe841 --- /dev/null +++ b/sound/hda/hdac_i915.c @@ -0,0 +1,160 @@ +/* + * hdac_i915.c - routines for sync between HD-A core and i915 display driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct completion bind_complete; + +#define CONTROLLER_IN_GPU(pci) (((pci)->device == 0x0a0c) || \ + ((pci)->device == 0x0c0c) || \ + ((pci)->device == 0x0d0c) || \ + ((pci)->device == 0x160c)) + +/** + * snd_hdac_i915_set_bclk - Reprogram BCLK for HSW/BDW + * @bus: HDA core bus + * + * Intel HSW/BDW display HDA controller is in GPU. Both its power and link BCLK + * depends on GPU. Two Extended Mode registers EM4 (M value) and EM5 (N Value) + * are used to convert CDClk (Core Display Clock) to 24MHz BCLK: + * BCLK = CDCLK * M / N + * The values will be lost when the display power well is disabled and need to + * be restored to avoid abnormal playback speed. + * + * Call this function at initializing and changing power well, as well as + * at ELD notifier for the hotplug. + */ +void snd_hdac_i915_set_bclk(struct hdac_bus *bus) +{ + struct drm_audio_component *acomp = bus->audio_component; + struct pci_dev *pci = to_pci_dev(bus->dev); + int cdclk_freq; + unsigned int bclk_m, bclk_n; + + if (!acomp || !acomp->ops || !acomp->ops->get_cdclk_freq) + return; /* only for i915 binding */ + if (!CONTROLLER_IN_GPU(pci)) + return; /* only HSW/BDW */ + + cdclk_freq = acomp->ops->get_cdclk_freq(acomp->dev); + switch (cdclk_freq) { + case 337500: + bclk_m = 16; + bclk_n = 225; + break; + + case 450000: + default: /* default CDCLK 450MHz */ + bclk_m = 4; + bclk_n = 75; + break; + + case 540000: + bclk_m = 4; + bclk_n = 90; + break; + + case 675000: + bclk_m = 8; + bclk_n = 225; + break; + } + + snd_hdac_chip_writew(bus, HSW_EM4, bclk_m); + snd_hdac_chip_writew(bus, HSW_EM5, bclk_n); +} +EXPORT_SYMBOL_GPL(snd_hdac_i915_set_bclk); + +static int i915_component_master_match(struct device *dev, void *data) +{ + return !strcmp(dev->driver->name, "i915"); +} + +/* check whether intel graphics is present */ +static bool i915_gfx_present(void) +{ + static const struct pci_device_id ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ANY_ID), + .class = PCI_BASE_CLASS_DISPLAY << 16, + .class_mask = 0xff << 16 }, + {} + }; + return pci_dev_present(ids); +} + +static int i915_master_bind(struct device *dev, + struct drm_audio_component *acomp) +{ + complete_all(&bind_complete); + /* clear audio_ops here as it was needed only for completion call */ + acomp->audio_ops = NULL; + return 0; +} + +static const struct drm_audio_component_audio_ops i915_init_ops = { + .master_bind = i915_master_bind +}; + +/** + * snd_hdac_i915_init - Initialize i915 audio component + * @bus: HDA core bus + * + * This function is supposed to be used only by a HD-audio controller + * driver that needs the interaction with i915 graphics. + * + * This function initializes and sets up the audio component to communicate + * with i915 graphics driver. + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_i915_init(struct hdac_bus *bus) +{ + struct drm_audio_component *acomp; + int err; + + if (!i915_gfx_present()) + return -ENODEV; + + init_completion(&bind_complete); + + err = snd_hdac_acomp_init(bus, &i915_init_ops, + i915_component_master_match, + sizeof(struct i915_audio_component) - sizeof(*acomp)); + if (err < 0) + return err; + acomp = bus->audio_component; + if (!acomp) + return -ENODEV; + if (!acomp->ops) { + if (!IS_ENABLED(CONFIG_MODULES) || + !request_module("i915")) { + /* 60s timeout */ + wait_for_completion_timeout(&bind_complete, + msecs_to_jiffies(60 * 1000)); + } + } + if (!acomp->ops) { + dev_info(bus->dev, "couldn't bind with audio component\n"); + snd_hdac_acomp_exit(bus); + return -ENODEV; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_i915_init); diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c new file mode 100644 index 000000000..419e285e0 --- /dev/null +++ b/sound/hda/hdac_regmap.c @@ -0,0 +1,531 @@ +/* + * Regmap support for HD-audio verbs + * + * A virtual register is translated to one or more hda verbs for write, + * vice versa for read. + * + * A few limitations: + * - Provided for not all verbs but only subset standard non-volatile verbs. + * - For reading, only AC_VERB_GET_* variants can be used. + * - For writing, mapped to the *corresponding* AC_VERB_SET_* variants, + * so can't handle asymmetric verbs for read and write + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int codec_pm_lock(struct hdac_device *codec) +{ + return snd_hdac_keep_power_up(codec); +} + +static void codec_pm_unlock(struct hdac_device *codec, int lock) +{ + if (lock == 1) + snd_hdac_power_down_pm(codec); +} + +#define get_verb(reg) (((reg) >> 8) & 0xfff) + +static bool hda_volatile_reg(struct device *dev, unsigned int reg) +{ + struct hdac_device *codec = dev_to_hdac_dev(dev); + unsigned int verb = get_verb(reg); + + switch (verb) { + case AC_VERB_GET_PROC_COEF: + return !codec->cache_coef; + case AC_VERB_GET_COEF_INDEX: + case AC_VERB_GET_PROC_STATE: + case AC_VERB_GET_POWER_STATE: + case AC_VERB_GET_PIN_SENSE: + case AC_VERB_GET_HDMI_DIP_SIZE: + case AC_VERB_GET_HDMI_ELDD: + case AC_VERB_GET_HDMI_DIP_INDEX: + case AC_VERB_GET_HDMI_DIP_DATA: + case AC_VERB_GET_HDMI_DIP_XMIT: + case AC_VERB_GET_HDMI_CP_CTRL: + case AC_VERB_GET_HDMI_CHAN_SLOT: + case AC_VERB_GET_DEVICE_SEL: + case AC_VERB_GET_DEVICE_LIST: /* read-only volatile */ + return true; + } + + return false; +} + +static bool hda_writeable_reg(struct device *dev, unsigned int reg) +{ + struct hdac_device *codec = dev_to_hdac_dev(dev); + unsigned int verb = get_verb(reg); + const unsigned int *v; + int i; + + snd_array_for_each(&codec->vendor_verbs, i, v) { + if (verb == *v) + return true; + } + + if (codec->caps_overwriting) + return true; + + switch (verb & 0xf00) { + case AC_VERB_GET_STREAM_FORMAT: + case AC_VERB_GET_AMP_GAIN_MUTE: + return true; + case AC_VERB_GET_PROC_COEF: + return codec->cache_coef; + case 0xf00: + break; + default: + return false; + } + + switch (verb) { + case AC_VERB_GET_CONNECT_SEL: + case AC_VERB_GET_SDI_SELECT: + case AC_VERB_GET_PIN_WIDGET_CONTROL: + case AC_VERB_GET_UNSOLICITED_RESPONSE: /* only as SET_UNSOLICITED_ENABLE */ + case AC_VERB_GET_BEEP_CONTROL: + case AC_VERB_GET_EAPD_BTLENABLE: + case AC_VERB_GET_DIGI_CONVERT_1: + case AC_VERB_GET_DIGI_CONVERT_2: /* only for beep control */ + case AC_VERB_GET_VOLUME_KNOB_CONTROL: + case AC_VERB_GET_GPIO_MASK: + case AC_VERB_GET_GPIO_DIRECTION: + case AC_VERB_GET_GPIO_DATA: /* not for volatile read */ + case AC_VERB_GET_GPIO_WAKE_MASK: + case AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK: + case AC_VERB_GET_GPIO_STICKY_MASK: + return true; + } + + return false; +} + +static bool hda_readable_reg(struct device *dev, unsigned int reg) +{ + struct hdac_device *codec = dev_to_hdac_dev(dev); + unsigned int verb = get_verb(reg); + + if (codec->caps_overwriting) + return true; + + switch (verb) { + case AC_VERB_PARAMETERS: + case AC_VERB_GET_CONNECT_LIST: + case AC_VERB_GET_SUBSYSTEM_ID: + return true; + /* below are basically writable, but disabled for reducing unnecessary + * writes at sync + */ + case AC_VERB_GET_CONFIG_DEFAULT: /* usually just read */ + case AC_VERB_GET_CONV: /* managed in PCM code */ + case AC_VERB_GET_CVT_CHAN_COUNT: /* managed in HDMI CA code */ + return true; + } + + return hda_writeable_reg(dev, reg); +} + +/* + * Stereo amp pseudo register: + * for making easier to handle the stereo volume control, we provide a + * fake register to deal both left and right channels by a single + * (pseudo) register access. A verb consisting of SET_AMP_GAIN with + * *both* SET_LEFT and SET_RIGHT bits takes a 16bit value, the lower 8bit + * for the left and the upper 8bit for the right channel. + */ +static bool is_stereo_amp_verb(unsigned int reg) +{ + if (((reg >> 8) & 0x700) != AC_VERB_SET_AMP_GAIN_MUTE) + return false; + return (reg & (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT)) == + (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT); +} + +/* read a pseudo stereo amp register (16bit left+right) */ +static int hda_reg_read_stereo_amp(struct hdac_device *codec, + unsigned int reg, unsigned int *val) +{ + unsigned int left, right; + int err; + + reg &= ~(AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT); + err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_LEFT, 0, &left); + if (err < 0) + return err; + err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_RIGHT, 0, &right); + if (err < 0) + return err; + *val = left | (right << 8); + return 0; +} + +/* write a pseudo stereo amp register (16bit left+right) */ +static int hda_reg_write_stereo_amp(struct hdac_device *codec, + unsigned int reg, unsigned int val) +{ + int err; + unsigned int verb, left, right; + + verb = AC_VERB_SET_AMP_GAIN_MUTE << 8; + if (reg & AC_AMP_GET_OUTPUT) + verb |= AC_AMP_SET_OUTPUT; + else + verb |= AC_AMP_SET_INPUT | ((reg & 0xf) << 8); + reg = (reg & ~0xfffff) | verb; + + left = val & 0xff; + right = (val >> 8) & 0xff; + if (left == right) { + reg |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT; + return snd_hdac_exec_verb(codec, reg | left, 0, NULL); + } + + err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_LEFT | left, 0, NULL); + if (err < 0) + return err; + err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_RIGHT | right, 0, NULL); + if (err < 0) + return err; + return 0; +} + +/* read a pseudo coef register (16bit) */ +static int hda_reg_read_coef(struct hdac_device *codec, unsigned int reg, + unsigned int *val) +{ + unsigned int verb; + int err; + + if (!codec->cache_coef) + return -EINVAL; + /* LSB 8bit = coef index */ + verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8); + err = snd_hdac_exec_verb(codec, verb, 0, NULL); + if (err < 0) + return err; + verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8); + return snd_hdac_exec_verb(codec, verb, 0, val); +} + +/* write a pseudo coef register (16bit) */ +static int hda_reg_write_coef(struct hdac_device *codec, unsigned int reg, + unsigned int val) +{ + unsigned int verb; + int err; + + if (!codec->cache_coef) + return -EINVAL; + /* LSB 8bit = coef index */ + verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8); + err = snd_hdac_exec_verb(codec, verb, 0, NULL); + if (err < 0) + return err; + verb = (reg & ~0xfffff) | (AC_VERB_GET_COEF_INDEX << 8) | + (val & 0xffff); + return snd_hdac_exec_verb(codec, verb, 0, NULL); +} + +static int hda_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct hdac_device *codec = context; + int verb = get_verb(reg); + int err; + int pm_lock = 0; + + if (verb != AC_VERB_GET_POWER_STATE) { + pm_lock = codec_pm_lock(codec); + if (pm_lock < 0) + return -EAGAIN; + } + reg |= (codec->addr << 28); + if (is_stereo_amp_verb(reg)) { + err = hda_reg_read_stereo_amp(codec, reg, val); + goto out; + } + if (verb == AC_VERB_GET_PROC_COEF) { + err = hda_reg_read_coef(codec, reg, val); + goto out; + } + if ((verb & 0x700) == AC_VERB_SET_AMP_GAIN_MUTE) + reg &= ~AC_AMP_FAKE_MUTE; + + err = snd_hdac_exec_verb(codec, reg, 0, val); + if (err < 0) + goto out; + /* special handling for asymmetric reads */ + if (verb == AC_VERB_GET_POWER_STATE) { + if (*val & AC_PWRST_ERROR) + *val = -1; + else /* take only the actual state */ + *val = (*val >> 4) & 0x0f; + } + out: + codec_pm_unlock(codec, pm_lock); + return err; +} + +static int hda_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct hdac_device *codec = context; + unsigned int verb; + int i, bytes, err; + int pm_lock = 0; + + if (codec->caps_overwriting) + return 0; + + reg &= ~0x00080000U; /* drop GET bit */ + reg |= (codec->addr << 28); + verb = get_verb(reg); + + if (verb != AC_VERB_SET_POWER_STATE) { + pm_lock = codec_pm_lock(codec); + if (pm_lock < 0) + return codec->lazy_cache ? 0 : -EAGAIN; + } + + if (is_stereo_amp_verb(reg)) { + err = hda_reg_write_stereo_amp(codec, reg, val); + goto out; + } + + if (verb == AC_VERB_SET_PROC_COEF) { + err = hda_reg_write_coef(codec, reg, val); + goto out; + } + + switch (verb & 0xf00) { + case AC_VERB_SET_AMP_GAIN_MUTE: + if ((reg & AC_AMP_FAKE_MUTE) && (val & AC_AMP_MUTE)) + val = 0; + verb = AC_VERB_SET_AMP_GAIN_MUTE; + if (reg & AC_AMP_GET_LEFT) + verb |= AC_AMP_SET_LEFT >> 8; + else + verb |= AC_AMP_SET_RIGHT >> 8; + if (reg & AC_AMP_GET_OUTPUT) { + verb |= AC_AMP_SET_OUTPUT >> 8; + } else { + verb |= AC_AMP_SET_INPUT >> 8; + verb |= reg & 0xf; + } + break; + } + + switch (verb) { + case AC_VERB_SET_DIGI_CONVERT_1: + bytes = 2; + break; + case AC_VERB_SET_CONFIG_DEFAULT_BYTES_0: + bytes = 4; + break; + default: + bytes = 1; + break; + } + + for (i = 0; i < bytes; i++) { + reg &= ~0xfffff; + reg |= (verb + i) << 8 | ((val >> (8 * i)) & 0xff); + err = snd_hdac_exec_verb(codec, reg, 0, NULL); + if (err < 0) + goto out; + } + + out: + codec_pm_unlock(codec, pm_lock); + return err; +} + +static const struct regmap_config hda_regmap_cfg = { + .name = "hdaudio", + .reg_bits = 32, + .val_bits = 32, + .max_register = 0xfffffff, + .writeable_reg = hda_writeable_reg, + .readable_reg = hda_readable_reg, + .volatile_reg = hda_volatile_reg, + .cache_type = REGCACHE_RBTREE, + .reg_read = hda_reg_read, + .reg_write = hda_reg_write, + .use_single_rw = true, +}; + +/** + * snd_hdac_regmap_init - Initialize regmap for HDA register accesses + * @codec: the codec object + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_regmap_init(struct hdac_device *codec) +{ + struct regmap *regmap; + + regmap = regmap_init(&codec->dev, NULL, codec, &hda_regmap_cfg); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + codec->regmap = regmap; + snd_array_init(&codec->vendor_verbs, sizeof(unsigned int), 8); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_regmap_init); + +/** + * snd_hdac_regmap_init - Release the regmap from HDA codec + * @codec: the codec object + */ +void snd_hdac_regmap_exit(struct hdac_device *codec) +{ + if (codec->regmap) { + regmap_exit(codec->regmap); + codec->regmap = NULL; + snd_array_free(&codec->vendor_verbs); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_regmap_exit); + +/** + * snd_hdac_regmap_add_vendor_verb - add a vendor-specific verb to regmap + * @codec: the codec object + * @verb: verb to allow accessing via regmap + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_regmap_add_vendor_verb(struct hdac_device *codec, + unsigned int verb) +{ + unsigned int *p = snd_array_new(&codec->vendor_verbs); + + if (!p) + return -ENOMEM; + *p = verb | 0x800; /* set GET bit */ + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_regmap_add_vendor_verb); + +/* + * helper functions + */ + +/* write a pseudo-register value (w/o power sequence) */ +static int reg_raw_write(struct hdac_device *codec, unsigned int reg, + unsigned int val) +{ + if (!codec->regmap) + return hda_reg_write(codec, reg, val); + else + return regmap_write(codec->regmap, reg, val); +} + +/** + * snd_hdac_regmap_write_raw - write a pseudo register with power mgmt + * @codec: the codec object + * @reg: pseudo register + * @val: value to write + * + * Returns zero if successful or a negative error code. + */ +int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg, + unsigned int val) +{ + int err; + + err = reg_raw_write(codec, reg, val); + if (err == -EAGAIN) { + err = snd_hdac_power_up_pm(codec); + if (err >= 0) + err = reg_raw_write(codec, reg, val); + snd_hdac_power_down_pm(codec); + } + return err; +} +EXPORT_SYMBOL_GPL(snd_hdac_regmap_write_raw); + +static int reg_raw_read(struct hdac_device *codec, unsigned int reg, + unsigned int *val, bool uncached) +{ + if (uncached || !codec->regmap) + return hda_reg_read(codec, reg, val); + else + return regmap_read(codec->regmap, reg, val); +} + +static int __snd_hdac_regmap_read_raw(struct hdac_device *codec, + unsigned int reg, unsigned int *val, + bool uncached) +{ + int err; + + err = reg_raw_read(codec, reg, val, uncached); + if (err == -EAGAIN) { + err = snd_hdac_power_up_pm(codec); + if (err >= 0) + err = reg_raw_read(codec, reg, val, uncached); + snd_hdac_power_down_pm(codec); + } + return err; +} + +/** + * snd_hdac_regmap_read_raw - read a pseudo register with power mgmt + * @codec: the codec object + * @reg: pseudo register + * @val: pointer to store the read value + * + * Returns zero if successful or a negative error code. + */ +int snd_hdac_regmap_read_raw(struct hdac_device *codec, unsigned int reg, + unsigned int *val) +{ + return __snd_hdac_regmap_read_raw(codec, reg, val, false); +} +EXPORT_SYMBOL_GPL(snd_hdac_regmap_read_raw); + +/* Works like snd_hdac_regmap_read_raw(), but this doesn't read from the + * cache but always via hda verbs. + */ +int snd_hdac_regmap_read_raw_uncached(struct hdac_device *codec, + unsigned int reg, unsigned int *val) +{ + return __snd_hdac_regmap_read_raw(codec, reg, val, true); +} + +/** + * snd_hdac_regmap_update_raw - update a pseudo register with power mgmt + * @codec: the codec object + * @reg: pseudo register + * @mask: bit mask to udpate + * @val: value to update + * + * Returns zero if successful or a negative error code. + */ +int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg, + unsigned int mask, unsigned int val) +{ + unsigned int orig; + int err; + + val &= mask; + err = snd_hdac_regmap_read_raw(codec, reg, &orig); + if (err < 0) + return err; + val |= orig & ~mask; + if (val == orig) + return 0; + err = snd_hdac_regmap_write_raw(codec, reg, val); + if (err < 0) + return err; + return 1; +} +EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw); diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c new file mode 100644 index 000000000..eee422390 --- /dev/null +++ b/sound/hda/hdac_stream.c @@ -0,0 +1,720 @@ +/* + * HD-audio stream operations + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "trace.h" + +/** + * snd_hdac_stream_init - initialize each stream (aka device) + * @bus: HD-audio core bus + * @azx_dev: HD-audio core stream object to initialize + * @idx: stream index number + * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE) + * @tag: the tag id to assign + * + * Assign the starting bdl address to each stream (device) and initialize. + */ +void snd_hdac_stream_init(struct hdac_bus *bus, struct hdac_stream *azx_dev, + int idx, int direction, int tag) +{ + azx_dev->bus = bus; + /* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */ + azx_dev->sd_addr = bus->remap_addr + (0x20 * idx + 0x80); + /* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */ + azx_dev->sd_int_sta_mask = 1 << idx; + azx_dev->index = idx; + azx_dev->direction = direction; + azx_dev->stream_tag = tag; + snd_hdac_dsp_lock_init(azx_dev); + list_add_tail(&azx_dev->list, &bus->stream_list); +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_init); + +/** + * snd_hdac_stream_start - start a stream + * @azx_dev: HD-audio core stream to start + * @fresh_start: false = wallclock timestamp relative to period wallclock + * + * Start a stream, set start_wallclk and set the running flag. + */ +void snd_hdac_stream_start(struct hdac_stream *azx_dev, bool fresh_start) +{ + struct hdac_bus *bus = azx_dev->bus; + + trace_snd_hdac_stream_start(bus, azx_dev); + + azx_dev->start_wallclk = snd_hdac_chip_readl(bus, WALLCLK); + if (!fresh_start) + azx_dev->start_wallclk -= azx_dev->period_wallclk; + + /* enable SIE */ + snd_hdac_chip_updatel(bus, INTCTL, 0, 1 << azx_dev->index); + /* set DMA start and interrupt mask */ + snd_hdac_stream_updateb(azx_dev, SD_CTL, + 0, SD_CTL_DMA_START | SD_INT_MASK); + azx_dev->running = true; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_start); + +/** + * snd_hdac_stream_clear - stop a stream DMA + * @azx_dev: HD-audio core stream to stop + */ +void snd_hdac_stream_clear(struct hdac_stream *azx_dev) +{ + snd_hdac_stream_updateb(azx_dev, SD_CTL, + SD_CTL_DMA_START | SD_INT_MASK, 0); + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ + azx_dev->running = false; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_clear); + +/** + * snd_hdac_stream_stop - stop a stream + * @azx_dev: HD-audio core stream to stop + * + * Stop a stream DMA and disable stream interrupt + */ +void snd_hdac_stream_stop(struct hdac_stream *azx_dev) +{ + trace_snd_hdac_stream_stop(azx_dev->bus, azx_dev); + + snd_hdac_stream_clear(azx_dev); + /* disable SIE */ + snd_hdac_chip_updatel(azx_dev->bus, INTCTL, 1 << azx_dev->index, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_stop); + +/** + * snd_hdac_stream_reset - reset a stream + * @azx_dev: HD-audio core stream to reset + */ +void snd_hdac_stream_reset(struct hdac_stream *azx_dev) +{ + unsigned char val; + int timeout; + + snd_hdac_stream_clear(azx_dev); + + snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); + udelay(3); + timeout = 300; + do { + val = snd_hdac_stream_readb(azx_dev, SD_CTL) & + SD_CTL_STREAM_RESET; + if (val) + break; + } while (--timeout); + val &= ~SD_CTL_STREAM_RESET; + snd_hdac_stream_writeb(azx_dev, SD_CTL, val); + udelay(3); + + timeout = 300; + /* waiting for hardware to report that the stream is out of reset */ + do { + val = snd_hdac_stream_readb(azx_dev, SD_CTL) & + SD_CTL_STREAM_RESET; + if (!val) + break; + } while (--timeout); + + /* reset first position - may not be synced with hw at this time */ + if (azx_dev->posbuf) + *azx_dev->posbuf = 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_reset); + +/** + * snd_hdac_stream_setup - set up the SD for streaming + * @azx_dev: HD-audio core stream to set up + */ +int snd_hdac_stream_setup(struct hdac_stream *azx_dev) +{ + struct hdac_bus *bus = azx_dev->bus; + struct snd_pcm_runtime *runtime; + unsigned int val; + + if (azx_dev->substream) + runtime = azx_dev->substream->runtime; + else + runtime = NULL; + /* make sure the run bit is zero for SD */ + snd_hdac_stream_clear(azx_dev); + /* program the stream_tag */ + val = snd_hdac_stream_readl(azx_dev, SD_CTL); + val = (val & ~SD_CTL_STREAM_TAG_MASK) | + (azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT); + if (!bus->snoop) + val |= SD_CTL_TRAFFIC_PRIO; + snd_hdac_stream_writel(azx_dev, SD_CTL, val); + + /* program the length of samples in cyclic buffer */ + snd_hdac_stream_writel(azx_dev, SD_CBL, azx_dev->bufsize); + + /* program the stream format */ + /* this value needs to be the same as the one programmed */ + snd_hdac_stream_writew(azx_dev, SD_FORMAT, azx_dev->format_val); + + /* program the stream LVI (last valid index) of the BDL */ + snd_hdac_stream_writew(azx_dev, SD_LVI, azx_dev->frags - 1); + + /* program the BDL address */ + /* lower BDL address */ + snd_hdac_stream_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl.addr); + /* upper BDL address */ + snd_hdac_stream_writel(azx_dev, SD_BDLPU, + upper_32_bits(azx_dev->bdl.addr)); + + /* enable the position buffer */ + if (bus->use_posbuf && bus->posbuf.addr) { + if (!(snd_hdac_chip_readl(bus, DPLBASE) & AZX_DPLBASE_ENABLE)) + snd_hdac_chip_writel(bus, DPLBASE, + (u32)bus->posbuf.addr | AZX_DPLBASE_ENABLE); + } + + /* set the interrupt enable bits in the descriptor control register */ + snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_INT_MASK); + + if (azx_dev->direction == SNDRV_PCM_STREAM_PLAYBACK) + azx_dev->fifo_size = + snd_hdac_stream_readw(azx_dev, SD_FIFOSIZE) + 1; + else + azx_dev->fifo_size = 0; + + /* when LPIB delay correction gives a small negative value, + * we ignore it; currently set the threshold statically to + * 64 frames + */ + if (runtime && runtime->period_size > 64) + azx_dev->delay_negative_threshold = + -frames_to_bytes(runtime, 64); + else + azx_dev->delay_negative_threshold = 0; + + /* wallclk has 24Mhz clock source */ + if (runtime) + azx_dev->period_wallclk = (((runtime->period_size * 24000) / + runtime->rate) * 1000); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_setup); + +/** + * snd_hdac_stream_cleanup - cleanup a stream + * @azx_dev: HD-audio core stream to clean up + */ +void snd_hdac_stream_cleanup(struct hdac_stream *azx_dev) +{ + snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0); + snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0); + snd_hdac_stream_writel(azx_dev, SD_CTL, 0); + azx_dev->bufsize = 0; + azx_dev->period_bytes = 0; + azx_dev->format_val = 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_cleanup); + +/** + * snd_hdac_stream_assign - assign a stream for the PCM + * @bus: HD-audio core bus + * @substream: PCM substream to assign + * + * Look for an unused stream for the given PCM substream, assign it + * and return the stream object. If no stream is free, returns NULL. + * The function tries to keep using the same stream object when it's used + * beforehand. Also, when bus->reverse_assign flag is set, the last free + * or matching entry is returned. This is needed for some strange codecs. + */ +struct hdac_stream *snd_hdac_stream_assign(struct hdac_bus *bus, + struct snd_pcm_substream *substream) +{ + struct hdac_stream *azx_dev; + struct hdac_stream *res = NULL; + + /* make a non-zero unique key for the substream */ + int key = (substream->pcm->device << 16) | (substream->number << 2) | + (substream->stream + 1); + + list_for_each_entry(azx_dev, &bus->stream_list, list) { + if (azx_dev->direction != substream->stream) + continue; + if (azx_dev->opened) + continue; + if (azx_dev->assigned_key == key) { + res = azx_dev; + break; + } + if (!res || bus->reverse_assign) + res = azx_dev; + } + if (res) { + spin_lock_irq(&bus->reg_lock); + res->opened = 1; + res->running = 0; + res->assigned_key = key; + res->substream = substream; + spin_unlock_irq(&bus->reg_lock); + } + return res; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_assign); + +/** + * snd_hdac_stream_release - release the assigned stream + * @azx_dev: HD-audio core stream to release + * + * Release the stream that has been assigned by snd_hdac_stream_assign(). + */ +void snd_hdac_stream_release(struct hdac_stream *azx_dev) +{ + struct hdac_bus *bus = azx_dev->bus; + + spin_lock_irq(&bus->reg_lock); + azx_dev->opened = 0; + azx_dev->running = 0; + azx_dev->substream = NULL; + spin_unlock_irq(&bus->reg_lock); +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_release); + +/** + * snd_hdac_get_stream - return hdac_stream based on stream_tag and + * direction + * + * @bus: HD-audio core bus + * @dir: direction for the stream to be found + * @stream_tag: stream tag for stream to be found + */ +struct hdac_stream *snd_hdac_get_stream(struct hdac_bus *bus, + int dir, int stream_tag) +{ + struct hdac_stream *s; + + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction == dir && s->stream_tag == stream_tag) + return s; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hdac_get_stream); + +/* + * set up a BDL entry + */ +static int setup_bdle(struct hdac_bus *bus, + struct snd_dma_buffer *dmab, + struct hdac_stream *azx_dev, __le32 **bdlp, + int ofs, int size, int with_ioc) +{ + __le32 *bdl = *bdlp; + + while (size > 0) { + dma_addr_t addr; + int chunk; + + if (azx_dev->frags >= AZX_MAX_BDL_ENTRIES) + return -EINVAL; + + addr = snd_sgbuf_get_addr(dmab, 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_sgbuf_get_chunk_size(dmab, ofs, size); + /* one BDLE cannot cross 4K boundary on CTHDA chips */ + if (bus->align_bdle_4k) { + u32 remain = 0x1000 - (ofs & 0xfff); + + if (chunk > remain) + chunk = remain; + } + bdl[2] = cpu_to_le32(chunk); + /* program the IOC to enable interrupt + * only when the whole fragment is processed + */ + size -= chunk; + bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01); + bdl += 4; + azx_dev->frags++; + ofs += chunk; + } + *bdlp = bdl; + return ofs; +} + +/** + * snd_hdac_stream_setup_periods - set up BDL entries + * @azx_dev: HD-audio core stream to set up + * + * Set up the buffer descriptor table of the given stream based on the + * period and buffer sizes of the assigned PCM substream. + */ +int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev) +{ + struct hdac_bus *bus = azx_dev->bus; + struct snd_pcm_substream *substream = azx_dev->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + __le32 *bdl; + int i, ofs, periods, period_bytes; + int pos_adj, pos_align; + + /* reset BDL address */ + snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0); + snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0); + + period_bytes = azx_dev->period_bytes; + periods = azx_dev->bufsize / period_bytes; + + /* program the initial BDL entries */ + bdl = (__le32 *)azx_dev->bdl.area; + ofs = 0; + azx_dev->frags = 0; + + pos_adj = bus->bdl_pos_adj; + if (!azx_dev->no_period_wakeup && pos_adj > 0) { + pos_align = pos_adj; + pos_adj = (pos_adj * runtime->rate + 47999) / 48000; + if (!pos_adj) + pos_adj = pos_align; + else + pos_adj = ((pos_adj + pos_align - 1) / pos_align) * + pos_align; + pos_adj = frames_to_bytes(runtime, pos_adj); + if (pos_adj >= period_bytes) { + dev_warn(bus->dev, "Too big adjustment %d\n", + pos_adj); + pos_adj = 0; + } else { + ofs = setup_bdle(bus, snd_pcm_get_dma_buf(substream), + azx_dev, + &bdl, ofs, pos_adj, true); + if (ofs < 0) + goto error; + } + } else + pos_adj = 0; + + for (i = 0; i < periods; i++) { + if (i == periods - 1 && pos_adj) + ofs = setup_bdle(bus, snd_pcm_get_dma_buf(substream), + azx_dev, &bdl, ofs, + period_bytes - pos_adj, 0); + else + ofs = setup_bdle(bus, snd_pcm_get_dma_buf(substream), + azx_dev, &bdl, ofs, + period_bytes, + !azx_dev->no_period_wakeup); + if (ofs < 0) + goto error; + } + return 0; + + error: + dev_err(bus->dev, "Too many BDL entries: buffer=%d, period=%d\n", + azx_dev->bufsize, period_bytes); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_setup_periods); + +/** + * snd_hdac_stream_set_params - set stream parameters + * @azx_dev: HD-audio core stream for which parameters are to be set + * @format_val: format value parameter + * + * Setup the HD-audio core stream parameters from substream of the stream + * and passed format value + */ +int snd_hdac_stream_set_params(struct hdac_stream *azx_dev, + unsigned int format_val) +{ + + unsigned int bufsize, period_bytes; + struct snd_pcm_substream *substream = azx_dev->substream; + struct snd_pcm_runtime *runtime; + int err; + + if (!substream) + return -EINVAL; + runtime = substream->runtime; + bufsize = snd_pcm_lib_buffer_bytes(substream); + period_bytes = snd_pcm_lib_period_bytes(substream); + + if (bufsize != azx_dev->bufsize || + period_bytes != azx_dev->period_bytes || + format_val != azx_dev->format_val || + runtime->no_period_wakeup != azx_dev->no_period_wakeup) { + azx_dev->bufsize = bufsize; + azx_dev->period_bytes = period_bytes; + azx_dev->format_val = format_val; + azx_dev->no_period_wakeup = runtime->no_period_wakeup; + err = snd_hdac_stream_setup_periods(azx_dev); + if (err < 0) + return err; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_set_params); + +static u64 azx_cc_read(const struct cyclecounter *cc) +{ + struct hdac_stream *azx_dev = container_of(cc, struct hdac_stream, cc); + + return snd_hdac_chip_readl(azx_dev->bus, WALLCLK); +} + +static void azx_timecounter_init(struct hdac_stream *azx_dev, + bool force, u64 last) +{ + struct timecounter *tc = &azx_dev->tc; + struct cyclecounter *cc = &azx_dev->cc; + u64 nsec; + + cc->read = azx_cc_read; + cc->mask = CLOCKSOURCE_MASK(32); + + /* + * Converting from 24 MHz to ns means applying a 125/3 factor. + * To avoid any saturation issues in intermediate operations, + * the 125 factor is applied first. The division is applied + * last after reading the timecounter value. + * Applying the 1/3 factor as part of the multiplication + * requires at least 20 bits for a decent precision, however + * overflows occur after about 4 hours or less, not a option. + */ + + cc->mult = 125; /* saturation after 195 years */ + cc->shift = 0; + + nsec = 0; /* audio time is elapsed time since trigger */ + timecounter_init(tc, cc, nsec); + if (force) { + /* + * force timecounter to use predefined value, + * used for synchronized starts + */ + tc->cycle_last = last; + } +} + +/** + * snd_hdac_stream_timecounter_init - initialize time counter + * @azx_dev: HD-audio core stream (master stream) + * @streams: bit flags of streams to set up + * + * Initializes the time counter of streams marked by the bit flags (each + * bit corresponds to the stream index). + * The trigger timestamp of PCM substream assigned to the given stream is + * updated accordingly, too. + */ +void snd_hdac_stream_timecounter_init(struct hdac_stream *azx_dev, + unsigned int streams) +{ + struct hdac_bus *bus = azx_dev->bus; + struct snd_pcm_runtime *runtime = azx_dev->substream->runtime; + struct hdac_stream *s; + bool inited = false; + u64 cycle_last = 0; + int i = 0; + + list_for_each_entry(s, &bus->stream_list, list) { + if (streams & (1 << i)) { + azx_timecounter_init(s, inited, cycle_last); + if (!inited) { + inited = true; + cycle_last = s->tc.cycle_last; + } + } + i++; + } + + snd_pcm_gettime(runtime, &runtime->trigger_tstamp); + runtime->trigger_tstamp_latched = true; +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_timecounter_init); + +/** + * snd_hdac_stream_sync_trigger - turn on/off stream sync register + * @azx_dev: HD-audio core stream (master stream) + * @streams: bit flags of streams to sync + */ +void snd_hdac_stream_sync_trigger(struct hdac_stream *azx_dev, bool set, + unsigned int streams, unsigned int reg) +{ + struct hdac_bus *bus = azx_dev->bus; + unsigned int val; + + if (!reg) + reg = AZX_REG_SSYNC; + val = _snd_hdac_chip_readl(bus, reg); + if (set) + val |= streams; + else + val &= ~streams; + _snd_hdac_chip_writel(bus, reg, val); +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_sync_trigger); + +/** + * snd_hdac_stream_sync - sync with start/strop trigger operation + * @azx_dev: HD-audio core stream (master stream) + * @start: true = start, false = stop + * @streams: bit flags of streams to sync + * + * For @start = true, wait until all FIFOs get ready. + * For @start = false, wait until all RUN bits are cleared. + */ +void snd_hdac_stream_sync(struct hdac_stream *azx_dev, bool start, + unsigned int streams) +{ + struct hdac_bus *bus = azx_dev->bus; + int i, nwait, timeout; + struct hdac_stream *s; + + for (timeout = 5000; timeout; timeout--) { + nwait = 0; + i = 0; + list_for_each_entry(s, &bus->stream_list, list) { + if (streams & (1 << i)) { + if (start) { + /* check FIFO gets ready */ + if (!(snd_hdac_stream_readb(s, SD_STS) & + SD_STS_FIFO_READY)) + nwait++; + } else { + /* check RUN bit is cleared */ + if (snd_hdac_stream_readb(s, SD_CTL) & + SD_CTL_DMA_START) + nwait++; + } + } + i++; + } + if (!nwait) + break; + cpu_relax(); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_sync); + +#ifdef CONFIG_SND_HDA_DSP_LOADER +/** + * snd_hdac_dsp_prepare - prepare for DSP loading + * @azx_dev: HD-audio core stream used for DSP loading + * @format: HD-audio stream format + * @byte_size: data chunk byte size + * @bufp: allocated buffer + * + * Allocate the buffer for the given size and set up the given stream for + * DSP loading. Returns the stream tag (>= 0), or a negative error code. + */ +int snd_hdac_dsp_prepare(struct hdac_stream *azx_dev, unsigned int format, + unsigned int byte_size, struct snd_dma_buffer *bufp) +{ + struct hdac_bus *bus = azx_dev->bus; + __le32 *bdl; + int err; + + snd_hdac_dsp_lock(azx_dev); + spin_lock_irq(&bus->reg_lock); + if (azx_dev->running || azx_dev->locked) { + spin_unlock_irq(&bus->reg_lock); + err = -EBUSY; + goto unlock; + } + azx_dev->locked = true; + spin_unlock_irq(&bus->reg_lock); + + err = bus->io_ops->dma_alloc_pages(bus, SNDRV_DMA_TYPE_DEV_SG, + byte_size, bufp); + if (err < 0) + goto err_alloc; + + azx_dev->substream = NULL; + azx_dev->bufsize = byte_size; + azx_dev->period_bytes = byte_size; + azx_dev->format_val = format; + + snd_hdac_stream_reset(azx_dev); + + /* reset BDL address */ + snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0); + snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0); + + azx_dev->frags = 0; + bdl = (__le32 *)azx_dev->bdl.area; + err = setup_bdle(bus, bufp, azx_dev, &bdl, 0, byte_size, 0); + if (err < 0) + goto error; + + snd_hdac_stream_setup(azx_dev); + snd_hdac_dsp_unlock(azx_dev); + return azx_dev->stream_tag; + + error: + bus->io_ops->dma_free_pages(bus, bufp); + err_alloc: + spin_lock_irq(&bus->reg_lock); + azx_dev->locked = false; + spin_unlock_irq(&bus->reg_lock); + unlock: + snd_hdac_dsp_unlock(azx_dev); + return err; +} +EXPORT_SYMBOL_GPL(snd_hdac_dsp_prepare); + +/** + * snd_hdac_dsp_trigger - start / stop DSP loading + * @azx_dev: HD-audio core stream used for DSP loading + * @start: trigger start or stop + */ +void snd_hdac_dsp_trigger(struct hdac_stream *azx_dev, bool start) +{ + if (start) + snd_hdac_stream_start(azx_dev, true); + else + snd_hdac_stream_stop(azx_dev); +} +EXPORT_SYMBOL_GPL(snd_hdac_dsp_trigger); + +/** + * snd_hdac_dsp_cleanup - clean up the stream from DSP loading to normal + * @azx_dev: HD-audio core stream used for DSP loading + * @dmab: buffer used by DSP loading + */ +void snd_hdac_dsp_cleanup(struct hdac_stream *azx_dev, + struct snd_dma_buffer *dmab) +{ + struct hdac_bus *bus = azx_dev->bus; + + if (!dmab->area || !azx_dev->locked) + return; + + snd_hdac_dsp_lock(azx_dev); + /* reset BDL address */ + snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0); + snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0); + snd_hdac_stream_writel(azx_dev, SD_CTL, 0); + azx_dev->bufsize = 0; + azx_dev->period_bytes = 0; + azx_dev->format_val = 0; + + bus->io_ops->dma_free_pages(bus, dmab); + dmab->area = NULL; + + spin_lock_irq(&bus->reg_lock); + azx_dev->locked = false; + spin_unlock_irq(&bus->reg_lock); + snd_hdac_dsp_unlock(azx_dev); +} +EXPORT_SYMBOL_GPL(snd_hdac_dsp_cleanup); +#endif /* CONFIG_SND_HDA_DSP_LOADER */ diff --git a/sound/hda/hdac_sysfs.c b/sound/hda/hdac_sysfs.c new file mode 100644 index 000000000..fb2aa3449 --- /dev/null +++ b/sound/hda/hdac_sysfs.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sysfs support for HD-audio core device + */ + +#include +#include +#include +#include +#include +#include "local.h" + +struct hdac_widget_tree { + struct kobject *root; + struct kobject *afg; + struct kobject **nodes; +}; + +#define CODEC_ATTR(type) \ +static ssize_t type##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct hdac_device *codec = dev_to_hdac_dev(dev); \ + return sprintf(buf, "0x%x\n", codec->type); \ +} \ +static DEVICE_ATTR_RO(type) + +#define CODEC_ATTR_STR(type) \ +static ssize_t type##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct hdac_device *codec = dev_to_hdac_dev(dev); \ + return sprintf(buf, "%s\n", \ + codec->type ? codec->type : ""); \ +} \ +static DEVICE_ATTR_RO(type) + +CODEC_ATTR(type); +CODEC_ATTR(vendor_id); +CODEC_ATTR(subsystem_id); +CODEC_ATTR(revision_id); +CODEC_ATTR(afg); +CODEC_ATTR(mfg); +CODEC_ATTR_STR(vendor_name); +CODEC_ATTR_STR(chip_name); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return snd_hdac_codec_modalias(dev_to_hdac_dev(dev), buf, 256); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *hdac_dev_attrs[] = { + &dev_attr_type.attr, + &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_modalias.attr, + NULL +}; + +static struct attribute_group hdac_dev_attr_group = { + .attrs = hdac_dev_attrs, +}; + +const struct attribute_group *hdac_dev_attr_groups[] = { + &hdac_dev_attr_group, + NULL +}; + +/* + * Widget tree sysfs + * + * This is a tree showing the attributes of each widget. It appears like + * /sys/bus/hdaudioC0D0/widgets/04/caps + */ + +struct widget_attribute; + +struct widget_attribute { + struct attribute attr; + ssize_t (*show)(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf); + ssize_t (*store)(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, + const char *buf, size_t count); +}; + +static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp) +{ + struct device *dev = kobj_to_dev(kobj->parent->parent); + int nid; + ssize_t ret; + + ret = kstrtoint(kobj->name, 16, &nid); + if (ret < 0) + return ret; + *codecp = dev_to_hdac_dev(dev); + return nid; +} + +static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct widget_attribute *wid_attr = + container_of(attr, struct widget_attribute, attr); + struct hdac_device *codec; + int nid; + + if (!wid_attr->show) + return -EIO; + nid = get_codec_nid(kobj, &codec); + if (nid < 0) + return nid; + return wid_attr->show(codec, nid, wid_attr, buf); +} + +static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct widget_attribute *wid_attr = + container_of(attr, struct widget_attribute, attr); + struct hdac_device *codec; + int nid; + + if (!wid_attr->store) + return -EIO; + nid = get_codec_nid(kobj, &codec); + if (nid < 0) + return nid; + return wid_attr->store(codec, nid, wid_attr, buf, count); +} + +static const struct sysfs_ops widget_sysfs_ops = { + .show = widget_attr_show, + .store = widget_attr_store, +}; + +static void widget_release(struct kobject *kobj) +{ + kfree(kobj); +} + +static struct kobj_type widget_ktype = { + .release = widget_release, + .sysfs_ops = &widget_sysfs_ops, +}; + +#define WIDGET_ATTR_RO(_name) \ + struct widget_attribute wid_attr_##_name = __ATTR_RO(_name) +#define WIDGET_ATTR_RW(_name) \ + struct widget_attribute wid_attr_##_name = __ATTR_RW(_name) + +static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", get_wcaps(codec, nid)); +} + +static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) + return 0; + return sprintf(buf, "0x%08x\n", + snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP)); +} + +static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + unsigned int val; + + if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) + return 0; + if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val)) + return 0; + return sprintf(buf, "0x%08x\n", val); +} + +static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid) +{ + if (nid == codec->afg || nid == codec->mfg) + return true; + switch (get_wcaps_type(get_wcaps(codec, nid))) { + case AC_WID_AUD_OUT: + case AC_WID_AUD_IN: + return true; + default: + return false; + } +} + +static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + if (!has_pcm_cap(codec, nid)) + return 0; + return sprintf(buf, "0x%08x\n", + snd_hdac_read_parm(codec, nid, AC_PAR_PCM)); +} + +static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + if (!has_pcm_cap(codec, nid)) + return 0; + return sprintf(buf, "0x%08x\n", + snd_hdac_read_parm(codec, nid, AC_PAR_STREAM)); +} + +static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_IN_AMP)) + return 0; + return sprintf(buf, "0x%08x\n", + snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP)); +} + +static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_OUT_AMP)) + return 0; + return sprintf(buf, "0x%08x\n", + snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP)); +} + +static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_POWER)) + return 0; + return sprintf(buf, "0x%08x\n", + snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE)); +} + +static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", + snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP)); +} + +static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid, + struct widget_attribute *attr, char *buf) +{ + hda_nid_t list[32]; + int i, nconns; + ssize_t ret = 0; + + nconns = snd_hdac_get_connections(codec, nid, list, ARRAY_SIZE(list)); + if (nconns <= 0) + return nconns; + for (i = 0; i < nconns; i++) + ret += sprintf(buf + ret, "%s0x%02x", i ? " " : "", list[i]); + ret += sprintf(buf + ret, "\n"); + return ret; +} + +static WIDGET_ATTR_RO(caps); +static WIDGET_ATTR_RO(pin_caps); +static WIDGET_ATTR_RO(pin_cfg); +static WIDGET_ATTR_RO(pcm_caps); +static WIDGET_ATTR_RO(pcm_formats); +static WIDGET_ATTR_RO(amp_in_caps); +static WIDGET_ATTR_RO(amp_out_caps); +static WIDGET_ATTR_RO(power_caps); +static WIDGET_ATTR_RO(gpio_caps); +static WIDGET_ATTR_RO(connections); + +static struct attribute *widget_node_attrs[] = { + &wid_attr_caps.attr, + &wid_attr_pin_caps.attr, + &wid_attr_pin_cfg.attr, + &wid_attr_pcm_caps.attr, + &wid_attr_pcm_formats.attr, + &wid_attr_amp_in_caps.attr, + &wid_attr_amp_out_caps.attr, + &wid_attr_power_caps.attr, + &wid_attr_connections.attr, + NULL, +}; + +static struct attribute *widget_afg_attrs[] = { + &wid_attr_pcm_caps.attr, + &wid_attr_pcm_formats.attr, + &wid_attr_amp_in_caps.attr, + &wid_attr_amp_out_caps.attr, + &wid_attr_power_caps.attr, + &wid_attr_gpio_caps.attr, + NULL, +}; + +static const struct attribute_group widget_node_group = { + .attrs = widget_node_attrs, +}; + +static const struct attribute_group widget_afg_group = { + .attrs = widget_afg_attrs, +}; + +static void free_widget_node(struct kobject *kobj, + const struct attribute_group *group) +{ + if (kobj) { + sysfs_remove_group(kobj, group); + kobject_put(kobj); + } +} + +static void widget_tree_free(struct hdac_device *codec) +{ + struct hdac_widget_tree *tree = codec->widgets; + struct kobject **p; + + if (!tree) + return; + free_widget_node(tree->afg, &widget_afg_group); + if (tree->nodes) { + for (p = tree->nodes; *p; p++) + free_widget_node(*p, &widget_node_group); + kfree(tree->nodes); + } + kobject_put(tree->root); + kfree(tree); + codec->widgets = NULL; +} + +static int add_widget_node(struct kobject *parent, hda_nid_t nid, + const struct attribute_group *group, + struct kobject **res) +{ + struct kobject *kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); + int err; + + if (!kobj) + return -ENOMEM; + kobject_init(kobj, &widget_ktype); + err = kobject_add(kobj, parent, "%02x", nid); + if (err < 0) + return err; + err = sysfs_create_group(kobj, group); + if (err < 0) { + kobject_put(kobj); + return err; + } + + *res = kobj; + return 0; +} + +static int widget_tree_create(struct hdac_device *codec) +{ + struct hdac_widget_tree *tree; + int i, err; + hda_nid_t nid; + + tree = codec->widgets = kzalloc(sizeof(*tree), GFP_KERNEL); + if (!tree) + return -ENOMEM; + + tree->root = kobject_create_and_add("widgets", &codec->dev.kobj); + if (!tree->root) + return -ENOMEM; + + tree->nodes = kcalloc(codec->num_nodes + 1, sizeof(*tree->nodes), + GFP_KERNEL); + if (!tree->nodes) + return -ENOMEM; + + for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { + err = add_widget_node(tree->root, nid, &widget_node_group, + &tree->nodes[i]); + if (err < 0) + return err; + } + + if (codec->afg) { + err = add_widget_node(tree->root, codec->afg, + &widget_afg_group, &tree->afg); + if (err < 0) + return err; + } + + kobject_uevent(tree->root, KOBJ_CHANGE); + return 0; +} + +int hda_widget_sysfs_init(struct hdac_device *codec) +{ + int err; + + if (codec->widgets) + return 0; /* already created */ + + err = widget_tree_create(codec); + if (err < 0) { + widget_tree_free(codec); + return err; + } + + return 0; +} + +void hda_widget_sysfs_exit(struct hdac_device *codec) +{ + widget_tree_free(codec); +} + +int hda_widget_sysfs_reinit(struct hdac_device *codec, + hda_nid_t start_nid, int num_nodes) +{ + struct hdac_widget_tree *tree; + hda_nid_t end_nid = start_nid + num_nodes; + hda_nid_t nid; + int i; + + if (!codec->widgets) + return hda_widget_sysfs_init(codec); + + tree = kmemdup(codec->widgets, sizeof(*tree), GFP_KERNEL); + if (!tree) + return -ENOMEM; + + tree->nodes = kcalloc(num_nodes + 1, sizeof(*tree->nodes), GFP_KERNEL); + if (!tree->nodes) { + kfree(tree); + return -ENOMEM; + } + + /* prune non-existing nodes */ + for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { + if (nid < start_nid || nid >= end_nid) + free_widget_node(codec->widgets->nodes[i], + &widget_node_group); + } + + /* add new nodes */ + for (i = 0, nid = start_nid; i < num_nodes; i++, nid++) { + if (nid < codec->start_nid || nid >= codec->end_nid) + add_widget_node(tree->root, nid, &widget_node_group, + &tree->nodes[i]); + else + tree->nodes[i] = + codec->widgets->nodes[nid - codec->start_nid]; + } + + /* replace with the new tree */ + kfree(codec->widgets->nodes); + kfree(codec->widgets); + codec->widgets = tree; + + kobject_uevent(tree->root, KOBJ_CHANGE); + return 0; +} diff --git a/sound/hda/hdmi_chmap.c b/sound/hda/hdmi_chmap.c new file mode 100644 index 000000000..acbe61b8d --- /dev/null +++ b/sound/hda/hdmi_chmap.c @@ -0,0 +1,849 @@ +/* + * HDMI Channel map support helpers + */ + +#include +#include +#include +#include + +/* + * CEA speaker placement: + * + * FLH FCH FRH + * FLW FL FLC FC FRC FR FRW + * + * LFE + * TC + * + * RL RLC RC RRC RR + * + * The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M corresponds to + * CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC. + */ +enum cea_speaker_placement { + FL = (1 << 0), /* Front Left */ + FC = (1 << 1), /* Front Center */ + FR = (1 << 2), /* Front Right */ + FLC = (1 << 3), /* Front Left Center */ + FRC = (1 << 4), /* Front Right Center */ + RL = (1 << 5), /* Rear Left */ + RC = (1 << 6), /* Rear Center */ + RR = (1 << 7), /* Rear Right */ + RLC = (1 << 8), /* Rear Left Center */ + RRC = (1 << 9), /* Rear Right Center */ + LFE = (1 << 10), /* Low Frequency Effect */ + FLW = (1 << 11), /* Front Left Wide */ + FRW = (1 << 12), /* Front Right Wide */ + FLH = (1 << 13), /* Front Left High */ + FCH = (1 << 14), /* Front Center High */ + FRH = (1 << 15), /* Front Right High */ + TC = (1 << 16), /* Top Center */ +}; + +static const char * const cea_speaker_allocation_names[] = { + /* 0 */ "FL/FR", + /* 1 */ "LFE", + /* 2 */ "FC", + /* 3 */ "RL/RR", + /* 4 */ "RC", + /* 5 */ "FLC/FRC", + /* 6 */ "RLC/RRC", + /* 7 */ "FLW/FRW", + /* 8 */ "FLH/FRH", + /* 9 */ "TC", + /* 10 */ "FCH", +}; + +/* + * ELD SA bits in the CEA Speaker Allocation data block + */ +static int eld_speaker_allocation_bits[] = { + [0] = FL | FR, + [1] = LFE, + [2] = FC, + [3] = RL | RR, + [4] = RC, + [5] = FLC | FRC, + [6] = RLC | RRC, + /* the following are not defined in ELD yet */ + [7] = FLW | FRW, + [8] = FLH | FRH, + [9] = TC, + [10] = FCH, +}; + +/* + * ALSA sequence is: + * + * surround40 surround41 surround50 surround51 surround71 + * ch0 front left = = = = + * ch1 front right = = = = + * ch2 rear left = = = = + * ch3 rear right = = = = + * ch4 LFE center center center + * ch5 LFE LFE + * ch6 side left + * ch7 side right + * + * surround71 = {FL, FR, RLC, RRC, FC, LFE, RL, RR} + */ +static int hdmi_channel_mapping[0x32][8] = { + /* stereo */ + [0x00] = { 0x00, 0x11, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 }, + /* 2.1 */ + [0x01] = { 0x00, 0x11, 0x22, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 }, + /* Dolby Surround */ + [0x02] = { 0x00, 0x11, 0x23, 0xf2, 0xf4, 0xf5, 0xf6, 0xf7 }, + /* surround40 */ + [0x08] = { 0x00, 0x11, 0x24, 0x35, 0xf3, 0xf2, 0xf6, 0xf7 }, + /* 4ch */ + [0x03] = { 0x00, 0x11, 0x23, 0x32, 0x44, 0xf5, 0xf6, 0xf7 }, + /* surround41 */ + [0x09] = { 0x00, 0x11, 0x24, 0x35, 0x42, 0xf3, 0xf6, 0xf7 }, + /* surround50 */ + [0x0a] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0xf2, 0xf6, 0xf7 }, + /* surround51 */ + [0x0b] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0x52, 0xf6, 0xf7 }, + /* 7.1 */ + [0x13] = { 0x00, 0x11, 0x26, 0x37, 0x43, 0x52, 0x64, 0x75 }, +}; + +/* + * This is an ordered list! + * + * The preceding ones have better chances to be selected by + * hdmi_channel_allocation(). + */ +static struct hdac_cea_channel_speaker_allocation channel_allocations[] = { +/* channel: 7 6 5 4 3 2 1 0 */ +{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } }, + /* 2.1 */ +{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } }, + /* Dolby Surround */ +{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } }, + /* surround40 */ +{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } }, + /* surround41 */ +{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } }, + /* surround50 */ +{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } }, + /* surround51 */ +{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } }, + /* 6.1 */ +{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } }, + /* surround71 */ +{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } }, + +{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } }, +{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } }, +{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } }, +{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } }, +{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } }, +{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } }, +{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } }, +{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } }, +{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } }, +{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } }, +{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } }, +{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } }, +{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } }, +{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL } }, +}; + +static int hdmi_pin_set_slot_channel(struct hdac_device *codec, + hda_nid_t pin_nid, int asp_slot, int channel) +{ + return snd_hdac_codec_write(codec, pin_nid, 0, + AC_VERB_SET_HDMI_CHAN_SLOT, + (channel << 4) | asp_slot); +} + +static int hdmi_pin_get_slot_channel(struct hdac_device *codec, + hda_nid_t pin_nid, int asp_slot) +{ + return (snd_hdac_codec_read(codec, pin_nid, 0, + AC_VERB_GET_HDMI_CHAN_SLOT, + asp_slot) & 0xf0) >> 4; +} + +static int hdmi_get_channel_count(struct hdac_device *codec, hda_nid_t cvt_nid) +{ + return 1 + snd_hdac_codec_read(codec, cvt_nid, 0, + AC_VERB_GET_CVT_CHAN_COUNT, 0); +} + +static void hdmi_set_channel_count(struct hdac_device *codec, + hda_nid_t cvt_nid, int chs) +{ + if (chs != hdmi_get_channel_count(codec, cvt_nid)) + snd_hdac_codec_write(codec, cvt_nid, 0, + AC_VERB_SET_CVT_CHAN_COUNT, chs - 1); +} + +/* + * Channel mapping routines + */ + +/* + * Compute derived values in channel_allocations[]. + */ +static void init_channel_allocations(void) +{ + int i, j; + struct hdac_cea_channel_speaker_allocation *p; + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + p = channel_allocations + i; + p->channels = 0; + p->spk_mask = 0; + for (j = 0; j < ARRAY_SIZE(p->speakers); j++) + if (p->speakers[j]) { + p->channels++; + p->spk_mask |= p->speakers[j]; + } + } +} + +static int get_channel_allocation_order(int ca) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + if (channel_allocations[i].ca_index == ca) + break; + } + return i; +} + +void snd_hdac_print_channel_allocation(int spk_alloc, char *buf, int buflen) +{ + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) { + if (spk_alloc & (1 << i)) + j += scnprintf(buf + j, buflen - j, " %s", + cea_speaker_allocation_names[i]); + } + buf[j] = '\0'; /* necessary when j == 0 */ +} +EXPORT_SYMBOL_GPL(snd_hdac_print_channel_allocation); + +/* + * The transformation takes two steps: + * + * eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask + * spk_mask => (channel_allocations[]) => ai->CA + * + * TODO: it could select the wrong CA from multiple candidates. +*/ +static int hdmi_channel_allocation_spk_alloc_blk(struct hdac_device *codec, + int spk_alloc, int channels) +{ + int i; + int ca = 0; + int spk_mask = 0; + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + + /* + * CA defaults to 0 for basic stereo audio + */ + if (channels <= 2) + return 0; + + /* + * expand ELD's speaker allocation mask + * + * ELD tells the speaker mask in a compact(paired) form, + * expand ELD's notions to match the ones used by Audio InfoFrame. + */ + for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { + if (spk_alloc & (1 << i)) + spk_mask |= eld_speaker_allocation_bits[i]; + } + + /* search for the first working match in the CA table */ + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + if (channels == channel_allocations[i].channels && + (spk_mask & channel_allocations[i].spk_mask) == + channel_allocations[i].spk_mask) { + ca = channel_allocations[i].ca_index; + break; + } + } + + if (!ca) { + /* + * if there was no match, select the regular ALSA channel + * allocation with the matching number of channels + */ + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + if (channels == channel_allocations[i].channels) { + ca = channel_allocations[i].ca_index; + break; + } + } + } + + snd_hdac_print_channel_allocation(spk_alloc, buf, sizeof(buf)); + dev_dbg(&codec->dev, "HDMI: select CA 0x%x for %d-channel allocation: %s\n", + ca, channels, buf); + + return ca; +} + +static void hdmi_debug_channel_mapping(struct hdac_chmap *chmap, + hda_nid_t pin_nid) +{ +#ifdef CONFIG_SND_DEBUG_VERBOSE + int i; + int channel; + + for (i = 0; i < 8; i++) { + channel = chmap->ops.pin_get_slot_channel( + chmap->hdac, pin_nid, i); + dev_dbg(&chmap->hdac->dev, "HDMI: ASP channel %d => slot %d\n", + channel, i); + } +#endif +} + +static void hdmi_std_setup_channel_mapping(struct hdac_chmap *chmap, + hda_nid_t pin_nid, + bool non_pcm, + int ca) +{ + struct hdac_cea_channel_speaker_allocation *ch_alloc; + int i; + int err; + int order; + int non_pcm_mapping[8]; + + order = get_channel_allocation_order(ca); + ch_alloc = &channel_allocations[order]; + + if (hdmi_channel_mapping[ca][1] == 0) { + int hdmi_slot = 0; + /* fill actual channel mappings in ALSA channel (i) order */ + for (i = 0; i < ch_alloc->channels; i++) { + while (!WARN_ON(hdmi_slot >= 8) && + !ch_alloc->speakers[7 - hdmi_slot]) + hdmi_slot++; /* skip zero slots */ + + hdmi_channel_mapping[ca][i] = (i << 4) | hdmi_slot++; + } + /* fill the rest of the slots with ALSA channel 0xf */ + for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++) + if (!ch_alloc->speakers[7 - hdmi_slot]) + hdmi_channel_mapping[ca][i++] = (0xf << 4) | hdmi_slot; + } + + if (non_pcm) { + for (i = 0; i < ch_alloc->channels; i++) + non_pcm_mapping[i] = (i << 4) | i; + for (; i < 8; i++) + non_pcm_mapping[i] = (0xf << 4) | i; + } + + for (i = 0; i < 8; i++) { + int slotsetup = non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i]; + int hdmi_slot = slotsetup & 0x0f; + int channel = (slotsetup & 0xf0) >> 4; + + err = chmap->ops.pin_set_slot_channel(chmap->hdac, + pin_nid, hdmi_slot, channel); + if (err) { + dev_dbg(&chmap->hdac->dev, "HDMI: channel mapping failed\n"); + break; + } + } +} + +struct channel_map_table { + unsigned char map; /* ALSA API channel map position */ + int spk_mask; /* speaker position bit mask */ +}; + +static struct channel_map_table map_tables[] = { + { SNDRV_CHMAP_FL, FL }, + { SNDRV_CHMAP_FR, FR }, + { SNDRV_CHMAP_RL, RL }, + { SNDRV_CHMAP_RR, RR }, + { SNDRV_CHMAP_LFE, LFE }, + { SNDRV_CHMAP_FC, FC }, + { SNDRV_CHMAP_RLC, RLC }, + { SNDRV_CHMAP_RRC, RRC }, + { SNDRV_CHMAP_RC, RC }, + { SNDRV_CHMAP_FLC, FLC }, + { SNDRV_CHMAP_FRC, FRC }, + { SNDRV_CHMAP_TFL, FLH }, + { SNDRV_CHMAP_TFR, FRH }, + { SNDRV_CHMAP_FLW, FLW }, + { SNDRV_CHMAP_FRW, FRW }, + { SNDRV_CHMAP_TC, TC }, + { SNDRV_CHMAP_TFC, FCH }, + {} /* terminator */ +}; + +/* from ALSA API channel position to speaker bit mask */ +int snd_hdac_chmap_to_spk_mask(unsigned char c) +{ + struct channel_map_table *t = map_tables; + + for (; t->map; t++) { + if (t->map == c) + return t->spk_mask; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_chmap_to_spk_mask); + +/* from ALSA API channel position to CEA slot */ +static int to_cea_slot(int ordered_ca, unsigned char pos) +{ + int mask = snd_hdac_chmap_to_spk_mask(pos); + int i; + + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (ordered_ca >= ARRAY_SIZE(channel_allocations)) + return -1; + + if (mask) { + for (i = 0; i < 8; i++) { + if (channel_allocations[ordered_ca].speakers[7 - i] == mask) + return i; + } + } + + return -1; +} + +/* from speaker bit mask to ALSA API channel position */ +int snd_hdac_spk_to_chmap(int spk) +{ + struct channel_map_table *t = map_tables; + + for (; t->map; t++) { + if (t->spk_mask == spk) + return t->map; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_spk_to_chmap); + +/* from CEA slot to ALSA API channel position */ +static int from_cea_slot(int ordered_ca, unsigned char slot) +{ + int mask; + + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (slot >= 8) + return 0; + + mask = channel_allocations[ordered_ca].speakers[7 - slot]; + + return snd_hdac_spk_to_chmap(mask); +} + +/* get the CA index corresponding to the given ALSA API channel map */ +static int hdmi_manual_channel_allocation(int chs, unsigned char *map) +{ + int i, spks = 0, spk_mask = 0; + + for (i = 0; i < chs; i++) { + int mask = snd_hdac_chmap_to_spk_mask(map[i]); + + if (mask) { + spk_mask |= mask; + spks++; + } + } + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + if ((chs == channel_allocations[i].channels || + spks == channel_allocations[i].channels) && + (spk_mask & channel_allocations[i].spk_mask) == + channel_allocations[i].spk_mask) + return channel_allocations[i].ca_index; + } + return -1; +} + +/* set up the channel slots for the given ALSA API channel map */ +static int hdmi_manual_setup_channel_mapping(struct hdac_chmap *chmap, + hda_nid_t pin_nid, + int chs, unsigned char *map, + int ca) +{ + int ordered_ca = get_channel_allocation_order(ca); + int alsa_pos, hdmi_slot; + int assignments[8] = {[0 ... 7] = 0xf}; + + for (alsa_pos = 0; alsa_pos < chs; alsa_pos++) { + + hdmi_slot = to_cea_slot(ordered_ca, map[alsa_pos]); + + if (hdmi_slot < 0) + continue; /* unassigned channel */ + + assignments[hdmi_slot] = alsa_pos; + } + + for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++) { + int err; + + err = chmap->ops.pin_set_slot_channel(chmap->hdac, + pin_nid, hdmi_slot, assignments[hdmi_slot]); + if (err) + return -EINVAL; + } + return 0; +} + +/* store ALSA API channel map from the current default map */ +static void hdmi_setup_fake_chmap(unsigned char *map, int ca) +{ + int i; + int ordered_ca = get_channel_allocation_order(ca); + + for (i = 0; i < 8; i++) { + if (ordered_ca < ARRAY_SIZE(channel_allocations) && + i < channel_allocations[ordered_ca].channels) + map[i] = from_cea_slot(ordered_ca, hdmi_channel_mapping[ca][i] & 0x0f); + else + map[i] = 0; + } +} + +void snd_hdac_setup_channel_mapping(struct hdac_chmap *chmap, + hda_nid_t pin_nid, bool non_pcm, int ca, + int channels, unsigned char *map, + bool chmap_set) +{ + if (!non_pcm && chmap_set) { + hdmi_manual_setup_channel_mapping(chmap, pin_nid, + channels, map, ca); + } else { + hdmi_std_setup_channel_mapping(chmap, pin_nid, non_pcm, ca); + hdmi_setup_fake_chmap(map, ca); + } + + hdmi_debug_channel_mapping(chmap, pin_nid); +} +EXPORT_SYMBOL_GPL(snd_hdac_setup_channel_mapping); + +int snd_hdac_get_active_channels(int ca) +{ + int ordered_ca = get_channel_allocation_order(ca); + + /* Add sanity check to pass klockwork check. + * This should never happen. + */ + if (ordered_ca >= ARRAY_SIZE(channel_allocations)) + ordered_ca = 0; + + return channel_allocations[ordered_ca].channels; +} +EXPORT_SYMBOL_GPL(snd_hdac_get_active_channels); + +struct hdac_cea_channel_speaker_allocation *snd_hdac_get_ch_alloc_from_ca(int ca) +{ + return &channel_allocations[get_channel_allocation_order(ca)]; +} +EXPORT_SYMBOL_GPL(snd_hdac_get_ch_alloc_from_ca); + +int snd_hdac_channel_allocation(struct hdac_device *hdac, int spk_alloc, + int channels, bool chmap_set, bool non_pcm, unsigned char *map) +{ + int ca; + + if (!non_pcm && chmap_set) + ca = hdmi_manual_channel_allocation(channels, map); + else + ca = hdmi_channel_allocation_spk_alloc_blk(hdac, + spk_alloc, channels); + + if (ca < 0) + ca = 0; + + return ca; +} +EXPORT_SYMBOL_GPL(snd_hdac_channel_allocation); + +/* + * ALSA API channel-map control callbacks + */ +static int hdmi_chmap_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct hdac_chmap *chmap = info->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chmap->channels_max; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SNDRV_CHMAP_LAST; + return 0; +} + +static int hdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap, + struct hdac_cea_channel_speaker_allocation *cap, int channels) +{ + /* 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 void hdmi_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap, + struct hdac_cea_channel_speaker_allocation *cap, + unsigned int *chmap, int channels) +{ + int count = 0; + int c; + + for (c = 7; c >= 0; c--) { + int spk = cap->speakers[c]; + + if (!spk) + continue; + + chmap[count++] = snd_hdac_spk_to_chmap(spk); + } + + WARN_ON(count != channels); +} + +static int spk_mask_from_spk_alloc(int spk_alloc) +{ + int i; + int spk_mask = eld_speaker_allocation_bits[0]; + + for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { + if (spk_alloc & (1 << i)) + spk_mask |= eld_speaker_allocation_bits[i]; + } + + return spk_mask; +} + +static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct hdac_chmap *chmap = info->private_data; + int pcm_idx = kcontrol->private_value; + unsigned int __user *dst; + int chs, count = 0; + unsigned long max_chs; + int type; + int spk_alloc, spk_mask; + + if (size < 8) + return -ENOMEM; + if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) + return -EFAULT; + size -= 8; + dst = tlv + 2; + + spk_alloc = chmap->ops.get_spk_alloc(chmap->hdac, pcm_idx); + spk_mask = spk_mask_from_spk_alloc(spk_alloc); + + max_chs = hweight_long(spk_mask); + + for (chs = 2; chs <= max_chs; chs++) { + int i; + struct hdac_cea_channel_speaker_allocation *cap; + + cap = channel_allocations; + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) { + int chs_bytes = chs * 4; + unsigned int tlv_chmap[8]; + + if (cap->channels != chs) + continue; + + if (!(cap->spk_mask == (spk_mask & cap->spk_mask))) + continue; + + type = chmap->ops.chmap_cea_alloc_validate_get_type( + chmap, cap, chs); + if (type < 0) + return -ENODEV; + if (size < 8) + return -ENOMEM; + + if (put_user(type, dst) || + put_user(chs_bytes, dst + 1)) + return -EFAULT; + + dst += 2; + size -= 8; + count += 8; + + if (size < chs_bytes) + return -ENOMEM; + + size -= chs_bytes; + count += chs_bytes; + chmap->ops.cea_alloc_to_tlv_chmap(chmap, cap, + tlv_chmap, chs); + + if (copy_to_user(dst, tlv_chmap, chs_bytes)) + return -EFAULT; + dst += chs; + } + } + + if (put_user(count, tlv + 1)) + return -EFAULT; + + return 0; +} + +static int hdmi_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct hdac_chmap *chmap = info->private_data; + int pcm_idx = kcontrol->private_value; + unsigned char pcm_chmap[8]; + int i; + + memset(pcm_chmap, 0, sizeof(pcm_chmap)); + chmap->ops.get_chmap(chmap->hdac, pcm_idx, pcm_chmap); + + for (i = 0; i < ARRAY_SIZE(pcm_chmap); i++) + ucontrol->value.integer.value[i] = pcm_chmap[i]; + + return 0; +} + +static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct hdac_chmap *hchmap = info->private_data; + int pcm_idx = kcontrol->private_value; + unsigned int ctl_idx; + struct snd_pcm_substream *substream; + unsigned char chmap[8], per_pin_chmap[8]; + int i, err, ca, prepared = 0; + + /* No monitor is connected in dyn_pcm_assign. + * It's invalid to setup the chmap + */ + if (!hchmap->ops.is_pcm_attached(hchmap->hdac, pcm_idx)) + return 0; + + ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + substream = snd_pcm_chmap_substream(info, ctl_idx); + if (!substream || !substream->runtime) + return 0; /* just for avoiding error from alsactl restore */ + switch (substream->runtime->status->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + break; + case SNDRV_PCM_STATE_PREPARED: + prepared = 1; + break; + default: + return -EBUSY; + } + memset(chmap, 0, sizeof(chmap)); + for (i = 0; i < ARRAY_SIZE(chmap); i++) + chmap[i] = ucontrol->value.integer.value[i]; + + hchmap->ops.get_chmap(hchmap->hdac, pcm_idx, per_pin_chmap); + if (!memcmp(chmap, per_pin_chmap, sizeof(chmap))) + return 0; + ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap); + if (ca < 0) + return -EINVAL; + if (hchmap->ops.chmap_validate) { + err = hchmap->ops.chmap_validate(hchmap, ca, + ARRAY_SIZE(chmap), chmap); + if (err) + return err; + } + + hchmap->ops.set_chmap(hchmap->hdac, pcm_idx, chmap, prepared); + + return 0; +} + +static const struct hdac_chmap_ops chmap_ops = { + .chmap_cea_alloc_validate_get_type = hdmi_chmap_cea_alloc_validate_get_type, + .cea_alloc_to_tlv_chmap = hdmi_cea_alloc_to_tlv_chmap, + .pin_get_slot_channel = hdmi_pin_get_slot_channel, + .pin_set_slot_channel = hdmi_pin_set_slot_channel, + .set_channel_count = hdmi_set_channel_count, +}; + +void snd_hdac_register_chmap_ops(struct hdac_device *hdac, + struct hdac_chmap *chmap) +{ + chmap->ops = chmap_ops; + chmap->hdac = hdac; + init_channel_allocations(); +} +EXPORT_SYMBOL_GPL(snd_hdac_register_chmap_ops); + +int snd_hdac_add_chmap_ctls(struct snd_pcm *pcm, int pcm_idx, + struct hdac_chmap *hchmap) +{ + struct snd_pcm_chmap *chmap; + struct snd_kcontrol *kctl; + int err, i; + + err = snd_pcm_add_chmap_ctls(pcm, + SNDRV_PCM_STREAM_PLAYBACK, + NULL, 0, pcm_idx, &chmap); + if (err < 0) + return err; + /* override handlers */ + chmap->private_data = hchmap; + kctl = chmap->kctl; + for (i = 0; i < kctl->count; i++) + kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_WRITE; + kctl->info = hdmi_chmap_ctl_info; + kctl->get = hdmi_chmap_ctl_get; + kctl->put = hdmi_chmap_ctl_put; + kctl->tlv.c = hdmi_chmap_ctl_tlv; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_add_chmap_ctls); diff --git a/sound/hda/local.h b/sound/hda/local.h new file mode 100644 index 000000000..877631e39 --- /dev/null +++ b/sound/hda/local.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Local helper macros and functions for HD-audio core drivers + */ + +#ifndef __HDAC_LOCAL_H +#define __HDAC_LOCAL_H + +#define get_wcaps(codec, nid) \ + snd_hdac_read_parm(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) + +/* 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) * 2; + + return chans; +} + +extern const struct attribute_group *hdac_dev_attr_groups[]; +int hda_widget_sysfs_init(struct hdac_device *codec); +int hda_widget_sysfs_reinit(struct hdac_device *codec, hda_nid_t start_nid, + int num_nodes); +void hda_widget_sysfs_exit(struct hdac_device *codec); + +#endif /* __HDAC_LOCAL_H */ diff --git a/sound/hda/trace.c b/sound/hda/trace.c new file mode 100644 index 000000000..ca2d6bd94 --- /dev/null +++ b/sound/hda/trace.c @@ -0,0 +1,6 @@ +/* + * tracepoint definitions for HD-audio core drivers + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/sound/hda/trace.h b/sound/hda/trace.h new file mode 100644 index 000000000..70af6c815 --- /dev/null +++ b/sound/hda/trace.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM hda + +#if !defined(__HDAC_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __HDAC_TRACE_H + +#include +#include +#include + +#ifndef HDAC_MSG_MAX +#define HDAC_MSG_MAX 500 +#endif + +struct hdac_bus; +struct hdac_codec; + +TRACE_EVENT(hda_send_cmd, + TP_PROTO(struct hdac_bus *bus, unsigned int cmd), + TP_ARGS(bus, cmd), + TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)), + TP_fast_assign( + snprintf(__get_str(msg), HDAC_MSG_MAX, + "[%s:%d] val=0x%08x", + dev_name((bus)->dev), (cmd) >> 28, cmd); + ), + TP_printk("%s", __get_str(msg)) +); + +TRACE_EVENT(hda_get_response, + TP_PROTO(struct hdac_bus *bus, unsigned int addr, unsigned int res), + TP_ARGS(bus, addr, res), + TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)), + TP_fast_assign( + snprintf(__get_str(msg), HDAC_MSG_MAX, + "[%s:%d] val=0x%08x", + dev_name((bus)->dev), addr, res); + ), + TP_printk("%s", __get_str(msg)) +); + +TRACE_EVENT(hda_unsol_event, + TP_PROTO(struct hdac_bus *bus, u32 res, u32 res_ex), + TP_ARGS(bus, res, res_ex), + TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)), + TP_fast_assign( + snprintf(__get_str(msg), HDAC_MSG_MAX, + "[%s:%d] res=0x%08x, res_ex=0x%08x", + dev_name((bus)->dev), res_ex & 0x0f, res, res_ex); + ), + TP_printk("%s", __get_str(msg)) +); + +DECLARE_EVENT_CLASS(hdac_stream, + TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev), + + TP_ARGS(bus, azx_dev), + + TP_STRUCT__entry( + __field(unsigned char, stream_tag) + ), + + TP_fast_assign( + __entry->stream_tag = (azx_dev)->stream_tag; + ), + + TP_printk("stream_tag: %d", __entry->stream_tag) +); + +DEFINE_EVENT(hdac_stream, snd_hdac_stream_start, + TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev), + TP_ARGS(bus, azx_dev) +); + +DEFINE_EVENT(hdac_stream, snd_hdac_stream_stop, + TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev), + TP_ARGS(bus, azx_dev) +); + +#endif /* __HDAC_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/sound/i2c/Makefile b/sound/i2c/Makefile new file mode 100644 index 000000000..09978855e --- /dev/null +++ b/sound/i2c/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-i2c-objs := i2c.o +snd-cs8427-objs := cs8427.o +snd-tea6330t-objs := tea6330t.o + +obj-$(CONFIG_SND) += other/ + +# Toplevel Module Dependency +obj-$(CONFIG_SND_INTERWAVE_STB) += snd-tea6330t.o snd-i2c.o +obj-$(CONFIG_SND_ICE1712) += snd-cs8427.o snd-i2c.o +obj-$(CONFIG_SND_ICE1724) += snd-i2c.o diff --git a/sound/i2c/cs8427.c b/sound/i2c/cs8427.c new file mode 100644 index 000000000..8afa2f888 --- /dev/null +++ b/sound/i2c/cs8427.c @@ -0,0 +1,623 @@ +/* + * Routines for control of the CS8427 via i2c bus + * IEC958 (S/PDIF) receiver & transmitter by Cirrus Logic + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void snd_cs8427_reset(struct snd_i2c_device *cs8427); + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("IEC958 (S/PDIF) receiver & transmitter by Cirrus Logic"); +MODULE_LICENSE("GPL"); + +#define CS8427_ADDR (0x20>>1) /* fixed address */ + +struct cs8427_stream { + struct snd_pcm_substream *substream; + char hw_status[24]; /* hardware status */ + char def_status[24]; /* default status */ + char pcm_status[24]; /* PCM private status */ + char hw_udata[32]; + struct snd_kcontrol *pcm_ctl; +}; + +struct cs8427 { + unsigned char regmap[0x14]; /* map of first 1 + 13 registers */ + unsigned int rate; + unsigned int reset_timeout; + struct cs8427_stream playback; + struct cs8427_stream capture; +}; + +int snd_cs8427_reg_write(struct snd_i2c_device *device, unsigned char reg, + unsigned char val) +{ + int err; + unsigned char buf[2]; + + buf[0] = reg & 0x7f; + buf[1] = val; + if ((err = snd_i2c_sendbytes(device, buf, 2)) != 2) { + snd_printk(KERN_ERR "unable to send bytes 0x%02x:0x%02x " + "to CS8427 (%i)\n", buf[0], buf[1], err); + return err < 0 ? err : -EIO; + } + return 0; +} + +EXPORT_SYMBOL(snd_cs8427_reg_write); + +static int snd_cs8427_reg_read(struct snd_i2c_device *device, unsigned char reg) +{ + int err; + unsigned char buf; + + if ((err = snd_i2c_sendbytes(device, ®, 1)) != 1) { + snd_printk(KERN_ERR "unable to send register 0x%x byte " + "to CS8427\n", reg); + return err < 0 ? err : -EIO; + } + if ((err = snd_i2c_readbytes(device, &buf, 1)) != 1) { + snd_printk(KERN_ERR "unable to read register 0x%x byte " + "from CS8427\n", reg); + return err < 0 ? err : -EIO; + } + return buf; +} + +static int snd_cs8427_select_corudata(struct snd_i2c_device *device, int udata) +{ + struct cs8427 *chip = device->private_data; + int err; + + udata = udata ? CS8427_BSEL : 0; + if (udata != (chip->regmap[CS8427_REG_CSDATABUF] & udata)) { + chip->regmap[CS8427_REG_CSDATABUF] &= ~CS8427_BSEL; + chip->regmap[CS8427_REG_CSDATABUF] |= udata; + err = snd_cs8427_reg_write(device, CS8427_REG_CSDATABUF, + chip->regmap[CS8427_REG_CSDATABUF]); + if (err < 0) + return err; + } + return 0; +} + +static int snd_cs8427_send_corudata(struct snd_i2c_device *device, + int udata, + unsigned char *ndata, + int count) +{ + struct cs8427 *chip = device->private_data; + char *hw_data = udata ? + chip->playback.hw_udata : chip->playback.hw_status; + unsigned char data[32]; + int err, idx; + + if (!memcmp(hw_data, ndata, count)) + return 0; + if ((err = snd_cs8427_select_corudata(device, udata)) < 0) + return err; + memcpy(hw_data, ndata, count); + if (udata) { + memset(data, 0, sizeof(data)); + if (memcmp(hw_data, data, count) == 0) { + chip->regmap[CS8427_REG_UDATABUF] &= ~CS8427_UBMMASK; + chip->regmap[CS8427_REG_UDATABUF] |= CS8427_UBMZEROS | + CS8427_EFTUI; + err = snd_cs8427_reg_write(device, CS8427_REG_UDATABUF, + chip->regmap[CS8427_REG_UDATABUF]); + return err < 0 ? err : 0; + } + } + data[0] = CS8427_REG_AUTOINC | CS8427_REG_CORU_DATABUF; + for (idx = 0; idx < count; idx++) + data[idx + 1] = bitrev8(ndata[idx]); + if (snd_i2c_sendbytes(device, data, count + 1) != count + 1) + return -EIO; + return 1; +} + +static void snd_cs8427_free(struct snd_i2c_device *device) +{ + kfree(device->private_data); +} + +int snd_cs8427_init(struct snd_i2c_bus *bus, + struct snd_i2c_device *device) +{ + static unsigned char initvals1[] = { + CS8427_REG_CONTROL1 | CS8427_REG_AUTOINC, + /* CS8427_REG_CONTROL1: RMCK to OMCK, valid PCM audio, disable mutes, + TCBL=output */ + CS8427_SWCLK | CS8427_TCBLDIR, + /* CS8427_REG_CONTROL2: hold last valid audio sample, RMCK=256*Fs, + normal stereo operation */ + 0x00, + /* CS8427_REG_DATAFLOW: output drivers normal operation, Tx<=serial, + Rx=>serial */ + CS8427_TXDSERIAL | CS8427_SPDAES3RECEIVER, + /* CS8427_REG_CLOCKSOURCE: Run off, CMCK=256*Fs, + output time base = OMCK, input time base = recovered input clock, + recovered input clock source is ILRCK changed to AES3INPUT + (workaround, see snd_cs8427_reset) */ + CS8427_RXDILRCK, + /* CS8427_REG_SERIALINPUT: Serial audio input port data format = I2S, + 24-bit, 64*Fsi */ + CS8427_SIDEL | CS8427_SILRPOL, + /* CS8427_REG_SERIALOUTPUT: Serial audio output port data format + = I2S, 24-bit, 64*Fsi */ + CS8427_SODEL | CS8427_SOLRPOL, + }; + static unsigned char initvals2[] = { + CS8427_REG_RECVERRMASK | CS8427_REG_AUTOINC, + /* CS8427_REG_RECVERRMASK: unmask the input PLL clock, V, confidence, + biphase, parity status bits */ + /* CS8427_UNLOCK | CS8427_V | CS8427_CONF | CS8427_BIP | CS8427_PAR,*/ + 0xff, /* set everything */ + /* CS8427_REG_CSDATABUF: + Registers 32-55 window to CS buffer + Inhibit D->E transfers from overwriting first 5 bytes of CS data. + Inhibit D->E transfers (all) of CS data. + Allow E->F transfer of CS data. + One byte mode; both A/B channels get same written CB data. + A channel info is output to chip's EMPH* pin. */ + CS8427_CBMR | CS8427_DETCI, + /* CS8427_REG_UDATABUF: + Use internal buffer to transmit User (U) data. + Chip's U pin is an output. + Transmit all O's for user data. + Inhibit D->E transfers. + Inhibit E->F transfers. */ + CS8427_UD | CS8427_EFTUI | CS8427_DETUI, + }; + struct cs8427 *chip = device->private_data; + int err; + unsigned char buf[24]; + + snd_i2c_lock(bus); + err = snd_cs8427_reg_read(device, CS8427_REG_ID_AND_VER); + if (err != CS8427_VER8427A) { + /* give second chance */ + snd_printk(KERN_WARNING "invalid CS8427 signature 0x%x: " + "let me try again...\n", err); + err = snd_cs8427_reg_read(device, CS8427_REG_ID_AND_VER); + } + if (err != CS8427_VER8427A) { + snd_i2c_unlock(bus); + snd_printk(KERN_ERR "unable to find CS8427 signature " + "(expected 0x%x, read 0x%x),\n", + CS8427_VER8427A, err); + snd_printk(KERN_ERR " initialization is not completed\n"); + return -EFAULT; + } + /* turn off run bit while making changes to configuration */ + err = snd_cs8427_reg_write(device, CS8427_REG_CLOCKSOURCE, 0x00); + if (err < 0) + goto __fail; + /* send initial values */ + memcpy(chip->regmap + (initvals1[0] & 0x7f), initvals1 + 1, 6); + if ((err = snd_i2c_sendbytes(device, initvals1, 7)) != 7) { + err = err < 0 ? err : -EIO; + goto __fail; + } + /* Turn off CS8427 interrupt stuff that is not used in hardware */ + memset(buf, 0, 7); + /* from address 9 to 15 */ + buf[0] = 9; /* register */ + if ((err = snd_i2c_sendbytes(device, buf, 7)) != 7) + goto __fail; + /* send transfer initialization sequence */ + memcpy(chip->regmap + (initvals2[0] & 0x7f), initvals2 + 1, 3); + if ((err = snd_i2c_sendbytes(device, initvals2, 4)) != 4) { + err = err < 0 ? err : -EIO; + goto __fail; + } + /* write default channel status bytes */ + put_unaligned_le32(SNDRV_PCM_DEFAULT_CON_SPDIF, buf); + memset(buf + 4, 0, 24 - 4); + if (snd_cs8427_send_corudata(device, 0, buf, 24) < 0) + goto __fail; + memcpy(chip->playback.def_status, buf, 24); + memcpy(chip->playback.pcm_status, buf, 24); + snd_i2c_unlock(bus); + + /* turn on run bit and rock'n'roll */ + snd_cs8427_reset(device); + + return 0; + +__fail: + snd_i2c_unlock(bus); + + return err; +} +EXPORT_SYMBOL(snd_cs8427_init); + +int snd_cs8427_create(struct snd_i2c_bus *bus, + unsigned char addr, + unsigned int reset_timeout, + struct snd_i2c_device **r_cs8427) +{ + int err; + struct cs8427 *chip; + struct snd_i2c_device *device; + + err = snd_i2c_device_create(bus, "CS8427", CS8427_ADDR | (addr & 7), + &device); + if (err < 0) + return err; + chip = device->private_data = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) { + snd_i2c_device_free(device); + return -ENOMEM; + } + device->private_free = snd_cs8427_free; + + if (reset_timeout < 1) + reset_timeout = 1; + chip->reset_timeout = reset_timeout; + + err = snd_cs8427_init(bus, device); + if (err) + goto __fail; + +#if 0 // it's nice for read tests + { + char buf[128]; + int xx; + buf[0] = 0x81; + snd_i2c_sendbytes(device, buf, 1); + snd_i2c_readbytes(device, buf, 127); + for (xx = 0; xx < 127; xx++) + printk(KERN_DEBUG "reg[0x%x] = 0x%x\n", xx+1, buf[xx]); + } +#endif + + if (r_cs8427) + *r_cs8427 = device; + return 0; + + __fail: + snd_i2c_device_free(device); + return err < 0 ? err : -EIO; +} + +EXPORT_SYMBOL(snd_cs8427_create); + +/* + * Reset the chip using run bit, also lock PLL using ILRCK and + * put back AES3INPUT. This workaround is described in latest + * CS8427 datasheet, otherwise TXDSERIAL will not work. + */ +static void snd_cs8427_reset(struct snd_i2c_device *cs8427) +{ + struct cs8427 *chip; + unsigned long end_time; + int data, aes3input = 0; + + if (snd_BUG_ON(!cs8427)) + return; + chip = cs8427->private_data; + snd_i2c_lock(cs8427->bus); + if ((chip->regmap[CS8427_REG_CLOCKSOURCE] & CS8427_RXDAES3INPUT) == + CS8427_RXDAES3INPUT) /* AES3 bit is set */ + aes3input = 1; + chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~(CS8427_RUN | CS8427_RXDMASK); + snd_cs8427_reg_write(cs8427, CS8427_REG_CLOCKSOURCE, + chip->regmap[CS8427_REG_CLOCKSOURCE]); + udelay(200); + chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RUN | CS8427_RXDILRCK; + snd_cs8427_reg_write(cs8427, CS8427_REG_CLOCKSOURCE, + chip->regmap[CS8427_REG_CLOCKSOURCE]); + udelay(200); + snd_i2c_unlock(cs8427->bus); + end_time = jiffies + chip->reset_timeout; + while (time_after_eq(end_time, jiffies)) { + snd_i2c_lock(cs8427->bus); + data = snd_cs8427_reg_read(cs8427, CS8427_REG_RECVERRORS); + snd_i2c_unlock(cs8427->bus); + if (!(data & CS8427_UNLOCK)) + break; + schedule_timeout_uninterruptible(1); + } + snd_i2c_lock(cs8427->bus); + chip->regmap[CS8427_REG_CLOCKSOURCE] &= ~CS8427_RXDMASK; + if (aes3input) + chip->regmap[CS8427_REG_CLOCKSOURCE] |= CS8427_RXDAES3INPUT; + snd_cs8427_reg_write(cs8427, CS8427_REG_CLOCKSOURCE, + chip->regmap[CS8427_REG_CLOCKSOURCE]); + snd_i2c_unlock(cs8427->bus); +} + +static int snd_cs8427_in_status_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_cs8427_in_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol); + int data; + + snd_i2c_lock(device->bus); + data = snd_cs8427_reg_read(device, kcontrol->private_value); + snd_i2c_unlock(device->bus); + if (data < 0) + return data; + ucontrol->value.integer.value[0] = data; + return 0; +} + +static int snd_cs8427_qsubcode_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 snd_cs8427_qsubcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol); + unsigned char reg = CS8427_REG_QSUBCODE; + int err; + + snd_i2c_lock(device->bus); + if ((err = snd_i2c_sendbytes(device, ®, 1)) != 1) { + snd_printk(KERN_ERR "unable to send register 0x%x byte " + "to CS8427\n", reg); + snd_i2c_unlock(device->bus); + return err < 0 ? err : -EIO; + } + err = snd_i2c_readbytes(device, ucontrol->value.bytes.data, 10); + if (err != 10) { + snd_printk(KERN_ERR "unable to read Q-subcode bytes " + "from CS8427\n"); + snd_i2c_unlock(device->bus); + return err < 0 ? err : -EIO; + } + snd_i2c_unlock(device->bus); + return 0; +} + +static int snd_cs8427_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_cs8427_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol); + struct cs8427 *chip = device->private_data; + + snd_i2c_lock(device->bus); + memcpy(ucontrol->value.iec958.status, chip->playback.def_status, 24); + snd_i2c_unlock(device->bus); + return 0; +} + +static int snd_cs8427_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol); + struct cs8427 *chip = device->private_data; + unsigned char *status = kcontrol->private_value ? + chip->playback.pcm_status : chip->playback.def_status; + struct snd_pcm_runtime *runtime = chip->playback.substream ? + chip->playback.substream->runtime : NULL; + int err, change; + + snd_i2c_lock(device->bus); + change = memcmp(ucontrol->value.iec958.status, status, 24) != 0; + memcpy(status, ucontrol->value.iec958.status, 24); + if (change && (kcontrol->private_value ? + runtime != NULL : runtime == NULL)) { + err = snd_cs8427_send_corudata(device, 0, status, 24); + if (err < 0) + change = err; + } + snd_i2c_unlock(device->bus); + return change; +} + +static int snd_cs8427_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_cs8427_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, 24); + return 0; +} + +static struct snd_kcontrol_new snd_cs8427_iec958_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_in_status_info, + .name = "IEC958 CS8427 Input Status", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_in_status_get, + .private_value = 15, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_in_status_info, + .name = "IEC958 CS8427 Error Status", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_in_status_get, + .private_value = 16, +}, +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = snd_cs8427_spdif_mask_info, + .get = snd_cs8427_spdif_mask_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = snd_cs8427_spdif_info, + .get = snd_cs8427_spdif_get, + .put = snd_cs8427_spdif_put, + .private_value = 0 +}, +{ + .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_cs8427_spdif_info, + .get = snd_cs8427_spdif_get, + .put = snd_cs8427_spdif_put, + .private_value = 1 +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .info = snd_cs8427_qsubcode_info, + .name = "IEC958 Q-subcode Capture Default", + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .get = snd_cs8427_qsubcode_get +}}; + +int snd_cs8427_iec958_build(struct snd_i2c_device *cs8427, + struct snd_pcm_substream *play_substream, + struct snd_pcm_substream *cap_substream) +{ + struct cs8427 *chip = cs8427->private_data; + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + if (snd_BUG_ON(!play_substream || !cap_substream)) + return -EINVAL; + for (idx = 0; idx < ARRAY_SIZE(snd_cs8427_iec958_controls); idx++) { + kctl = snd_ctl_new1(&snd_cs8427_iec958_controls[idx], cs8427); + if (kctl == NULL) + return -ENOMEM; + kctl->id.device = play_substream->pcm->device; + kctl->id.subdevice = play_substream->number; + err = snd_ctl_add(cs8427->bus->card, kctl); + if (err < 0) + return err; + if (! strcmp(kctl->id.name, + SNDRV_CTL_NAME_IEC958("",PLAYBACK,PCM_STREAM))) + chip->playback.pcm_ctl = kctl; + } + + chip->playback.substream = play_substream; + chip->capture.substream = cap_substream; + if (snd_BUG_ON(!chip->playback.pcm_ctl)) + return -EIO; + return 0; +} + +EXPORT_SYMBOL(snd_cs8427_iec958_build); + +int snd_cs8427_iec958_active(struct snd_i2c_device *cs8427, int active) +{ + struct cs8427 *chip; + + if (snd_BUG_ON(!cs8427)) + return -ENXIO; + chip = cs8427->private_data; + if (active) + memcpy(chip->playback.pcm_status, + chip->playback.def_status, 24); + chip->playback.pcm_ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(cs8427->bus->card, + SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO, + &chip->playback.pcm_ctl->id); + return 0; +} + +EXPORT_SYMBOL(snd_cs8427_iec958_active); + +int snd_cs8427_iec958_pcm(struct snd_i2c_device *cs8427, unsigned int rate) +{ + struct cs8427 *chip; + char *status; + int err, reset; + + if (snd_BUG_ON(!cs8427)) + return -ENXIO; + chip = cs8427->private_data; + status = chip->playback.pcm_status; + snd_i2c_lock(cs8427->bus); + if (status[0] & IEC958_AES0_PROFESSIONAL) { + status[0] &= ~IEC958_AES0_PRO_FS; + switch (rate) { + case 32000: status[0] |= IEC958_AES0_PRO_FS_32000; break; + case 44100: status[0] |= IEC958_AES0_PRO_FS_44100; break; + case 48000: status[0] |= IEC958_AES0_PRO_FS_48000; break; + default: status[0] |= IEC958_AES0_PRO_FS_NOTID; break; + } + } else { + status[3] &= ~IEC958_AES3_CON_FS; + switch (rate) { + case 32000: status[3] |= IEC958_AES3_CON_FS_32000; break; + case 44100: status[3] |= IEC958_AES3_CON_FS_44100; break; + case 48000: status[3] |= IEC958_AES3_CON_FS_48000; break; + } + } + err = snd_cs8427_send_corudata(cs8427, 0, status, 24); + if (err > 0) + snd_ctl_notify(cs8427->bus->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &chip->playback.pcm_ctl->id); + reset = chip->rate != rate; + chip->rate = rate; + snd_i2c_unlock(cs8427->bus); + if (reset) + snd_cs8427_reset(cs8427); + return err < 0 ? err : 0; +} + +EXPORT_SYMBOL(snd_cs8427_iec958_pcm); diff --git a/sound/i2c/i2c.c b/sound/i2c/i2c.c new file mode 100644 index 000000000..c4a232f18 --- /dev/null +++ b/sound/i2c/i2c.c @@ -0,0 +1,340 @@ +/* + * Generic i2c interface for ALSA + * + * (c) 1998 Gerd Knorr + * Modified for the ALSA driver by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Generic i2c interface for ALSA"); +MODULE_LICENSE("GPL"); + +static int snd_i2c_bit_sendbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count); +static int snd_i2c_bit_readbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count); +static int snd_i2c_bit_probeaddr(struct snd_i2c_bus *bus, + unsigned short addr); + +static const struct snd_i2c_ops snd_i2c_bit_ops = { + .sendbytes = snd_i2c_bit_sendbytes, + .readbytes = snd_i2c_bit_readbytes, + .probeaddr = snd_i2c_bit_probeaddr, +}; + +static int snd_i2c_bus_free(struct snd_i2c_bus *bus) +{ + struct snd_i2c_bus *slave; + struct snd_i2c_device *device; + + if (snd_BUG_ON(!bus)) + return -EINVAL; + while (!list_empty(&bus->devices)) { + device = snd_i2c_device(bus->devices.next); + snd_i2c_device_free(device); + } + if (bus->master) + list_del(&bus->buses); + else { + while (!list_empty(&bus->buses)) { + slave = snd_i2c_slave_bus(bus->buses.next); + snd_device_free(bus->card, slave); + } + } + if (bus->private_free) + bus->private_free(bus); + kfree(bus); + return 0; +} + +static int snd_i2c_bus_dev_free(struct snd_device *device) +{ + struct snd_i2c_bus *bus = device->device_data; + return snd_i2c_bus_free(bus); +} + +int snd_i2c_bus_create(struct snd_card *card, const char *name, + struct snd_i2c_bus *master, struct snd_i2c_bus **ri2c) +{ + struct snd_i2c_bus *bus; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_i2c_bus_dev_free, + }; + + *ri2c = NULL; + bus = kzalloc(sizeof(*bus), GFP_KERNEL); + if (bus == NULL) + return -ENOMEM; + mutex_init(&bus->lock_mutex); + INIT_LIST_HEAD(&bus->devices); + INIT_LIST_HEAD(&bus->buses); + bus->card = card; + bus->ops = &snd_i2c_bit_ops; + if (master) { + list_add_tail(&bus->buses, &master->buses); + bus->master = master; + } + strlcpy(bus->name, name, sizeof(bus->name)); + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &ops); + if (err < 0) { + snd_i2c_bus_free(bus); + return err; + } + *ri2c = bus; + return 0; +} + +EXPORT_SYMBOL(snd_i2c_bus_create); + +int snd_i2c_device_create(struct snd_i2c_bus *bus, const char *name, + unsigned char addr, struct snd_i2c_device **rdevice) +{ + struct snd_i2c_device *device; + + *rdevice = NULL; + if (snd_BUG_ON(!bus)) + return -EINVAL; + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (device == NULL) + return -ENOMEM; + device->addr = addr; + strlcpy(device->name, name, sizeof(device->name)); + list_add_tail(&device->list, &bus->devices); + device->bus = bus; + *rdevice = device; + return 0; +} + +EXPORT_SYMBOL(snd_i2c_device_create); + +int snd_i2c_device_free(struct snd_i2c_device *device) +{ + if (device->bus) + list_del(&device->list); + if (device->private_free) + device->private_free(device); + kfree(device); + return 0; +} + +EXPORT_SYMBOL(snd_i2c_device_free); + +int snd_i2c_sendbytes(struct snd_i2c_device *device, unsigned char *bytes, int count) +{ + return device->bus->ops->sendbytes(device, bytes, count); +} + +EXPORT_SYMBOL(snd_i2c_sendbytes); + +int snd_i2c_readbytes(struct snd_i2c_device *device, unsigned char *bytes, int count) +{ + return device->bus->ops->readbytes(device, bytes, count); +} + +EXPORT_SYMBOL(snd_i2c_readbytes); + +int snd_i2c_probeaddr(struct snd_i2c_bus *bus, unsigned short addr) +{ + return bus->ops->probeaddr(bus, addr); +} + +EXPORT_SYMBOL(snd_i2c_probeaddr); + +/* + * bit-operations + */ + +static inline void snd_i2c_bit_hw_start(struct snd_i2c_bus *bus) +{ + if (bus->hw_ops.bit->start) + bus->hw_ops.bit->start(bus); +} + +static inline void snd_i2c_bit_hw_stop(struct snd_i2c_bus *bus) +{ + if (bus->hw_ops.bit->stop) + bus->hw_ops.bit->stop(bus); +} + +static void snd_i2c_bit_direction(struct snd_i2c_bus *bus, int clock, int data) +{ + if (bus->hw_ops.bit->direction) + bus->hw_ops.bit->direction(bus, clock, data); +} + +static void snd_i2c_bit_set(struct snd_i2c_bus *bus, int clock, int data) +{ + bus->hw_ops.bit->setlines(bus, clock, data); +} + +#if 0 +static int snd_i2c_bit_clock(struct snd_i2c_bus *bus) +{ + if (bus->hw_ops.bit->getclock) + return bus->hw_ops.bit->getclock(bus); + return -ENXIO; +} +#endif + +static int snd_i2c_bit_data(struct snd_i2c_bus *bus, int ack) +{ + return bus->hw_ops.bit->getdata(bus, ack); +} + +static void snd_i2c_bit_start(struct snd_i2c_bus *bus) +{ + snd_i2c_bit_hw_start(bus); + snd_i2c_bit_direction(bus, 1, 1); /* SCL - wr, SDA - wr */ + snd_i2c_bit_set(bus, 1, 1); + snd_i2c_bit_set(bus, 1, 0); + snd_i2c_bit_set(bus, 0, 0); +} + +static void snd_i2c_bit_stop(struct snd_i2c_bus *bus) +{ + snd_i2c_bit_set(bus, 0, 0); + snd_i2c_bit_set(bus, 1, 0); + snd_i2c_bit_set(bus, 1, 1); + snd_i2c_bit_hw_stop(bus); +} + +static void snd_i2c_bit_send(struct snd_i2c_bus *bus, int data) +{ + snd_i2c_bit_set(bus, 0, data); + snd_i2c_bit_set(bus, 1, data); + snd_i2c_bit_set(bus, 0, data); +} + +static int snd_i2c_bit_ack(struct snd_i2c_bus *bus) +{ + int ack; + + snd_i2c_bit_set(bus, 0, 1); + snd_i2c_bit_set(bus, 1, 1); + snd_i2c_bit_direction(bus, 1, 0); /* SCL - wr, SDA - rd */ + ack = snd_i2c_bit_data(bus, 1); + snd_i2c_bit_direction(bus, 1, 1); /* SCL - wr, SDA - wr */ + snd_i2c_bit_set(bus, 0, 1); + return ack ? -EIO : 0; +} + +static int snd_i2c_bit_sendbyte(struct snd_i2c_bus *bus, unsigned char data) +{ + int i, err; + + for (i = 7; i >= 0; i--) + snd_i2c_bit_send(bus, !!(data & (1 << i))); + err = snd_i2c_bit_ack(bus); + if (err < 0) + return err; + return 0; +} + +static int snd_i2c_bit_readbyte(struct snd_i2c_bus *bus, int last) +{ + int i; + unsigned char data = 0; + + snd_i2c_bit_set(bus, 0, 1); + snd_i2c_bit_direction(bus, 1, 0); /* SCL - wr, SDA - rd */ + for (i = 7; i >= 0; i--) { + snd_i2c_bit_set(bus, 1, 1); + if (snd_i2c_bit_data(bus, 0)) + data |= (1 << i); + snd_i2c_bit_set(bus, 0, 1); + } + snd_i2c_bit_direction(bus, 1, 1); /* SCL - wr, SDA - wr */ + snd_i2c_bit_send(bus, !!last); + return data; +} + +static int snd_i2c_bit_sendbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count) +{ + struct snd_i2c_bus *bus = device->bus; + int err, res = 0; + + if (device->flags & SND_I2C_DEVICE_ADDRTEN) + return -EIO; /* not yet implemented */ + snd_i2c_bit_start(bus); + err = snd_i2c_bit_sendbyte(bus, device->addr << 1); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + while (count-- > 0) { + err = snd_i2c_bit_sendbyte(bus, *bytes++); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + res++; + } + snd_i2c_bit_stop(bus); + return res; +} + +static int snd_i2c_bit_readbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count) +{ + struct snd_i2c_bus *bus = device->bus; + int err, res = 0; + + if (device->flags & SND_I2C_DEVICE_ADDRTEN) + return -EIO; /* not yet implemented */ + snd_i2c_bit_start(bus); + err = snd_i2c_bit_sendbyte(bus, (device->addr << 1) | 1); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + while (count-- > 0) { + err = snd_i2c_bit_readbyte(bus, count == 0); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + *bytes++ = (unsigned char)err; + res++; + } + snd_i2c_bit_stop(bus); + return res; +} + +static int snd_i2c_bit_probeaddr(struct snd_i2c_bus *bus, unsigned short addr) +{ + int err; + + if (addr & 0x8000) /* 10-bit address */ + return -EIO; /* not yet implemented */ + if (addr & 0x7f80) /* invalid address */ + return -EINVAL; + snd_i2c_bit_start(bus); + err = snd_i2c_bit_sendbyte(bus, addr << 1); + snd_i2c_bit_stop(bus); + return err; +} diff --git a/sound/i2c/other/Makefile b/sound/i2c/other/Makefile new file mode 100644 index 000000000..1a4ce1236 --- /dev/null +++ b/sound/i2c/other/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2003 by Jaroslav Kysela +# + +snd-ak4114-objs := ak4114.o +snd-ak4117-objs := ak4117.o +snd-ak4113-objs := ak4113.o +snd-ak4xxx-adda-objs := ak4xxx-adda.o +snd-pt2258-objs := pt2258.o + +# Module Dependency +obj-$(CONFIG_SND_PDAUDIOCF) += snd-ak4117.o +obj-$(CONFIG_SND_ICE1712) += snd-ak4xxx-adda.o +obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o snd-ak4113.o snd-ak4xxx-adda.o snd-pt2258.o diff --git a/sound/i2c/other/ak4113.c b/sound/i2c/other/ak4113.c new file mode 100644 index 000000000..4099e6062 --- /dev/null +++ b/sound/i2c/other/ak4113.c @@ -0,0 +1,656 @@ +/* + * Routines for control of the AK4113 via I2C/4-wire serial interface + * IEC958 (S/PDIF) receiver by Asahi Kasei + * Copyright (c) by Jaroslav Kysela + * Copyright (c) by Pavel Hofman + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Pavel Hofman "); +MODULE_DESCRIPTION("AK4113 IEC958 (S/PDIF) receiver by Asahi Kasei"); +MODULE_LICENSE("GPL"); + +#define AK4113_ADDR 0x00 /* fixed address */ + +static void ak4113_stats(struct work_struct *work); +static void ak4113_init_regs(struct ak4113 *chip); + + +static void reg_write(struct ak4113 *ak4113, unsigned char reg, + unsigned char val) +{ + ak4113->write(ak4113->private_data, reg, val); + if (reg < sizeof(ak4113->regmap)) + ak4113->regmap[reg] = val; +} + +static inline unsigned char reg_read(struct ak4113 *ak4113, unsigned char reg) +{ + return ak4113->read(ak4113->private_data, reg); +} + +static void snd_ak4113_free(struct ak4113 *chip) +{ + atomic_inc(&chip->wq_processing); /* don't schedule new work */ + cancel_delayed_work_sync(&chip->work); + kfree(chip); +} + +static int snd_ak4113_dev_free(struct snd_device *device) +{ + struct ak4113 *chip = device->device_data; + snd_ak4113_free(chip); + return 0; +} + +int snd_ak4113_create(struct snd_card *card, ak4113_read_t *read, + ak4113_write_t *write, const unsigned char *pgm, + void *private_data, struct ak4113 **r_ak4113) +{ + struct ak4113 *chip; + int err; + unsigned char reg; + static struct snd_device_ops ops = { + .dev_free = snd_ak4113_dev_free, + }; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + spin_lock_init(&chip->lock); + chip->card = card; + chip->read = read; + chip->write = write; + chip->private_data = private_data; + INIT_DELAYED_WORK(&chip->work, ak4113_stats); + atomic_set(&chip->wq_processing, 0); + mutex_init(&chip->reinit_mutex); + + for (reg = 0; reg < AK4113_WRITABLE_REGS ; reg++) + chip->regmap[reg] = pgm[reg]; + ak4113_init_regs(chip); + + chip->rcs0 = reg_read(chip, AK4113_REG_RCS0) & ~(AK4113_QINT | + AK4113_CINT | AK4113_STC); + chip->rcs1 = reg_read(chip, AK4113_REG_RCS1); + chip->rcs2 = reg_read(chip, AK4113_REG_RCS2); + err = snd_device_new(card, SNDRV_DEV_CODEC, chip, &ops); + if (err < 0) + goto __fail; + + if (r_ak4113) + *r_ak4113 = chip; + return 0; + +__fail: + snd_ak4113_free(chip); + return err; +} +EXPORT_SYMBOL_GPL(snd_ak4113_create); + +void snd_ak4113_reg_write(struct ak4113 *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + if (reg >= AK4113_WRITABLE_REGS) + return; + reg_write(chip, reg, (chip->regmap[reg] & ~mask) | val); +} +EXPORT_SYMBOL_GPL(snd_ak4113_reg_write); + +static void ak4113_init_regs(struct ak4113 *chip) +{ + unsigned char old = chip->regmap[AK4113_REG_PWRDN], reg; + + /* bring the chip to reset state and powerdown state */ + reg_write(chip, AK4113_REG_PWRDN, old & ~(AK4113_RST|AK4113_PWN)); + udelay(200); + /* release reset, but leave powerdown */ + reg_write(chip, AK4113_REG_PWRDN, (old | AK4113_RST) & ~AK4113_PWN); + udelay(200); + for (reg = 1; reg < AK4113_WRITABLE_REGS; reg++) + reg_write(chip, reg, chip->regmap[reg]); + /* release powerdown, everything is initialized now */ + reg_write(chip, AK4113_REG_PWRDN, old | AK4113_RST | AK4113_PWN); +} + +void snd_ak4113_reinit(struct ak4113 *chip) +{ + if (atomic_inc_return(&chip->wq_processing) == 1) + cancel_delayed_work_sync(&chip->work); + mutex_lock(&chip->reinit_mutex); + ak4113_init_regs(chip); + mutex_unlock(&chip->reinit_mutex); + /* bring up statistics / event queing */ + if (atomic_dec_and_test(&chip->wq_processing)) + schedule_delayed_work(&chip->work, HZ / 10); +} +EXPORT_SYMBOL_GPL(snd_ak4113_reinit); + +static unsigned int external_rate(unsigned char rcs1) +{ + switch (rcs1 & (AK4113_FS0|AK4113_FS1|AK4113_FS2|AK4113_FS3)) { + case AK4113_FS_8000HZ: + return 8000; + case AK4113_FS_11025HZ: + return 11025; + case AK4113_FS_16000HZ: + return 16000; + case AK4113_FS_22050HZ: + return 22050; + case AK4113_FS_24000HZ: + return 24000; + case AK4113_FS_32000HZ: + return 32000; + case AK4113_FS_44100HZ: + return 44100; + case AK4113_FS_48000HZ: + return 48000; + case AK4113_FS_64000HZ: + return 64000; + case AK4113_FS_88200HZ: + return 88200; + case AK4113_FS_96000HZ: + return 96000; + case AK4113_FS_176400HZ: + return 176400; + case AK4113_FS_192000HZ: + return 192000; + default: + return 0; + } +} + +static int snd_ak4113_in_error_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 = LONG_MAX; + return 0; +} + +static int snd_ak4113_in_error_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + + spin_lock_irq(&chip->lock); + ucontrol->value.integer.value[0] = + chip->errors[kcontrol->private_value]; + chip->errors[kcontrol->private_value] = 0; + spin_unlock_irq(&chip->lock); + return 0; +} + +#define snd_ak4113_in_bit_info snd_ctl_boolean_mono_info + +static int snd_ak4113_in_bit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + unsigned char reg = kcontrol->private_value & 0xff; + unsigned char bit = (kcontrol->private_value >> 8) & 0xff; + unsigned char inv = (kcontrol->private_value >> 31) & 1; + + ucontrol->value.integer.value[0] = + ((reg_read(chip, reg) & (1 << bit)) ? 1 : 0) ^ inv; + return 0; +} + +static int snd_ak4113_rx_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 = 5; + return 0; +} + +static int snd_ak4113_rx_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = + (AK4113_IPS(chip->regmap[AK4113_REG_IO1])); + return 0; +} + +static int snd_ak4113_rx_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + int change; + u8 old_val; + + spin_lock_irq(&chip->lock); + old_val = chip->regmap[AK4113_REG_IO1]; + change = ucontrol->value.integer.value[0] != AK4113_IPS(old_val); + if (change) + reg_write(chip, AK4113_REG_IO1, + (old_val & (~AK4113_IPS(0xff))) | + (AK4113_IPS(ucontrol->value.integer.value[0]))); + spin_unlock_irq(&chip->lock); + return change; +} + +static int snd_ak4113_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 snd_ak4113_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = external_rate(reg_read(chip, + AK4113_REG_RCS1)); + return 0; +} + +static int snd_ak4113_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_ak4113_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4113_REG_RXCSB_SIZE; i++) + ucontrol->value.iec958.status[i] = reg_read(chip, + AK4113_REG_RXCSB0 + i); + return 0; +} + +static int snd_ak4113_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_ak4113_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, AK4113_REG_RXCSB_SIZE); + return 0; +} + +static int snd_ak4113_spdif_pinfo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xffff; + uinfo->count = 4; + return 0; +} + +static int snd_ak4113_spdif_pget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + unsigned short tmp; + + ucontrol->value.integer.value[0] = 0xf8f2; + ucontrol->value.integer.value[1] = 0x4e1f; + tmp = reg_read(chip, AK4113_REG_Pc0) | + (reg_read(chip, AK4113_REG_Pc1) << 8); + ucontrol->value.integer.value[2] = tmp; + tmp = reg_read(chip, AK4113_REG_Pd0) | + (reg_read(chip, AK4113_REG_Pd1) << 8); + ucontrol->value.integer.value[3] = tmp; + return 0; +} + +static int snd_ak4113_spdif_qinfo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = AK4113_REG_QSUB_SIZE; + return 0; +} + +static int snd_ak4113_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4113 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4113_REG_QSUB_SIZE; i++) + ucontrol->value.bytes.data[i] = reg_read(chip, + AK4113_REG_QSUB_ADDR + i); + return 0; +} + +/* Don't forget to change AK4113_CONTROLS define!!! */ +static struct snd_kcontrol_new snd_ak4113_iec958_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Parity Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_in_error_info, + .get = snd_ak4113_in_error_get, + .private_value = AK4113_PARITY_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_in_error_info, + .get = snd_ak4113_in_error_get, + .private_value = AK4113_V_BIT_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 C-CRC Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_in_error_info, + .get = snd_ak4113_in_error_get, + .private_value = AK4113_CCRC_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-CRC Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_in_error_info, + .get = snd_ak4113_in_error_get, + .private_value = AK4113_QCRC_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 External Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_rate_info, + .get = snd_ak4113_rate_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ak4113_spdif_mask_info, + .get = snd_ak4113_spdif_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 = snd_ak4113_spdif_info, + .get = snd_ak4113_spdif_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Preamble Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_spdif_pinfo, + .get = snd_ak4113_spdif_pget, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_spdif_qinfo, + .get = snd_ak4113_spdif_qget, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Audio", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_in_bit_info, + .get = snd_ak4113_in_bit_get, + .private_value = (1<<31) | (1<<8) | AK4113_REG_RCS0, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Non-PCM Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_in_bit_info, + .get = snd_ak4113_in_bit_get, + .private_value = (0<<8) | AK4113_REG_RCS1, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 DTS Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4113_in_bit_info, + .get = snd_ak4113_in_bit_get, + .private_value = (1<<8) | AK4113_REG_RCS1, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "AK4113 Input Select", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = snd_ak4113_rx_info, + .get = snd_ak4113_rx_get, + .put = snd_ak4113_rx_put, +} +}; + +static void snd_ak4113_proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct ak4113 *ak4113 = entry->private_data; + int reg, val; + /* all ak4113 registers 0x00 - 0x1c */ + for (reg = 0; reg < 0x1d; reg++) { + val = reg_read(ak4113, reg); + snd_iprintf(buffer, "0x%02x = 0x%02x\n", reg, val); + } +} + +static void snd_ak4113_proc_init(struct ak4113 *ak4113) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(ak4113->card, "ak4113", &entry)) + snd_info_set_text_ops(entry, ak4113, snd_ak4113_proc_regs_read); +} + +int snd_ak4113_build(struct ak4113 *ak4113, + struct snd_pcm_substream *cap_substream) +{ + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + if (snd_BUG_ON(!cap_substream)) + return -EINVAL; + ak4113->substream = cap_substream; + for (idx = 0; idx < AK4113_CONTROLS; idx++) { + kctl = snd_ctl_new1(&snd_ak4113_iec958_controls[idx], ak4113); + if (kctl == NULL) + return -ENOMEM; + kctl->id.device = cap_substream->pcm->device; + kctl->id.subdevice = cap_substream->number; + err = snd_ctl_add(ak4113->card, kctl); + if (err < 0) + return err; + ak4113->kctls[idx] = kctl; + } + snd_ak4113_proc_init(ak4113); + /* trigger workq */ + schedule_delayed_work(&ak4113->work, HZ / 10); + return 0; +} +EXPORT_SYMBOL_GPL(snd_ak4113_build); + +int snd_ak4113_external_rate(struct ak4113 *ak4113) +{ + unsigned char rcs1; + + rcs1 = reg_read(ak4113, AK4113_REG_RCS1); + return external_rate(rcs1); +} +EXPORT_SYMBOL_GPL(snd_ak4113_external_rate); + +int snd_ak4113_check_rate_and_errors(struct ak4113 *ak4113, unsigned int flags) +{ + struct snd_pcm_runtime *runtime = + ak4113->substream ? ak4113->substream->runtime : NULL; + unsigned long _flags; + int res = 0; + unsigned char rcs0, rcs1, rcs2; + unsigned char c0, c1; + + rcs1 = reg_read(ak4113, AK4113_REG_RCS1); + if (flags & AK4113_CHECK_NO_STAT) + goto __rate; + rcs0 = reg_read(ak4113, AK4113_REG_RCS0); + rcs2 = reg_read(ak4113, AK4113_REG_RCS2); + spin_lock_irqsave(&ak4113->lock, _flags); + if (rcs0 & AK4113_PAR) + ak4113->errors[AK4113_PARITY_ERRORS]++; + if (rcs0 & AK4113_V) + ak4113->errors[AK4113_V_BIT_ERRORS]++; + if (rcs2 & AK4113_CCRC) + ak4113->errors[AK4113_CCRC_ERRORS]++; + if (rcs2 & AK4113_QCRC) + ak4113->errors[AK4113_QCRC_ERRORS]++; + c0 = (ak4113->rcs0 & (AK4113_QINT | AK4113_CINT | AK4113_STC | + AK4113_AUDION | AK4113_AUTO | AK4113_UNLCK)) ^ + (rcs0 & (AK4113_QINT | AK4113_CINT | AK4113_STC | + AK4113_AUDION | AK4113_AUTO | AK4113_UNLCK)); + c1 = (ak4113->rcs1 & (AK4113_DTSCD | AK4113_NPCM | AK4113_PEM | + AK4113_DAT | 0xf0)) ^ + (rcs1 & (AK4113_DTSCD | AK4113_NPCM | AK4113_PEM | + AK4113_DAT | 0xf0)); + ak4113->rcs0 = rcs0 & ~(AK4113_QINT | AK4113_CINT | AK4113_STC); + ak4113->rcs1 = rcs1; + ak4113->rcs2 = rcs2; + spin_unlock_irqrestore(&ak4113->lock, _flags); + + if (rcs0 & AK4113_PAR) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[0]->id); + if (rcs0 & AK4113_V) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[1]->id); + if (rcs2 & AK4113_CCRC) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[2]->id); + if (rcs2 & AK4113_QCRC) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[3]->id); + + /* rate change */ + if (c1 & 0xf0) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[4]->id); + + if ((c1 & AK4113_PEM) | (c0 & AK4113_CINT)) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[6]->id); + if (c0 & AK4113_QINT) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[8]->id); + + if (c0 & AK4113_AUDION) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[9]->id); + if (c1 & AK4113_NPCM) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[10]->id); + if (c1 & AK4113_DTSCD) + snd_ctl_notify(ak4113->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4113->kctls[11]->id); + + if (ak4113->change_callback && (c0 | c1) != 0) + ak4113->change_callback(ak4113, c0, c1); + +__rate: + /* compare rate */ + res = external_rate(rcs1); + if (!(flags & AK4113_CHECK_NO_RATE) && runtime && + (runtime->rate != res)) { + snd_pcm_stream_lock_irqsave(ak4113->substream, _flags); + if (snd_pcm_running(ak4113->substream)) { + /*printk(KERN_DEBUG "rate changed (%i <- %i)\n", + * runtime->rate, res); */ + snd_pcm_stop(ak4113->substream, + SNDRV_PCM_STATE_DRAINING); + wake_up(&runtime->sleep); + res = 1; + } + snd_pcm_stream_unlock_irqrestore(ak4113->substream, _flags); + } + return res; +} +EXPORT_SYMBOL_GPL(snd_ak4113_check_rate_and_errors); + +static void ak4113_stats(struct work_struct *work) +{ + struct ak4113 *chip = container_of(work, struct ak4113, work.work); + + if (atomic_inc_return(&chip->wq_processing) == 1) + snd_ak4113_check_rate_and_errors(chip, chip->check_flags); + + if (atomic_dec_and_test(&chip->wq_processing)) + schedule_delayed_work(&chip->work, HZ / 10); +} + +#ifdef CONFIG_PM +void snd_ak4113_suspend(struct ak4113 *chip) +{ + atomic_inc(&chip->wq_processing); /* don't schedule new work */ + cancel_delayed_work_sync(&chip->work); +} +EXPORT_SYMBOL(snd_ak4113_suspend); + +void snd_ak4113_resume(struct ak4113 *chip) +{ + atomic_dec(&chip->wq_processing); + snd_ak4113_reinit(chip); +} +EXPORT_SYMBOL(snd_ak4113_resume); +#endif diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c new file mode 100644 index 000000000..7fb1aeb46 --- /dev/null +++ b/sound/i2c/other/ak4114.c @@ -0,0 +1,641 @@ +/* + * Routines for control of the AK4114 via I2C and 4-wire serial interface + * IEC958 (S/PDIF) receiver by Asahi Kasei + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("AK4114 IEC958 (S/PDIF) receiver by Asahi Kasei"); +MODULE_LICENSE("GPL"); + +#define AK4114_ADDR 0x00 /* fixed address */ + +static void ak4114_stats(struct work_struct *work); +static void ak4114_init_regs(struct ak4114 *chip); + +static void reg_write(struct ak4114 *ak4114, unsigned char reg, unsigned char val) +{ + ak4114->write(ak4114->private_data, reg, val); + if (reg <= AK4114_REG_INT1_MASK) + ak4114->regmap[reg] = val; + else if (reg >= AK4114_REG_TXCSB0 && reg <= AK4114_REG_TXCSB4) + ak4114->txcsb[reg-AK4114_REG_TXCSB0] = val; +} + +static inline unsigned char reg_read(struct ak4114 *ak4114, unsigned char reg) +{ + return ak4114->read(ak4114->private_data, reg); +} + +#if 0 +static void reg_dump(struct ak4114 *ak4114) +{ + int i; + + printk(KERN_DEBUG "AK4114 REG DUMP:\n"); + for (i = 0; i < 0x20; i++) + printk(KERN_DEBUG "reg[%02x] = %02x (%02x)\n", i, reg_read(ak4114, i), i < ARRAY_SIZE(ak4114->regmap) ? ak4114->regmap[i] : 0); +} +#endif + +static void snd_ak4114_free(struct ak4114 *chip) +{ + atomic_inc(&chip->wq_processing); /* don't schedule new work */ + cancel_delayed_work_sync(&chip->work); + kfree(chip); +} + +static int snd_ak4114_dev_free(struct snd_device *device) +{ + struct ak4114 *chip = device->device_data; + snd_ak4114_free(chip); + return 0; +} + +int snd_ak4114_create(struct snd_card *card, + ak4114_read_t *read, ak4114_write_t *write, + const unsigned char pgm[6], const unsigned char txcsb[5], + void *private_data, struct ak4114 **r_ak4114) +{ + struct ak4114 *chip; + int err = 0; + unsigned char reg; + static struct snd_device_ops ops = { + .dev_free = snd_ak4114_dev_free, + }; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + spin_lock_init(&chip->lock); + chip->card = card; + chip->read = read; + chip->write = write; + chip->private_data = private_data; + INIT_DELAYED_WORK(&chip->work, ak4114_stats); + atomic_set(&chip->wq_processing, 0); + mutex_init(&chip->reinit_mutex); + + for (reg = 0; reg < 6; reg++) + chip->regmap[reg] = pgm[reg]; + for (reg = 0; reg < 5; reg++) + chip->txcsb[reg] = txcsb[reg]; + + ak4114_init_regs(chip); + + chip->rcs0 = reg_read(chip, AK4114_REG_RCS0) & ~(AK4114_QINT | AK4114_CINT); + chip->rcs1 = reg_read(chip, AK4114_REG_RCS1); + + if ((err = snd_device_new(card, SNDRV_DEV_CODEC, chip, &ops)) < 0) + goto __fail; + + if (r_ak4114) + *r_ak4114 = chip; + return 0; + + __fail: + snd_ak4114_free(chip); + return err; +} +EXPORT_SYMBOL(snd_ak4114_create); + +void snd_ak4114_reg_write(struct ak4114 *chip, unsigned char reg, unsigned char mask, unsigned char val) +{ + if (reg <= AK4114_REG_INT1_MASK) + reg_write(chip, reg, (chip->regmap[reg] & ~mask) | val); + else if (reg >= AK4114_REG_TXCSB0 && reg <= AK4114_REG_TXCSB4) + reg_write(chip, reg, + (chip->txcsb[reg-AK4114_REG_TXCSB0] & ~mask) | val); +} +EXPORT_SYMBOL(snd_ak4114_reg_write); + +static void ak4114_init_regs(struct ak4114 *chip) +{ + unsigned char old = chip->regmap[AK4114_REG_PWRDN], reg; + + /* bring the chip to reset state and powerdown state */ + reg_write(chip, AK4114_REG_PWRDN, old & ~(AK4114_RST|AK4114_PWN)); + udelay(200); + /* release reset, but leave powerdown */ + reg_write(chip, AK4114_REG_PWRDN, (old | AK4114_RST) & ~AK4114_PWN); + udelay(200); + for (reg = 1; reg < 6; reg++) + reg_write(chip, reg, chip->regmap[reg]); + for (reg = 0; reg < 5; reg++) + reg_write(chip, reg + AK4114_REG_TXCSB0, chip->txcsb[reg]); + /* release powerdown, everything is initialized now */ + reg_write(chip, AK4114_REG_PWRDN, old | AK4114_RST | AK4114_PWN); +} + +void snd_ak4114_reinit(struct ak4114 *chip) +{ + if (atomic_inc_return(&chip->wq_processing) == 1) + cancel_delayed_work_sync(&chip->work); + mutex_lock(&chip->reinit_mutex); + ak4114_init_regs(chip); + mutex_unlock(&chip->reinit_mutex); + /* bring up statistics / event queing */ + if (atomic_dec_and_test(&chip->wq_processing)) + schedule_delayed_work(&chip->work, HZ / 10); +} +EXPORT_SYMBOL(snd_ak4114_reinit); + +static unsigned int external_rate(unsigned char rcs1) +{ + switch (rcs1 & (AK4114_FS0|AK4114_FS1|AK4114_FS2|AK4114_FS3)) { + case AK4114_FS_32000HZ: return 32000; + case AK4114_FS_44100HZ: return 44100; + case AK4114_FS_48000HZ: return 48000; + case AK4114_FS_88200HZ: return 88200; + case AK4114_FS_96000HZ: return 96000; + case AK4114_FS_176400HZ: return 176400; + case AK4114_FS_192000HZ: return 192000; + default: return 0; + } +} + +static int snd_ak4114_in_error_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 = LONG_MAX; + return 0; +} + +static int snd_ak4114_in_error_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + + spin_lock_irq(&chip->lock); + ucontrol->value.integer.value[0] = + chip->errors[kcontrol->private_value]; + chip->errors[kcontrol->private_value] = 0; + spin_unlock_irq(&chip->lock); + return 0; +} + +#define snd_ak4114_in_bit_info snd_ctl_boolean_mono_info + +static int snd_ak4114_in_bit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + unsigned char reg = kcontrol->private_value & 0xff; + unsigned char bit = (kcontrol->private_value >> 8) & 0xff; + unsigned char inv = (kcontrol->private_value >> 31) & 1; + + ucontrol->value.integer.value[0] = ((reg_read(chip, reg) & (1 << bit)) ? 1 : 0) ^ inv; + return 0; +} + +static int snd_ak4114_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 snd_ak4114_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = external_rate(reg_read(chip, AK4114_REG_RCS1)); + return 0; +} + +static int snd_ak4114_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_ak4114_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4114_REG_RXCSB_SIZE; i++) + ucontrol->value.iec958.status[i] = reg_read(chip, AK4114_REG_RXCSB0 + i); + return 0; +} + +static int snd_ak4114_spdif_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4114_REG_TXCSB_SIZE; i++) + ucontrol->value.iec958.status[i] = chip->txcsb[i]; + return 0; +} + +static int snd_ak4114_spdif_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4114_REG_TXCSB_SIZE; i++) + reg_write(chip, AK4114_REG_TXCSB0 + i, ucontrol->value.iec958.status[i]); + return 0; +} + +static int snd_ak4114_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_ak4114_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, AK4114_REG_RXCSB_SIZE); + return 0; +} + +static int snd_ak4114_spdif_pinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xffff; + uinfo->count = 4; + return 0; +} + +static int snd_ak4114_spdif_pget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + unsigned short tmp; + + ucontrol->value.integer.value[0] = 0xf8f2; + ucontrol->value.integer.value[1] = 0x4e1f; + tmp = reg_read(chip, AK4114_REG_Pc0) | (reg_read(chip, AK4114_REG_Pc1) << 8); + ucontrol->value.integer.value[2] = tmp; + tmp = reg_read(chip, AK4114_REG_Pd0) | (reg_read(chip, AK4114_REG_Pd1) << 8); + ucontrol->value.integer.value[3] = tmp; + return 0; +} + +static int snd_ak4114_spdif_qinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = AK4114_REG_QSUB_SIZE; + return 0; +} + +static int snd_ak4114_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4114 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4114_REG_QSUB_SIZE; i++) + ucontrol->value.bytes.data[i] = reg_read(chip, AK4114_REG_QSUB_ADDR + i); + return 0; +} + +/* Don't forget to change AK4114_CONTROLS define!!! */ +static struct snd_kcontrol_new snd_ak4114_iec958_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Parity Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_error_info, + .get = snd_ak4114_in_error_get, + .private_value = AK4114_PARITY_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_error_info, + .get = snd_ak4114_in_error_get, + .private_value = AK4114_V_BIT_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 C-CRC Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_error_info, + .get = snd_ak4114_in_error_get, + .private_value = AK4114_CCRC_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-CRC Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_error_info, + .get = snd_ak4114_in_error_get, + .private_value = AK4114_QCRC_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 External Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_rate_info, + .get = snd_ak4114_rate_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ak4114_spdif_mask_info, + .get = snd_ak4114_spdif_mask_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_spdif_info, + .get = snd_ak4114_spdif_playback_get, + .put = snd_ak4114_spdif_playback_put, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ak4114_spdif_mask_info, + .get = snd_ak4114_spdif_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 = snd_ak4114_spdif_info, + .get = snd_ak4114_spdif_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Preamble Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_spdif_pinfo, + .get = snd_ak4114_spdif_pget, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_spdif_qinfo, + .get = snd_ak4114_spdif_qget, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Audio", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_bit_info, + .get = snd_ak4114_in_bit_get, + .private_value = (1<<31) | (1<<8) | AK4114_REG_RCS0, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Non-PCM Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_bit_info, + .get = snd_ak4114_in_bit_get, + .private_value = (6<<8) | AK4114_REG_RCS0, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 DTS Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_bit_info, + .get = snd_ak4114_in_bit_get, + .private_value = (3<<8) | AK4114_REG_RCS0, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 PPL Lock Status", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4114_in_bit_info, + .get = snd_ak4114_in_bit_get, + .private_value = (1<<31) | (4<<8) | AK4114_REG_RCS0, +} +}; + + +static void snd_ak4114_proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct ak4114 *ak4114 = entry->private_data; + int reg, val; + /* all ak4114 registers 0x00 - 0x1f */ + for (reg = 0; reg < 0x20; reg++) { + val = reg_read(ak4114, reg); + snd_iprintf(buffer, "0x%02x = 0x%02x\n", reg, val); + } +} + +static void snd_ak4114_proc_init(struct ak4114 *ak4114) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(ak4114->card, "ak4114", &entry)) + snd_info_set_text_ops(entry, ak4114, snd_ak4114_proc_regs_read); +} + +int snd_ak4114_build(struct ak4114 *ak4114, + struct snd_pcm_substream *ply_substream, + struct snd_pcm_substream *cap_substream) +{ + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + if (snd_BUG_ON(!cap_substream)) + return -EINVAL; + ak4114->playback_substream = ply_substream; + ak4114->capture_substream = cap_substream; + for (idx = 0; idx < AK4114_CONTROLS; idx++) { + kctl = snd_ctl_new1(&snd_ak4114_iec958_controls[idx], ak4114); + if (kctl == NULL) + return -ENOMEM; + if (strstr(kctl->id.name, "Playback")) { + if (ply_substream == NULL) { + snd_ctl_free_one(kctl); + ak4114->kctls[idx] = NULL; + continue; + } + kctl->id.device = ply_substream->pcm->device; + kctl->id.subdevice = ply_substream->number; + } else { + kctl->id.device = cap_substream->pcm->device; + kctl->id.subdevice = cap_substream->number; + } + err = snd_ctl_add(ak4114->card, kctl); + if (err < 0) + return err; + ak4114->kctls[idx] = kctl; + } + snd_ak4114_proc_init(ak4114); + /* trigger workq */ + schedule_delayed_work(&ak4114->work, HZ / 10); + return 0; +} +EXPORT_SYMBOL(snd_ak4114_build); + +/* notify kcontrols if any parameters are changed */ +static void ak4114_notify(struct ak4114 *ak4114, + unsigned char rcs0, unsigned char rcs1, + unsigned char c0, unsigned char c1) +{ + if (!ak4114->kctls[0]) + return; + + if (rcs0 & AK4114_PAR) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[0]->id); + if (rcs0 & AK4114_V) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[1]->id); + if (rcs1 & AK4114_CCRC) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[2]->id); + if (rcs1 & AK4114_QCRC) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[3]->id); + + /* rate change */ + if (c1 & 0xf0) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[4]->id); + + if ((c0 & AK4114_PEM) | (c0 & AK4114_CINT)) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[9]->id); + if (c0 & AK4114_QINT) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[10]->id); + + if (c0 & AK4114_AUDION) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[11]->id); + if (c0 & AK4114_AUTO) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[12]->id); + if (c0 & AK4114_DTSCD) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[13]->id); + if (c0 & AK4114_UNLCK) + snd_ctl_notify(ak4114->card, SNDRV_CTL_EVENT_MASK_VALUE, + &ak4114->kctls[14]->id); +} + +int snd_ak4114_external_rate(struct ak4114 *ak4114) +{ + unsigned char rcs1; + + rcs1 = reg_read(ak4114, AK4114_REG_RCS1); + return external_rate(rcs1); +} +EXPORT_SYMBOL(snd_ak4114_external_rate); + +int snd_ak4114_check_rate_and_errors(struct ak4114 *ak4114, unsigned int flags) +{ + struct snd_pcm_runtime *runtime = ak4114->capture_substream ? ak4114->capture_substream->runtime : NULL; + unsigned long _flags; + int res = 0; + unsigned char rcs0, rcs1; + unsigned char c0, c1; + + rcs1 = reg_read(ak4114, AK4114_REG_RCS1); + if (flags & AK4114_CHECK_NO_STAT) + goto __rate; + rcs0 = reg_read(ak4114, AK4114_REG_RCS0); + spin_lock_irqsave(&ak4114->lock, _flags); + if (rcs0 & AK4114_PAR) + ak4114->errors[AK4114_PARITY_ERRORS]++; + if (rcs1 & AK4114_V) + ak4114->errors[AK4114_V_BIT_ERRORS]++; + if (rcs1 & AK4114_CCRC) + ak4114->errors[AK4114_CCRC_ERRORS]++; + if (rcs1 & AK4114_QCRC) + ak4114->errors[AK4114_QCRC_ERRORS]++; + c0 = (ak4114->rcs0 & (AK4114_QINT | AK4114_CINT | AK4114_PEM | AK4114_AUDION | AK4114_AUTO | AK4114_UNLCK)) ^ + (rcs0 & (AK4114_QINT | AK4114_CINT | AK4114_PEM | AK4114_AUDION | AK4114_AUTO | AK4114_UNLCK)); + c1 = (ak4114->rcs1 & 0xf0) ^ (rcs1 & 0xf0); + ak4114->rcs0 = rcs0 & ~(AK4114_QINT | AK4114_CINT); + ak4114->rcs1 = rcs1; + spin_unlock_irqrestore(&ak4114->lock, _flags); + + ak4114_notify(ak4114, rcs0, rcs1, c0, c1); + if (ak4114->change_callback && (c0 | c1) != 0) + ak4114->change_callback(ak4114, c0, c1); + + __rate: + /* compare rate */ + res = external_rate(rcs1); + if (!(flags & AK4114_CHECK_NO_RATE) && runtime && runtime->rate != res) { + snd_pcm_stream_lock_irqsave(ak4114->capture_substream, _flags); + if (snd_pcm_running(ak4114->capture_substream)) { + // printk(KERN_DEBUG "rate changed (%i <- %i)\n", runtime->rate, res); + snd_pcm_stop(ak4114->capture_substream, SNDRV_PCM_STATE_DRAINING); + res = 1; + } + snd_pcm_stream_unlock_irqrestore(ak4114->capture_substream, _flags); + } + return res; +} +EXPORT_SYMBOL(snd_ak4114_check_rate_and_errors); + +static void ak4114_stats(struct work_struct *work) +{ + struct ak4114 *chip = container_of(work, struct ak4114, work.work); + + if (atomic_inc_return(&chip->wq_processing) == 1) + snd_ak4114_check_rate_and_errors(chip, chip->check_flags); + if (atomic_dec_and_test(&chip->wq_processing)) + schedule_delayed_work(&chip->work, HZ / 10); +} + +#ifdef CONFIG_PM +void snd_ak4114_suspend(struct ak4114 *chip) +{ + atomic_inc(&chip->wq_processing); /* don't schedule new work */ + cancel_delayed_work_sync(&chip->work); +} +EXPORT_SYMBOL(snd_ak4114_suspend); + +void snd_ak4114_resume(struct ak4114 *chip) +{ + atomic_dec(&chip->wq_processing); + snd_ak4114_reinit(chip); +} +EXPORT_SYMBOL(snd_ak4114_resume); +#endif diff --git a/sound/i2c/other/ak4117.c b/sound/i2c/other/ak4117.c new file mode 100644 index 000000000..b923342ca --- /dev/null +++ b/sound/i2c/other/ak4117.c @@ -0,0 +1,547 @@ +/* + * Routines for control of the AK4117 via 4-wire serial interface + * IEC958 (S/PDIF) receiver by Asahi Kasei + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("AK4117 IEC958 (S/PDIF) receiver by Asahi Kasei"); +MODULE_LICENSE("GPL"); + +#define AK4117_ADDR 0x00 /* fixed address */ + +static void snd_ak4117_timer(struct timer_list *t); + +static void reg_write(struct ak4117 *ak4117, unsigned char reg, unsigned char val) +{ + ak4117->write(ak4117->private_data, reg, val); + if (reg < sizeof(ak4117->regmap)) + ak4117->regmap[reg] = val; +} + +static inline unsigned char reg_read(struct ak4117 *ak4117, unsigned char reg) +{ + return ak4117->read(ak4117->private_data, reg); +} + +#if 0 +static void reg_dump(struct ak4117 *ak4117) +{ + int i; + + printk(KERN_DEBUG "AK4117 REG DUMP:\n"); + for (i = 0; i < 0x1b; i++) + printk(KERN_DEBUG "reg[%02x] = %02x (%02x)\n", i, reg_read(ak4117, i), i < sizeof(ak4117->regmap) ? ak4117->regmap[i] : 0); +} +#endif + +static void snd_ak4117_free(struct ak4117 *chip) +{ + del_timer_sync(&chip->timer); + kfree(chip); +} + +static int snd_ak4117_dev_free(struct snd_device *device) +{ + struct ak4117 *chip = device->device_data; + snd_ak4117_free(chip); + return 0; +} + +int snd_ak4117_create(struct snd_card *card, ak4117_read_t *read, ak4117_write_t *write, + const unsigned char pgm[5], void *private_data, struct ak4117 **r_ak4117) +{ + struct ak4117 *chip; + int err = 0; + unsigned char reg; + static struct snd_device_ops ops = { + .dev_free = snd_ak4117_dev_free, + }; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + spin_lock_init(&chip->lock); + chip->card = card; + chip->read = read; + chip->write = write; + chip->private_data = private_data; + timer_setup(&chip->timer, snd_ak4117_timer, 0); + + for (reg = 0; reg < 5; reg++) + chip->regmap[reg] = pgm[reg]; + snd_ak4117_reinit(chip); + + chip->rcs0 = reg_read(chip, AK4117_REG_RCS0) & ~(AK4117_QINT | AK4117_CINT | AK4117_STC); + chip->rcs1 = reg_read(chip, AK4117_REG_RCS1); + chip->rcs2 = reg_read(chip, AK4117_REG_RCS2); + + if ((err = snd_device_new(card, SNDRV_DEV_CODEC, chip, &ops)) < 0) + goto __fail; + + if (r_ak4117) + *r_ak4117 = chip; + return 0; + + __fail: + snd_ak4117_free(chip); + return err; +} + +void snd_ak4117_reg_write(struct ak4117 *chip, unsigned char reg, unsigned char mask, unsigned char val) +{ + if (reg >= 5) + return; + reg_write(chip, reg, (chip->regmap[reg] & ~mask) | val); +} + +void snd_ak4117_reinit(struct ak4117 *chip) +{ + unsigned char old = chip->regmap[AK4117_REG_PWRDN], reg; + + del_timer(&chip->timer); + chip->init = 1; + /* bring the chip to reset state and powerdown state */ + reg_write(chip, AK4117_REG_PWRDN, 0); + udelay(200); + /* release reset, but leave powerdown */ + reg_write(chip, AK4117_REG_PWRDN, (old | AK4117_RST) & ~AK4117_PWN); + udelay(200); + for (reg = 1; reg < 5; reg++) + reg_write(chip, reg, chip->regmap[reg]); + /* release powerdown, everything is initialized now */ + reg_write(chip, AK4117_REG_PWRDN, old | AK4117_RST | AK4117_PWN); + chip->init = 0; + mod_timer(&chip->timer, 1 + jiffies); +} + +static unsigned int external_rate(unsigned char rcs1) +{ + switch (rcs1 & (AK4117_FS0|AK4117_FS1|AK4117_FS2|AK4117_FS3)) { + case AK4117_FS_32000HZ: return 32000; + case AK4117_FS_44100HZ: return 44100; + case AK4117_FS_48000HZ: return 48000; + case AK4117_FS_88200HZ: return 88200; + case AK4117_FS_96000HZ: return 96000; + case AK4117_FS_176400HZ: return 176400; + case AK4117_FS_192000HZ: return 192000; + default: return 0; + } +} + +static int snd_ak4117_in_error_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 = LONG_MAX; + return 0; +} + +static int snd_ak4117_in_error_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + + spin_lock_irq(&chip->lock); + ucontrol->value.integer.value[0] = + chip->errors[kcontrol->private_value]; + chip->errors[kcontrol->private_value] = 0; + spin_unlock_irq(&chip->lock); + return 0; +} + +#define snd_ak4117_in_bit_info snd_ctl_boolean_mono_info + +static int snd_ak4117_in_bit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + unsigned char reg = kcontrol->private_value & 0xff; + unsigned char bit = (kcontrol->private_value >> 8) & 0xff; + unsigned char inv = (kcontrol->private_value >> 31) & 1; + + ucontrol->value.integer.value[0] = ((reg_read(chip, reg) & (1 << bit)) ? 1 : 0) ^ inv; + return 0; +} + +static int snd_ak4117_rx_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 = 1; + return 0; +} + +static int snd_ak4117_rx_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = (chip->regmap[AK4117_REG_IO] & AK4117_IPS) ? 1 : 0; + return 0; +} + +static int snd_ak4117_rx_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + int change; + u8 old_val; + + spin_lock_irq(&chip->lock); + old_val = chip->regmap[AK4117_REG_IO]; + change = !!ucontrol->value.integer.value[0] != ((old_val & AK4117_IPS) ? 1 : 0); + if (change) + reg_write(chip, AK4117_REG_IO, (old_val & ~AK4117_IPS) | (ucontrol->value.integer.value[0] ? AK4117_IPS : 0)); + spin_unlock_irq(&chip->lock); + return change; +} + +static int snd_ak4117_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 snd_ak4117_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = external_rate(reg_read(chip, AK4117_REG_RCS1)); + return 0; +} + +static int snd_ak4117_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_ak4117_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4117_REG_RXCSB_SIZE; i++) + ucontrol->value.iec958.status[i] = reg_read(chip, AK4117_REG_RXCSB0 + i); + return 0; +} + +static int snd_ak4117_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_ak4117_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, AK4117_REG_RXCSB_SIZE); + return 0; +} + +static int snd_ak4117_spdif_pinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xffff; + uinfo->count = 4; + return 0; +} + +static int snd_ak4117_spdif_pget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + unsigned short tmp; + + ucontrol->value.integer.value[0] = 0xf8f2; + ucontrol->value.integer.value[1] = 0x4e1f; + tmp = reg_read(chip, AK4117_REG_Pc0) | (reg_read(chip, AK4117_REG_Pc1) << 8); + ucontrol->value.integer.value[2] = tmp; + tmp = reg_read(chip, AK4117_REG_Pd0) | (reg_read(chip, AK4117_REG_Pd1) << 8); + ucontrol->value.integer.value[3] = tmp; + return 0; +} + +static int snd_ak4117_spdif_qinfo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = AK4117_REG_QSUB_SIZE; + return 0; +} + +static int snd_ak4117_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct ak4117 *chip = snd_kcontrol_chip(kcontrol); + unsigned i; + + for (i = 0; i < AK4117_REG_QSUB_SIZE; i++) + ucontrol->value.bytes.data[i] = reg_read(chip, AK4117_REG_QSUB_ADDR + i); + return 0; +} + +/* Don't forget to change AK4117_CONTROLS define!!! */ +static struct snd_kcontrol_new snd_ak4117_iec958_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Parity Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_in_error_info, + .get = snd_ak4117_in_error_get, + .private_value = AK4117_PARITY_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_in_error_info, + .get = snd_ak4117_in_error_get, + .private_value = AK4117_V_BIT_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 C-CRC Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_in_error_info, + .get = snd_ak4117_in_error_get, + .private_value = AK4117_CCRC_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-CRC Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_in_error_info, + .get = snd_ak4117_in_error_get, + .private_value = AK4117_QCRC_ERRORS, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 External Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_rate_info, + .get = snd_ak4117_rate_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ak4117_spdif_mask_info, + .get = snd_ak4117_spdif_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 = snd_ak4117_spdif_info, + .get = snd_ak4117_spdif_get, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Preamble Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_spdif_pinfo, + .get = snd_ak4117_spdif_pget, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_spdif_qinfo, + .get = snd_ak4117_spdif_qget, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Audio", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_in_bit_info, + .get = snd_ak4117_in_bit_get, + .private_value = (1<<31) | (3<<8) | AK4117_REG_RCS0, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Non-PCM Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_in_bit_info, + .get = snd_ak4117_in_bit_get, + .private_value = (5<<8) | AK4117_REG_RCS1, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 DTS Bitstream", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ak4117_in_bit_info, + .get = snd_ak4117_in_bit_get, + .private_value = (6<<8) | AK4117_REG_RCS1, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "AK4117 Input Select", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = snd_ak4117_rx_info, + .get = snd_ak4117_rx_get, + .put = snd_ak4117_rx_put, +} +}; + +int snd_ak4117_build(struct ak4117 *ak4117, struct snd_pcm_substream *cap_substream) +{ + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + if (snd_BUG_ON(!cap_substream)) + return -EINVAL; + ak4117->substream = cap_substream; + for (idx = 0; idx < AK4117_CONTROLS; idx++) { + kctl = snd_ctl_new1(&snd_ak4117_iec958_controls[idx], ak4117); + if (kctl == NULL) + return -ENOMEM; + kctl->id.device = cap_substream->pcm->device; + kctl->id.subdevice = cap_substream->number; + err = snd_ctl_add(ak4117->card, kctl); + if (err < 0) + return err; + ak4117->kctls[idx] = kctl; + } + return 0; +} + +int snd_ak4117_external_rate(struct ak4117 *ak4117) +{ + unsigned char rcs1; + + rcs1 = reg_read(ak4117, AK4117_REG_RCS1); + return external_rate(rcs1); +} + +int snd_ak4117_check_rate_and_errors(struct ak4117 *ak4117, unsigned int flags) +{ + struct snd_pcm_runtime *runtime = ak4117->substream ? ak4117->substream->runtime : NULL; + unsigned long _flags; + int res = 0; + unsigned char rcs0, rcs1, rcs2; + unsigned char c0, c1; + + rcs1 = reg_read(ak4117, AK4117_REG_RCS1); + if (flags & AK4117_CHECK_NO_STAT) + goto __rate; + rcs0 = reg_read(ak4117, AK4117_REG_RCS0); + rcs2 = reg_read(ak4117, AK4117_REG_RCS2); + // printk(KERN_DEBUG "AK IRQ: rcs0 = 0x%x, rcs1 = 0x%x, rcs2 = 0x%x\n", rcs0, rcs1, rcs2); + spin_lock_irqsave(&ak4117->lock, _flags); + if (rcs0 & AK4117_PAR) + ak4117->errors[AK4117_PARITY_ERRORS]++; + if (rcs0 & AK4117_V) + ak4117->errors[AK4117_V_BIT_ERRORS]++; + if (rcs2 & AK4117_CCRC) + ak4117->errors[AK4117_CCRC_ERRORS]++; + if (rcs2 & AK4117_QCRC) + ak4117->errors[AK4117_QCRC_ERRORS]++; + c0 = (ak4117->rcs0 & (AK4117_QINT | AK4117_CINT | AK4117_STC | AK4117_AUDION | AK4117_AUTO | AK4117_UNLCK)) ^ + (rcs0 & (AK4117_QINT | AK4117_CINT | AK4117_STC | AK4117_AUDION | AK4117_AUTO | AK4117_UNLCK)); + c1 = (ak4117->rcs1 & (AK4117_DTSCD | AK4117_NPCM | AK4117_PEM | 0x0f)) ^ + (rcs1 & (AK4117_DTSCD | AK4117_NPCM | AK4117_PEM | 0x0f)); + ak4117->rcs0 = rcs0 & ~(AK4117_QINT | AK4117_CINT | AK4117_STC); + ak4117->rcs1 = rcs1; + ak4117->rcs2 = rcs2; + spin_unlock_irqrestore(&ak4117->lock, _flags); + + if (rcs0 & AK4117_PAR) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[0]->id); + if (rcs0 & AK4117_V) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[1]->id); + if (rcs2 & AK4117_CCRC) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[2]->id); + if (rcs2 & AK4117_QCRC) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[3]->id); + + /* rate change */ + if (c1 & 0x0f) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[4]->id); + + if ((c1 & AK4117_PEM) | (c0 & AK4117_CINT)) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[6]->id); + if (c0 & AK4117_QINT) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[8]->id); + + if (c0 & AK4117_AUDION) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[9]->id); + if (c1 & AK4117_NPCM) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[10]->id); + if (c1 & AK4117_DTSCD) + snd_ctl_notify(ak4117->card, SNDRV_CTL_EVENT_MASK_VALUE, &ak4117->kctls[11]->id); + + if (ak4117->change_callback && (c0 | c1) != 0) + ak4117->change_callback(ak4117, c0, c1); + + __rate: + /* compare rate */ + res = external_rate(rcs1); + if (!(flags & AK4117_CHECK_NO_RATE) && runtime && runtime->rate != res) { + snd_pcm_stream_lock_irqsave(ak4117->substream, _flags); + if (snd_pcm_running(ak4117->substream)) { + // printk(KERN_DEBUG "rate changed (%i <- %i)\n", runtime->rate, res); + snd_pcm_stop(ak4117->substream, SNDRV_PCM_STATE_DRAINING); + wake_up(&runtime->sleep); + res = 1; + } + snd_pcm_stream_unlock_irqrestore(ak4117->substream, _flags); + } + return res; +} + +static void snd_ak4117_timer(struct timer_list *t) +{ + struct ak4117 *chip = from_timer(chip, t, timer); + + if (chip->init) + return; + snd_ak4117_check_rate_and_errors(chip, 0); + mod_timer(&chip->timer, 1 + jiffies); +} + +EXPORT_SYMBOL(snd_ak4117_create); +EXPORT_SYMBOL(snd_ak4117_reg_write); +EXPORT_SYMBOL(snd_ak4117_reinit); +EXPORT_SYMBOL(snd_ak4117_build); +EXPORT_SYMBOL(snd_ak4117_external_rate); +EXPORT_SYMBOL(snd_ak4117_check_rate_and_errors); diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c new file mode 100644 index 000000000..971197c34 --- /dev/null +++ b/sound/i2c/other/ak4xxx-adda.c @@ -0,0 +1,914 @@ +/* + * ALSA driver for AK4524 / AK4528 / AK4529 / AK4355 / AK4358 / AK4381 + * AD and DA converters + * + * Copyright (c) 2000-2004 Jaroslav Kysela , + * Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela , Takashi Iwai "); +MODULE_DESCRIPTION("Routines for control of AK452x / AK43xx AD/DA converters"); +MODULE_LICENSE("GPL"); + +/* write the given register and save the data to the cache */ +void snd_akm4xxx_write(struct snd_akm4xxx *ak, int chip, unsigned char reg, + unsigned char val) +{ + ak->ops.lock(ak, chip); + ak->ops.write(ak, chip, reg, val); + + /* save the data */ + snd_akm4xxx_set(ak, chip, reg, val); + ak->ops.unlock(ak, chip); +} + +EXPORT_SYMBOL(snd_akm4xxx_write); + +/* reset procedure for AK4524 and AK4528 */ +static void ak4524_reset(struct snd_akm4xxx *ak, int state) +{ + unsigned int chip; + unsigned char reg; + + for (chip = 0; chip < ak->num_dacs/2; chip++) { + snd_akm4xxx_write(ak, chip, 0x01, state ? 0x00 : 0x03); + if (state) + continue; + /* DAC volumes */ + for (reg = 0x04; reg < ak->total_regs; reg++) + snd_akm4xxx_write(ak, chip, reg, + snd_akm4xxx_get(ak, chip, reg)); + } +} + +/* reset procedure for AK4355 and AK4358 */ +static void ak435X_reset(struct snd_akm4xxx *ak, int state) +{ + unsigned char reg; + + if (state) { + snd_akm4xxx_write(ak, 0, 0x01, 0x02); /* reset and soft-mute */ + return; + } + for (reg = 0x00; reg < ak->total_regs; reg++) + if (reg != 0x01) + snd_akm4xxx_write(ak, 0, reg, + snd_akm4xxx_get(ak, 0, reg)); + snd_akm4xxx_write(ak, 0, 0x01, 0x01); /* un-reset, unmute */ +} + +/* reset procedure for AK4381 */ +static void ak4381_reset(struct snd_akm4xxx *ak, int state) +{ + unsigned int chip; + unsigned char reg; + for (chip = 0; chip < ak->num_dacs/2; chip++) { + snd_akm4xxx_write(ak, chip, 0x00, state ? 0x0c : 0x0f); + if (state) + continue; + for (reg = 0x01; reg < ak->total_regs; reg++) + snd_akm4xxx_write(ak, chip, reg, + snd_akm4xxx_get(ak, chip, reg)); + } +} + +/* + * reset the AKM codecs + * @state: 1 = reset codec, 0 = restore the registers + * + * assert the reset operation and restores the register values to the chips. + */ +void snd_akm4xxx_reset(struct snd_akm4xxx *ak, int state) +{ + switch (ak->type) { + case SND_AK4524: + case SND_AK4528: + case SND_AK4620: + ak4524_reset(ak, state); + break; + case SND_AK4529: + /* FIXME: needed for ak4529? */ + break; + case SND_AK4355: + ak435X_reset(ak, state); + break; + case SND_AK4358: + ak435X_reset(ak, state); + break; + case SND_AK4381: + ak4381_reset(ak, state); + break; + default: + break; + } +} + +EXPORT_SYMBOL(snd_akm4xxx_reset); + + +/* + * Volume conversion table for non-linear volumes + * from -63.5dB (mute) to 0dB step 0.5dB + * + * Used for AK4524/AK4620 input/ouput attenuation, AK4528, and + * AK5365 input attenuation + */ +static const unsigned char vol_cvt_datt[128] = { + 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, + 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, + 0x06, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x0a, + 0x0a, 0x0b, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, + 0x10, 0x10, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x17, 0x18, 0x19, 0x1a, 0x1c, + 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x23, + 0x24, 0x25, 0x26, 0x28, 0x29, 0x2a, 0x2b, 0x2d, + 0x2e, 0x30, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x37, 0x38, 0x39, 0x3b, 0x3c, 0x3e, 0x3f, 0x40, + 0x41, 0x42, 0x43, 0x44, 0x46, 0x47, 0x48, 0x4a, + 0x4b, 0x4d, 0x4e, 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x58, 0x59, 0x5b, 0x5c, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x69, + 0x6a, 0x6c, 0x6d, 0x6f, 0x70, 0x71, 0x72, 0x73, + 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7f, +}; + +/* + * dB tables + */ +static const DECLARE_TLV_DB_SCALE(db_scale_vol_datt, -6350, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_8bit, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_7bit, -6350, 50, 1); +static const DECLARE_TLV_DB_LINEAR(db_scale_linear, TLV_DB_GAIN_MUTE, 0); + +/* + * initialize all the ak4xxx chips + */ +void snd_akm4xxx_init(struct snd_akm4xxx *ak) +{ + static const unsigned char inits_ak4524[] = { + 0x00, 0x07, /* 0: all power up */ + 0x01, 0x00, /* 1: ADC/DAC reset */ + 0x02, 0x60, /* 2: 24bit I2S */ + 0x03, 0x19, /* 3: deemphasis off */ + 0x01, 0x03, /* 1: ADC/DAC enable */ + 0x04, 0x00, /* 4: ADC left muted */ + 0x05, 0x00, /* 5: ADC right muted */ + 0x06, 0x00, /* 6: DAC left muted */ + 0x07, 0x00, /* 7: DAC right muted */ + 0xff, 0xff + }; + static const unsigned char inits_ak4528[] = { + 0x00, 0x07, /* 0: all power up */ + 0x01, 0x00, /* 1: ADC/DAC reset */ + 0x02, 0x60, /* 2: 24bit I2S */ + 0x03, 0x0d, /* 3: deemphasis off, turn LR highpass filters on */ + 0x01, 0x03, /* 1: ADC/DAC enable */ + 0x04, 0x00, /* 4: ADC left muted */ + 0x05, 0x00, /* 5: ADC right muted */ + 0xff, 0xff + }; + static const unsigned char inits_ak4529[] = { + 0x09, 0x01, /* 9: ATS=0, RSTN=1 */ + 0x0a, 0x3f, /* A: all power up, no zero/overflow detection */ + 0x00, 0x0c, /* 0: TDM=0, 24bit I2S, SMUTE=0 */ + 0x01, 0x00, /* 1: ACKS=0, ADC, loop off */ + 0x02, 0xff, /* 2: LOUT1 muted */ + 0x03, 0xff, /* 3: ROUT1 muted */ + 0x04, 0xff, /* 4: LOUT2 muted */ + 0x05, 0xff, /* 5: ROUT2 muted */ + 0x06, 0xff, /* 6: LOUT3 muted */ + 0x07, 0xff, /* 7: ROUT3 muted */ + 0x0b, 0xff, /* B: LOUT4 muted */ + 0x0c, 0xff, /* C: ROUT4 muted */ + 0x08, 0x55, /* 8: deemphasis all off */ + 0xff, 0xff + }; + static const unsigned char inits_ak4355[] = { + 0x01, 0x02, /* 1: reset and soft-mute */ + 0x00, 0x06, /* 0: mode3(i2s), disable auto-clock detect, + * disable DZF, sharp roll-off, RSTN#=0 */ + 0x02, 0x0e, /* 2: DA's power up, normal speed, RSTN#=0 */ + // 0x02, 0x2e, /* quad speed */ + 0x03, 0x01, /* 3: de-emphasis off */ + 0x04, 0x00, /* 4: LOUT1 volume muted */ + 0x05, 0x00, /* 5: ROUT1 volume muted */ + 0x06, 0x00, /* 6: LOUT2 volume muted */ + 0x07, 0x00, /* 7: ROUT2 volume muted */ + 0x08, 0x00, /* 8: LOUT3 volume muted */ + 0x09, 0x00, /* 9: ROUT3 volume muted */ + 0x0a, 0x00, /* a: DATT speed=0, ignore DZF */ + 0x01, 0x01, /* 1: un-reset, unmute */ + 0xff, 0xff + }; + static const unsigned char inits_ak4358[] = { + 0x01, 0x02, /* 1: reset and soft-mute */ + 0x00, 0x06, /* 0: mode3(i2s), disable auto-clock detect, + * disable DZF, sharp roll-off, RSTN#=0 */ + 0x02, 0x4e, /* 2: DA's power up, normal speed, RSTN#=0 */ + /* 0x02, 0x6e,*/ /* quad speed */ + 0x03, 0x01, /* 3: de-emphasis off */ + 0x04, 0x00, /* 4: LOUT1 volume muted */ + 0x05, 0x00, /* 5: ROUT1 volume muted */ + 0x06, 0x00, /* 6: LOUT2 volume muted */ + 0x07, 0x00, /* 7: ROUT2 volume muted */ + 0x08, 0x00, /* 8: LOUT3 volume muted */ + 0x09, 0x00, /* 9: ROUT3 volume muted */ + 0x0b, 0x00, /* b: LOUT4 volume muted */ + 0x0c, 0x00, /* c: ROUT4 volume muted */ + 0x0a, 0x00, /* a: DATT speed=0, ignore DZF */ + 0x01, 0x01, /* 1: un-reset, unmute */ + 0xff, 0xff + }; + static const unsigned char inits_ak4381[] = { + 0x00, 0x0c, /* 0: mode3(i2s), disable auto-clock detect */ + 0x01, 0x02, /* 1: de-emphasis off, normal speed, + * sharp roll-off, DZF off */ + // 0x01, 0x12, /* quad speed */ + 0x02, 0x00, /* 2: DZF disabled */ + 0x03, 0x00, /* 3: LATT 0 */ + 0x04, 0x00, /* 4: RATT 0 */ + 0x00, 0x0f, /* 0: power-up, un-reset */ + 0xff, 0xff + }; + static const unsigned char inits_ak4620[] = { + 0x00, 0x07, /* 0: normal */ + 0x01, 0x00, /* 0: reset */ + 0x01, 0x02, /* 1: RSTAD */ + 0x01, 0x03, /* 1: RSTDA */ + 0x01, 0x0f, /* 1: normal */ + 0x02, 0x60, /* 2: 24bit I2S */ + 0x03, 0x01, /* 3: deemphasis off */ + 0x04, 0x00, /* 4: LIN muted */ + 0x05, 0x00, /* 5: RIN muted */ + 0x06, 0x00, /* 6: LOUT muted */ + 0x07, 0x00, /* 7: ROUT muted */ + 0xff, 0xff + }; + + int chip; + const unsigned char *ptr, *inits; + unsigned char reg, data; + + memset(ak->images, 0, sizeof(ak->images)); + memset(ak->volumes, 0, sizeof(ak->volumes)); + + switch (ak->type) { + case SND_AK4524: + inits = inits_ak4524; + ak->num_chips = ak->num_dacs / 2; + ak->name = "ak4524"; + ak->total_regs = 0x08; + break; + case SND_AK4528: + inits = inits_ak4528; + ak->num_chips = ak->num_dacs / 2; + ak->name = "ak4528"; + ak->total_regs = 0x06; + break; + case SND_AK4529: + inits = inits_ak4529; + ak->num_chips = 1; + ak->name = "ak4529"; + ak->total_regs = 0x0d; + break; + case SND_AK4355: + inits = inits_ak4355; + ak->num_chips = 1; + ak->name = "ak4355"; + ak->total_regs = 0x0b; + break; + case SND_AK4358: + inits = inits_ak4358; + ak->num_chips = 1; + ak->name = "ak4358"; + ak->total_regs = 0x10; + break; + case SND_AK4381: + inits = inits_ak4381; + ak->num_chips = ak->num_dacs / 2; + ak->name = "ak4381"; + ak->total_regs = 0x05; + break; + case SND_AK5365: + /* FIXME: any init sequence? */ + ak->num_chips = 1; + ak->name = "ak5365"; + ak->total_regs = 0x08; + return; + case SND_AK4620: + inits = inits_ak4620; + ak->num_chips = ak->num_dacs / 2; + ak->name = "ak4620"; + ak->total_regs = 0x08; + break; + default: + snd_BUG(); + return; + } + + for (chip = 0; chip < ak->num_chips; chip++) { + ptr = inits; + while (*ptr != 0xff) { + reg = *ptr++; + data = *ptr++; + snd_akm4xxx_write(ak, chip, reg, data); + udelay(10); + } + } +} + +EXPORT_SYMBOL(snd_akm4xxx_init); + +/* + * Mixer callbacks + */ +#define AK_IPGA (1<<20) /* including IPGA */ +#define AK_VOL_CVT (1<<21) /* need dB conversion */ +#define AK_NEEDSMSB (1<<22) /* need MSB update bit */ +#define AK_INVERT (1<<23) /* data is inverted */ +#define AK_GET_CHIP(val) (((val) >> 8) & 0xff) +#define AK_GET_ADDR(val) ((val) & 0xff) +#define AK_GET_SHIFT(val) (((val) >> 16) & 0x0f) +#define AK_GET_VOL_CVT(val) (((val) >> 21) & 1) +#define AK_GET_IPGA(val) (((val) >> 20) & 1) +#define AK_GET_NEEDSMSB(val) (((val) >> 22) & 1) +#define AK_GET_INVERT(val) (((val) >> 23) & 1) +#define AK_GET_MASK(val) (((val) >> 24) & 0xff) +#define AK_COMPOSE(chip,addr,shift,mask) \ + (((chip) << 8) | (addr) | ((shift) << 16) | ((mask) << 24)) + +static int snd_akm4xxx_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int mask = AK_GET_MASK(kcontrol->private_value); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_akm4xxx_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + + ucontrol->value.integer.value[0] = snd_akm4xxx_get_vol(ak, chip, addr); + return 0; +} + +static int put_ak_reg(struct snd_kcontrol *kcontrol, int addr, + unsigned char nval) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + unsigned int mask = AK_GET_MASK(kcontrol->private_value); + int chip = AK_GET_CHIP(kcontrol->private_value); + + if (snd_akm4xxx_get_vol(ak, chip, addr) == nval) + return 0; + + snd_akm4xxx_set_vol(ak, chip, addr, nval); + if (AK_GET_VOL_CVT(kcontrol->private_value) && nval < 128) + nval = vol_cvt_datt[nval]; + if (AK_GET_IPGA(kcontrol->private_value) && nval >= 128) + nval++; /* need to correct + 1 since both 127 and 128 are 0dB */ + if (AK_GET_INVERT(kcontrol->private_value)) + nval = mask - nval; + if (AK_GET_NEEDSMSB(kcontrol->private_value)) + nval |= 0x80; + /* printk(KERN_DEBUG "DEBUG - AK writing reg: chip %x addr %x, + nval %x\n", chip, addr, nval); */ + snd_akm4xxx_write(ak, chip, addr, nval); + return 1; +} + +static int snd_akm4xxx_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int mask = AK_GET_MASK(kcontrol->private_value); + unsigned int val = ucontrol->value.integer.value[0]; + if (val > mask) + return -EINVAL; + return put_ak_reg(kcontrol, AK_GET_ADDR(kcontrol->private_value), val); +} + +static int snd_akm4xxx_stereo_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int mask = AK_GET_MASK(kcontrol->private_value); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_akm4xxx_stereo_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + + ucontrol->value.integer.value[0] = snd_akm4xxx_get_vol(ak, chip, addr); + ucontrol->value.integer.value[1] = snd_akm4xxx_get_vol(ak, chip, addr+1); + return 0; +} + +static int snd_akm4xxx_stereo_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int addr = AK_GET_ADDR(kcontrol->private_value); + unsigned int mask = AK_GET_MASK(kcontrol->private_value); + unsigned int val[2]; + int change; + + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > mask || val[1] > mask) + return -EINVAL; + change = put_ak_reg(kcontrol, addr, val[0]); + change |= put_ak_reg(kcontrol, addr + 1, val[1]); + return change; +} + +static int snd_akm4xxx_deemphasis_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "44.1kHz", "Off", "48kHz", "32kHz", + }; + return snd_ctl_enum_info(uinfo, 1, 4, texts); +} + +static int snd_akm4xxx_deemphasis_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int shift = AK_GET_SHIFT(kcontrol->private_value); + ucontrol->value.enumerated.item[0] = + (snd_akm4xxx_get(ak, chip, addr) >> shift) & 3; + return 0; +} + +static int snd_akm4xxx_deemphasis_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int shift = AK_GET_SHIFT(kcontrol->private_value); + unsigned char nval = ucontrol->value.enumerated.item[0] & 3; + int change; + + nval = (nval << shift) | + (snd_akm4xxx_get(ak, chip, addr) & ~(3 << shift)); + change = snd_akm4xxx_get(ak, chip, addr) != nval; + if (change) + snd_akm4xxx_write(ak, chip, addr, nval); + return change; +} + +#define ak4xxx_switch_info snd_ctl_boolean_mono_info + +static int ak4xxx_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int shift = AK_GET_SHIFT(kcontrol->private_value); + int invert = AK_GET_INVERT(kcontrol->private_value); + /* we observe the (1<value.integer.value[0] = (val & (1<private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int shift = AK_GET_SHIFT(kcontrol->private_value); + int invert = AK_GET_INVERT(kcontrol->private_value); + long flag = ucontrol->value.integer.value[0]; + unsigned char val, oval; + int change; + + if (invert) + flag = ! flag; + oval = snd_akm4xxx_get(ak, chip, addr); + if (flag) + val = oval | (1<adc_info[mixer_ch].input_names; + num_names = 0; + while (num_names < AK5365_NUM_INPUTS && input_names[num_names]) + ++num_names; + return num_names; +} + +static int ak4xxx_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int mixer_ch = AK_GET_SHIFT(kcontrol->private_value); + unsigned int num_names; + + num_names = ak4xxx_capture_num_inputs(ak, mixer_ch); + if (!num_names) + return -EINVAL; + return snd_ctl_enum_info(uinfo, 1, num_names, + ak->adc_info[mixer_ch].input_names); +} + +static int ak4xxx_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int mask = AK_GET_MASK(kcontrol->private_value); + unsigned char val; + + val = snd_akm4xxx_get(ak, chip, addr) & mask; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int ak4xxx_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int mixer_ch = AK_GET_SHIFT(kcontrol->private_value); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int mask = AK_GET_MASK(kcontrol->private_value); + unsigned char oval, val; + int num_names = ak4xxx_capture_num_inputs(ak, mixer_ch); + + if (ucontrol->value.enumerated.item[0] >= num_names) + return -EINVAL; + + oval = snd_akm4xxx_get(ak, chip, addr); + val = oval & ~mask; + val |= ucontrol->value.enumerated.item[0] & mask; + if (val != oval) { + snd_akm4xxx_write(ak, chip, addr, val); + return 1; + } + return 0; +} + +/* + * build AK4xxx controls + */ + +static int build_dac_controls(struct snd_akm4xxx *ak) +{ + int idx, err, mixer_ch, num_stereo; + struct snd_kcontrol_new knew; + + mixer_ch = 0; + for (idx = 0; idx < ak->num_dacs; ) { + /* mute control for Revolution 7.1 - AK4381 */ + if (ak->type == SND_AK4381 + && ak->dac_info[mixer_ch].switch_name) { + memset(&knew, 0, sizeof(knew)); + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.count = 1; + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + knew.name = ak->dac_info[mixer_ch].switch_name; + knew.info = ak4xxx_switch_info; + knew.get = ak4xxx_switch_get; + knew.put = ak4xxx_switch_put; + knew.access = 0; + /* register 1, bit 0 (SMUTE): 0 = normal operation, + 1 = mute */ + knew.private_value = + AK_COMPOSE(idx/2, 1, 0, 0) | AK_INVERT; + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; + } + memset(&knew, 0, sizeof(knew)); + if (! ak->dac_info || ! ak->dac_info[mixer_ch].name) { + knew.name = "DAC Volume"; + knew.index = mixer_ch + ak->idx_offset * 2; + num_stereo = 1; + } else { + knew.name = ak->dac_info[mixer_ch].name; + num_stereo = ak->dac_info[mixer_ch].num_channels; + } + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.count = 1; + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + if (num_stereo == 2) { + knew.info = snd_akm4xxx_stereo_volume_info; + knew.get = snd_akm4xxx_stereo_volume_get; + knew.put = snd_akm4xxx_stereo_volume_put; + } else { + knew.info = snd_akm4xxx_volume_info; + knew.get = snd_akm4xxx_volume_get; + knew.put = snd_akm4xxx_volume_put; + } + switch (ak->type) { + case SND_AK4524: + /* register 6 & 7 */ + knew.private_value = + AK_COMPOSE(idx/2, (idx%2) + 6, 0, 127) | + AK_VOL_CVT; + knew.tlv.p = db_scale_vol_datt; + break; + case SND_AK4528: + /* register 4 & 5 */ + knew.private_value = + AK_COMPOSE(idx/2, (idx%2) + 4, 0, 127) | + AK_VOL_CVT; + knew.tlv.p = db_scale_vol_datt; + break; + case SND_AK4529: { + /* registers 2-7 and b,c */ + int val = idx < 6 ? idx + 2 : (idx - 6) + 0xb; + knew.private_value = + AK_COMPOSE(0, val, 0, 255) | AK_INVERT; + knew.tlv.p = db_scale_8bit; + break; + } + case SND_AK4355: + /* register 4-9, chip #0 only */ + knew.private_value = AK_COMPOSE(0, idx + 4, 0, 255); + knew.tlv.p = db_scale_8bit; + break; + case SND_AK4358: { + /* register 4-9 and 11-12, chip #0 only */ + int addr = idx < 6 ? idx + 4 : idx + 5; + knew.private_value = + AK_COMPOSE(0, addr, 0, 127) | AK_NEEDSMSB; + knew.tlv.p = db_scale_7bit; + break; + } + case SND_AK4381: + /* register 3 & 4 */ + knew.private_value = + AK_COMPOSE(idx/2, (idx%2) + 3, 0, 255); + knew.tlv.p = db_scale_linear; + break; + case SND_AK4620: + /* register 6 & 7 */ + knew.private_value = + AK_COMPOSE(idx/2, (idx%2) + 6, 0, 255); + knew.tlv.p = db_scale_linear; + break; + default: + return -EINVAL; + } + + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; + + idx += num_stereo; + mixer_ch++; + } + return 0; +} + +static int build_adc_controls(struct snd_akm4xxx *ak) +{ + int idx, err, mixer_ch, num_stereo, max_steps; + struct snd_kcontrol_new knew; + + mixer_ch = 0; + if (ak->type == SND_AK4528) + return 0; /* no controls */ + for (idx = 0; idx < ak->num_adcs;) { + memset(&knew, 0, sizeof(knew)); + if (! ak->adc_info || ! ak->adc_info[mixer_ch].name) { + knew.name = "ADC Volume"; + knew.index = mixer_ch + ak->idx_offset * 2; + num_stereo = 1; + } else { + knew.name = ak->adc_info[mixer_ch].name; + num_stereo = ak->adc_info[mixer_ch].num_channels; + } + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.count = 1; + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + if (num_stereo == 2) { + knew.info = snd_akm4xxx_stereo_volume_info; + knew.get = snd_akm4xxx_stereo_volume_get; + knew.put = snd_akm4xxx_stereo_volume_put; + } else { + knew.info = snd_akm4xxx_volume_info; + knew.get = snd_akm4xxx_volume_get; + knew.put = snd_akm4xxx_volume_put; + } + /* register 4 & 5 */ + if (ak->type == SND_AK5365) + max_steps = 152; + else + max_steps = 164; + knew.private_value = + AK_COMPOSE(idx/2, (idx%2) + 4, 0, max_steps) | + AK_VOL_CVT | AK_IPGA; + knew.tlv.p = db_scale_vol_datt; + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; + + if (ak->type == SND_AK5365 && (idx % 2) == 0) { + if (! ak->adc_info || + ! ak->adc_info[mixer_ch].switch_name) { + knew.name = "Capture Switch"; + knew.index = mixer_ch + ak->idx_offset * 2; + } else + knew.name = ak->adc_info[mixer_ch].switch_name; + knew.info = ak4xxx_switch_info; + knew.get = ak4xxx_switch_get; + knew.put = ak4xxx_switch_put; + knew.access = 0; + /* register 2, bit 0 (SMUTE): 0 = normal operation, + 1 = mute */ + knew.private_value = + AK_COMPOSE(idx/2, 2, 0, 0) | AK_INVERT; + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; + + memset(&knew, 0, sizeof(knew)); + if (!ak->adc_info || + !ak->adc_info[mixer_ch].selector_name) { + knew.name = "Capture Channel"; + knew.index = mixer_ch + ak->idx_offset * 2; + } else + knew.name = ak->adc_info[mixer_ch].selector_name; + + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.info = ak4xxx_capture_source_info; + knew.get = ak4xxx_capture_source_get; + knew.put = ak4xxx_capture_source_put; + knew.access = 0; + /* input selector control: reg. 1, bits 0-2. + * mis-use 'shift' to pass mixer_ch */ + knew.private_value + = AK_COMPOSE(idx/2, 1, mixer_ch, 0x07); + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; + } + + idx += num_stereo; + mixer_ch++; + } + return 0; +} + +static int build_deemphasis(struct snd_akm4xxx *ak, int num_emphs) +{ + int idx, err; + struct snd_kcontrol_new knew; + + for (idx = 0; idx < num_emphs; idx++) { + memset(&knew, 0, sizeof(knew)); + knew.name = "Deemphasis"; + knew.index = idx + ak->idx_offset; + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.count = 1; + knew.info = snd_akm4xxx_deemphasis_info; + knew.get = snd_akm4xxx_deemphasis_get; + knew.put = snd_akm4xxx_deemphasis_put; + switch (ak->type) { + case SND_AK4524: + case SND_AK4528: + case SND_AK4620: + /* register 3 */ + knew.private_value = AK_COMPOSE(idx, 3, 0, 0); + break; + case SND_AK4529: { + int shift = idx == 3 ? 6 : (2 - idx) * 2; + /* register 8 with shift */ + knew.private_value = AK_COMPOSE(0, 8, shift, 0); + break; + } + case SND_AK4355: + case SND_AK4358: + knew.private_value = AK_COMPOSE(idx, 3, 0, 0); + break; + case SND_AK4381: + knew.private_value = AK_COMPOSE(idx, 1, 1, 0); + break; + default: + return -EINVAL; + } + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; + } + return 0; +} + +static void proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_akm4xxx *ak = entry->private_data; + int reg, val, chip; + for (chip = 0; chip < ak->num_chips; chip++) { + for (reg = 0; reg < ak->total_regs; reg++) { + val = snd_akm4xxx_get(ak, chip, reg); + snd_iprintf(buffer, "chip %d: 0x%02x = 0x%02x\n", chip, + reg, val); + } + } +} + +static int proc_init(struct snd_akm4xxx *ak) +{ + struct snd_info_entry *entry; + int err; + err = snd_card_proc_new(ak->card, ak->name, &entry); + if (err < 0) + return err; + snd_info_set_text_ops(entry, ak, proc_regs_read); + return 0; +} + +int snd_akm4xxx_build_controls(struct snd_akm4xxx *ak) +{ + int err, num_emphs; + + err = build_dac_controls(ak); + if (err < 0) + return err; + + err = build_adc_controls(ak); + if (err < 0) + return err; + if (ak->type == SND_AK4355 || ak->type == SND_AK4358) + num_emphs = 1; + else if (ak->type == SND_AK4620) + num_emphs = 0; + else + num_emphs = ak->num_dacs / 2; + err = build_deemphasis(ak, num_emphs); + if (err < 0) + return err; + err = proc_init(ak); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL(snd_akm4xxx_build_controls); diff --git a/sound/i2c/other/pt2258.c b/sound/i2c/other/pt2258.c new file mode 100644 index 000000000..9fa390ba1 --- /dev/null +++ b/sound/i2c/other/pt2258.c @@ -0,0 +1,227 @@ +/* + * ALSA Driver for the PT2258 volume controller. + * + * Copyright (c) 2006 Jochen Voss + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jochen Voss "); +MODULE_DESCRIPTION("PT2258 volume controller (Princeton Technology Corp.)"); +MODULE_LICENSE("GPL"); + +#define PT2258_CMD_RESET 0xc0 +#define PT2258_CMD_UNMUTE 0xf8 +#define PT2258_CMD_MUTE 0xf9 + +static const unsigned char pt2258_channel_code[12] = { + 0x80, 0x90, /* channel 1: -10dB, -1dB */ + 0x40, 0x50, /* channel 2: -10dB, -1dB */ + 0x00, 0x10, /* channel 3: -10dB, -1dB */ + 0x20, 0x30, /* channel 4: -10dB, -1dB */ + 0x60, 0x70, /* channel 5: -10dB, -1dB */ + 0xa0, 0xb0 /* channel 6: -10dB, -1dB */ +}; + +int snd_pt2258_reset(struct snd_pt2258 *pt) +{ + unsigned char bytes[2]; + int i; + + /* reset chip */ + bytes[0] = PT2258_CMD_RESET; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + /* mute all channels */ + pt->mute = 1; + bytes[0] = PT2258_CMD_MUTE; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + /* set all channels to 0dB */ + for (i = 0; i < 6; ++i) + pt->volume[i] = 0; + bytes[0] = 0xd0; + bytes[1] = 0xe0; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + return 0; + + __error: + snd_i2c_unlock(pt->i2c_bus); + snd_printk(KERN_ERR "PT2258 reset failed\n"); + return -EIO; +} + +static int pt2258_stereo_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 = 79; + return 0; +} + +static int pt2258_stereo_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + int base = kcontrol->private_value; + + /* chip does not support register reads */ + ucontrol->value.integer.value[0] = 79 - pt->volume[base]; + ucontrol->value.integer.value[1] = 79 - pt->volume[base + 1]; + return 0; +} + +static int pt2258_stereo_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + int base = kcontrol->private_value; + unsigned char bytes[2]; + int val0, val1; + + val0 = 79 - ucontrol->value.integer.value[0]; + val1 = 79 - ucontrol->value.integer.value[1]; + if (val0 < 0 || val0 > 79 || val1 < 0 || val1 > 79) + return -EINVAL; + if (val0 == pt->volume[base] && val1 == pt->volume[base + 1]) + return 0; + + pt->volume[base] = val0; + bytes[0] = pt2258_channel_code[2 * base] | (val0 / 10); + bytes[1] = pt2258_channel_code[2 * base + 1] | (val0 % 10); + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + pt->volume[base + 1] = val1; + bytes[0] = pt2258_channel_code[2 * base + 2] | (val1 / 10); + bytes[1] = pt2258_channel_code[2 * base + 3] | (val1 % 10); + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + return 1; + + __error: + snd_i2c_unlock(pt->i2c_bus); + snd_printk(KERN_ERR "PT2258 access failed\n"); + return -EIO; +} + +#define pt2258_switch_info snd_ctl_boolean_mono_info + +static int pt2258_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + + ucontrol->value.integer.value[0] = !pt->mute; + return 0; +} + +static int pt2258_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + unsigned char bytes[2]; + int val; + + val = !ucontrol->value.integer.value[0]; + if (pt->mute == val) + return 0; + + pt->mute = val; + bytes[0] = val ? PT2258_CMD_MUTE : PT2258_CMD_UNMUTE; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + return 1; + + __error: + snd_i2c_unlock(pt->i2c_bus); + snd_printk(KERN_ERR "PT2258 access failed 2\n"); + return -EIO; +} + +static const DECLARE_TLV_DB_SCALE(pt2258_db_scale, -7900, 100, 0); + +int snd_pt2258_build_controls(struct snd_pt2258 *pt) +{ + struct snd_kcontrol_new knew; + char *names[3] = { + "Mic Loopback Playback Volume", + "Line Loopback Playback Volume", + "CD Loopback Playback Volume" + }; + int i, err; + + for (i = 0; i < 3; ++i) { + memset(&knew, 0, sizeof(knew)); + knew.name = names[i]; + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.count = 1; + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + knew.private_value = 2 * i; + knew.info = pt2258_stereo_volume_info; + knew.get = pt2258_stereo_volume_get; + knew.put = pt2258_stereo_volume_put; + knew.tlv.p = pt2258_db_scale; + + err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt)); + if (err < 0) + return err; + } + + memset(&knew, 0, sizeof(knew)); + knew.name = "Loopback Switch"; + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.info = pt2258_switch_info; + knew.get = pt2258_switch_get; + knew.put = pt2258_switch_put; + knew.access = 0; + err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt)); + if (err < 0) + return err; + + return 0; +} + +EXPORT_SYMBOL(snd_pt2258_reset); +EXPORT_SYMBOL(snd_pt2258_build_controls); diff --git a/sound/i2c/tea6330t.c b/sound/i2c/tea6330t.c new file mode 100644 index 000000000..239c48224 --- /dev/null +++ b/sound/i2c/tea6330t.c @@ -0,0 +1,370 @@ +/* + * Routines for control of the TEA6330T circuit via i2c bus + * Sound fader control circuit for car radios by Philips Semiconductors + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Routines for control of the TEA6330T circuit via i2c bus"); +MODULE_LICENSE("GPL"); + +#define TEA6330T_ADDR (0x80>>1) /* fixed address */ + +#define TEA6330T_SADDR_VOLUME_LEFT 0x00 /* volume left */ +#define TEA6330T_SADDR_VOLUME_RIGHT 0x01 /* volume right */ +#define TEA6330T_SADDR_BASS 0x02 /* bass control */ +#define TEA6330T_SADDR_TREBLE 0x03 /* treble control */ +#define TEA6330T_SADDR_FADER 0x04 /* fader control */ +#define TEA6330T_MFN 0x20 /* mute control for selected channels */ +#define TEA6330T_FCH 0x10 /* select fader channels - front or rear */ +#define TEA6330T_SADDR_AUDIO_SWITCH 0x05 /* audio switch */ +#define TEA6330T_GMU 0x80 /* mute control, general mute */ +#define TEA6330T_EQN 0x40 /* equalizer switchover (0=equalizer-on) */ + + +struct tea6330t { + struct snd_i2c_device *device; + struct snd_i2c_bus *bus; + int equalizer; + int fader; + unsigned char regs[8]; + unsigned char mleft, mright; + unsigned char bass, treble; + unsigned char max_bass, max_treble; +}; + + +int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer) +{ + int res; + + snd_i2c_lock(bus); + res = snd_i2c_probeaddr(bus, TEA6330T_ADDR); + snd_i2c_unlock(bus); + return res; +} + +#if 0 +static void snd_tea6330t_set(struct tea6330t *tea, + unsigned char addr, unsigned char value) +{ +#if 0 + printk(KERN_DEBUG "set - 0x%x/0x%x\n", addr, value); +#endif + snd_i2c_write(tea->bus, TEA6330T_ADDR, addr, value, 1); +} +#endif + +#define TEA6330T_MASTER_VOLUME(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_tea6330t_info_master_volume, \ + .get = snd_tea6330t_get_master_volume, .put = snd_tea6330t_put_master_volume } + +static int snd_tea6330t_info_master_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 = 43; + return 0; +} + +static int snd_tea6330t_get_master_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + + snd_i2c_lock(tea->bus); + ucontrol->value.integer.value[0] = tea->mleft - 0x14; + ucontrol->value.integer.value[1] = tea->mright - 0x14; + snd_i2c_unlock(tea->bus); + return 0; +} + +static int snd_tea6330t_put_master_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + int change, count, err; + unsigned char bytes[3]; + unsigned char val1, val2; + + val1 = (ucontrol->value.integer.value[0] % 44) + 0x14; + val2 = (ucontrol->value.integer.value[1] % 44) + 0x14; + snd_i2c_lock(tea->bus); + change = val1 != tea->mleft || val2 != tea->mright; + tea->mleft = val1; + tea->mright = val2; + count = 0; + if (tea->regs[TEA6330T_SADDR_VOLUME_LEFT] != 0) { + bytes[count++] = TEA6330T_SADDR_VOLUME_LEFT; + bytes[count++] = tea->regs[TEA6330T_SADDR_VOLUME_LEFT] = tea->mleft; + } + if (tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] != 0) { + if (count == 0) + bytes[count++] = TEA6330T_SADDR_VOLUME_RIGHT; + bytes[count++] = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] = tea->mright; + } + if (count > 0) { + if ((err = snd_i2c_sendbytes(tea->device, bytes, count)) < 0) + change = err; + } + snd_i2c_unlock(tea->bus); + return change; +} + +#define TEA6330T_MASTER_SWITCH(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_tea6330t_info_master_switch, \ + .get = snd_tea6330t_get_master_switch, .put = snd_tea6330t_put_master_switch } + +#define snd_tea6330t_info_master_switch snd_ctl_boolean_stereo_info + +static int snd_tea6330t_get_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + + snd_i2c_lock(tea->bus); + ucontrol->value.integer.value[0] = tea->regs[TEA6330T_SADDR_VOLUME_LEFT] == 0 ? 0 : 1; + ucontrol->value.integer.value[1] = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] == 0 ? 0 : 1; + snd_i2c_unlock(tea->bus); + return 0; +} + +static int snd_tea6330t_put_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + int change, err; + unsigned char bytes[3]; + unsigned char oval1, oval2, val1, val2; + + val1 = ucontrol->value.integer.value[0] & 1; + val2 = ucontrol->value.integer.value[1] & 1; + snd_i2c_lock(tea->bus); + oval1 = tea->regs[TEA6330T_SADDR_VOLUME_LEFT] == 0 ? 0 : 1; + oval2 = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] == 0 ? 0 : 1; + change = val1 != oval1 || val2 != oval2; + tea->regs[TEA6330T_SADDR_VOLUME_LEFT] = val1 ? tea->mleft : 0; + tea->regs[TEA6330T_SADDR_VOLUME_RIGHT] = val2 ? tea->mright : 0; + bytes[0] = TEA6330T_SADDR_VOLUME_LEFT; + bytes[1] = tea->regs[TEA6330T_SADDR_VOLUME_LEFT]; + bytes[2] = tea->regs[TEA6330T_SADDR_VOLUME_RIGHT]; + if ((err = snd_i2c_sendbytes(tea->device, bytes, 3)) < 0) + change = err; + snd_i2c_unlock(tea->bus); + return change; +} + +#define TEA6330T_BASS(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_tea6330t_info_bass, \ + .get = snd_tea6330t_get_bass, .put = snd_tea6330t_put_bass } + +static int snd_tea6330t_info_bass(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tea->max_bass; + return 0; +} + +static int snd_tea6330t_get_bass(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tea->bass; + return 0; +} + +static int snd_tea6330t_put_bass(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + int change, err; + unsigned char bytes[2]; + unsigned char val1; + + val1 = ucontrol->value.integer.value[0] % (tea->max_bass + 1); + snd_i2c_lock(tea->bus); + tea->bass = val1; + val1 += tea->equalizer ? 7 : 3; + change = tea->regs[TEA6330T_SADDR_BASS] != val1; + bytes[0] = TEA6330T_SADDR_BASS; + bytes[1] = tea->regs[TEA6330T_SADDR_BASS] = val1; + if ((err = snd_i2c_sendbytes(tea->device, bytes, 2)) < 0) + change = err; + snd_i2c_unlock(tea->bus); + return change; +} + +#define TEA6330T_TREBLE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_tea6330t_info_treble, \ + .get = snd_tea6330t_get_treble, .put = snd_tea6330t_put_treble } + +static int snd_tea6330t_info_treble(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tea->max_treble; + return 0; +} + +static int snd_tea6330t_get_treble(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = tea->treble; + return 0; +} + +static int snd_tea6330t_put_treble(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tea6330t *tea = snd_kcontrol_chip(kcontrol); + int change, err; + unsigned char bytes[2]; + unsigned char val1; + + val1 = ucontrol->value.integer.value[0] % (tea->max_treble + 1); + snd_i2c_lock(tea->bus); + tea->treble = val1; + val1 += 3; + change = tea->regs[TEA6330T_SADDR_TREBLE] != val1; + bytes[0] = TEA6330T_SADDR_TREBLE; + bytes[1] = tea->regs[TEA6330T_SADDR_TREBLE] = val1; + if ((err = snd_i2c_sendbytes(tea->device, bytes, 2)) < 0) + change = err; + snd_i2c_unlock(tea->bus); + return change; +} + +static struct snd_kcontrol_new snd_tea6330t_controls[] = { +TEA6330T_MASTER_SWITCH("Master Playback Switch", 0), +TEA6330T_MASTER_VOLUME("Master Playback Volume", 0), +TEA6330T_BASS("Tone Control - Bass", 0), +TEA6330T_TREBLE("Tone Control - Treble", 0) +}; + +static void snd_tea6330_free(struct snd_i2c_device *device) +{ + kfree(device->private_data); +} + +int snd_tea6330t_update_mixer(struct snd_card *card, + struct snd_i2c_bus *bus, + int equalizer, int fader) +{ + struct snd_i2c_device *device; + struct tea6330t *tea; + struct snd_kcontrol_new *knew; + unsigned int idx; + int err = -ENOMEM; + u8 default_treble, default_bass; + unsigned char bytes[7]; + + tea = kzalloc(sizeof(*tea), GFP_KERNEL); + if (tea == NULL) + return -ENOMEM; + if ((err = snd_i2c_device_create(bus, "TEA6330T", TEA6330T_ADDR, &device)) < 0) { + kfree(tea); + return err; + } + tea->device = device; + tea->bus = bus; + tea->equalizer = equalizer; + tea->fader = fader; + device->private_data = tea; + device->private_free = snd_tea6330_free; + + snd_i2c_lock(bus); + + /* turn fader off and handle equalizer */ + tea->regs[TEA6330T_SADDR_FADER] = 0x3f; + tea->regs[TEA6330T_SADDR_AUDIO_SWITCH] = equalizer ? 0 : TEA6330T_EQN; + /* initialize mixer */ + if (!tea->equalizer) { + tea->max_bass = 9; + tea->max_treble = 8; + default_bass = 3 + 4; + tea->bass = 4; + default_treble = 3 + 4; + tea->treble = 4; + } else { + tea->max_bass = 5; + tea->max_treble = 0; + default_bass = 7 + 4; + tea->bass = 4; + default_treble = 3; + tea->treble = 0; + } + tea->mleft = tea->mright = 0x14; + tea->regs[TEA6330T_SADDR_BASS] = default_bass; + tea->regs[TEA6330T_SADDR_TREBLE] = default_treble; + + /* compose I2C message and put the hardware to initial state */ + bytes[0] = TEA6330T_SADDR_VOLUME_LEFT; + for (idx = 0; idx < 6; idx++) + bytes[idx+1] = tea->regs[idx]; + if ((err = snd_i2c_sendbytes(device, bytes, 7)) < 0) + goto __error; + + strcat(card->mixername, ",TEA6330T"); + if ((err = snd_component_add(card, "TEA6330T")) < 0) + goto __error; + + for (idx = 0; idx < ARRAY_SIZE(snd_tea6330t_controls); idx++) { + knew = &snd_tea6330t_controls[idx]; + if (tea->treble == 0 && !strcmp(knew->name, "Tone Control - Treble")) + continue; + if ((err = snd_ctl_add(card, snd_ctl_new1(knew, tea))) < 0) + goto __error; + } + + snd_i2c_unlock(bus); + return 0; + + __error: + snd_i2c_unlock(bus); + snd_i2c_device_free(device); + return err; +} + +EXPORT_SYMBOL(snd_tea6330t_detect); +EXPORT_SYMBOL(snd_tea6330t_update_mixer); diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig new file mode 100644 index 000000000..f8f343392 --- /dev/null +++ b/sound/isa/Kconfig @@ -0,0 +1,471 @@ +# ALSA ISA drivers + +config SND_WSS_LIB + tristate + select SND_PCM + select SND_TIMER + +config SND_SB_COMMON + tristate + +config SND_SB8_DSP + tristate + select SND_PCM + select SND_SB_COMMON + +config SND_SB16_DSP + tristate + select SND_PCM + select SND_SB_COMMON + +menuconfig SND_ISA + bool "ISA sound devices" + depends on ISA || COMPILE_TEST + depends on ISA_DMA_API && !M68K + default y + help + Support for sound devices connected via the ISA bus. + +if SND_ISA + +config SND_ADLIB + tristate "AdLib FM card" + select SND_OPL3_LIB + help + Say Y here to include support for AdLib FM cards. + + To compile this driver as a module, choose M here: the module + will be called snd-adlib. + +config SND_AD1816A + tristate "Analog Devices SoundPort AD1816A" + depends on PNP + select ISAPNP + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + select SND_TIMER + help + Say Y here to include support for Analog Devices SoundPort + AD1816A or compatible sound chips. + + To compile this driver as a module, choose M here: the module + will be called snd-ad1816a. + +config SND_AD1848 + tristate "Generic AD1848/CS4248 driver" + select SND_WSS_LIB + help + Say Y here to include support for AD1848 (Analog Devices) or + CS4248 (Cirrus Logic - Crystal Semiconductors) chips. + + For newer chips from Cirrus Logic, use the CS4231 or CS4232+ + drivers. + + To compile this driver as a module, choose M here: the module + will be called snd-ad1848. + +config SND_ALS100 + tristate "Diamond Tech. DT-019x and Avance Logic ALSxxx" + depends on PNP + select ISAPNP + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_SB16_DSP + help + Say Y here to include support for soundcards based on the + Diamond Technologies DT-019X or Avance Logic chips: ALS007, + ALS100, ALS110, ALS120 and ALS200 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-als100. + +config SND_AZT1605 + tristate "Aztech AZT1605 Driver" + depends on SND + select SND_WSS_LIB + select SND_MPU401_UART + select SND_OPL3_LIB + help + Say Y here to include support for Aztech Sound Galaxy cards + based on the AZT1605 chipset. + + To compile this driver as a module, choose M here: the module + will be called snd-azt1605. + +config SND_AZT2316 + tristate "Aztech AZT2316 Driver" + depends on SND + select SND_WSS_LIB + select SND_MPU401_UART + select SND_OPL3_LIB + help + Say Y here to include support for Aztech Sound Galaxy cards + based on the AZT2316 chipset. + + To compile this driver as a module, choose M here: the module + will be called snd-azt2316. + +config SND_AZT2320 + tristate "Aztech Systems AZT2320" + depends on PNP + select ISAPNP + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y here to include support for soundcards based on the + Aztech Systems AZT2320 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-azt2320. + +config SND_CMI8328 + tristate "C-Media CMI8328" + select SND_WSS_LIB + select SND_OPL3_LIB + select SND_MPU401_UART + help + Say Y here to include support for soundcards based on the + C-Media CMI8328 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-cmi8328. + +config SND_CMI8330 + tristate "C-Media CMI8330" + select SND_WSS_LIB + select SND_SB16_DSP + select SND_OPL3_LIB + select SND_MPU401_UART + help + Say Y here to include support for soundcards based on the + C-Media CMI8330 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-cmi8330. + +config SND_CS4231 + tristate "Generic Cirrus Logic CS4231 driver" + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y here to include support for CS4231 chips from Cirrus + Logic - Crystal Semiconductors. + + To compile this driver as a module, choose M here: the module + will be called snd-cs4231. + +config SND_CS4236 + tristate "Generic Cirrus Logic CS4232/CS4236+ driver" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y to include support for CS4232,CS4235,CS4236,CS4237B, + CS4238B,CS4239 chips from Cirrus Logic - Crystal + Semiconductors. + + To compile this driver as a module, choose M here: the module + will be called snd-cs4236. + +config SND_ES1688 + tristate "Generic ESS ES688/ES1688 and ES968 PnP driver" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for ESS AudioDrive ES688 or + ES1688 chips. Also, this module support cards with ES968 PnP chip. + + To compile this driver as a module, choose M here: the module + will be called snd-es1688. + +config SND_ES18XX + tristate "Generic ESS ES18xx driver" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for ESS AudioDrive ES18xx chips. + + To compile this driver as a module, choose M here: the module + will be called snd-es18xx. + +config SND_SC6000 + tristate "Gallant SC-6000/6600/7000 and Audio Excel DSP 16" + depends on HAS_IOPORT_MAP + select SND_WSS_LIB + select SND_OPL3_LIB + select SND_MPU401_UART + help + Say Y here to include support for Gallant SC-6000, SC-6600, SC-7000 + cards and clones: + Audio Excel DSP 16 and Zoltrix AV302. + + These cards are based on CompuMedia ASC-9308 or ASC-9408 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-sc6000. + +config SND_GUSCLASSIC + tristate "Gravis UltraSound Classic" + select SND_RAWMIDI + select SND_PCM + select SND_TIMER + help + Say Y here to include support for Gravis UltraSound Classic + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-gusclassic. + +config SND_GUSEXTREME + tristate "Gravis UltraSound Extreme" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + select SND_TIMER + help + Say Y here to include support for Gravis UltraSound Extreme + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-gusextreme. + +config SND_GUSMAX + tristate "Gravis UltraSound MAX" + select SND_RAWMIDI + select SND_WSS_LIB + help + Say Y here to include support for Gravis UltraSound MAX + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-gusmax. + +config SND_INTERWAVE + tristate "AMD InterWave, Gravis UltraSound PnP" + depends on PNP + select SND_RAWMIDI + select SND_WSS_LIB + help + Say Y here to include support for AMD InterWave based + soundcards (Gravis UltraSound Plug & Play, STB SoundRage32, + MED3210, Dynasonic Pro, Panasonic PCA761AW). + + To compile this driver as a module, choose M here: the module + will be called snd-interwave. + +config SND_INTERWAVE_STB + tristate "AMD InterWave + TEA6330T (UltraSound 32-Pro)" + depends on PNP + select SND_RAWMIDI + select SND_WSS_LIB + help + Say Y here to include support for AMD InterWave based + soundcards with a TEA6330T bass and treble regulator + (UltraSound 32-Pro). + + To compile this driver as a module, choose M here: the module + will be called snd-interwave-stb. + +config SND_JAZZ16 + tristate "Media Vision Jazz16 card and compatibles" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_SB8_DSP + help + Say Y here to include support for soundcards based on the + Media Vision Jazz16 chipset: digital chip MVD1216 (Jazz16), + codec MVA416 (CS4216) and mixer MVA514 (ICS2514). + Media Vision's Jazz16 cards were sold under names Pro Sonic 16, + Premium 3-D and Pro 3-D. There were also OEMs cards with the + Jazz16 chipset. + + To compile this driver as a module, choose M here: the module + will be called snd-jazz16. + +config SND_OPL3SA2 + tristate "Yamaha OPL3-SA2/SA3" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y here to include support for Yamaha OPL3-SA2 and OPL3-SA3 + chips. + + To compile this driver as a module, choose M here: the module + will be called snd-opl3sa2. + +config SND_OPTI92X_AD1848 + tristate "OPTi 82C92x - AD1848" + select SND_OPL3_LIB + select SND_OPL4_LIB + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y here to include support for soundcards based on Opti + 82C92x or OTI-601 chips and using an AD1848 codec. + + To compile this driver as a module, choose M here: the module + will be called snd-opti92x-ad1848. + +config SND_OPTI92X_CS4231 + tristate "OPTi 82C92x - CS4231" + select SND_OPL3_LIB + select SND_OPL4_LIB + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y here to include support for soundcards based on Opti + 82C92x chips and using a CS4231 codec. + + To compile this driver as a module, choose M here: the module + will be called snd-opti92x-cs4231. + +config SND_OPTI93X + tristate "OPTi 82C93x" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y here to include support for soundcards based on Opti + 82C93x chips. + + To compile this driver as a module, choose M here: the module + will be called snd-opti93x. + +config SND_MIRO + tristate "Miro miroSOUND PCM1pro/PCM12/PCM20radio driver" + select SND_OPL4_LIB + select SND_WSS_LIB + select SND_MPU401_UART + select SND_PCM + help + Say 'Y' or 'M' to include support for Miro miroSOUND PCM1 pro, + miroSOUND PCM12 and miroSOUND PCM20 Radio soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-miro. + +config SND_SB8 + tristate "Sound Blaster 1.0/2.0/Pro (8-bit)" + select SND_OPL3_LIB + select SND_RAWMIDI + select SND_SB8_DSP + help + Say Y here to include support for Creative Sound Blaster 1.0/ + 2.0/Pro (8-bit) or 100% compatible soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-sb8. + +config SND_SB16 + tristate "Sound Blaster 16 (PnP)" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_SB16_DSP + help + Say Y here to include support for Sound Blaster 16 soundcards + (including the Plug and Play version). + + To compile this driver as a module, choose M here: the module + will be called snd-sb16. + +config SND_SBAWE + tristate "Sound Blaster AWE (32,64) (PnP)" + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_SB16_DSP + select SND_SEQ_DEVICE if SND_SEQUENCER != n + help + Say Y here to include support for Sound Blaster AWE soundcards + (including the Plug and Play version). + + To compile this driver as a module, choose M here: the module + will be called snd-sbawe. + +# select SEQ stuff to min(SND_SEQUENCER,SND_XXX) +config SND_SBAWE_SEQ + def_tristate SND_SEQUENCER && SND_SBAWE + select SND_SEQ_MIDI_EMUL + select SND_SEQ_VIRMIDI + select SND_SYNTH_EMUX + +config SND_SB16_CSP + bool "Sound Blaster 16/AWE CSP support" + depends on (SND_SB16 || SND_SBAWE) && (BROKEN || !PPC) + select FW_LOADER + help + Say Y here to include support for the CSP core. This special + coprocessor can do variable tasks like various compression and + decompression algorithms. + +config SND_SSCAPE + tristate "Ensoniq SoundScape driver" + select SND_MPU401_UART + select SND_WSS_LIB + select FW_LOADER + help + Say Y here to include support for Ensoniq SoundScape + and Ensoniq OEM soundcards. + + The PCM audio is supported on SoundScape Classic, Elite, PnP + and VIVO cards. The supported OEM cards are SPEA Media FX and + Reveal SC-600. + The MIDI support is very experimental and requires binary + firmware files called "scope.cod" and "sndscape.co?" where the + ? is digit 0, 1, 2, 3 or 4. The firmware files can be found + in DOS or Windows driver packages. One has to put the firmware + files into the /lib/firmware directory. + + To compile this driver as a module, choose M here: the module + will be called snd-sscape. + +config SND_WAVEFRONT + tristate "Turtle Beach Maui,Tropez,Tropez+ (Wavefront)" + select FW_LOADER + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_WSS_LIB + help + Say Y here to include support for Turtle Beach Maui, Tropez + and Tropez+ soundcards based on the Wavefront chip. + + To compile this driver as a module, choose M here: the module + will be called snd-wavefront. + +config SND_MSND_PINNACLE + tristate "Turtle Beach MultiSound Pinnacle/Fiji driver" + depends on X86 + select FW_LOADER + select SND_MPU401_UART + select SND_PCM + help + Say Y to include support for Turtle Beach MultiSound Pinnacle/ + Fiji soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-msnd-pinnacle. + +config SND_MSND_CLASSIC + tristate "Support for Turtle Beach MultiSound Classic, Tahiti, Monterey" + depends on X86 + select FW_LOADER + select SND_MPU401_UART + select SND_PCM + help + Say M here if you have a Turtle Beach MultiSound Classic, Tahiti or + Monterey (not for the Pinnacle or Fiji). + + See for important information + about this driver. Note that it has been discontinued, but the + Voyetra Turtle Beach knowledge base entry for it is still available + at . + + To compile this driver as a module, choose M here: the module + will be called snd-msnd-classic. + +endif # SND_ISA + diff --git a/sound/isa/Makefile b/sound/isa/Makefile new file mode 100644 index 000000000..5eaddbf4a --- /dev/null +++ b/sound/isa/Makefile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-adlib-objs := adlib.o +snd-als100-objs := als100.o +snd-azt2320-objs := azt2320.o +snd-cmi8328-objs := cmi8328.o +snd-cmi8330-objs := cmi8330.o +snd-es18xx-objs := es18xx.o +snd-opl3sa2-objs := opl3sa2.o +snd-sc6000-objs := sc6000.o +snd-sscape-objs := sscape.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_ADLIB) += snd-adlib.o +obj-$(CONFIG_SND_ALS100) += snd-als100.o +obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o +obj-$(CONFIG_SND_CMI8328) += snd-cmi8328.o +obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o +obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o +obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o +obj-$(CONFIG_SND_SC6000) += snd-sc6000.o +obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o + +obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ galaxy/ gus/ msnd/ opti9xx/ \ + sb/ wavefront/ wss/ diff --git a/sound/isa/ad1816a/Makefile b/sound/isa/ad1816a/Makefile new file mode 100644 index 000000000..487ab2386 --- /dev/null +++ b/sound/isa/ad1816a/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ad1816a-objs := ad1816a.o ad1816a_lib.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AD1816A) += snd-ad1816a.o diff --git a/sound/isa/ad1816a/ad1816a.c b/sound/isa/ad1816a/ad1816a.c new file mode 100644 index 000000000..4be6c1245 --- /dev/null +++ b/sound/isa/ad1816a/ad1816a.c @@ -0,0 +1,303 @@ + +/* + card-ad1816a.c - driver for ADI SoundPort AD1816A based soundcards. + Copyright (C) 2000 by Massimo Piccioni + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PFX "ad1816a: " + +MODULE_AUTHOR("Massimo Piccioni "); +MODULE_DESCRIPTION("AD1816A, AD1815"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Highscreen,Sound-Boostar 16 3D}," + "{Analog Devices,AD1815}," + "{Analog Devices,AD1816A}," + "{TerraTec,Base 64}," + "{TerraTec,AudioSystem EWS64S}," + "{Aztech/Newcom SC-16 3D}," + "{Shark Predator ISA}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 1-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ +static int clockfreq[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ad1816a based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ad1816a based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ad1816a based soundcard."); +module_param_array(clockfreq, int, NULL, 0444); +MODULE_PARM_DESC(clockfreq, "Clock frequency for ad1816a driver (default = 0)."); + +static const struct pnp_card_device_id snd_ad1816a_pnpids[] = { + /* Analog Devices AD1815 */ + { .id = "ADS7150", .devs = { { .id = "ADS7150" }, { .id = "ADS7151" } } }, + /* Analog Device AD1816? */ + { .id = "ADS7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Analog Devices AD1816A - added by Kenneth Platz */ + { .id = "ADS7181", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Analog Devices AD1816A - Aztech/Newcom SC-16 3D */ + { .id = "AZT1022", .devs = { { .id = "AZT1018" }, { .id = "AZT2002" } } }, + /* Highscreen Sound-Boostar 16 3D - added by Stefan Behnel */ + { .id = "LWC1061", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Highscreen Sound-Boostar 16 3D */ + { .id = "MDK1605", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Shark Predator ISA - added by Ken Arromdee */ + { .id = "SMM7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */ + { .id = "TER1112", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Analog Devices AD1816A - Terratec AudioSystem EWS64 S */ + { .id = "TER1112", .devs = { { .id = "TER1100" }, { .id = "TER1101" } } }, + /* Analog Devices AD1816A - Terratec Base 64 */ + { .id = "TER1411", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* end */ + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_ad1816a_pnpids); + + +#define DRIVER_NAME "snd-card-ad1816a" + + +static int snd_card_ad1816a_pnp(int dev, struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + pdev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (pdev == NULL) + return -EBUSY; + + err = pnp_activate_dev(pdev); + if (err < 0) { + printk(KERN_ERR PFX "AUDIO PnP configure failure\n"); + return -EBUSY; + } + + port[dev] = pnp_port_start(pdev, 2); + fm_port[dev] = pnp_port_start(pdev, 1); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + + pdev = pnp_request_card_device(card, id->devs[1].id, NULL); + if (pdev == NULL) { + mpu_port[dev] = -1; + snd_printk(KERN_WARNING PFX "MPU401 device busy, skipping.\n"); + return 0; + } + + err = pnp_activate_dev(pdev); + if (err < 0) { + printk(KERN_ERR PFX "MPU401 PnP configure failure\n"); + mpu_port[dev] = -1; + } else { + mpu_port[dev] = pnp_port_start(pdev, 0); + mpu_irq[dev] = pnp_irq(pdev, 0); + } + + return 0; +} + +static int snd_card_ad1816a_probe(int dev, struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int error; + struct snd_card *card; + struct snd_ad1816a *chip; + struct snd_opl3 *opl3; + + error = snd_card_new(&pcard->card->dev, + index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_ad1816a), &card); + if (error < 0) + return error; + chip = card->private_data; + + if ((error = snd_card_ad1816a_pnp(dev, pcard, pid))) { + snd_card_free(card); + return error; + } + + if ((error = snd_ad1816a_create(card, port[dev], + irq[dev], + dma1[dev], + dma2[dev], + chip)) < 0) { + snd_card_free(card); + return error; + } + if (clockfreq[dev] >= 5000 && clockfreq[dev] <= 100000) + chip->clock_freq = clockfreq[dev]; + + strcpy(card->driver, "AD1816A"); + strcpy(card->shortname, "ADI SoundPort AD1816A"); + sprintf(card->longname, "%s, SS at 0x%lx, irq %d, dma %d&%d", + card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]); + + if ((error = snd_ad1816a_pcm(chip, 0)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_ad1816a_mixer(chip)) < 0) { + snd_card_free(card); + return error; + } + + error = snd_ad1816a_timer(chip, 0); + if (error < 0) { + snd_card_free(card); + return error; + } + + if (mpu_port[dev] > 0) { + if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + mpu_port[dev], 0, mpu_irq[dev], + NULL) < 0) + printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n", mpu_port[dev]); + } + + if (fm_port[dev] > 0) { + if (snd_opl3_create(card, + fm_port[dev], fm_port[dev] + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx.\n", fm_port[dev], fm_port[dev] + 2); + } else { + error = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (error < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static unsigned int ad1816a_devices; + +static int snd_ad1816a_pnp_detect(struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev]) + continue; + res = snd_card_ad1816a_probe(dev, card, id); + if (res < 0) + return res; + dev++; + ad1816a_devices++; + return 0; + } + return -ENODEV; +} + +static void snd_ad1816a_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_ad1816a_pnp_suspend(struct pnp_card_link *pcard, + pm_message_t state) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_ad1816a_suspend(card->private_data); + return 0; +} + +static int snd_ad1816a_pnp_resume(struct pnp_card_link *pcard) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + + snd_ad1816a_resume(card->private_data); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static struct pnp_card_driver ad1816a_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "ad1816a", + .id_table = snd_ad1816a_pnpids, + .probe = snd_ad1816a_pnp_detect, + .remove = snd_ad1816a_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_ad1816a_pnp_suspend, + .resume = snd_ad1816a_pnp_resume, +#endif +}; + +static int __init alsa_card_ad1816a_init(void) +{ + int err; + + err = pnp_register_card_driver(&ad1816a_pnpc_driver); + if (err) + return err; + + if (!ad1816a_devices) { + pnp_unregister_card_driver(&ad1816a_pnpc_driver); +#ifdef MODULE + printk(KERN_ERR "no AD1816A based soundcards found.\n"); +#endif /* MODULE */ + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_ad1816a_exit(void) +{ + pnp_unregister_card_driver(&ad1816a_pnpc_driver); +} + +module_init(alsa_card_ad1816a_init) +module_exit(alsa_card_ad1816a_exit) diff --git a/sound/isa/ad1816a/ad1816a_lib.c b/sound/isa/ad1816a/ad1816a_lib.c new file mode 100644 index 000000000..fba6d22f7 --- /dev/null +++ b/sound/isa/ad1816a/ad1816a_lib.c @@ -0,0 +1,981 @@ +/* + ad1816a.c - lowlevel code for Analog Devices AD1816A chip. + Copyright (C) 1999-2000 by Massimo Piccioni + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static inline int snd_ad1816a_busy_wait(struct snd_ad1816a *chip) +{ + int timeout; + + for (timeout = 1000; timeout-- > 0; udelay(10)) + if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY) + return 0; + + snd_printk(KERN_WARNING "chip busy.\n"); + return -EBUSY; +} + +static inline unsigned char snd_ad1816a_in(struct snd_ad1816a *chip, unsigned char reg) +{ + snd_ad1816a_busy_wait(chip); + return inb(AD1816A_REG(reg)); +} + +static inline void snd_ad1816a_out(struct snd_ad1816a *chip, unsigned char reg, + unsigned char value) +{ + snd_ad1816a_busy_wait(chip); + outb(value, AD1816A_REG(reg)); +} + +static inline void snd_ad1816a_out_mask(struct snd_ad1816a *chip, unsigned char reg, + unsigned char mask, unsigned char value) +{ + snd_ad1816a_out(chip, reg, + (value & mask) | (snd_ad1816a_in(chip, reg) & ~mask)); +} + +static unsigned short snd_ad1816a_read(struct snd_ad1816a *chip, unsigned char reg) +{ + snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f); + return snd_ad1816a_in(chip, AD1816A_INDIR_DATA_LOW) | + (snd_ad1816a_in(chip, AD1816A_INDIR_DATA_HIGH) << 8); +} + +static void snd_ad1816a_write(struct snd_ad1816a *chip, unsigned char reg, + unsigned short value) +{ + snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f); + snd_ad1816a_out(chip, AD1816A_INDIR_DATA_LOW, value & 0xff); + snd_ad1816a_out(chip, AD1816A_INDIR_DATA_HIGH, (value >> 8) & 0xff); +} + +static void snd_ad1816a_write_mask(struct snd_ad1816a *chip, unsigned char reg, + unsigned short mask, unsigned short value) +{ + snd_ad1816a_write(chip, reg, + (value & mask) | (snd_ad1816a_read(chip, reg) & ~mask)); +} + + +static unsigned char snd_ad1816a_get_format(struct snd_ad1816a *chip, + snd_pcm_format_t format, + int channels) +{ + unsigned char retval = AD1816A_FMT_LINEAR_8; + + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + retval = AD1816A_FMT_ULAW_8; + break; + case SNDRV_PCM_FORMAT_A_LAW: + retval = AD1816A_FMT_ALAW_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + retval = AD1816A_FMT_LINEAR_16_LIT; + break; + case SNDRV_PCM_FORMAT_S16_BE: + retval = AD1816A_FMT_LINEAR_16_BIG; + } + return (channels > 1) ? (retval | AD1816A_FMT_STEREO) : retval; +} + +static int snd_ad1816a_open(struct snd_ad1816a *chip, unsigned int mode) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + if (chip->mode & mode) { + spin_unlock_irqrestore(&chip->lock, flags); + return -EAGAIN; + } + + switch ((mode &= AD1816A_MODE_OPEN)) { + case AD1816A_MODE_PLAYBACK: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_PLAYBACK_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_PLAYBACK_IRQ_ENABLE, 0xffff); + break; + case AD1816A_MODE_CAPTURE: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_CAPTURE_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_CAPTURE_IRQ_ENABLE, 0xffff); + break; + case AD1816A_MODE_TIMER: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_TIMER_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_IRQ_ENABLE, 0xffff); + } + chip->mode |= mode; + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static void snd_ad1816a_close(struct snd_ad1816a *chip, unsigned int mode) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + switch ((mode &= AD1816A_MODE_OPEN)) { + case AD1816A_MODE_PLAYBACK: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_PLAYBACK_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_PLAYBACK_IRQ_ENABLE, 0x0000); + break; + case AD1816A_MODE_CAPTURE: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_CAPTURE_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_CAPTURE_IRQ_ENABLE, 0x0000); + break; + case AD1816A_MODE_TIMER: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_TIMER_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_IRQ_ENABLE, 0x0000); + } + if (!((chip->mode &= ~mode) & AD1816A_MODE_OPEN)) + chip->mode = 0; + + spin_unlock_irqrestore(&chip->lock, flags); +} + + +static int snd_ad1816a_trigger(struct snd_ad1816a *chip, unsigned char what, + int channel, int cmd, int iscapture) +{ + int error = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + spin_lock(&chip->lock); + cmd = (cmd == SNDRV_PCM_TRIGGER_START) ? 0xff: 0x00; + /* if (what & AD1816A_PLAYBACK_ENABLE) */ + /* That is not valid, because playback and capture enable + * are the same bit pattern, just to different addresses + */ + if (! iscapture) + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_PLAYBACK_ENABLE, cmd); + else + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_CAPTURE_ENABLE, cmd); + spin_unlock(&chip->lock); + break; + default: + snd_printk(KERN_WARNING "invalid trigger mode 0x%x.\n", what); + error = -EINVAL; + } + + return error; +} + +static int snd_ad1816a_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + return snd_ad1816a_trigger(chip, AD1816A_PLAYBACK_ENABLE, + SNDRV_PCM_STREAM_PLAYBACK, cmd, 0); +} + +static int snd_ad1816a_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + return snd_ad1816a_trigger(chip, AD1816A_CAPTURE_ENABLE, + SNDRV_PCM_STREAM_CAPTURE, cmd, 1); +} + +static int snd_ad1816a_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_ad1816a_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_ad1816a_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + unsigned long flags; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size, rate; + + spin_lock_irqsave(&chip->lock, flags); + + chip->p_dma_size = size = snd_pcm_lib_buffer_bytes(substream); + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00); + + snd_dma_program(chip->dma1, runtime->dma_addr, size, + DMA_MODE_WRITE | DMA_AUTOINIT); + + rate = runtime->rate; + if (chip->clock_freq) + rate = (rate * 33000) / chip->clock_freq; + snd_ad1816a_write(chip, AD1816A_PLAYBACK_SAMPLE_RATE, rate); + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_FMT_ALL | AD1816A_FMT_STEREO, + snd_ad1816a_get_format(chip, runtime->format, + runtime->channels)); + + snd_ad1816a_write(chip, AD1816A_PLAYBACK_BASE_COUNT, + snd_pcm_lib_period_bytes(substream) / 4 - 1); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_ad1816a_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + unsigned long flags; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size, rate; + + spin_lock_irqsave(&chip->lock, flags); + + chip->c_dma_size = size = snd_pcm_lib_buffer_bytes(substream); + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00); + + snd_dma_program(chip->dma2, runtime->dma_addr, size, + DMA_MODE_READ | DMA_AUTOINIT); + + rate = runtime->rate; + if (chip->clock_freq) + rate = (rate * 33000) / chip->clock_freq; + snd_ad1816a_write(chip, AD1816A_CAPTURE_SAMPLE_RATE, rate); + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_FMT_ALL | AD1816A_FMT_STEREO, + snd_ad1816a_get_format(chip, runtime->format, + runtime->channels)); + + snd_ad1816a_write(chip, AD1816A_CAPTURE_BASE_COUNT, + snd_pcm_lib_period_bytes(substream) / 4 - 1); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + + +static snd_pcm_uframes_t snd_ad1816a_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + size_t ptr; + if (!(chip->mode & AD1816A_MODE_PLAYBACK)) + return 0; + ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_ad1816a_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + size_t ptr; + if (!(chip->mode & AD1816A_MODE_CAPTURE)) + return 0; + ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + + +static irqreturn_t snd_ad1816a_interrupt(int irq, void *dev_id) +{ + struct snd_ad1816a *chip = dev_id; + unsigned char status; + + spin_lock(&chip->lock); + status = snd_ad1816a_in(chip, AD1816A_INTERRUPT_STATUS); + spin_unlock(&chip->lock); + + if ((status & AD1816A_PLAYBACK_IRQ_PENDING) && chip->playback_substream) + snd_pcm_period_elapsed(chip->playback_substream); + + if ((status & AD1816A_CAPTURE_IRQ_PENDING) && chip->capture_substream) + snd_pcm_period_elapsed(chip->capture_substream); + + if ((status & AD1816A_TIMER_IRQ_PENDING) && chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + + spin_lock(&chip->lock); + snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00); + spin_unlock(&chip->lock); + return IRQ_HANDLED; +} + + +static const struct snd_pcm_hardware snd_ad1816a_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 55200, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_ad1816a_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 55200, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_ad1816a_timer_close(struct snd_timer *timer) +{ + struct snd_ad1816a *chip = snd_timer_chip(timer); + snd_ad1816a_close(chip, AD1816A_MODE_TIMER); + return 0; +} + +static int snd_ad1816a_timer_open(struct snd_timer *timer) +{ + struct snd_ad1816a *chip = snd_timer_chip(timer); + snd_ad1816a_open(chip, AD1816A_MODE_TIMER); + return 0; +} + +static unsigned long snd_ad1816a_timer_resolution(struct snd_timer *timer) +{ + if (snd_BUG_ON(!timer)) + return 0; + + return 10000; +} + +static int snd_ad1816a_timer_start(struct snd_timer *timer) +{ + unsigned short bits; + unsigned long flags; + struct snd_ad1816a *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->lock, flags); + bits = snd_ad1816a_read(chip, AD1816A_INTERRUPT_ENABLE); + + if (!(bits & AD1816A_TIMER_ENABLE)) { + snd_ad1816a_write(chip, AD1816A_TIMER_BASE_COUNT, + timer->sticks & 0xffff); + + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_ENABLE, 0xffff); + } + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_ad1816a_timer_stop(struct snd_timer *timer) +{ + unsigned long flags; + struct snd_ad1816a *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->lock, flags); + + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_ENABLE, 0x0000); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static struct snd_timer_hardware snd_ad1816a_timer_table = { + .flags = SNDRV_TIMER_HW_AUTO, + .resolution = 10000, + .ticks = 65535, + .open = snd_ad1816a_timer_open, + .close = snd_ad1816a_timer_close, + .c_resolution = snd_ad1816a_timer_resolution, + .start = snd_ad1816a_timer_start, + .stop = snd_ad1816a_timer_stop, +}; + +static int snd_ad1816a_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int error; + + if ((error = snd_ad1816a_open(chip, AD1816A_MODE_PLAYBACK)) < 0) + return error; + runtime->hw = snd_ad1816a_playback; + snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max); + chip->playback_substream = substream; + return 0; +} + +static int snd_ad1816a_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int error; + + if ((error = snd_ad1816a_open(chip, AD1816A_MODE_CAPTURE)) < 0) + return error; + runtime->hw = snd_ad1816a_capture; + snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max); + chip->capture_substream = substream; + return 0; +} + +static int snd_ad1816a_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + snd_ad1816a_close(chip, AD1816A_MODE_PLAYBACK); + return 0; +} + +static int snd_ad1816a_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ad1816a *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + snd_ad1816a_close(chip, AD1816A_MODE_CAPTURE); + return 0; +} + + +static void snd_ad1816a_init(struct snd_ad1816a *chip) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00); + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00); + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00); + snd_ad1816a_write(chip, AD1816A_INTERRUPT_ENABLE, 0x0000); + snd_ad1816a_write_mask(chip, AD1816A_CHIP_CONFIG, + AD1816A_CAPTURE_NOT_EQUAL | AD1816A_WSS_ENABLE, 0xffff); + snd_ad1816a_write(chip, AD1816A_DSP_CONFIG, 0x0000); + snd_ad1816a_write(chip, AD1816A_POWERDOWN_CTRL, 0x0000); + + spin_unlock_irqrestore(&chip->lock, flags); +} + +#ifdef CONFIG_PM +void snd_ad1816a_suspend(struct snd_ad1816a *chip) +{ + int reg; + unsigned long flags; + + snd_pcm_suspend_all(chip->pcm); + spin_lock_irqsave(&chip->lock, flags); + for (reg = 0; reg < 48; reg++) + chip->image[reg] = snd_ad1816a_read(chip, reg); + spin_unlock_irqrestore(&chip->lock, flags); +} + +void snd_ad1816a_resume(struct snd_ad1816a *chip) +{ + int reg; + unsigned long flags; + + snd_ad1816a_init(chip); + spin_lock_irqsave(&chip->lock, flags); + for (reg = 0; reg < 48; reg++) + snd_ad1816a_write(chip, reg, chip->image[reg]); + spin_unlock_irqrestore(&chip->lock, flags); +} +#endif + +static int snd_ad1816a_probe(struct snd_ad1816a *chip) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + switch (chip->version = snd_ad1816a_read(chip, AD1816A_VERSION_ID)) { + case 0: + chip->hardware = AD1816A_HW_AD1815; + break; + case 1: + chip->hardware = AD1816A_HW_AD18MAX10; + break; + case 3: + chip->hardware = AD1816A_HW_AD1816A; + break; + default: + chip->hardware = AD1816A_HW_AUTO; + } + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_ad1816a_free(struct snd_ad1816a *chip) +{ + release_and_free_resource(chip->res_port); + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); + if (chip->dma1 >= 0) { + snd_dma_disable(chip->dma1); + free_dma(chip->dma1); + } + if (chip->dma2 >= 0) { + snd_dma_disable(chip->dma2); + free_dma(chip->dma2); + } + return 0; +} + +static int snd_ad1816a_dev_free(struct snd_device *device) +{ + struct snd_ad1816a *chip = device->device_data; + return snd_ad1816a_free(chip); +} + +static const char *snd_ad1816a_chip_id(struct snd_ad1816a *chip) +{ + switch (chip->hardware) { + case AD1816A_HW_AD1816A: return "AD1816A"; + case AD1816A_HW_AD1815: return "AD1815"; + case AD1816A_HW_AD18MAX10: return "AD18max10"; + default: + snd_printk(KERN_WARNING "Unknown chip version %d:%d.\n", + chip->version, chip->hardware); + return "AD1816A - unknown"; + } +} + +int snd_ad1816a_create(struct snd_card *card, + unsigned long port, int irq, int dma1, int dma2, + struct snd_ad1816a *chip) +{ + static struct snd_device_ops ops = { + .dev_free = snd_ad1816a_dev_free, + }; + int error; + + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + + if ((chip->res_port = request_region(port, 16, "AD1816A")) == NULL) { + snd_printk(KERN_ERR "ad1816a: can't grab port 0x%lx\n", port); + snd_ad1816a_free(chip); + return -EBUSY; + } + if (request_irq(irq, snd_ad1816a_interrupt, 0, "AD1816A", (void *) chip)) { + snd_printk(KERN_ERR "ad1816a: can't grab IRQ %d\n", irq); + snd_ad1816a_free(chip); + return -EBUSY; + } + chip->irq = irq; + if (request_dma(dma1, "AD1816A - 1")) { + snd_printk(KERN_ERR "ad1816a: can't grab DMA1 %d\n", dma1); + snd_ad1816a_free(chip); + return -EBUSY; + } + chip->dma1 = dma1; + if (request_dma(dma2, "AD1816A - 2")) { + snd_printk(KERN_ERR "ad1816a: can't grab DMA2 %d\n", dma2); + snd_ad1816a_free(chip); + return -EBUSY; + } + chip->dma2 = dma2; + + chip->card = card; + chip->port = port; + spin_lock_init(&chip->lock); + + if ((error = snd_ad1816a_probe(chip))) { + snd_ad1816a_free(chip); + return error; + } + + snd_ad1816a_init(chip); + + /* Register device */ + if ((error = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_ad1816a_free(chip); + return error; + } + + return 0; +} + +static const struct snd_pcm_ops snd_ad1816a_playback_ops = { + .open = snd_ad1816a_playback_open, + .close = snd_ad1816a_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ad1816a_hw_params, + .hw_free = snd_ad1816a_hw_free, + .prepare = snd_ad1816a_playback_prepare, + .trigger = snd_ad1816a_playback_trigger, + .pointer = snd_ad1816a_playback_pointer, +}; + +static const struct snd_pcm_ops snd_ad1816a_capture_ops = { + .open = snd_ad1816a_capture_open, + .close = snd_ad1816a_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ad1816a_hw_params, + .hw_free = snd_ad1816a_hw_free, + .prepare = snd_ad1816a_capture_prepare, + .trigger = snd_ad1816a_capture_trigger, + .pointer = snd_ad1816a_capture_pointer, +}; + +int snd_ad1816a_pcm(struct snd_ad1816a *chip, int device) +{ + int error; + struct snd_pcm *pcm; + + if ((error = snd_pcm_new(chip->card, "AD1816A", device, 1, 1, &pcm))) + return error; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1816a_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1816a_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = (chip->dma1 == chip->dma2 ) ? SNDRV_PCM_INFO_JOINT_DUPLEX : 0; + + strcpy(pcm->name, snd_ad1816a_chip_id(chip)); + snd_ad1816a_init(chip); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024); + + chip->pcm = pcm; + return 0; +} + +int snd_ad1816a_timer(struct snd_ad1816a *chip, int device) +{ + struct snd_timer *timer; + struct snd_timer_id tid; + int error; + + tid.dev_class = SNDRV_TIMER_CLASS_CARD; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = chip->card->number; + tid.device = device; + tid.subdevice = 0; + if ((error = snd_timer_new(chip->card, "AD1816A", &tid, &timer)) < 0) + return error; + strcpy(timer->name, snd_ad1816a_chip_id(chip)); + timer->private_data = chip; + chip->timer = timer; + timer->hw = snd_ad1816a_timer_table; + return 0; +} + +/* + * + */ + +static int snd_ad1816a_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[8] = { + "Line", "Mix", "CD", "Synth", "Video", + "Mic", "Phone", + }; + + return snd_ctl_enum_info(uinfo, 2, 7, texts); +} + +static int snd_ad1816a_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned short val; + + spin_lock_irqsave(&chip->lock, flags); + val = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL); + spin_unlock_irqrestore(&chip->lock, flags); + ucontrol->value.enumerated.item[0] = (val >> 12) & 7; + ucontrol->value.enumerated.item[1] = (val >> 4) & 7; + return 0; +} + +static int snd_ad1816a_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned short val; + int change; + + if (ucontrol->value.enumerated.item[0] > 6 || + ucontrol->value.enumerated.item[1] > 6) + return -EINVAL; + val = (ucontrol->value.enumerated.item[0] << 12) | + (ucontrol->value.enumerated.item[1] << 4); + spin_lock_irqsave(&chip->lock, flags); + change = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL) != val; + snd_ad1816a_write(chip, AD1816A_ADC_SOURCE_SEL, val); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +#define AD1816A_SINGLE_TLV(xname, reg, shift, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .name = xname, .info = snd_ad1816a_info_single, \ + .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \ + .tlv = { .p = (xtlv) } } +#define AD1816A_SINGLE(xname, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_single, \ + .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_ad1816a_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_ad1816a_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->lock, flags); + ucontrol->value.integer.value[0] = (snd_ad1816a_read(chip, reg) >> shift) & mask; + spin_unlock_irqrestore(&chip->lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_ad1816a_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short old_val, val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->lock, flags); + old_val = snd_ad1816a_read(chip, reg); + val = (old_val & ~(mask << shift)) | val; + change = val != old_val; + snd_ad1816a_write(chip, reg, val); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +#define AD1816A_DOUBLE_TLV(xname, reg, shift_left, shift_right, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .name = xname, .info = snd_ad1816a_info_double, \ + .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \ + .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24), \ + .tlv = { .p = (xtlv) } } + +#define AD1816A_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_double, \ + .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \ + .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) } + +static int snd_ad1816a_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_ad1816a_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift_left = (kcontrol->private_value >> 8) & 0x0f; + int shift_right = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + unsigned short val; + + spin_lock_irqsave(&chip->lock, flags); + val = snd_ad1816a_read(chip, reg); + ucontrol->value.integer.value[0] = (val >> shift_left) & mask; + ucontrol->value.integer.value[1] = (val >> shift_right) & mask; + spin_unlock_irqrestore(&chip->lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_ad1816a_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ad1816a *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift_left = (kcontrol->private_value >> 8) & 0x0f; + int shift_right = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short old_val, val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->lock, flags); + old_val = snd_ad1816a_read(chip, reg); + val1 = (old_val & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != old_val; + snd_ad1816a_write(chip, reg, val1); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0); + +static struct snd_kcontrol_new snd_ad1816a_controls[] = { +AD1816A_DOUBLE("Master Playback Switch", AD1816A_MASTER_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("Master Playback Volume", AD1816A_MASTER_ATT, 8, 0, 31, 1, + db_scale_5bit), +AD1816A_DOUBLE("PCM Playback Switch", AD1816A_VOICE_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("PCM Playback Volume", AD1816A_VOICE_ATT, 8, 0, 63, 1, + db_scale_6bit), +AD1816A_DOUBLE("Line Playback Switch", AD1816A_LINE_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("Line Playback Volume", AD1816A_LINE_GAIN_ATT, 8, 0, 31, 1, + db_scale_5bit_12db_max), +AD1816A_DOUBLE("CD Playback Switch", AD1816A_CD_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("CD Playback Volume", AD1816A_CD_GAIN_ATT, 8, 0, 31, 1, + db_scale_5bit_12db_max), +AD1816A_DOUBLE("Synth Playback Switch", AD1816A_SYNTH_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("Synth Playback Volume", AD1816A_SYNTH_GAIN_ATT, 8, 0, 31, 1, + db_scale_5bit_12db_max), +AD1816A_DOUBLE("FM Playback Switch", AD1816A_FM_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("FM Playback Volume", AD1816A_FM_ATT, 8, 0, 63, 1, + db_scale_6bit), +AD1816A_SINGLE("Mic Playback Switch", AD1816A_MIC_GAIN_ATT, 15, 1, 1), +AD1816A_SINGLE_TLV("Mic Playback Volume", AD1816A_MIC_GAIN_ATT, 8, 31, 1, + db_scale_5bit_12db_max), +AD1816A_SINGLE("Mic Boost", AD1816A_MIC_GAIN_ATT, 14, 1, 0), +AD1816A_DOUBLE("Video Playback Switch", AD1816A_VID_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("Video Playback Volume", AD1816A_VID_GAIN_ATT, 8, 0, 31, 1, + db_scale_5bit_12db_max), +AD1816A_SINGLE("Phone Capture Switch", AD1816A_PHONE_IN_GAIN_ATT, 15, 1, 1), +AD1816A_SINGLE_TLV("Phone Capture Volume", AD1816A_PHONE_IN_GAIN_ATT, 0, 15, 1, + db_scale_4bit), +AD1816A_SINGLE("Phone Playback Switch", AD1816A_PHONE_OUT_ATT, 7, 1, 1), +AD1816A_SINGLE_TLV("Phone Playback Volume", AD1816A_PHONE_OUT_ATT, 0, 31, 1, + db_scale_5bit), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_ad1816a_info_mux, + .get = snd_ad1816a_get_mux, + .put = snd_ad1816a_put_mux, +}, +AD1816A_DOUBLE("Capture Switch", AD1816A_ADC_PGA, 15, 7, 1, 1), +AD1816A_DOUBLE_TLV("Capture Volume", AD1816A_ADC_PGA, 8, 0, 15, 0, + db_scale_rec_gain), +AD1816A_SINGLE("3D Control - Switch", AD1816A_3D_PHAT_CTRL, 15, 1, 1), +AD1816A_SINGLE("3D Control - Level", AD1816A_3D_PHAT_CTRL, 0, 15, 0), +}; + +int snd_ad1816a_mixer(struct snd_ad1816a *chip) +{ + struct snd_card *card; + unsigned int idx; + int err; + + if (snd_BUG_ON(!chip || !chip->card)) + return -EINVAL; + + card = chip->card; + + strcpy(card->mixername, snd_ad1816a_chip_id(chip)); + + for (idx = 0; idx < ARRAY_SIZE(snd_ad1816a_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ad1816a_controls[idx], chip))) < 0) + return err; + } + return 0; +} diff --git a/sound/isa/ad1848/Makefile b/sound/isa/ad1848/Makefile new file mode 100644 index 000000000..3d6dea3ff --- /dev/null +++ b/sound/isa/ad1848/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ad1848-objs := ad1848.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AD1848) += snd-ad1848.o + diff --git a/sound/isa/ad1848/ad1848.c b/sound/isa/ad1848/ad1848.c new file mode 100644 index 000000000..7c8e92f62 --- /dev/null +++ b/sound/isa/ad1848/ad1848.c @@ -0,0 +1,177 @@ +/* + * Generic driver for AD1848/AD1847/CS4248 chips (0.1 Alpha) + * Copyright (c) by Tugrul Galatali , + * Jaroslav Kysela + * Based on card-4232.c by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CRD_NAME "Generic AD1848/AD1847/CS4248" +#define DEV_NAME "ad1848" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Tugrul Galatali , Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Analog Devices,AD1848}," + "{Analog Devices,AD1847}," + "{Crystal Semiconductors,CS4248}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static bool thinkpad[SNDRV_CARDS]; /* Thinkpad special case */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver."); +module_param_array(thinkpad, bool, NULL, 0444); +MODULE_PARM_DESC(thinkpad, "Enable only for the onboard CS4248 of IBM Thinkpad 360/750/755 series."); + +static int snd_ad1848_match(struct device *dev, unsigned int n) +{ + if (!enable[n]) + return 0; + + if (port[n] == SNDRV_AUTO_PORT) { + dev_err(dev, "please specify port\n"); + return 0; + } + if (irq[n] == SNDRV_AUTO_IRQ) { + dev_err(dev, "please specify irq\n"); + return 0; + } + if (dma1[n] == SNDRV_AUTO_DMA) { + dev_err(dev, "please specify dma1\n"); + return 0; + } + return 1; +} + +static int snd_ad1848_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + struct snd_wss *chip; + int error; + + error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card); + if (error < 0) + return error; + + error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], -1, + thinkpad[n] ? WSS_HW_THINKPAD : WSS_HW_DETECT, + 0, &chip); + if (error < 0) + goto out; + + card->private_data = chip; + + error = snd_wss_pcm(chip, 0); + if (error < 0) + goto out; + + error = snd_wss_mixer(chip); + if (error < 0) + goto out; + + strlcpy(card->driver, "AD1848", sizeof(card->driver)); + strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname)); + + if (!thinkpad[n]) + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %d, dma %d", + chip->pcm->name, chip->port, irq[n], dma1[n]); + else + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %d, dma %d [Thinkpad]", + chip->pcm->name, chip->port, irq[n], dma1[n]); + + error = snd_card_register(card); + if (error < 0) + goto out; + + dev_set_drvdata(dev, card); + return 0; + +out: snd_card_free(card); + return error; +} + +static int snd_ad1848_remove(struct device *dev, unsigned int n) +{ + snd_card_free(dev_get_drvdata(dev)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_ad1848_suspend(struct device *dev, unsigned int n, pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_wss *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + chip->suspend(chip); + return 0; +} + +static int snd_ad1848_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_wss *chip = card->private_data; + + chip->resume(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static struct isa_driver snd_ad1848_driver = { + .match = snd_ad1848_match, + .probe = snd_ad1848_probe, + .remove = snd_ad1848_remove, +#ifdef CONFIG_PM + .suspend = snd_ad1848_suspend, + .resume = snd_ad1848_resume, +#endif + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_ad1848_driver, SNDRV_CARDS); diff --git a/sound/isa/adlib.c b/sound/isa/adlib.c new file mode 100644 index 000000000..5fb619eca --- /dev/null +++ b/sound/isa/adlib.c @@ -0,0 +1,115 @@ +/* + * AdLib FM card driver. + */ + +#include +#include +#include +#include +#include +#include + +#define CRD_NAME "AdLib FM" +#define DEV_NAME "adlib" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Rene Herman"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); + +static int snd_adlib_match(struct device *dev, unsigned int n) +{ + if (!enable[n]) + return 0; + + if (port[n] == SNDRV_AUTO_PORT) { + dev_err(dev, "please specify port\n"); + return 0; + } + return 1; +} + +static void snd_adlib_free(struct snd_card *card) +{ + release_and_free_resource(card->private_data); +} + +static int snd_adlib_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + struct snd_opl3 *opl3; + int error; + + error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card); + if (error < 0) { + dev_err(dev, "could not create card\n"); + return error; + } + + card->private_data = request_region(port[n], 4, CRD_NAME); + if (!card->private_data) { + dev_err(dev, "could not grab ports\n"); + error = -EBUSY; + goto out; + } + card->private_free = snd_adlib_free; + + strcpy(card->driver, DEV_NAME); + strcpy(card->shortname, CRD_NAME); + sprintf(card->longname, CRD_NAME " at %#lx", port[n]); + + error = snd_opl3_create(card, port[n], port[n] + 2, OPL3_HW_AUTO, 1, &opl3); + if (error < 0) { + dev_err(dev, "could not create OPL\n"); + goto out; + } + + error = snd_opl3_hwdep_new(opl3, 0, 0, NULL); + if (error < 0) { + dev_err(dev, "could not create FM\n"); + goto out; + } + + error = snd_card_register(card); + if (error < 0) { + dev_err(dev, "could not register card\n"); + goto out; + } + + dev_set_drvdata(dev, card); + return 0; + +out: snd_card_free(card); + return error; +} + +static int snd_adlib_remove(struct device *dev, unsigned int n) +{ + snd_card_free(dev_get_drvdata(dev)); + return 0; +} + +static struct isa_driver snd_adlib_driver = { + .match = snd_adlib_match, + .probe = snd_adlib_probe, + .remove = snd_adlib_remove, + + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_adlib_driver, SNDRV_CARDS); diff --git a/sound/isa/als100.c b/sound/isa/als100.c new file mode 100644 index 000000000..f63142ec2 --- /dev/null +++ b/sound/isa/als100.c @@ -0,0 +1,379 @@ + +/* + card-als100.c - driver for Avance Logic ALS100 based soundcards. + Copyright (C) 1999-2000 by Massimo Piccioni + Copyright (C) 1999-2002 by Massimo Piccioni + + Thanks to Pierfrancesco 'qM2' Passerini. + + Generalised for soundcards based on DT-0196 and ALS-007 chips + by Jonathan Woithe : June 2002. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PFX "als100: " + +MODULE_DESCRIPTION("Avance Logic ALS007/ALS1X0"); +MODULE_SUPPORTED_DEVICE("{{Diamond Technologies DT-019X}," + "{Avance Logic ALS-007}}" + "{{Avance Logic,ALS100 - PRO16PNP}," + "{Avance Logic,ALS110}," + "{Avance Logic,ALS120}," + "{Avance Logic,ALS200}," + "{3D Melody,MF1000}," + "{Digimate,3D Sound}," + "{Avance Logic,ALS120}," + "{RTL,RTL3000}}"); + +MODULE_AUTHOR("Massimo Piccioni "); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ +static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Avance Logic based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Avance Logic based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Avance Logic based soundcard."); + +MODULE_ALIAS("snd-dt019x"); + +struct snd_card_als100 { + struct pnp_dev *dev; + struct pnp_dev *devmpu; + struct pnp_dev *devopl; + struct snd_sb *chip; +}; + +static const struct pnp_card_device_id snd_als100_pnpids[] = { + /* DT197A30 */ + { .id = "RWB1688", + .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } }, + .driver_data = SB_HW_DT019X }, + /* DT0196 / ALS-007 */ + { .id = "ALS0007", + .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } }, + .driver_data = SB_HW_DT019X }, + /* ALS100 - PRO16PNP */ + { .id = "ALS0001", + .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } }, + .driver_data = SB_HW_ALS100 }, + /* ALS110 - MF1000 - Digimate 3D Sound */ + { .id = "ALS0110", + .devs = { { "@@@1001" }, { "@X@1001" }, { "@H@1001" } }, + .driver_data = SB_HW_ALS100 }, + /* ALS120 */ + { .id = "ALS0120", + .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } }, + .driver_data = SB_HW_ALS100 }, + /* ALS200 */ + { .id = "ALS0200", + .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0001" } }, + .driver_data = SB_HW_ALS100 }, + /* ALS200 OEM */ + { .id = "ALS0200", + .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0020" } }, + .driver_data = SB_HW_ALS100 }, + /* RTL3000 */ + { .id = "RTL3000", + .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } }, + .driver_data = SB_HW_ALS100 }, + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_als100_pnpids); + +static int snd_card_als100_pnp(int dev, struct snd_card_als100 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) + return -ENODEV; + + acard->devmpu = pnp_request_card_device(card, id->devs[1].id, acard->dev); + acard->devopl = pnp_request_card_device(card, id->devs[2].id, acard->dev); + + pdev = acard->dev; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); + return err; + } + port[dev] = pnp_port_start(pdev, 0); + if (id->driver_data == SB_HW_DT019X) + dma8[dev] = pnp_dma(pdev, 0); + else { + dma8[dev] = pnp_dma(pdev, 1); + dma16[dev] = pnp_dma(pdev, 0); + } + irq[dev] = pnp_irq(pdev, 0); + + pdev = acard->devmpu; + if (pdev != NULL) { + err = pnp_activate_dev(pdev); + if (err < 0) + goto __mpu_error; + mpu_port[dev] = pnp_port_start(pdev, 0); + mpu_irq[dev] = pnp_irq(pdev, 0); + } else { + __mpu_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n"); + } + acard->devmpu = NULL; + mpu_port[dev] = -1; + } + + pdev = acard->devopl; + if (pdev != NULL) { + err = pnp_activate_dev(pdev); + if (err < 0) + goto __fm_error; + fm_port[dev] = pnp_port_start(pdev, 0); + } else { + __fm_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "OPL3 pnp configure failure, skipping\n"); + } + acard->devopl = NULL; + fm_port[dev] = -1; + } + + return 0; +} + +static int snd_card_als100_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int error; + struct snd_sb *chip; + struct snd_card *card; + struct snd_card_als100 *acard; + struct snd_opl3 *opl3; + + error = snd_card_new(&pcard->card->dev, + index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_als100), &card); + if (error < 0) + return error; + acard = card->private_data; + + if ((error = snd_card_als100_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return error; + } + + if (pid->driver_data == SB_HW_DT019X) + dma16[dev] = -1; + + error = snd_sbdsp_create(card, port[dev], irq[dev], + snd_sb16dsp_interrupt, + dma8[dev], dma16[dev], + pid->driver_data, + &chip); + if (error < 0) { + snd_card_free(card); + return error; + } + acard->chip = chip; + + if (pid->driver_data == SB_HW_DT019X) { + strcpy(card->driver, "DT-019X"); + strcpy(card->shortname, "Diamond Tech. DT-019X"); + snprintf(card->longname, sizeof(card->longname), + "Diamond Tech. DT-019X, %s at 0x%lx, irq %d, dma %d", + chip->name, chip->port, irq[dev], dma8[dev]); + } else { + strcpy(card->driver, "ALS100"); + strcpy(card->shortname, "Avance Logic ALS100"); + snprintf(card->longname, sizeof(card->longname), + "Avance Logic ALS100, %s at 0x%lx, irq %d, dma %d&%d", + chip->name, chip->port, irq[dev], dma8[dev], + dma16[dev]); + } + + if ((error = snd_sb16dsp_pcm(chip, 0)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_sbmixer_new(chip)) < 0) { + snd_card_free(card); + return error; + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + int mpu_type = MPU401_HW_ALS100; + + if (mpu_irq[dev] == SNDRV_AUTO_IRQ) + mpu_irq[dev] = -1; + + if (pid->driver_data == SB_HW_DT019X) + mpu_type = MPU401_HW_MPU401; + + if (snd_mpu401_uart_new(card, 0, + mpu_type, + mpu_port[dev], 0, + mpu_irq[dev], + NULL) < 0) + snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]); + } + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, + fm_port[dev], fm_port[dev] + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n", + fm_port[dev], fm_port[dev] + 2); + } else { + if ((error = snd_opl3_timer_new(opl3, 0, 1)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static unsigned int als100_devices; + +static int snd_als100_pnp_detect(struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev]) + continue; + res = snd_card_als100_probe(dev, card, id); + if (res < 0) + return res; + dev++; + als100_devices++; + return 0; + } + return -ENODEV; +} + +static void snd_als100_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_als100_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_card_als100 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + snd_sbmixer_suspend(chip); + return 0; +} + +static int snd_als100_pnp_resume(struct pnp_card_link *pcard) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_card_als100 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_sbdsp_reset(chip); + snd_sbmixer_resume(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static struct pnp_card_driver als100_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "als100", + .id_table = snd_als100_pnpids, + .probe = snd_als100_pnp_detect, + .remove = snd_als100_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_als100_pnp_suspend, + .resume = snd_als100_pnp_resume, +#endif +}; + +static int __init alsa_card_als100_init(void) +{ + int err; + + err = pnp_register_card_driver(&als100_pnpc_driver); + if (err) + return err; + + if (!als100_devices) { + pnp_unregister_card_driver(&als100_pnpc_driver); +#ifdef MODULE + snd_printk(KERN_ERR "no Avance Logic based soundcards found\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_als100_exit(void) +{ + pnp_unregister_card_driver(&als100_pnpc_driver); +} + +module_init(alsa_card_als100_init) +module_exit(alsa_card_als100_exit) diff --git a/sound/isa/azt2320.c b/sound/isa/azt2320.c new file mode 100644 index 000000000..4e6fad4f9 --- /dev/null +++ b/sound/isa/azt2320.c @@ -0,0 +1,354 @@ +/* + card-azt2320.c - driver for Aztech Systems AZT2320 based soundcards. + Copyright (C) 1999-2000 by Massimo Piccioni + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* + This driver should provide support for most Aztech AZT2320 based cards. + Several AZT2316 chips are also supported/tested, but autoprobe doesn't + work: all module option have to be set. + + No docs available for us at Aztech headquarters !!! Unbelievable ... + No other help obtained. + + Thanks to Rainer Wiesner for the WSS + activation method (full-duplex audio!). +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PFX "azt2320: " + +MODULE_AUTHOR("Massimo Piccioni "); +MODULE_DESCRIPTION("Aztech Systems AZT2320"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Aztech Systems,PRO16V}," + "{Aztech Systems,AZT2320}," + "{Aztech Systems,AZT3300}," + "{Aztech Systems,AZT2320}," + "{Aztech Systems,AZT3000}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for azt2320 based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for azt2320 based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable azt2320 based soundcard."); + +struct snd_card_azt2320 { + int dev_no; + struct pnp_dev *dev; + struct pnp_dev *devmpu; + struct snd_wss *chip; +}; + +static const struct pnp_card_device_id snd_azt2320_pnpids[] = { + /* PRO16V */ + { .id = "AZT1008", .devs = { { "AZT1008" }, { "AZT2001" }, } }, + /* Aztech Sound Galaxy 16 */ + { .id = "AZT2320", .devs = { { "AZT0001" }, { "AZT0002" }, } }, + /* Packard Bell Sound III 336 AM/SP */ + { .id = "AZT3000", .devs = { { "AZT1003" }, { "AZT2001" }, } }, + /* AT3300 */ + { .id = "AZT3002", .devs = { { "AZT1004" }, { "AZT2001" }, } }, + /* --- */ + { .id = "AZT3005", .devs = { { "AZT1003" }, { "AZT2001" }, } }, + /* --- */ + { .id = "AZT3011", .devs = { { "AZT1003" }, { "AZT2001" }, } }, + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_azt2320_pnpids); + +#define DRIVER_NAME "snd-card-azt2320" + +static int snd_card_azt2320_pnp(int dev, struct snd_card_azt2320 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) + return -ENODEV; + + acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL); + + pdev = acard->dev; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); + return err; + } + port[dev] = pnp_port_start(pdev, 0); + fm_port[dev] = pnp_port_start(pdev, 1); + wss_port[dev] = pnp_port_start(pdev, 2); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + + pdev = acard->devmpu; + if (pdev != NULL) { + err = pnp_activate_dev(pdev); + if (err < 0) + goto __mpu_error; + mpu_port[dev] = pnp_port_start(pdev, 0); + mpu_irq[dev] = pnp_irq(pdev, 0); + } else { + __mpu_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n"); + } + acard->devmpu = NULL; + mpu_port[dev] = -1; + } + + return 0; +} + +/* same of snd_sbdsp_command by Jaroslav Kysela */ +static int snd_card_azt2320_command(unsigned long port, unsigned char val) +{ + int i; + unsigned long limit; + + limit = jiffies + HZ / 10; + for (i = 50000; i && time_after(limit, jiffies); i--) + if (!(inb(port + 0x0c) & 0x80)) { + outb(val, port + 0x0c); + return 0; + } + return -EBUSY; +} + +static int snd_card_azt2320_enable_wss(unsigned long port) +{ + int error; + + if ((error = snd_card_azt2320_command(port, 0x09))) + return error; + if ((error = snd_card_azt2320_command(port, 0x00))) + return error; + + mdelay(5); + return 0; +} + +static int snd_card_azt2320_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int error; + struct snd_card *card; + struct snd_card_azt2320 *acard; + struct snd_wss *chip; + struct snd_opl3 *opl3; + + error = snd_card_new(&pcard->card->dev, + index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_azt2320), &card); + if (error < 0) + return error; + acard = card->private_data; + + if ((error = snd_card_azt2320_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return error; + } + + if ((error = snd_card_azt2320_enable_wss(port[dev]))) { + snd_card_free(card); + return error; + } + + error = snd_wss_create(card, wss_port[dev], -1, + irq[dev], + dma1[dev], dma2[dev], + WSS_HW_DETECT, 0, &chip); + if (error < 0) { + snd_card_free(card); + return error; + } + + strcpy(card->driver, "AZT2320"); + strcpy(card->shortname, "Aztech AZT2320"); + sprintf(card->longname, "%s, WSS at 0x%lx, irq %i, dma %i&%i", + card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]); + + error = snd_wss_pcm(chip, 0); + if (error < 0) { + snd_card_free(card); + return error; + } + error = snd_wss_mixer(chip); + if (error < 0) { + snd_card_free(card); + return error; + } + error = snd_wss_timer(chip, 0); + if (error < 0) { + snd_card_free(card); + return error; + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + if (snd_mpu401_uart_new(card, 0, MPU401_HW_AZT2320, + mpu_port[dev], 0, + mpu_irq[dev], NULL) < 0) + snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]); + } + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, + fm_port[dev], fm_port[dev] + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n", + fm_port[dev], fm_port[dev] + 2); + } else { + if ((error = snd_opl3_timer_new(opl3, 1, 2)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static unsigned int azt2320_devices; + +static int snd_azt2320_pnp_detect(struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev]) + continue; + res = snd_card_azt2320_probe(dev, card, id); + if (res < 0) + return res; + dev++; + azt2320_devices++; + return 0; + } + return -ENODEV; +} + +static void snd_azt2320_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_azt2320_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_card_azt2320 *acard = card->private_data; + struct snd_wss *chip = acard->chip; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + chip->suspend(chip); + return 0; +} + +static int snd_azt2320_pnp_resume(struct pnp_card_link *pcard) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_card_azt2320 *acard = card->private_data; + struct snd_wss *chip = acard->chip; + + chip->resume(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static struct pnp_card_driver azt2320_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "azt2320", + .id_table = snd_azt2320_pnpids, + .probe = snd_azt2320_pnp_detect, + .remove = snd_azt2320_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_azt2320_pnp_suspend, + .resume = snd_azt2320_pnp_resume, +#endif +}; + +static int __init alsa_card_azt2320_init(void) +{ + int err; + + err = pnp_register_card_driver(&azt2320_pnpc_driver); + if (err) + return err; + + if (!azt2320_devices) { + pnp_unregister_card_driver(&azt2320_pnpc_driver); +#ifdef MODULE + snd_printk(KERN_ERR "no AZT2320 based soundcards found\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_azt2320_exit(void) +{ + pnp_unregister_card_driver(&azt2320_pnpc_driver); +} + +module_init(alsa_card_azt2320_init) +module_exit(alsa_card_azt2320_exit) diff --git a/sound/isa/cmi8328.c b/sound/isa/cmi8328.c new file mode 100644 index 000000000..de6ef1b1c --- /dev/null +++ b/sound/isa/cmi8328.c @@ -0,0 +1,472 @@ +/* + * Driver for C-Media CMI8328-based soundcards, such as AudioExcel AV500 + * Copyright (c) 2012 Ondrej Zary + * + * AudioExcel AV500 card consists of: + * - CMI8328 - main chip (SB Pro emulation, gameport, OPL3, MPU401, CD-ROM) + * - CS4231A - WSS codec + * - Dream SAM9233+GMS950400+RAM+ROM: Wavetable MIDI, connected to MPU401 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IOPORT +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +MODULE_AUTHOR("Ondrej Zary "); +MODULE_DESCRIPTION("C-Media CMI8328"); +MODULE_LICENSE("GPL"); + +#if IS_ENABLED(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#endif + +/* I/O port is configured by jumpers on the card to one of these */ +static int cmi8328_ports[] = { 0x530, 0xe80, 0xf40, 0x604 }; +#define CMI8328_MAX ARRAY_SIZE(cmi8328_ports) + +static int index[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = -1}; +static char *id[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = NULL}; +static long port[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT}; +static int irq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ}; +static int dma1[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA}; +static int dma2[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_DMA}; +static long mpuport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_PORT}; +static int mpuirq[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = SNDRV_AUTO_IRQ}; +#ifdef SUPPORT_JOYSTICK +static bool gameport[CMI8328_MAX] = {[0 ... (CMI8328_MAX-1)] = true}; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for CMI8328 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for CMI8328 soundcard."); + +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for CMI8328 driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for CMI8328 driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 for CMI8328 driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 for CMI8328 driver."); + +module_param_hw_array(mpuport, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8328 driver."); +module_param_hw_array(mpuirq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8328 MPU-401 port."); +#ifdef SUPPORT_JOYSTICK +module_param_array(gameport, bool, NULL, 0444); +MODULE_PARM_DESC(gameport, "Enable gameport."); +#endif + +struct snd_cmi8328 { + u16 port; + u8 cfg[3]; + u8 wss_cfg; + struct snd_card *card; + struct snd_wss *wss; +#ifdef SUPPORT_JOYSTICK + struct gameport *gameport; +#endif +}; + +/* CMI8328 configuration registers */ +#define CFG1 0x61 +#define CFG1_SB_DISABLE (1 << 0) +#define CFG1_GAMEPORT (1 << 1) +/* + * bit 0: SB: 0=enabled, 1=disabled + * bit 1: gameport: 0=disabled, 1=enabled + * bits 2-4: SB IRQ: 001=3, 010=5, 011=7, 100=9, 101=10, 110=11 + * bits 5-6: SB DMA: 00=disabled (when SB disabled), 01=DMA0, 10=DMA1, 11=DMA3 + * bit 7: SB port: 0=0x220, 1=0x240 + */ +#define CFG2 0x62 +#define CFG2_MPU_ENABLE (1 << 2) +/* + * bits 0-1: CD-ROM mode: 00=disabled, 01=Panasonic, 10=Sony/Mitsumi/Wearnes, + 11=IDE + * bit 2: MPU401: 0=disabled, 1=enabled + * bits 3-4: MPU401 IRQ: 00=3, 01=5, 10=7, 11=9, + * bits 5-7: MPU401 port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x332, + 101=0x334, 110=0x336 + */ +#define CFG3 0x63 +/* + * bits 0-2: CD-ROM IRQ: 000=disabled, 001=3, 010=5, 011=7, 100=9, 101=10, + 110=11 + * bits 3-4: CD-ROM DMA: 00=disabled, 01=DMA0, 10=DMA1, 11=DMA3 + * bits 5-7: CD-ROM port: 000=0x300, 001=0x310, 010=0x320, 011=0x330, 100=0x340, + 101=0x350, 110=0x360, 111=0x370 + */ + +static u8 snd_cmi8328_cfg_read(u16 port, u8 reg) +{ + outb(0x43, port + 3); + outb(0x21, port + 3); + outb(reg, port + 3); + return inb(port); +} + +static void snd_cmi8328_cfg_write(u16 port, u8 reg, u8 val) +{ + outb(0x43, port + 3); + outb(0x21, port + 3); + outb(reg, port + 3); + outb(val, port + 3); /* yes, value goes to the same port as index */ +} + +#ifdef CONFIG_PM +static void snd_cmi8328_cfg_save(u16 port, u8 cfg[]) +{ + cfg[0] = snd_cmi8328_cfg_read(port, CFG1); + cfg[1] = snd_cmi8328_cfg_read(port, CFG2); + cfg[2] = snd_cmi8328_cfg_read(port, CFG3); +} + +static void snd_cmi8328_cfg_restore(u16 port, u8 cfg[]) +{ + snd_cmi8328_cfg_write(port, CFG1, cfg[0]); + snd_cmi8328_cfg_write(port, CFG2, cfg[1]); + snd_cmi8328_cfg_write(port, CFG3, cfg[2]); +} +#endif /* CONFIG_PM */ + +static int snd_cmi8328_mixer(struct snd_wss *chip) +{ + struct snd_card *card; + struct snd_ctl_elem_id id1, id2; + int err; + + card = chip->card; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* rename AUX0 switch to CD */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "CD Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) { + snd_printk(KERN_ERR "error renaming control\n"); + return err; + } + /* rename AUX0 volume to CD */ + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) { + snd_printk(KERN_ERR "error renaming control\n"); + return err; + } + /* rename AUX1 switch to Synth */ + strcpy(id1.name, "Aux Playback Switch"); + id1.index = 1; + strcpy(id2.name, "Synth Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) { + snd_printk(KERN_ERR "error renaming control\n"); + return err; + } + /* rename AUX1 volume to Synth */ + strcpy(id1.name, "Aux Playback Volume"); + id1.index = 1; + strcpy(id2.name, "Synth Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) { + snd_printk(KERN_ERR "error renaming control\n"); + return err; + } + + return 0; +} + +/* find index of an item in "-1"-ended array */ +static int array_find(int array[], int item) +{ + int i; + + for (i = 0; array[i] != -1; i++) + if (array[i] == item) + return i; + + return -1; +} +/* the same for long */ +static int array_find_l(long array[], long item) +{ + int i; + + for (i = 0; array[i] != -1; i++) + if (array[i] == item) + return i; + + return -1; +} + +static int snd_cmi8328_probe(struct device *pdev, unsigned int ndev) +{ + struct snd_card *card; + struct snd_opl3 *opl3; + struct snd_cmi8328 *cmi; +#ifdef SUPPORT_JOYSTICK + struct resource *res; +#endif + int err, pos; + static long mpu_ports[] = { 0x330, 0x300, 0x310, 0x320, 0x332, 0x334, + 0x336, -1 }; + static u8 mpu_port_bits[] = { 3, 0, 1, 2, 4, 5, 6 }; + static int mpu_irqs[] = { 9, 7, 5, 3, -1 }; + static u8 mpu_irq_bits[] = { 3, 2, 1, 0 }; + static int irqs[] = { 9, 10, 11, 7, -1 }; + static u8 irq_bits[] = { 2, 3, 4, 1 }; + static int dma1s[] = { 3, 1, 0, -1 }; + static u8 dma_bits[] = { 3, 2, 1 }; + static int dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, {0, -1} }; + u16 port = cmi8328_ports[ndev]; + u8 val; + + /* 0xff is invalid configuration (but settable - hope it isn't set) */ + if (snd_cmi8328_cfg_read(port, CFG1) == 0xff) + return -ENODEV; + /* the SB disable bit must NEVER EVER be cleared or the WSS dies */ + snd_cmi8328_cfg_write(port, CFG1, CFG1_SB_DISABLE); + if (snd_cmi8328_cfg_read(port, CFG1) != CFG1_SB_DISABLE) + return -ENODEV; + /* disable everything first */ + snd_cmi8328_cfg_write(port, CFG2, 0); /* disable CDROM and MPU401 */ + snd_cmi8328_cfg_write(port, CFG3, 0); /* disable CDROM IRQ and DMA */ + + if (irq[ndev] == SNDRV_AUTO_IRQ) { + irq[ndev] = snd_legacy_find_free_irq(irqs); + if (irq[ndev] < 0) { + snd_printk(KERN_ERR "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma1[ndev] == SNDRV_AUTO_DMA) { + dma1[ndev] = snd_legacy_find_free_dma(dma1s); + if (dma1[ndev] < 0) { + snd_printk(KERN_ERR "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (dma2[ndev] == SNDRV_AUTO_DMA) { + dma2[ndev] = snd_legacy_find_free_dma(dma2s[dma1[ndev] % 4]); + if (dma2[ndev] < 0) { + snd_printk(KERN_WARNING "unable to find a free DMA2, full-duplex will not work\n"); + dma2[ndev] = -1; + } + } + /* configure WSS IRQ... */ + pos = array_find(irqs, irq[ndev]); + if (pos < 0) { + snd_printk(KERN_ERR "invalid IRQ %d\n", irq[ndev]); + return -EINVAL; + } + val = irq_bits[pos] << 3; + /* ...and DMA... */ + pos = array_find(dma1s, dma1[ndev]); + if (pos < 0) { + snd_printk(KERN_ERR "invalid DMA1 %d\n", dma1[ndev]); + return -EINVAL; + } + val |= dma_bits[pos]; + /* ...and DMA2 */ + if (dma2[ndev] >= 0 && dma1[ndev] != dma2[ndev]) { + pos = array_find(dma2s[dma1[ndev]], dma2[ndev]); + if (pos < 0) { + snd_printk(KERN_ERR "invalid DMA2 %d\n", dma2[ndev]); + return -EINVAL; + } + val |= 0x04; /* enable separate capture DMA */ + } + outb(val, port); + + err = snd_card_new(pdev, index[ndev], id[ndev], THIS_MODULE, + sizeof(struct snd_cmi8328), &card); + if (err < 0) + return err; + cmi = card->private_data; + cmi->card = card; + cmi->port = port; + cmi->wss_cfg = val; + + err = snd_wss_create(card, port + 4, -1, irq[ndev], dma1[ndev], + dma2[ndev], WSS_HW_DETECT, 0, &cmi->wss); + if (err < 0) + goto error; + + err = snd_wss_pcm(cmi->wss, 0); + if (err < 0) + goto error; + + err = snd_wss_mixer(cmi->wss); + if (err < 0) + goto error; + err = snd_cmi8328_mixer(cmi->wss); + if (err < 0) + goto error; + + if (snd_wss_timer(cmi->wss, 0) < 0) + snd_printk(KERN_WARNING "error initializing WSS timer\n"); + + if (mpuport[ndev] == SNDRV_AUTO_PORT) { + mpuport[ndev] = snd_legacy_find_free_ioport(mpu_ports, 2); + if (mpuport[ndev] < 0) + snd_printk(KERN_ERR "unable to find a free MPU401 port\n"); + } + if (mpuirq[ndev] == SNDRV_AUTO_IRQ) { + mpuirq[ndev] = snd_legacy_find_free_irq(mpu_irqs); + if (mpuirq[ndev] < 0) + snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n"); + } + /* enable and configure MPU401 */ + if (mpuport[ndev] > 0 && mpuirq[ndev] > 0) { + val = CFG2_MPU_ENABLE; + pos = array_find_l(mpu_ports, mpuport[ndev]); + if (pos < 0) + snd_printk(KERN_WARNING "invalid MPU401 port 0x%lx\n", + mpuport[ndev]); + else { + val |= mpu_port_bits[pos] << 5; + pos = array_find(mpu_irqs, mpuirq[ndev]); + if (pos < 0) + snd_printk(KERN_WARNING "invalid MPU401 IRQ %d\n", + mpuirq[ndev]); + else { + val |= mpu_irq_bits[pos] << 3; + snd_cmi8328_cfg_write(port, CFG2, val); + if (snd_mpu401_uart_new(card, 0, + MPU401_HW_MPU401, mpuport[ndev], + 0, mpuirq[ndev], NULL) < 0) + snd_printk(KERN_ERR "error initializing MPU401\n"); + } + } + } + /* OPL3 is hardwired to 0x388 and cannot be disabled */ + if (snd_opl3_create(card, 0x388, 0x38a, OPL3_HW_AUTO, 0, &opl3) < 0) + snd_printk(KERN_ERR "error initializing OPL3\n"); + else + if (snd_opl3_hwdep_new(opl3, 0, 1, NULL) < 0) + snd_printk(KERN_WARNING "error initializing OPL3 hwdep\n"); + + strcpy(card->driver, "CMI8328"); + strcpy(card->shortname, "C-Media CMI8328"); + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d,%d", + card->shortname, cmi->wss->port, irq[ndev], dma1[ndev], + (dma2[ndev] >= 0) ? dma2[ndev] : dma1[ndev]); + + dev_set_drvdata(pdev, card); + err = snd_card_register(card); + if (err < 0) + goto error; +#ifdef SUPPORT_JOYSTICK + if (!gameport[ndev]) + return 0; + /* gameport is hardwired to 0x200 */ + res = request_region(0x200, 8, "CMI8328 gameport"); + if (!res) + snd_printk(KERN_WARNING "unable to allocate gameport I/O port\n"); + else { + struct gameport *gp = cmi->gameport = gameport_allocate_port(); + if (!cmi->gameport) + release_and_free_resource(res); + else { + gameport_set_name(gp, "CMI8328 Gameport"); + gameport_set_phys(gp, "%s/gameport0", dev_name(pdev)); + gameport_set_dev_parent(gp, pdev); + gp->io = 0x200; + gameport_set_port_data(gp, res); + /* Enable gameport */ + snd_cmi8328_cfg_write(port, CFG1, + CFG1_SB_DISABLE | CFG1_GAMEPORT); + gameport_register_port(gp); + } + } +#endif + return 0; +error: + snd_card_free(card); + + return err; +} + +static int snd_cmi8328_remove(struct device *pdev, unsigned int dev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct snd_cmi8328 *cmi = card->private_data; + +#ifdef SUPPORT_JOYSTICK + if (cmi->gameport) { + struct resource *res = gameport_get_port_data(cmi->gameport); + gameport_unregister_port(cmi->gameport); + release_and_free_resource(res); + } +#endif + /* disable everything */ + snd_cmi8328_cfg_write(cmi->port, CFG1, CFG1_SB_DISABLE); + snd_cmi8328_cfg_write(cmi->port, CFG2, 0); + snd_cmi8328_cfg_write(cmi->port, CFG3, 0); + snd_card_free(card); + return 0; +} + +#ifdef CONFIG_PM +static int snd_cmi8328_suspend(struct device *pdev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct snd_cmi8328 *cmi; + + if (!card) /* ignore absent devices */ + return 0; + cmi = card->private_data; + snd_cmi8328_cfg_save(cmi->port, cmi->cfg); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(cmi->wss->pcm); + cmi->wss->suspend(cmi->wss); + + return 0; +} + +static int snd_cmi8328_resume(struct device *pdev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct snd_cmi8328 *cmi; + + if (!card) /* ignore absent devices */ + return 0; + cmi = card->private_data; + snd_cmi8328_cfg_restore(cmi->port, cmi->cfg); + outb(cmi->wss_cfg, cmi->port); + cmi->wss->resume(cmi->wss); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} +#endif + +static struct isa_driver snd_cmi8328_driver = { + .probe = snd_cmi8328_probe, + .remove = snd_cmi8328_remove, +#ifdef CONFIG_PM + .suspend = snd_cmi8328_suspend, + .resume = snd_cmi8328_resume, +#endif + .driver = { + .name = "cmi8328" + }, +}; + +module_isa_driver(snd_cmi8328_driver, CMI8328_MAX); diff --git a/sound/isa/cmi8330.c b/sound/isa/cmi8330.c new file mode 100644 index 000000000..75b3d76eb --- /dev/null +++ b/sound/isa/cmi8330.c @@ -0,0 +1,780 @@ +/* + * Driver for C-Media's CMI8330 and CMI8329 soundcards. + * Copyright (c) by George Talusan + * http://www.undergrad.math.uwaterloo.ca/~gstalusa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * NOTES + * + * The extended registers contain mixer settings which are largely + * untapped for the time being. + * + * MPU401 and SPDIF are not supported yet. I don't have the hardware + * to aid in coding and testing, so I won't bother. + * + * To quickly load the module, + * + * modprobe -a snd-cmi8330 sbport=0x220 sbirq=5 sbdma8=1 + * sbdma16=5 wssport=0x530 wssirq=11 wssdma=0 fmport=0x388 + * + * This card has two mixers and two PCM devices. I've cheesed it such + * that recording and playback can be done through the same device. + * The driver "magically" routes the capturing to the AD1848 codec, + * and playback to the SB16 codec. This allows for full-duplex mode + * to some extent. + * The utilities in alsa-utils are aware of both devices, so passing + * the appropriate parameters to amixer and alsactl will give you + * full control over both mixers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + */ +/* #define ENABLE_SB_MIXER */ +#define PLAYBACK_ON_SB + +/* + */ +MODULE_AUTHOR("George Talusan "); +MODULE_DESCRIPTION("C-Media CMI8330/CMI8329"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8330,isapnp:{CMI0001,@@@0001,@X@0001}}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int sbirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int sbdma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int sbdma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int wssirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int wssdma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static long fmport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static long mpuport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int mpuirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for CMI8330/CMI8329 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for CMI8330/CMI8329 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CMI8330/CMI8329 soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif + +module_param_hw_array(sbport, long, ioport, NULL, 0444); +MODULE_PARM_DESC(sbport, "Port # for CMI8330/CMI8329 SB driver."); +module_param_hw_array(sbirq, int, irq, NULL, 0444); +MODULE_PARM_DESC(sbirq, "IRQ # for CMI8330/CMI8329 SB driver."); +module_param_hw_array(sbdma8, int, dma, NULL, 0444); +MODULE_PARM_DESC(sbdma8, "DMA8 for CMI8330/CMI8329 SB driver."); +module_param_hw_array(sbdma16, int, dma, NULL, 0444); +MODULE_PARM_DESC(sbdma16, "DMA16 for CMI8330/CMI8329 SB driver."); + +module_param_hw_array(wssport, long, ioport, NULL, 0444); +MODULE_PARM_DESC(wssport, "Port # for CMI8330/CMI8329 WSS driver."); +module_param_hw_array(wssirq, int, irq, NULL, 0444); +MODULE_PARM_DESC(wssirq, "IRQ # for CMI8330/CMI8329 WSS driver."); +module_param_hw_array(wssdma, int, dma, NULL, 0444); +MODULE_PARM_DESC(wssdma, "DMA for CMI8330/CMI8329 WSS driver."); + +module_param_hw_array(fmport, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fmport, "FM port # for CMI8330/CMI8329 driver."); +module_param_hw_array(mpuport, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpuport, "MPU-401 port # for CMI8330/CMI8329 driver."); +module_param_hw_array(mpuirq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpuirq, "IRQ # for CMI8330/CMI8329 MPU-401 port."); +#ifdef CONFIG_PNP +static int isa_registered; +static int pnp_registered; +#endif + +#define CMI8330_RMUX3D 16 +#define CMI8330_MUTEMUX 17 +#define CMI8330_OUTPUTVOL 18 +#define CMI8330_MASTVOL 19 +#define CMI8330_LINVOL 20 +#define CMI8330_CDINVOL 21 +#define CMI8330_WAVVOL 22 +#define CMI8330_RECMUX 23 +#define CMI8330_WAVGAIN 24 +#define CMI8330_LINGAIN 25 +#define CMI8330_CDINGAIN 26 + +static unsigned char snd_cmi8330_image[((CMI8330_CDINGAIN)-16) + 1] = +{ + 0x40, /* 16 - recording mux (SB-mixer-enabled) */ +#ifdef ENABLE_SB_MIXER + 0x40, /* 17 - mute mux (Mode2) */ +#else + 0x0, /* 17 - mute mux */ +#endif + 0x0, /* 18 - vol */ + 0x0, /* 19 - master volume */ + 0x0, /* 20 - line-in volume */ + 0x0, /* 21 - cd-in volume */ + 0x0, /* 22 - wave volume */ + 0x0, /* 23 - mute/rec mux */ + 0x0, /* 24 - wave rec gain */ + 0x0, /* 25 - line-in rec gain */ + 0x0 /* 26 - cd-in rec gain */ +}; + +typedef int (*snd_pcm_open_callback_t)(struct snd_pcm_substream *); + +enum card_type { + CMI8330, + CMI8329 +}; + +struct snd_cmi8330 { +#ifdef CONFIG_PNP + struct pnp_dev *cap; + struct pnp_dev *play; + struct pnp_dev *mpu; +#endif + struct snd_card *card; + struct snd_wss *wss; + struct snd_sb *sb; + + struct snd_pcm *pcm; + struct snd_cmi8330_stream { + struct snd_pcm_ops ops; + snd_pcm_open_callback_t open; + void *private_data; /* sb or wss */ + } streams[2]; + + enum card_type type; +}; + +#ifdef CONFIG_PNP + +static const struct pnp_card_device_id snd_cmi8330_pnpids[] = { + { .id = "CMI0001", .devs = { { "@X@0001" }, { "@@@0001" }, { "@H@0001" }, { "A@@0001" } } }, + { .id = "CMI0001", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } } }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_cmi8330_pnpids); + +#endif + + +static struct snd_kcontrol_new snd_cmi8330_controls[] = { +WSS_DOUBLE("Master Playback Volume", 0, + CMI8330_MASTVOL, CMI8330_MASTVOL, 4, 0, 15, 0), +WSS_SINGLE("Loud Playback Switch", 0, + CMI8330_MUTEMUX, 6, 1, 1), +WSS_DOUBLE("PCM Playback Switch", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +WSS_DOUBLE("PCM Playback Volume", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1), +WSS_DOUBLE("Line Playback Switch", 0, + CMI8330_MUTEMUX, CMI8330_MUTEMUX, 4, 3, 1, 0), +WSS_DOUBLE("Line Playback Volume", 0, + CMI8330_LINVOL, CMI8330_LINVOL, 4, 0, 15, 0), +WSS_DOUBLE("Line Capture Switch", 0, + CMI8330_RMUX3D, CMI8330_RMUX3D, 2, 1, 1, 0), +WSS_DOUBLE("Line Capture Volume", 0, + CMI8330_LINGAIN, CMI8330_LINGAIN, 4, 0, 15, 0), +WSS_DOUBLE("CD Playback Switch", 0, + CMI8330_MUTEMUX, CMI8330_MUTEMUX, 2, 1, 1, 0), +WSS_DOUBLE("CD Capture Switch", 0, + CMI8330_RMUX3D, CMI8330_RMUX3D, 4, 3, 1, 0), +WSS_DOUBLE("CD Playback Volume", 0, + CMI8330_CDINVOL, CMI8330_CDINVOL, 4, 0, 15, 0), +WSS_DOUBLE("CD Capture Volume", 0, + CMI8330_CDINGAIN, CMI8330_CDINGAIN, 4, 0, 15, 0), +WSS_SINGLE("Mic Playback Switch", 0, + CMI8330_MUTEMUX, 0, 1, 0), +WSS_SINGLE("Mic Playback Volume", 0, + CMI8330_OUTPUTVOL, 0, 7, 0), +WSS_SINGLE("Mic Capture Switch", 0, + CMI8330_RMUX3D, 0, 1, 0), +WSS_SINGLE("Mic Capture Volume", 0, + CMI8330_OUTPUTVOL, 5, 7, 0), +WSS_DOUBLE("Wavetable Playback Switch", 0, + CMI8330_RECMUX, CMI8330_RECMUX, 1, 0, 1, 0), +WSS_DOUBLE("Wavetable Playback Volume", 0, + CMI8330_WAVVOL, CMI8330_WAVVOL, 4, 0, 15, 0), +WSS_DOUBLE("Wavetable Capture Switch", 0, + CMI8330_RECMUX, CMI8330_RECMUX, 5, 4, 1, 0), +WSS_DOUBLE("Wavetable Capture Volume", 0, + CMI8330_WAVGAIN, CMI8330_WAVGAIN, 4, 0, 15, 0), +WSS_SINGLE("3D Control - Switch", 0, + CMI8330_RMUX3D, 5, 1, 1), +WSS_SINGLE("Beep Playback Volume", 0, + CMI8330_OUTPUTVOL, 3, 3, 0), +WSS_DOUBLE("FM Playback Switch", 0, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE("FM Playback Volume", 0, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1), +WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", CAPTURE, SWITCH), 0, + CMI8330_RMUX3D, 7, 1, 1), +WSS_SINGLE(SNDRV_CTL_NAME_IEC958("Input ", PLAYBACK, SWITCH), 0, + CMI8330_MUTEMUX, 7, 1, 1), +}; + +#ifdef ENABLE_SB_MIXER +static struct sbmix_elem cmi8330_sb_mixers[] = { +SB_DOUBLE("SB Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31), +SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15), +SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15), +SB_DOUBLE("SB PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31), +SB_DOUBLE("SB Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31), +SB_DOUBLE("SB CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1), +SB_DOUBLE("SB CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31), +SB_DOUBLE("SB Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1), +SB_DOUBLE("SB Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31), +SB_SINGLE("SB Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1), +SB_SINGLE("SB Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31), +SB_SINGLE("SB Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3), +SB_DOUBLE("SB Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3), +SB_DOUBLE("SB Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3), +SB_SINGLE("SB Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1), +}; + +static unsigned char cmi8330_sb_init_values[][2] = { + { SB_DSP4_MASTER_DEV + 0, 0 }, + { SB_DSP4_MASTER_DEV + 1, 0 }, + { SB_DSP4_PCM_DEV + 0, 0 }, + { SB_DSP4_PCM_DEV + 1, 0 }, + { SB_DSP4_SYNTH_DEV + 0, 0 }, + { SB_DSP4_SYNTH_DEV + 1, 0 }, + { SB_DSP4_INPUT_LEFT, 0 }, + { SB_DSP4_INPUT_RIGHT, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DSP4_SPEAKER_DEV, 0 }, +}; + + +static int cmi8330_add_sb_mixers(struct snd_sb *chip) +{ + int idx, err; + unsigned long flags; + + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, 0x00, 0x00); /* mixer reset */ + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + /* mute and zero volume channels */ + for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_init_values); idx++) { + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, cmi8330_sb_init_values[idx][0], + cmi8330_sb_init_values[idx][1]); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + } + + for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_mixers); idx++) { + if ((err = snd_sbmixer_add_ctl_elem(chip, &cmi8330_sb_mixers[idx])) < 0) + return err; + } + return 0; +} +#endif + +static int snd_cmi8330_mixer(struct snd_card *card, struct snd_cmi8330 *acard) +{ + unsigned int idx; + int err; + + strcpy(card->mixername, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D"); + + for (idx = 0; idx < ARRAY_SIZE(snd_cmi8330_controls); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_cmi8330_controls[idx], + acard->wss)); + if (err < 0) + return err; + } + +#ifdef ENABLE_SB_MIXER + if ((err = cmi8330_add_sb_mixers(acard->sb)) < 0) + return err; +#endif + return 0; +} + +#ifdef CONFIG_PNP +static int snd_cmi8330_pnp(int dev, struct snd_cmi8330 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + /* CMI8329 has a device with ID A@@0001, CMI8330 does not */ + acard->type = (id->devs[3].id[0]) ? CMI8329 : CMI8330; + + acard->cap = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->cap == NULL) + return -EBUSY; + + acard->play = pnp_request_card_device(card, id->devs[1].id, NULL); + if (acard->play == NULL) + return -EBUSY; + + acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL); + if (acard->mpu == NULL) + return -EBUSY; + + pdev = acard->cap; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "AD1848 PnP configure failure\n"); + return -EBUSY; + } + wssport[dev] = pnp_port_start(pdev, 0); + wssdma[dev] = pnp_dma(pdev, 0); + wssirq[dev] = pnp_irq(pdev, 0); + if (pnp_port_start(pdev, 1)) + fmport[dev] = pnp_port_start(pdev, 1); + + /* allocate SB16 resources */ + pdev = acard->play; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "SB16 PnP configure failure\n"); + return -EBUSY; + } + sbport[dev] = pnp_port_start(pdev, 0); + sbdma8[dev] = pnp_dma(pdev, 0); + sbdma16[dev] = pnp_dma(pdev, 1); + sbirq[dev] = pnp_irq(pdev, 0); + /* On CMI8239, the OPL3 port might be present in SB16 PnP resources */ + if (fmport[dev] == SNDRV_AUTO_PORT) { + if (pnp_port_start(pdev, 1)) + fmport[dev] = pnp_port_start(pdev, 1); + else + fmport[dev] = 0x388; /* Or hardwired */ + } + + /* allocate MPU-401 resources */ + pdev = acard->mpu; + + err = pnp_activate_dev(pdev); + if (err < 0) + snd_printk(KERN_ERR "MPU-401 PnP configure failure: will be disabled\n"); + else { + mpuport[dev] = pnp_port_start(pdev, 0); + mpuirq[dev] = pnp_irq(pdev, 0); + } + return 0; +} +#endif + +/* + * PCM interface + * + * since we call the different chip interfaces for playback and capture + * directions, we need a trick. + * + * - copy the ops for each direction into a local record. + * - replace the open callback with the new one, which replaces the + * substream->private_data with the corresponding chip instance + * and calls again the original open callback of the chip. + * + */ + +#ifdef PLAYBACK_ON_SB +#define CMI_SB_STREAM SNDRV_PCM_STREAM_PLAYBACK +#define CMI_AD_STREAM SNDRV_PCM_STREAM_CAPTURE +#else +#define CMI_SB_STREAM SNDRV_PCM_STREAM_CAPTURE +#define CMI_AD_STREAM SNDRV_PCM_STREAM_PLAYBACK +#endif + +static int snd_cmi8330_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream); + + /* replace the private_data and call the original open callback */ + substream->private_data = chip->streams[SNDRV_PCM_STREAM_PLAYBACK].private_data; + return chip->streams[SNDRV_PCM_STREAM_PLAYBACK].open(substream); +} + +static int snd_cmi8330_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream); + + /* replace the private_data and call the original open callback */ + substream->private_data = chip->streams[SNDRV_PCM_STREAM_CAPTURE].private_data; + return chip->streams[SNDRV_PCM_STREAM_CAPTURE].open(substream); +} + +static int snd_cmi8330_pcm(struct snd_card *card, struct snd_cmi8330 *chip) +{ + struct snd_pcm *pcm; + const struct snd_pcm_ops *ops; + int err; + static snd_pcm_open_callback_t cmi_open_callbacks[2] = { + snd_cmi8330_playback_open, + snd_cmi8330_capture_open + }; + + if ((err = snd_pcm_new(card, (chip->type == CMI8329) ? "CMI8329" : "CMI8330", 0, 1, 1, &pcm)) < 0) + return err; + strcpy(pcm->name, (chip->type == CMI8329) ? "CMI8329" : "CMI8330"); + pcm->private_data = chip; + + /* SB16 */ + ops = snd_sb16dsp_get_pcm_ops(CMI_SB_STREAM); + chip->streams[CMI_SB_STREAM].ops = *ops; + chip->streams[CMI_SB_STREAM].open = ops->open; + chip->streams[CMI_SB_STREAM].ops.open = cmi_open_callbacks[CMI_SB_STREAM]; + chip->streams[CMI_SB_STREAM].private_data = chip->sb; + + /* AD1848 */ + ops = snd_wss_get_pcm_ops(CMI_AD_STREAM); + chip->streams[CMI_AD_STREAM].ops = *ops; + chip->streams[CMI_AD_STREAM].open = ops->open; + chip->streams[CMI_AD_STREAM].ops.open = cmi_open_callbacks[CMI_AD_STREAM]; + chip->streams[CMI_AD_STREAM].private_data = chip->wss; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &chip->streams[SNDRV_PCM_STREAM_PLAYBACK].ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &chip->streams[SNDRV_PCM_STREAM_CAPTURE].ops); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, 128*1024); + chip->pcm = pcm; + + return 0; +} + + +#ifdef CONFIG_PM +static int snd_cmi8330_suspend(struct snd_card *card) +{ + struct snd_cmi8330 *acard = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(acard->pcm); + acard->wss->suspend(acard->wss); + snd_sbmixer_suspend(acard->sb); + return 0; +} + +static int snd_cmi8330_resume(struct snd_card *card) +{ + struct snd_cmi8330 *acard = card->private_data; + + snd_sbdsp_reset(acard->sb); + snd_sbmixer_suspend(acard->sb); + acard->wss->resume(acard->wss); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + + +/* + */ + +#ifdef CONFIG_PNP +#define is_isapnp_selected(dev) isapnp[dev] +#else +#define is_isapnp_selected(dev) 0 +#endif + +#define PFX "cmi8330: " + +static int snd_cmi8330_card_new(struct device *pdev, int dev, + struct snd_card **cardp) +{ + struct snd_card *card; + struct snd_cmi8330 *acard; + int err; + + err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_cmi8330), &card); + if (err < 0) { + snd_printk(KERN_ERR PFX "could not get a new card\n"); + return err; + } + acard = card->private_data; + acard->card = card; + *cardp = card; + return 0; +} + +static int snd_cmi8330_probe(struct snd_card *card, int dev) +{ + struct snd_cmi8330 *acard; + int i, err; + struct snd_opl3 *opl3; + + acard = card->private_data; + err = snd_wss_create(card, wssport[dev] + 4, -1, + wssirq[dev], + wssdma[dev], -1, + WSS_HW_DETECT, 0, &acard->wss); + if (err < 0) { + snd_printk(KERN_ERR PFX "AD1848 device busy??\n"); + return err; + } + if (acard->wss->hardware != WSS_HW_CMI8330) { + snd_printk(KERN_ERR PFX "AD1848 not found during probe\n"); + return -ENODEV; + } + + if ((err = snd_sbdsp_create(card, sbport[dev], + sbirq[dev], + snd_sb16dsp_interrupt, + sbdma8[dev], + sbdma16[dev], + SB_HW_AUTO, &acard->sb)) < 0) { + snd_printk(KERN_ERR PFX "SB16 device busy??\n"); + return err; + } + if (acard->sb->hardware != SB_HW_16) { + snd_printk(KERN_ERR PFX "SB16 not found during probe\n"); + return -ENODEV; + } + + snd_wss_out(acard->wss, CS4231_MISC_INFO, 0x40); /* switch on MODE2 */ + for (i = CMI8330_RMUX3D; i <= CMI8330_CDINGAIN; i++) + snd_wss_out(acard->wss, i, + snd_cmi8330_image[i - CMI8330_RMUX3D]); + + if ((err = snd_cmi8330_mixer(card, acard)) < 0) { + snd_printk(KERN_ERR PFX "failed to create mixers\n"); + return err; + } + + if ((err = snd_cmi8330_pcm(card, acard)) < 0) { + snd_printk(KERN_ERR PFX "failed to create pcms\n"); + return err; + } + if (fmport[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, + fmport[dev], fmport[dev] + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX + "no OPL device at 0x%lx-0x%lx ?\n", + fmport[dev], fmport[dev] + 2); + } else { + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + return err; + } + } + + if (mpuport[dev] != SNDRV_AUTO_PORT) { + if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + mpuport[dev], 0, mpuirq[dev], + NULL) < 0) + printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n", + mpuport[dev]); + } + + strcpy(card->driver, (acard->type == CMI8329) ? "CMI8329" : "CMI8330/C3D"); + strcpy(card->shortname, (acard->type == CMI8329) ? "C-Media CMI8329" : "C-Media CMI8330/C3D"); + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + card->shortname, + acard->wss->port, + wssirq[dev], + wssdma[dev]); + + return snd_card_register(card); +} + +static int snd_cmi8330_isa_match(struct device *pdev, + unsigned int dev) +{ + if (!enable[dev] || is_isapnp_selected(dev)) + return 0; + if (wssport[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR PFX "specify wssport\n"); + return 0; + } + if (sbport[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR PFX "specify sbport\n"); + return 0; + } + return 1; +} + +static int snd_cmi8330_isa_probe(struct device *pdev, + unsigned int dev) +{ + struct snd_card *card; + int err; + + err = snd_cmi8330_card_new(pdev, dev, &card); + if (err < 0) + return err; + if ((err = snd_cmi8330_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + dev_set_drvdata(pdev, card); + return 0; +} + +static int snd_cmi8330_isa_remove(struct device *devptr, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_cmi8330_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_cmi8330_suspend(dev_get_drvdata(dev)); +} + +static int snd_cmi8330_isa_resume(struct device *dev, unsigned int n) +{ + return snd_cmi8330_resume(dev_get_drvdata(dev)); +} +#endif + +#define DEV_NAME "cmi8330" + +static struct isa_driver snd_cmi8330_driver = { + .match = snd_cmi8330_isa_match, + .probe = snd_cmi8330_isa_probe, + .remove = snd_cmi8330_isa_remove, +#ifdef CONFIG_PM + .suspend = snd_cmi8330_isa_suspend, + .resume = snd_cmi8330_isa_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + + +#ifdef CONFIG_PNP +static int snd_cmi8330_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int dev; + struct snd_card *card; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + res = snd_cmi8330_card_new(&pcard->card->dev, dev, &card); + if (res < 0) + return res; + if ((res = snd_cmi8330_pnp(dev, card->private_data, pcard, pid)) < 0) { + snd_printk(KERN_ERR PFX "PnP detection failed\n"); + snd_card_free(card); + return res; + } + if ((res = snd_cmi8330_probe(card, dev)) < 0) { + snd_card_free(card); + return res; + } + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; +} + +static void snd_cmi8330_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_cmi8330_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_cmi8330_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_cmi8330_pnp_resume(struct pnp_card_link *pcard) +{ + return snd_cmi8330_resume(pnp_get_card_drvdata(pcard)); +} +#endif + +static struct pnp_card_driver cmi8330_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "cmi8330", + .id_table = snd_cmi8330_pnpids, + .probe = snd_cmi8330_pnp_detect, + .remove = snd_cmi8330_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_cmi8330_pnp_suspend, + .resume = snd_cmi8330_pnp_resume, +#endif +}; +#endif /* CONFIG_PNP */ + +static int __init alsa_card_cmi8330_init(void) +{ + int err; + + err = isa_register_driver(&snd_cmi8330_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_card_driver(&cmi8330_pnpc_driver); + if (!err) + pnp_registered = 1; + + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_cmi8330_exit(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_card_driver(&cmi8330_pnpc_driver); + + if (isa_registered) +#endif + isa_unregister_driver(&snd_cmi8330_driver); +} + +module_init(alsa_card_cmi8330_init) +module_exit(alsa_card_cmi8330_exit) diff --git a/sound/isa/cs423x/Makefile b/sound/isa/cs423x/Makefile new file mode 100644 index 000000000..6d397e8d5 --- /dev/null +++ b/sound/isa/cs423x/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-cs4231-objs := cs4231.o +snd-cs4236-objs := cs4236.o cs4236_lib.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_CS4231) += snd-cs4231.o +obj-$(CONFIG_SND_CS4236) += snd-cs4236.o + + diff --git a/sound/isa/cs423x/cs4231.c b/sound/isa/cs423x/cs4231.c new file mode 100644 index 000000000..d90ab9558 --- /dev/null +++ b/sound/isa/cs423x/cs4231.c @@ -0,0 +1,193 @@ +/* + * Generic driver for CS4231 chips + * Copyright (c) by Jaroslav Kysela + * Originally the CS4232/CS4232A driver, modified for use on CS4231 by + * Tugrul Galatali + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CRD_NAME "Generic CS4231" +#define DEV_NAME "cs4231" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Crystal Semiconductors,CS4231}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver."); + +static int snd_cs4231_match(struct device *dev, unsigned int n) +{ + if (!enable[n]) + return 0; + + if (port[n] == SNDRV_AUTO_PORT) { + dev_err(dev, "please specify port\n"); + return 0; + } + if (irq[n] == SNDRV_AUTO_IRQ) { + dev_err(dev, "please specify irq\n"); + return 0; + } + if (dma1[n] == SNDRV_AUTO_DMA) { + dev_err(dev, "please specify dma1\n"); + return 0; + } + return 1; +} + +static int snd_cs4231_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + struct snd_wss *chip; + int error; + + error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card); + if (error < 0) + return error; + + error = snd_wss_create(card, port[n], -1, irq[n], dma1[n], dma2[n], + WSS_HW_DETECT, 0, &chip); + if (error < 0) + goto out; + + card->private_data = chip; + + error = snd_wss_pcm(chip, 0); + if (error < 0) + goto out; + + strlcpy(card->driver, "CS4231", sizeof(card->driver)); + strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname)); + + if (dma2[n] < 0) + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %d, dma %d", + chip->pcm->name, chip->port, irq[n], dma1[n]); + else + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %d, dma %d&%d", + chip->pcm->name, chip->port, irq[n], dma1[n], dma2[n]); + + error = snd_wss_mixer(chip); + if (error < 0) + goto out; + + error = snd_wss_timer(chip, 0); + if (error < 0) + goto out; + + if (mpu_port[n] > 0 && mpu_port[n] != SNDRV_AUTO_PORT) { + if (mpu_irq[n] == SNDRV_AUTO_IRQ) + mpu_irq[n] = -1; + if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232, + mpu_port[n], 0, mpu_irq[n], + NULL) < 0) + dev_warn(dev, "MPU401 not detected\n"); + } + + error = snd_card_register(card); + if (error < 0) + goto out; + + dev_set_drvdata(dev, card); + return 0; + +out: snd_card_free(card); + return error; +} + +static int snd_cs4231_remove(struct device *dev, unsigned int n) +{ + snd_card_free(dev_get_drvdata(dev)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_cs4231_suspend(struct device *dev, unsigned int n, pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_wss *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + chip->suspend(chip); + return 0; +} + +static int snd_cs4231_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_wss *chip = card->private_data; + + chip->resume(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static struct isa_driver snd_cs4231_driver = { + .match = snd_cs4231_match, + .probe = snd_cs4231_probe, + .remove = snd_cs4231_remove, +#ifdef CONFIG_PM + .suspend = snd_cs4231_suspend, + .resume = snd_cs4231_resume, +#endif + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_cs4231_driver, SNDRV_CARDS); diff --git a/sound/isa/cs423x/cs4236.c b/sound/isa/cs423x/cs4236.c new file mode 100644 index 000000000..6fff77fe3 --- /dev/null +++ b/sound/isa/cs423x/cs4236.c @@ -0,0 +1,728 @@ +/* + * Driver for generic CS4232/CS4235/CS4236/CS4236B/CS4237B/CS4238B/CS4239 chips + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cirrus Logic CS4232-9"); +MODULE_SUPPORTED_DEVICE("{{Turtle Beach,TBS-2000}," + "{Turtle Beach,Tropez Plus}," + "{SIC CrystalWave 32}," + "{Hewlett Packard,Omnibook 5500}," + "{TerraTec,Maestro 32/96}," + "{Philips,PCA70PS}}," + "{{Crystal Semiconductors,CS4235}," + "{Crystal Semiconductors,CS4236}," + "{Crystal Semiconductors,CS4237}," + "{Crystal Semiconductors,CS4238}," + "{Crystal Semiconductors,CS4239}," + "{Acer,AW37}," + "{Acer,AW35/Pro}," + "{Crystal,3D}," + "{Crystal Computer,TidalWave128}," + "{Dell,Optiplex GX1}," + "{Dell,Workstation 400 sound}," + "{EliteGroup,P5TX-LA sound}," + "{Gallant,SC-70P}," + "{Gateway,E1000 Onboard CS4236B}," + "{Genius,Sound Maker 3DJ}," + "{Hewlett Packard,HP6330 sound}," + "{IBM,PC 300PL sound}," + "{IBM,Aptiva 2137 E24}," + "{IBM,IntelliStation M Pro}," + "{Intel,Marlin Spike Mobo CS4235}," + "{Intel PR440FX Onboard}," + "{Guillemot,MaxiSound 16 PnP}," + "{NewClear,3D}," + "{TerraTec,AudioSystem EWS64L/XL}," + "{Typhoon Soundsystem,CS4236B}," + "{Turtle Beach,Malibu}," + "{Unknown,Digital PC 5000 Onboard}}"); + +MODULE_ALIAS("snd_cs4232"); + +#define IDENT "CS4232+" +#define DEV_NAME "cs4232+" + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long cport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* PnP setup */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " IDENT " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " IDENT " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " IDENT " soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard."); +#endif +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " IDENT " driver."); +module_param_hw_array(cport, long, ioport, NULL, 0444); +MODULE_PARM_DESC(cport, "Control port # for " IDENT " driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " IDENT " driver."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for " IDENT " driver."); +module_param_hw_array(sb_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(sb_port, "SB port # for " IDENT " driver (optional)."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " IDENT " driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " IDENT " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for " IDENT " driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for " IDENT " driver."); + +#ifdef CONFIG_PNP +static int isa_registered; +static int pnpc_registered; +static int pnp_registered; +#endif /* CONFIG_PNP */ + +struct snd_card_cs4236 { + struct snd_wss *chip; + struct resource *res_sb_port; +#ifdef CONFIG_PNP + struct pnp_dev *wss; + struct pnp_dev *ctrl; + struct pnp_dev *mpu; +#endif +}; + +#ifdef CONFIG_PNP + +/* + * PNP BIOS + */ +static const struct pnp_device_id snd_cs423x_pnpbiosids[] = { + { .id = "CSC0100" }, + { .id = "CSC0000" }, + /* Guillemot Turtlebeach something appears to be cs4232 compatible + * (untested) */ + { .id = "GIM0100" }, + { .id = "" } +}; +MODULE_DEVICE_TABLE(pnp, snd_cs423x_pnpbiosids); + +#define CS423X_ISAPNP_DRIVER "cs4232_isapnp" +static const struct pnp_card_device_id snd_cs423x_pnpids[] = { + /* Philips PCA70PS */ + { .id = "CSC0d32", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } }, + /* TerraTec Maestro 32/96 (CS4232) */ + { .id = "CSC1a32", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* HP Omnibook 5500 onboard */ + { .id = "CSC4232", .devs = { { "CSC0000" }, { "CSC0002" }, { "CSC0003" } } }, + /* Unnamed CS4236 card (Made in Taiwan) */ + { .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Turtle Beach TBS-2000 (CS4232) */ + { .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSCb006" } } }, + /* Turtle Beach Tropez Plus (CS4232) */ + { .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } }, + /* SIC CrystalWave 32 (CS4232) */ + { .id = "CSCf032", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Netfinity 3000 on-board soundcard */ + { .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC010f" } } }, + /* Intel Marlin Spike Motherboard - CS4235 */ + { .id = "CSC0225", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Intel Marlin Spike Motherboard (#2) - CS4235 */ + { .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* Unknown Intel mainboard - CS4235 */ + { .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* Genius Sound Maker 3DJ - CS4237B */ + { .id = "CSC0437", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Digital PC 5000 Onboard - CS4236B */ + { .id = "CSC0735", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* some unknown CS4236B */ + { .id = "CSC0b35", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Intel PR440FX Onboard sound */ + { .id = "CSC0b36", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4235 on mainboard without MPU */ + { .id = "CSC1425", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* Gateway E1000 Onboard CS4236B */ + { .id = "CSC1335", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* HP 6330 Onboard sound */ + { .id = "CSC1525", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* Crystal Computer TidalWave128 */ + { .id = "CSC1e37", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* ACER AW37 - CS4235 */ + { .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* build-in soundcard in EliteGroup P5TX-LA motherboard - CS4237B */ + { .id = "CSC4237", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Crystal 3D - CS4237B */ + { .id = "CSC4336", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Typhoon Soundsystem PnP - CS4236B */ + { .id = "CSC4536", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Crystal CX4235-XQ3 EP - CS4235 */ + { .id = "CSC4625", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* Crystal Semiconductors CS4237B */ + { .id = "CSC4637", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* NewClear 3D - CX4237B-XQ3 */ + { .id = "CSC4837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Dell Optiplex GX1 - CS4236B */ + { .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Dell P410 motherboard - CS4236B */ + { .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* Dell Workstation 400 Onboard - CS4236B */ + { .id = "CSC6836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Turtle Beach Malibu - CS4237B */ + { .id = "CSC7537", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4235 - onboard */ + { .id = "CSC8025", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* IBM Aptiva 2137 E24 Onboard - CS4237B */ + { .id = "CSC8037", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* IBM IntelliStation M Pro motherboard */ + { .id = "CSCc835", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* Guillemot MaxiSound 16 PnP - CS4236B */ + { .id = "CSC9836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Gallant SC-70P */ + { .id = "CSC9837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Techmakers MF-4236PW */ + { .id = "CSCa736", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* TerraTec AudioSystem EWS64XL - CS4236B */ + { .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" }, { "CSCa803" } } }, + /* TerraTec AudioSystem EWS64XL - CS4236B */ + { .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" } } }, + /* ACER AW37/Pro - CS4235 */ + { .id = "CSCd925", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* ACER AW35/Pro - CS4237B */ + { .id = "CSCd937", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4235 without MPU401 */ + { .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* Unknown SiS530 - CS4235 */ + { .id = "CSC4825", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* IBM IntelliStation M Pro 6898 11U - CS4236B */ + { .id = "CSCe835", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* IBM PC 300PL Onboard - CS4236B */ + { .id = "CSCe836", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* Some noname CS4236 based card */ + { .id = "CSCe936", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4236B */ + { .id = "CSCf235", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4236B */ + { .id = "CSCf238", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* --- */ + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_cs423x_pnpids); + +/* WSS initialization */ +static int snd_cs423x_pnp_init_wss(int dev, struct pnp_dev *pdev) +{ + if (pnp_activate_dev(pdev) < 0) { + printk(KERN_ERR IDENT " WSS PnP configure failed for WSS (out of resources?)\n"); + return -EBUSY; + } + port[dev] = pnp_port_start(pdev, 0); + if (fm_port[dev] > 0) + fm_port[dev] = pnp_port_start(pdev, 1); + sb_port[dev] = pnp_port_start(pdev, 2); + irq[dev] = pnp_irq(pdev, 0); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1) == 4 ? -1 : (int)pnp_dma(pdev, 1); + snd_printdd("isapnp WSS: wss port=0x%lx, fm port=0x%lx, sb port=0x%lx\n", + port[dev], fm_port[dev], sb_port[dev]); + snd_printdd("isapnp WSS: irq=%i, dma1=%i, dma2=%i\n", + irq[dev], dma1[dev], dma2[dev]); + return 0; +} + +/* CTRL initialization */ +static int snd_cs423x_pnp_init_ctrl(int dev, struct pnp_dev *pdev) +{ + if (pnp_activate_dev(pdev) < 0) { + printk(KERN_ERR IDENT " CTRL PnP configure failed for WSS (out of resources?)\n"); + return -EBUSY; + } + cport[dev] = pnp_port_start(pdev, 0); + snd_printdd("isapnp CTRL: control port=0x%lx\n", cport[dev]); + return 0; +} + +/* MPU initialization */ +static int snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev) +{ + if (pnp_activate_dev(pdev) < 0) { + printk(KERN_ERR IDENT " MPU401 PnP configure failed for WSS (out of resources?)\n"); + mpu_port[dev] = SNDRV_AUTO_PORT; + mpu_irq[dev] = SNDRV_AUTO_IRQ; + } else { + mpu_port[dev] = pnp_port_start(pdev, 0); + if (mpu_irq[dev] >= 0 && + pnp_irq_valid(pdev, 0) && + pnp_irq(pdev, 0) != (resource_size_t)-1) { + mpu_irq[dev] = pnp_irq(pdev, 0); + } else { + mpu_irq[dev] = -1; /* disable interrupt */ + } + } + snd_printdd("isapnp MPU: port=0x%lx, irq=%i\n", mpu_port[dev], mpu_irq[dev]); + return 0; +} + +static int snd_card_cs423x_pnp(int dev, struct snd_card_cs4236 *acard, + struct pnp_dev *pdev, + struct pnp_dev *cdev) +{ + acard->wss = pdev; + if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0) + return -EBUSY; + if (cdev) + cport[dev] = pnp_port_start(cdev, 0); + else + cport[dev] = -1; + return 0; +} + +static int snd_card_cs423x_pnpc(int dev, struct snd_card_cs4236 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->wss == NULL) + return -EBUSY; + acard->ctrl = pnp_request_card_device(card, id->devs[1].id, NULL); + if (acard->ctrl == NULL) + return -EBUSY; + if (id->devs[2].id[0]) { + acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL); + if (acard->mpu == NULL) + return -EBUSY; + } + + /* WSS initialization */ + if (snd_cs423x_pnp_init_wss(dev, acard->wss) < 0) + return -EBUSY; + + /* CTRL initialization */ + if (acard->ctrl && cport[dev] > 0) { + if (snd_cs423x_pnp_init_ctrl(dev, acard->ctrl) < 0) + return -EBUSY; + } + /* MPU initialization */ + if (acard->mpu && mpu_port[dev] > 0) { + if (snd_cs423x_pnp_init_mpu(dev, acard->mpu) < 0) + return -EBUSY; + } + return 0; +} +#endif /* CONFIG_PNP */ + +#ifdef CONFIG_PNP +#define is_isapnp_selected(dev) isapnp[dev] +#else +#define is_isapnp_selected(dev) 0 +#endif + +static void snd_card_cs4236_free(struct snd_card *card) +{ + struct snd_card_cs4236 *acard = card->private_data; + + release_and_free_resource(acard->res_sb_port); +} + +static int snd_cs423x_card_new(struct device *pdev, int dev, + struct snd_card **cardp) +{ + struct snd_card *card; + int err; + + err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_cs4236), &card); + if (err < 0) + return err; + card->private_free = snd_card_cs4236_free; + *cardp = card; + return 0; +} + +static int snd_cs423x_probe(struct snd_card *card, int dev) +{ + struct snd_card_cs4236 *acard; + struct snd_wss *chip; + struct snd_opl3 *opl3; + int err; + + acard = card->private_data; + if (sb_port[dev] > 0 && sb_port[dev] != SNDRV_AUTO_PORT) + if ((acard->res_sb_port = request_region(sb_port[dev], 16, IDENT " SB")) == NULL) { + printk(KERN_ERR IDENT ": unable to register SB port at 0x%lx\n", sb_port[dev]); + return -EBUSY; + } + + err = snd_cs4236_create(card, port[dev], cport[dev], + irq[dev], + dma1[dev], dma2[dev], + WSS_HW_DETECT3, 0, &chip); + if (err < 0) + return err; + + acard->chip = chip; + if (chip->hardware & WSS_HW_CS4236B_MASK) { + + err = snd_cs4236_pcm(chip, 0); + if (err < 0) + return err; + + err = snd_cs4236_mixer(chip); + if (err < 0) + return err; + } else { + err = snd_wss_pcm(chip, 0); + if (err < 0) + return err; + + err = snd_wss_mixer(chip); + if (err < 0) + return err; + } + strlcpy(card->driver, chip->pcm->name, sizeof(card->driver)); + strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname)); + if (dma2[dev] < 0) + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i, dma %i", + chip->pcm->name, chip->port, irq[dev], dma1[dev]); + else + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i, dma %i&%d", + chip->pcm->name, chip->port, irq[dev], dma1[dev], + dma2[dev]); + + err = snd_wss_timer(chip, 0); + if (err < 0) + return err; + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, + fm_port[dev], fm_port[dev] + 2, + OPL3_HW_OPL3_CS, 0, &opl3) < 0) { + printk(KERN_WARNING IDENT ": OPL3 not detected\n"); + } else { + if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) + return err; + } + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + if (mpu_irq[dev] == SNDRV_AUTO_IRQ) + mpu_irq[dev] = -1; + if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232, + mpu_port[dev], 0, + mpu_irq[dev], NULL) < 0) + printk(KERN_WARNING IDENT ": MPU401 not detected\n"); + } + + return snd_card_register(card); +} + +static int snd_cs423x_isa_match(struct device *pdev, + unsigned int dev) +{ + if (!enable[dev] || is_isapnp_selected(dev)) + return 0; + + if (port[dev] == SNDRV_AUTO_PORT) { + dev_err(pdev, "please specify port\n"); + return 0; + } + if (cport[dev] == SNDRV_AUTO_PORT) { + dev_err(pdev, "please specify cport\n"); + return 0; + } + if (irq[dev] == SNDRV_AUTO_IRQ) { + dev_err(pdev, "please specify irq\n"); + return 0; + } + if (dma1[dev] == SNDRV_AUTO_DMA) { + dev_err(pdev, "please specify dma1\n"); + return 0; + } + return 1; +} + +static int snd_cs423x_isa_probe(struct device *pdev, + unsigned int dev) +{ + struct snd_card *card; + int err; + + err = snd_cs423x_card_new(pdev, dev, &card); + if (err < 0) + return err; + if ((err = snd_cs423x_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + + dev_set_drvdata(pdev, card); + return 0; +} + +static int snd_cs423x_isa_remove(struct device *pdev, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(pdev)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_cs423x_suspend(struct snd_card *card) +{ + struct snd_card_cs4236 *acard = card->private_data; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + acard->chip->suspend(acard->chip); + return 0; +} + +static int snd_cs423x_resume(struct snd_card *card) +{ + struct snd_card_cs4236 *acard = card->private_data; + acard->chip->resume(acard->chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_cs423x_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_cs423x_suspend(dev_get_drvdata(dev)); +} + +static int snd_cs423x_isa_resume(struct device *dev, unsigned int n) +{ + return snd_cs423x_resume(dev_get_drvdata(dev)); +} +#endif + +static struct isa_driver cs423x_isa_driver = { + .match = snd_cs423x_isa_match, + .probe = snd_cs423x_isa_probe, + .remove = snd_cs423x_isa_remove, +#ifdef CONFIG_PM + .suspend = snd_cs423x_isa_suspend, + .resume = snd_cs423x_isa_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + + +#ifdef CONFIG_PNP +static int snd_cs423x_pnpbios_detect(struct pnp_dev *pdev, + const struct pnp_device_id *id) +{ + static int dev; + int err; + struct snd_card *card; + struct pnp_dev *cdev, *iter; + char cid[PNP_ID_LEN]; + + if (pnp_device_is_isapnp(pdev)) + return -ENOENT; /* we have another procedure - card */ + for (; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + /* prepare second id */ + strcpy(cid, pdev->id[0].id); + cid[5] = '1'; + cdev = NULL; + list_for_each_entry(iter, &(pdev->protocol->devices), protocol_list) { + if (!strcmp(iter->id[0].id, cid)) { + cdev = iter; + break; + } + } + err = snd_cs423x_card_new(&pdev->dev, dev, &card); + if (err < 0) + return err; + err = snd_card_cs423x_pnp(dev, card->private_data, pdev, cdev); + if (err < 0) { + printk(KERN_ERR "PnP BIOS detection failed for " IDENT "\n"); + snd_card_free(card); + return err; + } + if ((err = snd_cs423x_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + pnp_set_drvdata(pdev, card); + dev++; + return 0; +} + +static void snd_cs423x_pnp_remove(struct pnp_dev *pdev) +{ + snd_card_free(pnp_get_drvdata(pdev)); +} + +#ifdef CONFIG_PM +static int snd_cs423x_pnp_suspend(struct pnp_dev *pdev, pm_message_t state) +{ + return snd_cs423x_suspend(pnp_get_drvdata(pdev)); +} + +static int snd_cs423x_pnp_resume(struct pnp_dev *pdev) +{ + return snd_cs423x_resume(pnp_get_drvdata(pdev)); +} +#endif + +static struct pnp_driver cs423x_pnp_driver = { + .name = "cs423x-pnpbios", + .id_table = snd_cs423x_pnpbiosids, + .probe = snd_cs423x_pnpbios_detect, + .remove = snd_cs423x_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_cs423x_pnp_suspend, + .resume = snd_cs423x_pnp_resume, +#endif +}; + +static int snd_cs423x_pnpc_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int dev; + struct snd_card *card; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + res = snd_cs423x_card_new(&pcard->card->dev, dev, &card); + if (res < 0) + return res; + if ((res = snd_card_cs423x_pnpc(dev, card->private_data, pcard, pid)) < 0) { + printk(KERN_ERR "isapnp detection failed and probing for " IDENT + " is not supported\n"); + snd_card_free(card); + return res; + } + if ((res = snd_cs423x_probe(card, dev)) < 0) { + snd_card_free(card); + return res; + } + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; +} + +static void snd_cs423x_pnpc_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_cs423x_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_cs423x_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_cs423x_pnpc_resume(struct pnp_card_link *pcard) +{ + return snd_cs423x_resume(pnp_get_card_drvdata(pcard)); +} +#endif + +static struct pnp_card_driver cs423x_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = CS423X_ISAPNP_DRIVER, + .id_table = snd_cs423x_pnpids, + .probe = snd_cs423x_pnpc_detect, + .remove = snd_cs423x_pnpc_remove, +#ifdef CONFIG_PM + .suspend = snd_cs423x_pnpc_suspend, + .resume = snd_cs423x_pnpc_resume, +#endif +}; +#endif /* CONFIG_PNP */ + +static int __init alsa_card_cs423x_init(void) +{ + int err; + + err = isa_register_driver(&cs423x_isa_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + err = pnp_register_driver(&cs423x_pnp_driver); + if (!err) + pnp_registered = 1; + err = pnp_register_card_driver(&cs423x_pnpc_driver); + if (!err) + pnpc_registered = 1; + if (pnp_registered) + err = 0; + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_cs423x_exit(void) +{ +#ifdef CONFIG_PNP + if (pnpc_registered) + pnp_unregister_card_driver(&cs423x_pnpc_driver); + if (pnp_registered) + pnp_unregister_driver(&cs423x_pnp_driver); + if (isa_registered) +#endif + isa_unregister_driver(&cs423x_isa_driver); +} + +module_init(alsa_card_cs423x_init) +module_exit(alsa_card_cs423x_exit) diff --git a/sound/isa/cs423x/cs4236_lib.c b/sound/isa/cs423x/cs4236_lib.c new file mode 100644 index 000000000..2012936f6 --- /dev/null +++ b/sound/isa/cs423x/cs4236_lib.c @@ -0,0 +1,1086 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of CS4235/4236B/4237B/4238B/4239 chips + * + * Note: + * ----- + * + * Bugs: + * ----- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Indirect control registers (CS4236B+) + * + * C0 + * D8: WSS reset (all chips) + * + * C1 (all chips except CS4236) + * D7-D5: version + * D4-D0: chip id + * 11101 - CS4235 + * 01011 - CS4236B + * 01000 - CS4237B + * 01001 - CS4238B + * 11110 - CS4239 + * + * C2 + * D7-D4: 3D Space (CS4235,CS4237B,CS4238B,CS4239) + * D3-D0: 3D Center (CS4237B); 3D Volume (CS4238B) + * + * C3 + * D7: 3D Enable (CS4237B) + * D6: 3D Mono Enable (CS4237B) + * D5: 3D Serial Output (CS4237B,CS4238B) + * D4: 3D Enable (CS4235,CS4238B,CS4239) + * + * C4 + * D7: consumer serial port enable (CS4237B,CS4238B) + * D6: channels status block reset (CS4237B,CS4238B) + * D5: user bit in sub-frame of digital audio data (CS4237B,CS4238B) + * D4: validity bit bit in sub-frame of digital audio data (CS4237B,CS4238B) + * + * C5 lower channel status (digital serial data description) (CS4237B,CS4238B) + * D7-D6: first two bits of category code + * D5: lock + * D4-D3: pre-emphasis (0 = none, 1 = 50/15us) + * D2: copy/copyright (0 = copy inhibited) + * D1: 0 = digital audio / 1 = non-digital audio + * + * C6 upper channel status (digital serial data description) (CS4237B,CS4238B) + * D7-D6: sample frequency (0 = 44.1kHz) + * D5: generation status (0 = no indication, 1 = original/commercially precaptureed data) + * D4-D0: category code (upper bits) + * + * C7 reserved (must write 0) + * + * C8 wavetable control + * D7: volume control interrupt enable (CS4235,CS4239) + * D6: hardware volume control format (CS4235,CS4239) + * D3: wavetable serial port enable (all chips) + * D2: DSP serial port switch (all chips) + * D1: disable MCLK (all chips) + * D0: force BRESET low (all chips) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * + */ + +static unsigned char snd_cs4236_ext_map[18] = { + /* CS4236_LEFT_LINE */ 0xff, + /* CS4236_RIGHT_LINE */ 0xff, + /* CS4236_LEFT_MIC */ 0xdf, + /* CS4236_RIGHT_MIC */ 0xdf, + /* CS4236_LEFT_MIX_CTRL */ 0xe0 | 0x18, + /* CS4236_RIGHT_MIX_CTRL */ 0xe0, + /* CS4236_LEFT_FM */ 0xbf, + /* CS4236_RIGHT_FM */ 0xbf, + /* CS4236_LEFT_DSP */ 0xbf, + /* CS4236_RIGHT_DSP */ 0xbf, + /* CS4236_RIGHT_LOOPBACK */ 0xbf, + /* CS4236_DAC_MUTE */ 0xe0, + /* CS4236_ADC_RATE */ 0x01, /* 48kHz */ + /* CS4236_DAC_RATE */ 0x01, /* 48kHz */ + /* CS4236_LEFT_MASTER */ 0xbf, + /* CS4236_RIGHT_MASTER */ 0xbf, + /* CS4236_LEFT_WAVE */ 0xbf, + /* CS4236_RIGHT_WAVE */ 0xbf +}; + +/* + * + */ + +static void snd_cs4236_ctrl_out(struct snd_wss *chip, + unsigned char reg, unsigned char val) +{ + outb(reg, chip->cport + 3); + outb(chip->cimage[reg] = val, chip->cport + 4); +} + +static unsigned char snd_cs4236_ctrl_in(struct snd_wss *chip, unsigned char reg) +{ + outb(reg, chip->cport + 3); + return inb(chip->cport + 4); +} + +/* + * PCM + */ + +#define CLOCKS 8 + +static const struct snd_ratnum clocks[CLOCKS] = { + { .num = 16934400, .den_min = 353, .den_max = 353, .den_step = 1 }, + { .num = 16934400, .den_min = 529, .den_max = 529, .den_step = 1 }, + { .num = 16934400, .den_min = 617, .den_max = 617, .den_step = 1 }, + { .num = 16934400, .den_min = 1058, .den_max = 1058, .den_step = 1 }, + { .num = 16934400, .den_min = 1764, .den_max = 1764, .den_step = 1 }, + { .num = 16934400, .den_min = 2117, .den_max = 2117, .den_step = 1 }, + { .num = 16934400, .den_min = 2558, .den_max = 2558, .den_step = 1 }, + { .num = 16934400/16, .den_min = 21, .den_max = 192, .den_step = 1 } +}; + +static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = { + .nrats = CLOCKS, + .rats = clocks, +}; + +static int snd_cs4236_xrate(struct snd_pcm_runtime *runtime) +{ + return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); +} + +static unsigned char divisor_to_rate_register(unsigned int divisor) +{ + switch (divisor) { + case 353: return 1; + case 529: return 2; + case 617: return 3; + case 1058: return 4; + case 1764: return 5; + case 2117: return 6; + case 2558: return 7; + default: + if (divisor < 21 || divisor > 192) { + snd_BUG(); + return 192; + } + return divisor; + } +} + +static void snd_cs4236_playback_format(struct snd_wss *chip, + struct snd_pcm_hw_params *params, + unsigned char pdfr) +{ + unsigned long flags; + unsigned char rate = divisor_to_rate_register(params->rate_den); + + spin_lock_irqsave(&chip->reg_lock, flags); + /* set fast playback format change and clean playback FIFO */ + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] | 0x10); + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr & 0xf0); + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] & ~0x10); + snd_cs4236_ext_out(chip, CS4236_DAC_RATE, rate); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_cs4236_capture_format(struct snd_wss *chip, + struct snd_pcm_hw_params *params, + unsigned char cdfr) +{ + unsigned long flags; + unsigned char rate = divisor_to_rate_register(params->rate_den); + + spin_lock_irqsave(&chip->reg_lock, flags); + /* set fast capture format change and clean capture FIFO */ + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] | 0x20); + snd_wss_out(chip, CS4231_REC_FORMAT, cdfr & 0xf0); + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] & ~0x20); + snd_cs4236_ext_out(chip, CS4236_ADC_RATE, rate); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +#ifdef CONFIG_PM + +static void snd_cs4236_suspend(struct snd_wss *chip) +{ + int reg; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) + chip->image[reg] = snd_wss_in(chip, reg); + for (reg = 0; reg < 18; reg++) + chip->eimage[reg] = snd_cs4236_ext_in(chip, CS4236_I23VAL(reg)); + for (reg = 2; reg < 9; reg++) + chip->cimage[reg] = snd_cs4236_ctrl_in(chip, reg); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_cs4236_resume(struct snd_wss *chip) +{ + int reg; + unsigned long flags; + + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) { + switch (reg) { + case CS4236_EXT_REG: + case CS4231_VERSION: + case 27: /* why? CS4235 - master left */ + case 29: /* why? CS4235 - master right */ + break; + default: + snd_wss_out(chip, reg, chip->image[reg]); + break; + } + } + for (reg = 0; reg < 18; reg++) + snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), chip->eimage[reg]); + for (reg = 2; reg < 9; reg++) { + switch (reg) { + case 7: + break; + default: + snd_cs4236_ctrl_out(chip, reg, chip->cimage[reg]); + } + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); +} + +#endif /* CONFIG_PM */ +/* + * This function does no fail if the chip is not CS4236B or compatible. + * It just an equivalent to the snd_wss_create() then. + */ +int snd_cs4236_create(struct snd_card *card, + unsigned long port, + unsigned long cport, + int irq, int dma1, int dma2, + unsigned short hardware, + unsigned short hwshare, + struct snd_wss **rchip) +{ + struct snd_wss *chip; + unsigned char ver1, ver2; + unsigned int reg; + int err; + + *rchip = NULL; + if (hardware == WSS_HW_DETECT) + hardware = WSS_HW_DETECT3; + + err = snd_wss_create(card, port, cport, + irq, dma1, dma2, hardware, hwshare, &chip); + if (err < 0) + return err; + + if ((chip->hardware & WSS_HW_CS4236B_MASK) == 0) { + snd_printd("chip is not CS4236+, hardware=0x%x\n", + chip->hardware); + *rchip = chip; + return 0; + } +#if 0 + { + int idx; + for (idx = 0; idx < 8; idx++) + snd_printk(KERN_DEBUG "CD%i = 0x%x\n", + idx, inb(chip->cport + idx)); + for (idx = 0; idx < 9; idx++) + snd_printk(KERN_DEBUG "C%i = 0x%x\n", + idx, snd_cs4236_ctrl_in(chip, idx)); + } +#endif + if (cport < 0x100 || cport == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR "please, specify control port " + "for CS4236+ chips\n"); + snd_device_free(card, chip); + return -ENODEV; + } + ver1 = snd_cs4236_ctrl_in(chip, 1); + ver2 = snd_cs4236_ext_in(chip, CS4236_VERSION); + snd_printdd("CS4236: [0x%lx] C1 (version) = 0x%x, ext = 0x%x\n", + cport, ver1, ver2); + if (ver1 != ver2) { + snd_printk(KERN_ERR "CS4236+ chip detected, but " + "control port 0x%lx is not valid\n", cport); + snd_device_free(card, chip); + return -ENODEV; + } + snd_cs4236_ctrl_out(chip, 0, 0x00); + snd_cs4236_ctrl_out(chip, 2, 0xff); + snd_cs4236_ctrl_out(chip, 3, 0x00); + snd_cs4236_ctrl_out(chip, 4, 0x80); + reg = ((IEC958_AES1_CON_PCM_CODER & 3) << 6) | + IEC958_AES0_CON_EMPHASIS_NONE; + snd_cs4236_ctrl_out(chip, 5, reg); + snd_cs4236_ctrl_out(chip, 6, IEC958_AES1_CON_PCM_CODER >> 2); + snd_cs4236_ctrl_out(chip, 7, 0x00); + /* + * 0x8c for C8 is valid for Turtle Beach Malibu - the IEC-958 + * output is working with this setup, other hardware should + * have different signal paths and this value should be + * selectable in the future + */ + snd_cs4236_ctrl_out(chip, 8, 0x8c); + chip->rate_constraint = snd_cs4236_xrate; + chip->set_playback_format = snd_cs4236_playback_format; + chip->set_capture_format = snd_cs4236_capture_format; +#ifdef CONFIG_PM + chip->suspend = snd_cs4236_suspend; + chip->resume = snd_cs4236_resume; +#endif + + /* initialize extended registers */ + for (reg = 0; reg < sizeof(snd_cs4236_ext_map); reg++) + snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), + snd_cs4236_ext_map[reg]); + + /* initialize compatible but more featured registers */ + snd_wss_out(chip, CS4231_LEFT_INPUT, 0x40); + snd_wss_out(chip, CS4231_RIGHT_INPUT, 0x40); + snd_wss_out(chip, CS4231_AUX1_LEFT_INPUT, 0xff); + snd_wss_out(chip, CS4231_AUX1_RIGHT_INPUT, 0xff); + snd_wss_out(chip, CS4231_AUX2_LEFT_INPUT, 0xdf); + snd_wss_out(chip, CS4231_AUX2_RIGHT_INPUT, 0xdf); + snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff); + snd_wss_out(chip, CS4231_LEFT_LINE_IN, 0xff); + snd_wss_out(chip, CS4231_RIGHT_LINE_IN, 0xff); + switch (chip->hardware) { + case WSS_HW_CS4235: + case WSS_HW_CS4239: + snd_wss_out(chip, CS4235_LEFT_MASTER, 0xff); + snd_wss_out(chip, CS4235_RIGHT_MASTER, 0xff); + break; + } + + *rchip = chip; + return 0; +} + +int snd_cs4236_pcm(struct snd_wss *chip, int device) +{ + int err; + + err = snd_wss_pcm(chip, device); + if (err < 0) + return err; + chip->pcm->info_flags &= ~SNDRV_PCM_INFO_JOINT_DUPLEX; + return 0; +} + +/* + * MIXER + */ + +#define CS4236_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_single, \ + .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +#define CS4236_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_cs4236_info_single, \ + .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \ + .tlv = { .p = (xtlv) } } + +static int snd_cs4236_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_cs4236_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(reg)] >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_cs4236_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->eimage[CS4236_REG(reg)] & ~(mask << shift)) | val; + change = val != chip->eimage[CS4236_REG(reg)]; + snd_cs4236_ext_out(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_SINGLEC(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_single, \ + .get = snd_cs4236_get_singlec, .put = snd_cs4236_put_singlec, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_cs4236_get_singlec(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->cimage[reg] >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_cs4236_put_singlec(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->cimage[reg] & ~(mask << shift)) | val; + change = val != chip->cimage[reg]; + snd_cs4236_ctrl_out(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +#define CS4236_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, \ + shift_right, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \ + (shift_right << 19) | (mask << 24) | (invert << 22), \ + .tlv = { .p = (xtlv) } } + +static int snd_cs4236_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_cs4236_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(left_reg)] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_cs4236_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + if (left_reg != right_reg) { + val1 = (chip->eimage[CS4236_REG(left_reg)] & ~(mask << shift_left)) | val1; + val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2; + change = val1 != chip->eimage[CS4236_REG(left_reg)] || val2 != chip->eimage[CS4236_REG(right_reg)]; + snd_cs4236_ext_out(chip, left_reg, val1); + snd_cs4236_ext_out(chip, right_reg, val2); + } else { + val1 = (chip->eimage[CS4236_REG(left_reg)] & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != chip->eimage[CS4236_REG(left_reg)]; + snd_cs4236_ext_out(chip, left_reg, val1); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_DOUBLE1(xname, xindex, left_reg, right_reg, shift_left, \ + shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +#define CS4236_DOUBLE1_TLV(xname, xindex, left_reg, right_reg, shift_left, \ + shift_right, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \ + (shift_right << 19) | (mask << 24) | (invert << 22), \ + .tlv = { .p = (xtlv) } } + +static int snd_cs4236_get_double1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_cs4236_put_double1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1; + val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2; + change = val1 != chip->image[left_reg] || val2 != chip->eimage[CS4236_REG(right_reg)]; + snd_wss_out(chip, left_reg, val1); + snd_cs4236_ext_out(chip, right_reg, val2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_MASTER_DIGITAL(xname, xindex, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_master_digital, .put = snd_cs4236_put_master_digital, \ + .private_value = 71 << 24, \ + .tlv = { .p = (xtlv) } } + +static inline int snd_cs4236_mixer_master_digital_invert_volume(int vol) +{ + return (vol < 64) ? 63 - vol : 64 + (71 - vol); +} + +static int snd_cs4236_get_master_digital(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & 0x7f); + ucontrol->value.integer.value[1] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & 0x7f); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4236_put_master_digital(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1, val2; + + val1 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[0] & 0x7f); + val2 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[1] & 0x7f); + spin_lock_irqsave(&chip->reg_lock, flags); + val1 = (chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & ~0x7f) | val1; + val2 = (chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & ~0x7f) | val2; + change = val1 != chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] || val2 != chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)]; + snd_cs4236_ext_out(chip, CS4236_LEFT_MASTER, val1); + snd_cs4236_ext_out(chip, CS4236_RIGHT_MASTER, val2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4235_OUTPUT_ACCU(xname, xindex, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4235_get_output_accu, .put = snd_cs4235_put_output_accu, \ + .private_value = 3 << 24, \ + .tlv = { .p = (xtlv) } } + +static inline int snd_cs4235_mixer_output_accu_get_volume(int vol) +{ + switch ((vol >> 5) & 3) { + case 0: return 1; + case 1: return 3; + case 2: return 2; + case 3: return 0; + } + return 3; +} + +static inline int snd_cs4235_mixer_output_accu_set_volume(int vol) +{ + switch (vol & 3) { + case 0: return 3 << 5; + case 1: return 0 << 5; + case 2: return 2 << 5; + case 3: return 1 << 5; + } + return 1 << 5; +} + +static int snd_cs4235_get_output_accu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_LEFT_MASTER]); + ucontrol->value.integer.value[1] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_RIGHT_MASTER]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4235_put_output_accu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1, val2; + + val1 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[0]); + val2 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[1]); + spin_lock_irqsave(&chip->reg_lock, flags); + val1 = (chip->image[CS4235_LEFT_MASTER] & ~(3 << 5)) | val1; + val2 = (chip->image[CS4235_RIGHT_MASTER] & ~(3 << 5)) | val2; + change = val1 != chip->image[CS4235_LEFT_MASTER] || val2 != chip->image[CS4235_RIGHT_MASTER]; + snd_wss_out(chip, CS4235_LEFT_MASTER, val1); + snd_wss_out(chip, CS4235_RIGHT_MASTER, val2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_7bit, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_6bit_12db_max, -8250, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_22db_max, -2400, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_2bit, -1800, 600, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0); + +static struct snd_kcontrol_new snd_cs4236_controls[] = { + +CS4236_DOUBLE("Master Digital Playback Switch", 0, + CS4236_LEFT_MASTER, CS4236_RIGHT_MASTER, 7, 7, 1, 1), +CS4236_DOUBLE("Master Digital Capture Switch", 0, + CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1), +CS4236_MASTER_DIGITAL("Master Digital Volume", 0, db_scale_7bit), + +CS4236_DOUBLE_TLV("Capture Boost Volume", 0, + CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1, + db_scale_2bit), + +WSS_DOUBLE("PCM Playback Switch", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("PCM Playback Volume", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1, + db_scale_6bit), + +CS4236_DOUBLE("DSP Playback Switch", 0, + CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1), +CS4236_DOUBLE_TLV("DSP Playback Volume", 0, + CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 0, 0, 63, 1, + db_scale_6bit), + +CS4236_DOUBLE("FM Playback Switch", 0, + CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1), +CS4236_DOUBLE_TLV("FM Playback Volume", 0, + CS4236_LEFT_FM, CS4236_RIGHT_FM, 0, 0, 63, 1, + db_scale_6bit), + +CS4236_DOUBLE("Wavetable Playback Switch", 0, + CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1), +CS4236_DOUBLE_TLV("Wavetable Playback Volume", 0, + CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 0, 0, 63, 1, + db_scale_6bit_12db_max), + +WSS_DOUBLE("Synth Playback Switch", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Synth Volume", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1, + db_scale_5bit_12db_max), +WSS_DOUBLE("Synth Capture Switch", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1), +WSS_DOUBLE("Synth Capture Bypass", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 5, 5, 1, 1), + +CS4236_DOUBLE("Mic Playback Switch", 0, + CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1), +CS4236_DOUBLE("Mic Capture Switch", 0, + CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1), +CS4236_DOUBLE_TLV("Mic Volume", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, + 0, 0, 31, 1, db_scale_5bit_22db_max), +CS4236_DOUBLE("Mic Playback Boost (+20dB)", 0, + CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 5, 5, 1, 0), + +WSS_DOUBLE("Line Playback Switch", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Line Volume", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1, + db_scale_5bit_12db_max), +WSS_DOUBLE("Line Capture Switch", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1), +WSS_DOUBLE("Line Capture Bypass", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 5, 5, 1, 1), + +WSS_DOUBLE("CD Playback Switch", 0, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("CD Volume", 0, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1, + db_scale_5bit_12db_max), +WSS_DOUBLE("CD Capture Switch", 0, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1), + +CS4236_DOUBLE1("Mono Output Playback Switch", 0, + CS4231_MONO_CTRL, CS4236_RIGHT_MIX_CTRL, 6, 7, 1, 1), +CS4236_DOUBLE1("Beep Playback Switch", 0, + CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1), +WSS_SINGLE_TLV("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1, + db_scale_4bit), +WSS_SINGLE("Beep Bypass Playback Switch", 0, CS4231_MONO_CTRL, 5, 1, 0), + +WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, + 0, 0, 15, 0, db_scale_rec_gain), +WSS_DOUBLE("Analog Loopback Capture Switch", 0, + CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0), + +WSS_SINGLE("Loopback Digital Playback Switch", 0, CS4231_LOOPBACK, 0, 1, 0), +CS4236_DOUBLE1_TLV("Loopback Digital Playback Volume", 0, + CS4231_LOOPBACK, CS4236_RIGHT_LOOPBACK, 2, 0, 63, 1, + db_scale_6bit), +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_6db_max, -5600, 200, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_2bit_16db_max, -2400, 800, 0); + +static struct snd_kcontrol_new snd_cs4235_controls[] = { + +WSS_DOUBLE("Master Playback Switch", 0, + CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Master Playback Volume", 0, + CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 0, 0, 31, 1, + db_scale_5bit_6db_max), + +CS4235_OUTPUT_ACCU("Playback Volume", 0, db_scale_2bit_16db_max), + +WSS_DOUBLE("Synth Playback Switch", 1, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +WSS_DOUBLE("Synth Capture Switch", 1, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1), +WSS_DOUBLE_TLV("Synth Volume", 1, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1, + db_scale_5bit_12db_max), + +CS4236_DOUBLE_TLV("Capture Volume", 0, + CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1, + db_scale_2bit), + +WSS_DOUBLE("PCM Playback Switch", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +WSS_DOUBLE("PCM Capture Switch", 0, + CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1), +WSS_DOUBLE_TLV("PCM Volume", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1, + db_scale_6bit), + +CS4236_DOUBLE("DSP Switch", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1), + +CS4236_DOUBLE("FM Switch", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1), + +CS4236_DOUBLE("Wavetable Switch", 0, + CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1), + +CS4236_DOUBLE("Mic Capture Switch", 0, + CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1), +CS4236_DOUBLE("Mic Playback Switch", 0, + CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1), +CS4236_SINGLE_TLV("Mic Volume", 0, CS4236_LEFT_MIC, 0, 31, 1, + db_scale_5bit_22db_max), +CS4236_SINGLE("Mic Boost (+20dB)", 0, CS4236_LEFT_MIC, 5, 1, 0), + +WSS_DOUBLE("Line Playback Switch", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE("Line Capture Switch", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1), +WSS_DOUBLE_TLV("Line Volume", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1, + db_scale_5bit_12db_max), + +WSS_DOUBLE("CD Playback Switch", 1, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE("CD Capture Switch", 1, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1), +WSS_DOUBLE_TLV("CD Volume", 1, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1, + db_scale_5bit_12db_max), + +CS4236_DOUBLE1("Beep Playback Switch", 0, + CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1), +WSS_SINGLE("Beep Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1), + +WSS_DOUBLE("Analog Loopback Switch", 0, + CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0), +}; + +#define CS4236_IEC958_ENABLE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_single, \ + .get = snd_cs4236_get_iec958_switch, .put = snd_cs4236_put_iec958_switch, \ + .private_value = 1 << 16 } + +static int snd_cs4236_get_iec958_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = chip->image[CS4231_ALT_FEATURE_1] & 0x02 ? 1 : 0; +#if 0 + printk(KERN_DEBUG "get valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, " + "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n", + snd_wss_in(chip, CS4231_ALT_FEATURE_1), + snd_cs4236_ctrl_in(chip, 3), + snd_cs4236_ctrl_in(chip, 4), + snd_cs4236_ctrl_in(chip, 5), + snd_cs4236_ctrl_in(chip, 6), + snd_cs4236_ctrl_in(chip, 8)); +#endif + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4236_put_iec958_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short enable, val; + + enable = ucontrol->value.integer.value[0] & 1; + + mutex_lock(&chip->mce_mutex); + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->image[CS4231_ALT_FEATURE_1] & ~0x0e) | (0<<2) | (enable << 1); + change = val != chip->image[CS4231_ALT_FEATURE_1]; + snd_wss_out(chip, CS4231_ALT_FEATURE_1, val); + val = snd_cs4236_ctrl_in(chip, 4) | 0xc0; + snd_cs4236_ctrl_out(chip, 4, val); + udelay(100); + val &= ~0x40; + snd_cs4236_ctrl_out(chip, 4, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + mutex_unlock(&chip->mce_mutex); + +#if 0 + printk(KERN_DEBUG "set valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, " + "C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n", + snd_wss_in(chip, CS4231_ALT_FEATURE_1), + snd_cs4236_ctrl_in(chip, 3), + snd_cs4236_ctrl_in(chip, 4), + snd_cs4236_ctrl_in(chip, 5), + snd_cs4236_ctrl_in(chip, 6), + snd_cs4236_ctrl_in(chip, 8)); +#endif + return change; +} + +static struct snd_kcontrol_new snd_cs4236_iec958_controls[] = { +CS4236_IEC958_ENABLE("IEC958 Output Enable", 0), +CS4236_SINGLEC("IEC958 Output Validity", 0, 4, 4, 1, 0), +CS4236_SINGLEC("IEC958 Output User", 0, 4, 5, 1, 0), +CS4236_SINGLEC("IEC958 Output CSBR", 0, 4, 6, 1, 0), +CS4236_SINGLEC("IEC958 Output Channel Status Low", 0, 5, 1, 127, 0), +CS4236_SINGLEC("IEC958 Output Channel Status High", 0, 6, 0, 255, 0) +}; + +static struct snd_kcontrol_new snd_cs4236_3d_controls_cs4235[] = { +CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0), +CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1) +}; + +static struct snd_kcontrol_new snd_cs4236_3d_controls_cs4237[] = { +CS4236_SINGLEC("3D Control - Switch", 0, 3, 7, 1, 0), +CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1), +CS4236_SINGLEC("3D Control - Center", 0, 2, 0, 15, 1), +CS4236_SINGLEC("3D Control - Mono", 0, 3, 6, 1, 0), +CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0) +}; + +static struct snd_kcontrol_new snd_cs4236_3d_controls_cs4238[] = { +CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0), +CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1), +CS4236_SINGLEC("3D Control - Volume", 0, 2, 0, 15, 1), +CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0) +}; + +int snd_cs4236_mixer(struct snd_wss *chip) +{ + struct snd_card *card; + unsigned int idx, count; + int err; + struct snd_kcontrol_new *kcontrol; + + if (snd_BUG_ON(!chip || !chip->card)) + return -EINVAL; + card = chip->card; + strcpy(card->mixername, snd_wss_chip_id(chip)); + + if (chip->hardware == WSS_HW_CS4235 || + chip->hardware == WSS_HW_CS4239) { + for (idx = 0; idx < ARRAY_SIZE(snd_cs4235_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4235_controls[idx], chip))) < 0) + return err; + } + } else { + for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_controls[idx], chip))) < 0) + return err; + } + } + switch (chip->hardware) { + case WSS_HW_CS4235: + case WSS_HW_CS4239: + count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4235); + kcontrol = snd_cs4236_3d_controls_cs4235; + break; + case WSS_HW_CS4237B: + count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4237); + kcontrol = snd_cs4236_3d_controls_cs4237; + break; + case WSS_HW_CS4238B: + count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4238); + kcontrol = snd_cs4236_3d_controls_cs4238; + break; + default: + count = 0; + kcontrol = NULL; + } + for (idx = 0; idx < count; idx++, kcontrol++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(kcontrol, chip))) < 0) + return err; + } + if (chip->hardware == WSS_HW_CS4237B || + chip->hardware == WSS_HW_CS4238B) { + for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_iec958_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_iec958_controls[idx], chip))) < 0) + return err; + } + } + return 0; +} diff --git a/sound/isa/es1688/Makefile b/sound/isa/es1688/Makefile new file mode 100644 index 000000000..aee1e4ddb --- /dev/null +++ b/sound/isa/es1688/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-es1688-lib-objs := es1688_lib.o +snd-es1688-objs := es1688.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_ES1688) += snd-es1688.o snd-es1688-lib.o +obj-$(CONFIG_SND_GUSEXTREME) += snd-es1688-lib.o diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c new file mode 100644 index 000000000..5a6931441 --- /dev/null +++ b/sound/isa/es1688/es1688.c @@ -0,0 +1,368 @@ +/* + * Driver for generic ESS AudioDrive ESx688 soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +#define CRD_NAME "Generic ESS ES1688/ES688 AudioDrive" +#define DEV_NAME "es1688" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ESS,ES688 PnP AudioDrive,pnp:ESS0100}," + "{ESS,ES1688 PnP AudioDrive,pnp:ESS0102}," + "{ESS,ES688 AudioDrive,pnp:ESS6881}," + "{ESS,ES1688 AudioDrive,pnp:ESS1681}}"); + +MODULE_ALIAS("snd_es968"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; +#endif +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* Usually 0x388 */ +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1}; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for ES1688 driver."); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma8, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver."); + +#ifdef CONFIG_PNP +#define is_isapnp_selected(dev) isapnp[dev] +#else +#define is_isapnp_selected(dev) 0 +#endif + +static int snd_es1688_match(struct device *dev, unsigned int n) +{ + return enable[n] && !is_isapnp_selected(n); +} + +static int snd_es1688_legacy_create(struct snd_card *card, + struct device *dev, unsigned int n) +{ + struct snd_es1688 *chip = card->private_data; + static long possible_ports[] = {0x220, 0x240, 0x260}; + static int possible_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dmas[] = {1, 3, 0, -1}; + + int i, error; + + if (irq[n] == SNDRV_AUTO_IRQ) { + irq[n] = snd_legacy_find_free_irq(possible_irqs); + if (irq[n] < 0) { + dev_err(dev, "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma8[n] == SNDRV_AUTO_DMA) { + dma8[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma8[n] < 0) { + dev_err(dev, "unable to find a free DMA\n"); + return -EBUSY; + } + } + + if (port[n] != SNDRV_AUTO_PORT) + return snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO); + + i = 0; + do { + port[n] = possible_ports[i]; + error = snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_AUTO); + } while (error < 0 && ++i < ARRAY_SIZE(possible_ports)); + + return error; +} + +static int snd_es1688_probe(struct snd_card *card, unsigned int n) +{ + struct snd_es1688 *chip = card->private_data; + struct snd_opl3 *opl3; + int error; + + error = snd_es1688_pcm(card, chip, 0); + if (error < 0) + return error; + + error = snd_es1688_mixer(card, chip); + if (error < 0) + return error; + + strlcpy(card->driver, "ES1688", sizeof(card->driver)); + strlcpy(card->shortname, chip->pcm->name, sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, irq %i, dma %i", chip->pcm->name, chip->port, + chip->irq, chip->dma8); + + if (fm_port[n] == SNDRV_AUTO_PORT) + fm_port[n] = port[n]; /* share the same port */ + + if (fm_port[n] > 0) { + if (snd_opl3_create(card, fm_port[n], fm_port[n] + 2, + OPL3_HW_OPL3, 0, &opl3) < 0) + dev_warn(card->dev, + "opl3 not detected at 0x%lx\n", fm_port[n]); + else { + error = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (error < 0) + return error; + } + } + + if (mpu_irq[n] >= 0 && mpu_irq[n] != SNDRV_AUTO_IRQ && + chip->mpu_port > 0) { + error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688, + chip->mpu_port, 0, + mpu_irq[n], NULL); + if (error < 0) + return error; + } + + return snd_card_register(card); +} + +static int snd_es1688_isa_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + int error; + + error = snd_card_new(dev, index[n], id[n], THIS_MODULE, + sizeof(struct snd_es1688), &card); + if (error < 0) + return error; + + error = snd_es1688_legacy_create(card, dev, n); + if (error < 0) + goto out; + + error = snd_es1688_probe(card, n); + if (error < 0) + goto out; + + dev_set_drvdata(dev, card); + + return 0; +out: + snd_card_free(card); + return error; +} + +static int snd_es1688_isa_remove(struct device *dev, unsigned int n) +{ + snd_card_free(dev_get_drvdata(dev)); + return 0; +} + +static struct isa_driver snd_es1688_driver = { + .match = snd_es1688_match, + .probe = snd_es1688_isa_probe, + .remove = snd_es1688_isa_remove, +#if 0 /* FIXME */ + .suspend = snd_es1688_suspend, + .resume = snd_es1688_resume, +#endif + .driver = { + .name = DEV_NAME + } +}; + +static int snd_es968_pnp_is_probed; + +#ifdef CONFIG_PNP +static int snd_card_es968_pnp(struct snd_card *card, unsigned int n, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct snd_es1688 *chip = card->private_data; + struct pnp_dev *pdev; + int error; + + pdev = pnp_request_card_device(pcard, pid->devs[0].id, NULL); + if (pdev == NULL) + return -ENODEV; + + error = pnp_activate_dev(pdev); + if (error < 0) { + snd_printk(KERN_ERR "ES968 pnp configure failure\n"); + return error; + } + port[n] = pnp_port_start(pdev, 0); + dma8[n] = pnp_dma(pdev, 0); + irq[n] = pnp_irq(pdev, 0); + + return snd_es1688_create(card, chip, port[n], mpu_port[n], irq[n], + mpu_irq[n], dma8[n], ES1688_HW_AUTO); +} + +static int snd_es968_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct snd_card *card; + static unsigned int dev; + int error; + + if (snd_es968_pnp_is_probed) + return -EBUSY; + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev == SNDRV_CARDS) + return -ENODEV; + + error = snd_card_new(&pcard->card->dev, + index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_es1688), &card); + if (error < 0) + return error; + + error = snd_card_es968_pnp(card, dev, pcard, pid); + if (error < 0) { + snd_card_free(card); + return error; + } + error = snd_es1688_probe(card, dev); + if (error < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + snd_es968_pnp_is_probed = 1; + return 0; +} + +static void snd_es968_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); + snd_es968_pnp_is_probed = 0; +} + +#ifdef CONFIG_PM +static int snd_es968_pnp_suspend(struct pnp_card_link *pcard, + pm_message_t state) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_es1688 *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + return 0; +} + +static int snd_es968_pnp_resume(struct pnp_card_link *pcard) +{ + struct snd_card *card = pnp_get_card_drvdata(pcard); + struct snd_es1688 *chip = card->private_data; + + snd_es1688_reset(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static const struct pnp_card_device_id snd_es968_pnpids[] = { + { .id = "ESS0968", .devs = { { "@@@0968" }, } }, + { .id = "ESS0968", .devs = { { "ESS0968" }, } }, + { .id = "", } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids); + +static struct pnp_card_driver es968_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = DEV_NAME " PnP", + .id_table = snd_es968_pnpids, + .probe = snd_es968_pnp_detect, + .remove = snd_es968_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_es968_pnp_suspend, + .resume = snd_es968_pnp_resume, +#endif +}; +#endif + +static int __init alsa_card_es1688_init(void) +{ +#ifdef CONFIG_PNP + pnp_register_card_driver(&es968_pnpc_driver); + if (snd_es968_pnp_is_probed) + return 0; + pnp_unregister_card_driver(&es968_pnpc_driver); +#endif + return isa_register_driver(&snd_es1688_driver, SNDRV_CARDS); +} + +static void __exit alsa_card_es1688_exit(void) +{ + if (!snd_es968_pnp_is_probed) { + isa_unregister_driver(&snd_es1688_driver); + return; + } +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&es968_pnpc_driver); +#endif +} + +module_init(alsa_card_es1688_init); +module_exit(alsa_card_es1688_exit); diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c new file mode 100644 index 000000000..50cdce0e8 --- /dev/null +++ b/sound/isa/es1688/es1688_lib.c @@ -0,0 +1,1031 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of ESS ES1688/688/488 chip + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ESS ESx688 lowlevel module"); +MODULE_LICENSE("GPL"); + +static int snd_es1688_dsp_command(struct snd_es1688 *chip, unsigned char val) +{ + int i; + + for (i = 10000; i; i--) + if ((inb(ES1688P(chip, STATUS)) & 0x80) == 0) { + outb(val, ES1688P(chip, COMMAND)); + return 1; + } +#ifdef CONFIG_SND_DEBUG + printk(KERN_DEBUG "snd_es1688_dsp_command: timeout (0x%x)\n", val); +#endif + return 0; +} + +static int snd_es1688_dsp_get_byte(struct snd_es1688 *chip) +{ + int i; + + for (i = 1000; i; i--) + if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) + return inb(ES1688P(chip, READ)); + snd_printd("es1688 get byte failed: 0x%lx = 0x%x!!!\n", ES1688P(chip, DATA_AVAIL), inb(ES1688P(chip, DATA_AVAIL))); + return -ENODEV; +} + +static int snd_es1688_write(struct snd_es1688 *chip, + unsigned char reg, unsigned char data) +{ + if (!snd_es1688_dsp_command(chip, reg)) + return 0; + return snd_es1688_dsp_command(chip, data); +} + +static int snd_es1688_read(struct snd_es1688 *chip, unsigned char reg) +{ + /* Read a byte from an extended mode register of ES1688 */ + if (!snd_es1688_dsp_command(chip, 0xc0)) + return -1; + if (!snd_es1688_dsp_command(chip, reg)) + return -1; + return snd_es1688_dsp_get_byte(chip); +} + +void snd_es1688_mixer_write(struct snd_es1688 *chip, + unsigned char reg, unsigned char data) +{ + outb(reg, ES1688P(chip, MIXER_ADDR)); + udelay(10); + outb(data, ES1688P(chip, MIXER_DATA)); + udelay(10); +} + +static unsigned char snd_es1688_mixer_read(struct snd_es1688 *chip, unsigned char reg) +{ + unsigned char result; + + outb(reg, ES1688P(chip, MIXER_ADDR)); + udelay(10); + result = inb(ES1688P(chip, MIXER_DATA)); + udelay(10); + return result; +} + +int snd_es1688_reset(struct snd_es1688 *chip) +{ + int i; + + outb(3, ES1688P(chip, RESET)); /* valid only for ESS chips, SB -> 1 */ + udelay(10); + outb(0, ES1688P(chip, RESET)); + udelay(30); + for (i = 0; i < 1000 && !(inb(ES1688P(chip, DATA_AVAIL)) & 0x80); i++); + if (inb(ES1688P(chip, READ)) != 0xaa) { + snd_printd("ess_reset at 0x%lx: failed!!!\n", chip->port); + return -ENODEV; + } + snd_es1688_dsp_command(chip, 0xc6); /* enable extended mode */ + return 0; +} +EXPORT_SYMBOL(snd_es1688_reset); + +static int snd_es1688_probe(struct snd_es1688 *chip) +{ + unsigned long flags; + unsigned short major, minor, hw; + int i; + + /* + * initialization sequence + */ + + spin_lock_irqsave(&chip->reg_lock, flags); /* Some ESS1688 cards need this */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE0)); /* ENABLE0 */ + + if (snd_es1688_reset(chip) < 0) { + snd_printdd("ESS: [0x%lx] reset failed... 0x%x\n", chip->port, inb(ES1688P(chip, READ))); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -ENODEV; + } + snd_es1688_dsp_command(chip, 0xe7); /* return identification */ + + for (i = 1000, major = minor = 0; i; i--) { + if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) { + if (major == 0) { + major = inb(ES1688P(chip, READ)); + } else { + minor = inb(ES1688P(chip, READ)); + } + } + } + + spin_unlock_irqrestore(&chip->reg_lock, flags); + + snd_printdd("ESS: [0x%lx] found.. major = 0x%x, minor = 0x%x\n", chip->port, major, minor); + + chip->version = (major << 8) | minor; + if (!chip->version) + return -ENODEV; /* probably SB */ + + hw = ES1688_HW_AUTO; + switch (chip->version & 0xfff0) { + case 0x4880: + snd_printk(KERN_ERR "[0x%lx] ESS: AudioDrive ES488 detected, " + "but driver is in another place\n", chip->port); + return -ENODEV; + case 0x6880: + hw = (chip->version & 0x0f) >= 8 ? ES1688_HW_1688 : ES1688_HW_688; + break; + default: + snd_printk(KERN_ERR "[0x%lx] ESS: unknown AudioDrive chip " + "with version 0x%x (Jazz16 soundcard?)\n", + chip->port, chip->version); + return -ENODEV; + } + + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */ + snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */ + spin_unlock_irqrestore(&chip->reg_lock, flags); + + /* enable joystick, but disable OPL3 */ + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_es1688_mixer_write(chip, 0x40, 0x01); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + return 0; +} + +static int snd_es1688_init(struct snd_es1688 * chip, int enable) +{ + static int irqs[16] = {-1, -1, 0, -1, -1, 1, -1, 2, -1, 0, 3, -1, -1, -1, -1, -1}; + unsigned long flags; + int cfg, irq_bits, dma, dma_bits, tmp, tmp1; + + /* ok.. setup MPU-401 port and joystick and OPL3 */ + cfg = 0x01; /* enable joystick, but disable OPL3 */ + if (enable && chip->mpu_port >= 0x300 && chip->mpu_irq > 0 && chip->hardware != ES1688_HW_688) { + tmp = (chip->mpu_port & 0x0f0) >> 4; + if (tmp <= 3) { + switch (chip->mpu_irq) { + case 9: + tmp1 = 4; + break; + case 5: + tmp1 = 5; + break; + case 7: + tmp1 = 6; + break; + case 10: + tmp1 = 7; + break; + default: + tmp1 = 0; + } + if (tmp1) { + cfg |= (tmp << 3) | (tmp1 << 5); + } + } + } +#if 0 + snd_printk(KERN_DEBUG "mpu cfg = 0x%x\n", cfg); +#endif + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_mixer_write(chip, 0x40, cfg); + spin_unlock_irqrestore(&chip->reg_lock, flags); + /* --- */ + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_read(chip, 0xb1); + snd_es1688_read(chip, 0xb2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (enable) { + cfg = 0xf0; /* enable only DMA counter interrupt */ + irq_bits = irqs[chip->irq & 0x0f]; + if (irq_bits < 0) { + snd_printk(KERN_ERR "[0x%lx] ESS: bad IRQ %d " + "for ES1688 chip!!\n", + chip->port, chip->irq); +#if 0 + irq_bits = 0; + cfg = 0x10; +#endif + return -EINVAL; + } + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb1, cfg | (irq_bits << 2)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + cfg = 0xf0; /* extended mode DMA enable */ + dma = chip->dma8; + if (dma > 3 || dma == 2) { + snd_printk(KERN_ERR "[0x%lx] ESS: bad DMA channel %d " + "for ES1688 chip!!\n", chip->port, dma); +#if 0 + dma_bits = 0; + cfg = 0x00; /* disable all DMA */ +#endif + return -EINVAL; + } else { + dma_bits = dma; + if (dma != 3) + dma_bits++; + } + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb2, cfg | (dma_bits << 2)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + } else { + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */ + snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */ + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_read(chip, 0xb1); + snd_es1688_read(chip, 0xb2); + snd_es1688_reset(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +/* + + */ + +static const struct snd_ratnum clocks[2] = { + { + .num = 795444, + .den_min = 1, + .den_max = 128, + .den_step = 1, + }, + { + .num = 397722, + .den_min = 1, + .den_max = 128, + .den_step = 1, + } +}; + +static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = { + .nrats = 2, + .rats = clocks, +}; + +static void snd_es1688_set_rate(struct snd_es1688 *chip, struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int bits, divider; + + if (runtime->rate_num == clocks[0].num) + bits = 256 - runtime->rate_den; + else + bits = 128 - runtime->rate_den; + /* set filter register */ + divider = 256 - 7160000*20/(8*82*runtime->rate); + /* write result to hardware */ + snd_es1688_write(chip, 0xa1, bits); + snd_es1688_write(chip, 0xa2, divider); +} + +static int snd_es1688_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_es1688_trigger(struct snd_es1688 *chip, int cmd, unsigned char value) +{ + int val; + + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + value = 0x00; + } else if (cmd != SNDRV_PCM_TRIGGER_START) { + return -EINVAL; + } + spin_lock(&chip->reg_lock); + chip->trigger_value = value; + val = snd_es1688_read(chip, 0xb8); + if ((val < 0) || (val & 0x0f) == value) { + spin_unlock(&chip->reg_lock); + return -EINVAL; /* something is wrong */ + } +#if 0 + printk(KERN_DEBUG "trigger: val = 0x%x, value = 0x%x\n", val, value); + printk(KERN_DEBUG "trigger: pointer = 0x%x\n", + snd_dma_pointer(chip->dma8, chip->dma_size)); +#endif + snd_es1688_write(chip, 0xb8, (val & 0xf0) | value); + spin_unlock(&chip->reg_lock); + return 0; +} + +static int snd_es1688_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_es1688_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_es1688_playback_prepare(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma_size = size; + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_reset(chip); + snd_es1688_set_rate(chip, substream); + snd_es1688_write(chip, 0xb8, 4); /* auto init DMA mode */ + snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels)); + snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */ + if (runtime->channels == 1) { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit mono */ + snd_es1688_write(chip, 0xb6, 0x80); + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0xd0); + } else { + /* 16. bit mono */ + snd_es1688_write(chip, 0xb6, 0x00); + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xf4); + } + } else { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit stereo */ + snd_es1688_write(chip, 0xb6, 0x80); + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0x98); + } else { + /* 16. bit stereo */ + snd_es1688_write(chip, 0xb6, 0x00); + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xbc); + } + } + snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50); + snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50); + snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKON); + spin_unlock_irqrestore(&chip->reg_lock, flags); + /* --- */ + count = -count; + snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xa4, (unsigned char) count); + snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_es1688_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + return snd_es1688_trigger(chip, cmd, 0x05); +} + +static int snd_es1688_capture_prepare(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma_size = size; + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_reset(chip); + snd_es1688_set_rate(chip, substream); + snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKOFF); + snd_es1688_write(chip, 0xb8, 0x0e); /* auto init DMA mode */ + snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels)); + snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */ + if (runtime->channels == 1) { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit mono */ + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0xd0); + } else { + /* 16. bit mono */ + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xf4); + } + } else { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit stereo */ + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0x98); + } else { + /* 16. bit stereo */ + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xbc); + } + } + snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50); + snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50); + spin_unlock_irqrestore(&chip->reg_lock, flags); + /* --- */ + count = -count; + snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xa4, (unsigned char) count); + snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_es1688_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + return snd_es1688_trigger(chip, cmd, 0x0f); +} + +static irqreturn_t snd_es1688_interrupt(int irq, void *dev_id) +{ + struct snd_es1688 *chip = dev_id; + + if (chip->trigger_value == 0x05) /* ok.. playback is active */ + snd_pcm_period_elapsed(chip->playback_substream); + if (chip->trigger_value == 0x0f) /* ok.. capture is active */ + snd_pcm_period_elapsed(chip->capture_substream); + + inb(ES1688P(chip, DATA_AVAIL)); /* ack interrupt */ + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_es1688_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (chip->trigger_value != 0x05) + return 0; + ptr = snd_dma_pointer(chip->dma8, chip->dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_es1688_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (chip->trigger_value != 0x0f) + return 0; + ptr = snd_dma_pointer(chip->dma8, chip->dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static const struct snd_pcm_hardware snd_es1688_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_es1688_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + + */ + +static int snd_es1688_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (chip->capture_substream != NULL) + return -EAGAIN; + chip->playback_substream = substream; + runtime->hw = snd_es1688_playback; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + return 0; +} + +static int snd_es1688_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + if (chip->playback_substream != NULL) + return -EAGAIN; + chip->capture_substream = substream; + runtime->hw = snd_es1688_capture; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + return 0; +} + +static int snd_es1688_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + return 0; +} + +static int snd_es1688_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_es1688 *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + return 0; +} + +static int snd_es1688_free(struct snd_es1688 *chip) +{ + if (chip->hardware != ES1688_HW_UNDEF) + snd_es1688_init(chip, 0); + release_and_free_resource(chip->res_port); + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); + if (chip->dma8 >= 0) { + disable_dma(chip->dma8); + free_dma(chip->dma8); + } + return 0; +} + +static int snd_es1688_dev_free(struct snd_device *device) +{ + struct snd_es1688 *chip = device->device_data; + return snd_es1688_free(chip); +} + +static const char *snd_es1688_chip_id(struct snd_es1688 *chip) +{ + static char tmp[16]; + sprintf(tmp, "ES%s688 rev %i", chip->hardware == ES1688_HW_688 ? "" : "1", chip->version & 0x0f); + return tmp; +} + +int snd_es1688_create(struct snd_card *card, + struct snd_es1688 *chip, + unsigned long port, + unsigned long mpu_port, + int irq, + int mpu_irq, + int dma8, + unsigned short hardware) +{ + static struct snd_device_ops ops = { + .dev_free = snd_es1688_dev_free, + }; + + int err; + + if (chip == NULL) + return -ENOMEM; + chip->irq = -1; + chip->dma8 = -1; + chip->hardware = ES1688_HW_UNDEF; + + chip->res_port = request_region(port + 4, 12, "ES1688"); + if (chip->res_port == NULL) { + snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4); + err = -EBUSY; + goto exit; + } + + err = request_irq(irq, snd_es1688_interrupt, 0, "ES1688", (void *) chip); + if (err < 0) { + snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq); + goto exit; + } + + chip->irq = irq; + err = request_dma(dma8, "ES1688"); + + if (err < 0) { + snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8); + goto exit; + } + chip->dma8 = dma8; + + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->mixer_lock); + chip->port = port; + mpu_port &= ~0x000f; + if (mpu_port < 0x300 || mpu_port > 0x330) + mpu_port = 0; + chip->mpu_port = mpu_port; + chip->mpu_irq = mpu_irq; + chip->hardware = hardware; + + err = snd_es1688_probe(chip); + if (err < 0) + goto exit; + + err = snd_es1688_init(chip, 1); + if (err < 0) + goto exit; + + /* Register device */ + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); +exit: + if (err) + snd_es1688_free(chip); + return err; +} + +static const struct snd_pcm_ops snd_es1688_playback_ops = { + .open = snd_es1688_playback_open, + .close = snd_es1688_playback_close, + .ioctl = snd_es1688_ioctl, + .hw_params = snd_es1688_hw_params, + .hw_free = snd_es1688_hw_free, + .prepare = snd_es1688_playback_prepare, + .trigger = snd_es1688_playback_trigger, + .pointer = snd_es1688_playback_pointer, +}; + +static const struct snd_pcm_ops snd_es1688_capture_ops = { + .open = snd_es1688_capture_open, + .close = snd_es1688_capture_close, + .ioctl = snd_es1688_ioctl, + .hw_params = snd_es1688_hw_params, + .hw_free = snd_es1688_hw_free, + .prepare = snd_es1688_capture_prepare, + .trigger = snd_es1688_capture_trigger, + .pointer = snd_es1688_capture_pointer, +}; + +int snd_es1688_pcm(struct snd_card *card, struct snd_es1688 *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(card, "ESx688", device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1688_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1688_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + strcpy(pcm->name, snd_es1688_chip_id(chip)); + chip->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, 64*1024); + return 0; +} + +/* + * MIXER part + */ + +static int snd_es1688_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[8] = { + "Mic", "Mic Master", "CD", "AOUT", + "Mic1", "Mix", "Line", "Master" + }; + + return snd_ctl_enum_info(uinfo, 1, 8, texts); +} + +static int snd_es1688_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = snd_es1688_mixer_read(chip, ES1688_REC_DEV) & 7; + return 0; +} + +static int snd_es1688_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char oval, nval; + int change; + + if (ucontrol->value.enumerated.item[0] > 8) + return -EINVAL; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = snd_es1688_mixer_read(chip, ES1688_REC_DEV); + nval = (ucontrol->value.enumerated.item[0] & 7) | (oval & ~15); + change = nval != oval; + if (change) + snd_es1688_mixer_write(chip, ES1688_REC_DEV, nval); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define ES1688_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_es1688_info_single, \ + .get = snd_es1688_get_single, .put = snd_es1688_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_es1688_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_es1688_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (snd_es1688_mixer_read(chip, reg) >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_es1688_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned char oval, nval; + + nval = (ucontrol->value.integer.value[0] & mask); + if (invert) + nval = mask - nval; + nval <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = snd_es1688_mixer_read(chip, reg); + nval = (oval & ~(mask << shift)) | nval; + change = nval != oval; + if (change) + snd_es1688_mixer_write(chip, reg, nval); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define ES1688_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_es1688_info_double, \ + .get = snd_es1688_get_double, .put = snd_es1688_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_es1688_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_es1688_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + unsigned char left, right; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (left_reg < 0xa0) + left = snd_es1688_mixer_read(chip, left_reg); + else + left = snd_es1688_read(chip, left_reg); + if (left_reg != right_reg) { + if (right_reg < 0xa0) + right = snd_es1688_mixer_read(chip, right_reg); + else + right = snd_es1688_read(chip, right_reg); + } else + right = left; + spin_unlock_irqrestore(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (left >> shift_left) & mask; + ucontrol->value.integer.value[1] = (right >> shift_right) & mask; + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_es1688_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es1688 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned char val1, val2, oval1, oval2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + if (left_reg != right_reg) { + if (left_reg < 0xa0) + oval1 = snd_es1688_mixer_read(chip, left_reg); + else + oval1 = snd_es1688_read(chip, left_reg); + if (right_reg < 0xa0) + oval2 = snd_es1688_mixer_read(chip, right_reg); + else + oval2 = snd_es1688_read(chip, right_reg); + val1 = (oval1 & ~(mask << shift_left)) | val1; + val2 = (oval2 & ~(mask << shift_right)) | val2; + change = val1 != oval1 || val2 != oval2; + if (change) { + if (left_reg < 0xa0) + snd_es1688_mixer_write(chip, left_reg, val1); + else + snd_es1688_write(chip, left_reg, val1); + if (right_reg < 0xa0) + snd_es1688_mixer_write(chip, right_reg, val1); + else + snd_es1688_write(chip, right_reg, val1); + } + } else { + if (left_reg < 0xa0) + oval1 = snd_es1688_mixer_read(chip, left_reg); + else + oval1 = snd_es1688_read(chip, left_reg); + val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != oval1; + if (change) { + if (left_reg < 0xa0) + snd_es1688_mixer_write(chip, left_reg, val1); + else + snd_es1688_write(chip, left_reg, val1); + } + + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static struct snd_kcontrol_new snd_es1688_controls[] = { +ES1688_DOUBLE("Master Playback Volume", 0, ES1688_MASTER_DEV, ES1688_MASTER_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("PCM Playback Volume", 0, ES1688_PCM_DEV, ES1688_PCM_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("Line Playback Volume", 0, ES1688_LINE_DEV, ES1688_LINE_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("CD Playback Volume", 0, ES1688_CD_DEV, ES1688_CD_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("FM Playback Volume", 0, ES1688_FM_DEV, ES1688_FM_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("Mic Playback Volume", 0, ES1688_MIC_DEV, ES1688_MIC_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("Aux Playback Volume", 0, ES1688_AUX_DEV, ES1688_AUX_DEV, 4, 0, 15, 0), +ES1688_SINGLE("Beep Playback Volume", 0, ES1688_SPEAKER_DEV, 0, 7, 0), +ES1688_DOUBLE("Capture Volume", 0, ES1688_RECLEV_DEV, ES1688_RECLEV_DEV, 4, 0, 15, 0), +ES1688_SINGLE("Capture Switch", 0, ES1688_REC_DEV, 4, 1, 1), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_es1688_info_mux, + .get = snd_es1688_get_mux, + .put = snd_es1688_put_mux, +}, +}; + +#define ES1688_INIT_TABLE_SIZE (sizeof(snd_es1688_init_table)/2) + +static unsigned char snd_es1688_init_table[][2] = { + { ES1688_MASTER_DEV, 0 }, + { ES1688_PCM_DEV, 0 }, + { ES1688_LINE_DEV, 0 }, + { ES1688_CD_DEV, 0 }, + { ES1688_FM_DEV, 0 }, + { ES1688_MIC_DEV, 0 }, + { ES1688_AUX_DEV, 0 }, + { ES1688_SPEAKER_DEV, 0 }, + { ES1688_RECLEV_DEV, 0 }, + { ES1688_REC_DEV, 0x17 } +}; + +int snd_es1688_mixer(struct snd_card *card, struct snd_es1688 *chip) +{ + unsigned int idx; + int err; + unsigned char reg, val; + + if (snd_BUG_ON(!chip || !card)) + return -EINVAL; + + strcpy(card->mixername, snd_es1688_chip_id(chip)); + + for (idx = 0; idx < ARRAY_SIZE(snd_es1688_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es1688_controls[idx], chip))) < 0) + return err; + } + for (idx = 0; idx < ES1688_INIT_TABLE_SIZE; idx++) { + reg = snd_es1688_init_table[idx][0]; + val = snd_es1688_init_table[idx][1]; + if (reg < 0xa0) + snd_es1688_mixer_write(chip, reg, val); + else + snd_es1688_write(chip, reg, val); + } + return 0; +} + +EXPORT_SYMBOL(snd_es1688_mixer_write); +EXPORT_SYMBOL(snd_es1688_create); +EXPORT_SYMBOL(snd_es1688_pcm); +EXPORT_SYMBOL(snd_es1688_mixer); diff --git a/sound/isa/es18xx.c b/sound/isa/es18xx.c new file mode 100644 index 000000000..0d103d6f8 --- /dev/null +++ b/sound/isa/es18xx.c @@ -0,0 +1,2441 @@ +/* + * Driver for generic ESS AudioDrive ES18xx soundcards + * Copyright (c) by Christian Fischbach + * Copyright (c) by Abramo Bagnara + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* GENERAL NOTES: + * + * BUGS: + * - There are pops (we can't delay in trigger function, cause midlevel + * often need to trigger down and then up very quickly). + * Any ideas? + * - Support for 16 bit DMA seems to be broken. I've no hardware to tune it. + */ + +/* + * ES1868 NOTES: + * - The chip has one half duplex pcm (with very limited full duplex support). + * + * - Duplex stereophonic sound is impossible. + * - Record and playback must share the same frequency rate. + * + * - The driver use dma2 for playback and dma1 for capture. + */ + +/* + * ES1869 NOTES: + * + * - there are a first full duplex pcm and a second playback only pcm + * (incompatible with first pcm capture) + * + * - there is support for the capture volume and ESS Spatializer 3D effect. + * + * - contrarily to some pages in DS_1869.PDF the rates can be set + * independently. + * + * - Zoom Video is implemented by sharing the FM DAC, thus the user can + * have either FM playback or Video playback but not both simultaneously. + * The Video Playback Switch mixer control toggles this choice. + * + * BUGS: + * + * - There is a major trouble I noted: + * + * using both channel for playback stereo 16 bit samples at 44100 Hz + * the second pcm (Audio1) DMA slows down irregularly and sound is garbled. + * + * The same happens using Audio1 for captureing. + * + * The Windows driver does not suffer of this (although it use Audio1 + * only for captureing). I'm unable to discover why. + * + */ + +/* + * ES1879 NOTES: + * - When Zoom Video is enabled (reg 0x71 bit 6 toggled on) the PCM playback + * seems to be effected (speaker_test plays a lower frequency). Can't find + * anything in the datasheet to account for this, so a Video Playback Switch + * control has been included to allow ZV to be enabled only when necessary. + * Then again on at least one test system the 0x71 bit 6 enable bit is not + * needed for ZV, so maybe the datasheet is entirely wrong here. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +#define PFX "es18xx: " + +struct snd_es18xx { + unsigned long port; /* port of ESS chip */ + unsigned long ctrl_port; /* Control port of ESS chip */ + struct resource *res_port; + struct resource *res_mpu_port; + struct resource *res_ctrl_port; + int irq; /* IRQ number of ESS chip */ + int dma1; /* DMA1 */ + int dma2; /* DMA2 */ + unsigned short version; /* version of ESS chip */ + int caps; /* Chip capabilities */ + unsigned short audio2_vol; /* volume level of audio2 */ + + unsigned short active; /* active channel mask */ + unsigned int dma1_shift; + unsigned int dma2_shift; + + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_a_substream; + struct snd_pcm_substream *capture_a_substream; + struct snd_pcm_substream *playback_b_substream; + + struct snd_rawmidi *rmidi; + + struct snd_kcontrol *hw_volume; + struct snd_kcontrol *hw_switch; + struct snd_kcontrol *master_volume; + struct snd_kcontrol *master_switch; + + spinlock_t reg_lock; + spinlock_t mixer_lock; +#ifdef CONFIG_PM + unsigned char pm_reg; +#endif +#ifdef CONFIG_PNP + struct pnp_dev *dev; + struct pnp_dev *devc; +#endif +}; + +#define AUDIO1_IRQ 0x01 +#define AUDIO2_IRQ 0x02 +#define HWV_IRQ 0x04 +#define MPU_IRQ 0x08 + +#define ES18XX_PCM2 0x0001 /* Has two useable PCM */ +#define ES18XX_SPATIALIZER 0x0002 /* Has 3D Spatializer */ +#define ES18XX_RECMIX 0x0004 /* Has record mixer */ +#define ES18XX_DUPLEX_MONO 0x0008 /* Has mono duplex only */ +#define ES18XX_DUPLEX_SAME 0x0010 /* Playback and record must share the same rate */ +#define ES18XX_NEW_RATE 0x0020 /* More precise rate setting */ +#define ES18XX_AUXB 0x0040 /* AuxB mixer control */ +#define ES18XX_HWV 0x0080 /* Has separate hardware volume mixer controls*/ +#define ES18XX_MONO 0x0100 /* Mono_in mixer control */ +#define ES18XX_I2S 0x0200 /* I2S mixer control */ +#define ES18XX_MUTEREC 0x0400 /* Record source can be muted */ +#define ES18XX_CONTROL 0x0800 /* Has control ports */ +#define ES18XX_GPO_2BIT 0x1000 /* GPO0,1 controlled by PM port */ + +/* Power Management */ +#define ES18XX_PM 0x07 +#define ES18XX_PM_GPO0 0x01 +#define ES18XX_PM_GPO1 0x02 +#define ES18XX_PM_PDR 0x04 +#define ES18XX_PM_ANA 0x08 +#define ES18XX_PM_FM 0x020 +#define ES18XX_PM_SUS 0x080 + +/* Lowlevel */ + +#define DAC1 0x01 +#define ADC1 0x02 +#define DAC2 0x04 +#define MILLISECOND 10000 + +static int snd_es18xx_dsp_command(struct snd_es18xx *chip, unsigned char val) +{ + int i; + + for(i = MILLISECOND; i; i--) + if ((inb(chip->port + 0x0C) & 0x80) == 0) { + outb(val, chip->port + 0x0C); + return 0; + } + snd_printk(KERN_ERR "dsp_command: timeout (0x%x)\n", val); + return -EINVAL; +} + +static int snd_es18xx_dsp_get_byte(struct snd_es18xx *chip) +{ + int i; + + for(i = MILLISECOND/10; i; i--) + if (inb(chip->port + 0x0C) & 0x40) + return inb(chip->port + 0x0A); + snd_printk(KERN_ERR "dsp_get_byte failed: 0x%lx = 0x%x!!!\n", + chip->port + 0x0A, inb(chip->port + 0x0A)); + return -ENODEV; +} + +#undef REG_DEBUG + +static int snd_es18xx_write(struct snd_es18xx *chip, + unsigned char reg, unsigned char data) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&chip->reg_lock, flags); + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_command(chip, data); + end: + spin_unlock_irqrestore(&chip->reg_lock, flags); +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Reg %02x set to %02x\n", reg, data); +#endif + return ret; +} + +static int snd_es18xx_read(struct snd_es18xx *chip, unsigned char reg) +{ + unsigned long flags; + int ret, data; + spin_lock_irqsave(&chip->reg_lock, flags); + ret = snd_es18xx_dsp_command(chip, 0xC0); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + data = snd_es18xx_dsp_get_byte(chip); + ret = data; +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Reg %02x now is %02x (%d)\n", reg, data, ret); +#endif + end: + spin_unlock_irqrestore(&chip->reg_lock, flags); + return ret; +} + +/* Return old value */ +static int snd_es18xx_bits(struct snd_es18xx *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + int ret; + unsigned char old, new, oval; + unsigned long flags; + spin_lock_irqsave(&chip->reg_lock, flags); + ret = snd_es18xx_dsp_command(chip, 0xC0); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_get_byte(chip); + if (ret < 0) { + goto end; + } + old = ret; + oval = old & mask; + if (val != oval) { + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + new = (old & ~mask) | (val & mask); + ret = snd_es18xx_dsp_command(chip, new); + if (ret < 0) + goto end; +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Reg %02x was %02x, set to %02x (%d)\n", + reg, old, new, ret); +#endif + } + ret = oval; + end: + spin_unlock_irqrestore(&chip->reg_lock, flags); + return ret; +} + +static inline void snd_es18xx_mixer_write(struct snd_es18xx *chip, + unsigned char reg, unsigned char data) +{ + unsigned long flags; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + outb(data, chip->port + 0x05); + spin_unlock_irqrestore(&chip->mixer_lock, flags); +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Mixer reg %02x set to %02x\n", reg, data); +#endif +} + +static inline int snd_es18xx_mixer_read(struct snd_es18xx *chip, unsigned char reg) +{ + unsigned long flags; + int data; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + data = inb(chip->port + 0x05); + spin_unlock_irqrestore(&chip->mixer_lock, flags); +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Mixer reg %02x now is %02x\n", reg, data); +#endif + return data; +} + +/* Return old value */ +static inline int snd_es18xx_mixer_bits(struct snd_es18xx *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + unsigned char old, new, oval; + unsigned long flags; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + old = inb(chip->port + 0x05); + oval = old & mask; + if (val != oval) { + new = (old & ~mask) | (val & mask); + outb(new, chip->port + 0x05); +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Mixer reg %02x was %02x, set to %02x\n", + reg, old, new); +#endif + } + spin_unlock_irqrestore(&chip->mixer_lock, flags); + return oval; +} + +static inline int snd_es18xx_mixer_writable(struct snd_es18xx *chip, unsigned char reg, + unsigned char mask) +{ + int old, expected, new; + unsigned long flags; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + old = inb(chip->port + 0x05); + expected = old ^ mask; + outb(expected, chip->port + 0x05); + new = inb(chip->port + 0x05); + spin_unlock_irqrestore(&chip->mixer_lock, flags); +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Mixer reg %02x was %02x, set to %02x, now is %02x\n", + reg, old, expected, new); +#endif + return expected == new; +} + + +static int snd_es18xx_reset(struct snd_es18xx *chip) +{ + int i; + outb(0x03, chip->port + 0x06); + inb(chip->port + 0x06); + outb(0x00, chip->port + 0x06); + for(i = 0; i < MILLISECOND && !(inb(chip->port + 0x0E) & 0x80); i++); + if (inb(chip->port + 0x0A) != 0xAA) + return -1; + return 0; +} + +static int snd_es18xx_reset_fifo(struct snd_es18xx *chip) +{ + outb(0x02, chip->port + 0x06); + inb(chip->port + 0x06); + outb(0x00, chip->port + 0x06); + return 0; +} + +static const struct snd_ratnum new_clocks[2] = { + { + .num = 793800, + .den_min = 1, + .den_max = 128, + .den_step = 1, + }, + { + .num = 768000, + .den_min = 1, + .den_max = 128, + .den_step = 1, + } +}; + +static const struct snd_pcm_hw_constraint_ratnums new_hw_constraints_clocks = { + .nrats = 2, + .rats = new_clocks, +}; + +static const struct snd_ratnum old_clocks[2] = { + { + .num = 795444, + .den_min = 1, + .den_max = 128, + .den_step = 1, + }, + { + .num = 397722, + .den_min = 1, + .den_max = 128, + .den_step = 1, + } +}; + +static const struct snd_pcm_hw_constraint_ratnums old_hw_constraints_clocks = { + .nrats = 2, + .rats = old_clocks, +}; + + +static void snd_es18xx_rate_set(struct snd_es18xx *chip, + struct snd_pcm_substream *substream, + int mode) +{ + unsigned int bits, div0; + struct snd_pcm_runtime *runtime = substream->runtime; + if (chip->caps & ES18XX_NEW_RATE) { + if (runtime->rate_num == new_clocks[0].num) + bits = 128 - runtime->rate_den; + else + bits = 256 - runtime->rate_den; + } else { + if (runtime->rate_num == old_clocks[0].num) + bits = 256 - runtime->rate_den; + else + bits = 128 - runtime->rate_den; + } + + /* set filter register */ + div0 = 256 - 7160000*20/(8*82*runtime->rate); + + if ((chip->caps & ES18XX_PCM2) && mode == DAC2) { + snd_es18xx_mixer_write(chip, 0x70, bits); + /* + * Comment from kernel oss driver: + * FKS: fascinating: 0x72 doesn't seem to work. + */ + snd_es18xx_write(chip, 0xA2, div0); + snd_es18xx_mixer_write(chip, 0x72, div0); + } else { + snd_es18xx_write(chip, 0xA1, bits); + snd_es18xx_write(chip, 0xA2, div0); + } +} + +static int snd_es18xx_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + int shift, err; + + shift = 0; + if (params_channels(hw_params) == 2) + shift++; + if (snd_pcm_format_width(params_format(hw_params)) == 16) + shift++; + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) { + if ((chip->caps & ES18XX_DUPLEX_MONO) && + (chip->capture_a_substream) && + params_channels(hw_params) != 1) { + _snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS); + return -EBUSY; + } + chip->dma2_shift = shift; + } else { + chip->dma1_shift = shift; + } + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + return 0; +} + +static int snd_es18xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_es18xx_playback1_prepare(struct snd_es18xx *chip, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + snd_es18xx_rate_set(chip, substream, DAC2); + + /* Transfer Count Reload */ + count = 0x10000 - count; + snd_es18xx_mixer_write(chip, 0x74, count & 0xff); + snd_es18xx_mixer_write(chip, 0x76, count >> 8); + + /* Set format */ + snd_es18xx_mixer_bits(chip, 0x7A, 0x07, + ((runtime->channels == 1) ? 0x00 : 0x02) | + (snd_pcm_format_width(runtime->format) == 16 ? 0x01 : 0x00) | + (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x04)); + + /* Set DMA controller */ + snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + + return 0; +} + +static int snd_es18xx_playback1_trigger(struct snd_es18xx *chip, + struct snd_pcm_substream *substream, + int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (chip->active & DAC2) + return 0; + chip->active |= DAC2; + /* Start DMA */ + if (chip->dma2 >= 4) + snd_es18xx_mixer_write(chip, 0x78, 0xb3); + else + snd_es18xx_mixer_write(chip, 0x78, 0x93); +#ifdef AVOID_POPS + /* Avoid pops */ + mdelay(100); + if (chip->caps & ES18XX_PCM2) + /* Restore Audio 2 volume */ + snd_es18xx_mixer_write(chip, 0x7C, chip->audio2_vol); + else + /* Enable PCM output */ + snd_es18xx_dsp_command(chip, 0xD1); +#endif + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!(chip->active & DAC2)) + return 0; + chip->active &= ~DAC2; + /* Stop DMA */ + snd_es18xx_mixer_write(chip, 0x78, 0x00); +#ifdef AVOID_POPS + mdelay(25); + if (chip->caps & ES18XX_PCM2) + /* Set Audio 2 volume to 0 */ + snd_es18xx_mixer_write(chip, 0x7C, 0); + else + /* Disable PCM output */ + snd_es18xx_dsp_command(chip, 0xD3); +#endif + break; + default: + return -EINVAL; + } + + return 0; +} + +static int snd_es18xx_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + int shift, err; + + shift = 0; + if ((chip->caps & ES18XX_DUPLEX_MONO) && + chip->playback_a_substream && + params_channels(hw_params) != 1) { + _snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS); + return -EBUSY; + } + if (params_channels(hw_params) == 2) + shift++; + if (snd_pcm_format_width(params_format(hw_params)) == 16) + shift++; + chip->dma1_shift = shift; + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + return 0; +} + +static int snd_es18xx_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + snd_es18xx_reset_fifo(chip); + + /* Set stereo/mono */ + snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01); + + snd_es18xx_rate_set(chip, substream, ADC1); + + /* Transfer Count Reload */ + count = 0x10000 - count; + snd_es18xx_write(chip, 0xA4, count & 0xff); + snd_es18xx_write(chip, 0xA5, count >> 8); + +#ifdef AVOID_POPS + mdelay(100); +#endif + + /* Set format */ + snd_es18xx_write(chip, 0xB7, + snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71); + snd_es18xx_write(chip, 0xB7, 0x90 | + ((runtime->channels == 1) ? 0x40 : 0x08) | + (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) | + (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20)); + + /* Set DMA controller */ + snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + + return 0; +} + +static int snd_es18xx_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (chip->active & ADC1) + return 0; + chip->active |= ADC1; + /* Start DMA */ + snd_es18xx_write(chip, 0xB8, 0x0f); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!(chip->active & ADC1)) + return 0; + chip->active &= ~ADC1; + /* Stop DMA */ + snd_es18xx_write(chip, 0xB8, 0x00); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int snd_es18xx_playback2_prepare(struct snd_es18xx *chip, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + snd_es18xx_reset_fifo(chip); + + /* Set stereo/mono */ + snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01); + + snd_es18xx_rate_set(chip, substream, DAC1); + + /* Transfer Count Reload */ + count = 0x10000 - count; + snd_es18xx_write(chip, 0xA4, count & 0xff); + snd_es18xx_write(chip, 0xA5, count >> 8); + + /* Set format */ + snd_es18xx_write(chip, 0xB6, + snd_pcm_format_unsigned(runtime->format) ? 0x80 : 0x00); + snd_es18xx_write(chip, 0xB7, + snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71); + snd_es18xx_write(chip, 0xB7, 0x90 | + (runtime->channels == 1 ? 0x40 : 0x08) | + (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) | + (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20)); + + /* Set DMA controller */ + snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + + return 0; +} + +static int snd_es18xx_playback2_trigger(struct snd_es18xx *chip, + struct snd_pcm_substream *substream, + int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (chip->active & DAC1) + return 0; + chip->active |= DAC1; + /* Start DMA */ + snd_es18xx_write(chip, 0xB8, 0x05); +#ifdef AVOID_POPS + /* Avoid pops */ + mdelay(100); + /* Enable Audio 1 */ + snd_es18xx_dsp_command(chip, 0xD1); +#endif + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!(chip->active & DAC1)) + return 0; + chip->active &= ~DAC1; + /* Stop DMA */ + snd_es18xx_write(chip, 0xB8, 0x00); +#ifdef AVOID_POPS + /* Avoid pops */ + mdelay(25); + /* Disable Audio 1 */ + snd_es18xx_dsp_command(chip, 0xD3); +#endif + break; + default: + return -EINVAL; + } + + return 0; +} + +static int snd_es18xx_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) + return snd_es18xx_playback1_prepare(chip, substream); + else + return snd_es18xx_playback2_prepare(chip, substream); +} + +static int snd_es18xx_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) + return snd_es18xx_playback1_trigger(chip, substream, cmd); + else + return snd_es18xx_playback2_trigger(chip, substream, cmd); +} + +static irqreturn_t snd_es18xx_interrupt(int irq, void *dev_id) +{ + struct snd_card *card = dev_id; + struct snd_es18xx *chip = card->private_data; + unsigned char status; + + if (chip->caps & ES18XX_CONTROL) { + /* Read Interrupt status */ + status = inb(chip->ctrl_port + 6); + } else { + /* Read Interrupt status */ + status = snd_es18xx_mixer_read(chip, 0x7f) >> 4; + } +#if 0 + else { + status = 0; + if (inb(chip->port + 0x0C) & 0x01) + status |= AUDIO1_IRQ; + if (snd_es18xx_mixer_read(chip, 0x7A) & 0x80) + status |= AUDIO2_IRQ; + if ((chip->caps & ES18XX_HWV) && + snd_es18xx_mixer_read(chip, 0x64) & 0x10) + status |= HWV_IRQ; + } +#endif + + /* Audio 1 & Audio 2 */ + if (status & AUDIO2_IRQ) { + if (chip->active & DAC2) + snd_pcm_period_elapsed(chip->playback_a_substream); + /* ack interrupt */ + snd_es18xx_mixer_bits(chip, 0x7A, 0x80, 0x00); + } + if (status & AUDIO1_IRQ) { + /* ok.. capture is active */ + if (chip->active & ADC1) + snd_pcm_period_elapsed(chip->capture_a_substream); + /* ok.. playback2 is active */ + else if (chip->active & DAC1) + snd_pcm_period_elapsed(chip->playback_b_substream); + /* ack interrupt */ + inb(chip->port + 0x0E); + } + + /* MPU */ + if ((status & MPU_IRQ) && chip->rmidi) + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + + /* Hardware volume */ + if (status & HWV_IRQ) { + int split = 0; + if (chip->caps & ES18XX_HWV) { + split = snd_es18xx_mixer_read(chip, 0x64) & 0x80; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hw_switch->id); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hw_volume->id); + } + if (!split) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_switch->id); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_volume->id); + } + /* ack interrupt */ + snd_es18xx_mixer_write(chip, 0x66, 0x00); + } + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_es18xx_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + int pos; + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) { + if (!(chip->active & DAC2)) + return 0; + pos = snd_dma_pointer(chip->dma2, size); + return pos >> chip->dma2_shift; + } else { + if (!(chip->active & DAC1)) + return 0; + pos = snd_dma_pointer(chip->dma1, size); + return pos >> chip->dma1_shift; + } +} + +static snd_pcm_uframes_t snd_es18xx_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + int pos; + + if (!(chip->active & ADC1)) + return 0; + pos = snd_dma_pointer(chip->dma1, size); + return pos >> chip->dma1_shift; +} + +static const struct snd_pcm_hardware snd_es18xx_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_es18xx_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_es18xx_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) { + if ((chip->caps & ES18XX_DUPLEX_MONO) && + chip->capture_a_substream && + chip->capture_a_substream->runtime->channels != 1) + return -EAGAIN; + chip->playback_a_substream = substream; + } else if (substream->number <= 1) { + if (chip->capture_a_substream) + return -EAGAIN; + chip->playback_b_substream = substream; + } else { + snd_BUG(); + return -EINVAL; + } + substream->runtime->hw = snd_es18xx_playback; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks); + return 0; +} + +static int snd_es18xx_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + + if (chip->playback_b_substream) + return -EAGAIN; + if ((chip->caps & ES18XX_DUPLEX_MONO) && + chip->playback_a_substream && + chip->playback_a_substream->runtime->channels != 1) + return -EAGAIN; + chip->capture_a_substream = substream; + substream->runtime->hw = snd_es18xx_capture; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks); + return 0; +} + +static int snd_es18xx_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) + chip->playback_a_substream = NULL; + else + chip->playback_b_substream = NULL; + + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_es18xx_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_es18xx *chip = snd_pcm_substream_chip(substream); + + chip->capture_a_substream = NULL; + snd_pcm_lib_free_pages(substream); + return 0; +} + +/* + * MIXER part + */ + +/* Record source mux routines: + * Depending on the chipset this mux switches between 4, 5, or 8 possible inputs. + * bit table for the 4/5 source mux: + * reg 1C: + * b2 b1 b0 muxSource + * x 0 x microphone + * 0 1 x CD + * 1 1 0 line + * 1 1 1 mixer + * if it's "mixer" and it's a 5 source mux chipset then reg 7A bit 3 determines + * either the play mixer or the capture mixer. + * + * "map4Source" translates from source number to reg bit pattern + * "invMap4Source" translates from reg bit pattern to source number + */ + +static int snd_es18xx_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts5Source[5] = { + "Mic", "CD", "Line", "Master", "Mix" + }; + static const char * const texts8Source[8] = { + "Mic", "Mic Master", "CD", "AOUT", + "Mic1", "Mix", "Line", "Master" + }; + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + + switch (chip->version) { + case 0x1868: + case 0x1878: + return snd_ctl_enum_info(uinfo, 1, 4, texts5Source); + case 0x1887: + case 0x1888: + return snd_ctl_enum_info(uinfo, 1, 5, texts5Source); + case 0x1869: /* DS somewhat contradictory for 1869: could be be 5 or 8 */ + case 0x1879: + return snd_ctl_enum_info(uinfo, 1, 8, texts8Source); + default: + return -EINVAL; + } +} + +static int snd_es18xx_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + static unsigned char invMap4Source[8] = {0, 0, 1, 1, 0, 0, 2, 3}; + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + int muxSource = snd_es18xx_mixer_read(chip, 0x1c) & 0x07; + if (!(chip->version == 0x1869 || chip->version == 0x1879)) { + muxSource = invMap4Source[muxSource]; + if (muxSource==3 && + (chip->version == 0x1887 || chip->version == 0x1888) && + (snd_es18xx_mixer_read(chip, 0x7a) & 0x08) + ) + muxSource = 4; + } + ucontrol->value.enumerated.item[0] = muxSource; + return 0; +} + +static int snd_es18xx_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + static unsigned char map4Source[4] = {0, 2, 6, 7}; + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + unsigned char val = ucontrol->value.enumerated.item[0]; + unsigned char retVal = 0; + + switch (chip->version) { + /* 5 source chips */ + case 0x1887: + case 0x1888: + if (val > 4) + return -EINVAL; + if (val == 4) { + retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x08) != 0x08; + val = 3; + } else + retVal = snd_es18xx_mixer_bits(chip, 0x7a, 0x08, 0x00) != 0x00; + /* fall through */ + /* 4 source chips */ + case 0x1868: + case 0x1878: + if (val > 3) + return -EINVAL; + val = map4Source[val]; + break; + /* 8 source chips */ + case 0x1869: + case 0x1879: + if (val > 7) + return -EINVAL; + break; + default: + return -EINVAL; + } + return (snd_es18xx_mixer_bits(chip, 0x1c, 0x07, val) != val) || retVal; +} + +#define snd_es18xx_info_spatializer_enable snd_ctl_boolean_mono_info + +static int snd_es18xx_get_spatializer_enable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + unsigned char val = snd_es18xx_mixer_read(chip, 0x50); + ucontrol->value.integer.value[0] = !!(val & 8); + return 0; +} + +static int snd_es18xx_put_spatializer_enable(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + unsigned char oval, nval; + int change; + nval = ucontrol->value.integer.value[0] ? 0x0c : 0x04; + oval = snd_es18xx_mixer_read(chip, 0x50) & 0x0c; + change = nval != oval; + if (change) { + snd_es18xx_mixer_write(chip, 0x50, nval & ~0x04); + snd_es18xx_mixer_write(chip, 0x50, nval); + } + return change; +} + +static int snd_es18xx_info_hw_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 63; + return 0; +} + +static int snd_es18xx_get_hw_volume(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = snd_es18xx_mixer_read(chip, 0x61) & 0x3f; + ucontrol->value.integer.value[1] = snd_es18xx_mixer_read(chip, 0x63) & 0x3f; + return 0; +} + +#define snd_es18xx_info_hw_switch snd_ctl_boolean_stereo_info + +static int snd_es18xx_get_hw_switch(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = !(snd_es18xx_mixer_read(chip, 0x61) & 0x40); + ucontrol->value.integer.value[1] = !(snd_es18xx_mixer_read(chip, 0x63) & 0x40); + return 0; +} + +static void snd_es18xx_hwv_free(struct snd_kcontrol *kcontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + chip->master_volume = NULL; + chip->master_switch = NULL; + chip->hw_volume = NULL; + chip->hw_switch = NULL; +} + +static int snd_es18xx_reg_bits(struct snd_es18xx *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + if (reg < 0xa0) + return snd_es18xx_mixer_bits(chip, reg, mask, val); + else + return snd_es18xx_bits(chip, reg, mask, val); +} + +static int snd_es18xx_reg_read(struct snd_es18xx *chip, unsigned char reg) +{ + if (reg < 0xa0) + return snd_es18xx_mixer_read(chip, reg); + else + return snd_es18xx_read(chip, reg); +} + +#define ES18XX_SINGLE(xname, xindex, reg, shift, mask, flags) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_es18xx_info_single, \ + .get = snd_es18xx_get_single, .put = snd_es18xx_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (flags << 24) } + +#define ES18XX_FL_INVERT (1 << 0) +#define ES18XX_FL_PMPORT (1 << 1) + +static int snd_es18xx_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_es18xx_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & ES18XX_FL_INVERT; + int pm_port = (kcontrol->private_value >> 24) & ES18XX_FL_PMPORT; + int val; + + if (pm_port) + val = inb(chip->port + ES18XX_PM); + else + val = snd_es18xx_reg_read(chip, reg); + ucontrol->value.integer.value[0] = (val >> shift) & mask; + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_es18xx_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & ES18XX_FL_INVERT; + int pm_port = (kcontrol->private_value >> 24) & ES18XX_FL_PMPORT; + unsigned char val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + mask <<= shift; + val <<= shift; + if (pm_port) { + unsigned char cur = inb(chip->port + ES18XX_PM); + + if ((cur & mask) == val) + return 0; + outb((cur & ~mask) | val, chip->port + ES18XX_PM); + return 1; + } + + return snd_es18xx_reg_bits(chip, reg, mask, val) != val; +} + +#define ES18XX_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_es18xx_info_double, \ + .get = snd_es18xx_get_double, .put = snd_es18xx_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_es18xx_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_es18xx_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + unsigned char left, right; + + left = snd_es18xx_reg_read(chip, left_reg); + if (left_reg != right_reg) + right = snd_es18xx_reg_read(chip, right_reg); + else + right = left; + ucontrol->value.integer.value[0] = (left >> shift_left) & mask; + ucontrol->value.integer.value[1] = (right >> shift_right) & mask; + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_es18xx_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_es18xx *chip = snd_kcontrol_chip(kcontrol); + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned char val1, val2, mask1, mask2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + mask1 = mask << shift_left; + mask2 = mask << shift_right; + if (left_reg != right_reg) { + change = 0; + if (snd_es18xx_reg_bits(chip, left_reg, mask1, val1) != val1) + change = 1; + if (snd_es18xx_reg_bits(chip, right_reg, mask2, val2) != val2) + change = 1; + } else { + change = (snd_es18xx_reg_bits(chip, left_reg, mask1 | mask2, + val1 | val2) != (val1 | val2)); + } + return change; +} + +/* Mixer controls + * These arrays contain setup data for mixer controls. + * + * The controls that are universal to all chipsets are fully initialized + * here. + */ +static struct snd_kcontrol_new snd_es18xx_base_controls[] = { +ES18XX_DOUBLE("Master Playback Volume", 0, 0x60, 0x62, 0, 0, 63, 0), +ES18XX_DOUBLE("Master Playback Switch", 0, 0x60, 0x62, 6, 6, 1, 1), +ES18XX_DOUBLE("Line Playback Volume", 0, 0x3e, 0x3e, 4, 0, 15, 0), +ES18XX_DOUBLE("CD Playback Volume", 0, 0x38, 0x38, 4, 0, 15, 0), +ES18XX_DOUBLE("FM Playback Volume", 0, 0x36, 0x36, 4, 0, 15, 0), +ES18XX_DOUBLE("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0), +ES18XX_DOUBLE("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0), +ES18XX_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0), +ES18XX_DOUBLE("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_es18xx_info_mux, + .get = snd_es18xx_get_mux, + .put = snd_es18xx_put_mux, +} +}; + +static struct snd_kcontrol_new snd_es18xx_recmix_controls[] = { +ES18XX_DOUBLE("PCM Capture Volume", 0, 0x69, 0x69, 4, 0, 15, 0), +ES18XX_DOUBLE("Mic Capture Volume", 0, 0x68, 0x68, 4, 0, 15, 0), +ES18XX_DOUBLE("Line Capture Volume", 0, 0x6e, 0x6e, 4, 0, 15, 0), +ES18XX_DOUBLE("FM Capture Volume", 0, 0x6b, 0x6b, 4, 0, 15, 0), +ES18XX_DOUBLE("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0), +ES18XX_DOUBLE("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0) +}; + +/* + * The chipset specific mixer controls + */ +static struct snd_kcontrol_new snd_es18xx_opt_speaker = + ES18XX_SINGLE("Beep Playback Volume", 0, 0x3c, 0, 7, 0); + +static struct snd_kcontrol_new snd_es18xx_opt_1869[] = { +ES18XX_SINGLE("Capture Switch", 0, 0x1c, 4, 1, ES18XX_FL_INVERT), +ES18XX_SINGLE("Video Playback Switch", 0, 0x7f, 0, 1, 0), +ES18XX_DOUBLE("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0), +ES18XX_DOUBLE("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0) +}; + +static struct snd_kcontrol_new snd_es18xx_opt_1878 = + ES18XX_DOUBLE("Video Playback Volume", 0, 0x68, 0x68, 4, 0, 15, 0); + +static struct snd_kcontrol_new snd_es18xx_opt_1879[] = { +ES18XX_SINGLE("Video Playback Switch", 0, 0x71, 6, 1, 0), +ES18XX_DOUBLE("Video Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0), +ES18XX_DOUBLE("Video Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0) +}; + +static struct snd_kcontrol_new snd_es18xx_pcm1_controls[] = { +ES18XX_DOUBLE("PCM Playback Volume", 0, 0x14, 0x14, 4, 0, 15, 0), +}; + +static struct snd_kcontrol_new snd_es18xx_pcm2_controls[] = { +ES18XX_DOUBLE("PCM Playback Volume", 0, 0x7c, 0x7c, 4, 0, 15, 0), +ES18XX_DOUBLE("PCM Playback Volume", 1, 0x14, 0x14, 4, 0, 15, 0) +}; + +static struct snd_kcontrol_new snd_es18xx_spatializer_controls[] = { +ES18XX_SINGLE("3D Control - Level", 0, 0x52, 0, 63, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Switch", + .info = snd_es18xx_info_spatializer_enable, + .get = snd_es18xx_get_spatializer_enable, + .put = snd_es18xx_put_spatializer_enable, +} +}; + +static struct snd_kcontrol_new snd_es18xx_micpre1_control = +ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0xa9, 2, 1, 0); + +static struct snd_kcontrol_new snd_es18xx_micpre2_control = +ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0); + +static struct snd_kcontrol_new snd_es18xx_hw_volume_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Hardware Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_es18xx_info_hw_volume, + .get = snd_es18xx_get_hw_volume, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Hardware Master Playback Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_es18xx_info_hw_switch, + .get = snd_es18xx_get_hw_switch, +}, +ES18XX_SINGLE("Hardware Master Volume Split", 0, 0x64, 7, 1, 0), +}; + +static struct snd_kcontrol_new snd_es18xx_opt_gpo_2bit[] = { +ES18XX_SINGLE("GPO0 Switch", 0, ES18XX_PM, 0, 1, ES18XX_FL_PMPORT), +ES18XX_SINGLE("GPO1 Switch", 0, ES18XX_PM, 1, 1, ES18XX_FL_PMPORT), +}; + +static int snd_es18xx_config_read(struct snd_es18xx *chip, unsigned char reg) +{ + int data; + + outb(reg, chip->ctrl_port); + data = inb(chip->ctrl_port + 1); + return data; +} + +static void snd_es18xx_config_write(struct snd_es18xx *chip, + unsigned char reg, unsigned char data) +{ + /* No need for spinlocks, this function is used only in + otherwise protected init code */ + outb(reg, chip->ctrl_port); + outb(data, chip->ctrl_port + 1); +#ifdef REG_DEBUG + snd_printk(KERN_DEBUG "Config reg %02x set to %02x\n", reg, data); +#endif +} + +static int snd_es18xx_initialize(struct snd_es18xx *chip, + unsigned long mpu_port, + unsigned long fm_port) +{ + int mask = 0; + + /* enable extended mode */ + snd_es18xx_dsp_command(chip, 0xC6); + /* Reset mixer registers */ + snd_es18xx_mixer_write(chip, 0x00, 0x00); + + /* Audio 1 DMA demand mode (4 bytes/request) */ + snd_es18xx_write(chip, 0xB9, 2); + if (chip->caps & ES18XX_CONTROL) { + /* Hardware volume IRQ */ + snd_es18xx_config_write(chip, 0x27, chip->irq); + if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) { + /* FM I/O */ + snd_es18xx_config_write(chip, 0x62, fm_port >> 8); + snd_es18xx_config_write(chip, 0x63, fm_port & 0xff); + } + if (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) { + /* MPU-401 I/O */ + snd_es18xx_config_write(chip, 0x64, mpu_port >> 8); + snd_es18xx_config_write(chip, 0x65, mpu_port & 0xff); + /* MPU-401 IRQ */ + snd_es18xx_config_write(chip, 0x28, chip->irq); + } + /* Audio1 IRQ */ + snd_es18xx_config_write(chip, 0x70, chip->irq); + /* Audio2 IRQ */ + snd_es18xx_config_write(chip, 0x72, chip->irq); + /* Audio1 DMA */ + snd_es18xx_config_write(chip, 0x74, chip->dma1); + /* Audio2 DMA */ + snd_es18xx_config_write(chip, 0x75, chip->dma2); + + /* Enable Audio 1 IRQ */ + snd_es18xx_write(chip, 0xB1, 0x50); + /* Enable Audio 2 IRQ */ + snd_es18xx_mixer_write(chip, 0x7A, 0x40); + /* Enable Audio 1 DMA */ + snd_es18xx_write(chip, 0xB2, 0x50); + /* Enable MPU and hardware volume interrupt */ + snd_es18xx_mixer_write(chip, 0x64, 0x42); + /* Enable ESS wavetable input */ + snd_es18xx_mixer_bits(chip, 0x48, 0x10, 0x10); + } + else { + int irqmask, dma1mask, dma2mask; + switch (chip->irq) { + case 2: + case 9: + irqmask = 0; + break; + case 5: + irqmask = 1; + break; + case 7: + irqmask = 2; + break; + case 10: + irqmask = 3; + break; + default: + snd_printk(KERN_ERR "invalid irq %d\n", chip->irq); + return -ENODEV; + } + switch (chip->dma1) { + case 0: + dma1mask = 1; + break; + case 1: + dma1mask = 2; + break; + case 3: + dma1mask = 3; + break; + default: + snd_printk(KERN_ERR "invalid dma1 %d\n", chip->dma1); + return -ENODEV; + } + switch (chip->dma2) { + case 0: + dma2mask = 0; + break; + case 1: + dma2mask = 1; + break; + case 3: + dma2mask = 2; + break; + case 5: + dma2mask = 3; + break; + default: + snd_printk(KERN_ERR "invalid dma2 %d\n", chip->dma2); + return -ENODEV; + } + + /* Enable and set Audio 1 IRQ */ + snd_es18xx_write(chip, 0xB1, 0x50 | (irqmask << 2)); + /* Enable and set Audio 1 DMA */ + snd_es18xx_write(chip, 0xB2, 0x50 | (dma1mask << 2)); + /* Set Audio 2 DMA */ + snd_es18xx_mixer_bits(chip, 0x7d, 0x07, 0x04 | dma2mask); + /* Enable Audio 2 IRQ and DMA + Set capture mixer input */ + snd_es18xx_mixer_write(chip, 0x7A, 0x68); + /* Enable and set hardware volume interrupt */ + snd_es18xx_mixer_write(chip, 0x64, 0x06); + if (mpu_port > 0 && mpu_port != SNDRV_AUTO_PORT) { + /* MPU401 share irq with audio + Joystick enabled + FM enabled */ + snd_es18xx_mixer_write(chip, 0x40, + 0x43 | (mpu_port & 0xf0) >> 1); + } + snd_es18xx_mixer_write(chip, 0x7f, ((irqmask + 1) << 1) | 0x01); + } + if (chip->caps & ES18XX_NEW_RATE) { + /* Change behaviour of register A1 + 4x oversampling + 2nd channel DAC asynchronous */ + snd_es18xx_mixer_write(chip, 0x71, 0x32); + } + if (!(chip->caps & ES18XX_PCM2)) { + /* Enable DMA FIFO */ + snd_es18xx_write(chip, 0xB7, 0x80); + } + if (chip->caps & ES18XX_SPATIALIZER) { + /* Set spatializer parameters to recommended values */ + snd_es18xx_mixer_write(chip, 0x54, 0x8f); + snd_es18xx_mixer_write(chip, 0x56, 0x95); + snd_es18xx_mixer_write(chip, 0x58, 0x94); + snd_es18xx_mixer_write(chip, 0x5a, 0x80); + } + /* Flip the "enable I2S" bits for those chipsets that need it */ + switch (chip->version) { + case 0x1879: + //Leaving I2S enabled on the 1879 screws up the PCM playback (rate effected somehow) + //so a Switch control has been added to toggle this 0x71 bit on/off: + //snd_es18xx_mixer_bits(chip, 0x71, 0x40, 0x40); + /* Note: we fall through on purpose here. */ + case 0x1878: + snd_es18xx_config_write(chip, 0x29, snd_es18xx_config_read(chip, 0x29) | 0x40); + break; + } + /* Mute input source */ + if (chip->caps & ES18XX_MUTEREC) + mask = 0x10; + if (chip->caps & ES18XX_RECMIX) + snd_es18xx_mixer_write(chip, 0x1c, 0x05 | mask); + else { + snd_es18xx_mixer_write(chip, 0x1c, 0x00 | mask); + snd_es18xx_write(chip, 0xb4, 0x00); + } +#ifndef AVOID_POPS + /* Enable PCM output */ + snd_es18xx_dsp_command(chip, 0xD1); +#endif + + return 0; +} + +static int snd_es18xx_identify(struct snd_es18xx *chip) +{ + int hi,lo; + + /* reset */ + if (snd_es18xx_reset(chip) < 0) { + snd_printk(KERN_ERR "reset at 0x%lx failed!!!\n", chip->port); + return -ENODEV; + } + + snd_es18xx_dsp_command(chip, 0xe7); + hi = snd_es18xx_dsp_get_byte(chip); + if (hi < 0) { + return hi; + } + lo = snd_es18xx_dsp_get_byte(chip); + if ((lo & 0xf0) != 0x80) { + return -ENODEV; + } + if (hi == 0x48) { + chip->version = 0x488; + return 0; + } + if (hi != 0x68) { + return -ENODEV; + } + if ((lo & 0x0f) < 8) { + chip->version = 0x688; + return 0; + } + + outb(0x40, chip->port + 0x04); + udelay(10); + hi = inb(chip->port + 0x05); + udelay(10); + lo = inb(chip->port + 0x05); + if (hi != lo) { + chip->version = hi << 8 | lo; + chip->ctrl_port = inb(chip->port + 0x05) << 8; + udelay(10); + chip->ctrl_port += inb(chip->port + 0x05); + + if ((chip->res_ctrl_port = request_region(chip->ctrl_port, 8, "ES18xx - CTRL")) == NULL) { + snd_printk(KERN_ERR PFX "unable go grab port 0x%lx\n", chip->ctrl_port); + return -EBUSY; + } + + return 0; + } + + /* If has Hardware volume */ + if (snd_es18xx_mixer_writable(chip, 0x64, 0x04)) { + /* If has Audio2 */ + if (snd_es18xx_mixer_writable(chip, 0x70, 0x7f)) { + /* If has volume count */ + if (snd_es18xx_mixer_writable(chip, 0x64, 0x20)) { + chip->version = 0x1887; + } else { + chip->version = 0x1888; + } + } else { + chip->version = 0x1788; + } + } + else + chip->version = 0x1688; + return 0; +} + +static int snd_es18xx_probe(struct snd_es18xx *chip, + unsigned long mpu_port, + unsigned long fm_port) +{ + if (snd_es18xx_identify(chip) < 0) { + snd_printk(KERN_ERR PFX "[0x%lx] ESS chip not found\n", chip->port); + return -ENODEV; + } + + switch (chip->version) { + case 0x1868: + chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_CONTROL | ES18XX_GPO_2BIT; + break; + case 0x1869: + chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_MONO | ES18XX_MUTEREC | ES18XX_CONTROL | ES18XX_HWV | ES18XX_GPO_2BIT; + break; + case 0x1878: + chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_I2S | ES18XX_CONTROL; + break; + case 0x1879: + chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV; + break; + case 0x1887: + case 0x1888: + chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME | ES18XX_GPO_2BIT; + break; + default: + snd_printk(KERN_ERR "[0x%lx] unsupported chip ES%x\n", + chip->port, chip->version); + return -ENODEV; + } + + snd_printd("[0x%lx] ESS%x chip found\n", chip->port, chip->version); + + if (chip->dma1 == chip->dma2) + chip->caps &= ~(ES18XX_PCM2 | ES18XX_DUPLEX_SAME); + + return snd_es18xx_initialize(chip, mpu_port, fm_port); +} + +static const struct snd_pcm_ops snd_es18xx_playback_ops = { + .open = snd_es18xx_playback_open, + .close = snd_es18xx_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_es18xx_playback_hw_params, + .hw_free = snd_es18xx_pcm_hw_free, + .prepare = snd_es18xx_playback_prepare, + .trigger = snd_es18xx_playback_trigger, + .pointer = snd_es18xx_playback_pointer, +}; + +static const struct snd_pcm_ops snd_es18xx_capture_ops = { + .open = snd_es18xx_capture_open, + .close = snd_es18xx_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_es18xx_capture_hw_params, + .hw_free = snd_es18xx_pcm_hw_free, + .prepare = snd_es18xx_capture_prepare, + .trigger = snd_es18xx_capture_trigger, + .pointer = snd_es18xx_capture_pointer, +}; + +static int snd_es18xx_pcm(struct snd_card *card, int device) +{ + struct snd_es18xx *chip = card->private_data; + struct snd_pcm *pcm; + char str[16]; + int err; + + sprintf(str, "ES%x", chip->version); + if (chip->caps & ES18XX_PCM2) + err = snd_pcm_new(card, str, device, 2, 1, &pcm); + else + err = snd_pcm_new(card, str, device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es18xx_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es18xx_capture_ops); + + /* global setup */ + pcm->private_data = chip; + pcm->info_flags = 0; + if (chip->caps & ES18XX_DUPLEX_SAME) + pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX; + if (! (chip->caps & ES18XX_PCM2)) + pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX; + sprintf(pcm->name, "ESS AudioDrive ES%x", chip->version); + chip->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, + chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024); + return 0; +} + +/* Power Management support functions */ +#ifdef CONFIG_PM +static int snd_es18xx_suspend(struct snd_card *card, pm_message_t state) +{ + struct snd_es18xx *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + snd_pcm_suspend_all(chip->pcm); + + /* power down */ + chip->pm_reg = (unsigned char)snd_es18xx_read(chip, ES18XX_PM); + chip->pm_reg |= (ES18XX_PM_FM | ES18XX_PM_SUS); + snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg); + snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_SUS); + + return 0; +} + +static int snd_es18xx_resume(struct snd_card *card) +{ + struct snd_es18xx *chip = card->private_data; + + /* restore PM register, we won't wake till (not 0x07) i/o activity though */ + snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_FM); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif /* CONFIG_PM */ + +static int snd_es18xx_free(struct snd_card *card) +{ + struct snd_es18xx *chip = card->private_data; + + release_and_free_resource(chip->res_port); + release_and_free_resource(chip->res_ctrl_port); + release_and_free_resource(chip->res_mpu_port); + if (chip->irq >= 0) + free_irq(chip->irq, (void *) card); + if (chip->dma1 >= 0) { + disable_dma(chip->dma1); + free_dma(chip->dma1); + } + if (chip->dma2 >= 0 && chip->dma1 != chip->dma2) { + disable_dma(chip->dma2); + free_dma(chip->dma2); + } + return 0; +} + +static int snd_es18xx_dev_free(struct snd_device *device) +{ + return snd_es18xx_free(device->card); +} + +static int snd_es18xx_new_device(struct snd_card *card, + unsigned long port, + unsigned long mpu_port, + unsigned long fm_port, + int irq, int dma1, int dma2) +{ + struct snd_es18xx *chip = card->private_data; + static struct snd_device_ops ops = { + .dev_free = snd_es18xx_dev_free, + }; + int err; + + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->mixer_lock); + chip->port = port; + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + chip->audio2_vol = 0x00; + chip->active = 0; + + chip->res_port = request_region(port, 16, "ES18xx"); + if (chip->res_port == NULL) { + snd_es18xx_free(card); + snd_printk(KERN_ERR PFX "unable to grap ports 0x%lx-0x%lx\n", port, port + 16 - 1); + return -EBUSY; + } + + if (request_irq(irq, snd_es18xx_interrupt, 0, "ES18xx", + (void *) card)) { + snd_es18xx_free(card); + snd_printk(KERN_ERR PFX "unable to grap IRQ %d\n", irq); + return -EBUSY; + } + chip->irq = irq; + + if (request_dma(dma1, "ES18xx DMA 1")) { + snd_es18xx_free(card); + snd_printk(KERN_ERR PFX "unable to grap DMA1 %d\n", dma1); + return -EBUSY; + } + chip->dma1 = dma1; + + if (dma2 != dma1 && request_dma(dma2, "ES18xx DMA 2")) { + snd_es18xx_free(card); + snd_printk(KERN_ERR PFX "unable to grap DMA2 %d\n", dma2); + return -EBUSY; + } + chip->dma2 = dma2; + + if (snd_es18xx_probe(chip, mpu_port, fm_port) < 0) { + snd_es18xx_free(card); + return -ENODEV; + } + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + snd_es18xx_free(card); + return err; + } + return 0; +} + +static int snd_es18xx_mixer(struct snd_card *card) +{ + struct snd_es18xx *chip = card->private_data; + int err; + unsigned int idx; + + strcpy(card->mixername, chip->pcm->name); + + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_base_controls); idx++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(&snd_es18xx_base_controls[idx], chip); + if (chip->caps & ES18XX_HWV) { + switch (idx) { + case 0: + chip->master_volume = kctl; + kctl->private_free = snd_es18xx_hwv_free; + break; + case 1: + chip->master_switch = kctl; + kctl->private_free = snd_es18xx_hwv_free; + break; + } + } + if ((err = snd_ctl_add(card, kctl)) < 0) + return err; + } + if (chip->caps & ES18XX_PCM2) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm2_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm2_controls[idx], chip))) < 0) + return err; + } + } else { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm1_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm1_controls[idx], chip))) < 0) + return err; + } + } + + if (chip->caps & ES18XX_RECMIX) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_recmix_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_recmix_controls[idx], chip))) < 0) + return err; + } + } + switch (chip->version) { + default: + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre1_control, chip))) < 0) + return err; + break; + case 0x1869: + case 0x1879: + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre2_control, chip))) < 0) + return err; + break; + } + if (chip->caps & ES18XX_SPATIALIZER) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_spatializer_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_spatializer_controls[idx], chip))) < 0) + return err; + } + } + if (chip->caps & ES18XX_HWV) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_hw_volume_controls); idx++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(&snd_es18xx_hw_volume_controls[idx], chip); + if (idx == 0) + chip->hw_volume = kctl; + else + chip->hw_switch = kctl; + kctl->private_free = snd_es18xx_hwv_free; + if ((err = snd_ctl_add(card, kctl)) < 0) + return err; + + } + } + /* finish initializing other chipset specific controls + */ + if (chip->version != 0x1868) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_speaker, + chip)); + if (err < 0) + return err; + } + if (chip->version == 0x1869) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1869); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_es18xx_opt_1869[idx], + chip)); + if (err < 0) + return err; + } + } else if (chip->version == 0x1878) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_opt_1878, + chip)); + if (err < 0) + return err; + } else if (chip->version == 0x1879) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_1879); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_es18xx_opt_1879[idx], + chip)); + if (err < 0) + return err; + } + } + if (chip->caps & ES18XX_GPO_2BIT) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_opt_gpo_2bit); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_es18xx_opt_gpo_2bit[idx], + chip)); + if (err < 0) + return err; + } + } + return 0; +} + + +/* Card level */ + +MODULE_AUTHOR("Christian Fischbach , Abramo Bagnara "); +MODULE_DESCRIPTION("ESS ES18xx AudioDrive"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ESS,ES1868 PnP AudioDrive}," + "{ESS,ES1869 PnP AudioDrive}," + "{ESS,ES1878 PnP AudioDrive}," + "{ESS,ES1879 PnP AudioDrive}," + "{ESS,ES1887 PnP AudioDrive}," + "{ESS,ES1888 PnP AudioDrive}," + "{ESS,ES1887 AudioDrive}," + "{ESS,ES1888 AudioDrive}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260,0x280 */ +#ifndef CONFIG_PNP +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1}; +#else +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +#endif +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ES18xx soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ES18xx soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ES18xx soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for ES18xx driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ES18xx driver."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for ES18xx driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for ES18xx driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA 1 # for ES18xx driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA 2 # for ES18xx driver."); + +#ifdef CONFIG_PNP +static int isa_registered; +static int pnp_registered; +static int pnpc_registered; + +static const struct pnp_device_id snd_audiodrive_pnpbiosids[] = { + { .id = "ESS1869" }, + { .id = "ESS1879" }, + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp, snd_audiodrive_pnpbiosids); + +/* PnP main device initialization */ +static int snd_audiodrive_pnp_init_main(int dev, struct pnp_dev *pdev) +{ + if (pnp_activate_dev(pdev) < 0) { + snd_printk(KERN_ERR PFX "PnP configure failure (out of resources?)\n"); + return -EBUSY; + } + /* ok. hack using Vendor-Defined Card-Level registers */ + /* skip csn and logdev initialization - already done in isapnp_configure */ + if (pnp_device_is_isapnp(pdev)) { + isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev)); + isapnp_write_byte(0x27, pnp_irq(pdev, 0)); /* Hardware Volume IRQ Number */ + if (mpu_port[dev] != SNDRV_AUTO_PORT) + isapnp_write_byte(0x28, pnp_irq(pdev, 0)); /* MPU-401 IRQ Number */ + isapnp_write_byte(0x72, pnp_irq(pdev, 0)); /* second IRQ */ + isapnp_cfg_end(); + } + port[dev] = pnp_port_start(pdev, 0); + fm_port[dev] = pnp_port_start(pdev, 1); + mpu_port[dev] = pnp_port_start(pdev, 2); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("PnP ES18xx: port=0x%lx, fm port=0x%lx, mpu port=0x%lx\n", port[dev], fm_port[dev], mpu_port[dev]); + snd_printdd("PnP ES18xx: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]); + return 0; +} + +static int snd_audiodrive_pnp(int dev, struct snd_es18xx *chip, + struct pnp_dev *pdev) +{ + chip->dev = pdev; + if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0) + return -EBUSY; + return 0; +} + +static const struct pnp_card_device_id snd_audiodrive_pnpids[] = { + /* ESS 1868 (integrated on Compaq dual P-Pro motherboard and Genius 18PnP 3D) */ + { .id = "ESS1868", .devs = { { "ESS1868" }, { "ESS0000" } } }, + /* ESS 1868 (integrated on Maxisound Cards) */ + { .id = "ESS1868", .devs = { { "ESS8601" }, { "ESS8600" } } }, + /* ESS 1868 (integrated on Maxisound Cards) */ + { .id = "ESS1868", .devs = { { "ESS8611" }, { "ESS8610" } } }, + /* ESS ES1869 Plug and Play AudioDrive */ + { .id = "ESS0003", .devs = { { "ESS1869" }, { "ESS0006" } } }, + /* ESS 1869 */ + { .id = "ESS1869", .devs = { { "ESS1869" }, { "ESS0006" } } }, + /* ESS 1878 */ + { .id = "ESS1878", .devs = { { "ESS1878" }, { "ESS0004" } } }, + /* ESS 1879 */ + { .id = "ESS1879", .devs = { { "ESS1879" }, { "ESS0009" } } }, + /* --- */ + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_audiodrive_pnpids); + +static int snd_audiodrive_pnpc(int dev, struct snd_es18xx *chip, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + chip->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (chip->dev == NULL) + return -EBUSY; + + chip->devc = pnp_request_card_device(card, id->devs[1].id, NULL); + if (chip->devc == NULL) + return -EBUSY; + + /* Control port initialization */ + if (pnp_activate_dev(chip->devc) < 0) { + snd_printk(KERN_ERR PFX "PnP control configure failure (out of resources?)\n"); + return -EAGAIN; + } + snd_printdd("pnp: port=0x%llx\n", + (unsigned long long)pnp_port_start(chip->devc, 0)); + if (snd_audiodrive_pnp_init_main(dev, chip->dev) < 0) + return -EBUSY; + + return 0; +} +#endif /* CONFIG_PNP */ + +#ifdef CONFIG_PNP +#define is_isapnp_selected(dev) isapnp[dev] +#else +#define is_isapnp_selected(dev) 0 +#endif + +static int snd_es18xx_card_new(struct device *pdev, int dev, + struct snd_card **cardp) +{ + return snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_es18xx), cardp); +} + +static int snd_audiodrive_probe(struct snd_card *card, int dev) +{ + struct snd_es18xx *chip = card->private_data; + struct snd_opl3 *opl3; + int err; + + err = snd_es18xx_new_device(card, + port[dev], mpu_port[dev], fm_port[dev], + irq[dev], dma1[dev], dma2[dev]); + if (err < 0) + return err; + + sprintf(card->driver, "ES%x", chip->version); + + sprintf(card->shortname, "ESS AudioDrive ES%x", chip->version); + if (dma1[dev] != dma2[dev]) + sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d", + card->shortname, + chip->port, + irq[dev], dma1[dev], dma2[dev]); + else + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + card->shortname, + chip->port, + irq[dev], dma1[dev]); + + err = snd_es18xx_pcm(card, 0); + if (err < 0) + return err; + + err = snd_es18xx_mixer(card); + if (err < 0) + return err; + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2, + OPL3_HW_OPL3, 0, &opl3) < 0) { + snd_printk(KERN_WARNING PFX + "opl3 not detected at 0x%lx\n", + fm_port[dev]); + } else { + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + return err; + } + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES18XX, + mpu_port[dev], MPU401_INFO_IRQ_HOOK, + -1, &chip->rmidi); + if (err < 0) + return err; + } + + return snd_card_register(card); +} + +static int snd_es18xx_isa_match(struct device *pdev, unsigned int dev) +{ + return enable[dev] && !is_isapnp_selected(dev); +} + +static int snd_es18xx_isa_probe1(int dev, struct device *devptr) +{ + struct snd_card *card; + int err; + + err = snd_es18xx_card_new(devptr, dev, &card); + if (err < 0) + return err; + if ((err = snd_audiodrive_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + dev_set_drvdata(devptr, card); + return 0; +} + +static int snd_es18xx_isa_probe(struct device *pdev, unsigned int dev) +{ + int err; + static int possible_irqs[] = {5, 9, 10, 7, 11, 12, -1}; + static int possible_dmas[] = {1, 0, 3, 5, -1}; + + if (irq[dev] == SNDRV_AUTO_IRQ) { + if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma1[dev] == SNDRV_AUTO_DMA) { + if ((dma1[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (dma2[dev] == SNDRV_AUTO_DMA) { + if ((dma2[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if (port[dev] != SNDRV_AUTO_PORT) { + return snd_es18xx_isa_probe1(dev, pdev); + } else { + static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280}; + int i; + for (i = 0; i < ARRAY_SIZE(possible_ports); i++) { + port[dev] = possible_ports[i]; + err = snd_es18xx_isa_probe1(dev, pdev); + if (! err) + return 0; + } + return err; + } +} + +static int snd_es18xx_isa_remove(struct device *devptr, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_es18xx_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_es18xx_suspend(dev_get_drvdata(dev), state); +} + +static int snd_es18xx_isa_resume(struct device *dev, unsigned int n) +{ + return snd_es18xx_resume(dev_get_drvdata(dev)); +} +#endif + +#define DEV_NAME "es18xx" + +static struct isa_driver snd_es18xx_isa_driver = { + .match = snd_es18xx_isa_match, + .probe = snd_es18xx_isa_probe, + .remove = snd_es18xx_isa_remove, +#ifdef CONFIG_PM + .suspend = snd_es18xx_isa_suspend, + .resume = snd_es18xx_isa_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + + +#ifdef CONFIG_PNP +static int snd_audiodrive_pnp_detect(struct pnp_dev *pdev, + const struct pnp_device_id *id) +{ + static int dev; + int err; + struct snd_card *card; + + if (pnp_device_is_isapnp(pdev)) + return -ENOENT; /* we have another procedure - card */ + for (; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + err = snd_es18xx_card_new(&pdev->dev, dev, &card); + if (err < 0) + return err; + if ((err = snd_audiodrive_pnp(dev, card->private_data, pdev)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_audiodrive_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + pnp_set_drvdata(pdev, card); + dev++; + return 0; +} + +static void snd_audiodrive_pnp_remove(struct pnp_dev *pdev) +{ + snd_card_free(pnp_get_drvdata(pdev)); +} + +#ifdef CONFIG_PM +static int snd_audiodrive_pnp_suspend(struct pnp_dev *pdev, pm_message_t state) +{ + return snd_es18xx_suspend(pnp_get_drvdata(pdev), state); +} +static int snd_audiodrive_pnp_resume(struct pnp_dev *pdev) +{ + return snd_es18xx_resume(pnp_get_drvdata(pdev)); +} +#endif + +static struct pnp_driver es18xx_pnp_driver = { + .name = "es18xx-pnpbios", + .id_table = snd_audiodrive_pnpbiosids, + .probe = snd_audiodrive_pnp_detect, + .remove = snd_audiodrive_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_audiodrive_pnp_suspend, + .resume = snd_audiodrive_pnp_resume, +#endif +}; + +static int snd_audiodrive_pnpc_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int dev; + struct snd_card *card; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + res = snd_es18xx_card_new(&pcard->card->dev, dev, &card); + if (res < 0) + return res; + + if ((res = snd_audiodrive_pnpc(dev, card->private_data, pcard, pid)) < 0) { + snd_card_free(card); + return res; + } + if ((res = snd_audiodrive_probe(card, dev)) < 0) { + snd_card_free(card); + return res; + } + + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; +} + +static void snd_audiodrive_pnpc_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_audiodrive_pnpc_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_es18xx_suspend(pnp_get_card_drvdata(pcard), state); +} + +static int snd_audiodrive_pnpc_resume(struct pnp_card_link *pcard) +{ + return snd_es18xx_resume(pnp_get_card_drvdata(pcard)); +} + +#endif + +static struct pnp_card_driver es18xx_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "es18xx", + .id_table = snd_audiodrive_pnpids, + .probe = snd_audiodrive_pnpc_detect, + .remove = snd_audiodrive_pnpc_remove, +#ifdef CONFIG_PM + .suspend = snd_audiodrive_pnpc_suspend, + .resume = snd_audiodrive_pnpc_resume, +#endif +}; +#endif /* CONFIG_PNP */ + +static int __init alsa_card_es18xx_init(void) +{ + int err; + + err = isa_register_driver(&snd_es18xx_isa_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_driver(&es18xx_pnp_driver); + if (!err) + pnp_registered = 1; + + err = pnp_register_card_driver(&es18xx_pnpc_driver); + if (!err) + pnpc_registered = 1; + + if (isa_registered || pnp_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_es18xx_exit(void) +{ +#ifdef CONFIG_PNP + if (pnpc_registered) + pnp_unregister_card_driver(&es18xx_pnpc_driver); + if (pnp_registered) + pnp_unregister_driver(&es18xx_pnp_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_es18xx_isa_driver); +} + +module_init(alsa_card_es18xx_init) +module_exit(alsa_card_es18xx_exit) diff --git a/sound/isa/galaxy/Makefile b/sound/isa/galaxy/Makefile new file mode 100644 index 000000000..e307066d4 --- /dev/null +++ b/sound/isa/galaxy/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-azt1605-objs := azt1605.o +snd-azt2316-objs := azt2316.o + +obj-$(CONFIG_SND_AZT1605) += snd-azt1605.o +obj-$(CONFIG_SND_AZT2316) += snd-azt2316.o diff --git a/sound/isa/galaxy/azt1605.c b/sound/isa/galaxy/azt1605.c new file mode 100644 index 000000000..9a97643cb --- /dev/null +++ b/sound/isa/galaxy/azt1605.c @@ -0,0 +1,91 @@ +/* + * Aztech AZT1605 Driver + * Copyright (C) 2007,2010 Rene Herman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#define AZT1605 + +#define CRD_NAME "Aztech AZT1605" +#define DRV_NAME "AZT1605" +#define DEV_NAME "azt1605" + +#define GALAXY_DSP_MAJOR 2 +#define GALAXY_DSP_MINOR 1 + +#define GALAXY_CONFIG_SIZE 3 + +/* + * 24-bit config register + */ + +#define GALAXY_CONFIG_SBA_220 (0 << 0) +#define GALAXY_CONFIG_SBA_240 (1 << 0) +#define GALAXY_CONFIG_SBA_260 (2 << 0) +#define GALAXY_CONFIG_SBA_280 (3 << 0) +#define GALAXY_CONFIG_SBA_MASK GALAXY_CONFIG_SBA_280 + +#define GALAXY_CONFIG_MPUA_300 (0 << 2) +#define GALAXY_CONFIG_MPUA_330 (1 << 2) + +#define GALAXY_CONFIG_MPU_ENABLE (1 << 3) + +#define GALAXY_CONFIG_GAME_ENABLE (1 << 4) + +#define GALAXY_CONFIG_CD_PANASONIC (1 << 5) +#define GALAXY_CONFIG_CD_MITSUMI (1 << 6) +#define GALAXY_CONFIG_CD_MASK (\ + GALAXY_CONFIG_CD_PANASONIC | GALAXY_CONFIG_CD_MITSUMI) + +#define GALAXY_CONFIG_UNUSED (1 << 7) +#define GALAXY_CONFIG_UNUSED_MASK GALAXY_CONFIG_UNUSED + +#define GALAXY_CONFIG_SBIRQ_2 (1 << 8) +#define GALAXY_CONFIG_SBIRQ_3 (1 << 9) +#define GALAXY_CONFIG_SBIRQ_5 (1 << 10) +#define GALAXY_CONFIG_SBIRQ_7 (1 << 11) + +#define GALAXY_CONFIG_MPUIRQ_2 (1 << 12) +#define GALAXY_CONFIG_MPUIRQ_3 (1 << 13) +#define GALAXY_CONFIG_MPUIRQ_5 (1 << 14) +#define GALAXY_CONFIG_MPUIRQ_7 (1 << 15) + +#define GALAXY_CONFIG_WSSA_530 (0 << 16) +#define GALAXY_CONFIG_WSSA_604 (1 << 16) +#define GALAXY_CONFIG_WSSA_E80 (2 << 16) +#define GALAXY_CONFIG_WSSA_F40 (3 << 16) + +#define GALAXY_CONFIG_WSS_ENABLE (1 << 18) + +#define GALAXY_CONFIG_CDIRQ_11 (1 << 19) +#define GALAXY_CONFIG_CDIRQ_12 (1 << 20) +#define GALAXY_CONFIG_CDIRQ_15 (1 << 21) +#define GALAXY_CONFIG_CDIRQ_MASK (\ + GALAXY_CONFIG_CDIRQ_11 | GALAXY_CONFIG_CDIRQ_12 |\ + GALAXY_CONFIG_CDIRQ_15) + +#define GALAXY_CONFIG_CDDMA_DISABLE (0 << 22) +#define GALAXY_CONFIG_CDDMA_0 (1 << 22) +#define GALAXY_CONFIG_CDDMA_1 (2 << 22) +#define GALAXY_CONFIG_CDDMA_3 (3 << 22) +#define GALAXY_CONFIG_CDDMA_MASK GALAXY_CONFIG_CDDMA_3 + +#define GALAXY_CONFIG_MASK (\ + GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CD_MASK |\ + GALAXY_CONFIG_UNUSED_MASK | GALAXY_CONFIG_CDIRQ_MASK |\ + GALAXY_CONFIG_CDDMA_MASK) + +#include "galaxy.c" diff --git a/sound/isa/galaxy/azt2316.c b/sound/isa/galaxy/azt2316.c new file mode 100644 index 000000000..189441141 --- /dev/null +++ b/sound/isa/galaxy/azt2316.c @@ -0,0 +1,111 @@ +/* + * Aztech AZT2316 Driver + * Copyright (C) 2007,2010 Rene Herman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#define AZT2316 + +#define CRD_NAME "Aztech AZT2316" +#define DRV_NAME "AZT2316" +#define DEV_NAME "azt2316" + +#define GALAXY_DSP_MAJOR 3 +#define GALAXY_DSP_MINOR 1 + +#define GALAXY_CONFIG_SIZE 4 + +/* + * 32-bit config register + */ + +#define GALAXY_CONFIG_SBA_220 (0 << 0) +#define GALAXY_CONFIG_SBA_240 (1 << 0) +#define GALAXY_CONFIG_SBA_260 (2 << 0) +#define GALAXY_CONFIG_SBA_280 (3 << 0) +#define GALAXY_CONFIG_SBA_MASK GALAXY_CONFIG_SBA_280 + +#define GALAXY_CONFIG_SBIRQ_2 (1 << 2) +#define GALAXY_CONFIG_SBIRQ_5 (1 << 3) +#define GALAXY_CONFIG_SBIRQ_7 (1 << 4) +#define GALAXY_CONFIG_SBIRQ_10 (1 << 5) + +#define GALAXY_CONFIG_SBDMA_DISABLE (0 << 6) +#define GALAXY_CONFIG_SBDMA_0 (1 << 6) +#define GALAXY_CONFIG_SBDMA_1 (2 << 6) +#define GALAXY_CONFIG_SBDMA_3 (3 << 6) + +#define GALAXY_CONFIG_WSSA_530 (0 << 8) +#define GALAXY_CONFIG_WSSA_604 (1 << 8) +#define GALAXY_CONFIG_WSSA_E80 (2 << 8) +#define GALAXY_CONFIG_WSSA_F40 (3 << 8) + +#define GALAXY_CONFIG_WSS_ENABLE (1 << 10) + +#define GALAXY_CONFIG_GAME_ENABLE (1 << 11) + +#define GALAXY_CONFIG_MPUA_300 (0 << 12) +#define GALAXY_CONFIG_MPUA_330 (1 << 12) + +#define GALAXY_CONFIG_MPU_ENABLE (1 << 13) + +#define GALAXY_CONFIG_CDA_310 (0 << 14) +#define GALAXY_CONFIG_CDA_320 (1 << 14) +#define GALAXY_CONFIG_CDA_340 (2 << 14) +#define GALAXY_CONFIG_CDA_350 (3 << 14) +#define GALAXY_CONFIG_CDA_MASK GALAXY_CONFIG_CDA_350 + +#define GALAXY_CONFIG_CD_DISABLE (0 << 16) +#define GALAXY_CONFIG_CD_PANASONIC (1 << 16) +#define GALAXY_CONFIG_CD_SONY (2 << 16) +#define GALAXY_CONFIG_CD_MITSUMI (3 << 16) +#define GALAXY_CONFIG_CD_AZTECH (4 << 16) +#define GALAXY_CONFIG_CD_UNUSED_5 (5 << 16) +#define GALAXY_CONFIG_CD_UNUSED_6 (6 << 16) +#define GALAXY_CONFIG_CD_UNUSED_7 (7 << 16) +#define GALAXY_CONFIG_CD_MASK GALAXY_CONFIG_CD_UNUSED_7 + +#define GALAXY_CONFIG_CDDMA8_DISABLE (0 << 20) +#define GALAXY_CONFIG_CDDMA8_0 (1 << 20) +#define GALAXY_CONFIG_CDDMA8_1 (2 << 20) +#define GALAXY_CONFIG_CDDMA8_3 (3 << 20) +#define GALAXY_CONFIG_CDDMA8_MASK GALAXY_CONFIG_CDDMA8_3 + +#define GALAXY_CONFIG_CDDMA16_DISABLE (0 << 22) +#define GALAXY_CONFIG_CDDMA16_5 (1 << 22) +#define GALAXY_CONFIG_CDDMA16_6 (2 << 22) +#define GALAXY_CONFIG_CDDMA16_7 (3 << 22) +#define GALAXY_CONFIG_CDDMA16_MASK GALAXY_CONFIG_CDDMA16_7 + +#define GALAXY_CONFIG_MPUIRQ_2 (1 << 24) +#define GALAXY_CONFIG_MPUIRQ_5 (1 << 25) +#define GALAXY_CONFIG_MPUIRQ_7 (1 << 26) +#define GALAXY_CONFIG_MPUIRQ_10 (1 << 27) + +#define GALAXY_CONFIG_CDIRQ_5 (1 << 28) +#define GALAXY_CONFIG_CDIRQ_11 (1 << 29) +#define GALAXY_CONFIG_CDIRQ_12 (1 << 30) +#define GALAXY_CONFIG_CDIRQ_15 (1 << 31) +#define GALAXY_CONFIG_CDIRQ_MASK (\ + GALAXY_CONFIG_CDIRQ_5 | GALAXY_CONFIG_CDIRQ_11 |\ + GALAXY_CONFIG_CDIRQ_12 | GALAXY_CONFIG_CDIRQ_15) + +#define GALAXY_CONFIG_MASK (\ + GALAXY_CONFIG_SBA_MASK | GALAXY_CONFIG_CDA_MASK |\ + GALAXY_CONFIG_CD_MASK | GALAXY_CONFIG_CDDMA16_MASK |\ + GALAXY_CONFIG_CDDMA8_MASK | GALAXY_CONFIG_CDIRQ_MASK) + +#include "galaxy.c" diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c new file mode 100644 index 000000000..af9eea413 --- /dev/null +++ b/sound/isa/galaxy/galaxy.c @@ -0,0 +1,640 @@ +/* + * Aztech AZT1605/AZT2316 Driver + * Copyright (C) 2007,2010 Rene Herman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Rene Herman"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); + +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; + +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(wss_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(wss_port, "WSS port # for " CRD_NAME " driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "Playback DMA # for " CRD_NAME " driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "Capture DMA # for " CRD_NAME " driver."); + +/* + * Generic SB DSP support routines + */ + +#define DSP_PORT_RESET 0x6 +#define DSP_PORT_READ 0xa +#define DSP_PORT_COMMAND 0xc +#define DSP_PORT_STATUS 0xc +#define DSP_PORT_DATA_AVAIL 0xe + +#define DSP_SIGNATURE 0xaa + +#define DSP_COMMAND_GET_VERSION 0xe1 + +static int dsp_get_byte(void __iomem *port, u8 *val) +{ + int loops = 1000; + + while (!(ioread8(port + DSP_PORT_DATA_AVAIL) & 0x80)) { + if (!loops--) + return -EIO; + cpu_relax(); + } + *val = ioread8(port + DSP_PORT_READ); + return 0; +} + +static int dsp_reset(void __iomem *port) +{ + u8 val; + + iowrite8(1, port + DSP_PORT_RESET); + udelay(10); + iowrite8(0, port + DSP_PORT_RESET); + + if (dsp_get_byte(port, &val) < 0 || val != DSP_SIGNATURE) + return -ENODEV; + + return 0; +} + +static int dsp_command(void __iomem *port, u8 cmd) +{ + int loops = 1000; + + while (ioread8(port + DSP_PORT_STATUS) & 0x80) { + if (!loops--) + return -EIO; + cpu_relax(); + } + iowrite8(cmd, port + DSP_PORT_COMMAND); + return 0; +} + +static int dsp_get_version(void __iomem *port, u8 *major, u8 *minor) +{ + int err; + + err = dsp_command(port, DSP_COMMAND_GET_VERSION); + if (err < 0) + return err; + + err = dsp_get_byte(port, major); + if (err < 0) + return err; + + err = dsp_get_byte(port, minor); + if (err < 0) + return err; + + return 0; +} + +/* + * Generic WSS support routines + */ + +#define WSS_CONFIG_DMA_0 (1 << 0) +#define WSS_CONFIG_DMA_1 (2 << 0) +#define WSS_CONFIG_DMA_3 (3 << 0) +#define WSS_CONFIG_DUPLEX (1 << 2) +#define WSS_CONFIG_IRQ_7 (1 << 3) +#define WSS_CONFIG_IRQ_9 (2 << 3) +#define WSS_CONFIG_IRQ_10 (3 << 3) +#define WSS_CONFIG_IRQ_11 (4 << 3) + +#define WSS_PORT_CONFIG 0 +#define WSS_PORT_SIGNATURE 3 + +#define WSS_SIGNATURE 4 + +static int wss_detect(void __iomem *wss_port) +{ + if ((ioread8(wss_port + WSS_PORT_SIGNATURE) & 0x3f) != WSS_SIGNATURE) + return -ENODEV; + + return 0; +} + +static void wss_set_config(void __iomem *wss_port, u8 wss_config) +{ + iowrite8(wss_config, wss_port + WSS_PORT_CONFIG); +} + +/* + * Aztech Sound Galaxy specifics + */ + +#define GALAXY_PORT_CONFIG 1024 +#define CONFIG_PORT_SET 4 + +#define DSP_COMMAND_GALAXY_8 8 +#define GALAXY_COMMAND_GET_TYPE 5 + +#define DSP_COMMAND_GALAXY_9 9 +#define GALAXY_COMMAND_WSSMODE 0 +#define GALAXY_COMMAND_SB8MODE 1 + +#define GALAXY_MODE_WSS GALAXY_COMMAND_WSSMODE +#define GALAXY_MODE_SB8 GALAXY_COMMAND_SB8MODE + +struct snd_galaxy { + void __iomem *port; + void __iomem *config_port; + void __iomem *wss_port; + u32 config; + struct resource *res_port; + struct resource *res_config_port; + struct resource *res_wss_port; +}; + +static u32 config[SNDRV_CARDS]; +static u8 wss_config[SNDRV_CARDS]; + +static int snd_galaxy_match(struct device *dev, unsigned int n) +{ + if (!enable[n]) + return 0; + + switch (port[n]) { + case SNDRV_AUTO_PORT: + dev_err(dev, "please specify port\n"); + return 0; + case 0x220: + config[n] |= GALAXY_CONFIG_SBA_220; + break; + case 0x240: + config[n] |= GALAXY_CONFIG_SBA_240; + break; + case 0x260: + config[n] |= GALAXY_CONFIG_SBA_260; + break; + case 0x280: + config[n] |= GALAXY_CONFIG_SBA_280; + break; + default: + dev_err(dev, "invalid port %#lx\n", port[n]); + return 0; + } + + switch (wss_port[n]) { + case SNDRV_AUTO_PORT: + dev_err(dev, "please specify wss_port\n"); + return 0; + case 0x530: + config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_530; + break; + case 0x604: + config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_604; + break; + case 0xe80: + config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_E80; + break; + case 0xf40: + config[n] |= GALAXY_CONFIG_WSS_ENABLE | GALAXY_CONFIG_WSSA_F40; + break; + default: + dev_err(dev, "invalid WSS port %#lx\n", wss_port[n]); + return 0; + } + + switch (irq[n]) { + case SNDRV_AUTO_IRQ: + dev_err(dev, "please specify irq\n"); + return 0; + case 7: + wss_config[n] |= WSS_CONFIG_IRQ_7; + break; + case 2: + irq[n] = 9; + /* Fall through */ + case 9: + wss_config[n] |= WSS_CONFIG_IRQ_9; + break; + case 10: + wss_config[n] |= WSS_CONFIG_IRQ_10; + break; + case 11: + wss_config[n] |= WSS_CONFIG_IRQ_11; + break; + default: + dev_err(dev, "invalid IRQ %d\n", irq[n]); + return 0; + } + + switch (dma1[n]) { + case SNDRV_AUTO_DMA: + dev_err(dev, "please specify dma1\n"); + return 0; + case 0: + wss_config[n] |= WSS_CONFIG_DMA_0; + break; + case 1: + wss_config[n] |= WSS_CONFIG_DMA_1; + break; + case 3: + wss_config[n] |= WSS_CONFIG_DMA_3; + break; + default: + dev_err(dev, "invalid playback DMA %d\n", dma1[n]); + return 0; + } + + if (dma2[n] == SNDRV_AUTO_DMA || dma2[n] == dma1[n]) { + dma2[n] = -1; + goto mpu; + } + + wss_config[n] |= WSS_CONFIG_DUPLEX; + switch (dma2[n]) { + case 0: + break; + case 1: + if (dma1[n] == 0) + break; + /* Fall through */ + default: + dev_err(dev, "invalid capture DMA %d\n", dma2[n]); + return 0; + } + +mpu: + switch (mpu_port[n]) { + case SNDRV_AUTO_PORT: + dev_warn(dev, "mpu_port not specified; not using MPU-401\n"); + mpu_port[n] = -1; + goto fm; + case 0x300: + config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_300; + break; + case 0x330: + config[n] |= GALAXY_CONFIG_MPU_ENABLE | GALAXY_CONFIG_MPUA_330; + break; + default: + dev_err(dev, "invalid MPU port %#lx\n", mpu_port[n]); + return 0; + } + + switch (mpu_irq[n]) { + case SNDRV_AUTO_IRQ: + dev_warn(dev, "mpu_irq not specified: using polling mode\n"); + mpu_irq[n] = -1; + break; + case 2: + mpu_irq[n] = 9; + /* Fall through */ + case 9: + config[n] |= GALAXY_CONFIG_MPUIRQ_2; + break; +#ifdef AZT1605 + case 3: + config[n] |= GALAXY_CONFIG_MPUIRQ_3; + break; +#endif + case 5: + config[n] |= GALAXY_CONFIG_MPUIRQ_5; + break; + case 7: + config[n] |= GALAXY_CONFIG_MPUIRQ_7; + break; +#ifdef AZT2316 + case 10: + config[n] |= GALAXY_CONFIG_MPUIRQ_10; + break; +#endif + default: + dev_err(dev, "invalid MPU IRQ %d\n", mpu_irq[n]); + return 0; + } + + if (mpu_irq[n] == irq[n]) { + dev_err(dev, "cannot share IRQ between WSS and MPU-401\n"); + return 0; + } + +fm: + switch (fm_port[n]) { + case SNDRV_AUTO_PORT: + dev_warn(dev, "fm_port not specified: not using OPL3\n"); + fm_port[n] = -1; + break; + case 0x388: + break; + default: + dev_err(dev, "illegal FM port %#lx\n", fm_port[n]); + return 0; + } + + config[n] |= GALAXY_CONFIG_GAME_ENABLE; + return 1; +} + +static int galaxy_init(struct snd_galaxy *galaxy, u8 *type) +{ + u8 major; + u8 minor; + int err; + + err = dsp_reset(galaxy->port); + if (err < 0) + return err; + + err = dsp_get_version(galaxy->port, &major, &minor); + if (err < 0) + return err; + + if (major != GALAXY_DSP_MAJOR || minor != GALAXY_DSP_MINOR) + return -ENODEV; + + err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_8); + if (err < 0) + return err; + + err = dsp_command(galaxy->port, GALAXY_COMMAND_GET_TYPE); + if (err < 0) + return err; + + err = dsp_get_byte(galaxy->port, type); + if (err < 0) + return err; + + return 0; +} + +static int galaxy_set_mode(struct snd_galaxy *galaxy, u8 mode) +{ + int err; + + err = dsp_command(galaxy->port, DSP_COMMAND_GALAXY_9); + if (err < 0) + return err; + + err = dsp_command(galaxy->port, mode); + if (err < 0) + return err; + +#ifdef AZT1605 + /* + * Needed for MPU IRQ on AZT1605, but AZT2316 loses WSS again + */ + err = dsp_reset(galaxy->port); + if (err < 0) + return err; +#endif + + return 0; +} + +static void galaxy_set_config(struct snd_galaxy *galaxy, u32 config) +{ + u8 tmp = ioread8(galaxy->config_port + CONFIG_PORT_SET); + int i; + + iowrite8(tmp | 0x80, galaxy->config_port + CONFIG_PORT_SET); + for (i = 0; i < GALAXY_CONFIG_SIZE; i++) { + iowrite8(config, galaxy->config_port + i); + config >>= 8; + } + iowrite8(tmp & 0x7f, galaxy->config_port + CONFIG_PORT_SET); + msleep(10); +} + +static void galaxy_config(struct snd_galaxy *galaxy, u32 config) +{ + int i; + + for (i = GALAXY_CONFIG_SIZE; i; i--) { + u8 tmp = ioread8(galaxy->config_port + i - 1); + galaxy->config = (galaxy->config << 8) | tmp; + } + config |= galaxy->config & GALAXY_CONFIG_MASK; + galaxy_set_config(galaxy, config); +} + +static int galaxy_wss_config(struct snd_galaxy *galaxy, u8 wss_config) +{ + int err; + + err = wss_detect(galaxy->wss_port); + if (err < 0) + return err; + + wss_set_config(galaxy->wss_port, wss_config); + + err = galaxy_set_mode(galaxy, GALAXY_MODE_WSS); + if (err < 0) + return err; + + return 0; +} + +static void snd_galaxy_free(struct snd_card *card) +{ + struct snd_galaxy *galaxy = card->private_data; + + if (galaxy->wss_port) { + wss_set_config(galaxy->wss_port, 0); + ioport_unmap(galaxy->wss_port); + release_and_free_resource(galaxy->res_wss_port); + } + if (galaxy->config_port) { + galaxy_set_config(galaxy, galaxy->config); + ioport_unmap(galaxy->config_port); + release_and_free_resource(galaxy->res_config_port); + } + if (galaxy->port) { + ioport_unmap(galaxy->port); + release_and_free_resource(galaxy->res_port); + } +} + +static int snd_galaxy_probe(struct device *dev, unsigned int n) +{ + struct snd_galaxy *galaxy; + struct snd_wss *chip; + struct snd_card *card; + u8 type; + int err; + + err = snd_card_new(dev, index[n], id[n], THIS_MODULE, + sizeof(*galaxy), &card); + if (err < 0) + return err; + + card->private_free = snd_galaxy_free; + galaxy = card->private_data; + + galaxy->res_port = request_region(port[n], 16, DRV_NAME); + if (!galaxy->res_port) { + dev_err(dev, "could not grab ports %#lx-%#lx\n", port[n], + port[n] + 15); + err = -EBUSY; + goto error; + } + galaxy->port = ioport_map(port[n], 16); + + err = galaxy_init(galaxy, &type); + if (err < 0) { + dev_err(dev, "did not find a Sound Galaxy at %#lx\n", port[n]); + goto error; + } + dev_info(dev, "Sound Galaxy (type %d) found at %#lx\n", type, port[n]); + + galaxy->res_config_port = request_region(port[n] + GALAXY_PORT_CONFIG, + 16, DRV_NAME); + if (!galaxy->res_config_port) { + dev_err(dev, "could not grab ports %#lx-%#lx\n", + port[n] + GALAXY_PORT_CONFIG, + port[n] + GALAXY_PORT_CONFIG + 15); + err = -EBUSY; + goto error; + } + galaxy->config_port = ioport_map(port[n] + GALAXY_PORT_CONFIG, 16); + + galaxy_config(galaxy, config[n]); + + galaxy->res_wss_port = request_region(wss_port[n], 4, DRV_NAME); + if (!galaxy->res_wss_port) { + dev_err(dev, "could not grab ports %#lx-%#lx\n", wss_port[n], + wss_port[n] + 3); + err = -EBUSY; + goto error; + } + galaxy->wss_port = ioport_map(wss_port[n], 4); + + err = galaxy_wss_config(galaxy, wss_config[n]); + if (err < 0) { + dev_err(dev, "could not configure WSS\n"); + goto error; + } + + strcpy(card->driver, DRV_NAME); + strcpy(card->shortname, DRV_NAME); + sprintf(card->longname, "%s at %#lx/%#lx, irq %d, dma %d/%d", + card->shortname, port[n], wss_port[n], irq[n], dma1[n], + dma2[n]); + + err = snd_wss_create(card, wss_port[n] + 4, -1, irq[n], dma1[n], + dma2[n], WSS_HW_DETECT, 0, &chip); + if (err < 0) + goto error; + + err = snd_wss_pcm(chip, 0); + if (err < 0) + goto error; + + err = snd_wss_mixer(chip); + if (err < 0) + goto error; + + err = snd_wss_timer(chip, 0); + if (err < 0) + goto error; + + if (mpu_port[n] >= 0) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + mpu_port[n], 0, mpu_irq[n], NULL); + if (err < 0) + goto error; + } + + if (fm_port[n] >= 0) { + struct snd_opl3 *opl3; + + err = snd_opl3_create(card, fm_port[n], fm_port[n] + 2, + OPL3_HW_AUTO, 0, &opl3); + if (err < 0) { + dev_err(dev, "no OPL device at %#lx\n", fm_port[n]); + goto error; + } + err = snd_opl3_timer_new(opl3, 1, 2); + if (err < 0) + goto error; + + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + goto error; + } + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(dev, card); + return 0; + +error: + snd_card_free(card); + return err; +} + +static int snd_galaxy_remove(struct device *dev, unsigned int n) +{ + snd_card_free(dev_get_drvdata(dev)); + return 0; +} + +static struct isa_driver snd_galaxy_driver = { + .match = snd_galaxy_match, + .probe = snd_galaxy_probe, + .remove = snd_galaxy_remove, + + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_galaxy_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/Makefile b/sound/isa/gus/Makefile new file mode 100644 index 000000000..c6f32ffd3 --- /dev/null +++ b/sound/isa/gus/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-gus-lib-objs := gus_main.o \ + gus_io.o gus_irq.o gus_timer.o \ + gus_mem.o gus_mem_proc.o gus_dram.o gus_dma.o gus_volume.o \ + gus_pcm.o gus_mixer.o \ + gus_uart.o \ + gus_reset.o + +snd-gusclassic-objs := gusclassic.o +snd-gusextreme-objs := gusextreme.o +snd-gusmax-objs := gusmax.o +snd-interwave-objs := interwave.o +snd-interwave-stb-objs := interwave-stb.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_GUSCLASSIC) += snd-gusclassic.o snd-gus-lib.o +obj-$(CONFIG_SND_GUSMAX) += snd-gusmax.o snd-gus-lib.o +obj-$(CONFIG_SND_GUSEXTREME) += snd-gusextreme.o snd-gus-lib.o +obj-$(CONFIG_SND_INTERWAVE) += snd-interwave.o snd-gus-lib.o +obj-$(CONFIG_SND_INTERWAVE_STB) += snd-interwave-stb.o snd-gus-lib.o diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c new file mode 100644 index 000000000..48e76b8fe --- /dev/null +++ b/sound/isa/gus/gus_dma.c @@ -0,0 +1,251 @@ +/* + * Routines for GF1 DMA control + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +static void snd_gf1_dma_ack(struct snd_gus_card * gus) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00); + snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static void snd_gf1_dma_program(struct snd_gus_card * gus, + unsigned int addr, + unsigned long buf_addr, + unsigned int count, + unsigned int cmd) +{ + unsigned long flags; + unsigned int address; + unsigned char dma_cmd; + unsigned int address_high; + + snd_printdd("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n", + addr, buf_addr, count); + + if (gus->gf1.dma1 > 3) { + if (gus->gf1.enh_mode) { + address = addr >> 1; + } else { + if (addr & 0x1f) { + snd_printd("snd_gf1_dma_transfer: unaligned address (0x%x)?\n", addr); + return; + } + address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1); + } + } else { + address = addr; + } + + dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd; +#if 0 + dma_cmd |= 0x08; +#endif + if (dma_cmd & SNDRV_GF1_DMA_16BIT) { + count++; + count &= ~1; /* align */ + } + if (gus->gf1.dma1 > 3) { + dma_cmd |= SNDRV_GF1_DMA_WIDTH16; + count++; + count &= ~1; /* align */ + } + snd_gf1_dma_ack(gus); + snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE); +#if 0 + snd_printk(KERN_DEBUG "address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n", + address << 1, count, dma_cmd); +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + if (gus->gf1.enh_mode) { + address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f); + snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high); + } else + snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static struct snd_gf1_dma_block *snd_gf1_dma_next_block(struct snd_gus_card * gus) +{ + struct snd_gf1_dma_block *block; + + /* PCM block have bigger priority than synthesizer one */ + if (gus->gf1.dma_data_pcm) { + block = gus->gf1.dma_data_pcm; + if (gus->gf1.dma_data_pcm_last == block) { + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = NULL; + } else { + gus->gf1.dma_data_pcm = block->next; + } + } else if (gus->gf1.dma_data_synth) { + block = gus->gf1.dma_data_synth; + if (gus->gf1.dma_data_synth_last == block) { + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = NULL; + } else { + gus->gf1.dma_data_synth = block->next; + } + } else { + block = NULL; + } + if (block) { + gus->gf1.dma_ack = block->ack; + gus->gf1.dma_private_data = block->private_data; + } + return block; +} + + +static void snd_gf1_dma_interrupt(struct snd_gus_card * gus) +{ + struct snd_gf1_dma_block *block; + + snd_gf1_dma_ack(gus); + if (gus->gf1.dma_ack) + gus->gf1.dma_ack(gus, gus->gf1.dma_private_data); + spin_lock(&gus->dma_lock); + if (gus->gf1.dma_data_pcm == NULL && + gus->gf1.dma_data_synth == NULL) { + gus->gf1.dma_ack = NULL; + gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER; + spin_unlock(&gus->dma_lock); + return; + } + block = snd_gf1_dma_next_block(gus); + spin_unlock(&gus->dma_lock); + if (!block) + return; + snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); + kfree(block); +#if 0 + snd_printd(KERN_DEBUG "program dma (IRQ) - " + "addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", + block->addr, block->buf_addr, block->count, block->cmd); +#endif +} + +int snd_gf1_dma_init(struct snd_gus_card * gus) +{ + mutex_lock(&gus->dma_mutex); + gus->gf1.dma_shared++; + if (gus->gf1.dma_shared > 1) { + mutex_unlock(&gus->dma_mutex); + return 0; + } + gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt; + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = NULL; + mutex_unlock(&gus->dma_mutex); + return 0; +} + +int snd_gf1_dma_done(struct snd_gus_card * gus) +{ + struct snd_gf1_dma_block *block; + + mutex_lock(&gus->dma_mutex); + gus->gf1.dma_shared--; + if (!gus->gf1.dma_shared) { + snd_dma_disable(gus->gf1.dma1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE); + snd_gf1_dma_ack(gus); + while ((block = gus->gf1.dma_data_pcm)) { + gus->gf1.dma_data_pcm = block->next; + kfree(block); + } + while ((block = gus->gf1.dma_data_synth)) { + gus->gf1.dma_data_synth = block->next; + kfree(block); + } + gus->gf1.dma_data_pcm_last = + gus->gf1.dma_data_synth_last = NULL; + } + mutex_unlock(&gus->dma_mutex); + return 0; +} + +int snd_gf1_dma_transfer_block(struct snd_gus_card * gus, + struct snd_gf1_dma_block * __block, + int atomic, + int synth) +{ + unsigned long flags; + struct snd_gf1_dma_block *block; + + block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL); + if (!block) + return -ENOMEM; + + *block = *__block; + block->next = NULL; + + snd_printdd("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", + block->addr, (long) block->buffer, block->count, + block->cmd); + + snd_printdd("gus->gf1.dma_data_pcm_last = 0x%lx\n", + (long)gus->gf1.dma_data_pcm_last); + snd_printdd("gus->gf1.dma_data_pcm = 0x%lx\n", + (long)gus->gf1.dma_data_pcm); + + spin_lock_irqsave(&gus->dma_lock, flags); + if (synth) { + if (gus->gf1.dma_data_synth_last) { + gus->gf1.dma_data_synth_last->next = block; + gus->gf1.dma_data_synth_last = block; + } else { + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = block; + } + } else { + if (gus->gf1.dma_data_pcm_last) { + gus->gf1.dma_data_pcm_last->next = block; + gus->gf1.dma_data_pcm_last = block; + } else { + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = block; + } + } + if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) { + gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER; + block = snd_gf1_dma_next_block(gus); + spin_unlock_irqrestore(&gus->dma_lock, flags); + if (block == NULL) + return 0; + snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); + kfree(block); + return 0; + } + spin_unlock_irqrestore(&gus->dma_lock, flags); + return 0; +} diff --git a/sound/isa/gus/gus_dram.c b/sound/isa/gus/gus_dram.c new file mode 100644 index 000000000..fd2e2e2ed --- /dev/null +++ b/sound/isa/gus/gus_dram.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) by Jaroslav Kysela + * DRAM access routines + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + + +static int snd_gus_dram_poke(struct snd_gus_card *gus, char __user *_buffer, + unsigned int address, unsigned int size) +{ + unsigned long flags; + unsigned int size1, size2; + char buffer[256], *pbuffer; + + while (size > 0) { + size1 = size > sizeof(buffer) ? sizeof(buffer) : size; + if (copy_from_user(buffer, _buffer, size1)) + return -EFAULT; + if (gus->interwave) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + snd_gf1_dram_addr(gus, address); + outsb(GUSP(gus, DRAM), buffer, size1); + spin_unlock_irqrestore(&gus->reg_lock, flags); + address += size1; + } else { + pbuffer = buffer; + size2 = size1; + while (size2--) + snd_gf1_poke(gus, address++, *pbuffer++); + } + size -= size1; + _buffer += size1; + } + return 0; +} + + +int snd_gus_dram_write(struct snd_gus_card *gus, char __user *buffer, + unsigned int address, unsigned int size) +{ + return snd_gus_dram_poke(gus, buffer, address, size); +} + +static int snd_gus_dram_peek(struct snd_gus_card *gus, char __user *_buffer, + unsigned int address, unsigned int size, + int rom) +{ + unsigned long flags; + unsigned int size1, size2; + char buffer[256], *pbuffer; + + while (size > 0) { + size1 = size > sizeof(buffer) ? sizeof(buffer) : size; + if (gus->interwave) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, rom ? 0x03 : 0x01); + snd_gf1_dram_addr(gus, address); + insb(GUSP(gus, DRAM), buffer, size1); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + spin_unlock_irqrestore(&gus->reg_lock, flags); + address += size1; + } else { + pbuffer = buffer; + size2 = size1; + while (size2--) + *pbuffer++ = snd_gf1_peek(gus, address++); + } + if (copy_to_user(_buffer, buffer, size1)) + return -EFAULT; + size -= size1; + _buffer += size1; + } + return 0; +} + +int snd_gus_dram_read(struct snd_gus_card *gus, char __user *buffer, + unsigned int address, unsigned int size, + int rom) +{ + return snd_gus_dram_peek(gus, buffer, address, size, rom); +} diff --git a/sound/isa/gus/gus_io.c b/sound/isa/gus/gus_io.c new file mode 100644 index 000000000..2fd32ef22 --- /dev/null +++ b/sound/isa/gus/gus_io.c @@ -0,0 +1,540 @@ +/* + * Copyright (c) by Jaroslav Kysela + * I/O routines for GF1/InterWave synthesizer chips + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +void snd_gf1_delay(struct snd_gus_card * gus) +{ + int i; + + for (i = 0; i < 6; i++) { + mb(); + inb(GUSP(gus, DRAM)); + } +} + +/* + * ======================================================================= + */ + +/* + * ok.. stop of control registers (wave & ramp) need some special things.. + * big UltraClick (tm) elimination... + */ + +static inline void __snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg) +{ + unsigned char value; + + outb(reg | 0x80, gus->gf1.reg_regsel); + mb(); + value = inb(gus->gf1.reg_data8); + mb(); + outb(reg, gus->gf1.reg_regsel); + mb(); + outb((value | 0x03) & ~(0x80 | 0x20), gus->gf1.reg_data8); + mb(); +} + +static inline void __snd_gf1_write8(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + outb(data, gus->gf1.reg_data8); + mb(); +} + +static inline unsigned char __snd_gf1_look8(struct snd_gus_card * gus, + unsigned char reg) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + return inb(gus->gf1.reg_data8); +} + +static inline void __snd_gf1_write16(struct snd_gus_card * gus, + unsigned char reg, unsigned int data) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) data, gus->gf1.reg_data16); + mb(); +} + +static inline unsigned short __snd_gf1_look16(struct snd_gus_card * gus, + unsigned char reg) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + return inw(gus->gf1.reg_data16); +} + +static inline void __snd_gf1_adlib_write(struct snd_gus_card * gus, + unsigned char reg, unsigned char data) +{ + outb(reg, gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); + outb(data, gus->gf1.reg_timerdata); + inb(gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); +} + +static inline void __snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg, + unsigned int addr, int w_16bit) +{ + if (gus->gf1.enh_mode) { + if (w_16bit) + addr = ((addr >> 1) & ~0x0000000f) | (addr & 0x0000000f); + __snd_gf1_write8(gus, SNDRV_GF1_VB_UPPER_ADDRESS, (unsigned char) ((addr >> 26) & 0x03)); + } else if (w_16bit) + addr = (addr & 0x00c0000f) | ((addr & 0x003ffff0) >> 1); + __snd_gf1_write16(gus, reg, (unsigned short) (addr >> 11)); + __snd_gf1_write16(gus, reg + 1, (unsigned short) (addr << 5)); +} + +static inline unsigned int __snd_gf1_read_addr(struct snd_gus_card * gus, + unsigned char reg, short w_16bit) +{ + unsigned int res; + + res = ((unsigned int) __snd_gf1_look16(gus, reg | 0x80) << 11) & 0xfff800; + res |= ((unsigned int) __snd_gf1_look16(gus, (reg + 1) | 0x80) >> 5) & 0x0007ff; + if (gus->gf1.enh_mode) { + res |= (unsigned int) __snd_gf1_look8(gus, SNDRV_GF1_VB_UPPER_ADDRESS | 0x80) << 26; + if (w_16bit) + res = ((res << 1) & 0xffffffe0) | (res & 0x0000000f); + } else if (w_16bit) + res = ((res & 0x001ffff0) << 1) | (res & 0x00c0000f); + return res; +} + + +/* + * ======================================================================= + */ + +void snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg) +{ + __snd_gf1_ctrl_stop(gus, reg); +} + +void snd_gf1_write8(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + __snd_gf1_write8(gus, reg, data); +} + +unsigned char snd_gf1_look8(struct snd_gus_card * gus, unsigned char reg) +{ + return __snd_gf1_look8(gus, reg); +} + +void snd_gf1_write16(struct snd_gus_card * gus, + unsigned char reg, + unsigned int data) +{ + __snd_gf1_write16(gus, reg, data); +} + +unsigned short snd_gf1_look16(struct snd_gus_card * gus, unsigned char reg) +{ + return __snd_gf1_look16(gus, reg); +} + +void snd_gf1_adlib_write(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + __snd_gf1_adlib_write(gus, reg, data); +} + +void snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg, + unsigned int addr, short w_16bit) +{ + __snd_gf1_write_addr(gus, reg, addr, w_16bit); +} + +unsigned int snd_gf1_read_addr(struct snd_gus_card * gus, + unsigned char reg, + short w_16bit) +{ + return __snd_gf1_read_addr(gus, reg, w_16bit); +} + +/* + + */ + +void snd_gf1_i_ctrl_stop(struct snd_gus_card * gus, unsigned char reg) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_ctrl_stop(gus, reg); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +void snd_gf1_i_write8(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_write8(gus, reg, data); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned char snd_gf1_i_look8(struct snd_gus_card * gus, unsigned char reg) +{ + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&gus->reg_lock, flags); + res = __snd_gf1_look8(gus, reg); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +void snd_gf1_i_write16(struct snd_gus_card * gus, + unsigned char reg, + unsigned int data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_write16(gus, reg, data); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned short snd_gf1_i_look16(struct snd_gus_card * gus, unsigned char reg) +{ + unsigned long flags; + unsigned short res; + + spin_lock_irqsave(&gus->reg_lock, flags); + res = __snd_gf1_look16(gus, reg); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +#if 0 + +void snd_gf1_i_adlib_write(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_adlib_write(gus, reg, data); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +void snd_gf1_i_write_addr(struct snd_gus_card * gus, unsigned char reg, + unsigned int addr, short w_16bit) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_write_addr(gus, reg, addr, w_16bit); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +#endif /* 0 */ + +#ifdef CONFIG_SND_DEBUG +static unsigned int snd_gf1_i_read_addr(struct snd_gus_card * gus, + unsigned char reg, short w_16bit) +{ + unsigned int res; + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + res = __snd_gf1_read_addr(gus, reg, w_16bit); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} +#endif + +/* + + */ + +void snd_gf1_dram_addr(struct snd_gus_card * gus, unsigned int addr) +{ + outb(0x43, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(0x44, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); +} + +void snd_gf1_poke(struct snd_gus_card * gus, unsigned int addr, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(data, gus->gf1.reg_dram); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned char snd_gf1_peek(struct snd_gus_card * gus, unsigned int addr) +{ + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + res = inb(gus->gf1.reg_dram); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +#if 0 + +void snd_gf1_pokew(struct snd_gus_card * gus, unsigned int addr, unsigned short data) +{ + unsigned long flags; + +#ifdef CONFIG_SND_DEBUG + if (!gus->interwave) + snd_printk(KERN_DEBUG "snd_gf1_pokew - GF1!!!\n"); +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + mb(); + outw(data, gus->gf1.reg_data16); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned short snd_gf1_peekw(struct snd_gus_card * gus, unsigned int addr) +{ + unsigned long flags; + unsigned short res; + +#ifdef CONFIG_SND_DEBUG + if (!gus->interwave) + snd_printk(KERN_DEBUG "snd_gf1_peekw - GF1!!!\n"); +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + mb(); + res = inw(gus->gf1.reg_data16); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +void snd_gf1_dram_setmem(struct snd_gus_card * gus, unsigned int addr, + unsigned short value, unsigned int count) +{ + unsigned long port; + unsigned long flags; + +#ifdef CONFIG_SND_DEBUG + if (!gus->interwave) + snd_printk(KERN_DEBUG "snd_gf1_dram_setmem - GF1!!!\n"); +#endif + addr &= ~1; + count >>= 1; + port = GUSP(gus, GF1DATALOW); + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + while (count--) + outw(value, port); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +#endif /* 0 */ + +void snd_gf1_select_active_voices(struct snd_gus_card * gus) +{ + unsigned short voices; + + static unsigned short voices_tbl[32 - 14 + 1] = + { + 44100, 41160, 38587, 36317, 34300, 32494, 30870, 29400, 28063, 26843, + 25725, 24696, 23746, 22866, 22050, 21289, 20580, 19916, 19293 + }; + + voices = gus->gf1.active_voices; + if (voices > 32) + voices = 32; + if (voices < 14) + voices = 14; + if (gus->gf1.enh_mode) + voices = 32; + gus->gf1.active_voices = voices; + gus->gf1.playback_freq = + gus->gf1.enh_mode ? 44100 : voices_tbl[voices - 14]; + if (!gus->gf1.enh_mode) { + snd_gf1_i_write8(gus, SNDRV_GF1_GB_ACTIVE_VOICES, 0xc0 | (voices - 1)); + udelay(100); + } +} + +#ifdef CONFIG_SND_DEBUG + +void snd_gf1_print_voice_registers(struct snd_gus_card * gus) +{ + unsigned char mode; + int voice, ctrl; + + voice = gus->gf1.active_voice; + printk(KERN_INFO " -%i- GF1 voice ctrl, ramp ctrl = 0x%x, 0x%x\n", voice, ctrl = snd_gf1_i_read8(gus, 0), snd_gf1_i_read8(gus, 0x0d)); + printk(KERN_INFO " -%i- GF1 frequency = 0x%x\n", voice, snd_gf1_i_read16(gus, 1)); + printk(KERN_INFO " -%i- GF1 loop start, end = 0x%x (0x%x), 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 2, ctrl & 4), snd_gf1_i_read_addr(gus, 2, (ctrl & 4) ^ 4), snd_gf1_i_read_addr(gus, 4, ctrl & 4), snd_gf1_i_read_addr(gus, 4, (ctrl & 4) ^ 4)); + printk(KERN_INFO " -%i- GF1 ramp start, end, rate = 0x%x, 0x%x, 0x%x\n", voice, snd_gf1_i_read8(gus, 7), snd_gf1_i_read8(gus, 8), snd_gf1_i_read8(gus, 6)); + printk(KERN_INFO" -%i- GF1 volume = 0x%x\n", voice, snd_gf1_i_read16(gus, 9)); + printk(KERN_INFO " -%i- GF1 position = 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 0x0a, ctrl & 4), snd_gf1_i_read_addr(gus, 0x0a, (ctrl & 4) ^ 4)); + if (gus->interwave && snd_gf1_i_read8(gus, 0x19) & 0x01) { /* enhanced mode */ + mode = snd_gf1_i_read8(gus, 0x15); + printk(KERN_INFO " -%i- GFA1 mode = 0x%x\n", voice, mode); + if (mode & 0x01) { /* Effect processor */ + printk(KERN_INFO " -%i- GFA1 effect address = 0x%x\n", voice, snd_gf1_i_read_addr(gus, 0x11, ctrl & 4)); + printk(KERN_INFO " -%i- GFA1 effect volume = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x16)); + printk(KERN_INFO " -%i- GFA1 effect volume final = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x1d)); + printk(KERN_INFO " -%i- GFA1 effect accumulator = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x14)); + } + if (mode & 0x20) { + printk(KERN_INFO " -%i- GFA1 left offset = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x13), snd_gf1_i_read16(gus, 0x13) >> 4); + printk(KERN_INFO " -%i- GFA1 left offset final = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1c), snd_gf1_i_read16(gus, 0x1c) >> 4); + printk(KERN_INFO " -%i- GFA1 right offset = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x0c), snd_gf1_i_read16(gus, 0x0c) >> 4); + printk(KERN_INFO " -%i- GFA1 right offset final = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1b), snd_gf1_i_read16(gus, 0x1b) >> 4); + } else + printk(KERN_INFO " -%i- GF1 pan = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c)); + } else + printk(KERN_INFO " -%i- GF1 pan = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c)); +} + +#if 0 + +void snd_gf1_print_global_registers(struct snd_gus_card * gus) +{ + unsigned char global_mode = 0x00; + + printk(KERN_INFO " -G- GF1 active voices = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ACTIVE_VOICES)); + if (gus->interwave) { + global_mode = snd_gf1_i_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE); + printk(KERN_INFO " -G- GF1 global mode = 0x%x\n", global_mode); + } + if (global_mode & 0x02) /* LFO enabled? */ + printk(KERN_INFO " -G- GF1 LFO base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_LFO_BASE)); + printk(KERN_INFO " -G- GF1 voices IRQ read = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VOICES_IRQ_READ)); + printk(KERN_INFO " -G- GF1 DRAM DMA control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL)); + printk(KERN_INFO " -G- GF1 DRAM DMA high/low = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW)); + printk(KERN_INFO " -G- GF1 DRAM IO high/low = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_IO_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_IO_LOW)); + if (!gus->interwave) + printk(KERN_INFO " -G- GF1 record DMA control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL)); + printk(KERN_INFO " -G- GF1 DRAM IO 16 = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_DRAM_IO16)); + if (gus->gf1.enh_mode) { + printk(KERN_INFO " -G- GFA1 memory config = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG)); + printk(KERN_INFO " -G- GFA1 memory control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MEMORY_CONTROL)); + printk(KERN_INFO " -G- GFA1 FIFO record base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR)); + printk(KERN_INFO " -G- GFA1 FIFO playback base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR)); + printk(KERN_INFO " -G- GFA1 interleave control = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_INTERLEAVE)); + } +} + +void snd_gf1_print_setup_registers(struct snd_gus_card * gus) +{ + printk(KERN_INFO " -S- mix control = 0x%x\n", inb(GUSP(gus, MIXCNTRLREG))); + printk(KERN_INFO " -S- IRQ status = 0x%x\n", inb(GUSP(gus, IRQSTAT))); + printk(KERN_INFO " -S- timer control = 0x%x\n", inb(GUSP(gus, TIMERCNTRL))); + printk(KERN_INFO " -S- timer data = 0x%x\n", inb(GUSP(gus, TIMERDATA))); + printk(KERN_INFO " -S- status read = 0x%x\n", inb(GUSP(gus, REGCNTRLS))); + printk(KERN_INFO " -S- Sound Blaster control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL)); + printk(KERN_INFO " -S- AdLib timer 1/2 = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1), snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2)); + printk(KERN_INFO " -S- reset = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)); + if (gus->interwave) { + printk(KERN_INFO " -S- compatibility = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_COMPATIBILITY)); + printk(KERN_INFO " -S- decode control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DECODE_CONTROL)); + printk(KERN_INFO " -S- version number = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER)); + printk(KERN_INFO " -S- MPU-401 emul. control A/B = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A), snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B)); + printk(KERN_INFO " -S- emulation IRQ = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_EMULATION_IRQ)); + } +} + +void snd_gf1_peek_print_block(struct snd_gus_card * gus, unsigned int addr, int count, int w_16bit) +{ + if (!w_16bit) { + while (count-- > 0) + printk(count > 0 ? "%02x:" : "%02x", snd_gf1_peek(gus, addr++)); + } else { + while (count-- > 0) { + printk(count > 0 ? "%04x:" : "%04x", snd_gf1_peek(gus, addr) | (snd_gf1_peek(gus, addr + 1) << 8)); + addr += 2; + } + } +} + +#endif /* 0 */ + +#endif diff --git a/sound/isa/gus/gus_irq.c b/sound/isa/gus/gus_irq.c new file mode 100644 index 000000000..2055aff71 --- /dev/null +++ b/sound/isa/gus/gus_irq.c @@ -0,0 +1,149 @@ +/* + * Routine for IRQ handling from GF1/InterWave chip + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#ifdef CONFIG_SND_DEBUG +#define STAT_ADD(x) ((x)++) +#else +#define STAT_ADD(x) while (0) { ; } +#endif + +irqreturn_t snd_gus_interrupt(int irq, void *dev_id) +{ + struct snd_gus_card * gus = dev_id; + unsigned char status; + int loop = 100; + int handled = 0; + +__again: + status = inb(gus->gf1.reg_irqstat); + if (status == 0) + return IRQ_RETVAL(handled); + handled = 1; + /* snd_printk(KERN_DEBUG "IRQ: status = 0x%x\n", status); */ + if (status & 0x02) { + STAT_ADD(gus->gf1.interrupt_stat_midi_in); + if (gus->gf1.interrupt_handler_midi_in) + gus->gf1.interrupt_handler_midi_in(gus); + } + if (status & 0x01) { + STAT_ADD(gus->gf1.interrupt_stat_midi_out); + if (gus->gf1.interrupt_handler_midi_out) + gus->gf1.interrupt_handler_midi_out(gus); + } + if (status & (0x20 | 0x40)) { + unsigned int already, _current_; + unsigned char voice_status, voice; + struct snd_gus_voice *pvoice; + + already = 0; + while (((voice_status = snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ)) & 0xc0) != 0xc0) { + voice = voice_status & 0x1f; + _current_ = 1 << voice; + if (already & _current_) + continue; /* multi request */ + already |= _current_; /* mark request */ +#if 0 + printk(KERN_DEBUG "voice = %i, voice_status = 0x%x, " + "voice_verify = %i\n", + voice, voice_status, inb(GUSP(gus, GF1PAGE))); +#endif + pvoice = &gus->gf1.voices[voice]; + if (pvoice->use) { + if (!(voice_status & 0x80)) { /* voice position IRQ */ + STAT_ADD(pvoice->interrupt_stat_wave); + pvoice->handler_wave(gus, pvoice); + } + if (!(voice_status & 0x40)) { /* volume ramp IRQ */ + STAT_ADD(pvoice->interrupt_stat_volume); + pvoice->handler_volume(gus, pvoice); + } + } else { + STAT_ADD(gus->gf1.interrupt_stat_voice_lost); + snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + } + } + } + if (status & 0x04) { + STAT_ADD(gus->gf1.interrupt_stat_timer1); + if (gus->gf1.interrupt_handler_timer1) + gus->gf1.interrupt_handler_timer1(gus); + } + if (status & 0x08) { + STAT_ADD(gus->gf1.interrupt_stat_timer2); + if (gus->gf1.interrupt_handler_timer2) + gus->gf1.interrupt_handler_timer2(gus); + } + if (status & 0x80) { + if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL) & 0x40) { + STAT_ADD(gus->gf1.interrupt_stat_dma_write); + if (gus->gf1.interrupt_handler_dma_write) + gus->gf1.interrupt_handler_dma_write(gus); + } + if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL) & 0x40) { + STAT_ADD(gus->gf1.interrupt_stat_dma_read); + if (gus->gf1.interrupt_handler_dma_read) + gus->gf1.interrupt_handler_dma_read(gus); + } + } + if (--loop > 0) + goto __again; + return IRQ_NONE; +} + +#ifdef CONFIG_SND_DEBUG +static void snd_gus_irq_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_gus_card *gus; + struct snd_gus_voice *pvoice; + int idx; + + gus = entry->private_data; + snd_iprintf(buffer, "midi out = %u\n", gus->gf1.interrupt_stat_midi_out); + snd_iprintf(buffer, "midi in = %u\n", gus->gf1.interrupt_stat_midi_in); + snd_iprintf(buffer, "timer1 = %u\n", gus->gf1.interrupt_stat_timer1); + snd_iprintf(buffer, "timer2 = %u\n", gus->gf1.interrupt_stat_timer2); + snd_iprintf(buffer, "dma write = %u\n", gus->gf1.interrupt_stat_dma_write); + snd_iprintf(buffer, "dma read = %u\n", gus->gf1.interrupt_stat_dma_read); + snd_iprintf(buffer, "voice lost = %u\n", gus->gf1.interrupt_stat_voice_lost); + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + snd_iprintf(buffer, "voice %i: wave = %u, volume = %u\n", + idx, + pvoice->interrupt_stat_wave, + pvoice->interrupt_stat_volume); + } +} + +void snd_gus_irq_profile_init(struct snd_gus_card *gus) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(gus->card, "gusirq", &entry)) + snd_info_set_text_ops(entry, gus, snd_gus_irq_info_read); +} + +#endif diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c new file mode 100644 index 000000000..3b8a0c880 --- /dev/null +++ b/sound/isa/gus/gus_main.c @@ -0,0 +1,467 @@ +/* + * Routines for Gravis UltraSound soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards"); +MODULE_LICENSE("GPL"); + +static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches); + +int snd_gus_use_inc(struct snd_gus_card * gus) +{ + if (!try_module_get(gus->card->module)) + return 0; + return 1; +} + +void snd_gus_use_dec(struct snd_gus_card * gus) +{ + module_put(gus->card->module); +} + +static int snd_gus_joystick_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 31; + return 0; +} + +static int snd_gus_joystick_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = gus->joystick_dac & 31; + return 0; +} + +static int snd_gus_joystick_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval; + + nval = ucontrol->value.integer.value[0] & 31; + spin_lock_irqsave(&gus->reg_lock, flags); + change = gus->joystick_dac != nval; + gus->joystick_dac = nval; + snd_gf1_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return change; +} + +static const struct snd_kcontrol_new snd_gus_joystick_control = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Joystick Speed", + .info = snd_gus_joystick_info, + .get = snd_gus_joystick_get, + .put = snd_gus_joystick_put +}; + +static void snd_gus_init_control(struct snd_gus_card *gus) +{ + if (!gus->ace_flag) + snd_ctl_add(gus->card, snd_ctl_new1(&snd_gus_joystick_control, gus)); +} + +/* + * + */ + +static int snd_gus_free(struct snd_gus_card *gus) +{ + if (gus->gf1.res_port2 == NULL) + goto __hw_end; + snd_gf1_stop(gus); + snd_gus_init_dma_irq(gus, 0); + __hw_end: + release_and_free_resource(gus->gf1.res_port1); + release_and_free_resource(gus->gf1.res_port2); + if (gus->gf1.irq >= 0) + free_irq(gus->gf1.irq, (void *) gus); + if (gus->gf1.dma1 >= 0) { + disable_dma(gus->gf1.dma1); + free_dma(gus->gf1.dma1); + } + if (!gus->equal_dma && gus->gf1.dma2 >= 0) { + disable_dma(gus->gf1.dma2); + free_dma(gus->gf1.dma2); + } + kfree(gus); + return 0; +} + +static int snd_gus_dev_free(struct snd_device *device) +{ + struct snd_gus_card *gus = device->device_data; + return snd_gus_free(gus); +} + +int snd_gus_create(struct snd_card *card, + unsigned long port, + int irq, int dma1, int dma2, + int timer_dev, + int voices, + int pcm_channels, + int effect, + struct snd_gus_card **rgus) +{ + struct snd_gus_card *gus; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_gus_dev_free, + }; + + *rgus = NULL; + gus = kzalloc(sizeof(*gus), GFP_KERNEL); + if (gus == NULL) + return -ENOMEM; + spin_lock_init(&gus->reg_lock); + spin_lock_init(&gus->voice_alloc); + spin_lock_init(&gus->active_voice_lock); + spin_lock_init(&gus->event_lock); + spin_lock_init(&gus->dma_lock); + spin_lock_init(&gus->pcm_volume_level_lock); + spin_lock_init(&gus->uart_cmd_lock); + mutex_init(&gus->dma_mutex); + gus->gf1.irq = -1; + gus->gf1.dma1 = -1; + gus->gf1.dma2 = -1; + gus->card = card; + gus->gf1.port = port; + /* fill register variables for speedup */ + gus->gf1.reg_page = GUSP(gus, GF1PAGE); + gus->gf1.reg_regsel = GUSP(gus, GF1REGSEL); + gus->gf1.reg_data8 = GUSP(gus, GF1DATAHIGH); + gus->gf1.reg_data16 = GUSP(gus, GF1DATALOW); + gus->gf1.reg_irqstat = GUSP(gus, IRQSTAT); + gus->gf1.reg_dram = GUSP(gus, DRAM); + gus->gf1.reg_timerctrl = GUSP(gus, TIMERCNTRL); + gus->gf1.reg_timerdata = GUSP(gus, TIMERDATA); + /* allocate resources */ + if ((gus->gf1.res_port1 = request_region(port, 16, "GUS GF1 (Adlib/SB)")) == NULL) { + snd_printk(KERN_ERR "gus: can't grab SB port 0x%lx\n", port); + snd_gus_free(gus); + return -EBUSY; + } + if ((gus->gf1.res_port2 = request_region(port + 0x100, 12, "GUS GF1 (Synth)")) == NULL) { + snd_printk(KERN_ERR "gus: can't grab synth port 0x%lx\n", port + 0x100); + snd_gus_free(gus); + return -EBUSY; + } + if (irq >= 0 && request_irq(irq, snd_gus_interrupt, 0, "GUS GF1", (void *) gus)) { + snd_printk(KERN_ERR "gus: can't grab irq %d\n", irq); + snd_gus_free(gus); + return -EBUSY; + } + gus->gf1.irq = irq; + if (request_dma(dma1, "GUS - 1")) { + snd_printk(KERN_ERR "gus: can't grab DMA1 %d\n", dma1); + snd_gus_free(gus); + return -EBUSY; + } + gus->gf1.dma1 = dma1; + if (dma2 >= 0 && dma1 != dma2) { + if (request_dma(dma2, "GUS - 2")) { + snd_printk(KERN_ERR "gus: can't grab DMA2 %d\n", dma2); + snd_gus_free(gus); + return -EBUSY; + } + gus->gf1.dma2 = dma2; + } else { + gus->gf1.dma2 = gus->gf1.dma1; + gus->equal_dma = 1; + } + gus->timer_dev = timer_dev; + if (voices < 14) + voices = 14; + if (voices > 32) + voices = 32; + if (pcm_channels < 0) + pcm_channels = 0; + if (pcm_channels > 8) + pcm_channels = 8; + pcm_channels++; + pcm_channels &= ~1; + gus->gf1.effect = effect ? 1 : 0; + gus->gf1.active_voices = voices; + gus->gf1.pcm_channels = pcm_channels; + gus->gf1.volume_ramp = 25; + gus->gf1.smooth_pan = 1; + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, gus, &ops)) < 0) { + snd_gus_free(gus); + return err; + } + *rgus = gus; + return 0; +} + +/* + * Memory detection routine for plain GF1 soundcards + */ + +static int snd_gus_detect_memory(struct snd_gus_card * gus) +{ + int l, idx, local; + unsigned char d; + + snd_gf1_poke(gus, 0L, 0xaa); + snd_gf1_poke(gus, 1L, 0x55); + if (snd_gf1_peek(gus, 0L) != 0xaa || snd_gf1_peek(gus, 1L) != 0x55) { + snd_printk(KERN_ERR "plain GF1 card at 0x%lx without onboard DRAM?\n", gus->gf1.port); + return -ENOMEM; + } + for (idx = 1, d = 0xab; idx < 4; idx++, d++) { + local = idx << 18; + snd_gf1_poke(gus, local, d); + snd_gf1_poke(gus, local + 1, d + 1); + if (snd_gf1_peek(gus, local) != d || + snd_gf1_peek(gus, local + 1) != d + 1 || + snd_gf1_peek(gus, 0L) != 0xaa) + break; + } +#if 1 + gus->gf1.memory = idx << 18; +#else + gus->gf1.memory = 256 * 1024; +#endif + for (l = 0, local = gus->gf1.memory; l < 4; l++, local -= 256 * 1024) { + gus->gf1.mem_alloc.banks_8[l].address = + gus->gf1.mem_alloc.banks_8[l].size = 0; + gus->gf1.mem_alloc.banks_16[l].address = l << 18; + gus->gf1.mem_alloc.banks_16[l].size = local > 0 ? 256 * 1024 : 0; + } + gus->gf1.mem_alloc.banks_8[0].size = gus->gf1.memory; + return 0; /* some memory were detected */ +} + +static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches) +{ + struct snd_card *card; + unsigned long flags; + int irq, dma1, dma2; + static unsigned char irqs[16] = + {0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7}; + static unsigned char dmas[8] = + {6, 1, 0, 2, 0, 3, 4, 5}; + + if (snd_BUG_ON(!gus)) + return -EINVAL; + card = gus->card; + if (snd_BUG_ON(!card)) + return -EINVAL; + + gus->mix_cntrl_reg &= 0xf8; + gus->mix_cntrl_reg |= 0x01; /* disable MIC, LINE IN, enable LINE OUT */ + if (gus->codec_flag || gus->ess_flag) { + gus->mix_cntrl_reg &= ~1; /* enable LINE IN */ + gus->mix_cntrl_reg |= 4; /* enable MIC */ + } + dma1 = gus->gf1.dma1; + dma1 = abs(dma1); + dma1 = dmas[dma1 & 7]; + dma2 = gus->gf1.dma2; + dma2 = abs(dma2); + dma2 = dmas[dma2 & 7]; + dma1 |= gus->equal_dma ? 0x40 : (dma2 << 3); + + if ((dma1 & 7) == 0 || (dma2 & 7) == 0) { + snd_printk(KERN_ERR "Error! DMA isn't defined.\n"); + return -EINVAL; + } + irq = gus->gf1.irq; + irq = abs(irq); + irq = irqs[irq & 0x0f]; + if (irq == 0) { + snd_printk(KERN_ERR "Error! IRQ isn't defined.\n"); + return -EINVAL; + } + irq |= 0x40; +#if 0 + card->mixer.mix_ctrl_reg |= 0x10; +#endif + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(5, GUSP(gus, REGCNTRLS)); + outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(0x00, GUSP(gus, IRQDMACNTRLREG)); + outb(0, GUSP(gus, REGCNTRLS)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + + udelay(100); + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(dma1, GUSP(gus, IRQDMACNTRLREG)); + if (latches) { + outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(irq, GUSP(gus, IRQDMACNTRLREG)); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + + udelay(100); + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(dma1, GUSP(gus, IRQDMACNTRLREG)); + if (latches) { + outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(irq, GUSP(gus, IRQDMACNTRLREG)); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + + snd_gf1_delay(gus); + + if (latches) + gus->mix_cntrl_reg |= 0x08; /* enable latches */ + else + gus->mix_cntrl_reg &= ~0x08; /* disable latches */ + spin_lock_irqsave(&gus->reg_lock, flags); + outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(0, GUSP(gus, GF1PAGE)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + + return 0; +} + +static int snd_gus_check_version(struct snd_gus_card * gus) +{ + unsigned long flags; + unsigned char val, rev; + struct snd_card *card; + + card = gus->card; + spin_lock_irqsave(&gus->reg_lock, flags); + outb(0x20, GUSP(gus, REGCNTRLS)); + val = inb(GUSP(gus, REGCNTRLS)); + rev = inb(GUSP(gus, BOARDVERSION)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + snd_printdd("GF1 [0x%lx] init - val = 0x%x, rev = 0x%x\n", gus->gf1.port, val, rev); + strcpy(card->driver, "GUS"); + strcpy(card->longname, "Gravis UltraSound Classic (2.4)"); + if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) { + if (rev >= 5 && rev <= 9) { + gus->ics_flag = 1; + if (rev == 5) + gus->ics_flipped = 1; + card->longname[27] = '3'; + card->longname[29] = rev == 5 ? '5' : '7'; + } + if (rev >= 10 && rev != 255) { + if (rev >= 10 && rev <= 11) { + strcpy(card->driver, "GUS MAX"); + strcpy(card->longname, "Gravis UltraSound MAX"); + gus->max_flag = 1; + } else if (rev == 0x30) { + strcpy(card->driver, "GUS ACE"); + strcpy(card->longname, "Gravis UltraSound Ace"); + gus->ace_flag = 1; + } else if (rev == 0x50) { + strcpy(card->driver, "GUS Extreme"); + strcpy(card->longname, "Gravis UltraSound Extreme"); + gus->ess_flag = 1; + } else { + snd_printk(KERN_ERR "unknown GF1 revision number at 0x%lx - 0x%x (0x%x)\n", gus->gf1.port, rev, val); + snd_printk(KERN_ERR " please - report to \n"); + } + } + } + strcpy(card->shortname, card->longname); + gus->uart_enable = 1; /* standard GUSes doesn't have midi uart trouble */ + snd_gus_init_control(gus); + return 0; +} + +int snd_gus_initialize(struct snd_gus_card *gus) +{ + int err; + + if (!gus->interwave) { + if ((err = snd_gus_check_version(gus)) < 0) { + snd_printk(KERN_ERR "version check failed\n"); + return err; + } + if ((err = snd_gus_detect_memory(gus)) < 0) + return err; + } + if ((err = snd_gus_init_dma_irq(gus, 1)) < 0) + return err; + snd_gf1_start(gus); + gus->initialized = 1; + return 0; +} + + /* gus_io.c */ +EXPORT_SYMBOL(snd_gf1_delay); +EXPORT_SYMBOL(snd_gf1_write8); +EXPORT_SYMBOL(snd_gf1_look8); +EXPORT_SYMBOL(snd_gf1_write16); +EXPORT_SYMBOL(snd_gf1_look16); +EXPORT_SYMBOL(snd_gf1_i_write8); +EXPORT_SYMBOL(snd_gf1_i_look8); +EXPORT_SYMBOL(snd_gf1_i_look16); +EXPORT_SYMBOL(snd_gf1_dram_addr); +EXPORT_SYMBOL(snd_gf1_write_addr); +EXPORT_SYMBOL(snd_gf1_poke); +EXPORT_SYMBOL(snd_gf1_peek); + /* gus_reset.c */ +EXPORT_SYMBOL(snd_gf1_alloc_voice); +EXPORT_SYMBOL(snd_gf1_free_voice); +EXPORT_SYMBOL(snd_gf1_ctrl_stop); +EXPORT_SYMBOL(snd_gf1_stop_voice); + /* gus_mixer.c */ +EXPORT_SYMBOL(snd_gf1_new_mixer); + /* gus_pcm.c */ +EXPORT_SYMBOL(snd_gf1_pcm_new); + /* gus.c */ +EXPORT_SYMBOL(snd_gus_use_inc); +EXPORT_SYMBOL(snd_gus_use_dec); +EXPORT_SYMBOL(snd_gus_create); +EXPORT_SYMBOL(snd_gus_initialize); + /* gus_irq.c */ +EXPORT_SYMBOL(snd_gus_interrupt); + /* gus_uart.c */ +EXPORT_SYMBOL(snd_gf1_rawmidi_new); + /* gus_dram.c */ +EXPORT_SYMBOL(snd_gus_dram_write); +EXPORT_SYMBOL(snd_gus_dram_read); + /* gus_volume.c */ +EXPORT_SYMBOL(snd_gf1_lvol_to_gvol_raw); +EXPORT_SYMBOL(snd_gf1_translate_freq); + /* gus_mem.c */ +EXPORT_SYMBOL(snd_gf1_mem_alloc); +EXPORT_SYMBOL(snd_gf1_mem_xfree); +EXPORT_SYMBOL(snd_gf1_mem_free); +EXPORT_SYMBOL(snd_gf1_mem_lock); diff --git a/sound/isa/gus/gus_mem.c b/sound/isa/gus/gus_mem.c new file mode 100644 index 000000000..af888a022 --- /dev/null +++ b/sound/isa/gus/gus_mem.c @@ -0,0 +1,351 @@ +/* + * Copyright (c) by Jaroslav Kysela + * GUS's memory allocation routines / bottom layer + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#ifdef CONFIG_SND_DEBUG +static void snd_gf1_mem_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer); +#endif + +void snd_gf1_mem_lock(struct snd_gf1_mem * alloc, int xup) +{ + if (!xup) { + mutex_lock(&alloc->memory_mutex); + } else { + mutex_unlock(&alloc->memory_mutex); + } +} + +static struct snd_gf1_mem_block *snd_gf1_mem_xalloc(struct snd_gf1_mem * alloc, + struct snd_gf1_mem_block * block) +{ + struct snd_gf1_mem_block *pblock, *nblock; + + nblock = kmalloc(sizeof(struct snd_gf1_mem_block), GFP_KERNEL); + if (nblock == NULL) + return NULL; + *nblock = *block; + pblock = alloc->first; + while (pblock) { + if (pblock->ptr > nblock->ptr) { + nblock->prev = pblock->prev; + nblock->next = pblock; + pblock->prev = nblock; + if (pblock == alloc->first) + alloc->first = nblock; + else + nblock->prev->next = nblock; + mutex_unlock(&alloc->memory_mutex); + return NULL; + } + pblock = pblock->next; + } + nblock->next = NULL; + if (alloc->last == NULL) { + nblock->prev = NULL; + alloc->first = alloc->last = nblock; + } else { + nblock->prev = alloc->last; + alloc->last->next = nblock; + alloc->last = nblock; + } + return nblock; +} + +int snd_gf1_mem_xfree(struct snd_gf1_mem * alloc, struct snd_gf1_mem_block * block) +{ + if (block->share) { /* ok.. shared block */ + block->share--; + mutex_unlock(&alloc->memory_mutex); + return 0; + } + if (alloc->first == block) { + alloc->first = block->next; + if (block->next) + block->next->prev = NULL; + } else { + block->prev->next = block->next; + if (block->next) + block->next->prev = block->prev; + } + if (alloc->last == block) { + alloc->last = block->prev; + if (block->prev) + block->prev->next = NULL; + } else { + block->next->prev = block->prev; + if (block->prev) + block->prev->next = block->next; + } + kfree(block->name); + kfree(block); + return 0; +} + +static struct snd_gf1_mem_block *snd_gf1_mem_look(struct snd_gf1_mem * alloc, + unsigned int address) +{ + struct snd_gf1_mem_block *block; + + for (block = alloc->first; block; block = block->next) { + if (block->ptr == address) { + return block; + } + } + return NULL; +} + +static struct snd_gf1_mem_block *snd_gf1_mem_share(struct snd_gf1_mem * alloc, + unsigned int *share_id) +{ + struct snd_gf1_mem_block *block; + + if (!share_id[0] && !share_id[1] && + !share_id[2] && !share_id[3]) + return NULL; + for (block = alloc->first; block; block = block->next) + if (!memcmp(share_id, block->share_id, + sizeof(block->share_id))) + return block; + return NULL; +} + +static int snd_gf1_mem_find(struct snd_gf1_mem * alloc, + struct snd_gf1_mem_block * block, + unsigned int size, int w_16, int align) +{ + struct snd_gf1_bank_info *info = w_16 ? alloc->banks_16 : alloc->banks_8; + unsigned int idx, boundary; + int size1; + struct snd_gf1_mem_block *pblock; + unsigned int ptr1, ptr2; + + if (w_16 && align < 2) + align = 2; + block->flags = w_16 ? SNDRV_GF1_MEM_BLOCK_16BIT : 0; + block->owner = SNDRV_GF1_MEM_OWNER_DRIVER; + block->share = 0; + block->share_id[0] = block->share_id[1] = + block->share_id[2] = block->share_id[3] = 0; + block->name = NULL; + block->prev = block->next = NULL; + for (pblock = alloc->first, idx = 0; pblock; pblock = pblock->next) { + while (pblock->ptr >= (boundary = info[idx].address + info[idx].size)) + idx++; + while (pblock->ptr + pblock->size >= (boundary = info[idx].address + info[idx].size)) + idx++; + ptr2 = boundary; + if (pblock->next) { + if (pblock->ptr + pblock->size == pblock->next->ptr) + continue; + if (pblock->next->ptr < boundary) + ptr2 = pblock->next->ptr; + } + ptr1 = ALIGN(pblock->ptr + pblock->size, align); + if (ptr1 >= ptr2) + continue; + size1 = ptr2 - ptr1; + if ((int)size <= size1) { + block->ptr = ptr1; + block->size = size; + return 0; + } + } + while (++idx < 4) { + if (size <= info[idx].size) { + /* I assume that bank address is already aligned.. */ + block->ptr = info[idx].address; + block->size = size; + return 0; + } + } + return -ENOMEM; +} + +struct snd_gf1_mem_block *snd_gf1_mem_alloc(struct snd_gf1_mem * alloc, int owner, + char *name, int size, int w_16, int align, + unsigned int *share_id) +{ + struct snd_gf1_mem_block block, *nblock; + + snd_gf1_mem_lock(alloc, 0); + if (share_id != NULL) { + nblock = snd_gf1_mem_share(alloc, share_id); + if (nblock != NULL) { + if (size != (int)nblock->size) { + /* TODO: remove in the future */ + snd_printk(KERN_ERR "snd_gf1_mem_alloc - share: sizes differ\n"); + goto __std; + } + nblock->share++; + snd_gf1_mem_lock(alloc, 1); + return NULL; + } + } + __std: + if (snd_gf1_mem_find(alloc, &block, size, w_16, align) < 0) { + snd_gf1_mem_lock(alloc, 1); + return NULL; + } + if (share_id != NULL) + memcpy(&block.share_id, share_id, sizeof(block.share_id)); + block.owner = owner; + block.name = kstrdup(name, GFP_KERNEL); + nblock = snd_gf1_mem_xalloc(alloc, &block); + snd_gf1_mem_lock(alloc, 1); + return nblock; +} + +int snd_gf1_mem_free(struct snd_gf1_mem * alloc, unsigned int address) +{ + int result; + struct snd_gf1_mem_block *block; + + snd_gf1_mem_lock(alloc, 0); + if ((block = snd_gf1_mem_look(alloc, address)) != NULL) { + result = snd_gf1_mem_xfree(alloc, block); + snd_gf1_mem_lock(alloc, 1); + return result; + } + snd_gf1_mem_lock(alloc, 1); + return -EINVAL; +} + +int snd_gf1_mem_init(struct snd_gus_card * gus) +{ + struct snd_gf1_mem *alloc; + struct snd_gf1_mem_block block; +#ifdef CONFIG_SND_DEBUG + struct snd_info_entry *entry; +#endif + + alloc = &gus->gf1.mem_alloc; + mutex_init(&alloc->memory_mutex); + alloc->first = alloc->last = NULL; + if (!gus->gf1.memory) + return 0; + + memset(&block, 0, sizeof(block)); + block.owner = SNDRV_GF1_MEM_OWNER_DRIVER; + if (gus->gf1.enh_mode) { + block.ptr = 0; + block.size = 1024; + block.name = kstrdup("InterWave LFOs", GFP_KERNEL); + if (snd_gf1_mem_xalloc(alloc, &block) == NULL) + return -ENOMEM; + } + block.ptr = gus->gf1.default_voice_address; + block.size = 4; + block.name = kstrdup("Voice default (NULL's)", GFP_KERNEL); + if (snd_gf1_mem_xalloc(alloc, &block) == NULL) + return -ENOMEM; +#ifdef CONFIG_SND_DEBUG + if (! snd_card_proc_new(gus->card, "gusmem", &entry)) + snd_info_set_text_ops(entry, gus, snd_gf1_mem_info_read); +#endif + return 0; +} + +int snd_gf1_mem_done(struct snd_gus_card * gus) +{ + struct snd_gf1_mem *alloc; + struct snd_gf1_mem_block *block, *nblock; + + alloc = &gus->gf1.mem_alloc; + block = alloc->first; + while (block) { + nblock = block->next; + snd_gf1_mem_xfree(alloc, block); + block = nblock; + } + return 0; +} + +#ifdef CONFIG_SND_DEBUG +static void snd_gf1_mem_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_gus_card *gus; + struct snd_gf1_mem *alloc; + struct snd_gf1_mem_block *block; + unsigned int total, used; + int i; + + gus = entry->private_data; + alloc = &gus->gf1.mem_alloc; + mutex_lock(&alloc->memory_mutex); + snd_iprintf(buffer, "8-bit banks : \n "); + for (i = 0; i < 4; i++) + snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_8[i].address, alloc->banks_8[i].size >> 10, i + 1 < 4 ? "," : ""); + snd_iprintf(buffer, "\n" + "16-bit banks : \n "); + for (i = total = 0; i < 4; i++) { + snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_16[i].address, alloc->banks_16[i].size >> 10, i + 1 < 4 ? "," : ""); + total += alloc->banks_16[i].size; + } + snd_iprintf(buffer, "\n"); + used = 0; + for (block = alloc->first, i = 0; block; block = block->next, i++) { + used += block->size; + snd_iprintf(buffer, "Block %i at 0x%lx onboard 0x%x size %i (0x%x):\n", i, (long) block, block->ptr, block->size, block->size); + if (block->share || + block->share_id[0] || block->share_id[1] || + block->share_id[2] || block->share_id[3]) + snd_iprintf(buffer, " Share : %i [id0 0x%x] [id1 0x%x] [id2 0x%x] [id3 0x%x]\n", + block->share, + block->share_id[0], block->share_id[1], + block->share_id[2], block->share_id[3]); + snd_iprintf(buffer, " Flags :%s\n", + block->flags & SNDRV_GF1_MEM_BLOCK_16BIT ? " 16-bit" : ""); + snd_iprintf(buffer, " Owner : "); + switch (block->owner) { + case SNDRV_GF1_MEM_OWNER_DRIVER: + snd_iprintf(buffer, "driver - %s\n", block->name); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE: + snd_iprintf(buffer, "SIMPLE wave\n"); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_GF1: + snd_iprintf(buffer, "GF1 wave\n"); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF: + snd_iprintf(buffer, "IWFFFF wave\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + } + } + snd_iprintf(buffer, " Total: memory = %i, used = %i, free = %i\n", + total, used, total - used); + mutex_unlock(&alloc->memory_mutex); +#if 0 + ultra_iprintf(buffer, " Verify: free = %i, max 8-bit block = %i, max 16-bit block = %i\n", + ultra_memory_free_size(card, &card->gf1.mem_alloc), + ultra_memory_free_block(card, &card->gf1.mem_alloc, 0), + ultra_memory_free_block(card, &card->gf1.mem_alloc, 1)); +#endif +} +#endif diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c new file mode 100644 index 000000000..2ccb3fadd --- /dev/null +++ b/sound/isa/gus/gus_mem_proc.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) by Jaroslav Kysela + * GUS's memory access via proc filesystem + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +struct gus_proc_private { + int rom; /* data are in ROM */ + unsigned int address; + unsigned int size; + struct snd_gus_card * gus; +}; + +static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct gus_proc_private *priv = entry->private_data; + struct snd_gus_card *gus = priv->gus; + int err; + + err = snd_gus_dram_read(gus, buf, pos, count, priv->rom); + if (err < 0) + return err; + return count; +} + +static void snd_gf1_mem_proc_free(struct snd_info_entry *entry) +{ + struct gus_proc_private *priv = entry->private_data; + kfree(priv); +} + +static struct snd_info_entry_ops snd_gf1_mem_proc_ops = { + .read = snd_gf1_mem_proc_dump, +}; + +int snd_gf1_mem_proc_init(struct snd_gus_card * gus) +{ + int idx; + char name[16]; + struct gus_proc_private *priv; + struct snd_info_entry *entry; + + for (idx = 0; idx < 4; idx++) { + if (gus->gf1.mem_alloc.banks_8[idx].size > 0) { + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + priv->gus = gus; + sprintf(name, "gus-ram-%i", idx); + if (! snd_card_proc_new(gus->card, name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = priv; + entry->private_free = snd_gf1_mem_proc_free; + entry->c.ops = &snd_gf1_mem_proc_ops; + priv->address = gus->gf1.mem_alloc.banks_8[idx].address; + priv->size = entry->size = gus->gf1.mem_alloc.banks_8[idx].size; + } + } + } + for (idx = 0; idx < 4; idx++) { + if (gus->gf1.rom_present & (1 << idx)) { + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + priv->rom = 1; + priv->gus = gus; + sprintf(name, "gus-rom-%i", idx); + if (! snd_card_proc_new(gus->card, name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = priv; + entry->private_free = snd_gf1_mem_proc_free; + entry->c.ops = &snd_gf1_mem_proc_ops; + priv->address = idx * 4096 * 1024; + priv->size = entry->size = gus->gf1.rom_memory; + } + } + } + return 0; +} diff --git a/sound/isa/gus/gus_mixer.c b/sound/isa/gus/gus_mixer.c new file mode 100644 index 000000000..3b5d9a7a6 --- /dev/null +++ b/sound/isa/gus/gus_mixer.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of ICS 2101 chip and "mixer" in GF1 chip + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +/* + * + */ + +#define GF1_SINGLE(xname, xindex, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_gf1_info_single, \ + .get = snd_gf1_get_single, .put = snd_gf1_put_single, \ + .private_value = shift | (invert << 8) } + +#define snd_gf1_info_single snd_ctl_boolean_mono_info + +static int snd_gf1_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + + ucontrol->value.integer.value[0] = (gus->mix_cntrl_reg >> shift) & 1; + if (invert) + ucontrol->value.integer.value[0] ^= 1; + return 0; +} + +static int snd_gf1_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + int change; + unsigned char oval, nval; + + nval = ucontrol->value.integer.value[0] & 1; + if (invert) + nval ^= 1; + nval <<= shift; + spin_lock_irqsave(&gus->reg_lock, flags); + oval = gus->mix_cntrl_reg; + nval = (oval & ~(1 << shift)) | nval; + change = nval != oval; + outb(gus->mix_cntrl_reg = nval, GUSP(gus, MIXCNTRLREG)); + outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return change; +} + +#define ICS_DOUBLE(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ics_info_double, \ + .get = snd_ics_get_double, .put = snd_ics_put_double, \ + .private_value = addr } + +static int snd_ics_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int snd_ics_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int addr = kcontrol->private_value & 0xff; + unsigned char left, right; + + spin_lock_irqsave(&gus->reg_lock, flags); + left = gus->gf1.ics_regs[addr][0]; + right = gus->gf1.ics_regs[addr][1]; + spin_unlock_irqrestore(&gus->reg_lock, flags); + ucontrol->value.integer.value[0] = left & 127; + ucontrol->value.integer.value[1] = right & 127; + return 0; +} + +static int snd_ics_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int addr = kcontrol->private_value & 0xff; + int change; + unsigned char val1, val2, oval1, oval2; + + val1 = ucontrol->value.integer.value[0] & 127; + val2 = ucontrol->value.integer.value[1] & 127; + spin_lock_irqsave(&gus->reg_lock, flags); + oval1 = gus->gf1.ics_regs[addr][0]; + oval2 = gus->gf1.ics_regs[addr][1]; + change = val1 != oval1 || val2 != oval2; + gus->gf1.ics_regs[addr][0] = val1; + gus->gf1.ics_regs[addr][1] = val2; + if (gus->ics_flag && gus->ics_flipped && + (addr == SNDRV_ICS_GF1_DEV || addr == SNDRV_ICS_MASTER_DEV)) + swap(val1, val2); + addr <<= 3; + outb(addr | 0, GUSP(gus, MIXCNTRLPORT)); + outb(1, GUSP(gus, MIXDATAPORT)); + outb(addr | 2, GUSP(gus, MIXCNTRLPORT)); + outb((unsigned char) val1, GUSP(gus, MIXDATAPORT)); + outb(addr | 1, GUSP(gus, MIXCNTRLPORT)); + outb(2, GUSP(gus, MIXDATAPORT)); + outb(addr | 3, GUSP(gus, MIXCNTRLPORT)); + outb((unsigned char) val2, GUSP(gus, MIXDATAPORT)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return change; +} + +static struct snd_kcontrol_new snd_gf1_controls[] = { +GF1_SINGLE("Master Playback Switch", 0, 1, 1), +GF1_SINGLE("Line Switch", 0, 0, 1), +GF1_SINGLE("Mic Switch", 0, 2, 0) +}; + +static struct snd_kcontrol_new snd_ics_controls[] = { +GF1_SINGLE("Master Playback Switch", 0, 1, 1), +ICS_DOUBLE("Master Playback Volume", 0, SNDRV_ICS_MASTER_DEV), +ICS_DOUBLE("Synth Playback Volume", 0, SNDRV_ICS_GF1_DEV), +GF1_SINGLE("Line Switch", 0, 0, 1), +ICS_DOUBLE("Line Playback Volume", 0, SNDRV_ICS_LINE_DEV), +GF1_SINGLE("Mic Switch", 0, 2, 0), +ICS_DOUBLE("Mic Playback Volume", 0, SNDRV_ICS_MIC_DEV), +ICS_DOUBLE("CD Playback Volume", 0, SNDRV_ICS_CD_DEV) +}; + +int snd_gf1_new_mixer(struct snd_gus_card * gus) +{ + struct snd_card *card; + unsigned int idx, max; + int err; + + if (snd_BUG_ON(!gus)) + return -EINVAL; + card = gus->card; + if (snd_BUG_ON(!card)) + return -EINVAL; + + if (gus->ics_flag) + snd_component_add(card, "ICS2101"); + if (card->mixername[0] == '\0') { + strcpy(card->mixername, gus->ics_flag ? "GF1,ICS2101" : "GF1"); + } else { + if (gus->ics_flag) + strcat(card->mixername, ",ICS2101"); + strcat(card->mixername, ",GF1"); + } + + if (!gus->ics_flag) { + max = gus->ess_flag ? 1 : ARRAY_SIZE(snd_gf1_controls); + for (idx = 0; idx < max; idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_gf1_controls[idx], gus))) < 0) + return err; + } + } else { + for (idx = 0; idx < ARRAY_SIZE(snd_ics_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ics_controls[idx], gus))) < 0) + return err; + } + } + return 0; +} diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c new file mode 100644 index 000000000..131b28997 --- /dev/null +++ b/sound/isa/gus/gus_pcm.c @@ -0,0 +1,924 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of GF1 chip (PCM things) + * + * InterWave chips supports interleaved DMA, but this feature isn't used in + * this code. + * + * This code emulates autoinit DMA transfer for playback, recording by GF1 + * chip doesn't support autoinit DMA. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include "gus_tables.h" + +/* maximum rate */ + +#define SNDRV_GF1_PCM_RATE 48000 + +#define SNDRV_GF1_PCM_PFLG_NONE 0 +#define SNDRV_GF1_PCM_PFLG_ACTIVE (1<<0) +#define SNDRV_GF1_PCM_PFLG_NEUTRAL (2<<0) + +struct gus_pcm_private { + struct snd_gus_card * gus; + struct snd_pcm_substream *substream; + spinlock_t lock; + unsigned int voices; + struct snd_gus_voice *pvoices[2]; + unsigned int memory; + unsigned short flags; + unsigned char voice_ctrl, ramp_ctrl; + unsigned int bpos; + unsigned int blocks; + unsigned int block_size; + unsigned int dma_size; + wait_queue_head_t sleep; + atomic_t dma_count; + int final_volume; +}; + +static void snd_gf1_pcm_block_change_ack(struct snd_gus_card * gus, void *private_data) +{ + struct gus_pcm_private *pcmp = private_data; + + if (pcmp) { + atomic_dec(&pcmp->dma_count); + wake_up(&pcmp->sleep); + } +} + +static int snd_gf1_pcm_block_change(struct snd_pcm_substream *substream, + unsigned int offset, + unsigned int addr, + unsigned int count) +{ + struct snd_gf1_dma_block block; + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + count += offset & 31; + offset &= ~31; + /* + snd_printk(KERN_DEBUG "block change - offset = 0x%x, count = 0x%x\n", + offset, count); + */ + memset(&block, 0, sizeof(block)); + block.cmd = SNDRV_GF1_DMA_IRQ; + if (snd_pcm_format_unsigned(runtime->format)) + block.cmd |= SNDRV_GF1_DMA_UNSIGNED; + if (snd_pcm_format_width(runtime->format) == 16) + block.cmd |= SNDRV_GF1_DMA_16BIT; + block.addr = addr & ~31; + block.buffer = runtime->dma_area + offset; + block.buf_addr = runtime->dma_addr + offset; + block.count = count; + block.private_data = pcmp; + block.ack = snd_gf1_pcm_block_change_ack; + if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0)) + atomic_inc(&pcmp->dma_count); + return 0; +} + +static void snd_gf1_pcm_trigger_up(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + struct snd_gus_card * gus = pcmp->gus; + unsigned long flags; + unsigned char voice_ctrl, ramp_ctrl; + unsigned short rate; + unsigned int curr, begin, end; + unsigned short vol; + unsigned char pan; + unsigned int voice; + + spin_lock_irqsave(&pcmp->lock, flags); + if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) { + spin_unlock_irqrestore(&pcmp->lock, flags); + return; + } + pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE; + pcmp->final_volume = 0; + spin_unlock_irqrestore(&pcmp->lock, flags); + rate = snd_gf1_translate_freq(gus, runtime->rate << 4); + /* enable WAVE IRQ */ + voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20; + /* enable RAMP IRQ + rollover */ + ramp_ctrl = 0x24; + if (pcmp->blocks == 1) { + voice_ctrl |= 0x08; /* loop enable */ + ramp_ctrl &= ~0x04; /* disable rollover */ + } + for (voice = 0; voice < pcmp->voices; voice++) { + begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels); + curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels; + end = curr + (pcmp->block_size / runtime->channels); + end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1; + /* + snd_printk(KERN_DEBUG "init: curr=0x%x, begin=0x%x, end=0x%x, " + "ctrl=0x%x, ramp=0x%x, rate=0x%x\n", + curr, begin, end, voice_ctrl, ramp_ctrl, rate); + */ + pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8; + vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan); + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + } + spin_lock_irqsave(&gus->reg_lock, flags); + for (voice = 0; voice < pcmp->voices; voice++) { + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00); /* deactivate voice */ + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + voice_ctrl &= ~0x20; + } + voice_ctrl |= 0x20; + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + for (voice = 0; voice < pcmp->voices; voice++) { + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + voice_ctrl &= ~0x20; /* disable IRQ for next voice */ + } + } + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static void snd_gf1_pcm_interrupt_wave(struct snd_gus_card * gus, + struct snd_gus_voice *pvoice) +{ + struct gus_pcm_private * pcmp; + struct snd_pcm_runtime *runtime; + unsigned char voice_ctrl, ramp_ctrl; + unsigned int idx; + unsigned int end, step; + + if (!pvoice->private_data) { + snd_printd("snd_gf1_pcm: unknown wave irq?\n"); + snd_gf1_smart_stop_voice(gus, pvoice->number); + return; + } + pcmp = pvoice->private_data; + if (pcmp == NULL) { + snd_printd("snd_gf1_pcm: unknown wave irq?\n"); + snd_gf1_smart_stop_voice(gus, pvoice->number); + return; + } + gus = pcmp->gus; + runtime = pcmp->substream->runtime; + + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b; + ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03; +#if 0 + snd_gf1_select_voice(gus, pvoice->number); + printk(KERN_DEBUG "position = 0x%x\n", + (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); + snd_gf1_select_voice(gus, pcmp->pvoices[1]->number); + printk(KERN_DEBUG "position = 0x%x\n", + (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); + snd_gf1_select_voice(gus, pvoice->number); +#endif + pcmp->bpos++; + pcmp->bpos %= pcmp->blocks; + if (pcmp->bpos + 1 >= pcmp->blocks) { /* last block? */ + voice_ctrl |= 0x08; /* enable loop */ + } else { + ramp_ctrl |= 0x04; /* enable rollover */ + } + end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels); + end -= voice_ctrl & 4 ? 2 : 1; + step = pcmp->dma_size / runtime->channels; + voice_ctrl |= 0x20; + if (!pcmp->final_volume) { + ramp_ctrl |= 0x20; + ramp_ctrl &= ~0x03; + } + for (idx = 0; idx < pcmp->voices; idx++, end += step) { + snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + voice_ctrl &= ~0x20; + } + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + voice_ctrl |= 0x20; + for (idx = 0; idx < pcmp->voices; idx++) { + snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + voice_ctrl &= ~0x20; + } + } + spin_unlock(&gus->reg_lock); + + snd_pcm_period_elapsed(pcmp->substream); +#if 0 + if ((runtime->flags & SNDRV_PCM_FLG_MMAP) && + *runtime->state == SNDRV_PCM_STATE_RUNNING) { + end = pcmp->bpos * pcmp->block_size; + if (runtime->channels > 1) { + snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + (end / 2), pcmp->block_size / 2); + snd_gf1_pcm_block_change(pcmp->substream, end + (pcmp->block_size / 2), pcmp->memory + (pcmp->dma_size / 2) + (end / 2), pcmp->block_size / 2); + } else { + snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + end, pcmp->block_size); + } + } +#endif +} + +static void snd_gf1_pcm_interrupt_volume(struct snd_gus_card * gus, + struct snd_gus_voice * pvoice) +{ + unsigned short vol; + int cvoice; + struct gus_pcm_private *pcmp = pvoice->private_data; + + /* stop ramp, but leave rollover bit untouched */ + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + spin_unlock(&gus->reg_lock); + if (pcmp == NULL) + return; + /* are we active? */ + if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE)) + return; + /* load real volume - better precision */ + cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1; + if (pcmp->substream == NULL) + return; + vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol); + pcmp->final_volume = 1; + spin_unlock(&gus->reg_lock); +} + +static void snd_gf1_pcm_volume_change(struct snd_gus_card * gus) +{ +} + +static int snd_gf1_pcm_poke_block(struct snd_gus_card *gus, unsigned char *buf, + unsigned int pos, unsigned int count, + int w16, int invert) +{ + unsigned int len; + unsigned long flags; + + /* + printk(KERN_DEBUG + "poke block; buf = 0x%x, pos = %i, count = %i, port = 0x%x\n", + (int)buf, pos, count, gus->gf1.port); + */ + while (count > 0) { + len = count; + if (len > 512) /* limit, to allow IRQ */ + len = 512; + count -= len; + if (gus->interwave) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01 | (invert ? 0x08 : 0x00)); + snd_gf1_dram_addr(gus, pos); + if (w16) { + outb(SNDRV_GF1_GW_DRAM_IO16, GUSP(gus, GF1REGSEL)); + outsw(GUSP(gus, GF1DATALOW), buf, len >> 1); + } else { + outsb(GUSP(gus, DRAM), buf, len); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + buf += 512; + pos += 512; + } else { + invert = invert ? 0x80 : 0x00; + if (w16) { + len >>= 1; + while (len--) { + snd_gf1_poke(gus, pos++, *buf++); + snd_gf1_poke(gus, pos++, *buf++ ^ invert); + } + } else { + while (len--) + snd_gf1_poke(gus, pos++, *buf++ ^ invert); + } + } + if (count > 0 && !in_interrupt()) { + schedule_timeout_interruptible(1); + if (signal_pending(current)) + return -EAGAIN; + } + } + return 0; +} + +static int get_bpos(struct gus_pcm_private *pcmp, int voice, unsigned int pos, + unsigned int len) +{ + unsigned int bpos = pos + (voice * (pcmp->dma_size / 2)); + if (snd_BUG_ON(bpos > pcmp->dma_size)) + return -EIO; + if (snd_BUG_ON(bpos + len > pcmp->dma_size)) + return -EIO; + return bpos; +} + +static int playback_copy_ack(struct snd_pcm_substream *substream, + unsigned int bpos, unsigned int len) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + struct snd_gus_card *gus = pcmp->gus; + int w16, invert; + + if (len > 32) + return snd_gf1_pcm_block_change(substream, bpos, + pcmp->memory + bpos, len); + + w16 = (snd_pcm_format_width(runtime->format) == 16); + invert = snd_pcm_format_unsigned(runtime->format); + return snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, + pcmp->memory + bpos, len, w16, invert); +} + +static int snd_gf1_pcm_playback_copy(struct snd_pcm_substream *substream, + int voice, unsigned long pos, + void __user *src, unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + unsigned int len = count; + int bpos; + + bpos = get_bpos(pcmp, voice, pos, len); + if (bpos < 0) + return pos; + if (copy_from_user(runtime->dma_area + bpos, src, len)) + return -EFAULT; + return playback_copy_ack(substream, bpos, len); +} + +static int snd_gf1_pcm_playback_copy_kernel(struct snd_pcm_substream *substream, + int voice, unsigned long pos, + void *src, unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + unsigned int len = count; + int bpos; + + bpos = get_bpos(pcmp, voice, pos, len); + if (bpos < 0) + return pos; + memcpy(runtime->dma_area + bpos, src, len); + return playback_copy_ack(substream, bpos, len); +} + +static int snd_gf1_pcm_playback_silence(struct snd_pcm_substream *substream, + int voice, unsigned long pos, + unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + unsigned int len = count; + int bpos; + + bpos = get_bpos(pcmp, voice, pos, len); + if (bpos < 0) + return pos; + snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos, + bytes_to_samples(runtime, count)); + return playback_copy_ack(substream, bpos, len); +} + +static int snd_gf1_pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + if (err > 0) { /* change */ + struct snd_gf1_mem_block *block; + if (pcmp->memory > 0) { + snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory); + pcmp->memory = 0; + } + if ((block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc, + SNDRV_GF1_MEM_OWNER_DRIVER, + "GF1 PCM", + runtime->dma_bytes, 1, 32, + NULL)) == NULL) + return -ENOMEM; + pcmp->memory = block->ptr; + } + pcmp->voices = params_channels(hw_params); + if (pcmp->pvoices[0] == NULL) { + if ((pcmp->pvoices[0] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL) + return -ENOMEM; + pcmp->pvoices[0]->handler_wave = snd_gf1_pcm_interrupt_wave; + pcmp->pvoices[0]->handler_volume = snd_gf1_pcm_interrupt_volume; + pcmp->pvoices[0]->volume_change = snd_gf1_pcm_volume_change; + pcmp->pvoices[0]->private_data = pcmp; + } + if (pcmp->voices > 1 && pcmp->pvoices[1] == NULL) { + if ((pcmp->pvoices[1] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL) + return -ENOMEM; + pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave; + pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume; + pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change; + pcmp->pvoices[1]->private_data = pcmp; + } else if (pcmp->voices == 1) { + if (pcmp->pvoices[1]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]); + pcmp->pvoices[1] = NULL; + } + } + return 0; +} + +static int snd_gf1_pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + snd_pcm_lib_free_pages(substream); + if (pcmp->pvoices[0]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[0]); + pcmp->pvoices[0] = NULL; + } + if (pcmp->pvoices[1]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]); + pcmp->pvoices[1] = NULL; + } + if (pcmp->memory > 0) { + snd_gf1_mem_free(&pcmp->gus->gf1.mem_alloc, pcmp->memory); + pcmp->memory = 0; + } + return 0; +} + +static int snd_gf1_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + pcmp->bpos = 0; + pcmp->dma_size = snd_pcm_lib_buffer_bytes(substream); + pcmp->block_size = snd_pcm_lib_period_bytes(substream); + pcmp->blocks = pcmp->dma_size / pcmp->block_size; + return 0; +} + +static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + int voice; + + if (cmd == SNDRV_PCM_TRIGGER_START) { + snd_gf1_pcm_trigger_up(substream); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + spin_lock(&pcmp->lock); + pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE; + spin_unlock(&pcmp->lock); + voice = pcmp->pvoices[0]->number; + snd_gf1_stop_voices(gus, voice, voice); + if (pcmp->pvoices[1]) { + voice = pcmp->pvoices[1]->number; + snd_gf1_stop_voices(gus, voice, voice); + } + } else { + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_gf1_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + unsigned int pos; + unsigned char voice_ctrl; + + pos = 0; + spin_lock(&gus->reg_lock); + if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) { + snd_gf1_select_voice(gus, pcmp->pvoices[0]->number); + voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + pos = (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - pcmp->memory; + if (substream->runtime->channels > 1) + pos <<= 1; + pos = bytes_to_frames(runtime, pos); + } + spin_unlock(&gus->reg_lock); + return pos; +} + +static const struct snd_ratnum clock = { + .num = 9878400/16, + .den_min = 2, + .den_max = 257, + .den_step = 1, +}; + +static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = { + .nrats = 1, + .rats = &clock, +}; + +static int snd_gf1_pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + + gus->c_dma_size = params_buffer_bytes(hw_params); + gus->c_period_size = params_period_bytes(hw_params); + gus->c_pos = 0; + gus->gf1.pcm_rcntrl_reg = 0x21; /* IRQ at end, enable & start */ + if (params_channels(hw_params) > 1) + gus->gf1.pcm_rcntrl_reg |= 2; + if (gus->gf1.dma2 > 3) + gus->gf1.pcm_rcntrl_reg |= 4; + if (snd_pcm_format_unsigned(params_format(hw_params))) + gus->gf1.pcm_rcntrl_reg |= 0x80; + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_gf1_pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_gf1_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RECORD_RATE, runtime->rate_den - 2); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */ + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */ + snd_dma_program(gus->gf1.dma2, runtime->dma_addr, gus->c_period_size, DMA_MODE_READ); + return 0; +} + +static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + int val; + + if (cmd == SNDRV_PCM_TRIGGER_START) { + val = gus->gf1.pcm_rcntrl_reg; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + val = 0; + } else { + return -EINVAL; + } + + spin_lock(&gus->reg_lock); + snd_gf1_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, val); + snd_gf1_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); + spin_unlock(&gus->reg_lock); + return 0; +} + +static snd_pcm_uframes_t snd_gf1_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + int pos = snd_dma_pointer(gus->gf1.dma2, gus->c_period_size); + pos = bytes_to_frames(substream->runtime, (gus->c_pos + pos) % gus->c_dma_size); + return pos; +} + +static void snd_gf1_pcm_interrupt_dma_read(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */ + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */ + if (gus->pcm_cap_substream != NULL) { + snd_gf1_pcm_capture_prepare(gus->pcm_cap_substream); + snd_gf1_pcm_capture_trigger(gus->pcm_cap_substream, SNDRV_PCM_TRIGGER_START); + gus->c_pos += gus->c_period_size; + snd_pcm_period_elapsed(gus->pcm_cap_substream); + } +} + +static const struct snd_pcm_hardware snd_gf1_pcm_playback = +{ + .info = SNDRV_PCM_INFO_NONINTERLEAVED, + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_gf1_pcm_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100, + .rate_min = 5510, + .rate_max = 44100, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void snd_gf1_pcm_playback_free(struct snd_pcm_runtime *runtime) +{ + kfree(runtime->private_data); +} + +static int snd_gf1_pcm_playback_open(struct snd_pcm_substream *substream) +{ + struct gus_pcm_private *pcmp; + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + pcmp = kzalloc(sizeof(*pcmp), GFP_KERNEL); + if (pcmp == NULL) + return -ENOMEM; + pcmp->gus = gus; + spin_lock_init(&pcmp->lock); + init_waitqueue_head(&pcmp->sleep); + atomic_set(&pcmp->dma_count, 0); + + runtime->private_data = pcmp; + runtime->private_free = snd_gf1_pcm_playback_free; + +#if 0 + printk(KERN_DEBUG "playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n", + (long) pcm->playback.buffer, (long) gus->gf1.pcm_buffer); +#endif + if ((err = snd_gf1_dma_init(gus)) < 0) + return err; + pcmp->flags = SNDRV_GF1_PCM_PFLG_NONE; + pcmp->substream = substream; + runtime->hw = snd_gf1_pcm_playback; + snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + return 0; +} + +static int snd_gf1_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + if (!wait_event_timeout(pcmp->sleep, (atomic_read(&pcmp->dma_count) <= 0), 2*HZ)) + snd_printk(KERN_ERR "gf1 pcm - serious DMA problem\n"); + + snd_gf1_dma_done(gus); + return 0; +} + +static int snd_gf1_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + + gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read; + gus->pcm_cap_substream = substream; + substream->runtime->hw = snd_gf1_pcm_capture; + snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + return 0; +} + +static int snd_gf1_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + + gus->pcm_cap_substream = NULL; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_READ); + return 0; +} + +static int snd_gf1_pcm_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int snd_gf1_pcm_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&gus->pcm_volume_level_lock, flags); + ucontrol->value.integer.value[0] = gus->gf1.pcm_volume_level_left1; + ucontrol->value.integer.value[1] = gus->gf1.pcm_volume_level_right1; + spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags); + return 0; +} + +static int snd_gf1_pcm_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned int idx; + unsigned short val1, val2, vol; + struct gus_pcm_private *pcmp; + struct snd_gus_voice *pvoice; + + val1 = ucontrol->value.integer.value[0] & 127; + val2 = ucontrol->value.integer.value[1] & 127; + spin_lock_irqsave(&gus->pcm_volume_level_lock, flags); + change = val1 != gus->gf1.pcm_volume_level_left1 || + val2 != gus->gf1.pcm_volume_level_right1; + gus->gf1.pcm_volume_level_left1 = val1; + gus->gf1.pcm_volume_level_right1 = val2; + gus->gf1.pcm_volume_level_left = snd_gf1_lvol_to_gvol_raw(val1 << 9) << 4; + gus->gf1.pcm_volume_level_right = snd_gf1_lvol_to_gvol_raw(val2 << 9) << 4; + spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags); + /* are we active? */ + spin_lock_irqsave(&gus->voice_alloc, flags); + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (!pvoice->pcm) + continue; + pcmp = pvoice->private_data; + if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE)) + continue; + /* load real volume - better precision */ + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + vol = pvoice == pcmp->pvoices[0] ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol); + pcmp->final_volume = 1; + spin_unlock(&gus->reg_lock); + } + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return change; +} + +static const struct snd_kcontrol_new snd_gf1_pcm_volume_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = snd_gf1_pcm_volume_info, + .get = snd_gf1_pcm_volume_get, + .put = snd_gf1_pcm_volume_put +}; + +static const struct snd_kcontrol_new snd_gf1_pcm_volume_control1 = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "GPCM Playback Volume", + .info = snd_gf1_pcm_volume_info, + .get = snd_gf1_pcm_volume_get, + .put = snd_gf1_pcm_volume_put +}; + +static const struct snd_pcm_ops snd_gf1_pcm_playback_ops = { + .open = snd_gf1_pcm_playback_open, + .close = snd_gf1_pcm_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_gf1_pcm_playback_hw_params, + .hw_free = snd_gf1_pcm_playback_hw_free, + .prepare = snd_gf1_pcm_playback_prepare, + .trigger = snd_gf1_pcm_playback_trigger, + .pointer = snd_gf1_pcm_playback_pointer, + .copy_user = snd_gf1_pcm_playback_copy, + .copy_kernel = snd_gf1_pcm_playback_copy_kernel, + .fill_silence = snd_gf1_pcm_playback_silence, +}; + +static const struct snd_pcm_ops snd_gf1_pcm_capture_ops = { + .open = snd_gf1_pcm_capture_open, + .close = snd_gf1_pcm_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_gf1_pcm_capture_hw_params, + .hw_free = snd_gf1_pcm_capture_hw_free, + .prepare = snd_gf1_pcm_capture_prepare, + .trigger = snd_gf1_pcm_capture_trigger, + .pointer = snd_gf1_pcm_capture_pointer, +}; + +int snd_gf1_pcm_new(struct snd_gus_card *gus, int pcm_dev, int control_index) +{ + struct snd_card *card; + struct snd_kcontrol *kctl; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + int capture, err; + + card = gus->card; + capture = !gus->interwave && !gus->ess_flag && !gus->ace_flag ? 1 : 0; + err = snd_pcm_new(card, + gus->interwave ? "AMD InterWave" : "GF1", + pcm_dev, + gus->gf1.pcm_channels / 2, + capture, + &pcm); + if (err < 0) + return err; + pcm->private_data = gus; + /* playback setup */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_gf1_pcm_playback_ops); + + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) + snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, gus->gf1.dma1 > 3 ? 128*1024 : 64*1024); + + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + if (capture) { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_gf1_pcm_capture_ops); + if (gus->gf1.dma2 == gus->gf1.dma1) + pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX; + snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV, snd_dma_isa_data(), + 64*1024, gus->gf1.dma2 > 3 ? 128*1024 : 64*1024); + } + strcpy(pcm->name, pcm->id); + if (gus->interwave) { + sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A'); + } + strcat(pcm->name, " (synth)"); + gus->pcm = pcm; + + if (gus->codec_flag) + kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control1, gus); + else + kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control, gus); + if ((err = snd_ctl_add(card, kctl)) < 0) + return err; + kctl->id.index = control_index; + + return 0; +} + diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c new file mode 100644 index 000000000..59b3f683d --- /dev/null +++ b/sound/isa/gus/gus_reset.c @@ -0,0 +1,411 @@ +/* + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +extern void snd_gf1_timers_init(struct snd_gus_card * gus); +extern void snd_gf1_timers_done(struct snd_gus_card * gus); +extern int snd_gf1_synth_init(struct snd_gus_card * gus); +extern void snd_gf1_synth_done(struct snd_gus_card * gus); + +/* + * ok.. default interrupt handlers... + */ + +static void snd_gf1_default_interrupt_handler_midi_out(struct snd_gus_card * gus) +{ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x20); +} + +static void snd_gf1_default_interrupt_handler_midi_in(struct snd_gus_card * gus) +{ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x80); +} + +static void snd_gf1_default_interrupt_handler_timer1(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~4); +} + +static void snd_gf1_default_interrupt_handler_timer2(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~8); +} + +static void snd_gf1_default_interrupt_handler_wave_and_volume(struct snd_gus_card * gus, struct snd_gus_voice * voice) +{ + snd_gf1_i_ctrl_stop(gus, 0x00); + snd_gf1_i_ctrl_stop(gus, 0x0d); +} + +static void snd_gf1_default_interrupt_handler_dma_write(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, 0x41, 0x00); +} + +static void snd_gf1_default_interrupt_handler_dma_read(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, 0x49, 0x00); +} + +void snd_gf1_set_default_handlers(struct snd_gus_card * gus, unsigned int what) +{ + if (what & SNDRV_GF1_HANDLER_MIDI_OUT) + gus->gf1.interrupt_handler_midi_out = snd_gf1_default_interrupt_handler_midi_out; + if (what & SNDRV_GF1_HANDLER_MIDI_IN) + gus->gf1.interrupt_handler_midi_in = snd_gf1_default_interrupt_handler_midi_in; + if (what & SNDRV_GF1_HANDLER_TIMER1) + gus->gf1.interrupt_handler_timer1 = snd_gf1_default_interrupt_handler_timer1; + if (what & SNDRV_GF1_HANDLER_TIMER2) + gus->gf1.interrupt_handler_timer2 = snd_gf1_default_interrupt_handler_timer2; + if (what & SNDRV_GF1_HANDLER_VOICE) { + struct snd_gus_voice *voice; + + voice = &gus->gf1.voices[what & 0xffff]; + voice->handler_wave = + voice->handler_volume = snd_gf1_default_interrupt_handler_wave_and_volume; + voice->handler_effect = NULL; + voice->volume_change = NULL; + } + if (what & SNDRV_GF1_HANDLER_DMA_WRITE) + gus->gf1.interrupt_handler_dma_write = snd_gf1_default_interrupt_handler_dma_write; + if (what & SNDRV_GF1_HANDLER_DMA_READ) + gus->gf1.interrupt_handler_dma_read = snd_gf1_default_interrupt_handler_dma_read; +} + +/* + + */ + +static void snd_gf1_clear_regs(struct snd_gus_card * gus) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + inb(GUSP(gus, IRQSTAT)); + snd_gf1_write8(gus, 0x41, 0); /* DRAM DMA Control Register */ + snd_gf1_write8(gus, 0x45, 0); /* Timer Control */ + snd_gf1_write8(gus, 0x49, 0); /* Sampling Control Register */ + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static void snd_gf1_look_regs(struct snd_gus_card * gus) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_look8(gus, 0x41); /* DRAM DMA Control Register */ + snd_gf1_look8(gus, 0x49); /* Sampling Control Register */ + inb(GUSP(gus, IRQSTAT)); + snd_gf1_read8(gus, 0x0f); /* IRQ Source Register */ + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +/* + * put selected GF1 voices to initial stage... + */ + +void snd_gf1_smart_stop_voice(struct snd_gus_card * gus, unsigned short voice) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice); +#if 0 + printk(KERN_DEBUG " -%i- smart stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME)); +#endif + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +void snd_gf1_stop_voice(struct snd_gus_card * gus, unsigned short voice) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice); +#if 0 + printk(KERN_DEBUG " -%i- stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME)); +#endif + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0); + spin_unlock_irqrestore(&gus->reg_lock, flags); +#if 0 + snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_VIBRATO); + snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_TREMOLO); +#endif +} + +static void snd_gf1_clear_voices(struct snd_gus_card * gus, unsigned short v_min, + unsigned short v_max) +{ + unsigned long flags; + unsigned int daddr; + unsigned short i, w_16; + + daddr = gus->gf1.default_voice_address << 4; + for (i = v_min; i <= v_max; i++) { +#if 0 + if (gus->gf1.syn_voices) + gus->gf1.syn_voices[i].flags = ~VFLG_DYNAMIC; +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, i); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); /* Voice Control Register = voice stop */ + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); /* Volume Ramp Control Register = ramp off */ + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, gus->gf1.memory ? 0x02 : 0x82); /* Deactivate voice */ + w_16 = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & 0x04; + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, 0x400); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, daddr, w_16); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, daddr, w_16); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, 0); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, 0); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, daddr, w_16); + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, 7); + if (gus->gf1.enh_mode) { + snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, 0); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); +#if 0 + snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_VIBRATO); + snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_TREMOLO); +#endif + } +} + +void snd_gf1_stop_voices(struct snd_gus_card * gus, unsigned short v_min, unsigned short v_max) +{ + unsigned long flags; + short i, ramp_ok; + unsigned short ramp_end; + + if (!in_interrupt()) { /* this can't be done in interrupt */ + for (i = v_min, ramp_ok = 0; i <= v_max; i++) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, i); + ramp_end = snd_gf1_read16(gus, 9) >> 8; + if (ramp_end > SNDRV_GF1_MIN_OFFSET) { + ramp_ok++; + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 20); /* ramp rate */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); /* ramp start */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, ramp_end); /* ramp end */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40); /* ramp down */ + if (gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40); + } + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + } + msleep_interruptible(50); + } + snd_gf1_clear_voices(gus, v_min, v_max); +} + +static void snd_gf1_alloc_voice_use(struct snd_gus_card * gus, + struct snd_gus_voice * pvoice, + int type, int client, int port) +{ + pvoice->use = 1; + switch (type) { + case SNDRV_GF1_VOICE_TYPE_PCM: + gus->gf1.pcm_alloc_voices++; + pvoice->pcm = 1; + break; + case SNDRV_GF1_VOICE_TYPE_SYNTH: + pvoice->synth = 1; + pvoice->client = client; + pvoice->port = port; + break; + case SNDRV_GF1_VOICE_TYPE_MIDI: + pvoice->midi = 1; + pvoice->client = client; + pvoice->port = port; + break; + } +} + +struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, int client, int port) +{ + struct snd_gus_voice *pvoice; + unsigned long flags; + int idx; + + spin_lock_irqsave(&gus->voice_alloc, flags); + if (type == SNDRV_GF1_VOICE_TYPE_PCM) { + if (gus->gf1.pcm_alloc_voices >= gus->gf1.pcm_channels) { + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return NULL; + } + } + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (!pvoice->use) { + snd_gf1_alloc_voice_use(gus, pvoice, type, client, port); + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return pvoice; + } + } + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (pvoice->midi && !pvoice->client) { + snd_gf1_clear_voices(gus, pvoice->number, pvoice->number); + snd_gf1_alloc_voice_use(gus, pvoice, type, client, port); + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return pvoice; + } + } + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return NULL; +} + +void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice) +{ + unsigned long flags; + void (*private_free)(struct snd_gus_voice *voice); + + if (voice == NULL || !voice->use) + return; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | voice->number); + snd_gf1_clear_voices(gus, voice->number, voice->number); + spin_lock_irqsave(&gus->voice_alloc, flags); + private_free = voice->private_free; + voice->private_free = NULL; + voice->private_data = NULL; + if (voice->pcm) + gus->gf1.pcm_alloc_voices--; + voice->use = voice->pcm = 0; + voice->sample_ops = NULL; + spin_unlock_irqrestore(&gus->voice_alloc, flags); + if (private_free) + private_free(voice); +} + +/* + * call this function only by start of driver + */ + +int snd_gf1_start(struct snd_gus_card * gus) +{ + unsigned long flags; + unsigned int i; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac); + + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL); + for (i = 0; i < 32; i++) { + gus->gf1.voices[i].number = i; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i); + } + + snd_gf1_uart_cmd(gus, 0x03); /* huh.. this cleanup took me some time... */ + + if (gus->gf1.enh_mode) { /* enhanced mode !!!! */ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + } + snd_gf1_clear_regs(gus); + snd_gf1_select_active_voices(gus); + snd_gf1_delay(gus); + gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8; + /* initialize LFOs & clear LFOs memory */ + if (gus->gf1.enh_mode && gus->gf1.memory) { + gus->gf1.hw_lfo = 1; + gus->gf1.default_voice_address += 1024; + } else { + gus->gf1.sw_lfo = 1; + } +#if 0 + snd_gf1_lfo_init(gus); +#endif + if (gus->gf1.memory > 0) + for (i = 0; i < 4; i++) + snd_gf1_poke(gus, gus->gf1.default_voice_address + i, 0); + snd_gf1_clear_regs(gus); + snd_gf1_clear_voices(gus, 0, 31); + snd_gf1_look_regs(gus); + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */ + if (gus->gf1.enh_mode) { /* enhanced mode !!!! */ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + } + while ((snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ) & 0xc0) != 0xc0); + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE)); + outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + + snd_gf1_timers_init(gus); + snd_gf1_look_regs(gus); + snd_gf1_mem_init(gus); + snd_gf1_mem_proc_init(gus); +#ifdef CONFIG_SND_DEBUG + snd_gus_irq_profile_init(gus); +#endif + +#if 0 + if (gus->pnp_flag) { + if (gus->chip.playback_fifo_size > 0) + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR, gus->chip.playback_fifo_block->ptr >> 8); + if (gus->chip.record_fifo_size > 0) + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR, gus->chip.record_fifo_block->ptr >> 8); + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_SIZE, gus->chip.interwave_fifo_reg); + } +#endif + + return 0; +} + +/* + * call this function only by shutdown of driver + */ + +int snd_gf1_stop(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); /* stop all timers */ + snd_gf1_stop_voices(gus, 0, 31); /* stop all voices */ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */ + snd_gf1_timers_done(gus); + snd_gf1_mem_done(gus); +#if 0 + snd_gf1_lfo_done(gus); +#endif + return 0; +} diff --git a/sound/isa/gus/gus_tables.h b/sound/isa/gus/gus_tables.h new file mode 100644 index 000000000..42a4ca0d6 --- /dev/null +++ b/sound/isa/gus/gus_tables.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define SNDRV_GF1_SCALE_TABLE_SIZE 128 +#define SNDRV_GF1_ATTEN_TABLE_SIZE 128 + +#ifdef __GUS_TABLES_ALLOC__ + +#if 0 + +unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE] = +{ + 8372, 8870, 9397, 9956, 10548, 11175, + 11840, 12544, 13290, 14080, 14917, 15804, + 16744, 17740, 18795, 19912, 21096, 22351, + 23680, 25088, 26580, 28160, 29834, 31609, + 33488, 35479, 37589, 39824, 42192, 44701, + 47359, 50175, 53159, 56320, 59669, 63217, + 66976, 70959, 75178, 79649, 84385, 89402, + 94719, 100351, 106318, 112640, 119338, 126434, + 133952, 141918, 150356, 159297, 168769, 178805, + 189437, 200702, 212636, 225280, 238676, 252868, + 267905, 283835, 300713, 318594, 337539, 357610, + 378874, 401403, 425272, 450560, 477352, 505737, + 535809, 567670, 601425, 637188, 675077, 715219, + 757749, 802807, 850544, 901120, 954703, 1011473, + 1071618, 1135340, 1202851, 1274376, 1350154, 1430439, + 1515497, 1605613, 1701088, 1802240, 1909407, 2022946, + 2143237, 2270680, 2405702, 2548752, 2700309, 2860878, + 3030994, 3211227, 3402176, 3604480, 3818814, 4045892, + 4286473, 4541360, 4811404, 5097505, 5400618, 5721755, + 6061989, 6422453, 6804352, 7208960, 7637627, 8091784, + 8572947, 9082720, 9622807, 10195009, 10801236, 11443511, + 12123977, 12844906 +}; + +#endif /* 0 */ + +unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE] = { + 4095 /* 0 */,1789 /* 1 */,1533 /* 2 */,1383 /* 3 */,1277 /* 4 */, + 1195 /* 5 */,1127 /* 6 */,1070 /* 7 */,1021 /* 8 */,978 /* 9 */, + 939 /* 10 */,903 /* 11 */,871 /* 12 */,842 /* 13 */,814 /* 14 */, + 789 /* 15 */,765 /* 16 */,743 /* 17 */,722 /* 18 */,702 /* 19 */, + 683 /* 20 */,665 /* 21 */,647 /* 22 */,631 /* 23 */,615 /* 24 */, + 600 /* 25 */,586 /* 26 */,572 /* 27 */,558 /* 28 */,545 /* 29 */, + 533 /* 30 */,521 /* 31 */,509 /* 32 */,498 /* 33 */,487 /* 34 */, + 476 /* 35 */,466 /* 36 */,455 /* 37 */,446 /* 38 */,436 /* 39 */, + 427 /* 40 */,418 /* 41 */,409 /* 42 */,400 /* 43 */,391 /* 44 */, + 383 /* 45 */,375 /* 46 */,367 /* 47 */,359 /* 48 */,352 /* 49 */, + 344 /* 50 */,337 /* 51 */,330 /* 52 */,323 /* 53 */,316 /* 54 */, + 309 /* 55 */,302 /* 56 */,296 /* 57 */,289 /* 58 */,283 /* 59 */, + 277 /* 60 */,271 /* 61 */,265 /* 62 */,259 /* 63 */,253 /* 64 */, + 247 /* 65 */,242 /* 66 */,236 /* 67 */,231 /* 68 */,225 /* 69 */, + 220 /* 70 */,215 /* 71 */,210 /* 72 */,205 /* 73 */,199 /* 74 */, + 195 /* 75 */,190 /* 76 */,185 /* 77 */,180 /* 78 */,175 /* 79 */, + 171 /* 80 */,166 /* 81 */,162 /* 82 */,157 /* 83 */,153 /* 84 */, + 148 /* 85 */,144 /* 86 */,140 /* 87 */,135 /* 88 */,131 /* 89 */, + 127 /* 90 */,123 /* 91 */,119 /* 92 */,115 /* 93 */,111 /* 94 */, + 107 /* 95 */,103 /* 96 */,100 /* 97 */,96 /* 98 */,92 /* 99 */, + 88 /* 100 */,85 /* 101 */,81 /* 102 */,77 /* 103 */,74 /* 104 */, + 70 /* 105 */,67 /* 106 */,63 /* 107 */,60 /* 108 */,56 /* 109 */, + 53 /* 110 */,50 /* 111 */,46 /* 112 */,43 /* 113 */,40 /* 114 */, + 37 /* 115 */,33 /* 116 */,30 /* 117 */,27 /* 118 */,24 /* 119 */, + 21 /* 120 */,18 /* 121 */,15 /* 122 */,12 /* 123 */,9 /* 124 */, + 6 /* 125 */,3 /* 126 */,0 /* 127 */, +}; + +#else + +extern unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE]; +extern unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE]; + +#endif diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c new file mode 100644 index 000000000..c53727147 --- /dev/null +++ b/sound/isa/gus/gus_timer.c @@ -0,0 +1,203 @@ +/* + * Routines for Gravis UltraSound soundcards - Timers + * Copyright (c) by Jaroslav Kysela + * + * GUS have similar timers as AdLib (OPL2/OPL3 chips). + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +/* + * Timer 1 - 80us + */ + +static int snd_gf1_timer1_start(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + unsigned int ticks; + struct snd_gus_card *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + ticks = timer->sticks; + tmp = (gus->gf1.timer_enabled |= 4); + snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1, 256 - ticks); /* timer 1 count */ + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 1 IRQ */ + snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +static int snd_gf1_timer1_stop(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + struct snd_gus_card *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + tmp = (gus->gf1.timer_enabled &= ~4); + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +/* + * Timer 2 - 320us + */ + +static int snd_gf1_timer2_start(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + unsigned int ticks; + struct snd_gus_card *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + ticks = timer->sticks; + tmp = (gus->gf1.timer_enabled |= 8); + snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2, 256 - ticks); /* timer 2 count */ + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 2 IRQ */ + snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +static int snd_gf1_timer2_stop(struct snd_timer * timer) +{ + unsigned long flags; + unsigned char tmp; + struct snd_gus_card *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + tmp = (gus->gf1.timer_enabled &= ~8); + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +/* + + */ + +static void snd_gf1_interrupt_timer1(struct snd_gus_card * gus) +{ + struct snd_timer *timer = gus->gf1.timer1; + + if (timer == NULL) + return; + snd_timer_interrupt(timer, timer->sticks); +} + +static void snd_gf1_interrupt_timer2(struct snd_gus_card * gus) +{ + struct snd_timer *timer = gus->gf1.timer2; + + if (timer == NULL) + return; + snd_timer_interrupt(timer, timer->sticks); +} + +/* + + */ + +static struct snd_timer_hardware snd_gf1_timer1 = +{ + .flags = SNDRV_TIMER_HW_STOP, + .resolution = 80000, + .ticks = 256, + .start = snd_gf1_timer1_start, + .stop = snd_gf1_timer1_stop, +}; + +static struct snd_timer_hardware snd_gf1_timer2 = +{ + .flags = SNDRV_TIMER_HW_STOP, + .resolution = 320000, + .ticks = 256, + .start = snd_gf1_timer2_start, + .stop = snd_gf1_timer2_stop, +}; + +static void snd_gf1_timer1_free(struct snd_timer *timer) +{ + struct snd_gus_card *gus = timer->private_data; + gus->gf1.timer1 = NULL; +} + +static void snd_gf1_timer2_free(struct snd_timer *timer) +{ + struct snd_gus_card *gus = timer->private_data; + gus->gf1.timer2 = NULL; +} + +void snd_gf1_timers_init(struct snd_gus_card * gus) +{ + struct snd_timer *timer; + struct snd_timer_id tid; + + if (gus->gf1.timer1 != NULL || gus->gf1.timer2 != NULL) + return; + + gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1; + gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2; + + tid.dev_class = SNDRV_TIMER_CLASS_CARD; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = gus->card->number; + tid.device = gus->timer_dev; + tid.subdevice = 0; + + if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) { + strcpy(timer->name, "GF1 timer #1"); + timer->private_data = gus; + timer->private_free = snd_gf1_timer1_free; + timer->hw = snd_gf1_timer1; + } + gus->gf1.timer1 = timer; + + tid.device++; + + if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) { + strcpy(timer->name, "GF1 timer #2"); + timer->private_data = gus; + timer->private_free = snd_gf1_timer2_free; + timer->hw = snd_gf1_timer2; + } + gus->gf1.timer2 = timer; +} + +void snd_gf1_timers_done(struct snd_gus_card * gus) +{ + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_TIMER1 | SNDRV_GF1_HANDLER_TIMER2); + if (gus->gf1.timer1) { + snd_device_free(gus->card, gus->gf1.timer1); + gus->gf1.timer1 = NULL; + } + if (gus->gf1.timer2) { + snd_device_free(gus->card, gus->gf1.timer2); + gus->gf1.timer2 = NULL; + } +} diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c new file mode 100644 index 000000000..ac5f5687d --- /dev/null +++ b/sound/isa/gus/gus_uart.c @@ -0,0 +1,258 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for the GF1 MIDI interface - like UART 6850 + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +static void snd_gf1_interrupt_midi_in(struct snd_gus_card * gus) +{ + int count; + unsigned char stat, data, byte; + unsigned long flags; + + count = 10; + while (count) { + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + stat = snd_gf1_uart_stat(gus); + if (!(stat & 0x01)) { /* data in Rx FIFO? */ + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + count--; + continue; + } + count = 100; /* arm counter to new value */ + data = snd_gf1_uart_get(gus); + if (!(gus->gf1.uart_cmd & 0x80)) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + continue; + } + if (stat & 0x10) { /* framing error */ + gus->gf1.uart_framing++; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + continue; + } + byte = snd_gf1_uart_get(gus); + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + snd_rawmidi_receive(gus->midi_substream_input, &byte, 1); + if (stat & 0x20) { + gus->gf1.uart_overrun++; + } + } +} + +static void snd_gf1_interrupt_midi_out(struct snd_gus_card * gus) +{ + char byte; + unsigned long flags; + + /* try unlock output */ + if (snd_gf1_uart_stat(gus) & 0x01) + snd_gf1_interrupt_midi_in(gus); + + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (snd_gf1_uart_stat(gus) & 0x02) { /* Tx FIFO free? */ + if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) { /* no other bytes or error */ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */ + } else { + snd_gf1_uart_put(gus, byte); + } + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +} + +static void snd_gf1_uart_reset(struct snd_gus_card * gus, int close) +{ + snd_gf1_uart_cmd(gus, 0x03); /* reset */ + if (!close && gus->uart_enable) { + udelay(160); + snd_gf1_uart_cmd(gus, 0x00); /* normal operations */ + } +} + +static int snd_gf1_uart_output_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_gus_card *gus; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (!(gus->gf1.uart_cmd & 0x80)) { /* input active? */ + snd_gf1_uart_reset(gus, 0); + } + gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out; + gus->midi_substream_output = substream; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +#if 0 + snd_printk(KERN_DEBUG "write init - cmd = 0x%x, stat = 0x%x\n", gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); +#endif + return 0; +} + +static int snd_gf1_uart_input_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_gus_card *gus; + int i; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) { + snd_gf1_uart_reset(gus, 0); + } + gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in; + gus->midi_substream_input = substream; + if (gus->uart_enable) { + for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++) + snd_gf1_uart_get(gus); /* clean Rx */ + if (i >= 1000) + snd_printk(KERN_ERR "gus midi uart init read - cleanup error\n"); + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +#if 0 + snd_printk(KERN_DEBUG + "read init - enable = %i, cmd = 0x%x, stat = 0x%x\n", + gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); + snd_printk(KERN_DEBUG + "[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x " + "(page = 0x%x)\n", + gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100), + inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102)); +#endif + return 0; +} + +static int snd_gf1_uart_output_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_gus_card *gus; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in) + snd_gf1_uart_reset(gus, 1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT); + gus->midi_substream_output = NULL; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return 0; +} + +static int snd_gf1_uart_input_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_gus_card *gus; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) + snd_gf1_uart_reset(gus, 1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN); + gus->midi_substream_input = NULL; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return 0; +} + +static void snd_gf1_uart_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_gus_card *gus; + unsigned long flags; + + gus = substream->rmidi->private_data; + + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (up) { + if ((gus->gf1.uart_cmd & 0x80) == 0) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */ + } else { + if (gus->gf1.uart_cmd & 0x80) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */ + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +} + +static void snd_gf1_uart_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_gus_card *gus; + char byte; + int timeout; + + gus = substream->rmidi->private_data; + + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (up) { + if ((gus->gf1.uart_cmd & 0x20) == 0) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + /* wait for empty Rx - Tx is probably unlocked */ + timeout = 10000; + while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01); + /* Tx FIFO free? */ + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.uart_cmd & 0x20) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return; + } + if (snd_gf1_uart_stat(gus) & 0x02) { + if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return; + } + snd_gf1_uart_put(gus, byte); + } + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20); /* enable Tx interrupt */ + } + } else { + if (gus->gf1.uart_cmd & 0x20) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +} + +static const struct snd_rawmidi_ops snd_gf1_uart_output = +{ + .open = snd_gf1_uart_output_open, + .close = snd_gf1_uart_output_close, + .trigger = snd_gf1_uart_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_gf1_uart_input = +{ + .open = snd_gf1_uart_input_open, + .close = snd_gf1_uart_input_close, + .trigger = snd_gf1_uart_input_trigger, +}; + +int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device) +{ + struct snd_rawmidi *rmidi; + int err; + + if ((err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi)) < 0) + return err; + strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = gus; + gus->midi_uart = rmidi; + return err; +} diff --git a/sound/isa/gus/gus_volume.c b/sound/isa/gus/gus_volume.c new file mode 100644 index 000000000..3dd841ae7 --- /dev/null +++ b/sound/isa/gus/gus_volume.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#define __GUS_TABLES_ALLOC__ +#include "gus_tables.h" + +EXPORT_SYMBOL(snd_gf1_atten_table); /* for snd-gus-synth module */ + +unsigned short snd_gf1_lvol_to_gvol_raw(unsigned int vol) +{ + unsigned short e, m, tmp; + + if (vol > 65535) + vol = 65535; + tmp = vol; + e = 7; + if (tmp < 128) { + while (e > 0 && tmp < (1 << e)) + e--; + } else { + while (tmp > 255) { + tmp >>= 1; + e++; + } + } + m = vol - (1 << e); + if (m > 0) { + if (e > 8) + m >>= e - 8; + else if (e < 8) + m <<= 8 - e; + m &= 255; + } + return (e << 8) | m; +} + +#if 0 + +unsigned int snd_gf1_gvol_to_lvol_raw(unsigned short gf1_vol) +{ + unsigned int rvol; + unsigned short e, m; + + if (!gf1_vol) + return 0; + e = gf1_vol >> 8; + m = (unsigned char) gf1_vol; + rvol = 1 << e; + if (e > 8) + return rvol | (m << (e - 8)); + return rvol | (m >> (8 - e)); +} + +unsigned int snd_gf1_calc_ramp_rate(struct snd_gus_card * gus, + unsigned short start, + unsigned short end, + unsigned int us) +{ + static unsigned char vol_rates[19] = + { + 23, 24, 26, 28, 29, 31, 32, 34, + 36, 37, 39, 40, 42, 44, 45, 47, + 49, 50, 52 + }; + unsigned short range, increment, value, i; + + start >>= 4; + end >>= 4; + if (start < end) + us /= end - start; + else + us /= start - end; + range = 4; + value = gus->gf1.enh_mode ? + vol_rates[0] : + vol_rates[gus->gf1.active_voices - 14]; + for (i = 0; i < 3; i++) { + if (us < value) { + range = i; + break; + } else + value <<= 3; + } + if (range == 4) { + range = 3; + increment = 1; + } else + increment = (value + (value >> 1)) / us; + return (range << 6) | (increment & 0x3f); +} + +#endif /* 0 */ + +unsigned short snd_gf1_translate_freq(struct snd_gus_card * gus, unsigned int freq16) +{ + freq16 >>= 3; + if (freq16 < 50) + freq16 = 50; + if (freq16 & 0xf8000000) { + freq16 = ~0xf8000000; + snd_printk(KERN_ERR "snd_gf1_translate_freq: overflow - freq = 0x%x\n", freq16); + } + return ((freq16 << 9) + (gus->gf1.playback_freq >> 1)) / gus->gf1.playback_freq; +} + +#if 0 + +short snd_gf1_compute_vibrato(short cents, unsigned short fc_register) +{ + static short vibrato_table[] = + { + 0, 0, 32, 592, 61, 1175, 93, 1808, + 124, 2433, 152, 3007, 182, 3632, 213, 4290, + 241, 4834, 255, 5200 + }; + + long depth; + short *vi1, *vi2, pcents, v1; + + pcents = cents < 0 ? -cents : cents; + for (vi1 = vibrato_table, vi2 = vi1 + 2; pcents > *vi2; vi1 = vi2, vi2 += 2); + v1 = *(vi1 + 1); + /* The FC table above is a list of pairs. The first number in the pair */ + /* is the cents index from 0-255 cents, and the second number in the */ + /* pair is the FC adjustment needed to change the pitch by the indexed */ + /* number of cents. The table was created for an FC of 32768. */ + /* The following expression does a linear interpolation against the */ + /* approximated log curve in the table above, and then scales the number */ + /* by the FC before the LFO. This calculation also adjusts the output */ + /* value to produce the appropriate depth for the hardware. The depth */ + /* is 2 * desired FC + 1. */ + depth = (((int) (*(vi2 + 1) - *vi1) * (pcents - *vi1) / (*vi2 - *vi1)) + v1) * fc_register >> 14; + if (depth) + depth++; + if (depth > 255) + depth = 255; + return cents < 0 ? -(short) depth : (short) depth; +} + +unsigned short snd_gf1_compute_pitchbend(unsigned short pitchbend, unsigned short sens) +{ + static long log_table[] = {1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933}; + int wheel, sensitivity; + unsigned int mantissa, f1, f2; + unsigned short semitones, f1_index, f2_index, f1_power, f2_power; + char bend_down = 0; + int bend; + + if (!sens) + return 1024; + wheel = (int) pitchbend - 8192; + sensitivity = ((int) sens * wheel) / 128; + if (sensitivity < 0) { + bend_down = 1; + sensitivity = -sensitivity; + } + semitones = (unsigned int) (sensitivity >> 13); + mantissa = sensitivity % 8192; + f1_index = semitones % 12; + f2_index = (semitones + 1) % 12; + f1_power = semitones / 12; + f2_power = (semitones + 1) / 12; + f1 = log_table[f1_index] << f1_power; + f2 = log_table[f2_index] << f2_power; + bend = (int) ((((f2 - f1) * mantissa) >> 13) + f1); + if (bend_down) + bend = 1048576L / bend; + return bend; +} + +unsigned short snd_gf1_compute_freq(unsigned int freq, + unsigned int rate, + unsigned short mix_rate) +{ + unsigned int fc; + int scale = 0; + + while (freq >= 4194304L) { + scale++; + freq >>= 1; + } + fc = (freq << 10) / rate; + if (fc > 97391L) { + fc = 97391; + snd_printk(KERN_ERR "patch: (1) fc frequency overflow - %u\n", fc); + } + fc = (fc * 44100UL) / mix_rate; + while (scale--) + fc <<= 1; + if (fc > 65535L) { + fc = 65535; + snd_printk(KERN_ERR "patch: (2) fc frequency overflow - %u\n", fc); + } + return (unsigned short) fc; +} + +#endif /* 0 */ diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c new file mode 100644 index 000000000..92a997ab1 --- /dev/null +++ b/sound/isa/gus/gusclassic.c @@ -0,0 +1,232 @@ +/* + * Driver for Gravis UltraSound Classic soundcard + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +#define CRD_NAME "Gravis UltraSound Classic" +#define DEV_NAME "gusclassic" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Classic}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x230,0x240,0x250,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver."); + +static int snd_gusclassic_match(struct device *dev, unsigned int n) +{ + return enable[n]; +} + +static int snd_gusclassic_create(struct snd_card *card, + struct device *dev, unsigned int n, + struct snd_gus_card **rgus) +{ + static long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260}; + static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1}; + static int possible_dmas[] = {5, 6, 7, 1, 3, -1}; + + int i, error; + + if (irq[n] == SNDRV_AUTO_IRQ) { + irq[n] = snd_legacy_find_free_irq(possible_irqs); + if (irq[n] < 0) { + dev_err(dev, "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma1[n] == SNDRV_AUTO_DMA) { + dma1[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma1[n] < 0) { + dev_err(dev, "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (dma2[n] == SNDRV_AUTO_DMA) { + dma2[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma2[n] < 0) { + dev_err(dev, "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if (port[n] != SNDRV_AUTO_PORT) + return snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n], + 0, channels[n], pcm_channels[n], 0, rgus); + + i = 0; + do { + port[n] = possible_ports[i]; + error = snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n], + 0, channels[n], pcm_channels[n], 0, rgus); + } while (error < 0 && ++i < ARRAY_SIZE(possible_ports)); + + return error; +} + +static int snd_gusclassic_detect(struct snd_gus_card *gus) +{ + unsigned char d; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + return 0; +} + +static int snd_gusclassic_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + struct snd_gus_card *gus; + int error; + + error = snd_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card); + if (error < 0) + return error; + + if (pcm_channels[n] < 2) + pcm_channels[n] = 2; + + error = snd_gusclassic_create(card, dev, n, &gus); + if (error < 0) + goto out; + + error = snd_gusclassic_detect(gus); + if (error < 0) + goto out; + + gus->joystick_dac = joystick_dac[n]; + + error = snd_gus_initialize(gus); + if (error < 0) + goto out; + + error = -ENODEV; + if (gus->max_flag || gus->ess_flag) { + dev_err(dev, "GUS Classic or ACE soundcard was " + "not detected at 0x%lx\n", gus->gf1.port); + goto out; + } + + error = snd_gf1_new_mixer(gus); + if (error < 0) + goto out; + + error = snd_gf1_pcm_new(gus, 0, 0); + if (error < 0) + goto out; + + if (!gus->ace_flag) { + error = snd_gf1_rawmidi_new(gus, 0); + if (error < 0) + goto out; + } + + sprintf(card->longname + strlen(card->longname), + " at 0x%lx, irq %d, dma %d", + gus->gf1.port, gus->gf1.irq, gus->gf1.dma1); + + if (gus->gf1.dma2 >= 0) + sprintf(card->longname + strlen(card->longname), + "&%d", gus->gf1.dma2); + + error = snd_card_register(card); + if (error < 0) + goto out; + + dev_set_drvdata(dev, card); + return 0; + +out: snd_card_free(card); + return error; +} + +static int snd_gusclassic_remove(struct device *dev, unsigned int n) +{ + snd_card_free(dev_get_drvdata(dev)); + return 0; +} + +static struct isa_driver snd_gusclassic_driver = { + .match = snd_gusclassic_match, + .probe = snd_gusclassic_probe, + .remove = snd_gusclassic_remove, +#if 0 /* FIXME */ + .suspend = snd_gusclassic_suspend, + .remove = snd_gusclassic_remove, +#endif + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_gusclassic_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c new file mode 100644 index 000000000..beb52c0f7 --- /dev/null +++ b/sound/isa/gus/gusextreme.c @@ -0,0 +1,361 @@ +/* + * Driver for Gravis UltraSound Extreme soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +#define CRD_NAME "Gravis UltraSound Extreme" +#define DEV_NAME "gusextreme" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Extreme}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */ +static long gf1_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x210,0x220,0x230,0x240,0x250,0x260,0x270 */ +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x300,0x310,0x320 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int gf1_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(gf1_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(gf1_port, "GF1 port # for " CRD_NAME " driver (optional)."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver."); +module_param_hw_array(gf1_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(gf1_irq, "GF1 IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma8, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "GF1 DMA # for " CRD_NAME " driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver."); + +static int snd_gusextreme_match(struct device *dev, unsigned int n) +{ + return enable[n]; +} + +static int snd_gusextreme_es1688_create(struct snd_card *card, + struct snd_es1688 *chip, + struct device *dev, unsigned int n) +{ + static long possible_ports[] = {0x220, 0x240, 0x260}; + static int possible_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dmas[] = {1, 3, 0, -1}; + + int i, error; + + if (irq[n] == SNDRV_AUTO_IRQ) { + irq[n] = snd_legacy_find_free_irq(possible_irqs); + if (irq[n] < 0) { + dev_err(dev, "unable to find a free IRQ for ES1688\n"); + return -EBUSY; + } + } + if (dma8[n] == SNDRV_AUTO_DMA) { + dma8[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma8[n] < 0) { + dev_err(dev, "unable to find a free DMA for ES1688\n"); + return -EBUSY; + } + } + + if (port[n] != SNDRV_AUTO_PORT) + return snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688); + + i = 0; + do { + port[n] = possible_ports[i]; + error = snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688); + } while (error < 0 && ++i < ARRAY_SIZE(possible_ports)); + + return error; +} + +static int snd_gusextreme_gus_card_create(struct snd_card *card, + struct device *dev, unsigned int n, + struct snd_gus_card **rgus) +{ + static int possible_irqs[] = {11, 12, 15, 9, 5, 7, 3, -1}; + static int possible_dmas[] = {5, 6, 7, 3, 1, -1}; + + if (gf1_irq[n] == SNDRV_AUTO_IRQ) { + gf1_irq[n] = snd_legacy_find_free_irq(possible_irqs); + if (gf1_irq[n] < 0) { + dev_err(dev, "unable to find a free IRQ for GF1\n"); + return -EBUSY; + } + } + if (dma1[n] == SNDRV_AUTO_DMA) { + dma1[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma1[n] < 0) { + dev_err(dev, "unable to find a free DMA for GF1\n"); + return -EBUSY; + } + } + return snd_gus_create(card, gf1_port[n], gf1_irq[n], dma1[n], -1, + 0, channels[n], pcm_channels[n], 0, rgus); +} + +static int snd_gusextreme_detect(struct snd_gus_card *gus, + struct snd_es1688 *es1688) +{ + unsigned long flags; + unsigned char d; + + /* + * This is main stuff - enable access to GF1 chip... + * I'm not sure, if this will work for card which have + * ES1688 chip in another place than 0x220. + * + * I used reverse-engineering in DOSEMU. [--jk] + * + * ULTRINIT.EXE: + * 0x230 = 0,2,3 + * 0x240 = 2,0,1 + * 0x250 = 2,0,3 + * 0x260 = 2,2,1 + */ + + spin_lock_irqsave(&es1688->mixer_lock, flags); + snd_es1688_mixer_write(es1688, 0x40, 0x0b); /* don't change!!! */ + spin_unlock_irqrestore(&es1688->mixer_lock, flags); + + spin_lock_irqsave(&es1688->reg_lock, flags); + outb(gus->gf1.port & 0x040 ? 2 : 0, ES1688P(es1688, INIT1)); + outb(0, 0x201); + outb(gus->gf1.port & 0x020 ? 2 : 0, ES1688P(es1688, INIT1)); + outb(0, 0x201); + outb(gus->gf1.port & 0x010 ? 3 : 1, ES1688P(es1688, INIT1)); + spin_unlock_irqrestore(&es1688->reg_lock, flags); + + udelay(100); + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -EIO; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -EIO; + } + + return 0; +} + +static int snd_gusextreme_mixer(struct snd_card *card) +{ + struct snd_ctl_elem_id id1, id2; + int error; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + + /* reassign AUX to SYNTHESIZER */ + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "Synth Playback Volume"); + error = snd_ctl_rename_id(card, &id1, &id2); + if (error < 0) + return error; + + /* reassign Master Playback Switch to Synth Playback Switch */ + strcpy(id1.name, "Master Playback Switch"); + strcpy(id2.name, "Synth Playback Switch"); + error = snd_ctl_rename_id(card, &id1, &id2); + if (error < 0) + return error; + + return 0; +} + +static int snd_gusextreme_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + struct snd_gus_card *gus; + struct snd_es1688 *es1688; + struct snd_opl3 *opl3; + int error; + + error = snd_card_new(dev, index[n], id[n], THIS_MODULE, + sizeof(struct snd_es1688), &card); + if (error < 0) + return error; + + es1688 = card->private_data; + + if (mpu_port[n] == SNDRV_AUTO_PORT) + mpu_port[n] = 0; + + if (mpu_irq[n] > 15) + mpu_irq[n] = -1; + + error = snd_gusextreme_es1688_create(card, es1688, dev, n); + if (error < 0) + goto out; + + if (gf1_port[n] < 0) + gf1_port[n] = es1688->port + 0x20; + + error = snd_gusextreme_gus_card_create(card, dev, n, &gus); + if (error < 0) + goto out; + + error = snd_gusextreme_detect(gus, es1688); + if (error < 0) + goto out; + + gus->joystick_dac = joystick_dac[n]; + + error = snd_gus_initialize(gus); + if (error < 0) + goto out; + + error = -ENODEV; + if (!gus->ess_flag) { + dev_err(dev, "GUS Extreme soundcard was not " + "detected at 0x%lx\n", gus->gf1.port); + goto out; + } + gus->codec_flag = 1; + + error = snd_es1688_pcm(card, es1688, 0); + if (error < 0) + goto out; + + error = snd_es1688_mixer(card, es1688); + if (error < 0) + goto out; + + snd_component_add(card, "ES1688"); + + if (pcm_channels[n] > 0) { + error = snd_gf1_pcm_new(gus, 1, 1); + if (error < 0) + goto out; + } + + error = snd_gf1_new_mixer(gus); + if (error < 0) + goto out; + + error = snd_gusextreme_mixer(card); + if (error < 0) + goto out; + + if (snd_opl3_create(card, es1688->port, es1688->port + 2, + OPL3_HW_OPL3, 0, &opl3) < 0) + dev_warn(dev, "opl3 not detected at 0x%lx\n", es1688->port); + else { + error = snd_opl3_hwdep_new(opl3, 0, 2, NULL); + if (error < 0) + goto out; + } + + if (es1688->mpu_port >= 0x300) { + error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688, + es1688->mpu_port, 0, mpu_irq[n], NULL); + if (error < 0) + goto out; + } + + sprintf(card->longname, "Gravis UltraSound Extreme at 0x%lx, " + "irq %i&%i, dma %i&%i", es1688->port, + gus->gf1.irq, es1688->irq, gus->gf1.dma1, es1688->dma8); + + error = snd_card_register(card); + if (error < 0) + goto out; + + dev_set_drvdata(dev, card); + return 0; + +out: snd_card_free(card); + return error; +} + +static int snd_gusextreme_remove(struct device *dev, unsigned int n) +{ + snd_card_free(dev_get_drvdata(dev)); + return 0; +} + +static struct isa_driver snd_gusextreme_driver = { + .match = snd_gusextreme_match, + .probe = snd_gusextreme_probe, + .remove = snd_gusextreme_remove, +#if 0 /* FIXME */ + .suspend = snd_gusextreme_suspend, + .resume = snd_gusextreme_resume, +#endif + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_gusextreme_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c new file mode 100644 index 000000000..63309a453 --- /dev/null +++ b/sound/isa/gus/gusmax.c @@ -0,0 +1,373 @@ +/* + * Driver for Gravis UltraSound MAX soundcard + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Gravis UltraSound MAX"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound MAX}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x230,0x240,0x250,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for GUS MAX soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for GUS MAX soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable GUS MAX soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for GUS MAX driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for GUS MAX driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for GUS MAX driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for GUS MAX driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS MAX driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "Used GF1 channels for GUS MAX driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS MAX driver."); + +struct snd_gusmax { + int irq; + struct snd_card *card; + struct snd_gus_card *gus; + struct snd_wss *wss; + unsigned short gus_status_reg; + unsigned short pcm_status_reg; +}; + +#define PFX "gusmax: " + +static int snd_gusmax_detect(struct snd_gus_card *gus) +{ + unsigned char d; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + + return 0; +} + +static irqreturn_t snd_gusmax_interrupt(int irq, void *dev_id) +{ + struct snd_gusmax *maxcard = dev_id; + int loop, max = 5; + int handled = 0; + + do { + loop = 0; + if (inb(maxcard->gus_status_reg)) { + handled = 1; + snd_gus_interrupt(irq, maxcard->gus); + loop++; + } + if (inb(maxcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */ + handled = 1; + snd_wss_interrupt(irq, maxcard->wss); + loop++; + } + } while (loop && --max > 0); + return IRQ_RETVAL(handled); +} + +static void snd_gusmax_init(int dev, struct snd_card *card, + struct snd_gus_card *gus) +{ + gus->equal_irq = 1; + gus->codec_flag = 1; + gus->joystick_dac = joystick_dac[dev]; + /* init control register */ + gus->max_cntrl_val = (gus->gf1.port >> 4) & 0x0f; + if (gus->gf1.dma1 > 3) + gus->max_cntrl_val |= 0x10; + if (gus->gf1.dma2 > 3) + gus->max_cntrl_val |= 0x20; + gus->max_cntrl_val |= 0x40; + outb(gus->max_cntrl_val, GUSP(gus, MAXCNTRLPORT)); +} + +static int snd_gusmax_mixer(struct snd_wss *chip) +{ + struct snd_card *card = chip->card; + struct snd_ctl_elem_id id1, id2; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUXA to SYNTHESIZER */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "Synth Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "Synth Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* reassign AUXB to CD */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "CD Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; +#if 0 + /* reassign Mono Input to MIC */ + if (snd_mixer_group_rename(mixer, + SNDRV_MIXER_IN_MONO, 0, + SNDRV_MIXER_IN_MIC, 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + SNDRV_MIXER_IN_MONO, 0, SNDRV_MIXER_ETYPE_INPUT, + SNDRV_MIXER_IN_MIC, 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + "Mono Capture Volume", 0, SNDRV_MIXER_ETYPE_VOLUME1, + "Mic Capture Volume", 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + "Mono Capture Switch", 0, SNDRV_MIXER_ETYPE_SWITCH1, + "Mic Capture Switch", 0) < 0) + goto __error; +#endif + return 0; +} + +static void snd_gusmax_free(struct snd_card *card) +{ + struct snd_gusmax *maxcard = card->private_data; + + if (maxcard == NULL) + return; + if (maxcard->irq >= 0) + free_irq(maxcard->irq, (void *)maxcard); +} + +static int snd_gusmax_match(struct device *pdev, unsigned int dev) +{ + return enable[dev]; +} + +static int snd_gusmax_probe(struct device *pdev, unsigned int dev) +{ + static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1}; + static int possible_dmas[] = {5, 6, 7, 1, 3, -1}; + int xirq, xdma1, xdma2, err; + struct snd_card *card; + struct snd_gus_card *gus = NULL; + struct snd_wss *wss; + struct snd_gusmax *maxcard; + + err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_gusmax), &card); + if (err < 0) + return err; + card->private_free = snd_gusmax_free; + maxcard = card->private_data; + maxcard->card = card; + maxcard->irq = -1; + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); + err = -EBUSY; + goto _err; + } + } + xdma1 = dma1[dev]; + if (xdma1 == SNDRV_AUTO_DMA) { + if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free DMA1\n"); + err = -EBUSY; + goto _err; + } + } + xdma2 = dma2[dev]; + if (xdma2 == SNDRV_AUTO_DMA) { + if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free DMA2\n"); + err = -EBUSY; + goto _err; + } + } + + if (port[dev] != SNDRV_AUTO_PORT) { + err = snd_gus_create(card, + port[dev], + -xirq, xdma1, xdma2, + 0, channels[dev], + pcm_channels[dev], + 0, &gus); + } else { + static unsigned long possible_ports[] = { + 0x220, 0x230, 0x240, 0x250, 0x260 + }; + int i; + for (i = 0; i < ARRAY_SIZE(possible_ports); i++) { + err = snd_gus_create(card, + possible_ports[i], + -xirq, xdma1, xdma2, + 0, channels[dev], + pcm_channels[dev], + 0, &gus); + if (err >= 0) { + port[dev] = possible_ports[i]; + break; + } + } + } + if (err < 0) + goto _err; + + if ((err = snd_gusmax_detect(gus)) < 0) + goto _err; + + maxcard->gus_status_reg = gus->gf1.reg_irqstat; + maxcard->pcm_status_reg = gus->gf1.port + 0x10c + 2; + snd_gusmax_init(dev, card, gus); + if ((err = snd_gus_initialize(gus)) < 0) + goto _err; + + if (!gus->max_flag) { + snd_printk(KERN_ERR PFX "GUS MAX soundcard was not detected at 0x%lx\n", gus->gf1.port); + err = -ENODEV; + goto _err; + } + + if (request_irq(xirq, snd_gusmax_interrupt, 0, "GUS MAX", (void *)maxcard)) { + snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq); + err = -EBUSY; + goto _err; + } + maxcard->irq = xirq; + + err = snd_wss_create(card, + gus->gf1.port + 0x10c, -1, xirq, + xdma2 < 0 ? xdma1 : xdma2, xdma1, + WSS_HW_DETECT, + WSS_HWSHARE_IRQ | + WSS_HWSHARE_DMA1 | + WSS_HWSHARE_DMA2, + &wss); + if (err < 0) + goto _err; + + err = snd_wss_pcm(wss, 0); + if (err < 0) + goto _err; + + err = snd_wss_mixer(wss); + if (err < 0) + goto _err; + + err = snd_wss_timer(wss, 2); + if (err < 0) + goto _err; + + if (pcm_channels[dev] > 0) { + if ((err = snd_gf1_pcm_new(gus, 1, 1)) < 0) + goto _err; + } + err = snd_gusmax_mixer(wss); + if (err < 0) + goto _err; + + err = snd_gf1_rawmidi_new(gus, 0); + if (err < 0) + goto _err; + + sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %i, dma %i", gus->gf1.port, xirq, xdma1); + if (xdma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%i", xdma2); + + err = snd_card_register(card); + if (err < 0) + goto _err; + + maxcard->gus = gus; + maxcard->wss = wss; + + dev_set_drvdata(pdev, card); + return 0; + + _err: + snd_card_free(card); + return err; +} + +static int snd_gusmax_remove(struct device *devptr, unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#define DEV_NAME "gusmax" + +static struct isa_driver snd_gusmax_driver = { + .match = snd_gusmax_match, + .probe = snd_gusmax_probe, + .remove = snd_gusmax_remove, + /* FIXME: suspend/resume */ + .driver = { + .name = DEV_NAME + }, +}; + +module_isa_driver(snd_gusmax_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/interwave-stb.c b/sound/isa/gus/interwave-stb.c new file mode 100644 index 000000000..dbe4f48a9 --- /dev/null +++ b/sound/isa/gus/interwave-stb.c @@ -0,0 +1,2 @@ +#define SNDRV_STB +#include "interwave.c" diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c new file mode 100644 index 000000000..a6fc26bf0 --- /dev/null +++ b/sound/isa/gus/interwave.c @@ -0,0 +1,937 @@ +/* + * Driver for AMD InterWave soundcard + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * 1999/07/22 Erik Inge Bolso + * * mixer group handlers + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SNDRV_STB +#include +#endif +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +#ifndef SNDRV_STB +MODULE_DESCRIPTION("AMD InterWave"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Plug & Play}," + "{STB,SoundRage32}," + "{MED,MED3210}," + "{Dynasonix,Dynasonix Pro}," + "{Panasonic,PCA761AW}}"); +#else +MODULE_DESCRIPTION("AMD InterWave STB with TEA6330T"); +MODULE_SUPPORTED_DEVICE("{{AMD,InterWave STB with TEA6330T}}"); +#endif + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x210,0x220,0x230,0x240,0x250,0x260 */ +#ifdef SNDRV_STB +static long port_tc[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x350,0x360,0x370,0x380 */ +#endif +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int midi[SNDRV_CARDS]; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +static int effect[SNDRV_CARDS]; + +#ifdef SNDRV_STB +#define PFX "interwave-stb: " +#define INTERWAVE_DRIVER "snd_interwave_stb" +#define INTERWAVE_PNP_DRIVER "interwave-stb" +#else +#define PFX "interwave: " +#define INTERWAVE_DRIVER "snd_interwave" +#define INTERWAVE_PNP_DRIVER "interwave" +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for InterWave soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for InterWave soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable InterWave soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard."); +#endif +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for InterWave driver."); +#ifdef SNDRV_STB +module_param_hw_array(port_tc, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver."); +#endif +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for InterWave driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for InterWave driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for InterWave driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver."); +module_param_array(midi, int, NULL, 0444); +MODULE_PARM_DESC(midi, "MIDI UART enable for InterWave driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for InterWave driver."); +module_param_array(effect, int, NULL, 0444); +MODULE_PARM_DESC(effect, "Effects enable for InterWave driver."); + +struct snd_interwave { + int irq; + struct snd_card *card; + struct snd_gus_card *gus; + struct snd_wss *wss; +#ifdef SNDRV_STB + struct resource *i2c_res; +#endif + unsigned short gus_status_reg; + unsigned short pcm_status_reg; +#ifdef CONFIG_PNP + struct pnp_dev *dev; +#ifdef SNDRV_STB + struct pnp_dev *devtc; +#endif +#endif +}; + + +#ifdef CONFIG_PNP +static int isa_registered; +static int pnp_registered; + +static const struct pnp_card_device_id snd_interwave_pnpids[] = { +#ifndef SNDRV_STB + /* Gravis UltraSound Plug & Play */ + { .id = "GRV0001", .devs = { { .id = "GRV0000" } } }, + /* STB SoundRage32 */ + { .id = "STB011a", .devs = { { .id = "STB0010" } } }, + /* MED3210 */ + { .id = "DXP3201", .devs = { { .id = "DXP0010" } } }, + /* Dynasonic Pro */ + /* This device also have CDC1117:DynaSonix Pro Audio Effects Processor */ + { .id = "CDC1111", .devs = { { .id = "CDC1112" } } }, + /* Panasonic PCA761AW Audio Card */ + { .id = "ADV55ff", .devs = { { .id = "ADV0010" } } }, + /* InterWave STB without TEA6330T */ + { .id = "ADV550a", .devs = { { .id = "ADV0010" } } }, +#else + /* InterWave STB with TEA6330T */ + { .id = "ADV550a", .devs = { { .id = "ADV0010" }, { .id = "ADV0015" } } }, +#endif + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_interwave_pnpids); + +#endif /* CONFIG_PNP */ + + +#ifdef SNDRV_STB +static void snd_interwave_i2c_setlines(struct snd_i2c_bus *bus, int ctrl, int data) +{ + unsigned long port = bus->private_value; + +#if 0 + printk(KERN_DEBUG "i2c_setlines - 0x%lx <- %i,%i\n", port, ctrl, data); +#endif + outb((data << 1) | ctrl, port); + udelay(10); +} + +static int snd_interwave_i2c_getclockline(struct snd_i2c_bus *bus) +{ + unsigned long port = bus->private_value; + unsigned char res; + + res = inb(port) & 1; +#if 0 + printk(KERN_DEBUG "i2c_getclockline - 0x%lx -> %i\n", port, res); +#endif + return res; +} + +static int snd_interwave_i2c_getdataline(struct snd_i2c_bus *bus, int ack) +{ + unsigned long port = bus->private_value; + unsigned char res; + + if (ack) + udelay(10); + res = (inb(port) & 2) >> 1; +#if 0 + printk(KERN_DEBUG "i2c_getdataline - 0x%lx -> %i\n", port, res); +#endif + return res; +} + +static struct snd_i2c_bit_ops snd_interwave_i2c_bit_ops = { + .setlines = snd_interwave_i2c_setlines, + .getclock = snd_interwave_i2c_getclockline, + .getdata = snd_interwave_i2c_getdataline, +}; + +static int snd_interwave_detect_stb(struct snd_interwave *iwcard, + struct snd_gus_card *gus, int dev, + struct snd_i2c_bus **rbus) +{ + unsigned long port; + struct snd_i2c_bus *bus; + struct snd_card *card = iwcard->card; + char name[32]; + int err; + + *rbus = NULL; + port = port_tc[dev]; + if (port == SNDRV_AUTO_PORT) { + port = 0x350; + if (gus->gf1.port == 0x250) { + port = 0x360; + } + while (port <= 0x380) { + if ((iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)")) != NULL) + break; + port += 0x10; + } + } else { + iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)"); + } + if (iwcard->i2c_res == NULL) { + snd_printk(KERN_ERR "interwave: can't grab i2c bus port\n"); + return -ENODEV; + } + + sprintf(name, "InterWave-%i", card->number); + if ((err = snd_i2c_bus_create(card, name, NULL, &bus)) < 0) + return err; + bus->private_value = port; + bus->hw_ops.bit = &snd_interwave_i2c_bit_ops; + if ((err = snd_tea6330t_detect(bus, 0)) < 0) + return err; + *rbus = bus; + return 0; +} +#endif + +static int snd_interwave_detect(struct snd_interwave *iwcard, + struct snd_gus_card *gus, + int dev +#ifdef SNDRV_STB + , struct snd_i2c_bus **rbus +#endif + ) +{ + unsigned long flags; + unsigned char rev1, rev2; + int d; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printdd("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printdd("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + spin_lock_irqsave(&gus->reg_lock, flags); + rev1 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, ~rev1); + rev2 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, rev1); + spin_unlock_irqrestore(&gus->reg_lock, flags); + snd_printdd("[0x%lx] InterWave check - rev1=0x%x, rev2=0x%x\n", gus->gf1.port, rev1, rev2); + if ((rev1 & 0xf0) == (rev2 & 0xf0) && + (rev1 & 0x0f) != (rev2 & 0x0f)) { + snd_printdd("[0x%lx] InterWave check - passed\n", gus->gf1.port); + gus->interwave = 1; + strcpy(gus->card->shortname, "AMD InterWave"); + gus->revision = rev1 >> 4; +#ifndef SNDRV_STB + return 0; /* ok.. We have an InterWave board */ +#else + return snd_interwave_detect_stb(iwcard, gus, dev, rbus); +#endif + } + snd_printdd("[0x%lx] InterWave check - failed\n", gus->gf1.port); + return -ENODEV; +} + +static irqreturn_t snd_interwave_interrupt(int irq, void *dev_id) +{ + struct snd_interwave *iwcard = dev_id; + int loop, max = 5; + int handled = 0; + + do { + loop = 0; + if (inb(iwcard->gus_status_reg)) { + handled = 1; + snd_gus_interrupt(irq, iwcard->gus); + loop++; + } + if (inb(iwcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */ + handled = 1; + snd_wss_interrupt(irq, iwcard->wss); + loop++; + } + } while (loop && --max > 0); + return IRQ_RETVAL(handled); +} + +static void snd_interwave_reset(struct snd_gus_card *gus) +{ + snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x00); + udelay(160); + snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x01); + udelay(160); +} + +static void snd_interwave_bank_sizes(struct snd_gus_card *gus, int *sizes) +{ + unsigned int idx; + unsigned int local; + unsigned char d; + + for (idx = 0; idx < 4; idx++) { + sizes[idx] = 0; + d = 0x55; + for (local = idx << 22; + local < (idx << 22) + 0x400000; + local += 0x40000, d++) { + snd_gf1_poke(gus, local, d); + snd_gf1_poke(gus, local + 1, d + 1); +#if 0 + printk(KERN_DEBUG "d = 0x%x, local = 0x%x, " + "local + 1 = 0x%x, idx << 22 = 0x%x\n", + d, + snd_gf1_peek(gus, local), + snd_gf1_peek(gus, local + 1), + snd_gf1_peek(gus, idx << 22)); +#endif + if (snd_gf1_peek(gus, local) != d || + snd_gf1_peek(gus, local + 1) != d + 1 || + snd_gf1_peek(gus, idx << 22) != 0x55) + break; + sizes[idx]++; + } + } +#if 0 + printk(KERN_DEBUG "sizes: %i %i %i %i\n", + sizes[0], sizes[1], sizes[2], sizes[3]); +#endif +} + +struct rom_hdr { + /* 000 */ unsigned char iwave[8]; + /* 008 */ unsigned char rom_hdr_revision; + /* 009 */ unsigned char series_number; + /* 010 */ unsigned char series_name[16]; + /* 026 */ unsigned char date[10]; + /* 036 */ unsigned short vendor_revision_major; + /* 038 */ unsigned short vendor_revision_minor; + /* 040 */ unsigned int rom_size; + /* 044 */ unsigned char copyright[128]; + /* 172 */ unsigned char vendor_name[64]; + /* 236 */ unsigned char rom_description[128]; + /* 364 */ unsigned char pad[147]; + /* 511 */ unsigned char csum; +}; + +static void snd_interwave_detect_memory(struct snd_gus_card *gus) +{ + static unsigned int lmc[13] = + { + 0x00000001, 0x00000101, 0x01010101, 0x00000401, + 0x04040401, 0x00040101, 0x04040101, 0x00000004, + 0x00000404, 0x04040404, 0x00000010, 0x00001010, + 0x10101010 + }; + + int bank_pos, pages; + unsigned int i, lmct; + int psizes[4]; + unsigned char iwave[8]; + unsigned char csum; + + snd_interwave_reset(gus); + snd_gf1_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); /* enhanced mode */ + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); /* DRAM I/O cycles selected */ + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff10) | 0x004c); + /* ok.. simple test of memory size */ + pages = 0; + snd_gf1_poke(gus, 0, 0x55); + snd_gf1_poke(gus, 1, 0xaa); +#if 1 + if (snd_gf1_peek(gus, 0) == 0x55 && snd_gf1_peek(gus, 1) == 0xaa) +#else + if (0) /* ok.. for testing of 0k RAM */ +#endif + { + snd_interwave_bank_sizes(gus, psizes); + lmct = (psizes[3] << 24) | (psizes[2] << 16) | + (psizes[1] << 8) | psizes[0]; +#if 0 + printk(KERN_DEBUG "lmct = 0x%08x\n", lmct); +#endif + for (i = 0; i < ARRAY_SIZE(lmc); i++) + if (lmct == lmc[i]) { +#if 0 + printk(KERN_DEBUG "found !!! %i\n", i); +#endif + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i); + snd_interwave_bank_sizes(gus, psizes); + break; + } + if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode) + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2); + for (i = 0; i < 4; i++) { + gus->gf1.mem_alloc.banks_8[i].address = + gus->gf1.mem_alloc.banks_16[i].address = i << 22; + gus->gf1.mem_alloc.banks_8[i].size = + gus->gf1.mem_alloc.banks_16[i].size = psizes[i] << 18; + pages += psizes[i]; + } + } + pages <<= 18; + gus->gf1.memory = pages; + + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x03); /* select ROM */ + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff1f) | (4 << 5)); + gus->gf1.rom_banks = 0; + gus->gf1.rom_memory = 0; + for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) { + for (i = 0; i < 8; ++i) + iwave[i] = snd_gf1_peek(gus, bank_pos + i); + if (strncmp(iwave, "INTRWAVE", 8)) + continue; /* first check */ + csum = 0; + for (i = 0; i < sizeof(struct rom_hdr); i++) + csum += snd_gf1_peek(gus, bank_pos + i); + if (csum != 0) + continue; /* not valid rom */ + gus->gf1.rom_banks++; + gus->gf1.rom_present |= 1 << (bank_pos >> 22); + gus->gf1.rom_memory = snd_gf1_peek(gus, bank_pos + 40) | + (snd_gf1_peek(gus, bank_pos + 41) << 8) | + (snd_gf1_peek(gus, bank_pos + 42) << 16) | + (snd_gf1_peek(gus, bank_pos + 43) << 24); + } +#if 0 + if (gus->gf1.rom_memory > 0) { + if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8) + gus->card->type = SNDRV_CARD_TYPE_IW_DYNASONIC; + } +#endif + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x00); /* select RAM */ + + if (!gus->gf1.enh_mode) + snd_interwave_reset(gus); +} + +static void snd_interwave_init(int dev, struct snd_gus_card *gus) +{ + unsigned long flags; + + /* ok.. some InterWave specific initialization */ + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00); + snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f); + snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30); + snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); + spin_unlock_irqrestore(&gus->reg_lock, flags); + gus->equal_irq = 1; + gus->codec_flag = 1; + gus->interwave = 1; + gus->max_flag = 1; + gus->joystick_dac = joystick_dac[dev]; + +} + +static struct snd_kcontrol_new snd_interwave_controls[] = { +WSS_DOUBLE("Master Playback Switch", 0, + CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 7, 7, 1, 1), +WSS_DOUBLE("Master Playback Volume", 0, + CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0, 31, 1), +WSS_DOUBLE("Mic Playback Switch", 0, + CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 7, 7, 1, 1), +WSS_DOUBLE("Mic Playback Volume", 0, + CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0, 31, 1) +}; + +static int snd_interwave_mixer(struct snd_wss *chip) +{ + struct snd_card *card = chip->card; + struct snd_ctl_elem_id id1, id2; + unsigned int idx; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; +#if 0 + /* remove mono microphone controls */ + strcpy(id1.name, "Mic Playback Switch"); + if ((err = snd_ctl_remove_id(card, &id1)) < 0) + return err; + strcpy(id1.name, "Mic Playback Volume"); + if ((err = snd_ctl_remove_id(card, &id1)) < 0) + return err; +#endif + /* add new master and mic controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_interwave_controls); idx++) + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_interwave_controls[idx], chip))) < 0) + return err; + snd_wss_out(chip, CS4231_LINE_LEFT_OUTPUT, 0x9f); + snd_wss_out(chip, CS4231_LINE_RIGHT_OUTPUT, 0x9f); + snd_wss_out(chip, CS4231_LEFT_MIC_INPUT, 0x9f); + snd_wss_out(chip, CS4231_RIGHT_MIC_INPUT, 0x9f); + /* reassign AUXA to SYNTHESIZER */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "Synth Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "Synth Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* reassign AUXB to CD */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "CD Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + return 0; +} + +#ifdef CONFIG_PNP + +static int snd_interwave_pnp(int dev, struct snd_interwave *iwcard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + iwcard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (iwcard->dev == NULL) + return -EBUSY; + +#ifdef SNDRV_STB + iwcard->devtc = pnp_request_card_device(card, id->devs[1].id, NULL); + if (iwcard->devtc == NULL) + return -EBUSY; +#endif + /* Synth & Codec initialization */ + pdev = iwcard->dev; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "InterWave PnP configure failure (out of resources?)\n"); + return err; + } + if (pnp_port_start(pdev, 0) + 0x100 != pnp_port_start(pdev, 1) || + pnp_port_start(pdev, 0) + 0x10c != pnp_port_start(pdev, 2)) { + snd_printk(KERN_ERR "PnP configure failure (wrong ports)\n"); + return -ENOENT; + } + port[dev] = pnp_port_start(pdev, 0); + dma1[dev] = pnp_dma(pdev, 0); + if (dma2[dev] >= 0) + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("isapnp IW: sb port=0x%llx, gf1 port=0x%llx, codec port=0x%llx\n", + (unsigned long long)pnp_port_start(pdev, 0), + (unsigned long long)pnp_port_start(pdev, 1), + (unsigned long long)pnp_port_start(pdev, 2)); + snd_printdd("isapnp IW: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]); +#ifdef SNDRV_STB + /* Tone Control initialization */ + pdev = iwcard->devtc; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "InterWave ToneControl PnP configure failure (out of resources?)\n"); + return err; + } + port_tc[dev] = pnp_port_start(pdev, 0); + snd_printdd("isapnp IW: tone control port=0x%lx\n", port_tc[dev]); +#endif + return 0; +} +#endif /* CONFIG_PNP */ + +static void snd_interwave_free(struct snd_card *card) +{ + struct snd_interwave *iwcard = card->private_data; + + if (iwcard == NULL) + return; +#ifdef SNDRV_STB + release_and_free_resource(iwcard->i2c_res); +#endif + if (iwcard->irq >= 0) + free_irq(iwcard->irq, (void *)iwcard); +} + +static int snd_interwave_card_new(struct device *pdev, int dev, + struct snd_card **cardp) +{ + struct snd_card *card; + struct snd_interwave *iwcard; + int err; + + err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_interwave), &card); + if (err < 0) + return err; + iwcard = card->private_data; + iwcard->card = card; + iwcard->irq = -1; + card->private_free = snd_interwave_free; + *cardp = card; + return 0; +} + +static int snd_interwave_probe(struct snd_card *card, int dev) +{ + int xirq, xdma1, xdma2; + struct snd_interwave *iwcard = card->private_data; + struct snd_wss *wss; + struct snd_gus_card *gus; +#ifdef SNDRV_STB + struct snd_i2c_bus *i2c_bus; +#endif + char *str; + int err; + + xirq = irq[dev]; + xdma1 = dma1[dev]; + xdma2 = dma2[dev]; + + if ((err = snd_gus_create(card, + port[dev], + -xirq, xdma1, xdma2, + 0, 32, + pcm_channels[dev], effect[dev], &gus)) < 0) + return err; + + if ((err = snd_interwave_detect(iwcard, gus, dev +#ifdef SNDRV_STB + , &i2c_bus +#endif + )) < 0) + return err; + + iwcard->gus_status_reg = gus->gf1.reg_irqstat; + iwcard->pcm_status_reg = gus->gf1.port + 0x10c + 2; + + snd_interwave_init(dev, gus); + snd_interwave_detect_memory(gus); + if ((err = snd_gus_initialize(gus)) < 0) + return err; + + if (request_irq(xirq, snd_interwave_interrupt, 0, + "InterWave", iwcard)) { + snd_printk(KERN_ERR PFX "unable to grab IRQ %d\n", xirq); + return -EBUSY; + } + iwcard->irq = xirq; + + err = snd_wss_create(card, + gus->gf1.port + 0x10c, -1, xirq, + xdma2 < 0 ? xdma1 : xdma2, xdma1, + WSS_HW_INTERWAVE, + WSS_HWSHARE_IRQ | + WSS_HWSHARE_DMA1 | + WSS_HWSHARE_DMA2, + &wss); + if (err < 0) + return err; + + err = snd_wss_pcm(wss, 0); + if (err < 0) + return err; + + sprintf(wss->pcm->name + strlen(wss->pcm->name), " rev %c", + gus->revision + 'A'); + strcat(wss->pcm->name, " (codec)"); + + err = snd_wss_timer(wss, 2); + if (err < 0) + return err; + + err = snd_wss_mixer(wss); + if (err < 0) + return err; + + if (pcm_channels[dev] > 0) { + err = snd_gf1_pcm_new(gus, 1, 1); + if (err < 0) + return err; + } + err = snd_interwave_mixer(wss); + if (err < 0) + return err; + +#ifdef SNDRV_STB + { + struct snd_ctl_elem_id id1, id2; + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strcpy(id1.name, "Master Playback Switch"); + strcpy(id2.name, id1.name); + id2.index = 1; + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Master Playback Volume"); + strcpy(id2.name, id1.name); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + if ((err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1)) < 0) + return err; + } +#endif + + gus->uart_enable = midi[dev]; + if ((err = snd_gf1_rawmidi_new(gus, 0)) < 0) + return err; + +#ifndef SNDRV_STB + str = "AMD InterWave"; + if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8) + str = "Dynasonic 3-D"; +#else + str = "InterWave STB"; +#endif + strcpy(card->driver, str); + strcpy(card->shortname, str); + sprintf(card->longname, "%s at 0x%lx, irq %i, dma %d", + str, + gus->gf1.port, + xirq, + xdma1); + if (xdma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", xdma2); + + err = snd_card_register(card); + if (err < 0) + return err; + + iwcard->wss = wss; + iwcard->gus = gus; + return 0; +} + +static int snd_interwave_isa_probe1(int dev, struct device *devptr) +{ + struct snd_card *card; + int err; + + err = snd_interwave_card_new(devptr, dev, &card); + if (err < 0) + return err; + + if ((err = snd_interwave_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + dev_set_drvdata(devptr, card); + return 0; +} + +static int snd_interwave_isa_match(struct device *pdev, + unsigned int dev) +{ + if (!enable[dev]) + return 0; +#ifdef CONFIG_PNP + if (isapnp[dev]) + return 0; +#endif + return 1; +} + +static int snd_interwave_isa_probe(struct device *pdev, + unsigned int dev) +{ + int err; + static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1}; + static int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1}; + + if (irq[dev] == SNDRV_AUTO_IRQ) { + if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma1[dev] == SNDRV_AUTO_DMA) { + if ((dma1[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (dma2[dev] == SNDRV_AUTO_DMA) { + if ((dma2[dev] = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if (port[dev] != SNDRV_AUTO_PORT) + return snd_interwave_isa_probe1(dev, pdev); + else { + static long possible_ports[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260}; + int i; + for (i = 0; i < ARRAY_SIZE(possible_ports); i++) { + port[dev] = possible_ports[i]; + err = snd_interwave_isa_probe1(dev, pdev); + if (! err) + return 0; + } + return err; + } +} + +static int snd_interwave_isa_remove(struct device *devptr, unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +static struct isa_driver snd_interwave_driver = { + .match = snd_interwave_isa_match, + .probe = snd_interwave_isa_probe, + .remove = snd_interwave_isa_remove, + /* FIXME: suspend,resume */ + .driver = { + .name = INTERWAVE_DRIVER + }, +}; + +#ifdef CONFIG_PNP +static int snd_interwave_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int dev; + struct snd_card *card; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + res = snd_interwave_card_new(&pcard->card->dev, dev, &card); + if (res < 0) + return res; + + if ((res = snd_interwave_pnp(dev, card->private_data, pcard, pid)) < 0) { + snd_card_free(card); + return res; + } + if ((res = snd_interwave_probe(card, dev)) < 0) { + snd_card_free(card); + return res; + } + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; +} + +static void snd_interwave_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +static struct pnp_card_driver interwave_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = INTERWAVE_PNP_DRIVER, + .id_table = snd_interwave_pnpids, + .probe = snd_interwave_pnp_detect, + .remove = snd_interwave_pnp_remove, + /* FIXME: suspend,resume */ +}; + +#endif /* CONFIG_PNP */ + +static int __init alsa_card_interwave_init(void) +{ + int err; + + err = isa_register_driver(&snd_interwave_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_card_driver(&interwave_pnpc_driver); + if (!err) + pnp_registered = 1; + + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_interwave_exit(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_card_driver(&interwave_pnpc_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_interwave_driver); +} + +module_init(alsa_card_interwave_init) +module_exit(alsa_card_interwave_exit) diff --git a/sound/isa/msnd/Makefile b/sound/isa/msnd/Makefile new file mode 100644 index 000000000..ec231a7b1 --- /dev/null +++ b/sound/isa/msnd/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +snd-msnd-lib-objs := msnd.o msnd_midi.o msnd_pinnacle_mixer.o +snd-msnd-pinnacle-objs := msnd_pinnacle.o +snd-msnd-classic-objs := msnd_classic.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_MSND_PINNACLE) += snd-msnd-pinnacle.o snd-msnd-lib.o +obj-$(CONFIG_SND_MSND_CLASSIC) += snd-msnd-classic.o snd-msnd-lib.o + diff --git a/sound/isa/msnd/msnd.c b/sound/isa/msnd/msnd.c new file mode 100644 index 000000000..7c3203fe4 --- /dev/null +++ b/sound/isa/msnd/msnd.c @@ -0,0 +1,705 @@ +/********************************************************************* + * + * 2002/06/30 Karsten Wiese: + * removed kernel-version dependencies. + * ripped from linux kernel 2.4.18 (OSS Implementation) by me. + * In the OSS Version, this file is compiled to a separate MODULE, + * that is used by the pinnacle and the classic driver. + * since there is no classic driver for alsa yet (i dont have a classic + * & writing one blindfold is difficult) this file's object is statically + * linked into the pinnacle-driver-module for now. look for the string + * "uncomment this to make this a module again" + * to do guess what. + * + * the following is a copy of the 2.4.18 OSS FREE file-heading comment: + * + * msnd.c - Driver Base + * + * Turtle Beach MultiSound Sound Card Driver for Linux + * + * Copyright (C) 1998 Andrew Veliath + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "msnd.h" + +#define LOGNAME "msnd" + + +void snd_msnd_init_queue(void __iomem *base, int start, int size) +{ + writew(PCTODSP_BASED(start), base + JQS_wStart); + writew(PCTODSP_OFFSET(size) - 1, base + JQS_wSize); + writew(0, base + JQS_wHead); + writew(0, base + JQS_wTail); +} +EXPORT_SYMBOL(snd_msnd_init_queue); + +static int snd_msnd_wait_TXDE(struct snd_msnd *dev) +{ + unsigned int io = dev->io; + int timeout = 1000; + + while (timeout-- > 0) + if (inb(io + HP_ISR) & HPISR_TXDE) + return 0; + + return -EIO; +} + +static int snd_msnd_wait_HC0(struct snd_msnd *dev) +{ + unsigned int io = dev->io; + int timeout = 1000; + + while (timeout-- > 0) + if (!(inb(io + HP_CVR) & HPCVR_HC)) + return 0; + + return -EIO; +} + +int snd_msnd_send_dsp_cmd(struct snd_msnd *dev, u8 cmd) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (snd_msnd_wait_HC0(dev) == 0) { + outb(cmd, dev->io + HP_CVR); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + spin_unlock_irqrestore(&dev->lock, flags); + + snd_printd(KERN_ERR LOGNAME ": Send DSP command timeout\n"); + + return -EIO; +} +EXPORT_SYMBOL(snd_msnd_send_dsp_cmd); + +int snd_msnd_send_word(struct snd_msnd *dev, unsigned char high, + unsigned char mid, unsigned char low) +{ + unsigned int io = dev->io; + + if (snd_msnd_wait_TXDE(dev) == 0) { + outb(high, io + HP_TXH); + outb(mid, io + HP_TXM); + outb(low, io + HP_TXL); + return 0; + } + + snd_printd(KERN_ERR LOGNAME ": Send host word timeout\n"); + + return -EIO; +} +EXPORT_SYMBOL(snd_msnd_send_word); + +int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len) +{ + int i; + + if (len % 3 != 0) { + snd_printk(KERN_ERR LOGNAME + ": Upload host data not multiple of 3!\n"); + return -EINVAL; + } + + for (i = 0; i < len; i += 3) + if (snd_msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2])) + return -EIO; + + inb(dev->io + HP_RXL); + inb(dev->io + HP_CVR); + + return 0; +} +EXPORT_SYMBOL(snd_msnd_upload_host); + +int snd_msnd_enable_irq(struct snd_msnd *dev) +{ + unsigned long flags; + + if (dev->irq_ref++) + return 0; + + snd_printdd(LOGNAME ": Enabling IRQ\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (snd_msnd_wait_TXDE(dev) == 0) { + outb(inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR); + if (dev->type == msndClassic) + outb(dev->irqid, dev->io + HP_IRQM); + + outb(inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR); + outb(inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR); + enable_irq(dev->irq); + snd_msnd_init_queue(dev->DSPQ, dev->dspq_data_buff, + dev->dspq_buff_size); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + spin_unlock_irqrestore(&dev->lock, flags); + + snd_printd(KERN_ERR LOGNAME ": Enable IRQ failed\n"); + + return -EIO; +} +EXPORT_SYMBOL(snd_msnd_enable_irq); + +int snd_msnd_disable_irq(struct snd_msnd *dev) +{ + unsigned long flags; + + if (--dev->irq_ref > 0) + return 0; + + if (dev->irq_ref < 0) + snd_printd(KERN_WARNING LOGNAME ": IRQ ref count is %d\n", + dev->irq_ref); + + snd_printdd(LOGNAME ": Disabling IRQ\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (snd_msnd_wait_TXDE(dev) == 0) { + outb(inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR); + if (dev->type == msndClassic) + outb(HPIRQ_NONE, dev->io + HP_IRQM); + disable_irq(dev->irq); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + spin_unlock_irqrestore(&dev->lock, flags); + + snd_printd(KERN_ERR LOGNAME ": Disable IRQ failed\n"); + + return -EIO; +} +EXPORT_SYMBOL(snd_msnd_disable_irq); + +static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size) +{ + long tmp = (size * HZ * chip->play_sample_size) / 8; + return tmp / (chip->play_sample_rate * chip->play_channels); +} + +static void snd_msnd_dsp_write_flush(struct snd_msnd *chip) +{ + if (!(chip->mode & FMODE_WRITE) || !test_bit(F_WRITING, &chip->flags)) + return; + set_bit(F_WRITEFLUSH, &chip->flags); +/* interruptible_sleep_on_timeout( + &chip->writeflush, + get_play_delay_jiffies(&chip, chip->DAPF.len));*/ + clear_bit(F_WRITEFLUSH, &chip->flags); + if (!signal_pending(current)) + schedule_timeout_interruptible( + get_play_delay_jiffies(chip, chip->play_period_bytes)); + clear_bit(F_WRITING, &chip->flags); +} + +void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file) +{ + if ((file ? file->f_mode : chip->mode) & FMODE_READ) { + clear_bit(F_READING, &chip->flags); + snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); + snd_msnd_disable_irq(chip); + if (file) { + snd_printd(KERN_INFO LOGNAME + ": Stopping read for %p\n", file); + chip->mode &= ~FMODE_READ; + } + clear_bit(F_AUDIO_READ_INUSE, &chip->flags); + } + if ((file ? file->f_mode : chip->mode) & FMODE_WRITE) { + if (test_bit(F_WRITING, &chip->flags)) { + snd_msnd_dsp_write_flush(chip); + snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); + } + snd_msnd_disable_irq(chip); + if (file) { + snd_printd(KERN_INFO + LOGNAME ": Stopping write for %p\n", file); + chip->mode &= ~FMODE_WRITE; + } + clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags); + } +} +EXPORT_SYMBOL(snd_msnd_dsp_halt); + + +int snd_msnd_DARQ(struct snd_msnd *chip, int bank) +{ + int /*size, n,*/ timeout = 3; + u16 wTmp; + /* void *DAQD; */ + + /* Increment the tail and check for queue wrap */ + wTmp = readw(chip->DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size); + if (wTmp > readw(chip->DARQ + JQS_wSize)) + wTmp = 0; + while (wTmp == readw(chip->DARQ + JQS_wHead) && timeout--) + udelay(1); + + if (chip->capturePeriods == 2) { + void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF + + bank * DAQDS__size + DAQDS_wStart; + unsigned short offset = 0x3000 + chip->capturePeriodBytes; + + if (readw(pDAQ) != PCTODSP_BASED(0x3000)) + offset = 0x3000; + writew(PCTODSP_BASED(offset), pDAQ); + } + + writew(wTmp, chip->DARQ + JQS_wTail); + +#if 0 + /* Get our digital audio queue struct */ + DAQD = bank * DAQDS__size + chip->mappedbase + DARQ_DATA_BUFF; + + /* Get length of data */ + size = readw(DAQD + DAQDS_wSize); + + /* Read data from the head (unprotected bank 1 access okay + since this is only called inside an interrupt) */ + outb(HPBLKSEL_1, chip->io + HP_BLKS); + n = msnd_fifo_write(&chip->DARF, + (char *)(chip->base + bank * DAR_BUFF_SIZE), + size, 0); + if (n <= 0) { + outb(HPBLKSEL_0, chip->io + HP_BLKS); + return n; + } + outb(HPBLKSEL_0, chip->io + HP_BLKS); +#endif + + return 1; +} +EXPORT_SYMBOL(snd_msnd_DARQ); + +int snd_msnd_DAPQ(struct snd_msnd *chip, int start) +{ + u16 DAPQ_tail; + int protect = start, nbanks = 0; + void __iomem *DAQD; + static int play_banks_submitted; + /* unsigned long flags; + spin_lock_irqsave(&chip->lock, flags); not necessary */ + + DAPQ_tail = readw(chip->DAPQ + JQS_wTail); + while (DAPQ_tail != readw(chip->DAPQ + JQS_wHead) || start) { + int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size); + + if (start) { + start = 0; + play_banks_submitted = 0; + } + + /* Get our digital audio queue struct */ + DAQD = bank_num * DAQDS__size + chip->mappedbase + + DAPQ_DATA_BUFF; + + /* Write size of this bank */ + writew(chip->play_period_bytes, DAQD + DAQDS_wSize); + if (play_banks_submitted < 3) + ++play_banks_submitted; + else if (chip->playPeriods == 2) { + unsigned short offset = chip->play_period_bytes; + + if (readw(DAQD + DAQDS_wStart) != PCTODSP_BASED(0x0)) + offset = 0; + + writew(PCTODSP_BASED(offset), DAQD + DAQDS_wStart); + } + ++nbanks; + + /* Then advance the tail */ + /* + if (protect) + snd_printd(KERN_INFO "B %X %lX\n", + bank_num, xtime.tv_usec); + */ + + DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size); + writew(DAPQ_tail, chip->DAPQ + JQS_wTail); + /* Tell the DSP to play the bank */ + snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_START); + if (protect) + if (2 == bank_num) + break; + } + /* + if (protect) + snd_printd(KERN_INFO "%lX\n", xtime.tv_usec); + */ + /* spin_unlock_irqrestore(&chip->lock, flags); not necessary */ + return nbanks; +} +EXPORT_SYMBOL(snd_msnd_DAPQ); + +static void snd_msnd_play_reset_queue(struct snd_msnd *chip, + unsigned int pcm_periods, + unsigned int pcm_count) +{ + int n; + void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF; + + chip->last_playbank = -1; + chip->playLimit = pcm_count * (pcm_periods - 1); + chip->playPeriods = pcm_periods; + writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wHead); + writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wTail); + + chip->play_period_bytes = pcm_count; + + for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) { + writew(PCTODSP_BASED((u32)(pcm_count * n)), + pDAQ + DAQDS_wStart); + writew(0, pDAQ + DAQDS_wSize); + writew(1, pDAQ + DAQDS_wFormat); + writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize); + writew(chip->play_channels, pDAQ + DAQDS_wChannels); + writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate); + writew(HIMT_PLAY_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg); + writew(n, pDAQ + DAQDS_wFlags); + } +} + +static void snd_msnd_capture_reset_queue(struct snd_msnd *chip, + unsigned int pcm_periods, + unsigned int pcm_count) +{ + int n; + void __iomem *pDAQ; + /* unsigned long flags; */ + + /* snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); */ + + chip->last_recbank = 2; + chip->captureLimit = pcm_count * (pcm_periods - 1); + chip->capturePeriods = pcm_periods; + writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DARQ + JQS_wHead); + writew(PCTODSP_OFFSET(chip->last_recbank * DAQDS__size), + chip->DARQ + JQS_wTail); + +#if 0 /* Critical section: bank 1 access. this is how the OSS driver does it:*/ + spin_lock_irqsave(&chip->lock, flags); + outb(HPBLKSEL_1, chip->io + HP_BLKS); + memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3); + outb(HPBLKSEL_0, chip->io + HP_BLKS); + spin_unlock_irqrestore(&chip->lock, flags); +#endif + + chip->capturePeriodBytes = pcm_count; + snd_printdd("snd_msnd_capture_reset_queue() %i\n", pcm_count); + + pDAQ = chip->mappedbase + DARQ_DATA_BUFF; + + for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) { + u32 tmp = pcm_count * n; + + writew(PCTODSP_BASED(tmp + 0x3000), pDAQ + DAQDS_wStart); + writew(pcm_count, pDAQ + DAQDS_wSize); + writew(1, pDAQ + DAQDS_wFormat); + writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize); + writew(chip->capture_channels, pDAQ + DAQDS_wChannels); + writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate); + writew(HIMT_RECORD_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg); + writew(n, pDAQ + DAQDS_wFlags); + } +} + +static const struct snd_pcm_hardware snd_msnd_playback = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x3000, + .period_bytes_min = 0x40, + .period_bytes_max = 0x1800, + .periods_min = 2, + .periods_max = 3, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_msnd_capture = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x3000, + .period_bytes_min = 0x40, + .period_bytes_max = 0x1800, + .periods_min = 2, + .periods_max = 3, + .fifo_size = 0, +}; + + +static int snd_msnd_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + + set_bit(F_AUDIO_WRITE_INUSE, &chip->flags); + clear_bit(F_WRITING, &chip->flags); + snd_msnd_enable_irq(chip); + + runtime->dma_area = (__force void *)chip->mappedbase; + runtime->dma_bytes = 0x3000; + + chip->playback_substream = substream; + runtime->hw = snd_msnd_playback; + return 0; +} + +static int snd_msnd_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + + snd_msnd_disable_irq(chip); + clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags); + return 0; +} + + +static int snd_msnd_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int i; + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF; + + chip->play_sample_size = snd_pcm_format_width(params_format(params)); + chip->play_channels = params_channels(params); + chip->play_sample_rate = params_rate(params); + + for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) { + writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize); + writew(chip->play_channels, pDAQ + DAQDS_wChannels); + writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate); + } + /* dont do this here: + * snd_msnd_calibrate_adc(chip->play_sample_rate); + */ + + return 0; +} + +static int snd_msnd_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream); + unsigned int pcm_count = snd_pcm_lib_period_bytes(substream); + unsigned int pcm_periods = pcm_size / pcm_count; + + snd_msnd_play_reset_queue(chip, pcm_periods, pcm_count); + chip->playDMAPos = 0; + return 0; +} + +static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + int result = 0; + + if (cmd == SNDRV_PCM_TRIGGER_START) { + snd_printdd("snd_msnd_playback_trigger(START)\n"); + chip->banksPlayed = 0; + set_bit(F_WRITING, &chip->flags); + snd_msnd_DAPQ(chip, 1); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + snd_printdd("snd_msnd_playback_trigger(STop)\n"); + /* interrupt diagnostic, comment this out later */ + clear_bit(F_WRITING, &chip->flags); + snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); + } else { + snd_printd(KERN_ERR "snd_msnd_playback_trigger(?????)\n"); + result = -EINVAL; + } + + snd_printdd("snd_msnd_playback_trigger() ENDE\n"); + return result; +} + +static snd_pcm_uframes_t +snd_msnd_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + + return bytes_to_frames(substream->runtime, chip->playDMAPos); +} + + +static const struct snd_pcm_ops snd_msnd_playback_ops = { + .open = snd_msnd_playback_open, + .close = snd_msnd_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_msnd_playback_hw_params, + .prepare = snd_msnd_playback_prepare, + .trigger = snd_msnd_playback_trigger, + .pointer = snd_msnd_playback_pointer, +}; + +static int snd_msnd_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + + set_bit(F_AUDIO_READ_INUSE, &chip->flags); + snd_msnd_enable_irq(chip); + runtime->dma_area = (__force void *)chip->mappedbase + 0x3000; + runtime->dma_bytes = 0x3000; + memset(runtime->dma_area, 0, runtime->dma_bytes); + chip->capture_substream = substream; + runtime->hw = snd_msnd_capture; + return 0; +} + +static int snd_msnd_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + + snd_msnd_disable_irq(chip); + clear_bit(F_AUDIO_READ_INUSE, &chip->flags); + return 0; +} + +static int snd_msnd_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream); + unsigned int pcm_count = snd_pcm_lib_period_bytes(substream); + unsigned int pcm_periods = pcm_size / pcm_count; + + snd_msnd_capture_reset_queue(chip, pcm_periods, pcm_count); + chip->captureDMAPos = 0; + return 0; +} + +static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + + if (cmd == SNDRV_PCM_TRIGGER_START) { + chip->last_recbank = -1; + set_bit(F_READING, &chip->flags); + if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0) + return 0; + + clear_bit(F_READING, &chip->flags); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + clear_bit(F_READING, &chip->flags); + snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); + return 0; + } + return -EINVAL; +} + + +static snd_pcm_uframes_t +snd_msnd_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + + return bytes_to_frames(runtime, chip->captureDMAPos); +} + + +static int snd_msnd_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int i; + struct snd_msnd *chip = snd_pcm_substream_chip(substream); + void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF; + + chip->capture_sample_size = snd_pcm_format_width(params_format(params)); + chip->capture_channels = params_channels(params); + chip->capture_sample_rate = params_rate(params); + + for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) { + writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize); + writew(chip->capture_channels, pDAQ + DAQDS_wChannels); + writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate); + } + return 0; +} + + +static const struct snd_pcm_ops snd_msnd_capture_ops = { + .open = snd_msnd_capture_open, + .close = snd_msnd_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_msnd_capture_hw_params, + .prepare = snd_msnd_capture_prepare, + .trigger = snd_msnd_capture_trigger, + .pointer = snd_msnd_capture_pointer, +}; + + +int snd_msnd_pcm(struct snd_card *card, int device) +{ + struct snd_msnd *chip = card->private_data; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(card, "MSNDPINNACLE", device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_msnd_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_msnd_capture_ops); + + pcm->private_data = chip; + strcpy(pcm->name, "Hurricane"); + + return 0; +} +EXPORT_SYMBOL(snd_msnd_pcm); + +MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers"); +MODULE_LICENSE("GPL"); + diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h new file mode 100644 index 000000000..80c718757 --- /dev/null +++ b/sound/isa/msnd/msnd.h @@ -0,0 +1,308 @@ +/********************************************************************* + * + * msnd.h + * + * Turtle Beach MultiSound Sound Card Driver for Linux + * + * Some parts of this header file were derived from the Turtle Beach + * MultiSound Driver Development Kit. + * + * Copyright (C) 1998 Andrew Veliath + * Copyright (C) 1993 Turtle Beach Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ********************************************************************/ +#ifndef __MSND_H +#define __MSND_H + +#define DEFSAMPLERATE 44100 +#define DEFSAMPLESIZE SNDRV_PCM_FORMAT_S16 +#define DEFCHANNELS 1 + +#define SRAM_BANK_SIZE 0x8000 +#define SRAM_CNTL_START 0x7F00 +#define SMA_STRUCT_START 0x7F40 + +#define DSP_BASE_ADDR 0x4000 +#define DSP_BANK_BASE 0x4000 + +#define AGND 0x01 +#define SIGNAL 0x02 + +#define EXT_DSP_BIT_DCAL 0x0001 +#define EXT_DSP_BIT_MIDI_CON 0x0002 + +#define BUFFSIZE 0x8000 +#define HOSTQ_SIZE 0x40 + +#define DAP_BUFF_SIZE 0x2400 + +#define DAPQ_STRUCT_SIZE 0x10 +#define DARQ_STRUCT_SIZE 0x10 +#define DAPQ_BUFF_SIZE (3 * 0x10) +#define DARQ_BUFF_SIZE (3 * 0x10) +#define MODQ_BUFF_SIZE 0x400 + +#define DAPQ_DATA_BUFF 0x6C00 +#define DARQ_DATA_BUFF 0x6C30 +#define MODQ_DATA_BUFF 0x6C60 +#define MIDQ_DATA_BUFF 0x7060 + +#define DAPQ_OFFSET SRAM_CNTL_START +#define DARQ_OFFSET (SRAM_CNTL_START + 0x08) +#define MODQ_OFFSET (SRAM_CNTL_START + 0x10) +#define MIDQ_OFFSET (SRAM_CNTL_START + 0x18) +#define DSPQ_OFFSET (SRAM_CNTL_START + 0x20) + +#define HP_ICR 0x00 +#define HP_CVR 0x01 +#define HP_ISR 0x02 +#define HP_IVR 0x03 +#define HP_NU 0x04 +#define HP_INFO 0x04 +#define HP_TXH 0x05 +#define HP_RXH 0x05 +#define HP_TXM 0x06 +#define HP_RXM 0x06 +#define HP_TXL 0x07 +#define HP_RXL 0x07 + +#define HP_ICR_DEF 0x00 +#define HP_CVR_DEF 0x12 +#define HP_ISR_DEF 0x06 +#define HP_IVR_DEF 0x0f +#define HP_NU_DEF 0x00 + +#define HP_IRQM 0x09 + +#define HPR_BLRC 0x08 +#define HPR_SPR1 0x09 +#define HPR_SPR2 0x0A +#define HPR_TCL0 0x0B +#define HPR_TCL1 0x0C +#define HPR_TCL2 0x0D +#define HPR_TCL3 0x0E +#define HPR_TCL4 0x0F + +#define HPICR_INIT 0x80 +#define HPICR_HM1 0x40 +#define HPICR_HM0 0x20 +#define HPICR_HF1 0x10 +#define HPICR_HF0 0x08 +#define HPICR_TREQ 0x02 +#define HPICR_RREQ 0x01 + +#define HPCVR_HC 0x80 + +#define HPISR_HREQ 0x80 +#define HPISR_DMA 0x40 +#define HPISR_HF3 0x10 +#define HPISR_HF2 0x08 +#define HPISR_TRDY 0x04 +#define HPISR_TXDE 0x02 +#define HPISR_RXDF 0x01 + +#define HPIO_290 0 +#define HPIO_260 1 +#define HPIO_250 2 +#define HPIO_240 3 +#define HPIO_230 4 +#define HPIO_220 5 +#define HPIO_210 6 +#define HPIO_3E0 7 + +#define HPMEM_NONE 0 +#define HPMEM_B000 1 +#define HPMEM_C800 2 +#define HPMEM_D000 3 +#define HPMEM_D400 4 +#define HPMEM_D800 5 +#define HPMEM_E000 6 +#define HPMEM_E800 7 + +#define HPIRQ_NONE 0 +#define HPIRQ_5 1 +#define HPIRQ_7 2 +#define HPIRQ_9 3 +#define HPIRQ_10 4 +#define HPIRQ_11 5 +#define HPIRQ_12 6 +#define HPIRQ_15 7 + +#define HIMT_PLAY_DONE 0x00 +#define HIMT_RECORD_DONE 0x01 +#define HIMT_MIDI_EOS 0x02 +#define HIMT_MIDI_OUT 0x03 + +#define HIMT_MIDI_IN_UCHAR 0x0E +#define HIMT_DSP 0x0F + +#define HDEX_BASE 0x92 +#define HDEX_PLAY_START (0 + HDEX_BASE) +#define HDEX_PLAY_STOP (1 + HDEX_BASE) +#define HDEX_PLAY_PAUSE (2 + HDEX_BASE) +#define HDEX_PLAY_RESUME (3 + HDEX_BASE) +#define HDEX_RECORD_START (4 + HDEX_BASE) +#define HDEX_RECORD_STOP (5 + HDEX_BASE) +#define HDEX_MIDI_IN_START (6 + HDEX_BASE) +#define HDEX_MIDI_IN_STOP (7 + HDEX_BASE) +#define HDEX_MIDI_OUT_START (8 + HDEX_BASE) +#define HDEX_MIDI_OUT_STOP (9 + HDEX_BASE) +#define HDEX_AUX_REQ (10 + HDEX_BASE) + +#define HDEXAR_CLEAR_PEAKS 1 +#define HDEXAR_IN_SET_POTS 2 +#define HDEXAR_AUX_SET_POTS 3 +#define HDEXAR_CAL_A_TO_D 4 +#define HDEXAR_RD_EXT_DSP_BITS 5 + +/* Pinnacle only HDEXAR defs */ +#define HDEXAR_SET_ANA_IN 0 +#define HDEXAR_SET_SYNTH_IN 4 +#define HDEXAR_READ_DAT_IN 5 +#define HDEXAR_MIC_SET_POTS 6 +#define HDEXAR_SET_DAT_IN 7 + +#define HDEXAR_SET_SYNTH_48 8 +#define HDEXAR_SET_SYNTH_44 9 + +#define HIWORD(l) ((u16)((((u32)(l)) >> 16) & 0xFFFF)) +#define LOWORD(l) ((u16)(u32)(l)) +#define HIBYTE(w) ((u8)(((u16)(w) >> 8) & 0xFF)) +#define LOBYTE(w) ((u8)(w)) +#define MAKELONG(low, hi) ((long)(((u16)(low))|(((u32)((u16)(hi)))<<16))) +#define MAKEWORD(low, hi) ((u16)(((u8)(low))|(((u16)((u8)(hi)))<<8))) + +#define PCTODSP_OFFSET(w) (u16)((w)/2) +#define PCTODSP_BASED(w) (u16)(((w)/2) + DSP_BASE_ADDR) +#define DSPTOPC_BASED(w) (((w) - DSP_BASE_ADDR) * 2) + +#ifdef SLOWIO +# undef outb +# undef inb +# define outb outb_p +# define inb inb_p +#endif + +/* JobQueueStruct */ +#define JQS_wStart 0x00 +#define JQS_wSize 0x02 +#define JQS_wHead 0x04 +#define JQS_wTail 0x06 +#define JQS__size 0x08 + +/* DAQueueDataStruct */ +#define DAQDS_wStart 0x00 +#define DAQDS_wSize 0x02 +#define DAQDS_wFormat 0x04 +#define DAQDS_wSampleSize 0x06 +#define DAQDS_wChannels 0x08 +#define DAQDS_wSampleRate 0x0A +#define DAQDS_wIntMsg 0x0C +#define DAQDS_wFlags 0x0E +#define DAQDS__size 0x10 + +#include + +struct snd_msnd { + void __iomem *mappedbase; + int play_period_bytes; + int playLimit; + int playPeriods; + int playDMAPos; + int banksPlayed; + int captureDMAPos; + int capturePeriodBytes; + int captureLimit; + int capturePeriods; + struct snd_card *card; + void *msndmidi_mpu; + struct snd_rawmidi *rmidi; + + /* Hardware resources */ + long io; + int memid, irqid; + int irq, irq_ref; + unsigned long base; + + /* Motorola 56k DSP SMA */ + void __iomem *SMA; + void __iomem *DAPQ; + void __iomem *DARQ; + void __iomem *MODQ; + void __iomem *MIDQ; + void __iomem *DSPQ; + int dspq_data_buff, dspq_buff_size; + + /* State variables */ + enum { msndClassic, msndPinnacle } type; + fmode_t mode; + unsigned long flags; +#define F_RESETTING 0 +#define F_HAVEDIGITAL 1 +#define F_AUDIO_WRITE_INUSE 2 +#define F_WRITING 3 +#define F_WRITEBLOCK 4 +#define F_WRITEFLUSH 5 +#define F_AUDIO_READ_INUSE 6 +#define F_READING 7 +#define F_READBLOCK 8 +#define F_EXT_MIDI_INUSE 9 +#define F_HDR_MIDI_INUSE 10 +#define F_DISABLE_WRITE_NDELAY 11 + spinlock_t lock; + spinlock_t mixer_lock; + int nresets; + unsigned recsrc; +#define LEVEL_ENTRIES 32 + int left_levels[LEVEL_ENTRIES]; + int right_levels[LEVEL_ENTRIES]; + int calibrate_signal; + int play_sample_size, play_sample_rate, play_channels; + int play_ndelay; + int capture_sample_size, capture_sample_rate, capture_channels; + int capture_ndelay; + u8 bCurrentMidiPatch; + + int last_playbank, last_recbank; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + +}; + +void snd_msnd_init_queue(void __iomem *base, int start, int size); + +int snd_msnd_send_dsp_cmd(struct snd_msnd *chip, u8 cmd); +int snd_msnd_send_word(struct snd_msnd *chip, + unsigned char high, + unsigned char mid, + unsigned char low); +int snd_msnd_upload_host(struct snd_msnd *chip, + const u8 *bin, int len); +int snd_msnd_enable_irq(struct snd_msnd *chip); +int snd_msnd_disable_irq(struct snd_msnd *chip); +void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file); +int snd_msnd_DAPQ(struct snd_msnd *chip, int start); +int snd_msnd_DARQ(struct snd_msnd *chip, int start); +int snd_msnd_pcm(struct snd_card *card, int device); + +int snd_msndmidi_new(struct snd_card *card, int device); +void snd_msndmidi_input_read(void *mpu); + +void snd_msndmix_setup(struct snd_msnd *chip); +int snd_msndmix_new(struct snd_card *card); +int snd_msndmix_force_recsrc(struct snd_msnd *chip, int recsrc); +#endif /* __MSND_H */ diff --git a/sound/isa/msnd/msnd_classic.c b/sound/isa/msnd/msnd_classic.c new file mode 100644 index 000000000..3b23a096f --- /dev/null +++ b/sound/isa/msnd/msnd_classic.c @@ -0,0 +1,3 @@ +/* The work is in msnd_pinnacle.c, just define MSND_CLASSIC before it. */ +#define MSND_CLASSIC +#include "msnd_pinnacle.c" diff --git a/sound/isa/msnd/msnd_classic.h b/sound/isa/msnd/msnd_classic.h new file mode 100644 index 000000000..f18d5fa5b --- /dev/null +++ b/sound/isa/msnd/msnd_classic.h @@ -0,0 +1,129 @@ +/********************************************************************* + * + * msnd_classic.h + * + * Turtle Beach MultiSound Sound Card Driver for Linux + * + * Some parts of this header file were derived from the Turtle Beach + * MultiSound Driver Development Kit. + * + * Copyright (C) 1998 Andrew Veliath + * Copyright (C) 1993 Turtle Beach Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ********************************************************************/ +#ifndef __MSND_CLASSIC_H +#define __MSND_CLASSIC_H + +#define DSP_NUMIO 0x10 + +#define HP_MEMM 0x08 + +#define HP_BITM 0x0E +#define HP_WAIT 0x0D +#define HP_DSPR 0x0A +#define HP_PROR 0x0B +#define HP_BLKS 0x0C + +#define HPPRORESET_OFF 0 +#define HPPRORESET_ON 1 + +#define HPDSPRESET_OFF 0 +#define HPDSPRESET_ON 1 + +#define HPBLKSEL_0 0 +#define HPBLKSEL_1 1 + +#define HPWAITSTATE_0 0 +#define HPWAITSTATE_1 1 + +#define HPBITMODE_16 0 +#define HPBITMODE_8 1 + +#define HIDSP_INT_PLAY_UNDER 0x00 +#define HIDSP_INT_RECORD_OVER 0x01 +#define HIDSP_INPUT_CLIPPING 0x02 +#define HIDSP_MIDI_IN_OVER 0x10 +#define HIDSP_MIDI_OVERRUN_ERR 0x13 + +#define TIME_PRO_RESET_DONE 0x028A +#define TIME_PRO_SYSEX 0x0040 +#define TIME_PRO_RESET 0x0032 + +#define DAR_BUFF_SIZE 0x2000 + +#define MIDQ_BUFF_SIZE 0x200 +#define DSPQ_BUFF_SIZE 0x40 + +#define DSPQ_DATA_BUFF 0x7260 + +#define MOP_SYNTH 0x10 +#define MOP_EXTOUT 0x32 +#define MOP_EXTTHRU 0x02 +#define MOP_OUTMASK 0x01 + +#define MIP_EXTIN 0x01 +#define MIP_SYNTH 0x00 +#define MIP_INMASK 0x32 + +/* Classic SMA Common Data */ +#define SMA_wCurrPlayBytes 0x0000 +#define SMA_wCurrRecordBytes 0x0002 +#define SMA_wCurrPlayVolLeft 0x0004 +#define SMA_wCurrPlayVolRight 0x0006 +#define SMA_wCurrInVolLeft 0x0008 +#define SMA_wCurrInVolRight 0x000a +#define SMA_wUser_3 0x000c +#define SMA_wUser_4 0x000e +#define SMA_dwUser_5 0x0010 +#define SMA_dwUser_6 0x0014 +#define SMA_wUser_7 0x0018 +#define SMA_wReserved_A 0x001a +#define SMA_wReserved_B 0x001c +#define SMA_wReserved_C 0x001e +#define SMA_wReserved_D 0x0020 +#define SMA_wReserved_E 0x0022 +#define SMA_wReserved_F 0x0024 +#define SMA_wReserved_G 0x0026 +#define SMA_wReserved_H 0x0028 +#define SMA_wCurrDSPStatusFlags 0x002a +#define SMA_wCurrHostStatusFlags 0x002c +#define SMA_wCurrInputTagBits 0x002e +#define SMA_wCurrLeftPeak 0x0030 +#define SMA_wCurrRightPeak 0x0032 +#define SMA_wExtDSPbits 0x0034 +#define SMA_bExtHostbits 0x0036 +#define SMA_bBoardLevel 0x0037 +#define SMA_bInPotPosRight 0x0038 +#define SMA_bInPotPosLeft 0x0039 +#define SMA_bAuxPotPosRight 0x003a +#define SMA_bAuxPotPosLeft 0x003b +#define SMA_wCurrMastVolLeft 0x003c +#define SMA_wCurrMastVolRight 0x003e +#define SMA_bUser_12 0x0040 +#define SMA_bUser_13 0x0041 +#define SMA_wUser_14 0x0042 +#define SMA_wUser_15 0x0044 +#define SMA_wCalFreqAtoD 0x0046 +#define SMA_wUser_16 0x0048 +#define SMA_wUser_17 0x004a +#define SMA__size 0x004c + +#define INITCODEFILE "turtlebeach/msndinit.bin" +#define PERMCODEFILE "turtlebeach/msndperm.bin" +#define LONGNAME "MultiSound (Classic/Monterey/Tahiti)" + +#endif /* __MSND_CLASSIC_H */ diff --git a/sound/isa/msnd/msnd_midi.c b/sound/isa/msnd/msnd_midi.c new file mode 100644 index 000000000..42876b0cb --- /dev/null +++ b/sound/isa/msnd/msnd_midi.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Copyright (c) 2009 by Krzysztof Helt + * Routines for control of MPU-401 in UART mode + * + * MPU-401 supports UART mode which is not capable generate transmit + * interrupts thus output is done via polling. Also, if irq < 0, then + * input is done also via polling. Do not expect good performance. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msnd.h" + +#define MSNDMIDI_MODE_BIT_INPUT 0 +#define MSNDMIDI_MODE_BIT_OUTPUT 1 +#define MSNDMIDI_MODE_BIT_INPUT_TRIGGER 2 +#define MSNDMIDI_MODE_BIT_OUTPUT_TRIGGER 3 + +struct snd_msndmidi { + struct snd_msnd *dev; + + unsigned long mode; /* MSNDMIDI_MODE_XXXX */ + + struct snd_rawmidi_substream *substream_input; + + spinlock_t input_lock; +}; + +/* + * input/output open/close - protected by open_mutex in rawmidi.c + */ +static int snd_msndmidi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_msndmidi *mpu; + + snd_printdd("snd_msndmidi_input_open()\n"); + + mpu = substream->rmidi->private_data; + + mpu->substream_input = substream; + + snd_msnd_enable_irq(mpu->dev); + + snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_START); + set_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode); + return 0; +} + +static int snd_msndmidi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_msndmidi *mpu; + + mpu = substream->rmidi->private_data; + snd_msnd_send_dsp_cmd(mpu->dev, HDEX_MIDI_IN_STOP); + clear_bit(MSNDMIDI_MODE_BIT_INPUT, &mpu->mode); + mpu->substream_input = NULL; + snd_msnd_disable_irq(mpu->dev); + return 0; +} + +static void snd_msndmidi_input_drop(struct snd_msndmidi *mpu) +{ + u16 tail; + + tail = readw(mpu->dev->MIDQ + JQS_wTail); + writew(tail, mpu->dev->MIDQ + JQS_wHead); +} + +/* + * trigger input + */ +static void snd_msndmidi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct snd_msndmidi *mpu; + + snd_printdd("snd_msndmidi_input_trigger(, %i)\n", up); + + mpu = substream->rmidi->private_data; + spin_lock_irqsave(&mpu->input_lock, flags); + if (up) { + if (!test_and_set_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, + &mpu->mode)) + snd_msndmidi_input_drop(mpu); + } else { + clear_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode); + } + spin_unlock_irqrestore(&mpu->input_lock, flags); + if (up) + snd_msndmidi_input_read(mpu); +} + +void snd_msndmidi_input_read(void *mpuv) +{ + unsigned long flags; + struct snd_msndmidi *mpu = mpuv; + void __iomem *pwMIDQData = mpu->dev->mappedbase + MIDQ_DATA_BUFF; + u16 head, tail, size; + + spin_lock_irqsave(&mpu->input_lock, flags); + head = readw(mpu->dev->MIDQ + JQS_wHead); + tail = readw(mpu->dev->MIDQ + JQS_wTail); + size = readw(mpu->dev->MIDQ + JQS_wSize); + if (head > size || tail > size) + goto out; + while (head != tail) { + unsigned char val = readw(pwMIDQData + 2 * head); + + if (test_bit(MSNDMIDI_MODE_BIT_INPUT_TRIGGER, &mpu->mode)) + snd_rawmidi_receive(mpu->substream_input, &val, 1); + if (++head > size) + head = 0; + writew(head, mpu->dev->MIDQ + JQS_wHead); + } + out: + spin_unlock_irqrestore(&mpu->input_lock, flags); +} +EXPORT_SYMBOL(snd_msndmidi_input_read); + +static const struct snd_rawmidi_ops snd_msndmidi_input = { + .open = snd_msndmidi_input_open, + .close = snd_msndmidi_input_close, + .trigger = snd_msndmidi_input_trigger, +}; + +static void snd_msndmidi_free(struct snd_rawmidi *rmidi) +{ + struct snd_msndmidi *mpu = rmidi->private_data; + kfree(mpu); +} + +int snd_msndmidi_new(struct snd_card *card, int device) +{ + struct snd_msnd *chip = card->private_data; + struct snd_msndmidi *mpu; + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(card, "MSND-MIDI", device, 1, 1, &rmidi); + if (err < 0) + return err; + mpu = kzalloc(sizeof(*mpu), GFP_KERNEL); + if (mpu == NULL) { + snd_device_free(card, rmidi); + return -ENOMEM; + } + mpu->dev = chip; + chip->msndmidi_mpu = mpu; + rmidi->private_data = mpu; + rmidi->private_free = snd_msndmidi_free; + spin_lock_init(&mpu->input_lock); + strcpy(rmidi->name, "MSND MIDI"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_msndmidi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + return 0; +} diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c new file mode 100644 index 000000000..11af9c40b --- /dev/null +++ b/sound/isa/msnd/msnd_pinnacle.c @@ -0,0 +1,1243 @@ +/********************************************************************* + * + * Linux multisound pinnacle/fiji driver for ALSA. + * + * 2002/06/30 Karsten Wiese: + * for now this is only used to build a pinnacle / fiji driver. + * the OSS parent of this code is designed to also support + * the multisound classic via the file msnd_classic.c. + * to make it easier for some brave heart to implemt classic + * support in alsa, i left all the MSND_CLASSIC tokens in this file. + * but for now this untested & undone. + * + * + * ripped from linux kernel 2.4.18 by Karsten Wiese. + * + * the following is a copy of the 2.4.18 OSS FREE file-heading comment: + * + * Turtle Beach MultiSound Sound Card Driver for Linux + * msnd_pinnacle.c / msnd_classic.c + * + * -- If MSND_CLASSIC is defined: + * + * -> driver for Turtle Beach Classic/Monterey/Tahiti + * + * -- Else + * + * -> driver for Turtle Beach Pinnacle/Fiji + * + * 12-3-2000 Modified IO port validation Steve Sycamore + * + * Copyright (C) 1998 Andrew Veliath + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef MSND_CLASSIC +# ifndef __alpha__ +# define SLOWIO +# endif +#endif +#include "msnd.h" +#ifdef MSND_CLASSIC +# include "msnd_classic.h" +# define LOGNAME "msnd_classic" +# define DEV_NAME "msnd-classic" +#else +# include "msnd_pinnacle.h" +# define LOGNAME "snd_msnd_pinnacle" +# define DEV_NAME "msnd-pinnacle" +#endif + +static void set_default_audio_parameters(struct snd_msnd *chip) +{ + chip->play_sample_size = snd_pcm_format_width(DEFSAMPLESIZE); + chip->play_sample_rate = DEFSAMPLERATE; + chip->play_channels = DEFCHANNELS; + chip->capture_sample_size = snd_pcm_format_width(DEFSAMPLESIZE); + chip->capture_sample_rate = DEFSAMPLERATE; + chip->capture_channels = DEFCHANNELS; +} + +static void snd_msnd_eval_dsp_msg(struct snd_msnd *chip, u16 wMessage) +{ + switch (HIBYTE(wMessage)) { + case HIMT_PLAY_DONE: { + if (chip->banksPlayed < 3) + snd_printdd("%08X: HIMT_PLAY_DONE: %i\n", + (unsigned)jiffies, LOBYTE(wMessage)); + + if (chip->last_playbank == LOBYTE(wMessage)) { + snd_printdd("chip.last_playbank == LOBYTE(wMessage)\n"); + break; + } + chip->banksPlayed++; + + if (test_bit(F_WRITING, &chip->flags)) + snd_msnd_DAPQ(chip, 0); + + chip->last_playbank = LOBYTE(wMessage); + chip->playDMAPos += chip->play_period_bytes; + if (chip->playDMAPos > chip->playLimit) + chip->playDMAPos = 0; + snd_pcm_period_elapsed(chip->playback_substream); + + break; + } + case HIMT_RECORD_DONE: + if (chip->last_recbank == LOBYTE(wMessage)) + break; + chip->last_recbank = LOBYTE(wMessage); + chip->captureDMAPos += chip->capturePeriodBytes; + if (chip->captureDMAPos > (chip->captureLimit)) + chip->captureDMAPos = 0; + + if (test_bit(F_READING, &chip->flags)) + snd_msnd_DARQ(chip, chip->last_recbank); + + snd_pcm_period_elapsed(chip->capture_substream); + break; + + case HIMT_DSP: + switch (LOBYTE(wMessage)) { +#ifndef MSND_CLASSIC + case HIDSP_PLAY_UNDER: +#endif + case HIDSP_INT_PLAY_UNDER: + snd_printd(KERN_WARNING LOGNAME ": Play underflow %i\n", + chip->banksPlayed); + if (chip->banksPlayed > 2) + clear_bit(F_WRITING, &chip->flags); + break; + + case HIDSP_INT_RECORD_OVER: + snd_printd(KERN_WARNING LOGNAME ": Record overflow\n"); + clear_bit(F_READING, &chip->flags); + break; + + default: + snd_printd(KERN_WARNING LOGNAME + ": DSP message %d 0x%02x\n", + LOBYTE(wMessage), LOBYTE(wMessage)); + break; + } + break; + + case HIMT_MIDI_IN_UCHAR: + if (chip->msndmidi_mpu) + snd_msndmidi_input_read(chip->msndmidi_mpu); + break; + + default: + snd_printd(KERN_WARNING LOGNAME ": HIMT message %d 0x%02x\n", + HIBYTE(wMessage), HIBYTE(wMessage)); + break; + } +} + +static irqreturn_t snd_msnd_interrupt(int irq, void *dev_id) +{ + struct snd_msnd *chip = dev_id; + void __iomem *pwDSPQData = chip->mappedbase + DSPQ_DATA_BUFF; + u16 head, tail, size; + + /* Send ack to DSP */ + /* inb(chip->io + HP_RXL); */ + + /* Evaluate queued DSP messages */ + head = readw(chip->DSPQ + JQS_wHead); + tail = readw(chip->DSPQ + JQS_wTail); + size = readw(chip->DSPQ + JQS_wSize); + if (head > size || tail > size) + goto out; + while (head != tail) { + snd_msnd_eval_dsp_msg(chip, readw(pwDSPQData + 2 * head)); + if (++head > size) + head = 0; + writew(head, chip->DSPQ + JQS_wHead); + } + out: + /* Send ack to DSP */ + inb(chip->io + HP_RXL); + return IRQ_HANDLED; +} + + +static int snd_msnd_reset_dsp(long io, unsigned char *info) +{ + int timeout = 100; + + outb(HPDSPRESET_ON, io + HP_DSPR); + msleep(1); +#ifndef MSND_CLASSIC + if (info) + *info = inb(io + HP_INFO); +#endif + outb(HPDSPRESET_OFF, io + HP_DSPR); + msleep(1); + while (timeout-- > 0) { + if (inb(io + HP_CVR) == HP_CVR_DEF) + return 0; + msleep(1); + } + snd_printk(KERN_ERR LOGNAME ": Cannot reset DSP\n"); + + return -EIO; +} + +static int snd_msnd_probe(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + unsigned char info; +#ifndef MSND_CLASSIC + char *xv, *rev = NULL; + char *pin = "TB Pinnacle", *fiji = "TB Fiji"; + char *pinfiji = "TB Pinnacle/Fiji"; +#endif + + if (!request_region(chip->io, DSP_NUMIO, "probing")) { + snd_printk(KERN_ERR LOGNAME ": I/O port conflict\n"); + return -ENODEV; + } + + if (snd_msnd_reset_dsp(chip->io, &info) < 0) { + release_region(chip->io, DSP_NUMIO); + return -ENODEV; + } + +#ifdef MSND_CLASSIC + strcpy(card->shortname, "Classic/Tahiti/Monterey"); + strcpy(card->longname, "Turtle Beach Multisound"); + printk(KERN_INFO LOGNAME ": %s, " + "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n", + card->shortname, + chip->io, chip->io + DSP_NUMIO - 1, + chip->irq, + chip->base, chip->base + 0x7fff); +#else + switch (info >> 4) { + case 0xf: + xv = "<= 1.15"; + break; + case 0x1: + xv = "1.18/1.2"; + break; + case 0x2: + xv = "1.3"; + break; + case 0x3: + xv = "1.4"; + break; + default: + xv = "unknown"; + break; + } + + switch (info & 0x7) { + case 0x0: + rev = "I"; + strcpy(card->shortname, pin); + break; + case 0x1: + rev = "F"; + strcpy(card->shortname, pin); + break; + case 0x2: + rev = "G"; + strcpy(card->shortname, pin); + break; + case 0x3: + rev = "H"; + strcpy(card->shortname, pin); + break; + case 0x4: + rev = "E"; + strcpy(card->shortname, fiji); + break; + case 0x5: + rev = "C"; + strcpy(card->shortname, fiji); + break; + case 0x6: + rev = "D"; + strcpy(card->shortname, fiji); + break; + case 0x7: + rev = "A-B (Fiji) or A-E (Pinnacle)"; + strcpy(card->shortname, pinfiji); + break; + } + strcpy(card->longname, "Turtle Beach Multisound Pinnacle"); + printk(KERN_INFO LOGNAME ": %s revision %s, Xilinx version %s, " + "I/O 0x%lx-0x%lx, IRQ %d, memory mapped to 0x%lX-0x%lX\n", + card->shortname, + rev, xv, + chip->io, chip->io + DSP_NUMIO - 1, + chip->irq, + chip->base, chip->base + 0x7fff); +#endif + + release_region(chip->io, DSP_NUMIO); + return 0; +} + +static int snd_msnd_init_sma(struct snd_msnd *chip) +{ + static int initted; + u16 mastVolLeft, mastVolRight; + unsigned long flags; + +#ifdef MSND_CLASSIC + outb(chip->memid, chip->io + HP_MEMM); +#endif + outb(HPBLKSEL_0, chip->io + HP_BLKS); + /* Motorola 56k shared memory base */ + chip->SMA = chip->mappedbase + SMA_STRUCT_START; + + if (initted) { + mastVolLeft = readw(chip->SMA + SMA_wCurrMastVolLeft); + mastVolRight = readw(chip->SMA + SMA_wCurrMastVolRight); + } else + mastVolLeft = mastVolRight = 0; + memset_io(chip->mappedbase, 0, 0x8000); + + /* Critical section: bank 1 access */ + spin_lock_irqsave(&chip->lock, flags); + outb(HPBLKSEL_1, chip->io + HP_BLKS); + memset_io(chip->mappedbase, 0, 0x8000); + outb(HPBLKSEL_0, chip->io + HP_BLKS); + spin_unlock_irqrestore(&chip->lock, flags); + + /* Digital audio play queue */ + chip->DAPQ = chip->mappedbase + DAPQ_OFFSET; + snd_msnd_init_queue(chip->DAPQ, DAPQ_DATA_BUFF, DAPQ_BUFF_SIZE); + + /* Digital audio record queue */ + chip->DARQ = chip->mappedbase + DARQ_OFFSET; + snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); + + /* MIDI out queue */ + chip->MODQ = chip->mappedbase + MODQ_OFFSET; + snd_msnd_init_queue(chip->MODQ, MODQ_DATA_BUFF, MODQ_BUFF_SIZE); + + /* MIDI in queue */ + chip->MIDQ = chip->mappedbase + MIDQ_OFFSET; + snd_msnd_init_queue(chip->MIDQ, MIDQ_DATA_BUFF, MIDQ_BUFF_SIZE); + + /* DSP -> host message queue */ + chip->DSPQ = chip->mappedbase + DSPQ_OFFSET; + snd_msnd_init_queue(chip->DSPQ, DSPQ_DATA_BUFF, DSPQ_BUFF_SIZE); + + /* Setup some DSP values */ +#ifndef MSND_CLASSIC + writew(1, chip->SMA + SMA_wCurrPlayFormat); + writew(chip->play_sample_size, chip->SMA + SMA_wCurrPlaySampleSize); + writew(chip->play_channels, chip->SMA + SMA_wCurrPlayChannels); + writew(chip->play_sample_rate, chip->SMA + SMA_wCurrPlaySampleRate); +#endif + writew(chip->play_sample_rate, chip->SMA + SMA_wCalFreqAtoD); + writew(mastVolLeft, chip->SMA + SMA_wCurrMastVolLeft); + writew(mastVolRight, chip->SMA + SMA_wCurrMastVolRight); +#ifndef MSND_CLASSIC + writel(0x00010000, chip->SMA + SMA_dwCurrPlayPitch); + writel(0x00000001, chip->SMA + SMA_dwCurrPlayRate); +#endif + writew(0x303, chip->SMA + SMA_wCurrInputTagBits); + + initted = 1; + + return 0; +} + + +static int upload_dsp_code(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + const struct firmware *init_fw = NULL, *perm_fw = NULL; + int err; + + outb(HPBLKSEL_0, chip->io + HP_BLKS); + + err = request_firmware(&init_fw, INITCODEFILE, card->dev); + if (err < 0) { + printk(KERN_ERR LOGNAME ": Error loading " INITCODEFILE); + goto cleanup1; + } + err = request_firmware(&perm_fw, PERMCODEFILE, card->dev); + if (err < 0) { + printk(KERN_ERR LOGNAME ": Error loading " PERMCODEFILE); + goto cleanup; + } + + memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size); + if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) { + printk(KERN_WARNING LOGNAME ": Error uploading to DSP\n"); + err = -ENODEV; + goto cleanup; + } + printk(KERN_INFO LOGNAME ": DSP firmware uploaded\n"); + err = 0; + +cleanup: + release_firmware(perm_fw); +cleanup1: + release_firmware(init_fw); + return err; +} + +#ifdef MSND_CLASSIC +static void reset_proteus(struct snd_msnd *chip) +{ + outb(HPPRORESET_ON, chip->io + HP_PROR); + msleep(TIME_PRO_RESET); + outb(HPPRORESET_OFF, chip->io + HP_PROR); + msleep(TIME_PRO_RESET_DONE); +} +#endif + +static int snd_msnd_initialize(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + int err, timeout; + +#ifdef MSND_CLASSIC + outb(HPWAITSTATE_0, chip->io + HP_WAIT); + outb(HPBITMODE_16, chip->io + HP_BITM); + + reset_proteus(chip); +#endif + err = snd_msnd_init_sma(chip); + if (err < 0) { + printk(KERN_WARNING LOGNAME ": Cannot initialize SMA\n"); + return err; + } + + err = snd_msnd_reset_dsp(chip->io, NULL); + if (err < 0) + return err; + + err = upload_dsp_code(card); + if (err < 0) { + printk(KERN_WARNING LOGNAME ": Cannot upload DSP code\n"); + return err; + } + + timeout = 200; + + while (readw(chip->mappedbase)) { + msleep(1); + if (!timeout--) { + snd_printd(KERN_ERR LOGNAME ": DSP reset timeout\n"); + return -EIO; + } + } + + snd_msndmix_setup(chip); + return 0; +} + +static int snd_msnd_dsp_full_reset(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + int rv; + + if (test_bit(F_RESETTING, &chip->flags) || ++chip->nresets > 10) + return 0; + + set_bit(F_RESETTING, &chip->flags); + snd_msnd_dsp_halt(chip, NULL); /* Unconditionally halt */ + + rv = snd_msnd_initialize(card); + if (rv) + printk(KERN_WARNING LOGNAME ": DSP reset failed\n"); + snd_msndmix_force_recsrc(chip, 0); + clear_bit(F_RESETTING, &chip->flags); + return rv; +} + +static int snd_msnd_dev_free(struct snd_device *device) +{ + snd_printdd("snd_msnd_chip_free()\n"); + return 0; +} + +static int snd_msnd_send_dsp_cmd_chk(struct snd_msnd *chip, u8 cmd) +{ + if (snd_msnd_send_dsp_cmd(chip, cmd) == 0) + return 0; + snd_msnd_dsp_full_reset(chip->card); + return snd_msnd_send_dsp_cmd(chip, cmd); +} + +static int snd_msnd_calibrate_adc(struct snd_msnd *chip, u16 srate) +{ + snd_printdd("snd_msnd_calibrate_adc(%i)\n", srate); + writew(srate, chip->SMA + SMA_wCalFreqAtoD); + if (chip->calibrate_signal == 0) + writew(readw(chip->SMA + SMA_wCurrHostStatusFlags) + | 0x0001, chip->SMA + SMA_wCurrHostStatusFlags); + else + writew(readw(chip->SMA + SMA_wCurrHostStatusFlags) + & ~0x0001, chip->SMA + SMA_wCurrHostStatusFlags); + if (snd_msnd_send_word(chip, 0, 0, HDEXAR_CAL_A_TO_D) == 0 && + snd_msnd_send_dsp_cmd_chk(chip, HDEX_AUX_REQ) == 0) { + schedule_timeout_interruptible(msecs_to_jiffies(333)); + return 0; + } + printk(KERN_WARNING LOGNAME ": ADC calibration failed\n"); + return -EIO; +} + +/* + * ALSA callback function, called when attempting to open the MIDI device. + */ +static int snd_msnd_mpu401_open(struct snd_mpu401 *mpu) +{ + snd_msnd_enable_irq(mpu->private_data); + snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_START); + return 0; +} + +static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu) +{ + snd_msnd_send_dsp_cmd(mpu->private_data, HDEX_MIDI_IN_STOP); + snd_msnd_disable_irq(mpu->private_data); +} + +static long mpu_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; + +static int snd_msnd_attach(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_msnd_dev_free, + }; + + err = request_irq(chip->irq, snd_msnd_interrupt, 0, card->shortname, + chip); + if (err < 0) { + printk(KERN_ERR LOGNAME ": Couldn't grab IRQ %d\n", chip->irq); + return err; + } + if (request_region(chip->io, DSP_NUMIO, card->shortname) == NULL) { + free_irq(chip->irq, chip); + return -EBUSY; + } + + if (!request_mem_region(chip->base, BUFFSIZE, card->shortname)) { + printk(KERN_ERR LOGNAME + ": unable to grab memory region 0x%lx-0x%lx\n", + chip->base, chip->base + BUFFSIZE - 1); + release_region(chip->io, DSP_NUMIO); + free_irq(chip->irq, chip); + return -EBUSY; + } + chip->mappedbase = ioremap_nocache(chip->base, 0x8000); + if (!chip->mappedbase) { + printk(KERN_ERR LOGNAME + ": unable to map memory region 0x%lx-0x%lx\n", + chip->base, chip->base + BUFFSIZE - 1); + err = -EIO; + goto err_release_region; + } + + err = snd_msnd_dsp_full_reset(card); + if (err < 0) + goto err_release_region; + + /* Register device */ + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) + goto err_release_region; + + err = snd_msnd_pcm(card, 0); + if (err < 0) { + printk(KERN_ERR LOGNAME ": error creating new PCM device\n"); + goto err_release_region; + } + + err = snd_msndmix_new(card); + if (err < 0) { + printk(KERN_ERR LOGNAME ": error creating new Mixer device\n"); + goto err_release_region; + } + + + if (mpu_io[0] != SNDRV_AUTO_PORT) { + struct snd_mpu401 *mpu; + + err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + mpu_io[0], + MPU401_MODE_INPUT | + MPU401_MODE_OUTPUT, + mpu_irq[0], + &chip->rmidi); + if (err < 0) { + printk(KERN_ERR LOGNAME + ": error creating new Midi device\n"); + goto err_release_region; + } + mpu = chip->rmidi->private_data; + + mpu->open_input = snd_msnd_mpu401_open; + mpu->close_input = snd_msnd_mpu401_close; + mpu->private_data = chip; + } + + disable_irq(chip->irq); + snd_msnd_calibrate_adc(chip, chip->play_sample_rate); + snd_msndmix_force_recsrc(chip, 0); + + err = snd_card_register(card); + if (err < 0) + goto err_release_region; + + return 0; + +err_release_region: + iounmap(chip->mappedbase); + release_mem_region(chip->base, BUFFSIZE); + release_region(chip->io, DSP_NUMIO); + free_irq(chip->irq, chip); + return err; +} + + +static void snd_msnd_unload(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + + iounmap(chip->mappedbase); + release_mem_region(chip->base, BUFFSIZE); + release_region(chip->io, DSP_NUMIO); + free_irq(chip->irq, chip); + snd_card_free(card); +} + +#ifndef MSND_CLASSIC + +/* Pinnacle/Fiji Logical Device Configuration */ + +static int snd_msnd_write_cfg(int cfg, int reg, int value) +{ + outb(reg, cfg); + outb(value, cfg + 1); + if (value != inb(cfg + 1)) { + printk(KERN_ERR LOGNAME ": snd_msnd_write_cfg: I/O error\n"); + return -EIO; + } + return 0; +} + +static int snd_msnd_write_cfg_io0(int cfg, int num, u16 io) +{ + if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num)) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_IO0_BASEHI, HIBYTE(io))) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_IO0_BASELO, LOBYTE(io))) + return -EIO; + return 0; +} + +static int snd_msnd_write_cfg_io1(int cfg, int num, u16 io) +{ + if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num)) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_IO1_BASEHI, HIBYTE(io))) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_IO1_BASELO, LOBYTE(io))) + return -EIO; + return 0; +} + +static int snd_msnd_write_cfg_irq(int cfg, int num, u16 irq) +{ + if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num)) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_IRQ_NUMBER, LOBYTE(irq))) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_IRQ_TYPE, IRQTYPE_EDGE)) + return -EIO; + return 0; +} + +static int snd_msnd_write_cfg_mem(int cfg, int num, int mem) +{ + u16 wmem; + + mem >>= 8; + wmem = (u16)(mem & 0xfff); + if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num)) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_MEMBASEHI, HIBYTE(wmem))) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_MEMBASELO, LOBYTE(wmem))) + return -EIO; + if (wmem && snd_msnd_write_cfg(cfg, IREG_MEMCONTROL, + MEMTYPE_HIADDR | MEMTYPE_16BIT)) + return -EIO; + return 0; +} + +static int snd_msnd_activate_logical(int cfg, int num) +{ + if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num)) + return -EIO; + if (snd_msnd_write_cfg(cfg, IREG_ACTIVATE, LD_ACTIVATE)) + return -EIO; + return 0; +} + +static int snd_msnd_write_cfg_logical(int cfg, int num, u16 io0, + u16 io1, u16 irq, int mem) +{ + if (snd_msnd_write_cfg(cfg, IREG_LOGDEVICE, num)) + return -EIO; + if (snd_msnd_write_cfg_io0(cfg, num, io0)) + return -EIO; + if (snd_msnd_write_cfg_io1(cfg, num, io1)) + return -EIO; + if (snd_msnd_write_cfg_irq(cfg, num, irq)) + return -EIO; + if (snd_msnd_write_cfg_mem(cfg, num, mem)) + return -EIO; + if (snd_msnd_activate_logical(cfg, num)) + return -EIO; + return 0; +} + +static int snd_msnd_pinnacle_cfg_reset(int cfg) +{ + int i; + + /* Reset devices if told to */ + printk(KERN_INFO LOGNAME ": Resetting all devices\n"); + for (i = 0; i < 4; ++i) + if (snd_msnd_write_cfg_logical(cfg, i, 0, 0, 0, 0)) + return -EIO; + + return 0; +} +#endif + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for msnd_pinnacle soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for msnd_pinnacle soundcard."); + +static long io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static long mem[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; + +#ifndef MSND_CLASSIC +static long cfg[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; + +/* Extra Peripheral Configuration (Default: Disable) */ +static long ide_io0[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static long ide_io1[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int ide_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; + +static long joystick_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +/* If we have the digital daugherboard... */ +static int digital[SNDRV_CARDS]; + +/* Extra Peripheral Configuration */ +static int reset[SNDRV_CARDS]; +#endif + +static int write_ndelay[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = 1 }; + +static int calibrate_signal; + +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard."); +#define has_isapnp(x) isapnp[x] +#else +#define has_isapnp(x) 0 +#endif + +MODULE_AUTHOR("Karsten Wiese "); +MODULE_DESCRIPTION("Turtle Beach " LONGNAME " Linux Driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(INITCODEFILE); +MODULE_FIRMWARE(PERMCODEFILE); + +module_param_hw_array(io, long, ioport, NULL, 0444); +MODULE_PARM_DESC(io, "IO port #"); +module_param_hw_array(irq, int, irq, NULL, 0444); +module_param_hw_array(mem, long, iomem, NULL, 0444); +module_param_array(write_ndelay, int, NULL, 0444); +module_param(calibrate_signal, int, 0444); +#ifndef MSND_CLASSIC +module_param_array(digital, int, NULL, 0444); +module_param_hw_array(cfg, long, ioport, NULL, 0444); +module_param_array(reset, int, NULL, 0444); +module_param_hw_array(mpu_io, long, ioport, NULL, 0444); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +module_param_hw_array(ide_io0, long, ioport, NULL, 0444); +module_param_hw_array(ide_io1, long, ioport, NULL, 0444); +module_param_hw_array(ide_irq, int, irq, NULL, 0444); +module_param_hw_array(joystick_io, long, ioport, NULL, 0444); +#endif + + +static int snd_msnd_isa_match(struct device *pdev, unsigned int i) +{ + if (io[i] == SNDRV_AUTO_PORT) + return 0; + + if (irq[i] == SNDRV_AUTO_PORT || mem[i] == SNDRV_AUTO_PORT) { + printk(KERN_WARNING LOGNAME ": io, irq and mem must be set\n"); + return 0; + } + +#ifdef MSND_CLASSIC + if (!(io[i] == 0x290 || + io[i] == 0x260 || + io[i] == 0x250 || + io[i] == 0x240 || + io[i] == 0x230 || + io[i] == 0x220 || + io[i] == 0x210 || + io[i] == 0x3e0)) { + printk(KERN_ERR LOGNAME ": \"io\" - DSP I/O base must be set " + " to 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0x290, " + "or 0x3E0\n"); + return 0; + } +#else + if (io[i] < 0x100 || io[i] > 0x3e0 || (io[i] % 0x10) != 0) { + printk(KERN_ERR LOGNAME + ": \"io\" - DSP I/O base must within the range 0x100 " + "to 0x3E0 and must be evenly divisible by 0x10\n"); + return 0; + } +#endif /* MSND_CLASSIC */ + + if (!(irq[i] == 5 || + irq[i] == 7 || + irq[i] == 9 || + irq[i] == 10 || + irq[i] == 11 || + irq[i] == 12)) { + printk(KERN_ERR LOGNAME + ": \"irq\" - must be set to 5, 7, 9, 10, 11 or 12\n"); + return 0; + } + + if (!(mem[i] == 0xb0000 || + mem[i] == 0xc8000 || + mem[i] == 0xd0000 || + mem[i] == 0xd8000 || + mem[i] == 0xe0000 || + mem[i] == 0xe8000)) { + printk(KERN_ERR LOGNAME ": \"mem\" - must be set to " + "0xb0000, 0xc8000, 0xd0000, 0xd8000, 0xe0000 or " + "0xe8000\n"); + return 0; + } + +#ifndef MSND_CLASSIC + if (cfg[i] == SNDRV_AUTO_PORT) { + printk(KERN_INFO LOGNAME ": Assuming PnP mode\n"); + } else if (cfg[i] != 0x250 && cfg[i] != 0x260 && cfg[i] != 0x270) { + printk(KERN_INFO LOGNAME + ": Config port must be 0x250, 0x260 or 0x270 " + "(or unspecified for PnP mode)\n"); + return 0; + } +#endif /* MSND_CLASSIC */ + + return 1; +} + +static int snd_msnd_isa_probe(struct device *pdev, unsigned int idx) +{ + int err; + struct snd_card *card; + struct snd_msnd *chip; + + if (has_isapnp(idx) +#ifndef MSND_CLASSIC + || cfg[idx] == SNDRV_AUTO_PORT +#endif + ) { + printk(KERN_INFO LOGNAME ": Assuming PnP mode\n"); + return -ENODEV; + } + + err = snd_card_new(pdev, index[idx], id[idx], THIS_MODULE, + sizeof(struct snd_msnd), &card); + if (err < 0) + return err; + + chip = card->private_data; + chip->card = card; + +#ifdef MSND_CLASSIC + switch (irq[idx]) { + case 5: + chip->irqid = HPIRQ_5; break; + case 7: + chip->irqid = HPIRQ_7; break; + case 9: + chip->irqid = HPIRQ_9; break; + case 10: + chip->irqid = HPIRQ_10; break; + case 11: + chip->irqid = HPIRQ_11; break; + case 12: + chip->irqid = HPIRQ_12; break; + } + + switch (mem[idx]) { + case 0xb0000: + chip->memid = HPMEM_B000; break; + case 0xc8000: + chip->memid = HPMEM_C800; break; + case 0xd0000: + chip->memid = HPMEM_D000; break; + case 0xd8000: + chip->memid = HPMEM_D800; break; + case 0xe0000: + chip->memid = HPMEM_E000; break; + case 0xe8000: + chip->memid = HPMEM_E800; break; + } +#else + printk(KERN_INFO LOGNAME ": Non-PnP mode: configuring at port 0x%lx\n", + cfg[idx]); + + if (!request_region(cfg[idx], 2, "Pinnacle/Fiji Config")) { + printk(KERN_ERR LOGNAME ": Config port 0x%lx conflict\n", + cfg[idx]); + snd_card_free(card); + return -EIO; + } + if (reset[idx]) + if (snd_msnd_pinnacle_cfg_reset(cfg[idx])) { + err = -EIO; + goto cfg_error; + } + + /* DSP */ + err = snd_msnd_write_cfg_logical(cfg[idx], 0, + io[idx], 0, + irq[idx], mem[idx]); + + if (err) + goto cfg_error; + + /* The following are Pinnacle specific */ + + /* MPU */ + if (mpu_io[idx] != SNDRV_AUTO_PORT + && mpu_irq[idx] != SNDRV_AUTO_IRQ) { + printk(KERN_INFO LOGNAME + ": Configuring MPU to I/O 0x%lx IRQ %d\n", + mpu_io[idx], mpu_irq[idx]); + err = snd_msnd_write_cfg_logical(cfg[idx], 1, + mpu_io[idx], 0, + mpu_irq[idx], 0); + + if (err) + goto cfg_error; + } + + /* IDE */ + if (ide_io0[idx] != SNDRV_AUTO_PORT + && ide_io1[idx] != SNDRV_AUTO_PORT + && ide_irq[idx] != SNDRV_AUTO_IRQ) { + printk(KERN_INFO LOGNAME + ": Configuring IDE to I/O 0x%lx, 0x%lx IRQ %d\n", + ide_io0[idx], ide_io1[idx], ide_irq[idx]); + err = snd_msnd_write_cfg_logical(cfg[idx], 2, + ide_io0[idx], ide_io1[idx], + ide_irq[idx], 0); + + if (err) + goto cfg_error; + } + + /* Joystick */ + if (joystick_io[idx] != SNDRV_AUTO_PORT) { + printk(KERN_INFO LOGNAME + ": Configuring joystick to I/O 0x%lx\n", + joystick_io[idx]); + err = snd_msnd_write_cfg_logical(cfg[idx], 3, + joystick_io[idx], 0, + 0, 0); + + if (err) + goto cfg_error; + } + release_region(cfg[idx], 2); + +#endif /* MSND_CLASSIC */ + + set_default_audio_parameters(chip); +#ifdef MSND_CLASSIC + chip->type = msndClassic; +#else + chip->type = msndPinnacle; +#endif + chip->io = io[idx]; + chip->irq = irq[idx]; + chip->base = mem[idx]; + + chip->calibrate_signal = calibrate_signal ? 1 : 0; + chip->recsrc = 0; + chip->dspq_data_buff = DSPQ_DATA_BUFF; + chip->dspq_buff_size = DSPQ_BUFF_SIZE; + if (write_ndelay[idx]) + clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags); + else + set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags); +#ifndef MSND_CLASSIC + if (digital[idx]) + set_bit(F_HAVEDIGITAL, &chip->flags); +#endif + spin_lock_init(&chip->lock); + err = snd_msnd_probe(card); + if (err < 0) { + printk(KERN_ERR LOGNAME ": Probe failed\n"); + snd_card_free(card); + return err; + } + + err = snd_msnd_attach(card); + if (err < 0) { + printk(KERN_ERR LOGNAME ": Attach failed\n"); + snd_card_free(card); + return err; + } + dev_set_drvdata(pdev, card); + + return 0; + +#ifndef MSND_CLASSIC +cfg_error: + release_region(cfg[idx], 2); + snd_card_free(card); + return err; +#endif +} + +static int snd_msnd_isa_remove(struct device *pdev, unsigned int dev) +{ + snd_msnd_unload(dev_get_drvdata(pdev)); + return 0; +} + +static struct isa_driver snd_msnd_driver = { + .match = snd_msnd_isa_match, + .probe = snd_msnd_isa_probe, + .remove = snd_msnd_isa_remove, + /* FIXME: suspend, resume */ + .driver = { + .name = DEV_NAME + }, +}; + +#ifdef CONFIG_PNP +static int snd_msnd_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int idx; + struct pnp_dev *pnp_dev; + struct pnp_dev *mpu_dev; + struct snd_card *card; + struct snd_msnd *chip; + int ret; + + for ( ; idx < SNDRV_CARDS; idx++) { + if (has_isapnp(idx)) + break; + } + if (idx >= SNDRV_CARDS) + return -ENODEV; + + /* + * Check that we still have room for another sound card ... + */ + pnp_dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL); + if (!pnp_dev) + return -ENODEV; + + mpu_dev = pnp_request_card_device(pcard, pid->devs[1].id, NULL); + if (!mpu_dev) + return -ENODEV; + + if (!pnp_is_active(pnp_dev) && pnp_activate_dev(pnp_dev) < 0) { + printk(KERN_INFO "msnd_pinnacle: device is inactive\n"); + return -EBUSY; + } + + if (!pnp_is_active(mpu_dev) && pnp_activate_dev(mpu_dev) < 0) { + printk(KERN_INFO "msnd_pinnacle: MPU device is inactive\n"); + return -EBUSY; + } + + /* + * Create a new ALSA sound card entry, in anticipation + * of detecting our hardware ... + */ + ret = snd_card_new(&pcard->card->dev, + index[idx], id[idx], THIS_MODULE, + sizeof(struct snd_msnd), &card); + if (ret < 0) + return ret; + + chip = card->private_data; + chip->card = card; + + /* + * Read the correct parameters off the ISA PnP bus ... + */ + io[idx] = pnp_port_start(pnp_dev, 0); + irq[idx] = pnp_irq(pnp_dev, 0); + mem[idx] = pnp_mem_start(pnp_dev, 0); + mpu_io[idx] = pnp_port_start(mpu_dev, 0); + mpu_irq[idx] = pnp_irq(mpu_dev, 0); + + set_default_audio_parameters(chip); +#ifdef MSND_CLASSIC + chip->type = msndClassic; +#else + chip->type = msndPinnacle; +#endif + chip->io = io[idx]; + chip->irq = irq[idx]; + chip->base = mem[idx]; + + chip->calibrate_signal = calibrate_signal ? 1 : 0; + chip->recsrc = 0; + chip->dspq_data_buff = DSPQ_DATA_BUFF; + chip->dspq_buff_size = DSPQ_BUFF_SIZE; + if (write_ndelay[idx]) + clear_bit(F_DISABLE_WRITE_NDELAY, &chip->flags); + else + set_bit(F_DISABLE_WRITE_NDELAY, &chip->flags); +#ifndef MSND_CLASSIC + if (digital[idx]) + set_bit(F_HAVEDIGITAL, &chip->flags); +#endif + spin_lock_init(&chip->lock); + ret = snd_msnd_probe(card); + if (ret < 0) { + printk(KERN_ERR LOGNAME ": Probe failed\n"); + goto _release_card; + } + + ret = snd_msnd_attach(card); + if (ret < 0) { + printk(KERN_ERR LOGNAME ": Attach failed\n"); + goto _release_card; + } + + pnp_set_card_drvdata(pcard, card); + ++idx; + return 0; + +_release_card: + snd_card_free(card); + return ret; +} + +static void snd_msnd_pnp_remove(struct pnp_card_link *pcard) +{ + snd_msnd_unload(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +static int isa_registered; +static int pnp_registered; + +static const struct pnp_card_device_id msnd_pnpids[] = { + /* Pinnacle PnP */ + { .id = "BVJ0440", .devs = { { "TBS0000" }, { "TBS0001" } } }, + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, msnd_pnpids); + +static struct pnp_card_driver msnd_pnpc_driver = { + .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, + .name = "msnd_pinnacle", + .id_table = msnd_pnpids, + .probe = snd_msnd_pnp_detect, + .remove = snd_msnd_pnp_remove, +}; +#endif /* CONFIG_PNP */ + +static int __init snd_msnd_init(void) +{ + int err; + + err = isa_register_driver(&snd_msnd_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_card_driver(&msnd_pnpc_driver); + if (!err) + pnp_registered = 1; + + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit snd_msnd_exit(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_card_driver(&msnd_pnpc_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_msnd_driver); +} + +module_init(snd_msnd_init); +module_exit(snd_msnd_exit); + diff --git a/sound/isa/msnd/msnd_pinnacle.h b/sound/isa/msnd/msnd_pinnacle.h new file mode 100644 index 000000000..48318d1ee --- /dev/null +++ b/sound/isa/msnd/msnd_pinnacle.h @@ -0,0 +1,181 @@ +/********************************************************************* + * + * msnd_pinnacle.h + * + * Turtle Beach MultiSound Sound Card Driver for Linux + * + * Some parts of this header file were derived from the Turtle Beach + * MultiSound Driver Development Kit. + * + * Copyright (C) 1998 Andrew Veliath + * Copyright (C) 1993 Turtle Beach Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ********************************************************************/ +#ifndef __MSND_PINNACLE_H +#define __MSND_PINNACLE_H + +#define DSP_NUMIO 0x08 + +#define IREG_LOGDEVICE 0x07 +#define IREG_ACTIVATE 0x30 +#define LD_ACTIVATE 0x01 +#define LD_DISACTIVATE 0x00 +#define IREG_EECONTROL 0x3F +#define IREG_MEMBASEHI 0x40 +#define IREG_MEMBASELO 0x41 +#define IREG_MEMCONTROL 0x42 +#define IREG_MEMRANGEHI 0x43 +#define IREG_MEMRANGELO 0x44 +#define MEMTYPE_8BIT 0x00 +#define MEMTYPE_16BIT 0x02 +#define MEMTYPE_RANGE 0x00 +#define MEMTYPE_HIADDR 0x01 +#define IREG_IO0_BASEHI 0x60 +#define IREG_IO0_BASELO 0x61 +#define IREG_IO1_BASEHI 0x62 +#define IREG_IO1_BASELO 0x63 +#define IREG_IRQ_NUMBER 0x70 +#define IREG_IRQ_TYPE 0x71 +#define IRQTYPE_HIGH 0x02 +#define IRQTYPE_LOW 0x00 +#define IRQTYPE_LEVEL 0x01 +#define IRQTYPE_EDGE 0x00 + +#define HP_DSPR 0x04 +#define HP_BLKS 0x04 + +#define HPDSPRESET_OFF 2 +#define HPDSPRESET_ON 0 + +#define HPBLKSEL_0 2 +#define HPBLKSEL_1 3 + +#define HIMT_DAT_OFF 0x03 + +#define HIDSP_PLAY_UNDER 0x00 +#define HIDSP_INT_PLAY_UNDER 0x01 +#define HIDSP_SSI_TX_UNDER 0x02 +#define HIDSP_RECQ_OVERFLOW 0x08 +#define HIDSP_INT_RECORD_OVER 0x09 +#define HIDSP_SSI_RX_OVERFLOW 0x0a + +#define HIDSP_MIDI_IN_OVER 0x10 + +#define HIDSP_MIDI_FRAME_ERR 0x11 +#define HIDSP_MIDI_PARITY_ERR 0x12 +#define HIDSP_MIDI_OVERRUN_ERR 0x13 + +#define HIDSP_INPUT_CLIPPING 0x20 +#define HIDSP_MIX_CLIPPING 0x30 +#define HIDSP_DAT_IN_OFF 0x21 + +#define TIME_PRO_RESET_DONE 0x028A +#define TIME_PRO_SYSEX 0x001E +#define TIME_PRO_RESET 0x0032 + +#define DAR_BUFF_SIZE 0x1000 + +#define MIDQ_BUFF_SIZE 0x800 +#define DSPQ_BUFF_SIZE 0x5A0 + +#define DSPQ_DATA_BUFF 0x7860 + +#define MOP_WAVEHDR 0 +#define MOP_EXTOUT 1 +#define MOP_HWINIT 0xfe +#define MOP_NONE 0xff +#define MOP_MAX 1 + +#define MIP_EXTIN 0 +#define MIP_WAVEHDR 1 +#define MIP_HWINIT 0xfe +#define MIP_MAX 1 + +/* Pinnacle/Fiji SMA Common Data */ +#define SMA_wCurrPlayBytes 0x0000 +#define SMA_wCurrRecordBytes 0x0002 +#define SMA_wCurrPlayVolLeft 0x0004 +#define SMA_wCurrPlayVolRight 0x0006 +#define SMA_wCurrInVolLeft 0x0008 +#define SMA_wCurrInVolRight 0x000a +#define SMA_wCurrMHdrVolLeft 0x000c +#define SMA_wCurrMHdrVolRight 0x000e +#define SMA_dwCurrPlayPitch 0x0010 +#define SMA_dwCurrPlayRate 0x0014 +#define SMA_wCurrMIDIIOPatch 0x0018 +#define SMA_wCurrPlayFormat 0x001a +#define SMA_wCurrPlaySampleSize 0x001c +#define SMA_wCurrPlayChannels 0x001e +#define SMA_wCurrPlaySampleRate 0x0020 +#define SMA_wCurrRecordFormat 0x0022 +#define SMA_wCurrRecordSampleSize 0x0024 +#define SMA_wCurrRecordChannels 0x0026 +#define SMA_wCurrRecordSampleRate 0x0028 +#define SMA_wCurrDSPStatusFlags 0x002a +#define SMA_wCurrHostStatusFlags 0x002c +#define SMA_wCurrInputTagBits 0x002e +#define SMA_wCurrLeftPeak 0x0030 +#define SMA_wCurrRightPeak 0x0032 +#define SMA_bMicPotPosLeft 0x0034 +#define SMA_bMicPotPosRight 0x0035 +#define SMA_bMicPotMaxLeft 0x0036 +#define SMA_bMicPotMaxRight 0x0037 +#define SMA_bInPotPosLeft 0x0038 +#define SMA_bInPotPosRight 0x0039 +#define SMA_bAuxPotPosLeft 0x003a +#define SMA_bAuxPotPosRight 0x003b +#define SMA_bInPotMaxLeft 0x003c +#define SMA_bInPotMaxRight 0x003d +#define SMA_bAuxPotMaxLeft 0x003e +#define SMA_bAuxPotMaxRight 0x003f +#define SMA_bInPotMaxMethod 0x0040 +#define SMA_bAuxPotMaxMethod 0x0041 +#define SMA_wCurrMastVolLeft 0x0042 +#define SMA_wCurrMastVolRight 0x0044 +#define SMA_wCalFreqAtoD 0x0046 +#define SMA_wCurrAuxVolLeft 0x0048 +#define SMA_wCurrAuxVolRight 0x004a +#define SMA_wCurrPlay1VolLeft 0x004c +#define SMA_wCurrPlay1VolRight 0x004e +#define SMA_wCurrPlay2VolLeft 0x0050 +#define SMA_wCurrPlay2VolRight 0x0052 +#define SMA_wCurrPlay3VolLeft 0x0054 +#define SMA_wCurrPlay3VolRight 0x0056 +#define SMA_wCurrPlay4VolLeft 0x0058 +#define SMA_wCurrPlay4VolRight 0x005a +#define SMA_wCurrPlay1PeakLeft 0x005c +#define SMA_wCurrPlay1PeakRight 0x005e +#define SMA_wCurrPlay2PeakLeft 0x0060 +#define SMA_wCurrPlay2PeakRight 0x0062 +#define SMA_wCurrPlay3PeakLeft 0x0064 +#define SMA_wCurrPlay3PeakRight 0x0066 +#define SMA_wCurrPlay4PeakLeft 0x0068 +#define SMA_wCurrPlay4PeakRight 0x006a +#define SMA_wCurrPlayPeakLeft 0x006c +#define SMA_wCurrPlayPeakRight 0x006e +#define SMA_wCurrDATSR 0x0070 +#define SMA_wCurrDATRXCHNL 0x0072 +#define SMA_wCurrDATTXCHNL 0x0074 +#define SMA_wCurrDATRXRate 0x0076 +#define SMA_dwDSPPlayCount 0x0078 +#define SMA__size 0x007c + +#define INITCODEFILE "turtlebeach/pndspini.bin" +#define PERMCODEFILE "turtlebeach/pndsperm.bin" +#define LONGNAME "MultiSound (Pinnacle/Fiji)" + +#endif /* __MSND_PINNACLE_H */ diff --git a/sound/isa/msnd/msnd_pinnacle_mixer.c b/sound/isa/msnd/msnd_pinnacle_mixer.c new file mode 100644 index 000000000..b40854079 --- /dev/null +++ b/sound/isa/msnd/msnd_pinnacle_mixer.c @@ -0,0 +1,338 @@ +/*************************************************************************** + msnd_pinnacle_mixer.c - description + ------------------- + begin : Fre Jun 7 2002 + copyright : (C) 2002 by karsten wiese + email : annabellesgarden@yahoo.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include + +#include +#include +#include "msnd.h" +#include "msnd_pinnacle.h" + + +#define MSND_MIXER_VOLUME 0 +#define MSND_MIXER_PCM 1 +#define MSND_MIXER_AUX 2 /* Input source 1 (aux1) */ +#define MSND_MIXER_IMIX 3 /* Recording monitor */ +#define MSND_MIXER_SYNTH 4 +#define MSND_MIXER_SPEAKER 5 +#define MSND_MIXER_LINE 6 +#define MSND_MIXER_MIC 7 +#define MSND_MIXER_RECLEV 11 /* Recording level */ +#define MSND_MIXER_IGAIN 12 /* Input gain */ +#define MSND_MIXER_OGAIN 13 /* Output gain */ +#define MSND_MIXER_DIGITAL 17 /* Digital (input) 1 */ + +/* Device mask bits */ + +#define MSND_MASK_VOLUME (1 << MSND_MIXER_VOLUME) +#define MSND_MASK_SYNTH (1 << MSND_MIXER_SYNTH) +#define MSND_MASK_PCM (1 << MSND_MIXER_PCM) +#define MSND_MASK_SPEAKER (1 << MSND_MIXER_SPEAKER) +#define MSND_MASK_LINE (1 << MSND_MIXER_LINE) +#define MSND_MASK_MIC (1 << MSND_MIXER_MIC) +#define MSND_MASK_IMIX (1 << MSND_MIXER_IMIX) +#define MSND_MASK_RECLEV (1 << MSND_MIXER_RECLEV) +#define MSND_MASK_IGAIN (1 << MSND_MIXER_IGAIN) +#define MSND_MASK_OGAIN (1 << MSND_MIXER_OGAIN) +#define MSND_MASK_AUX (1 << MSND_MIXER_AUX) +#define MSND_MASK_DIGITAL (1 << MSND_MIXER_DIGITAL) + +static int snd_msndmix_info_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Analog", "MASS", "SPDIF", + }; + struct snd_msnd *chip = snd_kcontrol_chip(kcontrol); + unsigned items = test_bit(F_HAVEDIGITAL, &chip->flags) ? 3 : 2; + + return snd_ctl_enum_info(uinfo, 1, items, texts); +} + +static int snd_msndmix_get_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_msnd *chip = snd_kcontrol_chip(kcontrol); + /* MSND_MASK_IMIX is the default */ + ucontrol->value.enumerated.item[0] = 0; + + if (chip->recsrc & MSND_MASK_SYNTH) { + ucontrol->value.enumerated.item[0] = 1; + } else if ((chip->recsrc & MSND_MASK_DIGITAL) && + test_bit(F_HAVEDIGITAL, &chip->flags)) { + ucontrol->value.enumerated.item[0] = 2; + } + + + return 0; +} + +static int snd_msndmix_set_mux(struct snd_msnd *chip, int val) +{ + unsigned newrecsrc; + int change; + unsigned char msndbyte; + + switch (val) { + case 0: + newrecsrc = MSND_MASK_IMIX; + msndbyte = HDEXAR_SET_ANA_IN; + break; + case 1: + newrecsrc = MSND_MASK_SYNTH; + msndbyte = HDEXAR_SET_SYNTH_IN; + break; + case 2: + newrecsrc = MSND_MASK_DIGITAL; + msndbyte = HDEXAR_SET_DAT_IN; + break; + default: + return -EINVAL; + } + change = newrecsrc != chip->recsrc; + if (change) { + change = 0; + if (!snd_msnd_send_word(chip, 0, 0, msndbyte)) + if (!snd_msnd_send_dsp_cmd(chip, HDEX_AUX_REQ)) { + chip->recsrc = newrecsrc; + change = 1; + } + } + return change; +} + +static int snd_msndmix_put_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol); + return snd_msndmix_set_mux(msnd, ucontrol->value.enumerated.item[0]); +} + + +static int snd_msndmix_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int snd_msndmix_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value; + unsigned long flags; + + spin_lock_irqsave(&msnd->mixer_lock, flags); + ucontrol->value.integer.value[0] = msnd->left_levels[addr] * 100; + ucontrol->value.integer.value[0] /= 0xFFFF; + ucontrol->value.integer.value[1] = msnd->right_levels[addr] * 100; + ucontrol->value.integer.value[1] /= 0xFFFF; + spin_unlock_irqrestore(&msnd->mixer_lock, flags); + return 0; +} + +#define update_volm(a, b) \ + do { \ + writew((dev->left_levels[a] >> 1) * \ + readw(dev->SMA + SMA_wCurrMastVolLeft) / 0xffff, \ + dev->SMA + SMA_##b##Left); \ + writew((dev->right_levels[a] >> 1) * \ + readw(dev->SMA + SMA_wCurrMastVolRight) / 0xffff, \ + dev->SMA + SMA_##b##Right); \ + } while (0); + +#define update_potm(d, s, ar) \ + do { \ + writeb((dev->left_levels[d] >> 8) * \ + readw(dev->SMA + SMA_wCurrMastVolLeft) / 0xffff, \ + dev->SMA + SMA_##s##Left); \ + writeb((dev->right_levels[d] >> 8) * \ + readw(dev->SMA + SMA_wCurrMastVolRight) / 0xffff, \ + dev->SMA + SMA_##s##Right); \ + if (snd_msnd_send_word(dev, 0, 0, ar) == 0) \ + snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ); \ + } while (0); + +#define update_pot(d, s, ar) \ + do { \ + writeb(dev->left_levels[d] >> 8, \ + dev->SMA + SMA_##s##Left); \ + writeb(dev->right_levels[d] >> 8, \ + dev->SMA + SMA_##s##Right); \ + if (snd_msnd_send_word(dev, 0, 0, ar) == 0) \ + snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ); \ + } while (0); + + +static int snd_msndmix_set(struct snd_msnd *dev, int d, int left, int right) +{ + int bLeft, bRight; + int wLeft, wRight; + int updatemaster = 0; + + if (d >= LEVEL_ENTRIES) + return -EINVAL; + + bLeft = left * 0xff / 100; + wLeft = left * 0xffff / 100; + + bRight = right * 0xff / 100; + wRight = right * 0xffff / 100; + + dev->left_levels[d] = wLeft; + dev->right_levels[d] = wRight; + + switch (d) { + /* master volume unscaled controls */ + case MSND_MIXER_LINE: /* line pot control */ + /* scaled by IMIX in digital mix */ + writeb(bLeft, dev->SMA + SMA_bInPotPosLeft); + writeb(bRight, dev->SMA + SMA_bInPotPosRight); + if (snd_msnd_send_word(dev, 0, 0, HDEXAR_IN_SET_POTS) == 0) + snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ); + break; + case MSND_MIXER_MIC: /* mic pot control */ + if (dev->type == msndClassic) + return -EINVAL; + /* scaled by IMIX in digital mix */ + writeb(bLeft, dev->SMA + SMA_bMicPotPosLeft); + writeb(bRight, dev->SMA + SMA_bMicPotPosRight); + if (snd_msnd_send_word(dev, 0, 0, HDEXAR_MIC_SET_POTS) == 0) + snd_msnd_send_dsp_cmd(dev, HDEX_AUX_REQ); + break; + case MSND_MIXER_VOLUME: /* master volume */ + writew(wLeft, dev->SMA + SMA_wCurrMastVolLeft); + writew(wRight, dev->SMA + SMA_wCurrMastVolRight); + /* fall through */ + + case MSND_MIXER_AUX: /* aux pot control */ + /* scaled by master volume */ + /* fall through */ + + /* digital controls */ + case MSND_MIXER_SYNTH: /* synth vol (dsp mix) */ + case MSND_MIXER_PCM: /* pcm vol (dsp mix) */ + case MSND_MIXER_IMIX: /* input monitor (dsp mix) */ + /* scaled by master volume */ + updatemaster = 1; + break; + + default: + return -EINVAL; + } + + if (updatemaster) { + /* update master volume scaled controls */ + update_volm(MSND_MIXER_PCM, wCurrPlayVol); + update_volm(MSND_MIXER_IMIX, wCurrInVol); + if (dev->type == msndPinnacle) + update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol); + update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS); + } + + return 0; +} + +static int snd_msndmix_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_msnd *msnd = snd_kcontrol_chip(kcontrol); + int change, addr = kcontrol->private_value; + int left, right; + unsigned long flags; + + left = ucontrol->value.integer.value[0] % 101; + right = ucontrol->value.integer.value[1] % 101; + spin_lock_irqsave(&msnd->mixer_lock, flags); + change = msnd->left_levels[addr] != left + || msnd->right_levels[addr] != right; + snd_msndmix_set(msnd, addr, left, right); + spin_unlock_irqrestore(&msnd->mixer_lock, flags); + return change; +} + + +#define DUMMY_VOLUME(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_msndmix_volume_info, \ + .get = snd_msndmix_volume_get, .put = snd_msndmix_volume_put, \ + .private_value = addr } + + +static struct snd_kcontrol_new snd_msnd_controls[] = { +DUMMY_VOLUME("Master Volume", 0, MSND_MIXER_VOLUME), +DUMMY_VOLUME("PCM Volume", 0, MSND_MIXER_PCM), +DUMMY_VOLUME("Aux Volume", 0, MSND_MIXER_AUX), +DUMMY_VOLUME("Line Volume", 0, MSND_MIXER_LINE), +DUMMY_VOLUME("Mic Volume", 0, MSND_MIXER_MIC), +DUMMY_VOLUME("Monitor", 0, MSND_MIXER_IMIX), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_msndmix_info_mux, + .get = snd_msndmix_get_mux, + .put = snd_msndmix_put_mux, +} +}; + + +int snd_msndmix_new(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + unsigned int idx; + int err; + + if (snd_BUG_ON(!chip)) + return -EINVAL; + spin_lock_init(&chip->mixer_lock); + strcpy(card->mixername, "MSND Pinnacle Mixer"); + + for (idx = 0; idx < ARRAY_SIZE(snd_msnd_controls); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(snd_msnd_controls + idx, chip)); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL(snd_msndmix_new); + +void snd_msndmix_setup(struct snd_msnd *dev) +{ + update_pot(MSND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS); + update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS); + update_volm(MSND_MIXER_PCM, wCurrPlayVol); + update_volm(MSND_MIXER_IMIX, wCurrInVol); + if (dev->type == msndPinnacle) { + update_pot(MSND_MIXER_MIC, bMicPotPos, HDEXAR_MIC_SET_POTS); + update_volm(MSND_MIXER_SYNTH, wCurrMHdrVol); + } +} +EXPORT_SYMBOL(snd_msndmix_setup); + +int snd_msndmix_force_recsrc(struct snd_msnd *dev, int recsrc) +{ + dev->recsrc = -1; + return snd_msndmix_set_mux(dev, recsrc); +} +EXPORT_SYMBOL(snd_msndmix_force_recsrc); diff --git a/sound/isa/opl3sa2.c b/sound/isa/opl3sa2.c new file mode 100644 index 000000000..7cce4cd4a --- /dev/null +++ b/sound/isa/opl3sa2.c @@ -0,0 +1,966 @@ +/* + * Driver for Yamaha OPL3-SA[2,3] soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Yamaha OPL3SA2+"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Yamaha,YMF719E-S}," + "{Genius,Sound Maker 3DX}," + "{Yamaha,OPL3SA3}," + "{Intel,AL440LX sound}," + "{NeoMagic,MagicWave 3DX}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0xf86,0x370,0x100 */ +static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */ +static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x530,0xe80,0xf40,0x604 */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x388 */ +static long midi_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x330,0x300 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 0,1,3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int opl3sa3_ymode[SNDRV_CARDS]; /* 0,1,2,3 */ /*SL Added*/ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for OPL3-SA soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for OPL3-SA soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable OPL3-SA soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for OPL3-SA driver."); +module_param_hw_array(sb_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(sb_port, "SB port # for OPL3-SA driver."); +module_param_hw_array(wss_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(wss_port, "WSS port # for OPL3-SA driver."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for OPL3-SA driver."); +module_param_hw_array(midi_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(midi_port, "MIDI port # for OPL3-SA driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for OPL3-SA driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for OPL3-SA driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for OPL3-SA driver."); +module_param_array(opl3sa3_ymode, int, NULL, 0444); +MODULE_PARM_DESC(opl3sa3_ymode, "Speaker size selection for 3D Enhancement mode: Desktop/Large Notebook/Small Notebook/HiFi."); + +#ifdef CONFIG_PNP +static int isa_registered; +static int pnp_registered; +static int pnpc_registered; +#endif + +/* control ports */ +#define OPL3SA2_PM_CTRL 0x01 +#define OPL3SA2_SYS_CTRL 0x02 +#define OPL3SA2_IRQ_CONFIG 0x03 +#define OPL3SA2_IRQ_STATUS 0x04 +#define OPL3SA2_DMA_CONFIG 0x06 +#define OPL3SA2_MASTER_LEFT 0x07 +#define OPL3SA2_MASTER_RIGHT 0x08 +#define OPL3SA2_MIC 0x09 +#define OPL3SA2_MISC 0x0A + +/* opl3sa3 only */ +#define OPL3SA3_DGTL_DOWN 0x12 +#define OPL3SA3_ANLG_DOWN 0x13 +#define OPL3SA3_WIDE 0x14 +#define OPL3SA3_BASS 0x15 +#define OPL3SA3_TREBLE 0x16 + +/* power management bits */ +#define OPL3SA2_PM_ADOWN 0x20 +#define OPL3SA2_PM_PSV 0x04 +#define OPL3SA2_PM_PDN 0x02 +#define OPL3SA2_PM_PDX 0x01 + +#define OPL3SA2_PM_D0 0x00 +#define OPL3SA2_PM_D3 (OPL3SA2_PM_ADOWN|OPL3SA2_PM_PSV|OPL3SA2_PM_PDN|OPL3SA2_PM_PDX) + +struct snd_opl3sa2 { + int version; /* 2 or 3 */ + unsigned long port; /* control port */ + struct resource *res_port; /* control port resource */ + int irq; + int single_dma; + spinlock_t reg_lock; + struct snd_hwdep *synth; + struct snd_rawmidi *rmidi; + struct snd_wss *wss; + unsigned char ctlregs[0x20]; + int ymode; /* SL added */ + struct snd_kcontrol *master_switch; + struct snd_kcontrol *master_volume; +}; + +#define PFX "opl3sa2: " + +#ifdef CONFIG_PNP + +static const struct pnp_device_id snd_opl3sa2_pnpbiosids[] = { + { .id = "YMH0021" }, + { .id = "NMX2210" }, /* Gateway Solo 2500 */ + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp, snd_opl3sa2_pnpbiosids); + +static const struct pnp_card_device_id snd_opl3sa2_pnpids[] = { + /* Yamaha YMF719E-S (Genius Sound Maker 3DX) */ + { .id = "YMH0020", .devs = { { "YMH0021" } } }, + /* Yamaha OPL3-SA3 (integrated on Intel's Pentium II AL440LX motherboard) */ + { .id = "YMH0030", .devs = { { "YMH0021" } } }, + /* Yamaha OPL3-SA2 */ + { .id = "YMH0800", .devs = { { "YMH0021" } } }, + /* Yamaha OPL3-SA2 */ + { .id = "YMH0801", .devs = { { "YMH0021" } } }, + /* NeoMagic MagicWave 3DX */ + { .id = "NMX2200", .devs = { { "YMH2210" } } }, + /* NeoMagic MagicWave 3D */ + { .id = "NMX2200", .devs = { { "NMX2210" } } }, + /* --- */ + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_opl3sa2_pnpids); + +#endif /* CONFIG_PNP */ + + +/* read control port (w/o spinlock) */ +static unsigned char __snd_opl3sa2_read(struct snd_opl3sa2 *chip, unsigned char reg) +{ + unsigned char result; +#if 0 + outb(0x1d, port); /* password */ + printk(KERN_DEBUG "read [0x%lx] = 0x%x\n", port, inb(port)); +#endif + outb(reg, chip->port); /* register */ + result = inb(chip->port + 1); +#if 0 + printk(KERN_DEBUG "read [0x%lx] = 0x%x [0x%x]\n", + port, result, inb(port)); +#endif + return result; +} + +/* read control port (with spinlock) */ +static unsigned char snd_opl3sa2_read(struct snd_opl3sa2 *chip, unsigned char reg) +{ + unsigned long flags; + unsigned char result; + + spin_lock_irqsave(&chip->reg_lock, flags); + result = __snd_opl3sa2_read(chip, reg); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return result; +} + +/* write control port (w/o spinlock) */ +static void __snd_opl3sa2_write(struct snd_opl3sa2 *chip, unsigned char reg, unsigned char value) +{ +#if 0 + outb(0x1d, port); /* password */ +#endif + outb(reg, chip->port); /* register */ + outb(value, chip->port + 1); + chip->ctlregs[reg] = value; +} + +/* write control port (with spinlock) */ +static void snd_opl3sa2_write(struct snd_opl3sa2 *chip, unsigned char reg, unsigned char value) +{ + unsigned long flags; + spin_lock_irqsave(&chip->reg_lock, flags); + __snd_opl3sa2_write(chip, reg, value); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static int snd_opl3sa2_detect(struct snd_card *card) +{ + struct snd_opl3sa2 *chip = card->private_data; + unsigned long port; + unsigned char tmp, tmp1; + char str[2]; + + port = chip->port; + if ((chip->res_port = request_region(port, 2, "OPL3-SA control")) == NULL) { + snd_printk(KERN_ERR PFX "can't grab port 0x%lx\n", port); + return -EBUSY; + } + /* + snd_printk(KERN_DEBUG "REG 0A = 0x%x\n", + snd_opl3sa2_read(chip, 0x0a)); + */ + chip->version = 0; + tmp = snd_opl3sa2_read(chip, OPL3SA2_MISC); + if (tmp == 0xff) { + snd_printd("OPL3-SA [0x%lx] detect = 0x%x\n", port, tmp); + return -ENODEV; + } + switch (tmp & 0x07) { + case 0x01: + chip->version = 2; /* YMF711 */ + break; + default: + chip->version = 3; + /* 0x02 - standard */ + /* 0x03 - YM715B */ + /* 0x04 - YM719 - OPL-SA4? */ + /* 0x05 - OPL3-SA3 - Libretto 100 */ + /* 0x07 - unknown - Neomagic MagicWave 3D */ + break; + } + str[0] = chip->version + '0'; + str[1] = 0; + strcat(card->shortname, str); + snd_opl3sa2_write(chip, OPL3SA2_MISC, tmp ^ 7); + if ((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MISC)) != tmp) { + snd_printd("OPL3-SA [0x%lx] detect (1) = 0x%x (0x%x)\n", port, tmp, tmp1); + return -ENODEV; + } + /* try if the MIC register is accessible */ + tmp = snd_opl3sa2_read(chip, OPL3SA2_MIC); + snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x8a); + if (((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MIC)) & 0x9f) != 0x8a) { + snd_printd("OPL3-SA [0x%lx] detect (2) = 0x%x (0x%x)\n", port, tmp, tmp1); + return -ENODEV; + } + snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x9f); + /* initialization */ + /* Power Management - full on */ + snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0); + if (chip->version > 2) { + /* ymode is bits 4&5 (of 0 to 7) on all but opl3sa2 versions */ + snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, (chip->ymode << 4)); + } else { + /* default for opl3sa2 versions */ + snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, 0x00); + } + snd_opl3sa2_write(chip, OPL3SA2_IRQ_CONFIG, 0x0d); /* Interrupt Channel Configuration - IRQ A = OPL3 + MPU + WSS */ + if (chip->single_dma) { + snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x03); /* DMA Configuration - DMA A = WSS-R + WSS-P */ + } else { + snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x21); /* DMA Configuration - DMA B = WSS-R, DMA A = WSS-P */ + } + snd_opl3sa2_write(chip, OPL3SA2_MISC, 0x80 | (tmp & 7)); /* Miscellaneous - default */ + if (chip->version > 2) { + snd_opl3sa2_write(chip, OPL3SA3_DGTL_DOWN, 0x00); /* Digital Block Partial Power Down - default */ + snd_opl3sa2_write(chip, OPL3SA3_ANLG_DOWN, 0x00); /* Analog Block Partial Power Down - default */ + } + return 0; +} + +static irqreturn_t snd_opl3sa2_interrupt(int irq, void *dev_id) +{ + unsigned short status; + struct snd_card *card = dev_id; + struct snd_opl3sa2 *chip; + int handled = 0; + + if (card == NULL) + return IRQ_NONE; + + chip = card->private_data; + status = snd_opl3sa2_read(chip, OPL3SA2_IRQ_STATUS); + + if (status & 0x20) { + handled = 1; + snd_opl3_interrupt(chip->synth); + } + + if ((status & 0x10) && chip->rmidi != NULL) { + handled = 1; + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); + } + + if (status & 0x07) { /* TI,CI,PI */ + handled = 1; + snd_wss_interrupt(irq, chip->wss); + } + + if (status & 0x40) { /* hardware volume change */ + handled = 1; + /* reading from Master Lch register at 0x07 clears this bit */ + snd_opl3sa2_read(chip, OPL3SA2_MASTER_RIGHT); + snd_opl3sa2_read(chip, OPL3SA2_MASTER_LEFT); + if (chip->master_switch && chip->master_volume) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_switch->id); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_volume->id); + } + } + return IRQ_RETVAL(handled); +} + +#define OPL3SA2_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_wss_info_single, \ + .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } +#define OPL3SA2_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .name = xname, .index = xindex, \ + .info = snd_wss_info_single, \ + .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24), \ + .tlv = { .p = (xtlv) } } + +static int snd_opl3sa2_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->ctlregs[reg] >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_opl3sa2_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val, oval; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = chip->ctlregs[reg]; + val = (oval & ~(mask << shift)) | val; + change = val != oval; + __snd_opl3sa2_write(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define OPL3SA2_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_wss_info_double, \ + .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } +#define OPL3SA2_DOUBLE_TLV(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .name = xname, .index = xindex, \ + .info = snd_wss_info_double, \ + .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22), \ + .tlv = { .p = (xtlv) } } + +static int snd_opl3sa2_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->ctlregs[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->ctlregs[right_reg] >> shift_right) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_opl3sa2_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2, oval1, oval2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + if (left_reg != right_reg) { + oval1 = chip->ctlregs[left_reg]; + oval2 = chip->ctlregs[right_reg]; + val1 = (oval1 & ~(mask << shift_left)) | val1; + val2 = (oval2 & ~(mask << shift_right)) | val2; + change = val1 != oval1 || val2 != oval2; + __snd_opl3sa2_write(chip, left_reg, val1); + __snd_opl3sa2_write(chip, right_reg, val2); + } else { + oval1 = chip->ctlregs[left_reg]; + val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != oval1; + __snd_opl3sa2_write(chip, left_reg, val1); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_master, -3000, 200, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0); + +static struct snd_kcontrol_new snd_opl3sa2_controls[] = { +OPL3SA2_DOUBLE("Master Playback Switch", 0, 0x07, 0x08, 7, 7, 1, 1), +OPL3SA2_DOUBLE_TLV("Master Playback Volume", 0, 0x07, 0x08, 0, 0, 15, 1, + db_scale_master), +OPL3SA2_SINGLE("Mic Playback Switch", 0, 0x09, 7, 1, 1), +OPL3SA2_SINGLE_TLV("Mic Playback Volume", 0, 0x09, 0, 31, 1, + db_scale_5bit_12db_max), +OPL3SA2_SINGLE("ZV Port Switch", 0, 0x02, 0, 1, 0), +}; + +static struct snd_kcontrol_new snd_opl3sa2_tone_controls[] = { +OPL3SA2_DOUBLE("3D Control - Wide", 0, 0x14, 0x14, 4, 0, 7, 0), +OPL3SA2_DOUBLE("Tone Control - Bass", 0, 0x15, 0x15, 4, 0, 7, 0), +OPL3SA2_DOUBLE("Tone Control - Treble", 0, 0x16, 0x16, 4, 0, 7, 0) +}; + +static void snd_opl3sa2_master_free(struct snd_kcontrol *kcontrol) +{ + struct snd_opl3sa2 *chip = snd_kcontrol_chip(kcontrol); + chip->master_switch = NULL; + chip->master_volume = NULL; +} + +static int snd_opl3sa2_mixer(struct snd_card *card) +{ + struct snd_opl3sa2 *chip = card->private_data; + struct snd_ctl_elem_id id1, id2; + struct snd_kcontrol *kctl; + unsigned int idx; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUX0 to CD */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "CD Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) { + snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n"); + return err; + } + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) { + snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n"); + return err; + } + /* reassign AUX1 to FM */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "FM Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) { + snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n"); + return err; + } + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "FM Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) { + snd_printk(KERN_ERR "Cannot rename opl3sa2 control\n"); + return err; + } + /* add OPL3SA2 controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_controls); idx++) { + if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_opl3sa2_controls[idx], chip))) < 0) + return err; + switch (idx) { + case 0: chip->master_switch = kctl; kctl->private_free = snd_opl3sa2_master_free; break; + case 1: chip->master_volume = kctl; kctl->private_free = snd_opl3sa2_master_free; break; + } + } + if (chip->version > 2) { + for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_tone_controls); idx++) + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_opl3sa2_tone_controls[idx], chip))) < 0) + return err; + } + return 0; +} + +/* Power Management support functions */ +#ifdef CONFIG_PM +static int snd_opl3sa2_suspend(struct snd_card *card, pm_message_t state) +{ + if (card) { + struct snd_opl3sa2 *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + chip->wss->suspend(chip->wss); + /* power down */ + snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D3); + } + + return 0; +} + +static int snd_opl3sa2_resume(struct snd_card *card) +{ + struct snd_opl3sa2 *chip; + int i; + + if (!card) + return 0; + + chip = card->private_data; + /* power up */ + snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0); + + /* restore registers */ + for (i = 2; i <= 0x0a; i++) { + if (i != OPL3SA2_IRQ_STATUS) + snd_opl3sa2_write(chip, i, chip->ctlregs[i]); + } + if (chip->version > 2) { + for (i = 0x12; i <= 0x16; i++) + snd_opl3sa2_write(chip, i, chip->ctlregs[i]); + } + /* restore wss */ + chip->wss->resume(chip->wss); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PNP +static int snd_opl3sa2_pnp(int dev, struct snd_opl3sa2 *chip, + struct pnp_dev *pdev) +{ + if (pnp_activate_dev(pdev) < 0) { + snd_printk(KERN_ERR "PnP configure failure (out of resources?)\n"); + return -EBUSY; + } + sb_port[dev] = pnp_port_start(pdev, 0); + wss_port[dev] = pnp_port_start(pdev, 1); + fm_port[dev] = pnp_port_start(pdev, 2); + midi_port[dev] = pnp_port_start(pdev, 3); + port[dev] = pnp_port_start(pdev, 4); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("%sPnP OPL3-SA: sb port=0x%lx, wss port=0x%lx, fm port=0x%lx, midi port=0x%lx\n", + pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", sb_port[dev], wss_port[dev], fm_port[dev], midi_port[dev]); + snd_printdd("%sPnP OPL3-SA: control port=0x%lx, dma1=%i, dma2=%i, irq=%i\n", + pnp_device_is_pnpbios(pdev) ? "BIOS" : "ISA", port[dev], dma1[dev], dma2[dev], irq[dev]); + return 0; +} +#endif /* CONFIG_PNP */ + +static void snd_opl3sa2_free(struct snd_card *card) +{ + struct snd_opl3sa2 *chip = card->private_data; + if (chip->irq >= 0) + free_irq(chip->irq, card); + release_and_free_resource(chip->res_port); +} + +static int snd_opl3sa2_card_new(struct device *pdev, int dev, + struct snd_card **cardp) +{ + struct snd_card *card; + struct snd_opl3sa2 *chip; + int err; + + err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_opl3sa2), &card); + if (err < 0) + return err; + strcpy(card->driver, "OPL3SA2"); + strcpy(card->shortname, "Yamaha OPL3-SA"); + chip = card->private_data; + spin_lock_init(&chip->reg_lock); + chip->irq = -1; + card->private_free = snd_opl3sa2_free; + *cardp = card; + return 0; +} + +static int snd_opl3sa2_probe(struct snd_card *card, int dev) +{ + int xirq, xdma1, xdma2; + struct snd_opl3sa2 *chip; + struct snd_wss *wss; + struct snd_opl3 *opl3; + int err; + + /* initialise this card from supplied (or default) parameter*/ + chip = card->private_data; + chip->ymode = opl3sa3_ymode[dev] & 0x03 ; + chip->port = port[dev]; + xirq = irq[dev]; + xdma1 = dma1[dev]; + xdma2 = dma2[dev]; + if (xdma2 < 0) + chip->single_dma = 1; + err = snd_opl3sa2_detect(card); + if (err < 0) + return err; + err = request_irq(xirq, snd_opl3sa2_interrupt, 0, + "OPL3-SA2", card); + if (err) { + snd_printk(KERN_ERR PFX "can't grab IRQ %d\n", xirq); + return -ENODEV; + } + chip->irq = xirq; + err = snd_wss_create(card, + wss_port[dev] + 4, -1, + xirq, xdma1, xdma2, + WSS_HW_OPL3SA2, WSS_HWSHARE_IRQ, &wss); + if (err < 0) { + snd_printd("Oops, WSS not detected at 0x%lx\n", wss_port[dev] + 4); + return err; + } + chip->wss = wss; + err = snd_wss_pcm(wss, 0); + if (err < 0) + return err; + err = snd_wss_mixer(wss); + if (err < 0) + return err; + err = snd_opl3sa2_mixer(card); + if (err < 0) + return err; + err = snd_wss_timer(wss, 0); + if (err < 0) + return err; + if (fm_port[dev] >= 0x340 && fm_port[dev] < 0x400) { + if ((err = snd_opl3_create(card, fm_port[dev], + fm_port[dev] + 2, + OPL3_HW_OPL3, 0, &opl3)) < 0) + return err; + if ((err = snd_opl3_timer_new(opl3, 1, 2)) < 0) + return err; + if ((err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth)) < 0) + return err; + } + if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x340) { + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_OPL3SA2, + midi_port[dev], + MPU401_INFO_IRQ_HOOK, -1, + &chip->rmidi)) < 0) + return err; + } + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + card->shortname, chip->port, xirq, xdma1); + if (xdma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", xdma2); + + return snd_card_register(card); +} + +#ifdef CONFIG_PNP +static int snd_opl3sa2_pnp_detect(struct pnp_dev *pdev, + const struct pnp_device_id *id) +{ + static int dev; + int err; + struct snd_card *card; + + if (pnp_device_is_isapnp(pdev)) + return -ENOENT; /* we have another procedure - card */ + for (; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + err = snd_opl3sa2_card_new(&pdev->dev, dev, &card); + if (err < 0) + return err; + if ((err = snd_opl3sa2_pnp(dev, card->private_data, pdev)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_opl3sa2_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + pnp_set_drvdata(pdev, card); + dev++; + return 0; +} + +static void snd_opl3sa2_pnp_remove(struct pnp_dev *pdev) +{ + snd_card_free(pnp_get_drvdata(pdev)); +} + +#ifdef CONFIG_PM +static int snd_opl3sa2_pnp_suspend(struct pnp_dev *pdev, pm_message_t state) +{ + return snd_opl3sa2_suspend(pnp_get_drvdata(pdev), state); +} +static int snd_opl3sa2_pnp_resume(struct pnp_dev *pdev) +{ + return snd_opl3sa2_resume(pnp_get_drvdata(pdev)); +} +#endif + +static struct pnp_driver opl3sa2_pnp_driver = { + .name = "snd-opl3sa2-pnpbios", + .id_table = snd_opl3sa2_pnpbiosids, + .probe = snd_opl3sa2_pnp_detect, + .remove = snd_opl3sa2_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_opl3sa2_pnp_suspend, + .resume = snd_opl3sa2_pnp_resume, +#endif +}; + +static int snd_opl3sa2_pnp_cdetect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *id) +{ + static int dev; + struct pnp_dev *pdev; + int err; + struct snd_card *card; + + pdev = pnp_request_card_device(pcard, id->devs[0].id, NULL); + if (pdev == NULL) { + snd_printk(KERN_ERR PFX "can't get pnp device from id '%s'\n", + id->devs[0].id); + return -EBUSY; + } + for (; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + err = snd_opl3sa2_card_new(&pdev->dev, dev, &card); + if (err < 0) + return err; + if ((err = snd_opl3sa2_pnp(dev, card->private_data, pdev)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_opl3sa2_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; +} + +static void snd_opl3sa2_pnp_cremove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_opl3sa2_pnp_csuspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_opl3sa2_suspend(pnp_get_card_drvdata(pcard), state); +} +static int snd_opl3sa2_pnp_cresume(struct pnp_card_link *pcard) +{ + return snd_opl3sa2_resume(pnp_get_card_drvdata(pcard)); +} +#endif + +static struct pnp_card_driver opl3sa2_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "snd-opl3sa2-cpnp", + .id_table = snd_opl3sa2_pnpids, + .probe = snd_opl3sa2_pnp_cdetect, + .remove = snd_opl3sa2_pnp_cremove, +#ifdef CONFIG_PM + .suspend = snd_opl3sa2_pnp_csuspend, + .resume = snd_opl3sa2_pnp_cresume, +#endif +}; +#endif /* CONFIG_PNP */ + +static int snd_opl3sa2_isa_match(struct device *pdev, + unsigned int dev) +{ + if (!enable[dev]) + return 0; +#ifdef CONFIG_PNP + if (isapnp[dev]) + return 0; +#endif + if (port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR PFX "specify port\n"); + return 0; + } + if (wss_port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR PFX "specify wss_port\n"); + return 0; + } + if (fm_port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR PFX "specify fm_port\n"); + return 0; + } + if (midi_port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR PFX "specify midi_port\n"); + return 0; + } + return 1; +} + +static int snd_opl3sa2_isa_probe(struct device *pdev, + unsigned int dev) +{ + struct snd_card *card; + int err; + + err = snd_opl3sa2_card_new(pdev, dev, &card); + if (err < 0) + return err; + if ((err = snd_opl3sa2_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + dev_set_drvdata(pdev, card); + return 0; +} + +static int snd_opl3sa2_isa_remove(struct device *devptr, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_opl3sa2_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_opl3sa2_suspend(dev_get_drvdata(dev), state); +} + +static int snd_opl3sa2_isa_resume(struct device *dev, unsigned int n) +{ + return snd_opl3sa2_resume(dev_get_drvdata(dev)); +} +#endif + +#define DEV_NAME "opl3sa2" + +static struct isa_driver snd_opl3sa2_isa_driver = { + .match = snd_opl3sa2_isa_match, + .probe = snd_opl3sa2_isa_probe, + .remove = snd_opl3sa2_isa_remove, +#ifdef CONFIG_PM + .suspend = snd_opl3sa2_isa_suspend, + .resume = snd_opl3sa2_isa_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + +static int __init alsa_card_opl3sa2_init(void) +{ + int err; + + err = isa_register_driver(&snd_opl3sa2_isa_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_driver(&opl3sa2_pnp_driver); + if (!err) + pnp_registered = 1; + + err = pnp_register_card_driver(&opl3sa2_pnpc_driver); + if (!err) + pnpc_registered = 1; + + if (isa_registered || pnp_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_opl3sa2_exit(void) +{ +#ifdef CONFIG_PNP + if (pnpc_registered) + pnp_unregister_card_driver(&opl3sa2_pnpc_driver); + if (pnp_registered) + pnp_unregister_driver(&opl3sa2_pnp_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_opl3sa2_isa_driver); +} + +module_init(alsa_card_opl3sa2_init) +module_exit(alsa_card_opl3sa2_exit) diff --git a/sound/isa/opti9xx/Makefile b/sound/isa/opti9xx/Makefile new file mode 100644 index 000000000..a9dcdeb50 --- /dev/null +++ b/sound/isa/opti9xx/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-opti92x-ad1848-objs := opti92x-ad1848.o +snd-opti92x-cs4231-objs := opti92x-cs4231.o +snd-opti93x-objs := opti93x.o +snd-miro-objs := miro.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-opti92x-ad1848.o +obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-opti92x-cs4231.o +obj-$(CONFIG_SND_OPTI93X) += snd-opti93x.o +obj-$(CONFIG_SND_MIRO) += snd-miro.o diff --git a/sound/isa/opti9xx/miro.c b/sound/isa/opti9xx/miro.c new file mode 100644 index 000000000..81eb9c894 --- /dev/null +++ b/sound/isa/opti9xx/miro.c @@ -0,0 +1,1669 @@ +/* + * ALSA soundcard driver for Miro miroSOUND PCM1 pro + * miroSOUND PCM12 + * miroSOUND PCM20 Radio + * + * Copyright (C) 2004-2005 Martin Langer + * + * Based on OSS ACI and ALSA OPTi9xx drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IOPORT +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include +#include + +MODULE_AUTHOR("Martin Langer "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Miro miroSOUND PCM1 pro, PCM12, PCM20 Radio"); +MODULE_SUPPORTED_DEVICE("{{Miro,miroSOUND PCM1 pro}, " + "{Miro,miroSOUND PCM12}, " + "{Miro,miroSOUND PCM20 Radio}}"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */ +static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */ +static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */ +static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */ +static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */ +static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +static int wss; +static int ide; +#ifdef CONFIG_PNP +static bool isapnp = 1; /* Enable ISA PnP detection */ +#endif + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for miro soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for miro soundcard."); +module_param_hw(port, long, ioport, 0444); +MODULE_PARM_DESC(port, "WSS port # for miro driver."); +module_param_hw(mpu_port, long, ioport, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for miro driver."); +module_param_hw(fm_port, long, ioport, 0444); +MODULE_PARM_DESC(fm_port, "FM Port # for miro driver."); +module_param_hw(irq, int, irq, 0444); +MODULE_PARM_DESC(irq, "WSS irq # for miro driver."); +module_param_hw(mpu_irq, int, irq, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for miro driver."); +module_param_hw(dma1, int, dma, 0444); +MODULE_PARM_DESC(dma1, "1st dma # for miro driver."); +module_param_hw(dma2, int, dma, 0444); +MODULE_PARM_DESC(dma2, "2nd dma # for miro driver."); +module_param(wss, int, 0444); +MODULE_PARM_DESC(wss, "wss mode"); +module_param(ide, int, 0444); +MODULE_PARM_DESC(ide, "enable ide port"); +#ifdef CONFIG_PNP +module_param(isapnp, bool, 0444); +MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard."); +#endif + +#define OPTi9XX_HW_DETECT 0 +#define OPTi9XX_HW_82C928 1 +#define OPTi9XX_HW_82C929 2 +#define OPTi9XX_HW_82C924 3 +#define OPTi9XX_HW_82C925 4 +#define OPTi9XX_HW_82C930 5 +#define OPTi9XX_HW_82C931 6 +#define OPTi9XX_HW_82C933 7 +#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933 + +#define OPTi9XX_MC_REG(n) n + +struct snd_miro { + unsigned short hardware; + unsigned char password; + char name[7]; + + struct resource *res_mc_base; + struct resource *res_aci_port; + + unsigned long mc_base; + unsigned long mc_base_size; + unsigned long pwd_reg; + + spinlock_t lock; + struct snd_pcm *pcm; + + long wss_base; + int irq; + int dma1; + int dma2; + + long mpu_port; + int mpu_irq; + + struct snd_miro_aci *aci; +}; + +static struct snd_miro_aci aci_device; + +static char * snd_opti9xx_names[] = { + "unknown", + "82C928", "82C929", + "82C924", "82C925", + "82C930", "82C931", "82C933" +}; + +static int snd_miro_pnp_is_probed; + +#ifdef CONFIG_PNP + +static const struct pnp_card_device_id snd_miro_pnpids[] = { + /* PCM20 and PCM12 in PnP mode */ + { .id = "MIR0924", + .devs = { { "MIR0000" }, { "MIR0002" }, { "MIR0005" } }, }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_miro_pnpids); + +#endif /* CONFIG_PNP */ + +/* + * ACI control + */ + +static int aci_busy_wait(struct snd_miro_aci *aci) +{ + long timeout; + unsigned char byte; + + for (timeout = 1; timeout <= ACI_MINTIME + 30; timeout++) { + byte = inb(aci->aci_port + ACI_REG_BUSY); + if ((byte & 1) == 0) { + if (timeout >= ACI_MINTIME) + snd_printd("aci ready in round %ld.\n", + timeout-ACI_MINTIME); + return byte; + } + if (timeout >= ACI_MINTIME) { + long out=10*HZ; + switch (timeout-ACI_MINTIME) { + case 0 ... 9: + out /= 10; + /* fall through */ + case 10 ... 19: + out /= 10; + /* fall through */ + case 20 ... 30: + out /= 10; + /* fall through */ + default: + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(out); + break; + } + } + } + snd_printk(KERN_ERR "aci_busy_wait() time out\n"); + return -EBUSY; +} + +static inline int aci_write(struct snd_miro_aci *aci, unsigned char byte) +{ + if (aci_busy_wait(aci) >= 0) { + outb(byte, aci->aci_port + ACI_REG_COMMAND); + return 0; + } else { + snd_printk(KERN_ERR "aci busy, aci_write(0x%x) stopped.\n", byte); + return -EBUSY; + } +} + +static inline int aci_read(struct snd_miro_aci *aci) +{ + unsigned char byte; + + if (aci_busy_wait(aci) >= 0) { + byte = inb(aci->aci_port + ACI_REG_STATUS); + return byte; + } else { + snd_printk(KERN_ERR "aci busy, aci_read() stopped.\n"); + return -EBUSY; + } +} + +int snd_aci_cmd(struct snd_miro_aci *aci, int write1, int write2, int write3) +{ + int write[] = {write1, write2, write3}; + int value, i; + + if (mutex_lock_interruptible(&aci->aci_mutex)) + return -EINTR; + + for (i=0; i<3; i++) { + if (write[i]< 0 || write[i] > 255) + break; + else { + value = aci_write(aci, write[i]); + if (value < 0) + goto out; + } + } + + value = aci_read(aci); + +out: mutex_unlock(&aci->aci_mutex); + return value; +} +EXPORT_SYMBOL(snd_aci_cmd); + +static int aci_getvalue(struct snd_miro_aci *aci, unsigned char index) +{ + return snd_aci_cmd(aci, ACI_STATUS, index, -1); +} + +static int aci_setvalue(struct snd_miro_aci *aci, unsigned char index, + int value) +{ + return snd_aci_cmd(aci, index, value, -1); +} + +struct snd_miro_aci *snd_aci_get_aci(void) +{ + if (aci_device.aci_port == 0) + return NULL; + return &aci_device; +} +EXPORT_SYMBOL(snd_aci_get_aci); + +/* + * MIXER part + */ + +#define snd_miro_info_capture snd_ctl_boolean_mono_info + +static int snd_miro_get_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int value; + + value = aci_getvalue(miro->aci, ACI_S_GENERAL); + if (value < 0) { + snd_printk(KERN_ERR "snd_miro_get_capture() failed: %d\n", + value); + return value; + } + + ucontrol->value.integer.value[0] = value & 0x20; + + return 0; +} + +static int snd_miro_put_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int change, value, error; + + value = !(ucontrol->value.integer.value[0]); + + error = aci_setvalue(miro->aci, ACI_SET_SOLOMODE, value); + if (error < 0) { + snd_printk(KERN_ERR "snd_miro_put_capture() failed: %d\n", + error); + return error; + } + + change = (value != miro->aci->aci_solomode); + miro->aci->aci_solomode = value; + + return change; +} + +static int snd_miro_info_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 3; + + return 0; +} + +static int snd_miro_get_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int value; + + if (miro->aci->aci_version <= 176) { + + /* + OSS says it's not readable with versions < 176. + But it doesn't work on my card, + which is a PCM12 with aci_version = 176. + */ + + ucontrol->value.integer.value[0] = miro->aci->aci_preamp; + return 0; + } + + value = aci_getvalue(miro->aci, ACI_GET_PREAMP); + if (value < 0) { + snd_printk(KERN_ERR "snd_miro_get_preamp() failed: %d\n", + value); + return value; + } + + ucontrol->value.integer.value[0] = value; + + return 0; +} + +static int snd_miro_put_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int error, value, change; + + value = ucontrol->value.integer.value[0]; + + error = aci_setvalue(miro->aci, ACI_SET_PREAMP, value); + if (error < 0) { + snd_printk(KERN_ERR "snd_miro_put_preamp() failed: %d\n", + error); + return error; + } + + change = (value != miro->aci->aci_preamp); + miro->aci->aci_preamp = value; + + return change; +} + +#define snd_miro_info_amp snd_ctl_boolean_mono_info + +static int snd_miro_get_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = miro->aci->aci_amp; + + return 0; +} + +static int snd_miro_put_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int error, value, change; + + value = ucontrol->value.integer.value[0]; + + error = aci_setvalue(miro->aci, ACI_SET_POWERAMP, value); + if (error < 0) { + snd_printk(KERN_ERR "snd_miro_put_amp() to %d failed: %d\n", value, error); + return error; + } + + change = (value != miro->aci->aci_amp); + miro->aci->aci_amp = value; + + return change; +} + +#define MIRO_DOUBLE(ctl_name, ctl_index, get_right_reg, set_right_reg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = ctl_name, \ + .index = ctl_index, \ + .info = snd_miro_info_double, \ + .get = snd_miro_get_double, \ + .put = snd_miro_put_double, \ + .private_value = get_right_reg | (set_right_reg << 8) \ +} + +static int snd_miro_info_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int reg = kcontrol->private_value & 0xff; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + + if ((reg >= ACI_GET_EQ1) && (reg <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + uinfo->value.integer.min = - 0x7f; + uinfo->value.integer.max = 0x7f; + } else { + + /* non-equalizer elements */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0x20; + } + + return 0; +} + +static int snd_miro_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uinfo) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int left_val, right_val; + + int right_reg = kcontrol->private_value & 0xff; + int left_reg = right_reg + 1; + + right_val = aci_getvalue(miro->aci, right_reg); + if (right_val < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", right_reg, right_val); + return right_val; + } + + left_val = aci_getvalue(miro->aci, left_reg); + if (left_val < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", left_reg, left_val); + return left_val; + } + + if ((right_reg >= ACI_GET_EQ1) && (right_reg <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + if (left_val < 0x80) { + uinfo->value.integer.value[0] = left_val; + } else { + uinfo->value.integer.value[0] = 0x80 - left_val; + } + + if (right_val < 0x80) { + uinfo->value.integer.value[1] = right_val; + } else { + uinfo->value.integer.value[1] = 0x80 - right_val; + } + + } else { + + /* non-equalizer elements */ + + uinfo->value.integer.value[0] = 0x20 - left_val; + uinfo->value.integer.value[1] = 0x20 - right_val; + } + + return 0; +} + +static int snd_miro_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + struct snd_miro_aci *aci = miro->aci; + int left, right, left_old, right_old; + int setreg_left, setreg_right, getreg_left, getreg_right; + int change, error; + + left = ucontrol->value.integer.value[0]; + right = ucontrol->value.integer.value[1]; + + setreg_right = (kcontrol->private_value >> 8) & 0xff; + setreg_left = setreg_right + 8; + if (setreg_right == ACI_SET_MASTER) + setreg_left -= 7; + + getreg_right = kcontrol->private_value & 0xff; + getreg_left = getreg_right + 1; + + left_old = aci_getvalue(aci, getreg_left); + if (left_old < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_left, left_old); + return left_old; + } + + right_old = aci_getvalue(aci, getreg_right); + if (right_old < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_right, right_old); + return right_old; + } + + if ((getreg_right >= ACI_GET_EQ1) && (getreg_right <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + if (left < -0x7f || left > 0x7f || + right < -0x7f || right > 0x7f) + return -EINVAL; + + if (left_old > 0x80) + left_old = 0x80 - left_old; + if (right_old > 0x80) + right_old = 0x80 - right_old; + + if (left >= 0) { + error = aci_setvalue(aci, setreg_left, left); + if (error < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + left, error); + return error; + } + } else { + error = aci_setvalue(aci, setreg_left, 0x80 - left); + if (error < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x80 - left, error); + return error; + } + } + + if (right >= 0) { + error = aci_setvalue(aci, setreg_right, right); + if (error < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + right, error); + return error; + } + } else { + error = aci_setvalue(aci, setreg_right, 0x80 - right); + if (error < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x80 - right, error); + return error; + } + } + + } else { + + /* non-equalizer elements */ + + if (left < 0 || left > 0x20 || + right < 0 || right > 0x20) + return -EINVAL; + + left_old = 0x20 - left_old; + right_old = 0x20 - right_old; + + error = aci_setvalue(aci, setreg_left, 0x20 - left); + if (error < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x20 - left, error); + return error; + } + error = aci_setvalue(aci, setreg_right, 0x20 - right); + if (error < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x20 - right, error); + return error; + } + } + + change = (left != left_old) || (right != right_old); + + return change; +} + +static struct snd_kcontrol_new snd_miro_controls[] = { +MIRO_DOUBLE("Master Playback Volume", 0, ACI_GET_MASTER, ACI_SET_MASTER), +MIRO_DOUBLE("Mic Playback Volume", 1, ACI_GET_MIC, ACI_SET_MIC), +MIRO_DOUBLE("Line Playback Volume", 1, ACI_GET_LINE, ACI_SET_LINE), +MIRO_DOUBLE("CD Playback Volume", 0, ACI_GET_CD, ACI_SET_CD), +MIRO_DOUBLE("Synth Playback Volume", 0, ACI_GET_SYNTH, ACI_SET_SYNTH), +MIRO_DOUBLE("PCM Playback Volume", 1, ACI_GET_PCM, ACI_SET_PCM), +MIRO_DOUBLE("Aux Playback Volume", 2, ACI_GET_LINE2, ACI_SET_LINE2), +}; + +/* Equalizer with seven bands (only PCM20) + from -12dB up to +12dB on each band */ +static struct snd_kcontrol_new snd_miro_eq_controls[] = { +MIRO_DOUBLE("Tone Control - 28 Hz", 0, ACI_GET_EQ1, ACI_SET_EQ1), +MIRO_DOUBLE("Tone Control - 160 Hz", 0, ACI_GET_EQ2, ACI_SET_EQ2), +MIRO_DOUBLE("Tone Control - 400 Hz", 0, ACI_GET_EQ3, ACI_SET_EQ3), +MIRO_DOUBLE("Tone Control - 1 kHz", 0, ACI_GET_EQ4, ACI_SET_EQ4), +MIRO_DOUBLE("Tone Control - 2.5 kHz", 0, ACI_GET_EQ5, ACI_SET_EQ5), +MIRO_DOUBLE("Tone Control - 6.3 kHz", 0, ACI_GET_EQ6, ACI_SET_EQ6), +MIRO_DOUBLE("Tone Control - 16 kHz", 0, ACI_GET_EQ7, ACI_SET_EQ7), +}; + +static struct snd_kcontrol_new snd_miro_radio_control[] = { +MIRO_DOUBLE("Radio Playback Volume", 0, ACI_GET_LINE1, ACI_SET_LINE1), +}; + +static struct snd_kcontrol_new snd_miro_line_control[] = { +MIRO_DOUBLE("Line Playback Volume", 2, ACI_GET_LINE1, ACI_SET_LINE1), +}; + +static struct snd_kcontrol_new snd_miro_preamp_control[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost", + .index = 1, + .info = snd_miro_info_preamp, + .get = snd_miro_get_preamp, + .put = snd_miro_put_preamp, +}}; + +static struct snd_kcontrol_new snd_miro_amp_control[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Boost", + .index = 0, + .info = snd_miro_info_amp, + .get = snd_miro_get_amp, + .put = snd_miro_put_amp, +}}; + +static struct snd_kcontrol_new snd_miro_capture_control[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Switch", + .index = 0, + .info = snd_miro_info_capture, + .get = snd_miro_get_capture, + .put = snd_miro_put_capture, +}}; + +static unsigned char aci_init_values[][2] = { + { ACI_SET_MUTE, 0x00 }, + { ACI_SET_POWERAMP, 0x00 }, + { ACI_SET_PREAMP, 0x00 }, + { ACI_SET_SOLOMODE, 0x00 }, + { ACI_SET_MIC + 0, 0x20 }, + { ACI_SET_MIC + 8, 0x20 }, + { ACI_SET_LINE + 0, 0x20 }, + { ACI_SET_LINE + 8, 0x20 }, + { ACI_SET_CD + 0, 0x20 }, + { ACI_SET_CD + 8, 0x20 }, + { ACI_SET_PCM + 0, 0x20 }, + { ACI_SET_PCM + 8, 0x20 }, + { ACI_SET_LINE1 + 0, 0x20 }, + { ACI_SET_LINE1 + 8, 0x20 }, + { ACI_SET_LINE2 + 0, 0x20 }, + { ACI_SET_LINE2 + 8, 0x20 }, + { ACI_SET_SYNTH + 0, 0x20 }, + { ACI_SET_SYNTH + 8, 0x20 }, + { ACI_SET_MASTER + 0, 0x20 }, + { ACI_SET_MASTER + 1, 0x20 }, +}; + +static int snd_set_aci_init_values(struct snd_miro *miro) +{ + int idx, error; + struct snd_miro_aci *aci = miro->aci; + + /* enable WSS on PCM1 */ + + if ((aci->aci_product == 'A') && wss) { + error = aci_setvalue(aci, ACI_SET_WSS, wss); + if (error < 0) { + snd_printk(KERN_ERR "enabling WSS mode failed\n"); + return error; + } + } + + /* enable IDE port */ + + if (ide) { + error = aci_setvalue(aci, ACI_SET_IDE, ide); + if (error < 0) { + snd_printk(KERN_ERR "enabling IDE port failed\n"); + return error; + } + } + + /* set common aci values */ + + for (idx = 0; idx < ARRAY_SIZE(aci_init_values); idx++) { + error = aci_setvalue(aci, aci_init_values[idx][0], + aci_init_values[idx][1]); + if (error < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + aci_init_values[idx][0], error); + return error; + } + } + aci->aci_amp = 0; + aci->aci_preamp = 0; + aci->aci_solomode = 1; + + return 0; +} + +static int snd_miro_mixer(struct snd_card *card, + struct snd_miro *miro) +{ + unsigned int idx; + int err; + + if (snd_BUG_ON(!miro || !card)) + return -EINVAL; + + switch (miro->hardware) { + case OPTi9XX_HW_82C924: + strcpy(card->mixername, "ACI & OPTi924"); + break; + case OPTi9XX_HW_82C929: + strcpy(card->mixername, "ACI & OPTi929"); + break; + default: + snd_BUG(); + break; + } + + for (idx = 0; idx < ARRAY_SIZE(snd_miro_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_controls[idx], miro))) < 0) + return err; + } + + if ((miro->aci->aci_product == 'A') || + (miro->aci->aci_product == 'B')) { + /* PCM1/PCM12 with power-amp and Line 2 */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_line_control[0], miro))) < 0) + return err; + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_amp_control[0], miro))) < 0) + return err; + } + + if ((miro->aci->aci_product == 'B') || + (miro->aci->aci_product == 'C')) { + /* PCM12/PCM20 with mic-preamp */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_preamp_control[0], miro))) < 0) + return err; + if (miro->aci->aci_version >= 176) + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_capture_control[0], miro))) < 0) + return err; + } + + if (miro->aci->aci_product == 'C') { + /* PCM20 with radio and 7 band equalizer */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_radio_control[0], miro))) < 0) + return err; + for (idx = 0; idx < ARRAY_SIZE(snd_miro_eq_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_eq_controls[idx], miro))) < 0) + return err; + } + } + + return 0; +} + +static int snd_miro_init(struct snd_miro *chip, + unsigned short hardware) +{ + static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2}; + + chip->hardware = hardware; + strcpy(chip->name, snd_opti9xx_names[hardware]); + + chip->mc_base_size = opti9xx_mc_size[hardware]; + + spin_lock_init(&chip->lock); + + chip->wss_base = -1; + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + chip->mpu_port = -1; + chip->mpu_irq = -1; + + chip->pwd_reg = 3; + +#ifdef CONFIG_PNP + if (isapnp && chip->mc_base) + /* PnP resource gives the least 10 bits */ + chip->mc_base |= 0xc00; + else +#endif + chip->mc_base = 0xf8c; + + switch (hardware) { + case OPTi9XX_HW_82C929: + chip->password = 0xe3; + break; + + case OPTi9XX_HW_82C924: + chip->password = 0xe5; + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", hardware); + return -ENODEV; + } + + return 0; +} + +static unsigned char snd_miro_read(struct snd_miro *chip, + unsigned char reg) +{ + unsigned long flags; + unsigned char retval = 0xff; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_base + 9); + break; + } + /* fall through */ + + case OPTi9XX_HW_82C929: + retval = inb(chip->mc_base + reg); + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); + return retval; +} + +static void snd_miro_write(struct snd_miro *chip, unsigned char reg, + unsigned char value) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_base + 9); + break; + } + /* fall through */ + + case OPTi9XX_HW_82C929: + outb(value, chip->mc_base + reg); + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); +} + +static inline void snd_miro_write_mask(struct snd_miro *chip, + unsigned char reg, unsigned char value, unsigned char mask) +{ + unsigned char oldval = snd_miro_read(chip, reg); + + snd_miro_write(chip, reg, (oldval & ~mask) | (value & mask)); +} + +/* + * Proc Interface + */ + +static void snd_miro_proc_read(struct snd_info_entry * entry, + struct snd_info_buffer *buffer) +{ + struct snd_miro *miro = (struct snd_miro *) entry->private_data; + struct snd_miro_aci *aci = miro->aci; + char* model = "unknown"; + + /* miroSOUND PCM1 pro, early PCM12 */ + + if ((miro->hardware == OPTi9XX_HW_82C929) && + (aci->aci_vendor == 'm') && + (aci->aci_product == 'A')) { + switch (aci->aci_version) { + case 3: + model = "miroSOUND PCM1 pro"; + break; + default: + model = "miroSOUND PCM1 pro / (early) PCM12"; + break; + } + } + + /* miroSOUND PCM12, PCM12 (Rev. E), PCM12 pnp */ + + if ((miro->hardware == OPTi9XX_HW_82C924) && + (aci->aci_vendor == 'm') && + (aci->aci_product == 'B')) { + switch (aci->aci_version) { + case 4: + model = "miroSOUND PCM12"; + break; + case 176: + model = "miroSOUND PCM12 (Rev. E)"; + break; + default: + model = "miroSOUND PCM12 / PCM12 pnp"; + break; + } + } + + /* miroSOUND PCM20 radio */ + + if ((miro->hardware == OPTi9XX_HW_82C924) && + (aci->aci_vendor == 'm') && + (aci->aci_product == 'C')) { + switch (aci->aci_version) { + case 7: + model = "miroSOUND PCM20 radio (Rev. E)"; + break; + default: + model = "miroSOUND PCM20 radio"; + break; + } + } + + snd_iprintf(buffer, "\nGeneral information:\n"); + snd_iprintf(buffer, " model : %s\n", model); + snd_iprintf(buffer, " opti : %s\n", miro->name); + snd_iprintf(buffer, " codec : %s\n", miro->pcm->name); + snd_iprintf(buffer, " port : 0x%lx\n", miro->wss_base); + snd_iprintf(buffer, " irq : %d\n", miro->irq); + snd_iprintf(buffer, " dma : %d,%d\n\n", miro->dma1, miro->dma2); + + snd_iprintf(buffer, "MPU-401:\n"); + snd_iprintf(buffer, " port : 0x%lx\n", miro->mpu_port); + snd_iprintf(buffer, " irq : %d\n\n", miro->mpu_irq); + + snd_iprintf(buffer, "ACI information:\n"); + snd_iprintf(buffer, " vendor : "); + switch (aci->aci_vendor) { + case 'm': + snd_iprintf(buffer, "Miro\n"); + break; + default: + snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_vendor); + break; + } + + snd_iprintf(buffer, " product : "); + switch (aci->aci_product) { + case 'A': + snd_iprintf(buffer, "miroSOUND PCM1 pro / (early) PCM12\n"); + break; + case 'B': + snd_iprintf(buffer, "miroSOUND PCM12\n"); + break; + case 'C': + snd_iprintf(buffer, "miroSOUND PCM20 radio\n"); + break; + default: + snd_iprintf(buffer, "unknown (0x%x)\n", aci->aci_product); + break; + } + + snd_iprintf(buffer, " firmware: %d (0x%x)\n", + aci->aci_version, aci->aci_version); + snd_iprintf(buffer, " port : 0x%lx-0x%lx\n", + aci->aci_port, aci->aci_port+2); + snd_iprintf(buffer, " wss : 0x%x\n", wss); + snd_iprintf(buffer, " ide : 0x%x\n", ide); + snd_iprintf(buffer, " solomode: 0x%x\n", aci->aci_solomode); + snd_iprintf(buffer, " amp : 0x%x\n", aci->aci_amp); + snd_iprintf(buffer, " preamp : 0x%x\n", aci->aci_preamp); +} + +static void snd_miro_proc_init(struct snd_card *card, + struct snd_miro *miro) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(card, "miro", &entry)) + snd_info_set_text_ops(entry, miro, snd_miro_proc_read); +} + +/* + * Init + */ + +static int snd_miro_configure(struct snd_miro *chip) +{ + unsigned char wss_base_bits; + unsigned char irq_bits; + unsigned char dma_bits; + unsigned char mpu_port_bits = 0; + unsigned char mpu_irq_bits; + unsigned long flags; + + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff); + break; + case OPTi9XX_HW_82C929: + /* untested init commands for OPTi929 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c); + break; + default: + snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware); + return -EINVAL; + } + + /* PnP resource says it decodes only 10 bits of address */ + switch (chip->wss_base & 0x3ff) { + case 0x130: + chip->wss_base = 0x530; + wss_base_bits = 0x00; + break; + case 0x204: + chip->wss_base = 0x604; + wss_base_bits = 0x03; + break; + case 0x280: + chip->wss_base = 0xe80; + wss_base_bits = 0x01; + break; + case 0x340: + chip->wss_base = 0xf40; + wss_base_bits = 0x02; + break; + default: + snd_printk(KERN_ERR "WSS port 0x%lx not valid\n", chip->wss_base); + goto __skip_base; + } + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30); + +__skip_base: + switch (chip->irq) { + case 5: + irq_bits = 0x05; + break; + case 7: + irq_bits = 0x01; + break; + case 9: + irq_bits = 0x02; + break; + case 10: + irq_bits = 0x03; + break; + case 11: + irq_bits = 0x04; + break; + default: + snd_printk(KERN_ERR "WSS irq # %d not valid\n", chip->irq); + goto __skip_resources; + } + + switch (chip->dma1) { + case 0: + dma_bits = 0x01; + break; + case 1: + dma_bits = 0x02; + break; + case 3: + dma_bits = 0x03; + break; + default: + snd_printk(KERN_ERR "WSS dma1 # %d not valid\n", chip->dma1); + goto __skip_resources; + } + + if (chip->dma1 == chip->dma2) { + snd_printk(KERN_ERR "don't want to share dmas\n"); + return -EBUSY; + } + + switch (chip->dma2) { + case 0: + case 1: + break; + default: + snd_printk(KERN_ERR "WSS dma2 # %d not valid\n", chip->dma2); + goto __skip_resources; + } + dma_bits |= 0x04; + + spin_lock_irqsave(&chip->lock, flags); + outb(irq_bits << 3 | dma_bits, chip->wss_base); + spin_unlock_irqrestore(&chip->lock, flags); + +__skip_resources: + if (chip->hardware > OPTi9XX_HW_82C928) { + switch (chip->mpu_port) { + case 0: + case -1: + break; + case 0x300: + mpu_port_bits = 0x03; + break; + case 0x310: + mpu_port_bits = 0x02; + break; + case 0x320: + mpu_port_bits = 0x01; + break; + case 0x330: + mpu_port_bits = 0x00; + break; + default: + snd_printk(KERN_ERR "MPU-401 port 0x%lx not valid\n", + chip->mpu_port); + goto __skip_mpu; + } + + switch (chip->mpu_irq) { + case 5: + mpu_irq_bits = 0x02; + break; + case 7: + mpu_irq_bits = 0x03; + break; + case 9: + mpu_irq_bits = 0x00; + break; + case 10: + mpu_irq_bits = 0x01; + break; + default: + snd_printk(KERN_ERR "MPU-401 irq # %d not valid\n", + chip->mpu_irq); + goto __skip_mpu; + } + + snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), + (chip->mpu_port <= 0) ? 0x00 : + 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3, + 0xf8); + } +__skip_mpu: + + return 0; +} + +static int snd_miro_opti_check(struct snd_miro *chip) +{ + unsigned char value; + + chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, + "OPTi9xx MC"); + if (chip->res_mc_base == NULL) + return -ENOMEM; + + value = snd_miro_read(chip, OPTi9XX_MC_REG(1)); + if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1))) + if (value == snd_miro_read(chip, OPTi9XX_MC_REG(1))) + return 0; + + release_and_free_resource(chip->res_mc_base); + chip->res_mc_base = NULL; + + return -ENODEV; +} + +static int snd_card_miro_detect(struct snd_card *card, + struct snd_miro *chip) +{ + int i, err; + + for (i = OPTi9XX_HW_82C929; i <= OPTi9XX_HW_82C924; i++) { + + if ((err = snd_miro_init(chip, i)) < 0) + return err; + + err = snd_miro_opti_check(chip); + if (err == 0) + return 1; + } + + return -ENODEV; +} + +static int snd_card_miro_aci_detect(struct snd_card *card, + struct snd_miro *miro) +{ + unsigned char regval; + int i; + struct snd_miro_aci *aci = &aci_device; + + miro->aci = aci; + + mutex_init(&aci->aci_mutex); + + /* get ACI port from OPTi9xx MC 4 */ + + regval=inb(miro->mc_base + 4); + aci->aci_port = (regval & 0x10) ? 0x344 : 0x354; + + miro->res_aci_port = request_region(aci->aci_port, 3, "miro aci"); + if (miro->res_aci_port == NULL) { + snd_printk(KERN_ERR "aci i/o area 0x%lx-0x%lx already used.\n", + aci->aci_port, aci->aci_port+2); + return -ENOMEM; + } + + /* force ACI into a known state */ + for (i = 0; i < 3; i++) + if (snd_aci_cmd(aci, ACI_ERROR_OP, -1, -1) < 0) { + snd_printk(KERN_ERR "can't force aci into known state.\n"); + return -ENXIO; + } + + aci->aci_vendor = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1); + aci->aci_product = snd_aci_cmd(aci, ACI_READ_IDCODE, -1, -1); + if (aci->aci_vendor < 0 || aci->aci_product < 0) { + snd_printk(KERN_ERR "can't read aci id on 0x%lx.\n", + aci->aci_port); + return -ENXIO; + } + + aci->aci_version = snd_aci_cmd(aci, ACI_READ_VERSION, -1, -1); + if (aci->aci_version < 0) { + snd_printk(KERN_ERR "can't read aci version on 0x%lx.\n", + aci->aci_port); + return -ENXIO; + } + + if (snd_aci_cmd(aci, ACI_INIT, -1, -1) < 0 || + snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0 || + snd_aci_cmd(aci, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0) { + snd_printk(KERN_ERR "can't initialize aci.\n"); + return -ENXIO; + } + + return 0; +} + +static void snd_card_miro_free(struct snd_card *card) +{ + struct snd_miro *miro = card->private_data; + + release_and_free_resource(miro->res_aci_port); + if (miro->aci) + miro->aci->aci_port = 0; + release_and_free_resource(miro->res_mc_base); +} + +static int snd_miro_probe(struct snd_card *card) +{ + int error; + struct snd_miro *miro = card->private_data; + struct snd_wss *codec; + struct snd_rawmidi *rmidi; + + if (!miro->res_mc_base) { + miro->res_mc_base = request_region(miro->mc_base, + miro->mc_base_size, + "miro (OPTi9xx MC)"); + if (miro->res_mc_base == NULL) { + snd_printk(KERN_ERR "request for OPTI9xx MC failed\n"); + return -ENOMEM; + } + } + + error = snd_card_miro_aci_detect(card, miro); + if (error < 0) { + snd_printk(KERN_ERR "unable to detect aci chip\n"); + return -ENODEV; + } + + miro->wss_base = port; + miro->mpu_port = mpu_port; + miro->irq = irq; + miro->mpu_irq = mpu_irq; + miro->dma1 = dma1; + miro->dma2 = dma2; + + /* init proc interface */ + snd_miro_proc_init(card, miro); + + error = snd_miro_configure(miro); + if (error) + return error; + + error = snd_wss_create(card, miro->wss_base + 4, -1, + miro->irq, miro->dma1, miro->dma2, + WSS_HW_DETECT, 0, &codec); + if (error < 0) + return error; + + error = snd_wss_pcm(codec, 0); + if (error < 0) + return error; + + error = snd_wss_mixer(codec); + if (error < 0) + return error; + + error = snd_wss_timer(codec, 0); + if (error < 0) + return error; + + miro->pcm = codec->pcm; + + error = snd_miro_mixer(card, miro); + if (error < 0) + return error; + + if (miro->aci->aci_vendor == 'm') { + /* It looks like a miro sound card. */ + switch (miro->aci->aci_product) { + case 'A': + sprintf(card->shortname, + "miroSOUND PCM1 pro / PCM12"); + break; + case 'B': + sprintf(card->shortname, + "miroSOUND PCM12"); + break; + case 'C': + sprintf(card->shortname, + "miroSOUND PCM20 radio"); + break; + default: + sprintf(card->shortname, + "unknown miro"); + snd_printk(KERN_INFO "unknown miro aci id\n"); + break; + } + } else { + snd_printk(KERN_INFO "found unsupported aci card\n"); + sprintf(card->shortname, "unknown Cardinal Technologies"); + } + + strcpy(card->driver, "miro"); + snprintf(card->longname, sizeof(card->longname), + "%s: OPTi%s, %s at 0x%lx, irq %d, dma %d&%d", + card->shortname, miro->name, codec->pcm->name, + miro->wss_base + 4, miro->irq, miro->dma1, miro->dma2); + + if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT) + rmidi = NULL; + else { + error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + mpu_port, 0, miro->mpu_irq, &rmidi); + if (error < 0) + snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n", + mpu_port); + } + + if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) { + struct snd_opl3 *opl3 = NULL; + struct snd_opl4 *opl4; + + if (snd_opl4_create(card, fm_port, fm_port - 8, + 2, &opl3, &opl4) < 0) + snd_printk(KERN_WARNING "no OPL4 device at 0x%lx\n", + fm_port); + } + + error = snd_set_aci_init_values(miro); + if (error < 0) + return error; + + return snd_card_register(card); +} + +static int snd_miro_isa_match(struct device *devptr, unsigned int n) +{ +#ifdef CONFIG_PNP + if (snd_miro_pnp_is_probed) + return 0; + if (isapnp) + return 0; +#endif + return 1; +} + +static int snd_miro_isa_probe(struct device *devptr, unsigned int n) +{ + static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1}; + static long possible_mpu_ports[] = {0x330, 0x300, 0x310, 0x320, -1}; + static int possible_irqs[] = {11, 9, 10, 7, -1}; + static int possible_mpu_irqs[] = {10, 5, 9, 7, -1}; + static int possible_dma1s[] = {3, 1, 0, -1}; + static int possible_dma2s[][2] = { {1, -1}, {0, -1}, {-1, -1}, + {0, -1} }; + + int error; + struct snd_miro *miro; + struct snd_card *card; + + error = snd_card_new(devptr, index, id, THIS_MODULE, + sizeof(struct snd_miro), &card); + if (error < 0) + return error; + + card->private_free = snd_card_miro_free; + miro = card->private_data; + + error = snd_card_miro_detect(card, miro); + if (error < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to detect OPTi9xx chip\n"); + return -ENODEV; + } + + if (port == SNDRV_AUTO_PORT) { + port = snd_legacy_find_free_ioport(possible_ports, 4); + if (port < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free WSS port\n"); + return -EBUSY; + } + } + + if (mpu_port == SNDRV_AUTO_PORT) { + mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2); + if (mpu_port < 0) { + snd_card_free(card); + snd_printk(KERN_ERR + "unable to find a free MPU401 port\n"); + return -EBUSY; + } + } + + if (irq == SNDRV_AUTO_IRQ) { + irq = snd_legacy_find_free_irq(possible_irqs); + if (irq < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (mpu_irq == SNDRV_AUTO_IRQ) { + mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs); + if (mpu_irq < 0) { + snd_card_free(card); + snd_printk(KERN_ERR + "unable to find a free MPU401 IRQ\n"); + return -EBUSY; + } + } + if (dma1 == SNDRV_AUTO_DMA) { + dma1 = snd_legacy_find_free_dma(possible_dma1s); + if (dma1 < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (dma2 == SNDRV_AUTO_DMA) { + dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4]); + if (dma2 < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + error = snd_miro_probe(card); + if (error < 0) { + snd_card_free(card); + return error; + } + + dev_set_drvdata(devptr, card); + return 0; +} + +static int snd_miro_isa_remove(struct device *devptr, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#define DEV_NAME "miro" + +static struct isa_driver snd_miro_driver = { + .match = snd_miro_isa_match, + .probe = snd_miro_isa_probe, + .remove = snd_miro_isa_remove, + /* FIXME: suspend/resume */ + .driver = { + .name = DEV_NAME + }, +}; + +#ifdef CONFIG_PNP + +static int snd_card_miro_pnp(struct snd_miro *chip, + struct pnp_card_link *card, + const struct pnp_card_device_id *pid) +{ + struct pnp_dev *pdev; + int err; + struct pnp_dev *devmpu; + struct pnp_dev *devmc; + + pdev = pnp_request_card_device(card, pid->devs[0].id, NULL); + if (pdev == NULL) + return -EBUSY; + + devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL); + if (devmpu == NULL) + return -EBUSY; + + devmc = pnp_request_card_device(card, pid->devs[2].id, NULL); + if (devmc == NULL) + return -EBUSY; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err); + return err; + } + + err = pnp_activate_dev(devmc); + if (err < 0) { + snd_printk(KERN_ERR "MC pnp configure failure: %d\n", + err); + return err; + } + + port = pnp_port_start(pdev, 1); + fm_port = pnp_port_start(pdev, 2) + 8; + + /* + * The MC(0) is never accessed and the miroSOUND PCM20 card does not + * include it in the PnP resource range. OPTI93x include it. + */ + chip->mc_base = pnp_port_start(devmc, 0) - 1; + chip->mc_base_size = pnp_port_len(devmc, 0) + 1; + + irq = pnp_irq(pdev, 0); + dma1 = pnp_dma(pdev, 0); + dma2 = pnp_dma(pdev, 1); + + if (mpu_port > 0) { + err = pnp_activate_dev(devmpu); + if (err < 0) { + snd_printk(KERN_ERR "MPU401 pnp configure failure\n"); + mpu_port = -1; + return err; + } + mpu_port = pnp_port_start(devmpu, 0); + mpu_irq = pnp_irq(devmpu, 0); + } + return 0; +} + +static int snd_miro_pnp_probe(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct snd_card *card; + int err; + struct snd_miro *miro; + + if (snd_miro_pnp_is_probed) + return -EBUSY; + if (!isapnp) + return -ENODEV; + err = snd_card_new(&pcard->card->dev, index, id, THIS_MODULE, + sizeof(struct snd_miro), &card); + if (err < 0) + return err; + + card->private_free = snd_card_miro_free; + miro = card->private_data; + + err = snd_card_miro_pnp(miro, pcard, pid); + if (err) { + snd_card_free(card); + return err; + } + + /* only miroSOUND PCM20 and PCM12 == OPTi924 */ + err = snd_miro_init(miro, OPTi9XX_HW_82C924); + if (err) { + snd_card_free(card); + return err; + } + + err = snd_miro_opti_check(miro); + if (err) { + snd_printk(KERN_ERR "OPTI chip not found\n"); + snd_card_free(card); + return err; + } + + err = snd_miro_probe(card); + if (err < 0) { + snd_card_free(card); + return err; + } + pnp_set_card_drvdata(pcard, card); + snd_miro_pnp_is_probed = 1; + return 0; +} + +static void snd_miro_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); + snd_miro_pnp_is_probed = 0; +} + +static struct pnp_card_driver miro_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "miro", + .id_table = snd_miro_pnpids, + .probe = snd_miro_pnp_probe, + .remove = snd_miro_pnp_remove, +}; +#endif + +static int __init alsa_card_miro_init(void) +{ +#ifdef CONFIG_PNP + pnp_register_card_driver(&miro_pnpc_driver); + if (snd_miro_pnp_is_probed) + return 0; + pnp_unregister_card_driver(&miro_pnpc_driver); +#endif + return isa_register_driver(&snd_miro_driver, 1); +} + +static void __exit alsa_card_miro_exit(void) +{ + if (!snd_miro_pnp_is_probed) { + isa_unregister_driver(&snd_miro_driver); + return; + } +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&miro_pnpc_driver); +#endif +} + +module_init(alsa_card_miro_init) +module_exit(alsa_card_miro_exit) diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c new file mode 100644 index 000000000..0d0fa6f54 --- /dev/null +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -0,0 +1,1209 @@ +/* + card-opti92x-ad1848.c - driver for OPTi 82c92x based soundcards. + Copyright (C) 1998-2000 by Massimo Piccioni + + Part of this code was developed at the Italian Ministry of Air Defence, + Sixth Division (oh, che pace ...), Rome. + + Thanks to Maria Grazia Pollarini, Salvatore Vassallo. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef OPTi93X +#include +#endif +#define SNDRV_LEGACY_FIND_FREE_IOPORT +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +MODULE_AUTHOR("Massimo Piccioni "); +MODULE_LICENSE("GPL"); +#ifdef OPTi93X +MODULE_DESCRIPTION("OPTi93X"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C931/3}}"); +#else /* OPTi93X */ +#ifdef CS4231 +MODULE_DESCRIPTION("OPTi92X - CS4231"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (CS4231)}," + "{OPTi,82C925 (CS4231)}}"); +#else /* CS4231 */ +MODULE_DESCRIPTION("OPTi92X - AD1848"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (AD1848)}," + "{OPTi,82C925 (AD1848)}," + "{OAK,Mozart}}"); +#endif /* CS4231 */ +#endif /* OPTi93X */ + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +//static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp = true; /* Enable ISA PnP detection */ +#endif +static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */ +static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */ +static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */ +static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */ +static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */ +static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +#if defined(CS4231) || defined(OPTi93X) +static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +#endif /* CS4231 || OPTi93X */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for opti9xx based soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for opti9xx based soundcard."); +//module_param(enable, bool, 0444); +//MODULE_PARM_DESC(enable, "Enable opti9xx soundcard."); +#ifdef CONFIG_PNP +module_param(isapnp, bool, 0444); +MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard."); +#endif +module_param_hw(port, long, ioport, 0444); +MODULE_PARM_DESC(port, "WSS port # for opti9xx driver."); +module_param_hw(mpu_port, long, ioport, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for opti9xx driver."); +module_param_hw(fm_port, long, ioport, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for opti9xx driver."); +module_param_hw(irq, int, irq, 0444); +MODULE_PARM_DESC(irq, "WSS irq # for opti9xx driver."); +module_param_hw(mpu_irq, int, irq, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for opti9xx driver."); +module_param_hw(dma1, int, dma, 0444); +MODULE_PARM_DESC(dma1, "1st dma # for opti9xx driver."); +#if defined(CS4231) || defined(OPTi93X) +module_param_hw(dma2, int, dma, 0444); +MODULE_PARM_DESC(dma2, "2nd dma # for opti9xx driver."); +#endif /* CS4231 || OPTi93X */ + +#define OPTi9XX_HW_82C928 1 +#define OPTi9XX_HW_82C929 2 +#define OPTi9XX_HW_82C924 3 +#define OPTi9XX_HW_82C925 4 +#define OPTi9XX_HW_82C930 5 +#define OPTi9XX_HW_82C931 6 +#define OPTi9XX_HW_82C933 7 +#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933 + +#define OPTi9XX_MC_REG(n) n + +#ifdef OPTi93X + +#define OPTi93X_STATUS 0x02 +#define OPTi93X_PORT(chip, r) ((chip)->port + OPTi93X_##r) + +#define OPTi93X_IRQ_PLAYBACK 0x04 +#define OPTi93X_IRQ_CAPTURE 0x08 + +#endif /* OPTi93X */ + +struct snd_opti9xx { + unsigned short hardware; + unsigned char password; + char name[7]; + + unsigned long mc_base; + struct resource *res_mc_base; + unsigned long mc_base_size; +#ifdef OPTi93X + unsigned long mc_indir_index; + struct resource *res_mc_indir; +#endif /* OPTi93X */ + struct snd_wss *codec; + unsigned long pwd_reg; + + spinlock_t lock; + + long wss_base; + int irq; +}; + +static int snd_opti9xx_pnp_is_probed; + +#ifdef CONFIG_PNP + +static const struct pnp_card_device_id snd_opti9xx_pnpids[] = { +#ifndef OPTi93X + /* OPTi 82C924 */ + { .id = "OPT0924", + .devs = { { "OPT0000" }, { "OPT0002" }, { "OPT0005" } }, + .driver_data = 0x0924 }, + /* OPTi 82C925 */ + { .id = "OPT0925", + .devs = { { "OPT9250" }, { "OPT0002" }, { "OPT0005" } }, + .driver_data = 0x0925 }, +#else + /* OPTi 82C931/3 */ + { .id = "OPT0931", .devs = { { "OPT9310" }, { "OPT0002" } }, + .driver_data = 0x0931 }, +#endif /* OPTi93X */ + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_opti9xx_pnpids); + +#endif /* CONFIG_PNP */ + +#define DEV_NAME KBUILD_MODNAME + +static char * snd_opti9xx_names[] = { + "unknown", + "82C928", "82C929", + "82C924", "82C925", + "82C930", "82C931", "82C933" +}; + +static int snd_opti9xx_init(struct snd_opti9xx *chip, + unsigned short hardware) +{ + static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2}; + + chip->hardware = hardware; + strcpy(chip->name, snd_opti9xx_names[hardware]); + + spin_lock_init(&chip->lock); + + chip->irq = -1; + +#ifndef OPTi93X +#ifdef CONFIG_PNP + if (isapnp && chip->mc_base) + /* PnP resource gives the least 10 bits */ + chip->mc_base |= 0xc00; + else +#endif /* CONFIG_PNP */ + { + chip->mc_base = 0xf8c; + chip->mc_base_size = opti9xx_mc_size[hardware]; + } +#else + chip->mc_base_size = opti9xx_mc_size[hardware]; +#endif + + switch (hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + chip->password = (hardware == OPTi9XX_HW_82C928) ? 0xe2 : 0xe3; + chip->pwd_reg = 3; + break; + + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + chip->password = 0xe5; + chip->pwd_reg = 3; + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + chip->mc_base = (hardware == OPTi9XX_HW_82C930) ? 0xf8f : 0xf8d; + if (!chip->mc_indir_index) + chip->mc_indir_index = 0xe0e; + chip->password = 0xe4; + chip->pwd_reg = 0; + break; +#endif /* OPTi93X */ + + default: + snd_printk(KERN_ERR "chip %d not supported\n", hardware); + return -ENODEV; + } + return 0; +} + +static unsigned char snd_opti9xx_read(struct snd_opti9xx *chip, + unsigned char reg) +{ + unsigned long flags; + unsigned char retval = 0xff; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_base + 9); + break; + } + /* Fall through */ + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + retval = inb(chip->mc_base + reg); + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + outb(reg, chip->mc_indir_index); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_indir_index + 1); + break; +#endif /* OPTi93X */ + + default: + snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); + return retval; +} + +static void snd_opti9xx_write(struct snd_opti9xx *chip, unsigned char reg, + unsigned char value) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_base + 9); + break; + } + /* Fall through */ + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + outb(value, chip->mc_base + reg); + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + outb(reg, chip->mc_indir_index); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_indir_index + 1); + break; +#endif /* OPTi93X */ + + default: + snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); +} + + +static inline void snd_opti9xx_write_mask(struct snd_opti9xx *chip, + unsigned char reg, unsigned char value, unsigned char mask) +{ + unsigned char oldval = snd_opti9xx_read(chip, reg); + + snd_opti9xx_write(chip, reg, (oldval & ~mask) | (value & mask)); +} + +static int snd_opti9xx_configure(struct snd_opti9xx *chip, + long port, + int irq, int dma1, int dma2, + long mpu_port, int mpu_irq) +{ + unsigned char wss_base_bits; + unsigned char irq_bits; + unsigned char dma_bits; + unsigned char mpu_port_bits = 0; + unsigned char mpu_irq_bits; + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + /* opti 929 mode (?), OPL3 clock output, audio enable */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0xf0, 0xfc); + /* enable wave audio */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02); + /* Fall through */ + + case OPTi9XX_HW_82C925: + /* enable WSS mode */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + /* OPL3 FM synthesis */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20); + /* disable Sound Blaster IRQ and DMA */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff); +#ifdef CS4231 + /* cs4231/4248 fix enabled */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); +#else + /* cs4231/4248 fix disabled */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02); +#endif /* CS4231 */ + break; + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20); + /* + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xa2, 0xae); + */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c); +#ifdef CS4231 + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); +#else + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02); +#endif /* CS4231 */ + break; + +#else /* OPTi93X */ + case OPTi9XX_HW_82C931: + /* disable 3D sound (set GPIO1 as output, low) */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(20), 0x04, 0x0c); + case OPTi9XX_HW_82C933: /* FALL THROUGH */ + /* + * The BTC 1817DW has QS1000 wavetable which is connected + * to the serial digital input of the OPTI931. + */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff); + /* + * This bit sets OPTI931 to automaticaly select FM + * or digital input signal. + */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01); + case OPTi9XX_HW_82C930: /* FALL THROUGH */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x03); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0x00, 0xff); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x10 | + (chip->hardware == OPTi9XX_HW_82C930 ? 0x00 : 0x04), + 0x34); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x20, 0xbf); + break; +#endif /* OPTi93X */ + + default: + snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware); + return -EINVAL; + } + + /* PnP resource says it decodes only 10 bits of address */ + switch (port & 0x3ff) { + case 0x130: + chip->wss_base = 0x530; + wss_base_bits = 0x00; + break; + case 0x204: + chip->wss_base = 0x604; + wss_base_bits = 0x03; + break; + case 0x280: + chip->wss_base = 0xe80; + wss_base_bits = 0x01; + break; + case 0x340: + chip->wss_base = 0xf40; + wss_base_bits = 0x02; + break; + default: + snd_printk(KERN_WARNING "WSS port 0x%lx not valid\n", port); + goto __skip_base; + } + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30); + +__skip_base: + switch (irq) { +//#ifdef OPTi93X + case 5: + irq_bits = 0x05; + break; +//#endif /* OPTi93X */ + case 7: + irq_bits = 0x01; + break; + case 9: + irq_bits = 0x02; + break; + case 10: + irq_bits = 0x03; + break; + case 11: + irq_bits = 0x04; + break; + default: + snd_printk(KERN_WARNING "WSS irq # %d not valid\n", irq); + goto __skip_resources; + } + + switch (dma1) { + case 0: + dma_bits = 0x01; + break; + case 1: + dma_bits = 0x02; + break; + case 3: + dma_bits = 0x03; + break; + default: + snd_printk(KERN_WARNING "WSS dma1 # %d not valid\n", dma1); + goto __skip_resources; + } + +#if defined(CS4231) || defined(OPTi93X) + if (dma1 == dma2) { + snd_printk(KERN_ERR "don't want to share dmas\n"); + return -EBUSY; + } + + switch (dma2) { + case 0: + case 1: + break; + default: + snd_printk(KERN_WARNING "WSS dma2 # %d not valid\n", dma2); + goto __skip_resources; + } + dma_bits |= 0x04; +#endif /* CS4231 || OPTi93X */ + +#ifndef OPTi93X + outb(irq_bits << 3 | dma_bits, chip->wss_base); +#else /* OPTi93X */ + snd_opti9xx_write(chip, OPTi9XX_MC_REG(3), (irq_bits << 3 | dma_bits)); +#endif /* OPTi93X */ + +__skip_resources: + if (chip->hardware > OPTi9XX_HW_82C928) { + switch (mpu_port) { + case 0: + case -1: + break; + case 0x300: + mpu_port_bits = 0x03; + break; + case 0x310: + mpu_port_bits = 0x02; + break; + case 0x320: + mpu_port_bits = 0x01; + break; + case 0x330: + mpu_port_bits = 0x00; + break; + default: + snd_printk(KERN_WARNING + "MPU-401 port 0x%lx not valid\n", mpu_port); + goto __skip_mpu; + } + + switch (mpu_irq) { + case 5: + mpu_irq_bits = 0x02; + break; + case 7: + mpu_irq_bits = 0x03; + break; + case 9: + mpu_irq_bits = 0x00; + break; + case 10: + mpu_irq_bits = 0x01; + break; + default: + snd_printk(KERN_WARNING "MPU-401 irq # %d not valid\n", + mpu_irq); + goto __skip_mpu; + } + + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), + (mpu_port <= 0) ? 0x00 : + 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3, + 0xf8); + } +__skip_mpu: + + return 0; +} + +#ifdef OPTi93X + +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_3db_step, -9300, 300, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_5bit, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_4bit_12db_max, -3300, 300, 0); + +static struct snd_kcontrol_new snd_opti93x_controls[] = { +WSS_DOUBLE("Master Playback Switch", 0, + OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Master Playback Volume", 0, + OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 1, 1, 31, 1, + db_scale_5bit_3db_step), +WSS_DOUBLE_TLV("PCM Playback Volume", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 31, 1, + db_scale_5bit), +WSS_DOUBLE_TLV("FM Playback Volume", 0, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 1, 1, 15, 1, + db_scale_4bit_12db_max), +WSS_DOUBLE("Line Playback Switch", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Line Playback Volume", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 15, 1, + db_scale_4bit_12db_max), +WSS_DOUBLE("Mic Playback Switch", 0, + OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Mic Playback Volume", 0, + OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 1, 1, 15, 1, + db_scale_4bit_12db_max), +WSS_DOUBLE_TLV("CD Playback Volume", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 1, 1, 15, 1, + db_scale_4bit_12db_max), +WSS_DOUBLE("Aux Playback Switch", 0, + OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Aux Playback Volume", 0, + OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 1, 1, 15, 1, + db_scale_4bit_12db_max), +}; + +static int snd_opti93x_mixer(struct snd_wss *chip) +{ + struct snd_card *card; + unsigned int idx; + struct snd_ctl_elem_id id1, id2; + int err; + + if (snd_BUG_ON(!chip || !chip->pcm)) + return -EINVAL; + + card = chip->card; + + strcpy(card->mixername, chip->pcm->name); + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUX0 switch to CD */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "CD Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) { + snd_printk(KERN_ERR "Cannot rename opti93x control\n"); + return err; + } + /* reassign AUX1 switch to FM */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "FM Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) { + snd_printk(KERN_ERR "Cannot rename opti93x control\n"); + return err; + } + /* remove AUX1 volume */ + strcpy(id1.name, "Aux Playback Volume"); id1.index = 1; + snd_ctl_remove_id(card, &id1); + + /* Replace WSS volume controls with OPTi93x volume controls */ + id1.index = 0; + for (idx = 0; idx < ARRAY_SIZE(snd_opti93x_controls); idx++) { + strcpy(id1.name, snd_opti93x_controls[idx].name); + snd_ctl_remove_id(card, &id1); + + err = snd_ctl_add(card, + snd_ctl_new1(&snd_opti93x_controls[idx], chip)); + if (err < 0) + return err; + } + return 0; +} + +static irqreturn_t snd_opti93x_interrupt(int irq, void *dev_id) +{ + struct snd_opti9xx *chip = dev_id; + struct snd_wss *codec = chip->codec; + unsigned char status; + + if (!codec) + return IRQ_HANDLED; + + status = snd_opti9xx_read(chip, OPTi9XX_MC_REG(11)); + if ((status & OPTi93X_IRQ_PLAYBACK) && codec->playback_substream) + snd_pcm_period_elapsed(codec->playback_substream); + if ((status & OPTi93X_IRQ_CAPTURE) && codec->capture_substream) { + snd_wss_overrange(codec); + snd_pcm_period_elapsed(codec->capture_substream); + } + outb(0x00, OPTi93X_PORT(codec, STATUS)); + return IRQ_HANDLED; +} + +#endif /* OPTi93X */ + +static int snd_opti9xx_read_check(struct snd_opti9xx *chip) +{ + unsigned char value; +#ifdef OPTi93X + unsigned long flags; +#endif + + chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, + "OPTi9xx MC"); + if (chip->res_mc_base == NULL) + return -EBUSY; +#ifndef OPTi93X + value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(1)); + if (value != 0xff && value != inb(chip->mc_base + OPTi9XX_MC_REG(1))) + if (value == snd_opti9xx_read(chip, OPTi9XX_MC_REG(1))) + return 0; +#else /* OPTi93X */ + chip->res_mc_indir = request_region(chip->mc_indir_index, 2, + "OPTi93x MC"); + if (chip->res_mc_indir == NULL) + return -EBUSY; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(((chip->mc_indir_index & 0x1f0) >> 4), chip->mc_base); + spin_unlock_irqrestore(&chip->lock, flags); + + value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)); + snd_opti9xx_write(chip, OPTi9XX_MC_REG(7), 0xff - value); + if (snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)) == 0xff - value) + return 0; + + release_and_free_resource(chip->res_mc_indir); + chip->res_mc_indir = NULL; +#endif /* OPTi93X */ + release_and_free_resource(chip->res_mc_base); + chip->res_mc_base = NULL; + + return -ENODEV; +} + +static int snd_card_opti9xx_detect(struct snd_card *card, + struct snd_opti9xx *chip) +{ + int i, err; + +#ifndef OPTi93X + for (i = OPTi9XX_HW_82C928; i < OPTi9XX_HW_82C930; i++) { +#else + for (i = OPTi9XX_HW_82C931; i >= OPTi9XX_HW_82C930; i--) { +#endif + err = snd_opti9xx_init(chip, i); + if (err < 0) + return err; + + err = snd_opti9xx_read_check(chip); + if (err == 0) + return 1; +#ifdef OPTi93X + chip->mc_indir_index = 0; +#endif + } + return -ENODEV; +} + +#ifdef CONFIG_PNP +static int snd_card_opti9xx_pnp(struct snd_opti9xx *chip, + struct pnp_card_link *card, + const struct pnp_card_device_id *pid) +{ + struct pnp_dev *pdev; + int err; + struct pnp_dev *devmpu; +#ifndef OPTi93X + struct pnp_dev *devmc; +#endif + + pdev = pnp_request_card_device(card, pid->devs[0].id, NULL); + if (pdev == NULL) + return -EBUSY; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err); + return err; + } + +#ifdef OPTi93X + port = pnp_port_start(pdev, 0) - 4; + fm_port = pnp_port_start(pdev, 1) + 8; + /* adjust mc_indir_index - some cards report it at 0xe?d, + other at 0xe?c but it really is always at 0xe?e */ + chip->mc_indir_index = (pnp_port_start(pdev, 3) & ~0xf) | 0xe; +#else + devmc = pnp_request_card_device(card, pid->devs[2].id, NULL); + if (devmc == NULL) + return -EBUSY; + + err = pnp_activate_dev(devmc); + if (err < 0) { + snd_printk(KERN_ERR "MC pnp configure failure: %d\n", err); + return err; + } + + port = pnp_port_start(pdev, 1); + fm_port = pnp_port_start(pdev, 2) + 8; + /* + * The MC(0) is never accessed and card does not + * include it in the PnP resource range. OPTI93x include it. + */ + chip->mc_base = pnp_port_start(devmc, 0) - 1; + chip->mc_base_size = pnp_port_len(devmc, 0) + 1; +#endif /* OPTi93X */ + irq = pnp_irq(pdev, 0); + dma1 = pnp_dma(pdev, 0); +#if defined(CS4231) || defined(OPTi93X) + dma2 = pnp_dma(pdev, 1); +#endif /* CS4231 || OPTi93X */ + + devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL); + + if (devmpu && mpu_port > 0) { + err = pnp_activate_dev(devmpu); + if (err < 0) { + snd_printk(KERN_ERR "MPU401 pnp configure failure\n"); + mpu_port = -1; + } else { + mpu_port = pnp_port_start(devmpu, 0); + mpu_irq = pnp_irq(devmpu, 0); + } + } + return pid->driver_data; +} +#endif /* CONFIG_PNP */ + +static void snd_card_opti9xx_free(struct snd_card *card) +{ + struct snd_opti9xx *chip = card->private_data; + + if (chip) { +#ifdef OPTi93X + if (chip->irq > 0) { + disable_irq(chip->irq); + free_irq(chip->irq, chip); + } + release_and_free_resource(chip->res_mc_indir); +#endif + release_and_free_resource(chip->res_mc_base); + } +} + +static int snd_opti9xx_probe(struct snd_card *card) +{ + static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1}; + int error; + int xdma2; + struct snd_opti9xx *chip = card->private_data; + struct snd_wss *codec; + struct snd_rawmidi *rmidi; + struct snd_hwdep *synth; + +#if defined(CS4231) || defined(OPTi93X) + xdma2 = dma2; +#else + xdma2 = -1; +#endif + + if (port == SNDRV_AUTO_PORT) { + port = snd_legacy_find_free_ioport(possible_ports, 4); + if (port < 0) { + snd_printk(KERN_ERR "unable to find a free WSS port\n"); + return -EBUSY; + } + } + error = snd_opti9xx_configure(chip, port, irq, dma1, xdma2, + mpu_port, mpu_irq); + if (error) + return error; + + error = snd_wss_create(card, chip->wss_base + 4, -1, irq, dma1, xdma2, +#ifdef OPTi93X + WSS_HW_OPTI93X, WSS_HWSHARE_IRQ, +#else + WSS_HW_DETECT, 0, +#endif + &codec); + if (error < 0) + return error; + chip->codec = codec; + error = snd_wss_pcm(codec, 0); + if (error < 0) + return error; + error = snd_wss_mixer(codec); + if (error < 0) + return error; +#ifdef OPTi93X + error = snd_opti93x_mixer(codec); + if (error < 0) + return error; +#endif +#ifdef CS4231 + error = snd_wss_timer(codec, 0); + if (error < 0) + return error; +#endif +#ifdef OPTi93X + error = request_irq(irq, snd_opti93x_interrupt, + 0, DEV_NAME" - WSS", chip); + if (error < 0) { + snd_printk(KERN_ERR "opti9xx: can't grab IRQ %d\n", irq); + return error; + } +#endif + chip->irq = irq; + strcpy(card->driver, chip->name); + sprintf(card->shortname, "OPTi %s", card->driver); +#if defined(CS4231) || defined(OPTi93X) + snprintf(card->longname, sizeof(card->longname), + "%s, %s at 0x%lx, irq %d, dma %d&%d", + card->shortname, codec->pcm->name, + chip->wss_base + 4, irq, dma1, xdma2); +#else + snprintf(card->longname, sizeof(card->longname), + "%s, %s at 0x%lx, irq %d, dma %d", + card->shortname, codec->pcm->name, chip->wss_base + 4, irq, + dma1); +#endif /* CS4231 || OPTi93X */ + + if (mpu_port <= 0 || mpu_port == SNDRV_AUTO_PORT) + rmidi = NULL; + else { + error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + mpu_port, 0, mpu_irq, &rmidi); + if (error) + snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n", + mpu_port); + } + + if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) { + struct snd_opl3 *opl3 = NULL; +#ifndef OPTi93X + if (chip->hardware == OPTi9XX_HW_82C928 || + chip->hardware == OPTi9XX_HW_82C929 || + chip->hardware == OPTi9XX_HW_82C924) { + struct snd_opl4 *opl4; + /* assume we have an OPL4 */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), + 0x20, 0x20); + if (snd_opl4_create(card, fm_port, fm_port - 8, + 2, &opl3, &opl4) < 0) { + /* no luck, use OPL3 instead */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), + 0x00, 0x20); + } + } +#endif /* !OPTi93X */ + if (!opl3 && snd_opl3_create(card, fm_port, fm_port + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n", + fm_port, fm_port + 4 - 1); + } + if (opl3) { + error = snd_opl3_hwdep_new(opl3, 0, 1, &synth); + if (error < 0) + return error; + } + } + + return snd_card_register(card); +} + +static int snd_opti9xx_card_new(struct device *pdev, struct snd_card **cardp) +{ + struct snd_card *card; + int err; + + err = snd_card_new(pdev, index, id, THIS_MODULE, + sizeof(struct snd_opti9xx), &card); + if (err < 0) + return err; + card->private_free = snd_card_opti9xx_free; + *cardp = card; + return 0; +} + +static int snd_opti9xx_isa_match(struct device *devptr, + unsigned int dev) +{ +#ifdef CONFIG_PNP + if (snd_opti9xx_pnp_is_probed) + return 0; + if (isapnp) + return 0; +#endif + return 1; +} + +static int snd_opti9xx_isa_probe(struct device *devptr, + unsigned int dev) +{ + struct snd_card *card; + int error; + static long possible_mpu_ports[] = {0x300, 0x310, 0x320, 0x330, -1}; +#ifdef OPTi93X + static int possible_irqs[] = {5, 9, 10, 11, 7, -1}; +#else + static int possible_irqs[] = {9, 10, 11, 7, -1}; +#endif /* OPTi93X */ + static int possible_mpu_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dma1s[] = {3, 1, 0, -1}; +#if defined(CS4231) || defined(OPTi93X) + static int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}}; +#endif /* CS4231 || OPTi93X */ + + if (mpu_port == SNDRV_AUTO_PORT) { + if ((mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) { + snd_printk(KERN_ERR "unable to find a free MPU401 port\n"); + return -EBUSY; + } + } + if (irq == SNDRV_AUTO_IRQ) { + if ((irq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_printk(KERN_ERR "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (mpu_irq == SNDRV_AUTO_IRQ) { + if ((mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) { + snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n"); + return -EBUSY; + } + } + if (dma1 == SNDRV_AUTO_DMA) { + if ((dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) { + snd_printk(KERN_ERR "unable to find a free DMA1\n"); + return -EBUSY; + } + } +#if defined(CS4231) || defined(OPTi93X) + if (dma2 == SNDRV_AUTO_DMA) { + if ((dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4])) < 0) { + snd_printk(KERN_ERR "unable to find a free DMA2\n"); + return -EBUSY; + } + } +#endif + + error = snd_opti9xx_card_new(devptr, &card); + if (error < 0) + return error; + + if ((error = snd_card_opti9xx_detect(card, card->private_data)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opti9xx_probe(card)) < 0) { + snd_card_free(card); + return error; + } + dev_set_drvdata(devptr, card); + return 0; +} + +static int snd_opti9xx_isa_remove(struct device *devptr, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_opti9xx_suspend(struct snd_card *card) +{ + struct snd_opti9xx *chip = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + chip->codec->suspend(chip->codec); + return 0; +} + +static int snd_opti9xx_resume(struct snd_card *card) +{ + struct snd_opti9xx *chip = card->private_data; + int error, xdma2; +#if defined(CS4231) || defined(OPTi93X) + xdma2 = dma2; +#else + xdma2 = -1; +#endif + + error = snd_opti9xx_configure(chip, port, irq, dma1, xdma2, + mpu_port, mpu_irq); + if (error) + return error; + chip->codec->resume(chip->codec); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_opti9xx_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_opti9xx_suspend(dev_get_drvdata(dev)); +} + +static int snd_opti9xx_isa_resume(struct device *dev, unsigned int n) +{ + return snd_opti9xx_resume(dev_get_drvdata(dev)); +} +#endif + +static struct isa_driver snd_opti9xx_driver = { + .match = snd_opti9xx_isa_match, + .probe = snd_opti9xx_isa_probe, + .remove = snd_opti9xx_isa_remove, +#ifdef CONFIG_PM + .suspend = snd_opti9xx_isa_suspend, + .resume = snd_opti9xx_isa_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + +#ifdef CONFIG_PNP +static int snd_opti9xx_pnp_probe(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct snd_card *card; + int error, hw; + struct snd_opti9xx *chip; + + if (snd_opti9xx_pnp_is_probed) + return -EBUSY; + if (! isapnp) + return -ENODEV; + error = snd_opti9xx_card_new(&pcard->card->dev, &card); + if (error < 0) + return error; + chip = card->private_data; + + hw = snd_card_opti9xx_pnp(chip, pcard, pid); + switch (hw) { + case 0x0924: + hw = OPTi9XX_HW_82C924; + break; + case 0x0925: + hw = OPTi9XX_HW_82C925; + break; + case 0x0931: + hw = OPTi9XX_HW_82C931; + break; + default: + snd_card_free(card); + return -ENODEV; + } + + if ((error = snd_opti9xx_init(chip, hw))) { + snd_card_free(card); + return error; + } + error = snd_opti9xx_read_check(chip); + if (error) { + snd_printk(KERN_ERR "OPTI chip not found\n"); + snd_card_free(card); + return error; + } + if ((error = snd_opti9xx_probe(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + snd_opti9xx_pnp_is_probed = 1; + return 0; +} + +static void snd_opti9xx_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); + snd_opti9xx_pnp_is_probed = 0; +} + +#ifdef CONFIG_PM +static int snd_opti9xx_pnp_suspend(struct pnp_card_link *pcard, + pm_message_t state) +{ + return snd_opti9xx_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_opti9xx_pnp_resume(struct pnp_card_link *pcard) +{ + return snd_opti9xx_resume(pnp_get_card_drvdata(pcard)); +} +#endif + +static struct pnp_card_driver opti9xx_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = DEV_NAME, + .id_table = snd_opti9xx_pnpids, + .probe = snd_opti9xx_pnp_probe, + .remove = snd_opti9xx_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_opti9xx_pnp_suspend, + .resume = snd_opti9xx_pnp_resume, +#endif +}; +#endif + +#ifdef OPTi93X +#define CHIP_NAME "82C93x" +#else +#define CHIP_NAME "82C92x" +#endif + +static int __init alsa_card_opti9xx_init(void) +{ +#ifdef CONFIG_PNP + pnp_register_card_driver(&opti9xx_pnpc_driver); + if (snd_opti9xx_pnp_is_probed) + return 0; + pnp_unregister_card_driver(&opti9xx_pnpc_driver); +#endif + return isa_register_driver(&snd_opti9xx_driver, 1); +} + +static void __exit alsa_card_opti9xx_exit(void) +{ + if (!snd_opti9xx_pnp_is_probed) { + isa_unregister_driver(&snd_opti9xx_driver); + return; + } +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&opti9xx_pnpc_driver); +#endif +} + +module_init(alsa_card_opti9xx_init) +module_exit(alsa_card_opti9xx_exit) diff --git a/sound/isa/opti9xx/opti92x-cs4231.c b/sound/isa/opti9xx/opti92x-cs4231.c new file mode 100644 index 000000000..b17ab19f6 --- /dev/null +++ b/sound/isa/opti9xx/opti92x-cs4231.c @@ -0,0 +1,2 @@ +#define CS4231 +#include "opti92x-ad1848.c" diff --git a/sound/isa/opti9xx/opti93x.c b/sound/isa/opti9xx/opti93x.c new file mode 100644 index 000000000..bad9da521 --- /dev/null +++ b/sound/isa/opti9xx/opti93x.c @@ -0,0 +1,3 @@ +#define OPTi93X +#include "opti92x-ad1848.c" + diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile new file mode 100644 index 000000000..f174a5b3c --- /dev/null +++ b/sound/isa/sb/Makefile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-sb-common-objs := sb_common.o sb_mixer.o +snd-sb8-dsp-objs := sb8_main.o sb8_midi.o +snd-sb16-dsp-objs := sb16_main.o +snd-sb16-csp-objs := sb16_csp.o +snd-sb8-objs := sb8.o +snd-sb16-objs := sb16.o +snd-sbawe-objs := sbawe.o emu8000.o +snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o +snd-jazz16-objs := jazz16.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_SB_COMMON) += snd-sb-common.o +obj-$(CONFIG_SND_SB16_DSP) += snd-sb16-dsp.o +obj-$(CONFIG_SND_SB8_DSP) += snd-sb8-dsp.o +obj-$(CONFIG_SND_SB8) += snd-sb8.o +obj-$(CONFIG_SND_SB16) += snd-sb16.o +obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o +obj-$(CONFIG_SND_JAZZ16) += snd-jazz16.o +ifeq ($(CONFIG_SND_SB16_CSP),y) + obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o + obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o +endif +obj-$(CONFIG_SND_SBAWE_SEQ) += snd-emu8000-synth.o diff --git a/sound/isa/sb/emu8000.c b/sound/isa/sb/emu8000.c new file mode 100644 index 000000000..24b91cb32 --- /dev/null +++ b/sound/isa/sb/emu8000.c @@ -0,0 +1,1173 @@ +/* + * Copyright (c) by Jaroslav Kysela + * and (c) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai + * + * Routines for control of EMU8000 chip + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * emu8000 register controls + */ + +/* + * The following routines read and write registers on the emu8000. They + * should always be called via the EMU8000*READ/WRITE macros and never + * directly. The macros handle the port number and command word. + */ +/* Write a word */ +void snd_emu8000_poke(struct snd_emu8000 *emu, unsigned int port, unsigned int reg, unsigned int val) +{ + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + outw((unsigned short)val, port); /* Send data */ + spin_unlock_irqrestore(&emu->reg_lock, flags); +} + +/* Read a word */ +unsigned short snd_emu8000_peek(struct snd_emu8000 *emu, unsigned int port, unsigned int reg) +{ + unsigned short res; + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + res = inw(port); /* Read data */ + spin_unlock_irqrestore(&emu->reg_lock, flags); + return res; +} + +/* Write a double word */ +void snd_emu8000_poke_dw(struct snd_emu8000 *emu, unsigned int port, unsigned int reg, unsigned int val) +{ + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + outw((unsigned short)val, port); /* Send low word of data */ + outw((unsigned short)(val>>16), port+2); /* Send high word of data */ + spin_unlock_irqrestore(&emu->reg_lock, flags); +} + +/* Read a double word */ +unsigned int snd_emu8000_peek_dw(struct snd_emu8000 *emu, unsigned int port, unsigned int reg) +{ + unsigned short low; + unsigned int res; + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + low = inw(port); /* Read low word of data */ + res = low + (inw(port+2) << 16); + spin_unlock_irqrestore(&emu->reg_lock, flags); + return res; +} + +/* + * Set up / close a channel to be used for DMA. + */ +/*exported*/ void +snd_emu8000_dma_chan(struct snd_emu8000 *emu, int ch, int mode) +{ + unsigned right_bit = (mode & EMU8000_RAM_RIGHT) ? 0x01000000 : 0; + mode &= EMU8000_RAM_MODE_MASK; + if (mode == EMU8000_RAM_CLOSE) { + EMU8000_CCCA_WRITE(emu, ch, 0); + EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F); + return; + } + EMU8000_DCYSUSV_WRITE(emu, ch, 0x80); + EMU8000_VTFT_WRITE(emu, ch, 0); + EMU8000_CVCF_WRITE(emu, ch, 0); + EMU8000_PTRX_WRITE(emu, ch, 0x40000000); + EMU8000_CPF_WRITE(emu, ch, 0x40000000); + EMU8000_PSST_WRITE(emu, ch, 0); + EMU8000_CSL_WRITE(emu, ch, 0); + if (mode == EMU8000_RAM_WRITE) /* DMA write */ + EMU8000_CCCA_WRITE(emu, ch, 0x06000000 | right_bit); + else /* DMA read */ + EMU8000_CCCA_WRITE(emu, ch, 0x04000000 | right_bit); +} + +/* + */ +static void +snd_emu8000_read_wait(struct snd_emu8000 *emu) +{ + while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) { + schedule_timeout_interruptible(1); + if (signal_pending(current)) + break; + } +} + +/* + */ +static void +snd_emu8000_write_wait(struct snd_emu8000 *emu) +{ + while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { + schedule_timeout_interruptible(1); + if (signal_pending(current)) + break; + } +} + +/* + * detect a card at the given port + */ +static int +snd_emu8000_detect(struct snd_emu8000 *emu) +{ + /* Initialise */ + EMU8000_HWCF1_WRITE(emu, 0x0059); + EMU8000_HWCF2_WRITE(emu, 0x0020); + EMU8000_HWCF3_WRITE(emu, 0x0000); + /* Check for a recognisable emu8000 */ + /* + if ((EMU8000_U1_READ(emu) & 0x000f) != 0x000c) + return -ENODEV; + */ + if ((EMU8000_HWCF1_READ(emu) & 0x007e) != 0x0058) + return -ENODEV; + if ((EMU8000_HWCF2_READ(emu) & 0x0003) != 0x0003) + return -ENODEV; + + snd_printdd("EMU8000 [0x%lx]: Synth chip found\n", + emu->port1); + return 0; +} + + +/* + * intiailize audio channels + */ +static void +init_audio(struct snd_emu8000 *emu) +{ + int ch; + + /* turn off envelope engines */ + for (ch = 0; ch < EMU8000_CHANNELS; ch++) + EMU8000_DCYSUSV_WRITE(emu, ch, 0x80); + + /* reset all other parameters to zero */ + for (ch = 0; ch < EMU8000_CHANNELS; ch++) { + EMU8000_ENVVOL_WRITE(emu, ch, 0); + EMU8000_ENVVAL_WRITE(emu, ch, 0); + EMU8000_DCYSUS_WRITE(emu, ch, 0); + EMU8000_ATKHLDV_WRITE(emu, ch, 0); + EMU8000_LFO1VAL_WRITE(emu, ch, 0); + EMU8000_ATKHLD_WRITE(emu, ch, 0); + EMU8000_LFO2VAL_WRITE(emu, ch, 0); + EMU8000_IP_WRITE(emu, ch, 0); + EMU8000_IFATN_WRITE(emu, ch, 0); + EMU8000_PEFE_WRITE(emu, ch, 0); + EMU8000_FMMOD_WRITE(emu, ch, 0); + EMU8000_TREMFRQ_WRITE(emu, ch, 0); + EMU8000_FM2FRQ2_WRITE(emu, ch, 0); + EMU8000_PTRX_WRITE(emu, ch, 0); + EMU8000_VTFT_WRITE(emu, ch, 0); + EMU8000_PSST_WRITE(emu, ch, 0); + EMU8000_CSL_WRITE(emu, ch, 0); + EMU8000_CCCA_WRITE(emu, ch, 0); + } + + for (ch = 0; ch < EMU8000_CHANNELS; ch++) { + EMU8000_CPF_WRITE(emu, ch, 0); + EMU8000_CVCF_WRITE(emu, ch, 0); + } +} + + +/* + * initialize DMA address + */ +static void +init_dma(struct snd_emu8000 *emu) +{ + EMU8000_SMALR_WRITE(emu, 0); + EMU8000_SMARR_WRITE(emu, 0); + EMU8000_SMALW_WRITE(emu, 0); + EMU8000_SMARW_WRITE(emu, 0); +} + +/* + * initialization arrays; from ADIP + */ +static unsigned short init1[128] = { + 0x03ff, 0x0030, 0x07ff, 0x0130, 0x0bff, 0x0230, 0x0fff, 0x0330, + 0x13ff, 0x0430, 0x17ff, 0x0530, 0x1bff, 0x0630, 0x1fff, 0x0730, + 0x23ff, 0x0830, 0x27ff, 0x0930, 0x2bff, 0x0a30, 0x2fff, 0x0b30, + 0x33ff, 0x0c30, 0x37ff, 0x0d30, 0x3bff, 0x0e30, 0x3fff, 0x0f30, + + 0x43ff, 0x0030, 0x47ff, 0x0130, 0x4bff, 0x0230, 0x4fff, 0x0330, + 0x53ff, 0x0430, 0x57ff, 0x0530, 0x5bff, 0x0630, 0x5fff, 0x0730, + 0x63ff, 0x0830, 0x67ff, 0x0930, 0x6bff, 0x0a30, 0x6fff, 0x0b30, + 0x73ff, 0x0c30, 0x77ff, 0x0d30, 0x7bff, 0x0e30, 0x7fff, 0x0f30, + + 0x83ff, 0x0030, 0x87ff, 0x0130, 0x8bff, 0x0230, 0x8fff, 0x0330, + 0x93ff, 0x0430, 0x97ff, 0x0530, 0x9bff, 0x0630, 0x9fff, 0x0730, + 0xa3ff, 0x0830, 0xa7ff, 0x0930, 0xabff, 0x0a30, 0xafff, 0x0b30, + 0xb3ff, 0x0c30, 0xb7ff, 0x0d30, 0xbbff, 0x0e30, 0xbfff, 0x0f30, + + 0xc3ff, 0x0030, 0xc7ff, 0x0130, 0xcbff, 0x0230, 0xcfff, 0x0330, + 0xd3ff, 0x0430, 0xd7ff, 0x0530, 0xdbff, 0x0630, 0xdfff, 0x0730, + 0xe3ff, 0x0830, 0xe7ff, 0x0930, 0xebff, 0x0a30, 0xefff, 0x0b30, + 0xf3ff, 0x0c30, 0xf7ff, 0x0d30, 0xfbff, 0x0e30, 0xffff, 0x0f30, +}; + +static unsigned short init2[128] = { + 0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330, + 0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730, + 0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30, + 0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30, + + 0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330, + 0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730, + 0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30, + 0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30, + + 0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330, + 0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730, + 0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30, + 0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30, + + 0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330, + 0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730, + 0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30, + 0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30, +}; + +static unsigned short init3[128] = { + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224, + + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3, + + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287, + 0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386, + 0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55, + + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F, + 0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +static unsigned short init4[128] = { + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224, + + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3, + + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287, + 0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386, + 0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55, + + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F, + 0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +/* send an initialization array + * Taken from the oss driver, not obvious from the doc how this + * is meant to work + */ +static void +send_array(struct snd_emu8000 *emu, unsigned short *data, int size) +{ + int i; + unsigned short *p; + + p = data; + for (i = 0; i < size; i++, p++) + EMU8000_INIT1_WRITE(emu, i, *p); + for (i = 0; i < size; i++, p++) + EMU8000_INIT2_WRITE(emu, i, *p); + for (i = 0; i < size; i++, p++) + EMU8000_INIT3_WRITE(emu, i, *p); + for (i = 0; i < size; i++, p++) + EMU8000_INIT4_WRITE(emu, i, *p); +} + + +/* + * Send initialization arrays to start up, this just follows the + * initialisation sequence in the adip. + */ +static void +init_arrays(struct snd_emu8000 *emu) +{ + send_array(emu, init1, ARRAY_SIZE(init1)/4); + + msleep((1024 * 1000) / 44100); /* wait for 1024 clocks */ + send_array(emu, init2, ARRAY_SIZE(init2)/4); + send_array(emu, init3, ARRAY_SIZE(init3)/4); + + EMU8000_HWCF4_WRITE(emu, 0); + EMU8000_HWCF5_WRITE(emu, 0x83); + EMU8000_HWCF6_WRITE(emu, 0x8000); + + send_array(emu, init4, ARRAY_SIZE(init4)/4); +} + + +#define UNIQUE_ID1 0xa5b9 +#define UNIQUE_ID2 0x9d53 + +/* + * Size the onboard memory. + * This is written so as not to need arbitrary delays after the write. It + * seems that the only way to do this is to use the one channel and keep + * reallocating between read and write. + */ +static void +size_dram(struct snd_emu8000 *emu) +{ + int i, size; + + if (emu->dram_checked) + return; + + size = 0; + + /* write out a magic number */ + snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE); + snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_READ); + EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET); + EMU8000_SMLD_WRITE(emu, UNIQUE_ID1); + snd_emu8000_init_fm(emu); /* This must really be here and not 2 lines back even */ + snd_emu8000_write_wait(emu); + + /* + * Detect first 512 KiB. If a write succeeds at the beginning of a + * 512 KiB page we assume that the whole page is there. + */ + EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET); + EMU8000_SMLD_READ(emu); /* discard stale data */ + if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1) + goto skip_detect; /* No RAM */ + snd_emu8000_read_wait(emu); + + for (size = 512 * 1024; size < EMU8000_MAX_DRAM; size += 512 * 1024) { + + /* Write a unique data on the test address. + * if the address is out of range, the data is written on + * 0x200000(=EMU8000_DRAM_OFFSET). Then the id word is + * changed by this data. + */ + /*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);*/ + EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1)); + EMU8000_SMLD_WRITE(emu, UNIQUE_ID2); + snd_emu8000_write_wait(emu); + + /* + * read the data on the just written DRAM address + * if not the same then we have reached the end of ram. + */ + /*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_READ);*/ + EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1)); + /*snd_emu8000_read_wait(emu);*/ + EMU8000_SMLD_READ(emu); /* discard stale data */ + if (EMU8000_SMLD_READ(emu) != UNIQUE_ID2) + break; /* no memory at this address */ + snd_emu8000_read_wait(emu); + + /* + * If it is the same it could be that the address just + * wraps back to the beginning; so check to see if the + * initial value has been overwritten. + */ + EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET); + EMU8000_SMLD_READ(emu); /* discard stale data */ + if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1) + break; /* we must have wrapped around */ + snd_emu8000_read_wait(emu); + + /* Otherwise, it's valid memory. */ + } + +skip_detect: + /* wait until FULL bit in SMAxW register is false */ + for (i = 0; i < 10000; i++) { + if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0) + break; + schedule_timeout_interruptible(1); + if (signal_pending(current)) + break; + } + snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_CLOSE); + snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_CLOSE); + + pr_info("EMU8000 [0x%lx]: %d KiB on-board DRAM detected\n", + emu->port1, size/1024); + + emu->mem_size = size; + emu->dram_checked = 1; +} + + +/* + * Initiailise the FM section. You have to do this to use sample RAM + * and therefore lose 2 voices. + */ +/*exported*/ void +snd_emu8000_init_fm(struct snd_emu8000 *emu) +{ + unsigned long flags; + + /* Initialize the last two channels for DRAM refresh and producing + the reverb and chorus effects for Yamaha OPL-3 synthesizer */ + + /* 31: FM left channel, 0xffffe0-0xffffe8 */ + EMU8000_DCYSUSV_WRITE(emu, 30, 0x80); + EMU8000_PSST_WRITE(emu, 30, 0xFFFFFFE0); /* full left */ + EMU8000_CSL_WRITE(emu, 30, 0x00FFFFE8 | (emu->fm_chorus_depth << 24)); + EMU8000_PTRX_WRITE(emu, 30, (emu->fm_reverb_depth << 8)); + EMU8000_CPF_WRITE(emu, 30, 0); + EMU8000_CCCA_WRITE(emu, 30, 0x00FFFFE3); + + /* 32: FM right channel, 0xfffff0-0xfffff8 */ + EMU8000_DCYSUSV_WRITE(emu, 31, 0x80); + EMU8000_PSST_WRITE(emu, 31, 0x00FFFFF0); /* full right */ + EMU8000_CSL_WRITE(emu, 31, 0x00FFFFF8 | (emu->fm_chorus_depth << 24)); + EMU8000_PTRX_WRITE(emu, 31, (emu->fm_reverb_depth << 8)); + EMU8000_CPF_WRITE(emu, 31, 0x8000); + EMU8000_CCCA_WRITE(emu, 31, 0x00FFFFF3); + + snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0); + + spin_lock_irqsave(&emu->reg_lock, flags); + while (!(inw(EMU8000_PTR(emu)) & 0x1000)) + ; + while ((inw(EMU8000_PTR(emu)) & 0x1000)) + ; + spin_unlock_irqrestore(&emu->reg_lock, flags); + snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0x4828); + /* this is really odd part.. */ + outb(0x3C, EMU8000_PTR(emu)); + outb(0, EMU8000_DATA1(emu)); + + /* skew volume & cutoff */ + EMU8000_VTFT_WRITE(emu, 30, 0x8000FFFF); + EMU8000_VTFT_WRITE(emu, 31, 0x8000FFFF); +} + + +/* + * The main initialization routine. + */ +static void +snd_emu8000_init_hw(struct snd_emu8000 *emu) +{ + int i; + + emu->last_reg = 0xffff; /* reset the last register index */ + + /* initialize hardware configuration */ + EMU8000_HWCF1_WRITE(emu, 0x0059); + EMU8000_HWCF2_WRITE(emu, 0x0020); + + /* disable audio; this seems to reduce a clicking noise a bit.. */ + EMU8000_HWCF3_WRITE(emu, 0); + + /* initialize audio channels */ + init_audio(emu); + + /* initialize DMA */ + init_dma(emu); + + /* initialize init arrays */ + init_arrays(emu); + + /* + * Initialize the FM section of the AWE32, this is needed + * for DRAM refresh as well + */ + snd_emu8000_init_fm(emu); + + /* terminate all voices */ + for (i = 0; i < EMU8000_DRAM_VOICES; i++) + EMU8000_DCYSUSV_WRITE(emu, 0, 0x807F); + + /* check DRAM memory size */ + size_dram(emu); + + /* enable audio */ + EMU8000_HWCF3_WRITE(emu, 0x4); + + /* set equzlier, chorus and reverb modes */ + snd_emu8000_update_equalizer(emu); + snd_emu8000_update_chorus_mode(emu); + snd_emu8000_update_reverb_mode(emu); +} + + +/*---------------------------------------------------------------- + * Bass/Treble Equalizer + *----------------------------------------------------------------*/ + +static unsigned short bass_parm[12][3] = { + {0xD26A, 0xD36A, 0x0000}, /* -12 dB */ + {0xD25B, 0xD35B, 0x0000}, /* -8 */ + {0xD24C, 0xD34C, 0x0000}, /* -6 */ + {0xD23D, 0xD33D, 0x0000}, /* -4 */ + {0xD21F, 0xD31F, 0x0000}, /* -2 */ + {0xC208, 0xC308, 0x0001}, /* 0 (HW default) */ + {0xC219, 0xC319, 0x0001}, /* +2 */ + {0xC22A, 0xC32A, 0x0001}, /* +4 */ + {0xC24C, 0xC34C, 0x0001}, /* +6 */ + {0xC26E, 0xC36E, 0x0001}, /* +8 */ + {0xC248, 0xC384, 0x0002}, /* +10 */ + {0xC26A, 0xC36A, 0x0002}, /* +12 dB */ +}; + +static unsigned short treble_parm[12][9] = { + {0x821E, 0xC26A, 0x031E, 0xC36A, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, /* -12 dB */ + {0x821E, 0xC25B, 0x031E, 0xC35B, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC24C, 0x031E, 0xC34C, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC23D, 0x031E, 0xC33D, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC21F, 0x031E, 0xC31F, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021E, 0xD208, 0x831E, 0xD308, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021D, 0xD219, 0x831D, 0xD319, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021C, 0xD22A, 0x831C, 0xD32A, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021A, 0xD24C, 0x831A, 0xD34C, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +8 (HW default) */ + {0x821D, 0xD219, 0x031D, 0xD319, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, + {0x821C, 0xD22A, 0x031C, 0xD32A, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002} /* +12 dB */ +}; + + +/* + * set Emu8000 digital equalizer; from 0 to 11 [-12dB - 12dB] + */ +/*exported*/ void +snd_emu8000_update_equalizer(struct snd_emu8000 *emu) +{ + unsigned short w; + int bass = emu->bass_level; + int treble = emu->treble_level; + + if (bass < 0 || bass > 11 || treble < 0 || treble > 11) + return; + EMU8000_INIT4_WRITE(emu, 0x01, bass_parm[bass][0]); + EMU8000_INIT4_WRITE(emu, 0x11, bass_parm[bass][1]); + EMU8000_INIT3_WRITE(emu, 0x11, treble_parm[treble][0]); + EMU8000_INIT3_WRITE(emu, 0x13, treble_parm[treble][1]); + EMU8000_INIT3_WRITE(emu, 0x1b, treble_parm[treble][2]); + EMU8000_INIT4_WRITE(emu, 0x07, treble_parm[treble][3]); + EMU8000_INIT4_WRITE(emu, 0x0b, treble_parm[treble][4]); + EMU8000_INIT4_WRITE(emu, 0x0d, treble_parm[treble][5]); + EMU8000_INIT4_WRITE(emu, 0x17, treble_parm[treble][6]); + EMU8000_INIT4_WRITE(emu, 0x19, treble_parm[treble][7]); + w = bass_parm[bass][2] + treble_parm[treble][8]; + EMU8000_INIT4_WRITE(emu, 0x15, (unsigned short)(w + 0x0262)); + EMU8000_INIT4_WRITE(emu, 0x1d, (unsigned short)(w + 0x8362)); +} + + +/*---------------------------------------------------------------- + * Chorus mode control + *----------------------------------------------------------------*/ + +/* + * chorus mode parameters + */ +#define SNDRV_EMU8000_CHORUS_1 0 +#define SNDRV_EMU8000_CHORUS_2 1 +#define SNDRV_EMU8000_CHORUS_3 2 +#define SNDRV_EMU8000_CHORUS_4 3 +#define SNDRV_EMU8000_CHORUS_FEEDBACK 4 +#define SNDRV_EMU8000_CHORUS_FLANGER 5 +#define SNDRV_EMU8000_CHORUS_SHORTDELAY 6 +#define SNDRV_EMU8000_CHORUS_SHORTDELAY2 7 +#define SNDRV_EMU8000_CHORUS_PREDEFINED 8 +/* user can define chorus modes up to 32 */ +#define SNDRV_EMU8000_CHORUS_NUMBERS 32 + +struct soundfont_chorus_fx { + unsigned short feedback; /* feedback level (0xE600-0xE6FF) */ + unsigned short delay_offset; /* delay (0-0x0DA3) [1/44100 sec] */ + unsigned short lfo_depth; /* LFO depth (0xBC00-0xBCFF) */ + unsigned int delay; /* right delay (0-0xFFFFFFFF) [1/256/44100 sec] */ + unsigned int lfo_freq; /* LFO freq LFO freq (0-0xFFFFFFFF) */ +}; + +/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */ +static char chorus_defined[SNDRV_EMU8000_CHORUS_NUMBERS]; +static struct soundfont_chorus_fx chorus_parm[SNDRV_EMU8000_CHORUS_NUMBERS] = { + {0xE600, 0x03F6, 0xBC2C ,0x00000000, 0x0000006D}, /* chorus 1 */ + {0xE608, 0x031A, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 2 */ + {0xE610, 0x031A, 0xBC84, 0x00000000, 0x00000083}, /* chorus 3 */ + {0xE620, 0x0269, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 4 */ + {0xE680, 0x04D3, 0xBCA6, 0x00000000, 0x0000005B}, /* feedback */ + {0xE6E0, 0x044E, 0xBC37, 0x00000000, 0x00000026}, /* flanger */ + {0xE600, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay */ + {0xE6C0, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay + feedback */ +}; + +/*exported*/ int +snd_emu8000_load_chorus_fx(struct snd_emu8000 *emu, int mode, const void __user *buf, long len) +{ + struct soundfont_chorus_fx rec; + if (mode < SNDRV_EMU8000_CHORUS_PREDEFINED || mode >= SNDRV_EMU8000_CHORUS_NUMBERS) { + snd_printk(KERN_WARNING "invalid chorus mode %d for uploading\n", mode); + return -EINVAL; + } + if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec))) + return -EFAULT; + chorus_parm[mode] = rec; + chorus_defined[mode] = 1; + return 0; +} + +/*exported*/ void +snd_emu8000_update_chorus_mode(struct snd_emu8000 *emu) +{ + int effect = emu->chorus_mode; + if (effect < 0 || effect >= SNDRV_EMU8000_CHORUS_NUMBERS || + (effect >= SNDRV_EMU8000_CHORUS_PREDEFINED && !chorus_defined[effect])) + return; + EMU8000_INIT3_WRITE(emu, 0x09, chorus_parm[effect].feedback); + EMU8000_INIT3_WRITE(emu, 0x0c, chorus_parm[effect].delay_offset); + EMU8000_INIT4_WRITE(emu, 0x03, chorus_parm[effect].lfo_depth); + EMU8000_HWCF4_WRITE(emu, chorus_parm[effect].delay); + EMU8000_HWCF5_WRITE(emu, chorus_parm[effect].lfo_freq); + EMU8000_HWCF6_WRITE(emu, 0x8000); + EMU8000_HWCF7_WRITE(emu, 0x0000); +} + +/*---------------------------------------------------------------- + * Reverb mode control + *----------------------------------------------------------------*/ + +/* + * reverb mode parameters + */ +#define SNDRV_EMU8000_REVERB_ROOM1 0 +#define SNDRV_EMU8000_REVERB_ROOM2 1 +#define SNDRV_EMU8000_REVERB_ROOM3 2 +#define SNDRV_EMU8000_REVERB_HALL1 3 +#define SNDRV_EMU8000_REVERB_HALL2 4 +#define SNDRV_EMU8000_REVERB_PLATE 5 +#define SNDRV_EMU8000_REVERB_DELAY 6 +#define SNDRV_EMU8000_REVERB_PANNINGDELAY 7 +#define SNDRV_EMU8000_REVERB_PREDEFINED 8 +/* user can define reverb modes up to 32 */ +#define SNDRV_EMU8000_REVERB_NUMBERS 32 + +struct soundfont_reverb_fx { + unsigned short parms[28]; +}; + +/* reverb mode settings; write the following 28 data of 16 bit length + * on the corresponding ports in the reverb_cmds array + */ +static char reverb_defined[SNDRV_EMU8000_CHORUS_NUMBERS]; +static struct soundfont_reverb_fx reverb_parm[SNDRV_EMU8000_REVERB_NUMBERS] = { +{{ /* room 1 */ + 0xB488, 0xA450, 0x9550, 0x84B5, 0x383A, 0x3EB5, 0x72F4, + 0x72A4, 0x7254, 0x7204, 0x7204, 0x7204, 0x4416, 0x4516, + 0xA490, 0xA590, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* room 2 */ + 0xB488, 0xA458, 0x9558, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* room 3 */ + 0xB488, 0xA460, 0x9560, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4416, 0x4516, + 0xA490, 0xA590, 0x842C, 0x852C, 0x842C, 0x852C, 0x842B, + 0x852B, 0x842B, 0x852B, 0x842A, 0x852A, 0x842A, 0x852A, +}}, +{{ /* hall 1 */ + 0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842B, 0x852B, 0x842B, 0x852B, 0x842A, + 0x852A, 0x842A, 0x852A, 0x8429, 0x8529, 0x8429, 0x8529, +}}, +{{ /* hall 2 */ + 0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7254, + 0x7234, 0x7224, 0x7254, 0x7264, 0x7294, 0x44C3, 0x45C3, + 0xA404, 0xA504, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* plate */ + 0xB4FF, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7234, + 0x7234, 0x7234, 0x7234, 0x7234, 0x7234, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* delay */ + 0xB4FF, 0xA470, 0x9500, 0x84B5, 0x333A, 0x39B5, 0x7204, + 0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500, + 0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, + 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, +}}, +{{ /* panning delay */ + 0xB4FF, 0xA490, 0x9590, 0x8474, 0x333A, 0x39B5, 0x7204, + 0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500, + 0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, + 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, +}}, +}; + +enum { DATA1, DATA2 }; +#define AWE_INIT1(c) EMU8000_CMD(2,c), DATA1 +#define AWE_INIT2(c) EMU8000_CMD(2,c), DATA2 +#define AWE_INIT3(c) EMU8000_CMD(3,c), DATA1 +#define AWE_INIT4(c) EMU8000_CMD(3,c), DATA2 + +static struct reverb_cmd_pair { + unsigned short cmd, port; +} reverb_cmds[28] = { + {AWE_INIT1(0x03)}, {AWE_INIT1(0x05)}, {AWE_INIT4(0x1F)}, {AWE_INIT1(0x07)}, + {AWE_INIT2(0x14)}, {AWE_INIT2(0x16)}, {AWE_INIT1(0x0F)}, {AWE_INIT1(0x17)}, + {AWE_INIT1(0x1F)}, {AWE_INIT2(0x07)}, {AWE_INIT2(0x0F)}, {AWE_INIT2(0x17)}, + {AWE_INIT2(0x1D)}, {AWE_INIT2(0x1F)}, {AWE_INIT3(0x01)}, {AWE_INIT3(0x03)}, + {AWE_INIT1(0x09)}, {AWE_INIT1(0x0B)}, {AWE_INIT1(0x11)}, {AWE_INIT1(0x13)}, + {AWE_INIT1(0x19)}, {AWE_INIT1(0x1B)}, {AWE_INIT2(0x01)}, {AWE_INIT2(0x03)}, + {AWE_INIT2(0x09)}, {AWE_INIT2(0x0B)}, {AWE_INIT2(0x11)}, {AWE_INIT2(0x13)}, +}; + +/*exported*/ int +snd_emu8000_load_reverb_fx(struct snd_emu8000 *emu, int mode, const void __user *buf, long len) +{ + struct soundfont_reverb_fx rec; + + if (mode < SNDRV_EMU8000_REVERB_PREDEFINED || mode >= SNDRV_EMU8000_REVERB_NUMBERS) { + snd_printk(KERN_WARNING "invalid reverb mode %d for uploading\n", mode); + return -EINVAL; + } + if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec))) + return -EFAULT; + reverb_parm[mode] = rec; + reverb_defined[mode] = 1; + return 0; +} + +/*exported*/ void +snd_emu8000_update_reverb_mode(struct snd_emu8000 *emu) +{ + int effect = emu->reverb_mode; + int i; + + if (effect < 0 || effect >= SNDRV_EMU8000_REVERB_NUMBERS || + (effect >= SNDRV_EMU8000_REVERB_PREDEFINED && !reverb_defined[effect])) + return; + for (i = 0; i < 28; i++) { + int port; + if (reverb_cmds[i].port == DATA1) + port = EMU8000_DATA1(emu); + else + port = EMU8000_DATA2(emu); + snd_emu8000_poke(emu, port, reverb_cmds[i].cmd, reverb_parm[effect].parms[i]); + } +} + + +/*---------------------------------------------------------------- + * mixer interface + *----------------------------------------------------------------*/ + +/* + * bass/treble + */ +static int mixer_bass_treble_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 11; + return 0; +} + +static int mixer_bass_treble_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->treble_level : emu->bass_level; + return 0; +} + +static int mixer_bass_treble_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1; + + val1 = ucontrol->value.integer.value[0] % 12; + spin_lock_irqsave(&emu->control_lock, flags); + if (kcontrol->private_value) { + change = val1 != emu->treble_level; + emu->treble_level = val1; + } else { + change = val1 != emu->bass_level; + emu->bass_level = val1; + } + spin_unlock_irqrestore(&emu->control_lock, flags); + snd_emu8000_update_equalizer(emu); + return change; +} + +static struct snd_kcontrol_new mixer_bass_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Synth Tone Control - Bass", + .info = mixer_bass_treble_info, + .get = mixer_bass_treble_get, + .put = mixer_bass_treble_put, + .private_value = 0, +}; + +static struct snd_kcontrol_new mixer_treble_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Synth Tone Control - Treble", + .info = mixer_bass_treble_info, + .get = mixer_bass_treble_get, + .put = mixer_bass_treble_put, + .private_value = 1, +}; + +/* + * chorus/reverb mode + */ +static int mixer_chorus_reverb_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = kcontrol->private_value ? (SNDRV_EMU8000_CHORUS_NUMBERS-1) : (SNDRV_EMU8000_REVERB_NUMBERS-1); + return 0; +} + +static int mixer_chorus_reverb_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->chorus_mode : emu->reverb_mode; + return 0; +} + +static int mixer_chorus_reverb_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1; + + spin_lock_irqsave(&emu->control_lock, flags); + if (kcontrol->private_value) { + val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_CHORUS_NUMBERS; + change = val1 != emu->chorus_mode; + emu->chorus_mode = val1; + } else { + val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_REVERB_NUMBERS; + change = val1 != emu->reverb_mode; + emu->reverb_mode = val1; + } + spin_unlock_irqrestore(&emu->control_lock, flags); + if (change) { + if (kcontrol->private_value) + snd_emu8000_update_chorus_mode(emu); + else + snd_emu8000_update_reverb_mode(emu); + } + return change; +} + +static struct snd_kcontrol_new mixer_chorus_mode_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Chorus Mode", + .info = mixer_chorus_reverb_info, + .get = mixer_chorus_reverb_get, + .put = mixer_chorus_reverb_put, + .private_value = 1, +}; + +static struct snd_kcontrol_new mixer_reverb_mode_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Reverb Mode", + .info = mixer_chorus_reverb_info, + .get = mixer_chorus_reverb_get, + .put = mixer_chorus_reverb_put, + .private_value = 0, +}; + +/* + * FM OPL3 chorus/reverb depth + */ +static int mixer_fm_depth_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +static int mixer_fm_depth_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->fm_chorus_depth : emu->fm_reverb_depth; + return 0; +} + +static int mixer_fm_depth_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu8000 *emu = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1; + + val1 = ucontrol->value.integer.value[0] % 256; + spin_lock_irqsave(&emu->control_lock, flags); + if (kcontrol->private_value) { + change = val1 != emu->fm_chorus_depth; + emu->fm_chorus_depth = val1; + } else { + change = val1 != emu->fm_reverb_depth; + emu->fm_reverb_depth = val1; + } + spin_unlock_irqrestore(&emu->control_lock, flags); + if (change) + snd_emu8000_init_fm(emu); + return change; +} + +static struct snd_kcontrol_new mixer_fm_chorus_depth_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "FM Chorus Depth", + .info = mixer_fm_depth_info, + .get = mixer_fm_depth_get, + .put = mixer_fm_depth_put, + .private_value = 1, +}; + +static struct snd_kcontrol_new mixer_fm_reverb_depth_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "FM Reverb Depth", + .info = mixer_fm_depth_info, + .get = mixer_fm_depth_get, + .put = mixer_fm_depth_put, + .private_value = 0, +}; + + +static struct snd_kcontrol_new *mixer_defs[EMU8000_NUM_CONTROLS] = { + &mixer_bass_control, + &mixer_treble_control, + &mixer_chorus_mode_control, + &mixer_reverb_mode_control, + &mixer_fm_chorus_depth_control, + &mixer_fm_reverb_depth_control, +}; + +/* + * create and attach mixer elements for WaveTable treble/bass controls + */ +static int +snd_emu8000_create_mixer(struct snd_card *card, struct snd_emu8000 *emu) +{ + int i, err = 0; + + if (snd_BUG_ON(!emu || !card)) + return -EINVAL; + + spin_lock_init(&emu->control_lock); + + memset(emu->controls, 0, sizeof(emu->controls)); + for (i = 0; i < EMU8000_NUM_CONTROLS; i++) { + if ((err = snd_ctl_add(card, emu->controls[i] = snd_ctl_new1(mixer_defs[i], emu))) < 0) { + emu->controls[i] = NULL; + goto __error; + } + } + return 0; + +__error: + for (i = 0; i < EMU8000_NUM_CONTROLS; i++) { + down_write(&card->controls_rwsem); + if (emu->controls[i]) + snd_ctl_remove(card, emu->controls[i]); + up_write(&card->controls_rwsem); + } + return err; +} + + +/* + * free resources + */ +static int snd_emu8000_free(struct snd_emu8000 *hw) +{ + release_and_free_resource(hw->res_port1); + release_and_free_resource(hw->res_port2); + release_and_free_resource(hw->res_port3); + kfree(hw); + return 0; +} + +/* + */ +static int snd_emu8000_dev_free(struct snd_device *device) +{ + struct snd_emu8000 *hw = device->device_data; + return snd_emu8000_free(hw); +} + +/* + * initialize and register emu8000 synth device. + */ +int +snd_emu8000_new(struct snd_card *card, int index, long port, int seq_ports, + struct snd_seq_device **awe_ret) +{ + struct snd_seq_device *awe; + struct snd_emu8000 *hw; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_emu8000_dev_free, + }; + + if (awe_ret) + *awe_ret = NULL; + + if (seq_ports <= 0) + return 0; + + hw = kzalloc(sizeof(*hw), GFP_KERNEL); + if (hw == NULL) + return -ENOMEM; + spin_lock_init(&hw->reg_lock); + hw->index = index; + hw->port1 = port; + hw->port2 = port + 0x400; + hw->port3 = port + 0x800; + if (!(hw->res_port1 = request_region(hw->port1, 4, "Emu8000-1")) || + !(hw->res_port2 = request_region(hw->port2, 4, "Emu8000-2")) || + !(hw->res_port3 = request_region(hw->port3, 4, "Emu8000-3"))) { + snd_printk(KERN_ERR "sbawe: can't grab ports 0x%lx, 0x%lx, 0x%lx\n", hw->port1, hw->port2, hw->port3); + snd_emu8000_free(hw); + return -EBUSY; + } + hw->mem_size = 0; + hw->card = card; + hw->seq_ports = seq_ports; + hw->bass_level = 5; + hw->treble_level = 9; + hw->chorus_mode = 2; + hw->reverb_mode = 4; + hw->fm_chorus_depth = 0; + hw->fm_reverb_depth = 0; + + if (snd_emu8000_detect(hw) < 0) { + snd_emu8000_free(hw); + return -ENODEV; + } + + snd_emu8000_init_hw(hw); + if ((err = snd_emu8000_create_mixer(card, hw)) < 0) { + snd_emu8000_free(hw); + return err; + } + + if ((err = snd_device_new(card, SNDRV_DEV_CODEC, hw, &ops)) < 0) { + snd_emu8000_free(hw); + return err; + } +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (snd_seq_device_new(card, index, SNDRV_SEQ_DEV_ID_EMU8000, + sizeof(struct snd_emu8000*), &awe) >= 0) { + strcpy(awe->name, "EMU-8000"); + *(struct snd_emu8000 **)SNDRV_SEQ_DEVICE_ARGPTR(awe) = hw; + } +#else + awe = NULL; +#endif + if (awe_ret) + *awe_ret = awe; + + return 0; +} + + +/* + * exported stuff + */ + +EXPORT_SYMBOL(snd_emu8000_poke); +EXPORT_SYMBOL(snd_emu8000_peek); +EXPORT_SYMBOL(snd_emu8000_poke_dw); +EXPORT_SYMBOL(snd_emu8000_peek_dw); +EXPORT_SYMBOL(snd_emu8000_dma_chan); +EXPORT_SYMBOL(snd_emu8000_init_fm); +EXPORT_SYMBOL(snd_emu8000_load_chorus_fx); +EXPORT_SYMBOL(snd_emu8000_load_reverb_fx); +EXPORT_SYMBOL(snd_emu8000_update_chorus_mode); +EXPORT_SYMBOL(snd_emu8000_update_reverb_mode); +EXPORT_SYMBOL(snd_emu8000_update_equalizer); diff --git a/sound/isa/sb/emu8000_callback.c b/sound/isa/sb/emu8000_callback.c new file mode 100644 index 000000000..5a485504f --- /dev/null +++ b/sound/isa/sb/emu8000_callback.c @@ -0,0 +1,547 @@ +/* + * synth callback routines for the emu8000 (AWE32/64) + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "emu8000_local.h" +#include +#include + +/* + * prototypes + */ +static struct snd_emux_voice *get_voice(struct snd_emux *emu, + struct snd_emux_port *port); +static int start_voice(struct snd_emux_voice *vp); +static void trigger_voice(struct snd_emux_voice *vp); +static void release_voice(struct snd_emux_voice *vp); +static void update_voice(struct snd_emux_voice *vp, int update); +static void reset_voice(struct snd_emux *emu, int ch); +static void terminate_voice(struct snd_emux_voice *vp); +static void sysex(struct snd_emux *emu, char *buf, int len, int parsed, + struct snd_midi_channel_set *chset); +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) +static int oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2); +#endif +static int load_fx(struct snd_emux *emu, int type, int mode, + const void __user *buf, long len); + +static void set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp); +static void set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp); +static void set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp); +static void set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp); +static void set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp); +static void set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp); +static void set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp); +static void snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int ch); + +/* + * Ensure a value is between two points + * macro evaluates its args more than once, so changed to upper-case. + */ +#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0) +#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0) + + +/* + * set up operators + */ +static const struct snd_emux_operators emu8000_ops = { + .owner = THIS_MODULE, + .get_voice = get_voice, + .prepare = start_voice, + .trigger = trigger_voice, + .release = release_voice, + .update = update_voice, + .terminate = terminate_voice, + .reset = reset_voice, + .sample_new = snd_emu8000_sample_new, + .sample_free = snd_emu8000_sample_free, + .sample_reset = snd_emu8000_sample_reset, + .load_fx = load_fx, + .sysex = sysex, +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + .oss_ioctl = oss_ioctl, +#endif +}; + +void +snd_emu8000_ops_setup(struct snd_emu8000 *hw) +{ + hw->emu->ops = emu8000_ops; +} + + + +/* + * Terminate a voice + */ +static void +release_voice(struct snd_emux_voice *vp) +{ + int dcysusv; + struct snd_emu8000 *hw; + + hw = vp->hw; + dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease; + EMU8000_DCYSUS_WRITE(hw, vp->ch, dcysusv); + dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease; + EMU8000_DCYSUSV_WRITE(hw, vp->ch, dcysusv); +} + + +/* + */ +static void +terminate_voice(struct snd_emux_voice *vp) +{ + struct snd_emu8000 *hw; + + hw = vp->hw; + EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F); +} + + +/* + */ +static void +update_voice(struct snd_emux_voice *vp, int update) +{ + struct snd_emu8000 *hw; + + hw = vp->hw; + if (update & SNDRV_EMUX_UPDATE_VOLUME) + set_volume(hw, vp); + if (update & SNDRV_EMUX_UPDATE_PITCH) + set_pitch(hw, vp); + if ((update & SNDRV_EMUX_UPDATE_PAN) && + vp->port->ctrls[EMUX_MD_REALTIME_PAN]) + set_pan(hw, vp); + if (update & SNDRV_EMUX_UPDATE_FMMOD) + set_fmmod(hw, vp); + if (update & SNDRV_EMUX_UPDATE_TREMFREQ) + set_tremfreq(hw, vp); + if (update & SNDRV_EMUX_UPDATE_FM2FRQ2) + set_fm2frq2(hw, vp); + if (update & SNDRV_EMUX_UPDATE_Q) + set_filterQ(hw, vp); +} + + +/* + * Find a channel (voice) within the EMU that is not in use or at least + * less in use than other channels. Always returns a valid pointer + * no matter what. If there is a real shortage of voices then one + * will be cut. Such is life. + * + * The channel index (vp->ch) must be initialized in this routine. + * In Emu8k, it is identical with the array index. + */ +static struct snd_emux_voice * +get_voice(struct snd_emux *emu, struct snd_emux_port *port) +{ + int i; + struct snd_emux_voice *vp; + struct snd_emu8000 *hw; + + /* what we are looking for, in order of preference */ + enum { + OFF=0, RELEASED, PLAYING, END + }; + + /* Keeps track of what we are finding */ + struct best { + unsigned int time; + int voice; + } best[END]; + struct best *bp; + + hw = emu->hw; + + for (i = 0; i < END; i++) { + best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */ + best[i].voice = -1; + } + + /* + * Go through them all and get a best one to use. + */ + for (i = 0; i < emu->max_voices; i++) { + int state, val; + + vp = &emu->voices[i]; + state = vp->state; + + if (state == SNDRV_EMUX_ST_OFF) + bp = best + OFF; + else if (state == SNDRV_EMUX_ST_RELEASED || + state == SNDRV_EMUX_ST_PENDING) { + bp = best + RELEASED; + val = (EMU8000_CVCF_READ(hw, vp->ch) >> 16) & 0xffff; + if (! val) + bp = best + OFF; + } + else if (state & SNDRV_EMUX_ST_ON) + bp = best + PLAYING; + else + continue; + + /* check if sample is finished playing (non-looping only) */ + if (state != SNDRV_EMUX_ST_OFF && + (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) { + val = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff; + if (val >= vp->reg.loopstart) + bp = best + OFF; + } + + if (vp->time < bp->time) { + bp->time = vp->time; + bp->voice = i; + } + } + + for (i = 0; i < END; i++) { + if (best[i].voice >= 0) { + vp = &emu->voices[best[i].voice]; + vp->ch = best[i].voice; + return vp; + } + } + + /* not found */ + return NULL; +} + +/* + */ +static int +start_voice(struct snd_emux_voice *vp) +{ + unsigned int temp; + int ch; + int addr; + struct snd_midi_channel *chan; + struct snd_emu8000 *hw; + + hw = vp->hw; + ch = vp->ch; + chan = vp->chan; + + /* channel to be silent and idle */ + EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); + EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); + EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); + EMU8000_PTRX_WRITE(hw, ch, 0); + EMU8000_CPF_WRITE(hw, ch, 0); + + /* set pitch offset */ + set_pitch(hw, vp); + + /* set envelope parameters */ + EMU8000_ENVVAL_WRITE(hw, ch, vp->reg.parm.moddelay); + EMU8000_ATKHLD_WRITE(hw, ch, vp->reg.parm.modatkhld); + EMU8000_DCYSUS_WRITE(hw, ch, vp->reg.parm.moddcysus); + EMU8000_ENVVOL_WRITE(hw, ch, vp->reg.parm.voldelay); + EMU8000_ATKHLDV_WRITE(hw, ch, vp->reg.parm.volatkhld); + /* decay/sustain parameter for volume envelope is used + for triggerg the voice */ + + /* cutoff and volume */ + set_volume(hw, vp); + + /* modulation envelope heights */ + EMU8000_PEFE_WRITE(hw, ch, vp->reg.parm.pefe); + + /* lfo1/2 delay */ + EMU8000_LFO1VAL_WRITE(hw, ch, vp->reg.parm.lfo1delay); + EMU8000_LFO2VAL_WRITE(hw, ch, vp->reg.parm.lfo2delay); + + /* lfo1 pitch & cutoff shift */ + set_fmmod(hw, vp); + /* lfo1 volume & freq */ + set_tremfreq(hw, vp); + /* lfo2 pitch & freq */ + set_fm2frq2(hw, vp); + /* pan & loop start */ + set_pan(hw, vp); + + /* chorus & loop end (chorus 8bit, MSB) */ + addr = vp->reg.loopend - 1; + temp = vp->reg.parm.chorus; + temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10; + LIMITMAX(temp, 255); + temp = (temp <<24) | (unsigned int)addr; + EMU8000_CSL_WRITE(hw, ch, temp); + + /* Q & current address (Q 4bit value, MSB) */ + addr = vp->reg.start - 1; + temp = vp->reg.parm.filterQ; + temp = (temp<<28) | (unsigned int)addr; + EMU8000_CCCA_WRITE(hw, ch, temp); + + /* clear unknown registers */ + EMU8000_00A0_WRITE(hw, ch, 0); + EMU8000_0080_WRITE(hw, ch, 0); + + /* reset volume */ + temp = vp->vtarget << 16; + EMU8000_VTFT_WRITE(hw, ch, temp | vp->ftarget); + EMU8000_CVCF_WRITE(hw, ch, temp | 0xff00); + + return 0; +} + +/* + * Start envelope + */ +static void +trigger_voice(struct snd_emux_voice *vp) +{ + int ch = vp->ch; + unsigned int temp; + struct snd_emu8000 *hw; + + hw = vp->hw; + + /* set reverb and pitch target */ + temp = vp->reg.parm.reverb; + temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10; + LIMITMAX(temp, 255); + temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux; + EMU8000_PTRX_WRITE(hw, ch, temp); + EMU8000_CPF_WRITE(hw, ch, vp->ptarget << 16); + EMU8000_DCYSUSV_WRITE(hw, ch, vp->reg.parm.voldcysus); +} + +/* + * reset voice parameters + */ +static void +reset_voice(struct snd_emux *emu, int ch) +{ + struct snd_emu8000 *hw; + + hw = emu->hw; + EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); + snd_emu8000_tweak_voice(hw, ch); +} + +/* + * Set the pitch of a possibly playing note. + */ +static void +set_pitch(struct snd_emu8000 *hw, struct snd_emux_voice *vp) +{ + EMU8000_IP_WRITE(hw, vp->ch, vp->apitch); +} + +/* + * Set the volume of a possibly already playing note + */ +static void +set_volume(struct snd_emu8000 *hw, struct snd_emux_voice *vp) +{ + int ifatn; + + ifatn = (unsigned char)vp->acutoff; + ifatn = (ifatn << 8); + ifatn |= (unsigned char)vp->avol; + EMU8000_IFATN_WRITE(hw, vp->ch, ifatn); +} + +/* + * Set pan and loop start address. + */ +static void +set_pan(struct snd_emu8000 *hw, struct snd_emux_voice *vp) +{ + unsigned int temp; + + temp = ((unsigned int)vp->apan<<24) | ((unsigned int)vp->reg.loopstart - 1); + EMU8000_PSST_WRITE(hw, vp->ch, temp); +} + +#define MOD_SENSE 18 + +static void +set_fmmod(struct snd_emu8000 *hw, struct snd_emux_voice *vp) +{ + unsigned short fmmod; + short pitch; + unsigned char cutoff; + int modulation; + + pitch = (char)(vp->reg.parm.fmmod>>8); + cutoff = (vp->reg.parm.fmmod & 0xff); + modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; + pitch += (MOD_SENSE * modulation) / 1200; + LIMITVALUE(pitch, -128, 127); + fmmod = ((unsigned char)pitch<<8) | cutoff; + EMU8000_FMMOD_WRITE(hw, vp->ch, fmmod); +} + +/* set tremolo (lfo1) volume & frequency */ +static void +set_tremfreq(struct snd_emu8000 *hw, struct snd_emux_voice *vp) +{ + EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq); +} + +/* set lfo2 pitch & frequency */ +static void +set_fm2frq2(struct snd_emu8000 *hw, struct snd_emux_voice *vp) +{ + unsigned short fm2frq2; + short pitch; + unsigned char freq; + int modulation; + + pitch = (char)(vp->reg.parm.fm2frq2>>8); + freq = vp->reg.parm.fm2frq2 & 0xff; + modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; + pitch += (MOD_SENSE * modulation) / 1200; + LIMITVALUE(pitch, -128, 127); + fm2frq2 = ((unsigned char)pitch<<8) | freq; + EMU8000_FM2FRQ2_WRITE(hw, vp->ch, fm2frq2); +} + +/* set filterQ */ +static void +set_filterQ(struct snd_emu8000 *hw, struct snd_emux_voice *vp) +{ + unsigned int addr; + addr = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff; + addr |= (vp->reg.parm.filterQ << 28); + EMU8000_CCCA_WRITE(hw, vp->ch, addr); +} + +/* + * set the envelope & LFO parameters to the default values + */ +static void +snd_emu8000_tweak_voice(struct snd_emu8000 *emu, int i) +{ + /* set all mod/vol envelope shape to minimum */ + EMU8000_ENVVOL_WRITE(emu, i, 0x8000); + EMU8000_ENVVAL_WRITE(emu, i, 0x8000); + EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F); + EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F); + EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F); + EMU8000_PEFE_WRITE(emu, i, 0); /* mod envelope height to zero */ + EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */ + EMU8000_LFO2VAL_WRITE(emu, i, 0x8000); + EMU8000_IP_WRITE(emu, i, 0xE000); /* no pitch shift */ + EMU8000_IFATN_WRITE(emu, i, 0xFF00); /* volume to minimum */ + EMU8000_FMMOD_WRITE(emu, i, 0); + EMU8000_TREMFRQ_WRITE(emu, i, 0); + EMU8000_FM2FRQ2_WRITE(emu, i, 0); +} + +/* + * sysex callback + */ +static void +sysex(struct snd_emux *emu, char *buf, int len, int parsed, struct snd_midi_channel_set *chset) +{ + struct snd_emu8000 *hw; + + hw = emu->hw; + + switch (parsed) { + case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE: + hw->chorus_mode = chset->gs_chorus_mode; + snd_emu8000_update_chorus_mode(hw); + break; + + case SNDRV_MIDI_SYSEX_GS_REVERB_MODE: + hw->reverb_mode = chset->gs_reverb_mode; + snd_emu8000_update_reverb_mode(hw); + break; + } +} + + +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) +/* + * OSS ioctl callback + */ +static int +oss_ioctl(struct snd_emux *emu, int cmd, int p1, int p2) +{ + struct snd_emu8000 *hw; + + hw = emu->hw; + + switch (cmd) { + case _EMUX_OSS_REVERB_MODE: + hw->reverb_mode = p1; + snd_emu8000_update_reverb_mode(hw); + break; + + case _EMUX_OSS_CHORUS_MODE: + hw->chorus_mode = p1; + snd_emu8000_update_chorus_mode(hw); + break; + + case _EMUX_OSS_INITIALIZE_CHIP: + /* snd_emu8000_init(hw); */ /*ignored*/ + break; + + case _EMUX_OSS_EQUALIZER: + hw->bass_level = p1; + hw->treble_level = p2; + snd_emu8000_update_equalizer(hw); + break; + } + return 0; +} +#endif + + +/* + * additional patch keys + */ + +#define SNDRV_EMU8000_LOAD_CHORUS_FX 0x10 /* optarg=mode */ +#define SNDRV_EMU8000_LOAD_REVERB_FX 0x11 /* optarg=mode */ + + +/* + * callback routine + */ + +static int +load_fx(struct snd_emux *emu, int type, int mode, const void __user *buf, long len) +{ + struct snd_emu8000 *hw; + hw = emu->hw; + + /* skip header */ + buf += 16; + len -= 16; + + switch (type) { + case SNDRV_EMU8000_LOAD_CHORUS_FX: + return snd_emu8000_load_chorus_fx(hw, mode, buf, len); + case SNDRV_EMU8000_LOAD_REVERB_FX: + return snd_emu8000_load_reverb_fx(hw, mode, buf, len); + } + return -EINVAL; +} + diff --git a/sound/isa/sb/emu8000_local.h b/sound/isa/sb/emu8000_local.h new file mode 100644 index 000000000..7e87c3492 --- /dev/null +++ b/sound/isa/sb/emu8000_local.h @@ -0,0 +1,45 @@ +#ifndef __EMU8000_LOCAL_H +#define __EMU8000_LOCAL_H +/* + * Local defininitons for the emu8000 (AWE32/64) + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +/* emu8000_patch.c */ +int snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr, + const void __user *data, long count); +int snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr); +void snd_emu8000_sample_reset(struct snd_emux *rec); + +/* emu8000_callback.c */ +void snd_emu8000_ops_setup(struct snd_emu8000 *emu); + +/* emu8000_pcm.c */ +int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index); + +#endif /* __EMU8000_LOCAL_H */ diff --git a/sound/isa/sb/emu8000_patch.c b/sound/isa/sb/emu8000_patch.c new file mode 100644 index 000000000..d45a6b9d6 --- /dev/null +++ b/sound/isa/sb/emu8000_patch.c @@ -0,0 +1,304 @@ +/* + * Patch routines for the emu8000 (AWE32/64) + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "emu8000_local.h" + +#include +#include +#include + +static int emu8000_reset_addr; +module_param(emu8000_reset_addr, int, 0444); +MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)"); + + +/* + * Open up channels. + */ +static int +snd_emu8000_open_dma(struct snd_emu8000 *emu, int write) +{ + int i; + + /* reserve all 30 voices for loading */ + for (i = 0; i < EMU8000_DRAM_VOICES; i++) { + snd_emux_lock_voice(emu->emu, i); + snd_emu8000_dma_chan(emu, i, write); + } + + /* assign voice 31 and 32 to ROM */ + EMU8000_VTFT_WRITE(emu, 30, 0); + EMU8000_PSST_WRITE(emu, 30, 0x1d8); + EMU8000_CSL_WRITE(emu, 30, 0x1e0); + EMU8000_CCCA_WRITE(emu, 30, 0x1d8); + EMU8000_VTFT_WRITE(emu, 31, 0); + EMU8000_PSST_WRITE(emu, 31, 0x1d8); + EMU8000_CSL_WRITE(emu, 31, 0x1e0); + EMU8000_CCCA_WRITE(emu, 31, 0x1d8); + + return 0; +} + +/* + * Close all dram channels. + */ +static void +snd_emu8000_close_dma(struct snd_emu8000 *emu) +{ + int i; + + for (i = 0; i < EMU8000_DRAM_VOICES; i++) { + snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); + snd_emux_unlock_voice(emu->emu, i); + } +} + +/* + */ + +#define BLANK_LOOP_START 4 +#define BLANK_LOOP_END 8 +#define BLANK_LOOP_SIZE 12 +#define BLANK_HEAD_SIZE 48 + +/* + * Read a word from userland, taking care of conversions from + * 8bit samples etc. + */ +static unsigned short +read_word(const void __user *buf, int offset, int mode) +{ + unsigned short c; + if (mode & SNDRV_SFNT_SAMPLE_8BITS) { + unsigned char cc; + get_user(cc, (unsigned char __user *)buf + offset); + c = cc << 8; /* convert 8bit -> 16bit */ + } else { +#ifdef SNDRV_LITTLE_ENDIAN + get_user(c, (unsigned short __user *)buf + offset); +#else + unsigned short cc; + get_user(cc, (unsigned short __user *)buf + offset); + c = swab16(cc); +#endif + } + if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED) + c ^= 0x8000; /* unsigned -> signed */ + return c; +} + +/* + */ +static void +snd_emu8000_write_wait(struct snd_emu8000 *emu) +{ + while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { + schedule_timeout_interruptible(1); + if (signal_pending(current)) + break; + } +} + +/* + * write sample word data + * + * You should not have to keep resetting the address each time + * as the chip is supposed to step on the next address automatically. + * It mostly does, but during writes of some samples at random it + * completely loses words (every one in 16 roughly but with no + * obvious pattern). + * + * This is therefore much slower than need be, but is at least + * working. + */ +static inline void +write_word(struct snd_emu8000 *emu, int *offset, unsigned short data) +{ + if (emu8000_reset_addr) { + if (emu8000_reset_addr > 1) + snd_emu8000_write_wait(emu); + EMU8000_SMALW_WRITE(emu, *offset); + } + EMU8000_SMLD_WRITE(emu, data); + *offset += 1; +} + +/* + * Write the sample to EMU800 memory. This routine is invoked out of + * the generic soundfont routines as a callback. + */ +int +snd_emu8000_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr, + const void __user *data, long count) +{ + int i; + int rc; + int offset; + int truesize; + int dram_offset, dram_start; + struct snd_emu8000 *emu; + + emu = rec->hw; + if (snd_BUG_ON(!sp)) + return -EINVAL; + + if (sp->v.size == 0) + return 0; + + /* be sure loop points start < end */ + if (sp->v.loopstart > sp->v.loopend) + swap(sp->v.loopstart, sp->v.loopend); + + /* compute true data size to be loaded */ + truesize = sp->v.size; + if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) + truesize += sp->v.loopend - sp->v.loopstart; + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) + truesize += BLANK_LOOP_SIZE; + + sp->block = snd_util_mem_alloc(hdr, truesize * 2); + if (sp->block == NULL) { + /*snd_printd("EMU8000: out of memory\n");*/ + /* not ENOMEM (for compatibility) */ + return -ENOSPC; + } + + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) { + if (!access_ok(VERIFY_READ, data, sp->v.size)) + return -EFAULT; + } else { + if (!access_ok(VERIFY_READ, data, sp->v.size * 2)) + return -EFAULT; + } + + /* recalculate address offset */ + sp->v.end -= sp->v.start; + sp->v.loopstart -= sp->v.start; + sp->v.loopend -= sp->v.start; + sp->v.start = 0; + + /* dram position (in word) -- mem_offset is byte */ + dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1); + dram_start = dram_offset; + + /* set the total size (store onto obsolete checksum value) */ + sp->v.truesize = truesize * 2; /* in bytes */ + + snd_emux_terminate_all(emu->emu); + if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0) + return rc; + + /* Set the address to start writing at */ + snd_emu8000_write_wait(emu); + EMU8000_SMALW_WRITE(emu, dram_offset); + + /*snd_emu8000_init_fm(emu);*/ + +#if 0 + /* first block - write 48 samples for silence */ + if (! sp->block->offset) { + for (i = 0; i < BLANK_HEAD_SIZE; i++) { + write_word(emu, &dram_offset, 0); + } + } +#endif + + offset = 0; + for (i = 0; i < sp->v.size; i++) { + unsigned short s; + + s = read_word(data, offset, sp->v.mode_flags); + offset++; + write_word(emu, &dram_offset, s); + + /* we may take too long time in this loop. + * so give controls back to kernel if needed. + */ + cond_resched(); + + if (i == sp->v.loopend && + (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))) + { + int looplen = sp->v.loopend - sp->v.loopstart; + int k; + + /* copy reverse loop */ + for (k = 1; k <= looplen; k++) { + s = read_word(data, offset - k, sp->v.mode_flags); + write_word(emu, &dram_offset, s); + } + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) { + sp->v.loopend += looplen; + } else { + sp->v.loopstart += looplen; + sp->v.loopend += looplen; + } + sp->v.end += looplen; + } + } + + /* if no blank loop is attached in the sample, add it */ + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) { + for (i = 0; i < BLANK_LOOP_SIZE; i++) { + write_word(emu, &dram_offset, 0); + } + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) { + sp->v.loopstart = sp->v.end + BLANK_LOOP_START; + sp->v.loopend = sp->v.end + BLANK_LOOP_END; + } + } + + /* add dram offset */ + sp->v.start += dram_start; + sp->v.end += dram_start; + sp->v.loopstart += dram_start; + sp->v.loopend += dram_start; + + snd_emu8000_close_dma(emu); + snd_emu8000_init_fm(emu); + + return 0; +} + +/* + * free a sample block + */ +int +snd_emu8000_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr) +{ + if (sp->block) { + snd_util_mem_free(hdr, sp->block); + sp->block = NULL; + } + return 0; +} + + +/* + * sample_reset callback - terminate voices + */ +void +snd_emu8000_sample_reset(struct snd_emux *rec) +{ + snd_emux_terminate_all(rec); +} diff --git a/sound/isa/sb/emu8000_pcm.c b/sound/isa/sb/emu8000_pcm.c new file mode 100644 index 000000000..f46f6ec3e --- /dev/null +++ b/sound/isa/sb/emu8000_pcm.c @@ -0,0 +1,709 @@ +/* + * pcm emulation on emu8000 wavetable + * + * Copyright (C) 2002 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "emu8000_local.h" + +#include +#include +#include +#include +#include + +/* + * define the following if you want to use this pcm with non-interleaved mode + */ +/* #define USE_NONINTERLEAVE */ + +/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set + * mmap_emulation flag to 1 in your .asoundrc, such like + * + * pcm.emu8k { + * type plug + * slave.pcm { + * type hw + * card 0 + * device 1 + * mmap_emulation 1 + * } + * } + * + * besides, for the time being, the non-interleaved mode doesn't work well on + * alsa-lib... + */ + + +struct snd_emu8k_pcm { + struct snd_emu8000 *emu; + struct snd_pcm_substream *substream; + + unsigned int allocated_bytes; + struct snd_util_memblk *block; + unsigned int offset; + unsigned int buf_size; + unsigned int period_size; + unsigned int loop_start[2]; + unsigned int pitch; + int panning[2]; + int last_ptr; + int period_pos; + int voices; + unsigned int dram_opened: 1; + unsigned int running: 1; + unsigned int timer_running: 1; + struct timer_list timer; + spinlock_t timer_lock; +}; + +#define LOOP_BLANK_SIZE 8 + + +/* + * open up channels for the simultaneous data transfer and playback + */ +static int +emu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels) +{ + int i; + + /* reserve up to 2 voices for playback */ + snd_emux_lock_voice(emu->emu, 0); + if (channels > 1) + snd_emux_lock_voice(emu->emu, 1); + + /* reserve 28 voices for loading */ + for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) { + unsigned int mode = EMU8000_RAM_WRITE; + snd_emux_lock_voice(emu->emu, i); +#ifndef USE_NONINTERLEAVE + if (channels > 1 && (i & 1) != 0) + mode |= EMU8000_RAM_RIGHT; +#endif + snd_emu8000_dma_chan(emu, i, mode); + } + + /* assign voice 31 and 32 to ROM */ + EMU8000_VTFT_WRITE(emu, 30, 0); + EMU8000_PSST_WRITE(emu, 30, 0x1d8); + EMU8000_CSL_WRITE(emu, 30, 0x1e0); + EMU8000_CCCA_WRITE(emu, 30, 0x1d8); + EMU8000_VTFT_WRITE(emu, 31, 0); + EMU8000_PSST_WRITE(emu, 31, 0x1d8); + EMU8000_CSL_WRITE(emu, 31, 0x1e0); + EMU8000_CCCA_WRITE(emu, 31, 0x1d8); + + return 0; +} + +/* + */ +static void +snd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule) +{ + while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { + if (can_schedule) { + schedule_timeout_interruptible(1); + if (signal_pending(current)) + break; + } + } +} + +/* + * close all channels + */ +static void +emu8k_close_dram(struct snd_emu8000 *emu) +{ + int i; + + for (i = 0; i < 2; i++) + snd_emux_unlock_voice(emu->emu, i); + for (; i < EMU8000_DRAM_VOICES; i++) { + snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); + snd_emux_unlock_voice(emu->emu, i); + } +} + +/* + * convert Hz to AWE32 rate offset (see emux/soundfont.c) + */ + +#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ +#define SAMPLERATE_RATIO 4096 + +static int calc_rate_offset(int hz) +{ + return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); +} + + +/* + */ + +static const struct snd_pcm_hardware emu8k_pcm_hw = { +#ifdef USE_NONINTERLEAVE + .info = SNDRV_PCM_INFO_NONINTERLEAVED, +#else + .info = SNDRV_PCM_INFO_INTERLEAVED, +#endif + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 1024, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, + +}; + +/* + * get the current position at the given channel from CCCA register + */ +static inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch) +{ + int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff; + val -= rec->loop_start[ch] - 1; + return val; +} + + +/* + * timer interrupt handler + * check the current position and update the period if necessary. + */ +static void emu8k_pcm_timer_func(struct timer_list *t) +{ + struct snd_emu8k_pcm *rec = from_timer(rec, t, timer); + int ptr, delta; + + spin_lock(&rec->timer_lock); + /* update the current pointer */ + ptr = emu8k_get_curpos(rec, 0); + if (ptr < rec->last_ptr) + delta = ptr + rec->buf_size - rec->last_ptr; + else + delta = ptr - rec->last_ptr; + rec->period_pos += delta; + rec->last_ptr = ptr; + + /* reprogram timer */ + mod_timer(&rec->timer, jiffies + 1); + + /* update period */ + if (rec->period_pos >= (int)rec->period_size) { + rec->period_pos %= rec->period_size; + spin_unlock(&rec->timer_lock); + snd_pcm_period_elapsed(rec->substream); + return; + } + spin_unlock(&rec->timer_lock); +} + + +/* + * open pcm + * creating an instance here + */ +static int emu8k_pcm_open(struct snd_pcm_substream *subs) +{ + struct snd_emu8000 *emu = snd_pcm_substream_chip(subs); + struct snd_emu8k_pcm *rec; + struct snd_pcm_runtime *runtime = subs->runtime; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (! rec) + return -ENOMEM; + + rec->emu = emu; + rec->substream = subs; + runtime->private_data = rec; + + spin_lock_init(&rec->timer_lock); + timer_setup(&rec->timer, emu8k_pcm_timer_func, 0); + + runtime->hw = emu8k_pcm_hw; + runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3; + runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2; + + /* use timer to update periods.. (specified in msec) */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, + (1000000 + HZ - 1) / HZ, UINT_MAX); + + return 0; +} + +static int emu8k_pcm_close(struct snd_pcm_substream *subs) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + kfree(rec); + subs->runtime->private_data = NULL; + return 0; +} + +/* + * calculate pitch target + */ +static int calc_pitch_target(int pitch) +{ + int ptarget = 1 << (pitch >> 12); + if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710; + if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710; + if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710; + ptarget += (ptarget >> 1); + if (ptarget > 0xffff) ptarget = 0xffff; + return ptarget; +} + +/* + * set up the voice + */ +static void setup_voice(struct snd_emu8k_pcm *rec, int ch) +{ + struct snd_emu8000 *hw = rec->emu; + unsigned int temp; + + /* channel to be silent and idle */ + EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); + EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); + EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); + EMU8000_PTRX_WRITE(hw, ch, 0); + EMU8000_CPF_WRITE(hw, ch, 0); + + /* pitch offset */ + EMU8000_IP_WRITE(hw, ch, rec->pitch); + /* set envelope parameters */ + EMU8000_ENVVAL_WRITE(hw, ch, 0x8000); + EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f); + EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f); + EMU8000_ENVVOL_WRITE(hw, ch, 0x8000); + EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f); + /* decay/sustain parameter for volume envelope is used + for triggerg the voice */ + /* modulation envelope heights */ + EMU8000_PEFE_WRITE(hw, ch, 0x0); + /* lfo1/2 delay */ + EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000); + EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000); + /* lfo1 pitch & cutoff shift */ + EMU8000_FMMOD_WRITE(hw, ch, 0); + /* lfo1 volume & freq */ + EMU8000_TREMFRQ_WRITE(hw, ch, 0); + /* lfo2 pitch & freq */ + EMU8000_FM2FRQ2_WRITE(hw, ch, 0); + /* pan & loop start */ + temp = rec->panning[ch]; + temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1); + EMU8000_PSST_WRITE(hw, ch, temp); + /* chorus & loop end (chorus 8bit, MSB) */ + temp = 0; // chorus + temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1); + EMU8000_CSL_WRITE(hw, ch, temp); + /* Q & current address (Q 4bit value, MSB) */ + temp = 0; // filterQ + temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1); + EMU8000_CCCA_WRITE(hw, ch, temp); + /* clear unknown registers */ + EMU8000_00A0_WRITE(hw, ch, 0); + EMU8000_0080_WRITE(hw, ch, 0); +} + +/* + * trigger the voice + */ +static void start_voice(struct snd_emu8k_pcm *rec, int ch) +{ + unsigned long flags; + struct snd_emu8000 *hw = rec->emu; + unsigned int temp, aux; + int pt = calc_pitch_target(rec->pitch); + + /* cutoff and volume */ + EMU8000_IFATN_WRITE(hw, ch, 0xff00); + EMU8000_VTFT_WRITE(hw, ch, 0xffff); + EMU8000_CVCF_WRITE(hw, ch, 0xffff); + /* trigger envelope */ + EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f); + /* set reverb and pitch target */ + temp = 0; // reverb + if (rec->panning[ch] == 0) + aux = 0xff; + else + aux = (-rec->panning[ch]) & 0xff; + temp = (temp << 8) | (pt << 16) | aux; + EMU8000_PTRX_WRITE(hw, ch, temp); + EMU8000_CPF_WRITE(hw, ch, pt << 16); + + /* start timer */ + spin_lock_irqsave(&rec->timer_lock, flags); + if (! rec->timer_running) { + mod_timer(&rec->timer, jiffies + 1); + rec->timer_running = 1; + } + spin_unlock_irqrestore(&rec->timer_lock, flags); +} + +/* + * stop the voice immediately + */ +static void stop_voice(struct snd_emu8k_pcm *rec, int ch) +{ + unsigned long flags; + struct snd_emu8000 *hw = rec->emu; + + EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); + + /* stop timer */ + spin_lock_irqsave(&rec->timer_lock, flags); + if (rec->timer_running) { + del_timer(&rec->timer); + rec->timer_running = 0; + } + spin_unlock_irqrestore(&rec->timer_lock, flags); +} + +static int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + int ch; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + for (ch = 0; ch < rec->voices; ch++) + start_voice(rec, ch); + rec->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + rec->running = 0; + for (ch = 0; ch < rec->voices; ch++) + stop_voice(rec, ch); + break; + default: + return -EINVAL; + } + return 0; +} + + +/* + * copy / silence ops + */ + +/* + * this macro should be inserted in the copy/silence loops + * to reduce the latency. without this, the system will hang up + * during the whole loop. + */ +#define CHECK_SCHEDULER() \ +do { \ + cond_resched();\ + if (signal_pending(current))\ + return -EAGAIN;\ +} while (0) + +enum { + COPY_USER, COPY_KERNEL, FILL_SILENCE, +}; + +#define GET_VAL(sval, buf, mode) \ + do { \ + switch (mode) { \ + case FILL_SILENCE: \ + sval = 0; \ + break; \ + case COPY_KERNEL: \ + sval = *buf++; \ + break; \ + default: \ + if (get_user(sval, (unsigned short __user *)buf)) \ + return -EFAULT; \ + buf++; \ + break; \ + } \ + } while (0) + +#ifdef USE_NONINTERLEAVE + +#define LOOP_WRITE(rec, offset, _buf, count, mode) \ + do { \ + struct snd_emu8000 *emu = (rec)->emu; \ + unsigned short *buf = (unsigned short *)(_buf); \ + snd_emu8000_write_wait(emu, 1); \ + EMU8000_SMALW_WRITE(emu, offset); \ + while (count > 0) { \ + unsigned short sval; \ + CHECK_SCHEDULER(); \ + GET_VAL(sval, buf, mode); \ + EMU8000_SMLD_WRITE(emu, sval); \ + count--; \ + } \ + } while (0) + +/* copy one channel block */ +static int emu8k_pcm_copy(struct snd_pcm_substream *subs, + int voice, unsigned long pos, + void __user *src, unsigned long count) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + /* convert to word unit */ + pos = (pos << 1) + rec->loop_start[voice]; + count <<= 1; + LOOP_WRITE(rec, pos, src, count, COPY_USER); + return 0; +} + +static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, + int voice, unsigned long pos, + void *src, unsigned long count) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + /* convert to word unit */ + pos = (pos << 1) + rec->loop_start[voice]; + count <<= 1; + LOOP_WRITE(rec, pos, src, count, COPY_KERNEL); + return 0; +} + +/* make a channel block silence */ +static int emu8k_pcm_silence(struct snd_pcm_substream *subs, + int voice, unsigned long pos, unsigned long count) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + /* convert to word unit */ + pos = (pos << 1) + rec->loop_start[voice]; + count <<= 1; + LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); + return 0; +} + +#else /* interleave */ + +#define LOOP_WRITE(rec, pos, _buf, count, mode) \ + do { \ + struct snd_emu8000 *emu = rec->emu; \ + unsigned short *buf = (unsigned short *)(_buf); \ + snd_emu8000_write_wait(emu, 1); \ + EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]); \ + if (rec->voices > 1) \ + EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \ + while (count > 0) { \ + unsigned short sval; \ + CHECK_SCHEDULER(); \ + GET_VAL(sval, buf, mode); \ + EMU8000_SMLD_WRITE(emu, sval); \ + if (rec->voices > 1) { \ + CHECK_SCHEDULER(); \ + GET_VAL(sval, buf, mode); \ + EMU8000_SMRD_WRITE(emu, sval); \ + } \ + count--; \ + } \ + } while (0) + + +/* + * copy the interleaved data can be done easily by using + * DMA "left" and "right" channels on emu8k engine. + */ +static int emu8k_pcm_copy(struct snd_pcm_substream *subs, + int voice, unsigned long pos, + void __user *src, unsigned long count) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + /* convert to frames */ + pos = bytes_to_frames(subs->runtime, pos); + count = bytes_to_frames(subs->runtime, count); + LOOP_WRITE(rec, pos, src, count, COPY_USER); + return 0; +} + +static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, + int voice, unsigned long pos, + void *src, unsigned long count) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + /* convert to frames */ + pos = bytes_to_frames(subs->runtime, pos); + count = bytes_to_frames(subs->runtime, count); + LOOP_WRITE(rec, pos, src, count, COPY_KERNEL); + return 0; +} + +static int emu8k_pcm_silence(struct snd_pcm_substream *subs, + int voice, unsigned long pos, unsigned long count) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + /* convert to frames */ + pos = bytes_to_frames(subs->runtime, pos); + count = bytes_to_frames(subs->runtime, count); + LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); + return 0; +} +#endif + + +/* + * allocate a memory block + */ +static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + if (rec->block) { + /* reallocation - release the old block */ + snd_util_mem_free(rec->emu->memhdr, rec->block); + rec->block = NULL; + } + + rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4; + rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes); + if (! rec->block) + return -ENOMEM; + rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */ + /* at least dma_bytes must be set for non-interleaved mode */ + subs->dma_buffer.bytes = params_buffer_bytes(hw_params); + + return 0; +} + +/* + * free the memory block + */ +static int emu8k_pcm_hw_free(struct snd_pcm_substream *subs) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + if (rec->block) { + int ch; + for (ch = 0; ch < rec->voices; ch++) + stop_voice(rec, ch); // to be sure + if (rec->dram_opened) + emu8k_close_dram(rec->emu); + snd_util_mem_free(rec->emu->memhdr, rec->block); + rec->block = NULL; + } + return 0; +} + +/* + */ +static int emu8k_pcm_prepare(struct snd_pcm_substream *subs) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + + rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate); + rec->last_ptr = 0; + rec->period_pos = 0; + + rec->buf_size = subs->runtime->buffer_size; + rec->period_size = subs->runtime->period_size; + rec->voices = subs->runtime->channels; + rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE; + if (rec->voices > 1) + rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE; + if (rec->voices > 1) { + rec->panning[0] = 0xff; + rec->panning[1] = 0x00; + } else + rec->panning[0] = 0x80; + + if (! rec->dram_opened) { + int err, i, ch; + + snd_emux_terminate_all(rec->emu->emu); + if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0) + return err; + rec->dram_opened = 1; + + /* clear loop blanks */ + snd_emu8000_write_wait(rec->emu, 0); + EMU8000_SMALW_WRITE(rec->emu, rec->offset); + for (i = 0; i < LOOP_BLANK_SIZE; i++) + EMU8000_SMLD_WRITE(rec->emu, 0); + for (ch = 0; ch < rec->voices; ch++) { + EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size); + for (i = 0; i < LOOP_BLANK_SIZE; i++) + EMU8000_SMLD_WRITE(rec->emu, 0); + } + } + + setup_voice(rec, 0); + if (rec->voices > 1) + setup_voice(rec, 1); + return 0; +} + +static snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct snd_emu8k_pcm *rec = subs->runtime->private_data; + if (rec->running) + return emu8k_get_curpos(rec, 0); + return 0; +} + + +static const struct snd_pcm_ops emu8k_pcm_ops = { + .open = emu8k_pcm_open, + .close = emu8k_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = emu8k_pcm_hw_params, + .hw_free = emu8k_pcm_hw_free, + .prepare = emu8k_pcm_prepare, + .trigger = emu8k_pcm_trigger, + .pointer = emu8k_pcm_pointer, + .copy_user = emu8k_pcm_copy, + .copy_kernel = emu8k_pcm_copy_kernel, + .fill_silence = emu8k_pcm_silence, +}; + + +static void snd_emu8000_pcm_free(struct snd_pcm *pcm) +{ + struct snd_emu8000 *emu = pcm->private_data; + emu->pcm = NULL; +} + +int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index) +{ + struct snd_pcm *pcm; + int err; + + if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0) + return err; + pcm->private_data = emu; + pcm->private_free = snd_emu8000_pcm_free; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops); + emu->pcm = pcm; + + snd_device_register(card, pcm); + + return 0; +} diff --git a/sound/isa/sb/emu8000_synth.c b/sound/isa/sb/emu8000_synth.c new file mode 100644 index 000000000..4aa719cad --- /dev/null +++ b/sound/isa/sb/emu8000_synth.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) by Jaroslav Kysela + * and (c) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai + * + * Emu8000 synth plug-in routine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "emu8000_local.h" +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai, Steve Ratcliffe"); +MODULE_DESCRIPTION("Emu8000 synth plug-in routine"); +MODULE_LICENSE("GPL"); + +/*----------------------------------------------------------------*/ + +/* + * create a new hardware dependent device for Emu8000 + */ +static int snd_emu8000_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_emu8000 *hw; + struct snd_emux *emu; + + hw = *(struct snd_emu8000**)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (hw == NULL) + return -EINVAL; + + if (hw->emu) + return -EBUSY; /* already exists..? */ + + if (snd_emux_new(&emu) < 0) + return -ENOMEM; + + hw->emu = emu; + snd_emu8000_ops_setup(hw); + + emu->hw = hw; + emu->max_voices = EMU8000_DRAM_VOICES; + emu->num_ports = hw->seq_ports; + + if (hw->memhdr) { + snd_printk(KERN_ERR "memhdr is already initialized!?\n"); + snd_util_memhdr_free(hw->memhdr); + } + hw->memhdr = snd_util_memhdr_new(hw->mem_size); + if (hw->memhdr == NULL) { + snd_emux_free(emu); + hw->emu = NULL; + return -ENOMEM; + } + + emu->memhdr = hw->memhdr; + emu->midi_ports = hw->seq_ports < 2 ? hw->seq_ports : 2; /* number of virmidi ports */ + emu->midi_devidx = 1; + emu->linear_panning = 1; + emu->hwdep_idx = 2; /* FIXED */ + + if (snd_emux_register(emu, dev->card, hw->index, "Emu8000") < 0) { + snd_emux_free(emu); + snd_util_memhdr_free(hw->memhdr); + hw->emu = NULL; + hw->memhdr = NULL; + return -ENOMEM; + } + + if (hw->mem_size > 0) + snd_emu8000_pcm_new(dev->card, hw, 1); + + dev->driver_data = hw; + + return 0; +} + + +/* + * free all resources + */ +static int snd_emu8000_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_emu8000 *hw; + + if (dev->driver_data == NULL) + return 0; /* no synth was allocated actually */ + + hw = dev->driver_data; + if (hw->pcm) + snd_device_free(dev->card, hw->pcm); + snd_emux_free(hw->emu); + snd_util_memhdr_free(hw->memhdr); + hw->emu = NULL; + hw->memhdr = NULL; + return 0; +} + +/* + * INIT part + */ + +static struct snd_seq_driver emu8000_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_emu8000_probe, + .remove = snd_emu8000_remove, + }, + .id = SNDRV_SEQ_DEV_ID_EMU8000, + .argsize = sizeof(struct snd_emu8000 *), +}; + +module_snd_seq_driver(emu8000_driver); diff --git a/sound/isa/sb/jazz16.c b/sound/isa/sb/jazz16.c new file mode 100644 index 000000000..bfa0055e1 --- /dev/null +++ b/sound/isa/sb/jazz16.c @@ -0,0 +1,390 @@ + +/* + * jazz16.c - driver for Media Vision Jazz16 based soundcards. + * Copyright (C) 2009 Krzysztof Helt + * Based on patches posted by Rask Ingemann Lambertsen and Rene Herman. + * Based on OSS Sound Blaster driver. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +#define PFX "jazz16: " + +MODULE_DESCRIPTION("Media Vision Jazz16"); +MODULE_SUPPORTED_DEVICE("{{Media Vision ??? }," + "{RTL,RTL3000}}"); + +MODULE_AUTHOR("Krzysztof Helt "); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static unsigned long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static unsigned long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Media Vision Jazz16 based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Media Vision Jazz16 based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Media Vision Jazz16 based soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for jazz16 driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for jazz16 driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for jazz16 driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for jazz16 driver."); +module_param_hw_array(dma8, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma8, "DMA8 # for jazz16 driver."); +module_param_hw_array(dma16, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma16, "DMA16 # for jazz16 driver."); + +#define SB_JAZZ16_WAKEUP 0xaf +#define SB_JAZZ16_SET_PORTS 0x50 +#define SB_DSP_GET_JAZZ_BRD_REV 0xfa +#define SB_JAZZ16_SET_DMAINTR 0xfb +#define SB_DSP_GET_JAZZ_MODEL 0xfe + +struct snd_card_jazz16 { + struct snd_sb *chip; +}; + +static irqreturn_t jazz16_interrupt(int irq, void *chip) +{ + return snd_sb8dsp_interrupt(chip); +} + +static int jazz16_configure_ports(unsigned long port, + unsigned long mpu_port, int idx) +{ + unsigned char val; + + if (!request_region(0x201, 1, "jazz16 config")) { + snd_printk(KERN_ERR "config port region is already in use.\n"); + return -EBUSY; + } + outb(SB_JAZZ16_WAKEUP - idx, 0x201); + udelay(100); + outb(SB_JAZZ16_SET_PORTS + idx, 0x201); + udelay(100); + val = port & 0x70; + val |= (mpu_port & 0x30) >> 4; + outb(val, 0x201); + + release_region(0x201, 1); + return 0; +} + +static int jazz16_detect_board(unsigned long port, + unsigned long mpu_port) +{ + int err; + int val; + struct snd_sb chip; + + if (!request_region(port, 0x10, "jazz16")) { + snd_printk(KERN_ERR "I/O port region is already in use.\n"); + return -EBUSY; + } + /* just to call snd_sbdsp_command/reset/get_byte() */ + chip.port = port; + + err = snd_sbdsp_reset(&chip); + if (err < 0) + for (val = 0; val < 4; val++) { + err = jazz16_configure_ports(port, mpu_port, val); + if (err < 0) + break; + + err = snd_sbdsp_reset(&chip); + if (!err) + break; + } + if (err < 0) { + err = -ENODEV; + goto err_unmap; + } + if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_BRD_REV)) { + err = -EBUSY; + goto err_unmap; + } + val = snd_sbdsp_get_byte(&chip); + if (val >= 0x30) + snd_sbdsp_get_byte(&chip); + + if ((val & 0xf0) != 0x10) { + err = -ENODEV; + goto err_unmap; + } + if (!snd_sbdsp_command(&chip, SB_DSP_GET_JAZZ_MODEL)) { + err = -EBUSY; + goto err_unmap; + } + snd_sbdsp_get_byte(&chip); + err = snd_sbdsp_get_byte(&chip); + snd_printd("Media Vision Jazz16 board detected: rev 0x%x, model 0x%x\n", + val, err); + + err = 0; + +err_unmap: + release_region(port, 0x10); + return err; +} + +static int jazz16_configure_board(struct snd_sb *chip, int mpu_irq) +{ + static unsigned char jazz_irq_bits[] = { 0, 0, 2, 3, 0, 1, 0, 4, + 0, 2, 5, 0, 0, 0, 0, 6 }; + static unsigned char jazz_dma_bits[] = { 0, 1, 0, 2, 0, 3, 0, 4 }; + + if (jazz_dma_bits[chip->dma8] == 0 || + jazz_dma_bits[chip->dma16] == 0 || + jazz_irq_bits[chip->irq] == 0) + return -EINVAL; + + if (!snd_sbdsp_command(chip, SB_JAZZ16_SET_DMAINTR)) + return -EBUSY; + + if (!snd_sbdsp_command(chip, + jazz_dma_bits[chip->dma8] | + (jazz_dma_bits[chip->dma16] << 4))) + return -EBUSY; + + if (!snd_sbdsp_command(chip, + jazz_irq_bits[chip->irq] | + (jazz_irq_bits[mpu_irq] << 4))) + return -EBUSY; + + return 0; +} + +static int snd_jazz16_match(struct device *devptr, unsigned int dev) +{ + if (!enable[dev]) + return 0; + if (port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR "please specify port\n"); + return 0; + } else if (port[dev] == 0x200 || (port[dev] & ~0x270)) { + snd_printk(KERN_ERR "incorrect port specified\n"); + return 0; + } + if (dma8[dev] != SNDRV_AUTO_DMA && + dma8[dev] != 1 && dma8[dev] != 3) { + snd_printk(KERN_ERR "dma8 must be 1 or 3\n"); + return 0; + } + if (dma16[dev] != SNDRV_AUTO_DMA && + dma16[dev] != 5 && dma16[dev] != 7) { + snd_printk(KERN_ERR "dma16 must be 5 or 7\n"); + return 0; + } + if (mpu_port[dev] != SNDRV_AUTO_PORT && + (mpu_port[dev] & ~0x030) != 0x300) { + snd_printk(KERN_ERR "incorrect mpu_port specified\n"); + return 0; + } + if (mpu_irq[dev] != SNDRV_AUTO_DMA && + mpu_irq[dev] != 2 && mpu_irq[dev] != 3 && + mpu_irq[dev] != 5 && mpu_irq[dev] != 7) { + snd_printk(KERN_ERR "mpu_irq must be 2, 3, 5 or 7\n"); + return 0; + } + return 1; +} + +static int snd_jazz16_probe(struct device *devptr, unsigned int dev) +{ + struct snd_card *card; + struct snd_card_jazz16 *jazz16; + struct snd_sb *chip; + struct snd_opl3 *opl3; + static int possible_irqs[] = {2, 3, 5, 7, 9, 10, 15, -1}; + static int possible_dmas8[] = {1, 3, -1}; + static int possible_dmas16[] = {5, 7, -1}; + int err, xirq, xdma8, xdma16, xmpu_port, xmpu_irq; + + err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_jazz16), &card); + if (err < 0) + return err; + + jazz16 = card->private_data; + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + xirq = snd_legacy_find_free_irq(possible_irqs); + if (xirq < 0) { + snd_printk(KERN_ERR "unable to find a free IRQ\n"); + err = -EBUSY; + goto err_free; + } + } + xdma8 = dma8[dev]; + if (xdma8 == SNDRV_AUTO_DMA) { + xdma8 = snd_legacy_find_free_dma(possible_dmas8); + if (xdma8 < 0) { + snd_printk(KERN_ERR "unable to find a free DMA8\n"); + err = -EBUSY; + goto err_free; + } + } + xdma16 = dma16[dev]; + if (xdma16 == SNDRV_AUTO_DMA) { + xdma16 = snd_legacy_find_free_dma(possible_dmas16); + if (xdma16 < 0) { + snd_printk(KERN_ERR "unable to find a free DMA16\n"); + err = -EBUSY; + goto err_free; + } + } + + xmpu_port = mpu_port[dev]; + if (xmpu_port == SNDRV_AUTO_PORT) + xmpu_port = 0; + err = jazz16_detect_board(port[dev], xmpu_port); + if (err < 0) { + printk(KERN_ERR "Media Vision Jazz16 board not detected\n"); + goto err_free; + } + err = snd_sbdsp_create(card, port[dev], irq[dev], + jazz16_interrupt, + dma8[dev], dma16[dev], + SB_HW_JAZZ16, + &chip); + if (err < 0) + goto err_free; + + xmpu_irq = mpu_irq[dev]; + if (xmpu_irq == SNDRV_AUTO_IRQ || mpu_port[dev] == SNDRV_AUTO_PORT) + xmpu_irq = 0; + err = jazz16_configure_board(chip, xmpu_irq); + if (err < 0) { + printk(KERN_ERR "Media Vision Jazz16 configuration failed\n"); + goto err_free; + } + + jazz16->chip = chip; + + strcpy(card->driver, "jazz16"); + strcpy(card->shortname, "Media Vision Jazz16"); + sprintf(card->longname, + "Media Vision Jazz16 at 0x%lx, irq %d, dma8 %d, dma16 %d", + port[dev], xirq, xdma8, xdma16); + + err = snd_sb8dsp_pcm(chip, 0); + if (err < 0) + goto err_free; + err = snd_sbmixer_new(chip); + if (err < 0) + goto err_free; + + err = snd_opl3_create(card, chip->port, chip->port + 2, + OPL3_HW_AUTO, 1, &opl3); + if (err < 0) + snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n", + chip->port, chip->port + 2); + else { + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + goto err_free; + } + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + if (mpu_irq[dev] == SNDRV_AUTO_IRQ) + mpu_irq[dev] = -1; + + if (snd_mpu401_uart_new(card, 0, + MPU401_HW_MPU401, + mpu_port[dev], 0, + mpu_irq[dev], + NULL) < 0) + snd_printk(KERN_ERR "no MPU-401 device at 0x%lx\n", + mpu_port[dev]); + } + + err = snd_card_register(card); + if (err < 0) + goto err_free; + + dev_set_drvdata(devptr, card); + return 0; + +err_free: + snd_card_free(card); + return err; +} + +static int snd_jazz16_remove(struct device *devptr, unsigned int dev) +{ + struct snd_card *card = dev_get_drvdata(devptr); + + snd_card_free(card); + return 0; +} + +#ifdef CONFIG_PM +static int snd_jazz16_suspend(struct device *pdev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct snd_card_jazz16 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + snd_sbmixer_suspend(chip); + return 0; +} + +static int snd_jazz16_resume(struct device *pdev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(pdev); + struct snd_card_jazz16 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_sbdsp_reset(chip); + snd_sbmixer_resume(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static struct isa_driver snd_jazz16_driver = { + .match = snd_jazz16_match, + .probe = snd_jazz16_probe, + .remove = snd_jazz16_remove, +#ifdef CONFIG_PM + .suspend = snd_jazz16_suspend, + .resume = snd_jazz16_resume, +#endif + .driver = { + .name = "jazz16" + }, +}; + +module_isa_driver(snd_jazz16_driver, SNDRV_CARDS); diff --git a/sound/isa/sb/sb16.c b/sound/isa/sb/sb16.c new file mode 100644 index 000000000..8f9ebeb99 --- /dev/null +++ b/sound/isa/sb/sb16.c @@ -0,0 +1,697 @@ +/* + * Driver for SoundBlaster 16/AWE32/AWE64 soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +#ifdef SNDRV_SBAWE +#define PFX "sbawe: " +#else +#define PFX "sb16: " +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_LICENSE("GPL"); +#ifndef SNDRV_SBAWE +MODULE_DESCRIPTION("Sound Blaster 16"); +MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 16}," + "{Creative Labs,SB Vibra16S}," + "{Creative Labs,SB Vibra16C}," + "{Creative Labs,SB Vibra16CL}," + "{Creative Labs,SB Vibra16X}}"); +#else +MODULE_DESCRIPTION("Sound Blaster AWE"); +MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB AWE 32}," + "{Creative Labs,SB AWE 64}," + "{Creative Labs,SB AWE 64 Gold}}"); +#endif + +#if 0 +#define SNDRV_DEBUG_IRQ +#endif + +#if defined(SNDRV_SBAWE) && IS_ENABLED(CONFIG_SND_SEQUENCER) +#define SNDRV_SBAWE_EMU8000 +#endif + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260,0x280 */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x330,0x300 */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +#ifdef SNDRV_SBAWE_EMU8000 +static long awe_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +#endif +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ +static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 5,6,7 */ +static int mic_agc[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#ifdef CONFIG_SND_SB16_CSP +static int csp[SNDRV_CARDS]; +#endif +#ifdef SNDRV_SBAWE_EMU8000 +static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4}; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for SoundBlaster 16 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for SoundBlaster 16 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable SoundBlaster 16 soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for SB16 driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver."); +#ifdef SNDRV_SBAWE_EMU8000 +module_param_hw_array(awe_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver."); +#endif +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for SB16 driver."); +module_param_hw_array(dma8, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver."); +module_param_hw_array(dma16, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma16, "16-bit DMA # for SB16 driver."); +module_param_array(mic_agc, int, NULL, 0444); +MODULE_PARM_DESC(mic_agc, "Mic Auto-Gain-Control switch."); +#ifdef CONFIG_SND_SB16_CSP +module_param_array(csp, int, NULL, 0444); +MODULE_PARM_DESC(csp, "ASP/CSP chip support."); +#endif +#ifdef SNDRV_SBAWE_EMU8000 +module_param_array(seq_ports, int, NULL, 0444); +MODULE_PARM_DESC(seq_ports, "Number of sequencer ports for WaveTable synth."); +#endif + +#ifdef CONFIG_PNP +static int isa_registered; +static int pnp_registered; +#endif + +struct snd_card_sb16 { + struct resource *fm_res; /* used to block FM i/o region for legacy cards */ + struct snd_sb *chip; +#ifdef CONFIG_PNP + int dev_no; + struct pnp_dev *dev; +#ifdef SNDRV_SBAWE_EMU8000 + struct pnp_dev *devwt; +#endif +#endif +}; + +#ifdef CONFIG_PNP + +static const struct pnp_card_device_id snd_sb16_pnpids[] = { +#ifndef SNDRV_SBAWE + /* Sound Blaster 16 PnP */ + { .id = "CTL0024", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0025", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0026", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0027", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0028", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0029", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL002a", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + /* Note: This card has also a CTL0051:StereoEnhance device!!! */ + { .id = "CTL002b", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL002c", .devs = { { "CTL0031" } } }, + /* Sound Blaster Vibra16S */ + { .id = "CTL0051", .devs = { { "CTL0001" } } }, + /* Sound Blaster Vibra16C */ + { .id = "CTL0070", .devs = { { "CTL0001" } } }, + /* Sound Blaster Vibra16CL - added by ctm@ardi.com */ + { .id = "CTL0080", .devs = { { "CTL0041" } } }, + /* Sound Blaster 16 'value' PnP. It says model ct4130 on the pcb, */ + /* but ct4131 on a sticker on the board.. */ + { .id = "CTL0086", .devs = { { "CTL0041" } } }, + /* Sound Blaster Vibra16X */ + { .id = "CTL00f0", .devs = { { "CTL0043" } } }, + /* Sound Blaster 16 (Virtual PC 2004) */ + { .id = "tBA03b0", .devs = { {.id="PNPb003" } } }, +#else /* SNDRV_SBAWE defined */ + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0035", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0039", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0042", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0043", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + /* Note: This card has also a CTL0051:StereoEnhance device!!! */ + { .id = "CTL0044", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + /* Note: This card has also a CTL0051:StereoEnhance device!!! */ + { .id = "CTL0045", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0046", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0047", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0048", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0054", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL009a", .devs = { { "CTL0041" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL009c", .devs = { { "CTL0041" }, { "CTL0021" } } }, + /* Sound Blaster 32 PnP */ + { .id = "CTL009f", .devs = { { "CTL0041" }, { "CTL0021" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL009d", .devs = { { "CTL0042" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP Gold */ + { .id = "CTL009e", .devs = { { "CTL0044" }, { "CTL0023" } } }, + /* Sound Blaster AWE 64 PnP Gold */ + { .id = "CTL00b2", .devs = { { "CTL0044" }, { "CTL0023" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c1", .devs = { { "CTL0042" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c3", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c5", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c7", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00e4", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00e9", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster 16 PnP (AWE) */ + { .id = "CTL00ed", .devs = { { "CTL0041" }, { "CTL0070" } } }, + /* Generic entries */ + { .id = "CTLXXXX" , .devs = { { "CTL0031" }, { "CTL0021" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0041" }, { "CTL0021" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0042" }, { "CTL0022" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0044" }, { "CTL0023" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0045" }, { "CTL0022" } } }, +#endif /* SNDRV_SBAWE */ + { .id = "", } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_sb16_pnpids); + +#endif /* CONFIG_PNP */ + +#ifdef SNDRV_SBAWE_EMU8000 +#define DRIVER_NAME "snd-card-sbawe" +#else +#define DRIVER_NAME "snd-card-sb16" +#endif + +#ifdef CONFIG_PNP + +static int snd_card_sb16_pnp(int dev, struct snd_card_sb16 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) + return -ENODEV; + +#ifdef SNDRV_SBAWE_EMU8000 + acard->devwt = pnp_request_card_device(card, id->devs[1].id, acard->dev); +#endif + /* Audio initialization */ + pdev = acard->dev; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); + return err; + } + port[dev] = pnp_port_start(pdev, 0); + mpu_port[dev] = pnp_port_start(pdev, 1); + fm_port[dev] = pnp_port_start(pdev, 2); + dma8[dev] = pnp_dma(pdev, 0); + dma16[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("pnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n", + port[dev], mpu_port[dev], fm_port[dev]); + snd_printdd("pnp SB16: dma1=%i, dma2=%i, irq=%i\n", + dma8[dev], dma16[dev], irq[dev]); +#ifdef SNDRV_SBAWE_EMU8000 + /* WaveTable initialization */ + pdev = acard->devwt; + if (pdev != NULL) { + err = pnp_activate_dev(pdev); + if (err < 0) { + goto __wt_error; + } + awe_port[dev] = pnp_port_start(pdev, 0); + snd_printdd("pnp SB16: wavetable port=0x%llx\n", + (unsigned long long)pnp_port_start(pdev, 0)); + } else { +__wt_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "WaveTable pnp configure failure\n"); + } + acard->devwt = NULL; + awe_port[dev] = -1; + } +#endif + return 0; +} + +#endif /* CONFIG_PNP */ + +static void snd_sb16_free(struct snd_card *card) +{ + struct snd_card_sb16 *acard = card->private_data; + + if (acard == NULL) + return; + release_and_free_resource(acard->fm_res); +} + +#ifdef CONFIG_PNP +#define is_isapnp_selected(dev) isapnp[dev] +#else +#define is_isapnp_selected(dev) 0 +#endif + +static int snd_sb16_card_new(struct device *devptr, int dev, + struct snd_card **cardp) +{ + struct snd_card *card; + int err; + + err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_sb16), &card); + if (err < 0) + return err; + card->private_free = snd_sb16_free; + *cardp = card; + return 0; +} + +static int snd_sb16_probe(struct snd_card *card, int dev) +{ + int xirq, xdma8, xdma16; + struct snd_sb *chip; + struct snd_card_sb16 *acard = card->private_data; + struct snd_opl3 *opl3; + struct snd_hwdep *synth = NULL; +#ifdef CONFIG_SND_SB16_CSP + struct snd_hwdep *xcsp = NULL; +#endif + unsigned long flags; + int err; + + xirq = irq[dev]; + xdma8 = dma8[dev]; + xdma16 = dma16[dev]; + + if ((err = snd_sbdsp_create(card, + port[dev], + xirq, + snd_sb16dsp_interrupt, + xdma8, + xdma16, + SB_HW_AUTO, + &chip)) < 0) + return err; + + acard->chip = chip; + if (chip->hardware != SB_HW_16) { + snd_printk(KERN_ERR PFX "SB 16 chip was not detected at 0x%lx\n", port[dev]); + return -ENODEV; + } + chip->mpu_port = mpu_port[dev]; + if (! is_isapnp_selected(dev) && (err = snd_sb16dsp_configure(chip)) < 0) + return err; + + if ((err = snd_sb16dsp_pcm(chip, 0)) < 0) + return err; + + strcpy(card->driver, +#ifdef SNDRV_SBAWE_EMU8000 + awe_port[dev] > 0 ? "SB AWE" : +#endif + "SB16"); + strcpy(card->shortname, chip->name); + sprintf(card->longname, "%s at 0x%lx, irq %i, dma ", + chip->name, + chip->port, + xirq); + if (xdma8 >= 0) + sprintf(card->longname + strlen(card->longname), "%d", xdma8); + if (xdma16 >= 0) + sprintf(card->longname + strlen(card->longname), "%s%d", + xdma8 >= 0 ? "&" : "", xdma16); + + if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) { + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB, + chip->mpu_port, + MPU401_INFO_IRQ_HOOK, -1, + &chip->rmidi)) < 0) + return err; + chip->rmidi_callback = snd_mpu401_uart_interrupt; + } + +#ifdef SNDRV_SBAWE_EMU8000 + if (awe_port[dev] == SNDRV_AUTO_PORT) + awe_port[dev] = 0; /* disable */ +#endif + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2, + OPL3_HW_OPL3, + acard->fm_res != NULL || fm_port[dev] == port[dev], + &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n", + fm_port[dev], fm_port[dev] + 2); + } else { +#ifdef SNDRV_SBAWE_EMU8000 + int seqdev = awe_port[dev] > 0 ? 2 : 1; +#else + int seqdev = 1; +#endif + if ((err = snd_opl3_hwdep_new(opl3, 0, seqdev, &synth)) < 0) + return err; + } + } + + if ((err = snd_sbmixer_new(chip)) < 0) + return err; + +#ifdef CONFIG_SND_SB16_CSP + /* CSP chip on SB16ASP/AWE32 */ + if ((chip->hardware == SB_HW_16) && csp[dev]) { + snd_sb_csp_new(chip, synth != NULL ? 1 : 0, &xcsp); + if (xcsp) { + chip->csp = xcsp->private_data; + chip->hardware = SB_HW_16CSP; + } else { + snd_printk(KERN_INFO PFX "warning - CSP chip not detected on soundcard #%i\n", dev + 1); + } + } +#endif +#ifdef SNDRV_SBAWE_EMU8000 + if (awe_port[dev] > 0) { + if ((err = snd_emu8000_new(card, 1, awe_port[dev], + seq_ports[dev], NULL)) < 0) { + snd_printk(KERN_ERR PFX "fatal error - EMU-8000 synthesizer not detected at 0x%lx\n", awe_port[dev]); + + return err; + } + } +#endif + + /* setup Mic AGC */ + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, SB_DSP4_MIC_AGC, + (snd_sbmixer_read(chip, SB_DSP4_MIC_AGC) & 0x01) | + (mic_agc[dev] ? 0x00 : 0x01)); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + if ((err = snd_card_register(card)) < 0) + return err; + + return 0; +} + +#ifdef CONFIG_PM +static int snd_sb16_suspend(struct snd_card *card, pm_message_t state) +{ + struct snd_card_sb16 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + snd_sbmixer_suspend(chip); + return 0; +} + +static int snd_sb16_resume(struct snd_card *card) +{ + struct snd_card_sb16 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_sbdsp_reset(chip); + snd_sbmixer_resume(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +static int snd_sb16_isa_probe1(int dev, struct device *pdev) +{ + struct snd_card_sb16 *acard; + struct snd_card *card; + int err; + + err = snd_sb16_card_new(pdev, dev, &card); + if (err < 0) + return err; + + acard = card->private_data; + /* non-PnP FM port address is hardwired with base port address */ + fm_port[dev] = port[dev]; + /* block the 0x388 port to avoid PnP conflicts */ + acard->fm_res = request_region(0x388, 4, "SoundBlaster FM"); +#ifdef SNDRV_SBAWE_EMU8000 + /* non-PnP AWE port address is hardwired with base port address */ + awe_port[dev] = port[dev] + 0x400; +#endif + + if ((err = snd_sb16_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + dev_set_drvdata(pdev, card); + return 0; +} + + +static int snd_sb16_isa_match(struct device *pdev, unsigned int dev) +{ + return enable[dev] && !is_isapnp_selected(dev); +} + +static int snd_sb16_isa_probe(struct device *pdev, unsigned int dev) +{ + int err; + static int possible_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dmas8[] = {1, 3, 0, -1}; + static int possible_dmas16[] = {5, 6, 7, -1}; + + if (irq[dev] == SNDRV_AUTO_IRQ) { + if ((irq[dev] = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma8[dev] == SNDRV_AUTO_DMA) { + if ((dma8[dev] = snd_legacy_find_free_dma(possible_dmas8)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n"); + return -EBUSY; + } + } + if (dma16[dev] == SNDRV_AUTO_DMA) { + if ((dma16[dev] = snd_legacy_find_free_dma(possible_dmas16)) < 0) { + snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n"); + return -EBUSY; + } + } + + if (port[dev] != SNDRV_AUTO_PORT) + return snd_sb16_isa_probe1(dev, pdev); + else { + static int possible_ports[] = {0x220, 0x240, 0x260, 0x280}; + int i; + for (i = 0; i < ARRAY_SIZE(possible_ports); i++) { + port[dev] = possible_ports[i]; + err = snd_sb16_isa_probe1(dev, pdev); + if (! err) + return 0; + } + return err; + } +} + +static int snd_sb16_isa_remove(struct device *pdev, unsigned int dev) +{ + snd_card_free(dev_get_drvdata(pdev)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_sb16_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_sb16_suspend(dev_get_drvdata(dev), state); +} + +static int snd_sb16_isa_resume(struct device *dev, unsigned int n) +{ + return snd_sb16_resume(dev_get_drvdata(dev)); +} +#endif + +#ifdef SNDRV_SBAWE +#define DEV_NAME "sbawe" +#else +#define DEV_NAME "sb16" +#endif + +static struct isa_driver snd_sb16_isa_driver = { + .match = snd_sb16_isa_match, + .probe = snd_sb16_isa_probe, + .remove = snd_sb16_isa_remove, +#ifdef CONFIG_PM + .suspend = snd_sb16_isa_suspend, + .resume = snd_sb16_isa_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + + +#ifdef CONFIG_PNP +static int snd_sb16_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int dev; + struct snd_card *card; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || !isapnp[dev]) + continue; + res = snd_sb16_card_new(&pcard->card->dev, dev, &card); + if (res < 0) + return res; + if ((res = snd_card_sb16_pnp(dev, card->private_data, pcard, pid)) < 0 || + (res = snd_sb16_probe(card, dev)) < 0) { + snd_card_free(card); + return res; + } + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; + } + + return -ENODEV; +} + +static void snd_sb16_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +#ifdef CONFIG_PM +static int snd_sb16_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_sb16_suspend(pnp_get_card_drvdata(pcard), state); +} +static int snd_sb16_pnp_resume(struct pnp_card_link *pcard) +{ + return snd_sb16_resume(pnp_get_card_drvdata(pcard)); +} +#endif + +static struct pnp_card_driver sb16_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, +#ifdef SNDRV_SBAWE + .name = "sbawe", +#else + .name = "sb16", +#endif + .id_table = snd_sb16_pnpids, + .probe = snd_sb16_pnp_detect, + .remove = snd_sb16_pnp_remove, +#ifdef CONFIG_PM + .suspend = snd_sb16_pnp_suspend, + .resume = snd_sb16_pnp_resume, +#endif +}; + +#endif /* CONFIG_PNP */ + +static int __init alsa_card_sb16_init(void) +{ + int err; + + err = isa_register_driver(&snd_sb16_isa_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_card_driver(&sb16_pnpc_driver); + if (!err) + pnp_registered = 1; + + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_sb16_exit(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_card_driver(&sb16_pnpc_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_sb16_isa_driver); +} + +module_init(alsa_card_sb16_init) +module_exit(alsa_card_sb16_exit) diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c new file mode 100644 index 000000000..c16c81511 --- /dev/null +++ b/sound/isa/sb/sb16_csp.c @@ -0,0 +1,1199 @@ +/* + * Copyright (c) 1999 by Uros Bizjak + * Takashi Iwai + * + * SB16ASP/AWE32 CSP control + * + * CSP microcode loader: + * alsa-tools/sb16_csp/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Uros Bizjak "); +MODULE_DESCRIPTION("ALSA driver for SB16 Creative Signal Processor"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("sb16/mulaw_main.csp"); +MODULE_FIRMWARE("sb16/alaw_main.csp"); +MODULE_FIRMWARE("sb16/ima_adpcm_init.csp"); +MODULE_FIRMWARE("sb16/ima_adpcm_playback.csp"); +MODULE_FIRMWARE("sb16/ima_adpcm_capture.csp"); + +#ifdef SNDRV_LITTLE_ENDIAN +#define CSP_HDR_VALUE(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) +#else +#define CSP_HDR_VALUE(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) +#endif + +#define RIFF_HEADER CSP_HDR_VALUE('R', 'I', 'F', 'F') +#define CSP__HEADER CSP_HDR_VALUE('C', 'S', 'P', ' ') +#define LIST_HEADER CSP_HDR_VALUE('L', 'I', 'S', 'T') +#define FUNC_HEADER CSP_HDR_VALUE('f', 'u', 'n', 'c') +#define CODE_HEADER CSP_HDR_VALUE('c', 'o', 'd', 'e') +#define INIT_HEADER CSP_HDR_VALUE('i', 'n', 'i', 't') +#define MAIN_HEADER CSP_HDR_VALUE('m', 'a', 'i', 'n') + +/* + * RIFF data format + */ +struct riff_header { + __le32 name; + __le32 len; +}; + +struct desc_header { + struct riff_header info; + __le16 func_nr; + __le16 VOC_type; + __le16 flags_play_rec; + __le16 flags_16bit_8bit; + __le16 flags_stereo_mono; + __le16 flags_rates; +}; + +/* + * prototypes + */ +static void snd_sb_csp_free(struct snd_hwdep *hw); +static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file); +static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg); +static int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file); + +static int csp_detect(struct snd_sb *chip, int *version); +static int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val); +static int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val); +static int read_register(struct snd_sb *chip, unsigned char reg); +static int set_mode_register(struct snd_sb *chip, unsigned char mode); +static int get_version(struct snd_sb *chip); + +static int snd_sb_csp_riff_load(struct snd_sb_csp * p, + struct snd_sb_csp_microcode __user * code); +static int snd_sb_csp_unload(struct snd_sb_csp * p); +static int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags); +static int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t pcm_sfmt, int play_rec_mode); +static int snd_sb_csp_check_version(struct snd_sb_csp * p); + +static int snd_sb_csp_use(struct snd_sb_csp * p); +static int snd_sb_csp_unuse(struct snd_sb_csp * p); +static int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels); +static int snd_sb_csp_stop(struct snd_sb_csp * p); +static int snd_sb_csp_pause(struct snd_sb_csp * p); +static int snd_sb_csp_restart(struct snd_sb_csp * p); + +static int snd_sb_qsound_build(struct snd_sb_csp * p); +static void snd_sb_qsound_destroy(struct snd_sb_csp * p); +static int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p); + +static int init_proc_entry(struct snd_sb_csp * p, int device); +static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); + +/* + * Detect CSP chip and create a new instance + */ +int snd_sb_csp_new(struct snd_sb *chip, int device, struct snd_hwdep ** rhwdep) +{ + struct snd_sb_csp *p; + int uninitialized_var(version); + int err; + struct snd_hwdep *hw; + + if (rhwdep) + *rhwdep = NULL; + + if (csp_detect(chip, &version)) + return -ENODEV; + + if ((err = snd_hwdep_new(chip->card, "SB16-CSP", device, &hw)) < 0) + return err; + + if ((p = kzalloc(sizeof(*p), GFP_KERNEL)) == NULL) { + snd_device_free(chip->card, hw); + return -ENOMEM; + } + p->chip = chip; + p->version = version; + + /* CSP operators */ + p->ops.csp_use = snd_sb_csp_use; + p->ops.csp_unuse = snd_sb_csp_unuse; + p->ops.csp_autoload = snd_sb_csp_autoload; + p->ops.csp_start = snd_sb_csp_start; + p->ops.csp_stop = snd_sb_csp_stop; + p->ops.csp_qsound_transfer = snd_sb_csp_qsound_transfer; + + mutex_init(&p->access_mutex); + sprintf(hw->name, "CSP v%d.%d", (version >> 4), (version & 0x0f)); + hw->iface = SNDRV_HWDEP_IFACE_SB16CSP; + hw->private_data = p; + hw->private_free = snd_sb_csp_free; + + /* operators - only write/ioctl */ + hw->ops.open = snd_sb_csp_open; + hw->ops.ioctl = snd_sb_csp_ioctl; + hw->ops.release = snd_sb_csp_release; + + /* create a proc entry */ + init_proc_entry(p, device); + if (rhwdep) + *rhwdep = hw; + return 0; +} + +/* + * free_private for hwdep instance + */ +static void snd_sb_csp_free(struct snd_hwdep *hwdep) +{ + int i; + struct snd_sb_csp *p = hwdep->private_data; + if (p) { + if (p->running & SNDRV_SB_CSP_ST_RUNNING) + snd_sb_csp_stop(p); + for (i = 0; i < ARRAY_SIZE(p->csp_programs); ++i) + release_firmware(p->csp_programs[i]); + kfree(p); + } +} + +/* ------------------------------ */ + +/* + * open the device exclusively + */ +static int snd_sb_csp_open(struct snd_hwdep * hw, struct file *file) +{ + struct snd_sb_csp *p = hw->private_data; + return (snd_sb_csp_use(p)); +} + +/* + * ioctl for hwdep device: + */ +static int snd_sb_csp_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_sb_csp *p = hw->private_data; + struct snd_sb_csp_info info; + struct snd_sb_csp_start start_info; + int err; + + if (snd_BUG_ON(!p)) + return -EINVAL; + + if (snd_sb_csp_check_version(p)) + return -ENODEV; + + switch (cmd) { + /* get information */ + case SNDRV_SB_CSP_IOCTL_INFO: + memset(&info, 0, sizeof(info)); + *info.codec_name = *p->codec_name; + info.func_nr = p->func_nr; + info.acc_format = p->acc_format; + info.acc_channels = p->acc_channels; + info.acc_width = p->acc_width; + info.acc_rates = p->acc_rates; + info.csp_mode = p->mode; + info.run_channels = p->run_channels; + info.run_width = p->run_width; + info.version = p->version; + info.state = p->running; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + err = -EFAULT; + else + err = 0; + break; + + /* load CSP microcode */ + case SNDRV_SB_CSP_IOCTL_LOAD_CODE: + err = (p->running & SNDRV_SB_CSP_ST_RUNNING ? + -EBUSY : snd_sb_csp_riff_load(p, (struct snd_sb_csp_microcode __user *) arg)); + break; + case SNDRV_SB_CSP_IOCTL_UNLOAD_CODE: + err = (p->running & SNDRV_SB_CSP_ST_RUNNING ? + -EBUSY : snd_sb_csp_unload(p)); + break; + + /* change CSP running state */ + case SNDRV_SB_CSP_IOCTL_START: + if (copy_from_user(&start_info, (void __user *) arg, sizeof(start_info))) + err = -EFAULT; + else + err = snd_sb_csp_start(p, start_info.sample_width, start_info.channels); + break; + case SNDRV_SB_CSP_IOCTL_STOP: + err = snd_sb_csp_stop(p); + break; + case SNDRV_SB_CSP_IOCTL_PAUSE: + err = snd_sb_csp_pause(p); + break; + case SNDRV_SB_CSP_IOCTL_RESTART: + err = snd_sb_csp_restart(p); + break; + default: + err = -ENOTTY; + break; + } + + return err; +} + +/* + * close the device + */ +static int snd_sb_csp_release(struct snd_hwdep * hw, struct file *file) +{ + struct snd_sb_csp *p = hw->private_data; + return (snd_sb_csp_unuse(p)); +} + +/* ------------------------------ */ + +/* + * acquire device + */ +static int snd_sb_csp_use(struct snd_sb_csp * p) +{ + mutex_lock(&p->access_mutex); + if (p->used) { + mutex_unlock(&p->access_mutex); + return -EAGAIN; + } + p->used++; + mutex_unlock(&p->access_mutex); + + return 0; + +} + +/* + * release device + */ +static int snd_sb_csp_unuse(struct snd_sb_csp * p) +{ + mutex_lock(&p->access_mutex); + p->used--; + mutex_unlock(&p->access_mutex); + + return 0; +} + +/* + * load microcode via ioctl: + * code is user-space pointer + */ +static int snd_sb_csp_riff_load(struct snd_sb_csp * p, + struct snd_sb_csp_microcode __user * mcode) +{ + struct snd_sb_csp_mc_header info; + + unsigned char __user *data_ptr; + unsigned char __user *data_end; + unsigned short func_nr = 0; + + struct riff_header file_h, item_h, code_h; + __le32 item_type; + struct desc_header funcdesc_h; + + unsigned long flags; + int err; + + if (copy_from_user(&info, mcode, sizeof(info))) + return -EFAULT; + data_ptr = mcode->data; + + if (copy_from_user(&file_h, data_ptr, sizeof(file_h))) + return -EFAULT; + if ((le32_to_cpu(file_h.name) != RIFF_HEADER) || + (le32_to_cpu(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) { + snd_printd("%s: Invalid RIFF header\n", __func__); + return -EINVAL; + } + data_ptr += sizeof(file_h); + data_end = data_ptr + le32_to_cpu(file_h.len); + + if (copy_from_user(&item_type, data_ptr, sizeof(item_type))) + return -EFAULT; + if (le32_to_cpu(item_type) != CSP__HEADER) { + snd_printd("%s: Invalid RIFF file type\n", __func__); + return -EINVAL; + } + data_ptr += sizeof (item_type); + + for (; data_ptr < data_end; data_ptr += le32_to_cpu(item_h.len)) { + if (copy_from_user(&item_h, data_ptr, sizeof(item_h))) + return -EFAULT; + data_ptr += sizeof(item_h); + if (le32_to_cpu(item_h.name) != LIST_HEADER) + continue; + + if (copy_from_user(&item_type, data_ptr, sizeof(item_type))) + return -EFAULT; + switch (le32_to_cpu(item_type)) { + case FUNC_HEADER: + if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h))) + return -EFAULT; + func_nr = le16_to_cpu(funcdesc_h.func_nr); + break; + case CODE_HEADER: + if (func_nr != info.func_req) + break; /* not required function, try next */ + data_ptr += sizeof(item_type); + + /* destroy QSound mixer element */ + if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { + snd_sb_qsound_destroy(p); + } + /* Clear all flags */ + p->running = 0; + p->mode = 0; + + /* load microcode blocks */ + for (;;) { + if (data_ptr >= data_end) + return -EINVAL; + if (copy_from_user(&code_h, data_ptr, sizeof(code_h))) + return -EFAULT; + + /* init microcode blocks */ + if (le32_to_cpu(code_h.name) != INIT_HEADER) + break; + data_ptr += sizeof(code_h); + err = snd_sb_csp_load_user(p, data_ptr, le32_to_cpu(code_h.len), + SNDRV_SB_CSP_LOAD_INITBLOCK); + if (err) + return err; + data_ptr += le32_to_cpu(code_h.len); + } + /* main microcode block */ + if (copy_from_user(&code_h, data_ptr, sizeof(code_h))) + return -EFAULT; + + if (le32_to_cpu(code_h.name) != MAIN_HEADER) { + snd_printd("%s: Missing 'main' microcode\n", __func__); + return -EINVAL; + } + data_ptr += sizeof(code_h); + err = snd_sb_csp_load_user(p, data_ptr, + le32_to_cpu(code_h.len), 0); + if (err) + return err; + + /* fill in codec header */ + strlcpy(p->codec_name, info.codec_name, sizeof(p->codec_name)); + p->func_nr = func_nr; + p->mode = le16_to_cpu(funcdesc_h.flags_play_rec); + switch (le16_to_cpu(funcdesc_h.VOC_type)) { + case 0x0001: /* QSound decoder */ + if (le16_to_cpu(funcdesc_h.flags_play_rec) == SNDRV_SB_CSP_MODE_DSP_WRITE) { + if (snd_sb_qsound_build(p) == 0) + /* set QSound flag and clear all other mode flags */ + p->mode = SNDRV_SB_CSP_MODE_QSOUND; + } + p->acc_format = 0; + break; + case 0x0006: /* A Law codec */ + p->acc_format = SNDRV_PCM_FMTBIT_A_LAW; + break; + case 0x0007: /* Mu Law codec */ + p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW; + break; + case 0x0011: /* what Creative thinks is IMA ADPCM codec */ + case 0x0200: /* Creative ADPCM codec */ + p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM; + break; + case 201: /* Text 2 Speech decoder */ + /* TODO: Text2Speech handling routines */ + p->acc_format = 0; + break; + case 0x0202: /* Fast Speech 8 codec */ + case 0x0203: /* Fast Speech 10 codec */ + p->acc_format = SNDRV_PCM_FMTBIT_SPECIAL; + break; + default: /* other codecs are unsupported */ + p->acc_format = p->acc_width = p->acc_rates = 0; + p->mode = 0; + snd_printd("%s: Unsupported CSP codec type: 0x%04x\n", + __func__, + le16_to_cpu(funcdesc_h.VOC_type)); + return -EINVAL; + } + p->acc_channels = le16_to_cpu(funcdesc_h.flags_stereo_mono); + p->acc_width = le16_to_cpu(funcdesc_h.flags_16bit_8bit); + p->acc_rates = le16_to_cpu(funcdesc_h.flags_rates); + + /* Decouple CSP from IRQ and DMAREQ lines */ + spin_lock_irqsave(&p->chip->reg_lock, flags); + set_mode_register(p->chip, 0xfc); + set_mode_register(p->chip, 0x00); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + + /* finished loading successfully */ + p->running = SNDRV_SB_CSP_ST_LOADED; /* set LOADED flag */ + return 0; + } + } + snd_printd("%s: Function #%d not found\n", __func__, info.func_req); + return -EINVAL; +} + +/* + * unload CSP microcode + */ +static int snd_sb_csp_unload(struct snd_sb_csp * p) +{ + if (p->running & SNDRV_SB_CSP_ST_RUNNING) + return -EBUSY; + if (!(p->running & SNDRV_SB_CSP_ST_LOADED)) + return -ENXIO; + + /* clear supported formats */ + p->acc_format = 0; + p->acc_channels = p->acc_width = p->acc_rates = 0; + /* destroy QSound mixer element */ + if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { + snd_sb_qsound_destroy(p); + } + /* clear all flags */ + p->running = 0; + p->mode = 0; + return 0; +} + +/* + * send command sequence to DSP + */ +static inline int command_seq(struct snd_sb *chip, const unsigned char *seq, int size) +{ + int i; + for (i = 0; i < size; i++) { + if (!snd_sbdsp_command(chip, seq[i])) + return -EIO; + } + return 0; +} + +/* + * set CSP codec parameter + */ +static int set_codec_parameter(struct snd_sb *chip, unsigned char par, unsigned char val) +{ + unsigned char dsp_cmd[3]; + + dsp_cmd[0] = 0x05; /* CSP set codec parameter */ + dsp_cmd[1] = val; /* Parameter value */ + dsp_cmd[2] = par; /* Parameter */ + command_seq(chip, dsp_cmd, 3); + snd_sbdsp_command(chip, 0x03); /* DSP read? */ + if (snd_sbdsp_get_byte(chip) != par) + return -EIO; + return 0; +} + +/* + * set CSP register + */ +static int set_register(struct snd_sb *chip, unsigned char reg, unsigned char val) +{ + unsigned char dsp_cmd[3]; + + dsp_cmd[0] = 0x0e; /* CSP set register */ + dsp_cmd[1] = reg; /* CSP Register */ + dsp_cmd[2] = val; /* value */ + return command_seq(chip, dsp_cmd, 3); +} + +/* + * read CSP register + * return < 0 -> error + */ +static int read_register(struct snd_sb *chip, unsigned char reg) +{ + unsigned char dsp_cmd[2]; + + dsp_cmd[0] = 0x0f; /* CSP read register */ + dsp_cmd[1] = reg; /* CSP Register */ + command_seq(chip, dsp_cmd, 2); + return snd_sbdsp_get_byte(chip); /* Read DSP value */ +} + +/* + * set CSP mode register + */ +static int set_mode_register(struct snd_sb *chip, unsigned char mode) +{ + unsigned char dsp_cmd[2]; + + dsp_cmd[0] = 0x04; /* CSP set mode register */ + dsp_cmd[1] = mode; /* mode */ + return command_seq(chip, dsp_cmd, 2); +} + +/* + * Detect CSP + * return 0 if CSP exists. + */ +static int csp_detect(struct snd_sb *chip, int *version) +{ + unsigned char csp_test1, csp_test2; + unsigned long flags; + int result = -ENODEV; + + spin_lock_irqsave(&chip->reg_lock, flags); + + set_codec_parameter(chip, 0x00, 0x00); + set_mode_register(chip, 0xfc); /* 0xfc = ?? */ + + csp_test1 = read_register(chip, 0x83); + set_register(chip, 0x83, ~csp_test1); + csp_test2 = read_register(chip, 0x83); + if (csp_test2 != (csp_test1 ^ 0xff)) + goto __fail; + + set_register(chip, 0x83, csp_test1); + csp_test2 = read_register(chip, 0x83); + if (csp_test2 != csp_test1) + goto __fail; + + set_mode_register(chip, 0x00); /* 0x00 = ? */ + + *version = get_version(chip); + snd_sbdsp_reset(chip); /* reset DSP after getversion! */ + if (*version >= 0x10 && *version <= 0x1f) + result = 0; /* valid version id */ + + __fail: + spin_unlock_irqrestore(&chip->reg_lock, flags); + return result; +} + +/* + * get CSP version number + */ +static int get_version(struct snd_sb *chip) +{ + unsigned char dsp_cmd[2]; + + dsp_cmd[0] = 0x08; /* SB_DSP_!something! */ + dsp_cmd[1] = 0x03; /* get chip version id? */ + command_seq(chip, dsp_cmd, 2); + + return (snd_sbdsp_get_byte(chip)); +} + +/* + * check if the CSP version is valid + */ +static int snd_sb_csp_check_version(struct snd_sb_csp * p) +{ + if (p->version < 0x10 || p->version > 0x1f) { + snd_printd("%s: Invalid CSP version: 0x%x\n", __func__, p->version); + return 1; + } + return 0; +} + +/* + * download microcode to CSP (microcode should have one "main" block). + */ +static int snd_sb_csp_load(struct snd_sb_csp * p, const unsigned char *buf, int size, int load_flags) +{ + int status, i; + int err; + int result = -EIO; + unsigned long flags; + + spin_lock_irqsave(&p->chip->reg_lock, flags); + snd_sbdsp_command(p->chip, 0x01); /* CSP download command */ + if (snd_sbdsp_get_byte(p->chip)) { + snd_printd("%s: Download command failed\n", __func__); + goto __fail; + } + /* Send CSP low byte (size - 1) */ + snd_sbdsp_command(p->chip, (unsigned char)(size - 1)); + /* Send high byte */ + snd_sbdsp_command(p->chip, (unsigned char)((size - 1) >> 8)); + /* send microcode sequence */ + /* load from kernel space */ + while (size--) { + if (!snd_sbdsp_command(p->chip, *buf++)) + goto __fail; + } + if (snd_sbdsp_get_byte(p->chip)) + goto __fail; + + if (load_flags & SNDRV_SB_CSP_LOAD_INITBLOCK) { + i = 0; + /* some codecs (FastSpeech) take some time to initialize */ + while (1) { + snd_sbdsp_command(p->chip, 0x03); + status = snd_sbdsp_get_byte(p->chip); + if (status == 0x55 || ++i >= 10) + break; + udelay (10); + } + if (status != 0x55) { + snd_printd("%s: Microcode initialization failed\n", __func__); + goto __fail; + } + } else { + /* + * Read mixer register SB_DSP4_DMASETUP after loading 'main' code. + * Start CSP chip if no 16bit DMA channel is set - some kind + * of autorun or perhaps a bugfix? + */ + spin_lock(&p->chip->mixer_lock); + status = snd_sbmixer_read(p->chip, SB_DSP4_DMASETUP); + spin_unlock(&p->chip->mixer_lock); + if (!(status & (SB_DMASETUP_DMA7 | SB_DMASETUP_DMA6 | SB_DMASETUP_DMA5))) { + err = (set_codec_parameter(p->chip, 0xaa, 0x00) || + set_codec_parameter(p->chip, 0xff, 0x00)); + snd_sbdsp_reset(p->chip); /* really! */ + if (err) + goto __fail; + set_mode_register(p->chip, 0xc0); /* c0 = STOP */ + set_mode_register(p->chip, 0x70); /* 70 = RUN */ + } + } + result = 0; + + __fail: + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + return result; +} + +static int snd_sb_csp_load_user(struct snd_sb_csp * p, const unsigned char __user *buf, int size, int load_flags) +{ + int err; + unsigned char *kbuf; + + kbuf = memdup_user(buf, size); + if (IS_ERR(kbuf)) + return PTR_ERR(kbuf); + + err = snd_sb_csp_load(p, kbuf, size, load_flags); + + kfree(kbuf); + return err; +} + +static int snd_sb_csp_firmware_load(struct snd_sb_csp *p, int index, int flags) +{ + static const char *const names[] = { + "sb16/mulaw_main.csp", + "sb16/alaw_main.csp", + "sb16/ima_adpcm_init.csp", + "sb16/ima_adpcm_playback.csp", + "sb16/ima_adpcm_capture.csp", + }; + const struct firmware *program; + + BUILD_BUG_ON(ARRAY_SIZE(names) != CSP_PROGRAM_COUNT); + program = p->csp_programs[index]; + if (!program) { + int err = request_firmware(&program, names[index], + p->chip->card->dev); + if (err < 0) + return err; + p->csp_programs[index] = program; + } + return snd_sb_csp_load(p, program->data, program->size, flags); +} + +/* + * autoload hardware codec if necessary + * return 0 if CSP is loaded and ready to run (p->running != 0) + */ +static int snd_sb_csp_autoload(struct snd_sb_csp * p, snd_pcm_format_t pcm_sfmt, int play_rec_mode) +{ + unsigned long flags; + int err = 0; + + /* if CSP is running or manually loaded then exit */ + if (p->running & (SNDRV_SB_CSP_ST_RUNNING | SNDRV_SB_CSP_ST_LOADED)) + return -EBUSY; + + /* autoload microcode only if requested hardware codec is not already loaded */ + if (((1U << (__force int)pcm_sfmt) & p->acc_format) && (play_rec_mode & p->mode)) { + p->running = SNDRV_SB_CSP_ST_AUTO; + } else { + switch (pcm_sfmt) { + case SNDRV_PCM_FORMAT_MU_LAW: + err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_MULAW, 0); + p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW; + p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE; + break; + case SNDRV_PCM_FORMAT_A_LAW: + err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ALAW, 0); + p->acc_format = SNDRV_PCM_FMTBIT_A_LAW; + p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE; + break; + case SNDRV_PCM_FORMAT_IMA_ADPCM: + err = snd_sb_csp_firmware_load(p, CSP_PROGRAM_ADPCM_INIT, + SNDRV_SB_CSP_LOAD_INITBLOCK); + if (err) + break; + if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) { + err = snd_sb_csp_firmware_load + (p, CSP_PROGRAM_ADPCM_PLAYBACK, 0); + p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE; + } else { + err = snd_sb_csp_firmware_load + (p, CSP_PROGRAM_ADPCM_CAPTURE, 0); + p->mode = SNDRV_SB_CSP_MODE_DSP_READ; + } + p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM; + break; + default: + /* Decouple CSP from IRQ and DMAREQ lines */ + if (p->running & SNDRV_SB_CSP_ST_AUTO) { + spin_lock_irqsave(&p->chip->reg_lock, flags); + set_mode_register(p->chip, 0xfc); + set_mode_register(p->chip, 0x00); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + p->running = 0; /* clear autoloaded flag */ + } + return -EINVAL; + } + if (err) { + p->acc_format = 0; + p->acc_channels = p->acc_width = p->acc_rates = 0; + + p->running = 0; /* clear autoloaded flag */ + p->mode = 0; + return (err); + } else { + p->running = SNDRV_SB_CSP_ST_AUTO; /* set autoloaded flag */ + p->acc_width = SNDRV_SB_CSP_SAMPLE_16BIT; /* only 16 bit data */ + p->acc_channels = SNDRV_SB_CSP_MONO | SNDRV_SB_CSP_STEREO; + p->acc_rates = SNDRV_SB_CSP_RATE_ALL; /* HW codecs accept all rates */ + } + + } + return (p->running & SNDRV_SB_CSP_ST_AUTO) ? 0 : -ENXIO; +} + +/* + * start CSP + */ +static int snd_sb_csp_start(struct snd_sb_csp * p, int sample_width, int channels) +{ + unsigned char s_type; /* sample type */ + unsigned char mixL, mixR; + int result = -EIO; + unsigned long flags; + + if (!(p->running & (SNDRV_SB_CSP_ST_LOADED | SNDRV_SB_CSP_ST_AUTO))) { + snd_printd("%s: Microcode not loaded\n", __func__); + return -ENXIO; + } + if (p->running & SNDRV_SB_CSP_ST_RUNNING) { + snd_printd("%s: CSP already running\n", __func__); + return -EBUSY; + } + if (!(sample_width & p->acc_width)) { + snd_printd("%s: Unsupported PCM sample width\n", __func__); + return -EINVAL; + } + if (!(channels & p->acc_channels)) { + snd_printd("%s: Invalid number of channels\n", __func__); + return -EINVAL; + } + + /* Mute PCM volume */ + spin_lock_irqsave(&p->chip->mixer_lock, flags); + mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV); + mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7); + spin_unlock_irqrestore(&p->chip->mixer_lock, flags); + + spin_lock(&p->chip->reg_lock); + set_mode_register(p->chip, 0xc0); /* c0 = STOP */ + set_mode_register(p->chip, 0x70); /* 70 = RUN */ + + s_type = 0x00; + if (channels == SNDRV_SB_CSP_MONO) + s_type = 0x11; /* 000n 000n (n = 1 if mono) */ + if (sample_width == SNDRV_SB_CSP_SAMPLE_8BIT) + s_type |= 0x22; /* 00dX 00dX (d = 1 if 8 bit samples) */ + + if (set_codec_parameter(p->chip, 0x81, s_type)) { + snd_printd("%s: Set sample type command failed\n", __func__); + goto __fail; + } + if (set_codec_parameter(p->chip, 0x80, 0x00)) { + snd_printd("%s: Codec start command failed\n", __func__); + goto __fail; + } + p->run_width = sample_width; + p->run_channels = channels; + + p->running |= SNDRV_SB_CSP_ST_RUNNING; + + if (p->mode & SNDRV_SB_CSP_MODE_QSOUND) { + set_codec_parameter(p->chip, 0xe0, 0x01); + /* enable QSound decoder */ + set_codec_parameter(p->chip, 0x00, 0xff); + set_codec_parameter(p->chip, 0x01, 0xff); + p->running |= SNDRV_SB_CSP_ST_QSOUND; + /* set QSound startup value */ + snd_sb_csp_qsound_transfer(p); + } + result = 0; + + __fail: + spin_unlock(&p->chip->reg_lock); + + /* restore PCM volume */ + spin_lock_irqsave(&p->chip->mixer_lock, flags); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR); + spin_unlock_irqrestore(&p->chip->mixer_lock, flags); + + return result; +} + +/* + * stop CSP + */ +static int snd_sb_csp_stop(struct snd_sb_csp * p) +{ + int result; + unsigned char mixL, mixR; + unsigned long flags; + + if (!(p->running & SNDRV_SB_CSP_ST_RUNNING)) + return 0; + + /* Mute PCM volume */ + spin_lock_irqsave(&p->chip->mixer_lock, flags); + mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV); + mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7); + spin_unlock_irqrestore(&p->chip->mixer_lock, flags); + + spin_lock(&p->chip->reg_lock); + if (p->running & SNDRV_SB_CSP_ST_QSOUND) { + set_codec_parameter(p->chip, 0xe0, 0x01); + /* disable QSound decoder */ + set_codec_parameter(p->chip, 0x00, 0x00); + set_codec_parameter(p->chip, 0x01, 0x00); + + p->running &= ~SNDRV_SB_CSP_ST_QSOUND; + } + result = set_mode_register(p->chip, 0xc0); /* c0 = STOP */ + spin_unlock(&p->chip->reg_lock); + + /* restore PCM volume */ + spin_lock_irqsave(&p->chip->mixer_lock, flags); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR); + spin_unlock_irqrestore(&p->chip->mixer_lock, flags); + + if (!(result)) + p->running &= ~(SNDRV_SB_CSP_ST_PAUSED | SNDRV_SB_CSP_ST_RUNNING); + return result; +} + +/* + * pause CSP codec and hold DMA transfer + */ +static int snd_sb_csp_pause(struct snd_sb_csp * p) +{ + int result; + unsigned long flags; + + if (!(p->running & SNDRV_SB_CSP_ST_RUNNING)) + return -EBUSY; + + spin_lock_irqsave(&p->chip->reg_lock, flags); + result = set_codec_parameter(p->chip, 0x80, 0xff); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + if (!(result)) + p->running |= SNDRV_SB_CSP_ST_PAUSED; + + return result; +} + +/* + * restart CSP codec and resume DMA transfer + */ +static int snd_sb_csp_restart(struct snd_sb_csp * p) +{ + int result; + unsigned long flags; + + if (!(p->running & SNDRV_SB_CSP_ST_PAUSED)) + return -EBUSY; + + spin_lock_irqsave(&p->chip->reg_lock, flags); + result = set_codec_parameter(p->chip, 0x80, 0x00); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + if (!(result)) + p->running &= ~SNDRV_SB_CSP_ST_PAUSED; + + return result; +} + +/* ------------------------------ */ + +/* + * QSound mixer control for PCM + */ + +#define snd_sb_qsound_switch_info snd_ctl_boolean_mono_info + +static int snd_sb_qsound_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0; + return 0; +} + +static int snd_sb_qsound_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval; + + nval = ucontrol->value.integer.value[0] & 0x01; + spin_lock_irqsave(&p->q_lock, flags); + change = p->q_enabled != nval; + p->q_enabled = nval; + spin_unlock_irqrestore(&p->q_lock, flags); + return change; +} + +static int snd_sb_qsound_space_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; + return 0; +} + +static int snd_sb_qsound_space_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&p->q_lock, flags); + ucontrol->value.integer.value[0] = p->qpos_left; + ucontrol->value.integer.value[1] = p->qpos_right; + spin_unlock_irqrestore(&p->q_lock, flags); + return 0; +} + +static int snd_sb_qsound_space_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb_csp *p = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval1, nval2; + + nval1 = ucontrol->value.integer.value[0]; + if (nval1 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT) + nval1 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; + nval2 = ucontrol->value.integer.value[1]; + if (nval2 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT) + nval2 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; + spin_lock_irqsave(&p->q_lock, flags); + change = p->qpos_left != nval1 || p->qpos_right != nval2; + p->qpos_left = nval1; + p->qpos_right = nval2; + p->qpos_changed = change; + spin_unlock_irqrestore(&p->q_lock, flags); + return change; +} + +static const struct snd_kcontrol_new snd_sb_qsound_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Switch", + .info = snd_sb_qsound_switch_info, + .get = snd_sb_qsound_switch_get, + .put = snd_sb_qsound_switch_put +}; + +static const struct snd_kcontrol_new snd_sb_qsound_space = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Space", + .info = snd_sb_qsound_space_info, + .get = snd_sb_qsound_space_get, + .put = snd_sb_qsound_space_put +}; + +static int snd_sb_qsound_build(struct snd_sb_csp * p) +{ + struct snd_card *card; + int err; + + if (snd_BUG_ON(!p)) + return -EINVAL; + + card = p->chip->card; + p->qpos_left = p->qpos_right = SNDRV_SB_CSP_QSOUND_MAX_RIGHT / 2; + p->qpos_changed = 0; + + spin_lock_init(&p->q_lock); + + if ((err = snd_ctl_add(card, p->qsound_switch = snd_ctl_new1(&snd_sb_qsound_switch, p))) < 0) { + p->qsound_switch = NULL; + goto __error; + } + if ((err = snd_ctl_add(card, p->qsound_space = snd_ctl_new1(&snd_sb_qsound_space, p))) < 0) { + p->qsound_space = NULL; + goto __error; + } + + return 0; + + __error: + snd_sb_qsound_destroy(p); + return err; +} + +static void snd_sb_qsound_destroy(struct snd_sb_csp * p) +{ + struct snd_card *card; + unsigned long flags; + + if (snd_BUG_ON(!p)) + return; + + card = p->chip->card; + + down_write(&card->controls_rwsem); + if (p->qsound_switch) { + snd_ctl_remove(card, p->qsound_switch); + p->qsound_switch = NULL; + } + if (p->qsound_space) { + snd_ctl_remove(card, p->qsound_space); + p->qsound_space = NULL; + } + up_write(&card->controls_rwsem); + + /* cancel pending transfer of QSound parameters */ + spin_lock_irqsave (&p->q_lock, flags); + p->qpos_changed = 0; + spin_unlock_irqrestore (&p->q_lock, flags); +} + +/* + * Transfer qsound parameters to CSP, + * function should be called from interrupt routine + */ +static int snd_sb_csp_qsound_transfer(struct snd_sb_csp * p) +{ + int err = -ENXIO; + + spin_lock(&p->q_lock); + if (p->running & SNDRV_SB_CSP_ST_QSOUND) { + set_codec_parameter(p->chip, 0xe0, 0x01); + /* left channel */ + set_codec_parameter(p->chip, 0x00, p->qpos_left); + set_codec_parameter(p->chip, 0x02, 0x00); + /* right channel */ + set_codec_parameter(p->chip, 0x00, p->qpos_right); + set_codec_parameter(p->chip, 0x03, 0x00); + err = 0; + } + p->qpos_changed = 0; + spin_unlock(&p->q_lock); + return err; +} + +/* ------------------------------ */ + +/* + * proc interface + */ +static int init_proc_entry(struct snd_sb_csp * p, int device) +{ + char name[16]; + struct snd_info_entry *entry; + sprintf(name, "cspD%d", device); + if (! snd_card_proc_new(p->chip->card, name, &entry)) + snd_info_set_text_ops(entry, p, info_read); + return 0; +} + +static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_sb_csp *p = entry->private_data; + + snd_iprintf(buffer, "Creative Signal Processor [v%d.%d]\n", (p->version >> 4), (p->version & 0x0f)); + snd_iprintf(buffer, "State: %cx%c%c%c\n", ((p->running & SNDRV_SB_CSP_ST_QSOUND) ? 'Q' : '-'), + ((p->running & SNDRV_SB_CSP_ST_PAUSED) ? 'P' : '-'), + ((p->running & SNDRV_SB_CSP_ST_RUNNING) ? 'R' : '-'), + ((p->running & SNDRV_SB_CSP_ST_LOADED) ? 'L' : '-')); + if (p->running & SNDRV_SB_CSP_ST_LOADED) { + snd_iprintf(buffer, "Codec: %s [func #%d]\n", p->codec_name, p->func_nr); + snd_iprintf(buffer, "Sample rates: "); + if (p->acc_rates == SNDRV_SB_CSP_RATE_ALL) { + snd_iprintf(buffer, "All\n"); + } else { + snd_iprintf(buffer, "%s%s%s%s\n", + ((p->acc_rates & SNDRV_SB_CSP_RATE_8000) ? "8000Hz " : ""), + ((p->acc_rates & SNDRV_SB_CSP_RATE_11025) ? "11025Hz " : ""), + ((p->acc_rates & SNDRV_SB_CSP_RATE_22050) ? "22050Hz " : ""), + ((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : "")); + } + if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { + snd_iprintf(buffer, "QSound decoder %sabled\n", + p->q_enabled ? "en" : "dis"); + } else { + snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n", + p->acc_format, + ((p->acc_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? "16bit" : "-"), + ((p->acc_width & SNDRV_SB_CSP_SAMPLE_8BIT) ? "8bit" : "-"), + ((p->acc_channels & SNDRV_SB_CSP_MONO) ? "mono" : "-"), + ((p->acc_channels & SNDRV_SB_CSP_STEREO) ? "stereo" : "-"), + ((p->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) ? "playback" : "-"), + ((p->mode & SNDRV_SB_CSP_MODE_DSP_READ) ? "capture" : "-")); + } + } + if (p->running & SNDRV_SB_CSP_ST_AUTO) { + snd_iprintf(buffer, "Autoloaded Mu-Law, A-Law or Ima-ADPCM hardware codec\n"); + } + if (p->running & SNDRV_SB_CSP_ST_RUNNING) { + snd_iprintf(buffer, "Processing %dbit %s PCM samples\n", + ((p->run_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? 16 : 8), + ((p->run_channels & SNDRV_SB_CSP_MONO) ? "mono" : "stereo")); + } + if (p->running & SNDRV_SB_CSP_ST_QSOUND) { + snd_iprintf(buffer, "Qsound position: left = 0x%x, right = 0x%x\n", + p->qpos_left, p->qpos_right); + } +} + +/* */ + +EXPORT_SYMBOL(snd_sb_csp_new); diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c new file mode 100644 index 000000000..37e6ce7b0 --- /dev/null +++ b/sound/isa/sb/sb16_main.c @@ -0,0 +1,902 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of 16-bit SoundBlaster cards and clones + * Note: This is very ugly hardware which uses one 8-bit DMA channel and + * second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't + * transfer 16-bit samples and 16-bit DMA channels can't transfer + * 8-bit samples. This make full duplex more complicated than + * can be... People, don't buy these soundcards for full 16-bit + * duplex!!! + * Note: 16-bit wide is assigned to first direction which made request. + * With full duplex - playback is preferred with abstract layer. + * + * Note: Some chip revisions have hardware bug. Changing capture + * channel from full-duplex 8bit DMA to 16bit DMA will block + * 16bit DMA transfers from DSP chip (capture) until 8bit transfer + * to DSP chip (playback) starts. This bug can be avoided with + * "16bit DMA Allocation" setting set to Playback or Capture. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones"); +MODULE_LICENSE("GPL"); + +#define runtime_format_bits(runtime) \ + ((unsigned int)pcm_format_to_bits((runtime)->format)) + +#ifdef CONFIG_SND_SB16_CSP +static void snd_sb16_csp_playback_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime) +{ + if (chip->hardware == SB_HW_16CSP) { + struct snd_sb_csp *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) && + (runtime_format_bits(runtime) == csp->acc_format)) { + /* Supported runtime PCM format for playback */ + if (csp->ops.csp_use(csp) == 0) { + /* If CSP was successfully acquired */ + goto __start_CSP; + } + } else if ((csp->mode & SNDRV_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) { + /* QSound decoder is loaded and enabled */ + if (runtime_format_bits(runtime) & (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)) { + /* Only for simple PCM formats */ + if (csp->ops.csp_use(csp) == 0) { + /* If CSP was successfully acquired */ + goto __start_CSP; + } + } + } + } else if (csp->ops.csp_use(csp) == 0) { + /* Acquire CSP and try to autoload hardware codec */ + if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_WRITE)) { + /* Unsupported format, release CSP */ + csp->ops.csp_unuse(csp); + } else { + __start_CSP: + /* Try to start CSP */ + if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_PLAYBACK_16) ? + SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT, + (runtime->channels > 1) ? + SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) { + /* Failed, release CSP */ + csp->ops.csp_unuse(csp); + } else { + /* Success, CSP acquired and running */ + chip->open = SNDRV_SB_CSP_MODE_DSP_WRITE; + } + } + } + } +} + +static void snd_sb16_csp_capture_prepare(struct snd_sb *chip, struct snd_pcm_runtime *runtime) +{ + if (chip->hardware == SB_HW_16CSP) { + struct snd_sb_csp *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) && + (runtime_format_bits(runtime) == csp->acc_format)) { + /* Supported runtime PCM format for capture */ + if (csp->ops.csp_use(csp) == 0) { + /* If CSP was successfully acquired */ + goto __start_CSP; + } + } + } else if (csp->ops.csp_use(csp) == 0) { + /* Acquire CSP and try to autoload hardware codec */ + if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_READ)) { + /* Unsupported format, release CSP */ + csp->ops.csp_unuse(csp); + } else { + __start_CSP: + /* Try to start CSP */ + if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_CAPTURE_16) ? + SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT, + (runtime->channels > 1) ? + SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) { + /* Failed, release CSP */ + csp->ops.csp_unuse(csp); + } else { + /* Success, CSP acquired and running */ + chip->open = SNDRV_SB_CSP_MODE_DSP_READ; + } + } + } + } +} + +static void snd_sb16_csp_update(struct snd_sb *chip) +{ + if (chip->hardware == SB_HW_16CSP) { + struct snd_sb_csp *csp = chip->csp; + + if (csp->qpos_changed) { + spin_lock(&chip->reg_lock); + csp->ops.csp_qsound_transfer (csp); + spin_unlock(&chip->reg_lock); + } + } +} + +static void snd_sb16_csp_playback_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime) +{ + /* CSP decoders (QSound excluded) support only 16bit transfers */ + if (chip->hardware == SB_HW_16CSP) { + struct snd_sb_csp *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if (csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) { + runtime->hw.formats |= csp->acc_format; + } + } else { + /* autoloaded codecs */ + runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_IMA_ADPCM; + } + } +} + +static void snd_sb16_csp_playback_close(struct snd_sb *chip) +{ + if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) { + struct snd_sb_csp *csp = chip->csp; + + if (csp->ops.csp_stop(csp) == 0) { + csp->ops.csp_unuse(csp); + chip->open = 0; + } + } +} + +static void snd_sb16_csp_capture_open(struct snd_sb *chip, struct snd_pcm_runtime *runtime) +{ + /* CSP coders support only 16bit transfers */ + if (chip->hardware == SB_HW_16CSP) { + struct snd_sb_csp *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if (csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) { + runtime->hw.formats |= csp->acc_format; + } + } else { + /* autoloaded codecs */ + runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_IMA_ADPCM; + } + } +} + +static void snd_sb16_csp_capture_close(struct snd_sb *chip) +{ + if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) { + struct snd_sb_csp *csp = chip->csp; + + if (csp->ops.csp_stop(csp) == 0) { + csp->ops.csp_unuse(csp); + chip->open = 0; + } + } +} +#else +#define snd_sb16_csp_playback_prepare(chip, runtime) /*nop*/ +#define snd_sb16_csp_capture_prepare(chip, runtime) /*nop*/ +#define snd_sb16_csp_update(chip) /*nop*/ +#define snd_sb16_csp_playback_open(chip, runtime) /*nop*/ +#define snd_sb16_csp_playback_close(chip) /*nop*/ +#define snd_sb16_csp_capture_open(chip, runtime) /*nop*/ +#define snd_sb16_csp_capture_close(chip) /*nop*/ +#endif + + +static void snd_sb16_setup_rate(struct snd_sb *chip, + unsigned short rate, + int channel) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->mode & (channel == SNDRV_PCM_STREAM_PLAYBACK ? SB_MODE_PLAYBACK_16 : SB_MODE_CAPTURE_16)) + snd_sb_ack_16bit(chip); + else + snd_sb_ack_8bit(chip); + if (!(chip->mode & SB_RATE_LOCK)) { + chip->locked_rate = rate; + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_IN); + snd_sbdsp_command(chip, rate >> 8); + snd_sbdsp_command(chip, rate & 0xff); + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT); + snd_sbdsp_command(chip, rate >> 8); + snd_sbdsp_command(chip, rate & 0xff); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static int snd_sb16_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_sb16_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_sb16_playback_prepare(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned char format; + unsigned int size, count, dma; + + snd_sb16_csp_playback_prepare(chip, runtime); + if (snd_pcm_format_unsigned(runtime->format) > 0) { + format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO; + } else { + format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO; + } + + snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_PLAYBACK); + size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream); + dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16; + snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + + count = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->mode & SB_MODE_PLAYBACK_16) { + count >>= 1; + count--; + snd_sbdsp_command(chip, SB_DSP4_OUT16_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA16_OFF); + } else { + count--; + snd_sbdsp_command(chip, SB_DSP4_OUT8_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_sb16_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + int result = 0; + + spin_lock(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + chip->mode |= SB_RATE_LOCK_PLAYBACK; + snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF); + /* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */ + if (chip->mode & SB_RATE_LOCK_CAPTURE) + snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + chip->mode &= ~SB_RATE_LOCK_PLAYBACK; + break; + default: + result = -EINVAL; + } + spin_unlock(&chip->reg_lock); + return result; +} + +static int snd_sb16_capture_prepare(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned char format; + unsigned int size, count, dma; + + snd_sb16_csp_capture_prepare(chip, runtime); + if (snd_pcm_format_unsigned(runtime->format) > 0) { + format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO; + } else { + format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO; + } + snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_CAPTURE); + size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream); + dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16; + snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + + count = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->mode & SB_MODE_CAPTURE_16) { + count >>= 1; + count--; + snd_sbdsp_command(chip, SB_DSP4_IN16_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA16_OFF); + } else { + count--; + snd_sbdsp_command(chip, SB_DSP4_IN8_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_sb16_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + int result = 0; + + spin_lock(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + chip->mode |= SB_RATE_LOCK_CAPTURE; + snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF); + /* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */ + if (chip->mode & SB_RATE_LOCK_PLAYBACK) + snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + chip->mode &= ~SB_RATE_LOCK_CAPTURE; + break; + default: + result = -EINVAL; + } + spin_unlock(&chip->reg_lock); + return result; +} + +irqreturn_t snd_sb16dsp_interrupt(int irq, void *dev_id) +{ + struct snd_sb *chip = dev_id; + unsigned char status; + int ok; + + spin_lock(&chip->mixer_lock); + status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS); + spin_unlock(&chip->mixer_lock); + if ((status & SB_IRQTYPE_MPUIN) && chip->rmidi_callback) + chip->rmidi_callback(irq, chip->rmidi->private_data); + if (status & SB_IRQTYPE_8BIT) { + ok = 0; + if (chip->mode & SB_MODE_PLAYBACK_8) { + snd_pcm_period_elapsed(chip->playback_substream); + snd_sb16_csp_update(chip); + ok++; + } + if (chip->mode & SB_MODE_CAPTURE_8) { + snd_pcm_period_elapsed(chip->capture_substream); + ok++; + } + spin_lock(&chip->reg_lock); + if (!ok) + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + snd_sb_ack_8bit(chip); + spin_unlock(&chip->reg_lock); + } + if (status & SB_IRQTYPE_16BIT) { + ok = 0; + if (chip->mode & SB_MODE_PLAYBACK_16) { + snd_pcm_period_elapsed(chip->playback_substream); + snd_sb16_csp_update(chip); + ok++; + } + if (chip->mode & SB_MODE_CAPTURE_16) { + snd_pcm_period_elapsed(chip->capture_substream); + ok++; + } + spin_lock(&chip->reg_lock); + if (!ok) + snd_sbdsp_command(chip, SB_DSP_DMA16_OFF); + snd_sb_ack_16bit(chip); + spin_unlock(&chip->reg_lock); + } + return IRQ_HANDLED; +} + +/* + + */ + +static snd_pcm_uframes_t snd_sb16_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + unsigned int dma; + size_t ptr; + + dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16; + ptr = snd_dma_pointer(dma, chip->p_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_sb16_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + unsigned int dma; + size_t ptr; + + dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16; + ptr = snd_dma_pointer(dma, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static const struct snd_pcm_hardware snd_sb16_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = 0, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100, + .rate_min = 4000, + .rate_max = 44100, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_sb16_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = 0, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100, + .rate_min = 4000, + .rate_max = 44100, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * open/close + */ + +static int snd_sb16_playback_open(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->mode & SB_MODE_PLAYBACK) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + runtime->hw = snd_sb16_playback; + + /* skip if 16 bit DMA was reserved for capture */ + if (chip->force_mode16 & SB_MODE_CAPTURE_16) + goto __skip_16bit; + + if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_CAPTURE_16)) { + chip->mode |= SB_MODE_PLAYBACK_16; + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + /* Vibra16X hack */ + if (chip->dma16 <= 3) { + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + } else { + snd_sb16_csp_playback_open(chip, runtime); + } + goto __open_ok; + } + + __skip_16bit: + if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_CAPTURE_8)) { + chip->mode |= SB_MODE_PLAYBACK_8; + /* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */ + if (chip->dma16 < 0) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + chip->mode |= SB_MODE_PLAYBACK_16; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8; + } + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + goto __open_ok; + } + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + + __open_ok: + if (chip->hardware == SB_HW_ALS100) + runtime->hw.rate_max = 48000; + if (chip->hardware == SB_HW_CS5530) { + runtime->hw.buffer_bytes_max = 32 * 1024; + runtime->hw.periods_min = 2; + runtime->hw.rate_min = 44100; + } + if (chip->mode & SB_RATE_LOCK) + runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate; + chip->playback_substream = substream; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +static int snd_sb16_playback_close(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + + snd_sb16_csp_playback_close(chip); + spin_lock_irqsave(&chip->open_lock, flags); + chip->playback_substream = NULL; + chip->mode &= ~SB_MODE_PLAYBACK; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +static int snd_sb16_capture_open(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->mode & SB_MODE_CAPTURE) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + runtime->hw = snd_sb16_capture; + + /* skip if 16 bit DMA was reserved for playback */ + if (chip->force_mode16 & SB_MODE_PLAYBACK_16) + goto __skip_16bit; + + if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_16)) { + chip->mode |= SB_MODE_CAPTURE_16; + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + /* Vibra16X hack */ + if (chip->dma16 <= 3) { + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + } else { + snd_sb16_csp_capture_open(chip, runtime); + } + goto __open_ok; + } + + __skip_16bit: + if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_8)) { + chip->mode |= SB_MODE_CAPTURE_8; + /* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */ + if (chip->dma16 < 0) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + chip->mode |= SB_MODE_CAPTURE_16; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8; + } + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + goto __open_ok; + } + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + + __open_ok: + if (chip->hardware == SB_HW_ALS100) + runtime->hw.rate_max = 48000; + if (chip->hardware == SB_HW_CS5530) { + runtime->hw.buffer_bytes_max = 32 * 1024; + runtime->hw.periods_min = 2; + runtime->hw.rate_min = 44100; + } + if (chip->mode & SB_RATE_LOCK) + runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate; + chip->capture_substream = substream; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +static int snd_sb16_capture_close(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + + snd_sb16_csp_capture_close(chip); + spin_lock_irqsave(&chip->open_lock, flags); + chip->capture_substream = NULL; + chip->mode &= ~SB_MODE_CAPTURE; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +/* + * DMA control interface + */ + +static int snd_sb16_set_dma_mode(struct snd_sb *chip, int what) +{ + if (chip->dma8 < 0 || chip->dma16 < 0) { + if (snd_BUG_ON(what)) + return -EINVAL; + return 0; + } + if (what == 0) { + chip->force_mode16 = 0; + } else if (what == 1) { + chip->force_mode16 = SB_MODE_PLAYBACK_16; + } else if (what == 2) { + chip->force_mode16 = SB_MODE_CAPTURE_16; + } else { + return -EINVAL; + } + return 0; +} + +static int snd_sb16_get_dma_mode(struct snd_sb *chip) +{ + if (chip->dma8 < 0 || chip->dma16 < 0) + return 0; + switch (chip->force_mode16) { + case SB_MODE_PLAYBACK_16: + return 1; + case SB_MODE_CAPTURE_16: + return 2; + default: + return 0; + } +} + +static int snd_sb16_dma_control_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Auto", "Playback", "Capture" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_sb16_dma_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.enumerated.item[0] = snd_sb16_get_dma_mode(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_sb16_dma_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char nval, oval; + int change; + + if ((nval = ucontrol->value.enumerated.item[0]) > 2) + return -EINVAL; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = snd_sb16_get_dma_mode(chip); + change = nval != oval; + snd_sb16_set_dma_mode(chip, nval); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static const struct snd_kcontrol_new snd_sb16_dma_control = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "16-bit DMA Allocation", + .info = snd_sb16_dma_control_info, + .get = snd_sb16_dma_control_get, + .put = snd_sb16_dma_control_put +}; + +/* + * Initialization part + */ + +int snd_sb16dsp_configure(struct snd_sb * chip) +{ + unsigned long flags; + unsigned char irqreg = 0, dmareg = 0, mpureg; + unsigned char realirq, realdma, realmpureg; + /* note: mpu register should be present only on SB16 Vibra soundcards */ + + // printk(KERN_DEBUG "codec->irq=%i, codec->dma8=%i, codec->dma16=%i\n", chip->irq, chip->dma8, chip->dma16); + spin_lock_irqsave(&chip->mixer_lock, flags); + mpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP) & ~0x06; + spin_unlock_irqrestore(&chip->mixer_lock, flags); + switch (chip->irq) { + case 2: + case 9: + irqreg |= SB_IRQSETUP_IRQ9; + break; + case 5: + irqreg |= SB_IRQSETUP_IRQ5; + break; + case 7: + irqreg |= SB_IRQSETUP_IRQ7; + break; + case 10: + irqreg |= SB_IRQSETUP_IRQ10; + break; + default: + return -EINVAL; + } + if (chip->dma8 >= 0) { + switch (chip->dma8) { + case 0: + dmareg |= SB_DMASETUP_DMA0; + break; + case 1: + dmareg |= SB_DMASETUP_DMA1; + break; + case 3: + dmareg |= SB_DMASETUP_DMA3; + break; + default: + return -EINVAL; + } + } + if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) { + switch (chip->dma16) { + case 5: + dmareg |= SB_DMASETUP_DMA5; + break; + case 6: + dmareg |= SB_DMASETUP_DMA6; + break; + case 7: + dmareg |= SB_DMASETUP_DMA7; + break; + default: + return -EINVAL; + } + } + switch (chip->mpu_port) { + case 0x300: + mpureg |= 0x04; + break; + case 0x330: + mpureg |= 0x00; + break; + default: + mpureg |= 0x02; /* disable MPU */ + } + spin_lock_irqsave(&chip->mixer_lock, flags); + + snd_sbmixer_write(chip, SB_DSP4_IRQSETUP, irqreg); + realirq = snd_sbmixer_read(chip, SB_DSP4_IRQSETUP); + + snd_sbmixer_write(chip, SB_DSP4_DMASETUP, dmareg); + realdma = snd_sbmixer_read(chip, SB_DSP4_DMASETUP); + + snd_sbmixer_write(chip, SB_DSP4_MPUSETUP, mpureg); + realmpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP); + + spin_unlock_irqrestore(&chip->mixer_lock, flags); + if ((~realirq) & irqreg || (~realdma) & dmareg) { + snd_printk(KERN_ERR "SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port); + snd_printk(KERN_ERR "SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg); + snd_printk(KERN_ERR "SB16 [0x%lx]: got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg); + return -ENODEV; + } + return 0; +} + +static const struct snd_pcm_ops snd_sb16_playback_ops = { + .open = snd_sb16_playback_open, + .close = snd_sb16_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb16_hw_params, + .hw_free = snd_sb16_hw_free, + .prepare = snd_sb16_playback_prepare, + .trigger = snd_sb16_playback_trigger, + .pointer = snd_sb16_playback_pointer, +}; + +static const struct snd_pcm_ops snd_sb16_capture_ops = { + .open = snd_sb16_capture_open, + .close = snd_sb16_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb16_hw_params, + .hw_free = snd_sb16_hw_free, + .prepare = snd_sb16_capture_prepare, + .trigger = snd_sb16_capture_trigger, + .pointer = snd_sb16_capture_pointer, +}; + +int snd_sb16dsp_pcm(struct snd_sb *chip, int device) +{ + struct snd_card *card = chip->card; + struct snd_pcm *pcm; + int err; + + if ((err = snd_pcm_new(card, "SB16 DSP", device, 1, 1, &pcm)) < 0) + return err; + sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff); + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + pcm->private_data = chip; + chip->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb16_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb16_capture_ops); + + if (chip->dma16 >= 0 && chip->dma8 != chip->dma16) + snd_ctl_add(card, snd_ctl_new1(&snd_sb16_dma_control, chip)); + else + pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, 128*1024); + return 0; +} + +const struct snd_pcm_ops *snd_sb16dsp_get_pcm_ops(int direction) +{ + return direction == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_sb16_playback_ops : &snd_sb16_capture_ops; +} + +EXPORT_SYMBOL(snd_sb16dsp_pcm); +EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops); +EXPORT_SYMBOL(snd_sb16dsp_configure); +EXPORT_SYMBOL(snd_sb16dsp_interrupt); diff --git a/sound/isa/sb/sb8.c b/sound/isa/sb/sb8.c new file mode 100644 index 000000000..d77dcba27 --- /dev/null +++ b/sound/isa/sb/sb8.c @@ -0,0 +1,254 @@ +/* + * Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Sound Blaster 1.0/2.0/Pro"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 1.0/SB 2.0/SB Pro}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Sound Blaster soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Sound Blaster soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Sound Blaster soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for SB8 driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for SB8 driver."); +module_param_hw_array(dma8, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for SB8 driver."); + +struct snd_sb8 { + struct resource *fm_res; /* used to block FM i/o region for legacy cards */ + struct snd_sb *chip; +}; + +static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id) +{ + struct snd_sb *chip = dev_id; + + if (chip->open & SB_OPEN_PCM) { + return snd_sb8dsp_interrupt(chip); + } else { + return snd_sb8dsp_midi_interrupt(chip); + } +} + +static void snd_sb8_free(struct snd_card *card) +{ + struct snd_sb8 *acard = card->private_data; + + if (acard == NULL) + return; + release_and_free_resource(acard->fm_res); +} + +static int snd_sb8_match(struct device *pdev, unsigned int dev) +{ + if (!enable[dev]) + return 0; + if (irq[dev] == SNDRV_AUTO_IRQ) { + dev_err(pdev, "please specify irq\n"); + return 0; + } + if (dma8[dev] == SNDRV_AUTO_DMA) { + dev_err(pdev, "please specify dma8\n"); + return 0; + } + return 1; +} + +static int snd_sb8_probe(struct device *pdev, unsigned int dev) +{ + struct snd_sb *chip; + struct snd_card *card; + struct snd_sb8 *acard; + struct snd_opl3 *opl3; + int err; + + err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_sb8), &card); + if (err < 0) + return err; + acard = card->private_data; + card->private_free = snd_sb8_free; + + /* block the 0x388 port to avoid PnP conflicts */ + acard->fm_res = request_region(0x388, 4, "SoundBlaster FM"); + + if (port[dev] != SNDRV_AUTO_PORT) { + if ((err = snd_sbdsp_create(card, port[dev], irq[dev], + snd_sb8_interrupt, + dma8[dev], + -1, + SB_HW_AUTO, + &chip)) < 0) + goto _err; + } else { + /* auto-probe legacy ports */ + static unsigned long possible_ports[] = { + 0x220, 0x240, 0x260, + }; + int i; + for (i = 0; i < ARRAY_SIZE(possible_ports); i++) { + err = snd_sbdsp_create(card, possible_ports[i], + irq[dev], + snd_sb8_interrupt, + dma8[dev], + -1, + SB_HW_AUTO, + &chip); + if (err >= 0) { + port[dev] = possible_ports[i]; + break; + } + } + if (i >= ARRAY_SIZE(possible_ports)) { + err = -EINVAL; + goto _err; + } + } + acard->chip = chip; + + if (chip->hardware >= SB_HW_16) { + if (chip->hardware == SB_HW_ALS100) + snd_printk(KERN_WARNING "ALS100 chip detected at 0x%lx, try snd-als100 module\n", + port[dev]); + else + snd_printk(KERN_WARNING "SB 16 chip detected at 0x%lx, try snd-sb16 module\n", + port[dev]); + err = -ENODEV; + goto _err; + } + + if ((err = snd_sb8dsp_pcm(chip, 0)) < 0) + goto _err; + + if ((err = snd_sbmixer_new(chip)) < 0) + goto _err; + + if (chip->hardware == SB_HW_10 || chip->hardware == SB_HW_20) { + if ((err = snd_opl3_create(card, chip->port + 8, 0, + OPL3_HW_AUTO, 1, + &opl3)) < 0) { + snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx\n", chip->port + 8); + } + } else { + if ((err = snd_opl3_create(card, chip->port, chip->port + 2, + OPL3_HW_AUTO, 1, + &opl3)) < 0) { + snd_printk(KERN_WARNING "sb8: no OPL device at 0x%lx-0x%lx\n", + chip->port, chip->port + 2); + } + } + if (err >= 0) { + if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) + goto _err; + } + + if ((err = snd_sb8dsp_midi(chip, 0)) < 0) + goto _err; + + strcpy(card->driver, chip->hardware == SB_HW_PRO ? "SB Pro" : "SB8"); + strcpy(card->shortname, chip->name); + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + chip->name, + chip->port, + irq[dev], dma8[dev]); + + if ((err = snd_card_register(card)) < 0) + goto _err; + + dev_set_drvdata(pdev, card); + return 0; + + _err: + snd_card_free(card); + return err; +} + +static int snd_sb8_remove(struct device *pdev, unsigned int dev) +{ + snd_card_free(dev_get_drvdata(pdev)); + return 0; +} + +#ifdef CONFIG_PM +static int snd_sb8_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_sb8 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + snd_sbmixer_suspend(chip); + return 0; +} + +static int snd_sb8_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_sb8 *acard = card->private_data; + struct snd_sb *chip = acard->chip; + + snd_sbdsp_reset(chip); + snd_sbmixer_resume(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +#define DEV_NAME "sb8" + +static struct isa_driver snd_sb8_driver = { + .match = snd_sb8_match, + .probe = snd_sb8_probe, + .remove = snd_sb8_remove, +#ifdef CONFIG_PM + .suspend = snd_sb8_suspend, + .resume = snd_sb8_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + +module_isa_driver(snd_sb8_driver, SNDRV_CARDS); diff --git a/sound/isa/sb/sb8_main.c b/sound/isa/sb/sb8_main.c new file mode 100644 index 000000000..481797744 --- /dev/null +++ b/sound/isa/sb/sb8_main.c @@ -0,0 +1,623 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Uros Bizjak + * + * Routines for control of 8-bit SoundBlaster cards and clones + * Please note: I don't have access to old SB8 soundcards. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * -- + * + * Thu Apr 29 20:36:17 BST 1999 George David Morrison + * DSP can't respond to commands whilst in "high speed" mode. Caused + * glitching during playback. Fixed. + * + * Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak + * Cleaned up and rewrote lowlevel routines. + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela , Uros Bizjak "); +MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones"); +MODULE_LICENSE("GPL"); + +#define SB8_CLOCK 1000000 +#define SB8_DEN(v) ((SB8_CLOCK + (v) / 2) / (v)) +#define SB8_RATE(v) (SB8_CLOCK / SB8_DEN(v)) + +static const struct snd_ratnum clock = { + .num = SB8_CLOCK, + .den_min = 1, + .den_max = 256, + .den_step = 1, +}; + +static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clock = { + .nrats = 1, + .rats = &clock, +}; + +static const struct snd_ratnum stereo_clocks[] = { + { + .num = SB8_CLOCK, + .den_min = SB8_DEN(22050), + .den_max = SB8_DEN(22050), + .den_step = 1, + }, + { + .num = SB8_CLOCK, + .den_min = SB8_DEN(11025), + .den_max = SB8_DEN(11025), + .den_step = 1, + } +}; + +static int snd_sb8_hw_constraint_rate_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (c->min > 1) { + unsigned int num = 0, den = 0; + int err = snd_interval_ratnum(hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE), + 2, stereo_clocks, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + return err; + } + return 0; +} + +static int snd_sb8_hw_constraint_channels_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) { + struct snd_interval t = { .min = 1, .max = 1 }; + return snd_interval_refine(hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), &t); + } + return 0; +} + +static int snd_sb8_playback_prepare(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int mixreg, rate, size, count; + unsigned char format; + unsigned char stereo = runtime->channels > 1; + int dma; + + rate = runtime->rate; + switch (chip->hardware) { + case SB_HW_JAZZ16: + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) { + if (chip->mode & SB_MODE_CAPTURE_16) + return -EBUSY; + else + chip->mode |= SB_MODE_PLAYBACK_16; + } + chip->playback_format = SB_DSP_LO_OUTPUT_AUTO; + break; + case SB_HW_PRO: + if (runtime->channels > 1) { + if (snd_BUG_ON(rate != SB8_RATE(11025) && + rate != SB8_RATE(22050))) + return -EINVAL; + chip->playback_format = SB_DSP_HI_OUTPUT_AUTO; + break; + } + /* fallthru */ + case SB_HW_201: + if (rate > 23000) { + chip->playback_format = SB_DSP_HI_OUTPUT_AUTO; + break; + } + /* fallthru */ + case SB_HW_20: + chip->playback_format = SB_DSP_LO_OUTPUT_AUTO; + break; + case SB_HW_10: + chip->playback_format = SB_DSP_OUTPUT; + break; + default: + return -EINVAL; + } + if (chip->mode & SB_MODE_PLAYBACK_16) { + format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT; + dma = chip->dma16; + } else { + format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT; + chip->mode |= SB_MODE_PLAYBACK_8; + dma = chip->dma8; + } + size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream); + count = chip->p_period_size = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON); + if (chip->hardware == SB_HW_JAZZ16) + snd_sbdsp_command(chip, format); + else if (stereo) { + /* set playback stereo mode */ + spin_lock(&chip->mixer_lock); + mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW); + snd_sbmixer_write(chip, SB_DSP_STEREO_SW, mixreg | 0x02); + spin_unlock(&chip->mixer_lock); + + /* Soundblaster hardware programming reference guide, 3-23 */ + snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT); + runtime->dma_area[0] = 0x80; + snd_dma_program(dma, runtime->dma_addr, 1, DMA_MODE_WRITE); + /* force interrupt */ + snd_sbdsp_command(chip, SB_DSP_OUTPUT); + snd_sbdsp_command(chip, 0); + snd_sbdsp_command(chip, 0); + } + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE); + if (stereo) { + snd_sbdsp_command(chip, 256 - runtime->rate_den / 2); + spin_lock(&chip->mixer_lock); + /* save output filter status and turn it off */ + mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT); + snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, mixreg | 0x20); + spin_unlock(&chip->mixer_lock); + /* just use force_mode16 for temporary storate... */ + chip->force_mode16 = mixreg; + } else { + snd_sbdsp_command(chip, 256 - runtime->rate_den); + } + if (chip->playback_format != SB_DSP_OUTPUT) { + if (chip->mode & SB_MODE_PLAYBACK_16) + count /= 2; + count--; + snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_dma_program(dma, runtime->dma_addr, + size, DMA_MODE_WRITE | DMA_AUTOINIT); + return 0; +} + +static int snd_sb8_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + unsigned int count; + + spin_lock_irqsave(&chip->reg_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_sbdsp_command(chip, chip->playback_format); + if (chip->playback_format == SB_DSP_OUTPUT) { + count = chip->p_period_size - 1; + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) { + struct snd_pcm_runtime *runtime = substream->runtime; + snd_sbdsp_reset(chip); + if (runtime->channels > 1) { + spin_lock(&chip->mixer_lock); + /* restore output filter and set hardware to mono mode */ + snd_sbmixer_write(chip, SB_DSP_STEREO_SW, chip->force_mode16 & ~0x02); + spin_unlock(&chip->mixer_lock); + } + } else { + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_sb8_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_sb8_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_sb8_capture_prepare(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int mixreg, rate, size, count; + unsigned char format; + unsigned char stereo = runtime->channels > 1; + int dma; + + rate = runtime->rate; + switch (chip->hardware) { + case SB_HW_JAZZ16: + if (runtime->format == SNDRV_PCM_FORMAT_S16_LE) { + if (chip->mode & SB_MODE_PLAYBACK_16) + return -EBUSY; + else + chip->mode |= SB_MODE_CAPTURE_16; + } + chip->capture_format = SB_DSP_LO_INPUT_AUTO; + break; + case SB_HW_PRO: + if (runtime->channels > 1) { + if (snd_BUG_ON(rate != SB8_RATE(11025) && + rate != SB8_RATE(22050))) + return -EINVAL; + chip->capture_format = SB_DSP_HI_INPUT_AUTO; + break; + } + chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO; + break; + case SB_HW_201: + if (rate > 13000) { + chip->capture_format = SB_DSP_HI_INPUT_AUTO; + break; + } + /* fallthru */ + case SB_HW_20: + chip->capture_format = SB_DSP_LO_INPUT_AUTO; + break; + case SB_HW_10: + chip->capture_format = SB_DSP_INPUT; + break; + default: + return -EINVAL; + } + if (chip->mode & SB_MODE_CAPTURE_16) { + format = stereo ? SB_DSP_STEREO_16BIT : SB_DSP_MONO_16BIT; + dma = chip->dma16; + } else { + format = stereo ? SB_DSP_STEREO_8BIT : SB_DSP_MONO_8BIT; + chip->mode |= SB_MODE_CAPTURE_8; + dma = chip->dma8; + } + size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream); + count = chip->c_period_size = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF); + if (chip->hardware == SB_HW_JAZZ16) + snd_sbdsp_command(chip, format); + else if (stereo) + snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT); + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE); + if (stereo) { + snd_sbdsp_command(chip, 256 - runtime->rate_den / 2); + spin_lock(&chip->mixer_lock); + /* save input filter status and turn it off */ + mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT); + snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, mixreg | 0x20); + spin_unlock(&chip->mixer_lock); + /* just use force_mode16 for temporary storate... */ + chip->force_mode16 = mixreg; + } else { + snd_sbdsp_command(chip, 256 - runtime->rate_den); + } + if (chip->capture_format != SB_DSP_INPUT) { + if (chip->mode & SB_MODE_PLAYBACK_16) + count /= 2; + count--; + snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_dma_program(dma, runtime->dma_addr, + size, DMA_MODE_READ | DMA_AUTOINIT); + return 0; +} + +static int snd_sb8_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + unsigned int count; + + spin_lock_irqsave(&chip->reg_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_sbdsp_command(chip, chip->capture_format); + if (chip->capture_format == SB_DSP_INPUT) { + count = chip->c_period_size - 1; + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) { + struct snd_pcm_runtime *runtime = substream->runtime; + snd_sbdsp_reset(chip); + if (runtime->channels > 1) { + /* restore input filter status */ + spin_lock(&chip->mixer_lock); + snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, chip->force_mode16); + spin_unlock(&chip->mixer_lock); + /* set hardware to mono mode */ + snd_sbdsp_command(chip, SB_DSP_MONO_8BIT); + } + } else { + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +irqreturn_t snd_sb8dsp_interrupt(struct snd_sb *chip) +{ + struct snd_pcm_substream *substream; + + snd_sb_ack_8bit(chip); + switch (chip->mode) { + case SB_MODE_PLAYBACK_16: /* ok.. playback is active */ + if (chip->hardware != SB_HW_JAZZ16) + break; + /* fallthru */ + case SB_MODE_PLAYBACK_8: + substream = chip->playback_substream; + if (chip->playback_format == SB_DSP_OUTPUT) + snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START); + snd_pcm_period_elapsed(substream); + break; + case SB_MODE_CAPTURE_16: + if (chip->hardware != SB_HW_JAZZ16) + break; + /* fallthru */ + case SB_MODE_CAPTURE_8: + substream = chip->capture_substream; + if (chip->capture_format == SB_DSP_INPUT) + snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START); + snd_pcm_period_elapsed(substream); + break; + } + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_sb8_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + size_t ptr; + int dma; + + if (chip->mode & SB_MODE_PLAYBACK_8) + dma = chip->dma8; + else if (chip->mode & SB_MODE_PLAYBACK_16) + dma = chip->dma16; + else + return 0; + ptr = snd_dma_pointer(dma, chip->p_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_sb8_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + size_t ptr; + int dma; + + if (chip->mode & SB_MODE_CAPTURE_8) + dma = chip->dma8; + else if (chip->mode & SB_MODE_CAPTURE_16) + dma = chip->dma16; + else + return 0; + ptr = snd_dma_pointer(dma, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static const struct snd_pcm_hardware snd_sb8_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050), + .rate_min = 4000, + .rate_max = 23000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_sb8_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_11025), + .rate_min = 4000, + .rate_max = 13000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * + */ + +static int snd_sb8_open(struct snd_pcm_substream *substream) +{ + struct snd_sb *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->open) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + chip->open |= SB_OPEN_PCM; + spin_unlock_irqrestore(&chip->open_lock, flags); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + chip->playback_substream = substream; + runtime->hw = snd_sb8_playback; + } else { + chip->capture_substream = substream; + runtime->hw = snd_sb8_capture; + } + switch (chip->hardware) { + case SB_HW_JAZZ16: + if (chip->dma16 == 5 || chip->dma16 == 7) + runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE; + runtime->hw.rates |= SNDRV_PCM_RATE_8000_48000; + runtime->hw.rate_min = 4000; + runtime->hw.rate_max = 50000; + runtime->hw.channels_max = 2; + break; + case SB_HW_PRO: + runtime->hw.rate_max = 44100; + runtime->hw.channels_max = 2; + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_sb8_hw_constraint_rate_channels, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_sb8_hw_constraint_channels_rate, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + break; + case SB_HW_201: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.rate_max = 44100; + } else { + runtime->hw.rate_max = 15000; + } + default: + break; + } + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clock); + if (chip->dma8 > 3 || chip->dma16 >= 0) { + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 2); + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 2); + runtime->hw.buffer_bytes_max = 128 * 1024 * 1024; + runtime->hw.period_bytes_max = 128 * 1024 * 1024; + } + return 0; +} + +static int snd_sb8_close(struct snd_pcm_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + chip->capture_substream = NULL; + spin_lock_irqsave(&chip->open_lock, flags); + chip->open &= ~SB_OPEN_PCM; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + chip->mode &= ~SB_MODE_PLAYBACK; + else + chip->mode &= ~SB_MODE_CAPTURE; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +/* + * Initialization part + */ + +static const struct snd_pcm_ops snd_sb8_playback_ops = { + .open = snd_sb8_open, + .close = snd_sb8_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb8_hw_params, + .hw_free = snd_sb8_hw_free, + .prepare = snd_sb8_playback_prepare, + .trigger = snd_sb8_playback_trigger, + .pointer = snd_sb8_playback_pointer, +}; + +static const struct snd_pcm_ops snd_sb8_capture_ops = { + .open = snd_sb8_open, + .close = snd_sb8_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb8_hw_params, + .hw_free = snd_sb8_hw_free, + .prepare = snd_sb8_capture_prepare, + .trigger = snd_sb8_capture_trigger, + .pointer = snd_sb8_capture_pointer, +}; + +int snd_sb8dsp_pcm(struct snd_sb *chip, int device) +{ + struct snd_card *card = chip->card; + struct snd_pcm *pcm; + int err; + size_t max_prealloc = 64 * 1024; + + if ((err = snd_pcm_new(card, "SB8 DSP", device, 1, 1, &pcm)) < 0) + return err; + sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff); + pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + pcm->private_data = chip; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb8_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb8_capture_ops); + + if (chip->dma8 > 3 || chip->dma16 >= 0) + max_prealloc = 128 * 1024; + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, max_prealloc); + + return 0; +} + +EXPORT_SYMBOL(snd_sb8dsp_pcm); +EXPORT_SYMBOL(snd_sb8dsp_interrupt); + /* sb8_midi.c */ +EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt); +EXPORT_SYMBOL(snd_sb8dsp_midi); diff --git a/sound/isa/sb/sb8_midi.c b/sound/isa/sb/sb8_midi.c new file mode 100644 index 000000000..4affdcb78 --- /dev/null +++ b/sound/isa/sb/sb8_midi.c @@ -0,0 +1,279 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of SoundBlaster cards - MIDI interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * -- + * + * Sun May 9 22:54:38 BST 1999 George David Morrison + * Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from + * working. + * + * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch + * Added full duplex UART mode for DSP version 2.0 and later. + */ + +#include +#include +#include +#include + + +irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip) +{ + struct snd_rawmidi *rmidi; + int max = 64; + char byte; + + if (!chip) + return IRQ_NONE; + + rmidi = chip->rmidi; + if (!rmidi) { + inb(SBP(chip, DATA_AVAIL)); /* ack interrupt */ + return IRQ_NONE; + } + + spin_lock(&chip->midi_input_lock); + while (max-- > 0) { + if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { + byte = inb(SBP(chip, READ)); + if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) { + snd_rawmidi_receive(chip->midi_substream_input, &byte, 1); + } + } + } + spin_unlock(&chip->midi_input_lock); + return IRQ_HANDLED; +} + +static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip; + unsigned int valid_open_flags; + + chip = substream->rmidi->private_data; + valid_open_flags = chip->hardware >= SB_HW_20 + ? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0; + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->open & ~valid_open_flags) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + chip->open |= SB_OPEN_MIDI_INPUT; + chip->midi_substream_input = substream; + if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + if (chip->hardware >= SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ); + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip; + unsigned int valid_open_flags; + + chip = substream->rmidi->private_data; + valid_open_flags = chip->hardware >= SB_HW_20 + ? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0; + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->open & ~valid_open_flags) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + chip->open |= SB_OPEN_MIDI_OUTPUT; + chip->midi_substream_output = substream; + if (!(chip->open & SB_OPEN_MIDI_INPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + if (chip->hardware >= SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ); + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip; + + chip = substream->rmidi->private_data; + spin_lock_irqsave(&chip->open_lock, flags); + chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER); + chip->midi_substream_input = NULL; + if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip; + + chip = substream->rmidi->private_data; + del_timer_sync(&chip->midi_timer); + spin_lock_irqsave(&chip->open_lock, flags); + chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER); + chip->midi_substream_output = NULL; + if (!(chip->open & SB_OPEN_MIDI_INPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_sb *chip; + + chip = substream->rmidi->private_data; + spin_lock_irqsave(&chip->open_lock, flags); + if (up) { + if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) { + if (chip->hardware < SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ); + chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER; + } + } else { + if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) { + if (chip->hardware < SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ); + chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER; + } + } + spin_unlock_irqrestore(&chip->open_lock, flags); +} + +static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_sb *chip; + char byte; + int max = 32; + + /* how big is Tx FIFO? */ + chip = substream->rmidi->private_data; + while (max-- > 0) { + spin_lock_irqsave(&chip->open_lock, flags); + if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) { + chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER; + del_timer(&chip->midi_timer); + spin_unlock_irqrestore(&chip->open_lock, flags); + break; + } + if (chip->hardware >= SB_HW_20) { + int timeout = 8; + while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0) + ; + if (timeout == 0) { + /* Tx FIFO full - try again later */ + spin_unlock_irqrestore(&chip->open_lock, flags); + break; + } + outb(byte, SBP(chip, WRITE)); + } else { + snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT); + snd_sbdsp_command(chip, byte); + } + snd_rawmidi_transmit_ack(substream, 1); + spin_unlock_irqrestore(&chip->open_lock, flags); + } +} + +static void snd_sb8dsp_midi_output_timer(struct timer_list *t) +{ + struct snd_sb *chip = from_timer(chip, t, midi_timer); + struct snd_rawmidi_substream *substream = chip->midi_substream_output; + unsigned long flags; + + spin_lock_irqsave(&chip->open_lock, flags); + mod_timer(&chip->midi_timer, 1 + jiffies); + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sb8dsp_midi_output_write(substream); +} + +static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_sb *chip; + + chip = substream->rmidi->private_data; + spin_lock_irqsave(&chip->open_lock, flags); + if (up) { + if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) { + mod_timer(&chip->midi_timer, 1 + jiffies); + chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER; + } + } else { + if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) { + chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER; + } + } + spin_unlock_irqrestore(&chip->open_lock, flags); + + if (up) + snd_sb8dsp_midi_output_write(substream); +} + +static const struct snd_rawmidi_ops snd_sb8dsp_midi_output = +{ + .open = snd_sb8dsp_midi_output_open, + .close = snd_sb8dsp_midi_output_close, + .trigger = snd_sb8dsp_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_sb8dsp_midi_input = +{ + .open = snd_sb8dsp_midi_input_open, + .close = snd_sb8dsp_midi_input_close, + .trigger = snd_sb8dsp_midi_input_trigger, +}; + +int snd_sb8dsp_midi(struct snd_sb *chip, int device) +{ + struct snd_rawmidi *rmidi; + int err; + + if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0) + return err; + strcpy(rmidi->name, "SB8 MIDI"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT; + if (chip->hardware >= SB_HW_20) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = chip; + timer_setup(&chip->midi_timer, snd_sb8dsp_midi_output_timer, 0); + chip->rmidi = rmidi; + return 0; +} diff --git a/sound/isa/sb/sb_common.c b/sound/isa/sb/sb_common.c new file mode 100644 index 000000000..90b254aae --- /dev/null +++ b/sound/isa/sb/sb_common.c @@ -0,0 +1,307 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Uros Bizjak + * + * Lowlevel routines for control of Sound Blaster cards + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards"); +MODULE_LICENSE("GPL"); + +#define BUSY_LOOPS 100000 + +#undef IO_DEBUG + +int snd_sbdsp_command(struct snd_sb *chip, unsigned char val) +{ + int i; +#ifdef IO_DEBUG + snd_printk(KERN_DEBUG "command 0x%x\n", val); +#endif + for (i = BUSY_LOOPS; i; i--) + if ((inb(SBP(chip, STATUS)) & 0x80) == 0) { + outb(val, SBP(chip, COMMAND)); + return 1; + } + snd_printd("%s [0x%lx]: timeout (0x%x)\n", __func__, chip->port, val); + return 0; +} + +int snd_sbdsp_get_byte(struct snd_sb *chip) +{ + int val; + int i; + for (i = BUSY_LOOPS; i; i--) { + if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { + val = inb(SBP(chip, READ)); +#ifdef IO_DEBUG + snd_printk(KERN_DEBUG "get_byte 0x%x\n", val); +#endif + return val; + } + } + snd_printd("%s [0x%lx]: timeout\n", __func__, chip->port); + return -ENODEV; +} + +int snd_sbdsp_reset(struct snd_sb *chip) +{ + int i; + + outb(1, SBP(chip, RESET)); + udelay(10); + outb(0, SBP(chip, RESET)); + udelay(30); + for (i = BUSY_LOOPS; i; i--) + if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { + if (inb(SBP(chip, READ)) == 0xaa) + return 0; + else + break; + } + snd_printdd("%s [0x%lx] failed...\n", __func__, chip->port); + return -ENODEV; +} + +static int snd_sbdsp_version(struct snd_sb * chip) +{ + unsigned int result = -ENODEV; + + snd_sbdsp_command(chip, SB_DSP_GET_VERSION); + result = (short) snd_sbdsp_get_byte(chip) << 8; + result |= (short) snd_sbdsp_get_byte(chip); + return result; +} + +static int snd_sbdsp_probe(struct snd_sb * chip) +{ + int version; + int major, minor; + char *str; + unsigned long flags; + + /* + * initialization sequence + */ + + spin_lock_irqsave(&chip->reg_lock, flags); + if (snd_sbdsp_reset(chip) < 0) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -ENODEV; + } + version = snd_sbdsp_version(chip); + if (version < 0) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -ENODEV; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + major = version >> 8; + minor = version & 0xff; + snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n", + chip->port, major, minor); + + switch (chip->hardware) { + case SB_HW_AUTO: + switch (major) { + case 1: + chip->hardware = SB_HW_10; + str = "1.0"; + break; + case 2: + if (minor) { + chip->hardware = SB_HW_201; + str = "2.01+"; + } else { + chip->hardware = SB_HW_20; + str = "2.0"; + } + break; + case 3: + chip->hardware = SB_HW_PRO; + str = "Pro"; + break; + case 4: + chip->hardware = SB_HW_16; + str = "16"; + break; + default: + snd_printk(KERN_INFO "SB [0x%lx]: unknown DSP chip version %i.%i\n", + chip->port, major, minor); + return -ENODEV; + } + break; + case SB_HW_ALS100: + str = "16 (ALS-100)"; + break; + case SB_HW_ALS4000: + str = "16 (ALS-4000)"; + break; + case SB_HW_DT019X: + str = "(DT019X/ALS007)"; + break; + case SB_HW_CS5530: + str = "16 (CS5530)"; + break; + case SB_HW_JAZZ16: + str = "Pro (Jazz16)"; + break; + default: + return -ENODEV; + } + sprintf(chip->name, "Sound Blaster %s", str); + chip->version = (major << 8) | minor; + return 0; +} + +static int snd_sbdsp_free(struct snd_sb *chip) +{ + release_and_free_resource(chip->res_port); + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); +#ifdef CONFIG_ISA + if (chip->dma8 >= 0) { + disable_dma(chip->dma8); + free_dma(chip->dma8); + } + if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) { + disable_dma(chip->dma16); + free_dma(chip->dma16); + } +#endif + kfree(chip); + return 0; +} + +static int snd_sbdsp_dev_free(struct snd_device *device) +{ + struct snd_sb *chip = device->device_data; + return snd_sbdsp_free(chip); +} + +int snd_sbdsp_create(struct snd_card *card, + unsigned long port, + int irq, + irq_handler_t irq_handler, + int dma8, + int dma16, + unsigned short hardware, + struct snd_sb **r_chip) +{ + struct snd_sb *chip; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_sbdsp_dev_free, + }; + + if (snd_BUG_ON(!r_chip)) + return -EINVAL; + *r_chip = NULL; + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->open_lock); + spin_lock_init(&chip->midi_input_lock); + spin_lock_init(&chip->mixer_lock); + chip->irq = -1; + chip->dma8 = -1; + chip->dma16 = -1; + chip->port = port; + + if (request_irq(irq, irq_handler, + (hardware == SB_HW_ALS4000 || + hardware == SB_HW_CS5530) ? + IRQF_SHARED : 0, + "SoundBlaster", (void *) chip)) { + snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq); + snd_sbdsp_free(chip); + return -EBUSY; + } + chip->irq = irq; + + if (hardware == SB_HW_ALS4000) + goto __skip_allocation; + + if ((chip->res_port = request_region(port, 16, "SoundBlaster")) == NULL) { + snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port); + snd_sbdsp_free(chip); + return -EBUSY; + } + +#ifdef CONFIG_ISA + if (dma8 >= 0 && request_dma(dma8, "SoundBlaster - 8bit")) { + snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8); + snd_sbdsp_free(chip); + return -EBUSY; + } + chip->dma8 = dma8; + if (dma16 >= 0) { + if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) { + /* no duplex */ + dma16 = -1; + } else if (request_dma(dma16, "SoundBlaster - 16bit")) { + snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16); + snd_sbdsp_free(chip); + return -EBUSY; + } + } + chip->dma16 = dma16; +#endif + + __skip_allocation: + chip->card = card; + chip->hardware = hardware; + if ((err = snd_sbdsp_probe(chip)) < 0) { + snd_sbdsp_free(chip); + return err; + } + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_sbdsp_free(chip); + return err; + } + *r_chip = chip; + return 0; +} + +EXPORT_SYMBOL(snd_sbdsp_command); +EXPORT_SYMBOL(snd_sbdsp_get_byte); +EXPORT_SYMBOL(snd_sbdsp_reset); +EXPORT_SYMBOL(snd_sbdsp_create); +/* sb_mixer.c */ +EXPORT_SYMBOL(snd_sbmixer_write); +EXPORT_SYMBOL(snd_sbmixer_read); +EXPORT_SYMBOL(snd_sbmixer_new); +EXPORT_SYMBOL(snd_sbmixer_add_ctl); +#ifdef CONFIG_PM +EXPORT_SYMBOL(snd_sbmixer_suspend); +EXPORT_SYMBOL(snd_sbmixer_resume); +#endif diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c new file mode 100644 index 000000000..add1d3f99 --- /dev/null +++ b/sound/isa/sb/sb_mixer.c @@ -0,0 +1,961 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for Sound Blaster mixer control + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#undef IO_DEBUG + +void snd_sbmixer_write(struct snd_sb *chip, unsigned char reg, unsigned char data) +{ + outb(reg, SBP(chip, MIXER_ADDR)); + udelay(10); + outb(data, SBP(chip, MIXER_DATA)); + udelay(10); +#ifdef IO_DEBUG + snd_printk(KERN_DEBUG "mixer_write 0x%x 0x%x\n", reg, data); +#endif +} + +unsigned char snd_sbmixer_read(struct snd_sb *chip, unsigned char reg) +{ + unsigned char result; + + outb(reg, SBP(chip, MIXER_ADDR)); + udelay(10); + result = inb(SBP(chip, MIXER_DATA)); + udelay(10); +#ifdef IO_DEBUG + snd_printk(KERN_DEBUG "mixer_read 0x%x 0x%x\n", reg, result); +#endif + return result; +} + +/* + * Single channel mixer element + */ + +static int snd_sbmixer_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 16) & 0xff; + int mask = (kcontrol->private_value >> 24) & 0xff; + unsigned char val; + + spin_lock_irqsave(&sb->mixer_lock, flags); + val = (snd_sbmixer_read(sb, reg) >> shift) & mask; + spin_unlock_irqrestore(&sb->mixer_lock, flags); + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_sbmixer_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 16) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned char val, oval; + + val = (ucontrol->value.integer.value[0] & mask) << shift; + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, reg); + val = (oval & ~(mask << shift)) | val; + change = val != oval; + if (change) + snd_sbmixer_write(sb, reg, val); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * Double channel mixer element + */ + +static int snd_sbmixer_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_sbmixer_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x07; + int right_shift = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + unsigned char left, right; + + spin_lock_irqsave(&sb->mixer_lock, flags); + left = (snd_sbmixer_read(sb, left_reg) >> left_shift) & mask; + right = (snd_sbmixer_read(sb, right_reg) >> right_shift) & mask; + spin_unlock_irqrestore(&sb->mixer_lock, flags); + ucontrol->value.integer.value[0] = left; + ucontrol->value.integer.value[1] = right; + return 0; +} + +static int snd_sbmixer_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x07; + int right_shift = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned char left, right, oleft, oright; + + left = (ucontrol->value.integer.value[0] & mask) << left_shift; + right = (ucontrol->value.integer.value[1] & mask) << right_shift; + spin_lock_irqsave(&sb->mixer_lock, flags); + if (left_reg == right_reg) { + oleft = snd_sbmixer_read(sb, left_reg); + left = (oleft & ~((mask << left_shift) | (mask << right_shift))) | left | right; + change = left != oleft; + if (change) + snd_sbmixer_write(sb, left_reg, left); + } else { + oleft = snd_sbmixer_read(sb, left_reg); + oright = snd_sbmixer_read(sb, right_reg); + left = (oleft & ~(mask << left_shift)) | left; + right = (oright & ~(mask << right_shift)) | right; + change = left != oleft || right != oright; + if (change) { + snd_sbmixer_write(sb, left_reg, left); + snd_sbmixer_write(sb, right_reg, right); + } + } + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * DT-019x / ALS-007 capture/input switch + */ + +static int snd_dt019x_input_sw_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[5] = { + "CD", "Mic", "Line", "Synth", "Master" + }; + + return snd_ctl_enum_info(uinfo, 1, 5, texts); +} + +static int snd_dt019x_input_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char oval; + + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + switch (oval & 0x07) { + case SB_DT019X_CAP_CD: + ucontrol->value.enumerated.item[0] = 0; + break; + case SB_DT019X_CAP_MIC: + ucontrol->value.enumerated.item[0] = 1; + break; + case SB_DT019X_CAP_LINE: + ucontrol->value.enumerated.item[0] = 2; + break; + case SB_DT019X_CAP_MAIN: + ucontrol->value.enumerated.item[0] = 4; + break; + /* To record the synth on these cards you must record the main. */ + /* Thus SB_DT019X_CAP_SYNTH == SB_DT019X_CAP_MAIN and would cause */ + /* duplicate case labels if left uncommented. */ + /* case SB_DT019X_CAP_SYNTH: + * ucontrol->value.enumerated.item[0] = 3; + * break; + */ + default: + ucontrol->value.enumerated.item[0] = 4; + break; + } + return 0; +} + +static int snd_dt019x_input_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval, oval; + + if (ucontrol->value.enumerated.item[0] > 4) + return -EINVAL; + switch (ucontrol->value.enumerated.item[0]) { + case 0: + nval = SB_DT019X_CAP_CD; + break; + case 1: + nval = SB_DT019X_CAP_MIC; + break; + case 2: + nval = SB_DT019X_CAP_LINE; + break; + case 3: + nval = SB_DT019X_CAP_SYNTH; + break; + case 4: + nval = SB_DT019X_CAP_MAIN; + break; + default: + nval = SB_DT019X_CAP_MAIN; + } + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW); + change = nval != oval; + if (change) + snd_sbmixer_write(sb, SB_DT019X_CAPTURE_SW, nval); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * ALS4000 mono recording control switch + */ + +static int snd_als4k_mono_capture_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "L chan only", "R chan only", "L ch/2 + R ch/2" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int snd_als4k_mono_capture_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char oval; + + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + oval >>= 6; + if (oval > 2) + oval = 2; + + ucontrol->value.enumerated.item[0] = oval; + return 0; +} + +static int snd_als4k_mono_capture_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval, oval; + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_ALS4000_MONO_IO_CTRL); + + nval = (oval & ~(3 << 6)) + | (ucontrol->value.enumerated.item[0] << 6); + change = nval != oval; + if (change) + snd_sbmixer_write(sb, SB_ALS4000_MONO_IO_CTRL, nval); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * SBPRO input multiplexer + */ + +static int snd_sb8mixer_info_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Mic", "CD", "Line" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + + +static int snd_sb8mixer_get_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char oval; + + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + switch ((oval >> 0x01) & 0x03) { + case SB_DSP_MIXS_CD: + ucontrol->value.enumerated.item[0] = 1; + break; + case SB_DSP_MIXS_LINE: + ucontrol->value.enumerated.item[0] = 2; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + return 0; +} + +static int snd_sb8mixer_put_mux(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval, oval; + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + switch (ucontrol->value.enumerated.item[0]) { + case 1: + nval = SB_DSP_MIXS_CD; + break; + case 2: + nval = SB_DSP_MIXS_LINE; + break; + default: + nval = SB_DSP_MIXS_MIC; + } + nval <<= 1; + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE); + nval |= oval & ~0x06; + change = nval != oval; + if (change) + snd_sbmixer_write(sb, SB_DSP_CAPTURE_SOURCE, nval); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * SB16 input switch + */ + +static int snd_sb16mixer_info_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 4; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_sb16mixer_get_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg1 = kcontrol->private_value & 0xff; + int reg2 = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x0f; + int right_shift = (kcontrol->private_value >> 24) & 0x0f; + unsigned char val1, val2; + + spin_lock_irqsave(&sb->mixer_lock, flags); + val1 = snd_sbmixer_read(sb, reg1); + val2 = snd_sbmixer_read(sb, reg2); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + ucontrol->value.integer.value[0] = (val1 >> left_shift) & 0x01; + ucontrol->value.integer.value[1] = (val2 >> left_shift) & 0x01; + ucontrol->value.integer.value[2] = (val1 >> right_shift) & 0x01; + ucontrol->value.integer.value[3] = (val2 >> right_shift) & 0x01; + return 0; +} + +static int snd_sb16mixer_put_input_sw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sb *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg1 = kcontrol->private_value & 0xff; + int reg2 = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x0f; + int right_shift = (kcontrol->private_value >> 24) & 0x0f; + int change; + unsigned char val1, val2, oval1, oval2; + + spin_lock_irqsave(&sb->mixer_lock, flags); + oval1 = snd_sbmixer_read(sb, reg1); + oval2 = snd_sbmixer_read(sb, reg2); + val1 = oval1 & ~((1 << left_shift) | (1 << right_shift)); + val2 = oval2 & ~((1 << left_shift) | (1 << right_shift)); + val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift; + val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift; + val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift; + val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift; + change = val1 != oval1 || val2 != oval2; + if (change) { + snd_sbmixer_write(sb, reg1, val1); + snd_sbmixer_write(sb, reg2, val2); + } + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + + +/* + */ +/* + */ +int snd_sbmixer_add_ctl(struct snd_sb *chip, const char *name, int index, int type, unsigned long value) +{ + static struct snd_kcontrol_new newctls[] = { + [SB_MIX_SINGLE] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sbmixer_info_single, + .get = snd_sbmixer_get_single, + .put = snd_sbmixer_put_single, + }, + [SB_MIX_DOUBLE] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sbmixer_info_double, + .get = snd_sbmixer_get_double, + .put = snd_sbmixer_put_double, + }, + [SB_MIX_INPUT_SW] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sb16mixer_info_input_sw, + .get = snd_sb16mixer_get_input_sw, + .put = snd_sb16mixer_put_input_sw, + }, + [SB_MIX_CAPTURE_PRO] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sb8mixer_info_mux, + .get = snd_sb8mixer_get_mux, + .put = snd_sb8mixer_put_mux, + }, + [SB_MIX_CAPTURE_DT019X] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_dt019x_input_sw_info, + .get = snd_dt019x_input_sw_get, + .put = snd_dt019x_input_sw_put, + }, + [SB_MIX_MONO_CAPTURE_ALS4K] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_als4k_mono_capture_route_info, + .get = snd_als4k_mono_capture_route_get, + .put = snd_als4k_mono_capture_route_put, + }, + }; + struct snd_kcontrol *ctl; + int err; + + ctl = snd_ctl_new1(&newctls[type], chip); + if (! ctl) + return -ENOMEM; + strlcpy(ctl->id.name, name, sizeof(ctl->id.name)); + ctl->id.index = index; + ctl->private_value = value; + if ((err = snd_ctl_add(chip->card, ctl)) < 0) + return err; + return 0; +} + +/* + * SB 2.0 specific mixer elements + */ + +static struct sbmix_elem snd_sb20_controls[] = { + SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7), + SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3), + SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7), + SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7) +}; + +static unsigned char snd_sb20_init_values[][2] = { + { SB_DSP20_MASTER_DEV, 0 }, + { SB_DSP20_FM_DEV, 0 }, +}; + +/* + * SB Pro specific mixer elements + */ +static struct sbmix_elem snd_sbpro_controls[] = { + SB_DOUBLE("Master Playback Volume", + SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7), + SB_DOUBLE("PCM Playback Volume", + SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7), + SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1), + SB_DOUBLE("Synth Playback Volume", + SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7), + SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7), + SB_DOUBLE("Line Playback Volume", + SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7), + SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3), + { + .name = "Capture Source", + .type = SB_MIX_CAPTURE_PRO + }, + SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1), + SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1) +}; + +static unsigned char snd_sbpro_init_values[][2] = { + { SB_DSP_MASTER_DEV, 0 }, + { SB_DSP_PCM_DEV, 0 }, + { SB_DSP_FM_DEV, 0 }, +}; + +/* + * SB16 specific mixer elements + */ +static struct sbmix_elem snd_sb16_controls[] = { + SB_DOUBLE("Master Playback Volume", + SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31), + SB_DOUBLE("PCM Playback Volume", + SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31), + SB16_INPUT_SW("Synth Capture Route", + SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5), + SB_DOUBLE("Synth Playback Volume", + SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31), + SB16_INPUT_SW("CD Capture Route", + SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1), + SB_DOUBLE("CD Playback Switch", + SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1), + SB_DOUBLE("CD Playback Volume", + SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31), + SB16_INPUT_SW("Mic Capture Route", + SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0), + SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1), + SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31), + SB_SINGLE("Beep Volume", SB_DSP4_SPEAKER_DEV, 6, 3), + SB_DOUBLE("Capture Volume", + SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3), + SB_DOUBLE("Playback Volume", + SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3), + SB16_INPUT_SW("Line Capture Route", + SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3), + SB_DOUBLE("Line Playback Switch", + SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1), + SB_DOUBLE("Line Playback Volume", + SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31), + SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1), + SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1), + SB_DOUBLE("Tone Control - Bass", + SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15), + SB_DOUBLE("Tone Control - Treble", + SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15) +}; + +static unsigned char snd_sb16_init_values[][2] = { + { SB_DSP4_MASTER_DEV + 0, 0 }, + { SB_DSP4_MASTER_DEV + 1, 0 }, + { SB_DSP4_PCM_DEV + 0, 0 }, + { SB_DSP4_PCM_DEV + 1, 0 }, + { SB_DSP4_SYNTH_DEV + 0, 0 }, + { SB_DSP4_SYNTH_DEV + 1, 0 }, + { SB_DSP4_INPUT_LEFT, 0 }, + { SB_DSP4_INPUT_RIGHT, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DSP4_SPEAKER_DEV, 0 }, +}; + +/* + * DT019x specific mixer elements + */ +static struct sbmix_elem snd_dt019x_controls[] = { + /* ALS4000 below has some parts which we might be lacking, + * e.g. snd_als4000_ctl_mono_playback_switch - check it! */ + SB_DOUBLE("Master Playback Volume", + SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4, 0, 15), + SB_DOUBLE("PCM Playback Switch", + SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1), + SB_DOUBLE("PCM Playback Volume", + SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4, 0, 15), + SB_DOUBLE("Synth Playback Switch", + SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1), + SB_DOUBLE("Synth Playback Volume", + SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4, 0, 15), + SB_DOUBLE("CD Playback Switch", + SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1), + SB_DOUBLE("CD Playback Volume", + SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4, 0, 15), + SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1), + SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7), + SB_SINGLE("Beep Volume", SB_DT019X_SPKR_DEV, 0, 7), + SB_DOUBLE("Line Playback Switch", + SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1), + SB_DOUBLE("Line Playback Volume", + SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4, 0, 15), + { + .name = "Capture Source", + .type = SB_MIX_CAPTURE_DT019X + } +}; + +static unsigned char snd_dt019x_init_values[][2] = { + { SB_DT019X_MASTER_DEV, 0 }, + { SB_DT019X_PCM_DEV, 0 }, + { SB_DT019X_SYNTH_DEV, 0 }, + { SB_DT019X_CD_DEV, 0 }, + { SB_DT019X_MIC_DEV, 0 }, /* Includes PC-speaker in high nibble */ + { SB_DT019X_LINE_DEV, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DT019X_OUTPUT_SW2, 0 }, + { SB_DT019X_CAPTURE_SW, 0x06 }, +}; + +/* + * ALS4000 specific mixer elements + */ +static struct sbmix_elem snd_als4000_controls[] = { + SB_DOUBLE("PCM Playback Switch", + SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2, 1, 1), + SB_DOUBLE("Synth Playback Switch", + SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4, 3, 1), + SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03), + SB_SINGLE("Master Mono Playback Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1), + { + .name = "Master Mono Capture Route", + .type = SB_MIX_MONO_CAPTURE_ALS4K + }, + SB_SINGLE("Mono Playback Switch", SB_DT019X_OUTPUT_SW2, 0, 1), + SB_SINGLE("Analog Loopback Switch", SB_ALS4000_MIC_IN_GAIN, 7, 0x01), + SB_SINGLE("3D Control - Switch", SB_ALS4000_3D_SND_FX, 6, 0x01), + SB_SINGLE("Digital Loopback Switch", + SB_ALS4000_CR3_CONFIGURATION, 7, 0x01), + /* FIXME: functionality of 3D controls might be swapped, I didn't find + * a description of how to identify what is supposed to be what */ + SB_SINGLE("3D Control - Level", SB_ALS4000_3D_SND_FX, 0, 0x07), + /* FIXME: maybe there's actually some standard 3D ctrl name for it?? */ + SB_SINGLE("3D Control - Freq", SB_ALS4000_3D_SND_FX, 4, 0x03), + /* FIXME: ALS4000a.pdf mentions BBD (Bucket Brigade Device) time delay, + * but what ALSA 3D attribute is that actually? "Center", "Depth", + * "Wide" or "Space" or even "Level"? Assuming "Wide" for now... */ + SB_SINGLE("3D Control - Wide", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f), + SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01), + SB_SINGLE("Master Playback 8kHz / 20kHz LPF Switch", + SB_ALS4000_FMDAC, 5, 0x01), +#ifdef NOT_AVAILABLE + SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01), + SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f), +#endif +}; + +static unsigned char snd_als4000_init_values[][2] = { + { SB_DSP4_MASTER_DEV + 0, 0 }, + { SB_DSP4_MASTER_DEV + 1, 0 }, + { SB_DSP4_PCM_DEV + 0, 0 }, + { SB_DSP4_PCM_DEV + 1, 0 }, + { SB_DSP4_SYNTH_DEV + 0, 0 }, + { SB_DSP4_SYNTH_DEV + 1, 0 }, + { SB_DSP4_SPEAKER_DEV, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DSP4_INPUT_LEFT, 0 }, + { SB_DSP4_INPUT_RIGHT, 0 }, + { SB_DT019X_OUTPUT_SW2, 0 }, + { SB_ALS4000_MIC_IN_GAIN, 0 }, +}; + +/* + */ +static int snd_sbmixer_init(struct snd_sb *chip, + struct sbmix_elem *controls, + int controls_count, + unsigned char map[][2], + int map_count, + char *name) +{ + unsigned long flags; + struct snd_card *card = chip->card; + int idx, err; + + /* mixer reset */ + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, 0x00, 0x00); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + /* mute and zero volume channels */ + for (idx = 0; idx < map_count; idx++) { + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, map[idx][0], map[idx][1]); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + } + + for (idx = 0; idx < controls_count; idx++) { + err = snd_sbmixer_add_ctl_elem(chip, &controls[idx]); + if (err < 0) + return err; + } + snd_component_add(card, name); + strcpy(card->mixername, name); + return 0; +} + +int snd_sbmixer_new(struct snd_sb *chip) +{ + struct snd_card *card; + int err; + + if (snd_BUG_ON(!chip || !chip->card)) + return -EINVAL; + + card = chip->card; + + switch (chip->hardware) { + case SB_HW_10: + return 0; /* no mixer chip on SB1.x */ + case SB_HW_20: + case SB_HW_201: + if ((err = snd_sbmixer_init(chip, + snd_sb20_controls, + ARRAY_SIZE(snd_sb20_controls), + snd_sb20_init_values, + ARRAY_SIZE(snd_sb20_init_values), + "CTL1335")) < 0) + return err; + break; + case SB_HW_PRO: + case SB_HW_JAZZ16: + if ((err = snd_sbmixer_init(chip, + snd_sbpro_controls, + ARRAY_SIZE(snd_sbpro_controls), + snd_sbpro_init_values, + ARRAY_SIZE(snd_sbpro_init_values), + "CTL1345")) < 0) + return err; + break; + case SB_HW_16: + case SB_HW_ALS100: + case SB_HW_CS5530: + if ((err = snd_sbmixer_init(chip, + snd_sb16_controls, + ARRAY_SIZE(snd_sb16_controls), + snd_sb16_init_values, + ARRAY_SIZE(snd_sb16_init_values), + "CTL1745")) < 0) + return err; + break; + case SB_HW_ALS4000: + /* use only the first 16 controls from SB16 */ + err = snd_sbmixer_init(chip, + snd_sb16_controls, + 16, + snd_sb16_init_values, + ARRAY_SIZE(snd_sb16_init_values), + "ALS4000"); + if (err < 0) + return err; + if ((err = snd_sbmixer_init(chip, + snd_als4000_controls, + ARRAY_SIZE(snd_als4000_controls), + snd_als4000_init_values, + ARRAY_SIZE(snd_als4000_init_values), + "ALS4000")) < 0) + return err; + break; + case SB_HW_DT019X: + err = snd_sbmixer_init(chip, + snd_dt019x_controls, + ARRAY_SIZE(snd_dt019x_controls), + snd_dt019x_init_values, + ARRAY_SIZE(snd_dt019x_init_values), + "DT019X"); + if (err < 0) + return err; + break; + default: + strcpy(card->mixername, "???"); + } + return 0; +} + +#ifdef CONFIG_PM +static unsigned char sb20_saved_regs[] = { + SB_DSP20_MASTER_DEV, + SB_DSP20_PCM_DEV, + SB_DSP20_FM_DEV, + SB_DSP20_CD_DEV, +}; + +static unsigned char sbpro_saved_regs[] = { + SB_DSP_MASTER_DEV, + SB_DSP_PCM_DEV, + SB_DSP_PLAYBACK_FILT, + SB_DSP_FM_DEV, + SB_DSP_CD_DEV, + SB_DSP_LINE_DEV, + SB_DSP_MIC_DEV, + SB_DSP_CAPTURE_SOURCE, + SB_DSP_CAPTURE_FILT, +}; + +static unsigned char sb16_saved_regs[] = { + SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1, + SB_DSP4_3DSE, + SB_DSP4_BASS_DEV, SB_DSP4_BASS_DEV + 1, + SB_DSP4_TREBLE_DEV, SB_DSP4_TREBLE_DEV + 1, + SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1, + SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, + SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1, + SB_DSP4_OUTPUT_SW, + SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1, + SB_DSP4_LINE_DEV, SB_DSP4_LINE_DEV + 1, + SB_DSP4_MIC_DEV, + SB_DSP4_SPEAKER_DEV, + SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1, + SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1, + SB_DSP4_MIC_AGC +}; + +static unsigned char dt019x_saved_regs[] = { + SB_DT019X_MASTER_DEV, + SB_DT019X_PCM_DEV, + SB_DT019X_SYNTH_DEV, + SB_DT019X_CD_DEV, + SB_DT019X_MIC_DEV, + SB_DT019X_SPKR_DEV, + SB_DT019X_LINE_DEV, + SB_DSP4_OUTPUT_SW, + SB_DT019X_OUTPUT_SW2, + SB_DT019X_CAPTURE_SW, +}; + +static unsigned char als4000_saved_regs[] = { + /* please verify in dsheet whether regs to be added + are actually real H/W or just dummy */ + SB_DSP4_MASTER_DEV, SB_DSP4_MASTER_DEV + 1, + SB_DSP4_OUTPUT_SW, + SB_DSP4_PCM_DEV, SB_DSP4_PCM_DEV + 1, + SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, + SB_DSP4_SYNTH_DEV, SB_DSP4_SYNTH_DEV + 1, + SB_DSP4_CD_DEV, SB_DSP4_CD_DEV + 1, + SB_DSP4_MIC_DEV, + SB_DSP4_SPEAKER_DEV, + SB_DSP4_IGAIN_DEV, SB_DSP4_IGAIN_DEV + 1, + SB_DSP4_OGAIN_DEV, SB_DSP4_OGAIN_DEV + 1, + SB_DT019X_OUTPUT_SW2, + SB_ALS4000_MONO_IO_CTRL, + SB_ALS4000_MIC_IN_GAIN, + SB_ALS4000_FMDAC, + SB_ALS4000_3D_SND_FX, + SB_ALS4000_3D_TIME_DELAY, + SB_ALS4000_CR3_CONFIGURATION, +}; + +static void save_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs) +{ + unsigned char *val = chip->saved_regs; + if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs))) + return; + for (; num_regs; num_regs--) + *val++ = snd_sbmixer_read(chip, *regs++); +} + +static void restore_mixer(struct snd_sb *chip, unsigned char *regs, int num_regs) +{ + unsigned char *val = chip->saved_regs; + if (snd_BUG_ON(num_regs > ARRAY_SIZE(chip->saved_regs))) + return; + for (; num_regs; num_regs--) + snd_sbmixer_write(chip, *regs++, *val++); +} + +void snd_sbmixer_suspend(struct snd_sb *chip) +{ + switch (chip->hardware) { + case SB_HW_20: + case SB_HW_201: + save_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs)); + break; + case SB_HW_PRO: + case SB_HW_JAZZ16: + save_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs)); + break; + case SB_HW_16: + case SB_HW_ALS100: + case SB_HW_CS5530: + save_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs)); + break; + case SB_HW_ALS4000: + save_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs)); + break; + case SB_HW_DT019X: + save_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs)); + break; + default: + break; + } +} + +void snd_sbmixer_resume(struct snd_sb *chip) +{ + switch (chip->hardware) { + case SB_HW_20: + case SB_HW_201: + restore_mixer(chip, sb20_saved_regs, ARRAY_SIZE(sb20_saved_regs)); + break; + case SB_HW_PRO: + case SB_HW_JAZZ16: + restore_mixer(chip, sbpro_saved_regs, ARRAY_SIZE(sbpro_saved_regs)); + break; + case SB_HW_16: + case SB_HW_ALS100: + case SB_HW_CS5530: + restore_mixer(chip, sb16_saved_regs, ARRAY_SIZE(sb16_saved_regs)); + break; + case SB_HW_ALS4000: + restore_mixer(chip, als4000_saved_regs, ARRAY_SIZE(als4000_saved_regs)); + break; + case SB_HW_DT019X: + restore_mixer(chip, dt019x_saved_regs, ARRAY_SIZE(dt019x_saved_regs)); + break; + default: + break; + } +} +#endif diff --git a/sound/isa/sb/sbawe.c b/sound/isa/sb/sbawe.c new file mode 100644 index 000000000..2ec52a347 --- /dev/null +++ b/sound/isa/sb/sbawe.c @@ -0,0 +1,2 @@ +#define SNDRV_SBAWE +#include "sb16.c" diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c new file mode 100644 index 000000000..a985e9183 --- /dev/null +++ b/sound/isa/sc6000.c @@ -0,0 +1,714 @@ +/* + * Driver for Gallant SC-6000 soundcard. This card is also known as + * Audio Excel DSP 16 or Zoltrix AV302. + * These cards use CompuMedia ASC-9308 chip + AD1848 codec. + * SC-6600 and SC-7000 cards are also supported. They are based on + * CompuMedia ASC-9408 chip and CS4231 codec. + * + * Copyright (C) 2007 Krzysztof Helt + * + * I don't have documentation for this card. I used the driver + * for OSS/Free included in the kernel source as reference. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include + +MODULE_AUTHOR("Krzysztof Helt"); +MODULE_DESCRIPTION("Gallant SC-6000"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Gallant, SC-6000}," + "{AudioExcel, Audio Excel DSP 16}," + "{Zoltrix, AV302}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220, 0x240 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 11 */ +static long mss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x530, 0xe80 */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; + /* 0x300, 0x310, 0x320, 0x330 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5, 7, 9, 10, 0 */ +static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0, 1, 3 */ +static bool joystick[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = false }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for sc-6000 based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for sc-6000 based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable sc-6000 based soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for sc-6000 driver."); +module_param_hw_array(mss_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mss_port, "MSS Port # for sc-6000 driver."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for sc-6000 driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for sc-6000 driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for sc-6000 driver."); +module_param_hw_array(dma, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma, "DMA # for sc-6000 driver."); +module_param_array(joystick, bool, NULL, 0444); +MODULE_PARM_DESC(joystick, "Enable gameport."); + +/* + * Commands of SC6000's DSP (SBPRO+special). + * Some of them are COMMAND_xx, in the future they may change. + */ +#define WRITE_MDIRQ_CFG 0x50 /* Set M&I&DRQ mask (the real config) */ +#define COMMAND_52 0x52 /* */ +#define READ_HARD_CFG 0x58 /* Read Hardware Config (I/O base etc) */ +#define COMMAND_5C 0x5c /* */ +#define COMMAND_60 0x60 /* */ +#define COMMAND_66 0x66 /* */ +#define COMMAND_6C 0x6c /* */ +#define COMMAND_6E 0x6e /* */ +#define COMMAND_88 0x88 /* Unknown command */ +#define DSP_INIT_MSS 0x8c /* Enable Microsoft Sound System mode */ +#define COMMAND_C5 0xc5 /* */ +#define GET_DSP_VERSION 0xe1 /* Get DSP Version */ +#define GET_DSP_COPYRIGHT 0xe3 /* Get DSP Copyright */ + +/* + * Offsets of SC6000 DSP I/O ports. The offset is added to base I/O port + * to have the actual I/O port. + * Register permissions are: + * (wo) == Write Only + * (ro) == Read Only + * (w-) == Write + * (r-) == Read + */ +#define DSP_RESET 0x06 /* offset of DSP RESET (wo) */ +#define DSP_READ 0x0a /* offset of DSP READ (ro) */ +#define DSP_WRITE 0x0c /* offset of DSP WRITE (w-) */ +#define DSP_COMMAND 0x0c /* offset of DSP COMMAND (w-) */ +#define DSP_STATUS 0x0c /* offset of DSP STATUS (r-) */ +#define DSP_DATAVAIL 0x0e /* offset of DSP DATA AVAILABLE (ro) */ + +#define PFX "sc6000: " +#define DRV_NAME "SC-6000" + +/* hardware dependent functions */ + +/* + * sc6000_irq_to_softcfg - Decode irq number into cfg code. + */ +static unsigned char sc6000_irq_to_softcfg(int irq) +{ + unsigned char val = 0; + + switch (irq) { + case 5: + val = 0x28; + break; + case 7: + val = 0x8; + break; + case 9: + val = 0x10; + break; + case 10: + val = 0x18; + break; + case 11: + val = 0x20; + break; + default: + break; + } + return val; +} + +/* + * sc6000_dma_to_softcfg - Decode dma number into cfg code. + */ +static unsigned char sc6000_dma_to_softcfg(int dma) +{ + unsigned char val = 0; + + switch (dma) { + case 0: + val = 1; + break; + case 1: + val = 2; + break; + case 3: + val = 3; + break; + default: + break; + } + return val; +} + +/* + * sc6000_mpu_irq_to_softcfg - Decode MPU-401 irq number into cfg code. + */ +static unsigned char sc6000_mpu_irq_to_softcfg(int mpu_irq) +{ + unsigned char val = 0; + + switch (mpu_irq) { + case 5: + val = 4; + break; + case 7: + val = 0x44; + break; + case 9: + val = 0x84; + break; + case 10: + val = 0xc4; + break; + default: + break; + } + return val; +} + +static int sc6000_wait_data(char __iomem *vport) +{ + int loop = 1000; + unsigned char val = 0; + + do { + val = ioread8(vport + DSP_DATAVAIL); + if (val & 0x80) + return 0; + cpu_relax(); + } while (loop--); + + return -EAGAIN; +} + +static int sc6000_read(char __iomem *vport) +{ + if (sc6000_wait_data(vport)) + return -EBUSY; + + return ioread8(vport + DSP_READ); + +} + +static int sc6000_write(char __iomem *vport, int cmd) +{ + unsigned char val; + int loop = 500000; + + do { + val = ioread8(vport + DSP_STATUS); + /* + * DSP ready to receive data if bit 7 of val == 0 + */ + if (!(val & 0x80)) { + iowrite8(cmd, vport + DSP_COMMAND); + return 0; + } + cpu_relax(); + } while (loop--); + + snd_printk(KERN_ERR "DSP Command (0x%x) timeout.\n", cmd); + + return -EIO; +} + +static int sc6000_dsp_get_answer(char __iomem *vport, int command, + char *data, int data_len) +{ + int len = 0; + + if (sc6000_write(vport, command)) { + snd_printk(KERN_ERR "CMD 0x%x: failed!\n", command); + return -EIO; + } + + do { + int val = sc6000_read(vport); + + if (val < 0) + break; + + data[len++] = val; + + } while (len < data_len); + + /* + * If no more data available, return to the caller, no error if len>0. + * We have no other way to know when the string is finished. + */ + return len ? len : -EIO; +} + +static int sc6000_dsp_reset(char __iomem *vport) +{ + iowrite8(1, vport + DSP_RESET); + udelay(10); + iowrite8(0, vport + DSP_RESET); + udelay(20); + if (sc6000_read(vport) == 0xaa) + return 0; + return -ENODEV; +} + +/* detection and initialization */ +static int sc6000_hw_cfg_write(char __iomem *vport, const int *cfg) +{ + if (sc6000_write(vport, COMMAND_6C) < 0) { + snd_printk(KERN_WARNING "CMD 0x%x: failed!\n", COMMAND_6C); + return -EIO; + } + if (sc6000_write(vport, COMMAND_5C) < 0) { + snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_5C); + return -EIO; + } + if (sc6000_write(vport, cfg[0]) < 0) { + snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[0]); + return -EIO; + } + if (sc6000_write(vport, cfg[1]) < 0) { + snd_printk(KERN_ERR "DATA 0x%x: failed!\n", cfg[1]); + return -EIO; + } + if (sc6000_write(vport, COMMAND_C5) < 0) { + snd_printk(KERN_ERR "CMD 0x%x: failed!\n", COMMAND_C5); + return -EIO; + } + + return 0; +} + +static int sc6000_cfg_write(char __iomem *vport, unsigned char softcfg) +{ + + if (sc6000_write(vport, WRITE_MDIRQ_CFG)) { + snd_printk(KERN_ERR "CMD 0x%x: failed!\n", WRITE_MDIRQ_CFG); + return -EIO; + } + if (sc6000_write(vport, softcfg)) { + snd_printk(KERN_ERR "sc6000_cfg_write: failed!\n"); + return -EIO; + } + return 0; +} + +static int sc6000_setup_board(char __iomem *vport, int config) +{ + int loop = 10; + + do { + if (sc6000_write(vport, COMMAND_88)) { + snd_printk(KERN_ERR "CMD 0x%x: failed!\n", + COMMAND_88); + return -EIO; + } + } while ((sc6000_wait_data(vport) < 0) && loop--); + + if (sc6000_read(vport) < 0) { + snd_printk(KERN_ERR "sc6000_read after CMD 0x%x: failed\n", + COMMAND_88); + return -EIO; + } + + if (sc6000_cfg_write(vport, config)) + return -ENODEV; + + return 0; +} + +static int sc6000_init_mss(char __iomem *vport, int config, + char __iomem *vmss_port, int mss_config) +{ + if (sc6000_write(vport, DSP_INIT_MSS)) { + snd_printk(KERN_ERR "sc6000_init_mss [0x%x]: failed!\n", + DSP_INIT_MSS); + return -EIO; + } + + msleep(10); + + if (sc6000_cfg_write(vport, config)) + return -EIO; + + iowrite8(mss_config, vmss_port); + + return 0; +} + +static void sc6000_hw_cfg_encode(char __iomem *vport, int *cfg, + long xport, long xmpu, + long xmss_port, int joystick) +{ + cfg[0] = 0; + cfg[1] = 0; + if (xport == 0x240) + cfg[0] |= 1; + if (xmpu != SNDRV_AUTO_PORT) { + cfg[0] |= (xmpu & 0x30) >> 2; + cfg[1] |= 0x20; + } + if (xmss_port == 0xe80) + cfg[0] |= 0x10; + cfg[0] |= 0x40; /* always set */ + if (!joystick) + cfg[0] |= 0x02; + cfg[1] |= 0x80; /* enable WSS system */ + cfg[1] &= ~0x40; /* disable IDE */ + snd_printd("hw cfg %x, %x\n", cfg[0], cfg[1]); +} + +static int sc6000_init_board(char __iomem *vport, + char __iomem *vmss_port, int dev) +{ + char answer[15]; + char version[2]; + int mss_config = sc6000_irq_to_softcfg(irq[dev]) | + sc6000_dma_to_softcfg(dma[dev]); + int config = mss_config | + sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); + int err; + int old = 0; + + err = sc6000_dsp_reset(vport); + if (err < 0) { + snd_printk(KERN_ERR "sc6000_dsp_reset: failed!\n"); + return err; + } + + memset(answer, 0, sizeof(answer)); + err = sc6000_dsp_get_answer(vport, GET_DSP_COPYRIGHT, answer, 15); + if (err <= 0) { + snd_printk(KERN_ERR "sc6000_dsp_copyright: failed!\n"); + return -ENODEV; + } + /* + * My SC-6000 card return "SC-6000" in DSPCopyright, so + * if we have something different, we have to be warned. + */ + if (strncmp("SC-6000", answer, 7)) + snd_printk(KERN_WARNING "Warning: non SC-6000 audio card!\n"); + + if (sc6000_dsp_get_answer(vport, GET_DSP_VERSION, version, 2) < 2) { + snd_printk(KERN_ERR "sc6000_dsp_version: failed!\n"); + return -ENODEV; + } + printk(KERN_INFO PFX "Detected model: %s, DSP version %d.%d\n", + answer, version[0], version[1]); + + /* set configuration */ + sc6000_write(vport, COMMAND_5C); + if (sc6000_read(vport) < 0) + old = 1; + + if (!old) { + int cfg[2]; + sc6000_hw_cfg_encode(vport, &cfg[0], port[dev], mpu_port[dev], + mss_port[dev], joystick[dev]); + if (sc6000_hw_cfg_write(vport, cfg) < 0) { + snd_printk(KERN_ERR "sc6000_hw_cfg_write: failed!\n"); + return -EIO; + } + } + err = sc6000_setup_board(vport, config); + if (err < 0) { + snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); + return -ENODEV; + } + + sc6000_dsp_reset(vport); + + if (!old) { + sc6000_write(vport, COMMAND_60); + sc6000_write(vport, 0x02); + sc6000_dsp_reset(vport); + } + + err = sc6000_setup_board(vport, config); + if (err < 0) { + snd_printk(KERN_ERR "sc6000_setup_board: failed!\n"); + return -ENODEV; + } + err = sc6000_init_mss(vport, config, vmss_port, mss_config); + if (err < 0) { + snd_printk(KERN_ERR "Cannot initialize " + "Microsoft Sound System mode.\n"); + return -ENODEV; + } + + return 0; +} + +static int snd_sc6000_mixer(struct snd_wss *chip) +{ + struct snd_card *card = chip->card; + struct snd_ctl_elem_id id1, id2; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUX0 to FM */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "FM Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "FM Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + /* reassign AUX1 to CD */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "CD Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + return 0; +} + +static int snd_sc6000_match(struct device *devptr, unsigned int dev) +{ + if (!enable[dev]) + return 0; + if (port[dev] == SNDRV_AUTO_PORT) { + printk(KERN_ERR PFX "specify IO port\n"); + return 0; + } + if (mss_port[dev] == SNDRV_AUTO_PORT) { + printk(KERN_ERR PFX "specify MSS port\n"); + return 0; + } + if (port[dev] != 0x220 && port[dev] != 0x240) { + printk(KERN_ERR PFX "Port must be 0x220 or 0x240\n"); + return 0; + } + if (mss_port[dev] != 0x530 && mss_port[dev] != 0xe80) { + printk(KERN_ERR PFX "MSS port must be 0x530 or 0xe80\n"); + return 0; + } + if (irq[dev] != SNDRV_AUTO_IRQ && !sc6000_irq_to_softcfg(irq[dev])) { + printk(KERN_ERR PFX "invalid IRQ %d\n", irq[dev]); + return 0; + } + if (dma[dev] != SNDRV_AUTO_DMA && !sc6000_dma_to_softcfg(dma[dev])) { + printk(KERN_ERR PFX "invalid DMA %d\n", dma[dev]); + return 0; + } + if (mpu_port[dev] != SNDRV_AUTO_PORT && + (mpu_port[dev] & ~0x30L) != 0x300) { + printk(KERN_ERR PFX "invalid MPU-401 port %lx\n", + mpu_port[dev]); + return 0; + } + if (mpu_port[dev] != SNDRV_AUTO_PORT && + mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] != 0 && + !sc6000_mpu_irq_to_softcfg(mpu_irq[dev])) { + printk(KERN_ERR PFX "invalid MPU-401 IRQ %d\n", mpu_irq[dev]); + return 0; + } + return 1; +} + +static int snd_sc6000_probe(struct device *devptr, unsigned int dev) +{ + static int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; + static int possible_dmas[] = { 1, 3, 0, -1 }; + int err; + int xirq = irq[dev]; + int xdma = dma[dev]; + struct snd_card *card; + struct snd_wss *chip; + struct snd_opl3 *opl3; + char __iomem **vport; + char __iomem *vmss_port; + + + err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE, + sizeof(vport), &card); + if (err < 0) + return err; + + vport = card->private_data; + if (xirq == SNDRV_AUTO_IRQ) { + xirq = snd_legacy_find_free_irq(possible_irqs); + if (xirq < 0) { + snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); + err = -EBUSY; + goto err_exit; + } + } + + if (xdma == SNDRV_AUTO_DMA) { + xdma = snd_legacy_find_free_dma(possible_dmas); + if (xdma < 0) { + snd_printk(KERN_ERR PFX "unable to find a free DMA\n"); + err = -EBUSY; + goto err_exit; + } + } + + if (!request_region(port[dev], 0x10, DRV_NAME)) { + snd_printk(KERN_ERR PFX + "I/O port region is already in use.\n"); + err = -EBUSY; + goto err_exit; + } + *vport = devm_ioport_map(devptr, port[dev], 0x10); + if (*vport == NULL) { + snd_printk(KERN_ERR PFX + "I/O port cannot be iomapped.\n"); + err = -EBUSY; + goto err_unmap1; + } + + /* to make it marked as used */ + if (!request_region(mss_port[dev], 4, DRV_NAME)) { + snd_printk(KERN_ERR PFX + "SC-6000 port I/O port region is already in use.\n"); + err = -EBUSY; + goto err_unmap1; + } + vmss_port = devm_ioport_map(devptr, mss_port[dev], 4); + if (!vmss_port) { + snd_printk(KERN_ERR PFX + "MSS port I/O cannot be iomapped.\n"); + err = -EBUSY; + goto err_unmap2; + } + + snd_printd("Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n", + port[dev], xirq, xdma, + mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); + + err = sc6000_init_board(*vport, vmss_port, dev); + if (err < 0) + goto err_unmap2; + + err = snd_wss_create(card, mss_port[dev] + 4, -1, xirq, xdma, -1, + WSS_HW_DETECT, 0, &chip); + if (err < 0) + goto err_unmap2; + + err = snd_wss_pcm(chip, 0); + if (err < 0) { + snd_printk(KERN_ERR PFX + "error creating new WSS PCM device\n"); + goto err_unmap2; + } + err = snd_wss_mixer(chip); + if (err < 0) { + snd_printk(KERN_ERR PFX "error creating new WSS mixer\n"); + goto err_unmap2; + } + err = snd_sc6000_mixer(chip); + if (err < 0) { + snd_printk(KERN_ERR PFX "the mixer rewrite failed\n"); + goto err_unmap2; + } + if (snd_opl3_create(card, + 0x388, 0x388 + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%x-0x%x ?\n", + 0x388, 0x388 + 2); + } else { + err = snd_opl3_hwdep_new(opl3, 0, 1, NULL); + if (err < 0) + goto err_unmap2; + } + + if (mpu_port[dev] != SNDRV_AUTO_PORT) { + if (mpu_irq[dev] == SNDRV_AUTO_IRQ) + mpu_irq[dev] = -1; + if (snd_mpu401_uart_new(card, 0, + MPU401_HW_MPU401, + mpu_port[dev], 0, + mpu_irq[dev], NULL) < 0) + snd_printk(KERN_ERR "no MPU-401 device at 0x%lx ?\n", + mpu_port[dev]); + } + + strcpy(card->driver, DRV_NAME); + strcpy(card->shortname, "SC-6000"); + sprintf(card->longname, "Gallant SC-6000 at 0x%lx, irq %d, dma %d", + mss_port[dev], xirq, xdma); + + err = snd_card_register(card); + if (err < 0) + goto err_unmap2; + + dev_set_drvdata(devptr, card); + return 0; + +err_unmap2: + sc6000_setup_board(*vport, 0); + release_region(mss_port[dev], 4); +err_unmap1: + release_region(port[dev], 0x10); +err_exit: + snd_card_free(card); + return err; +} + +static int snd_sc6000_remove(struct device *devptr, unsigned int dev) +{ + struct snd_card *card = dev_get_drvdata(devptr); + char __iomem **vport = card->private_data; + + if (sc6000_setup_board(*vport, 0) < 0) + snd_printk(KERN_WARNING "sc6000_setup_board failed on exit!\n"); + + release_region(port[dev], 0x10); + release_region(mss_port[dev], 4); + + snd_card_free(card); + return 0; +} + +static struct isa_driver snd_sc6000_driver = { + .match = snd_sc6000_match, + .probe = snd_sc6000_probe, + .remove = snd_sc6000_remove, + /* FIXME: suspend/resume */ + .driver = { + .name = DRV_NAME, + }, +}; + + +module_isa_driver(snd_sc6000_driver, SNDRV_CARDS); diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c new file mode 100644 index 000000000..733adee5a --- /dev/null +++ b/sound/isa/sscape.c @@ -0,0 +1,1357 @@ +/* + * Low-level ALSA driver for the ENSONIQ SoundScape + * Copyright (c) by Chris Rankin + * + * This driver was written in part using information obtained from + * the OSS/Free SoundScape driver, written by Hannu Savolainen. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_AUTHOR("Chris Rankin"); +MODULE_DESCRIPTION("ENSONIQ SoundScape driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("sndscape.co0"); +MODULE_FIRMWARE("sndscape.co1"); +MODULE_FIRMWARE("sndscape.co2"); +MODULE_FIRMWARE("sndscape.co3"); +MODULE_FIRMWARE("sndscape.co4"); +MODULE_FIRMWARE("scope.cod"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static bool joystick[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index number for SoundScape soundcard"); + +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "Description for SoundScape card"); + +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for SoundScape driver."); + +module_param_hw_array(wss_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(wss_port, "WSS Port # for SoundScape driver."); + +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for SoundScape driver."); + +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU401 IRQ # for SoundScape driver."); + +module_param_hw_array(dma, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma, "DMA # for SoundScape driver."); + +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for SoundScape driver."); + +module_param_array(joystick, bool, NULL, 0444); +MODULE_PARM_DESC(joystick, "Enable gameport."); + +#ifdef CONFIG_PNP +static int isa_registered; +static int pnp_registered; + +static const struct pnp_card_device_id sscape_pnpids[] = { + { .id = "ENS3081", .devs = { { "ENS0000" } } }, /* Soundscape PnP */ + { .id = "ENS4081", .devs = { { "ENS1011" } } }, /* VIVO90 */ + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, sscape_pnpids); +#endif + + +#define HOST_CTRL_IO(i) ((i) + 2) +#define HOST_DATA_IO(i) ((i) + 3) +#define ODIE_ADDR_IO(i) ((i) + 4) +#define ODIE_DATA_IO(i) ((i) + 5) +#define CODEC_IO(i) ((i) + 8) + +#define IC_ODIE 1 +#define IC_OPUS 2 + +#define RX_READY 0x01 +#define TX_READY 0x02 + +#define CMD_ACK 0x80 +#define CMD_SET_MIDI_VOL 0x84 +#define CMD_GET_MIDI_VOL 0x85 +#define CMD_XXX_MIDI_VOL 0x86 +#define CMD_SET_EXTMIDI 0x8a +#define CMD_GET_EXTMIDI 0x8b +#define CMD_SET_MT32 0x8c +#define CMD_GET_MT32 0x8d + +enum GA_REG { + GA_INTSTAT_REG = 0, + GA_INTENA_REG, + GA_DMAA_REG, + GA_DMAB_REG, + GA_INTCFG_REG, + GA_DMACFG_REG, + GA_CDCFG_REG, + GA_SMCFGA_REG, + GA_SMCFGB_REG, + GA_HMCTL_REG +}; + +#define DMA_8BIT 0x80 + + +enum card_type { + MEDIA_FX, /* Sequoia S-1000 */ + SSCAPE, /* Sequoia S-2000 */ + SSCAPE_PNP, + SSCAPE_VIVO, +}; + +struct soundscape { + spinlock_t lock; + unsigned io_base; + int ic_type; + enum card_type type; + struct resource *io_res; + struct resource *wss_res; + struct snd_wss *chip; + + unsigned char midi_vol; +}; + +#define INVALID_IRQ ((unsigned)-1) + + +static inline struct soundscape *get_card_soundscape(struct snd_card *c) +{ + return (struct soundscape *) (c->private_data); +} + +/* + * Allocates some kernel memory that we can use for DMA. + * I think this means that the memory has to map to + * contiguous pages of physical memory. + */ +static struct snd_dma_buffer *get_dmabuf(struct snd_dma_buffer *buf, + unsigned long size) +{ + if (buf) { + if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + size, buf) < 0) { + snd_printk(KERN_ERR "sscape: Failed to allocate " + "%lu bytes for DMA\n", + size); + return NULL; + } + } + + return buf; +} + +/* + * Release the DMA-able kernel memory ... + */ +static void free_dmabuf(struct snd_dma_buffer *buf) +{ + if (buf && buf->area) + snd_dma_free_pages(buf); +} + +/* + * This function writes to the SoundScape's control registers, + * but doesn't do any locking. It's up to the caller to do that. + * This is why this function is "unsafe" ... + */ +static inline void sscape_write_unsafe(unsigned io_base, enum GA_REG reg, + unsigned char val) +{ + outb(reg, ODIE_ADDR_IO(io_base)); + outb(val, ODIE_DATA_IO(io_base)); +} + +/* + * Write to the SoundScape's control registers, and do the + * necessary locking ... + */ +static void sscape_write(struct soundscape *s, enum GA_REG reg, + unsigned char val) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + sscape_write_unsafe(s->io_base, reg, val); + spin_unlock_irqrestore(&s->lock, flags); +} + +/* + * Read from the SoundScape's control registers, but leave any + * locking to the caller. This is why the function is "unsafe" ... + */ +static inline unsigned char sscape_read_unsafe(unsigned io_base, + enum GA_REG reg) +{ + outb(reg, ODIE_ADDR_IO(io_base)); + return inb(ODIE_DATA_IO(io_base)); +} + +/* + * Puts the SoundScape into "host" mode, as compared to "MIDI" mode + */ +static inline void set_host_mode_unsafe(unsigned io_base) +{ + outb(0x0, HOST_CTRL_IO(io_base)); +} + +/* + * Puts the SoundScape into "MIDI" mode, as compared to "host" mode + */ +static inline void set_midi_mode_unsafe(unsigned io_base) +{ + outb(0x3, HOST_CTRL_IO(io_base)); +} + +/* + * Read the SoundScape's host-mode control register, but leave + * any locking issues to the caller ... + */ +static inline int host_read_unsafe(unsigned io_base) +{ + int data = -1; + if ((inb(HOST_CTRL_IO(io_base)) & RX_READY) != 0) + data = inb(HOST_DATA_IO(io_base)); + + return data; +} + +/* + * Read the SoundScape's host-mode control register, performing + * a limited amount of busy-waiting if the register isn't ready. + * Also leaves all locking-issues to the caller ... + */ +static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout) +{ + int data; + + while (((data = host_read_unsafe(io_base)) < 0) && (timeout != 0)) { + udelay(100); + --timeout; + } /* while */ + + return data; +} + +/* + * Write to the SoundScape's host-mode control registers, but + * leave any locking issues to the caller ... + */ +static inline int host_write_unsafe(unsigned io_base, unsigned char data) +{ + if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) { + outb(data, HOST_DATA_IO(io_base)); + return 1; + } + + return 0; +} + +/* + * Write to the SoundScape's host-mode control registers, performing + * a limited amount of busy-waiting if the register isn't ready. + * Also leaves all locking-issues to the caller ... + */ +static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data, + unsigned timeout) +{ + int err; + + while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) { + udelay(100); + --timeout; + } /* while */ + + return err; +} + + +/* + * Check that the MIDI subsystem is operational. If it isn't, + * then we will hang the computer if we try to use it ... + * + * NOTE: This check is based upon observation, not documentation. + */ +static inline int verify_mpu401(const struct snd_mpu401 *mpu) +{ + return ((inb(MPU401C(mpu)) & 0xc0) == 0x80); +} + +/* + * This is apparently the standard way to initailise an MPU-401 + */ +static inline void initialise_mpu401(const struct snd_mpu401 *mpu) +{ + outb(0, MPU401D(mpu)); +} + +/* + * Tell the SoundScape to activate the AD1845 chip (I think). + * The AD1845 detection fails if we *don't* do this, so I + * think that this is a good idea ... + */ +static void activate_ad1845_unsafe(unsigned io_base) +{ + unsigned char val = sscape_read_unsafe(io_base, GA_HMCTL_REG); + sscape_write_unsafe(io_base, GA_HMCTL_REG, (val & 0xcf) | 0x10); + sscape_write_unsafe(io_base, GA_CDCFG_REG, 0x80); +} + +/* + * Do the necessary ALSA-level cleanup to deallocate our driver ... + */ +static void soundscape_free(struct snd_card *c) +{ + struct soundscape *sscape = get_card_soundscape(c); + release_and_free_resource(sscape->io_res); + release_and_free_resource(sscape->wss_res); + free_dma(sscape->chip->dma1); +} + +/* + * Tell the SoundScape to begin a DMA tranfer using the given channel. + * All locking issues are left to the caller. + */ +static void sscape_start_dma_unsafe(unsigned io_base, enum GA_REG reg) +{ + sscape_write_unsafe(io_base, reg, + sscape_read_unsafe(io_base, reg) | 0x01); + sscape_write_unsafe(io_base, reg, + sscape_read_unsafe(io_base, reg) & 0xfe); +} + +/* + * Wait for a DMA transfer to complete. This is a "limited busy-wait", + * and all locking issues are left to the caller. + */ +static int sscape_wait_dma_unsafe(unsigned io_base, enum GA_REG reg, + unsigned timeout) +{ + while (!(sscape_read_unsafe(io_base, reg) & 0x01) && (timeout != 0)) { + udelay(100); + --timeout; + } /* while */ + + return sscape_read_unsafe(io_base, reg) & 0x01; +} + +/* + * Wait for the On-Board Processor to return its start-up + * acknowledgement sequence. This wait is too long for + * us to perform "busy-waiting", and so we must sleep. + * This in turn means that we must not be holding any + * spinlocks when we call this function. + */ +static int obp_startup_ack(struct soundscape *s, unsigned timeout) +{ + unsigned long end_time = jiffies + msecs_to_jiffies(timeout); + + do { + unsigned long flags; + int x; + + spin_lock_irqsave(&s->lock, flags); + x = host_read_unsafe(s->io_base); + spin_unlock_irqrestore(&s->lock, flags); + if (x == 0xfe || x == 0xff) + return 1; + + msleep(10); + } while (time_before(jiffies, end_time)); + + return 0; +} + +/* + * Wait for the host to return its start-up acknowledgement + * sequence. This wait is too long for us to perform + * "busy-waiting", and so we must sleep. This in turn means + * that we must not be holding any spinlocks when we call + * this function. + */ +static int host_startup_ack(struct soundscape *s, unsigned timeout) +{ + unsigned long end_time = jiffies + msecs_to_jiffies(timeout); + + do { + unsigned long flags; + int x; + + spin_lock_irqsave(&s->lock, flags); + x = host_read_unsafe(s->io_base); + spin_unlock_irqrestore(&s->lock, flags); + if (x == 0xfe) + return 1; + + msleep(10); + } while (time_before(jiffies, end_time)); + + return 0; +} + +/* + * Upload a byte-stream into the SoundScape using DMA channel A. + */ +static int upload_dma_data(struct soundscape *s, const unsigned char *data, + size_t size) +{ + unsigned long flags; + struct snd_dma_buffer dma; + int ret; + unsigned char val; + + if (!get_dmabuf(&dma, PAGE_ALIGN(32 * 1024))) + return -ENOMEM; + + spin_lock_irqsave(&s->lock, flags); + + /* + * Reset the board ... + */ + val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG); + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val & 0x3f); + + /* + * Enable the DMA channels and configure them ... + */ + val = (s->chip->dma1 << 4) | DMA_8BIT; + sscape_write_unsafe(s->io_base, GA_DMAA_REG, val); + sscape_write_unsafe(s->io_base, GA_DMAB_REG, 0x20); + + /* + * Take the board out of reset ... + */ + val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG); + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x80); + + /* + * Upload the firmware to the SoundScape + * board through the DMA channel ... + */ + while (size != 0) { + unsigned long len; + + len = min(size, dma.bytes); + memcpy(dma.area, data, len); + data += len; + size -= len; + + snd_dma_program(s->chip->dma1, dma.addr, len, DMA_MODE_WRITE); + sscape_start_dma_unsafe(s->io_base, GA_DMAA_REG); + if (!sscape_wait_dma_unsafe(s->io_base, GA_DMAA_REG, 5000)) { + /* + * Don't forget to release this spinlock we're holding + */ + spin_unlock_irqrestore(&s->lock, flags); + + snd_printk(KERN_ERR + "sscape: DMA upload has timed out\n"); + ret = -EAGAIN; + goto _release_dma; + } + } /* while */ + + set_host_mode_unsafe(s->io_base); + outb(0x0, s->io_base); + + /* + * Boot the board ... (I think) + */ + val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG); + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x40); + spin_unlock_irqrestore(&s->lock, flags); + + /* + * If all has gone well, then the board should acknowledge + * the new upload and tell us that it has rebooted OK. We + * give it 5 seconds (max) ... + */ + ret = 0; + if (!obp_startup_ack(s, 5000)) { + snd_printk(KERN_ERR "sscape: No response " + "from on-board processor after upload\n"); + ret = -EAGAIN; + } else if (!host_startup_ack(s, 5000)) { + snd_printk(KERN_ERR + "sscape: SoundScape failed to initialise\n"); + ret = -EAGAIN; + } + +_release_dma: + /* + * NOTE!!! We are NOT holding any spinlocks at this point !!! + */ + sscape_write(s, GA_DMAA_REG, (s->ic_type == IC_OPUS ? 0x40 : 0x70)); + free_dmabuf(&dma); + + return ret; +} + +/* + * Upload the bootblock(?) into the SoundScape. The only + * purpose of this block of code seems to be to tell + * us which version of the microcode we should be using. + */ +static int sscape_upload_bootblock(struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + unsigned long flags; + const struct firmware *init_fw = NULL; + int data = 0; + int ret; + + ret = request_firmware(&init_fw, "scope.cod", card->dev); + if (ret < 0) { + snd_printk(KERN_ERR "sscape: Error loading scope.cod"); + return ret; + } + ret = upload_dma_data(sscape, init_fw->data, init_fw->size); + + release_firmware(init_fw); + + spin_lock_irqsave(&sscape->lock, flags); + if (ret == 0) + data = host_read_ctrl_unsafe(sscape->io_base, 100); + + if (data & 0x10) + sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2f); + + spin_unlock_irqrestore(&sscape->lock, flags); + + data &= 0xf; + if (ret == 0 && data > 7) { + snd_printk(KERN_ERR + "sscape: timeout reading firmware version\n"); + ret = -EAGAIN; + } + + return (ret == 0) ? data : ret; +} + +/* + * Upload the microcode into the SoundScape. + */ +static int sscape_upload_microcode(struct snd_card *card, int version) +{ + struct soundscape *sscape = get_card_soundscape(card); + const struct firmware *init_fw = NULL; + char name[14]; + int err; + + snprintf(name, sizeof(name), "sndscape.co%d", version); + + err = request_firmware(&init_fw, name, card->dev); + if (err < 0) { + snd_printk(KERN_ERR "sscape: Error loading sndscape.co%d", + version); + return err; + } + err = upload_dma_data(sscape, init_fw->data, init_fw->size); + if (err == 0) + snd_printk(KERN_INFO "sscape: MIDI firmware loaded %zu KBs\n", + init_fw->size >> 10); + + release_firmware(init_fw); + + return err; +} + +/* + * Mixer control for the SoundScape's MIDI device. + */ +static int sscape_midi_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int sscape_midi_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_wss *chip = snd_kcontrol_chip(kctl); + struct snd_card *card = chip->card; + register struct soundscape *s = get_card_soundscape(card); + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + uctl->value.integer.value[0] = s->midi_vol; + spin_unlock_irqrestore(&s->lock, flags); + return 0; +} + +static int sscape_midi_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct snd_wss *chip = snd_kcontrol_chip(kctl); + struct snd_card *card = chip->card; + struct soundscape *s = get_card_soundscape(card); + unsigned long flags; + int change; + unsigned char new_val; + + spin_lock_irqsave(&s->lock, flags); + + new_val = uctl->value.integer.value[0] & 127; + /* + * We need to put the board into HOST mode before we + * can send any volume-changing HOST commands ... + */ + set_host_mode_unsafe(s->io_base); + + /* + * To successfully change the MIDI volume setting, you seem to + * have to write a volume command, write the new volume value, + * and then perform another volume-related command. Perhaps the + * first command is an "open" and the second command is a "close"? + */ + if (s->midi_vol == new_val) { + change = 0; + goto __skip_change; + } + change = host_write_ctrl_unsafe(s->io_base, CMD_SET_MIDI_VOL, 100) + && host_write_ctrl_unsafe(s->io_base, new_val, 100) + && host_write_ctrl_unsafe(s->io_base, CMD_XXX_MIDI_VOL, 100) + && host_write_ctrl_unsafe(s->io_base, new_val, 100); + s->midi_vol = new_val; +__skip_change: + + /* + * Take the board out of HOST mode and back into MIDI mode ... + */ + set_midi_mode_unsafe(s->io_base); + + spin_unlock_irqrestore(&s->lock, flags); + return change; +} + +static const struct snd_kcontrol_new midi_mixer_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MIDI", + .info = sscape_midi_info, + .get = sscape_midi_get, + .put = sscape_midi_put +}; + +/* + * The SoundScape can use two IRQs from a possible set of four. + * These IRQs are encoded as bit patterns so that they can be + * written to the control registers. + */ +static unsigned get_irq_config(int sscape_type, int irq) +{ + static const int valid_irq[] = { 9, 5, 7, 10 }; + static const int old_irq[] = { 9, 7, 5, 15 }; + unsigned cfg; + + if (sscape_type == MEDIA_FX) { + for (cfg = 0; cfg < ARRAY_SIZE(old_irq); ++cfg) + if (irq == old_irq[cfg]) + return cfg; + } else { + for (cfg = 0; cfg < ARRAY_SIZE(valid_irq); ++cfg) + if (irq == valid_irq[cfg]) + return cfg; + } + + return INVALID_IRQ; +} + +/* + * Perform certain arcane port-checks to see whether there + * is a SoundScape board lurking behind the given ports. + */ +static int detect_sscape(struct soundscape *s, long wss_io) +{ + unsigned long flags; + unsigned d; + int retval = 0; + + spin_lock_irqsave(&s->lock, flags); + + /* + * The following code is lifted from the original OSS driver, + * and as I don't have a datasheet I cannot really comment + * on what it is doing... + */ + if ((inb(HOST_CTRL_IO(s->io_base)) & 0x78) != 0) + goto _done; + + d = inb(ODIE_ADDR_IO(s->io_base)) & 0xf0; + if ((d & 0x80) != 0) + goto _done; + + if (d == 0) + s->ic_type = IC_ODIE; + else if ((d & 0x60) != 0) + s->ic_type = IC_OPUS; + else + goto _done; + + outb(0xfa, ODIE_ADDR_IO(s->io_base)); + if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0a) + goto _done; + + outb(0xfe, ODIE_ADDR_IO(s->io_base)); + if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0e) + goto _done; + + outb(0xfe, ODIE_ADDR_IO(s->io_base)); + d = inb(ODIE_DATA_IO(s->io_base)); + if (s->type != SSCAPE_VIVO && (d & 0x9f) != 0x0e) + goto _done; + + if (s->ic_type == IC_OPUS) + activate_ad1845_unsafe(s->io_base); + + if (s->type == SSCAPE_VIVO) + wss_io += 4; + + d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG); + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0); + + /* wait for WSS codec */ + for (d = 0; d < 500; d++) { + if ((inb(wss_io) & 0x80) == 0) + break; + spin_unlock_irqrestore(&s->lock, flags); + msleep(1); + spin_lock_irqsave(&s->lock, flags); + } + + if ((inb(wss_io) & 0x80) != 0) + goto _done; + + if (inb(wss_io + 2) == 0xff) + goto _done; + + d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG) & 0x3f; + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d); + + if ((inb(wss_io) & 0x80) != 0) + s->type = MEDIA_FX; + + d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG); + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0); + /* wait for WSS codec */ + for (d = 0; d < 500; d++) { + if ((inb(wss_io) & 0x80) == 0) + break; + spin_unlock_irqrestore(&s->lock, flags); + msleep(1); + spin_lock_irqsave(&s->lock, flags); + } + + /* + * SoundScape successfully detected! + */ + retval = 1; + +_done: + spin_unlock_irqrestore(&s->lock, flags); + return retval; +} + +/* + * ALSA callback function, called when attempting to open the MIDI device. + * Check that the MIDI firmware has been loaded, because we don't want + * to crash the machine. Also check that someone isn't using the hardware + * IOCTL device. + */ +static int mpu401_open(struct snd_mpu401 *mpu) +{ + if (!verify_mpu401(mpu)) { + snd_printk(KERN_ERR "sscape: MIDI disabled, " + "please load firmware\n"); + return -ENODEV; + } + + return 0; +} + +/* + * Initialse an MPU-401 subdevice for MIDI support on the SoundScape. + */ +static int create_mpu401(struct snd_card *card, int devnum, + unsigned long port, int irq) +{ + struct soundscape *sscape = get_card_soundscape(card); + struct snd_rawmidi *rawmidi; + int err; + + err = snd_mpu401_uart_new(card, devnum, MPU401_HW_MPU401, port, + MPU401_INFO_INTEGRATED, irq, &rawmidi); + if (err == 0) { + struct snd_mpu401 *mpu = rawmidi->private_data; + mpu->open_input = mpu401_open; + mpu->open_output = mpu401_open; + mpu->private_data = sscape; + + initialise_mpu401(mpu); + } + + return err; +} + + +/* + * Create an AD1845 PCM subdevice on the SoundScape. The AD1845 + * is very much like a CS4231, with a few extra bits. We will + * try to support at least some of the extra bits by overriding + * some of the CS4231 callback. + */ +static int create_ad1845(struct snd_card *card, unsigned port, + int irq, int dma1, int dma2) +{ + register struct soundscape *sscape = get_card_soundscape(card); + struct snd_wss *chip; + int err; + int codec_type = WSS_HW_DETECT; + + switch (sscape->type) { + case MEDIA_FX: + case SSCAPE: + /* + * There are some freak examples of early Soundscape cards + * with CS4231 instead of AD1848/CS4248. Unfortunately, the + * CS4231 works only in CS4248 compatibility mode on + * these cards so force it. + */ + if (sscape->ic_type != IC_OPUS) + codec_type = WSS_HW_AD1848; + break; + + case SSCAPE_VIVO: + port += 4; + break; + default: + break; + } + + err = snd_wss_create(card, port, -1, irq, dma1, dma2, + codec_type, WSS_HWSHARE_DMA1, &chip); + if (!err) { + unsigned long flags; + + if (sscape->type != SSCAPE_VIVO) { + /* + * The input clock frequency on the SoundScape must + * be 14.31818 MHz, because we must set this register + * to get the playback to sound correct ... + */ + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_wss_out(chip, AD1845_CLOCK, 0x20); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + + } + + err = snd_wss_pcm(chip, 0); + if (err < 0) { + snd_printk(KERN_ERR "sscape: No PCM device " + "for AD1845 chip\n"); + goto _error; + } + + err = snd_wss_mixer(chip); + if (err < 0) { + snd_printk(KERN_ERR "sscape: No mixer device " + "for AD1845 chip\n"); + goto _error; + } + if (chip->hardware != WSS_HW_AD1848) { + err = snd_wss_timer(chip, 0); + if (err < 0) { + snd_printk(KERN_ERR "sscape: No timer device " + "for AD1845 chip\n"); + goto _error; + } + } + + if (sscape->type != SSCAPE_VIVO) { + err = snd_ctl_add(card, + snd_ctl_new1(&midi_mixer_ctl, chip)); + if (err < 0) { + snd_printk(KERN_ERR "sscape: Could not create " + "MIDI mixer control\n"); + goto _error; + } + } + + sscape->chip = chip; + } + +_error: + return err; +} + + +/* + * Create an ALSA soundcard entry for the SoundScape, using + * the given list of port, IRQ and DMA resources. + */ +static int create_sscape(int dev, struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + unsigned dma_cfg; + unsigned irq_cfg; + unsigned mpu_irq_cfg; + struct resource *io_res; + struct resource *wss_res; + unsigned long flags; + int err; + int val; + const char *name; + + /* + * Grab IO ports that we will need to probe so that we + * can detect and control this hardware ... + */ + io_res = request_region(port[dev], 8, "SoundScape"); + if (!io_res) { + snd_printk(KERN_ERR + "sscape: can't grab port 0x%lx\n", port[dev]); + return -EBUSY; + } + wss_res = NULL; + if (sscape->type == SSCAPE_VIVO) { + wss_res = request_region(wss_port[dev], 4, "SoundScape"); + if (!wss_res) { + snd_printk(KERN_ERR "sscape: can't grab port 0x%lx\n", + wss_port[dev]); + err = -EBUSY; + goto _release_region; + } + } + + /* + * Grab one DMA channel ... + */ + err = request_dma(dma[dev], "SoundScape"); + if (err < 0) { + snd_printk(KERN_ERR "sscape: can't grab DMA %d\n", dma[dev]); + goto _release_region; + } + + spin_lock_init(&sscape->lock); + sscape->io_res = io_res; + sscape->wss_res = wss_res; + sscape->io_base = port[dev]; + + if (!detect_sscape(sscape, wss_port[dev])) { + printk(KERN_ERR "sscape: hardware not detected at 0x%x\n", + sscape->io_base); + err = -ENODEV; + goto _release_dma; + } + + switch (sscape->type) { + case MEDIA_FX: + name = "MediaFX/SoundFX"; + break; + case SSCAPE: + name = "Soundscape"; + break; + case SSCAPE_PNP: + name = "Soundscape PnP"; + break; + case SSCAPE_VIVO: + name = "Soundscape VIVO"; + break; + default: + name = "unknown Soundscape"; + break; + } + + printk(KERN_INFO "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n", + name, sscape->io_base, irq[dev], dma[dev]); + + /* + * Check that the user didn't pass us garbage data ... + */ + irq_cfg = get_irq_config(sscape->type, irq[dev]); + if (irq_cfg == INVALID_IRQ) { + snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", irq[dev]); + err = -ENXIO; + goto _release_dma; + } + + mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]); + if (mpu_irq_cfg == INVALID_IRQ) { + snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", mpu_irq[dev]); + err = -ENXIO; + goto _release_dma; + } + + /* + * Tell the on-board devices where their resources are (I think - + * I can't be sure without a datasheet ... So many magic values!) + */ + spin_lock_irqsave(&sscape->lock, flags); + + sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e); + sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00); + + /* + * Enable and configure the DMA channels ... + */ + sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50); + dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70); + sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg); + sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20); + + mpu_irq_cfg |= mpu_irq_cfg << 2; + val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7; + if (joystick[dev]) + val |= 8; + sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10); + sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg); + sscape_write_unsafe(sscape->io_base, + GA_CDCFG_REG, 0x09 | DMA_8BIT + | (dma[dev] << 4) | (irq_cfg << 1)); + /* + * Enable the master IRQ ... + */ + sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80); + + spin_unlock_irqrestore(&sscape->lock, flags); + + /* + * We have now enabled the codec chip, and so we should + * detect the AD1845 device ... + */ + err = create_ad1845(card, wss_port[dev], irq[dev], + dma[dev], dma2[dev]); + if (err < 0) { + snd_printk(KERN_ERR + "sscape: No AD1845 device at 0x%lx, IRQ %d\n", + wss_port[dev], irq[dev]); + goto _release_dma; + } + strcpy(card->driver, "SoundScape"); + strcpy(card->shortname, name); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, IRQ %d, DMA1 %d, DMA2 %d\n", + name, sscape->chip->port, sscape->chip->irq, + sscape->chip->dma1, sscape->chip->dma2); + +#define MIDI_DEVNUM 0 + if (sscape->type != SSCAPE_VIVO) { + err = sscape_upload_bootblock(card); + if (err >= 0) + err = sscape_upload_microcode(card, err); + + if (err == 0) { + err = create_mpu401(card, MIDI_DEVNUM, port[dev], + mpu_irq[dev]); + if (err < 0) { + snd_printk(KERN_ERR "sscape: Failed to create " + "MPU-401 device at 0x%lx\n", + port[dev]); + goto _release_dma; + } + + /* + * Initialize mixer + */ + spin_lock_irqsave(&sscape->lock, flags); + sscape->midi_vol = 0; + host_write_ctrl_unsafe(sscape->io_base, + CMD_SET_MIDI_VOL, 100); + host_write_ctrl_unsafe(sscape->io_base, + sscape->midi_vol, 100); + host_write_ctrl_unsafe(sscape->io_base, + CMD_XXX_MIDI_VOL, 100); + host_write_ctrl_unsafe(sscape->io_base, + sscape->midi_vol, 100); + host_write_ctrl_unsafe(sscape->io_base, + CMD_SET_EXTMIDI, 100); + host_write_ctrl_unsafe(sscape->io_base, + 0, 100); + host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100); + + set_midi_mode_unsafe(sscape->io_base); + spin_unlock_irqrestore(&sscape->lock, flags); + } + } + + /* + * Now that we have successfully created this sound card, + * it is safe to store the pointer. + * NOTE: we only register the sound card's "destructor" + * function now that our "constructor" has completed. + */ + card->private_free = soundscape_free; + + return 0; + +_release_dma: + free_dma(dma[dev]); + +_release_region: + release_and_free_resource(wss_res); + release_and_free_resource(io_res); + + return err; +} + + +static int snd_sscape_match(struct device *pdev, unsigned int i) +{ + /* + * Make sure we were given ALL of the other parameters. + */ + if (port[i] == SNDRV_AUTO_PORT) + return 0; + + if (irq[i] == SNDRV_AUTO_IRQ || + mpu_irq[i] == SNDRV_AUTO_IRQ || + dma[i] == SNDRV_AUTO_DMA) { + printk(KERN_INFO + "sscape: insufficient parameters, " + "need IO, IRQ, MPU-IRQ and DMA\n"); + return 0; + } + + return 1; +} + +static int snd_sscape_probe(struct device *pdev, unsigned int dev) +{ + struct snd_card *card; + struct soundscape *sscape; + int ret; + + ret = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct soundscape), &card); + if (ret < 0) + return ret; + + sscape = get_card_soundscape(card); + sscape->type = SSCAPE; + + dma[dev] &= 0x03; + + ret = create_sscape(dev, card); + if (ret < 0) + goto _release_card; + + ret = snd_card_register(card); + if (ret < 0) { + snd_printk(KERN_ERR "sscape: Failed to register sound card\n"); + goto _release_card; + } + dev_set_drvdata(pdev, card); + return 0; + +_release_card: + snd_card_free(card); + return ret; +} + +static int snd_sscape_remove(struct device *devptr, unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#define DEV_NAME "sscape" + +static struct isa_driver snd_sscape_driver = { + .match = snd_sscape_match, + .probe = snd_sscape_probe, + .remove = snd_sscape_remove, + /* FIXME: suspend/resume */ + .driver = { + .name = DEV_NAME + }, +}; + +#ifdef CONFIG_PNP +static inline int get_next_autoindex(int i) +{ + while (i < SNDRV_CARDS && port[i] != SNDRV_AUTO_PORT) + ++i; + return i; +} + + +static int sscape_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int idx = 0; + struct pnp_dev *dev; + struct snd_card *card; + struct soundscape *sscape; + int ret; + + /* + * Allow this function to fail *quietly* if all the ISA PnP + * devices were configured using module parameters instead. + */ + idx = get_next_autoindex(idx); + if (idx >= SNDRV_CARDS) + return -ENOSPC; + + /* + * Check that we still have room for another sound card ... + */ + dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL); + if (!dev) + return -ENODEV; + + if (!pnp_is_active(dev)) { + if (pnp_activate_dev(dev) < 0) { + snd_printk(KERN_INFO "sscape: device is inactive\n"); + return -EBUSY; + } + } + + /* + * Create a new ALSA sound card entry, in anticipation + * of detecting our hardware ... + */ + ret = snd_card_new(&pcard->card->dev, + index[idx], id[idx], THIS_MODULE, + sizeof(struct soundscape), &card); + if (ret < 0) + return ret; + + sscape = get_card_soundscape(card); + + /* + * Identify card model ... + */ + if (!strncmp("ENS4081", pid->id, 7)) + sscape->type = SSCAPE_VIVO; + else + sscape->type = SSCAPE_PNP; + + /* + * Read the correct parameters off the ISA PnP bus ... + */ + port[idx] = pnp_port_start(dev, 0); + irq[idx] = pnp_irq(dev, 0); + mpu_irq[idx] = pnp_irq(dev, 1); + dma[idx] = pnp_dma(dev, 0) & 0x03; + if (sscape->type == SSCAPE_PNP) { + dma2[idx] = dma[idx]; + wss_port[idx] = CODEC_IO(port[idx]); + } else { + wss_port[idx] = pnp_port_start(dev, 1); + dma2[idx] = pnp_dma(dev, 1); + } + + ret = create_sscape(idx, card); + if (ret < 0) + goto _release_card; + + ret = snd_card_register(card); + if (ret < 0) { + snd_printk(KERN_ERR "sscape: Failed to register sound card\n"); + goto _release_card; + } + + pnp_set_card_drvdata(pcard, card); + ++idx; + return 0; + +_release_card: + snd_card_free(card); + return ret; +} + +static void sscape_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +static struct pnp_card_driver sscape_pnpc_driver = { + .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, + .name = "sscape", + .id_table = sscape_pnpids, + .probe = sscape_pnp_detect, + .remove = sscape_pnp_remove, +}; + +#endif /* CONFIG_PNP */ + +static int __init sscape_init(void) +{ + int err; + + err = isa_register_driver(&snd_sscape_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_card_driver(&sscape_pnpc_driver); + if (!err) + pnp_registered = 1; + + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit sscape_exit(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_card_driver(&sscape_pnpc_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_sscape_driver); +} + +module_init(sscape_init); +module_exit(sscape_exit); diff --git a/sound/isa/wavefront/Makefile b/sound/isa/wavefront/Makefile new file mode 100644 index 000000000..601bdddd4 --- /dev/null +++ b/sound/isa/wavefront/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-wavefront-objs := wavefront.o wavefront_fx.o wavefront_synth.o wavefront_midi.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_WAVEFRONT) += snd-wavefront.o diff --git a/sound/isa/wavefront/wavefront.c b/sound/isa/wavefront/wavefront.c new file mode 100644 index 000000000..b1989facd --- /dev/null +++ b/sound/isa/wavefront/wavefront.c @@ -0,0 +1,682 @@ +/* + * ALSA card-level driver for Turtle Beach Wavefront cards + * (Maui,Tropez,Tropez+) + * + * Copyright (c) 1997-1999 by Paul Barton-Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Paul Barton-Davis "); +MODULE_DESCRIPTION("Turtle Beach Wavefront"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Turtle Beach,Maui/Tropez/Tropez+}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long cs4232_pcm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int cs4232_pcm_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static long cs4232_mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int cs4232_mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */ +static long ics2115_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int ics2115_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,9,11,12,15 */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static bool use_cs4232_midi[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for WaveFront soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for WaveFront soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable WaveFront soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "ISA PnP detection for WaveFront soundcards."); +#endif +module_param_hw_array(cs4232_pcm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(cs4232_pcm_port, "Port # for CS4232 PCM interface."); +module_param_hw_array(cs4232_pcm_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(cs4232_pcm_irq, "IRQ # for CS4232 PCM interface."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for CS4232 PCM interface."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for CS4232 PCM interface."); +module_param_hw_array(cs4232_mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(cs4232_mpu_port, "port # for CS4232 MPU-401 interface."); +module_param_hw_array(cs4232_mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(cs4232_mpu_irq, "IRQ # for CS4232 MPU-401 interface."); +module_param_hw_array(ics2115_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(ics2115_irq, "IRQ # for ICS2115."); +module_param_hw_array(ics2115_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(ics2115_port, "Port # for ICS2115."); +module_param_hw_array(fm_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port #."); +module_param_array(use_cs4232_midi, bool, NULL, 0444); +MODULE_PARM_DESC(use_cs4232_midi, "Use CS4232 MPU-401 interface (inaccessibly located inside your computer)"); + +#ifdef CONFIG_PNP +static int isa_registered; +static int pnp_registered; + +static const struct pnp_card_device_id snd_wavefront_pnpids[] = { + /* Tropez */ + { .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } }, + /* Tropez+ */ + { .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_wavefront_pnpids); + +static int +snd_wavefront_pnp (int dev, snd_wavefront_card_t *acard, struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + /* Check for each logical device. */ + + /* CS4232 chip (aka "windows sound system") is logical device 0 */ + + acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->wss == NULL) + return -EBUSY; + + /* there is a game port at logical device 1, but we ignore it completely */ + + /* the control interface is logical device 2, but we ignore it + completely. in fact, nobody even seems to know what it + does. + */ + + /* Only configure the CS4232 MIDI interface if its been + specifically requested. It is logical device 3. + */ + + if (use_cs4232_midi[dev]) { + acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL); + if (acard->mpu == NULL) + return -EBUSY; + } + + /* The ICS2115 synth is logical device 4 */ + + acard->synth = pnp_request_card_device(card, id->devs[3].id, NULL); + if (acard->synth == NULL) + return -EBUSY; + + /* PCM/FM initialization */ + + pdev = acard->wss; + + /* An interesting note from the Tropez+ FAQ: + + Q. [Ports] Why is the base address of the WSS I/O ports off by 4? + + A. WSS I/O requires a block of 8 I/O addresses ("ports"). Of these, the first + 4 are used to identify and configure the board. With the advent of PnP, + these first 4 addresses have become obsolete, and software applications + only use the last 4 addresses to control the codec chip. Therefore, the + base address setting "skips past" the 4 unused addresses. + + */ + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "PnP WSS pnp configure failure\n"); + return err; + } + + cs4232_pcm_port[dev] = pnp_port_start(pdev, 0); + fm_port[dev] = pnp_port_start(pdev, 1); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + cs4232_pcm_irq[dev] = pnp_irq(pdev, 0); + + /* Synth initialization */ + + pdev = acard->synth; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "PnP ICS2115 pnp configure failure\n"); + return err; + } + + ics2115_port[dev] = pnp_port_start(pdev, 0); + ics2115_irq[dev] = pnp_irq(pdev, 0); + + /* CS4232 MPU initialization. Configure this only if + explicitly requested, since its physically inaccessible and + consumes another IRQ. + */ + + if (use_cs4232_midi[dev]) { + + pdev = acard->mpu; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "PnP MPU401 pnp configure failure\n"); + cs4232_mpu_port[dev] = SNDRV_AUTO_PORT; + } else { + cs4232_mpu_port[dev] = pnp_port_start(pdev, 0); + cs4232_mpu_irq[dev] = pnp_irq(pdev, 0); + } + + snd_printk (KERN_INFO "CS4232 MPU: port=0x%lx, irq=%i\n", + cs4232_mpu_port[dev], + cs4232_mpu_irq[dev]); + } + + snd_printdd ("CS4232: pcm port=0x%lx, fm port=0x%lx, dma1=%i, dma2=%i, irq=%i\nICS2115: port=0x%lx, irq=%i\n", + cs4232_pcm_port[dev], + fm_port[dev], + dma1[dev], + dma2[dev], + cs4232_pcm_irq[dev], + ics2115_port[dev], + ics2115_irq[dev]); + + return 0; +} + +#endif /* CONFIG_PNP */ + +static irqreturn_t snd_wavefront_ics2115_interrupt(int irq, void *dev_id) +{ + snd_wavefront_card_t *acard; + + acard = (snd_wavefront_card_t *) dev_id; + + if (acard == NULL) + return IRQ_NONE; + + if (acard->wavefront.interrupts_are_midi) { + snd_wavefront_midi_interrupt (acard); + } else { + snd_wavefront_internal_interrupt (acard); + } + return IRQ_HANDLED; +} + +static struct snd_hwdep *snd_wavefront_new_synth(struct snd_card *card, + int hw_dev, + snd_wavefront_card_t *acard) +{ + struct snd_hwdep *wavefront_synth; + + if (snd_wavefront_detect (acard) < 0) { + return NULL; + } + + if (snd_wavefront_start (&acard->wavefront) < 0) { + return NULL; + } + + if (snd_hwdep_new(card, "WaveFront", hw_dev, &wavefront_synth) < 0) + return NULL; + strcpy (wavefront_synth->name, + "WaveFront (ICS2115) wavetable synthesizer"); + wavefront_synth->ops.open = snd_wavefront_synth_open; + wavefront_synth->ops.release = snd_wavefront_synth_release; + wavefront_synth->ops.ioctl = snd_wavefront_synth_ioctl; + + return wavefront_synth; +} + +static struct snd_hwdep *snd_wavefront_new_fx(struct snd_card *card, + int hw_dev, + snd_wavefront_card_t *acard, + unsigned long port) + +{ + struct snd_hwdep *fx_processor; + + if (snd_wavefront_fx_start (&acard->wavefront)) { + snd_printk (KERN_ERR "cannot initialize YSS225 FX processor"); + return NULL; + } + + if (snd_hwdep_new (card, "YSS225", hw_dev, &fx_processor) < 0) + return NULL; + sprintf (fx_processor->name, "YSS225 FX Processor at 0x%lx", port); + fx_processor->ops.open = snd_wavefront_fx_open; + fx_processor->ops.release = snd_wavefront_fx_release; + fx_processor->ops.ioctl = snd_wavefront_fx_ioctl; + + return fx_processor; +} + +static snd_wavefront_mpu_id internal_id = internal_mpu; +static snd_wavefront_mpu_id external_id = external_mpu; + +static struct snd_rawmidi *snd_wavefront_new_midi(struct snd_card *card, + int midi_dev, + snd_wavefront_card_t *acard, + unsigned long port, + snd_wavefront_mpu_id mpu) + +{ + struct snd_rawmidi *rmidi; + static int first = 1; + + if (first) { + first = 0; + acard->wavefront.midi.base = port; + if (snd_wavefront_midi_start (acard)) { + snd_printk (KERN_ERR "cannot initialize MIDI interface\n"); + return NULL; + } + } + + if (snd_rawmidi_new (card, "WaveFront MIDI", midi_dev, 1, 1, &rmidi) < 0) + return NULL; + + if (mpu == internal_mpu) { + strcpy(rmidi->name, "WaveFront MIDI (Internal)"); + rmidi->private_data = &internal_id; + } else { + strcpy(rmidi->name, "WaveFront MIDI (External)"); + rmidi->private_data = &external_id; + } + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_wavefront_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_wavefront_midi_input); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + return rmidi; +} + +static void +snd_wavefront_free(struct snd_card *card) +{ + snd_wavefront_card_t *acard = (snd_wavefront_card_t *)card->private_data; + + if (acard) { + release_and_free_resource(acard->wavefront.res_base); + if (acard->wavefront.irq > 0) + free_irq(acard->wavefront.irq, (void *)acard); + } +} + +static int snd_wavefront_card_new(struct device *pdev, int dev, + struct snd_card **cardp) +{ + struct snd_card *card; + snd_wavefront_card_t *acard; + int err; + + err = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(snd_wavefront_card_t), &card); + if (err < 0) + return err; + + acard = card->private_data; + acard->wavefront.irq = -1; + spin_lock_init(&acard->wavefront.irq_lock); + init_waitqueue_head(&acard->wavefront.interrupt_sleeper); + spin_lock_init(&acard->wavefront.midi.open); + spin_lock_init(&acard->wavefront.midi.virtual); + acard->wavefront.card = card; + card->private_free = snd_wavefront_free; + + *cardp = card; + return 0; +} + +static int +snd_wavefront_probe (struct snd_card *card, int dev) +{ + snd_wavefront_card_t *acard = card->private_data; + struct snd_wss *chip; + struct snd_hwdep *wavefront_synth; + struct snd_rawmidi *ics2115_internal_rmidi = NULL; + struct snd_rawmidi *ics2115_external_rmidi = NULL; + struct snd_hwdep *fx_processor; + int hw_dev = 0, midi_dev = 0, err; + + /* --------- PCM --------------- */ + + err = snd_wss_create(card, cs4232_pcm_port[dev], -1, + cs4232_pcm_irq[dev], dma1[dev], dma2[dev], + WSS_HW_DETECT, 0, &chip); + if (err < 0) { + snd_printk(KERN_ERR "can't allocate WSS device\n"); + return err; + } + + err = snd_wss_pcm(chip, 0); + if (err < 0) + return err; + + err = snd_wss_timer(chip, 0); + if (err < 0) + return err; + + /* ---------- OPL3 synth --------- */ + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + struct snd_opl3 *opl3; + + err = snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2, + OPL3_HW_OPL3_CS, 0, &opl3); + if (err < 0) { + snd_printk (KERN_ERR "can't allocate or detect OPL3 synth\n"); + return err; + } + + err = snd_opl3_hwdep_new(opl3, hw_dev, 1, NULL); + if (err < 0) + return err; + hw_dev++; + } + + /* ------- ICS2115 Wavetable synth ------- */ + + acard->wavefront.res_base = request_region(ics2115_port[dev], 16, + "ICS2115"); + if (acard->wavefront.res_base == NULL) { + snd_printk(KERN_ERR "unable to grab ICS2115 i/o region 0x%lx-0x%lx\n", + ics2115_port[dev], ics2115_port[dev] + 16 - 1); + return -EBUSY; + } + if (request_irq(ics2115_irq[dev], snd_wavefront_ics2115_interrupt, + 0, "ICS2115", acard)) { + snd_printk(KERN_ERR "unable to use ICS2115 IRQ %d\n", ics2115_irq[dev]); + return -EBUSY; + } + + acard->wavefront.irq = ics2115_irq[dev]; + acard->wavefront.base = ics2115_port[dev]; + + wavefront_synth = snd_wavefront_new_synth(card, hw_dev, acard); + if (wavefront_synth == NULL) { + snd_printk (KERN_ERR "can't create WaveFront synth device\n"); + return -ENOMEM; + } + + strcpy (wavefront_synth->name, "ICS2115 Wavetable MIDI Synthesizer"); + wavefront_synth->iface = SNDRV_HWDEP_IFACE_ICS2115; + hw_dev++; + + /* --------- Mixer ------------ */ + + err = snd_wss_mixer(chip); + if (err < 0) { + snd_printk (KERN_ERR "can't allocate mixer device\n"); + return err; + } + + /* -------- CS4232 MPU-401 interface -------- */ + + if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) { + err = snd_mpu401_uart_new(card, midi_dev, MPU401_HW_CS4232, + cs4232_mpu_port[dev], 0, + cs4232_mpu_irq[dev], NULL); + if (err < 0) { + snd_printk (KERN_ERR "can't allocate CS4232 MPU-401 device\n"); + return err; + } + midi_dev++; + } + + /* ------ ICS2115 internal MIDI ------------ */ + + if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) { + ics2115_internal_rmidi = + snd_wavefront_new_midi (card, + midi_dev, + acard, + ics2115_port[dev], + internal_mpu); + if (ics2115_internal_rmidi == NULL) { + snd_printk (KERN_ERR "can't setup ICS2115 internal MIDI device\n"); + return -ENOMEM; + } + midi_dev++; + } + + /* ------ ICS2115 external MIDI ------------ */ + + if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) { + ics2115_external_rmidi = + snd_wavefront_new_midi (card, + midi_dev, + acard, + ics2115_port[dev], + external_mpu); + if (ics2115_external_rmidi == NULL) { + snd_printk (KERN_ERR "can't setup ICS2115 external MIDI device\n"); + return -ENOMEM; + } + midi_dev++; + } + + /* FX processor for Tropez+ */ + + if (acard->wavefront.has_fx) { + fx_processor = snd_wavefront_new_fx (card, + hw_dev, + acard, + ics2115_port[dev]); + if (fx_processor == NULL) { + snd_printk (KERN_ERR "can't setup FX device\n"); + return -ENOMEM; + } + + hw_dev++; + + strcpy(card->driver, "Tropez+"); + strcpy(card->shortname, "Turtle Beach Tropez+"); + } else { + /* Need a way to distinguish between Maui and Tropez */ + strcpy(card->driver, "WaveFront"); + strcpy(card->shortname, "Turtle Beach WaveFront"); + } + + /* ----- Register the card --------- */ + + /* Not safe to include "Turtle Beach" in longname, due to + length restrictions + */ + + sprintf(card->longname, "%s PCM 0x%lx irq %d dma %d", + card->driver, + chip->port, + cs4232_pcm_irq[dev], + dma1[dev]); + + if (dma2[dev] >= 0 && dma2[dev] < 8) + sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]); + + if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) { + sprintf (card->longname + strlen (card->longname), + " MPU-401 0x%lx irq %d", + cs4232_mpu_port[dev], + cs4232_mpu_irq[dev]); + } + + sprintf (card->longname + strlen (card->longname), + " SYNTH 0x%lx irq %d", + ics2115_port[dev], + ics2115_irq[dev]); + + return snd_card_register(card); +} + +static int snd_wavefront_isa_match(struct device *pdev, + unsigned int dev) +{ + if (!enable[dev]) + return 0; +#ifdef CONFIG_PNP + if (isapnp[dev]) + return 0; +#endif + if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR "specify CS4232 port\n"); + return 0; + } + if (ics2115_port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR "specify ICS2115 port\n"); + return 0; + } + return 1; +} + +static int snd_wavefront_isa_probe(struct device *pdev, + unsigned int dev) +{ + struct snd_card *card; + int err; + + err = snd_wavefront_card_new(pdev, dev, &card); + if (err < 0) + return err; + if ((err = snd_wavefront_probe(card, dev)) < 0) { + snd_card_free(card); + return err; + } + + dev_set_drvdata(pdev, card); + return 0; +} + +static int snd_wavefront_isa_remove(struct device *devptr, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + return 0; +} + +#define DEV_NAME "wavefront" + +static struct isa_driver snd_wavefront_driver = { + .match = snd_wavefront_isa_match, + .probe = snd_wavefront_isa_probe, + .remove = snd_wavefront_isa_remove, + /* FIXME: suspend, resume */ + .driver = { + .name = DEV_NAME + }, +}; + + +#ifdef CONFIG_PNP +static int snd_wavefront_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int dev; + struct snd_card *card; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + res = snd_wavefront_card_new(&pcard->card->dev, dev, &card); + if (res < 0) + return res; + + if (snd_wavefront_pnp (dev, card->private_data, pcard, pid) < 0) { + if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) { + snd_printk (KERN_ERR "isapnp detection failed\n"); + snd_card_free (card); + return -ENODEV; + } + } + + if ((res = snd_wavefront_probe(card, dev)) < 0) + return res; + + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; +} + +static void snd_wavefront_pnp_remove(struct pnp_card_link *pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); +} + +static struct pnp_card_driver wavefront_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "wavefront", + .id_table = snd_wavefront_pnpids, + .probe = snd_wavefront_pnp_detect, + .remove = snd_wavefront_pnp_remove, + /* FIXME: suspend,resume */ +}; + +#endif /* CONFIG_PNP */ + +static int __init alsa_card_wavefront_init(void) +{ + int err; + + err = isa_register_driver(&snd_wavefront_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_card_driver(&wavefront_pnpc_driver); + if (!err) + pnp_registered = 1; + + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_wavefront_exit(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_card_driver(&wavefront_pnpc_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_wavefront_driver); +} + +module_init(alsa_card_wavefront_init) +module_exit(alsa_card_wavefront_exit) diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c new file mode 100644 index 000000000..0608a5a42 --- /dev/null +++ b/sound/isa/wavefront/wavefront_fx.c @@ -0,0 +1,285 @@ +/* + * Copyright (c) 1998-2002 by Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Control bits for the Load Control Register + */ + +#define FX_LSB_TRANSFER 0x01 /* transfer after DSP LSB byte written */ +#define FX_MSB_TRANSFER 0x02 /* transfer after DSP MSB byte written */ +#define FX_AUTO_INCR 0x04 /* auto-increment DSP address after transfer */ + +#define WAIT_IDLE 0xff + +static int +wavefront_fx_idle (snd_wavefront_t *dev) + +{ + int i; + unsigned int x = 0x80; + + for (i = 0; i < 1000; i++) { + x = inb (dev->fx_status); + if ((x & 0x80) == 0) { + break; + } + } + + if (x & 0x80) { + snd_printk ("FX device never idle.\n"); + return 0; + } + + return (1); +} + +static void +wavefront_fx_mute (snd_wavefront_t *dev, int onoff) + +{ + if (!wavefront_fx_idle(dev)) { + return; + } + + outb (onoff ? 0x02 : 0x00, dev->fx_op); +} + +static int +wavefront_fx_memset (snd_wavefront_t *dev, + int page, + int addr, + int cnt, + unsigned short *data) +{ + if (page < 0 || page > 7) { + snd_printk ("FX memset: " + "page must be >= 0 and <= 7\n"); + return -EINVAL; + } + + if (addr < 0 || addr > 0x7f) { + snd_printk ("FX memset: " + "addr must be >= 0 and <= 7f\n"); + return -EINVAL; + } + + if (cnt == 1) { + + outb (FX_LSB_TRANSFER, dev->fx_lcr); + outb (page, dev->fx_dsp_page); + outb (addr, dev->fx_dsp_addr); + outb ((data[0] >> 8), dev->fx_dsp_msb); + outb ((data[0] & 0xff), dev->fx_dsp_lsb); + + snd_printk ("FX: addr %d:%x set to 0x%x\n", + page, addr, data[0]); + + } else { + int i; + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (page, dev->fx_dsp_page); + outb (addr, dev->fx_dsp_addr); + + for (i = 0; i < cnt; i++) { + outb ((data[i] >> 8), dev->fx_dsp_msb); + outb ((data[i] & 0xff), dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) { + break; + } + } + + if (i != cnt) { + snd_printk ("FX memset " + "(0x%x, 0x%x, 0x%lx, %d) incomplete\n", + page, addr, (unsigned long) data, cnt); + return -EIO; + } + } + + return 0; +} + +int +snd_wavefront_fx_detect (snd_wavefront_t *dev) + +{ + /* This is a crude check, but its the best one I have for now. + Certainly on the Maui and the Tropez, wavefront_fx_idle() will + report "never idle", which suggests that this test should + work OK. + */ + + if (inb (dev->fx_status) & 0x80) { + snd_printk ("Hmm, probably a Maui or Tropez.\n"); + return -1; + } + + return 0; +} + +int +snd_wavefront_fx_open (struct snd_hwdep *hw, struct file *file) + +{ + if (!try_module_get(hw->card->module)) + return -EFAULT; + file->private_data = hw; + return 0; +} + +int +snd_wavefront_fx_release (struct snd_hwdep *hw, struct file *file) + +{ + module_put(hw->card->module); + return 0; +} + +int +snd_wavefront_fx_ioctl (struct snd_hwdep *sdev, struct file *file, + unsigned int cmd, unsigned long arg) + +{ + struct snd_card *card; + snd_wavefront_card_t *acard; + snd_wavefront_t *dev; + wavefront_fx_info r; + unsigned short *page_data = NULL; + unsigned short *pd; + int err = 0; + + card = sdev->card; + if (snd_BUG_ON(!card)) + return -ENODEV; + if (snd_BUG_ON(!card->private_data)) + return -ENODEV; + + acard = card->private_data; + dev = &acard->wavefront; + + if (copy_from_user (&r, (void __user *)arg, sizeof (wavefront_fx_info))) + return -EFAULT; + + switch (r.request) { + case WFFX_MUTE: + wavefront_fx_mute (dev, r.data[0]); + return -EIO; + + case WFFX_MEMSET: + if (r.data[2] <= 0) { + snd_printk ("cannot write " + "<= 0 bytes to FX\n"); + return -EIO; + } else if (r.data[2] == 1) { + pd = (unsigned short *) &r.data[3]; + } else { + if (r.data[2] > 256) { + snd_printk ("cannot write " + "> 512 bytes to FX\n"); + return -EIO; + } + page_data = memdup_user((unsigned char __user *) + r.data[3], + r.data[2] * sizeof(short)); + if (IS_ERR(page_data)) + return PTR_ERR(page_data); + pd = page_data; + } + + err = wavefront_fx_memset (dev, + r.data[0], /* page */ + r.data[1], /* addr */ + r.data[2], /* cnt */ + pd); + kfree(page_data); + break; + + default: + snd_printk ("FX: ioctl %d not yet supported\n", + r.request); + return -ENOTTY; + } + return err; +} + +/* YSS225 initialization. + + This code was developed using DOSEMU. The Turtle Beach SETUPSND + utility was run with I/O tracing in DOSEMU enabled, and a reconstruction + of the port I/O done, using the Yamaha faxback document as a guide + to add more logic to the code. Its really pretty weird. + + This is the approach of just dumping the whole I/O + sequence as a series of port/value pairs and a simple loop + that outputs it. +*/ + +int +snd_wavefront_fx_start (snd_wavefront_t *dev) +{ + unsigned int i; + int err; + const struct firmware *firmware = NULL; + + if (dev->fx_initialized) + return 0; + + err = request_firmware(&firmware, "yamaha/yss225_registers.bin", + dev->card->dev); + if (err < 0) { + err = -1; + goto out; + } + + for (i = 0; i + 1 < firmware->size; i += 2) { + if (firmware->data[i] >= 8 && firmware->data[i] < 16) { + outb(firmware->data[i + 1], + dev->base + firmware->data[i]); + } else if (firmware->data[i] == WAIT_IDLE) { + if (!wavefront_fx_idle(dev)) { + err = -1; + goto out; + } + } else { + snd_printk(KERN_ERR "invalid address" + " in register data\n"); + err = -1; + goto out; + } + } + + dev->fx_initialized = 1; + err = 0; + +out: + release_firmware(firmware); + return err; +} + +MODULE_FIRMWARE("yamaha/yss225_registers.bin"); diff --git a/sound/isa/wavefront/wavefront_midi.c b/sound/isa/wavefront/wavefront_midi.c new file mode 100644 index 000000000..556b14738 --- /dev/null +++ b/sound/isa/wavefront/wavefront_midi.c @@ -0,0 +1,575 @@ +/* + * Copyright (C) by Paul Barton-Davis 1998-1999 + * + * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) + * Version 2 (June 1991). See the "COPYING" file distributed with this + * software for more info. + */ + +/* The low level driver for the WaveFront ICS2115 MIDI interface(s) + * + * Note that there is also an MPU-401 emulation (actually, a UART-401 + * emulation) on the CS4232 on the Tropez and Tropez Plus. This code + * has nothing to do with that interface at all. + * + * The interface is essentially just a UART-401, but is has the + * interesting property of supporting what Turtle Beach called + * "Virtual MIDI" mode. In this mode, there are effectively *two* + * MIDI buses accessible via the interface, one that is routed + * solely to/from the external WaveFront synthesizer and the other + * corresponding to the pin/socket connector used to link external + * MIDI devices to the board. + * + * This driver fully supports this mode, allowing two distinct MIDI + * busses to be used completely independently, giving 32 channels of + * MIDI routing, 16 to the WaveFront synth and 16 to the external MIDI + * bus. The devices are named /dev/snd/midiCnD0 and /dev/snd/midiCnD1, + * where `n' is the card number. Note that the device numbers may be + * something other than 0 and 1 if the CS4232 UART/MPU-401 interface + * is enabled. + * + * Switching between the two is accomplished externally by the driver + * using the two otherwise unused MIDI bytes. See the code for more details. + * + * NOTE: VIRTUAL MIDI MODE IS ON BY DEFAULT (see lowlevel/isa/wavefront.c) + * + * The main reason to turn off Virtual MIDI mode is when you want to + * tightly couple the WaveFront synth with an external MIDI + * device. You won't be able to distinguish the source of any MIDI + * data except via SysEx ID, but thats probably OK, since for the most + * part, the WaveFront won't be sending any MIDI data at all. + * + * The main reason to turn on Virtual MIDI Mode is to provide two + * completely independent 16-channel MIDI buses, one to the + * WaveFront and one to any external MIDI devices. Given the 32 + * voice nature of the WaveFront, its pretty easy to find a use + * for all 16 channels driving just that synth. + * + */ + +#include +#include +#include +#include +#include +#include + +static inline int +wf_mpu_status (snd_wavefront_midi_t *midi) + +{ + return inb (midi->mpu_status_port); +} + +static inline int +input_avail (snd_wavefront_midi_t *midi) + +{ + return !(wf_mpu_status(midi) & INPUT_AVAIL); +} + +static inline int +output_ready (snd_wavefront_midi_t *midi) + +{ + return !(wf_mpu_status(midi) & OUTPUT_READY); +} + +static inline int +read_data (snd_wavefront_midi_t *midi) + +{ + return inb (midi->mpu_data_port); +} + +static inline void +write_data (snd_wavefront_midi_t *midi, unsigned char byte) + +{ + outb (byte, midi->mpu_data_port); +} + +static snd_wavefront_midi_t * +get_wavefront_midi (struct snd_rawmidi_substream *substream) + +{ + struct snd_card *card; + snd_wavefront_card_t *acard; + + if (substream == NULL || substream->rmidi == NULL) + return NULL; + + card = substream->rmidi->card; + + if (card == NULL) + return NULL; + + if (card->private_data == NULL) + return NULL; + + acard = card->private_data; + + return &acard->wavefront.midi; +} + +static void snd_wavefront_midi_output_write(snd_wavefront_card_t *card) +{ + snd_wavefront_midi_t *midi = &card->wavefront.midi; + snd_wavefront_mpu_id mpu; + unsigned long flags; + unsigned char midi_byte; + int max = 256, mask = 1; + int timeout; + + /* Its not OK to try to change the status of "virtuality" of + the MIDI interface while we're outputting stuff. See + snd_wavefront_midi_{enable,disable}_virtual () for the + other half of this. + + The first loop attempts to flush any data from the + current output device, and then the second + emits the switch byte (if necessary), and starts + outputting data for the output device currently in use. + */ + + if (midi->substream_output[midi->output_mpu] == NULL) { + goto __second; + } + + while (max > 0) { + + /* XXX fix me - no hard timing loops allowed! */ + + for (timeout = 30000; timeout > 0; timeout--) { + if (output_ready (midi)) + break; + } + + spin_lock_irqsave (&midi->virtual, flags); + if ((midi->mode[midi->output_mpu] & MPU401_MODE_OUTPUT) == 0) { + spin_unlock_irqrestore (&midi->virtual, flags); + goto __second; + } + if (output_ready (midi)) { + if (snd_rawmidi_transmit(midi->substream_output[midi->output_mpu], &midi_byte, 1) == 1) { + if (!midi->isvirtual || + (midi_byte != WF_INTERNAL_SWITCH && + midi_byte != WF_EXTERNAL_SWITCH)) + write_data(midi, midi_byte); + max--; + } else { + if (midi->istimer) { + if (--midi->istimer <= 0) + del_timer(&midi->timer); + } + midi->mode[midi->output_mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; + spin_unlock_irqrestore (&midi->virtual, flags); + goto __second; + } + } else { + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + spin_unlock_irqrestore (&midi->virtual, flags); + } + + __second: + + if (midi->substream_output[!midi->output_mpu] == NULL) { + return; + } + + while (max > 0) { + + /* XXX fix me - no hard timing loops allowed! */ + + for (timeout = 30000; timeout > 0; timeout--) { + if (output_ready (midi)) + break; + } + + spin_lock_irqsave (&midi->virtual, flags); + if (!midi->isvirtual) + mask = 0; + mpu = midi->output_mpu ^ mask; + mask = 0; /* don't invert the value from now */ + if ((midi->mode[mpu] & MPU401_MODE_OUTPUT) == 0) { + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + if (snd_rawmidi_transmit_empty(midi->substream_output[mpu])) + goto __timer; + if (output_ready (midi)) { + if (mpu != midi->output_mpu) { + write_data(midi, mpu == internal_mpu ? + WF_INTERNAL_SWITCH : + WF_EXTERNAL_SWITCH); + midi->output_mpu = mpu; + } else if (snd_rawmidi_transmit(midi->substream_output[mpu], &midi_byte, 1) == 1) { + if (!midi->isvirtual || + (midi_byte != WF_INTERNAL_SWITCH && + midi_byte != WF_EXTERNAL_SWITCH)) + write_data(midi, midi_byte); + max--; + } else { + __timer: + if (midi->istimer) { + if (--midi->istimer <= 0) + del_timer(&midi->timer); + } + midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + } else { + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + spin_unlock_irqrestore (&midi->virtual, flags); + } +} + +static int snd_wavefront_midi_input_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (snd_BUG_ON(!substream || !substream->rmidi)) + return -ENXIO; + if (snd_BUG_ON(!substream->rmidi->private_data)) + return -ENXIO; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] |= MPU401_MODE_INPUT; + midi->substream_input[mpu] = substream; + spin_unlock_irqrestore (&midi->open, flags); + + return 0; +} + +static int snd_wavefront_midi_output_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (snd_BUG_ON(!substream || !substream->rmidi)) + return -ENXIO; + if (snd_BUG_ON(!substream->rmidi->private_data)) + return -ENXIO; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] |= MPU401_MODE_OUTPUT; + midi->substream_output[mpu] = substream; + spin_unlock_irqrestore (&midi->open, flags); + + return 0; +} + +static int snd_wavefront_midi_input_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (snd_BUG_ON(!substream || !substream->rmidi)) + return -ENXIO; + if (snd_BUG_ON(!substream->rmidi->private_data)) + return -ENXIO; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] &= ~MPU401_MODE_INPUT; + spin_unlock_irqrestore (&midi->open, flags); + + return 0; +} + +static int snd_wavefront_midi_output_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (snd_BUG_ON(!substream || !substream->rmidi)) + return -ENXIO; + if (snd_BUG_ON(!substream->rmidi->private_data)) + return -ENXIO; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] &= ~MPU401_MODE_OUTPUT; + spin_unlock_irqrestore (&midi->open, flags); + return 0; +} + +static void snd_wavefront_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (substream == NULL || substream->rmidi == NULL) + return; + + if (substream->rmidi->private_data == NULL) + return; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) { + return; + } + + spin_lock_irqsave (&midi->virtual, flags); + if (up) { + midi->mode[mpu] |= MPU401_MODE_INPUT_TRIGGER; + } else { + midi->mode[mpu] &= ~MPU401_MODE_INPUT_TRIGGER; + } + spin_unlock_irqrestore (&midi->virtual, flags); +} + +static void snd_wavefront_midi_output_timer(struct timer_list *t) +{ + snd_wavefront_midi_t *midi = from_timer(midi, t, timer); + snd_wavefront_card_t *card = midi->timer_card; + unsigned long flags; + + spin_lock_irqsave (&midi->virtual, flags); + mod_timer(&midi->timer, 1 + jiffies); + spin_unlock_irqrestore (&midi->virtual, flags); + snd_wavefront_midi_output_write(card); +} + +static void snd_wavefront_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (substream == NULL || substream->rmidi == NULL) + return; + + if (substream->rmidi->private_data == NULL) + return; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) { + return; + } + + spin_lock_irqsave (&midi->virtual, flags); + if (up) { + if ((midi->mode[mpu] & MPU401_MODE_OUTPUT_TRIGGER) == 0) { + if (!midi->istimer) { + timer_setup(&midi->timer, + snd_wavefront_midi_output_timer, + 0); + mod_timer(&midi->timer, 1 + jiffies); + } + midi->istimer++; + midi->mode[mpu] |= MPU401_MODE_OUTPUT_TRIGGER; + } + } else { + midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; + } + spin_unlock_irqrestore (&midi->virtual, flags); + + if (up) + snd_wavefront_midi_output_write((snd_wavefront_card_t *)substream->rmidi->card->private_data); +} + +void +snd_wavefront_midi_interrupt (snd_wavefront_card_t *card) + +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + static struct snd_rawmidi_substream *substream = NULL; + static int mpu = external_mpu; + int max = 128; + unsigned char byte; + + midi = &card->wavefront.midi; + + if (!input_avail (midi)) { /* not for us */ + snd_wavefront_midi_output_write(card); + return; + } + + spin_lock_irqsave (&midi->virtual, flags); + while (--max) { + + if (input_avail (midi)) { + byte = read_data (midi); + + if (midi->isvirtual) { + if (byte == WF_EXTERNAL_SWITCH) { + substream = midi->substream_input[external_mpu]; + mpu = external_mpu; + } else if (byte == WF_INTERNAL_SWITCH) { + substream = midi->substream_output[internal_mpu]; + mpu = internal_mpu; + } /* else just leave it as it is */ + } else { + substream = midi->substream_input[internal_mpu]; + mpu = internal_mpu; + } + + if (substream == NULL) { + continue; + } + + if (midi->mode[mpu] & MPU401_MODE_INPUT_TRIGGER) { + snd_rawmidi_receive(substream, &byte, 1); + } + } else { + break; + } + } + spin_unlock_irqrestore (&midi->virtual, flags); + + snd_wavefront_midi_output_write(card); +} + +void +snd_wavefront_midi_enable_virtual (snd_wavefront_card_t *card) + +{ + unsigned long flags; + + spin_lock_irqsave (&card->wavefront.midi.virtual, flags); + card->wavefront.midi.isvirtual = 1; + card->wavefront.midi.output_mpu = internal_mpu; + card->wavefront.midi.input_mpu = internal_mpu; + spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags); +} + +void +snd_wavefront_midi_disable_virtual (snd_wavefront_card_t *card) + +{ + unsigned long flags; + + spin_lock_irqsave (&card->wavefront.midi.virtual, flags); + // snd_wavefront_midi_input_close (card->ics2115_external_rmidi); + // snd_wavefront_midi_output_close (card->ics2115_external_rmidi); + card->wavefront.midi.isvirtual = 0; + spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags); +} + +int +snd_wavefront_midi_start (snd_wavefront_card_t *card) + +{ + int ok, i; + unsigned char rbuf[4], wbuf[4]; + snd_wavefront_t *dev; + snd_wavefront_midi_t *midi; + + dev = &card->wavefront; + midi = &dev->midi; + + /* The ICS2115 MPU-401 interface doesn't do anything + until its set into UART mode. + */ + + /* XXX fix me - no hard timing loops allowed! */ + + for (i = 0; i < 30000 && !output_ready (midi); i++); + + if (!output_ready (midi)) { + snd_printk ("MIDI interface not ready for command\n"); + return -1; + } + + /* Any interrupts received from now on + are owned by the MIDI side of things. + */ + + dev->interrupts_are_midi = 1; + + outb (UART_MODE_ON, midi->mpu_command_port); + + for (ok = 0, i = 50000; i > 0 && !ok; i--) { + if (input_avail (midi)) { + if (read_data (midi) == MPU_ACK) { + ok = 1; + break; + } + } + } + + if (!ok) { + snd_printk ("cannot set UART mode for MIDI interface"); + dev->interrupts_are_midi = 0; + return -1; + } + + /* Route external MIDI to WaveFront synth (by default) */ + + if (snd_wavefront_cmd (dev, WFC_MISYNTH_ON, rbuf, wbuf)) { + snd_printk ("can't enable MIDI-IN-2-synth routing.\n"); + /* XXX error ? */ + } + + /* Turn on Virtual MIDI, but first *always* turn it off, + since otherwise consecutive reloads of the driver will + never cause the hardware to generate the initial "internal" or + "external" source bytes in the MIDI data stream. This + is pretty important, since the internal hardware generally will + be used to generate none or very little MIDI output, and + thus the only source of MIDI data is actually external. Without + the switch bytes, the driver will think it all comes from + the internal interface. Duh. + */ + + if (snd_wavefront_cmd (dev, WFC_VMIDI_OFF, rbuf, wbuf)) { + snd_printk ("virtual MIDI mode not disabled\n"); + return 0; /* We're OK, but missing the external MIDI dev */ + } + + snd_wavefront_midi_enable_virtual (card); + + if (snd_wavefront_cmd (dev, WFC_VMIDI_ON, rbuf, wbuf)) { + snd_printk ("cannot enable virtual MIDI mode.\n"); + snd_wavefront_midi_disable_virtual (card); + } + return 0; +} + +const struct snd_rawmidi_ops snd_wavefront_midi_output = +{ + .open = snd_wavefront_midi_output_open, + .close = snd_wavefront_midi_output_close, + .trigger = snd_wavefront_midi_output_trigger, +}; + +const struct snd_rawmidi_ops snd_wavefront_midi_input = +{ + .open = snd_wavefront_midi_input_open, + .close = snd_wavefront_midi_input_close, + .trigger = snd_wavefront_midi_input_trigger, +}; + diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c new file mode 100644 index 000000000..9dd0ae377 --- /dev/null +++ b/sound/isa/wavefront/wavefront_synth.c @@ -0,0 +1,2216 @@ +/* Copyright (C) by Paul Barton-Davis 1998-1999 + * + * Some portions of this file are taken from work that is + * copyright (C) by Hannu Savolainen 1993-1996 + * + * This program is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) + * Version 2 (June 1991). See the "COPYING" file distributed with this software + * for more info. + */ + +/* + * An ALSA lowlevel driver for Turtle Beach ICS2115 wavetable synth + * (Maui, Tropez, Tropez Plus) + * + * This driver supports the onboard wavetable synthesizer (an ICS2115), + * including patch, sample and program loading and unloading, conversion + * of GUS patches during loading, and full user-level access to all + * WaveFront commands. It tries to provide semi-intelligent patch and + * sample management as well. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int wf_raw = 0; /* we normally check for "raw state" to firmware + loading. if non-zero, then during driver loading, the + state of the board is ignored, and we reset the + board and load the firmware anyway. + */ + +static int fx_raw = 1; /* if this is zero, we'll leave the FX processor in + whatever state it is when the driver is loaded. + The default is to download the microprogram and + associated coefficients to set it up for "default" + operation, whatever that means. + */ + +static int debug_default = 0; /* you can set this to control debugging + during driver loading. it takes any combination + of the WF_DEBUG_* flags defined in + wavefront.h + */ + +/* XXX this needs to be made firmware and hardware version dependent */ + +#define DEFAULT_OSPATH "wavefront.os" +static char *ospath = DEFAULT_OSPATH; /* the firmware file name */ + +static int wait_usecs = 150; /* This magic number seems to give pretty optimal + throughput based on my limited experimentation. + If you want to play around with it and find a better + value, be my guest. Remember, the idea is to + get a number that causes us to just busy wait + for as many WaveFront commands as possible, without + coming up with a number so large that we hog the + whole CPU. + + Specifically, with this number, out of about 134,000 + status waits, only about 250 result in a sleep. + */ + +static int sleep_interval = 100; /* HZ/sleep_interval seconds per sleep */ +static int sleep_tries = 50; /* number of times we'll try to sleep */ + +static int reset_time = 2; /* hundreths of a second we wait after a HW + reset for the expected interrupt. + */ + +static int ramcheck_time = 20; /* time in seconds to wait while ROM code + checks on-board RAM. + */ + +static int osrun_time = 10; /* time in seconds we wait for the OS to + start running. + */ +module_param(wf_raw, int, 0444); +MODULE_PARM_DESC(wf_raw, "if non-zero, assume that we need to boot the OS"); +module_param(fx_raw, int, 0444); +MODULE_PARM_DESC(fx_raw, "if non-zero, assume that the FX process needs help"); +module_param(debug_default, int, 0444); +MODULE_PARM_DESC(debug_default, "debug parameters for card initialization"); +module_param(wait_usecs, int, 0444); +MODULE_PARM_DESC(wait_usecs, "how long to wait without sleeping, usecs"); +module_param(sleep_interval, int, 0444); +MODULE_PARM_DESC(sleep_interval, "how long to sleep when waiting for reply"); +module_param(sleep_tries, int, 0444); +MODULE_PARM_DESC(sleep_tries, "how many times to try sleeping during a wait"); +module_param(ospath, charp, 0444); +MODULE_PARM_DESC(ospath, "pathname to processed ICS2115 OS firmware"); +module_param(reset_time, int, 0444); +MODULE_PARM_DESC(reset_time, "how long to wait for a reset to take effect"); +module_param(ramcheck_time, int, 0444); +MODULE_PARM_DESC(ramcheck_time, "how many seconds to wait for the RAM test"); +module_param(osrun_time, int, 0444); +MODULE_PARM_DESC(osrun_time, "how many seconds to wait for the ICS2115 OS"); + +/* if WF_DEBUG not defined, no run-time debugging messages will + be available via the debug flag setting. Given the current + beta state of the driver, this will remain set until a future + version. +*/ + +#define WF_DEBUG 1 + +#ifdef WF_DEBUG + +#define DPRINT(cond, ...) \ + if ((dev->debug & (cond)) == (cond)) { \ + snd_printk (__VA_ARGS__); \ + } +#else +#define DPRINT(cond, args...) +#endif /* WF_DEBUG */ + +#define LOGNAME "WaveFront: " + +/* bitmasks for WaveFront status port value */ + +#define STAT_RINTR_ENABLED 0x01 +#define STAT_CAN_READ 0x02 +#define STAT_INTR_READ 0x04 +#define STAT_WINTR_ENABLED 0x10 +#define STAT_CAN_WRITE 0x20 +#define STAT_INTR_WRITE 0x40 + +static int wavefront_delete_sample (snd_wavefront_t *, int sampnum); +static int wavefront_find_free_sample (snd_wavefront_t *); + +struct wavefront_command { + int cmd; + char *action; + unsigned int read_cnt; + unsigned int write_cnt; + int need_ack; +}; + +static struct { + int errno; + const char *errstr; +} wavefront_errors[] = { + { 0x01, "Bad sample number" }, + { 0x02, "Out of sample memory" }, + { 0x03, "Bad patch number" }, + { 0x04, "Error in number of voices" }, + { 0x06, "Sample load already in progress" }, + { 0x0B, "No sample load request pending" }, + { 0x0E, "Bad MIDI channel number" }, + { 0x10, "Download Record Error" }, + { 0x80, "Success" }, + { 0x0 } +}; + +#define NEEDS_ACK 1 + +static struct wavefront_command wavefront_commands[] = { + { WFC_SET_SYNTHVOL, "set synthesizer volume", 0, 1, NEEDS_ACK }, + { WFC_GET_SYNTHVOL, "get synthesizer volume", 1, 0, 0}, + { WFC_SET_NVOICES, "set number of voices", 0, 1, NEEDS_ACK }, + { WFC_GET_NVOICES, "get number of voices", 1, 0, 0 }, + { WFC_SET_TUNING, "set synthesizer tuning", 0, 2, NEEDS_ACK }, + { WFC_GET_TUNING, "get synthesizer tuning", 2, 0, 0 }, + { WFC_DISABLE_CHANNEL, "disable synth channel", 0, 1, NEEDS_ACK }, + { WFC_ENABLE_CHANNEL, "enable synth channel", 0, 1, NEEDS_ACK }, + { WFC_GET_CHANNEL_STATUS, "get synth channel status", 3, 0, 0 }, + { WFC_MISYNTH_OFF, "disable midi-in to synth", 0, 0, NEEDS_ACK }, + { WFC_MISYNTH_ON, "enable midi-in to synth", 0, 0, NEEDS_ACK }, + { WFC_VMIDI_ON, "enable virtual midi mode", 0, 0, NEEDS_ACK }, + { WFC_VMIDI_OFF, "disable virtual midi mode", 0, 0, NEEDS_ACK }, + { WFC_MIDI_STATUS, "report midi status", 1, 0, 0 }, + { WFC_FIRMWARE_VERSION, "report firmware version", 2, 0, 0 }, + { WFC_HARDWARE_VERSION, "report hardware version", 2, 0, 0 }, + { WFC_GET_NSAMPLES, "report number of samples", 2, 0, 0 }, + { WFC_INSTOUT_LEVELS, "report instantaneous output levels", 7, 0, 0 }, + { WFC_PEAKOUT_LEVELS, "report peak output levels", 7, 0, 0 }, + { WFC_DOWNLOAD_SAMPLE, "download sample", + 0, WF_SAMPLE_BYTES, NEEDS_ACK }, + { WFC_DOWNLOAD_BLOCK, "download block", 0, 0, NEEDS_ACK}, + { WFC_DOWNLOAD_SAMPLE_HEADER, "download sample header", + 0, WF_SAMPLE_HDR_BYTES, NEEDS_ACK }, + { WFC_UPLOAD_SAMPLE_HEADER, "upload sample header", 13, 2, 0 }, + + /* This command requires a variable number of bytes to be written. + There is a hack in snd_wavefront_cmd() to support this. The actual + count is passed in as the read buffer ptr, cast appropriately. + Ugh. + */ + + { WFC_DOWNLOAD_MULTISAMPLE, "download multisample", 0, 0, NEEDS_ACK }, + + /* This one is a hack as well. We just read the first byte of the + response, don't fetch an ACK, and leave the rest to the + calling function. Ugly, ugly, ugly. + */ + + { WFC_UPLOAD_MULTISAMPLE, "upload multisample", 2, 1, 0 }, + { WFC_DOWNLOAD_SAMPLE_ALIAS, "download sample alias", + 0, WF_ALIAS_BYTES, NEEDS_ACK }, + { WFC_UPLOAD_SAMPLE_ALIAS, "upload sample alias", WF_ALIAS_BYTES, 2, 0}, + { WFC_DELETE_SAMPLE, "delete sample", 0, 2, NEEDS_ACK }, + { WFC_IDENTIFY_SAMPLE_TYPE, "identify sample type", 5, 2, 0 }, + { WFC_UPLOAD_SAMPLE_PARAMS, "upload sample parameters" }, + { WFC_REPORT_FREE_MEMORY, "report free memory", 4, 0, 0 }, + { WFC_DOWNLOAD_PATCH, "download patch", 0, 134, NEEDS_ACK }, + { WFC_UPLOAD_PATCH, "upload patch", 132, 2, 0 }, + { WFC_DOWNLOAD_PROGRAM, "download program", 0, 33, NEEDS_ACK }, + { WFC_UPLOAD_PROGRAM, "upload program", 32, 1, 0 }, + { WFC_DOWNLOAD_EDRUM_PROGRAM, "download enhanced drum program", 0, 9, + NEEDS_ACK}, + { WFC_UPLOAD_EDRUM_PROGRAM, "upload enhanced drum program", 8, 1, 0}, + { WFC_SET_EDRUM_CHANNEL, "set enhanced drum program channel", + 0, 1, NEEDS_ACK }, + { WFC_DISABLE_DRUM_PROGRAM, "disable drum program", 0, 1, NEEDS_ACK }, + { WFC_REPORT_CHANNEL_PROGRAMS, "report channel program numbers", + 32, 0, 0 }, + { WFC_NOOP, "the no-op command", 0, 0, NEEDS_ACK }, + { 0x00 } +}; + +static const char * +wavefront_errorstr (int errnum) + +{ + int i; + + for (i = 0; wavefront_errors[i].errstr; i++) { + if (wavefront_errors[i].errno == errnum) { + return wavefront_errors[i].errstr; + } + } + + return "Unknown WaveFront error"; +} + +static struct wavefront_command * +wavefront_get_command (int cmd) + +{ + int i; + + for (i = 0; wavefront_commands[i].cmd != 0; i++) { + if (cmd == wavefront_commands[i].cmd) { + return &wavefront_commands[i]; + } + } + + return NULL; +} + +static inline int +wavefront_status (snd_wavefront_t *dev) + +{ + return inb (dev->status_port); +} + +static int +wavefront_sleep (int limit) + +{ + schedule_timeout_interruptible(limit); + + return signal_pending(current); +} + +static int +wavefront_wait (snd_wavefront_t *dev, int mask) + +{ + int i; + + /* Spin for a short period of time, because >99% of all + requests to the WaveFront can be serviced inline like this. + */ + + for (i = 0; i < wait_usecs; i += 5) { + if (wavefront_status (dev) & mask) { + return 1; + } + udelay(5); + } + + for (i = 0; i < sleep_tries; i++) { + + if (wavefront_status (dev) & mask) { + return 1; + } + + if (wavefront_sleep (HZ/sleep_interval)) { + return (0); + } + } + + return (0); +} + +static int +wavefront_read (snd_wavefront_t *dev) + +{ + if (wavefront_wait (dev, STAT_CAN_READ)) + return inb (dev->data_port); + + DPRINT (WF_DEBUG_DATA, "read timeout.\n"); + + return -1; +} + +static int +wavefront_write (snd_wavefront_t *dev, unsigned char data) + +{ + if (wavefront_wait (dev, STAT_CAN_WRITE)) { + outb (data, dev->data_port); + return 0; + } + + DPRINT (WF_DEBUG_DATA, "write timeout.\n"); + + return -1; +} + +int +snd_wavefront_cmd (snd_wavefront_t *dev, + int cmd, unsigned char *rbuf, unsigned char *wbuf) + +{ + int ack; + unsigned int i; + int c; + struct wavefront_command *wfcmd; + + if ((wfcmd = wavefront_get_command (cmd)) == NULL) { + snd_printk ("command 0x%x not supported.\n", + cmd); + return 1; + } + + /* Hack to handle the one variable-size write command. See + wavefront_send_multisample() for the other half of this + gross and ugly strategy. + */ + + if (cmd == WFC_DOWNLOAD_MULTISAMPLE) { + wfcmd->write_cnt = (unsigned long) rbuf; + rbuf = NULL; + } + + DPRINT (WF_DEBUG_CMD, "0x%x [%s] (%d,%d,%d)\n", + cmd, wfcmd->action, wfcmd->read_cnt, + wfcmd->write_cnt, wfcmd->need_ack); + + if (wavefront_write (dev, cmd)) { + DPRINT ((WF_DEBUG_IO|WF_DEBUG_CMD), "cannot request " + "0x%x [%s].\n", + cmd, wfcmd->action); + return 1; + } + + if (wfcmd->write_cnt > 0) { + DPRINT (WF_DEBUG_DATA, "writing %d bytes " + "for 0x%x\n", + wfcmd->write_cnt, cmd); + + for (i = 0; i < wfcmd->write_cnt; i++) { + if (wavefront_write (dev, wbuf[i])) { + DPRINT (WF_DEBUG_IO, "bad write for byte " + "%d of 0x%x [%s].\n", + i, cmd, wfcmd->action); + return 1; + } + + DPRINT (WF_DEBUG_DATA, "write[%d] = 0x%x\n", + i, wbuf[i]); + } + } + + if (wfcmd->read_cnt > 0) { + DPRINT (WF_DEBUG_DATA, "reading %d ints " + "for 0x%x\n", + wfcmd->read_cnt, cmd); + + for (i = 0; i < wfcmd->read_cnt; i++) { + + if ((c = wavefront_read (dev)) == -1) { + DPRINT (WF_DEBUG_IO, "bad read for byte " + "%d of 0x%x [%s].\n", + i, cmd, wfcmd->action); + return 1; + } + + /* Now handle errors. Lots of special cases here */ + + if (c == 0xff) { + if ((c = wavefront_read (dev)) == -1) { + DPRINT (WF_DEBUG_IO, "bad read for " + "error byte at " + "read byte %d " + "of 0x%x [%s].\n", + i, cmd, + wfcmd->action); + return 1; + } + + /* Can you believe this madness ? */ + + if (c == 1 && + wfcmd->cmd == WFC_IDENTIFY_SAMPLE_TYPE) { + rbuf[0] = WF_ST_EMPTY; + return (0); + + } else if (c == 3 && + wfcmd->cmd == WFC_UPLOAD_PATCH) { + + return 3; + + } else if (c == 1 && + wfcmd->cmd == WFC_UPLOAD_PROGRAM) { + + return 1; + + } else { + + DPRINT (WF_DEBUG_IO, "error %d (%s) " + "during " + "read for byte " + "%d of 0x%x " + "[%s].\n", + c, + wavefront_errorstr (c), + i, cmd, + wfcmd->action); + return 1; + + } + + } else { + rbuf[i] = c; + } + + DPRINT (WF_DEBUG_DATA, "read[%d] = 0x%x\n",i, rbuf[i]); + } + } + + if ((wfcmd->read_cnt == 0 && wfcmd->write_cnt == 0) || wfcmd->need_ack) { + + DPRINT (WF_DEBUG_CMD, "reading ACK for 0x%x\n", cmd); + + /* Some commands need an ACK, but return zero instead + of the standard value. + */ + + if ((ack = wavefront_read (dev)) == 0) { + ack = WF_ACK; + } + + if (ack != WF_ACK) { + if (ack == -1) { + DPRINT (WF_DEBUG_IO, "cannot read ack for " + "0x%x [%s].\n", + cmd, wfcmd->action); + return 1; + + } else { + int err = -1; /* something unknown */ + + if (ack == 0xff) { /* explicit error */ + + if ((err = wavefront_read (dev)) == -1) { + DPRINT (WF_DEBUG_DATA, + "cannot read err " + "for 0x%x [%s].\n", + cmd, wfcmd->action); + } + } + + DPRINT (WF_DEBUG_IO, "0x%x [%s] " + "failed (0x%x, 0x%x, %s)\n", + cmd, wfcmd->action, ack, err, + wavefront_errorstr (err)); + + return -err; + } + } + + DPRINT (WF_DEBUG_DATA, "ack received " + "for 0x%x [%s]\n", + cmd, wfcmd->action); + } else { + + DPRINT (WF_DEBUG_CMD, "0x%x [%s] does not need " + "ACK (%d,%d,%d)\n", + cmd, wfcmd->action, wfcmd->read_cnt, + wfcmd->write_cnt, wfcmd->need_ack); + } + + return 0; + +} + +/*********************************************************************** +WaveFront data munging + +Things here are weird. All data written to the board cannot +have its most significant bit set. Any data item with values +potentially > 0x7F (127) must be split across multiple bytes. + +Sometimes, we need to munge numeric values that are represented on +the x86 side as 8-32 bit values. Sometimes, we need to munge data +that is represented on the x86 side as an array of bytes. The most +efficient approach to handling both cases seems to be to use 2 +different functions for munging and 2 for de-munging. This avoids +weird casting and worrying about bit-level offsets. + +**********************************************************************/ + +static unsigned char * +munge_int32 (unsigned int src, + unsigned char *dst, + unsigned int dst_size) +{ + unsigned int i; + + for (i = 0; i < dst_size; i++) { + *dst = src & 0x7F; /* Mask high bit of LSB */ + src = src >> 7; /* Rotate Right 7 bits */ + /* Note: we leave the upper bits in place */ + + dst++; + } + return dst; +}; + +static int +demunge_int32 (unsigned char* src, int src_size) + +{ + int i; + int outval = 0; + + for (i = src_size - 1; i >= 0; i--) { + outval=(outval<<7)+src[i]; + } + + return outval; +}; + +static +unsigned char * +munge_buf (unsigned char *src, unsigned char *dst, unsigned int dst_size) + +{ + unsigned int i; + unsigned int last = dst_size / 2; + + for (i = 0; i < last; i++) { + *dst++ = src[i] & 0x7f; + *dst++ = src[i] >> 7; + } + return dst; +} + +static +unsigned char * +demunge_buf (unsigned char *src, unsigned char *dst, unsigned int src_bytes) + +{ + int i; + unsigned char *end = src + src_bytes; + + end = src + src_bytes; + + /* NOTE: src and dst *CAN* point to the same address */ + + for (i = 0; src != end; i++) { + dst[i] = *src++; + dst[i] |= (*src++)<<7; + } + + return dst; +} + +/*********************************************************************** +WaveFront: sample, patch and program management. +***********************************************************************/ + +static int +wavefront_delete_sample (snd_wavefront_t *dev, int sample_num) + +{ + unsigned char wbuf[2]; + int x; + + wbuf[0] = sample_num & 0x7f; + wbuf[1] = sample_num >> 7; + + if ((x = snd_wavefront_cmd (dev, WFC_DELETE_SAMPLE, NULL, wbuf)) == 0) { + dev->sample_status[sample_num] = WF_ST_EMPTY; + } + + return x; +} + +static int +wavefront_get_sample_status (snd_wavefront_t *dev, int assume_rom) + +{ + int i; + unsigned char rbuf[32], wbuf[32]; + unsigned int sc_real, sc_alias, sc_multi; + + /* check sample status */ + + if (snd_wavefront_cmd (dev, WFC_GET_NSAMPLES, rbuf, wbuf)) { + snd_printk ("cannot request sample count.\n"); + return -1; + } + + sc_real = sc_alias = sc_multi = dev->samples_used = 0; + + for (i = 0; i < WF_MAX_SAMPLE; i++) { + + wbuf[0] = i & 0x7f; + wbuf[1] = i >> 7; + + if (snd_wavefront_cmd (dev, WFC_IDENTIFY_SAMPLE_TYPE, rbuf, wbuf)) { + snd_printk(KERN_WARNING "cannot identify sample " + "type of slot %d\n", i); + dev->sample_status[i] = WF_ST_EMPTY; + continue; + } + + dev->sample_status[i] = (WF_SLOT_FILLED|rbuf[0]); + + if (assume_rom) { + dev->sample_status[i] |= WF_SLOT_ROM; + } + + switch (rbuf[0] & WF_ST_MASK) { + case WF_ST_SAMPLE: + sc_real++; + break; + case WF_ST_MULTISAMPLE: + sc_multi++; + break; + case WF_ST_ALIAS: + sc_alias++; + break; + case WF_ST_EMPTY: + break; + + default: + snd_printk ("unknown sample type for " + "slot %d (0x%x)\n", + i, rbuf[0]); + } + + if (rbuf[0] != WF_ST_EMPTY) { + dev->samples_used++; + } + } + + snd_printk ("%d samples used (%d real, %d aliases, %d multi), " + "%d empty\n", dev->samples_used, sc_real, sc_alias, sc_multi, + WF_MAX_SAMPLE - dev->samples_used); + + + return (0); + +} + +static int +wavefront_get_patch_status (snd_wavefront_t *dev) + +{ + unsigned char patchbuf[WF_PATCH_BYTES]; + unsigned char patchnum[2]; + wavefront_patch *p; + int i, x, cnt, cnt2; + + for (i = 0; i < WF_MAX_PATCH; i++) { + patchnum[0] = i & 0x7f; + patchnum[1] = i >> 7; + + if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PATCH, patchbuf, + patchnum)) == 0) { + + dev->patch_status[i] |= WF_SLOT_FILLED; + p = (wavefront_patch *) patchbuf; + dev->sample_status + [p->sample_number|(p->sample_msb<<7)] |= + WF_SLOT_USED; + + } else if (x == 3) { /* Bad patch number */ + dev->patch_status[i] = 0; + } else { + snd_printk ("upload patch " + "error 0x%x\n", x); + dev->patch_status[i] = 0; + return 1; + } + } + + /* program status has already filled in slot_used bits */ + + for (i = 0, cnt = 0, cnt2 = 0; i < WF_MAX_PATCH; i++) { + if (dev->patch_status[i] & WF_SLOT_FILLED) { + cnt++; + } + if (dev->patch_status[i] & WF_SLOT_USED) { + cnt2++; + } + + } + snd_printk ("%d patch slots filled, %d in use\n", cnt, cnt2); + + return (0); +} + +static int +wavefront_get_program_status (snd_wavefront_t *dev) + +{ + unsigned char progbuf[WF_PROGRAM_BYTES]; + wavefront_program prog; + unsigned char prognum; + int i, x, l, cnt; + + for (i = 0; i < WF_MAX_PROGRAM; i++) { + prognum = i; + + if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PROGRAM, progbuf, + &prognum)) == 0) { + + dev->prog_status[i] |= WF_SLOT_USED; + + demunge_buf (progbuf, (unsigned char *) &prog, + WF_PROGRAM_BYTES); + + for (l = 0; l < WF_NUM_LAYERS; l++) { + if (prog.layer[l].mute) { + dev->patch_status + [prog.layer[l].patch_number] |= + WF_SLOT_USED; + } + } + } else if (x == 1) { /* Bad program number */ + dev->prog_status[i] = 0; + } else { + snd_printk ("upload program " + "error 0x%x\n", x); + dev->prog_status[i] = 0; + } + } + + for (i = 0, cnt = 0; i < WF_MAX_PROGRAM; i++) { + if (dev->prog_status[i]) { + cnt++; + } + } + + snd_printk ("%d programs slots in use\n", cnt); + + return (0); +} + +static int +wavefront_send_patch (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char buf[WF_PATCH_BYTES+2]; + unsigned char *bptr; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading patch %d\n", + header->number); + + if (header->number >= ARRAY_SIZE(dev->patch_status)) + return -EINVAL; + + dev->patch_status[header->number] |= WF_SLOT_FILLED; + + bptr = buf; + bptr = munge_int32 (header->number, buf, 2); + munge_buf ((unsigned char *)&header->hdr.p, bptr, WF_PATCH_BYTES); + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PATCH, NULL, buf)) { + snd_printk ("download patch failed\n"); + return -EIO; + } + + return (0); +} + +static int +wavefront_send_program (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char buf[WF_PROGRAM_BYTES+1]; + int i; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n", + header->number); + + if (header->number >= ARRAY_SIZE(dev->prog_status)) + return -EINVAL; + + dev->prog_status[header->number] = WF_SLOT_USED; + + /* XXX need to zero existing SLOT_USED bit for program_status[i] + where `i' is the program that's being (potentially) overwritten. + */ + + for (i = 0; i < WF_NUM_LAYERS; i++) { + if (header->hdr.pr.layer[i].mute) { + dev->patch_status[header->hdr.pr.layer[i].patch_number] |= + WF_SLOT_USED; + + /* XXX need to mark SLOT_USED for sample used by + patch_number, but this means we have to load it. Ick. + */ + } + } + + buf[0] = header->number; + munge_buf ((unsigned char *)&header->hdr.pr, &buf[1], WF_PROGRAM_BYTES); + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PROGRAM, NULL, buf)) { + snd_printk ("download patch failed\n"); + return -EIO; + } + + return (0); +} + +static int +wavefront_freemem (snd_wavefront_t *dev) + +{ + char rbuf[8]; + + if (snd_wavefront_cmd (dev, WFC_REPORT_FREE_MEMORY, rbuf, NULL)) { + snd_printk ("can't get memory stats.\n"); + return -1; + } else { + return demunge_int32 (rbuf, 4); + } +} + +static int +wavefront_send_sample (snd_wavefront_t *dev, + wavefront_patch_info *header, + u16 __user *dataptr, + int data_is_unsigned) + +{ + /* samples are downloaded via a 16-bit wide i/o port + (you could think of it as 2 adjacent 8-bit wide ports + but its less efficient that way). therefore, all + the blocksizes and so forth listed in the documentation, + and used conventionally to refer to sample sizes, + which are given in 8-bit units (bytes), need to be + divided by 2. + */ + + u16 sample_short = 0; + u32 length; + u16 __user *data_end = NULL; + unsigned int i; + const unsigned int max_blksize = 4096/2; + unsigned int written; + unsigned int blocksize; + int dma_ack; + int blocknum; + unsigned char sample_hdr[WF_SAMPLE_HDR_BYTES]; + unsigned char *shptr; + int skip = 0; + int initial_skip = 0; + + DPRINT (WF_DEBUG_LOAD_PATCH, "sample %sdownload for slot %d, " + "type %d, %d bytes from 0x%lx\n", + header->size ? "" : "header ", + header->number, header->subkey, + header->size, + (unsigned long) header->dataptr); + + if (header->number == WAVEFRONT_FIND_FREE_SAMPLE_SLOT) { + int x; + + if ((x = wavefront_find_free_sample (dev)) < 0) { + return -ENOMEM; + } + snd_printk ("unspecified sample => %d\n", x); + header->number = x; + } + + if (header->number >= WF_MAX_SAMPLE) + return -EINVAL; + + if (header->size) { + + /* XXX it's a debatable point whether or not RDONLY semantics + on the ROM samples should cover just the sample data or + the sample header. For now, it only covers the sample data, + so anyone is free at all times to rewrite sample headers. + + My reason for this is that we have the sample headers + available in the WFB file for General MIDI, and so these + can always be reset if needed. The sample data, however, + cannot be recovered without a complete reset and firmware + reload of the ICS2115, which is a very expensive operation. + + So, doing things this way allows us to honor the notion of + "RESETSAMPLES" reasonably cheaply. Note however, that this + is done purely at user level: there is no WFB parser in + this driver, and so a complete reset (back to General MIDI, + or theoretically some other configuration) is the + responsibility of the user level library. + + To try to do this in the kernel would be a little + crazy: we'd need 158K of kernel space just to hold + a copy of the patch/program/sample header data. + */ + + if (dev->rom_samples_rdonly) { + if (dev->sample_status[header->number] & WF_SLOT_ROM) { + snd_printk ("sample slot %d " + "write protected\n", + header->number); + return -EACCES; + } + } + + wavefront_delete_sample (dev, header->number); + } + + if (header->size) { + dev->freemem = wavefront_freemem (dev); + + if (dev->freemem < (int)header->size) { + snd_printk ("insufficient memory to " + "load %d byte sample.\n", + header->size); + return -ENOMEM; + } + + } + + skip = WF_GET_CHANNEL(&header->hdr.s); + + if (skip > 0 && header->hdr.s.SampleResolution != LINEAR_16BIT) { + snd_printk ("channel selection only " + "possible on 16-bit samples"); + return -EINVAL; + } + + switch (skip) { + case 0: + initial_skip = 0; + skip = 1; + break; + case 1: + initial_skip = 0; + skip = 2; + break; + case 2: + initial_skip = 1; + skip = 2; + break; + case 3: + initial_skip = 2; + skip = 3; + break; + case 4: + initial_skip = 3; + skip = 4; + break; + case 5: + initial_skip = 4; + skip = 5; + break; + case 6: + initial_skip = 5; + skip = 6; + break; + } + + DPRINT (WF_DEBUG_LOAD_PATCH, "channel selection: %d => " + "initial skip = %d, skip = %d\n", + WF_GET_CHANNEL (&header->hdr.s), + initial_skip, skip); + + /* Be safe, and zero the "Unused" bits ... */ + + WF_SET_CHANNEL(&header->hdr.s, 0); + + /* adjust size for 16 bit samples by dividing by two. We always + send 16 bits per write, even for 8 bit samples, so the length + is always half the size of the sample data in bytes. + */ + + length = header->size / 2; + + /* the data we're sent has not been munged, and in fact, the + header we have to send isn't just a munged copy either. + so, build the sample header right here. + */ + + shptr = &sample_hdr[0]; + + shptr = munge_int32 (header->number, shptr, 2); + + if (header->size) { + shptr = munge_int32 (length, shptr, 4); + } + + /* Yes, a 4 byte result doesn't contain all of the offset bits, + but the offset only uses 24 bits. + */ + + shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleStartOffset), + shptr, 4); + shptr = munge_int32 (*((u32 *) &header->hdr.s.loopStartOffset), + shptr, 4); + shptr = munge_int32 (*((u32 *) &header->hdr.s.loopEndOffset), + shptr, 4); + shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleEndOffset), + shptr, 4); + + /* This one is truly weird. What kind of weirdo decided that in + a system dominated by 16 and 32 bit integers, they would use + a just 12 bits ? + */ + + shptr = munge_int32 (header->hdr.s.FrequencyBias, shptr, 3); + + /* Why is this nybblified, when the MSB is *always* zero ? + Anyway, we can't take address of bitfield, so make a + good-faith guess at where it starts. + */ + + shptr = munge_int32 (*(&header->hdr.s.FrequencyBias+1), + shptr, 2); + + if (snd_wavefront_cmd (dev, + header->size ? + WFC_DOWNLOAD_SAMPLE : WFC_DOWNLOAD_SAMPLE_HEADER, + NULL, sample_hdr)) { + snd_printk ("sample %sdownload refused.\n", + header->size ? "" : "header "); + return -EIO; + } + + if (header->size == 0) { + goto sent; /* Sorry. Just had to have one somewhere */ + } + + data_end = dataptr + length; + + /* Do any initial skip over an unused channel's data */ + + dataptr += initial_skip; + + for (written = 0, blocknum = 0; + written < length; written += max_blksize, blocknum++) { + + if ((length - written) > max_blksize) { + blocksize = max_blksize; + } else { + /* round to nearest 16-byte value */ + blocksize = ALIGN(length - written, 8); + } + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_BLOCK, NULL, NULL)) { + snd_printk ("download block " + "request refused.\n"); + return -EIO; + } + + for (i = 0; i < blocksize; i++) { + + if (dataptr < data_end) { + + if (get_user(sample_short, dataptr)) + return -EFAULT; + dataptr += skip; + + if (data_is_unsigned) { /* GUS ? */ + + if (WF_SAMPLE_IS_8BIT(&header->hdr.s)) { + + /* 8 bit sample + resolution, sign + extend both bytes. + */ + + ((unsigned char*) + &sample_short)[0] += 0x7f; + ((unsigned char*) + &sample_short)[1] += 0x7f; + + } else { + + /* 16 bit sample + resolution, sign + extend the MSB. + */ + + sample_short += 0x7fff; + } + } + + } else { + + /* In padding section of final block: + + Don't fetch unsupplied data from + user space, just continue with + whatever the final value was. + */ + } + + if (i < blocksize - 1) { + outw (sample_short, dev->block_port); + } else { + outw (sample_short, dev->last_block_port); + } + } + + /* Get "DMA page acknowledge", even though its really + nothing to do with DMA at all. + */ + + if ((dma_ack = wavefront_read (dev)) != WF_DMA_ACK) { + if (dma_ack == -1) { + snd_printk ("upload sample " + "DMA ack timeout\n"); + return -EIO; + } else { + snd_printk ("upload sample " + "DMA ack error 0x%x\n", + dma_ack); + return -EIO; + } + } + } + + dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_SAMPLE); + + /* Note, label is here because sending the sample header shouldn't + alter the sample_status info at all. + */ + + sent: + return (0); +} + +static int +wavefront_send_alias (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char alias_hdr[WF_ALIAS_BYTES]; + + DPRINT (WF_DEBUG_LOAD_PATCH, "download alias, %d is " + "alias for %d\n", + header->number, + header->hdr.a.OriginalSample); + + if (header->number >= WF_MAX_SAMPLE) + return -EINVAL; + + munge_int32 (header->number, &alias_hdr[0], 2); + munge_int32 (header->hdr.a.OriginalSample, &alias_hdr[2], 2); + munge_int32 (*((unsigned int *)&header->hdr.a.sampleStartOffset), + &alias_hdr[4], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.loopStartOffset), + &alias_hdr[8], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.loopEndOffset), + &alias_hdr[12], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.sampleEndOffset), + &alias_hdr[16], 4); + munge_int32 (header->hdr.a.FrequencyBias, &alias_hdr[20], 3); + munge_int32 (*(&header->hdr.a.FrequencyBias+1), &alias_hdr[23], 2); + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_SAMPLE_ALIAS, NULL, alias_hdr)) { + snd_printk ("download alias failed.\n"); + return -EIO; + } + + dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_ALIAS); + + return (0); +} + +static int +wavefront_send_multisample (snd_wavefront_t *dev, wavefront_patch_info *header) +{ + int i; + int num_samples; + unsigned char *msample_hdr; + + if (header->number >= WF_MAX_SAMPLE) + return -EINVAL; + + msample_hdr = kmalloc(WF_MSAMPLE_BYTES, GFP_KERNEL); + if (! msample_hdr) + return -ENOMEM; + + munge_int32 (header->number, &msample_hdr[0], 2); + + /* You'll recall at this point that the "number of samples" value + in a wavefront_multisample struct is actually the log2 of the + real number of samples. + */ + + num_samples = (1<<(header->hdr.ms.NumberOfSamples&7)); + msample_hdr[2] = (unsigned char) header->hdr.ms.NumberOfSamples; + + DPRINT (WF_DEBUG_LOAD_PATCH, "multi %d with %d=%d samples\n", + header->number, + header->hdr.ms.NumberOfSamples, + num_samples); + + for (i = 0; i < num_samples; i++) { + DPRINT(WF_DEBUG_LOAD_PATCH|WF_DEBUG_DATA, "sample[%d] = %d\n", + i, header->hdr.ms.SampleNumber[i]); + munge_int32 (header->hdr.ms.SampleNumber[i], + &msample_hdr[3+(i*2)], 2); + } + + /* Need a hack here to pass in the number of bytes + to be written to the synth. This is ugly, and perhaps + one day, I'll fix it. + */ + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_MULTISAMPLE, + (unsigned char *) (long) ((num_samples*2)+3), + msample_hdr)) { + snd_printk ("download of multisample failed.\n"); + kfree(msample_hdr); + return -EIO; + } + + dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_MULTISAMPLE); + + kfree(msample_hdr); + return (0); +} + +static int +wavefront_fetch_multisample (snd_wavefront_t *dev, + wavefront_patch_info *header) +{ + int i; + unsigned char log_ns[1]; + unsigned char number[2]; + int num_samples; + + munge_int32 (header->number, number, 2); + + if (snd_wavefront_cmd (dev, WFC_UPLOAD_MULTISAMPLE, log_ns, number)) { + snd_printk ("upload multisample failed.\n"); + return -EIO; + } + + DPRINT (WF_DEBUG_DATA, "msample %d has %d samples\n", + header->number, log_ns[0]); + + header->hdr.ms.NumberOfSamples = log_ns[0]; + + /* get the number of samples ... */ + + num_samples = (1 << log_ns[0]); + + for (i = 0; i < num_samples; i++) { + char d[2]; + int val; + + if ((val = wavefront_read (dev)) == -1) { + snd_printk ("upload multisample failed " + "during sample loop.\n"); + return -EIO; + } + d[0] = val; + + if ((val = wavefront_read (dev)) == -1) { + snd_printk ("upload multisample failed " + "during sample loop.\n"); + return -EIO; + } + d[1] = val; + + header->hdr.ms.SampleNumber[i] = + demunge_int32 ((unsigned char *) d, 2); + + DPRINT (WF_DEBUG_DATA, "msample sample[%d] = %d\n", + i, header->hdr.ms.SampleNumber[i]); + } + + return (0); +} + + +static int +wavefront_send_drum (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char drumbuf[WF_DRUM_BYTES]; + wavefront_drum *drum = &header->hdr.d; + int i; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading edrum for MIDI " + "note %d, patch = %d\n", + header->number, drum->PatchNumber); + + drumbuf[0] = header->number & 0x7f; + + for (i = 0; i < 4; i++) { + munge_int32 (((unsigned char *)drum)[i], &drumbuf[1+(i*2)], 2); + } + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_EDRUM_PROGRAM, NULL, drumbuf)) { + snd_printk ("download drum failed.\n"); + return -EIO; + } + + return (0); +} + +static int +wavefront_find_free_sample (snd_wavefront_t *dev) + +{ + int i; + + for (i = 0; i < WF_MAX_SAMPLE; i++) { + if (!(dev->sample_status[i] & WF_SLOT_FILLED)) { + return i; + } + } + snd_printk ("no free sample slots!\n"); + return -1; +} + +#if 0 +static int +wavefront_find_free_patch (snd_wavefront_t *dev) + +{ + int i; + + for (i = 0; i < WF_MAX_PATCH; i++) { + if (!(dev->patch_status[i] & WF_SLOT_FILLED)) { + return i; + } + } + snd_printk ("no free patch slots!\n"); + return -1; +} +#endif + +static int +wavefront_load_patch (snd_wavefront_t *dev, const char __user *addr) +{ + wavefront_patch_info *header; + int err; + + header = kmalloc(sizeof(*header), GFP_KERNEL); + if (! header) + return -ENOMEM; + + if (copy_from_user (header, addr, sizeof(wavefront_patch_info) - + sizeof(wavefront_any))) { + snd_printk ("bad address for load patch.\n"); + err = -EFAULT; + goto __error; + } + + DPRINT (WF_DEBUG_LOAD_PATCH, "download " + "Sample type: %d " + "Sample number: %d " + "Sample size: %d\n", + header->subkey, + header->number, + header->size); + + switch (header->subkey) { + case WF_ST_SAMPLE: /* sample or sample_header, based on patch->size */ + + if (copy_from_user (&header->hdr.s, header->hdrptr, + sizeof (wavefront_sample))) { + err = -EFAULT; + break; + } + + err = wavefront_send_sample (dev, header, header->dataptr, 0); + break; + + case WF_ST_MULTISAMPLE: + + if (copy_from_user (&header->hdr.s, header->hdrptr, + sizeof (wavefront_multisample))) { + err = -EFAULT; + break; + } + + err = wavefront_send_multisample (dev, header); + break; + + case WF_ST_ALIAS: + + if (copy_from_user (&header->hdr.a, header->hdrptr, + sizeof (wavefront_alias))) { + err = -EFAULT; + break; + } + + err = wavefront_send_alias (dev, header); + break; + + case WF_ST_DRUM: + if (copy_from_user (&header->hdr.d, header->hdrptr, + sizeof (wavefront_drum))) { + err = -EFAULT; + break; + } + + err = wavefront_send_drum (dev, header); + break; + + case WF_ST_PATCH: + if (copy_from_user (&header->hdr.p, header->hdrptr, + sizeof (wavefront_patch))) { + err = -EFAULT; + break; + } + + err = wavefront_send_patch (dev, header); + break; + + case WF_ST_PROGRAM: + if (copy_from_user (&header->hdr.pr, header->hdrptr, + sizeof (wavefront_program))) { + err = -EFAULT; + break; + } + + err = wavefront_send_program (dev, header); + break; + + default: + snd_printk ("unknown patch type %d.\n", + header->subkey); + err = -EINVAL; + break; + } + + __error: + kfree(header); + return err; +} + +/*********************************************************************** +WaveFront: hardware-dependent interface +***********************************************************************/ + +static void +process_sample_hdr (u8 *buf) + +{ + wavefront_sample s; + u8 *ptr; + + ptr = buf; + + /* The board doesn't send us an exact copy of a "wavefront_sample" + in response to an Upload Sample Header command. Instead, we + have to convert the data format back into our data structure, + just as in the Download Sample command, where we have to do + something very similar in the reverse direction. + */ + + *((u32 *) &s.sampleStartOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.loopStartOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.loopEndOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.sampleEndOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.FrequencyBias) = demunge_int32 (ptr, 3); ptr += 3; + + s.SampleResolution = *ptr & 0x3; + s.Loop = *ptr & 0x8; + s.Bidirectional = *ptr & 0x10; + s.Reverse = *ptr & 0x40; + + /* Now copy it back to where it came from */ + + memcpy (buf, (unsigned char *) &s, sizeof (wavefront_sample)); +} + +static int +wavefront_synth_control (snd_wavefront_card_t *acard, + wavefront_control *wc) + +{ + snd_wavefront_t *dev = &acard->wavefront; + unsigned char patchnumbuf[2]; + int i; + + DPRINT (WF_DEBUG_CMD, "synth control with " + "cmd 0x%x\n", wc->cmd); + + /* Pre-handling of or for various commands */ + + switch (wc->cmd) { + + case WFC_DISABLE_INTERRUPTS: + snd_printk ("interrupts disabled.\n"); + outb (0x80|0x20, dev->control_port); + dev->interrupts_are_midi = 1; + return 0; + + case WFC_ENABLE_INTERRUPTS: + snd_printk ("interrupts enabled.\n"); + outb (0x80|0x40|0x20, dev->control_port); + dev->interrupts_are_midi = 1; + return 0; + + case WFC_INTERRUPT_STATUS: + wc->rbuf[0] = dev->interrupts_are_midi; + return 0; + + case WFC_ROMSAMPLES_RDONLY: + dev->rom_samples_rdonly = wc->wbuf[0]; + wc->status = 0; + return 0; + + case WFC_IDENTIFY_SLOT_TYPE: + i = wc->wbuf[0] | (wc->wbuf[1] << 7); + if (i <0 || i >= WF_MAX_SAMPLE) { + snd_printk ("invalid slot ID %d\n", + i); + wc->status = EINVAL; + return -EINVAL; + } + wc->rbuf[0] = dev->sample_status[i]; + wc->status = 0; + return 0; + + case WFC_DEBUG_DRIVER: + dev->debug = wc->wbuf[0]; + snd_printk ("debug = 0x%x\n", dev->debug); + return 0; + + case WFC_UPLOAD_PATCH: + munge_int32 (*((u32 *) wc->wbuf), patchnumbuf, 2); + memcpy (wc->wbuf, patchnumbuf, 2); + break; + + case WFC_UPLOAD_MULTISAMPLE: + /* multisamples have to be handled differently, and + cannot be dealt with properly by snd_wavefront_cmd() alone. + */ + wc->status = wavefront_fetch_multisample + (dev, (wavefront_patch_info *) wc->rbuf); + return 0; + + case WFC_UPLOAD_SAMPLE_ALIAS: + snd_printk ("support for sample alias upload " + "being considered.\n"); + wc->status = EINVAL; + return -EINVAL; + } + + wc->status = snd_wavefront_cmd (dev, wc->cmd, wc->rbuf, wc->wbuf); + + /* Post-handling of certain commands. + + In particular, if the command was an upload, demunge the data + so that the user-level doesn't have to think about it. + */ + + if (wc->status == 0) { + switch (wc->cmd) { + /* intercept any freemem requests so that we know + we are always current with the user-level view + of things. + */ + + case WFC_REPORT_FREE_MEMORY: + dev->freemem = demunge_int32 (wc->rbuf, 4); + break; + + case WFC_UPLOAD_PATCH: + demunge_buf (wc->rbuf, wc->rbuf, WF_PATCH_BYTES); + break; + + case WFC_UPLOAD_PROGRAM: + demunge_buf (wc->rbuf, wc->rbuf, WF_PROGRAM_BYTES); + break; + + case WFC_UPLOAD_EDRUM_PROGRAM: + demunge_buf (wc->rbuf, wc->rbuf, WF_DRUM_BYTES - 1); + break; + + case WFC_UPLOAD_SAMPLE_HEADER: + process_sample_hdr (wc->rbuf); + break; + + case WFC_UPLOAD_SAMPLE_ALIAS: + snd_printk ("support for " + "sample aliases still " + "being considered.\n"); + break; + + case WFC_VMIDI_OFF: + snd_wavefront_midi_disable_virtual (acard); + break; + + case WFC_VMIDI_ON: + snd_wavefront_midi_enable_virtual (acard); + break; + } + } + + return 0; +} + +int +snd_wavefront_synth_open (struct snd_hwdep *hw, struct file *file) + +{ + if (!try_module_get(hw->card->module)) + return -EFAULT; + file->private_data = hw; + return 0; +} + +int +snd_wavefront_synth_release (struct snd_hwdep *hw, struct file *file) + +{ + module_put(hw->card->module); + return 0; +} + +int +snd_wavefront_synth_ioctl (struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) + +{ + struct snd_card *card; + snd_wavefront_t *dev; + snd_wavefront_card_t *acard; + wavefront_control *wc; + void __user *argp = (void __user *)arg; + int err; + + card = (struct snd_card *) hw->card; + + if (snd_BUG_ON(!card)) + return -ENODEV; + if (snd_BUG_ON(!card->private_data)) + return -ENODEV; + + acard = card->private_data; + dev = &acard->wavefront; + + switch (cmd) { + case WFCTL_LOAD_SPP: + if (wavefront_load_patch (dev, argp) != 0) { + return -EIO; + } + break; + + case WFCTL_WFCMD: + wc = memdup_user(argp, sizeof(*wc)); + if (IS_ERR(wc)) + return PTR_ERR(wc); + + if (wavefront_synth_control (acard, wc) < 0) + err = -EIO; + else if (copy_to_user (argp, wc, sizeof (*wc))) + err = -EFAULT; + else + err = 0; + kfree(wc); + return err; + + default: + return -EINVAL; + } + + return 0; +} + + +/***********************************************************************/ +/* WaveFront: interface for card-level wavefront module */ +/***********************************************************************/ + +void +snd_wavefront_internal_interrupt (snd_wavefront_card_t *card) +{ + snd_wavefront_t *dev = &card->wavefront; + + /* + Some comments on interrupts. I attempted a version of this + driver that used interrupts throughout the code instead of + doing busy and/or sleep-waiting. Alas, it appears that once + the Motorola firmware is downloaded, the card *never* + generates an RX interrupt. These are successfully generated + during firmware loading, and after that wavefront_status() + reports that an interrupt is pending on the card from time + to time, but it never seems to be delivered to this + driver. Note also that wavefront_status() continues to + report that RX interrupts are enabled, suggesting that I + didn't goof up and disable them by mistake. + + Thus, I stepped back to a prior version of + wavefront_wait(), the only place where this really + matters. Its sad, but I've looked through the code to check + on things, and I really feel certain that the Motorola + firmware prevents RX-ready interrupts. + */ + + if ((wavefront_status(dev) & (STAT_INTR_READ|STAT_INTR_WRITE)) == 0) { + return; + } + + spin_lock(&dev->irq_lock); + dev->irq_ok = 1; + dev->irq_cnt++; + spin_unlock(&dev->irq_lock); + wake_up(&dev->interrupt_sleeper); +} + +/* STATUS REGISTER + +0 Host Rx Interrupt Enable (1=Enabled) +1 Host Rx Register Full (1=Full) +2 Host Rx Interrupt Pending (1=Interrupt) +3 Unused +4 Host Tx Interrupt (1=Enabled) +5 Host Tx Register empty (1=Empty) +6 Host Tx Interrupt Pending (1=Interrupt) +7 Unused +*/ + +static int +snd_wavefront_interrupt_bits (int irq) + +{ + int bits; + + switch (irq) { + case 9: + bits = 0x00; + break; + case 5: + bits = 0x08; + break; + case 12: + bits = 0x10; + break; + case 15: + bits = 0x18; + break; + + default: + snd_printk ("invalid IRQ %d\n", irq); + bits = -1; + } + + return bits; +} + +static void +wavefront_should_cause_interrupt (snd_wavefront_t *dev, + int val, int port, unsigned long timeout) + +{ + wait_queue_entry_t wait; + + init_waitqueue_entry(&wait, current); + spin_lock_irq(&dev->irq_lock); + add_wait_queue(&dev->interrupt_sleeper, &wait); + dev->irq_ok = 0; + outb (val,port); + spin_unlock_irq(&dev->irq_lock); + while (!dev->irq_ok && time_before(jiffies, timeout)) { + schedule_timeout_uninterruptible(1); + barrier(); + } +} + +static int +wavefront_reset_to_cleanliness (snd_wavefront_t *dev) + +{ + int bits; + int hwv[2]; + + /* IRQ already checked */ + + bits = snd_wavefront_interrupt_bits (dev->irq); + + /* try reset of port */ + + outb (0x0, dev->control_port); + + /* At this point, the board is in reset, and the H/W initialization + register is accessed at the same address as the data port. + + Bit 7 - Enable IRQ Driver + 0 - Tri-state the Wave-Board drivers for the PC Bus IRQs + 1 - Enable IRQ selected by bits 5:3 to be driven onto the PC Bus. + + Bit 6 - MIDI Interface Select + + 0 - Use the MIDI Input from the 26-pin WaveBlaster + compatible header as the serial MIDI source + 1 - Use the MIDI Input from the 9-pin D connector as the + serial MIDI source. + + Bits 5:3 - IRQ Selection + 0 0 0 - IRQ 2/9 + 0 0 1 - IRQ 5 + 0 1 0 - IRQ 12 + 0 1 1 - IRQ 15 + 1 0 0 - Reserved + 1 0 1 - Reserved + 1 1 0 - Reserved + 1 1 1 - Reserved + + Bits 2:1 - Reserved + Bit 0 - Disable Boot ROM + 0 - memory accesses to 03FC30-03FFFFH utilize the internal Boot ROM + 1 - memory accesses to 03FC30-03FFFFH are directed to external + storage. + + */ + + /* configure hardware: IRQ, enable interrupts, + plus external 9-pin MIDI interface selected + */ + + outb (0x80 | 0x40 | bits, dev->data_port); + + /* CONTROL REGISTER + + 0 Host Rx Interrupt Enable (1=Enabled) 0x1 + 1 Unused 0x2 + 2 Unused 0x4 + 3 Unused 0x8 + 4 Host Tx Interrupt Enable 0x10 + 5 Mute (0=Mute; 1=Play) 0x20 + 6 Master Interrupt Enable (1=Enabled) 0x40 + 7 Master Reset (0=Reset; 1=Run) 0x80 + + Take us out of reset, mute output, master + TX + RX interrupts on. + + We'll get an interrupt presumably to tell us that the TX + register is clear. + */ + + wavefront_should_cause_interrupt(dev, 0x80|0x40|0x10|0x1, + dev->control_port, + (reset_time*HZ)/100); + + /* Note: data port is now the data port, not the h/w initialization + port. + */ + + if (!dev->irq_ok) { + snd_printk ("intr not received after h/w un-reset.\n"); + goto gone_bad; + } + + /* Note: data port is now the data port, not the h/w initialization + port. + + At this point, only "HW VERSION" or "DOWNLOAD OS" commands + will work. So, issue one of them, and wait for TX + interrupt. This can take a *long* time after a cold boot, + while the ISC ROM does its RAM test. The SDK says up to 4 + seconds - with 12MB of RAM on a Tropez+, it takes a lot + longer than that (~16secs). Note that the card understands + the difference between a warm and a cold boot, so + subsequent ISC2115 reboots (say, caused by module + reloading) will get through this much faster. + + XXX Interesting question: why is no RX interrupt received first ? + */ + + wavefront_should_cause_interrupt(dev, WFC_HARDWARE_VERSION, + dev->data_port, ramcheck_time*HZ); + + if (!dev->irq_ok) { + snd_printk ("post-RAM-check interrupt not received.\n"); + goto gone_bad; + } + + if (!wavefront_wait (dev, STAT_CAN_READ)) { + snd_printk ("no response to HW version cmd.\n"); + goto gone_bad; + } + + if ((hwv[0] = wavefront_read (dev)) == -1) { + snd_printk ("board not responding correctly.\n"); + goto gone_bad; + } + + if (hwv[0] == 0xFF) { /* NAK */ + + /* Board's RAM test failed. Try to read error code, + and tell us about it either way. + */ + + if ((hwv[0] = wavefront_read (dev)) == -1) { + snd_printk ("on-board RAM test failed " + "(bad error code).\n"); + } else { + snd_printk ("on-board RAM test failed " + "(error code: 0x%x).\n", + hwv[0]); + } + goto gone_bad; + } + + /* We're OK, just get the next byte of the HW version response */ + + if ((hwv[1] = wavefront_read (dev)) == -1) { + snd_printk ("incorrect h/w response.\n"); + goto gone_bad; + } + + snd_printk ("hardware version %d.%d\n", + hwv[0], hwv[1]); + + return 0; + + + gone_bad: + return (1); +} + +static int +wavefront_download_firmware (snd_wavefront_t *dev, char *path) + +{ + const unsigned char *buf; + int len, err; + int section_cnt_downloaded = 0; + const struct firmware *firmware; + + err = request_firmware(&firmware, path, dev->card->dev); + if (err < 0) { + snd_printk(KERN_ERR "firmware (%s) download failed!!!\n", path); + return 1; + } + + len = 0; + buf = firmware->data; + for (;;) { + int section_length = *(signed char *)buf; + if (section_length == 0) + break; + if (section_length < 0 || section_length > WF_SECTION_MAX) { + snd_printk(KERN_ERR + "invalid firmware section length %d\n", + section_length); + goto failure; + } + buf++; + len++; + + if (firmware->size < len + section_length) { + snd_printk(KERN_ERR "firmware section read error.\n"); + goto failure; + } + + /* Send command */ + if (wavefront_write(dev, WFC_DOWNLOAD_OS)) + goto failure; + + for (; section_length; section_length--) { + if (wavefront_write(dev, *buf)) + goto failure; + buf++; + len++; + } + + /* get ACK */ + if (!wavefront_wait(dev, STAT_CAN_READ)) { + snd_printk(KERN_ERR "time out for firmware ACK.\n"); + goto failure; + } + err = inb(dev->data_port); + if (err != WF_ACK) { + snd_printk(KERN_ERR + "download of section #%d not " + "acknowledged, ack = 0x%x\n", + section_cnt_downloaded + 1, err); + goto failure; + } + + section_cnt_downloaded++; + } + + release_firmware(firmware); + return 0; + + failure: + release_firmware(firmware); + snd_printk(KERN_ERR "firmware download failed!!!\n"); + return 1; +} + + +static int +wavefront_do_reset (snd_wavefront_t *dev) + +{ + char voices[1]; + + if (wavefront_reset_to_cleanliness (dev)) { + snd_printk ("hw reset failed.\n"); + goto gone_bad; + } + + if (dev->israw) { + if (wavefront_download_firmware (dev, ospath)) { + goto gone_bad; + } + + dev->israw = 0; + + /* Wait for the OS to get running. The protocol for + this is non-obvious, and was determined by + using port-IO tracing in DOSemu and some + experimentation here. + + Rather than using timed waits, use interrupts creatively. + */ + + wavefront_should_cause_interrupt (dev, WFC_NOOP, + dev->data_port, + (osrun_time*HZ)); + + if (!dev->irq_ok) { + snd_printk ("no post-OS interrupt.\n"); + goto gone_bad; + } + + /* Now, do it again ! */ + + wavefront_should_cause_interrupt (dev, WFC_NOOP, + dev->data_port, (10*HZ)); + + if (!dev->irq_ok) { + snd_printk ("no post-OS interrupt(2).\n"); + goto gone_bad; + } + + /* OK, no (RX/TX) interrupts any more, but leave mute + in effect. + */ + + outb (0x80|0x40, dev->control_port); + } + + /* SETUPSND.EXE asks for sample memory config here, but since i + have no idea how to interpret the result, we'll forget + about it. + */ + + if ((dev->freemem = wavefront_freemem (dev)) < 0) { + goto gone_bad; + } + + snd_printk ("available DRAM %dk\n", dev->freemem / 1024); + + if (wavefront_write (dev, 0xf0) || + wavefront_write (dev, 1) || + (wavefront_read (dev) < 0)) { + dev->debug = 0; + snd_printk ("MPU emulation mode not set.\n"); + goto gone_bad; + } + + voices[0] = 32; + + if (snd_wavefront_cmd (dev, WFC_SET_NVOICES, NULL, voices)) { + snd_printk ("cannot set number of voices to 32.\n"); + goto gone_bad; + } + + + return 0; + + gone_bad: + /* reset that sucker so that it doesn't bother us. */ + + outb (0x0, dev->control_port); + dev->interrupts_are_midi = 0; + return 1; +} + +int +snd_wavefront_start (snd_wavefront_t *dev) + +{ + int samples_are_from_rom; + + /* IMPORTANT: assumes that snd_wavefront_detect() and/or + wavefront_reset_to_cleanliness() has already been called + */ + + if (dev->israw) { + samples_are_from_rom = 1; + } else { + /* XXX is this always true ? */ + samples_are_from_rom = 0; + } + + if (dev->israw || fx_raw) { + if (wavefront_do_reset (dev)) { + return -1; + } + } + /* Check for FX device, present only on Tropez+ */ + + dev->has_fx = (snd_wavefront_fx_detect (dev) == 0); + + if (dev->has_fx && fx_raw) { + snd_wavefront_fx_start (dev); + } + + wavefront_get_sample_status (dev, samples_are_from_rom); + wavefront_get_program_status (dev); + wavefront_get_patch_status (dev); + + /* Start normal operation: unreset, master interrupt enabled, no mute + */ + + outb (0x80|0x40|0x20, dev->control_port); + + return (0); +} + +int +snd_wavefront_detect (snd_wavefront_card_t *card) + +{ + unsigned char rbuf[4], wbuf[4]; + snd_wavefront_t *dev = &card->wavefront; + + /* returns zero if a WaveFront card is successfully detected. + negative otherwise. + */ + + dev->israw = 0; + dev->has_fx = 0; + dev->debug = debug_default; + dev->interrupts_are_midi = 0; + dev->irq_cnt = 0; + dev->rom_samples_rdonly = 1; + + if (snd_wavefront_cmd (dev, WFC_FIRMWARE_VERSION, rbuf, wbuf) == 0) { + + dev->fw_version[0] = rbuf[0]; + dev->fw_version[1] = rbuf[1]; + + snd_printk ("firmware %d.%d already loaded.\n", + rbuf[0], rbuf[1]); + + /* check that a command actually works */ + + if (snd_wavefront_cmd (dev, WFC_HARDWARE_VERSION, + rbuf, wbuf) == 0) { + dev->hw_version[0] = rbuf[0]; + dev->hw_version[1] = rbuf[1]; + } else { + snd_printk ("not raw, but no " + "hardware version!\n"); + return -1; + } + + if (!wf_raw) { + return 0; + } else { + snd_printk ("reloading firmware as you requested.\n"); + dev->israw = 1; + } + + } else { + + dev->israw = 1; + snd_printk ("no response to firmware probe, assume raw.\n"); + + } + + return 0; +} + +MODULE_FIRMWARE(DEFAULT_OSPATH); diff --git a/sound/isa/wss/Makefile b/sound/isa/wss/Makefile new file mode 100644 index 000000000..454fee769 --- /dev/null +++ b/sound/isa/wss/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 2008 by Jaroslav Kysela +# + +snd-wss-lib-objs := wss_lib.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_WSS_LIB) += snd-wss-lib.o + diff --git a/sound/isa/wss/wss_lib.c b/sound/isa/wss/wss_lib.c new file mode 100644 index 000000000..3a5008837 --- /dev/null +++ b/sound/isa/wss/wss_lib.c @@ -0,0 +1,2279 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of CS4231(A)/CS4232/InterWave & compatible chips + * + * Bugs: + * - sometimes record brokes playback with WSS portion of + * Yamaha OPL3-SA3 chip + * - CS4231 (GUS MAX) - still trouble with occasional noises + * - broken initialization? + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Routines for control of CS4231(A)/CS4232/InterWave & compatible chips"); +MODULE_LICENSE("GPL"); + +#if 0 +#define SNDRV_DEBUG_MCE +#endif + +/* + * Some variables + */ + +static unsigned char freq_bits[14] = { + /* 5510 */ 0x00 | CS4231_XTAL2, + /* 6620 */ 0x0E | CS4231_XTAL2, + /* 8000 */ 0x00 | CS4231_XTAL1, + /* 9600 */ 0x0E | CS4231_XTAL1, + /* 11025 */ 0x02 | CS4231_XTAL2, + /* 16000 */ 0x02 | CS4231_XTAL1, + /* 18900 */ 0x04 | CS4231_XTAL2, + /* 22050 */ 0x06 | CS4231_XTAL2, + /* 27042 */ 0x04 | CS4231_XTAL1, + /* 32000 */ 0x06 | CS4231_XTAL1, + /* 33075 */ 0x0C | CS4231_XTAL2, + /* 37800 */ 0x08 | CS4231_XTAL2, + /* 44100 */ 0x0A | CS4231_XTAL2, + /* 48000 */ 0x0C | CS4231_XTAL1 +}; + +static const unsigned int rates[14] = { + 5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050, + 27042, 32000, 33075, 37800, 44100, 48000 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int snd_wss_xrate(struct snd_pcm_runtime *runtime) +{ + return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); +} + +static unsigned char snd_wss_original_image[32] = +{ + 0x00, /* 00/00 - lic */ + 0x00, /* 01/01 - ric */ + 0x9f, /* 02/02 - la1ic */ + 0x9f, /* 03/03 - ra1ic */ + 0x9f, /* 04/04 - la2ic */ + 0x9f, /* 05/05 - ra2ic */ + 0xbf, /* 06/06 - loc */ + 0xbf, /* 07/07 - roc */ + 0x20, /* 08/08 - pdfr */ + CS4231_AUTOCALIB, /* 09/09 - ic */ + 0x00, /* 0a/10 - pc */ + 0x00, /* 0b/11 - ti */ + CS4231_MODE2, /* 0c/12 - mi */ + 0xfc, /* 0d/13 - lbc */ + 0x00, /* 0e/14 - pbru */ + 0x00, /* 0f/15 - pbrl */ + 0x80, /* 10/16 - afei */ + 0x01, /* 11/17 - afeii */ + 0x9f, /* 12/18 - llic */ + 0x9f, /* 13/19 - rlic */ + 0x00, /* 14/20 - tlb */ + 0x00, /* 15/21 - thb */ + 0x00, /* 16/22 - la3mic/reserved */ + 0x00, /* 17/23 - ra3mic/reserved */ + 0x00, /* 18/24 - afs */ + 0x00, /* 19/25 - lamoc/version */ + 0xcf, /* 1a/26 - mioc */ + 0x00, /* 1b/27 - ramoc/reserved */ + 0x20, /* 1c/28 - cdfr */ + 0x00, /* 1d/29 - res4 */ + 0x00, /* 1e/30 - cbru */ + 0x00, /* 1f/31 - cbrl */ +}; + +static unsigned char snd_opti93x_original_image[32] = +{ + 0x00, /* 00/00 - l_mixout_outctrl */ + 0x00, /* 01/01 - r_mixout_outctrl */ + 0x88, /* 02/02 - l_cd_inctrl */ + 0x88, /* 03/03 - r_cd_inctrl */ + 0x88, /* 04/04 - l_a1/fm_inctrl */ + 0x88, /* 05/05 - r_a1/fm_inctrl */ + 0x80, /* 06/06 - l_dac_inctrl */ + 0x80, /* 07/07 - r_dac_inctrl */ + 0x00, /* 08/08 - ply_dataform_reg */ + 0x00, /* 09/09 - if_conf */ + 0x00, /* 0a/10 - pin_ctrl */ + 0x00, /* 0b/11 - err_init_reg */ + 0x0a, /* 0c/12 - id_reg */ + 0x00, /* 0d/13 - reserved */ + 0x00, /* 0e/14 - ply_upcount_reg */ + 0x00, /* 0f/15 - ply_lowcount_reg */ + 0x88, /* 10/16 - reserved/l_a1_inctrl */ + 0x88, /* 11/17 - reserved/r_a1_inctrl */ + 0x88, /* 12/18 - l_line_inctrl */ + 0x88, /* 13/19 - r_line_inctrl */ + 0x88, /* 14/20 - l_mic_inctrl */ + 0x88, /* 15/21 - r_mic_inctrl */ + 0x80, /* 16/22 - l_out_outctrl */ + 0x80, /* 17/23 - r_out_outctrl */ + 0x00, /* 18/24 - reserved */ + 0x00, /* 19/25 - reserved */ + 0x00, /* 1a/26 - reserved */ + 0x00, /* 1b/27 - reserved */ + 0x00, /* 1c/28 - cap_dataform_reg */ + 0x00, /* 1d/29 - reserved */ + 0x00, /* 1e/30 - cap_upcount_reg */ + 0x00 /* 1f/31 - cap_lowcount_reg */ +}; + +/* + * Basic I/O functions + */ + +static inline void wss_outb(struct snd_wss *chip, u8 offset, u8 val) +{ + outb(val, chip->port + offset); +} + +static inline u8 wss_inb(struct snd_wss *chip, u8 offset) +{ + return inb(chip->port + offset); +} + +static void snd_wss_wait(struct snd_wss *chip) +{ + int timeout; + + for (timeout = 250; + timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(100); +} + +static void snd_wss_dout(struct snd_wss *chip, unsigned char reg, + unsigned char value) +{ + int timeout; + + for (timeout = 250; + timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(10); + wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg); + wss_outb(chip, CS4231P(REG), value); + mb(); +} + +void snd_wss_out(struct snd_wss *chip, unsigned char reg, unsigned char value) +{ + snd_wss_wait(chip); +#ifdef CONFIG_SND_DEBUG + if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk(KERN_DEBUG "out: auto calibration time out " + "- reg = 0x%x, value = 0x%x\n", reg, value); +#endif + wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg); + wss_outb(chip, CS4231P(REG), value); + chip->image[reg] = value; + mb(); + snd_printdd("codec out - reg 0x%x = 0x%x\n", + chip->mce_bit | reg, value); +} +EXPORT_SYMBOL(snd_wss_out); + +unsigned char snd_wss_in(struct snd_wss *chip, unsigned char reg) +{ + snd_wss_wait(chip); +#ifdef CONFIG_SND_DEBUG + if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk(KERN_DEBUG "in: auto calibration time out " + "- reg = 0x%x\n", reg); +#endif + wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg); + mb(); + return wss_inb(chip, CS4231P(REG)); +} +EXPORT_SYMBOL(snd_wss_in); + +void snd_cs4236_ext_out(struct snd_wss *chip, unsigned char reg, + unsigned char val) +{ + wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17); + wss_outb(chip, CS4231P(REG), + reg | (chip->image[CS4236_EXT_REG] & 0x01)); + wss_outb(chip, CS4231P(REG), val); + chip->eimage[CS4236_REG(reg)] = val; +#if 0 + printk(KERN_DEBUG "ext out : reg = 0x%x, val = 0x%x\n", reg, val); +#endif +} +EXPORT_SYMBOL(snd_cs4236_ext_out); + +unsigned char snd_cs4236_ext_in(struct snd_wss *chip, unsigned char reg) +{ + wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17); + wss_outb(chip, CS4231P(REG), + reg | (chip->image[CS4236_EXT_REG] & 0x01)); +#if 1 + return wss_inb(chip, CS4231P(REG)); +#else + { + unsigned char res; + res = wss_inb(chip, CS4231P(REG)); + printk(KERN_DEBUG "ext in : reg = 0x%x, val = 0x%x\n", + reg, res); + return res; + } +#endif +} +EXPORT_SYMBOL(snd_cs4236_ext_in); + +#if 0 + +static void snd_wss_debug(struct snd_wss *chip) +{ + printk(KERN_DEBUG + "CS4231 REGS: INDEX = 0x%02x " + " STATUS = 0x%02x\n", + wss_inb(chip, CS4231P(REGSEL)), + wss_inb(chip, CS4231P(STATUS))); + printk(KERN_DEBUG + " 0x00: left input = 0x%02x " + " 0x10: alt 1 (CFIG 2) = 0x%02x\n", + snd_wss_in(chip, 0x00), + snd_wss_in(chip, 0x10)); + printk(KERN_DEBUG + " 0x01: right input = 0x%02x " + " 0x11: alt 2 (CFIG 3) = 0x%02x\n", + snd_wss_in(chip, 0x01), + snd_wss_in(chip, 0x11)); + printk(KERN_DEBUG + " 0x02: GF1 left input = 0x%02x " + " 0x12: left line in = 0x%02x\n", + snd_wss_in(chip, 0x02), + snd_wss_in(chip, 0x12)); + printk(KERN_DEBUG + " 0x03: GF1 right input = 0x%02x " + " 0x13: right line in = 0x%02x\n", + snd_wss_in(chip, 0x03), + snd_wss_in(chip, 0x13)); + printk(KERN_DEBUG + " 0x04: CD left input = 0x%02x " + " 0x14: timer low = 0x%02x\n", + snd_wss_in(chip, 0x04), + snd_wss_in(chip, 0x14)); + printk(KERN_DEBUG + " 0x05: CD right input = 0x%02x " + " 0x15: timer high = 0x%02x\n", + snd_wss_in(chip, 0x05), + snd_wss_in(chip, 0x15)); + printk(KERN_DEBUG + " 0x06: left output = 0x%02x " + " 0x16: left MIC (PnP) = 0x%02x\n", + snd_wss_in(chip, 0x06), + snd_wss_in(chip, 0x16)); + printk(KERN_DEBUG + " 0x07: right output = 0x%02x " + " 0x17: right MIC (PnP) = 0x%02x\n", + snd_wss_in(chip, 0x07), + snd_wss_in(chip, 0x17)); + printk(KERN_DEBUG + " 0x08: playback format = 0x%02x " + " 0x18: IRQ status = 0x%02x\n", + snd_wss_in(chip, 0x08), + snd_wss_in(chip, 0x18)); + printk(KERN_DEBUG + " 0x09: iface (CFIG 1) = 0x%02x " + " 0x19: left line out = 0x%02x\n", + snd_wss_in(chip, 0x09), + snd_wss_in(chip, 0x19)); + printk(KERN_DEBUG + " 0x0a: pin control = 0x%02x " + " 0x1a: mono control = 0x%02x\n", + snd_wss_in(chip, 0x0a), + snd_wss_in(chip, 0x1a)); + printk(KERN_DEBUG + " 0x0b: init & status = 0x%02x " + " 0x1b: right line out = 0x%02x\n", + snd_wss_in(chip, 0x0b), + snd_wss_in(chip, 0x1b)); + printk(KERN_DEBUG + " 0x0c: revision & mode = 0x%02x " + " 0x1c: record format = 0x%02x\n", + snd_wss_in(chip, 0x0c), + snd_wss_in(chip, 0x1c)); + printk(KERN_DEBUG + " 0x0d: loopback = 0x%02x " + " 0x1d: var freq (PnP) = 0x%02x\n", + snd_wss_in(chip, 0x0d), + snd_wss_in(chip, 0x1d)); + printk(KERN_DEBUG + " 0x0e: ply upr count = 0x%02x " + " 0x1e: ply lwr count = 0x%02x\n", + snd_wss_in(chip, 0x0e), + snd_wss_in(chip, 0x1e)); + printk(KERN_DEBUG + " 0x0f: rec upr count = 0x%02x " + " 0x1f: rec lwr count = 0x%02x\n", + snd_wss_in(chip, 0x0f), + snd_wss_in(chip, 0x1f)); +} + +#endif + +/* + * CS4231 detection / MCE routines + */ + +static void snd_wss_busy_wait(struct snd_wss *chip) +{ + int timeout; + + /* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */ + for (timeout = 5; timeout > 0; timeout--) + wss_inb(chip, CS4231P(REGSEL)); + /* end of cleanup sequence */ + for (timeout = 25000; + timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(10); +} + +void snd_wss_mce_up(struct snd_wss *chip) +{ + unsigned long flags; + int timeout; + + snd_wss_wait(chip); +#ifdef CONFIG_SND_DEBUG + if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk(KERN_DEBUG + "mce_up - auto calibration time out (0)\n"); +#endif + spin_lock_irqsave(&chip->reg_lock, flags); + chip->mce_bit |= CS4231_MCE; + timeout = wss_inb(chip, CS4231P(REGSEL)); + if (timeout == 0x80) + snd_printk(KERN_DEBUG "mce_up [0x%lx]: " + "serious init problem - codec still busy\n", + chip->port); + if (!(timeout & CS4231_MCE)) + wss_outb(chip, CS4231P(REGSEL), + chip->mce_bit | (timeout & 0x1f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} +EXPORT_SYMBOL(snd_wss_mce_up); + +void snd_wss_mce_down(struct snd_wss *chip) +{ + unsigned long flags; + unsigned long end_time; + int timeout; + int hw_mask = WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK | WSS_HW_AD1848; + + snd_wss_busy_wait(chip); + +#ifdef CONFIG_SND_DEBUG + if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk(KERN_DEBUG "mce_down [0x%lx] - " + "auto calibration time out (0)\n", + (long)CS4231P(REGSEL)); +#endif + spin_lock_irqsave(&chip->reg_lock, flags); + chip->mce_bit &= ~CS4231_MCE; + timeout = wss_inb(chip, CS4231P(REGSEL)); + wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (timeout == 0x80) + snd_printk(KERN_DEBUG "mce_down [0x%lx]: " + "serious init problem - codec still busy\n", + chip->port); + if ((timeout & CS4231_MCE) == 0 || !(chip->hardware & hw_mask)) + return; + + /* + * Wait for (possible -- during init auto-calibration may not be set) + * calibration process to start. Needs up to 5 sample periods on AD1848 + * which at the slowest possible rate of 5.5125 kHz means 907 us. + */ + msleep(1); + + snd_printdd("(1) jiffies = %lu\n", jiffies); + + /* check condition up to 250 ms */ + end_time = jiffies + msecs_to_jiffies(250); + while (snd_wss_in(chip, CS4231_TEST_INIT) & + CS4231_CALIB_IN_PROGRESS) { + + if (time_after(jiffies, end_time)) { + snd_printk(KERN_ERR "mce_down - " + "auto calibration time out (2)\n"); + return; + } + msleep(1); + } + + snd_printdd("(2) jiffies = %lu\n", jiffies); + + /* check condition up to 100 ms */ + end_time = jiffies + msecs_to_jiffies(100); + while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) { + if (time_after(jiffies, end_time)) { + snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n"); + return; + } + msleep(1); + } + + snd_printdd("(3) jiffies = %lu\n", jiffies); + snd_printd("mce_down - exit = 0x%x\n", wss_inb(chip, CS4231P(REGSEL))); +} +EXPORT_SYMBOL(snd_wss_mce_down); + +static unsigned int snd_wss_get_count(unsigned char format, unsigned int size) +{ + switch (format & 0xe0) { + case CS4231_LINEAR_16: + case CS4231_LINEAR_16_BIG: + size >>= 1; + break; + case CS4231_ADPCM_16: + return size >> 2; + } + if (format & CS4231_STEREO) + size >>= 1; + return size; +} + +static int snd_wss_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + int result = 0; + unsigned int what; + struct snd_pcm_substream *s; + int do_start; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + do_start = 1; break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + do_start = 0; break; + default: + return -EINVAL; + } + + what = 0; + snd_pcm_group_for_each_entry(s, substream) { + if (s == chip->playback_substream) { + what |= CS4231_PLAYBACK_ENABLE; + snd_pcm_trigger_done(s, substream); + } else if (s == chip->capture_substream) { + what |= CS4231_RECORD_ENABLE; + snd_pcm_trigger_done(s, substream); + } + } + spin_lock(&chip->reg_lock); + if (do_start) { + chip->image[CS4231_IFACE_CTRL] |= what; + if (chip->trigger) + chip->trigger(chip, what, 1); + } else { + chip->image[CS4231_IFACE_CTRL] &= ~what; + if (chip->trigger) + chip->trigger(chip, what, 0); + } + snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); + spin_unlock(&chip->reg_lock); +#if 0 + snd_wss_debug(chip); +#endif + return result; +} + +/* + * CODEC I/O + */ + +static unsigned char snd_wss_get_rate(unsigned int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (rate == rates[i]) + return freq_bits[i]; + // snd_BUG(); + return freq_bits[ARRAY_SIZE(rates) - 1]; +} + +static unsigned char snd_wss_get_format(struct snd_wss *chip, + snd_pcm_format_t format, + int channels) +{ + unsigned char rformat; + + rformat = CS4231_LINEAR_8; + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: rformat = CS4231_ULAW_8; break; + case SNDRV_PCM_FORMAT_A_LAW: rformat = CS4231_ALAW_8; break; + case SNDRV_PCM_FORMAT_S16_LE: rformat = CS4231_LINEAR_16; break; + case SNDRV_PCM_FORMAT_S16_BE: rformat = CS4231_LINEAR_16_BIG; break; + case SNDRV_PCM_FORMAT_IMA_ADPCM: rformat = CS4231_ADPCM_16; break; + } + if (channels > 1) + rformat |= CS4231_STEREO; +#if 0 + snd_printk(KERN_DEBUG "get_format: 0x%x (mode=0x%x)\n", format, mode); +#endif + return rformat; +} + +static void snd_wss_calibrate_mute(struct snd_wss *chip, int mute) +{ + unsigned long flags; + + mute = mute ? 0x80 : 0; + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->calibrate_mute == mute) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return; + } + if (!mute) { + snd_wss_dout(chip, CS4231_LEFT_INPUT, + chip->image[CS4231_LEFT_INPUT]); + snd_wss_dout(chip, CS4231_RIGHT_INPUT, + chip->image[CS4231_RIGHT_INPUT]); + snd_wss_dout(chip, CS4231_LOOPBACK, + chip->image[CS4231_LOOPBACK]); + } else { + snd_wss_dout(chip, CS4231_LEFT_INPUT, + 0); + snd_wss_dout(chip, CS4231_RIGHT_INPUT, + 0); + snd_wss_dout(chip, CS4231_LOOPBACK, + 0xfd); + } + + snd_wss_dout(chip, CS4231_AUX1_LEFT_INPUT, + mute | chip->image[CS4231_AUX1_LEFT_INPUT]); + snd_wss_dout(chip, CS4231_AUX1_RIGHT_INPUT, + mute | chip->image[CS4231_AUX1_RIGHT_INPUT]); + snd_wss_dout(chip, CS4231_AUX2_LEFT_INPUT, + mute | chip->image[CS4231_AUX2_LEFT_INPUT]); + snd_wss_dout(chip, CS4231_AUX2_RIGHT_INPUT, + mute | chip->image[CS4231_AUX2_RIGHT_INPUT]); + snd_wss_dout(chip, CS4231_LEFT_OUTPUT, + mute | chip->image[CS4231_LEFT_OUTPUT]); + snd_wss_dout(chip, CS4231_RIGHT_OUTPUT, + mute | chip->image[CS4231_RIGHT_OUTPUT]); + if (!(chip->hardware & WSS_HW_AD1848_MASK)) { + snd_wss_dout(chip, CS4231_LEFT_LINE_IN, + mute | chip->image[CS4231_LEFT_LINE_IN]); + snd_wss_dout(chip, CS4231_RIGHT_LINE_IN, + mute | chip->image[CS4231_RIGHT_LINE_IN]); + snd_wss_dout(chip, CS4231_MONO_CTRL, + mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]); + } + if (chip->hardware == WSS_HW_INTERWAVE) { + snd_wss_dout(chip, CS4231_LEFT_MIC_INPUT, + mute | chip->image[CS4231_LEFT_MIC_INPUT]); + snd_wss_dout(chip, CS4231_RIGHT_MIC_INPUT, + mute | chip->image[CS4231_RIGHT_MIC_INPUT]); + snd_wss_dout(chip, CS4231_LINE_LEFT_OUTPUT, + mute | chip->image[CS4231_LINE_LEFT_OUTPUT]); + snd_wss_dout(chip, CS4231_LINE_RIGHT_OUTPUT, + mute | chip->image[CS4231_LINE_RIGHT_OUTPUT]); + } + chip->calibrate_mute = mute; + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_wss_playback_format(struct snd_wss *chip, + struct snd_pcm_hw_params *params, + unsigned char pdfr) +{ + unsigned long flags; + int full_calib = 1; + + mutex_lock(&chip->mce_mutex); + if (chip->hardware == WSS_HW_CS4231A || + (chip->hardware & WSS_HW_CS4232_MASK)) { + spin_lock_irqsave(&chip->reg_lock, flags); + if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) { /* rate is same? */ + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] | 0x10); + chip->image[CS4231_PLAYBK_FORMAT] = pdfr; + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, + chip->image[CS4231_PLAYBK_FORMAT]); + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] &= ~0x10); + udelay(100); /* Fixes audible clicks at least on GUS MAX */ + full_calib = 0; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + } else if (chip->hardware == WSS_HW_AD1845) { + unsigned rate = params_rate(params); + + /* + * Program the AD1845 correctly for the playback stream. + * Note that we do NOT need to toggle the MCE bit because + * the PLAYBACK_ENABLE bit of the Interface Configuration + * register is set. + * + * NOTE: We seem to need to write to the MSB before the LSB + * to get the correct sample frequency. + */ + spin_lock_irqsave(&chip->reg_lock, flags); + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, (pdfr & 0xf0)); + snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff); + snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff); + full_calib = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + if (full_calib) { + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->hardware != WSS_HW_INTERWAVE && !chip->single_dma) { + if (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) + pdfr = (pdfr & 0xf0) | + (chip->image[CS4231_REC_FORMAT] & 0x0f); + } else { + chip->image[CS4231_PLAYBK_FORMAT] = pdfr; + } + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (chip->hardware == WSS_HW_OPL3SA2) + udelay(100); /* this seems to help */ + snd_wss_mce_down(chip); + } + mutex_unlock(&chip->mce_mutex); +} + +static void snd_wss_capture_format(struct snd_wss *chip, + struct snd_pcm_hw_params *params, + unsigned char cdfr) +{ + unsigned long flags; + int full_calib = 1; + + mutex_lock(&chip->mce_mutex); + if (chip->hardware == WSS_HW_CS4231A || + (chip->hardware & WSS_HW_CS4232_MASK)) { + spin_lock_irqsave(&chip->reg_lock, flags); + if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (cdfr & 0x0f) || /* rate is same? */ + (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) { + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] | 0x20); + snd_wss_out(chip, CS4231_REC_FORMAT, + chip->image[CS4231_REC_FORMAT] = cdfr); + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] &= ~0x20); + full_calib = 0; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + } else if (chip->hardware == WSS_HW_AD1845) { + unsigned rate = params_rate(params); + + /* + * Program the AD1845 correctly for the capture stream. + * Note that we do NOT need to toggle the MCE bit because + * the PLAYBACK_ENABLE bit of the Interface Configuration + * register is set. + * + * NOTE: We seem to need to write to the MSB before the LSB + * to get the correct sample frequency. + */ + spin_lock_irqsave(&chip->reg_lock, flags); + snd_wss_out(chip, CS4231_REC_FORMAT, (cdfr & 0xf0)); + snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff); + snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff); + full_calib = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + if (full_calib) { + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->hardware != WSS_HW_INTERWAVE && + !(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) { + if (chip->single_dma) + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr); + else + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, + (chip->image[CS4231_PLAYBK_FORMAT] & 0xf0) | + (cdfr & 0x0f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + } + if (chip->hardware & WSS_HW_AD1848_MASK) + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr); + else + snd_wss_out(chip, CS4231_REC_FORMAT, cdfr); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + } + mutex_unlock(&chip->mce_mutex); +} + +/* + * Timer interface + */ + +static unsigned long snd_wss_timer_resolution(struct snd_timer *timer) +{ + struct snd_wss *chip = snd_timer_chip(timer); + if (chip->hardware & WSS_HW_CS4236B_MASK) + return 14467; + else + return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920; +} + +static int snd_wss_timer_start(struct snd_timer *timer) +{ + unsigned long flags; + unsigned int ticks; + struct snd_wss *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->reg_lock, flags); + ticks = timer->sticks; + if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 || + (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] || + (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) { + chip->image[CS4231_TIMER_HIGH] = (unsigned char) (ticks >> 8); + snd_wss_out(chip, CS4231_TIMER_HIGH, + chip->image[CS4231_TIMER_HIGH]); + chip->image[CS4231_TIMER_LOW] = (unsigned char) ticks; + snd_wss_out(chip, CS4231_TIMER_LOW, + chip->image[CS4231_TIMER_LOW]); + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] | + CS4231_TIMER_ENABLE); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_wss_timer_stop(struct snd_timer *timer) +{ + unsigned long flags; + struct snd_wss *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE; + snd_wss_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static void snd_wss_init(struct snd_wss *chip) +{ + unsigned long flags; + + snd_wss_calibrate_mute(chip, 1); + snd_wss_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk(KERN_DEBUG "init: (1)\n"); +#endif + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | + CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | + CS4231_RECORD_PIO | + CS4231_CALIB_MODE); + chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB; + snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk(KERN_DEBUG "init: (2)\n"); +#endif + + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[CS4231_IFACE_CTRL] &= ~CS4231_AUTOCALIB; + snd_wss_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); + snd_wss_out(chip, + CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk(KERN_DEBUG "init: (3) - afei = 0x%x\n", + chip->image[CS4231_ALT_FEATURE_1]); +#endif + + spin_lock_irqsave(&chip->reg_lock, flags); + snd_wss_out(chip, CS4231_ALT_FEATURE_2, + chip->image[CS4231_ALT_FEATURE_2]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, + chip->image[CS4231_PLAYBK_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk(KERN_DEBUG "init: (4)\n"); +#endif + + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + if (!(chip->hardware & WSS_HW_AD1848_MASK)) + snd_wss_out(chip, CS4231_REC_FORMAT, + chip->image[CS4231_REC_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + snd_wss_calibrate_mute(chip, 0); + +#ifdef SNDRV_DEBUG_MCE + snd_printk(KERN_DEBUG "init: (5)\n"); +#endif +} + +static int snd_wss_open(struct snd_wss *chip, unsigned int mode) +{ + unsigned long flags; + + mutex_lock(&chip->open_mutex); + if ((chip->mode & mode) || + ((chip->mode & WSS_MODE_OPEN) && chip->single_dma)) { + mutex_unlock(&chip->open_mutex); + return -EAGAIN; + } + if (chip->mode & WSS_MODE_OPEN) { + chip->mode |= mode; + mutex_unlock(&chip->open_mutex); + return 0; + } + /* ok. now enable and ack CODEC IRQ */ + spin_lock_irqsave(&chip->reg_lock, flags); + if (!(chip->hardware & WSS_HW_AD1848_MASK)) { + snd_wss_out(chip, CS4231_IRQ_STATUS, + CS4231_PLAYBACK_IRQ | + CS4231_RECORD_IRQ | + CS4231_TIMER_IRQ); + snd_wss_out(chip, CS4231_IRQ_STATUS, 0); + } + wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE; + snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]); + if (!(chip->hardware & WSS_HW_AD1848_MASK)) { + snd_wss_out(chip, CS4231_IRQ_STATUS, + CS4231_PLAYBACK_IRQ | + CS4231_RECORD_IRQ | + CS4231_TIMER_IRQ); + snd_wss_out(chip, CS4231_IRQ_STATUS, 0); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + + chip->mode = mode; + mutex_unlock(&chip->open_mutex); + return 0; +} + +static void snd_wss_close(struct snd_wss *chip, unsigned int mode) +{ + unsigned long flags; + + mutex_lock(&chip->open_mutex); + chip->mode &= ~mode; + if (chip->mode & WSS_MODE_OPEN) { + mutex_unlock(&chip->open_mutex); + return; + } + /* disable IRQ */ + spin_lock_irqsave(&chip->reg_lock, flags); + if (!(chip->hardware & WSS_HW_AD1848_MASK)) + snd_wss_out(chip, CS4231_IRQ_STATUS, 0); + wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE; + snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]); + + /* now disable record & playback */ + + if (chip->image[CS4231_IFACE_CTRL] & (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | CS4231_RECORD_PIO); + snd_wss_out(chip, CS4231_IFACE_CTRL, + chip->image[CS4231_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_down(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + } + + /* clear IRQ again */ + if (!(chip->hardware & WSS_HW_AD1848_MASK)) + snd_wss_out(chip, CS4231_IRQ_STATUS, 0); + wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + wss_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + spin_unlock_irqrestore(&chip->reg_lock, flags); + + chip->mode = 0; + mutex_unlock(&chip->open_mutex); +} + +/* + * timer open/close + */ + +static int snd_wss_timer_open(struct snd_timer *timer) +{ + struct snd_wss *chip = snd_timer_chip(timer); + snd_wss_open(chip, WSS_MODE_TIMER); + return 0; +} + +static int snd_wss_timer_close(struct snd_timer *timer) +{ + struct snd_wss *chip = snd_timer_chip(timer); + snd_wss_close(chip, WSS_MODE_TIMER); + return 0; +} + +static struct snd_timer_hardware snd_wss_timer_table = +{ + .flags = SNDRV_TIMER_HW_AUTO, + .resolution = 9945, + .ticks = 65535, + .open = snd_wss_timer_open, + .close = snd_wss_timer_close, + .c_resolution = snd_wss_timer_resolution, + .start = snd_wss_timer_start, + .stop = snd_wss_timer_stop, +}; + +/* + * ok.. exported functions.. + */ + +static int snd_wss_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + unsigned char new_pdfr; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + new_pdfr = snd_wss_get_format(chip, params_format(hw_params), + params_channels(hw_params)) | + snd_wss_get_rate(params_rate(hw_params)); + chip->set_playback_format(chip, hw_params, new_pdfr); + return 0; +} + +static int snd_wss_playback_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_wss_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + spin_lock_irqsave(&chip->reg_lock, flags); + chip->p_dma_size = size; + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO); + snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1; + snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count); + snd_wss_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); +#if 0 + snd_wss_debug(chip); +#endif + return 0; +} + +static int snd_wss_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + unsigned char new_cdfr; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + new_cdfr = snd_wss_get_format(chip, params_format(hw_params), + params_channels(hw_params)) | + snd_wss_get_rate(params_rate(hw_params)); + chip->set_capture_format(chip, hw_params, new_cdfr); + return 0; +} + +static int snd_wss_capture_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_wss_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + spin_lock_irqsave(&chip->reg_lock, flags); + chip->c_dma_size = size; + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO); + snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + if (chip->hardware & WSS_HW_AD1848_MASK) + count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT], + count); + else + count = snd_wss_get_count(chip->image[CS4231_REC_FORMAT], + count); + count--; + if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) { + snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count); + snd_wss_out(chip, CS4231_PLY_UPR_CNT, + (unsigned char) (count >> 8)); + } else { + snd_wss_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count); + snd_wss_out(chip, CS4231_REC_UPR_CNT, + (unsigned char) (count >> 8)); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +void snd_wss_overrange(struct snd_wss *chip) +{ + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&chip->reg_lock, flags); + res = snd_wss_in(chip, CS4231_TEST_INIT); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (res & (0x08 | 0x02)) /* detect overrange only above 0dB; may be user selectable? */ + chip->capture_substream->runtime->overrange++; +} +EXPORT_SYMBOL(snd_wss_overrange); + +irqreturn_t snd_wss_interrupt(int irq, void *dev_id) +{ + struct snd_wss *chip = dev_id; + unsigned char status; + + if (chip->hardware & WSS_HW_AD1848_MASK) + /* pretend it was the only possible irq for AD1848 */ + status = CS4231_PLAYBACK_IRQ; + else + status = snd_wss_in(chip, CS4231_IRQ_STATUS); + if (status & CS4231_TIMER_IRQ) { + if (chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + } + if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) { + if (status & CS4231_PLAYBACK_IRQ) { + if (chip->mode & WSS_MODE_PLAY) { + if (chip->playback_substream) + snd_pcm_period_elapsed(chip->playback_substream); + } + if (chip->mode & WSS_MODE_RECORD) { + if (chip->capture_substream) { + snd_wss_overrange(chip); + snd_pcm_period_elapsed(chip->capture_substream); + } + } + } + } else { + if (status & CS4231_PLAYBACK_IRQ) { + if (chip->playback_substream) + snd_pcm_period_elapsed(chip->playback_substream); + } + if (status & CS4231_RECORD_IRQ) { + if (chip->capture_substream) { + snd_wss_overrange(chip); + snd_pcm_period_elapsed(chip->capture_substream); + } + } + } + + spin_lock(&chip->reg_lock); + status = ~CS4231_ALL_IRQS | ~status; + if (chip->hardware & WSS_HW_AD1848_MASK) + wss_outb(chip, CS4231P(STATUS), 0); + else + snd_wss_out(chip, CS4231_IRQ_STATUS, status); + spin_unlock(&chip->reg_lock); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(snd_wss_interrupt); + +static snd_pcm_uframes_t snd_wss_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) + return 0; + ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_wss_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)) + return 0; + ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static int snd_ad1848_probe(struct snd_wss *chip) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + unsigned long flags; + unsigned char r; + unsigned short hardware = 0; + int err = 0; + int i; + + while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) { + if (time_after(jiffies, timeout)) + return -ENODEV; + cond_resched(); + } + spin_lock_irqsave(&chip->reg_lock, flags); + + /* set CS423x MODE 1 */ + snd_wss_dout(chip, CS4231_MISC_INFO, 0); + + snd_wss_dout(chip, CS4231_RIGHT_INPUT, 0x45); /* 0x55 & ~0x10 */ + r = snd_wss_in(chip, CS4231_RIGHT_INPUT); + if (r != 0x45) { + /* RMGE always high on AD1847 */ + if ((r & ~CS4231_ENABLE_MIC_GAIN) != 0x45) { + err = -ENODEV; + goto out; + } + hardware = WSS_HW_AD1847; + } else { + snd_wss_dout(chip, CS4231_LEFT_INPUT, 0xaa); + r = snd_wss_in(chip, CS4231_LEFT_INPUT); + /* L/RMGE always low on AT2320 */ + if ((r | CS4231_ENABLE_MIC_GAIN) != 0xaa) { + err = -ENODEV; + goto out; + } + } + + /* clear pending IRQ */ + wss_inb(chip, CS4231P(STATUS)); + wss_outb(chip, CS4231P(STATUS), 0); + mb(); + + if ((chip->hardware & WSS_HW_TYPE_MASK) != WSS_HW_DETECT) + goto out; + + if (hardware) { + chip->hardware = hardware; + goto out; + } + + r = snd_wss_in(chip, CS4231_MISC_INFO); + + /* set CS423x MODE 2 */ + snd_wss_dout(chip, CS4231_MISC_INFO, CS4231_MODE2); + for (i = 0; i < 16; i++) { + if (snd_wss_in(chip, i) != snd_wss_in(chip, 16 + i)) { + /* we have more than 16 registers: check ID */ + if ((r & 0xf) != 0xa) + goto out_mode; + /* + * on CMI8330, CS4231_VERSION is volume control and + * can be set to 0 + */ + snd_wss_dout(chip, CS4231_VERSION, 0); + r = snd_wss_in(chip, CS4231_VERSION) & 0xe7; + if (!r) + chip->hardware = WSS_HW_CMI8330; + goto out_mode; + } + } + if (r & 0x80) + chip->hardware = WSS_HW_CS4248; + else + chip->hardware = WSS_HW_AD1848; +out_mode: + snd_wss_dout(chip, CS4231_MISC_INFO, 0); +out: + spin_unlock_irqrestore(&chip->reg_lock, flags); + return err; +} + +static int snd_wss_probe(struct snd_wss *chip) +{ + unsigned long flags; + int i, id, rev, regnum; + unsigned char *ptr; + unsigned int hw; + + id = snd_ad1848_probe(chip); + if (id < 0) + return id; + + hw = chip->hardware; + if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) { + for (i = 0; i < 50; i++) { + mb(); + if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + msleep(2); + else { + spin_lock_irqsave(&chip->reg_lock, flags); + snd_wss_out(chip, CS4231_MISC_INFO, + CS4231_MODE2); + id = snd_wss_in(chip, CS4231_MISC_INFO) & 0x0f; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (id == 0x0a) + break; /* this is valid value */ + } + } + snd_printdd("wss: port = 0x%lx, id = 0x%x\n", chip->port, id); + if (id != 0x0a) + return -ENODEV; /* no valid device found */ + + rev = snd_wss_in(chip, CS4231_VERSION) & 0xe7; + snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev); + if (rev == 0x80) { + unsigned char tmp = snd_wss_in(chip, 23); + snd_wss_out(chip, 23, ~tmp); + if (snd_wss_in(chip, 23) != tmp) + chip->hardware = WSS_HW_AD1845; + else + chip->hardware = WSS_HW_CS4231; + } else if (rev == 0xa0) { + chip->hardware = WSS_HW_CS4231A; + } else if (rev == 0xa2) { + chip->hardware = WSS_HW_CS4232; + } else if (rev == 0xb2) { + chip->hardware = WSS_HW_CS4232A; + } else if (rev == 0x83) { + chip->hardware = WSS_HW_CS4236; + } else if (rev == 0x03) { + chip->hardware = WSS_HW_CS4236B; + } else { + snd_printk(KERN_ERR + "unknown CS chip with version 0x%x\n", rev); + return -ENODEV; /* unknown CS4231 chip? */ + } + } + spin_lock_irqsave(&chip->reg_lock, flags); + wss_inb(chip, CS4231P(STATUS)); /* clear any pendings IRQ */ + wss_outb(chip, CS4231P(STATUS), 0); + mb(); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + if (!(chip->hardware & WSS_HW_AD1848_MASK)) + chip->image[CS4231_MISC_INFO] = CS4231_MODE2; + switch (chip->hardware) { + case WSS_HW_INTERWAVE: + chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3; + break; + case WSS_HW_CS4235: + case WSS_HW_CS4236B: + case WSS_HW_CS4237B: + case WSS_HW_CS4238B: + case WSS_HW_CS4239: + if (hw == WSS_HW_DETECT3) + chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3; + else + chip->hardware = WSS_HW_CS4236; + break; + } + + chip->image[CS4231_IFACE_CTRL] = + (chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA) | + (chip->single_dma ? CS4231_SINGLE_DMA : 0); + if (chip->hardware != WSS_HW_OPTI93X) { + chip->image[CS4231_ALT_FEATURE_1] = 0x80; + chip->image[CS4231_ALT_FEATURE_2] = + chip->hardware == WSS_HW_INTERWAVE ? 0xc2 : 0x01; + } + /* enable fine grained frequency selection */ + if (chip->hardware == WSS_HW_AD1845) + chip->image[AD1845_PWR_DOWN] = 8; + + ptr = (unsigned char *) &chip->image; + regnum = (chip->hardware & WSS_HW_AD1848_MASK) ? 16 : 32; + snd_wss_mce_down(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + for (i = 0; i < regnum; i++) /* ok.. fill all registers */ + snd_wss_out(chip, i, *ptr++); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_wss_mce_up(chip); + snd_wss_mce_down(chip); + + mdelay(2); + + /* ok.. try check hardware version for CS4236+ chips */ + if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) { + if (chip->hardware == WSS_HW_CS4236B) { + rev = snd_cs4236_ext_in(chip, CS4236_VERSION); + snd_cs4236_ext_out(chip, CS4236_VERSION, 0xff); + id = snd_cs4236_ext_in(chip, CS4236_VERSION); + snd_cs4236_ext_out(chip, CS4236_VERSION, rev); + snd_printdd("CS4231: ext version; rev = 0x%x, id = 0x%x\n", rev, id); + if ((id & 0x1f) == 0x1d) { /* CS4235 */ + chip->hardware = WSS_HW_CS4235; + switch (id >> 5) { + case 4: + case 5: + case 6: + break; + default: + snd_printk(KERN_WARNING + "unknown CS4235 chip " + "(enhanced version = 0x%x)\n", + id); + } + } else if ((id & 0x1f) == 0x0b) { /* CS4236/B */ + switch (id >> 5) { + case 4: + case 5: + case 6: + case 7: + chip->hardware = WSS_HW_CS4236B; + break; + default: + snd_printk(KERN_WARNING + "unknown CS4236 chip " + "(enhanced version = 0x%x)\n", + id); + } + } else if ((id & 0x1f) == 0x08) { /* CS4237B */ + chip->hardware = WSS_HW_CS4237B; + switch (id >> 5) { + case 4: + case 5: + case 6: + case 7: + break; + default: + snd_printk(KERN_WARNING + "unknown CS4237B chip " + "(enhanced version = 0x%x)\n", + id); + } + } else if ((id & 0x1f) == 0x09) { /* CS4238B */ + chip->hardware = WSS_HW_CS4238B; + switch (id >> 5) { + case 5: + case 6: + case 7: + break; + default: + snd_printk(KERN_WARNING + "unknown CS4238B chip " + "(enhanced version = 0x%x)\n", + id); + } + } else if ((id & 0x1f) == 0x1e) { /* CS4239 */ + chip->hardware = WSS_HW_CS4239; + switch (id >> 5) { + case 4: + case 5: + case 6: + break; + default: + snd_printk(KERN_WARNING + "unknown CS4239 chip " + "(enhanced version = 0x%x)\n", + id); + } + } else { + snd_printk(KERN_WARNING + "unknown CS4236/CS423xB chip " + "(enhanced version = 0x%x)\n", id); + } + } + } + return 0; /* all things are ok.. */ +} + +/* + + */ + +static const struct snd_pcm_hardware snd_wss_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_wss_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + + */ + +static int snd_wss_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + runtime->hw = snd_wss_playback; + + /* hardware limitation of older chipsets */ + if (chip->hardware & WSS_HW_AD1848_MASK) + runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_S16_BE); + + /* hardware bug in InterWave chipset */ + if (chip->hardware == WSS_HW_INTERWAVE && chip->dma1 > 3) + runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW; + + /* hardware limitation of cheap chips */ + if (chip->hardware == WSS_HW_CS4235 || + chip->hardware == WSS_HW_CS4239) + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE; + + snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max); + + if (chip->claim_dma) { + if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma1)) < 0) + return err; + } + + err = snd_wss_open(chip, WSS_MODE_PLAY); + if (err < 0) { + if (chip->release_dma) + chip->release_dma(chip, chip->dma_private_data, chip->dma1); + return err; + } + chip->playback_substream = substream; + snd_pcm_set_sync(substream); + chip->rate_constraint(runtime); + return 0; +} + +static int snd_wss_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + runtime->hw = snd_wss_capture; + + /* hardware limitation of older chipsets */ + if (chip->hardware & WSS_HW_AD1848_MASK) + runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_S16_BE); + + /* hardware limitation of cheap chips */ + if (chip->hardware == WSS_HW_CS4235 || + chip->hardware == WSS_HW_CS4239 || + chip->hardware == WSS_HW_OPTI93X) + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE; + + snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max); + + if (chip->claim_dma) { + if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma2)) < 0) + return err; + } + + err = snd_wss_open(chip, WSS_MODE_RECORD); + if (err < 0) { + if (chip->release_dma) + chip->release_dma(chip, chip->dma_private_data, chip->dma2); + return err; + } + chip->capture_substream = substream; + snd_pcm_set_sync(substream); + chip->rate_constraint(runtime); + return 0; +} + +static int snd_wss_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + snd_wss_close(chip, WSS_MODE_PLAY); + return 0; +} + +static int snd_wss_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_wss *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + snd_wss_close(chip, WSS_MODE_RECORD); + return 0; +} + +static void snd_wss_thinkpad_twiddle(struct snd_wss *chip, int on) +{ + int tmp; + + if (!chip->thinkpad_flag) + return; + + outb(0x1c, AD1848_THINKPAD_CTL_PORT1); + tmp = inb(AD1848_THINKPAD_CTL_PORT2); + + if (on) + /* turn it on */ + tmp |= AD1848_THINKPAD_CS4248_ENABLE_BIT; + else + /* turn it off */ + tmp &= ~AD1848_THINKPAD_CS4248_ENABLE_BIT; + + outb(tmp, AD1848_THINKPAD_CTL_PORT2); +} + +#ifdef CONFIG_PM + +/* lowlevel suspend callback for CS4231 */ +static void snd_wss_suspend(struct snd_wss *chip) +{ + int reg; + unsigned long flags; + + snd_pcm_suspend_all(chip->pcm); + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) + chip->image[reg] = snd_wss_in(chip, reg); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (chip->thinkpad_flag) + snd_wss_thinkpad_twiddle(chip, 0); +} + +/* lowlevel resume callback for CS4231 */ +static void snd_wss_resume(struct snd_wss *chip) +{ + int reg; + unsigned long flags; + /* int timeout; */ + + if (chip->thinkpad_flag) + snd_wss_thinkpad_twiddle(chip, 1); + snd_wss_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) { + switch (reg) { + case CS4231_VERSION: + break; + default: + snd_wss_out(chip, reg, chip->image[reg]); + break; + } + } + /* Yamaha needs this to resume properly */ + if (chip->hardware == WSS_HW_OPL3SA2) + snd_wss_out(chip, CS4231_PLAYBK_FORMAT, + chip->image[CS4231_PLAYBK_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); +#if 1 + snd_wss_mce_down(chip); +#else + /* The following is a workaround to avoid freeze after resume on TP600E. + This is the first half of copy of snd_wss_mce_down(), but doesn't + include rescheduling. -- iwai + */ + snd_wss_busy_wait(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->mce_bit &= ~CS4231_MCE; + timeout = wss_inb(chip, CS4231P(REGSEL)); + wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (timeout == 0x80) + snd_printk(KERN_ERR "down [0x%lx]: serious init problem " + "- codec still busy\n", chip->port); + if ((timeout & CS4231_MCE) == 0 || + !(chip->hardware & (WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK))) { + return; + } + snd_wss_busy_wait(chip); +#endif +} +#endif /* CONFIG_PM */ + +static int snd_wss_free(struct snd_wss *chip) +{ + release_and_free_resource(chip->res_port); + release_and_free_resource(chip->res_cport); + if (chip->irq >= 0) { + disable_irq(chip->irq); + if (!(chip->hwshare & WSS_HWSHARE_IRQ)) + free_irq(chip->irq, (void *) chip); + } + if (!(chip->hwshare & WSS_HWSHARE_DMA1) && chip->dma1 >= 0) { + snd_dma_disable(chip->dma1); + free_dma(chip->dma1); + } + if (!(chip->hwshare & WSS_HWSHARE_DMA2) && + chip->dma2 >= 0 && chip->dma2 != chip->dma1) { + snd_dma_disable(chip->dma2); + free_dma(chip->dma2); + } + if (chip->timer) + snd_device_free(chip->card, chip->timer); + kfree(chip); + return 0; +} + +static int snd_wss_dev_free(struct snd_device *device) +{ + struct snd_wss *chip = device->device_data; + return snd_wss_free(chip); +} + +const char *snd_wss_chip_id(struct snd_wss *chip) +{ + switch (chip->hardware) { + case WSS_HW_CS4231: + return "CS4231"; + case WSS_HW_CS4231A: + return "CS4231A"; + case WSS_HW_CS4232: + return "CS4232"; + case WSS_HW_CS4232A: + return "CS4232A"; + case WSS_HW_CS4235: + return "CS4235"; + case WSS_HW_CS4236: + return "CS4236"; + case WSS_HW_CS4236B: + return "CS4236B"; + case WSS_HW_CS4237B: + return "CS4237B"; + case WSS_HW_CS4238B: + return "CS4238B"; + case WSS_HW_CS4239: + return "CS4239"; + case WSS_HW_INTERWAVE: + return "AMD InterWave"; + case WSS_HW_OPL3SA2: + return chip->card->shortname; + case WSS_HW_AD1845: + return "AD1845"; + case WSS_HW_OPTI93X: + return "OPTi 93x"; + case WSS_HW_AD1847: + return "AD1847"; + case WSS_HW_AD1848: + return "AD1848"; + case WSS_HW_CS4248: + return "CS4248"; + case WSS_HW_CMI8330: + return "CMI8330/C3D"; + default: + return "???"; + } +} +EXPORT_SYMBOL(snd_wss_chip_id); + +static int snd_wss_new(struct snd_card *card, + unsigned short hardware, + unsigned short hwshare, + struct snd_wss **rchip) +{ + struct snd_wss *chip; + + *rchip = NULL; + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->hardware = hardware; + chip->hwshare = hwshare; + + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->mce_mutex); + mutex_init(&chip->open_mutex); + chip->card = card; + chip->rate_constraint = snd_wss_xrate; + chip->set_playback_format = snd_wss_playback_format; + chip->set_capture_format = snd_wss_capture_format; + if (chip->hardware == WSS_HW_OPTI93X) + memcpy(&chip->image, &snd_opti93x_original_image, + sizeof(snd_opti93x_original_image)); + else + memcpy(&chip->image, &snd_wss_original_image, + sizeof(snd_wss_original_image)); + if (chip->hardware & WSS_HW_AD1848_MASK) { + chip->image[CS4231_PIN_CTRL] = 0; + chip->image[CS4231_TEST_INIT] = 0; + } + + *rchip = chip; + return 0; +} + +int snd_wss_create(struct snd_card *card, + unsigned long port, + unsigned long cport, + int irq, int dma1, int dma2, + unsigned short hardware, + unsigned short hwshare, + struct snd_wss **rchip) +{ + static struct snd_device_ops ops = { + .dev_free = snd_wss_dev_free, + }; + struct snd_wss *chip; + int err; + + err = snd_wss_new(card, hardware, hwshare, &chip); + if (err < 0) + return err; + + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + + chip->res_port = request_region(port, 4, "WSS"); + if (!chip->res_port) { + snd_printk(KERN_ERR "wss: can't grab port 0x%lx\n", port); + snd_wss_free(chip); + return -EBUSY; + } + chip->port = port; + if ((long)cport >= 0) { + chip->res_cport = request_region(cport, 8, "CS4232 Control"); + if (!chip->res_cport) { + snd_printk(KERN_ERR + "wss: can't grab control port 0x%lx\n", cport); + snd_wss_free(chip); + return -ENODEV; + } + } + chip->cport = cport; + if (!(hwshare & WSS_HWSHARE_IRQ)) + if (request_irq(irq, snd_wss_interrupt, 0, + "WSS", (void *) chip)) { + snd_printk(KERN_ERR "wss: can't grab IRQ %d\n", irq); + snd_wss_free(chip); + return -EBUSY; + } + chip->irq = irq; + if (!(hwshare & WSS_HWSHARE_DMA1) && request_dma(dma1, "WSS - 1")) { + snd_printk(KERN_ERR "wss: can't grab DMA1 %d\n", dma1); + snd_wss_free(chip); + return -EBUSY; + } + chip->dma1 = dma1; + if (!(hwshare & WSS_HWSHARE_DMA2) && dma1 != dma2 && + dma2 >= 0 && request_dma(dma2, "WSS - 2")) { + snd_printk(KERN_ERR "wss: can't grab DMA2 %d\n", dma2); + snd_wss_free(chip); + return -EBUSY; + } + if (dma1 == dma2 || dma2 < 0) { + chip->single_dma = 1; + chip->dma2 = chip->dma1; + } else + chip->dma2 = dma2; + + if (hardware == WSS_HW_THINKPAD) { + chip->thinkpad_flag = 1; + chip->hardware = WSS_HW_DETECT; /* reset */ + snd_wss_thinkpad_twiddle(chip, 1); + } + + /* global setup */ + if (snd_wss_probe(chip) < 0) { + snd_wss_free(chip); + return -ENODEV; + } + snd_wss_init(chip); + +#if 0 + if (chip->hardware & WSS_HW_CS4232_MASK) { + if (chip->res_cport == NULL) + snd_printk(KERN_ERR "CS4232 control port features are " + "not accessible\n"); + } +#endif + + /* Register device */ + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + snd_wss_free(chip); + return err; + } + +#ifdef CONFIG_PM + /* Power Management */ + chip->suspend = snd_wss_suspend; + chip->resume = snd_wss_resume; +#endif + + *rchip = chip; + return 0; +} +EXPORT_SYMBOL(snd_wss_create); + +static const struct snd_pcm_ops snd_wss_playback_ops = { + .open = snd_wss_playback_open, + .close = snd_wss_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_wss_playback_hw_params, + .hw_free = snd_wss_playback_hw_free, + .prepare = snd_wss_playback_prepare, + .trigger = snd_wss_trigger, + .pointer = snd_wss_playback_pointer, +}; + +static const struct snd_pcm_ops snd_wss_capture_ops = { + .open = snd_wss_capture_open, + .close = snd_wss_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_wss_capture_hw_params, + .hw_free = snd_wss_capture_hw_free, + .prepare = snd_wss_capture_prepare, + .trigger = snd_wss_trigger, + .pointer = snd_wss_capture_pointer, +}; + +int snd_wss_pcm(struct snd_wss *chip, int device) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "WSS", device, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_wss_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_wss_capture_ops); + + /* global setup */ + pcm->private_data = chip; + pcm->info_flags = 0; + if (chip->single_dma) + pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX; + if (chip->hardware != WSS_HW_INTERWAVE) + pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX; + strcpy(pcm->name, snd_wss_chip_id(chip)); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024); + + chip->pcm = pcm; + return 0; +} +EXPORT_SYMBOL(snd_wss_pcm); + +static void snd_wss_timer_free(struct snd_timer *timer) +{ + struct snd_wss *chip = timer->private_data; + chip->timer = NULL; +} + +int snd_wss_timer(struct snd_wss *chip, int device) +{ + struct snd_timer *timer; + struct snd_timer_id tid; + int err; + + /* Timer initialization */ + tid.dev_class = SNDRV_TIMER_CLASS_CARD; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = chip->card->number; + tid.device = device; + tid.subdevice = 0; + if ((err = snd_timer_new(chip->card, "CS4231", &tid, &timer)) < 0) + return err; + strcpy(timer->name, snd_wss_chip_id(chip)); + timer->private_data = chip; + timer->private_free = snd_wss_timer_free; + timer->hw = snd_wss_timer_table; + chip->timer = timer; + return 0; +} +EXPORT_SYMBOL(snd_wss_timer); + +/* + * MIXER part + */ + +static int snd_wss_info_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "Line", "Aux", "Mic", "Mix" + }; + static const char * const opl3sa_texts[4] = { + "Line", "CD", "Mic", "Mix" + }; + static const char * const gusmax_texts[4] = { + "Line", "Synth", "Mic", "Mix" + }; + const char * const *ptexts = texts; + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + + if (snd_BUG_ON(!chip->card)) + return -EINVAL; + if (!strcmp(chip->card->driver, "GUS MAX")) + ptexts = gusmax_texts; + switch (chip->hardware) { + case WSS_HW_INTERWAVE: + ptexts = gusmax_texts; + break; + case WSS_HW_OPTI93X: + case WSS_HW_OPL3SA2: + ptexts = opl3sa_texts; + break; + } + return snd_ctl_enum_info(uinfo, 2, 4, ptexts); +} + +static int snd_wss_get_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.enumerated.item[0] = (chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6; + ucontrol->value.enumerated.item[1] = (chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6; + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_wss_put_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned short left, right; + int change; + + if (ucontrol->value.enumerated.item[0] > 3 || + ucontrol->value.enumerated.item[1] > 3) + return -EINVAL; + left = ucontrol->value.enumerated.item[0] << 6; + right = ucontrol->value.enumerated.item[1] << 6; + spin_lock_irqsave(&chip->reg_lock, flags); + left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left; + right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right; + change = left != chip->image[CS4231_LEFT_INPUT] || + right != chip->image[CS4231_RIGHT_INPUT]; + snd_wss_out(chip, CS4231_LEFT_INPUT, left); + snd_wss_out(chip, CS4231_RIGHT_INPUT, right); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +int snd_wss_info_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} +EXPORT_SYMBOL(snd_wss_info_single); + +int snd_wss_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} +EXPORT_SYMBOL(snd_wss_get_single); + +int snd_wss_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->image[reg] & ~(mask << shift)) | val; + change = val != chip->image[reg]; + snd_wss_out(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_wss_put_single); + +int snd_wss_info_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} +EXPORT_SYMBOL(snd_wss_info_double); + +int snd_wss_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} +EXPORT_SYMBOL(snd_wss_get_double); + +int snd_wss_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wss *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + if (left_reg != right_reg) { + val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1; + val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2; + change = val1 != chip->image[left_reg] || + val2 != chip->image[right_reg]; + snd_wss_out(chip, left_reg, val1); + snd_wss_out(chip, right_reg, val2); + } else { + mask = (mask << shift_left) | (mask << shift_right); + val1 = (chip->image[left_reg] & ~mask) | val1 | val2; + change = val1 != chip->image[left_reg]; + snd_wss_out(chip, left_reg, val1); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} +EXPORT_SYMBOL(snd_wss_put_double); + +static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_4bit, -4500, 300, 0); + +static struct snd_kcontrol_new snd_wss_controls[] = { +WSS_DOUBLE("PCM Playback Switch", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("PCM Playback Volume", 0, + CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1, + db_scale_6bit), +WSS_DOUBLE("Aux Playback Switch", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Aux Playback Volume", 0, + CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1, + db_scale_5bit_12db_max), +WSS_DOUBLE("Aux Playback Switch", 1, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Aux Playback Volume", 1, + CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1, + db_scale_5bit_12db_max), +WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, + 0, 0, 15, 0, db_scale_rec_gain), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_wss_info_mux, + .get = snd_wss_get_mux, + .put = snd_wss_put_mux, +}, +WSS_DOUBLE("Mic Boost (+20dB)", 0, + CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0), +WSS_SINGLE("Loopback Capture Switch", 0, + CS4231_LOOPBACK, 0, 1, 0), +WSS_SINGLE_TLV("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1, + db_scale_6bit), +WSS_DOUBLE("Line Playback Switch", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +WSS_DOUBLE_TLV("Line Playback Volume", 0, + CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1, + db_scale_5bit_12db_max), +WSS_SINGLE("Beep Playback Switch", 0, + CS4231_MONO_CTRL, 7, 1, 1), +WSS_SINGLE_TLV("Beep Playback Volume", 0, + CS4231_MONO_CTRL, 0, 15, 1, + db_scale_4bit), +WSS_SINGLE("Mono Output Playback Switch", 0, + CS4231_MONO_CTRL, 6, 1, 1), +WSS_SINGLE("Beep Bypass Playback Switch", 0, + CS4231_MONO_CTRL, 5, 1, 0), +}; + +int snd_wss_mixer(struct snd_wss *chip) +{ + struct snd_card *card; + unsigned int idx; + int err; + int count = ARRAY_SIZE(snd_wss_controls); + + if (snd_BUG_ON(!chip || !chip->pcm)) + return -EINVAL; + + card = chip->card; + + strcpy(card->mixername, chip->pcm->name); + + /* Use only the first 11 entries on AD1848 */ + if (chip->hardware & WSS_HW_AD1848_MASK) + count = 11; + /* There is no loopback on OPTI93X */ + else if (chip->hardware == WSS_HW_OPTI93X) + count = 9; + + for (idx = 0; idx < count; idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_wss_controls[idx], + chip)); + if (err < 0) + return err; + } + return 0; +} +EXPORT_SYMBOL(snd_wss_mixer); + +const struct snd_pcm_ops *snd_wss_get_pcm_ops(int direction) +{ + return direction == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_wss_playback_ops : &snd_wss_capture_ops; +} +EXPORT_SYMBOL(snd_wss_get_pcm_ops); diff --git a/sound/last.c b/sound/last.c new file mode 100644 index 000000000..43f222825 --- /dev/null +++ b/sound/last.c @@ -0,0 +1,40 @@ +/* + * Advanced Linux Sound Architecture + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include + +static int __init alsa_sound_last_init(void) +{ + int idx, ok = 0; + + printk(KERN_INFO "ALSA device list:\n"); + for (idx = 0; idx < SNDRV_CARDS; idx++) + if (snd_cards[idx] != NULL) { + printk(KERN_INFO " #%i: %s\n", idx, snd_cards[idx]->longname); + ok++; + } + if (ok == 0) + printk(KERN_INFO " No soundcards found.\n"); + return 0; +} + +late_initcall_sync(alsa_sound_last_init); diff --git a/sound/mips/Kconfig b/sound/mips/Kconfig new file mode 100644 index 000000000..4a4705031 --- /dev/null +++ b/sound/mips/Kconfig @@ -0,0 +1,27 @@ +# ALSA MIPS drivers + +menuconfig SND_MIPS + bool "MIPS sound devices" + depends on MIPS + default y + help + Support for sound devices of MIPS architectures. + +if SND_MIPS + +config SND_SGI_O2 + tristate "SGI O2 Audio" + depends on SGI_IP32 + select SND_PCM + help + Sound support for the SGI O2 Workstation. + +config SND_SGI_HAL2 + tristate "SGI HAL2 Audio" + depends on SGI_HAS_HAL2 + select SND_PCM + help + Sound support for the SGI Indy and Indigo2 Workstation. + +endif # SND_MIPS + diff --git a/sound/mips/Makefile b/sound/mips/Makefile new file mode 100644 index 000000000..b977c4433 --- /dev/null +++ b/sound/mips/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# + +snd-sgi-o2-objs := sgio2audio.o ad1843.o +snd-sgi-hal2-objs := hal2.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_SGI_O2) += snd-sgi-o2.o +obj-$(CONFIG_SND_SGI_HAL2) += snd-sgi-hal2.o diff --git a/sound/mips/ad1843.c b/sound/mips/ad1843.c new file mode 100644 index 000000000..586907500 --- /dev/null +++ b/sound/mips/ad1843.c @@ -0,0 +1,561 @@ +/* + * AD1843 low level driver + * + * Copyright 2003 Vivien Chappelier + * Copyright 2008 Thomas Bogendoerfer + * + * inspired from vwsnd.c (SGI VW audio driver) + * Copyright 1999 Silicon Graphics, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +/* + * AD1843 bitfield definitions. All are named as in the AD1843 data + * sheet, with ad1843_ prepended and individual bit numbers removed. + * + * E.g., bits LSS0 through LSS2 become ad1843_LSS. + * + * Only the bitfields we need are defined. + */ + +struct ad1843_bitfield { + char reg; + char lo_bit; + char nbits; +}; + +static const struct ad1843_bitfield + ad1843_PDNO = { 0, 14, 1 }, /* Converter Power-Down Flag */ + ad1843_INIT = { 0, 15, 1 }, /* Clock Initialization Flag */ + ad1843_RIG = { 2, 0, 4 }, /* Right ADC Input Gain */ + ad1843_RMGE = { 2, 4, 1 }, /* Right ADC Mic Gain Enable */ + ad1843_RSS = { 2, 5, 3 }, /* Right ADC Source Select */ + ad1843_LIG = { 2, 8, 4 }, /* Left ADC Input Gain */ + ad1843_LMGE = { 2, 12, 1 }, /* Left ADC Mic Gain Enable */ + ad1843_LSS = { 2, 13, 3 }, /* Left ADC Source Select */ + ad1843_RD2M = { 3, 0, 5 }, /* Right DAC 2 Mix Gain/Atten */ + ad1843_RD2MM = { 3, 7, 1 }, /* Right DAC 2 Mix Mute */ + ad1843_LD2M = { 3, 8, 5 }, /* Left DAC 2 Mix Gain/Atten */ + ad1843_LD2MM = { 3, 15, 1 }, /* Left DAC 2 Mix Mute */ + ad1843_RX1M = { 4, 0, 5 }, /* Right Aux 1 Mix Gain/Atten */ + ad1843_RX1MM = { 4, 7, 1 }, /* Right Aux 1 Mix Mute */ + ad1843_LX1M = { 4, 8, 5 }, /* Left Aux 1 Mix Gain/Atten */ + ad1843_LX1MM = { 4, 15, 1 }, /* Left Aux 1 Mix Mute */ + ad1843_RX2M = { 5, 0, 5 }, /* Right Aux 2 Mix Gain/Atten */ + ad1843_RX2MM = { 5, 7, 1 }, /* Right Aux 2 Mix Mute */ + ad1843_LX2M = { 5, 8, 5 }, /* Left Aux 2 Mix Gain/Atten */ + ad1843_LX2MM = { 5, 15, 1 }, /* Left Aux 2 Mix Mute */ + ad1843_RMCM = { 7, 0, 5 }, /* Right Mic Mix Gain/Atten */ + ad1843_RMCMM = { 7, 7, 1 }, /* Right Mic Mix Mute */ + ad1843_LMCM = { 7, 8, 5 }, /* Left Mic Mix Gain/Atten */ + ad1843_LMCMM = { 7, 15, 1 }, /* Left Mic Mix Mute */ + ad1843_HPOS = { 8, 4, 1 }, /* Headphone Output Voltage Swing */ + ad1843_HPOM = { 8, 5, 1 }, /* Headphone Output Mute */ + ad1843_MPOM = { 8, 6, 1 }, /* Mono Output Mute */ + ad1843_RDA1G = { 9, 0, 6 }, /* Right DAC1 Analog/Digital Gain */ + ad1843_RDA1GM = { 9, 7, 1 }, /* Right DAC1 Analog Mute */ + ad1843_LDA1G = { 9, 8, 6 }, /* Left DAC1 Analog/Digital Gain */ + ad1843_LDA1GM = { 9, 15, 1 }, /* Left DAC1 Analog Mute */ + ad1843_RDA2G = { 10, 0, 6 }, /* Right DAC2 Analog/Digital Gain */ + ad1843_RDA2GM = { 10, 7, 1 }, /* Right DAC2 Analog Mute */ + ad1843_LDA2G = { 10, 8, 6 }, /* Left DAC2 Analog/Digital Gain */ + ad1843_LDA2GM = { 10, 15, 1 }, /* Left DAC2 Analog Mute */ + ad1843_RDA1AM = { 11, 7, 1 }, /* Right DAC1 Digital Mute */ + ad1843_LDA1AM = { 11, 15, 1 }, /* Left DAC1 Digital Mute */ + ad1843_RDA2AM = { 12, 7, 1 }, /* Right DAC2 Digital Mute */ + ad1843_LDA2AM = { 12, 15, 1 }, /* Left DAC2 Digital Mute */ + ad1843_ADLC = { 15, 0, 2 }, /* ADC Left Sample Rate Source */ + ad1843_ADRC = { 15, 2, 2 }, /* ADC Right Sample Rate Source */ + ad1843_DA1C = { 15, 8, 2 }, /* DAC1 Sample Rate Source */ + ad1843_DA2C = { 15, 10, 2 }, /* DAC2 Sample Rate Source */ + ad1843_C1C = { 17, 0, 16 }, /* Clock 1 Sample Rate Select */ + ad1843_C2C = { 20, 0, 16 }, /* Clock 2 Sample Rate Select */ + ad1843_C3C = { 23, 0, 16 }, /* Clock 3 Sample Rate Select */ + ad1843_DAADL = { 25, 4, 2 }, /* Digital ADC Left Source Select */ + ad1843_DAADR = { 25, 6, 2 }, /* Digital ADC Right Source Select */ + ad1843_DAMIX = { 25, 14, 1 }, /* DAC Digital Mix Enable */ + ad1843_DRSFLT = { 25, 15, 1 }, /* Digital Reampler Filter Mode */ + ad1843_ADLF = { 26, 0, 2 }, /* ADC Left Channel Data Format */ + ad1843_ADRF = { 26, 2, 2 }, /* ADC Right Channel Data Format */ + ad1843_ADTLK = { 26, 4, 1 }, /* ADC Transmit Lock Mode Select */ + ad1843_SCF = { 26, 7, 1 }, /* SCLK Frequency Select */ + ad1843_DA1F = { 26, 8, 2 }, /* DAC1 Data Format Select */ + ad1843_DA2F = { 26, 10, 2 }, /* DAC2 Data Format Select */ + ad1843_DA1SM = { 26, 14, 1 }, /* DAC1 Stereo/Mono Mode Select */ + ad1843_DA2SM = { 26, 15, 1 }, /* DAC2 Stereo/Mono Mode Select */ + ad1843_ADLEN = { 27, 0, 1 }, /* ADC Left Channel Enable */ + ad1843_ADREN = { 27, 1, 1 }, /* ADC Right Channel Enable */ + ad1843_AAMEN = { 27, 4, 1 }, /* Analog to Analog Mix Enable */ + ad1843_ANAEN = { 27, 7, 1 }, /* Analog Channel Enable */ + ad1843_DA1EN = { 27, 8, 1 }, /* DAC1 Enable */ + ad1843_DA2EN = { 27, 9, 1 }, /* DAC2 Enable */ + ad1843_DDMEN = { 27, 12, 1 }, /* DAC2 to DAC1 Mix Enable */ + ad1843_C1EN = { 28, 11, 1 }, /* Clock Generator 1 Enable */ + ad1843_C2EN = { 28, 12, 1 }, /* Clock Generator 2 Enable */ + ad1843_C3EN = { 28, 13, 1 }, /* Clock Generator 3 Enable */ + ad1843_PDNI = { 28, 15, 1 }; /* Converter Power Down */ + +/* + * The various registers of the AD1843 use three different formats for + * specifying gain. The ad1843_gain structure parameterizes the + * formats. + */ + +struct ad1843_gain { + int negative; /* nonzero if gain is negative. */ + const struct ad1843_bitfield *lfield; + const struct ad1843_bitfield *rfield; + const struct ad1843_bitfield *lmute; + const struct ad1843_bitfield *rmute; +}; + +static const struct ad1843_gain ad1843_gain_RECLEV = { + .negative = 0, + .lfield = &ad1843_LIG, + .rfield = &ad1843_RIG +}; +static const struct ad1843_gain ad1843_gain_LINE = { + .negative = 1, + .lfield = &ad1843_LX1M, + .rfield = &ad1843_RX1M, + .lmute = &ad1843_LX1MM, + .rmute = &ad1843_RX1MM +}; +static const struct ad1843_gain ad1843_gain_LINE_2 = { + .negative = 1, + .lfield = &ad1843_LDA2G, + .rfield = &ad1843_RDA2G, + .lmute = &ad1843_LDA2GM, + .rmute = &ad1843_RDA2GM +}; +static const struct ad1843_gain ad1843_gain_MIC = { + .negative = 1, + .lfield = &ad1843_LMCM, + .rfield = &ad1843_RMCM, + .lmute = &ad1843_LMCMM, + .rmute = &ad1843_RMCMM +}; +static const struct ad1843_gain ad1843_gain_PCM_0 = { + .negative = 1, + .lfield = &ad1843_LDA1G, + .rfield = &ad1843_RDA1G, + .lmute = &ad1843_LDA1GM, + .rmute = &ad1843_RDA1GM +}; +static const struct ad1843_gain ad1843_gain_PCM_1 = { + .negative = 1, + .lfield = &ad1843_LD2M, + .rfield = &ad1843_RD2M, + .lmute = &ad1843_LD2MM, + .rmute = &ad1843_RD2MM +}; + +static const struct ad1843_gain *ad1843_gain[AD1843_GAIN_SIZE] = +{ + &ad1843_gain_RECLEV, + &ad1843_gain_LINE, + &ad1843_gain_LINE_2, + &ad1843_gain_MIC, + &ad1843_gain_PCM_0, + &ad1843_gain_PCM_1, +}; + +/* read the current value of an AD1843 bitfield. */ + +static int ad1843_read_bits(struct snd_ad1843 *ad1843, + const struct ad1843_bitfield *field) +{ + int w; + + w = ad1843->read(ad1843->chip, field->reg); + return w >> field->lo_bit & ((1 << field->nbits) - 1); +} + +/* + * write a new value to an AD1843 bitfield and return the old value. + */ + +static int ad1843_write_bits(struct snd_ad1843 *ad1843, + const struct ad1843_bitfield *field, + int newval) +{ + int w, mask, oldval, newbits; + + w = ad1843->read(ad1843->chip, field->reg); + mask = ((1 << field->nbits) - 1) << field->lo_bit; + oldval = (w & mask) >> field->lo_bit; + newbits = (newval << field->lo_bit) & mask; + w = (w & ~mask) | newbits; + ad1843->write(ad1843->chip, field->reg, w); + + return oldval; +} + +/* + * ad1843_read_multi reads multiple bitfields from the same AD1843 + * register. It uses a single read cycle to do it. (Reading the + * ad1843 requires 256 bit times at 12.288 MHz, or nearly 20 + * microseconds.) + * + * Called like this. + * + * ad1843_read_multi(ad1843, nfields, + * &ad1843_FIELD1, &val1, + * &ad1843_FIELD2, &val2, ...); + */ + +static void ad1843_read_multi(struct snd_ad1843 *ad1843, int argcount, ...) +{ + va_list ap; + const struct ad1843_bitfield *fp; + int w = 0, mask, *value, reg = -1; + + va_start(ap, argcount); + while (--argcount >= 0) { + fp = va_arg(ap, const struct ad1843_bitfield *); + value = va_arg(ap, int *); + if (reg == -1) { + reg = fp->reg; + w = ad1843->read(ad1843->chip, reg); + } + + mask = (1 << fp->nbits) - 1; + *value = w >> fp->lo_bit & mask; + } + va_end(ap); +} + +/* + * ad1843_write_multi stores multiple bitfields into the same AD1843 + * register. It uses one read and one write cycle to do it. + * + * Called like this. + * + * ad1843_write_multi(ad1843, nfields, + * &ad1843_FIELD1, val1, + * &ad1843_FIELF2, val2, ...); + */ + +static void ad1843_write_multi(struct snd_ad1843 *ad1843, int argcount, ...) +{ + va_list ap; + int reg; + const struct ad1843_bitfield *fp; + int value; + int w, m, mask, bits; + + mask = 0; + bits = 0; + reg = -1; + + va_start(ap, argcount); + while (--argcount >= 0) { + fp = va_arg(ap, const struct ad1843_bitfield *); + value = va_arg(ap, int); + if (reg == -1) + reg = fp->reg; + else + WARN_ON(reg != fp->reg); + m = ((1 << fp->nbits) - 1) << fp->lo_bit; + mask |= m; + bits |= (value << fp->lo_bit) & m; + } + va_end(ap); + + if (~mask & 0xFFFF) + w = ad1843->read(ad1843->chip, reg); + else + w = 0; + w = (w & ~mask) | bits; + ad1843->write(ad1843->chip, reg, w); +} + +int ad1843_get_gain_max(struct snd_ad1843 *ad1843, int id) +{ + const struct ad1843_gain *gp = ad1843_gain[id]; + int ret; + + ret = (1 << gp->lfield->nbits); + if (!gp->lmute) + ret -= 1; + return ret; +} + +/* + * ad1843_get_gain reads the specified register and extracts the gain value + * using the supplied gain type. + */ + +int ad1843_get_gain(struct snd_ad1843 *ad1843, int id) +{ + int lg, rg, lm, rm; + const struct ad1843_gain *gp = ad1843_gain[id]; + unsigned short mask = (1 << gp->lfield->nbits) - 1; + + ad1843_read_multi(ad1843, 2, gp->lfield, &lg, gp->rfield, &rg); + if (gp->negative) { + lg = mask - lg; + rg = mask - rg; + } + if (gp->lmute) { + ad1843_read_multi(ad1843, 2, gp->lmute, &lm, gp->rmute, &rm); + if (lm) + lg = 0; + if (rm) + rg = 0; + } + return lg << 0 | rg << 8; +} + +/* + * Set an audio channel's gain. + * + * Returns the new gain, which may be lower than the old gain. + */ + +int ad1843_set_gain(struct snd_ad1843 *ad1843, int id, int newval) +{ + const struct ad1843_gain *gp = ad1843_gain[id]; + unsigned short mask = (1 << gp->lfield->nbits) - 1; + + int lg = (newval >> 0) & mask; + int rg = (newval >> 8) & mask; + int lm = (lg == 0) ? 1 : 0; + int rm = (rg == 0) ? 1 : 0; + + if (gp->negative) { + lg = mask - lg; + rg = mask - rg; + } + if (gp->lmute) + ad1843_write_multi(ad1843, 2, gp->lmute, lm, gp->rmute, rm); + ad1843_write_multi(ad1843, 2, gp->lfield, lg, gp->rfield, rg); + return ad1843_get_gain(ad1843, id); +} + +/* Returns the current recording source */ + +int ad1843_get_recsrc(struct snd_ad1843 *ad1843) +{ + int val = ad1843_read_bits(ad1843, &ad1843_LSS); + + if (val < 0 || val > 2) { + val = 2; + ad1843_write_multi(ad1843, 2, + &ad1843_LSS, val, &ad1843_RSS, val); + } + return val; +} + +/* + * Set recording source. + * + * Returns newsrc on success, -errno on failure. + */ + +int ad1843_set_recsrc(struct snd_ad1843 *ad1843, int newsrc) +{ + if (newsrc < 0 || newsrc > 2) + return -EINVAL; + + ad1843_write_multi(ad1843, 2, &ad1843_LSS, newsrc, &ad1843_RSS, newsrc); + return newsrc; +} + +/* Setup ad1843 for D/A conversion. */ + +void ad1843_setup_dac(struct snd_ad1843 *ad1843, + unsigned int id, + unsigned int framerate, + snd_pcm_format_t fmt, + unsigned int channels) +{ + int ad_fmt = 0, ad_mode = 0; + + switch (fmt) { + case SNDRV_PCM_FORMAT_S8: + ad_fmt = 0; + break; + case SNDRV_PCM_FORMAT_U8: + ad_fmt = 0; + break; + case SNDRV_PCM_FORMAT_S16_LE: + ad_fmt = 1; + break; + case SNDRV_PCM_FORMAT_MU_LAW: + ad_fmt = 2; + break; + case SNDRV_PCM_FORMAT_A_LAW: + ad_fmt = 3; + break; + default: + break; + } + + switch (channels) { + case 2: + ad_mode = 0; + break; + case 1: + ad_mode = 1; + break; + default: + break; + } + + if (id) { + ad1843_write_bits(ad1843, &ad1843_C2C, framerate); + ad1843_write_multi(ad1843, 2, + &ad1843_DA2SM, ad_mode, + &ad1843_DA2F, ad_fmt); + } else { + ad1843_write_bits(ad1843, &ad1843_C1C, framerate); + ad1843_write_multi(ad1843, 2, + &ad1843_DA1SM, ad_mode, + &ad1843_DA1F, ad_fmt); + } +} + +void ad1843_shutdown_dac(struct snd_ad1843 *ad1843, unsigned int id) +{ + if (id) + ad1843_write_bits(ad1843, &ad1843_DA2F, 1); + else + ad1843_write_bits(ad1843, &ad1843_DA1F, 1); +} + +void ad1843_setup_adc(struct snd_ad1843 *ad1843, + unsigned int framerate, + snd_pcm_format_t fmt, + unsigned int channels) +{ + int da_fmt = 0; + + switch (fmt) { + case SNDRV_PCM_FORMAT_S8: da_fmt = 0; break; + case SNDRV_PCM_FORMAT_U8: da_fmt = 0; break; + case SNDRV_PCM_FORMAT_S16_LE: da_fmt = 1; break; + case SNDRV_PCM_FORMAT_MU_LAW: da_fmt = 2; break; + case SNDRV_PCM_FORMAT_A_LAW: da_fmt = 3; break; + default: break; + } + + ad1843_write_bits(ad1843, &ad1843_C3C, framerate); + ad1843_write_multi(ad1843, 2, + &ad1843_ADLF, da_fmt, &ad1843_ADRF, da_fmt); +} + +void ad1843_shutdown_adc(struct snd_ad1843 *ad1843) +{ + /* nothing to do */ +} + +/* + * Fully initialize the ad1843. As described in the AD1843 data + * sheet, section "START-UP SEQUENCE". The numbered comments are + * subsection headings from the data sheet. See the data sheet, pages + * 52-54, for more info. + * + * return 0 on success, -errno on failure. */ + +int ad1843_init(struct snd_ad1843 *ad1843) +{ + unsigned long later; + + if (ad1843_read_bits(ad1843, &ad1843_INIT) != 0) { + printk(KERN_ERR "ad1843: AD1843 won't initialize\n"); + return -EIO; + } + + ad1843_write_bits(ad1843, &ad1843_SCF, 1); + + /* 4. Put the conversion resources into standby. */ + ad1843_write_bits(ad1843, &ad1843_PDNI, 0); + later = jiffies + msecs_to_jiffies(500); + + while (ad1843_read_bits(ad1843, &ad1843_PDNO)) { + if (time_after(jiffies, later)) { + printk(KERN_ERR + "ad1843: AD1843 won't power up\n"); + return -EIO; + } + schedule_timeout_interruptible(5); + } + + /* 5. Power up the clock generators and enable clock output pins. */ + ad1843_write_multi(ad1843, 3, + &ad1843_C1EN, 1, + &ad1843_C2EN, 1, + &ad1843_C3EN, 1); + + /* 6. Configure conversion resources while they are in standby. */ + + /* DAC1/2 use clock 1/2 as source, ADC uses clock 3. Always. */ + ad1843_write_multi(ad1843, 4, + &ad1843_DA1C, 1, + &ad1843_DA2C, 2, + &ad1843_ADLC, 3, + &ad1843_ADRC, 3); + + /* 7. Enable conversion resources. */ + ad1843_write_bits(ad1843, &ad1843_ADTLK, 1); + ad1843_write_multi(ad1843, 7, + &ad1843_ANAEN, 1, + &ad1843_AAMEN, 1, + &ad1843_DA1EN, 1, + &ad1843_DA2EN, 1, + &ad1843_DDMEN, 1, + &ad1843_ADLEN, 1, + &ad1843_ADREN, 1); + + /* 8. Configure conversion resources while they are enabled. */ + + /* set gain to 0 for all channels */ + ad1843_set_gain(ad1843, AD1843_GAIN_RECLEV, 0); + ad1843_set_gain(ad1843, AD1843_GAIN_LINE, 0); + ad1843_set_gain(ad1843, AD1843_GAIN_LINE_2, 0); + ad1843_set_gain(ad1843, AD1843_GAIN_MIC, 0); + ad1843_set_gain(ad1843, AD1843_GAIN_PCM_0, 0); + ad1843_set_gain(ad1843, AD1843_GAIN_PCM_1, 0); + + /* Unmute all channels. */ + /* DAC1 */ + ad1843_write_multi(ad1843, 2, &ad1843_LDA1GM, 0, &ad1843_RDA1GM, 0); + /* DAC2 */ + ad1843_write_multi(ad1843, 2, &ad1843_LDA2GM, 0, &ad1843_RDA2GM, 0); + + /* Set default recording source to Line In and set + * mic gain to +20 dB. + */ + ad1843_set_recsrc(ad1843, 2); + ad1843_write_multi(ad1843, 2, &ad1843_LMGE, 1, &ad1843_RMGE, 1); + + /* Set Speaker Out level to +/- 4V and unmute it. */ + ad1843_write_multi(ad1843, 3, + &ad1843_HPOS, 1, + &ad1843_HPOM, 0, + &ad1843_MPOM, 0); + + return 0; +} diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c new file mode 100644 index 000000000..c8904e732 --- /dev/null +++ b/sound/mips/hal2.c @@ -0,0 +1,937 @@ +/* + * Driver for A2 audio system used in SGI machines + * Copyright (c) 2008 Thomas Bogendoerfer + * + * Based on OSS code from Ladislav Michl , which + * was based on code from Ulf Carlsson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "hal2.h" + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for SGI HAL2 soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for SGI HAL2 soundcard."); +MODULE_DESCRIPTION("ALSA driver for SGI HAL2 audio"); +MODULE_AUTHOR("Thomas Bogendoerfer"); +MODULE_LICENSE("GPL"); + + +#define H2_BLOCK_SIZE 1024 +#define H2_BUF_SIZE 16384 + +struct hal2_pbus { + struct hpc3_pbus_dmacregs *pbus; + int pbusnr; + unsigned int ctrl; /* Current state of pbus->pbdma_ctrl */ +}; + +struct hal2_desc { + struct hpc_dma_desc desc; + u32 pad; /* padding */ +}; + +struct hal2_codec { + struct snd_pcm_indirect pcm_indirect; + struct snd_pcm_substream *substream; + + unsigned char *buffer; + dma_addr_t buffer_dma; + struct hal2_desc *desc; + dma_addr_t desc_dma; + int desc_count; + struct hal2_pbus pbus; + int voices; /* mono/stereo */ + unsigned int sample_rate; + unsigned int master; /* Master frequency */ + unsigned short mod; /* MOD value */ + unsigned short inc; /* INC value */ +}; + +#define H2_MIX_OUTPUT_ATT 0 +#define H2_MIX_INPUT_GAIN 1 + +struct snd_hal2 { + struct snd_card *card; + + struct hal2_ctl_regs *ctl_regs; /* HAL2 ctl registers */ + struct hal2_aes_regs *aes_regs; /* HAL2 aes registers */ + struct hal2_vol_regs *vol_regs; /* HAL2 vol registers */ + struct hal2_syn_regs *syn_regs; /* HAL2 syn registers */ + + struct hal2_codec dac; + struct hal2_codec adc; +}; + +#define H2_INDIRECT_WAIT(regs) while (hal2_read(®s->isr) & H2_ISR_TSTATUS); + +#define H2_READ_ADDR(addr) (addr | (1<<7)) +#define H2_WRITE_ADDR(addr) (addr) + +static inline u32 hal2_read(u32 *reg) +{ + return __raw_readl(reg); +} + +static inline void hal2_write(u32 val, u32 *reg) +{ + __raw_writel(val, reg); +} + + +static u32 hal2_i_read32(struct snd_hal2 *hal2, u16 addr) +{ + u32 ret; + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + hal2_write(H2_READ_ADDR(addr), ®s->iar); + H2_INDIRECT_WAIT(regs); + ret = hal2_read(®s->idr0) & 0xffff; + hal2_write(H2_READ_ADDR(addr) | 0x1, ®s->iar); + H2_INDIRECT_WAIT(regs); + ret |= (hal2_read(®s->idr0) & 0xffff) << 16; + return ret; +} + +static void hal2_i_write16(struct snd_hal2 *hal2, u16 addr, u16 val) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + hal2_write(val, ®s->idr0); + hal2_write(0, ®s->idr1); + hal2_write(0, ®s->idr2); + hal2_write(0, ®s->idr3); + hal2_write(H2_WRITE_ADDR(addr), ®s->iar); + H2_INDIRECT_WAIT(regs); +} + +static void hal2_i_write32(struct snd_hal2 *hal2, u16 addr, u32 val) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + hal2_write(val & 0xffff, ®s->idr0); + hal2_write(val >> 16, ®s->idr1); + hal2_write(0, ®s->idr2); + hal2_write(0, ®s->idr3); + hal2_write(H2_WRITE_ADDR(addr), ®s->iar); + H2_INDIRECT_WAIT(regs); +} + +static void hal2_i_setbit16(struct snd_hal2 *hal2, u16 addr, u16 bit) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + hal2_write(H2_READ_ADDR(addr), ®s->iar); + H2_INDIRECT_WAIT(regs); + hal2_write((hal2_read(®s->idr0) & 0xffff) | bit, ®s->idr0); + hal2_write(0, ®s->idr1); + hal2_write(0, ®s->idr2); + hal2_write(0, ®s->idr3); + hal2_write(H2_WRITE_ADDR(addr), ®s->iar); + H2_INDIRECT_WAIT(regs); +} + +static void hal2_i_clearbit16(struct snd_hal2 *hal2, u16 addr, u16 bit) +{ + struct hal2_ctl_regs *regs = hal2->ctl_regs; + + hal2_write(H2_READ_ADDR(addr), ®s->iar); + H2_INDIRECT_WAIT(regs); + hal2_write((hal2_read(®s->idr0) & 0xffff) & ~bit, ®s->idr0); + hal2_write(0, ®s->idr1); + hal2_write(0, ®s->idr2); + hal2_write(0, ®s->idr3); + hal2_write(H2_WRITE_ADDR(addr), ®s->iar); + H2_INDIRECT_WAIT(regs); +} + +static int hal2_gain_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; + switch ((int)kcontrol->private_value) { + case H2_MIX_OUTPUT_ATT: + uinfo->value.integer.max = 31; + break; + case H2_MIX_INPUT_GAIN: + uinfo->value.integer.max = 15; + break; + } + return 0; +} + +static int hal2_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_hal2 *hal2 = snd_kcontrol_chip(kcontrol); + u32 tmp; + int l, r; + + switch ((int)kcontrol->private_value) { + case H2_MIX_OUTPUT_ATT: + tmp = hal2_i_read32(hal2, H2I_DAC_C2); + if (tmp & H2I_C2_MUTE) { + l = 0; + r = 0; + } else { + l = 31 - ((tmp >> H2I_C2_L_ATT_SHIFT) & 31); + r = 31 - ((tmp >> H2I_C2_R_ATT_SHIFT) & 31); + } + break; + case H2_MIX_INPUT_GAIN: + tmp = hal2_i_read32(hal2, H2I_ADC_C2); + l = (tmp >> H2I_C2_L_GAIN_SHIFT) & 15; + r = (tmp >> H2I_C2_R_GAIN_SHIFT) & 15; + break; + default: + return -EINVAL; + } + ucontrol->value.integer.value[0] = l; + ucontrol->value.integer.value[1] = r; + + return 0; +} + +static int hal2_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_hal2 *hal2 = snd_kcontrol_chip(kcontrol); + u32 old, new; + int l, r; + + l = ucontrol->value.integer.value[0]; + r = ucontrol->value.integer.value[1]; + + switch ((int)kcontrol->private_value) { + case H2_MIX_OUTPUT_ATT: + old = hal2_i_read32(hal2, H2I_DAC_C2); + new = old & ~(H2I_C2_L_ATT_M | H2I_C2_R_ATT_M | H2I_C2_MUTE); + if (l | r) { + l = 31 - l; + r = 31 - r; + new |= (l << H2I_C2_L_ATT_SHIFT); + new |= (r << H2I_C2_R_ATT_SHIFT); + } else + new |= H2I_C2_L_ATT_M | H2I_C2_R_ATT_M | H2I_C2_MUTE; + hal2_i_write32(hal2, H2I_DAC_C2, new); + break; + case H2_MIX_INPUT_GAIN: + old = hal2_i_read32(hal2, H2I_ADC_C2); + new = old & ~(H2I_C2_L_GAIN_M | H2I_C2_R_GAIN_M); + new |= (l << H2I_C2_L_GAIN_SHIFT); + new |= (r << H2I_C2_R_GAIN_SHIFT); + hal2_i_write32(hal2, H2I_ADC_C2, new); + break; + default: + return -EINVAL; + } + return old != new; +} + +static const struct snd_kcontrol_new hal2_ctrl_headphone = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = H2_MIX_OUTPUT_ATT, + .info = hal2_gain_info, + .get = hal2_gain_get, + .put = hal2_gain_put, +}; + +static const struct snd_kcontrol_new hal2_ctrl_mic = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = H2_MIX_INPUT_GAIN, + .info = hal2_gain_info, + .get = hal2_gain_get, + .put = hal2_gain_put, +}; + +static int hal2_mixer_create(struct snd_hal2 *hal2) +{ + int err; + + /* mute DAC */ + hal2_i_write32(hal2, H2I_DAC_C2, + H2I_C2_L_ATT_M | H2I_C2_R_ATT_M | H2I_C2_MUTE); + /* mute ADC */ + hal2_i_write32(hal2, H2I_ADC_C2, 0); + + err = snd_ctl_add(hal2->card, + snd_ctl_new1(&hal2_ctrl_headphone, hal2)); + if (err < 0) + return err; + + err = snd_ctl_add(hal2->card, + snd_ctl_new1(&hal2_ctrl_mic, hal2)); + if (err < 0) + return err; + + return 0; +} + +static irqreturn_t hal2_interrupt(int irq, void *dev_id) +{ + struct snd_hal2 *hal2 = dev_id; + irqreturn_t ret = IRQ_NONE; + + /* decide what caused this interrupt */ + if (hal2->dac.pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_INT) { + snd_pcm_period_elapsed(hal2->dac.substream); + ret = IRQ_HANDLED; + } + if (hal2->adc.pbus.pbus->pbdma_ctrl & HPC3_PDMACTRL_INT) { + snd_pcm_period_elapsed(hal2->adc.substream); + ret = IRQ_HANDLED; + } + return ret; +} + +static int hal2_compute_rate(struct hal2_codec *codec, unsigned int rate) +{ + unsigned short mod; + + if (44100 % rate < 48000 % rate) { + mod = 4 * 44100 / rate; + codec->master = 44100; + } else { + mod = 4 * 48000 / rate; + codec->master = 48000; + } + + codec->inc = 4; + codec->mod = mod; + rate = 4 * codec->master / mod; + + return rate; +} + +static void hal2_set_dac_rate(struct snd_hal2 *hal2) +{ + unsigned int master = hal2->dac.master; + int inc = hal2->dac.inc; + int mod = hal2->dac.mod; + + hal2_i_write16(hal2, H2I_BRES1_C1, (master == 44100) ? 1 : 0); + hal2_i_write32(hal2, H2I_BRES1_C2, + ((0xffff & (inc - mod - 1)) << 16) | inc); +} + +static void hal2_set_adc_rate(struct snd_hal2 *hal2) +{ + unsigned int master = hal2->adc.master; + int inc = hal2->adc.inc; + int mod = hal2->adc.mod; + + hal2_i_write16(hal2, H2I_BRES2_C1, (master == 44100) ? 1 : 0); + hal2_i_write32(hal2, H2I_BRES2_C2, + ((0xffff & (inc - mod - 1)) << 16) | inc); +} + +static void hal2_setup_dac(struct snd_hal2 *hal2) +{ + unsigned int fifobeg, fifoend, highwater, sample_size; + struct hal2_pbus *pbus = &hal2->dac.pbus; + + /* Now we set up some PBUS information. The PBUS needs information about + * what portion of the fifo it will use. If it's receiving or + * transmitting, and finally whether the stream is little endian or big + * endian. The information is written later, on the start call. + */ + sample_size = 2 * hal2->dac.voices; + /* Fifo should be set to hold exactly four samples. Highwater mark + * should be set to two samples. */ + highwater = (sample_size * 2) >> 1; /* halfwords */ + fifobeg = 0; /* playback is first */ + fifoend = (sample_size * 4) >> 3; /* doublewords */ + pbus->ctrl = HPC3_PDMACTRL_RT | HPC3_PDMACTRL_LD | + (highwater << 8) | (fifobeg << 16) | (fifoend << 24); + /* We disable everything before we do anything at all */ + pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; + hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); + /* Setup the HAL2 for playback */ + hal2_set_dac_rate(hal2); + /* Set endianess */ + hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX); + /* Set DMA bus */ + hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); + /* We are using 1st Bresenham clock generator for playback */ + hal2_i_write16(hal2, H2I_DAC_C1, (pbus->pbusnr << H2I_C1_DMA_SHIFT) + | (1 << H2I_C1_CLKID_SHIFT) + | (hal2->dac.voices << H2I_C1_DATAT_SHIFT)); +} + +static void hal2_setup_adc(struct snd_hal2 *hal2) +{ + unsigned int fifobeg, fifoend, highwater, sample_size; + struct hal2_pbus *pbus = &hal2->adc.pbus; + + sample_size = 2 * hal2->adc.voices; + highwater = (sample_size * 2) >> 1; /* halfwords */ + fifobeg = (4 * 4) >> 3; /* record is second */ + fifoend = (4 * 4 + sample_size * 4) >> 3; /* doublewords */ + pbus->ctrl = HPC3_PDMACTRL_RT | HPC3_PDMACTRL_RCV | HPC3_PDMACTRL_LD | + (highwater << 8) | (fifobeg << 16) | (fifoend << 24); + pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; + hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); + /* Setup the HAL2 for record */ + hal2_set_adc_rate(hal2); + /* Set endianess */ + hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR); + /* Set DMA bus */ + hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); + /* We are using 2nd Bresenham clock generator for record */ + hal2_i_write16(hal2, H2I_ADC_C1, (pbus->pbusnr << H2I_C1_DMA_SHIFT) + | (2 << H2I_C1_CLKID_SHIFT) + | (hal2->adc.voices << H2I_C1_DATAT_SHIFT)); +} + +static void hal2_start_dac(struct snd_hal2 *hal2) +{ + struct hal2_pbus *pbus = &hal2->dac.pbus; + + pbus->pbus->pbdma_dptr = hal2->dac.desc_dma; + pbus->pbus->pbdma_ctrl = pbus->ctrl | HPC3_PDMACTRL_ACT; + /* enable DAC */ + hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); +} + +static void hal2_start_adc(struct snd_hal2 *hal2) +{ + struct hal2_pbus *pbus = &hal2->adc.pbus; + + pbus->pbus->pbdma_dptr = hal2->adc.desc_dma; + pbus->pbus->pbdma_ctrl = pbus->ctrl | HPC3_PDMACTRL_ACT; + /* enable ADC */ + hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); +} + +static inline void hal2_stop_dac(struct snd_hal2 *hal2) +{ + hal2->dac.pbus.pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; + /* The HAL2 itself may remain enabled safely */ +} + +static inline void hal2_stop_adc(struct snd_hal2 *hal2) +{ + hal2->adc.pbus.pbus->pbdma_ctrl = HPC3_PDMACTRL_LD; +} + +static int hal2_alloc_dmabuf(struct hal2_codec *codec) +{ + struct hal2_desc *desc; + dma_addr_t desc_dma, buffer_dma; + int count = H2_BUF_SIZE / H2_BLOCK_SIZE; + int i; + + codec->buffer = dma_alloc_attrs(NULL, H2_BUF_SIZE, &buffer_dma, + GFP_KERNEL, DMA_ATTR_NON_CONSISTENT); + if (!codec->buffer) + return -ENOMEM; + desc = dma_alloc_attrs(NULL, count * sizeof(struct hal2_desc), + &desc_dma, GFP_KERNEL, DMA_ATTR_NON_CONSISTENT); + if (!desc) { + dma_free_attrs(NULL, H2_BUF_SIZE, codec->buffer, buffer_dma, + DMA_ATTR_NON_CONSISTENT); + return -ENOMEM; + } + codec->buffer_dma = buffer_dma; + codec->desc_dma = desc_dma; + codec->desc = desc; + for (i = 0; i < count; i++) { + desc->desc.pbuf = buffer_dma + i * H2_BLOCK_SIZE; + desc->desc.cntinfo = HPCDMA_XIE | H2_BLOCK_SIZE; + desc->desc.pnext = (i == count - 1) ? + desc_dma : desc_dma + (i + 1) * sizeof(struct hal2_desc); + desc++; + } + dma_cache_sync(NULL, codec->desc, count * sizeof(struct hal2_desc), + DMA_TO_DEVICE); + codec->desc_count = count; + return 0; +} + +static void hal2_free_dmabuf(struct hal2_codec *codec) +{ + dma_free_attrs(NULL, codec->desc_count * sizeof(struct hal2_desc), + codec->desc, codec->desc_dma, DMA_ATTR_NON_CONSISTENT); + dma_free_attrs(NULL, H2_BUF_SIZE, codec->buffer, codec->buffer_dma, + DMA_ATTR_NON_CONSISTENT); +} + +static const struct snd_pcm_hardware hal2_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 1024, + .period_bytes_max = 65536, + .periods_min = 2, + .periods_max = 1024, +}; + +static int hal2_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int err; + + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (err < 0) + return err; + + return 0; +} + +static int hal2_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int hal2_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + int err; + + runtime->hw = hal2_pcm_hw; + + err = hal2_alloc_dmabuf(&hal2->dac); + if (err) + return err; + return 0; +} + +static int hal2_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + + hal2_free_dmabuf(&hal2->dac); + return 0; +} + +static int hal2_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct hal2_codec *dac = &hal2->dac; + + dac->voices = runtime->channels; + dac->sample_rate = hal2_compute_rate(dac, runtime->rate); + memset(&dac->pcm_indirect, 0, sizeof(dac->pcm_indirect)); + dac->pcm_indirect.hw_buffer_size = H2_BUF_SIZE; + dac->pcm_indirect.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); + dac->substream = substream; + hal2_setup_dac(hal2); + return 0; +} + +static int hal2_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + hal2->dac.pcm_indirect.hw_io = hal2->dac.buffer_dma; + hal2->dac.pcm_indirect.hw_data = 0; + substream->ops->ack(substream); + hal2_start_dac(hal2); + break; + case SNDRV_PCM_TRIGGER_STOP: + hal2_stop_dac(hal2); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t +hal2_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + struct hal2_codec *dac = &hal2->dac; + + return snd_pcm_indirect_playback_pointer(substream, &dac->pcm_indirect, + dac->pbus.pbus->pbdma_bptr); +} + +static void hal2_playback_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect *rec, size_t bytes) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + unsigned char *buf = hal2->dac.buffer + rec->hw_data; + + memcpy(buf, substream->runtime->dma_area + rec->sw_data, bytes); + dma_cache_sync(NULL, buf, bytes, DMA_TO_DEVICE); + +} + +static int hal2_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + struct hal2_codec *dac = &hal2->dac; + + dac->pcm_indirect.hw_queue_size = H2_BUF_SIZE / 2; + return snd_pcm_indirect_playback_transfer(substream, + &dac->pcm_indirect, + hal2_playback_transfer); +} + +static int hal2_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + struct hal2_codec *adc = &hal2->adc; + int err; + + runtime->hw = hal2_pcm_hw; + + err = hal2_alloc_dmabuf(adc); + if (err) + return err; + return 0; +} + +static int hal2_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + + hal2_free_dmabuf(&hal2->adc); + return 0; +} + +static int hal2_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct hal2_codec *adc = &hal2->adc; + + adc->voices = runtime->channels; + adc->sample_rate = hal2_compute_rate(adc, runtime->rate); + memset(&adc->pcm_indirect, 0, sizeof(adc->pcm_indirect)); + adc->pcm_indirect.hw_buffer_size = H2_BUF_SIZE; + adc->pcm_indirect.hw_queue_size = H2_BUF_SIZE / 2; + adc->pcm_indirect.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); + adc->substream = substream; + hal2_setup_adc(hal2); + return 0; +} + +static int hal2_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + hal2->adc.pcm_indirect.hw_io = hal2->adc.buffer_dma; + hal2->adc.pcm_indirect.hw_data = 0; + printk(KERN_DEBUG "buffer_dma %x\n", hal2->adc.buffer_dma); + hal2_start_adc(hal2); + break; + case SNDRV_PCM_TRIGGER_STOP: + hal2_stop_adc(hal2); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t +hal2_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + struct hal2_codec *adc = &hal2->adc; + + return snd_pcm_indirect_capture_pointer(substream, &adc->pcm_indirect, + adc->pbus.pbus->pbdma_bptr); +} + +static void hal2_capture_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect *rec, size_t bytes) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + unsigned char *buf = hal2->adc.buffer + rec->hw_data; + + dma_cache_sync(NULL, buf, bytes, DMA_FROM_DEVICE); + memcpy(substream->runtime->dma_area + rec->sw_data, buf, bytes); +} + +static int hal2_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_hal2 *hal2 = snd_pcm_substream_chip(substream); + struct hal2_codec *adc = &hal2->adc; + + return snd_pcm_indirect_capture_transfer(substream, + &adc->pcm_indirect, + hal2_capture_transfer); +} + +static const struct snd_pcm_ops hal2_playback_ops = { + .open = hal2_playback_open, + .close = hal2_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hal2_pcm_hw_params, + .hw_free = hal2_pcm_hw_free, + .prepare = hal2_playback_prepare, + .trigger = hal2_playback_trigger, + .pointer = hal2_playback_pointer, + .ack = hal2_playback_ack, +}; + +static const struct snd_pcm_ops hal2_capture_ops = { + .open = hal2_capture_open, + .close = hal2_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hal2_pcm_hw_params, + .hw_free = hal2_pcm_hw_free, + .prepare = hal2_capture_prepare, + .trigger = hal2_capture_trigger, + .pointer = hal2_capture_pointer, + .ack = hal2_capture_ack, +}; + +static int hal2_pcm_create(struct snd_hal2 *hal2) +{ + struct snd_pcm *pcm; + int err; + + /* create first pcm device with one outputs and one input */ + err = snd_pcm_new(hal2->card, "SGI HAL2 Audio", 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = hal2; + strcpy(pcm->name, "SGI HAL2"); + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &hal2_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &hal2_capture_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 0, 1024 * 1024); + + return 0; +} + +static int hal2_dev_free(struct snd_device *device) +{ + struct snd_hal2 *hal2 = device->device_data; + + free_irq(SGI_HPCDMA_IRQ, hal2); + kfree(hal2); + return 0; +} + +static struct snd_device_ops hal2_ops = { + .dev_free = hal2_dev_free, +}; + +static void hal2_init_codec(struct hal2_codec *codec, struct hpc3_regs *hpc3, + int index) +{ + codec->pbus.pbusnr = index; + codec->pbus.pbus = &hpc3->pbdma[index]; +} + +static int hal2_detect(struct snd_hal2 *hal2) +{ + unsigned short board, major, minor; + unsigned short rev; + + /* reset HAL2 */ + hal2_write(0, &hal2->ctl_regs->isr); + + /* release reset */ + hal2_write(H2_ISR_GLOBAL_RESET_N | H2_ISR_CODEC_RESET_N, + &hal2->ctl_regs->isr); + + + hal2_i_write16(hal2, H2I_RELAY_C, H2I_RELAY_C_STATE); + rev = hal2_read(&hal2->ctl_regs->rev); + if (rev & H2_REV_AUDIO_PRESENT) + return -ENODEV; + + board = (rev & H2_REV_BOARD_M) >> 12; + major = (rev & H2_REV_MAJOR_CHIP_M) >> 4; + minor = (rev & H2_REV_MINOR_CHIP_M); + + printk(KERN_INFO "SGI HAL2 revision %i.%i.%i\n", + board, major, minor); + + return 0; +} + +static int hal2_create(struct snd_card *card, struct snd_hal2 **rchip) +{ + struct snd_hal2 *hal2; + struct hpc3_regs *hpc3 = hpc3c0; + int err; + + hal2 = kzalloc(sizeof(*hal2), GFP_KERNEL); + if (!hal2) + return -ENOMEM; + + hal2->card = card; + + if (request_irq(SGI_HPCDMA_IRQ, hal2_interrupt, IRQF_SHARED, + "SGI HAL2", hal2)) { + printk(KERN_ERR "HAL2: Can't get irq %d\n", SGI_HPCDMA_IRQ); + kfree(hal2); + return -EAGAIN; + } + + hal2->ctl_regs = (struct hal2_ctl_regs *)hpc3->pbus_extregs[0]; + hal2->aes_regs = (struct hal2_aes_regs *)hpc3->pbus_extregs[1]; + hal2->vol_regs = (struct hal2_vol_regs *)hpc3->pbus_extregs[2]; + hal2->syn_regs = (struct hal2_syn_regs *)hpc3->pbus_extregs[3]; + + if (hal2_detect(hal2) < 0) { + kfree(hal2); + return -ENODEV; + } + + hal2_init_codec(&hal2->dac, hpc3, 0); + hal2_init_codec(&hal2->adc, hpc3, 1); + + /* + * All DMA channel interfaces in HAL2 are designed to operate with + * PBUS programmed for 2 cycles in D3, 2 cycles in D4 and 2 cycles + * in D5. HAL2 is a 16-bit device which can accept both big and little + * endian format. It assumes that even address bytes are on high + * portion of PBUS (15:8) and assumes that HPC3 is programmed to + * accept a live (unsynchronized) version of P_DREQ_N from HAL2. + */ +#define HAL2_PBUS_DMACFG ((0 << HPC3_DMACFG_D3R_SHIFT) | \ + (2 << HPC3_DMACFG_D4R_SHIFT) | \ + (2 << HPC3_DMACFG_D5R_SHIFT) | \ + (0 << HPC3_DMACFG_D3W_SHIFT) | \ + (2 << HPC3_DMACFG_D4W_SHIFT) | \ + (2 << HPC3_DMACFG_D5W_SHIFT) | \ + HPC3_DMACFG_DS16 | \ + HPC3_DMACFG_EVENHI | \ + HPC3_DMACFG_RTIME | \ + (8 << HPC3_DMACFG_BURST_SHIFT) | \ + HPC3_DMACFG_DRQLIVE) + /* + * Ignore what's mentioned in the specification and write value which + * works in The Real World (TM) + */ + hpc3->pbus_dmacfg[hal2->dac.pbus.pbusnr][0] = 0x8208844; + hpc3->pbus_dmacfg[hal2->adc.pbus.pbusnr][0] = 0x8208844; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, hal2, &hal2_ops); + if (err < 0) { + free_irq(SGI_HPCDMA_IRQ, hal2); + kfree(hal2); + return err; + } + *rchip = hal2; + return 0; +} + +static int hal2_probe(struct platform_device *pdev) +{ + struct snd_card *card; + struct snd_hal2 *chip; + int err; + + err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + err = hal2_create(card, &chip); + if (err < 0) { + snd_card_free(card); + return err; + } + + err = hal2_pcm_create(chip); + if (err < 0) { + snd_card_free(card); + return err; + } + err = hal2_mixer_create(chip); + if (err < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, "SGI HAL2 Audio"); + strcpy(card->shortname, "SGI HAL2 Audio"); + sprintf(card->longname, "%s irq %i", + card->shortname, + SGI_HPCDMA_IRQ); + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + platform_set_drvdata(pdev, card); + return 0; +} + +static int hal2_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + + snd_card_free(card); + return 0; +} + +static struct platform_driver hal2_driver = { + .probe = hal2_probe, + .remove = hal2_remove, + .driver = { + .name = "sgihal2", + } +}; + +module_platform_driver(hal2_driver); diff --git a/sound/mips/hal2.h b/sound/mips/hal2.h new file mode 100644 index 000000000..f19828bc6 --- /dev/null +++ b/sound/mips/hal2.h @@ -0,0 +1,245 @@ +#ifndef __HAL2_H +#define __HAL2_H + +/* + * Driver for HAL2 sound processors + * Copyright (c) 1999 Ulf Carlsson + * Copyright (c) 2001, 2002, 2003 Ladislav Michl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include + +/* Indirect status register */ + +#define H2_ISR_TSTATUS 0x01 /* RO: transaction status 1=busy */ +#define H2_ISR_USTATUS 0x02 /* RO: utime status bit 1=armed */ +#define H2_ISR_QUAD_MODE 0x04 /* codec mode 0=indigo 1=quad */ +#define H2_ISR_GLOBAL_RESET_N 0x08 /* chip global reset 0=reset */ +#define H2_ISR_CODEC_RESET_N 0x10 /* codec/synth reset 0=reset */ + +/* Revision register */ + +#define H2_REV_AUDIO_PRESENT 0x8000 /* RO: audio present 0=present */ +#define H2_REV_BOARD_M 0x7000 /* RO: bits 14:12, board revision */ +#define H2_REV_MAJOR_CHIP_M 0x00F0 /* RO: bits 7:4, major chip revision */ +#define H2_REV_MINOR_CHIP_M 0x000F /* RO: bits 3:0, minor chip revision */ + +/* Indirect address register */ + +/* + * Address of indirect internal register to be accessed. A write to this + * register initiates read or write access to the indirect registers in the + * HAL2. Note that there af four indirect data registers for write access to + * registers larger than 16 byte. + */ + +#define H2_IAR_TYPE_M 0xF000 /* bits 15:12, type of functional */ + /* block the register resides in */ + /* 1=DMA Port */ + /* 9=Global DMA Control */ + /* 2=Bresenham */ + /* 3=Unix Timer */ +#define H2_IAR_NUM_M 0x0F00 /* bits 11:8 instance of the */ + /* blockin which the indirect */ + /* register resides */ + /* If IAR_TYPE_M=DMA Port: */ + /* 1=Synth In */ + /* 2=AES In */ + /* 3=AES Out */ + /* 4=DAC Out */ + /* 5=ADC Out */ + /* 6=Synth Control */ + /* If IAR_TYPE_M=Global DMA Control: */ + /* 1=Control */ + /* If IAR_TYPE_M=Bresenham: */ + /* 1=Bresenham Clock Gen 1 */ + /* 2=Bresenham Clock Gen 2 */ + /* 3=Bresenham Clock Gen 3 */ + /* If IAR_TYPE_M=Unix Timer: */ + /* 1=Unix Timer */ +#define H2_IAR_ACCESS_SELECT 0x0080 /* 1=read 0=write */ +#define H2_IAR_PARAM 0x000C /* Parameter Select */ +#define H2_IAR_RB_INDEX_M 0x0003 /* Read Back Index */ + /* 00:word0 */ + /* 01:word1 */ + /* 10:word2 */ + /* 11:word3 */ +/* + * HAL2 internal addressing + * + * The HAL2 has "indirect registers" (idr) which are accessed by writing to the + * Indirect Data registers. Write the address to the Indirect Address register + * to transfer the data. + * + * We define the H2IR_* to the read address and H2IW_* to the write address and + * H2I_* to be fields in whatever register is referred to. + * + * When we write to indirect registers which are larger than one word (16 bit) + * we have to fill more than one indirect register before writing. When we read + * back however we have to read several times, each time with different Read + * Back Indexes (there are defs for doing this easily). + */ + +/* + * Relay Control + */ +#define H2I_RELAY_C 0x9100 +#define H2I_RELAY_C_STATE 0x01 /* state of RELAY pin signal */ + +/* DMA port enable */ + +#define H2I_DMA_PORT_EN 0x9104 +#define H2I_DMA_PORT_EN_SY_IN 0x01 /* Synth_in DMA port */ +#define H2I_DMA_PORT_EN_AESRX 0x02 /* AES receiver DMA port */ +#define H2I_DMA_PORT_EN_AESTX 0x04 /* AES transmitter DMA port */ +#define H2I_DMA_PORT_EN_CODECTX 0x08 /* CODEC transmit DMA port */ +#define H2I_DMA_PORT_EN_CODECR 0x10 /* CODEC receive DMA port */ + +#define H2I_DMA_END 0x9108 /* global dma endian select */ +#define H2I_DMA_END_SY_IN 0x01 /* Synth_in DMA port */ +#define H2I_DMA_END_AESRX 0x02 /* AES receiver DMA port */ +#define H2I_DMA_END_AESTX 0x04 /* AES transmitter DMA port */ +#define H2I_DMA_END_CODECTX 0x08 /* CODEC transmit DMA port */ +#define H2I_DMA_END_CODECR 0x10 /* CODEC receive DMA port */ + /* 0=b_end 1=l_end */ + +#define H2I_DMA_DRV 0x910C /* global PBUS DMA enable */ + +#define H2I_SYNTH_C 0x1104 /* Synth DMA control */ + +#define H2I_AESRX_C 0x1204 /* AES RX dma control */ + +#define H2I_C_TS_EN 0x20 /* Timestamp enable */ +#define H2I_C_TS_FRMT 0x40 /* Timestamp format */ +#define H2I_C_NAUDIO 0x80 /* Sign extend */ + +/* AESRX CTL, 16 bit */ + +#define H2I_AESTX_C 0x1304 /* AES TX DMA control */ +#define H2I_AESTX_C_CLKID_SHIFT 3 /* Bresenham Clock Gen 1-3 */ +#define H2I_AESTX_C_CLKID_M 0x18 +#define H2I_AESTX_C_DATAT_SHIFT 8 /* 1=mono 2=stereo (3=quad) */ +#define H2I_AESTX_C_DATAT_M 0x300 + +/* CODEC registers */ + +#define H2I_DAC_C1 0x1404 /* DAC DMA control, 16 bit */ +#define H2I_DAC_C2 0x1408 /* DAC DMA control, 32 bit */ +#define H2I_ADC_C1 0x1504 /* ADC DMA control, 16 bit */ +#define H2I_ADC_C2 0x1508 /* ADC DMA control, 32 bit */ + +/* Bits in CTL1 register */ + +#define H2I_C1_DMA_SHIFT 0 /* DMA channel */ +#define H2I_C1_DMA_M 0x7 +#define H2I_C1_CLKID_SHIFT 3 /* Bresenham Clock Gen 1-3 */ +#define H2I_C1_CLKID_M 0x18 +#define H2I_C1_DATAT_SHIFT 8 /* 1=mono 2=stereo (3=quad) */ +#define H2I_C1_DATAT_M 0x300 + +/* Bits in CTL2 register */ + +#define H2I_C2_R_GAIN_SHIFT 0 /* right a/d input gain */ +#define H2I_C2_R_GAIN_M 0xf +#define H2I_C2_L_GAIN_SHIFT 4 /* left a/d input gain */ +#define H2I_C2_L_GAIN_M 0xf0 +#define H2I_C2_R_SEL 0x100 /* right input select */ +#define H2I_C2_L_SEL 0x200 /* left input select */ +#define H2I_C2_MUTE 0x400 /* mute */ +#define H2I_C2_DO1 0x00010000 /* digital output port bit 0 */ +#define H2I_C2_DO2 0x00020000 /* digital output port bit 1 */ +#define H2I_C2_R_ATT_SHIFT 18 /* right d/a output - */ +#define H2I_C2_R_ATT_M 0x007c0000 /* attenuation */ +#define H2I_C2_L_ATT_SHIFT 23 /* left d/a output - */ +#define H2I_C2_L_ATT_M 0x0f800000 /* attenuation */ + +#define H2I_SYNTH_MAP_C 0x1104 /* synth dma handshake ctrl */ + +/* Clock generator CTL 1, 16 bit */ + +#define H2I_BRES1_C1 0x2104 +#define H2I_BRES2_C1 0x2204 +#define H2I_BRES3_C1 0x2304 + +#define H2I_BRES_C1_SHIFT 0 /* 0=48.0 1=44.1 2=aes_rx */ +#define H2I_BRES_C1_M 0x03 + +/* Clock generator CTL 2, 32 bit */ + +#define H2I_BRES1_C2 0x2108 +#define H2I_BRES2_C2 0x2208 +#define H2I_BRES3_C2 0x2308 + +#define H2I_BRES_C2_INC_SHIFT 0 /* increment value */ +#define H2I_BRES_C2_INC_M 0xffff +#define H2I_BRES_C2_MOD_SHIFT 16 /* modcontrol value */ +#define H2I_BRES_C2_MOD_M 0xffff0000 /* modctrl=0xffff&(modinc-1) */ + +/* Unix timer, 64 bit */ + +#define H2I_UTIME 0x3104 +#define H2I_UTIME_0_LD 0xffff /* microseconds, LSB's */ +#define H2I_UTIME_1_LD0 0x0f /* microseconds, MSB's */ +#define H2I_UTIME_1_LD1 0xf0 /* tenths of microseconds */ +#define H2I_UTIME_2_LD 0xffff /* seconds, LSB's */ +#define H2I_UTIME_3_LD 0xffff /* seconds, MSB's */ + +struct hal2_ctl_regs { + u32 _unused0[4]; + u32 isr; /* 0x10 Status Register */ + u32 _unused1[3]; + u32 rev; /* 0x20 Revision Register */ + u32 _unused2[3]; + u32 iar; /* 0x30 Indirect Address Register */ + u32 _unused3[3]; + u32 idr0; /* 0x40 Indirect Data Register 0 */ + u32 _unused4[3]; + u32 idr1; /* 0x50 Indirect Data Register 1 */ + u32 _unused5[3]; + u32 idr2; /* 0x60 Indirect Data Register 2 */ + u32 _unused6[3]; + u32 idr3; /* 0x70 Indirect Data Register 3 */ +}; + +struct hal2_aes_regs { + u32 rx_stat[2]; /* Status registers */ + u32 rx_cr[2]; /* Control registers */ + u32 rx_ud[4]; /* User data window */ + u32 rx_st[24]; /* Channel status data */ + + u32 tx_stat[1]; /* Status register */ + u32 tx_cr[3]; /* Control registers */ + u32 tx_ud[4]; /* User data window */ + u32 tx_st[24]; /* Channel status data */ +}; + +struct hal2_vol_regs { + u32 right; /* Right volume */ + u32 left; /* Left volume */ +}; + +struct hal2_syn_regs { + u32 _unused0[2]; + u32 page; /* DOC Page register */ + u32 regsel; /* DOC Register selection */ + u32 dlow; /* DOC Data low */ + u32 dhigh; /* DOC Data high */ + u32 irq; /* IRQ Status */ + u32 dram; /* DRAM Access */ +}; + +#endif /* __HAL2_H */ diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c new file mode 100644 index 000000000..3ec9391a4 --- /dev/null +++ b/sound/mips/sgio2audio.c @@ -0,0 +1,966 @@ +/* + * Sound driver for Silicon Graphics O2 Workstations A/V board audio. + * + * Copyright 2003 Vivien Chappelier + * Copyright 2008 Thomas Bogendoerfer + * Mxier part taken from mace_audio.c: + * Copyright 2007 Thorben Jändling + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#define SNDRV_GET_ID +#include +#include + + +MODULE_AUTHOR("Vivien Chappelier "); +MODULE_DESCRIPTION("SGI O2 Audio"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Silicon Graphics, O2 Audio}}"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for SGI O2 soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for SGI O2 soundcard."); + + +#define AUDIO_CONTROL_RESET BIT(0) /* 1: reset audio interface */ +#define AUDIO_CONTROL_CODEC_PRESENT BIT(1) /* 1: codec detected */ + +#define CODEC_CONTROL_WORD_SHIFT 0 +#define CODEC_CONTROL_READ BIT(16) +#define CODEC_CONTROL_ADDRESS_SHIFT 17 + +#define CHANNEL_CONTROL_RESET BIT(10) /* 1: reset channel */ +#define CHANNEL_DMA_ENABLE BIT(9) /* 1: enable DMA transfer */ +#define CHANNEL_INT_THRESHOLD_DISABLED (0 << 5) /* interrupt disabled */ +#define CHANNEL_INT_THRESHOLD_25 (1 << 5) /* int on buffer >25% full */ +#define CHANNEL_INT_THRESHOLD_50 (2 << 5) /* int on buffer >50% full */ +#define CHANNEL_INT_THRESHOLD_75 (3 << 5) /* int on buffer >75% full */ +#define CHANNEL_INT_THRESHOLD_EMPTY (4 << 5) /* int on buffer empty */ +#define CHANNEL_INT_THRESHOLD_NOT_EMPTY (5 << 5) /* int on buffer !empty */ +#define CHANNEL_INT_THRESHOLD_FULL (6 << 5) /* int on buffer empty */ +#define CHANNEL_INT_THRESHOLD_NOT_FULL (7 << 5) /* int on buffer !empty */ + +#define CHANNEL_RING_SHIFT 12 +#define CHANNEL_RING_SIZE (1 << CHANNEL_RING_SHIFT) +#define CHANNEL_RING_MASK (CHANNEL_RING_SIZE - 1) + +#define CHANNEL_LEFT_SHIFT 40 +#define CHANNEL_RIGHT_SHIFT 8 + +struct snd_sgio2audio_chan { + int idx; + struct snd_pcm_substream *substream; + int pos; + snd_pcm_uframes_t size; + spinlock_t lock; +}; + +/* definition of the chip-specific record */ +struct snd_sgio2audio { + struct snd_card *card; + + /* codec */ + struct snd_ad1843 ad1843; + spinlock_t ad1843_lock; + + /* channels */ + struct snd_sgio2audio_chan channel[3]; + + /* resources */ + void *ring_base; + dma_addr_t ring_base_dma; +}; + +/* AD1843 access */ + +/* + * read_ad1843_reg returns the current contents of a 16 bit AD1843 register. + * + * Returns unsigned register value on success, -errno on failure. + */ +static int read_ad1843_reg(void *priv, int reg) +{ + struct snd_sgio2audio *chip = priv; + int val; + unsigned long flags; + + spin_lock_irqsave(&chip->ad1843_lock, flags); + + writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) | + CODEC_CONTROL_READ, &mace->perif.audio.codec_control); + wmb(); + val = readq(&mace->perif.audio.codec_control); /* flush bus */ + udelay(200); + + val = readq(&mace->perif.audio.codec_read); + + spin_unlock_irqrestore(&chip->ad1843_lock, flags); + return val; +} + +/* + * write_ad1843_reg writes the specified value to a 16 bit AD1843 register. + */ +static int write_ad1843_reg(void *priv, int reg, int word) +{ + struct snd_sgio2audio *chip = priv; + int val; + unsigned long flags; + + spin_lock_irqsave(&chip->ad1843_lock, flags); + + writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) | + (word << CODEC_CONTROL_WORD_SHIFT), + &mace->perif.audio.codec_control); + wmb(); + val = readq(&mace->perif.audio.codec_control); /* flush bus */ + udelay(200); + + spin_unlock_irqrestore(&chip->ad1843_lock, flags); + return 0; +} + +static int sgio2audio_gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = ad1843_get_gain_max(&chip->ad1843, + (int)kcontrol->private_value); + return 0; +} + +static int sgio2audio_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); + int vol; + + vol = ad1843_get_gain(&chip->ad1843, (int)kcontrol->private_value); + + ucontrol->value.integer.value[0] = (vol >> 8) & 0xFF; + ucontrol->value.integer.value[1] = vol & 0xFF; + + return 0; +} + +static int sgio2audio_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); + int newvol, oldvol; + + oldvol = ad1843_get_gain(&chip->ad1843, kcontrol->private_value); + newvol = (ucontrol->value.integer.value[0] << 8) | + ucontrol->value.integer.value[1]; + + newvol = ad1843_set_gain(&chip->ad1843, kcontrol->private_value, + newvol); + + return newvol != oldvol; +} + +static int sgio2audio_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Cam Mic", "Mic", "Line" + }; + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int sgio2audio_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = ad1843_get_recsrc(&chip->ad1843); + return 0; +} + +static int sgio2audio_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); + int newsrc, oldsrc; + + oldsrc = ad1843_get_recsrc(&chip->ad1843); + newsrc = ad1843_set_recsrc(&chip->ad1843, + ucontrol->value.enumerated.item[0]); + + return newsrc != oldsrc; +} + +/* dac1/pcm0 mixer control */ +static const struct snd_kcontrol_new sgio2audio_ctrl_pcm0 = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = AD1843_GAIN_PCM_0, + .info = sgio2audio_gain_info, + .get = sgio2audio_gain_get, + .put = sgio2audio_gain_put, +}; + +/* dac2/pcm1 mixer control */ +static const struct snd_kcontrol_new sgio2audio_ctrl_pcm1 = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 1, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = AD1843_GAIN_PCM_1, + .info = sgio2audio_gain_info, + .get = sgio2audio_gain_get, + .put = sgio2audio_gain_put, +}; + +/* record level mixer control */ +static const struct snd_kcontrol_new sgio2audio_ctrl_reclevel = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = AD1843_GAIN_RECLEV, + .info = sgio2audio_gain_info, + .get = sgio2audio_gain_get, + .put = sgio2audio_gain_put, +}; + +/* record level source control */ +static const struct snd_kcontrol_new sgio2audio_ctrl_recsource = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = sgio2audio_source_info, + .get = sgio2audio_source_get, + .put = sgio2audio_source_put, +}; + +/* line mixer control */ +static const struct snd_kcontrol_new sgio2audio_ctrl_line = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = AD1843_GAIN_LINE, + .info = sgio2audio_gain_info, + .get = sgio2audio_gain_get, + .put = sgio2audio_gain_put, +}; + +/* cd mixer control */ +static const struct snd_kcontrol_new sgio2audio_ctrl_cd = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Playback Volume", + .index = 1, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = AD1843_GAIN_LINE_2, + .info = sgio2audio_gain_info, + .get = sgio2audio_gain_get, + .put = sgio2audio_gain_put, +}; + +/* mic mixer control */ +static const struct snd_kcontrol_new sgio2audio_ctrl_mic = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = AD1843_GAIN_MIC, + .info = sgio2audio_gain_info, + .get = sgio2audio_gain_get, + .put = sgio2audio_gain_put, +}; + + +static int snd_sgio2audio_new_mixer(struct snd_sgio2audio *chip) +{ + int err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&sgio2audio_ctrl_pcm0, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&sgio2audio_ctrl_pcm1, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&sgio2audio_ctrl_reclevel, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&sgio2audio_ctrl_recsource, chip)); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, + snd_ctl_new1(&sgio2audio_ctrl_line, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&sgio2audio_ctrl_cd, chip)); + if (err < 0) + return err; + + err = snd_ctl_add(chip->card, + snd_ctl_new1(&sgio2audio_ctrl_mic, chip)); + if (err < 0) + return err; + + return 0; +} + +/* low-level audio interface DMA */ + +/* get data out of bounce buffer, count must be a multiple of 32 */ +/* returns 1 if a period has elapsed */ +static int snd_sgio2audio_dma_pull_frag(struct snd_sgio2audio *chip, + unsigned int ch, unsigned int count) +{ + int ret; + unsigned long src_base, src_pos, dst_mask; + unsigned char *dst_base; + int dst_pos; + u64 *src; + s16 *dst; + u64 x; + unsigned long flags; + struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; + + spin_lock_irqsave(&chip->channel[ch].lock, flags); + + src_base = (unsigned long) chip->ring_base | (ch << CHANNEL_RING_SHIFT); + src_pos = readq(&mace->perif.audio.chan[ch].read_ptr); + dst_base = runtime->dma_area; + dst_pos = chip->channel[ch].pos; + dst_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; + + /* check if a period has elapsed */ + chip->channel[ch].size += (count >> 3); /* in frames */ + ret = chip->channel[ch].size >= runtime->period_size; + chip->channel[ch].size %= runtime->period_size; + + while (count) { + src = (u64 *)(src_base + src_pos); + dst = (s16 *)(dst_base + dst_pos); + + x = *src; + dst[0] = (x >> CHANNEL_LEFT_SHIFT) & 0xffff; + dst[1] = (x >> CHANNEL_RIGHT_SHIFT) & 0xffff; + + src_pos = (src_pos + sizeof(u64)) & CHANNEL_RING_MASK; + dst_pos = (dst_pos + 2 * sizeof(s16)) & dst_mask; + count -= sizeof(u64); + } + + writeq(src_pos, &mace->perif.audio.chan[ch].read_ptr); /* in bytes */ + chip->channel[ch].pos = dst_pos; + + spin_unlock_irqrestore(&chip->channel[ch].lock, flags); + return ret; +} + +/* put some DMA data in bounce buffer, count must be a multiple of 32 */ +/* returns 1 if a period has elapsed */ +static int snd_sgio2audio_dma_push_frag(struct snd_sgio2audio *chip, + unsigned int ch, unsigned int count) +{ + int ret; + s64 l, r; + unsigned long dst_base, dst_pos, src_mask; + unsigned char *src_base; + int src_pos; + u64 *dst; + s16 *src; + unsigned long flags; + struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; + + spin_lock_irqsave(&chip->channel[ch].lock, flags); + + dst_base = (unsigned long)chip->ring_base | (ch << CHANNEL_RING_SHIFT); + dst_pos = readq(&mace->perif.audio.chan[ch].write_ptr); + src_base = runtime->dma_area; + src_pos = chip->channel[ch].pos; + src_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; + + /* check if a period has elapsed */ + chip->channel[ch].size += (count >> 3); /* in frames */ + ret = chip->channel[ch].size >= runtime->period_size; + chip->channel[ch].size %= runtime->period_size; + + while (count) { + src = (s16 *)(src_base + src_pos); + dst = (u64 *)(dst_base + dst_pos); + + l = src[0]; /* sign extend */ + r = src[1]; /* sign extend */ + + *dst = ((l & 0x00ffffff) << CHANNEL_LEFT_SHIFT) | + ((r & 0x00ffffff) << CHANNEL_RIGHT_SHIFT); + + dst_pos = (dst_pos + sizeof(u64)) & CHANNEL_RING_MASK; + src_pos = (src_pos + 2 * sizeof(s16)) & src_mask; + count -= sizeof(u64); + } + + writeq(dst_pos, &mace->perif.audio.chan[ch].write_ptr); /* in bytes */ + chip->channel[ch].pos = src_pos; + + spin_unlock_irqrestore(&chip->channel[ch].lock, flags); + return ret; +} + +static int snd_sgio2audio_dma_start(struct snd_pcm_substream *substream) +{ + struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); + struct snd_sgio2audio_chan *chan = substream->runtime->private_data; + int ch = chan->idx; + + /* reset DMA channel */ + writeq(CHANNEL_CONTROL_RESET, &mace->perif.audio.chan[ch].control); + udelay(10); + writeq(0, &mace->perif.audio.chan[ch].control); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* push a full buffer */ + snd_sgio2audio_dma_push_frag(chip, ch, CHANNEL_RING_SIZE - 32); + } + /* set DMA to wake on 50% empty and enable interrupt */ + writeq(CHANNEL_DMA_ENABLE | CHANNEL_INT_THRESHOLD_50, + &mace->perif.audio.chan[ch].control); + return 0; +} + +static int snd_sgio2audio_dma_stop(struct snd_pcm_substream *substream) +{ + struct snd_sgio2audio_chan *chan = substream->runtime->private_data; + + writeq(0, &mace->perif.audio.chan[chan->idx].control); + return 0; +} + +static irqreturn_t snd_sgio2audio_dma_in_isr(int irq, void *dev_id) +{ + struct snd_sgio2audio_chan *chan = dev_id; + struct snd_pcm_substream *substream; + struct snd_sgio2audio *chip; + int count, ch; + + substream = chan->substream; + chip = snd_pcm_substream_chip(substream); + ch = chan->idx; + + /* empty the ring */ + count = CHANNEL_RING_SIZE - + readq(&mace->perif.audio.chan[ch].depth) - 32; + if (snd_sgio2audio_dma_pull_frag(chip, ch, count)) + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +static irqreturn_t snd_sgio2audio_dma_out_isr(int irq, void *dev_id) +{ + struct snd_sgio2audio_chan *chan = dev_id; + struct snd_pcm_substream *substream; + struct snd_sgio2audio *chip; + int count, ch; + + substream = chan->substream; + chip = snd_pcm_substream_chip(substream); + ch = chan->idx; + /* fill the ring */ + count = CHANNEL_RING_SIZE - + readq(&mace->perif.audio.chan[ch].depth) - 32; + if (snd_sgio2audio_dma_push_frag(chip, ch, count)) + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +static irqreturn_t snd_sgio2audio_error_isr(int irq, void *dev_id) +{ + struct snd_sgio2audio_chan *chan = dev_id; + struct snd_pcm_substream *substream; + + substream = chan->substream; + snd_sgio2audio_dma_stop(substream); + snd_sgio2audio_dma_start(substream); + return IRQ_HANDLED; +} + +/* PCM part */ +/* PCM hardware definition */ +static const struct snd_pcm_hardware snd_sgio2audio_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 32768, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, +}; + +/* PCM playback open callback */ +static int snd_sgio2audio_playback1_open(struct snd_pcm_substream *substream) +{ + struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_sgio2audio_pcm_hw; + runtime->private_data = &chip->channel[1]; + return 0; +} + +static int snd_sgio2audio_playback2_open(struct snd_pcm_substream *substream) +{ + struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_sgio2audio_pcm_hw; + runtime->private_data = &chip->channel[2]; + return 0; +} + +/* PCM capture open callback */ +static int snd_sgio2audio_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_sgio2audio_pcm_hw; + runtime->private_data = &chip->channel[0]; + return 0; +} + +/* PCM close callback */ +static int snd_sgio2audio_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->private_data = NULL; + return 0; +} + + +/* hw_params callback */ +static int snd_sgio2audio_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +/* hw_free callback */ +static int snd_sgio2audio_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +/* prepare callback */ +static int snd_sgio2audio_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_sgio2audio_chan *chan = substream->runtime->private_data; + int ch = chan->idx; + unsigned long flags; + + spin_lock_irqsave(&chip->channel[ch].lock, flags); + + /* Setup the pseudo-dma transfer pointers. */ + chip->channel[ch].pos = 0; + chip->channel[ch].size = 0; + chip->channel[ch].substream = substream; + + /* set AD1843 format */ + /* hardware format is always S16_LE */ + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + ad1843_setup_dac(&chip->ad1843, + ch - 1, + runtime->rate, + SNDRV_PCM_FORMAT_S16_LE, + runtime->channels); + break; + case SNDRV_PCM_STREAM_CAPTURE: + ad1843_setup_adc(&chip->ad1843, + runtime->rate, + SNDRV_PCM_FORMAT_S16_LE, + runtime->channels); + break; + } + spin_unlock_irqrestore(&chip->channel[ch].lock, flags); + return 0; +} + +/* trigger callback */ +static int snd_sgio2audio_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* start the PCM engine */ + snd_sgio2audio_dma_start(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + /* stop the PCM engine */ + snd_sgio2audio_dma_stop(substream); + break; + default: + return -EINVAL; + } + return 0; +} + +/* pointer callback */ +static snd_pcm_uframes_t +snd_sgio2audio_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); + struct snd_sgio2audio_chan *chan = substream->runtime->private_data; + + /* get the current hardware pointer */ + return bytes_to_frames(substream->runtime, + chip->channel[chan->idx].pos); +} + +/* operators */ +static const struct snd_pcm_ops snd_sgio2audio_playback1_ops = { + .open = snd_sgio2audio_playback1_open, + .close = snd_sgio2audio_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sgio2audio_pcm_hw_params, + .hw_free = snd_sgio2audio_pcm_hw_free, + .prepare = snd_sgio2audio_pcm_prepare, + .trigger = snd_sgio2audio_pcm_trigger, + .pointer = snd_sgio2audio_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct snd_pcm_ops snd_sgio2audio_playback2_ops = { + .open = snd_sgio2audio_playback2_open, + .close = snd_sgio2audio_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sgio2audio_pcm_hw_params, + .hw_free = snd_sgio2audio_pcm_hw_free, + .prepare = snd_sgio2audio_pcm_prepare, + .trigger = snd_sgio2audio_pcm_trigger, + .pointer = snd_sgio2audio_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct snd_pcm_ops snd_sgio2audio_capture_ops = { + .open = snd_sgio2audio_capture_open, + .close = snd_sgio2audio_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sgio2audio_pcm_hw_params, + .hw_free = snd_sgio2audio_pcm_hw_free, + .prepare = snd_sgio2audio_pcm_prepare, + .trigger = snd_sgio2audio_pcm_trigger, + .pointer = snd_sgio2audio_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +/* + * definitions of capture are omitted here... + */ + +/* create a pcm device */ +static int snd_sgio2audio_new_pcm(struct snd_sgio2audio *chip) +{ + struct snd_pcm *pcm; + int err; + + /* create first pcm device with one outputs and one input */ + err = snd_pcm_new(chip->card, "SGI O2 Audio", 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + strcpy(pcm->name, "SGI O2 DAC1"); + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_sgio2audio_playback1_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_sgio2audio_capture_ops); + + /* create second pcm device with one outputs and no input */ + err = snd_pcm_new(chip->card, "SGI O2 Audio", 1, 1, 0, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + strcpy(pcm->name, "SGI O2 DAC2"); + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_sgio2audio_playback2_ops); + + return 0; +} + +static struct { + int idx; + int irq; + irqreturn_t (*isr)(int, void *); + const char *desc; +} snd_sgio2_isr_table[] = { + { + .idx = 0, + .irq = MACEISA_AUDIO1_DMAT_IRQ, + .isr = snd_sgio2audio_dma_in_isr, + .desc = "Capture DMA Channel 0" + }, { + .idx = 0, + .irq = MACEISA_AUDIO1_OF_IRQ, + .isr = snd_sgio2audio_error_isr, + .desc = "Capture Overflow" + }, { + .idx = 1, + .irq = MACEISA_AUDIO2_DMAT_IRQ, + .isr = snd_sgio2audio_dma_out_isr, + .desc = "Playback DMA Channel 1" + }, { + .idx = 1, + .irq = MACEISA_AUDIO2_MERR_IRQ, + .isr = snd_sgio2audio_error_isr, + .desc = "Memory Error Channel 1" + }, { + .idx = 2, + .irq = MACEISA_AUDIO3_DMAT_IRQ, + .isr = snd_sgio2audio_dma_out_isr, + .desc = "Playback DMA Channel 2" + }, { + .idx = 2, + .irq = MACEISA_AUDIO3_MERR_IRQ, + .isr = snd_sgio2audio_error_isr, + .desc = "Memory Error Channel 2" + } +}; + +/* ALSA driver */ + +static int snd_sgio2audio_free(struct snd_sgio2audio *chip) +{ + int i; + + /* reset interface */ + writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); + udelay(1); + writeq(0, &mace->perif.audio.control); + + /* release IRQ's */ + for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) + free_irq(snd_sgio2_isr_table[i].irq, + &chip->channel[snd_sgio2_isr_table[i].idx]); + + dma_free_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, + chip->ring_base, chip->ring_base_dma); + + /* release card data */ + kfree(chip); + return 0; +} + +static int snd_sgio2audio_dev_free(struct snd_device *device) +{ + struct snd_sgio2audio *chip = device->device_data; + + return snd_sgio2audio_free(chip); +} + +static struct snd_device_ops ops = { + .dev_free = snd_sgio2audio_dev_free, +}; + +static int snd_sgio2audio_create(struct snd_card *card, + struct snd_sgio2audio **rchip) +{ + struct snd_sgio2audio *chip; + int i, err; + + *rchip = NULL; + + /* check if a codec is attached to the interface */ + /* (Audio or Audio/Video board present) */ + if (!(readq(&mace->perif.audio.control) & AUDIO_CONTROL_CODEC_PRESENT)) + return -ENOENT; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->card = card; + + chip->ring_base = dma_alloc_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, + &chip->ring_base_dma, GFP_USER); + if (chip->ring_base == NULL) { + printk(KERN_ERR + "sgio2audio: could not allocate ring buffers\n"); + kfree(chip); + return -ENOMEM; + } + + spin_lock_init(&chip->ad1843_lock); + + /* initialize channels */ + for (i = 0; i < 3; i++) { + spin_lock_init(&chip->channel[i].lock); + chip->channel[i].idx = i; + } + + /* allocate IRQs */ + for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) { + if (request_irq(snd_sgio2_isr_table[i].irq, + snd_sgio2_isr_table[i].isr, + 0, + snd_sgio2_isr_table[i].desc, + &chip->channel[snd_sgio2_isr_table[i].idx])) { + snd_sgio2audio_free(chip); + printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", + snd_sgio2_isr_table[i].irq); + return -EBUSY; + } + } + + /* reset the interface */ + writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); + udelay(1); + writeq(0, &mace->perif.audio.control); + msleep_interruptible(1); /* give time to recover */ + + /* set ring base */ + writeq(chip->ring_base_dma, &mace->perif.ctrl.ringbase); + + /* attach the AD1843 codec */ + chip->ad1843.read = read_ad1843_reg; + chip->ad1843.write = write_ad1843_reg; + chip->ad1843.chip = chip; + + /* initialize the AD1843 codec */ + err = ad1843_init(&chip->ad1843); + if (err < 0) { + snd_sgio2audio_free(chip); + return err; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + snd_sgio2audio_free(chip); + return err; + } + *rchip = chip; + return 0; +} + +static int snd_sgio2audio_probe(struct platform_device *pdev) +{ + struct snd_card *card; + struct snd_sgio2audio *chip; + int err; + + err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + err = snd_sgio2audio_create(card, &chip); + if (err < 0) { + snd_card_free(card); + return err; + } + + err = snd_sgio2audio_new_pcm(chip); + if (err < 0) { + snd_card_free(card); + return err; + } + err = snd_sgio2audio_new_mixer(chip); + if (err < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, "SGI O2 Audio"); + strcpy(card->shortname, "SGI O2 Audio"); + sprintf(card->longname, "%s irq %i-%i", + card->shortname, + MACEISA_AUDIO1_DMAT_IRQ, + MACEISA_AUDIO3_MERR_IRQ); + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + platform_set_drvdata(pdev, card); + return 0; +} + +static int snd_sgio2audio_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + + snd_card_free(card); + return 0; +} + +static struct platform_driver sgio2audio_driver = { + .probe = snd_sgio2audio_probe, + .remove = snd_sgio2audio_remove, + .driver = { + .name = "sgio2audio", + } +}; + +module_platform_driver(sgio2audio_driver); diff --git a/sound/oss/.gitignore b/sound/oss/.gitignore new file mode 100644 index 000000000..12a3920d6 --- /dev/null +++ b/sound/oss/.gitignore @@ -0,0 +1,3 @@ +#Ignore generated files +pss_boot.h +trix_boot.h diff --git a/sound/oss/dmasound/Kconfig b/sound/oss/dmasound/Kconfig new file mode 100644 index 000000000..f456574a9 --- /dev/null +++ b/sound/oss/dmasound/Kconfig @@ -0,0 +1,45 @@ +config DMASOUND_ATARI + tristate "Atari DMA sound support" + depends on ATARI && SOUND + select DMASOUND + help + If you want to use the internal audio of your Atari in Linux, answer + Y to this question. This will provide a Sun-like /dev/audio, + compatible with the Linux/i386 sound system. Otherwise, say N. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you + want). If you want to compile it as a module, say M here and read + . + +config DMASOUND_PAULA + tristate "Amiga DMA sound support" + depends on AMIGA && SOUND + select DMASOUND + help + If you want to use the internal audio of your Amiga in Linux, answer + Y to this question. This will provide a Sun-like /dev/audio, + compatible with the Linux/i386 sound system. Otherwise, say N. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you + want). If you want to compile it as a module, say M here and read + . + +config DMASOUND_Q40 + tristate "Q40 sound support" + depends on Q40 && SOUND + select DMASOUND + help + If you want to use the internal audio of your Q40 in Linux, answer + Y to this question. This will provide a Sun-like /dev/audio, + compatible with the Linux/i386 sound system. Otherwise, say N. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you + want). If you want to compile it as a module, say M here and read + . + +config DMASOUND + tristate + select SOUND_OSS_CORE diff --git a/sound/oss/dmasound/Makefile b/sound/oss/dmasound/Makefile new file mode 100644 index 000000000..3c1531652 --- /dev/null +++ b/sound/oss/dmasound/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the DMA sound driver +# + +obj-$(CONFIG_DMASOUND_ATARI) += dmasound_core.o dmasound_atari.o +obj-$(CONFIG_DMASOUND_PAULA) += dmasound_core.o dmasound_paula.o +obj-$(CONFIG_DMASOUND_Q40) += dmasound_core.o dmasound_q40.o diff --git a/sound/oss/dmasound/dmasound.h b/sound/oss/dmasound/dmasound.h new file mode 100644 index 000000000..c1c52b479 --- /dev/null +++ b/sound/oss/dmasound/dmasound.h @@ -0,0 +1,262 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _dmasound_h_ +/* + * linux/sound/oss/dmasound/dmasound.h + * + * + * Minor numbers for the sound driver. + * + * Unfortunately Creative called the codec chip of SB as a DSP. For this + * reason the /dev/dsp is reserved for digitized audio use. There is a + * device for true DSP processors but it will be called something else. + * In v3.0 it's /dev/sndproc but this could be a temporary solution. + */ +#define _dmasound_h_ + +#include + +#define SND_NDEVS 256 /* Number of supported devices */ +#define SND_DEV_CTL 0 /* Control port /dev/mixer */ +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define SND_DEV_MIDIN 2 /* Raw midi access */ +#define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ +#define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ +#define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ +#define SND_DEV_STATUS 6 /* /dev/sndstat */ +/* #7 not in use now. Was in 2.4. Free for use after v3.0. */ +#define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ +#define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ +#define SND_DEV_PSS SND_DEV_SNDPROC + +/* switch on various prinks */ +#define DEBUG_DMASOUND 1 + +#define MAX_AUDIO_DEV 5 +#define MAX_MIXER_DEV 4 +#define MAX_SYNTH_DEV 3 +#define MAX_MIDI_DEV 6 +#define MAX_TIMER_DEV 3 + +#define MAX_CATCH_RADIUS 10 + +#define le2be16(x) (((x)<<8 & 0xff00) | ((x)>>8 & 0x00ff)) +#define le2be16dbl(x) (((x)<<8 & 0xff00ff00) | ((x)>>8 & 0x00ff00ff)) + +#define IOCTL_IN(arg, ret) \ + do { int error = get_user(ret, (int __user *)(arg)); \ + if (error) return error; \ + } while (0) +#define IOCTL_OUT(arg, ret) ioctl_return((int __user *)(arg), ret) + +static inline int ioctl_return(int __user *addr, int value) +{ + return value < 0 ? value : put_user(value, addr); +} + + + /* + * Configuration + */ + +#undef HAS_8BIT_TABLES + +#if defined(CONFIG_DMASOUND_ATARI) || defined(CONFIG_DMASOUND_ATARI_MODULE) ||\ + defined(CONFIG_DMASOUND_PAULA) || defined(CONFIG_DMASOUND_PAULA_MODULE) ||\ + defined(CONFIG_DMASOUND_Q40) || defined(CONFIG_DMASOUND_Q40_MODULE) +#define HAS_8BIT_TABLES +#define MIN_BUFFERS 4 +#define MIN_BUFSIZE (1<<12) /* in bytes (- where does this come from ?) */ +#define MIN_FRAG_SIZE 8 /* not 100% sure about this */ +#define MAX_BUFSIZE (1<<17) /* Limit for Amiga is 128 kb */ +#define MAX_FRAG_SIZE 15 /* allow *4 for mono-8 => stereo-16 (for multi) */ + +#else /* is pmac and multi is off */ + +#define MIN_BUFFERS 2 +#define MIN_BUFSIZE (1<<8) /* in bytes */ +#define MIN_FRAG_SIZE 8 +#define MAX_BUFSIZE (1<<18) /* this is somewhat arbitrary for pmac */ +#define MAX_FRAG_SIZE 16 /* need to allow *4 for mono-8 => stereo-16 */ +#endif + +#define DEFAULT_N_BUFFERS 4 +#define DEFAULT_BUFF_SIZE (1<<15) + + /* + * Initialization + */ + +extern int dmasound_init(void); +#ifdef MODULE +extern void dmasound_deinit(void); +#else +#define dmasound_deinit() do { } while (0) +#endif + +/* description of the set-up applies to either hard or soft settings */ + +typedef struct { + int format; /* AFMT_* */ + int stereo; /* 0 = mono, 1 = stereo */ + int size; /* 8/16 bit*/ + int speed; /* speed */ +} SETTINGS; + + /* + * Machine definitions + */ + +typedef struct { + const char *name; + const char *name2; + struct module *owner; + void *(*dma_alloc)(unsigned int, gfp_t); + void (*dma_free)(void *, unsigned int); + int (*irqinit)(void); +#ifdef MODULE + void (*irqcleanup)(void); +#endif + void (*init)(void); + void (*silence)(void); + int (*setFormat)(int); + int (*setVolume)(int); + int (*setBass)(int); + int (*setTreble)(int); + int (*setGain)(int); + void (*play)(void); + void (*record)(void); /* optional */ + void (*mixer_init)(void); /* optional */ + int (*mixer_ioctl)(u_int, u_long); /* optional */ + int (*write_sq_setup)(void); /* optional */ + int (*read_sq_setup)(void); /* optional */ + int (*sq_open)(fmode_t); /* optional */ + int (*state_info)(char *, size_t); /* optional */ + void (*abort_read)(void); /* optional */ + int min_dsp_speed; + int max_dsp_speed; + int version ; + int hardware_afmts ; /* OSS says we only return h'ware info */ + /* when queried via SNDCTL_DSP_GETFMTS */ + int capabilities ; /* low-level reply to SNDCTL_DSP_GETCAPS */ + SETTINGS default_hard ; /* open() or init() should set something valid */ + SETTINGS default_soft ; /* you can make it look like old OSS, if you want to */ +} MACHINE; + + /* + * Low level stuff + */ + +typedef struct { + ssize_t (*ct_ulaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_alaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_s8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_u8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_s16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_u16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_s16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_u16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); +} TRANS; + +struct sound_settings { + MACHINE mach; /* machine dependent things */ + SETTINGS hard; /* hardware settings */ + SETTINGS soft; /* software settings */ + SETTINGS dsp; /* /dev/dsp default settings */ + TRANS *trans_write; /* supported translations */ + int volume_left; /* volume (range is machine dependent) */ + int volume_right; + int bass; /* tone (range is machine dependent) */ + int treble; + int gain; + int minDev; /* minor device number currently open */ + spinlock_t lock; +}; + +extern struct sound_settings dmasound; + +#ifdef HAS_8BIT_TABLES +extern char dmasound_ulaw2dma8[]; +extern char dmasound_alaw2dma8[]; +#endif + + /* + * Mid level stuff + */ + +static inline int dmasound_set_volume(int volume) +{ + return dmasound.mach.setVolume(volume); +} + +static inline int dmasound_set_bass(int bass) +{ + return dmasound.mach.setBass ? dmasound.mach.setBass(bass) : 50; +} + +static inline int dmasound_set_treble(int treble) +{ + return dmasound.mach.setTreble ? dmasound.mach.setTreble(treble) : 50; +} + +static inline int dmasound_set_gain(int gain) +{ + return dmasound.mach.setGain ? dmasound.mach.setGain(gain) : 100; +} + + + /* + * Sound queue stuff, the heart of the driver + */ + +struct sound_queue { + /* buffers allocated for this queue */ + int numBufs; /* real limits on what the user can have */ + int bufSize; /* in bytes */ + char **buffers; + + /* current parameters */ + int locked ; /* params cannot be modified when != 0 */ + int user_frags ; /* user requests this many */ + int user_frag_size ; /* of this size */ + int max_count; /* actual # fragments <= numBufs */ + int block_size; /* internal block size in bytes */ + int max_active; /* in-use fragments <= max_count */ + + /* it shouldn't be necessary to declare any of these volatile */ + int front, rear, count; + int rear_size; + /* + * The use of the playing field depends on the hardware + * + * Atari, PMac: The number of frames that are loaded/playing + * + * Amiga: Bit 0 is set: a frame is loaded + * Bit 1 is set: a frame is playing + */ + int active; + wait_queue_head_t action_queue, open_queue, sync_queue; + int non_blocking; + int busy, syncing, xruns, died; +}; + +#define WAKE_UP(queue) (wake_up_interruptible(&queue)) + +extern struct sound_queue dmasound_write_sq; +#define write_sq dmasound_write_sq + +extern int dmasound_catchRadius; +#define catchRadius dmasound_catchRadius + +/* define the value to be put in the byte-swap reg in mac-io + when we want it to swap for us. +*/ +#define BS_VAL 1 + +#define SW_INPUT_VOLUME_SCALE 4 +#define SW_INPUT_VOLUME_DEFAULT (128 / SW_INPUT_VOLUME_SCALE) + +extern int expand_read_bal; /* Balance factor for reading */ +extern uint software_input_volume; /* software implemented recording volume! */ + +#endif /* _dmasound_h_ */ diff --git a/sound/oss/dmasound/dmasound_atari.c b/sound/oss/dmasound/dmasound_atari.c new file mode 100644 index 000000000..a1a2979c0 --- /dev/null +++ b/sound/oss/dmasound/dmasound_atari.c @@ -0,0 +1,1620 @@ +/* + * linux/sound/oss/dmasound/dmasound_atari.c + * + * Atari TT and Falcon DMA Sound Driver + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits + * prior to 28/01/2001 + * + * 28/01/2001 [0.1] Iain Sandoe + * - added versioning + * - put in and populated the hardware_afmts field. + * [0.2] - put in SNDCTL_DSP_GETCAPS value. + * 01/02/2001 [0.3] - put in default hard/soft settings. + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dmasound.h" + +#define DMASOUND_ATARI_REVISION 0 +#define DMASOUND_ATARI_EDITION 3 + +extern void atari_microwire_cmd(int cmd); + +static int is_falcon; +static int write_sq_ignore_int; /* ++TeSche: used for Falcon */ + +static int expand_bal; /* Balance factor for expanding (not volume!) */ +static int expand_data; /* Data for expanding */ + + +/*** Translations ************************************************************/ + + +/* ++TeSche: radically changed for new expanding purposes... + * + * These two routines now deal with copying/expanding/translating the samples + * from user space into our buffer at the right frequency. They take care about + * how much data there's actually to read, how much buffer space there is and + * to convert samples into the right frequency/encoding. They will only work on + * complete samples so it may happen they leave some bytes in the input stream + * if the user didn't write a multiple of the current sample size. They both + * return the number of bytes they've used from both streams so you may detect + * such a situation. Luckily all programs should be able to cope with that. + * + * I think I've optimized anything as far as one can do in plain C, all + * variables should fit in registers and the loops are really short. There's + * one loop for every possible situation. Writing a more generalized and thus + * parameterized loop would only produce slower code. Feel free to optimize + * this in assembler if you like. :) + * + * I think these routines belong here because they're not yet really hardware + * independent, especially the fact that the Falcon can play 16bit samples + * only in stereo is hardcoded in both of them! + * + * ++geert: split in even more functions (one per format) + */ + +static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); + + +/*** Low level stuff *********************************************************/ + + +static void *AtaAlloc(unsigned int size, gfp_t flags); +static void AtaFree(void *, unsigned int size); +static int AtaIrqInit(void); +#ifdef MODULE +static void AtaIrqCleanUp(void); +#endif /* MODULE */ +static int AtaSetBass(int bass); +static int AtaSetTreble(int treble); +static void TTSilence(void); +static void TTInit(void); +static int TTSetFormat(int format); +static int TTSetVolume(int volume); +static int TTSetGain(int gain); +static void FalconSilence(void); +static void FalconInit(void); +static int FalconSetFormat(int format); +static int FalconSetVolume(int volume); +static void AtaPlayNextFrame(int index); +static void AtaPlay(void); +static irqreturn_t AtaInterrupt(int irq, void *dummy); + +/*** Mid level stuff *********************************************************/ + +static void TTMixerInit(void); +static void FalconMixerInit(void); +static int AtaMixerIoctl(u_int cmd, u_long arg); +static int TTMixerIoctl(u_int cmd, u_long arg); +static int FalconMixerIoctl(u_int cmd, u_long arg); +static int AtaWriteSqSetup(void); +static int AtaSqOpen(fmode_t mode); +static int TTStateInfo(char *buffer, size_t space); +static int FalconStateInfo(char *buffer, size_t space); + + +/*** Translations ************************************************************/ + + +static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8 + : dmasound_alaw2dma8; + ssize_t count, used; + u_char *p = &frame[*frameUsed]; + + count = min_t(unsigned long, userCount, frameLeft); + if (dmasound.soft.stereo) + count &= ~1; + used = count; + while (count > 0) { + u_char data; + if (get_user(data, userPtr++)) + return -EFAULT; + *p++ = table[data]; + count--; + } + *frameUsed += used; + return used; +} + + +static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + void *p = &frame[*frameUsed]; + + count = min_t(unsigned long, userCount, frameLeft); + if (dmasound.soft.stereo) + count &= ~1; + used = count; + if (copy_from_user(p, userPtr, count)) + return -EFAULT; + *frameUsed += used; + return used; +} + + +static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft); + used = count; + while (count > 0) { + u_char data; + if (get_user(data, userPtr++)) + return -EFAULT; + *p++ = data ^ 0x80; + count--; + } + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + *p++ = data ^ 0x8080; + count--; + } + } + *frameUsed += used; + return used; +} + + +static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + *p++ = data; + *p++ = data; + count--; + } + *frameUsed += used*2; + } else { + void *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft) & ~3; + used = count; + if (copy_from_user(p, userPtr, count)) + return -EFAULT; + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data ^= 0x8000; + *p++ = data; + *p++ = data; + count--; + } + *frameUsed += used*2; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>2; + used = count*4; + while (count > 0) { + u_int data; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + *p++ = data ^ 0x80008000; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + count = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data); + *p++ = data; + *p++ = data; + count--; + } + *frameUsed += used*2; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>2; + used = count*4; + while (count > 0) { + u_long data; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data); + *p++ = data; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + count = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data) ^ 0x8000; + *p++ = data; + *p++ = data; + } + *frameUsed += used*2; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>2; + used = count; + while (count > 0) { + u_long data; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data) ^ 0x80008000; + *p++ = data; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8 + : dmasound_alaw2dma8; + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + u_char data = expand_data; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (!userCount) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = table[c]; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_data = data; + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 2) { + u_char c; + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = table[c] << 8; + if (get_user(c, userPtr++)) + return -EFAULT; + data |= table[c]; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 2; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + u_char data = expand_data; + while (frameLeft) { + if (bal < 0) { + if (!userCount) + break; + if (get_user(data, userPtr++)) + return -EFAULT; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_data = data; + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 2) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 2; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + u_char data = expand_data; + while (frameLeft) { + if (bal < 0) { + if (!userCount) + break; + if (get_user(data, userPtr++)) + return -EFAULT; + data ^= 0x80; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_data = data; + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 2) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data ^= 0x8080; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 2; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data ^= 0x8000; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data ^= 0x80008000; + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data); + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data); + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data) ^ 0x8000; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data) ^ 0x80008000; + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static TRANS transTTNormal = { + .ct_ulaw = ata_ct_law, + .ct_alaw = ata_ct_law, + .ct_s8 = ata_ct_s8, + .ct_u8 = ata_ct_u8, +}; + +static TRANS transTTExpanding = { + .ct_ulaw = ata_ctx_law, + .ct_alaw = ata_ctx_law, + .ct_s8 = ata_ctx_s8, + .ct_u8 = ata_ctx_u8, +}; + +static TRANS transFalconNormal = { + .ct_ulaw = ata_ct_law, + .ct_alaw = ata_ct_law, + .ct_s8 = ata_ct_s8, + .ct_u8 = ata_ct_u8, + .ct_s16be = ata_ct_s16be, + .ct_u16be = ata_ct_u16be, + .ct_s16le = ata_ct_s16le, + .ct_u16le = ata_ct_u16le +}; + +static TRANS transFalconExpanding = { + .ct_ulaw = ata_ctx_law, + .ct_alaw = ata_ctx_law, + .ct_s8 = ata_ctx_s8, + .ct_u8 = ata_ctx_u8, + .ct_s16be = ata_ctx_s16be, + .ct_u16be = ata_ctx_u16be, + .ct_s16le = ata_ctx_s16le, + .ct_u16le = ata_ctx_u16le, +}; + + +/*** Low level stuff *********************************************************/ + + + +/* + * Atari (TT/Falcon) + */ + +static void *AtaAlloc(unsigned int size, gfp_t flags) +{ + return atari_stram_alloc(size, "dmasound"); +} + +static void AtaFree(void *obj, unsigned int size) +{ + atari_stram_free( obj ); +} + +static int __init AtaIrqInit(void) +{ + /* Set up timer A. Timer A + will receive a signal upon end of playing from the sound + hardware. Furthermore Timer A is able to count events + and will cause an interrupt after a programmed number + of events. So all we need to keep the music playing is + to provide the sound hardware with new data upon + an interrupt from timer A. */ + st_mfp.tim_ct_a = 0; /* ++roman: Stop timer before programming! */ + st_mfp.tim_dt_a = 1; /* Cause interrupt after first event. */ + st_mfp.tim_ct_a = 8; /* Turn on event counting. */ + /* Register interrupt handler. */ + if (request_irq(IRQ_MFP_TIMA, AtaInterrupt, 0, "DMA sound", + AtaInterrupt)) + return 0; + st_mfp.int_en_a |= 0x20; /* Turn interrupt on. */ + st_mfp.int_mk_a |= 0x20; + return 1; +} + +#ifdef MODULE +static void AtaIrqCleanUp(void) +{ + st_mfp.tim_ct_a = 0; /* stop timer */ + st_mfp.int_en_a &= ~0x20; /* turn interrupt off */ + free_irq(IRQ_MFP_TIMA, AtaInterrupt); +} +#endif /* MODULE */ + + +#define TONE_VOXWARE_TO_DB(v) \ + (((v) < 0) ? -12 : ((v) > 100) ? 12 : ((v) - 50) * 6 / 25) +#define TONE_DB_TO_VOXWARE(v) (((v) * 25 + ((v) > 0 ? 5 : -5)) / 6 + 50) + + +static int AtaSetBass(int bass) +{ + dmasound.bass = TONE_VOXWARE_TO_DB(bass); + atari_microwire_cmd(MW_LM1992_BASS(dmasound.bass)); + return TONE_DB_TO_VOXWARE(dmasound.bass); +} + + +static int AtaSetTreble(int treble) +{ + dmasound.treble = TONE_VOXWARE_TO_DB(treble); + atari_microwire_cmd(MW_LM1992_TREBLE(dmasound.treble)); + return TONE_DB_TO_VOXWARE(dmasound.treble); +} + + + +/* + * TT + */ + + +static void TTSilence(void) +{ + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + atari_microwire_cmd(MW_LM1992_PSG_HIGH); /* mix in PSG signal 1:1 */ +} + + +static void TTInit(void) +{ + int mode, i, idx; + const int freq[4] = {50066, 25033, 12517, 6258}; + + /* search a frequency that fits into the allowed error range */ + + idx = -1; + for (i = 0; i < ARRAY_SIZE(freq); i++) + /* this isn't as much useful for a TT than for a Falcon, but + * then it doesn't hurt very much to implement it for a TT too. + */ + if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius) + idx = i; + if (idx > -1) { + dmasound.soft.speed = freq[idx]; + dmasound.trans_write = &transTTNormal; + } else + dmasound.trans_write = &transTTExpanding; + + TTSilence(); + dmasound.hard = dmasound.soft; + + if (dmasound.hard.speed > 50066) { + /* we would need to squeeze the sound, but we won't do that */ + dmasound.hard.speed = 50066; + mode = DMASND_MODE_50KHZ; + dmasound.trans_write = &transTTNormal; + } else if (dmasound.hard.speed > 25033) { + dmasound.hard.speed = 50066; + mode = DMASND_MODE_50KHZ; + } else if (dmasound.hard.speed > 12517) { + dmasound.hard.speed = 25033; + mode = DMASND_MODE_25KHZ; + } else if (dmasound.hard.speed > 6258) { + dmasound.hard.speed = 12517; + mode = DMASND_MODE_12KHZ; + } else { + dmasound.hard.speed = 6258; + mode = DMASND_MODE_6KHZ; + } + + tt_dmasnd.mode = (dmasound.hard.stereo ? + DMASND_MODE_STEREO : DMASND_MODE_MONO) | + DMASND_MODE_8BIT | mode; + + expand_bal = -dmasound.soft.speed; +} + + +static int TTSetFormat(int format) +{ + /* TT sound DMA supports only 8bit modes */ + + switch (format) { + case AFMT_QUERY: + return dmasound.soft.format; + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_S8: + case AFMT_U8: + break; + default: + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = 8; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = 8; + } + TTInit(); + + return format; +} + + +#define VOLUME_VOXWARE_TO_DB(v) \ + (((v) < 0) ? -40 : ((v) > 100) ? 0 : ((v) * 2) / 5 - 40) +#define VOLUME_DB_TO_VOXWARE(v) ((((v) + 40) * 5 + 1) / 2) + + +static int TTSetVolume(int volume) +{ + dmasound.volume_left = VOLUME_VOXWARE_TO_DB(volume & 0xff); + atari_microwire_cmd(MW_LM1992_BALLEFT(dmasound.volume_left)); + dmasound.volume_right = VOLUME_VOXWARE_TO_DB((volume & 0xff00) >> 8); + atari_microwire_cmd(MW_LM1992_BALRIGHT(dmasound.volume_right)); + return VOLUME_DB_TO_VOXWARE(dmasound.volume_left) | + (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8); +} + + +#define GAIN_VOXWARE_TO_DB(v) \ + (((v) < 0) ? -80 : ((v) > 100) ? 0 : ((v) * 4) / 5 - 80) +#define GAIN_DB_TO_VOXWARE(v) ((((v) + 80) * 5 + 1) / 4) + +static int TTSetGain(int gain) +{ + dmasound.gain = GAIN_VOXWARE_TO_DB(gain); + atari_microwire_cmd(MW_LM1992_VOLUME(dmasound.gain)); + return GAIN_DB_TO_VOXWARE(dmasound.gain); +} + + + +/* + * Falcon + */ + + +static void FalconSilence(void) +{ + /* stop playback, set sample rate 50kHz for PSG sound */ + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + tt_dmasnd.mode = DMASND_MODE_50KHZ | DMASND_MODE_STEREO | DMASND_MODE_8BIT; + tt_dmasnd.int_div = 0; /* STE compatible divider */ + tt_dmasnd.int_ctrl = 0x0; + tt_dmasnd.cbar_src = 0x0000; /* no matrix inputs */ + tt_dmasnd.cbar_dst = 0x0000; /* no matrix outputs */ + tt_dmasnd.dac_src = 1; /* connect ADC to DAC, disconnect matrix */ + tt_dmasnd.adc_src = 3; /* ADC Input = PSG */ +} + + +static void FalconInit(void) +{ + int divider, i, idx; + const int freq[8] = {49170, 32780, 24585, 19668, 16390, 12292, 9834, 8195}; + + /* search a frequency that fits into the allowed error range */ + + idx = -1; + for (i = 0; i < ARRAY_SIZE(freq); i++) + /* if we will tolerate 3% error 8000Hz->8195Hz (2.38%) would + * be playable without expanding, but that now a kernel runtime + * option + */ + if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius) + idx = i; + if (idx > -1) { + dmasound.soft.speed = freq[idx]; + dmasound.trans_write = &transFalconNormal; + } else + dmasound.trans_write = &transFalconExpanding; + + FalconSilence(); + dmasound.hard = dmasound.soft; + + if (dmasound.hard.size == 16) { + /* the Falcon can play 16bit samples only in stereo */ + dmasound.hard.stereo = 1; + } + + if (dmasound.hard.speed > 49170) { + /* we would need to squeeze the sound, but we won't do that */ + dmasound.hard.speed = 49170; + divider = 1; + dmasound.trans_write = &transFalconNormal; + } else if (dmasound.hard.speed > 32780) { + dmasound.hard.speed = 49170; + divider = 1; + } else if (dmasound.hard.speed > 24585) { + dmasound.hard.speed = 32780; + divider = 2; + } else if (dmasound.hard.speed > 19668) { + dmasound.hard.speed = 24585; + divider = 3; + } else if (dmasound.hard.speed > 16390) { + dmasound.hard.speed = 19668; + divider = 4; + } else if (dmasound.hard.speed > 12292) { + dmasound.hard.speed = 16390; + divider = 5; + } else if (dmasound.hard.speed > 9834) { + dmasound.hard.speed = 12292; + divider = 7; + } else if (dmasound.hard.speed > 8195) { + dmasound.hard.speed = 9834; + divider = 9; + } else { + dmasound.hard.speed = 8195; + divider = 11; + } + tt_dmasnd.int_div = divider; + + /* Setup Falcon sound DMA for playback */ + tt_dmasnd.int_ctrl = 0x4; /* Timer A int at play end */ + tt_dmasnd.track_select = 0x0; /* play 1 track, track 1 */ + tt_dmasnd.cbar_src = 0x0001; /* DMA(25MHz) --> DAC */ + tt_dmasnd.cbar_dst = 0x0000; + tt_dmasnd.rec_track_select = 0; + tt_dmasnd.dac_src = 2; /* connect matrix to DAC */ + tt_dmasnd.adc_src = 0; /* ADC Input = Mic */ + + tt_dmasnd.mode = (dmasound.hard.stereo ? + DMASND_MODE_STEREO : DMASND_MODE_MONO) | + ((dmasound.hard.size == 8) ? + DMASND_MODE_8BIT : DMASND_MODE_16BIT) | + DMASND_MODE_6KHZ; + + expand_bal = -dmasound.soft.speed; +} + + +static int FalconSetFormat(int format) +{ + int size; + /* Falcon sound DMA supports 8bit and 16bit modes */ + + switch (format) { + case AFMT_QUERY: + return dmasound.soft.format; + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_U8: + case AFMT_S8: + size = 8; + break; + case AFMT_S16_BE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_U16_LE: + size = 16; + break; + default: /* :-) */ + size = 8; + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = size; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = dmasound.soft.size; + } + + FalconInit(); + + return format; +} + + +/* This is for the Falcon output *attenuation* in 1.5dB steps, + * i.e. output level from 0 to -22.5dB in -1.5dB steps. + */ +#define VOLUME_VOXWARE_TO_ATT(v) \ + ((v) < 0 ? 15 : (v) > 100 ? 0 : 15 - (v) * 3 / 20) +#define VOLUME_ATT_TO_VOXWARE(v) (100 - (v) * 20 / 3) + + +static int FalconSetVolume(int volume) +{ + dmasound.volume_left = VOLUME_VOXWARE_TO_ATT(volume & 0xff); + dmasound.volume_right = VOLUME_VOXWARE_TO_ATT((volume & 0xff00) >> 8); + tt_dmasnd.output_atten = dmasound.volume_left << 8 | dmasound.volume_right << 4; + return VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) | + VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8; +} + + +static void AtaPlayNextFrame(int index) +{ + char *start, *end; + + /* used by AtaPlay() if all doubts whether there really is something + * to be played are already wiped out. + */ + start = write_sq.buffers[write_sq.front]; + end = start+((write_sq.count == index) ? write_sq.rear_size + : write_sq.block_size); + /* end might not be a legal virtual address. */ + DMASNDSetEnd(virt_to_phys(end - 1) + 1); + DMASNDSetBase(virt_to_phys(start)); + /* Since only an even number of samples per frame can + be played, we might lose one byte here. (TO DO) */ + write_sq.front = (write_sq.front+1) % write_sq.max_count; + write_sq.active++; + tt_dmasnd.ctrl = DMASND_CTRL_ON | DMASND_CTRL_REPEAT; +} + + +static void AtaPlay(void) +{ + /* ++TeSche: Note that write_sq.active is no longer just a flag but + * holds the number of frames the DMA is currently programmed for + * instead, may be 0, 1 (currently being played) or 2 (pre-programmed). + * + * Changes done to write_sq.count and write_sq.active are a bit more + * subtle again so now I must admit I also prefer disabling the irq + * here rather than considering all possible situations. But the point + * is that disabling the irq doesn't have any bad influence on this + * version of the driver as we benefit from having pre-programmed the + * DMA wherever possible: There's no need to reload the DMA at the + * exact time of an interrupt but only at some time while the + * pre-programmed frame is playing! + */ + atari_disable_irq(IRQ_MFP_TIMA); + + if (write_sq.active == 2 || /* DMA is 'full' */ + write_sq.count <= 0) { /* nothing to do */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + + if (write_sq.active == 0) { + /* looks like there's nothing 'in' the DMA yet, so try + * to put two frames into it (at least one is available). + */ + if (write_sq.count == 1 && + write_sq.rear_size < write_sq.block_size && + !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + AtaPlayNextFrame(1); + if (write_sq.count == 1) { + /* no more frames */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + if (write_sq.count == 2 && + write_sq.rear_size < write_sq.block_size && + !write_sq.syncing) { + /* hmmm, there were two frames, but the second + * one is not yet filled and we're not syncing? + */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + AtaPlayNextFrame(2); + } else { + /* there's already a frame being played so we may only stuff + * one new into the DMA, but even if this may be the last + * frame existing the previous one is still on write_sq.count. + */ + if (write_sq.count == 2 && + write_sq.rear_size < write_sq.block_size && + !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + AtaPlayNextFrame(2); + } + atari_enable_irq(IRQ_MFP_TIMA); +} + + +static irqreturn_t AtaInterrupt(int irq, void *dummy) +{ +#if 0 + /* ++TeSche: if you should want to test this... */ + static int cnt; + if (write_sq.active == 2) + if (++cnt == 10) { + /* simulate losing an interrupt */ + cnt = 0; + return IRQ_HANDLED; + } +#endif + spin_lock(&dmasound.lock); + if (write_sq_ignore_int && is_falcon) { + /* ++TeSche: Falcon only: ignore first irq because it comes + * immediately after starting a frame. after that, irqs come + * (almost) like on the TT. + */ + write_sq_ignore_int = 0; + goto out; + } + + if (!write_sq.active) { + /* playing was interrupted and sq_reset() has already cleared + * the sq variables, so better don't do anything here. + */ + WAKE_UP(write_sq.sync_queue); + goto out; + } + + /* Probably ;) one frame is finished. Well, in fact it may be that a + * pre-programmed one is also finished because there has been a long + * delay in interrupt delivery and we've completely lost one, but + * there's no way to detect such a situation. In such a case the last + * frame will be played more than once and the situation will recover + * as soon as the irq gets through. + */ + write_sq.count--; + write_sq.active--; + + if (!write_sq.active) { + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + write_sq_ignore_int = 1; + } + + WAKE_UP(write_sq.action_queue); + /* At least one block of the queue is free now + so wake up a writing process blocked because + of a full queue. */ + + if ((write_sq.active != 1) || (write_sq.count != 1)) + /* We must be a bit carefully here: write_sq.count indicates the + * number of buffers used and not the number of frames to be + * played. If write_sq.count==1 and write_sq.active==1 that + * means the only remaining frame was already programmed + * earlier (and is currently running) so we mustn't call + * AtaPlay() here, otherwise we'll play one frame too much. + */ + AtaPlay(); + + if (!write_sq.active) WAKE_UP(write_sq.sync_queue); + /* We are not playing after AtaPlay(), so there + is nothing to play any more. Wake up a process + waiting for audio output to drain. */ +out: + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} + + +/*** Mid level stuff *********************************************************/ + + +/* + * /dev/mixer abstraction + */ + +#define RECLEVEL_VOXWARE_TO_GAIN(v) \ + ((v) < 0 ? 0 : (v) > 100 ? 15 : (v) * 3 / 20) +#define RECLEVEL_GAIN_TO_VOXWARE(v) (((v) * 20 + 2) / 3) + + +static void __init TTMixerInit(void) +{ + atari_microwire_cmd(MW_LM1992_VOLUME(0)); + dmasound.volume_left = 0; + atari_microwire_cmd(MW_LM1992_BALLEFT(0)); + dmasound.volume_right = 0; + atari_microwire_cmd(MW_LM1992_BALRIGHT(0)); + atari_microwire_cmd(MW_LM1992_TREBLE(0)); + atari_microwire_cmd(MW_LM1992_BASS(0)); +} + +static void __init FalconMixerInit(void) +{ + dmasound.volume_left = (tt_dmasnd.output_atten & 0xf00) >> 8; + dmasound.volume_right = (tt_dmasnd.output_atten & 0xf0) >> 4; +} + +static int AtaMixerIoctl(u_int cmd, u_long arg) +{ + int data; + unsigned long flags; + switch (cmd) { + case SOUND_MIXER_READ_SPEAKER: + if (is_falcon || MACH_IS_TT) { + int porta; + spin_lock_irqsave(&dmasound.lock, flags); + sound_ym.rd_data_reg_sel = 14; + porta = sound_ym.rd_data_reg_sel; + spin_unlock_irqrestore(&dmasound.lock, flags); + return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100); + } + break; + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_volume(data)); + case SOUND_MIXER_WRITE_SPEAKER: + if (is_falcon || MACH_IS_TT) { + int porta; + IOCTL_IN(arg, data); + spin_lock_irqsave(&dmasound.lock, flags); + sound_ym.rd_data_reg_sel = 14; + porta = (sound_ym.rd_data_reg_sel & ~0x40) | + (data < 50 ? 0x40 : 0); + sound_ym.wd_data = porta; + spin_unlock_irqrestore(&dmasound.lock, flags); + return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100); + } + } + return -EINVAL; +} + + +static int TTMixerIoctl(u_int cmd, u_long arg) +{ + int data; + switch (cmd) { + case SOUND_MIXER_READ_RECMASK: + return IOCTL_OUT(arg, 0); + case SOUND_MIXER_READ_DEVMASK: + return IOCTL_OUT(arg, + SOUND_MASK_VOLUME | SOUND_MASK_TREBLE | SOUND_MASK_BASS | + (MACH_IS_TT ? SOUND_MASK_SPEAKER : 0)); + case SOUND_MIXER_READ_STEREODEVS: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME); + case SOUND_MIXER_READ_VOLUME: + return IOCTL_OUT(arg, + VOLUME_DB_TO_VOXWARE(dmasound.volume_left) | + (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8)); + case SOUND_MIXER_READ_BASS: + return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.bass)); + case SOUND_MIXER_READ_TREBLE: + return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.treble)); + case SOUND_MIXER_READ_OGAIN: + return IOCTL_OUT(arg, GAIN_DB_TO_VOXWARE(dmasound.gain)); + case SOUND_MIXER_WRITE_BASS: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_bass(data)); + case SOUND_MIXER_WRITE_TREBLE: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_treble(data)); + case SOUND_MIXER_WRITE_OGAIN: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_gain(data)); + } + return AtaMixerIoctl(cmd, arg); +} + +static int FalconMixerIoctl(u_int cmd, u_long arg) +{ + int data; + switch (cmd) { + case SOUND_MIXER_READ_RECMASK: + return IOCTL_OUT(arg, SOUND_MASK_MIC); + case SOUND_MIXER_READ_DEVMASK: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC | SOUND_MASK_SPEAKER); + case SOUND_MIXER_READ_STEREODEVS: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC); + case SOUND_MIXER_READ_VOLUME: + return IOCTL_OUT(arg, + VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) | + VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8); + case SOUND_MIXER_READ_CAPS: + return IOCTL_OUT(arg, SOUND_CAP_EXCL_INPUT); + case SOUND_MIXER_WRITE_MIC: + IOCTL_IN(arg, data); + tt_dmasnd.input_gain = + RECLEVEL_VOXWARE_TO_GAIN(data & 0xff) << 4 | + RECLEVEL_VOXWARE_TO_GAIN(data >> 8 & 0xff); + /* fall thru, return set value */ + case SOUND_MIXER_READ_MIC: + return IOCTL_OUT(arg, + RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain >> 4 & 0xf) | + RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain & 0xf) << 8); + } + return AtaMixerIoctl(cmd, arg); +} + +static int AtaWriteSqSetup(void) +{ + write_sq_ignore_int = 0; + return 0 ; +} + +static int AtaSqOpen(fmode_t mode) +{ + write_sq_ignore_int = 1; + return 0 ; +} + +static int TTStateInfo(char *buffer, size_t space) +{ + int len = 0; + len += sprintf(buffer+len, "\tvol left %ddB [-40... 0]\n", + dmasound.volume_left); + len += sprintf(buffer+len, "\tvol right %ddB [-40... 0]\n", + dmasound.volume_right); + len += sprintf(buffer+len, "\tbass %ddB [-12...+12]\n", + dmasound.bass); + len += sprintf(buffer+len, "\ttreble %ddB [-12...+12]\n", + dmasound.treble); + if (len >= space) { + printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ; + len = space ; + } + return len; +} + +static int FalconStateInfo(char *buffer, size_t space) +{ + int len = 0; + len += sprintf(buffer+len, "\tvol left %ddB [-22.5 ... 0]\n", + dmasound.volume_left); + len += sprintf(buffer+len, "\tvol right %ddB [-22.5 ... 0]\n", + dmasound.volume_right); + if (len >= space) { + printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ; + len = space ; + } + return len; +} + + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard_falcon = { + .format = AFMT_S8, + .stereo = 0, + .size = 8, + .speed = 8195 +} ; + +static SETTINGS def_hard_tt = { + .format = AFMT_S8, + .stereo = 0, + .size = 8, + .speed = 12517 +} ; + +static SETTINGS def_soft = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static __initdata MACHINE machTT = { + .name = "Atari", + .name2 = "TT", + .owner = THIS_MODULE, + .dma_alloc = AtaAlloc, + .dma_free = AtaFree, + .irqinit = AtaIrqInit, +#ifdef MODULE + .irqcleanup = AtaIrqCleanUp, +#endif /* MODULE */ + .init = TTInit, + .silence = TTSilence, + .setFormat = TTSetFormat, + .setVolume = TTSetVolume, + .setBass = AtaSetBass, + .setTreble = AtaSetTreble, + .setGain = TTSetGain, + .play = AtaPlay, + .mixer_init = TTMixerInit, + .mixer_ioctl = TTMixerIoctl, + .write_sq_setup = AtaWriteSqSetup, + .sq_open = AtaSqOpen, + .state_info = TTStateInfo, + .min_dsp_speed = 6258, + .version = ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION), + .hardware_afmts = AFMT_S8, /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + +static __initdata MACHINE machFalcon = { + .name = "Atari", + .name2 = "FALCON", + .dma_alloc = AtaAlloc, + .dma_free = AtaFree, + .irqinit = AtaIrqInit, +#ifdef MODULE + .irqcleanup = AtaIrqCleanUp, +#endif /* MODULE */ + .init = FalconInit, + .silence = FalconSilence, + .setFormat = FalconSetFormat, + .setVolume = FalconSetVolume, + .setBass = AtaSetBass, + .setTreble = AtaSetTreble, + .play = AtaPlay, + .mixer_init = FalconMixerInit, + .mixer_ioctl = FalconMixerIoctl, + .write_sq_setup = AtaWriteSqSetup, + .sq_open = AtaSqOpen, + .state_info = FalconStateInfo, + .min_dsp_speed = 8195, + .version = ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION), + .hardware_afmts = (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + + +/*** Config & Setup **********************************************************/ + + +static int __init dmasound_atari_init(void) +{ + if (MACH_IS_ATARI && ATARIHW_PRESENT(PCM_8BIT)) { + if (ATARIHW_PRESENT(CODEC)) { + dmasound.mach = machFalcon; + dmasound.mach.default_soft = def_soft ; + dmasound.mach.default_hard = def_hard_falcon ; + is_falcon = 1; + } else if (ATARIHW_PRESENT(MICROWIRE)) { + dmasound.mach = machTT; + dmasound.mach.default_soft = def_soft ; + dmasound.mach.default_hard = def_hard_tt ; + is_falcon = 0; + } else + return -ENODEV; + if ((st_mfp.int_en_a & st_mfp.int_mk_a & 0x20) == 0) + return dmasound_init(); + else { + printk("DMA sound driver: Timer A interrupt already in use\n"); + return -EBUSY; + } + } + return -ENODEV; +} + +static void __exit dmasound_atari_cleanup(void) +{ + dmasound_deinit(); +} + +module_init(dmasound_atari_init); +module_exit(dmasound_atari_cleanup); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/dmasound/dmasound_core.c b/sound/oss/dmasound/dmasound_core.c new file mode 100644 index 000000000..fc9bcd47d --- /dev/null +++ b/sound/oss/dmasound/dmasound_core.c @@ -0,0 +1,1600 @@ +/* + * linux/sound/oss/dmasound/dmasound_core.c + * + * + * OSS/Free compatible Atari TT/Falcon and Amiga DMA sound driver for + * Linux/m68k + * Extended to support Power Macintosh for Linux/ppc by Paul Mackerras + * + * (c) 1995 by Michael Schlueter & Michael Marte + * + * Michael Schlueter (michael@duck.syd.de) did the basic structure of the VFS + * interface and the u-law to signed byte conversion. + * + * Michael Marte (marte@informatik.uni-muenchen.de) did the sound queue, + * /dev/mixer, /dev/sndstat and complemented the VFS interface. He would like + * to thank: + * - Michael Schlueter for initial ideas and documentation on the MFP and + * the DMA sound hardware. + * - Therapy? for their CD 'Troublegum' which really made me rock. + * + * /dev/sndstat is based on code by Hannu Savolainen, the author of the + * VoxWare family of drivers. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * History: + * + * 1995/8/25 First release + * + * 1995/9/02 Roman Hodek: + * - Fixed atari_stram_alloc() call, the timer + * programming and several race conditions + * 1995/9/14 Roman Hodek: + * - After some discussion with Michael Schlueter, + * revised the interrupt disabling + * - Slightly speeded up U8->S8 translation by using + * long operations where possible + * - Added 4:3 interpolation for /dev/audio + * + * 1995/9/20 Torsten Scherer: + * - Fixed a bug in sq_write and changed /dev/audio + * converting to play at 12517Hz instead of 6258Hz. + * + * 1995/9/23 Torsten Scherer: + * - Changed sq_interrupt() and sq_play() to pre-program + * the DMA for another frame while there's still one + * running. This allows the IRQ response to be + * arbitrarily delayed and playing will still continue. + * + * 1995/10/14 Guenther Kelleter, Torsten Scherer: + * - Better support for Falcon audio (the Falcon doesn't + * raise an IRQ at the end of a frame, but at the + * beginning instead!). uses 'if (codec_dma)' in lots + * of places to simply switch between Falcon and TT + * code. + * + * 1995/11/06 Torsten Scherer: + * - Started introducing a hardware abstraction scheme + * (may perhaps also serve for Amigas?) + * - Can now play samples at almost all frequencies by + * means of a more generalized expand routine + * - Takes a good deal of care to cut data only at + * sample sizes + * - Buffer size is now a kernel runtime option + * - Implemented fsync() & several minor improvements + * Guenther Kelleter: + * - Useful hints and bug fixes + * - Cross-checked it for Falcons + * + * 1996/3/9 Geert Uytterhoeven: + * - Support added for Amiga, A-law, 16-bit little + * endian. + * - Unification to drivers/sound/dmasound.c. + * + * 1996/4/6 Martin Mitchell: + * - Updated to 1.3 kernel. + * + * 1996/6/13 Topi Kanerva: + * - Fixed things that were broken (mainly the amiga + * 14-bit routines) + * - /dev/sndstat shows now the real hardware frequency + * - The lowpass filter is disabled by default now + * + * 1996/9/25 Geert Uytterhoeven: + * - Modularization + * + * 1998/6/10 Andreas Schwab: + * - Converted to use sound_core + * + * 1999/12/28 Richard Zidlicky: + * - Added support for Q40 + * + * 2000/2/27 Geert Uytterhoeven: + * - Clean up and split the code into 4 parts: + * o dmasound_core: machine-independent code + * o dmasound_atari: Atari TT and Falcon support + * o dmasound_awacs: Apple PowerMac support + * o dmasound_paula: Amiga support + * + * 2000/3/25 Geert Uytterhoeven: + * - Integration of dmasound_q40 + * - Small clean ups + * + * 2001/01/26 [1.0] Iain Sandoe + * - make /dev/sndstat show revision & edition info. + * - since dmasound.mach.sq_setup() can fail on pmac + * its type has been changed to int and the returns + * are checked. + * [1.1] - stop missing translations from being called. + * 2001/02/08 [1.2] - remove unused translation tables & move machine- + * specific tables to low-level. + * - return correct info. for SNDCTL_DSP_GETFMTS. + * [1.3] - implement SNDCTL_DSP_GETCAPS fully. + * [1.4] - make /dev/sndstat text length usage deterministic. + * - make /dev/sndstat call to low-level + * dmasound.mach.state_info() pass max space to ll driver. + * - tidy startup banners and output info. + * [1.5] - tidy up a little (removed some unused #defines in + * dmasound.h) + * - fix up HAS_RECORD conditionalisation. + * - add record code in places it is missing... + * - change buf-sizes to bytes to allow < 1kb for pmac + * if user param entry is < 256 the value is taken to + * be in kb > 256 is taken to be in bytes. + * - make default buff/frag params conditional on + * machine to allow smaller values for pmac. + * - made the ioctls, read & write comply with the OSS + * rules on setting params. + * - added parsing of _setup() params for record. + * 2001/04/04 [1.6] - fix bug where sample rates higher than maximum were + * being reported as OK. + * - fix open() to return -EBUSY as per OSS doc. when + * audio is in use - this is independent of O_NOBLOCK. + * - fix bug where SNDCTL_DSP_POST was blocking. + */ + + /* Record capability notes 30/01/2001: + * At present these observations apply only to pmac LL driver (the only one + * that can do record, at present). However, if other LL drivers for machines + * with record are added they may apply. + * + * The fragment parameters for the record and play channels are separate. + * However, if the driver is opened O_RDWR there is no way (in the current OSS + * API) to specify their values independently for the record and playback + * channels. Since the only common factor between the input & output is the + * sample rate (on pmac) it should be possible to open /dev/dspX O_WRONLY and + * /dev/dspY O_RDONLY. The input & output channels could then have different + * characteristics (other than the first that sets sample rate claiming the + * right to set it for ever). As it stands, the format, channels, number of + * bits & sample rate are assumed to be common. In the future perhaps these + * should be the responsibility of the LL driver - and then if a card really + * does not share items between record & playback they can be specified + * separately. +*/ + +/* Thread-safeness of shared_resources notes: 31/01/2001 + * If the user opens O_RDWR and then splits record & play between two threads + * both of which inherit the fd - and then starts changing things from both + * - we will have difficulty telling. + * + * It's bad application coding - but ... + * TODO: think about how to sort this out... without bogging everything down in + * semaphores. + * + * Similarly, the OSS spec says "all changes to parameters must be between + * open() and the first read() or write(). - and a bit later on (by + * implication) "between SNDCTL_DSP_RESET and the first read() or write() after + * it". If the app is multi-threaded and this rule is broken between threads + * we will have trouble spotting it - and the fault will be rather obscure :-( + * + * We will try and put out at least a kmsg if we see it happen... but I think + * it will be quite hard to trap it with an -EXXX return... because we can't + * see the fault until after the damage is done. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dmasound.h" + +#define DMASOUND_CORE_REVISION 1 +#define DMASOUND_CORE_EDITION 6 + + /* + * Declarations + */ + +static DEFINE_MUTEX(dmasound_core_mutex); +int dmasound_catchRadius = 0; +module_param(dmasound_catchRadius, int, 0); + +static unsigned int numWriteBufs = DEFAULT_N_BUFFERS; +module_param(numWriteBufs, int, 0); +static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ; /* in bytes */ +module_param(writeBufSize, int, 0); + +MODULE_LICENSE("GPL"); + +#ifdef MODULE +static int sq_unit = -1; +static int mixer_unit = -1; +static int state_unit = -1; +static int irq_installed; +#endif /* MODULE */ + +/* control over who can modify resources shared between play/record */ +static fmode_t shared_resource_owner; +static int shared_resources_initialised; + + /* + * Mid level stuff + */ + +struct sound_settings dmasound = { + .lock = __SPIN_LOCK_UNLOCKED(dmasound.lock) +}; + +static inline void sound_silence(void) +{ + dmasound.mach.silence(); /* _MUST_ stop DMA */ +} + +static inline int sound_set_format(int format) +{ + return dmasound.mach.setFormat(format); +} + + +static int sound_set_speed(int speed) +{ + if (speed < 0) + return dmasound.soft.speed; + + /* trap out-of-range speed settings. + at present we allow (arbitrarily) low rates - using soft + up-conversion - but we can't allow > max because there is + no soft down-conversion. + */ + if (dmasound.mach.max_dsp_speed && + (speed > dmasound.mach.max_dsp_speed)) + speed = dmasound.mach.max_dsp_speed ; + + dmasound.soft.speed = speed; + + if (dmasound.minDev == SND_DEV_DSP) + dmasound.dsp.speed = dmasound.soft.speed; + + return dmasound.soft.speed; +} + +static int sound_set_stereo(int stereo) +{ + if (stereo < 0) + return dmasound.soft.stereo; + + stereo = !!stereo; /* should be 0 or 1 now */ + + dmasound.soft.stereo = stereo; + if (dmasound.minDev == SND_DEV_DSP) + dmasound.dsp.stereo = stereo; + + return stereo; +} + +static ssize_t sound_copy_translate(TRANS *trans, const u_char __user *userPtr, + size_t userCount, u_char frame[], + ssize_t *frameUsed, ssize_t frameLeft) +{ + ssize_t (*ct_func)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + + switch (dmasound.soft.format) { + case AFMT_MU_LAW: + ct_func = trans->ct_ulaw; + break; + case AFMT_A_LAW: + ct_func = trans->ct_alaw; + break; + case AFMT_S8: + ct_func = trans->ct_s8; + break; + case AFMT_U8: + ct_func = trans->ct_u8; + break; + case AFMT_S16_BE: + ct_func = trans->ct_s16be; + break; + case AFMT_U16_BE: + ct_func = trans->ct_u16be; + break; + case AFMT_S16_LE: + ct_func = trans->ct_s16le; + break; + case AFMT_U16_LE: + ct_func = trans->ct_u16le; + break; + default: + return 0; + } + /* if the user has requested a non-existent translation don't try + to call it but just return 0 bytes moved + */ + if (ct_func) + return ct_func(userPtr, userCount, frame, frameUsed, frameLeft); + return 0; +} + + /* + * /dev/mixer abstraction + */ + +static struct { + int busy; + int modify_counter; +} mixer; + +static int mixer_open(struct inode *inode, struct file *file) +{ + mutex_lock(&dmasound_core_mutex); + if (!try_module_get(dmasound.mach.owner)) { + mutex_unlock(&dmasound_core_mutex); + return -ENODEV; + } + mixer.busy = 1; + mutex_unlock(&dmasound_core_mutex); + return 0; +} + +static int mixer_release(struct inode *inode, struct file *file) +{ + mutex_lock(&dmasound_core_mutex); + mixer.busy = 0; + module_put(dmasound.mach.owner); + mutex_unlock(&dmasound_core_mutex); + return 0; +} + +static int mixer_ioctl(struct file *file, u_int cmd, u_long arg) +{ + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + mixer.modify_counter++; + switch (cmd) { + case OSS_GETVERSION: + return IOCTL_OUT(arg, SOUND_VERSION); + case SOUND_MIXER_INFO: + { + mixer_info info; + memset(&info, 0, sizeof(info)); + strlcpy(info.id, dmasound.mach.name2, sizeof(info.id)); + strlcpy(info.name, dmasound.mach.name2, sizeof(info.name)); + info.modify_counter = mixer.modify_counter; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + } + if (dmasound.mach.mixer_ioctl) + return dmasound.mach.mixer_ioctl(cmd, arg); + return -EINVAL; +} + +static long mixer_unlocked_ioctl(struct file *file, u_int cmd, u_long arg) +{ + int ret; + + mutex_lock(&dmasound_core_mutex); + ret = mixer_ioctl(file, cmd, arg); + mutex_unlock(&dmasound_core_mutex); + + return ret; +} + +static const struct file_operations mixer_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = mixer_unlocked_ioctl, + .open = mixer_open, + .release = mixer_release, +}; + +static void mixer_init(void) +{ +#ifndef MODULE + int mixer_unit; +#endif + mixer_unit = register_sound_mixer(&mixer_fops, -1); + if (mixer_unit < 0) + return; + + mixer.busy = 0; + dmasound.treble = 0; + dmasound.bass = 0; + if (dmasound.mach.mixer_init) + dmasound.mach.mixer_init(); +} + + + /* + * Sound queue stuff, the heart of the driver + */ + +struct sound_queue dmasound_write_sq; +static void sq_reset_output(void) ; + +static int sq_allocate_buffers(struct sound_queue *sq, int num, int size) +{ + int i; + + if (sq->buffers) + return 0; + sq->numBufs = num; + sq->bufSize = size; + sq->buffers = kmalloc_array (num, sizeof(char *), GFP_KERNEL); + if (!sq->buffers) + return -ENOMEM; + for (i = 0; i < num; i++) { + sq->buffers[i] = dmasound.mach.dma_alloc(size, GFP_KERNEL); + if (!sq->buffers[i]) { + while (i--) + dmasound.mach.dma_free(sq->buffers[i], size); + kfree(sq->buffers); + sq->buffers = NULL; + return -ENOMEM; + } + } + return 0; +} + +static void sq_release_buffers(struct sound_queue *sq) +{ + int i; + + if (sq->buffers) { + for (i = 0; i < sq->numBufs; i++) + dmasound.mach.dma_free(sq->buffers[i], sq->bufSize); + kfree(sq->buffers); + sq->buffers = NULL; + } +} + + +static int sq_setup(struct sound_queue *sq) +{ + int (*setup_func)(void) = NULL; + int hard_frame ; + + if (sq->locked) { /* are we already set? - and not changeable */ +#ifdef DEBUG_DMASOUND +printk("dmasound_core: tried to sq_setup a locked queue\n") ; +#endif + return -EINVAL ; + } + sq->locked = 1 ; /* don't think we have a race prob. here _check_ */ + + /* make sure that the parameters are set up + This should have been done already... + */ + + dmasound.mach.init(); + + /* OK. If the user has set fragment parameters explicitly, then we + should leave them alone... as long as they are valid. + Invalid user fragment params can occur if we allow the whole buffer + to be used when the user requests the fragments sizes (with no soft + x-lation) and then the user subsequently sets a soft x-lation that + requires increased internal buffering. + + Othwerwise (if the user did not set them) OSS says that we should + select frag params on the basis of 0.5 s output & 0.1 s input + latency. (TODO. For now we will copy in the defaults.) + */ + + if (sq->user_frags <= 0) { + sq->max_count = sq->numBufs ; + sq->max_active = sq->numBufs ; + sq->block_size = sq->bufSize; + /* set up the user info */ + sq->user_frags = sq->numBufs ; + sq->user_frag_size = sq->bufSize ; + sq->user_frag_size *= + (dmasound.soft.size * (dmasound.soft.stereo+1) ) ; + sq->user_frag_size /= + (dmasound.hard.size * (dmasound.hard.stereo+1) ) ; + } else { + /* work out requested block size */ + sq->block_size = sq->user_frag_size ; + sq->block_size *= + (dmasound.hard.size * (dmasound.hard.stereo+1) ) ; + sq->block_size /= + (dmasound.soft.size * (dmasound.soft.stereo+1) ) ; + /* the user wants to write frag-size chunks */ + sq->block_size *= dmasound.hard.speed ; + sq->block_size /= dmasound.soft.speed ; + /* this only works for size values which are powers of 2 */ + hard_frame = + (dmasound.hard.size * (dmasound.hard.stereo+1))/8 ; + sq->block_size += (hard_frame - 1) ; + sq->block_size &= ~(hard_frame - 1) ; /* make sure we are aligned */ + /* let's just check for obvious mistakes */ + if ( sq->block_size <= 0 || sq->block_size > sq->bufSize) { +#ifdef DEBUG_DMASOUND +printk("dmasound_core: invalid frag size (user set %d)\n", sq->user_frag_size) ; +#endif + sq->block_size = sq->bufSize ; + } + if ( sq->user_frags <= sq->numBufs ) { + sq->max_count = sq->user_frags ; + /* if user has set max_active - then use it */ + sq->max_active = (sq->max_active <= sq->max_count) ? + sq->max_active : sq->max_count ; + } else { +#ifdef DEBUG_DMASOUND +printk("dmasound_core: invalid frag count (user set %d)\n", sq->user_frags) ; +#endif + sq->max_count = + sq->max_active = sq->numBufs ; + } + } + sq->front = sq->count = sq->rear_size = 0; + sq->syncing = 0; + sq->active = 0; + + if (sq == &write_sq) { + sq->rear = -1; + setup_func = dmasound.mach.write_sq_setup; + } + if (setup_func) + return setup_func(); + return 0 ; +} + +static inline void sq_play(void) +{ + dmasound.mach.play(); +} + +static ssize_t sq_write(struct file *file, const char __user *src, size_t uLeft, + loff_t *ppos) +{ + ssize_t uWritten = 0; + u_char *dest; + ssize_t uUsed = 0, bUsed, bLeft; + unsigned long flags ; + + /* ++TeSche: Is something like this necessary? + * Hey, that's an honest question! Or does any other part of the + * filesystem already checks this situation? I really don't know. + */ + if (uLeft == 0) + return 0; + + /* implement any changes we have made to the soft/hard params. + this is not satisfactory really, all we have done up to now is to + say what we would like - there hasn't been any real checking of capability + */ + + if (shared_resources_initialised == 0) { + dmasound.mach.init() ; + shared_resources_initialised = 1 ; + } + + /* set up the sq if it is not already done. This may seem a dumb place + to do it - but it is what OSS requires. It means that write() can + return memory allocation errors. To avoid this possibility use the + GETBLKSIZE or GETOSPACE ioctls (after you've fiddled with all the + params you want to change) - these ioctls also force the setup. + */ + + if (write_sq.locked == 0) { + if ((uWritten = sq_setup(&write_sq)) < 0) return uWritten ; + uWritten = 0 ; + } + +/* FIXME: I think that this may be the wrong behaviour when we get strapped + for time and the cpu is close to being (or actually) behind in sending data. + - because we've lost the time that the N samples, already in the buffer, + would have given us to get here with the next lot from the user. +*/ + /* The interrupt doesn't start to play the last, incomplete frame. + * Thus we can append to it without disabling the interrupts! (Note + * also that write_sq.rear isn't affected by the interrupt.) + */ + + /* as of 1.6 this behaviour changes if SNDCTL_DSP_POST has been issued: + this will mimic the behaviour of syncing and allow the sq_play() to + queue a partial fragment. Since sq_play() may/will be called from + the IRQ handler - at least on Pmac we have to deal with it. + The strategy - possibly not optimum - is to kill _POST status if we + get here. This seems, at least, reasonable - in the sense that POST + is supposed to indicate that we might not write before the queue + is drained - and if we get here in time then it does not apply. + */ + + spin_lock_irqsave(&dmasound.lock, flags); + write_sq.syncing &= ~2 ; /* take out POST status */ + spin_unlock_irqrestore(&dmasound.lock, flags); + + if (write_sq.count > 0 && + (bLeft = write_sq.block_size-write_sq.rear_size) > 0) { + dest = write_sq.buffers[write_sq.rear]; + bUsed = write_sq.rear_size; + uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft, + dest, &bUsed, bLeft); + if (uUsed <= 0) + return uUsed; + src += uUsed; + uWritten += uUsed; + uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */ + write_sq.rear_size = bUsed; + } + + while (uLeft) { + DEFINE_WAIT(wait); + + while (write_sq.count >= write_sq.max_active) { + prepare_to_wait(&write_sq.action_queue, &wait, TASK_INTERRUPTIBLE); + sq_play(); + if (write_sq.non_blocking) { + finish_wait(&write_sq.action_queue, &wait); + return uWritten > 0 ? uWritten : -EAGAIN; + } + if (write_sq.count < write_sq.max_active) + break; + + schedule_timeout(HZ); + if (signal_pending(current)) { + finish_wait(&write_sq.action_queue, &wait); + return uWritten > 0 ? uWritten : -EINTR; + } + } + + finish_wait(&write_sq.action_queue, &wait); + + /* Here, we can avoid disabling the interrupt by first + * copying and translating the data, and then updating + * the write_sq variables. Until this is done, the interrupt + * won't see the new frame and we can work on it + * undisturbed. + */ + + dest = write_sq.buffers[(write_sq.rear+1) % write_sq.max_count]; + bUsed = 0; + bLeft = write_sq.block_size; + uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft, + dest, &bUsed, bLeft); + if (uUsed <= 0) + break; + src += uUsed; + uWritten += uUsed; + uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */ + if (bUsed) { + write_sq.rear = (write_sq.rear+1) % write_sq.max_count; + write_sq.rear_size = bUsed; + write_sq.count++; + } + } /* uUsed may have been 0 */ + + sq_play(); + + return uUsed < 0? uUsed: uWritten; +} + +static __poll_t sq_poll(struct file *file, struct poll_table_struct *wait) +{ + __poll_t mask = 0; + int retVal; + + if (write_sq.locked == 0) { + if ((retVal = sq_setup(&write_sq)) < 0) + return retVal; + return 0; + } + if (file->f_mode & FMODE_WRITE ) + poll_wait(file, &write_sq.action_queue, wait); + if (file->f_mode & FMODE_WRITE) + if (write_sq.count < write_sq.max_active || write_sq.block_size - write_sq.rear_size > 0) + mask |= EPOLLOUT | EPOLLWRNORM; + return mask; + +} + +static inline void sq_init_waitqueue(struct sound_queue *sq) +{ + init_waitqueue_head(&sq->action_queue); + init_waitqueue_head(&sq->open_queue); + init_waitqueue_head(&sq->sync_queue); + sq->busy = 0; +} + +#if 0 /* blocking open() */ +static inline void sq_wake_up(struct sound_queue *sq, struct file *file, + fmode_t mode) +{ + if (file->f_mode & mode) { + sq->busy = 0; /* CHECK: IS THIS OK??? */ + WAKE_UP(sq->open_queue); + } +} +#endif + +static int sq_open2(struct sound_queue *sq, struct file *file, fmode_t mode, + int numbufs, int bufsize) +{ + int rc = 0; + + if (file->f_mode & mode) { + if (sq->busy) { +#if 0 /* blocking open() */ + rc = -EBUSY; + if (file->f_flags & O_NONBLOCK) + return rc; + rc = -EINTR; + if (wait_event_interruptible(sq->open_queue, !sq->busy)) + return rc; + rc = 0; +#else + /* OSS manual says we will return EBUSY regardless + of O_NOBLOCK. + */ + return -EBUSY ; +#endif + } + sq->busy = 1; /* Let's play spot-the-race-condition */ + + /* allocate the default number & size of buffers. + (i.e. specified in _setup() or as module params) + can't be changed at the moment - but _could_ be perhaps + in the setfragments ioctl. + */ + if (( rc = sq_allocate_buffers(sq, numbufs, bufsize))) { +#if 0 /* blocking open() */ + sq_wake_up(sq, file, mode); +#else + sq->busy = 0 ; +#endif + return rc; + } + + sq->non_blocking = file->f_flags & O_NONBLOCK; + } + return rc; +} + +#define write_sq_init_waitqueue() sq_init_waitqueue(&write_sq) +#if 0 /* blocking open() */ +#define write_sq_wake_up(file) sq_wake_up(&write_sq, file, FMODE_WRITE) +#endif +#define write_sq_release_buffers() sq_release_buffers(&write_sq) +#define write_sq_open(file) \ + sq_open2(&write_sq, file, FMODE_WRITE, numWriteBufs, writeBufSize ) + +static int sq_open(struct inode *inode, struct file *file) +{ + int rc; + + mutex_lock(&dmasound_core_mutex); + if (!try_module_get(dmasound.mach.owner)) { + mutex_unlock(&dmasound_core_mutex); + return -ENODEV; + } + + rc = write_sq_open(file); /* checks the f_mode */ + if (rc) + goto out; + if (file->f_mode & FMODE_READ) { + /* TODO: if O_RDWR, release any resources grabbed by write part */ + rc = -ENXIO ; /* I think this is what is required by open(2) */ + goto out; + } + + if (dmasound.mach.sq_open) + dmasound.mach.sq_open(file->f_mode); + + /* CHECK whether this is sensible - in the case that dsp0 could be opened + O_RDONLY and dsp1 could be opened O_WRONLY + */ + + dmasound.minDev = iminor(inode) & 0x0f; + + /* OK. - we should make some attempt at consistency. At least the H'ware + options should be set with a valid mode. We will make it that the LL + driver must supply defaults for hard & soft params. + */ + + if (shared_resource_owner == 0) { + /* you can make this AFMT_U8/mono/8K if you want to mimic old + OSS behaviour - while we still have soft translations ;-) */ + dmasound.soft = dmasound.mach.default_soft ; + dmasound.dsp = dmasound.mach.default_soft ; + dmasound.hard = dmasound.mach.default_hard ; + } + +#ifndef DMASOUND_STRICT_OSS_COMPLIANCE + /* none of the current LL drivers can actually do this "native" at the moment + OSS does not really require us to supply /dev/audio if we can't do it. + */ + if (dmasound.minDev == SND_DEV_AUDIO) { + sound_set_speed(8000); + sound_set_stereo(0); + sound_set_format(AFMT_MU_LAW); + } +#endif + mutex_unlock(&dmasound_core_mutex); + return 0; + out: + module_put(dmasound.mach.owner); + mutex_unlock(&dmasound_core_mutex); + return rc; +} + +static void sq_reset_output(void) +{ + sound_silence(); /* this _must_ stop DMA, we might be about to lose the buffers */ + write_sq.active = 0; + write_sq.count = 0; + write_sq.rear_size = 0; + /* write_sq.front = (write_sq.rear+1) % write_sq.max_count;*/ + write_sq.front = 0 ; + write_sq.rear = -1 ; /* same as for set-up */ + + /* OK - we can unlock the parameters and fragment settings */ + write_sq.locked = 0 ; + write_sq.user_frags = 0 ; + write_sq.user_frag_size = 0 ; +} + +static void sq_reset(void) +{ + sq_reset_output() ; + /* we could consider resetting the shared_resources_owner here... but I + think it is probably still rather non-obvious to application writer + */ + + /* we release everything else though */ + shared_resources_initialised = 0 ; +} + +static int sq_fsync(void) +{ + int rc = 0; + int timeout = 5; + + write_sq.syncing |= 1; + sq_play(); /* there may be an incomplete frame waiting */ + + while (write_sq.active) { + wait_event_interruptible_timeout(write_sq.sync_queue, + !write_sq.active, HZ); + if (signal_pending(current)) { + /* While waiting for audio output to drain, an + * interrupt occurred. Stop audio output immediately + * and clear the queue. */ + sq_reset_output(); + rc = -EINTR; + break; + } + if (!--timeout) { + printk(KERN_WARNING "dmasound: Timeout draining output\n"); + sq_reset_output(); + rc = -EIO; + break; + } + } + + /* flag no sync regardless of whether we had a DSP_POST or not */ + write_sq.syncing = 0 ; + return rc; +} + +static int sq_release(struct inode *inode, struct file *file) +{ + int rc = 0; + + mutex_lock(&dmasound_core_mutex); + + if (file->f_mode & FMODE_WRITE) { + if (write_sq.busy) + rc = sq_fsync(); + + sq_reset_output() ; /* make sure dma is stopped and all is quiet */ + write_sq_release_buffers(); + write_sq.busy = 0; + } + + if (file->f_mode & shared_resource_owner) { /* it's us that has them */ + shared_resource_owner = 0 ; + shared_resources_initialised = 0 ; + dmasound.hard = dmasound.mach.default_hard ; + } + + module_put(dmasound.mach.owner); + +#if 0 /* blocking open() */ + /* Wake up a process waiting for the queue being released. + * Note: There may be several processes waiting for a call + * to open() returning. */ + + /* Iain: hmm I don't understand this next comment ... */ + /* There is probably a DOS atack here. They change the mode flag. */ + /* XXX add check here,*/ + read_sq_wake_up(file); /* checks f_mode */ + write_sq_wake_up(file); /* checks f_mode */ +#endif /* blocking open() */ + + mutex_unlock(&dmasound_core_mutex); + + return rc; +} + +/* here we see if we have a right to modify format, channels, size and so on + if no-one else has claimed it already then we do... + + TODO: We might change this to mask O_RDWR such that only one or the other channel + is the owner - if we have problems. +*/ + +static int shared_resources_are_mine(fmode_t md) +{ + if (shared_resource_owner) + return (shared_resource_owner & md) != 0; + else { + shared_resource_owner = md ; + return 1 ; + } +} + +/* if either queue is locked we must deny the right to change shared params +*/ + +static int queues_are_quiescent(void) +{ + if (write_sq.locked) + return 0 ; + return 1 ; +} + +/* check and set a queue's fragments per user's wishes... + we will check against the pre-defined literals and the actual sizes. + This is a bit fraught - because soft translations can mess with our + buffer requirements *after* this call - OSS says "call setfrags first" +*/ + +/* It is possible to replace all the -EINVAL returns with an override that + just puts the allowable value in. This may be what many OSS apps require +*/ + +static int set_queue_frags(struct sound_queue *sq, int bufs, int size) +{ + if (sq->locked) { +#ifdef DEBUG_DMASOUND +printk("dmasound_core: tried to set_queue_frags on a locked queue\n") ; +#endif + return -EINVAL ; + } + + if ((size < MIN_FRAG_SIZE) || (size > MAX_FRAG_SIZE)) + return -EINVAL ; + size = (1< sq->bufSize) + return -EINVAL ; /* this might still not work */ + + if (bufs <= 0) + return -EINVAL ; + if (bufs > sq->numBufs) /* the user is allowed say "don't care" with 0x7fff */ + bufs = sq->numBufs ; + + /* there is, currently, no way to specify max_active separately + from max_count. This could be a LL driver issue - I guess + if there is a requirement for these values to be different then + we will have to pass that info. up to this level. + */ + sq->user_frags = + sq->max_active = bufs ; + sq->user_frag_size = size ; + + return 0 ; +} + +static int sq_ioctl(struct file *file, u_int cmd, u_long arg) +{ + int val, result; + u_long fmt; + int data; + int size, nbufs; + audio_buf_info info; + + switch (cmd) { + case SNDCTL_DSP_RESET: + sq_reset(); + return 0; + break ; + case SNDCTL_DSP_GETFMTS: + fmt = dmasound.mach.hardware_afmts ; /* this is what OSS says.. */ + return IOCTL_OUT(arg, fmt); + break ; + case SNDCTL_DSP_GETBLKSIZE: + /* this should tell the caller about bytes that the app can + read/write - the app doesn't care about our internal buffers. + We force sq_setup() here as per OSS 1.1 (which should + compute the values necessary). + Since there is no mechanism to specify read/write separately, for + fds opened O_RDWR, the write_sq values will, arbitrarily, overwrite + the read_sq ones. + */ + size = 0 ; + if (file->f_mode & FMODE_WRITE) { + if ( !write_sq.locked ) + sq_setup(&write_sq) ; + size = write_sq.user_frag_size ; + } + return IOCTL_OUT(arg, size); + break ; + case SNDCTL_DSP_POST: + /* all we are going to do is to tell the LL that any + partial frags can be queued for output. + The LL will have to clear this flag when last output + is queued. + */ + write_sq.syncing |= 0x2 ; + sq_play() ; + return 0 ; + case SNDCTL_DSP_SYNC: + /* This call, effectively, has the same behaviour as SNDCTL_DSP_RESET + except that it waits for output to finish before resetting + everything - read, however, is killed immediately. + */ + result = 0 ; + if (file->f_mode & FMODE_WRITE) { + result = sq_fsync(); + sq_reset_output() ; + } + /* if we are the shared resource owner then release them */ + if (file->f_mode & shared_resource_owner) + shared_resources_initialised = 0 ; + return result ; + break ; + case SOUND_PCM_READ_RATE: + return IOCTL_OUT(arg, dmasound.soft.speed); + case SNDCTL_DSP_SPEED: + /* changing this on the fly will have weird effects on the sound. + Where there are rate conversions implemented in soft form - it + will cause the _ctx_xxx() functions to be substituted. + However, there doesn't appear to be any reason to dis-allow it from + a driver pov. + */ + if (shared_resources_are_mine(file->f_mode)) { + IOCTL_IN(arg, data); + data = sound_set_speed(data) ; + shared_resources_initialised = 0 ; + return IOCTL_OUT(arg, data); + } else + return -EINVAL ; + break ; + /* OSS says these next 4 actions are undefined when the device is + busy/active - we will just return -EINVAL. + To be allowed to change one - (a) you have to own the right + (b) the queue(s) must be quiescent + */ + case SNDCTL_DSP_STEREO: + if (shared_resources_are_mine(file->f_mode) && + queues_are_quiescent()) { + IOCTL_IN(arg, data); + shared_resources_initialised = 0 ; + return IOCTL_OUT(arg, sound_set_stereo(data)); + } else + return -EINVAL ; + break ; + case SOUND_PCM_WRITE_CHANNELS: + if (shared_resources_are_mine(file->f_mode) && + queues_are_quiescent()) { + IOCTL_IN(arg, data); + /* the user might ask for 20 channels, we will return 1 or 2 */ + shared_resources_initialised = 0 ; + return IOCTL_OUT(arg, sound_set_stereo(data-1)+1); + } else + return -EINVAL ; + break ; + case SNDCTL_DSP_SETFMT: + if (shared_resources_are_mine(file->f_mode) && + queues_are_quiescent()) { + int format; + IOCTL_IN(arg, data); + shared_resources_initialised = 0 ; + format = sound_set_format(data); + result = IOCTL_OUT(arg, format); + if (result < 0) + return result; + if (format != data && data != AFMT_QUERY) + return -EINVAL; + return 0; + } else + return -EINVAL ; + case SNDCTL_DSP_SUBDIVIDE: + return -EINVAL ; + case SNDCTL_DSP_SETFRAGMENT: + /* we can do this independently for the two queues - with the + proviso that for fds opened O_RDWR we cannot separate the + actions and both queues will be set per the last call. + NOTE: this does *NOT* actually set the queue up - merely + registers our intentions. + */ + IOCTL_IN(arg, data); + result = 0 ; + nbufs = (data >> 16) & 0x7fff ; /* 0x7fff is 'use maximum' */ + size = data & 0xffff; + if (file->f_mode & FMODE_WRITE) { + result = set_queue_frags(&write_sq, nbufs, size) ; + if (result) + return result ; + } + /* NOTE: this return value is irrelevant - OSS specifically says that + the value is 'random' and that the user _must_ check the actual + frags values using SNDCTL_DSP_GETBLKSIZE or similar */ + return IOCTL_OUT(arg, data); + break ; + case SNDCTL_DSP_GETOSPACE: + /* + */ + if (file->f_mode & FMODE_WRITE) { + if ( !write_sq.locked ) + sq_setup(&write_sq) ; + info.fragments = write_sq.max_active - write_sq.count; + info.fragstotal = write_sq.max_active; + info.fragsize = write_sq.user_frag_size; + info.bytes = info.fragments * info.fragsize; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } else + return -EINVAL ; + break ; + case SNDCTL_DSP_GETCAPS: + val = dmasound.mach.capabilities & 0xffffff00; + return IOCTL_OUT(arg,val); + + default: + return mixer_ioctl(file, cmd, arg); + } + return -EINVAL; +} + +static long sq_unlocked_ioctl(struct file *file, u_int cmd, u_long arg) +{ + int ret; + + mutex_lock(&dmasound_core_mutex); + ret = sq_ioctl(file, cmd, arg); + mutex_unlock(&dmasound_core_mutex); + + return ret; +} + +static const struct file_operations sq_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sq_write, + .poll = sq_poll, + .unlocked_ioctl = sq_unlocked_ioctl, + .open = sq_open, + .release = sq_release, +}; + +static int sq_init(void) +{ + const struct file_operations *fops = &sq_fops; +#ifndef MODULE + int sq_unit; +#endif + + sq_unit = register_sound_dsp(fops, -1); + if (sq_unit < 0) { + printk(KERN_ERR "dmasound_core: couldn't register fops\n") ; + return sq_unit ; + } + + write_sq_init_waitqueue(); + + /* These parameters will be restored for every clean open() + * in the case of multiple open()s (e.g. dsp0 & dsp1) they + * will be set so long as the shared resources have no owner. + */ + + if (shared_resource_owner == 0) { + dmasound.soft = dmasound.mach.default_soft ; + dmasound.hard = dmasound.mach.default_hard ; + dmasound.dsp = dmasound.mach.default_soft ; + shared_resources_initialised = 0 ; + } + return 0 ; +} + + + /* + * /dev/sndstat + */ + +/* we allow more space for record-enabled because there are extra output lines. + the number here must include the amount we are prepared to give to the low-level + driver. +*/ + +#define STAT_BUFF_LEN 768 + +/* this is how much space we will allow the low-level driver to use + in the stat buffer. Currently, 2 * (80 character line + ). + We do not police this (it is up to the ll driver to be honest). +*/ + +#define LOW_LEVEL_STAT_ALLOC 162 + +static struct { + int busy; + char buf[STAT_BUFF_LEN]; /* state.buf should not overflow! */ + int len, ptr; +} state; + +/* publish this function for use by low-level code, if required */ + +static char *get_afmt_string(int afmt) +{ + switch(afmt) { + case AFMT_MU_LAW: + return "mu-law"; + break; + case AFMT_A_LAW: + return "A-law"; + break; + case AFMT_U8: + return "unsigned 8 bit"; + break; + case AFMT_S8: + return "signed 8 bit"; + break; + case AFMT_S16_BE: + return "signed 16 bit BE"; + break; + case AFMT_U16_BE: + return "unsigned 16 bit BE"; + break; + case AFMT_S16_LE: + return "signed 16 bit LE"; + break; + case AFMT_U16_LE: + return "unsigned 16 bit LE"; + break; + case 0: + return "format not set" ; + break ; + default: + break ; + } + return "ERROR: Unsupported AFMT_XXXX code" ; +} + +static int state_open(struct inode *inode, struct file *file) +{ + char *buffer = state.buf; + int len = 0; + int ret; + + mutex_lock(&dmasound_core_mutex); + ret = -EBUSY; + if (state.busy) + goto out; + + ret = -ENODEV; + if (!try_module_get(dmasound.mach.owner)) + goto out; + + state.ptr = 0; + state.busy = 1; + + len += sprintf(buffer+len, "%sDMA sound driver rev %03d :\n", + dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) + + ((dmasound.mach.version>>8) & 0x0f)); + len += sprintf(buffer+len, + "Core driver edition %02d.%02d : %s driver edition %02d.%02d\n", + DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2, + (dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ; + + /* call the low-level module to fill in any stat info. that it has + if present. Maximum buffer usage is specified. + */ + + if (dmasound.mach.state_info) + len += dmasound.mach.state_info(buffer+len, + (size_t) LOW_LEVEL_STAT_ALLOC) ; + + /* make usage of the state buffer as deterministic as poss. + exceptional conditions could cause overrun - and this is flagged as + a kernel error. + */ + + /* formats and settings */ + + len += sprintf(buffer+len,"\t\t === Formats & settings ===\n") ; + len += sprintf(buffer+len,"Parameter %20s%20s\n","soft","hard") ; + len += sprintf(buffer+len,"Format :%20s%20s\n", + get_afmt_string(dmasound.soft.format), + get_afmt_string(dmasound.hard.format)); + + len += sprintf(buffer+len,"Samp Rate:%14d s/sec%14d s/sec\n", + dmasound.soft.speed, dmasound.hard.speed); + + len += sprintf(buffer+len,"Channels :%20s%20s\n", + dmasound.soft.stereo ? "stereo" : "mono", + dmasound.hard.stereo ? "stereo" : "mono" ); + + /* sound queue status */ + + len += sprintf(buffer+len,"\t\t === Sound Queue status ===\n"); + len += sprintf(buffer+len,"Allocated:%8s%6s\n","Buffers","Size") ; + len += sprintf(buffer+len,"%9s:%8d%6d\n", + "write", write_sq.numBufs, write_sq.bufSize) ; + len += sprintf(buffer+len, + "Current : MaxFrg FragSiz MaxAct Frnt Rear " + "Cnt RrSize A B S L xruns\n") ; + len += sprintf(buffer+len,"%9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d\n", + "write", write_sq.max_count, write_sq.block_size, + write_sq.max_active, write_sq.front, write_sq.rear, + write_sq.count, write_sq.rear_size, write_sq.active, + write_sq.busy, write_sq.syncing, write_sq.locked, write_sq.xruns) ; +#ifdef DEBUG_DMASOUND +printk("dmasound: stat buffer used %d bytes\n", len) ; +#endif + + if (len >= STAT_BUFF_LEN) + printk(KERN_ERR "dmasound_core: stat buffer overflowed!\n"); + + state.len = len; + ret = 0; +out: + mutex_unlock(&dmasound_core_mutex); + return ret; +} + +static int state_release(struct inode *inode, struct file *file) +{ + mutex_lock(&dmasound_core_mutex); + state.busy = 0; + module_put(dmasound.mach.owner); + mutex_unlock(&dmasound_core_mutex); + return 0; +} + +static ssize_t state_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int n = state.len - state.ptr; + if (n > count) + n = count; + if (n <= 0) + return 0; + if (copy_to_user(buf, &state.buf[state.ptr], n)) + return -EFAULT; + state.ptr += n; + return n; +} + +static const struct file_operations state_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = state_read, + .open = state_open, + .release = state_release, +}; + +static int state_init(void) +{ +#ifndef MODULE + int state_unit; +#endif + state_unit = register_sound_special(&state_fops, SND_DEV_STATUS); + if (state_unit < 0) + return state_unit ; + state.busy = 0; + return 0 ; +} + + + /* + * Config & Setup + * + * This function is called by _one_ chipset-specific driver + */ + +int dmasound_init(void) +{ + int res ; +#ifdef MODULE + if (irq_installed) + return -EBUSY; +#endif + + /* Set up sound queue, /dev/audio and /dev/dsp. */ + + /* Set default settings. */ + if ((res = sq_init()) < 0) + return res ; + + /* Set up /dev/sndstat. */ + if ((res = state_init()) < 0) + return res ; + + /* Set up /dev/mixer. */ + mixer_init(); + + if (!dmasound.mach.irqinit()) { + printk(KERN_ERR "DMA sound driver: Interrupt initialization failed\n"); + return -ENODEV; + } +#ifdef MODULE + irq_installed = 1; +#endif + + printk(KERN_INFO "%s DMA sound driver rev %03d installed\n", + dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) + + ((dmasound.mach.version>>8) & 0x0f)); + printk(KERN_INFO + "Core driver edition %02d.%02d : %s driver edition %02d.%02d\n", + DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2, + (dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ; + printk(KERN_INFO "Write will use %4d fragments of %7d bytes as default\n", + numWriteBufs, writeBufSize) ; + return 0; +} + +#ifdef MODULE + +void dmasound_deinit(void) +{ + if (irq_installed) { + sound_silence(); + dmasound.mach.irqcleanup(); + irq_installed = 0; + } + + write_sq_release_buffers(); + + if (mixer_unit >= 0) + unregister_sound_mixer(mixer_unit); + if (state_unit >= 0) + unregister_sound_special(state_unit); + if (sq_unit >= 0) + unregister_sound_dsp(sq_unit); +} + +#else /* !MODULE */ + +static int dmasound_setup(char *str) +{ + int ints[6], size; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + /* check the bootstrap parameter for "dmasound=" */ + + /* FIXME: other than in the most naive of cases there is no sense in these + * buffers being other than powers of two. This is not checked yet. + */ + + switch (ints[0]) { + case 3: + if ((ints[3] < 0) || (ints[3] > MAX_CATCH_RADIUS)) + printk("dmasound_setup: invalid catch radius, using default = %d\n", catchRadius); + else + catchRadius = ints[3]; + /* fall through */ + case 2: + if (ints[1] < MIN_BUFFERS) + printk("dmasound_setup: invalid number of buffers, using default = %d\n", numWriteBufs); + else + numWriteBufs = ints[1]; + /* fall through */ + case 1: + if ((size = ints[2]) < 256) /* check for small buffer specs */ + size <<= 10 ; + if (size < MIN_BUFSIZE || size > MAX_BUFSIZE) + printk("dmasound_setup: invalid write buffer size, using default = %d\n", writeBufSize); + else + writeBufSize = size; + case 0: + break; + default: + printk("dmasound_setup: invalid number of arguments\n"); + return 0; + } + return 1; +} + +__setup("dmasound=", dmasound_setup); + +#endif /* !MODULE */ + + /* + * Conversion tables + */ + +#ifdef HAS_8BIT_TABLES +/* 8 bit mu-law */ + +char dmasound_ulaw2dma8[] = { + -126, -122, -118, -114, -110, -106, -102, -98, + -94, -90, -86, -82, -78, -74, -70, -66, + -63, -61, -59, -57, -55, -53, -51, -49, + -47, -45, -43, -41, -39, -37, -35, -33, + -31, -30, -29, -28, -27, -26, -25, -24, + -23, -22, -21, -20, -19, -18, -17, -16, + -16, -15, -15, -14, -14, -13, -13, -12, + -12, -11, -11, -10, -10, -9, -9, -8, + -8, -8, -7, -7, -7, -7, -6, -6, + -6, -6, -5, -5, -5, -5, -4, -4, + -4, -4, -4, -4, -3, -3, -3, -3, + -3, -3, -3, -3, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 0, + 125, 121, 117, 113, 109, 105, 101, 97, + 93, 89, 85, 81, 77, 73, 69, 65, + 62, 60, 58, 56, 54, 52, 50, 48, + 46, 44, 42, 40, 38, 36, 34, 32, + 30, 29, 28, 27, 26, 25, 24, 23, + 22, 21, 20, 19, 18, 17, 16, 15, + 15, 14, 14, 13, 13, 12, 12, 11, + 11, 10, 10, 9, 9, 8, 8, 7, + 7, 7, 6, 6, 6, 6, 5, 5, + 5, 5, 4, 4, 4, 4, 3, 3, + 3, 3, 3, 3, 2, 2, 2, 2, + 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* 8 bit A-law */ + +char dmasound_alaw2dma8[] = { + -22, -21, -24, -23, -18, -17, -20, -19, + -30, -29, -32, -31, -26, -25, -28, -27, + -11, -11, -12, -12, -9, -9, -10, -10, + -15, -15, -16, -16, -13, -13, -14, -14, + -86, -82, -94, -90, -70, -66, -78, -74, + -118, -114, -126, -122, -102, -98, -110, -106, + -43, -41, -47, -45, -35, -33, -39, -37, + -59, -57, -63, -61, -51, -49, -55, -53, + -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -6, -6, -6, -6, -5, -5, -5, -5, + -8, -8, -8, -8, -7, -7, -7, -7, + -3, -3, -3, -3, -3, -3, -3, -3, + -4, -4, -4, -4, -4, -4, -4, -4, + 21, 20, 23, 22, 17, 16, 19, 18, + 29, 28, 31, 30, 25, 24, 27, 26, + 10, 10, 11, 11, 8, 8, 9, 9, + 14, 14, 15, 15, 12, 12, 13, 13, + 86, 82, 94, 90, 70, 66, 78, 74, + 118, 114, 126, 122, 102, 98, 110, 106, + 43, 41, 47, 45, 35, 33, 39, 37, + 59, 57, 63, 61, 51, 49, 55, 53, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 5, 4, 4, 4, 4, + 7, 7, 7, 7, 6, 6, 6, 6, + 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3 +}; +#endif /* HAS_8BIT_TABLES */ + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(dmasound); +EXPORT_SYMBOL(dmasound_init); +#ifdef MODULE +EXPORT_SYMBOL(dmasound_deinit); +#endif +EXPORT_SYMBOL(dmasound_write_sq); +EXPORT_SYMBOL(dmasound_catchRadius); +#ifdef HAS_8BIT_TABLES +EXPORT_SYMBOL(dmasound_ulaw2dma8); +EXPORT_SYMBOL(dmasound_alaw2dma8); +#endif diff --git a/sound/oss/dmasound/dmasound_paula.c b/sound/oss/dmasound/dmasound_paula.c new file mode 100644 index 000000000..81eb82c46 --- /dev/null +++ b/sound/oss/dmasound/dmasound_paula.c @@ -0,0 +1,738 @@ +/* + * linux/sound/oss/dmasound/dmasound_paula.c + * + * Amiga `Paula' DMA Sound Driver + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits + * prior to 28/01/2001 + * + * 28/01/2001 [0.1] Iain Sandoe + * - added versioning + * - put in and populated the hardware_afmts field. + * [0.2] - put in SNDCTL_DSP_GETCAPS value. + * [0.3] - put in constraint on state buffer usage. + * [0.4] - put in default hard/soft settings +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dmasound.h" + +#define DMASOUND_PAULA_REVISION 0 +#define DMASOUND_PAULA_EDITION 4 + +#define custom amiga_custom + /* + * The minimum period for audio depends on htotal (for OCS/ECS/AGA) + * (Imported from arch/m68k/amiga/amisound.c) + */ + +extern volatile u_short amiga_audio_min_period; + + + /* + * amiga_mksound() should be able to restore the period after beeping + * (Imported from arch/m68k/amiga/amisound.c) + */ + +extern u_short amiga_audio_period; + + + /* + * Audio DMA masks + */ + +#define AMI_AUDIO_OFF (DMAF_AUD0 | DMAF_AUD1 | DMAF_AUD2 | DMAF_AUD3) +#define AMI_AUDIO_8 (DMAF_SETCLR | DMAF_MASTER | DMAF_AUD0 | DMAF_AUD1) +#define AMI_AUDIO_14 (AMI_AUDIO_8 | DMAF_AUD2 | DMAF_AUD3) + + + /* + * Helper pointers for 16(14)-bit sound + */ + +static int write_sq_block_size_half, write_sq_block_size_quarter; + + +/*** Low level stuff *********************************************************/ + + +static void *AmiAlloc(unsigned int size, gfp_t flags); +static void AmiFree(void *obj, unsigned int size); +static int AmiIrqInit(void); +#ifdef MODULE +static void AmiIrqCleanUp(void); +#endif +static void AmiSilence(void); +static void AmiInit(void); +static int AmiSetFormat(int format); +static int AmiSetVolume(int volume); +static int AmiSetTreble(int treble); +static void AmiPlayNextFrame(int index); +static void AmiPlay(void); +static irqreturn_t AmiInterrupt(int irq, void *dummy); + +#ifdef CONFIG_HEARTBEAT + + /* + * Heartbeat interferes with sound since the 7 kHz low-pass filter and the + * power LED are controlled by the same line. + */ + +static void (*saved_heartbeat)(int) = NULL; + +static inline void disable_heartbeat(void) +{ + if (mach_heartbeat) { + saved_heartbeat = mach_heartbeat; + mach_heartbeat = NULL; + } + AmiSetTreble(dmasound.treble); +} + +static inline void enable_heartbeat(void) +{ + if (saved_heartbeat) + mach_heartbeat = saved_heartbeat; +} +#else /* !CONFIG_HEARTBEAT */ +#define disable_heartbeat() do { } while (0) +#define enable_heartbeat() do { } while (0) +#endif /* !CONFIG_HEARTBEAT */ + + +/*** Mid level stuff *********************************************************/ + +static void AmiMixerInit(void); +static int AmiMixerIoctl(u_int cmd, u_long arg); +static int AmiWriteSqSetup(void); +static int AmiStateInfo(char *buffer, size_t space); + + +/*** Translations ************************************************************/ + +/* ++TeSche: radically changed for new expanding purposes... + * + * These two routines now deal with copying/expanding/translating the samples + * from user space into our buffer at the right frequency. They take care about + * how much data there's actually to read, how much buffer space there is and + * to convert samples into the right frequency/encoding. They will only work on + * complete samples so it may happen they leave some bytes in the input stream + * if the user didn't write a multiple of the current sample size. They both + * return the number of bytes they've used from both streams so you may detect + * such a situation. Luckily all programs should be able to cope with that. + * + * I think I've optimized anything as far as one can do in plain C, all + * variables should fit in registers and the loops are really short. There's + * one loop for every possible situation. Writing a more generalized and thus + * parameterized loop would only produce slower code. Feel free to optimize + * this in assembler if you like. :) + * + * I think these routines belong here because they're not yet really hardware + * independent, especially the fact that the Falcon can play 16bit samples + * only in stereo is hardcoded in both of them! + * + * ++geert: split in even more functions (one per format) + */ + + + /* + * Native format + */ + +static ssize_t ami_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + void *p = &frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft) & ~1; + used = count; + if (copy_from_user(p, userPtr, count)) + return -EFAULT; + } else { + u_char *left = &frame[*frameUsed>>1]; + u_char *right = left+write_sq_block_size_half; + count = min_t(unsigned long, userCount, frameLeft)>>1 & ~1; + used = count*2; + while (count > 0) { + if (get_user(*left++, userPtr++) + || get_user(*right++, userPtr++)) + return -EFAULT; + count--; + } + } + *frameUsed += used; + return used; +} + + + /* + * Copy and convert 8 bit data + */ + +#define GENERATE_AMI_CT8(funcname, convsample) \ +static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \ + u_char frame[], ssize_t *frameUsed, \ + ssize_t frameLeft) \ +{ \ + ssize_t count, used; \ + \ + if (!dmasound.soft.stereo) { \ + u_char *p = &frame[*frameUsed]; \ + count = min_t(size_t, userCount, frameLeft) & ~1; \ + used = count; \ + while (count > 0) { \ + u_char data; \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *p++ = convsample(data); \ + count--; \ + } \ + } else { \ + u_char *left = &frame[*frameUsed>>1]; \ + u_char *right = left+write_sq_block_size_half; \ + count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \ + used = count*2; \ + while (count > 0) { \ + u_char data; \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *left++ = convsample(data); \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *right++ = convsample(data); \ + count--; \ + } \ + } \ + *frameUsed += used; \ + return used; \ +} + +#define AMI_CT_ULAW(x) (dmasound_ulaw2dma8[(x)]) +#define AMI_CT_ALAW(x) (dmasound_alaw2dma8[(x)]) +#define AMI_CT_U8(x) ((x) ^ 0x80) + +GENERATE_AMI_CT8(ami_ct_ulaw, AMI_CT_ULAW) +GENERATE_AMI_CT8(ami_ct_alaw, AMI_CT_ALAW) +GENERATE_AMI_CT8(ami_ct_u8, AMI_CT_U8) + + + /* + * Copy and convert 16 bit data + */ + +#define GENERATE_AMI_CT_16(funcname, convsample) \ +static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \ + u_char frame[], ssize_t *frameUsed, \ + ssize_t frameLeft) \ +{ \ + const u_short __user *ptr = (const u_short __user *)userPtr; \ + ssize_t count, used; \ + u_short data; \ + \ + if (!dmasound.soft.stereo) { \ + u_char *high = &frame[*frameUsed>>1]; \ + u_char *low = high+write_sq_block_size_half; \ + count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \ + used = count*2; \ + while (count > 0) { \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *high++ = data>>8; \ + *low++ = (data>>2) & 0x3f; \ + count--; \ + } \ + } else { \ + u_char *lefth = &frame[*frameUsed>>2]; \ + u_char *leftl = lefth+write_sq_block_size_quarter; \ + u_char *righth = lefth+write_sq_block_size_half; \ + u_char *rightl = righth+write_sq_block_size_quarter; \ + count = min_t(size_t, userCount, frameLeft)>>2 & ~1; \ + used = count*4; \ + while (count > 0) { \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *lefth++ = data>>8; \ + *leftl++ = (data>>2) & 0x3f; \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *righth++ = data>>8; \ + *rightl++ = (data>>2) & 0x3f; \ + count--; \ + } \ + } \ + *frameUsed += used; \ + return used; \ +} + +#define AMI_CT_S16BE(x) (x) +#define AMI_CT_U16BE(x) ((x) ^ 0x8000) +#define AMI_CT_S16LE(x) (le2be16((x))) +#define AMI_CT_U16LE(x) (le2be16((x)) ^ 0x8000) + +GENERATE_AMI_CT_16(ami_ct_s16be, AMI_CT_S16BE) +GENERATE_AMI_CT_16(ami_ct_u16be, AMI_CT_U16BE) +GENERATE_AMI_CT_16(ami_ct_s16le, AMI_CT_S16LE) +GENERATE_AMI_CT_16(ami_ct_u16le, AMI_CT_U16LE) + + +static TRANS transAmiga = { + .ct_ulaw = ami_ct_ulaw, + .ct_alaw = ami_ct_alaw, + .ct_s8 = ami_ct_s8, + .ct_u8 = ami_ct_u8, + .ct_s16be = ami_ct_s16be, + .ct_u16be = ami_ct_u16be, + .ct_s16le = ami_ct_s16le, + .ct_u16le = ami_ct_u16le, +}; + +/*** Low level stuff *********************************************************/ + +static inline void StopDMA(void) +{ + custom.aud[0].audvol = custom.aud[1].audvol = 0; + custom.aud[2].audvol = custom.aud[3].audvol = 0; + custom.dmacon = AMI_AUDIO_OFF; + enable_heartbeat(); +} + +static void *AmiAlloc(unsigned int size, gfp_t flags) +{ + return amiga_chip_alloc((long)size, "dmasound [Paula]"); +} + +static void AmiFree(void *obj, unsigned int size) +{ + amiga_chip_free (obj); +} + +static int __init AmiIrqInit(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); + + /* Register interrupt handler. */ + if (request_irq(IRQ_AMIGA_AUD0, AmiInterrupt, 0, "DMA sound", + AmiInterrupt)) + return 0; + return 1; +} + +#ifdef MODULE +static void AmiIrqCleanUp(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); + /* release the interrupt */ + free_irq(IRQ_AMIGA_AUD0, AmiInterrupt); +} +#endif /* MODULE */ + +static void AmiSilence(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); +} + + +static void AmiInit(void) +{ + int period, i; + + AmiSilence(); + + if (dmasound.soft.speed) + period = amiga_colorclock/dmasound.soft.speed-1; + else + period = amiga_audio_min_period; + dmasound.hard = dmasound.soft; + dmasound.trans_write = &transAmiga; + + if (period < amiga_audio_min_period) { + /* we would need to squeeze the sound, but we won't do that */ + period = amiga_audio_min_period; + } else if (period > 65535) { + period = 65535; + } + dmasound.hard.speed = amiga_colorclock/(period+1); + + for (i = 0; i < 4; i++) + custom.aud[i].audper = period; + amiga_audio_period = period; +} + + +static int AmiSetFormat(int format) +{ + int size; + + /* Amiga sound DMA supports 8bit and 16bit (pseudo 14 bit) modes */ + + switch (format) { + case AFMT_QUERY: + return dmasound.soft.format; + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_U8: + case AFMT_S8: + size = 8; + break; + case AFMT_S16_BE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_U16_LE: + size = 16; + break; + default: /* :-) */ + size = 8; + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = size; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = dmasound.soft.size; + } + AmiInit(); + + return format; +} + + +#define VOLUME_VOXWARE_TO_AMI(v) \ + (((v) < 0) ? 0 : ((v) > 100) ? 64 : ((v) * 64)/100) +#define VOLUME_AMI_TO_VOXWARE(v) ((v)*100/64) + +static int AmiSetVolume(int volume) +{ + dmasound.volume_left = VOLUME_VOXWARE_TO_AMI(volume & 0xff); + custom.aud[0].audvol = dmasound.volume_left; + dmasound.volume_right = VOLUME_VOXWARE_TO_AMI((volume & 0xff00) >> 8); + custom.aud[1].audvol = dmasound.volume_right; + if (dmasound.hard.size == 16) { + if (dmasound.volume_left == 64 && dmasound.volume_right == 64) { + custom.aud[2].audvol = 1; + custom.aud[3].audvol = 1; + } else { + custom.aud[2].audvol = 0; + custom.aud[3].audvol = 0; + } + } + return VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) | + (VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8); +} + +static int AmiSetTreble(int treble) +{ + dmasound.treble = treble; + if (treble < 50) + ciaa.pra &= ~0x02; + else + ciaa.pra |= 0x02; + return treble; +} + + +#define AMI_PLAY_LOADED 1 +#define AMI_PLAY_PLAYING 2 +#define AMI_PLAY_MASK 3 + + +static void AmiPlayNextFrame(int index) +{ + u_char *start, *ch0, *ch1, *ch2, *ch3; + u_long size; + + /* used by AmiPlay() if all doubts whether there really is something + * to be played are already wiped out. + */ + start = write_sq.buffers[write_sq.front]; + size = (write_sq.count == index ? write_sq.rear_size + : write_sq.block_size)>>1; + + if (dmasound.hard.stereo) { + ch0 = start; + ch1 = start+write_sq_block_size_half; + size >>= 1; + } else { + ch0 = start; + ch1 = start; + } + + disable_heartbeat(); + custom.aud[0].audvol = dmasound.volume_left; + custom.aud[1].audvol = dmasound.volume_right; + if (dmasound.hard.size == 8) { + custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0); + custom.aud[0].audlen = size; + custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1); + custom.aud[1].audlen = size; + custom.dmacon = AMI_AUDIO_8; + } else { + size >>= 1; + custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0); + custom.aud[0].audlen = size; + custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1); + custom.aud[1].audlen = size; + if (dmasound.volume_left == 64 && dmasound.volume_right == 64) { + /* We can play pseudo 14-bit only with the maximum volume */ + ch3 = ch0+write_sq_block_size_quarter; + ch2 = ch1+write_sq_block_size_quarter; + custom.aud[2].audvol = 1; /* we are being affected by the beeps */ + custom.aud[3].audvol = 1; /* restoring volume here helps a bit */ + custom.aud[2].audlc = (u_short *)ZTWO_PADDR(ch2); + custom.aud[2].audlen = size; + custom.aud[3].audlc = (u_short *)ZTWO_PADDR(ch3); + custom.aud[3].audlen = size; + custom.dmacon = AMI_AUDIO_14; + } else { + custom.aud[2].audvol = 0; + custom.aud[3].audvol = 0; + custom.dmacon = AMI_AUDIO_8; + } + } + write_sq.front = (write_sq.front+1) % write_sq.max_count; + write_sq.active |= AMI_PLAY_LOADED; +} + + +static void AmiPlay(void) +{ + int minframes = 1; + + custom.intena = IF_AUD0; + + if (write_sq.active & AMI_PLAY_LOADED) { + /* There's already a frame loaded */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + if (write_sq.active & AMI_PLAY_PLAYING) + /* Increase threshold: frame 1 is already being played */ + minframes = 2; + + if (write_sq.count < minframes) { + /* Nothing to do */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + if (write_sq.count <= minframes && + write_sq.rear_size < write_sq.block_size && !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + AmiPlayNextFrame(minframes); + + custom.intena = IF_SETCLR | IF_AUD0; +} + + +static irqreturn_t AmiInterrupt(int irq, void *dummy) +{ + int minframes = 1; + + custom.intena = IF_AUD0; + + if (!write_sq.active) { + /* Playing was interrupted and sq_reset() has already cleared + * the sq variables, so better don't do anything here. + */ + WAKE_UP(write_sq.sync_queue); + return IRQ_HANDLED; + } + + if (write_sq.active & AMI_PLAY_PLAYING) { + /* We've just finished a frame */ + write_sq.count--; + WAKE_UP(write_sq.action_queue); + } + + if (write_sq.active & AMI_PLAY_LOADED) + /* Increase threshold: frame 1 is already being played */ + minframes = 2; + + /* Shift the flags */ + write_sq.active = (write_sq.active<<1) & AMI_PLAY_MASK; + + if (!write_sq.active) + /* No frame is playing, disable audio DMA */ + StopDMA(); + + custom.intena = IF_SETCLR | IF_AUD0; + + if (write_sq.count >= minframes) + /* Try to play the next frame */ + AmiPlay(); + + if (!write_sq.active) + /* Nothing to play anymore. + Wake up a process waiting for audio output to drain. */ + WAKE_UP(write_sq.sync_queue); + return IRQ_HANDLED; +} + +/*** Mid level stuff *********************************************************/ + + +/* + * /dev/mixer abstraction + */ + +static void __init AmiMixerInit(void) +{ + dmasound.volume_left = 64; + dmasound.volume_right = 64; + custom.aud[0].audvol = dmasound.volume_left; + custom.aud[3].audvol = 1; /* For pseudo 14bit */ + custom.aud[1].audvol = dmasound.volume_right; + custom.aud[2].audvol = 1; /* For pseudo 14bit */ + dmasound.treble = 50; +} + +static int AmiMixerIoctl(u_int cmd, u_long arg) +{ + int data; + switch (cmd) { + case SOUND_MIXER_READ_DEVMASK: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_TREBLE); + case SOUND_MIXER_READ_RECMASK: + return IOCTL_OUT(arg, 0); + case SOUND_MIXER_READ_STEREODEVS: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME); + case SOUND_MIXER_READ_VOLUME: + return IOCTL_OUT(arg, + VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) | + VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8); + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_volume(data)); + case SOUND_MIXER_READ_TREBLE: + return IOCTL_OUT(arg, dmasound.treble); + case SOUND_MIXER_WRITE_TREBLE: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_treble(data)); + } + return -EINVAL; +} + + +static int AmiWriteSqSetup(void) +{ + write_sq_block_size_half = write_sq.block_size>>1; + write_sq_block_size_quarter = write_sq_block_size_half>>1; + return 0; +} + + +static int AmiStateInfo(char *buffer, size_t space) +{ + int len = 0; + len += sprintf(buffer+len, "\tsound.volume_left = %d [0...64]\n", + dmasound.volume_left); + len += sprintf(buffer+len, "\tsound.volume_right = %d [0...64]\n", + dmasound.volume_right); + if (len >= space) { + printk(KERN_ERR "dmasound_paula: overflowed state buffer alloc.\n") ; + len = space ; + } + return len; +} + + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard = { + .format = AFMT_S8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static SETTINGS def_soft = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static MACHINE machAmiga = { + .name = "Amiga", + .name2 = "AMIGA", + .owner = THIS_MODULE, + .dma_alloc = AmiAlloc, + .dma_free = AmiFree, + .irqinit = AmiIrqInit, +#ifdef MODULE + .irqcleanup = AmiIrqCleanUp, +#endif /* MODULE */ + .init = AmiInit, + .silence = AmiSilence, + .setFormat = AmiSetFormat, + .setVolume = AmiSetVolume, + .setTreble = AmiSetTreble, + .play = AmiPlay, + .mixer_init = AmiMixerInit, + .mixer_ioctl = AmiMixerIoctl, + .write_sq_setup = AmiWriteSqSetup, + .state_info = AmiStateInfo, + .min_dsp_speed = 8000, + .version = ((DMASOUND_PAULA_REVISION<<8) | DMASOUND_PAULA_EDITION), + .hardware_afmts = (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + + +/*** Config & Setup **********************************************************/ + + +static int __init amiga_audio_probe(struct platform_device *pdev) +{ + dmasound.mach = machAmiga; + dmasound.mach.default_hard = def_hard ; + dmasound.mach.default_soft = def_soft ; + return dmasound_init(); +} + +static int __exit amiga_audio_remove(struct platform_device *pdev) +{ + dmasound_deinit(); + return 0; +} + +static struct platform_driver amiga_audio_driver = { + .remove = __exit_p(amiga_audio_remove), + .driver = { + .name = "amiga-audio", + }, +}; + +module_platform_driver_probe(amiga_audio_driver, amiga_audio_probe); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:amiga-audio"); diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c new file mode 100644 index 000000000..be4fe15cf --- /dev/null +++ b/sound/oss/dmasound/dmasound_q40.c @@ -0,0 +1,638 @@ +/* + * linux/sound/oss/dmasound/dmasound_q40.c + * + * Q40 DMA Sound Driver + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits + * prior to 28/01/2001 + * + * 28/01/2001 [0.1] Iain Sandoe + * - added versioning + * - put in and populated the hardware_afmts field. + * [0.2] - put in SNDCTL_DSP_GETCAPS value. + * [0.3] - put in default hard/soft settings. + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dmasound.h" + +#define DMASOUND_Q40_REVISION 0 +#define DMASOUND_Q40_EDITION 3 + +static int expand_bal; /* Balance factor for expanding (not volume!) */ +static int expand_data; /* Data for expanding */ + + +/*** Low level stuff *********************************************************/ + + +static void *Q40Alloc(unsigned int size, gfp_t flags); +static void Q40Free(void *, unsigned int); +static int Q40IrqInit(void); +#ifdef MODULE +static void Q40IrqCleanUp(void); +#endif +static void Q40Silence(void); +static void Q40Init(void); +static int Q40SetFormat(int format); +static int Q40SetVolume(int volume); +static void Q40PlayNextFrame(int index); +static void Q40Play(void); +static irqreturn_t Q40StereoInterrupt(int irq, void *dummy); +static irqreturn_t Q40MonoInterrupt(int irq, void *dummy); +static void Q40Interrupt(void); + + +/*** Mid level stuff *********************************************************/ + + + +/* userCount, frameUsed, frameLeft == byte counts */ +static ssize_t q40_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8; + ssize_t count, used; + u_char *p = (u_char *) &frame[*frameUsed]; + + used = count = min_t(size_t, userCount, frameLeft); + if (copy_from_user(p,userPtr,count)) + return -EFAULT; + while (count > 0) { + *p = table[*p]+128; + p++; + count--; + } + *frameUsed += used ; + return used; +} + + +static ssize_t q40_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + u_char *p = (u_char *) &frame[*frameUsed]; + + used = count = min_t(size_t, userCount, frameLeft); + if (copy_from_user(p,userPtr,count)) + return -EFAULT; + while (count > 0) { + *p = *p + 128; + p++; + count--; + } + *frameUsed += used; + return used; +} + +static ssize_t q40_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + u_char *p = (u_char *) &frame[*frameUsed]; + + used = count = min_t(size_t, userCount, frameLeft); + if (copy_from_user(p,userPtr,count)) + return -EFAULT; + *frameUsed += used; + return used; +} + + +/* a bit too complicated to optimise right now ..*/ +static ssize_t q40_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned char *table = (unsigned char *) + (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8); + unsigned int data = expand_data; + u_char *p = (u_char *) &frame[*frameUsed]; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = table[c]; + data += 0x80; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = c ; + data += 0x80; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = c ; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) ; + utotal -= userCount; + return utotal; +} + +/* compressing versions */ +static ssize_t q40_ctc_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned char *table = (unsigned char *) + (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8); + unsigned int data = expand_data; + u_char *p = (u_char *) &frame[*frameUsed]; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + while(bal<0) { + if (userCount == 0) + goto lout; + if (!(bal<(-hSpeed))) { + if (get_user(c, userPtr)) + return -EFAULT; + data = 0x80 + table[c]; + } + userPtr++; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + lout: + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctc_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + while (bal < 0) { + if (userCount == 0) + goto lout; + if (!(bal<(-hSpeed))) { + if (get_user(c, userPtr)) + return -EFAULT; + data = c + 0x80; + } + userPtr++; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + lout: + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctc_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + while (bal < 0) { + if (userCount == 0) + goto lout; + if (!(bal<(-hSpeed))) { + if (get_user(c, userPtr)) + return -EFAULT; + data = c ; + } + userPtr++; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + lout: + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) ; + utotal -= userCount; + return utotal; +} + + +static TRANS transQ40Normal = { + q40_ct_law, q40_ct_law, q40_ct_s8, q40_ct_u8, NULL, NULL, NULL, NULL +}; + +static TRANS transQ40Expanding = { + q40_ctx_law, q40_ctx_law, q40_ctx_s8, q40_ctx_u8, NULL, NULL, NULL, NULL +}; + +static TRANS transQ40Compressing = { + q40_ctc_law, q40_ctc_law, q40_ctc_s8, q40_ctc_u8, NULL, NULL, NULL, NULL +}; + + +/*** Low level stuff *********************************************************/ + +static void *Q40Alloc(unsigned int size, gfp_t flags) +{ + return kmalloc(size, flags); /* change to vmalloc */ +} + +static void Q40Free(void *ptr, unsigned int size) +{ + kfree(ptr); +} + +static int __init Q40IrqInit(void) +{ + /* Register interrupt handler. */ + if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, + "DMA sound", Q40Interrupt)) + return 0; + + return(1); +} + + +#ifdef MODULE +static void Q40IrqCleanUp(void) +{ + master_outb(0,SAMPLE_ENABLE_REG); + free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); +} +#endif /* MODULE */ + + +static void Q40Silence(void) +{ + master_outb(0,SAMPLE_ENABLE_REG); + *DAC_LEFT=*DAC_RIGHT=127; +} + +static char *q40_pp; +static unsigned int q40_sc; + +static void Q40PlayNextFrame(int index) +{ + u_char *start; + u_long size; + u_char speed; + int error; + + /* used by Q40Play() if all doubts whether there really is something + * to be played are already wiped out. + */ + start = write_sq.buffers[write_sq.front]; + size = (write_sq.count == index ? write_sq.rear_size : write_sq.block_size); + + q40_pp=start; + q40_sc=size; + + write_sq.front = (write_sq.front+1) % write_sq.max_count; + write_sq.active++; + + speed=(dmasound.hard.speed==10000 ? 0 : 1); + + master_outb( 0,SAMPLE_ENABLE_REG); + free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); + if (dmasound.soft.stereo) + error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, + "Q40 sound", Q40Interrupt); + else + error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0, + "Q40 sound", Q40Interrupt); + if (error && printk_ratelimit()) + pr_err("Couldn't register sound interrupt\n"); + + master_outb( speed, SAMPLE_RATE_REG); + master_outb( 1,SAMPLE_CLEAR_REG); + master_outb( 1,SAMPLE_ENABLE_REG); +} + +static void Q40Play(void) +{ + unsigned long flags; + + if (write_sq.active || write_sq.count<=0 ) { + /* There's already a frame loaded */ + return; + } + + /* nothing in the queue */ + if (write_sq.count <= 1 && write_sq.rear_size < write_sq.block_size && !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + return; + } + spin_lock_irqsave(&dmasound.lock, flags); + Q40PlayNextFrame(1); + spin_unlock_irqrestore(&dmasound.lock, flags); +} + +static irqreturn_t Q40StereoInterrupt(int irq, void *dummy) +{ + spin_lock(&dmasound.lock); + if (q40_sc>1){ + *DAC_LEFT=*q40_pp++; + *DAC_RIGHT=*q40_pp++; + q40_sc -=2; + master_outb(1,SAMPLE_CLEAR_REG); + }else Q40Interrupt(); + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} +static irqreturn_t Q40MonoInterrupt(int irq, void *dummy) +{ + spin_lock(&dmasound.lock); + if (q40_sc>0){ + *DAC_LEFT=*q40_pp; + *DAC_RIGHT=*q40_pp++; + q40_sc --; + master_outb(1,SAMPLE_CLEAR_REG); + }else Q40Interrupt(); + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} +static void Q40Interrupt(void) +{ + if (!write_sq.active) { + /* playing was interrupted and sq_reset() has already cleared + * the sq variables, so better don't do anything here. + */ + WAKE_UP(write_sq.sync_queue); + master_outb(0,SAMPLE_ENABLE_REG); /* better safe */ + goto exit; + } else write_sq.active=0; + write_sq.count--; + Q40Play(); + + if (q40_sc<2) + { /* there was nothing to play, disable irq */ + master_outb(0,SAMPLE_ENABLE_REG); + *DAC_LEFT=*DAC_RIGHT=127; + } + WAKE_UP(write_sq.action_queue); + + exit: + master_outb(1,SAMPLE_CLEAR_REG); +} + + +static void Q40Init(void) +{ + int i, idx; + const int freq[] = {10000, 20000}; + + /* search a frequency that fits into the allowed error range */ + + idx = -1; + for (i = 0; i < 2; i++) + if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) <= catchRadius) + idx = i; + + dmasound.hard = dmasound.soft; + /*sound.hard.stereo=1;*/ /* no longer true */ + dmasound.hard.size=8; + + if (idx > -1) { + dmasound.soft.speed = freq[idx]; + dmasound.trans_write = &transQ40Normal; + } else + dmasound.trans_write = &transQ40Expanding; + + Q40Silence(); + + if (dmasound.hard.speed > 20200) { + /* squeeze the sound, we do that */ + dmasound.hard.speed = 20000; + dmasound.trans_write = &transQ40Compressing; + } else if (dmasound.hard.speed > 10000) { + dmasound.hard.speed = 20000; + } else { + dmasound.hard.speed = 10000; + } + expand_bal = -dmasound.soft.speed; +} + + +static int Q40SetFormat(int format) +{ + /* Q40 sound supports only 8bit modes */ + + switch (format) { + case AFMT_QUERY: + return(dmasound.soft.format); + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_S8: + case AFMT_U8: + break; + default: + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = 8; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = 8; + } + Q40Init(); + + return(format); +} + +static int Q40SetVolume(int volume) +{ + return 0; +} + + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 10000 +} ; + +static SETTINGS def_soft = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static MACHINE machQ40 = { + .name = "Q40", + .name2 = "Q40", + .owner = THIS_MODULE, + .dma_alloc = Q40Alloc, + .dma_free = Q40Free, + .irqinit = Q40IrqInit, +#ifdef MODULE + .irqcleanup = Q40IrqCleanUp, +#endif /* MODULE */ + .init = Q40Init, + .silence = Q40Silence, + .setFormat = Q40SetFormat, + .setVolume = Q40SetVolume, + .play = Q40Play, + .min_dsp_speed = 10000, + .version = ((DMASOUND_Q40_REVISION<<8) | DMASOUND_Q40_EDITION), + .hardware_afmts = AFMT_U8, /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + + +/*** Config & Setup **********************************************************/ + + +static int __init dmasound_q40_init(void) +{ + if (MACH_IS_Q40) { + dmasound.mach = machQ40; + dmasound.mach.default_hard = def_hard ; + dmasound.mach.default_soft = def_soft ; + return dmasound_init(); + } else + return -ENODEV; +} + +static void __exit dmasound_q40_cleanup(void) +{ + dmasound_deinit(); +} + +module_init(dmasound_q40_init); +module_exit(dmasound_q40_cleanup); + +MODULE_DESCRIPTION("Q40/Q60 sound driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/parisc/Kconfig b/sound/parisc/Kconfig new file mode 100644 index 000000000..9b61d9501 --- /dev/null +++ b/sound/parisc/Kconfig @@ -0,0 +1,20 @@ +# ALSA PA-RISC drivers + +menuconfig SND_GSC + bool "GSC sound devices" + depends on GSC + default y + help + Support for GSC sound devices on PA-RISC architectures. + +if SND_GSC + +config SND_HARMONY + tristate "Harmony/Vivace sound chip" + select SND_PCM + help + Say 'Y' or 'M' to include support for the Harmony/Vivace sound + chip found in most GSC-based PA-RISC workstations. It's frequently + provided as part of the Lasi multi-function IC. + +endif # SND_GSC diff --git a/sound/parisc/Makefile b/sound/parisc/Makefile new file mode 100644 index 000000000..b91e750ab --- /dev/null +++ b/sound/parisc/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# + +snd-harmony-objs := harmony.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_HARMONY) += snd-harmony.o diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c new file mode 100644 index 000000000..f36e7006e --- /dev/null +++ b/sound/parisc/harmony.c @@ -0,0 +1,1034 @@ +/* Hewlett-Packard Harmony audio driver + * + * This is a driver for the Harmony audio chipset found + * on the LASI ASIC of various early HP PA-RISC workstations. + * + * Copyright (C) 2004, Kyle McMartin + * + * Based on the previous Harmony incarnations by, + * Copyright 2000 (c) Linuxcare Canada, Alex deVries + * Copyright 2000-2003 (c) Helge Deller + * Copyright 2001 (c) Matthieu Delahaye + * Copyright 2001 (c) Jean-Christophe Vaugeois + * Copyright 2003 (c) Laurent Canet + * Copyright 2004 (c) Stuart Brady + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Notes: + * - graveyard and silence buffers last for lifetime of + * the driver. playback and capture buffers are allocated + * per _open()/_close(). + * + * TODO: + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "harmony.h" + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for Harmony driver."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for Harmony driver."); + + +static const struct parisc_device_id snd_harmony_devtable[] __initconst = { + /* bushmaster / flounder */ + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, + /* 712 / 715 */ + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, + /* pace */ + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, + /* outfield / coral II */ + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F }, + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, snd_harmony_devtable); + +#define NAME "harmony" +#define PFX NAME ": " + +static const unsigned int snd_harmony_rates[] = { + 5512, 6615, 8000, 9600, + 11025, 16000, 18900, 22050, + 27428, 32000, 33075, 37800, + 44100, 48000 +}; + +static const unsigned int rate_bits[14] = { + HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ, + HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ, + HARMONY_SR_18KHZ, HARMONY_SR_22KHZ, HARMONY_SR_27KHZ, + HARMONY_SR_32KHZ, HARMONY_SR_33KHZ, HARMONY_SR_37KHZ, + HARMONY_SR_44KHZ, HARMONY_SR_48KHZ +}; + +static const struct snd_pcm_hw_constraint_list hw_constraint_rates = { + .count = ARRAY_SIZE(snd_harmony_rates), + .list = snd_harmony_rates, + .mask = 0, +}; + +static inline unsigned long +harmony_read(struct snd_harmony *h, unsigned r) +{ + return __raw_readl(h->iobase + r); +} + +static inline void +harmony_write(struct snd_harmony *h, unsigned r, unsigned long v) +{ + __raw_writel(v, h->iobase + r); +} + +static inline void +harmony_wait_for_control(struct snd_harmony *h) +{ + while (harmony_read(h, HARMONY_CNTL) & HARMONY_CNTL_C) ; +} + +static inline void +harmony_reset(struct snd_harmony *h) +{ + harmony_write(h, HARMONY_RESET, 1); + mdelay(50); + harmony_write(h, HARMONY_RESET, 0); +} + +static void +harmony_disable_interrupts(struct snd_harmony *h) +{ + u32 dstatus; + harmony_wait_for_control(h); + dstatus = harmony_read(h, HARMONY_DSTATUS); + dstatus &= ~HARMONY_DSTATUS_IE; + harmony_write(h, HARMONY_DSTATUS, dstatus); +} + +static void +harmony_enable_interrupts(struct snd_harmony *h) +{ + u32 dstatus; + harmony_wait_for_control(h); + dstatus = harmony_read(h, HARMONY_DSTATUS); + dstatus |= HARMONY_DSTATUS_IE; + harmony_write(h, HARMONY_DSTATUS, dstatus); +} + +static void +harmony_mute(struct snd_harmony *h) +{ + unsigned long flags; + + spin_lock_irqsave(&h->mixer_lock, flags); + harmony_wait_for_control(h); + harmony_write(h, HARMONY_GAINCTL, HARMONY_GAIN_SILENCE); + spin_unlock_irqrestore(&h->mixer_lock, flags); +} + +static void +harmony_unmute(struct snd_harmony *h) +{ + unsigned long flags; + + spin_lock_irqsave(&h->mixer_lock, flags); + harmony_wait_for_control(h); + harmony_write(h, HARMONY_GAINCTL, h->st.gain); + spin_unlock_irqrestore(&h->mixer_lock, flags); +} + +static void +harmony_set_control(struct snd_harmony *h) +{ + u32 ctrl; + unsigned long flags; + + spin_lock_irqsave(&h->lock, flags); + + ctrl = (HARMONY_CNTL_C | + (h->st.format << 6) | + (h->st.stereo << 5) | + (h->st.rate)); + + harmony_wait_for_control(h); + harmony_write(h, HARMONY_CNTL, ctrl); + + spin_unlock_irqrestore(&h->lock, flags); +} + +static irqreturn_t +snd_harmony_interrupt(int irq, void *dev) +{ + u32 dstatus; + struct snd_harmony *h = dev; + + spin_lock(&h->lock); + harmony_disable_interrupts(h); + harmony_wait_for_control(h); + dstatus = harmony_read(h, HARMONY_DSTATUS); + spin_unlock(&h->lock); + + if (dstatus & HARMONY_DSTATUS_PN) { + if (h->psubs && h->st.playing) { + spin_lock(&h->lock); + h->pbuf.buf += h->pbuf.count; /* PAGE_SIZE */ + h->pbuf.buf %= h->pbuf.size; /* MAX_BUFS*PAGE_SIZE */ + + harmony_write(h, HARMONY_PNXTADD, + h->pbuf.addr + h->pbuf.buf); + h->stats.play_intr++; + spin_unlock(&h->lock); + snd_pcm_period_elapsed(h->psubs); + } else { + spin_lock(&h->lock); + harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); + h->stats.silence_intr++; + spin_unlock(&h->lock); + } + } + + if (dstatus & HARMONY_DSTATUS_RN) { + if (h->csubs && h->st.capturing) { + spin_lock(&h->lock); + h->cbuf.buf += h->cbuf.count; + h->cbuf.buf %= h->cbuf.size; + + harmony_write(h, HARMONY_RNXTADD, + h->cbuf.addr + h->cbuf.buf); + h->stats.rec_intr++; + spin_unlock(&h->lock); + snd_pcm_period_elapsed(h->csubs); + } else { + spin_lock(&h->lock); + harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); + h->stats.graveyard_intr++; + spin_unlock(&h->lock); + } + } + + spin_lock(&h->lock); + harmony_enable_interrupts(h); + spin_unlock(&h->lock); + + return IRQ_HANDLED; +} + +static unsigned int +snd_harmony_rate_bits(int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_harmony_rates); i++) + if (snd_harmony_rates[i] == rate) + return rate_bits[i]; + + return HARMONY_SR_44KHZ; +} + +static const struct snd_pcm_hardware snd_harmony_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW), + .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 5512, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUF_SIZE, + .period_bytes_min = BUF_SIZE, + .period_bytes_max = BUF_SIZE, + .periods_min = 1, + .periods_max = MAX_BUFS, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_harmony_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW), + .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 5512, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUF_SIZE, + .period_bytes_min = BUF_SIZE, + .period_bytes_max = BUF_SIZE, + .periods_min = 1, + .periods_max = MAX_BUFS, + .fifo_size = 0, +}; + +static int +snd_harmony_playback_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + + if (h->st.capturing) + return -EBUSY; + + spin_lock(&h->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + h->st.playing = 1; + harmony_write(h, HARMONY_PNXTADD, h->pbuf.addr); + harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); + harmony_unmute(h); + harmony_enable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_STOP: + h->st.playing = 0; + harmony_mute(h); + harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); + harmony_disable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + default: + spin_unlock(&h->lock); + snd_BUG(); + return -EINVAL; + } + spin_unlock(&h->lock); + + return 0; +} + +static int +snd_harmony_capture_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + + if (h->st.playing) + return -EBUSY; + + spin_lock(&h->lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + h->st.capturing = 1; + harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); + harmony_write(h, HARMONY_RNXTADD, h->cbuf.addr); + harmony_unmute(h); + harmony_enable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_STOP: + h->st.capturing = 0; + harmony_mute(h); + harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); + harmony_disable_interrupts(h); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_SUSPEND: + default: + spin_unlock(&h->lock); + snd_BUG(); + return -EINVAL; + } + spin_unlock(&h->lock); + + return 0; +} + +static int +snd_harmony_set_data_format(struct snd_harmony *h, int fmt, int force) +{ + int o = h->st.format; + int n; + + switch(fmt) { + case SNDRV_PCM_FORMAT_S16_BE: + n = HARMONY_DF_16BIT_LINEAR; + break; + case SNDRV_PCM_FORMAT_A_LAW: + n = HARMONY_DF_8BIT_ALAW; + break; + case SNDRV_PCM_FORMAT_MU_LAW: + n = HARMONY_DF_8BIT_ULAW; + break; + default: + n = HARMONY_DF_16BIT_LINEAR; + break; + } + + if (force || o != n) { + snd_pcm_format_set_silence(fmt, h->sdma.area, SILENCE_BUFSZ / + (snd_pcm_format_physical_width(fmt) + / 8)); + } + + return n; +} + +static int +snd_harmony_playback_prepare(struct snd_pcm_substream *ss) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + + if (h->st.capturing) + return -EBUSY; + + h->pbuf.size = snd_pcm_lib_buffer_bytes(ss); + h->pbuf.count = snd_pcm_lib_period_bytes(ss); + if (h->pbuf.buf >= h->pbuf.size) + h->pbuf.buf = 0; + h->st.playing = 0; + + h->st.rate = snd_harmony_rate_bits(rt->rate); + h->st.format = snd_harmony_set_data_format(h, rt->format, 0); + + if (rt->channels == 2) + h->st.stereo = HARMONY_SS_STEREO; + else + h->st.stereo = HARMONY_SS_MONO; + + harmony_set_control(h); + + h->pbuf.addr = rt->dma_addr; + + return 0; +} + +static int +snd_harmony_capture_prepare(struct snd_pcm_substream *ss) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + + if (h->st.playing) + return -EBUSY; + + h->cbuf.size = snd_pcm_lib_buffer_bytes(ss); + h->cbuf.count = snd_pcm_lib_period_bytes(ss); + if (h->cbuf.buf >= h->cbuf.size) + h->cbuf.buf = 0; + h->st.capturing = 0; + + h->st.rate = snd_harmony_rate_bits(rt->rate); + h->st.format = snd_harmony_set_data_format(h, rt->format, 0); + + if (rt->channels == 2) + h->st.stereo = HARMONY_SS_STEREO; + else + h->st.stereo = HARMONY_SS_MONO; + + harmony_set_control(h); + + h->cbuf.addr = rt->dma_addr; + + return 0; +} + +static snd_pcm_uframes_t +snd_harmony_playback_pointer(struct snd_pcm_substream *ss) +{ + struct snd_pcm_runtime *rt = ss->runtime; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + unsigned long pcuradd; + unsigned long played; + + if (!(h->st.playing) || (h->psubs == NULL)) + return 0; + + if ((h->pbuf.addr == 0) || (h->pbuf.size == 0)) + return 0; + + pcuradd = harmony_read(h, HARMONY_PCURADD); + played = pcuradd - h->pbuf.addr; + +#ifdef HARMONY_DEBUG + printk(KERN_DEBUG PFX "playback_pointer is 0x%lx-0x%lx = %d bytes\n", + pcuradd, h->pbuf.addr, played); +#endif + + if (pcuradd > h->pbuf.addr + h->pbuf.size) { + return 0; + } + + return bytes_to_frames(rt, played); +} + +static snd_pcm_uframes_t +snd_harmony_capture_pointer(struct snd_pcm_substream *ss) +{ + struct snd_pcm_runtime *rt = ss->runtime; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + unsigned long rcuradd; + unsigned long caught; + + if (!(h->st.capturing) || (h->csubs == NULL)) + return 0; + + if ((h->cbuf.addr == 0) || (h->cbuf.size == 0)) + return 0; + + rcuradd = harmony_read(h, HARMONY_RCURADD); + caught = rcuradd - h->cbuf.addr; + +#ifdef HARMONY_DEBUG + printk(KERN_DEBUG PFX "capture_pointer is 0x%lx-0x%lx = %d bytes\n", + rcuradd, h->cbuf.addr, caught); +#endif + + if (rcuradd > h->cbuf.addr + h->cbuf.size) { + return 0; + } + + return bytes_to_frames(rt, caught); +} + +static int +snd_harmony_playback_open(struct snd_pcm_substream *ss) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + int err; + + h->psubs = ss; + rt->hw = snd_harmony_playback; + snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraint_rates); + + err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + +static int +snd_harmony_capture_open(struct snd_pcm_substream *ss) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + struct snd_pcm_runtime *rt = ss->runtime; + int err; + + h->csubs = ss; + rt->hw = snd_harmony_capture; + snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraint_rates); + + err = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + + return 0; +} + +static int +snd_harmony_playback_close(struct snd_pcm_substream *ss) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + h->psubs = NULL; + return 0; +} + +static int +snd_harmony_capture_close(struct snd_pcm_substream *ss) +{ + struct snd_harmony *h = snd_pcm_substream_chip(ss); + h->csubs = NULL; + return 0; +} + +static int +snd_harmony_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *hw) +{ + int err; + struct snd_harmony *h = snd_pcm_substream_chip(ss); + + err = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw)); + if (err > 0 && h->dma.type == SNDRV_DMA_TYPE_CONTINUOUS) + ss->runtime->dma_addr = __pa(ss->runtime->dma_area); + + return err; +} + +static int +snd_harmony_hw_free(struct snd_pcm_substream *ss) +{ + return snd_pcm_lib_free_pages(ss); +} + +static const struct snd_pcm_ops snd_harmony_playback_ops = { + .open = snd_harmony_playback_open, + .close = snd_harmony_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_harmony_hw_params, + .hw_free = snd_harmony_hw_free, + .prepare = snd_harmony_playback_prepare, + .trigger = snd_harmony_playback_trigger, + .pointer = snd_harmony_playback_pointer, +}; + +static const struct snd_pcm_ops snd_harmony_capture_ops = { + .open = snd_harmony_capture_open, + .close = snd_harmony_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_harmony_hw_params, + .hw_free = snd_harmony_hw_free, + .prepare = snd_harmony_capture_prepare, + .trigger = snd_harmony_capture_trigger, + .pointer = snd_harmony_capture_pointer, +}; + +static int +snd_harmony_pcm_init(struct snd_harmony *h) +{ + struct snd_pcm *pcm; + int err; + + if (snd_BUG_ON(!h)) + return -EINVAL; + + harmony_disable_interrupts(h); + + err = snd_pcm_new(h->card, "harmony", 0, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_harmony_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_harmony_capture_ops); + + pcm->private_data = h; + pcm->info_flags = 0; + strcpy(pcm->name, "harmony"); + h->pcm = pcm; + + h->psubs = NULL; + h->csubs = NULL; + + /* initialize graveyard buffer */ + h->dma.type = SNDRV_DMA_TYPE_DEV; + h->dma.dev = &h->dev->dev; + err = snd_dma_alloc_pages(h->dma.type, + h->dma.dev, + BUF_SIZE*GRAVEYARD_BUFS, + &h->gdma); + if (err < 0) { + printk(KERN_ERR PFX "cannot allocate graveyard buffer!\n"); + return err; + } + + /* initialize silence buffers */ + err = snd_dma_alloc_pages(h->dma.type, + h->dma.dev, + BUF_SIZE*SILENCE_BUFS, + &h->sdma); + if (err < 0) { + printk(KERN_ERR PFX "cannot allocate silence buffer!\n"); + return err; + } + + /* pre-allocate space for DMA */ + err = snd_pcm_lib_preallocate_pages_for_all(pcm, h->dma.type, + h->dma.dev, + MAX_BUF_SIZE, + MAX_BUF_SIZE); + if (err < 0) { + printk(KERN_ERR PFX "buffer allocation error: %d\n", err); + return err; + } + + h->st.format = snd_harmony_set_data_format(h, + SNDRV_PCM_FORMAT_S16_BE, 1); + + return 0; +} + +static void +snd_harmony_set_new_gain(struct snd_harmony *h) +{ + harmony_wait_for_control(h); + harmony_write(h, HARMONY_GAINCTL, h->st.gain); +} + +static int +snd_harmony_mixercontrol_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + int mask = (kc->private_value >> 16) & 0xff; + int left_shift = (kc->private_value) & 0xff; + int right_shift = (kc->private_value >> 8) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : + SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = left_shift == right_shift ? 1 : 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + + return 0; +} + +static int +snd_harmony_volume_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_harmony *h = snd_kcontrol_chip(kc); + int shift_left = (kc->private_value) & 0xff; + int shift_right = (kc->private_value >> 8) & 0xff; + int mask = (kc->private_value >> 16) & 0xff; + int invert = (kc->private_value >> 24) & 0xff; + int left, right; + + spin_lock_irq(&h->mixer_lock); + + left = (h->st.gain >> shift_left) & mask; + right = (h->st.gain >> shift_right) & mask; + if (invert) { + left = mask - left; + right = mask - right; + } + + ucontrol->value.integer.value[0] = left; + if (shift_left != shift_right) + ucontrol->value.integer.value[1] = right; + + spin_unlock_irq(&h->mixer_lock); + + return 0; +} + +static int +snd_harmony_volume_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_harmony *h = snd_kcontrol_chip(kc); + int shift_left = (kc->private_value) & 0xff; + int shift_right = (kc->private_value >> 8) & 0xff; + int mask = (kc->private_value >> 16) & 0xff; + int invert = (kc->private_value >> 24) & 0xff; + int left, right; + int old_gain = h->st.gain; + + spin_lock_irq(&h->mixer_lock); + + left = ucontrol->value.integer.value[0] & mask; + if (invert) + left = mask - left; + h->st.gain &= ~( (mask << shift_left ) ); + h->st.gain |= (left << shift_left); + + if (shift_left != shift_right) { + right = ucontrol->value.integer.value[1] & mask; + if (invert) + right = mask - right; + h->st.gain &= ~( (mask << shift_right) ); + h->st.gain |= (right << shift_right); + } + + snd_harmony_set_new_gain(h); + + spin_unlock_irq(&h->mixer_lock); + + return h->st.gain != old_gain; +} + +static int +snd_harmony_captureroute_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { "Line", "Mic" }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int +snd_harmony_captureroute_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_harmony *h = snd_kcontrol_chip(kc); + int value; + + spin_lock_irq(&h->mixer_lock); + + value = (h->st.gain >> HARMONY_GAIN_IS_SHIFT) & 1; + ucontrol->value.enumerated.item[0] = value; + + spin_unlock_irq(&h->mixer_lock); + + return 0; +} + +static int +snd_harmony_captureroute_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_harmony *h = snd_kcontrol_chip(kc); + int value; + int old_gain = h->st.gain; + + spin_lock_irq(&h->mixer_lock); + + value = ucontrol->value.enumerated.item[0] & 1; + h->st.gain &= ~HARMONY_GAIN_IS_MASK; + h->st.gain |= value << HARMONY_GAIN_IS_SHIFT; + + snd_harmony_set_new_gain(h); + + spin_unlock_irq(&h->mixer_lock); + + return h->st.gain != old_gain; +} + +#define HARMONY_CONTROLS ARRAY_SIZE(snd_harmony_controls) + +#define HARMONY_VOLUME(xname, left_shift, right_shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_harmony_mixercontrol_info, \ + .get = snd_harmony_volume_get, .put = snd_harmony_volume_put, \ + .private_value = ((left_shift) | ((right_shift) << 8) | \ + ((mask) << 16) | ((invert) << 24)) } + +static struct snd_kcontrol_new snd_harmony_controls[] = { + HARMONY_VOLUME("Master Playback Volume", HARMONY_GAIN_LO_SHIFT, + HARMONY_GAIN_RO_SHIFT, HARMONY_GAIN_OUT, 1), + HARMONY_VOLUME("Capture Volume", HARMONY_GAIN_LI_SHIFT, + HARMONY_GAIN_RI_SHIFT, HARMONY_GAIN_IN, 0), + HARMONY_VOLUME("Monitor Volume", HARMONY_GAIN_MA_SHIFT, + HARMONY_GAIN_MA_SHIFT, HARMONY_GAIN_MA, 1), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Route", + .info = snd_harmony_captureroute_info, + .get = snd_harmony_captureroute_get, + .put = snd_harmony_captureroute_put + }, + HARMONY_VOLUME("Internal Speaker Switch", HARMONY_GAIN_SE_SHIFT, + HARMONY_GAIN_SE_SHIFT, 1, 0), + HARMONY_VOLUME("Line-Out Switch", HARMONY_GAIN_LE_SHIFT, + HARMONY_GAIN_LE_SHIFT, 1, 0), + HARMONY_VOLUME("Headphones Switch", HARMONY_GAIN_HE_SHIFT, + HARMONY_GAIN_HE_SHIFT, 1, 0), +}; + +static void +snd_harmony_mixer_reset(struct snd_harmony *h) +{ + harmony_mute(h); + harmony_reset(h); + h->st.gain = HARMONY_GAIN_DEFAULT; + harmony_unmute(h); +} + +static int +snd_harmony_mixer_init(struct snd_harmony *h) +{ + struct snd_card *card; + int idx, err; + + if (snd_BUG_ON(!h)) + return -EINVAL; + card = h->card; + strcpy(card->mixername, "Harmony Gain control interface"); + + for (idx = 0; idx < HARMONY_CONTROLS; idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_harmony_controls[idx], h)); + if (err < 0) + return err; + } + + snd_harmony_mixer_reset(h); + + return 0; +} + +static int +snd_harmony_free(struct snd_harmony *h) +{ + if (h->gdma.addr) + snd_dma_free_pages(&h->gdma); + if (h->sdma.addr) + snd_dma_free_pages(&h->sdma); + + if (h->irq >= 0) + free_irq(h->irq, h); + + iounmap(h->iobase); + kfree(h); + return 0; +} + +static int +snd_harmony_dev_free(struct snd_device *dev) +{ + struct snd_harmony *h = dev->device_data; + return snd_harmony_free(h); +} + +static int +snd_harmony_create(struct snd_card *card, + struct parisc_device *padev, + struct snd_harmony **rchip) +{ + int err; + struct snd_harmony *h; + static struct snd_device_ops ops = { + .dev_free = snd_harmony_dev_free, + }; + + *rchip = NULL; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (h == NULL) + return -ENOMEM; + + h->hpa = padev->hpa.start; + h->card = card; + h->dev = padev; + h->irq = -1; + h->iobase = ioremap_nocache(padev->hpa.start, HARMONY_SIZE); + if (h->iobase == NULL) { + printk(KERN_ERR PFX "unable to remap hpa 0x%lx\n", + (unsigned long)padev->hpa.start); + err = -EBUSY; + goto free_and_ret; + } + + err = request_irq(padev->irq, snd_harmony_interrupt, 0, + "harmony", h); + if (err) { + printk(KERN_ERR PFX "could not obtain interrupt %d", + padev->irq); + goto free_and_ret; + } + h->irq = padev->irq; + + spin_lock_init(&h->mixer_lock); + spin_lock_init(&h->lock); + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, + h, &ops)) < 0) { + goto free_and_ret; + } + + *rchip = h; + + return 0; + +free_and_ret: + snd_harmony_free(h); + return err; +} + +static int __init +snd_harmony_probe(struct parisc_device *padev) +{ + int err; + struct snd_card *card; + struct snd_harmony *h; + + err = snd_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + err = snd_harmony_create(card, padev, &h); + if (err < 0) + goto free_and_ret; + + err = snd_harmony_pcm_init(h); + if (err < 0) + goto free_and_ret; + + err = snd_harmony_mixer_init(h); + if (err < 0) + goto free_and_ret; + + strcpy(card->driver, "harmony"); + strcpy(card->shortname, "Harmony"); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, h->hpa, h->irq); + + err = snd_card_register(card); + if (err < 0) + goto free_and_ret; + + parisc_set_drvdata(padev, card); + return 0; + +free_and_ret: + snd_card_free(card); + return err; +} + +static int __exit +snd_harmony_remove(struct parisc_device *padev) +{ + snd_card_free(parisc_get_drvdata(padev)); + return 0; +} + +static struct parisc_driver snd_harmony_driver __refdata = { + .name = "harmony", + .id_table = snd_harmony_devtable, + .probe = snd_harmony_probe, + .remove = __exit_p(snd_harmony_remove), +}; + +static int __init +alsa_harmony_init(void) +{ + return register_parisc_driver(&snd_harmony_driver); +} + +static void __exit +alsa_harmony_fini(void) +{ + unregister_parisc_driver(&snd_harmony_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyle McMartin "); +MODULE_DESCRIPTION("Harmony sound driver"); + +module_init(alsa_harmony_init); +module_exit(alsa_harmony_fini); diff --git a/sound/parisc/harmony.h b/sound/parisc/harmony.h new file mode 100644 index 000000000..f4c29a2e3 --- /dev/null +++ b/sound/parisc/harmony.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Hewlett-Packard Harmony audio driver + * Copyright (C) 2004, Kyle McMartin + */ + +#ifndef __HARMONY_H__ +#define __HARMONY_H__ + +struct harmony_buffer { + unsigned long addr; + int buf; + int count; + int size; + int coherent; +}; + +struct snd_harmony { + int irq; + + unsigned long hpa; /* hard physical address */ + void __iomem *iobase; /* remapped io address */ + + struct parisc_device *dev; + + struct { + u32 gain; + u32 rate; + u32 format; + u32 stereo; + int playing; + int capturing; + } st; + + struct snd_dma_device dma; /* playback/capture */ + struct harmony_buffer pbuf; + struct harmony_buffer cbuf; + + struct snd_dma_buffer gdma; /* graveyard */ + struct snd_dma_buffer sdma; /* silence */ + + struct { + unsigned long play_intr; + unsigned long rec_intr; + unsigned long graveyard_intr; + unsigned long silence_intr; + } stats; + + struct snd_pcm *pcm; + struct snd_card *card; + struct snd_pcm_substream *psubs; + struct snd_pcm_substream *csubs; + struct snd_info_entry *proc; + + spinlock_t lock; + spinlock_t mixer_lock; +}; + +#define MAX_PCM_DEVICES 1 +#define MAX_PCM_SUBSTREAMS 4 +#define MAX_MIDI_DEVICES 0 + +#define HARMONY_SIZE 64 + +#define BUF_SIZE PAGE_SIZE +#define MAX_BUFS 16 +#define MAX_BUF_SIZE (MAX_BUFS * BUF_SIZE) + +#define PLAYBACK_BUFS MAX_BUFS +#define RECORD_BUFS MAX_BUFS +#define GRAVEYARD_BUFS 1 +#define GRAVEYARD_BUFSZ (GRAVEYARD_BUFS*BUF_SIZE) +#define SILENCE_BUFS 1 +#define SILENCE_BUFSZ (SILENCE_BUFS*BUF_SIZE) + +#define HARMONY_ID 0x000 +#define HARMONY_RESET 0x004 +#define HARMONY_CNTL 0x008 +#define HARMONY_GAINCTL 0x00c +#define HARMONY_PNXTADD 0x010 +#define HARMONY_PCURADD 0x014 +#define HARMONY_RNXTADD 0x018 +#define HARMONY_RCURADD 0x01c +#define HARMONY_DSTATUS 0x020 +#define HARMONY_OV 0x024 +#define HARMONY_PIO 0x028 +#define HARMONY_DIAG 0x03c + +#define HARMONY_CNTL_C 0x80000000 +#define HARMONY_CNTL_ST 0x00000020 +#define HARMONY_CNTL_44100 0x00000015 /* HARMONY_SR_44KHZ */ +#define HARMONY_CNTL_8000 0x00000008 /* HARMONY_SR_8KHZ */ + +#define HARMONY_DSTATUS_ID 0x00000000 /* interrupts off */ +#define HARMONY_DSTATUS_PN 0x00000200 /* playback fill */ +#define HARMONY_DSTATUS_RN 0x00000002 /* record fill */ +#define HARMONY_DSTATUS_IE 0x80000000 /* interrupts on */ + +#define HARMONY_DF_16BIT_LINEAR 0x00000000 +#define HARMONY_DF_8BIT_ULAW 0x00000001 +#define HARMONY_DF_8BIT_ALAW 0x00000002 + +#define HARMONY_SS_MONO 0x00000000 +#define HARMONY_SS_STEREO 0x00000001 + +#define HARMONY_GAIN_SILENCE 0x01F00FFF +#define HARMONY_GAIN_DEFAULT 0x01F00FFF + +#define HARMONY_GAIN_HE_SHIFT 27 /* headphones enabled */ +#define HARMONY_GAIN_HE_MASK (1 << HARMONY_GAIN_HE_SHIFT) +#define HARMONY_GAIN_LE_SHIFT 26 /* line-out enabled */ +#define HARMONY_GAIN_LE_MASK (1 << HARMONY_GAIN_LE_SHIFT) +#define HARMONY_GAIN_SE_SHIFT 25 /* internal-speaker enabled */ +#define HARMONY_GAIN_SE_MASK (1 << HARMONY_GAIN_SE_SHIFT) +#define HARMONY_GAIN_IS_SHIFT 24 /* input select - 0 for line, 1 for mic */ +#define HARMONY_GAIN_IS_MASK (1 << HARMONY_GAIN_IS_SHIFT) + +/* monitor attenuation */ +#define HARMONY_GAIN_MA 0x0f +#define HARMONY_GAIN_MA_SHIFT 20 +#define HARMONY_GAIN_MA_MASK (HARMONY_GAIN_MA << HARMONY_GAIN_MA_SHIFT) + +/* input gain */ +#define HARMONY_GAIN_IN 0x0f +#define HARMONY_GAIN_LI_SHIFT 16 +#define HARMONY_GAIN_LI_MASK (HARMONY_GAIN_IN << HARMONY_GAIN_LI_SHIFT) +#define HARMONY_GAIN_RI_SHIFT 12 +#define HARMONY_GAIN_RI_MASK (HARMONY_GAIN_IN << HARMONY_GAIN_RI_SHIFT) + +/* output gain (master volume) */ +#define HARMONY_GAIN_OUT 0x3f +#define HARMONY_GAIN_LO_SHIFT 6 +#define HARMONY_GAIN_LO_MASK (HARMONY_GAIN_OUT << HARMONY_GAIN_LO_SHIFT) +#define HARMONY_GAIN_RO_SHIFT 0 +#define HARMONY_GAIN_RO_MASK (HARMONY_GAIN_OUT << HARMONY_GAIN_RO_SHIFT) + +#define HARMONY_MAX_OUT (HARMONY_GAIN_RO_MASK >> HARMONY_GAIN_RO_SHIFT) +#define HARMONY_MAX_IN (HARMONY_GAIN_RI_MASK >> HARMONY_GAIN_RI_SHIFT) +#define HARMONY_MAX_MON (HARMONY_GAIN_MA_MASK >> HARMONY_GAIN_MA_SHIFT) + +#define HARMONY_SR_8KHZ 0x08 +#define HARMONY_SR_16KHZ 0x09 +#define HARMONY_SR_27KHZ 0x0A +#define HARMONY_SR_32KHZ 0x0B +#define HARMONY_SR_48KHZ 0x0E +#define HARMONY_SR_9KHZ 0x0F +#define HARMONY_SR_5KHZ 0x10 +#define HARMONY_SR_11KHZ 0x11 +#define HARMONY_SR_18KHZ 0x12 +#define HARMONY_SR_22KHZ 0x13 +#define HARMONY_SR_37KHZ 0x14 +#define HARMONY_SR_44KHZ 0x15 +#define HARMONY_SR_33KHZ 0x16 +#define HARMONY_SR_6KHZ 0x17 + +#endif /* __HARMONY_H__ */ diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig new file mode 100644 index 000000000..bbaf46dc3 --- /dev/null +++ b/sound/pci/Kconfig @@ -0,0 +1,913 @@ +# 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 . + + 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 . + + 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 . + + 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 + . + + 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 + . + + 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 + and + . + + To compile this driver as a module, choose M here: the module + will be called snd-emu10k1. + +# select SEQ stuff to min(SND_SEQUENCER,SND_XXX) +config SND_EMU10K1_SEQ + def_tristate SND_SEQUENCER && SND_EMU10K1 + select SND_SEQ_MIDI_EMUL + select SND_SEQ_VIRMIDI + select SND_SYNTH_EMUX + +config SND_EMU10K1X + tristate "Emu10k1X (Dell OEM Version)" + select SND_AC97_CODEC + select SND_RAWMIDI + depends on ZONE_DMA + 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 . + + 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 +# + +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..526175333 --- /dev/null +++ b/sound/pci/ac97/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-ac97-codec-y := ac97_codec.o ac97_pcm.o +snd-ac97-codec-$(CONFIG_SND_PROC_FS) += ac97_proc.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AC97_CODEC) += snd-ac97-codec.o diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c new file mode 100644 index 000000000..34d75d4fb --- /dev/null +++ b/sound/pci/ac97/ac97_codec.c @@ -0,0 +1,2943 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac97_id.h" + +#include "ac97_patch.c" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Universal interface for Audio Codec '97"); +MODULE_LICENSE("GPL"); + +static bool enable_loopback; + +module_param(enable_loopback, bool, 0444); +MODULE_PARM_DESC(enable_loopback, "Enable AC97 ADC/DAC Loopback Control"); + +#ifdef CONFIG_SND_AC97_POWER_SAVE +static int power_save = CONFIG_SND_AC97_POWER_SAVE_DEFAULT; +module_param(power_save, int, 0644); +MODULE_PARM_DESC(power_save, "Automatic power-saving timeout " + "(in second, 0 = disable)."); +#endif +/* + + */ + +struct ac97_codec_id { + unsigned int id; + unsigned int mask; + const char *name; + int (*patch)(struct snd_ac97 *ac97); + int (*mpatch)(struct snd_ac97 *ac97); + unsigned int flags; +}; + +static const struct ac97_codec_id snd_ac97_codec_id_vendors[] = { +{ 0x41445300, 0xffffff00, "Analog Devices", NULL, NULL }, +{ 0x414b4d00, 0xffffff00, "Asahi Kasei", NULL, NULL }, +{ 0x414c4300, 0xffffff00, "Realtek", NULL, NULL }, +{ 0x414c4700, 0xffffff00, "Realtek", NULL, NULL }, +/* + * This is an _inofficial_ Aztech Labs entry + * (value might differ from unknown official Aztech ID), + * currently used by the AC97 emulation of the almost-AC97 PCI168 card. + */ +{ 0x415a5400, 0xffffff00, "Aztech Labs (emulated)", NULL, NULL }, +{ 0x434d4900, 0xffffff00, "C-Media Electronics", NULL, NULL }, +{ 0x43525900, 0xffffff00, "Cirrus Logic", NULL, NULL }, +{ 0x43585400, 0xffffff00, "Conexant", NULL, NULL }, +{ 0x44543000, 0xffffff00, "Diamond Technology", NULL, NULL }, +{ 0x454d4300, 0xffffff00, "eMicro", NULL, NULL }, +{ 0x45838300, 0xffffff00, "ESS Technology", NULL, NULL }, +{ 0x48525300, 0xffffff00, "Intersil", NULL, NULL }, +{ 0x49434500, 0xffffff00, "ICEnsemble", NULL, NULL }, +{ 0x49544500, 0xffffff00, "ITE Tech.Inc", NULL, NULL }, +{ 0x4e534300, 0xffffff00, "National Semiconductor", NULL, NULL }, +{ 0x50534300, 0xffffff00, "Philips", NULL, NULL }, +{ 0x53494c00, 0xffffff00, "Silicon Laboratory", NULL, NULL }, +{ 0x53544d00, 0xffffff00, "STMicroelectronics", NULL, NULL }, +{ 0x54524100, 0xffffff00, "TriTech", NULL, NULL }, +{ 0x54584e00, 0xffffff00, "Texas Instruments", NULL, NULL }, +{ 0x56494100, 0xffffff00, "VIA Technologies", NULL, NULL }, +{ 0x57454300, 0xffffff00, "Winbond", NULL, NULL }, +{ 0x574d4c00, 0xffffff00, "Wolfson", NULL, NULL }, +{ 0x594d4800, 0xffffff00, "Yamaha", NULL, NULL }, +{ 0x83847600, 0xffffff00, "SigmaTel", NULL, NULL }, +{ 0, 0, NULL, NULL, NULL } +}; + +static const struct ac97_codec_id snd_ac97_codec_ids[] = { +{ 0x41445303, 0xffffffff, "AD1819", patch_ad1819, NULL }, +{ 0x41445340, 0xffffffff, "AD1881", patch_ad1881, NULL }, +{ 0x41445348, 0xffffffff, "AD1881A", patch_ad1881, NULL }, +{ 0x41445360, 0xffffffff, "AD1885", patch_ad1885, NULL }, +{ 0x41445361, 0xffffffff, "AD1886", patch_ad1886, NULL }, +{ 0x41445362, 0xffffffff, "AD1887", patch_ad1881, NULL }, +{ 0x41445363, 0xffffffff, "AD1886A", patch_ad1881, NULL }, +{ 0x41445368, 0xffffffff, "AD1888", patch_ad1888, NULL }, +{ 0x41445370, 0xffffffff, "AD1980", patch_ad1980, NULL }, +{ 0x41445372, 0xffffffff, "AD1981A", patch_ad1981a, NULL }, +{ 0x41445374, 0xffffffff, "AD1981B", patch_ad1981b, NULL }, +{ 0x41445375, 0xffffffff, "AD1985", patch_ad1985, NULL }, +{ 0x41445378, 0xffffffff, "AD1986", patch_ad1986, NULL }, +{ 0x414b4d00, 0xffffffff, "AK4540", NULL, NULL }, +{ 0x414b4d01, 0xffffffff, "AK4542", NULL, NULL }, +{ 0x414b4d02, 0xffffffff, "AK4543", NULL, NULL }, +{ 0x414b4d06, 0xffffffff, "AK4544A", NULL, NULL }, +{ 0x414b4d07, 0xffffffff, "AK4545", NULL, NULL }, +{ 0x414c4300, 0xffffff00, "ALC100,100P", NULL, NULL }, +{ 0x414c4710, 0xfffffff0, "ALC200,200P", NULL, NULL }, +{ 0x414c4721, 0xffffffff, "ALC650D", NULL, NULL }, /* already patched */ +{ 0x414c4722, 0xffffffff, "ALC650E", NULL, NULL }, /* already patched */ +{ 0x414c4723, 0xffffffff, "ALC650F", NULL, NULL }, /* already patched */ +{ 0x414c4720, 0xfffffff0, "ALC650", patch_alc650, NULL }, +{ 0x414c4730, 0xffffffff, "ALC101", NULL, NULL }, +{ 0x414c4740, 0xfffffff0, "ALC202", NULL, NULL }, +{ 0x414c4750, 0xfffffff0, "ALC250", NULL, NULL }, +{ 0x414c4760, 0xfffffff0, "ALC655", patch_alc655, NULL }, +{ 0x414c4770, 0xfffffff0, "ALC203", patch_alc203, NULL }, +{ 0x414c4781, 0xffffffff, "ALC658D", NULL, NULL }, /* already patched */ +{ 0x414c4780, 0xfffffff0, "ALC658", patch_alc655, NULL }, +{ 0x414c4790, 0xfffffff0, "ALC850", patch_alc850, NULL }, +{ 0x415a5401, 0xffffffff, "AZF3328", patch_aztech_azf3328, NULL }, +{ 0x434d4941, 0xffffffff, "CMI9738", patch_cm9738, NULL }, +{ 0x434d4961, 0xffffffff, "CMI9739", patch_cm9739, NULL }, +{ 0x434d4969, 0xffffffff, "CMI9780", patch_cm9780, NULL }, +{ 0x434d4978, 0xffffffff, "CMI9761A", patch_cm9761, NULL }, +{ 0x434d4982, 0xffffffff, "CMI9761B", patch_cm9761, NULL }, +{ 0x434d4983, 0xffffffff, "CMI9761A+", patch_cm9761, NULL }, +{ 0x43525900, 0xfffffff8, "CS4297", NULL, NULL }, +{ 0x43525910, 0xfffffff8, "CS4297A", patch_cirrus_spdif, NULL }, +{ 0x43525920, 0xfffffff8, "CS4298", patch_cirrus_spdif, NULL }, +{ 0x43525928, 0xfffffff8, "CS4294", NULL, NULL }, +{ 0x43525930, 0xfffffff8, "CS4299", patch_cirrus_cs4299, NULL }, +{ 0x43525948, 0xfffffff8, "CS4201", NULL, NULL }, +{ 0x43525958, 0xfffffff8, "CS4205", patch_cirrus_spdif, NULL }, +{ 0x43525960, 0xfffffff8, "CS4291", NULL, NULL }, +{ 0x43525970, 0xfffffff8, "CS4202", NULL, NULL }, +{ 0x43585421, 0xffffffff, "HSD11246", NULL, NULL }, // SmartMC II +{ 0x43585428, 0xfffffff8, "Cx20468", patch_conexant, NULL }, // SmartAMC fixme: the mask might be different +{ 0x43585430, 0xffffffff, "Cx20468-31", patch_conexant, NULL }, +{ 0x43585431, 0xffffffff, "Cx20551", patch_cx20551, NULL }, +{ 0x44543031, 0xfffffff0, "DT0398", NULL, NULL }, +{ 0x454d4328, 0xffffffff, "EM28028", NULL, NULL }, // same as TR28028? +{ 0x45838308, 0xffffffff, "ESS1988", NULL, NULL }, +{ 0x48525300, 0xffffff00, "HMP9701", NULL, NULL }, +{ 0x49434501, 0xffffffff, "ICE1230", NULL, NULL }, +{ 0x49434511, 0xffffffff, "ICE1232", NULL, NULL }, // alias VIA VT1611A? +{ 0x49434514, 0xffffffff, "ICE1232A", NULL, NULL }, +{ 0x49434551, 0xffffffff, "VT1616", patch_vt1616, NULL }, +{ 0x49434552, 0xffffffff, "VT1616i", patch_vt1616, NULL }, // VT1616 compatible (chipset integrated) +{ 0x49544520, 0xffffffff, "IT2226E", NULL, NULL }, +{ 0x49544561, 0xffffffff, "IT2646E", patch_it2646, NULL }, +{ 0x4e534300, 0xffffffff, "LM4540,43,45,46,48", NULL, NULL }, // only guess --jk +{ 0x4e534331, 0xffffffff, "LM4549", NULL, NULL }, +{ 0x4e534350, 0xffffffff, "LM4550", patch_lm4550, NULL }, // volume wrap fix +{ 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; + /* fall through */ + case AC97_ID_ST7597: + if (reg == 0x22 || reg == 0x7a) + return 1; + /* fall through */ + 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_master_mono[2] = { +AC97_SINGLE("Master Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), +AC97_SINGLE("Master Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1) +}; + +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 unsigned short ctl_bits[] = { + AC97_SC_SPSR_44K, AC97_SC_SPSR_32K, AC97_SC_SPSR_48K + }; + static 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, struct snd_ac97_bus_ops *ops, + void *private_data, struct snd_ac97_bus **rbus) +{ + int err; + struct snd_ac97_bus *bus; + static 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"); + 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 struct snd_device_ops ops = { + .dev_free = snd_ac97_dev_free, + .dev_register = snd_ac97_dev_register, + .dev_disconnect = snd_ac97_dev_disconnect, + }; + + if (rac97) + *rac97 = NULL; + if (snd_BUG_ON(!bus || !template)) + return -EINVAL; + 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 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 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; + 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..d603147c4 --- /dev/null +++ b/sound/pci/ac97/ac97_id.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..941a5062c --- /dev/null +++ b/sound/pci/ac97/ac97_local.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +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..8cf0dc7a0 --- /dev/null +++ b/sound/pci/ac97/ac97_patch.c @@ -0,0 +1,3947 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com) and to datasheets + * for specific codecs. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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 *slaves); + +/* + * 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 */ + +/* 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 + * removed broken wolfson00 patch. + * added support for WM9705,WM9708,WM9709,WM9710,WM9711,WM9712 and WM9717. + */ + +static const struct snd_kcontrol_new wm97xx_snd_ac97_controls[] = { +AC97_DOUBLE("Front Playback Volume", AC97_WM97XX_FMIXER_VOL, 8, 0, 31, 1), +AC97_SINGLE("Front Playback Switch", AC97_WM97XX_FMIXER_VOL, 15, 1, 1), +}; + +static int patch_wolfson_wm9703_specific(struct snd_ac97 * ac97) +{ + /* This is known to work for the ViewSonic ViewPad 1000 + * Randolph Bentson + * WM9703/9707/9708/9717 + */ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm97xx_snd_ac97_controls); i++) { + 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 unsigned short regs[4] = { + AC97_SIGMATEL_OUTSEL, + AC97_SIGMATEL_IOMISC, + AC97_SIGMATEL_INSEL, + AC97_SIGMATEL_VARIOUS + }; + static 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 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 */ + }; + 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 Device AD18xx, AD19xx codecs + */ +#ifdef CONFIG_PM +static void ad18xx_resume(struct snd_ac97 *ac97) +{ + static 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 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), +}; + +/* black list to avoid HP/Line jack-sense controls + * (SS vendor << 16 | device) + */ +static unsigned int ad1981_jacks_blacklist[] = { + 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_blacklist)) + 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 +}; + +/* white list to enable HP jack-sense bits + * (SS vendor << 16 | device) + */ +static unsigned int ad1981_jacks_whitelist[] = { + 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_whitelist)) + /* 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_blacklist)) + 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 unsigned short surr_on[3][2] = { + { 0x0008, 0x0000 }, /* 9761-78 & 82 */ + { 0x0000, 0x0008 }, /* 9761-82 rev.B */ + { 0x0000, 0x0008 }, /* 9761-83 */ + }; + static unsigned short clfe_on[3][2] = { + { 0x0000, 0x1000 }, /* 9761-78 & 82 */ + { 0x1000, 0x0000 }, /* 9761-82 rev.B */ + { 0x0000, 0x1000 }, /* 9761-83 */ + }; + static unsigned short surr_shared[3][2] = { + { 0x0000, 0x0400 }, /* 9761-78 & 82 */ + { 0x0000, 0x0400 }, /* 9761-82 rev.B */ + { 0x0000, 0x0400 }, /* 9761-83 */ + }; + static 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 slave_vols_vt1616[] = { + "Front Playback Volume", + "Surround Playback Volume", + "Center Playback Volume", + "LFE Playback Volume", + NULL +}; + +static const char * const slave_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 slaves */ +static int snd_ac97_add_vmaster(struct snd_ac97 *ac97, char *name, + const unsigned int *tlv, + const char * const *slaves) +{ + 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 = slaves; *s; s++) { + struct snd_kcontrol *sctl; + + sctl = snd_ac97_find_mixer_ctl(ac97, *s); + if (!sctl) { + dev_dbg(ac97->bus->card->dev, + "Cannot find slave %s, skipped\n", *s); + continue; + } + err = snd_ctl_add_slave(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, slave_vols_vt1616); + if (err < 0) + return err; + + err = snd_ac97_add_vmaster(ac97, "Master Playback Switch", + NULL, slave_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 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 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..d1ce151fe --- /dev/null +++ b/sound/pci/ac97/ac97_patch.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..d15297a68 --- /dev/null +++ b/sound/pci/ac97/ac97_pcm.c @@ -0,0 +1,752 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com) and to datasheets + * for specific codecs. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "ac97_id.h" +#include "ac97_local.h" + +/* + * PCM support + */ + +static 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 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<addr) { + case 0: + slots |= (1<scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<scaps & AC97_SCAP_CENTER_LFE_DAC) + slots |= (1<ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<scaps & AC97_SCAP_CENTER_LFE_DAC)) + *spdif_slots = (1<scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<ext_id & AC97_EI_SPDIF) + *spdif_slots = (1<scaps & AC97_SCAP_SURROUND_DAC) + slots |= (1<scaps & AC97_SCAP_CENTER_LFE_DAC) + slots |= (1<ext_id & AC97_EI_SPDIF) { + if (!(ac97->scaps & AC97_SCAP_SURROUND_DAC)) + *spdif_slots = (1<scaps & AC97_SCAP_CENTER_LFE_DAC)) + *spdif_slots = (1<r[dbl].codec[cidx]->rates[idx]; + } + if (!dbl) + rates &= ~(SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000); + return rates; +} + +/** + * snd_ac97_pcm_assign - assign AC97 slots to given PCM streams + * @bus: the ac97 bus instance + * @pcms_count: count of PCMs to be assigned + * @pcms: PCMs to be assigned + * + * It assigns available AC97 slots for given PCMs. If none or only + * some slots are available, pcm->xxx.slots and pcm->xxx.rslots[] members + * are reduced and might be zero. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ac97_pcm_assign(struct snd_ac97_bus *bus, + unsigned short pcms_count, + const struct ac97_pcm *pcms) +{ + int i, j, k; + const struct ac97_pcm *pcm; + struct ac97_pcm *rpcms, *rpcm; + unsigned short avail_slots[2][4]; + unsigned char rate_table[2][4]; + unsigned short tmp, slots; + unsigned short spdif_slots[4]; + unsigned int rates; + struct snd_ac97 *codec; + + rpcms = 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<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..e120a11c6 --- /dev/null +++ b/sound/pci/ac97/ac97_proc.c @@ -0,0 +1,490 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal interface for Audio Codec '97 + * + * For more details look to AC '97 component specification revision 2.2 + * by Intel Corporation (http://developer.intel.com). + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include +#include +#include +#include "ac97_local.h" +#include "ac97_id.h" + +/* + * proc interface + */ + +static void snd_ac97_proc_read_functions(struct snd_ac97 *ac97, struct snd_info_buffer *buffer) +{ + int header = 0, function; + unsigned short info, sense_info; + static const char *function_names[12] = { + "Master Out", "AUX Out", "Center/LFE Out", "SPDIF Out", + "Phone In", "Mic 1", "Mic 2", "Line In", "CD In", "Video In", + "Aux In", "Mono Out" + }; + static const char *locations[8] = { + "Rear I/O Panel", "Front Panel", "Motherboard", "Dock/External", + "reserved", "reserved", "reserved", "NC/unused" + }; + + for (function = 0; function < 12; ++function) { + snd_ac97_write(ac97, AC97_FUNC_SELECT, function << 1); + info = snd_ac97_read(ac97, AC97_FUNC_INFO); + if (!(info & 0x0001)) + continue; + if (!header) { + snd_iprintf(buffer, "\n Gain Inverted Buffer delay Location\n"); + header = 1; + } + sense_info = snd_ac97_read(ac97, AC97_SENSE_INFO); + snd_iprintf(buffer, "%-17s: %3d.%d dBV %c %2d/fs %s\n", + function_names[function], + (info & 0x8000 ? -1 : 1) * ((info & 0x7000) >> 12) * 3 / 2, + ((info & 0x0800) >> 11) * 5, + info & 0x0400 ? 'X' : '-', + (info & 0x03e0) >> 5, + locations[sense_info >> 13]); + } +} + +static const char *snd_ac97_stereo_enhancements[] = +{ + /* 0 */ "No 3D Stereo Enhancement", + /* 1 */ "Analog Devices Phat Stereo", + /* 2 */ "Creative Stereo Enhancement", + /* 3 */ "National Semi 3D Stereo Enhancement", + /* 4 */ "YAMAHA Ymersion", + /* 5 */ "BBE 3D Stereo Enhancement", + /* 6 */ "Crystal Semi 3D Stereo Enhancement", + /* 7 */ "Qsound QXpander", + /* 8 */ "Spatializer 3D Stereo Enhancement", + /* 9 */ "SRS 3D Stereo Enhancement", + /* 10 */ "Platform Tech 3D Stereo Enhancement", + /* 11 */ "AKM 3D Audio", + /* 12 */ "Aureal Stereo Enhancement", + /* 13 */ "Aztech 3D Enhancement", + /* 14 */ "Binaura 3D Audio Enhancement", + /* 15 */ "ESS Technology Stereo Enhancement", + /* 16 */ "Harman International VMAx", + /* 17 */ "Nvidea/IC Ensemble/KS Waves 3D Stereo Enhancement", + /* 18 */ "Philips Incredible Sound", + /* 19 */ "Texas Instruments 3D Stereo Enhancement", + /* 20 */ "VLSI Technology 3D Stereo Enhancement", + /* 21 */ "TriTech 3D Stereo Enhancement", + /* 22 */ "Realtek 3D Stereo Enhancement", + /* 23 */ "Samsung 3D Stereo Enhancement", + /* 24 */ "Wolfson Microelectronics 3D Enhancement", + /* 25 */ "Delta Integration 3D Enhancement", + /* 26 */ "SigmaTel 3D Enhancement", + /* 27 */ "IC Ensemble/KS Waves", + /* 28 */ "Rockwell 3D Stereo Enhancement", + /* 29 */ "Reserved 29", + /* 30 */ "Reserved 30", + /* 31 */ "Reserved 31" +}; + +static void snd_ac97_proc_read_main(struct snd_ac97 *ac97, struct snd_info_buffer *buffer, int subidx) +{ + char name[64]; + unsigned short val, tmp, ext, mext; + static const char *spdif_slots[4] = { " SPDIF=3/4", " SPDIF=7/8", " SPDIF=6/9", " SPDIF=10/11" }; + static const char *spdif_rates[4] = { " Rate=44.1kHz", " Rate=res", " Rate=48kHz", " Rate=32kHz" }; + static const char *spdif_rates_cs4205[4] = { " Rate=48kHz", " Rate=44.1kHz", " Rate=res", " Rate=res" }; + static const char *double_rate_slots[4] = { "10/11", "7/8", "reserved", "reserved" }; + + snd_ac97_get_name(NULL, ac97->id, name, 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", ®, &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); + if ((entry = snd_info_create_card_entry(ac97->bus->card, name, ac97->bus->proc)) != NULL) { + snd_info_set_text_ops(entry, ac97, snd_ac97_proc_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + ac97->proc = entry; + sprintf(name, "%s#%d-%d+regs", prefix, ac97->addr, ac97->num); + if ((entry = snd_info_create_card_entry(ac97->bus->card, name, ac97->bus->proc)) != NULL) { + 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 + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + 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); + if ((entry = snd_info_create_card_entry(bus->card, name, bus->card->proc_root)) != NULL) { + entry->mode = S_IFDIR | 0555; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + 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..d9c54c08e --- /dev/null +++ b/sound/pci/ad1889.c @@ -0,0 +1,1051 @@ +/* Analog Devices 1889 audio driver + * + * This is a driver for the AD1889 PCI audio chipset found + * on the HP PA-RISC [BCJ]-xxx0 workstations. + * + * Copyright (C) 2004-2005, Kyle McMartin + * Copyright (C) 2005, Thibaut Varene + * Based on the OSS AD1889 driver by Randolph Chung + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * TODO: + * Do we need to take care of CCS register? + * Maybe we could use finer grained locking (separate locks for pb/cap)? + * Wishlist: + * Control Interface (mixer) support + * Better AC97 support (VSR...)? + * PM support + * MIDI support + * Game Port support + * SG DMA support (this will need *a lot* of work) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad1889.h" +#include "ac97/ac97_id.h" + +#define AD1889_DRVVER "Version: 1.7" + +MODULE_AUTHOR("Kyle McMartin , Thibaut Varene "); +MODULE_DESCRIPTION("Analog Devices AD1889 ALSA sound driver"); +MODULE_LICENSE("GPL"); +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 int +snd_ad1889_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int +snd_ad1889_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ad1889_hw_params, + .hw_free = snd_ad1889_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ad1889_hw_params, + .hw_free = snd_ad1889_hw_free, + .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; + + err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + BUFFER_BYTES_MAX / 2, + BUFFER_BYTES_MAX); + + if (err < 0) { + dev_err(chip->card->dev, "buffer allocation error: %d\n", err); + return err; + } + + 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) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, chip->card->driver, &entry)) + snd_info_set_text_ops(entry, 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 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 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; + synchronize_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 + */ + +#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..2fb1fbba3 --- /dev/null +++ b/sound/pci/ak4531_codec.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Universal routines for AK4531 codec + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Universal routines for AK4531 codec"); +MODULE_LICENSE("GPL"); +*/ + +static void snd_ak4531_proc_init(struct snd_card *card, struct snd_ak4531 *ak4531); + +/* + * + */ + +#if 0 + +static void snd_ak4531_dump(struct snd_ak4531 *ak4531) +{ + int idx; + + for (idx = 0; idx < 0x19; idx++) + printk(KERN_DEBUG "ak4531 0x%x: 0x%x\n", + idx, ak4531->regs[idx]); +} + +#endif + +/* + * + */ + +#define AK4531_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ak4531_info_single, \ + .get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \ + .private_value = reg | (shift << 16) | (mask << 24) | (invert << 22) } +#define AK4531_SINGLE_TLV(xname, xindex, reg, shift, mask, invert, xtlv) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .name = xname, .index = xindex, \ + .info = snd_ak4531_info_single, \ + .get = snd_ak4531_get_single, .put = snd_ak4531_put_single, \ + .private_value = reg | (shift << 16) | (mask << 24) | (invert << 22), \ + .tlv = { .p = (xtlv) } } + +static int snd_ak4531_info_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_ak4531_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ak4531 *ak4531 = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 16) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int val; + + 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 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 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 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(card, "ak4531", &entry)) + snd_info_set_text_ops(entry, ak4531, snd_ak4531_proc_read); +} diff --git a/sound/pci/ali5451/Makefile b/sound/pci/ali5451/Makefile new file mode 100644 index 000000000..713459c12 --- /dev/null +++ b/sound/pci/ali5451/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +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..9f569379b --- /dev/null +++ b/sound/pci/ali5451/ali5451.c @@ -0,0 +1,2281 @@ +/* + * Matt Wu + * Apr 26, 2001 + * Routines for control of ALi pci audio M5451 + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public Lcodecnse as published by + * the Free Software Foundation; either version 2 of the Lcodecnse, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public Lcodecnse for more details. + * + * You should have received a copy of the GNU General Public Lcodecnse + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Matt Wu "); +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, capture_flag; + 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 = capture_flag = 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); + if (pvoice->mode) + capture_flag = 1; + } + } + 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; + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + /* 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; + + snd_pcm_lib_free_pages(substream); + if (evoice) { + snd_ali_free_voice(codec, evoice); + pvoice->extra = NULL; + } + return 0; +} + +static int snd_ali_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_ali_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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 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 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, 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ali_hw_params, + .hw_free = snd_ali_hw_free, + .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 snd_ali_hw_params(substream, hw_params); +} + +static 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ali_modem_hw_params, + .hw_free = snd_ali_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ali_modem_hw_params, + .hw_free = snd_ali_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(codec->pci), + 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 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 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_pcm_suspend_all(chip->pcm[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) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(codec->card, "ali5451", &entry)) + snd_info_set_text_ops(entry, 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; + 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 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; + } + + synchronize_irq(pci->irq); + + 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..eaa2d853d --- /dev/null +++ b/sound/pci/als300.c @@ -0,0 +1,816 @@ +/* + * als300.c - driver for Avance Logic ALS300/ALS300+ soundcards. + * Copyright (C) 2005 by Ash Willis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * TODO + * 4 channel playback for ALS300+ + * gameport + * mpu401 + * opl3 + * + * NOTES + * The BLOCK_COUNTER registers for the ALS300(+) return a figure related to + * the position in the current period, NOT the whole buffer. It is important + * to know which period we are in so we can calculate the correct pointer. + * This is why we always use 2 periods. We can then use a flip-flop variable + * to keep track of what period we are in. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* snd_als300_set_irq_flag */ +#define IRQ_DISABLE 0 +#define IRQ_ENABLE 1 + +/* I/O port layout */ +#define AC97_ACCESS 0x00 +#define AC97_READ 0x04 +#define AC97_STATUS 0x06 +#define AC97_DATA_AVAIL (1<<6) +#define AC97_BUSY (1<<7) +#define ALS300_IRQ_STATUS 0x07 /* ALS300 Only */ +#define IRQ_PLAYBACK (1<<3) +#define IRQ_CAPTURE (1<<2) +#define GCR_DATA 0x08 +#define GCR_INDEX 0x0C +#define ALS300P_DRAM_IRQ_STATUS 0x0D /* ALS300+ Only */ +#define MPU_IRQ_STATUS 0x0E /* ALS300 Rev. E+, ALS300+ */ +#define ALS300P_IRQ_STATUS 0x0F /* ALS300+ Only */ + +/* General Control Registers */ +#define PLAYBACK_START 0x80 +#define PLAYBACK_END 0x81 +#define PLAYBACK_CONTROL 0x82 +#define TRANSFER_START (1<<16) +#define FIFO_PAUSE (1<<17) +#define RECORD_START 0x83 +#define RECORD_END 0x84 +#define RECORD_CONTROL 0x85 +#define DRAM_WRITE_CONTROL 0x8B +#define WRITE_TRANS_START (1<<16) +#define DRAM_MODE_2 (1<<17) +#define MISC_CONTROL 0x8C +#define IRQ_SET_BIT (1<<15) +#define VMUTE_NORMAL (1<<20) +#define MMUTE_NORMAL (1<<21) +#define MUS_VOC_VOL 0x8E +#define PLAYBACK_BLOCK_COUNTER 0x9A +#define RECORD_BLOCK_COUNTER 0x9B + +#define DEBUG_PLAY_REC 0 + +#if DEBUG_PLAY_REC +#define snd_als300_dbgplay(format, args...) printk(KERN_ERR format, ##args) +#else +#define snd_als300_dbgplay(format, args...) +#endif + +enum {DEVICE_ALS300, DEVICE_ALS300_PLUS}; + +MODULE_AUTHOR("Ash Willis "); +MODULE_DESCRIPTION("Avance Logic ALS300"); +MODULE_LICENSE("GPL"); +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 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; + snd_pcm_lib_free_pages(substream); + 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; + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_als300_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_als300_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_als300_pcm_hw_params, + .hw_free = snd_als300_pcm_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_als300_pcm_hw_params, + .hw_free = snd_als300_pcm_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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 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; + + + 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_pcm_suspend_all(chip->pcm); + 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..26b097ede --- /dev/null +++ b/sound/pci/als4000.c @@ -0,0 +1,1037 @@ +/* + * card-als4000.c - driver for Avance Logic ALS4000 based soundcards. + * Copyright (C) 2000 by Bart Hartgers , + * Jaroslav Kysela + * Copyright (C) 2002, 2008 by Andreas Mohr + * + * Framework borrowed from Massimo Piccioni's card-als100.c. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * NOTES + * + * Since Avance does not provide any meaningful documentation, and I + * bought an ALS4000 based soundcard, I was forced to base this driver + * on reverse engineering. + * + * Note: this is no longer true (thank you!): + * pretty verbose chip docu (ALS4000a.PDF) can be found on the ALSA web site. + * Page numbers stated anywhere below with the "SPECS_PAGE:" tag + * refer to: ALS4000a.PDF specs Ver 1.0, May 28th, 1998. + * + * The ALS4000 seems to be the PCI-cousin of the ALS100. It contains an + * ALS100-like SB DSP/mixer, an OPL3 synth, a MPU401 and a gameport + * interface. These subsystems can be mapped into ISA io-port space, + * using the PCI-interface. In addition, the PCI-bit provides DMA and IRQ + * services to the subsystems. + * + * While ALS4000 is very similar to a SoundBlaster, the differences in + * DMA and capturing require more changes to the SoundBlaster than + * desirable, so I made this separate driver. + * + * The ALS4000 can do real full duplex playback/capture. + * + * FMDAC: + * - 0x4f -> port 0x14 + * - port 0x15 |= 1 + * + * Enable/disable 3D sound: + * - 0x50 -> port 0x14 + * - change bit 6 (0x40) of port 0x15 + * + * Set QSound: + * - 0xdb -> port 0x14 + * - set port 0x15: + * 0x3e (mode 3), 0x3c (mode 2), 0x3a (mode 1), 0x38 (mode 0) + * + * Set KSound: + * - value -> some port 0x0c0d + * + * ToDo: + * - by default, don't enable legacy game and use PCI game I/O + * - power management? (card can do voice wakeup according to datasheet!!) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Bart Hartgers , Andreas Mohr"); +MODULE_DESCRIPTION("Avance Logic ALS4000"); +MODULE_LICENSE("GPL"); +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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_als4000_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +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; + snd_pcm_lib_free_pages(substream); + 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; + snd_pcm_lib_free_pages(substream); + return 0; +} + +/******************************************************************/ + +static const struct snd_pcm_ops snd_als4000_playback_ops = { + .open = snd_als4000_playback_open, + .close = snd_als4000_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_als4000_hw_params, + .hw_free = snd_als4000_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_als4000_hw_params, + .hw_free = snd_als4000_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), + 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_pcm_suspend_all(chip->pcm); + 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..391830a45 --- /dev/null +++ b/sound/pci/asihpi/Makefile @@ -0,0 +1,5 @@ +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..a31fe1550 --- /dev/null +++ b/sound/pci/asihpi/asihpi.c @@ -0,0 +1,3053 @@ +/* + * Asihpi soundcard + * Copyright (c) by AudioScience Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 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 + * + * + * The following is not a condition of use, merely a request: + * If you modify this program, particularly if you fix errors, AudioScience Inc + * would appreciate it if you grant us the right to use those modifications + * for any purpose including commercial applications. + */ + +#include "hpi_internal.h" +#include "hpi_version.h" +#include "hpimsginit.h" +#include "hpioctl.h" +#include "hpicmn.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("AudioScience inc. "); +MODULE_DESCRIPTION("AudioScience ALSA ASI5xxx ASI6xxx ASI87xx ASI89xx " + HPI_VER_STRING); + +#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; + struct tasklet_struct t; + 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 inline u16 hpi_stream_group_get_map( + u32 h_stream, u32 *mo, u32 *mi) +{ + if (hpi_handle_object(h_stream) == HPI_OBJ_OSTREAM) + return hpi_outstream_group_get_map(h_stream, mo, mi); + else + return hpi_instream_group_get_map(h_stream, mo, mi); +} + +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 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_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (err < 0) + return err; + 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); + + snd_pcm_lib_free_pages(substream); + 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()); + tasklet_disable(&card->t); + card->llmode_streampriv = dpcm; + tasklet_enable(&card->t); + + 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)); + + if (in_interrupt()) + card->llmode_streampriv = NULL; + else { + tasklet_disable(&card->t); + card->llmode_streampriv = NULL; + tasklet_enable(&card->t); + } +} + +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_int_task(unsigned long data) +{ + struct hpi_adapter *a = (struct hpi_adapter *)data; + 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); +} + +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; + tasklet_schedule(&asihpi->t); +} + +/***************************** PLAYBACK OPS ****************/ +static int snd_card_asihpi_playback_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + char name[16]; + snd_pcm_debug_name(substream, name, sizeof(name)); + snd_printddd(KERN_INFO "%s ioctl %d\n", name, cmd); + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +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, + .ioctl = snd_card_asihpi_playback_ioctl, + .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_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +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, + .ioctl = snd_card_asihpi_capture_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(asihpi->pci), + 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) +{ + int change; + 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; + */ + change = 1; + hpi_handle_error(hpi_volume_set_gain(h_control, an_gain_mB)); + return change; +} + +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; + int change = 1; + /* 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 change; +} + +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]; + u32 num_bands = 0; + + 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]; + u32 num_bands = 0; + + 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 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) +{ + int err; + 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; + + err = + 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) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(asihpi->card, "info", &entry)) + snd_info_set_text_ops(entry, 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; + tasklet_init(&asihpi->t, snd_card_asihpi_int_task, + (unsigned long)hpi); + 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); + struct snd_card_asihpi *asihpi = hpi->snd_card->private_data; + + /* 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)); + tasklet_kill(&asihpi->t); + } + + 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..4466bd2c5 --- /dev/null +++ b/sound/pci/asihpi/hpi.h @@ -0,0 +1,1735 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +*/ +/** \file hpi.h + + AudioScience Hardware Programming Interface (HPI) + public API definition. + + The HPI is a low-level hardware abstraction layer to all + AudioScience digital audio adapters + +(C) Copyright AudioScience Inc. 1998-2010 +*/ + +#ifndef _HPI_H_ +#define _HPI_H_ + +#include +#define HPI_BUILD_KERNEL_MODE + +/******************************************************************************/ +/******** HPI API DEFINITIONS *****/ +/******************************************************************************/ + +/*******************************************/ +/** Audio format types +\ingroup stream +*/ +enum HPI_FORMATS { +/** Used internally on adapter. */ + HPI_FORMAT_MIXER_NATIVE = 0, +/** 8-bit unsigned PCM. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM8_UNSIGNED = 1, +/** 16-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM16_SIGNED = 2, +/** MPEG-1 Layer-1. */ + HPI_FORMAT_MPEG_L1 = 3, +/** MPEG-1 Layer-2. + +Windows equivalent is WAVE_FORMAT_MPEG. + +The following table shows what combinations of mode and bitrate are possible: + + + +

Bitrate (kbs)

+

Mono

+

Stereo,
Joint Stereo or
Dual Channel

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

Bitrate (kbs)

+

Mono
Stereo @ 8,
11.025 and
12kHz*

+

Mono
Stereo @ 16,
22.050 and
24kHz*

+

Mono
Stereo @ 32,
44.1 and
48kHz

+ +
16XX_ +
24XX_ +
32XXX +
40XXX +
48XXX +
56XXX +
64XXX +
80_XX +
96_XX +
112_XX +
128_XX +
144_X_ +
160_XX +
192__X +
224__X +
256-_X +
320-_X +
+\b * Available on the ASI6000 series only +*/ + HPI_FORMAT_MPEG_L3 = 5, +/** Dolby AC-2. */ + HPI_FORMAT_DOLBY_AC2 = 6, +/** Dolbt AC-3. */ + HPI_FORMAT_DOLBY_AC3 = 7, +/** 16-bit PCM big-endian. */ + HPI_FORMAT_PCM16_BIGENDIAN = 8, +/** TAGIT-1 algorithm - hits. */ + HPI_FORMAT_AA_TAGIT1_HITS = 9, +/** TAGIT-1 algorithm - inserts. */ + HPI_FORMAT_AA_TAGIT1_INSERTS = 10, +/** 32-bit signed PCM. Windows equivalent is WAVE_FORMAT_PCM. +Each sample is a 32bit word. The most significant 24 bits contain a 24-bit +sample and the least significant 8 bits are set to 0. +*/ + HPI_FORMAT_PCM32_SIGNED = 11, +/** Raw bitstream - unknown format. */ + HPI_FORMAT_RAW_BITSTREAM = 12, +/** TAGIT-1 algorithm hits - extended. */ + HPI_FORMAT_AA_TAGIT1_HITS_EX1 = 13, +/** 32-bit PCM as an IEEE float. Windows equivalent is WAVE_FORMAT_IEEE_FLOAT. +Each sample is a 32bit word in IEEE754 floating point format. +The range is +1.0 to -1.0, which corresponds to digital fullscale. +*/ + HPI_FORMAT_PCM32_FLOAT = 14, +/** 24-bit PCM signed. Windows equivalent is WAVE_FORMAT_PCM. */ + HPI_FORMAT_PCM24_SIGNED = 15, +/** OEM format 1 - private. */ + HPI_FORMAT_OEM1 = 16, +/** OEM format 2 - private. */ + HPI_FORMAT_OEM2 = 17, +/** Undefined format. */ + HPI_FORMAT_UNDEFINED = 0xffff +}; + +/*******************************************/ +/** Stream States +\ingroup stream +*/ +enum HPI_STREAM_STATES { + /** State stopped - stream is stopped. */ + HPI_STATE_STOPPED = 1, + /** State playing - stream is playing audio. */ + HPI_STATE_PLAYING = 2, + /** State recording - stream is recording. */ + HPI_STATE_RECORDING = 3, + /** State drained - playing stream ran out of data to play. */ + HPI_STATE_DRAINED = 4, + /** State generate sine - to be implemented. */ + HPI_STATE_SINEGEN = 5, + /** State wait - used for inter-card sync to mean waiting for all + cards to be ready. */ + HPI_STATE_WAIT = 6 +}; +/*******************************************/ +/** Source node types +\ingroup mixer +*/ +enum HPI_SOURCENODES { + /** This define can be used instead of 0 to indicate + that there is no valid source node. A control that + exists on a destination node can be searched for using a source + node value of either 0, or HPI_SOURCENODE_NONE */ + HPI_SOURCENODE_NONE = 100, + /** Out Stream (Play) node. */ + HPI_SOURCENODE_OSTREAM = 101, + /** Line in node - could be analog, AES/EBU or network. */ + HPI_SOURCENODE_LINEIN = 102, + HPI_SOURCENODE_AESEBU_IN = 103, /**< AES/EBU input node. */ + HPI_SOURCENODE_TUNER = 104, /**< tuner node. */ + HPI_SOURCENODE_RF = 105, /**< RF input node. */ + HPI_SOURCENODE_CLOCK_SOURCE = 106, /**< clock source node. */ + HPI_SOURCENODE_RAW_BITSTREAM = 107, /**< raw bitstream node. */ + HPI_SOURCENODE_MICROPHONE = 108, /**< microphone node. */ + /** Cobranet input node - + Audio samples come from the Cobranet network and into the device. */ + HPI_SOURCENODE_COBRANET = 109, + HPI_SOURCENODE_ANALOG = 110, /**< analog input node. */ + HPI_SOURCENODE_ADAPTER = 111, /**< adapter node. */ + /** RTP stream input node - This node is a destination for + packets of RTP audio samples from other devices. */ + HPI_SOURCENODE_RTP_DESTINATION = 112, + HPI_SOURCENODE_INTERNAL = 113, /**< node internal to the device. */ + HPI_SOURCENODE_AVB = 114, /**< AVB input stream */ + HPI_SOURCENODE_BLULINK = 115, /**< BLU-link input channel */ + /* !!!Update this AND hpidebug.h if you add a new sourcenode type!!! */ + HPI_SOURCENODE_LAST_INDEX = 115 /**< largest ID */ + /* AX6 max sourcenode types = 15 */ +}; + +/*******************************************/ +/** Destination node types +\ingroup mixer +*/ +enum HPI_DESTNODES { + /** This define can be used instead of 0 to indicate + that there is no valid destination node. A control that + exists on a source node can be searched for using a destination + node value of either 0, or HPI_DESTNODE_NONE */ + HPI_DESTNODE_NONE = 200, + /** In Stream (Record) node. */ + HPI_DESTNODE_ISTREAM = 201, + HPI_DESTNODE_LINEOUT = 202, /**< line out node. */ + HPI_DESTNODE_AESEBU_OUT = 203, /**< AES/EBU output node. */ + HPI_DESTNODE_RF = 204, /**< RF output node. */ + HPI_DESTNODE_SPEAKER = 205, /**< speaker output node. */ + /** Cobranet output node - + Audio samples from the device are sent out on the Cobranet network.*/ + HPI_DESTNODE_COBRANET = 206, + HPI_DESTNODE_ANALOG = 207, /**< analog output node. */ + /** RTP stream output node - This node is a source for + packets of RTP audio samples that are sent to other devices. */ + HPI_DESTNODE_RTP_SOURCE = 208, + HPI_DESTNODE_AVB = 209, /**< AVB output stream */ + HPI_DESTNODE_INTERNAL = 210, /**< node internal to the device. */ + HPI_DESTNODE_BLULINK = 211, /**< BLU-link output channel. */ + /* !!!Update this AND hpidebug.h if you add a new destnode type!!! */ + HPI_DESTNODE_LAST_INDEX = 211 /**< largest ID */ + /* AX6 max destnode types = 15 */ +}; + +/*******************************************/ +/** Mixer control types +\ingroup mixer +*/ +enum HPI_CONTROLS { + HPI_CONTROL_GENERIC = 0, /**< generic control. */ + HPI_CONTROL_CONNECTION = 1, /**< A connection between nodes. */ + HPI_CONTROL_VOLUME = 2, /**< volume control - works in dB_fs. */ + HPI_CONTROL_METER = 3, /**< peak meter control. */ + HPI_CONTROL_MUTE = 4, /*mute control - not used at present. */ + HPI_CONTROL_MULTIPLEXER = 5, /**< multiplexer control. */ + + HPI_CONTROL_AESEBU_TRANSMITTER = 6, /**< AES/EBU transmitter control */ + HPI_CONTROL_AESEBUTX = 6, /* HPI_CONTROL_AESEBU_TRANSMITTER */ + + HPI_CONTROL_AESEBU_RECEIVER = 7, /**< AES/EBU receiver control. */ + HPI_CONTROL_AESEBURX = 7, /* HPI_CONTROL_AESEBU_RECEIVER */ + + HPI_CONTROL_LEVEL = 8, /**< level/trim control - works in d_bu. */ + HPI_CONTROL_TUNER = 9, /**< tuner control. */ +/* HPI_CONTROL_ONOFFSWITCH = 10 */ + HPI_CONTROL_VOX = 11, /**< vox control. */ +/* HPI_CONTROL_AES18_TRANSMITTER = 12 */ +/* HPI_CONTROL_AES18_RECEIVER = 13 */ +/* HPI_CONTROL_AES18_BLOCKGENERATOR = 14 */ + HPI_CONTROL_CHANNEL_MODE = 15, /**< channel mode control. */ + + HPI_CONTROL_BITSTREAM = 16, /**< bitstream control. */ + HPI_CONTROL_SAMPLECLOCK = 17, /**< sample clock control. */ + HPI_CONTROL_MICROPHONE = 18, /**< microphone control. */ + HPI_CONTROL_PARAMETRIC_EQ = 19, /**< parametric EQ control. */ + HPI_CONTROL_EQUALIZER = 19, /*HPI_CONTROL_PARAMETRIC_EQ */ + + HPI_CONTROL_COMPANDER = 20, /**< compander control. */ + HPI_CONTROL_COBRANET = 21, /**< cobranet control. */ + HPI_CONTROL_TONEDETECTOR = 22, /**< tone detector control. */ + HPI_CONTROL_SILENCEDETECTOR = 23, /**< silence detector control. */ + HPI_CONTROL_PAD = 24, /**< tuner PAD control. */ + HPI_CONTROL_SRC = 25, /**< samplerate converter control. */ + HPI_CONTROL_UNIVERSAL = 26, /**< universal control. */ + +/* !!! Update this AND hpidebug.h if you add a new control type!!!*/ + HPI_CONTROL_LAST_INDEX = 26 /** 4 line outs = 1 to 8 channel streams), + 4 lineins -> 1 instream (1 to 8 channel streams) at 0-48kHz. + For more info see the SSX Specification. +*/ + HPI_ADAPTER_MODE_MULTICHANNEL = 9, + +/** 12 outstream mode. +- ASI6514, ASI6614: 2 instreams +- ASI6540,ASI6544: 8 instreams +- ASI6640,ASI6644: 8 instreams +*/ + HPI_ADAPTER_MODE_12OSTREAM = 10, + +/** 9 outstream mode. +- ASI6044: 8 instreams +*/ + HPI_ADAPTER_MODE_9OSTREAM = 11, + +/** mono mode. +- ASI6416: 16 outstreams/instreams +- ASI5402: 2 outstreams/instreams +*/ + HPI_ADAPTER_MODE_MONO = 12, + +/** Low latency mode. +- ASI6416/ASI6316: 1 16 channel outstream and instream +*/ + HPI_ADAPTER_MODE_LOW_LATENCY = 13 +}; + +/* Note, adapters can have more than one capability - +encoding as bitfield is recommended. */ +#define HPI_CAPABILITY_NONE (0) +#define HPI_CAPABILITY_MPEG_LAYER3 (1) + +/* Set this equal to maximum capability index, +Must not be greater than 32 - see axnvdef.h */ +#define HPI_CAPABILITY_MAX 1 +/* #define HPI_CAPABILITY_AAC 2 */ + +/******************************************* STREAM ATTRIBUTES ****/ + +/** MPEG Ancillary Data modes + +The mode for the ancillary data insertion or extraction to operate in. +\ingroup stream +*/ +enum HPI_MPEG_ANC_MODES { + /** the MPEG frames have energy information stored in them (5 bytes per stereo frame, 3 per mono) */ + HPI_MPEG_ANC_HASENERGY = 0, + /** the entire ancillary data field is taken up by data from the Anc data buffer + On encode, the encoder will insert the energy bytes before filling the remainder + of the ancillary data space with data from the ancillary data buffer. + */ + HPI_MPEG_ANC_RAW = 1 +}; + +/** Ancillary Data Alignment +\ingroup instream +*/ +enum HPI_ISTREAM_MPEG_ANC_ALIGNS { + /** data is packed against the end of data, then padded to the end of frame */ + HPI_MPEG_ANC_ALIGN_LEFT = 0, + /** data is packed against the end of the frame */ + HPI_MPEG_ANC_ALIGN_RIGHT = 1 +}; + +/** MPEG modes +MPEG modes - can be used optionally for HPI_FormatCreate() +parameter dwAttributes. + +Using any mode setting other than HPI_MPEG_MODE_DEFAULT +with single channel format will return an error. +\ingroup stream +*/ +enum HPI_MPEG_MODES { +/** Causes the MPEG-1 Layer II bitstream to be recorded +in single_channel mode when the number of channels is 1 and in stereo when the +number of channels is 2. */ + HPI_MPEG_MODE_DEFAULT = 0, + /** Standard stereo without joint-stereo compression */ + HPI_MPEG_MODE_STEREO = 1, + /** Joint stereo */ + HPI_MPEG_MODE_JOINTSTEREO = 2, + /** Left and Right channels are completely independent */ + HPI_MPEG_MODE_DUALCHANNEL = 3 +}; +/******************************************* MIXER ATTRIBUTES ****/ + +/* \defgroup mixer_flags Mixer flags for HPI_MIXER_GET_CONTROL_MULTIPLE_VALUES +{ +*/ +#define HPI_MIXER_GET_CONTROL_MULTIPLE_CHANGED (0) +#define HPI_MIXER_GET_CONTROL_MULTIPLE_RESET (1) +/*}*/ + +/** Commands used by HPI_MixerStore() +\ingroup mixer +*/ +enum HPI_MIXER_STORE_COMMAND { +/** Save all mixer control settings. */ + HPI_MIXER_STORE_SAVE = 1, +/** Restore all controls from saved. */ + HPI_MIXER_STORE_RESTORE = 2, +/** Delete saved control settings. */ + HPI_MIXER_STORE_DELETE = 3, +/** Enable auto storage of some control settings. */ + HPI_MIXER_STORE_ENABLE = 4, +/** Disable auto storage of some control settings. */ + HPI_MIXER_STORE_DISABLE = 5, +/** Unimplemented - save the attributes of a single control. */ + HPI_MIXER_STORE_SAVE_SINGLE = 6 +}; + +/****************************/ +/* CONTROL ATTRIBUTE VALUES */ +/****************************/ + +/** Used by mixer plugin enable functions + +E.g. HPI_ParametricEq_SetState() +\ingroup mixer +*/ +enum HPI_SWITCH_STATES { + HPI_SWITCH_OFF = 0, /**< turn the mixer plugin on. */ + HPI_SWITCH_ON = 1 /**< turn the mixer plugin off. */ +}; + +/* Volume control special gain values */ + +/** volumes units are 100ths of a dB +\ingroup volume +*/ +#define HPI_UNITS_PER_dB 100 +/** turns volume control OFF or MUTE +\ingroup volume +*/ +#define HPI_GAIN_OFF (-100 * HPI_UNITS_PER_dB) + +/** channel mask specifying all channels +\ingroup volume +*/ +#define HPI_BITMASK_ALL_CHANNELS (0xFFFFFFFF) + +/** value returned for no signal +\ingroup meter +*/ +#define HPI_METER_MINIMUM (-150 * HPI_UNITS_PER_dB) + +/** autofade profiles +\ingroup volume +*/ +enum HPI_VOLUME_AUTOFADES { +/** log fade - dB attenuation changes linearly over time */ + HPI_VOLUME_AUTOFADE_LOG = 2, +/** linear fade - amplitude changes linearly */ + HPI_VOLUME_AUTOFADE_LINEAR = 3 +}; + +/** The physical encoding format of the AESEBU I/O. + +Used in HPI_Aesebu_Transmitter_SetFormat(), HPI_Aesebu_Receiver_SetFormat() +along with related Get and Query functions +\ingroup aestx +*/ +enum HPI_AESEBU_FORMATS { +/** AES/EBU physical format - AES/EBU balanced "professional" */ + HPI_AESEBU_FORMAT_AESEBU = 1, +/** AES/EBU physical format - S/PDIF unbalanced "consumer" */ + HPI_AESEBU_FORMAT_SPDIF = 2 +}; + +/** AES/EBU error status bits + +Returned by HPI_Aesebu_Receiver_GetErrorStatus() +\ingroup aesrx +*/ +enum HPI_AESEBU_ERRORS { +/** bit0: 1 when PLL is not locked */ + HPI_AESEBU_ERROR_NOT_LOCKED = 0x01, +/** bit1: 1 when signal quality is poor */ + HPI_AESEBU_ERROR_POOR_QUALITY = 0x02, +/** bit2: 1 when there is a parity error */ + HPI_AESEBU_ERROR_PARITY_ERROR = 0x04, +/** bit3: 1 when there is a bi-phase coding violation */ + HPI_AESEBU_ERROR_BIPHASE_VIOLATION = 0x08, +/** bit4: 1 when the validity bit is high */ + HPI_AESEBU_ERROR_VALIDITY = 0x10, +/** bit5: 1 when the CRC error bit is high */ + HPI_AESEBU_ERROR_CRC = 0x20 +}; + +/** \addtogroup pad +\{ +*/ +/** The text string containing the station/channel combination. */ +#define HPI_PAD_CHANNEL_NAME_LEN 16 +/** The text string containing the artist. */ +#define HPI_PAD_ARTIST_LEN 64 +/** The text string containing the title. */ +#define HPI_PAD_TITLE_LEN 64 +/** The text string containing the comment. */ +#define HPI_PAD_COMMENT_LEN 256 +/** The PTY when the tuner has not received any PTY. */ +#define HPI_PAD_PROGRAM_TYPE_INVALID 0xffff +/** \} */ + +/** Data types for PTY string translation. +\ingroup rds +*/ +enum eHPI_RDS_type { + HPI_RDS_DATATYPE_RDS = 0, /**< RDS bitstream.*/ + HPI_RDS_DATATYPE_RBDS = 1 /**< RBDS bitstream.*/ +}; + +/** Tuner bands + +Used for HPI_Tuner_SetBand(),HPI_Tuner_GetBand() +\ingroup tuner +*/ +enum HPI_TUNER_BAND { + HPI_TUNER_BAND_AM = 1, /**< AM band */ + HPI_TUNER_BAND_FM = 2, /**< FM band (mono) */ + HPI_TUNER_BAND_TV_NTSC_M = 3, /**< NTSC-M TV band*/ + HPI_TUNER_BAND_TV = 3, /* use TV_NTSC_M */ + HPI_TUNER_BAND_FM_STEREO = 4, /**< FM band (stereo) */ + HPI_TUNER_BAND_AUX = 5, /**< auxiliary input */ + HPI_TUNER_BAND_TV_PAL_BG = 6, /**< PAL-B/G TV band*/ + HPI_TUNER_BAND_TV_PAL_I = 7, /**< PAL-I TV band*/ + HPI_TUNER_BAND_TV_PAL_DK = 8, /**< PAL-D/K TV band*/ + HPI_TUNER_BAND_TV_SECAM_L = 9, /**< SECAM-L TV band*/ + HPI_TUNER_BAND_DAB = 10, + HPI_TUNER_BAND_LAST = 10 /**< the index of the last tuner band. */ +}; + +/** Tuner mode attributes + +Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode() +\ingroup tuner + +*/ +enum HPI_TUNER_MODES { + HPI_TUNER_MODE_RSS = 1, /**< control RSS */ + HPI_TUNER_MODE_RDS = 2 /**< control RBDS/RDS */ +}; + +/** Tuner mode attribute values + +Used by HPI_Tuner_SetMode(), HPI_Tuner_GetMode() +\ingroup tuner +*/ +enum HPI_TUNER_MODE_VALUES { +/* RSS attribute values */ + HPI_TUNER_MODE_RSS_DISABLE = 0, /**< RSS disable */ + HPI_TUNER_MODE_RSS_ENABLE = 1, /**< RSS enable */ + +/* RDS mode attributes */ + HPI_TUNER_MODE_RDS_DISABLE = 0, /**< RDS - disabled */ + HPI_TUNER_MODE_RDS_RDS = 1, /**< RDS - RDS mode */ + HPI_TUNER_MODE_RDS_RBDS = 2 /**< RDS - RBDS mode */ +}; + +/** Tuner Status Bits + +These bitfield values are returned by a call to HPI_Tuner_GetStatus(). +Multiple fields are returned from a single call. +\ingroup tuner +*/ +enum HPI_TUNER_STATUS_BITS { + HPI_TUNER_VIDEO_COLOR_PRESENT = 0x0001, /**< video color is present. */ + HPI_TUNER_VIDEO_IS_60HZ = 0x0020, /**< 60 hz video detected. */ + HPI_TUNER_VIDEO_HORZ_SYNC_MISSING = 0x0040, /**< video HSYNC is missing. */ + HPI_TUNER_VIDEO_STATUS_VALID = 0x0100, /**< video status is valid. */ + HPI_TUNER_DIGITAL = 0x0200, /**< tuner reports digital programming. */ + HPI_TUNER_MULTIPROGRAM = 0x0400, /**< tuner reports multiple programs. */ + HPI_TUNER_PLL_LOCKED = 0x1000, /**< the tuner's PLL is locked. */ + HPI_TUNER_FM_STEREO = 0x2000 /**< tuner reports back FM stereo. */ +}; + +/** Channel Modes +Used for HPI_ChannelModeSet/Get() +\ingroup channelmode +*/ +enum HPI_CHANNEL_MODES { +/** Left channel out = left channel in, Right channel out = right channel in. */ + HPI_CHANNEL_MODE_NORMAL = 1, +/** Left channel out = right channel in, Right channel out = left channel in. */ + HPI_CHANNEL_MODE_SWAP = 2, +/** Left channel out = left channel in, Right channel out = left channel in. */ + HPI_CHANNEL_MODE_LEFT_TO_STEREO = 3, +/** Left channel out = right channel in, Right channel out = right channel in.*/ + HPI_CHANNEL_MODE_RIGHT_TO_STEREO = 4, +/** Left channel out = (left channel in + right channel in)/2, + Right channel out = mute. */ + HPI_CHANNEL_MODE_STEREO_TO_LEFT = 5, +/** Left channel out = mute, + Right channel out = (right channel in + left channel in)/2. */ + HPI_CHANNEL_MODE_STEREO_TO_RIGHT = 6, + HPI_CHANNEL_MODE_LAST = 6 +}; + +/** SampleClock source values +\ingroup sampleclock +*/ +enum HPI_SAMPLECLOCK_SOURCES { +/** The sampleclock output is derived from its local samplerate generator. + The local samplerate may be set using HPI_SampleClock_SetLocalRate(). */ + HPI_SAMPLECLOCK_SOURCE_LOCAL = 1, +/** The adapter is clocked from a dedicated AES/EBU SampleClock input.*/ + HPI_SAMPLECLOCK_SOURCE_AESEBU_SYNC = 2, +/** From external wordclock connector */ + HPI_SAMPLECLOCK_SOURCE_WORD = 3, +/** Board-to-board header */ + HPI_SAMPLECLOCK_SOURCE_WORD_HEADER = 4, +/** FUTURE - SMPTE clock. */ + HPI_SAMPLECLOCK_SOURCE_SMPTE = 5, +/** One of the aesebu inputs */ + HPI_SAMPLECLOCK_SOURCE_AESEBU_INPUT = 6, +/** From a network interface e.g. Cobranet or Livewire at either 48 or 96kHz */ + HPI_SAMPLECLOCK_SOURCE_NETWORK = 8, +/** From previous adjacent module (ASI2416 only)*/ + HPI_SAMPLECLOCK_SOURCE_PREV_MODULE = 10, +/** Blu link sample clock*/ + HPI_SAMPLECLOCK_SOURCE_BLULINK = 11, +/*! Update this if you add a new clock source.*/ + HPI_SAMPLECLOCK_SOURCE_LAST = 11 +}; + +/** Equalizer filter types. Used by HPI_ParametricEq_SetBand() +\ingroup parmeq +*/ +enum HPI_FILTER_TYPE { + HPI_FILTER_TYPE_BYPASS = 0, /**< filter is turned off */ + + HPI_FILTER_TYPE_LOWSHELF = 1, /**< EQ low shelf */ + HPI_FILTER_TYPE_HIGHSHELF = 2, /**< EQ high shelf */ + HPI_FILTER_TYPE_EQ_BAND = 3, /**< EQ gain */ + + HPI_FILTER_TYPE_LOWPASS = 4, /**< standard low pass */ + HPI_FILTER_TYPE_HIGHPASS = 5, /**< standard high pass */ + HPI_FILTER_TYPE_BANDPASS = 6, /**< standard band pass */ + HPI_FILTER_TYPE_BANDSTOP = 7 /**< standard band stop/notch */ +}; + +/** Async Event sources +\ingroup async +*/ +enum ASYNC_EVENT_SOURCES { + HPI_ASYNC_EVENT_GPIO = 1, /**< GPIO event. */ + HPI_ASYNC_EVENT_SILENCE = 2, /**< silence event detected. */ + HPI_ASYNC_EVENT_TONE = 3 /**< tone event detected. */ +}; +/*******************************************/ +/** HPI Error codes + +Almost all HPI functions return an error code +A return value of zero means there was no error. +Otherwise one of these error codes is returned. +Error codes can be converted to a descriptive string using HPI_GetErrorText() + +\note When a new error code is added HPI_GetErrorText() MUST be updated. +\note Codes 1-100 are reserved for driver use +\ingroup utility +*/ +enum HPI_ERROR_CODES { + /** Message type does not exist. */ + HPI_ERROR_INVALID_TYPE = 100, + /** Object type does not exist. */ + HPI_ERROR_INVALID_OBJ = 101, + /** Function does not exist. */ + HPI_ERROR_INVALID_FUNC = 102, + /** The specified object does not exist. */ + HPI_ERROR_INVALID_OBJ_INDEX = 103, + /** Trying to access an object that has not been opened yet. */ + HPI_ERROR_OBJ_NOT_OPEN = 104, + /** Trying to open an already open object. */ + HPI_ERROR_OBJ_ALREADY_OPEN = 105, + /** PCI, ISA resource not valid. */ + HPI_ERROR_INVALID_RESOURCE = 106, + /* HPI_ERROR_SUBSYSFINDADAPTERS_GETINFO= 107 */ + /** Default response was never updated with actual error code. */ + HPI_ERROR_INVALID_RESPONSE = 108, + /** wSize field of response was not updated, + indicating that the message was not processed. */ + HPI_ERROR_PROCESSING_MESSAGE = 109, + /** The network did not respond in a timely manner. */ + HPI_ERROR_NETWORK_TIMEOUT = 110, + /* An HPI handle is invalid (uninitialised?). */ + HPI_ERROR_INVALID_HANDLE = 111, + /** A function or attribute has not been implemented yet. */ + HPI_ERROR_UNIMPLEMENTED = 112, + /** There are too many clients attempting + to access a network resource. */ + HPI_ERROR_NETWORK_TOO_MANY_CLIENTS = 113, + /** Response buffer passed to HPI_Message + was smaller than returned response. + wSpecificError field of hpi response contains the required size. + */ + HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL = 114, + /** The returned response did not match the sent message */ + HPI_ERROR_RESPONSE_MISMATCH = 115, + /** A control setting that should have been cached was not. */ + HPI_ERROR_CONTROL_CACHING = 116, + /** A message buffer in the path to the adapter was smaller + than the message size. + wSpecificError field of hpi response contains the actual size. + */ + HPI_ERROR_MESSAGE_BUFFER_TOO_SMALL = 117, + + /* HPI_ERROR_TOO_MANY_ADAPTERS= 200 */ + /** Bad adpater. */ + HPI_ERROR_BAD_ADAPTER = 201, + /** Adapter number out of range or not set properly. */ + HPI_ERROR_BAD_ADAPTER_NUMBER = 202, + /** 2 adapters with the same adapter number. */ + HPI_ERROR_DUPLICATE_ADAPTER_NUMBER = 203, + /** DSP code failed to bootload. Usually a DSP memory test failure. */ + HPI_ERROR_DSP_BOOTLOAD = 204, + /** Couldn't find or open the DSP code file. */ + HPI_ERROR_DSP_FILE_NOT_FOUND = 206, + /** Internal DSP hardware error. */ + HPI_ERROR_DSP_HARDWARE = 207, + /** Could not allocate memory */ + HPI_ERROR_MEMORY_ALLOC = 208, + /** Failed to correctly load/config PLD. (unused) */ + HPI_ERROR_PLD_LOAD = 209, + /** Unexpected end of file, block length too big etc. */ + HPI_ERROR_DSP_FILE_FORMAT = 210, + + /** Found but could not open DSP code file. */ + HPI_ERROR_DSP_FILE_ACCESS_DENIED = 211, + /** First DSP code section header not found in DSP file. */ + HPI_ERROR_DSP_FILE_NO_HEADER = 212, + /* HPI_ERROR_DSP_FILE_READ_ERROR= 213, */ + /** DSP code for adapter family not found. */ + HPI_ERROR_DSP_SECTION_NOT_FOUND = 214, + /** Other OS specific error opening DSP file. */ + HPI_ERROR_DSP_FILE_OTHER_ERROR = 215, + /** Sharing violation opening DSP code file. */ + HPI_ERROR_DSP_FILE_SHARING_VIOLATION = 216, + /** DSP code section header had size == 0. */ + HPI_ERROR_DSP_FILE_NULL_HEADER = 217, + + /* HPI_ERROR_FLASH = 220, */ + + /** Flash has bad checksum */ + HPI_ERROR_BAD_CHECKSUM = 221, + HPI_ERROR_BAD_SEQUENCE = 222, + HPI_ERROR_FLASH_ERASE = 223, + HPI_ERROR_FLASH_PROGRAM = 224, + HPI_ERROR_FLASH_VERIFY = 225, + HPI_ERROR_FLASH_TYPE = 226, + HPI_ERROR_FLASH_START = 227, + HPI_ERROR_FLASH_READ = 228, + HPI_ERROR_FLASH_READ_NO_FILE = 229, + HPI_ERROR_FLASH_SIZE = 230, + + /** Reserved for OEMs. */ + HPI_ERROR_RESERVED_1 = 290, + + /* HPI_ERROR_INVALID_STREAM = 300 use HPI_ERROR_INVALID_OBJ_INDEX */ + /** Invalid compression format. */ + HPI_ERROR_INVALID_FORMAT = 301, + /** Invalid format samplerate */ + HPI_ERROR_INVALID_SAMPLERATE = 302, + /** Invalid format number of channels. */ + HPI_ERROR_INVALID_CHANNELS = 303, + /** Invalid format bitrate. */ + HPI_ERROR_INVALID_BITRATE = 304, + /** Invalid datasize used for stream read/write. */ + HPI_ERROR_INVALID_DATASIZE = 305, + /* HPI_ERROR_BUFFER_FULL = 306 use HPI_ERROR_INVALID_DATASIZE */ + /* HPI_ERROR_BUFFER_EMPTY = 307 use HPI_ERROR_INVALID_DATASIZE */ + /** Null data pointer used for stream read/write. */ + HPI_ERROR_INVALID_DATA_POINTER = 308, + /** Packet ordering error for stream read/write. */ + HPI_ERROR_INVALID_PACKET_ORDER = 309, + + /** Object can't do requested operation in its current + state, eg set format, change rec mux state while recording.*/ + HPI_ERROR_INVALID_OPERATION = 310, + + /** Where a SRG is shared amongst streams, an incompatible samplerate + is one that is different to any currently active stream. */ + HPI_ERROR_INCOMPATIBLE_SAMPLERATE = 311, + /** Adapter mode is illegal.*/ + HPI_ERROR_BAD_ADAPTER_MODE = 312, + + /** There have been too many attempts to set the adapter's + capabilities (using bad keys), the card should be returned + to ASI if further capabilities updates are required */ + HPI_ERROR_TOO_MANY_CAPABILITY_CHANGE_ATTEMPTS = 313, + /** Streams on different adapters cannot be grouped. */ + HPI_ERROR_NO_INTERADAPTER_GROUPS = 314, + /** Streams on different DSPs cannot be grouped. */ + HPI_ERROR_NO_INTERDSP_GROUPS = 315, + /** Stream wait cancelled before threshold reached. */ + HPI_ERROR_WAIT_CANCELLED = 316, + /** A character string is invalid. */ + HPI_ERROR_INVALID_STRING = 317, + + /** Invalid mixer node for this adapter. */ + HPI_ERROR_INVALID_NODE = 400, + /** Invalid control. */ + HPI_ERROR_INVALID_CONTROL = 401, + /** Invalid control value was passed. */ + HPI_ERROR_INVALID_CONTROL_VALUE = 402, + /** Control attribute not supported by this control. */ + HPI_ERROR_INVALID_CONTROL_ATTRIBUTE = 403, + /** Control is disabled. */ + HPI_ERROR_CONTROL_DISABLED = 404, + /** I2C transaction failed due to a missing ACK. */ + HPI_ERROR_CONTROL_I2C_MISSING_ACK = 405, + HPI_ERROR_I2C_MISSING_ACK = 405, + /** Control is busy, or coming out of + reset and cannot be accessed at this time. */ + HPI_ERROR_CONTROL_NOT_READY = 407, + + /** Non volatile memory */ + HPI_ERROR_NVMEM_BUSY = 450, + HPI_ERROR_NVMEM_FULL = 451, + HPI_ERROR_NVMEM_FAIL = 452, + + /** I2C */ + HPI_ERROR_I2C_BAD_ADR = 460, + + /** Entity type did not match requested type */ + HPI_ERROR_ENTITY_TYPE_MISMATCH = 470, + /** Entity item count did not match requested count */ + HPI_ERROR_ENTITY_ITEM_COUNT = 471, + /** Entity type is not one of the valid types */ + HPI_ERROR_ENTITY_TYPE_INVALID = 472, + /** Entity role is not one of the valid roles */ + HPI_ERROR_ENTITY_ROLE_INVALID = 473, + /** Entity size doesn't match target size */ + HPI_ERROR_ENTITY_SIZE_MISMATCH = 474, + + /* AES18 specific errors were 500..507 */ + + /** custom error to use for debugging */ + HPI_ERROR_CUSTOM = 600, + + /** hpioct32.c can't obtain mutex */ + HPI_ERROR_MUTEX_TIMEOUT = 700, + + /** Backend errors used to be greater than this. + \deprecated Now, all backends return only errors defined here in hpi.h + */ + HPI_ERROR_BACKEND_BASE = 900, + + /** Communication with DSP failed */ + HPI_ERROR_DSP_COMMUNICATION = 900 + /* Note that the dsp communication error is set to this value so that + it remains compatible with any software that expects such errors + to be backend errors i.e. >= 900. + Do not define any new error codes with values > 900. + */ +}; + +/** \defgroup maximums HPI maximum values +\{ +*/ +/** Maximum number of PCI HPI adapters */ +#define HPI_MAX_ADAPTERS 20 +/** Maximum number of in or out streams per adapter */ +#define HPI_MAX_STREAMS 16 +#define HPI_MAX_CHANNELS 2 /* per stream */ +#define HPI_MAX_NODES 8 /* per mixer ? */ +#define HPI_MAX_CONTROLS 4 /* per node ? */ +/** maximum number of ancillary bytes per MPEG frame */ +#define HPI_MAX_ANC_BYTES_PER_FRAME (64) +#define HPI_STRING_LEN 16 + +/** Networked adapters have index >= 100 */ +#define HPI_MIN_NETWORK_ADAPTER_IDX 100 + +/** Velocity units */ +#define HPI_OSTREAM_VELOCITY_UNITS 4096 +/** OutStream timescale units */ +#define HPI_OSTREAM_TIMESCALE_UNITS 10000 +/** OutStream timescale passthrough - turns timescaling on in passthough mode */ +#define HPI_OSTREAM_TIMESCALE_PASSTHROUGH 99999 + +/**\}*/ + +/**************/ +/* STRUCTURES */ +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(push, 1) +#endif + +/** Structure containing sample format information. + See also HPI_FormatCreate(). + */ +struct hpi_format { + u32 sample_rate; + /**< 11025, 32000, 44100 ... */ + u32 bit_rate; /**< for MPEG */ + u32 attributes; + /**< Stereo/JointStereo/Mono */ + u16 mode_legacy; + /**< Legacy ancillary mode or idle bit */ + u16 unused; /**< Unused */ + u16 channels; /**< 1,2..., (or ancillary mode or idle bit */ + u16 format; /**< HPI_FORMAT_PCM16, _MPEG etc. see #HPI_FORMATS. */ +}; + +struct hpi_anc_frame { + u32 valid_bits_in_this_frame; + u8 b_data[HPI_MAX_ANC_BYTES_PER_FRAME]; +}; + +/** An object for containing a single async event. +*/ +struct hpi_async_event { + u16 event_type; /**< type of event. \sa async_event */ + u16 sequence; /**< Sequence number, allows lost event detection */ + u32 state; /**< New state */ + u32 h_object; /**< handle to the object returning the event. */ + union { + struct { + u16 index; /**< GPIO bit index. */ + } gpio; + struct { + u16 node_index; /**< what node is the control on ? */ + u16 node_type; /**< what type of node is the control on ? */ + } control; + } u; +}; + +#ifndef DISABLE_PRAGMA_PACK1 +#pragma pack(pop) +#endif + +/*****************/ +/* HPI FUNCTIONS */ +/*****************/ + +/* Stream */ +u16 hpi_stream_estimate_buffer_size(struct hpi_format *pF, + u32 host_polling_rate_in_milli_seconds, u32 *recommended_buffer_size); + +/*************/ +/* SubSystem */ +/*************/ + +u16 hpi_subsys_get_version_ex(u32 *pversion_ex); + +u16 hpi_subsys_get_num_adapters(int *pn_num_adapters); + +u16 hpi_subsys_get_adapter(int iterator, u32 *padapter_index, + u16 *pw_adapter_type); + +/***********/ +/* Adapter */ +/***********/ + +u16 hpi_adapter_open(u16 adapter_index); + +u16 hpi_adapter_close(u16 adapter_index); + +u16 hpi_adapter_get_info(u16 adapter_index, u16 *pw_num_outstreams, + u16 *pw_num_instreams, u16 *pw_version, u32 *pserial_number, + u16 *pw_adapter_type); + +u16 hpi_adapter_get_module_by_index(u16 adapter_index, u16 module_index, + u16 *pw_num_outputs, u16 *pw_num_inputs, u16 *pw_version, + u32 *pserial_number, u16 *pw_module_type, u32 *ph_module); + +u16 hpi_adapter_set_mode(u16 adapter_index, u32 adapter_mode); + +u16 hpi_adapter_set_mode_ex(u16 adapter_index, u32 adapter_mode, + u16 query_or_set); + +u16 hpi_adapter_get_mode(u16 adapter_index, u32 *padapter_mode); + +u16 hpi_adapter_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..2d6364825 --- /dev/null +++ b/sound/pci/asihpi/hpi6000.c @@ -0,0 +1,1812 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + + 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..7e0deeff5 --- /dev/null +++ b/sound/pci/asihpi/hpi6000.h @@ -0,0 +1,70 @@ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +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..286469843 --- /dev/null +++ b/sound/pci/asihpi/hpi6205.c @@ -0,0 +1,2230 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + + 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 */ + _HPI_6205(NULL, phm, phr); + 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; ipci.pci_dev->subsystem_device == 0x5000) { + /* DSP 0 is always C6205 */ + dRAM_start_address = 0x00400000; + dRAM_size = 0x200000; + /*dwDRAMinc=1024; */ + } else + return 0; + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + dRAM_start_address = 0x80000000; + dRAM_size = 0x200000; + /*dwDRAMinc=1024; */ + } + + if (boot_loader_test_memory(pao, dsp_index, dRAM_start_address, + dRAM_size)) + return HPI6205_ERROR_DSP_EXTMEM; + return 0; +} + +static u16 boot_loader_test_pld(struct hpi_adapter_obj *pao, int dsp_index) +{ + u32 data = 0; + if (dsp_index == 0) { + /* only test for DSP0 PLD on ASI5000 card */ + if (pao->pci.pci_dev->subsystem_device == 0x5000) { + /* PLD is located at CE3=0x03000000 */ + data = boot_loader_read_mem32(pao, dsp_index, + 0x03000008); + if ((data & 0xF) != 0x5) + return HPI6205_ERROR_DSP_PLD; + data = boot_loader_read_mem32(pao, dsp_index, + 0x0300000C); + if ((data & 0xF) != 0xA) + return HPI6205_ERROR_DSP_PLD; + } + } else if (dsp_index == 1) { + /* DSP 1 is a C6713 */ + if (pao->pci.pci_dev->subsystem_device == 0x8700) { + /* PLD is located at CE1=0x90000000 */ + data = boot_loader_read_mem32(pao, dsp_index, + 0x90000010); + if ((data & 0xFF) != 0xAA) + return HPI6205_ERROR_DSP_PLD; + /* 8713 - LED on */ + boot_loader_write_mem32(pao, dsp_index, 0x90000000, + 0x02); + } + } + return 0; +} + +/** Transfer data to or from DSP + nOperation = H620_H620_HIF_SEND_DATA or H620_HIF_GET_DATA +*/ +static short hpi6205_transfer_data(struct hpi_adapter_obj *pao, u8 *p_data, + u32 data_size, int operation) +{ + struct hpi_hw_obj *phw = pao->priv; + u32 data_transferred = 0; + u16 err = 0; + u32 temp2; + struct bus_master_interface *interface = phw->p_interface_buffer; + + if (!p_data) + return HPI_ERROR_INVALID_DATA_POINTER; + + data_size &= ~3L; /* round data_size down to nearest 4 bytes */ + + /* make sure state is IDLE */ + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) + return HPI_ERROR_DSP_HARDWARE; + + while (data_transferred < data_size) { + u32 this_copy = data_size - data_transferred; + + if (this_copy > HPI6205_SIZEOF_DATA) + this_copy = HPI6205_SIZEOF_DATA; + + if (operation == H620_HIF_SEND_DATA) + memcpy((void *)&interface->u.b_data[0], + &p_data[data_transferred], this_copy); + + interface->transfer_size_in_bytes = this_copy; + + /* DSP must change this back to nOperation */ + interface->dsp_ack = H620_HIF_IDLE; + send_dsp_command(phw, operation); + + temp2 = wait_dsp_ack(phw, operation, HPI6205_TIMEOUT); + HPI_DEBUG_LOG(DEBUG, "spun %d times for data xfer of %d\n", + HPI6205_TIMEOUT - temp2, this_copy); + + if (!temp2) { + /* timed out */ + HPI_DEBUG_LOG(ERROR, + "Timed out waiting for " "state %d got %d\n", + operation, interface->dsp_ack); + + break; + } + if (operation == H620_HIF_GET_DATA) + memcpy(&p_data[data_transferred], + (void *)&interface->u.b_data[0], this_copy); + + data_transferred += this_copy; + } + if (interface->dsp_ack != operation) + HPI_DEBUG_LOG(DEBUG, "interface->dsp_ack=%d, expected %d\n", + interface->dsp_ack, operation); + /* err=HPI_ERROR_DSP_HARDWARE; */ + + send_dsp_command(phw, H620_HIF_IDLE); + + return err; +} + +/* wait for up to timeout_us microseconds for the DSP + to signal state by DMA into dwDspAck +*/ +static int wait_dsp_ack(struct hpi_hw_obj *phw, int state, int timeout_us) +{ + struct bus_master_interface *interface = phw->p_interface_buffer; + int t = timeout_us / 4; + + rmb(); /* ensure interface->dsp_ack is up to date */ + while ((interface->dsp_ack != state) && --t) { + hpios_delay_micro_seconds(4); + rmb(); /* DSP changes dsp_ack by DMA */ + } + + /*HPI_DEBUG_LOG(VERBOSE, "Spun %d for %d\n", timeout_us/4-t, state); */ + return t * 4; +} + +/* set the busmaster interface to cmd, then interrupt the DSP */ +static void send_dsp_command(struct hpi_hw_obj *phw, int cmd) +{ + struct bus_master_interface *interface = phw->p_interface_buffer; + u32 r; + + interface->host_cmd = cmd; + wmb(); /* DSP gets state by DMA, make sure it is written to memory */ + /* before we interrupt the DSP */ + r = ioread32(phw->prHDCR); + r |= (u32)C6205_HDCR_DSPINT; + iowrite32(r, phw->prHDCR); + r &= ~(u32)C6205_HDCR_DSPINT; + iowrite32(r, phw->prHDCR); +} + +static unsigned int message_count; + +static u16 message_response_sequence(struct hpi_adapter_obj *pao, + struct hpi_message *phm, struct hpi_response *phr) +{ + u32 time_out, time_out2; + struct hpi_hw_obj *phw = pao->priv; + struct bus_master_interface *interface = phw->p_interface_buffer; + u16 err = 0; + + message_count++; + if (phm->size > sizeof(interface->u.message_buffer)) { + phr->error = HPI_ERROR_MESSAGE_BUFFER_TOO_SMALL; + phr->specific_error = sizeof(interface->u.message_buffer); + phr->size = sizeof(struct hpi_response_header); + HPI_DEBUG_LOG(ERROR, + "message len %d too big for buffer %zd \n", phm->size, + sizeof(interface->u.message_buffer)); + return 0; + } + + /* Assume buffer of type struct bus_master_interface_62 + is allocated "noncacheable" */ + + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) { + HPI_DEBUG_LOG(DEBUG, "timeout waiting for idle\n"); + return HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT; + } + + memcpy(&interface->u.message_buffer, phm, phm->size); + /* signal we want a response */ + send_dsp_command(phw, H620_HIF_GET_RESP); + + time_out2 = wait_dsp_ack(phw, H620_HIF_GET_RESP, HPI6205_TIMEOUT); + + if (!time_out2) { + HPI_DEBUG_LOG(ERROR, + "(%u) Timed out waiting for " "GET_RESP state [%x]\n", + message_count, interface->dsp_ack); + } else { + HPI_DEBUG_LOG(VERBOSE, + "(%u) transition to GET_RESP after %u\n", + message_count, HPI6205_TIMEOUT - time_out2); + } + /* spin waiting on HIF interrupt flag (end of msg process) */ + time_out = HPI6205_TIMEOUT; + + /* read the result */ + if (time_out) { + if (interface->u.response_buffer.response.size <= phr->size) + memcpy(phr, &interface->u.response_buffer, + interface->u.response_buffer.response.size); + else { + HPI_DEBUG_LOG(ERROR, + "response len %d too big for buffer %d\n", + interface->u.response_buffer.response.size, + phr->size); + memcpy(phr, &interface->u.response_buffer, + sizeof(struct hpi_response_header)); + phr->error = HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + phr->specific_error = + interface->u.response_buffer.response.size; + phr->size = sizeof(struct hpi_response_header); + } + } + /* set interface back to idle */ + send_dsp_command(phw, H620_HIF_IDLE); + + if (!time_out || !time_out2) { + HPI_DEBUG_LOG(DEBUG, "something timed out!\n"); + return HPI6205_ERROR_MSG_RESP_TIMEOUT; + } + /* special case for adapter close - */ + /* wait for the DSP to indicate it is idle */ + if (phm->function == HPI_ADAPTER_CLOSE) { + if (!wait_dsp_ack(phw, H620_HIF_IDLE, HPI6205_TIMEOUT)) { + HPI_DEBUG_LOG(DEBUG, + "Timeout waiting for idle " + "(on adapter_close)\n"); + return HPI6205_ERROR_MSG_RESP_IDLE_TIMEOUT; + } + } + err = hpi_validate_response(phm, phr); + return err; +} + +static void hw_message(struct hpi_adapter_obj *pao, struct hpi_message *phm, + struct hpi_response *phr) +{ + + u16 err = 0; + + hpios_dsplock_lock(pao); + + err = message_response_sequence(pao, phm, phr); + + /* maybe an error response */ + if (err) { + /* something failed in the HPI/DSP interface */ + if (err >= HPI_ERROR_BACKEND_BASE) { + phr->error = HPI_ERROR_DSP_COMMUNICATION; + phr->specific_error = err; + } else { + phr->error = err; + } + + pao->dsp_crashed++; + + /* just the header of the response is valid */ + phr->size = sizeof(struct hpi_response_header); + goto err; + } else + pao->dsp_crashed = 0; + + if (phr->error != 0) /* something failed in the DSP */ + goto err; + + switch (phm->function) { + case HPI_OSTREAM_WRITE: + case HPI_ISTREAM_ANC_WRITE: + err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data, + phm->u.d.u.data.data_size, H620_HIF_SEND_DATA); + break; + + case HPI_ISTREAM_READ: + case HPI_OSTREAM_ANC_READ: + err = hpi6205_transfer_data(pao, phm->u.d.u.data.pb_data, + phm->u.d.u.data.data_size, H620_HIF_GET_DATA); + break; + + } + phr->error = err; + +err: + hpios_dsplock_unlock(pao); + + return; +} diff --git a/sound/pci/asihpi/hpi6205.h b/sound/pci/asihpi/hpi6205.h new file mode 100644 index 000000000..ec0827b63 --- /dev/null +++ b/sound/pci/asihpi/hpi6205.h @@ -0,0 +1,103 @@ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +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..aeea679b2 --- /dev/null +++ b/sound/pci/asihpi/hpi_internal.h @@ -0,0 +1,1435 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2012 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +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 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..c7751243d --- /dev/null +++ b/sound/pci/asihpi/hpicmn.c @@ -0,0 +1,722 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +\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; + +/** +* 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 +**/ +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); +} + +/** +* FindAdapter returns a pointer to the struct hpi_adapter_obj with +* index wAdapterIndex in an HPI_ADAPTERS_LIST structure. +* +*/ +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 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..46629c2d1 --- /dev/null +++ b/sound/pci/asihpi/hpicmn.h @@ -0,0 +1,82 @@ +/** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +*/ + +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..9e122327d --- /dev/null +++ b/sound/pci/asihpi/hpidebug.c @@ -0,0 +1,78 @@ +/************************************************************************ + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +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..2c9af2329 --- /dev/null +++ b/sound/pci/asihpi/hpidebug.h @@ -0,0 +1,102 @@ +/***************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +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..3603c24f3 --- /dev/null +++ b/sound/pci/asihpi/hpidspcd.c @@ -0,0 +1,142 @@ +/*********************************************************************** + + AudioScience HPI driver + Functions for reading DSP code using hotplug firmware loader + + Copyright (C) 1997-2014 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +***********************************************************************/ +#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..659d19ca6 --- /dev/null +++ b/sound/pci/asihpi/hpidspcd.h @@ -0,0 +1,106 @@ +/***********************************************************************/ +/** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +\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, ¶m); + if (!err && pw_format) + *pw_format = (u16)param; + + return err; +} + +u16 hpi_aesebu_receiver_get_sample_rate(u32 h_control, u32 *psample_rate) +{ + return hpi_control_param1_get(h_control, HPI_AESEBURX_SAMPLERATE, + psample_rate); +} + +u16 hpi_aesebu_receiver_get_user_data(u32 h_control, u16 index, u16 *pw_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_AESEBURX_USERDATA; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (pw_data) + *pw_data = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_aesebu_receiver_get_channel_status(u32 h_control, u16 index, + u16 *pw_data) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_AESEBURX_CHANNELSTATUS; + hm.u.c.param1 = index; + + hpi_send_recv(&hm, &hr); + + if (pw_data) + *pw_data = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_aesebu_receiver_get_error_status(u32 h_control, u16 *pw_error_data) +{ + u32 error_data = 0; + u16 err = 0; + + err = hpi_control_param1_get(h_control, HPI_AESEBURX_ERRORSTATUS, + &error_data); + if (pw_error_data) + *pw_error_data = (u16)error_data; + return err; +} + +u16 hpi_aesebu_transmitter_set_sample_rate(u32 h_control, u32 sample_rate) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_SAMPLERATE, + sample_rate, 0); +} + +u16 hpi_aesebu_transmitter_set_user_data(u32 h_control, u16 index, u16 data) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_USERDATA, index, + data); +} + +u16 hpi_aesebu_transmitter_set_channel_status(u32 h_control, u16 index, + u16 data) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_CHANNELSTATUS, + index, data); +} + +u16 hpi_aesebu_transmitter_get_channel_status(u32 h_control, u16 index, + u16 *pw_data) +{ + return HPI_ERROR_INVALID_OPERATION; +} + +u16 hpi_aesebu_transmitter_query_format(const u32 h_aes_tx, const u32 index, + u16 *pw_format) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_aes_tx, HPI_AESEBUTX_FORMAT, index, 0, &qr); + *pw_format = (u16)qr; + return err; +} + +u16 hpi_aesebu_transmitter_set_format(u32 h_control, u16 output_format) +{ + return hpi_control_param_set(h_control, HPI_AESEBUTX_FORMAT, + output_format, 0); +} + +u16 hpi_aesebu_transmitter_get_format(u32 h_control, u16 *pw_output_format) +{ + u16 err; + u32 param; + + err = hpi_control_param1_get(h_control, HPI_AESEBUTX_FORMAT, ¶m); + if (!err && pw_output_format) + *pw_output_format = (u16)param; + + return err; +} + +u16 hpi_bitstream_set_clock_edge(u32 h_control, u16 edge_type) +{ + return hpi_control_param_set(h_control, HPI_BITSTREAM_CLOCK_EDGE, + edge_type, 0); +} + +u16 hpi_bitstream_set_data_polarity(u32 h_control, u16 polarity) +{ + return hpi_control_param_set(h_control, HPI_BITSTREAM_DATA_POLARITY, + polarity, 0); +} + +u16 hpi_bitstream_get_activity(u32 h_control, u16 *pw_clk_activity, + u16 *pw_data_activity) +{ + struct hpi_message hm; + struct hpi_response hr; + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_BITSTREAM_ACTIVITY; + hpi_send_recv(&hm, &hr); + if (pw_clk_activity) + *pw_clk_activity = (u16)hr.u.c.param1; + if (pw_data_activity) + *pw_data_activity = (u16)hr.u.c.param2; + return hr.error; +} + +u16 hpi_channel_mode_query_mode(const u32 h_mode, const u32 index, + u16 *pw_mode) +{ + u32 qr; + u16 err; + + err = hpi_control_query(h_mode, HPI_CHANNEL_MODE_MODE, index, 0, &qr); + *pw_mode = (u16)qr; + return err; +} + +u16 hpi_channel_mode_set(u32 h_control, u16 mode) +{ + return hpi_control_param_set(h_control, HPI_CHANNEL_MODE_MODE, mode, + 0); +} + +u16 hpi_channel_mode_get(u32 h_control, u16 *mode) +{ + u32 mode32 = 0; + u16 err = hpi_control_param1_get(h_control, + HPI_CHANNEL_MODE_MODE, &mode32); + if (mode) + *mode = (u16)mode32; + return err; +} + +u16 hpi_cobranet_hmi_write(u32 h_control, u32 hmi_address, u32 byte_count, + u8 *pb_data) +{ + struct hpi_msg_cobranet_hmiwrite hm; + struct hpi_response_header hr; + + hpi_init_message_responseV1(&hm.h, sizeof(hm), &hr, sizeof(hr), + HPI_OBJ_CONTROL, HPI_CONTROL_SET_STATE); + + if (hpi_handle_indexes(h_control, &hm.h.adapter_index, + &hm.h.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + if (byte_count > sizeof(hm.bytes)) + return HPI_ERROR_MESSAGE_BUFFER_TOO_SMALL; + + hm.p.attribute = HPI_COBRANET_SET; + hm.p.byte_count = byte_count; + hm.p.hmi_address = hmi_address; + memcpy(hm.bytes, pb_data, byte_count); + hm.h.size = (u16)(sizeof(hm.h) + sizeof(hm.p) + byte_count); + + hpi_send_recvV1(&hm.h, &hr); + return hr.error; +} + +u16 hpi_cobranet_hmi_read(u32 h_control, u32 hmi_address, u32 max_byte_count, + u32 *pbyte_count, u8 *pb_data) +{ + struct hpi_msg_cobranet_hmiread hm; + struct hpi_res_cobranet_hmiread hr; + + hpi_init_message_responseV1(&hm.h, sizeof(hm), &hr.h, sizeof(hr), + HPI_OBJ_CONTROL, HPI_CONTROL_GET_STATE); + + if (hpi_handle_indexes(h_control, &hm.h.adapter_index, + &hm.h.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + if (max_byte_count > sizeof(hr.bytes)) + return HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + + hm.p.attribute = HPI_COBRANET_GET; + hm.p.byte_count = max_byte_count; + hm.p.hmi_address = hmi_address; + + hpi_send_recvV1(&hm.h, &hr.h); + + if (!hr.h.error && pb_data) { + if (hr.byte_count > sizeof(hr.bytes)) + + return HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL; + + *pbyte_count = hr.byte_count; + + if (hr.byte_count < max_byte_count) + max_byte_count = *pbyte_count; + + memcpy(pb_data, hr.bytes, max_byte_count); + } + return hr.h.error; +} + +u16 hpi_cobranet_hmi_get_status(u32 h_control, u32 *pstatus, + u32 *preadable_size, u32 *pwriteable_size) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + + hm.u.c.attribute = HPI_COBRANET_GET_STATUS; + + hpi_send_recv(&hm, &hr); + if (!hr.error) { + if (pstatus) + *pstatus = hr.u.cu.cobranet.status.status; + if (preadable_size) + *preadable_size = + hr.u.cu.cobranet.status.readable_size; + if (pwriteable_size) + *pwriteable_size = + hr.u.cu.cobranet.status.writeable_size; + } + return hr.error; +} + +u16 hpi_cobranet_get_ip_address(u32 h_control, u32 *pdw_ip_address) +{ + u32 byte_count; + u32 iP; + u16 err; + + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, &byte_count, + (u8 *)&iP); + + *pdw_ip_address = + ((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP & + 0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8); + + if (err) + *pdw_ip_address = 0; + + return err; + +} + +u16 hpi_cobranet_set_ip_address(u32 h_control, u32 dw_ip_address) +{ + u32 iP; + u16 err; + + iP = ((dw_ip_address & 0xff000000) >> 8) | ((dw_ip_address & + 0x00ff0000) << 8) | ((dw_ip_address & 0x0000ff00) >> + 8) | ((dw_ip_address & 0x000000ff) << 8); + + err = hpi_cobranet_hmi_write(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_currentIP, 4, (u8 *)&iP); + + return err; + +} + +u16 hpi_cobranet_get_static_ip_address(u32 h_control, u32 *pdw_ip_address) +{ + u32 byte_count; + u32 iP; + u16 err; + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, &byte_count, + (u8 *)&iP); + + *pdw_ip_address = + ((iP & 0xff000000) >> 8) | ((iP & 0x00ff0000) << 8) | ((iP & + 0x0000ff00) >> 8) | ((iP & 0x000000ff) << 8); + + if (err) + *pdw_ip_address = 0; + + return err; + +} + +u16 hpi_cobranet_set_static_ip_address(u32 h_control, u32 dw_ip_address) +{ + u32 iP; + u16 err; + + iP = ((dw_ip_address & 0xff000000) >> 8) | ((dw_ip_address & + 0x00ff0000) << 8) | ((dw_ip_address & 0x0000ff00) >> + 8) | ((dw_ip_address & 0x000000ff) << 8); + + err = hpi_cobranet_hmi_write(h_control, + HPI_COBRANET_HMI_cobra_ip_mon_staticIP, 4, (u8 *)&iP); + + return err; + +} + +u16 hpi_cobranet_get_macaddress(u32 h_control, u32 *p_mac_msbs, + u32 *p_mac_lsbs) +{ + u32 byte_count; + u16 err; + u32 mac; + + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_if_phy_address, 4, &byte_count, + (u8 *)&mac); + + if (!err) { + *p_mac_msbs = + ((mac & 0xff000000) >> 8) | ((mac & 0x00ff0000) << 8) + | ((mac & 0x0000ff00) >> 8) | ((mac & 0x000000ff) << + 8); + + err = hpi_cobranet_hmi_read(h_control, + HPI_COBRANET_HMI_cobra_if_phy_address + 1, 4, + &byte_count, (u8 *)&mac); + } + + if (!err) { + *p_mac_lsbs = + ((mac & 0xff000000) >> 8) | ((mac & 0x00ff0000) << 8) + | ((mac & 0x0000ff00) >> 8) | ((mac & 0x000000ff) << + 8); + } else { + *p_mac_msbs = 0; + *p_mac_lsbs = 0; + } + + return err; +} + +u16 hpi_compander_set_enable(u32 h_control, u32 enable) +{ + return hpi_control_param_set(h_control, HPI_GENERIC_ENABLE, enable, + 0); +} + +u16 hpi_compander_get_enable(u32 h_control, u32 *enable) +{ + return hpi_control_param1_get(h_control, HPI_GENERIC_ENABLE, enable); +} + +u16 hpi_compander_set_makeup_gain(u32 h_control, short makeup_gain0_01dB) +{ + return hpi_control_log_set2(h_control, HPI_COMPANDER_MAKEUPGAIN, + makeup_gain0_01dB, 0); +} + +u16 hpi_compander_get_makeup_gain(u32 h_control, short *makeup_gain0_01dB) +{ + return hpi_control_log_get2(h_control, HPI_COMPANDER_MAKEUPGAIN, + makeup_gain0_01dB, NULL); +} + +u16 hpi_compander_set_attack_time_constant(u32 h_control, unsigned int index, + u32 attack) +{ + return hpi_control_param_set(h_control, HPI_COMPANDER_ATTACK, attack, + index); +} + +u16 hpi_compander_get_attack_time_constant(u32 h_control, unsigned int index, + u32 *attack) +{ + return hpi_control_param_get(h_control, HPI_COMPANDER_ATTACK, 0, + index, attack, NULL); +} + +u16 hpi_compander_set_decay_time_constant(u32 h_control, unsigned int index, + u32 decay) +{ + return hpi_control_param_set(h_control, HPI_COMPANDER_DECAY, decay, + index); +} + +u16 hpi_compander_get_decay_time_constant(u32 h_control, unsigned int index, + u32 *decay) +{ + return hpi_control_param_get(h_control, HPI_COMPANDER_DECAY, 0, index, + decay, NULL); + +} + +u16 hpi_compander_set_threshold(u32 h_control, unsigned int index, + short threshold0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_SET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_COMPANDER_THRESHOLD; + hm.u.c.param2 = index; + hm.u.c.an_log_value[0] = threshold0_01dB; + + hpi_send_recv(&hm, &hr); + + return hr.error; +} + +u16 hpi_compander_get_threshold(u32 h_control, unsigned int index, + short *threshold0_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_COMPANDER_THRESHOLD; + hm.u.c.param2 = index; + + hpi_send_recv(&hm, &hr); + *threshold0_01dB = hr.u.c.an_log_value[0]; + + return hr.error; +} + +u16 hpi_compander_set_ratio(u32 h_control, u32 index, u32 ratio100) +{ + return hpi_control_param_set(h_control, HPI_COMPANDER_RATIO, ratio100, + index); +} + +u16 hpi_compander_get_ratio(u32 h_control, u32 index, u32 *ratio100) +{ + return hpi_control_param_get(h_control, HPI_COMPANDER_RATIO, 0, index, + ratio100, NULL); +} + +u16 hpi_level_query_range(u32 h_control, short *min_gain_01dB, + short *max_gain_01dB, short *step_gain_01dB) +{ + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.u.c.attribute = HPI_LEVEL_RANGE; + + hpi_send_recv(&hm, &hr); + if (hr.error) { + hr.u.c.an_log_value[0] = 0; + hr.u.c.an_log_value[1] = 0; + hr.u.c.param1 = 0; + } + if (min_gain_01dB) + *min_gain_01dB = hr.u.c.an_log_value[0]; + if (max_gain_01dB) + *max_gain_01dB = hr.u.c.an_log_value[1]; + if (step_gain_01dB) + *step_gain_01dB = (short)hr.u.c.param1; + return hr.error; +} + +u16 hpi_level_set_gain(u32 h_control, short an_gain0_01dB[HPI_MAX_CHANNELS] + ) +{ + return hpi_control_log_set2(h_control, HPI_LEVEL_GAIN, + an_gain0_01dB[0], an_gain0_01dB[1]); +} + +u16 hpi_level_get_gain(u32 h_control, short an_gain0_01dB[HPI_MAX_CHANNELS] + ) +{ + return hpi_control_log_get2(h_control, HPI_LEVEL_GAIN, + &an_gain0_01dB[0], &an_gain0_01dB[1]); +} + +u16 hpi_meter_query_channels(const u32 h_meter, u32 *p_channels) +{ + return hpi_control_query(h_meter, HPI_METER_NUM_CHANNELS, 0, 0, + p_channels); +} + +u16 hpi_meter_get_peak(u32 h_control, short an_peakdB[HPI_MAX_CHANNELS] + ) +{ + short i = 0; + + struct hpi_message hm; + struct hpi_response hr; + + hpi_init_message_response(&hm, &hr, HPI_OBJ_CONTROL, + HPI_CONTROL_GET_STATE); + if (hpi_handle_indexes(h_control, &hm.adapter_index, &hm.obj_index)) + return HPI_ERROR_INVALID_HANDLE; + hm.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..a31a70dcc --- /dev/null +++ b/sound/pci/asihpi/hpimsginit.c @@ -0,0 +1,131 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + + Hardware Programming Interface (HPI) Utility functions. + + (C) Copyright AudioScience Inc. 2007 +*******************************************************************************/ + +#include "hpi_internal.h" +#include "hpimsginit.h" +#include + +/* The actual message size for each object type */ +static u16 msg_size[HPI_OBJ_MAXINDEX + 1] = HPI_MESSAGE_SIZE_BY_OBJECT; +/* The actual response size for each object type */ +static u16 res_size[HPI_OBJ_MAXINDEX + 1] = HPI_RESPONSE_SIZE_BY_OBJECT; +/* Flag to enable alternate message type for SSX2 bypass. */ +static u16 gwSSX2_bypass; + +/** \internal + * initialize the HPI message structure + */ +static void hpi_init_message(struct hpi_message *phm, u16 object, + u16 function) +{ + u16 size; + + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) { + object = array_index_nospec(object, HPI_OBJ_MAXINDEX + 1); + size = msg_size[object]; + } else { + size = sizeof(*phm); + } + + memset(phm, 0, size); + phm->size = size; + + if (gwSSX2_bypass) + phm->type = HPI_TYPE_SSX2BYPASS_MESSAGE; + else + phm->type = HPI_TYPE_REQUEST; + phm->object = object; + phm->function = function; + phm->version = 0; + phm->adapter_index = HPI_ADAPTER_INDEX_INVALID; + /* Expect actual adapter index to be set by caller */ +} + +/** \internal + * initialize the HPI response structure + */ +void hpi_init_response(struct hpi_response *phr, u16 object, u16 function, + u16 error) +{ + u16 size; + + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) { + object = array_index_nospec(object, HPI_OBJ_MAXINDEX + 1); + size = res_size[object]; + } else { + size = sizeof(*phr); + } + + memset(phr, 0, sizeof(*phr)); + phr->size = size; + phr->type = HPI_TYPE_RESPONSE; + phr->object = object; + phr->function = function; + phr->error = error; + phr->specific_error = 0; + phr->version = 0; +} + +void hpi_init_message_response(struct hpi_message *phm, + struct hpi_response *phr, u16 object, u16 function) +{ + hpi_init_message(phm, object, function); + /* default error return if the response is + not filled in by the callee */ + hpi_init_response(phr, object, function, + HPI_ERROR_PROCESSING_MESSAGE); +} + +static void hpi_init_messageV1(struct hpi_message_header *phm, u16 size, + u16 object, u16 function) +{ + memset(phm, 0, size); + if ((object > 0) && (object <= HPI_OBJ_MAXINDEX)) { + phm->size = size; + phm->type = HPI_TYPE_REQUEST; + phm->object = object; + phm->function = function; + phm->version = 1; + /* Expect adapter index to be set by caller */ + } +} + +void hpi_init_responseV1(struct hpi_response_header *phr, u16 size, + u16 object, u16 function) +{ + (void)object; + (void)function; + memset(phr, 0, size); + phr->size = size; + phr->version = 1; + phr->type = HPI_TYPE_RESPONSE; + phr->error = HPI_ERROR_PROCESSING_MESSAGE; +} + +void hpi_init_message_responseV1(struct hpi_message_header *phm, u16 msg_size, + struct hpi_response_header *phr, u16 res_size, u16 object, + u16 function) +{ + hpi_init_messageV1(phm, msg_size, object, function); + hpi_init_responseV1(phr, res_size, object, function); +} diff --git a/sound/pci/asihpi/hpimsginit.h b/sound/pci/asihpi/hpimsginit.h new file mode 100644 index 000000000..5b48708c7 --- /dev/null +++ b/sound/pci/asihpi/hpimsginit.h @@ -0,0 +1,46 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + + 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..736f45337 --- /dev/null +++ b/sound/pci/asihpi/hpimsgx.c @@ -0,0 +1,809 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2014 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +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 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..37f3efd95 --- /dev/null +++ b/sound/pci/asihpi/hpimsgx.h @@ -0,0 +1,36 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + + 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..3f06986fb --- /dev/null +++ b/sound/pci/asihpi/hpioctl.c @@ -0,0 +1,591 @@ +/******************************************************************************* + AudioScience HPI driver + Common Linux HPI ioctl and module probe/remove functions + + Copyright (C) 1997-2014 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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. + +*******************************************************************************/ +#define SOURCEFILE_NAME "hpioctl.c" + +#include "hpi_internal.h" +#include "hpi_version.h" +#include "hpimsginit.h" +#include "hpidebug.h" +#include "hpimsgx.h" +#include "hpioctl.h" +#include "hpicmn.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef MODULE_FIRMWARE +MODULE_FIRMWARE("asihpi/dsp5000.bin"); +MODULE_FIRMWARE("asihpi/dsp6200.bin"); +MODULE_FIRMWARE("asihpi/dsp6205.bin"); +MODULE_FIRMWARE("asihpi/dsp6400.bin"); +MODULE_FIRMWARE("asihpi/dsp6600.bin"); +MODULE_FIRMWARE("asihpi/dsp8700.bin"); +MODULE_FIRMWARE("asihpi/dsp8900.bin"); +#endif + +static int prealloc_stream_buf; +module_param(prealloc_stream_buf, int, 0444); +MODULE_PARM_DESC(prealloc_stream_buf, + "Preallocate size for per-adapter stream buffer"); + +/* Allow the debug level to be changed after module load. + E.g. echo 2 > /sys/module/asihpi/parameters/hpiDebugLevel +*/ +module_param(hpi_debug_level, int, 0644); +MODULE_PARM_DESC(hpi_debug_level, "debug verbosity 0..5"); + +/* List of adapters found */ +static struct hpi_adapter adapters[HPI_MAX_ADAPTERS]; + +/* Wrapper function to HPI_Message to enable dumping of the + message and response types. +*/ +static void hpi_send_recv_f(struct hpi_message *phm, struct hpi_response *phr, + struct file *file) +{ + if ((phm->adapter_index >= HPI_MAX_ADAPTERS) + && (phm->object != HPI_OBJ_SUBSYSTEM)) + phr->error = HPI_ERROR_INVALID_OBJ_INDEX; + else + hpi_send_recv_ex(phm, phr, file); +} + +/* This is called from hpifunc.c functions, called by ALSA + * (or other kernel process) In this case there is no file descriptor + * available for the message cache code + */ +void hpi_send_recv(struct hpi_message *phm, struct hpi_response *phr) +{ + hpi_send_recv_f(phm, phr, HOWNER_KERNEL); +} + +EXPORT_SYMBOL(hpi_send_recv); +/* for radio-asihpi */ + +int asihpi_hpi_release(struct file *file) +{ + struct hpi_message hm; + struct hpi_response hr; + +/* HPI_DEBUG_LOG(INFO,"hpi_release file %p, pid %d\n", file, current->pid); */ + /* close the subsystem just in case the application forgot to. */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_SUBSYSTEM, + HPI_SUBSYS_CLOSE); + hpi_send_recv_ex(&hm, &hr, file); + return 0; +} + +long asihpi_hpi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct hpi_ioctl_linux __user *phpi_ioctl_data; + void __user *puhm; + void __user *puhr; + union hpi_message_buffer_v1 *hm; + union hpi_response_buffer_v1 *hr; + u16 msg_size; + u16 res_max_size; + u32 uncopied_bytes; + int err = 0; + + if (cmd != HPI_IOCTL_LINUX) + return -EINVAL; + + hm = kmalloc(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) + 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 (pci_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_irq(pci_dev->irq, asihpi_isr, 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..0d767e10a --- /dev/null +++ b/sound/pci/asihpi/hpioctl.h @@ -0,0 +1,38 @@ +/******************************************************************************* + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +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..5ef4fe964 --- /dev/null +++ b/sound/pci/asihpi/hpios.c @@ -0,0 +1,83 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2012 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +HPI Operating System function implementation for Linux + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ +#define SOURCEFILE_NAME "hpios.c" +#include "hpi_internal.h" +#include "hpidebug.h" +#include +#include + +void hpios_delay_micro_seconds(u32 num_micro_sec) +{ + if ((usecs_to_jiffies(num_micro_sec) > 1) && !in_interrupt()) { + /* MUST NOT SCHEDULE IN INTERRUPT CONTEXT! */ + schedule_timeout_uninterruptible(usecs_to_jiffies + (num_micro_sec)); + } else if (num_micro_sec <= 2000) + udelay(num_micro_sec); + else + mdelay(num_micro_sec / 1000); + +} + +/** Allocate an area of locked memory for bus master DMA operations. + +If allocation fails, return 1, and *pMemArea.size = 0 +*/ +u16 hpios_locked_mem_alloc(struct consistent_dma_area *p_mem_area, u32 size, + struct pci_dev *pdev) +{ + /*?? any benefit in using managed dmam_alloc_coherent? */ + p_mem_area->vaddr = + dma_alloc_coherent(&pdev->dev, size, &p_mem_area->dma_handle, + GFP_DMA32 | 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..4e383601b --- /dev/null +++ b/sound/pci/asihpi/hpios.h @@ -0,0 +1,165 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + +HPI Operating System Specific macros for Linux Kernel driver + +(C) Copyright AudioScience Inc. 1997-2003 +******************************************************************************/ +#ifndef _HPIOS_H_ +#define _HPIOS_H_ + +#undef HPI_OS_LINUX_KERNEL +#define HPI_OS_LINUX_KERNEL + +#define HPI_OS_DEFINED +#define HPI_BUILD_KERNEL_MODE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HPI_NO_OS_FILE_OPS + +/** Details of a memory area allocated with pci_alloc_consistent +Need all info for parameters to pci_free_consistent +*/ +struct consistent_dma_area { + struct device *pdev; + /* looks like dma-mapping dma_devres ?! */ + size_t size; + void *vaddr; + dma_addr_t dma_handle; +}; + +static inline u16 hpios_locked_mem_get_phys_addr(struct consistent_dma_area + *locked_mem_handle, u32 *p_physical_addr) +{ + *p_physical_addr = locked_mem_handle->dma_handle; + return 0; +} + +static inline u16 hpios_locked_mem_get_virt_addr(struct consistent_dma_area + *locked_mem_handle, void **pp_virtual_addr) +{ + *pp_virtual_addr = locked_mem_handle->vaddr; + return 0; +} + +static inline u16 hpios_locked_mem_valid(struct consistent_dma_area + *locked_mem_handle) +{ + return locked_mem_handle->size != 0; +} + +struct hpi_ioctl_linux { + void __user *phm; + void __user *phr; +}; + +/* Conflict?: H is already used by a number of drivers hid, bluetooth hci, + and some sound drivers sb16, hdsp, emu10k. AFAIK 0xFC is ununsed command +*/ +#define HPI_IOCTL_LINUX _IOWR('H', 0xFC, struct hpi_ioctl_linux) + +#define HPI_DEBUG_FLAG_ERROR KERN_ERR +#define HPI_DEBUG_FLAG_WARNING KERN_WARNING +#define HPI_DEBUG_FLAG_NOTICE KERN_NOTICE +#define HPI_DEBUG_FLAG_INFO KERN_INFO +#define HPI_DEBUG_FLAG_DEBUG KERN_DEBUG +#define HPI_DEBUG_FLAG_VERBOSE KERN_DEBUG /* kernel has no verbose */ + +#include + +#define HPI_LOCKING + +struct hpios_spinlock { + spinlock_t lock; /* SEE hpios_spinlock */ + int lock_context; +}; + +/* The reason for all this evilness is that ALSA calls some of a drivers + * operators in atomic context, and some not. But all our functions channel + * through the HPI_Message conduit, so we can't handle the different context + * per function + */ +#define IN_LOCK_BH 1 +#define IN_LOCK_IRQ 0 +static inline void cond_lock(struct hpios_spinlock *l) +{ + if (irqs_disabled()) { + /* NO bh or isr can execute on this processor, + so ordinary lock will do + */ + spin_lock(&((l)->lock)); + l->lock_context = IN_LOCK_IRQ; + } else { + spin_lock_bh(&((l)->lock)); + l->lock_context = IN_LOCK_BH; + } +} + +static inline void cond_unlock(struct hpios_spinlock *l) +{ + if (l->lock_context == IN_LOCK_BH) + spin_unlock_bh(&((l)->lock)); + else + spin_unlock(&((l)->lock)); +} + +#define hpios_msgxlock_init(obj) spin_lock_init(&(obj)->lock) +#define hpios_msgxlock_lock(obj) cond_lock(obj) +#define hpios_msgxlock_unlock(obj) cond_unlock(obj) + +#define hpios_dsplock_init(obj) spin_lock_init(&(obj)->dsp_lock.lock) +#define hpios_dsplock_lock(obj) cond_lock(&(obj)->dsp_lock) +#define hpios_dsplock_unlock(obj) cond_unlock(&(obj)->dsp_lock) + +#ifdef CONFIG_SND_DEBUG +#define HPI_BUILD_DEBUG +#endif + +#define HPI_ALIST_LOCKING +#define hpios_alistlock_init(obj) spin_lock_init(&((obj)->list_lock.lock)) +#define hpios_alistlock_lock(obj) spin_lock(&((obj)->list_lock.lock)) +#define hpios_alistlock_unlock(obj) spin_unlock(&((obj)->list_lock.lock)) + +struct snd_card; + +/** pci drvdata points to an instance of this struct */ +struct hpi_adapter { + struct hpi_adapter_obj *adapter; + struct snd_card *snd_card; + + int irq; + int interrupt_mode; + void (*interrupt_callback) (struct hpi_adapter *); + + /* mutex prevents contention for one card + between multiple user programs (via ioctl) */ + struct mutex mutex; + char *p_buffer; + size_t buffer_size; +}; + +#endif diff --git a/sound/pci/asihpi/hpipcida.h b/sound/pci/asihpi/hpipcida.h new file mode 100644 index 000000000..db570ddf6 --- /dev/null +++ b/sound/pci/asihpi/hpipcida.h @@ -0,0 +1,37 @@ +/****************************************************************************** + + AudioScience HPI driver + Copyright (C) 1997-2011 AudioScience Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 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 + + 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..a1e4944dc --- /dev/null +++ b/sound/pci/atiixp.c @@ -0,0 +1,1706 @@ +/* + * ALSA driver for ATI IXP 150/200/250/300 AC97 controllers + * + * Copyright (c) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("ATI IXP AC97 controller"); +MODULE_LICENSE("GPL"); +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 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, + snd_dma_pci_data(chip->pci), + 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: + 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->ops->enable_transfer(chip, 0); + dma->running = 0; + dma->suspended = cmd == SNDRV_PCM_TRIGGER_SUSPEND; + 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); + /* fallthru */ + case 6: + data |= ATI_REG_OUT_DMA_SLOT_BIT(7) | + ATI_REG_OUT_DMA_SLOT_BIT(8); + /* fallthru */ + case 4: + data |= ATI_REG_OUT_DMA_SLOT_BIT(6) | + ATI_REG_OUT_DMA_SLOT_BIT(9); + /* fallthru */ + 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; + + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return 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); + snd_pcm_lib_free_pages(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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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 struct snd_ac97_bus_ops ops = { + .write = snd_atiixp_ac97_write, + .read = snd_atiixp_ac97_read, + }; + static 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_PCMDEVS; i++) + if (chip->pcmdevs[i]) { + struct atiixp_dma *dma = &chip->dmas[i]; + if (dma->substream && dma->running) + dma->saved_curptr = readl(chip->remap_addr + + dma->ops->dt_cur); + snd_pcm_suspend_all(chip->pcmdevs[i]); + } + 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); + writel(dma->saved_curptr, chip->remap_addr + + dma->ops->dt_cur); + } + } + + 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "atiixp", &entry)) + snd_info_set_text_ops(entry, 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 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; + pci_set_master(pci); + synchronize_irq(chip->irq); + + 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..dc1de860c --- /dev/null +++ b/sound/pci/atiixp_modem.c @@ -0,0 +1,1327 @@ +/* + * ALSA driver for ATI IXP 150/200/250 AC97 modem controllers + * + * Copyright (c) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("ATI IXP MC97 controller"); +MODULE_LICENSE("GPL"); +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, snd_dma_pci_data(chip->pci), + 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; + + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return 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; + + /* 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); + snd_pcm_lib_free_pages(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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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 struct snd_ac97_bus_ops ops = { + .write = snd_atiixp_ac97_write, + .read = snd_atiixp_ac97_read, + }; + static 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_PCMDEVS; i++) + snd_pcm_suspend_all(chip->pcmdevs[i]); + 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "atiixp-modem", &entry)) + snd_info_set_text_ops(entry, 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 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; + pci_set_master(pci); + synchronize_irq(chip->irq); + + 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<this_10 / 2 DWORD in size. */ + +/* ADB */ +#define VORTEX_ADB_SR 0x10a00 /* Samplerates enable/disable */ +#define VORTEX_ADB_RTBASE 0x10800 +#define VORTEX_ADB_RTBASE_COUNT 103 +#define VORTEX_ADB_CHNBASE 0x1099c +#define VORTEX_ADB_CHNBASE_COUNT 22 +#define ROUTE_MASK 0x3fff +#define ADB_MASK 0x7f +#define ADB_SHIFT 0x7 +//#define ADB_MIX_MASK 0xf +/* ADB address */ +#define OFFSET_ADBDMA 0x00 +#define OFFSET_SRCOUT 0x10 /* on channel 0x11 */ +#define OFFSET_SRCIN 0x10 /* on channel < 0x11 */ +#define OFFSET_MIXOUT 0x20 /* source */ +#define OFFSET_MIXIN 0x30 /* sink */ +#define OFFSET_CODECIN 0x48 /* ADB source */ +#define OFFSET_CODECOUT 0x58 /* ADB sink/target */ +#define OFFSET_SPORTOUT 0x60 /* sink */ +#define OFFSET_SPORTIN 0x50 /* source */ +#define OFFSET_EFXOUT 0x50 /* sink */ +#define OFFSET_EFXIN 0x40 /* source */ +#define OFFSET_A3DOUT 0x00 /* This card has no HRTF :( */ +#define OFFSET_A3DIN 0x00 +#define OFFSET_WTOUT 0x58 /* */ + +/* ADB route translate helper */ +#define ADB_DMA(x) (x + OFFSET_ADBDMA) +#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT) +#define ADB_SRCIN(x) (x + OFFSET_SRCIN) +#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT) +#define ADB_MIXIN(x) (x + OFFSET_MIXIN) +#define ADB_CODECIN(x) (x + OFFSET_CODECIN) +#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT) +#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT) +#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN) /* */ +#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 8 A3D blocks */ +#define ADB_A3DIN(x) (x + OFFSET_A3DIN) +#define ADB_WTOUT(x,y) (y + OFFSET_WTOUT) + +/* WTDMA */ +#define VORTEX_WTDMA_CTRL 0x10500 /* format, DMA pos */ +#define VORTEX_WTDMA_STAT 0x10500 /* DMA subbuf, DMA pos */ +#define WT_SUBBUF_MASK (0x3 << WT_SUBBUF_SHIFT) +#define WT_SUBBUF_SHIFT 0x15 +#define VORTEX_WTDMA_BUFBASE 0x10000 +#define VORTEX_WTDMA_BUFCFG0 0x10300 +#define VORTEX_WTDMA_BUFCFG1 0x10304 +#define VORTEX_WTDMA_START 0x10640 /* which subbuffer is first */ + +#define VORTEX_WT_BASE 0x9000 + +/* MIXER */ +#define VORTEX_MIXER_SR 0x9f00 +#define VORTEX_MIXER_CLIP 0x9f80 +#define VORTEX_MIXER_CHNBASE 0x9e40 +#define VORTEX_MIXER_RTBASE 0x9e00 +#define MIXER_RTBASE_SIZE 0x26 +#define VORTEX_MIX_ENIN 0x9a00 /* Input enable bits. 4 bits wide. */ +#define VORTEX_MIX_SMP 0x9c00 + +/* MIX */ +#define VORTEX_MIX_INVOL_A 0x9000 /* in? */ +#define VORTEX_MIX_INVOL_B 0x8000 /* out? */ +#define VORTEX_MIX_VOL_A 0x9800 +#define VORTEX_MIX_VOL_B 0x8800 + +#define VOL_MIN 0x80 /* Input volume when muted. */ +#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */ + +//#define MIX_OUTL 0xe +//#define MIX_OUTR 0xf +//#define MIX_INL 0xe +//#define MIX_INR 0xf +#define MIX_DEFIGAIN 0x08 /* 0x8 => 6dB */ +#define MIX_DEFOGAIN 0x08 + +/* SRC */ +#define VORTEX_SRCBLOCK_SR 0xccc0 +#define VORTEX_SRC_CHNBASE 0xcc40 +#define VORTEX_SRC_RTBASE 0xcc00 +#define VORTEX_SRC_SOURCE 0xccc4 +#define VORTEX_SRC_SOURCESIZE 0xccc8 +#define VORTEX_SRC_U0 0xce00 +#define VORTEX_SRC_DRIFT0 0xce80 +#define VORTEX_SRC_DRIFT1 0xcec0 +#define VORTEX_SRC_U1 0xcf00 +#define VORTEX_SRC_DRIFT2 0xcf40 +#define VORTEX_SRC_U2 0xcf80 +#define VORTEX_SRC_DATA 0xc800 +#define VORTEX_SRC_DATA0 0xc000 +#define VORTEX_SRC_CONVRATIO 0xce40 +//#define SRC_RATIO(x) ((((x<<15)/48000) + 1)/2) /* Playback */ +//#define SRC_RATIO2(x) ((((48000<<15)/x) + 1)/2) /* Recording */ + +/* FIFO */ +#define VORTEX_FIFO_ADBCTRL 0xf800 /* Control bits. */ +#define VORTEX_FIFO_WTCTRL 0xf840 +#define FIFO_RDONLY 0x00000001 +#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */ +#define FIFO_VALID 0x00000010 +#define FIFO_EMPTY 0x00000020 +#define FIFO_U0 0x00001000 /* Unknown. */ +#define FIFO_U1 0x00010000 +#define FIFO_SIZE_BITS 5 +#define FIFO_SIZE (1<this_10 / 2 DWORD in size. */ +/* Starting at the MSB, each pair of bits seem to be the current DMA page. */ +/* This current page bits are consistent (same value) with VORTEX_ADBDMA_STAT) */ + +/* DMA */ +#define VORTEX_ENGINE_CTRL 0x27ae8 +#define ENGINE_INIT 0x1380000 + +/* WTDMA */ +#define VORTEX_WTDMA_CTRL 0x27900 /* format, DMA pos */ +#define VORTEX_WTDMA_STAT 0x27d00 /* DMA subbuf, DMA pos */ +#define WT_SUBBUF_MASK 0x3 +#define WT_SUBBUF_SHIFT 0xc +#define VORTEX_WTDMA_BUFBASE 0x27000 +#define VORTEX_WTDMA_BUFCFG0 0x27600 +#define VORTEX_WTDMA_BUFCFG1 0x27604 +#define VORTEX_WTDMA_START 0x27b00 /* which subbuffer is first */ + +/* ADB */ +#define VORTEX_ADB_SR 0x28400 /* Samplerates enable/disable */ +#define VORTEX_ADB_RTBASE 0x28000 +#define VORTEX_ADB_RTBASE_COUNT 173 +#define VORTEX_ADB_CHNBASE 0x282b4 +#define VORTEX_ADB_CHNBASE_COUNT 24 +#define ROUTE_MASK 0xffff +#define SOURCE_MASK 0xff00 +#define ADB_MASK 0xff +#define ADB_SHIFT 0x8 +/* ADB address */ +#define OFFSET_ADBDMA 0x00 +#define OFFSET_ADBDMAB 0x20 +#define OFFSET_SRCIN 0x40 +#define OFFSET_SRCOUT 0x20 /* ch 0x11 */ +#define OFFSET_MIXIN 0x50 /* ch 0x11 */ +#define OFFSET_MIXOUT 0x30 /* ch 0x11 */ +#define OFFSET_CODECIN 0x70 /* ch 0x11 */ /* adb source */ +#define OFFSET_CODECOUT 0x88 /* ch 0x11 */ /* adb target */ +#define OFFSET_SPORTIN 0x78 /* ch 0x13 ADB source. 2 routes. */ +#define OFFSET_SPORTOUT 0x90 /* ch 0x13 ADB sink. 2 routes. */ +#define OFFSET_SPDIFIN 0x7A /* ch 0x14 ADB source. */ +#define OFFSET_SPDIFOUT 0x92 /* ch 0x14 ADB sink. */ +#define OFFSET_AC98IN 0x7c /* ch 0x14 ADB source. */ +#define OFFSET_AC98OUT 0x94 /* ch 0x14 ADB sink. */ +#define OFFSET_EQIN 0xa0 /* ch 0x11 */ +#define OFFSET_EQOUT 0x7e /* ch 0x11 */ /* 2 routes on ch 0x11 */ +#define OFFSET_A3DIN 0x70 /* ADB sink. */ +#define OFFSET_A3DOUT 0xA6 /* ADB source. 2 routes per slice = 8 */ +#define OFFSET_WT0 0x40 /* WT bank 0 output. 0x40 - 0x65 */ +#define OFFSET_WT1 0x80 /* WT bank 1 output. 0x80 - 0xA5 */ +/* WT sources offset : 0x00-0x1f Direct stream. */ +/* WT sources offset : 0x20-0x25 Mixed Output. */ +#define OFFSET_XTALKOUT 0x66 /* crosstalk canceller (source) 2 routes */ +#define OFFSET_XTALKIN 0x96 /* crosstalk canceller (sink). 10 routes */ +#define OFFSET_EFXOUT 0x68 /* ADB source. 8 routes. */ +#define OFFSET_EFXIN 0x80 /* ADB sink. 8 routes. */ + +/* ADB route translate helper */ +#define ADB_DMA(x) (x) +#define ADB_SRCOUT(x) (x + OFFSET_SRCOUT) +#define ADB_SRCIN(x) (x + OFFSET_SRCIN) +#define ADB_MIXOUT(x) (x + OFFSET_MIXOUT) +#define ADB_MIXIN(x) (x + OFFSET_MIXIN) +#define ADB_CODECIN(x) (x + OFFSET_CODECIN) +#define ADB_CODECOUT(x) (x + OFFSET_CODECOUT) +#define ADB_SPORTIN(x) (x + OFFSET_SPORTIN) +#define ADB_SPORTOUT(x) (x + OFFSET_SPORTOUT) +#define ADB_SPDIFIN(x) (x + OFFSET_SPDIFIN) +#define ADB_SPDIFOUT(x) (x + OFFSET_SPDIFOUT) +#define ADB_EQIN(x) (x + OFFSET_EQIN) +#define ADB_EQOUT(x) (x + OFFSET_EQOUT) +#define ADB_A3DOUT(x) (x + OFFSET_A3DOUT) /* 0x10 A3D blocks */ +#define ADB_A3DIN(x) (x + OFFSET_A3DIN) +//#define ADB_WTOUT(x) ((x6dB (6dB = x4) 16 to 18 bit conversion? */ + +/* MIXER */ +#define VORTEX_MIXER_SR 0x21f00 +#define VORTEX_MIXER_CLIP 0x21f80 +#define VORTEX_MIXER_CHNBASE 0x21e40 +#define VORTEX_MIXER_RTBASE 0x21e00 +#define MIXER_RTBASE_SIZE 0x38 +#define VORTEX_MIX_ENIN 0x21a00 /* Input enable bits. 4 bits wide. */ +#define VORTEX_MIX_SMP 0x21c00 /* wave data buffers. AU8820: 0x9c00 */ + +/* MIX */ +#define VORTEX_MIX_INVOL_B 0x20000 /* Input volume current */ +#define VORTEX_MIX_VOL_B 0x20800 /* Output Volume current */ +#define VORTEX_MIX_INVOL_A 0x21000 /* Input Volume target */ +#define VORTEX_MIX_VOL_A 0x21800 /* Output Volume target */ + +#define VOL_MIN 0x80 /* Input volume when muted. */ +#define VOL_MAX 0x7f /* FIXME: Not confirmed! Just guessed. */ + +/* SRC */ +#define VORTEX_SRC_CHNBASE 0x26c40 +#define VORTEX_SRC_RTBASE 0x26c00 +#define VORTEX_SRCBLOCK_SR 0x26cc0 +#define VORTEX_SRC_SOURCE 0x26cc4 +#define VORTEX_SRC_SOURCESIZE 0x26cc8 +/* Params + 0x26e00 : 1 U0 + 0x26e40 : 2 CR + 0x26e80 : 3 U3 + 0x26ec0 : 4 DRIFT1 + 0x26f00 : 5 U1 + 0x26f40 : 6 DRIFT2 + 0x26f80 : 7 U2 : Target rate, direction +*/ + +#define VORTEX_SRC_CONVRATIO 0x26e40 +#define VORTEX_SRC_DRIFT0 0x26e80 +#define VORTEX_SRC_DRIFT1 0x26ec0 +#define VORTEX_SRC_DRIFT2 0x26f40 +#define VORTEX_SRC_U0 0x26e00 +#define U0_SLOWLOCK 0x200 +#define VORTEX_SRC_U1 0x26f00 +#define VORTEX_SRC_U2 0x26f80 +#define VORTEX_SRC_DATA 0x26800 /* 0xc800 */ +#define VORTEX_SRC_DATA0 0x26000 + +/* FIFO */ +#define VORTEX_FIFO_ADBCTRL 0x16100 /* Control bits. */ +#define VORTEX_FIFO_WTCTRL 0x16000 +#define FIFO_RDONLY 0x00000001 +#define FIFO_CTRL 0x00000002 /* Allow ctrl. ? */ +#define FIFO_VALID 0x00000010 +#define FIFO_EMPTY 0x00000020 +#define FIFO_U0 0x00002000 /* Unknown. */ +#define FIFO_U1 0x00040000 +#define FIFO_SIZE_BITS 6 +#define FIFO_SIZE (1<<(FIFO_SIZE_BITS)) // 0x40 +#define FIFO_MASK (FIFO_SIZE-1) //0x3f /* at shift left 0xc */ +#define FIFO_BITS 0x1c400000 +#define VORTEX_FIFO_ADBDATA 0x14000 +#define VORTEX_FIFO_WTDATA 0x10000 + +#define VORTEX_FIFO_GIRT 0x17000 /* wt0, wt1, adb */ +#define GIRT_COUNT 3 + +/* CODEC */ + +#define VORTEX_CODEC_CHN 0x29080 /* The name "CHN" is wrong. */ + +#define VORTEX_CODEC_CTRL 0x29184 +#define VORTEX_CODEC_IO 0x29188 + +#define VORTEX_CODEC_SPORTCTRL 0x2918c + +#define VORTEX_CODEC_EN 0x29190 +#define EN_AUDIO0 0x00000300 +#define EN_MODEM 0x00000c00 +#define EN_AUDIO1 0x00003000 +#define EN_SPORT 0x00030000 +#define EN_SPDIF 0x000c0000 +#define EN_CODEC (EN_AUDIO1 | EN_AUDIO0) + +#define VORTEX_SPDIF_SMPRATE 0x29194 + +#define VORTEX_SPDIF_FLAGS 0x2205c +#define VORTEX_SPDIF_CFG0 0x291D0 /* status data */ +#define VORTEX_SPDIF_CFG1 0x291D4 + +#define VORTEX_SMP_TIME 0x29198 /* Sample counter/timer */ +#define VORTEX_SMP_TIMER 0x2919c +#define VORTEX_CODEC2_CTRL 0x291a0 + +#define VORTEX_MODEM_CTRL 0x291ac + +/* IRQ */ +#define VORTEX_IRQ_SOURCE 0x2a000 /* Interrupt source flags. */ +#define VORTEX_IRQ_CTRL 0x2a004 /* Interrupt source mask. */ + +//#define VORTEX_IRQ_U0 0x2a008 /* ?? */ +#define VORTEX_STAT 0x2a008 /* Some sort of status */ +#define STAT_IRQ 0x00000001 /* This bitis set if the IRQ is valid. */ + +#define VORTEX_CTRL 0x2a00c +#define CTRL_MIDI_EN 0x00000001 +#define CTRL_MIDI_PORT 0x00000060 +#define CTRL_GAME_EN 0x00000008 +#define CTRL_GAME_PORT 0x00000e00 +#define CTRL_IRQ_ENABLE 0x00004000 +#define CTRL_SPDIF 0x00000000 /* unknown. Please find this value */ +#define CTRL_SPORT 0x00200000 +#define CTRL_RST 0x00800000 +#define CTRL_UNKNOWN 0x01000000 + +/* write: Timer period config / read: TIMER IRQ ack. */ +#define VORTEX_IRQ_STAT 0x2919c + + /* MIDI *//* GAME. */ +#define VORTEX_MIDI_DATA 0x28800 +#define VORTEX_MIDI_CMD 0x28804 /* Write command / Read status */ + +#define VORTEX_GAME_LEGACY 0x28808 +#define VORTEX_CTRL2 0x2880c +#define CTRL2_GAME_ADCMODE 0x40 +#define VORTEX_GAME_AXIS 0x28810 /* Axis base register. 4 axis's */ +#define AXIS_SIZE 4 +#define AXIS_RANGE 0x1fff diff --git a/sound/pci/au88x0/au88x0.c b/sound/pci/au88x0/au88x0.c new file mode 100644 index 000000000..32092184b --- /dev/null +++ b/sound/pci/au88x0/au88x0.c @@ -0,0 +1,382 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +// module parameters (see "Module Parameters") +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static int pcifix[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 255 }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); +module_param_array(pcifix, int, NULL, 0444); +MODULE_PARM_DESC(pcifix, "Enable VIA-workaround for " CARD_NAME " soundcard."); + +MODULE_DESCRIPTION("Aureal vortex"); +MODULE_LICENSE("GPL"); +MODULE_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 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; + + 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..e3e31f07d --- /dev/null +++ b/sound/pci/au88x0/au88x0.h @@ -0,0 +1,293 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +#ifndef __SOUND_AU88X0_H +#define __SOUND_AU88X0_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CHIP_AU8820 +#include "au88x0_eq.h" +#include "au88x0_a3d.h" +#endif +#ifndef CHIP_AU8810 +#include "au88x0_wt.h" +#endif + +#define hwread(x,y) readl((x)+(y)) +#define hwwrite(x,y,z) writel((z),(x)+(y)) + +/* Vortex MPU401 defines. */ +#define MIDI_CLOCK_DIV 0x61 +/* Standart MPU401 defines. */ +#define MPU401_RESET 0xff +#define MPU401_ENTER_UART 0x3f +#define MPU401_ACK 0xfe + +// Get src register value to convert from x to y. +#define SRC_RATIO(x,y) ((((x<<15)/y) + 1)/2) + +/* FIFO software state constants. */ +#define FIFO_STOP 0 +#define FIFO_START 1 +#define FIFO_PAUSE 2 + +/* IRQ flags */ +#define IRQ_ERR_MASK 0x00ff +#define IRQ_FATAL 0x0001 +#define IRQ_PARITY 0x0002 +#define IRQ_REG 0x0004 +#define IRQ_FIFO 0x0008 +#define IRQ_DMA 0x0010 +#define IRQ_PCMOUT 0x0020 /* PCM OUT page crossing */ +#define IRQ_TIMER 0x1000 +#define IRQ_MIDI 0x2000 +#define IRQ_MODEM 0x4000 + +/* ADB Resource */ +#define VORTEX_RESOURCE_DMA 0x00000000 +#define VORTEX_RESOURCE_SRC 0x00000001 +#define VORTEX_RESOURCE_MIXIN 0x00000002 +#define VORTEX_RESOURCE_MIXOUT 0x00000003 +#define VORTEX_RESOURCE_A3D 0x00000004 +#define VORTEX_RESOURCE_LAST 0x00000005 + +/* codec io: VORTEX_CODEC_IO bits */ +#define VORTEX_CODEC_ID_SHIFT 24 +#define VORTEX_CODEC_WRITE 0x00800000 +#define VORTEX_CODEC_ADDSHIFT 16 +#define VORTEX_CODEC_ADDMASK 0x7f0000 +#define VORTEX_CODEC_DATSHIFT 0 +#define VORTEX_CODEC_DATMASK 0xffff + +/* Check for SDAC bit in "Extended audio ID" AC97 register */ +//#define VORTEX_IS_QUAD(x) (((x)->codec == NULL) ? 0 : ((x)->codec->ext_id&0x80)) +#define VORTEX_IS_QUAD(x) ((x)->isquad) +/* Check if chip has bug. */ +#define IS_BAD_CHIP(x) (\ + (x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_VORTEX_2) || \ + (x->rev == 0xfe && x->device == PCI_DEVICE_ID_AUREAL_ADVANTAGE)) + + +/* PCM devices */ +#define VORTEX_PCM_ADB 0 +#define VORTEX_PCM_SPDIF 1 +#define VORTEX_PCM_A3D 2 +#define VORTEX_PCM_WT 3 +#define VORTEX_PCM_I2S 4 +#define VORTEX_PCM_LAST 5 + +#define MIX_CAPT(x) (vortex->mixcapt[x]) +#define MIX_PLAYB(x) (vortex->mixplayb[x]) +#define MIX_SPDIF(x) (vortex->mixspdif[x]) + +#define NR_WTPB 0x20 /* WT channels per each bank. */ +#define NR_PCM 0x10 + +struct pcm_vol { + struct snd_kcontrol *kctl; + int active; + int dma; + int mixin[4]; + int vol[4]; +}; + +/* Structs */ +typedef struct { + //int this_08; /* Still unknown */ + int fifo_enabled; /* this_24 */ + int fifo_status; /* this_1c */ + u32 dma_ctrl; /* this_78 (ADB), this_7c (WT) */ + int dma_unknown; /* this_74 (ADB), this_78 (WT). WDM: +8 */ + int cfg0; + int cfg1; + + int nr_ch; /* Nr of PCM channels in use */ + int type; /* Output type (ac97, a3d, spdif, i2s, dsp) */ + int dma; /* Hardware DMA index. */ + int dir; /* Stream Direction. */ + u32 resources[5]; + + /* Virtual page extender stuff */ + int nr_periods; + int period_bytes; + int period_real; + int period_virt; + + struct snd_pcm_substream *substream; +} stream_t; + +typedef struct snd_vortex vortex_t; +struct snd_vortex { + /* ALSA structs. */ + struct snd_card *card; + struct snd_pcm *pcm[VORTEX_PCM_LAST]; + + struct snd_rawmidi *rmidi; /* Legacy Midi interface. */ + struct snd_ac97 *codec; + + /* Stream structs. */ + stream_t dma_adb[NR_ADB]; + int spdif_sr; +#ifndef CHIP_AU8810 + stream_t dma_wt[NR_WT]; + wt_voice_t wt_voice[NR_WT]; /* WT register cache. */ + char 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 char 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..7a4558a70 --- /dev/null +++ b/sound/pci/au88x0/au88x0_a3d.c @@ -0,0 +1,915 @@ +/*************************************************************************** + * 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... + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "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 changed = 1, 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 changed; +} + +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, changed = 1; + 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 changed; +} + +static int +snd_vortex_a3d_ild_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + a3dsrc_t *a = kcontrol->private_data; + int changed = 1; + 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 changed; +} + +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, changed = 1; + 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 changed; +} + +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..0584c65bc --- /dev/null +++ b/sound/pci/au88x0/au88x0_a3d.h @@ -0,0 +1,123 @@ +/*************************************************************************** + * au88x0_a3d.h + * + * Fri Jul 18 14:16:03 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.net + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +#ifndef _AU88X0_A3D_H +#define _AU88X0_A3D_H + +//#include + +#define HRTF_SZ 0x38 +#define DLINE_SZ 0x28 + +#define CTRLID_HRTF 1 +#define CTRLID_ITD 2 +#define CTRLID_ILD 4 +#define CTRLID_FILTER 8 +#define CTRLID_GAINS 16 + +/* 3D parameter structs */ +typedef unsigned short int a3d_Hrtf_t[HRTF_SZ]; +typedef unsigned short int a3d_ItdDline_t[DLINE_SZ]; +typedef unsigned short int a3d_atmos_t[5]; +typedef unsigned short int a3d_LRGains_t[2]; +typedef unsigned short int a3d_Itd_t[2]; +typedef unsigned short int a3d_Ild_t[2]; + +typedef struct { + void *vortex; // Formerly CAsp4HwIO*, now vortex_t*. + unsigned int source; /* this_04 */ + unsigned int slice; /* this_08 */ + a3d_Hrtf_t hrtf[2]; + a3d_Itd_t itd; + a3d_Ild_t ild; + a3d_ItdDline_t dline; + a3d_atmos_t filter; +} a3dsrc_t; + +/* First Register bank */ + +#define A3D_A_HrtfCurrent 0x18000 /* 56 ULONG */ +#define A3D_A_GainCurrent 0x180E0 +#define A3D_A_GainTarget 0x180E4 +#define A3D_A_A12Current 0x180E8 /* Atmospheric current. */ +#define A3D_A_A21Target 0x180EC /* Atmospheric target */ +#define A3D_A_B01Current 0x180F0 /* Atmospheric current */ +#define A3D_A_B10Target 0x180F4 /* Atmospheric target */ +#define A3D_A_B2Current 0x180F8 /* Atmospheric current */ +#define A3D_A_B2Target 0x180FC /* Atmospheric target */ +#define A3D_A_HrtfTarget 0x18100 /* 56 ULONG */ +#define A3D_A_ITDCurrent 0x181E0 +#define A3D_A_ITDTarget 0x181E4 +#define A3D_A_HrtfDelayLine 0x181E8 /* 56 ULONG */ +#define A3D_A_ITDDelayLine 0x182C8 /* 40/45 ULONG */ +#define A3D_A_HrtfTrackTC 0x1837C /* Time Constants */ +#define A3D_A_GainTrackTC 0x18380 +#define A3D_A_CoeffTrackTC 0x18384 +#define A3D_A_ITDTrackTC 0x18388 +#define A3D_A_x1 0x1838C +#define A3D_A_x2 0x18390 +#define A3D_A_y1 0x18394 +#define A3D_A_y2 0x18398 +#define A3D_A_HrtfOutL 0x1839C +#define A3D_A_HrtfOutR 0x183A0 +#define A3D_A_TAIL 0x183A4 + +/* Second register bank */ +#define A3D_B_HrtfCurrent 0x19000 /* 56 ULONG */ +#define A3D_B_GainCurrent 0x190E0 +#define A3D_B_GainTarget 0x190E4 +#define A3D_B_A12Current 0x190E8 +#define A3D_B_A21Target 0x190EC +#define A3D_B_B01Current 0x190F0 +#define A3D_B_B10Target 0x190F4 +#define A3D_B_B2Current 0x190F8 +#define A3D_B_B2Target 0x190FC +#define A3D_B_HrtfTarget 0x19100 /* 56 ULONG */ +#define A3D_B_ITDCurrent 0x191E0 +#define A3D_B_ITDTarget 0x191E4 +#define A3D_B_HrtfDelayLine 0x191E8 /* 56 ULONG */ +#define A3D_B_TAIL 0x192C8 + +/* There are 4 slices, 4 a3d each = 16 a3d sources. */ +#define A3D_SLICE_BANK_A 0x18000 /* 4 sources */ +#define A3D_SLICE_BANK_B 0x19000 /* 4 sources */ +#define A3D_SLICE_VDBDest 0x19C00 /* 8 ULONG */ +#define A3D_SLICE_VDBSource 0x19C20 /* 4 ULONG */ +#define A3D_SLICE_ABReg 0x19C30 +#define A3D_SLICE_CReg 0x19C34 +#define A3D_SLICE_Control 0x19C38 +#define A3D_SLICE_DebugReserved 0x19C3c /* Dangerous! */ +#define A3D_SLICE_Pointers 0x19C40 +#define A3D_SLICE_TAIL 0x1A000 + +// Slice size: 0x2000 +// Source size: 0x3A4, 0x2C8 + +/* Address generator macro. */ +#define a3d_addrA(slice,source,reg) (((slice)<<0xd)+((source)*0x3A4)+(reg)) +#define a3d_addrB(slice,source,reg) (((slice)<<0xd)+((source)*0x2C8)+(reg)) +#define a3d_addrS(slice,reg) (((slice)<<0xd)+(reg)) +//#define a3d_addr(slice,source,reg) (((reg)>=0x19000) ? a3d_addr2((slice),(source),(reg)) : a3d_addr1((slice),(source),(reg))) + +#endif /* _AU88X0_A3D_H */ diff --git a/sound/pci/au88x0/au88x0_a3ddata.c b/sound/pci/au88x0/au88x0_a3ddata.c new file mode 100644 index 000000000..6fab4bba5 --- /dev/null +++ b/sound/pci/au88x0/au88x0_a3ddata.c @@ -0,0 +1,91 @@ +/*************************************************************************** + * au88x0_a3ddata.c + * + * Wed Nov 19 21:11:32 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.org + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +/* 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 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 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 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 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..2e5b460a8 --- /dev/null +++ b/sound/pci/au88x0/au88x0_core.c @@ -0,0 +1,2867 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +/* + Vortex core low level functions. + + Author: Manuel Jander (mjander@users.sourceforge.cl) + These functions are mainly the result of translations made + from the original disassembly of the au88x0 binary drivers, + written by Aureal before they went down. + Many thanks to the Jeff Muizelaar, Kester Maddock, and whoever + contributed to the OpenVortex project. + The author of this file, put the few available pieces together + and translated the rest of the riddle (Mix, Src and connection stuff). + Some things are still to be discovered, and their meanings are unclear. + + Some of these functions aren't intended to be really used, rather + to help to understand how does the AU88X0 chips work. Keep them in, because + they could be used somewhere in the future. + + This code hasn't been tested or proof read thoroughly. If you wanna help, + take a look at the AU88X0 assembly and check if this matches. + Functions tested ok so far are (they show the desired effect + at least): + vortex_routes(); (1 bug fixed). + vortex_adb_addroute(); + vortex_adb_addroutes(); + vortex_connect_codecplay(); + vortex_src_flushbuffers(); + vortex_adbdma_setmode(); note: still some unknown arguments! + vortex_adbdma_startfifo(); + vortex_adbdma_stopfifo(); + vortex_fifo_setadbctrl(); note: still some unknown arguments! + vortex_mix_setinputvolumebyte(); + vortex_mix_enableinput(); + vortex_mixer_addWTD(); (fixed) + vortex_connection_adbdma_src_src(); + vortex_connection_adbdma_src(); + vortex_src_change_convratio(); + vortex_src_addWTD(); (fixed) + + History: + + 01-03-2003 First revision. + 01-21-2003 Some bug fixes. + 17-02-2003 many bugfixes after a big versioning mess. + 18-02-2003 JAAAAAHHHUUUUUU!!!! The mixer works !! I'm just so happy ! + (2 hours later...) I cant believe it! Im really lucky today. + Now the SRC is working too! Yeah! XMMS works ! + 20-02-2003 First steps into the ALSA world. + 28-02-2003 As my birthday present, i discovered how the DMA buffer pages really + work :-). It was all wrong. + 12-03-2003 ALSA driver starts working (2 channels). + 16-03-2003 More srcblock_setupchannel discoveries. + 12-04-2003 AU8830 playback support. Recording in the works. + 17-04-2003 vortex_route() and vortex_routes() bug fixes. AU8830 recording + works now, but chipn' dale effect is still there. + 16-05-2003 SrcSetupChannel cleanup. Moved the Src setup stuff entirely + into au88x0_pcm.c . + 06-06-2003 Buffer shifter bugfix. Mixer volume fix. + 07-12-2003 A3D routing finally fixed. Believed to be OK. + 25-03-2004 Many thanks to Claudia, for such valuable bug reports. + +*/ + +#include "au88x0.h" +#include "au88x0_a3d.h" +#include + +/* MIXER (CAsp4Mix.s and CAsp4Mixer.s) */ + +// FIXME: get rid of this. +static int mchannels[NR_MIXIN]; +static int rampchs[NR_MIXIN]; + +static void vortex_mixer_en_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_MIXER_SR, + hwread(vortex->mmio, VORTEX_MIXER_SR) | (0x1 << channel)); +} +static void vortex_mixer_dis_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_MIXER_SR, + hwread(vortex->mmio, VORTEX_MIXER_SR) & ~(0x1 << channel)); +} + +#if 0 +static void +vortex_mix_muteinputgain(vortex_t * vortex, unsigned char mix, + unsigned char channel) +{ + hwwrite(vortex->mmio, VORTEX_MIX_INVOL_A + ((mix << 5) + channel), + 0x80); + hwwrite(vortex->mmio, VORTEX_MIX_INVOL_B + ((mix << 5) + channel), + 0x80); +} + +static int vortex_mix_getvolume(vortex_t * vortex, unsigned char mix) +{ + int a; + a = hwread(vortex->mmio, VORTEX_MIX_VOL_A + (mix << 2)) & 0xff; + //FP2LinearFrac(a); + return (a); +} + +static int +vortex_mix_getinputvolume(vortex_t * vortex, unsigned char mix, + int channel, int *vol) +{ + int a; + if (!(mchannels[mix] & (1 << channel))) + return 0; + a = hwread(vortex->mmio, + VORTEX_MIX_INVOL_A + (((mix << 5) + channel) << 2)); + /* + if (rampchs[mix] == 0) + a = FP2LinearFrac(a); + else + a = FP2LinearFracWT(a); + */ + *vol = a; + return (0); +} + +static unsigned int vortex_mix_boost6db(unsigned char vol) +{ + return (vol + 8); /* WOW! what a complex function! */ +} + +static void vortex_mix_rampvolume(vortex_t * vortex, int mix) +{ + int ch; + char a; + // This function is intended for ramping down only (see vortex_disableinput()). + for (ch = 0; ch < 0x20; ch++) { + if (((1 << ch) & rampchs[mix]) == 0) + continue; + a = hwread(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + ch) << 2)); + if (a > -126) { + a -= 2; + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_A + + (((mix << 5) + ch) << 2), a); + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_B + + (((mix << 5) + ch) << 2), a); + } else + vortex_mix_killinput(vortex, mix, ch); + } +} + +static int +vortex_mix_getenablebit(vortex_t * vortex, unsigned char mix, int mixin) +{ + int addr, temp; + if (mixin >= 0) + addr = mixin; + else + addr = mixin + 3; + addr = ((mix << 3) + (addr >> 2)) << 2; + temp = hwread(vortex->mmio, VORTEX_MIX_ENIN + addr); + return ((temp >> (mixin & 3)) & 1); +} +#endif +static void +vortex_mix_setvolumebyte(vortex_t * vortex, unsigned char mix, + unsigned char vol) +{ + int temp; + hwwrite(vortex->mmio, VORTEX_MIX_VOL_A + (mix << 2), vol); + if (1) { /*if (this_10) */ + temp = hwread(vortex->mmio, VORTEX_MIX_VOL_B + (mix << 2)); + if ((temp != 0x80) || (vol == 0x80)) + return; + } + hwwrite(vortex->mmio, VORTEX_MIX_VOL_B + (mix << 2), vol); +} + +static void +vortex_mix_setinputvolumebyte(vortex_t * vortex, unsigned char mix, + int mixin, unsigned char vol) +{ + int temp; + + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_A + (((mix << 5) + mixin) << 2), vol); + if (1) { /* this_10, initialized to 1. */ + temp = + hwread(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2)); + if ((temp != 0x80) || (vol == 0x80)) + return; + } + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2), vol); +} + +static void +vortex_mix_setenablebit(vortex_t * vortex, unsigned char mix, int mixin, int en) +{ + int temp, addr; + + if (mixin < 0) + addr = (mixin + 3); + else + addr = mixin; + addr = ((mix << 3) + (addr >> 2)) << 2; + temp = hwread(vortex->mmio, VORTEX_MIX_ENIN + addr); + if (en) + temp |= (1 << (mixin & 3)); + else + temp &= ~(1 << (mixin & 3)); + /* Mute input. Astatic void crackling? */ + hwwrite(vortex->mmio, + VORTEX_MIX_INVOL_B + (((mix << 5) + mixin) << 2), 0x80); + /* Looks like clear buffer. */ + hwwrite(vortex->mmio, VORTEX_MIX_SMP + (mixin << 2), 0x0); + hwwrite(vortex->mmio, VORTEX_MIX_SMP + 4 + (mixin << 2), 0x0); + /* Write enable bit. */ + hwwrite(vortex->mmio, VORTEX_MIX_ENIN + addr, temp); +} + +static void +vortex_mix_killinput(vortex_t * vortex, unsigned char mix, int mixin) +{ + rampchs[mix] &= ~(1 << mixin); + vortex_mix_setinputvolumebyte(vortex, mix, mixin, 0x80); + mchannels[mix] &= ~(1 << mixin); + vortex_mix_setenablebit(vortex, mix, mixin, 0); +} + +static void +vortex_mix_enableinput(vortex_t * vortex, unsigned char mix, int mixin) +{ + vortex_mix_killinput(vortex, mix, mixin); + if ((mchannels[mix] & (1 << mixin)) == 0) { + vortex_mix_setinputvolumebyte(vortex, mix, mixin, 0x80); /*0x80 : mute */ + mchannels[mix] |= (1 << mixin); + } + vortex_mix_setenablebit(vortex, mix, mixin, 1); +} + +static void +vortex_mix_disableinput(vortex_t * vortex, unsigned char mix, int channel, + int ramp) +{ + if (ramp) { + rampchs[mix] |= (1 << channel); + // Register callback. + //vortex_mix_startrampvolume(vortex); + vortex_mix_killinput(vortex, mix, channel); + } else + vortex_mix_killinput(vortex, mix, channel); +} + +static int +vortex_mixer_addWTD(vortex_t * vortex, unsigned char mix, unsigned char ch) +{ + int temp, lifeboat = 0, prev; + + temp = hwread(vortex->mmio, VORTEX_MIXER_SR); + if ((temp & (1 << ch)) == 0) { + hwwrite(vortex->mmio, VORTEX_MIXER_CHNBASE + (ch << 2), mix); + vortex_mixer_en_sr(vortex, ch); + return 1; + } + prev = VORTEX_MIXER_CHNBASE + (ch << 2); + temp = hwread(vortex->mmio, prev); + while (temp & 0x10) { + prev = VORTEX_MIXER_RTBASE + ((temp & 0xf) << 2); + temp = hwread(vortex->mmio, prev); + //printk(KERN_INFO "vortex: mixAddWTD: while addr=%x, val=%x\n", prev, temp); + if ((++lifeboat) > 0xf) { + dev_err(vortex->card->dev, + "vortex_mixer_addWTD: lifeboat overflow\n"); + return 0; + } + } + hwwrite(vortex->mmio, VORTEX_MIXER_RTBASE + ((temp & 0xf) << 2), mix); + hwwrite(vortex->mmio, prev, (temp & 0xf) | 0x10); + return 1; +} + +static int +vortex_mixer_delWTD(vortex_t * vortex, unsigned char mix, unsigned char ch) +{ + int esp14 = -1, esp18, eax, ebx, edx, ebp, esi = 0; + //int esp1f=edi(while)=src, esp10=ch; + + eax = hwread(vortex->mmio, VORTEX_MIXER_SR); + if (((1 << ch) & eax) == 0) { + dev_err(vortex->card->dev, "mix ALARM %x\n", eax); + return 0; + } + ebp = VORTEX_MIXER_CHNBASE + (ch << 2); + esp18 = hwread(vortex->mmio, ebp); + if (esp18 & 0x10) { + ebx = (esp18 & 0xf); + if (mix == ebx) { + ebx = VORTEX_MIXER_RTBASE + (mix << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + } else { + //7ad3 + edx = + hwread(vortex->mmio, + VORTEX_MIXER_RTBASE + (ebx << 2)); + //printk(KERN_INFO "vortex: mixdelWTD: 1 addr=%x, val=%x, src=%x\n", ebx, edx, src); + while ((edx & 0xf) != mix) { + if ((esi) > 0xf) { + dev_err(vortex->card->dev, + "mixdelWTD: error lifeboat overflow\n"); + return 0; + } + esp14 = ebx; + ebx = edx & 0xf; + ebp = ebx << 2; + edx = + hwread(vortex->mmio, + VORTEX_MIXER_RTBASE + ebp); + //printk(KERN_INFO "vortex: mixdelWTD: while addr=%x, val=%x\n", ebp, edx); + esi++; + } + //7b30 + ebp = ebx << 2; + if (edx & 0x10) { /* Delete entry in between others */ + ebx = VORTEX_MIXER_RTBASE + ((edx & 0xf) << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, + VORTEX_MIXER_RTBASE + ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + //printk(KERN_INFO "vortex mixdelWTD between addr= 0x%x, val= 0x%x\n", ebp, edx); + } else { /* Delete last entry */ + //7b83 + if (esp14 == -1) + hwwrite(vortex->mmio, + VORTEX_MIXER_CHNBASE + + (ch << 2), esp18 & 0xef); + else { + ebx = (0xffffffe0 & edx) | (0xf & ebx); + hwwrite(vortex->mmio, + VORTEX_MIXER_RTBASE + + (esp14 << 2), ebx); + //printk(KERN_INFO "vortex mixdelWTD last addr= 0x%x, val= 0x%x\n", esp14, ebx); + } + hwwrite(vortex->mmio, + VORTEX_MIXER_RTBASE + ebp, 0); + return 1; + } + } + } else { + //printk(KERN_INFO "removed last mix\n"); + //7be0 + vortex_mixer_dis_sr(vortex, ch); + hwwrite(vortex->mmio, ebp, 0); + } + return 1; +} + +static void vortex_mixer_init(vortex_t * vortex) +{ + u32 addr; + int x; + + // FIXME: get rid of this crap. + memset(mchannels, 0, NR_MIXOUT * sizeof(int)); + memset(rampchs, 0, NR_MIXOUT * sizeof(int)); + + addr = VORTEX_MIX_SMP + 0x17c; + for (x = 0x5f; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + addr = VORTEX_MIX_ENIN + 0x1fc; + for (x = 0x7f; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + addr = VORTEX_MIX_SMP + 0x17c; + for (x = 0x5f; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + addr = VORTEX_MIX_INVOL_A + 0x7fc; + for (x = 0x1ff; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIX_VOL_A + 0x3c; + for (x = 0xf; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIX_INVOL_B + 0x7fc; + for (x = 0x1ff; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIX_VOL_B + 0x3c; + for (x = 0xf; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x80); + addr -= 4; + } + addr = VORTEX_MIXER_RTBASE + (MIXER_RTBASE_SIZE - 1) * 4; + for (x = (MIXER_RTBASE_SIZE - 1); x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0x0); + addr -= 4; + } + hwwrite(vortex->mmio, VORTEX_MIXER_SR, 0); + + /* Set clipping ceiling (this may be all wrong). */ + /* + for (x = 0; x < 0x80; x++) { + hwwrite(vortex->mmio, VORTEX_MIXER_CLIP + (x << 2), 0x3ffff); + } + */ + /* + call CAsp4Mix__Initialize_CAsp4HwIO____CAsp4Mixer____ + Register ISR callback for volume smooth fade out. + Maybe this avoids clicks when press "stop" ? + */ +} + +/* SRC (CAsp4Src.s and CAsp4SrcBlock) */ + +static void vortex_src_en_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_SRCBLOCK_SR, + hwread(vortex->mmio, VORTEX_SRCBLOCK_SR) | (0x1 << channel)); +} + +static void vortex_src_dis_sr(vortex_t * vortex, int channel) +{ + hwwrite(vortex->mmio, VORTEX_SRCBLOCK_SR, + hwread(vortex->mmio, VORTEX_SRCBLOCK_SR) & ~(0x1 << channel)); +} + +static void vortex_src_flushbuffers(vortex_t * vortex, unsigned char src) +{ + int i; + + for (i = 0x1f; i >= 0; i--) + hwwrite(vortex->mmio, + VORTEX_SRC_DATA0 + (src << 7) + (i << 2), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DATA + (src << 3), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DATA + (src << 3) + 4, 0); +} + +static void vortex_src_cleardrift(vortex_t * vortex, unsigned char src) +{ + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT0 + (src << 2), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT1 + (src << 2), 0); + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT2 + (src << 2), 1); +} + +static void +vortex_src_set_throttlesource(vortex_t * vortex, unsigned char src, int en) +{ + int temp; + + temp = hwread(vortex->mmio, VORTEX_SRC_SOURCE); + if (en) + temp |= 1 << src; + else + temp &= ~(1 << src); + hwwrite(vortex->mmio, VORTEX_SRC_SOURCE, temp); +} + +static int +vortex_src_persist_convratio(vortex_t * vortex, unsigned char src, int ratio) +{ + int temp, lifeboat = 0; + + do { + hwwrite(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2), ratio); + temp = hwread(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2)); + if ((++lifeboat) > 0x9) { + dev_err(vortex->card->dev, "Src cvr fail\n"); + break; + } + } + while (temp != ratio); + return temp; +} + +#if 0 +static void vortex_src_slowlock(vortex_t * vortex, unsigned char src) +{ + int temp; + + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT2 + (src << 2), 1); + hwwrite(vortex->mmio, VORTEX_SRC_DRIFT0 + (src << 2), 0); + temp = hwread(vortex->mmio, VORTEX_SRC_U0 + (src << 2)); + if (temp & 0x200) + hwwrite(vortex->mmio, VORTEX_SRC_U0 + (src << 2), + temp & ~0x200L); +} + +static void +vortex_src_change_convratio(vortex_t * vortex, unsigned char src, int ratio) +{ + int temp, a; + + if ((ratio & 0x10000) && (ratio != 0x10000)) { + if (ratio & 0x3fff) + a = (0x11 - ((ratio >> 0xe) & 0x3)) - 1; + else + a = (0x11 - ((ratio >> 0xe) & 0x3)) - 2; + } else + a = 0xc; + temp = hwread(vortex->mmio, VORTEX_SRC_U0 + (src << 2)); + if (((temp >> 4) & 0xf) != a) + hwwrite(vortex->mmio, VORTEX_SRC_U0 + (src << 2), + (temp & 0xf) | ((a & 0xf) << 4)); + + vortex_src_persist_convratio(vortex, src, ratio); +} + +static int +vortex_src_checkratio(vortex_t * vortex, unsigned char src, + unsigned int desired_ratio) +{ + int hw_ratio, lifeboat = 0; + + hw_ratio = hwread(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2)); + + while (hw_ratio != desired_ratio) { + hwwrite(vortex->mmio, VORTEX_SRC_CONVRATIO + (src << 2), desired_ratio); + + if ((lifeboat++) > 15) { + pr_err( "Vortex: could not set src-%d from %d to %d\n", + src, hw_ratio, desired_ratio); + break; + } + } + + return hw_ratio; +} + +#endif +/* + Objective: Set samplerate for given SRC module. + Arguments: + card: pointer to vortex_t strcut. + src: Integer index of the SRC module. + cr: Current sample rate conversion factor. + b: unknown 16 bit value. + sweep: Enable Samplerate fade from cr toward tr flag. + dirplay: 1: playback, 0: recording. + sl: Slow Lock flag. + tr: Target samplerate conversion. + thsource: Throttle source flag (no idea what that means). +*/ +static void vortex_src_setupchannel(vortex_t * card, unsigned char src, + unsigned int cr, unsigned int b, int sweep, int d, + int dirplay, int sl, unsigned int tr, int thsource) +{ + // noplayback: d=2,4,7,0xa,0xb when using first 2 src's. + // c: enables pitch sweep. + // looks like g is c related. Maybe g is a sweep parameter ? + // g = cvr + // dirplay: 0 = recording, 1 = playback + // d = src hw index. + + int esi, ebp = 0, esp10; + + vortex_src_flushbuffers(card, src); + + if (sweep) { + if ((tr & 0x10000) && (tr != 0x10000)) { + tr = 0; + esi = 0x7; + } else { + if ((((short)tr) < 0) && (tr != 0x8000)) { + tr = 0; + esi = 0x8; + } else { + tr = 1; + esi = 0xc; + } + } + } else { + if ((cr & 0x10000) && (cr != 0x10000)) { + tr = 0; /*ebx = 0 */ + esi = 0x11 - ((cr >> 0xe) & 7); + if (cr & 0x3fff) + esi -= 1; + else + esi -= 2; + } else { + tr = 1; + esi = 0xc; + } + } + vortex_src_cleardrift(card, src); + vortex_src_set_throttlesource(card, src, thsource); + + if ((dirplay == 0) && (sweep == 0)) { + if (tr) + esp10 = 0xf; + else + esp10 = 0xc; + ebp = 0; + } else { + if (tr) + ebp = 0xf; + else + ebp = 0xc; + esp10 = 0; + } + hwwrite(card->mmio, VORTEX_SRC_U0 + (src << 2), + (sl << 0x9) | (sweep << 0x8) | ((esi & 0xf) << 4) | d); + /* 0xc0 esi=0xc c=f=0 d=0 */ + vortex_src_persist_convratio(card, src, cr); + hwwrite(card->mmio, VORTEX_SRC_U1 + (src << 2), b & 0xffff); + /* 0 b=0 */ + hwwrite(card->mmio, VORTEX_SRC_U2 + (src << 2), + (tr << 0x11) | (dirplay << 0x10) | (ebp << 0x8) | esp10); + /* 0x30f00 e=g=1 esp10=0 ebp=f */ + //printk(KERN_INFO "vortex: SRC %d, d=0x%x, esi=0x%x, esp10=0x%x, ebp=0x%x\n", src, d, esi, esp10, ebp); +} + +static void vortex_srcblock_init(vortex_t * vortex) +{ + u32 addr; + int x; + hwwrite(vortex->mmio, VORTEX_SRC_SOURCESIZE, 0x1ff); + /* + for (x=0; x<0x10; x++) { + vortex_src_init(&vortex_src[x], x); + } + */ + //addr = 0xcc3c; + //addr = 0x26c3c; + addr = VORTEX_SRC_RTBASE + 0x3c; + for (x = 0xf; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } + //addr = 0xcc94; + //addr = 0x26c94; + addr = VORTEX_SRC_CHNBASE + 0x54; + for (x = 0x15; x >= 0; x--) { + hwwrite(vortex->mmio, addr, 0); + addr -= 4; + } +} + +static int +vortex_src_addWTD(vortex_t * vortex, unsigned char src, unsigned char ch) +{ + int temp, lifeboat = 0, prev; + // esp13 = src + + temp = hwread(vortex->mmio, VORTEX_SRCBLOCK_SR); + if ((temp & (1 << ch)) == 0) { + hwwrite(vortex->mmio, VORTEX_SRC_CHNBASE + (ch << 2), src); + vortex_src_en_sr(vortex, ch); + return 1; + } + prev = VORTEX_SRC_CHNBASE + (ch << 2); /*ebp */ + temp = hwread(vortex->mmio, prev); + //while (temp & NR_SRC) { + while (temp & 0x10) { + prev = VORTEX_SRC_RTBASE + ((temp & 0xf) << 2); /*esp12 */ + //prev = VORTEX_SRC_RTBASE + ((temp & (NR_SRC-1)) << 2); /*esp12*/ + temp = hwread(vortex->mmio, prev); + //printk(KERN_INFO "vortex: srcAddWTD: while addr=%x, val=%x\n", prev, temp); + if ((++lifeboat) > 0xf) { + dev_err(vortex->card->dev, + "vortex_src_addWTD: lifeboat overflow\n"); + return 0; + } + } + hwwrite(vortex->mmio, VORTEX_SRC_RTBASE + ((temp & 0xf) << 2), src); + //hwwrite(vortex->mmio, prev, (temp & (NR_SRC-1)) | NR_SRC); + hwwrite(vortex->mmio, prev, (temp & 0xf) | 0x10); + return 1; +} + +static int +vortex_src_delWTD(vortex_t * vortex, unsigned char src, unsigned char ch) +{ + int esp14 = -1, esp18, eax, ebx, edx, ebp, esi = 0; + //int esp1f=edi(while)=src, esp10=ch; + + eax = hwread(vortex->mmio, VORTEX_SRCBLOCK_SR); + if (((1 << ch) & eax) == 0) { + dev_err(vortex->card->dev, "src alarm\n"); + return 0; + } + ebp = VORTEX_SRC_CHNBASE + (ch << 2); + esp18 = hwread(vortex->mmio, ebp); + if (esp18 & 0x10) { + ebx = (esp18 & 0xf); + if (src == ebx) { + ebx = VORTEX_SRC_RTBASE + (src << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + } else { + //7ad3 + edx = + hwread(vortex->mmio, + VORTEX_SRC_RTBASE + (ebx << 2)); + //printk(KERN_INFO "vortex: srcdelWTD: 1 addr=%x, val=%x, src=%x\n", ebx, edx, src); + while ((edx & 0xf) != src) { + if ((esi) > 0xf) { + dev_warn(vortex->card->dev, + "srcdelWTD: error, lifeboat overflow\n"); + return 0; + } + esp14 = ebx; + ebx = edx & 0xf; + ebp = ebx << 2; + edx = + hwread(vortex->mmio, + VORTEX_SRC_RTBASE + ebp); + //printk(KERN_INFO "vortex: srcdelWTD: while addr=%x, val=%x\n", ebp, edx); + esi++; + } + //7b30 + ebp = ebx << 2; + if (edx & 0x10) { /* Delete entry in between others */ + ebx = VORTEX_SRC_RTBASE + ((edx & 0xf) << 2); + edx = hwread(vortex->mmio, ebx); + //7b60 + hwwrite(vortex->mmio, + VORTEX_SRC_RTBASE + ebp, edx); + hwwrite(vortex->mmio, ebx, 0); + //printk(KERN_INFO "vortex srcdelWTD between addr= 0x%x, val= 0x%x\n", ebp, edx); + } else { /* Delete last entry */ + //7b83 + if (esp14 == -1) + hwwrite(vortex->mmio, + VORTEX_SRC_CHNBASE + + (ch << 2), esp18 & 0xef); + else { + ebx = (0xffffffe0 & edx) | (0xf & ebx); + hwwrite(vortex->mmio, + VORTEX_SRC_RTBASE + + (esp14 << 2), ebx); + //printk(KERN_INFO"vortex srcdelWTD last addr= 0x%x, val= 0x%x\n", esp14, ebx); + } + hwwrite(vortex->mmio, + VORTEX_SRC_RTBASE + ebp, 0); + return 1; + } + } + } else { + //7be0 + vortex_src_dis_sr(vortex, ch); + hwwrite(vortex->mmio, ebp, 0); + } + return 1; +} + + /*FIFO*/ + +static void +vortex_fifo_clearadbdata(vortex_t * vortex, int fifo, int x) +{ + for (x--; x >= 0; x--) + hwwrite(vortex->mmio, + VORTEX_FIFO_ADBDATA + + (((fifo << FIFO_SIZE_BITS) + x) << 2), 0); +} + +#if 0 +static void vortex_fifo_adbinitialize(vortex_t * vortex, int fifo, int j) +{ + vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE); +#ifdef CHIP_AU8820 + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xb))); +#else + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xc))); +#endif +} +#endif +static void vortex_fifo_setadbvalid(vortex_t * vortex, int fifo, int en) +{ + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), + (hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2)) & + 0xffffffef) | ((1 & en) << 4) | FIFO_U1); +} + +static void +vortex_fifo_setadbctrl(vortex_t * vortex, int fifo, int stereo, int priority, + int empty, int valid, int f) +{ + int temp, lifeboat = 0; + //int this_8[NR_ADB] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; /* position */ + int this_4 = 0x2; + /* f seems priority related. + * CAsp4AdbDma::SetPriority is the only place that calls SetAdbCtrl with f set to 1 + * every where else it is set to 0. It seems, however, that CAsp4AdbDma::SetPriority + * is never called, thus the f related bits remain a mystery for now. + */ + do { + temp = hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2)); + if (lifeboat++ > 0xbb8) { + dev_err(vortex->card->dev, + "vortex_fifo_setadbctrl fail\n"); + break; + } + } + while (temp & FIFO_RDONLY); + + // AU8830 semes to take some special care about fifo content (data). + // But i'm just to lazy to translate that :) + if (valid) { + if ((temp & FIFO_VALID) == 0) { + //this_8[fifo] = 0; + vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE); // this_4 +#ifdef CHIP_AU8820 + temp = (this_4 & 0x1f) << 0xb; +#else + temp = (this_4 & 0x3f) << 0xc; +#endif + temp = (temp & 0xfffffffd) | ((stereo & 1) << 1); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp |= FIFO_U1; + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); +#ifdef CHIP_AU8820 + temp = (temp & 0xfffbffff) | ((f & 1) << 0x12); +#endif +#ifdef CHIP_AU8830 + temp = (temp & 0xf7ffffff) | ((f & 1) << 0x1b); + temp = (temp & 0xefffffff) | ((f & 1) << 0x1c); +#endif +#ifdef CHIP_AU8810 + temp = (temp & 0xfeffffff) | ((f & 1) << 0x18); + temp = (temp & 0xfdffffff) | ((f & 1) << 0x19); +#endif + } + } else { + if (temp & FIFO_VALID) { +#ifdef CHIP_AU8820 + temp = ((f & 1) << 0x12) | (temp & 0xfffbffef); +#endif +#ifdef CHIP_AU8830 + temp = + ((f & 1) << 0x1b) | (temp & 0xe7ffffef) | FIFO_BITS; +#endif +#ifdef CHIP_AU8810 + temp = + ((f & 1) << 0x18) | (temp & 0xfcffffef) | FIFO_BITS; +#endif + } else + /*if (this_8[fifo]) */ + vortex_fifo_clearadbdata(vortex, fifo, FIFO_SIZE); + } + hwwrite(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2), temp); + hwread(vortex->mmio, VORTEX_FIFO_ADBCTRL + (fifo << 2)); +} + +#ifndef CHIP_AU8810 +static void vortex_fifo_clearwtdata(vortex_t * vortex, int fifo, int x) +{ + if (x < 1) + return; + for (x--; x >= 0; x--) + hwwrite(vortex->mmio, + VORTEX_FIFO_WTDATA + + (((fifo << FIFO_SIZE_BITS) + x) << 2), 0); +} + +static void vortex_fifo_wtinitialize(vortex_t * vortex, int fifo, int j) +{ + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); +#ifdef CHIP_AU8820 + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xb))); +#else + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), + (FIFO_U1 | ((j & FIFO_MASK) << 0xc))); +#endif +} + +static void vortex_fifo_setwtvalid(vortex_t * vortex, int fifo, int en) +{ + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), + (hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)) & + 0xffffffef) | ((en & 1) << 4) | FIFO_U1); +} + +static void +vortex_fifo_setwtctrl(vortex_t * vortex, int fifo, int ctrl, int priority, + int empty, int valid, int f) +{ + int temp = 0, lifeboat = 0; + int this_4 = 2; + + do { + temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + if (lifeboat++ > 0xbb8) { + dev_err(vortex->card->dev, + "vortex_fifo_setwtctrl fail\n"); + break; + } + } + while (temp & FIFO_RDONLY); + + if (valid) { + if ((temp & FIFO_VALID) == 0) { + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); // this_4 +#ifdef CHIP_AU8820 + temp = (this_4 & 0x1f) << 0xb; +#else + temp = (this_4 & 0x3f) << 0xc; +#endif + temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp |= FIFO_U1; + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); +#ifdef CHIP_AU8820 + temp = (temp & 0xfffbffff) | ((f & 1) << 0x12); +#endif +#ifdef CHIP_AU8830 + temp = (temp & 0xf7ffffff) | ((f & 1) << 0x1b); + temp = (temp & 0xefffffff) | ((f & 1) << 0x1c); +#endif +#ifdef CHIP_AU8810 + temp = (temp & 0xfeffffff) | ((f & 1) << 0x18); + temp = (temp & 0xfdffffff) | ((f & 1) << 0x19); +#endif + } + } else { + if (temp & FIFO_VALID) { +#ifdef CHIP_AU8820 + temp = ((f & 1) << 0x12) | (temp & 0xfffbffef); +#endif +#ifdef CHIP_AU8830 + temp = + ((f & 1) << 0x1b) | (temp & 0xe7ffffef) | FIFO_BITS; +#endif +#ifdef CHIP_AU8810 + temp = + ((f & 1) << 0x18) | (temp & 0xfcffffef) | FIFO_BITS; +#endif + } else + /*if (this_8[fifo]) */ + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); + } + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + +/* + do { + temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + if (lifeboat++ > 0xbb8) { + pr_err( "Vortex: vortex_fifo_setwtctrl fail (hanging)\n"); + break; + } + } while ((temp & FIFO_RDONLY)&&(temp & FIFO_VALID)&&(temp != 0xFFFFFFFF)); + + + if (valid) { + if (temp & FIFO_VALID) { + temp = 0x40000; + //temp |= 0x08000000; + //temp |= 0x10000000; + //temp |= 0x04000000; + //temp |= 0x00400000; + temp |= 0x1c400000; + temp &= 0xFFFFFFF3; + temp &= 0xFFFFFFEF; + temp |= (valid & 1) << 4; + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + return; + } else { + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); + return; + } + } else { + temp &= 0xffffffef; + temp |= 0x08000000; + temp |= 0x10000000; + temp |= 0x04000000; + temp |= 0x00400000; + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + temp = hwread(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2)); + //((temp >> 6) & 0x3f) + + priority = 0; + if (((temp & 0x0fc0) ^ ((temp >> 6) & 0x0fc0)) & 0FFFFFFC0) + vortex_fifo_clearwtdata(vortex, fifo, FIFO_SIZE); + valid = 0xfb; + temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1); + temp = (temp & 0xfffdffff) | ((f & 1) << 0x11); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + } + + */ + + /* + temp = (temp & 0xfffffffd) | ((ctrl & 1) << 1); + temp = (temp & 0xfffdffff) | ((f & 1) << 0x11); + temp = (temp & 0xfffffff3) | ((priority & 3) << 2); + temp = (temp & 0xffffffef) | ((valid & 1) << 4); + temp = (temp & 0xffffffdf) | ((empty & 1) << 5); + #ifdef FIFO_BITS + temp = temp | FIFO_BITS | 40000; + #endif + // 0x1c440010, 0x1c400000 + hwwrite(vortex->mmio, VORTEX_FIFO_WTCTRL + (fifo << 2), temp); + */ +} + +#endif +static void vortex_fifo_init(vortex_t * vortex) +{ + int x; + u32 addr; + + /* ADB DMA channels fifos. */ + addr = VORTEX_FIFO_ADBCTRL + ((NR_ADB - 1) * 4); + for (x = NR_ADB - 1; x >= 0; x--) { + hwwrite(vortex->mmio, addr, (FIFO_U0 | FIFO_U1)); + if (hwread(vortex->mmio, addr) != (FIFO_U0 | FIFO_U1)) + dev_err(vortex->card->dev, "bad adb fifo reset!\n"); + vortex_fifo_clearadbdata(vortex, x, FIFO_SIZE); + addr -= 4; + } + +#ifndef CHIP_AU8810 + /* WT DMA channels fifos. */ + addr = VORTEX_FIFO_WTCTRL + ((NR_WT - 1) * 4); + for (x = NR_WT - 1; x >= 0; x--) { + hwwrite(vortex->mmio, addr, FIFO_U0); + if (hwread(vortex->mmio, addr) != FIFO_U0) + dev_err(vortex->card->dev, + "bad wt fifo reset (0x%08x, 0x%08x)!\n", + addr, hwread(vortex->mmio, addr)); + vortex_fifo_clearwtdata(vortex, x, FIFO_SIZE); + addr -= 4; + } +#endif + /* trigger... */ +#ifdef CHIP_AU8820 + hwwrite(vortex->mmio, 0xf8c0, 0xd03); //0x0843 0xd6b +#else +#ifdef CHIP_AU8830 + hwwrite(vortex->mmio, 0x17000, 0x61); /* wt a */ + hwwrite(vortex->mmio, 0x17004, 0x61); /* wt b */ +#endif + hwwrite(vortex->mmio, 0x17008, 0x61); /* adb */ +#endif +} + +/* ADBDMA */ + +static void vortex_adbdma_init(vortex_t * vortex) +{ +} + +static void vortex_adbdma_setfirstbuffer(vortex_t * vortex, int adbdma) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + hwwrite(vortex->mmio, VORTEX_ADBDMA_CTRL + (adbdma << 2), + dma->dma_ctrl); +} + +static void vortex_adbdma_setstartbuffer(vortex_t * vortex, int adbdma, int sb) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + //hwwrite(vortex->mmio, VORTEX_ADBDMA_START + (adbdma << 2), sb << (((NR_ADB-1)-((adbdma&0xf)*2)))); + hwwrite(vortex->mmio, VORTEX_ADBDMA_START + (adbdma << 2), + sb << ((0xf - (adbdma & 0xf)) * 2)); + dma->period_real = dma->period_virt = sb; +} + +static void +vortex_adbdma_setbuffers(vortex_t * vortex, int adbdma, + int psize, int count) +{ + stream_t *dma = &vortex->dma_adb[adbdma]; + + dma->period_bytes = psize; + dma->nr_periods = count; + + dma->cfg0 = 0; + dma->cfg1 = 0; + switch (count) { + /* Four or more pages */ + default: + case 4: + dma->cfg1 |= 0x88000000 | 0x44000000 | 0x30000000 | (psize - 1); + hwwrite(vortex->mmio, + VORTEX_ADBDMA_BUFBASE + (adbdma << 4) + 0xc, + snd_pcm_sgbuf_get_addr(dma->substream, psize * 3)); + /* 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)); + /* 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)); + /* 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)); + /* 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)); + /* 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)); + /* 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 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 char +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 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..b566b44e4 --- /dev/null +++ b/sound/pci/au88x0/au88x0_eq.c @@ -0,0 +1,930 @@ +/*************************************************************************** + * 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. + * + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +/* + 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, 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, 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, u16 a[], 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, u16 a[], 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, 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, 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, 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, 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, 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, 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 char *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..49a52d298 --- /dev/null +++ b/sound/pci/au88x0/au88x0_eqdata.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Data structs */ + +static 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 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 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 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 u16 eq_gains_zero[10] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +/* _rodatab7c: ProgramPipe */ +static u16 eq_gains_current[12] = { + 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, 0x7fff, + 0x7fff, + 0x7fff, 0x7fff, 0x7fff +}; + +/* _rodatab78 */ +static u16 eq_states_zero[2] = { 0x0000, 0x0000 }; + +static 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 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..53abcd3ec --- /dev/null +++ b/sound/pci/au88x0/au88x0_game.c @@ -0,0 +1,134 @@ +/* + * Manuel Jander. + * + * Based on the work of: + * Vojtech Pavlik + * Raymond Ingles + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to , or by paper mail: + * Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic + * + * Based 90% on Vojtech Pavlik pcigame driver. + * Merged and modified by Manuel Jander, for the OpenVortex + * driver. (email: mjander@embedded.cl). + */ + +#include +#include +#include +#include +#include "au88x0.h" +#include +#include + +#if IS_REACHABLE(CONFIG_GAMEPORT) + +#define VORTEX_GAME_DWAIT 20 /* 20 ms */ + +static unsigned char vortex_game_read(struct gameport *gameport) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + return hwread(vortex->mmio, VORTEX_GAME_LEGACY); +} + +static void vortex_game_trigger(struct gameport *gameport) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + hwwrite(vortex->mmio, VORTEX_GAME_LEGACY, 0xff); +} + +static int +vortex_game_cooked_read(struct gameport *gameport, int *axes, int *buttons) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + int i; + + *buttons = (~hwread(vortex->mmio, VORTEX_GAME_LEGACY) >> 4) & 0xf; + + for (i = 0; i < 4; i++) { + axes[i] = + hwread(vortex->mmio, VORTEX_GAME_AXIS + (i * AXIS_SIZE)); + if (axes[i] == AXIS_RANGE) + axes[i] = -1; + } + return 0; +} + +static int vortex_game_open(struct gameport *gameport, int mode) +{ + vortex_t *vortex = gameport_get_port_data(gameport); + + switch (mode) { + case GAMEPORT_MODE_COOKED: + hwwrite(vortex->mmio, VORTEX_CTRL2, + hwread(vortex->mmio, + VORTEX_CTRL2) | CTRL2_GAME_ADCMODE); + msleep(VORTEX_GAME_DWAIT); + return 0; + case GAMEPORT_MODE_RAW: + hwwrite(vortex->mmio, VORTEX_CTRL2, + hwread(vortex->mmio, + VORTEX_CTRL2) & ~CTRL2_GAME_ADCMODE); + return 0; + default: + return -1; + } + + return 0; +} + +static int vortex_gameport_register(vortex_t *vortex) +{ + struct gameport *gp; + + vortex->gameport = gp = gameport_allocate_port(); + if (!gp) { + dev_err(vortex->card->dev, + "cannot allocate memory for gameport\n"); + return -ENOMEM; + } + + gameport_set_name(gp, "AU88x0 Gameport"); + gameport_set_phys(gp, "pci%s/gameport0", pci_name(vortex->pci_dev)); + gameport_set_dev_parent(gp, &vortex->pci_dev->dev); + + gp->read = vortex_game_read; + gp->trigger = vortex_game_trigger; + gp->cooked_read = vortex_game_cooked_read; + gp->open = vortex_game_open; + + gameport_set_port_data(gp, vortex); + gp->fuzz = 64; + + gameport_register_port(gp); + + return 0; +} + +static void vortex_gameport_unregister(vortex_t * vortex) +{ + if (vortex->gameport) { + gameport_unregister_port(vortex->gameport); + vortex->gameport = NULL; + } +} + +#else +static inline int vortex_gameport_register(vortex_t * vortex) { return -ENOSYS; } +static inline void vortex_gameport_unregister(vortex_t * vortex) { } +#endif diff --git a/sound/pci/au88x0/au88x0_mixer.c b/sound/pci/au88x0/au88x0_mixer.c new file mode 100644 index 000000000..60dd8a091 --- /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 +#include +#include +#include "au88x0.h" + +static int remove_ctl(struct snd_card *card, const char *name) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + 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 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..1025e55ca --- /dev/null +++ b/sound/pci/au88x0/au88x0_mpu401.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of MPU-401 in UART mode + * + * Modified for the Aureal Vortex based Soundcards + * by Manuel Jander (mjande@embedded.cl). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "au88x0.h" + +/* Check for mpu401 mmio support. */ +/* MPU401 legacy support is only provided as a emergency fallback * + * for older versions of ALSA. Its usage is strongly discouraged. */ +#ifndef MPU401_HW_AUREAL +#define VORTEX_MPU401_LEGACY +#endif + +/* Vortex MPU401 defines. */ +#define MIDI_CLOCK_DIV 0x61 +/* Standart MPU401 defines. */ +#define MPU401_RESET 0xff +#define MPU401_ENTER_UART 0x3f +#define MPU401_ACK 0xfe + +static int snd_vortex_midi(vortex_t *vortex) +{ + struct snd_rawmidi *rmidi; + int temp, mode; + struct snd_mpu401 *mpu; + unsigned long port; + +#ifdef VORTEX_MPU401_LEGACY + /* EnableHardCodedMPU401Port() */ + /* Enable Legacy MIDI Interface port. */ + port = (0x03 << 5); /* FIXME: static address. 0x330 */ + temp = + (hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) | + CTRL_MIDI_EN | port; + hwwrite(vortex->mmio, VORTEX_CTRL, temp); +#else + /* Disable Legacy MIDI Interface port. */ + temp = + (hwread(vortex->mmio, VORTEX_CTRL) & ~CTRL_MIDI_PORT) & + ~CTRL_MIDI_EN; + hwwrite(vortex->mmio, VORTEX_CTRL, temp); +#endif + /* Mpu401UartInit() */ + mode = 1; + temp = hwread(vortex->mmio, VORTEX_CTRL2) & 0xffff00cf; + temp |= (MIDI_CLOCK_DIV << 8) | ((mode >> 24) & 0xff) << 4; + hwwrite(vortex->mmio, VORTEX_CTRL2, temp); + hwwrite(vortex->mmio, VORTEX_MIDI_CMD, MPU401_RESET); + + /* Check if anything is OK. */ + temp = hwread(vortex->mmio, VORTEX_MIDI_DATA); + if (temp != MPU401_ACK /*0xfe */ ) { + dev_err(vortex->card->dev, "midi port doesn't acknowledge!\n"); + return -ENODEV; + } + /* Enable MPU401 interrupts. */ + hwwrite(vortex->mmio, VORTEX_IRQ_CTRL, + hwread(vortex->mmio, VORTEX_IRQ_CTRL) | IRQ_MIDI); + + /* Create MPU401 instance. */ +#ifdef VORTEX_MPU401_LEGACY + 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..53714e0bf --- /dev/null +++ b/sound/pci/au88x0/au88x0_pcm.c @@ -0,0 +1,704 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +/* + * Vortex PCM ALSA driver. + * + * Supports ADB and WT DMA. Unfortunately, WT channels do not run yet. + * It remains stuck,and DMA transfers do not happen. + */ +#include +#include +#include +#include +#include +#include "au88x0.h" + +#define VORTEX_PCM_TYPE(x) (x->name[40]) + +/* hardware definition */ +static const struct snd_pcm_hardware snd_vortex_playback_hw_adb = { + .info = + (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x20, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 1024, +}; + +#ifndef CHIP_AU8820 +static const struct snd_pcm_hardware snd_vortex_playback_hw_a3d = { + .info = + (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x100, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 64, +}; +#endif +static const struct snd_pcm_hardware snd_vortex_playback_hw_spdif = { + .info = + (SNDRV_PCM_INFO_MMAP | /* SNDRV_PCM_INFO_RESUME | */ + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE | SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW, + .rates = + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x100, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 64, +}; + +#ifndef CHIP_AU8810 +static const struct snd_pcm_hardware snd_vortex_playback_hw_wt = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS, // SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 0x10000, + .period_bytes_min = 0x0400, + .period_bytes_max = 0x1000, + .periods_min = 2, + .periods_max = 64, +}; +#endif +#ifdef CHIP_AU8830 +static const unsigned int au8830_channels[3] = { + 1, 2, 4, +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_au8830_channels = { + .count = ARRAY_SIZE(au8830_channels), + .list = au8830_channels, + .mask = 0, +}; +#endif + +static void vortex_notify_pcm_vol_change(struct snd_card *card, + struct snd_kcontrol *kctl, int activate) +{ + if (activate) + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + else + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &(kctl->id)); +} + +/* open callback */ +static int snd_vortex_pcm_open(struct snd_pcm_substream *substream) +{ + vortex_t *vortex = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* Force equal size periods */ + 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); + int err; + + // Alloc buffer memory. + err = + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) { + dev_err(chip->card->dev, "Vortex: pcm page alloc failed!\n"); + return err; + } + /* + 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 snd_pcm_lib_free_pages(substream); +} + +/* 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* +* definitions of capture are omitted here... +*/ + +static char *vortex_pcm_prettyname[VORTEX_PCM_LAST] = { + CARD_NAME " ADB", + CARD_NAME " SPDIF", + CARD_NAME " A3D", + CARD_NAME " WT", + CARD_NAME " I2S", +}; +static char *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 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci_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..78e12f479 --- /dev/null +++ b/sound/pci/au88x0/au88x0_synth.c @@ -0,0 +1,412 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +/* + * 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..b278e285f --- /dev/null +++ b/sound/pci/au88x0/au88x0_xtalk.c @@ -0,0 +1,795 @@ +/*************************************************************************** + * au88x0_cxtalk.c + * + * Wed Nov 19 16:29:47 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.org + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "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 short const sXtalkWideKRightXt = 0xF25E; +static short const sXtalkWideShiftLeftEq = 1; +static short const sXtalkWideShiftRightEq = 1; +static short const sXtalkWideShiftLeftXt = 0; +static 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 short const sXtalkNarrowKRightXt = 0xF82E; +static short const sXtalkNarrowShiftLeftEq = 0x3; +static short const sXtalkNarrowShiftRightEq = 0x3; +static short const sXtalkNarrowShiftLeftXt = 0; +static short const sXtalkNarrowShiftRightXt = 0; +static unsigned short const wXtalkNarrowLeftDelay = 0x7; +static unsigned short const wXtalkNarrowRightDelay = 0x7; + +static xtalk_gains_t const asXtalkGainsDefault = { + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, + 0x4000, 0x4000, 0x4000, 0x4000, 0x4000 +}; + +static xtalk_gains_t const asXtalkGainsTest = { + 0x7fff, 0x8000, 0x0000, 0x0000, 0x0001, + 0xffff, 0x4000, 0xc000, 0x0002, 0xfffe +}; + +static 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 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 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 short const sDiamondKRightXt = 0xF90E; +static short const sDiamondShiftLeftEq = 1; +static short const sDiamondShiftRightEq = 1; +static short const sDiamondShiftLeftXt = 0; +static 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 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 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 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 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 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 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 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..7f4534b94 --- /dev/null +++ b/sound/pci/au88x0/au88x0_xtalk.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * au88x0_cxtalk.h + * + * Wed Nov 19 19:07:17 2003 + * Copyright 2003 mjander + * mjander@users.sourceforge.org + ****************************************************************************/ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library 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. + */ + +/* 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..842335d3b --- /dev/null +++ b/sound/pci/aw2/Makefile @@ -0,0 +1,3 @@ +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..9a49e4243 --- /dev/null +++ b/sound/pci/aw2/aw2-alsa.c @@ -0,0 +1,761 @@ +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + * The Audiowerk2 ALSA driver 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; version 2. + * + * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7146.h" +#include "aw2-saa7146.h" + +MODULE_AUTHOR("Cedric Bregardis , " + "Jean-Christian Hassler "); +MODULE_DESCRIPTION("Emagic Audiowerk 2 sound driver"); +MODULE_LICENSE("GPL"); + +/********************************* + * DEFINES + ********************************/ +#define CTL_ROUTE_ANALOG 0 +#define CTL_ROUTE_DIGITAL 1 + +/********************************* + * TYPEDEFS + ********************************/ + /* hardware definition */ +static const struct snd_pcm_hardware snd_aw2_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 4, + .buffer_bytes_max = 32768, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +static const struct snd_pcm_hardware snd_aw2_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 32768, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +struct aw2_pcm_device { + struct snd_pcm *pcm; + unsigned int stream_number; + struct aw2 *chip; +}; + +struct aw2 { + struct snd_aw2_saa7146 saa7146; + + struct pci_dev *pci; + int irq; + spinlock_t reg_lock; + struct mutex mtx; + + unsigned long iobase_phys; + void __iomem *iobase_virt; + + struct snd_card *card; + + struct aw2_pcm_device device_playback[NB_STREAM_PLAYBACK]; + struct aw2_pcm_device device_capture[NB_STREAM_CAPTURE]; +}; + +/********************************* + * FUNCTION DECLARATIONS + ********************************/ +static int snd_aw2_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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params); +static int snd_aw2_pcm_hw_free(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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_aw2_pcm_hw_params, + .hw_free = snd_aw2_pcm_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_aw2_pcm_hw_params, + .hw_free = snd_aw2_pcm_hw_free, + .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 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_nocache(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; + + 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; +} + + /* hw_params callback */ +static int snd_aw2_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +/* hw_free callback */ +static int snd_aw2_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* 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. */ + err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_ana, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data + (chip->pci), + 64 * 1024, 64 * 1024); + if (err) + dev_err(chip->card->dev, + "snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n", + err); + + 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. */ + err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_num, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data + (chip->pci), + 64 * 1024, 64 * 1024); + if (err) + dev_err(chip->card->dev, + "snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n", + err); + + 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. */ + err = snd_pcm_lib_preallocate_pages_for_all(pcm_capture, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data + (chip->pci), + 64 * 1024, 64 * 1024); + if (err) + dev_err(chip->card->dev, + "snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n", + err); + + + /* 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..1d7890459 --- /dev/null +++ b/sound/pci/aw2/aw2-saa7146.c @@ -0,0 +1,461 @@ +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + * The Audiowerk2 ALSA driver 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; version 2. + * + * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + *****************************************************************************/ + +#define AW2_SAA7146_M + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7146.h" +#include "aw2-saa7146.h" + +#include "aw2-tsl.c" + +#define WRITEREG(value, addr) writel((value), chip->base_addr + (addr)) +#define READREG(addr) readl(chip->base_addr + (addr)) + +static struct snd_aw2_saa7146_cb_param + arr_substream_it_playback_cb[NB_STREAM_PLAYBACK]; +static struct snd_aw2_saa7146_cb_param + arr_substream_it_capture_cb[NB_STREAM_CAPTURE]; + +static int snd_aw2_saa7146_get_limit(int size); + +/* chip-specific destructor */ +int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip) +{ + /* disable all irqs */ + WRITEREG(0, IER); + + /* reset saa7146 */ + WRITEREG((MRST_N << 16), MC1); + + /* Unset base addr */ + chip->base_addr = NULL; + + return 0; +} + +void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip, + void __iomem *pci_base_addr) +{ + /* set PCI burst/threshold + + Burst length definition + VALUE BURST LENGTH + 000 1 Dword + 001 2 Dwords + 010 4 Dwords + 011 8 Dwords + 100 16 Dwords + 101 32 Dwords + 110 64 Dwords + 111 128 Dwords + + Threshold definition + VALUE WRITE MODE READ MODE + 00 1 Dword of valid data 1 empty Dword + 01 4 Dwords of valid data 4 empty Dwords + 10 8 Dwords of valid data 8 empty Dwords + 11 16 Dwords of valid data 16 empty Dwords */ + + unsigned int acon2; + unsigned int acon1 = 0; + int i; + + /* Set base addr */ + chip->base_addr = pci_base_addr; + + /* disable all irqs */ + WRITEREG(0, IER); + + /* reset saa7146 */ + WRITEREG((MRST_N << 16), MC1); + + /* enable audio interface */ +#ifdef __BIG_ENDIAN + acon1 |= A1_SWAP; + acon1 |= A2_SWAP; +#endif + /* WS0_CTRL, WS0_SYNC: input TSL1, I2S */ + + /* At initialization WS1 and WS2 are disabled (configured as input) */ + acon1 |= 0 * WS1_CTRL; + acon1 |= 0 * WS2_CTRL; + + /* WS4 is not used. So it must not restart A2. + This is why it is configured as output (force to low) */ + acon1 |= 3 * WS4_CTRL; + + /* WS3_CTRL, WS3_SYNC: output TSL2, I2S */ + acon1 |= 2 * WS3_CTRL; + + /* A1 and A2 are active and asynchronous */ + acon1 |= 3 * AUDIO_MODE; + WRITEREG(acon1, ACON1); + + /* The following comes from original windows driver. + It is needed to have a correct behavior of input and output + simultenously, but I don't know why ! */ + WRITEREG(3 * (BurstA1_in) + 3 * (ThreshA1_in) + + 3 * (BurstA1_out) + 3 * (ThreshA1_out) + + 3 * (BurstA2_out) + 3 * (ThreshA2_out), PCI_BT_A); + + /* enable audio port pins */ + WRITEREG((EAP << 16) | EAP, MC1); + + /* enable I2C */ + WRITEREG((EI2C << 16) | EI2C, MC1); + /* enable interrupts */ + WRITEREG(A1_out | A2_out | A1_in | IIC_S | IIC_E, IER); + + /* audio configuration */ + acon2 = A2_CLKSRC | BCLK1_OEN; + WRITEREG(acon2, ACON2); + + /* By default use analog input */ + snd_aw2_saa7146_use_digital_input(chip, 0); + + /* TSL setup */ + for (i = 0; i < 8; ++i) { + WRITEREG(tsl1[i], TSL1 + (i * 4)); + WRITEREG(tsl2[i], TSL2 + (i * 4)); + } + +} + +void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip, + int stream_number, + unsigned long dma_addr, + unsigned long period_size, + unsigned long buffer_size) +{ + unsigned long dw_page, dw_limit; + + /* Configure DMA for substream + Configuration informations: ALSA has allocated continuous memory + pages. So we don't need to use MMU of saa7146. + */ + + /* No MMU -> nothing to do with PageA1, we only configure the limit of + PageAx_out register */ + /* Disable MMU */ + dw_page = (0L << 11); + + /* Configure Limit for DMA access. + The limit register defines an address limit, which generates + an interrupt if passed by the actual PCI address pointer. + '0001' means an interrupt will be generated if the lower + 6 bits (64 bytes) of the PCI address are zero. '0010' + defines a limit of 128 bytes, '0011' one of 256 bytes, and + so on up to 1 Mbyte defined by '1111'. This interrupt range + can be calculated as follows: + Range = 2^(5 + Limit) bytes. + */ + dw_limit = snd_aw2_saa7146_get_limit(period_size); + dw_page |= (dw_limit << 4); + + if (stream_number == 0) { + WRITEREG(dw_page, PageA2_out); + + /* Base address for DMA transfert. */ + /* This address has been reserved by ALSA. */ + /* This is a physical address */ + WRITEREG(dma_addr, BaseA2_out); + + /* Define upper limit for DMA access */ + WRITEREG(dma_addr + buffer_size, ProtA2_out); + + } else if (stream_number == 1) { + WRITEREG(dw_page, PageA1_out); + + /* Base address for DMA transfert. */ + /* This address has been reserved by ALSA. */ + /* This is a physical address */ + WRITEREG(dma_addr, BaseA1_out); + + /* Define upper limit for DMA access */ + WRITEREG(dma_addr + buffer_size, ProtA1_out); + } else { + pr_err("aw2: snd_aw2_saa7146_pcm_init_playback: " + "Substream number is not 0 or 1 -> not managed\n"); + } +} + +void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip, + int stream_number, unsigned long dma_addr, + unsigned long period_size, + unsigned long buffer_size) +{ + unsigned long dw_page, dw_limit; + + /* Configure DMA for substream + Configuration informations: ALSA has allocated continuous memory + pages. So we don't need to use MMU of saa7146. + */ + + /* No MMU -> nothing to do with PageA1, we only configure the limit of + PageAx_out register */ + /* Disable MMU */ + dw_page = (0L << 11); + + /* Configure Limit for DMA access. + The limit register defines an address limit, which generates + an interrupt if passed by the actual PCI address pointer. + '0001' means an interrupt will be generated if the lower + 6 bits (64 bytes) of the PCI address are zero. '0010' + defines a limit of 128 bytes, '0011' one of 256 bytes, and + so on up to 1 Mbyte defined by '1111'. This interrupt range + can be calculated as follows: + Range = 2^(5 + Limit) bytes. + */ + dw_limit = snd_aw2_saa7146_get_limit(period_size); + dw_page |= (dw_limit << 4); + + if (stream_number == 0) { + WRITEREG(dw_page, PageA1_in); + + /* Base address for DMA transfert. */ + /* This address has been reserved by ALSA. */ + /* This is a physical address */ + WRITEREG(dma_addr, BaseA1_in); + + /* Define upper limit for DMA access */ + WRITEREG(dma_addr + buffer_size, ProtA1_in); + } else { + pr_err("aw2: snd_aw2_saa7146_pcm_init_capture: " + "Substream number is not 0 -> not managed\n"); + } +} + +void snd_aw2_saa7146_define_it_playback_callback(unsigned int stream_number, + snd_aw2_saa7146_it_cb + p_it_callback, + void *p_callback_param) +{ + if (stream_number < NB_STREAM_PLAYBACK) { + arr_substream_it_playback_cb[stream_number].p_it_callback = + (snd_aw2_saa7146_it_cb) p_it_callback; + arr_substream_it_playback_cb[stream_number].p_callback_param = + (void *)p_callback_param; + } +} + +void snd_aw2_saa7146_define_it_capture_callback(unsigned int stream_number, + snd_aw2_saa7146_it_cb + p_it_callback, + void *p_callback_param) +{ + if (stream_number < NB_STREAM_CAPTURE) { + arr_substream_it_capture_cb[stream_number].p_it_callback = + (snd_aw2_saa7146_it_cb) p_it_callback; + arr_substream_it_capture_cb[stream_number].p_callback_param = + (void *)p_callback_param; + } +} + +void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + unsigned int acon1 = 0; + /* In aw8 driver, dma transfert is always active. It is + started and stopped in a larger "space" */ + acon1 = READREG(ACON1); + if (stream_number == 0) { + WRITEREG((TR_E_A2_OUT << 16) | TR_E_A2_OUT, MC1); + + /* WS2_CTRL, WS2_SYNC: output TSL2, I2S */ + acon1 |= 2 * WS2_CTRL; + WRITEREG(acon1, ACON1); + + } else if (stream_number == 1) { + WRITEREG((TR_E_A1_OUT << 16) | TR_E_A1_OUT, MC1); + + /* WS1_CTRL, WS1_SYNC: output TSL1, I2S */ + acon1 |= 1 * WS1_CTRL; + WRITEREG(acon1, ACON1); + } +} + +void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + unsigned int acon1 = 0; + acon1 = READREG(ACON1); + if (stream_number == 0) { + /* WS2_CTRL, WS2_SYNC: output TSL2, I2S */ + acon1 &= ~(3 * WS2_CTRL); + WRITEREG(acon1, ACON1); + + WRITEREG((TR_E_A2_OUT << 16), MC1); + } else if (stream_number == 1) { + /* WS1_CTRL, WS1_SYNC: output TSL1, I2S */ + acon1 &= ~(3 * WS1_CTRL); + WRITEREG(acon1, ACON1); + + WRITEREG((TR_E_A1_OUT << 16), MC1); + } +} + +void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + /* In aw8 driver, dma transfert is always active. It is + started and stopped in a larger "space" */ + if (stream_number == 0) + WRITEREG((TR_E_A1_IN << 16) | TR_E_A1_IN, MC1); +} + +void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146 *chip, + int stream_number) +{ + if (stream_number == 0) + WRITEREG((TR_E_A1_IN << 16), MC1); +} + +irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id) +{ + unsigned int isr; + 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..5b35e3589 --- /dev/null +++ b/sound/pci/aw2/aw2-saa7146.h @@ -0,0 +1,105 @@ +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + * The Audiowerk2 ALSA driver 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; version 2. + * + * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + *****************************************************************************/ + +#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..459b0311e --- /dev/null +++ b/sound/pci/aw2/aw2-tsl.c @@ -0,0 +1,110 @@ +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * Copyright 1998 Emagic Soft- und Hardware GmbH + * Copyright 2002 Martijn Sipkema + * + * This file is part of the Audiowerk2 ALSA driver + * + * The Audiowerk2 ALSA driver 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; version 2. + * + * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + *****************************************************************************/ + +#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 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 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..ce0ab5f9e --- /dev/null +++ b/sound/pci/aw2/saa7146.h @@ -0,0 +1,168 @@ +/***************************************************************************** + * + * Copyright (C) 2008 Cedric Bregardis and + * Jean-Christian Hassler + * + * This file is part of the Audiowerk2 ALSA driver + * + * The Audiowerk2 ALSA driver 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; version 2. + * + * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + *****************************************************************************/ + +/* 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..fc18c29a8 --- /dev/null +++ b/sound/pci/azt3328.c @@ -0,0 +1,2762 @@ +/* azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168). + * Copyright (C) 2002, 2005 - 2011 by Andreas Mohr + * + * Framework borrowed from Bart Hartgers's als4000.c. + * Driver developed on PCI168 AP(W) version (PCI rev. 10, subsystem ID 1801), + * found in a Fujitsu-Siemens PC ("Cordant", aluminum case). + * Other versions are: + * PCI168 A(W), sub ID 1800 + * PCI168 A/AP, sub ID 8000 + * Please give me feedback in case you try my driver with one of these!! + * + * Keywords: Windows XP Vista 168nt4-125.zip 168win95-125.zip PCI 168 download + * (XP/Vista do not support this card at all but every Linux distribution + * has very good support out of the box; + * just to make sure that the right people hit this and get to know that, + * despite the high level of Internet ignorance - as usual :-P - + * about very good support for this card - on Linux!) + * + * GPL LICENSE + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * NOTES + * Since Aztech does not provide any chipset documentation, + * even on repeated request to various addresses, + * and the answer that was finally given was negative + * (and I was stupid enough to manage to get hold of a PCI168 soundcard + * in the first place >:-P}), + * I was forced to base this driver on reverse engineering + * (3 weeks' worth of evenings filled with driver work). + * (and no, I did NOT go the easy way: to pick up a SB PCI128 for 9 Euros) + * + * It is quite likely that the AZF3328 chip is the PCI cousin of the + * AZF3318 ("azt1020 pnp", "MM Pro 16") ISA chip, given very similar specs. + * + * The AZF3328 chip (note: AZF3328, *not* AZT3328, that's just the driver name + * for compatibility reasons) from Azfin (joint-venture of Aztech and Fincitec, + * Fincitec acquired by National Semiconductor in 2002, together with the + * Fincitec-related company ARSmikro) has the following features: + * + * - compatibility & compliance: + * - Microsoft PC 97 ("PC 97 Hardware Design Guide", + * http://www.microsoft.com/whdc/archive/pcguides.mspx) + * - Microsoft PC 98 Baseline Audio + * - MPU401 UART + * - Sound Blaster Emulation (DOS Box) + * - builtin AC97 conformant codec (SNR over 80dB) + * Note that "conformant" != "compliant"!! this chip's mixer register layout + * *differs* from the standard AC97 layout: + * they chose to not implement the headphone register (which is not a + * problem since it's merely optional), yet when doing this, they committed + * the grave sin of letting other registers follow immediately instead of + * keeping a headphone dummy register, thereby shifting the mixer register + * addresses illegally. So far unfortunately it looks like the very flexible + * ALSA AC97 support is still not enough to easily compensate for such a + * grave layout violation despite all tweaks and quirks mechanisms it offers. + * Well, not quite: now ac97 layer is much improved (bus-specific ops!), + * thus I was able to implement support - it's actually working quite well. + * An interesting item might be Aztech AMR 2800-W, since it's an AC97 + * modem card which might reveal the Aztech-specific codec ID which + * we might want to pretend, too. Dito PCI168's brother, PCI368, + * where the advertising datasheet says it's AC97-based and has a + * Digital Enhanced Game Port. + * - builtin genuine OPL3 - verified to work fine, 20080506 + * - full duplex 16bit playback/record at independent sampling rate + * - MPU401 (+ legacy address support, claimed by one official spec sheet) + * FIXME: how to enable legacy addr?? + * - game port (legacy address support) + * - builtin DirectInput support, helps reduce CPU overhead (interrupt-driven + * features supported). - See common term "Digital Enhanced Game Port"... + * (probably DirectInput 3.0 spec - confirm) + * - builtin 3D enhancement (said to be YAMAHA Ymersion) + * - built-in General DirectX timer having a 20 bits counter + * with 1us resolution (see below!) + * - I2S serial output port for external DAC + * [FIXME: 3.3V or 5V level? maximum rate is 66.2kHz right?] + * - supports 33MHz PCI spec 2.1, PCI power management 1.0, compliant with ACPI + * - supports hardware volume control + * - single chip low cost solution (128 pin QFP) + * - supports programmable Sub-vendor and Sub-system ID [24C02 SEEPROM chip] + * required for Microsoft's logo compliance (FIXME: where?) + * At least the Trident 4D Wave DX has one bit somewhere + * to enable writes to PCI subsystem VID registers, that should be it. + * This might easily be in extended PCI reg space, since PCI168 also has + * some custom data starting at 0x80. What kind of config settings + * are located in our extended PCI space anyway?? + * - PCI168 AP(W) card: power amplifier with 4 Watts/channel at 4 Ohms + * [TDA1517P chip] + * + * Note that this driver now is actually *better* than the Windows driver, + * since it additionally supports the card's 1MHz DirectX timer - just try + * the following snd-seq module parameters etc.: + * - options snd-seq seq_default_timer_class=2 seq_default_timer_sclass=0 + * seq_default_timer_card=0 seq_client_load=1 seq_default_timer_device=0 + * seq_default_timer_subdevice=0 seq_default_timer_resolution=1000000 + * - "timidity -iAv -B2,8 -Os -EFreverb=0" + * - "pmidi -p 128:0 jazz.mid" + * + * OPL3 hardware playback testing, try something like: + * cat /proc/asound/hwdep + * and + * aconnect -o + * Then use + * sbiload -Dhw:x,y --opl3 /usr/share/sounds/opl3/std.o3 ......./drums.o3 + * where x,y is the xx-yy number as given in hwdep. + * Then try + * pmidi -p a:b jazz.mid + * where a:b is the client number plus 0 usually, as given by aconnect above. + * Oh, and make sure to unmute the FM mixer control (doh!) + * NOTE: power use during OPL3 playback is _VERY_ high (70W --> 90W!) + * despite no CPU activity, possibly due to hindering ACPI idling somehow. + * Shouldn't be a problem of the AZF3328 chip itself, I'd hope. + * Higher PCM / FM mixer levels seem to conflict (causes crackling), + * at least sometimes. Maybe even use with hardware sequencer timer above :) + * adplay/adplug-utils might soon offer hardware-based OPL3 playback, too. + * + * Certain PCI versions of this card are susceptible to DMA traffic underruns + * in some systems (resulting in sound crackling/clicking/popping), + * probably because they don't have a DMA FIFO buffer or so. + * Overview (PCI ID/PCI subID/PCI rev.): + * - no DMA crackling on SiS735: 0x50DC/0x1801/16 + * - unknown performance: 0x50DC/0x1801/10 + * (well, it's not bad on an Athlon 1800 with now very optimized IRQ handler) + * + * Crackling happens with VIA chipsets or, in my case, an SiS735, which is + * supposed to be very fast and supposed to get rid of crackling much + * better than a VIA, yet ironically I still get crackling, like many other + * people with the same chipset. + * Possible remedies: + * - use speaker (amplifier) output instead of headphone output + * (in case crackling is due to overloaded output clipping) + * - plug card into a different PCI slot, preferably one that isn't shared + * too much (this helps a lot, but not completely!) + * - get rid of PCI VGA card, use AGP instead + * - upgrade or downgrade BIOS + * - fiddle with PCI latency settings (setpci -v -s BUSID latency_timer=XX) + * Not too helpful. + * - Disable ACPI/power management/"Auto Detect RAM/PCI Clk" in BIOS + * + * BUGS + * - full-duplex might *still* be problematic, however a recent test was fine + * - (non-bug) "Bass/Treble or 3D settings don't work" - they do get evaluated + * if you set PCM output switch to "pre 3D" instead of "post 3D". + * If this can't be set, then get a mixer application that Isn't Stupid (tm) + * (e.g. kmix, gamix) - unfortunately several are!! + * - locking is not entirely clean, especially the audio stream activity + * ints --> may be racy + * - an _unconnected_ secondary joystick at the gameport will be reported + * to be "active" (floating values, not precisely -1) due to the way we need + * to read the Digital Enhanced Game Port. Not sure whether it is fixable. + * + * TODO + * - use PCI_VDEVICE + * - verify driver status on x86_64 + * - test multi-card driver operation + * - (ab)use 1MHz DirectX timer as kernel clocksource + * - test MPU401 MIDI playback etc. + * - add more power micro-management (disable various units of the card + * as long as they're unused, to improve audio quality and save power). + * However this requires more I/O ports which I haven't figured out yet + * and which thus might not even exist... + * The standard suspend/resume functionality could probably make use of + * some improvement, too... + * - figure out what all unknown port bits are responsible for + * - figure out some cleverly evil scheme to possibly make ALSA AC97 code + * fully accept our quite incompatible ""AC97"" mixer and thus save some + * code (but I'm not too optimistic that doing this is possible at all) + * - use MMIO (memory-mapped I/O)? Slightly faster access, e.g. for gameport. + */ + +#include +#include +#include /* WARN_ONCE */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* + * Config switch, to use ALSA's AC97 layer instead of old custom mixer crap. + * If the AC97 compatibility parts we needed to implement locally turn out + * to work nicely, then remove the old implementation eventually. + */ +#define AZF_USE_AC97_LAYER 1 + +#ifdef AZF_USE_AC97_LAYER +#include +#endif +#include "azt3328.h" + +MODULE_AUTHOR("Andreas Mohr "); +MODULE_DESCRIPTION("Aztech AZF3328 (PCI168)"); +MODULE_LICENSE("GPL"); +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 struct snd_ac97_bus_ops ops = { + .write = snd_azf3328_mixer_ac97_write, + .read = snd_azf3328_mixer_ac97_read, + }; + int rc; + + memset(&ac97, 0, sizeof(ac97)); + ac97.scaps = AC97_SCAP_SKIP_MODEM + | AC97_SCAP_AUDIO /* we support audio! */ + | AC97_SCAP_NO_SPDIF; + ac97.private_data = chip; + ac97.pci = chip->pci; + + /* + * ALSA's AC97 layer has terrible init crackling issues, + * unfortunately, and since it makes use of AC97_RESET, + * there's no use trying to mute Master Playback proactively. + */ + + rc = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus); + if (!rc) + rc = snd_ac97_mixer(bus, &ac97, &chip->ac97); + /* + * Make sure to complain loudly in case of AC97 init failure, + * since failure may happen quite often, + * due to this card being a very quirky AC97 "lookalike". + */ + if (rc) + dev_err(chip->card->dev, "AC97 init failed, err %d!\n", rc); + + /* If we return an error here, then snd_card_free() should + * free up any ac97 codecs that got created, as well as the bus. + */ + return rc; +} +#else /* AZF_USE_AC97_LAYER */ +static void +snd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip, + unsigned reg, + unsigned char dst_vol_left, + unsigned char dst_vol_right, + int chan_sel, int delay +) +{ + unsigned long portbase = chip->mixer_io + reg; + unsigned char curr_vol_left = 0, curr_vol_right = 0; + int left_change = 0, right_change = 0; + + if (chan_sel & SET_CHAN_LEFT) { + curr_vol_left = inb(portbase + 1); + + /* take care of muting flag contained in left channel */ + if (curr_vol_left & AZF_MUTE_BIT) + dst_vol_left |= AZF_MUTE_BIT; + else + dst_vol_left &= ~AZF_MUTE_BIT; + + left_change = (curr_vol_left > dst_vol_left) ? -1 : 1; + } + + if (chan_sel & SET_CHAN_RIGHT) { + curr_vol_right = inb(portbase + 0); + + right_change = (curr_vol_right > dst_vol_right) ? -1 : 1; + } + + do { + if (left_change) { + if (curr_vol_left != dst_vol_left) { + curr_vol_left += left_change; + outb(curr_vol_left, portbase + 1); + } else + left_change = 0; + } + if (right_change) { + if (curr_vol_right != dst_vol_right) { + curr_vol_right += right_change; + + /* during volume change, the right channel is crackling + * somewhat more than the left channel, unfortunately. + * This seems to be a hardware issue. */ + outb(curr_vol_right, portbase + 0); + } else + right_change = 0; + } + if (delay) + mdelay(delay); + } while ((left_change) || (right_change)); +} + +/* + * general mixer element + */ +struct azf3328_mixer_reg { + unsigned reg; + unsigned int lchan_shift, rchan_shift; + unsigned int mask; + unsigned int invert: 1; + unsigned int stereo: 1; + unsigned int enum_c: 4; +}; + +#define COMPOSE_MIXER_REG(reg,lchan_shift,rchan_shift,mask,invert,stereo,enum_c) \ + ((reg) | (lchan_shift << 8) | (rchan_shift << 12) | \ + (mask << 16) | \ + (invert << 24) | \ + (stereo << 25) | \ + (enum_c << 26)) + +static void snd_azf3328_mixer_reg_decode(struct azf3328_mixer_reg *r, unsigned long val) +{ + r->reg = val & 0xff; + r->lchan_shift = (val >> 8) & 0x0f; + r->rchan_shift = (val >> 12) & 0x0f; + r->mask = (val >> 16) & 0xff; + r->invert = (val >> 24) & 1; + r->stereo = (val >> 25) & 1; + r->enum_c = (val >> 26) & 0x0f; +} + +/* + * mixer switches/volumes + */ + +#define AZF3328_MIXER_SWITCH(xname, reg, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0x1, invert, 0, 0), \ +} + +#define AZF3328_MIXER_VOL_STEREO(xname, reg, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, 8, 0, mask, invert, 1, 0), \ +} + +#define AZF3328_MIXER_VOL_MONO(xname, reg, mask, is_right_chan) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, is_right_chan ? 0 : 8, 0, mask, 1, 0, 0), \ +} + +#define AZF3328_MIXER_VOL_SPECIAL(xname, reg, mask, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer, \ + .get = snd_azf3328_get_mixer, .put = snd_azf3328_put_mixer, \ + .private_value = COMPOSE_MIXER_REG(reg, shift, 0, mask, invert, 0, 0), \ +} + +#define AZF3328_MIXER_ENUM(xname, reg, enum_c, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_azf3328_info_mixer_enum, \ + .get = snd_azf3328_get_mixer_enum, .put = snd_azf3328_put_mixer_enum, \ + .private_value = COMPOSE_MIXER_REG(reg, shift, 0, 0, 0, 0, enum_c), \ +} + +static int +snd_azf3328_info_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct azf3328_mixer_reg reg; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + uinfo->type = reg.mask == 1 ? + SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = reg.stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = reg.mask; + return 0; +} + +static int +snd_azf3328_get_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + u16 oreg, val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + + oreg = snd_azf3328_mixer_inw(chip, reg.reg); + val = (oreg >> reg.lchan_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[0] = val; + if (reg.stereo) { + val = (oreg >> reg.rchan_shift) & reg.mask; + if (reg.invert) + val = reg.mask - val; + ucontrol->value.integer.value[1] = val; + } + dev_dbg(chip->card->dev, + "get: %02x is %04x -> vol %02lx|%02lx (shift %02d|%02d, mask %02x, inv. %d, stereo %d)\n", + reg.reg, oreg, + ucontrol->value.integer.value[0], ucontrol->value.integer.value[1], + reg.lchan_shift, reg.rchan_shift, reg.mask, reg.invert, reg.stereo); + return 0; +} + +static int +snd_azf3328_put_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + u16 oreg, nreg, val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + oreg = snd_azf3328_mixer_inw(chip, reg.reg); + val = ucontrol->value.integer.value[0] & reg.mask; + if (reg.invert) + val = reg.mask - val; + nreg = oreg & ~(reg.mask << reg.lchan_shift); + nreg |= (val << reg.lchan_shift); + if (reg.stereo) { + val = ucontrol->value.integer.value[1] & reg.mask; + if (reg.invert) + val = reg.mask - val; + nreg &= ~(reg.mask << reg.rchan_shift); + nreg |= (val << reg.rchan_shift); + } + if (reg.mask >= 0x07) /* it's a volume control, so better take care */ + snd_azf3328_mixer_write_volume_gradually( + chip, reg.reg, nreg >> 8, nreg & 0xff, + /* just set both channels, doesn't matter */ + SET_CHAN_LEFT|SET_CHAN_RIGHT, + 0); + else + snd_azf3328_mixer_outw(chip, reg.reg, nreg); + + dev_dbg(chip->card->dev, + "put: %02x to %02lx|%02lx, oreg %04x; shift %02d|%02d -> nreg %04x; after: %04x\n", + reg.reg, ucontrol->value.integer.value[0], ucontrol->value.integer.value[1], + oreg, reg.lchan_shift, reg.rchan_shift, + nreg, snd_azf3328_mixer_inw(chip, reg.reg)); + return (nreg != oreg); +} + +static int +snd_azf3328_info_mixer_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts1[] = { + "Mic1", "Mic2" + }; + static const char * const texts2[] = { + "Mix", "Mic" + }; + static const char * const texts3[] = { + "Mic", "CD", "Video", "Aux", + "Line", "Mix", "Mix Mono", "Phone" + }; + static const char * const texts4[] = { + "pre 3D", "post 3D" + }; + struct azf3328_mixer_reg reg; + const char * const *p = NULL; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + if (reg.reg == IDX_MIXER_ADVCTL2) { + switch(reg.lchan_shift) { + case 8: /* modem out sel */ + p = texts1; + break; + case 9: /* mono sel source */ + p = texts2; + break; + case 15: /* PCM Out Path */ + p = texts4; + break; + } + } else if (reg.reg == IDX_MIXER_REC_SELECT) + p = texts3; + + return snd_ctl_enum_info(uinfo, + (reg.reg == IDX_MIXER_REC_SELECT) ? 2 : 1, + reg.enum_c, p); +} + +static int +snd_azf3328_get_mixer_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + unsigned short val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + val = snd_azf3328_mixer_inw(chip, reg.reg); + if (reg.reg == IDX_MIXER_REC_SELECT) { + ucontrol->value.enumerated.item[0] = (val >> 8) & (reg.enum_c - 1); + ucontrol->value.enumerated.item[1] = (val >> 0) & (reg.enum_c - 1); + } else + ucontrol->value.enumerated.item[0] = (val >> reg.lchan_shift) & (reg.enum_c - 1); + + dev_dbg(chip->card->dev, + "get_enum: %02x is %04x -> %d|%d (shift %02d, enum_c %d)\n", + reg.reg, val, ucontrol->value.enumerated.item[0], ucontrol->value.enumerated.item[1], + reg.lchan_shift, reg.enum_c); + return 0; +} + +static int +snd_azf3328_put_mixer_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); + struct azf3328_mixer_reg reg; + u16 oreg, nreg, val; + + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); + oreg = snd_azf3328_mixer_inw(chip, reg.reg); + val = oreg; + if (reg.reg == IDX_MIXER_REC_SELECT) { + if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U || + ucontrol->value.enumerated.item[1] > reg.enum_c - 1U) + return -EINVAL; + val = (ucontrol->value.enumerated.item[0] << 8) | + (ucontrol->value.enumerated.item[1] << 0); + } else { + if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U) + return -EINVAL; + val &= ~((reg.enum_c - 1) << reg.lchan_shift); + val |= (ucontrol->value.enumerated.item[0] << reg.lchan_shift); + } + snd_azf3328_mixer_outw(chip, reg.reg, val); + nreg = val; + + dev_dbg(chip->card->dev, + "put_enum: %02x to %04x, oreg %04x\n", reg.reg, val, oreg); + return (nreg != oreg); +} + +static 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 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 int +snd_azf3328_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int +snd_azf3328_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +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); + /* fall-through */ + 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_azf3328_hw_params, + .hw_free = snd_azf3328_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_azf3328_hw_params, + .hw_free = snd_azf3328_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_azf3328_hw_params, + .hw_free = snd_azf3328_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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 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; + pci_set_master(pci); + synchronize_irq(chip->irq); + + 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); + + /* same pcm object for playback/capture */ + snd_pcm_suspend_all(chip->pcm[AZF_CODEC_PLAYBACK]); + snd_pcm_suspend_all(chip->pcm[AZF_CODEC_I2S_OUT]); + + 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..ba971042f --- /dev/null +++ b/sound/pci/bt87x.c @@ -0,0 +1,994 @@ +/* + * bt87x.c - Brooktree Bt878/Bt879 driver for ALSA + * + * Copyright (c) Clemens Ladisch + * + * based on btaudio.c by Gerd Knorr + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Clemens Ladisch "); +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 the non-whitelisted cards */ + +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 the non-whitelisted cards"); + + +/* 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 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, snd_dma_pci_data(chip->pci), + 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) +{ + u16 pci_status; + + pci_read_config_word(chip->pci, PCI_STATUS, &pci_status); + pci_status &= PCI_STATUS_PARITY | PCI_STATUS_SIG_TARGET_ABORT | + PCI_STATUS_REC_TARGET_ABORT | PCI_STATUS_REC_MASTER_ABORT | + PCI_STATUS_SIG_SYSTEM_ERROR | PCI_STATUS_DETECTED_PARITY; + pci_write_config_word(chip->pci, PCI_STATUS, pci_status); + 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); + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + 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); + snd_pcm_lib_free_pages(substream); + 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_bt87x_hw_params, + .hw_free = snd_bt87x_hw_free, + .prepare = snd_bt87x_prepare, + .trigger = snd_bt87x_trigger, + .pointer = snd_bt87x_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +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); + return snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 128 * 1024, + ALIGN(255 * 4092, 1024)); +} + +static int snd_bt87x_create(struct snd_card *card, + struct pci_dev *pci, + struct snd_bt87x **rchip) +{ + struct snd_bt87x *chip; + int err; + static 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; + pci_set_master(pci); + synchronize_irq(chip->irq); + + 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; +} blacklist[] = { + {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 blacklisted */ +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(blacklist); ++i) + if (blacklist[i].subvendor == pci->subsystem_vendor && + blacklist[i].subdevice == pci->subsystem_device) { + dev_dbg(&pci->dev, + "card %#04x-%#04x:%#04x has no audio\n", + pci->device, pci->subsystem_vendor, pci->subsystem_device); + return -EBUSY; + } + + dev_info(&pci->dev, "unknown card %#04x-%#04x:%#04x\n", + pci->device, pci->subsystem_vendor, pci->subsystem_device); + dev_info(&pci->dev, "please mail id, board name, and, " + "if it works, the correct digital_rate option to " + "\n"); + return SND_BT87X_BOARD_UNKNOWN; +} + +static int snd_bt87x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct snd_bt87x *chip; + int err; + enum snd_bt87x_boardid boardid; + + if (!pci_id->driver_data) { + err = snd_bt87x_detect_card(pci); + if (err < 0) + return -ENODEV; + boardid = err; + } else + boardid = pci_id->driver_data; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + ++dev; + return -ENOENT; + } + + err = snd_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..c1455fc55 --- /dev/null +++ b/sound/pci/ca0106/Makefile @@ -0,0 +1,4 @@ +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..9847b669c --- /dev/null +++ b/sound/pci/ca0106/ca0106.h @@ -0,0 +1,742 @@ +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.22 + * + * FEATURES currently supported: + * See ca0106_main.c for features. + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Separated ca0106.c into separate functional .c files. + * 0.0.16 + * Implement 192000 sample rate. + * 0.0.17 + * Add support for SB0410 and SB0413. + * 0.0.18 + * Modified Copyright message. + * 0.0.19 + * Added I2C and SPI registers. Filled in interrupt enable. + * 0.0.20 + * Added GPIO info for SB Live 24bit. + * 0.0.21 + * Implement support for Line-in capture on SB Live 24bit. + * 0.0.22 + * Add support for mute control on SB Live 24bit (cards w/ SPI DAC) + * + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/************************************************************************************************/ +/* PCI function 0 registers, address = + PCIBASE0 */ +/************************************************************************************************/ + +#define PTR 0x00 /* Indexed register set pointer register */ + /* NOTE: The CHANNELNUM and ADDRESS words can */ + /* be modified independently of each other. */ + /* 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< Center Speaker, 2 -> Sub Woofer, 3 -> Ground, 4 -> Ground + * For Digital: 1 -> Front SPDIF, 2 -> Rear SPDIF, 3 -> Center/Subwoofer SPDIF, 4 -> Ground. + * Standard 4 pole Video A/V cable with RCA outputs: 1 -> White, 2 -> Yellow, 3 -> Shield on all three, 4 -> Red. + * So, from this you can see that you cannot use a Standard 4 pole Video A/V cable with the SB Audigy LS card. + */ +/* The Front SPDIF PCM gets mixed with samples from the AC97 codec, so can only work for Stereo PCM and not AC3/DTS + * The Rear SPDIF can be used for Stereo PCM and also AC3/DTS + * The Center/LFE SPDIF cannot be used for AC3/DTS, but can be used for Stereo PCM. + * Summary: For ALSA we use the Rear channel for SPDIF Digital AC3/DTS output + */ +/* A standard 2 pole mono mini-jack to RCA plug can be used for SPDIF Stereo PCM output from the Front channel. + * A standard 3 pole stereo mini-jack to 2 RCA plugs can be used for SPDIF AC3/DTS and Stereo PCM output utilising the Rear channel and just one of the RCA plugs. + */ +#define SPCS0 0x41 /* SPDIF output Channel Status 0 register. For Rear. default=0x02108004, non-audio=0x02108006 */ +#define SPCS1 0x42 /* SPDIF output Channel Status 1 register. For Front */ +#define SPCS2 0x43 /* SPDIF output Channel Status 2 register. For Center/LFE */ +#define SPCS3 0x44 /* SPDIF output Channel Status 3 register. Unknown */ + /* When Channel set to 0: */ +#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */ +#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */ +#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */ +#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */ +#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */ +#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */ +#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */ +#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */ +#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */ +#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */ +#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */ +#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */ +#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */ +#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */ +#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */ +#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */ +#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */ +#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */ +#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */ +#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */ +#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */ +#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */ +#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */ + + /* When Channel set to 1: */ +#define SPCS_WORD_LENGTH_MASK 0x0000000f /* Word Length Mask */ +#define SPCS_WORD_LENGTH_16 0x00000008 /* Word Length 16 bit */ +#define SPCS_WORD_LENGTH_17 0x00000006 /* Word Length 17 bit */ +#define SPCS_WORD_LENGTH_18 0x00000004 /* Word Length 18 bit */ +#define SPCS_WORD_LENGTH_19 0x00000002 /* Word Length 19 bit */ +#define SPCS_WORD_LENGTH_20A 0x0000000a /* Word Length 20 bit */ +#define SPCS_WORD_LENGTH_20 0x00000009 /* Word Length 20 bit (both 0xa and 0x9 are 20 bit) */ +#define SPCS_WORD_LENGTH_21 0x00000007 /* Word Length 21 bit */ +#define SPCS_WORD_LENGTH_22 0x00000005 /* Word Length 22 bit */ +#define SPCS_WORD_LENGTH_23 0x00000003 /* Word Length 23 bit */ +#define SPCS_WORD_LENGTH_24 0x0000000b /* Word Length 24 bit */ +#define SPCS_ORIGINAL_SAMPLE_RATE_MASK 0x000000f0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_NONE 0x00000000 /* Original Sample rate not indicated */ +#define SPCS_ORIGINAL_SAMPLE_RATE_16000 0x00000010 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_RES1 0x00000020 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_32000 0x00000030 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_12000 0x00000040 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_11025 0x00000050 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_8000 0x00000060 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_RES2 0x00000070 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_192000 0x00000080 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_24000 0x00000090 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_96000 0x000000a0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_48000 0x000000b0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_176400 0x000000c0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_22050 0x000000d0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_88200 0x000000e0 /* Original Sample rate */ +#define SPCS_ORIGINAL_SAMPLE_RATE_44100 0x000000f0 /* Original Sample rate */ + +#define SPDIF_SELECT1 0x45 /* Enables SPDIF or Analogue outputs 0-SPDIF, 0xf00-Analogue */ + /* 0x100 - Front, 0x800 - Rear, 0x200 - Center/LFE. + * But as the jack is shared, use 0xf00. + * The Windows2000 driver uses 0x0000000f for both digital and analog. + * 0xf00 introduces interesting noises onto the Center/LFE. + * If you turn the volume up, you hear computer noise, + * e.g. mouse moving, changing between app windows etc. + * So, I am going to set this to 0x0000000f all the time now, + * same as the windows driver does. + * Use register SPDIF_SELECT2(0x72) to switch between SPDIF and Analog. + */ + /* When Channel = 0: + * Wide SPDIF format [3:0] (one bit for each channel) (0=20bit, 1=24bit) + * Tristate SPDIF Output [11:8] (one bit for each channel) (0=Not tristate, 1=Tristate) + * SPDIF Bypass enable [19:16] (one bit for each channel) (0=Not bypass, 1=Bypass) + */ + /* When Channel = 1: + * SPDIF 0 User data [7:0] + * SPDIF 1 User data [15:8] + * SPDIF 0 User data [23:16] + * SPDIF 0 User data [31:24] + * User data can be sent by using the SPDIF output frame pending and SPDIF output user bit interrupts. + */ +#define WATERMARK 0x46 /* Test bit to indicate cache usage level */ +#define SPDIF_INPUT_STATUS 0x49 /* SPDIF Input status register. Bits the same as SPCS. + * When Channel = 0: Bits the same as SPCS channel 0. + * When Channel = 1: Bits the same as SPCS channel 1. + * When Channel = 2: + * SPDIF Input User data [16:0] + * SPDIF Input Frame count [21:16] + */ +#define CAPTURE_CACHE_DATA 0x50 /* 0x50-0x5f Recorded samples. */ +#define CAPTURE_SOURCE 0x60 /* Capture Source 0 = MIC */ +#define CAPTURE_SOURCE_CHANNEL0 0xf0000000 /* Mask for selecting the Capture sources */ +#define CAPTURE_SOURCE_CHANNEL1 0x0f000000 /* 0 - SPDIF mixer output. */ +#define CAPTURE_SOURCE_CHANNEL2 0x00f00000 /* 1 - What you hear or . 2 - ?? */ +#define CAPTURE_SOURCE_CHANNEL3 0x000f0000 /* 3 - Mic in, Line in, TAD in, Aux in. */ +#define CAPTURE_SOURCE_RECORD_MAP 0x0000ffff /* Default 0x00e4 */ + /* Record Map [7:0] (2 bits per channel) 0=mapped to channel 0, 1=mapped to channel 1, 2=mapped to channel2, 3=mapped to channel3 + * Record source select for channel 0 [18:16] + * Record source select for channel 1 [22:20] + * Record source select for channel 2 [26:24] + * Record source select for channel 3 [30:28] + * 0 - SPDIF mixer output. + * 1 - i2s mixer output. + * 2 - SPDIF input. + * 3 - i2s input. + * 4 - AC97 capture. + * 5 - SRC output. + */ +#define CAPTURE_VOLUME1 0x61 /* Capture volume per channel 0-3 */ +#define CAPTURE_VOLUME2 0x62 /* Capture volume per channel 4-7 */ + +#define PLAYBACK_ROUTING1 0x63 /* Playback routing of channels 0-7. Effects AC3 output. Default 0x32765410 */ +#define ROUTING1_REAR 0x77000000 /* Channel_id 0 sends to 10, Channel_id 1 sends to 32 */ +#define ROUTING1_NULL 0x00770000 /* Channel_id 2 sends to 54, Channel_id 3 sends to 76 */ +#define ROUTING1_CENTER_LFE 0x00007700 /* 0x32765410 means, send Channel_id 0 to FRONT, Channel_id 1 to REAR */ +#define ROUTING1_FRONT 0x00000077 /* Channel_id 2 to CENTER_LFE, Channel_id 3 to NULL. */ + /* Channel_id's handle stereo channels. Channel X is a single mono channel */ + /* Host is input from the PCI bus. */ + /* Host channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7. + * Host channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7. + * Host channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7. + * Host channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7. + * Host channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7. + * Host channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7. + * Host channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7. + * Host channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7. + */ + +#define PLAYBACK_ROUTING2 0x64 /* Playback Routing . Feeding Capture channels back into Playback. Effects AC3 output. Default 0x76767676 */ + /* SRC is input from the capture inputs. */ + /* SRC channel 0 [2:0] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 1 [6:4] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 2 [10:8] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 3 [14:12] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 4 [18:16] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 5 [22:20] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 6 [26:24] -> SPDIF Mixer/Router channel 0-7. + * SRC channel 7 [30:28] -> SPDIF Mixer/Router channel 0-7. + */ + +#define PLAYBACK_MUTE 0x65 /* Unknown. While playing 0x0, while silent 0x00fc0000 */ + /* SPDIF Mixer input control: + * Invert SRC to SPDIF Mixer [7-0] (One bit per channel) + * Invert Host to SPDIF Mixer [15:8] (One bit per channel) + * SRC to SPDIF Mixer disable [23:16] (One bit per channel) + * Host to SPDIF Mixer disable [31:24] (One bit per channel) + */ +#define PLAYBACK_VOLUME1 0x66 /* Playback SPDIF volume per channel. Set to the same PLAYBACK_VOLUME(0x6a) */ + /* PLAYBACK_VOLUME1 must be set to 30303030 for SPDIF AC3 Playback */ + /* SPDIF mixer input volume. 0=12dB, 0x30=0dB, 0xFE=-51.5dB, 0xff=Mute */ + /* One register for each of the 4 stereo streams. */ + /* SRC Right volume [7:0] + * SRC Left volume [15:8] + * Host Right volume [23:16] + * Host Left volume [31:24] + */ +#define CAPTURE_ROUTING1 0x67 /* Capture Routing. Default 0x32765410 */ + /* Similar to register 0x63, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define CAPTURE_ROUTING2 0x68 /* Unknown Routing. Default 0x76767676 */ + /* Similar to register 0x64, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define CAPTURE_MUTE 0x69 /* Unknown. While capturing 0x0, while silent 0x00fc0000 */ + /* Similar to register 0x65, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define PLAYBACK_VOLUME2 0x6a /* Playback Analog volume per channel. Does not effect AC3 output */ + /* Similar to register 0x66, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */ +#define UNKNOWN6b 0x6b /* Unknown. Readonly. Default 00400000 00400000 00400000 00400000 */ +#define MIDI_UART_A_DATA 0x6c /* Midi Uart A Data */ +#define MIDI_UART_A_CMD 0x6d /* Midi Uart A Command/Status */ +#define MIDI_UART_B_DATA 0x6e /* Midi Uart B Data (currently unused) */ +#define MIDI_UART_B_CMD 0x6f /* Midi Uart B Command/Status (currently unused) */ + +/* unique channel identifier for midi->channel */ + +#define CA0106_MIDI_CHAN_A 0x1 +#define CA0106_MIDI_CHAN_B 0x2 + +/* from mpu401 */ + +#define CA0106_MIDI_INPUT_AVAIL 0x80 +#define CA0106_MIDI_OUTPUT_READY 0x40 +#define CA0106_MPU401_RESET 0xff +#define CA0106_MPU401_ENTER_UART 0x3f +#define CA0106_MPU401_ACK 0xfe + +#define SAMPLE_RATE_TRACKER_STATUS 0x70 /* Readonly. Default 00108000 00108000 00500000 00500000 */ + /* Estimated sample rate [19:0] Relative to 48kHz. 0x8000 = 1.0 + * Rate Locked [20] + * SPDIF Locked [21] For SPDIF channel only. + * Valid Audio [22] For SPDIF channel only. + */ +#define CAPTURE_CONTROL 0x71 /* Some sort of routing. default = 40c81000 30303030 30300000 00700000 */ + /* Channel_id 0: 0x40c81000 must be changed to 0x40c80000 for SPDIF AC3 input or output. */ + /* Channel_id 1: 0xffffffff(mute) 0x30303030(max) controls CAPTURE feedback into PLAYBACK. */ + /* Sample rate output control register Channel=0 + * Sample output rate [1:0] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * Sample input rate [3:2] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz) + * SRC input source select [4] 0=Audio from digital mixer, 1=Audio from analog source. + * Record rate [9:8] (0=48kHz, 1=Not available, 2=96kHz, 3=192Khz) + * Record mixer output enable [12:10] + * I2S input rate master mode [15:14] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * I2S output rate [17:16] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * I2S output source select [18] (0=Audio from host, 1=Audio from SRC) + * Record mixer I2S enable [20:19] (enable/disable i2sin1 and i2sin0) + * I2S output master clock select [21] (0=256*I2S output rate, 1=512*I2S output rate.) + * I2S input master clock select [22] (0=256*I2S input rate, 1=512*I2S input rate.) + * I2S input mode [23] (0=Slave, 1=Master) + * SPDIF output rate [25:24] (0=48kHz, 1=44.1kHz, 2=96kHz, 3=192Khz) + * SPDIF output source select [26] (0=host, 1=SRC) + * Not used [27] + * Record Source 0 input [29:28] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM) + * Record Source 1 input [31:30] (0=SPDIF in, 1=I2S in, 2=AC97 Mic, 3=AC97 PCM) + */ + /* Sample rate output control register Channel=1 + * I2S Input 0 volume Right [7:0] + * I2S Input 0 volume Left [15:8] + * I2S Input 1 volume Right [23:16] + * I2S Input 1 volume Left [31:24] + */ + /* Sample rate output control register Channel=2 + * SPDIF Input volume Right [23:16] + * SPDIF Input volume Left [31:24] + */ + /* Sample rate output control register Channel=3 + * No used + */ +#define SPDIF_SELECT2 0x72 /* Some sort of routing. Channel_id 0 only. default = 0x0f0f003f. Analog 0x000b0000, Digital 0x0b000000 */ +#define ROUTING2_FRONT_MASK 0x00010000 /* Enable for Front speakers. */ +#define ROUTING2_CENTER_LFE_MASK 0x00020000 /* Enable for Center/LFE speakers. */ +#define ROUTING2_REAR_MASK 0x00080000 /* Enable for Rear speakers. */ + /* Audio output control + * AC97 output enable [5:0] + * I2S output enable [19:16] + * SPDIF output enable [27:24] + */ +#define UNKNOWN73 0x73 /* Unknown. Readonly. Default 0x0 */ +#define CHIP_VERSION 0x74 /* P17 Chip version. Channel_id 0 only. Default 00000071 */ +#define EXTENDED_INT_MASK 0x75 /* Used by both playback and capture interrupt handler */ + /* Sets which Interrupts are enabled. */ + /* 0x00000001 = Half period. Playback. + * 0x00000010 = Full period. Playback. + * 0x00000100 = Half buffer. Playback. + * 0x00001000 = Full buffer. Playback. + * 0x00010000 = Half buffer. Capture. + * 0x00100000 = Full buffer. Capture. + * Capture can only do 2 periods. + * 0x01000000 = End audio. Playback. + * 0x40000000 = Half buffer Playback,Caputre xrun. + * 0x80000000 = Full buffer Playback,Caputre xrun. + */ +#define EXTENDED_INT 0x76 /* Used by both playback and capture interrupt handler */ + /* Shows which interrupts are active at the moment. */ + /* Same bit layout as EXTENDED_INT_MASK */ +#define COUNTER77 0x77 /* Counter range 0 to 0x3fffff, 192000 counts per second. */ +#define COUNTER78 0x78 /* Counter range 0 to 0x3fffff, 44100 counts per second. */ +#define EXTENDED_INT_TIMER 0x79 /* Channel_id 0 only. Used by both playback and capture interrupt handler */ + /* Causes interrupts based on timer intervals. */ +#define SPI 0x7a /* SPI: Serial Interface Register */ +#define I2C_A 0x7b /* I2C Address. 32 bit */ +#define I2C_D0 0x7c /* I2C Data Port 0. 32 bit */ +#define I2C_D1 0x7d /* I2C Data Port 1. 32 bit */ +//I2C values +#define I2C_A_ADC_ADD_MASK 0x000000fe //The address is a 7 bit address +#define I2C_A_ADC_RW_MASK 0x00000001 //bit mask for R/W +#define I2C_A_ADC_TRANS_MASK 0x00000010 //Bit mask for I2c address DAC value +#define I2C_A_ADC_ABORT_MASK 0x00000020 //Bit mask for I2C transaction abort flag +#define I2C_A_ADC_LAST_MASK 0x00000040 //Bit mask for Last word transaction +#define I2C_A_ADC_BYTE_MASK 0x00000080 //Bit mask for Byte Mode + +#define I2C_A_ADC_ADD 0x00000034 //This is the Device address for ADC +#define I2C_A_ADC_READ 0x00000001 //To perform a read operation +#define I2C_A_ADC_START 0x00000100 //Start I2C transaction +#define I2C_A_ADC_ABORT 0x00000200 //I2C transaction abort +#define I2C_A_ADC_LAST 0x00000400 //I2C last transaction +#define I2C_A_ADC_BYTE 0x00000800 //I2C one byte mode + +#define I2C_D_ADC_REG_MASK 0xfe000000 //ADC address register +#define I2C_D_ADC_DAT_MASK 0x01ff0000 //ADC data register + +#define ADC_TIMEOUT 0x00000007 //ADC Timeout Clock Disable +#define ADC_IFC_CTRL 0x0000000b //ADC Interface Control +#define ADC_MASTER 0x0000000c //ADC Master Mode Control +#define ADC_POWER 0x0000000d //ADC PowerDown Control +#define ADC_ATTEN_ADCL 0x0000000e //ADC Attenuation ADCL +#define ADC_ATTEN_ADCR 0x0000000f //ADC Attenuation ADCR +#define ADC_ALC_CTRL1 0x00000010 //ADC ALC Control 1 +#define ADC_ALC_CTRL2 0x00000011 //ADC ALC Control 2 +#define ADC_ALC_CTRL3 0x00000012 //ADC ALC Control 3 +#define ADC_NOISE_CTRL 0x00000013 //ADC Noise Gate Control +#define ADC_LIMIT_CTRL 0x00000014 //ADC Limiter Control +#define ADC_MUX 0x00000015 //ADC Mux offset + +#if 0 +/* FIXME: Not tested yet. */ +#define ADC_GAIN_MASK 0x000000ff //Mask for ADC Gain +#define ADC_ZERODB 0x000000cf //Value to set ADC to 0dB +#define ADC_MUTE_MASK 0x000000c0 //Mask for ADC mute +#define ADC_MUTE 0x000000c0 //Value to mute ADC +#define ADC_OSR 0x00000008 //Mask for ADC oversample rate select +#define ADC_TIMEOUT_DISABLE 0x00000008 //Value and mask to disable Timeout clock +#define ADC_HPF_DISABLE 0x00000100 //Value and mask to disable High pass filter +#define ADC_TRANWIN_MASK 0x00000070 //Mask for Length of Transient Window +#endif + +#define ADC_MUX_MASK 0x0000000f //Mask for ADC Mux +#define ADC_MUX_PHONE 0x00000001 //Value to select TAD at ADC Mux (Not used) +#define ADC_MUX_MIC 0x00000002 //Value to select Mic at ADC Mux +#define ADC_MUX_LINEIN 0x00000004 //Value to select LineIn at ADC Mux +#define ADC_MUX_AUX 0x00000008 //Value to select Aux at ADC Mux + +#define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */ +#define PCM_FRONT_CHANNEL 0 +#define PCM_REAR_CHANNEL 1 +#define PCM_CENTER_LFE_CHANNEL 2 +#define PCM_UNKNOWN_CHANNEL 3 +#define CONTROL_FRONT_CHANNEL 0 +#define CONTROL_REAR_CHANNEL 3 +#define CONTROL_CENTER_LFE_CHANNEL 1 +#define CONTROL_UNKNOWN_CHANNEL 2 + + +/* Based on WM8768 Datasheet Rev 4.2 page 32 */ +#define SPI_REG_MASK 0x1ff /* 16-bit SPI writes have a 7-bit address */ +#define SPI_REG_SHIFT 9 /* followed by 9 bits of data */ + +#define SPI_LDA1_REG 0 /* digital attenuation */ +#define SPI_RDA1_REG 1 +#define SPI_LDA2_REG 4 +#define SPI_RDA2_REG 5 +#define SPI_LDA3_REG 6 +#define SPI_RDA3_REG 7 +#define SPI_LDA4_REG 13 +#define SPI_RDA4_REG 14 +#define SPI_MASTDA_REG 8 + +#define SPI_DA_BIT_UPDATE (1<<8) /* update attenuation values */ +#define SPI_DA_BIT_0dB 0xff /* 0 dB */ +#define SPI_DA_BIT_infdB 0x00 /* inf dB attenuation (mute) */ + +#define SPI_PL_REG 2 +#define SPI_PL_BIT_L_M (0<<5) /* left channel = mute */ +#define SPI_PL_BIT_L_L (1<<5) /* left channel = left */ +#define SPI_PL_BIT_L_R (2<<5) /* left channel = right */ +#define SPI_PL_BIT_L_C (3<<5) /* left channel = (L+R)/2 */ +#define SPI_PL_BIT_R_M (0<<7) /* right channel = mute */ +#define SPI_PL_BIT_R_L (1<<7) /* right channel = left */ +#define SPI_PL_BIT_R_R (2<<7) /* right channel = right */ +#define SPI_PL_BIT_R_C (3<<7) /* right channel = (L+R)/2 */ +#define SPI_IZD_REG 2 +#define SPI_IZD_BIT (0<<4) /* infinite zero detect */ + +#define SPI_FMT_REG 3 +#define SPI_FMT_BIT_RJ (0<<0) /* right justified mode */ +#define SPI_FMT_BIT_LJ (1<<0) /* left justified mode */ +#define SPI_FMT_BIT_I2S (2<<0) /* I2S mode */ +#define SPI_FMT_BIT_DSP (3<<0) /* DSP Modes A or B */ +#define SPI_LRP_REG 3 +#define SPI_LRP_BIT (1<<2) /* invert LRCLK polarity */ +#define SPI_BCP_REG 3 +#define SPI_BCP_BIT (1<<3) /* invert BCLK polarity */ +#define SPI_IWL_REG 3 +#define SPI_IWL_BIT_16 (0<<4) /* 16-bit world length */ +#define SPI_IWL_BIT_20 (1<<4) /* 20-bit world length */ +#define SPI_IWL_BIT_24 (2<<4) /* 24-bit world length */ +#define SPI_IWL_BIT_32 (3<<4) /* 32-bit world length */ + +#define SPI_MS_REG 10 +#define SPI_MS_BIT (1<<5) /* master mode */ +#define SPI_RATE_REG 10 /* only applies in master mode */ +#define SPI_RATE_BIT_128 (0<<6) /* MCLK = LRCLK * 128 */ +#define SPI_RATE_BIT_192 (1<<6) +#define SPI_RATE_BIT_256 (2<<6) +#define SPI_RATE_BIT_384 (3<<6) +#define SPI_RATE_BIT_512 (4<<6) +#define SPI_RATE_BIT_768 (5<<6) + +/* They really do label the bit for the 4th channel "4" and not "3" */ +#define SPI_DMUTE0_REG 9 +#define SPI_DMUTE1_REG 9 +#define SPI_DMUTE2_REG 9 +#define SPI_DMUTE4_REG 15 +#define SPI_DMUTE0_BIT (1<<3) +#define SPI_DMUTE1_BIT (1<<4) +#define SPI_DMUTE2_BIT (1<<5) +#define SPI_DMUTE4_BIT (1<<2) + +#define SPI_PHASE0_REG 3 +#define SPI_PHASE1_REG 3 +#define SPI_PHASE2_REG 3 +#define SPI_PHASE4_REG 15 +#define SPI_PHASE0_BIT (1<<6) +#define SPI_PHASE1_BIT (1<<7) +#define SPI_PHASE2_BIT (1<<8) +#define SPI_PHASE4_BIT (1<<3) + +#define SPI_PDWN_REG 2 /* power down all DACs */ +#define SPI_PDWN_BIT (1<<2) +#define SPI_DACD0_REG 10 /* power down individual DACs */ +#define SPI_DACD1_REG 10 +#define SPI_DACD2_REG 10 +#define SPI_DACD4_REG 15 +#define SPI_DACD0_BIT (1<<1) +#define SPI_DACD1_BIT (1<<2) +#define SPI_DACD2_BIT (1<<3) +#define SPI_DACD4_BIT (1<<0) /* datasheet error says it's 1 */ + +#define SPI_PWRDNALL_REG 10 /* power down everything */ +#define SPI_PWRDNALL_BIT (1<<4) + +#include "ca_midi.h" + +struct snd_ca0106; + +struct snd_ca0106_channel { + struct snd_ca0106 *emu; + int number; + int use; + void (*interrupt)(struct snd_ca0106 *emu, struct snd_ca0106_channel *channel); + struct snd_ca0106_pcm *epcm; +}; + +struct snd_ca0106_pcm { + struct snd_ca0106 *emu; + struct snd_pcm_substream *substream; + int channel_id; + unsigned short running; +}; + +struct snd_ca0106_details { + u32 serial; + char * name; + int ac97; /* ac97 = 0 -> Select MIC, Line in, TAD in, AUX in. + ac97 = 1 -> Default to AC97 in. */ + int gpio_type; /* gpio_type = 1 -> shared mic-in/line-in + gpio_type = 2 -> shared side-out/line-in. */ + int i2c_adc; /* with i2c_adc=1, the driver adds some capture volume + controls, phone, mic, line-in and aux. */ + u16 spi_dac; /* spi_dac = 0 -> no spi interface for DACs + spi_dac = 0x + -> specifies DAC id for each channel pair. */ +}; + +// definition of the chip-specific record +struct snd_ca0106 { + struct snd_card *card; + 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..675b812e9 --- /dev/null +++ b/sound/pci/ca0106/ca0106_main.c @@ -0,0 +1,1971 @@ +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.25 + * + * FEATURES currently supported: + * Front, Rear and Center/LFE. + * Surround40 and Surround51. + * Capture from MIC an LINE IN input. + * SPDIF digital playback of PCM stereo and AC3/DTS works. + * (One can use a standard mono mini-jack to one RCA plugs cable. + * or one can use a standard stereo mini-jack to two RCA plugs cable. + * Plug one of the RCA plugs into the Coax input of the external decoder/receiver.) + * ( In theory one could output 3 different AC3 streams at once, to 3 different SPDIF outputs. ) + * Notes on how to capture sound: + * The AC97 is used in the PLAYBACK direction. + * The output from the AC97 chip, instead of reaching the speakers, is fed into the Philips 1361T ADC. + * So, to record from the MIC, set the MIC Playback volume to max, + * unmute the MIC and turn up the MASTER Playback volume. + * So, to prevent feedback when capturing, minimise the "Capture feedback into Playback" volume. + * + * The only playback controls that currently do anything are: - + * Analog Front + * Analog Rear + * Analog Center/LFE + * SPDIF Front + * SPDIF Rear + * SPDIF Center/LFE + * + * For capture from Mic in or Line in. + * Digital/Analog ( switch must be in Analog mode for CAPTURE. ) + * + * CAPTURE feedback into PLAYBACK + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Minor updates. + * 0.0.16 + * Implement 192000 sample rate. + * 0.0.17 + * Add support for SB0410 and SB0413. + * 0.0.18 + * Modified Copyright message. + * 0.0.19 + * Finally fix support for SB Live 24 bit. SB0410 and SB0413. + * The output codec needs resetting, otherwise all output is muted. + * 0.0.20 + * Merge "pci_disable_device(pci);" fixes. + * 0.0.21 + * Add 4 capture channels. (SPDIF only comes in on channel 0. ) + * Add SPDIF capture using optional digital I/O module for SB Live 24bit. (Analog capture does not yet work.) + * 0.0.22 + * Add support for MSI K8N Diamond Motherboard with onboard SB Live 24bit without AC97. From kiksen, bug #901 + * 0.0.23 + * Implement support for Line-in capture on SB Live 24bit. + * 0.0.24 + * Add support for mute control on SB Live 24bit (cards w/ SPI DAC) + * 0.0.25 + * Powerdown SPI DAC channels when not in use + * + * BUGS: + * Some stability problems when unloading the snd-ca0106 kernel module. + * -- + * + * TODO: + * 4 Capture channels, only one implemented so far. + * Other capture rates apart from 48khz not implemented. + * MIDI + * -- + * GENERAL INFO: + * Model: SB0310 + * P17 Chip: CA0106-DAT + * AC97 Codec: STAC 9721 + * ADC: Philips 1361T (Stereo 24bit) + * DAC: WM8746EDS (6-channel, 24bit, 192Khz) + * + * GENERAL INFO: + * Model: SB0410 + * P17 Chip: CA0106-DAT + * AC97 Codec: None + * ADC: WM8775EDS (4 Channel) + * DAC: CS4382 (114 dB, 24-Bit, 192 kHz, 8-Channel D/A Converter with DSD Support) + * SPDIF Out control switches between Mic in and SPDIF out. + * No sound out or mic input working yet. + * + * GENERAL INFO: + * Model: SB0413 + * P17 Chip: CA0106-DAT + * AC97 Codec: None. + * ADC: Unknown + * DAC: Unknown + * Trying to handle it like the SB0410. + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("James Courtier-Dutton "); +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 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, + 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); +} + +/* hw_params callback */ +static int snd_ca0106_pcm_hw_params_playback(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +/* hw_free callback */ +static int snd_ca0106_pcm_hw_free_playback(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* hw_params callback */ +static int snd_ca0106_pcm_hw_params_capture(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +/* hw_free callback */ +static int snd_ca0106_pcm_hw_free_capture(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* 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<running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_ca0106_ptr_write(emu, BASIC_INTERRUPT, 0, snd_ca0106_ptr_read(emu, BASIC_INTERRUPT, 0) & ~(0x100<running = 0; + break; + default: + result = -EINVAL; + break; + } + return result; +} + +/* pointer_playback callback */ +static snd_pcm_uframes_t +snd_ca0106_pcm_pointer_playback(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + unsigned int ptr, prev_ptr; + int channel = epcm->channel_id; + int timeout = 10; + + if (!epcm->running) + return 0; + + prev_ptr = -1; + do { + ptr = snd_ca0106_ptr_read(emu, PLAYBACK_LIST_PTR, channel); + ptr = (ptr >> 3) * runtime->period_size; + ptr += bytes_to_frames(runtime, + snd_ca0106_ptr_read(emu, PLAYBACK_POINTER, channel)); + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + if (prev_ptr == ptr) + return ptr; + prev_ptr = ptr; + } while (--timeout); + dev_warn(emu->card->dev, "ca0106: unstable DMA pointer!\n"); + return 0; +} + +/* pointer_capture callback */ +static snd_pcm_uframes_t +snd_ca0106_pcm_pointer_capture(struct snd_pcm_substream *substream) +{ + struct snd_ca0106 *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ca0106_pcm *epcm = runtime->private_data; + snd_pcm_uframes_t ptr, ptr1, ptr2 = 0; + int channel = epcm->channel_id; + + if (!epcm->running) + return 0; + + ptr1 = snd_ca0106_ptr_read(emu, CAPTURE_POINTER, channel); + ptr2 = bytes_to_frames(runtime, ptr1); + ptr=ptr2; + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + /* + dev_dbg(emu->card->dev, "ptr1 = 0x%lx, ptr2=0x%lx, ptr=0x%lx, " + "buffer_size = 0x%x, period_size = 0x%x, bits=%d, rate=%d\n", + ptr1, ptr2, ptr, (int)runtime->buffer_size, + (int)runtime->period_size, (int)runtime->frame_bits, + (int)runtime->rate); + */ + return ptr; +} + +/* operators */ +static const struct snd_pcm_ops snd_ca0106_playback_front_ops = { + .open = snd_ca0106_pcm_open_playback_front, + .close = snd_ca0106_pcm_close_playback, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_playback, + .hw_free = snd_ca0106_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_capture, + .hw_free = snd_ca0106_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_capture, + .hw_free = snd_ca0106_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_capture, + .hw_free = snd_ca0106_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_capture, + .hw_free = snd_ca0106_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_playback, + .hw_free = snd_ca0106_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_playback, + .hw_free = snd_ca0106_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ca0106_pcm_hw_params_playback, + .hw_free = snd_ca0106_pcm_hw_free_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 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) { + if ((err = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(emu->pci), + 64*1024, 64*1024)) < 0) /* FIXME: 32*1024 for sound buffer, between 32and64 for Periods table. */ + return err; + } + + for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + substream; + substream = substream->next) { + if ((err = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(emu->pci), + 64*1024, 64*1024)) < 0) + return err; + } + + 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 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 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; + struct snd_ca0106_details *c; + int err; + static 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; + + /* This stores the periods table. */ + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + 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; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < 4; i++) + snd_pcm_suspend_all(chip->pcm[i]); + 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..b4d341533 --- /dev/null +++ b/sound/pci/ca0106/ca0106_mixer.c @@ -0,0 +1,932 @@ +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.18 + * + * FEATURES currently supported: + * See ca0106_main.c for features. + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Separated ca0106.c into separate functional .c files. + * 0.0.16 + * Modified Copyright message. + * 0.0.17 + * Implement Mic and Line in Capture. + * 0.0.18 + * Add support for mute control on SB Live 24bit (cards w/ SPI DAC) + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ca0106.h" + +static void ca0106_spdif_enable(struct snd_ca0106 *emu) +{ + unsigned int val; + + if (emu->spdif_enable) { + /* Digital */ + snd_ca0106_ptr_write(emu, SPDIF_SELECT1, 0, 0xf); + snd_ca0106_ptr_write(emu, SPDIF_SELECT2, 0, 0x0b000000); + val = snd_ca0106_ptr_read(emu, CAPTURE_CONTROL, 0) & ~0x1000; + snd_ca0106_ptr_write(emu, CAPTURE_CONTROL, 0, val); + val = inl(emu->port + 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 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 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(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 char *slave_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 char *slave_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_slaves(struct snd_card *card, + struct snd_kcontrol *master, char **list) +{ + for (; *list; list++) { + struct snd_kcontrol *slave = ctl_find(card, *list); + if (slave) + snd_ctl_add_slave(master, slave); + } +} + +int snd_ca0106_mixer(struct snd_ca0106 *emu) +{ + int err; + struct snd_card *card = emu->card; + char **c; + struct snd_kcontrol *vmaster; + static char *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 char *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_slaves(card, vmaster, slave_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_slaves(card, vmaster, slave_sws); + } + + strcpy(card->mixername, "CA0106"); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +struct ca0106_vol_tbl { + unsigned int channel_id; + unsigned int reg; +}; + +static 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..a2c85cc37 --- /dev/null +++ b/sound/pci/ca0106/ca0106_proc.c @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2004 James Courtier-Dutton + * Driver CA0106 chips. e.g. Sound Blaster Audigy LS and Live 24bit + * Version: 0.0.18 + * + * FEATURES currently supported: + * See ca0106_main.c for features. + * + * Changelog: + * Support interrupts per period. + * Removed noise from Center/LFE channel when in Analog mode. + * Rename and remove mixer controls. + * 0.0.6 + * Use separate card based DMA buffer for periods table list. + * 0.0.7 + * Change remove and rename ctrls into lists. + * 0.0.8 + * Try to fix capture sources. + * 0.0.9 + * Fix AC3 output. + * Enable S32_LE format support. + * 0.0.10 + * Enable playback 48000 and 96000 rates. (Rates other that these do not work, even with "plug:front".) + * 0.0.11 + * Add Model name recognition. + * 0.0.12 + * Correct interrupt timing. interrupt at end of period, instead of in the middle of a playback period. + * Remove redundent "voice" handling. + * 0.0.13 + * Single trigger call for multi channels. + * 0.0.14 + * Set limits based on what the sound card hardware can do. + * playback periods_min=2, periods_max=8 + * capture hw constraints require period_size = n * 64 bytes. + * playback hw constraints require period_size = n * 64 bytes. + * 0.0.15 + * Separate ca0106.c into separate functional .c files. + * 0.0.16 + * Modified Copyright message. + * 0.0.17 + * Add iec958 file in proc file system to show status of SPDIF in. + * 0.0.18 + * Implement support for Line-in capture on SB Live 24bit. + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ca0106.h" + + +struct snd_ca0106_category_str { + int val; + const char *name; +}; + +static 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", ®, &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", ®, &channel_id, &val) != 3) + continue; + if (reg < 0x80 && val <= 0xffffffff && channel_id <= 3) + snd_ca0106_ptr_write(emu, reg, channel_id, val); + } +} + +static void snd_ca0106_proc_i2c_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ca0106 *emu = entry->private_data; + char line[64]; + unsigned int reg, val; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%x %x", ®, &val) != 2) + continue; + if ((reg <= 0x7f) || (val <= 0x1ff)) { + snd_ca0106_i2c_write(emu, reg, val); + } + } +} + +int snd_ca0106_proc_init(struct snd_ca0106 *emu) +{ + struct snd_info_entry *entry; + + if(! snd_card_proc_new(emu->card, "iec958", &entry)) + snd_info_set_text_ops(entry, emu, snd_ca0106_proc_iec958); + if(! snd_card_proc_new(emu->card, "ca0106_reg32", &entry)) { + snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read32); + entry->c.text.write = snd_ca0106_proc_reg_write32; + entry->mode |= 0200; + } + if(! snd_card_proc_new(emu->card, "ca0106_reg16", &entry)) + snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read16); + if(! snd_card_proc_new(emu->card, "ca0106_reg8", &entry)) + snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read8); + if(! snd_card_proc_new(emu->card, "ca0106_regs1", &entry)) { + snd_info_set_text_ops(entry, emu, snd_ca0106_proc_reg_read1); + entry->c.text.write = snd_ca0106_proc_reg_write; + entry->mode |= 0200; + } + if(! snd_card_proc_new(emu->card, "ca0106_i2c", &entry)) { + entry->c.text.write = snd_ca0106_proc_i2c_write; + entry->private_data = emu; + entry->mode |= 0200; + } + if(! snd_card_proc_new(emu->card, "ca0106_regs2", &entry)) + snd_info_set_text_ops(entry, 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..4d4d38520 --- /dev/null +++ b/sound/pci/ca0106/ca_midi.c @@ -0,0 +1,316 @@ +/* + * Copyright 10/16/2005 Tilman Kranz + * Creative Audio MIDI, for the CA0106 Driver + * Version: 0.0.1 + * + * Changelog: + * Implementation is based on mpu401 and emu10k1x and + * tested with ca0106. + * mpu401: Copyright (c) by Jaroslav Kysela + * emu10k1x: Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + */ + +#include +#include +#include + +#include "ca_midi.h" + +#define ca_midi_write_data(midi, data) midi->write(midi, data, 0) +#define ca_midi_write_cmd(midi, data) midi->write(midi, data, 1) +#define ca_midi_read_data(midi) midi->read(midi, 0) +#define ca_midi_read_stat(midi) midi->read(midi, 1) +#define ca_midi_input_avail(midi) (!(ca_midi_read_stat(midi) & midi->input_avail)) +#define ca_midi_output_ready(midi) (!(ca_midi_read_stat(midi) & midi->output_ready)) + +static void ca_midi_clear_rx(struct snd_ca_midi *midi) +{ + int timeout = 100000; + for (; timeout > 0 && ca_midi_input_avail(midi); timeout--) + ca_midi_read_data(midi); +#ifdef CONFIG_SND_DEBUG + if (timeout <= 0) + pr_err("ca_midi_clear_rx: timeout (status = 0x%x)\n", + ca_midi_read_stat(midi)); +#endif +} + +static void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status) +{ + unsigned char byte; + + if (midi->rmidi == NULL) { + midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable); + return; + } + + 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..922ed3e37 --- /dev/null +++ b/sound/pci/ca0106/ca_midi.h @@ -0,0 +1,66 @@ +/* + * Copyright 10/16/2005 Tilman Kranz + * Creative Audio MIDI, for the CA0106 Driver + * Version: 0.0.1 + * + * Changelog: + * See ca_midi.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#define CA_MIDI_MODE_INPUT MPU401_MODE_INPUT +#define CA_MIDI_MODE_OUTPUT MPU401_MODE_OUTPUT + +struct snd_ca_midi { + + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream_input; + struct snd_rawmidi_substream *substream_output; + + void *dev_id; + + spinlock_t input_lock; + spinlock_t output_lock; + spinlock_t open_lock; + + unsigned int channel; + + unsigned int midi_mode; + int port; + int tx_enable, rx_enable; + int ipr_tx, ipr_rx; + + int input_avail, output_ready; + int ack, reset, enter_uart; + + void (*interrupt)(struct snd_ca_midi *midi, unsigned int status); + void (*interrupt_enable)(struct snd_ca_midi *midi, int intr); + void (*interrupt_disable)(struct snd_ca_midi *midi, int intr); + + unsigned char (*read)(struct snd_ca_midi *midi, int idx); + void (*write)(struct snd_ca_midi *midi, int data, int idx); + + /* get info from dev_id */ + struct snd_card *(*get_dev_id_card)(void *dev_id); + int (*get_dev_id_port)(void *dev_id); +}; + +int ca_midi_init(void *card, struct snd_ca_midi *midi, int device, char *name); diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c new file mode 100644 index 000000000..79df78a7e --- /dev/null +++ b/sound/pci/cmipci.c @@ -0,0 +1,3406 @@ +/* + * Driver for C-Media CMI8338 and 8738 PCI soundcards. + * Copyright (c) 2000 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Does not work. Warning may block system in capture mode */ +/* #define USE_VAR48KRATE */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("C-Media CMI8x38 PCI"); +MODULE_LICENSE("GPL"); +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]; +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 unsigned int rates[] = { 5512, 11025, 22050, 44100, 8000, 16000, 32000, 48000 }; + +static unsigned int snd_cmipci_rate_freq(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rates); i++) { + if (rates[i] == rate) + return i; + } + snd_BUG(); + return 0; +} + +#ifdef USE_VAR48KRATE +/* + * Determine PLL values for frequency setup, maybe the CMI8338 (CMI8738???) + * does it this way .. maybe not. Never get any information from C-Media about + * that . + */ +static int snd_cmipci_pll_rmn(unsigned int rate, unsigned int adcmult, int *r, int *m, int *n) +{ + unsigned int delta, tolerance; + int xm, xn, xr; + + for (*r = 0; rate < CM_MAXIMUM_RATE/adcmult; *r += (1<<5)) + rate <<= 1; + *n = -1; + if (*r > 0xff) + goto out; + tolerance = rate*CM_TOLERANCE_RATE; + + for (xn = (1+2); xn < (0x1f+2); xn++) { + for (xm = (1+2); xm < (0xff+2); xm++) { + xr = ((CM_REFFREQ_XIN/adcmult) * xm) / xn; + + if (xr < rate) + delta = rate - xr; + else + delta = xr - rate; + + /* + * If we found one, remember this, + * and try to find a closer one + */ + if (delta < tolerance) { + tolerance = delta; + *m = xm - 2; + *n = xn - 2; + } + } + } +out: + return (*n > -1); +} + +/* + * Program pll register bits, I assume that the 8 registers 0xf8 up to 0xff + * are mapped onto the 8 ADC/DAC sampling frequency which can be chosen + * at the register CM_REG_FUNCTRL1 (0x04). + * Problem: other ways are also possible (any information about that?) + */ +static void snd_cmipci_set_pll(struct cmipci *cm, unsigned int rate, unsigned int slot) +{ + unsigned int reg = CM_REG_PLL + slot; + /* + * Guess that this programs at reg. 0x04 the pos 15:13/12:10 + * for DSFC/ASFC (000 up to 111). + */ + + /* FIXME: Init (Do we've to set an other register first before programming?) */ + + /* FIXME: Is this correct? Or shouldn't the m/n/r values be used for that? */ + snd_cmipci_write_b(cm, reg, rate>>8); + snd_cmipci_write_b(cm, reg, rate&0xff); + + /* FIXME: Setup (Do we've to set an other register first to enable this?) */ +} +#endif /* USE_VAR48KRATE */ + +static int snd_cmipci_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +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 snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +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 int snd_cmipci_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + + +/* + */ + +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 snd_cmipci_hw_free(substream); +} + +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 snd_cmipci_hw_free(substream); +} + +/* 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 snd_cmipci_hw_free(subs); +} + + +/* + * 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cmipci_hw_params, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cmipci_hw_params, + .hw_free = snd_cmipci_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cmipci_hw_params, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cmipci_hw_params, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(cm->pci), 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(cm->pci), 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(cm->pci), 64*1024, 128*1024); + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + snd_pcm_alt_chmaps, cm->max_channels, 0, + NULL); + if (err < 0) + return err; + + return 0; +} + +/* + * mixer interface: + * - CM8338/8738 has a compatible mixer interface with SB16, but + * lack of some elements like tone control, i/o gain and AGC. + * - Access to native registers: + * - A 3D switch + * - Output mute switches + */ + +static void snd_cmipci_mixer_write(struct cmipci *s, unsigned char idx, unsigned char data) +{ + outb(idx, s->iobase + CM_REG_SB16_ADDR); + outb(data, s->iobase + CM_REG_SB16_DATA); +} + +static unsigned char snd_cmipci_mixer_read(struct cmipci *s, unsigned char idx) +{ + unsigned char v; + + outb(idx, s->iobase + CM_REG_SB16_ADDR); + v = inb(s->iobase + CM_REG_SB16_DATA); + return v; +} + +/* + * general mixer element + */ +struct cmipci_sb_reg { + unsigned int left_reg, right_reg; + unsigned int left_shift, right_shift; + unsigned int mask; + unsigned int invert: 1; + unsigned int stereo: 1; +}; + +#define COMPOSE_SB_REG(lreg,rreg,lshift,rshift,mask,invert,stereo) \ + ((lreg) | ((rreg) << 8) | (lshift << 16) | (rshift << 19) | (mask << 24) | (invert << 22) | (stereo << 23)) + +#define CMIPCI_DOUBLE(xname, left_reg, right_reg, left_shift, right_shift, mask, invert, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_cmipci_info_volume, \ + .get = snd_cmipci_get_volume, .put = snd_cmipci_put_volume, \ + .private_value = COMPOSE_SB_REG(left_reg, right_reg, left_shift, right_shift, mask, invert, stereo), \ +} + +#define CMIPCI_SB_VOL_STEREO(xname,reg,shift,mask) CMIPCI_DOUBLE(xname, reg, reg+1, shift, shift, mask, 0, 1) +#define CMIPCI_SB_VOL_MONO(xname,reg,shift,mask) CMIPCI_DOUBLE(xname, reg, reg, shift, shift, mask, 0, 0) +#define CMIPCI_SB_SW_STEREO(xname,lshift,rshift) CMIPCI_DOUBLE(xname, SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, lshift, rshift, 1, 0, 1) +#define CMIPCI_SB_SW_MONO(xname,shift) CMIPCI_DOUBLE(xname, SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, shift, shift, 1, 0, 0) + +static void cmipci_sb_reg_decode(struct cmipci_sb_reg *r, unsigned long val) +{ + r->left_reg = val & 0xff; + r->right_reg = (val >> 8) & 0xff; + r->left_shift = (val >> 16) & 0x07; + r->right_shift = (val >> 19) & 0x07; + r->invert = (val >> 22) & 1; + r->stereo = (val >> 23) & 1; + r->mask = (val >> 24) & 0xff; +} + +static int snd_cmipci_info_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct cmipci_sb_reg reg; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + uinfo->type = reg.mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = reg.stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = reg.mask; + return 0; +} + +static int snd_cmipci_get_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + int val; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + 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(®, 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(®, 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(®, 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(®, kcontrol->private_value); + uinfo->type = reg.mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = reg.stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = reg.mask; + return 0; + +} + +static int snd_cmipci_get_native_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cmipci *cm = snd_kcontrol_chip(kcontrol); + struct cmipci_sb_reg reg; + unsigned char oreg, val; + + cmipci_sb_reg_decode(®, kcontrol->private_value); + 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(®, 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 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 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 struct snd_kcontrol_new snd_cmipci_nomulti_switch = +DEFINE_MIXER_SWITCH("Exchange DAC", exchange_dac); + +/* only for CM8738 */ +static 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 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 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 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; + 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(cm->card, "cmipci", &entry)) + snd_info_set_text_ops(entry, 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 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 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; + + 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) { + 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 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 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); + + snd_pcm_suspend_all(cm->pcm); + snd_pcm_suspend_all(cm->pcm2); + snd_pcm_suspend_all(cm->pcm_spdif); + + /* 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..ec4247638 --- /dev/null +++ b/sound/pci/cs4281.c @@ -0,0 +1,2085 @@ +/* + * Driver for Cirrus Logic CS4281 based PCI soundcard + * Copyright (c) by Jaroslav Kysela , + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Cirrus Logic CS4281"); +MODULE_LICENSE("GPL"); +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 = ~0; + + 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: + goto __variable; + } + __variable: + 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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_cs4281_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cs4281_hw_params, + .hw_free = snd_cs4281_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cs4281_hw_params, + .hw_free = snd_cs4281_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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 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 struct snd_info_entry_ops snd_cs4281_proc_ops_BA0 = { + .read = snd_cs4281_BA0_read, +}; + +static 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; + + if (! snd_card_proc_new(chip->card, "cs4281", &entry)) + snd_info_set_text_ops(entry, 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); + + if (chip->irq >= 0) + synchronize_irq(chip->irq); + + /* 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 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; + + 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))); + synchronize_irq(chip->irq); + + 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 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_pcm_suspend_all(chip->pcm); + + 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..67e811ec8 --- /dev/null +++ b/sound/pci/cs46xx/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-cs46xx-y := cs46xx.o cs46xx_lib.o +snd-cs46xx-$(CONFIG_SND_CS46XX_NEW_DSP) += dsp_spos.o dsp_spos_scb_lib.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_CS46XX) += snd-cs46xx.o diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c new file mode 100644 index 000000000..4910d3f46 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx.c @@ -0,0 +1,176 @@ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + NOTES: + - sometimes the sound is metallic and sibilant, unloading and + reloading the module may solve this. +*/ + +#include +#include +#include +#include +#include +#include "cs46xx.h" +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Cirrus Logic Sound Fusion CS46XX"); +MODULE_LICENSE("GPL"); +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..9c9f89a8b --- /dev/null +++ b/sound/pci/cs46xx/cs46xx.h @@ -0,0 +1,1749 @@ +#ifndef __SOUND_CS46XX_H +#define __SOUND_CS46XX_H + +/* + * Copyright (c) by Jaroslav Kysela , + * Cirrus Logic, Inc. + * Definitions for Cirrus Logic CS46xx chips + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "cs46xx_dsp_spos.h" + +/* + * Direct registers + */ + +/* + * The following define the offsets of the registers accessed via base address + * register zero on the CS46xx part. + */ +#define BA0_HISR 0x00000000 +#define BA0_HSR0 0x00000004 +#define BA0_HICR 0x00000008 +#define BA0_DMSR 0x00000100 +#define BA0_HSAR 0x00000110 +#define BA0_HDAR 0x00000114 +#define BA0_HDMR 0x00000118 +#define BA0_HDCR 0x0000011C +#define BA0_PFMC 0x00000200 +#define BA0_PFCV1 0x00000204 +#define BA0_PFCV2 0x00000208 +#define BA0_PCICFG00 0x00000300 +#define BA0_PCICFG04 0x00000304 +#define BA0_PCICFG08 0x00000308 +#define BA0_PCICFG0C 0x0000030C +#define BA0_PCICFG10 0x00000310 +#define BA0_PCICFG14 0x00000314 +#define BA0_PCICFG18 0x00000318 +#define BA0_PCICFG1C 0x0000031C +#define BA0_PCICFG20 0x00000320 +#define BA0_PCICFG24 0x00000324 +#define BA0_PCICFG28 0x00000328 +#define BA0_PCICFG2C 0x0000032C +#define BA0_PCICFG30 0x00000330 +#define BA0_PCICFG34 0x00000334 +#define BA0_PCICFG38 0x00000338 +#define BA0_PCICFG3C 0x0000033C +#define BA0_CLKCR1 0x00000400 +#define BA0_CLKCR2 0x00000404 +#define BA0_PLLM 0x00000408 +#define BA0_PLLCC 0x0000040C +#define BA0_FRR 0x00000410 +#define BA0_CFL1 0x00000414 +#define BA0_CFL2 0x00000418 +#define BA0_SERMC1 0x00000420 +#define BA0_SERMC2 0x00000424 +#define BA0_SERC1 0x00000428 +#define BA0_SERC2 0x0000042C +#define BA0_SERC3 0x00000430 +#define BA0_SERC4 0x00000434 +#define BA0_SERC5 0x00000438 +#define BA0_SERBSP 0x0000043C +#define BA0_SERBST 0x00000440 +#define BA0_SERBCM 0x00000444 +#define BA0_SERBAD 0x00000448 +#define BA0_SERBCF 0x0000044C +#define BA0_SERBWP 0x00000450 +#define BA0_SERBRP 0x00000454 +#ifndef NO_CS4612 +#define BA0_ASER_FADDR 0x00000458 +#endif +#define BA0_ACCTL 0x00000460 +#define BA0_ACSTS 0x00000464 +#define BA0_ACOSV 0x00000468 +#define BA0_ACCAD 0x0000046C +#define BA0_ACCDA 0x00000470 +#define BA0_ACISV 0x00000474 +#define BA0_ACSAD 0x00000478 +#define BA0_ACSDA 0x0000047C +#define BA0_JSPT 0x00000480 +#define BA0_JSCTL 0x00000484 +#define BA0_JSC1 0x00000488 +#define BA0_JSC2 0x0000048C +#define BA0_MIDCR 0x00000490 +#define BA0_MIDSR 0x00000494 +#define BA0_MIDWP 0x00000498 +#define BA0_MIDRP 0x0000049C +#define BA0_JSIO 0x000004A0 +#ifndef NO_CS4612 +#define BA0_ASER_MASTER 0x000004A4 +#endif +#define BA0_CFGI 0x000004B0 +#define BA0_SSVID 0x000004B4 +#define BA0_GPIOR 0x000004B8 +#ifndef NO_CS4612 +#define BA0_EGPIODR 0x000004BC +#define BA0_EGPIOPTR 0x000004C0 +#define BA0_EGPIOTR 0x000004C4 +#define BA0_EGPIOWR 0x000004C8 +#define BA0_EGPIOSR 0x000004CC +#define BA0_SERC6 0x000004D0 +#define BA0_SERC7 0x000004D4 +#define BA0_SERACC 0x000004D8 +#define BA0_ACCTL2 0x000004E0 +#define BA0_ACSTS2 0x000004E4 +#define BA0_ACOSV2 0x000004E8 +#define BA0_ACCAD2 0x000004EC +#define BA0_ACCDA2 0x000004F0 +#define BA0_ACISV2 0x000004F4 +#define BA0_ACSAD2 0x000004F8 +#define BA0_ACSDA2 0x000004FC +#define BA0_IOTAC0 0x00000500 +#define BA0_IOTAC1 0x00000504 +#define BA0_IOTAC2 0x00000508 +#define BA0_IOTAC3 0x0000050C +#define BA0_IOTAC4 0x00000510 +#define BA0_IOTAC5 0x00000514 +#define BA0_IOTAC6 0x00000518 +#define BA0_IOTAC7 0x0000051C +#define BA0_IOTAC8 0x00000520 +#define BA0_IOTAC9 0x00000524 +#define BA0_IOTAC10 0x00000528 +#define BA0_IOTAC11 0x0000052C +#define BA0_IOTFR0 0x00000540 +#define BA0_IOTFR1 0x00000544 +#define BA0_IOTFR2 0x00000548 +#define BA0_IOTFR3 0x0000054C +#define BA0_IOTFR4 0x00000550 +#define BA0_IOTFR5 0x00000554 +#define BA0_IOTFR6 0x00000558 +#define BA0_IOTFR7 0x0000055C +#define BA0_IOTFIFO 0x00000580 +#define BA0_IOTRRD 0x00000584 +#define BA0_IOTFP 0x00000588 +#define BA0_IOTCR 0x0000058C +#define BA0_DPCID 0x00000590 +#define BA0_DPCIA 0x00000594 +#define BA0_DPCIC 0x00000598 +#define BA0_PCPCIR 0x00000600 +#define BA0_PCPCIG 0x00000604 +#define BA0_PCPCIEN 0x00000608 +#define BA0_EPCIPMC 0x00000610 +#endif + +/* + * The following define the offsets of the registers and memories accessed via + * base address register one on the CS46xx part. + */ +#define BA1_SP_DMEM0 0x00000000 +#define BA1_SP_DMEM1 0x00010000 +#define BA1_SP_PMEM 0x00020000 +#define BA1_SP_REG 0x00030000 +#define BA1_SPCR 0x00030000 +#define BA1_DREG 0x00030004 +#define BA1_DSRWP 0x00030008 +#define BA1_TWPR 0x0003000C +#define BA1_SPWR 0x00030010 +#define BA1_SPIR 0x00030014 +#define BA1_FGR1 0x00030020 +#define BA1_SPCS 0x00030028 +#define BA1_SDSR 0x0003002C +#define BA1_FRMT 0x00030030 +#define BA1_FRCC 0x00030034 +#define BA1_FRSC 0x00030038 +#define BA1_OMNI_MEM 0x000E0000 + + +/* + * The following defines are for the flags in the host interrupt status + * register. + */ +#define HISR_VC_MASK 0x0000FFFF +#define HISR_VC0 0x00000001 +#define HISR_VC1 0x00000002 +#define HISR_VC2 0x00000004 +#define HISR_VC3 0x00000008 +#define HISR_VC4 0x00000010 +#define HISR_VC5 0x00000020 +#define HISR_VC6 0x00000040 +#define HISR_VC7 0x00000080 +#define HISR_VC8 0x00000100 +#define HISR_VC9 0x00000200 +#define HISR_VC10 0x00000400 +#define HISR_VC11 0x00000800 +#define HISR_VC12 0x00001000 +#define HISR_VC13 0x00002000 +#define HISR_VC14 0x00004000 +#define HISR_VC15 0x00008000 +#define HISR_INT0 0x00010000 +#define HISR_INT1 0x00020000 +#define HISR_DMAI 0x00040000 +#define HISR_FROVR 0x00080000 +#define HISR_MIDI 0x00100000 +#ifdef NO_CS4612 +#define HISR_RESERVED 0x0FE00000 +#else +#define HISR_SBINT 0x00200000 +#define HISR_RESERVED 0x0FC00000 +#endif +#define HISR_H0P 0x40000000 +#define HISR_INTENA 0x80000000 + +/* + * The following defines are for the flags in the host signal register 0. + */ +#define HSR0_VC_MASK 0xFFFFFFFF +#define HSR0_VC16 0x00000001 +#define HSR0_VC17 0x00000002 +#define HSR0_VC18 0x00000004 +#define HSR0_VC19 0x00000008 +#define HSR0_VC20 0x00000010 +#define HSR0_VC21 0x00000020 +#define HSR0_VC22 0x00000040 +#define HSR0_VC23 0x00000080 +#define HSR0_VC24 0x00000100 +#define HSR0_VC25 0x00000200 +#define HSR0_VC26 0x00000400 +#define HSR0_VC27 0x00000800 +#define HSR0_VC28 0x00001000 +#define HSR0_VC29 0x00002000 +#define HSR0_VC30 0x00004000 +#define HSR0_VC31 0x00008000 +#define HSR0_VC32 0x00010000 +#define HSR0_VC33 0x00020000 +#define HSR0_VC34 0x00040000 +#define HSR0_VC35 0x00080000 +#define HSR0_VC36 0x00100000 +#define HSR0_VC37 0x00200000 +#define HSR0_VC38 0x00400000 +#define HSR0_VC39 0x00800000 +#define HSR0_VC40 0x01000000 +#define HSR0_VC41 0x02000000 +#define HSR0_VC42 0x04000000 +#define HSR0_VC43 0x08000000 +#define HSR0_VC44 0x10000000 +#define HSR0_VC45 0x20000000 +#define HSR0_VC46 0x40000000 +#define HSR0_VC47 0x80000000 + +/* + * The following defines are for the flags in the host interrupt control + * register. + */ +#define HICR_IEV 0x00000001 +#define HICR_CHGM 0x00000002 + +/* + * The following defines are for the flags in the DMA status register. + */ +#define DMSR_HP 0x00000001 +#define DMSR_HR 0x00000002 +#define DMSR_SP 0x00000004 +#define DMSR_SR 0x00000008 + +/* + * The following defines are for the flags in the host DMA source address + * register. + */ +#define HSAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HSAR_DSP_ADDR_MASK 0x0000FFFF +#define HSAR_MEMID_MASK 0x000F0000 +#define HSAR_MEMID_SP_DMEM0 0x00000000 +#define HSAR_MEMID_SP_DMEM1 0x00010000 +#define HSAR_MEMID_SP_PMEM 0x00020000 +#define HSAR_MEMID_SP_DEBUG 0x00030000 +#define HSAR_MEMID_OMNI_MEM 0x000E0000 +#define HSAR_END 0x40000000 +#define HSAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA destination address + * register. + */ +#define HDAR_HOST_ADDR_MASK 0xFFFFFFFF +#define HDAR_DSP_ADDR_MASK 0x0000FFFF +#define HDAR_MEMID_MASK 0x000F0000 +#define HDAR_MEMID_SP_DMEM0 0x00000000 +#define HDAR_MEMID_SP_DMEM1 0x00010000 +#define HDAR_MEMID_SP_PMEM 0x00020000 +#define HDAR_MEMID_SP_DEBUG 0x00030000 +#define HDAR_MEMID_OMNI_MEM 0x000E0000 +#define HDAR_END 0x40000000 +#define HDAR_ERR 0x80000000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDMR_AC_MASK 0x0000F000 +#define HDMR_AC_8_16 0x00001000 +#define HDMR_AC_M_S 0x00002000 +#define HDMR_AC_B_L 0x00004000 +#define HDMR_AC_S_U 0x00008000 + +/* + * The following defines are for the flags in the host DMA control register. + */ +#define HDCR_COUNT_MASK 0x000003FF +#define HDCR_DONE 0x00004000 +#define HDCR_OPT 0x00008000 +#define HDCR_WBD 0x00400000 +#define HDCR_WBS 0x00800000 +#define HDCR_DMS_MASK 0x07000000 +#define HDCR_DMS_LINEAR 0x00000000 +#define HDCR_DMS_16_DWORDS 0x01000000 +#define HDCR_DMS_32_DWORDS 0x02000000 +#define HDCR_DMS_64_DWORDS 0x03000000 +#define HDCR_DMS_128_DWORDS 0x04000000 +#define HDCR_DMS_256_DWORDS 0x05000000 +#define HDCR_DMS_512_DWORDS 0x06000000 +#define HDCR_DMS_1024_DWORDS 0x07000000 +#define HDCR_DH 0x08000000 +#define HDCR_SMS_MASK 0x70000000 +#define HDCR_SMS_LINEAR 0x00000000 +#define HDCR_SMS_16_DWORDS 0x10000000 +#define HDCR_SMS_32_DWORDS 0x20000000 +#define HDCR_SMS_64_DWORDS 0x30000000 +#define HDCR_SMS_128_DWORDS 0x40000000 +#define HDCR_SMS_256_DWORDS 0x50000000 +#define HDCR_SMS_512_DWORDS 0x60000000 +#define HDCR_SMS_1024_DWORDS 0x70000000 +#define HDCR_SH 0x80000000 +#define HDCR_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the performance monitor control + * register. + */ +#define PFMC_C1SS_MASK 0x0000001F +#define PFMC_C1EV 0x00000020 +#define PFMC_C1RS 0x00008000 +#define PFMC_C2SS_MASK 0x001F0000 +#define PFMC_C2EV 0x00200000 +#define PFMC_C2RS 0x80000000 +#define PFMC_C1SS_SHIFT 0 +#define PFMC_C2SS_SHIFT 16 +#define PFMC_BUS_GRANT 0 +#define PFMC_GRANT_AFTER_REQ 1 +#define PFMC_TRANSACTION 2 +#define PFMC_DWORD_TRANSFER 3 +#define PFMC_SLAVE_READ 4 +#define PFMC_SLAVE_WRITE 5 +#define PFMC_PREEMPTION 6 +#define PFMC_DISCONNECT_RETRY 7 +#define PFMC_INTERRUPT 8 +#define PFMC_BUS_OWNERSHIP 9 +#define PFMC_TRANSACTION_LAG 10 +#define PFMC_PCI_CLOCK 11 +#define PFMC_SERIAL_CLOCK 12 +#define PFMC_SP_CLOCK 13 + +/* + * The following defines are for the flags in the performance counter value 1 + * register. + */ +#define PFCV1_PC1V_MASK 0xFFFFFFFF +#define PFCV1_PC1V_SHIFT 0 + +/* + * The following defines are for the flags in the performance counter value 2 + * register. + */ +#define PFCV2_PC2V_MASK 0xFFFFFFFF +#define PFCV2_PC2V_SHIFT 0 + +/* + * The following defines are for the flags in the clock control register 1. + */ +#define CLKCR1_OSCS 0x00000001 +#define CLKCR1_OSCP 0x00000002 +#define CLKCR1_PLLSS_MASK 0x0000000C +#define CLKCR1_PLLSS_SERIAL 0x00000000 +#define CLKCR1_PLLSS_CRYSTAL 0x00000004 +#define CLKCR1_PLLSS_PCI 0x00000008 +#define CLKCR1_PLLSS_RESERVED 0x0000000C +#define CLKCR1_PLLP 0x00000010 +#define CLKCR1_SWCE 0x00000020 +#define CLKCR1_PLLOS 0x00000040 + +/* + * The following defines are for the flags in the clock control register 2. + */ +#define CLKCR2_PDIVS_MASK 0x0000000F +#define CLKCR2_PDIVS_1 0x00000001 +#define CLKCR2_PDIVS_2 0x00000002 +#define CLKCR2_PDIVS_4 0x00000004 +#define CLKCR2_PDIVS_7 0x00000007 +#define CLKCR2_PDIVS_8 0x00000008 +#define CLKCR2_PDIVS_16 0x00000000 + +/* + * The following defines are for the flags in the PLL multiplier register. + */ +#define PLLM_MASK 0x000000FF +#define PLLM_SHIFT 0 + +/* + * The following defines are for the flags in the PLL capacitor coefficient + * register. + */ +#define PLLCC_CDR_MASK 0x00000007 +#ifndef NO_CS4610 +#define PLLCC_CDR_240_350_MHZ 0x00000000 +#define PLLCC_CDR_184_265_MHZ 0x00000001 +#define PLLCC_CDR_144_205_MHZ 0x00000002 +#define PLLCC_CDR_111_160_MHZ 0x00000003 +#define PLLCC_CDR_87_123_MHZ 0x00000004 +#define PLLCC_CDR_67_96_MHZ 0x00000005 +#define PLLCC_CDR_52_74_MHZ 0x00000006 +#define PLLCC_CDR_45_58_MHZ 0x00000007 +#endif +#ifndef NO_CS4612 +#define PLLCC_CDR_271_398_MHZ 0x00000000 +#define PLLCC_CDR_227_330_MHZ 0x00000001 +#define PLLCC_CDR_167_239_MHZ 0x00000002 +#define PLLCC_CDR_150_215_MHZ 0x00000003 +#define PLLCC_CDR_107_154_MHZ 0x00000004 +#define PLLCC_CDR_98_140_MHZ 0x00000005 +#define PLLCC_CDR_73_104_MHZ 0x00000006 +#define PLLCC_CDR_63_90_MHZ 0x00000007 +#endif +#define PLLCC_LPF_MASK 0x000000F8 +#ifndef NO_CS4610 +#define PLLCC_LPF_23850_60000_KHZ 0x00000000 +#define PLLCC_LPF_7960_26290_KHZ 0x00000008 +#define PLLCC_LPF_4160_10980_KHZ 0x00000018 +#define PLLCC_LPF_1740_4580_KHZ 0x00000038 +#define PLLCC_LPF_724_1910_KHZ 0x00000078 +#define PLLCC_LPF_317_798_KHZ 0x000000F8 +#endif +#ifndef NO_CS4612 +#define PLLCC_LPF_25580_64530_KHZ 0x00000000 +#define PLLCC_LPF_14360_37270_KHZ 0x00000008 +#define PLLCC_LPF_6100_16020_KHZ 0x00000018 +#define PLLCC_LPF_2540_6690_KHZ 0x00000038 +#define PLLCC_LPF_1050_2780_KHZ 0x00000078 +#define PLLCC_LPF_450_1160_KHZ 0x000000F8 +#endif + +/* + * The following defines are for the flags in the feature reporting register. + */ +#define FRR_FAB_MASK 0x00000003 +#define FRR_MASK_MASK 0x0000001C +#ifdef NO_CS4612 +#define FRR_CFOP_MASK 0x000000E0 +#else +#define FRR_CFOP_MASK 0x00000FE0 +#endif +#define FRR_CFOP_NOT_DVD 0x00000020 +#define FRR_CFOP_A3D 0x00000040 +#define FRR_CFOP_128_PIN 0x00000080 +#ifndef NO_CS4612 +#define FRR_CFOP_CS4280 0x00000800 +#endif +#define FRR_FAB_SHIFT 0 +#define FRR_MASK_SHIFT 2 +#define FRR_CFOP_SHIFT 5 + +/* + * The following defines are for the flags in the configuration load 1 + * register. + */ +#define CFL1_CLOCK_SOURCE_MASK 0x00000003 +#define CFL1_CLOCK_SOURCE_CS423X 0x00000000 +#define CFL1_CLOCK_SOURCE_AC97 0x00000001 +#define CFL1_CLOCK_SOURCE_CRYSTAL 0x00000002 +#define CFL1_CLOCK_SOURCE_DUAL_AC97 0x00000003 +#define CFL1_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the configuration load 2 + * register. + */ +#define CFL2_VALID_DATA_MASK 0x000000FF + +/* + * The following defines are for the flags in the serial port master control + * register 1. + */ +#define SERMC1_MSPE 0x00000001 +#define SERMC1_PTC_MASK 0x0000000E +#define SERMC1_PTC_CS423X 0x00000000 +#define SERMC1_PTC_AC97 0x00000002 +#define SERMC1_PTC_DAC 0x00000004 +#define SERMC1_PLB 0x00000010 +#define SERMC1_XLB 0x00000020 + +/* + * The following defines are for the flags in the serial port master control + * register 2. + */ +#define SERMC2_LROE 0x00000001 +#define SERMC2_MCOE 0x00000002 +#define SERMC2_MCDIV 0x00000004 + +/* + * The following defines are for the flags in the serial port 1 configuration + * register. + */ +#define SERC1_SO1EN 0x00000001 +#define SERC1_SO1F_MASK 0x0000000E +#define SERC1_SO1F_CS423X 0x00000000 +#define SERC1_SO1F_AC97 0x00000002 +#define SERC1_SO1F_DAC 0x00000004 +#define SERC1_SO1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 2 configuration + * register. + */ +#define SERC2_SI1EN 0x00000001 +#define SERC2_SI1F_MASK 0x0000000E +#define SERC2_SI1F_CS423X 0x00000000 +#define SERC2_SI1F_AC97 0x00000002 +#define SERC2_SI1F_ADC 0x00000004 +#define SERC2_SI1F_SPDIF 0x00000006 + +/* + * The following defines are for the flags in the serial port 3 configuration + * register. + */ +#define SERC3_SO2EN 0x00000001 +#define SERC3_SO2F_MASK 0x00000006 +#define SERC3_SO2F_DAC 0x00000000 +#define SERC3_SO2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 4 configuration + * register. + */ +#define SERC4_SO3EN 0x00000001 +#define SERC4_SO3F_MASK 0x00000006 +#define SERC4_SO3F_DAC 0x00000000 +#define SERC4_SO3F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port 5 configuration + * register. + */ +#define SERC5_SI2EN 0x00000001 +#define SERC5_SI2F_MASK 0x00000006 +#define SERC5_SI2F_ADC 0x00000000 +#define SERC5_SI2F_SPDIF 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor sample + * pointer register. + */ +#define SERBSP_FSP_MASK 0x0000000F +#define SERBSP_FSP_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor status + * register. + */ +#define SERBST_RRDY 0x00000001 +#define SERBST_WBSY 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor command + * register. + */ +#define SERBCM_RDC 0x00000001 +#define SERBCM_WRC 0x00000002 + +/* + * The following defines are for the flags in the serial port backdoor address + * register. + */ +#ifdef NO_CS4612 +#define SERBAD_FAD_MASK 0x000000FF +#else +#define SERBAD_FAD_MASK 0x000001FF +#endif +#define SERBAD_FAD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor + * configuration register. + */ +#define SERBCF_HBP 0x00000001 + +/* + * The following defines are for the flags in the serial port backdoor write + * port register. + */ +#define SERBWP_FWD_MASK 0x000FFFFF +#define SERBWP_FWD_SHIFT 0 + +/* + * The following defines are for the flags in the serial port backdoor read + * port register. + */ +#define SERBRP_FRD_MASK 0x000FFFFF +#define SERBRP_FRD_SHIFT 0 + +/* + * The following defines are for the flags in the async FIFO address register. + */ +#ifndef NO_CS4612 +#define ASER_FADDR_A1_MASK 0x000001FF +#define ASER_FADDR_EN1 0x00008000 +#define ASER_FADDR_A2_MASK 0x01FF0000 +#define ASER_FADDR_EN2 0x80000000 +#define ASER_FADDR_A1_SHIFT 0 +#define ASER_FADDR_A2_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the AC97 control register. + */ +#define ACCTL_RSTN 0x00000001 +#define ACCTL_ESYN 0x00000002 +#define ACCTL_VFRM 0x00000004 +#define ACCTL_DCV 0x00000008 +#define ACCTL_CRW 0x00000010 +#define ACCTL_ASYN 0x00000020 +#ifndef NO_CS4612 +#define ACCTL_TC 0x00000040 +#endif + +/* + * The following defines are for the flags in the AC97 status register. + */ +#define ACSTS_CRDY 0x00000001 +#define ACSTS_VSTS 0x00000002 +#ifndef NO_CS4612 +#define ACSTS_WKUP 0x00000004 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register. + */ +#define ACOSV_SLV3 0x00000001 +#define ACOSV_SLV4 0x00000002 +#define ACOSV_SLV5 0x00000004 +#define ACOSV_SLV6 0x00000008 +#define ACOSV_SLV7 0x00000010 +#define ACOSV_SLV8 0x00000020 +#define ACOSV_SLV9 0x00000040 +#define ACOSV_SLV10 0x00000080 +#define ACOSV_SLV11 0x00000100 +#define ACOSV_SLV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 command address + * register. + */ +#define ACCAD_CI_MASK 0x0000007F +#define ACCAD_CI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 command data register. + */ +#define ACCDA_CD_MASK 0x0000FFFF +#define ACCDA_CD_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 input slot valid + * register. + */ +#define ACISV_ISV3 0x00000001 +#define ACISV_ISV4 0x00000002 +#define ACISV_ISV5 0x00000004 +#define ACISV_ISV6 0x00000008 +#define ACISV_ISV7 0x00000010 +#define ACISV_ISV8 0x00000020 +#define ACISV_ISV9 0x00000040 +#define ACISV_ISV10 0x00000080 +#define ACISV_ISV11 0x00000100 +#define ACISV_ISV12 0x00000200 + +/* + * The following defines are for the flags in the AC97 status address + * register. + */ +#define ACSAD_SI_MASK 0x0000007F +#define ACSAD_SI_SHIFT 0 + +/* + * The following defines are for the flags in the AC97 status data register. + */ +#define ACSDA_SD_MASK 0x0000FFFF +#define ACSDA_SD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick poll/trigger + * register. + */ +#define JSPT_CAX 0x00000001 +#define JSPT_CAY 0x00000002 +#define JSPT_CBX 0x00000004 +#define JSPT_CBY 0x00000008 +#define JSPT_BA1 0x00000010 +#define JSPT_BA2 0x00000020 +#define JSPT_BB1 0x00000040 +#define JSPT_BB2 0x00000080 + +/* + * The following defines are for the flags in the joystick control register. + */ +#define JSCTL_SP_MASK 0x00000003 +#define JSCTL_SP_SLOW 0x00000000 +#define JSCTL_SP_MEDIUM_SLOW 0x00000001 +#define JSCTL_SP_MEDIUM_FAST 0x00000002 +#define JSCTL_SP_FAST 0x00000003 +#define JSCTL_ARE 0x00000004 + +/* + * The following defines are for the flags in the joystick coordinate pair 1 + * readback register. + */ +#define JSC1_Y1V_MASK 0x0000FFFF +#define JSC1_X1V_MASK 0xFFFF0000 +#define JSC1_Y1V_SHIFT 0 +#define JSC1_X1V_SHIFT 16 + +/* + * The following defines are for the flags in the joystick coordinate pair 2 + * readback register. + */ +#define JSC2_Y2V_MASK 0x0000FFFF +#define JSC2_X2V_MASK 0xFFFF0000 +#define JSC2_Y2V_SHIFT 0 +#define JSC2_X2V_SHIFT 16 + +/* + * The following defines are for the flags in the MIDI control register. + */ +#define MIDCR_TXE 0x00000001 /* Enable transmitting. */ +#define MIDCR_RXE 0x00000002 /* Enable receiving. */ +#define MIDCR_RIE 0x00000004 /* Interrupt upon tx ready. */ +#define MIDCR_TIE 0x00000008 /* Interrupt upon rx ready. */ +#define MIDCR_MLB 0x00000010 /* Enable midi loopback. */ +#define MIDCR_MRST 0x00000020 /* Reset interface. */ + +/* + * The following defines are for the flags in the MIDI status register. + */ +#define MIDSR_TBF 0x00000001 /* Tx FIFO is full. */ +#define MIDSR_RBE 0x00000002 /* Rx FIFO is empty. */ + +/* + * The following defines are for the flags in the MIDI write port register. + */ +#define MIDWP_MWD_MASK 0x000000FF +#define MIDWP_MWD_SHIFT 0 + +/* + * The following defines are for the flags in the MIDI read port register. + */ +#define MIDRP_MRD_MASK 0x000000FF +#define MIDRP_MRD_SHIFT 0 + +/* + * The following defines are for the flags in the joystick GPIO register. + */ +#define JSIO_DAX 0x00000001 +#define JSIO_DAY 0x00000002 +#define JSIO_DBX 0x00000004 +#define JSIO_DBY 0x00000008 +#define JSIO_AXOE 0x00000010 +#define JSIO_AYOE 0x00000020 +#define JSIO_BXOE 0x00000040 +#define JSIO_BYOE 0x00000080 + +/* + * The following defines are for the flags in the master async/sync serial + * port enable register. + */ +#ifndef NO_CS4612 +#define ASER_MASTER_ME 0x00000001 +#endif + +/* + * The following defines are for the flags in the configuration interface + * register. + */ +#define CFGI_CLK 0x00000001 +#define CFGI_DOUT 0x00000002 +#define CFGI_DIN_EEN 0x00000004 +#define CFGI_EELD 0x00000008 + +/* + * The following defines are for the flags in the subsystem ID and vendor ID + * register. + */ +#define SSVID_VID_MASK 0x0000FFFF +#define SSVID_SID_MASK 0xFFFF0000 +#define SSVID_VID_SHIFT 0 +#define SSVID_SID_SHIFT 16 + +/* + * The following defines are for the flags in the GPIO pin interface register. + */ +#define GPIOR_VOLDN 0x00000001 +#define GPIOR_VOLUP 0x00000002 +#define GPIOR_SI2D 0x00000004 +#define GPIOR_SI2OE 0x00000008 + +/* + * The following defines are for the flags in the extended GPIO pin direction + * register. + */ +#ifndef NO_CS4612 +#define EGPIODR_GPOE0 0x00000001 +#define EGPIODR_GPOE1 0x00000002 +#define EGPIODR_GPOE2 0x00000004 +#define EGPIODR_GPOE3 0x00000008 +#define EGPIODR_GPOE4 0x00000010 +#define EGPIODR_GPOE5 0x00000020 +#define EGPIODR_GPOE6 0x00000040 +#define EGPIODR_GPOE7 0x00000080 +#define EGPIODR_GPOE8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin polarity/ + * type register. + */ +#ifndef NO_CS4612 +#define EGPIOPTR_GPPT0 0x00000001 +#define EGPIOPTR_GPPT1 0x00000002 +#define EGPIOPTR_GPPT2 0x00000004 +#define EGPIOPTR_GPPT3 0x00000008 +#define EGPIOPTR_GPPT4 0x00000010 +#define EGPIOPTR_GPPT5 0x00000020 +#define EGPIOPTR_GPPT6 0x00000040 +#define EGPIOPTR_GPPT7 0x00000080 +#define EGPIOPTR_GPPT8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin sticky + * register. + */ +#ifndef NO_CS4612 +#define EGPIOTR_GPS0 0x00000001 +#define EGPIOTR_GPS1 0x00000002 +#define EGPIOTR_GPS2 0x00000004 +#define EGPIOTR_GPS3 0x00000008 +#define EGPIOTR_GPS4 0x00000010 +#define EGPIOTR_GPS5 0x00000020 +#define EGPIOTR_GPS6 0x00000040 +#define EGPIOTR_GPS7 0x00000080 +#define EGPIOTR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO ping wakeup + * register. + */ +#ifndef NO_CS4612 +#define EGPIOWR_GPW0 0x00000001 +#define EGPIOWR_GPW1 0x00000002 +#define EGPIOWR_GPW2 0x00000004 +#define EGPIOWR_GPW3 0x00000008 +#define EGPIOWR_GPW4 0x00000010 +#define EGPIOWR_GPW5 0x00000020 +#define EGPIOWR_GPW6 0x00000040 +#define EGPIOWR_GPW7 0x00000080 +#define EGPIOWR_GPW8 0x00000100 +#endif + +/* + * The following defines are for the flags in the extended GPIO pin status + * register. + */ +#ifndef NO_CS4612 +#define EGPIOSR_GPS0 0x00000001 +#define EGPIOSR_GPS1 0x00000002 +#define EGPIOSR_GPS2 0x00000004 +#define EGPIOSR_GPS3 0x00000008 +#define EGPIOSR_GPS4 0x00000010 +#define EGPIOSR_GPS5 0x00000020 +#define EGPIOSR_GPS6 0x00000040 +#define EGPIOSR_GPS7 0x00000080 +#define EGPIOSR_GPS8 0x00000100 +#endif + +/* + * The following defines are for the flags in the serial port 6 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC6_ASDO2EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the serial port 7 configuration + * register. + */ +#ifndef NO_CS4612 +#define SERC7_ASDI2EN 0x00000001 +#define SERC7_POSILB 0x00000002 +#define SERC7_SIPOLB 0x00000004 +#define SERC7_SOSILB 0x00000008 +#define SERC7_SISOLB 0x00000010 +#endif + +/* + * The following defines are for the flags in the serial port AC link + * configuration register. + */ +#ifndef NO_CS4612 +#define SERACC_CHIP_TYPE_MASK 0x00000001 +#define SERACC_CHIP_TYPE_1_03 0x00000000 +#define SERACC_CHIP_TYPE_2_0 0x00000001 +#define SERACC_TWO_CODECS 0x00000002 +#define SERACC_MDM 0x00000004 +#define SERACC_HSP 0x00000008 +#define SERACC_ODT 0x00000010 /* only CS4630 */ +#endif + +/* + * The following defines are for the flags in the AC97 control register 2. + */ +#ifndef NO_CS4612 +#define ACCTL2_RSTN 0x00000001 +#define ACCTL2_ESYN 0x00000002 +#define ACCTL2_VFRM 0x00000004 +#define ACCTL2_DCV 0x00000008 +#define ACCTL2_CRW 0x00000010 +#define ACCTL2_ASYN 0x00000020 +#endif + +/* + * The following defines are for the flags in the AC97 status register 2. + */ +#ifndef NO_CS4612 +#define ACSTS2_CRDY 0x00000001 +#define ACSTS2_VSTS 0x00000002 +#endif + +/* + * The following defines are for the flags in the AC97 output slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACOSV2_SLV3 0x00000001 +#define ACOSV2_SLV4 0x00000002 +#define ACOSV2_SLV5 0x00000004 +#define ACOSV2_SLV6 0x00000008 +#define ACOSV2_SLV7 0x00000010 +#define ACOSV2_SLV8 0x00000020 +#define ACOSV2_SLV9 0x00000040 +#define ACOSV2_SLV10 0x00000080 +#define ACOSV2_SLV11 0x00000100 +#define ACOSV2_SLV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 command address + * register 2. + */ +#ifndef NO_CS4612 +#define ACCAD2_CI_MASK 0x0000007F +#define ACCAD2_CI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 command data register + * 2. + */ +#ifndef NO_CS4612 +#define ACCDA2_CD_MASK 0x0000FFFF +#define ACCDA2_CD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 input slot valid + * register 2. + */ +#ifndef NO_CS4612 +#define ACISV2_ISV3 0x00000001 +#define ACISV2_ISV4 0x00000002 +#define ACISV2_ISV5 0x00000004 +#define ACISV2_ISV6 0x00000008 +#define ACISV2_ISV7 0x00000010 +#define ACISV2_ISV8 0x00000020 +#define ACISV2_ISV9 0x00000040 +#define ACISV2_ISV10 0x00000080 +#define ACISV2_ISV11 0x00000100 +#define ACISV2_ISV12 0x00000200 +#endif + +/* + * The following defines are for the flags in the AC97 status address + * register 2. + */ +#ifndef NO_CS4612 +#define ACSAD2_SI_MASK 0x0000007F +#define ACSAD2_SI_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the AC97 status data register 2. + */ +#ifndef NO_CS4612 +#define ACSDA2_SD_MASK 0x0000FFFF +#define ACSDA2_SD_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap address and control + * registers (all 12). + */ +#ifndef NO_CS4612 +#define IOTAC_SA_MASK 0x0000FFFF +#define IOTAC_MSK_MASK 0x000F0000 +#define IOTAC_IODC_MASK 0x06000000 +#define IOTAC_IODC_16_BIT 0x00000000 +#define IOTAC_IODC_10_BIT 0x02000000 +#define IOTAC_IODC_12_BIT 0x04000000 +#define IOTAC_WSPI 0x08000000 +#define IOTAC_RSPI 0x10000000 +#define IOTAC_WSE 0x20000000 +#define IOTAC_WE 0x40000000 +#define IOTAC_RE 0x80000000 +#define IOTAC_SA_SHIFT 0 +#define IOTAC_MSK_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap fast read registers + * (all 8). + */ +#ifndef NO_CS4612 +#define IOTFR_D_MASK 0x0000FFFF +#define IOTFR_A_MASK 0x000F0000 +#define IOTFR_R_MASK 0x0F000000 +#define IOTFR_ALL 0x40000000 +#define IOTFR_VL 0x80000000 +#define IOTFR_D_SHIFT 0 +#define IOTFR_A_SHIFT 16 +#define IOTFR_R_SHIFT 24 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO register. + */ +#ifndef NO_CS4612 +#define IOTFIFO_BA_MASK 0x00003FFF +#define IOTFIFO_S_MASK 0x00FF0000 +#define IOTFIFO_OF 0x40000000 +#define IOTFIFO_SPIOF 0x80000000 +#define IOTFIFO_BA_SHIFT 0 +#define IOTFIFO_S_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap retry read data + * register. + */ +#ifndef NO_CS4612 +#define IOTRRD_D_MASK 0x0000FFFF +#define IOTRRD_RDV 0x80000000 +#define IOTRRD_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the I/O trap FIFO pointer + * register. + */ +#ifndef NO_CS4612 +#define IOTFP_CA_MASK 0x00003FFF +#define IOTFP_PA_MASK 0x3FFF0000 +#define IOTFP_CA_SHIFT 0 +#define IOTFP_PA_SHIFT 16 +#endif + +/* + * The following defines are for the flags in the I/O trap control register. + */ +#ifndef NO_CS4612 +#define IOTCR_ITD 0x00000001 +#define IOTCR_HRV 0x00000002 +#define IOTCR_SRV 0x00000004 +#define IOTCR_DTI 0x00000008 +#define IOTCR_DFI 0x00000010 +#define IOTCR_DDP 0x00000020 +#define IOTCR_JTE 0x00000040 +#define IOTCR_PPE 0x00000080 +#endif + +/* + * The following defines are for the flags in the direct PCI data register. + */ +#ifndef NO_CS4612 +#define DPCID_D_MASK 0xFFFFFFFF +#define DPCID_D_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI address register. + */ +#ifndef NO_CS4612 +#define DPCIA_A_MASK 0xFFFFFFFF +#define DPCIA_A_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the direct PCI command register. + */ +#ifndef NO_CS4612 +#define DPCIC_C_MASK 0x0000000F +#define DPCIC_C_IOREAD 0x00000002 +#define DPCIC_C_IOWRITE 0x00000003 +#define DPCIC_BE_MASK 0x000000F0 +#endif + +/* + * The following defines are for the flags in the PC/PCI request register. + */ +#ifndef NO_CS4612 +#define PCPCIR_RDC_MASK 0x00000007 +#define PCPCIR_C_MASK 0x00007000 +#define PCPCIR_REQ 0x00008000 +#define PCPCIR_RDC_SHIFT 0 +#define PCPCIR_C_SHIFT 12 +#endif + +/* + * The following defines are for the flags in the PC/PCI grant register. + */ +#ifndef NO_CS4612 +#define PCPCIG_GDC_MASK 0x00000007 +#define PCPCIG_VL 0x00008000 +#define PCPCIG_GDC_SHIFT 0 +#endif + +/* + * The following defines are for the flags in the PC/PCI master enable + * register. + */ +#ifndef NO_CS4612 +#define PCPCIEN_EN 0x00000001 +#endif + +/* + * The following defines are for the flags in the extended PCI power + * management control register. + */ +#ifndef NO_CS4612 +#define EPCIPMC_GWU 0x00000001 +#define EPCIPMC_FSPC 0x00000002 +#endif + +/* + * The following defines are for the flags in the SP control register. + */ +#define SPCR_RUN 0x00000001 +#define SPCR_STPFR 0x00000002 +#define SPCR_RUNFR 0x00000004 +#define SPCR_TICK 0x00000008 +#define SPCR_DRQEN 0x00000020 +#define SPCR_RSTSP 0x00000040 +#define SPCR_OREN 0x00000080 +#ifndef NO_CS4612 +#define SPCR_PCIINT 0x00000100 +#define SPCR_OINTD 0x00000200 +#define SPCR_CRE 0x00008000 +#endif + +/* + * The following defines are for the flags in the debug index register. + */ +#define DREG_REGID_MASK 0x0000007F +#define DREG_DEBUG 0x00000080 +#define DREG_RGBK_MASK 0x00000700 +#define DREG_TRAP 0x00000800 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_TRAPX 0x00001000 +#endif +#endif +#define DREG_REGID_SHIFT 0 +#define DREG_RGBK_SHIFT 8 +#define DREG_RGBK_REGID_MASK 0x0000077F +#define DREG_REGID_R0 0x00000010 +#define DREG_REGID_R1 0x00000011 +#define DREG_REGID_R2 0x00000012 +#define DREG_REGID_R3 0x00000013 +#define DREG_REGID_R4 0x00000014 +#define DREG_REGID_R5 0x00000015 +#define DREG_REGID_R6 0x00000016 +#define DREG_REGID_R7 0x00000017 +#define DREG_REGID_R8 0x00000018 +#define DREG_REGID_R9 0x00000019 +#define DREG_REGID_RA 0x0000001A +#define DREG_REGID_RB 0x0000001B +#define DREG_REGID_RC 0x0000001C +#define DREG_REGID_RD 0x0000001D +#define DREG_REGID_RE 0x0000001E +#define DREG_REGID_RF 0x0000001F +#define DREG_REGID_RA_BUS_LOW 0x00000020 +#define DREG_REGID_RA_BUS_HIGH 0x00000038 +#define DREG_REGID_YBUS_LOW 0x00000050 +#define DREG_REGID_YBUS_HIGH 0x00000058 +#define DREG_REGID_TRAP_0 0x00000100 +#define DREG_REGID_TRAP_1 0x00000101 +#define DREG_REGID_TRAP_2 0x00000102 +#define DREG_REGID_TRAP_3 0x00000103 +#define DREG_REGID_TRAP_4 0x00000104 +#define DREG_REGID_TRAP_5 0x00000105 +#define DREG_REGID_TRAP_6 0x00000106 +#define DREG_REGID_TRAP_7 0x00000107 +#define DREG_REGID_INDIRECT_ADDRESS 0x0000010E +#define DREG_REGID_TOP_OF_STACK 0x0000010F +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_8 0x00000110 +#define DREG_REGID_TRAP_9 0x00000111 +#define DREG_REGID_TRAP_10 0x00000112 +#define DREG_REGID_TRAP_11 0x00000113 +#define DREG_REGID_TRAP_12 0x00000114 +#define DREG_REGID_TRAP_13 0x00000115 +#define DREG_REGID_TRAP_14 0x00000116 +#define DREG_REGID_TRAP_15 0x00000117 +#define DREG_REGID_TRAP_16 0x00000118 +#define DREG_REGID_TRAP_17 0x00000119 +#define DREG_REGID_TRAP_18 0x0000011A +#define DREG_REGID_TRAP_19 0x0000011B +#define DREG_REGID_TRAP_20 0x0000011C +#define DREG_REGID_TRAP_21 0x0000011D +#define DREG_REGID_TRAP_22 0x0000011E +#define DREG_REGID_TRAP_23 0x0000011F +#endif +#endif +#define DREG_REGID_RSA0_LOW 0x00000200 +#define DREG_REGID_RSA0_HIGH 0x00000201 +#define DREG_REGID_RSA1_LOW 0x00000202 +#define DREG_REGID_RSA1_HIGH 0x00000203 +#define DREG_REGID_RSA2 0x00000204 +#define DREG_REGID_RSA3 0x00000205 +#define DREG_REGID_RSI0_LOW 0x00000206 +#define DREG_REGID_RSI0_HIGH 0x00000207 +#define DREG_REGID_RSI1 0x00000208 +#define DREG_REGID_RSI2 0x00000209 +#define DREG_REGID_SAGUSTATUS 0x0000020A +#define DREG_REGID_RSCONFIG01_LOW 0x0000020B +#define DREG_REGID_RSCONFIG01_HIGH 0x0000020C +#define DREG_REGID_RSCONFIG23_LOW 0x0000020D +#define DREG_REGID_RSCONFIG23_HIGH 0x0000020E +#define DREG_REGID_RSDMA01E 0x0000020F +#define DREG_REGID_RSDMA23E 0x00000210 +#define DREG_REGID_RSD0_LOW 0x00000211 +#define DREG_REGID_RSD0_HIGH 0x00000212 +#define DREG_REGID_RSD1_LOW 0x00000213 +#define DREG_REGID_RSD1_HIGH 0x00000214 +#define DREG_REGID_RSD2_LOW 0x00000215 +#define DREG_REGID_RSD2_HIGH 0x00000216 +#define DREG_REGID_RSD3_LOW 0x00000217 +#define DREG_REGID_RSD3_HIGH 0x00000218 +#define DREG_REGID_SRAR_HIGH 0x0000021A +#define DREG_REGID_SRAR_LOW 0x0000021B +#define DREG_REGID_DMA_STATE 0x0000021C +#define DREG_REGID_CURRENT_DMA_STREAM 0x0000021D +#define DREG_REGID_NEXT_DMA_STREAM 0x0000021E +#define DREG_REGID_CPU_STATUS 0x00000300 +#define DREG_REGID_MAC_MODE 0x00000301 +#define DREG_REGID_STACK_AND_REPEAT 0x00000302 +#define DREG_REGID_INDEX0 0x00000304 +#define DREG_REGID_INDEX1 0x00000305 +#define DREG_REGID_DMA_STATE_0_3 0x00000400 +#define DREG_REGID_DMA_STATE_4_7 0x00000404 +#define DREG_REGID_DMA_STATE_8_11 0x00000408 +#define DREG_REGID_DMA_STATE_12_15 0x0000040C +#define DREG_REGID_DMA_STATE_16_19 0x00000410 +#define DREG_REGID_DMA_STATE_20_23 0x00000414 +#define DREG_REGID_DMA_STATE_24_27 0x00000418 +#define DREG_REGID_DMA_STATE_28_31 0x0000041C +#define DREG_REGID_DMA_STATE_32_35 0x00000420 +#define DREG_REGID_DMA_STATE_36_39 0x00000424 +#define DREG_REGID_DMA_STATE_40_43 0x00000428 +#define DREG_REGID_DMA_STATE_44_47 0x0000042C +#define DREG_REGID_DMA_STATE_48_51 0x00000430 +#define DREG_REGID_DMA_STATE_52_55 0x00000434 +#define DREG_REGID_DMA_STATE_56_59 0x00000438 +#define DREG_REGID_DMA_STATE_60_63 0x0000043C +#define DREG_REGID_DMA_STATE_64_67 0x00000440 +#define DREG_REGID_DMA_STATE_68_71 0x00000444 +#define DREG_REGID_DMA_STATE_72_75 0x00000448 +#define DREG_REGID_DMA_STATE_76_79 0x0000044C +#define DREG_REGID_DMA_STATE_80_83 0x00000450 +#define DREG_REGID_DMA_STATE_84_87 0x00000454 +#define DREG_REGID_DMA_STATE_88_91 0x00000458 +#define DREG_REGID_DMA_STATE_92_95 0x0000045C +#define DREG_REGID_TRAP_SELECT 0x00000500 +#define DREG_REGID_TRAP_WRITE_0 0x00000500 +#define DREG_REGID_TRAP_WRITE_1 0x00000501 +#define DREG_REGID_TRAP_WRITE_2 0x00000502 +#define DREG_REGID_TRAP_WRITE_3 0x00000503 +#define DREG_REGID_TRAP_WRITE_4 0x00000504 +#define DREG_REGID_TRAP_WRITE_5 0x00000505 +#define DREG_REGID_TRAP_WRITE_6 0x00000506 +#define DREG_REGID_TRAP_WRITE_7 0x00000507 +#if !defined(NO_CS4612) +#if !defined(NO_CS4615) +#define DREG_REGID_TRAP_WRITE_8 0x00000510 +#define DREG_REGID_TRAP_WRITE_9 0x00000511 +#define DREG_REGID_TRAP_WRITE_10 0x00000512 +#define DREG_REGID_TRAP_WRITE_11 0x00000513 +#define DREG_REGID_TRAP_WRITE_12 0x00000514 +#define DREG_REGID_TRAP_WRITE_13 0x00000515 +#define DREG_REGID_TRAP_WRITE_14 0x00000516 +#define DREG_REGID_TRAP_WRITE_15 0x00000517 +#define DREG_REGID_TRAP_WRITE_16 0x00000518 +#define DREG_REGID_TRAP_WRITE_17 0x00000519 +#define DREG_REGID_TRAP_WRITE_18 0x0000051A +#define DREG_REGID_TRAP_WRITE_19 0x0000051B +#define DREG_REGID_TRAP_WRITE_20 0x0000051C +#define DREG_REGID_TRAP_WRITE_21 0x0000051D +#define DREG_REGID_TRAP_WRITE_22 0x0000051E +#define DREG_REGID_TRAP_WRITE_23 0x0000051F +#endif +#endif +#define DREG_REGID_MAC0_ACC0_LOW 0x00000600 +#define DREG_REGID_MAC0_ACC1_LOW 0x00000601 +#define DREG_REGID_MAC0_ACC2_LOW 0x00000602 +#define DREG_REGID_MAC0_ACC3_LOW 0x00000603 +#define DREG_REGID_MAC1_ACC0_LOW 0x00000604 +#define DREG_REGID_MAC1_ACC1_LOW 0x00000605 +#define DREG_REGID_MAC1_ACC2_LOW 0x00000606 +#define DREG_REGID_MAC1_ACC3_LOW 0x00000607 +#define DREG_REGID_MAC0_ACC0_MID 0x00000608 +#define DREG_REGID_MAC0_ACC1_MID 0x00000609 +#define DREG_REGID_MAC0_ACC2_MID 0x0000060A +#define DREG_REGID_MAC0_ACC3_MID 0x0000060B +#define DREG_REGID_MAC1_ACC0_MID 0x0000060C +#define DREG_REGID_MAC1_ACC1_MID 0x0000060D +#define DREG_REGID_MAC1_ACC2_MID 0x0000060E +#define DREG_REGID_MAC1_ACC3_MID 0x0000060F +#define DREG_REGID_MAC0_ACC0_HIGH 0x00000610 +#define DREG_REGID_MAC0_ACC1_HIGH 0x00000611 +#define DREG_REGID_MAC0_ACC2_HIGH 0x00000612 +#define DREG_REGID_MAC0_ACC3_HIGH 0x00000613 +#define DREG_REGID_MAC1_ACC0_HIGH 0x00000614 +#define DREG_REGID_MAC1_ACC1_HIGH 0x00000615 +#define DREG_REGID_MAC1_ACC2_HIGH 0x00000616 +#define DREG_REGID_MAC1_ACC3_HIGH 0x00000617 +#define DREG_REGID_RSHOUT_LOW 0x00000620 +#define DREG_REGID_RSHOUT_MID 0x00000628 +#define DREG_REGID_RSHOUT_HIGH 0x00000630 + +/* + * The following defines are for the flags in the DMA stream requestor write + */ +#define DSRWP_DSR_MASK 0x0000000F +#define DSRWP_DSR_BG_RQ 0x00000001 +#define DSRWP_DSR_PRIORITY_MASK 0x00000006 +#define DSRWP_DSR_PRIORITY_0 0x00000000 +#define DSRWP_DSR_PRIORITY_1 0x00000002 +#define DSRWP_DSR_PRIORITY_2 0x00000004 +#define DSRWP_DSR_PRIORITY_3 0x00000006 +#define DSRWP_DSR_RQ_PENDING 0x00000008 + +/* + * The following defines are for the flags in the trap write port register. + */ +#define TWPR_TW_MASK 0x0000FFFF +#define TWPR_TW_SHIFT 0 + +/* + * The following defines are for the flags in the stack pointer write + * register. + */ +#define SPWR_STKP_MASK 0x0000000F +#define SPWR_STKP_SHIFT 0 + +/* + * The following defines are for the flags in the SP interrupt register. + */ +#define SPIR_FRI 0x00000001 +#define SPIR_DOI 0x00000002 +#define SPIR_GPI2 0x00000004 +#define SPIR_GPI3 0x00000008 +#define SPIR_IP0 0x00000010 +#define SPIR_IP1 0x00000020 +#define SPIR_IP2 0x00000040 +#define SPIR_IP3 0x00000080 + +/* + * The following defines are for the flags in the functional group 1 register. + */ +#define FGR1_F1S_MASK 0x0000FFFF +#define FGR1_F1S_SHIFT 0 + +/* + * The following defines are for the flags in the SP clock status register. + */ +#define SPCS_FRI 0x00000001 +#define SPCS_DOI 0x00000002 +#define SPCS_GPI2 0x00000004 +#define SPCS_GPI3 0x00000008 +#define SPCS_IP0 0x00000010 +#define SPCS_IP1 0x00000020 +#define SPCS_IP2 0x00000040 +#define SPCS_IP3 0x00000080 +#define SPCS_SPRUN 0x00000100 +#define SPCS_SLEEP 0x00000200 +#define SPCS_FG 0x00000400 +#define SPCS_ORUN 0x00000800 +#define SPCS_IRQ 0x00001000 +#define SPCS_FGN_MASK 0x0000E000 +#define SPCS_FGN_SHIFT 13 + +/* + * The following defines are for the flags in the SP DMA requestor status + * register. + */ +#define SDSR_DCS_MASK 0x000000FF +#define SDSR_DCS_SHIFT 0 +#define SDSR_DCS_NONE 0x00000007 + +/* + * The following defines are for the flags in the frame timer register. + */ +#define FRMT_FTV_MASK 0x0000FFFF +#define FRMT_FTV_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer current count + * register. + */ +#define FRCC_FCC_MASK 0x0000FFFF +#define FRCC_FCC_SHIFT 0 + +/* + * The following defines are for the flags in the frame timer save count + * register. + */ +#define FRSC_FCS_MASK 0x0000FFFF +#define FRSC_FCS_SHIFT 0 + +/* + * The following define the various flags stored in the scatter/gather + * descriptors. + */ +#define DMA_SG_NEXT_ENTRY_MASK 0x00000FF8 +#define DMA_SG_SAMPLE_END_MASK 0x0FFF0000 +#define DMA_SG_SAMPLE_END_FLAG 0x10000000 +#define DMA_SG_LOOP_END_FLAG 0x20000000 +#define DMA_SG_SIGNAL_END_FLAG 0x40000000 +#define DMA_SG_SIGNAL_PAGE_FLAG 0x80000000 +#define DMA_SG_NEXT_ENTRY_SHIFT 3 +#define DMA_SG_SAMPLE_END_SHIFT 16 + +/* + * The following define the offsets of the fields within the on-chip generic + * DMA requestor. + */ +#define DMA_RQ_CONTROL1 0x00000000 +#define DMA_RQ_CONTROL2 0x00000004 +#define DMA_RQ_SOURCE_ADDR 0x00000008 +#define DMA_RQ_DESTINATION_ADDR 0x0000000C +#define DMA_RQ_NEXT_PAGE_ADDR 0x00000010 +#define DMA_RQ_NEXT_PAGE_SGDESC 0x00000014 +#define DMA_RQ_LOOP_START_ADDR 0x00000018 +#define DMA_RQ_POST_LOOP_ADDR 0x0000001C +#define DMA_RQ_PAGE_MAP_ADDR 0x00000020 + +/* + * The following defines are for the flags in the first control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C1_COUNT_MASK 0x000003FF +#define DMA_RQ_C1_DESTINATION_SCATTER 0x00001000 +#define DMA_RQ_C1_SOURCE_GATHER 0x00002000 +#define DMA_RQ_C1_DONE_FLAG 0x00004000 +#define DMA_RQ_C1_OPTIMIZE_STATE 0x00008000 +#define DMA_RQ_C1_SAMPLE_END_STATE_MASK 0x00030000 +#define DMA_RQ_C1_FULL_PAGE 0x00000000 +#define DMA_RQ_C1_BEFORE_SAMPLE_END 0x00010000 +#define DMA_RQ_C1_PAGE_MAP_ERROR 0x00020000 +#define DMA_RQ_C1_AT_SAMPLE_END 0x00030000 +#define DMA_RQ_C1_LOOP_END_STATE_MASK 0x000C0000 +#define DMA_RQ_C1_NOT_LOOP_END 0x00000000 +#define DMA_RQ_C1_BEFORE_LOOP_END 0x00040000 +#define DMA_RQ_C1_2PAGE_LOOP_BEGIN 0x00080000 +#define DMA_RQ_C1_LOOP_BEGIN 0x000C0000 +#define DMA_RQ_C1_PAGE_MAP_MASK 0x00300000 +#define DMA_RQ_C1_PM_NONE_PENDING 0x00000000 +#define DMA_RQ_C1_PM_NEXT_PENDING 0x00100000 +#define DMA_RQ_C1_PM_RESERVED 0x00200000 +#define DMA_RQ_C1_PM_LOOP_NEXT_PENDING 0x00300000 +#define DMA_RQ_C1_WRITEBACK_DEST_FLAG 0x00400000 +#define DMA_RQ_C1_WRITEBACK_SRC_FLAG 0x00800000 +#define DMA_RQ_C1_DEST_SIZE_MASK 0x07000000 +#define DMA_RQ_C1_DEST_LINEAR 0x00000000 +#define DMA_RQ_C1_DEST_MOD16 0x01000000 +#define DMA_RQ_C1_DEST_MOD32 0x02000000 +#define DMA_RQ_C1_DEST_MOD64 0x03000000 +#define DMA_RQ_C1_DEST_MOD128 0x04000000 +#define DMA_RQ_C1_DEST_MOD256 0x05000000 +#define DMA_RQ_C1_DEST_MOD512 0x06000000 +#define DMA_RQ_C1_DEST_MOD1024 0x07000000 +#define DMA_RQ_C1_DEST_ON_HOST 0x08000000 +#define DMA_RQ_C1_SOURCE_SIZE_MASK 0x70000000 +#define DMA_RQ_C1_SOURCE_LINEAR 0x00000000 +#define DMA_RQ_C1_SOURCE_MOD16 0x10000000 +#define DMA_RQ_C1_SOURCE_MOD32 0x20000000 +#define DMA_RQ_C1_SOURCE_MOD64 0x30000000 +#define DMA_RQ_C1_SOURCE_MOD128 0x40000000 +#define DMA_RQ_C1_SOURCE_MOD256 0x50000000 +#define DMA_RQ_C1_SOURCE_MOD512 0x60000000 +#define DMA_RQ_C1_SOURCE_MOD1024 0x70000000 +#define DMA_RQ_C1_SOURCE_ON_HOST 0x80000000 +#define DMA_RQ_C1_COUNT_SHIFT 0 + +/* + * The following defines are for the flags in the second control word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_C2_VIRTUAL_CHANNEL_MASK 0x0000003F +#define DMA_RQ_C2_VIRTUAL_SIGNAL_MASK 0x00000300 +#define DMA_RQ_C2_NO_VIRTUAL_SIGNAL 0x00000000 +#define DMA_RQ_C2_SIGNAL_EVERY_DMA 0x00000100 +#define DMA_RQ_C2_SIGNAL_SOURCE_PINGPONG 0x00000200 +#define DMA_RQ_C2_SIGNAL_DEST_PINGPONG 0x00000300 +#define DMA_RQ_C2_AUDIO_CONVERT_MASK 0x0000F000 +#define DMA_RQ_C2_AC_NONE 0x00000000 +#define DMA_RQ_C2_AC_8_TO_16_BIT 0x00001000 +#define DMA_RQ_C2_AC_MONO_TO_STEREO 0x00002000 +#define DMA_RQ_C2_AC_ENDIAN_CONVERT 0x00004000 +#define DMA_RQ_C2_AC_SIGNED_CONVERT 0x00008000 +#define DMA_RQ_C2_LOOP_END_MASK 0x0FFF0000 +#define DMA_RQ_C2_LOOP_MASK 0x30000000 +#define DMA_RQ_C2_NO_LOOP 0x00000000 +#define DMA_RQ_C2_ONE_PAGE_LOOP 0x10000000 +#define DMA_RQ_C2_TWO_PAGE_LOOP 0x20000000 +#define DMA_RQ_C2_MULTI_PAGE_LOOP 0x30000000 +#define DMA_RQ_C2_SIGNAL_LOOP_BACK 0x40000000 +#define DMA_RQ_C2_SIGNAL_POST_BEGIN_PAGE 0x80000000 +#define DMA_RQ_C2_VIRTUAL_CHANNEL_SHIFT 0 +#define DMA_RQ_C2_LOOP_END_SHIFT 16 + +/* + * The following defines are for the flags in the source and destination words + * of the on-chip generic DMA requestor. + */ +#define DMA_RQ_SD_ADDRESS_MASK 0x0000FFFF +#define DMA_RQ_SD_MEMORY_ID_MASK 0x000F0000 +#define DMA_RQ_SD_SP_PARAM_ADDR 0x00000000 +#define DMA_RQ_SD_SP_SAMPLE_ADDR 0x00010000 +#define DMA_RQ_SD_SP_PROGRAM_ADDR 0x00020000 +#define DMA_RQ_SD_SP_DEBUG_ADDR 0x00030000 +#define DMA_RQ_SD_OMNIMEM_ADDR 0x000E0000 +#define DMA_RQ_SD_END_FLAG 0x40000000 +#define DMA_RQ_SD_ERROR_FLAG 0x80000000 +#define DMA_RQ_SD_ADDRESS_SHIFT 0 + +/* + * The following defines are for the flags in the page map address word of the + * on-chip generic DMA requestor. + */ +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_MASK 0x00000FF8 +#define DMA_RQ_PMA_PAGE_TABLE_MASK 0xFFFFF000 +#define DMA_RQ_PMA_LOOP_THIRD_PAGE_ENTRY_SHIFT 3 +#define DMA_RQ_PMA_PAGE_TABLE_SHIFT 12 + +#define BA1_VARIDEC_BUF_1 0x000 + +#define BA1_PDTC 0x0c0 /* BA1_PLAY_DMA_TRANSACTION_COUNT_REG */ +#define BA1_PFIE 0x0c4 /* BA1_PLAY_FORMAT_&_INTERRUPT_ENABLE_REG */ +#define BA1_PBA 0x0c8 /* BA1_PLAY_BUFFER_ADDRESS */ +#define BA1_PVOL 0x0f8 /* BA1_PLAY_VOLUME_REG */ +#define BA1_PSRC 0x288 /* BA1_PLAY_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_PCTL 0x2a4 /* BA1_PLAY_CONTROL_REG */ +#define BA1_PPI 0x2b4 /* BA1_PLAY_PHASE_INCREMENT_REG */ + +#define BA1_CCTL 0x064 /* BA1_CAPTURE_CONTROL_REG */ +#define BA1_CIE 0x104 /* BA1_CAPTURE_INTERRUPT_ENABLE_REG */ +#define BA1_CBA 0x10c /* BA1_CAPTURE_BUFFER_ADDRESS */ +#define BA1_CSRC 0x2c8 /* BA1_CAPTURE_SAMPLE_RATE_CORRECTION_REG */ +#define BA1_CCI 0x2d8 /* BA1_CAPTURE_COEFFICIENT_INCREMENT_REG */ +#define BA1_CD 0x2e0 /* BA1_CAPTURE_DELAY_REG */ +#define BA1_CPI 0x2f4 /* BA1_CAPTURE_PHASE_INCREMENT_REG */ +#define BA1_CVOL 0x2f8 /* BA1_CAPTURE_VOLUME_REG */ + +#define BA1_CFG1 0x134 /* BA1_CAPTURE_FRAME_GROUP_1_REG */ +#define BA1_CFG2 0x138 /* BA1_CAPTURE_FRAME_GROUP_2_REG */ +#define BA1_CCST 0x13c /* BA1_CAPTURE_CONSTANT_REG */ +#define BA1_CSPB 0x340 /* BA1_CAPTURE_SPB_ADDRESS */ + +/* + * + */ + +#define CS46XX_MODE_OUTPUT (1<<0) /* MIDI UART - output */ +#define CS46XX_MODE_INPUT (1<<1) /* MIDI UART - input */ + +/* + * + */ + +#define SAVE_REG_MAX 0x10 +#define POWER_DOWN_ALL 0x7f0f + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define MAX_NR_AC97 4 +#define CS46XX_PRIMARY_CODEC_INDEX 0 +#define CS46XX_SECONDARY_CODEC_INDEX 1 +#define CS46XX_SECONDARY_CODEC_OFFSET 0x80 +#define CS46XX_DSP_CAPTURE_CHANNEL 1 + +/* capture */ +#define CS46XX_DSP_CAPTURE_CHANNEL 1 + +/* mixer */ +#define CS46XX_MIXER_SPDIF_INPUT_ELEMENT 1 +#define CS46XX_MIXER_SPDIF_OUTPUT_ELEMENT 2 + + +struct snd_cs46xx_pcm { + struct snd_dma_buffer hw_buf; + + unsigned int ctl; + unsigned int shift; /* Shift count to trasform frames in bytes */ + struct snd_pcm_indirect pcm_rec; + struct snd_pcm_substream *substream; + + struct dsp_pcm_channel_descriptor * pcm_channel; + + int pcm_channel_id; /* Fron Rear, Center Lfe ... */ +}; + +struct snd_cs46xx_region { + char name[24]; + unsigned long base; + void __iomem *remap_addr; + unsigned long size; + struct 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..080857ad0 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_scb_types.h @@ -0,0 +1,1213 @@ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * NOTE: comments are copy/paste from cwcemb80.lst + * provided by Tom Woller at Cirrus (my only + * documentation about the SP OS running inside + * the DSP) + */ + +#ifndef __CS46XX_DSP_SCB_TYPES_H__ +#define __CS46XX_DSP_SCB_TYPES_H__ + +#include + +#ifndef ___DSP_DUAL_16BIT_ALLOC +#if defined(__LITTLE_ENDIAN) +#define ___DSP_DUAL_16BIT_ALLOC(a,b) u16 a; u16 b; +#elif defined(__BIG_ENDIAN) +#define ___DSP_DUAL_16BIT_ALLOC(a,b) u16 b; u16 a; +#else +#error Not __LITTLE_ENDIAN and not __BIG_ENDIAN, then what ??? +#endif +#endif + +/* This structs are used internally by the SP */ + +struct dsp_basic_dma_req { + /* DMA Requestor Word 0 (DCW) fields: + + 31 [30-28]27 [26:24] 23 22 21 20 [19:18] [17:16] 15 14 13 12 11 10 9 8 7 6 [5:0] + _______________________________________________________________________________________ + |S| SBT |D| DBT |wb|wb| | | LS | SS |Opt|Do|SSG|DSG| | | | | | | Dword | + |H|_____ |H|_________|S_|D |__|__|______|_______|___|ne|__ |__ |__|__|_|_|_|_|_Count -1| + */ + u32 dcw; /* DMA Control Word */ + u32 dmw; /* DMA Mode Word */ + u32 saw; /* Source Address Word */ + u32 daw; /* Destination Address Word */ +}; + +struct dsp_scatter_gather_ext { + u32 npaw; /* Next-Page Address Word */ + + /* DMA Requestor Word 5 (NPCW) fields: + + 31-30 29 28 [27:16] [15:12] [11:3] [2:0] + _________________________________________________________________________________________ + |SV |LE|SE| Sample-end byte offset | | Page-map entry offset for next | | + |page|__|__| ___________________________|_________|__page, if !sample-end___________|____| + */ + u32 npcw; /* Next-Page Control Word */ + u32 lbaw; /* Loop-Begin Address Word */ + u32 nplbaw; /* Next-Page after Loop-Begin Address Word */ + u32 sgaw; /* Scatter/Gather Address Word */ +}; + +struct dsp_volume_control { + ___DSP_DUAL_16BIT_ALLOC( + rightTarg, /* Target volume for left & right channels */ + leftTarg + ) + ___DSP_DUAL_16BIT_ALLOC( + rightVol, /* Current left & right channel volumes */ + leftVol + ) +}; + +/* Generic stream control block (SCB) structure definition */ +struct dsp_generic_scb { + /* For streaming I/O, the DSP should never alter any words in the DMA + requestor or the scatter/gather extension. Only ad hoc DMA request + streams are free to alter the requestor (currently only occur in the + DOS-based MIDI controller and in debugger-inserted code). + + If an SCB does not have any associated DMA requestor, these 9 ints + may be freed for use by other tasks, but the pointer to the SCB must + still be such that the insOrd:nextSCB appear at offset 9 from the + SCB pointer. + + Basic (non scatter/gather) DMA requestor (4 ints) + */ + + /* Initialized by the host, only modified by DMA + R/O for the DSP task */ + struct dsp_basic_dma_req basic_req; /* Optional */ + + /* Scatter/gather DMA requestor extension (5 ints) + Initialized by the host, only modified by DMA + DSP task never needs to even read these. + */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + + /* Sublist pointer & next stream control block (SCB) link. + Initialized & modified by the host R/O for the DSP task + */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + /* Pointer to this tasks parameter block & stream function pointer + Initialized by the host R/O for the DSP task */ + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + /* rsConfig register for stream buffer (rsDMA reg. + is loaded from basicReq.daw for incoming streams, or + basicReq.saw, for outgoing streams) + + 31 30 29 [28:24] [23:16] 15 14 13 12 11 10 9 8 7 6 5 4 [3:0] + ______________________________________________________________________________ + |DMA |D|maxDMAsize| streamNum|dir|p| | | | | | |ds |shr 1|rev Cy | mod | + |prio |_|__________|__________|___|_|__|__|__|__|_|_|___|_____|_______|_______| + 31 30 29 [28:24] [23:16] 15 14 13 12 11 10 9 8 7 6 5 4 [3:0] + + + Initialized by the host R/O for the DSP task + */ + u32 strm_rs_config; /* REQUIRED */ + // + /* On mixer input streams: indicates mixer input stream configuration + On Tees, this is copied from the stream being snooped + + Stream sample pointer & MAC-unit mode for this stream + + Initialized by the host Updated by the DSP task + */ + u32 strm_buf_ptr; /* REQUIRED */ + + /* On mixer input streams: points to next mixer input and is updated by the + mixer subroutine in the "parent" DSP task + (least-significant 16 bits are preserved, unused) + + On Tees, the pointer is copied from the stream being snooped on + initialization, and, subsequently, it is copied into the + stream being snooped. + + On wavetable/3D voices: the strmBufPtr will use all 32 bits to allow for + fractional phase accumulation + + Fractional increment per output sample in the input sample buffer + + (Not used on mixer input streams & redefined on Tees) + On wavetable/3D voices: this 32-bit word specifies the integer.fractional + increment per output sample. + */ + u32 strmPhiIncr; + + + /* Standard stereo volume control + Initialized by the host (host updates target volumes) + + Current volumes update by the DSP task + On mixer input streams: required & updated by the mixer subroutine in the + "parent" DSP task + + On Tees, both current & target volumes are copied up on initialization, + and, subsequently, the target volume is copied up while the current + volume is copied down. + + These two 32-bit words are redefined for wavetable & 3-D voices. + */ + struct dsp_volume_control vol_ctrl_t; /* Optional */ +}; + + +struct dsp_spos_control_block { + /* WARNING: Certain items in this structure are modified by the host + Any dword that can be modified by the host, must not be + modified by the SP as the host can only do atomic dword + writes, and to do otherwise, even a read modify write, + may lead to corrupted data on the SP. + + This rule does not apply to one off boot time initialisation prior to starting the SP + */ + + + ___DSP_DUAL_16BIT_ALLOC( + /* First element on the Hyper forground task tree */ + hfg_tree_root_ptr, /* HOST */ + /* First 3 dwords are written by the host and read-only on the DSP */ + hfg_stack_base /* HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Point to this data structure to enable easy access */ + spos_cb_ptr, /* SP */ + prev_task_tree_ptr /* SP && HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Currently Unused */ + xxinterval_timer_period, + /* Enable extension of SPOS data structure */ + HFGSPB_ptr + ) + + + ___DSP_DUAL_16BIT_ALLOC( + xxnum_HFG_ticks_thisInterval, + /* Modified by the DSP */ + xxnum_tntervals + ) + + + /* Set by DSP upon encountering a trap (breakpoint) or a spurious + interrupt. The host must clear this dword after reading it + upon receiving spInt1. */ + ___DSP_DUAL_16BIT_ALLOC( + spurious_int_flag, /* (Host & SP) Nature of the spurious interrupt */ + trap_flag /* (Host & SP) Nature of detected Trap */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused2, + invalid_IP_flag /* (Host & SP ) Indicate detection of invalid instruction pointer */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* pointer to forground task tree header for use in next task search */ + fg_task_tree_hdr_ptr, /* HOST */ + /* Data structure for controlling synchronous link update */ + hfg_sync_update_ptr /* HOST */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + begin_foreground_FCNT, /* SP */ + /* Place holder for holding sleep timing */ + last_FCNT_before_sleep /* SP */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused7, /* SP */ + next_task_treePtr /* SP */ + ) + + u32 unused5; + + ___DSP_DUAL_16BIT_ALLOC( + active_flags, /* SP */ + /* State flags, used to assist control of execution of Hyper Forground */ + HFG_flags /* SP */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused9, + unused8 + ) + + /* Space for saving enough context so that we can set up enough + to save some more context. + */ + u32 rFE_save_for_invalid_IP; + u32 r32_save_for_spurious_int; + u32 r32_save_for_trap; + u32 r32_save_for_HFG; +}; + +/* SPB for MIX_TO_OSTREAM algorithm family */ +struct dsp_mix2_ostream_spb +{ + /* 16b.16b integer.frac approximation to the + number of 3 sample triplets to output each + frame. (approximation must be floor, to + insure that the fractional error is always + positive) + */ + u32 outTripletsPerFrame; + + /* 16b.16b integer.frac accumulated number of + output triplets since the start of group + */ + u32 accumOutTriplets; +}; + +/* SCB for Timing master algorithm */ +struct dsp_timing_master_scb { + /* First 12 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + /* Initial values are 0000:xxxx */ + reserved, + extra_sample_accum + ) + + + /* Initial values are xxxx:0000 + hi: Current CODEC output FIFO pointer + (0 to 0x0f) + lo: Flag indicating that the CODEC + FIFO is sync'd (host clears to + resynchronize the FIFO pointer + upon start/restart) + */ + ___DSP_DUAL_16BIT_ALLOC( + codec_FIFO_syncd, + codec_FIFO_ptr + ) + + /* Init. 8000:0005 for 44.1k + 8000:0001 for 48k + hi: Fractional sample accumulator 0.16b + lo: Number of frames remaining to be + processed in the current group of + frames + */ + ___DSP_DUAL_16BIT_ALLOC( + frac_samp_accum_qm1, + TM_frms_left_in_group + ) + + /* Init. 0001:0005 for 44.1k + 0000:0001 for 48k + hi: Fractional sample correction factor 0.16b + to be added every frameGroupLength frames + to correct for truncation error in + nsamp_per_frm_q15 + lo: Number of frames in the group + */ + ___DSP_DUAL_16BIT_ALLOC( + frac_samp_correction_qm1, + TM_frm_group_length + ) + + /* Init. 44.1k*65536/8k = 0x00058333 for 44.1k + 48k*65536/8k = 0x00060000 for 48k + 16b.16b integer.frac approximation to the + number of samples to output each frame. + (approximation must be floor, to insure */ + u32 nsamp_per_frm_q15; +}; + +/* SCB for CODEC output algorithm */ +struct dsp_codec_output_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + + u32 strm_buf_ptr; /* REQUIRED */ + + /* NOTE: The CODEC output task reads samples from the first task on its + sublist at the stream buffer pointer (init. to lag DMA destination + address word). After the required number of samples is transferred, + the CODEC output task advances sub_list_ptr->strm_buf_ptr past the samples + consumed. + */ + + /* Init. 0000:0010 for SDout + 0060:0010 for SDout2 + 0080:0010 for SDout3 + hi: Base IO address of FIFO to which + the left-channel samples are to + be written. + lo: Displacement for the base IO + address for left-channel to obtain + the base IO address for the FIFO + to which the right-channel samples + are to be written. + */ + ___DSP_DUAL_16BIT_ALLOC( + left_chan_base_IO_addr, + right_chan_IO_disp + ) + + + /* Init: 0x0080:0004 for non-AC-97 + Init: 0x0080:0000 for AC-97 + hi: Exponential volume change rate + for input stream + lo: Positive shift count to shift the + 16-bit input sample to obtain the + 32-bit output word + */ + ___DSP_DUAL_16BIT_ALLOC( + CO_scale_shift_count, + CO_exp_vol_change_rate + ) + + /* Pointer to SCB at end of input chain */ + ___DSP_DUAL_16BIT_ALLOC( + reserved, + last_sub_ptr + ) +}; + +/* SCB for CODEC input algorithm */ +struct dsp_codec_input_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + /* NOTE: The CODEC input task reads samples from the hardware FIFO + sublist at the DMA source address word (sub_list_ptr->basic_req.saw). + After the required number of samples is transferred, the CODEC + output task advances sub_list_ptr->basic_req.saw past the samples + consumed. SPuD must initialize the sub_list_ptr->basic_req.saw + to point half-way around from the initial sub_list_ptr->strm_nuf_ptr + to allow for lag/lead. + */ + + /* Init. 0000:0010 for SDout + 0060:0010 for SDout2 + 0080:0010 for SDout3 + hi: Base IO address of FIFO to which + the left-channel samples are to + be written. + lo: Displacement for the base IO + address for left-channel to obtain + the base IO address for the FIFO + to which the right-channel samples + are to be written. + */ + ___DSP_DUAL_16BIT_ALLOC( + rightChanINdisp, + left_chan_base_IN_addr + ) + /* Init. ?:fffc + lo: Negative shift count to shift the + 32-bit input dword to obtain the + 16-bit sample msb-aligned (count + is negative to shift left) + */ + ___DSP_DUAL_16BIT_ALLOC( + scaleShiftCount, + reserver1 + ) + + u32 reserved2; +}; + + +struct dsp_pcm_serial_input_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_buf_ptr; /* REQUIRED */ + u32 strm_rs_config; /* REQUIRED */ + + /* Init. Ptr to CODEC input SCB + hi: Pointer to the SCB containing the + input buffer to which CODEC input + samples are written + lo: Flag indicating the link to the CODEC + input task is to be initialized + */ + ___DSP_DUAL_16BIT_ALLOC( + init_codec_input_link, + codec_input_buf_scb + ) + + /* Initialized by the host (host updates target volumes) */ + struct dsp_volume_control psi_vol_ctrl; + +}; + +struct dsp_src_task_scb { + ___DSP_DUAL_16BIT_ALLOC( + frames_left_in_gof, + gofs_left_in_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + const2_thirds, + num_extra_tnput_samples + ) + + ___DSP_DUAL_16BIT_ALLOC( + cor_per_gof, + correction_per_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + output_buf_producer_ptr, + junk_DMA_MID + ) + + ___DSP_DUAL_16BIT_ALLOC( + gof_length, + gofs_per_sec + ) + + u32 input_buf_strm_config; + + ___DSP_DUAL_16BIT_ALLOC( + reserved_for_SRC_use, + input_buf_consumer_ptr + ) + + u32 accum_phi; + + ___DSP_DUAL_16BIT_ALLOC( + exp_src_vol_change_rate, + input_buf_producer_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + src_next_scb, + src_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + src_entry_point, + src_this_sbp + ) + + u32 src_strm_rs_config; + u32 src_strm_buf_ptr; + + u32 phiIncr6int_26frac; + + struct dsp_volume_control src_vol_ctrl; +}; + +struct dsp_decimate_by_pow2_scb { + /* decimationFactor = 2, 4, or 8 (larger factors waste too much memory + when compared to cascading decimators) + */ + ___DSP_DUAL_16BIT_ALLOC( + dec2_coef_base_ptr, + dec2_coef_increment + ) + + /* coefIncrement = 128 / decimationFactor (for our ROM filter) + coefBasePtr = 0x8000 (for our ROM filter) + */ + ___DSP_DUAL_16BIT_ALLOC( + dec2_in_samples_per_out_triplet, + dec2_extra_in_samples + ) + /* extraInSamples: # of accumulated, unused input samples (init. to 0) + inSamplesPerOutTriplet = 3 * decimationFactor + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_const2_thirds, + dec2_half_num_taps_mp5 + ) + /* halfNumTapsM5: (1/2 number of taps in decimation filter) minus 5 + const2thirds: constant 2/3 in 16Q0 format (sign.15) + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_output_buf_producer_ptr, + dec2_junkdma_mid + ) + + u32 dec2_reserved2; + + u32 dec2_input_nuf_strm_config; + /* inputBufStrmConfig: rsConfig for the input buffer to the decimator + (buffer size = decimationFactor * 32 dwords) + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_phi_incr, + dec2_input_buf_consumer_ptr + ) + /* inputBufConsumerPtr: Input buffer read pointer (into SRC filter) + phiIncr = decimationFactor * 4 + */ + + u32 dec2_reserved3; + + ___DSP_DUAL_16BIT_ALLOC( + dec2_exp_vol_change_rate, + dec2_input_buf_producer_ptr + ) + /* inputBufProducerPtr: Input buffer write pointer + expVolChangeRate: Exponential volume change rate for possible + future mixer on input streams + */ + + ___DSP_DUAL_16BIT_ALLOC( + dec2_next_scb, + dec2_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + dec2_entry_point, + dec2_this_spb + ) + + u32 dec2_strm_rs_config; + u32 dec2_strm_buf_ptr; + + u32 dec2_reserved4; + + struct dsp_volume_control dec2_vol_ctrl; /* Not used! */ +}; + +struct dsp_vari_decimate_scb { + ___DSP_DUAL_16BIT_ALLOC( + vdec_frames_left_in_gof, + vdec_gofs_left_in_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_const2_thirds, + vdec_extra_in_samples + ) + /* extraInSamples: # of accumulated, unused input samples (init. to 0) + const2thirds: constant 2/3 in 16Q0 format (sign.15) */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_cor_per_gof, + vdec_correction_per_sec + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_output_buf_producer_ptr, + vdec_input_buf_consumer_ptr + ) + /* inputBufConsumerPtr: Input buffer read pointer (into SRC filter) */ + ___DSP_DUAL_16BIT_ALLOC( + vdec_gof_length, + vdec_gofs_per_sec + ) + + u32 vdec_input_buf_strm_config; + /* inputBufStrmConfig: rsConfig for the input buffer to the decimator + (buffer size = 64 dwords) */ + u32 vdec_coef_increment; + /* coefIncrement = - 128.0 / decimationFactor (as a 32Q15 number) */ + + u32 vdec_accumphi; + /* accumPhi: accumulated fractional phase increment (6.26) */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_exp_vol_change_rate, + vdec_input_buf_producer_ptr + ) + /* inputBufProducerPtr: Input buffer write pointer + expVolChangeRate: Exponential volume change rate for possible + future mixer on input streams */ + + ___DSP_DUAL_16BIT_ALLOC( + vdec_next_scb, + vdec_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + vdec_entry_point, + vdec_this_spb + ) + + u32 vdec_strm_rs_config; + u32 vdec_strm_buf_ptr; + + u32 vdec_phi_incr_6int_26frac; + + struct dsp_volume_control vdec_vol_ctrl; +}; + + +/* SCB for MIX_TO_OSTREAM algorithm family */ +struct dsp_mix2_ostream_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + + /* hi: Number of mixed-down input triplets + computed since start of group + lo: Number of frames remaining to be + processed in the current group of + frames + */ + ___DSP_DUAL_16BIT_ALLOC( + frames_left_in_group, + accum_input_triplets + ) + + /* hi: Exponential volume change rate + for mixer on input streams + lo: Number of frames in the group + */ + ___DSP_DUAL_16BIT_ALLOC( + frame_group_length, + exp_vol_change_rate + ) + + ___DSP_DUAL_16BIT_ALLOC( + const_FFFF, + const_zero + ) +}; + + +/* SCB for S16_MIX algorithm */ +struct dsp_mix_only_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + u32 reserved; + struct dsp_volume_control vol_ctrl; +}; + +/* SCB for the async. CODEC input algorithm */ +struct dsp_async_codec_input_scb { + u32 io_free2; + + u32 io_current_total; + u32 io_previous_total; + + u16 io_count; + u16 io_count_limit; + + u16 o_fifo_base_addr; + u16 ost_mo_format; + /* 1 = stereo; 0 = mono + xxx for ASER 1 (not allowed); 118 for ASER2 */ + + u32 ostrm_rs_config; + u32 ostrm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + io_sclks_per_lr_clk, + io_io_enable + ) + + u32 io_free4; + + ___DSP_DUAL_16BIT_ALLOC( + io_next_scb, + io_sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + io_entry_point, + io_this_spb + ) + + u32 istrm_rs_config; + u32 istrm_buf_ptr; + + /* Init. 0000:8042: for ASER1 + 0000:8044: for ASER2 */ + ___DSP_DUAL_16BIT_ALLOC( + io_stat_reg_addr, + iofifo_pointer + ) + + /* Init 1 stero:100 ASER1 + Init 0 mono:110 ASER2 + */ + ___DSP_DUAL_16BIT_ALLOC( + ififo_base_addr, + ist_mo_format + ) + + u32 i_free; +}; + + +/* SCB for the SP/DIF CODEC input and output */ +struct dsp_spdifiscb { + ___DSP_DUAL_16BIT_ALLOC( + status_ptr, + status_start_ptr + ) + + u32 current_total; + u32 previous_total; + + ___DSP_DUAL_16BIT_ALLOC( + count, + count_limit + ) + + u32 status_data; + + ___DSP_DUAL_16BIT_ALLOC( + status, + free4 + ) + + u32 free3; + + ___DSP_DUAL_16BIT_ALLOC( + free2, + bit_count + ) + + u32 temp_status; + + ___DSP_DUAL_16BIT_ALLOC( + next_SCB, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_spb + ) + + u32 strm_rs_config; + u32 strm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + stat_reg_addr, + fifo_pointer + ) + + ___DSP_DUAL_16BIT_ALLOC( + fifo_base_addr, + st_mo_format + ) + + u32 free1; +}; + + +/* SCB for the SP/DIF CODEC input and output */ +struct dsp_spdifoscb { + + u32 free2; + + u32 free3[4]; + + /* Need to be here for compatibility with AsynchFGTxCode */ + u32 strm_rs_config; + + u32 strm_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + status, + free5 + ) + + u32 free4; + + ___DSP_DUAL_16BIT_ALLOC( + next_scb, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_spb + ) + + u32 free6[2]; + + ___DSP_DUAL_16BIT_ALLOC( + stat_reg_addr, + fifo_pointer + ) + + ___DSP_DUAL_16BIT_ALLOC( + fifo_base_addr, + st_mo_format + ) + + u32 free1; +}; + + +struct dsp_asynch_fg_rx_scb { + ___DSP_DUAL_16BIT_ALLOC( + bot_buf_mask, + buf_Mask + ) + + ___DSP_DUAL_16BIT_ALLOC( + max, + min + ) + + ___DSP_DUAL_16BIT_ALLOC( + old_producer_pointer, + hfg_scb_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + delta, + adjust_count + ) + + u32 unused2[5]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + code_ptr, + this_ptr + ) + + u32 strm_rs_config; + + u32 strm_buf_ptr; + + u32 unused_phi_incr; + + ___DSP_DUAL_16BIT_ALLOC( + right_targ, + left_targ + ) + + ___DSP_DUAL_16BIT_ALLOC( + right_vol, + left_vol + ) +}; + + +struct dsp_asynch_fg_tx_scb { + ___DSP_DUAL_16BIT_ALLOC( + not_buf_mask, + buf_mask + ) + + ___DSP_DUAL_16BIT_ALLOC( + max, + min + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused1, + hfg_scb_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + delta, + adjust_count + ) + + u32 accum_phi; + + ___DSP_DUAL_16BIT_ALLOC( + unused2, + const_one_third + ) + + u32 unused3[3]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + codePtr, + this_ptr + ) + + u32 strm_rs_config; + + u32 strm_buf_ptr; + + u32 phi_incr; + + ___DSP_DUAL_16BIT_ALLOC( + unused_right_targ, + unused_left_targ + ) + + ___DSP_DUAL_16BIT_ALLOC( + unused_right_vol, + unused_left_vol + ) +}; + + +struct dsp_output_snoop_scb { + /* First 13 dwords from generic_scb_t */ + struct dsp_basic_dma_req basic_req; /* Optional */ + struct dsp_scatter_gather_ext sg_ext; /* Optional */ + ___DSP_DUAL_16BIT_ALLOC( + next_scb, /* REQUIRED */ + sub_list_ptr /* REQUIRED */ + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* REQUIRED */ + this_spb /* REQUIRED */ + ) + + u32 strm_rs_config; /* REQUIRED */ + u32 strm_buf_ptr; /* REQUIRED */ + + ___DSP_DUAL_16BIT_ALLOC( + init_snoop_input_link, + snoop_child_input_scb + ) + + u32 snoop_input_buf_ptr; + + ___DSP_DUAL_16BIT_ALLOC( + reserved, + input_scb + ) +}; + +struct dsp_spio_write_scb { + ___DSP_DUAL_16BIT_ALLOC( + address1, + address2 + ) + + u32 data1; + + u32 data2; + + ___DSP_DUAL_16BIT_ALLOC( + address3, + address4 + ) + + u32 data3; + + u32 data4; + + ___DSP_DUAL_16BIT_ALLOC( + unused1, + data_ptr + ) + + u32 unused2[2]; + + ___DSP_DUAL_16BIT_ALLOC( + sibling_ptr, + child_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_ptr + ) + + u32 unused3[5]; +}; + +struct dsp_magic_snoop_task { + u32 i0; + u32 i1; + + u32 strm_buf_ptr1; + + u16 i2; + u16 snoop_scb; + + u32 i3; + u32 i4; + u32 i5; + u32 i6; + + u32 i7; + + ___DSP_DUAL_16BIT_ALLOC( + next_scb, + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, + this_ptr + ) + + u32 strm_buf_config; + u32 strm_buf_ptr2; + + u32 i8; + + struct dsp_volume_control vdec_vol_ctrl; +}; + + +struct dsp_filter_scb { + ___DSP_DUAL_16BIT_ALLOC( + a0_right, /* 0x00 */ + a0_left + ) + ___DSP_DUAL_16BIT_ALLOC( + a1_right, /* 0x01 */ + a1_left + ) + ___DSP_DUAL_16BIT_ALLOC( + a2_right, /* 0x02 */ + a2_left + ) + ___DSP_DUAL_16BIT_ALLOC( + output_buf_ptr, /* 0x03 */ + init + ) + + ___DSP_DUAL_16BIT_ALLOC( + filter_unused3, /* 0x04 */ + filter_unused2 + ) + + u32 prev_sample_output1; /* 0x05 */ + u32 prev_sample_output2; /* 0x06 */ + u32 prev_sample_input1; /* 0x07 */ + u32 prev_sample_input2; /* 0x08 */ + + ___DSP_DUAL_16BIT_ALLOC( + next_scb_ptr, /* 0x09 */ + sub_list_ptr + ) + + ___DSP_DUAL_16BIT_ALLOC( + entry_point, /* 0x0A */ + spb_ptr + ) + + u32 strm_rs_config; /* 0x0B */ + u32 strm_buf_ptr; /* 0x0C */ + + ___DSP_DUAL_16BIT_ALLOC( + b0_right, /* 0x0D */ + b0_left + ) + ___DSP_DUAL_16BIT_ALLOC( + b1_right, /* 0x0E */ + b1_left + ) + ___DSP_DUAL_16BIT_ALLOC( + b2_right, /* 0x0F */ + b2_left + ) +}; +#endif /* __DSP_SCB_TYPES_H__ */ diff --git a/sound/pci/cs46xx/cs46xx_dsp_spos.h b/sound/pci/cs46xx/cs46xx_dsp_spos.h new file mode 100644 index 000000000..8008c5928 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_spos.h @@ -0,0 +1,234 @@ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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; + struct snd_info_entry * proc_sym_info_entry; + struct snd_info_entry * proc_modules_info_entry; + struct snd_info_entry * proc_parameter_dump_info_entry; + struct snd_info_entry * proc_sample_dump_info_entry; + + /* SCB's descriptors */ + int nscb; + int scb_highest_frag_index; + struct dsp_scb_descriptor scbs[DSP_MAX_SCB_DESC]; + struct snd_info_entry * proc_scb_info_entry; + struct dsp_scb_descriptor * the_null_scb; + + /* Task's descriptors */ + int ntask; + struct dsp_task_descriptor tasks[DSP_MAX_TASK_DESC]; + struct snd_info_entry * proc_task_info_entry; + + /* 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..be5694718 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_dsp_task_types.h @@ -0,0 +1,252 @@ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * 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..419da70cd --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -0,0 +1,4052 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Abramo Bagnara + * Cirrus Logic, Inc. + * Routines for control of Cirrus Logic CS461x chips + * + * KNOWN BUGS: + * - Sometimes the SPDIF input DSP tasks get's unsynchronized + * and the SPDIF get somewhat "distorcionated", or/and left right channel + * are swapped. To get around this problem when it happens, mute and unmute + * the SPDIF input mixer control. + * - On the Hercules Game Theater XP the amplifier are sometimes turned + * off on inadecuate moments which causes distorcions on sound. + * + * TODO: + * - Secondary CODEC on some soundcards + * - SPDIF input support for other sample rates then 48khz + * - Posibility to mix the SPDIF output with analog sources. + * - PCM channels for Center and LFE on secondary codec + * + * NOTE: with CONFIG_SND_CS46XX_NEW_DSP unset uses old DSP image (which + * is default configuration), no SPDIF, no secondary codec, no + * multi channel PCM. But known to work. + * + * FINALLY: A credit to the developers Tom and Jordan + * at Cirrus for have helping me out with the DSP, however we + * still don't have sufficient documentation and technical + * references to be able to implement all fancy feutures + * supported by the cs46xx DSP's. + * Benny + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +static void amp_voyetra(struct snd_cs46xx *chip, int change); + +#ifdef CONFIG_SND_CS46XX_NEW_DSP +static const struct snd_pcm_ops snd_cs46xx_playback_rear_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_clfe_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_iec958_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops; +#endif + +static const struct snd_pcm_ops snd_cs46xx_playback_ops; +static const struct snd_pcm_ops snd_cs46xx_playback_indirect_ops; +static const struct snd_pcm_ops snd_cs46xx_capture_ops; +static const struct snd_pcm_ops snd_cs46xx_capture_indirect_ops; + +static unsigned short snd_cs46xx_codec_read(struct snd_cs46xx *chip, + unsigned short reg, + int codec_index) +{ + int count; + unsigned short result,tmp; + u32 offset = 0; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return 0xffff; + + chip->active_ctrl(chip, 1); + + if (codec_index == CS46XX_SECONDARY_CODEC_INDEX) + offset = CS46XX_SECONDARY_CODEC_OFFSET; + + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write7---55 + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 17h + * 5. if DCV not cleared, break and return error + * 6. Read ACSTS = Status Register = 464h, check VSTS bit + */ + + snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset); + + tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL); + if ((tmp & ACCTL_VFRM) == 0) { + dev_warn(chip->card->dev, "ACCTL_VFRM not set 0x%x\n", tmp); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, (tmp & (~ACCTL_ESYN)) | ACCTL_VFRM ); + msleep(50); + tmp = snd_cs46xx_peekBA0(chip, BA0_ACCTL + offset); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, tmp | ACCTL_ESYN | ACCTL_VFRM ); + + } + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * set CRW - Read command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + + snd_cs46xx_pokeBA0(chip, BA0_ACCAD, reg); + snd_cs46xx_pokeBA0(chip, BA0_ACCDA, 0); + if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL,/* clear ACCTL_DCV */ ACCTL_CRW | + ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | + ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + } else { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC | + ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + } + + /* + * Wait for the read to occur. + */ + for (count = 0; count < 1000; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10); + /* + * Now, check to see if the read has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 17h + */ + if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV)) + goto ok1; + } + + dev_err(chip->card->dev, + "AC'97 read problem (ACCTL_DCV), reg = 0x%x\n", reg); + result = 0xffff; + goto end; + + ok1: + /* + * Wait for the valid status bit to go active. + */ + for (count = 0; count < 100; count++) { + /* + * Read the AC97 status register. + * ACSTS = Status Register = 464h + * VSTS - Valid Status + */ + if (snd_cs46xx_peekBA0(chip, BA0_ACSTS + offset) & ACSTS_VSTS) + goto ok2; + udelay(10); + } + + dev_err(chip->card->dev, + "AC'97 read problem (ACSTS_VSTS), codec_index %d, reg = 0x%x\n", + codec_index, reg); + result = 0xffff; + goto end; + + ok2: + /* + * Read the data returned from the AC97 register. + * ACSDA = Status Data Register = 474h + */ +#if 0 + dev_dbg(chip->card->dev, + "e) reg = 0x%x, val = 0x%x, BA0_ACCAD = 0x%x\n", reg, + snd_cs46xx_peekBA0(chip, BA0_ACSDA), + snd_cs46xx_peekBA0(chip, BA0_ACCAD)); +#endif + + //snd_cs46xx_peekBA0(chip, BA0_ACCAD); + result = snd_cs46xx_peekBA0(chip, BA0_ACSDA + offset); + end: + chip->active_ctrl(chip, -1); + return result; +} + +static unsigned short snd_cs46xx_ac97_read(struct snd_ac97 * ac97, + unsigned short reg) +{ + struct snd_cs46xx *chip = ac97->private_data; + unsigned short val; + int codec_index = ac97->num; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return 0xffff; + + val = snd_cs46xx_codec_read(chip, reg, codec_index); + + return val; +} + + +static void snd_cs46xx_codec_write(struct snd_cs46xx *chip, + unsigned short reg, + unsigned short val, + int codec_index) +{ + int count; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return; + + chip->active_ctrl(chip, 1); + + /* + * 1. Write ACCAD = Command Address Register = 46Ch for AC97 register address + * 2. Write ACCDA = Command Data Register = 470h for data to write to AC97 + * 3. Write ACCTL = Control Register = 460h for initiating the write + * 4. Read ACCTL = 460h, DCV should be reset by now and 460h = 07h + * 5. if DCV not cleared, break and return error + */ + + /* + * Setup the AC97 control registers on the CS461x to send the + * appropriate command to the AC97 to perform the read. + * ACCAD = Command Address Register = 46Ch + * ACCDA = Command Data Register = 470h + * ACCTL = Control Register = 460h + * set DCV - will clear when process completed + * reset CRW - Write command + * set VFRM - valid frame enabled + * set ESYN - ASYNC generation enabled + * set RSTN - ARST# inactive, AC97 codec not reset + */ + snd_cs46xx_pokeBA0(chip, BA0_ACCAD , reg); + snd_cs46xx_pokeBA0(chip, BA0_ACCDA , val); + snd_cs46xx_peekBA0(chip, BA0_ACCTL); + + if (codec_index == CS46XX_PRIMARY_CODEC_INDEX) { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, /* clear ACCTL_DCV */ ACCTL_VFRM | + ACCTL_ESYN | ACCTL_RSTN); + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | + ACCTL_ESYN | ACCTL_RSTN); + } else { + snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_DCV | ACCTL_TC | + ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); + } + + for (count = 0; count < 4000; count++) { + /* + * First, we want to wait for a short time. + */ + udelay(10); + /* + * Now, check to see if the write has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 07h + */ + if (!(snd_cs46xx_peekBA0(chip, BA0_ACCTL) & ACCTL_DCV)) { + goto end; + } + } + dev_err(chip->card->dev, + "AC'97 write problem, codec_index = %d, reg = 0x%x, val = 0x%x\n", + codec_index, reg, val); + end: + chip->active_ctrl(chip, -1); +} + +static void snd_cs46xx_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct snd_cs46xx *chip = ac97->private_data; + int codec_index = ac97->num; + + if (snd_BUG_ON(codec_index != CS46XX_PRIMARY_CODEC_INDEX && + codec_index != CS46XX_SECONDARY_CODEC_INDEX)) + return; + + snd_cs46xx_codec_write(chip, reg, val, codec_index); +} + + +/* + * Chip initialization + */ + +int snd_cs46xx_download(struct snd_cs46xx *chip, + u32 *src, + unsigned long offset, + unsigned long len) +{ + void __iomem *dst; + unsigned int bank = offset >> 16; + offset = offset & 0xffff; + + if (snd_BUG_ON((offset & 3) || (len & 3))) + return -EINVAL; + dst = chip->region.idx[bank+1].remap_addr + offset; + len /= sizeof(u32); + + /* writel already converts 32-bit value to right endianess */ + while (len-- > 0) { + writel(*src++, dst); + dst += sizeof(u32); + } + return 0; +} + +static inline void memcpy_le32(void *dst, const void *src, unsigned int len) +{ +#ifdef __LITTLE_ENDIAN + memcpy(dst, src, len); +#else + u32 *_dst = dst; + const __le32 *_src = src; + len /= 4; + while (len-- > 0) + *_dst++ = le32_to_cpu(*_src++); +#endif +} + +#ifdef CONFIG_SND_CS46XX_NEW_DSP + +static const char *module_names[CS46XX_DSP_MODULES] = { + "cwc4630", "cwcasync", "cwcsnoop", "cwcbinhack", "cwcdma" +}; + +MODULE_FIRMWARE("cs46xx/cwc4630"); +MODULE_FIRMWARE("cs46xx/cwcasync"); +MODULE_FIRMWARE("cs46xx/cwcsnoop"); +MODULE_FIRMWARE("cs46xx/cwcbinhack"); +MODULE_FIRMWARE("cs46xx/cwcdma"); + +static void free_module_desc(struct dsp_module_desc *module) +{ + if (!module) + return; + kfree(module->module_name); + kfree(module->symbol_table.symbols); + if (module->segments) { + int i; + for (i = 0; i < module->nsegments; i++) + kfree(module->segments[i].data); + kfree(module->segments); + } + kfree(module); +} + +/* firmware binary format: + * le32 nsymbols; + * struct { + * le32 address; + * char symbol_name[DSP_MAX_SYMBOL_NAME]; + * le32 symbol_type; + * } symbols[nsymbols]; + * le32 nsegments; + * struct { + * le32 segment_type; + * le32 offset; + * le32 size; + * le32 data[size]; + * } segments[nsegments]; + */ + +static int load_firmware(struct snd_cs46xx *chip, + struct dsp_module_desc **module_ret, + const char *fw_name) +{ + int i, err; + unsigned int nums, fwlen, fwsize; + const __le32 *fwdat; + struct dsp_module_desc *module = NULL; + const struct firmware *fw; + 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*/), + .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*/), + .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, snd_dma_pci_data(chip->pci), + 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, snd_dma_pci_data(chip->pci), + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + snd_dma_pci_data(chip->pci), 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, + snd_dma_pci_data(chip->pci), 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, + snd_dma_pci_data(chip->pci), 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, + snd_dma_pci_data(chip->pci), 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 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 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 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 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 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; + snd_pcm_suspend_all(chip->pcm); +#ifdef CONFIG_SND_CS46XX_NEW_DSP + snd_pcm_suspend_all(chip->pcm_rear); + snd_pcm_suspend_all(chip->pcm_center_lfe); + snd_pcm_suspend_all(chip->pcm_iec958); +#endif + // 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 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_nocache(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; + +#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..bdf411416 --- /dev/null +++ b/sound/pci/cs46xx/cs46xx_lib.h @@ -0,0 +1,210 @@ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..5fc497c6d --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos.c @@ -0,0 +1,2056 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +static int cs46xx_dsp_async_init (struct snd_cs46xx *chip, + struct dsp_scb_descriptor * fg_entry); + +static 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; + + if ((entry = snd_info_create_card_entry(card, "dsp", card->proc_root)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->mode = S_IFDIR | 0555; + + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + + ins->proc_dsp_dir = entry; + + if (!ins->proc_dsp_dir) + return -ENOMEM; + + if ((entry = snd_info_create_card_entry(card, "spos_symbols", ins->proc_dsp_dir)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = chip; + entry->mode = S_IFREG | 0644; + entry->c.text.read = cs46xx_dsp_proc_symbol_table_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + ins->proc_sym_info_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "spos_modules", ins->proc_dsp_dir)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = chip; + entry->mode = S_IFREG | 0644; + entry->c.text.read = cs46xx_dsp_proc_modules_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + ins->proc_modules_info_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "parameter", ins->proc_dsp_dir)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = chip; + entry->mode = S_IFREG | 0644; + entry->c.text.read = cs46xx_dsp_proc_parameter_dump_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + ins->proc_parameter_dump_info_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "sample", ins->proc_dsp_dir)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = chip; + entry->mode = S_IFREG | 0644; + entry->c.text.read = cs46xx_dsp_proc_sample_dump_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + ins->proc_sample_dump_info_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "task_tree", ins->proc_dsp_dir)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = chip; + entry->mode = S_IFREG | 0644; + entry->c.text.read = cs46xx_dsp_proc_task_tree_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + ins->proc_task_info_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "scb_info", ins->proc_dsp_dir)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = chip; + entry->mode = S_IFREG | 0644; + entry->c.text.read = cs46xx_dsp_proc_scb_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + ins->proc_scb_info_entry = entry; + + 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; + + snd_info_free_entry(ins->proc_sym_info_entry); + ins->proc_sym_info_entry = NULL; + + snd_info_free_entry(ins->proc_modules_info_entry); + ins->proc_modules_info_entry = NULL; + + snd_info_free_entry(ins->proc_parameter_dump_info_entry); + ins->proc_parameter_dump_info_entry = NULL; + + snd_info_free_entry(ins->proc_sample_dump_info_entry); + ins->proc_sample_dump_info_entry = NULL; + + snd_info_free_entry(ins->proc_scb_info_entry); + ins->proc_scb_info_entry = NULL; + + snd_info_free_entry(ins->proc_task_info_entry); + ins->proc_task_info_entry = NULL; + + 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 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..ca47a8114 --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos.h @@ -0,0 +1,231 @@ +/* + * The driver for the Cirrus Logic's Sound Fusion CS46XX based soundcards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * 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..8ef51a293 --- /dev/null +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -0,0 +1,1800 @@ +/* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * 2002-07 Benny Sjostrand benny@hostmobility.com + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "cs46xx.h" + +#include "cs46xx_lib.h" +#include "dsp_spos.h" + +struct proc_scb_info { + struct dsp_scb_descriptor * scb_desc; + struct snd_cs46xx *chip; +}; + +static void remove_symbol (struct snd_cs46xx * chip, struct dsp_symbol_entry * symbol) +{ + struct dsp_spos_instance * ins = chip->dsp_spos_instance; + int symbol_index = (int)(symbol - ins->symbol_table.symbols); + + if (snd_BUG_ON(ins->symbol_table.nsymbols <= 0)) + return; + if (snd_BUG_ON(symbol_index < 0 || + symbol_index >= ins->symbol_table.nsymbols)) + return; + + ins->symbol_table.symbols[symbol_index].deleted = 1; + + if (symbol_index < ins->symbol_table.highest_frag_index) { + ins->symbol_table.highest_frag_index = symbol_index; + } + + if (symbol_index == ins->symbol_table.nsymbols - 1) + ins->symbol_table.nsymbols --; + + if (ins->symbol_table.highest_frag_index > ins->symbol_table.nsymbols) { + ins->symbol_table.highest_frag_index = ins->symbol_table.nsymbols; + } + +} + +#ifdef CONFIG_SND_PROC_FS +static void cs46xx_dsp_proc_scb_info_read (struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct proc_scb_info * scb_info = entry->private_data; + struct dsp_scb_descriptor * scb = scb_info->scb_desc; + struct snd_cs46xx *chip = scb_info->chip; + int j,col; + void __iomem *dst = chip->region.idx[1].remap_addr + DSP_PARAMETER_BYTE_OFFSET; + + 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) { + + if ((entry = snd_info_create_card_entry(ins->snd_card, scb->scb_name, + ins->proc_dsp_dir)) != NULL) { + 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; + + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = scb_info; + entry->mode = S_IFREG | 0644; + + entry->c.text.read = cs46xx_dsp_proc_scb_info_read; + + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + kfree (scb_info); + entry = NULL; + } + } +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 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 u32 src_output_buffer_addr[DSP_MAX_SRC_NR] = { + 0x2B80, + 0x2BA0, + 0x2BC0, + 0x2BE0, + 0x2D00, + 0x2D20, + 0x2D40, + 0x2D60, + 0x2D80, + 0x2DA0, + 0x2DC0, + 0x2DE0, + 0x2E00, + 0x2E20 +}; + +static 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: to 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..0a8cf94c4 --- /dev/null +++ b/sound/pci/cs5530.c @@ -0,0 +1,298 @@ +/* + * cs5530.c - Initialisation code for Cyrix/NatSemi VSA1 softaudio + * + * (C) Copyright 2007 Ash Willis + * (C) Copyright 2003 Red Hat Inc + * + * This driver was ported (shamelessly ripped ;) from oss/kahlua.c but I did + * mess with it a bit. The chip seems to have to have trouble with full duplex + * mode. If we're recording in 8bit 8000kHz, say, and we then attempt to + * simultaneously play back audio at 16bit 44100kHz, the device actually plays + * back in the same format in which it is capturing. By forcing the chip to + * always play/capture in 16/44100, we can let alsa-lib convert the samples and + * that way we can hack up some full duplex audio. + * + * XpressAudio(tm) is used on the Cyrix MediaGX (now NatSemi Geode) systems. + * The older version (VSA1) provides fairly good soundblaster emulation + * although there are a couple of bugs: large DMA buffers break record, + * and the MPU event handling seems suspect. VSA2 allows the native driver + * to control the AC97 audio engine directly and requires a different driver. + * + * Thanks to National Semiconductor for providing the needed information + * on the XpressAudio(tm) internals. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * TO DO: + * Investigate whether we can portably support Cognac (5520) in the + * same manner. + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Ash Willis"); +MODULE_DESCRIPTION("CS5530 Audio"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for CS5530 Audio driver."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for CS5530 Audio driver."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CS5530 Audio driver."); + +struct snd_cs5530 { + struct snd_card *card; + struct pci_dev *pci; + struct snd_sb *sb; + unsigned long pci_base; +}; + +static const struct pci_device_id snd_cs5530_ids[] = { + {PCI_VENDOR_ID_CYRIX, PCI_DEVICE_ID_CYRIX_5530_AUDIO, PCI_ANY_ID, + PCI_ANY_ID, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, snd_cs5530_ids); + +static 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 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..a8f75f8df --- /dev/null +++ b/sound/pci/cs5535audio/Makefile @@ -0,0 +1,10 @@ +# +# 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..4590086d9 --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio.c @@ -0,0 +1,411 @@ +/* + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs5535audio.h" + +#define DRIVER_NAME "cs5535audio" + +static char *ac97_quirk; +module_param(ac97_quirk, charp, 0444); +MODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds."); + +static const struct ac97_quirk ac97_quirks[] = { +#if 0 /* Not yet confirmed if all 5536 boards are HP only */ + { + .subvendor = PCI_VENDOR_ID_AMD, + .subdevice = PCI_DEVICE_ID_AMD_CS5536_AUDIO, + .name = "AMD RDK", + .type = AC97_TUNE_HP_ONLY + }, +#endif + {} +}; + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " DRIVER_NAME); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " DRIVER_NAME); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " DRIVER_NAME); + +static const struct pci_device_id snd_cs5535audio_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_AUDIO) }, + {} +}; + +MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids); + +static void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout) +{ + unsigned int tmp; + do { + tmp = cs_readl(cs5535au, ACC_CODEC_CNTL); + if (!(tmp & CMD_NEW)) + break; + udelay(1); + } while (--timeout); + if (!timeout) + dev_err(cs5535au->card->dev, + "Failure writing to cs5535 codec\n"); +} + +static unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au, + unsigned short reg) +{ + unsigned int regdata; + unsigned int timeout; + unsigned int val; + + regdata = ((unsigned int) reg) << 24; + regdata |= ACC_CODEC_CNTL_RD_CMD; + regdata |= CMD_NEW; + + cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); + wait_till_cmd_acked(cs5535au, 50); + + timeout = 50; + do { + val = cs_readl(cs5535au, ACC_CODEC_STATUS); + if ((val & STS_NEW) && reg == (val >> 24)) + break; + udelay(1); + } while (--timeout); + if (!timeout) + dev_err(cs5535au->card->dev, + "Failure reading codec reg 0x%x, Last value=0x%x\n", + reg, val); + + return (unsigned short) val; +} + +static void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au, + unsigned short reg, unsigned short val) +{ + unsigned int regdata; + + regdata = ((unsigned int) reg) << 24; + regdata |= val; + regdata &= CMD_MASK; + regdata |= CMD_NEW; + regdata &= ACC_CODEC_CNTL_WR_CMD; + + cs_writel(cs5535au, ACC_CODEC_CNTL, regdata); + wait_till_cmd_acked(cs5535au, 50); +} + +static void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + struct cs5535audio *cs5535au = ac97->private_data; + snd_cs5535audio_codec_write(cs5535au, reg, val); +} + +static unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct cs5535audio *cs5535au = ac97->private_data; + return snd_cs5535audio_codec_read(cs5535au, reg); +} + +static int snd_cs5535audio_mixer(struct cs5535audio *cs5535au) +{ + struct snd_card *card = cs5535au->card; + struct snd_ac97_bus *pbus; + struct snd_ac97_template ac97; + int err; + static 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) +{ + synchronize_irq(cs5535au->irq); + 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 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; + 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..3b0fdaca8 --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio_olpc.c @@ -0,0 +1,192 @@ +/* + * OLPC XO-1 additional sound features + * + * Copyright © 2006 Jaya Kumar + * Copyright © 2007-2008 Andres Salomon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include + +#include +#include "cs5535audio.h" + +#define DRV_NAME "cs5535audio-olpc" + +/* + * OLPC has an additional feature on top of the regular AD1888 codec features. + * It has an Analog Input mode that is switched into (after disabling the + * High Pass Filter) via GPIO. It is supported on B2 and later models. + */ +void olpc_analog_input(struct snd_ac97 *ac97, int on) +{ + int err; + + if (!machine_is_olpc()) + return; + + /* update the High Pass Filter (via AC97_AD_TEST2) */ + err = snd_ac97_update_bits(ac97, AC97_AD_TEST2, + 1 << AC97_AD_HPFD_SHIFT, on << AC97_AD_HPFD_SHIFT); + if (err < 0) { + dev_err(ac97->bus->card->dev, + "setting High Pass Filter - %d\n", err); + return; + } + + /* set Analog Input through GPIO */ + gpio_set_value(OLPC_GPIO_MIC_AC, on); +} + +/* + * OLPC XO-1's V_REFOUT is a mic bias enable. + */ +void olpc_mic_bias(struct snd_ac97 *ac97, int on) +{ + int err; + + if (!machine_is_olpc()) + return; + + on = on ? 0 : 1; + err = snd_ac97_update_bits(ac97, AC97_AD_MISC, + 1 << AC97_AD_VREFD_SHIFT, on << AC97_AD_VREFD_SHIFT); + if (err < 0) + dev_err(ac97->bus->card->dev, "setting MIC Bias - %d\n", err); +} + +static int olpc_dc_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int olpc_dc_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + v->value.integer.value[0] = gpio_get_value(OLPC_GPIO_MIC_AC); + return 0; +} + +static int olpc_dc_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); + + olpc_analog_input(cs5535au->ac97, v->value.integer.value[0]); + return 1; +} + +static int olpc_mic_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int olpc_mic_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); + struct snd_ac97 *ac97 = cs5535au->ac97; + int i; + + i = (snd_ac97_read(ac97, AC97_AD_MISC) >> AC97_AD_VREFD_SHIFT) & 0x1; + v->value.integer.value[0] = i ? 0 : 1; + return 0; +} + +static int olpc_mic_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v) +{ + struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl); + + olpc_mic_bias(cs5535au->ac97, v->value.integer.value[0]); + return 1; +} + +static 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..326caec85 --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio_pcm.c @@ -0,0 +1,454 @@ +/* + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * todo: add be fmt support, spdif, pm + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs5535audio.h" + +static const struct snd_pcm_hardware snd_cs5535audio_playback = +{ + .info = ( + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME + ), + .formats = ( + SNDRV_PCM_FMTBIT_S16_LE + ), + .rates = ( + SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000 + ), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (64*1024 - 16), + .periods_min = 1, + .periods_max = CS5535AUDIO_MAX_DESCRIPTORS, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_cs5535audio_capture = +{ + .info = ( + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID + ), + .formats = ( + SNDRV_PCM_FMTBIT_S16_LE + ), + .rates = ( + SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000 + ), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (64*1024 - 16), + .periods_min = 1, + .periods_max = CS5535AUDIO_MAX_DESCRIPTORS, + .fifo_size = 0, +}; + +static int snd_cs5535audio_playback_open(struct snd_pcm_substream *substream) +{ + int err; + struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_cs5535audio_playback; + runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_FRONT_DAC]; + snd_pcm_limit_hw_rates(runtime); + cs5535au->playback_substream = substream; + runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]); + 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, + snd_dma_pci_data(cs5535au->pci), + 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; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return 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 snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(cs5535au->pci), + 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..82bd10b68 --- /dev/null +++ b/sound/pci/cs5535audio/cs5535audio_pm.c @@ -0,0 +1,115 @@ +/* + * Power management for audio on multifunction CS5535 companion device + * Copyright (C) Jaya Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs5535audio.h" + +static void snd_cs5535audio_stop_hardware(struct cs5535audio *cs5535au) +{ + /* + we depend on snd_ac97_suspend to tell the + AC97 codec to shutdown. the amd spec suggests + that the LNK_SHUTDOWN be done at the same time + that the codec power-down is issued. instead, + we do it just after rather than at the same + time. excluding codec specific build_ops->suspend + ac97 powerdown hits: + 0x8000 EAPD + 0x4000 Headphone amplifier + 0x0300 ADC & DAC + 0x0400 Analog Mixer powerdown (Vref on) + I am not sure if this is the best that we can do. + The remainder to be investigated are: + - analog mixer (vref off) 0x0800 + - AC-link powerdown 0x1000 + - codec internal clock 0x2000 + */ + + /* set LNK_SHUTDOWN to shutdown AC link */ + cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_SHUTDOWN); + +} + +static int __maybe_unused snd_cs5535audio_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct cs5535audio *cs5535au = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(cs5535au->pcm); + 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..15075f89e --- /dev/null +++ b/sound/pci/ctxfi/Makefile @@ -0,0 +1,5 @@ +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..5851249f1 --- /dev/null +++ b/sound/pci/ctxfi/ct20k1reg.h @@ -0,0 +1,634 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + */ + +#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..ca501ba03 --- /dev/null +++ b/sound/pci/ctxfi/ct20k2reg.h @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + */ + +#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..d32685ce6 --- /dev/null +++ b/sound/pci/ctxfi/ctamixer.c @@ -0,0 +1,488 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctamixer.c + * + * @Brief + * This file contains the implementation of the Audio Mixer + * resource management object. + * + * @Author Liu Chun + * @Date May 21 2008 + * + */ + +#include "ctamixer.h" +#include "cthardware.h" +#include + +#define AMIXER_RESOURCE_NUM 256 +#define SUM_RESOURCE_NUM 256 + +#define AMIXER_Y_IMMEDIATE 1 + +#define BLANK_SLOT 4094 + +static void amixer_master(struct rsc *rsc) +{ + rsc->conj = 0; + rsc->idx = container_of(rsc, struct amixer, rsc)->idx[0]; +} + +static void amixer_next_conj(struct rsc *rsc) +{ + rsc->conj++; +} + +static int amixer_index(const struct rsc *rsc) +{ + return container_of(rsc, struct amixer, rsc)->idx[rsc->conj]; +} + +static int amixer_output_slot(const struct rsc *rsc) +{ + return (amixer_index(rsc) << 4) + 0x4; +} + +static const struct rsc_ops amixer_basic_rsc_ops = { + .master = amixer_master, + .next_conj = amixer_next_conj, + .index = amixer_index, + .output_slot = amixer_output_slot, +}; + +static int amixer_set_input(struct amixer *amixer, struct rsc *rsc) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + hw->amixer_set_mode(amixer->rsc.ctrl_blk, AMIXER_Y_IMMEDIATE); + amixer->input = rsc; + if (!rsc) + hw->amixer_set_x(amixer->rsc.ctrl_blk, BLANK_SLOT); + else + hw->amixer_set_x(amixer->rsc.ctrl_blk, + rsc->ops->output_slot(rsc)); + + return 0; +} + +/* y is a 14-bit immediate constant */ +static int amixer_set_y(struct amixer *amixer, unsigned int y) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + hw->amixer_set_y(amixer->rsc.ctrl_blk, y); + + return 0; +} + +static int amixer_set_invalid_squash(struct amixer *amixer, unsigned int iv) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + hw->amixer_set_iv(amixer->rsc.ctrl_blk, iv); + + return 0; +} + +static int amixer_set_sum(struct amixer *amixer, struct sum *sum) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + amixer->sum = sum; + if (!sum) { + hw->amixer_set_se(amixer->rsc.ctrl_blk, 0); + } else { + hw->amixer_set_se(amixer->rsc.ctrl_blk, 1); + hw->amixer_set_sadr(amixer->rsc.ctrl_blk, + sum->rsc.ops->index(&sum->rsc)); + } + + return 0; +} + +static int amixer_commit_write(struct amixer *amixer) +{ + struct hw *hw; + unsigned int index; + int i; + struct rsc *input; + struct sum *sum; + + hw = amixer->rsc.hw; + input = amixer->input; + sum = amixer->sum; + + /* Program master and conjugate resources */ + amixer->rsc.ops->master(&amixer->rsc); + if (input) + input->ops->master(input); + + if (sum) + sum->rsc.ops->master(&sum->rsc); + + for (i = 0; i < amixer->rsc.msr; i++) { + hw->amixer_set_dirty_all(amixer->rsc.ctrl_blk); + if (input) { + hw->amixer_set_x(amixer->rsc.ctrl_blk, + input->ops->output_slot(input)); + input->ops->next_conj(input); + } + if (sum) { + hw->amixer_set_sadr(amixer->rsc.ctrl_blk, + sum->rsc.ops->index(&sum->rsc)); + sum->rsc.ops->next_conj(&sum->rsc); + } + index = amixer->rsc.ops->output_slot(&amixer->rsc); + hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk); + amixer->rsc.ops->next_conj(&amixer->rsc); + } + amixer->rsc.ops->master(&amixer->rsc); + if (input) + input->ops->master(input); + + if (sum) + sum->rsc.ops->master(&sum->rsc); + + return 0; +} + +static int amixer_commit_raw_write(struct amixer *amixer) +{ + struct hw *hw; + unsigned int index; + + hw = amixer->rsc.hw; + index = amixer->rsc.ops->output_slot(&amixer->rsc); + hw->amixer_commit_write(hw, index, amixer->rsc.ctrl_blk); + + return 0; +} + +static int amixer_get_y(struct amixer *amixer) +{ + struct hw *hw; + + hw = amixer->rsc.hw; + return hw->amixer_get_y(amixer->rsc.ctrl_blk); +} + +static int amixer_setup(struct amixer *amixer, struct rsc *input, + unsigned int scale, struct sum *sum) +{ + amixer_set_input(amixer, input); + amixer_set_y(amixer, scale); + amixer_set_sum(amixer, sum); + amixer_commit_write(amixer); + return 0; +} + +static const struct amixer_rsc_ops amixer_ops = { + .set_input = amixer_set_input, + .set_invalid_squash = amixer_set_invalid_squash, + .set_scale = amixer_set_y, + .set_sum = amixer_set_sum, + .commit_write = amixer_commit_write, + .commit_raw_write = amixer_commit_raw_write, + .setup = amixer_setup, + .get_scale = amixer_get_y, +}; + +static int amixer_rsc_init(struct amixer *amixer, + const struct amixer_desc *desc, + struct amixer_mgr *mgr) +{ + int err; + + err = rsc_init(&amixer->rsc, amixer->idx[0], + AMIXER, desc->msr, mgr->mgr.hw); + if (err) + return err; + + /* Set amixer specific operations */ + amixer->rsc.ops = &amixer_basic_rsc_ops; + amixer->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..2de18aa65 --- /dev/null +++ b/sound/pci/ctxfi/ctamixer.h @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctamixer.h + * + * @Brief + * This file contains the definition of the Audio Mixer + * resource management object. + * + * @Author Liu Chun + * @Date May 21 2008 + * + */ + +#ifndef CTAMIXER_H +#define CTAMIXER_H + +#include "ctresource.h" +#include +#include + +/* Define the descriptor of a summation node resource */ +struct sum { + struct rsc rsc; /* Basic resource info */ + unsigned char idx[8]; +}; + +/* Define sum resource request description info */ +struct sum_desc { + unsigned int msr; +}; + +struct sum_mgr { + struct rsc_mgr mgr; /* Basic resource manager info */ + struct snd_card *card; /* pointer to this card */ + spinlock_t mgr_lock; + + /* request one sum resource */ + int (*get_sum)(struct sum_mgr *mgr, + const struct sum_desc *desc, struct sum **rsum); + /* return one sum resource */ + int (*put_sum)(struct sum_mgr *mgr, struct sum *sum); +}; + +/* Constructor and destructor of daio resource manager */ +int sum_mgr_create(struct hw *hw, 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..2ada8444a --- /dev/null +++ b/sound/pci/ctxfi/ctatc.c @@ -0,0 +1,1753 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctatc.c + * + * @Brief + * This file contains the implementation of the device resource management + * object. + * + * @Author Liu Chun + * @Date Mar 28 2008 + */ + +#include "ctatc.h" +#include "ctpcm.h" +#include "ctmixer.h" +#include "ctsrc.h" +#include "ctamixer.h" +#include "ctdaio.h" +#include "cttimer.h" +#include +#include +#include +#include +#include + +#define 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 struct snd_pci_quirk subsys_20k1_list[] = { + 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 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 */ + [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 black-listed\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) +{ + int i; + struct hw *hw = atc->hw; + + snd_power_change_state(atc->card, SNDRV_CTL_POWER_D3hot); + + for (i = FRONT; i < NUM_PCMS; i++) { + if (!atc->pcms[i]) + continue; + + snd_pcm_suspend_all(atc->pcms[i]); + } + + 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 + * @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 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..56413343a --- /dev/null +++ b/sound/pci/ctxfi/ctatc.h @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctatc.h + * + * @Brief + * This file contains the definition of the device resource management object. + * + * @Author Liu Chun + * @Date Mar 28 2008 + * + */ + +#ifndef CTATC_H +#define CTATC_H + +#include +#include +#include +#include +#include + +#include "ctvmem.h" +#include "cthardware.h" +#include "ctresource.h" + +enum CTALSADEVS { /* Types of alsa devices */ + FRONT, + SURROUND, + CLFE, + SIDE, + IEC958, + MIXER, + NUM_CTALSADEVS /* This should always be the last */ +}; + +struct ct_atc_chip_sub_details { + u16 subsys; + const char *nm_model; +}; + +struct ct_atc_chip_details { + u16 vendor; + u16 device; + const struct ct_atc_chip_sub_details *sub_details; + const char *nm_card; +}; + +struct ct_atc; +struct ct_timer; +struct ct_timer_instance; + +/* alsa pcm stream descriptor */ +struct ct_atc_pcm { + struct snd_pcm_substream *substream; + void (*interrupt)(struct ct_atc_pcm *apcm); + struct ct_timer_instance *timer; + unsigned int started:1; + + /* Only mono and interleaved modes are supported now. */ + struct ct_vm_block *vm_block; + void *src; /* SRC for interacting with host memory */ + void **srccs; /* SRCs for sample rate conversion */ + void **srcimps; /* SRC Input Mappers */ + void **amixers; /* AMIXERs for routing converted data */ + void *mono; /* A SUM resource for mixing chs to one */ + unsigned char n_srcc; /* Number of converting SRCs */ + unsigned char n_srcimp; /* Number of SRC Input Mappers */ + unsigned char n_amixer; /* Number of AMIXERs */ +}; + +/* Chip resource management object */ +struct ct_atc { + struct pci_dev *pci; + struct snd_card *card; + unsigned int rsr; /* reference sample rate in Hz */ + unsigned int msr; /* master sample rate in rsr */ + unsigned int pll_rate; /* current rate of Phase Lock Loop */ + + int chip_type; + int model; + const char *chip_name; + const char *model_name; + + 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..ed6e15d1f --- /dev/null +++ b/sound/pci/ctxfi/ctdaio.c @@ -0,0 +1,759 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctdaio.c + * + * @Brief + * This file contains the implementation of Digital Audio Input Output + * resource management object. + * + * @Author Liu Chun + * @Date May 23 2008 + * + */ + +#include "ctdaio.h" +#include "cthardware.h" +#include "ctimap.h" +#include +#include + +#define DAIO_OUT_MAX SPDIFOO + +struct daio_usage { + unsigned short data; +}; + +struct daio_rsc_idx { + unsigned short left; + unsigned short right; +}; + +static 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 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..a30be73b0 --- /dev/null +++ b/sound/pci/ctxfi/ctdaio.h @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctdaio.h + * + * @Brief + * This file contains the definition of Digital Audio Input Output + * resource management object. + * + * @Author Liu Chun + * @Date May 23 2008 + * + */ + +#ifndef CTDAIO_H +#define CTDAIO_H + +#include "ctresource.h" +#include "ctimap.h" +#include +#include +#include + +/* Define the descriptor of a daio resource */ +enum DAIOTYP { + LINEO1, + LINEO2, + LINEO3, + LINEO4, + SPDIFOO, /* S/PDIF Out (Flexijack/Optical) */ + LINEIM, + SPDIFIO, /* S/PDIF In (Flexijack/Optical) on the card */ + MIC, /* Dedicated mic on Titanium HD */ + 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..a689f2552 --- /dev/null +++ b/sound/pci/ctxfi/cthardware.c @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File cthardware.c + * + * @Brief + * This file contains the implementation of hardware access methord. + * + * @Author Liu Chun + * @Date Jun 26 2008 + * + */ + +#include "cthardware.h" +#include "cthw20k1.h" +#include "cthw20k2.h" +#include + +int create_hw_obj(struct pci_dev *pci, enum CHIPTYP chip_type, + enum CTCARDS model, struct hw **rhw) +{ + int err; + + switch (chip_type) { + case ATC20K1: + err = create_20k1_hw_obj(rhw); + break; + case ATC20K2: + err = create_20k2_hw_obj(rhw); + break; + default: + err = -ENODEV; + break; + } + if (err) + return err; + + (*rhw)->pci = pci; + (*rhw)->chip_type = chip_type; + (*rhw)->model = model; + + return 0; +} + +int destroy_hw_obj(struct hw *hw) +{ + int err; + + switch (hw->pci->device) { + case 0x0005: /* 20k1 device */ + err = destroy_20k1_hw_obj(hw); + break; + case 0x000B: /* 20k2 device */ + err = destroy_20k2_hw_obj(hw); + break; + default: + err = -ENODEV; + break; + } + + return err; +} + +unsigned int get_field(unsigned int data, unsigned int field) +{ + int i; + + if (WARN_ON(!field)) + return 0; + /* @field should always be greater than 0 */ + for (i = 0; !(field & (1 << i)); ) + i++; + + return (data & field) >> i; +} + +void set_field(unsigned int *data, unsigned int field, unsigned int value) +{ + int i; + + if (WARN_ON(!field)) + return; + /* @field should always be greater than 0 */ + for (i = 0; !(field & (1 << i)); ) + i++; + + *data = (*data & (~field)) | ((value << i) & field); +} + diff --git a/sound/pci/ctxfi/cthardware.h b/sound/pci/ctxfi/cthardware.h new file mode 100644 index 000000000..54cc9cb75 --- /dev/null +++ b/sound/pci/ctxfi/cthardware.h @@ -0,0 +1,217 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File cthardware.h + * + * @Brief + * This file contains the definition of hardware access methord. + * + * @Author Liu Chun + * @Date May 13 2008 + * + */ + +#ifndef CTHARDWARE_H +#define CTHARDWARE_H + +#include +#include +#include + +enum CHIPTYP { + ATC20K1, + ATC20K2, + ATCNONE +}; + +enum CTCARDS { + /* 20k1 models */ + CTSB055X, + CT20K1_MODEL_FIRST = 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..6a051a1c3 --- /dev/null +++ b/sound/pci/ctxfi/cthw20k1.c @@ -0,0 +1,2287 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File cthw20k1.c + * + * @Brief + * This file contains the implementation of hardware access methord for 20k1. + * + * @Author Liu Chun + * @Date Jun 24 2008 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cthw20k1.h" +#include "ct20k1reg.h" + +struct hw20k1 { + struct hw hw; + spinlock_t reg_20k1_lock; + spinlock_t reg_pci_lock; +}; + +static u32 hw_read_20kx(struct hw *hw, u32 reg); +static void hw_write_20kx(struct hw *hw, u32 reg, u32 data); +static u32 hw_read_pci(struct hw *hw, u32 reg); +static void hw_write_pci(struct hw *hw, u32 reg, u32 data); + +/* + * Type definition block. + * The layout of control structures can be directly applied on 20k2 chip. + */ + +/* + * SRC control block definitions. + */ + +/* SRC resource control block */ +#define SRCCTL_STATE 0x00000007 +#define SRCCTL_BM 0x00000008 +#define SRCCTL_RSR 0x00000030 +#define SRCCTL_SF 0x000001C0 +#define SRCCTL_WR 0x00000200 +#define SRCCTL_PM 0x00000400 +#define SRCCTL_ROM 0x00001800 +#define SRCCTL_VO 0x00002000 +#define SRCCTL_ST 0x00004000 +#define SRCCTL_IE 0x00008000 +#define SRCCTL_ILSZ 0x000F0000 +#define SRCCTL_BP 0x00100000 + +#define SRCCCR_CISZ 0x000007FF +#define SRCCCR_CWA 0x001FF800 +#define SRCCCR_D 0x00200000 +#define SRCCCR_RS 0x01C00000 +#define SRCCCR_NAL 0x3E000000 +#define SRCCCR_RA 0xC0000000 + +#define SRCCA_CA 0x03FFFFFF +#define SRCCA_RS 0x1C000000 +#define SRCCA_NAL 0xE0000000 + +#define SRCSA_SA 0x03FFFFFF + +#define SRCLA_LA 0x03FFFFFF + +/* Mixer Parameter Ring ram Low and Hight register. + * Fixed-point value in 8.24 format for parameter channel */ +#define MPRLH_PITCH 0xFFFFFFFF + +/* SRC resource register dirty flags */ +union src_dirty { + struct { + u16 ctl:1; + u16 ccr:1; + u16 sa:1; + u16 la:1; + u16 ca:1; + u16 mpr:1; + u16 czbfs:1; /* Clear Z-Buffers */ + u16 rsv:9; + } bf; + u16 data; +}; + +struct src_rsc_ctrl_blk { + unsigned int ctl; + unsigned int ccr; + unsigned int ca; + unsigned int sa; + unsigned int la; + unsigned int mpr; + union src_dirty dirty; +}; + +/* SRC manager control block */ +union src_mgr_dirty { + struct { + u16 enb0:1; + u16 enb1:1; + u16 enb2:1; + u16 enb3:1; + u16 enb4:1; + u16 enb5:1; + u16 enb6:1; + u16 enb7:1; + u16 enbsa:1; + u16 rsv:7; + } bf; + u16 data; +}; + +struct src_mgr_ctrl_blk { + unsigned int enbsa; + unsigned int enb[8]; + union src_mgr_dirty dirty; +}; + +/* SRCIMP manager control block */ +#define SRCAIM_ARC 0x00000FFF +#define SRCAIM_NXT 0x00FF0000 +#define SRCAIM_SRC 0xFF000000 + +struct srcimap { + unsigned int srcaim; + unsigned int idx; +}; + +/* SRCIMP manager register dirty flags */ +union srcimp_mgr_dirty { + struct { + u16 srcimap:1; + u16 rsv:15; + } bf; + u16 data; +}; + +struct srcimp_mgr_ctrl_blk { + struct srcimap srcimap; + union srcimp_mgr_dirty dirty; +}; + +/* + * Function implementation block. + */ + +static int src_get_rsc_ctrl_blk(void **rblk) +{ + struct src_rsc_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc(sizeof(*blk), GFP_KERNEL); + if (!blk) + return -ENOMEM; + + *rblk = blk; + + return 0; +} + +static int src_put_rsc_ctrl_blk(void *blk) +{ + kfree((struct src_rsc_ctrl_blk *)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((struct src_mgr_ctrl_blk *)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((struct srcimp_mgr_ctrl_blk *)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((struct amixer_rsc_ctrl_blk *)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((struct dai_ctrl_blk *)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((struct dao_ctrl_blk *)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((struct daio_mgr_ctrl_blk *)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; + } + + 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)))); + + /* TODO: Disable interrupt and so on... */ + if (hw->irq >= 0) + synchronize_irq(hw->irq); + 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..02f72fb44 --- /dev/null +++ b/sound/pci/ctxfi/cthw20k1.h @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @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..6c9ab6022 --- /dev/null +++ b/sound/pci/ctxfi/cthw20k2.c @@ -0,0 +1,2353 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File cthw20k2.c + * + * @Brief + * This file contains the implementation of hardware access method for 20k2. + * + * @Author Liu Chun + * @Date May 14 2008 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cthw20k2.h" +#include "ct20k2reg.h" + +struct hw20k2 { + struct hw hw; + /* for i2c */ + unsigned char dev_id; + unsigned char addr_size; + unsigned char data_size; + + int mic_source; +}; + +static u32 hw_read_20kx(struct hw *hw, u32 reg); +static void hw_write_20kx(struct hw *hw, u32 reg, u32 data); + +/* + * Type definition block. + * The layout of control structures can be directly applied on 20k2 chip. + */ + +/* + * SRC control block definitions. + */ + +/* SRC resource control block */ +#define SRCCTL_STATE 0x00000007 +#define SRCCTL_BM 0x00000008 +#define SRCCTL_RSR 0x00000030 +#define SRCCTL_SF 0x000001C0 +#define SRCCTL_WR 0x00000200 +#define SRCCTL_PM 0x00000400 +#define SRCCTL_ROM 0x00001800 +#define SRCCTL_VO 0x00002000 +#define SRCCTL_ST 0x00004000 +#define SRCCTL_IE 0x00008000 +#define SRCCTL_ILSZ 0x000F0000 +#define SRCCTL_BP 0x00100000 + +#define SRCCCR_CISZ 0x000007FF +#define SRCCCR_CWA 0x001FF800 +#define SRCCCR_D 0x00200000 +#define SRCCCR_RS 0x01C00000 +#define SRCCCR_NAL 0x3E000000 +#define SRCCCR_RA 0xC0000000 + +#define SRCCA_CA 0x0FFFFFFF +#define SRCCA_RS 0xE0000000 + +#define SRCSA_SA 0x0FFFFFFF + +#define SRCLA_LA 0x0FFFFFFF + +/* Mixer Parameter Ring ram Low and Hight register. + * Fixed-point value in 8.24 format for parameter channel */ +#define MPRLH_PITCH 0xFFFFFFFF + +/* SRC resource register dirty flags */ +union src_dirty { + struct { + u16 ctl:1; + u16 ccr:1; + u16 sa:1; + u16 la:1; + u16 ca:1; + u16 mpr:1; + u16 czbfs:1; /* Clear Z-Buffers */ + u16 rsv:9; + } bf; + u16 data; +}; + +struct src_rsc_ctrl_blk { + unsigned int ctl; + unsigned int ccr; + unsigned int ca; + unsigned int sa; + unsigned int la; + unsigned int mpr; + union src_dirty dirty; +}; + +/* SRC manager control block */ +union src_mgr_dirty { + struct { + u16 enb0:1; + u16 enb1:1; + u16 enb2:1; + u16 enb3:1; + u16 enb4:1; + u16 enb5:1; + u16 enb6:1; + u16 enb7:1; + u16 enbsa:1; + u16 rsv:7; + } bf; + u16 data; +}; + +struct src_mgr_ctrl_blk { + unsigned int enbsa; + unsigned int enb[8]; + union src_mgr_dirty dirty; +}; + +/* SRCIMP manager control block */ +#define SRCAIM_ARC 0x00000FFF +#define SRCAIM_NXT 0x00FF0000 +#define SRCAIM_SRC 0xFF000000 + +struct srcimap { + unsigned int srcaim; + unsigned int idx; +}; + +/* SRCIMP manager register dirty flags */ +union srcimp_mgr_dirty { + struct { + u16 srcimap:1; + u16 rsv:15; + } bf; + u16 data; +}; + +struct srcimp_mgr_ctrl_blk { + struct srcimap srcimap; + union srcimp_mgr_dirty dirty; +}; + +/* + * Function implementation block. + */ + +static int src_get_rsc_ctrl_blk(void **rblk) +{ + struct src_rsc_ctrl_blk *blk; + + *rblk = NULL; + blk = kzalloc(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; + } + + 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..d2b7daab6 --- /dev/null +++ b/sound/pci/ctxfi/cthw20k2.h @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @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..0b73368a4 --- /dev/null +++ b/sound/pci/ctxfi/ctimap.c @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctimap.c + * + * @Brief + * This file contains the implementation of generic input mapper operations + * for input mapper management. + * + * @Author Liu Chun + * @Date May 23 2008 + * + */ + +#include "ctimap.h" +#include + +int input_mapper_add(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data) +{ + struct list_head *pos, *pre, *head; + struct imapper *pre_ent, *pos_ent; + + head = mappers; + + if (list_empty(head)) { + entry->next = entry->addr; + map_op(data, entry); + list_add(&entry->list, head); + return 0; + } + + list_for_each(pos, head) { + pos_ent = list_entry(pos, struct imapper, list); + if (pos_ent->slot > entry->slot) { + /* found a position in list */ + break; + } + } + + if (pos != head) { + pre = pos->prev; + if (pre == head) + pre = head->prev; + + __list_add(&entry->list, pos->prev, pos); + } else { + pre = head->prev; + pos = head->next; + list_add_tail(&entry->list, head); + } + + pre_ent = list_entry(pre, struct imapper, list); + pos_ent = list_entry(pos, struct imapper, list); + + entry->next = pos_ent->addr; + map_op(data, entry); + pre_ent->next = entry->addr; + map_op(data, pre_ent); + + return 0; +} + +int input_mapper_delete(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data) +{ + struct list_head *next, *pre, *head; + struct imapper *pre_ent, *next_ent; + + head = mappers; + + if (list_empty(head)) + return 0; + + pre = (entry->list.prev == head) ? head->prev : entry->list.prev; + next = (entry->list.next == head) ? head->next : entry->list.next; + + if (pre == &entry->list) { + /* entry is the only one node in mappers list */ + entry->next = entry->addr = entry->user = entry->slot = 0; + map_op(data, entry); + list_del(&entry->list); + return 0; + } + + pre_ent = list_entry(pre, struct imapper, list); + next_ent = list_entry(next, struct imapper, list); + + pre_ent->next = next_ent->addr; + map_op(data, pre_ent); + list_del(&entry->list); + + return 0; +} + +void free_input_mapper_list(struct list_head *head) +{ + struct imapper *entry; + struct list_head *pos; + + while (!list_empty(head)) { + pos = head->next; + list_del(pos); + entry = list_entry(pos, struct imapper, list); + kfree(entry); + } +} + diff --git a/sound/pci/ctxfi/ctimap.h b/sound/pci/ctxfi/ctimap.h new file mode 100644 index 000000000..53ccf9be8 --- /dev/null +++ b/sound/pci/ctxfi/ctimap.h @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctimap.h + * + * @Brief + * This file contains the definition of generic input mapper operations + * for input mapper management. + * + * @Author Liu Chun + * @Date May 23 2008 + * + */ + +#ifndef CTIMAP_H +#define CTIMAP_H + +#include + +struct imapper { + unsigned short slot; /* the id of the slot containing input data */ + unsigned short user; /* the id of the user resource consuming data */ + unsigned short addr; /* the input mapper ram id */ + unsigned short next; /* the next input mapper ram id */ + struct list_head list; +}; + +int input_mapper_add(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data); + +int input_mapper_delete(struct list_head *mappers, struct imapper *entry, + int (*map_op)(void *, struct imapper *), void *data); + +void free_input_mapper_list(struct list_head *mappers); + +#endif /* CTIMAP_H */ diff --git a/sound/pci/ctxfi/ctmixer.c b/sound/pci/ctxfi/ctmixer.c new file mode 100644 index 000000000..4777d50fb --- /dev/null +++ b/sound/pci/ctxfi/ctmixer.c @@ -0,0 +1,1229 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctmixer.c + * + * @Brief + * This file contains the implementation of alsa mixer device functions. + * + * @Author Liu Chun + * @Date May 28 2008 + * + */ + + +#include "ctmixer.h" +#include "ctamixer.h" +#include +#include +#include +#include +#include +#include + +enum CT_SUM_CTL { + SUM_IN_F, + SUM_IN_R, + SUM_IN_C, + SUM_IN_S, + SUM_IN_F_C, + + NUM_CT_SUMS +}; + +enum CT_AMIXER_CTL { + /* volume control mixers */ + AMIXER_MASTER_F, + AMIXER_MASTER_R, + AMIXER_MASTER_C, + AMIXER_MASTER_S, + AMIXER_PCM_F, + AMIXER_PCM_R, + AMIXER_PCM_C, + AMIXER_PCM_S, + AMIXER_SPDIFI, + AMIXER_LINEIN, + AMIXER_MIC, + AMIXER_SPDIFO, + AMIXER_WAVE_F, + AMIXER_WAVE_R, + AMIXER_WAVE_C, + AMIXER_WAVE_S, + AMIXER_MASTER_F_C, + AMIXER_PCM_F_C, + AMIXER_SPDIFI_C, + AMIXER_LINEIN_C, + AMIXER_MIC_C, + + /* this should always be the last one */ + NUM_CT_AMIXERS +}; + +enum CTALSA_MIXER_CTL { + /* volume control mixers */ + MIXER_MASTER_P, + MIXER_PCM_P, + MIXER_LINEIN_P, + MIXER_MIC_P, + MIXER_SPDIFI_P, + MIXER_SPDIFO_P, + MIXER_WAVEF_P, + MIXER_WAVER_P, + MIXER_WAVEC_P, + MIXER_WAVES_P, + MIXER_MASTER_C, + MIXER_PCM_C, + MIXER_LINEIN_C, + MIXER_MIC_C, + MIXER_SPDIFI_C, + + /* switch control mixers */ + MIXER_PCM_C_S, + MIXER_LINEIN_C_S, + MIXER_MIC_C_S, + MIXER_SPDIFI_C_S, + MIXER_SPDIFO_P_S, + MIXER_WAVEF_P_S, + MIXER_WAVER_P_S, + MIXER_WAVEC_P_S, + MIXER_WAVES_P_S, + MIXER_DIGITAL_IO_S, + MIXER_IEC958_MASK, + MIXER_IEC958_DEFAULT, + MIXER_IEC958_STREAM, + + /* this should always be the last one */ + NUM_CTALSA_MIXERS +}; + +#define VOL_MIXER_START MIXER_MASTER_P +#define VOL_MIXER_END MIXER_SPDIFI_C +#define VOL_MIXER_NUM (VOL_MIXER_END - VOL_MIXER_START + 1) +#define SWH_MIXER_START MIXER_PCM_C_S +#define SWH_MIXER_END MIXER_DIGITAL_IO_S +#define SWH_CAPTURE_START MIXER_PCM_C_S +#define SWH_CAPTURE_END MIXER_SPDIFI_C_S + +#define CHN_NUM 2 + +struct ct_kcontrol_init { + unsigned char ctl; + char *name; +}; + +static struct ct_kcontrol_init +ct_kcontrol_init_table[NUM_CTALSA_MIXERS] = { + [MIXER_MASTER_P] = { + .ctl = 1, + .name = "Master Playback Volume", + }, + [MIXER_MASTER_C] = { + .ctl = 1, + .name = "Master Capture Volume", + }, + [MIXER_PCM_P] = { + .ctl = 1, + .name = "PCM Playback Volume", + }, + [MIXER_PCM_C] = { + .ctl = 1, + .name = "PCM Capture Volume", + }, + [MIXER_LINEIN_P] = { + .ctl = 1, + .name = "Line Playback Volume", + }, + [MIXER_LINEIN_C] = { + .ctl = 1, + .name = "Line Capture Volume", + }, + [MIXER_MIC_P] = { + .ctl = 1, + .name = "Mic Playback Volume", + }, + [MIXER_MIC_C] = { + .ctl = 1, + .name = "Mic Capture Volume", + }, + [MIXER_SPDIFI_P] = { + .ctl = 1, + .name = "IEC958 Playback Volume", + }, + [MIXER_SPDIFI_C] = { + .ctl = 1, + .name = "IEC958 Capture Volume", + }, + [MIXER_SPDIFO_P] = { + .ctl = 1, + .name = "Digital Playback Volume", + }, + [MIXER_WAVEF_P] = { + .ctl = 1, + .name = "Front Playback Volume", + }, + [MIXER_WAVES_P] = { + .ctl = 1, + .name = "Side Playback Volume", + }, + [MIXER_WAVEC_P] = { + .ctl = 1, + .name = "Center/LFE Playback Volume", + }, + [MIXER_WAVER_P] = { + .ctl = 1, + .name = "Surround Playback Volume", + }, + [MIXER_PCM_C_S] = { + .ctl = 1, + .name = "PCM Capture Switch", + }, + [MIXER_LINEIN_C_S] = { + .ctl = 1, + .name = "Line Capture Switch", + }, + [MIXER_MIC_C_S] = { + .ctl = 1, + .name = "Mic Capture Switch", + }, + [MIXER_SPDIFI_C_S] = { + .ctl = 1, + .name = "IEC958 Capture Switch", + }, + [MIXER_SPDIFO_P_S] = { + .ctl = 1, + .name = "Digital Playback Switch", + }, + [MIXER_WAVEF_P_S] = { + .ctl = 1, + .name = "Front Playback Switch", + }, + [MIXER_WAVES_P_S] = { + .ctl = 1, + .name = "Side Playback Switch", + }, + [MIXER_WAVEC_P_S] = { + .ctl = 1, + .name = "Center/LFE Playback Switch", + }, + [MIXER_WAVER_P_S] = { + .ctl = 1, + .name = "Surround Playback Switch", + }, + [MIXER_DIGITAL_IO_S] = { + .ctl = 0, + .name = "Digit-IO Playback Switch", + }, +}; + +static void +ct_mixer_recording_select(struct ct_mixer *mixer, enum CT_AMIXER_CTL type); + +static void +ct_mixer_recording_unselect(struct ct_mixer *mixer, enum CT_AMIXER_CTL type); + +/* 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..be881c639 --- /dev/null +++ b/sound/pci/ctxfi/ctmixer.h @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @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..b36e37ae2 --- /dev/null +++ b/sound/pci/ctxfi/ctpcm.c @@ -0,0 +1,490 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctpcm.c + * + * @Brief + * This file contains the definition of the pcm device functions. + * + * @Author Liu Chun + * @Date Apr 2 2008 + * + */ + +#include "ctpcm.h" +#include "cttimer.h" +#include +#include + +/* Hardware descriptions for playback */ +static const struct snd_pcm_hardware ct_pcm_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_FLOAT_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_192000), + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = (64), + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware ct_spdif_passthru_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000), + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = (64), + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* Hardware descriptions for capture */ +static const struct snd_pcm_hardware ct_pcm_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_FLOAT_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_96000), + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = (384), + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void ct_atc_pcm_interrupt(struct ct_atc_pcm *atc_pcm) +{ + struct ct_atc_pcm *apcm = atc_pcm; + + if (!apcm->substream) + return; + + snd_pcm_period_elapsed(apcm->substream); +} + +static void ct_atc_pcm_free_substream(struct snd_pcm_runtime *runtime) +{ + struct ct_atc_pcm *apcm = runtime->private_data; + struct ct_atc *atc = snd_pcm_substream_chip(apcm->substream); + + atc->pcm_release_resources(atc, apcm); + ct_timer_instance_free(apcm->timer); + kfree(apcm); + runtime->private_data = NULL; +} + +/* pcm playback operations */ +static int ct_pcm_playback_open(struct snd_pcm_substream *substream) +{ + struct ct_atc *atc = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ct_atc_pcm *apcm; + int err; + + apcm = kzalloc(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; + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + /* clear previous resources */ + atc->pcm_release_resources(atc, apcm); + return err; +} + +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); + /* Free snd-allocated pages */ + return snd_pcm_lib_free_pages(substream); +} + + +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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* PCM operators for capture */ +static const struct snd_pcm_ops ct_pcm_capture_ops = { + .open = ct_pcm_capture_open, + .close = ct_pcm_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(atc->pci), 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..178da0dca --- /dev/null +++ b/sound/pci/ctxfi/ctpcm.h @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @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..f05a09ed4 --- /dev/null +++ b/sound/pci/ctxfi/ctresource.c @@ -0,0 +1,293 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctresource.c + * + * @Brief + * This file contains the implementation of some generic helper functions. + * + * @Author Liu Chun + * @Date May 15 2008 + * + */ + +#include "ctresource.h" +#include "cthardware.h" +#include +#include + +#define AUDIO_SLOT_BLOCK_NUM 256 + +/* Resource allocation based on bit-map management mechanism */ +static int +get_resource(u8 *rscs, unsigned int amount, + unsigned int multi, unsigned int *ridx) +{ + int i, j, k, n; + + /* Check whether there are sufficient resources to meet request. */ + for (i = 0, n = multi; i < amount; i++) { + j = i / 8; + k = i % 8; + if (rscs[j] & ((u8)1 << k)) { + n = multi; + continue; + } + if (!(--n)) + break; /* found sufficient contiguous resources */ + } + + if (i >= amount) { + /* Can not find sufficient contiguous resources */ + return -ENOENT; + } + + /* Mark the contiguous bits in resource bit-map as used */ + for (n = multi; n > 0; n--) { + j = i / 8; + k = i % 8; + rscs[j] |= ((u8)1 << k); + i--; + } + + *ridx = i + 1; + + return 0; +} + +static int put_resource(u8 *rscs, unsigned int multi, unsigned int idx) +{ + unsigned int i, j, k, n; + + /* Mark the contiguous bits in resource bit-map as used */ + for (n = multi, i = idx; n > 0; n--) { + j = i / 8; + k = i % 8; + rscs[j] &= ~((u8)1 << k); + i++; + } + + return 0; +} + +int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx) +{ + int err; + + if (n > mgr->avail) + return -ENOENT; + + err = get_resource(mgr->rscs, mgr->amount, n, ridx); + if (!err) + mgr->avail -= n; + + return err; +} + +int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx) +{ + put_resource(mgr->rscs, n, idx); + mgr->avail += n; + + return 0; +} + +static 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..29b6fe6de --- /dev/null +++ b/sound/pci/ctxfi/ctresource.h @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctresource.h + * + * @Brief + * This file contains the definition of generic hardware resources for + * resource management. + * + * @Author Liu Chun + * @Date May 13 2008 + * + */ + +#ifndef CTRESOURCE_H +#define CTRESOURCE_H + +#include + +enum RSCTYP { + SRC, + SRCIMP, + AMIXER, + SUM, + DAIO, + NUM_RSCTYP /* This must be the last one and less than 16 */ +}; + +struct rsc_ops; + +struct rsc { + u32 idx:12; /* The index of a resource */ + u32 type:4; /* The type (RSCTYP) of a resource */ + u32 conj:12; /* Current conjugate index */ + u32 msr:4; /* The Master Sample Rate a resource working on */ + void *ctrl_blk; /* Chip specific control info block for a resource */ + struct hw *hw; /* Chip specific object for hardware access means */ + const struct rsc_ops *ops; /* Generic resource operations */ +}; + +struct rsc_ops { + void (*master)(struct rsc *rsc); /* Move to master resource */ + void (*next_conj)(struct rsc *rsc); /* Move to next conjugate resource */ + int (*index)(const struct rsc *rsc); /* Return the index of resource */ + /* Return the output slot number */ + int (*output_slot)(const struct rsc *rsc); +}; + +int +rsc_init(struct rsc *rsc, u32 idx, enum RSCTYP type, u32 msr, struct hw *hw); +int rsc_uninit(struct rsc *rsc); + +struct rsc_mgr { + enum RSCTYP type; /* The type (RSCTYP) of resource to manage */ + unsigned int amount; /* The total amount of a kind of resource */ + unsigned int avail; /* The amount of currently available resources */ + unsigned char *rscs; /* The bit-map for resource allocation */ + void *ctrl_blk; /* Chip specific control info block */ + struct hw *hw; /* Chip specific object for hardware access */ +}; + +/* Resource management is based on bit-map mechanism */ +int rsc_mgr_init(struct rsc_mgr *mgr, enum RSCTYP type, + unsigned int amount, struct hw *hw); +int rsc_mgr_uninit(struct rsc_mgr *mgr); +int mgr_get_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int *ridx); +int mgr_put_resource(struct rsc_mgr *mgr, unsigned int n, unsigned int idx); + +#endif /* CTRESOURCE_H */ diff --git a/sound/pci/ctxfi/ctsrc.c b/sound/pci/ctxfi/ctsrc.c new file mode 100644 index 000000000..660ca0272 --- /dev/null +++ b/sound/pci/ctxfi/ctsrc.c @@ -0,0 +1,886 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctsrc.c + * + * @Brief + * This file contains the implementation of the Sample Rate Convertor + * resource management object. + * + * @Author Liu Chun + * @Date May 13 2008 + * + */ + +#include "ctsrc.h" +#include "cthardware.h" +#include + +#define SRC_RESOURCE_NUM 256 +#define SRCIMP_RESOURCE_NUM 256 + +static unsigned int conj_mask; + +static int src_default_config_memrd(struct src *src); +static int src_default_config_memwr(struct src *src); +static int src_default_config_arcrw(struct src *src); + +static int (*src_default_config[3])(struct src *) = { + [MEMRD] = src_default_config_memrd, + [MEMWR] = src_default_config_memwr, + [ARCRW] = src_default_config_arcrw +}; + +static int src_set_state(struct src *src, unsigned int state) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_state(src->rsc.ctrl_blk, state); + + return 0; +} + +static int src_set_bm(struct src *src, unsigned int bm) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_bm(src->rsc.ctrl_blk, bm); + + return 0; +} + +static int src_set_sf(struct src *src, unsigned int sf) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_sf(src->rsc.ctrl_blk, sf); + + return 0; +} + +static int src_set_pm(struct src *src, unsigned int pm) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_pm(src->rsc.ctrl_blk, pm); + + return 0; +} + +static int src_set_rom(struct src *src, unsigned int rom) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_rom(src->rsc.ctrl_blk, rom); + + return 0; +} + +static int src_set_vo(struct src *src, unsigned int vo) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_vo(src->rsc.ctrl_blk, vo); + + return 0; +} + +static int src_set_st(struct src *src, unsigned int st) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_st(src->rsc.ctrl_blk, st); + + return 0; +} + +static int src_set_bp(struct src *src, unsigned int bp) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_bp(src->rsc.ctrl_blk, bp); + + return 0; +} + +static int src_set_cisz(struct src *src, unsigned int cisz) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_cisz(src->rsc.ctrl_blk, cisz); + + return 0; +} + +static int src_set_ca(struct src *src, unsigned int ca) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_ca(src->rsc.ctrl_blk, ca); + + return 0; +} + +static int src_set_sa(struct src *src, unsigned int sa) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_sa(src->rsc.ctrl_blk, sa); + + return 0; +} + +static int src_set_la(struct src *src, unsigned int la) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_la(src->rsc.ctrl_blk, la); + + return 0; +} + +static int src_set_pitch(struct src *src, unsigned int pitch) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_pitch(src->rsc.ctrl_blk, pitch); + + return 0; +} + +static int src_set_clear_zbufs(struct src *src) +{ + struct hw *hw; + + hw = src->rsc.hw; + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + return 0; +} + +static int src_commit_write(struct src *src) +{ + struct hw *hw; + int i; + unsigned int dirty = 0; + + hw = src->rsc.hw; + src->rsc.ops->master(&src->rsc); + if (src->rsc.msr > 1) { + /* Save dirty flags for conjugate resource programming */ + dirty = hw->src_get_dirty(src->rsc.ctrl_blk) & conj_mask; + } + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + + /* Program conjugate parameter mixer resources */ + if (MEMWR == src->mode) + return 0; + + for (i = 1; i < src->rsc.msr; i++) { + src->rsc.ops->next_conj(&src->rsc); + hw->src_set_dirty(src->rsc.ctrl_blk, dirty); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static int src_get_ca(struct src *src) +{ + struct hw *hw; + + hw = src->rsc.hw; + return hw->src_get_ca(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); +} + +static int src_init(struct src *src) +{ + src_default_config[src->mode](src); + + return 0; +} + +static struct src *src_next_interleave(struct src *src) +{ + return src->intlv; +} + +static int src_default_config_memrd(struct src *src) +{ + struct hw *hw = src->rsc.hw; + unsigned int rsr, msr; + + hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF); + hw->src_set_bm(src->rsc.ctrl_blk, 1); + for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1) + rsr++; + + hw->src_set_rsr(src->rsc.ctrl_blk, rsr); + hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16); + hw->src_set_wr(src->rsc.ctrl_blk, 0); + hw->src_set_pm(src->rsc.ctrl_blk, 0); + hw->src_set_rom(src->rsc.ctrl_blk, 0); + hw->src_set_vo(src->rsc.ctrl_blk, 0); + hw->src_set_st(src->rsc.ctrl_blk, 0); + hw->src_set_ilsz(src->rsc.ctrl_blk, src->multi - 1); + hw->src_set_cisz(src->rsc.ctrl_blk, 0x80); + hw->src_set_sa(src->rsc.ctrl_blk, 0x0); + hw->src_set_la(src->rsc.ctrl_blk, 0x1000); + hw->src_set_ca(src->rsc.ctrl_blk, 0x80); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + src->rsc.ops->master(&src->rsc); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + + for (msr = 1; msr < src->rsc.msr; msr++) { + src->rsc.ops->next_conj(&src->rsc); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static int src_default_config_memwr(struct src *src) +{ + struct hw *hw = src->rsc.hw; + + hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF); + hw->src_set_bm(src->rsc.ctrl_blk, 1); + hw->src_set_rsr(src->rsc.ctrl_blk, 0); + hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_S16); + hw->src_set_wr(src->rsc.ctrl_blk, 1); + hw->src_set_pm(src->rsc.ctrl_blk, 0); + hw->src_set_rom(src->rsc.ctrl_blk, 0); + hw->src_set_vo(src->rsc.ctrl_blk, 0); + hw->src_set_st(src->rsc.ctrl_blk, 0); + hw->src_set_ilsz(src->rsc.ctrl_blk, 0); + hw->src_set_cisz(src->rsc.ctrl_blk, 0x80); + hw->src_set_sa(src->rsc.ctrl_blk, 0x0); + hw->src_set_la(src->rsc.ctrl_blk, 0x1000); + hw->src_set_ca(src->rsc.ctrl_blk, 0x80); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + src->rsc.ops->master(&src->rsc); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + + return 0; +} + +static int src_default_config_arcrw(struct src *src) +{ + struct hw *hw = src->rsc.hw; + unsigned int rsr, msr; + unsigned int dirty; + + hw->src_set_state(src->rsc.ctrl_blk, SRC_STATE_OFF); + hw->src_set_bm(src->rsc.ctrl_blk, 0); + for (rsr = 0, msr = src->rsc.msr; msr > 1; msr >>= 1) + rsr++; + + hw->src_set_rsr(src->rsc.ctrl_blk, rsr); + hw->src_set_sf(src->rsc.ctrl_blk, SRC_SF_F32); + hw->src_set_wr(src->rsc.ctrl_blk, 0); + hw->src_set_pm(src->rsc.ctrl_blk, 0); + hw->src_set_rom(src->rsc.ctrl_blk, 0); + hw->src_set_vo(src->rsc.ctrl_blk, 0); + hw->src_set_st(src->rsc.ctrl_blk, 0); + hw->src_set_ilsz(src->rsc.ctrl_blk, 0); + hw->src_set_cisz(src->rsc.ctrl_blk, 0x80); + hw->src_set_sa(src->rsc.ctrl_blk, 0x0); + /*hw->src_set_sa(src->rsc.ctrl_blk, 0x100);*/ + hw->src_set_la(src->rsc.ctrl_blk, 0x1000); + /*hw->src_set_la(src->rsc.ctrl_blk, 0x03ffffe0);*/ + hw->src_set_ca(src->rsc.ctrl_blk, 0x80); + hw->src_set_pitch(src->rsc.ctrl_blk, 0x1000000); + hw->src_set_clear_zbufs(src->rsc.ctrl_blk, 1); + + dirty = hw->src_get_dirty(src->rsc.ctrl_blk); + src->rsc.ops->master(&src->rsc); + for (msr = 0; msr < src->rsc.msr; msr++) { + hw->src_set_dirty(src->rsc.ctrl_blk, dirty); + hw->src_commit_write(hw, src->rsc.ops->index(&src->rsc), + src->rsc.ctrl_blk); + src->rsc.ops->next_conj(&src->rsc); + } + src->rsc.ops->master(&src->rsc); + + return 0; +} + +static const struct src_rsc_ops src_rsc_ops = { + .set_state = src_set_state, + .set_bm = src_set_bm, + .set_sf = src_set_sf, + .set_pm = src_set_pm, + .set_rom = src_set_rom, + .set_vo = src_set_vo, + .set_st = src_set_st, + .set_bp = src_set_bp, + .set_cisz = src_set_cisz, + .set_ca = src_set_ca, + .set_sa = src_set_sa, + .set_la = src_set_la, + .set_pitch = src_set_pitch, + .set_clr_zbufs = src_set_clear_zbufs, + .commit_write = src_commit_write, + .get_ca = src_get_ca, + .init = src_init, + .next_interleave = src_next_interleave, +}; + +static int +src_rsc_init(struct src *src, u32 idx, + const struct src_desc *desc, struct src_mgr *mgr) +{ + int err; + int i, n; + struct src *p; + + n = (MEMRD == desc->mode) ? desc->multi : 1; + for (i = 0, p = src; i < n; i++, p++) { + err = rsc_init(&p->rsc, idx + i, SRC, desc->msr, mgr->mgr.hw); + if (err) + goto error1; + + /* Initialize src specific rsc operations */ + p->ops = &src_rsc_ops; + p->multi = (0 == i) ? desc->multi : 1; + p->mode = desc->mode; + src_default_config[desc->mode](p); + mgr->src_enable(mgr, p); + p->intlv = p + 1; + } + (--p)->intlv = NULL; /* Set @intlv of the last SRC to NULL */ + + mgr->commit_write(mgr); + + return 0; + +error1: + for (i--, p--; i >= 0; i--, p--) { + mgr->src_disable(mgr, p); + rsc_uninit(&p->rsc); + } + mgr->commit_write(mgr); + return err; +} + +static int src_rsc_uninit(struct src *src, struct src_mgr *mgr) +{ + int i, n; + struct src *p; + + n = (MEMRD == src->mode) ? src->multi : 1; + for (i = 0, p = src; i < n; i++, p++) { + mgr->src_disable(mgr, p); + rsc_uninit(&p->rsc); + p->multi = 0; + p->ops = NULL; + p->mode = NUM_SRCMODES; + p->intlv = NULL; + } + mgr->commit_write(mgr); + + return 0; +} + +static int +get_src_rsc(struct src_mgr *mgr, const struct src_desc *desc, struct src **rsrc) +{ + unsigned int idx = SRC_RESOURCE_NUM; + int err; + struct src *src; + 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..92944a012 --- /dev/null +++ b/sound/pci/ctxfi/ctsrc.h @@ -0,0 +1,152 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctsrc.h + * + * @Brief + * This file contains the definition of the Sample Rate Convertor + * resource management object. + * + * @Author Liu Chun + * @Date May 13 2008 + * + */ + +#ifndef CTSRC_H +#define CTSRC_H + +#include "ctresource.h" +#include "ctimap.h" +#include +#include +#include + +#define SRC_STATE_OFF 0x0 +#define SRC_STATE_INIT 0x4 +#define SRC_STATE_RUN 0x5 + +#define SRC_SF_U8 0x0 +#define SRC_SF_S16 0x1 +#define SRC_SF_S24 0x2 +#define SRC_SF_S32 0x3 +#define SRC_SF_F32 0x4 + +/* Define the descriptor of a src resource */ +enum SRCMODE { + MEMRD, /* Read data from host memory */ + MEMWR, /* Write data to host memory */ + ARCRW, /* Read from and write to audio ring channel */ + NUM_SRCMODES +}; + +struct src_rsc_ops; + +struct src { + struct rsc rsc; /* Basic resource info */ + struct src *intlv; /* Pointer to next interleaved SRC in a series */ + const struct src_rsc_ops *ops; /* SRC specific operations */ + /* Number of contiguous srcs for interleaved usage */ + unsigned char multi; + unsigned char mode; /* Working mode of this SRC resource */ +}; + +struct src_rsc_ops { + int (*set_state)(struct src *src, unsigned int state); + int (*set_bm)(struct src *src, unsigned int bm); + int (*set_sf)(struct src *src, unsigned int sf); + int (*set_pm)(struct src *src, unsigned int pm); + int (*set_rom)(struct src *src, unsigned int rom); + int (*set_vo)(struct src *src, unsigned int vo); + int (*set_st)(struct src *src, unsigned int st); + int (*set_bp)(struct src *src, unsigned int bp); + int (*set_cisz)(struct src *src, unsigned int cisz); + int (*set_ca)(struct src *src, unsigned int ca); + int (*set_sa)(struct src *src, unsigned int sa); + int (*set_la)(struct src *src, unsigned int la); + int (*set_pitch)(struct src *src, unsigned int pitch); + int (*set_clr_zbufs)(struct src *src); + int (*commit_write)(struct src *src); + int (*get_ca)(struct src *src); + int (*init)(struct src *src); + struct src* (*next_interleave)(struct src *src); +}; + +/* Define src resource request description info */ +struct src_desc { + /* Number of contiguous master srcs for interleaved usage */ + unsigned char multi; + unsigned char msr; + unsigned char mode; /* Working mode of the requested srcs */ +}; + +/* Define src manager object */ +struct src_mgr { + struct rsc_mgr mgr; /* Basic resource manager info */ + struct snd_card *card; /* pointer to this card */ + spinlock_t mgr_lock; + + /* request src resource */ + int (*get_src)(struct src_mgr *mgr, + const struct src_desc *desc, struct src **rsrc); + /* return src resource */ + int (*put_src)(struct src_mgr *mgr, struct src *src); + int (*src_enable_s)(struct src_mgr *mgr, struct src *src); + int (*src_enable)(struct src_mgr *mgr, struct src *src); + int (*src_disable)(struct src_mgr *mgr, struct src *src); + int (*commit_write)(struct src_mgr *mgr); +}; + +/* Define the descriptor of a SRC Input Mapper resource */ +struct srcimp_mgr; +struct srcimp_rsc_ops; + +struct srcimp { + struct rsc rsc; + unsigned char idx[8]; + 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..2099e9ce4 --- /dev/null +++ b/sound/pci/ctxfi/cttimer.c @@ -0,0 +1,442 @@ +/* + * PCM timer handling on ctxfi + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + */ + +#include +#include +#include +#include +#include +#include "ctatc.h" +#include "cthardware.h" +#include "cttimer.h" + +static bool use_system_timer; +MODULE_PARM_DESC(use_system_timer, "Force to use system-timer"); +module_param(use_system_timer, bool, 0444); + +struct ct_timer_ops { + void (*init)(struct ct_timer_instance *); + void (*prepare)(struct ct_timer_instance *); + void (*start)(struct ct_timer_instance *); + void (*stop)(struct ct_timer_instance *); + void (*free_instance)(struct ct_timer_instance *); + void (*interrupt)(struct ct_timer *); + void (*free_global)(struct ct_timer *); +}; + +/* timer instance -- assigned to each PCM stream */ +struct ct_timer_instance { + spinlock_t lock; + struct ct_timer *timer_base; + struct ct_atc_pcm *apcm; + struct snd_pcm_substream *substream; + struct timer_list timer; + struct list_head instance_list; + struct list_head running_list; + unsigned int position; + unsigned int frag_count; + unsigned int running:1; + unsigned int need_update:1; +}; + +/* timer instance manager */ +struct ct_timer { + spinlock_t lock; /* global timer lock (for xfitimer) */ + spinlock_t list_lock; /* lock for instance list */ + struct ct_atc *atc; + const struct ct_timer_ops *ops; + struct list_head instance_head; + struct list_head running_head; + unsigned int wc; /* current wallclock */ + unsigned int irq_handling:1; /* in IRQ handling */ + unsigned int reprogram:1; /* need to reprogram the internval */ + unsigned int running:1; /* global timer running */ +}; + + +/* + * system-timer-based updates + */ + +static void ct_systimer_callback(struct timer_list *t) +{ + struct ct_timer_instance *ti = 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 +#include +#include + +struct snd_pcm_substream; +struct ct_atc; +struct ct_atc_pcm; + +struct ct_timer; +struct ct_timer_instance; + +struct ct_timer *ct_timer_new(struct ct_atc *atc); +void ct_timer_free(struct ct_timer *atimer); + +struct ct_timer_instance * +ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm); +void ct_timer_instance_free(struct ct_timer_instance *ti); +void ct_timer_start(struct ct_timer_instance *ti); +void ct_timer_stop(struct ct_timer_instance *ti); +void ct_timer_prepare(struct ct_timer_instance *ti); + +#endif /* __CTTIMER_H */ diff --git a/sound/pci/ctxfi/ctvmem.c b/sound/pci/ctxfi/ctvmem.c new file mode 100644 index 000000000..520e19bc6 --- /dev/null +++ b/sound/pci/ctxfi/ctvmem.c @@ -0,0 +1,245 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @File ctvmem.c + * + * @Brief + * This file contains the implementation of virtual memory management object + * for card device. + * + * @Author Liu Chun + * @Date Apr 1 2008 + */ + +#include "ctvmem.h" +#include "ctatc.h" +#include +#include +#include +#include + +#define CT_PTES_PER_PAGE (CT_PAGE_SIZE / sizeof(void *)) +#define CT_ADDRS_PER_PAGE (CT_PTES_PER_PAGE * CT_PAGE_SIZE) + +/* * + * Find or create vm block based on requested @size. + * @size must be page aligned. + * */ +static struct ct_vm_block * +get_vm_block(struct ct_vm *vm, unsigned int size, struct ct_atc *atc) +{ + struct ct_vm_block *block = 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, + snd_dma_pci_data(pci), + 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..e6da60eb1 --- /dev/null +++ b/sound/pci/ctxfi/ctvmem.h @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + * @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 +#include +#include +#include + +/* The chip can handle the page table of 4k pages + * (emu20k1 can handle even 8k pages, but we don't use it right now) + */ +#define CT_PAGE_SIZE 4096 +#define CT_PAGE_SHIFT 12 +#define CT_PAGE_MASK (~(PAGE_SIZE - 1)) +#define CT_PAGE_ALIGN(addr) ALIGN(addr, CT_PAGE_SIZE) + +struct ct_vm_block { + unsigned int addr; /* starting logical addr of this block */ + unsigned int size; /* size of this device virtual mem block */ + struct list_head list; +}; + +struct snd_pcm_substream; + +/* Virtual memory management object for card device */ +struct ct_vm { + struct snd_dma_buffer ptp[CT_PTP_NUM]; /* Device page table pages */ + unsigned int size; /* Available addr space in bytes */ + struct list_head unused; /* List of unused blocks */ + struct list_head used; /* List of used blocks */ + struct mutex lock; + + /* Map host addr (kmalloced/vmalloced) to device logical addr. */ + struct ct_vm_block *(*map)(struct ct_vm *, struct snd_pcm_substream *, + int size); + /* Unmap device logical addr area. */ + void (*unmap)(struct ct_vm *, struct ct_vm_block *block); + dma_addr_t (*get_ptp_phys)(struct ct_vm *vm, int index); +}; + +int ct_vm_create(struct ct_vm **rvm, struct pci_dev *pci); +void ct_vm_destroy(struct ct_vm *vm); + +#endif /* CTVMEM_H */ diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c new file mode 100644 index 000000000..b2874220b --- /dev/null +++ b/sound/pci/ctxfi/xfi.c @@ -0,0 +1,162 @@ +/* + * xfi linux driver. + * + * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved. + * + * This source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "ctatc.h" +#include "cthardware.h" + +MODULE_AUTHOR("Creative Technology Ltd"); +MODULE_DESCRIPTION("X-Fi driver version 1.03"); +MODULE_LICENSE("GPL v2"); +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 +# + +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..c95da6301 --- /dev/null +++ b/sound/pci/echoaudio/darla20.c @@ -0,0 +1,101 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_DARLA20 +#define ECHOCARD_NAME "Darla20" +#define ECHOCARD_HAS_MONITOR + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 0 */ +#define BX_ANALOG_IN 8 /* 2 */ +#define BX_DIGITAL_IN 10 /* 0 */ +#define BX_NUM 10 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/darla20_dsp.fw"); + +#define FW_DARLA20_DSP 0 + +static const struct firmware card_fw[] = { + {0, "darla20_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x1801, 0xECC0, 0x0010, 0, 0, 0}, /* DSP 56301 Darla20 rev.0 */ + {0,} +}; + +static 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 + +****************************************************************************/ + + +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..3013b4daa --- /dev/null +++ b/sound/pci/echoaudio/darla24.c @@ -0,0 +1,108 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_DARLA24 +#define ECHOCARD_NAME "Darla24" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_SUPER_INTERLEAVE + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 0 */ +#define BX_ANALOG_IN 8 /* 2 */ +#define BX_DIGITAL_IN 10 /* 0 */ +#define BX_NUM 10 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/darla24_dsp.fw"); + +#define FW_DARLA24_DSP 0 + +static const struct firmware card_fw[] = { + {0, "darla24_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {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 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 + +****************************************************************************/ + + +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..1f34a07b0 --- /dev/null +++ b/sound/pci/echoaudio/echo3g.c @@ -0,0 +1,122 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHO3G_FAMILY +#define ECHOCARD_ECHO3G +#define ECHOCARD_NAME "Echo3G" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 +#define ECHOCARD_HAS_MIDI +#define ECHOCARD_HAS_PHANTOM_POWER + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 +#define PX_DIGITAL_OUT chip->px_digital_out +#define PX_ANALOG_IN chip->px_analog_in +#define PX_DIGITAL_IN chip->px_digital_in +#define PX_NUM chip->px_num + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 +#define BX_DIGITAL_OUT chip->bx_digital_out +#define BX_ANALOG_IN chip->bx_analog_in +#define BX_DIGITAL_IN chip->bx_digital_in +#define BX_NUM chip->bx_num + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/echo3g_dsp.fw"); +MODULE_FIRMWARE("ea/3g_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_ECHO3G_DSP 1 +#define FW_3G_ASIC 2 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "echo3g_dsp.fw"}, + {0, "3g_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x3410, 0xECC0, 0x0100, 0, 0, 0}, /* Echo 3G */ + {0,} +}; + +static 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 + +****************************************************************************/ + +static int load_asic(struct echoaudio *chip); +static int dsp_set_digital_mode(struct echoaudio *chip, u8 mode); +static int set_digital_mode(struct echoaudio *chip, u8 mode); +static int check_asic_status(struct echoaudio *chip); +static int set_sample_rate(struct echoaudio *chip, u32 rate); +static int set_input_clock(struct echoaudio *chip, u16 clock); +static int set_professional_spdif(struct echoaudio *chip, char prof); +static int set_phantom_power(struct echoaudio *chip, char on); +static int write_control_reg(struct echoaudio *chip, u32 ctl, u32 frq, + char force); + +#include + +static int init_hw(struct echoaudio *chip, u16 device_id, u16 subdevice_id) +{ + int err; + + local_irq_enable(); + if (snd_BUG_ON((subdevice_id & 0xfff0) != ECHO3G)) + return -ENODEV; + + 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..f32c55fff --- /dev/null +++ b/sound/pci/echoaudio/echoaudio.c @@ -0,0 +1,2293 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +MODULE_AUTHOR("Giuliano Pochini "); +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 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; + + if (!chip->can_set_rate) { + snd_interval_any(&fixed); + fixed.min = fixed.max = chip->sample_rate; + return snd_interval_refine(rate, &fixed); + } + return 0; +} + + +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; + + /* Finally allocate a page for the scatter-gather list */ + if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + PAGE_SIZE, &pipe->sgpage)) < 0) { + dev_err(chip->card->dev, "s-g list allocation failed\n"); + return err; + } + + 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; + atomic_inc(&chip->opencount); + if (atomic_read(&chip->opencount) > 1 && chip->rate_set) + chip->can_set_rate=0; + dev_dbg(chip->card->dev, "pcm_analog_in_open cs=%d oc=%d r=%d\n", + chip->can_set_rate, atomic_read(&chip->opencount), + chip->sample_rate); + 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; + atomic_inc(&chip->opencount); + if (atomic_read(&chip->opencount) > 1 && chip->rate_set) + chip->can_set_rate=0; + dev_dbg(chip->card->dev, "pcm_analog_out_open cs=%d oc=%d r=%d\n", + chip->can_set_rate, atomic_read(&chip->opencount), + chip->sample_rate); + 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; + + atomic_inc(&chip->opencount); + if (atomic_read(&chip->opencount) > 1 && chip->rate_set) + chip->can_set_rate=0; + +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; + atomic_inc(&chip->opencount); + if (atomic_read(&chip->opencount) > 1 && chip->rate_set) + chip->can_set_rate=0; +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); + int oc; + + /* Nothing to do here. Audio is already off and pipe will be + * freed by its callback + */ + + atomic_dec(&chip->opencount); + oc = atomic_read(&chip->opencount); + dev_dbg(chip->card->dev, "pcm_close oc=%d cs=%d rs=%d\n", oc, + chip->can_set_rate, chip->rate_set); + if (oc < 2) + chip->can_set_rate = 1; + if (oc == 0) + chip->rate_set = 0; + dev_dbg(chip->card->dev, "pcm_close2 oc=%d cs=%d rs=%d\n", oc, + chip->can_set_rate, chip->rate_set); + + 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)); + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) { + dev_err(chip->card->dev, "malloc_pages err=%d\n", err); + spin_lock_irq(&chip->lock); + free_pipes(chip, pipe); + spin_unlock_irq(&chip->lock); + pipe->index = -1; + return err; + } + + 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 + */ + chip->last_period[pipe_index] = 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); + + snd_pcm_lib_free_pages(substream); + 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; + /* fall through */ + 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; + if (snd_BUG_ON(!is_pipe_allocated(chip, pipe_index))) + return -EINVAL; + set_audio_format(chip, pipe_index, &format); + return 0; +} + + + +static int pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct echoaudio *chip = snd_pcm_substream_chip(substream); + struct audiopipe *pipe; + int i, err; + u32 channelmask = 0; + struct snd_pcm_substream *s; + + snd_pcm_group_for_each_entry(s, substream) { + for (i = 0; i < DSP_MAXPIPES; i++) { + if (s == chip->substream[i]) { + channelmask |= 1 << i; + snd_pcm_trigger_done(s, substream); + } + } + } + + 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: + chip->last_period[i] = 0; + pipe->last_counter = 0; + pipe->position = 0; + *pipe->dma_counter = 0; + /* fall through */ + 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; + size_t cnt, bufsize, pos; + + cnt = le32_to_cpu(*pipe->dma_counter); + pipe->position += cnt - pipe->last_counter; + pipe->last_counter = cnt; + bufsize = substream->runtime->buffer_size; + pos = bytes_to_frames(substream->runtime, pipe->position); + + while (pos >= bufsize) { + pipe->position -= frames_to_bytes(substream->runtime, bufsize); + pos -= bufsize; + } + return pos; +} + + + +/* pcm *_ops structures */ +static const struct snd_pcm_ops analog_playback_ops = { + .open = pcm_analog_out_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_analog_out_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; +static const struct snd_pcm_ops analog_capture_ops = { + .open = pcm_analog_in_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_analog_in_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; +#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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_digital_out_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; +#endif /* !ECHOCARD_HAS_VMIXER */ +static const struct snd_pcm_ops digital_capture_ops = { + .open = pcm_digital_in_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_digital_in_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; +#endif /* ECHOCARD_HAS_DIGITAL_IO */ + + + +/* Preallocate memory only for the first substream because it's the most + * used one + */ +static int snd_echo_preallocate_pages(struct snd_pcm *pcm, struct device *dev) +{ + struct snd_pcm_substream *ss; + int stream, err; + + for (stream = 0; stream < 2; stream++) + for (ss = pcm->streams[stream].substream; ss; ss = ss->next) { + err = snd_pcm_lib_preallocate_pages(ss, SNDRV_DMA_TYPE_DEV_SG, + dev, + ss->number ? 0 : 128<<10, + 256<<10); + if (err < 0) + return err; + } + return 0; +} + + + +/*<--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); + if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0) + return err; + +#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); + if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0) + return err; +#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); + if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0) + return err; + +#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); + if ((err = snd_echo_preallocate_pages(pcm, snd_dma_pci_data(chip->pci))) < 0) + return err; +#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) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = ECHOGAIN_MINOUT; + uinfo->value.integer.max = ECHOGAIN_MAXOUT; + uinfo->dimen.d[0] = num_busses_out(chip); + uinfo->dimen.d[1] = num_busses_in(chip); + 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) +{ + struct echoaudio *chip; + + chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = ECHOGAIN_MINOUT; + uinfo->value.integer.max = ECHOGAIN_MAXOUT; + uinfo->dimen.d[0] = num_busses_out(chip); + uinfo->dimen.d[1] = num_pipes_out(chip); + 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 (atomic_read(&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; +#ifdef ECHOCARD_HAS_VMIXER + uinfo->dimen.d[0] = 3; /* Out, In, Virt */ +#else + uinfo->dimen.d[0] = 2; /* Out, In */ +#endif + uinfo->dimen.d[1] = 16; /* 16 channels */ + uinfo->dimen.d[2] = 2; /* 0=level, 1=peak */ + 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 Handler +******************************************************************************/ + +static irqreturn_t snd_echo_interrupt(int irq, void *dev_id) +{ + struct echoaudio *chip = dev_id; + struct snd_pcm_substream *substream; + int period, 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++) { + substream = chip->substream[ss]; + if (substream && ((struct audiopipe *)substream->runtime-> + private_data)->state == PIPE_STATE_STARTED) { + period = pcm_pointer(substream) / + substream->runtime->period_size; + if (period != chip->last_period[ss]) { + chip->last_period[ss] = period; + 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 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; + atomic_set(&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 = (volatile u32 __iomem *) + ioremap_nocache(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; + 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, snd_dma_pci_data(chip->pci), + 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; + int i, 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); + + snd_pcm_suspend_all(chip->analog_pcm); + snd_pcm_suspend_all(chip->digital_pcm); + +#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; + 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_bak = kmalloc(sizeof(*commpage), GFP_KERNEL); + if (commpage_bak == NULL) + return -ENOMEM; + commpage = chip->comm_page; + memcpy(commpage_bak, commpage, sizeof(*commpage)); + + 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; + 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..be4d04893 --- /dev/null +++ b/sound/pci/echoaudio/echoaudio.h @@ -0,0 +1,562 @@ +/**************************************************************************** + + 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 + + **************************************************************************** + + + Here's a block diagram of how most of the cards work: + + +-----------+ + record | |<-------------------- Inputs + <-------| | | + PCI | Transport | | + bus | engine | \|/ + ------->| | +-------+ + play | |--->|monitor|-------> Outputs + +-----------+ | mixer | + +-------+ + + The lines going to and from the PCI bus represent "pipes". A pipe performs + audio transport - moving audio data to and from buffers on the host via + bus mastering. + + The inputs and outputs on the right represent input and output "busses." + A bus is a physical, real connection to the outside world. An example + of a bus would be the 1/4" analog connectors on the back of Layla or + an RCA S/PDIF connector. + + For most cards, there is a one-to-one correspondence between outputs + and busses; that is, each individual pipe is hard-wired to a single bus. + + Cards that work this way are Darla20, Gina20, Layla20, Darla24, Gina24, + Layla24, Mona, and Indigo. + + + Mia has a feature called "virtual outputs." + + + +-----------+ + record | |<----------------------------- Inputs + <-------| | | + PCI | Transport | | + bus | engine | \|/ + ------->| | +------+ +-------+ + play | |-->|vmixer|-->|monitor|-------> Outputs + +-----------+ +------+ | mixer | + +-------+ + + + Obviously, the difference here is the box labeled "vmixer." Vmixer is + short for "virtual output mixer." For Mia, pipes are *not* hard-wired + to a single bus; the vmixer lets you mix any pipe to any bus in any + combination. + + Note, however, that the left-hand side of the diagram is unchanged. + Transport works exactly the same way - the difference is in the mixer stage. + + + Pipes and busses are numbered starting at zero. + + + + Pipe index + ========== + + A number of calls in CEchoGals refer to a "pipe index". A pipe index is + a unique number for a pipe that unambiguously refers to a playback or record + pipe. Pipe indices are numbered starting with analog outputs, followed by + digital outputs, then analog inputs, then digital inputs. + + Take Gina24 as an example: + + Pipe index + + 0-7 Analog outputs (0 .. FirstDigitalBusOut-1) + 8-15 Digital outputs (FirstDigitalBusOut .. NumBussesOut-1) + 16-17 Analog inputs + 18-25 Digital inputs + + + You get the pipe index by calling CEchoGals::OpenAudio; the other transport + functions take the pipe index as a parameter. If you need a pipe index for + some other reason, use the handy Makepipe_index method. + + + Some calls take a CChannelMask parameter; CChannelMask is a handy way to + group pipe indices. + + + + Digital mode switch + =================== + + Some cards (right now, Gina24, Layla24, and Mona) have a Digital Mode Switch + or DMS. Cards with a DMS can be set to one of three mutually exclusive + digital modes: S/PDIF RCA, S/PDIF optical, or ADAT optical. + + This may create some confusion since ADAT optical is 8 channels wide and + S/PDIF is only two channels wide. Gina24, Layla24, and Mona handle this + by acting as if they always have 8 digital outs and ins. If you are in + either S/PDIF mode, the last 6 channels don't do anything - data sent + out these channels is thrown away and you will always record zeros. + + Note that with Gina24, Layla24, and Mona, sample rates above 50 kHz are + only available if you have the card configured for S/PDIF optical or S/PDIF + RCA. + + + + Double speed mode + ================= + + Some of the cards support 88.2 kHz and 96 kHz sampling (Darla24, Gina24, + Layla24, Mona, Mia, and Indigo). For these cards, the driver sometimes has + to worry about "double speed mode"; double speed mode applies whenever the + sampling rate is above 50 kHz. + + For instance, Mona and Layla24 support word clock sync. However, they + actually support two different word clock modes - single speed (below + 50 kHz) and double speed (above 50 kHz). The hardware detects if a single + or double speed word clock signal is present; the generic code uses that + information to determine which mode to use. + + The generic code takes care of all this for you. +*/ + + +#ifndef _ECHOAUDIO_H_ +#define _ECHOAUDIO_H_ + + +#include "echoaudio_dsp.h" + + + +/*********************************************************************** + + PCI configuration space + +***********************************************************************/ + +/* + * PCI vendor ID and device IDs for the hardware + */ +#define VENDOR_ID 0x1057 +#define DEVICE_ID_56301 0x1801 +#define DEVICE_ID_56361 0x3410 +#define SUBVENDOR_ID 0xECC0 + + +/* + * Valid Echo PCI subsystem card IDs + */ +#define DARLA20 0x0010 +#define GINA20 0x0020 +#define LAYLA20 0x0030 +#define DARLA24 0x0040 +#define GINA24 0x0050 +#define LAYLA24 0x0060 +#define MONA 0x0070 +#define MIA 0x0080 +#define INDIGO 0x0090 +#define INDIGO_IO 0x00a0 +#define INDIGO_DJ 0x00b0 +#define DC8 0x00c0 +#define INDIGO_IOX 0x00d0 +#define INDIGO_DJX 0x00e0 +#define ECHO3G 0x0100 + + +/************************************************************************ + + Array sizes and so forth + +***********************************************************************/ + +/* + * Sizes + */ +#define ECHO_MAXAUDIOINPUTS 32 /* Max audio input channels */ +#define ECHO_MAXAUDIOOUTPUTS 32 /* Max audio output channels */ +#define ECHO_MAXAUDIOPIPES 32 /* Max number of input and output + * pipes */ +#define E3G_MAX_OUTPUTS 16 +#define ECHO_MAXMIDIJACKS 1 /* Max MIDI ports */ +#define ECHO_MIDI_QUEUE_SZ 512 /* Max MIDI input queue entries */ +#define ECHO_MTC_QUEUE_SZ 32 /* Max MIDI time code input queue + * entries */ + +/* + * MIDI activity indicator timeout + */ +#define MIDI_ACTIVITY_TIMEOUT_USEC 200000 + + +/**************************************************************************** + + Clocks + +*****************************************************************************/ + +/* + * Clock numbers + */ +#define ECHO_CLOCK_INTERNAL 0 +#define ECHO_CLOCK_WORD 1 +#define ECHO_CLOCK_SUPER 2 +#define ECHO_CLOCK_SPDIF 3 +#define ECHO_CLOCK_ADAT 4 +#define ECHO_CLOCK_ESYNC 5 +#define ECHO_CLOCK_ESYNC96 6 +#define ECHO_CLOCK_MTC 7 +#define ECHO_CLOCK_NUMBER 8 +#define ECHO_CLOCKS 0xffff + +/* + * Clock bit numbers - used to report capabilities and whatever clocks + * are being detected dynamically. + */ +#define ECHO_CLOCK_BIT_INTERNAL (1 << ECHO_CLOCK_INTERNAL) +#define ECHO_CLOCK_BIT_WORD (1 << ECHO_CLOCK_WORD) +#define ECHO_CLOCK_BIT_SUPER (1 << ECHO_CLOCK_SUPER) +#define ECHO_CLOCK_BIT_SPDIF (1 << ECHO_CLOCK_SPDIF) +#define ECHO_CLOCK_BIT_ADAT (1 << ECHO_CLOCK_ADAT) +#define ECHO_CLOCK_BIT_ESYNC (1 << ECHO_CLOCK_ESYNC) +#define ECHO_CLOCK_BIT_ESYNC96 (1 << ECHO_CLOCK_ESYNC96) +#define ECHO_CLOCK_BIT_MTC (1<comm_page->handshake = 0; +} + +static inline u32 get_dsp_register(struct echoaudio *chip, u32 index) +{ + return readl(&chip->dsp_registers[index]); +} + +static inline void set_dsp_register(struct echoaudio *chip, u32 index, + u32 value) +{ + writel(value, &chip->dsp_registers[index]); +} + + +/* Pipe and bus indexes. PX_* and BX_* are defined as chip->px_* and chip->bx_* +for 3G cards because they depend on the external box. They are integer +constants for all other cards. +Never use those defines directly, use the following functions instead. */ + +static inline int px_digital_out(const struct echoaudio *chip) +{ + return PX_DIGITAL_OUT; +} + +static inline int px_analog_in(const struct echoaudio *chip) +{ + return PX_ANALOG_IN; +} + +static inline int px_digital_in(const struct echoaudio *chip) +{ + return PX_DIGITAL_IN; +} + +static inline int px_num(const struct echoaudio *chip) +{ + return PX_NUM; +} + +static inline int bx_digital_out(const struct echoaudio *chip) +{ + return BX_DIGITAL_OUT; +} + +static inline int bx_analog_in(const struct echoaudio *chip) +{ + return BX_ANALOG_IN; +} + +static inline int bx_digital_in(const struct echoaudio *chip) +{ + return BX_DIGITAL_IN; +} + +static inline int bx_num(const struct echoaudio *chip) +{ + return BX_NUM; +} + +static inline int num_pipes_out(const struct echoaudio *chip) +{ + return px_analog_in(chip); +} + +static inline int num_pipes_in(const struct echoaudio *chip) +{ + return px_num(chip) - px_analog_in(chip); +} + +static inline int num_busses_out(const struct echoaudio *chip) +{ + return bx_analog_in(chip); +} + +static inline int num_busses_in(const struct echoaudio *chip) +{ + return bx_num(chip) - bx_analog_in(chip); +} + +static inline int num_analog_busses_out(const struct echoaudio *chip) +{ + return bx_digital_out(chip); +} + +static inline int num_analog_busses_in(const struct echoaudio *chip) +{ + return bx_digital_in(chip) - bx_analog_in(chip); +} + +static inline int num_digital_busses_out(const struct echoaudio *chip) +{ + return num_busses_out(chip) - num_analog_busses_out(chip); +} + +static inline int num_digital_busses_in(const struct echoaudio *chip) +{ + return num_busses_in(chip) - num_analog_busses_in(chip); +} + +/* The monitor array is a one-dimensional array; compute the offset + * into the array */ +static inline int monitor_index(const struct echoaudio *chip, int out, int in) +{ + return out * num_busses_in(chip) + in; +} + +#endif /* _ECHOAUDIO_H_ */ diff --git a/sound/pci/echoaudio/echoaudio_3g.c b/sound/pci/echoaudio/echoaudio_3g.c new file mode 100644 index 000000000..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 + +****************************************************************************/ + + + +/* 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..b181752b8 --- /dev/null +++ b/sound/pci/echoaudio/echoaudio_dsp.c @@ -0,0 +1,1161 @@ +/**************************************************************************** + + 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 + +****************************************************************************/ + +#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) +{ + int i, m, n; + + m = 0; + n = 0; + for (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]; + } + for (; n < 32; n++) + meters[n] = 0; + +#ifdef ECHOCARD_ECHO3G + m = E3G_MAX_OUTPUTS; /* Skip unused meters */ +#endif + + for (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]; + } + for (; n < 64; n++) + meters[n] = 0; + +#ifdef ECHOCARD_HAS_VMIXER + for (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 + for (; n < 96; n++) + meters[n] = 0; +} + + + +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_warn(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_warn(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; + char is_cyclic; + + dev_dbg(chip->card->dev, + "allocate_pipes: ch=%d int=%d\n", pipe_index, interleave); + + if (chip->bad_board) + return -EIO; + + is_cyclic = 1; /* This driver uses cyclic buffers only */ + + 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; + if (is_cyclic) + 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 + +****************************************************************************/ + +#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 + +****************************************************************************/ + + +/* 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..67bd0c9fc --- /dev/null +++ b/sound/pci/echoaudio/gina20.c @@ -0,0 +1,105 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_GINA20 +#define ECHOCARD_NAME "Gina20" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_INPUT_GAIN +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT false + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 2 */ +#define PX_ANALOG_IN 10 /* 2 */ +#define PX_DIGITAL_IN 12 /* 2 */ +#define PX_NUM 14 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 2 */ +#define BX_ANALOG_IN 10 /* 2 */ +#define BX_DIGITAL_IN 12 /* 2 */ +#define BX_NUM 14 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/gina20_dsp.fw"); + +#define FW_GINA20_DSP 0 + +static const struct firmware card_fw[] = { + {0, "gina20_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x1801, 0xECC0, 0x0020, 0, 0, 0}, /* DSP 56301 Gina20 rev.0 */ + {0,} +}; + +static 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 + +****************************************************************************/ + + +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..b1bcacaef --- /dev/null +++ b/sound/pci/echoaudio/gina24.c @@ -0,0 +1,129 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHO24_FAMILY +#define ECHOCARD_GINA24 +#define ECHOCARD_NAME "Gina24" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 8 */ +#define PX_ANALOG_IN 16 /* 2 */ +#define PX_DIGITAL_IN 18 /* 8 */ +#define PX_NUM 26 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 8 */ +#define BX_ANALOG_IN 16 /* 2 */ +#define BX_DIGITAL_IN 18 /* 8 */ +#define BX_NUM 26 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/gina24_301_dsp.fw"); +MODULE_FIRMWARE("ea/gina24_361_dsp.fw"); +MODULE_FIRMWARE("ea/gina24_301_asic.fw"); +MODULE_FIRMWARE("ea/gina24_361_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_GINA24_301_DSP 1 +#define FW_GINA24_361_DSP 2 +#define FW_GINA24_301_ASIC 3 +#define FW_GINA24_361_ASIC 4 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "gina24_301_dsp.fw"}, + {0, "gina24_361_dsp.fw"}, + {0, "gina24_301_asic.fw"}, + {0, "gina24_361_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {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 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 + +****************************************************************************/ + + +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..175af9b14 --- /dev/null +++ b/sound/pci/echoaudio/indigo.c @@ -0,0 +1,107 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO +#define ECHOCARD_NAME "Indigo" +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 0 */ +#define PX_DIGITAL_IN 8 /* 0 */ +#define PX_NUM 8 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 0 */ +#define BX_ANALOG_IN 2 /* 0 */ +#define BX_DIGITAL_IN 2 /* 0 */ +#define BX_NUM 2 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x3410, 0xECC0, 0x0090, 0, 0, 0}, /* Indigo */ + {0,} +}; + +static 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 + +****************************************************************************/ + + +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 + +*************************************************************************/ + +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..8c60314e4 --- /dev/null +++ b/sound/pci/echoaudio/indigodj.c @@ -0,0 +1,107 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_DJ +#define ECHOCARD_NAME "Indigo DJ" +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 0 */ +#define PX_DIGITAL_IN 8 /* 0 */ +#define PX_NUM 8 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 4 */ +#define BX_DIGITAL_OUT 4 /* 0 */ +#define BX_ANALOG_IN 4 /* 0 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_dj_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_DJ_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_dj_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x3410, 0xECC0, 0x00B0, 0, 0, 0}, /* Indigo DJ*/ + {0,} +}; + +static 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 + +****************************************************************************/ + + +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..201688ee5 --- /dev/null +++ b/sound/pci/echoaudio/indigodjx.c @@ -0,0 +1,108 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2009 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_DJX +#define ECHOCARD_NAME "Indigo DJx" +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 0 */ +#define PX_DIGITAL_IN 8 /* 0 */ +#define PX_NUM 8 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 4 */ +#define BX_DIGITAL_OUT 4 /* 0 */ +#define BX_ANALOG_IN 4 /* 0 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_djx_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_DJX_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_djx_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x3410, 0xECC0, 0x00E0, 0, 0, 0}, /* Indigo DJx*/ + {0,} +}; + +static 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 + +*************************************************************************/ + +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..f7618edfd --- /dev/null +++ b/sound/pci/echoaudio/indigoio.c @@ -0,0 +1,108 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_IO +#define ECHOCARD_NAME "Indigo IO" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 0 */ +#define BX_ANALOG_IN 2 /* 2 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_io_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_IO_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_io_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x3410, 0xECC0, 0x00A0, 0, 0, 0}, /* Indigo IO*/ + {0,} +}; + +static 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 + +****************************************************************************/ + + +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..e145b6881 --- /dev/null +++ b/sound/pci/echoaudio/indigoiox.c @@ -0,0 +1,110 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2009 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define INDIGO_FAMILY +#define ECHOCARD_INDIGO_IOX +#define ECHOCARD_NAME "Indigo IOx" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 0 */ +#define PX_NUM 10 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 0 */ +#define BX_ANALOG_IN 2 /* 2 */ +#define BX_DIGITAL_IN 4 /* 0 */ +#define BX_NUM 4 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/indigo_iox_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_INDIGO_IOX_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "indigo_iox_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x3410, 0xECC0, 0x00D0, 0, 0, 0}, /* Indigo IOx */ + {0,} +}; + +static 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 + +*************************************************************************/ + +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..fc8468dab --- /dev/null +++ b/sound/pci/echoaudio/layla20.c @@ -0,0 +1,115 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHOGALS_FAMILY +#define ECHOCARD_LAYLA20 +#define ECHOCARD_NAME "Layla20" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_GAIN +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT false +#define ECHOCARD_HAS_OUTPUT_CLOCK_SWITCH +#define ECHOCARD_HAS_MIDI + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 10 */ +#define PX_DIGITAL_OUT 10 /* 2 */ +#define PX_ANALOG_IN 12 /* 8 */ +#define PX_DIGITAL_IN 20 /* 2 */ +#define PX_NUM 22 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 10 */ +#define BX_DIGITAL_OUT 10 /* 2 */ +#define BX_ANALOG_IN 12 /* 8 */ +#define BX_DIGITAL_IN 20 /* 2 */ +#define BX_NUM 22 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/layla20_dsp.fw"); +MODULE_FIRMWARE("ea/layla20_asic.fw"); + +#define FW_LAYLA20_DSP 0 +#define FW_LAYLA20_ASIC 1 + +static const struct firmware card_fw[] = { + {0, "layla20_dsp.fw"}, + {0, "layla20_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {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 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 + +****************************************************************************/ + + +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..6e4023728 --- /dev/null +++ b/sound/pci/echoaudio/layla24.c @@ -0,0 +1,127 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHO24_FAMILY +#define ECHOCARD_LAYLA24 +#define ECHOCARD_NAME "Layla24" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 +#define ECHOCARD_HAS_MIDI + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 8 */ +#define PX_ANALOG_IN 16 /* 8 */ +#define PX_DIGITAL_IN 24 /* 8 */ +#define PX_NUM 32 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 8 */ +#define BX_DIGITAL_OUT 8 /* 8 */ +#define BX_ANALOG_IN 16 /* 8 */ +#define BX_DIGITAL_IN 24 /* 8 */ +#define BX_NUM 32 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/layla24_dsp.fw"); +MODULE_FIRMWARE("ea/layla24_1_asic.fw"); +MODULE_FIRMWARE("ea/layla24_2A_asic.fw"); +MODULE_FIRMWARE("ea/layla24_2S_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_LAYLA24_DSP 1 +#define FW_LAYLA24_1_ASIC 2 +#define FW_LAYLA24_2A_ASIC 3 +#define FW_LAYLA24_2S_ASIC 4 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "layla24_dsp.fw"}, + {0, "layla24_1_asic.fw"}, + {0, "layla24_2A_asic.fw"}, + {0, "layla24_2S_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {0x1057, 0x3410, 0xECC0, 0x0060, 0, 0, 0}, /* DSP 56361 Layla24 rev.0 */ + {0,} +}; + +static 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 + +****************************************************************************/ + + +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..62b5240f5 --- /dev/null +++ b/sound/pci/echoaudio/mia.c @@ -0,0 +1,121 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHO24_FAMILY +#define ECHOCARD_MIA +#define ECHOCARD_NAME "Mia" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_INPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_OUTPUT_NOMINAL_LEVEL +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_VMIXER +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT false +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 +#define ECHOCARD_HAS_MIDI +#define ECHOCARD_HAS_LINE_OUT_GAIN + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 8 */ +#define PX_DIGITAL_OUT 8 /* 0 */ +#define PX_ANALOG_IN 8 /* 2 */ +#define PX_DIGITAL_IN 10 /* 2 */ +#define PX_NUM 12 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 2 */ +#define BX_DIGITAL_OUT 2 /* 2 */ +#define BX_ANALOG_IN 4 /* 2 */ +#define BX_DIGITAL_IN 6 /* 2 */ +#define BX_NUM 8 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/mia_dsp.fw"); + +#define FW_361_LOADER 0 +#define FW_MIA_DSP 1 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "mia_dsp.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {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 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 + +****************************************************************************/ + + +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 + +****************************************************************************/ + + +/****************************************************************************** + 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..34d499466 --- /dev/null +++ b/sound/pci/echoaudio/mona.c @@ -0,0 +1,138 @@ +/* + * ALSA driver for Echoaudio soundcards. + * Copyright (C) 2003-2004 Giuliano Pochini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#define ECHO24_FAMILY +#define ECHOCARD_MONA +#define ECHOCARD_NAME "Mona" +#define ECHOCARD_HAS_MONITOR +#define ECHOCARD_HAS_ASIC +#define ECHOCARD_HAS_SUPER_INTERLEAVE +#define ECHOCARD_HAS_DIGITAL_IO +#define ECHOCARD_HAS_DIGITAL_IN_AUTOMUTE +#define ECHOCARD_HAS_DIGITAL_MODE_SWITCH +#define ECHOCARD_HAS_EXTERNAL_CLOCK +#define ECHOCARD_HAS_ADAT 6 +#define ECHOCARD_HAS_STEREO_BIG_ENDIAN32 + +/* Pipe indexes */ +#define PX_ANALOG_OUT 0 /* 6 */ +#define PX_DIGITAL_OUT 6 /* 8 */ +#define PX_ANALOG_IN 14 /* 4 */ +#define PX_DIGITAL_IN 18 /* 8 */ +#define PX_NUM 26 + +/* Bus indexes */ +#define BX_ANALOG_OUT 0 /* 6 */ +#define BX_DIGITAL_OUT 6 /* 8 */ +#define BX_ANALOG_IN 14 /* 4 */ +#define BX_DIGITAL_IN 18 /* 8 */ +#define BX_NUM 26 + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "echoaudio.h" + +MODULE_FIRMWARE("ea/loader_dsp.fw"); +MODULE_FIRMWARE("ea/mona_301_dsp.fw"); +MODULE_FIRMWARE("ea/mona_361_dsp.fw"); +MODULE_FIRMWARE("ea/mona_301_1_asic_48.fw"); +MODULE_FIRMWARE("ea/mona_301_1_asic_96.fw"); +MODULE_FIRMWARE("ea/mona_361_1_asic_48.fw"); +MODULE_FIRMWARE("ea/mona_361_1_asic_96.fw"); +MODULE_FIRMWARE("ea/mona_2_asic.fw"); + +#define FW_361_LOADER 0 +#define FW_MONA_301_DSP 1 +#define FW_MONA_361_DSP 2 +#define FW_MONA_301_1_ASIC48 3 +#define FW_MONA_301_1_ASIC96 4 +#define FW_MONA_361_1_ASIC48 5 +#define FW_MONA_361_1_ASIC96 6 +#define FW_MONA_2_ASIC 7 + +static const struct firmware card_fw[] = { + {0, "loader_dsp.fw"}, + {0, "mona_301_dsp.fw"}, + {0, "mona_361_dsp.fw"}, + {0, "mona_301_1_asic_48.fw"}, + {0, "mona_301_1_asic_96.fw"}, + {0, "mona_361_1_asic_48.fw"}, + {0, "mona_361_1_asic_96.fw"}, + {0, "mona_2_asic.fw"} +}; + +static const struct pci_device_id snd_echo_ids[] = { + {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 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..dce9e57d0 --- /dev/null +++ b/sound/pci/echoaudio/mona_dsp.c @@ -0,0 +1,426 @@ +/**************************************************************************** + + 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 + +****************************************************************************/ + + +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; + + + /* Prevent two simultaneous calls to switch_asic() */ + if (atomic_read(&chip->opencount)) + return -EAGAIN; + + /* 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 +# + +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..d3203df50 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1.c @@ -0,0 +1,283 @@ +/* + * The driver for the EMU10K1 (SB Live!) based soundcards + * Copyright (c) by Jaroslav Kysela + * + * Copyright (c) by James Courtier-Dutton + * Added support for Audigy 2 Value. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +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 +#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 */ + if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + 1024, &emu->p16v_buffer)) < 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_pcm_suspend_all(emu->pcm); + snd_pcm_suspend_all(emu->pcm_mic); + snd_pcm_suspend_all(emu->pcm_efx); + snd_pcm_suspend_all(emu->pcm_multi); + snd_pcm_suspend_all(emu->pcm_p16v); + + 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..aa2cc27b8 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -0,0 +1,550 @@ +/* + * synth callback routines for Emu10k1 + * + * Copyright (C) 2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include "emu10k1_synth_local.h" +#include + +/* voice status */ +enum { + V_FREE=0, V_OFF, V_RELEASED, V_PLAYING, V_END +}; + +/* Keeps track of what we are finding */ +struct best_voice { + unsigned int time; + int voice; +}; + +/* + * prototypes + */ +static void lookup_voices(struct snd_emux *emux, struct snd_emu10k1 *hw, + struct best_voice *best, int active_only); +static struct snd_emux_voice *get_voice(struct snd_emux *emux, + struct snd_emux_port *port); +static int start_voice(struct snd_emux_voice *vp); +static void trigger_voice(struct snd_emux_voice *vp); +static void release_voice(struct snd_emux_voice *vp); +static void update_voice(struct snd_emux_voice *vp, int update); +static void terminate_voice(struct snd_emux_voice *vp); +static void free_voice(struct snd_emux_voice *vp); +static 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..61f85ff91 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -0,0 +1,2178 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Routines for control of EMU10K1 chips + * + * Copyright (c) by James Courtier-Dutton + * Added support for Audigy 2 Value. + * Added EMU 1010 support. + * General bug fixes and enhancements. + * + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include "p16v.h" +#include "tina2.h" +#include "p17v.h" + + +#define HANA_FILENAME "emu/hana.fw" +#define DOCK_FILENAME "emu/audio_dock.fw" +#define EMU1010B_FILENAME "emu/emu1010b.fw" +#define MICRO_DOCK_FILENAME "emu/micro_dock.fw" +#define EMU0404_FILENAME "emu/emu0404.fw" +#define EMU1010_NOTEBOOK_FILENAME "emu/emu1010_notebook.fw" + +MODULE_FIRMWARE(HANA_FILENAME); +MODULE_FIRMWARE(DOCK_FILENAME); +MODULE_FIRMWARE(EMU1010B_FILENAME); +MODULE_FIRMWARE(MICRO_DOCK_FILENAME); +MODULE_FIRMWARE(EMU0404_FILENAME); +MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME); + + +/************************************************************************* + * EMU10K1 init / done + *************************************************************************/ + +void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) +{ + snd_emu10k1_ptr_write(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 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 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; + 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; + 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, ®); /* 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, ®); + 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, ®); + 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, ®); + if ((reg & 0x3f) != 0x15) { + /* FPGA failed to be programmed */ + dev_info(emu->card->dev, + "emu1010: Loading Hana Firmware file failed, reg = 0x%x\n", + reg); + return -ENODEV; + } + + dev_info(emu->card->dev, "emu1010: Hana Firmware loaded\n"); + snd_emu1010_fpga_read(emu, EMU_HANA_MAJOR_REV, &tmp); + snd_emu1010_fpga_read(emu, EMU_HANA_MINOR_REV, &tmp2); + dev_info(emu->card->dev, "emu1010: Hana version: %u.%u\n", tmp, tmp2); + /* Enable 48Volt power to Audio Dock */ + snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_PWR, EMU_HANA_DOCK_PWR_ON); + + snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®); + dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg); + snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®); + 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, ®); + 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 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 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; + unsigned int silent_page; + const struct snd_emu_chip_details *c; + static 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) { + int i, n = 0; + strlcpy(card->id, c->id, sizeof(card->id)); + for (;;) { + for (i = 0; i < snd_ecards_limit; i++) { + if (snd_cards[i] && !strcmp(snd_cards[i]->id, card->id)) + break; + } + if (i >= snd_ecards_limit) + break; + n++; + if (n >= SNDRV_CARDS) + break; + snprintf(card->id, sizeof(card->id), "%s_%d", c->id, n); + } + } + + 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; + + /* + * 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; + for (idx = 0; idx < (emu->address_mode ? MAXPAGES1 : MAXPAGES0); idx++) + ((u32 *)emu->ptb_pages.area)[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 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 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; + 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; + 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..c32eb7053 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_patch.c @@ -0,0 +1,228 @@ +/* + * Patch transfer callback for Emu10k1 + * + * Copyright (C) 2000 Takashi iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * 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, loopsize, blocksize; + 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..5457d5613 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_synth.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2000 Takashi Iwai + * + * Routines for control of EMU10K1 WaveTable synth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "emu10k1_synth_local.h" +#include +#include + +MODULE_AUTHOR("Takashi Iwai"); +MODULE_DESCRIPTION("Routines for control of EMU10K1 WaveTable synth"); +MODULE_LICENSE("GPL"); + +/* + * create a new hardware dependent device for Emu10k1 + */ +static int snd_emu10k1_synth_probe(struct 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..25f328ff6 --- /dev/null +++ b/sound/pci/emu10k1/emu10k1_synth_local.h @@ -0,0 +1,42 @@ +#ifndef __EMU10K1_SYNTH_LOCAL_H +#define __EMU10K1_SYNTH_LOCAL_H +/* + * Local defininitons for Emu10k1 wavetable + * + * Copyright (C) 2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +/* emu10k1_patch.c */ +int snd_emu10k1_sample_new(struct snd_emux *private_data, + struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr, + const void __user *_data, long count); +int snd_emu10k1_sample_free(struct snd_emux *private_data, + struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr); +int snd_emu10k1_memhdr_init(struct snd_emux *emu); + +/* emu10k1_callback.c */ +void snd_emu10k1_ops_setup(struct snd_emux *emu); +int snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw); + + +#endif /* __EMU10K1_SYNTH_LOCAL_H */ diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c new file mode 100644 index 000000000..611589cbd --- /dev/null +++ b/sound/pci/emu10k1/emu10k1x.c @@ -0,0 +1,1642 @@ +/* + * Copyright (c) by Francisco Moraes + * Driver EMU10K1X chips + * + * Parts of this code were adapted from audigyls.c driver which is + * Copyright (c) by James Courtier-Dutton + * + * BUGS: + * -- + * + * TODO: + * + * Chips (SB0200 model): + * - EMU10K1X-DBQ + * - STAC 9708T + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Francisco Moraes "); +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 = + 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 snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +/* 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 snd_pcm_lib_free_pages(substream); +} + +/* prepare callback */ +static int snd_emu10k1x_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct emu10k1x *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + int voice = epcm->voice->number; + u32 *table_base = (u32 *)(emu->dma_buffer.area+1024*voice); + u32 period_size_bytes = frames_to_bytes(runtime, runtime->period_size); + int i; + + for(i = 0; i < runtime->periods; i++) { + *table_base++=runtime->dma_addr+(i*period_size_bytes); + *table_base++=period_size_bytes<<16; + } + + snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_ADDR, voice, emu->dma_buffer.addr+1024*voice); + snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_SIZE, voice, (runtime->periods - 1) << 19); + snd_emu10k1x_ptr_write(emu, PLAYBACK_LIST_PTR, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_POINTER, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN1, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_UNKNOWN2, voice, 0); + snd_emu10k1x_ptr_write(emu, PLAYBACK_DMA_ADDR, voice, runtime->dma_addr); + + snd_emu10k1x_ptr_write(emu, PLAYBACK_PERIOD_SIZE, voice, frames_to_bytes(runtime, runtime->period_size)<<16); + + return 0; +} + +/* trigger callback */ +static int snd_emu10k1x_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct emu10k1x *emu = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + int channel = epcm->voice->number; + int result = 0; + + /* + dev_dbg(emu->card->dev, + "trigger - emu10k1x = 0x%x, cmd = %i, pointer = %d\n", + (int)emu, cmd, (int)substream->ops->pointer(substream)); + */ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if(runtime->periods == 2) + snd_emu10k1x_intr_enable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel); + else + snd_emu10k1x_intr_enable(emu, INTE_CH_0_LOOP << channel); + epcm->running = 1; + snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0)|(TRIGGER_CHANNEL_0<running = 0; + snd_emu10k1x_intr_disable(emu, (INTE_CH_0_LOOP | INTE_CH_0_HALF_LOOP) << channel); + snd_emu10k1x_ptr_write(emu, TRIGGER_CHANNEL, 0, snd_emu10k1x_ptr_read(emu, TRIGGER_CHANNEL, 0) & ~(TRIGGER_CHANNEL_0<runtime; + struct emu10k1x_pcm *epcm = runtime->private_data; + int channel = epcm->voice->number; + snd_pcm_uframes_t ptr = 0, ptr1 = 0, ptr2= 0,ptr3 = 0,ptr4 = 0; + + if (!epcm->running) + return 0; + + ptr3 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel); + ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel); + ptr4 = snd_emu10k1x_ptr_read(emu, PLAYBACK_LIST_PTR, channel); + + if(ptr4 == 0 && ptr1 == frames_to_bytes(runtime, runtime->buffer_size)) + return 0; + + if (ptr3 != ptr4) + ptr1 = snd_emu10k1x_ptr_read(emu, PLAYBACK_POINTER, channel); + ptr2 = bytes_to_frames(runtime, ptr1); + ptr2 += (ptr4 >> 3) * runtime->period_size; + ptr = ptr2; + + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + + return ptr; +} + +/* operators */ +static const struct snd_pcm_ops snd_emu10k1x_playback_ops = { + .open = snd_emu10k1x_playback_open, + .close = snd_emu10k1x_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .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 snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +/* 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 snd_pcm_lib_free_pages(substream); +} + +/* 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, + .ioctl = snd_pcm_lib_ioctl, + .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 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(emu->pci), + 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 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; + + if(snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + 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", ®, &channel_id, &val) != 3) + continue; + + if (reg < 0x49 && val <= 0xffffffff && channel_id <= 2) + snd_emu10k1x_ptr_write(emu, reg, channel_id, val); + } +} + +static int snd_emu10k1x_proc_init(struct emu10k1x *emu) +{ + struct snd_info_entry *entry; + + if(! snd_card_proc_new(emu->card, "emu10k1x_regs", &entry)) { + snd_info_set_text_ops(entry, emu, snd_emu10k1x_proc_reg_read); + entry->c.text.write = snd_emu10k1x_proc_reg_write; + entry->mode |= 0200; + entry->private_data = emu; + } + + 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; + int change = 0; + + 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 change; +} + +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..1f25e6d02 --- /dev/null +++ b/sound/pci/emu10k1/emufx.c @@ -0,0 +1,2794 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Routines for effect processor FX8010 + * + * Copyright (c) by James Courtier-Dutton + * Added EMU 1010 support. + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if 0 /* for testing purposes - digital out -> capture */ +#define EMU10K1_CAPTURE_DIGITAL_OUT +#endif +#if 0 /* for testing purposes - set S/PDIF to AC3 output */ +#define EMU10K1_SET_AC3_IEC958 +#endif +#if 0 /* for testing purposes - feed the front signal to Center/LFE outputs */ +#define EMU10K1_CENTER_LFE_FROM_FRONT +#endif + +static bool high_res_gpr_volume; +module_param(high_res_gpr_volume, bool, 0444); +MODULE_PARM_DESC(high_res_gpr_volume, "GPR mixer controls use 31-bit range."); + +/* + * Tables + */ + +static char *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 char *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 char *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 char *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 char *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 = (u_int32_t __force *)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 = (u_int32_t __force *)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 = *(__force u32 *)&icode->gpr_map[gpr]; + else if (get_user(val, &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, &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 = *(__force u32 *)&icode->tram_data_map[tram]; + addr = *(__force u32 *)&icode->tram_addr_map[tram]; + } else { + if (get_user(val, &icode->tram_data_map[tram]) || + get_user(addr, &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, &icode->tram_data_map[tram]) || + put_user(addr, &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 = *(__force u32 *)&icode->code[pc + 0]; + hi = *(__force u32 *)&icode->code[pc + 1]; + } else { + if (get_user(lo, &icode->code[pc + 0]) || + get_user(hi, &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), &icode->code[pc + 0])) + return -EFAULT; + if (put_user(snd_emu10k1_efx_read(emu, pc + 1), &icode->code[pc + 1])) + return -EFAULT; + } + return 0; +} + +static struct snd_emu10k1_fx8010_ctl * +snd_emu10k1_look_for_ctl(struct snd_emu10k1 *emu, 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 *gctl, + struct snd_emu10k1_fx8010_control_gpr __user *_gctl, + int idx, bool in_kernel) +{ + struct snd_emu10k1_fx8010_control_old_gpr __user *octl; + + if (emu->support_tlv) { + if (in_kernel) + memcpy(gctl, (__force void *)&_gctl[idx], sizeof(*gctl)); + else if (copy_from_user(gctl, &_gctl[idx], sizeof(*gctl))) + return -EFAULT; + return 0; + } + + octl = (struct snd_emu10k1_fx8010_control_old_gpr __user *)_gctl; + if (in_kernel) + memcpy(gctl, (__force void *)&octl[idx], sizeof(*octl)); + else if (copy_from_user(gctl, &octl[idx], sizeof(*octl))) + return -EFAULT; + gctl->tlv = NULL; + return 0; +} + +static int copy_gctl_to_user(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_control_gpr __user *_gctl, + struct snd_emu10k1_fx8010_control_gpr *gctl, + int idx) +{ + struct snd_emu10k1_fx8010_control_old_gpr __user *octl; + + if (emu->support_tlv) + return copy_to_user(&_gctl[idx], gctl, sizeof(*gctl)); + + octl = (struct snd_emu10k1_fx8010_control_old_gpr __user *)_gctl; + return copy_to_user(&octl[idx], gctl, sizeof(*octl)); +} + +static int snd_emu10k1_verify_controls(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_code *icode, + bool in_kernel) +{ + unsigned int i; + struct snd_ctl_elem_id __user *_id; + struct snd_ctl_elem_id id; + struct snd_emu10k1_fx8010_control_gpr *gctl; + int err; + + for (i = 0, _id = icode->gpr_del_controls; + i < icode->gpr_del_control_count; i++, _id++) { + if (in_kernel) + id = *(__force struct snd_ctl_elem_id *)_id; + else if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + 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; + down_read(&emu->card->controls_rwsem); + if (snd_ctl_find_id(emu->card, &gctl->id) != NULL) { + 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_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; + } + 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[0]) { + 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((__force 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 snd_ctl_elem_id id; + struct snd_ctl_elem_id __user *_id; + struct snd_emu10k1_fx8010_ctl *ctl; + struct snd_card *card = emu->card; + + for (i = 0, _id = icode->gpr_del_controls; + i < icode->gpr_del_control_count; i++, _id++) { + if (in_kernel) + id = *(__force struct snd_ctl_elem_id *)_id; + else if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + 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 = 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 = 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 = 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 = 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 = 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 = (u_int32_t __user *) 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 = (u32 __force *)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 = 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 = 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) */ + A_OP(icode, &ptr, iMAC0, A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+7+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); + */ + /* For the EMU1010: How to get 32bit values from the DSP. High 16bits into L, low 16bits into R. */ + /* A_P16VIN(0) is delayed by one sample, + * so all other A_P16VIN channels will need to also be delayed + */ + /* Left ADC in. 1 of 2 */ + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_P16VIN(0x0), A_FXBUS2(0) ); + /* Right ADC in 1 of 2 */ + gpr_map[gpr++] = 0x00000000; + /* Delaying by one sample: instead of copying the input + * value A_P16VIN to output A_FXBUS2 as in the first channel, + * we use an auxiliary register, delaying the value by one + * sample + */ + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(2) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x1), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(4) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x2), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(6) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x3), A_C_00000000, A_C_00000000); + /* For 96kHz mode */ + /* Left ADC in. 2 of 2 */ + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0x8) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x4), A_C_00000000, A_C_00000000); + /* Right ADC in 2 of 2 */ + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xa) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x5), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xc) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x6), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xe) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x7), A_C_00000000, A_C_00000000); + /* Pavel Hofman - we still have voices, A_FXBUS2s, and + * A_P16VINs available - + * let's add 8 more capture channels - total of 16 + */ + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x10)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x8), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x12)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x9), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x14)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xa), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x16)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xb), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x18)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xc), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x1a)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xd), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x1c)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xe), + A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, + bit_shifter16, + A_GPR(gpr - 1), + A_FXBUS2(0x1e)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xf), + A_C_00000000, A_C_00000000); + } + +#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 = (struct snd_emu10k1_fx8010_control_gpr __user *)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((void __force *)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 = (u_int32_t __user *) 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 = (u32 __force *)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<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<fx8010.extin_mask & ((1<id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + 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 = 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<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & (1<fx8010.extout_mask & (1<fx8010.extout_mask & (1<card_capabilities->sblive51) { + /* 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 = (struct snd_emu10k1_fx8010_control_gpr __user *)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((void __force *)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, snd_dma_pci_data(emu->pci), + 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, char *src, 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) +{ + char **fxbus, **extin, **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..b2219a73c --- /dev/null +++ b/sound/pci/emu10k1/emumixer.c @@ -0,0 +1,2234 @@ +/* + * Copyright (c) by Jaroslav Kysela , + * Takashi Iwai + * Creative Labs, Inc. + * Routines for control of EMU10K1 chips / mixer routines + * Multichannel PCM support Copyright (c) Lee Revell + * + * Copyright (c) by James Courtier-Dutton + * Added EMU 1010 support. + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "p17v.h" + +#define AC97_ID_STAC9758 0x83847658 + +static const DECLARE_TLV_DB_SCALE(snd_audigy_db_scale2, -10350, 50, 1); /* WM8775 gain scale */ + +static int 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 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 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 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 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 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 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 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 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 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 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 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 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; + char **c; + static char *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 char *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 char *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 char *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 char *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 char *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 char *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 char *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 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 + * Voluspa + */ + if (emu->ac97->id == AC97_ID_STAC9758) { + emu->rear_ac97 = 1; + snd_emu10k1_ptr_write(emu, AC97SLOT, 0, AC97SLOT_CNTR|AC97SLOT_LFE|AC97SLOT_REAR_LEFT|AC97SLOT_REAR_RIGHT); + snd_ac97_write_cache(emu->ac97, AC97_HEADPHONE, 0x0202); + remove_ctl(card,"Front Playback Volume"); + remove_ctl(card,"Front Playback Switch"); + } + /* remove unused AC97 controls */ + snd_ac97_write_cache(emu->ac97, AC97_SURROUND_MASTER, 0x0202); + snd_ac97_write_cache(emu->ac97, AC97_CENTER_LFE_MASTER, 0x0202); + c = emu10k1_remove_ctls; + } + for (; *c; c++) + remove_ctl(card, *c); + } else if (emu->card_capabilities->i2c_adc) { + c = audigy_remove_ctls_i2c_adc; + for (; *c; c++) + remove_ctl(card, *c); + } else { + no_ac97: + if (emu->card_capabilities->ecard) + 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..b6650f5c1 --- /dev/null +++ b/sound/pci/emu10k1/emumpu401.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of EMU10K1 MPU-401 in UART mode + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#define EMU10K1_MIDI_MODE_INPUT (1<<0) +#define EMU10K1_MIDI_MODE_OUTPUT (1<<1) + +static inline unsigned char mpu401_read(struct snd_emu10k1 *emu, + struct snd_emu10k1_midi *mpu, int idx) +{ + if (emu->audigy) + return (unsigned char)snd_emu10k1_ptr_read(emu, mpu->port + idx, 0); + else + return inb(emu->port + mpu->port + idx); +} + +static inline void mpu401_write(struct snd_emu10k1 *emu, + struct snd_emu10k1_midi *mpu, int data, int idx) +{ + if (emu->audigy) + snd_emu10k1_ptr_write(emu, mpu->port + idx, 0, data); + else + outb(data, emu->port + mpu->port + idx); +} + +#define mpu401_write_data(emu, mpu, data) mpu401_write(emu, mpu, data, 0) +#define mpu401_write_cmd(emu, mpu, data) mpu401_write(emu, mpu, data, 1) +#define mpu401_read_data(emu, mpu) mpu401_read(emu, mpu, 0) +#define mpu401_read_stat(emu, mpu) mpu401_read(emu, mpu, 1) + +#define mpu401_input_avail(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x80)) +#define mpu401_output_ready(emu,mpu) (!(mpu401_read_stat(emu,mpu) & 0x40)) + +#define MPU401_RESET 0xff +#define MPU401_ENTER_UART 0x3f +#define MPU401_ACK 0xfe + +static void mpu401_clear_rx(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *mpu) +{ + int timeout = 100000; + for (; timeout > 0 && mpu401_input_avail(emu, mpu); timeout--) + mpu401_read_data(emu, mpu); +#ifdef CONFIG_SND_DEBUG + if (timeout <= 0) + dev_err(emu->card->dev, + "cmd: clear rx timeout (status = 0x%x)\n", + mpu401_read_stat(emu, mpu)); +#endif +} + +/* + + */ + +static void do_emu10k1_midi_interrupt(struct snd_emu10k1 *emu, struct snd_emu10k1_midi *midi, unsigned int status) +{ + unsigned char byte; + + if (midi->rmidi == NULL) { + snd_emu10k1_intr_disable(emu, midi->tx_enable | midi->rx_enable); + return; + } + + 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..9f2b6097f --- /dev/null +++ b/sound/pci/emu10k1/emupcm.c @@ -0,0 +1,1867 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Routines for control of EMU10K1 chips / PCM routines + * Multichannel PCM support Copyright (c) Lee Revell + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +static void snd_emu10k1_pcm_interrupt(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *voice) +{ + struct snd_emu10k1_pcm *epcm; + + 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]; + 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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_emu10k1_capture_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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]); + /* fall through */ + 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); + + /* fall through */ + 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_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_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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +static const struct snd_pcm_ops snd_emu10k1_capture_ops = { + .open = snd_emu10k1_capture_open, + .close = snd_emu10k1_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_emu10k1_capture_hw_params, + .hw_free = snd_emu10k1_capture_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +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; + + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) + if ((err = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0) + return err; + + for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next) + snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 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) + if ((err = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, snd_dma_pci_data(emu->pci), 64*1024, 64*1024)) < 0) + return err; + + 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_emu10k1_capture_hw_params, + .hw_free = snd_emu10k1_capture_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_emu10k1_capture_hw_params, + .hw_free = snd_emu10k1_capture_hw_free, + .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_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +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); + snd_pcm_lib_free_pages(substream); + 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), + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_emu10k1_fx8010_playback_hw_params, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(emu->pci), 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..b57008031 --- /dev/null +++ b/sound/pci/emu10k1/emuproc.c @@ -0,0 +1,656 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Routines for control of EMU10K1 chips / proc interface routines + * + * Copyright (c) by James Courtier-Dutton + * Added EMU 1010 support. + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "p16v.h" + +static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu, + struct snd_info_buffer *buffer, + char *title, + int status_reg, + int rate_reg) +{ + static char *clkaccy[4] = { "1000ppm", "50ppm", "variable", "unknown" }; + static int samplerate[16] = { 44100, 1, 48000, 32000, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + static char *channel[16] = { "unspec", "left", "right", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" }; + static char *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 char *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 char *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; + char **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 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", ®, &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", ®, &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 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) { + if (! snd_card_proc_new(emu->card, "emu1010_regs", &entry)) + snd_info_set_text_ops(entry, emu, snd_emu_proc_emu1010_reg_read); + } + if (! snd_card_proc_new(emu->card, "io_regs", &entry)) { + snd_info_set_text_ops(entry, emu, snd_emu_proc_io_reg_read); + entry->c.text.write = snd_emu_proc_io_reg_write; + entry->mode |= 0200; + } + if (! snd_card_proc_new(emu->card, "ptr_regs00a", &entry)) { + snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00a); + entry->c.text.write = snd_emu_proc_ptr_reg_write00; + entry->mode |= 0200; + } + if (! snd_card_proc_new(emu->card, "ptr_regs00b", &entry)) { + snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read00b); + entry->c.text.write = snd_emu_proc_ptr_reg_write00; + entry->mode |= 0200; + } + if (! snd_card_proc_new(emu->card, "ptr_regs20a", &entry)) { + snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20a); + entry->c.text.write = snd_emu_proc_ptr_reg_write20; + entry->mode |= 0200; + } + if (! snd_card_proc_new(emu->card, "ptr_regs20b", &entry)) { + snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20b); + entry->c.text.write = snd_emu_proc_ptr_reg_write20; + entry->mode |= 0200; + } + if (! snd_card_proc_new(emu->card, "ptr_regs20c", &entry)) { + snd_info_set_text_ops(entry, emu, snd_emu_proc_ptr_reg_read20c); + entry->c.text.write = snd_emu_proc_ptr_reg_write20; + entry->mode |= 0200; + } +#endif + + if (! snd_card_proc_new(emu->card, "emu10k1", &entry)) + snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_read); + + if (emu->card_capabilities->emu10k2_chip) { + if (! snd_card_proc_new(emu->card, "spdif-in", &entry)) + snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_spdif_read); + } + if (emu->card_capabilities->ca0151_chip) { + if (! snd_card_proc_new(emu->card, "capture-rates", &entry)) + snd_info_set_text_ops(entry, emu, snd_emu10k1_proc_rates_read); + } + + if (! snd_card_proc_new(emu->card, "voices", &entry)) + snd_info_set_text_ops(entry, 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; + } + if (! snd_card_proc_new(emu->card, "fx8010_acode", &entry)) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = emu; + entry->mode = S_IFREG | 0444 /*| S_IWUSR*/; + entry->c.text.read = 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..706b4f0c6 --- /dev/null +++ b/sound/pci/emu10k1/io.c @@ -0,0 +1,580 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Routines for control of EMU10K1 chips + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#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 u32 logMagTable[128] = { + 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, + 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, + 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, + 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, + 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, + 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, + 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, + 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, + 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, + 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, + 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, + 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, + 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, + 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, + 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, + 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df + }; + static char logSlopeTable[128] = { + 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, + 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, + 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, + 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, + 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, + 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, + 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, + 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, + 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, + 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, + 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, + 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f + }; + 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..3c5c5e3dc --- /dev/null +++ b/sound/pci/emu10k1/irq.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Routines for IRQ control of EMU10K1 chips + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +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..dbc7d8d0e --- /dev/null +++ b/sound/pci/emu10k1/memory.c @@ -0,0 +1,636 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Copyright (c) by Takashi Iwai + * + * EMU10K1 memory page allocation (PTB area) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +/* page arguments of these two macros are Emu page (4096 bytes), not like + * aligned pages in others + */ +#define __set_ptb_entry(emu,page,addr) \ + (((__le32 *)(emu)->ptb_pages.area)[page] = \ + cpu_to_le32(((addr) << (emu->address_mode)) | (page))) +#define __get_ptb_entry(emu, page) \ + (le32_to_cpu(((__le32 *)(emu)->ptb_pages.area)[page])) + +#define UNIT_PAGES (PAGE_SIZE / EMUPAGESIZE) +#define MAX_ALIGN_PAGES0 (MAXPAGES0 / UNIT_PAGES) +#define MAX_ALIGN_PAGES1 (MAXPAGES1 / UNIT_PAGES) +/* get aligned page from offset address */ +#define get_aligned_page(offset) ((offset) >> PAGE_SHIFT) +/* get offset address from aligned page */ +#define aligned_page_offset(page) ((page) << PAGE_SHIFT) + +#if PAGE_SIZE == EMUPAGESIZE && !IS_ENABLED(CONFIG_DYNAMIC_DEBUG) +/* fill PTB entrie(s) corresponding to page with addr */ +#define set_ptb_entry(emu,page,addr) __set_ptb_entry(emu,page,addr) +/* fill PTB entrie(s) corresponding to page with silence pointer */ +#define set_silent_ptb(emu,page) __set_ptb_entry(emu,page,emu->silent_page.addr) +#else +/* fill PTB entries -- we need to fill UNIT_PAGES entries */ +static inline void set_ptb_entry(struct snd_emu10k1 *emu, int page, dma_addr_t addr) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) { + __set_ptb_entry(emu, page, addr); + dev_dbg(emu->card->dev, "mapped page %d to entry %.8x\n", page, + (unsigned int)__get_ptb_entry(emu, page)); + addr += EMUPAGESIZE; + } +} +static inline void set_silent_ptb(struct snd_emu10k1 *emu, int page) +{ + int i; + page *= UNIT_PAGES; + for (i = 0; i < UNIT_PAGES; i++, page++) { + /* do not increment ptr */ + __set_ptb_entry(emu, page, emu->silent_page.addr); + dev_dbg(emu->card->dev, "mapped silent page %d to entry %.8x\n", + page, (unsigned int)__get_ptb_entry(emu, page)); + } +} +#endif /* PAGE_SIZE */ + + +/* + */ +static int synth_alloc_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk); +static int synth_free_pages(struct snd_emu10k1 *hw, struct snd_emu10k1_memblk *blk); + +#define get_emu10k1_memblk(l,member) list_entry(l, struct snd_emu10k1_memblk, member) + + +/* initialize emu10k1 part */ +static void emu10k1_memblk_init(struct snd_emu10k1_memblk *blk) +{ + blk->mapped_page = -1; + INIT_LIST_HEAD(&blk->mapped_link); + INIT_LIST_HEAD(&blk->mapped_order_link); + blk->map_locked = 0; + + blk->first_page = get_aligned_page(blk->mem.offset); + blk->last_page = get_aligned_page(blk->mem.offset + blk->mem.size - 1); + blk->pages = blk->last_page - blk->first_page + 1; +} + +/* + * search empty region on PTB with the given size + * + * if an empty region is found, return the page and store the next mapped block + * in nextp + * if not found, return a negative error code. + */ +static int search_empty_map_area(struct snd_emu10k1 *emu, int npages, struct list_head **nextp) +{ + int page = 1, found_page = -ENOMEM; + int max_size = npages; + int size; + struct list_head *candidate = &emu->mapped_link_head; + struct list_head *pos; + + list_for_each (pos, &emu->mapped_link_head) { + struct snd_emu10k1_memblk *blk = get_emu10k1_memblk(pos, mapped_link); + if (blk->mapped_page < 0) + continue; + size = blk->mapped_page - page; + if (size == npages) { + *nextp = pos; + return page; + } + else if (size > max_size) { + /* we look for the maximum empty hole */ + max_size = size; + candidate = pos; + found_page = page; + } + page = blk->mapped_page + blk->pages; + } + size = (emu->address_mode ? MAX_ALIGN_PAGES1 : MAX_ALIGN_PAGES0) - page; + if (size >= max_size) { + *nextp = pos; + return page; + } + *nextp = candidate; + return found_page; +} + +/* + * map a memory block onto emu10k1's PTB + * + * call with memblk_lock held + */ +static int map_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk) +{ + int page, pg; + struct list_head *next; + + page = search_empty_map_area(emu, blk->pages, &next); + if (page < 0) /* not found */ + return page; + if (page == 0) { + dev_err(emu->card->dev, "trying to map zero (reserved) page\n"); + return -EINVAL; + } + /* insert this block in the proper position of mapped list */ + list_add_tail(&blk->mapped_link, next); + /* append this as a newest block in order list */ + list_add_tail(&blk->mapped_order_link, &emu->mapped_order_link_head); + blk->mapped_page = page; + /* fill PTB */ + for (pg = blk->first_page; pg <= blk->last_page; pg++) { + set_ptb_entry(emu, page, emu->page_addr_table[pg]); + page++; + } + return 0; +} + +/* + * unmap the block + * return the size of resultant empty pages + * + * call with memblk_lock held + */ +static int unmap_memblk(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk) +{ + int start_page, end_page, mpage, pg; + struct list_head *p; + struct snd_emu10k1_memblk *q; + + /* calculate the expected size of empty region */ + 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, + snd_dma_pci_data(emu->pci), 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 = snd_dma_pci_data(emu->pci); + + 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..4948b95f6 --- /dev/null +++ b/sound/pci/emu10k1/p16v.c @@ -0,0 +1,909 @@ +/* + * Copyright (c) by James Courtier-Dutton + * Driver p16v chips + * Version: 0.25 + * + * FEATURES currently supported: + * Output fixed at S32_LE, 2 channel to hw:0,0 + * Rates: 44.1, 48, 96, 192. + * + * Changelog: + * 0.8 + * Use separate card based buffer for periods table. + * 0.9 + * Use 2 channel output streams instead of 8 channel. + * (8 channel output streams might be good for ASIO type output) + * Corrected speaker output, so Front -> Front etc. + * 0.10 + * Fixed missed interrupts. + * 0.11 + * Add Sound card model number and names. + * Add Analog volume controls. + * 0.12 + * Corrected playback interrupts. Now interrupt per period, instead of half period. + * 0.13 + * Use single trigger for multichannel. + * 0.14 + * Mic capture now works at fixed: S32_LE, 96000Hz, Stereo. + * 0.15 + * Force buffer_size / period_size == INTEGER. + * 0.16 + * Update p16v.c to work with changed alsa api. + * 0.17 + * Update p16v.c to work with changed alsa api. Removed boot_devs. + * 0.18 + * Merging with snd-emu10k1 driver. + * 0.19 + * One stereo channel at 24bit now works. + * 0.20 + * Added better register defines. + * 0.21 + * Integrated with snd-emu10k1 driver. + * 0.22 + * Removed #if 0 ... #endif + * 0.23 + * Implement different capture rates. + * 0.24 + * Implement different capture source channels. + * e.g. When HD Capture source is set to SPDIF, + * setting HD Capture channel to 0 captures from CDROM digital input. + * setting HD Capture channel to 1 captures from SPDIF in. + * 0.25 + * Include capture buffer sizes. + * + * BUGS: + * Some stability problems when unloading the snd-p16v kernel module. + * -- + * + * TODO: + * SPDIF out. + * Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz. + * Currently capture fixed at 48000Hz. + * + * -- + * GENERAL INFO: + * Model: SB0240 + * P16V Chip: CA0151-DBS + * Audigy 2 Chip: CA0102-IAT + * AC97 Codec: STAC 9721 + * ADC: Philips 1361T (Stereo 24bit) + * DAC: CS4382-K (8-channel, 24bit, 192Khz) + * + * This code was initially based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "p16v.h" + +#define SET_CHANNEL 0 /* Testing channel outputs 0=Front, 1=Center/LFE, 2=Unknown, 3=Rear */ +#define PCM_FRONT_CHANNEL 0 +#define PCM_REAR_CHANNEL 1 +#define PCM_CENTER_LFE_CHANNEL 2 +#define PCM_SIDE_CHANNEL 3 +#define CONTROL_FRONT_CHANNEL 0 +#define CONTROL_REAR_CHANNEL 3 +#define CONTROL_CENTER_LFE_CHANNEL 1 +#define CONTROL_SIDE_CHANNEL 2 + +/* Card IDs: + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2002 -> Audigy2 ZS 7.1 Model:SB0350 + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1007 -> Audigy2 6.1 Model:SB0240 + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:1002 -> Audigy2 Platinum Model:SB msb0240230009266 + * Class 0401: 1102:0004 (rev 04) Subsystem: 1102:2007 -> Audigy4 Pro Model:SB0380 M1SB0380472001901E + * + */ + + /* hardware definition */ +static const struct snd_pcm_hardware snd_p16v_playback_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_S32_LE, /* Only supports 24-bit samples padded to 32 bits. */ + .rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 192000, + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = ((65536 - 64) * 8), + .period_bytes_min = 64, + .period_bytes_max = (65536 - 64), + .periods_min = 2, + .periods_max = 8, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_p16v_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 192000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (65536 - 64), + .period_bytes_min = 64, + .period_bytes_max = (65536 - 128) >> 1, /* size has to be N*64 bytes */ + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +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); +} + +/* hw_params callback */ +static int snd_p16v_pcm_hw_params_playback(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +/* hw_params callback */ +static int snd_p16v_pcm_hw_params_capture(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + + +/* hw_free callback */ +static int snd_p16v_pcm_hw_free_playback(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* hw_free callback */ +static int snd_p16v_pcm_hw_free_capture(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + + +/* 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<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<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<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<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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_p16v_pcm_hw_params_playback, + .hw_free = snd_p16v_pcm_hw_free_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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_p16v_pcm_hw_params_capture, + .hw_free = snd_p16v_pcm_hw_free_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) { + if ((err = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(emu->pci), + ((65536 - 64) * 8), ((65536 - 64) * 8))) < 0) + return err; + /* + 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) { + if ((err = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(emu->pci), + 65536 - 64, 65536 - 64)) < 0) + return err; + /* + 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 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..4e0ee1a97 --- /dev/null +++ b/sound/pci/emu10k1/p16v.h @@ -0,0 +1,299 @@ +/* + * Copyright (c) by James Courtier-Dutton + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/********************************************************************************************************/ +/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */ +/********************************************************************************************************/ + +/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE. + * The sample rate is also controlled by the same registers that control the rate of the EMU10K2 sample rate converters. + */ + +/* Initially all registers from 0x00 to 0x3f have zero contents. */ +#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */ + /* One list entry: 4 bytes for DMA address, + * 4 bytes for period_size << 16. + * One list entry is 8 bytes long. + * One list entry for each period in the buffer. + */ +#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */ +#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */ +#define PLAYBACK_UNKNOWN3 0x03 /* Not used */ +#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA address */ +#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */ +#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */ +#define PLAYBACK_FIFO_END_ADDRESS 0x07 /* Playback FIFO end address */ +#define PLAYBACK_FIFO_POINTER 0x08 /* Playback FIFO pointer and number of valid sound samples in cache */ +#define PLAYBACK_UNKNOWN9 0x09 /* Not used */ +#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */ +#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */ +#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */ +#define CAPTURE_FIFO_POINTER 0x13 /* Capture FIFO pointer and number of valid sound samples in cache */ +#define CAPTURE_P16V_VOLUME1 0x14 /* Low: Capture volume 0xXXXX3030 */ +#define CAPTURE_P16V_VOLUME2 0x15 /* High:Has no effect on capture volume */ +#define CAPTURE_P16V_SOURCE 0x16 /* P16V source select. Set to 0x0700E4E5 for AC97 CAPTURE */ + /* [0:1] Capture input 0 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [3:2] Capture input 1 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [5:4] Capture input 2 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [7:6] Capture input 3 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [9:8] Playback input 0 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [11:10] Playback input 1 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [13:12] Playback input 2 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [15:14] Playback input 3 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [19:16] Playback mixer output enable. 1 bit per channel. + * [23:20] Capture mixer output enable. 1 bit per channel. + * [26:24] FX engine channel capture 0 = 0x60-0x67. + * 1 = 0x68-0x6f. + * 2 = 0x70-0x77. + * 3 = 0x78-0x7f. + * 4 = 0x80-0x87. + * 5 = 0x88-0x8f. + * 6 = 0x90-0x97. + * 7 = 0x98-0x9f. + * [31:27] Not used. + */ + + /* 0x1 = capture on. + * 0x100 = capture off. + * 0x200 = capture off. + * 0x1000 = capture off. + */ +#define CAPTURE_RATE_STATUS 0x17 /* Capture sample rate. Read only */ + /* [15:0] Not used. + * [18:16] Channel 0 Detected sample rate. 0 - 44.1khz + * 1 - 48 khz + * 2 - 96 khz + * 3 - 192 khz + * 7 - undefined rate. + * [19] Channel 0. 1 - Valid, 0 - Not Valid. + * [22:20] Channel 1 Detected sample rate. + * [23] Channel 1. 1 - Valid, 0 - Not Valid. + * [26:24] Channel 2 Detected sample rate. + * [27] Channel 2. 1 - Valid, 0 - Not Valid. + * [30:28] Channel 3 Detected sample rate. + * [31] Channel 3. 1 - Valid, 0 - Not Valid. + */ +/* 0x18 - 0x1f unused */ +#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played. Read only */ +/* 0x21 - 0x3f unused */ +#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */ + /* Playback (0x1< 77770000 so it must be some sort of route. + * bit 0x1 starts DMA playback on channel_id 0 + */ +/* 0x41,42 take values from 0 - 0xffffffff, but have no effect on playback */ +/* 0x43,0x48 do not remember settings */ +/* 0x41-45 unused */ +#define WATERMARK 0x46 /* Test bit to indicate cache level usage */ + /* Values it can have while playing on channel 0. + * 0000f000, 0000f004, 0000f008, 0000f00c. + * Readonly. + */ +/* 0x47-0x4f unused */ +/* 0x50-0x5f Capture cache data */ +#define SRCSel 0x60 /* SRCSel. Default 0x4. Bypass P16V 0x14 */ + /* [0] 0 = 10K2 audio, 1 = SRC48 mixer output. + * [2] 0 = 10K2 audio, 1 = SRCMulti SPDIF mixer output. + * [4] 0 = 10K2 audio, 1 = SRCMulti I2S mixer output. + */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* SRC48 and SRCMULTI sample rate select and output select. */ + /* 0xffffffff -> 0xC0000015 + * 0xXXXXXXX4 = Enable Front Left/Right + * Enable PCMs + */ + +/* 0x61 -> 0x6c are Volume controls */ +#define PLAYBACK_VOLUME_MIXER1 0x61 /* SRC48 Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER2 0x62 /* SRC48 High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER3 0x63 /* SRCMULTI SPDIF Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER4 0x64 /* SRCMULTI SPDIF High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER5 0x65 /* SRCMULTI I2S Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER6 0x66 /* SRCMULTI I2S High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER7 0x67 /* P16V Low to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER8 0x68 /* P16V High to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER9 0x69 /* P16V Low to SRCMULTI I2S mixer input volume control. */ + /* 0xXXXX3030 = PCM0 Volume (Front). + * 0x3030XXXX = PCM1 Volume (Center) + */ +#define PLAYBACK_VOLUME_MIXER10 0x6a /* P16V High to SRCMULTI I2S mixer input volume control. */ + /* 0x3030XXXX = PCM3 Volume (Rear). */ +#define PLAYBACK_VOLUME_MIXER11 0x6b /* E10K2 Low to SRC48 mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER12 0x6c /* E10K2 High to SRC48 mixer input volume control. */ + +#define SRC48_ENABLE 0x6d /* SRC48 input audio enable */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* [23:16] The corresponding P16V channel to SRC48 enabled if == 1. + * [31:24] The corresponding E10K2 channel to SRC48 enabled. + */ +#define SRCMULTI_ENABLE 0x6e /* SRCMulti input audio enable. Default 0xffffffff */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* [7:0] The corresponding P16V channel to SRCMulti_I2S enabled if == 1. + * [15:8] The corresponding E10K2 channel to SRCMulti I2S enabled. + * [23:16] The corresponding P16V channel to SRCMulti SPDIF enabled. + * [31:24] The corresponding E10K2 channel to SRCMulti SPDIF enabled. + */ + /* Bypass P16V 0xff00ff00 + * Bitmap. 0 = Off, 1 = On. + * P16V playback outputs: + * 0xXXXXXXX1 = PCM0 Left. (Front) + * 0xXXXXXXX2 = PCM0 Right. + * 0xXXXXXXX4 = PCM1 Left. (Center/LFE) + * 0xXXXXXXX8 = PCM1 Right. + * 0xXXXXXX1X = PCM2 Left. (Unknown) + * 0xXXXXXX2X = PCM2 Right. + * 0xXXXXXX4X = PCM3 Left. (Rear) + * 0xXXXXXX8X = PCM3 Right. + */ +#define AUDIO_OUT_ENABLE 0x6f /* Default: 000100FF */ + /* [3:0] Does something, but not documented. Probably capture enable. + * [7:4] Playback channels enable. not documented. + * [16] AC97 output enable if == 1 + * [30] 0 = SRCMulti_I2S input from fxengine 0x68-0x6f. + * 1 = SRCMulti_I2S input from SRC48 output. + * [31] 0 = SRCMulti_SPDIF input from fxengine 0x60-0x67. + * 1 = SRCMulti_SPDIF input from SRC48 output. + */ + /* 0xffffffff -> C00100FF */ + /* 0 -> Not playback sound, irq still running */ + /* 0xXXXXXX10 = PCM0 Left/Right On. (Front) + * 0xXXXXXX20 = PCM1 Left/Right On. (Center/LFE) + * 0xXXXXXX40 = PCM2 Left/Right On. (Unknown) + * 0xXXXXXX80 = PCM3 Left/Right On. (Rear) + */ +#define PLAYBACK_SPDIF_SELECT 0x70 /* Default: 12030F00 */ + /* 0xffffffff -> 3FF30FFF */ + /* 0x00000001 pauses stream/irq fail. */ + /* All other bits do not effect playback */ +#define PLAYBACK_SPDIF_SRC_SELECT 0x71 /* Default: 0000E4E4 */ + /* 0xffffffff -> F33FFFFF */ + /* All bits do not effect playback */ +#define PLAYBACK_SPDIF_USER_DATA0 0x72 /* SPDIF out user data 0 */ +#define PLAYBACK_SPDIF_USER_DATA1 0x73 /* SPDIF out user data 1 */ +/* 0x74-0x75 unknown */ +#define CAPTURE_SPDIF_CONTROL 0x76 /* SPDIF in control setting */ +#define CAPTURE_SPDIF_STATUS 0x77 /* SPDIF in status */ +#define CAPURE_SPDIF_USER_DATA0 0x78 /* SPDIF in user data 0 */ +#define CAPURE_SPDIF_USER_DATA1 0x79 /* SPDIF in user data 1 */ +#define CAPURE_SPDIF_USER_DATA2 0x7a /* SPDIF in user data 2 */ + diff --git a/sound/pci/emu10k1/p17v.h b/sound/pci/emu10k1/p17v.h new file mode 100644 index 000000000..4ef5f68a9 --- /dev/null +++ b/sound/pci/emu10k1/p17v.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) by James Courtier-Dutton + * Driver p17v chips + * Version: 0.01 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/******************************************************************************/ +/* 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..b69a7f8a2 --- /dev/null +++ b/sound/pci/emu10k1/timer.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) by Lee Revell + * Clemens Ladisch + * Routines for control of EMU10K1 chips + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +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 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..f2d8eb6c8 --- /dev/null +++ b/sound/pci/emu10k1/tina2.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) by James Courtier-Dutton + * Driver tina2 chips + * Version: 0.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/********************************************************************************************************/ +/* 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..f16fd5cfb --- /dev/null +++ b/sound/pci/emu10k1/voice.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Creative Labs, Inc. + * Lee Revell + * Routines for control of EMU10K1 chips - voice manager + * + * Rewrote voice allocator for multichannel support - rlrevell 12/2004 + * + * BUGS: + * -- + * + * TODO: + * -- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +/* Previously the voice allocator started at 0 every time. The new voice + * allocator uses a round robin scheme. The next free voice is tracked in + * the card record and each allocation begins where the last left off. The + * hardware requires stereo interleaved voices be aligned to an even/odd + * boundary. 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..727eb3da1 --- /dev/null +++ b/sound/pci/ens1370.c @@ -0,0 +1,2476 @@ +/* + * Driver for Ensoniq ES1370/ES1371 AudioPCI soundcard + * Copyright (c) by Jaroslav Kysela , + * Thomas Sailer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#ifdef CHIP1371 +#include +#else +#include +#endif +#include +#include + +#ifndef CHIP1371 +#undef CHIP1370 +#define CHIP1370 +#endif + +#ifdef CHIP1370 +#define DRIVER_NAME "ENS1370" +#define CHIP_NAME "ES1370" /* it can be ENS but just to keep compatibility... */ +#else +#define DRIVER_NAME "ENS1371" +#define CHIP_NAME "ES1371" +#endif + + +MODULE_AUTHOR("Jaroslav Kysela , Thomas Sailer "); +MODULE_LICENSE("GPL"); +#ifdef CHIP1370 +MODULE_DESCRIPTION("Ensoniq AudioPCI ES1370"); +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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_ensoniq_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ensoniq_hw_params, + .hw_free = snd_ensoniq_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ensoniq_hw_params, + .hw_free = snd_ensoniq_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ensoniq_hw_params, + .hw_free = snd_ensoniq_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ensoniq->pci), 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ensoniq->pci), 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 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, + 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 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 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 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 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(ensoniq->card, "audiopci", &entry)) + snd_info_set_text_ops(entry, 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 + if (ensoniq->irq >= 0) + synchronize_irq(ensoniq->irq); + 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 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 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)); + synchronize_irq(ensoniq->irq); +} + +#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); + + snd_pcm_suspend_all(ensoniq->pcm1); + snd_pcm_suspend_all(ensoniq->pcm2); + +#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 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; +#ifdef CHIP1370 + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + 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..9d248eb2e --- /dev/null +++ b/sound/pci/es1938.c @@ -0,0 +1,1883 @@ +/* + * Driver for ESS Solo-1 (ES1938, ES1946, ES1969) soundcard + * Copyright (c) by Jaromir Koutek , + * Jaroslav Kysela , + * Thomas Sailer , + * Abramo Bagnara , + * Markus Gruber + * + * Rewritten from sonicvibes.c source. + * + * TODO: + * Rewrite better spinlocks + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + NOTES: + - Capture data is written unaligned starting from dma_base + 1 so I need to + disable mmap and to add a copy callback. + - After several cycle of the following: + while : ; do arecord -d1 -f cd -t raw | aplay -f cd ; done + a "playback write error (DMA or IRQ trouble?)" may happen. + This is due to playback interrupts not generated. + I suspect a timing issue. + - Sometimes the interrupt handler is invoked wrongly during playback. + This generates some harmless "Unexpected hw_pointer: wrong interrupt + acknowledge". + I've seen that using small period sizes. + Reproducible with: + mpg123 test.mp3 & + hdparm -t -T /dev/hda +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaromir Koutek "); +MODULE_DESCRIPTION("ESS Solo-1"); +MODULE_LICENSE("GPL"); +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; +} + +/* + * buffer management + */ +static int snd_es1938_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) + +{ + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + return 0; +} + +static int snd_es1938_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* ---------------------------------------------------------------------- + * 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_es1938_pcm_hw_params, + .hw_free = snd_es1938_pcm_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_es1938_pcm_hw_params, + .hw_free = snd_es1938_pcm_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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 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 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; + unsigned char *s, *d; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + + /* 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; + } + 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; + unsigned char *s, *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; + 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 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; + 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, 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..0b1845ca6 --- /dev/null +++ b/sound/pci/es1968.c @@ -0,0 +1,2934 @@ +/* + * Driver for ESS Maestro 1/2/2E Sound Card (started 21.8.99) + * Copyright (c) by Matze Braun . + * Takashi Iwai + * + * Most of the driver code comes from Zach Brown(zab@redhat.com) + * Alan Cox OSS Driver + * Rewritted from card-es1938.c source. + * + * TODO: + * Perhaps Synth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Notes from Zach Brown about the driver code + * + * Hardware Description + * + * A working Maestro setup contains the Maestro chip wired to a + * codec or 2. In the Maestro we have the APUs, the ASSP, and the + * Wavecache. The APUs can be though of as virtual audio routing + * channels. They can take data from a number of sources and perform + * basic encodings of the data. The wavecache is a storehouse for + * PCM data. Typically it deals with PCI and interracts with the + * APUs. The ASSP is a wacky DSP like device that ESS is loth + * to release docs on. Thankfully it isn't required on the Maestro + * until you start doing insane things like FM emulation and surround + * encoding. The codecs are almost always AC-97 compliant codecs, + * but it appears that early Maestros may have had PT101 (an ESS + * part?) wired to them. The only real difference in the Maestro + * families is external goop like docking capability, memory for + * the ASSP, and initialization differences. + * + * Driver Operation + * + * We only drive the APU/Wavecache as typical DACs and drive the + * mixers in the codecs. There are 64 APUs. We assign 6 to each + * /dev/dsp? device. 2 channels for output, and 4 channels for + * input. + * + * Each APU can do a number of things, but we only really use + * 3 basic functions. For playback we use them to convert PCM + * data fetched over PCI by the wavecahche into analog data that + * is handed to the codec. One APU for mono, and a pair for stereo. + * When in stereo, the combination of smarts in the APU and Wavecache + * decide which wavecache gets the left or right channel. + * + * For record we still use the old overly mono system. For each in + * coming channel the data comes in from the codec, through a 'input' + * APU, through another rate converter APU, and then into memory via + * the wavecache and PCI. If its stereo, we mash it back into LRLR in + * software. The pass between the 2 APUs is supposedly what requires us + * to have a 512 byte buffer sitting around in wavecache/memory. + * + * The wavecache makes our life even more fun. First off, it can + * only address the first 28 bits of PCI address space, making it + * useless on quite a few architectures. Secondly, its insane. + * It claims to fetch from 4 regions of PCI space, each 4 meg in length. + * But that doesn't really work. You can only use 1 region. So all our + * allocations have to be in 4meg of each other. Booo. Hiss. + * So we have a module parameter, dsps_order, that is the order of + * the number of dsps to provide. All their buffer space is allocated + * on open time. The sonicvibes OSS routines we inherited really want + * power of 2 buffers, so we have all those next to each other, then + * 512 byte regions for the recording wavecaches. This ends up + * wasting quite a bit of memory. The only fixes I can see would be + * getting a kernel allocator that could work in zones, or figuring out + * just how to coerce the WP into doing what we want. + * + * The indirection of the various registers means we have to spinlock + * nearly all register accesses. We have the main register indirection + * like the wave cache, maestro registers, etc. Then we have beasts + * like the APU interface that is indirect registers gotten at through + * the main maestro indirection. Ouch. We spinlock around the actual + * ports on a per card basis. This means spinlock activity at each IO + * operation, but the only IO operation clusters are in non critical + * paths and it makes the code far easier to follow. Interrupts are + * blocked while holding the locks because the int handler has to + * get at some of them :(. The mixer interface doesn't, however. + * We also have an OSS state lock that is thrown around in a few + * places. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef CONFIG_SND_ES1968_RADIO +#include +#endif + +#define CARD_NAME "ESS Maestro1/2" +#define DRIVER_NAME "ES1968" + +MODULE_DESCRIPTION("ESS Maestro"); +MODULE_LICENSE("GPL"); +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; + + chip->dma.dev.type = SNDRV_DMA_TYPE_DEV; + chip->dma.dev.dev = snd_dma_pci_data(chip->pci); + err = snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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 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_pcm_suspend_all(chip->pcm); + 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 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) { + if (chip->irq >= 0) + synchronize_irq(chip->irq); + 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 struct ess_device_list pm_whitelist[] = { + { 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 struct ess_device_list mpu_blacklist[] = { + { 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 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; + + /* 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 whitelist */ + unsigned short vend; + pci_read_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend); + for (i = 0; i < (int)ARRAY_SIZE(pm_whitelist); i++) { + if (chip->type == pm_whitelist[i].type && + vend == pm_whitelist[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 black list */ + unsigned short vend; + pci_read_config_word(chip->pci, PCI_SUBSYSTEM_VENDOR_ID, &vend); + for (i = 0; i < ARRAY_SIZE(mpu_blacklist); i++) { + if (chip->type == mpu_blacklist[i].type && + vend == mpu_blacklist[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..e3fb9c610 --- /dev/null +++ b/sound/pci/fm801.c @@ -0,0 +1,1463 @@ +/* + * The driver for the ForteMedia FM801 based soundcards + * Copyright (c) by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SND_FM801_TEA575X_BOOL +#include +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ForteMedia FM801"); +MODULE_LICENSE("GPL"); +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 + * @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 + */ +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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_fm801_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_fm801_hw_params, + .hw_free = snd_fm801_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_fm801_hw_params, + .hw_free = snd_fm801_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(pdev), + 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 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 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 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 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 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; + 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 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_pcm_suspend_all(chip->pcm); + 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..4235907b7 --- /dev/null +++ b/sound/pci/hda/Kconfig @@ -0,0 +1,231 @@ +menu "HD-Audio" + +config SND_HDA + tristate + select SND_PCM + select SND_VMASTER + select SND_JACK + select SND_HDA_CORE + +config SND_HDA_INTEL + tristate "HD Audio PCI" + depends on SND_PCI + select SND_HDA + 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 + 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 + 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 Device HD-audio codec support" + select SND_HDA_GENERIC + help + Say Y or M here to include Analog Device 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 + 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 + 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 + 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" + 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. + +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..8371274aa --- /dev/null +++ b/sound/pci/hda/ca0132_regs.h @@ -0,0 +1,409 @@ +/* + * HD audio interface patch for Creative CA0132 chip. + * CA0132 registers defines. + * + * Copyright (c) 2011, Creative Technology Ltd. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver 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 + */ + +#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/dell_wmi_helper.c b/sound/pci/hda/dell_wmi_helper.c new file mode 100644 index 000000000..bbd6c87a4 --- /dev/null +++ b/sound/pci/hda/dell_wmi_helper.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Helper functions for Dell Mic Mute LED control; + * to be included from codec driver + */ + +#if IS_ENABLED(CONFIG_DELL_LAPTOP) +#include + +static int (*dell_micmute_led_set_func)(int); + +static void dell_micmute_update(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + dell_micmute_led_set_func(spec->micmute_led.led_value); +} + +static void alc_fixup_dell_wmi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + bool removefunc = false; + + if (action == HDA_FIXUP_ACT_PROBE) { + if (!dell_micmute_led_set_func) + dell_micmute_led_set_func = symbol_request(dell_micmute_led_set); + if (!dell_micmute_led_set_func) { + codec_warn(codec, "Failed to find dell wmi symbol dell_micmute_led_set\n"); + return; + } + + removefunc = (dell_micmute_led_set_func(false) < 0) || + (snd_hda_gen_add_micmute_led(codec, + dell_micmute_update) < 0); + } + + if (dell_micmute_led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) { + symbol_put(dell_micmute_led_set); + dell_micmute_led_set_func = NULL; + } +} + +#else /* CONFIG_DELL_LAPTOP */ +static void alc_fixup_dell_wmi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ +} + +#endif /* CONFIG_DELL_LAPTOP */ diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c new file mode 100644 index 000000000..d23d61dbc --- /dev/null +++ b/sound/pci/hda/hda_auto_parser.c @@ -0,0 +1,1046 @@ +/* + * BIOS auto-parser helper functions for HD-audio + * + * Copyright (c) 2012 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include "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)) { + int 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); +} + +static void 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) + 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; + } +} + +/** + * 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) + 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) +{ + 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 (!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 + */ +void snd_hda_pick_pin_fixup(struct hda_codec *codec, + const struct snd_hda_pin_quirk *pin_quirk, + const struct hda_fixup *fixlist) +{ + 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)) { + 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..2b8e29fd7 --- /dev/null +++ b/sound/pci/hda/hda_auto_parser.h @@ -0,0 +1,119 @@ +/* + * BIOS auto-parser helper functions for HD-audio + * + * Copyright (c) 2012 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#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..0224011a2 --- /dev/null +++ b/sound/pci/hda/hda_beep.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Digital Beep Input Interface for HD-audio codec + * + * Author: Matt Ranostay + * Copyright (c) 2008 Embedded Alley Solutions Inc + */ + +#include +#include +#include +#include +#include +#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; + /* fallthru */ + 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_off_beep(struct hda_beep *beep) +{ + cancel_work_sync(&beep->beep_work); + if (beep->playing) { + /* turn off beep */ + generate_tone(beep, 0); + } +} + +static void snd_hda_do_detach(struct hda_beep *beep) +{ + if (beep->registered) + input_unregister_device(beep->dev); + else + input_free_device(beep->dev); + beep->dev = NULL; + turn_off_beep(beep); +} + +static int snd_hda_do_attach(struct hda_beep *beep) +{ + struct input_dev *input_dev; + struct hda_codec *codec = beep->codec; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + /* 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; + return 0; +} + +/** + * 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_off_beep(beep); + return 1; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_enable_beep_device); + +/** + * 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. + * + * The attached beep device has to be registered via + * snd_hda_register_beep_device() and released via snd_hda_detach_beep_device() + * appropriately. + * + * Currently, only one beep device is allowed to each codec. + */ +int snd_hda_attach_beep_device(struct hda_codec *codec, int nid) +{ + 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); + + err = snd_hda_do_attach(beep); + if (err < 0) { + kfree(beep); + codec->beep = NULL; + return err; + } + + return 0; +} +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) +{ + struct hda_beep *beep = codec->beep; + if (beep) { + if (beep->dev) + snd_hda_do_detach(beep); + codec->beep = NULL; + kfree(beep); + } +} +EXPORT_SYMBOL_GPL(snd_hda_detach_beep_device); + +/** + * snd_hda_register_beep_device - Register the beep device + * @codec: the HDA codec + */ +int snd_hda_register_beep_device(struct hda_codec *codec) +{ + struct hda_beep *beep = codec->beep; + int err; + + if (!beep || !beep->dev) + return 0; + + err = input_register_device(beep->dev); + if (err < 0) { + codec_err(codec, "hda_beep: unable to register input device\n"); + input_free_device(beep->dev); + codec->beep = NULL; + kfree(beep); + return err; + } + beep->registered = true; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_register_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..d1a6a9c13 --- /dev/null +++ b/sound/pci/hda/hda_beep.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Digital Beep Input Interface for HD-audio codec + * + * Author: Matt Ranostay + * Copyright (c) 2008 Embedded Alley Solutions Inc + */ + +#ifndef __SOUND_HDA_BEEP_H +#define __SOUND_HDA_BEEP_H + +#include "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; + 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); +int snd_hda_register_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) +{ +} +static inline int snd_hda_register_beep_device(struct hda_codec *codec) +{ + return 0; +} +#endif +#endif diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c new file mode 100644 index 000000000..66010d077 --- /dev/null +++ b/sound/pci/hda/hda_bind.c @@ -0,0 +1,316 @@ +/* + * HD-audio codec driver binding + * Copyright (c) Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "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 (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); + return err; +} + +static int hda_codec_driver_remove(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + if (codec->patch_ops.free) + codec->patch_ops.free(codec); + snd_hda_codec_cleanup_for_unbind(codec); + 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 (is_generic_config(codec)) + codec->probe_id = HDA_CODEC_ID_GENERIC; + else + codec->probe_id = 0; + + 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_err(codec, "Unable to bind the codec\n"); + goto error; + } + } + + return 0; + + error: + snd_hdac_device_unregister(&codec->core); + return err; +} +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..b43558ffd --- /dev/null +++ b/sound/pci/hda/hda_codec.c @@ -0,0 +1,4032 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Copyright (c) 2004 Takashi Iwai + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hda_codec.h" +#include +#include +#include +#include +#include "hda_local.h" +#include "hda_beep.h" +#include "hda_jack.h" +#include + +#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[0]; +}; + +/* 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(sizeof(*p) + len * sizeof(hda_nid_t), 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); + + snd_hda_jack_set_dirty_all(codec); + snd_hda_jack_poll_all(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->preset = NULL; + codec->slave_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); +} + +static unsigned int hda_set_power_state(struct hda_codec *codec, + unsigned int power_state); + +/* 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))) { + snd_hda_register_beep_device(codec); + snd_hdac_link_power(&codec->core, 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_disconnect(struct snd_device *device) +{ + struct hda_codec *codec = device->device_data; + + snd_hda_detach_beep_device(codec); + 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_hdac_device_unregister(&codec->core); + snd_hdac_link_power(&codec->core, false); + 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); + 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 + * @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 struct snd_device_ops dev_ops = { + .dev_register = snd_hda_codec_dev_register, + .dev_disconnect = snd_hda_codec_dev_disconnect, + .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; + + 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, true); + 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); + +/** + * 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 = 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 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) +{ + int orig; + + if (!codec->core.regmap) + return -EINVAL; + regcache_cache_only(codec->core.regmap, true); + orig = snd_hda_codec_amp_read(codec, nid, ch, dir, idx); + regcache_cache_only(codec->core.regmap, false); + if (orig >= 0) + return 0; + return snd_hda_codec_amp_update(codec, nid, ch, dir, idx, 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_slave_func_t)(struct hda_codec *, void *, struct snd_kcontrol *); + +/* apply the function to all matching slave ctls in the mixer list */ +static int map_slaves(struct hda_codec *codec, const char * const *slaves, + const char *suffix, map_slave_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 = slaves; *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_slave_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 slave_init_arg { + struct hda_codec *codec; + int step; +}; + +/* initialize the slave volume with 0dB via snd_ctl_apply_vmaster_slaves() */ +static int init_slave_0dB(struct snd_kcontrol *slave, + struct snd_kcontrol *kctl, + void *_arg) +{ + struct slave_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 slave %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 slave (%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(slave, val); + return val; + } + + return 0; +} + +/* unmute the slave via snd_ctl_apply_vmaster_slaves() */ +static int init_slave_unmute(struct snd_kcontrol *slave, + struct snd_kcontrol *kctl, + void *_arg) +{ + return put_kctl_with_value(slave, 1); +} + +static int add_slave(struct hda_codec *codec, + void *data, struct snd_kcontrol *slave) +{ + return snd_ctl_add_slave(data, slave); +} + +/** + * __snd_hda_add_vmaster - create a virtual master control and add slaves + * @codec: HD-audio codec + * @name: vmaster control name + * @tlv: TLV data (optional) + * @slaves: slave control names (optional) + * @suffix: suffix string to each slave name (optional) + * @init_slave_vol: initialize slaves 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. + * + * @slaves is a NULL-terminated array of strings, each of which is a + * slave 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 *slaves, + const char *suffix, bool init_slave_vol, + struct snd_kcontrol **ctl_ret) +{ + struct snd_kcontrol *kctl; + int err; + + if (ctl_ret) + *ctl_ret = NULL; + + err = map_slaves(codec, slaves, suffix, check_slave_present, NULL); + if (err != 1) { + codec_dbg(codec, "No slave 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_slaves(codec, slaves, suffix, add_slave, kctl); + if (err < 0) + return err; + + /* init with master mute & zero volume */ + put_kctl_with_value(kctl, 0); + if (init_slave_vol) { + struct slave_init_arg arg = { + .codec = codec, + .step = 0, + }; + snd_ctl_apply_vmaster_slaves(kctl, + tlv ? init_slave_0dB : init_slave_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 slaves */ +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->slave_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 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; + 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 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; + 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); + if (codec->core.regmap) + regcache_sync(codec->core.regmap); + } + + 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); + struct hda_pcm *pcm; + unsigned int state; + + cancel_delayed_work_sync(&codec->jackpoll_work); + list_for_each_entry(pcm, &codec->pcm_list_head, list) + snd_pcm_suspend_all(pcm->pcm); + 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); + snd_hdac_link_power(&codec->core, false); + return 0; +} + +static int hda_codec_runtime_resume(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + + snd_hdac_link_power(&codec->core, 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_force_resume(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + bool forced_resume = !codec->relaxed_resume; + int ret; + + /* The get/put pair below enforces the runtime resume even if the + * device hasn't been used at suspend time. This trick is needed to + * update the jack state change during the sleep. + */ + if (forced_resume) + pm_runtime_get_noresume(dev); + ret = pm_runtime_force_resume(dev); + if (forced_resume) + pm_runtime_put(dev); + return ret; +} + +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 hda_codec_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 hda_codec_force_resume(dev); +} + +static int hda_codec_pm_restore(struct device *dev) +{ + dev->power.power_state = PMSG_RESTORE; + return hda_codec_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 + .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 - Prepare a stream + * @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 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->slave_dig_outs) { + const hda_nid_t *d; + for (d = codec->slave_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->slave_dig_outs) { + const hda_nid_t *d; + for (d = codec->slave_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 slave, 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 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 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_codec.h b/sound/pci/hda/hda_codec.h new file mode 100644 index 000000000..2003403ce --- /dev/null +++ b/sound/pci/hda/hda_codec.h @@ -0,0 +1,537 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Copyright (c) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOUND_HDA_CODEC_H +#define __SOUND_HDA_CODEC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Structures + */ + +struct hda_bus; +struct hda_beep; +struct hda_codec; +struct hda_pcm; +struct hda_pcm_stream; + +/* + * codec bus + * + * each controller needs to creata a hda_bus to assign the accessor. + * A hda_bus contains several codecs in the list codec_list. + */ +struct hda_bus { + struct hdac_bus core; + + struct snd_card *card; + + struct pci_dev *pci; + const char *modelname; + + struct mutex prepare_mutex; + + /* assigned PCMs */ + DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES); + + /* misc op flags */ + unsigned int needs_damn_long_delay :1; + unsigned int allow_bus_reset:1; /* allow bus reset at fatal error */ + /* status for codec/controller */ + unsigned int shutdown :1; /* being unloaded */ + unsigned int response_reset:1; /* controller was reset */ + unsigned int in_reset:1; /* during reset operation */ + unsigned int no_response_fallback:1; /* don't fallback at RIRB error */ + unsigned int bus_probing :1; /* during probing process */ + + int primary_dig_out_type; /* primary digital out PCM type */ + unsigned int mixer_assigned; /* codec addr for mixer name */ +}; + +/* from hdac_bus to hda_bus */ +#define to_hda_bus(bus) container_of(bus, struct hda_bus, core) + +/* + * codec preset + * + * Known codecs have the patch to build and set up the controls/PCMs + * better than the generic parser. + */ +typedef int (*hda_codec_patch_t)(struct hda_codec *); + +#define HDA_CODEC_ID_SKIP_PROBE 0x00000001 +#define HDA_CODEC_ID_GENERIC_HDMI 0x00000101 +#define HDA_CODEC_ID_GENERIC 0x00000201 + +#define HDA_CODEC_REV_ENTRY(_vid, _rev, _name, _patch) \ + { .vendor_id = (_vid), .rev_id = (_rev), .name = (_name), \ + .api_version = HDA_DEV_LEGACY, \ + .driver_data = (unsigned long)(_patch) } +#define HDA_CODEC_ENTRY(_vid, _name, _patch) \ + HDA_CODEC_REV_ENTRY(_vid, 0, _name, _patch) + +struct hda_codec_driver { + struct hdac_driver core; + const struct hda_device_id *id; +}; + +int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name, + struct module *owner); +#define hda_codec_driver_register(drv) \ + __hda_codec_driver_register(drv, KBUILD_MODNAME, THIS_MODULE) +void hda_codec_driver_unregister(struct hda_codec_driver *drv); +#define module_hda_codec_driver(drv) \ + module_driver(drv, hda_codec_driver_register, \ + hda_codec_driver_unregister) + +/* ops set by the preset patch */ +struct hda_codec_ops { + int (*build_controls)(struct hda_codec *codec); + int (*build_pcms)(struct hda_codec *codec); + int (*init)(struct hda_codec *codec); + void (*free)(struct hda_codec *codec); + void (*unsol_event)(struct hda_codec *codec, unsigned int res); + void (*set_power_state)(struct hda_codec *codec, hda_nid_t fg, + unsigned int power_state); +#ifdef CONFIG_PM + int (*suspend)(struct hda_codec *codec); + int (*resume)(struct hda_codec *codec); + int (*check_power_status)(struct hda_codec *codec, hda_nid_t nid); +#endif + void (*reboot_notify)(struct hda_codec *codec); + void (*stream_pm)(struct hda_codec *codec, hda_nid_t nid, bool on); +}; + +/* PCM callbacks */ +struct hda_pcm_ops { + int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec, + struct snd_pcm_substream *substream); + int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec, + struct snd_pcm_substream *substream); + int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec, + unsigned int stream_tag, unsigned int format, + struct snd_pcm_substream *substream); + int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec, + struct snd_pcm_substream *substream); + unsigned int (*get_delay)(struct hda_pcm_stream *info, + struct hda_codec *codec, + struct snd_pcm_substream *substream); +}; + +/* PCM information for each substream */ +struct hda_pcm_stream { + unsigned int substreams; /* number of substreams, 0 = not exist*/ + unsigned int channels_min; /* min. number of channels */ + unsigned int channels_max; /* max. number of channels */ + hda_nid_t nid; /* default NID to query rates/formats/bps, or set up */ + u32 rates; /* supported rates */ + u64 formats; /* supported formats (SNDRV_PCM_FMTBIT_) */ + unsigned int maxbps; /* supported max. bit per sample */ + const struct snd_pcm_chmap_elem *chmap; /* chmap to override */ + struct hda_pcm_ops ops; +}; + +/* PCM types */ +enum { + HDA_PCM_TYPE_AUDIO, + HDA_PCM_TYPE_SPDIF, + HDA_PCM_TYPE_HDMI, + HDA_PCM_TYPE_MODEM, + HDA_PCM_NTYPES +}; + +#define SNDRV_PCM_INVALID_DEVICE (-1) +/* for PCM creation */ +struct hda_pcm { + char *name; + struct hda_pcm_stream stream[2]; + unsigned int pcm_type; /* HDA_PCM_TYPE_XXX */ + int device; /* device number to assign */ + struct snd_pcm *pcm; /* assigned PCM instance */ + bool own_chmap; /* codec driver provides own channel maps */ + /* private: */ + struct hda_codec *codec; + struct kref kref; + struct list_head list; +}; + +/* codec information */ +struct hda_codec { + struct hdac_device core; + struct hda_bus *bus; + struct snd_card *card; + unsigned int addr; /* codec addr*/ + u32 probe_id; /* overridden id for probing */ + + /* detected preset */ + const struct hda_device_id *preset; + const char *modelname; /* model name for preset */ + + /* set by patch */ + struct hda_codec_ops patch_ops; + + /* PCM to create, set by patch_ops.build_pcms callback */ + struct list_head pcm_list_head; + + /* codec specific info */ + void *spec; + + /* beep device */ + struct hda_beep *beep; + unsigned int beep_mode; + + /* widget capabilities cache */ + u32 *wcaps; + + struct snd_array mixers; /* list of assigned mixer elements */ + struct snd_array nids; /* list of mapped mixer elements */ + + struct list_head conn_list; /* linked-list of connection-list */ + + struct mutex spdif_mutex; + struct mutex control_mutex; + struct snd_array spdif_out; + unsigned int spdif_in_enable; /* SPDIF input enable? */ + const hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */ + struct snd_array init_pins; /* initial (BIOS) pin configurations */ + struct snd_array driver_pins; /* pin configs set by codec parser */ + struct snd_array cvt_setups; /* audio convert setups */ + + struct mutex user_mutex; +#ifdef CONFIG_SND_HDA_RECONFIG + struct snd_array init_verbs; /* additional init verbs */ + struct snd_array hints; /* additional hints */ + struct snd_array user_pins; /* default pin configs to override */ +#endif + +#ifdef CONFIG_SND_HDA_HWDEP + struct snd_hwdep *hwdep; /* assigned hwdep device */ +#endif + + /* misc flags */ + unsigned int in_freeing:1; /* being released */ + unsigned int registered:1; /* codec was registered */ + unsigned int spdif_status_reset :1; /* needs to toggle SPDIF for each + * status change + * (e.g. Realtek codecs) + */ + unsigned int pin_amp_workaround:1; /* pin out-amp takes index + * (e.g. Conexant codecs) + */ + unsigned int single_adc_amp:1; /* adc in-amp takes no index + * (e.g. CX20549 codec) + */ + unsigned int no_sticky_stream:1; /* no sticky-PCM stream assignment */ + unsigned int pins_shutup:1; /* pins are shut up */ + unsigned int no_trigger_sense:1; /* don't trigger at pin-sensing */ + unsigned int no_jack_detect:1; /* Machine has no jack-detection */ + unsigned int inv_eapd:1; /* broken h/w: inverted EAPD control */ + unsigned int inv_jack_detect:1; /* broken h/w: inverted detection bit */ + unsigned int pcm_format_first:1; /* PCM format must be set first */ + unsigned int cached_write:1; /* write only to caches */ + unsigned int dp_mst:1; /* support DP1.2 Multi-stream transport */ + unsigned int dump_coef:1; /* dump processing coefs in codec proc file */ + unsigned int power_save_node:1; /* advanced PM for each widget */ + unsigned int auto_runtime_pm:1; /* enable automatic codec runtime pm */ + unsigned int force_pin_prefix:1; /* Add location prefix */ + unsigned int link_down_at_suspend:1; /* link down at runtime suspend */ + unsigned int relaxed_resume:1; /* don't resume forcibly for jack */ + +#ifdef CONFIG_PM + unsigned long power_on_acct; + unsigned long power_off_acct; + unsigned long power_jiffies; +#endif + + /* filter the requested power state per nid */ + unsigned int (*power_filter)(struct hda_codec *codec, hda_nid_t nid, + unsigned int power_state); + + /* codec-specific additional proc output */ + void (*proc_widget_hook)(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid); + + /* jack detection */ + struct snd_array jacktbl; + unsigned long jackpoll_interval; /* In jiffies. Zero means no poll, rely on unsol events */ + struct delayed_work jackpoll_work; + + /* jack detection */ + struct snd_array jacks; + + int depop_delay; /* depop delay in ms, -1 for default delay time */ + + /* fix-up list */ + int fixup_id; + const struct hda_fixup *fixup_list; + const char *fixup_name; + + /* additional init verbs */ + struct snd_array verbs; +}; + +#define dev_to_hda_codec(_dev) container_of(_dev, struct hda_codec, core.dev) +#define hda_codec_dev(_dev) (&(_dev)->core.dev) + +#define list_for_each_codec(c, bus) \ + list_for_each_entry(c, &(bus)->core.codec_list, core.list) +#define list_for_each_codec_safe(c, n, bus) \ + list_for_each_entry_safe(c, n, &(bus)->core.codec_list, core.list) + +/* snd_hda_codec_read/write optional flags */ +#define HDA_RW_NO_RESPONSE_FALLBACK (1 << 0) + +/* + * constructors + */ +int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card, + unsigned int codec_addr, struct hda_codec **codecp); +int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card, + unsigned int codec_addr, struct hda_codec *codec); +int snd_hda_codec_configure(struct hda_codec *codec); +int snd_hda_codec_update_widgets(struct hda_codec *codec); + +/* + * low level functions + */ +static inline unsigned int +snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid, + int flags, + unsigned int verb, unsigned int parm) +{ + return snd_hdac_codec_read(&codec->core, nid, flags, verb, parm); +} + +static inline int +snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int flags, + unsigned int verb, unsigned int parm) +{ + return snd_hdac_codec_write(&codec->core, nid, flags, verb, parm); +} + +#define snd_hda_param_read(codec, nid, param) \ + snd_hdac_read_parm(&(codec)->core, nid, param) +#define snd_hda_get_sub_nodes(codec, nid, start_nid) \ + snd_hdac_get_sub_nodes(&(codec)->core, nid, start_nid) +int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid, + hda_nid_t *conn_list, int max_conns); +static inline int +snd_hda_get_num_conns(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_get_connections(codec, nid, NULL, 0); +} + +#define snd_hda_get_raw_connections(codec, nid, list, max_conns) \ + snd_hdac_get_connections(&(codec)->core, nid, list, max_conns) +#define snd_hda_get_num_raw_conns(codec, nid) \ + snd_hdac_get_connections(&(codec)->core, nid, NULL, 0); + +int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid, + const hda_nid_t **listp); +int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int nums, + const hda_nid_t *list); +int snd_hda_get_conn_index(struct hda_codec *codec, hda_nid_t mux, + hda_nid_t nid, int recursive); +unsigned int snd_hda_get_num_devices(struct hda_codec *codec, hda_nid_t nid); +int snd_hda_get_devices(struct hda_codec *codec, hda_nid_t nid, + u8 *dev_list, int max_devices); +int snd_hda_get_dev_select(struct hda_codec *codec, hda_nid_t nid); +int snd_hda_set_dev_select(struct hda_codec *codec, hda_nid_t nid, int dev_id); + +struct hda_verb { + hda_nid_t nid; + u32 verb; + u32 param; +}; + +void snd_hda_sequence_write(struct hda_codec *codec, + const struct hda_verb *seq); + +/* unsolicited event */ +static inline void +snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex) +{ + snd_hdac_bus_queue_event(&bus->core, res, res_ex); +} + +/* cached write */ +static inline int +snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, + int flags, unsigned int verb, unsigned int parm) +{ + return snd_hdac_regmap_write(&codec->core, nid, verb, parm); +} + +/* the struct for codec->pin_configs */ +struct hda_pincfg { + hda_nid_t nid; + unsigned char ctrl; /* original pin control value */ + unsigned char target; /* target pin control value */ + unsigned int cfg; /* default configuration */ +}; + +unsigned int snd_hda_codec_get_pincfg(struct hda_codec *codec, hda_nid_t nid); +int snd_hda_codec_set_pincfg(struct hda_codec *codec, hda_nid_t nid, + unsigned int cfg); +int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list, + hda_nid_t nid, unsigned int cfg); /* for hwdep */ +void snd_hda_shutup_pins(struct hda_codec *codec); + +/* SPDIF controls */ +struct hda_spdif_out { + hda_nid_t nid; /* Converter nid values relate to */ + unsigned int status; /* IEC958 status bits */ + unsigned short ctls; /* SPDIF control bits */ +}; +struct hda_spdif_out *snd_hda_spdif_out_of_nid(struct hda_codec *codec, + hda_nid_t nid); +void snd_hda_spdif_ctls_unassign(struct hda_codec *codec, int idx); +void snd_hda_spdif_ctls_assign(struct hda_codec *codec, int idx, hda_nid_t nid); + +/* + * Mixer + */ +int snd_hda_codec_build_controls(struct hda_codec *codec); + +/* + * PCM + */ +int snd_hda_codec_parse_pcms(struct hda_codec *codec); +int snd_hda_codec_build_pcms(struct hda_codec *codec); + +__printf(2, 3) +struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec, + const char *fmt, ...); + +static inline void snd_hda_codec_pcm_get(struct hda_pcm *pcm) +{ + kref_get(&pcm->kref); +} +void snd_hda_codec_pcm_put(struct hda_pcm *pcm); + +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); +void snd_hda_codec_cleanup(struct hda_codec *codec, + struct hda_pcm_stream *hinfo, + struct snd_pcm_substream *substream); + +void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, + u32 stream_tag, + int channel_id, int format); +void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid, + int do_now); +#define snd_hda_codec_cleanup_stream(codec, nid) \ + __snd_hda_codec_cleanup_stream(codec, nid, 0) + +#define snd_hda_query_supported_pcm(codec, nid, ratesp, fmtsp, bpsp) \ + snd_hdac_query_supported_pcm(&(codec)->core, nid, ratesp, fmtsp, bpsp) +#define snd_hda_is_supported_format(codec, nid, fmt) \ + snd_hdac_is_supported_format(&(codec)->core, nid, fmt) + +extern const struct snd_pcm_chmap_elem snd_pcm_2_1_chmaps[]; + +int snd_hda_attach_pcm_stream(struct hda_bus *_bus, struct hda_codec *codec, + struct hda_pcm *cpcm); + +/* + * Misc + */ +void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen); +void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg, + unsigned int power_state); + +int snd_hda_lock_devices(struct hda_bus *bus); +void snd_hda_unlock_devices(struct hda_bus *bus); +void snd_hda_bus_reset(struct hda_bus *bus); +void snd_hda_bus_reset_codecs(struct hda_bus *bus); + +int snd_hda_codec_set_name(struct hda_codec *codec, const char *name); + +/* + * power management + */ +extern const struct dev_pm_ops hda_codec_driver_pm; + +static inline +int hda_call_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ +#ifdef CONFIG_PM + if (codec->patch_ops.check_power_status) + return codec->patch_ops.check_power_status(codec, nid); +#endif + return 0; +} + +/* + * power saving + */ +#define snd_hda_power_up(codec) snd_hdac_power_up(&(codec)->core) +#define snd_hda_power_up_pm(codec) snd_hdac_power_up_pm(&(codec)->core) +#define snd_hda_power_down(codec) snd_hdac_power_down(&(codec)->core) +#define snd_hda_power_down_pm(codec) snd_hdac_power_down_pm(&(codec)->core) +#ifdef CONFIG_PM +void snd_hda_set_power_save(struct hda_bus *bus, int delay); +void snd_hda_update_power_acct(struct hda_codec *codec); +#else +static inline void snd_hda_set_power_save(struct hda_bus *bus, int delay) {} +#endif + +#ifdef CONFIG_SND_HDA_PATCH_LOADER +/* + * patch firmware + */ +int snd_hda_load_patch(struct hda_bus *bus, size_t size, const void *buf); +#endif + +#ifdef CONFIG_SND_HDA_DSP_LOADER +int snd_hda_codec_load_dsp_prepare(struct hda_codec *codec, unsigned int format, + unsigned int size, + struct snd_dma_buffer *bufp); +void snd_hda_codec_load_dsp_trigger(struct hda_codec *codec, bool start); +void snd_hda_codec_load_dsp_cleanup(struct hda_codec *codec, + struct snd_dma_buffer *dmab); +#else +static inline int +snd_hda_codec_load_dsp_prepare(struct hda_codec *codec, unsigned int format, + unsigned int size, + struct snd_dma_buffer *bufp) +{ + return -ENOSYS; +} +static inline void +snd_hda_codec_load_dsp_trigger(struct hda_codec *codec, bool start) {} +static inline void +snd_hda_codec_load_dsp_cleanup(struct hda_codec *codec, + struct snd_dma_buffer *dmab) {} +#endif + +#endif /* __SOUND_HDA_CODEC_H */ diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c new file mode 100644 index 000000000..0c5d41e5d --- /dev/null +++ b/sound/pci/hda/hda_controller.c @@ -0,0 +1,1420 @@ +/* + * + * Implementation of primary alsa driver code base for Intel HD Audio. + * + * Copyright(c) 2004 Intel Corporation. All rights reserved. + * + * Copyright (c) 2004 Takashi Iwai + * PeiSen Hou + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_X86 +/* for art-tsc conversion */ +#include +#endif + +#include +#include +#include "hda_controller.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; + + 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; + ret = chip->ops->substream_alloc_pages(chip, substream, + params_buffer_bytes(hw_params)); +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 azx *chip = apcm->chip; + struct hda_pcm_stream *hinfo = to_hda_pcm_stream(substream); + int err; + + /* 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); + + err = chip->ops->substream_free_pages(chip, substream); + azx_stream(azx_dev)->prepared = 0; + dsp_unlock(azx_dev); + return err; +} + +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 = 200; + 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 timespec *system_ts, struct timespec *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_timespec(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_timespec(xtstamp.sys_monoraw); + break; + + default: + *system_ts = ktime_to_timespec(xtstamp.sys_realtime); + break; + + } + + *audio_ts = ktime_to_timespec(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 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +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; + + 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; + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + 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; + unsigned long timeout; + unsigned long loopcounter; + int do_poll = 0; + + again: + timeout = jiffies + msecs_to_jiffies(1000); + + for (loopcounter = 0;; loopcounter++) { + spin_lock_irq(&bus->reg_lock); + if (chip->polling_mode || do_poll) + snd_hdac_bus_update_rirb(bus); + if (!bus->rirb.cmds[addr]) { + if (!do_poll) + chip->poll_count = 0; + if (res) + *res = bus->rirb.res[addr]; /* the last value */ + spin_unlock_irq(&bus->reg_lock); + return 0; + } + spin_unlock_irq(&bus->reg_lock); + if (time_after(jiffies, timeout)) + break; + if (hbus->needs_damn_long_delay || loopcounter > 3000) + msleep(2); /* temporary workaround */ + else { + udelay(10); + cond_resched(); + } + } + + if (hbus->no_response_fallback) + return -EIO; + + if (!chip->polling_mode && chip->poll_count < 2) { + dev_dbg(chip->card->dev, + "azx_get_response timeout, polling the codec once: last cmd=0x%08x\n", + bus->last_cmd[addr]); + do_poll = 1; + chip->poll_count++; + goto again; + } + + + if (!chip->polling_mode) { + dev_warn(chip->card->dev, + "azx_get_response timeout, switching to polling mode: last cmd=0x%08x\n", + bus->last_cmd[addr]); + chip->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 int azx_link_power(struct hdac_bus *bus, bool enable) +{ + struct azx *chip = bus_to_azx(bus); + + if (chip->ops->link_power) + return chip->ops->link_power(chip, enable); + else + return -EINVAL; +} + +static const struct hdac_bus_ops bus_core_ops = { + .command = azx_send_cmd, + .get_response = azx_get_response, + .link_power = azx_link_power, +}; + +#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); + struct hdac_stream *s; + + list_for_each_entry(s, &bus->stream_list, list) + snd_hdac_stream_stop(s); +} +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; +} + +static int get_jackpoll_interval(struct azx *chip) +{ + int i; + unsigned int j; + + if (!chip->jackpoll_ms) + return 0; + + i = chip->jackpoll_ms[chip->dev_index]; + if (i == 0) + return 0; + if (i < 50 || i > 60000) + j = 0; + else + j = msecs_to_jiffies(i); + if (j == 0) + dev_warn(chip->card->dev, + "jackpoll_ms value out of range: %d\n", i); + return j; +} + +/* HD-audio bus initialization */ +int azx_bus_init(struct azx *chip, const char *model, + const struct hdac_io_ops *io_ops) +{ + struct hda_bus *bus = &chip->bus; + int err; + + err = snd_hdac_bus_init(&bus->core, chip->card->dev, &bus_core_ops, + io_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; + + /* AMD chipsets often cause the communication stalls upon certain + * sequence like the pin-detection. It seems that forcing the synced + * access works around the stall. Grrr... + */ + if (chip->driver_caps & AZX_DCAPS_SYNC_WRITE) { + dev_dbg(chip->card->dev, "Enable sync_write for stable communication\n"); + bus->core.sync_write = 1; + bus->allow_bus_reset = 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 = get_jackpoll_interval(chip); + 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; + + /* use _safe version here since snd_hda_codec_configure() deregisters + * the device upon error and deletes itself from the bus list. + */ + list_for_each_codec_safe(codec, next, &chip->bus) { + snd_hda_codec_configure(codec); + } + + if (!azx_bus(chip)->num_codecs) + return -ENODEV; + return 0; +} +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..63cc10604 --- /dev/null +++ b/sound/pci/hda/hda_controller.h @@ -0,0 +1,237 @@ +/* + * Common functionality for the alsa driver code base for HD Audio. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef __SOUND_HDA_CONTROLLER_H +#define __SOUND_HDA_CONTROLLER_H + +#include +#include +#include +#include +#include +#include "hda_codec.h" +#include + +#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 */ +#define AZX_DCAPS_SYNC_WRITE (1 << 19) /* sync each cmd write */ +#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 */ +#ifdef CONFIG_SND_HDA_I915 +#define AZX_DCAPS_I915_POWERWELL (1 << 27) /* HSW i915 powerwell support */ +#else +#define AZX_DCAPS_I915_POWERWELL 0 /* NOP */ +#endif +#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; + unsigned int wc_marked: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 *); + int (*substream_alloc_pages)(struct azx *chip, + struct snd_pcm_substream *substream, + size_t size); + int (*substream_free_pages)(struct azx *chip, + struct snd_pcm_substream *substream); + 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; + const int *jackpoll_ms; /* per-card jack poll interval */ + + /* 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; + int poll_count; + unsigned int running:1; + unsigned int fallback_to_single_cmd:1; + unsigned int single_cmd:1; + unsigned int polling_mode: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 */ + + /* 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, + const struct hdac_io_ops *io_ops); +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 + +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 diff --git a/sound/pci/hda/hda_eld.c b/sound/pci/hda/hda_eld.c new file mode 100644 index 000000000..864cc8c9a --- /dev/null +++ b/sound/pci/hda/hda_eld.c @@ -0,0 +1,780 @@ +/* + * Generic routines and proc interface for ELD(EDID Like Data) information + * + * Copyright(c) 2008 Intel Corporation. + * Copyright (c) 2013 Anssi Hannula + * + * Authors: + * Wu Fengguang + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "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 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 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 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..ff263ad19 --- /dev/null +++ b/sound/pci/hda/hda_generic.c @@ -0,0 +1,6146 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Generic widget tree parser + * + * Copyright (c) 2004 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "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); +} + +/* + * 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[4] = { + "Front", "Surround", "CLFE", "Side" +}; + +/* 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) + return channel_name[ch]; + + 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"; + + 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; +} + +/* + * 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; + if (spec->micmute_led.update) + spec->micmute_led.update(codec); +} + +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, +}; + +/** + * snd_hda_gen_add_micmute_led - helper for setting up mic mute LED hook + * @codec: the HDA codec + * @hook: the callback for updating LED + * + * Called from the codec drivers for offering the mic mute LED controls. + * When established, it sets up cap_sync_hook and triggers the callback at + * each time when the capture mixer switch changes. The callback is supposed + * to update the LED accordingly. + * + * Returns 0 if the hook is established or a negative error code. + */ +int snd_hda_gen_add_micmute_led(struct hda_codec *codec, + void (*hook)(struct hda_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->micmute_led.update = hook; + spec->cap_sync_hook = update_micmute_led; + if (!snd_hda_gen_add_kctl(spec, NULL, &micmute_led_mode_ctl)) + return -ENOMEM; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_gen_add_micmute_led); + +/* + * 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 slave */ + 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.slave_dig_outs = spec->slave_dig_outs; + if (nums >= ARRAY_SIZE(spec->slave_dig_outs) - 1) + break; + spec->slave_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, 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, 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 slaves 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 + */ + +/* slave controls for virtual master */ +static const char * const slave_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, slave_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, slave_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->slave_dig_outs = spec->multiout.slave_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); + + regcache_sync(codec->core.regmap); + + 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..2ccdd92c8 --- /dev/null +++ b/sound/pci/hda/hda_generic.h @@ -0,0 +1,364 @@ +/* + * Generic BIOS auto-parser helper functions for HD-audio + * + * Copyright (c) 2012 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SOUND_HDA_GENERIC_H +#define __SOUND_HDA_GENERIC_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 (*update)(struct hda_codec *codec); + 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 slave_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); +}; + +/* 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_micmute_led(struct hda_codec *codec, + void (*hook)(struct hda_codec *)); + +#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..cc009a4a3 --- /dev/null +++ b/sound/pci/hda/hda_hwdep.c @@ -0,0 +1,134 @@ +/* + * HWDEP Interface for HD-audio codec + * + * Copyright (c) 2007 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "hda_codec.h" +#include "hda_local.h" +#include +#include + +/* + * 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..686f0fed6 --- /dev/null +++ b/sound/pci/hda/hda_intel.c @@ -0,0 +1,2849 @@ +/* + * + * 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 + * PeiSen Hou + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_X86 +/* for snoop control */ +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "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 IN stream 0 FIFO size offset in VIA controller */ +#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90 +/* 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 + +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 + +#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 blacklist"); + +/* 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_VIA, + AZX_DRIVER_SIS, + AZX_DRIVER_ULI, + AZX_DRIVER_NVIDIA, + AZX_DRIVER_TERA, + AZX_DRIVER_CTX, + AZX_DRIVER_CTHDA, + AZX_DRIVER_CMEDIA, + 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_I915_POWERWELL | 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_I915_POWERWELL | AZX_DCAPS_SNOOP_TYPE(SCH)) + +#define AZX_DCAPS_INTEL_BAYTRAIL \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_I915_COMPONENT |\ + AZX_DCAPS_I915_POWERWELL) + +#define AZX_DCAPS_INTEL_BRASWELL \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME |\ + AZX_DCAPS_I915_COMPONENT | AZX_DCAPS_I915_POWERWELL) + +#define AZX_DCAPS_INTEL_SKYLAKE \ + (AZX_DCAPS_INTEL_PCH_BASE | AZX_DCAPS_PM_RUNTIME |\ + AZX_DCAPS_SYNC_WRITE |\ + AZX_DCAPS_SEPARATE_STREAM_TAG | AZX_DCAPS_I915_COMPONENT |\ + AZX_DCAPS_I915_POWERWELL) + +#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_SYNC_WRITE | 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_SYNC_WRITE | 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_SYNC_WRITE | AZX_DCAPS_AMD_WORKAROUND |\ + AZX_DCAPS_SNOOP_TYPE(ATI) | AZX_DCAPS_PM_RUNTIME) + +/* 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)->need_eld_notify_link) +#else +#define use_vga_switcheroo(chip) 0 +#define needs_eld_notify_link(chip) false +#endif + +#define CONTROLLER_IN_GPU(pci) (((pci)->device == 0x0a0c) || \ + ((pci)->device == 0x0c0c) || \ + ((pci)->device == 0x0d0c) || \ + ((pci)->device == 0x160c)) + +#define IS_BXT(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x5a98) +#define IS_CFL(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa348) +#define IS_CNL(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9dc8) + +static char *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_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_GENERIC] = "HD-Audio Generic", +}; + +#ifdef CONFIG_X86 +static void __mark_pages_wc(struct azx *chip, struct snd_dma_buffer *dmab, bool on) +{ + int pages; + + if (azx_snoop(chip)) + return; + if (!dmab || !dmab->area || !dmab->bytes) + return; + +#ifdef CONFIG_SND_DMA_SGBUF + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_SG) { + struct snd_sg_buf *sgbuf = dmab->private_data; + if (!chip->uc_buffer) + return; /* deal with only CORB/RIRB buffers */ + if (on) + set_pages_array_wc(sgbuf->page_table, sgbuf->pages); + else + set_pages_array_wb(sgbuf->page_table, sgbuf->pages); + return; + } +#endif + + pages = (dmab->bytes + PAGE_SIZE - 1) >> PAGE_SHIFT; + if (on) + set_memory_wc((unsigned long)dmab->area, pages); + else + set_memory_wb((unsigned long)dmab->area, pages); +} + +static inline void mark_pages_wc(struct azx *chip, struct snd_dma_buffer *buf, + bool on) +{ + __mark_pages_wc(chip, buf, on); +} +static inline void mark_runtime_wc(struct azx *chip, struct azx_dev *azx_dev, + struct snd_pcm_substream *substream, bool on) +{ + if (azx_dev->wc_marked != on) { + __mark_pages_wc(chip, snd_pcm_get_dma_buf(substream), on); + azx_dev->wc_marked = on; + } +} +#else +/* NOP for other archs */ +static inline void mark_pages_wc(struct azx *chip, struct snd_dma_buffer *buf, + bool on) +{ +} +static inline void mark_runtime_wc(struct azx *chip, struct azx_dev *azx_dev, + struct snd_pcm_substream *substream, bool on) +{ +} +#endif + +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 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; + + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + 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); + } + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + 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; +} + +/* Enable/disable i915 display power for the link */ +static int azx_intel_link_power(struct azx *chip, bool enable) +{ + struct hdac_bus *bus = azx_bus(chip); + + return snd_hdac_display_power(bus, 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; + 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; + + /* azx_dev->fifo_size can't get FIFO size of in stream. + * Get from base address + offset. + */ + fifo_size = readw(azx_bus(chip)->remap_addr + + VIA_IN_STREAM0_FIFO_SIZE_OFFSET); + + 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 unsigned int azx_skl_get_dpib_pos(struct azx *chip, + struct azx_dev *azx_dev) +{ + return _snd_hdac_chip_readl(azx_bus(chip), + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + azx_dev->core.index)); +} + +/* get the current DMA position with correction on SKL+ chips */ +static unsigned int azx_get_pos_skl(struct azx *chip, struct azx_dev *azx_dev) +{ + /* DPIB register gives a more accurate position for playback */ + if (azx_dev->core.substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return azx_skl_get_dpib_pos(chip, azx_dev); + + /* read of DPIB fetches the actual posbuf */ + azx_skl_get_dpib_pos(chip, azx_dev); + return azx_get_pos_posbuf(chip, azx_dev); +} + +#ifdef CONFIG_PM +static DEFINE_MUTEX(card_list_lock); +static LIST_HEAD(card_list); + +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; +} +#else +#define azx_add_card_list(chip) /* NOP */ +#define azx_del_card_list(chip) /* NOP */ +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +/* + * power management + */ +static int azx_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_intel *hda; + struct hdac_bus *bus; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_intel, chip); + if (chip->disabled || hda->init_failed || !chip->running) + return 0; + + bus = azx_bus(chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + azx_clear_irq_pending(chip); + azx_stop_chip(chip); + azx_enter_link_reset(chip); + if (bus->irq >= 0) { + free_irq(bus->irq, chip); + bus->irq = -1; + } + + if (chip->msi) + pci_disable_msi(chip->pci); + if ((chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + && hda->need_i915_power) + snd_hdac_display_power(bus, false); + + trace_azx_suspend(chip); + return 0; +} + +static int azx_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip; + struct hda_intel *hda; + struct hdac_bus *bus; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_intel, chip); + bus = azx_bus(chip); + if (chip->disabled || hda->init_failed || !chip->running) + return 0; + + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { + snd_hdac_display_power(bus, true); + if (hda->need_i915_power) + snd_hdac_i915_set_bclk(bus); + } + + if (chip->msi) + if (pci_enable_msi(pci) < 0) + chip->msi = 0; + if (azx_acquire_irq(chip, 1) < 0) + return -EIO; + azx_init_pci(chip); + + hda_intel_init_chip(chip, true); + + /* power down again for link-controlled chips */ + if ((chip->driver_caps & AZX_DCAPS_I915_POWERWELL) && + !hda->need_i915_power) + snd_hdac_display_power(bus, false); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + 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 (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 (chip->driver_type == AZX_DRIVER_SKL) + pci_set_power_state(pci, PCI_D0); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int azx_runtime_suspend(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 (!azx_has_pm_runtime(chip)) + return 0; + + /* 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); + azx_clear_irq_pending(chip); + if ((chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + && hda->need_i915_power) + snd_hdac_display_power(azx_bus(chip), false); + + 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; + struct hda_intel *hda; + struct hdac_bus *bus; + struct hda_codec *codec; + int status; + + if (!card) + return 0; + + chip = card->private_data; + hda = container_of(chip, struct hda_intel, chip); + bus = azx_bus(chip); + if (chip->disabled || hda->init_failed) + return 0; + + if (!azx_has_pm_runtime(chip)) + return 0; + + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { + snd_hdac_display_power(bus, 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); + + if (status) { + list_for_each_codec(codec, &chip->bus) + if (status & (1 << codec->addr)) + schedule_delayed_work(&codec->jackpoll_work, + codec->jackpoll_interval); + } + + /* disable controller Wake Up event*/ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & + ~STATESTS_INT_MASK); + + /* power down again for link-controlled chips */ + if ((chip->driver_caps & AZX_DCAPS_I915_POWERWELL) && + !hda->need_i915_power) + snd_hdac_display_power(bus, false); + + 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(hda)) + 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 + .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_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"); + hda->init_failed = true; + } + } + } 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 && !hda->need_eld_notify_link) { + 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; + struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + + if (client_id == VGA_SWITCHEROO_DIS) + hda->need_eld_notify_link = 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); + if (p) { + dev_info(chip->card->dev, + "Handle vga_switcheroo audio client\n"); + hda->use_vga_switcheroo = 1; + hda->need_eld_notify_link = 1; /* cleared in gpu_bound op */ + 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 int 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 (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 + + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { + if (hda->need_i915_power) + snd_hdac_display_power(bus, false); + } + if (chip->driver_caps & AZX_DCAPS_I915_COMPONENT) + snd_hdac_i915_exit(bus); + kfree(hda); + + return 0; +} + +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) +{ + return azx_free(device->device_data); +} + +#ifdef SUPPORT_VGA_SWITCHEROO +/* + * 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: + 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 */ + +/* + * white/black-listing for position_fix + */ +static 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 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_skl, + [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; +} + +/* + * black-lists for probe_mask + */ +static 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); + } +} + +/* + * white/black-list for enable_msi + */ +static struct snd_pci_quirk msi_black_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_black_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); + 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) { + case AZX_DRIVER_ICH: + case AZX_DRIVER_PCH: + return 1; + default: + return 32; + } +} + +/* + * constructor + */ +static const struct hdac_io_ops pci_hda_io_ops; +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 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 = kzalloc(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; + chip->jackpoll_ms = jackpoll_ms; + 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]; + + /* Workaround for a communication error on CFL (bko#199007) and CNL */ + if (IS_CFL(pci) || IS_CNL(pci)) + chip->polling_mode = 1; + + err = azx_bus_init(chip, model[dev], &pci_hda_io_ops); + if (err < 0) { + kfree(hda); + pci_disable_device(pci); + return err; + } + + if (chip->driver_type == AZX_DRIVER_NVIDIA) { + dev_dbg(chip->card->dev, "Enable delay in RIRB handling\n"); + chip->bus.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_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 + + 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); + synchronize_irq(bus->irq); + + 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_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); + + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + 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 + +/* + * HDA controller ops. + */ + +/* PCI register access. */ +static void pci_azx_writel(u32 value, u32 __iomem *addr) +{ + writel(value, addr); +} + +static u32 pci_azx_readl(u32 __iomem *addr) +{ + return readl(addr); +} + +static void pci_azx_writew(u16 value, u16 __iomem *addr) +{ + writew(value, addr); +} + +static u16 pci_azx_readw(u16 __iomem *addr) +{ + return readw(addr); +} + +static void pci_azx_writeb(u8 value, u8 __iomem *addr) +{ + writeb(value, addr); +} + +static u8 pci_azx_readb(u8 __iomem *addr) +{ + return readb(addr); +} + +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; + pci_disable_msi(chip->pci); + chip->msi = 0; + err = azx_acquire_irq(chip, 1); + if (err < 0) + return err; + + return 0; +} + +/* DMA page allocation helpers. */ +static int dma_alloc_pages(struct hdac_bus *bus, + int type, + size_t size, + struct snd_dma_buffer *buf) +{ + struct azx *chip = bus_to_azx(bus); + int err; + + err = snd_dma_alloc_pages(type, + bus->dev, + size, buf); + if (err < 0) + return err; + mark_pages_wc(chip, buf, true); + return 0; +} + +static void dma_free_pages(struct hdac_bus *bus, struct snd_dma_buffer *buf) +{ + struct azx *chip = bus_to_azx(bus); + + mark_pages_wc(chip, buf, false); + snd_dma_free_pages(buf); +} + +static int substream_alloc_pages(struct azx *chip, + struct snd_pcm_substream *substream, + size_t size) +{ + struct azx_dev *azx_dev = get_azx_dev(substream); + int ret; + + mark_runtime_wc(chip, azx_dev, substream, false); + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) + return ret; + mark_runtime_wc(chip, azx_dev, substream, true); + return 0; +} + +static int substream_free_pages(struct azx *chip, + struct snd_pcm_substream *substream) +{ + struct azx_dev *azx_dev = get_azx_dev(substream); + mark_runtime_wc(chip, azx_dev, substream, false); + return snd_pcm_lib_free_pages(substream); +} + +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 +} + +static const struct hdac_io_ops pci_hda_io_ops = { + .reg_writel = pci_azx_writel, + .reg_readl = pci_azx_readl, + .reg_writew = pci_azx_writew, + .reg_readw = pci_azx_readw, + .reg_writeb = pci_azx_writeb, + .reg_readb = pci_azx_readb, + .dma_alloc_pages = dma_alloc_pages, + .dma_free_pages = dma_free_pages, +}; + +/* Blacklist 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_blacklist[] = { + { 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, + .substream_alloc_pages = substream_alloc_pages, + .substream_free_pages = substream_free_pages, + .pcm_mmap_prepare = pcm_mmap_prepare, + .position_check = azx_position_check, + .link_power = azx_intel_link_power, +}; + +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_blacklist, pci)) { + dev_info(&pci->dev, "Skipping the blacklisted device\n"); + 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) { + 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_work(&hda->probe_work); + + 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 struct snd_pci_quirk power_save_blacklist[] = { + /* 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), + /* 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), + {} +}; +#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_blacklist); + if (q && val) { + dev_info(chip->card->dev, "device %04x:%04x is on the power_save blacklist, 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 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; + + 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 | AZX_DCAPS_I915_POWERWELL); + } + } + } + + /* 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. + */ + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { + /* HSW/BDW controllers need this power */ + if (CONTROLLER_IN_GPU(pci)) + hda->need_i915_power = 1; + + err = snd_hdac_display_power(bus, true); + if (err < 0) { + dev_err(chip->card->dev, + "Cannot turn on display power on i915\n"); + goto i915_power_fail; + } + } + + 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 + if (bus->codec_mask && !(probe_only[dev] & 1)) { + err = azx_codec_configure(chip); + if (err < 0) + 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_put_autosuspend(&pci->dev); + } + +out_free: + if ((chip->driver_caps & AZX_DCAPS_I915_POWERWELL) + && !hda->need_i915_power) + snd_hdac_display_power(bus, false); + +i915_power_fail: + if (err < 0) + hda->init_failed = 1; + complete_all(&hda->probe_wait); + to_hda_bus(bus)->bus_probing = 0; + return err; +} + +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_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_stop_chip(chip); +} + +/* 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}, + /* Icelake */ + { PCI_DEVICE(0x8086, 0x34c8), + .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 }, + /* Poulsbo */ + { PCI_DEVICE(0x8086, 0x811b), + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_BASE }, + /* 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 }, + { PCI_DEVICE(0x1002, 0xaae8), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0xaae0), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + { PCI_DEVICE(0x1002, 0xaaf0), + .driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS }, + /* 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 }, + { 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..f59719e06 --- /dev/null +++ b/sound/pci/hda/hda_intel.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#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 work_struct 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 need_eld_notify_link:1; + unsigned int vga_switcheroo_registered:1; + unsigned int init_failed:1; /* delayed init failed */ + + bool need_i915_power:1; /* the hda controller needs i915 power */ +}; + +#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 + +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 diff --git a/sound/pci/hda/hda_jack.c b/sound/pci/hda/hda_jack.c new file mode 100644 index 000000000..a33234e04 --- /dev/null +++ b/sound/pci/hda/hda_jack.c @@ -0,0 +1,575 @@ +/* + * Jack-detection handling for HD-audio + * + * Copyright (c) 2011 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include "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) +{ + 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, 0); + if (codec->inv_jack_detect) + val ^= AC_PINSENSE_PRESENCE; + return val; +} + +/** + * snd_hda_jack_tbl_get - query the jack-table entry for the given NID + * @codec: the HDA codec + * @nid: pin NID to refer to + */ +struct hda_jack_tbl * +snd_hda_jack_tbl_get(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; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get); + +/** + * 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 + */ +struct hda_jack_tbl * +snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag) +{ + 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) + return jack; + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag); + +/** + * snd_hda_jack_tbl_new - create a jack-table entry for the given NID + * @codec: the HDA codec + * @nid: pin NID to assign + */ +static struct hda_jack_tbl * +snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid); + if (jack) + return jack; + jack = snd_array_new(&codec->jacktbl); + if (!jack) + return NULL; + jack->nid = nid; + jack->jack_dirty = 1; + 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); + + /* A gating jack indicates the jack is invalid if gating is unplugged */ + if (jack->gating_jack && !snd_hda_jack_detect(codec, jack->gating_jack)) + 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(codec, jack->gated_jack); + 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_pin_sense - execute pin sense measurement + * @codec: the CODEC to sense + * @nid: the pin NID to sense + * + * Execute necessary pin sense measurement and return its Presence Detect, + * Impedance, ELD Valid etc. status bits. + */ +u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid); + if (jack) { + jack_detect_update(codec, jack); + return jack->pin_sense; + } + return read_pin_sense(codec, nid); +} +EXPORT_SYMBOL_GPL(snd_hda_pin_sense); + +/** + * 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. + */ +int snd_hda_jack_detect_state(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_jack_tbl *jack = snd_hda_jack_tbl_get(codec, nid); + if (jack && jack->phantom_jack) + return HDA_JACK_PHANTOM; + else if (snd_hda_pin_sense(codec, nid) & AC_PINSENSE_PRESENCE) + return HDA_JACK_PRESENT; + else + return HDA_JACK_NOT_PRESENT; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_detect_state); + +/** + * 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(). + */ +struct hda_jack_callback * +snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid, + 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); + if (!jack) + return ERR_PTR(-ENOMEM); + if (func) { + callback = kzalloc(sizeof(*callback), GFP_KERNEL); + if (!callback) + return ERR_PTR(-ENOMEM); + callback->func = func; + callback->nid = jack->nid; + 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); + +/** + * snd_hda_jack_detect_enable - Enable the jack detection on the given pin + * @codec: the HDA codec + * @nid: pin NID to enable jack detection + * + * 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) +{ + return PTR_ERR_OR_ZERO(snd_hda_jack_detect_enable_callback(codec, nid, 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); + struct hda_jack_tbl *gating = snd_hda_jack_tbl_new(codec, gating_nid); + + 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 = get_jack_plug_state(jack->pin_sense); + snd_jack_report(jack->jack, + state ? jack->type : 0); + } +} +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 - 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 + * + * 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(struct hda_codec *codec, hda_nid_t nid, + const char *name, bool phantom_jack) +{ + struct hda_jack_tbl *jack; + int err, state, type; + + jack = snd_hda_jack_tbl_new(codec, nid); + if (!jack) + return 0; + if (jack->jack) + return 0; /* already created */ + + type = get_input_jack_type(codec, nid); + err = snd_jack_new(codec->card, name, type, + &jack->jack, true, phantom_jack); + if (err < 0) + return err; + + jack->phantom_jack = !!phantom_jack; + jack->type = type; + jack->jack->private_data = jack; + jack->jack->private_free = hda_free_jack_priv; + state = snd_hda_jack_detect(codec, nid); + snd_jack_report(jack->jack, state ? jack->type : 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_jack_add_kctl); + +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; + + 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); + if (err < 0) + return err; + + if (!phantom_jack) + return snd_hda_jack_detect_enable(codec, nid); + 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, + struct hda_jack_tbl *jack) +{ + struct hda_jack_callback *cb; + + for (cb = jack->callback; cb; cb = cb->next) + cb->func(codec, cb); + if (jack->gated_jack) { + struct hda_jack_tbl *gated = + snd_hda_jack_tbl_get(codec, jack->gated_jack); + if (gated) { + for (cb = gated->callback; cb; cb = cb->next) + 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_SHIFT) & 0x7f; + + event = snd_hda_jack_tbl_get_from_tag(codec, tag); + if (!event) + return; + event->jack_dirty = 1; + + call_jack_callback(codec, 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, 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..e9814c016 --- /dev/null +++ b/sound/pci/hda/hda_jack.h @@ -0,0 +1,95 @@ +/* + * Jack-detection handling for HD-audio + * + * Copyright (c) 2011 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __SOUND_HDA_JACK_H +#define __SOUND_HDA_JACK_H + +#include + +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; + hda_jack_callback_fn func; + unsigned int private_data; /* arbitrary data */ + struct hda_jack_callback *next; +}; + +struct hda_jack_tbl { + hda_nid_t nid; + 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; + struct snd_jack *jack; +}; + +struct hda_jack_tbl * +snd_hda_jack_tbl_get(struct hda_codec *codec, hda_nid_t nid); +struct hda_jack_tbl * +snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag); + +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); +struct hda_jack_callback * +snd_hda_jack_detect_enable_callback(struct hda_codec *codec, hda_nid_t nid, + hda_jack_callback_fn 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_pin_sense(struct hda_codec *codec, hda_nid_t nid); + +/* 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(struct hda_codec *codec, hda_nid_t nid); + +/** + * 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_state(codec, nid) != HDA_JACK_NOT_PRESENT; +} + +bool is_jack_detectable(struct hda_codec *codec, hda_nid_t nid); + +int snd_hda_jack_add_kctl(struct hda_codec *codec, hda_nid_t nid, + const char *name, bool phantom_jack); +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..9bd935216 --- /dev/null +++ b/sound/pci/hda/hda_local.h @@ -0,0 +1,741 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Local helper functions + * + * Copyright (c) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#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 dir, 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 *slaves, + const char *suffix, bool init_slave_vol, + struct snd_kcontrol **ctl_ret); +#define snd_hda_add_vmaster(codec, name, tlv, slaves, suffix) \ + __snd_hda_add_vmaster(codec, name, tlv, slaves, 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); + +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_index_ret); + +/* + * 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 *slave_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_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); + +/* 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_entries, 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..c6b778b25 --- /dev/null +++ b/sound/pci/hda/hda_proc.c @@ -0,0 +1,933 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * Generic proc interface + * + * Copyright (c) 2004 Takashi Iwai + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "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 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<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]; + struct snd_info_entry *entry; + int err; + + snprintf(name, sizeof(name), "codec#%d", codec->core.addr); + err = snd_card_proc_new(codec->card, name, &entry); + if (err < 0) + return err; + + snd_info_set_text_ops(entry, codec, print_codec_info); + return 0; +} + diff --git a/sound/pci/hda/hda_sysfs.c b/sound/pci/hda/hda_sysfs.c new file mode 100644 index 000000000..25a4c2d58 --- /dev/null +++ b/sound/pci/hda/hda_sysfs.c @@ -0,0 +1,798 @@ +/* + * sysfs interface for HD-audio codec + * + * Copyright (c) 2014 Takashi Iwai + * + * split from hda_hwdep.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hda_codec.h" +#include "hda_local.h" +#include +#include + +/* 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, ¶m) != 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 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..b567c4bda --- /dev/null +++ b/sound/pci/hda/hda_tegra.c @@ -0,0 +1,586 @@ +/* + * + * Implementation of primary ALSA driver code base for NVIDIA Tegra HDA. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "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) + +/* max number of SDs */ +#define NUM_CAPTURE_SD 1 +#define NUM_PLAYBACK_SD 1 + +struct hda_tegra { + struct azx chip; + struct device *dev; + struct clk *hda_clk; + struct clk *hda2codec_2x_clk; + struct clk *hda2hdmi_clk; + 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 + +/* + * DMA page allocation ops. + */ +static int dma_alloc_pages(struct hdac_bus *bus, int type, size_t size, + struct snd_dma_buffer *buf) +{ + return snd_dma_alloc_pages(type, bus->dev, size, buf); +} + +static void dma_free_pages(struct hdac_bus *bus, struct snd_dma_buffer *buf) +{ + snd_dma_free_pages(buf); +} + +static int substream_alloc_pages(struct azx *chip, + struct snd_pcm_substream *substream, + size_t size) +{ + return snd_pcm_lib_malloc_pages(substream, size); +} + +static int substream_free_pages(struct azx *chip, + struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* + * Register access ops. Tegra HDA register access is DWORD only. + */ +static void hda_tegra_writel(u32 value, u32 __iomem *addr) +{ + writel(value, addr); +} + +static u32 hda_tegra_readl(u32 __iomem *addr) +{ + return readl(addr); +} + +static void hda_tegra_writew(u16 value, u16 __iomem *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void __iomem *dword_addr = (void __iomem *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + v &= ~(0xffff << shift); + v |= value << shift; + writel(v, dword_addr); +} + +static u16 hda_tegra_readw(u16 __iomem *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void __iomem *dword_addr = (void __iomem *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + return (v >> shift) & 0xffff; +} + +static void hda_tegra_writeb(u8 value, u8 __iomem *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void __iomem *dword_addr = (void __iomem *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + v &= ~(0xff << shift); + v |= value << shift; + writel(v, dword_addr); +} + +static u8 hda_tegra_readb(u8 __iomem *addr) +{ + unsigned int shift = ((unsigned long)(addr) & 0x3) << 3; + void __iomem *dword_addr = (void __iomem *)((unsigned long)(addr) & ~0x3); + u32 v; + + v = readl(dword_addr); + return (v >> shift) & 0xff; +} + +static const struct hdac_io_ops hda_tegra_io_ops = { + .reg_writel = hda_tegra_writel, + .reg_readl = hda_tegra_readl, + .reg_writew = hda_tegra_writew, + .reg_readw = hda_tegra_readw, + .reg_writeb = hda_tegra_writeb, + .reg_readb = hda_tegra_readb, + .dma_alloc_pages = dma_alloc_pages, + .dma_free_pages = dma_free_pages, +}; + +static const struct hda_controller_ops hda_tegra_ops = { + .substream_alloc_pages = substream_alloc_pages, + .substream_free_pages = substream_free_pages, +}; + +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); +} + +static int hda_tegra_enable_clocks(struct hda_tegra *data) +{ + int rc; + + rc = clk_prepare_enable(data->hda_clk); + if (rc) + return rc; + rc = clk_prepare_enable(data->hda2codec_2x_clk); + if (rc) + goto disable_hda; + rc = clk_prepare_enable(data->hda2hdmi_clk); + if (rc) + goto disable_codec_2x; + + return 0; + +disable_codec_2x: + clk_disable_unprepare(data->hda2codec_2x_clk); +disable_hda: + clk_disable_unprepare(data->hda_clk); + return rc; +} + +#ifdef CONFIG_PM_SLEEP +static void hda_tegra_disable_clocks(struct hda_tegra *data) +{ + clk_disable_unprepare(data->hda2hdmi_clk); + clk_disable_unprepare(data->hda2codec_2x_clk); + clk_disable_unprepare(data->hda_clk); +} + +/* + * power management + */ +static int hda_tegra_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); + struct hdac_bus *bus = azx_bus(chip); + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + azx_stop_chip(chip); + synchronize_irq(bus->irq); + azx_enter_link_reset(chip); + hda_tegra_disable_clocks(hda); + + return 0; +} + +static int hda_tegra_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); + + hda_tegra_enable_clocks(hda); + + hda_tegra_init(hda); + + azx_init_chip(chip, 1); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops hda_tegra_pm = { + SET_SYSTEM_SLEEP_PM_OPS(hda_tegra_suspend, hda_tegra_resume) +}; + +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; + int err; + + hda->hda_clk = devm_clk_get(dev, "hda"); + if (IS_ERR(hda->hda_clk)) { + dev_err(dev, "failed to get hda clock\n"); + return PTR_ERR(hda->hda_clk); + } + hda->hda2codec_2x_clk = devm_clk_get(dev, "hda2codec_2x"); + if (IS_ERR(hda->hda2codec_2x_clk)) { + dev_err(dev, "failed to get hda2codec_2x clock\n"); + return PTR_ERR(hda->hda2codec_2x_clk); + } + hda->hda2hdmi_clk = devm_clk_get(dev, "hda2hdmi"); + if (IS_ERR(hda->hda2hdmi_clk)) { + dev_err(dev, "failed to get hda2hdmi clock\n"); + return PTR_ERR(hda->hda2hdmi_clk); + } + + 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; + + err = hda_tegra_enable_clocks(hda); + if (err) { + dev_err(dev, "failed to get enable clocks\n"); + return err; + } + + hda_tegra_init(hda); + + return 0; +} + +static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) +{ + 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); + + 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; + + synchronize_irq(bus->irq); + + gcap = azx_readw(chip, GCAP); + dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* 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); + + /* codec detection */ + if (!bus->codec_mask) { + dev_err(card->dev, "no codecs found!\n"); + return -ENODEV; + } + + strcpy(card->driver, "tegra-hda"); + strcpy(card->shortname, "tegra-hda"); + 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 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, &hda_tegra_io_ops); + if (err < 0) + return err; + + chip->bus.needs_damn_long_delay = 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" }, + {}, +}; +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; + 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; + } + + err = hda_tegra_create(card, driver_flags, hda); + if (err < 0) + goto out_free; + card->private_data = chip; + + dev_set_drvdata(&pdev->dev, card); + 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; + + err = hda_tegra_first_init(chip, pdev); + if (err < 0) + goto out_free; + + /* create codec instances */ + err = azx_probe_codecs(chip, 0); + 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: + return; /* no error return from async probe */ +} + +static int hda_tegra_remove(struct platform_device *pdev) +{ + return snd_card_free(dev_get_drvdata(&pdev->dev)); +} + +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/patch_analog.c b/sound/pci/hda/patch_analog.c new file mode 100644 index 000000000..677dcc0ac --- /dev/null +++ b/sound/pci/hda/patch_analog.c @@ -0,0 +1,1196 @@ +/* + * HD audio interface patch for AD1882, AD1884, AD1981HD, AD1983, AD1984, + * AD1986A, AD1988 + * + * Copyright (c) 2005-2007 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include +#include "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() */ +}; + + +#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 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" }; + hda_nid_t dig_out = spec->gen.multiout.dig_out_nid; + int num_conns = snd_hda_get_num_conns(codec, dig_out); + + 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 = snd_hda_get_num_conns(codec, dig_out); + + 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; + 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) +{ + struct ad198x_spec *spec; + static hda_nid_t conn_0c[] = { 0x08 }; + static hda_nid_t conn_0d[] = { 0x09 }; + 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); + static const char * const texts[] = { + "PCM", "ADC1", "ADC2", "ADC3", + }; + int num_conns = snd_hda_get_num_conns(codec, 0x0b) + 1; + 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 = snd_hda_get_num_conns(codec, 0x0b) + 1; + + 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 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; + + 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..c2d9ee9cf --- /dev/null +++ b/sound/pci/hda/patch_ca0110.c @@ -0,0 +1,101 @@ +/* + * HD audio interface patch for Creative X-Fi CA0110-IBG chip + * + * Copyright (c) 2008 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "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->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..004a7772b --- /dev/null +++ b/sound/pci/hda/patch_ca0132.c @@ -0,0 +1,7716 @@ +/* + * HD audio interface patch for Creative CA0132 chip + * + * Copyright (c) 2011, Creative Technology Ltd. + * + * Based on patch_ca0110.c + * Copyright (c) 2008 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "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 +#endif + +#define FLOAT_ZERO 0x00000000 +#define FLOAT_ONE 0x3f800000 +#define FLOAT_TWO 0x40000000 +#define FLOAT_THREE 0x40400000 +#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 SBZ_EFX_FILE "ctefx-sbz.bin" +#define R3DI_EFX_FILE "ctefx-r3di.bin" + +#ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP +MODULE_FIRMWARE(EFX_FILE); +MODULE_FIRMWARE(SBZ_EFX_FILE); +MODULE_FIRMWARE(R3DI_EFX_FILE); +#endif + +static const char *const dirstr[2] = { "Playback", "Capture" }; + +#define NUM_OF_OUTPUTS 3 +enum { + SPEAKER_OUT, + HEADPHONE_OUT, + SURROUND_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 +#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 command sequences for ca0132_alt_select_out */ +#define ALT_OUT_SET_MAX_COMMANDS 9 /* Max number of commands in sequence */ +struct ca0132_alt_out_set { + char *name; /*preset name*/ + unsigned char commands; + unsigned int mids[ALT_OUT_SET_MAX_COMMANDS]; + unsigned int reqs[ALT_OUT_SET_MAX_COMMANDS]; + unsigned int vals[ALT_OUT_SET_MAX_COMMANDS]; +}; + +static const struct ca0132_alt_out_set alt_out_presets[] = { + { .name = "Line Out", + .commands = 7, + .mids = { 0x96, 0x96, 0x96, 0x8F, + 0x96, 0x96, 0x96 }, + .reqs = { 0x19, 0x17, 0x18, 0x01, + 0x1F, 0x15, 0x3A }, + .vals = { 0x3F000000, 0x42A00000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000 } + }, + { .name = "Headphone", + .commands = 7, + .mids = { 0x96, 0x96, 0x96, 0x8F, + 0x96, 0x96, 0x96 }, + .reqs = { 0x19, 0x17, 0x18, 0x01, + 0x1F, 0x15, 0x3A }, + .vals = { 0x3F000000, 0x42A00000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000 } + }, + { .name = "Surround", + .commands = 8, + .mids = { 0x96, 0x8F, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96 }, + .reqs = { 0x18, 0x01, 0x1F, 0x15, + 0x3A, 0x1A, 0x1B, 0x1C }, + .vals = { 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 } + } +}; + +/* + * 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} + } +}; + +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_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_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, + + /* 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 alt_firmware_present; + 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 mic_boost_enum_val; + unsigned char smart_volume_setting; + 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; + + + 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_R3DI, + QUIRK_R3D, +}; + +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 */ + {} +}; + +/* 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 */ + {} +}; + +/* 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 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(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(0x1102, 0x0013, "Recon3D", QUIRK_R3D), + {} +}; + +/* + * 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); +} + +/* + * 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 + * @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, ®); + + 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, ®); + + 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[0]; +}; + +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 sizeof(*p) + p->count*sizeof(u32); +} + +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 (!spec->use_alt_functions) { + /*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; +} + +/* + * Setup GPIO for the other variants of Core3D. + */ + +/* + * 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. + */ +static void ca0132_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); +} + +/* + * 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 (spec->quirk) { + case QUIRK_SBZ: + 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; + } + +} + +/* Sets the GPIO for audio output. */ +static void ca0132_gpio_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (spec->quirk) { + 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; + } +} + +/* + * 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_out_set(struct hda_codec *codec, + enum r3di_out_select cur_out) +{ + 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_out) { + case R3DI_HEADPHONE_OUT: + cur_gpio &= ~(1 << R3DI_OUT_SELECT_BIT); + break; + case R3DI_LINE_OUT: + cur_gpio |= (1 << R3DI_OUT_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 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 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 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, + unsigned int *lookup, int idx) +{ + int i = 0; + + for (i = 0; i < TUNING_CTLS_COUNT; i++) + if (nid == ca0132_tuning_ctls[i].nid) + break; + + 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; +} + +/* + * 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. The only + * way to select surround is to disable auto detect, and set Surround with the + * enumerated control. + */ +static int ca0132_alt_select_out(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int pin_ctl; + int jack_present; + int auto_jack; + unsigned int i; + unsigned int tmp; + 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; + + /* Begin DSP output switch */ + tmp = FLOAT_ONE; + err = dspio_set_uint_param(codec, 0x96, 0x3A, tmp); + if (err < 0) + goto exit; + + switch (spec->cur_out_type) { + case SPEAKER_OUT: + codec_dbg(codec, "%s speaker\n", __func__); + /*speaker out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + ca0132_mmio_gpio_set(codec, 7, false); + ca0132_mmio_gpio_set(codec, 4, true); + ca0132_mmio_gpio_set(codec, 1, true); + chipio_set_control_param(codec, 0x0D, 0x18); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x24); + r3di_gpio_out_set(codec, R3DI_LINE_OUT); + break; + case QUIRK_R3D: + chipio_set_control_param(codec, 0x0D, 0x24); + ca0132_mmio_gpio_set(codec, 1, true); + break; + } + + /* 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 line-out 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); + /* Enable EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x01); + + /* If PlayEnhancement is enabled, set different source */ + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_EIGHT); + break; + case HEADPHONE_OUT: + codec_dbg(codec, "%s hp\n", __func__); + /* Headphone out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + ca0132_mmio_gpio_set(codec, 7, true); + ca0132_mmio_gpio_set(codec, 4, true); + ca0132_mmio_gpio_set(codec, 1, false); + chipio_set_control_param(codec, 0x0D, 0x12); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x21); + r3di_gpio_out_set(codec, R3DI_HEADPHONE_OUT); + break; + case QUIRK_R3D: + chipio_set_control_param(codec, 0x0D, 0x21); + ca0132_mmio_gpio_set(codec, 0x1, false); + break; + } + + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + /* 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, 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]; + + pin_ctl = snd_hda_codec_read(codec, headphone_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, headphone_nid, + pin_ctl | PIN_HP); + + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ZERO); + break; + case SURROUND_OUT: + codec_dbg(codec, "%s surround\n", __func__); + /* Surround out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + ca0132_mmio_gpio_set(codec, 7, false); + ca0132_mmio_gpio_set(codec, 4, true); + ca0132_mmio_gpio_set(codec, 1, true); + chipio_set_control_param(codec, 0x0D, 0x18); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x24); + r3di_gpio_out_set(codec, R3DI_LINE_OUT); + break; + case QUIRK_R3D: + ca0132_mmio_gpio_set(codec, 1, true); + chipio_set_control_param(codec, 0x0D, 0x24); + break; + } + /* enable line out 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); + /* Disable headphone out */ + 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 EAPD on line out */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x01); + /* enable center/lfe out node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[2], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[2], + pin_ctl | PIN_OUT); + /* Now set rear surround node as out. */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[3], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[3], + pin_ctl | PIN_OUT); + + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_EIGHT); + break; + } + + /* run through the output dsp commands for line-out */ + for (i = 0; i < alt_out_presets[spec->cur_out_type].commands; i++) { + err = dspio_set_uint_param(codec, + alt_out_presets[spec->cur_out_type].mids[i], + alt_out_presets[spec->cur_out_type].reqs[i], + alt_out_presets[spec->cur_out_type].vals[i]); + + 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 (spec->use_alt_functions) + 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 int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, 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 (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + + if (spec->in_enum_val == REAR_LINE_IN) + tmp = FLOAT_ZERO; + else { + if (spec->quirk == 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 (spec->quirk == 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 (spec->quirk) { + case QUIRK_SBZ: + case QUIRK_R3D: + ca0132_mmio_gpio_set(codec, 0, false); + tmp = FLOAT_THREE; + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + tmp = FLOAT_ONE; + 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 (spec->quirk == 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); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x0000000C); + } + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); + break; + case REAR_LINE_IN: + ca0132_mic_boost_set(codec, 0); + switch (spec->quirk) { + case QUIRK_SBZ: + case QUIRK_R3D: + ca0132_mmio_gpio_set(codec, 0, false); + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x00000000); + chipio_write(codec, 0x18B09C, 0x00000000); + } + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + break; + case FRONT_MIC: + switch (spec->quirk) { + case QUIRK_SBZ: + case QUIRK_R3D: + ca0132_mmio_gpio_set(codec, 0, true); + ca0132_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; + 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 (spec->quirk == 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); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x000000CC); + } + 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; + 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; + } + + /* 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) && (spec->use_pci_mmio) + && (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) && (spec->use_pci_mmio) + && (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 (spec->use_alt_functions && + 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 (spec->use_alt_functions) + 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 (spec->use_alt_functions) + 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 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 (spec->use_alt_functions) + 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 (spec->use_alt_functions) + 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. */ +/* + * 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; + + *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; + int idx; + + /* any change? */ + if (spec->xbass_xover_freq == *valp) + return 0; + + spec->xbass_xover_freq = *valp; + + idx = *valp; + 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; +} + + +/* + * 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; + + 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, + alt_out_presets[uinfo->value.enumerated.item].name); + 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, alt_out_presets[sel].name); + + 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; +} + +/* + * 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; + } + + 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 (spec->use_alt_functions) { + 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; + } + +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 = 1; + + 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 ((spec->use_alt_controls) && (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)); +} + +/* + * 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)); + +} + +/* + * Need to create slave controls for the alternate codecs that have surround + * capabilities. + */ +static const char * const ca0132_alt_slave_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 slaves for desktop ca0132 devices */ + if (spec->use_alt_functions) { + 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_slave_pfxs, + "Playback Volume"); + err = __snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, ca0132_alt_slave_pfxs, + "Playback Switch", + true, &spec->vmaster_mute.sw_kctl); + + } + + /* 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 (spec->use_pci_mmio) { + 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 (spec->use_alt_controls) { + ca0132_alt_add_svm_enum(codec); + add_ca0132_alt_eq_presets(codec); + 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; + } + add_voicefx(codec); + + /* + * 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 (spec->use_alt_functions) { + ca0132_alt_add_output_enum(codec); + ca0132_alt_add_input_enum(codec); + ca0132_alt_add_mic_boost_enum(codec); + } +#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 (spec->use_alt_functions) + ca0132_alt_add_chmap_ctls(codec); + + 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 (spec->use_alt_functions) { + 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 (!spec->use_alt_functions) { + 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 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 (spec->quirk == 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); + } +} + +/* + * Recon3D r3d_setup_defaults sub functions. + */ + +static void r3d_dsp_scp_startup(struct hda_codec *codec) +{ + unsigned int 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); + +} + +static void r3d_dsp_initial_mic_setup(struct hda_codec *codec) +{ + 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); + /* This ConnPointID is unique to Recon3Di. Haven't seen it elsewhere */ + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + /* Mic 2 Setup, even though it isn't connected on SBZ */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, SR_96_000); + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x01, tmp); +} + +/* + * Initialize Sound Blaster Z analog microphones. + */ +static void sbz_init_analog_mics(struct hda_codec *codec) +{ + 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); + tmp = FLOAT_THREE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + /* Mic 2 Setup, even though it isn't connected on SBZ */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, 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 */ + 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); + + chipio_write_no_mutex(codec, 0x19042c, 0x00000001); + + codec_dbg(codec, "Startup Data exited, mutex released.\n"); + mutex_unlock(&spec->chipio_mutex); +} + +/* + * Sound Blaster Z uses these after DSP is loaded. Weird SCP commands + * without a 0x20 source like normal. + */ +static void sbz_dsp_scp_startup(struct hda_codec *codec) +{ + unsigned int tmp; + + 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); + +} + +static void sbz_dsp_initial_mic_setup(struct hda_codec *codec) +{ + 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); + + chipio_write(codec, 0x18b098, 0x0000000c); + chipio_write(codec, 0x18b09C, 0x0000000c); +} + +/* + * 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; + + r3d_dsp_scp_startup(codec); + + r3d_dsp_initial_mic_setup(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 (spec->quirk == QUIRK_R3DI) + r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADED); + + /* 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, stream_format; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + sbz_dsp_scp_startup(codec); + + sbz_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); + + sbz_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]); + } + } + + /* + * Have to make a stream to bind the sound output to, otherwise + * you'll get dead audio. Before I did this, it would bind to an + * audio input, and would never work + */ + 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]); + + 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]); +} + +/* + * Initialization of flags in chip + */ +static void ca0132_init_flags(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + if (spec->use_alt_functions) { + 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 (spec->use_alt_functions) { + 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; + /* + * 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 (spec->quirk) { + case QUIRK_SBZ: + if (request_firmware(&fw_entry, SBZ_EFX_FILE, + codec->card->dev) != 0) { + codec_dbg(codec, "SBZ alt firmware not detected. "); + spec->alt_firmware_present = false; + } else { + codec_dbg(codec, "Sound Blaster Z firmware selected."); + spec->alt_firmware_present = true; + } + break; + case QUIRK_R3DI: + if (request_firmware(&fw_entry, R3DI_EFX_FILE, + codec->card->dev) != 0) { + codec_dbg(codec, "Recon3Di alt firmware not detected."); + spec->alt_firmware_present = false; + } else { + codec_dbg(codec, "Recon3Di firmware selected."); + spec->alt_firmware_present = true; + } + break; + default: + spec->alt_firmware_present = false; + break; + } + /* + * Use default ctefx.bin if no alt firmware is detected, or if none + * exists for your particular codec. + */ + if (!spec->alt_firmware_present) { + 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 && (!spec->use_alt_functions)) + 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 (spec->use_alt_functions) + 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 (spec->use_alt_functions) + snd_hda_jack_detect_enable_callback(codec, + spec->unsol_tag_front_hp, hp_callback); +} + +/* + * Verbs tables. + */ + +/* Sends before DSP download. */ +static struct hda_verb ca0132_base_init_verbs[] = { + /*enable ct extension*/ + {0x15, VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0x1}, + {} +}; + +/* Send at exit. */ +static 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 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 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 (!spec->use_alt_functions) + 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 (spec->use_alt_controls) { + spec->xbass_xover_freq = 8; + for (i = 0; i < EFFECT_LEVEL_SLIDERS; i++) + spec->fx_ctl_val[i] = effect_slider_defaults[i]; + } + + spec->voicefx_val = 0; + spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID] = 1; + spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] = 0; + +#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); + + ca0132_mmio_gpio_set(codec, 0, false); + ca0132_mmio_gpio_set(codec, 1, false); + ca0132_mmio_gpio_set(codec, 4, true); + ca0132_mmio_gpio_set(codec, 5, false); + ca0132_mmio_gpio_set(codec, 7, false); +} + +static void sbz_set_pin_ctl_default(struct hda_codec *codec) +{ + hda_nid_t pins[5] = {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 < 5; 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) +{ + hda_nid_t pins[7] = {0x0B, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}; + unsigned int i; + + for (i = 0; i < 7; 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 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 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); + + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfc); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfd); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfe); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xff); + + 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) +{ + + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfc); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfd); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfe); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xff); + + 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 void ca0132_mmio_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + writel(0x00000000, spec->mem_base + 0x400); + writel(0x00000000, spec->mem_base + 0x408); + writel(0x00000000, spec->mem_base + 0x40C); + writel(0x00880680, spec->mem_base + 0x01C); + writel(0x00000083, spec->mem_base + 0xC0C); + writel(0x00000030, spec->mem_base + 0xC00); + writel(0x00000000, spec->mem_base + 0xC04); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x000000C1, spec->mem_base + 0xC08); + writel(0x000000F1, spec->mem_base + 0xC08); + writel(0x00000001, spec->mem_base + 0xC08); + writel(0x000000C7, spec->mem_base + 0xC08); + writel(0x000000C1, spec->mem_base + 0xC08); + writel(0x00000080, spec->mem_base + 0xC04); +} + +/* + * 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 (spec->quirk) { + 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; + } +} + +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 (spec->quirk == 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 (spec->use_pci_mmio) + ca0132_mmio_init(codec); + + snd_hda_power_up_pm(codec); + + ca0132_init_unsol(codec); + ca0132_init_params(codec); + ca0132_init_flags(codec); + + snd_hda_sequence_write(codec, spec->base_init_verbs); + + if (spec->use_alt_functions) + ca0132_alt_init(codec); + + ca0132_download_dsp(codec); + + ca0132_refresh_widget_caps(codec); + + switch (spec->quirk) { + case QUIRK_R3DI: + case QUIRK_R3D: + r3d_setup_defaults(codec); + break; + case QUIRK_SBZ: + sbz_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 (!spec->use_alt_functions) { + 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 (spec->quirk == QUIRK_SBZ) + ca0132_gpio_setup(codec); + + snd_hda_sequence_write(codec, spec->spec_init_verbs); + if (spec->use_alt_functions) { + 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 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 (spec->quirk) { + case QUIRK_SBZ: + sbz_exit_chip(codec); + break; + case QUIRK_R3D: + r3d_exit_chip(codec); + break; + case QUIRK_R3DI: + r3di_gpio_shutdown(codec); + 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 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 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 (!spec->use_alt_functions) + spec->multiout.max_channels = 2; + else + spec->multiout.max_channels = 6; + + switch (spec->quirk) { + case QUIRK_ALIENWARE: + codec_dbg(codec, "ca0132_config: QUIRK_ALIENWARE applied.\n"); + snd_hda_apply_pincfgs(codec, alienware_pincfgs); + + 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: + if (spec->quirk == QUIRK_SBZ) { + codec_dbg(codec, "%s: QUIRK_SBZ applied.\n", __func__); + snd_hda_apply_pincfgs(codec, sbz_pincfgs); + } + if (spec->quirk == QUIRK_R3D) { + codec_dbg(codec, "%s: QUIRK_R3D applied.\n", __func__); + snd_hda_apply_pincfgs(codec, r3d_pincfgs); + } + + 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_R3DI: + codec_dbg(codec, "%s: QUIRK_R3DI applied.\n", __func__); + snd_hda_apply_pincfgs(codec, r3di_pincfgs); + + 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; + if (spec->quirk == QUIRK_SBZ || spec->quirk == QUIRK_R3D) + 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; +} + +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; + + codec->patch_ops = ca0132_patch_ops; + codec->pcm_format_first = 1; + codec->no_sticky_stream = 1; + + /* Detect codec quirk */ + quirk = snd_pci_quirk_lookup(codec->bus->pci, ca0132_quirks); + if (quirk) + spec->quirk = quirk->value; + else + spec->quirk = QUIRK_NONE; + + spec->dsp_state = DSP_DOWNLOAD_INIT; + spec->num_mixers = 1; + + /* Set which mixers each quirk uses. */ + switch (spec->quirk) { + case QUIRK_SBZ: + spec->mixers[0] = desktop_mixer; + snd_hda_codec_set_name(codec, "Sound Blaster Z"); + 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; + default: + spec->mixers[0] = ca0132_mixer; + break; + } + + /* Setup whether or not to use alt functions/controls/pci_mmio */ + switch (spec->quirk) { + case QUIRK_SBZ: + case QUIRK_R3D: + 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; + } + + 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; + } + } + + 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 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..a7f91be45 --- /dev/null +++ b/sound/pci/hda/patch_cirrus.c @@ -0,0 +1,1256 @@ +/* + * HD audio interface patch for Cirrus Logic CS420x chip + * + * Copyright (c) 2009 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "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, 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..1b2195dd2 --- /dev/null +++ b/sound/pci/hda/patch_cmedia.c @@ -0,0 +1,141 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for C-Media CMI9880 + * + * Copyright (c) 2004 Takashi Iwai + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "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, &_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..629857f55 --- /dev/null +++ b/sound/pci/hda/patch_conexant.c @@ -0,0 +1,1138 @@ +/* + * HD audio interface patch for Conexant HDA audio codec + * + * Copyright (c) 2006 Pototskiy Akex + * Takashi Iwai + * Tobin Davis + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include "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, + 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 void cx_auto_vmaster_hook_mute_led(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct conexant_spec *spec = codec->spec; + + snd_hda_codec_write(codec, spec->mute_led_eapd, 0, + AC_VERB_SET_EAPD_BTLENABLE, + enabled ? 0x00 : 0x02); +} + +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); + + 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_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_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; + spec->gen.vmaster_mute.hook = cx_auto_vmaster_hook_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 void cxt_fixup_gpio_mute_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct conexant_spec *spec = codec->spec; + /* muted -> LED on */ + cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, !enabled); +} + +/* turn on/off mic-mute LED via GPIO per capture hook */ +static void cxt_gpio_micmute_update(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + + cxt_update_gpio_led(codec, spec->gpio_mic_led_mask, + spec->gen.micmute_led.led_value); +} + + +static void cxt_fixup_mute_led_gpio(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + static const struct hda_verb gpio_init[] = { + { 0x01, AC_VERB_SET_GPIO_MASK, 0x03 }, + { 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03 }, + {} + }; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gen.vmaster_mute.hook = cxt_fixup_gpio_mute_hook; + spec->gpio_led = 0; + spec->mute_led_polarity = 0; + spec->gpio_mute_led_mask = 0x01; + spec->gpio_mic_led_mask = 0x02; + snd_hda_gen_add_micmute_led(codec, cxt_gpio_micmute_update); + } + snd_hda_add_verbs(codec, gpio_init); + if (spec->gpio_led) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_led); +} + + +/* 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_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_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, 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, 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), + 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_MIC_NO_PRESENCE, .name = "hp-mic-fix" }, + {} +}; + +/* 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 hda_nid_t out_nids[] = { + 0x10, 0x11, 0 + }; + 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; + if (spec->dynamic_eapd) + spec->gen.vmaster_mute.hook = cx_auto_vmaster_hook; + + 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; + /* Fall through */ + default: + codec->pin_amp_workaround = 1; + snd_hda_pick_fixup(codec, cxt5066_fixup_models, + cxt5066_fixups, cxt_fixups); + break; + } + + /* Show mute-led control only on HP laptops + * This is a sort of white-list: on HP laptops, EAPD corresponds + * only to the mute-LED without actualy amp function. Meanwhile, + * others may use EAPD really as an amp switch, so it might be + * not good to expose it blindly. + */ + switch (codec->core.subsystem_id >> 16) { + case 0x103c: + spec->gen.vmaster_mute_enum = 1; + break; + } + + 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 = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + goto error; + + err = cx_auto_parse_beep(codec); + 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(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..d21a4eb1c --- /dev/null +++ b/sound/pci/hda/patch_hdmi.c @@ -0,0 +1,3970 @@ +/* + * + * 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 + * Copyright (c) 2013 Anssi Hannula + * + * Authors: + * Wu Fengguang + * + * Maintained by: + * Wu Fengguang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hda_codec.h" +#include "hda_local.h" +#include "hda_jack.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"); + +#define is_haswell(codec) ((codec)->core.vendor_id == 0x80862807) +#define is_broadwell(codec) ((codec)->core.vendor_id == 0x80862808) +#define is_skylake(codec) ((codec)->core.vendor_id == 0x80862809) +#define is_broxton(codec) ((codec)->core.vendor_id == 0x8086280a) +#define is_kabylake(codec) ((codec)->core.vendor_id == 0x8086280b) +#define is_geminilake(codec) (((codec)->core.vendor_id == 0x8086280d) || \ + ((codec)->core.vendor_id == 0x80862800)) +#define is_cannonlake(codec) ((codec)->core.vendor_id == 0x8086280c) +#define is_haswell_plus(codec) (is_haswell(codec) || is_broadwell(codec) \ + || is_skylake(codec) || is_broxton(codec) \ + || is_kabylake(codec)) || is_geminilake(codec) \ + || is_cannonlake(codec) +#define is_valleyview(codec) ((codec)->core.vendor_id == 0x80862882) +#define is_cherryview(codec) ((codec)->core.vendor_id == 0x80862883) +#define is_valleyview_plus(codec) (is_valleyview(codec) || is_cherryview(codec)) + +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 */ + 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, + unsigned char *buf, int *eld_size); + + void (*pin_setup_infoframe)(struct hda_codec *codec, hda_nid_t pin_nid, + 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, bool hbr); + + int (*setup_stream)(struct hda_codec *codec, hda_nid_t cvt_nid, + hda_nid_t pin_nid, 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 { + 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; + /* 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; + /* + * Non-generic VIA/NVIDIA specific + */ + struct hda_multi_out multiout; + struct hda_pcm_stream pcm_playback; + + /* i915/powerwell (Haswell+/Valleyview+) specific */ + bool use_acomp_notifier; /* use i915 eld_notify callback for hotplug */ + struct drm_audio_component_audio_ops drm_audio_ops; + + struct hdac_chmap chmap; + hda_nid_t vendor_nid; +}; + +#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 registered\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 not registered\n", 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, + .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 void hdmi_pin_setup_infoframe(struct hda_codec *codec, + hda_nid_t pin_nid, + int ca, int active_channels, + int conn_type) +{ + union audio_infoframe ai; + + memset(&ai, 0, sizeof(ai)); + if (conn_type == 0) { /* HDMI */ + struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi; + + hdmi_ai->type = 0x84; + hdmi_ai->ver = 0x01; + hdmi_ai->len = 0x0a; + 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; + } + + /* + * 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 channels = per_pin->channels; + int active_channels; + struct hdmi_eld *eld; + int ca; + + if (!channels) + return; + + /* 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, ca, active_channels, + eld->info.conn_type); + + per_pin->non_pcm = non_pcm; +} + +/* + * Unsolicited events + */ + +static bool 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); + if (hdmi_present_sense(get_pin(spec, pin_idx), 1)) + snd_hda_jack_report_sync(codec); + mutex_unlock(&spec->pcm_lock); +} + +static void jack_callback(struct hda_codec *codec, + struct hda_jack_callback *jack) +{ + /* hda_jack don't support DP MST */ + check_presence_and_report(codec, jack->nid, 0); +} + +static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res) +{ + int tag = res >> AC_UNSOL_RES_TAG_SHIFT; + struct hda_jack_tbl *jack; + int dev_entry = (res & AC_UNSOL_RES_DE) >> AC_UNSOL_RES_DE_SHIFT; + + /* + * assume DP MST uses dyn_pcm_assign and acomp and + * never comes here + * if DP MST supports unsol event, below code need + * consider dev_entry + */ + jack = snd_hda_jack_tbl_get_from_tag(codec, tag); + if (!jack) + return; + 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, dev_entry, !!(res & AC_UNSOL_RES_IA), + !!(res & AC_UNSOL_RES_PD), !!(res & AC_UNSOL_RES_ELDV)); + + /* hda_jack don't support DP MST */ + check_presence_and_report(codec, jack->nid, 0); +} + +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; + + if (!snd_hda_jack_tbl_get_from_tag(codec, tag)) { + codec_dbg(codec, "Unexpected HDMI event tag 0x%x\n", tag); + return; + } + + if (subtag == 0) + hdmi_intrinsic_event(codec, res); + 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, + bool hbr) +{ + int pinctl, new_pinctl; + + if (snd_hda_query_pin_caps(codec, pin_nid) & AC_PINCAP_HBR) { + 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, 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, is_hbr_format(format)); + + if (err) { + codec_dbg(codec, "hdmi_setup_stream: HBR is not supported\n"); + return err; + } + + if (is_haswell_plus(codec)) { + + /* + * 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); + + /* 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; + hinfo->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_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; + + 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; + } + + /* all the device entries on the same pin have the same conn list */ + per_pin->num_mux_nids = snd_hda_get_connections(codec, pin_nid, + per_pin->mux_nids, + HDA_MAX_CONNECTIONS); + + return 0; +} + +static int hdmi_find_pcm_slot(struct hdmi_spec *spec, + struct hdmi_spec_per_pin *per_pin) +{ + int i; + + /* try the prefer PCM */ + if (!test_bit(per_pin->pin_nid_idx, &spec->pcm_bitmap)) + return per_pin->pin_nid_idx; + + /* have a second try; check the "reserved area" over num_pins */ + for (i = spec->num_nids; i < spec->pcm_used; i++) { + if (!test_bit(i, &spec->pcm_bitmap)) + return i; + } + + /* the last try; check the empty slots in pins */ + for (i = 0; i < spec->num_nids; 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; +} + +/* update per_pin ELD from the given new ELD; + * setup info frame and notification accordingly + */ +static void update_eld(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + struct hdmi_eld *eld) +{ + struct hdmi_eld *pin_eld = &per_pin->sink_eld; + struct hdmi_spec *spec = codec->spec; + bool old_eld_valid = pin_eld->eld_valid; + bool eld_changed; + int pcm_idx = -1; + + /* for monitor disconnection, save pcm_idx firstly */ + pcm_idx = per_pin->pcm_idx; + 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 (eld->eld_valid) + snd_hdmi_show_eld(codec, &eld->info); + + eld_changed = (pin_eld->eld_valid != eld->eld_valid); + if (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; + + 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); +} + +/* update ELD and jack state via HD-audio verbs */ +static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin, + int repoll) +{ + struct hda_jack_tbl *jack; + struct hda_codec *codec = per_pin->codec; + struct hdmi_spec *spec = codec->spec; + struct hdmi_eld *eld = &spec->temp_eld; + hda_nid_t pin_nid = per_pin->pin_nid; + /* + * 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; + bool ret; + bool do_repoll = false; + + present = snd_hda_pin_sense(codec, pin_nid); + + 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, eld->eld_buffer, + &eld->eld_size) < 0) + eld->eld_valid = false; + else { + if (snd_hdmi_parse_eld(codec, &eld->info, eld->eld_buffer, + eld->eld_size) < 0) + eld->eld_valid = false; + } + if (!eld->eld_valid && repoll) + do_repoll = true; + } + + if (do_repoll) + schedule_delayed_work(&per_pin->work, msecs_to_jiffies(300)); + else + update_eld(codec, per_pin, eld); + + ret = !repoll || !eld->monitor_present || eld->eld_valid; + + jack = snd_hda_jack_tbl_get(codec, pin_nid); + if (jack) { + jack->block_report = !ret; + jack->pin_sense = (eld->monitor_present && eld->eld_valid) ? + AC_PINSENSE_PRESENCE : 0; + } + mutex_unlock(&per_pin->lock); + return ret; +} + +static struct snd_jack *pin_idx_to_jack(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + struct hdmi_spec *spec = codec->spec; + struct snd_jack *jack = NULL; + struct hda_jack_tbl *jack_tbl; + + /* if !dyn_pcm_assign, get jack from hda_jack_tbl + * in !dyn_pcm_assign case, spec->pcm_rec[].jack is not + * NULL even after snd_hda_jack_tbl_clear() is called to + * free snd_jack. This may cause access invalid memory + * when calling snd_jack_report + */ + if (per_pin->pcm_idx >= 0 && spec->dyn_pcm_assign) + jack = spec->pcm_rec[per_pin->pcm_idx].jack; + else if (!spec->dyn_pcm_assign) { + /* + * jack tbl doesn't support DP MST + * DP MST will use dyn_pcm_assign, + * so DP MST will never come here + */ + jack_tbl = snd_hda_jack_tbl_get(codec, per_pin->pin_nid); + if (jack_tbl) + jack = jack_tbl->jack; + } + return jack; +} + +/* 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; + struct snd_jack *jack = NULL; + int size; + + mutex_lock(&per_pin->lock); + eld->monitor_present = false; + 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); + if (size > 0) { + size = min(size, ELD_MAX_SIZE); + if (snd_hdmi_parse_eld(codec, &eld->info, + eld->eld_buffer, size) < 0) + size = -EINVAL; + } + + if (size > 0) { + eld->eld_valid = true; + eld->eld_size = size; + } else { + eld->eld_valid = false; + eld->eld_size = 0; + } + + /* pcm_idx >=0 before update_eld() means it is in monitor + * disconnected event. Jack must be fetched before update_eld() + */ + jack = pin_idx_to_jack(codec, per_pin); + update_eld(codec, per_pin, eld); + if (jack == NULL) + jack = pin_idx_to_jack(codec, per_pin); + if (jack == NULL) + goto unlock; + snd_jack_report(jack, + eld->monitor_present ? SND_JACK_AVOUT : 0); + unlock: + mutex_unlock(&per_pin->lock); +} + +static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll) +{ + struct hda_codec *codec = per_pin->codec; + int ret; + + /* no temporary power up/down needed for component notifier */ + if (!codec_has_acomp(codec)) { + ret = snd_hda_power_up_pm(codec); + if (ret < 0 && pm_runtime_suspended(hda_codec_dev(codec))) { + snd_hda_power_down_pm(codec); + return false; + } + } + + if (codec_has_acomp(codec)) { + sync_eld_via_acomp(codec, per_pin); + ret = false; /* don't call snd_hda_jack_report_sync() */ + } else { + ret = hdmi_present_sense_via_verbs(per_pin, repoll); + } + + if (!codec_has_acomp(codec)) + snd_hda_power_down_pm(codec); + + return ret; +} + +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(codec, per_pin->pin_nid); + if (jack) + jack->jack_dirty = 1; + + if (per_pin->repoll_count++ > 6) + per_pin->repoll_count = 0; + + mutex_lock(&spec->pcm_lock); + if (hdmi_present_sense(per_pin, per_pin->repoll_count)) + snd_hda_jack_report_sync(per_pin->codec); + mutex_unlock(&spec->pcm_lock); +} + +static void intel_haswell_fixup_connect_list(struct hda_codec *codec, + hda_nid_t nid); + +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) + return 0; + + /* + * To simplify the implementation, malloc all + * the virtual pins in the initialization statically + */ + if (is_haswell_plus(codec)) { + /* + * 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); + if (is_haswell_plus(codec)) + intel_haswell_fixup_connect_list(codec, pin_nid); + 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 int hdmi_parse_codec(struct hda_codec *codec) +{ + hda_nid_t nid; + int i, nodes; + + nodes = snd_hda_get_sub_nodes(codec, codec->core.afg, &nid); + if (!nid || nodes < 0) { + codec_warn(codec, "HDMI: failed to get afg sub nodes\n"); + return -EINVAL; + } + + for (i = 0; i < nodes; i++, nid++) { + unsigned int caps; + unsigned int type; + + caps = get_wcaps(codec, nid); + type = get_wcaps_type(caps); + + if (!(caps & AC_WCAP_DIGITAL)) + continue; + + switch (type) { + case AC_WID_AUD_OUT: + hdmi_add_cvt(codec, nid); + break; + case AC_WID_PIN: + hdmi_add_pin(codec, nid); + break; + } + } + + 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; + hda_nid_t pin_nid; + struct snd_pcm_runtime *runtime = substream->runtime; + bool non_pcm; + int pinctl; + 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); + pin_nid = per_pin->pin_nid; + + /* 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, 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; + + hdmi_setup_audio_infoframe(codec, per_pin, non_pcm); + mutex_unlock(&per_pin->lock); + if (spec->dyn_pin_out) { + pinctl = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write(codec, 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, pin_nid, + 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); + snd_BUG_ON(!per_cvt->assigned); + per_cvt->assigned = 0; + hinfo->nid = 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) { + 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 = container_of(hdac, struct hda_codec, core); + 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 = container_of(hdac, struct hda_codec, core); + 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 = container_of(hdac, struct hda_codec, core); + 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 = container_of(hdac, struct hda_codec, core); + 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; + + /* + * for non-mst mode, pcm number is the same as before + * for DP MST mode, pcm number is (nid number + dev_num - 1) + * dev_num is the device entry number in a pin + * + */ + for (idx = 0; idx < spec->num_nids + spec->dev_num - 1; 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 add_hdmi_jack_kctl(struct hda_codec *codec, + struct hdmi_spec *spec, + int pcm_idx, + const char *name) +{ + struct snd_jack *jack; + int err; + + err = snd_jack_new(codec->card, name, 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_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; + struct hda_jack_tbl *jack; + int pcmdev = get_pcm_rec(spec, pcm_idx)->device; + bool phantom_jack; + int ret; + + if (pcmdev > 0) + sprintf(hdmi_str + strlen(hdmi_str), ",pcm=%d", pcmdev); + + if (spec->dyn_pcm_assign) + return add_hdmi_jack_kctl(codec, spec, pcm_idx, hdmi_str); + + /* for !dyn_pcm_assign, we still use hda_jack for compatibility */ + /* if !dyn_pcm_assign, it must be non-MST mode. + * This means pcms and pins are statically mapped. + * And pcm_idx is pin_idx. + */ + per_pin = get_pin(spec, pcm_idx); + phantom_jack = !is_jack_detectable(codec, per_pin->pin_nid); + if (phantom_jack) + strncat(hdmi_str, " Phantom", + sizeof(hdmi_str) - strlen(hdmi_str) - 1); + ret = snd_hda_jack_add_kctl(codec, per_pin->pin_nid, hdmi_str, + phantom_jack); + if (ret < 0) + return ret; + jack = snd_hda_jack_tbl_get(codec, per_pin->pin_nid); + if (jack == NULL) + return 0; + /* assign jack->jack to pcm_rec[].jack to + * align with dyn_pcm_assign mode + */ + spec->pcm_rec[pcm_idx].jack = jack->jack; + 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; + + 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)) + snd_hda_jack_detect_enable_callback(codec, pin_nid, + codec->jackpoll_interval > 0 ? + jack_callback : NULL); + } + 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 (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); + regcache_sync(codec->core.regmap); + + 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 = snd_hdmi_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->ops = generic_standard_hdmi_ops; + spec->dev_num = 1; /* initialize to 1 */ + mutex_init(&spec->pcm_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; +} + +/* + * Intel codec parsers and helpers + */ + +static void intel_haswell_fixup_connect_list(struct hda_codec *codec, + hda_nid_t nid) +{ + struct hdmi_spec *spec = codec->spec; + hda_nid_t conns[4]; + int nconns; + + nconns = snd_hda_get_connections(codec, nid, conns, ARRAY_SIZE(conns)); + if (nconns == spec->num_cvts && + !memcmp(conns, spec->cvt_nids, spec->num_cvts * sizeof(hda_nid_t))) + return; + + /* override pins connection list */ + codec_dbg(codec, "hdmi: haswell: override pin connection 0x%x\n", nid); + snd_hda_override_conn_list(codec, nid, spec->num_cvts, spec->cvt_nids); +} + +#define INTEL_VENDOR_NID 0x08 +#define INTEL_GLK_VENDOR_NID 0x0B +#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) +{ + int base_nid = intel_base_nid(audio_ptr); + + if (WARN_ON(pin_nid < base_nid || pin_nid >= base_nid + 3)) + return -1; + return pin_nid - base_nid + 1; /* intel port is 1-based */ +} + +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; + + /* we assume only from port-B to port-D */ + if (port < 1 || port > 3) + return; + + pin_nid = port + intel_base_nid(codec) - 1; /* intel port is 1-based */ + + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ + if (snd_power_get_state(codec->card) != SNDRV_CTL_POWER_D0) + return; + /* ditto during suspend/resume process itself */ + if (snd_hdac_is_in_pm(&codec->core)) + return; + + snd_hdac_i915_set_bclk(&codec->bus->core); + check_presence_and_report(codec, pin_nid, dev_id); +} + +/* 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->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 = intel_pin2port; + spec->drm_audio_ops.pin_eld_notify = intel_pin_eld_notify; + 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, u32 stream_tag, int format) +{ + haswell_verify_D0(codec, cvt_nid, pin_nid); + return hdmi_setup_stream(codec, cvt_nid, pin_nid, 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) +{ + 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; + + intel_haswell_enable_all_pins(codec, true); + intel_haswell_fixup_enable_dp12(codec); + + /* For Haswell/Broadwell, the controller is also in the power well and + * can cover the codec power request, and so need not set this flag. + */ + if (!is_haswell(codec) && !is_broadwell(codec)) + codec->core.link_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; + + return parse_intel_hdmi(codec); +} + +static int patch_i915_hsw_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_VENDOR_NID); +} + +static int patch_i915_glk_hdmi(struct hda_codec *codec) +{ + return intel_hsw_common_init(codec, INTEL_GLK_VENDOR_NID); +} + +/* 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->core.link_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); + 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; + + 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; + 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; +} + +static int patch_nvhdmi(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; + + 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->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; + + 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, + unsigned char *buf, int *eld_size) +{ + /* 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 ca, + int active_channels, int conn_type) +{ + 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 = container_of(hdac, struct hda_codec, core); + 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 = container_of(hdac, struct hda_codec, core); + 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, + bool hbr) +{ + int hbr_ctl, hbr_ctl_new; + + 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, 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, 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); + } + + return 0; +} + +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; + + 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); +} + +/* + * 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), +HDA_CODEC_ENTRY(0x10de0009, "GPU 09 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de000a, "GPU 0a HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de000b, "GPU 0b HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de000c, "MCP89 HDMI", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de000d, "GPU 0d HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0010, "GPU 10 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0011, "GPU 11 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0012, "GPU 12 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0013, "GPU 13 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0014, "GPU 14 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0015, "GPU 15 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0016, "GPU 16 HDMI/DP", patch_nvhdmi), +/* 17 is known to be absent */ +HDA_CODEC_ENTRY(0x10de0018, "GPU 18 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de0019, "GPU 19 HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de001a, "GPU 1a HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de001b, "GPU 1b HDMI/DP", patch_nvhdmi), +HDA_CODEC_ENTRY(0x10de001c, "GPU 1c HDMI/DP", patch_nvhdmi), +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(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(0x10de8001, "MCP73 HDMI", patch_nvhdmi_2ch), +HDA_CODEC_ENTRY(0x10de8067, "MCP67/68 HDMI", patch_nvhdmi_2ch), +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(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(0x80862800, "Geminilake HDMI", patch_i915_glk_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..ed0b8ec50 --- /dev/null +++ b/sound/pci/hda/patch_realtek.c @@ -0,0 +1,9277 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for Realtek ALC codecs + * + * Copyright (c) 2004 Kailang Yang + * PeiSen Hou + * Takashi Iwai + * Jonathan Woithe + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "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_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 alc269_fixup_mic_mute_hook() */ + 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; + + 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; + + /* 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 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; +} + +#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); +} + +#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); +} + +#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) +{ + 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); + } +} + +/* + * 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); +} + +/* + * 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 0x10ec0233: + case 0x10ec0235: + case 0x10ec0236: + case 0x10ec0245: + case 0x10ec0255: + case 0x10ec0256: + 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 hda_nid_t pins[] = { + 0x0f, 0x10, 0x14, 0x15, 0x17, 0 + }; + 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 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_fill_eapd_coef(codec); + 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, 0x03); + 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 int alc_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + 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); + regcache_sync(codec->core.regmap); + 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 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 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_white_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), + /* blacklist -- 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_white_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->patch_ops = alc_patch_ops; + + 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; + + 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; + + 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, +}; + +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 */ + hda_nid_t conn1[2] = { 0x0c, 0x0d }; + hda_nid_t conn2[2] = { 0x0e, 0x0f }; + snd_hda_override_conn_list(codec, 0x14, 2, conn1); + snd_hda_override_conn_list(codec, 0x15, 2, conn1); + snd_hda_override_conn_list(codec, 0x18, 2, conn2); + snd_hda_override_conn_list(codec, 0x1a, 2, conn2); + } else if (action == HDA_FIXUP_ACT_PROBE) { + /* restore the connections */ + hda_nid_t conn[5] = { 0x0c, 0x0d, 0x0e, 0x0f, 0x26 }; + snd_hda_override_conn_list(codec, 0x14, 5, conn); + snd_hda_override_conn_list(codec, 0x15, 5, conn); + snd_hda_override_conn_list(codec, 0x18, 5, conn); + snd_hda_override_conn_list(codec, 0x1a, 5, conn); + } +} + +/* Set VREF on HP pin */ +static void alc889_fixup_mbp_vref(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + static hda_nid_t nids[3] = { 0x14, 0x15, 0x19 }; + 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 hda_nid_t nids[2] = { 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 hda_nid_t nids[1] = { 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 hda_nid_t nids[2] = { 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) +{ + hda_nid_t conn1[1] = { 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, 1, conn1); + snd_hda_override_conn_list(codec, 0x1b, 1, 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 */ + {} + } + }, +}; + +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(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, 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, 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"}, + {} +}; + +/* + * 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; + } + + snd_hda_pick_fixup(codec, alc882_fixup_models, alc882_fixup_tbl, + alc882_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 = 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); + + 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; + spec->gen.beep_nid = 0x01; + + spec->shutup = alc_eapd_shutup; + + 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 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 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 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 (!hp_pin) + return; + + 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) + msleep(85); + + snd_hda_codec_write(codec, hp_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + if (hp_pin_sense) + 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) { + 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); + + /* 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 (codec->core.vendor_id != 0x10ec0236 && + codec->core.vendor_id != 0x10ec0257) + 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) + msleep(100); + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); +} + +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 (!hp_pin) + return; + + 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) + 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) + msleep(85); + + if (hp1_pin_sense) + 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) + 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) { + alc269_shutup(codec); + return; + } + + /* 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) + 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) + msleep(85); + + if (hp1_pin_sense) + 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) + msleep(100); + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(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) /* NOP */ +#define alc5505_dsp_resume(codec) /* 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); + } + + regcache_sync(codec->core.regmap); + 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); +} + +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); +} + +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; + } +} + + +/* update mute-LED according to the speaker mute state via mic VREF pin */ +static void alc269_fixup_mic_mute_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct alc_spec *spec = codec->spec; + unsigned int pinval; + + if (spec->mute_led_polarity) + enabled = !enabled; + pinval = snd_hda_codec_get_pin_target(codec, spec->mute_led_nid); + pinval &= ~AC_PINCTL_VREFEN; + pinval |= enabled ? AC_PINCTL_VREF_HIZ : AC_PINCTL_VREF_80; + if (spec->mute_led_nid) { + /* temporarily power up/down for setting VREF */ + snd_hda_power_up_pm(codec); + snd_hda_set_pin_ctl_cache(codec, spec->mute_led_nid, pinval); + snd_hda_power_down_pm(codec); + } +} + +/* 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; + spec->gen.vmaster_mute.hook = alc269_fixup_mic_mute_hook; + spec->gen.vmaster_mute_enum = 1; + 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; + spec->gen.vmaster_mute.hook = alc269_fixup_mic_mute_hook; + spec->gen.vmaster_mute_enum = 1; + 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 void alc_fixup_gpio_mute_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct alc_spec *spec = codec->spec; + + alc_update_gpio_led(codec, spec->gpio_mute_led_mask, + spec->mute_led_polarity, enabled); +} + +/* turn on/off mic-mute LED via GPIO per capture hook */ +static void alc_gpio_micmute_update(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + + alc_update_gpio_led(codec, spec->gpio_mic_led_mask, + spec->micmute_led_polarity, + spec->gen.micmute_led.led_value); +} + +/* 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; + spec->gen.vmaster_mute.hook = alc_fixup_gpio_mute_hook; + } + if (micmute_mask) { + spec->gpio_mic_led_mask = micmute_mask; + snd_hda_gen_add_micmute_led(codec, alc_gpio_micmute_update); + } +} + +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) +{ + struct alc_spec *spec = codec->spec; + + spec->micmute_led_polarity = 1; + + 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); +} + +/* turn on/off mic-mute LED per capture hook */ +static void alc_cap_micmute_update(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int pinval; + + if (!spec->cap_mute_led_nid) + return; + pinval = snd_hda_codec_get_pin_target(codec, spec->cap_mute_led_nid); + pinval &= ~AC_PINCTL_VREFEN; + if (spec->gen.micmute_led.led_value) + pinval |= AC_PINCTL_VREF_80; + else + pinval |= AC_PINCTL_VREF_HIZ; + snd_hda_set_pin_ctl_cache(codec, spec->cap_mute_led_nid, pinval); +} + +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(codec, alc_cap_micmute_update); + 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(codec, alc_cap_micmute_update); + codec->power_filter = led_power_filter; + } +} + +#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; + + spec->micmute_led_polarity = 1; + 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(codec, alc_cap_micmute_update); + } +} + +static 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) +{ + static 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 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 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 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 struct coef_fw coef0298[] = { + UPDATE_COEF(0x19, 0x1300, 0x0300), + {} + }; + static struct coef_fw coef0292[] = { + WRITE_COEF(0x76, 0x000e), + WRITE_COEF(0x6c, 0x2400), + WRITE_COEF(0x18, 0x7308), + WRITE_COEF(0x6b, 0xc429), + {} + }; + static 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 struct coef_fw coef0668[] = { + WRITE_COEF(0x15, 0x0d40), + WRITE_COEF(0xb7, 0x802b), + {} + }; + static struct coef_fw coef0225[] = { + UPDATE_COEF(0x63, 3<<14, 0), + {} + }; + static 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), + {} + }; + + switch (codec->core.vendor_id) { + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; + case 0x10ec0236: + case 0x10ec0256: + 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 struct coef_fw coef0255[] = { + WRITE_COEFEX(0x57, 0x03, 0x8aa6), + WRITE_COEF(0x06, 0x6100), /* Set MIC2 Vref gate to normal */ + {} + }; + static 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 struct coef_fw coef0233[] = { + UPDATE_COEF(0x35, 0, 1<<14), + WRITE_COEF(0x06, 0x2100), + WRITE_COEF(0x1a, 0x0021), + WRITE_COEF(0x26, 0x008c), + {} + }; + static 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 struct coef_fw coef0292[] = { + WRITE_COEF(0x19, 0xa208), + WRITE_COEF(0x2e, 0xacf0), + {} + }; + static 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 struct coef_fw coef0688[] = { + WRITE_COEF(0xb7, 0x802b), + WRITE_COEF(0xb5, 0x1040), + UPDATE_COEF(0xc3, 0, 1<<12), + {} + }; + static 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 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 0x10ec0236: + case 0x10ec0256: + 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); + /* fallthru */ + 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 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 struct coef_fw coef0255[] = { + WRITE_COEF(0x45, 0xc089), + WRITE_COEF(0x45, 0xc489), + WRITE_COEFEX(0x57, 0x03, 0x8ea6), + WRITE_COEF(0x49, 0x0049), + {} + }; + static 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 struct coef_fw coef0233[] = { + WRITE_COEF(0x06, 0x2100), + WRITE_COEF(0x32, 0x4ea3), + {} + }; + static 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 struct coef_fw coef0292[] = { + WRITE_COEF(0x76, 0x000e), + WRITE_COEF(0x6c, 0x2400), + WRITE_COEF(0x6b, 0xc429), + WRITE_COEF(0x18, 0x7308), + {} + }; + static 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 struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0041), + WRITE_COEF(0x15, 0x0d40), + WRITE_COEF(0xb7, 0x802b), + {} + }; + static 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 0x10ec0236: + case 0x10ec0256: + 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 struct coef_fw coef0255[] = { + WRITE_COEF(0x45, 0xd489), /* Set to CTIA type */ + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEFEX(0x57, 0x03, 0x8ea6), + {} + }; + static struct coef_fw coef0256[] = { + WRITE_COEF(0x45, 0xd489), /* Set to CTIA type */ + WRITE_COEF(0x1b, 0x0e6b), + {} + }; + static struct coef_fw coef0233[] = { + WRITE_COEF(0x45, 0xd429), + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEF(0x32, 0x4ea3), + {} + }; + static 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 struct coef_fw coef0292[] = { + WRITE_COEF(0x6b, 0xd429), + WRITE_COEF(0x76, 0x0008), + WRITE_COEF(0x18, 0x7388), + {} + }; + static 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 struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0001), + WRITE_COEF(0x15, 0x0d60), + WRITE_COEF(0xc3, 0x0000), + {} + }; + static struct coef_fw coef0225_1[] = { + UPDATE_COEF(0x45, 0x3f<<10, 0x35<<10), + UPDATE_COEF(0x63, 3<<14, 2<<14), + {} + }; + static 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 0x10ec0236: + case 0x10ec0256: + 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 struct coef_fw coef0255[] = { + WRITE_COEF(0x45, 0xe489), /* Set to OMTP Type */ + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEFEX(0x57, 0x03, 0x8ea6), + {} + }; + static struct coef_fw coef0256[] = { + WRITE_COEF(0x45, 0xe489), /* Set to OMTP Type */ + WRITE_COEF(0x1b, 0x0e6b), + {} + }; + static struct coef_fw coef0233[] = { + WRITE_COEF(0x45, 0xe429), + WRITE_COEF(0x1b, 0x0c2b), + WRITE_COEF(0x32, 0x4ea3), + {} + }; + static 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 struct coef_fw coef0292[] = { + WRITE_COEF(0x6b, 0xe429), + WRITE_COEF(0x76, 0x0008), + WRITE_COEF(0x18, 0x7388), + {} + }; + static 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 struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0001), + WRITE_COEF(0x15, 0x0d50), + WRITE_COEF(0xc3, 0x0000), + {} + }; + static 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 0x10ec0236: + case 0x10ec0256: + 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 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 struct coef_fw coef0288[] = { + UPDATE_COEF(0x4f, 0xfcc0, 0xd400), /* Check Type */ + {} + }; + static 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 struct coef_fw coef0293[] = { + UPDATE_COEF(0x4a, 0x000f, 0x0008), /* Combo Jack auto detect */ + WRITE_COEF(0x45, 0xD429), /* Set to ctia type */ + {} + }; + static struct coef_fw coef0688[] = { + WRITE_COEF(0x11, 0x0001), + WRITE_COEF(0xb7, 0x802b), + WRITE_COEF(0x15, 0x0d60), + WRITE_COEF(0xc3, 0x0c00), + {} + }; + static struct coef_fw coef0274[] = { + UPDATE_COEF(0x4a, 0x0010, 0), + UPDATE_COEF(0x4a, 0x8000, 0), + WRITE_COEF(0x45, 0xd289), + UPDATE_COEF(0x49, 0x0300, 0x0300), + {} + }; + + 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 0x10ec0236: + case 0x10ec0256: + 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->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) +{ + struct alc_spec *spec = codec->spec; + spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN; + 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: + spec->current_headset_mode = 0; + 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 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 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 0x10ec0236: + case 0x10ec0256: + 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 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 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. */ + hda_nid_t conn1[2] = { 0x0c }; + snd_hda_override_conn_list(codec, 0x14, 1, conn1); + snd_hda_override_conn_list(codec, 0x15, 1, 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. */ + + hda_nid_t conn1[1] = { 0x0c }; + snd_hda_override_conn_list(codec, 0x17, 1, 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) { + hda_nid_t conn[2] = { 0x02, 0x03 }; + snd_hda_override_conn_list(codec, 0x17, 2, 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) { + hda_nid_t conn[1] = { 0x02 }; + snd_hda_override_conn_list(codec, 0x17, 1, 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 */ + } +} + +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 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; +} + +/* 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_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); +} + +/* 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); +} + +/* for dell wmi mic mute led */ +#include "dell_wmi_helper.c" + +/* for alc295_fixup_hp_top_speakers */ +#include "hp_x360_helper.c" + +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, + 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, + 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_DELL_WMI_MIC_MUTE_LED, + ALC282_FIXUP_ASPIRE_V5_PINS, + 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, + ALC288_FIXUP_DELL_HEADSET_MODE, + ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC288_FIXUP_DELL_XPS_13, + ALC288_FIXUP_DISABLE_AAMIX, + ALC292_FIXUP_DELL_E7X, + ALC292_FIXUP_DISABLE_AAMIX, + ALC293_FIXUP_DISABLE_AAMIX_MULTIJACK, + 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_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_MBXP_PINS, + 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, + 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, + ALC294_FIXUP_ASUS_HPE, + ALC285_FIXUP_HP_GPIO_LED, +}; + +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, + }, + [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_DELL_WMI_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_MBXP_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}, + { } + }, + }, + [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, + }, + [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_DELL_WMI_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_DELL_WMI_MIC_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_dell_wmi, + }, + [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 }, + { }, + }, + }, + [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, + }, + [ALC288_FIXUP_DELL_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_dell_alc288, + .chained = true, + .chain_id = ALC255_FIXUP_DELL_WMI_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] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_dell_xps13, + .chained = true, + .chain_id = ALC292_FIXUP_DISABLE_AAMIX + }, + [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, + }, + [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_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 + }, + [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 + }, + [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 + }, + [ALC285_FIXUP_HP_GPIO_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_hp_gpio_led, + }, +}; + +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, 0x080d, "Acer Aspire V5-122P", 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, 0x102b, "Acer Aspire C24-860", ALC286_FIXUP_ACER_AIO_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x106d, "Acer Cloudbook 14", ALC283_FIXUP_CHROME_BOOK), + 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, 0x1246, "Acer Predator Helios 500", ALC299_FIXUP_PREDATOR_SPK), + 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, 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(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + 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, 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, 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, 0x820d, "HP Pavilion 15", ALC269_FIXUP_HP_MUTE_LED_MIC3), + 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, 0x84e7, "HP Pavilion 15", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x8736, "HP", ALC285_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, 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, 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, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC), + 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, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x1043, 0x17d1, "ASUS UX431FL", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x19ce, "ASUS B9450FA", ALC294_FIXUP_ASUS_HPE), + 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, 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, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), + 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, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), + SND_PCI_QUIRK(0x10cf, 0x1629, "Lifebook U7x7", ALC255_FIXUP_LIFEBOOK_U7x7_HEADSET_MIC), + 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(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, 0xc740, "Samsung Ativ book 8 (NP870Z5G)", ALC269_FIXUP_ATIV_BOOK_8), + 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, 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, 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, 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, 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, 0x8680, "Clevo NJ50LU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x8686, "Clevo NH50[CZ]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + 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", ALC283_FIXUP_HEADSET_MIC), + 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 Yoga 7th", ALC285_FIXUP_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x2293, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_SPEAKER2_TO_DAC1), + 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, 0x3818, "Lenovo C940", ALC298_FIXUP_LENOVO_SPK_VOLUME), + 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, 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(0x19e5, 0x3204, "Huawei MBXP", ALC256_FIXUP_HUAWEI_MBXP_PINS), + SND_PCI_QUIRK(0x1b7d, 0xa831, "Ordissimo EVE2 ", ALC269VB_FIXUP_ORDISSIMO_EVE2), /* Also known as Malata PC-B1303 */ + SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), + +#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), + {} +}; + +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_DELL_WMI_MIC_MUTE_LED, .name = "alc255-dell-mute"}, + {.id = ALC282_FIXUP_ASPIRE_V5_PINS, .name = "aspire-v5"}, + {.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 = ALC299_FIXUP_PREDATOR_SPK, .name = "predator-spk"}, + {.id = ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, .name = "alc256-medion-headset"}, + {} +}; +#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", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60140}, + {0x14, 0x90170110}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60140}, + {0x14, 0x90170150}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0236, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x40000000}, + {0x14, 0x90170110}, + {0x21, 0x02211020}), + 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, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x1b, 0x01011020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60160}, + {0x14, 0x90170120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60170}, + {0x14, 0x90170120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell Inspiron 5468", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60180}, + {0x14, 0x90170120}, + {0x21, 0x02211030}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0xb7a60130}, + {0x14, 0x90170110}, + {0x21, 0x02211020}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x12, 0x90a60130}, + {0x14, 0x90170110}, + {0x14, 0x01011020}, + {0x21, 0x0221101f}), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC256_STANDARD_PINS), + SND_HDA_PIN_QUIRK(0x10ec0256, 0x1028, "Dell", ALC255_FIXUP_DELL1_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x1b, 0x01011020}, + {0x21, 0x0221101f}), + 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(0x10ec0274, 0x1028, "Dell", ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + {0x12, 0xb7a60130}, + {0x13, 0xb8a61140}, + {0x16, 0x90170110}, + {0x21, 0x04211020}), + 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(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(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(0x10ec0289, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + {0x12, 0xb7a60130}, + {0x14, 0x90170110}, + {0x21, 0x04211020}), + 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, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x21, 0x04211020}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1028, "Dell", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, + {0x14, 0x90170110}, + {0x21, 0x04211030}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_STANDARD_PINS, + {0x17, 0x21014020}, + {0x18, 0x21a19030}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_STANDARD_PINS, + {0x17, 0x21014040}, + {0x18, 0x21a19050}), + SND_HDA_PIN_QUIRK(0x10ec0295, 0x1028, "Dell", ALC269_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_STANDARD_PINS), + 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(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}), + {} +}; + +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; + +#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 0x10ec0236: + case 0x10ec0256: + 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 */ + break; + case 0x10ec0257: + spec->codec_variant = ALC269_TYPE_ALC257; + spec->shutup = alc256_shutup; + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; + 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; + } + + snd_hda_pick_fixup(codec, alc269_fixup_models, + alc269_fixup_tbl, alc269_fixups); + snd_hda_pick_pin_fixup(codec, alc269_pin_fixup_tbl, alc269_fixups); + 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; + spec->gen.beep_nid = 0x23; + +#ifdef CONFIG_PM + spec->power_hook = alc_power_eapd; +#endif + + 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; + spec->gen.beep_nid = 0x23; + + spec->shutup = alc_eapd_shutup; + + 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 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_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, +}; + +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_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, + }, +}; + +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(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(0x1043, 0x1080, "Asus UX501VW", ALC668_FIXUP_HEADSET_MODE), + SND_PCI_QUIRK(0x1043, 0x11cd, "Asus N550", ALC662_FIXUP_ASUS_Nx50), + SND_PCI_QUIRK(0x1043, 0x13df, "Asus N550JX", ALC662_FIXUP_BASS_1A), + 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, 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, 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, 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, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + +#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 = 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"}, + {} +}; + +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}), + {} +}; + +/* + */ +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; + } + + 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); + 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(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), + {} /* 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..f63acb1b9 --- /dev/null +++ b/sound/pci/hda/patch_si3054.c @@ -0,0 +1,318 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for Silicon Labs 3054/5 modem codec + * + * Copyright (c) 2005 Sasha Khapyorsky + * Takashi Iwai + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "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..85c33f528 --- /dev/null +++ b/sound/pci/hda/patch_sigmatel.c @@ -0,0 +1,5177 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for SigmaTel STAC92xx + * + * Copyright (c) 2005 Embedded Alley Solutions, Inc. + * Matt Porter + * + * Based on patch_cmedia.c and patch_realtek.c + * Copyright (c) 2004 Takashi Iwai + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "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; + + /* 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 void stac_capture_led_update(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + if (spec->gen.micmute_led.led_value) + 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); +} + +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, int enabled) +{ + struct sigmatel_spec *spec = codec->spec; + int muted = !enabled; + + 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 void stac_vmaster_hook(void *private_data, int val) +{ + stac_update_led_status(private_data, val); +} + +/* 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; + 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 struct snd_kcontrol_new abeep_mute_ctl = + HDA_CODEC_MUTE(NULL, 0, 0, 0); + static struct snd_kcontrol_new dbeep_mute_ctl = + HDA_CODEC_MUTE_BEEP(NULL, 0, 0, 0); + static 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_core_init[] = { + /* set dac0mux for dac converter */ + { 0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {} +}; + +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[] = { + { 0x0a, 0x02214030 }, + { 0x0b, 0x02a19040 }, + { 0x0c, 0x01a19020 }, + { 0x0d, 0x02214030 }, + { 0x0e, 0x0181302e }, + { 0x0f, 0x01014010 }, + { 0x10, 0x01014020 }, + { 0x11, 0x01014030 }, + { 0x12, 0x02319040 }, + { 0x13, 0x90a000f0 }, + { 0x14, 0x90a000f0 }, + { 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[] = { + { 0x0a, 0x02214230 }, + { 0x0b, 0x02A19240 }, + { 0x0c, 0x01013214 }, + { 0x0d, 0x01014210 }, + { 0x0e, 0x01A19250 }, + { 0x0f, 0x01011212 }, + { 0x10, 0x01016211 }, + {} +}; + +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, 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 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 slaved 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; + /* 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) + spec->gen.vmaster_mute.hook = 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(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->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..0046ea78a --- /dev/null +++ b/sound/pci/hda/patch_via.c @@ -0,0 +1,1264 @@ +/* + * 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 + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver 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 + */ + +/* * * * * * * * * * * * * * 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 +#include +#include +#include +#include +#include +#include "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); + regcache_sync(codec->core.regmap); + 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 = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + return err; + + err = auto_parse_beep(codec); + 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); + 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, 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 hda_nid_t conn_24[] = { 0x14, 0x1c }; + static 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..568575b72 --- /dev/null +++ b/sound/pci/hda/thinkpad_helper.c @@ -0,0 +1,80 @@ +// 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 +#include + +static int (*led_set_func)(int, bool); +static void (*old_vmaster_hook)(void *, int); + +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 update_tpacpi_mute_led(void *private_data, int enabled) +{ + if (old_vmaster_hook) + old_vmaster_hook(private_data, enabled); + + if (led_set_func) + led_set_func(TPACPI_LED_MUTE, !enabled); +} + +static void update_tpacpi_micmute(struct hda_codec *codec) +{ + struct hda_gen_spec *spec = codec->spec; + + led_set_func(TPACPI_LED_MICMUTE, spec->micmute_led.led_value); +} + +static void hda_fixup_thinkpad_acpi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct hda_gen_spec *spec = codec->spec; + bool removefunc = false; + + if (action == HDA_FIXUP_ACT_PROBE) { + if (!is_thinkpad(codec)) + return; + if (!led_set_func) + led_set_func = symbol_request(tpacpi_led_set); + if (!led_set_func) { + codec_warn(codec, + "Failed to find thinkpad-acpi symbol tpacpi_led_set\n"); + return; + } + + removefunc = true; + if (led_set_func(TPACPI_LED_MUTE, false) >= 0) { + old_vmaster_hook = spec->vmaster_mute.hook; + spec->vmaster_mute.hook = update_tpacpi_mute_led; + removefunc = false; + } + if (led_set_func(TPACPI_LED_MICMUTE, false) >= 0 && + !snd_hda_gen_add_micmute_led(codec, + update_tpacpi_micmute)) + removefunc = false; + } + + if (led_set_func && (action == HDA_FIXUP_ACT_FREE || removefunc)) { + symbol_put(tpacpi_led_set); + led_set_func = NULL; + old_vmaster_hook = 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 +# + +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..a553897a4 --- /dev/null +++ b/sound/pci/ice1712/ak4xxx.c @@ -0,0 +1,184 @@ +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * AK4524 / AK4528 / AK4529 / AK4355 / AK4381 interface + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ice1712.h" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ICEnsemble ICE17xx <-> AK4xxx AD/DA chip interface"); +MODULE_LICENSE("GPL"); + +static void snd_ice1712_akm4xxx_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_save_gpio_status(ice); +} + +static void snd_ice1712_akm4xxx_unlock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + + snd_ice1712_restore_gpio_status(ice); +} + +/* + * write AK4xxx register + */ +static void snd_ice1712_akm4xxx_write(struct snd_akm4xxx *ak, int chip, + unsigned char addr, unsigned char data) +{ + unsigned int tmp; + int idx; + unsigned int addrdata; + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + + if (snd_BUG_ON(chip < 0 || chip >= 4)) + return; + + tmp = snd_ice1712_gpio_read(ice); + tmp |= priv->add_flags; + tmp &= ~priv->mask_flags; + if (priv->cs_mask == priv->cs_addr) { + if (priv->cif) { + tmp |= priv->cs_mask; /* start without chip select */ + } else { + tmp &= ~priv->cs_mask; /* chip select low */ + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + } else { + /* doesn't handle cf=1 yet */ + tmp &= ~priv->cs_mask; + tmp |= priv->cs_addr; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + + /* build I2C address + data byte */ + addrdata = (priv->caddr << 6) | 0x20 | (addr & 0x1f); + addrdata = (addrdata << 8) | data; + for (idx = 15; idx >= 0; idx--) { + /* drop clock */ + tmp &= ~priv->clk_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + /* set data */ + if (addrdata & (1 << idx)) + tmp |= priv->data_mask; + else + tmp &= ~priv->data_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + /* raise clock */ + tmp |= priv->clk_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + + if (priv->cs_mask == priv->cs_addr) { + if (priv->cif) { + /* assert a cs pulse to trigger */ + tmp &= ~priv->cs_mask; + snd_ice1712_gpio_write(ice, tmp); + udelay(1); + } + tmp |= priv->cs_mask; /* chip select high to trigger */ + } else { + tmp &= ~priv->cs_mask; + tmp |= priv->cs_none; /* deselect address */ + } + snd_ice1712_gpio_write(ice, tmp); + udelay(1); +} + +/* + * initialize the struct snd_akm4xxx record with the template + */ +int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *temp, + const struct snd_ak4xxx_private *_priv, struct snd_ice1712 *ice) +{ + struct snd_ak4xxx_private *priv; + + if (_priv != NULL) { + priv = kmalloc(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..2f9b93467 --- /dev/null +++ b/sound/pci/ice1712/amp.c @@ -0,0 +1,97 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Advanced Micro Peripherals Ltd AUDIO2000 + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "amp.h" + +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + unsigned short cval; + cval = (reg << 9) | val; + snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff); +} + +static int snd_vt1724_amp_init(struct snd_ice1712 *ice) +{ + static const unsigned short wm_inits[] = { + WM_ATTEN_L, 0x0000, /* 0 db */ + WM_ATTEN_R, 0x0000, /* 0 db */ + WM_DAC_CTRL, 0x0008, /* 24bit I2S */ + WM_INT_CTRL, 0x0001, /* 24bit I2S */ + }; + + unsigned int i; + + /* only use basic functionality for now */ + + /* VT1616 6ch codec connected to PSDOUT0 using packed mode */ + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + + /* Chaintech AV-710 has another WM8728 codec connected to PSDOUT4 + (shared with the SPDIF output). Mixer control for this codec + is not yet supported. */ + if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AV710) { + for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2) + wm_put(ice, wm_inits[i], wm_inits[i+1]); + } + + return 0; +} + +static int snd_vt1724_amp_add_controls(struct snd_ice1712 *ice) +{ + if (ice->ac97) + /* we use pins 39 and 41 of the VT1616 for left and right + read outputs */ + snd_ac97_write_cache(ice->ac97, 0x5a, + snd_ac97_read(ice->ac97, 0x5a) & ~0x8000); + return 0; +} + + +/* entry point */ +struct snd_ice1712_card_info snd_vt1724_amp_cards[] = { + { + .subvendor = VT1724_SUBDEVICE_AV710, + .name = "Chaintech AV-710", + .model = "av710", + .chip_init = snd_vt1724_amp_init, + .build_controls = snd_vt1724_amp_add_controls, + }, + { + .subvendor = VT1724_SUBDEVICE_AUDIO2000, + .name = "AMP Ltd AUDIO2000", + .model = "amp2000", + .chip_init = snd_vt1724_amp_init, + .build_controls = snd_vt1724_amp_add_controls, + }, + { } /* terminator */ +}; + diff --git a/sound/pci/ice1712/amp.h b/sound/pci/ice1712/amp.h new file mode 100644 index 000000000..bf81d30d9 --- /dev/null +++ b/sound/pci/ice1712/amp.h @@ -0,0 +1,48 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..c9411dfff --- /dev/null +++ b/sound/pci/ice1712/aureon.c @@ -0,0 +1,2281 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Terratec Aureon cards + * + * Copyright (c) 2003 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * NOTES: + * + * - we reuse the struct snd_akm4xxx record for storing the wm8770 codec data. + * both wm and akm codecs are pretty similar, so we can integrate + * both controls in the future, once if wm codecs are reused in + * many boards. + * + * - DAC digital volumes are not implemented in the mixer. + * if they show better response than DAC analog volumes, we can use them + * instead. + * + * Lowlevel functions for AudioTrak Prodigy 7.1 (and possibly 192) cards + * Copyright (c) 2003 Dimitromanolakis Apostolos + * + * version 0.82: Stable / not all features work yet (no communication with AC97 secondary) + * added 64x/128x oversampling switch (should be 64x only for 96khz) + * fixed some recording labels (still need to check the rest) + * recording is working probably thanks to correct wm8770 initialization + * + * version 0.5: Initial release: + * working: analog output, mixer, headphone amplifier switch + * not working: prety much everything else, at least i could verify that + * we have no digital output, no capture, pretty bad clicks and poops + * on mixer switch and other coll stuff. + */ + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "aureon.h" +#include + +/* AC97 register cache for Aureon */ +struct aureon_spec { + unsigned short stac9744[64]; + unsigned int cs8415_mux; + unsigned short master[2]; + unsigned short vol[8]; + unsigned char pca9554_out; +}; + +/* WM8770 registers */ +#define WM_DAC_ATTEN 0x00 /* DAC1-8 analog attenuation */ +#define WM_DAC_MASTER_ATTEN 0x08 /* DAC master analog attenuation */ +#define WM_DAC_DIG_ATTEN 0x09 /* DAC1-8 digital attenuation */ +#define WM_DAC_DIG_MASTER_ATTEN 0x11 /* DAC master digital attenuation */ +#define WM_PHASE_SWAP 0x12 /* DAC phase */ +#define WM_DAC_CTRL1 0x13 /* DAC control bits */ +#define WM_MUTE 0x14 /* mute controls */ +#define WM_DAC_CTRL2 0x15 /* de-emphasis and zefo-flag */ +#define WM_INT_CTRL 0x16 /* interface control */ +#define WM_MASTER 0x17 /* master clock and mode */ +#define WM_POWERDOWN 0x18 /* power-down controls */ +#define WM_ADC_GAIN 0x19 /* ADC gain L(19)/R(1a) */ +#define WM_ADC_MUX 0x1b /* input MUX */ +#define WM_OUT_MUX1 0x1c /* output MUX */ +#define WM_OUT_MUX2 0x1e /* output MUX */ +#define WM_RESET 0x1f /* software reset */ + +/* CS8415A registers */ +#define CS8415_CTRL1 0x01 +#define CS8415_CTRL2 0x02 +#define CS8415_QSUB 0x14 +#define CS8415_RATIO 0x1E +#define CS8415_C_BUFFER 0x20 +#define CS8415_ID 0x7F + +/* PCA9554 registers */ +#define PCA9554_DEV 0x40 /* I2C device address */ +#define PCA9554_IN 0x00 /* input port */ +#define PCA9554_OUT 0x01 /* output port */ +#define PCA9554_INVERT 0x02 /* input invert */ +#define PCA9554_DIR 0x03 /* port directions */ + +/* + * Aureon Universe additional controls using PCA9554 + */ + +/* + * Send data to pca9554 + */ +static void aureon_pca9554_write(struct snd_ice1712 *ice, unsigned char reg, + unsigned char data) +{ + unsigned int tmp; + int i, j; + unsigned char dev = PCA9554_DEV; /* ID 0100000, write */ + unsigned char val = 0; + + tmp = snd_ice1712_gpio_read(ice); + + snd_ice1712_gpio_set_mask(ice, ~(AUREON_SPI_MOSI|AUREON_SPI_CLK| + AUREON_WM_RW|AUREON_WM_CS| + AUREON_CS8415_CS)); + tmp |= AUREON_WM_RW; + tmp |= AUREON_CS8415_CS | AUREON_WM_CS; /* disable SPI devices */ + + tmp &= ~AUREON_SPI_MOSI; + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + + /* + * send i2c stop condition and start condition + * to obtain sane state + */ + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp |= AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); + /* + * send device address, command and value, + * skipping ack cycles in between + */ + for (j = 0; j < 3; j++) { + switch (j) { + case 0: + val = dev; + break; + case 1: + val = reg; + break; + case 2: + val = data; + break; + } + for (i = 7; i >= 0; i--) { + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + if (val & (1 << i)) + tmp |= AUREON_SPI_MOSI; + else + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + } + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + } + tmp &= ~AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp &= ~AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(40); + tmp |= AUREON_SPI_CLK; + snd_ice1712_gpio_write(ice, tmp); + udelay(50); + tmp |= AUREON_SPI_MOSI; + snd_ice1712_gpio_write(ice, tmp); + udelay(100); +} + +static int aureon_universe_inmux_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = + {"Internal Aux", "Wavetable", "Rear Line-In"}; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int aureon_universe_inmux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + ucontrol->value.enumerated.item[0] = spec->pca9554_out; + return 0; +} + +static int aureon_universe_inmux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct aureon_spec *spec = ice->spec; + unsigned char oval, nval; + int change; + + nval = ucontrol->value.enumerated.item[0]; + if (nval >= 3) + return -EINVAL; + snd_ice1712_save_gpio_status(ice); + oval = spec->pca9554_out; + change = (oval != nval); + if (change) { + aureon_pca9554_write(ice, PCA9554_OUT, nval); + spec->pca9554_out = nval; + } + snd_ice1712_restore_gpio_status(ice); + return change; +} + + +static void aureon_ac97_write(struct snd_ice1712 *ice, unsigned short reg, + unsigned short val) +{ + struct aureon_spec *spec = ice->spec; + unsigned int tmp; + + /* Send address to XILINX chip */ + tmp = (snd_ice1712_gpio_read(ice) & ~0xFF) | (reg & 0x7F); + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp |= AUREON_AC97_ADDR; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_ADDR; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Send low-order byte to XILINX chip */ + tmp &= ~AUREON_AC97_DATA_MASK; + tmp |= val & AUREON_AC97_DATA_MASK; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp |= AUREON_AC97_DATA_LOW; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_DATA_LOW; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Send high-order byte to XILINX chip */ + tmp &= ~AUREON_AC97_DATA_MASK; + tmp |= (val >> 8) & AUREON_AC97_DATA_MASK; + + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp |= AUREON_AC97_DATA_HIGH; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_DATA_HIGH; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Instruct XILINX chip to parse the data to the STAC9744 chip */ + tmp |= AUREON_AC97_COMMIT; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + tmp &= ~AUREON_AC97_COMMIT; + snd_ice1712_gpio_write(ice, tmp); + udelay(10); + + /* Store the data in out private buffer */ + spec->stac9744[(reg & 0x7F) >> 1] = val; +} + +static unsigned short aureon_ac97_read(struct snd_ice1712 *ice, unsigned short reg) +{ + struct aureon_spec *spec = ice->spec; + return spec->stac9744[(reg & 0x7F) >> 1]; +} + +/* + * Initialize STAC9744 chip + */ +static int aureon_ac97_init(struct snd_ice1712 *ice) +{ + struct aureon_spec *spec = ice->spec; + int i; + static const unsigned short ac97_defaults[] = { + 0x00, 0x9640, + 0x02, 0x8000, + 0x04, 0x8000, + 0x06, 0x8000, + 0x0C, 0x8008, + 0x0E, 0x8008, + 0x10, 0x8808, + 0x12, 0x8808, + 0x14, 0x8808, + 0x16, 0x8808, + 0x18, 0x8808, + 0x1C, 0x8000, + 0x26, 0x000F, + 0x28, 0x0201, + 0x2C, 0xBB80, + 0x32, 0xBB80, + 0x7C, 0x8384, + 0x7E, 0x7644, + (unsigned short)-1 + }; + unsigned int tmp; + + /* Cold reset */ + tmp = (snd_ice1712_gpio_read(ice) | AUREON_AC97_RESET) & ~AUREON_AC97_DATA_MASK; + snd_ice1712_gpio_write(ice, tmp); + udelay(3); + + tmp &= ~AUREON_AC97_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(3); + + tmp |= AUREON_AC97_RESET; + snd_ice1712_gpio_write(ice, tmp); + udelay(3); + + memset(&spec->stac9744, 0, sizeof(spec->stac9744)); + for (i = 0; ac97_defaults[i] != (unsigned short)-1; i += 2) + spec->stac9744[(ac97_defaults[i]) >> 1] = ac97_defaults[i+1]; + + /* Unmute AC'97 master volume permanently - muting is done by WM8770 */ + aureon_ac97_write(ice, AC97_MASTER, 0x0000); + + return 0; +} + +#define AUREON_AC97_STEREO 0x80 + +/* + * AC'97 volume controls + */ +static int aureon_ac97_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = kcontrol->private_value & AUREON_AC97_STEREO ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 31; + return 0; +} + +static int aureon_ac97_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short vol; + + 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 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 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 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 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 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); + 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; + } + } + snd_ice1712_restore_gpio_status(ice); + } + + 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 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 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 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 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..c253b8e2c --- /dev/null +++ b/sound/pci/ice1712/aureon.h @@ -0,0 +1,65 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..6808bed01 --- /dev/null +++ b/sound/pci/ice1712/delta.c @@ -0,0 +1,934 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ice1712.h" +#include "delta.h" + +#define SND_CS8403 +#include + + +/* + * CS8427 via SPI mode (for Audiophile), emulated I2C + */ + +/* send 8 bits */ +static void ap_cs8427_write_byte(struct snd_ice1712 *ice, unsigned char data, unsigned char tmp) +{ + int idx; + + for (idx = 7; idx >= 0; idx--) { + tmp &= ~(ICE1712_DELTA_AP_DOUT|ICE1712_DELTA_AP_CCLK); + if (data & (1 << idx)) + tmp |= ICE1712_DELTA_AP_DOUT; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + tmp |= ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + } +} + +/* read 8 bits */ +static unsigned char ap_cs8427_read_byte(struct snd_ice1712 *ice, unsigned char tmp) +{ + unsigned char data = 0; + int idx; + + for (idx = 7; idx >= 0; idx--) { + tmp &= ~ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + if (snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_DELTA_AP_DIN) + data |= 1 << idx; + tmp |= ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + } + return data; +} + +/* assert chip select */ +static unsigned char ap_cs8427_codec_select(struct snd_ice1712 *ice) +{ + unsigned char tmp; + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + tmp &= ~ICE1712_DELTA_1010LT_CS; + tmp |= ICE1712_DELTA_1010LT_CCLK | ICE1712_DELTA_1010LT_CS_CS8427; + break; + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + tmp |= ICE1712_DELTA_AP_CCLK | ICE1712_DELTA_AP_CS_CODEC; + tmp &= ~ICE1712_DELTA_AP_CS_DIGITAL; + break; + case ICE1712_SUBDEVICE_DELTA66E: + tmp |= ICE1712_DELTA_66E_CCLK | ICE1712_DELTA_66E_CS_CHIP_A | + ICE1712_DELTA_66E_CS_CHIP_B; + tmp &= ~ICE1712_DELTA_66E_CS_CS8427; + break; + case ICE1712_SUBDEVICE_VX442: + tmp |= ICE1712_VX442_CCLK | ICE1712_VX442_CODEC_CHIP_A | ICE1712_VX442_CODEC_CHIP_B; + tmp &= ~ICE1712_VX442_CS_DIGITAL; + break; + } + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + return tmp; +} + +/* deassert chip select */ +static void ap_cs8427_codec_deassert(struct snd_ice1712 *ice, unsigned char tmp) +{ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + tmp &= ~ICE1712_DELTA_1010LT_CS; + tmp |= ICE1712_DELTA_1010LT_CS_NONE; + break; + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + tmp |= ICE1712_DELTA_AP_CS_DIGITAL; + break; + case ICE1712_SUBDEVICE_DELTA66E: + tmp |= ICE1712_DELTA_66E_CS_CS8427; + break; + case ICE1712_SUBDEVICE_VX442: + tmp |= ICE1712_VX442_CS_DIGITAL; + break; + } + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); +} + +/* sequential write */ +static int ap_cs8427_sendbytes(struct snd_i2c_device *device, unsigned char *bytes, int count) +{ + struct snd_ice1712 *ice = device->bus->private_data; + int res = count; + unsigned char tmp; + + 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, ®, 1) != 1) + dev_err(ice->card->dev, + "unable to send register 0x%x byte to CS8427\n", reg); + snd_i2c_readbytes(ice->cs8427, ®, 1); + ucontrol->value.integer.value[0] = (reg & CS8427_UNLOCK) ? 1 : 0; + return 0; +} + +static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status = +{ + .access = (SNDRV_CTL_ELEM_ACCESS_READ), + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Word Clock Status", + .info = snd_ice1712_delta1010lt_wordclock_status_info, + .get = snd_ice1712_delta1010lt_wordclock_status_get, +}; + +/* + * initialize the chips on M-Audio cards + */ + +static const struct snd_akm4xxx akm_audiophile = { + .type = SND_AK4528, + .num_adcs = 2, + .num_dacs = 2, + .ops = { + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_audiophile_priv = { + .caddr = 2, + .cif = 0, + .data_mask = ICE1712_DELTA_AP_DOUT, + .clk_mask = ICE1712_DELTA_AP_CCLK, + .cs_mask = ICE1712_DELTA_AP_CS_CODEC, + .cs_addr = ICE1712_DELTA_AP_CS_CODEC, + .cs_none = 0, + .add_flags = ICE1712_DELTA_AP_CS_DIGITAL, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_delta410 = { + .type = SND_AK4529, + .num_adcs = 2, + .num_dacs = 8, + .ops = { + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta410_priv = { + .caddr = 0, + .cif = 0, + .data_mask = ICE1712_DELTA_AP_DOUT, + .clk_mask = ICE1712_DELTA_AP_CCLK, + .cs_mask = ICE1712_DELTA_AP_CS_CODEC, + .cs_addr = ICE1712_DELTA_AP_CS_CODEC, + .cs_none = 0, + .add_flags = ICE1712_DELTA_AP_CS_DIGITAL, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_delta1010lt = { + .type = SND_AK4524, + .num_adcs = 8, + .num_dacs = 8, + .ops = { + .lock = delta1010lt_ak4524_lock, + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta1010lt_priv = { + .caddr = 2, + .cif = 0, /* the default level of the CIF pin from AK4524 */ + .data_mask = ICE1712_DELTA_1010LT_DOUT, + .clk_mask = ICE1712_DELTA_1010LT_CCLK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = ICE1712_DELTA_1010LT_CS_NONE, + .add_flags = 0, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_delta66e = { + .type = SND_AK4524, + .num_adcs = 4, + .num_dacs = 4, + .ops = { + .lock = delta66e_ak4524_lock, + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta66e_priv = { + .caddr = 2, + .cif = 0, /* the default level of the CIF pin from AK4524 */ + .data_mask = ICE1712_DELTA_66E_DOUT, + .clk_mask = ICE1712_DELTA_66E_CCLK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = 0, + .add_flags = 0, + .mask_flags = 0, +}; + + +static const struct snd_akm4xxx akm_delta44 = { + .type = SND_AK4524, + .num_adcs = 4, + .num_dacs = 4, + .ops = { + .lock = delta_ak4524_lock, + .set_rate_val = delta_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_delta44_priv = { + .caddr = 2, + .cif = 0, /* the default level of the CIF pin from AK4524 */ + .data_mask = ICE1712_DELTA_CODEC_SERIAL_DATA, + .clk_mask = ICE1712_DELTA_CODEC_SERIAL_CLOCK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = 0, + .add_flags = 0, + .mask_flags = 0, +}; + +static const struct snd_akm4xxx akm_vx442 = { + .type = SND_AK4524, + .num_adcs = 4, + .num_dacs = 4, + .ops = { + .lock = vx442_ak4524_lock, + .set_rate_val = vx442_ak4524_set_rate_val + } +}; + +static const struct snd_ak4xxx_private akm_vx442_priv = { + .caddr = 2, + .cif = 0, + .data_mask = ICE1712_VX442_DOUT, + .clk_mask = ICE1712_VX442_CCLK, + .cs_mask = 0, + .cs_addr = 0, /* set later */ + .cs_none = 0, + .add_flags = 0, + .mask_flags = 0, +}; + +#ifdef CONFIG_PM_SLEEP +static int snd_ice1712_delta_resume(struct snd_ice1712 *ice) +{ + unsigned char akm_img_bak[AK4XXX_IMAGE_SIZE]; + unsigned char akm_vol_bak[AK4XXX_IMAGE_SIZE]; + + /* init spdif */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_VX442: + case ICE1712_SUBDEVICE_DELTA66E: + snd_cs8427_init(ice->i2c, ice->cs8427); + break; + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_MEDIASTATION: + /* nothing */ + break; + case ICE1712_SUBDEVICE_DELTADIO2496: + case ICE1712_SUBDEVICE_DELTA66: + /* Set spdif defaults */ + snd_ice1712_delta_cs8403_spdif_write(ice, ice->spdif.cs8403_bits); + break; + } + + /* init codec and restore registers */ + if (ice->akm_codecs) { + memcpy(akm_img_bak, ice->akm->images, sizeof(akm_img_bak)); + memcpy(akm_vol_bak, ice->akm->volumes, sizeof(akm_vol_bak)); + snd_akm4xxx_init(ice->akm); + memcpy(ice->akm->images, akm_img_bak, sizeof(akm_img_bak)); + memcpy(ice->akm->volumes, akm_vol_bak, sizeof(akm_vol_bak)); + snd_akm4xxx_reset(ice->akm, 0); + } + + return 0; +} + +static int snd_ice1712_delta_suspend(struct snd_ice1712 *ice) +{ + if (ice->akm_codecs) /* reset & mute codec */ + snd_akm4xxx_reset(ice->akm, 1); + + return 0; +} +#endif + +static int snd_ice1712_delta_init(struct snd_ice1712 *ice) +{ + int err; + struct snd_akm4xxx *ak; + unsigned char tmp; + + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA1010 && + ice->eeprom.gpiodir == 0x7b) + ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA1010E; + + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA66 && + ice->eeprom.gpiodir == 0xfb) + ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA66E; + + /* determine I2C, DACs and ADCs */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_AUDIOPHILE: + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + break; + case ICE1712_SUBDEVICE_DELTA410: + ice->num_total_dacs = 8; + ice->num_total_adcs = 2; + break; + case ICE1712_SUBDEVICE_DELTA44: + case ICE1712_SUBDEVICE_DELTA66: + ice->num_total_dacs = ice->omni ? 8 : 4; + ice->num_total_adcs = ice->omni ? 8 : 4; + break; + case ICE1712_SUBDEVICE_DELTA1010: + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_MEDIASTATION: + case ICE1712_SUBDEVICE_EDIROLDA2496: + ice->num_total_dacs = 8; + ice->num_total_adcs = 8; + break; + case ICE1712_SUBDEVICE_DELTADIO2496: + ice->num_total_dacs = 4; /* two AK4324 codecs */ + break; + case ICE1712_SUBDEVICE_VX442: + case ICE1712_SUBDEVICE_DELTA66E: /* omni not supported yet */ + ice->num_total_dacs = 4; + ice->num_total_adcs = 4; + break; + } +#ifdef CONFIG_PM_SLEEP + ice->pm_resume = snd_ice1712_delta_resume; + ice->pm_suspend = snd_ice1712_delta_suspend; + ice->pm_suspend_enabled = 1; +#endif + /* initialize the SPI clock to high */ + tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA); + tmp |= ICE1712_DELTA_AP_CCLK; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); + + /* initialize spdif */ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_AUDIOPHILE: + case ICE1712_SUBDEVICE_DELTA410: + case ICE1712_SUBDEVICE_DELTA1010E: + case ICE1712_SUBDEVICE_DELTA1010LT: + case ICE1712_SUBDEVICE_VX442: + case ICE1712_SUBDEVICE_DELTA66E: + 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; + /* fall thru */ + 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 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 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 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 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 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..11a9c3a76 --- /dev/null +++ b/sound/pci/ice1712/delta.h @@ -0,0 +1,166 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define DELTA_DEVICE_DESC \ + "{MidiMan M Audio,Delta 1010},"\ + "{MidiMan M Audio,Delta 1010LT},"\ + "{MidiMan M Audio,Delta DiO 2496},"\ + "{MidiMan M Audio,Delta 66},"\ + "{MidiMan M Audio,Delta 44},"\ + "{MidiMan M Audio,Delta 410},"\ + "{MidiMan M Audio,Audiophile 24/96},"\ + "{Digigram,VX442},"\ + "{Lionstracs,Mediastation},"\ + "{Edirol,DA2496}," + +#define ICE1712_SUBDEVICE_DELTA1010 0x121430d6 +#define ICE1712_SUBDEVICE_DELTA1010E 0xff1430d6 +#define ICE1712_SUBDEVICE_DELTADIO2496 0x121431d6 +#define ICE1712_SUBDEVICE_DELTA66 0x121432d6 +#define ICE1712_SUBDEVICE_DELTA66E 0xff1432d6 +#define ICE1712_SUBDEVICE_DELTA44 0x121433d6 +#define ICE1712_SUBDEVICE_AUDIOPHILE 0x121434d6 +#define ICE1712_SUBDEVICE_DELTA410 0x121438d6 +#define ICE1712_SUBDEVICE_DELTA1010LT 0x12143bd6 +#define ICE1712_SUBDEVICE_VX442 0x12143cd6 +#define ICE1712_SUBDEVICE_MEDIASTATION 0x694c0100 +#define ICE1712_SUBDEVICE_EDIROLDA2496 0xce164010 + +/* entry point */ +extern struct snd_ice1712_card_info snd_ice1712_delta_cards[]; + + +/* + * MidiMan M-Audio Delta GPIO definitions + */ + +/* MidiMan M-Audio Delta shared pins */ +#define ICE1712_DELTA_DFS 0x01 /* fast/slow sample rate mode */ + /* (>48kHz must be 1) */ +#define ICE1712_DELTA_SPDIF_IN_STAT 0x02 + /* S/PDIF input status */ + /* 0 = valid signal is present */ + /* all except Delta44 */ + /* look to CS8414 datasheet */ +#define ICE1712_DELTA_SPDIF_OUT_STAT_CLOCK 0x04 + /* S/PDIF output status clock */ + /* (writing on rising edge - 0->1) */ + /* all except Delta44 */ + /* look to CS8404A datasheet */ +#define ICE1712_DELTA_SPDIF_OUT_STAT_DATA 0x08 + /* S/PDIF output status data */ + /* all except Delta44 */ + /* look to CS8404A datasheet */ +/* MidiMan M-Audio DeltaDiO */ +/* 0x01 = DFS */ +/* 0x02 = SPDIF_IN_STAT */ +/* 0x04 = SPDIF_OUT_STAT_CLOCK */ +/* 0x08 = SPDIF_OUT_STAT_DATA */ +#define ICE1712_DELTA_SPDIF_INPUT_SELECT 0x10 + /* coaxial (0), optical (1) */ + /* S/PDIF input select*/ + +/* MidiMan M-Audio Delta1010 */ +/* 0x01 = DFS */ +/* 0x02 = SPDIF_IN_STAT */ +/* 0x04 = SPDIF_OUT_STAT_CLOCK */ +/* 0x08 = SPDIF_OUT_STAT_DATA */ +#define ICE1712_DELTA_WORD_CLOCK_SELECT 0x10 + /* 1 - clock are taken from S/PDIF input */ + /* 0 - clock are taken from Word Clock input */ + /* affected SPMCLKIN pin of Envy24 */ +#define ICE1712_DELTA_WORD_CLOCK_STATUS 0x20 + /* 0 = valid word clock signal is present */ + +/* MidiMan M-Audio Delta66 */ +/* 0x01 = DFS */ +/* 0x02 = SPDIF_IN_STAT */ +/* 0x04 = SPDIF_OUT_STAT_CLOCK */ +/* 0x08 = SPDIF_OUT_STAT_DATA */ +#define ICE1712_DELTA_CODEC_SERIAL_DATA 0x10 + /* AKM4524 serial data */ +#define ICE1712_DELTA_CODEC_SERIAL_CLOCK 0x20 + /* AKM4524 serial clock */ + /* (writing on rising edge - 0->1 */ +#define ICE1712_DELTA_CODEC_CHIP_A 0x40 +#define ICE1712_DELTA_CODEC_CHIP_B 0x80 + /* 1 - select chip A or B */ + +/* MidiMan M-Audio Delta44 */ +/* 0x01 = DFS */ +/* 0x10 = CODEC_SERIAL_DATA */ +/* 0x20 = CODEC_SERIAL_CLOCK */ +/* 0x40 = CODEC_CHIP_A */ +/* 0x80 = CODEC_CHIP_B */ + +/* MidiMan M-Audio Audiophile/Delta410 definitions */ +/* thanks to Kristof Pelckmans for Delta410 info */ +/* 0x01 = DFS */ +#define ICE1712_DELTA_AP_CCLK 0x02 /* SPI clock */ + /* (clocking on rising edge - 0->1) */ +#define ICE1712_DELTA_AP_DIN 0x04 /* data input */ +#define ICE1712_DELTA_AP_DOUT 0x08 /* data output */ +#define ICE1712_DELTA_AP_CS_DIGITAL 0x10 /* CS8427 chip select */ + /* low signal = select */ +#define ICE1712_DELTA_AP_CS_CODEC 0x20 /* AK4528 (audiophile), AK4529 (Delta410) chip select */ + /* low signal = select */ + +/* MidiMan M-Audio Delta1010LT definitions */ +/* thanks to Anders Johansson */ +/* 0x01 = DFS */ +#define ICE1712_DELTA_1010LT_CCLK 0x02 /* SPI clock (AK4524 + CS8427) */ +#define ICE1712_DELTA_1010LT_DIN 0x04 /* data input (CS8427) */ +#define ICE1712_DELTA_1010LT_DOUT 0x08 /* data output (AK4524 + CS8427) */ +#define ICE1712_DELTA_1010LT_CS 0x70 /* mask for CS address */ +#define ICE1712_DELTA_1010LT_CS_CHIP_A 0x00 /* AK4524 #0 */ +#define ICE1712_DELTA_1010LT_CS_CHIP_B 0x10 /* AK4524 #1 */ +#define ICE1712_DELTA_1010LT_CS_CHIP_C 0x20 /* AK4524 #2 */ +#define ICE1712_DELTA_1010LT_CS_CHIP_D 0x30 /* AK4524 #3 */ +#define ICE1712_DELTA_1010LT_CS_CS8427 0x40 /* CS8427 */ +#define ICE1712_DELTA_1010LT_CS_NONE 0x50 /* nothing */ +#define ICE1712_DELTA_1010LT_WORDCLOCK 0x80 /* sample clock source: 0 = Word Clock Input, 1 = S/PDIF Input ??? */ + +/* M-Audio Delta 66 rev. E definitions. + * Newer revisions of Delta 66 have CS8427 over SPI for + * S/PDIF transceiver instead of CS8404/CS8414. */ +/* 0x01 = DFS */ +#define ICE1712_DELTA_66E_CCLK 0x02 /* SPI clock */ +#define ICE1712_DELTA_66E_DIN 0x04 /* data input */ +#define ICE1712_DELTA_66E_DOUT 0x08 /* data output */ +#define ICE1712_DELTA_66E_CS_CS8427 0x10 /* chip select, low = CS8427 */ +#define ICE1712_DELTA_66E_CS_CHIP_A 0x20 /* AK4524 #0 */ +#define ICE1712_DELTA_66E_CS_CHIP_B 0x40 /* AK4524 #1 */ + +/* Digigram VX442 definitions */ +#define ICE1712_VX442_CCLK 0x02 /* SPI clock */ +#define ICE1712_VX442_DIN 0x04 /* data input */ +#define ICE1712_VX442_DOUT 0x08 /* data output */ +#define ICE1712_VX442_CS_DIGITAL 0x10 /* chip select, low = CS8427 */ +#define ICE1712_VX442_CODEC_CHIP_A 0x20 /* select chip A */ +#define ICE1712_VX442_CODEC_CHIP_B 0x40 /* select chip B */ + +#endif /* __SOUND_DELTA_H */ diff --git a/sound/pci/ice1712/envy24ht.h b/sound/pci/ice1712/envy24ht.h new file mode 100644 index 000000000..4ca33a800 --- /dev/null +++ b/sound/pci/ice1712/envy24ht.h @@ -0,0 +1,220 @@ +#ifndef __SOUND_VT1724_H +#define __SOUND_VT1724_H + +/* + * ALSA driver for ICEnsemble VT1724 (Envy24) + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include "ice1712.h" + +enum { + ICE_EEP2_SYSCONF = 0, /* 06 */ + ICE_EEP2_ACLINK, /* 07 */ + ICE_EEP2_I2S, /* 08 */ + ICE_EEP2_SPDIF, /* 09 */ + ICE_EEP2_GPIO_DIR, /* 0a */ + ICE_EEP2_GPIO_DIR1, /* 0b */ + ICE_EEP2_GPIO_DIR2, /* 0c */ + ICE_EEP2_GPIO_MASK, /* 0d */ + ICE_EEP2_GPIO_MASK1, /* 0e */ + ICE_EEP2_GPIO_MASK2, /* 0f */ + ICE_EEP2_GPIO_STATE, /* 10 */ + ICE_EEP2_GPIO_STATE1, /* 11 */ + ICE_EEP2_GPIO_STATE2 /* 12 */ +}; + +/* + * Direct registers + */ + +#define ICEREG1724(ice, x) ((ice)->port + VT1724_REG_##x) + +#define VT1724_REG_CONTROL 0x00 /* byte */ +#define VT1724_RESET 0x80 /* reset whole chip */ +#define VT1724_REG_IRQMASK 0x01 /* byte */ +#define VT1724_IRQ_MPU_RX 0x80 +#define VT1724_IRQ_MPU_TX 0x20 +#define VT1724_IRQ_MTPCM 0x10 +#define VT1724_REG_IRQSTAT 0x02 /* byte */ +/* look to VT1724_IRQ_* */ +#define VT1724_REG_SYS_CFG 0x04 /* byte - system configuration PCI60 on Envy24*/ +#define VT1724_CFG_CLOCK 0xc0 +#define VT1724_CFG_CLOCK512 0x00 /* 22.5692Mhz, 44.1kHz*512 */ +#define VT1724_CFG_CLOCK384 0x40 /* 16.9344Mhz, 44.1kHz*384 */ +#define VT1724_CFG_MPU401 0x20 /* MPU401 UARTs */ +#define VT1724_CFG_ADC_MASK 0x0c /* one, two or one and S/PDIF, stereo ADCs */ +#define VT1724_CFG_ADC_NONE 0x0c /* no ADCs */ +#define VT1724_CFG_DAC_MASK 0x03 /* one, two, three, four stereo DACs */ + +#define VT1724_REG_AC97_CFG 0x05 /* byte */ +#define VT1724_CFG_PRO_I2S 0x80 /* multitrack converter: I2S or AC'97 */ +#define VT1724_CFG_AC97_PACKED 0x01 /* split or packed mode - AC'97 */ + +#define VT1724_REG_I2S_FEATURES 0x06 /* byte */ +#define VT1724_CFG_I2S_VOLUME 0x80 /* volume/mute capability */ +#define VT1724_CFG_I2S_96KHZ 0x40 /* supports 96kHz sampling */ +#define VT1724_CFG_I2S_RESMASK 0x30 /* resolution mask, 16,18,20,24-bit */ +#define VT1724_CFG_I2S_192KHZ 0x08 /* supports 192kHz sampling */ +#define VT1724_CFG_I2S_OTHER 0x07 /* other I2S IDs */ + +#define VT1724_REG_SPDIF_CFG 0x07 /* byte */ +#define VT1724_CFG_SPDIF_OUT_EN 0x80 /*Internal S/PDIF output is enabled*/ +#define VT1724_CFG_SPDIF_OUT_INT 0x40 /*Internal S/PDIF output is implemented*/ +#define VT1724_CFG_I2S_CHIPID 0x3c /* I2S chip ID */ +#define VT1724_CFG_SPDIF_IN 0x02 /* S/PDIF input is present */ +#define VT1724_CFG_SPDIF_OUT 0x01 /* External S/PDIF output is present */ + +/*there is no consumer AC97 codec with the VT1724*/ +//#define VT1724_REG_AC97_INDEX 0x08 /* byte */ +//#define VT1724_REG_AC97_CMD 0x09 /* byte */ + +#define VT1724_REG_MPU_TXFIFO 0x0a /*byte ro. number of bytes in TX fifo*/ +#define VT1724_REG_MPU_RXFIFO 0x0b /*byte ro. number of bytes in RX fifo*/ + +#define VT1724_REG_MPU_DATA 0x0c /* byte */ +#define VT1724_REG_MPU_CTRL 0x0d /* byte */ +#define VT1724_MPU_UART 0x01 +#define VT1724_MPU_TX_EMPTY 0x02 +#define VT1724_MPU_TX_FULL 0x04 +#define VT1724_MPU_RX_EMPTY 0x08 +#define VT1724_MPU_RX_FULL 0x10 + +#define VT1724_REG_MPU_FIFO_WM 0x0e /*byte set the high/low watermarks for RX/TX fifos*/ +#define VT1724_MPU_RX_FIFO 0x20 //1=rx fifo watermark 0=tx fifo watermark +#define VT1724_MPU_FIFO_MASK 0x1f + +#define VT1724_REG_I2C_DEV_ADDR 0x10 /* byte */ +#define VT1724_I2C_WRITE 0x01 /* write direction */ +#define VT1724_REG_I2C_BYTE_ADDR 0x11 /* byte */ +#define VT1724_REG_I2C_DATA 0x12 /* byte */ +#define VT1724_REG_I2C_CTRL 0x13 /* byte */ +#define VT1724_I2C_EEPROM 0x80 /* 1 = EEPROM exists */ +#define VT1724_I2C_BUSY 0x01 /* busy bit */ + +#define VT1724_REG_GPIO_DATA 0x14 /* word */ +#define VT1724_REG_GPIO_WRITE_MASK 0x16 /* word */ +#define VT1724_REG_GPIO_DIRECTION 0x18 /* dword? (3 bytes) 0=input 1=output. + bit3 - during reset used for Eeprom power-on strapping + if TESTEN# pin active, bit 2 always input*/ +#define VT1724_REG_POWERDOWN 0x1c +#define VT1724_REG_GPIO_DATA_22 0x1e /* byte direction for GPIO 16:22 */ +#define VT1724_REG_GPIO_WRITE_MASK_22 0x1f /* byte write mask for GPIO 16:22 */ + + +/* + * Professional multi-track direct control registers + */ + +#define ICEMT1724(ice, x) ((ice)->profi_port + VT1724_MT_##x) + +#define VT1724_MT_IRQ 0x00 /* byte - interrupt mask */ +#define VT1724_MULTI_PDMA4 0x80 /* SPDIF Out / PDMA4 */ +#define VT1724_MULTI_PDMA3 0x40 /* PDMA3 */ +#define VT1724_MULTI_PDMA2 0x20 /* PDMA2 */ +#define VT1724_MULTI_PDMA1 0x10 /* PDMA1 */ +#define VT1724_MULTI_FIFO_ERR 0x08 /* DMA FIFO underrun/overrun. */ +#define VT1724_MULTI_RDMA1 0x04 /* RDMA1 (S/PDIF input) */ +#define VT1724_MULTI_RDMA0 0x02 /* RMDA0 */ +#define VT1724_MULTI_PDMA0 0x01 /* MC Interleave/PDMA0 */ + +#define VT1724_MT_RATE 0x01 /* byte - sampling rate select */ +#define VT1724_SPDIF_MASTER 0x10 /* S/PDIF input is master clock */ +#define VT1724_MT_I2S_FORMAT 0x02 /* byte - I2S data format */ +#define VT1724_MT_I2S_MCLK_128X 0x08 +#define VT1724_MT_I2S_FORMAT_MASK 0x03 +#define VT1724_MT_I2S_FORMAT_I2S 0x00 +#define VT1724_MT_DMA_INT_MASK 0x03 /* byte -DMA Interrupt Mask */ +/* lool to VT1724_MULTI_* */ +#define VT1724_MT_AC97_INDEX 0x04 /* byte - AC'97 index */ +#define VT1724_MT_AC97_CMD 0x05 /* byte - AC'97 command & status */ +#define VT1724_AC97_COLD 0x80 /* cold reset */ +#define VT1724_AC97_WARM 0x40 /* warm reset */ +#define VT1724_AC97_WRITE 0x20 /* W: write, R: write in progress */ +#define VT1724_AC97_READ 0x10 /* W: read, R: read in progress */ +#define VT1724_AC97_READY 0x08 /* codec ready status bit */ +#define VT1724_AC97_ID_MASK 0x03 /* codec id mask */ +#define VT1724_MT_AC97_DATA 0x06 /* word - AC'97 data */ +#define VT1724_MT_PLAYBACK_ADDR 0x10 /* dword - playback address */ +#define VT1724_MT_PLAYBACK_SIZE 0x14 /* dword - playback size */ +#define VT1724_MT_DMA_CONTROL 0x18 /* byte - control */ +#define VT1724_PDMA4_START 0x80 /* SPDIF out / PDMA4 start */ +#define VT1724_PDMA3_START 0x40 /* PDMA3 start */ +#define VT1724_PDMA2_START 0x20 /* PDMA2 start */ +#define VT1724_PDMA1_START 0x10 /* PDMA1 start */ +#define VT1724_RDMA1_START 0x04 /* RDMA1 start */ +#define VT1724_RDMA0_START 0x02 /* RMDA0 start */ +#define VT1724_PDMA0_START 0x01 /* MC Interleave / PDMA0 start */ +#define VT1724_MT_BURST 0x19 /* Interleaved playback DMA Active streams / PCI burst size */ +#define VT1724_MT_DMA_FIFO_ERR 0x1a /*Global playback and record DMA FIFO Underrun/Overrun */ +#define VT1724_PDMA4_UNDERRUN 0x80 +#define VT1724_PDMA2_UNDERRUN 0x40 +#define VT1724_PDMA3_UNDERRUN 0x20 +#define VT1724_PDMA1_UNDERRUN 0x10 +#define VT1724_RDMA1_UNDERRUN 0x04 +#define VT1724_RDMA0_UNDERRUN 0x02 +#define VT1724_PDMA0_UNDERRUN 0x01 +#define VT1724_MT_DMA_PAUSE 0x1b /*Global playback and record DMA FIFO pause/resume */ +#define VT1724_PDMA4_PAUSE 0x80 +#define VT1724_PDMA3_PAUSE 0x40 +#define VT1724_PDMA2_PAUSE 0x20 +#define VT1724_PDMA1_PAUSE 0x10 +#define VT1724_RDMA1_PAUSE 0x04 +#define VT1724_RDMA0_PAUSE 0x02 +#define VT1724_PDMA0_PAUSE 0x01 +#define VT1724_MT_PLAYBACK_COUNT 0x1c /* word - playback count */ +#define VT1724_MT_CAPTURE_ADDR 0x20 /* dword - capture address */ +#define VT1724_MT_CAPTURE_SIZE 0x24 /* word - capture size */ +#define VT1724_MT_CAPTURE_COUNT 0x26 /* word - capture count */ + +#define VT1724_MT_ROUTE_PLAYBACK 0x2c /* word */ + +#define VT1724_MT_RDMA1_ADDR 0x30 /* dword - RDMA1 capture address */ +#define VT1724_MT_RDMA1_SIZE 0x34 /* word - RDMA1 capture size */ +#define VT1724_MT_RDMA1_COUNT 0x36 /* word - RDMA1 capture count */ + +#define VT1724_MT_SPDIF_CTRL 0x3c /* word */ +#define VT1724_MT_MONITOR_PEAKINDEX 0x3e /* byte */ +#define VT1724_MT_MONITOR_PEAKDATA 0x3f /* byte */ + +/* concurrent stereo channels */ +#define VT1724_MT_PDMA4_ADDR 0x40 /* dword */ +#define VT1724_MT_PDMA4_SIZE 0x44 /* word */ +#define VT1724_MT_PDMA4_COUNT 0x46 /* word */ +#define VT1724_MT_PDMA3_ADDR 0x50 /* dword */ +#define VT1724_MT_PDMA3_SIZE 0x54 /* word */ +#define VT1724_MT_PDMA3_COUNT 0x56 /* word */ +#define VT1724_MT_PDMA2_ADDR 0x60 /* dword */ +#define VT1724_MT_PDMA2_SIZE 0x64 /* word */ +#define VT1724_MT_PDMA2_COUNT 0x66 /* word */ +#define VT1724_MT_PDMA1_ADDR 0x70 /* dword */ +#define VT1724_MT_PDMA1_SIZE 0x74 /* word */ +#define VT1724_MT_PDMA1_COUNT 0x76 /* word */ + + +unsigned char snd_vt1724_read_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr); +void snd_vt1724_write_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr, unsigned char data); + +#endif /* __SOUND_VT1724_H */ diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c new file mode 100644 index 000000000..b8af747ec --- /dev/null +++ b/sound/pci/ice1712/ews.c @@ -0,0 +1,1076 @@ +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire + * + * Copyright (c) 2000 Jaroslav Kysela + * 2002 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "ews.h" + +#define SND_CS8404 +#include + +enum { + EWS_I2C_CS8404 = 0, EWS_I2C_PCF1, EWS_I2C_PCF2, + EWS_I2C_88D = 0, + EWS_I2C_6FIRE = 0 +}; + + +/* additional i2c devices for EWS boards */ +struct ews_spec { + struct snd_i2c_device *i2cdevs[3]; +}; + +/* + * access via i2c mode (for EWX 24/96, EWS 88MT&D) + */ + +/* send SDA and SCL */ +static void ewx_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned char tmp = 0; + if (clk) + tmp |= ICE1712_EWX2496_SERIAL_CLOCK; + if (data) + tmp |= ICE1712_EWX2496_SERIAL_DATA; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp); + udelay(5); +} + +static int ewx_i2c_getclock(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_CLOCK ? 1 : 0; +} + +static int ewx_i2c_getdata(struct snd_i2c_bus *bus, int ack) +{ + struct snd_ice1712 *ice = bus->private_data; + int bit; + /* set RW pin to low */ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_RW); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, 0); + if (ack) + udelay(5); + bit = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_DATA ? 1 : 0; + /* set RW pin to high */ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ICE1712_EWX2496_RW); + /* reset write mask */ + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_SERIAL_CLOCK); + return bit; +} + +static void ewx_i2c_start(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned char mask; + + snd_ice1712_save_gpio_status(ice); + /* set RW high */ + mask = ICE1712_EWX2496_RW; + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWX2496: + mask |= ICE1712_EWX2496_AK4524_CS; /* CS high also */ + break; + case ICE1712_SUBDEVICE_DMX6FIRE: + mask |= ICE1712_6FIRE_AK4524_CS_MASK; /* CS high also */ + break; + } + snd_ice1712_gpio_write_bits(ice, mask, mask); +} + +static void ewx_i2c_stop(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + snd_ice1712_restore_gpio_status(ice); +} + +static void ewx_i2c_direction(struct snd_i2c_bus *bus, int clock, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned char mask = 0; + + if (clock) + mask |= ICE1712_EWX2496_SERIAL_CLOCK; /* write SCL */ + if (data) + mask |= ICE1712_EWX2496_SERIAL_DATA; /* write SDA */ + ice->gpio.direction &= ~(ICE1712_EWX2496_SERIAL_CLOCK|ICE1712_EWX2496_SERIAL_DATA); + ice->gpio.direction |= mask; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->gpio.direction); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~mask); +} + +static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = { + .start = ewx_i2c_start, + .stop = ewx_i2c_stop, + .direction = ewx_i2c_direction, + .setlines = ewx_i2c_setlines, + .getclock = ewx_i2c_getclock, + .getdata = ewx_i2c_getdata, +}; + + +/* + * AK4524 access + */ + +/* AK4524 chip select; address 0x48 bit 0-3 */ +static int snd_ice1712_ews88mt_chip_select(struct snd_ice1712 *ice, int chip_mask) +{ + struct ews_spec *spec = ice->spec; + unsigned char data, ndata; + + if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f)) + return -EINVAL; + snd_i2c_lock(ice->i2c); + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) + goto __error; + ndata = (data & 0xf0) | chip_mask; + if (ndata != data) + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], &ndata, 1) + != 1) + goto __error; + snd_i2c_unlock(ice->i2c); + return 0; + + __error: + snd_i2c_unlock(ice->i2c); + dev_err(ice->card->dev, + "AK4524 chip select failed, check cable to the front module\n"); + return -EIO; +} + +/* start callback for EWS88MT, needs to select a certain chip mask */ +static void ews88mt_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + unsigned char tmp; + /* assert AK4524 CS */ + if (snd_ice1712_ews88mt_chip_select(ice, ~(1 << chip) & 0x0f) < 0) + dev_err(ice->card->dev, "fatal error (ews88mt chip select)\n"); + snd_ice1712_save_gpio_status(ice); + tmp = ICE1712_EWS88_SERIAL_DATA | + ICE1712_EWS88_SERIAL_CLOCK | + ICE1712_EWS88_RW; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->gpio.direction | tmp); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp); +} + +/* stop callback for EWS88MT, needs to deselect chip mask */ +static void ews88mt_ak4524_unlock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + snd_ice1712_restore_gpio_status(ice); + udelay(1); + snd_ice1712_ews88mt_chip_select(ice, 0x0f); +} + +/* start callback for EWX24/96 */ +static void ewx2496_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + unsigned char tmp; + snd_ice1712_save_gpio_status(ice); + tmp = ICE1712_EWX2496_SERIAL_DATA | + ICE1712_EWX2496_SERIAL_CLOCK | + ICE1712_EWX2496_AK4524_CS | + ICE1712_EWX2496_RW; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->gpio.direction | tmp); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp); +} + +/* start callback for DMX 6fire */ +static void dmx6fire_ak4524_lock(struct snd_akm4xxx *ak, int chip) +{ + struct snd_ak4xxx_private *priv = (void *)ak->private_value[0]; + struct snd_ice1712 *ice = ak->private_data[0]; + unsigned char tmp; + snd_ice1712_save_gpio_status(ice); + tmp = priv->cs_mask = priv->cs_addr = (1 << chip) & ICE1712_6FIRE_AK4524_CS_MASK; + tmp |= ICE1712_6FIRE_SERIAL_DATA | + ICE1712_6FIRE_SERIAL_CLOCK | + ICE1712_6FIRE_RW; + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, + ice->gpio.direction | tmp); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp); +} + +/* + * CS8404 interface on EWS88MT/D + */ + +static void snd_ice1712_ews_cs8404_spdif_write(struct snd_ice1712 *ice, unsigned char bits) +{ + struct ews_spec *spec = ice->spec; + unsigned char bytes[2]; + + snd_i2c_lock(ice->i2c); + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_EWS88MT: + case ICE1712_SUBDEVICE_EWS88MT_NEW: + case ICE1712_SUBDEVICE_PHASE88: + case ICE1712_SUBDEVICE_TS88: + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_CS8404], &bits, 1) + != 1) + goto _error; + break; + case ICE1712_SUBDEVICE_EWS88D: + if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], bytes, 2) + != 2) + goto _error; + if (bits != bytes[1]) { + bytes[1] = bits; + if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_88D], + bytes, 2) != 2) + goto _error; + } + break; + } + _error: + snd_i2c_unlock(ice->i2c); +} + +/* + */ + +static void ews88_spdif_default_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + snd_cs8404_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_bits); +} + +static int ews88_spdif_default_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + int change; + + val = snd_cs8404_encode_spdif_bits(&ucontrol->value.iec958); + 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 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 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; + snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_6FIRE], &byte, 1); + 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 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..1c443718a --- /dev/null +++ b/sound/pci/ice1712/ews.h @@ -0,0 +1,86 @@ +#ifndef __SOUND_EWS_H +#define __SOUND_EWS_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire + * + * Copyright (c) 2000 Jaroslav Kysela + * 2002 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..fa301d317 --- /dev/null +++ b/sound/pci/ice1712/hoontech.c @@ -0,0 +1,384 @@ +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Hoontech STDSP24 + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "hoontech.h" + +/* Hoontech-specific setting */ +struct hoontech_spec { + unsigned char boxbits[4]; + unsigned int config; + unsigned short boxconfig[4]; +}; + +static void snd_ice1712_stdsp24_gpio_write(struct snd_ice1712 *ice, unsigned char byte) +{ + byte |= ICE1712_STDSP24_CLOCK_BIT; + udelay(100); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte); + byte &= ~ICE1712_STDSP24_CLOCK_BIT; + udelay(100); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte); + byte |= ICE1712_STDSP24_CLOCK_BIT; + udelay(100); + snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte); +} + +static void snd_ice1712_stdsp24_darear(struct snd_ice1712 *ice, int activate) +{ + struct hoontech_spec *spec = ice->spec; + 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..7f9494339 --- /dev/null +++ b/sound/pci/ice1712/hoontech.h @@ -0,0 +1,78 @@ +#ifndef __SOUND_HOONTECH_H +#define __SOUND_HOONTECH_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for Hoontech STDSP24 + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..7702b62dc --- /dev/null +++ b/sound/pci/ice1712/ice1712.c @@ -0,0 +1,2874 @@ +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + NOTES: + - spdif nonaudio consumer mode does not work (at least with my + Sony STR-DB830) +*/ + +/* + * Changes: + * + * 2002.09.09 Takashi Iwai + * split the code to several files. each low-level routine + * is stored in the local file and called from registration + * function from card_info struct. + * + * 2002.11.26 James Stafford + * Added support for VT1724 (Envy24HT) + * I have left out support for 176.4 and 192 KHz for the moment. + * I also haven't done anything with the internal S/PDIF transmitter or the MPU-401 + * + * 2003.02.20 Taksahi Iwai + * Split vt1724 part to an independent driver. + * The GPIO is accessed through the callback functions now. + * + * 2004.03.31 Doug McLain + * Added support for Event Electronics EZ8 card to hoontech.c. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" + +/* lowlevel routines */ +#include "delta.h" +#include "ews.h" +#include "hoontech.h" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("ICEnsemble ICE1712 (Envy24)"); +MODULE_LICENSE("GPL"); +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 - misc + */ + +static int snd_ice1712_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_ice1712_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +/* + * 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ice1712_hw_params, + .hw_free = snd_ice1712_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ice1712_hw_params, + .hw_free = snd_ice1712_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ice1712_hw_params, + .hw_free = snd_ice1712_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ice->pci), 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ice->pci), 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 snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +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 snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ice1712_playback_pro_hw_params, + .hw_free = snd_ice1712_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ice1712_capture_pro_hw_params, + .hw_free = snd_ice1712_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ice->pci), 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 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 struct snd_ac97_bus_ops con_ops = { + .write = snd_ice1712_ac97_write, + .read = snd_ice1712_ac97_read, + }; + static 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) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(ice->card, "ice1712", &entry)) + snd_info_set_text_ops(entry, 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 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; + 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 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); + synchronize_irq(pci->irq); + + 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; + + 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; + 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_pcm_suspend_all(ice->pcm); + snd_pcm_suspend_all(ice->pcm_pro); + snd_pcm_suspend_all(ice->pcm_ds); + 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..8ae874266 --- /dev/null +++ b/sound/pci/ice1712/ice1712.h @@ -0,0 +1,539 @@ +#ifndef __SOUND_ICE1712_H +#define __SOUND_ICE1712_H + +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * Direct registers + */ + +#define ICEREG(ice, x) ((ice)->port + ICE1712_REG_##x) + +#define ICE1712_REG_CONTROL 0x00 /* byte */ +#define ICE1712_RESET 0x80 /* soft reset whole chip */ +#define ICE1712_SERR_ASSERT_DS_DMA 0x40 /* disabled SERR# assertion for the DS DMA Ch-C irq otherwise enabled */ +#define ICE1712_DOS_VOL 0x10 /* DOS WT/FM volume control */ +#define ICE1712_SERR_LEVEL 0x08 /* SERR# level otherwise edge */ +#define ICE1712_SERR_ASSERT_SB 0x02 /* disabled SERR# assertion for SB irq otherwise enabled */ +#define ICE1712_NATIVE 0x01 /* native mode otherwise SB */ +#define ICE1712_REG_IRQMASK 0x01 /* byte */ +#define ICE1712_IRQ_MPU1 0x80 /* MIDI irq mask */ +#define ICE1712_IRQ_TIMER 0x40 /* Timer mask */ +#define ICE1712_IRQ_MPU2 0x20 /* Secondary MIDI irq mask */ +#define ICE1712_IRQ_PROPCM 0x10 /* professional multi-track */ +#define ICE1712_IRQ_FM 0x08 /* FM/MIDI - legacy */ +#define ICE1712_IRQ_PBKDS 0x04 /* playback DS channels */ +#define ICE1712_IRQ_CONCAP 0x02 /* consumer capture */ +#define ICE1712_IRQ_CONPBK 0x01 /* consumer playback */ +#define ICE1712_REG_IRQSTAT 0x02 /* byte */ +/* look to ICE1712_IRQ_* */ +#define ICE1712_REG_INDEX 0x03 /* byte - indirect CCIxx regs */ +#define ICE1712_REG_DATA 0x04 /* byte - indirect CCIxx regs */ +#define ICE1712_REG_NMI_STAT1 0x05 /* byte */ +#define ICE1712_REG_NMI_DATA 0x06 /* byte */ +#define ICE1712_REG_NMI_INDEX 0x07 /* byte */ +#define ICE1712_REG_AC97_INDEX 0x08 /* byte */ +#define ICE1712_REG_AC97_CMD 0x09 /* byte */ +#define ICE1712_AC97_COLD 0x80 /* cold reset */ +#define ICE1712_AC97_WARM 0x40 /* warm reset */ +#define ICE1712_AC97_WRITE 0x20 /* W: write, R: write in progress */ +#define ICE1712_AC97_READ 0x10 /* W: read, R: read in progress */ +#define ICE1712_AC97_READY 0x08 /* codec ready status bit */ +#define ICE1712_AC97_PBK_VSR 0x02 /* playback VSR */ +#define ICE1712_AC97_CAP_VSR 0x01 /* capture VSR */ +#define ICE1712_REG_AC97_DATA 0x0a /* word (little endian) */ +#define ICE1712_REG_MPU1_CTRL 0x0c /* byte */ +#define ICE1712_REG_MPU1_DATA 0x0d /* byte */ +#define ICE1712_REG_I2C_DEV_ADDR 0x10 /* byte */ +#define ICE1712_I2C_WRITE 0x01 /* write direction */ +#define ICE1712_REG_I2C_BYTE_ADDR 0x11 /* byte */ +#define ICE1712_REG_I2C_DATA 0x12 /* byte */ +#define ICE1712_REG_I2C_CTRL 0x13 /* byte */ +#define ICE1712_I2C_EEPROM 0x80 /* EEPROM exists */ +#define ICE1712_I2C_BUSY 0x01 /* busy bit */ +#define ICE1712_REG_CONCAP_ADDR 0x14 /* dword - consumer capture */ +#define ICE1712_REG_CONCAP_COUNT 0x18 /* word - current/base count */ +#define ICE1712_REG_SERR_SHADOW 0x1b /* byte */ +#define ICE1712_REG_MPU2_CTRL 0x1c /* byte */ +#define ICE1712_REG_MPU2_DATA 0x1d /* byte */ +#define ICE1712_REG_TIMER 0x1e /* word */ + +/* + * Indirect registers + */ + +#define ICE1712_IREG_PBK_COUNT_LO 0x00 +#define ICE1712_IREG_PBK_COUNT_HI 0x01 +#define ICE1712_IREG_PBK_CTRL 0x02 +#define ICE1712_IREG_PBK_LEFT 0x03 /* left volume */ +#define ICE1712_IREG_PBK_RIGHT 0x04 /* right volume */ +#define ICE1712_IREG_PBK_SOFT 0x05 /* soft volume */ +#define ICE1712_IREG_PBK_RATE_LO 0x06 +#define ICE1712_IREG_PBK_RATE_MID 0x07 +#define ICE1712_IREG_PBK_RATE_HI 0x08 +#define ICE1712_IREG_CAP_COUNT_LO 0x10 +#define ICE1712_IREG_CAP_COUNT_HI 0x11 +#define ICE1712_IREG_CAP_CTRL 0x12 +#define ICE1712_IREG_GPIO_DATA 0x20 +#define ICE1712_IREG_GPIO_WRITE_MASK 0x21 +#define ICE1712_IREG_GPIO_DIRECTION 0x22 +#define ICE1712_IREG_CONSUMER_POWERDOWN 0x30 +#define ICE1712_IREG_PRO_POWERDOWN 0x31 + +/* + * Consumer section direct DMA registers + */ + +#define ICEDS(ice, x) ((ice)->dmapath_port + ICE1712_DS_##x) + +#define ICE1712_DS_INTMASK 0x00 /* word - interrupt mask */ +#define ICE1712_DS_INTSTAT 0x02 /* word - interrupt status */ +#define ICE1712_DS_DATA 0x04 /* dword - channel data */ +#define ICE1712_DS_INDEX 0x08 /* dword - channel index */ + +/* + * Consumer section channel registers + */ + +#define ICE1712_DSC_ADDR0 0x00 /* dword - base address 0 */ +#define ICE1712_DSC_COUNT0 0x01 /* word - count 0 */ +#define ICE1712_DSC_ADDR1 0x02 /* dword - base address 1 */ +#define ICE1712_DSC_COUNT1 0x03 /* word - count 1 */ +#define ICE1712_DSC_CONTROL 0x04 /* byte - control & status */ +#define ICE1712_BUFFER1 0x80 /* buffer1 is active */ +#define ICE1712_BUFFER1_AUTO 0x40 /* buffer1 auto init */ +#define ICE1712_BUFFER0_AUTO 0x20 /* buffer0 auto init */ +#define ICE1712_FLUSH 0x10 /* flush FIFO */ +#define ICE1712_STEREO 0x08 /* stereo */ +#define ICE1712_16BIT 0x04 /* 16-bit data */ +#define ICE1712_PAUSE 0x02 /* pause */ +#define ICE1712_START 0x01 /* start */ +#define ICE1712_DSC_RATE 0x05 /* dword - rate */ +#define ICE1712_DSC_VOLUME 0x06 /* word - volume control */ + +/* + * Professional multi-track direct control registers + */ + +#define ICEMT(ice, x) ((ice)->profi_port + ICE1712_MT_##x) + +#define ICE1712_MT_IRQ 0x00 /* byte - interrupt mask */ +#define ICE1712_MULTI_CAPTURE 0x80 /* capture IRQ */ +#define ICE1712_MULTI_PLAYBACK 0x40 /* playback IRQ */ +#define ICE1712_MULTI_CAPSTATUS 0x02 /* capture IRQ status */ +#define ICE1712_MULTI_PBKSTATUS 0x01 /* playback IRQ status */ +#define ICE1712_MT_RATE 0x01 /* byte - sampling rate select */ +#define ICE1712_SPDIF_MASTER 0x10 /* S/PDIF input is master clock */ +#define ICE1712_MT_I2S_FORMAT 0x02 /* byte - I2S data format */ +#define ICE1712_MT_AC97_INDEX 0x04 /* byte - AC'97 index */ +#define ICE1712_MT_AC97_CMD 0x05 /* byte - AC'97 command & status */ +/* look to ICE1712_AC97_* */ +#define ICE1712_MT_AC97_DATA 0x06 /* word - AC'97 data */ +#define ICE1712_MT_PLAYBACK_ADDR 0x10 /* dword - playback address */ +#define ICE1712_MT_PLAYBACK_SIZE 0x14 /* word - playback size */ +#define ICE1712_MT_PLAYBACK_COUNT 0x16 /* word - playback count */ +#define ICE1712_MT_PLAYBACK_CONTROL 0x18 /* byte - control */ +#define ICE1712_CAPTURE_START_SHADOW 0x04 /* capture start */ +#define ICE1712_PLAYBACK_PAUSE 0x02 /* playback pause */ +#define ICE1712_PLAYBACK_START 0x01 /* playback start */ +#define ICE1712_MT_CAPTURE_ADDR 0x20 /* dword - capture address */ +#define ICE1712_MT_CAPTURE_SIZE 0x24 /* word - capture size */ +#define ICE1712_MT_CAPTURE_COUNT 0x26 /* word - capture count */ +#define ICE1712_MT_CAPTURE_CONTROL 0x28 /* byte - control */ +#define ICE1712_CAPTURE_START 0x01 /* capture start */ +#define ICE1712_MT_ROUTE_PSDOUT03 0x30 /* word */ +#define ICE1712_MT_ROUTE_SPDOUT 0x32 /* word */ +#define ICE1712_MT_ROUTE_CAPTURE 0x34 /* dword */ +#define ICE1712_MT_MONITOR_VOLUME 0x38 /* word */ +#define ICE1712_MT_MONITOR_INDEX 0x3a /* byte */ +#define ICE1712_MT_MONITOR_RATE 0x3b /* byte */ +#define ICE1712_MT_MONITOR_ROUTECTRL 0x3c /* byte */ +#define ICE1712_ROUTE_AC97 0x01 /* route digital mixer output to AC'97 */ +#define ICE1712_MT_MONITOR_PEAKINDEX 0x3e /* byte */ +#define ICE1712_MT_MONITOR_PEAKDATA 0x3f /* byte */ + +/* + * Codec configuration bits + */ + +/* PCI[60] System Configuration */ +#define ICE1712_CFG_CLOCK 0xc0 +#define ICE1712_CFG_CLOCK512 0x00 /* 22.5692Mhz, 44.1kHz*512 */ +#define ICE1712_CFG_CLOCK384 0x40 /* 16.9344Mhz, 44.1kHz*384 */ +#define ICE1712_CFG_EXT 0x80 /* external clock */ +#define ICE1712_CFG_2xMPU401 0x20 /* two MPU401 UARTs */ +#define ICE1712_CFG_NO_CON_AC97 0x10 /* consumer AC'97 codec is not present */ +#define ICE1712_CFG_ADC_MASK 0x0c /* one, two, three, four stereo ADCs */ +#define ICE1712_CFG_DAC_MASK 0x03 /* one, two, three, four stereo DACs */ +/* PCI[61] AC-Link Configuration */ +#define ICE1712_CFG_PRO_I2S 0x80 /* multitrack converter: I2S or AC'97 */ +#define ICE1712_CFG_AC97_PACKED 0x01 /* split or packed mode - AC'97 */ +/* PCI[62] I2S Features */ +#define ICE1712_CFG_I2S_VOLUME 0x80 /* volume/mute capability */ +#define ICE1712_CFG_I2S_96KHZ 0x40 /* supports 96kHz sampling */ +#define ICE1712_CFG_I2S_RESMASK 0x30 /* resolution mask, 16,18,20,24-bit */ +#define ICE1712_CFG_I2S_OTHER 0x0f /* other I2S IDs */ +/* PCI[63] S/PDIF Configuration */ +#define ICE1712_CFG_I2S_CHIPID 0xfc /* I2S chip ID */ +#define ICE1712_CFG_SPDIF_IN 0x02 /* S/PDIF input is present */ +#define ICE1712_CFG_SPDIF_OUT 0x01 /* S/PDIF output is present */ + +/* + * DMA mode values + * identical with DMA_XXX on i386 architecture. + */ +#define ICE1712_DMA_MODE_WRITE 0x48 +#define ICE1712_DMA_AUTOINIT 0x10 + + +/* + * I2C EEPROM Address + */ +#define ICE_I2C_EEPROM_ADDR 0xA0 + +struct snd_ice1712; + +struct snd_ice1712_eeprom { + unsigned int subvendor; /* PCI[2c-2f] */ + unsigned char size; /* size of EEPROM image in bytes */ + unsigned char version; /* must be 1 (or 2 for vt1724) */ + unsigned char data[32]; + unsigned int gpiomask; + unsigned int gpiostate; + unsigned int gpiodir; +}; + +enum { + ICE_EEP1_CODEC = 0, /* 06 */ + ICE_EEP1_ACLINK, /* 07 */ + ICE_EEP1_I2SID, /* 08 */ + ICE_EEP1_SPDIF, /* 09 */ + ICE_EEP1_GPIO_MASK, /* 0a */ + ICE_EEP1_GPIO_STATE, /* 0b */ + ICE_EEP1_GPIO_DIR, /* 0c */ + ICE_EEP1_AC97_MAIN_LO, /* 0d */ + ICE_EEP1_AC97_MAIN_HI, /* 0e */ + ICE_EEP1_AC97_PCM_LO, /* 0f */ + ICE_EEP1_AC97_PCM_HI, /* 10 */ + ICE_EEP1_AC97_REC_LO, /* 11 */ + ICE_EEP1_AC97_REC_HI, /* 12 */ + ICE_EEP1_AC97_RECSRC, /* 13 */ + ICE_EEP1_DAC_ID, /* 14 */ + ICE_EEP1_DAC_ID1, + ICE_EEP1_DAC_ID2, + ICE_EEP1_DAC_ID3, + ICE_EEP1_ADC_ID, /* 18 */ + ICE_EEP1_ADC_ID1, + ICE_EEP1_ADC_ID2, + ICE_EEP1_ADC_ID3 +}; + +#define ice_has_con_ac97(ice) (!((ice)->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97)) + + +struct snd_ak4xxx_private { + unsigned int cif:1; /* CIF mode */ + unsigned char caddr; /* C0 and C1 bits */ + unsigned int data_mask; /* DATA gpio bit */ + unsigned int clk_mask; /* CLK gpio bit */ + unsigned int cs_mask; /* bit mask for select/deselect address */ + unsigned int cs_addr; /* bits to select address */ + unsigned int cs_none; /* bits to deselect address */ + unsigned int add_flags; /* additional bits at init */ + unsigned int mask_flags; /* total mask bits */ + struct snd_akm4xxx_ops { + void (*set_rate_val)(struct snd_akm4xxx *ak, unsigned int rate); + } ops; +}; + +struct snd_ice1712_spdif { + unsigned char cs8403_bits; + unsigned char cs8403_stream_bits; + struct snd_kcontrol *stream_ctl; + + struct snd_ice1712_spdif_ops { + void (*open)(struct snd_ice1712 *, struct snd_pcm_substream *); + void (*setup_rate)(struct snd_ice1712 *, int rate); + void (*close)(struct snd_ice1712 *, struct snd_pcm_substream *); + void (*default_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + int (*default_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + void (*stream_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + int (*stream_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol); + } ops; +}; + +struct snd_ice1712_card_info; + +struct snd_ice1712 { + unsigned long conp_dma_size; + unsigned long conc_dma_size; + unsigned long prop_dma_size; + unsigned long proc_dma_size; + int irq; + + unsigned long port; + unsigned long ddma_port; + unsigned long dmapath_port; + unsigned long profi_port; + + struct pci_dev *pci; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm *pcm_ds; + struct snd_pcm *pcm_pro; + struct snd_pcm_substream *playback_con_substream; + struct snd_pcm_substream *playback_con_substream_ds[6]; + struct snd_pcm_substream *capture_con_substream; + struct snd_pcm_substream *playback_pro_substream; + struct snd_pcm_substream *capture_pro_substream; + unsigned int playback_pro_size; + unsigned int capture_pro_size; + unsigned int playback_con_virt_addr[6]; + unsigned int playback_con_active_buf[6]; + unsigned int capture_con_virt_addr; + unsigned int ac97_ext_id; + struct snd_ac97 *ac97; + struct snd_rawmidi *rmidi[2]; + + spinlock_t reg_lock; + struct snd_info_entry *proc_entry; + + struct snd_ice1712_eeprom eeprom; + 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..41ea8e7b3 --- /dev/null +++ b/sound/pci/ice1712/ice1724.c @@ -0,0 +1,2885 @@ +/* + * ALSA driver for VT1724 ICEnsemble ICE1724 / VIA VT1724 (Envy24HT) + * VIA VT1720 (Envy24PT) + * + * Copyright (c) 2000 Jaroslav Kysela + * 2002 James Stafford + * 2003 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "envy24ht.h" + +/* lowlevel routines */ +#include "amp.h" +#include "revo.h" +#include "aureon.h" +#include "vt1720_mobo.h" +#include "pontis.h" +#include "prodigy192.h" +#include "prodigy_hifi.h" +#include "juli.h" +#include "maya44.h" +#include "phase.h" +#include "wtm.h" +#include "se.h" +#include "quartet.h" +#include "psc724.h" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)"); +MODULE_LICENSE("GPL"); +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, err; + + 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); + + err = snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0); + if (err < 0) + return err; + + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +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 snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ice->pci), + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ice->pci), + 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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(ice->pci), + 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 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) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(ice->card, "ice1724", &entry)) + snd_info_set_text_ops(entry, 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 struct snd_ice1712_card_info no_matched; + + +/* + ooAoo cards with no controls +*/ +static 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 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 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; + 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 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); + synchronize_irq(pci->irq); + + 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; + + 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; + 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_pcm_suspend_all(ice->pcm); + snd_pcm_suspend_all(ice->pcm_pro); + snd_pcm_suspend_all(ice->pcm_ds); + 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..21806bab4 --- /dev/null +++ b/sound/pci/ice1712/juli.c @@ -0,0 +1,699 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for ESI Juli@ cards + * + * Copyright (c) 2004 Jaroslav Kysela + * 2008 Pavel Hofman + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "juli.h" + +struct juli_spec { + struct ak4114 *ak4114; + unsigned int analog:1; +}; + +/* + * chip addresses on I2C bus + */ +#define AK4114_ADDR 0x20 /* S/PDIF receiver */ +#define AK4358_ADDR 0x22 /* DAC */ + +/* + * Juli does not use the standard ICE1724 clock scheme. Juli's ice1724 chip is + * supplied by external clock provided by Xilinx array and MK73-1 PLL frequency + * multiplier. Actual frequency is set by ice1724 GPIOs hooked to the Xilinx. + * + * The clock circuitry is supplied by the two ice1724 crystals. This + * arrangement allows to generate independent clock signal for AK4114's input + * rate detection circuit. As a result, Juli, unlike most other + * ice1724+ak4114-based cards, detects spdif input rate correctly. + * This fact is applied in the driver, allowing to modify PCM stream rate + * parameter according to the actual input rate. + * + * Juli uses the remaining three stereo-channels of its DAC to optionally + * monitor analog input, digital input, and digital output. The corresponding + * I2S signals are routed by Xilinx, controlled by GPIOs. + * + * The master mute is implemented using output muting transistors (GPIO) in + * combination with smuting the DAC. + * + * The card itself has no HW master volume control, implemented using the + * vmaster control. + * + * TODO: + * researching and fixing the input monitors + */ + +/* + * GPIO pins + */ +#define GPIO_FREQ_MASK (3<<0) +#define GPIO_FREQ_32KHZ (0<<0) +#define GPIO_FREQ_44KHZ (1<<0) +#define GPIO_FREQ_48KHZ (2<<0) +#define GPIO_MULTI_MASK (3<<2) +#define GPIO_MULTI_4X (0<<2) +#define GPIO_MULTI_2X (1<<2) +#define GPIO_MULTI_1X (2<<2) /* also external */ +#define GPIO_MULTI_HALF (3<<2) +#define GPIO_INTERNAL_CLOCK (1<<4) /* 0 = external, 1 = internal */ +#define GPIO_CLOCK_MASK (1<<4) +#define GPIO_ANALOG_PRESENT (1<<5) /* RO only: 0 = present */ +#define GPIO_RXMCLK_SEL (1<<7) /* must be 0 */ +#define GPIO_AK5385A_CKS0 (1<<8) +#define GPIO_AK5385A_DFS1 (1<<9) +#define GPIO_AK5385A_DFS0 (1<<10) +#define GPIO_DIGOUT_MONITOR (1<<11) /* 1 = active */ +#define GPIO_DIGIN_MONITOR (1<<12) /* 1 = active */ +#define GPIO_ANAIN_MONITOR (1<<13) /* 1 = active */ +#define GPIO_AK5385A_CKS1 (1<<14) /* must be 0 */ +#define GPIO_MUTE_CONTROL (1<<15) /* output mute, 1 = muted */ + +#define GPIO_RATE_MASK (GPIO_FREQ_MASK | GPIO_MULTI_MASK | \ + GPIO_CLOCK_MASK) +#define GPIO_AK5385A_MASK (GPIO_AK5385A_CKS0 | GPIO_AK5385A_DFS0 | \ + GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS1) + +#define JULI_PCM_RATE (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define GPIO_RATE_16000 (GPIO_FREQ_32KHZ | GPIO_MULTI_HALF | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_22050 (GPIO_FREQ_44KHZ | GPIO_MULTI_HALF | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_24000 (GPIO_FREQ_48KHZ | GPIO_MULTI_HALF | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_32000 (GPIO_FREQ_32KHZ | GPIO_MULTI_1X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_44100 (GPIO_FREQ_44KHZ | GPIO_MULTI_1X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_48000 (GPIO_FREQ_48KHZ | GPIO_MULTI_1X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_64000 (GPIO_FREQ_32KHZ | GPIO_MULTI_2X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_88200 (GPIO_FREQ_44KHZ | GPIO_MULTI_2X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_96000 (GPIO_FREQ_48KHZ | GPIO_MULTI_2X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_176400 (GPIO_FREQ_44KHZ | GPIO_MULTI_4X | \ + GPIO_INTERNAL_CLOCK) +#define GPIO_RATE_192000 (GPIO_FREQ_48KHZ | GPIO_MULTI_4X | \ + GPIO_INTERNAL_CLOCK) + +/* + * Initial setup of the conversion array GPIO <-> rate + */ +static const unsigned int juli_rates[] = { + 16000, 22050, 24000, 32000, + 44100, 48000, 64000, 88200, + 96000, 176400, 192000, +}; + +static const unsigned int gpio_vals[] = { + GPIO_RATE_16000, GPIO_RATE_22050, GPIO_RATE_24000, GPIO_RATE_32000, + GPIO_RATE_44100, GPIO_RATE_48000, GPIO_RATE_64000, GPIO_RATE_88200, + GPIO_RATE_96000, GPIO_RATE_176400, GPIO_RATE_192000, +}; + +static const struct snd_pcm_hw_constraint_list juli_rates_info = { + .count = ARRAY_SIZE(juli_rates), + .list = juli_rates, + .mask = 0, +}; + +static int get_gpio_val(int rate) +{ + int i; + for (i = 0; i < ARRAY_SIZE(juli_rates); i++) + if (juli_rates[i] == rate) + return gpio_vals[i]; + return 0; +} + +static void juli_ak4114_write(void *private_data, unsigned char reg, + unsigned char val) +{ + snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4114_ADDR, + reg, val); +} + +static unsigned char juli_ak4114_read(void *private_data, unsigned char reg) +{ + return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data, + AK4114_ADDR, reg); +} + +/* + * If SPDIF capture and slaved to SPDIF-IN, setting runtime rate + * to the external rate + */ +static void juli_spdif_in_open(struct snd_ice1712 *ice, + struct snd_pcm_substream *substream) +{ + struct juli_spec *spec = ice->spec; + struct snd_pcm_runtime *runtime = substream->runtime; + int rate; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + !ice->is_spdif_master(ice)) + return; + rate = snd_ak4114_external_rate(spec->ak4114); + if (rate >= runtime->hw.rate_min && rate <= runtime->hw.rate_max) { + runtime->hw.rate_min = rate; + runtime->hw.rate_max = rate; + } +} + +/* + * AK4358 section + */ + +static void juli_akm_lock(struct snd_akm4xxx *ak, int chip) +{ +} + +static void juli_akm_unlock(struct snd_akm4xxx *ak, int chip) +{ +} + +static void juli_akm_write(struct snd_akm4xxx *ak, int chip, + unsigned char addr, unsigned char data) +{ + struct snd_ice1712 *ice = ak->private_data[0]; + + if (snd_BUG_ON(chip)) + return; + snd_vt1724_write_i2c(ice, AK4358_ADDR, addr, data); +} + +/* + * change the rate of envy24HT, AK4358, AK5385 + */ +static void juli_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char old, tmp, ak4358_dfs; + unsigned int ak5385_pins, old_gpio, new_gpio; + struct snd_ice1712 *ice = ak->private_data[0]; + struct juli_spec *spec = ice->spec; + + if (rate == 0) /* no hint - S/PDIF input is master or the new spdif + input rate undetected, simply return */ + return; + + /* adjust DFS on codecs */ + if (rate > 96000) { + ak4358_dfs = 2; + ak5385_pins = GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS0; + } else if (rate > 48000) { + ak4358_dfs = 1; + ak5385_pins = GPIO_AK5385A_DFS0; + } else { + ak4358_dfs = 0; + ak5385_pins = 0; + } + /* AK5385 first, since it requires cold reset affecting both codecs */ + old_gpio = ice->gpio.get_data(ice); + new_gpio = (old_gpio & ~GPIO_AK5385A_MASK) | ak5385_pins; + /* dev_dbg(ice->card->dev, "JULI - ak5385 set_rate_val: new gpio 0x%x\n", + new_gpio); */ + ice->gpio.set_data(ice, new_gpio); + + /* cold reset */ + old = inb(ICEMT1724(ice, AC97_CMD)); + outb(old | VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD)); + udelay(1); + outb(old & ~VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD)); + + /* AK4358 */ + /* set new value, reset DFS */ + tmp = snd_akm4xxx_get(ak, 0, 2); + snd_akm4xxx_reset(ak, 1); + tmp = snd_akm4xxx_get(ak, 0, 2); + tmp &= ~(0x03 << 4); + tmp |= ak4358_dfs << 4; + snd_akm4xxx_set(ak, 0, 2, tmp); + snd_akm4xxx_reset(ak, 0); + + /* reinit ak4114 */ + snd_ak4114_reinit(spec->ak4114); +} + +#define AK_DAC(xname, xch) { .name = xname, .num_channels = xch } +#define PCM_VOLUME "PCM Playback Volume" +#define MONITOR_AN_IN_VOLUME "Monitor Analog In Volume" +#define MONITOR_DIG_IN_VOLUME "Monitor Digital In Volume" +#define MONITOR_DIG_OUT_VOLUME "Monitor Digital Out Volume" + +static const struct snd_akm4xxx_dac_channel juli_dac[] = { + AK_DAC(PCM_VOLUME, 2), + AK_DAC(MONITOR_AN_IN_VOLUME, 2), + AK_DAC(MONITOR_DIG_OUT_VOLUME, 2), + AK_DAC(MONITOR_DIG_IN_VOLUME, 2), +}; + + +static const struct snd_akm4xxx akm_juli_dac = { + .type = SND_AK4358, + .num_dacs = 8, /* DAC1 - analog out + DAC2 - analog in monitor + DAC3 - digital out monitor + DAC4 - digital in monitor + */ + .ops = { + .lock = juli_akm_lock, + .unlock = juli_akm_unlock, + .write = juli_akm_write, + .set_rate_val = juli_akm_set_rate_val + }, + .dac_info = juli_dac, +}; + +#define juli_mute_info snd_ctl_boolean_mono_info + +static int juli_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int val; + val = ice->gpio.get_data(ice) & (unsigned int) kcontrol->private_value; + if (kcontrol->private_value == GPIO_MUTE_CONTROL) + /* val 0 = signal on */ + ucontrol->value.integer.value[0] = (val) ? 0 : 1; + else + /* val 1 = signal on */ + ucontrol->value.integer.value[0] = (val) ? 1 : 0; + return 0; +} + +static int juli_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned int old_gpio, new_gpio; + old_gpio = ice->gpio.get_data(ice); + if (ucontrol->value.integer.value[0]) { + /* unmute */ + if (kcontrol->private_value == GPIO_MUTE_CONTROL) { + /* 0 = signal on */ + new_gpio = old_gpio & ~GPIO_MUTE_CONTROL; + /* un-smuting DAC */ + snd_akm4xxx_write(ice->akm, 0, 0x01, 0x01); + } else + /* 1 = signal on */ + new_gpio = old_gpio | + (unsigned int) kcontrol->private_value; + } else { + /* mute */ + if (kcontrol->private_value == GPIO_MUTE_CONTROL) { + /* 1 = signal off */ + new_gpio = old_gpio | GPIO_MUTE_CONTROL; + /* smuting DAC */ + snd_akm4xxx_write(ice->akm, 0, 0x01, 0x03); + } else + /* 0 = signal off */ + new_gpio = old_gpio & + ~((unsigned int) kcontrol->private_value); + } + /* dev_dbg(ice->card->dev, + "JULI - mute/unmute: control_value: 0x%x, old_gpio: 0x%x, " + "new_gpio 0x%x\n", + (unsigned int)ucontrol->value.integer.value[0], old_gpio, + new_gpio); */ + if (old_gpio != new_gpio) { + ice->gpio.set_data(ice, new_gpio); + return 1; + } + /* no change */ + return 0; +} + +static 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 char *slave_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_slaves(struct snd_card *card, + struct snd_kcontrol *master, + char * const *list) +{ + for (; *list; list++) { + struct snd_kcontrol *slave = ctl_find(card, *list); + /* dev_dbg(card->dev, "add_slaves - %s\n", *list); */ + if (slave) { + /* dev_dbg(card->dev, "slave %s found\n", *list); */ + snd_ctl_add_slave(master, slave); + } + } +} + +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_slaves(ice->card, vmaster, slave_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 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..0e30419f6 --- /dev/null +++ b/sound/pci/ice1712/maya44.c @@ -0,0 +1,762 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for ESI Maya44 cards + * + * Copyright (c) 2009 Takashi Iwai + * Based on the patches by Rainer Zimmermann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "maya44.h" + +/* WM8776 register indexes */ +#define WM8776_REG_HEADPHONE_L 0x00 +#define WM8776_REG_HEADPHONE_R 0x01 +#define WM8776_REG_HEADPHONE_MASTER 0x02 +#define WM8776_REG_DAC_ATTEN_L 0x03 +#define WM8776_REG_DAC_ATTEN_R 0x04 +#define WM8776_REG_DAC_ATTEN_MASTER 0x05 +#define WM8776_REG_DAC_PHASE 0x06 +#define WM8776_REG_DAC_CONTROL 0x07 +#define WM8776_REG_DAC_MUTE 0x08 +#define WM8776_REG_DAC_DEEMPH 0x09 +#define WM8776_REG_DAC_IF_CONTROL 0x0a +#define WM8776_REG_ADC_IF_CONTROL 0x0b +#define WM8776_REG_MASTER_MODE_CONTROL 0x0c +#define WM8776_REG_POWERDOWN 0x0d +#define WM8776_REG_ADC_ATTEN_L 0x0e +#define WM8776_REG_ADC_ATTEN_R 0x0f +#define WM8776_REG_ADC_ALC1 0x10 +#define WM8776_REG_ADC_ALC2 0x11 +#define WM8776_REG_ADC_ALC3 0x12 +#define WM8776_REG_ADC_NOISE_GATE 0x13 +#define WM8776_REG_ADC_LIMITER 0x14 +#define WM8776_REG_ADC_MUX 0x15 +#define WM8776_REG_OUTPUT_MUX 0x16 +#define WM8776_REG_RESET 0x17 + +#define WM8776_NUM_REGS 0x18 + +/* clock ratio identifiers for snd_wm8776_set_rate() */ +#define WM8776_CLOCK_RATIO_128FS 0 +#define WM8776_CLOCK_RATIO_192FS 1 +#define WM8776_CLOCK_RATIO_256FS 2 +#define WM8776_CLOCK_RATIO_384FS 3 +#define WM8776_CLOCK_RATIO_512FS 4 +#define WM8776_CLOCK_RATIO_768FS 5 + +enum { WM_VOL_HP, WM_VOL_DAC, WM_VOL_ADC, WM_NUM_VOLS }; +enum { WM_SW_DAC, WM_SW_BYPASS, WM_NUM_SWITCHES }; + +struct snd_wm8776 { + unsigned char addr; + unsigned short regs[WM8776_NUM_REGS]; + unsigned char volumes[WM_NUM_VOLS][2]; + unsigned int switch_bits; +}; + +struct snd_maya44 { + struct snd_ice1712 *ice; + struct snd_wm8776 wm[2]; + struct mutex mutex; +}; + + +/* write the given register and save the data to the cache */ +static void wm8776_write(struct snd_ice1712 *ice, struct snd_wm8776 *wm, + unsigned char reg, unsigned short val) +{ + /* + * WM8776 registers are up to 9 bits wide, bit 8 is placed in the LSB + * of the address field + */ + snd_vt1724_write_i2c(ice, wm->addr, + (reg << 1) | ((val >> 8) & 1), + val & 0xff); + wm->regs[reg] = val; +} + +/* + * update the given register with and/or mask and save the data to the cache + */ +static int wm8776_write_bits(struct snd_ice1712 *ice, struct snd_wm8776 *wm, + unsigned char reg, + unsigned short mask, unsigned short val) +{ + val |= wm->regs[reg] & ~mask; + if (val != wm->regs[reg]) { + wm8776_write(ice, wm, reg, val); + return 1; + } + return 0; +} + + +/* + * WM8776 volume controls + */ + +struct maya_vol_info { + unsigned int maxval; /* volume range: 0..maxval */ + unsigned char regs[2]; /* left and right registers */ + unsigned short mask; /* value mask */ + unsigned short offset; /* zero-value offset */ + unsigned short mute; /* mute bit */ + unsigned short update; /* update bits */ + unsigned char mux_bits[2]; /* extra bits for ADC mute */ +}; + +static 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; + 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; + 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 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 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 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..67fbb28bf --- /dev/null +++ b/sound/pci/ice1712/phase.c @@ -0,0 +1,964 @@ +/* + * ALSA driver for ICEnsemble ICE1724 (Envy24) + * + * Lowlevel functions for Terratec PHASE 22 + * + * Copyright (c) 2005 Misha Zhilin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* PHASE 22 overview: + * Audio controller: VIA Envy24HT-S (slightly trimmed down Envy24HT, 4in/4out) + * Analog chip: AK4524 (partially via Philip's 74HCT125) + * Digital receiver: CS8414-CS (supported in this release) + * PHASE 22 revision 2.0 and Terrasoniq/Musonik TS22PCI have CS8416 + * (support status unknown, please test and report) + * + * Envy connects to AK4524 + * - CS directly from GPIO 10 + * - CCLK via 74HCT125's gate #4 from GPIO 4 + * - CDTI via 74HCT125's gate #2 from GPIO 5 + * CDTI may be completely blocked by 74HCT125's gate #1 + * controlled by GPIO 3 + */ + +/* PHASE 28 overview: + * Audio controller: VIA Envy24HT (full untrimmed version, 4in/8out) + * Analog chip: WM8770 (8 channel 192k DAC, 2 channel 96k ADC) + * Digital receiver: CS8414-CS (supported in this release) + */ + +#include +#include +#include +#include +#include + +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "phase.h" +#include + +/* AC97 register cache for Phase28 */ +struct phase28_spec { + unsigned short master[2]; + unsigned short vol[8]; +}; + +/* WM8770 registers */ +#define WM_DAC_ATTEN 0x00 /* DAC1-8 analog attenuation */ +#define WM_DAC_MASTER_ATTEN 0x08 /* DAC master analog attenuation */ +#define WM_DAC_DIG_ATTEN 0x09 /* DAC1-8 digital attenuation */ +#define WM_DAC_DIG_MASTER_ATTEN 0x11 /* DAC master digital attenuation */ +#define WM_PHASE_SWAP 0x12 /* DAC phase */ +#define WM_DAC_CTRL1 0x13 /* DAC control bits */ +#define WM_MUTE 0x14 /* mute controls */ +#define WM_DAC_CTRL2 0x15 /* de-emphasis and zefo-flag */ +#define WM_INT_CTRL 0x16 /* interface control */ +#define WM_MASTER 0x17 /* master clock and mode */ +#define WM_POWERDOWN 0x18 /* power-down controls */ +#define WM_ADC_GAIN 0x19 /* ADC gain L(19)/R(1a) */ +#define WM_ADC_MUX 0x1b /* input MUX */ +#define WM_OUT_MUX1 0x1c /* output MUX */ +#define WM_OUT_MUX2 0x1e /* output MUX */ +#define WM_RESET 0x1f /* software reset */ + + +/* + * Logarithmic volume values for WM8770 + * Computed as 20 * Log10(255 / x) + */ +static const unsigned char wm_vol[256] = { + 127, 48, 42, 39, 36, 34, 33, 31, 30, 29, 28, 27, 27, 26, 25, 25, 24, + 24, 23, 23, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 18, + 17, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, + 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#define WM_VOL_MAX (sizeof(wm_vol) - 1) +#define WM_VOL_MUTE 0x8000 + +static const struct snd_akm4xxx akm_phase22 = { + .type = SND_AK4524, + .num_dacs = 2, + .num_adcs = 2, +}; + +static const struct snd_ak4xxx_private akm_phase22_priv = { + .caddr = 2, + .cif = 1, + .data_mask = 1 << 4, + .clk_mask = 1 << 5, + .cs_mask = 1 << 10, + .cs_addr = 1 << 10, + .cs_none = 0, + .add_flags = 1 << 3, + .mask_flags = 0, +}; + +static int phase22_init(struct snd_ice1712 *ice) +{ + struct snd_akm4xxx *ak; + int err; + + /* Configure DAC/ADC description for generic part of ice1724 */ + switch (ice->eeprom.subvendor) { + case VT1724_SUBDEVICE_PHASE22: + case VT1724_SUBDEVICE_TS22: + ice->num_total_dacs = 2; + ice->num_total_adcs = 2; + ice->vt1720 = 1; /* Envy24HT-S have 16 bit wide GPIO */ + break; + default: + snd_BUG(); + return -EINVAL; + } + + /* Initialize analog chips */ + ice->akm = kzalloc(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 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 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 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 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..7fc22d9d4 --- /dev/null +++ b/sound/pci/ice1712/phase.h @@ -0,0 +1,53 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..93b8cfc66 --- /dev/null +++ b/sound/pci/ice1712/pontis.c @@ -0,0 +1,829 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Pontis MS300 + * + * Copyright (c) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "pontis.h" + +/* I2C addresses */ +#define WM_DEV 0x34 +#define CS_DEV 0x20 + +/* WM8776 registers */ +#define WM_HP_ATTEN_L 0x00 /* headphone left attenuation */ +#define WM_HP_ATTEN_R 0x01 /* headphone left attenuation */ +#define WM_HP_MASTER 0x02 /* headphone master (both channels) */ + /* override LLR */ +#define WM_DAC_ATTEN_L 0x03 /* digital left attenuation */ +#define WM_DAC_ATTEN_R 0x04 +#define WM_DAC_MASTER 0x05 +#define WM_PHASE_SWAP 0x06 /* DAC phase swap */ +#define WM_DAC_CTRL1 0x07 +#define WM_DAC_MUTE 0x08 +#define WM_DAC_CTRL2 0x09 +#define WM_DAC_INT 0x0a +#define WM_ADC_INT 0x0b +#define WM_MASTER_CTRL 0x0c +#define WM_POWERDOWN 0x0d +#define WM_ADC_ATTEN_L 0x0e +#define WM_ADC_ATTEN_R 0x0f +#define WM_ALC_CTRL1 0x10 +#define WM_ALC_CTRL2 0x11 +#define WM_ALC_CTRL3 0x12 +#define WM_NOISE_GATE 0x13 +#define WM_LIMITER 0x14 +#define WM_ADC_MUX 0x15 +#define WM_OUT_MUX 0x16 +#define WM_RESET 0x17 + +/* + * GPIO + */ +#define PONTIS_CS_CS (1<<4) /* CS */ +#define PONTIS_CS_CLK (1<<5) /* CLK */ +#define PONTIS_CS_RDATA (1<<6) /* CS8416 -> VT1720 */ +#define PONTIS_CS_WDATA (1<<7) /* VT1720 -> CS8416 */ + + +/* + * get the current register value of WM codec + */ +static unsigned short wm_get(struct snd_ice1712 *ice, int reg) +{ + reg <<= 1; + return ((unsigned short)ice->akm[0].images[reg] << 8) | + ice->akm[0].images[reg + 1]; +} + +/* + * set the register value of WM codec and remember it + */ +static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + unsigned short cval; + cval = (reg << 9) | val; + snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff); +} + +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + wm_put_nocache(ice, reg, val); + reg <<= 1; + ice->akm[0].images[reg] = val >> 8; + ice->akm[0].images[reg + 1] = val; +} + +/* + * DAC volume attenuation mixer control (-64dB to 0dB) + */ + +#define DAC_0dB 0xff +#define DAC_RES 128 +#define DAC_MIN (DAC_0dB - DAC_RES) + +static int wm_dac_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; /* mute */ + uinfo->value.integer.max = DAC_RES; /* 0dB, 0.5dB step */ + return 0; +} + +static int wm_dac_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned short val; + int i; + + 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 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", ®, &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) +{ + struct snd_info_entry *entry; + if (! snd_card_proc_new(ice->card, "wm_codec", &entry)) { + snd_info_set_text_ops(entry, ice, wm_proc_regs_read); + entry->mode |= 0200; + entry->c.text.write = 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) +{ + struct snd_info_entry *entry; + if (! snd_card_proc_new(ice->card, "cs_codec", &entry)) + snd_info_set_text_ops(entry, 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 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..d0d1378b9 --- /dev/null +++ b/sound/pci/ice1712/pontis.h @@ -0,0 +1,33 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..5e52086d7 --- /dev/null +++ b/sound/pci/ice1712/prodigy192.c @@ -0,0 +1,806 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for AudioTrak Prodigy 192 cards + * Supported IEC958 input from optional MI/ODI/O add-on card. + * + * Specifics (SW, HW): + * ------------------- + * * 49.5MHz crystal + * * SPDIF-OUT on the card: + * - coax (through isolation transformer)/toslink supplied by + * 74HC04 gates - 3 in parallel + * - output switched between on-board CD drive dig-out connector + * and ice1724 SPDTX pin, using 74HC02 NOR gates, controlled + * by GPIO20 (0 = CD dig-out, 1 = SPDTX) + * * SPDTX goes straight to MI/ODI/O card's SPDIF-OUT coax + * + * * MI/ODI/O card: AK4114 based, used for iec958 input only + * - toslink input -> RX0 + * - coax input -> RX1 + * - 4wire protocol: + * AK4114 ICE1724 + * ------------------------------ + * CDTO (pin 32) -- GPIO11 pin 86 + * CDTI (pin 33) -- GPIO10 pin 77 + * CCLK (pin 34) -- GPIO9 pin 76 + * CSN (pin 35) -- GPIO8 pin 75 + * - output data Mode 7 (24bit, I2S, slave) + * - both MCKO1 and MCKO2 of ak4114 are fed to FPGA, which + * outputs master clock to SPMCLKIN of ice1724. + * Experimentally I found out that only a combination of + * OCKS0=1, OCKS1=1 (128fs, 64fs output) and ice1724 - + * VT1724_MT_I2S_MCLK_128X=0 (256fs input) yields correct + * sampling rate. That means that the FPGA doubles the + * MCK01 rate. + * + * Copyright (c) 2003 Takashi Iwai + * Copyright (c) 2003 Dimitromanolakis Apostolos + * Copyright (c) 2004 Kouichi ONO + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "prodigy192.h" +#include "stac946x.h" +#include + +struct prodigy192_spec { + struct ak4114 *ak4114; + /* rate change needs atomic mute/unmute of all dacs*/ + struct mutex mute_mutex; +}; + +static inline void stac9460_put(struct snd_ice1712 *ice, int reg, unsigned char val) +{ + snd_vt1724_write_i2c(ice, PRODIGY192_STAC9460_ADDR, reg, val); +} + +static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg) +{ + return snd_vt1724_read_i2c(ice, PRODIGY192_STAC9460_ADDR, reg); +} + +/* + * DAC mute control + */ + +/* + * idx = STAC9460 volume register number, mute: 0 = mute, 1 = unmute + */ +static int stac9460_dac_mute(struct snd_ice1712 *ice, int idx, + unsigned char mute) +{ + unsigned char new, old; + int change; + old = stac9460_get(ice, idx); + new = (~mute << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) + /* dev_dbg(ice->card->dev, "Volume register 0x%02x: 0x%02x\n", idx, new);*/ + stac9460_put(ice, idx, new); + return change; +} + +#define stac9460_dac_mute_info snd_ctl_boolean_mono_info + +static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + unsigned char val; + int idx; + + if (kcontrol->private_value) + idx = STAC946X_MASTER_VOLUME; + else + idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME; + val = stac9460_get(ice, idx); + ucontrol->value.integer.value[0] = (~val >> 7) & 0x1; + return 0; +} + +static int stac9460_dac_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy192_spec *spec = ice->spec; + int idx, 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 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 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) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(ice->card, "stac9460_codec", &entry)) + snd_info_set_text_ops(entry, 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 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..317bbb725 --- /dev/null +++ b/sound/pci/ice1712/prodigy_hifi.c @@ -0,0 +1,1283 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Audiotrak Prodigy 7.1 Hifi + * based on pontis.c + * + * Copyright (c) 2007 Julian Scheel + * Copyright (c) 2007 allank + * Copyright (c) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "prodigy_hifi.h" + +struct prodigy_hifi_spec { + unsigned short master[2]; + unsigned short vol[8]; +}; + +/* I2C addresses */ +#define WM_DEV 0x34 + +/* WM8776 registers */ +#define WM_HP_ATTEN_L 0x00 /* headphone left attenuation */ +#define WM_HP_ATTEN_R 0x01 /* headphone left attenuation */ +#define WM_HP_MASTER 0x02 /* headphone master (both channels), + override LLR */ +#define WM_DAC_ATTEN_L 0x03 /* digital left attenuation */ +#define WM_DAC_ATTEN_R 0x04 +#define WM_DAC_MASTER 0x05 +#define WM_PHASE_SWAP 0x06 /* DAC phase swap */ +#define WM_DAC_CTRL1 0x07 +#define WM_DAC_MUTE 0x08 +#define WM_DAC_CTRL2 0x09 +#define WM_DAC_INT 0x0a +#define WM_ADC_INT 0x0b +#define WM_MASTER_CTRL 0x0c +#define WM_POWERDOWN 0x0d +#define WM_ADC_ATTEN_L 0x0e +#define WM_ADC_ATTEN_R 0x0f +#define WM_ALC_CTRL1 0x10 +#define WM_ALC_CTRL2 0x11 +#define WM_ALC_CTRL3 0x12 +#define WM_NOISE_GATE 0x13 +#define WM_LIMITER 0x14 +#define WM_ADC_MUX 0x15 +#define WM_OUT_MUX 0x16 +#define WM_RESET 0x17 + +/* Analog Recording Source :- Mic, LineIn, CD/Video, */ + +/* implement capture source select control for WM8776 */ + +#define WM_AIN1 "AIN1" +#define WM_AIN2 "AIN2" +#define WM_AIN3 "AIN3" +#define WM_AIN4 "AIN4" +#define WM_AIN5 "AIN5" + +/* GPIO pins of envy24ht connected to wm8766 */ +#define WM8766_SPI_CLK (1<<17) /* CLK, Pin97 on ICE1724 */ +#define WM8766_SPI_MD (1<<16) /* DATA VT1724 -> WM8766, Pin96 */ +#define WM8766_SPI_ML (1<<18) /* Latch, Pin98 */ + +/* WM8766 registers */ +#define WM8766_DAC_CTRL 0x02 /* DAC Control */ +#define WM8766_INT_CTRL 0x03 /* Interface Control */ +#define WM8766_DAC_CTRL2 0x09 +#define WM8766_DAC_CTRL3 0x0a +#define WM8766_RESET 0x1f +#define WM8766_LDA1 0x00 +#define WM8766_LDA2 0x04 +#define WM8766_LDA3 0x06 +#define WM8766_RDA1 0x01 +#define WM8766_RDA2 0x05 +#define WM8766_RDA3 0x07 +#define WM8766_MUTE1 0x0C +#define WM8766_MUTE2 0x0F + + +/* + * Prodigy HD2 + */ +#define AK4396_ADDR 0x00 +#define AK4396_CSN (1 << 8) /* CSN->GPIO8, pin 75 */ +#define AK4396_CCLK (1 << 9) /* CCLK->GPIO9, pin 76 */ +#define AK4396_CDTI (1 << 10) /* CDTI->GPIO10, pin 77 */ + +/* ak4396 registers */ +#define AK4396_CTRL1 0x00 +#define AK4396_CTRL2 0x01 +#define AK4396_CTRL3 0x02 +#define AK4396_LCH_ATT 0x03 +#define AK4396_RCH_ATT 0x04 + + +/* + * get the current register value of WM codec + */ +static unsigned short wm_get(struct snd_ice1712 *ice, int reg) +{ + reg <<= 1; + return ((unsigned short)ice->akm[0].images[reg] << 8) | + ice->akm[0].images[reg + 1]; +} + +/* + * set the register value of WM codec and remember it + */ +static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + unsigned short cval; + cval = (reg << 9) | val; + snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff); +} + +static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val) +{ + wm_put_nocache(ice, reg, val); + reg <<= 1; + ice->akm[0].images[reg] = val >> 8; + ice->akm[0].images[reg + 1] = val; +} + +/* + * write data in the SPI mode + */ + +static void set_gpio_bit(struct snd_ice1712 *ice, unsigned int bit, int val) +{ + unsigned int tmp = snd_ice1712_gpio_read(ice); + if (val) + tmp |= bit; + else + tmp &= ~bit; + snd_ice1712_gpio_write(ice, tmp); +} + +/* + * SPI implementation for WM8766 codec - only writing supported, no readback + */ + +static void wm8766_spi_send_word(struct snd_ice1712 *ice, unsigned int data) +{ + int i; + for (i = 0; i < 16; i++) { + set_gpio_bit(ice, WM8766_SPI_CLK, 0); + udelay(1); + set_gpio_bit(ice, WM8766_SPI_MD, data & 0x8000); + udelay(1); + set_gpio_bit(ice, WM8766_SPI_CLK, 1); + udelay(1); + data <<= 1; + } +} + +static void wm8766_spi_write(struct snd_ice1712 *ice, unsigned int reg, + unsigned int data) +{ + unsigned int block; + + snd_ice1712_gpio_set_dir(ice, WM8766_SPI_MD| + WM8766_SPI_CLK|WM8766_SPI_ML); + snd_ice1712_gpio_set_mask(ice, ~(WM8766_SPI_MD| + WM8766_SPI_CLK|WM8766_SPI_ML)); + /* latch must be low when writing */ + set_gpio_bit(ice, WM8766_SPI_ML, 0); + block = (reg << 9) | (data & 0x1ff); + wm8766_spi_send_word(ice, block); /* REGISTER ADDRESS */ + /* release latch */ + set_gpio_bit(ice, WM8766_SPI_ML, 1); + udelay(1); + /* restore */ + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); +} + + +/* + * serial interface for ak4396 - only writing supported, no readback + */ + +static void ak4396_send_word(struct snd_ice1712 *ice, unsigned int data) +{ + int i; + for (i = 0; i < 16; i++) { + set_gpio_bit(ice, AK4396_CCLK, 0); + udelay(1); + set_gpio_bit(ice, AK4396_CDTI, data & 0x8000); + udelay(1); + set_gpio_bit(ice, AK4396_CCLK, 1); + udelay(1); + data <<= 1; + } +} + +static void ak4396_write(struct snd_ice1712 *ice, unsigned int reg, + unsigned int data) +{ + unsigned int block; + + snd_ice1712_gpio_set_dir(ice, AK4396_CSN|AK4396_CCLK|AK4396_CDTI); + snd_ice1712_gpio_set_mask(ice, ~(AK4396_CSN|AK4396_CCLK|AK4396_CDTI)); + /* latch must be low when writing */ + set_gpio_bit(ice, AK4396_CSN, 0); + block = ((AK4396_ADDR & 0x03) << 14) | (1 << 13) | + ((reg & 0x1f) << 8) | (data & 0xff); + ak4396_send_word(ice, block); /* REGISTER ADDRESS */ + /* release latch */ + set_gpio_bit(ice, AK4396_CSN, 1); + udelay(1); + /* restore */ + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); +} + + +/* + * ak4396 mixers + */ + + + +/* + * DAC volume attenuation mixer control (-64dB to 0dB) + */ + +static int ak4396_dac_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; /* mute */ + uinfo->value.integer.max = 0xFF; /* linear */ + return 0; +} + +static int ak4396_dac_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i; + + for (i = 0; i < 2; i++) + ucontrol->value.integer.value[i] = spec->vol[i]; + + return 0; +} + +static int ak4396_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct prodigy_hifi_spec *spec = ice->spec; + int i; + int change = 0; + + 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 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 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", ®, &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) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(ice->card, "wm_codec", &entry)) { + snd_info_set_text_ops(entry, ice, wm_proc_regs_read); + entry->mode |= 0200; + entry->c.text.write = 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 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 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 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 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 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 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 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 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..a4415d455 --- /dev/null +++ b/sound/pci/ice1712/prodigy_hifi.h @@ -0,0 +1,38 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..4019cf27d --- /dev/null +++ b/sound/pci/ice1712/psc724.c @@ -0,0 +1,464 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Philips PSC724 Ultimate Edge + * + * Copyright (c) 2012 Ondrej Zary + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "psc724.h" +#include "wm8766.h" +#include "wm8776.h" + +struct psc724_spec { + struct snd_wm8766 wm8766; + struct snd_wm8776 wm8776; + bool mute_all, jack_detect; + struct snd_ice1712 *ice; + struct delayed_work hp_work; + bool hp_connected; +}; + +/****************************************************************************/ +/* PHILIPS PSC724 ULTIMATE EDGE */ +/****************************************************************************/ +/* + * VT1722 (Envy24GT) - 6 outputs, 4 inputs (only 2 used), 24-bit/96kHz + * + * system configuration ICE_EEP2_SYSCONF=0x42 + * XIN1 49.152MHz + * no MPU401 + * one stereo ADC, no S/PDIF receiver + * three stereo DACs (FRONT, REAR, CENTER+LFE) + * + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * use I2S, not AC97 + * + * I2S converters feature ICE_EEP2_I2S=0x30 + * I2S codec has no volume/mute control feature (bug!) + * I2S codec does not support 96KHz or 192KHz (bug!) + * I2S codec 24bits + * + * S/PDIF configuration ICE_EEP2_SPDIF=0xc1 + * Enable integrated S/PDIF transmitter + * internal S/PDIF out implemented + * No S/PDIF input + * External S/PDIF out implemented + * + * + * ** connected chips ** + * + * WM8776 + * 2-channel DAC used for main output and stereo ADC (with 10-channel MUX) + * AIN1: LINE IN, AIN2: CD/VIDEO, AIN3: AUX, AIN4: Front MIC, AIN5: Rear MIC + * Controlled by I2C using VT1722 I2C interface: + * MODE (pin16) -- GND + * CE (pin17) -- GND I2C mode (address=0x34) + * DI (pin18) -- SDA (VT1722 pin70) + * CL (pin19) -- SCLK (VT1722 pin71) + * + * WM8766 + * 6-channel DAC used for rear & center/LFE outputs (only 4 channels used) + * Controlled by SPI using VT1722 GPIO pins: + * MODE (pin 1) -- GPIO19 (VT1722 pin99) + * ML/I2S (pin11) -- GPIO18 (VT1722 pin98) + * MC/IWL (pin12) -- GPIO17 (VT1722 pin97) + * MD/DM (pin13) -- GPIO16 (VT1722 pin96) + * MUTE (pin14) -- GPIO20 (VT1722 pin101) + * + * GPIO14 is used as input for headphone jack detection (1 = connected) + * GPIO22 is used as MUTE ALL output, grounding all 6 channels + * + * ** output pins and device names ** + * + * 5.1ch name -- output connector color -- device (-D option) + * + * FRONT 2ch -- green -- plughw:0,0 + * CENTER(Lch) SUBWOOFER(Rch) -- orange -- plughw:0,2,0 + * REAR 2ch -- black -- plughw:0,2,1 + */ + +/* codec access low-level functions */ + +#define GPIO_HP_JACK (1 << 14) +#define GPIO_MUTE_SUR (1 << 20) +#define GPIO_MUTE_ALL (1 << 22) + +#define JACK_INTERVAL 1000 + +#define PSC724_SPI_DELAY 1 + +#define PSC724_SPI_DATA (1 << 16) +#define PSC724_SPI_CLK (1 << 17) +#define PSC724_SPI_LOAD (1 << 18) +#define PSC724_SPI_MASK (PSC724_SPI_DATA | PSC724_SPI_CLK | PSC724_SPI_LOAD) + +static void psc724_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data) +{ + struct psc724_spec *spec = container_of(wm, struct psc724_spec, wm8766); + struct snd_ice1712 *ice = spec->ice; + u32 st, bits; + int i; + + snd_ice1712_save_gpio_status(ice); + + st = ((addr & 0x7f) << 9) | (data & 0x1ff); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | PSC724_SPI_MASK); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~PSC724_SPI_MASK); + bits = snd_ice1712_gpio_read(ice) & ~PSC724_SPI_MASK; + snd_ice1712_gpio_write(ice, bits); + + for (i = 0; i < 16; i++) { + udelay(PSC724_SPI_DELAY); + bits &= ~PSC724_SPI_CLK; + /* MSB first */ + st <<= 1; + if (st & 0x10000) + bits |= PSC724_SPI_DATA; + else + bits &= ~PSC724_SPI_DATA; + snd_ice1712_gpio_write(ice, bits); + /* CLOCK high */ + udelay(PSC724_SPI_DELAY); + bits |= PSC724_SPI_CLK; + snd_ice1712_gpio_write(ice, bits); + } + /* LOAD high */ + udelay(PSC724_SPI_DELAY); + bits |= PSC724_SPI_LOAD; + snd_ice1712_gpio_write(ice, bits); + /* LOAD low, DATA and CLOCK high */ + udelay(PSC724_SPI_DELAY); + bits |= (PSC724_SPI_DATA | PSC724_SPI_CLK); + snd_ice1712_gpio_write(ice, bits); + + snd_ice1712_restore_gpio_status(ice); +} + +static void psc724_wm8776_write(struct snd_wm8776 *wm, u8 addr, u8 data) +{ + struct psc724_spec *spec = container_of(wm, struct psc724_spec, wm8776); + + snd_vt1724_write_i2c(spec->ice, 0x34, addr, data); +} + +/* mute all */ + +static void psc724_set_master_switch(struct snd_ice1712 *ice, bool on) +{ + unsigned int bits = snd_ice1712_gpio_read(ice); + struct psc724_spec *spec = ice->spec; + + spec->mute_all = !on; + if (on) + bits &= ~(GPIO_MUTE_ALL | GPIO_MUTE_SUR); + else + bits |= GPIO_MUTE_ALL | GPIO_MUTE_SUR; + snd_ice1712_gpio_write(ice, bits); +} + +static bool psc724_get_master_switch(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec = ice->spec; + + return !spec->mute_all; +} + +/* jack detection */ + +static void psc724_set_jack_state(struct snd_ice1712 *ice, bool hp_connected) +{ + struct psc724_spec *spec = ice->spec; + struct snd_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 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..5bc836241 --- /dev/null +++ b/sound/pci/ice1712/quartet.c @@ -0,0 +1,1105 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Infrasonic Quartet + * + * Copyright (c) 2009 Pavel Hofman + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include +#include "quartet.h" + +struct qtet_spec { + struct ak4113 *ak4113; + unsigned int scr; /* system control register */ + unsigned int mcr; /* monitoring control register */ + unsigned int cpld; /* cpld register */ +}; + +struct qtet_kcontrol_private { + unsigned int bit; + void (*set_register)(struct snd_ice1712 *ice, unsigned int val); + unsigned int (*get_register)(struct snd_ice1712 *ice); + const char * const texts[2]; +}; + +enum { + IN12_SEL = 0, + IN34_SEL, + AIN34_SEL, + COAX_OUT, + IN12_MON12, + IN12_MON34, + IN34_MON12, + IN34_MON34, + OUT12_MON34, + OUT34_MON12, +}; + +static const char * const ext_clock_names[3] = {"IEC958 In", "Word Clock 1xFS", + "Word Clock 256xFS"}; + +/* chip address on I2C bus */ +#define AK4113_ADDR 0x26 /* S/PDIF receiver */ + +/* chip address on SPI bus */ +#define AK4620_ADDR 0x02 /* ADC/DAC */ + + +/* + * GPIO pins + */ + +/* GPIO0 - O - DATA0, def. 0 */ +#define GPIO_D0 (1<<0) +/* GPIO1 - I/O - DATA1, Jack Detect Input0 (0:present, 1:missing), def. 1 */ +#define GPIO_D1_JACKDTC0 (1<<1) +/* GPIO2 - I/O - DATA2, Jack Detect Input1 (0:present, 1:missing), def. 1 */ +#define GPIO_D2_JACKDTC1 (1<<2) +/* GPIO3 - I/O - DATA3, def. 1 */ +#define GPIO_D3 (1<<3) +/* GPIO4 - I/O - DATA4, SPI CDTO, def. 1 */ +#define GPIO_D4_SPI_CDTO (1<<4) +/* GPIO5 - I/O - DATA5, SPI CCLK, def. 1 */ +#define GPIO_D5_SPI_CCLK (1<<5) +/* GPIO6 - I/O - DATA6, Cable Detect Input (0:detected, 1:not detected */ +#define GPIO_D6_CD (1<<6) +/* GPIO7 - I/O - DATA7, Device Detect Input (0:detected, 1:not detected */ +#define GPIO_D7_DD (1<<7) +/* GPIO8 - O - CPLD Chip Select, def. 1 */ +#define GPIO_CPLD_CSN (1<<8) +/* GPIO9 - O - CPLD register read/write (0:write, 1:read), def. 0 */ +#define GPIO_CPLD_RW (1<<9) +/* GPIO10 - O - SPI Chip Select for CODEC#0, def. 1 */ +#define GPIO_SPI_CSN0 (1<<10) +/* GPIO11 - O - SPI Chip Select for CODEC#1, def. 1 */ +#define GPIO_SPI_CSN1 (1<<11) +/* GPIO12 - O - Ex. Register Output Enable (0:enable, 1:disable), def. 1, + * init 0 */ +#define GPIO_EX_GPIOE (1<<12) +/* GPIO13 - O - Ex. Register0 Chip Select for System Control Register, + * def. 1 */ +#define GPIO_SCR (1<<13) +/* GPIO14 - O - Ex. Register1 Chip Select for Monitor Control Register, + * def. 1 */ +#define GPIO_MCR (1<<14) + +#define GPIO_SPI_ALL (GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK |\ + GPIO_SPI_CSN0 | GPIO_SPI_CSN1) + +#define GPIO_DATA_MASK (GPIO_D0 | GPIO_D1_JACKDTC0 | \ + GPIO_D2_JACKDTC1 | GPIO_D3 | \ + GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK | \ + GPIO_D6_CD | GPIO_D7_DD) + +/* System Control Register GPIO_SCR data bits */ +/* Mic/Line select relay (0:line, 1:mic) */ +#define SCR_RELAY GPIO_D0 +/* Phantom power drive control (0:5V, 1:48V) */ +#define SCR_PHP_V GPIO_D1_JACKDTC0 +/* H/W mute control (0:Normal, 1:Mute) */ +#define SCR_MUTE GPIO_D2_JACKDTC1 +/* Phantom power control (0:Phantom on, 1:off) */ +#define SCR_PHP GPIO_D3 +/* Analog input 1/2 Source Select */ +#define SCR_AIN12_SEL0 GPIO_D4_SPI_CDTO +#define SCR_AIN12_SEL1 GPIO_D5_SPI_CCLK +/* Analog input 3/4 Source Select (0:line, 1:hi-z) */ +#define SCR_AIN34_SEL GPIO_D6_CD +/* Codec Power Down (0:power down, 1:normal) */ +#define SCR_CODEC_PDN GPIO_D7_DD + +#define SCR_AIN12_LINE (0) +#define SCR_AIN12_MIC (SCR_AIN12_SEL0) +#define SCR_AIN12_LOWCUT (SCR_AIN12_SEL1 | SCR_AIN12_SEL0) + +/* Monitor Control Register GPIO_MCR data bits */ +/* Input 1/2 to Monitor 1/2 (0:off, 1:on) */ +#define MCR_IN12_MON12 GPIO_D0 +/* Input 1/2 to Monitor 3/4 (0:off, 1:on) */ +#define MCR_IN12_MON34 GPIO_D1_JACKDTC0 +/* Input 3/4 to Monitor 1/2 (0:off, 1:on) */ +#define MCR_IN34_MON12 GPIO_D2_JACKDTC1 +/* Input 3/4 to Monitor 3/4 (0:off, 1:on) */ +#define MCR_IN34_MON34 GPIO_D3 +/* Output to Monitor 1/2 (0:off, 1:on) */ +#define MCR_OUT34_MON12 GPIO_D4_SPI_CDTO +/* Output to Monitor 3/4 (0:off, 1:on) */ +#define MCR_OUT12_MON34 GPIO_D5_SPI_CCLK + +/* CPLD Register DATA bits */ +/* Clock Rate Select */ +#define CPLD_CKS0 GPIO_D0 +#define CPLD_CKS1 GPIO_D1_JACKDTC0 +#define CPLD_CKS2 GPIO_D2_JACKDTC1 +/* Sync Source Select (0:Internal, 1:External) */ +#define CPLD_SYNC_SEL GPIO_D3 +/* Word Clock FS Select (0:FS, 1:256FS) */ +#define CPLD_WORD_SEL GPIO_D4_SPI_CDTO +/* Coaxial Output Source (IS-Link) (0:SPDIF, 1:I2S) */ +#define CPLD_COAX_OUT GPIO_D5_SPI_CCLK +/* Input 1/2 Source Select (0:Analog12, 1:An34) */ +#define CPLD_IN12_SEL GPIO_D6_CD +/* Input 3/4 Source Select (0:Analog34, 1:Digital In) */ +#define CPLD_IN34_SEL GPIO_D7_DD + +/* internal clock (CPLD_SYNC_SEL = 0) options */ +#define CPLD_CKS_44100HZ (0) +#define CPLD_CKS_48000HZ (CPLD_CKS0) +#define CPLD_CKS_88200HZ (CPLD_CKS1) +#define CPLD_CKS_96000HZ (CPLD_CKS1 | CPLD_CKS0) +#define CPLD_CKS_176400HZ (CPLD_CKS2) +#define CPLD_CKS_192000HZ (CPLD_CKS2 | CPLD_CKS0) + +#define CPLD_CKS_MASK (CPLD_CKS0 | CPLD_CKS1 | CPLD_CKS2) + +/* external clock (CPLD_SYNC_SEL = 1) options */ +/* external clock - SPDIF */ +#define CPLD_EXT_SPDIF (0 | CPLD_SYNC_SEL) +/* external clock - WordClock 1xfs */ +#define CPLD_EXT_WORDCLOCK_1FS (CPLD_CKS1 | CPLD_SYNC_SEL) +/* external clock - WordClock 256xfs */ +#define CPLD_EXT_WORDCLOCK_256FS (CPLD_CKS1 | CPLD_WORD_SEL |\ + CPLD_SYNC_SEL) + +#define EXT_SPDIF_TYPE 0 +#define EXT_WORDCLOCK_1FS_TYPE 1 +#define EXT_WORDCLOCK_256FS_TYPE 2 + +#define AK4620_DFS0 (1<<0) +#define AK4620_DFS1 (1<<1) +#define AK4620_CKS0 (1<<2) +#define AK4620_CKS1 (1<<3) +/* Clock and Format Control register */ +#define AK4620_DFS_REG 0x02 + +/* Deem and Volume Control register */ +#define AK4620_DEEMVOL_REG 0x03 +#define AK4620_SMUTE (1<<7) + +/* + * Conversion from int value to its binary form. Used for debugging. + * The output buffer must be allocated prior to calling the function. + */ +static char *get_binary(char *buffer, int value) +{ + int i, j, pos; + pos = 0; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 8; ++j) { + if (value & (1 << (31-(i*8 + j)))) + buffer[pos] = '1'; + else + buffer[pos] = '0'; + pos++; + } + if (i < 3) { + buffer[pos] = ' '; + pos++; + } + } + buffer[pos] = '\0'; + return buffer; +} + +/* + * Initial setup of the conversion array GPIO <-> rate + */ +static const unsigned int qtet_rates[] = { + 44100, 48000, 88200, + 96000, 176400, 192000, +}; + +static const unsigned int cks_vals[] = { + CPLD_CKS_44100HZ, CPLD_CKS_48000HZ, CPLD_CKS_88200HZ, + CPLD_CKS_96000HZ, CPLD_CKS_176400HZ, CPLD_CKS_192000HZ, +}; + +static const struct snd_pcm_hw_constraint_list qtet_rates_info = { + .count = ARRAY_SIZE(qtet_rates), + .list = qtet_rates, + .mask = 0, +}; + +static void qtet_ak4113_write(void *private_data, unsigned char reg, + unsigned char val) +{ + snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4113_ADDR, + reg, val); +} + +static unsigned char qtet_ak4113_read(void *private_data, unsigned char reg) +{ + return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data, + AK4113_ADDR, reg); +} + + +/* + * AK4620 section + */ + +/* + * Write data to addr register of ak4620 + */ +static void qtet_akm_write(struct snd_akm4xxx *ak, int chip, + unsigned char addr, unsigned char data) +{ + unsigned int tmp, orig_dir; + int idx; + unsigned int addrdata; + struct snd_ice1712 *ice = ak->private_data[0]; + + if (snd_BUG_ON(chip < 0 || chip >= 4)) + return; + /*dev_dbg(ice->card->dev, "Writing to AK4620: chip=%d, addr=0x%x, + data=0x%x\n", chip, addr, data);*/ + orig_dir = ice->gpio.get_dir(ice); + ice->gpio.set_dir(ice, orig_dir | GPIO_SPI_ALL); + /* set mask - only SPI bits */ + ice->gpio.set_mask(ice, ~GPIO_SPI_ALL); + + tmp = ice->gpio.get_data(ice); + /* high all */ + tmp |= GPIO_SPI_ALL; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* drop chip select */ + if (chip) + /* CODEC 1 */ + tmp &= ~GPIO_SPI_CSN1; + else + tmp &= ~GPIO_SPI_CSN0; + ice->gpio.set_data(ice, tmp); + udelay(100); + + /* build I2C address + data byte */ + addrdata = (AK4620_ADDR << 6) | 0x20 | (addr & 0x1f); + addrdata = (addrdata << 8) | data; + for (idx = 15; idx >= 0; idx--) { + /* drop clock */ + tmp &= ~GPIO_D5_SPI_CCLK; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* set data */ + if (addrdata & (1 << idx)) + tmp |= GPIO_D4_SPI_CDTO; + else + tmp &= ~GPIO_D4_SPI_CDTO; + ice->gpio.set_data(ice, tmp); + udelay(100); + /* raise clock */ + tmp |= GPIO_D5_SPI_CCLK; + ice->gpio.set_data(ice, tmp); + udelay(100); + } + /* all back to 1 */ + tmp |= GPIO_SPI_ALL; + ice->gpio.set_data(ice, tmp); + udelay(100); + + /* return all gpios to non-writable */ + ice->gpio.set_mask(ice, 0xffffff); + /* restore GPIOs direction */ + ice->gpio.set_dir(ice, orig_dir); +} + +static void qtet_akm_set_regs(struct snd_akm4xxx *ak, unsigned char addr, + unsigned char mask, unsigned char value) +{ + unsigned char tmp; + int chip; + for (chip = 0; chip < ak->num_chips; chip++) { + tmp = snd_akm4xxx_get(ak, chip, addr); + /* clear the bits */ + tmp &= ~mask; + /* set the new bits */ + tmp |= value; + snd_akm4xxx_write(ak, chip, addr, tmp); + } +} + +/* + * change the rate of AK4620 + */ +static void qtet_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char ak4620_dfs; + + if (rate == 0) /* no hint - S/PDIF input is master or the new spdif + input rate undetected, simply return */ + return; + + /* adjust DFS on codecs - see datasheet */ + if (rate > 108000) + ak4620_dfs = AK4620_DFS1 | AK4620_CKS1; + else if (rate > 54000) + ak4620_dfs = AK4620_DFS0 | AK4620_CKS0; + else + ak4620_dfs = 0; + + /* set new value */ + qtet_akm_set_regs(ak, AK4620_DFS_REG, AK4620_DFS0 | AK4620_DFS1 | + AK4620_CKS0 | AK4620_CKS1, ak4620_dfs); +} + +#define AK_CONTROL(xname, xch) { .name = xname, .num_channels = xch } + +#define PCM_12_PLAYBACK_VOLUME "PCM 1/2 Playback Volume" +#define PCM_34_PLAYBACK_VOLUME "PCM 3/4 Playback Volume" +#define PCM_12_CAPTURE_VOLUME "PCM 1/2 Capture Volume" +#define PCM_34_CAPTURE_VOLUME "PCM 3/4 Capture Volume" + +static const struct snd_akm4xxx_dac_channel qtet_dac[] = { + AK_CONTROL(PCM_12_PLAYBACK_VOLUME, 2), + AK_CONTROL(PCM_34_PLAYBACK_VOLUME, 2), +}; + +static const struct snd_akm4xxx_adc_channel qtet_adc[] = { + AK_CONTROL(PCM_12_CAPTURE_VOLUME, 2), + AK_CONTROL(PCM_34_CAPTURE_VOLUME, 2), +}; + +static const struct snd_akm4xxx akm_qtet_dac = { + .type = SND_AK4620, + .num_dacs = 4, /* DAC1 - Output 12 + */ + .num_adcs = 4, /* ADC1 - Input 12 + */ + .ops = { + .write = qtet_akm_write, + .set_rate_val = qtet_akm_set_rate_val, + }, + .dac_info = qtet_dac, + .adc_info = qtet_adc, +}; + +/* Communication routines with the CPLD */ + + +/* Writes data to external register reg, both reg and data are + * GPIO representations */ +static void reg_write(struct snd_ice1712 *ice, unsigned int reg, + unsigned int data) +{ + unsigned int tmp; + + 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) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(ice->card, "quartet", &entry)) + snd_info_set_text_ops(entry, 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 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 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 char *slave_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_slaves(struct snd_card *card, + struct snd_kcontrol *master, char * const *list) +{ + for (; *list; list++) { + struct snd_kcontrol *slave = ctl_find(card, *list); + if (slave) + snd_ctl_add_slave(master, slave); + } +} + +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_slaves(ice->card, vmaster, slave_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 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..6669c389f --- /dev/null +++ b/sound/pci/ice1712/revo.c @@ -0,0 +1,645 @@ +/* + * ALSA driver for ICEnsemble ICE1712 (Envy24) + * + * Lowlevel functions for M-Audio Audiophile 192, Revolution 7.1 and 5.1 + * + * Copyright (c) 2003 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "revo.h" + +/* a non-standard I2C device for revo51 */ +struct revo51_spec { + struct snd_i2c_device *dev; + struct snd_pt2258 *pt2258; + struct ak4114 *ak4114; +}; + +static void revo_i2s_mclk_changed(struct snd_ice1712 *ice) +{ + /* assert PRST# to converters; MT05 bit 7 */ + outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD)); + mdelay(5); + /* deassert PRST# */ + outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD)); +} + +/* + * change the rate of Envy24HT, AK4355 and AK4381 + */ +static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) +{ + unsigned char old, tmp, dfs; + int reg, shift; + + if (rate == 0) /* no hint - S/PDIF input is master, simply return */ + return; + + /* adjust DFS on codecs */ + if (rate > 96000) + dfs = 2; + else if (rate > 48000) + dfs = 1; + else + dfs = 0; + + if (ak->type == SND_AK4355 || ak->type == SND_AK4358) { + reg = 2; + shift = 4; + } else { + reg = 1; + shift = 3; + } + tmp = snd_akm4xxx_get(ak, 0, reg); + old = (tmp >> shift) & 0x03; + if (old == dfs) + return; + + /* reset DFS */ + snd_akm4xxx_reset(ak, 1); + tmp = snd_akm4xxx_get(ak, 0, reg); + tmp &= ~(0x03 << shift); + tmp |= dfs << shift; + /* snd_akm4xxx_write(ak, 0, reg, tmp); */ + snd_akm4xxx_set(ak, 0, reg, tmp); /* value is written in reset(0) */ + snd_akm4xxx_reset(ak, 0); +} + +/* + * I2C access to the PT2258 volume controller on GPIO 6/7 (Revolution 5.1) + */ + +static void revo_i2c_start(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + snd_ice1712_save_gpio_status(ice); +} + +static void revo_i2c_stop(struct snd_i2c_bus *bus) +{ + struct snd_ice1712 *ice = bus->private_data; + snd_ice1712_restore_gpio_status(ice); +} + +static void revo_i2c_direction(struct snd_i2c_bus *bus, int clock, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned int mask, val; + + val = 0; + if (clock) + val |= VT1724_REVO_I2C_CLOCK; /* write SCL */ + if (data) + val |= VT1724_REVO_I2C_DATA; /* write SDA */ + mask = VT1724_REVO_I2C_CLOCK | VT1724_REVO_I2C_DATA; + ice->gpio.direction &= ~mask; + ice->gpio.direction |= val; + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction); + snd_ice1712_gpio_set_mask(ice, ~mask); +} + +static void revo_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data) +{ + struct snd_ice1712 *ice = bus->private_data; + unsigned int val = 0; + + if (clk) + val |= VT1724_REVO_I2C_CLOCK; + if (data) + val |= VT1724_REVO_I2C_DATA; + snd_ice1712_gpio_write_bits(ice, + VT1724_REVO_I2C_DATA | + VT1724_REVO_I2C_CLOCK, val); + udelay(5); +} + +static int revo_i2c_getdata(struct snd_i2c_bus *bus, int ack) +{ + struct snd_ice1712 *ice = bus->private_data; + int bit; + + if (ack) + udelay(5); + bit = snd_ice1712_gpio_read_bits(ice, VT1724_REVO_I2C_DATA) ? 1 : 0; + return bit; +} + +static struct snd_i2c_bit_ops revo51_bit_ops = { + .start = revo_i2c_start, + .stop = revo_i2c_stop, + .direction = revo_i2c_direction, + .setlines = revo_i2c_setlines, + .getdata = revo_i2c_getdata, +}; + +static int revo51_i2c_init(struct snd_ice1712 *ice, + struct snd_pt2258 *pt) +{ + struct revo51_spec *spec; + int err; + + spec = kzalloc(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..a3ba42591 --- /dev/null +++ b/sound/pci/ice1712/revo.h @@ -0,0 +1,55 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..1c5d5b22c --- /dev/null +++ b/sound/pci/ice1712/se.c @@ -0,0 +1,766 @@ +/* + * 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) -> @ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "se.h" + +struct se_spec { + struct { + unsigned char ch1, ch2; + } vol[8]; +}; + +/****************************************************************************/ +/* ONKYO WAVIO SE-200PCI */ +/****************************************************************************/ +/* + * system configuration ICE_EEP2_SYSCONF=0x4b + * XIN1 49.152MHz + * not have UART + * one stereo ADC and a S/PDIF receiver connected + * four stereo DACs connected + * + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * use I2C, not use AC97 + * + * I2S converters feature ICE_EEP2_I2S=0x78 + * I2S codec has no volume/mute control feature + * I2S codec supports 96KHz and 192KHz + * I2S codec 24bits + * + * S/PDIF configuration ICE_EEP2_SPDIF=0xc3 + * Enable integrated S/PDIF transmitter + * internal S/PDIF out implemented + * S/PDIF is stereo + * External S/PDIF out implemented + * + * + * ** connected chips ** + * + * WM8740 + * A 2ch-DAC of main outputs. + * It setuped as I2S mode by wire, so no way to setup from software. + * The sample-rate are automatically changed. + * ML/I2S (28pin) --------+ + * MC/DM1 (27pin) -- 5V | + * MD/DM0 (26pin) -- GND | + * MUTEB (25pin) -- NC | + * MODE (24pin) -- GND | + * CSBIW (23pin) --------+ + * | + * RSTB (22pin) --R(1K)-+ + * Probably it reduce the noise from the control line. + * + * WM8766 + * A 6ch-DAC for surrounds. + * It's control wire was connected to GPIOxx (3-wire serial interface) + * ML/I2S (11pin) -- GPIO18 + * MC/IWL (12pin) -- GPIO17 + * MD/DM (13pin) -- GPIO16 + * MUTE (14pin) -- GPIO01 + * + * WM8776 + * A 2ch-ADC(with 10ch-selector) plus 2ch-DAC. + * It's control wire was connected to SDA/SCLK (2-wire serial interface) + * MODE (16pin) -- R(1K) -- GND + * CE (17pin) -- R(1K) -- GND 2-wire mode (address=0x34) + * DI (18pin) -- SDA + * CL (19pin) -- SCLK + * + * + * ** output pins and device names ** + * + * 7.1ch name -- output connector color -- device (-D option) + * + * FRONT 2ch -- green -- plughw:0,0 + * CENTER(Lch) SUBWOOFER(Rch) -- black -- plughw:0,2,0 + * SURROUND 2ch -- orange -- plughw:0,2,1 + * SURROUND BACK 2ch -- white -- plughw:0,2,2 + * + */ + + +/****************************************************************************/ +/* WM8740 interface */ +/****************************************************************************/ + +static void se200pci_WM8740_init(struct snd_ice1712 *ice) +{ + /* nothing to do */ +} + + +static void se200pci_WM8740_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* nothing to do */ +} + + +/****************************************************************************/ +/* WM8766 interface */ +/****************************************************************************/ + +static void se200pci_WM8766_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int st; + unsigned int bits; + int i; + const unsigned int DATA = 0x010000; + const unsigned int CLOCK = 0x020000; + const unsigned int LOAD = 0x040000; + const unsigned int ALL_MASK = (DATA | CLOCK | LOAD); + + snd_ice1712_save_gpio_status(ice); + + st = ((addr & 0x7f) << 9) | (data & 0x1ff); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | ALL_MASK); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~ALL_MASK); + bits = snd_ice1712_gpio_read(ice) & ~ALL_MASK; + + snd_ice1712_gpio_write(ice, bits); + for (i = 0; i < 16; i++) { + udelay(1); + bits &= ~CLOCK; + st = (st << 1); + if (st & 0x10000) + bits |= DATA; + else + bits &= ~DATA; + + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= CLOCK; + snd_ice1712_gpio_write(ice, bits); + } + + udelay(1); + bits |= LOAD; + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= (DATA | CLOCK); + snd_ice1712_gpio_write(ice, bits); + + snd_ice1712_restore_gpio_status(ice); +} + +static void se200pci_WM8766_set_volume(struct snd_ice1712 *ice, int ch, + unsigned int vol1, unsigned int vol2) +{ + switch (ch) { + case 0: + se200pci_WM8766_write(ice, 0x000, vol1); + se200pci_WM8766_write(ice, 0x001, vol2 | 0x100); + break; + case 1: + se200pci_WM8766_write(ice, 0x004, vol1); + se200pci_WM8766_write(ice, 0x005, vol2 | 0x100); + break; + case 2: + se200pci_WM8766_write(ice, 0x006, vol1); + se200pci_WM8766_write(ice, 0x007, vol2 | 0x100); + break; + } +} + +static void se200pci_WM8766_init(struct snd_ice1712 *ice) +{ + se200pci_WM8766_write(ice, 0x1f, 0x000); /* RESET ALL */ + udelay(10); + + se200pci_WM8766_set_volume(ice, 0, 0, 0); /* volume L=0 R=0 */ + se200pci_WM8766_set_volume(ice, 1, 0, 0); /* volume L=0 R=0 */ + se200pci_WM8766_set_volume(ice, 2, 0, 0); /* volume L=0 R=0 */ + + se200pci_WM8766_write(ice, 0x03, 0x022); /* serial mode I2S-24bits */ + se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ + se200pci_WM8766_write(ice, 0x12, 0x000); /* MDP=0 */ + se200pci_WM8766_write(ice, 0x15, 0x000); /* MDP=0 */ + se200pci_WM8766_write(ice, 0x09, 0x000); /* demp=off mute=off */ + + se200pci_WM8766_write(ice, 0x02, 0x124); /* ch-assign L=L R=R RESET */ + se200pci_WM8766_write(ice, 0x02, 0x120); /* ch-assign L=L R=R */ +} + +static void se200pci_WM8766_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + if (rate > 96000) + se200pci_WM8766_write(ice, 0x0a, 0x000); /* MCLK=128fs */ + else + se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ +} + + +/****************************************************************************/ +/* WM8776 interface */ +/****************************************************************************/ + +static void se200pci_WM8776_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int val; + + val = (addr << 9) | data; + snd_vt1724_write_i2c(ice, 0x34, val >> 8, val & 0xff); +} + + +static void se200pci_WM8776_set_output_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + se200pci_WM8776_write(ice, 0x03, vol1); + se200pci_WM8776_write(ice, 0x04, vol2 | 0x100); +} + +static void se200pci_WM8776_set_input_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + se200pci_WM8776_write(ice, 0x0e, vol1); + se200pci_WM8776_write(ice, 0x0f, vol2 | 0x100); +} + +static const char * const se200pci_sel[] = { + "LINE-IN", "CD-IN", "MIC-IN", "ALL-MIX", NULL +}; + +static void se200pci_WM8776_set_input_selector(struct snd_ice1712 *ice, + unsigned int sel) +{ + static 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 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 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 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..5dbb867e6 --- /dev/null +++ b/sound/pci/ice1712/vt1720_mobo.c @@ -0,0 +1,138 @@ +/* + * ALSA driver for VT1720/VT1724 (Envy24PT/Envy24HT) + * + * Lowlevel functions for VT1720-based motherboards + * + * Copyright (c) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "vt1720_mobo.h" + + +static int k8x800_init(struct snd_ice1712 *ice) +{ + ice->vt1720 = 1; + + /* VT1616 codec */ + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + + /* WM8728 codec */ + /* FIXME: TODO */ + + return 0; +} + +static int k8x800_add_controls(struct snd_ice1712 *ice) +{ + /* FIXME: needs some quirks for VT1616? */ + return 0; +} + +/* EEPROM image */ + +static 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 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..0b1b0ee1b --- /dev/null +++ b/sound/pci/ice1712/vt1720_mobo.h @@ -0,0 +1,41 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..27c03e40c --- /dev/null +++ b/sound/pci/ice1712/wm8766.c @@ -0,0 +1,346 @@ +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8766 codec + * + * Copyright (c) 2012 Ondrej Zary + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "wm8766.h" + +/* low-level access */ + +static void snd_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data) +{ + if (addr < WM8766_REG_COUNT) + wm->regs[addr] = data; + wm->ops.write(wm, addr, data); +} + +/* mixer controls */ + +static const DECLARE_TLV_DB_SCALE(wm8766_tlv, -12750, 50, 1); + +static 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..18c8d9d47 --- /dev/null +++ b/sound/pci/ice1712/wm8766.h @@ -0,0 +1,161 @@ +#ifndef __SOUND_WM8766_H +#define __SOUND_WM8766_H + +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8766 codec + * + * Copyright (c) 2012 Ondrej Zary + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..553669b10 --- /dev/null +++ b/sound/pci/ice1712/wm8776.c @@ -0,0 +1,619 @@ +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8776 codec + * + * Copyright (c) 2012 Ondrej Zary + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "wm8776.h" + +/* low-level access */ + +static void snd_wm8776_write(struct snd_wm8776 *wm, u16 addr, u16 data) +{ + u8 bus_addr = addr << 1 | data >> 8; /* addr + 9th data bit */ + u8 bus_data = data & 0xff; /* remaining 8 data bits */ + + if (addr < WM8776_REG_RESET) + wm->regs[addr] = data; + wm->ops.write(wm, bus_addr, bus_data); +} + +/* register-level functions */ + +static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm, + const char *ctl_name, + bool active) +{ + struct snd_card *card = wm->card; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + 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 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..42acef055 --- /dev/null +++ b/sound/pci/ice1712/wm8776.h @@ -0,0 +1,223 @@ +#ifndef __SOUND_WM8776_H +#define __SOUND_WM8776_H + +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8776 codec + * + * Copyright (c) 2012 Ondrej Zary + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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..9906119e0 --- /dev/null +++ b/sound/pci/ice1712/wtm.c @@ -0,0 +1,646 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Ego Sys Waveterminal 192M + * + * Copyright (c) 2006 Guedez Clement + * Some functions are taken from the Prodigy192 driver + * source + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + + +#include +#include +#include +#include +#include +#include + +#include "ice1712.h" +#include "envy24ht.h" +#include "wtm.h" +#include "stac946x.h" + +struct wtm_spec { + /* rate change needs atomic mute/unmute of all dacs*/ + struct mutex mute_mutex; +}; + + +/* + * 2*ADC 6*DAC no1 ringbuffer r/w on i2c bus + */ +static inline void stac9460_put(struct snd_ice1712 *ice, int reg, + unsigned char val) +{ + snd_vt1724_write_i2c(ice, STAC9460_I2C_ADDR, reg, val); +} + +static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg) +{ + return snd_vt1724_read_i2c(ice, STAC9460_I2C_ADDR, reg); +} + +/* + * 2*ADC 2*DAC no2 ringbuffer r/w on i2c bus + */ +static inline void stac9460_2_put(struct snd_ice1712 *ice, int reg, + unsigned char val) +{ + snd_vt1724_write_i2c(ice, STAC9460_2_I2C_ADDR, reg, val); +} + +static inline unsigned char stac9460_2_get(struct snd_ice1712 *ice, int reg) +{ + return snd_vt1724_read_i2c(ice, STAC9460_2_I2C_ADDR, reg); +} + + +/* + * DAC mute control + */ +static void stac9460_dac_mute_all(struct snd_ice1712 *ice, unsigned char mute, + unsigned short int *change_mask) +{ + unsigned char new, old; + int id, idx, change; + + /*stac9460 1*/ + for (id = 0; id < 7; id++) { + if (*change_mask & (0x01 << id)) { + if (id == 0) + idx = STAC946X_MASTER_VOLUME; + else + idx = STAC946X_LF_VOLUME - 1 + id; + old = stac9460_get(ice, idx); + new = (~mute << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) { + stac9460_put(ice, idx, new); + *change_mask = *change_mask | (0x01 << id); + } else { + *change_mask = *change_mask & ~(0x01 << id); + } + } + } + + /*stac9460 2*/ + for (id = 0; id < 3; id++) { + if (*change_mask & (0x01 << (id + 7))) { + if (id == 0) + idx = STAC946X_MASTER_VOLUME; + else + idx = STAC946X_LF_VOLUME - 1 + id; + old = stac9460_2_get(ice, idx); + new = (~mute << 7 & 0x80) | (old & ~0x80); + change = (new != old); + if (change) { + stac9460_2_put(ice, idx, new); + *change_mask = *change_mask | (0x01 << id); + } else { + *change_mask = *change_mask & ~(0x01 << id); + } + } + } +} + + + +#define stac9460_dac_mute_info snd_ctl_boolean_mono_info + +static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct wtm_spec *spec = ice->spec; + unsigned char val; + int idx, id; + + 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 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 unsigned short stac_inits_wtm[] = { + STAC946X_RESET, 0, + STAC946X_MASTER_CLOCKING, 0x11, + (unsigned short)-1 + }; + 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 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..5ee468d1a --- /dev/null +++ b/sound/pci/intel8x0.c @@ -0,0 +1,3356 @@ +/* + * ALSA driver for Intel ICH (i8x0) chipsets + * + * Copyright (c) 2000 Jaroslav Kysela + * + * + * This code also contains alpha support for SiS 735 chipsets provided + * by Mike Pieper . We have no datasheet + * for SiS735, so the code is not fully functional. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* for 440MX workaround */ +#include +#ifdef CONFIG_X86 +#include +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +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 = whitelist + 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 page_attr_changed: 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; + 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); +} + +#ifdef __i386__ +/* + * 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 + */ +static void fill_nocache(void *buf, int size, int nocache) +{ + size = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + if (nocache) + set_pages_uc(virt_to_page(buf), size); + else + set_pages_wb(virt_to_page(buf), size); +} +#else +#define fill_nocache(buf, size, nocache) do { ; } while (0) +#endif + +/* + * 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; + + 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; + /* fallthru */ + 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; + /* fallthru */ + 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 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; + /* fallthru */ + 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; + /* fallthru */ + 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); + struct snd_pcm_runtime *runtime = substream->runtime; + int dbl = params_rate(hw_params) > 48000; + int err; + + if (chip->fix_nocache && ichdev->page_attr_changed) { + fill_nocache(runtime->dma_area, runtime->dma_bytes, 0); /* clear */ + ichdev->page_attr_changed = 0; + } + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return err; + if (chip->fix_nocache) { + if (runtime->dma_area && ! ichdev->page_attr_changed) { + fill_nocache(runtime->dma_area, runtime->dma_bytes, 1); + ichdev->page_attr_changed = 1; + } + } + if (ichdev->pcm_open_flag) { + snd_ac97_pcm_close(ichdev->pcm); + ichdev->pcm_open_flag = 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 intel8x0 *chip = snd_pcm_substream_chip(substream); + struct ichdev *ichdev = get_ichdev(substream); + + if (ichdev->pcm_open_flag) { + snd_ac97_pcm_close(ichdev->pcm); + ichdev->pcm_open_flag = 0; + } + if (chip->fix_nocache && ichdev->page_attr_changed) { + fill_nocache(substream->runtime->dma_area, substream->runtime->dma_bytes, 0); + ichdev->page_attr_changed = 0; + } + return snd_pcm_lib_free_pages(substream); +} + +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); + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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; +}; + +static int snd_intel8x0_pcm1(struct intel8x0 *chip, int device, + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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 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 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 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; + 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; + struct snd_ac97_bus_ops *ops; + static struct snd_ac97_bus_ops standard_bus_ops = { + .write = snd_intel8x0_codec_write, + .read = snd_intel8x0_codec_read, + }; + static 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 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) { + if (chip->fix_nocache) + fill_nocache(chip->bdbars.area, chip->bdbars.bytes, 0); + 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->pcm_devs; i++) + snd_pcm_suspend_all(chip->pcm[i]); + /* clear nocache */ + if (chip->fix_nocache) { + for (i = 0; i < chip->bdbars_count; i++) { + struct ichdev *ichdev = &chip->ichd[i]; + if (ichdev->substream && ichdev->page_attr_changed) { + struct snd_pcm_runtime *runtime = ichdev->substream->runtime; + if (runtime->dma_area) + fill_nocache(runtime->dma_area, runtime->dma_bytes, 0); + } + } + } + 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; + } + 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; + synchronize_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); + } + + /* refill nocache */ + if (chip->fix_nocache) + fill_nocache(chip->bdbars.area, chip->bdbars.bytes, 1); + + for (i = 0; i < chip->ncodecs; i++) + snd_ac97_resume(chip->ac97[i]); + + /* refill nocache */ + if (chip->fix_nocache) { + for (i = 0; i < chip->bdbars_count; i++) { + struct ichdev *ichdev = &chip->ichd[i]; + if (ichdev->substream && ichdev->page_attr_changed) { + struct snd_pcm_runtime *runtime = ichdev->substream->runtime; + if (runtime->dma_area) + fill_nocache(runtime->dma_area, runtime->dma_bytes, 1); + } + } + } + + /* 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 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, "white 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "intel8x0", &entry)) + snd_info_set_text_ops(entry, 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 unsigned int ich_codec_bits[3] = { + ICH_PCR, ICH_SCR, ICH_TCR +}; +static 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 struct snd_device_ops ops = { + .dev_free = snd_intel8x0_dev_free, + }; + + static unsigned int bdbars[] = { + 3, /* DEVICE_INTEL */ + 6, /* DEVICE_INTEL_ICH4 */ + 3, /* DEVICE_SIS */ + 6, /* DEVICE_ALI */ + 4, /* DEVICE_NFORCE */ + }; + static 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 struct ich_reg_info nforce_regs[4] = { + { ICH_PIINT, 0 }, + { ICH_POINT, 0x10 }, + { ICH_MCINT, 0x20 }, + { ICH_NVSPINT, 0x70 }, + }; + static 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 }, + }; + 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); + + 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(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + 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) */ + /* workaround for 440MX */ + if (chip->fix_nocache) + fill_nocache(chip->bdbars.area, chip->bdbars.bytes, 1); + 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; + + 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 struct snd_pci_quirk spdif_aclink_defaults[] = { + SND_PCI_QUIRK(0x147b, 0x1c1a, "ASUS KN8", 1), + { } /* end */ +}; + +/* look up white/black 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..c84629190 --- /dev/null +++ b/sound/pci/intel8x0m.c @@ -0,0 +1,1332 @@ +/* + * ALSA modem driver for Intel ICH (i8x0) chipsets + * + * Copyright (c) 2000 Jaroslav Kysela + * + * This is modified (by Sasha Khapyorsky ) version + * of ALSA ICH sound driver intel8x0.c . + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Intel 82801AA,82901AB,i810,i820,i830,i840,i845,MX440; " + "SiS 7013; NVidia MCP/2/2S/3 modems"); +MODULE_LICENSE("GPL"); +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 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 int snd_intel8x0m_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_intel8x0m_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_intel8x0m_hw_params, + .hw_free = snd_intel8x0m_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_intel8x0m_hw_params, + .hw_free = snd_intel8x0m_hw_free, + .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, + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + rec->prealloc_size, + rec->prealloc_max_size); + + return 0; +} + +static 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; + 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 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; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + for (i = 0; i < chip->pcm_devs; i++) + snd_pcm_suspend_all(chip->pcm[i]); + snd_ac97_suspend(chip->ac97); + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + chip->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; + 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "intel8x0m", &entry)) + snd_info_set_text_ops(entry, 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 struct snd_device_ops ops = { + .dev_free = snd_intel8x0m_dev_free, + }; + static struct ich_reg_info intel_regs[2] = { + { ICH_MIINT, 0 }, + { ICH_MOINT, 0x10 }, + }; + 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, snd_dma_pci_data(pci), + 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; + + 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..f11ce1b1b --- /dev/null +++ b/sound/pci/korg1212/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +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..4e189a93f --- /dev/null +++ b/sound/pci/korg1212/korg1212.c @@ -0,0 +1,2486 @@ +/* + * Driver for the Korg 1212 IO PCI card + * + * Copyright (c) 2001 Haroldo Gamal + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Debug Stuff +// ---------------------------------------------------------------------------- +#define K1212_DEBUG_LEVEL 0 +#if K1212_DEBUG_LEVEL > 0 +#define K1212_DEBUG_PRINTK(fmt,args...) printk(KERN_DEBUG fmt,##args) +#else +#define K1212_DEBUG_PRINTK(fmt,...) +#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 "); + +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 char *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 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 enum ClockSourceIndex s44[] = { + K1212_CLKIDX_AdatAt44_1K, + K1212_CLKIDX_WordAt44_1K, + K1212_CLKIDX_LocalAt44_1K + }; + static 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 struct snd_kcontrol_new snd_korg1212_controls[] = { + MON_MIXER(8, "Analog"), + MON_MIXER(10, "SPDIF"), + MON_MIXER(0, "ADAT-1"), MON_MIXER(1, "ADAT-2"), MON_MIXER(2, "ADAT-3"), MON_MIXER(3, "ADAT-4"), + MON_MIXER(4, "ADAT-5"), MON_MIXER(5, "ADAT-6"), MON_MIXER(6, "ADAT-7"), MON_MIXER(7, "ADAT-8"), + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sync Source", + .info = snd_korg1212_control_sync_info, + .get = snd_korg1212_control_sync_get, + .put = snd_korg1212_control_sync_put, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Attenuation", + .info = snd_korg1212_control_info, + .get = snd_korg1212_control_get, + .put = snd_korg1212_control_put, + } +}; + +/* + * proc interface + */ + +static void snd_korg1212_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int n; + struct snd_korg1212 *korg1212 = entry->private_data; + + snd_iprintf(buffer, korg1212->card->longname); + snd_iprintf(buffer, " (index #%d)\n", korg1212->card->number + 1); + snd_iprintf(buffer, "\nGeneral settings\n"); + snd_iprintf(buffer, " period size: %zd bytes\n", K1212_PERIOD_BYTES); + snd_iprintf(buffer, " clock mode: %s\n", clockSourceName[korg1212->clkSrcRate] ); + snd_iprintf(buffer, " left ADC Sens: %d\n", korg1212->leftADCInSens ); + snd_iprintf(buffer, " right ADC Sens: %d\n", korg1212->rightADCInSens ); + snd_iprintf(buffer, " Volume Info:\n"); + for (n=0; n %s [%d]\n", n, + channelName[n], + channelName[korg1212->sharedBufferPtr->routeData[n]], + korg1212->sharedBufferPtr->volumeData[n]); + snd_iprintf(buffer, "\nGeneral status\n"); + snd_iprintf(buffer, " ADAT Time Code: %d\n", korg1212->sharedBufferPtr->AdatTimeCode); + snd_iprintf(buffer, " Card State: %s\n", stateName[korg1212->cardState]); + snd_iprintf(buffer, "Idle mon. State: %d\n", korg1212->idleMonitorOn); + snd_iprintf(buffer, "Cmd retry count: %d\n", korg1212->cmdRetryCount); + snd_iprintf(buffer, " Irq count: %ld\n", korg1212->irqcount); + snd_iprintf(buffer, " Error count: %ld\n", korg1212->totalerrorcnt); +} + +static void snd_korg1212_proc_init(struct snd_korg1212 *korg1212) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(korg1212->card, "korg1212", &entry)) + snd_info_set_text_ops(entry, 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 ioport_size, iomem_size, iomem2_size; + struct snd_korg1212 * korg1212; + const struct firmware *dsp_code; + + static 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; ivolumePhase[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; + + 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, snd_dma_pci_data(pci), + 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, snd_dma_pci_data(pci), + 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, snd_dma_pci_data(pci), + 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, snd_dma_pci_data(pci), + 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..8178a2a59 --- /dev/null +++ b/sound/pci/lola/Makefile @@ -0,0 +1,4 @@ +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..254f24366 --- /dev/null +++ b/sound/pci/lola/lola.c @@ -0,0 +1,775 @@ +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lola.h" + +/* Standard options */ +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Digigram Lola driver."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Digigram Lola driver."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Digigram Lola driver."); + +/* Lola-specific options */ + +/* for instance use always max granularity which is compatible + * with all sample rates + */ +static int granularity[SNDRV_CARDS] = { + [0 ... (SNDRV_CARDS - 1)] = LOLA_GRANULARITY_MAX +}; + +/* below a sample_rate of 16kHz the analogue audio quality is NOT excellent */ +static int sample_rate_min[SNDRV_CARDS] = { + [0 ... (SNDRV_CARDS - 1) ] = 16000 +}; + +module_param_array(granularity, int, NULL, 0444); +MODULE_PARM_DESC(granularity, "Granularity value"); +module_param_array(sample_rate_min, int, NULL, 0444); +MODULE_PARM_DESC(sample_rate_min, "Minimal sample rate"); + +/* + */ + +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Digigram, Lola}}"); +MODULE_DESCRIPTION("Digigram Lola driver"); +MODULE_AUTHOR("Takashi Iwai "); + +#ifdef CONFIG_SND_DEBUG_VERBOSE +static int debug; +module_param(debug, int, 0644); +#define verbose_debug(fmt, args...) \ + do { if (debug > 1) pr_debug(SFX fmt, ##args); } while (0) +#else +#define verbose_debug(fmt, args...) +#endif + +/* + * pseudo-codec read/write via CORB/RIRB + */ + +static int corb_send_verb(struct lola *chip, unsigned int nid, + unsigned int verb, unsigned int data, + unsigned int extdata) +{ + 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, + snd_dma_pci_data(chip->pci), + 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 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; + synchronize_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..bd852fed8 --- /dev/null +++ b/sound/pci/lola/lola.h @@ -0,0 +1,527 @@ +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#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..2bef6b412 --- /dev/null +++ b/sound/pci/lola/lola_clock.c @@ -0,0 +1,323 @@ +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include "lola.h" + +unsigned int lola_sample_rate_convert(unsigned int coded) +{ + unsigned int freq; + + /* base frequency */ + switch (coded & 0x3) { + case 0: freq = 48000; break; + case 1: freq = 44100; break; + case 2: freq = 32000; break; + default: return 0; /* error */ + } + + /* multiplier / devisor */ + switch (coded & 0x1c) { + case (0 << 2): break; + case (4 << 2): break; + case (1 << 2): freq *= 2; break; + case (2 << 2): freq *= 4; break; + case (5 << 2): freq /= 2; break; + case (6 << 2): freq /= 4; break; + default: return 0; /* error */ + } + + /* 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..cb25acf7b --- /dev/null +++ b/sound/pci/lola/lola_mixer.c @@ -0,0 +1,896 @@ +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "lola.h" + +static int lola_init_pin(struct lola *chip, struct lola_pin *pin, + int dir, int nid) +{ + unsigned int val; + int err; + + pin->nid = nid; + err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read wcaps for 0x%x\n", nid); + return err; + } + val &= 0x00f00fff; /* test TYPE and bits 0..11 */ + if (val == 0x00400200) /* Type = 4, Digital = 1 */ + pin->is_analog = false; + else if (val == 0x0040000a && dir == CAPT) /* Dig=0, InAmp/ovrd */ + pin->is_analog = true; + else if (val == 0x0040000c && dir == PLAY) /* Dig=0, OutAmp/ovrd */ + pin->is_analog = true; + else { + dev_err(chip->card->dev, "Invalid wcaps 0x%x for 0x%x\n", val, nid); + return -EINVAL; + } + + /* analog parameters only following, so continue in case of Digital pin + */ + if (!pin->is_analog) + return 0; + + if (dir == PLAY) + err = lola_read_param(chip, nid, LOLA_PAR_AMP_OUT_CAP, &val); + else + err = lola_read_param(chip, nid, LOLA_PAR_AMP_IN_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read AMP-caps for 0x%x\n", nid); + return err; + } + + pin->amp_mute = LOLA_AMP_MUTE_CAPABLE(val); + pin->amp_step_size = LOLA_AMP_STEP_SIZE(val); + pin->amp_num_steps = LOLA_AMP_NUM_STEPS(val); + if (pin->amp_num_steps) { + /* zero as mute state */ + pin->amp_num_steps++; + pin->amp_step_size++; + } + pin->amp_offset = LOLA_AMP_OFFSET(val); + + err = lola_codec_read(chip, nid, LOLA_VERB_GET_MAX_LEVEL, 0, 0, &val, + NULL); + if (err < 0) { + dev_err(chip->card->dev, "Can't get MAX_LEVEL 0x%x\n", nid); + return err; + } + pin->max_level = val & 0x3ff; /* 10 bits */ + + pin->config_default_reg = 0; + pin->fixed_gain_list_len = 0; + pin->cur_gain_step = 0; + + return 0; +} + +int lola_init_pins(struct lola *chip, int dir, int *nidp) +{ + int i, err, nid; + nid = *nidp; + for (i = 0; i < chip->pin[dir].num_pins; i++, nid++) { + err = lola_init_pin(chip, &chip->pin[dir].pins[i], dir, nid); + if (err < 0) + return err; + if (chip->pin[dir].pins[i].is_analog) + chip->pin[dir].num_analog_pins++; + } + *nidp = nid; + return 0; +} + +void lola_free_mixer(struct lola *chip) +{ + vfree(chip->mixer.array_saved); +} + +int lola_init_mixer_widget(struct lola *chip, int nid) +{ + unsigned int val; + int err; + + err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read wcaps for 0x%x\n", nid); + return err; + } + + if ((val & 0xfff00000) != 0x02f00000) { /* test SubType and Type */ + dev_dbg(chip->card->dev, "No valid mixer widget\n"); + return 0; + } + + chip->mixer.nid = nid; + chip->mixer.caps = val; + chip->mixer.array = (struct lola_mixer_array __iomem *) + (chip->bar[BAR1].remap_addr + LOLA_BAR1_SOURCE_GAIN_ENABLE); + + /* reserve memory to copy mixer data for sleep mode transitions */ + chip->mixer.array_saved = vmalloc(sizeof(struct lola_mixer_array)); + + /* 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..e70276c3e --- /dev/null +++ b/sound/pci/lola/lola_pcm.c @@ -0,0 +1,710 @@ +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "lola.h" + +#define LOLA_MAX_BDL_ENTRIES 8 +#define LOLA_MAX_BUF_SIZE (1024*1024*1024) +#define LOLA_BDL_ENTRY_SIZE (16 * 16) + +static struct lola_pcm *lola_get_pcm(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + return &chip->pcm[substream->stream]; +} + +static struct lola_stream *lola_get_stream(struct snd_pcm_substream *substream) +{ + struct lola_pcm *pcm = lola_get_pcm(substream); + unsigned int idx = substream->number; + return &pcm->streams[idx]; +} + +static unsigned int lola_get_lrc(struct lola *chip) +{ + return lola_readl(chip, BAR1, LRC); +} + +static unsigned int lola_get_tstamp(struct lola *chip, bool quick_no_sync) +{ + unsigned int tstamp = lola_get_lrc(chip) >> 8; + if (chip->granularity) { + unsigned int wait_banks = quick_no_sync ? 0 : 8; + tstamp += (wait_banks + 1) * chip->granularity - 1; + tstamp -= tstamp % chip->granularity; + } + return tstamp << 8; +} + +/* clear any pending interrupt status */ +static void lola_stream_clear_pending_irq(struct lola *chip, + struct lola_stream *str) +{ + unsigned int val = lola_dsd_read(chip, str->dsd, STS); + val &= LOLA_DSD_STS_DESE | LOLA_DSD_STS_BCIS; + if (val) + lola_dsd_write(chip, str->dsd, STS, val); +} + +static void lola_stream_start(struct lola *chip, struct lola_stream *str, + unsigned int tstamp) +{ + lola_stream_clear_pending_irq(chip, str); + lola_dsd_write(chip, str->dsd, CTL, + LOLA_DSD_CTL_SRUN | + LOLA_DSD_CTL_IOCE | + LOLA_DSD_CTL_DEIE | + LOLA_DSD_CTL_VLRCV | + tstamp); +} + +static void lola_stream_stop(struct lola *chip, struct lola_stream *str, + unsigned int tstamp) +{ + lola_dsd_write(chip, str->dsd, CTL, + LOLA_DSD_CTL_IOCE | + LOLA_DSD_CTL_DEIE | + LOLA_DSD_CTL_VLRCV | + tstamp); + lola_stream_clear_pending_irq(chip, str); +} + +static void wait_for_srst_clear(struct lola *chip, struct lola_stream *str) +{ + unsigned long end_time = jiffies + msecs_to_jiffies(200); + while (time_before(jiffies, end_time)) { + unsigned int val; + val = lola_dsd_read(chip, str->dsd, CTL); + if (!(val & LOLA_DSD_CTL_SRST)) + return; + msleep(1); + } + dev_warn(chip->card->dev, "SRST not clear (stream %d)\n", str->dsd); +} + +static int lola_stream_wait_for_fifo(struct lola *chip, + struct lola_stream *str, + bool ready) +{ + unsigned int val = ready ? LOLA_DSD_STS_FIFORDY : 0; + unsigned long end_time = jiffies + msecs_to_jiffies(200); + while (time_before(jiffies, end_time)) { + unsigned int reg = lola_dsd_read(chip, str->dsd, STS); + if ((reg & LOLA_DSD_STS_FIFORDY) == val) + return 0; + msleep(1); + } + dev_warn(chip->card->dev, "FIFO not ready (stream %d)\n", str->dsd); + return -EIO; +} + +/* sync for FIFO ready/empty for all linked streams; + * clear paused flag when FIFO gets ready again + */ +static int lola_sync_wait_for_fifo(struct lola *chip, + struct snd_pcm_substream *substream, + bool ready) +{ + unsigned int val = ready ? LOLA_DSD_STS_FIFORDY : 0; + unsigned long end_time = jiffies + msecs_to_jiffies(200); + struct snd_pcm_substream *s; + int pending = 0; + + while (time_before(jiffies, end_time)) { + pending = 0; + snd_pcm_group_for_each_entry(s, substream) { + struct lola_stream *str; + if (s->pcm->card != substream->pcm->card) + continue; + str = lola_get_stream(s); + if (str->prepared && str->paused) { + unsigned int reg; + reg = lola_dsd_read(chip, str->dsd, STS); + if ((reg & LOLA_DSD_STS_FIFORDY) != val) { + pending = str->dsd + 1; + break; + } + if (ready) + str->paused = 0; + } + } + if (!pending) + return 0; + msleep(1); + } + dev_warn(chip->card->dev, "FIFO not ready (pending %d)\n", pending - 1); + return -EIO; +} + +/* finish pause - prepare for a new resume */ +static void lola_sync_pause(struct lola *chip, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_substream *s; + + lola_sync_wait_for_fifo(chip, substream, false); + snd_pcm_group_for_each_entry(s, substream) { + struct lola_stream *str; + if (s->pcm->card != substream->pcm->card) + continue; + str = lola_get_stream(s); + if (str->paused && str->prepared) + lola_dsd_write(chip, str->dsd, CTL, LOLA_DSD_CTL_SRUN | + LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE); + } + lola_sync_wait_for_fifo(chip, substream, true); +} + +static void lola_stream_reset(struct lola *chip, struct lola_stream *str) +{ + if (str->prepared) { + if (str->paused) + lola_sync_pause(chip, str->substream); + str->prepared = 0; + lola_dsd_write(chip, str->dsd, CTL, + LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE); + lola_stream_wait_for_fifo(chip, str, false); + lola_stream_clear_pending_irq(chip, str); + lola_dsd_write(chip, str->dsd, CTL, LOLA_DSD_CTL_SRST); + lola_dsd_write(chip, str->dsd, LVI, 0); + lola_dsd_write(chip, str->dsd, BDPU, 0); + lola_dsd_write(chip, str->dsd, BDPL, 0); + wait_for_srst_clear(chip, str); + } +} + +static const struct snd_pcm_hardware lola_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_FLOAT_LE), + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = LOLA_MAX_BUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = LOLA_MAX_BUF_SIZE / 2, + .periods_min = 2, + .periods_max = LOLA_MAX_BDL_ENTRIES, + .fifo_size = 0, +}; + +static int lola_pcm_open(struct snd_pcm_substream *substream) +{ + struct lola *chip = snd_pcm_substream_chip(substream); + struct lola_pcm *pcm = lola_get_pcm(substream); + struct lola_stream *str = lola_get_stream(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + 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 snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +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 snd_pcm_lib_free_pages(substream); +} + +/* + * 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = lola_pcm_hw_params, + .hw_free = lola_pcm_hw_free, + .prepare = lola_pcm_prepare, + .trigger = lola_pcm_trigger, + .pointer = lola_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +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, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 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..904e3c4f4 --- /dev/null +++ b/sound/pci/lola/lola_proc.c @@ -0,0 +1,222 @@ +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "lola.h" + +static void print_audio_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid, const char *name) +{ + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x %s wcaps 0x%x\n", nid, name, val); + lola_read_param(chip, nid, LOLA_PAR_STREAM_FORMATS, &val); + snd_iprintf(buffer, " Formats: 0x%x\n", val); +} + +static void print_pin_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid, unsigned int ampcap, + const char *name) +{ + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x %s wcaps 0x%x\n", nid, name, val); + if (val == 0x00400200) + return; + lola_read_param(chip, nid, ampcap, &val); + snd_iprintf(buffer, " Amp-Caps: 0x%x\n", val); + snd_iprintf(buffer, " mute=%d, step-size=%d, steps=%d, ofs=%d\n", + LOLA_AMP_MUTE_CAPABLE(val), + LOLA_AMP_STEP_SIZE(val), + LOLA_AMP_NUM_STEPS(val), + LOLA_AMP_OFFSET(val)); + lola_codec_read(chip, nid, LOLA_VERB_GET_MAX_LEVEL, 0, 0, &val, NULL); + snd_iprintf(buffer, " Max-level: 0x%x\n", val); +} + +static void print_clock_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid) +{ + int i, j, num_clocks; + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x [Clock] wcaps 0x%x\n", nid, val); + num_clocks = val & 0xff; + for (i = 0; i < num_clocks; i += 4) { + unsigned int res_ex; + unsigned short items[4]; + const char *name; + + lola_codec_read(chip, nid, LOLA_VERB_GET_CLOCK_LIST, + i, 0, &val, &res_ex); + items[0] = val & 0xfff; + items[1] = (val >> 16) & 0xfff; + items[2] = res_ex & 0xfff; + items[3] = (res_ex >> 16) & 0xfff; + for (j = 0; j < 4; j++) { + unsigned char type = items[j] >> 8; + unsigned int freq = items[j] & 0xff; + if (i + j >= num_clocks) + break; + if (type == LOLA_CLOCK_TYPE_INTERNAL) { + name = "Internal"; + freq = lola_sample_rate_convert(freq); + } else if (type == LOLA_CLOCK_TYPE_VIDEO) { + name = "Video"; + freq = lola_sample_rate_convert(freq); + } else { + name = "Other"; + } + snd_iprintf(buffer, " Clock %d: Type %d:%s, freq=%d\n", + i + j, type, name, freq); + } + } +} + +static void print_mixer_widget(struct snd_info_buffer *buffer, + struct lola *chip, int nid) +{ + unsigned int val; + + lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val); + snd_iprintf(buffer, "Node 0x%02x [Mixer] wcaps 0x%x\n", nid, val); +} + +static void lola_proc_codec_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + unsigned int val; + int i, nid; + + lola_read_param(chip, 0, LOLA_PAR_VENDOR_ID, &val); + snd_iprintf(buffer, "Vendor: 0x%08x\n", val); + lola_read_param(chip, 1, LOLA_PAR_FUNCTION_TYPE, &val); + snd_iprintf(buffer, "Function Type: %d\n", val); + lola_read_param(chip, 1, LOLA_PAR_SPECIFIC_CAPS, &val); + snd_iprintf(buffer, "Specific-Caps: 0x%08x\n", val); + snd_iprintf(buffer, " Pins-In %d, Pins-Out %d\n", + chip->pin[CAPT].num_pins, chip->pin[PLAY].num_pins); + nid = 2; + for (i = 0; i < chip->pcm[CAPT].num_streams; i++, nid++) + print_audio_widget(buffer, chip, nid, "[Audio-In]"); + for (i = 0; i < chip->pcm[PLAY].num_streams; i++, nid++) + print_audio_widget(buffer, chip, nid, "[Audio-Out]"); + for (i = 0; i < chip->pin[CAPT].num_pins; i++, nid++) + print_pin_widget(buffer, chip, nid, LOLA_PAR_AMP_IN_CAP, + "[Pin-In]"); + for (i = 0; i < chip->pin[PLAY].num_pins; i++, nid++) + print_pin_widget(buffer, chip, nid, LOLA_PAR_AMP_OUT_CAP, + "[Pin-Out]"); + if (LOLA_AFG_CLOCK_WIDGET_PRESENT(chip->lola_caps)) { + print_clock_widget(buffer, chip, nid); + nid++; + } + if (LOLA_AFG_MIXER_WIDGET_PRESENT(chip->lola_caps)) { + print_mixer_widget(buffer, chip, nid); + nid++; + } +} + +/* direct codec access for debugging */ +static void lola_proc_codec_rw_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + char line[64]; + unsigned int id, verb, data, extdata; + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%u %u %u %u", &id, &verb, &data, &extdata) != 4) + continue; + lola_codec_read(chip, id, verb, data, extdata, + &chip->debug_res, + &chip->debug_res_ex); + } +} + +static void lola_proc_codec_rw_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + snd_iprintf(buffer, "0x%x 0x%x\n", chip->debug_res, chip->debug_res_ex); +} + +/* + * dump some registers + */ +static void lola_proc_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct lola *chip = entry->private_data; + int i; + + for (i = 0; i < 0x40; i += 4) { + snd_iprintf(buffer, "BAR0 %02x: %08x\n", i, + readl(chip->bar[BAR0].remap_addr + i)); + } + snd_iprintf(buffer, "\n"); + for (i = 0; i < 0x30; i += 4) { + snd_iprintf(buffer, "BAR1 %02x: %08x\n", i, + readl(chip->bar[BAR1].remap_addr + i)); + } + snd_iprintf(buffer, "\n"); + for (i = 0x80; i < 0xa0; i += 4) { + snd_iprintf(buffer, "BAR1 %02x: %08x\n", i, + readl(chip->bar[BAR1].remap_addr + i)); + } + snd_iprintf(buffer, "\n"); + for (i = 0; i < 32; i++) { + snd_iprintf(buffer, "DSD %02x STS %08x\n", i, + lola_dsd_read(chip, i, STS)); + snd_iprintf(buffer, "DSD %02x LPIB %08x\n", i, + lola_dsd_read(chip, i, LPIB)); + snd_iprintf(buffer, "DSD %02x CTL %08x\n", i, + lola_dsd_read(chip, i, CTL)); + snd_iprintf(buffer, "DSD %02x LVIL %08x\n", i, + lola_dsd_read(chip, i, LVI)); + snd_iprintf(buffer, "DSD %02x BDPL %08x\n", i, + lola_dsd_read(chip, i, BDPL)); + snd_iprintf(buffer, "DSD %02x BDPU %08x\n", i, + lola_dsd_read(chip, i, BDPU)); + } +} + +void lola_proc_debug_new(struct lola *chip) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "codec", &entry)) + snd_info_set_text_ops(entry, chip, lola_proc_codec_read); + if (!snd_card_proc_new(chip->card, "codec_rw", &entry)) { + snd_info_set_text_ops(entry, chip, lola_proc_codec_rw_read); + entry->mode |= 0200; + entry->c.text.write = lola_proc_codec_rw_write; + } + if (!snd_card_proc_new(chip->card, "regs", &entry)) + snd_info_set_text_ops(entry, chip, lola_proc_regs_read); +} diff --git a/sound/pci/lx6464es/Makefile b/sound/pci/lx6464es/Makefile new file mode 100644 index 000000000..eb04a6c73 --- /dev/null +++ b/sound/pci/lx6464es/Makefile @@ -0,0 +1,2 @@ +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..daf25655f --- /dev/null +++ b/sound/pci/lx6464es/lx6464es.c @@ -0,0 +1,1153 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * + * Copyright (c) 2008, 2009 Tim Blechmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "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) +{ + int err = 0; + dev_dbg(substream->pcm->card->dev, "->lx_pcm_close\n"); + return err; +} + +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); + int err = 0; + + dev_dbg(chip->card->dev, "->lx_pcm_hw_params\n"); + + mutex_lock(&chip->setup_mutex); + + /* set dma buffer */ + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + + if (is_capture) + chip->capture_stream.stream = substream; + else + chip->playback_stream.stream = substream; + + mutex_unlock(&chip->setup_mutex); + return err; +} + +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; + } + + err = snd_pcm_lib_free_pages(substream); + + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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); + + err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + size, size); + if (err < 0) + return err; + + 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) +{ + struct snd_info_entry *entry; + int err = snd_card_proc_new(card, "levels", &entry); + if (err < 0) + return err; + + snd_info_set_text_ops(entry, chip, lx_proc_levels_read); + return 0; +} + + +static int snd_lx6464es_create(struct snd_card *card, + struct pci_dev *pci, + struct lx6464es **rchip) +{ + struct lx6464es *chip; + int err; + + static 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; + + 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..1bec187d7 --- /dev/null +++ b/sound/pci/lx6464es/lx6464es.h @@ -0,0 +1,111 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * + * Copyright (c) 2009 Tim Blechmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef LX6464ES_H +#define LX6464ES_H + +#include +#include + +#include +#include + +#include "lx_core.h" + +#define LXP "LX6464ES: " + +enum { + ES_cmd_free = 0, /* no command executing */ + ES_cmd_processing = 1, /* execution of a read/write command */ + ES_read_pending = 2, /* a asynchron read command is pending */ + ES_read_finishing = 3, /* a read command has finished waiting (set by + * Interrupt or CancelIrp) */ +}; + +enum lx_stream_status { + LX_STREAM_STATUS_FREE, +/* LX_STREAM_STATUS_OPEN, */ + LX_STREAM_STATUS_SCHEDULE_RUN, +/* LX_STREAM_STATUS_STARTED, */ + LX_STREAM_STATUS_RUNNING, + LX_STREAM_STATUS_SCHEDULE_STOP, +/* LX_STREAM_STATUS_STOPPED, */ +/* LX_STREAM_STATUS_PAUSED */ +}; + + +struct lx_stream { + struct snd_pcm_substream *stream; + snd_pcm_uframes_t frame_pos; + enum lx_stream_status status; /* free, open, running, draining + * pause */ + unsigned int is_capture:1; +}; + + +struct lx6464es { + struct snd_card *card; + struct pci_dev *pci; + int irq; + + u8 mac_address[6]; + + struct mutex lock; /* interrupt lock */ + struct mutex setup_mutex; /* mutex used in hw_params, open + * and close */ + + /* ports */ + unsigned long port_plx; /* io port (size=256) */ + void __iomem *port_plx_remapped; /* remapped plx port */ + void __iomem *port_dsp_bar; /* memory port (32-bit, + * non-prefetchable, + * size=8K) */ + + /* messaging */ + struct mutex msg_lock; /* message lock */ + struct lx_rmh rmh; + u32 irqsrc; + + /* configuration */ + uint freq_ratio : 2; + uint playback_mute : 1; + uint hardware_running[2]; + u32 board_sample_rate; /* sample rate read from + * board */ + u16 pcm_granularity; /* board blocksize */ + + /* dma */ + struct snd_dma_buffer capture_dma_buf; + struct snd_dma_buffer playback_dma_buf; + + /* pcm */ + struct snd_pcm *pcm; + + /* streams */ + struct lx_stream capture_stream; + struct lx_stream playback_stream; +}; + + +#endif /* LX6464ES_H */ diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c new file mode 100644 index 000000000..a80684bdc --- /dev/null +++ b/sound/pci/lx6464es/lx_core.c @@ -0,0 +1,1198 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * low-level interface + * + * Copyright (c) 2009 Tim Blechmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +/* #define RMH_DEBUG 1 */ + +#include +#include +#include +#include + +#include "lx6464es.h" +#include "lx_core.h" + +/* low-level register access */ + +static const unsigned long dsp_port_offsets[] = { + 0, + 0x400, + 0x401, + 0x402, + 0x403, + 0x404, + 0x405, + 0x406, + 0x407, + 0x408, + 0x409, + 0x40a, + 0x40b, + 0x40c, + + 0x410, + 0x411, + 0x412, + 0x413, + 0x414, + 0x415, + 0x416, + + 0x420, + 0x430, + 0x431, + 0x432, + 0x433, + 0x434, + 0x440 +}; + +static void __iomem *lx_dsp_register(struct lx6464es *chip, int port) +{ + void __iomem *base_address = chip->port_dsp_bar; + return base_address + dsp_port_offsets[port]*4; +} + +unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port) +{ + void __iomem *address = lx_dsp_register(chip, port); + return ioread32(address); +} + +static void lx_dsp_reg_readbuf(struct lx6464es *chip, int port, u32 *data, + u32 len) +{ + u32 __iomem *address = lx_dsp_register(chip, port); + int i; + + /* we cannot use memcpy_fromio */ + for (i = 0; i != len; ++i) + data[i] = ioread32(address + i); +} + + +void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data) +{ + void __iomem *address = lx_dsp_register(chip, port); + iowrite32(data, address); +} + +static void lx_dsp_reg_writebuf(struct lx6464es *chip, int port, + const u32 *data, u32 len) +{ + u32 __iomem *address = lx_dsp_register(chip, port); + int i; + + /* we cannot use memcpy_to */ + for (i = 0; i != len; ++i) + iowrite32(data[i], address + i); +} + + +static const unsigned long plx_port_offsets[] = { + 0x04, + 0x40, + 0x44, + 0x48, + 0x4c, + 0x50, + 0x54, + 0x58, + 0x5c, + 0x64, + 0x68, + 0x6C +}; + +static void __iomem *lx_plx_register(struct lx6464es *chip, int port) +{ + void __iomem *base_address = chip->port_plx_remapped; + return base_address + plx_port_offsets[port]; +} + +unsigned long lx_plx_reg_read(struct lx6464es *chip, int port) +{ + void __iomem *address = lx_plx_register(chip, port); + return ioread32(address); +} + +void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data) +{ + void __iomem *address = lx_plx_register(chip, port); + iowrite32(data, address); +} + +/* rmh */ + +#ifdef CONFIG_SND_DEBUG +#define CMD_NAME(a) a +#else +#define CMD_NAME(a) NULL +#endif + +#define Reg_CSM_MR 0x00000002 +#define Reg_CSM_MC 0x00000001 + +struct dsp_cmd_info { + u32 dcCodeOp; /* Op Code of the command (usually 1st 24-bits + * word).*/ + u16 dcCmdLength; /* Command length in words of 24 bits.*/ + u16 dcStatusType; /* Status type: 0 for fixed length, 1 for + * random. */ + u16 dcStatusLength; /* Status length (if fixed).*/ + char *dcOpName; +}; + +/* + Initialization and control data for the Microblaze interface + - OpCode: + the opcode field of the command set at the proper offset + - CmdLength + the number of command words + - StatusType + offset in the status registers: 0 means that the return value may be + different from 0, and must be read + - StatusLength + the number of status words (in addition to the return value) +*/ + +static 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) { + for (i = 0; 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, ¤t_state); + + if (err < 0) + return err; + + if (!err && current_state == state) + return 0; + + mdelay(1); + } + + return -ETIMEDOUT; +} + +int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture) +{ + return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_RUN); +} + +int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture) +{ + return lx_pipe_wait_for_state(chip, pipe, is_capture, PSTATE_IDLE); +} + +/* low-level stream handling */ +int lx_stream_set_state(struct lx6464es *chip, u32 pipe, + int is_capture, enum stream_state_t state) +{ + 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 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 + * */ + + u64 orun_mask; + u64 urun_mask; + 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); + } + + orun_mask = ((u64)stat[7] << 32) + stat[8]; + urun_mask = ((u64)stat[5] << 32) + stat[6]; + + /* 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, + ¬ified_in_pipe_mask, + ¬ified_out_pipe_mask); + if (err) + dev_err(chip->card->dev, "error handling async events\n"); + + if (notified_in_pipe_mask) { + struct lx_stream *lx_stream = &chip->capture_stream; + + dev_dbg(chip->card->dev, + "requesting audio transfer for capture\n"); + err = lx_interrupt_request_new_buffer(chip, lx_stream); + if (err < 0) + dev_err(chip->card->dev, + "cannot request new buffer for capture\n"); + snd_pcm_period_elapsed(lx_stream->stream); + } + + if (notified_out_pipe_mask) { + struct lx_stream *lx_stream = &chip->playback_stream; + + dev_dbg(chip->card->dev, + "requesting audio transfer for playback\n"); + err = lx_interrupt_request_new_buffer(chip, lx_stream); + if (err < 0) + dev_err(chip->card->dev, + "cannot request new buffer for playback\n"); + snd_pcm_period_elapsed(lx_stream->stream); + } + + return IRQ_HANDLED; +} + + +static void lx_irq_set(struct lx6464es *chip, int enable) +{ + u32 reg = lx_plx_reg_read(chip, ePLX_IRQCS); + + /* enable/disable interrupts + * + * Set the Doorbell and PCI interrupt enable bits + * + * */ + if (enable) + reg |= (IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB); + else + reg &= ~(IRQCS_ENABLE_PCIIRQ | IRQCS_ENABLE_PCIDB); + lx_plx_reg_write(chip, ePLX_IRQCS, reg); +} + +void lx_irq_enable(struct lx6464es *chip) +{ + dev_dbg(chip->card->dev, "->lx_irq_enable\n"); + lx_irq_set(chip, 1); +} + +void lx_irq_disable(struct lx6464es *chip) +{ + dev_dbg(chip->card->dev, "->lx_irq_disable\n"); + lx_irq_set(chip, 0); +} diff --git a/sound/pci/lx6464es/lx_core.h b/sound/pci/lx6464es/lx_core.h new file mode 100644 index 000000000..0cc140ca9 --- /dev/null +++ b/sound/pci/lx6464es/lx_core.h @@ -0,0 +1,237 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * low-level interface + * + * Copyright (c) 2009 Tim Blechmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef LX_CORE_H +#define LX_CORE_H + +#include + +#include "lx_defs.h" + +#define REG_CRM_NUMBER 12 + +struct lx6464es; + +/* low-level register access */ + +/* dsp register access */ +enum { + eReg_BASE, + eReg_CSM, + eReg_CRM1, + eReg_CRM2, + eReg_CRM3, + eReg_CRM4, + eReg_CRM5, + eReg_CRM6, + eReg_CRM7, + eReg_CRM8, + eReg_CRM9, + eReg_CRM10, + eReg_CRM11, + eReg_CRM12, + + eReg_ICR, + eReg_CVR, + eReg_ISR, + eReg_RXHTXH, + eReg_RXMTXM, + eReg_RHLTXL, + eReg_RESETDSP, + + eReg_CSUF, + eReg_CSES, + eReg_CRESMSB, + eReg_CRESLSB, + eReg_ADMACESMSB, + eReg_ADMACESLSB, + eReg_CONFES, + + eMaxPortLx +}; + +unsigned long lx_dsp_reg_read(struct lx6464es *chip, int port); +void lx_dsp_reg_write(struct lx6464es *chip, int port, unsigned data); + +/* plx register access */ +enum { + ePLX_PCICR, + + ePLX_MBOX0, + ePLX_MBOX1, + ePLX_MBOX2, + ePLX_MBOX3, + ePLX_MBOX4, + ePLX_MBOX5, + ePLX_MBOX6, + ePLX_MBOX7, + + ePLX_L2PCIDB, + ePLX_IRQCS, + ePLX_CHIPSC, + + eMaxPort +}; + +unsigned long lx_plx_reg_read(struct lx6464es *chip, int port); +void lx_plx_reg_write(struct lx6464es *chip, int port, u32 data); + +/* rhm */ +struct lx_rmh { + u16 cmd_len; /* length of the command to send (WORDs) */ + u16 stat_len; /* length of the status received (WORDs) */ + u16 dsp_stat; /* status type, RMP_SSIZE_XXX */ + u16 cmd_idx; /* index of the command */ + u32 cmd[REG_CRM_NUMBER]; + u32 stat[REG_CRM_NUMBER]; +}; + + +/* low-level dsp access */ +int lx_dsp_get_version(struct lx6464es *chip, u32 *rdsp_version); +int lx_dsp_get_clock_frequency(struct lx6464es *chip, u32 *rfreq); +int lx_dsp_set_granularity(struct lx6464es *chip, u32 gran); +int lx_dsp_read_async_events(struct lx6464es *chip, u32 *data); +int lx_dsp_get_mac(struct lx6464es *chip); + + +/* low-level pipe handling */ +int lx_pipe_allocate(struct lx6464es *chip, u32 pipe, int is_capture, + int channels); +int lx_pipe_release(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_sample_count(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *rsample_count); +int lx_pipe_state(struct lx6464es *chip, u32 pipe, int is_capture, u16 *rstate); +int lx_pipe_stop(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_start(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_pause(struct lx6464es *chip, u32 pipe, int is_capture); + +int lx_pipe_wait_for_start(struct lx6464es *chip, u32 pipe, int is_capture); +int lx_pipe_wait_for_idle(struct lx6464es *chip, u32 pipe, int is_capture); + +/* low-level stream handling */ +int lx_stream_set_format(struct lx6464es *chip, struct snd_pcm_runtime *runtime, + u32 pipe, int is_capture); +int lx_stream_state(struct lx6464es *chip, u32 pipe, int is_capture, + int *rstate); +int lx_stream_sample_position(struct lx6464es *chip, u32 pipe, int is_capture, + u64 *r_bytepos); + +int lx_stream_set_state(struct lx6464es *chip, u32 pipe, + int is_capture, enum stream_state_t state); + +static inline int lx_stream_start(struct lx6464es *chip, u32 pipe, + int is_capture) +{ + 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..469bcc685 --- /dev/null +++ b/sound/pci/lx6464es/lx_defs.h @@ -0,0 +1,376 @@ +/* -*- linux-c -*- * + * + * ALSA driver for the digigram lx6464es interface + * adapted upstream headers + * + * Copyright (c) 2009 Tim Blechmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#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..62962178a --- /dev/null +++ b/sound/pci/maestro3.c @@ -0,0 +1,2794 @@ +/* + * Driver for ESS Maestro3/Allegro (ES1988) soundcards. + * Copyright (c) 2000 by Zach Brown + * Takashi Iwai + * + * Most of the hardware init stuffs are based on maestro3 driver for + * OSS/Free by Zach Brown. Many thanks to Zach! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * ChangeLog: + * Aug. 27, 2001 + * - Fixed deadlock on capture + * - Added Canyon3D-2 support by Rob Riggs + * + */ + +#define CARD_NAME "ESS Maestro3/Allegro/Canyon3D-2" +#define DRIVER_NAME "Maestro3" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Zach Brown , Takashi Iwai "); +MODULE_DESCRIPTION("ESS Maestro3 PCI"); +MODULE_LICENSE("GPL"); +MODULE_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 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 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 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 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; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + /* 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; + snd_pcm_lib_free_pages(substream); + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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 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_pcm_suspend_all(chip->pcm); + 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 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; + +#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..cce159ec5 --- /dev/null +++ b/sound/pci/mixart/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +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..9cd297a42 --- /dev/null +++ b/sound/pci/mixart/mixart.c @@ -0,0 +1,1421 @@ +/* + * Driver for Digigram miXart soundcards + * + * main file with alsa callbacks + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "mixart.h" +#include "mixart_hwdep.h" +#include "mixart_core.h" +#include "mixart_mixer.h" + +#define CARD_NAME "miXart" + +MODULE_AUTHOR("Digigram "); +MODULE_DESCRIPTION("Digigram " CARD_NAME); +MODULE_LICENSE("GPL"); +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; + /* fall through */ + 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; isgroup_req.stream_info[i].size_max_byte_frame = 1024; + buf->sgroup_req.stream_info[i].size_max_sample_frame = 256; + buf->sgroup_req.stream_info[i].nb_bytes_max_per_sample = MIXART_FLOAT_P__4_0_TO_HEX; /* is 4.0f */ + + /* find the right bufferinfo_array */ + j = (chip->chip_idx * MIXART_MAX_STREAM_PER_CARD) + (pcm_number * (MIXART_PLAYBACK_STREAMS + MIXART_CAPTURE_STREAMS)) + i; + if(capture) j += MIXART_PLAYBACK_STREAMS; /* in the array capture is behind playback */ + + buf->sgroup_req.flow_entry[i] = j; + + flowinfo = (struct mixart_flowinfo *)chip->mgr->flowinfo.area; + flowinfo[j].bufferinfo_array_phy_address = (u32)chip->mgr->bufferinfo.addr + (j * sizeof(struct mixart_bufferinfo)); + flowinfo[j].bufferinfo_count = 1; /* 1 will set the miXart to ring-buffer mode ! */ + + bufferinfo = (struct mixart_bufferinfo *)chip->mgr->bufferinfo.area; + bufferinfo[j].buffer_address = 0; /* buffer is not yet allocated */ + bufferinfo[j].available_length = 0; /* buffer is not yet allocated */ + + /* construct the identifier of the stream buffer received in the interrupts ! */ + bufferinfo[j].buffer_id = (chip->chip_idx << MIXART_NOTIFY_CARD_OFFSET) + (pcm_number << MIXART_NOTIFY_PCM_OFFSET ) + i; + if(capture) { + bufferinfo[j].buffer_id |= MIXART_NOTIFY_CAPT_MASK; + } + } + + err = snd_mixart_send_msg(chip->mgr, &request, sizeof(buf->sgroup_resp), &buf->sgroup_resp); + if((err < 0) || (buf->sgroup_resp.status != 0)) { + dev_err(chip->card->dev, + "error MSG_STREAM_ADD_**PUT_GROUP err=%x stat=%x !\n", + err, buf->sgroup_resp.status); + kfree(buf); + return NULL; + } + + pipe->group_uid = buf->sgroup_resp.group; /* id of the pipe, as returned by embedded */ + pipe->stream_count = buf->sgroup_resp.stream_count; + /* pipe->stream_uid[i] = buf->sgroup_resp.stream[i].stream_uid; */ + + pipe->status = PIPE_STOPPED; + kfree(buf); + } + + if(monitoring) pipe->monitoring = 1; + else pipe->references++; + + return pipe; +} + + +int snd_mixart_kill_ref_pipe(struct mixart_mgr *mgr, + struct mixart_pipe *pipe, int monitoring) +{ + int err = 0; + + if(pipe->status == PIPE_UNDEFINED) + return 0; + + if(monitoring) + pipe->monitoring = 0; + else + pipe->references--; + + if((pipe->references <= 0) && (pipe->monitoring == 0)) { + + struct mixart_msg request; + struct mixart_delete_group_resp delete_resp; + + /* release the clock */ + err = mixart_set_clock( mgr, pipe, 0); + if( err < 0 ) { + dev_err(&mgr->pci->dev, + "mixart_set_clock(0) return error!\n"); + } + + /* stop the pipe */ + err = mixart_set_pipe_state(mgr, pipe, 0); + if( err < 0 ) { + dev_err(&mgr->pci->dev, "error stopping pipe!\n"); + } + + request.message_id = MSG_STREAM_DELETE_GROUP; + request.uid = (struct mixart_uid){0,0}; + request.data = &pipe->group_uid; /* the streaming group ! */ + request.size = sizeof(pipe->group_uid); + + /* delete the pipe */ + err = snd_mixart_send_msg(mgr, &request, sizeof(delete_resp), &delete_resp); + if ((err < 0) || (delete_resp.status != 0)) { + dev_err(&mgr->pci->dev, + "error MSG_STREAM_DELETE_GROUP err(%x), status(%x)\n", + err, delete_resp.status); + } + + pipe->group_uid = (struct mixart_uid){0,0}; + pipe->stream_count = 0; + pipe->status = PIPE_UNDEFINED; + } + + return err; +} + +static int mixart_set_stream_state(struct mixart_stream *stream, int start) +{ + struct snd_mixart *chip; + struct mixart_stream_state_req stream_state_req; + struct mixart_msg request; + + if(!stream->substream) + return -EINVAL; + + memset(&stream_state_req, 0, sizeof(stream_state_req)); + stream_state_req.stream_count = 1; + stream_state_req.stream_info.stream_desc.uid_pipe = stream->pipe->group_uid; + stream_state_req.stream_info.stream_desc.stream_idx = stream->substream->number; + + if (stream->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + request.message_id = start ? MSG_STREAM_START_INPUT_STAGE_PACKET : MSG_STREAM_STOP_INPUT_STAGE_PACKET; + else + request.message_id = start ? MSG_STREAM_START_OUTPUT_STAGE_PACKET : MSG_STREAM_STOP_OUTPUT_STAGE_PACKET; + + request.uid = (struct mixart_uid){0,0}; + request.data = &stream_state_req; + request.size = sizeof(stream_state_req); + + stream->abs_period_elapsed = 0; /* reset stream pos */ + stream->buf_periods = 0; + stream->buf_period_frag = 0; + + chip = snd_pcm_substream_chip(stream->substream); + + return snd_mixart_send_msg_nonblock(chip->mgr, &request); +} + +/* + * Trigger callback + */ + +static int snd_mixart_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct mixart_stream *stream = subs->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_TRIGGER_START\n"); + + /* START_STREAM */ + if( mixart_set_stream_state(stream, 1) ) + return -EINVAL; + + stream->status = MIXART_STREAM_STATUS_RUNNING; + + break; + case SNDRV_PCM_TRIGGER_STOP: + + /* STOP_STREAM */ + if( mixart_set_stream_state(stream, 0) ) + return -EINVAL; + + stream->status = MIXART_STREAM_STATUS_OPEN; + + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_TRIGGER_STOP\n"); + + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* TODO */ + stream->status = MIXART_STREAM_STATUS_PAUSE; + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_PAUSE_PUSH\n"); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* TODO */ + stream->status = MIXART_STREAM_STATUS_RUNNING; + dev_dbg(subs->pcm->card->dev, "SNDRV_PCM_PAUSE_RELEASE\n"); + break; + default: + return -EINVAL; + } + return 0; +} + +static int mixart_sync_nonblock_events(struct mixart_mgr *mgr) +{ + unsigned long timeout = jiffies + HZ; + while (atomic_read(&mgr->msg_processed) > 0) { + if (time_after(jiffies, timeout)) { + dev_err(&mgr->pci->dev, + "mixart: cannot process nonblock events!\n"); + return -EBUSY; + } + schedule_timeout_uninterruptible(1); + } + return 0; +} + +/* + * prepare callback for all pcms + */ +static int snd_mixart_prepare(struct snd_pcm_substream *subs) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + struct mixart_stream *stream = subs->runtime->private_data; + + /* TODO de façon non bloquante, réappliquer les hw_params (rate, bits, codec) */ + + dev_dbg(chip->card->dev, "snd_mixart_prepare\n"); + + mixart_sync_nonblock_events(chip->mgr); + + /* only the first stream can choose the sample rate */ + /* the further opened streams will be limited to its frequency (see open) */ + if(chip->mgr->ref_count_rate == 1) + chip->mgr->sample_rate = subs->runtime->rate; + + /* set the clock only once (first stream) on the same pipe */ + if(stream->pipe->references == 1) { + if( mixart_set_clock(chip->mgr, stream->pipe, subs->runtime->rate) ) + return -EINVAL; + } + + return 0; +} + + +static int mixart_set_format(struct mixart_stream *stream, snd_pcm_format_t format) +{ + int err; + struct snd_mixart *chip; + struct mixart_msg request; + struct mixart_stream_param_desc stream_param; + struct mixart_return_uid resp; + + chip = snd_pcm_substream_chip(stream->substream); + + memset(&stream_param, 0, sizeof(stream_param)); + + stream_param.coding_type = CT_LINEAR; + stream_param.number_of_channel = stream->channels; + + stream_param.sampling_freq = chip->mgr->sample_rate; + if(stream_param.sampling_freq == 0) + stream_param.sampling_freq = 44100; /* if frequency not yet defined, use some default */ + + switch(format){ + case SNDRV_PCM_FORMAT_U8: + stream_param.sample_type = ST_INTEGER_8; + stream_param.sample_size = 8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + stream_param.sample_type = ST_INTEGER_16LE; + stream_param.sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S16_BE: + stream_param.sample_type = ST_INTEGER_16BE; + stream_param.sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + stream_param.sample_type = ST_INTEGER_24LE; + stream_param.sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S24_3BE: + stream_param.sample_type = ST_INTEGER_24BE; + stream_param.sample_size = 24; + break; + case SNDRV_PCM_FORMAT_FLOAT_LE: + stream_param.sample_type = ST_FLOATING_POINT_32LE; + stream_param.sample_size = 32; + break; + case SNDRV_PCM_FORMAT_FLOAT_BE: + stream_param.sample_type = ST_FLOATING_POINT_32BE; + stream_param.sample_size = 32; + break; + default: + dev_err(chip->card->dev, + "error mixart_set_format() : unknown format\n"); + return -EINVAL; + } + + dev_dbg(chip->card->dev, + "set SNDRV_PCM_FORMAT sample_type(%d) sample_size(%d) freq(%d) channels(%d)\n", + stream_param.sample_type, stream_param.sample_size, stream_param.sampling_freq, stream->channels); + + /* TODO: what else to configure ? */ + /* stream_param.samples_per_frame = 2; */ + /* stream_param.bytes_per_frame = 4; */ + /* stream_param.bytes_per_sample = 2; */ + + stream_param.pipe_count = 1; /* set to 1 */ + stream_param.stream_count = 1; /* set to 1 */ + stream_param.stream_desc[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; + } + + /* allocate buffer */ + err = snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw)); + + if (err > 0) { + 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 err; +} + +static int snd_mixart_hw_free(struct snd_pcm_substream *subs) +{ + struct snd_mixart *chip = snd_pcm_substream_chip(subs); + snd_pcm_lib_free_pages(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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->mgr->pci), 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 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; + + 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 struct snd_info_entry_ops snd_mixart_proc_ops_BA0 = { + .read = snd_mixart_BA0_read, +}; + +static 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 */ + if (! snd_card_proc_new(chip->card, "board_info", &entry)) { + entry->private_data = chip; + entry->c.text.read = 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, snd_dma_pci_data(pci), + 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, snd_dma_pci_data(pci), + 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..69b3ece09 --- /dev/null +++ b/sound/pci/mixart/mixart.h @@ -0,0 +1,220 @@ +/* + * Driver for Digigram miXart soundcards + * + * main header file + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SOUND_MIXART_H +#define __SOUND_MIXART_H + +#include +#include +#include + +#define MIXART_DRIVER_VERSION 0x000100 /* 0.1.0 */ + + +/* + */ + +struct mixart_uid { + u32 object_id; + u32 desc; +}; + +struct mem_area { + unsigned long phys; + void __iomem *virt; + struct resource *res; +}; + + +struct mixart_route { + unsigned char connected; + unsigned char phase_inv; + int volume; +}; + + +/* firmware status codes */ +#define MIXART_MOTHERBOARD_XLX_INDEX 0 +#define MIXART_MOTHERBOARD_ELF_INDEX 1 +#define MIXART_AESEBUBOARD_XLX_INDEX 2 +#define MIXART_HARDW_FILES_MAX_INDEX 3 /* xilinx, elf, AESEBU xilinx */ + +#define MIXART_MAX_CARDS 4 +#define MSG_FIFO_SIZE 16 + +#define MIXART_MAX_PHYS_CONNECTORS (MIXART_MAX_CARDS * 2 * 2) /* 4 * stereo * (analog+digital) */ + +struct mixart_mgr { + unsigned int num_cards; + struct snd_mixart *chip[MIXART_MAX_CARDS]; + + struct pci_dev *pci; + + int irq; + + /* memory-maps */ + struct mem_area mem[2]; + + /* one and only blocking message or notification may be pending */ + u32 pending_event; + wait_queue_head_t msg_sleep; + + /* messages fifo */ + u32 msg_fifo[MSG_FIFO_SIZE]; + int msg_fifo_readptr; + int msg_fifo_writeptr; + atomic_t msg_processed; /* number of messages to be processed in tasklet */ + + 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..5a956f8bf --- /dev/null +++ b/sound/pci/mixart/mixart_core.c @@ -0,0 +1,601 @@ +/* + * Driver for Digigram miXart soundcards + * + * low level interface with interrupt handling and mail box implementation + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include +#include "mixart.h" +#include "mixart_hwdep.h" +#include "mixart_core.h" + + +#define MSG_TIMEOUT_JIFFIES (400 * HZ) / 1000 /* 400 ms */ + +#define MSG_DESCRIPTOR_SIZE 0x24 +#define MSG_HEADER_SIZE (MSG_DESCRIPTOR_SIZE + 4) + +#define MSG_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, ¬if_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; istream_count; i++) { + + u32 buffer_id = notify->streams[i].buffer_id; + unsigned int chip_number = (buffer_id & MIXART_NOTIFY_CARD_MASK) >> MIXART_NOTIFY_CARD_OFFSET; /* card0 to 3 */ + unsigned int pcm_number = (buffer_id & MIXART_NOTIFY_PCM_MASK ) >> MIXART_NOTIFY_PCM_OFFSET; /* pcm0 to 3 */ + unsigned int sub_number = buffer_id & MIXART_NOTIFY_SUBS_MASK; /* 0 to MIXART_PLAYBACK_STREAMS */ + unsigned int is_capture = ((buffer_id & MIXART_NOTIFY_CAPT_MASK) != 0); /* playback == 0 / capture == 1 */ + + struct snd_mixart *chip = mgr->chip[chip_number]; + struct mixart_stream *stream; + + if ((chip_number >= mgr->num_cards) || (pcm_number >= MIXART_PCM_TOTAL) || (sub_number >= MIXART_PLAYBACK_STREAMS)) { + dev_err(&mgr->pci->dev, + "error MSG_SERVICES_TIMER_NOTIFY buffer_id (%x) pos(%d)\n", + buffer_id, notify->streams[i].sample_pos_low_part); + break; + } + + if (is_capture) + stream = &chip->capture_stream[pcm_number]; + else + stream = &chip->playback_stream[pcm_number][sub_number]; + + if (stream->substream && (stream->status == MIXART_STREAM_STATUS_RUNNING)) { + struct snd_pcm_runtime *runtime = stream->substream->runtime; + int elapsed = 0; + u64 sample_count = ((u64)notify->streams[i].sample_pos_high_part) << 32; + sample_count |= notify->streams[i].sample_pos_low_part; + + while (1) { + u64 new_elapse_pos = stream->abs_period_elapsed + runtime->period_size; + + if (new_elapse_pos > sample_count) { + break; /* while */ + } + else { + elapsed = 1; + stream->buf_periods++; + if (stream->buf_periods >= runtime->periods) + stream->buf_periods = 0; + + stream->abs_period_elapsed = new_elapse_pos; + } + } + stream->buf_period_frag = (u32)( sample_count - stream->abs_period_elapsed ); + + if(elapsed) { + mutex_unlock(&mgr->lock); + snd_pcm_period_elapsed(stream->substream); + mutex_lock(&mgr->lock); + } + } + } + break; + } + if(resp.message_id == MSG_SERVICES_REPORT_TRACES) { + if(resp.size > 1) { +#ifndef __BIG_ENDIAN + /* Traces are text: the swapped msg_data has to be swapped back ! */ + int i; + for(i=0; i<(resp.size/4); i++) { + ((__be32*)mixart_msg_data)[i] = cpu_to_be32((mixart_msg_data)[i]); + } +#endif + ((char*)mixart_msg_data)[resp.size - 1] = 0; + dev_dbg(&mgr->pci->dev, + "MIXART TRACE : %s\n", + (char *)mixart_msg_data); + } + break; + } + + dev_dbg(&mgr->pci->dev, "command %x not handled\n", + resp.message_id); + break; + + case MSG_TYPE_NOTIFY: + if(msg & MSG_CANCEL_NOTIFY_MASK) { + msg &= ~MSG_CANCEL_NOTIFY_MASK; + dev_err(&mgr->pci->dev, + "canceled notification %x !\n", msg); + } + /* fall through */ + 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..d1722e575 --- /dev/null +++ b/sound/pci/mixart/mixart_core.h @@ -0,0 +1,571 @@ +/* + * Driver for Digigram miXart soundcards + * + * low level interface with interrupt handling and mail box implementation + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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..bc92758de --- /dev/null +++ b/sound/pci/mixart/mixart_hwdep.c @@ -0,0 +1,591 @@ +/* + * Driver for Digigram miXart soundcards + * + * DSP firmware management + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; knum_cards; k++) { + mgr->chip[k]->uid_in_analog_physio = phys_io.uid[k]; + mgr->chip[k]->uid_out_analog_physio = phys_io.uid[phys_io.nb_uid/2 + k]; + } + + return 0; +} + + +static int mixart_first_init(struct mixart_mgr *mgr) +{ + u32 k; + int err; + struct mixart_msg request; + + 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 char *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..2794cd385 --- /dev/null +++ b/sound/pci/mixart/mixart_hwdep.h @@ -0,0 +1,155 @@ +/* + * Driver for Digigram miXart soundcards + * + * definitions and makros for basic card access + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SOUND_MIXART_HWDEP_H +#define __SOUND_MIXART_HWDEP_H + +#include + +#ifndef readl_be +#define readl_be(x) be32_to_cpu((__force __be32)__raw_readl(x)) +#endif + +#ifndef writel_be +#define writel_be(data,addr) __raw_writel((__force u32)cpu_to_be32(data),addr) +#endif + +#ifndef readl_le +#define readl_le(x) le32_to_cpu((__force __le32)__raw_readl(x)) +#endif + +#ifndef writel_le +#define writel_le(data,addr) __raw_writel((__force u32)cpu_to_le32(data),addr) +#endif + +#define MIXART_MEM(mgr,x) ((mgr)->mem[0].virt + (x)) +#define MIXART_REG(mgr,x) ((mgr)->mem[1].virt + (x)) + + +/* Daughter board Type */ +#define DAUGHTER_TYPE_MASK 0x0F +#define DAUGHTER_VER_MASK 0xF0 +#define DAUGHTER_TYPEVER_MASK (DAUGHTER_TYPE_MASK|DAUGHTER_VER_MASK) + +#define MIXART_DAUGHTER_TYPE_NONE 0x00 +#define MIXART_DAUGHTER_TYPE_COBRANET 0x08 +#define MIXART_DAUGHTER_TYPE_AES 0x0E + + + +#define MIXART_BA0_SIZE (16 * 1024 * 1024) /* 16M */ +#define MIXART_BA1_SIZE (4 * 1024) /* 4k */ + +/* + * -----------BAR 0 -------------------------------------------------------------------------------------------------------- + */ +#define MIXART_PSEUDOREG 0x2000 /* base address for pseudoregister */ + +#define MIXART_PSEUDOREG_BOARDNUMBER MIXART_PSEUDOREG+0 /* board number */ + +/* perfmeter (available when elf loaded)*/ +#define MIXART_PSEUDOREG_PERF_STREAM_LOAD_OFFSET MIXART_PSEUDOREG+0x70 /* streaming load */ +#define MIXART_PSEUDOREG_PERF_SYSTEM_LOAD_OFFSET MIXART_PSEUDOREG+0x78 /* system load (reference)*/ +#define MIXART_PSEUDOREG_PERF_MAILBX_LOAD_OFFSET MIXART_PSEUDOREG+0x7C /* mailbox load */ +#define MIXART_PSEUDOREG_PERF_INTERR_LOAD_OFFSET MIXART_PSEUDOREG+0x74 /* interrupt handling load */ + +/* motherboard xilinx loader info */ +#define MIXART_PSEUDOREG_MXLX_BASE_ADDR_OFFSET MIXART_PSEUDOREG+0x9C /* 0x00600000 */ +#define MIXART_PSEUDOREG_MXLX_SIZE_OFFSET MIXART_PSEUDOREG+0xA0 /* xilinx size in bytes */ +#define MIXART_PSEUDOREG_MXLX_STATUS_OFFSET MIXART_PSEUDOREG+0xA4 /* status = EMBEBBED_STAT_XXX */ + +/* elf loader info */ +#define MIXART_PSEUDOREG_ELF_STATUS_OFFSET MIXART_PSEUDOREG+0xB0 /* status = EMBEBBED_STAT_XXX */ + +/* +* after the elf code is loaded, and the flowtable info was passed to it, +* the driver polls on this address, until it shows 1 (presence) or 2 (absence) +* once it is non-zero, the daughter board type may be read +*/ +#define MIXART_PSEUDOREG_DBRD_PRESENCE_OFFSET MIXART_PSEUDOREG+0x990 + +/* Global info structure */ +#define MIXART_PSEUDOREG_DBRD_TYPE_OFFSET MIXART_PSEUDOREG+0x994 /* Type and version of daughterboard */ + + +/* daughterboard xilinx loader info */ +#define MIXART_PSEUDOREG_DXLX_BASE_ADDR_OFFSET MIXART_PSEUDOREG+0x998 /* get the address here where to write the file */ +#define MIXART_PSEUDOREG_DXLX_SIZE_OFFSET MIXART_PSEUDOREG+0x99C /* xilinx size in bytes */ +#define MIXART_PSEUDOREG_DXLX_STATUS_OFFSET MIXART_PSEUDOREG+0x9A0 /* status = EMBEBBED_STAT_XXX */ + +/* */ +#define MIXART_FLOWTABLE_PTR 0x3000 /* pointer to flow table */ + +/* mailbox addresses */ + +/* message DRV -> EMB */ +#define MSG_INBOUND_POST_HEAD 0x010008 /* DRV posts MF + increment4 */ +#define MSG_INBOUND_POST_TAIL 0x01000C /* EMB gets MF + increment4 */ +/* message EMB -> DRV */ +#define MSG_OUTBOUND_POST_TAIL 0x01001C /* DRV gets MF + increment4 */ +#define MSG_OUTBOUND_POST_HEAD 0x010018 /* EMB posts MF + increment4 */ +/* Get Free Frames */ +#define MSG_INBOUND_FREE_TAIL 0x010004 /* DRV gets MFA + increment4 */ +#define MSG_OUTBOUND_FREE_TAIL 0x010014 /* EMB gets MFA + increment4 */ +/* Put Free Frames */ +#define MSG_OUTBOUND_FREE_HEAD 0x010010 /* DRV puts MFA + increment4 */ +#define MSG_INBOUND_FREE_HEAD 0x010000 /* EMB puts MFA + increment4 */ + +/* firmware addresses of the message fifos */ +#define MSG_BOUND_STACK_SIZE 0x004000 /* size of each following stack */ +/* posted messages */ +#define MSG_OUTBOUND_POST_STACK 0x108000 /* stack of messages to the DRV */ +#define MSG_INBOUND_POST_STACK 0x104000 /* stack of messages to the EMB */ +/* available empty messages */ +#define MSG_OUTBOUND_FREE_STACK 0x10C000 /* stack of free enveloped for EMB */ +#define MSG_INBOUND_FREE_STACK 0x100000 /* stack of free enveloped for DRV */ + + +/* defines for mailbox message frames */ +#define MSG_FRAME_OFFSET 0x64 +#define MSG_FRAME_SIZE 0x6400 +#define MSG_FRAME_NUMBER 32 +#define MSG_FROM_AGENT_ITMF_OFFSET (MSG_FRAME_OFFSET + (MSG_FRAME_SIZE * MSG_FRAME_NUMBER)) +#define MSG_TO_AGENT_ITMF_OFFSET (MSG_FROM_AGENT_ITMF_OFFSET + MSG_FRAME_SIZE) +#define MSG_HOST_RSC_PROTECTION (MSG_TO_AGENT_ITMF_OFFSET + MSG_FRAME_SIZE) +#define MSG_AGENT_RSC_PROTECTION (MSG_HOST_RSC_PROTECTION + 4) + + +/* + * -----------BAR 1 -------------------------------------------------------------------------------------------------------- + */ + +/* interrupt addresses and constants */ +#define MIXART_PCI_OMIMR_OFFSET 0x34 /* outbound message interrupt mask register */ +#define MIXART_PCI_OMISR_OFFSET 0x30 /* outbound message interrupt status register */ +#define MIXART_PCI_ODBR_OFFSET 0x60 /* outbound doorbell register */ + +#define MIXART_BA1_BRUTAL_RESET_OFFSET 0x68 /* write 1 in LSBit to reset board */ + +#define MIXART_HOST_ALL_INTERRUPT_MASKED 0x02B /* 0000 0010 1011 */ +#define MIXART_ALLOW_OUTBOUND_DOORBELL 0x023 /* 0000 0010 0011 */ +#define MIXART_OIDI 0x008 /* 0000 0000 1000 */ + + +int snd_mixart_setup_firmware(struct mixart_mgr *mgr); + +#endif /* __SOUND_MIXART_HWDEP_H */ diff --git a/sound/pci/mixart/mixart_mixer.c b/sound/pci/mixart/mixart_mixer.c new file mode 100644 index 000000000..2b9496a66 --- /dev/null +++ b/sound/pci/mixart/mixart_mixer.c @@ -0,0 +1,1194 @@ +/* + * Driver for Digigram miXart soundcards + * + * mixer callbacks + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include +#include "mixart.h" +#include "mixart_core.h" +#include "mixart_hwdep.h" +#include +#include +#include "mixart_mixer.h" + +static 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 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<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; inum_cards; i++) { + struct snd_kcontrol_new temp; + chip = mgr->chip[i]; + + /* analog output level control */ + temp = mixart_control_analog_level; + temp.name = "Master Playback Volume"; + temp.private_value = 0; /* playback */ + 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..04aa24e35 --- /dev/null +++ b/sound/pci/mixart/mixart_mixer.h @@ -0,0 +1,31 @@ +/* + * Driver for Digigram miXart soundcards + * + * include file for mixer + * + * Copyright (c) 2003 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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..a1bd44ff8 --- /dev/null +++ b/sound/pci/nm256/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +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..b97f4ea6b --- /dev/null +++ b/sound/pci/nm256/nm256.c @@ -0,0 +1,1772 @@ +/* + * Driver for NeoMagic 256AV and 256ZX chipsets. + * Copyright (c) 2000 by Takashi Iwai + * + * Based on nm256_audio.c OSS driver in linux kernel. + * The original author of OSS nm256 driver wishes to remain anonymous, + * so I just put my acknoledgment to him/her here. + * The original author's web page is found at + * http://www.uglx.org/sony.html + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define CARD_NAME "NeoMagic 256AV/ZX" +#define DRIVER_NAME "NM256" + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("NeoMagic NM256AV/ZX"); +MODULE_LICENSE("GPL"); +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, 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->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; + } + 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; + /* fallthru */ + 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; + /* fallthru */ + 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 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 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, + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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 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 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_nocache(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_pcm_suspend_all(chip->pcm); + 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 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_nocache(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_nocache(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_BLACKLISTED, NM_RESET_WORKAROUND, NM_RESET_WORKAROUND_2 }; + +static struct snd_pci_quirk nm256_quirks[] = { + /* HP omnibook 4150 has cs4232 codec internally */ + SND_PCI_QUIRK(0x103c, 0x0007, "HP omnibook 4150", NM_BLACKLISTED), + /* 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_BLACKLISTED: + dev_info(&pci->dev, + "The device is blacklisted. Loading stopped\n"); + return -ENODEV; + case NM_RESET_WORKAROUND_2: + reset_workaround_2 = 1; + /* Fall-through */ + 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..c75725211 --- /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 char coefficients[NM_TOTAL_COEFF_COUNT * 4] = { + 0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA5, 0x01, 0xEF, 0xFC, 0x21, + 0x05, 0x87, 0xF7, 0x62, 0x11, 0xE9, 0x45, 0x5E, 0xF9, 0xB5, 0x01, + 0xDE, 0xFF, 0xA4, 0xFF, 0x60, 0x00, 0xCA, 0xFF, 0x0D, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06, + 0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1, + 0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFD, 0xFF, + 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, + 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, + 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x02, 0x00, 0x05, + 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3, + 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF, + 0x60, 0x00, 0xA4, 0xFF, 0xDE, 0xFF, 0xB5, 0x01, 0x5E, 0xF9, 0xE9, + 0x45, 0x62, 0x11, 0x87, 0xF7, 0x21, 0x05, 0xEF, 0xFC, 0xA5, 0x01, + 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, + 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, + 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, + 0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xCA, 0x01, 0x95, 0xFC, 0xEA, 0x05, 0xBB, 0xF5, 0x25, 0x17, 0x3C, + 0x43, 0x8D, 0xF6, 0x43, 0x03, 0xF5, 0xFE, 0x26, 0x00, 0x20, 0x00, + 0xE2, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5, + 0x01, 0x4C, 0xFC, 0x26, 0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33, + 0x8F, 0xF1, 0xCA, 0x06, 0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD5, 0xFF, 0xBC, 0x00, + 0xF0, 0xFD, 0xEC, 0x04, 0xD9, 0xF3, 0xB1, 0x3E, 0xCD, 0x1E, 0xC1, + 0xF3, 0xAF, 0x06, 0x49, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38, + 0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA, + 0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0x98, 0x01, 0x0D, 0xFD, + 0xE0, 0x04, 0x14, 0xF8, 0xC3, 0x0F, 0x89, 0x46, 0x4C, 0xFA, 0x38, + 0x01, 0x25, 0x00, 0x7D, 0xFF, 0x73, 0x00, 0xC2, 0xFF, 0x0F, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F, + 0x07, 0x84, 0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05, + 0x41, 0xFD, 0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x15, 0x00, 0x97, 0xFF, 0x37, 0x01, 0x22, 0xFD, 0x23, 0x06, + 0x2F, 0xF2, 0x11, 0x39, 0x7B, 0x26, 0x50, 0xF2, 0x1B, 0x07, 0x32, + 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC8, 0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, + 0xF9, 0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, + 0xA2, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, + 0x00, 0x6A, 0xFF, 0x53, 0x01, 0xA6, 0xFD, 0xA6, 0x03, 0xA1, 0xFA, + 0xDE, 0x08, 0x76, 0x48, 0x0C, 0xFF, 0xDE, 0xFE, 0x73, 0x01, 0xC9, + 0xFE, 0xCA, 0x00, 0xA0, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78, + 0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00, + 0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x68, + 0xFF, 0x93, 0x01, 0x92, 0xFC, 0xE2, 0x06, 0x83, 0xF1, 0x8C, 0x32, + 0xED, 0x2D, 0x90, 0xF1, 0x1E, 0x07, 0x57, 0xFC, 0xBD, 0x01, 0x51, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00, + 0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76, + 0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, 0x03, 0x01, 0x53, + 0xFE, 0x53, 0x02, 0x39, 0xFD, 0xA9, 0x02, 0xF2, 0x48, 0xB9, 0x04, + 0x54, 0xFC, 0xCA, 0x02, 0x16, 0xFE, 0x20, 0x01, 0x7F, 0xFF, 0x20, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC3, 0x01, + 0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7, 0x43, 0x20, + 0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00, 0xDD, 0xFF, + 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCD, 0x01, 0x43, + 0xFC, 0x2A, 0x07, 0xBC, 0xF1, 0x64, 0x2B, 0xE3, 0x34, 0xA3, 0xF1, + 0xAE, 0x06, 0xBD, 0xFC, 0x77, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8, 0xFD, + 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3, 0xC8, + 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x14, 0x00, 0xAC, 0xFF, 0xAC, 0x00, 0x08, 0xFF, 0xFD, 0x00, 0xB5, + 0xFF, 0x4B, 0xFD, 0xF4, 0x47, 0x30, 0x0B, 0xBC, 0xF9, 0x17, 0x04, + 0x6E, 0xFD, 0x6D, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01, 0x26, 0xFD, 0xAD, 0x04, + 0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C, 0xFB, 0xD4, 0x00, 0x5D, + 0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF, 0x10, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0x01, 0x07, 0xBE, + 0xF2, 0xD6, 0x23, 0x1F, 0x3B, 0xA5, 0xF2, 0xC5, 0x05, 0x62, 0xFD, + 0x10, 0x01, 0xAB, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19, + 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD, 0x4D, 0x06, 0x00, 0xF2, + 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23, 0x07, 0x34, 0xFC, 0xDD, + 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF, + 0x56, 0x00, 0xB9, 0xFF, 0xB8, 0xFF, 0xF7, 0x01, 0xE2, 0xF8, 0x8D, + 0x45, 0x46, 0x12, 0x3C, 0xF7, 0x43, 0x05, 0xDF, 0xFC, 0xAC, 0x01, + 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, + 0xFF, 0x46, 0x01, 0xC3, 0xFD, 0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07, + 0xA6, 0x48, 0xF8, 0xFF, 0x70, 0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9, + 0x00, 0x9A, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xDE, 0x01, 0x5D, 0xFC, 0x74, 0x06, 0x63, 0xF4, 0x23, 0x1C, 0x66, + 0x40, 0xAA, 0xF4, 0x65, 0x04, 0x44, 0xFE, 0x8B, 0x00, 0xEE, 0xFF, + 0xF5, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, + 0x01, 0x80, 0xFC, 0xF7, 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, + 0x83, 0xF1, 0x13, 0x07, 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, + 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xED, 0xFF, 0x05, 0x00, 0x5D, 0x00, + 0x95, 0xFE, 0xE2, 0x03, 0x7F, 0xF5, 0xCC, 0x41, 0xC7, 0x19, 0xFF, + 0xF4, 0x37, 0x06, 0x75, 0xFC, 0xD6, 0x01, 0x39, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1B, 0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18, + 0x02, 0xAA, 0xFD, 0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB, + 0x05, 0x03, 0xF7, 0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBB, 0x01, 0xBA, 0xFC, + 0x95, 0x05, 0x83, 0xF6, 0x8C, 0x14, 0x87, 0x44, 0xBB, 0xF7, 0x98, + 0x02, 0x5A, 0xFF, 0xEE, 0xFF, 0x3C, 0x00, 0xD8, 0xFF, 0x0A, 0x00, + 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, + 0x07, 0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, + 0xD5, 0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01, + 0x00, 0x07, 0x00, 0xBE, 0xFF, 0xEA, 0x00, 0xA2, 0xFD, 0x65, 0x05, + 0x28, 0xF3, 0xDB, 0x3C, 0x78, 0x21, 0x30, 0xF3, 0xDF, 0x06, 0x3A, + 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, + 0xB2, 0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76, + 0xFC, 0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD, + 0x79, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x58, 0xFF, 0x82, 0x01, 0x3F, 0xFD, 0x78, 0x04, 0xF2, 0xF8, + 0x50, 0x0D, 0x5E, 0x47, 0xD5, 0xFB, 0x6F, 0x00, 0x96, 0x00, 0x40, + 0xFF, 0x91, 0x00, 0xB7, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81, + 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00, + 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x85, + 0xFF, 0x5B, 0x01, 0xE9, 0xFC, 0x73, 0x06, 0xD8, 0xF1, 0xE5, 0x36, + 0x19, 0x29, 0xF8, 0xF1, 0x29, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x42, + 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3, 0xFF, 0x47, 0x00, + 0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44, 0x8D, + 0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45, 0xFF, + 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x75, 0xFF, 0x39, + 0x01, 0xE0, 0xFD, 0x33, 0x03, 0x87, 0xFB, 0xA2, 0x06, 0xCB, 0x48, + 0xEA, 0x00, 0x01, 0xFE, 0xE9, 0x01, 0x8A, 0xFE, 0xE8, 0x00, 0x95, + 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF, 0xDA, 0x01, + 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32, 0x41, 0x1F, + 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF, 0xF0, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5B, 0xFF, 0xAB, 0x01, 0x6F, + 0xFC, 0x08, 0x07, 0x7E, 0xF1, 0x21, 0x30, 0x67, 0x30, 0x7D, 0xF1, + 0x05, 0x07, 0x73, 0xFC, 0xA8, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, + 0xFF, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0x67, 0xFE, + 0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B, 0xA6, 0xF4, 0x5A, + 0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x96, 0xFF, 0xE5, 0x00, 0x91, 0xFE, 0xDC, 0x01, 0x1A, + 0xFE, 0xB3, 0x00, 0xC3, 0x48, 0xE1, 0x06, 0x6E, 0xFB, 0x40, 0x03, + 0xDA, 0xFD, 0x3C, 0x01, 0x74, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05, + 0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E, + 0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x41, 0xFF, 0xD9, 0x01, 0x36, 0xFC, 0x28, 0x07, 0x01, + 0xF2, 0xCE, 0x28, 0x23, 0x37, 0xE0, 0xF1, 0x6B, 0x06, 0xEF, 0xFC, + 0x57, 0x01, 0x87, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B, + 0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E, 0xFD, 0x9C, 0x05, 0xDC, 0xF2, + 0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2, 0xF3, 0x06, 0x35, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xB8, 0xFF, + 0x8E, 0x00, 0x46, 0xFF, 0x8A, 0x00, 0x86, 0x00, 0xA7, 0xFB, 0x48, + 0x47, 0x95, 0x0D, 0xD9, 0xF8, 0x84, 0x04, 0x39, 0xFD, 0x85, 0x01, + 0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, + 0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, + 0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, + 0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE6, 0x01, 0x3B, 0xFC, 0xDA, 0x06, 0x3F, 0xF3, 0x2C, 0x21, 0x11, + 0x3D, 0x3A, 0xF3, 0x58, 0x05, 0xAA, 0xFD, 0xE5, 0x00, 0xC1, 0xFF, + 0x06, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B, + 0x01, 0xCF, 0xFC, 0x96, 0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A, + 0xD4, 0xF1, 0x2B, 0x07, 0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32, + 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD9, 0xFF, 0x39, 0x00, 0xF4, 0xFF, + 0x4E, 0xFF, 0xAC, 0x02, 0x98, 0xF7, 0x65, 0x44, 0xD6, 0x14, 0x6C, + 0xF6, 0x9F, 0x05, 0xB6, 0xFC, 0xBD, 0x01, 0x42, 0xFF, 0x32, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE, + 0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01, + 0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD5, 0x01, 0x78, 0xFC, + 0x2F, 0x06, 0x13, 0xF5, 0x7C, 0x19, 0xF7, 0x41, 0x9B, 0xF5, 0xD1, + 0x03, 0x9F, 0xFE, 0x57, 0x00, 0x08, 0x00, 0xEC, 0xFF, 0x06, 0x00, + 0xFD, 0xFF, 0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16, + 0x07, 0x85, 0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06, + 0x84, 0xFC, 0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04, + 0x00, 0xF6, 0xFF, 0xEB, 0xFF, 0x91, 0x00, 0x3B, 0xFE, 0x75, 0x04, + 0x92, 0xF4, 0x36, 0x40, 0x6E, 0x1C, 0x50, 0xF4, 0x7B, 0x06, 0x5B, + 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, + 0x9C, 0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, + 0xFF, 0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, + 0x49, 0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x49, 0xFF, 0xAA, 0x01, 0xE4, 0xFC, 0x38, 0x05, 0x54, 0xF7, + 0xFE, 0x11, 0xAA, 0x45, 0x09, 0xF9, 0xE2, 0x01, 0xC4, 0xFF, 0xB3, + 0xFF, 0x59, 0x00, 0xCD, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80, + 0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01, + 0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA9, + 0xFF, 0x15, 0x01, 0x5B, 0xFD, 0xD0, 0x05, 0x97, 0xF2, 0xE6, 0x3A, + 0x21, 0x24, 0xB1, 0xF2, 0x04, 0x07, 0x33, 0xFC, 0xE5, 0x01, 0x39, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00, + 0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD, + 0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF, + 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x6A, + 0x01, 0x74, 0xFD, 0x0A, 0x04, 0xD5, 0xF9, 0xED, 0x0A, 0x03, 0x48, + 0x7C, 0xFD, 0x9E, 0xFF, 0x0A, 0x01, 0x01, 0xFF, 0xAF, 0x00, 0xAB, + 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, + 0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE, 0x3D, 0x91, + 0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF, 0x02, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x75, 0xFF, 0x7A, 0x01, 0xB8, + 0xFC, 0xB4, 0x06, 0x9E, 0xF1, 0xA2, 0x34, 0xAD, 0x2B, 0xB6, 0xF1, + 0x29, 0x07, 0x45, 0xFC, 0xCB, 0x01, 0x49, 0xFF, 0x31, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B, 0xFF, + 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6, 0xCA, + 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x20, 0x00, 0x80, 0xFF, 0x1C, 0x01, 0x1C, 0xFE, 0xBD, + 0x02, 0x6E, 0xFC, 0x7D, 0x04, 0xF3, 0x48, 0xE2, 0x02, 0x1F, 0xFD, + 0x60, 0x02, 0x4C, 0xFE, 0x06, 0x01, 0x89, 0xFF, 0x1D, 0x00, 0xFE, + 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01, 0x88, 0xFC, 0x09, 0x06, + 0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20, 0xF6, 0x83, 0x03, 0xCF, + 0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF, + 0x2E, 0x00, 0x50, 0xFF, 0xBF, 0x01, 0x54, 0xFC, 0x20, 0x07, 0x94, + 0xF1, 0xA6, 0x2D, 0xD0, 0x32, 0x85, 0xF1, 0xDD, 0x06, 0x96, 0xFC, + 0x90, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB, + 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE, 0xB9, 0x04, 0x27, 0xF4, + 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99, 0x06, 0x50, 0xFC, 0xE2, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA2, 0xFF, + 0xC7, 0x00, 0xD0, 0xFE, 0x65, 0x01, 0xF6, 0xFE, 0xD9, 0xFE, 0x6A, + 0x48, 0x1F, 0x09, 0x87, 0xFA, 0xB3, 0x03, 0xA0, 0xFD, 0x56, 0x01, + 0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4D, + 0xFF, 0xA0, 0x01, 0xFB, 0xFC, 0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10, + 0x2B, 0x46, 0xBB, 0xF9, 0x83, 0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68, + 0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, + 0xE1, 0x01, 0x31, 0xFC, 0x19, 0x07, 0x5B, 0xF2, 0x30, 0x26, 0x4B, + 0x39, 0x3B, 0xF2, 0x1A, 0x06, 0x29, 0xFD, 0x33, 0x01, 0x99, 0xFF, + 0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, + 0x01, 0x3A, 0xFD, 0x00, 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, + 0x79, 0xF2, 0x12, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC4, 0xFF, 0x70, 0x00, 0x84, 0xFF, + 0x19, 0x00, 0x4D, 0x01, 0x22, 0xFA, 0x70, 0x46, 0x0A, 0x10, 0xFC, + 0xF7, 0xEB, 0x04, 0x08, 0xFD, 0x9A, 0x01, 0x4F, 0xFF, 0x2E, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90, + 0xFD, 0xD2, 0x03, 0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE, + 0x33, 0xFF, 0x45, 0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16, + 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4B, 0xFC, + 0xA9, 0x06, 0xD2, 0xF3, 0x81, 0x1E, 0xE4, 0x3E, 0xEF, 0xF3, 0xDE, + 0x04, 0xF9, 0xFD, 0xB7, 0x00, 0xD8, 0xFF, 0xFD, 0xFF, 0x03, 0x00, + 0xFD, 0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, + 0x06, 0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, + 0x4E, 0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE4, 0xFF, 0x1D, 0x00, 0x2D, 0x00, 0xEA, 0xFE, 0x56, 0x03, + 0x6D, 0xF6, 0x17, 0x43, 0x70, 0x17, 0xA6, 0xF5, 0xF3, 0x05, 0x91, + 0xFC, 0xCC, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x86, 0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73, + 0x03, 0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE, + 0x14, 0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, + 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x99, 0xFC, 0xE1, 0x05, 0xD1, 0xF5, + 0xDC, 0x16, 0x65, 0x43, 0xAD, 0xF6, 0x31, 0x03, 0x00, 0xFF, 0x20, + 0x00, 0x23, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, + 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62, + 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01, + 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0xFF, 0xFF, 0xD3, + 0xFF, 0xC1, 0x00, 0xE7, 0xFD, 0xFA, 0x04, 0xC4, 0xF3, 0x7E, 0x3E, + 0x19, 0x1F, 0xB0, 0xF3, 0xB5, 0x06, 0x47, 0xFC, 0xE4, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8, 0x00, + 0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48, 0x47, + 0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x96, + 0x01, 0x13, 0xFD, 0xD5, 0x04, 0x2C, 0xF8, 0x7D, 0x0F, 0xA3, 0x46, + 0x76, 0xFA, 0x22, 0x01, 0x32, 0x00, 0x76, 0xFF, 0x76, 0x00, 0xC1, + 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF, 0xE4, 0x01, + 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54, 0x3A, 0x74, + 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF, 0x11, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x95, 0xFF, 0x3B, 0x01, 0x1B, + 0xFD, 0x2D, 0x06, 0x24, 0xF2, 0xD3, 0x38, 0xC6, 0x26, 0x45, 0xF2, + 0x1D, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2, 0xFF, 0xE2, 0xFF, + 0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11, 0x8F, 0xF7, 0x1D, + 0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x51, 0x01, 0xAC, 0xFD, 0x9A, + 0x03, 0xBA, 0xFA, 0x9E, 0x08, 0x81, 0x48, 0x40, 0xFF, 0xC6, 0xFE, + 0x80, 0x01, 0xC2, 0xFE, 0xCE, 0x00, 0x9F, 0xFF, 0x17, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06, + 0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23, + 0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFD, 0xFF, + 0x27, 0x00, 0x66, 0xFF, 0x96, 0x01, 0x8E, 0xFC, 0xE7, 0x06, 0x81, + 0xF1, 0x48, 0x32, 0x34, 0x2E, 0x8D, 0xF1, 0x1C, 0x07, 0x5A, 0xFC, + 0xBB, 0x01, 0x53, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9, + 0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9, 0xFE, 0xA6, 0x03, 0xE4, 0xF5, + 0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5, 0x1A, 0x06, 0x81, 0xFC, 0xD2, + 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8B, 0xFF, + 0xFF, 0x00, 0x5A, 0xFE, 0x46, 0x02, 0x52, 0xFD, 0x70, 0x02, 0xED, + 0x48, 0xF5, 0x04, 0x3B, 0xFC, 0xD7, 0x02, 0x0F, 0xFE, 0x23, 0x01, + 0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, + 0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, + 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF, + 0xCE, 0x01, 0x41, 0xFC, 0x2A, 0x07, 0xC2, 0xF1, 0x1B, 0x2B, 0x25, + 0x35, 0xA8, 0xF1, 0xA7, 0x06, 0xC2, 0xFC, 0x74, 0x01, 0x78, 0xFF, + 0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9, + 0x00, 0xBF, 0xFD, 0x38, 0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20, + 0x66, 0xF3, 0xCE, 0x06, 0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAE, 0xFF, 0xA9, 0x00, 0x0F, 0xFF, + 0xF0, 0x00, 0xCD, 0xFF, 0x1B, 0xFD, 0xE4, 0x47, 0x73, 0x0B, 0xA2, + 0xF9, 0x23, 0x04, 0x68, 0xFD, 0x70, 0x01, 0x5F, 0xFF, 0x29, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B, + 0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB, + 0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, + 0xFD, 0x06, 0xCB, 0xF2, 0x8A, 0x23, 0x58, 0x3B, 0xB4, 0xF2, 0xBA, + 0x05, 0x6A, 0xFD, 0x0B, 0x01, 0xAE, 0xFF, 0x0D, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56, + 0x06, 0xF7, 0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07, + 0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, + 0x00, 0xCF, 0xFF, 0x52, 0x00, 0xC0, 0xFF, 0xAC, 0xFF, 0x0C, 0x02, + 0xBC, 0xF8, 0x6D, 0x45, 0x8E, 0x12, 0x24, 0xF7, 0x4D, 0x05, 0xDB, + 0xFC, 0xAE, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x24, 0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, + 0xFB, 0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, + 0xA3, 0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xDD, 0x01, 0x60, 0xFC, 0x6D, 0x06, 0x76, 0xF4, + 0xD8, 0x1B, 0x95, 0x40, 0xC3, 0xF4, 0x56, 0x04, 0x4E, 0xFE, 0x85, + 0x00, 0xF1, 0xFF, 0xF4, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x29, 0x00, + 0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15, + 0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01, + 0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x02, + 0x00, 0x63, 0x00, 0x8A, 0xFE, 0xF3, 0x03, 0x63, 0xF5, 0xA1, 0x41, + 0x12, 0x1A, 0xEB, 0xF4, 0x3F, 0x06, 0x72, 0xFC, 0xD7, 0x01, 0x39, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00, + 0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07, + 0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF, + 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x43, 0xFF, 0xBA, + 0x01, 0xBF, 0xFC, 0x8B, 0x05, 0x99, 0xF6, 0x43, 0x14, 0xA9, 0x44, + 0xDE, 0xF7, 0x85, 0x02, 0x65, 0xFF, 0xE7, 0xFF, 0x3F, 0x00, 0xD6, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD5, 0x01, + 0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46, 0x36, 0xC5, + 0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF, 0x1E, 0x00, + 0xFE, 0xFF, 0x01, 0x00, 0x08, 0x00, 0xBC, 0xFF, 0xEF, 0x00, 0x9A, + 0xFD, 0x72, 0x05, 0x16, 0xF3, 0xA5, 0x3C, 0xC4, 0x21, 0x21, 0xF3, + 0xE4, 0x06, 0x39, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6, 0x00, + 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9, 0x5A, + 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x80, 0x01, 0x45, 0xFD, 0x6C, + 0x04, 0x0B, 0xF9, 0x0B, 0x0D, 0x73, 0x47, 0x02, 0xFC, 0x58, 0x00, + 0xA3, 0x00, 0x39, 0xFF, 0x94, 0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37, 0xFC, 0xEB, 0x06, + 0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD, 0xF2, 0x84, 0x05, 0x8D, + 0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF, + 0x1D, 0x00, 0x83, 0xFF, 0x5E, 0x01, 0xE3, 0xFC, 0x7B, 0x06, 0xD0, + 0xF1, 0xA5, 0x36, 0x62, 0x29, 0xEF, 0xF1, 0x29, 0x07, 0x39, 0xFC, + 0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5, + 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF, 0x67, 0x02, 0x14, 0xF8, + 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C, 0x05, 0xC5, 0xFC, 0xB7, + 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, + 0x76, 0xFF, 0x35, 0x01, 0xE7, 0xFD, 0x26, 0x03, 0xA1, 0xFB, 0x64, + 0x06, 0xD2, 0x48, 0x21, 0x01, 0xE8, 0xFD, 0xF7, 0x01, 0x83, 0xFE, + 0xEC, 0x00, 0x93, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, + 0xFF, 0xD9, 0x01, 0x6D, 0xFC, 0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A, + 0x5F, 0x41, 0x3A, 0xF5, 0x0C, 0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE, + 0xFF, 0xEF, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF, + 0xAD, 0x01, 0x6C, 0xFC, 0x0C, 0x07, 0x7F, 0xF1, 0xDC, 0x2F, 0xAD, + 0x30, 0x7D, 0xF1, 0x01, 0x07, 0x76, 0xFC, 0xA6, 0x01, 0x5E, 0xFF, + 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, + 0x00, 0x5D, 0xFE, 0x3E, 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, + 0x93, 0xF4, 0x62, 0x06, 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x97, 0xFF, 0xE2, 0x00, 0x98, 0xFE, + 0xCF, 0x01, 0x33, 0xFE, 0x7D, 0x00, 0xBB, 0x48, 0x1F, 0x07, 0x54, + 0xFB, 0x4C, 0x03, 0xD3, 0xFD, 0x3F, 0x01, 0x73, 0xFF, 0x23, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3, + 0xFC, 0x5D, 0x05, 0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8, + 0x2A, 0x02, 0x9A, 0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C, + 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDA, 0x01, 0x35, 0xFC, + 0x27, 0x07, 0x09, 0xF2, 0x85, 0x28, 0x63, 0x37, 0xE9, 0xF1, 0x63, + 0x06, 0xF5, 0xFC, 0x53, 0x01, 0x89, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x00, 0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, + 0x05, 0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, + 0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, + 0x00, 0xB9, 0xFF, 0x8A, 0x00, 0x4D, 0xFF, 0x7D, 0x00, 0x9C, 0x00, + 0x7B, 0xFB, 0x31, 0x47, 0xD9, 0x0D, 0xC0, 0xF8, 0x8F, 0x04, 0x34, + 0xFD, 0x87, 0x01, 0x56, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C, + 0xF9, 0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00, + 0x19, 0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD5, 0x06, 0x4F, 0xF3, + 0xE0, 0x20, 0x45, 0x3D, 0x4D, 0xF3, 0x4B, 0x05, 0xB3, 0xFD, 0xE0, + 0x00, 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, + 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86, + 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01, + 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xDA, 0xFF, 0x36, + 0x00, 0xFA, 0xFF, 0x43, 0xFF, 0xBF, 0x02, 0x75, 0xF7, 0x42, 0x44, + 0x20, 0x15, 0x55, 0xF6, 0xA9, 0x05, 0xB2, 0xFC, 0xBF, 0x01, 0x41, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF, + 0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05, 0xEA, + 0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA, 0x00, + 0x8D, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD4, + 0x01, 0x7C, 0xFC, 0x27, 0x06, 0x28, 0xF5, 0x31, 0x19, 0x21, 0x42, + 0xB8, 0xF5, 0xC0, 0x03, 0xAA, 0xFE, 0x51, 0x00, 0x0B, 0x00, 0xEA, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF, 0xB7, 0x01, + 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3, 0x31, 0x7E, + 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF, 0x28, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xE8, 0xFF, 0x96, 0x00, 0x31, + 0xFE, 0x84, 0x04, 0x79, 0xF4, 0x07, 0x40, 0xBA, 0x1C, 0x3E, 0xF4, + 0x82, 0x06, 0x58, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8, 0xFE, 0x93, 0x01, + 0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08, 0xE1, 0xFA, 0x86, + 0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA8, 0x01, 0xE9, 0xFC, 0x2D, + 0x05, 0x6B, 0xF7, 0xB6, 0x11, 0xC8, 0x45, 0x30, 0xF9, 0xCD, 0x01, + 0xD0, 0xFF, 0xAC, 0xFF, 0x5C, 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07, + 0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11, + 0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x10, 0x00, 0xA7, 0xFF, 0x19, 0x01, 0x53, 0xFD, 0xDB, 0x05, 0x88, + 0xF2, 0xAD, 0x3A, 0x6D, 0x24, 0xA4, 0xF2, 0x08, 0x07, 0x32, 0xFC, + 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBF, + 0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44, 0x00, 0x01, 0x01, 0xB6, 0xFA, + 0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8, 0xC4, 0x04, 0x1B, 0xFD, 0x92, + 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x63, 0xFF, 0x67, 0x01, 0x7A, 0xFD, 0xFE, 0x03, 0xEE, 0xF9, 0xAA, + 0x0A, 0x16, 0x48, 0xAC, 0xFD, 0x86, 0xFF, 0x17, 0x01, 0xFA, 0xFE, + 0xB3, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, + 0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, + 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x73, 0xFF, + 0x7D, 0x01, 0xB3, 0xFC, 0xBB, 0x06, 0x9A, 0xF1, 0x60, 0x34, 0xF5, + 0x2B, 0xB0, 0xF1, 0x28, 0x07, 0x47, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, + 0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17, + 0x00, 0x10, 0xFF, 0x15, 0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16, + 0xF1, 0xF5, 0xD3, 0x05, 0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x19, 0x01, + 0x23, 0xFE, 0xB0, 0x02, 0x87, 0xFC, 0x41, 0x04, 0xF4, 0x48, 0x1C, + 0x03, 0x06, 0xFD, 0x6E, 0x02, 0x45, 0xFE, 0x09, 0x01, 0x88, 0xFF, + 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C, + 0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6, + 0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4F, 0xFF, 0xC1, 0x01, 0x52, 0xFC, + 0x22, 0x07, 0x98, 0xF1, 0x5E, 0x2D, 0x13, 0x33, 0x87, 0xF1, 0xD8, + 0x06, 0x9B, 0xFC, 0x8D, 0x01, 0x6B, 0xFF, 0x25, 0x00, 0xFD, 0xFF, + 0x03, 0x00, 0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8, + 0x04, 0x10, 0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06, + 0x4E, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, + 0x00, 0xA3, 0xFF, 0xC3, 0x00, 0xD7, 0xFE, 0x58, 0x01, 0x0F, 0xFF, + 0xA6, 0xFE, 0x5D, 0x48, 0x61, 0x09, 0x6E, 0xFA, 0xC0, 0x03, 0x99, + 0xFD, 0x59, 0x01, 0x68, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2E, 0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, + 0xF7, 0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, + 0x8E, 0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x16, 0x07, 0x67, 0xF2, + 0xE5, 0x25, 0x87, 0x39, 0x47, 0xF2, 0x10, 0x06, 0x30, 0xFD, 0x2F, + 0x01, 0x9C, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x00, + 0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5, + 0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC5, 0xFF, 0x6D, + 0x00, 0x8B, 0xFF, 0x0D, 0x00, 0x63, 0x01, 0xF9, 0xF9, 0x55, 0x46, + 0x51, 0x10, 0xE3, 0xF7, 0xF7, 0x04, 0x03, 0xFD, 0x9D, 0x01, 0x4E, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, + 0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57, + 0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00, + 0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, + 0x01, 0x4D, 0xFC, 0xA3, 0x06, 0xE4, 0xF3, 0x36, 0x1E, 0x16, 0x3F, + 0x05, 0xF4, 0xCF, 0x04, 0x02, 0xFE, 0xB2, 0x00, 0xDB, 0xFF, 0xFC, + 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF, 0x8B, 0x01, + 0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A, 0x2D, 0x9A, + 0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF, 0x2F, 0x00, + 0xFD, 0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x1A, 0x00, 0x33, 0x00, 0xDF, + 0xFE, 0x68, 0x03, 0x4E, 0xF6, 0xEE, 0x42, 0xBB, 0x17, 0x90, 0xF5, + 0xFC, 0x05, 0x8E, 0xFC, 0xCD, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74, 0x02, + 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC, 0xA9, + 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC7, 0x01, 0x9D, 0xFC, 0xD8, + 0x05, 0xE7, 0xF5, 0x91, 0x16, 0x89, 0x43, 0xCD, 0xF6, 0x1E, 0x03, + 0x0B, 0xFF, 0x1A, 0x00, 0x26, 0x00, 0xE0, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01, 0x48, 0xFC, 0x28, 0x07, + 0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97, 0xF1, 0xBE, 0x06, 0xB0, + 0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x02, 0x00, + 0x00, 0x00, 0xD0, 0xFF, 0xC7, 0x00, 0xDE, 0xFD, 0x08, 0x05, 0xB0, + 0xF3, 0x4A, 0x3E, 0x64, 0x1F, 0xA0, 0xF3, 0xBB, 0x06, 0x45, 0xFC, + 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xA9, + 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01, 0x7A, 0xFF, 0xC5, 0xFD, + 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8, 0x03, 0x7D, 0xFD, 0x66, + 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, + 0x52, 0xFF, 0x93, 0x01, 0x18, 0xFD, 0xC9, 0x04, 0x45, 0xF8, 0x36, + 0x0F, 0xBB, 0x46, 0xA1, 0xFA, 0x0C, 0x01, 0x3E, 0x00, 0x70, 0xFF, + 0x7A, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, + 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24, + 0x8F, 0x3A, 0x82, 0xF2, 0xE1, 0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6, + 0xFF, 0x10, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x17, 0x00, 0x93, 0xFF, + 0x3F, 0x01, 0x15, 0xFD, 0x36, 0x06, 0x19, 0xF2, 0x97, 0x38, 0x11, + 0x27, 0x3B, 0xF2, 0x1F, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, + 0xFF, 0xD6, 0xFF, 0xC3, 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, + 0x77, 0xF7, 0x28, 0x05, 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6D, 0xFF, 0x4E, 0x01, + 0xB3, 0xFD, 0x8D, 0x03, 0xD4, 0xFA, 0x5D, 0x08, 0x8D, 0x48, 0x74, + 0xFF, 0xAE, 0xFE, 0x8D, 0x01, 0xBB, 0xFE, 0xD1, 0x00, 0x9E, 0xFF, + 0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57, + 0xFC, 0x85, 0x06, 0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4, + 0x8C, 0x04, 0x2C, 0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04, + 0x00, 0xFD, 0xFF, 0x27, 0x00, 0x65, 0xFF, 0x98, 0x01, 0x8A, 0xFC, + 0xEC, 0x06, 0x7F, 0xF1, 0x04, 0x32, 0x7B, 0x2E, 0x8A, 0xF1, 0x1A, + 0x07, 0x5D, 0xFC, 0xB8, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, + 0x06, 0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, + 0x03, 0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, + 0x7D, 0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, + 0x00, 0x8D, 0xFF, 0xFC, 0x00, 0x61, 0xFE, 0x39, 0x02, 0x6B, 0xFD, + 0x37, 0x02, 0xEB, 0x48, 0x31, 0x05, 0x21, 0xFC, 0xE4, 0x02, 0x08, + 0xFE, 0x26, 0x01, 0x7C, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x32, 0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A, + 0xF6, 0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF, + 0xFE, 0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x32, + 0x00, 0x47, 0xFF, 0xD0, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xCA, 0xF1, + 0xD1, 0x2A, 0x65, 0x35, 0xAE, 0xF1, 0xA0, 0x06, 0xC7, 0xFC, 0x70, + 0x01, 0x7A, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00, + 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61, + 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA5, + 0x00, 0x16, 0xFF, 0xE3, 0x00, 0xE4, 0xFF, 0xEB, 0xFC, 0xD2, 0x47, + 0xB6, 0x0B, 0x89, 0xF9, 0x2F, 0x04, 0x62, 0xFD, 0x72, 0x01, 0x5E, + 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x56, 0xFF, + 0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D, 0x26, + 0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89, 0x00, + 0xBA, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, + 0x01, 0x34, 0xFC, 0xF9, 0x06, 0xD9, 0xF2, 0x3F, 0x23, 0x90, 0x3B, + 0xC4, 0xF2, 0xAE, 0x05, 0x72, 0xFD, 0x07, 0x01, 0xB0, 0xFF, 0x0C, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF, 0x51, 0x01, + 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60, 0x28, 0x0E, + 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x4F, 0x00, 0xC7, 0xFF, 0xA0, + 0xFF, 0x20, 0x02, 0x96, 0xF8, 0x4E, 0x45, 0xD7, 0x12, 0x0D, 0xF7, + 0x58, 0x05, 0xD6, 0xFC, 0xB0, 0x01, 0x47, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40, 0x01, 0xD0, 0xFD, + 0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48, 0x62, 0x00, 0x3F, + 0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98, 0xFF, 0x19, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x63, 0xFC, 0x66, + 0x06, 0x89, 0xF4, 0x8C, 0x1B, 0xC3, 0x40, 0xDD, 0xF4, 0x46, 0x04, + 0x58, 0xFE, 0x80, 0x00, 0xF4, 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD, + 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06, + 0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A, + 0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, + 0xEF, 0xFF, 0xFF, 0xFF, 0x69, 0x00, 0x80, 0xFE, 0x04, 0x04, 0x48, + 0xF5, 0x74, 0x41, 0x5D, 0x1A, 0xD7, 0xF4, 0x47, 0x06, 0x6F, 0xFC, + 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x93, + 0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD, 0x01, 0xDC, 0xFD, 0x3C, 0x01, + 0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB, 0x1F, 0x03, 0xEA, 0xFD, 0x34, + 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, + 0x44, 0xFF, 0xB8, 0x01, 0xC3, 0xFC, 0x81, 0x05, 0xB0, 0xF6, 0xFA, + 0x13, 0xCC, 0x44, 0x02, 0xF8, 0x71, 0x02, 0x71, 0xFF, 0xE1, 0xFF, + 0x42, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43, + 0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, + 0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, + 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x09, 0x00, 0xBA, 0xFF, + 0xF4, 0x00, 0x91, 0xFD, 0x7E, 0x05, 0x05, 0xF3, 0x6E, 0x3C, 0x10, + 0x22, 0x12, 0xF3, 0xE9, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35, + 0xFF, 0xA9, 0x00, 0x4D, 0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C, + 0x18, 0xF9, 0x66, 0x04, 0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5A, 0xFF, 0x7D, 0x01, + 0x4B, 0xFD, 0x60, 0x04, 0x24, 0xF9, 0xC6, 0x0C, 0x86, 0x47, 0x30, + 0xFC, 0x41, 0x00, 0xB0, 0x00, 0x32, 0xFF, 0x98, 0x00, 0xB4, 0xFF, + 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38, + 0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3, + 0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x81, 0xFF, 0x62, 0x01, 0xDD, 0xFC, + 0x83, 0x06, 0xC9, 0xF1, 0x66, 0x36, 0xAC, 0x29, 0xE7, 0xF1, 0x2A, + 0x07, 0x3A, 0xFC, 0xD5, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B, + 0x02, 0xF0, 0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05, + 0xC1, 0xFC, 0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x77, 0xFF, 0x32, 0x01, 0xED, 0xFD, 0x19, 0x03, + 0xBB, 0xFB, 0x26, 0x06, 0xD7, 0x48, 0x58, 0x01, 0xCF, 0xFD, 0x04, + 0x02, 0x7D, 0xFE, 0xEF, 0x00, 0x92, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, + 0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, + 0xF4, 0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, + 0x66, 0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2B, + 0x00, 0x59, 0xFF, 0xB0, 0x01, 0x69, 0xFC, 0x0F, 0x07, 0x80, 0xF1, + 0x96, 0x2F, 0xF2, 0x30, 0x7C, 0xF1, 0xFD, 0x06, 0x7A, 0xFC, 0xA3, + 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF4, 0xFF, + 0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB, + 0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x98, 0xFF, 0xDE, + 0x00, 0x9F, 0xFE, 0xC2, 0x01, 0x4B, 0xFE, 0x48, 0x00, 0xB3, 0x48, + 0x5E, 0x07, 0x3B, 0xFB, 0x59, 0x03, 0xCD, 0xFD, 0x42, 0x01, 0x71, + 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF, + 0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C, + 0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00, + 0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x40, 0xFF, 0xDB, + 0x01, 0x35, 0xFC, 0x25, 0x07, 0x13, 0xF2, 0x3A, 0x28, 0xA0, 0x37, + 0xF2, 0xF1, 0x5A, 0x06, 0xFB, 0xFC, 0x4F, 0x01, 0x8B, 0xFF, 0x1A, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF, 0x09, 0x01, + 0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64, 0x23, 0xD2, + 0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x11, 0x00, 0xBB, 0xFF, 0x87, 0x00, 0x54, 0xFF, 0x70, + 0x00, 0xB3, 0x00, 0x4E, 0xFB, 0x1A, 0x47, 0x1F, 0x0E, 0xA8, 0xF8, + 0x9B, 0x04, 0x2E, 0xFD, 0x8A, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65, 0xFD, + 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD, 0xD9, + 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD0, + 0x06, 0x5E, 0xF3, 0x94, 0x20, 0x7B, 0x3D, 0x60, 0xF3, 0x3E, 0x05, + 0xBB, 0xFD, 0xDB, 0x00, 0xC6, 0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE, + 0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01, 0xC4, 0xFC, 0xA4, 0x06, + 0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6, 0xF1, 0x2A, 0x07, 0x40, + 0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, + 0xDB, 0xFF, 0x33, 0x00, 0x01, 0x00, 0x38, 0xFF, 0xD3, 0x02, 0x53, + 0xF7, 0x1F, 0x44, 0x69, 0x15, 0x3F, 0xF6, 0xB2, 0x05, 0xAD, 0xFC, + 0xC1, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, + 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE, 0xDE, 0x02, 0x2E, 0xFC, + 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E, 0xFD, 0x3F, 0x02, 0x5D, + 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3B, 0xFF, 0xD3, 0x01, 0x7F, 0xFC, 0x1F, 0x06, 0x3C, 0xF5, 0xE6, + 0x18, 0x4D, 0x42, 0xD5, 0xF5, 0xAF, 0x03, 0xB4, 0xFE, 0x4B, 0x00, + 0x0E, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53, + 0xFF, 0xBA, 0x01, 0x5B, 0xFC, 0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E, + 0x26, 0x32, 0x80, 0xF1, 0xEA, 0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66, + 0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8, 0xFF, 0xE6, 0xFF, + 0x9C, 0x00, 0x27, 0xFE, 0x94, 0x04, 0x61, 0xF4, 0xD7, 0x3F, 0x06, + 0x1D, 0x2B, 0xF4, 0x89, 0x06, 0x56, 0xFC, 0xE0, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, + 0xFE, 0x86, 0x01, 0xBA, 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, + 0xC7, 0xFA, 0x93, 0x03, 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4B, 0xFF, 0xA6, 0x01, + 0xEE, 0xFC, 0x23, 0x05, 0x83, 0xF7, 0x6E, 0x11, 0xE5, 0x45, 0x57, + 0xF9, 0xB8, 0x01, 0xDC, 0xFF, 0xA5, 0xFF, 0x5F, 0x00, 0xCA, 0xFF, + 0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32, + 0xFC, 0x1E, 0x07, 0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2, + 0x32, 0x06, 0x18, 0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x11, 0x00, 0xA4, 0xFF, 0x1D, 0x01, 0x4C, 0xFD, + 0xE6, 0x05, 0x7B, 0xF2, 0x71, 0x3A, 0xB8, 0x24, 0x97, 0xF2, 0x0B, + 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x0F, 0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, + 0x01, 0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, + 0x15, 0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x64, 0xFF, 0x65, 0x01, 0x81, 0xFD, 0xF2, 0x03, + 0x08, 0xFA, 0x68, 0x0A, 0x25, 0x48, 0xDE, 0xFD, 0x6E, 0xFF, 0x24, + 0x01, 0xF3, 0xFE, 0xB6, 0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8, + 0xF3, 0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD, + 0xC4, 0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x23, + 0x00, 0x71, 0xFF, 0x81, 0x01, 0xAE, 0xFC, 0xC1, 0x06, 0x95, 0xF1, + 0x1E, 0x34, 0x3E, 0x2C, 0xAB, 0xF1, 0x27, 0x07, 0x49, 0xFC, 0xC8, + 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF, + 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77, + 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01, + 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, + 0xFF, 0x16, 0x01, 0x2A, 0xFE, 0xA3, 0x02, 0xA1, 0xFC, 0x06, 0x04, + 0xF5, 0x48, 0x56, 0x03, 0xED, 0xFC, 0x7B, 0x02, 0x3E, 0xFE, 0x0C, + 0x01, 0x86, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17, 0x02, + 0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B, 0x00, + 0xE4, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, + 0x01, 0x4F, 0xFC, 0x24, 0x07, 0x9C, 0xF1, 0x17, 0x2D, 0x57, 0x33, + 0x8A, 0xF1, 0xD3, 0x06, 0x9F, 0xFC, 0x8A, 0x01, 0x6D, 0xFF, 0x25, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF, 0xB4, 0x00, + 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B, 0x1E, 0xDB, + 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC0, 0x00, 0xDE, 0xFE, 0x4B, + 0x01, 0x27, 0xFF, 0x73, 0xFE, 0x4F, 0x48, 0xA2, 0x09, 0x54, 0xFA, + 0xCC, 0x03, 0x93, 0xFD, 0x5C, 0x01, 0x67, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C, 0x01, 0x05, 0xFD, + 0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46, 0x0D, 0xFA, 0x58, + 0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x14, + 0x07, 0x73, 0xF2, 0x99, 0x25, 0xC2, 0x39, 0x54, 0xF2, 0x05, 0x06, + 0x37, 0xFD, 0x2B, 0x01, 0x9E, 0xFF, 0x13, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06, + 0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31, + 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC6, 0xFF, 0x69, 0x00, 0x91, 0xFF, 0x00, 0x00, 0x78, 0x01, 0xD0, + 0xF9, 0x39, 0x46, 0x98, 0x10, 0xCB, 0xF7, 0x02, 0x05, 0xFE, 0xFC, + 0x9F, 0x01, 0x4D, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, + 0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D, 0xFD, 0xB9, 0x03, 0x7B, 0xFA, + 0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE, 0x03, 0xFF, 0x5F, 0x01, 0xD4, + 0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE2, 0x01, 0x4F, 0xFC, 0x9C, 0x06, 0xF5, 0xF3, 0xEA, + 0x1D, 0x47, 0x3F, 0x1B, 0xF4, 0xC1, 0x04, 0x0B, 0xFE, 0xAC, 0x00, + 0xDE, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A, + 0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, + 0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE6, 0xFF, 0x17, 0x00, + 0x39, 0x00, 0xD4, 0xFE, 0x7A, 0x03, 0x2F, 0xF6, 0xC7, 0x42, 0x06, + 0x18, 0x7B, 0xF5, 0x05, 0x06, 0x8A, 0xFC, 0xCF, 0x01, 0x3C, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49, + 0xFE, 0x67, 0x02, 0x13, 0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04, + 0x7A, 0xFC, 0xB6, 0x02, 0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01, + 0xA1, 0xFC, 0xCF, 0x05, 0xFC, 0xF5, 0x47, 0x16, 0xB0, 0x43, 0xEE, + 0xF6, 0x0C, 0x03, 0x16, 0xFF, 0x14, 0x00, 0x29, 0x00, 0xDF, 0xFF, + 0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46, + 0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1, + 0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x01, 0x00, 0xCE, 0xFF, 0xCC, 0x00, 0xD5, 0xFD, + 0x16, 0x05, 0x9B, 0xF3, 0x18, 0x3E, 0xB1, 0x1F, 0x8F, 0xF3, 0xC0, + 0x06, 0x43, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x15, 0x00, 0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92, + 0xFF, 0x94, 0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04, + 0x77, 0xFD, 0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x91, 0x01, 0x1E, 0xFD, 0xBE, 0x04, + 0x5E, 0xF8, 0xF0, 0x0E, 0xD3, 0x46, 0xCB, 0xFA, 0xF6, 0x00, 0x4B, + 0x00, 0x69, 0xFF, 0x7D, 0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, + 0xF2, 0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, + 0x17, 0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, + 0x00, 0x91, 0xFF, 0x43, 0x01, 0x0E, 0xFD, 0x40, 0x06, 0x0F, 0xF2, + 0x5B, 0x38, 0x5C, 0x27, 0x30, 0xF2, 0x21, 0x07, 0x33, 0xFC, 0xDE, + 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCC, 0xFF, + 0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8, + 0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01, + 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6E, + 0xFF, 0x4B, 0x01, 0xB9, 0xFD, 0x80, 0x03, 0xEE, 0xFA, 0x1D, 0x08, + 0x98, 0x48, 0xA8, 0xFF, 0x95, 0xFE, 0x9A, 0x01, 0xB4, 0xFE, 0xD4, + 0x00, 0x9C, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F, + 0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF, + 0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9B, + 0x01, 0x86, 0xFC, 0xF1, 0x06, 0x7E, 0xF1, 0xC0, 0x31, 0xC2, 0x2E, + 0x87, 0xF1, 0x17, 0x07, 0x5F, 0xFC, 0xB6, 0x01, 0x55, 0xFF, 0x2D, + 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00, 0x54, 0x00, + 0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56, 0x19, 0x1E, + 0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1C, 0x00, 0x8E, 0xFF, 0xF9, 0x00, 0x68, 0xFE, 0x2C, + 0x02, 0x84, 0xFD, 0xFF, 0x01, 0xE6, 0x48, 0x6E, 0x05, 0x07, 0xFC, + 0xF1, 0x02, 0x01, 0xFE, 0x29, 0x01, 0x7B, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4, 0xFC, + 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7, 0xB6, + 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A, 0x00, + 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3E, 0xFC, 0x2B, + 0x07, 0xD0, 0xF1, 0x89, 0x2A, 0xA6, 0x35, 0xB4, 0xF1, 0x99, 0x06, + 0xCD, 0xFC, 0x6D, 0x01, 0x7C, 0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01, + 0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00, 0xAE, 0xFD, 0x52, 0x05, + 0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47, 0xF3, 0xD8, 0x06, 0x3C, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, + 0xB0, 0xFF, 0xA2, 0x00, 0x1D, 0xFF, 0xD6, 0x00, 0xFC, 0xFF, 0xBC, + 0xFC, 0xC0, 0x47, 0xFA, 0x0B, 0x70, 0xF9, 0x3C, 0x04, 0x5C, 0xFD, + 0x75, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD, 0x89, 0x04, 0xCD, 0xF8, + 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91, 0x00, 0x83, 0x00, 0x4A, + 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF5, 0x06, 0xE7, 0xF2, 0xF2, + 0x22, 0xC7, 0x3B, 0xD4, 0xF2, 0xA2, 0x05, 0x7A, 0xFD, 0x02, 0x01, + 0xB2, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88, + 0xFF, 0x55, 0x01, 0xF2, 0xFC, 0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37, + 0xAA, 0x28, 0x05, 0xF2, 0x27, 0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41, + 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD2, 0xFF, 0x4C, 0x00, + 0xCD, 0xFF, 0x94, 0xFF, 0x34, 0x02, 0x70, 0xF8, 0x2E, 0x45, 0x20, + 0x13, 0xF6, 0xF6, 0x62, 0x05, 0xD1, 0xFC, 0xB2, 0x01, 0x46, 0xFF, + 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, + 0x01, 0xD6, 0xFD, 0x46, 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, + 0x98, 0x00, 0x26, 0xFE, 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, + 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01, + 0x66, 0xFC, 0x5E, 0x06, 0x9C, 0xF4, 0x40, 0x1B, 0xEF, 0x40, 0xF7, + 0xF4, 0x35, 0x04, 0x62, 0xFE, 0x7A, 0x00, 0xF7, 0xFF, 0xF2, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75, + 0xFC, 0x03, 0x07, 0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1, + 0x0A, 0x07, 0x6E, 0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD, + 0xFF, 0x05, 0x00, 0xF0, 0xFF, 0xFC, 0xFF, 0x6E, 0x00, 0x76, 0xFE, + 0x15, 0x04, 0x2C, 0xF5, 0x49, 0x41, 0xA9, 0x1A, 0xC3, 0xF4, 0x4F, + 0x06, 0x6C, 0xFC, 0xD9, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, + 0xFD, 0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, + 0xE4, 0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB6, 0x01, 0xC8, 0xFC, 0x77, 0x05, + 0xC7, 0xF6, 0xB1, 0x13, 0xED, 0x44, 0x26, 0xF8, 0x5D, 0x02, 0x7D, + 0xFF, 0xDA, 0xFF, 0x46, 0x00, 0xD4, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3, + 0xF1, 0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC, + 0x5C, 0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, + 0x00, 0xB7, 0xFF, 0xF9, 0x00, 0x89, 0xFD, 0x8A, 0x05, 0xF4, 0xF2, + 0x37, 0x3C, 0x5B, 0x22, 0x03, 0xF3, 0xED, 0x06, 0x37, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB6, 0xFF, + 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69, + 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B, + 0xFF, 0x7A, 0x01, 0x50, 0xFD, 0x54, 0x04, 0x3D, 0xF9, 0x82, 0x0C, + 0x9A, 0x47, 0x5E, 0xFC, 0x2A, 0x00, 0xBD, 0x00, 0x2B, 0xFF, 0x9B, + 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21, 0xC0, + 0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD, 0xFF, + 0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x80, 0xFF, 0x66, + 0x01, 0xD8, 0xFC, 0x8B, 0x06, 0xC1, 0xF1, 0x27, 0x36, 0xF6, 0x29, + 0xDF, 0xF1, 0x2A, 0x07, 0x3B, 0xFC, 0xD4, 0x01, 0x44, 0xFF, 0x32, + 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00, 0xEA, 0xFF, + 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68, 0x14, 0x8E, + 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF, 0x32, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x79, 0xFF, 0x2F, 0x01, 0xF4, + 0xFD, 0x0C, 0x03, 0xD4, 0xFB, 0xE9, 0x05, 0xDE, 0x48, 0x8F, 0x01, + 0xB6, 0xFD, 0x11, 0x02, 0x76, 0xFE, 0xF2, 0x00, 0x91, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, 0x01, 0x73, 0xFC, + 0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41, 0x71, 0xF5, 0xEB, + 0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED, 0xFF, 0x06, 0x00, + 0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB2, 0x01, 0x65, 0xFC, 0x12, + 0x07, 0x82, 0xF1, 0x50, 0x2F, 0x38, 0x31, 0x7C, 0xF1, 0xF9, 0x06, + 0x7E, 0xFC, 0xA1, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04, + 0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04, + 0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F, + 0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, + 0x9A, 0xFF, 0xDB, 0x00, 0xA6, 0xFE, 0xB4, 0x01, 0x64, 0xFE, 0x12, + 0x00, 0xAA, 0x48, 0x9E, 0x07, 0x21, 0xFB, 0x66, 0x03, 0xC6, 0xFD, + 0x45, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD, 0xFC, 0x48, 0x05, 0x30, 0xF7, + 0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8, 0x01, 0x02, 0xB2, 0xFF, 0xBD, + 0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x24, 0x07, 0x1C, 0xF2, 0xF0, + 0x27, 0xDF, 0x37, 0xFB, 0xF1, 0x51, 0x06, 0x01, 0xFD, 0x4B, 0x01, + 0x8D, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC, + 0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, + 0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBC, 0xFF, 0x84, 0x00, + 0x5B, 0xFF, 0x64, 0x00, 0xC9, 0x00, 0x22, 0xFB, 0x02, 0x47, 0x64, + 0x0E, 0x8F, 0xF8, 0xA7, 0x04, 0x29, 0xFD, 0x8C, 0x01, 0x54, 0xFF, + 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E, + 0x01, 0x6B, 0xFD, 0x1D, 0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47, + 0x33, 0xFD, 0xC1, 0xFF, 0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD, + 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x40, 0xFC, 0xCB, 0x06, 0x6E, 0xF3, 0x49, 0x20, 0xB0, 0x3D, 0x73, + 0xF3, 0x31, 0x05, 0xC4, 0xFD, 0xD6, 0x00, 0xC8, 0xFF, 0x03, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF, + 0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1, + 0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x2F, 0x00, 0x07, 0x00, 0x2C, 0xFF, + 0xE6, 0x02, 0x31, 0xF7, 0xFA, 0x43, 0xB3, 0x15, 0x29, 0xF6, 0xBC, + 0x05, 0xA9, 0xFC, 0xC2, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1, + 0x02, 0x47, 0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD, + 0x4D, 0x02, 0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFE, + 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01, 0x83, 0xFC, 0x16, 0x06, + 0x51, 0xF5, 0x9B, 0x18, 0x75, 0x42, 0xF3, 0xF5, 0x9D, 0x03, 0xBF, + 0xFE, 0x45, 0x00, 0x11, 0x00, 0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF, + 0x2E, 0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, + 0xF1, 0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, + 0x94, 0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF9, + 0xFF, 0xE3, 0xFF, 0xA1, 0x00, 0x1E, 0xFE, 0xA3, 0x04, 0x49, 0xF4, + 0xA8, 0x3F, 0x52, 0x1D, 0x19, 0xF4, 0x90, 0x06, 0x53, 0xFC, 0xE1, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF, + 0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C, + 0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01, + 0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, + 0xFF, 0xA3, 0x01, 0xF3, 0xFC, 0x18, 0x05, 0x9B, 0xF7, 0x27, 0x11, + 0x02, 0x46, 0x7F, 0xF9, 0xA3, 0x01, 0xE8, 0xFF, 0x9F, 0xFF, 0x63, + 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, + 0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2, + 0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF, + 0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00, 0xA2, 0xFF, 0x22, + 0x01, 0x45, 0xFD, 0xF1, 0x05, 0x6D, 0xF2, 0x38, 0x3A, 0x03, 0x25, + 0x8B, 0xF2, 0x0E, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x3A, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00, 0x7A, 0xFF, + 0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0, 0x0F, 0x20, + 0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF, 0x2E, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x62, 0x01, 0x87, + 0xFD, 0xE5, 0x03, 0x21, 0xFA, 0x25, 0x0A, 0x33, 0x48, 0x0F, 0xFE, + 0x57, 0xFF, 0x31, 0x01, 0xEC, 0xFE, 0xB9, 0x00, 0xA7, 0xFF, 0x15, + 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48, 0xFC, + 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3, 0xF3, + 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03, 0x00, + 0xFE, 0xFF, 0x23, 0x00, 0x70, 0xFF, 0x84, 0x01, 0xA9, 0xFC, 0xC7, + 0x06, 0x91, 0xF1, 0xDC, 0x33, 0x87, 0x2C, 0xA5, 0xF1, 0x26, 0x07, + 0x4B, 0xFC, 0xC6, 0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00, 0xFA, 0xFE, 0x3A, 0x03, + 0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6, 0xF5, 0xE6, 0x05, 0x97, + 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00, + 0x1E, 0x00, 0x84, 0xFF, 0x13, 0x01, 0x31, 0xFE, 0x95, 0x02, 0xBA, + 0xFC, 0xCB, 0x03, 0xF7, 0x48, 0x91, 0x03, 0xD3, 0xFC, 0x88, 0x02, + 0x38, 0xFE, 0x10, 0x01, 0x85, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, + 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC, 0xEF, 0x05, 0xB0, 0xF5, + 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D, 0x03, 0xEF, 0xFE, 0x2A, + 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4D, 0xFF, 0xC4, 0x01, 0x4D, 0xFC, 0x25, 0x07, 0xA1, 0xF1, 0xCE, + 0x2C, 0x99, 0x33, 0x8E, 0xF1, 0xCD, 0x06, 0xA4, 0xFC, 0x87, 0x01, + 0x6E, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFE, 0xFF, 0xD7, + 0xFF, 0xBA, 0x00, 0xF4, 0xFD, 0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E, + 0xA7, 0x1E, 0xCA, 0xF3, 0xAC, 0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA6, 0xFF, 0xBD, 0x00, + 0xE5, 0xFE, 0x3E, 0x01, 0x3F, 0xFF, 0x41, 0xFE, 0x41, 0x48, 0xE4, + 0x09, 0x3B, 0xFA, 0xD9, 0x03, 0x8D, 0xFD, 0x5F, 0x01, 0x66, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, + 0x01, 0x0B, 0xFD, 0xE6, 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, + 0x37, 0xFA, 0x42, 0x01, 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, + 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, + 0x31, 0xFC, 0x11, 0x07, 0x7F, 0xF2, 0x4E, 0x25, 0xFD, 0x39, 0x60, + 0xF2, 0xFB, 0x05, 0x3E, 0xFD, 0x26, 0x01, 0xA0, 0xFF, 0x12, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25, + 0xFD, 0x1E, 0x06, 0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2, + 0x1A, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x66, 0x00, 0x98, 0xFF, 0xF4, 0xFF, + 0x8E, 0x01, 0xA7, 0xF9, 0x1D, 0x46, 0xDF, 0x10, 0xB3, 0xF7, 0x0D, + 0x05, 0xF8, 0xFC, 0xA1, 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, + 0x03, 0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, + 0x6C, 0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x51, 0xFC, 0x96, 0x06, + 0x07, 0xF4, 0x9E, 0x1D, 0x77, 0x3F, 0x32, 0xF4, 0xB2, 0x04, 0x15, + 0xFE, 0xA7, 0x00, 0xE0, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0xFD, 0xFF, + 0x26, 0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84, + 0xF1, 0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC, + 0xBE, 0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE7, + 0xFF, 0x14, 0x00, 0x3F, 0x00, 0xC9, 0xFE, 0x8C, 0x03, 0x11, 0xF6, + 0x9E, 0x42, 0x50, 0x18, 0x66, 0xF5, 0x0D, 0x06, 0x86, 0xFC, 0xD0, + 0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, + 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2, + 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01, + 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC4, 0x01, 0xA5, 0xFC, 0xC5, 0x05, 0x13, 0xF6, 0xFD, 0x15, + 0xD4, 0x43, 0x0F, 0xF7, 0xF9, 0x02, 0x21, 0xFF, 0x0D, 0x00, 0x2C, + 0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49, 0xFF, + 0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B, 0xC3, + 0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76, 0xFF, + 0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00, 0xCB, 0xFF, 0xD1, + 0x00, 0xCC, 0xFD, 0x24, 0x05, 0x87, 0xF3, 0xE4, 0x3D, 0xFD, 0x1F, + 0x7F, 0xF3, 0xC6, 0x06, 0x41, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00, 0x05, 0xFF, + 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E, 0x0B, 0xC8, + 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF, 0x28, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x8F, 0x01, 0x23, + 0xFD, 0xB2, 0x04, 0x76, 0xF8, 0xAA, 0x0E, 0xED, 0x46, 0xF7, 0xFA, + 0xDF, 0x00, 0x57, 0x00, 0x62, 0xFF, 0x80, 0x00, 0xBD, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x33, 0xFC, + 0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B, 0x9E, 0xF2, 0xCB, + 0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x18, 0x00, 0x8F, 0xFF, 0x47, 0x01, 0x08, 0xFD, 0x49, + 0x06, 0x05, 0xF2, 0x1D, 0x38, 0xA6, 0x27, 0x26, 0xF2, 0x23, 0x07, + 0x33, 0xFC, 0xDD, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, + 0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01, + 0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2, + 0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x24, 0x00, 0x6F, 0xFF, 0x48, 0x01, 0xC0, 0xFD, 0x73, 0x03, 0x07, + 0xFB, 0xDD, 0x07, 0xA1, 0x48, 0xDD, 0xFF, 0x7D, 0xFE, 0xA7, 0x01, + 0xAD, 0xFE, 0xD8, 0x00, 0x9B, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C, 0xFC, 0x78, 0x06, 0x5A, 0xF4, + 0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4, 0x6D, 0x04, 0x3F, 0xFE, 0x8E, + 0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x62, 0xFF, 0x9E, 0x01, 0x82, 0xFC, 0xF5, 0x06, 0x7D, 0xF1, 0x7B, + 0x31, 0x09, 0x2F, 0x84, 0xF1, 0x15, 0x07, 0x62, 0xFC, 0xB4, 0x01, + 0x56, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06, + 0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, + 0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x8F, 0xFF, 0xF5, 0x00, + 0x6F, 0xFE, 0x1E, 0x02, 0x9D, 0xFD, 0xC7, 0x01, 0xE1, 0x48, 0xAB, + 0x05, 0xEE, 0xFB, 0xFE, 0x02, 0xFB, 0xFD, 0x2C, 0x01, 0x7A, 0xFF, + 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC, + 0x01, 0xB8, 0xFC, 0x9A, 0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44, + 0xA9, 0xF7, 0xA2, 0x02, 0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD3, 0x01, + 0x3C, 0xFC, 0x2A, 0x07, 0xD8, 0xF1, 0x3F, 0x2A, 0xE6, 0x35, 0xBB, + 0xF1, 0x92, 0x06, 0xD2, 0xFC, 0x69, 0x01, 0x7E, 0xFF, 0x1F, 0x00, + 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6, + 0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3, + 0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x13, 0x00, 0xB1, 0xFF, 0x9F, 0x00, 0x24, 0xFF, 0xC9, 0x00, + 0x13, 0x00, 0x8D, 0xFC, 0xAE, 0x47, 0x3E, 0x0C, 0x56, 0xF9, 0x48, + 0x04, 0x56, 0xFD, 0x78, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E, + 0x04, 0xE6, 0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00, + 0x90, 0x00, 0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xF1, 0x06, + 0xF5, 0xF2, 0xA7, 0x22, 0xFF, 0x3B, 0xE4, 0xF2, 0x96, 0x05, 0x81, + 0xFD, 0xFD, 0x00, 0xB5, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0xFE, 0xFF, + 0x1C, 0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, + 0xF1, 0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, + 0xD8, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD3, + 0xFF, 0x49, 0x00, 0xD4, 0xFF, 0x88, 0xFF, 0x49, 0x02, 0x4B, 0xF8, + 0x0D, 0x45, 0x68, 0x13, 0xDF, 0xF6, 0x6C, 0x05, 0xCC, 0xFC, 0xB4, + 0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1, + 0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE, + 0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDA, 0x01, 0x69, 0xFC, 0x57, 0x06, 0xAF, 0xF4, 0xF5, 0x1A, + 0x1D, 0x41, 0x11, 0xF5, 0x25, 0x04, 0x6C, 0xFE, 0x74, 0x00, 0xF9, + 0xFF, 0xF1, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF, + 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44, + 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF, + 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF1, 0xFF, 0xF9, 0xFF, 0x74, + 0x00, 0x6C, 0xFE, 0x25, 0x04, 0x11, 0xF5, 0x1D, 0x41, 0xF5, 0x1A, + 0xAF, 0xF4, 0x57, 0x06, 0x69, 0xFC, 0xDA, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00, 0x8E, 0xFE, + 0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1, 0x06, 0x7B, + 0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF, 0x23, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF, 0xB4, 0x01, 0xCC, + 0xFC, 0x6C, 0x05, 0xDF, 0xF6, 0x68, 0x13, 0x0D, 0x45, 0x4B, 0xF8, + 0x49, 0x02, 0x88, 0xFF, 0xD4, 0xFF, 0x49, 0x00, 0xD3, 0xFF, 0x0B, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37, 0xFC, + 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1, 0x6F, + 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, + 0x01, 0x00, 0x0B, 0x00, 0xB5, 0xFF, 0xFD, 0x00, 0x81, 0xFD, 0x96, + 0x05, 0xE4, 0xF2, 0xFF, 0x3B, 0xA7, 0x22, 0xF5, 0xF2, 0xF1, 0x06, + 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, + 0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF, 0x90, 0x00, 0x7A, 0x00, + 0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6, 0xF8, 0x7E, 0x04, 0x3C, + 0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x00, 0x5C, 0xFF, 0x78, 0x01, 0x56, 0xFD, 0x48, 0x04, 0x56, + 0xF9, 0x3E, 0x0C, 0xAE, 0x47, 0x8D, 0xFC, 0x13, 0x00, 0xC9, 0x00, + 0x24, 0xFF, 0x9F, 0x00, 0xB1, 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC, 0xDD, 0x06, 0x37, 0xF3, + 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F, 0x05, 0xA6, 0xFD, 0xE8, + 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1F, 0x00, + 0x7E, 0xFF, 0x69, 0x01, 0xD2, 0xFC, 0x92, 0x06, 0xBB, 0xF1, 0xE6, + 0x35, 0x3F, 0x2A, 0xD8, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, + 0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD8, 0xFF, 0x3A, + 0x00, 0xF1, 0xFF, 0x54, 0xFF, 0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44, + 0xB1, 0x14, 0x77, 0xF6, 0x9A, 0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, + 0x2C, 0x01, 0xFB, 0xFD, 0xFE, 0x02, 0xEE, 0xFB, 0xAB, 0x05, 0xE1, + 0x48, 0xC7, 0x01, 0x9D, 0xFD, 0x1E, 0x02, 0x6F, 0xFE, 0xF5, 0x00, + 0x8F, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, + 0x01, 0x77, 0xFC, 0x33, 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, + 0x8D, 0xF5, 0xDA, 0x03, 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x56, 0xFF, 0xB4, 0x01, + 0x62, 0xFC, 0x15, 0x07, 0x84, 0xF1, 0x09, 0x2F, 0x7B, 0x31, 0x7D, + 0xF1, 0xF5, 0x06, 0x82, 0xFC, 0x9E, 0x01, 0x62, 0xFF, 0x28, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F, + 0xFE, 0x6D, 0x04, 0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4, + 0x78, 0x06, 0x5C, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x18, 0x00, 0x9B, 0xFF, 0xD8, 0x00, 0xAD, 0xFE, 0xA7, 0x01, + 0x7D, 0xFE, 0xDD, 0xFF, 0xA1, 0x48, 0xDD, 0x07, 0x07, 0xFB, 0x73, + 0x03, 0xC0, 0xFD, 0x48, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, + 0x05, 0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, + 0xBE, 0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDD, 0x01, 0x33, 0xFC, 0x23, 0x07, + 0x26, 0xF2, 0xA6, 0x27, 0x1D, 0x38, 0x05, 0xF2, 0x49, 0x06, 0x08, + 0xFD, 0x47, 0x01, 0x8F, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x0E, 0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E, + 0xF2, 0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC, + 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBD, + 0xFF, 0x80, 0x00, 0x62, 0xFF, 0x57, 0x00, 0xDF, 0x00, 0xF7, 0xFA, + 0xED, 0x46, 0xAA, 0x0E, 0x76, 0xF8, 0xB2, 0x04, 0x23, 0xFD, 0x8F, + 0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E, + 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF, + 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x41, 0xFC, 0xC6, 0x06, 0x7F, 0xF3, 0xFD, 0x1F, + 0xE4, 0x3D, 0x87, 0xF3, 0x24, 0x05, 0xCC, 0xFD, 0xD1, 0x00, 0xCB, + 0xFF, 0x02, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76, 0xFF, + 0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34, 0x89, + 0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2C, 0x00, 0x0D, + 0x00, 0x21, 0xFF, 0xF9, 0x02, 0x0F, 0xF7, 0xD4, 0x43, 0xFD, 0x15, + 0x13, 0xF6, 0xC5, 0x05, 0xA5, 0xFC, 0xC4, 0x01, 0x40, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0x1E, 0x01, + 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2, 0x48, 0xC6, + 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01, 0x8A, 0xFF, + 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD0, 0x01, 0x86, + 0xFC, 0x0D, 0x06, 0x66, 0xF5, 0x50, 0x18, 0x9E, 0x42, 0x11, 0xF6, + 0x8C, 0x03, 0xC9, 0xFE, 0x3F, 0x00, 0x14, 0x00, 0xE7, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE, 0x01, 0x56, 0xFC, + 0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32, 0x84, 0xF1, 0xE0, + 0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26, 0x00, 0xFD, 0xFF, + 0x03, 0x00, 0xFA, 0xFF, 0xE0, 0xFF, 0xA7, 0x00, 0x15, 0xFE, 0xB2, + 0x04, 0x32, 0xF4, 0x77, 0x3F, 0x9E, 0x1D, 0x07, 0xF4, 0x96, 0x06, + 0x51, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, + 0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE, + 0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3, + 0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2F, 0x00, 0x4C, 0xFF, 0xA1, 0x01, 0xF8, 0xFC, 0x0D, 0x05, 0xB3, + 0xF7, 0xDF, 0x10, 0x1D, 0x46, 0xA7, 0xF9, 0x8E, 0x01, 0xF4, 0xFF, + 0x98, 0xFF, 0x66, 0x00, 0xC7, 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31, 0xFC, 0x1A, 0x07, 0x56, 0xF2, + 0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2, 0x1E, 0x06, 0x25, 0xFD, 0x35, + 0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x12, 0x00, + 0xA0, 0xFF, 0x26, 0x01, 0x3E, 0xFD, 0xFB, 0x05, 0x60, 0xF2, 0xFD, + 0x39, 0x4E, 0x25, 0x7F, 0xF2, 0x11, 0x07, 0x31, 0xFC, 0xE3, 0x01, + 0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71, + 0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, + 0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x66, 0xFF, + 0x5F, 0x01, 0x8D, 0xFD, 0xD9, 0x03, 0x3B, 0xFA, 0xE4, 0x09, 0x41, + 0x48, 0x41, 0xFE, 0x3F, 0xFF, 0x3E, 0x01, 0xE5, 0xFE, 0xBD, 0x00, + 0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x4A, 0xFC, 0xAC, 0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E, + 0xE4, 0xF3, 0xE5, 0x04, 0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE, + 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6E, 0xFF, 0x87, 0x01, + 0xA4, 0xFC, 0xCD, 0x06, 0x8E, 0xF1, 0x99, 0x33, 0xCE, 0x2C, 0xA1, + 0xF1, 0x25, 0x07, 0x4D, 0xFC, 0xC4, 0x01, 0x4D, 0xFF, 0x2F, 0x00, + 0xFD, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF, + 0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5, + 0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x85, 0xFF, 0x10, 0x01, 0x38, 0xFE, 0x88, 0x02, + 0xD3, 0xFC, 0x91, 0x03, 0xF7, 0x48, 0xCB, 0x03, 0xBA, 0xFC, 0x95, + 0x02, 0x31, 0xFE, 0x13, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6, + 0x05, 0xC6, 0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03, + 0xFA, 0xFE, 0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4C, 0xFF, 0xC6, 0x01, 0x4B, 0xFC, 0x26, 0x07, + 0xA5, 0xF1, 0x87, 0x2C, 0xDC, 0x33, 0x91, 0xF1, 0xC7, 0x06, 0xA9, + 0xFC, 0x84, 0x01, 0x70, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x03, 0x00, + 0xFF, 0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, + 0xF3, 0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, + 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA7, + 0xFF, 0xB9, 0x00, 0xEC, 0xFE, 0x31, 0x01, 0x57, 0xFF, 0x0F, 0xFE, + 0x33, 0x48, 0x25, 0x0A, 0x21, 0xFA, 0xE5, 0x03, 0x87, 0xFD, 0x62, + 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, + 0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0, + 0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF, + 0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, + 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0E, 0x07, 0x8B, 0xF2, 0x03, 0x25, + 0x38, 0x3A, 0x6D, 0xF2, 0xF1, 0x05, 0x45, 0xFD, 0x22, 0x01, 0xA2, + 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF, + 0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0, + 0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF, + 0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xC9, 0xFF, 0x63, 0x00, 0x9F, + 0xFF, 0xE8, 0xFF, 0xA3, 0x01, 0x7F, 0xF9, 0x02, 0x46, 0x27, 0x11, + 0x9B, 0xF7, 0x18, 0x05, 0xF3, 0xFC, 0xA3, 0x01, 0x4C, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF, 0x52, 0x01, + 0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C, 0x48, 0x26, + 0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00, 0xA0, 0xFF, + 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE1, 0x01, 0x53, + 0xFC, 0x90, 0x06, 0x19, 0xF4, 0x52, 0x1D, 0xA8, 0x3F, 0x49, 0xF4, + 0xA3, 0x04, 0x1E, 0xFE, 0xA1, 0x00, 0xE3, 0xFF, 0xF9, 0xFF, 0x04, + 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90, 0xFC, + 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1, 0x1D, + 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x07, 0x00, 0xE8, 0xFF, 0x11, 0x00, 0x45, 0x00, 0xBF, 0xFE, 0x9D, + 0x03, 0xF3, 0xF5, 0x75, 0x42, 0x9B, 0x18, 0x51, 0xF5, 0x16, 0x06, + 0x83, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, + 0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE, 0x4D, 0x02, 0x45, 0xFD, + 0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47, 0xFC, 0xD1, 0x02, 0x12, + 0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x33, 0x00, 0x40, 0xFF, 0xC2, 0x01, 0xA9, 0xFC, 0xBC, 0x05, 0x29, + 0xF6, 0xB3, 0x15, 0xFA, 0x43, 0x31, 0xF7, 0xE6, 0x02, 0x2C, 0xFF, + 0x07, 0x00, 0x2F, 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, + 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC, 0x2A, 0x07, 0xBF, 0xF1, + 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB, 0x06, 0xBF, 0xFC, 0x75, + 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00, + 0xC8, 0xFF, 0xD6, 0x00, 0xC4, 0xFD, 0x31, 0x05, 0x73, 0xF3, 0xB0, + 0x3D, 0x49, 0x20, 0x6E, 0xF3, 0xCB, 0x06, 0x40, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA, + 0x00, 0x0C, 0xFF, 0xF7, 0x00, 0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47, + 0x51, 0x0B, 0xAF, 0xF9, 0x1D, 0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60, + 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF, + 0x8C, 0x01, 0x29, 0xFD, 0xA7, 0x04, 0x8F, 0xF8, 0x64, 0x0E, 0x02, + 0x47, 0x22, 0xFB, 0xC9, 0x00, 0x64, 0x00, 0x5B, 0xFF, 0x84, 0x00, + 0xBC, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0xFF, 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, + 0xAD, 0xF2, 0xBF, 0x05, 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8D, 0xFF, 0x4B, 0x01, + 0x01, 0xFD, 0x51, 0x06, 0xFB, 0xF1, 0xDF, 0x37, 0xF0, 0x27, 0x1C, + 0xF2, 0x24, 0x07, 0x34, 0xFC, 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0C, 0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2, + 0xFF, 0x01, 0x02, 0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7, + 0x48, 0x05, 0xDD, 0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x45, 0x01, 0xC6, 0xFD, + 0x66, 0x03, 0x21, 0xFB, 0x9E, 0x07, 0xAA, 0x48, 0x12, 0x00, 0x64, + 0xFE, 0xB4, 0x01, 0xA6, 0xFE, 0xDB, 0x00, 0x9A, 0xFF, 0x19, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, + 0x06, 0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, + 0x49, 0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFD, + 0xFF, 0x29, 0x00, 0x61, 0xFF, 0xA1, 0x01, 0x7E, 0xFC, 0xF9, 0x06, + 0x7C, 0xF1, 0x38, 0x31, 0x50, 0x2F, 0x82, 0xF1, 0x12, 0x07, 0x65, + 0xFC, 0xB2, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x06, 0x00, + 0xED, 0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71, + 0xF5, 0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC, + 0xD7, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x91, + 0xFF, 0xF2, 0x00, 0x76, 0xFE, 0x11, 0x02, 0xB6, 0xFD, 0x8F, 0x01, + 0xDE, 0x48, 0xE9, 0x05, 0xD4, 0xFB, 0x0C, 0x03, 0xF4, 0xFD, 0x2F, + 0x01, 0x79, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, + 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68, + 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF, + 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, + 0xFF, 0xD4, 0x01, 0x3B, 0xFC, 0x2A, 0x07, 0xDF, 0xF1, 0xF6, 0x29, + 0x27, 0x36, 0xC1, 0xF1, 0x8B, 0x06, 0xD8, 0xFC, 0x66, 0x01, 0x80, + 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xBD, 0xFF, + 0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C, 0x9E, + 0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x9B, 0x00, 0x2B, + 0xFF, 0xBD, 0x00, 0x2A, 0x00, 0x5E, 0xFC, 0x9A, 0x47, 0x82, 0x0C, + 0x3D, 0xF9, 0x54, 0x04, 0x50, 0xFD, 0x7A, 0x01, 0x5B, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, 0x81, 0x01, + 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69, 0x47, 0xEB, + 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00, 0xB6, 0xFF, + 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x37, + 0xFC, 0xED, 0x06, 0x03, 0xF3, 0x5B, 0x22, 0x37, 0x3C, 0xF4, 0xF2, + 0x8A, 0x05, 0x89, 0xFD, 0xF9, 0x00, 0xB7, 0xFF, 0x0A, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C, 0x01, 0xE6, 0xFC, + 0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29, 0xF3, 0xF1, 0x29, + 0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD4, 0xFF, 0x46, 0x00, 0xDA, 0xFF, 0x7D, 0xFF, 0x5D, + 0x02, 0x26, 0xF8, 0xED, 0x44, 0xB1, 0x13, 0xC7, 0xF6, 0x77, 0x05, + 0xC8, 0xFC, 0xB6, 0x01, 0x45, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03, + 0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0, + 0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x35, 0x00, 0x38, 0xFF, 0xD9, 0x01, 0x6C, 0xFC, 0x4F, 0x06, 0xC3, + 0xF4, 0xA9, 0x1A, 0x49, 0x41, 0x2C, 0xF5, 0x15, 0x04, 0x76, 0xFE, + 0x6E, 0x00, 0xFC, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2B, + 0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E, 0xFC, 0x0A, 0x07, 0x7E, 0xF1, + 0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1, 0x03, 0x07, 0x75, 0xFC, 0xA7, + 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF2, 0xFF, + 0xF7, 0xFF, 0x7A, 0x00, 0x62, 0xFE, 0x35, 0x04, 0xF7, 0xF4, 0xEF, + 0x40, 0x40, 0x1B, 0x9C, 0xF4, 0x5E, 0x06, 0x66, 0xFC, 0xDB, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3, + 0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, + 0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, + 0xB2, 0x01, 0xD1, 0xFC, 0x62, 0x05, 0xF6, 0xF6, 0x20, 0x13, 0x2E, + 0x45, 0x70, 0xF8, 0x34, 0x02, 0x94, 0xFF, 0xCD, 0xFF, 0x4C, 0x00, + 0xD2, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA, + 0x01, 0x36, 0xFC, 0x27, 0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37, + 0xE4, 0xF1, 0x67, 0x06, 0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0B, 0x00, 0xB2, 0xFF, 0x02, 0x01, + 0x7A, 0xFD, 0xA2, 0x05, 0xD4, 0xF2, 0xC7, 0x3B, 0xF2, 0x22, 0xE7, + 0xF2, 0xF5, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83, + 0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8, + 0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x75, 0x01, 0x5C, 0xFD, + 0x3C, 0x04, 0x70, 0xF9, 0xFA, 0x0B, 0xC0, 0x47, 0xBC, 0xFC, 0xFC, + 0xFF, 0xD6, 0x00, 0x1D, 0xFF, 0xA2, 0x00, 0xB0, 0xFF, 0x13, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8, + 0x06, 0x47, 0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05, + 0xAE, 0xFD, 0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0xFE, + 0xFF, 0x1F, 0x00, 0x7C, 0xFF, 0x6D, 0x01, 0xCD, 0xFC, 0x99, 0x06, + 0xB4, 0xF1, 0xA6, 0x35, 0x89, 0x2A, 0xD0, 0xF1, 0x2B, 0x07, 0x3E, + 0xFC, 0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, + 0xD9, 0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, + 0xF7, 0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, + 0xBE, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, + 0x00, 0x7B, 0xFF, 0x29, 0x01, 0x01, 0xFE, 0xF1, 0x02, 0x07, 0xFC, + 0x6E, 0x05, 0xE6, 0x48, 0xFF, 0x01, 0x84, 0xFD, 0x2C, 0x02, 0x68, + 0xFE, 0xF9, 0x00, 0x8E, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56, + 0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00, + 0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x55, + 0xFF, 0xB6, 0x01, 0x5F, 0xFC, 0x17, 0x07, 0x87, 0xF1, 0xC2, 0x2E, + 0xC0, 0x31, 0x7E, 0xF1, 0xF1, 0x06, 0x86, 0xFC, 0x9B, 0x01, 0x63, + 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF, + 0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94, + 0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9C, 0xFF, 0xD4, 0x00, 0xB4, + 0xFE, 0x9A, 0x01, 0x95, 0xFE, 0xA8, 0xFF, 0x98, 0x48, 0x1D, 0x08, + 0xEE, 0xFA, 0x80, 0x03, 0xB9, 0xFD, 0x4B, 0x01, 0x6E, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xA9, 0x01, + 0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8, 0x45, 0x1C, + 0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00, 0xCC, 0xFF, + 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDE, 0x01, 0x33, + 0xFC, 0x21, 0x07, 0x30, 0xF2, 0x5C, 0x27, 0x5B, 0x38, 0x0F, 0xF2, + 0x40, 0x06, 0x0E, 0xFD, 0x43, 0x01, 0x91, 0xFF, 0x18, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57, 0xFD, + 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2, 0x06, + 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x10, 0x00, 0xBE, 0xFF, 0x7D, 0x00, 0x69, 0xFF, 0x4B, 0x00, 0xF6, + 0x00, 0xCB, 0xFA, 0xD3, 0x46, 0xF0, 0x0E, 0x5E, 0xF8, 0xBE, 0x04, + 0x1E, 0xFD, 0x91, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01, 0x77, 0xFD, 0x04, 0x04, + 0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94, 0xFD, 0x92, 0xFF, 0x10, + 0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF, 0x15, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x43, 0xFC, 0xC0, 0x06, 0x8F, + 0xF3, 0xB1, 0x1F, 0x18, 0x3E, 0x9B, 0xF3, 0x16, 0x05, 0xD5, 0xFD, + 0xCC, 0x00, 0xCE, 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x22, + 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC, 0xB8, 0x06, 0x9C, 0xF1, + 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29, 0x07, 0x46, 0xFC, 0xCA, + 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDF, 0xFF, + 0x29, 0x00, 0x14, 0x00, 0x16, 0xFF, 0x0C, 0x03, 0xEE, 0xF6, 0xB0, + 0x43, 0x47, 0x16, 0xFC, 0xF5, 0xCF, 0x05, 0xA1, 0xFC, 0xC6, 0x01, + 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, + 0xFF, 0x1B, 0x01, 0x20, 0xFE, 0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04, + 0xF4, 0x48, 0xFF, 0x02, 0x13, 0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07, + 0x01, 0x88, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, + 0xCF, 0x01, 0x8A, 0xFC, 0x05, 0x06, 0x7B, 0xF5, 0x06, 0x18, 0xC7, + 0x42, 0x2F, 0xF6, 0x7A, 0x03, 0xD4, 0xFE, 0x39, 0x00, 0x17, 0x00, + 0xE6, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, + 0x01, 0x53, 0xFC, 0x21, 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, + 0x86, 0xF1, 0xDB, 0x06, 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDE, 0xFF, 0xAC, 0x00, + 0x0B, 0xFE, 0xC1, 0x04, 0x1B, 0xF4, 0x47, 0x3F, 0xEA, 0x1D, 0xF5, + 0xF3, 0x9C, 0x06, 0x4F, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x16, 0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F, + 0x01, 0x03, 0xFF, 0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA, + 0xB9, 0x03, 0x9D, 0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4D, 0xFF, 0x9F, 0x01, 0xFE, 0xFC, + 0x02, 0x05, 0xCB, 0xF7, 0x98, 0x10, 0x39, 0x46, 0xD0, 0xF9, 0x78, + 0x01, 0x00, 0x00, 0x91, 0xFF, 0x69, 0x00, 0xC6, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, + 0x07, 0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, + 0x2C, 0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0x13, 0x00, 0x9E, 0xFF, 0x2B, 0x01, 0x37, 0xFD, 0x05, 0x06, + 0x54, 0xF2, 0xC2, 0x39, 0x99, 0x25, 0x73, 0xF2, 0x14, 0x07, 0x31, + 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, + 0xC4, 0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D, + 0xFA, 0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD, + 0x9C, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, + 0x00, 0x67, 0xFF, 0x5C, 0x01, 0x93, 0xFD, 0xCC, 0x03, 0x54, 0xFA, + 0xA2, 0x09, 0x4F, 0x48, 0x73, 0xFE, 0x27, 0xFF, 0x4B, 0x01, 0xDE, + 0xFE, 0xC0, 0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B, + 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00, + 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6D, + 0xFF, 0x8A, 0x01, 0x9F, 0xFC, 0xD3, 0x06, 0x8A, 0xF1, 0x57, 0x33, + 0x17, 0x2D, 0x9C, 0xF1, 0x24, 0x07, 0x4F, 0xFC, 0xC3, 0x01, 0x4E, + 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE4, 0xFF, 0x1B, 0x00, + 0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43, 0x96, + 0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x86, 0xFF, 0x0C, 0x01, 0x3E, + 0xFE, 0x7B, 0x02, 0xED, 0xFC, 0x56, 0x03, 0xF5, 0x48, 0x06, 0x04, + 0xA1, 0xFC, 0xA3, 0x02, 0x2A, 0xFE, 0x16, 0x01, 0x83, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, 0xC8, 0x01, + 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77, 0x43, 0xBD, + 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00, 0xE1, 0xFF, + 0x08, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC8, 0x01, 0x49, + 0xFC, 0x27, 0x07, 0xAB, 0xF1, 0x3E, 0x2C, 0x1E, 0x34, 0x95, 0xF1, + 0xC1, 0x06, 0xAE, 0xFC, 0x81, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE, + 0xFF, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4, 0x00, 0xE2, 0xFD, + 0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F, 0xA8, 0xF3, 0xB8, + 0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x15, 0x00, 0xA8, 0xFF, 0xB6, 0x00, 0xF3, 0xFE, 0x24, 0x01, 0x6E, + 0xFF, 0xDE, 0xFD, 0x25, 0x48, 0x68, 0x0A, 0x08, 0xFA, 0xF2, 0x03, + 0x81, 0xFD, 0x65, 0x01, 0x64, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04, + 0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38, + 0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0B, 0x07, 0x97, + 0xF2, 0xB8, 0x24, 0x71, 0x3A, 0x7B, 0xF2, 0xE6, 0x05, 0x4C, 0xFD, + 0x1D, 0x01, 0xA4, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x16, + 0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18, 0xFD, 0x32, 0x06, 0x1F, 0xF2, + 0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2, 0x1E, 0x07, 0x32, 0xFC, 0xDF, + 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0D, 0x00, 0xCA, 0xFF, + 0x5F, 0x00, 0xA5, 0xFF, 0xDC, 0xFF, 0xB8, 0x01, 0x57, 0xF9, 0xE5, + 0x45, 0x6E, 0x11, 0x83, 0xF7, 0x23, 0x05, 0xEE, 0xFC, 0xA6, 0x01, + 0x4B, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C, + 0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, + 0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, + 0x00, 0x9E, 0xFF, 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE0, 0x01, 0x56, 0xFC, 0x89, 0x06, 0x2B, 0xF4, 0x06, 0x1D, 0xD7, + 0x3F, 0x61, 0xF4, 0x94, 0x04, 0x27, 0xFE, 0x9C, 0x00, 0xE6, 0xFF, + 0xF8, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97, + 0x01, 0x8C, 0xFC, 0xEA, 0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E, + 0x8B, 0xF1, 0x1B, 0x07, 0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D, + 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE9, 0xFF, 0x0E, 0x00, 0x4B, 0x00, + 0xB4, 0xFE, 0xAF, 0x03, 0xD5, 0xF5, 0x4D, 0x42, 0xE6, 0x18, 0x3C, + 0xF5, 0x1F, 0x06, 0x7F, 0xFC, 0xD3, 0x01, 0x3B, 0xFF, 0x35, 0x00, + 0xFE, 0xFF, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F, + 0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC, + 0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xC1, 0x01, 0xAD, 0xFC, + 0xB2, 0x05, 0x3F, 0xF6, 0x69, 0x15, 0x1F, 0x44, 0x53, 0xF7, 0xD3, + 0x02, 0x38, 0xFF, 0x01, 0x00, 0x33, 0x00, 0xDB, 0xFF, 0x09, 0x00, + 0xFD, 0xFF, 0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A, + 0x07, 0xC6, 0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06, + 0xC4, 0xFC, 0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x02, + 0x00, 0x04, 0x00, 0xC6, 0xFF, 0xDB, 0x00, 0xBB, 0xFD, 0x3E, 0x05, + 0x60, 0xF3, 0x7B, 0x3D, 0x94, 0x20, 0x5E, 0xF3, 0xD0, 0x06, 0x3E, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, + 0xAE, 0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, + 0xFD, 0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, + 0x71, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2C, + 0x00, 0x55, 0xFF, 0x8A, 0x01, 0x2E, 0xFD, 0x9B, 0x04, 0xA8, 0xF8, + 0x1F, 0x0E, 0x1A, 0x47, 0x4E, 0xFB, 0xB3, 0x00, 0x70, 0x00, 0x54, + 0xFF, 0x87, 0x00, 0xBB, 0xFF, 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64, + 0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01, + 0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8B, + 0xFF, 0x4F, 0x01, 0xFB, 0xFC, 0x5A, 0x06, 0xF2, 0xF1, 0xA0, 0x37, + 0x3A, 0x28, 0x13, 0xF2, 0x25, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, + 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00, + 0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2, + 0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF, + 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x42, + 0x01, 0xCD, 0xFD, 0x59, 0x03, 0x3B, 0xFB, 0x5E, 0x07, 0xB3, 0x48, + 0x48, 0x00, 0x4B, 0xFE, 0xC2, 0x01, 0x9F, 0xFE, 0xDE, 0x00, 0x98, + 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDD, 0x01, + 0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB, 0x40, 0xD0, + 0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF, 0xF4, 0xFF, + 0x05, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA3, 0x01, 0x7A, + 0xFC, 0xFD, 0x06, 0x7C, 0xF1, 0xF2, 0x30, 0x96, 0x2F, 0x80, 0xF1, + 0x0F, 0x07, 0x69, 0xFC, 0xB0, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0xFD, + 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85, 0xFE, + 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4, 0x43, + 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0x1B, 0x00, 0x92, 0xFF, 0xEF, 0x00, 0x7D, 0xFE, 0x04, 0x02, 0xCF, + 0xFD, 0x58, 0x01, 0xD7, 0x48, 0x26, 0x06, 0xBB, 0xFB, 0x19, 0x03, + 0xED, 0xFD, 0x32, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01, 0xC1, 0xFC, 0x86, 0x05, + 0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0, 0xF7, 0x7B, 0x02, 0x6B, + 0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, + 0x33, 0x00, 0x43, 0xFF, 0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE7, + 0xF1, 0xAC, 0x29, 0x66, 0x36, 0xC9, 0xF1, 0x83, 0x06, 0xDD, 0xFC, + 0x62, 0x01, 0x81, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x08, + 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD, 0x78, 0x05, 0x0E, 0xF3, + 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6, 0x06, 0x38, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, 0x00, 0xB4, 0xFF, + 0x98, 0x00, 0x32, 0xFF, 0xB0, 0x00, 0x41, 0x00, 0x30, 0xFC, 0x86, + 0x47, 0xC6, 0x0C, 0x24, 0xF9, 0x60, 0x04, 0x4B, 0xFD, 0x7D, 0x01, + 0x5A, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x5A, + 0xFF, 0x7E, 0x01, 0x48, 0xFD, 0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C, + 0x7C, 0x47, 0x19, 0xFC, 0x4D, 0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96, + 0x00, 0xB5, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x38, 0xFC, 0xE9, 0x06, 0x12, 0xF3, 0x10, 0x22, 0x6E, + 0x3C, 0x05, 0xF3, 0x7E, 0x05, 0x91, 0xFD, 0xF4, 0x00, 0xBA, 0xFF, + 0x09, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, + 0x01, 0xE0, 0xFC, 0x7F, 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, + 0xEB, 0xF1, 0x2A, 0x07, 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0x0B, 0x00, 0xD5, 0xFF, 0x42, 0x00, 0xE1, 0xFF, + 0x71, 0xFF, 0x71, 0x02, 0x02, 0xF8, 0xCC, 0x44, 0xFA, 0x13, 0xB0, + 0xF6, 0x81, 0x05, 0xC3, 0xFC, 0xB8, 0x01, 0x44, 0xFF, 0x31, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA, + 0xFD, 0x1F, 0x03, 0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01, + 0xDC, 0xFD, 0xFD, 0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B, + 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x6F, 0xFC, + 0x47, 0x06, 0xD7, 0xF4, 0x5D, 0x1A, 0x74, 0x41, 0x48, 0xF5, 0x04, + 0x04, 0x80, 0xFE, 0x69, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0x05, 0x00, + 0xFD, 0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, + 0x07, 0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, + 0x78, 0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x05, + 0x00, 0xF3, 0xFF, 0xF4, 0xFF, 0x80, 0x00, 0x58, 0xFE, 0x46, 0x04, + 0xDD, 0xF4, 0xC3, 0x40, 0x8C, 0x1B, 0x89, 0xF4, 0x66, 0x06, 0x63, + 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, + 0x98, 0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62, + 0x00, 0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD, + 0x40, 0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, + 0x00, 0x47, 0xFF, 0xB0, 0x01, 0xD6, 0xFC, 0x58, 0x05, 0x0D, 0xF7, + 0xD7, 0x12, 0x4E, 0x45, 0x96, 0xF8, 0x20, 0x02, 0xA0, 0xFF, 0xC7, + 0xFF, 0x4F, 0x00, 0xD0, 0xFF, 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60, + 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01, + 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00, 0xB0, + 0xFF, 0x07, 0x01, 0x72, 0xFD, 0xAE, 0x05, 0xC4, 0xF2, 0x90, 0x3B, + 0x3F, 0x23, 0xD9, 0xF2, 0xF9, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89, 0x00, + 0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47, 0xFC, + 0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56, 0xFF, + 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5E, 0xFF, 0x72, + 0x01, 0x62, 0xFD, 0x2F, 0x04, 0x89, 0xF9, 0xB6, 0x0B, 0xD2, 0x47, + 0xEB, 0xFC, 0xE4, 0xFF, 0xE3, 0x00, 0x16, 0xFF, 0xA5, 0x00, 0xAF, + 0xFF, 0x13, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61, 0x3D, 0x56, + 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF, 0x05, 0x00, + 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x7A, 0xFF, 0x70, 0x01, 0xC7, + 0xFC, 0xA0, 0x06, 0xAE, 0xF1, 0x65, 0x35, 0xD1, 0x2A, 0xCA, 0xF1, + 0x2A, 0x07, 0x40, 0xFC, 0xD0, 0x01, 0x47, 0xFF, 0x32, 0x00, 0xFD, + 0xFF, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x3D, 0xFF, + 0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15, 0x4A, 0xF6, 0xAD, + 0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x21, 0x00, 0x7C, 0xFF, 0x26, 0x01, 0x08, 0xFE, 0xE4, + 0x02, 0x21, 0xFC, 0x31, 0x05, 0xEB, 0x48, 0x37, 0x02, 0x6B, 0xFD, + 0x39, 0x02, 0x61, 0xFE, 0xFC, 0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFE, + 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06, + 0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF, + 0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFD, 0xFF, + 0x2D, 0x00, 0x54, 0xFF, 0xB8, 0x01, 0x5D, 0xFC, 0x1A, 0x07, 0x8A, + 0xF1, 0x7B, 0x2E, 0x04, 0x32, 0x7F, 0xF1, 0xEC, 0x06, 0x8A, 0xFC, + 0x98, 0x01, 0x65, 0xFF, 0x27, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF8, + 0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C, 0xFE, 0x8C, 0x04, 0x6D, 0xF4, + 0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4, 0x85, 0x06, 0x57, 0xFC, 0xE0, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x18, 0x00, 0x9E, 0xFF, + 0xD1, 0x00, 0xBB, 0xFE, 0x8D, 0x01, 0xAE, 0xFE, 0x74, 0xFF, 0x8D, + 0x48, 0x5D, 0x08, 0xD4, 0xFA, 0x8D, 0x03, 0xB3, 0xFD, 0x4E, 0x01, + 0x6D, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, + 0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, + 0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, + 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3D, 0xFF, + 0xDF, 0x01, 0x32, 0xFC, 0x1F, 0x07, 0x3B, 0xF2, 0x11, 0x27, 0x97, + 0x38, 0x19, 0xF2, 0x36, 0x06, 0x15, 0xFD, 0x3F, 0x01, 0x93, 0xFF, + 0x17, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B, + 0x01, 0x50, 0xFD, 0xE1, 0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24, + 0x9D, 0xF2, 0x09, 0x07, 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0x7A, 0x00, 0x70, 0xFF, + 0x3E, 0x00, 0x0C, 0x01, 0xA1, 0xFA, 0xBB, 0x46, 0x36, 0x0F, 0x45, + 0xF8, 0xC9, 0x04, 0x18, 0xFD, 0x93, 0x01, 0x52, 0xFF, 0x2D, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D, + 0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD, + 0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x45, 0xFC, + 0xBB, 0x06, 0xA0, 0xF3, 0x64, 0x1F, 0x4A, 0x3E, 0xB0, 0xF3, 0x08, + 0x05, 0xDE, 0xFD, 0xC7, 0x00, 0xD0, 0xFF, 0x00, 0x00, 0x02, 0x00, + 0xFE, 0xFF, 0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE, + 0x06, 0x97, 0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07, + 0x48, 0xFC, 0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, + 0x00, 0xE0, 0xFF, 0x26, 0x00, 0x1A, 0x00, 0x0B, 0xFF, 0x1E, 0x03, + 0xCD, 0xF6, 0x89, 0x43, 0x91, 0x16, 0xE7, 0xF5, 0xD8, 0x05, 0x9D, + 0xFC, 0xC7, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x1F, 0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, + 0xFC, 0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, + 0x42, 0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x34, + 0x00, 0x3C, 0xFF, 0xCD, 0x01, 0x8E, 0xFC, 0xFC, 0x05, 0x90, 0xF5, + 0xBB, 0x17, 0xEE, 0x42, 0x4E, 0xF6, 0x68, 0x03, 0xDF, 0xFE, 0x33, + 0x00, 0x1A, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A, + 0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01, + 0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFC, 0xFF, 0xDB, + 0xFF, 0xB2, 0x00, 0x02, 0xFE, 0xCF, 0x04, 0x05, 0xF4, 0x16, 0x3F, + 0x36, 0x1E, 0xE4, 0xF3, 0xA3, 0x06, 0x4D, 0xFC, 0xE3, 0x01, 0x36, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00, + 0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81, + 0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF, + 0x26, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9D, + 0x01, 0x03, 0xFD, 0xF7, 0x04, 0xE3, 0xF7, 0x51, 0x10, 0x55, 0x46, + 0xF9, 0xF9, 0x63, 0x01, 0x0D, 0x00, 0x8B, 0xFF, 0x6D, 0x00, 0xC5, + 0xFF, 0x0E, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, + 0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5, 0x39, 0x4D, + 0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF, 0x13, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x14, 0x00, 0x9C, 0xFF, 0x2F, 0x01, 0x30, + 0xFD, 0x10, 0x06, 0x47, 0xF2, 0x87, 0x39, 0xE5, 0x25, 0x67, 0xF2, + 0x16, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06, 0x00, + 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7, 0xFC, + 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x59, 0x01, 0x99, 0xFD, 0xC0, + 0x03, 0x6E, 0xFA, 0x61, 0x09, 0x5D, 0x48, 0xA6, 0xFE, 0x0F, 0xFF, + 0x58, 0x01, 0xD7, 0xFE, 0xC3, 0x00, 0xA3, 0xFF, 0x16, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4E, 0xFC, 0xA0, 0x06, + 0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10, 0xF4, 0xC8, 0x04, 0x07, + 0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF, 0x03, 0x00, 0xFD, 0xFF, + 0x25, 0x00, 0x6B, 0xFF, 0x8D, 0x01, 0x9B, 0xFC, 0xD8, 0x06, 0x87, + 0xF1, 0x13, 0x33, 0x5E, 0x2D, 0x98, 0xF1, 0x22, 0x07, 0x52, 0xFC, + 0xC1, 0x01, 0x4F, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x07, 0x00, 0xE5, + 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE, 0x71, 0x03, 0x3F, 0xF6, + 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00, 0x06, 0x8C, 0xFC, 0xCE, + 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, 0x00, 0x88, 0xFF, + 0x09, 0x01, 0x45, 0xFE, 0x6E, 0x02, 0x06, 0xFD, 0x1C, 0x03, 0xF4, + 0x48, 0x41, 0x04, 0x87, 0xFC, 0xB0, 0x02, 0x23, 0xFE, 0x19, 0x01, + 0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, + 0xFF, 0xC6, 0x01, 0x9F, 0xFC, 0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16, + 0x9E, 0x43, 0xDD, 0xF6, 0x15, 0x03, 0x10, 0xFF, 0x17, 0x00, 0x28, + 0x00, 0xDF, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xCA, 0x01, 0x47, 0xFC, 0x28, 0x07, 0xB0, 0xF1, 0xF5, 0x2B, 0x60, + 0x34, 0x9A, 0xF1, 0xBB, 0x06, 0xB3, 0xFC, 0x7D, 0x01, 0x73, 0xFF, + 0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, + 0x00, 0xDA, 0xFD, 0x0F, 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, + 0x97, 0xF3, 0xBD, 0x06, 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x15, 0x00, 0xAA, 0xFF, 0xB3, 0x00, 0xFA, 0xFE, + 0x17, 0x01, 0x86, 0xFF, 0xAC, 0xFD, 0x16, 0x48, 0xAA, 0x0A, 0xEE, + 0xF9, 0xFE, 0x03, 0x7A, 0xFD, 0x67, 0x01, 0x63, 0xFF, 0x28, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B, + 0xFD, 0xC4, 0x04, 0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA, + 0x01, 0x01, 0x44, 0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, + 0x08, 0x07, 0xA4, 0xF2, 0x6D, 0x24, 0xAD, 0x3A, 0x88, 0xF2, 0xDB, + 0x05, 0x53, 0xFD, 0x19, 0x01, 0xA7, 0xFF, 0x10, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, + 0x06, 0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, + 0x33, 0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0D, + 0x00, 0xCB, 0xFF, 0x5C, 0x00, 0xAC, 0xFF, 0xD0, 0xFF, 0xCD, 0x01, + 0x30, 0xF9, 0xC8, 0x45, 0xB6, 0x11, 0x6B, 0xF7, 0x2D, 0x05, 0xE9, + 0xFC, 0xA8, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x25, 0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1, + 0xFA, 0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01, + 0xB8, 0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFE, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x58, 0xFC, 0x82, 0x06, 0x3E, 0xF4, + 0xBA, 0x1C, 0x07, 0x40, 0x79, 0xF4, 0x84, 0x04, 0x31, 0xFE, 0x96, + 0x00, 0xE8, 0xFF, 0xF7, 0xFF, 0x04, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3, + 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01, + 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00, 0xEA, 0xFF, 0x0B, + 0x00, 0x51, 0x00, 0xAA, 0xFE, 0xC0, 0x03, 0xB8, 0xF5, 0x21, 0x42, + 0x31, 0x19, 0x28, 0xF5, 0x27, 0x06, 0x7C, 0xFC, 0xD4, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA, 0x00, + 0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48, 0x50, + 0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C, 0xFF, + 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x41, 0xFF, 0xBF, + 0x01, 0xB2, 0xFC, 0xA9, 0x05, 0x55, 0xF6, 0x20, 0x15, 0x42, 0x44, + 0x75, 0xF7, 0xBF, 0x02, 0x43, 0xFF, 0xFA, 0xFF, 0x36, 0x00, 0xDA, + 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, 0xD1, 0x01, + 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86, 0x35, 0xB1, + 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF, 0x20, 0x00, + 0xFE, 0xFF, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE0, 0x00, 0xB3, + 0xFD, 0x4B, 0x05, 0x4D, 0xF3, 0x45, 0x3D, 0xE0, 0x20, 0x4F, 0xF3, + 0xD5, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19, 0xFF, 0xDD, 0x00, + 0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B, 0x7C, 0xF9, 0x35, + 0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2C, 0x00, 0x56, 0xFF, 0x87, 0x01, 0x34, 0xFD, 0x8F, + 0x04, 0xC0, 0xF8, 0xD9, 0x0D, 0x31, 0x47, 0x7B, 0xFB, 0x9C, 0x00, + 0x7D, 0x00, 0x4D, 0xFF, 0x8A, 0x00, 0xB9, 0xFF, 0x11, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06, + 0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76, + 0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF, + 0x1A, 0x00, 0x89, 0xFF, 0x53, 0x01, 0xF5, 0xFC, 0x63, 0x06, 0xE9, + 0xF1, 0x63, 0x37, 0x85, 0x28, 0x09, 0xF2, 0x27, 0x07, 0x35, 0xFC, + 0xDA, 0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x0C, 0x00, 0xD1, + 0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A, 0xFF, 0x2A, 0x02, 0x83, 0xF8, + 0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7, 0x5D, 0x05, 0xD3, 0xFC, 0xB1, + 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x73, 0xFF, 0x3F, 0x01, 0xD3, 0xFD, 0x4C, 0x03, 0x54, 0xFB, 0x1F, + 0x07, 0xBB, 0x48, 0x7D, 0x00, 0x33, 0xFE, 0xCF, 0x01, 0x98, 0xFE, + 0xE2, 0x00, 0x97, 0xFF, 0x19, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, + 0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, + 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5E, 0xFF, + 0xA6, 0x01, 0x76, 0xFC, 0x01, 0x07, 0x7D, 0xF1, 0xAD, 0x30, 0xDC, + 0x2F, 0x7F, 0xF1, 0x0C, 0x07, 0x6C, 0xFC, 0xAD, 0x01, 0x5A, 0xFF, + 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C, + 0x00, 0x7B, 0xFE, 0x0C, 0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A, + 0xCD, 0xF4, 0x4B, 0x06, 0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35, + 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x93, 0xFF, 0xEC, 0x00, 0x83, 0xFE, + 0xF7, 0x01, 0xE8, 0xFD, 0x21, 0x01, 0xD2, 0x48, 0x64, 0x06, 0xA1, + 0xFB, 0x26, 0x03, 0xE7, 0xFD, 0x35, 0x01, 0x76, 0xFF, 0x22, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5, + 0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8, + 0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x39, 0xFC, + 0x29, 0x07, 0xEF, 0xF1, 0x62, 0x29, 0xA5, 0x36, 0xD0, 0xF1, 0x7B, + 0x06, 0xE3, 0xFC, 0x5E, 0x01, 0x83, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, + 0x01, 0x00, 0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84, + 0x05, 0xFD, 0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06, + 0x37, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x12, + 0x00, 0xB5, 0xFF, 0x94, 0x00, 0x39, 0xFF, 0xA3, 0x00, 0x58, 0x00, + 0x02, 0xFC, 0x73, 0x47, 0x0B, 0x0D, 0x0B, 0xF9, 0x6C, 0x04, 0x45, + 0xFD, 0x80, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, + 0xF9, 0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, + 0x2E, 0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x39, 0xFC, 0xE4, 0x06, 0x21, 0xF3, + 0xC4, 0x21, 0xA5, 0x3C, 0x16, 0xF3, 0x72, 0x05, 0x9A, 0xFD, 0xEF, + 0x00, 0xBC, 0xFF, 0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46, + 0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, 0xD6, 0xFF, 0x3F, + 0x00, 0xE7, 0xFF, 0x65, 0xFF, 0x85, 0x02, 0xDE, 0xF7, 0xA9, 0x44, + 0x43, 0x14, 0x99, 0xF6, 0x8B, 0x05, 0xBF, 0xFC, 0xBA, 0x01, 0x43, + 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF, + 0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB, + 0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00, + 0x91, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, + 0x01, 0x72, 0xFC, 0x3F, 0x06, 0xEB, 0xF4, 0x12, 0x1A, 0xA1, 0x41, + 0x63, 0xF5, 0xF3, 0x03, 0x8A, 0xFE, 0x63, 0x00, 0x02, 0x00, 0xEE, + 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF, 0xB1, 0x01, + 0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15, 0x31, 0x7C, + 0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF, 0x29, 0x00, + 0xFD, 0xFF, 0x04, 0x00, 0xF4, 0xFF, 0xF1, 0xFF, 0x85, 0x00, 0x4E, + 0xFE, 0x56, 0x04, 0xC3, 0xF4, 0x95, 0x40, 0xD8, 0x1B, 0x76, 0xF4, + 0x6D, 0x06, 0x60, 0xFC, 0xDD, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB, 0x01, + 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB, 0x60, + 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAE, 0x01, 0xDB, 0xFC, 0x4D, + 0x05, 0x24, 0xF7, 0x8E, 0x12, 0x6D, 0x45, 0xBC, 0xF8, 0x0C, 0x02, + 0xAC, 0xFF, 0xC0, 0xFF, 0x52, 0x00, 0xCF, 0xFF, 0x0C, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01, 0x34, 0xFC, 0x25, 0x07, + 0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7, 0xF1, 0x56, 0x06, 0xFE, + 0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x0D, 0x00, 0xAE, 0xFF, 0x0B, 0x01, 0x6A, 0xFD, 0xBA, 0x05, 0xB4, + 0xF2, 0x58, 0x3B, 0x8A, 0x23, 0xCB, 0xF2, 0xFD, 0x06, 0x34, 0xFC, + 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x10, 0x00, 0xBB, + 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00, 0xBE, 0x00, 0x38, 0xFB, + 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1, 0x04, 0x2B, 0xFD, 0x8B, + 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x29, 0x00, + 0x5F, 0xFF, 0x70, 0x01, 0x68, 0xFD, 0x23, 0x04, 0xA2, 0xF9, 0x73, + 0x0B, 0xE4, 0x47, 0x1B, 0xFD, 0xCD, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, + 0xA9, 0x00, 0xAE, 0xFF, 0x14, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE6, 0x01, 0x3F, 0xFC, 0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20, + 0x96, 0x3D, 0x69, 0xF3, 0x38, 0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7, + 0xFF, 0x04, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x78, 0xFF, + 0x74, 0x01, 0xC2, 0xFC, 0xA7, 0x06, 0xA8, 0xF1, 0x25, 0x35, 0x1B, + 0x2B, 0xC2, 0xF1, 0x2A, 0x07, 0x41, 0xFC, 0xCE, 0x01, 0x47, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, + 0x00, 0x32, 0xFF, 0xDC, 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, + 0x34, 0xF6, 0xB7, 0x05, 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7E, 0xFF, 0x23, 0x01, + 0x0F, 0xFE, 0xD7, 0x02, 0x3B, 0xFC, 0xF5, 0x04, 0xED, 0x48, 0x70, + 0x02, 0x52, 0xFD, 0x46, 0x02, 0x5A, 0xFE, 0xFF, 0x00, 0x8B, 0xFF, + 0x1C, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81, + 0xFC, 0x1A, 0x06, 0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5, + 0xA6, 0x03, 0xB9, 0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07, + 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x53, 0xFF, 0xBB, 0x01, 0x5A, 0xFC, + 0x1C, 0x07, 0x8D, 0xF1, 0x34, 0x2E, 0x48, 0x32, 0x81, 0xF1, 0xE7, + 0x06, 0x8E, 0xFC, 0x96, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF, + 0x04, 0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, + 0x04, 0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, + 0x55, 0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x17, + 0x00, 0x9F, 0xFF, 0xCE, 0x00, 0xC2, 0xFE, 0x80, 0x01, 0xC6, 0xFE, + 0x40, 0xFF, 0x81, 0x48, 0x9E, 0x08, 0xBA, 0xFA, 0x9A, 0x03, 0xAC, + 0xFD, 0x51, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x2F, 0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F, + 0xF7, 0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF, + 0xA2, 0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0xFD, 0xFF, 0x35, + 0x00, 0x3D, 0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1D, 0x07, 0x45, 0xF2, + 0xC6, 0x26, 0xD3, 0x38, 0x24, 0xF2, 0x2D, 0x06, 0x1B, 0xFD, 0x3B, + 0x01, 0x95, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x11, 0x00, + 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54, + 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01, + 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x0F, 0x00, 0xC1, 0xFF, 0x76, + 0x00, 0x76, 0xFF, 0x32, 0x00, 0x22, 0x01, 0x76, 0xFA, 0xA3, 0x46, + 0x7D, 0x0F, 0x2C, 0xF8, 0xD5, 0x04, 0x13, 0xFD, 0x96, 0x01, 0x51, + 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64, 0xFF, + 0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A, 0x2C, + 0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8, 0x00, + 0xA8, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x47, 0xFC, 0xB5, 0x06, 0xB0, 0xF3, 0x19, 0x1F, 0x7E, 0x3E, + 0xC4, 0xF3, 0xFA, 0x04, 0xE7, 0xFD, 0xC1, 0x00, 0xD3, 0xFF, 0xFF, + 0xFF, 0x02, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF, 0x82, 0x01, + 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62, 0x2C, 0xA8, + 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF, 0x30, 0x00, + 0xFD, 0xFF, 0x08, 0x00, 0xE1, 0xFF, 0x23, 0x00, 0x20, 0x00, 0x00, + 0xFF, 0x31, 0x03, 0xAD, 0xF6, 0x65, 0x43, 0xDC, 0x16, 0xD1, 0xF5, + 0xE1, 0x05, 0x99, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14, 0x01, 0x2D, 0xFE, + 0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48, 0x73, 0x03, 0xE0, + 0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86, 0xFF, 0x1E, 0x00, + 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCC, 0x01, 0x91, 0xFC, 0xF3, + 0x05, 0xA6, 0xF5, 0x70, 0x17, 0x17, 0x43, 0x6D, 0xF6, 0x56, 0x03, + 0xEA, 0xFE, 0x2D, 0x00, 0x1D, 0x00, 0xE4, 0xFF, 0x08, 0x00, 0xFD, + 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07, + 0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2, + 0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x03, 0x00, + 0xFD, 0xFF, 0xD8, 0xFF, 0xB7, 0x00, 0xF9, 0xFD, 0xDE, 0x04, 0xEF, + 0xF3, 0xE4, 0x3E, 0x81, 0x1E, 0xD2, 0xF3, 0xA9, 0x06, 0x4B, 0xFC, + 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x16, 0x00, 0xA5, + 0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45, 0x01, 0x33, 0xFF, 0x5A, 0xFE, + 0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA, 0xD2, 0x03, 0x90, 0xFD, 0x5E, + 0x01, 0x66, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2E, 0x00, + 0x4F, 0xFF, 0x9A, 0x01, 0x08, 0xFD, 0xEB, 0x04, 0xFC, 0xF7, 0x0A, + 0x10, 0x70, 0x46, 0x22, 0xFA, 0x4D, 0x01, 0x19, 0x00, 0x84, 0xFF, + 0x70, 0x00, 0xC4, 0xFF, 0x0F, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, + 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, + 0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, + 0xFF, 0x13, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x99, 0xFF, + 0x33, 0x01, 0x29, 0xFD, 0x1A, 0x06, 0x3B, 0xF2, 0x4B, 0x39, 0x30, + 0x26, 0x5B, 0xF2, 0x19, 0x07, 0x31, 0xFC, 0xE1, 0x01, 0x3C, 0xFF, + 0x35, 0x00, 0xFD, 0xFF, 0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95, + 0xFF, 0xFA, 0xFF, 0x83, 0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10, + 0xBF, 0xF7, 0x07, 0x05, 0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x56, 0x01, + 0xA0, 0xFD, 0xB3, 0x03, 0x87, 0xFA, 0x1F, 0x09, 0x6A, 0x48, 0xD9, + 0xFE, 0xF6, 0xFE, 0x65, 0x01, 0xD0, 0xFE, 0xC7, 0x00, 0xA2, 0xFF, + 0x17, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50, + 0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4, + 0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03, + 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x69, 0xFF, 0x90, 0x01, 0x96, 0xFC, + 0xDD, 0x06, 0x85, 0xF1, 0xD0, 0x32, 0xA6, 0x2D, 0x94, 0xF1, 0x20, + 0x07, 0x54, 0xFC, 0xBF, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x07, 0x00, 0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83, + 0x03, 0x20, 0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06, + 0x88, 0xFC, 0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0x1D, + 0x00, 0x89, 0xFF, 0x06, 0x01, 0x4C, 0xFE, 0x60, 0x02, 0x1F, 0xFD, + 0xE2, 0x02, 0xF3, 0x48, 0x7D, 0x04, 0x6E, 0xFC, 0xBD, 0x02, 0x1C, + 0xFE, 0x1C, 0x01, 0x80, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x33, 0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, + 0xF6, 0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, + 0x11, 0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0xFD, 0xFF, 0x31, + 0x00, 0x49, 0xFF, 0xCB, 0x01, 0x45, 0xFC, 0x29, 0x07, 0xB6, 0xF1, + 0xAD, 0x2B, 0xA2, 0x34, 0x9E, 0xF1, 0xB4, 0x06, 0xB8, 0xFC, 0x7A, + 0x01, 0x75, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x02, 0x00, 0x02, 0x00, + 0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE, + 0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x14, 0x00, 0xAB, 0xFF, 0xAF, + 0x00, 0x01, 0xFF, 0x0A, 0x01, 0x9E, 0xFF, 0x7C, 0xFD, 0x03, 0x48, + 0xED, 0x0A, 0xD5, 0xF9, 0x0A, 0x04, 0x74, 0xFD, 0x6A, 0x01, 0x62, + 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, + 0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1, + 0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00, + 0xBE, 0xFF, 0x10, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0x04, 0x07, 0xB1, 0xF2, 0x21, 0x24, 0xE6, 0x3A, + 0x97, 0xF2, 0xD0, 0x05, 0x5B, 0xFD, 0x15, 0x01, 0xA9, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF, 0x45, 0x01, + 0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80, 0x27, 0x2B, + 0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF, 0x34, 0x00, + 0xFD, 0xFF, 0x0D, 0x00, 0xCD, 0xFF, 0x59, 0x00, 0xB3, 0xFF, 0xC4, + 0xFF, 0xE2, 0x01, 0x09, 0xF9, 0xAA, 0x45, 0xFE, 0x11, 0x54, 0xF7, + 0x38, 0x05, 0xE4, 0xFC, 0xAA, 0x01, 0x49, 0xFF, 0x30, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC, 0xFD, + 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF, 0x89, + 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18, 0x00, + 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5B, 0xFC, 0x7B, + 0x06, 0x50, 0xF4, 0x6E, 0x1C, 0x36, 0x40, 0x92, 0xF4, 0x75, 0x04, + 0x3B, 0xFE, 0x91, 0x00, 0xEB, 0xFF, 0xF6, 0xFF, 0x04, 0x00, 0xFD, + 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01, 0x84, 0xFC, 0xF3, 0x06, + 0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85, 0xF1, 0x16, 0x07, 0x61, + 0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x06, 0x00, + 0xEC, 0xFF, 0x08, 0x00, 0x57, 0x00, 0x9F, 0xFE, 0xD1, 0x03, 0x9B, + 0xF5, 0xF7, 0x41, 0x7C, 0x19, 0x13, 0xF5, 0x2F, 0x06, 0x78, 0xFC, + 0xD5, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x8F, + 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02, 0x91, 0xFD, 0xE3, 0x01, + 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8, 0x02, 0xFE, 0xFD, 0x2B, + 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x32, 0x00, + 0x42, 0xFF, 0xBD, 0x01, 0xB6, 0xFC, 0x9F, 0x05, 0x6C, 0xF6, 0xD6, + 0x14, 0x65, 0x44, 0x98, 0xF7, 0xAC, 0x02, 0x4E, 0xFF, 0xF4, 0xFF, + 0x39, 0x00, 0xD9, 0xFF, 0x0A, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, + 0xFF, 0xD2, 0x01, 0x3D, 0xFC, 0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A, + 0xC6, 0x35, 0xB7, 0xF1, 0x96, 0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D, + 0xFF, 0x1F, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x06, 0x00, 0xC1, 0xFF, + 0xE5, 0x00, 0xAA, 0xFD, 0x58, 0x05, 0x3A, 0xF3, 0x11, 0x3D, 0x2C, + 0x21, 0x3F, 0xF3, 0xDA, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, + 0xFF, 0xD0, 0x00, 0x07, 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, + 0x63, 0xF9, 0x42, 0x04, 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x85, 0x01, + 0x39, 0xFD, 0x84, 0x04, 0xD9, 0xF8, 0x95, 0x0D, 0x48, 0x47, 0xA7, + 0xFB, 0x86, 0x00, 0x8A, 0x00, 0x46, 0xFF, 0x8E, 0x00, 0xB8, 0xFF, + 0x11, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35, + 0xFC, 0xF3, 0x06, 0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2, + 0x9C, 0x05, 0x7E, 0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01, + 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x87, 0xFF, 0x57, 0x01, 0xEF, 0xFC, + 0x6B, 0x06, 0xE0, 0xF1, 0x23, 0x37, 0xCE, 0x28, 0x01, 0xF2, 0x28, + 0x07, 0x36, 0xFC, 0xD9, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x0B, 0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, + 0x02, 0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, + 0xCF, 0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x23, 0x00, 0x74, 0xFF, 0x3C, 0x01, 0xDA, 0xFD, 0x40, 0x03, + 0x6E, 0xFB, 0xE1, 0x06, 0xC3, 0x48, 0xB3, 0x00, 0x1A, 0xFE, 0xDC, + 0x01, 0x91, 0xFE, 0xE5, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFE, 0xFF, + 0x36, 0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6, + 0xF4, 0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE, + 0x77, 0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0xFD, 0xFF, 0x2A, + 0x00, 0x5C, 0xFF, 0xA8, 0x01, 0x73, 0xFC, 0x05, 0x07, 0x7D, 0xF1, + 0x67, 0x30, 0x21, 0x30, 0x7E, 0xF1, 0x08, 0x07, 0x6F, 0xFC, 0xAB, + 0x01, 0x5B, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x05, 0x00, 0xF0, 0xFF, + 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32, + 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01, + 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE8, + 0x00, 0x8A, 0xFE, 0xE9, 0x01, 0x01, 0xFE, 0xEA, 0x00, 0xCB, 0x48, + 0xA2, 0x06, 0x87, 0xFB, 0x33, 0x03, 0xE0, 0xFD, 0x39, 0x01, 0x75, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x45, 0xFF, + 0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13, 0xFD, + 0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47, 0x00, + 0xD3, 0xFF, 0x0B, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x42, 0xFF, 0xD8, + 0x01, 0x37, 0xFC, 0x29, 0x07, 0xF8, 0xF1, 0x19, 0x29, 0xE5, 0x36, + 0xD8, 0xF1, 0x73, 0x06, 0xE9, 0xFC, 0x5B, 0x01, 0x85, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF, 0xFB, 0x00, + 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81, 0x22, 0xFC, + 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x12, 0x00, 0xB7, 0xFF, 0x91, 0x00, 0x40, 0xFF, 0x96, + 0x00, 0x6F, 0x00, 0xD5, 0xFB, 0x5E, 0x47, 0x50, 0x0D, 0xF2, 0xF8, + 0x78, 0x04, 0x3F, 0xFD, 0x82, 0x01, 0x58, 0xFF, 0x2B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79, 0x01, 0x53, 0xFD, + 0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47, 0x76, 0xFC, 0x1F, + 0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2, 0xFF, 0x13, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xDF, + 0x06, 0x30, 0xF3, 0x78, 0x21, 0xDB, 0x3C, 0x28, 0xF3, 0x65, 0x05, + 0xA2, 0xFD, 0xEA, 0x00, 0xBE, 0xFF, 0x07, 0x00, 0x01, 0x00, 0xFE, + 0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06, + 0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C, + 0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x0A, 0x00, + 0xD8, 0xFF, 0x3C, 0x00, 0xEE, 0xFF, 0x5A, 0xFF, 0x98, 0x02, 0xBB, + 0xF7, 0x87, 0x44, 0x8C, 0x14, 0x83, 0xF6, 0x95, 0x05, 0xBA, 0xFC, + 0xBB, 0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x21, + 0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7, 0xFD, 0x05, 0x03, 0xE1, 0xFB, + 0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01, 0xAA, 0xFD, 0x18, 0x02, 0x72, + 0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x39, 0xFF, 0xD6, 0x01, 0x75, 0xFC, 0x37, 0x06, 0xFF, 0xF4, 0xC7, + 0x19, 0xCC, 0x41, 0x7F, 0xF5, 0xE2, 0x03, 0x95, 0xFE, 0x5D, 0x00, + 0x05, 0x00, 0xED, 0xFF, 0x06, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57, + 0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, + 0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, + 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0xF5, 0xFF, 0xEE, 0xFF, + 0x8B, 0x00, 0x44, 0xFE, 0x65, 0x04, 0xAA, 0xF4, 0x66, 0x40, 0x23, + 0x1C, 0x63, 0xF4, 0x74, 0x06, 0x5D, 0xFC, 0xDE, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA, + 0xFE, 0xAE, 0x01, 0x70, 0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07, + 0x14, 0xFB, 0x6D, 0x03, 0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAC, 0x01, + 0xDF, 0xFC, 0x43, 0x05, 0x3C, 0xF7, 0x46, 0x12, 0x8D, 0x45, 0xE2, + 0xF8, 0xF7, 0x01, 0xB8, 0xFF, 0xB9, 0xFF, 0x56, 0x00, 0xCE, 0xFF, + 0x0C, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34, + 0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2, + 0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAB, 0xFF, 0x10, 0x01, 0x62, 0xFD, + 0xC5, 0x05, 0xA5, 0xF2, 0x1F, 0x3B, 0xD6, 0x23, 0xBE, 0xF2, 0x01, + 0x07, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x10, 0x00, 0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4, + 0x00, 0x0C, 0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04, + 0x26, 0xFD, 0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6D, 0x01, 0x6E, 0xFD, 0x17, 0x04, + 0xBC, 0xF9, 0x30, 0x0B, 0xF4, 0x47, 0x4B, 0xFD, 0xB5, 0xFF, 0xFD, + 0x00, 0x08, 0xFF, 0xAC, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, + 0xF3, 0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, + 0xD4, 0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x21, + 0x00, 0x77, 0xFF, 0x77, 0x01, 0xBD, 0xFC, 0xAE, 0x06, 0xA3, 0xF1, + 0xE3, 0x34, 0x64, 0x2B, 0xBC, 0xF1, 0x2A, 0x07, 0x43, 0xFC, 0xCD, + 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x09, 0x00, 0xDD, 0xFF, + 0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7, + 0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01, + 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, + 0xFF, 0x20, 0x01, 0x16, 0xFE, 0xCA, 0x02, 0x54, 0xFC, 0xB9, 0x04, + 0xF2, 0x48, 0xA9, 0x02, 0x39, 0xFD, 0x53, 0x02, 0x53, 0xFE, 0x03, + 0x01, 0x8A, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, + 0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89, + 0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00, + 0xE8, 0xFF, 0x07, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBD, + 0x01, 0x57, 0xFC, 0x1E, 0x07, 0x90, 0xF1, 0xED, 0x2D, 0x8C, 0x32, + 0x83, 0xF1, 0xE2, 0x06, 0x92, 0xFC, 0x93, 0x01, 0x68, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF, 0xA4, 0x00, + 0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78, 0x1D, 0x10, + 0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x17, 0x00, 0xA0, 0xFF, 0xCA, 0x00, 0xC9, 0xFE, 0x73, + 0x01, 0xDE, 0xFE, 0x0C, 0xFF, 0x76, 0x48, 0xDE, 0x08, 0xA1, 0xFA, + 0xA6, 0x03, 0xA6, 0xFD, 0x53, 0x01, 0x6A, 0xFF, 0x26, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6, 0xFC, + 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9, 0x98, + 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E, 0x00, + 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x32, 0xFC, 0x1B, + 0x07, 0x50, 0xF2, 0x7B, 0x26, 0x11, 0x39, 0x2F, 0xF2, 0x23, 0x06, + 0x22, 0xFD, 0x37, 0x01, 0x97, 0xFF, 0x15, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01, 0x41, 0xFD, 0xF6, 0x05, + 0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84, 0xF2, 0x0F, 0x07, 0x31, + 0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x0F, 0x00, + 0xC2, 0xFF, 0x73, 0x00, 0x7D, 0xFF, 0x25, 0x00, 0x38, 0x01, 0x4C, + 0xFA, 0x89, 0x46, 0xC3, 0x0F, 0x14, 0xF8, 0xE0, 0x04, 0x0D, 0xFD, + 0x98, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x27, + 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD, 0xDF, 0x03, 0x2E, 0xFA, + 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B, 0xFF, 0x38, 0x01, 0xE9, + 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE4, 0x01, 0x49, 0xFC, 0xAF, 0x06, 0xC1, 0xF3, 0xCD, + 0x1E, 0xB1, 0x3E, 0xD9, 0xF3, 0xEC, 0x04, 0xF0, 0xFD, 0xBC, 0x00, + 0xD5, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F, + 0xFF, 0x85, 0x01, 0xA6, 0xFC, 0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33, + 0xAB, 0x2C, 0xA3, 0xF1, 0x26, 0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D, + 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x08, 0x00, 0xE2, 0xFF, 0x20, 0x00, + 0x26, 0x00, 0xF5, 0xFE, 0x43, 0x03, 0x8D, 0xF6, 0x3C, 0x43, 0x25, + 0x17, 0xBB, 0xF5, 0xEA, 0x05, 0x95, 0xFC, 0xCA, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, + 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, + 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, + 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, + 0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED, 0x20, 0x3D, 0x3D, 0x4A, + 0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00, 0xC3, 0xFF, 0x05, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, + 0xFD, 0x4E, 0x05, 0x4A, 0xF3, 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, + 0xD6, 0x06, 0x3D, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, + 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, + 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, + 0xFD, 0xFF, 0x30, 0x00, 0x4D, 0xFF, 0xC5, 0x01, 0x4C, 0xFC, 0x26, + 0x07, 0xA3, 0xF1, 0xAB, 0x2C, 0xBB, 0x33, 0x8F, 0xF1, 0xCA, 0x06, + 0xA6, 0xFC, 0x85, 0x01, 0x6F, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0x16, + 0x00, 0xA6, 0xFF, 0xBB, 0x00, 0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF, + 0x28, 0xFE, 0x3A, 0x48, 0x04, 0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A, + 0xFD, 0x60, 0x01, 0x65, 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x35, 0x00, 0x3A, 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x0F, 0x07, 0x84, + 0xF2, 0x29, 0x25, 0x1A, 0x3A, 0x67, 0xF2, 0xF6, 0x05, 0x41, 0xFD, + 0x24, 0x01, 0xA1, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8, + 0xFF, 0x64, 0x00, 0x9B, 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9, + 0x10, 0x46, 0x03, 0x11, 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2, + 0x01, 0x4C, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE1, 0x01, 0x52, 0xFC, 0x93, 0x06, 0x10, 0xF4, 0x78, + 0x1D, 0x90, 0x3F, 0x3E, 0xF4, 0xAA, 0x04, 0x19, 0xFE, 0xA4, 0x00, + 0xE2, 0xFF, 0xFA, 0xFF, 0x03, 0x00, 0x07, 0x00, 0xE8, 0xFF, 0x12, + 0x00, 0x42, 0x00, 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, + 0x76, 0x18, 0x5C, 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, + 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, + 0xC3, 0x01, 0xA7, 0xFC, 0xC0, 0x05, 0x1E, 0xF6, 0xD8, 0x15, 0xE7, + 0x43, 0x20, 0xF7, 0xEF, 0x02, 0x27, 0xFF, 0x0A, 0x00, 0x2E, 0x00, + 0xDD, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, + 0x00, 0xC8, 0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, + 0x76, 0xF3, 0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x54, 0xFF, 0x8D, 0x01, + 0x26, 0xFD, 0xAD, 0x04, 0x82, 0xF8, 0x87, 0x0E, 0xF9, 0x46, 0x0C, + 0xFB, 0xD4, 0x00, 0x5D, 0x00, 0x5E, 0xFF, 0x82, 0x00, 0xBD, 0xFF, + 0x10, 0x00, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, + 0xFD, 0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, + 0x23, 0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x24, 0x00, 0x70, 0xFF, 0x46, 0x01, 0xC3, 0xFD, + 0x6D, 0x03, 0x14, 0xFB, 0xBE, 0x07, 0xA6, 0x48, 0xF8, 0xFF, 0x70, + 0xFE, 0xAE, 0x01, 0xAA, 0xFE, 0xD9, 0x00, 0x9A, 0xFF, 0x19, 0x00, + 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7, + 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07, + 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B, + 0x00, 0x90, 0xFF, 0xF4, 0x00, 0x72, 0xFE, 0x18, 0x02, 0xAA, 0xFD, + 0xAB, 0x01, 0xDF, 0x48, 0xCA, 0x05, 0xE1, 0xFB, 0x05, 0x03, 0xF7, + 0xFD, 0x2E, 0x01, 0x79, 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07, 0xDC, + 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5, 0xFC, + 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xB2, + 0xFF, 0x9D, 0x00, 0x27, 0xFF, 0xC3, 0x00, 0x1F, 0x00, 0x76, 0xFC, + 0xA3, 0x47, 0x60, 0x0C, 0x4A, 0xF9, 0x4E, 0x04, 0x53, 0xFD, 0x79, + 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, 0xF2, 0x81, + 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, 0xFB, 0x00, + 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xD3, 0xFF, 0x47, + 0x00, 0xD7, 0xFF, 0x82, 0xFF, 0x53, 0x02, 0x39, 0xF8, 0xFD, 0x44, + 0x8D, 0x13, 0xD3, 0xF6, 0x72, 0x05, 0xCA, 0xFC, 0xB5, 0x01, 0x45, + 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x38, 0xFF, + 0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, 0xCE, 0x1A, 0x32, + 0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, 0x00, 0xFB, 0xFF, + 0xF0, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF2, 0xFF, 0xF8, 0xFF, 0x77, + 0x00, 0x67, 0xFE, 0x2D, 0x04, 0x04, 0xF5, 0x07, 0x41, 0x1B, 0x1B, + 0xA6, 0xF4, 0x5A, 0x06, 0x67, 0xFC, 0xDB, 0x01, 0x38, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB3, 0x01, + 0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44, 0x13, 0x1E, 0x45, 0x5E, + 0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF, 0x4A, 0x00, 0xD2, 0xFF, + 0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xB4, 0xFF, 0x00, 0x01, 0x7E, + 0xFD, 0x9C, 0x05, 0xDC, 0xF2, 0xE4, 0x3B, 0xCD, 0x22, 0xEE, 0xF2, + 0xF3, 0x06, 0x35, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5D, 0xFF, 0x76, 0x01, 0x59, 0xFD, + 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, 0xB6, 0x47, 0xA4, 0xFC, 0x07, + 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, 0x00, 0xB1, 0xFF, 0x13, 0x00, + 0xFE, 0xFF, 0x1F, 0x00, 0x7D, 0xFF, 0x6B, 0x01, 0xCF, 0xFC, 0x96, + 0x06, 0xB7, 0xF1, 0xC6, 0x35, 0x64, 0x2A, 0xD4, 0xF1, 0x2B, 0x07, + 0x3D, 0xFC, 0xD2, 0x01, 0x45, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x21, 0x00, 0x7A, 0xFF, 0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02, + 0xFB, 0xFB, 0x8D, 0x05, 0xE5, 0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25, + 0x02, 0x6B, 0xFE, 0xF7, 0x00, 0x8F, 0xFF, 0x1C, 0x00, 0xFD, 0xFF, + 0x2D, 0x00, 0x55, 0xFF, 0xB5, 0x01, 0x61, 0xFC, 0x16, 0x07, 0x85, + 0xF1, 0xE6, 0x2E, 0x9E, 0x31, 0x7D, 0xF1, 0xF3, 0x06, 0x84, 0xFC, + 0x9D, 0x01, 0x63, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9C, + 0xFF, 0xD6, 0x00, 0xB1, 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF, + 0x9C, 0x48, 0xFD, 0x07, 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49, + 0x01, 0x6E, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDE, 0x01, 0x33, 0xFC, 0x22, 0x07, 0x2B, 0xF2, 0x80, + 0x27, 0x3B, 0x38, 0x0A, 0xF2, 0x44, 0x06, 0x0B, 0xFD, 0x45, 0x01, + 0x90, 0xFF, 0x18, 0x00, 0xFF, 0xFF, 0x10, 0x00, 0xBE, 0xFF, 0x7F, + 0x00, 0x65, 0xFF, 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, + 0xCD, 0x0E, 0x6A, 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, + 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE5, 0x01, 0x42, 0xFC, 0xC3, 0x06, 0x87, 0xF3, 0xD7, 0x1F, 0xFE, + 0x3D, 0x91, 0xF3, 0x1D, 0x05, 0xD1, 0xFD, 0xCE, 0x00, 0xCC, 0xFF, + 0x02, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, + 0x00, 0x1B, 0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, + 0x07, 0xF6, 0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCF, 0x01, + 0x88, 0xFC, 0x09, 0x06, 0x71, 0xF5, 0x2B, 0x18, 0xB2, 0x42, 0x20, + 0xF6, 0x83, 0x03, 0xCF, 0xFE, 0x3C, 0x00, 0x15, 0x00, 0xE6, 0xFF, + 0x07, 0x00, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, + 0xFE, 0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, + 0x99, 0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4D, 0xFF, 0xA0, 0x01, 0xFB, 0xFC, + 0x07, 0x05, 0xBF, 0xF7, 0xBB, 0x10, 0x2B, 0x46, 0xBB, 0xF9, 0x83, + 0x01, 0xFA, 0xFF, 0x95, 0xFF, 0x68, 0x00, 0xC7, 0xFF, 0x0E, 0x00, + 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00, + 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07, + 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x27, 0x00, 0x66, 0xFF, 0x5E, 0x01, 0x90, 0xFD, 0xD2, 0x03, + 0x47, 0xFA, 0xC3, 0x09, 0x48, 0x48, 0x5A, 0xFE, 0x33, 0xFF, 0x45, + 0x01, 0xE2, 0xFE, 0xBE, 0x00, 0xA5, 0xFF, 0x16, 0x00, 0xFD, 0xFF, + 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06, 0x8C, + 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E, 0xFC, + 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x86, + 0xFF, 0x0E, 0x01, 0x3B, 0xFE, 0x82, 0x02, 0xE0, 0xFC, 0x73, 0x03, + 0xF6, 0x48, 0xE9, 0x03, 0xAD, 0xFC, 0x9C, 0x02, 0x2D, 0xFE, 0x14, + 0x01, 0x83, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00, + 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, 0xF1, 0x62, + 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, 0x82, 0x01, + 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0x15, 0x00, 0xA8, 0xFF, 0xB8, + 0x00, 0xF0, 0xFE, 0x2B, 0x01, 0x63, 0xFF, 0xF6, 0xFD, 0x2C, 0x48, + 0x47, 0x0A, 0x14, 0xFA, 0xEB, 0x03, 0x84, 0xFD, 0x63, 0x01, 0x64, + 0xFF, 0x27, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x3A, 0xFF, + 0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, 0xDD, 0x24, 0x54, + 0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, 0x01, 0xA3, 0xFF, + 0x11, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xC9, 0xFF, 0x61, 0x00, 0xA2, + 0xFF, 0xE2, 0xFF, 0xAE, 0x01, 0x6B, 0xF9, 0xF2, 0x45, 0x4A, 0x11, + 0x8F, 0xF7, 0x1D, 0x05, 0xF1, 0xFC, 0xA4, 0x01, 0x4B, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE1, 0x01, + 0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C, 0x1D, 0xC0, 0x3F, 0x55, + 0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00, 0xE4, 0xFF, 0xF9, 0xFF, + 0x04, 0x00, 0x07, 0x00, 0xE9, 0xFF, 0x0F, 0x00, 0x48, 0x00, 0xB9, + 0xFE, 0xA6, 0x03, 0xE4, 0xF5, 0x60, 0x42, 0xC1, 0x18, 0x47, 0xF5, + 0x1A, 0x06, 0x81, 0xFC, 0xD2, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x40, 0xFF, 0xC1, 0x01, 0xAB, 0xFC, + 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, 0x0B, 0x44, 0x42, 0xF7, 0xDC, + 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, 0x00, 0xDC, 0xFF, 0x09, 0x00, + 0x02, 0x00, 0x04, 0x00, 0xC7, 0xFF, 0xD9, 0x00, 0xBF, 0xFD, 0x38, + 0x05, 0x69, 0xF3, 0x96, 0x3D, 0x6F, 0x20, 0x66, 0xF3, 0xCE, 0x06, + 0x3F, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x2C, 0x00, 0x55, 0xFF, 0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04, + 0x9B, 0xF8, 0x42, 0x0E, 0x0F, 0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A, + 0x00, 0x58, 0xFF, 0x85, 0x00, 0xBB, 0xFF, 0x10, 0x00, 0xFF, 0xFF, + 0x19, 0x00, 0x8C, 0xFF, 0x4D, 0x01, 0xFE, 0xFC, 0x56, 0x06, 0xF7, + 0xF1, 0xBF, 0x37, 0x15, 0x28, 0x18, 0xF2, 0x25, 0x07, 0x34, 0xFC, + 0xDC, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24, + 0x00, 0x71, 0xFF, 0x43, 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB, + 0x7E, 0x07, 0xAF, 0x48, 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3, + 0xFE, 0xDD, 0x00, 0x99, 0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00, + 0x60, 0xFF, 0xA2, 0x01, 0x7C, 0xFC, 0xFB, 0x06, 0x7C, 0xF1, 0x15, + 0x31, 0x73, 0x2F, 0x81, 0xF1, 0x10, 0x07, 0x67, 0xFC, 0xB1, 0x01, + 0x58, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0x1B, 0x00, 0x91, 0xFF, 0xF1, + 0x00, 0x79, 0xFE, 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, + 0x07, 0x06, 0xC7, 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, + 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x44, 0xFF, + 0xD5, 0x01, 0x3A, 0xFC, 0x2A, 0x07, 0xE3, 0xF1, 0xD1, 0x29, 0x46, + 0x36, 0xC5, 0xF1, 0x87, 0x06, 0xDA, 0xFC, 0x64, 0x01, 0x80, 0xFF, + 0x1E, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, + 0xFF, 0xB6, 0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, + 0x31, 0xF9, 0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, + 0x37, 0xFC, 0xEB, 0x06, 0x0B, 0xF3, 0x35, 0x22, 0x52, 0x3C, 0xFD, + 0xF2, 0x84, 0x05, 0x8D, 0xFD, 0xF6, 0x00, 0xB8, 0xFF, 0x09, 0x00, + 0x01, 0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, + 0xFF, 0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, + 0x7C, 0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD9, 0x01, 0x6D, 0xFC, + 0x4B, 0x06, 0xCD, 0xF4, 0x83, 0x1A, 0x5F, 0x41, 0x3A, 0xF5, 0x0C, + 0x04, 0x7B, 0xFE, 0x6C, 0x00, 0xFE, 0xFF, 0xEF, 0xFF, 0x05, 0x00, + 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E, + 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06, + 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, + 0xFF, 0x31, 0x00, 0x46, 0xFF, 0xB1, 0x01, 0xD3, 0xFC, 0x5D, 0x05, + 0x01, 0xF7, 0xFB, 0x12, 0x3F, 0x45, 0x83, 0xF8, 0x2A, 0x02, 0x9A, + 0xFF, 0xCA, 0xFF, 0x4E, 0x00, 0xD1, 0xFF, 0x0C, 0x00, 0x00, 0x00, + 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05, 0xCC, + 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35, 0xFC, + 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29, + 0x00, 0x5E, 0xFF, 0x74, 0x01, 0x5F, 0xFD, 0x35, 0x04, 0x7C, 0xF9, + 0xD8, 0x0B, 0xC9, 0x47, 0xD4, 0xFC, 0xF0, 0xFF, 0xDD, 0x00, 0x19, + 0xFF, 0xA4, 0x00, 0xAF, 0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x20, 0x00, + 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, 0xF1, 0x86, + 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, 0xD1, 0x01, + 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x7C, + 0xFF, 0x27, 0x01, 0x05, 0xFE, 0xEB, 0x02, 0x14, 0xFC, 0x50, 0x05, + 0xEA, 0x48, 0x1B, 0x02, 0x78, 0xFD, 0x32, 0x02, 0x64, 0xFE, 0xFA, + 0x00, 0x8D, 0xFF, 0x1C, 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x54, 0xFF, + 0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, 0x9F, 0x2E, 0xE3, + 0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, 0x01, 0x64, 0xFF, + 0x28, 0x00, 0xFD, 0xFF, 0x18, 0x00, 0x9D, 0xFF, 0xD3, 0x00, 0xB8, + 0xFE, 0x93, 0x01, 0xA1, 0xFE, 0x8E, 0xFF, 0x92, 0x48, 0x3D, 0x08, + 0xE1, 0xFA, 0x86, 0x03, 0xB6, 0xFD, 0x4C, 0x01, 0x6D, 0xFF, 0x25, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3E, 0xFF, 0xDF, 0x01, + 0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36, 0x27, 0x78, 0x38, 0x14, + 0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01, 0x92, 0xFF, 0x17, 0x00, + 0xFF, 0xFF, 0x10, 0x00, 0xBF, 0xFF, 0x7B, 0x00, 0x6C, 0xFF, 0x44, + 0x00, 0x01, 0x01, 0xB6, 0xFA, 0xC8, 0x46, 0x13, 0x0F, 0x51, 0xF8, + 0xC4, 0x04, 0x1B, 0xFD, 0x92, 0x01, 0x52, 0xFF, 0x2D, 0x00, 0xFF, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x44, 0xFC, + 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, 0x31, 0x3E, 0xA5, 0xF3, 0x0F, + 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, 0xFF, 0x01, 0x00, 0x02, 0x00, + 0x09, 0x00, 0xDF, 0xFF, 0x28, 0x00, 0x17, 0x00, 0x10, 0xFF, 0x15, + 0x03, 0xDD, 0xF6, 0x9E, 0x43, 0x6C, 0x16, 0xF1, 0xF5, 0xD3, 0x05, + 0x9F, 0xFC, 0xC6, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x34, 0x00, 0x3C, 0xFF, 0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06, + 0x86, 0xF5, 0xE0, 0x17, 0xDB, 0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9, + 0xFE, 0x36, 0x00, 0x18, 0x00, 0xE5, 0xFF, 0x07, 0x00, 0x03, 0x00, + 0xFC, 0xFF, 0xDC, 0xFF, 0xAF, 0x00, 0x07, 0xFE, 0xC8, 0x04, 0x10, + 0xF4, 0x2D, 0x3F, 0x0F, 0x1E, 0xED, 0xF3, 0xA0, 0x06, 0x4E, 0xFC, + 0xE3, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, + 0x00, 0x4E, 0xFF, 0x9E, 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7, + 0x75, 0x10, 0x48, 0x46, 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E, + 0xFF, 0x6B, 0x00, 0xC6, 0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x13, 0x00, + 0x9D, 0xFF, 0x2D, 0x01, 0x33, 0xFD, 0x0B, 0x06, 0x4D, 0xF2, 0xA5, + 0x39, 0xBF, 0x25, 0x6D, 0xF2, 0x15, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x68, + 0xFF, 0x5B, 0x01, 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, + 0x57, 0x48, 0x8D, 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, + 0x00, 0xA4, 0xFF, 0x16, 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6C, 0xFF, + 0x8B, 0x01, 0x9D, 0xFC, 0xD5, 0x06, 0x89, 0xF1, 0x35, 0x33, 0x3A, + 0x2D, 0x9A, 0xF1, 0x23, 0x07, 0x51, 0xFC, 0xC2, 0x01, 0x4F, 0xFF, + 0x2F, 0x00, 0xFD, 0xFF, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, + 0xFE, 0x74, 0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, + 0x94, 0xFC, 0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x30, 0x00, 0x4B, 0xFF, 0xC9, 0x01, + 0x48, 0xFC, 0x28, 0x07, 0xAD, 0xF1, 0x19, 0x2C, 0x3F, 0x34, 0x97, + 0xF1, 0xBE, 0x06, 0xB0, 0xFC, 0x7F, 0x01, 0x72, 0xFF, 0x23, 0x00, + 0xFE, 0xFF, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, + 0x01, 0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, + 0xF8, 0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE4, 0x01, 0x32, 0xFC, + 0x09, 0x07, 0x9D, 0xF2, 0x92, 0x24, 0x8F, 0x3A, 0x82, 0xF2, 0xE1, + 0x05, 0x50, 0xFD, 0x1B, 0x01, 0xA6, 0xFF, 0x10, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3, + 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05, + 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE0, 0x01, 0x57, 0xFC, 0x85, 0x06, + 0x34, 0xF4, 0xE0, 0x1C, 0xF0, 0x3F, 0x6D, 0xF4, 0x8C, 0x04, 0x2C, + 0xFE, 0x99, 0x00, 0xE7, 0xFF, 0xF8, 0xFF, 0x04, 0x00, 0x06, 0x00, + 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03, 0xC7, + 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D, 0xFC, + 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, + 0x00, 0x41, 0xFF, 0xC0, 0x01, 0xAF, 0xFC, 0xAD, 0x05, 0x4A, 0xF6, + 0x44, 0x15, 0x2F, 0x44, 0x64, 0xF7, 0xC9, 0x02, 0x3D, 0xFF, 0xFE, + 0xFF, 0x34, 0x00, 0xDB, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x05, 0x00, + 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, 0xF3, 0x61, + 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, 0xE6, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x56, + 0xFF, 0x88, 0x01, 0x31, 0xFD, 0x95, 0x04, 0xB4, 0xF8, 0xFC, 0x0D, + 0x26, 0x47, 0x64, 0xFB, 0xA7, 0x00, 0x77, 0x00, 0x51, 0xFF, 0x89, + 0x00, 0xBA, 0xFF, 0x11, 0x00, 0xFF, 0xFF, 0x1A, 0x00, 0x8A, 0xFF, + 0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, 0x82, 0x37, 0x60, + 0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, 0x01, 0x40, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00, 0x72, 0xFF, 0x40, + 0x01, 0xD0, 0xFD, 0x53, 0x03, 0x47, 0xFB, 0x3F, 0x07, 0xB8, 0x48, + 0x62, 0x00, 0x3F, 0xFE, 0xC8, 0x01, 0x9C, 0xFE, 0xE0, 0x00, 0x98, + 0xFF, 0x19, 0x00, 0xFD, 0xFF, 0x29, 0x00, 0x5F, 0xFF, 0xA5, 0x01, + 0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF, 0x30, 0xB8, 0x2F, 0x80, + 0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01, 0x59, 0xFF, 0x2B, 0x00, + 0xFD, 0xFF, 0x1B, 0x00, 0x93, 0xFF, 0xED, 0x00, 0x80, 0xFE, 0xFD, + 0x01, 0xDC, 0xFD, 0x3C, 0x01, 0xD5, 0x48, 0x45, 0x06, 0xAE, 0xFB, + 0x1F, 0x03, 0xEA, 0xFD, 0x34, 0x01, 0x77, 0xFF, 0x22, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x43, 0xFF, 0xD6, 0x01, 0x39, 0xFC, + 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, 0x85, 0x36, 0xCC, 0xF1, 0x7F, + 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, 0xFF, 0x1D, 0x00, 0xFE, 0xFF, + 0x12, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0x35, 0xFF, 0xA9, 0x00, 0x4D, + 0x00, 0x19, 0xFC, 0x7C, 0x47, 0xE8, 0x0C, 0x18, 0xF9, 0x66, 0x04, + 0x48, 0xFD, 0x7E, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06, + 0x19, 0xF3, 0xEA, 0x21, 0x8A, 0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96, + 0xFD, 0xF1, 0x00, 0xBB, 0xFF, 0x08, 0x00, 0x01, 0x00, 0x0B, 0x00, + 0xD6, 0xFF, 0x41, 0x00, 0xE4, 0xFF, 0x6B, 0xFF, 0x7B, 0x02, 0xF0, + 0xF7, 0xBA, 0x44, 0x1E, 0x14, 0xA5, 0xF6, 0x86, 0x05, 0xC1, 0xFC, + 0xB9, 0x01, 0x44, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, + 0x00, 0x39, 0xFF, 0xD8, 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4, + 0x38, 0x1A, 0x8C, 0x41, 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66, + 0x00, 0x01, 0x00, 0xEE, 0xFF, 0x06, 0x00, 0x05, 0x00, 0xF4, 0xFF, + 0xF2, 0xFF, 0x83, 0x00, 0x53, 0xFE, 0x4E, 0x04, 0xD0, 0xF4, 0xAB, + 0x40, 0xB2, 0x1B, 0x7F, 0xF4, 0x69, 0x06, 0x62, 0xFC, 0xDD, 0x01, + 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x47, + 0xFF, 0xAF, 0x01, 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, + 0x5C, 0x45, 0xA9, 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, + 0x00, 0xD0, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xAF, 0xFF, + 0x09, 0x01, 0x6E, 0xFD, 0xB4, 0x05, 0xBC, 0xF2, 0x73, 0x3B, 0x64, + 0x23, 0xD2, 0xF2, 0xFB, 0x06, 0x34, 0xFC, 0xE6, 0x01, 0x38, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, + 0x01, 0x65, 0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, + 0x03, 0xFD, 0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, + 0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x20, 0x00, 0x79, 0xFF, 0x72, 0x01, + 0xC4, 0xFC, 0xA4, 0x06, 0xAB, 0xF1, 0x46, 0x35, 0xF7, 0x2A, 0xC6, + 0xF1, 0x2A, 0x07, 0x40, 0xFC, 0xCF, 0x01, 0x47, 0xFF, 0x31, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, + 0xFE, 0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, + 0x5E, 0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, + 0x00, 0xFD, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0xBA, 0x01, 0x5B, 0xFC, + 0x1B, 0x07, 0x8B, 0xF1, 0x58, 0x2E, 0x26, 0x32, 0x80, 0xF1, 0xEA, + 0x06, 0x8C, 0xFC, 0x97, 0x01, 0x66, 0xFF, 0x27, 0x00, 0xFD, 0xFF, + 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA, + 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03, + 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x35, 0x00, 0x3D, 0xFF, 0xDF, 0x01, 0x32, 0xFC, 0x1E, 0x07, + 0x40, 0xF2, 0xEB, 0x26, 0xB5, 0x38, 0x1F, 0xF2, 0x32, 0x06, 0x18, + 0xFD, 0x3D, 0x01, 0x94, 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00, + 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01, 0x8B, + 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15, 0xFD, + 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE5, 0x01, 0x46, 0xFC, 0xB8, 0x06, 0xA8, 0xF3, + 0x3F, 0x1F, 0x64, 0x3E, 0xBA, 0xF3, 0x01, 0x05, 0xE2, 0xFD, 0xC4, + 0x00, 0xD2, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00, 0xE1, 0xFF, + 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, 0xF6, 0x77, + 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, 0xC8, 0x01, + 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D, + 0xFF, 0xCC, 0x01, 0x8F, 0xFC, 0xF8, 0x05, 0x9B, 0xF5, 0x96, 0x17, + 0x02, 0x43, 0x5E, 0xF6, 0x5F, 0x03, 0xE4, 0xFE, 0x30, 0x00, 0x1B, + 0x00, 0xE4, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFD, 0xFF, 0xD9, 0xFF, + 0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, 0xFC, 0x3E, 0x5B, + 0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9C, + 0x01, 0x05, 0xFD, 0xF1, 0x04, 0xF0, 0xF7, 0x2D, 0x10, 0x61, 0x46, + 0x0D, 0xFA, 0x58, 0x01, 0x13, 0x00, 0x87, 0xFF, 0x6E, 0x00, 0xC4, + 0xFF, 0x0E, 0x00, 0xFF, 0xFF, 0x14, 0x00, 0x9B, 0xFF, 0x31, 0x01, + 0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A, 0x39, 0x0A, 0x26, 0x61, + 0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01, 0x3B, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x26, 0x00, 0x69, 0xFF, 0x58, 0x01, 0x9D, + 0xFD, 0xB9, 0x03, 0x7B, 0xFA, 0x40, 0x09, 0x63, 0x48, 0xBF, 0xFE, + 0x03, 0xFF, 0x5F, 0x01, 0xD4, 0xFE, 0xC5, 0x00, 0xA2, 0xFF, 0x16, + 0x00, 0xFD, 0xFF, 0x25, 0x00, 0x6A, 0xFF, 0x8E, 0x01, 0x99, 0xFC, + 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, 0x82, 0x2D, 0x96, 0xF1, 0x21, + 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, + 0x1D, 0x00, 0x88, 0xFF, 0x07, 0x01, 0x49, 0xFE, 0x67, 0x02, 0x13, + 0xFD, 0xFF, 0x02, 0xF4, 0x48, 0x5F, 0x04, 0x7A, 0xFC, 0xB6, 0x02, + 0x20, 0xFE, 0x1B, 0x01, 0x81, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x30, 0x00, 0x4A, 0xFF, 0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07, + 0xB3, 0xF1, 0xD1, 0x2B, 0x81, 0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5, + 0xFC, 0x7C, 0x01, 0x74, 0xFF, 0x22, 0x00, 0xFE, 0xFF, 0x15, 0x00, + 0xAA, 0xFF, 0xB1, 0x00, 0xFE, 0xFE, 0x10, 0x01, 0x92, 0xFF, 0x94, + 0xFD, 0x0D, 0x48, 0xCB, 0x0A, 0xE2, 0xF9, 0x04, 0x04, 0x77, 0xFD, + 0x69, 0x01, 0x62, 0xFF, 0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, + 0x00, 0x39, 0xFF, 0xE5, 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2, + 0x46, 0x24, 0xC8, 0x3A, 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17, + 0x01, 0xA8, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x0D, 0x00, 0xCC, 0xFF, + 0x5A, 0x00, 0xAF, 0xFF, 0xCA, 0xFF, 0xD8, 0x01, 0x1C, 0xF9, 0xB8, + 0x45, 0xDA, 0x11, 0x60, 0xF7, 0x33, 0x05, 0xE7, 0xFC, 0xA9, 0x01, + 0x4A, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, + 0xFF, 0xDF, 0x01, 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, + 0x1F, 0x40, 0x85, 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, + 0xFF, 0xF7, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xEB, 0xFF, 0x09, 0x00, + 0x54, 0x00, 0xA4, 0xFE, 0xC9, 0x03, 0xAA, 0xF5, 0x0C, 0x42, 0x56, + 0x19, 0x1E, 0xF5, 0x2B, 0x06, 0x7A, 0xFC, 0xD4, 0x01, 0x3A, 0xFF, + 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, + 0x01, 0xB4, 0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, + 0x86, 0xF7, 0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, + 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x06, 0x00, 0xC2, 0xFF, 0xE3, 0x00, + 0xAE, 0xFD, 0x52, 0x05, 0x44, 0xF3, 0x2A, 0x3D, 0x06, 0x21, 0x47, + 0xF3, 0xD8, 0x06, 0x3C, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, + 0xFD, 0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, + 0x91, 0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, + 0x00, 0xFE, 0xFF, 0x1B, 0x00, 0x88, 0xFF, 0x55, 0x01, 0xF2, 0xFC, + 0x67, 0x06, 0xE4, 0xF1, 0x44, 0x37, 0xAA, 0x28, 0x05, 0xF2, 0x27, + 0x07, 0x36, 0xFC, 0xDA, 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46, + 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE, + 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0xFD, + 0xFF, 0x2A, 0x00, 0x5D, 0xFF, 0xA7, 0x01, 0x75, 0xFC, 0x03, 0x07, + 0x7D, 0xF1, 0x8A, 0x30, 0xFF, 0x2F, 0x7E, 0xF1, 0x0A, 0x07, 0x6E, + 0xFC, 0xAC, 0x01, 0x5A, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0x1A, 0x00, + 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD, 0x05, + 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4, 0xFD, + 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33, + 0x00, 0x42, 0xFF, 0xD7, 0x01, 0x38, 0xFC, 0x29, 0x07, 0xF3, 0xF1, + 0x3E, 0x29, 0xC6, 0x36, 0xD4, 0xF1, 0x77, 0x06, 0xE6, 0xFC, 0x5C, + 0x01, 0x84, 0xFF, 0x1C, 0x00, 0xFE, 0xFF, 0x12, 0x00, 0xB6, 0xFF, + 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, 0xFB, 0x69, + 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, 0x81, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x37, + 0xFF, 0xE6, 0x01, 0x3A, 0xFC, 0xE2, 0x06, 0x28, 0xF3, 0x9E, 0x21, + 0xC0, 0x3C, 0x1F, 0xF3, 0x6C, 0x05, 0x9E, 0xFD, 0xED, 0x00, 0xBD, + 0xFF, 0x07, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD7, 0xFF, 0x3E, 0x00, + 0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, 0x99, 0x44, 0x68, + 0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, 0x01, 0x43, 0xFF, + 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD7, + 0x01, 0x73, 0xFC, 0x3B, 0x06, 0xF5, 0xF4, 0xED, 0x19, 0xB7, 0x41, + 0x71, 0xF5, 0xEB, 0x03, 0x90, 0xFE, 0x60, 0x00, 0x04, 0x00, 0xED, + 0xFF, 0x06, 0x00, 0x04, 0x00, 0xF5, 0xFF, 0xEF, 0xFF, 0x88, 0x00, + 0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D, 0x40, 0xFD, 0x1B, 0x6C, + 0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x48, 0xFF, 0xAD, 0x01, 0xDD, + 0xFC, 0x48, 0x05, 0x30, 0xF7, 0x6B, 0x12, 0x7D, 0x45, 0xCF, 0xF8, + 0x01, 0x02, 0xB2, 0xFF, 0xBD, 0xFF, 0x54, 0x00, 0xCE, 0xFF, 0x0C, + 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAC, 0xFF, 0x0E, 0x01, 0x66, 0xFD, + 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, 0xB0, 0x23, 0xC4, 0xF2, 0xFF, + 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x29, 0x00, 0x60, 0xFF, 0x6E, 0x01, 0x6B, 0xFD, 0x1D, + 0x04, 0xAF, 0xF9, 0x51, 0x0B, 0xEC, 0x47, 0x33, 0xFD, 0xC1, 0xFF, + 0xF7, 0x00, 0x0C, 0xFF, 0xAA, 0x00, 0xAD, 0xFF, 0x14, 0x00, 0xFE, + 0xFF, 0x21, 0x00, 0x77, 0xFF, 0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06, + 0xA6, 0xF1, 0x05, 0x35, 0x40, 0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42, + 0xFC, 0xCE, 0x01, 0x48, 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x20, 0x00, 0x7E, 0xFF, 0x21, 0x01, 0x12, 0xFE, 0xD1, 0x02, 0x47, + 0xFC, 0xD7, 0x04, 0xF0, 0x48, 0x8D, 0x02, 0x45, 0xFD, 0x4D, 0x02, + 0x56, 0xFE, 0x01, 0x01, 0x8B, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E, + 0x00, 0x52, 0xFF, 0xBC, 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1, + 0x11, 0x2E, 0x6B, 0x32, 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94, + 0x01, 0x67, 0xFF, 0x26, 0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA0, 0xFF, + 0xCC, 0x00, 0xC6, 0xFE, 0x79, 0x01, 0xD2, 0xFE, 0x26, 0xFF, 0x7C, + 0x48, 0xBE, 0x08, 0xAE, 0xFA, 0xA0, 0x03, 0xA9, 0xFD, 0x52, 0x01, + 0x6B, 0xFF, 0x25, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, + 0xFF, 0xE0, 0x01, 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, + 0xF2, 0x38, 0x2A, 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, + 0xFF, 0x16, 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xC2, 0xFF, 0x75, 0x00, + 0x7A, 0xFF, 0x2B, 0x00, 0x2D, 0x01, 0x61, 0xFA, 0x97, 0x46, 0xA0, + 0x0F, 0x20, 0xF8, 0xDA, 0x04, 0x10, 0xFD, 0x97, 0x01, 0x50, 0xFF, + 0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, + 0x01, 0x48, 0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, + 0xCF, 0xF3, 0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, + 0xFF, 0x03, 0x00, 0x08, 0x00, 0xE2, 0xFF, 0x21, 0x00, 0x23, 0x00, + 0xFA, 0xFE, 0x3A, 0x03, 0x9D, 0xF6, 0x50, 0x43, 0x00, 0x17, 0xC6, + 0xF5, 0xE6, 0x05, 0x97, 0xFC, 0xC9, 0x01, 0x3E, 0xFF, 0x34, 0x00, + 0xFE, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, + 0xFC, 0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, + 0x4D, 0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, + 0x00, 0x03, 0x00, 0xFE, 0xFF, 0xD7, 0xFF, 0xBA, 0x00, 0xF4, 0xFD, + 0xE5, 0x04, 0xE4, 0xF3, 0xCA, 0x3E, 0xA7, 0x1E, 0xCA, 0xF3, 0xAC, + 0x06, 0x4A, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6, + 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01, + 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0xFF, + 0xFF, 0x15, 0x00, 0x98, 0xFF, 0x35, 0x01, 0x25, 0xFD, 0x1E, 0x06, + 0x35, 0xF2, 0x2E, 0x39, 0x55, 0x26, 0x56, 0xF2, 0x1A, 0x07, 0x31, + 0xFC, 0xE1, 0x01, 0x3C, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03, 0x94, + 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C, 0x01, + 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26, + 0x00, 0x69, 0xFF, 0x91, 0x01, 0x94, 0xFC, 0xE0, 0x06, 0x84, 0xF1, + 0xAF, 0x32, 0xCA, 0x2D, 0x92, 0xF1, 0x1F, 0x07, 0x56, 0xFC, 0xBE, + 0x01, 0x51, 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8A, 0xFF, + 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, 0x02, 0xF2, + 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, 0x1E, 0x01, + 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x49, + 0xFF, 0xCC, 0x01, 0x44, 0xFC, 0x29, 0x07, 0xB9, 0xF1, 0x89, 0x2B, + 0xC3, 0x34, 0xA0, 0xF1, 0xB1, 0x06, 0xBA, 0xFC, 0x79, 0x01, 0x76, + 0xFF, 0x21, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAC, 0xFF, 0xAE, 0x00, + 0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, 0xFD, 0x47, 0x0E, + 0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, 0x01, 0x61, 0xFF, + 0x28, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x33, 0xFC, 0x03, 0x07, 0xB7, 0xF2, 0xFC, 0x23, 0x03, 0x3B, + 0x9E, 0xF2, 0xCB, 0x05, 0x5F, 0xFD, 0x12, 0x01, 0xAA, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF, 0x57, 0x00, 0xB6, 0xFF, + 0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B, 0x45, 0x22, 0x12, 0x48, + 0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01, 0x49, 0xFF, 0x30, 0x00, + 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, 0x5C, + 0xFC, 0x78, 0x06, 0x5A, 0xF4, 0x49, 0x1C, 0x4E, 0x40, 0x9E, 0xF4, + 0x6D, 0x04, 0x3F, 0xFE, 0x8E, 0x00, 0xED, 0xFF, 0xF6, 0xFF, 0x04, + 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x5A, 0x00, 0x9A, 0xFE, + 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, 0xA1, 0x19, 0x09, 0xF5, 0x33, + 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, + 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBC, 0x01, 0xB8, 0xFC, 0x9A, + 0x05, 0x77, 0xF6, 0xB1, 0x14, 0x77, 0x44, 0xA9, 0xF7, 0xA2, 0x02, + 0x54, 0xFF, 0xF1, 0xFF, 0x3A, 0x00, 0xD8, 0xFF, 0x0A, 0x00, 0x01, + 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05, + 0x31, 0xF3, 0xF6, 0x3C, 0x52, 0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B, + 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x2B, 0x00, 0x58, 0xFF, 0x83, 0x01, 0x3C, 0xFD, 0x7E, 0x04, 0xE6, + 0xF8, 0x72, 0x0D, 0x52, 0x47, 0xBE, 0xFB, 0x7A, 0x00, 0x90, 0x00, + 0x43, 0xFF, 0x8F, 0x00, 0xB7, 0xFF, 0x11, 0x00, 0xFE, 0xFF, 0x1C, + 0x00, 0x86, 0xFF, 0x59, 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1, + 0x04, 0x37, 0xF3, 0x28, 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8, + 0x01, 0x41, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x23, 0x00, + 0x74, 0xFF, 0x3A, 0x01, 0xDD, 0xFD, 0x39, 0x03, 0x7B, 0xFB, 0xC1, + 0x06, 0xC7, 0x48, 0xCF, 0x00, 0x0D, 0xFE, 0xE3, 0x01, 0x8E, 0xFE, + 0xE7, 0x00, 0x95, 0xFF, 0x1A, 0x00, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, + 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, + 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, + 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x1A, 0x00, 0x95, 0xFF, 0xE7, 0x00, + 0x8E, 0xFE, 0xE3, 0x01, 0x0D, 0xFE, 0xCF, 0x00, 0xC7, 0x48, 0xC1, + 0x06, 0x7B, 0xFB, 0x39, 0x03, 0xDD, 0xFD, 0x3A, 0x01, 0x74, 0xFF, + 0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, + 0x01, 0x37, 0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, + 0xDC, 0xF1, 0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, + 0x00, 0xFE, 0xFF, 0x11, 0x00, 0xB7, 0xFF, 0x8F, 0x00, 0x43, 0xFF, + 0x90, 0x00, 0x7A, 0x00, 0xBE, 0xFB, 0x52, 0x47, 0x72, 0x0D, 0xE6, + 0xF8, 0x7E, 0x04, 0x3C, 0xFD, 0x83, 0x01, 0x58, 0xFF, 0x2B, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, + 0xFC, 0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, + 0x5F, 0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, + 0x00, 0x0A, 0x00, 0xD8, 0xFF, 0x3A, 0x00, 0xF1, 0xFF, 0x54, 0xFF, + 0xA2, 0x02, 0xA9, 0xF7, 0x77, 0x44, 0xB1, 0x14, 0x77, 0xF6, 0x9A, + 0x05, 0xB8, 0xFC, 0xBC, 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, + 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33, + 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03, + 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0x04, + 0x00, 0xF6, 0xFF, 0xED, 0xFF, 0x8E, 0x00, 0x3F, 0xFE, 0x6D, 0x04, + 0x9E, 0xF4, 0x4E, 0x40, 0x49, 0x1C, 0x5A, 0xF4, 0x78, 0x06, 0x5C, + 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, + 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05, 0x48, + 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE, 0xFF, + 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0E, + 0x00, 0xAA, 0xFF, 0x12, 0x01, 0x5F, 0xFD, 0xCB, 0x05, 0x9E, 0xF2, + 0x03, 0x3B, 0xFC, 0x23, 0xB7, 0xF2, 0x03, 0x07, 0x33, 0xFC, 0xE5, + 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00, + 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, 0xF9, 0x0E, + 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, 0x05, 0xFF, + 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0xFE, 0xFF, 0x21, 0x00, 0x76, + 0xFF, 0x79, 0x01, 0xBA, 0xFC, 0xB1, 0x06, 0xA0, 0xF1, 0xC3, 0x34, + 0x89, 0x2B, 0xB9, 0xF1, 0x29, 0x07, 0x44, 0xFC, 0xCC, 0x01, 0x49, + 0xFF, 0x31, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x7F, 0xFF, + 0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, 0x9B, 0x04, 0xF2, + 0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, 0xFE, 0x04, 0x01, + 0x8A, 0xFF, 0x1D, 0x00, 0xFD, 0xFF, 0x2E, 0x00, 0x51, 0xFF, 0xBE, + 0x01, 0x56, 0xFC, 0x1F, 0x07, 0x92, 0xF1, 0xCA, 0x2D, 0xAF, 0x32, + 0x84, 0xF1, 0xE0, 0x06, 0x94, 0xFC, 0x91, 0x01, 0x69, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0x17, 0x00, 0xA1, 0xFF, 0xC9, 0x00, 0xCD, 0xFE, + 0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70, 0x48, 0xFF, 0x08, 0x94, + 0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01, 0x6A, 0xFF, 0x26, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE1, 0x01, 0x31, + 0xFC, 0x1A, 0x07, 0x56, 0xF2, 0x55, 0x26, 0x2E, 0x39, 0x35, 0xF2, + 0x1E, 0x06, 0x25, 0xFD, 0x35, 0x01, 0x98, 0xFF, 0x15, 0x00, 0xFF, + 0xFF, 0x0F, 0x00, 0xC3, 0xFF, 0x71, 0x00, 0x81, 0xFF, 0x1F, 0x00, + 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, 0xE7, 0x0F, 0x08, 0xF8, 0xE6, + 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, + 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x4A, 0xFC, 0xAC, + 0x06, 0xCA, 0xF3, 0xA7, 0x1E, 0xCA, 0x3E, 0xE4, 0xF3, 0xE5, 0x04, + 0xF4, 0xFD, 0xBA, 0x00, 0xD7, 0xFF, 0xFE, 0xFF, 0x03, 0x00, 0x08, + 0x00, 0xE3, 0xFF, 0x1E, 0x00, 0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03, + 0x7D, 0xF6, 0x2A, 0x43, 0x4B, 0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93, + 0xFC, 0xCB, 0x01, 0x3D, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFE, 0xFF, + 0x34, 0x00, 0x3E, 0xFF, 0xC9, 0x01, 0x97, 0xFC, 0xE6, 0x05, 0xC6, + 0xF5, 0x00, 0x17, 0x50, 0x43, 0x9D, 0xF6, 0x3A, 0x03, 0xFA, 0xFE, + 0x23, 0x00, 0x21, 0x00, 0xE2, 0xFF, 0x08, 0x00, 0x03, 0x00, 0xFF, + 0xFF, 0xD4, 0xFF, 0xBF, 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3, + 0x98, 0x3E, 0xF3, 0x1E, 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, + 0x50, 0xFF, 0x97, 0x01, 0x10, 0xFD, 0xDA, 0x04, 0x20, 0xF8, 0xA0, + 0x0F, 0x97, 0x46, 0x61, 0xFA, 0x2D, 0x01, 0x2B, 0x00, 0x7A, 0xFF, + 0x75, 0x00, 0xC2, 0xFF, 0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x96, + 0xFF, 0x39, 0x01, 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, + 0xA0, 0x26, 0x4B, 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, + 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6B, 0xFF, + 0x52, 0x01, 0xA9, 0xFD, 0xA0, 0x03, 0xAE, 0xFA, 0xBE, 0x08, 0x7C, + 0x48, 0x26, 0xFF, 0xD2, 0xFE, 0x79, 0x01, 0xC6, 0xFE, 0xCC, 0x00, + 0xA0, 0xFF, 0x17, 0x00, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, + 0x01, 0x90, 0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, + 0x8E, 0xF1, 0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, + 0x00, 0xFD, 0xFF, 0x1D, 0x00, 0x8B, 0xFF, 0x01, 0x01, 0x56, 0xFE, + 0x4D, 0x02, 0x45, 0xFD, 0x8D, 0x02, 0xF0, 0x48, 0xD7, 0x04, 0x47, + 0xFC, 0xD1, 0x02, 0x12, 0xFE, 0x21, 0x01, 0x7E, 0xFF, 0x20, 0x00, + 0x00, 0x00, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, + 0xFC, 0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, + 0xAB, 0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, + 0xFF, 0x14, 0x00, 0xAD, 0xFF, 0xAA, 0x00, 0x0C, 0xFF, 0xF7, 0x00, + 0xC1, 0xFF, 0x33, 0xFD, 0xEC, 0x47, 0x51, 0x0B, 0xAF, 0xF9, 0x1D, + 0x04, 0x6B, 0xFD, 0x6E, 0x01, 0x60, 0xFF, 0x29, 0x00, 0x00, 0x00, + 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF, + 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05, + 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x0C, + 0x00, 0xCE, 0xFF, 0x54, 0x00, 0xBD, 0xFF, 0xB2, 0xFF, 0x01, 0x02, + 0xCF, 0xF8, 0x7D, 0x45, 0x6B, 0x12, 0x30, 0xF7, 0x48, 0x05, 0xDD, + 0xFC, 0xAD, 0x01, 0x48, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, + 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06, 0x6C, + 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49, 0xFE, + 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0x06, 0x00, 0xED, + 0xFF, 0x04, 0x00, 0x60, 0x00, 0x90, 0xFE, 0xEB, 0x03, 0x71, 0xF5, + 0xB7, 0x41, 0xED, 0x19, 0xF5, 0xF4, 0x3B, 0x06, 0x73, 0xFC, 0xD7, + 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, + 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, 0xF6, 0x68, + 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, 0xEA, 0xFF, + 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0x07, 0x00, 0xBD, + 0xFF, 0xED, 0x00, 0x9E, 0xFD, 0x6C, 0x05, 0x1F, 0xF3, 0xC0, 0x3C, + 0x9E, 0x21, 0x28, 0xF3, 0xE2, 0x06, 0x3A, 0xFC, 0xE6, 0x01, 0x37, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x59, 0xFF, + 0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, 0x2D, 0x0D, 0x69, + 0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, 0xFF, 0x93, 0x00, + 0xB6, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1C, 0x00, 0x84, 0xFF, 0x5C, + 0x01, 0xE6, 0xFC, 0x77, 0x06, 0xD4, 0xF1, 0xC6, 0x36, 0x3E, 0x29, + 0xF3, 0xF1, 0x29, 0x07, 0x38, 0xFC, 0xD7, 0x01, 0x42, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x76, 0xFF, 0x37, 0x01, + 0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83, 0x06, 0xCE, 0x48, 0x05, + 0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE, 0xEA, 0x00, 0x94, 0xFF, + 0x1A, 0x00, 0xFD, 0xFF, 0x2B, 0x00, 0x5A, 0xFF, 0xAC, 0x01, 0x6E, + 0xFC, 0x0A, 0x07, 0x7E, 0xF1, 0xFF, 0x2F, 0x8A, 0x30, 0x7D, 0xF1, + 0x03, 0x07, 0x75, 0xFC, 0xA7, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0xFD, + 0xFF, 0x1A, 0x00, 0x96, 0xFF, 0xE3, 0x00, 0x95, 0xFE, 0xD5, 0x01, + 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, 0x00, 0x07, 0x61, 0xFB, 0x46, + 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, 0xFF, 0x23, 0x00, 0x00, 0x00, + 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xDA, 0x01, 0x36, 0xFC, 0x27, + 0x07, 0x05, 0xF2, 0xAA, 0x28, 0x44, 0x37, 0xE4, 0xF1, 0x67, 0x06, + 0xF2, 0xFC, 0x55, 0x01, 0x88, 0xFF, 0x1B, 0x00, 0xFE, 0xFF, 0x11, + 0x00, 0xB9, 0xFF, 0x8C, 0x00, 0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00, + 0x91, 0xFB, 0x3D, 0x47, 0xB7, 0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36, + 0xFD, 0x86, 0x01, 0x57, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3C, 0xFC, 0xD8, 0x06, 0x47, + 0xF3, 0x06, 0x21, 0x2A, 0x3D, 0x44, 0xF3, 0x52, 0x05, 0xAE, 0xFD, + 0xE3, 0x00, 0xC2, 0xFF, 0x06, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xD9, + 0xFF, 0x37, 0x00, 0xF7, 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7, + 0x53, 0x44, 0xFB, 0x14, 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE, + 0x01, 0x42, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD4, 0x01, 0x7A, 0xFC, 0x2B, 0x06, 0x1E, 0xF5, 0x56, + 0x19, 0x0C, 0x42, 0xAA, 0xF5, 0xC9, 0x03, 0xA4, 0xFE, 0x54, 0x00, + 0x09, 0x00, 0xEB, 0xFF, 0x06, 0x00, 0x04, 0x00, 0xF7, 0xFF, 0xEA, + 0xFF, 0x93, 0x00, 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, + 0x94, 0x1C, 0x47, 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, + 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xA9, 0x01, 0xE7, 0xFC, 0x33, 0x05, 0x60, 0xF7, 0xDA, 0x11, 0xB8, + 0x45, 0x1C, 0xF9, 0xD8, 0x01, 0xCA, 0xFF, 0xAF, 0xFF, 0x5A, 0x00, + 0xCC, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, + 0x01, 0x57, 0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, + 0xAA, 0xF2, 0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x62, 0xFF, 0x69, 0x01, + 0x77, 0xFD, 0x04, 0x04, 0xE2, 0xF9, 0xCB, 0x0A, 0x0D, 0x48, 0x94, + 0xFD, 0x92, 0xFF, 0x10, 0x01, 0xFE, 0xFE, 0xB1, 0x00, 0xAA, 0xFF, + 0x15, 0x00, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, + 0xFC, 0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, + 0x29, 0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x81, 0xFF, 0x1B, 0x01, 0x20, 0xFE, + 0xB6, 0x02, 0x7A, 0xFC, 0x5F, 0x04, 0xF4, 0x48, 0xFF, 0x02, 0x13, + 0xFD, 0x67, 0x02, 0x49, 0xFE, 0x07, 0x01, 0x88, 0xFF, 0x1D, 0x00, + 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21, + 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06, + 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16, + 0x00, 0xA2, 0xFF, 0xC5, 0x00, 0xD4, 0xFE, 0x5F, 0x01, 0x03, 0xFF, + 0xBF, 0xFE, 0x63, 0x48, 0x40, 0x09, 0x7B, 0xFA, 0xB9, 0x03, 0x9D, + 0xFD, 0x58, 0x01, 0x69, 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07, 0x61, + 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C, 0xFD, + 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC4, + 0xFF, 0x6E, 0x00, 0x87, 0xFF, 0x13, 0x00, 0x58, 0x01, 0x0D, 0xFA, + 0x61, 0x46, 0x2D, 0x10, 0xF0, 0xF7, 0xF1, 0x04, 0x05, 0xFD, 0x9C, + 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, 0xF3, 0x5B, + 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, 0xB4, 0x00, + 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0x08, 0x00, 0xE4, 0xFF, 0x1B, + 0x00, 0x30, 0x00, 0xE4, 0xFE, 0x5F, 0x03, 0x5E, 0xF6, 0x02, 0x43, + 0x96, 0x17, 0x9B, 0xF5, 0xF8, 0x05, 0x8F, 0xFC, 0xCC, 0x01, 0x3D, + 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3E, 0xFF, + 0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, 0xB6, 0x16, 0x77, + 0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, 0x00, 0x25, 0x00, + 0xE1, 0xFF, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xC4, + 0x00, 0xE2, 0xFD, 0x01, 0x05, 0xBA, 0xF3, 0x64, 0x3E, 0x3F, 0x1F, + 0xA8, 0xF3, 0xB8, 0x06, 0x46, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x51, 0xFF, 0x95, 0x01, + 0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59, 0x0F, 0xAF, 0x46, 0x8B, + 0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF, 0x78, 0x00, 0xC0, 0xFF, + 0x0F, 0x00, 0xFF, 0xFF, 0x16, 0x00, 0x94, 0xFF, 0x3D, 0x01, 0x18, + 0xFD, 0x32, 0x06, 0x1F, 0xF2, 0xB5, 0x38, 0xEB, 0x26, 0x40, 0xF2, + 0x1E, 0x07, 0x32, 0xFC, 0xDF, 0x01, 0x3D, 0xFF, 0x35, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x25, 0x00, 0x6C, 0xFF, 0x4F, 0x01, 0xB0, 0xFD, + 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, 0x86, 0x48, 0x5A, 0xFF, 0xBA, + 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, 0x00, 0x9E, 0xFF, 0x17, 0x00, + 0xFD, 0xFF, 0x27, 0x00, 0x66, 0xFF, 0x97, 0x01, 0x8C, 0xFC, 0xEA, + 0x06, 0x80, 0xF1, 0x26, 0x32, 0x58, 0x2E, 0x8B, 0xF1, 0x1B, 0x07, + 0x5B, 0xFC, 0xBA, 0x01, 0x53, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C, + 0x00, 0x8C, 0xFF, 0xFE, 0x00, 0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD, + 0x54, 0x02, 0xEC, 0x48, 0x13, 0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C, + 0xFE, 0x24, 0x01, 0x7D, 0xFF, 0x20, 0x00, 0x00, 0x00, 0xFD, 0xFF, + 0x31, 0x00, 0x47, 0xFF, 0xCF, 0x01, 0x40, 0xFC, 0x2A, 0x07, 0xC6, + 0xF1, 0xF7, 0x2A, 0x46, 0x35, 0xAB, 0xF1, 0xA4, 0x06, 0xC4, 0xFC, + 0x72, 0x01, 0x79, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0x14, 0x00, 0xAE, + 0xFF, 0xA7, 0x00, 0x12, 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD, + 0xDC, 0x47, 0x95, 0x0B, 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71, + 0x01, 0x5F, 0xFF, 0x29, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x34, 0xFC, 0xFB, 0x06, 0xD2, 0xF2, 0x64, + 0x23, 0x73, 0x3B, 0xBC, 0xF2, 0xB4, 0x05, 0x6E, 0xFD, 0x09, 0x01, + 0xAF, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51, + 0x00, 0xC3, 0xFF, 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, + 0xB2, 0x12, 0x19, 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, + 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, + 0xDD, 0x01, 0x62, 0xFC, 0x69, 0x06, 0x7F, 0xF4, 0xB2, 0x1B, 0xAB, + 0x40, 0xD0, 0xF4, 0x4E, 0x04, 0x53, 0xFE, 0x83, 0x00, 0xF2, 0xFF, + 0xF4, 0xFF, 0x05, 0x00, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, + 0x00, 0x85, 0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, + 0xE1, 0xF4, 0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xB9, 0x01, + 0xC1, 0xFC, 0x86, 0x05, 0xA5, 0xF6, 0x1E, 0x14, 0xBA, 0x44, 0xF0, + 0xF7, 0x7B, 0x02, 0x6B, 0xFF, 0xE4, 0xFF, 0x41, 0x00, 0xD6, 0xFF, + 0x0B, 0x00, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, + 0xFD, 0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, + 0xE6, 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0x00, 0x00, 0x2B, 0x00, 0x5A, 0xFF, 0x7E, 0x01, 0x48, 0xFD, + 0x66, 0x04, 0x18, 0xF9, 0xE8, 0x0C, 0x7C, 0x47, 0x19, 0xFC, 0x4D, + 0x00, 0xA9, 0x00, 0x35, 0xFF, 0x96, 0x00, 0xB5, 0xFF, 0x12, 0x00, + 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F, + 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07, + 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x22, 0x00, 0x77, 0xFF, 0x34, 0x01, 0xEA, 0xFD, 0x1F, 0x03, + 0xAE, 0xFB, 0x45, 0x06, 0xD5, 0x48, 0x3C, 0x01, 0xDC, 0xFD, 0xFD, + 0x01, 0x80, 0xFE, 0xED, 0x00, 0x93, 0xFF, 0x1B, 0x00, 0xFD, 0xFF, + 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07, 0x80, + 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78, 0xFC, + 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x98, + 0xFF, 0xE0, 0x00, 0x9C, 0xFE, 0xC8, 0x01, 0x3F, 0xFE, 0x62, 0x00, + 0xB8, 0x48, 0x3F, 0x07, 0x47, 0xFB, 0x53, 0x03, 0xD0, 0xFD, 0x40, + 0x01, 0x72, 0xFF, 0x23, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, + 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, 0xF2, 0x60, + 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, 0x51, 0x01, + 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0x11, 0x00, 0xBA, 0xFF, 0x89, + 0x00, 0x51, 0xFF, 0x77, 0x00, 0xA7, 0x00, 0x64, 0xFB, 0x26, 0x47, + 0xFC, 0x0D, 0xB4, 0xF8, 0x95, 0x04, 0x31, 0xFD, 0x88, 0x01, 0x56, + 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, 0xBA, 0x20, 0x61, + 0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, 0x00, 0xC5, 0xFF, + 0x05, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDB, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0x3D, 0xFF, 0xC9, 0x02, 0x64, 0xF7, 0x2F, 0x44, 0x44, 0x15, + 0x4A, 0xF6, 0xAD, 0x05, 0xAF, 0xFC, 0xC0, 0x01, 0x41, 0xFF, 0x32, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD3, 0x01, + 0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C, 0x19, 0x38, 0x42, 0xC7, + 0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00, 0x0C, 0x00, 0xEA, 0xFF, + 0x06, 0x00, 0x04, 0x00, 0xF8, 0xFF, 0xE7, 0xFF, 0x99, 0x00, 0x2C, + 0xFE, 0x8C, 0x04, 0x6D, 0xF4, 0xF0, 0x3F, 0xE0, 0x1C, 0x34, 0xF4, + 0x85, 0x06, 0x57, 0xFC, 0xE0, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, 0xFF, 0xA7, 0x01, 0xEC, 0xFC, + 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, 0xD7, 0x45, 0x43, 0xF9, 0xC3, + 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, 0x00, 0xCB, 0xFF, 0x0D, 0x00, + 0x00, 0x00, 0x10, 0x00, 0xA6, 0xFF, 0x1B, 0x01, 0x50, 0xFD, 0xE1, + 0x05, 0x82, 0xF2, 0x8F, 0x3A, 0x92, 0x24, 0x9D, 0xF2, 0x09, 0x07, + 0x32, 0xFC, 0xE4, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, + 0x00, 0x28, 0x00, 0x63, 0xFF, 0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03, + 0xFB, 0xF9, 0x89, 0x0A, 0x1D, 0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D, + 0x01, 0xF7, 0xFE, 0xB4, 0x00, 0xA9, 0xFF, 0x15, 0x00, 0xFE, 0xFF, + 0x23, 0x00, 0x72, 0xFF, 0x7F, 0x01, 0xB0, 0xFC, 0xBE, 0x06, 0x97, + 0xF1, 0x3F, 0x34, 0x19, 0x2C, 0xAD, 0xF1, 0x28, 0x07, 0x48, 0xFC, + 0xC9, 0x01, 0x4B, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F, + 0x00, 0x82, 0xFF, 0x18, 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC, + 0x24, 0x04, 0xF5, 0x48, 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42, + 0xFE, 0x0B, 0x01, 0x87, 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00, + 0x4F, 0xFF, 0xC2, 0x01, 0x51, 0xFC, 0x23, 0x07, 0x9A, 0xF1, 0x3A, + 0x2D, 0x35, 0x33, 0x89, 0xF1, 0xD5, 0x06, 0x9D, 0xFC, 0x8B, 0x01, + 0x6C, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0x16, 0x00, 0xA4, 0xFF, 0xC2, + 0x00, 0xDB, 0xFE, 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, + 0x81, 0x09, 0x61, 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, + 0xFF, 0x26, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, + 0xE2, 0x01, 0x31, 0xFC, 0x15, 0x07, 0x6D, 0xF2, 0xBF, 0x25, 0xA5, + 0x39, 0x4D, 0xF2, 0x0B, 0x06, 0x33, 0xFD, 0x2D, 0x01, 0x9D, 0xFF, + 0x13, 0x00, 0xFF, 0xFF, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, + 0xFF, 0x06, 0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, + 0xD7, 0xF7, 0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, + 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, + 0x4E, 0xFC, 0xA0, 0x06, 0xED, 0xF3, 0x0F, 0x1E, 0x2D, 0x3F, 0x10, + 0xF4, 0xC8, 0x04, 0x07, 0xFE, 0xAF, 0x00, 0xDC, 0xFF, 0xFC, 0xFF, + 0x03, 0x00, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, + 0xFE, 0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, + 0x00, 0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, + 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC6, 0x01, 0x9F, 0xFC, + 0xD3, 0x05, 0xF1, 0xF5, 0x6C, 0x16, 0x9E, 0x43, 0xDD, 0xF6, 0x15, + 0x03, 0x10, 0xFF, 0x17, 0x00, 0x28, 0x00, 0xDF, 0xFF, 0x09, 0x00, + 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F, + 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06, + 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x2D, 0x00, 0x52, 0xFF, 0x92, 0x01, 0x1B, 0xFD, 0xC4, 0x04, + 0x51, 0xF8, 0x13, 0x0F, 0xC8, 0x46, 0xB6, 0xFA, 0x01, 0x01, 0x44, + 0x00, 0x6C, 0xFF, 0x7B, 0x00, 0xBF, 0xFF, 0x10, 0x00, 0xFF, 0xFF, + 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06, 0x14, + 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33, 0xFC, + 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x25, + 0x00, 0x6D, 0xFF, 0x4C, 0x01, 0xB6, 0xFD, 0x86, 0x03, 0xE1, 0xFA, + 0x3D, 0x08, 0x92, 0x48, 0x8E, 0xFF, 0xA1, 0xFE, 0x93, 0x01, 0xB8, + 0xFE, 0xD3, 0x00, 0x9D, 0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00, + 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, 0xF1, 0xE3, + 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, 0xB7, 0x01, + 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0x1C, 0x00, 0x8D, 0xFF, 0xFA, + 0x00, 0x64, 0xFE, 0x32, 0x02, 0x78, 0xFD, 0x1B, 0x02, 0xEA, 0x48, + 0x50, 0x05, 0x14, 0xFC, 0xEB, 0x02, 0x05, 0xFE, 0x27, 0x01, 0x7C, + 0xFF, 0x21, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x46, 0xFF, + 0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, 0xAE, 0x2A, 0x86, + 0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, 0x01, 0x7B, 0xFF, + 0x20, 0x00, 0xFE, 0xFF, 0x13, 0x00, 0xAF, 0xFF, 0xA4, 0x00, 0x19, + 0xFF, 0xDD, 0x00, 0xF0, 0xFF, 0xD4, 0xFC, 0xC9, 0x47, 0xD8, 0x0B, + 0x7C, 0xF9, 0x35, 0x04, 0x5F, 0xFD, 0x74, 0x01, 0x5E, 0xFF, 0x29, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE6, 0x01, + 0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18, 0x23, 0xAB, 0x3B, 0xCC, + 0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01, 0xB1, 0xFF, 0x0C, 0x00, + 0x00, 0x00, 0x0C, 0x00, 0xD1, 0xFF, 0x4E, 0x00, 0xCA, 0xFF, 0x9A, + 0xFF, 0x2A, 0x02, 0x83, 0xF8, 0x3F, 0x45, 0xFB, 0x12, 0x01, 0xF7, + 0x5D, 0x05, 0xD3, 0xFC, 0xB1, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, + 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xDC, 0x01, 0x64, 0xFC, + 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, 0xD9, 0x40, 0xEA, 0xF4, 0x3E, + 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, 0xFF, 0xF3, 0xFF, 0x05, 0x00, + 0x05, 0x00, 0xEF, 0xFF, 0xFE, 0xFF, 0x6C, 0x00, 0x7B, 0xFE, 0x0C, + 0x04, 0x3A, 0xF5, 0x5F, 0x41, 0x83, 0x1A, 0xCD, 0xF4, 0x4B, 0x06, + 0x6D, 0xFC, 0xD9, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, + 0xFF, 0x31, 0x00, 0x44, 0xFF, 0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05, + 0xBC, 0xF6, 0xD5, 0x13, 0xDC, 0x44, 0x14, 0xF8, 0x67, 0x02, 0x77, + 0xFF, 0xDD, 0xFF, 0x44, 0x00, 0xD5, 0xFF, 0x0B, 0x00, 0x01, 0x00, + 0x09, 0x00, 0xB8, 0xFF, 0xF6, 0x00, 0x8D, 0xFD, 0x84, 0x05, 0xFD, + 0xF2, 0x52, 0x3C, 0x35, 0x22, 0x0B, 0xF3, 0xEB, 0x06, 0x37, 0xFC, + 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A, + 0x00, 0x5B, 0xFF, 0x7C, 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9, + 0xA4, 0x0C, 0x90, 0x47, 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E, + 0xFF, 0x99, 0x00, 0xB3, 0xFF, 0x12, 0x00, 0xFE, 0xFF, 0x1E, 0x00, + 0x80, 0xFF, 0x64, 0x01, 0xDA, 0xFC, 0x87, 0x06, 0xC5, 0xF1, 0x46, + 0x36, 0xD1, 0x29, 0xE3, 0xF1, 0x2A, 0x07, 0x3A, 0xFC, 0xD5, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x22, 0x00, 0x78, + 0xFF, 0x31, 0x01, 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, + 0xDB, 0x48, 0x73, 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, + 0x00, 0x91, 0xFF, 0x1B, 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x58, 0xFF, + 0xB1, 0x01, 0x67, 0xFC, 0x10, 0x07, 0x81, 0xF1, 0x73, 0x2F, 0x15, + 0x31, 0x7C, 0xF1, 0xFB, 0x06, 0x7C, 0xFC, 0xA2, 0x01, 0x60, 0xFF, + 0x29, 0x00, 0xFD, 0xFF, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, + 0xFE, 0xBB, 0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, + 0x2E, 0xFB, 0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDC, 0x01, + 0x34, 0xFC, 0x25, 0x07, 0x18, 0xF2, 0x15, 0x28, 0xBF, 0x37, 0xF7, + 0xF1, 0x56, 0x06, 0xFE, 0xFC, 0x4D, 0x01, 0x8C, 0xFF, 0x19, 0x00, + 0xFF, 0xFF, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, + 0x00, 0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, + 0xA1, 0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3F, 0xFC, + 0xCE, 0x06, 0x66, 0xF3, 0x6F, 0x20, 0x96, 0x3D, 0x69, 0xF3, 0x38, + 0x05, 0xBF, 0xFD, 0xD9, 0x00, 0xC7, 0xFF, 0x04, 0x00, 0x02, 0x00, + 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC, + 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05, + 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xD2, 0x01, 0x81, 0xFC, 0x1A, 0x06, + 0x47, 0xF5, 0xC1, 0x18, 0x60, 0x42, 0xE4, 0xF5, 0xA6, 0x03, 0xB9, + 0xFE, 0x48, 0x00, 0x0F, 0x00, 0xE9, 0xFF, 0x07, 0x00, 0x04, 0x00, + 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04, 0x55, + 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55, 0xFC, + 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F, + 0x00, 0x4B, 0xFF, 0xA4, 0x01, 0xF1, 0xFC, 0x1D, 0x05, 0x8F, 0xF7, + 0x4A, 0x11, 0xF2, 0x45, 0x6B, 0xF9, 0xAE, 0x01, 0xE2, 0xFF, 0xA2, + 0xFF, 0x61, 0x00, 0xC9, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x11, 0x00, + 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, 0xF2, 0x54, + 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, 0xE4, 0x01, + 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x64, + 0xFF, 0x63, 0x01, 0x84, 0xFD, 0xEB, 0x03, 0x14, 0xFA, 0x47, 0x0A, + 0x2C, 0x48, 0xF6, 0xFD, 0x63, 0xFF, 0x2B, 0x01, 0xF0, 0xFE, 0xB8, + 0x00, 0xA8, 0xFF, 0x15, 0x00, 0xFE, 0xFF, 0x23, 0x00, 0x71, 0xFF, + 0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, 0xFD, 0x33, 0x62, + 0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, 0x01, 0x4C, 0xFF, + 0x30, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x1F, 0x00, 0x83, 0xFF, 0x14, + 0x01, 0x2D, 0xFE, 0x9C, 0x02, 0xAD, 0xFC, 0xE9, 0x03, 0xF6, 0x48, + 0x73, 0x03, 0xE0, 0xFC, 0x82, 0x02, 0x3B, 0xFE, 0x0E, 0x01, 0x86, + 0xFF, 0x1E, 0x00, 0xFD, 0xFF, 0x2F, 0x00, 0x4E, 0xFF, 0xC3, 0x01, + 0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2, 0x2C, 0x78, 0x33, 0x8C, + 0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01, 0x6D, 0xFF, 0x24, 0x00, + 0xFD, 0xFF, 0x16, 0x00, 0xA5, 0xFF, 0xBE, 0x00, 0xE2, 0xFE, 0x45, + 0x01, 0x33, 0xFF, 0x5A, 0xFE, 0x48, 0x48, 0xC3, 0x09, 0x47, 0xFA, + 0xD2, 0x03, 0x90, 0xFD, 0x5E, 0x01, 0x66, 0xFF, 0x27, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE3, 0x01, 0x31, 0xFC, + 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, 0xDF, 0x39, 0x5A, 0xF2, 0x00, + 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, 0xFF, 0x13, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0xC7, 0xFF, 0x68, 0x00, 0x95, 0xFF, 0xFA, 0xFF, 0x83, + 0x01, 0xBB, 0xF9, 0x2B, 0x46, 0xBB, 0x10, 0xBF, 0xF7, 0x07, 0x05, + 0xFB, 0xFC, 0xA0, 0x01, 0x4D, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0xFE, + 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06, + 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, 0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10, + 0xFE, 0xA9, 0x00, 0xDF, 0xFF, 0xFB, 0xFF, 0x03, 0x00, 0x07, 0x00, + 0xE6, 0xFF, 0x15, 0x00, 0x3C, 0x00, 0xCF, 0xFE, 0x83, 0x03, 0x20, + 0xF6, 0xB2, 0x42, 0x2B, 0x18, 0x71, 0xF5, 0x09, 0x06, 0x88, 0xFC, + 0xCF, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x33, + 0x00, 0x3F, 0xFF, 0xC5, 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6, + 0x22, 0x16, 0xC3, 0x43, 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11, + 0x00, 0x2B, 0x00, 0xDE, 0xFF, 0x09, 0x00, 0x02, 0x00, 0x02, 0x00, + 0xCC, 0xFF, 0xCE, 0x00, 0xD1, 0xFD, 0x1D, 0x05, 0x91, 0xF3, 0xFE, + 0x3D, 0xD7, 0x1F, 0x87, 0xF3, 0xC3, 0x06, 0x42, 0xFC, 0xE5, 0x01, + 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, 0x53, + 0xFF, 0x90, 0x01, 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, + 0xE1, 0x46, 0xE1, 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, + 0x00, 0xBE, 0xFF, 0x10, 0x00, 0xFF, 0xFF, 0x18, 0x00, 0x90, 0xFF, + 0x45, 0x01, 0x0B, 0xFD, 0x44, 0x06, 0x0A, 0xF2, 0x3B, 0x38, 0x80, + 0x27, 0x2B, 0xF2, 0x22, 0x07, 0x33, 0xFC, 0xDE, 0x01, 0x3E, 0xFF, + 0x34, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, + 0x01, 0xBC, 0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, + 0xC3, 0xFF, 0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, + 0xFF, 0x18, 0x00, 0xFD, 0xFF, 0x28, 0x00, 0x63, 0xFF, 0x9D, 0x01, + 0x84, 0xFC, 0xF3, 0x06, 0x7D, 0xF1, 0x9E, 0x31, 0xE6, 0x2E, 0x85, + 0xF1, 0x16, 0x07, 0x61, 0xFC, 0xB5, 0x01, 0x55, 0xFF, 0x2D, 0x00, + 0xFD, 0xFF, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, + 0x02, 0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, + 0xF8, 0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x32, 0x00, 0x45, 0xFF, 0xD2, 0x01, 0x3D, 0xFC, + 0x2B, 0x07, 0xD4, 0xF1, 0x64, 0x2A, 0xC6, 0x35, 0xB7, 0xF1, 0x96, + 0x06, 0xCF, 0xFC, 0x6B, 0x01, 0x7D, 0xFF, 0x1F, 0x00, 0xFE, 0xFF, + 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07, + 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04, + 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF3, 0x06, + 0xEE, 0xF2, 0xCD, 0x22, 0xE4, 0x3B, 0xDC, 0xF2, 0x9C, 0x05, 0x7E, + 0xFD, 0x00, 0x01, 0xB4, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0B, 0x00, + 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02, 0x5E, + 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF, 0xFC, + 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x36, + 0x00, 0x38, 0xFF, 0xDB, 0x01, 0x67, 0xFC, 0x5A, 0x06, 0xA6, 0xF4, + 0x1B, 0x1B, 0x07, 0x41, 0x04, 0xF5, 0x2D, 0x04, 0x67, 0xFE, 0x77, + 0x00, 0xF8, 0xFF, 0xF2, 0xFF, 0x05, 0x00, 0x05, 0x00, 0xF0, 0xFF, + 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, 0xF5, 0x32, + 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, 0xDA, 0x01, + 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x45, + 0xFF, 0xB5, 0x01, 0xCA, 0xFC, 0x72, 0x05, 0xD3, 0xF6, 0x8D, 0x13, + 0xFD, 0x44, 0x39, 0xF8, 0x53, 0x02, 0x82, 0xFF, 0xD7, 0xFF, 0x47, + 0x00, 0xD3, 0xFF, 0x0B, 0x00, 0x01, 0x00, 0x0A, 0x00, 0xB6, 0xFF, + 0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, 0x1C, 0x3C, 0x81, + 0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, 0x01, 0x37, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x5C, 0xFF, 0x79, + 0x01, 0x53, 0xFD, 0x4E, 0x04, 0x4A, 0xF9, 0x60, 0x0C, 0xA3, 0x47, + 0x76, 0xFC, 0x1F, 0x00, 0xC3, 0x00, 0x27, 0xFF, 0x9D, 0x00, 0xB2, + 0xFF, 0x13, 0x00, 0xFE, 0xFF, 0x1E, 0x00, 0x7F, 0xFF, 0x67, 0x01, + 0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06, 0x36, 0x1A, 0x2A, 0xDC, + 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, 0x44, 0xFF, 0x32, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x21, 0x00, 0x79, 0xFF, 0x2E, 0x01, 0xF7, + 0xFD, 0x05, 0x03, 0xE1, 0xFB, 0xCA, 0x05, 0xDF, 0x48, 0xAB, 0x01, + 0xAA, 0xFD, 0x18, 0x02, 0x72, 0xFE, 0xF4, 0x00, 0x90, 0xFF, 0x1B, + 0x00, 0xFD, 0xFF, 0x2C, 0x00, 0x57, 0xFF, 0xB3, 0x01, 0x64, 0xFC, + 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, 0x5A, 0x31, 0x7D, 0xF1, 0xF7, + 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, 0xFF, 0x29, 0x00, 0xFD, 0xFF, + 0x19, 0x00, 0x9A, 0xFF, 0xD9, 0x00, 0xAA, 0xFE, 0xAE, 0x01, 0x70, + 0xFE, 0xF8, 0xFF, 0xA6, 0x48, 0xBE, 0x07, 0x14, 0xFB, 0x6D, 0x03, + 0xC3, 0xFD, 0x46, 0x01, 0x70, 0xFF, 0x24, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0x34, 0x00, 0x3F, 0xFF, 0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07, + 0x21, 0xF2, 0xCB, 0x27, 0xFE, 0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04, + 0xFD, 0x49, 0x01, 0x8E, 0xFF, 0x19, 0x00, 0xFF, 0xFF, 0x10, 0x00, + 0xBD, 0xFF, 0x82, 0x00, 0x5E, 0xFF, 0x5D, 0x00, 0xD4, 0x00, 0x0C, + 0xFB, 0xF9, 0x46, 0x87, 0x0E, 0x82, 0xF8, 0xAD, 0x04, 0x26, 0xFD, + 0x8D, 0x01, 0x54, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3, + 0x22, 0x20, 0xCA, 0x3D, 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4, + 0x00, 0xCA, 0xFF, 0x03, 0x00, 0x02, 0x00, 0x09, 0x00, 0xDD, 0xFF, + 0x2E, 0x00, 0x0A, 0x00, 0x27, 0xFF, 0xEF, 0x02, 0x20, 0xF7, 0xE7, + 0x43, 0xD8, 0x15, 0x1E, 0xF6, 0xC0, 0x05, 0xA7, 0xFC, 0xC3, 0x01, + 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0x34, 0x00, 0x3B, + 0xFF, 0xD1, 0x01, 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, + 0x89, 0x42, 0x02, 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, + 0x00, 0xE8, 0xFF, 0x07, 0x00, 0x03, 0x00, 0xFA, 0xFF, 0xE2, 0xFF, + 0xA4, 0x00, 0x19, 0xFE, 0xAA, 0x04, 0x3E, 0xF4, 0x90, 0x3F, 0x78, + 0x1D, 0x10, 0xF4, 0x93, 0x06, 0x52, 0xFC, 0xE1, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, + 0x01, 0xF6, 0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, + 0x93, 0xF9, 0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, + 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0xA1, 0xFF, 0x24, 0x01, + 0x41, 0xFD, 0xF6, 0x05, 0x67, 0xF2, 0x1A, 0x3A, 0x29, 0x25, 0x84, + 0xF2, 0x0F, 0x07, 0x31, 0xFC, 0xE3, 0x01, 0x3A, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, + 0xFD, 0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, + 0x4B, 0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, + 0x00, 0xFD, 0xFF, 0x24, 0x00, 0x6F, 0xFF, 0x85, 0x01, 0xA6, 0xFC, + 0xCA, 0x06, 0x8F, 0xF1, 0xBB, 0x33, 0xAB, 0x2C, 0xA3, 0xF1, 0x26, + 0x07, 0x4C, 0xFC, 0xC5, 0x01, 0x4D, 0xFF, 0x30, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, + 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, + 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0xFD, + 0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, 0x71, 0xFC, 0x07, 0x07, + 0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, 0xF1, 0x07, 0x07, 0x71, + 0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, 0xFD, 0xFF, 0x00, 0x00, + 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, + 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, + 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x02, 0x00, 0x05, + 0x00, 0xC3, 0xFF, 0xE1, 0x00, 0xB1, 0xFD, 0x4E, 0x05, 0x4A, 0xF3, + 0x3D, 0x3D, 0xED, 0x20, 0x4C, 0xF3, 0xD6, 0x06, 0x3D, 0xFC, 0xE6, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x36, 0x00, + 0x36, 0xFF, 0xE6, 0x01, 0x3D, 0xFC, 0xD6, 0x06, 0x4C, 0xF3, 0xED, + 0x20, 0x3D, 0x3D, 0x4A, 0xF3, 0x4E, 0x05, 0xB1, 0xFD, 0xE1, 0x00, + 0xC3, 0xFF, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x84, + 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, 0x02, 0xC7, 0xFC, 0xAE, 0x03, + 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, 0x8F, 0x02, 0x34, 0xFE, 0x11, + 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA6, 0xFF, 0xBB, 0x00, + 0xE9, 0xFE, 0x38, 0x01, 0x4B, 0xFF, 0x28, 0xFE, 0x3A, 0x48, 0x04, + 0x0A, 0x2E, 0xFA, 0xDF, 0x03, 0x8A, 0xFD, 0x60, 0x01, 0x65, 0xFF, + 0x27, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xC8, 0xFF, 0x64, 0x00, 0x9B, + 0xFF, 0xEE, 0xFF, 0x98, 0x01, 0x93, 0xF9, 0x10, 0x46, 0x03, 0x11, + 0xA7, 0xF7, 0x12, 0x05, 0xF6, 0xFC, 0xA2, 0x01, 0x4C, 0xFF, 0x2F, + 0x00, 0xFF, 0xFF, 0x07, 0x00, 0xE8, 0xFF, 0x12, 0x00, 0x42, 0x00, + 0xC4, 0xFE, 0x94, 0x03, 0x02, 0xF6, 0x89, 0x42, 0x76, 0x18, 0x5C, + 0xF5, 0x12, 0x06, 0x84, 0xFC, 0xD1, 0x01, 0x3B, 0xFF, 0x34, 0x00, + 0xFE, 0xFF, 0x02, 0x00, 0x03, 0x00, 0xCA, 0xFF, 0xD4, 0x00, 0xC8, + 0xFD, 0x2A, 0x05, 0x7D, 0xF3, 0xCA, 0x3D, 0x22, 0x20, 0x76, 0xF3, + 0xC8, 0x06, 0x41, 0xFC, 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x8E, 0xFF, 0x49, 0x01, 0x04, 0xFD, + 0x4D, 0x06, 0x00, 0xF2, 0xFE, 0x37, 0xCB, 0x27, 0x21, 0xF2, 0x23, + 0x07, 0x34, 0xFC, 0xDD, 0x01, 0x3F, 0xFF, 0x34, 0x00, 0xFD, 0xFF, + 0xFD, 0xFF, 0x29, 0x00, 0x61, 0xFF, 0x9F, 0x01, 0x80, 0xFC, 0xF7, + 0x06, 0x7D, 0xF1, 0x5A, 0x31, 0x2C, 0x2F, 0x83, 0xF1, 0x13, 0x07, + 0x64, 0xFC, 0xB3, 0x01, 0x57, 0xFF, 0x2C, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x32, 0x00, 0x44, 0xFF, 0xD3, 0x01, 0x3C, 0xFC, 0x2A, 0x07, + 0xDC, 0xF1, 0x1A, 0x2A, 0x06, 0x36, 0xBE, 0xF1, 0x8E, 0x06, 0xD5, + 0xFC, 0x67, 0x01, 0x7F, 0xFF, 0x1E, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, + 0x36, 0x00, 0x37, 0xFF, 0xE6, 0x01, 0x36, 0xFC, 0xEF, 0x06, 0xFC, + 0xF2, 0x81, 0x22, 0x1C, 0x3C, 0xEC, 0xF2, 0x90, 0x05, 0x85, 0xFD, + 0xFB, 0x00, 0xB6, 0xFF, 0x0A, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35, + 0x00, 0x38, 0xFF, 0xDA, 0x01, 0x6A, 0xFC, 0x53, 0x06, 0xBA, 0xF4, + 0xCE, 0x1A, 0x32, 0x41, 0x1F, 0xF5, 0x1D, 0x04, 0x71, 0xFE, 0x71, + 0x00, 0xFB, 0xFF, 0xF0, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00, + 0x46, 0xFF, 0xB3, 0x01, 0xCF, 0xFC, 0x67, 0x05, 0xEA, 0xF6, 0x44, + 0x13, 0x1E, 0x45, 0x5E, 0xF8, 0x3F, 0x02, 0x8E, 0xFF, 0xD0, 0xFF, + 0x4A, 0x00, 0xD2, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5D, + 0xFF, 0x76, 0x01, 0x59, 0xFD, 0x42, 0x04, 0x63, 0xF9, 0x1C, 0x0C, + 0xB6, 0x47, 0xA4, 0xFC, 0x07, 0x00, 0xD0, 0x00, 0x20, 0xFF, 0xA0, + 0x00, 0xB1, 0xFF, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x7A, 0xFF, + 0x2B, 0x01, 0xFE, 0xFD, 0xF8, 0x02, 0xFB, 0xFB, 0x8D, 0x05, 0xE5, + 0x48, 0xE3, 0x01, 0x91, 0xFD, 0x25, 0x02, 0x6B, 0xFE, 0xF7, 0x00, + 0x8F, 0xFF, 0x1C, 0x00, 0x18, 0x00, 0x9C, 0xFF, 0xD6, 0x00, 0xB1, + 0xFE, 0xA1, 0x01, 0x89, 0xFE, 0xC3, 0xFF, 0x9C, 0x48, 0xFD, 0x07, + 0xFA, 0xFA, 0x7A, 0x03, 0xBC, 0xFD, 0x49, 0x01, 0x6E, 0xFF, 0x24, + 0x00, 0x00, 0x00, 0x10, 0x00, 0xBE, 0xFF, 0x7F, 0x00, 0x65, 0xFF, + 0x51, 0x00, 0xEB, 0x00, 0xE1, 0xFA, 0xE1, 0x46, 0xCD, 0x0E, 0x6A, + 0xF8, 0xB8, 0x04, 0x20, 0xFD, 0x90, 0x01, 0x53, 0xFF, 0x2D, 0x00, + 0xFF, 0xFF, 0x09, 0x00, 0xDE, 0xFF, 0x2B, 0x00, 0x11, 0x00, 0x1B, + 0xFF, 0x02, 0x03, 0xFE, 0xF6, 0xC3, 0x43, 0x22, 0x16, 0x07, 0xF6, + 0xCA, 0x05, 0xA3, 0xFC, 0xC5, 0x01, 0x3F, 0xFF, 0x33, 0x00, 0xFF, + 0xFF, 0x03, 0x00, 0xFB, 0xFF, 0xDF, 0xFF, 0xA9, 0x00, 0x10, 0xFE, + 0xB9, 0x04, 0x27, 0xF4, 0x5E, 0x3F, 0xC3, 0x1D, 0xFE, 0xF3, 0x99, + 0x06, 0x50, 0xFC, 0xE2, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, + 0x00, 0x00, 0x13, 0x00, 0x9F, 0xFF, 0x28, 0x01, 0x3A, 0xFD, 0x00, + 0x06, 0x5A, 0xF2, 0xDF, 0x39, 0x73, 0x25, 0x79, 0xF2, 0x12, 0x07, + 0x31, 0xFC, 0xE3, 0x01, 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x24, 0x00, 0x6D, 0xFF, 0x88, 0x01, 0xA2, 0xFC, 0xD0, 0x06, + 0x8C, 0xF1, 0x78, 0x33, 0xF2, 0x2C, 0x9E, 0xF1, 0x24, 0x07, 0x4E, + 0xFC, 0xC3, 0x01, 0x4E, 0xFF, 0x2F, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x30, 0x00, 0x4C, 0xFF, 0xC7, 0x01, 0x4A, 0xFC, 0x27, 0x07, 0xA8, + 0xF1, 0x62, 0x2C, 0xFD, 0x33, 0x93, 0xF1, 0xC4, 0x06, 0xAB, 0xFC, + 0x82, 0x01, 0x71, 0xFF, 0x23, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x3A, 0xFF, 0xE4, 0x01, 0x32, 0xFC, 0x0C, 0x07, 0x91, 0xF2, + 0xDD, 0x24, 0x54, 0x3A, 0x74, 0xF2, 0xEB, 0x05, 0x49, 0xFD, 0x20, + 0x01, 0xA3, 0xFF, 0x11, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, + 0x37, 0xFF, 0xE1, 0x01, 0x55, 0xFC, 0x8C, 0x06, 0x22, 0xF4, 0x2C, + 0x1D, 0xC0, 0x3F, 0x55, 0xF4, 0x9B, 0x04, 0x23, 0xFE, 0x9F, 0x00, + 0xE4, 0xFF, 0xF9, 0xFF, 0x04, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x40, + 0xFF, 0xC1, 0x01, 0xAB, 0xFC, 0xB7, 0x05, 0x34, 0xF6, 0x8E, 0x15, + 0x0B, 0x44, 0x42, 0xF7, 0xDC, 0x02, 0x32, 0xFF, 0x04, 0x00, 0x31, + 0x00, 0xDC, 0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2C, 0x00, 0x55, 0xFF, + 0x8B, 0x01, 0x2B, 0xFD, 0xA1, 0x04, 0x9B, 0xF8, 0x42, 0x0E, 0x0F, + 0x47, 0x38, 0xFB, 0xBE, 0x00, 0x6A, 0x00, 0x58, 0xFF, 0x85, 0x00, + 0xBB, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x71, 0xFF, 0x43, + 0x01, 0xC9, 0xFD, 0x60, 0x03, 0x2E, 0xFB, 0x7E, 0x07, 0xAF, 0x48, + 0x2D, 0x00, 0x58, 0xFE, 0xBB, 0x01, 0xA3, 0xFE, 0xDD, 0x00, 0x99, + 0xFF, 0x19, 0x00, 0x1B, 0x00, 0x91, 0xFF, 0xF1, 0x00, 0x79, 0xFE, + 0x0A, 0x02, 0xC3, 0xFD, 0x73, 0x01, 0xDB, 0x48, 0x07, 0x06, 0xC7, + 0xFB, 0x12, 0x03, 0xF1, 0xFD, 0x31, 0x01, 0x78, 0xFF, 0x22, 0x00, + 0x00, 0x00, 0x12, 0x00, 0xB3, 0xFF, 0x99, 0x00, 0x2E, 0xFF, 0xB6, + 0x00, 0x36, 0x00, 0x47, 0xFC, 0x90, 0x47, 0xA4, 0x0C, 0x31, 0xF9, + 0x5A, 0x04, 0x4E, 0xFD, 0x7C, 0x01, 0x5B, 0xFF, 0x2A, 0x00, 0x00, + 0x00, 0x0B, 0x00, 0xD5, 0xFF, 0x44, 0x00, 0xDD, 0xFF, 0x77, 0xFF, + 0x67, 0x02, 0x14, 0xF8, 0xDC, 0x44, 0xD5, 0x13, 0xBC, 0xF6, 0x7C, + 0x05, 0xC5, 0xFC, 0xB7, 0x01, 0x44, 0xFF, 0x31, 0x00, 0xFF, 0xFF, + 0x05, 0x00, 0xF3, 0xFF, 0xF5, 0xFF, 0x7D, 0x00, 0x5D, 0xFE, 0x3E, + 0x04, 0xEA, 0xF4, 0xD9, 0x40, 0x66, 0x1B, 0x93, 0xF4, 0x62, 0x06, + 0x64, 0xFC, 0xDC, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, + 0x00, 0x0C, 0x00, 0xB1, 0xFF, 0x04, 0x01, 0x76, 0xFD, 0xA8, 0x05, + 0xCC, 0xF2, 0xAB, 0x3B, 0x18, 0x23, 0xE0, 0xF2, 0xF7, 0x06, 0x35, + 0xFC, 0xE6, 0x01, 0x38, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, + 0x20, 0x00, 0x7B, 0xFF, 0x6E, 0x01, 0xCA, 0xFC, 0x9D, 0x06, 0xB1, + 0xF1, 0x86, 0x35, 0xAE, 0x2A, 0xCD, 0xF1, 0x2B, 0x07, 0x3F, 0xFC, + 0xD1, 0x01, 0x46, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2D, + 0x00, 0x54, 0xFF, 0xB7, 0x01, 0x5E, 0xFC, 0x19, 0x07, 0x88, 0xF1, + 0x9F, 0x2E, 0xE3, 0x31, 0x7E, 0xF1, 0xEE, 0x06, 0x88, 0xFC, 0x9A, + 0x01, 0x64, 0xFF, 0x28, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00, + 0x3E, 0xFF, 0xDF, 0x01, 0x33, 0xFC, 0x20, 0x07, 0x35, 0xF2, 0x36, + 0x27, 0x78, 0x38, 0x14, 0xF2, 0x3B, 0x06, 0x11, 0xFD, 0x41, 0x01, + 0x92, 0xFF, 0x17, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, + 0xFF, 0xE5, 0x01, 0x44, 0xFC, 0xBD, 0x06, 0x97, 0xF3, 0x8A, 0x1F, + 0x31, 0x3E, 0xA5, 0xF3, 0x0F, 0x05, 0xDA, 0xFD, 0xC9, 0x00, 0xCF, + 0xFF, 0x01, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3C, 0xFF, + 0xCE, 0x01, 0x8C, 0xFC, 0x00, 0x06, 0x86, 0xF5, 0xE0, 0x17, 0xDB, + 0x42, 0x3F, 0xF6, 0x71, 0x03, 0xD9, 0xFE, 0x36, 0x00, 0x18, 0x00, + 0xE5, 0xFF, 0x07, 0x00, 0xFF, 0xFF, 0x2E, 0x00, 0x4E, 0xFF, 0x9E, + 0x01, 0x00, 0xFD, 0xFC, 0x04, 0xD7, 0xF7, 0x75, 0x10, 0x48, 0x46, + 0xE4, 0xF9, 0x6E, 0x01, 0x06, 0x00, 0x8E, 0xFF, 0x6B, 0x00, 0xC6, + 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x26, 0x00, 0x68, 0xFF, 0x5B, 0x01, + 0x96, 0xFD, 0xC6, 0x03, 0x61, 0xFA, 0x81, 0x09, 0x57, 0x48, 0x8D, + 0xFE, 0x1B, 0xFF, 0x52, 0x01, 0xDB, 0xFE, 0xC2, 0x00, 0xA4, 0xFF, + 0x16, 0x00, 0x1E, 0x00, 0x87, 0xFF, 0x0B, 0x01, 0x42, 0xFE, 0x74, + 0x02, 0xF9, 0xFC, 0x39, 0x03, 0xF5, 0x48, 0x24, 0x04, 0x94, 0xFC, + 0xA9, 0x02, 0x27, 0xFE, 0x18, 0x01, 0x82, 0xFF, 0x1F, 0x00, 0x00, + 0x00, 0x15, 0x00, 0xA9, 0xFF, 0xB4, 0x00, 0xF7, 0xFE, 0x1D, 0x01, + 0x7A, 0xFF, 0xC5, 0xFD, 0x1D, 0x48, 0x89, 0x0A, 0xFB, 0xF9, 0xF8, + 0x03, 0x7D, 0xFD, 0x66, 0x01, 0x63, 0xFF, 0x28, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0xCB, 0xFF, 0x5E, 0x00, 0xA9, 0xFF, 0xD6, 0xFF, 0xC3, + 0x01, 0x43, 0xF9, 0xD7, 0x45, 0x92, 0x11, 0x77, 0xF7, 0x28, 0x05, + 0xEC, 0xFC, 0xA7, 0x01, 0x4A, 0xFF, 0x2F, 0x00, 0xFF, 0xFF, 0x06, + 0x00, 0xEA, 0xFF, 0x0C, 0x00, 0x4E, 0x00, 0xAF, 0xFE, 0xB8, 0x03, + 0xC7, 0xF5, 0x38, 0x42, 0x0C, 0x19, 0x32, 0xF5, 0x23, 0x06, 0x7D, + 0xFC, 0xD3, 0x01, 0x3A, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x02, 0x00, + 0x05, 0x00, 0xC5, 0xFF, 0xDE, 0x00, 0xB7, 0xFD, 0x45, 0x05, 0x56, + 0xF3, 0x61, 0x3D, 0xBA, 0x20, 0x56, 0xF3, 0xD3, 0x06, 0x3E, 0xFC, + 0xE6, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0x1A, + 0x00, 0x8A, 0xFF, 0x51, 0x01, 0xF8, 0xFC, 0x5E, 0x06, 0xED, 0xF1, + 0x82, 0x37, 0x60, 0x28, 0x0E, 0xF2, 0x26, 0x07, 0x35, 0xFC, 0xDB, + 0x01, 0x40, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x29, 0x00, + 0x5F, 0xFF, 0xA5, 0x01, 0x78, 0xFC, 0xFF, 0x06, 0x7D, 0xF1, 0xCF, + 0x30, 0xB8, 0x2F, 0x80, 0xF1, 0x0D, 0x07, 0x6A, 0xFC, 0xAE, 0x01, + 0x59, 0xFF, 0x2B, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x43, + 0xFF, 0xD6, 0x01, 0x39, 0xFC, 0x2A, 0x07, 0xEB, 0xF1, 0x87, 0x29, + 0x85, 0x36, 0xCC, 0xF1, 0x7F, 0x06, 0xE0, 0xFC, 0x60, 0x01, 0x82, + 0xFF, 0x1D, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x37, 0xFF, + 0xE6, 0x01, 0x38, 0xFC, 0xE6, 0x06, 0x19, 0xF3, 0xEA, 0x21, 0x8A, + 0x3C, 0x0E, 0xF3, 0x78, 0x05, 0x96, 0xFD, 0xF1, 0x00, 0xBB, 0xFF, + 0x08, 0x00, 0x01, 0x00, 0xFE, 0xFF, 0x35, 0x00, 0x39, 0xFF, 0xD8, + 0x01, 0x70, 0xFC, 0x43, 0x06, 0xE1, 0xF4, 0x38, 0x1A, 0x8C, 0x41, + 0x55, 0xF5, 0xFC, 0x03, 0x85, 0xFE, 0x66, 0x00, 0x01, 0x00, 0xEE, + 0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x30, 0x00, 0x47, 0xFF, 0xAF, 0x01, + 0xD8, 0xFC, 0x52, 0x05, 0x19, 0xF7, 0xB2, 0x12, 0x5C, 0x45, 0xA9, + 0xF8, 0x16, 0x02, 0xA6, 0xFF, 0xC3, 0xFF, 0x51, 0x00, 0xD0, 0xFF, + 0x0C, 0x00, 0x00, 0x00, 0x29, 0x00, 0x5F, 0xFF, 0x71, 0x01, 0x65, + 0xFD, 0x29, 0x04, 0x96, 0xF9, 0x95, 0x0B, 0xDC, 0x47, 0x03, 0xFD, + 0xD9, 0xFF, 0xEA, 0x00, 0x12, 0xFF, 0xA7, 0x00, 0xAE, 0xFF, 0x14, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x7D, 0xFF, 0x24, 0x01, 0x0C, 0xFE, + 0xDE, 0x02, 0x2E, 0xFC, 0x13, 0x05, 0xEC, 0x48, 0x54, 0x02, 0x5E, + 0xFD, 0x3F, 0x02, 0x5D, 0xFE, 0xFE, 0x00, 0x8C, 0xFF, 0x1C, 0x00, + 0x17, 0x00, 0x9E, 0xFF, 0xCF, 0x00, 0xBF, 0xFE, 0x86, 0x01, 0xBA, + 0xFE, 0x5A, 0xFF, 0x86, 0x48, 0x7D, 0x08, 0xC7, 0xFA, 0x93, 0x03, + 0xB0, 0xFD, 0x4F, 0x01, 0x6C, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x0F, + 0x00, 0xC0, 0xFF, 0x78, 0x00, 0x73, 0xFF, 0x38, 0x00, 0x17, 0x01, + 0x8B, 0xFA, 0xAF, 0x46, 0x59, 0x0F, 0x39, 0xF8, 0xCF, 0x04, 0x15, + 0xFD, 0x95, 0x01, 0x51, 0xFF, 0x2D, 0x00, 0xFF, 0xFF, 0x08, 0x00, + 0xE1, 0xFF, 0x25, 0x00, 0x1D, 0x00, 0x05, 0xFF, 0x28, 0x03, 0xBD, + 0xF6, 0x77, 0x43, 0xB6, 0x16, 0xDC, 0xF5, 0xDD, 0x05, 0x9B, 0xFC, + 0xC8, 0x01, 0x3E, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0xFD, + 0xFF, 0xD9, 0xFF, 0xB4, 0x00, 0xFD, 0xFD, 0xD7, 0x04, 0xFA, 0xF3, + 0xFC, 0x3E, 0x5B, 0x1E, 0xDB, 0xF3, 0xA6, 0x06, 0x4C, 0xFC, 0xE3, + 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x14, 0x00, + 0x9B, 0xFF, 0x31, 0x01, 0x2C, 0xFD, 0x15, 0x06, 0x41, 0xF2, 0x6A, + 0x39, 0x0A, 0x26, 0x61, 0xF2, 0x17, 0x07, 0x31, 0xFC, 0xE2, 0x01, + 0x3B, 0xFF, 0x35, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x25, 0x00, 0x6A, + 0xFF, 0x8E, 0x01, 0x99, 0xFC, 0xDB, 0x06, 0x86, 0xF1, 0xF2, 0x32, + 0x82, 0x2D, 0x96, 0xF1, 0x21, 0x07, 0x53, 0xFC, 0xC0, 0x01, 0x50, + 0xFF, 0x2E, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x30, 0x00, 0x4A, 0xFF, + 0xCA, 0x01, 0x46, 0xFC, 0x29, 0x07, 0xB3, 0xF1, 0xD1, 0x2B, 0x81, + 0x34, 0x9C, 0xF1, 0xB8, 0x06, 0xB5, 0xFC, 0x7C, 0x01, 0x74, 0xFF, + 0x22, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x39, 0xFF, 0xE5, + 0x01, 0x32, 0xFC, 0x06, 0x07, 0xAA, 0xF2, 0x46, 0x24, 0xC8, 0x3A, + 0x90, 0xF2, 0xD6, 0x05, 0x57, 0xFD, 0x17, 0x01, 0xA8, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDF, 0x01, + 0x5A, 0xFC, 0x7E, 0x06, 0x47, 0xF4, 0x94, 0x1C, 0x1F, 0x40, 0x85, + 0xF4, 0x7D, 0x04, 0x36, 0xFE, 0x93, 0x00, 0xEA, 0xFF, 0xF7, 0xFF, + 0x04, 0x00, 0xFF, 0xFF, 0x32, 0x00, 0x42, 0xFF, 0xBE, 0x01, 0xB4, + 0xFC, 0xA4, 0x05, 0x61, 0xF6, 0xFB, 0x14, 0x53, 0x44, 0x86, 0xF7, + 0xB6, 0x02, 0x49, 0xFF, 0xF7, 0xFF, 0x37, 0x00, 0xD9, 0xFF, 0x0A, + 0x00, 0x00, 0x00, 0x2B, 0x00, 0x57, 0xFF, 0x86, 0x01, 0x36, 0xFD, + 0x89, 0x04, 0xCD, 0xF8, 0xB7, 0x0D, 0x3D, 0x47, 0x91, 0xFB, 0x91, + 0x00, 0x83, 0x00, 0x4A, 0xFF, 0x8C, 0x00, 0xB9, 0xFF, 0x11, 0x00, + 0x00, 0x00, 0x23, 0x00, 0x73, 0xFF, 0x3D, 0x01, 0xD6, 0xFD, 0x46, + 0x03, 0x61, 0xFB, 0x00, 0x07, 0xBF, 0x48, 0x98, 0x00, 0x26, 0xFE, + 0xD5, 0x01, 0x95, 0xFE, 0xE3, 0x00, 0x96, 0xFF, 0x1A, 0x00, 0x1A, + 0x00, 0x94, 0xFF, 0xEA, 0x00, 0x87, 0xFE, 0xF0, 0x01, 0xF5, 0xFD, + 0x05, 0x01, 0xCE, 0x48, 0x83, 0x06, 0x94, 0xFB, 0x2C, 0x03, 0xE4, + 0xFD, 0x37, 0x01, 0x76, 0xFF, 0x22, 0x00, 0x00, 0x00, 0x12, 0x00, + 0xB6, 0xFF, 0x93, 0x00, 0x3C, 0xFF, 0x9D, 0x00, 0x63, 0x00, 0xEB, + 0xFB, 0x69, 0x47, 0x2D, 0x0D, 0xFF, 0xF8, 0x72, 0x04, 0x42, 0xFD, + 0x81, 0x01, 0x59, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD7, + 0xFF, 0x3E, 0x00, 0xEA, 0xFF, 0x60, 0xFF, 0x8F, 0x02, 0xCD, 0xF7, + 0x99, 0x44, 0x68, 0x14, 0x8E, 0xF6, 0x90, 0x05, 0xBC, 0xFC, 0xBA, + 0x01, 0x43, 0xFF, 0x32, 0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF5, 0xFF, + 0xEF, 0xFF, 0x88, 0x00, 0x49, 0xFE, 0x5D, 0x04, 0xB7, 0xF4, 0x7D, + 0x40, 0xFD, 0x1B, 0x6C, 0xF4, 0x70, 0x06, 0x5F, 0xFC, 0xDE, 0x01, + 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0xAC, + 0xFF, 0x0E, 0x01, 0x66, 0xFD, 0xBF, 0x05, 0xAD, 0xF2, 0x3B, 0x3B, + 0xB0, 0x23, 0xC4, 0xF2, 0xFF, 0x06, 0x33, 0xFC, 0xE5, 0x01, 0x38, + 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x21, 0x00, 0x77, 0xFF, + 0x75, 0x01, 0xBF, 0xFC, 0xAB, 0x06, 0xA6, 0xF1, 0x05, 0x35, 0x40, + 0x2B, 0xBF, 0xF1, 0x2A, 0x07, 0x42, 0xFC, 0xCE, 0x01, 0x48, 0xFF, + 0x31, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2E, 0x00, 0x52, 0xFF, 0xBC, + 0x01, 0x58, 0xFC, 0x1D, 0x07, 0x8E, 0xF1, 0x11, 0x2E, 0x6B, 0x32, + 0x81, 0xF1, 0xE5, 0x06, 0x90, 0xFC, 0x94, 0x01, 0x67, 0xFF, 0x26, + 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3C, 0xFF, 0xE0, 0x01, + 0x32, 0xFC, 0x1C, 0x07, 0x4B, 0xF2, 0xA0, 0x26, 0xF2, 0x38, 0x2A, + 0xF2, 0x28, 0x06, 0x1F, 0xFD, 0x39, 0x01, 0x96, 0xFF, 0x16, 0x00, + 0xFF, 0xFF, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE4, 0x01, 0x48, + 0xFC, 0xB2, 0x06, 0xB9, 0xF3, 0xF3, 0x1E, 0x98, 0x3E, 0xCF, 0xF3, + 0xF3, 0x04, 0xEB, 0xFD, 0xBF, 0x00, 0xD4, 0xFF, 0xFF, 0xFF, 0x03, + 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3D, 0xFF, 0xCB, 0x01, 0x93, 0xFC, + 0xEF, 0x05, 0xB0, 0xF5, 0x4B, 0x17, 0x2A, 0x43, 0x7D, 0xF6, 0x4D, + 0x03, 0xEF, 0xFE, 0x2A, 0x00, 0x1E, 0x00, 0xE3, 0xFF, 0x08, 0x00, + 0xFF, 0xFF, 0x2E, 0x00, 0x4F, 0xFF, 0x99, 0x01, 0x0B, 0xFD, 0xE6, + 0x04, 0x08, 0xF8, 0xE7, 0x0F, 0x7C, 0x46, 0x37, 0xFA, 0x42, 0x01, + 0x1F, 0x00, 0x81, 0xFF, 0x71, 0x00, 0xC3, 0xFF, 0x0F, 0x00, 0x00, + 0x00, 0x26, 0x00, 0x6A, 0xFF, 0x55, 0x01, 0xA3, 0xFD, 0xAD, 0x03, + 0x94, 0xFA, 0xFF, 0x08, 0x70, 0x48, 0xF3, 0xFE, 0xEA, 0xFE, 0x6C, + 0x01, 0xCD, 0xFE, 0xC9, 0x00, 0xA1, 0xFF, 0x17, 0x00, 0x1D, 0x00, + 0x8A, 0xFF, 0x04, 0x01, 0x50, 0xFE, 0x5A, 0x02, 0x2C, 0xFD, 0xC6, + 0x02, 0xF2, 0x48, 0x9B, 0x04, 0x61, 0xFC, 0xC3, 0x02, 0x19, 0xFE, + 0x1E, 0x01, 0x7F, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAC, + 0xFF, 0xAE, 0x00, 0x05, 0xFF, 0x03, 0x01, 0xAA, 0xFF, 0x63, 0xFD, + 0xFD, 0x47, 0x0E, 0x0B, 0xC8, 0xF9, 0x11, 0x04, 0x71, 0xFD, 0x6C, + 0x01, 0x61, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xCD, 0xFF, + 0x57, 0x00, 0xB6, 0xFF, 0xBE, 0xFF, 0xED, 0x01, 0xF5, 0xF8, 0x9B, + 0x45, 0x22, 0x12, 0x48, 0xF7, 0x3D, 0x05, 0xE2, 0xFC, 0xAB, 0x01, + 0x49, 0xFF, 0x30, 0x00, 0xFF, 0xFF, 0x06, 0x00, 0xEC, 0xFF, 0x06, + 0x00, 0x5A, 0x00, 0x9A, 0xFE, 0xDA, 0x03, 0x8D, 0xF5, 0xE1, 0x41, + 0xA1, 0x19, 0x09, 0xF5, 0x33, 0x06, 0x77, 0xFC, 0xD6, 0x01, 0x3A, + 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, + 0xE8, 0x00, 0xA6, 0xFD, 0x5F, 0x05, 0x31, 0xF3, 0xF6, 0x3C, 0x52, + 0x21, 0x37, 0xF3, 0xDD, 0x06, 0x3B, 0xFC, 0xE6, 0x01, 0x36, 0xFF, + 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1C, 0x00, 0x86, 0xFF, 0x59, + 0x01, 0xEC, 0xFC, 0x6F, 0x06, 0xDC, 0xF1, 0x04, 0x37, 0xF3, 0x28, + 0xFC, 0xF1, 0x28, 0x07, 0x37, 0xFC, 0xD8, 0x01, 0x41, 0xFF, 0x33, + 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2A, 0x00, 0x5C, 0xFF, 0xAA, 0x01, + 0x71, 0xFC, 0x07, 0x07, 0x7E, 0xF1, 0x44, 0x30, 0x44, 0x30, 0x7E, + 0xF1, 0x07, 0x07, 0x71, 0xFC, 0xAA, 0x01, 0x5C, 0xFF, 0x2A, 0x00, + 0xFD, 0xFF, 0xFD, 0xFF, 0x33, 0x00, 0x41, 0xFF, 0xD8, 0x01, 0x37, + 0xFC, 0x28, 0x07, 0xFC, 0xF1, 0xF3, 0x28, 0x04, 0x37, 0xDC, 0xF1, + 0x6F, 0x06, 0xEC, 0xFC, 0x59, 0x01, 0x86, 0xFF, 0x1C, 0x00, 0xFE, + 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3B, 0xFC, + 0xDD, 0x06, 0x37, 0xF3, 0x52, 0x21, 0xF6, 0x3C, 0x31, 0xF3, 0x5F, + 0x05, 0xA6, 0xFD, 0xE8, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0x01, 0x00, + 0xFE, 0xFF, 0x35, 0x00, 0x3A, 0xFF, 0xD6, 0x01, 0x77, 0xFC, 0x33, + 0x06, 0x09, 0xF5, 0xA1, 0x19, 0xE1, 0x41, 0x8D, 0xF5, 0xDA, 0x03, + 0x9A, 0xFE, 0x5A, 0x00, 0x06, 0x00, 0xEC, 0xFF, 0x06, 0x00, 0xFF, + 0xFF, 0x30, 0x00, 0x49, 0xFF, 0xAB, 0x01, 0xE2, 0xFC, 0x3D, 0x05, + 0x48, 0xF7, 0x22, 0x12, 0x9B, 0x45, 0xF5, 0xF8, 0xED, 0x01, 0xBE, + 0xFF, 0xB6, 0xFF, 0x57, 0x00, 0xCD, 0xFF, 0x0C, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x61, 0xFF, 0x6C, 0x01, 0x71, 0xFD, 0x11, 0x04, 0xC8, + 0xF9, 0x0E, 0x0B, 0xFD, 0x47, 0x63, 0xFD, 0xAA, 0xFF, 0x03, 0x01, + 0x05, 0xFF, 0xAE, 0x00, 0xAC, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x7F, 0xFF, 0x1E, 0x01, 0x19, 0xFE, 0xC3, 0x02, 0x61, 0xFC, + 0x9B, 0x04, 0xF2, 0x48, 0xC6, 0x02, 0x2C, 0xFD, 0x5A, 0x02, 0x50, + 0xFE, 0x04, 0x01, 0x8A, 0xFF, 0x1D, 0x00, 0x17, 0x00, 0xA1, 0xFF, + 0xC9, 0x00, 0xCD, 0xFE, 0x6C, 0x01, 0xEA, 0xFE, 0xF3, 0xFE, 0x70, + 0x48, 0xFF, 0x08, 0x94, 0xFA, 0xAD, 0x03, 0xA3, 0xFD, 0x55, 0x01, + 0x6A, 0xFF, 0x26, 0x00, 0x00, 0x00, 0x0F, 0x00, 0xC3, 0xFF, 0x71, + 0x00, 0x81, 0xFF, 0x1F, 0x00, 0x42, 0x01, 0x37, 0xFA, 0x7C, 0x46, + 0xE7, 0x0F, 0x08, 0xF8, 0xE6, 0x04, 0x0B, 0xFD, 0x99, 0x01, 0x4F, + 0xFF, 0x2E, 0x00, 0xFF, 0xFF, 0x08, 0x00, 0xE3, 0xFF, 0x1E, 0x00, + 0x2A, 0x00, 0xEF, 0xFE, 0x4D, 0x03, 0x7D, 0xF6, 0x2A, 0x43, 0x4B, + 0x17, 0xB0, 0xF5, 0xEF, 0x05, 0x93, 0xFC, 0xCB, 0x01, 0x3D, 0xFF, + 0x34, 0x00, 0xFE, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0xD4, 0xFF, 0xBF, + 0x00, 0xEB, 0xFD, 0xF3, 0x04, 0xCF, 0xF3, 0x98, 0x3E, 0xF3, 0x1E, + 0xB9, 0xF3, 0xB2, 0x06, 0x48, 0xFC, 0xE4, 0x01, 0x36, 0xFF, 0x36, + 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x16, 0x00, 0x96, 0xFF, 0x39, 0x01, + 0x1F, 0xFD, 0x28, 0x06, 0x2A, 0xF2, 0xF2, 0x38, 0xA0, 0x26, 0x4B, + 0xF2, 0x1C, 0x07, 0x32, 0xFC, 0xE0, 0x01, 0x3C, 0xFF, 0x35, 0x00, + 0xFD, 0xFF, 0xFD, 0xFF, 0x26, 0x00, 0x67, 0xFF, 0x94, 0x01, 0x90, + 0xFC, 0xE5, 0x06, 0x81, 0xF1, 0x6B, 0x32, 0x11, 0x2E, 0x8E, 0xF1, + 0x1D, 0x07, 0x58, 0xFC, 0xBC, 0x01, 0x52, 0xFF, 0x2E, 0x00, 0xFD, + 0xFF, 0xFD, 0xFF, 0x31, 0x00, 0x48, 0xFF, 0xCE, 0x01, 0x42, 0xFC, + 0x2A, 0x07, 0xBF, 0xF1, 0x40, 0x2B, 0x05, 0x35, 0xA6, 0xF1, 0xAB, + 0x06, 0xBF, 0xFC, 0x75, 0x01, 0x77, 0xFF, 0x21, 0x00, 0xFE, 0xFF, + 0xFD, 0xFF, 0x36, 0x00, 0x38, 0xFF, 0xE5, 0x01, 0x33, 0xFC, 0xFF, + 0x06, 0xC4, 0xF2, 0xB0, 0x23, 0x3B, 0x3B, 0xAD, 0xF2, 0xBF, 0x05, + 0x66, 0xFD, 0x0E, 0x01, 0xAC, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0x36, 0x00, 0x37, 0xFF, 0xDE, 0x01, 0x5F, 0xFC, 0x70, 0x06, + 0x6C, 0xF4, 0xFD, 0x1B, 0x7D, 0x40, 0xB7, 0xF4, 0x5D, 0x04, 0x49, + 0xFE, 0x88, 0x00, 0xEF, 0xFF, 0xF5, 0xFF, 0x04, 0x00, 0xFF, 0xFF, + 0x32, 0x00, 0x43, 0xFF, 0xBA, 0x01, 0xBC, 0xFC, 0x90, 0x05, 0x8E, + 0xF6, 0x68, 0x14, 0x99, 0x44, 0xCD, 0xF7, 0x8F, 0x02, 0x60, 0xFF, + 0xEA, 0xFF, 0x3E, 0x00, 0xD7, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x2B, + 0x00, 0x59, 0xFF, 0x81, 0x01, 0x42, 0xFD, 0x72, 0x04, 0xFF, 0xF8, + 0x2D, 0x0D, 0x69, 0x47, 0xEB, 0xFB, 0x63, 0x00, 0x9D, 0x00, 0x3C, + 0xFF, 0x93, 0x00, 0xB6, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00, + 0x76, 0xFF, 0x37, 0x01, 0xE4, 0xFD, 0x2C, 0x03, 0x94, 0xFB, 0x83, + 0x06, 0xCE, 0x48, 0x05, 0x01, 0xF5, 0xFD, 0xF0, 0x01, 0x87, 0xFE, + 0xEA, 0x00, 0x94, 0xFF, 0x1A, 0x00, 0x1A, 0x00, 0x96, 0xFF, 0xE3, + 0x00, 0x95, 0xFE, 0xD5, 0x01, 0x26, 0xFE, 0x98, 0x00, 0xBF, 0x48, + 0x00, 0x07, 0x61, 0xFB, 0x46, 0x03, 0xD6, 0xFD, 0x3D, 0x01, 0x73, + 0xFF, 0x23, 0x00, 0x00, 0x00, 0x11, 0x00, 0xB9, 0xFF, 0x8C, 0x00, + 0x4A, 0xFF, 0x83, 0x00, 0x91, 0x00, 0x91, 0xFB, 0x3D, 0x47, 0xB7, + 0x0D, 0xCD, 0xF8, 0x89, 0x04, 0x36, 0xFD, 0x86, 0x01, 0x57, 0xFF, + 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xD9, 0xFF, 0x37, 0x00, 0xF7, + 0xFF, 0x49, 0xFF, 0xB6, 0x02, 0x86, 0xF7, 0x53, 0x44, 0xFB, 0x14, + 0x61, 0xF6, 0xA4, 0x05, 0xB4, 0xFC, 0xBE, 0x01, 0x42, 0xFF, 0x32, + 0x00, 0xFF, 0xFF, 0x04, 0x00, 0xF7, 0xFF, 0xEA, 0xFF, 0x93, 0x00, + 0x36, 0xFE, 0x7D, 0x04, 0x85, 0xF4, 0x1F, 0x40, 0x94, 0x1C, 0x47, + 0xF4, 0x7E, 0x06, 0x5A, 0xFC, 0xDF, 0x01, 0x37, 0xFF, 0x36, 0x00, + 0xFE, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0xA8, 0xFF, 0x17, 0x01, 0x57, + 0xFD, 0xD6, 0x05, 0x90, 0xF2, 0xC8, 0x3A, 0x46, 0x24, 0xAA, 0xF2, + 0x06, 0x07, 0x32, 0xFC, 0xE5, 0x01, 0x39, 0xFF, 0x36, 0x00, 0xFD, + 0xFF, 0xFE, 0xFF, 0x22, 0x00, 0x74, 0xFF, 0x7C, 0x01, 0xB5, 0xFC, + 0xB8, 0x06, 0x9C, 0xF1, 0x81, 0x34, 0xD1, 0x2B, 0xB3, 0xF1, 0x29, + 0x07, 0x46, 0xFC, 0xCA, 0x01, 0x4A, 0xFF, 0x30, 0x00, 0xFD, 0xFF, + 0xFD, 0xFF, 0x2E, 0x00, 0x50, 0xFF, 0xC0, 0x01, 0x53, 0xFC, 0x21, + 0x07, 0x96, 0xF1, 0x82, 0x2D, 0xF2, 0x32, 0x86, 0xF1, 0xDB, 0x06, + 0x99, 0xFC, 0x8E, 0x01, 0x6A, 0xFF, 0x25, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x35, 0x00, 0x3B, 0xFF, 0xE2, 0x01, 0x31, 0xFC, 0x17, 0x07, + 0x61, 0xF2, 0x0A, 0x26, 0x6A, 0x39, 0x41, 0xF2, 0x15, 0x06, 0x2C, + 0xFD, 0x31, 0x01, 0x9B, 0xFF, 0x14, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, + 0x36, 0x00, 0x36, 0xFF, 0xE3, 0x01, 0x4C, 0xFC, 0xA6, 0x06, 0xDB, + 0xF3, 0x5B, 0x1E, 0xFC, 0x3E, 0xFA, 0xF3, 0xD7, 0x04, 0xFD, 0xFD, + 0xB4, 0x00, 0xD9, 0xFF, 0xFD, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33, + 0x00, 0x3E, 0xFF, 0xC8, 0x01, 0x9B, 0xFC, 0xDD, 0x05, 0xDC, 0xF5, + 0xB6, 0x16, 0x77, 0x43, 0xBD, 0xF6, 0x28, 0x03, 0x05, 0xFF, 0x1D, + 0x00, 0x25, 0x00, 0xE1, 0xFF, 0x08, 0x00, 0xFF, 0xFF, 0x2D, 0x00, + 0x51, 0xFF, 0x95, 0x01, 0x15, 0xFD, 0xCF, 0x04, 0x39, 0xF8, 0x59, + 0x0F, 0xAF, 0x46, 0x8B, 0xFA, 0x17, 0x01, 0x38, 0x00, 0x73, 0xFF, + 0x78, 0x00, 0xC0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x25, 0x00, 0x6C, + 0xFF, 0x4F, 0x01, 0xB0, 0xFD, 0x93, 0x03, 0xC7, 0xFA, 0x7D, 0x08, + 0x86, 0x48, 0x5A, 0xFF, 0xBA, 0xFE, 0x86, 0x01, 0xBF, 0xFE, 0xCF, + 0x00, 0x9E, 0xFF, 0x17, 0x00, 0x1C, 0x00, 0x8C, 0xFF, 0xFE, 0x00, + 0x5D, 0xFE, 0x3F, 0x02, 0x5E, 0xFD, 0x54, 0x02, 0xEC, 0x48, 0x13, + 0x05, 0x2E, 0xFC, 0xDE, 0x02, 0x0C, 0xFE, 0x24, 0x01, 0x7D, 0xFF, + 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0xAE, 0xFF, 0xA7, 0x00, 0x12, + 0xFF, 0xEA, 0x00, 0xD9, 0xFF, 0x03, 0xFD, 0xDC, 0x47, 0x95, 0x0B, + 0x96, 0xF9, 0x29, 0x04, 0x65, 0xFD, 0x71, 0x01, 0x5F, 0xFF, 0x29, + 0x00, 0x00, 0x00, 0x0C, 0x00, 0xD0, 0xFF, 0x51, 0x00, 0xC3, 0xFF, + 0xA6, 0xFF, 0x16, 0x02, 0xA9, 0xF8, 0x5C, 0x45, 0xB2, 0x12, 0x19, + 0xF7, 0x52, 0x05, 0xD8, 0xFC, 0xAF, 0x01, 0x47, 0xFF, 0x30, 0x00, + 0xFF, 0xFF, 0x06, 0x00, 0xEE, 0xFF, 0x01, 0x00, 0x66, 0x00, 0x85, + 0xFE, 0xFC, 0x03, 0x55, 0xF5, 0x8C, 0x41, 0x38, 0x1A, 0xE1, 0xF4, + 0x43, 0x06, 0x70, 0xFC, 0xD8, 0x01, 0x39, 0xFF, 0x35, 0x00, 0xFE, + 0xFF, 0x01, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0xF1, 0x00, 0x96, 0xFD, + 0x78, 0x05, 0x0E, 0xF3, 0x8A, 0x3C, 0xEA, 0x21, 0x19, 0xF3, 0xE6, + 0x06, 0x38, 0xFC, 0xE6, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, + 0xFE, 0xFF, 0x1D, 0x00, 0x82, 0xFF, 0x60, 0x01, 0xE0, 0xFC, 0x7F, + 0x06, 0xCC, 0xF1, 0x85, 0x36, 0x87, 0x29, 0xEB, 0xF1, 0x2A, 0x07, + 0x39, 0xFC, 0xD6, 0x01, 0x43, 0xFF, 0x33, 0x00, 0xFD, 0xFF, 0xFD, + 0xFF, 0x2B, 0x00, 0x59, 0xFF, 0xAE, 0x01, 0x6A, 0xFC, 0x0D, 0x07, + 0x80, 0xF1, 0xB8, 0x2F, 0xCF, 0x30, 0x7D, 0xF1, 0xFF, 0x06, 0x78, + 0xFC, 0xA5, 0x01, 0x5F, 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x34, 0x00, 0x40, 0xFF, 0xDB, 0x01, 0x35, 0xFC, 0x26, 0x07, 0x0E, + 0xF2, 0x60, 0x28, 0x82, 0x37, 0xED, 0xF1, 0x5E, 0x06, 0xF8, 0xFC, + 0x51, 0x01, 0x8A, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, + 0x00, 0x36, 0xFF, 0xE6, 0x01, 0x3E, 0xFC, 0xD3, 0x06, 0x56, 0xF3, + 0xBA, 0x20, 0x61, 0x3D, 0x56, 0xF3, 0x45, 0x05, 0xB7, 0xFD, 0xDE, + 0x00, 0xC5, 0xFF, 0x05, 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x35, 0x00, + 0x3A, 0xFF, 0xD3, 0x01, 0x7D, 0xFC, 0x23, 0x06, 0x32, 0xF5, 0x0C, + 0x19, 0x38, 0x42, 0xC7, 0xF5, 0xB8, 0x03, 0xAF, 0xFE, 0x4E, 0x00, + 0x0C, 0x00, 0xEA, 0xFF, 0x06, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4A, + 0xFF, 0xA7, 0x01, 0xEC, 0xFC, 0x28, 0x05, 0x77, 0xF7, 0x92, 0x11, + 0xD7, 0x45, 0x43, 0xF9, 0xC3, 0x01, 0xD6, 0xFF, 0xA9, 0xFF, 0x5E, + 0x00, 0xCB, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x28, 0x00, 0x63, 0xFF, + 0x66, 0x01, 0x7D, 0xFD, 0xF8, 0x03, 0xFB, 0xF9, 0x89, 0x0A, 0x1D, + 0x48, 0xC5, 0xFD, 0x7A, 0xFF, 0x1D, 0x01, 0xF7, 0xFE, 0xB4, 0x00, + 0xA9, 0xFF, 0x15, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x82, 0xFF, 0x18, + 0x01, 0x27, 0xFE, 0xA9, 0x02, 0x94, 0xFC, 0x24, 0x04, 0xF5, 0x48, + 0x39, 0x03, 0xF9, 0xFC, 0x74, 0x02, 0x42, 0xFE, 0x0B, 0x01, 0x87, + 0xFF, 0x1E, 0x00, 0x16, 0x00, 0xA4, 0xFF, 0xC2, 0x00, 0xDB, 0xFE, + 0x52, 0x01, 0x1B, 0xFF, 0x8D, 0xFE, 0x57, 0x48, 0x81, 0x09, 0x61, + 0xFA, 0xC6, 0x03, 0x96, 0xFD, 0x5B, 0x01, 0x68, 0xFF, 0x26, 0x00, + 0x00, 0x00, 0x0E, 0x00, 0xC6, 0xFF, 0x6B, 0x00, 0x8E, 0xFF, 0x06, + 0x00, 0x6E, 0x01, 0xE4, 0xF9, 0x48, 0x46, 0x75, 0x10, 0xD7, 0xF7, + 0xFC, 0x04, 0x00, 0xFD, 0x9E, 0x01, 0x4E, 0xFF, 0x2E, 0x00, 0xFF, + 0xFF, 0x07, 0x00, 0xE5, 0xFF, 0x18, 0x00, 0x36, 0x00, 0xD9, 0xFE, + 0x71, 0x03, 0x3F, 0xF6, 0xDB, 0x42, 0xE0, 0x17, 0x86, 0xF5, 0x00, + 0x06, 0x8C, 0xFC, 0xCE, 0x01, 0x3C, 0xFF, 0x34, 0x00, 0xFE, 0xFF, + 0x02, 0x00, 0x01, 0x00, 0xCF, 0xFF, 0xC9, 0x00, 0xDA, 0xFD, 0x0F, + 0x05, 0xA5, 0xF3, 0x31, 0x3E, 0x8A, 0x1F, 0x97, 0xF3, 0xBD, 0x06, + 0x44, 0xFC, 0xE5, 0x01, 0x36, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFF, + 0xFF, 0x17, 0x00, 0x92, 0xFF, 0x41, 0x01, 0x11, 0xFD, 0x3B, 0x06, + 0x14, 0xF2, 0x78, 0x38, 0x36, 0x27, 0x35, 0xF2, 0x20, 0x07, 0x33, + 0xFC, 0xDF, 0x01, 0x3E, 0xFF, 0x34, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, + 0x28, 0x00, 0x64, 0xFF, 0x9A, 0x01, 0x88, 0xFC, 0xEE, 0x06, 0x7E, + 0xF1, 0xE3, 0x31, 0x9F, 0x2E, 0x88, 0xF1, 0x19, 0x07, 0x5E, 0xFC, + 0xB7, 0x01, 0x54, 0xFF, 0x2D, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x32, + 0x00, 0x46, 0xFF, 0xD1, 0x01, 0x3F, 0xFC, 0x2B, 0x07, 0xCD, 0xF1, + 0xAE, 0x2A, 0x86, 0x35, 0xB1, 0xF1, 0x9D, 0x06, 0xCA, 0xFC, 0x6E, + 0x01, 0x7B, 0xFF, 0x20, 0x00, 0xFE, 0xFF, 0xFD, 0xFF, 0x36, 0x00, + 0x38, 0xFF, 0xE6, 0x01, 0x35, 0xFC, 0xF7, 0x06, 0xE0, 0xF2, 0x18, + 0x23, 0xAB, 0x3B, 0xCC, 0xF2, 0xA8, 0x05, 0x76, 0xFD, 0x04, 0x01, + 0xB1, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x38, + 0xFF, 0xDC, 0x01, 0x64, 0xFC, 0x62, 0x06, 0x93, 0xF4, 0x66, 0x1B, + 0xD9, 0x40, 0xEA, 0xF4, 0x3E, 0x04, 0x5D, 0xFE, 0x7D, 0x00, 0xF5, + 0xFF, 0xF3, 0xFF, 0x05, 0x00, 0xFF, 0xFF, 0x31, 0x00, 0x44, 0xFF, + 0xB7, 0x01, 0xC5, 0xFC, 0x7C, 0x05, 0xBC, 0xF6, 0xD5, 0x13, 0xDC, + 0x44, 0x14, 0xF8, 0x67, 0x02, 0x77, 0xFF, 0xDD, 0xFF, 0x44, 0x00, + 0xD5, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x5B, 0xFF, 0x7C, + 0x01, 0x4E, 0xFD, 0x5A, 0x04, 0x31, 0xF9, 0xA4, 0x0C, 0x90, 0x47, + 0x47, 0xFC, 0x36, 0x00, 0xB6, 0x00, 0x2E, 0xFF, 0x99, 0x00, 0xB3, + 0xFF, 0x12, 0x00, 0x00, 0x00, 0x22, 0x00, 0x78, 0xFF, 0x31, 0x01, + 0xF1, 0xFD, 0x12, 0x03, 0xC7, 0xFB, 0x07, 0x06, 0xDB, 0x48, 0x73, + 0x01, 0xC3, 0xFD, 0x0A, 0x02, 0x79, 0xFE, 0xF1, 0x00, 0x91, 0xFF, + 0x1B, 0x00, 0x19, 0x00, 0x99, 0xFF, 0xDD, 0x00, 0xA3, 0xFE, 0xBB, + 0x01, 0x58, 0xFE, 0x2D, 0x00, 0xAF, 0x48, 0x7E, 0x07, 0x2E, 0xFB, + 0x60, 0x03, 0xC9, 0xFD, 0x43, 0x01, 0x71, 0xFF, 0x24, 0x00, 0x00, + 0x00, 0x10, 0x00, 0xBB, 0xFF, 0x85, 0x00, 0x58, 0xFF, 0x6A, 0x00, + 0xBE, 0x00, 0x38, 0xFB, 0x0F, 0x47, 0x42, 0x0E, 0x9B, 0xF8, 0xA1, + 0x04, 0x2B, 0xFD, 0x8B, 0x01, 0x55, 0xFF, 0x2C, 0x00, 0xFF, 0xFF, + 0x09, 0x00, 0xDC, 0xFF, 0x31, 0x00, 0x04, 0x00, 0x32, 0xFF, 0xDC, + 0x02, 0x42, 0xF7, 0x0B, 0x44, 0x8E, 0x15, 0x34, 0xF6, 0xB7, 0x05, + 0xAB, 0xFC, 0xC1, 0x01, 0x40, 0xFF, 0x33, 0x00, 0xFF, 0xFF, 0x04, + 0x00, 0xF9, 0xFF, 0xE4, 0xFF, 0x9F, 0x00, 0x23, 0xFE, 0x9B, 0x04, + 0x55, 0xF4, 0xC0, 0x3F, 0x2C, 0x1D, 0x22, 0xF4, 0x8C, 0x06, 0x55, + 0xFC, 0xE1, 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFE, 0xFF, 0x00, 0x00, + 0x11, 0x00, 0xA3, 0xFF, 0x20, 0x01, 0x49, 0xFD, 0xEB, 0x05, 0x74, + 0xF2, 0x54, 0x3A, 0xDD, 0x24, 0x91, 0xF2, 0x0C, 0x07, 0x32, 0xFC, + 0xE4, 0x01, 0x3A, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x23, + 0x00, 0x71, 0xFF, 0x82, 0x01, 0xAB, 0xFC, 0xC4, 0x06, 0x93, 0xF1, + 0xFD, 0x33, 0x62, 0x2C, 0xA8, 0xF1, 0x27, 0x07, 0x4A, 0xFC, 0xC7, + 0x01, 0x4C, 0xFF, 0x30, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2F, 0x00, + 0x4E, 0xFF, 0xC3, 0x01, 0x4E, 0xFC, 0x24, 0x07, 0x9E, 0xF1, 0xF2, + 0x2C, 0x78, 0x33, 0x8C, 0xF1, 0xD0, 0x06, 0xA2, 0xFC, 0x88, 0x01, + 0x6D, 0xFF, 0x24, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x35, 0x00, 0x3B, + 0xFF, 0xE3, 0x01, 0x31, 0xFC, 0x12, 0x07, 0x79, 0xF2, 0x73, 0x25, + 0xDF, 0x39, 0x5A, 0xF2, 0x00, 0x06, 0x3A, 0xFD, 0x28, 0x01, 0x9F, + 0xFF, 0x13, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x36, 0x00, 0x36, 0xFF, + 0xE2, 0x01, 0x50, 0xFC, 0x99, 0x06, 0xFE, 0xF3, 0xC3, 0x1D, 0x5E, + 0x3F, 0x27, 0xF4, 0xB9, 0x04, 0x10, 0xFE, 0xA9, 0x00, 0xDF, 0xFF, + 0xFB, 0xFF, 0x03, 0x00, 0xFF, 0xFF, 0x33, 0x00, 0x3F, 0xFF, 0xC5, + 0x01, 0xA3, 0xFC, 0xCA, 0x05, 0x07, 0xF6, 0x22, 0x16, 0xC3, 0x43, + 0xFE, 0xF6, 0x02, 0x03, 0x1B, 0xFF, 0x11, 0x00, 0x2B, 0x00, 0xDE, + 0xFF, 0x09, 0x00, 0xFF, 0xFF, 0x2D, 0x00, 0x53, 0xFF, 0x90, 0x01, + 0x20, 0xFD, 0xB8, 0x04, 0x6A, 0xF8, 0xCD, 0x0E, 0xE1, 0x46, 0xE1, + 0xFA, 0xEB, 0x00, 0x51, 0x00, 0x65, 0xFF, 0x7F, 0x00, 0xBE, 0xFF, + 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x6E, 0xFF, 0x49, 0x01, 0xBC, + 0xFD, 0x7A, 0x03, 0xFA, 0xFA, 0xFD, 0x07, 0x9C, 0x48, 0xC3, 0xFF, + 0x89, 0xFE, 0xA1, 0x01, 0xB1, 0xFE, 0xD6, 0x00, 0x9C, 0xFF, 0x18, + 0x00, 0x1C, 0x00, 0x8F, 0xFF, 0xF7, 0x00, 0x6B, 0xFE, 0x25, 0x02, + 0x91, 0xFD, 0xE3, 0x01, 0xE5, 0x48, 0x8D, 0x05, 0xFB, 0xFB, 0xF8, + 0x02, 0xFE, 0xFD, 0x2B, 0x01, 0x7A, 0xFF, 0x21, 0x00, 0x00, 0x00, + 0x13, 0x00, 0xB1, 0xFF, 0xA0, 0x00, 0x20, 0xFF, 0xD0, 0x00, 0x07, + 0x00, 0xA4, 0xFC, 0xB6, 0x47, 0x1C, 0x0C, 0x63, 0xF9, 0x42, 0x04, + 0x59, 0xFD, 0x76, 0x01, 0x5D, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x0B, + 0x00, 0xD2, 0xFF, 0x4A, 0x00, 0xD0, 0xFF, 0x8E, 0xFF, 0x3F, 0x02, + 0x5E, 0xF8, 0x1E, 0x45, 0x44, 0x13, 0xEA, 0xF6, 0x67, 0x05, 0xCF, + 0xFC, 0xB3, 0x01, 0x46, 0xFF, 0x31, 0x00, 0xFF, 0xFF, 0x05, 0x00, + 0xF0, 0xFF, 0xFB, 0xFF, 0x71, 0x00, 0x71, 0xFE, 0x1D, 0x04, 0x1F, + 0xF5, 0x32, 0x41, 0xCE, 0x1A, 0xBA, 0xF4, 0x53, 0x06, 0x6A, 0xFC, + 0xDA, 0x01, 0x38, 0xFF, 0x35, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x0A, + 0x00, 0xB6, 0xFF, 0xFB, 0x00, 0x85, 0xFD, 0x90, 0x05, 0xEC, 0xF2, + 0x1C, 0x3C, 0x81, 0x22, 0xFC, 0xF2, 0xEF, 0x06, 0x36, 0xFC, 0xE6, + 0x01, 0x37, 0xFF, 0x36, 0x00, 0xFD, 0xFF, 0xFE, 0xFF, 0x1E, 0x00, + 0x7F, 0xFF, 0x67, 0x01, 0xD5, 0xFC, 0x8E, 0x06, 0xBE, 0xF1, 0x06, + 0x36, 0x1A, 0x2A, 0xDC, 0xF1, 0x2A, 0x07, 0x3C, 0xFC, 0xD3, 0x01, + 0x44, 0xFF, 0x32, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x2C, 0x00, 0x57, + 0xFF, 0xB3, 0x01, 0x64, 0xFC, 0x13, 0x07, 0x83, 0xF1, 0x2C, 0x2F, + 0x5A, 0x31, 0x7D, 0xF1, 0xF7, 0x06, 0x80, 0xFC, 0x9F, 0x01, 0x61, + 0xFF, 0x29, 0x00, 0xFD, 0xFF, 0xFD, 0xFF, 0x34, 0x00, 0x3F, 0xFF, + 0xDD, 0x01, 0x34, 0xFC, 0x23, 0x07, 0x21, 0xF2, 0xCB, 0x27, 0xFE, + 0x37, 0x00, 0xF2, 0x4D, 0x06, 0x04, 0xFD, 0x49, 0x01, 0x8E, 0xFF, + 0x19, 0x00, 0xFF, 0xFF, 0xFD, 0xFF, 0x36, 0x00, 0x36, 0xFF, 0xE6, + 0x01, 0x41, 0xFC, 0xC8, 0x06, 0x76, 0xF3, 0x22, 0x20, 0xCA, 0x3D, + 0x7D, 0xF3, 0x2A, 0x05, 0xC8, 0xFD, 0xD4, 0x00, 0xCA, 0xFF, 0x03, + 0x00, 0x02, 0x00, 0xFE, 0xFF, 0x34, 0x00, 0x3B, 0xFF, 0xD1, 0x01, + 0x84, 0xFC, 0x12, 0x06, 0x5C, 0xF5, 0x76, 0x18, 0x89, 0x42, 0x02, + 0xF6, 0x94, 0x03, 0xC4, 0xFE, 0x42, 0x00, 0x12, 0x00, 0xE8, 0xFF, + 0x07, 0x00, 0xFF, 0xFF, 0x2F, 0x00, 0x4C, 0xFF, 0xA2, 0x01, 0xF6, + 0xFC, 0x12, 0x05, 0xA7, 0xF7, 0x03, 0x11, 0x10, 0x46, 0x93, 0xF9, + 0x98, 0x01, 0xEE, 0xFF, 0x9B, 0xFF, 0x64, 0x00, 0xC8, 0xFF, 0x0E, + 0x00, 0x00, 0x00, 0x27, 0x00, 0x65, 0xFF, 0x60, 0x01, 0x8A, 0xFD, + 0xDF, 0x03, 0x2E, 0xFA, 0x04, 0x0A, 0x3A, 0x48, 0x28, 0xFE, 0x4B, + 0xFF, 0x38, 0x01, 0xE9, 0xFE, 0xBB, 0x00, 0xA6, 0xFF, 0x16, 0x00, + 0x00, 0x00, 0x1E, 0x00, 0x84, 0xFF, 0x11, 0x01, 0x34, 0xFE, 0x8F, + 0x02, 0xC7, 0xFC, 0xAE, 0x03, 0xF7, 0x48, 0xAE, 0x03, 0xC7, 0xFC, + 0x8F, 0x02, 0x34, 0xFE, 0x11, 0x01, 0x84, 0xFF, 0x1E, 0x00, 0x00, + 0x00, 0xF4, 0xFF, 0x1A, 0x00, 0xFF, 0x00, 0x07, 0x03, 0x16, 0x06, + 0x7C, 0x09, 0x2A, 0x0C, 0x2E, 0x0D, 0x2A, 0x0C, 0x7C, 0x09, 0x16, + 0x06, 0x07, 0x03, 0xFF, 0x00, 0x1A, 0x00, 0xF4, 0xFF, 0xF2, 0xFF, + 0xA0, 0xFF, 0x71, 0xFF, 0x71, 0x00, 0x86, 0x03, 0x73, 0x08, 0x88, + 0x0D, 0x78, 0x10, 0xC9, 0x0F, 0xD5, 0x0B, 0x8B, 0x06, 0x28, 0x02, + 0xDF, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDC, + 0xFF, 0x80, 0xFF, 0x9A, 0xFF, 0x46, 0x01, 0x1E, 0x05, 0x5A, 0x0A, + 0xED, 0x0E, 0xAA, 0x10, 0xAF, 0x0E, 0xFD, 0x09, 0xCB, 0x04, 0x18, + 0x01, 0x8E, 0xFF, 0x85, 0xFF, 0xE1, 0xFF, 0xFC, 0xFF, 0xBD, 0xFF, + 0x6D, 0xFF, 0xF6, 0xFF, 0x65, 0x02, 0xE5, 0x06, 0x2B, 0x0C, 0xF3, + 0x0F, 0x60, 0x10, 0x3B, 0x0D, 0x16, 0x08, 0x3F, 0x03, 0x50, 0x00, + 0x6E, 0xFF, 0xA7, 0xFF, 0xF5, 0xFF, 0xEF, 0xFF, 0x9A, 0xFF, 0x75, + 0xFF, 0x91, 0x00, 0xC9, 0x03, 0xC8, 0x08, 0xCC, 0x0D, 0x89, 0x10, + 0x9F, 0x0F, 0x85, 0x0B, 0x3B, 0x06, 0xF4, 0x01, 0xCD, 0xFF, 0x72, + 0xFF, 0xC9, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD7, 0xFF, 0x7B, 0xFF, + 0xA5, 0xFF, 0x73, 0x01, 0x6A, 0x05, 0xAD, 0x0A, 0x21, 0x0F, 0xA6, + 0x10, 0x74, 0x0E, 0xA9, 0x09, 0x83, 0x04, 0xF0, 0x00, 0x85, 0xFF, + 0x8B, 0xFF, 0xE5, 0xFF, 0xFA, 0xFF, 0xB7, 0xFF, 0x6C, 0xFF, 0x0C, + 0x00, 0x9D, 0x02, 0x37, 0x07, 0x78, 0x0C, 0x15, 0x10, 0x47, 0x10, + 0xF3, 0x0C, 0xC2, 0x07, 0x01, 0x03, 0x35, 0x00, 0x6D, 0xFF, 0xAD, + 0xFF, 0xF7, 0xFF, 0xEB, 0xFF, 0x94, 0xFF, 0x7A, 0xFF, 0xB3, 0x00, + 0x0D, 0x04, 0x1C, 0x09, 0x0D, 0x0E, 0x97, 0x10, 0x73, 0x0F, 0x35, + 0x0B, 0xEB, 0x05, 0xC1, 0x01, 0xBD, 0xFF, 0x75, 0xFF, 0xCE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x77, 0xFF, 0xB3, 0xFF, 0xA1, + 0x01, 0xB7, 0x05, 0xFF, 0x0A, 0x53, 0x0F, 0x9E, 0x10, 0x37, 0x0E, + 0x55, 0x09, 0x3B, 0x04, 0xCB, 0x00, 0x7E, 0xFF, 0x90, 0xFF, 0xE9, + 0xFF, 0xF8, 0xFF, 0xB1, 0xFF, 0x6C, 0xFF, 0x24, 0x00, 0xD8, 0x02, + 0x8A, 0x07, 0xC2, 0x0C, 0x34, 0x10, 0x2A, 0x10, 0xAA, 0x0C, 0x6F, + 0x07, 0xC4, 0x02, 0x1C, 0x00, 0x6C, 0xFF, 0xB3, 0xFF, 0xF9, 0xFF, + 0xE8, 0xFF, 0x8E, 0xFF, 0x80, 0xFF, 0xD7, 0x00, 0x53, 0x04, 0x71, + 0x09, 0x4C, 0x0E, 0xA1, 0x10, 0x43, 0x0F, 0xE3, 0x0A, 0x9D, 0x05, + 0x91, 0x01, 0xAE, 0xFF, 0x79, 0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC2, 0xFF, 0xD2, 0x01, 0x06, 0x06, + 0x50, 0x0B, 0x82, 0x0F, 0x93, 0x10, 0xF8, 0x0D, 0x00, 0x09, 0xF6, + 0x03, 0xA7, 0x00, 0x78, 0xFF, 0x96, 0xFF, 0xEC, 0xFF, 0xF6, 0xFF, + 0xAB, 0xFF, 0x6D, 0xFF, 0x3E, 0x00, 0x15, 0x03, 0xDE, 0x07, 0x0B, + 0x0D, 0x50, 0x10, 0x0A, 0x10, 0x5E, 0x0C, 0x1C, 0x07, 0x8A, 0x02, + 0x04, 0x00, 0x6C, 0xFF, 0xB9, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF, 0x89, + 0xFF, 0x88, 0xFF, 0xFD, 0x00, 0x9B, 0x04, 0xC5, 0x09, 0x88, 0x0E, + 0xA8, 0x10, 0x10, 0x0F, 0x91, 0x0A, 0x50, 0x05, 0x64, 0x01, 0xA1, + 0xFF, 0x7D, 0xFF, 0xD9, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF, + 0x71, 0xFF, 0xD3, 0xFF, 0x05, 0x02, 0x55, 0x06, 0xA0, 0x0B, 0xAD, + 0x0F, 0x84, 0x10, 0xB6, 0x0D, 0xAC, 0x08, 0xB3, 0x03, 0x86, 0x00, + 0x74, 0xFF, 0x9C, 0xFF, 0xF0, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF, 0x6F, + 0xFF, 0x5A, 0x00, 0x54, 0x03, 0x32, 0x08, 0x52, 0x0D, 0x68, 0x10, + 0xE6, 0x0F, 0x11, 0x0C, 0xCA, 0x06, 0x52, 0x02, 0xEF, 0xFF, 0x6E, + 0xFF, 0xBF, 0xFF, 0xFC, 0xFF, 0xDF, 0xFF, 0x84, 0xFF, 0x91, 0xFF, + 0x25, 0x01, 0xE4, 0x04, 0x19, 0x0A, 0xC2, 0x0E, 0xAA, 0x10, 0xDA, + 0x0E, 0x3E, 0x0A, 0x05, 0x05, 0x38, 0x01, 0x96, 0xFF, 0x81, 0xFF, + 0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE6, + 0xFF, 0x3A, 0x02, 0xA6, 0x06, 0xEF, 0x0B, 0xD6, 0x0F, 0x71, 0x10, + 0x71, 0x0D, 0x57, 0x08, 0x71, 0x03, 0x67, 0x00, 0x70, 0xFF, 0xA2, + 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x78, 0x00, + 0x95, 0x03, 0x86, 0x08, 0x98, 0x0D, 0x7C, 0x10, 0xC0, 0x0F, 0xC3, + 0x0B, 0x79, 0x06, 0x1C, 0x02, 0xDB, 0xFF, 0x70, 0xFF, 0xC5, 0xFF, + 0xFE, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9C, 0xFF, 0x50, + 0x01, 0x2F, 0x05, 0x6C, 0x0A, 0xF9, 0x0E, 0xA9, 0x10, 0xA2, 0x0E, + 0xEA, 0x09, 0xBB, 0x04, 0x0F, 0x01, 0x8C, 0xFF, 0x87, 0xFF, 0xE2, + 0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xFA, 0xFF, 0x71, 0x02, + 0xF7, 0x06, 0x3C, 0x0C, 0xFB, 0x0F, 0x5B, 0x10, 0x2B, 0x0D, 0x03, + 0x08, 0x31, 0x03, 0x4A, 0x00, 0x6E, 0xFF, 0xA8, 0xFF, 0xF5, 0xFF, + 0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x98, 0x00, 0xD8, 0x03, 0xDB, + 0x08, 0xDB, 0x0D, 0x8D, 0x10, 0x96, 0x0F, 0x73, 0x0B, 0x29, 0x06, + 0xE8, 0x01, 0xC9, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF, 0x00, + 0x00, 0xD6, 0xFF, 0x7A, 0xFF, 0xA8, 0xFF, 0x7D, 0x01, 0x7B, 0x05, + 0xBF, 0x0A, 0x2D, 0x0F, 0xA5, 0x10, 0x67, 0x0E, 0x96, 0x09, 0x73, + 0x04, 0xE7, 0x00, 0x84, 0xFF, 0x8C, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF, + 0xB6, 0xFF, 0x6C, 0xFF, 0x11, 0x00, 0xAA, 0x02, 0x4A, 0x07, 0x88, + 0x0C, 0x1C, 0x10, 0x41, 0x10, 0xE3, 0x0C, 0xAF, 0x07, 0xF3, 0x02, + 0x2F, 0x00, 0x6C, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEA, 0xFF, 0x93, + 0xFF, 0x7B, 0xFF, 0xBB, 0x00, 0x1C, 0x04, 0x2F, 0x09, 0x1B, 0x0E, + 0x9A, 0x10, 0x68, 0x0F, 0x23, 0x0B, 0xDA, 0x05, 0xB7, 0x01, 0xB9, + 0xFF, 0x76, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0xFF, + 0x76, 0xFF, 0xB6, 0xFF, 0xAC, 0x01, 0xC8, 0x05, 0x11, 0x0B, 0x5E, + 0x0F, 0x9C, 0x10, 0x29, 0x0E, 0x42, 0x09, 0x2C, 0x04, 0xC2, 0x00, + 0x7D, 0xFF, 0x92, 0xFF, 0xEA, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF, 0x6C, + 0xFF, 0x29, 0x00, 0xE6, 0x02, 0x9D, 0x07, 0xD3, 0x0C, 0x3B, 0x10, + 0x23, 0x10, 0x99, 0x0C, 0x5C, 0x07, 0xB7, 0x02, 0x16, 0x00, 0x6C, + 0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8D, 0xFF, 0x82, 0xFF, + 0xDF, 0x00, 0x63, 0x04, 0x84, 0x09, 0x59, 0x0E, 0xA3, 0x10, 0x38, + 0x0F, 0xD1, 0x0A, 0x8C, 0x05, 0x87, 0x01, 0xAB, 0xFF, 0x79, 0xFF, + 0xD5, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC6, + 0xFF, 0xDD, 0x01, 0x17, 0x06, 0x62, 0x0B, 0x8C, 0x0F, 0x90, 0x10, + 0xE9, 0x0D, 0xED, 0x08, 0xE7, 0x03, 0xA0, 0x00, 0x77, 0xFF, 0x97, + 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xA9, 0xFF, 0x6D, 0xFF, 0x44, 0x00, + 0x23, 0x03, 0xF1, 0x07, 0x1B, 0x0D, 0x55, 0x10, 0x02, 0x10, 0x4D, + 0x0C, 0x0A, 0x07, 0x7E, 0x02, 0xFF, 0xFF, 0x6D, 0xFF, 0xBA, 0xFF, + 0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x8A, 0xFF, 0x06, 0x01, 0xAB, + 0x04, 0xD8, 0x09, 0x95, 0x0E, 0xA9, 0x10, 0x05, 0x0F, 0x7F, 0x0A, + 0x40, 0x05, 0x5A, 0x01, 0x9F, 0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00, + 0x00, 0xFE, 0xFF, 0xC6, 0xFF, 0x70, 0xFF, 0xD7, 0xFF, 0x10, 0x02, + 0x67, 0x06, 0xB1, 0x0B, 0xB7, 0x0F, 0x80, 0x10, 0xA7, 0x0D, 0x99, + 0x08, 0xA4, 0x03, 0x7F, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0, 0xFF, + 0xF3, 0xFF, 0xA3, 0xFF, 0x70, 0xFF, 0x60, 0x00, 0x62, 0x03, 0x45, + 0x08, 0x62, 0x0D, 0x6C, 0x10, 0xDE, 0x0F, 0x00, 0x0C, 0xB8, 0x06, + 0x46, 0x02, 0xEA, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFD, 0xFF, 0x00, + 0x00, 0xDE, 0xFF, 0x83, 0xFF, 0x94, 0xFF, 0x2F, 0x01, 0xF4, 0x04, + 0x2B, 0x0A, 0xCE, 0x0E, 0xAA, 0x10, 0xCE, 0x0E, 0x2B, 0x0A, 0xF4, + 0x04, 0x2F, 0x01, 0x94, 0xFF, 0x83, 0xFF, 0xDE, 0xFF, 0xFD, 0xFF, + 0xC0, 0xFF, 0x6E, 0xFF, 0xEA, 0xFF, 0x46, 0x02, 0xB8, 0x06, 0x00, + 0x0C, 0xDE, 0x0F, 0x6C, 0x10, 0x62, 0x0D, 0x45, 0x08, 0x62, 0x03, + 0x60, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D, + 0xFF, 0x73, 0xFF, 0x7F, 0x00, 0xA4, 0x03, 0x99, 0x08, 0xA7, 0x0D, + 0x80, 0x10, 0xB7, 0x0F, 0xB1, 0x0B, 0x67, 0x06, 0x10, 0x02, 0xD7, + 0xFF, 0x70, 0xFF, 0xC6, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA, 0xFF, + 0x7E, 0xFF, 0x9F, 0xFF, 0x5A, 0x01, 0x40, 0x05, 0x7F, 0x0A, 0x05, + 0x0F, 0xA9, 0x10, 0x95, 0x0E, 0xD8, 0x09, 0xAB, 0x04, 0x06, 0x01, + 0x8A, 0xFF, 0x88, 0xFF, 0xE3, 0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D, + 0xFF, 0xFF, 0xFF, 0x7E, 0x02, 0x0A, 0x07, 0x4D, 0x0C, 0x02, 0x10, + 0x55, 0x10, 0x1B, 0x0D, 0xF1, 0x07, 0x23, 0x03, 0x44, 0x00, 0x6D, + 0xFF, 0xA9, 0xFF, 0xF6, 0xFF, 0xED, 0xFF, 0x97, 0xFF, 0x77, 0xFF, + 0xA0, 0x00, 0xE7, 0x03, 0xED, 0x08, 0xE9, 0x0D, 0x90, 0x10, 0x8C, + 0x0F, 0x62, 0x0B, 0x17, 0x06, 0xDD, 0x01, 0xC6, 0xFF, 0x73, 0xFF, + 0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x79, 0xFF, 0xAB, + 0xFF, 0x87, 0x01, 0x8C, 0x05, 0xD1, 0x0A, 0x38, 0x0F, 0xA3, 0x10, + 0x59, 0x0E, 0x84, 0x09, 0x63, 0x04, 0xDF, 0x00, 0x82, 0xFF, 0x8D, + 0xFF, 0xE7, 0xFF, 0xF9, 0xFF, 0xB4, 0xFF, 0x6C, 0xFF, 0x16, 0x00, + 0xB7, 0x02, 0x5C, 0x07, 0x99, 0x0C, 0x23, 0x10, 0x3B, 0x10, 0xD3, + 0x0C, 0x9D, 0x07, 0xE6, 0x02, 0x29, 0x00, 0x6C, 0xFF, 0xB0, 0xFF, + 0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7D, 0xFF, 0xC2, 0x00, 0x2C, + 0x04, 0x42, 0x09, 0x29, 0x0E, 0x9C, 0x10, 0x5E, 0x0F, 0x11, 0x0B, + 0xC8, 0x05, 0xAC, 0x01, 0xB6, 0xFF, 0x76, 0xFF, 0xD1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB9, 0xFF, 0xB7, 0x01, + 0xDA, 0x05, 0x23, 0x0B, 0x68, 0x0F, 0x9A, 0x10, 0x1B, 0x0E, 0x2F, + 0x09, 0x1C, 0x04, 0xBB, 0x00, 0x7B, 0xFF, 0x93, 0xFF, 0xEA, 0xFF, + 0xF7, 0xFF, 0xAE, 0xFF, 0x6C, 0xFF, 0x2F, 0x00, 0xF3, 0x02, 0xAF, + 0x07, 0xE3, 0x0C, 0x41, 0x10, 0x1C, 0x10, 0x88, 0x0C, 0x4A, 0x07, + 0xAA, 0x02, 0x11, 0x00, 0x6C, 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE6, + 0xFF, 0x8C, 0xFF, 0x84, 0xFF, 0xE7, 0x00, 0x73, 0x04, 0x96, 0x09, + 0x67, 0x0E, 0xA5, 0x10, 0x2D, 0x0F, 0xBF, 0x0A, 0x7B, 0x05, 0x7D, + 0x01, 0xA8, 0xFF, 0x7A, 0xFF, 0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF, + 0xCA, 0xFF, 0x72, 0xFF, 0xC9, 0xFF, 0xE8, 0x01, 0x29, 0x06, 0x73, + 0x0B, 0x96, 0x0F, 0x8D, 0x10, 0xDB, 0x0D, 0xDB, 0x08, 0xD8, 0x03, + 0x98, 0x00, 0x76, 0xFF, 0x99, 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA8, + 0xFF, 0x6E, 0xFF, 0x4A, 0x00, 0x31, 0x03, 0x03, 0x08, 0x2B, 0x0D, + 0x5B, 0x10, 0xFB, 0x0F, 0x3C, 0x0C, 0xF7, 0x06, 0x71, 0x02, 0xFA, + 0xFF, 0x6D, 0xFF, 0xBC, 0xFF, 0xFC, 0xFF, 0xE2, 0xFF, 0x87, 0xFF, + 0x8C, 0xFF, 0x0F, 0x01, 0xBB, 0x04, 0xEA, 0x09, 0xA2, 0x0E, 0xA9, + 0x10, 0xF9, 0x0E, 0x6C, 0x0A, 0x2F, 0x05, 0x50, 0x01, 0x9C, 0xFF, + 0x7F, 0xFF, 0xDB, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF, 0x70, + 0xFF, 0xDB, 0xFF, 0x1C, 0x02, 0x79, 0x06, 0xC3, 0x0B, 0xC0, 0x0F, + 0x7C, 0x10, 0x98, 0x0D, 0x86, 0x08, 0x95, 0x03, 0x78, 0x00, 0x72, + 0xFF, 0x9F, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA2, 0xFF, 0x70, 0xFF, + 0x67, 0x00, 0x71, 0x03, 0x57, 0x08, 0x71, 0x0D, 0x71, 0x10, 0xD6, + 0x0F, 0xEF, 0x0B, 0xA6, 0x06, 0x3A, 0x02, 0xE6, 0xFF, 0x6E, 0xFF, + 0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x96, + 0xFF, 0x38, 0x01, 0x05, 0x05, 0x3E, 0x0A, 0xDA, 0x0E, 0xAA, 0x10, + 0xC2, 0x0E, 0x19, 0x0A, 0xE4, 0x04, 0x25, 0x01, 0x91, 0xFF, 0x84, + 0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xBF, 0xFF, 0x6E, 0xFF, 0xEF, 0xFF, + 0x52, 0x02, 0xCA, 0x06, 0x11, 0x0C, 0xE6, 0x0F, 0x68, 0x10, 0x52, + 0x0D, 0x32, 0x08, 0x54, 0x03, 0x5A, 0x00, 0x6F, 0xFF, 0xA5, 0xFF, + 0xF4, 0xFF, 0xF0, 0xFF, 0x9C, 0xFF, 0x74, 0xFF, 0x86, 0x00, 0xB3, + 0x03, 0xAC, 0x08, 0xB6, 0x0D, 0x84, 0x10, 0xAD, 0x0F, 0xA0, 0x0B, + 0x55, 0x06, 0x05, 0x02, 0xD3, 0xFF, 0x71, 0xFF, 0xC7, 0xFF, 0xFE, + 0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA1, 0xFF, 0x64, 0x01, + 0x50, 0x05, 0x91, 0x0A, 0x10, 0x0F, 0xA8, 0x10, 0x88, 0x0E, 0xC5, + 0x09, 0x9B, 0x04, 0xFD, 0x00, 0x88, 0xFF, 0x89, 0xFF, 0xE4, 0xFF, + 0xFB, 0xFF, 0xB9, 0xFF, 0x6C, 0xFF, 0x04, 0x00, 0x8A, 0x02, 0x1C, + 0x07, 0x5E, 0x0C, 0x0A, 0x10, 0x50, 0x10, 0x0B, 0x0D, 0xDE, 0x07, + 0x15, 0x03, 0x3E, 0x00, 0x6D, 0xFF, 0xAB, 0xFF, 0xF6, 0xFF, 0xEC, + 0xFF, 0x96, 0xFF, 0x78, 0xFF, 0xA7, 0x00, 0xF6, 0x03, 0x00, 0x09, + 0xF8, 0x0D, 0x93, 0x10, 0x82, 0x0F, 0x50, 0x0B, 0x06, 0x06, 0xD2, + 0x01, 0xC2, 0xFF, 0x74, 0xFF, 0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0xD4, 0xFF, 0x79, 0xFF, 0xAE, 0xFF, 0x91, 0x01, 0x9D, 0x05, 0xE3, + 0x0A, 0x43, 0x0F, 0xA1, 0x10, 0x4C, 0x0E, 0x71, 0x09, 0x53, 0x04, + 0xD7, 0x00, 0x80, 0xFF, 0x8E, 0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB3, + 0xFF, 0x6C, 0xFF, 0x1C, 0x00, 0xC4, 0x02, 0x6F, 0x07, 0xAA, 0x0C, + 0x2A, 0x10, 0x34, 0x10, 0xC2, 0x0C, 0x8A, 0x07, 0xD8, 0x02, 0x24, + 0x00, 0x6C, 0xFF, 0xB1, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x90, 0xFF, + 0x7E, 0xFF, 0xCB, 0x00, 0x3B, 0x04, 0x55, 0x09, 0x37, 0x0E, 0x9E, + 0x10, 0x53, 0x0F, 0xFF, 0x0A, 0xB7, 0x05, 0xA1, 0x01, 0xB3, 0xFF, + 0x77, 0xFF, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x75, + 0xFF, 0xBD, 0xFF, 0xC1, 0x01, 0xEB, 0x05, 0x35, 0x0B, 0x73, 0x0F, + 0x97, 0x10, 0x0D, 0x0E, 0x1C, 0x09, 0x0D, 0x04, 0xB3, 0x00, 0x7A, + 0xFF, 0x94, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAD, 0xFF, 0x6D, 0xFF, + 0x35, 0x00, 0x01, 0x03, 0xC2, 0x07, 0xF3, 0x0C, 0x47, 0x10, 0x15, + 0x10, 0x78, 0x0C, 0x37, 0x07, 0x9D, 0x02, 0x0C, 0x00, 0x6C, 0xFF, + 0xB7, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF, 0xF0, + 0x00, 0x83, 0x04, 0xA9, 0x09, 0x74, 0x0E, 0xA6, 0x10, 0x21, 0x0F, + 0xAD, 0x0A, 0x6A, 0x05, 0x73, 0x01, 0xA5, 0xFF, 0x7B, 0xFF, 0xD7, + 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC9, 0xFF, 0x72, 0xFF, 0xCD, 0xFF, + 0xF4, 0x01, 0x3B, 0x06, 0x85, 0x0B, 0x9F, 0x0F, 0x89, 0x10, 0xCC, + 0x0D, 0xC8, 0x08, 0xC9, 0x03, 0x91, 0x00, 0x75, 0xFF, 0x9A, 0xFF, + 0xEF, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x50, 0x00, 0x3F, + 0x03, 0x16, 0x08, 0x3B, 0x0D, 0x60, 0x10, 0xF3, 0x0F, 0x2B, 0x0C, + 0xE5, 0x06, 0x65, 0x02, 0xF6, 0xFF, 0x6D, 0xFF, 0xBD, 0xFF, 0xFC, + 0xFF, 0xE1, 0xFF, 0x85, 0xFF, 0x8E, 0xFF, 0x18, 0x01, 0xCB, 0x04, + 0xFD, 0x09, 0xAF, 0x0E, 0xAA, 0x10, 0xED, 0x0E, 0x5A, 0x0A, 0x1E, + 0x05, 0x46, 0x01, 0x9A, 0xFF, 0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00, + 0xFD, 0xFF, 0xC3, 0xFF, 0x6F, 0xFF, 0xDF, 0xFF, 0x28, 0x02, 0x8B, + 0x06, 0xD5, 0x0B, 0xC9, 0x0F, 0x78, 0x10, 0x88, 0x0D, 0x73, 0x08, + 0x86, 0x03, 0x71, 0x00, 0x71, 0xFF, 0xA0, 0xFF, 0xF2, 0xFF, 0xF2, + 0xFF, 0xA1, 0xFF, 0x71, 0xFF, 0x6E, 0x00, 0x7F, 0x03, 0x6A, 0x08, + 0x81, 0x0D, 0x76, 0x10, 0xCD, 0x0F, 0xDD, 0x0B, 0x94, 0x06, 0x2E, + 0x02, 0xE1, 0xFF, 0x6F, 0xFF, 0xC3, 0xFF, 0xFD, 0xFF, 0x00, 0x00, + 0xDC, 0xFF, 0x80, 0xFF, 0x98, 0xFF, 0x42, 0x01, 0x16, 0x05, 0x50, + 0x0A, 0xE7, 0x0E, 0xAA, 0x10, 0xB5, 0x0E, 0x06, 0x0A, 0xD3, 0x04, + 0x1C, 0x01, 0x8F, 0xFF, 0x85, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF, 0xBE, + 0xFF, 0x6D, 0xFF, 0xF3, 0xFF, 0x5E, 0x02, 0xDC, 0x06, 0x23, 0x0C, + 0xEF, 0x0F, 0x63, 0x10, 0x43, 0x0D, 0x1F, 0x08, 0x46, 0x03, 0x53, + 0x00, 0x6E, 0xFF, 0xA6, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B, 0xFF, + 0x75, 0xFF, 0x8D, 0x00, 0xC1, 0x03, 0xBE, 0x08, 0xC4, 0x0D, 0x88, + 0x10, 0xA4, 0x0F, 0x8E, 0x0B, 0x43, 0x06, 0xF9, 0x01, 0xCF, 0xFF, + 0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF, 0x7C, + 0xFF, 0xA4, 0xFF, 0x6E, 0x01, 0x61, 0x05, 0xA3, 0x0A, 0x1C, 0x0F, + 0xA7, 0x10, 0x7B, 0x0E, 0xB2, 0x09, 0x8B, 0x04, 0xF4, 0x00, 0x86, + 0xFF, 0x8A, 0xFF, 0xE4, 0xFF, 0xFA, 0xFF, 0xB8, 0xFF, 0x6C, 0xFF, + 0x09, 0x00, 0x97, 0x02, 0x2E, 0x07, 0x6F, 0x0C, 0x11, 0x10, 0x4A, + 0x10, 0xFB, 0x0C, 0xCB, 0x07, 0x07, 0x03, 0x38, 0x00, 0x6D, 0xFF, + 0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF, 0xAF, + 0x00, 0x05, 0x04, 0x13, 0x09, 0x06, 0x0E, 0x96, 0x10, 0x78, 0x0F, + 0x3E, 0x0B, 0xF4, 0x05, 0xC7, 0x01, 0xBF, 0xFF, 0x74, 0xFF, 0xCE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0x78, 0xFF, 0xB1, 0xFF, + 0x9C, 0x01, 0xAE, 0x05, 0xF6, 0x0A, 0x4E, 0x0F, 0x9F, 0x10, 0x3E, + 0x0E, 0x5E, 0x09, 0x43, 0x04, 0xCF, 0x00, 0x7F, 0xFF, 0x90, 0xFF, + 0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x21, 0x00, 0xD2, + 0x02, 0x81, 0x07, 0xBA, 0x0C, 0x31, 0x10, 0x2E, 0x10, 0xB2, 0x0C, + 0x78, 0x07, 0xCB, 0x02, 0x1E, 0x00, 0x6C, 0xFF, 0xB2, 0xFF, 0xF9, + 0xFF, 0xE8, 0xFF, 0x8F, 0xFF, 0x80, 0xFF, 0xD3, 0x00, 0x4B, 0x04, + 0x67, 0x09, 0x45, 0x0E, 0xA0, 0x10, 0x48, 0x0F, 0xEC, 0x0A, 0xA6, + 0x05, 0x97, 0x01, 0xB0, 0xFF, 0x78, 0xFF, 0xD3, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xCD, 0xFF, 0x74, 0xFF, 0xC0, 0xFF, 0xCC, 0x01, 0xFD, + 0x05, 0x47, 0x0B, 0x7D, 0x0F, 0x94, 0x10, 0xFF, 0x0D, 0x0A, 0x09, + 0xFE, 0x03, 0xAB, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF, 0xF7, + 0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x3B, 0x00, 0x0E, 0x03, 0xD5, 0x07, + 0x03, 0x0D, 0x4D, 0x10, 0x0E, 0x10, 0x67, 0x0C, 0x25, 0x07, 0x91, + 0x02, 0x07, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFB, 0xFF, 0xE4, 0xFF, + 0x89, 0xFF, 0x87, 0xFF, 0xF9, 0x00, 0x93, 0x04, 0xBC, 0x09, 0x82, + 0x0E, 0xA7, 0x10, 0x16, 0x0F, 0x9A, 0x0A, 0x59, 0x05, 0x69, 0x01, + 0xA3, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC8, + 0xFF, 0x71, 0xFF, 0xD1, 0xFF, 0xFF, 0x01, 0x4C, 0x06, 0x97, 0x0B, + 0xA9, 0x0F, 0x86, 0x10, 0xBD, 0x0D, 0xB5, 0x08, 0xBA, 0x03, 0x8A, + 0x00, 0x74, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA5, 0xFF, + 0x6F, 0xFF, 0x57, 0x00, 0x4D, 0x03, 0x29, 0x08, 0x4B, 0x0D, 0x65, + 0x10, 0xEB, 0x0F, 0x1A, 0x0C, 0xD3, 0x06, 0x58, 0x02, 0xF1, 0xFF, + 0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x84, 0xFF, 0x90, + 0xFF, 0x21, 0x01, 0xDC, 0x04, 0x10, 0x0A, 0xBB, 0x0E, 0xAA, 0x10, + 0xE1, 0x0E, 0x47, 0x0A, 0x0D, 0x05, 0x3D, 0x01, 0x97, 0xFF, 0x81, + 0xFF, 0xDD, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC2, 0xFF, 0x6F, 0xFF, + 0xE4, 0xFF, 0x34, 0x02, 0x9D, 0x06, 0xE6, 0x0B, 0xD1, 0x0F, 0x73, + 0x10, 0x79, 0x0D, 0x61, 0x08, 0x78, 0x03, 0x6A, 0x00, 0x70, 0xFF, + 0xA1, 0xFF, 0xF2, 0xFF, 0xF1, 0xFF, 0x9F, 0xFF, 0x72, 0xFF, 0x74, + 0x00, 0x8E, 0x03, 0x7D, 0x08, 0x90, 0x0D, 0x7A, 0x10, 0xC4, 0x0F, + 0xCC, 0x0B, 0x82, 0x06, 0x22, 0x02, 0xDD, 0xFF, 0x6F, 0xFF, 0xC4, + 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDB, 0xFF, 0x7F, 0xFF, 0x9B, 0xFF, + 0x4B, 0x01, 0x26, 0x05, 0x63, 0x0A, 0xF3, 0x0E, 0xAA, 0x10, 0xA8, + 0x0E, 0xF4, 0x09, 0xC3, 0x04, 0x13, 0x01, 0x8D, 0xFF, 0x86, 0xFF, + 0xE1, 0xFF, 0xFC, 0xFF, 0xBC, 0xFF, 0x6D, 0xFF, 0xF8, 0xFF, 0x6B, + 0x02, 0xEE, 0x06, 0x34, 0x0C, 0xF7, 0x0F, 0x5D, 0x10, 0x33, 0x0D, + 0x0D, 0x08, 0x38, 0x03, 0x4D, 0x00, 0x6E, 0xFF, 0xA7, 0xFF, 0xF5, + 0xFF, 0xEE, 0xFF, 0x99, 0xFF, 0x76, 0xFF, 0x94, 0x00, 0xD0, 0x03, + 0xD1, 0x08, 0xD3, 0x0D, 0x8B, 0x10, 0x9A, 0x0F, 0x7C, 0x0B, 0x32, + 0x06, 0xEE, 0x01, 0xCB, 0xFF, 0x72, 0xFF, 0xCA, 0xFF, 0xFE, 0xFF, + 0x00, 0x00, 0xD6, 0xFF, 0x7B, 0xFF, 0xA7, 0xFF, 0x78, 0x01, 0x72, + 0x05, 0xB6, 0x0A, 0x27, 0x0F, 0xA5, 0x10, 0x6E, 0x0E, 0xA0, 0x09, + 0x7B, 0x04, 0xEC, 0x00, 0x85, 0xFF, 0x8B, 0xFF, 0xE5, 0xFF, 0xFA, + 0xFF, 0xB6, 0xFF, 0x6C, 0xFF, 0x0E, 0x00, 0xA4, 0x02, 0x41, 0x07, + 0x80, 0x0C, 0x19, 0x10, 0x44, 0x10, 0xEB, 0x0C, 0xB9, 0x07, 0xFA, + 0x02, 0x32, 0x00, 0x6D, 0xFF, 0xAE, 0xFF, 0xF7, 0xFF, 0xEB, 0xFF, + 0x93, 0xFF, 0x7B, 0xFF, 0xB7, 0x00, 0x15, 0x04, 0x26, 0x09, 0x14, + 0x0E, 0x98, 0x10, 0x6D, 0x0F, 0x2C, 0x0B, 0xE3, 0x05, 0xBC, 0x01, + 0xBB, 0xFF, 0x75, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, + 0xFF, 0x77, 0xFF, 0xB5, 0xFF, 0xA6, 0x01, 0xC0, 0x05, 0x08, 0x0B, + 0x58, 0x0F, 0x9D, 0x10, 0x30, 0x0E, 0x4B, 0x09, 0x34, 0x04, 0xC6, + 0x00, 0x7D, 0xFF, 0x91, 0xFF, 0xE9, 0xFF, 0xF8, 0xFF, 0xB0, 0xFF, + 0x6C, 0xFF, 0x27, 0x00, 0xDF, 0x02, 0x94, 0x07, 0xCA, 0x0C, 0x37, + 0x10, 0x27, 0x10, 0xA1, 0x0C, 0x65, 0x07, 0xBE, 0x02, 0x19, 0x00, + 0x6C, 0xFF, 0xB4, 0xFF, 0xF9, 0xFF, 0xE7, 0xFF, 0x8E, 0xFF, 0x81, + 0xFF, 0xDB, 0x00, 0x5B, 0x04, 0x7A, 0x09, 0x53, 0x0E, 0xA2, 0x10, + 0x3D, 0x0F, 0xDA, 0x0A, 0x95, 0x05, 0x8C, 0x01, 0xAD, 0xFF, 0x79, + 0xFF, 0xD4, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCC, 0xFF, 0x73, 0xFF, + 0xC4, 0xFF, 0xD7, 0x01, 0x0E, 0x06, 0x59, 0x0B, 0x87, 0x0F, 0x91, + 0x10, 0xF0, 0x0D, 0xF7, 0x08, 0xEF, 0x03, 0xA3, 0x00, 0x78, 0xFF, + 0x97, 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xAA, 0xFF, 0x6D, 0xFF, 0x41, + 0x00, 0x1C, 0x03, 0xE7, 0x07, 0x13, 0x0D, 0x52, 0x10, 0x06, 0x10, + 0x56, 0x0C, 0x13, 0x07, 0x84, 0x02, 0x02, 0x00, 0x6D, 0xFF, 0xBA, + 0xFF, 0xFB, 0xFF, 0xE3, 0xFF, 0x88, 0xFF, 0x89, 0xFF, 0x01, 0x01, + 0xA3, 0x04, 0xCE, 0x09, 0x8F, 0x0E, 0xA8, 0x10, 0x0A, 0x0F, 0x88, + 0x0A, 0x48, 0x05, 0x5F, 0x01, 0xA0, 0xFF, 0x7D, 0xFF, 0xD9, 0xFF, + 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0xFF, 0x70, 0xFF, 0xD5, 0xFF, 0x0B, + 0x02, 0x5E, 0x06, 0xA9, 0x0B, 0xB2, 0x0F, 0x82, 0x10, 0xAE, 0x0D, + 0xA2, 0x08, 0xAB, 0x03, 0x82, 0x00, 0x73, 0xFF, 0x9D, 0xFF, 0xF0, + 0xFF, 0xF3, 0xFF, 0xA4, 0xFF, 0x6F, 0xFF, 0x5D, 0x00, 0x5B, 0x03, + 0x3B, 0x08, 0x5A, 0x0D, 0x6A, 0x10, 0xE2, 0x0F, 0x09, 0x0C, 0xC1, + 0x06, 0x4C, 0x02, 0xEC, 0xFF, 0x6E, 0xFF, 0xC0, 0xFF, 0xFC, 0xFF, + 0xDF, 0xFF, 0x83, 0xFF, 0x93, 0xFF, 0x2A, 0x01, 0xEC, 0x04, 0x22, + 0x0A, 0xC8, 0x0E, 0xAB, 0x10, 0xD4, 0x0E, 0x35, 0x0A, 0xFD, 0x04, + 0x33, 0x01, 0x95, 0xFF, 0x82, 0xFF, 0xDE, 0xFF, 0x00, 0x00, 0xFD, + 0xFF, 0xC1, 0xFF, 0x6E, 0xFF, 0xE8, 0xFF, 0x40, 0x02, 0xAF, 0x06, + 0xF7, 0x0B, 0xDA, 0x0F, 0x6F, 0x10, 0x6A, 0x0D, 0x4E, 0x08, 0x6A, + 0x03, 0x64, 0x00, 0x70, 0xFF, 0xA3, 0xFF, 0xF3, 0xFF, 0xF1, 0xFF, + 0x9E, 0xFF, 0x72, 0xFF, 0x7B, 0x00, 0x9C, 0x03, 0x90, 0x08, 0x9F, + 0x0D, 0x7E, 0x10, 0xBB, 0x0F, 0xBA, 0x0B, 0x70, 0x06, 0x16, 0x02, + 0xD9, 0xFF, 0x70, 0xFF, 0xC5, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xDA, + 0xFF, 0x7E, 0xFF, 0x9D, 0xFF, 0x55, 0x01, 0x37, 0x05, 0x75, 0x0A, + 0xFF, 0x0E, 0xA9, 0x10, 0x9C, 0x0E, 0xE1, 0x09, 0xB3, 0x04, 0x0A, + 0x01, 0x8B, 0xFF, 0x87, 0xFF, 0xE2, 0xFF, 0xFB, 0xFF, 0xBB, 0xFF, + 0x6D, 0xFF, 0xFD, 0xFF, 0x77, 0x02, 0x01, 0x07, 0x45, 0x0C, 0xFF, + 0x0F, 0x58, 0x10, 0x23, 0x0D, 0xFA, 0x07, 0x2A, 0x03, 0x47, 0x00, + 0x6E, 0xFF, 0xA9, 0xFF, 0xF5, 0xFF, 0xED, 0xFF, 0x98, 0xFF, 0x77, + 0xFF, 0x9C, 0x00, 0xDF, 0x03, 0xE4, 0x08, 0xE2, 0x0D, 0x8E, 0x10, + 0x91, 0x0F, 0x6B, 0x0B, 0x20, 0x06, 0xE3, 0x01, 0xC8, 0xFF, 0x73, + 0xFF, 0xCB, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD5, 0xFF, 0x7A, 0xFF, + 0xAA, 0xFF, 0x82, 0x01, 0x83, 0x05, 0xC8, 0x0A, 0x32, 0x0F, 0xA4, + 0x10, 0x60, 0x0E, 0x8D, 0x09, 0x6B, 0x04, 0xE3, 0x00, 0x83, 0xFF, + 0x8D, 0xFF, 0xE6, 0xFF, 0xFA, 0xFF, 0xB5, 0xFF, 0x6C, 0xFF, 0x14, + 0x00, 0xB1, 0x02, 0x53, 0x07, 0x91, 0x0C, 0x20, 0x10, 0x3E, 0x10, + 0xDB, 0x0C, 0xA6, 0x07, 0xEC, 0x02, 0x2C, 0x00, 0x6C, 0xFF, 0xAF, + 0xFF, 0xF8, 0xFF, 0xEA, 0xFF, 0x92, 0xFF, 0x7C, 0xFF, 0xBE, 0x00, + 0x24, 0x04, 0x38, 0x09, 0x22, 0x0E, 0x9B, 0x10, 0x63, 0x0F, 0x1A, + 0x0B, 0xD1, 0x05, 0xB1, 0x01, 0xB8, 0xFF, 0x76, 0xFF, 0xD0, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0x76, 0xFF, 0xB8, 0xFF, 0xB1, + 0x01, 0xD1, 0x05, 0x1A, 0x0B, 0x63, 0x0F, 0x9B, 0x10, 0x22, 0x0E, + 0x38, 0x09, 0x24, 0x04, 0xBE, 0x00, 0x7C, 0xFF, 0x92, 0xFF, 0xEA, + 0xFF, 0xF8, 0xFF, 0xAF, 0xFF, 0x6C, 0xFF, 0x2C, 0x00, 0xEC, 0x02, + 0xA6, 0x07, 0xDB, 0x0C, 0x3E, 0x10, 0x20, 0x10, 0x91, 0x0C, 0x53, + 0x07, 0xB1, 0x02, 0x14, 0x00, 0x6C, 0xFF, 0xB5, 0xFF, 0xFA, 0xFF, + 0xE6, 0xFF, 0x8D, 0xFF, 0x83, 0xFF, 0xE3, 0x00, 0x6B, 0x04, 0x8D, + 0x09, 0x60, 0x0E, 0xA4, 0x10, 0x32, 0x0F, 0xC8, 0x0A, 0x83, 0x05, + 0x82, 0x01, 0xAA, 0xFF, 0x7A, 0xFF, 0xD5, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0xCB, 0xFF, 0x73, 0xFF, 0xC8, 0xFF, 0xE3, 0x01, 0x20, 0x06, + 0x6B, 0x0B, 0x91, 0x0F, 0x8E, 0x10, 0xE2, 0x0D, 0xE4, 0x08, 0xDF, + 0x03, 0x9C, 0x00, 0x77, 0xFF, 0x98, 0xFF, 0xED, 0xFF, 0xF5, 0xFF, + 0xA9, 0xFF, 0x6E, 0xFF, 0x47, 0x00, 0x2A, 0x03, 0xFA, 0x07, 0x23, + 0x0D, 0x58, 0x10, 0xFF, 0x0F, 0x45, 0x0C, 0x01, 0x07, 0x77, 0x02, + 0xFD, 0xFF, 0x6D, 0xFF, 0xBB, 0xFF, 0xFB, 0xFF, 0xE2, 0xFF, 0x87, + 0xFF, 0x8B, 0xFF, 0x0A, 0x01, 0xB3, 0x04, 0xE1, 0x09, 0x9C, 0x0E, + 0xA9, 0x10, 0xFF, 0x0E, 0x75, 0x0A, 0x37, 0x05, 0x55, 0x01, 0x9D, + 0xFF, 0x7E, 0xFF, 0xDA, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xC5, 0xFF, + 0x70, 0xFF, 0xD9, 0xFF, 0x16, 0x02, 0x70, 0x06, 0xBA, 0x0B, 0xBB, + 0x0F, 0x7E, 0x10, 0x9F, 0x0D, 0x90, 0x08, 0x9C, 0x03, 0x7B, 0x00, + 0x72, 0xFF, 0x9E, 0xFF, 0xF1, 0xFF, 0xF3, 0xFF, 0xA3, 0xFF, 0x70, + 0xFF, 0x64, 0x00, 0x6A, 0x03, 0x4E, 0x08, 0x6A, 0x0D, 0x6F, 0x10, + 0xDA, 0x0F, 0xF7, 0x0B, 0xAF, 0x06, 0x40, 0x02, 0xE8, 0xFF, 0x6E, + 0xFF, 0xC1, 0xFF, 0xFD, 0xFF, 0x00, 0x00, 0xDE, 0xFF, 0x82, 0xFF, + 0x95, 0xFF, 0x33, 0x01, 0xFD, 0x04, 0x35, 0x0A, 0xD4, 0x0E, 0xAB, + 0x10, 0xC8, 0x0E, 0x22, 0x0A, 0xEC, 0x04, 0x2A, 0x01, 0x93, 0xFF, + 0x83, 0xFF, 0xDF, 0xFF, 0xFC, 0xFF, 0xC0, 0xFF, 0x6E, 0xFF, 0xEC, + 0xFF, 0x4C, 0x02, 0xC1, 0x06, 0x09, 0x0C, 0xE2, 0x0F, 0x6A, 0x10, + 0x5A, 0x0D, 0x3B, 0x08, 0x5B, 0x03, 0x5D, 0x00, 0x6F, 0xFF, 0xA4, + 0xFF, 0xF3, 0xFF, 0xF0, 0xFF, 0x9D, 0xFF, 0x73, 0xFF, 0x82, 0x00, + 0xAB, 0x03, 0xA2, 0x08, 0xAE, 0x0D, 0x82, 0x10, 0xB2, 0x0F, 0xA9, + 0x0B, 0x5E, 0x06, 0x0B, 0x02, 0xD5, 0xFF, 0x70, 0xFF, 0xC7, 0xFF, + 0xFE, 0xFF, 0x00, 0x00, 0xD9, 0xFF, 0x7D, 0xFF, 0xA0, 0xFF, 0x5F, + 0x01, 0x48, 0x05, 0x88, 0x0A, 0x0A, 0x0F, 0xA8, 0x10, 0x8F, 0x0E, + 0xCE, 0x09, 0xA3, 0x04, 0x01, 0x01, 0x89, 0xFF, 0x88, 0xFF, 0xE3, + 0xFF, 0xFB, 0xFF, 0xBA, 0xFF, 0x6D, 0xFF, 0x02, 0x00, 0x84, 0x02, + 0x13, 0x07, 0x56, 0x0C, 0x06, 0x10, 0x52, 0x10, 0x13, 0x0D, 0xE7, + 0x07, 0x1C, 0x03, 0x41, 0x00, 0x6D, 0xFF, 0xAA, 0xFF, 0xF6, 0xFF, + 0xED, 0xFF, 0x97, 0xFF, 0x78, 0xFF, 0xA3, 0x00, 0xEF, 0x03, 0xF7, + 0x08, 0xF0, 0x0D, 0x91, 0x10, 0x87, 0x0F, 0x59, 0x0B, 0x0E, 0x06, + 0xD7, 0x01, 0xC4, 0xFF, 0x73, 0xFF, 0xCC, 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xD4, 0xFF, 0x79, 0xFF, 0xAD, 0xFF, 0x8C, 0x01, 0x95, 0x05, + 0xDA, 0x0A, 0x3D, 0x0F, 0xA2, 0x10, 0x53, 0x0E, 0x7A, 0x09, 0x5B, + 0x04, 0xDB, 0x00, 0x81, 0xFF, 0x8E, 0xFF, 0xE7, 0xFF, 0xF9, 0xFF, + 0xB4, 0xFF, 0x6C, 0xFF, 0x19, 0x00, 0xBE, 0x02, 0x65, 0x07, 0xA1, + 0x0C, 0x27, 0x10, 0x37, 0x10, 0xCA, 0x0C, 0x94, 0x07, 0xDF, 0x02, + 0x27, 0x00, 0x6C, 0xFF, 0xB0, 0xFF, 0xF8, 0xFF, 0xE9, 0xFF, 0x91, + 0xFF, 0x7D, 0xFF, 0xC6, 0x00, 0x34, 0x04, 0x4B, 0x09, 0x30, 0x0E, + 0x9D, 0x10, 0x58, 0x0F, 0x08, 0x0B, 0xC0, 0x05, 0xA6, 0x01, 0xB5, + 0xFF, 0x77, 0xFF, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, + 0x75, 0xFF, 0xBB, 0xFF, 0xBC, 0x01, 0xE3, 0x05, 0x2C, 0x0B, 0x6D, + 0x0F, 0x98, 0x10, 0x14, 0x0E, 0x26, 0x09, 0x15, 0x04, 0xB7, 0x00, + 0x7B, 0xFF, 0x93, 0xFF, 0xEB, 0xFF, 0xF7, 0xFF, 0xAE, 0xFF, 0x6D, + 0xFF, 0x32, 0x00, 0xFA, 0x02, 0xB9, 0x07, 0xEB, 0x0C, 0x44, 0x10, + 0x19, 0x10, 0x80, 0x0C, 0x41, 0x07, 0xA4, 0x02, 0x0E, 0x00, 0x6C, + 0xFF, 0xB6, 0xFF, 0xFA, 0xFF, 0xE5, 0xFF, 0x8B, 0xFF, 0x85, 0xFF, + 0xEC, 0x00, 0x7B, 0x04, 0xA0, 0x09, 0x6E, 0x0E, 0xA5, 0x10, 0x27, + 0x0F, 0xB6, 0x0A, 0x72, 0x05, 0x78, 0x01, 0xA7, 0xFF, 0x7B, 0xFF, + 0xD6, 0xFF, 0x00, 0x00, 0xFE, 0xFF, 0xCA, 0xFF, 0x72, 0xFF, 0xCB, + 0xFF, 0xEE, 0x01, 0x32, 0x06, 0x7C, 0x0B, 0x9A, 0x0F, 0x8B, 0x10, + 0xD3, 0x0D, 0xD1, 0x08, 0xD0, 0x03, 0x94, 0x00, 0x76, 0xFF, 0x99, + 0xFF, 0xEE, 0xFF, 0xF5, 0xFF, 0xA7, 0xFF, 0x6E, 0xFF, 0x4D, 0x00, + 0x38, 0x03, 0x0D, 0x08, 0x33, 0x0D, 0x5D, 0x10, 0xF7, 0x0F, 0x34, + 0x0C, 0xEE, 0x06, 0x6B, 0x02, 0xF8, 0xFF, 0x6D, 0xFF, 0xBC, 0xFF, + 0xFC, 0xFF, 0xE1, 0xFF, 0x86, 0xFF, 0x8D, 0xFF, 0x13, 0x01, 0xC3, + 0x04, 0xF4, 0x09, 0xA8, 0x0E, 0xAA, 0x10, 0xF3, 0x0E, 0x63, 0x0A, + 0x26, 0x05, 0x4B, 0x01, 0x9B, 0xFF, 0x7F, 0xFF, 0xDB, 0xFF, 0x00, + 0x00, 0xFD, 0xFF, 0xC4, 0xFF, 0x6F, 0xFF, 0xDD, 0xFF, 0x22, 0x02, + 0x82, 0x06, 0xCC, 0x0B, 0xC4, 0x0F, 0x7A, 0x10, 0x90, 0x0D, 0x7D, + 0x08, 0x8E, 0x03, 0x74, 0x00, 0x72, 0xFF, 0x9F, 0xFF, 0xF1, 0xFF, + 0xF2, 0xFF, 0xA1, 0xFF, 0x70, 0xFF, 0x6A, 0x00, 0x78, 0x03, 0x61, + 0x08, 0x79, 0x0D, 0x73, 0x10, 0xD1, 0x0F, 0xE6, 0x0B, 0x9D, 0x06, + 0x34, 0x02, 0xE4, 0xFF, 0x6F, 0xFF, 0xC2, 0xFF, 0xFD, 0xFF, 0x00, + 0x00, 0xDD, 0xFF, 0x81, 0xFF, 0x97, 0xFF, 0x3D, 0x01, 0x0D, 0x05, + 0x47, 0x0A, 0xE1, 0x0E, 0xAA, 0x10, 0xBB, 0x0E, 0x10, 0x0A, 0xDC, + 0x04, 0x21, 0x01, 0x90, 0xFF, 0x84, 0xFF, 0xE0, 0xFF, 0xFC, 0xFF, + 0xBE, 0xFF, 0x6D, 0xFF, 0xF1, 0xFF, 0x58, 0x02, 0xD3, 0x06, 0x1A, + 0x0C, 0xEB, 0x0F, 0x65, 0x10, 0x4B, 0x0D, 0x29, 0x08, 0x4D, 0x03, + 0x57, 0x00, 0x6F, 0xFF, 0xA5, 0xFF, 0xF4, 0xFF, 0xEF, 0xFF, 0x9B, + 0xFF, 0x74, 0xFF, 0x8A, 0x00, 0xBA, 0x03, 0xB5, 0x08, 0xBD, 0x0D, + 0x86, 0x10, 0xA9, 0x0F, 0x97, 0x0B, 0x4C, 0x06, 0xFF, 0x01, 0xD1, + 0xFF, 0x71, 0xFF, 0xC8, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0xD8, 0xFF, + 0x7C, 0xFF, 0xA3, 0xFF, 0x69, 0x01, 0x59, 0x05, 0x9A, 0x0A, 0x16, + 0x0F, 0xA7, 0x10, 0x82, 0x0E, 0xBC, 0x09, 0x93, 0x04, 0xF9, 0x00, + 0x87, 0xFF, 0x89, 0xFF, 0xE4, 0xFF, 0xFB, 0xFF, 0xB8, 0xFF, 0x6C, + 0xFF, 0x07, 0x00, 0x91, 0x02, 0x25, 0x07, 0x67, 0x0C, 0x0E, 0x10, + 0x4D, 0x10, 0x03, 0x0D, 0xD5, 0x07, 0x0E, 0x03, 0x3B, 0x00, 0x6D, + 0xFF, 0xAC, 0xFF, 0xF7, 0xFF, 0xEC, 0xFF, 0x95, 0xFF, 0x79, 0xFF, + 0xAB, 0x00, 0xFE, 0x03, 0x0A, 0x09, 0xFF, 0x0D, 0x94, 0x10, 0x7D, + 0x0F, 0x47, 0x0B, 0xFD, 0x05, 0xCC, 0x01, 0xC0, 0xFF, 0x74, 0xFF, + 0xCD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xD3, 0xFF, 0x78, 0xFF, 0xB0, + 0xFF, 0x97, 0x01, 0xA6, 0x05, 0xEC, 0x0A, 0x48, 0x0F, 0xA0, 0x10, + 0x45, 0x0E, 0x67, 0x09, 0x4B, 0x04, 0xD3, 0x00, 0x80, 0xFF, 0x8F, + 0xFF, 0xE8, 0xFF, 0xF9, 0xFF, 0xB2, 0xFF, 0x6C, 0xFF, 0x1E, 0x00, + 0xCB, 0x02, 0x78, 0x07, 0xB2, 0x0C, 0x2E, 0x10, 0x31, 0x10, 0xBA, + 0x0C, 0x81, 0x07, 0xD2, 0x02, 0x21, 0x00, 0x6C, 0xFF, 0xB2, 0xFF, + 0xF9, 0xFF, 0xE8, 0xFF, 0x90, 0xFF, 0x7F, 0xFF, 0xCF, 0x00, 0x43, + 0x04, 0x5E, 0x09, 0x3E, 0x0E, 0x9F, 0x10, 0x4E, 0x0F, 0xF6, 0x0A, + 0xAE, 0x05, 0x9C, 0x01, 0xB1, 0xFF, 0x78, 0xFF, 0xD2, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0x74, 0xFF, 0xBF, 0xFF, 0xC7, 0x01, + 0xF4, 0x05, 0x3E, 0x0B, 0x78, 0x0F, 0x96, 0x10, 0x06, 0x0E, 0x13, + 0x09, 0x05, 0x04, 0xAF, 0x00, 0x79, 0xFF, 0x95, 0xFF, 0xEC, 0xFF, + 0xF7, 0xFF, 0xAC, 0xFF, 0x6D, 0xFF, 0x38, 0x00, 0x07, 0x03, 0xCB, + 0x07, 0xFB, 0x0C, 0x4A, 0x10, 0x11, 0x10, 0x6F, 0x0C, 0x2E, 0x07, + 0x97, 0x02, 0x09, 0x00, 0x6C, 0xFF, 0xB8, 0xFF, 0xFA, 0xFF, 0xE4, + 0xFF, 0x8A, 0xFF, 0x86, 0xFF, 0xF4, 0x00, 0x8B, 0x04, 0xB2, 0x09, + 0x7B, 0x0E, 0xA7, 0x10, 0x1C, 0x0F, 0xA3, 0x0A, 0x61, 0x05, 0x6E, + 0x01, 0xA4, 0xFF, 0x7C, 0xFF, 0xD8, 0xFF, 0x00, 0x00, 0xFE, 0xFF, + 0xC8, 0xFF, 0x71, 0xFF, 0xCF, 0xFF, 0xF9, 0x01, 0x43, 0x06, 0x8E, + 0x0B, 0xA4, 0x0F, 0x88, 0x10, 0xC4, 0x0D, 0xBE, 0x08, 0xC1, 0x03, + 0x8D, 0x00, 0x75, 0xFF, 0x9B, 0xFF, 0xEF, 0xFF, 0xF4, 0xFF, 0xA6, + 0xFF, 0x6E, 0xFF, 0x53, 0x00, 0x46, 0x03, 0x1F, 0x08, 0x43, 0x0D, + 0x63, 0x10, 0xEF, 0x0F, 0x23, 0x0C, 0xDC, 0x06, 0x5E, 0x02, 0xF3, + 0xFF, 0x6D, 0xFF, 0xBE, 0xFF, 0xFC, 0xFF, 0xE0, 0xFF, 0x85, 0xFF, + 0x8F, 0xFF, 0x1C, 0x01, 0xD3, 0x04, 0x06, 0x0A, 0xB5, 0x0E, 0xAA, + 0x10, 0xE7, 0x0E, 0x50, 0x0A, 0x16, 0x05, 0x42, 0x01, 0x98, 0xFF, + 0x80, 0xFF, 0xDC, 0xFF, 0x00, 0x00, 0xFD, 0xFF, 0xC3, 0xFF, 0x6F, + 0xFF, 0xE1, 0xFF, 0x2E, 0x02, 0x94, 0x06, 0xDD, 0x0B, 0xCD, 0x0F, + 0x76, 0x10, 0x81, 0x0D, 0x6A, 0x08, 0x7F, 0x03, 0x6E, 0x00, 0x71, + 0xFF, 0xA1, 0xFF, 0xF2, 0xFF, 0x00, 0x00, 0x15, 0x00, 0xD1, 0xFF, + 0x8B, 0xFE, 0xBC, 0xFD, 0xE1, 0x00, 0x84, 0x09, 0xB0, 0x13, 0x47, + 0x18, 0xB0, 0x13, 0x84, 0x09, 0xE1, 0x00, 0xBC, 0xFD, 0x8B, 0xFE, + 0xD1, 0xFF, 0x15, 0x00, 0xFD, 0xFF, 0x13, 0x00, 0xDA, 0x00, 0x30, + 0x00, 0x5D, 0xFC, 0xB3, 0xFC, 0x35, 0x0A, 0xC2, 0x1C, 0x24, 0x20, + 0x48, 0x10, 0x5D, 0xFF, 0x74, 0xFB, 0x3A, 0xFF, 0xFB, 0x00, 0x42, + 0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2C, 0x00, 0xF3, 0x00, 0xAD, 0xFF, + 0xC5, 0xFB, 0x11, 0xFE, 0xAF, 0x0D, 0xEF, 0x1E, 0x68, 0x1E, 0xBC, + 0x0C, 0xA7, 0xFD, 0xEA, 0xFB, 0xD3, 0xFF, 0xEE, 0x00, 0x24, 0x00, + 0xFA, 0xFF, 0xF7, 0xFF, 0x4C, 0x00, 0xFB, 0x00, 0x0C, 0xFF, 0x5F, + 0xFB, 0xE8, 0xFF, 0x3D, 0x11, 0x7E, 0x20, 0x13, 0x1C, 0x4C, 0x09, + 0x6A, 0xFC, 0x8C, 0xFC, 0x4E, 0x00, 0xD1, 0x00, 0x0E, 0x00, 0xFD, + 0xFF, 0xF7, 0xFF, 0x72, 0x00, 0xEC, 0x00, 0x55, 0xFE, 0x3D, 0xFB, + 0x37, 0x02, 0xBE, 0x14, 0x5D, 0x21, 0x40, 0x19, 0x18, 0x06, 0xA2, + 0xFB, 0x47, 0xFD, 0xA7, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0xFF, 0x9B, 0x00, 0xC0, 0x00, 0x92, 0xFD, 0x73, + 0xFB, 0xF2, 0x04, 0x0E, 0x18, 0x81, 0x21, 0x0C, 0x16, 0x37, 0x03, + 0x47, 0xFB, 0x0B, 0xFE, 0xDF, 0x00, 0x82, 0x00, 0xF9, 0xFF, 0xFE, + 0xFF, 0x08, 0x00, 0xC3, 0x00, 0x74, 0x00, 0xD2, 0xFC, 0x10, 0xFC, + 0x08, 0x08, 0x0A, 0x1B, 0xE9, 0x20, 0x9A, 0x12, 0xBE, 0x00, 0x49, + 0xFB, 0xC8, 0xFE, 0xF9, 0x00, 0x5A, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, + 0x1B, 0x00, 0xE4, 0x00, 0x06, 0x00, 0x24, 0xFC, 0x1E, 0xFD, 0x65, + 0x0B, 0x94, 0x1D, 0x9D, 0x1F, 0x0D, 0x0F, 0xB8, 0xFE, 0x96, 0xFB, + 0x72, 0xFF, 0xF9, 0x00, 0x37, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x36, + 0x00, 0xF8, 0x00, 0x78, 0xFF, 0x9B, 0xFB, 0xA6, 0xFE, 0xE9, 0x0E, + 0x8D, 0x1F, 0xAA, 0x1D, 0x87, 0x0B, 0x2B, 0xFD, 0x1E, 0xFC, 0x02, + 0x00, 0xE5, 0x00, 0x1C, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x58, 0x00, + 0xF9, 0x00, 0xCF, 0xFE, 0x4A, 0xFB, 0xA7, 0x00, 0x77, 0x12, 0xE0, + 0x20, 0x26, 0x1B, 0x28, 0x08, 0x18, 0xFC, 0xCB, 0xFC, 0x71, 0x00, + 0xC5, 0x00, 0x08, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x80, 0x00, 0xE1, + 0x00, 0x13, 0xFE, 0x45, 0xFB, 0x1D, 0x03, 0xEB, 0x15, 0x7F, 0x21, + 0x2D, 0x18, 0x0E, 0x05, 0x77, 0xFB, 0x8B, 0xFD, 0xBE, 0x00, 0x9D, + 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA9, 0x00, + 0xAA, 0x00, 0x4F, 0xFD, 0x9D, 0xFB, 0xFA, 0x05, 0x22, 0x19, 0x62, + 0x21, 0xE0, 0x14, 0x50, 0x02, 0x3E, 0xFB, 0x4E, 0xFE, 0xEB, 0x00, + 0x73, 0x00, 0xF7, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xD0, 0x00, 0x52, + 0x00, 0x93, 0xFC, 0x60, 0xFC, 0x2C, 0x09, 0xFA, 0x1B, 0x8A, 0x20, + 0x60, 0x11, 0xFD, 0xFF, 0x5C, 0xFB, 0x06, 0xFF, 0xFB, 0x00, 0x4D, + 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x23, 0x00, 0xED, 0x00, 0xD9, 0xFF, + 0xEF, 0xFB, 0x98, 0xFD, 0x99, 0x0C, 0x54, 0x1E, 0x02, 0x1F, 0xD2, + 0x0D, 0x20, 0xFE, 0xC0, 0xFB, 0xA7, 0xFF, 0xF4, 0x00, 0x2D, 0x00, + 0xF9, 0xFF, 0xF8, 0xFF, 0x41, 0x00, 0xFB, 0x00, 0x41, 0xFF, 0x78, + 0xFB, 0x4A, 0xFF, 0x25, 0x10, 0x16, 0x20, 0xDA, 0x1C, 0x56, 0x0A, + 0xBE, 0xFC, 0x56, 0xFC, 0x2C, 0x00, 0xDB, 0x00, 0x14, 0x00, 0xFD, + 0xFF, 0xF7, 0xFF, 0x66, 0x00, 0xF4, 0x00, 0x8F, 0xFE, 0x3F, 0xFB, + 0x75, 0x01, 0xAE, 0x13, 0x2C, 0x21, 0x2A, 0x1A, 0x0D, 0x07, 0xD4, + 0xFB, 0x0C, 0xFD, 0x8F, 0x00, 0xB7, 0x00, 0x03, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFA, 0xFF, 0x8E, 0x00, 0xD1, 0x00, 0xCF, 0xFD, 0x58, + 0xFB, 0x10, 0x04, 0x10, 0x17, 0x8A, 0x21, 0x10, 0x17, 0x10, 0x04, + 0x58, 0xFB, 0xCF, 0xFD, 0xD1, 0x00, 0x8E, 0x00, 0xFA, 0xFF, 0xFF, + 0xFF, 0x03, 0x00, 0xB7, 0x00, 0x8F, 0x00, 0x0C, 0xFD, 0xD4, 0xFB, + 0x0D, 0x07, 0x2A, 0x1A, 0x2C, 0x21, 0xAE, 0x13, 0x75, 0x01, 0x3F, + 0xFB, 0x8F, 0xFE, 0xF4, 0x00, 0x66, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, + 0x14, 0x00, 0xDB, 0x00, 0x2C, 0x00, 0x56, 0xFC, 0xBE, 0xFC, 0x56, + 0x0A, 0xDA, 0x1C, 0x16, 0x20, 0x25, 0x10, 0x4A, 0xFF, 0x78, 0xFB, + 0x41, 0xFF, 0xFB, 0x00, 0x41, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2D, + 0x00, 0xF4, 0x00, 0xA7, 0xFF, 0xC0, 0xFB, 0x20, 0xFE, 0xD2, 0x0D, + 0x02, 0x1F, 0x54, 0x1E, 0x99, 0x0C, 0x98, 0xFD, 0xEF, 0xFB, 0xD9, + 0xFF, 0xED, 0x00, 0x23, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4D, 0x00, + 0xFB, 0x00, 0x06, 0xFF, 0x5C, 0xFB, 0xFD, 0xFF, 0x60, 0x11, 0x8A, + 0x20, 0xFA, 0x1B, 0x2C, 0x09, 0x60, 0xFC, 0x93, 0xFC, 0x52, 0x00, + 0xD0, 0x00, 0x0D, 0x00, 0xFE, 0xFF, 0xF7, 0xFF, 0x73, 0x00, 0xEB, + 0x00, 0x4E, 0xFE, 0x3E, 0xFB, 0x50, 0x02, 0xE0, 0x14, 0x62, 0x21, + 0x22, 0x19, 0xFA, 0x05, 0x9D, 0xFB, 0x4F, 0xFD, 0xAA, 0x00, 0xA9, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0x9D, 0x00, + 0xBE, 0x00, 0x8B, 0xFD, 0x77, 0xFB, 0x0E, 0x05, 0x2D, 0x18, 0x7F, + 0x21, 0xEB, 0x15, 0x1D, 0x03, 0x45, 0xFB, 0x13, 0xFE, 0xE1, 0x00, + 0x80, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x08, 0x00, 0xC5, 0x00, 0x71, + 0x00, 0xCB, 0xFC, 0x18, 0xFC, 0x28, 0x08, 0x26, 0x1B, 0xE0, 0x20, + 0x77, 0x12, 0xA7, 0x00, 0x4A, 0xFB, 0xCF, 0xFE, 0xF9, 0x00, 0x58, + 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1C, 0x00, 0xE5, 0x00, 0x02, 0x00, + 0x1E, 0xFC, 0x2B, 0xFD, 0x87, 0x0B, 0xAA, 0x1D, 0x8D, 0x1F, 0xE9, + 0x0E, 0xA6, 0xFE, 0x9B, 0xFB, 0x78, 0xFF, 0xF8, 0x00, 0x36, 0x00, + 0xF9, 0xFF, 0xF8, 0xFF, 0x37, 0x00, 0xF9, 0x00, 0x72, 0xFF, 0x96, + 0xFB, 0xB8, 0xFE, 0x0D, 0x0F, 0x9D, 0x1F, 0x94, 0x1D, 0x65, 0x0B, + 0x1E, 0xFD, 0x24, 0xFC, 0x06, 0x00, 0xE4, 0x00, 0x1B, 0x00, 0xFC, + 0xFF, 0xF7, 0xFF, 0x5A, 0x00, 0xF9, 0x00, 0xC8, 0xFE, 0x49, 0xFB, + 0xBE, 0x00, 0x9A, 0x12, 0xE9, 0x20, 0x0A, 0x1B, 0x08, 0x08, 0x10, + 0xFC, 0xD2, 0xFC, 0x74, 0x00, 0xC3, 0x00, 0x08, 0x00, 0xFE, 0xFF, + 0xF9, 0xFF, 0x82, 0x00, 0xDF, 0x00, 0x0B, 0xFE, 0x47, 0xFB, 0x37, + 0x03, 0x0C, 0x16, 0x81, 0x21, 0x0E, 0x18, 0xF2, 0x04, 0x73, 0xFB, + 0x92, 0xFD, 0xC0, 0x00, 0x9B, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xAB, 0x00, 0xA7, 0x00, 0x47, 0xFD, 0xA2, 0xFB, + 0x18, 0x06, 0x40, 0x19, 0x5D, 0x21, 0xBE, 0x14, 0x37, 0x02, 0x3D, + 0xFB, 0x55, 0xFE, 0xEC, 0x00, 0x72, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, + 0x0E, 0x00, 0xD1, 0x00, 0x4E, 0x00, 0x8C, 0xFC, 0x6A, 0xFC, 0x4C, + 0x09, 0x13, 0x1C, 0x7E, 0x20, 0x3D, 0x11, 0xE8, 0xFF, 0x5F, 0xFB, + 0x0C, 0xFF, 0xFB, 0x00, 0x4C, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x24, + 0x00, 0xEE, 0x00, 0xD3, 0xFF, 0xEA, 0xFB, 0xA7, 0xFD, 0xBC, 0x0C, + 0x68, 0x1E, 0xEF, 0x1E, 0xAF, 0x0D, 0x11, 0xFE, 0xC5, 0xFB, 0xAD, + 0xFF, 0xF3, 0x00, 0x2C, 0x00, 0xFA, 0xFF, 0xF8, 0xFF, 0x42, 0x00, + 0xFB, 0x00, 0x3A, 0xFF, 0x74, 0xFB, 0x5D, 0xFF, 0x48, 0x10, 0x24, + 0x20, 0xC2, 0x1C, 0x35, 0x0A, 0xB3, 0xFC, 0x5D, 0xFC, 0x30, 0x00, + 0xDA, 0x00, 0x13, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x67, 0x00, 0xF3, + 0x00, 0x88, 0xFE, 0x3E, 0xFB, 0x8C, 0x01, 0xD0, 0x13, 0x33, 0x21, + 0x0D, 0x1A, 0xEE, 0x06, 0xCD, 0xFB, 0x13, 0xFD, 0x92, 0x00, 0xB6, + 0x00, 0x03, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFA, 0xFF, 0x90, 0x00, + 0xCF, 0x00, 0xC7, 0xFD, 0x5B, 0xFB, 0x2B, 0x04, 0x31, 0x17, 0x8A, + 0x21, 0xF0, 0x16, 0xF4, 0x03, 0x56, 0xFB, 0xD6, 0xFD, 0xD3, 0x00, + 0x8D, 0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0xB9, 0x00, 0x8C, + 0x00, 0x05, 0xFD, 0xDB, 0xFB, 0x2C, 0x07, 0x47, 0x1A, 0x25, 0x21, + 0x8B, 0x13, 0x5D, 0x01, 0x40, 0xFB, 0x97, 0xFE, 0xF5, 0x00, 0x64, + 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x15, 0x00, 0xDC, 0x00, 0x27, 0x00, + 0x50, 0xFC, 0xCA, 0xFC, 0x78, 0x0A, 0xF2, 0x1C, 0x07, 0x20, 0x02, + 0x10, 0x37, 0xFF, 0x7B, 0xFB, 0x47, 0xFF, 0xFB, 0x00, 0x40, 0x00, + 0xF8, 0xFF, 0xF9, 0xFF, 0x2E, 0x00, 0xF5, 0x00, 0xA2, 0xFF, 0xBB, + 0xFB, 0x31, 0xFE, 0xF5, 0x0D, 0x14, 0x1F, 0x3F, 0x1E, 0x77, 0x0C, + 0x8A, 0xFD, 0xF5, 0xFB, 0xDE, 0xFF, 0xEC, 0x00, 0x22, 0x00, 0xFB, + 0xFF, 0xF7, 0xFF, 0x4E, 0x00, 0xFB, 0x00, 0xFF, 0xFE, 0x59, 0xFB, + 0x11, 0x00, 0x83, 0x11, 0x96, 0x20, 0xE0, 0x1B, 0x0B, 0x09, 0x56, + 0xFC, 0x99, 0xFC, 0x56, 0x00, 0xCE, 0x00, 0x0D, 0x00, 0xFE, 0xFF, + 0xF8, 0xFF, 0x75, 0x00, 0xEA, 0x00, 0x47, 0xFE, 0x3E, 0xFB, 0x69, + 0x02, 0x02, 0x15, 0x66, 0x21, 0x04, 0x19, 0xDC, 0x05, 0x98, 0xFB, + 0x56, 0xFD, 0xAD, 0x00, 0xA8, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, + 0x00, 0xFD, 0xFF, 0x9E, 0x00, 0xBC, 0x00, 0x83, 0xFD, 0x7B, 0xFB, + 0x2B, 0x05, 0x4C, 0x18, 0x7C, 0x21, 0xCA, 0x15, 0x03, 0x03, 0x44, + 0xFB, 0x1A, 0xFE, 0xE2, 0x00, 0x7E, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, + 0x09, 0x00, 0xC6, 0x00, 0x6D, 0x00, 0xC3, 0xFC, 0x20, 0xFC, 0x49, + 0x08, 0x41, 0x1B, 0xD6, 0x20, 0x54, 0x12, 0x92, 0x00, 0x4C, 0xFB, + 0xD6, 0xFE, 0xFA, 0x00, 0x57, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1D, + 0x00, 0xE6, 0x00, 0xFD, 0xFF, 0x18, 0xFC, 0x38, 0xFD, 0xA9, 0x0B, + 0xC0, 0x1D, 0x7C, 0x1F, 0xC6, 0x0E, 0x95, 0xFE, 0x9F, 0xFB, 0x7E, + 0xFF, 0xF8, 0x00, 0x35, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x38, 0x00, + 0xF9, 0x00, 0x6C, 0xFF, 0x92, 0xFB, 0xC9, 0xFE, 0x2F, 0x0F, 0xAD, + 0x1F, 0x7D, 0x1D, 0x42, 0x0B, 0x12, 0xFD, 0x2A, 0xFC, 0x0B, 0x00, + 0xE3, 0x00, 0x1A, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5B, 0x00, 0xF8, + 0x00, 0xC1, 0xFE, 0x47, 0xFB, 0xD4, 0x00, 0xBC, 0x12, 0xF3, 0x20, + 0xEF, 0x1A, 0xE9, 0x07, 0x08, 0xFC, 0xD9, 0xFC, 0x78, 0x00, 0xC2, + 0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x83, 0x00, 0xDD, 0x00, + 0x04, 0xFE, 0x49, 0xFB, 0x52, 0x03, 0x2D, 0x16, 0x83, 0x21, 0xEF, + 0x17, 0xD5, 0x04, 0x6F, 0xFB, 0x9A, 0xFD, 0xC3, 0x00, 0x9A, 0x00, + 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xAD, 0x00, 0xA4, + 0x00, 0x40, 0xFD, 0xA8, 0xFB, 0x36, 0x06, 0x5E, 0x19, 0x58, 0x21, + 0x9C, 0x14, 0x1E, 0x02, 0x3D, 0xFB, 0x5D, 0xFE, 0xED, 0x00, 0x70, + 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F, 0x00, 0xD2, 0x00, 0x4A, 0x00, + 0x85, 0xFC, 0x74, 0xFC, 0x6D, 0x09, 0x2D, 0x1C, 0x72, 0x20, 0x1A, + 0x11, 0xD4, 0xFF, 0x61, 0xFB, 0x13, 0xFF, 0xFC, 0x00, 0x4A, 0x00, + 0xF7, 0xFF, 0xFA, 0xFF, 0x25, 0x00, 0xEF, 0x00, 0xCE, 0xFF, 0xE4, + 0xFB, 0xB5, 0xFD, 0xDE, 0x0C, 0x7C, 0x1E, 0xDD, 0x1E, 0x8C, 0x0D, + 0x01, 0xFE, 0xCA, 0xFB, 0xB3, 0xFF, 0xF3, 0x00, 0x2B, 0x00, 0xFA, + 0xFF, 0xF8, 0xFF, 0x44, 0x00, 0xFB, 0x00, 0x34, 0xFF, 0x71, 0xFB, + 0x71, 0xFF, 0x6B, 0x10, 0x32, 0x20, 0xA9, 0x1C, 0x13, 0x0A, 0xA8, + 0xFC, 0x63, 0xFC, 0x35, 0x00, 0xD9, 0x00, 0x12, 0x00, 0xFD, 0xFF, + 0xF7, 0xFF, 0x69, 0x00, 0xF2, 0x00, 0x81, 0xFE, 0x3E, 0xFB, 0xA4, + 0x01, 0xF2, 0x13, 0x3A, 0x21, 0xF0, 0x19, 0xCF, 0x06, 0xC7, 0xFB, + 0x1B, 0xFD, 0x96, 0x00, 0xB4, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0xFB, 0xFF, 0x92, 0x00, 0xCD, 0x00, 0xC0, 0xFD, 0x5E, 0xFB, + 0x47, 0x04, 0x51, 0x17, 0x8A, 0x21, 0xD0, 0x16, 0xD9, 0x03, 0x53, + 0xFB, 0xDE, 0xFD, 0xD5, 0x00, 0x8B, 0x00, 0xFA, 0xFF, 0xFF, 0xFF, + 0x04, 0x00, 0xBA, 0x00, 0x89, 0x00, 0xFD, 0xFC, 0xE2, 0xFB, 0x4B, + 0x07, 0x63, 0x1A, 0x1D, 0x21, 0x69, 0x13, 0x46, 0x01, 0x41, 0xFB, + 0x9E, 0xFE, 0xF5, 0x00, 0x63, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x16, + 0x00, 0xDD, 0x00, 0x23, 0x00, 0x49, 0xFC, 0xD5, 0xFC, 0x99, 0x0A, + 0x09, 0x1D, 0xF9, 0x1F, 0xDF, 0x0F, 0x24, 0xFF, 0x7F, 0xFB, 0x4D, + 0xFF, 0xFB, 0x00, 0x3F, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x2F, 0x00, + 0xF5, 0x00, 0x9C, 0xFF, 0xB6, 0xFB, 0x41, 0xFE, 0x17, 0x0E, 0x26, + 0x1F, 0x2B, 0x1E, 0x54, 0x0C, 0x7C, 0xFD, 0xFA, 0xFB, 0xE3, 0xFF, + 0xEB, 0x00, 0x21, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x50, 0x00, 0xFB, + 0x00, 0xF8, 0xFE, 0x57, 0xFB, 0x26, 0x00, 0xA6, 0x11, 0xA1, 0x20, + 0xC6, 0x1B, 0xEA, 0x08, 0x4D, 0xFC, 0xA0, 0xFC, 0x5A, 0x00, 0xCD, + 0x00, 0x0C, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x77, 0x00, 0xE9, 0x00, + 0x3F, 0xFE, 0x3F, 0xFB, 0x82, 0x02, 0x23, 0x15, 0x6B, 0x21, 0xE5, + 0x18, 0xBE, 0x05, 0x93, 0xFB, 0x5E, 0xFD, 0xAF, 0x00, 0xA6, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0xA0, 0x00, 0xB9, + 0x00, 0x7C, 0xFD, 0x80, 0xFB, 0x48, 0x05, 0x6B, 0x18, 0x79, 0x21, + 0xA9, 0x15, 0xE9, 0x02, 0x43, 0xFB, 0x21, 0xFE, 0xE3, 0x00, 0x7D, + 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x09, 0x00, 0xC7, 0x00, 0x69, 0x00, + 0xBC, 0xFC, 0x29, 0xFC, 0x69, 0x08, 0x5C, 0x1B, 0xCC, 0x20, 0x32, + 0x12, 0x7C, 0x00, 0x4E, 0xFB, 0xDD, 0xFE, 0xFA, 0x00, 0x56, 0x00, + 0xF7, 0xFF, 0xFB, 0xFF, 0x1D, 0x00, 0xE7, 0x00, 0xF8, 0xFF, 0x12, + 0xFC, 0x45, 0xFD, 0xCB, 0x0B, 0xD6, 0x1D, 0x6C, 0x1F, 0xA3, 0x0E, + 0x84, 0xFE, 0xA4, 0xFB, 0x84, 0xFF, 0xF7, 0x00, 0x34, 0x00, 0xF9, + 0xFF, 0xF8, 0xFF, 0x3A, 0x00, 0xFA, 0x00, 0x66, 0xFF, 0x8E, 0xFB, + 0xDB, 0xFE, 0x53, 0x0F, 0xBD, 0x1F, 0x66, 0x1D, 0x21, 0x0B, 0x05, + 0xFD, 0x30, 0xFC, 0x10, 0x00, 0xE2, 0x00, 0x19, 0x00, 0xFC, 0xFF, + 0xF7, 0xFF, 0x5D, 0x00, 0xF8, 0x00, 0xBA, 0xFE, 0x46, 0xFB, 0xEA, + 0x00, 0xDF, 0x12, 0xFC, 0x20, 0xD3, 0x1A, 0xC9, 0x07, 0x00, 0xFC, + 0xE0, 0xFC, 0x7B, 0x00, 0xC0, 0x00, 0x07, 0x00, 0xFF, 0xFF, 0xF9, + 0xFF, 0x85, 0x00, 0xDC, 0x00, 0xFC, 0xFD, 0x4A, 0xFB, 0x6C, 0x03, + 0x4E, 0x16, 0x85, 0x21, 0xCF, 0x17, 0xB8, 0x04, 0x6C, 0xFB, 0xA2, + 0xFD, 0xC5, 0x00, 0x98, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0x01, 0x00, 0xAE, 0x00, 0xA1, 0x00, 0x38, 0xFD, 0xAE, 0xFB, 0x54, + 0x06, 0x7C, 0x19, 0x53, 0x21, 0x7B, 0x14, 0x05, 0x02, 0x3D, 0xFB, + 0x64, 0xFE, 0xEE, 0x00, 0x6F, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x0F, + 0x00, 0xD4, 0x00, 0x46, 0x00, 0x7E, 0xFC, 0x7E, 0xFC, 0x8E, 0x09, + 0x46, 0x1C, 0x66, 0x20, 0xF7, 0x10, 0xC0, 0xFF, 0x64, 0xFB, 0x1A, + 0xFF, 0xFC, 0x00, 0x49, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x26, 0x00, + 0xF0, 0x00, 0xC9, 0xFF, 0xDF, 0xFB, 0xC4, 0xFD, 0x01, 0x0D, 0x90, + 0x1E, 0xCA, 0x1E, 0x69, 0x0D, 0xF1, 0xFD, 0xCF, 0xFB, 0xB8, 0xFF, + 0xF2, 0x00, 0x29, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x45, 0x00, 0xFC, + 0x00, 0x2D, 0xFF, 0x6D, 0xFB, 0x84, 0xFF, 0x8E, 0x10, 0x3F, 0x20, + 0x91, 0x1C, 0xF2, 0x09, 0x9D, 0xFC, 0x6A, 0xFC, 0x39, 0x00, 0xD7, + 0x00, 0x12, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6A, 0x00, 0xF1, 0x00, + 0x7A, 0xFE, 0x3D, 0xFB, 0xBC, 0x01, 0x14, 0x14, 0x41, 0x21, 0xD4, + 0x19, 0xB0, 0x06, 0xC0, 0xFB, 0x22, 0xFD, 0x99, 0x00, 0xB3, 0x00, + 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x93, 0x00, 0xCB, + 0x00, 0xB8, 0xFD, 0x61, 0xFB, 0x63, 0x04, 0x71, 0x17, 0x89, 0x21, + 0xB0, 0x16, 0xBD, 0x03, 0x51, 0xFB, 0xE6, 0xFD, 0xD7, 0x00, 0x8A, + 0x00, 0xFA, 0xFF, 0xFF, 0xFF, 0x05, 0x00, 0xBC, 0x00, 0x86, 0x00, + 0xF6, 0xFC, 0xE9, 0xFB, 0x6A, 0x07, 0x80, 0x1A, 0x15, 0x21, 0x47, + 0x13, 0x2F, 0x01, 0x42, 0xFB, 0xA5, 0xFE, 0xF6, 0x00, 0x61, 0x00, + 0xF7, 0xFF, 0xFC, 0xFF, 0x16, 0x00, 0xDF, 0x00, 0x1E, 0x00, 0x43, + 0xFC, 0xE1, 0xFC, 0xBB, 0x0A, 0x21, 0x1D, 0xEA, 0x1F, 0xBC, 0x0F, + 0x12, 0xFF, 0x82, 0xFB, 0x54, 0xFF, 0xFA, 0x00, 0x3D, 0x00, 0xF8, + 0xFF, 0xF9, 0xFF, 0x30, 0x00, 0xF6, 0x00, 0x96, 0xFF, 0xB1, 0xFB, + 0x51, 0xFE, 0x3A, 0x0E, 0x38, 0x1F, 0x16, 0x1E, 0x32, 0x0C, 0x6E, + 0xFD, 0x00, 0xFC, 0xE8, 0xFF, 0xEA, 0x00, 0x20, 0x00, 0xFB, 0xFF, + 0xF7, 0xFF, 0x51, 0x00, 0xFB, 0x00, 0xF1, 0xFE, 0x54, 0xFB, 0x3B, + 0x00, 0xC9, 0x11, 0xAD, 0x20, 0xAC, 0x1B, 0xCA, 0x08, 0x44, 0xFC, + 0xA7, 0xFC, 0x5E, 0x00, 0xCC, 0x00, 0x0B, 0x00, 0xFE, 0xFF, 0xF8, + 0xFF, 0x78, 0x00, 0xE7, 0x00, 0x38, 0xFE, 0x40, 0xFB, 0x9B, 0x02, + 0x45, 0x15, 0x6F, 0x21, 0xC7, 0x18, 0xA1, 0x05, 0x8E, 0xFB, 0x65, + 0xFD, 0xB2, 0x00, 0xA5, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xA2, 0x00, 0xB7, 0x00, 0x74, 0xFD, 0x84, 0xFB, 0x66, + 0x05, 0x8A, 0x18, 0x76, 0x21, 0x87, 0x15, 0xCF, 0x02, 0x41, 0xFB, + 0x29, 0xFE, 0xE5, 0x00, 0x7B, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0A, + 0x00, 0xC9, 0x00, 0x66, 0x00, 0xB5, 0xFC, 0x32, 0xFC, 0x89, 0x08, + 0x77, 0x1B, 0xC2, 0x20, 0x0F, 0x12, 0x66, 0x00, 0x50, 0xFB, 0xE4, + 0xFE, 0xFA, 0x00, 0x54, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x1E, 0x00, + 0xE8, 0x00, 0xF3, 0xFF, 0x0C, 0xFC, 0x53, 0xFD, 0xED, 0x0B, 0xEB, + 0x1D, 0x5A, 0x1F, 0x80, 0x0E, 0x73, 0xFE, 0xA8, 0xFB, 0x8A, 0xFF, + 0xF7, 0x00, 0x32, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3B, 0x00, 0xFA, + 0x00, 0x60, 0xFF, 0x8A, 0xFB, 0xED, 0xFE, 0x76, 0x0F, 0xCC, 0x1F, + 0x4F, 0x1D, 0xFF, 0x0A, 0xF9, 0xFC, 0x36, 0xFC, 0x15, 0x00, 0xE1, + 0x00, 0x18, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x5E, 0x00, 0xF7, 0x00, + 0xB3, 0xFE, 0x44, 0xFB, 0x01, 0x01, 0x02, 0x13, 0x04, 0x21, 0xB8, + 0x1A, 0xA9, 0x07, 0xF8, 0xFB, 0xE7, 0xFC, 0x7F, 0x00, 0xBF, 0x00, + 0x06, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, 0x86, 0x00, 0xDA, 0x00, 0xF5, + 0xFD, 0x4C, 0xFB, 0x87, 0x03, 0x6E, 0x16, 0x86, 0x21, 0xB0, 0x17, + 0x9C, 0x04, 0x68, 0xFB, 0xA9, 0xFD, 0xC7, 0x00, 0x96, 0x00, 0xFB, + 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x01, 0x00, 0xB0, 0x00, 0x9F, 0x00, + 0x31, 0xFD, 0xB4, 0xFB, 0x73, 0x06, 0x99, 0x19, 0x4D, 0x21, 0x59, + 0x14, 0xED, 0x01, 0x3D, 0xFB, 0x6B, 0xFE, 0xEF, 0x00, 0x6D, 0x00, + 0xF7, 0xFF, 0xFD, 0xFF, 0x10, 0x00, 0xD5, 0x00, 0x42, 0x00, 0x77, + 0xFC, 0x88, 0xFC, 0xAF, 0x09, 0x5F, 0x1C, 0x59, 0x20, 0xD4, 0x10, + 0xAC, 0xFF, 0x67, 0xFB, 0x20, 0xFF, 0xFC, 0x00, 0x48, 0x00, 0xF7, + 0xFF, 0xFA, 0xFF, 0x27, 0x00, 0xF0, 0x00, 0xC3, 0xFF, 0xD9, 0xFB, + 0xD3, 0xFD, 0x24, 0x0D, 0xA3, 0x1E, 0xB7, 0x1E, 0x46, 0x0D, 0xE2, + 0xFD, 0xD4, 0xFB, 0xBE, 0xFF, 0xF1, 0x00, 0x28, 0x00, 0xFA, 0xFF, + 0xF7, 0xFF, 0x46, 0x00, 0xFC, 0x00, 0x27, 0xFF, 0x6A, 0xFB, 0x98, + 0xFF, 0xB1, 0x10, 0x4C, 0x20, 0x78, 0x1C, 0xD1, 0x09, 0x93, 0xFC, + 0x71, 0xFC, 0x3D, 0x00, 0xD6, 0x00, 0x11, 0x00, 0xFD, 0xFF, 0xF7, + 0xFF, 0x6C, 0x00, 0xF0, 0x00, 0x72, 0xFE, 0x3D, 0xFB, 0xD4, 0x01, + 0x36, 0x14, 0x47, 0x21, 0xB6, 0x19, 0x91, 0x06, 0xBA, 0xFB, 0x29, + 0xFD, 0x9C, 0x00, 0xB1, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0xFB, 0xFF, 0x95, 0x00, 0xC9, 0x00, 0xB1, 0xFD, 0x65, 0xFB, 0x80, + 0x04, 0x90, 0x17, 0x88, 0x21, 0x8F, 0x16, 0xA2, 0x03, 0x4E, 0xFB, + 0xED, 0xFD, 0xD9, 0x00, 0x88, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x05, + 0x00, 0xBD, 0x00, 0x82, 0x00, 0xEF, 0xFC, 0xF0, 0xFB, 0x8A, 0x07, + 0x9C, 0x1A, 0x0D, 0x21, 0x24, 0x13, 0x18, 0x01, 0x43, 0xFB, 0xAC, + 0xFE, 0xF7, 0x00, 0x60, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x17, 0x00, + 0xE0, 0x00, 0x1A, 0x00, 0x3D, 0xFC, 0xED, 0xFC, 0xDD, 0x0A, 0x38, + 0x1D, 0xDB, 0x1F, 0x99, 0x0F, 0xFF, 0xFE, 0x86, 0xFB, 0x5A, 0xFF, + 0xFA, 0x00, 0x3C, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x31, 0x00, 0xF6, + 0x00, 0x90, 0xFF, 0xAD, 0xFB, 0x62, 0xFE, 0x5D, 0x0E, 0x49, 0x1F, + 0x01, 0x1E, 0x10, 0x0C, 0x60, 0xFD, 0x06, 0xFC, 0xEE, 0xFF, 0xE9, + 0x00, 0x1F, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x53, 0x00, 0xFB, 0x00, + 0xEB, 0xFE, 0x52, 0xFB, 0x51, 0x00, 0xEC, 0x11, 0xB7, 0x20, 0x91, + 0x1B, 0xA9, 0x08, 0x3B, 0xFC, 0xAE, 0xFC, 0x62, 0x00, 0xCA, 0x00, + 0x0B, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7A, 0x00, 0xE6, 0x00, 0x30, + 0xFE, 0x40, 0xFB, 0xB5, 0x02, 0x66, 0x15, 0x73, 0x21, 0xA9, 0x18, + 0x83, 0x05, 0x89, 0xFB, 0x6D, 0xFD, 0xB4, 0x00, 0xA3, 0x00, 0xFE, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xA3, 0x00, 0xB4, 0x00, + 0x6D, 0xFD, 0x89, 0xFB, 0x83, 0x05, 0xA9, 0x18, 0x73, 0x21, 0x66, + 0x15, 0xB5, 0x02, 0x40, 0xFB, 0x30, 0xFE, 0xE6, 0x00, 0x7A, 0x00, + 0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00, 0xCA, 0x00, 0x62, 0x00, 0xAE, + 0xFC, 0x3B, 0xFC, 0xA9, 0x08, 0x91, 0x1B, 0xB7, 0x20, 0xEC, 0x11, + 0x51, 0x00, 0x52, 0xFB, 0xEB, 0xFE, 0xFB, 0x00, 0x53, 0x00, 0xF7, + 0xFF, 0xFB, 0xFF, 0x1F, 0x00, 0xE9, 0x00, 0xEE, 0xFF, 0x06, 0xFC, + 0x60, 0xFD, 0x10, 0x0C, 0x01, 0x1E, 0x49, 0x1F, 0x5D, 0x0E, 0x62, + 0xFE, 0xAD, 0xFB, 0x90, 0xFF, 0xF6, 0x00, 0x31, 0x00, 0xF9, 0xFF, + 0xF8, 0xFF, 0x3C, 0x00, 0xFA, 0x00, 0x5A, 0xFF, 0x86, 0xFB, 0xFF, + 0xFE, 0x99, 0x0F, 0xDB, 0x1F, 0x38, 0x1D, 0xDD, 0x0A, 0xED, 0xFC, + 0x3D, 0xFC, 0x1A, 0x00, 0xE0, 0x00, 0x17, 0x00, 0xFC, 0xFF, 0xF7, + 0xFF, 0x60, 0x00, 0xF7, 0x00, 0xAC, 0xFE, 0x43, 0xFB, 0x18, 0x01, + 0x24, 0x13, 0x0D, 0x21, 0x9C, 0x1A, 0x8A, 0x07, 0xF0, 0xFB, 0xEF, + 0xFC, 0x82, 0x00, 0xBD, 0x00, 0x05, 0x00, 0xFF, 0xFF, 0xF9, 0xFF, + 0x88, 0x00, 0xD9, 0x00, 0xED, 0xFD, 0x4E, 0xFB, 0xA2, 0x03, 0x8F, + 0x16, 0x88, 0x21, 0x90, 0x17, 0x80, 0x04, 0x65, 0xFB, 0xB1, 0xFD, + 0xC9, 0x00, 0x95, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02, + 0x00, 0xB1, 0x00, 0x9C, 0x00, 0x29, 0xFD, 0xBA, 0xFB, 0x91, 0x06, + 0xB6, 0x19, 0x47, 0x21, 0x36, 0x14, 0xD4, 0x01, 0x3D, 0xFB, 0x72, + 0xFE, 0xF0, 0x00, 0x6C, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x11, 0x00, + 0xD6, 0x00, 0x3D, 0x00, 0x71, 0xFC, 0x93, 0xFC, 0xD1, 0x09, 0x78, + 0x1C, 0x4C, 0x20, 0xB1, 0x10, 0x98, 0xFF, 0x6A, 0xFB, 0x27, 0xFF, + 0xFC, 0x00, 0x46, 0x00, 0xF7, 0xFF, 0xFA, 0xFF, 0x28, 0x00, 0xF1, + 0x00, 0xBE, 0xFF, 0xD4, 0xFB, 0xE2, 0xFD, 0x46, 0x0D, 0xB7, 0x1E, + 0xA3, 0x1E, 0x24, 0x0D, 0xD3, 0xFD, 0xD9, 0xFB, 0xC3, 0xFF, 0xF0, + 0x00, 0x27, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x48, 0x00, 0xFC, 0x00, + 0x20, 0xFF, 0x67, 0xFB, 0xAC, 0xFF, 0xD4, 0x10, 0x59, 0x20, 0x5F, + 0x1C, 0xAF, 0x09, 0x88, 0xFC, 0x77, 0xFC, 0x42, 0x00, 0xD5, 0x00, + 0x10, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x6D, 0x00, 0xEF, 0x00, 0x6B, + 0xFE, 0x3D, 0xFB, 0xED, 0x01, 0x59, 0x14, 0x4D, 0x21, 0x99, 0x19, + 0x73, 0x06, 0xB4, 0xFB, 0x31, 0xFD, 0x9F, 0x00, 0xB0, 0x00, 0x01, + 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFB, 0xFF, 0x96, 0x00, 0xC7, 0x00, + 0xA9, 0xFD, 0x68, 0xFB, 0x9C, 0x04, 0xB0, 0x17, 0x86, 0x21, 0x6E, + 0x16, 0x87, 0x03, 0x4C, 0xFB, 0xF5, 0xFD, 0xDA, 0x00, 0x86, 0x00, + 0xF9, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0xBF, 0x00, 0x7F, 0x00, 0xE7, + 0xFC, 0xF8, 0xFB, 0xA9, 0x07, 0xB8, 0x1A, 0x04, 0x21, 0x02, 0x13, + 0x01, 0x01, 0x44, 0xFB, 0xB3, 0xFE, 0xF7, 0x00, 0x5E, 0x00, 0xF7, + 0xFF, 0xFC, 0xFF, 0x18, 0x00, 0xE1, 0x00, 0x15, 0x00, 0x36, 0xFC, + 0xF9, 0xFC, 0xFF, 0x0A, 0x4F, 0x1D, 0xCC, 0x1F, 0x76, 0x0F, 0xED, + 0xFE, 0x8A, 0xFB, 0x60, 0xFF, 0xFA, 0x00, 0x3B, 0x00, 0xF8, 0xFF, + 0xF9, 0xFF, 0x32, 0x00, 0xF7, 0x00, 0x8A, 0xFF, 0xA8, 0xFB, 0x73, + 0xFE, 0x80, 0x0E, 0x5A, 0x1F, 0xEB, 0x1D, 0xED, 0x0B, 0x53, 0xFD, + 0x0C, 0xFC, 0xF3, 0xFF, 0xE8, 0x00, 0x1E, 0x00, 0xFB, 0xFF, 0xF7, + 0xFF, 0x54, 0x00, 0xFA, 0x00, 0xE4, 0xFE, 0x50, 0xFB, 0x66, 0x00, + 0x0F, 0x12, 0xC2, 0x20, 0x77, 0x1B, 0x89, 0x08, 0x32, 0xFC, 0xB5, + 0xFC, 0x66, 0x00, 0xC9, 0x00, 0x0A, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, + 0x7B, 0x00, 0xE5, 0x00, 0x29, 0xFE, 0x41, 0xFB, 0xCF, 0x02, 0x87, + 0x15, 0x76, 0x21, 0x8A, 0x18, 0x66, 0x05, 0x84, 0xFB, 0x74, 0xFD, + 0xB7, 0x00, 0xA2, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0xFF, 0xA5, 0x00, 0xB2, 0x00, 0x65, 0xFD, 0x8E, 0xFB, 0xA1, 0x05, + 0xC7, 0x18, 0x6F, 0x21, 0x45, 0x15, 0x9B, 0x02, 0x40, 0xFB, 0x38, + 0xFE, 0xE7, 0x00, 0x78, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0B, 0x00, + 0xCC, 0x00, 0x5E, 0x00, 0xA7, 0xFC, 0x44, 0xFC, 0xCA, 0x08, 0xAC, + 0x1B, 0xAD, 0x20, 0xC9, 0x11, 0x3B, 0x00, 0x54, 0xFB, 0xF1, 0xFE, + 0xFB, 0x00, 0x51, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x20, 0x00, 0xEA, + 0x00, 0xE8, 0xFF, 0x00, 0xFC, 0x6E, 0xFD, 0x32, 0x0C, 0x16, 0x1E, + 0x38, 0x1F, 0x3A, 0x0E, 0x51, 0xFE, 0xB1, 0xFB, 0x96, 0xFF, 0xF6, + 0x00, 0x30, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x3D, 0x00, 0xFA, 0x00, + 0x54, 0xFF, 0x82, 0xFB, 0x12, 0xFF, 0xBC, 0x0F, 0xEA, 0x1F, 0x21, + 0x1D, 0xBB, 0x0A, 0xE1, 0xFC, 0x43, 0xFC, 0x1E, 0x00, 0xDF, 0x00, + 0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x61, 0x00, 0xF6, 0x00, 0xA5, + 0xFE, 0x42, 0xFB, 0x2F, 0x01, 0x47, 0x13, 0x15, 0x21, 0x80, 0x1A, + 0x6A, 0x07, 0xE9, 0xFB, 0xF6, 0xFC, 0x86, 0x00, 0xBC, 0x00, 0x05, + 0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8A, 0x00, 0xD7, 0x00, 0xE6, 0xFD, + 0x51, 0xFB, 0xBD, 0x03, 0xB0, 0x16, 0x89, 0x21, 0x71, 0x17, 0x63, + 0x04, 0x61, 0xFB, 0xB8, 0xFD, 0xCB, 0x00, 0x93, 0x00, 0xFB, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, 0xB3, 0x00, 0x99, 0x00, 0x22, + 0xFD, 0xC0, 0xFB, 0xB0, 0x06, 0xD4, 0x19, 0x41, 0x21, 0x14, 0x14, + 0xBC, 0x01, 0x3D, 0xFB, 0x7A, 0xFE, 0xF1, 0x00, 0x6A, 0x00, 0xF7, + 0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD7, 0x00, 0x39, 0x00, 0x6A, 0xFC, + 0x9D, 0xFC, 0xF2, 0x09, 0x91, 0x1C, 0x3F, 0x20, 0x8E, 0x10, 0x84, + 0xFF, 0x6D, 0xFB, 0x2D, 0xFF, 0xFC, 0x00, 0x45, 0x00, 0xF7, 0xFF, + 0xFA, 0xFF, 0x29, 0x00, 0xF2, 0x00, 0xB8, 0xFF, 0xCF, 0xFB, 0xF1, + 0xFD, 0x69, 0x0D, 0xCA, 0x1E, 0x90, 0x1E, 0x01, 0x0D, 0xC4, 0xFD, + 0xDF, 0xFB, 0xC9, 0xFF, 0xF0, 0x00, 0x26, 0x00, 0xFA, 0xFF, 0xF7, + 0xFF, 0x49, 0x00, 0xFC, 0x00, 0x1A, 0xFF, 0x64, 0xFB, 0xC0, 0xFF, + 0xF7, 0x10, 0x66, 0x20, 0x46, 0x1C, 0x8E, 0x09, 0x7E, 0xFC, 0x7E, + 0xFC, 0x46, 0x00, 0xD4, 0x00, 0x0F, 0x00, 0xFD, 0xFF, 0xF7, 0xFF, + 0x6F, 0x00, 0xEE, 0x00, 0x64, 0xFE, 0x3D, 0xFB, 0x05, 0x02, 0x7B, + 0x14, 0x53, 0x21, 0x7C, 0x19, 0x54, 0x06, 0xAE, 0xFB, 0x38, 0xFD, + 0xA1, 0x00, 0xAE, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFC, + 0xFF, 0x98, 0x00, 0xC5, 0x00, 0xA2, 0xFD, 0x6C, 0xFB, 0xB8, 0x04, + 0xCF, 0x17, 0x85, 0x21, 0x4E, 0x16, 0x6C, 0x03, 0x4A, 0xFB, 0xFC, + 0xFD, 0xDC, 0x00, 0x85, 0x00, 0xF9, 0xFF, 0xFF, 0xFF, 0x07, 0x00, + 0xC0, 0x00, 0x7B, 0x00, 0xE0, 0xFC, 0x00, 0xFC, 0xC9, 0x07, 0xD3, + 0x1A, 0xFC, 0x20, 0xDF, 0x12, 0xEA, 0x00, 0x46, 0xFB, 0xBA, 0xFE, + 0xF8, 0x00, 0x5D, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0x19, 0x00, 0xE2, + 0x00, 0x10, 0x00, 0x30, 0xFC, 0x05, 0xFD, 0x21, 0x0B, 0x66, 0x1D, + 0xBD, 0x1F, 0x53, 0x0F, 0xDB, 0xFE, 0x8E, 0xFB, 0x66, 0xFF, 0xFA, + 0x00, 0x3A, 0x00, 0xF8, 0xFF, 0xF9, 0xFF, 0x34, 0x00, 0xF7, 0x00, + 0x84, 0xFF, 0xA4, 0xFB, 0x84, 0xFE, 0xA3, 0x0E, 0x6C, 0x1F, 0xD6, + 0x1D, 0xCB, 0x0B, 0x45, 0xFD, 0x12, 0xFC, 0xF8, 0xFF, 0xE7, 0x00, + 0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, 0x56, 0x00, 0xFA, 0x00, 0xDD, + 0xFE, 0x4E, 0xFB, 0x7C, 0x00, 0x32, 0x12, 0xCC, 0x20, 0x5C, 0x1B, + 0x69, 0x08, 0x29, 0xFC, 0xBC, 0xFC, 0x69, 0x00, 0xC7, 0x00, 0x09, + 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7D, 0x00, 0xE3, 0x00, 0x21, 0xFE, + 0x43, 0xFB, 0xE9, 0x02, 0xA9, 0x15, 0x79, 0x21, 0x6B, 0x18, 0x48, + 0x05, 0x80, 0xFB, 0x7C, 0xFD, 0xB9, 0x00, 0xA0, 0x00, 0xFD, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xA6, 0x00, 0xAF, 0x00, 0x5E, + 0xFD, 0x93, 0xFB, 0xBE, 0x05, 0xE5, 0x18, 0x6B, 0x21, 0x23, 0x15, + 0x82, 0x02, 0x3F, 0xFB, 0x3F, 0xFE, 0xE9, 0x00, 0x77, 0x00, 0xF8, + 0xFF, 0xFE, 0xFF, 0x0C, 0x00, 0xCD, 0x00, 0x5A, 0x00, 0xA0, 0xFC, + 0x4D, 0xFC, 0xEA, 0x08, 0xC6, 0x1B, 0xA1, 0x20, 0xA6, 0x11, 0x26, + 0x00, 0x57, 0xFB, 0xF8, 0xFE, 0xFB, 0x00, 0x50, 0x00, 0xF7, 0xFF, + 0xFB, 0xFF, 0x21, 0x00, 0xEB, 0x00, 0xE3, 0xFF, 0xFA, 0xFB, 0x7C, + 0xFD, 0x54, 0x0C, 0x2B, 0x1E, 0x26, 0x1F, 0x17, 0x0E, 0x41, 0xFE, + 0xB6, 0xFB, 0x9C, 0xFF, 0xF5, 0x00, 0x2F, 0x00, 0xF9, 0xFF, 0xF8, + 0xFF, 0x3F, 0x00, 0xFB, 0x00, 0x4D, 0xFF, 0x7F, 0xFB, 0x24, 0xFF, + 0xDF, 0x0F, 0xF9, 0x1F, 0x09, 0x1D, 0x99, 0x0A, 0xD5, 0xFC, 0x49, + 0xFC, 0x23, 0x00, 0xDD, 0x00, 0x16, 0x00, 0xFC, 0xFF, 0xF7, 0xFF, + 0x63, 0x00, 0xF5, 0x00, 0x9E, 0xFE, 0x41, 0xFB, 0x46, 0x01, 0x69, + 0x13, 0x1D, 0x21, 0x63, 0x1A, 0x4B, 0x07, 0xE2, 0xFB, 0xFD, 0xFC, + 0x89, 0x00, 0xBA, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xFA, 0xFF, 0x8B, + 0x00, 0xD5, 0x00, 0xDE, 0xFD, 0x53, 0xFB, 0xD9, 0x03, 0xD0, 0x16, + 0x8A, 0x21, 0x51, 0x17, 0x47, 0x04, 0x5E, 0xFB, 0xC0, 0xFD, 0xCD, + 0x00, 0x92, 0x00, 0xFB, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x02, 0x00, + 0xB4, 0x00, 0x96, 0x00, 0x1B, 0xFD, 0xC7, 0xFB, 0xCF, 0x06, 0xF0, + 0x19, 0x3A, 0x21, 0xF2, 0x13, 0xA4, 0x01, 0x3E, 0xFB, 0x81, 0xFE, + 0xF2, 0x00, 0x69, 0x00, 0xF7, 0xFF, 0xFD, 0xFF, 0x12, 0x00, 0xD9, + 0x00, 0x35, 0x00, 0x63, 0xFC, 0xA8, 0xFC, 0x13, 0x0A, 0xA9, 0x1C, + 0x32, 0x20, 0x6B, 0x10, 0x71, 0xFF, 0x71, 0xFB, 0x34, 0xFF, 0xFB, + 0x00, 0x44, 0x00, 0xF8, 0xFF, 0xFA, 0xFF, 0x2B, 0x00, 0xF3, 0x00, + 0xB3, 0xFF, 0xCA, 0xFB, 0x01, 0xFE, 0x8C, 0x0D, 0xDD, 0x1E, 0x7C, + 0x1E, 0xDE, 0x0C, 0xB5, 0xFD, 0xE4, 0xFB, 0xCE, 0xFF, 0xEF, 0x00, + 0x25, 0x00, 0xFA, 0xFF, 0xF7, 0xFF, 0x4A, 0x00, 0xFC, 0x00, 0x13, + 0xFF, 0x61, 0xFB, 0xD4, 0xFF, 0x1A, 0x11, 0x72, 0x20, 0x2D, 0x1C, + 0x6D, 0x09, 0x74, 0xFC, 0x85, 0xFC, 0x4A, 0x00, 0xD2, 0x00, 0x0F, + 0x00, 0xFD, 0xFF, 0xF7, 0xFF, 0x70, 0x00, 0xED, 0x00, 0x5D, 0xFE, + 0x3D, 0xFB, 0x1E, 0x02, 0x9C, 0x14, 0x58, 0x21, 0x5E, 0x19, 0x36, + 0x06, 0xA8, 0xFB, 0x40, 0xFD, 0xA4, 0x00, 0xAD, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0xFC, 0xFF, 0x9A, 0x00, 0xC3, 0x00, 0x9A, + 0xFD, 0x6F, 0xFB, 0xD5, 0x04, 0xEF, 0x17, 0x83, 0x21, 0x2D, 0x16, + 0x52, 0x03, 0x49, 0xFB, 0x04, 0xFE, 0xDD, 0x00, 0x83, 0x00, 0xF9, + 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0xC2, 0x00, 0x78, 0x00, 0xD9, 0xFC, + 0x08, 0xFC, 0xE9, 0x07, 0xEF, 0x1A, 0xF3, 0x20, 0xBC, 0x12, 0xD4, + 0x00, 0x47, 0xFB, 0xC1, 0xFE, 0xF8, 0x00, 0x5B, 0x00, 0xF7, 0xFF, + 0xFC, 0xFF, 0x1A, 0x00, 0xE3, 0x00, 0x0B, 0x00, 0x2A, 0xFC, 0x12, + 0xFD, 0x42, 0x0B, 0x7D, 0x1D, 0xAD, 0x1F, 0x2F, 0x0F, 0xC9, 0xFE, + 0x92, 0xFB, 0x6C, 0xFF, 0xF9, 0x00, 0x38, 0x00, 0xF8, 0xFF, 0xF9, + 0xFF, 0x35, 0x00, 0xF8, 0x00, 0x7E, 0xFF, 0x9F, 0xFB, 0x95, 0xFE, + 0xC6, 0x0E, 0x7C, 0x1F, 0xC0, 0x1D, 0xA9, 0x0B, 0x38, 0xFD, 0x18, + 0xFC, 0xFD, 0xFF, 0xE6, 0x00, 0x1D, 0x00, 0xFB, 0xFF, 0xF7, 0xFF, + 0x57, 0x00, 0xFA, 0x00, 0xD6, 0xFE, 0x4C, 0xFB, 0x92, 0x00, 0x54, + 0x12, 0xD6, 0x20, 0x41, 0x1B, 0x49, 0x08, 0x20, 0xFC, 0xC3, 0xFC, + 0x6D, 0x00, 0xC6, 0x00, 0x09, 0x00, 0xFE, 0xFF, 0xF8, 0xFF, 0x7E, + 0x00, 0xE2, 0x00, 0x1A, 0xFE, 0x44, 0xFB, 0x03, 0x03, 0xCA, 0x15, + 0x7C, 0x21, 0x4C, 0x18, 0x2B, 0x05, 0x7B, 0xFB, 0x83, 0xFD, 0xBC, + 0x00, 0x9E, 0x00, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xA8, 0x00, 0xAD, 0x00, 0x56, 0xFD, 0x98, 0xFB, 0xDC, 0x05, 0x04, + 0x19, 0x66, 0x21, 0x02, 0x15, 0x69, 0x02, 0x3E, 0xFB, 0x47, 0xFE, + 0xEA, 0x00, 0x75, 0x00, 0xF8, 0xFF, 0xFE, 0xFF, 0x0D, 0x00, 0xCE, + 0x00, 0x56, 0x00, 0x99, 0xFC, 0x56, 0xFC, 0x0B, 0x09, 0xE0, 0x1B, + 0x96, 0x20, 0x83, 0x11, 0x11, 0x00, 0x59, 0xFB, 0xFF, 0xFE, 0xFB, + 0x00, 0x4E, 0x00, 0xF7, 0xFF, 0xFB, 0xFF, 0x22, 0x00, 0xEC, 0x00, + 0xDE, 0xFF, 0xF5, 0xFB, 0x8A, 0xFD, 0x77, 0x0C, 0x3F, 0x1E, 0x14, + 0x1F, 0xF5, 0x0D, 0x31, 0xFE, 0xBB, 0xFB, 0xA2, 0xFF, 0xF5, 0x00, + 0x2E, 0x00, 0xF9, 0xFF, 0xF8, 0xFF, 0x40, 0x00, 0xFB, 0x00, 0x47, + 0xFF, 0x7B, 0xFB, 0x37, 0xFF, 0x02, 0x10, 0x07, 0x20, 0xF2, 0x1C, + 0x78, 0x0A, 0xCA, 0xFC, 0x50, 0xFC, 0x27, 0x00, 0xDC, 0x00, 0x15, + 0x00, 0xFC, 0xFF, 0xF7, 0xFF, 0x64, 0x00, 0xF5, 0x00, 0x97, 0xFE, + 0x40, 0xFB, 0x5D, 0x01, 0x8B, 0x13, 0x25, 0x21, 0x47, 0x1A, 0x2C, + 0x07, 0xDB, 0xFB, 0x05, 0xFD, 0x8C, 0x00, 0xB9, 0x00, 0x04, 0x00, + 0xFF, 0xFF, 0xFA, 0xFF, 0x8D, 0x00, 0xD3, 0x00, 0xD6, 0xFD, 0x56, + 0xFB, 0xF4, 0x03, 0xF0, 0x16, 0x8A, 0x21, 0x31, 0x17, 0x2B, 0x04, + 0x5B, 0xFB, 0xC7, 0xFD, 0xCF, 0x00, 0x90, 0x00, 0xFA, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0x03, 0x00, 0xB6, 0x00, 0x92, 0x00, 0x13, 0xFD, + 0xCD, 0xFB, 0xEE, 0x06, 0x0D, 0x1A, 0x33, 0x21, 0xD0, 0x13, 0x8C, + 0x01, 0x3E, 0xFB, 0x88, 0xFE, 0xF3, 0x00, 0x67, 0x00, 0xF7, 0xFF, + 0x06, 0x00, 0x1D, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0xA1, 0x02, 0xA6, + 0xF8, 0x56, 0x02, 0xA5, 0x28, 0xA5, 0x28, 0x56, 0x02, 0xA6, 0xF8, + 0xA1, 0x02, 0xFE, 0x00, 0x03, 0xFF, 0x1D, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x21, 0x00, 0xA6, 0xFF, 0x3F, 0xFF, 0x0B, 0x03, 0x42, 0xFE, + 0x3E, 0xF8, 0x7F, 0x15, 0xAC, 0x30, 0x7F, 0x15, 0x3E, 0xF8, 0x42, + 0xFE, 0x0B, 0x03, 0x3F, 0xFF, 0xA6, 0xFF, 0x21, 0x00, 0x00, 0x00, + 0xFA, 0xFF, 0xCE, 0xFF, 0x14, 0x01, 0x00, 0xFD, 0x35, 0x06, 0xD5, + 0xF4, 0xDA, 0x15, 0x92, 0x40, 0xAE, 0xFE, 0xF3, 0xFC, 0x68, 0x03, + 0x86, 0xFD, 0x51, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEC, + 0xFF, 0xF9, 0xFF, 0xC6, 0x00, 0x55, 0xFD, 0x35, 0x06, 0x90, 0xF3, + 0xE5, 0x1C, 0x6B, 0x3D, 0x71, 0xFA, 0x34, 0xFF, 0x46, 0x02, 0xFF, + 0xFD, 0x2D, 0x01, 0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDB, 0xFF, + 0x2D, 0x00, 0x60, 0x00, 0xE1, 0xFD, 0xCE, 0x05, 0xED, 0xF2, 0xF3, + 0x23, 0x20, 0x39, 0x22, 0xF7, 0x44, 0x01, 0x1F, 0x01, 0x89, 0xFE, + 0xFB, 0x00, 0x9C, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC9, 0xFF, 0x68, + 0x00, 0xE5, 0xFF, 0xA0, 0xFE, 0xFB, 0x04, 0x0C, 0xF3, 0xC5, 0x2A, + 0xD8, 0x33, 0xC9, 0xF4, 0x0B, 0x03, 0x05, 0x00, 0x1A, 0xFF, 0xC1, + 0x00, 0xAD, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB5, 0xFF, 0xA5, 0x00, + 0x5C, 0xFF, 0x8C, 0xFF, 0xBF, 0x03, 0x06, 0xF4, 0x22, 0x31, 0xC8, + 0x2D, 0x63, 0xF3, 0x76, 0x04, 0x08, 0xFF, 0xA7, 0xFF, 0x84, 0x00, + 0xC0, 0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA4, 0xFF, 0xE1, 0x00, 0xCB, + 0xFE, 0x9B, 0x00, 0x21, 0x02, 0xEE, 0xF5, 0xCD, 0x36, 0x24, 0x27, + 0xE1, 0xF2, 0x7A, 0x05, 0x33, 0xFE, 0x2A, 0x00, 0x47, 0x00, 0xD3, + 0xFF, 0x04, 0x00, 0x0F, 0x00, 0x95, 0xFF, 0x17, 0x01, 0x3D, 0xFE, + 0xBD, 0x01, 0x30, 0x00, 0xCC, 0xF8, 0x92, 0x3B, 0x2A, 0x20, 0x2E, + 0xF3, 0x12, 0x06, 0x8F, 0xFD, 0x9A, 0x00, 0x10, 0x00, 0xE5, 0xFF, + 0x02, 0x00, 0x10, 0x00, 0x8C, 0xFF, 0x42, 0x01, 0xBB, 0xFD, 0xE4, + 0x02, 0x01, 0xFE, 0x9C, 0xFC, 0x45, 0x3F, 0x16, 0x19, 0x2D, 0xF4, + 0x41, 0x06, 0x21, 0xFD, 0xF3, 0x00, 0xE0, 0xFF, 0xF4, 0xFF, 0x01, + 0x00, 0x10, 0x00, 0x8B, 0xFF, 0x5D, 0x01, 0x4F, 0xFD, 0xFB, 0x03, + 0xB2, 0xFB, 0x53, 0x01, 0xC2, 0x41, 0x24, 0x12, 0xBA, 0xF5, 0x0F, + 0x06, 0xE9, 0xFC, 0x33, 0x01, 0xBB, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0x93, 0xFF, 0x63, 0x01, 0x04, 0xFD, 0xEF, 0x04, 0x62, + 0xF9, 0xD7, 0x06, 0xF2, 0x42, 0x8D, 0x0B, 0xB0, 0xF7, 0x87, 0x05, + 0xE6, 0xFC, 0x58, 0x01, 0xA0, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x00, 0xA5, 0xFF, 0x52, 0x01, 0xE2, 0xFC, 0xAD, 0x05, + 0x35, 0xF7, 0x08, 0x0D, 0xCB, 0x42, 0x81, 0x05, 0xE8, 0xF9, 0xBB, + 0x04, 0x12, 0xFD, 0x64, 0x01, 0x90, 0xFF, 0x0E, 0x00, 0x00, 0x00, + 0xFE, 0xFF, 0xC2, 0xFF, 0x27, 0x01, 0xF1, 0xFC, 0x22, 0x06, 0x54, + 0xF5, 0xB8, 0x13, 0x4A, 0x41, 0x29, 0x00, 0x3C, 0xFC, 0xBD, 0x03, + 0x66, 0xFD, 0x58, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF1, + 0xFF, 0xEB, 0xFF, 0xE1, 0x00, 0x35, 0xFD, 0x40, 0x06, 0xE4, 0xF3, + 0xB7, 0x1A, 0x85, 0x3E, 0xA6, 0xFB, 0x86, 0xFE, 0xA0, 0x02, 0xD7, + 0xFD, 0x39, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xE1, 0xFF, + 0x1C, 0x00, 0x82, 0x00, 0xB0, 0xFD, 0xF9, 0x05, 0x0C, 0xF3, 0xCB, + 0x21, 0x8F, 0x3A, 0x0D, 0xF8, 0xA9, 0x00, 0x79, 0x01, 0x5D, 0xFE, + 0x0B, 0x01, 0x98, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCE, 0xFF, 0x55, + 0x00, 0x0D, 0x00, 0x60, 0xFE, 0x48, 0x05, 0xEC, 0xF2, 0xB6, 0x28, + 0x91, 0x35, 0x68, 0xF5, 0x88, 0x02, 0x5A, 0x00, 0xED, 0xFE, 0xD4, + 0x00, 0xA8, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBB, 0xFF, 0x92, 0x00, + 0x87, 0xFF, 0x3F, 0xFF, 0x2B, 0x04, 0xA1, 0xF3, 0x3D, 0x2F, 0xB8, + 0x2F, 0xB8, 0xF3, 0x11, 0x04, 0x52, 0xFF, 0x7C, 0xFF, 0x97, 0x00, + 0xBA, 0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA9, 0xFF, 0xCF, 0x00, 0xF8, + 0xFE, 0x44, 0x00, 0xAA, 0x02, 0x3E, 0xF5, 0x24, 0x35, 0x3B, 0x29, + 0xF2, 0xF2, 0x35, 0x05, 0x70, 0xFE, 0x03, 0x00, 0x5A, 0x00, 0xCD, + 0xFF, 0x05, 0x00, 0x0E, 0x00, 0x99, 0xFF, 0x07, 0x01, 0x68, 0xFE, + 0x63, 0x01, 0xD0, 0x00, 0xD0, 0xF7, 0x35, 0x3A, 0x55, 0x22, 0x02, + 0xF3, 0xEF, 0x05, 0xBC, 0xFD, 0x7A, 0x00, 0x20, 0x00, 0xDF, 0xFF, + 0x03, 0x00, 0x10, 0x00, 0x8E, 0xFF, 0x36, 0x01, 0xE1, 0xFD, 0x8A, + 0x02, 0xB2, 0xFE, 0x56, 0xFB, 0x40, 0x3E, 0x42, 0x1B, 0xCE, 0xF3, + 0x3E, 0x06, 0x3D, 0xFD, 0xDB, 0x00, 0xEE, 0xFF, 0xF0, 0xFF, 0x01, + 0x00, 0x11, 0x00, 0x8A, 0xFF, 0x57, 0x01, 0x6D, 0xFD, 0xA8, 0x03, + 0x69, 0xFC, 0xC8, 0xFF, 0x20, 0x41, 0x40, 0x14, 0x33, 0xF5, 0x28, + 0x06, 0xF5, 0xFC, 0x22, 0x01, 0xC5, 0xFF, 0xFD, 0xFF, 0x00, 0x00, + 0x0F, 0x00, 0x8F, 0xFF, 0x64, 0x01, 0x17, 0xFD, 0xA9, 0x04, 0x16, + 0xFA, 0x10, 0x05, 0xB8, 0x42, 0x87, 0x0D, 0x0D, 0xF7, 0xB9, 0x05, + 0xE2, 0xFC, 0x50, 0x01, 0xA7, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x00, 0x9E, 0xFF, 0x5A, 0x01, 0xE8, 0xFC, 0x7A, 0x05, + 0xDA, 0xF7, 0x10, 0x0B, 0xFB, 0x42, 0x4B, 0x07, 0x35, 0xF9, 0x00, + 0x05, 0x00, 0xFD, 0x63, 0x01, 0x94, 0xFF, 0x0D, 0x00, 0x00, 0x00, + 0x01, 0x00, 0xB8, 0xFF, 0x37, 0x01, 0xE7, 0xFC, 0x07, 0x06, 0xDE, + 0xF5, 0x9F, 0x11, 0xE4, 0x41, 0xB8, 0x01, 0x84, 0xFB, 0x0F, 0x04, + 0x48, 0xFD, 0x5E, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF5, + 0xFF, 0xDD, 0xFF, 0xF9, 0x00, 0x1B, 0xFD, 0x41, 0x06, 0x47, 0xF4, + 0x8B, 0x18, 0x81, 0x3F, 0xF1, 0xFC, 0xD5, 0xFD, 0xFA, 0x02, 0xB2, + 0xFD, 0x45, 0x01, 0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE6, 0xFF, + 0x0C, 0x00, 0xA2, 0x00, 0x85, 0xFD, 0x1A, 0x06, 0x3C, 0xF3, 0x9F, + 0x1F, 0xE6, 0x3B, 0x0E, 0xF9, 0x07, 0x00, 0xD4, 0x01, 0x33, 0xFE, + 0x1B, 0x01, 0x94, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD4, 0xFF, 0x43, + 0x00, 0x33, 0x00, 0x25, 0xFE, 0x89, 0x05, 0xE0, 0xF2, 0x9C, 0x26, + 0x33, 0x37, 0x1E, 0xF6, 0xFD, 0x01, 0xB0, 0x00, 0xC0, 0xFE, 0xE6, + 0x00, 0xA2, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC1, 0xFF, 0x7F, 0x00, + 0xB2, 0xFF, 0xF6, 0xFE, 0x8E, 0x04, 0x51, 0xF3, 0x49, 0x2D, 0x98, + 0x31, 0x23, 0xF4, 0xA2, 0x03, 0xA0, 0xFF, 0x51, 0xFF, 0xAA, 0x00, + 0xB4, 0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAE, 0xFF, 0xBD, 0x00, 0x25, + 0xFF, 0xF1, 0xFF, 0x2B, 0x03, 0xA5, 0xF4, 0x68, 0x33, 0x48, 0x2B, + 0x17, 0xF3, 0xE7, 0x04, 0xB1, 0xFE, 0xDB, 0xFF, 0x6C, 0x00, 0xC7, + 0xFF, 0x06, 0x00, 0x0D, 0x00, 0x9E, 0xFF, 0xF7, 0x00, 0x94, 0xFE, + 0x09, 0x01, 0x6A, 0x01, 0xEB, 0xF6, 0xC1, 0x38, 0x7D, 0x24, 0xE8, + 0xF2, 0xC1, 0x05, 0xEE, 0xFD, 0x57, 0x00, 0x31, 0x00, 0xDA, 0xFF, + 0x03, 0x00, 0x10, 0x00, 0x91, 0xFF, 0x29, 0x01, 0x09, 0xFE, 0x2F, + 0x02, 0x5F, 0xFF, 0x27, 0xFA, 0x20, 0x3D, 0x70, 0x1D, 0x7D, 0xF3, + 0x31, 0x06, 0x5E, 0xFD, 0xBF, 0x00, 0xFD, 0xFF, 0xEB, 0xFF, 0x02, + 0x00, 0x11, 0x00, 0x8B, 0xFF, 0x4E, 0x01, 0x8E, 0xFD, 0x52, 0x03, + 0x20, 0xFD, 0x52, 0xFE, 0x60, 0x40, 0x63, 0x16, 0xB7, 0xF4, 0x39, + 0x06, 0x05, 0xFD, 0x0F, 0x01, 0xD1, 0xFF, 0xF9, 0xFF, 0x00, 0x00, + 0x10, 0x00, 0x8D, 0xFF, 0x62, 0x01, 0x2E, 0xFD, 0x5E, 0x04, 0xCC, + 0xFA, 0x5B, 0x03, 0x5E, 0x42, 0x8E, 0x0F, 0x71, 0xF6, 0xE4, 0x05, + 0xE2, 0xFC, 0x45, 0x01, 0xAF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0B, 0x00, 0x99, 0xFF, 0x60, 0x01, 0xF2, 0xFC, 0x40, 0x05, + 0x85, 0xF8, 0x26, 0x09, 0x0C, 0x43, 0x26, 0x09, 0x85, 0xF8, 0x40, + 0x05, 0xF2, 0xFC, 0x60, 0x01, 0x99, 0xFF, 0x0B, 0x00, 0x00, 0x00, + 0x04, 0x00, 0xAF, 0xFF, 0x45, 0x01, 0xE2, 0xFC, 0xE4, 0x05, 0x71, + 0xF6, 0x8E, 0x0F, 0x5E, 0x42, 0x5B, 0x03, 0xCC, 0xFA, 0x5E, 0x04, + 0x2E, 0xFD, 0x62, 0x01, 0x8D, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xF9, + 0xFF, 0xD1, 0xFF, 0x0F, 0x01, 0x05, 0xFD, 0x39, 0x06, 0xB7, 0xF4, + 0x63, 0x16, 0x60, 0x40, 0x52, 0xFE, 0x20, 0xFD, 0x52, 0x03, 0x8E, + 0xFD, 0x4E, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEB, 0xFF, + 0xFD, 0xFF, 0xBF, 0x00, 0x5E, 0xFD, 0x31, 0x06, 0x7D, 0xF3, 0x70, + 0x1D, 0x20, 0x3D, 0x27, 0xFA, 0x5F, 0xFF, 0x2F, 0x02, 0x09, 0xFE, + 0x29, 0x01, 0x91, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDA, 0xFF, 0x31, + 0x00, 0x57, 0x00, 0xEE, 0xFD, 0xC1, 0x05, 0xE8, 0xF2, 0x7D, 0x24, + 0xC1, 0x38, 0xEB, 0xF6, 0x6A, 0x01, 0x09, 0x01, 0x94, 0xFE, 0xF7, + 0x00, 0x9E, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC7, 0xFF, 0x6C, 0x00, + 0xDB, 0xFF, 0xB1, 0xFE, 0xE7, 0x04, 0x17, 0xF3, 0x48, 0x2B, 0x68, + 0x33, 0xA5, 0xF4, 0x2B, 0x03, 0xF1, 0xFF, 0x25, 0xFF, 0xBD, 0x00, + 0xAE, 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB4, 0xFF, 0xAA, 0x00, 0x51, + 0xFF, 0xA0, 0xFF, 0xA2, 0x03, 0x23, 0xF4, 0x98, 0x31, 0x49, 0x2D, + 0x51, 0xF3, 0x8E, 0x04, 0xF6, 0xFE, 0xB2, 0xFF, 0x7F, 0x00, 0xC1, + 0xFF, 0x07, 0x00, 0x0C, 0x00, 0xA2, 0xFF, 0xE6, 0x00, 0xC0, 0xFE, + 0xB0, 0x00, 0xFD, 0x01, 0x1E, 0xF6, 0x33, 0x37, 0x9C, 0x26, 0xE0, + 0xF2, 0x89, 0x05, 0x25, 0xFE, 0x33, 0x00, 0x43, 0x00, 0xD4, 0xFF, + 0x04, 0x00, 0x0F, 0x00, 0x94, 0xFF, 0x1B, 0x01, 0x33, 0xFE, 0xD4, + 0x01, 0x07, 0x00, 0x0E, 0xF9, 0xE6, 0x3B, 0x9F, 0x1F, 0x3C, 0xF3, + 0x1A, 0x06, 0x85, 0xFD, 0xA2, 0x00, 0x0C, 0x00, 0xE6, 0xFF, 0x02, + 0x00, 0x11, 0x00, 0x8C, 0xFF, 0x45, 0x01, 0xB2, 0xFD, 0xFA, 0x02, + 0xD5, 0xFD, 0xF1, 0xFC, 0x81, 0x3F, 0x8B, 0x18, 0x47, 0xF4, 0x41, + 0x06, 0x1B, 0xFD, 0xF9, 0x00, 0xDD, 0xFF, 0xF5, 0xFF, 0x01, 0x00, + 0x10, 0x00, 0x8B, 0xFF, 0x5E, 0x01, 0x48, 0xFD, 0x0F, 0x04, 0x84, + 0xFB, 0xB8, 0x01, 0xE4, 0x41, 0x9F, 0x11, 0xDE, 0xF5, 0x07, 0x06, + 0xE7, 0xFC, 0x37, 0x01, 0xB8, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x0D, + 0x00, 0x94, 0xFF, 0x63, 0x01, 0x00, 0xFD, 0x00, 0x05, 0x35, 0xF9, + 0x4B, 0x07, 0xFB, 0x42, 0x10, 0x0B, 0xDA, 0xF7, 0x7A, 0x05, 0xE8, + 0xFC, 0x5A, 0x01, 0x9E, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0xA7, 0xFF, 0x50, 0x01, 0xE2, 0xFC, 0xB9, 0x05, 0x0D, + 0xF7, 0x87, 0x0D, 0xB8, 0x42, 0x10, 0x05, 0x16, 0xFA, 0xA9, 0x04, + 0x17, 0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFD, + 0xFF, 0xC5, 0xFF, 0x22, 0x01, 0xF5, 0xFC, 0x28, 0x06, 0x33, 0xF5, + 0x40, 0x14, 0x20, 0x41, 0xC8, 0xFF, 0x69, 0xFC, 0xA8, 0x03, 0x6D, + 0xFD, 0x57, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xF0, 0xFF, + 0xEE, 0xFF, 0xDB, 0x00, 0x3D, 0xFD, 0x3E, 0x06, 0xCE, 0xF3, 0x42, + 0x1B, 0x40, 0x3E, 0x56, 0xFB, 0xB2, 0xFE, 0x8A, 0x02, 0xE1, 0xFD, + 0x36, 0x01, 0x8E, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDF, 0xFF, 0x20, + 0x00, 0x7A, 0x00, 0xBC, 0xFD, 0xEF, 0x05, 0x02, 0xF3, 0x55, 0x22, + 0x35, 0x3A, 0xD0, 0xF7, 0xD0, 0x00, 0x63, 0x01, 0x68, 0xFE, 0x07, + 0x01, 0x99, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCD, 0xFF, 0x5A, 0x00, + 0x03, 0x00, 0x70, 0xFE, 0x35, 0x05, 0xF2, 0xF2, 0x3B, 0x29, 0x24, + 0x35, 0x3E, 0xF5, 0xAA, 0x02, 0x44, 0x00, 0xF8, 0xFE, 0xCF, 0x00, + 0xA9, 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xBA, 0xFF, 0x97, 0x00, 0x7C, + 0xFF, 0x52, 0xFF, 0x11, 0x04, 0xB8, 0xF3, 0xB8, 0x2F, 0x3D, 0x2F, + 0xA1, 0xF3, 0x2B, 0x04, 0x3F, 0xFF, 0x87, 0xFF, 0x92, 0x00, 0xBB, + 0xFF, 0x08, 0x00, 0x0B, 0x00, 0xA8, 0xFF, 0xD4, 0x00, 0xED, 0xFE, + 0x5A, 0x00, 0x88, 0x02, 0x68, 0xF5, 0x91, 0x35, 0xB6, 0x28, 0xEC, + 0xF2, 0x48, 0x05, 0x60, 0xFE, 0x0D, 0x00, 0x55, 0x00, 0xCE, 0xFF, + 0x05, 0x00, 0x0E, 0x00, 0x98, 0xFF, 0x0B, 0x01, 0x5D, 0xFE, 0x79, + 0x01, 0xA9, 0x00, 0x0D, 0xF8, 0x8F, 0x3A, 0xCB, 0x21, 0x0C, 0xF3, + 0xF9, 0x05, 0xB0, 0xFD, 0x82, 0x00, 0x1C, 0x00, 0xE1, 0xFF, 0x03, + 0x00, 0x10, 0x00, 0x8E, 0xFF, 0x39, 0x01, 0xD7, 0xFD, 0xA0, 0x02, + 0x86, 0xFE, 0xA6, 0xFB, 0x85, 0x3E, 0xB7, 0x1A, 0xE4, 0xF3, 0x40, + 0x06, 0x35, 0xFD, 0xE1, 0x00, 0xEB, 0xFF, 0xF1, 0xFF, 0x01, 0x00, + 0x11, 0x00, 0x8A, 0xFF, 0x58, 0x01, 0x66, 0xFD, 0xBD, 0x03, 0x3C, + 0xFC, 0x29, 0x00, 0x4A, 0x41, 0xB8, 0x13, 0x54, 0xF5, 0x22, 0x06, + 0xF1, 0xFC, 0x27, 0x01, 0xC2, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0x0E, + 0x00, 0x90, 0xFF, 0x64, 0x01, 0x12, 0xFD, 0xBB, 0x04, 0xE8, 0xF9, + 0x81, 0x05, 0xCB, 0x42, 0x08, 0x0D, 0x35, 0xF7, 0xAD, 0x05, 0xE2, + 0xFC, 0x52, 0x01, 0xA5, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0xA0, 0xFF, 0x58, 0x01, 0xE6, 0xFC, 0x87, 0x05, 0xB0, + 0xF7, 0x8D, 0x0B, 0xF2, 0x42, 0xD7, 0x06, 0x62, 0xF9, 0xEF, 0x04, + 0x04, 0xFD, 0x63, 0x01, 0x93, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xBB, 0xFF, 0x33, 0x01, 0xE9, 0xFC, 0x0F, 0x06, 0xBA, 0xF5, + 0x24, 0x12, 0xC2, 0x41, 0x53, 0x01, 0xB2, 0xFB, 0xFB, 0x03, 0x4F, + 0xFD, 0x5D, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF4, 0xFF, + 0xE0, 0xFF, 0xF3, 0x00, 0x21, 0xFD, 0x41, 0x06, 0x2D, 0xF4, 0x16, + 0x19, 0x45, 0x3F, 0x9C, 0xFC, 0x01, 0xFE, 0xE4, 0x02, 0xBB, 0xFD, + 0x42, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE5, 0xFF, 0x10, + 0x00, 0x9A, 0x00, 0x8F, 0xFD, 0x12, 0x06, 0x2E, 0xF3, 0x2A, 0x20, + 0x92, 0x3B, 0xCC, 0xF8, 0x30, 0x00, 0xBD, 0x01, 0x3D, 0xFE, 0x17, + 0x01, 0x95, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD3, 0xFF, 0x47, 0x00, + 0x2A, 0x00, 0x33, 0xFE, 0x7A, 0x05, 0xE1, 0xF2, 0x24, 0x27, 0xCD, + 0x36, 0xEE, 0xF5, 0x21, 0x02, 0x9B, 0x00, 0xCB, 0xFE, 0xE1, 0x00, + 0xA4, 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0x84, 0x00, 0xA7, + 0xFF, 0x08, 0xFF, 0x76, 0x04, 0x63, 0xF3, 0xC8, 0x2D, 0x22, 0x31, + 0x06, 0xF4, 0xBF, 0x03, 0x8C, 0xFF, 0x5C, 0xFF, 0xA5, 0x00, 0xB5, + 0xFF, 0x09, 0x00, 0x0A, 0x00, 0xAD, 0xFF, 0xC1, 0x00, 0x1A, 0xFF, + 0x05, 0x00, 0x0B, 0x03, 0xC9, 0xF4, 0xD8, 0x33, 0xC5, 0x2A, 0x0C, + 0xF3, 0xFB, 0x04, 0xA0, 0xFE, 0xE5, 0xFF, 0x68, 0x00, 0xC9, 0xFF, + 0x06, 0x00, 0x0D, 0x00, 0x9C, 0xFF, 0xFB, 0x00, 0x89, 0xFE, 0x1F, + 0x01, 0x44, 0x01, 0x22, 0xF7, 0x20, 0x39, 0xF3, 0x23, 0xED, 0xF2, + 0xCE, 0x05, 0xE1, 0xFD, 0x60, 0x00, 0x2D, 0x00, 0xDB, 0xFF, 0x03, + 0x00, 0x10, 0x00, 0x90, 0xFF, 0x2D, 0x01, 0xFF, 0xFD, 0x46, 0x02, + 0x34, 0xFF, 0x71, 0xFA, 0x6B, 0x3D, 0xE5, 0x1C, 0x90, 0xF3, 0x35, + 0x06, 0x55, 0xFD, 0xC6, 0x00, 0xF9, 0xFF, 0xEC, 0xFF, 0x01, 0x00, + 0x11, 0x00, 0x8B, 0xFF, 0x51, 0x01, 0x86, 0xFD, 0x68, 0x03, 0xF3, + 0xFC, 0xAE, 0xFE, 0x92, 0x40, 0xDA, 0x15, 0xD5, 0xF4, 0x35, 0x06, + 0x00, 0xFD, 0x14, 0x01, 0xCE, 0xFF, 0xFA, 0xFF, 0x00, 0x00, 0x0F, + 0x00, 0x8D, 0xFF, 0x63, 0x01, 0x28, 0xFD, 0x71, 0x04, 0x9E, 0xFA, + 0xC7, 0x03, 0x79, 0x42, 0x0B, 0x0F, 0x97, 0xF6, 0xDA, 0x05, 0xE2, + 0xFC, 0x48, 0x01, 0xAD, 0xFF, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x9A, 0xFF, 0x5F, 0x01, 0xEF, 0xFC, 0x4F, 0x05, 0x5A, + 0xF8, 0x9F, 0x09, 0x0A, 0x43, 0xAE, 0x08, 0xB1, 0xF8, 0x30, 0x05, + 0xF5, 0xFC, 0x61, 0x01, 0x97, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03, + 0x00, 0xB1, 0xFF, 0x41, 0x01, 0xE3, 0xFC, 0xED, 0x05, 0x4C, 0xF6, + 0x11, 0x10, 0x42, 0x42, 0xF1, 0x02, 0xFA, 0xFA, 0x4B, 0x04, 0x34, + 0xFD, 0x61, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF8, 0xFF, + 0xD4, 0xFF, 0x0A, 0x01, 0x0A, 0xFD, 0x3C, 0x06, 0x9A, 0xF4, 0xED, + 0x16, 0x2A, 0x40, 0xF8, 0xFD, 0x4D, 0xFD, 0x3C, 0x03, 0x97, 0xFD, + 0x4C, 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xEA, 0xFF, 0x00, + 0x00, 0xB8, 0x00, 0x67, 0xFD, 0x2C, 0x06, 0x6B, 0xF3, 0xFC, 0x1D, + 0xD3, 0x3C, 0xDF, 0xF9, 0x89, 0xFF, 0x18, 0x02, 0x13, 0xFE, 0x26, + 0x01, 0x92, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD9, 0xFF, 0x36, 0x00, + 0x4E, 0x00, 0xFB, 0xFD, 0xB4, 0x05, 0xE4, 0xF2, 0x04, 0x25, 0x5F, + 0x38, 0xB6, 0xF6, 0x90, 0x01, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00, + 0x9F, 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC6, 0xFF, 0x71, 0x00, 0xD1, + 0xFF, 0xC2, 0xFE, 0xD1, 0x04, 0x23, 0xF3, 0xC9, 0x2B, 0xF5, 0x32, + 0x83, 0xF4, 0x49, 0x03, 0xDC, 0xFF, 0x30, 0xFF, 0xB8, 0x00, 0xB0, + 0xFF, 0x0A, 0x00, 0x09, 0x00, 0xB3, 0xFF, 0xAE, 0x00, 0x46, 0xFF, + 0xB4, 0xFF, 0x85, 0x03, 0x42, 0xF4, 0x0E, 0x32, 0xCA, 0x2C, 0x41, + 0xF3, 0xA5, 0x04, 0xE4, 0xFE, 0xBC, 0xFF, 0x7A, 0x00, 0xC3, 0xFF, + 0x07, 0x00, 0x0D, 0x00, 0xA1, 0xFF, 0xEA, 0x00, 0xB5, 0xFE, 0xC6, + 0x00, 0xD9, 0x01, 0x4F, 0xF6, 0x99, 0x37, 0x16, 0x26, 0xE0, 0xF2, + 0x98, 0x05, 0x16, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0xD6, 0xFF, 0x04, + 0x00, 0x0F, 0x00, 0x93, 0xFF, 0x1F, 0x01, 0x28, 0xFE, 0xEB, 0x01, + 0xDD, 0xFF, 0x52, 0xF9, 0x36, 0x3C, 0x13, 0x1F, 0x4B, 0xF3, 0x20, + 0x06, 0x7B, 0xFD, 0xA9, 0x00, 0x08, 0x00, 0xE7, 0xFF, 0x02, 0x00, + 0x11, 0x00, 0x8C, 0xFF, 0x47, 0x01, 0xA9, 0xFD, 0x10, 0x03, 0xA8, + 0xFD, 0x47, 0xFD, 0xBB, 0x3F, 0x01, 0x18, 0x62, 0xF4, 0x40, 0x06, + 0x15, 0xFD, 0xFF, 0x00, 0xDA, 0xFF, 0xF6, 0xFF, 0x01, 0x00, 0x10, + 0x00, 0x8B, 0xFF, 0x5F, 0x01, 0x41, 0xFD, 0x23, 0x04, 0x56, 0xFB, + 0x1F, 0x02, 0x06, 0x42, 0x19, 0x11, 0x02, 0xF6, 0xFF, 0x05, 0xE5, + 0xFC, 0x3B, 0x01, 0xB6, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x0D, 0x00, + 0x95, 0xFF, 0x62, 0x01, 0xFC, 0xFC, 0x10, 0x05, 0x09, 0xF9, 0xC1, + 0x07, 0x03, 0x43, 0x94, 0x0A, 0x05, 0xF8, 0x6C, 0x05, 0xEA, 0xFC, + 0x5C, 0x01, 0x9D, 0xFF, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x00, 0xA9, 0xFF, 0x4D, 0x01, 0xE1, 0xFC, 0xC4, 0x05, 0xE6, 0xF6, + 0x08, 0x0E, 0xA5, 0x42, 0xA1, 0x04, 0x43, 0xFA, 0x97, 0x04, 0x1D, + 0xFD, 0x64, 0x01, 0x8F, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFC, 0xFF, + 0xC8, 0xFF, 0x1E, 0x01, 0xF8, 0xFC, 0x2D, 0x06, 0x13, 0xF5, 0xC8, + 0x14, 0xF2, 0x40, 0x69, 0xFF, 0x97, 0xFC, 0x92, 0x03, 0x75, 0xFD, + 0x55, 0x01, 0x8A, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xEF, 0xFF, 0xF2, + 0xFF, 0xD4, 0x00, 0x45, 0xFD, 0x3B, 0x06, 0xB8, 0xF3, 0xCE, 0x1B, + 0xFB, 0x3D, 0x08, 0xFB, 0xDE, 0xFE, 0x73, 0x02, 0xEB, 0xFD, 0x33, + 0x01, 0x8F, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDE, 0xFF, 0x25, 0x00, + 0x71, 0x00, 0xC8, 0xFD, 0xE5, 0x05, 0xFA, 0xF2, 0xDF, 0x22, 0xDB, + 0x39, 0x94, 0xF7, 0xF7, 0x00, 0x4C, 0x01, 0x73, 0xFE, 0x03, 0x01, + 0x9A, 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xCC, 0xFF, 0x5E, 0x00, 0xF9, + 0xFF, 0x80, 0xFE, 0x23, 0x05, 0xF9, 0xF2, 0xC0, 0x29, 0xB8, 0x34, + 0x16, 0xF5, 0xCB, 0x02, 0x2F, 0x00, 0x03, 0xFF, 0xCA, 0x00, 0xAA, + 0xFF, 0x0B, 0x00, 0x08, 0x00, 0xB8, 0xFF, 0x9B, 0x00, 0x72, 0xFF, + 0x65, 0xFF, 0xF6, 0x03, 0xD1, 0xF3, 0x31, 0x30, 0xC1, 0x2E, 0x8B, + 0xF3, 0x45, 0x04, 0x2D, 0xFF, 0x92, 0xFF, 0x8D, 0x00, 0xBD, 0xFF, + 0x08, 0x00, 0x0C, 0x00, 0xA6, 0xFF, 0xD8, 0x00, 0xE2, 0xFE, 0x6F, + 0x00, 0x66, 0x02, 0x93, 0xF5, 0xFB, 0x35, 0x31, 0x28, 0xE7, 0xF2, + 0x59, 0x05, 0x51, 0xFE, 0x17, 0x00, 0x50, 0x00, 0xD0, 0xFF, 0x05, + 0x00, 0x0E, 0x00, 0x97, 0xFF, 0x0F, 0x01, 0x53, 0xFE, 0x90, 0x01, + 0x81, 0x00, 0x4B, 0xF8, 0xE6, 0x3A, 0x3F, 0x21, 0x16, 0xF3, 0x02, + 0x06, 0xA5, 0xFD, 0x8A, 0x00, 0x18, 0x00, 0xE2, 0xFF, 0x02, 0x00, + 0x10, 0x00, 0x8D, 0xFF, 0x3C, 0x01, 0xCE, 0xFD, 0xB7, 0x02, 0x5A, + 0xFE, 0xF7, 0xFB, 0xC6, 0x3E, 0x2C, 0x1A, 0xFC, 0xF3, 0x41, 0x06, + 0x2E, 0xFD, 0xE7, 0x00, 0xE7, 0xFF, 0xF2, 0xFF, 0x01, 0x00, 0x10, + 0x00, 0x8B, 0xFF, 0x5A, 0x01, 0x5E, 0xFD, 0xD2, 0x03, 0x0E, 0xFC, + 0x8B, 0x00, 0x75, 0x41, 0x32, 0x13, 0x75, 0xF5, 0x1C, 0x06, 0xEE, + 0xFC, 0x2B, 0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0E, 0x00, + 0x91, 0xFF, 0x64, 0x01, 0x0D, 0xFD, 0xCD, 0x04, 0xBB, 0xF9, 0xF2, + 0x05, 0xD9, 0x42, 0x88, 0x0C, 0x5E, 0xF7, 0xA1, 0x05, 0xE3, 0xFC, + 0x54, 0x01, 0xA3, 0xFF, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x00, 0xA2, 0xFF, 0x56, 0x01, 0xE5, 0xFC, 0x94, 0x05, 0x87, 0xF7, + 0x0A, 0x0C, 0xE6, 0x42, 0x64, 0x06, 0x8E, 0xF9, 0xDE, 0x04, 0x09, + 0xFD, 0x64, 0x01, 0x92, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xBD, 0xFF, 0x2F, 0x01, 0xEC, 0xFC, 0x16, 0x06, 0x98, 0xF5, 0xAB, + 0x12, 0x9C, 0x41, 0xEE, 0x00, 0xE0, 0xFB, 0xE6, 0x03, 0x57, 0xFD, + 0x5B, 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF3, 0xFF, 0xE4, + 0xFF, 0xED, 0x00, 0x27, 0xFD, 0x41, 0x06, 0x14, 0xF4, 0xA1, 0x19, + 0x06, 0x3F, 0x49, 0xFC, 0x2E, 0xFE, 0xCD, 0x02, 0xC4, 0xFD, 0x3F, + 0x01, 0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE3, 0xFF, 0x14, 0x00, + 0x92, 0x00, 0x9A, 0xFD, 0x0A, 0x06, 0x22, 0xF3, 0xB4, 0x20, 0x3C, + 0x3B, 0x8B, 0xF8, 0x58, 0x00, 0xA7, 0x01, 0x48, 0xFE, 0x13, 0x01, + 0x96, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD1, 0xFF, 0x4C, 0x00, 0x20, + 0x00, 0x42, 0xFE, 0x6A, 0x05, 0xE3, 0xF2, 0xAB, 0x27, 0x66, 0x36, + 0xC0, 0xF5, 0x44, 0x02, 0x85, 0x00, 0xD7, 0xFE, 0xDD, 0x00, 0xA5, + 0xFF, 0x0C, 0x00, 0x07, 0x00, 0xBE, 0xFF, 0x89, 0x00, 0x9D, 0xFF, + 0x1A, 0xFF, 0x5E, 0x04, 0x76, 0xF3, 0x45, 0x2E, 0xAA, 0x30, 0xEB, + 0xF3, 0xDB, 0x03, 0x79, 0xFF, 0x67, 0xFF, 0xA0, 0x00, 0xB7, 0xFF, + 0x09, 0x00, 0x0B, 0x00, 0xAC, 0xFF, 0xC6, 0x00, 0x0E, 0xFF, 0x1A, + 0x00, 0xEB, 0x02, 0xEF, 0xF4, 0x49, 0x34, 0x43, 0x2A, 0x02, 0xF3, + 0x0F, 0x05, 0x90, 0xFE, 0xEF, 0xFF, 0x63, 0x00, 0xCA, 0xFF, 0x06, + 0x00, 0x0E, 0x00, 0x9B, 0xFF, 0xFF, 0x00, 0x7E, 0xFE, 0x36, 0x01, + 0x1E, 0x01, 0x5B, 0xF7, 0x7E, 0x39, 0x69, 0x23, 0xF3, 0xF2, 0xD9, + 0x05, 0xD4, 0xFD, 0x69, 0x00, 0x29, 0x00, 0xDD, 0xFF, 0x03, 0x00, + 0x10, 0x00, 0x90, 0xFF, 0x30, 0x01, 0xF5, 0xFD, 0x5C, 0x02, 0x09, + 0xFF, 0xBC, 0xFA, 0xB5, 0x3D, 0x5A, 0x1C, 0xA3, 0xF3, 0x38, 0x06, + 0x4D, 0xFD, 0xCD, 0x00, 0xF5, 0xFF, 0xED, 0xFF, 0x01, 0x00, 0x11, + 0x00, 0x8B, 0xFF, 0x53, 0x01, 0x7E, 0xFD, 0x7D, 0x03, 0xC5, 0xFC, + 0x0B, 0xFF, 0xC3, 0x40, 0x51, 0x15, 0xF4, 0xF4, 0x31, 0x06, 0xFC, + 0xFC, 0x19, 0x01, 0xCB, 0xFF, 0xFB, 0xFF, 0x00, 0x00, 0x0F, 0x00, + 0x8E, 0xFF, 0x63, 0x01, 0x22, 0xFD, 0x84, 0x04, 0x71, 0xFA, 0x34, + 0x04, 0x90, 0x42, 0x89, 0x0E, 0xBE, 0xF6, 0xCF, 0x05, 0xE1, 0xFC, + 0x4A, 0x01, 0xAB, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, + 0x00, 0x9B, 0xFF, 0x5D, 0x01, 0xEC, 0xFC, 0x5D, 0x05, 0x2F, 0xF8, + 0x19, 0x0A, 0x07, 0x43, 0x37, 0x08, 0xDD, 0xF8, 0x21, 0x05, 0xF8, + 0xFC, 0x62, 0x01, 0x96, 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, + 0xB4, 0xFF, 0x3E, 0x01, 0xE4, 0xFC, 0xF6, 0x05, 0x26, 0xF6, 0x95, + 0x10, 0x26, 0x42, 0x87, 0x02, 0x28, 0xFB, 0x37, 0x04, 0x3B, 0xFD, + 0x60, 0x01, 0x8C, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF7, 0xFF, 0xD7, + 0xFF, 0x04, 0x01, 0x0F, 0xFD, 0x3E, 0x06, 0x7D, 0xF4, 0x76, 0x17, + 0xF4, 0x3F, 0x9F, 0xFD, 0x7B, 0xFD, 0x26, 0x03, 0xA0, 0xFD, 0x4A, + 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE9, 0xFF, 0x04, 0x00, + 0xB1, 0x00, 0x71, 0xFD, 0x26, 0x06, 0x5A, 0xF3, 0x88, 0x1E, 0x87, + 0x3C, 0x98, 0xF9, 0xB3, 0xFF, 0x02, 0x02, 0x1E, 0xFE, 0x22, 0x01, + 0x93, 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD7, 0xFF, 0x3A, 0x00, 0x45, + 0x00, 0x09, 0xFE, 0xA7, 0x05, 0xE1, 0xF2, 0x8D, 0x25, 0xFD, 0x37, + 0x82, 0xF6, 0xB5, 0x01, 0xDC, 0x00, 0xAA, 0xFE, 0xEE, 0x00, 0xA0, + 0xFF, 0x0D, 0x00, 0x06, 0x00, 0xC4, 0xFF, 0x76, 0x00, 0xC7, 0xFF, + 0xD3, 0xFE, 0xBC, 0x04, 0x31, 0xF3, 0x4A, 0x2C, 0x83, 0x32, 0x61, + 0xF4, 0x68, 0x03, 0xC8, 0xFF, 0x3B, 0xFF, 0xB3, 0x00, 0xB1, 0xFF, + 0x0A, 0x00, 0x0A, 0x00, 0xB1, 0xFF, 0xB3, 0x00, 0x3B, 0xFF, 0xC8, + 0xFF, 0x68, 0x03, 0x61, 0xF4, 0x83, 0x32, 0x4A, 0x2C, 0x31, 0xF3, + 0xBC, 0x04, 0xD3, 0xFE, 0xC7, 0xFF, 0x76, 0x00, 0xC4, 0xFF, 0x06, + 0x00, 0x0D, 0x00, 0xA0, 0xFF, 0xEE, 0x00, 0xAA, 0xFE, 0xDC, 0x00, + 0xB5, 0x01, 0x82, 0xF6, 0xFD, 0x37, 0x8D, 0x25, 0xE1, 0xF2, 0xA7, + 0x05, 0x09, 0xFE, 0x45, 0x00, 0x3A, 0x00, 0xD7, 0xFF, 0x04, 0x00, + 0x0F, 0x00, 0x93, 0xFF, 0x22, 0x01, 0x1E, 0xFE, 0x02, 0x02, 0xB3, + 0xFF, 0x98, 0xF9, 0x87, 0x3C, 0x88, 0x1E, 0x5A, 0xF3, 0x26, 0x06, + 0x71, 0xFD, 0xB1, 0x00, 0x04, 0x00, 0xE9, 0xFF, 0x02, 0x00, 0x11, + 0x00, 0x8B, 0xFF, 0x4A, 0x01, 0xA0, 0xFD, 0x26, 0x03, 0x7B, 0xFD, + 0x9F, 0xFD, 0xF4, 0x3F, 0x76, 0x17, 0x7D, 0xF4, 0x3E, 0x06, 0x0F, + 0xFD, 0x04, 0x01, 0xD7, 0xFF, 0xF7, 0xFF, 0x01, 0x00, 0x10, 0x00, + 0x8C, 0xFF, 0x60, 0x01, 0x3B, 0xFD, 0x37, 0x04, 0x28, 0xFB, 0x87, + 0x02, 0x26, 0x42, 0x95, 0x10, 0x26, 0xF6, 0xF6, 0x05, 0xE4, 0xFC, + 0x3E, 0x01, 0xB4, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x96, + 0xFF, 0x62, 0x01, 0xF8, 0xFC, 0x21, 0x05, 0xDD, 0xF8, 0x37, 0x08, + 0x07, 0x43, 0x19, 0x0A, 0x2F, 0xF8, 0x5D, 0x05, 0xEC, 0xFC, 0x5D, + 0x01, 0x9B, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0xAB, 0xFF, 0x4A, 0x01, 0xE1, 0xFC, 0xCF, 0x05, 0xBE, 0xF6, 0x89, + 0x0E, 0x90, 0x42, 0x34, 0x04, 0x71, 0xFA, 0x84, 0x04, 0x22, 0xFD, + 0x63, 0x01, 0x8E, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xFB, 0xFF, 0xCB, + 0xFF, 0x19, 0x01, 0xFC, 0xFC, 0x31, 0x06, 0xF4, 0xF4, 0x51, 0x15, + 0xC3, 0x40, 0x0B, 0xFF, 0xC5, 0xFC, 0x7D, 0x03, 0x7E, 0xFD, 0x53, + 0x01, 0x8B, 0xFF, 0x11, 0x00, 0x01, 0x00, 0xED, 0xFF, 0xF5, 0xFF, + 0xCD, 0x00, 0x4D, 0xFD, 0x38, 0x06, 0xA3, 0xF3, 0x5A, 0x1C, 0xB5, + 0x3D, 0xBC, 0xFA, 0x09, 0xFF, 0x5C, 0x02, 0xF5, 0xFD, 0x30, 0x01, + 0x90, 0xFF, 0x10, 0x00, 0x03, 0x00, 0xDD, 0xFF, 0x29, 0x00, 0x69, + 0x00, 0xD4, 0xFD, 0xD9, 0x05, 0xF3, 0xF2, 0x69, 0x23, 0x7E, 0x39, + 0x5B, 0xF7, 0x1E, 0x01, 0x36, 0x01, 0x7E, 0xFE, 0xFF, 0x00, 0x9B, + 0xFF, 0x0E, 0x00, 0x06, 0x00, 0xCA, 0xFF, 0x63, 0x00, 0xEF, 0xFF, + 0x90, 0xFE, 0x0F, 0x05, 0x02, 0xF3, 0x43, 0x2A, 0x49, 0x34, 0xEF, + 0xF4, 0xEB, 0x02, 0x1A, 0x00, 0x0E, 0xFF, 0xC6, 0x00, 0xAC, 0xFF, + 0x0B, 0x00, 0x09, 0x00, 0xB7, 0xFF, 0xA0, 0x00, 0x67, 0xFF, 0x79, + 0xFF, 0xDB, 0x03, 0xEB, 0xF3, 0xAA, 0x30, 0x45, 0x2E, 0x76, 0xF3, + 0x5E, 0x04, 0x1A, 0xFF, 0x9D, 0xFF, 0x89, 0x00, 0xBE, 0xFF, 0x07, + 0x00, 0x0C, 0x00, 0xA5, 0xFF, 0xDD, 0x00, 0xD7, 0xFE, 0x85, 0x00, + 0x44, 0x02, 0xC0, 0xF5, 0x66, 0x36, 0xAB, 0x27, 0xE3, 0xF2, 0x6A, + 0x05, 0x42, 0xFE, 0x20, 0x00, 0x4C, 0x00, 0xD1, 0xFF, 0x04, 0x00, + 0x0F, 0x00, 0x96, 0xFF, 0x13, 0x01, 0x48, 0xFE, 0xA7, 0x01, 0x58, + 0x00, 0x8B, 0xF8, 0x3C, 0x3B, 0xB4, 0x20, 0x22, 0xF3, 0x0A, 0x06, + 0x9A, 0xFD, 0x92, 0x00, 0x14, 0x00, 0xE3, 0xFF, 0x02, 0x00, 0x10, + 0x00, 0x8D, 0xFF, 0x3F, 0x01, 0xC4, 0xFD, 0xCD, 0x02, 0x2E, 0xFE, + 0x49, 0xFC, 0x06, 0x3F, 0xA1, 0x19, 0x14, 0xF4, 0x41, 0x06, 0x27, + 0xFD, 0xED, 0x00, 0xE4, 0xFF, 0xF3, 0xFF, 0x01, 0x00, 0x10, 0x00, + 0x8B, 0xFF, 0x5B, 0x01, 0x57, 0xFD, 0xE6, 0x03, 0xE0, 0xFB, 0xEE, + 0x00, 0x9C, 0x41, 0xAB, 0x12, 0x98, 0xF5, 0x16, 0x06, 0xEC, 0xFC, + 0x2F, 0x01, 0xBD, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x92, + 0xFF, 0x64, 0x01, 0x09, 0xFD, 0xDE, 0x04, 0x8E, 0xF9, 0x64, 0x06, + 0xE6, 0x42, 0x0A, 0x0C, 0x87, 0xF7, 0x94, 0x05, 0xE5, 0xFC, 0x56, + 0x01, 0xA2, 0xFF, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0xA3, 0xFF, 0x54, 0x01, 0xE3, 0xFC, 0xA1, 0x05, 0x5E, 0xF7, 0x88, + 0x0C, 0xD9, 0x42, 0xF2, 0x05, 0xBB, 0xF9, 0xCD, 0x04, 0x0D, 0xFD, + 0x64, 0x01, 0x91, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, + 0xFF, 0x2B, 0x01, 0xEE, 0xFC, 0x1C, 0x06, 0x75, 0xF5, 0x32, 0x13, + 0x75, 0x41, 0x8B, 0x00, 0x0E, 0xFC, 0xD2, 0x03, 0x5E, 0xFD, 0x5A, + 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF2, 0xFF, 0xE7, 0xFF, + 0xE7, 0x00, 0x2E, 0xFD, 0x41, 0x06, 0xFC, 0xF3, 0x2C, 0x1A, 0xC6, + 0x3E, 0xF7, 0xFB, 0x5A, 0xFE, 0xB7, 0x02, 0xCE, 0xFD, 0x3C, 0x01, + 0x8D, 0xFF, 0x10, 0x00, 0x02, 0x00, 0xE2, 0xFF, 0x18, 0x00, 0x8A, + 0x00, 0xA5, 0xFD, 0x02, 0x06, 0x16, 0xF3, 0x3F, 0x21, 0xE6, 0x3A, + 0x4B, 0xF8, 0x81, 0x00, 0x90, 0x01, 0x53, 0xFE, 0x0F, 0x01, 0x97, + 0xFF, 0x0E, 0x00, 0x05, 0x00, 0xD0, 0xFF, 0x50, 0x00, 0x17, 0x00, + 0x51, 0xFE, 0x59, 0x05, 0xE7, 0xF2, 0x31, 0x28, 0xFB, 0x35, 0x93, + 0xF5, 0x66, 0x02, 0x6F, 0x00, 0xE2, 0xFE, 0xD8, 0x00, 0xA6, 0xFF, + 0x0C, 0x00, 0x08, 0x00, 0xBD, 0xFF, 0x8D, 0x00, 0x92, 0xFF, 0x2D, + 0xFF, 0x45, 0x04, 0x8B, 0xF3, 0xC1, 0x2E, 0x31, 0x30, 0xD1, 0xF3, + 0xF6, 0x03, 0x65, 0xFF, 0x72, 0xFF, 0x9B, 0x00, 0xB8, 0xFF, 0x08, + 0x00, 0x0B, 0x00, 0xAA, 0xFF, 0xCA, 0x00, 0x03, 0xFF, 0x2F, 0x00, + 0xCB, 0x02, 0x16, 0xF5, 0xB8, 0x34, 0xC0, 0x29, 0xF9, 0xF2, 0x23, + 0x05, 0x80, 0xFE, 0xF9, 0xFF, 0x5E, 0x00, 0xCC, 0xFF, 0x05, 0x00, + 0x0E, 0x00, 0x9A, 0xFF, 0x03, 0x01, 0x73, 0xFE, 0x4C, 0x01, 0xF7, + 0x00, 0x94, 0xF7, 0xDB, 0x39, 0xDF, 0x22, 0xFA, 0xF2, 0xE5, 0x05, + 0xC8, 0xFD, 0x71, 0x00, 0x25, 0x00, 0xDE, 0xFF, 0x03, 0x00, 0x10, + 0x00, 0x8F, 0xFF, 0x33, 0x01, 0xEB, 0xFD, 0x73, 0x02, 0xDE, 0xFE, + 0x08, 0xFB, 0xFB, 0x3D, 0xCE, 0x1B, 0xB8, 0xF3, 0x3B, 0x06, 0x45, + 0xFD, 0xD4, 0x00, 0xF2, 0xFF, 0xEF, 0xFF, 0x01, 0x00, 0x11, 0x00, + 0x8A, 0xFF, 0x55, 0x01, 0x75, 0xFD, 0x92, 0x03, 0x97, 0xFC, 0x69, + 0xFF, 0xF2, 0x40, 0xC8, 0x14, 0x13, 0xF5, 0x2D, 0x06, 0xF8, 0xFC, + 0x1E, 0x01, 0xC8, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x0F, 0x00, 0x8F, + 0xFF, 0x64, 0x01, 0x1D, 0xFD, 0x97, 0x04, 0x43, 0xFA, 0xA1, 0x04, + 0xA5, 0x42, 0x08, 0x0E, 0xE6, 0xF6, 0xC4, 0x05, 0xE1, 0xFC, 0x4D, + 0x01, 0xA9, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, + 0x9D, 0xFF, 0x5C, 0x01, 0xEA, 0xFC, 0x6C, 0x05, 0x05, 0xF8, 0x94, + 0x0A, 0x03, 0x43, 0xC1, 0x07, 0x09, 0xF9, 0x10, 0x05, 0xFC, 0xFC, + 0x62, 0x01, 0x95, 0xFF, 0x0D, 0x00, 0x00, 0x00, 0x02, 0x00, 0xB6, + 0xFF, 0x3B, 0x01, 0xE5, 0xFC, 0xFF, 0x05, 0x02, 0xF6, 0x19, 0x11, + 0x06, 0x42, 0x1F, 0x02, 0x56, 0xFB, 0x23, 0x04, 0x41, 0xFD, 0x5F, + 0x01, 0x8B, 0xFF, 0x10, 0x00, 0x01, 0x00, 0xF6, 0xFF, 0xDA, 0xFF, + 0xFF, 0x00, 0x15, 0xFD, 0x40, 0x06, 0x62, 0xF4, 0x01, 0x18, 0xBB, + 0x3F, 0x47, 0xFD, 0xA8, 0xFD, 0x10, 0x03, 0xA9, 0xFD, 0x47, 0x01, + 0x8C, 0xFF, 0x11, 0x00, 0x02, 0x00, 0xE7, 0xFF, 0x08, 0x00, 0xA9, + 0x00, 0x7B, 0xFD, 0x20, 0x06, 0x4B, 0xF3, 0x13, 0x1F, 0x36, 0x3C, + 0x52, 0xF9, 0xDD, 0xFF, 0xEB, 0x01, 0x28, 0xFE, 0x1F, 0x01, 0x93, + 0xFF, 0x0F, 0x00, 0x04, 0x00, 0xD6, 0xFF, 0x3F, 0x00, 0x3C, 0x00, + 0x16, 0xFE, 0x98, 0x05, 0xE0, 0xF2, 0x16, 0x26, 0x99, 0x37, 0x4F, + 0xF6, 0xD9, 0x01, 0xC6, 0x00, 0xB5, 0xFE, 0xEA, 0x00, 0xA1, 0xFF, + 0x0D, 0x00, 0x07, 0x00, 0xC3, 0xFF, 0x7A, 0x00, 0xBC, 0xFF, 0xE4, + 0xFE, 0xA5, 0x04, 0x41, 0xF3, 0xCA, 0x2C, 0x0E, 0x32, 0x42, 0xF4, + 0x85, 0x03, 0xB4, 0xFF, 0x46, 0xFF, 0xAE, 0x00, 0xB3, 0xFF, 0x09, + 0x00, 0x0A, 0x00, 0xB0, 0xFF, 0xB8, 0x00, 0x30, 0xFF, 0xDC, 0xFF, + 0x49, 0x03, 0x83, 0xF4, 0xF5, 0x32, 0xC9, 0x2B, 0x23, 0xF3, 0xD1, + 0x04, 0xC2, 0xFE, 0xD1, 0xFF, 0x71, 0x00, 0xC6, 0xFF, 0x06, 0x00, + 0x0D, 0x00, 0x9F, 0xFF, 0xF3, 0x00, 0x9F, 0xFE, 0xF3, 0x00, 0x90, + 0x01, 0xB6, 0xF6, 0x5F, 0x38, 0x04, 0x25, 0xE4, 0xF2, 0xB4, 0x05, + 0xFB, 0xFD, 0x4E, 0x00, 0x36, 0x00, 0xD9, 0xFF, 0x04, 0x00, 0x0F, + 0x00, 0x92, 0xFF, 0x26, 0x01, 0x13, 0xFE, 0x18, 0x02, 0x89, 0xFF, + 0xDF, 0xF9, 0xD3, 0x3C, 0xFC, 0x1D, 0x6B, 0xF3, 0x2C, 0x06, 0x67, + 0xFD, 0xB8, 0x00, 0x00, 0x00, 0xEA, 0xFF, 0x02, 0x00, 0x11, 0x00, + 0x8B, 0xFF, 0x4C, 0x01, 0x97, 0xFD, 0x3C, 0x03, 0x4D, 0xFD, 0xF8, + 0xFD, 0x2A, 0x40, 0xED, 0x16, 0x9A, 0xF4, 0x3C, 0x06, 0x0A, 0xFD, + 0x0A, 0x01, 0xD4, 0xFF, 0xF8, 0xFF, 0x01, 0x00, 0x10, 0x00, 0x8C, + 0xFF, 0x61, 0x01, 0x34, 0xFD, 0x4B, 0x04, 0xFA, 0xFA, 0xF1, 0x02, + 0x42, 0x42, 0x11, 0x10, 0x4C, 0xF6, 0xED, 0x05, 0xE3, 0xFC, 0x41, + 0x01, 0xB1, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x97, 0xFF, + 0x61, 0x01, 0xF5, 0xFC, 0x30, 0x05, 0xB1, 0xF8, 0xAE, 0x08, 0x0A, + 0x43, 0x9F, 0x09, 0x5A, 0xF8, 0x4F, 0x05, 0xEF, 0xFC, 0x5F, 0x01, + 0x9A, 0xFF, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xAD, + 0xFF, 0x48, 0x01, 0xE2, 0xFC, 0xDA, 0x05, 0x97, 0xF6, 0x0B, 0x0F, + 0x79, 0x42, 0xC7, 0x03, 0x9E, 0xFA, 0x71, 0x04, 0x28, 0xFD, 0x63, + 0x01, 0x8D, 0xFF, 0x0F, 0x00 +}; + +static u16 +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..e36ed8af5 --- /dev/null +++ b/sound/pci/oxygen/oxygen.c @@ -0,0 +1,880 @@ +/* + * C-Media CMI8788 driver for C-Media's reference design and similar models + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "xonar_dg.h" +#include "ak4396.h" +#include "wm8785.h" + +MODULE_AUTHOR("Clemens Ladisch "); +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 +#include +#include +#include +#include "oxygen_regs.h" + +/* 1 << PCM_x == OXYGEN_CHANNEL_x */ +#define PCM_A 0 +#define PCM_B 1 +#define PCM_C 2 +#define PCM_SPDIF 3 +#define PCM_MULTICH 4 +#define PCM_AC97 5 +#define PCM_COUNT 6 + +#define OXYGEN_MCLKS(f_single, f_double, f_quad) ((MCLK_##f_single << 0) | \ + (MCLK_##f_double << 2) | \ + (MCLK_##f_quad << 4)) + +#define OXYGEN_IO_SIZE 0x100 + +#define OXYGEN_EEPROM_ID 0x434d /* "CM" */ + +/* model-specific configuration of outputs/inputs */ +#define PLAYBACK_0_TO_I2S 0x0001 + /* PLAYBACK_0_TO_AC97_0 not implemented */ +#define PLAYBACK_1_TO_SPDIF 0x0004 +#define PLAYBACK_2_TO_AC97_1 0x0008 +#define CAPTURE_0_FROM_I2S_1 0x0010 +#define CAPTURE_0_FROM_I2S_2 0x0020 + /* CAPTURE_0_FROM_AC97_0 not implemented */ +#define CAPTURE_1_FROM_SPDIF 0x0080 +#define CAPTURE_2_FROM_I2S_2 0x0100 +#define CAPTURE_2_FROM_AC97_1 0x0200 +#define CAPTURE_3_FROM_I2S_3 0x0400 +#define MIDI_OUTPUT 0x0800 +#define MIDI_INPUT 0x1000 +#define AC97_CD_INPUT 0x2000 +#define AC97_FMIC_SWITCH 0x4000 + +enum { + CONTROL_SPDIF_PCM, + CONTROL_SPDIF_INPUT_BITS, + CONTROL_MIC_CAPTURE_SWITCH, + CONTROL_LINE_CAPTURE_SWITCH, + CONTROL_CD_CAPTURE_SWITCH, + CONTROL_AUX_CAPTURE_SWITCH, + CONTROL_COUNT +}; + +#define OXYGEN_PCI_SUBID(sv, sd) \ + .vendor = PCI_VENDOR_ID_CMEDIA, \ + .device = 0x8788, \ + .subvendor = sv, \ + .subdevice = sd + +#define BROKEN_EEPROM_DRIVER_DATA ((unsigned long)-1) +#define OXYGEN_PCI_SUBID_BROKEN_EEPROM \ + OXYGEN_PCI_SUBID(PCI_VENDOR_ID_CMEDIA, 0x8788), \ + .driver_data = BROKEN_EEPROM_DRIVER_DATA + +struct pci_dev; +struct pci_device_id; +struct snd_card; +struct snd_pcm_substream; +struct snd_pcm_hardware; +struct snd_pcm_hw_params; +struct snd_kcontrol_new; +struct snd_rawmidi; +struct snd_info_buffer; +struct oxygen; + +struct oxygen_model { + const char *shortname; + const char *longname; + const char *chip; + void (*init)(struct oxygen *chip); + int (*control_filter)(struct snd_kcontrol_new *template); + int (*mixer_init)(struct oxygen *chip); + void (*cleanup)(struct oxygen *chip); + void (*suspend)(struct oxygen *chip); + void (*resume)(struct oxygen *chip); + void (*pcm_hardware_filter)(unsigned int channel, + struct snd_pcm_hardware *hardware); + void (*set_dac_params)(struct oxygen *chip, + struct snd_pcm_hw_params *params); + void (*set_adc_params)(struct oxygen *chip, + struct snd_pcm_hw_params *params); + void (*update_dac_volume)(struct oxygen *chip); + void (*update_dac_mute)(struct oxygen *chip); + void (*update_center_lfe_mix)(struct oxygen *chip, bool mixed); + unsigned int (*adjust_dac_routing)(struct oxygen *chip, + unsigned int play_routing); + void (*gpio_changed)(struct oxygen *chip); + void (*uart_input)(struct oxygen *chip); + void (*ac97_switch)(struct oxygen *chip, + unsigned int reg, unsigned int mute); + void (*dump_registers)(struct oxygen *chip, + struct snd_info_buffer *buffer); + const unsigned int *dac_tlv; + size_t model_data_size; + unsigned int device_config; + u8 dac_channels_pcm; + u8 dac_channels_mixer; + u8 dac_volume_min; + u8 dac_volume_max; + u8 misc_flags; + u8 function_flags; + u8 dac_mclks; + u8 adc_mclks; + u16 dac_i2s_format; + u16 adc_i2s_format; +}; + +struct oxygen { + unsigned long addr; + spinlock_t reg_lock; + struct mutex mutex; + struct snd_card *card; + struct pci_dev *pci; + struct snd_rawmidi *midi; + int irq; + void *model_data; + unsigned int interrupt_mask; + u8 dac_volume[8]; + u8 dac_mute; + u8 pcm_active; + u8 pcm_running; + u8 dac_routing; + u8 spdif_playback_enable; + u8 has_ac97_0; + u8 has_ac97_1; + u32 spdif_bits; + u32 spdif_pcm_bits; + struct snd_pcm_substream *streams[PCM_COUNT]; + struct snd_kcontrol *controls[CONTROL_COUNT]; + struct work_struct spdif_input_bits_work; + struct work_struct gpio_work; + wait_queue_head_t ac97_waitqueue; + union { + u8 _8[OXYGEN_IO_SIZE]; + __le16 _16[OXYGEN_IO_SIZE / 2]; + __le32 _32[OXYGEN_IO_SIZE / 4]; + } saved_registers; + u16 saved_ac97_registers[2][0x40]; + unsigned int uart_input_count; + u8 uart_input[32]; + struct oxygen_model model; +}; + +/* oxygen_lib.c */ + +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, + struct module *owner, + const struct pci_device_id *ids, + int (*get_model)(struct oxygen *chip, + const struct pci_device_id *id + ) + ); +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..c7851da37 --- /dev/null +++ b/sound/pci/oxygen/oxygen_io.c @@ -0,0 +1,292 @@ +/* + * C-Media CMI8788 driver - helper functions + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "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..b4ef58042 --- /dev/null +++ b/sound/pci/oxygen/oxygen_lib.c @@ -0,0 +1,834 @@ +/* + * C-Media CMI8788 driver - main driver module + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "cm9780.h" + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("C-Media CMI8788 helper library"); +MODULE_LICENSE("GPL v2"); + +#define DRIVER "oxygen" + +static inline int oxygen_uart_input_ready(struct oxygen *chip) +{ + return !(oxygen_read8(chip, OXYGEN_MPU401 + 1) & MPU401_RX_EMPTY); +} + +static void oxygen_read_uart(struct oxygen *chip) +{ + if (unlikely(!oxygen_uart_input_ready(chip))) { + /* no data, but read it anyway to clear the interrupt */ + oxygen_read8(chip, OXYGEN_MPU401); + return; + } + do { + u8 data = oxygen_read8(chip, OXYGEN_MPU401); + if (data == MPU401_ACK) + continue; + if (chip->uart_input_count >= ARRAY_SIZE(chip->uart_input)) + chip->uart_input_count = 0; + chip->uart_input[chip->uart_input_count++] = data; + } while (oxygen_uart_input_ready(chip)); + if (chip->model.uart_input) + chip->model.uart_input(chip); +} + +static irqreturn_t oxygen_interrupt(int dummy, void *dev_id) +{ + struct oxygen *chip = dev_id; + unsigned int status, clear, elapsed_streams, i; + + status = oxygen_read16(chip, OXYGEN_INTERRUPT_STATUS); + if (!status) + return IRQ_NONE; + + 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) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "oxygen", &entry)) + snd_info_set_text_ops(entry, 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 = 1; + 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; + + 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 i, saved_interrupt_mask; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + for (i = 0; i < PCM_COUNT; ++i) + snd_pcm_suspend(chip->streams[i]); + + 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); + + synchronize_irq(chip->irq); + 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..81af21ac1 --- /dev/null +++ b/sound/pci/oxygen/oxygen_mixer.c @@ -0,0 +1,1139 @@ +/* + * C-Media CMI8788 driver - mixer code + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "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[0] & 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..042a2439f --- /dev/null +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -0,0 +1,807 @@ +/* + * C-Media CMI8788 driver - PCM code + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "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; + } + /* fall through */ + 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); + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + 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 snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + DEFAULT_BUFFER_BYTES_MULTICH, + BUFFER_BYTES_MAX_MULTICH); + if (ins) + snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 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..34d07dd2d --- /dev/null +++ b/sound/pci/oxygen/pcm1796.h @@ -0,0 +1,59 @@ +/* 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_DISABLED 0x00 +#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..f70d514c1 --- /dev/null +++ b/sound/pci/oxygen/se6x.c @@ -0,0 +1,160 @@ +/* + * C-Media CMI8787 driver for the Studio Evolution SE6X + * + * Copyright (c) Clemens Ladisch + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +/* + * CMI8787: + * + * SPI -> microcontroller (not actually used) + * GPIO 0 -> do. + * GPIO 2 -> do. + * + * DAC0 -> both PCM1792A (L+R, each in mono mode) + * ADC1 <- 1st PCM1804 + * ADC2 <- 2nd PCM1804 + * ADC3 <- 3rd PCM1804 + */ + +#include +#include +#include +#include +#include +#include +#include "oxygen.h" + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("Studio Evolution SE6X driver"); +MODULE_LICENSE("GPL v2"); +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..83de6fb01 --- /dev/null +++ b/sound/pci/oxygen/virtuoso.c @@ -0,0 +1,107 @@ +/* + * C-Media CMI8788 driver for Asus Xonar cards + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "xonar.h" + +MODULE_AUTHOR("Clemens Ladisch "); +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..1a96f5615 --- /dev/null +++ b/sound/pci/oxygen/wm8776.h @@ -0,0 +1,177 @@ +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#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..d231b93d6 --- /dev/null +++ b/sound/pci/oxygen/xonar_cs43xx.c @@ -0,0 +1,452 @@ +/* + * card driver for models with CS4398/CS4362A DACs (Xonar D1/DX) + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +/* + * Xonar D1/DX + * ----------- + * + * CMI8788: + * + * I²C <-> CS4398 (addr 1001111) (front) + * <-> CS4362A (addr 0011000) (surround, center/LFE, back) + * + * GPI 0 <- external power present (DX only) + * + * GPIO 0 -> enable output to speakers + * GPIO 1 -> route output to front panel + * GPIO 2 -> M0 of CS5361 + * GPIO 3 -> M1 of CS5361 + * GPIO 6 -> ? + * GPIO 7 -> ? + * GPIO 8 -> route input jack to line-in (0) or mic-in (1) + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * MIC_IN <- mic + * FMIC_IN <- front mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5361 input + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" +#include "cm9780.h" +#include "cs4398.h" +#include "cs4362a.h" + +#define GPI_EXT_POWER 0x01 +#define GPIO_D1_OUTPUT_ENABLE 0x0001 +#define GPIO_D1_FRONT_PANEL 0x0002 +#define GPIO_D1_MAGIC 0x00c0 +#define GPIO_D1_INPUT_ROUTE 0x0100 + +#define I2C_DEVICE_CS4398 0x9e /* 10011, AD1=1, AD0=1, /W=0 */ +#define I2C_DEVICE_CS4362A 0x30 /* 001100, AD0=0, /W=0 */ + +struct xonar_cs43xx { + struct xonar_generic generic; + u8 cs4398_regs[8]; + u8 cs4362a_regs[15]; +}; + +static void cs4398_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + oxygen_write_i2c(chip, I2C_DEVICE_CS4398, reg, value); + if (reg < ARRAY_SIZE(data->cs4398_regs)) + data->cs4398_regs[reg] = value; +} + +static void cs4398_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + if (value != data->cs4398_regs[reg]) + cs4398_write(chip, reg, value); +} + +static void cs4362a_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + oxygen_write_i2c(chip, I2C_DEVICE_CS4362A, reg, value); + if (reg < ARRAY_SIZE(data->cs4362a_regs)) + data->cs4362a_regs[reg] = value; +} + +static void cs4362a_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_cs43xx *data = chip->model_data; + + if (value != data->cs4362a_regs[reg]) + cs4362a_write(chip, reg, value); +} + +static void cs43xx_registers_init(struct oxygen *chip) +{ + struct xonar_cs43xx *data = chip->model_data; + unsigned int i; + + /* set CPEN (control port mode) and power down */ + cs4398_write(chip, 8, CS4398_CPEN | CS4398_PDN); + cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN); + /* configure */ + cs4398_write(chip, 2, data->cs4398_regs[2]); + cs4398_write(chip, 3, CS4398_ATAPI_B_R | CS4398_ATAPI_A_L); + cs4398_write(chip, 4, data->cs4398_regs[4]); + cs4398_write(chip, 5, data->cs4398_regs[5]); + cs4398_write(chip, 6, data->cs4398_regs[6]); + cs4398_write(chip, 7, data->cs4398_regs[7]); + cs4362a_write(chip, 0x02, CS4362A_DIF_LJUST); + cs4362a_write(chip, 0x03, CS4362A_MUTEC_6 | CS4362A_AMUTE | + CS4362A_RMP_UP | CS4362A_ZERO_CROSS | CS4362A_SOFT_RAMP); + cs4362a_write(chip, 0x04, data->cs4362a_regs[0x04]); + cs4362a_write(chip, 0x05, 0); + for (i = 6; i <= 14; ++i) + cs4362a_write(chip, i, data->cs4362a_regs[i]); + /* clear power down */ + cs4398_write(chip, 8, CS4398_CPEN); + cs4362a_write(chip, 0x01, CS4362A_CPEN); +} + +static void xonar_d1_init(struct oxygen *chip) +{ + struct xonar_cs43xx *data = chip->model_data; + + data->generic.anti_pop_delay = 800; + data->generic.output_enable_bit = GPIO_D1_OUTPUT_ENABLE; + data->cs4398_regs[2] = + CS4398_FM_SINGLE | CS4398_DEM_NONE | CS4398_DIF_LJUST; + data->cs4398_regs[4] = CS4398_MUTEP_LOW | + CS4398_MUTE_B | CS4398_MUTE_A | CS4398_PAMUTE; + data->cs4398_regs[5] = 60 * 2; + data->cs4398_regs[6] = 60 * 2; + data->cs4398_regs[7] = CS4398_RMP_DN | CS4398_RMP_UP | + CS4398_ZERO_CROSS | CS4398_SOFT_RAMP; + data->cs4362a_regs[4] = CS4362A_RMP_DN | CS4362A_DEM_NONE; + data->cs4362a_regs[6] = CS4362A_FM_SINGLE | + CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L; + data->cs4362a_regs[7] = 60 | CS4362A_MUTE; + data->cs4362a_regs[8] = 60 | CS4362A_MUTE; + data->cs4362a_regs[9] = data->cs4362a_regs[6]; + data->cs4362a_regs[10] = 60 | CS4362A_MUTE; + data->cs4362a_regs[11] = 60 | CS4362A_MUTE; + data->cs4362a_regs[12] = data->cs4362a_regs[6]; + data->cs4362a_regs[13] = 60 | CS4362A_MUTE; + data->cs4362a_regs[14] = 60 | CS4362A_MUTE; + + oxygen_write16(chip, OXYGEN_2WIRE_BUS_STATUS, + OXYGEN_2WIRE_LENGTH_8 | + OXYGEN_2WIRE_INTERRUPT_MASK | + OXYGEN_2WIRE_SPEED_FAST); + + cs43xx_registers_init(chip); + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_D1_FRONT_PANEL | + GPIO_D1_MAGIC | + GPIO_D1_INPUT_ROUTE); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, + GPIO_D1_FRONT_PANEL | GPIO_D1_INPUT_ROUTE); + + xonar_init_cs53x1(chip); + xonar_enable_output(chip); + + snd_component_add(chip->card, "CS4398"); + snd_component_add(chip->card, "CS4362A"); + snd_component_add(chip->card, "CS5361"); +} + +static void xonar_dx_init(struct oxygen *chip) +{ + struct xonar_cs43xx *data = chip->model_data; + + data->generic.ext_power_reg = OXYGEN_GPI_DATA; + data->generic.ext_power_int_reg = OXYGEN_GPI_INTERRUPT_MASK; + data->generic.ext_power_bit = GPI_EXT_POWER; + xonar_init_ext_power(chip); + xonar_d1_init(chip); +} + +static void xonar_d1_cleanup(struct oxygen *chip) +{ + xonar_disable_output(chip); + cs4362a_write(chip, 0x01, CS4362A_PDN | CS4362A_CPEN); + oxygen_clear_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC); +} + +static void xonar_d1_suspend(struct oxygen *chip) +{ + xonar_d1_cleanup(chip); +} + +static void xonar_d1_resume(struct oxygen *chip) +{ + oxygen_set_bits8(chip, OXYGEN_FUNCTION, OXYGEN_FUNCTION_RESET_CODEC); + msleep(1); + cs43xx_registers_init(chip); + xonar_enable_output(chip); +} + +static void set_cs43xx_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct xonar_cs43xx *data = chip->model_data; + u8 cs4398_fm, cs4362a_fm; + + if (params_rate(params) <= 50000) { + cs4398_fm = CS4398_FM_SINGLE; + cs4362a_fm = CS4362A_FM_SINGLE; + } else if (params_rate(params) <= 100000) { + cs4398_fm = CS4398_FM_DOUBLE; + cs4362a_fm = CS4362A_FM_DOUBLE; + } else { + cs4398_fm = CS4398_FM_QUAD; + cs4362a_fm = CS4362A_FM_QUAD; + } + cs4398_fm |= CS4398_DEM_NONE | CS4398_DIF_LJUST; + cs4398_write_cached(chip, 2, cs4398_fm); + cs4362a_fm |= data->cs4362a_regs[6] & ~CS4362A_FM_MASK; + cs4362a_write_cached(chip, 6, cs4362a_fm); + cs4362a_write_cached(chip, 12, cs4362a_fm); + cs4362a_fm &= CS4362A_FM_MASK; + cs4362a_fm |= data->cs4362a_regs[9] & ~CS4362A_FM_MASK; + cs4362a_write_cached(chip, 9, cs4362a_fm); +} + +static void update_cs4362a_volumes(struct oxygen *chip) +{ + unsigned int i; + u8 mute; + + mute = chip->dac_mute ? CS4362A_MUTE : 0; + for (i = 0; i < 6; ++i) + cs4362a_write_cached(chip, 7 + i + i / 2, + (127 - chip->dac_volume[2 + i]) | mute); +} + +static void update_cs43xx_volume(struct oxygen *chip) +{ + cs4398_write_cached(chip, 5, (127 - chip->dac_volume[0]) * 2); + cs4398_write_cached(chip, 6, (127 - chip->dac_volume[1]) * 2); + update_cs4362a_volumes(chip); +} + +static void update_cs43xx_mute(struct oxygen *chip) +{ + u8 reg; + + reg = CS4398_MUTEP_LOW | CS4398_PAMUTE; + if (chip->dac_mute) + reg |= CS4398_MUTE_B | CS4398_MUTE_A; + cs4398_write_cached(chip, 4, reg); + update_cs4362a_volumes(chip); +} + +static void update_cs43xx_center_lfe_mix(struct oxygen *chip, bool mixed) +{ + struct xonar_cs43xx *data = chip->model_data; + u8 reg; + + reg = data->cs4362a_regs[9] & ~CS4362A_ATAPI_MASK; + if (mixed) + reg |= CS4362A_ATAPI_B_LR | CS4362A_ATAPI_A_LR; + else + reg |= CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L; + cs4362a_write_cached(chip, 9, reg); +} + +static const struct snd_kcontrol_new front_panel_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Front Panel Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = xonar_gpio_bit_switch_get, + .put = xonar_gpio_bit_switch_put, + .private_value = GPIO_D1_FRONT_PANEL, +}; + +static int rolloff_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "Fast Roll-off", "Slow Roll-off" + }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int rolloff_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_cs43xx *data = chip->model_data; + + value->value.enumerated.item[0] = + (data->cs4398_regs[7] & CS4398_FILT_SEL) != 0; + return 0; +} + +static int rolloff_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_cs43xx *data = chip->model_data; + int changed; + u8 reg; + + 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..df44135e1 --- /dev/null +++ b/sound/pci/oxygen/xonar_dg.c @@ -0,0 +1,295 @@ +/* + * card driver for the Xonar DG/DGX + * + * Copyright (c) Clemens Ladisch + * Copyright (c) Roman Volkov + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +/* + * Xonar DG/DGX + * ------------ + * + * CS4245 and CS4361 both will mute all outputs if any clock ratio + * is invalid. + * + * CMI8788: + * + * SPI 0 -> CS4245 + * + * Playback: + * I²S 1 -> CS4245 + * I²S 2 -> CS4361 (center/LFE) + * I²S 3 -> CS4361 (surround) + * I²S 4 -> CS4361 (front) + * Capture: + * I²S ADC 1 <- CS4245 + * + * GPIO 3 <- ? + * GPIO 4 <- headphone detect + * GPIO 5 -> enable ADC analog circuit for the left channel + * GPIO 6 -> enable ADC analog circuit for the right channel + * GPIO 7 -> switch green rear output jack between CS4245 and the first + * channel of CS4361 (mechanical relay) + * GPIO 8 -> enable output to speakers + * + * CS4245: + * + * input 0 <- mic + * input 1 <- aux + * input 2 <- front mic + * input 4 <- line + * DAC out -> headphones + * aux out -> front panel headphones + */ + +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "xonar_dg.h" +#include "cs4245.h" + +int cs4245_write_spi(struct oxygen *chip, u8 reg) +{ + struct dg *data = chip->model_data; + unsigned int packet; + + packet = reg << 8; + packet |= (CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 16; + packet |= data->cs4245_shadow[reg]; + + return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_3 | + OXYGEN_SPI_CLOCK_1280 | + (0 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI, + packet); +} + +int cs4245_read_spi(struct oxygen *chip, u8 addr) +{ + struct dg *data = chip->model_data; + int ret; + + ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI | + OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), + ((CS4245_SPI_ADDRESS | CS4245_SPI_WRITE) << 8) | addr); + if (ret < 0) + return ret; + + ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI | + OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT), + (CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8); + if (ret < 0) + return ret; + + data->cs4245_shadow[addr] = oxygen_read8(chip, OXYGEN_SPI_DATA1); + + return 0; +} + +int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op) +{ + struct dg *data = chip->model_data; + unsigned char addr; + int ret; + + for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) { + ret = (op == CS4245_SAVE_TO_SHADOW ? + cs4245_read_spi(chip, addr) : + cs4245_write_spi(chip, addr)); + if (ret < 0) + return ret; + } + return 0; +} + +static void cs4245_init(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + /* save the initial state: codec version, registers */ + cs4245_shadow_control(chip, CS4245_SAVE_TO_SHADOW); + + /* + * Power up the CODEC internals, enable soft ramp & zero cross, work in + * async. mode, enable aux output from DAC. Invert DAC output as in the + * Windows driver. + */ + data->cs4245_shadow[CS4245_POWER_CTRL] = 0; + data->cs4245_shadow[CS4245_SIGNAL_SEL] = + CS4245_A_OUT_SEL_DAC | CS4245_ASYNCH; + data->cs4245_shadow[CS4245_DAC_CTRL_1] = + CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST; + data->cs4245_shadow[CS4245_DAC_CTRL_2] = + CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC; + data->cs4245_shadow[CS4245_ADC_CTRL] = + CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST; + data->cs4245_shadow[CS4245_ANALOG_IN] = + CS4245_PGA_SOFT | CS4245_PGA_ZERO; + data->cs4245_shadow[CS4245_PGA_B_CTRL] = 0; + data->cs4245_shadow[CS4245_PGA_A_CTRL] = 0; + data->cs4245_shadow[CS4245_DAC_A_CTRL] = 8; + data->cs4245_shadow[CS4245_DAC_B_CTRL] = 8; + + cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); + snd_component_add(chip->card, "CS4245"); +} + +void dg_init(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + data->output_sel = PLAYBACK_DST_HP_FP; + data->input_sel = CAPTURE_SRC_MIC; + + cs4245_init(chip); + oxygen_write16(chip, OXYGEN_GPIO_CONTROL, + GPIO_OUTPUT_ENABLE | GPIO_HP_REAR | GPIO_INPUT_ROUTE); + /* anti-pop delay, wait some time before enabling the output */ + msleep(2500); + oxygen_write16(chip, OXYGEN_GPIO_DATA, + GPIO_OUTPUT_ENABLE | GPIO_INPUT_ROUTE); +} + +void dg_cleanup(struct oxygen *chip) +{ + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); +} + +void dg_suspend(struct oxygen *chip) +{ + dg_cleanup(chip); +} + +void dg_resume(struct oxygen *chip) +{ + cs4245_shadow_control(chip, CS4245_LOAD_FROM_SHADOW); + msleep(2500); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE); +} + +void set_cs4245_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct dg *data = chip->model_data; + unsigned char dac_ctrl; + unsigned char mclk_freq; + + dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK; + mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK; + if (params_rate(params) <= 50000) { + dac_ctrl |= CS4245_DAC_FM_SINGLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else if (params_rate(params) <= 100000) { + dac_ctrl |= CS4245_DAC_FM_DOUBLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT; + } else { + dac_ctrl |= CS4245_DAC_FM_QUAD; + mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT; + } + data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl; + data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; + cs4245_write_spi(chip, CS4245_DAC_CTRL_1); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); +} + +void set_cs4245_adc_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct dg *data = chip->model_data; + unsigned char adc_ctrl; + unsigned char mclk_freq; + + adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK; + mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK; + if (params_rate(params) <= 50000) { + adc_ctrl |= CS4245_ADC_FM_SINGLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else if (params_rate(params) <= 100000) { + adc_ctrl |= CS4245_ADC_FM_DOUBLE; + mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT; + } else { + adc_ctrl |= CS4245_ADC_FM_QUAD; + mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT; + } + data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl; + data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq; + cs4245_write_spi(chip, CS4245_ADC_CTRL); + cs4245_write_spi(chip, CS4245_MCLK_FREQ); +} + +static inline unsigned int shift_bits(unsigned int value, + unsigned int shift_from, + unsigned int shift_to, + unsigned int mask) +{ + if (shift_from < shift_to) + return (value << (shift_to - shift_from)) & mask; + else + return (value >> (shift_from - shift_to)) & mask; +} + +unsigned int adjust_dg_dac_routing(struct oxygen *chip, + unsigned int play_routing) +{ + struct dg *data = chip->model_data; + + switch (data->output_sel) { + case PLAYBACK_DST_HP: + case PLAYBACK_DST_HP_FP: + oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 | + OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK); + break; + case PLAYBACK_DST_MULTICH: + oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING, + OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK); + break; + } + return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) | + shift_bits(play_routing, + OXYGEN_PLAY_DAC2_SOURCE_SHIFT, + OXYGEN_PLAY_DAC1_SOURCE_SHIFT, + OXYGEN_PLAY_DAC1_SOURCE_MASK) | + shift_bits(play_routing, + OXYGEN_PLAY_DAC1_SOURCE_SHIFT, + OXYGEN_PLAY_DAC2_SOURCE_SHIFT, + OXYGEN_PLAY_DAC2_SOURCE_MASK) | + shift_bits(play_routing, + OXYGEN_PLAY_DAC0_SOURCE_SHIFT, + OXYGEN_PLAY_DAC3_SOURCE_SHIFT, + OXYGEN_PLAY_DAC3_SOURCE_MASK); +} + +void dump_cs4245_registers(struct oxygen *chip, + struct snd_info_buffer *buffer) +{ + struct dg *data = chip->model_data; + unsigned int addr; + + snd_iprintf(buffer, "\nCS4245:"); + cs4245_read_spi(chip, CS4245_INT_STATUS); + for (addr = 1; addr < ARRAY_SIZE(data->cs4245_shadow); addr++) + snd_iprintf(buffer, " %02x", data->cs4245_shadow[addr]); + snd_iprintf(buffer, "\n"); +} diff --git a/sound/pci/oxygen/xonar_dg.h b/sound/pci/oxygen/xonar_dg.h new file mode 100644 index 000000000..24d97721c --- /dev/null +++ b/sound/pci/oxygen/xonar_dg.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef XONAR_DG_H_INCLUDED +#define XONAR_DG_H_INCLUDED + +#include "oxygen.h" + +#define GPIO_MAGIC 0x0008 +#define GPIO_HP_DETECT 0x0010 +#define GPIO_INPUT_ROUTE 0x0060 +#define GPIO_HP_REAR 0x0080 +#define GPIO_OUTPUT_ENABLE 0x0100 + +#define CAPTURE_SRC_MIC 0 +#define CAPTURE_SRC_FP_MIC 1 +#define CAPTURE_SRC_LINE 2 +#define CAPTURE_SRC_AUX 3 + +#define PLAYBACK_DST_HP 0 +#define PLAYBACK_DST_HP_FP 1 +#define PLAYBACK_DST_MULTICH 2 + +enum cs4245_shadow_operation { + CS4245_SAVE_TO_SHADOW, + CS4245_LOAD_FROM_SHADOW +}; + +struct dg { + /* shadow copy of the CS4245 register space */ + unsigned char cs4245_shadow[17]; + /* output select: headphone/speakers */ + unsigned char output_sel; + /* volumes for all capture sources */ + char input_vol[4][2]; + /* input select: mic/fp mic/line/aux */ + unsigned char input_sel; +}; + +/* Xonar DG control routines */ +int cs4245_write_spi(struct oxygen *chip, u8 reg); +int cs4245_read_spi(struct oxygen *chip, u8 reg); +int cs4245_shadow_control(struct oxygen *chip, enum cs4245_shadow_operation op); +void dg_init(struct oxygen *chip); +void set_cs4245_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params); +void set_cs4245_adc_params(struct oxygen *chip, + struct snd_pcm_hw_params *params); +unsigned int adjust_dg_dac_routing(struct oxygen *chip, + unsigned int play_routing); +void dump_cs4245_registers(struct oxygen *chip, + struct snd_info_buffer *buffer); +void dg_suspend(struct oxygen *chip); +void dg_resume(struct oxygen *chip); +void dg_cleanup(struct oxygen *chip); + +extern const struct oxygen_model model_xonar_dg; + +#endif diff --git a/sound/pci/oxygen/xonar_dg_mixer.c b/sound/pci/oxygen/xonar_dg_mixer.c new file mode 100644 index 000000000..d22fbe8ae --- /dev/null +++ b/sound/pci/oxygen/xonar_dg_mixer.c @@ -0,0 +1,477 @@ +/* + * Mixer controls for the Xonar DG/DGX + * + * Copyright (c) Clemens Ladisch + * Copyright (c) Roman Volkov + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "oxygen.h" +#include "xonar_dg.h" +#include "cs4245.h" + +/* analog output select */ + +static int output_select_apply(struct oxygen *chip) +{ + struct dg *data = chip->model_data; + + data->cs4245_shadow[CS4245_SIGNAL_SEL] &= ~CS4245_A_OUT_SEL_MASK; + if (data->output_sel == PLAYBACK_DST_HP) { + /* mute FP (aux output) amplifier, switch rear jack to CS4245 */ + oxygen_set_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); + } else if (data->output_sel == PLAYBACK_DST_HP_FP) { + /* + * Unmute FP amplifier, switch rear jack to CS4361; + * I2S channels 2,3,4 should be inactive. + */ + oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); + data->cs4245_shadow[CS4245_SIGNAL_SEL] |= CS4245_A_OUT_SEL_DAC; + } else { + /* + * 2.0, 4.0, 5.1: switch to CS4361, mute FP amp., + * and change playback routing. + */ + oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR); + } + return cs4245_write_spi(chip, CS4245_SIGNAL_SEL); +} + +static int output_select_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[3] = { + "Stereo Headphones", + "Stereo Headphones FP", + "Multichannel", + }; + + return snd_ctl_enum_info(info, 1, 3, names); +} + +static int output_select_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct dg *data = chip->model_data; + + 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..91d92bc32 --- /dev/null +++ b/sound/pci/oxygen/xonar_hdmi.c @@ -0,0 +1,128 @@ +/* + * helper functions for HDMI models (Xonar HDAV1.3/HDAV1.3 Slim) + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" + +static void hdmi_write_command(struct oxygen *chip, u8 command, + unsigned int count, const u8 *params) +{ + unsigned int i; + u8 checksum; + + oxygen_write_uart(chip, 0xfb); + oxygen_write_uart(chip, 0xef); + oxygen_write_uart(chip, command); + oxygen_write_uart(chip, count); + for (i = 0; i < count; ++i) + oxygen_write_uart(chip, params[i]); + checksum = 0xfb + 0xef + command + count; + for (i = 0; i < count; ++i) + checksum += params[i]; + oxygen_write_uart(chip, checksum); +} + +static void xonar_hdmi_init_commands(struct oxygen *chip, + struct xonar_hdmi *hdmi) +{ + u8 param; + + oxygen_reset_uart(chip); + param = 0; + hdmi_write_command(chip, 0x61, 1, ¶m); + param = 1; + hdmi_write_command(chip, 0x74, 1, ¶m); + hdmi_write_command(chip, 0x54, 5, hdmi->params); +} + +void xonar_hdmi_init(struct oxygen *chip, struct xonar_hdmi *hdmi) +{ + hdmi->params[1] = IEC958_AES3_CON_FS_48000; + hdmi->params[4] = 1; + xonar_hdmi_init_commands(chip, hdmi); +} + +void xonar_hdmi_cleanup(struct oxygen *chip) +{ + u8 param = 0; + + hdmi_write_command(chip, 0x74, 1, ¶m); +} + +void xonar_hdmi_resume(struct oxygen *chip, struct xonar_hdmi *hdmi) +{ + xonar_hdmi_init_commands(chip, hdmi); +} + +void xonar_hdmi_pcm_hardware_filter(unsigned int channel, + struct snd_pcm_hardware *hardware) +{ + if (channel == PCM_MULTICH) { + hardware->rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000; + hardware->rate_min = 44100; + } +} + +void xonar_set_hdmi_params(struct oxygen *chip, struct xonar_hdmi *hdmi, + struct snd_pcm_hw_params *params) +{ + hdmi->params[0] = 0; /* 1 = non-audio */ + switch (params_rate(params)) { + case 44100: + hdmi->params[1] = IEC958_AES3_CON_FS_44100; + break; + case 48000: + hdmi->params[1] = IEC958_AES3_CON_FS_48000; + break; + default: /* 96000 */ + hdmi->params[1] = IEC958_AES3_CON_FS_96000; + break; + case 192000: + hdmi->params[1] = IEC958_AES3_CON_FS_192000; + break; + } + hdmi->params[2] = params_channels(params) / 2 - 1; + if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE) + hdmi->params[3] = 0; + else + hdmi->params[3] = 0xc0; + hdmi->params[4] = 1; /* ? */ + hdmi_write_command(chip, 0x54, 5, hdmi->params); +} + +void xonar_hdmi_uart_input(struct oxygen *chip) +{ + if (chip->uart_input_count >= 2 && + chip->uart_input[chip->uart_input_count - 2] == 'O' && + chip->uart_input[chip->uart_input_count - 1] == 'K') { + dev_dbg(chip->card->dev, "message from HDMI chip received:\n"); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + chip->uart_input, chip->uart_input_count); + chip->uart_input_count = 0; + } +} diff --git a/sound/pci/oxygen/xonar_lib.c b/sound/pci/oxygen/xonar_lib.c new file mode 100644 index 000000000..706b1a421 --- /dev/null +++ b/sound/pci/oxygen/xonar_lib.c @@ -0,0 +1,134 @@ +/* + * helper functions for Asus Xonar cards + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +#include +#include +#include +#include +#include +#include "xonar.h" + + +#define GPIO_CS53x1_M_MASK 0x000c +#define GPIO_CS53x1_M_SINGLE 0x0000 +#define GPIO_CS53x1_M_DOUBLE 0x0004 +#define GPIO_CS53x1_M_QUAD 0x0008 + + +void xonar_enable_output(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, data->output_enable_bit); + msleep(data->anti_pop_delay); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit); +} + +void xonar_disable_output(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, data->output_enable_bit); +} + +static void xonar_ext_power_gpio_changed(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + u8 has_power; + + has_power = !!(oxygen_read8(chip, data->ext_power_reg) + & data->ext_power_bit); + if (has_power != data->has_power) { + data->has_power = has_power; + if (has_power) { + dev_notice(chip->card->dev, "power restored\n"); + } else { + dev_crit(chip->card->dev, + "Hey! Don't unplug the power cable!\n"); + /* TODO: stop PCMs */ + } + } +} + +void xonar_init_ext_power(struct oxygen *chip) +{ + struct xonar_generic *data = chip->model_data; + + oxygen_set_bits8(chip, data->ext_power_int_reg, + data->ext_power_bit); + chip->interrupt_mask |= OXYGEN_INT_GPIO; + chip->model.gpio_changed = xonar_ext_power_gpio_changed; + data->has_power = !!(oxygen_read8(chip, data->ext_power_reg) + & data->ext_power_bit); +} + +void xonar_init_cs53x1(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CS53x1_M_MASK); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + GPIO_CS53x1_M_SINGLE, GPIO_CS53x1_M_MASK); +} + +void xonar_set_cs53x1_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int value; + + if (params_rate(params) <= 54000) + value = GPIO_CS53x1_M_SINGLE; + else if (params_rate(params) <= 108000) + value = GPIO_CS53x1_M_DOUBLE; + else + value = GPIO_CS53x1_M_QUAD; + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + value, GPIO_CS53x1_M_MASK); +} + +int xonar_gpio_bit_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 bit = ctl->private_value; + bool invert = ctl->private_value & XONAR_GPIO_BIT_INVERT; + + value->value.integer.value[0] = + !!(oxygen_read16(chip, OXYGEN_GPIO_DATA) & bit) ^ invert; + return 0; +} + +int xonar_gpio_bit_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + u16 bit = ctl->private_value; + bool invert = ctl->private_value & XONAR_GPIO_BIT_INVERT; + u16 old_bits, new_bits; + int changed; + + 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..24109d37c --- /dev/null +++ b/sound/pci/oxygen/xonar_pcm179x.c @@ -0,0 +1,1299 @@ +/* + * card driver for models with PCM1796 DACs (Xonar D2/D2X/HDAV1.3/ST/STX) + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +/* + * Xonar D2/D2X + * ------------ + * + * CMI8788: + * + * SPI 0 -> 1st PCM1796 (front) + * SPI 1 -> 2nd PCM1796 (surround) + * SPI 2 -> 3rd PCM1796 (center/LFE) + * SPI 4 -> 4th PCM1796 (back) + * + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 5 <- external power present (D2X only) + * GPIO 7 -> ALT + * GPIO 8 -> enable output to speakers + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * VIDEO_IN <- CD + * FMIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + */ + +/* + * Xonar HDAV1.3 (Deluxe) + * ---------------------- + * + * CMI8788: + * + * I²C <-> PCM1796 (addr 1001100) (front) + * + * GPI 0 <- external power present + * + * GPIO 0 -> enable HDMI (0) or speaker (1) output + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 4 <- daughterboard detection + * GPIO 5 <- daughterboard detection + * GPIO 6 -> ? + * GPIO 7 -> ? + * GPIO 8 -> route input jack to line-in (0) or mic-in (1) + * + * UART <-> HDMI controller + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * CD_IN <- CD + * MIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + * + * no daughterboard + * ---------------- + * + * GPIO 4 <- 1 + * + * H6 daughterboard + * ---------------- + * + * GPIO 4 <- 0 + * GPIO 5 <- 0 + * + * I²C <-> PCM1796 (addr 1001101) (surround) + * <-> PCM1796 (addr 1001110) (center/LFE) + * <-> PCM1796 (addr 1001111) (back) + * + * unknown daughterboard + * --------------------- + * + * GPIO 4 <- 0 + * GPIO 5 <- 1 + * + * I²C <-> CS4362A (addr 0011000) (surround, center/LFE, back) + */ + +/* + * Xonar Essence ST (Deluxe)/STX (II) + * ---------------------------------- + * + * CMI8788: + * + * I²C <-> PCM1792A (addr 1001100) + * <-> CS2000 (addr 1001110) (ST only) + * + * ADC1 MCLK -> REF_CLK of CS2000 (ST only) + * + * GPI 0 <- external power present (STX only) + * + * GPIO 0 -> enable output to speakers + * GPIO 1 -> route HP to front panel (0) or rear jack (1) + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 4 <- daughterboard detection + * GPIO 5 <- daughterboard detection + * GPIO 6 -> ? + * GPIO 7 -> route output to speaker jacks (0) or HP (1) + * GPIO 8 -> route input jack to line-in (0) or mic-in (1) + * + * PCM1792A: + * + * SCK <- CLK_OUT of CS2000 (ST only) + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * MIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + * + * H6 daughterboard + * ---------------- + * + * GPIO 4 <- 0 + * GPIO 5 <- 0 + */ + +/* + * Xonar Xense + * ----------- + * + * CMI8788: + * + * I²C <-> PCM1796 (addr 1001100) (front) + * <-> CS4362A (addr 0011000) (surround, center/LFE, back) + * <-> CS2000 (addr 1001110) + * + * ADC1 MCLK -> REF_CLK of CS2000 + * + * GPI 0 <- external power present + * + * GPIO 0 -> enable output + * GPIO 1 -> route HP to front panel (0) or rear jack (1) + * GPIO 2 -> M0 of CS5381 + * GPIO 3 -> M1 of CS5381 + * GPIO 4 -> enable output + * GPIO 5 -> enable output + * GPIO 6 -> ? + * GPIO 7 -> route output to HP (0) or speaker (1) + * GPIO 8 -> route input jack to mic-in (0) or line-in (1) + * + * CM9780: + * + * LINE_OUT -> input of ADC + * + * AUX_IN <- aux + * VIDEO_IN <- ? + * FMIC_IN <- mic + * + * GPO 0 -> route line-in (0) or AC97 output (1) to CS5381 input + * GPO 1 -> route mic-in from input jack (0) or front panel header (1) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" +#include "cm9780.h" +#include "pcm1796.h" +#include "cs2000.h" + + +#define GPIO_D2X_EXT_POWER 0x0020 +#define GPIO_D2_ALT 0x0080 +#define GPIO_D2_OUTPUT_ENABLE 0x0100 + +#define GPI_EXT_POWER 0x01 +#define GPIO_INPUT_ROUTE 0x0100 + +#define GPIO_HDAV_OUTPUT_ENABLE 0x0001 +#define GPIO_HDAV_MAGIC 0x00c0 + +#define GPIO_DB_MASK 0x0030 +#define GPIO_DB_H6 0x0000 + +#define GPIO_ST_OUTPUT_ENABLE 0x0001 +#define GPIO_ST_HP_REAR 0x0002 +#define GPIO_ST_MAGIC 0x0040 +#define GPIO_ST_HP 0x0080 + +#define GPIO_XENSE_OUTPUT_ENABLE (0x0001 | 0x0010 | 0x0020) +#define GPIO_XENSE_SPEAKERS 0x0080 + +#define I2C_DEVICE_PCM1796(i) (0x98 + ((i) << 1)) /* 10011, ii, /W=0 */ +#define I2C_DEVICE_CS2000 0x9c /* 100111, 0, /W=0 */ + +#define PCM1796_REG_BASE 16 + + +struct xonar_pcm179x { + struct xonar_generic generic; + unsigned int dacs; + u8 pcm1796_regs[4][5]; + unsigned int current_rate; + bool h6; + bool hp_active; + s8 hp_gain_offset; + bool has_cs2000; + u8 cs2000_regs[0x1f]; + bool broken_i2c; +}; + +struct xonar_hdav { + struct xonar_pcm179x pcm179x; + struct xonar_hdmi hdmi; +}; + + +static inline void pcm1796_write_spi(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + /* maps ALSA channel pair number to SPI output */ + static const u8 codec_map[4] = { + 0, 1, 2, 4 + }; + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (codec_map[codec] << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_HI, + (reg << 8) | value); +} + +static inline void pcm1796_write_i2c(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + oxygen_write_i2c(chip, I2C_DEVICE_PCM1796(codec), reg, value); +} + +static void pcm1796_write(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + if ((chip->model.function_flags & OXYGEN_FUNCTION_2WIRE_SPI_MASK) == + OXYGEN_FUNCTION_SPI) + pcm1796_write_spi(chip, codec, reg, value); + else + pcm1796_write_i2c(chip, codec, reg, value); + if ((unsigned int)(reg - PCM1796_REG_BASE) + < ARRAY_SIZE(data->pcm1796_regs[codec])) + data->pcm1796_regs[codec][reg - PCM1796_REG_BASE] = value; +} + +static void pcm1796_write_cached(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + if (value != data->pcm1796_regs[codec][reg - PCM1796_REG_BASE]) + pcm1796_write(chip, codec, reg, value); +} + +static void cs2000_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + oxygen_write_i2c(chip, I2C_DEVICE_CS2000, reg, value); + data->cs2000_regs[reg] = value; +} + +static void cs2000_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct xonar_pcm179x *data = chip->model_data; + + if (value != data->cs2000_regs[reg]) + cs2000_write(chip, reg, value); +} + +static void pcm1796_registers_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + unsigned int i; + s8 gain_offset; + + msleep(1); + gain_offset = data->hp_active ? data->hp_gain_offset : 0; + for (i = 0; i < data->dacs; ++i) { + /* set ATLD before ATL/ATR */ + pcm1796_write(chip, i, 18, + data->pcm1796_regs[0][18 - PCM1796_REG_BASE]); + pcm1796_write(chip, i, 16, chip->dac_volume[i * 2] + + gain_offset); + pcm1796_write(chip, i, 17, chip->dac_volume[i * 2 + 1] + + gain_offset); + pcm1796_write(chip, i, 19, + data->pcm1796_regs[0][19 - PCM1796_REG_BASE]); + pcm1796_write(chip, i, 20, + data->pcm1796_regs[0][20 - PCM1796_REG_BASE]); + pcm1796_write(chip, i, 21, 0); + gain_offset = 0; + } +} + +static void pcm1796_init(struct oxygen *chip) +{ + struct xonar_pcm179x *data = chip->model_data; + + data->pcm1796_regs[0][18 - PCM1796_REG_BASE] = + PCM1796_DMF_DISABLED | 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 = 1; + 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 = 1; + 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 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); +} + +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 = PCM1796_DMF_DISABLED | PCM1796_FMT_24_I2S | PCM1796_ATLD; + if (chip->dac_mute) + 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 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; + } + 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..90ac479f3 --- /dev/null +++ b/sound/pci/oxygen/xonar_wm87x6.c @@ -0,0 +1,1342 @@ +/* + * card driver for models with WM8776/WM8766 DACs (Xonar DS/HDAV1.3 Slim) + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +/* + * Xonar DS + * -------- + * + * CMI8788: + * + * SPI 0 -> WM8766 (surround, center/LFE, back) + * SPI 1 -> WM8776 (front, input) + * + * GPIO 4 <- headphone detect, 0 = plugged + * GPIO 6 -> route input jack to mic-in (0) or line-in (1) + * GPIO 7 -> enable output to front L/R speaker channels + * GPIO 8 -> enable output to other speaker channels and front panel headphone + * + * WM8776: + * + * input 1 <- line + * input 2 <- mic + * input 3 <- front mic + * input 4 <- aux + */ + +/* + * Xonar HDAV1.3 Slim + * ------------------ + * + * CMI8788: + * + * I²C <-> WM8776 (addr 0011010) + * + * GPIO 0 -> disable HDMI output + * GPIO 1 -> enable HP output + * GPIO 6 -> firmware EEPROM I²C clock + * GPIO 7 <-> firmware EEPROM I²C data + * + * UART <-> HDMI controller + * + * WM8776: + * + * input 1 <- mic + * input 2 <- aux + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" +#include "wm8776.h" +#include "wm8766.h" + +#define GPIO_DS_HP_DETECT 0x0010 +#define GPIO_DS_INPUT_ROUTE 0x0040 +#define GPIO_DS_OUTPUT_FRONTLR 0x0080 +#define GPIO_DS_OUTPUT_ENABLE 0x0100 + +#define GPIO_SLIM_HDMI_DISABLE 0x0001 +#define GPIO_SLIM_OUTPUT_ENABLE 0x0002 +#define GPIO_SLIM_FIRMWARE_CLK 0x0040 +#define GPIO_SLIM_FIRMWARE_DATA 0x0080 + +#define I2C_DEVICE_WM8776 0x34 /* 001101, 0, /W=0 */ + +#define LC_CONTROL_LIMITER 0x40000000 +#define LC_CONTROL_ALC 0x20000000 + +struct xonar_wm87x6 { + struct xonar_generic generic; + u16 wm8776_regs[0x17]; + u16 wm8766_regs[0x10]; + struct snd_kcontrol *line_adcmux_control; + struct snd_kcontrol *mic_adcmux_control; + struct snd_kcontrol *lc_controls[13]; + struct snd_jack *hp_jack; + struct xonar_hdmi hdmi; +}; + +static void wm8776_write_spi(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (1 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_LO, + (reg << 9) | value); +} + +static void wm8776_write_i2c(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + oxygen_write_i2c(chip, I2C_DEVICE_WM8776, + (reg << 1) | (value >> 8), value); +} + +static void wm8776_write(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + if ((chip->model.function_flags & OXYGEN_FUNCTION_2WIRE_SPI_MASK) == + OXYGEN_FUNCTION_SPI) + wm8776_write_spi(chip, reg, value); + else + wm8776_write_i2c(chip, reg, value); + if (reg < ARRAY_SIZE(data->wm8776_regs)) { + if (reg >= WM8776_HPLVOL && 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)) { + if ((reg >= WM8766_LDA1 && 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..b06128e91 --- /dev/null +++ b/sound/pci/pcxhr/Makefile @@ -0,0 +1,2 @@ +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..e57da4036 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr.c @@ -0,0 +1,1699 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * main file with alsa callbacks + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" +#include "pcxhr_mix22.h" + +#define DRIVER_NAME "pcxhr" + +MODULE_AUTHOR("Markus Bollinger , " + "Marc Titinger "); +MODULE_DESCRIPTION("Digigram " DRIVER_NAME " " PCXHR_DRIVER_VERSION_STRING); +MODULE_LICENSE("GPL"); +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 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<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<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; + 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); + + stream->channels = channels; + stream->format = format; + + /* allocate buffer */ + err = snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw)); + + mutex_unlock(&mgr->setup_mutex); + + return err; +} + +static int pcxhr_hw_free(struct snd_pcm_substream *subs) +{ + snd_pcm_lib_free_pages(subs); + 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, + .ioctl = snd_pcm_lib_ioctl, + .prepare = pcxhr_prepare, + .hw_params = pcxhr_hw_params, + .hw_free = pcxhr_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->mgr->pci), + 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 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; + + 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "info", &entry)) + snd_info_set_text_ops(entry, chip, pcxhr_proc_info); + if (! snd_card_proc_new(chip->card, "sync", &entry)) + snd_info_set_text_ops(entry, chip, pcxhr_proc_sync); + /* gpio available on stereo sound cards only */ + if (chip->mgr->is_hr_stereo && + !snd_card_proc_new(chip->card, "gpio", &entry)) { + snd_info_set_text_ops(entry, chip, pcxhr_proc_gpio_read); + entry->c.text.write = pcxhr_proc_gpo_write; + entry->mode |= 0200; + } + if (!snd_card_proc_new(chip->card, "ltc", &entry)) + snd_info_set_text_ops(entry, 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, snd_dma_pci_data(pci), + 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..d799cbd37 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr.h @@ -0,0 +1,212 @@ +/* + * Driver for Digigram pcxhr soundcards + * + * main header file + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SOUND_PCXHR_H +#define __SOUND_PCXHR_H + +#include +#include +#include + +#define PCXHR_DRIVER_VERSION 0x000906 /* 0.9.6 */ +#define PCXHR_DRIVER_VERSION_STRING "0.9.6" /* 0.9.6 */ + + +#define PCXHR_MAX_CARDS 6 +#define PCXHR_PLAYBACK_STREAMS 4 + +#define PCXHR_GRANULARITY 96 /* min 96 and multiple of 48 */ +/* transfer granularity of pipes and the dsp time (MBOX4) */ +#define PCXHR_GRANULARITY_MIN 96 +/* TODO : granularity could be 64 or 128 */ +#define PCXHR_GRANULARITY_HR22 192 /* granularity for stereo cards */ + +struct snd_pcxhr; +struct pcxhr_mgr; + +struct pcxhr_stream; +struct pcxhr_pipe; + +enum pcxhr_clock_type { + PCXHR_CLOCK_TYPE_INTERNAL = 0, + PCXHR_CLOCK_TYPE_WORD_CLOCK, + PCXHR_CLOCK_TYPE_AES_SYNC, + PCXHR_CLOCK_TYPE_AES_1, + PCXHR_CLOCK_TYPE_AES_2, + PCXHR_CLOCK_TYPE_AES_3, + PCXHR_CLOCK_TYPE_AES_4, + PCXHR_CLOCK_TYPE_MAX = PCXHR_CLOCK_TYPE_AES_4, + HR22_CLOCK_TYPE_INTERNAL = PCXHR_CLOCK_TYPE_INTERNAL, + HR22_CLOCK_TYPE_AES_SYNC, + HR22_CLOCK_TYPE_AES_1, + HR22_CLOCK_TYPE_MAX = HR22_CLOCK_TYPE_AES_1, +}; + +struct pcxhr_mgr { + unsigned int num_cards; + struct snd_pcxhr *chip[PCXHR_MAX_CARDS]; + + struct pci_dev *pci; + + int irq; + + int granularity; + + /* card access with 1 mem bar and 2 io bar's */ + unsigned long port[3]; + + /* share the name */ + char name[40]; /* name of this soundcard */ + + struct pcxhr_rmh *prmh; + + struct mutex lock; /* interrupt lock */ + struct mutex msg_lock; /* message lock */ + + struct mutex setup_mutex; /* mutex used in hw_params, open and close */ + struct mutex mixer_mutex; /* mutex for mixer */ + + /* hardware interface */ + unsigned int dsp_loaded; /* bit flags of loaded dsp indices */ + unsigned int dsp_version; /* read from embedded once firmware is loaded */ + int playback_chips; + int capture_chips; + int fw_file_set; + int firmware_num; + unsigned int is_hr_stereo:1; + unsigned int board_has_aes1:1; /* if 1 board has AES1 plug and SRC */ + unsigned int board_has_analog:1; /* if 0 the board is digital only */ + unsigned int board_has_mic:1; /* if 1 the board has microphone input */ + unsigned int board_aes_in_192k:1;/* if 1 the aes input plugs do support 192kHz */ + unsigned int mono_capture:1; /* if 1 the board does mono capture */ + unsigned int capture_ltc:1; /* if 1 the board captures LTC input */ + + struct snd_dma_buffer hostport; + + enum pcxhr_clock_type use_clock_type; /* clock type selected by mixer */ + enum pcxhr_clock_type cur_clock_type; /* current clock type synced */ + int sample_rate; + int ref_count_rate; + int timer_toggle; /* timer interrupt toggles between the two values 0x200 and 0x300 */ + int dsp_time_last; /* the last dsp time (read by interrupt) */ + int dsp_time_err; /* dsp time errors */ + unsigned int src_it_dsp; /* dsp interrupt source */ + unsigned int io_num_reg_cont; /* backup of IO_NUM_REG_CONT */ + unsigned int codec_speed; /* speed mode of the codecs */ + unsigned int sample_rate_real; /* current real sample rate */ + int last_reg_stat; + int async_err_stream_xrun; + int async_err_pipe_xrun; + int async_err_other_last; + + unsigned char xlx_cfg; /* copy of PCXHR_XLX_CFG register */ + unsigned char xlx_selmic; /* copy of PCXHR_XLX_SELMIC register */ + unsigned char dsp_reset; /* copy of PCXHR_DSP_RESET register */ +}; + + +enum pcxhr_stream_status { + PCXHR_STREAM_STATUS_FREE, + PCXHR_STREAM_STATUS_OPEN, + PCXHR_STREAM_STATUS_SCHEDULE_RUN, + PCXHR_STREAM_STATUS_STARTED, + PCXHR_STREAM_STATUS_RUNNING, + PCXHR_STREAM_STATUS_SCHEDULE_STOP, + PCXHR_STREAM_STATUS_STOPPED, + PCXHR_STREAM_STATUS_PAUSED +}; + +struct pcxhr_stream { + struct snd_pcm_substream *substream; + snd_pcm_format_t format; + struct pcxhr_pipe *pipe; + + enum pcxhr_stream_status status; /* free, open, running, draining, pause */ + + u_int64_t timer_abs_periods; /* timer: samples elapsed since TRIGGER_START (multiple of period_size) */ + u_int32_t timer_period_frag; /* timer: samples elapsed since last call to snd_pcm_period_elapsed (0..period_size) */ + u_int32_t timer_buf_periods; /* nb of periods in the buffer that have already elapsed */ + int timer_is_synced; /* if(0) : timer needs to be resynced with real hardware pointer */ + + int channels; +}; + + +enum pcxhr_pipe_status { + PCXHR_PIPE_UNDEFINED, + PCXHR_PIPE_DEFINED +}; + +struct pcxhr_pipe { + enum pcxhr_pipe_status status; + int is_capture; /* this is a capture pipe */ + int first_audio; /* first audio num */ +}; + + +struct snd_pcxhr { + struct snd_card *card; + struct pcxhr_mgr *mgr; + int chip_idx; /* zero based */ + + struct snd_pcm *pcm; /* PCM */ + + struct pcxhr_pipe playback_pipe; /* 1 stereo pipe only */ + struct pcxhr_pipe capture_pipe[2]; /* 1 stereo or 2 mono pipes */ + + struct pcxhr_stream playback_stream[PCXHR_PLAYBACK_STREAMS]; + struct pcxhr_stream capture_stream[2]; /* 1 stereo or 2 mono streams */ + int nb_streams_play; + int nb_streams_capt; + + int analog_playback_active[2]; /* Mixer : Master Playback !mute */ + int analog_playback_volume[2]; /* Mixer : Master Playback Volume */ + int analog_capture_volume[2]; /* Mixer : Master Capture Volume */ + int digital_playback_active[PCXHR_PLAYBACK_STREAMS][2]; + int digital_playback_volume[PCXHR_PLAYBACK_STREAMS][2]; + int digital_capture_volume[2]; /* Mixer : Digital Capture Volume */ + int monitoring_active[2]; /* Mixer : Monitoring Active */ + int monitoring_volume[2]; /* Mixer : Monitoring Volume */ + int audio_capture_source; /* Mixer : Audio Capture Source */ + int mic_volume; /* used by cards with MIC only */ + int mic_boost; /* used by cards with MIC only */ + int mic_active; /* used by cards with MIC only */ + int analog_capture_active; /* used by cards with MIC only */ + int phantom_power; /* used by cards with MIC only */ + + unsigned char aes_bits[5]; /* Mixer : IEC958_AES bits */ +}; + +struct pcxhr_hostport +{ + char purgebuffer[6]; + char reserved[2]; +}; + +/* exported */ +int pcxhr_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..d7e71f309 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -0,0 +1,1346 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt and message handling implementation + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" + + +/* registers used on the PLX (port 1) */ +#define PCXHR_PLX_OFFSET_MIN 0x40 +#define PCXHR_PLX_MBOX0 0x40 +#define PCXHR_PLX_MBOX1 0x44 +#define PCXHR_PLX_MBOX2 0x48 +#define PCXHR_PLX_MBOX3 0x4C +#define PCXHR_PLX_MBOX4 0x50 +#define PCXHR_PLX_MBOX5 0x54 +#define PCXHR_PLX_MBOX6 0x58 +#define PCXHR_PLX_MBOX7 0x5C +#define PCXHR_PLX_L2PCIDB 0x64 +#define PCXHR_PLX_IRQCS 0x68 +#define PCXHR_PLX_CHIPSC 0x6C + +/* registers used on the DSP (port 2) */ +#define PCXHR_DSP_ICR 0x00 +#define PCXHR_DSP_CVR 0x04 +#define PCXHR_DSP_ISR 0x08 +#define PCXHR_DSP_IVR 0x0C +#define PCXHR_DSP_RXH 0x14 +#define PCXHR_DSP_TXH 0x14 +#define PCXHR_DSP_RXM 0x18 +#define PCXHR_DSP_TXM 0x18 +#define PCXHR_DSP_RXL 0x1C +#define PCXHR_DSP_TXL 0x1C +#define PCXHR_DSP_RESET 0x20 +#define PCXHR_DSP_OFFSET_MAX 0x20 + +/* access to the card */ +#define PCXHR_PLX 1 +#define PCXHR_DSP 2 + +#if (PCXHR_DSP_OFFSET_MAX > PCXHR_PLX_OFFSET_MIN) +#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, ®); + if (err) { + dev_err(&mgr->pci->dev, "pcxhr_send_it_dsp : TIMEOUT CVR\n"); + return err; + } + if (itdsp & PCXHR_MASK_IT_MANAGE_HF5) { + /* wait for hf5 bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0, + PCXHR_MBOX0_HF5, + PCXHR_MBOX0_HF5, + PCXHR_TIMEOUT_DSP, + ®); + if (err) { + dev_err(&mgr->pci->dev, + "pcxhr_send_it_dsp : TIMEOUT HF5\n"); + return err; + } + } + return 0; /* retry not handled here */ +} + +void pcxhr_reset_xilinx_com(struct pcxhr_mgr *mgr) +{ + /* reset second xilinx */ + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, + PCXHR_CHIPSC_INIT_VALUE & ~PCXHR_CHIPSC_RESET_XILINX); +} + +static void pcxhr_enable_irq(struct pcxhr_mgr *mgr, int enable) +{ + unsigned int reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS); + /* enable/disable interrupts */ + if (enable) + reg |= (PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB); + else + reg &= ~(PCXHR_IRQCS_ENABLE_PCIIRQ | PCXHR_IRQCS_ENABLE_PCIDB); + PCXHR_OUTPL(mgr, PCXHR_PLX_IRQCS, reg); +} + +void pcxhr_reset_dsp(struct pcxhr_mgr *mgr) +{ + /* disable interrupts */ + pcxhr_enable_irq(mgr, 0); + + /* let's reset the DSP */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 0); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, 3); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + + /* reset mailbox */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, 0); +} + +void pcxhr_enable_dsp(struct pcxhr_mgr *mgr) +{ + /* enable interrupts */ + pcxhr_enable_irq(mgr, 1); +} + +/* + * load the xilinx image + */ +int pcxhr_load_xilinx_binary(struct pcxhr_mgr *mgr, + const struct firmware *xilinx, int second) +{ + unsigned int i; + unsigned int chipsc; + unsigned char data; + unsigned char mask; + const unsigned char *image; + + /* test first xilinx */ + chipsc = PCXHR_INPL(mgr, PCXHR_PLX_CHIPSC); + /* REV01 cards do not support the PCXHR_CHIPSC_GPI_USERI bit anymore */ + /* this bit will always be 1; + * no possibility to test presence of first xilinx + */ + if(second) { + if ((chipsc & PCXHR_CHIPSC_GPI_USERI) == 0) { + dev_err(&mgr->pci->dev, "error loading first xilinx\n"); + return -EINVAL; + } + /* activate second xilinx */ + chipsc |= PCXHR_CHIPSC_RESET_XILINX; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + msleep( PCXHR_WAIT_DEFAULT ); /* wait 2 msec */ + } + image = xilinx->data; + for (i = 0; i < xilinx->size; i++, image++) { + data = *image; + mask = 0x80; + while (mask) { + chipsc &= ~(PCXHR_CHIPSC_DATA_CLK | + PCXHR_CHIPSC_DATA_IN); + if (data & mask) + chipsc |= PCXHR_CHIPSC_DATA_IN; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + chipsc |= PCXHR_CHIPSC_DATA_CLK; + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + mask >>= 1; + } + /* don't take too much time in this loop... */ + cond_resched(); + } + chipsc &= ~(PCXHR_CHIPSC_DATA_CLK | PCXHR_CHIPSC_DATA_IN); + PCXHR_OUTPL(mgr, PCXHR_PLX_CHIPSC, chipsc); + /* wait 2 msec (time to boot the xilinx before any access) */ + msleep( PCXHR_WAIT_DEFAULT ); + return 0; +} + +/* + * send an executable file to the DSP + */ +static int pcxhr_download_dsp(struct pcxhr_mgr *mgr, const struct firmware *dsp) +{ + int err; + unsigned int i; + unsigned int len; + const unsigned char *data; + unsigned char dummy; + /* check the length of boot image */ + if (dsp->size <= 0) + return -EINVAL; + if (dsp->size % 3) + return -EINVAL; + if (snd_BUG_ON(!dsp->data)) + return -EINVAL; + /* transfert data buffer from PC to DSP */ + for (i = 0; i < dsp->size; i += 3) { + data = dsp->data + i; + if (i == 0) { + /* test data header consistency */ + len = (unsigned int)((data[0]<<16) + + (data[1]<<8) + + data[2]); + if (len && (dsp->size != (len + 2) * 3)) + return -EINVAL; + } + /* wait DSP ready for new transfer */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, + PCXHR_TIMEOUT_DSP, &dummy); + if (err) { + dev_err(&mgr->pci->dev, + "dsp loading error at position %d\n", i); + return err; + } + /* send host data */ + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, data[0]); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, data[1]); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, data[2]); + + /* don't take too much time in this loop... */ + cond_resched(); + } + /* give some time to boot the DSP */ + msleep(PCXHR_WAIT_DEFAULT); + return 0; +} + +/* + * load the eeprom image + */ +int pcxhr_load_eeprom_binary(struct pcxhr_mgr *mgr, + const struct firmware *eeprom) +{ + int err; + unsigned char reg; + + /* init value of the ICR register */ + reg = PCXHR_ICR_HI08_RREQ | PCXHR_ICR_HI08_TREQ | PCXHR_ICR_HI08_HDRQ; + if (PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & PCXHR_MBOX0_BOOT_HERE) { + /* no need to load the eeprom binary, + * but init the HI08 interface + */ + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg | PCXHR_ICR_HI08_INIT); + msleep(PCXHR_WAIT_DEFAULT); + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + msleep(PCXHR_WAIT_DEFAULT); + dev_dbg(&mgr->pci->dev, "no need to load eeprom boot\n"); + return 0; + } + PCXHR_OUTPB(mgr, PCXHR_DSP_ICR, reg); + + err = pcxhr_download_dsp(mgr, eeprom); + if (err) + return err; + /* wait for chk bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); +} + +/* + * load the boot image + */ +int pcxhr_load_boot_binary(struct pcxhr_mgr *mgr, const struct firmware *boot) +{ + int err; + unsigned int physaddr = mgr->hostport.addr; + unsigned char dummy; + + /* send the hostport address to the DSP (only the upper 24 bit !) */ + if (snd_BUG_ON(physaddr & 0xff)) + return -EINVAL; + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX1, (physaddr >> 8)); + + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_BOOT, 0); + if (err) + return err; + /* clear hf5 bit */ + PCXHR_OUTPL(mgr, PCXHR_PLX_MBOX0, + PCXHR_INPL(mgr, PCXHR_PLX_MBOX0) & ~PCXHR_MBOX0_HF5); + + err = pcxhr_download_dsp(mgr, boot); + if (err) + return err; + /* wait for hf5 bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_PLX_MBOX0, PCXHR_MBOX0_HF5, + PCXHR_MBOX0_HF5, PCXHR_TIMEOUT_DSP, &dummy); +} + +/* + * load the final dsp image + */ +int pcxhr_load_dsp_binary(struct pcxhr_mgr *mgr, const struct firmware *dsp) +{ + int err; + unsigned char dummy; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_BOARD_FUNC, 0); + if (err) + return err; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_DOWNLOAD_DSP, 0); + if (err) + return err; + err = pcxhr_download_dsp(mgr, dsp); + if (err) + return err; + /* wait for chk bit */ + return pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, + PCXHR_TIMEOUT_DSP, &dummy); +} + + +struct pcxhr_cmd_info { + u32 opcode; /* command word */ + u16 st_length; /* status length */ + u16 st_type; /* status type (RMH_SSIZE_XXX) */ +}; + +/* RMH status type */ +enum { + RMH_SSIZE_FIXED = 0, /* status size fix (st_length = 0..x) */ + RMH_SSIZE_ARG = 1, /* status size given in the LSB byte */ + RMH_SSIZE_MASK = 2, /* status size given in bitmask */ +}; + +/* + * Array of DSP commands + */ +static 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 char* cmd_names[] = { +[CMD_VERSION] = "CMD_VERSION", +[CMD_SUPPORTED] = "CMD_SUPPORTED", +[CMD_TEST_IT] = "CMD_TEST_IT", +[CMD_SEND_IRQA] = "CMD_SEND_IRQA", +[CMD_ACCESS_IO_WRITE] = "CMD_ACCESS_IO_WRITE", +[CMD_ACCESS_IO_READ] = "CMD_ACCESS_IO_READ", +[CMD_ASYNC] = "CMD_ASYNC", +[CMD_MODIFY_CLOCK] = "CMD_MODIFY_CLOCK", +[CMD_RESYNC_AUDIO_INPUTS] = "CMD_RESYNC_AUDIO_INPUTS", +[CMD_GET_DSP_RESOURCES] = "CMD_GET_DSP_RESOURCES", +[CMD_SET_TIMER_INTERRUPT] = "CMD_SET_TIMER_INTERRUPT", +[CMD_RES_PIPE] = "CMD_RES_PIPE", +[CMD_FREE_PIPE] = "CMD_FREE_PIPE", +[CMD_CONF_PIPE] = "CMD_CONF_PIPE", +[CMD_STOP_PIPE] = "CMD_STOP_PIPE", +[CMD_PIPE_SAMPLE_COUNT] = "CMD_PIPE_SAMPLE_COUNT", +[CMD_CAN_START_PIPE] = "CMD_CAN_START_PIPE", +[CMD_START_STREAM] = "CMD_START_STREAM", +[CMD_STREAM_OUT_LEVEL_ADJUST] = "CMD_STREAM_OUT_LEVEL_ADJUST", +[CMD_STOP_STREAM] = "CMD_STOP_STREAM", +[CMD_UPDATE_R_BUFFERS] = "CMD_UPDATE_R_BUFFERS", +[CMD_FORMAT_STREAM_OUT] = "CMD_FORMAT_STREAM_OUT", +[CMD_FORMAT_STREAM_IN] = "CMD_FORMAT_STREAM_IN", +[CMD_STREAM_SAMPLE_COUNT] = "CMD_STREAM_SAMPLE_COUNT", +[CMD_AUDIO_LEVEL_ADJUST] = "CMD_AUDIO_LEVEL_ADJUST", +[CMD_GET_TIME_CODE] = "CMD_GET_TIME_CODE", +[CMD_MANAGE_SIGNAL] = "CMD_MANAGE_SIGNAL", +}; +#endif + + +static int pcxhr_read_rmh_status(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + int err; + int i; + u32 data; + u32 size_mask; + unsigned char reg; + int max_stat_len; + + if (rmh->stat_len < PCXHR_SIZE_MAX_STATUS) + max_stat_len = PCXHR_SIZE_MAX_STATUS; + else max_stat_len = rmh->stat_len; + + for (i = 0; i < rmh->stat_len; i++) { + /* wait for receiver full */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_RXDF, + PCXHR_ISR_HI08_RXDF, + PCXHR_TIMEOUT_DSP, ®); + if (err) { + dev_err(&mgr->pci->dev, + "ERROR RMH stat: ISR:RXDF=1 (ISR = %x; i=%d )\n", + reg, i); + return err; + } + /* read data */ + data = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL); + + /* need to update rmh->stat_len on the fly ?? */ + if (!i) { + if (rmh->dsp_stat != RMH_SSIZE_FIXED) { + if (rmh->dsp_stat == RMH_SSIZE_ARG) { + rmh->stat_len = (data & 0x0000ff) + 1; + data &= 0xffff00; + } else { + /* rmh->dsp_stat == RMH_SSIZE_MASK */ + rmh->stat_len = 1; + size_mask = data; + while (size_mask) { + if (size_mask & 1) + rmh->stat_len++; + size_mask >>= 1; + } + } + } + } +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (rmh->cmd_idx < CMD_LAST_INDEX) + dev_dbg(&mgr->pci->dev, " stat[%d]=%x\n", i, data); +#endif + if (i < max_stat_len) + rmh->stat[i] = data; + } + if (rmh->stat_len > max_stat_len) { + dev_dbg(&mgr->pci->dev, "PCXHR : rmh->stat_len=%x too big\n", + rmh->stat_len); + rmh->stat_len = max_stat_len; + } + return 0; +} + +static int pcxhr_send_msg_nolock(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + int err; + int i; + u32 data; + unsigned char reg; + + if (snd_BUG_ON(rmh->cmd_len >= PCXHR_SIZE_MAX_CMD)) + return -EINVAL; + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_MESSAGE, 1); + if (err) { + dev_err(&mgr->pci->dev, + "pcxhr_send_message : ED_DSP_CRASHED\n"); + return err; + } + /* wait for chk bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + /* reset irq chk */ + err = pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_CHK, 1); + if (err) + return err; + /* wait for chk bit == 0*/ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, 0, + PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + + data = rmh->cmd[0]; + + if (rmh->cmd_len > 1) + data |= 0x008000; /* MASK_MORE_THAN_1_WORD_COMMAND */ + else + data &= 0xff7fff; /* MASK_1_WORD_COMMAND */ +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (rmh->cmd_idx < CMD_LAST_INDEX) + dev_dbg(&mgr->pci->dev, "MSG cmd[0]=%x (%s)\n", + data, cmd_names[rmh->cmd_idx]); +#endif + + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + + if (rmh->cmd_len > 1) { + /* send length */ + data = rmh->cmd_len - 1; + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, + PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + + for (i=1; i < rmh->cmd_len; i++) { + /* send other words */ + data = rmh->cmd[i]; +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (rmh->cmd_idx < CMD_LAST_INDEX) + dev_dbg(&mgr->pci->dev, + " cmd[%d]=%x\n", i, data); +#endif + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_TRDY, + PCXHR_ISR_HI08_TRDY, + PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + PCXHR_OUTPB(mgr, PCXHR_DSP_TXH, (data>>16)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXM, (data>>8)&0xFF); + PCXHR_OUTPB(mgr, PCXHR_DSP_TXL, (data&0xFF)); + } + } + /* wait for chk bit */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, PCXHR_ISR_HI08_CHK, + PCXHR_ISR_HI08_CHK, PCXHR_TIMEOUT_DSP, ®); + if (err) + return err; + /* test status ISR */ + if (reg & PCXHR_ISR_HI08_ERR) { + /* ERROR, wait for receiver full */ + err = pcxhr_check_reg_bit(mgr, PCXHR_DSP_ISR, + PCXHR_ISR_HI08_RXDF, + PCXHR_ISR_HI08_RXDF, + PCXHR_TIMEOUT_DSP, ®); + if (err) { + dev_err(&mgr->pci->dev, + "ERROR RMH: ISR:RXDF=1 (ISR = %x)\n", reg); + return err; + } + /* read error code */ + data = PCXHR_INPB(mgr, PCXHR_DSP_TXH) << 16; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXM) << 8; + data |= PCXHR_INPB(mgr, PCXHR_DSP_TXL); + dev_err(&mgr->pci->dev, "ERROR RMH(%d): 0x%x\n", + rmh->cmd_idx, data); + err = -EINVAL; + } else { + /* read the response data */ + err = pcxhr_read_rmh_status(mgr, rmh); + } + /* reset semaphore */ + if (pcxhr_send_it_dsp(mgr, PCXHR_IT_RESET_SEMAPHORE, 1) < 0) + return -EIO; + return err; +} + + +/** + * pcxhr_init_rmh - initialize the RMH instance + * @rmh: the rmh pointer to be initialized + * @cmd: the rmh command to be set + */ +void pcxhr_init_rmh(struct pcxhr_rmh *rmh, int cmd) +{ + if (snd_BUG_ON(cmd >= CMD_LAST_INDEX)) + return; + rmh->cmd[0] = pcxhr_dsp_cmds[cmd].opcode; + rmh->cmd_len = 1; + rmh->stat_len = pcxhr_dsp_cmds[cmd].st_length; + rmh->dsp_stat = pcxhr_dsp_cmds[cmd].st_type; + rmh->cmd_idx = cmd; +} + + +void pcxhr_set_pipe_cmd_params(struct pcxhr_rmh *rmh, int capture, + unsigned int param1, unsigned int param2, + unsigned int param3) +{ + snd_BUG_ON(param1 > MASK_FIRST_FIELD); + if (capture) + rmh->cmd[0] |= 0x800; /* COMMAND_RECORD_MASK */ + if (param1) + rmh->cmd[0] |= (param1 << FIELD_SIZE); + if (param2) { + snd_BUG_ON(param2 > MASK_FIRST_FIELD); + rmh->cmd[0] |= param2; + } + if(param3) { + snd_BUG_ON(param3 > MASK_DSP_WORD); + rmh->cmd[1] = param3; + rmh->cmd_len = 2; + } +} + +/* + * pcxhr_send_msg - send a DSP message with spinlock + * @rmh: the rmh record to send and receive + * + * returns 0 if successful, or a negative error code. + */ +int pcxhr_send_msg(struct pcxhr_mgr *mgr, struct pcxhr_rmh *rmh) +{ + 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<>=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 char* err_src_name[] = { + [PCXHR_ERR_PIPE] = "Pipe", + [PCXHR_ERR_STREAM] = "Stream", + [PCXHR_ERR_AUDIO] = "Audio" + }; + + if (err & 0xfff) + err &= 0xfff; + else + err = ((err >> 12) & 0xfff); + if (!err) + return 0; + dev_dbg(&mgr->pci->dev, "CMD_ASYNC : Error %s %s Pipe %d err=%x\n", + err_src_name[err_src], + is_capture ? "Record" : "Play", pipe, err); + if (err == 0xe01) + mgr->async_err_stream_xrun++; + else if (err == 0xe10) + mgr->async_err_pipe_xrun++; + else + mgr->async_err_other_last = (int)err; + return 1; +} + + +static void pcxhr_msg_thread(struct pcxhr_mgr *mgr) +{ + struct pcxhr_rmh *prmh = mgr->prmh; + int err; + int i, j; + + if (mgr->src_it_dsp & PCXHR_IRQ_FREQ_CHANGE) + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_FREQ_CHANGE event occurred\n"); + if (mgr->src_it_dsp & PCXHR_IRQ_TIME_CODE) + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_TIME_CODE event occurred\n"); + if (mgr->src_it_dsp & PCXHR_IRQ_NOTIFY) + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_NOTIFY event occurred\n"); + if (mgr->src_it_dsp & (PCXHR_IRQ_FREQ_CHANGE | PCXHR_IRQ_TIME_CODE)) { + /* clear events FREQ_CHANGE and TIME_CODE */ + pcxhr_init_rmh(prmh, CMD_TEST_IT); + err = pcxhr_send_msg(mgr, prmh); + dev_dbg(&mgr->pci->dev, "CMD_TEST_IT : err=%x, stat=%x\n", + err, prmh->stat[0]); + } + if (mgr->src_it_dsp & PCXHR_IRQ_ASYNC) { + dev_dbg(&mgr->pci->dev, + "PCXHR_IRQ_ASYNC event occurred\n"); + + pcxhr_init_rmh(prmh, CMD_ASYNC); + prmh->cmd[0] |= 1; /* add SEL_ASYNC_EVENTS */ + /* this is the only one extra long response command */ + prmh->stat_len = PCXHR_SIZE_MAX_LONG_STATUS; + err = pcxhr_send_msg(mgr, prmh); + if (err) + dev_err(&mgr->pci->dev, "ERROR pcxhr_msg_thread=%x;\n", + err); + i = 1; + while (i < prmh->stat_len) { + int nb_audio = ((prmh->stat[i] >> FIELD_SIZE) & + MASK_FIRST_FIELD); + int nb_stream = ((prmh->stat[i] >> (2*FIELD_SIZE)) & + MASK_FIRST_FIELD); + int pipe = prmh->stat[i] & MASK_FIRST_FIELD; + int is_capture = prmh->stat[i] & 0x400000; + u32 err2; + + if (prmh->stat[i] & 0x800000) { /* if BIT_END */ + dev_dbg(&mgr->pci->dev, + "TASKLET : End%sPipe %d\n", + is_capture ? "Record" : "Play", + pipe); + } + i++; + err2 = prmh->stat[i] ? prmh->stat[i] : prmh->stat[i+1]; + if (err2) + pcxhr_handle_async_err(mgr, err2, + PCXHR_ERR_PIPE, + pipe, is_capture); + i += 2; + for (j = 0; j < nb_stream; j++) { + err2 = prmh->stat[i] ? + prmh->stat[i] : prmh->stat[i+1]; + if (err2) + pcxhr_handle_async_err(mgr, err2, + PCXHR_ERR_STREAM, + pipe, + is_capture); + i += 2; + } + for (j = 0; j < nb_audio; j++) { + err2 = prmh->stat[i] ? + prmh->stat[i] : prmh->stat[i+1]; + if (err2) + pcxhr_handle_async_err(mgr, err2, + PCXHR_ERR_AUDIO, + pipe, + is_capture); + i += 2; + } + } + } +} + +static u_int64_t pcxhr_stream_read_position(struct pcxhr_mgr *mgr, + struct pcxhr_stream *stream) +{ + u_int64_t hw_sample_count; + struct pcxhr_rmh rmh; + int err, stream_mask; + + stream_mask = stream->pipe->is_capture ? 1 : 1<substream->number; + + /* get sample count for one stream */ + pcxhr_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT); + pcxhr_set_pipe_cmd_params(&rmh, stream->pipe->is_capture, + stream->pipe->first_audio, 0, stream_mask); + /* rmh.stat_len = 2; */ /* 2 resp data for each stream of the pipe */ + + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return 0; + + hw_sample_count = ((u_int64_t)rmh.stat[0]) << 24; + hw_sample_count += (u_int64_t)rmh.stat[1]; + + dev_dbg(&mgr->pci->dev, + "stream %c%d : abs samples real(%llu) timer(%llu)\n", + stream->pipe->is_capture ? 'C' : 'P', + stream->substream->number, + hw_sample_count, + stream->timer_abs_periods + stream->timer_period_frag + + mgr->granularity); + return hw_sample_count; +} + +static void pcxhr_update_timer_pos(struct pcxhr_mgr *mgr, + struct pcxhr_stream *stream, + int samples_to_add) +{ + if (stream->substream && + (stream->status == PCXHR_STREAM_STATUS_RUNNING)) { + u_int64_t new_sample_count; + int elapsed = 0; + int hardware_read = 0; + struct snd_pcm_runtime *runtime = stream->substream->runtime; + + if (samples_to_add < 0) { + stream->timer_is_synced = 0; + /* add default if no hardware_read possible */ + samples_to_add = mgr->granularity; + } + + if (!stream->timer_is_synced) { + if ((stream->timer_abs_periods != 0) || + ((stream->timer_period_frag + samples_to_add) >= + runtime->period_size)) { + new_sample_count = + pcxhr_stream_read_position(mgr, stream); + hardware_read = 1; + if (new_sample_count >= mgr->granularity) { + /* sub security offset because of + * jitter and finer granularity of + * dsp time (MBOX4) + */ + new_sample_count -= mgr->granularity; + stream->timer_is_synced = 1; + } + } + } + if (!hardware_read) { + /* if we didn't try to sync the position, increment it + * by PCXHR_GRANULARITY every timer interrupt + */ + new_sample_count = stream->timer_abs_periods + + stream->timer_period_frag + samples_to_add; + } + while (1) { + u_int64_t new_elapse_pos = stream->timer_abs_periods + + runtime->period_size; + if (new_elapse_pos > new_sample_count) + break; + elapsed = 1; + stream->timer_buf_periods++; + if (stream->timer_buf_periods >= runtime->periods) + stream->timer_buf_periods = 0; + stream->timer_abs_periods = new_elapse_pos; + } + if (new_sample_count >= stream->timer_abs_periods) { + stream->timer_period_frag = + (u_int32_t)(new_sample_count - + stream->timer_abs_periods); + } else { + dev_err(&mgr->pci->dev, + "ERROR new_sample_count too small ??? %ld\n", + (long unsigned int)new_sample_count); + } + + if (elapsed) { + mutex_unlock(&mgr->lock); + snd_pcm_period_elapsed(stream->substream); + mutex_lock(&mgr->lock); + } + } +} + +irqreturn_t pcxhr_interrupt(int irq, void *dev_id) +{ + struct pcxhr_mgr *mgr = dev_id; + unsigned int reg; + bool wake_thread = false; + + reg = PCXHR_INPL(mgr, PCXHR_PLX_IRQCS); + if (! (reg & PCXHR_IRQCS_ACTIVE_PCIDB)) { + /* this device did not cause the interrupt */ + return IRQ_NONE; + } + + /* clear interrupt */ + reg = PCXHR_INPL(mgr, PCXHR_PLX_L2PCIDB); + PCXHR_OUTPL(mgr, PCXHR_PLX_L2PCIDB, reg); + + /* timer irq occurred */ + if (reg & PCXHR_IRQ_TIMER) { + int timer_toggle = reg & PCXHR_IRQ_TIMER; + if (timer_toggle == mgr->timer_toggle) { + dev_dbg(&mgr->pci->dev, "ERROR TIMER TOGGLE\n"); + mgr->dsp_time_err++; + } + + mgr->timer_toggle = timer_toggle; + mgr->src_it_dsp = reg; + wake_thread = true; + } + + /* other irq's handled in the thread */ + if (reg & PCXHR_IRQ_MASK) { + if (reg & PCXHR_IRQ_ASYNC) { + /* as we didn't request any async notifications, + * some kind of xrun error will probably occurred + */ + /* better resynchronize all streams next interrupt : */ + mgr->dsp_time_last = PCXHR_DSP_TIME_INVALID; + } + mgr->src_it_dsp = reg; + wake_thread = true; + } +#ifdef CONFIG_SND_DEBUG_VERBOSE + if (reg & PCXHR_FATAL_DSP_ERR) + dev_dbg(&mgr->pci->dev, "FATAL DSP ERROR : %x\n", reg); +#endif + + return wake_thread ? IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +irqreturn_t pcxhr_threaded_irq(int irq, void *dev_id) +{ + struct pcxhr_mgr *mgr = dev_id; + int i, j; + struct snd_pcxhr *chip; + + 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..dc267e4c1 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_core.h @@ -0,0 +1,205 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt and message handling + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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..a99808ab0 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_hwdep.c @@ -0,0 +1,422 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * hwdep device manager + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_mixer.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" +#include "pcxhr_mix22.h" + + +static int pcxhr_sub_init(struct pcxhr_mgr *mgr); +/* + * get basic information and init pcxhr card + */ +static int pcxhr_init_board(struct pcxhr_mgr *mgr) +{ + int err; + struct pcxhr_rmh rmh; + int card_streams; + + /* calc the number of all streams used */ + if (mgr->mono_capture) + card_streams = mgr->capture_chips * 2; + else + card_streams = mgr->capture_chips; + card_streams += mgr->playback_chips * PCXHR_PLAYBACK_STREAMS; + + /* enable interrupts */ + pcxhr_enable_dsp(mgr); + + pcxhr_init_rmh(&rmh, CMD_SUPPORTED); + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + /* test 4, 8 or 12 phys out */ + if ((rmh.stat[0] & MASK_FIRST_FIELD) < mgr->playback_chips * 2) + return -EINVAL; + /* test 4, 8 or 2 phys in */ + if (((rmh.stat[0] >> (2 * FIELD_SIZE)) & MASK_FIRST_FIELD) < + mgr->capture_chips * 2) + return -EINVAL; + /* test max nb substream per board */ + if ((rmh.stat[1] & 0x5F) < card_streams) + return -EINVAL; + /* test max nb substream per pipe */ + if (((rmh.stat[1] >> 7) & 0x5F) < PCXHR_PLAYBACK_STREAMS) + return -EINVAL; + dev_dbg(&mgr->pci->dev, + "supported formats : playback=%x capture=%x\n", + rmh.stat[2], rmh.stat[3]); + + pcxhr_init_rmh(&rmh, CMD_VERSION); + /* firmware num for DSP */ + rmh.cmd[0] |= mgr->firmware_num; + /* transfer granularity in samples (should be multiple of 48) */ + rmh.cmd[1] = (1<<23) + mgr->granularity; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + dev_dbg(&mgr->pci->dev, + "PCXHR DSP version is %d.%d.%d\n", (rmh.stat[0]>>16)&0xff, + (rmh.stat[0]>>8)&0xff, rmh.stat[0]&0xff); + mgr->dsp_version = rmh.stat[0]; + + if (mgr->is_hr_stereo) + err = hr222_sub_init(mgr); + else + err = pcxhr_sub_init(mgr); + return err; +} + +static int pcxhr_sub_init(struct pcxhr_mgr *mgr) +{ + int err; + struct pcxhr_rmh rmh; + + /* get options */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd[0] |= IO_NUM_REG_STATUS; + rmh.cmd[1] = REG_STATUS_OPTIONS; + rmh.cmd_len = 2; + err = pcxhr_send_msg(mgr, &rmh); + if (err) + return err; + + if ((rmh.stat[1] & REG_STATUS_OPT_DAUGHTER_MASK) == + REG_STATUS_OPT_ANALOG_BOARD) + mgr->board_has_analog = 1; /* analog addon board found */ + + /* unmute inputs */ + err = pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS, + REG_CONT_UNMUTE_INPUTS, NULL); + if (err) + return err; + /* unmute outputs (a write to IO_NUM_REG_MUTE_OUT mutes!) */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_READ); + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + if (DSP_EXT_CMD_SET(mgr)) { + rmh.cmd[1] = 1; /* unmute digital plugs */ + rmh.cmd_len = 2; + } + err = pcxhr_send_msg(mgr, &rmh); + return err; +} + +void pcxhr_reset_board(struct pcxhr_mgr *mgr) +{ + struct pcxhr_rmh rmh; + + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_MAIN_INDEX)) { + /* mute outputs */ + if (!mgr->is_hr_stereo) { + /* a read to IO_NUM_REG_MUTE_OUT register unmutes! */ + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + rmh.cmd[0] |= IO_NUM_REG_MUTE_OUT; + pcxhr_send_msg(mgr, &rmh); + /* mute inputs */ + pcxhr_write_io_num_reg_cont(mgr, REG_CONT_UNMUTE_INPUTS, + 0, NULL); + } + /* stereo cards mute with reset of dsp */ + } + /* reset pcxhr dsp */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_DSP_EPRM_INDEX)) + pcxhr_reset_dsp(mgr); + /* reset second xilinx */ + if (mgr->dsp_loaded & (1 << PCXHR_FIRMWARE_XLX_COM_INDEX)) { + pcxhr_reset_xilinx_com(mgr); + mgr->dsp_loaded = 1; + } + return; +} + + +/* + * allocate a playback/capture pipe (pcmp0/pcmc0) + */ +static int pcxhr_dsp_allocate_pipe(struct pcxhr_mgr *mgr, + struct pcxhr_pipe *pipe, + int is_capture, int pin) +{ + int stream_count, audio_count; + int err; + struct pcxhr_rmh rmh; + + if (is_capture) { + stream_count = 1; + if (mgr->mono_capture) + audio_count = 1; + else + audio_count = 2; + } else { + stream_count = PCXHR_PLAYBACK_STREAMS; + audio_count = 2; /* always stereo */ + } + dev_dbg(&mgr->pci->dev, "snd_add_ref_pipe pin(%d) pcm%c0\n", + pin, is_capture ? 'c' : 'p'); + pipe->is_capture = is_capture; + pipe->first_audio = pin; + /* define pipe (P_PCM_ONLY_MASK (0x020000) is not necessary) */ + pcxhr_init_rmh(&rmh, CMD_RES_PIPE); + pcxhr_set_pipe_cmd_params(&rmh, is_capture, pin, + audio_count, stream_count); + rmh.cmd[1] |= 0x020000; /* add P_PCM_ONLY_MASK */ + if (DSP_EXT_CMD_SET(mgr)) { + /* add channel mask to command */ + rmh.cmd[rmh.cmd_len++] = (audio_count == 1) ? 0x01 : 0x03; + } + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) { + dev_err(&mgr->pci->dev, "error pipe allocation " + "(CMD_RES_PIPE) err=%x!\n", err); + return err; + } + pipe->status = PCXHR_PIPE_DEFINED; + + return 0; +} + +/* + * free playback/capture pipe (pcmp0/pcmc0) + */ +#if 0 +static int pcxhr_dsp_free_pipe( struct pcxhr_mgr *mgr, struct pcxhr_pipe *pipe) +{ + struct pcxhr_rmh rmh; + int capture_mask = 0; + int playback_mask = 0; + int err = 0; + + if (pipe->is_capture) + capture_mask = (1 << pipe->first_audio); + else + playback_mask = (1 << pipe->first_audio); + + /* stop one pipe */ + err = pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 0); + if (err < 0) + dev_err(&mgr->pci->dev, "error stopping pipe!\n"); + /* release the pipe */ + pcxhr_init_rmh(&rmh, CMD_FREE_PIPE); + pcxhr_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->first_audio, + 0, 0); + err = pcxhr_send_msg(mgr, &rmh); + if (err < 0) + dev_err(&mgr->pci->dev, "error pipe release " + "(CMD_FREE_PIPE) err(%x)\n", err); + pipe->status = PCXHR_PIPE_UNDEFINED; + return err; +} +#endif + + +static int pcxhr_config_pipes(struct pcxhr_mgr *mgr) +{ + int err, i, j; + struct snd_pcxhr *chip; + struct pcxhr_pipe *pipe; + + /* allocate the pipes on the dsp */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + if (chip->nb_streams_play) { + pipe = &chip->playback_pipe; + err = pcxhr_dsp_allocate_pipe( mgr, pipe, 0, i*2); + if (err) + return err; + for(j = 0; j < chip->nb_streams_play; j++) + chip->playback_stream[j].pipe = pipe; + } + for (j = 0; j < chip->nb_streams_capt; j++) { + pipe = &chip->capture_pipe[j]; + err = pcxhr_dsp_allocate_pipe(mgr, pipe, 1, i*2 + j); + if (err) + return err; + chip->capture_stream[j].pipe = pipe; + } + } + return 0; +} + +static int pcxhr_start_pipes(struct pcxhr_mgr *mgr) +{ + int i, j; + struct snd_pcxhr *chip; + int playback_mask = 0; + int capture_mask = 0; + + /* start all the pipes on the dsp */ + for (i = 0; i < mgr->num_cards; i++) { + chip = mgr->chip[i]; + if (chip->nb_streams_play) + playback_mask |= 1 << chip->playback_pipe.first_audio; + for (j = 0; j < chip->nb_streams_capt; j++) + capture_mask |= 1 << chip->capture_pipe[j].first_audio; + } + return pcxhr_set_pipe_state(mgr, playback_mask, capture_mask, 1); +} + + +static int pcxhr_dsp_load(struct pcxhr_mgr *mgr, int index, + const struct firmware *dsp) +{ + int err, card_index; + + dev_dbg(&mgr->pci->dev, + "loading dsp [%d] size = %zd\n", index, dsp->size); + + switch (index) { + case PCXHR_FIRMWARE_XLX_INT_INDEX: + pcxhr_reset_xilinx_com(mgr); + return pcxhr_load_xilinx_binary(mgr, dsp, 0); + + case PCXHR_FIRMWARE_XLX_COM_INDEX: + pcxhr_reset_xilinx_com(mgr); + return pcxhr_load_xilinx_binary(mgr, dsp, 1); + + case PCXHR_FIRMWARE_DSP_EPRM_INDEX: + pcxhr_reset_dsp(mgr); + return pcxhr_load_eeprom_binary(mgr, dsp); + + case PCXHR_FIRMWARE_DSP_BOOT_INDEX: + return pcxhr_load_boot_binary(mgr, dsp); + + case PCXHR_FIRMWARE_DSP_MAIN_INDEX: + err = pcxhr_load_dsp_binary(mgr, dsp); + if (err) + return err; + break; /* continue with first init */ + default: + dev_err(&mgr->pci->dev, "wrong file index\n"); + return -EFAULT; + } /* end of switch file index*/ + + /* first communication with embedded */ + err = pcxhr_init_board(mgr); + if (err < 0) { + dev_err(&mgr->pci->dev, "pcxhr could not be set up\n"); + return err; + } + err = pcxhr_config_pipes(mgr); + if (err < 0) { + dev_err(&mgr->pci->dev, "pcxhr pipes could not be set up\n"); + return err; + } + /* create devices and mixer in accordance with HW options*/ + for (card_index = 0; card_index < mgr->num_cards; card_index++) { + struct snd_pcxhr *chip = mgr->chip[card_index]; + + 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 char *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..f561909dc --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_hwdep.h @@ -0,0 +1,40 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * definitions and makros for basic card access + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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..8b4d0282e --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mix22.c @@ -0,0 +1,868 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * mixer interface for stereo cards + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_core.h" +#include "pcxhr_mix22.h" + + +/* registers used on the DSP and Xilinx (port 2) : HR stereo cards only */ +#define PCXHR_DSP_RESET 0x20 +#define PCXHR_XLX_CFG 0x24 +#define PCXHR_XLX_RUER 0x28 +#define PCXHR_XLX_DATA 0x2C +#define PCXHR_XLX_STATUS 0x30 +#define PCXHR_XLX_LOFREQ 0x34 +#define PCXHR_XLX_HIFREQ 0x38 +#define PCXHR_XLX_CSUER 0x3C +#define PCXHR_XLX_SELMIC 0x40 + +#define PCXHR_DSP 2 + +/* byte access only ! */ +#define PCXHR_INPB(mgr, x) inb((mgr)->port[PCXHR_DSP] + (x)) +#define PCXHR_OUTPB(mgr, x, data) outb((data), (mgr)->port[PCXHR_DSP] + (x)) + + +/* values for PCHR_DSP_RESET register */ +#define PCXHR_DSP_RESET_DSP 0x01 +#define PCXHR_DSP_RESET_MUTE 0x02 +#define PCXHR_DSP_RESET_CODEC 0x08 +#define PCXHR_DSP_RESET_SMPTE 0x10 +#define PCXHR_DSP_RESET_GPO_OFFSET 5 +#define PCXHR_DSP_RESET_GPO_MASK 0x60 + +/* values for PCHR_XLX_CFG register */ +#define PCXHR_CFG_SYNCDSP_MASK 0x80 +#define PCXHR_CFG_DEPENDENCY_MASK 0x60 +#define PCXHR_CFG_INDEPENDANT_SEL 0x00 +#define PCXHR_CFG_MASTER_SEL 0x40 +#define PCXHR_CFG_SLAVE_SEL 0x20 +#define PCXHR_CFG_DATA_UER1_SEL_MASK 0x10 /* 0 (UER0), 1(UER1) */ +#define PCXHR_CFG_DATAIN_SEL_MASK 0x08 /* 0 (ana), 1 (UER) */ +#define PCXHR_CFG_SRC_MASK 0x04 /* 0 (Bypass), 1 (SRC Actif) */ +#define PCXHR_CFG_CLOCK_UER1_SEL_MASK 0x02 /* 0 (UER0), 1(UER1) */ +#define PCXHR_CFG_CLOCKIN_SEL_MASK 0x01 /* 0 (internal), 1 (AES/EBU) */ + +/* values for PCHR_XLX_DATA register */ +#define PCXHR_DATA_CODEC 0x80 +#define AKM_POWER_CONTROL_CMD 0xA007 +#define AKM_RESET_ON_CMD 0xA100 +#define AKM_RESET_OFF_CMD 0xA103 +#define AKM_CLOCK_INF_55K_CMD 0xA240 +#define AKM_CLOCK_SUP_55K_CMD 0xA24D +#define AKM_MUTE_CMD 0xA38D +#define AKM_UNMUTE_CMD 0xA30D +#define AKM_LEFT_LEVEL_CMD 0xA600 +#define AKM_RIGHT_LEVEL_CMD 0xA700 + +/* values for PCHR_XLX_STATUS register - READ */ +#define PCXHR_STAT_SRC_LOCK 0x01 +#define PCXHR_STAT_LEVEL_IN 0x02 +#define PCXHR_STAT_GPI_OFFSET 2 +#define PCXHR_STAT_GPI_MASK 0x0C +#define PCXHR_STAT_MIC_CAPS 0x10 +/* values for PCHR_XLX_STATUS register - WRITE */ +#define PCXHR_STAT_FREQ_SYNC_MASK 0x01 +#define PCXHR_STAT_FREQ_UER1_MASK 0x02 +#define PCXHR_STAT_FREQ_SAVE_MASK 0x80 + +/* values for PCHR_XLX_CSUER register */ +#define PCXHR_SUER1_BIT_U_READ_MASK 0x80 +#define PCXHR_SUER1_BIT_C_READ_MASK 0x40 +#define PCXHR_SUER1_DATA_PRESENT_MASK 0x20 +#define PCXHR_SUER1_CLOCK_PRESENT_MASK 0x10 +#define PCXHR_SUER_BIT_U_READ_MASK 0x08 +#define PCXHR_SUER_BIT_C_READ_MASK 0x04 +#define PCXHR_SUER_DATA_PRESENT_MASK 0x02 +#define PCXHR_SUER_CLOCK_PRESENT_MASK 0x01 + +#define PCXHR_SUER_BIT_U_WRITE_MASK 0x02 +#define PCXHR_SUER_BIT_C_WRITE_MASK 0x01 + +/* values for PCXHR_XLX_SELMIC register - WRITE */ +#define PCXHR_SELMIC_PREAMPLI_OFFSET 2 +#define PCXHR_SELMIC_PREAMPLI_MASK 0x0C +#define PCXHR_SELMIC_PHANTOM_ALIM 0x80 + + +static const unsigned char g_hr222_p_level[] = { + 0x00, /* [000] -49.5 dB: AKM[000] = -1.#INF dB (mute) */ + 0x01, /* [001] -49.0 dB: AKM[001] = -48.131 dB (diff=0.86920 dB) */ + 0x01, /* [002] -48.5 dB: AKM[001] = -48.131 dB (diff=0.36920 dB) */ + 0x01, /* [003] -48.0 dB: AKM[001] = -48.131 dB (diff=0.13080 dB) */ + 0x01, /* [004] -47.5 dB: AKM[001] = -48.131 dB (diff=0.63080 dB) */ + 0x01, /* [005] -46.5 dB: AKM[001] = -48.131 dB (diff=1.63080 dB) */ + 0x01, /* [006] -47.0 dB: AKM[001] = -48.131 dB (diff=1.13080 dB) */ + 0x01, /* [007] -46.0 dB: AKM[001] = -48.131 dB (diff=2.13080 dB) */ + 0x01, /* [008] -45.5 dB: AKM[001] = -48.131 dB (diff=2.63080 dB) */ + 0x02, /* [009] -45.0 dB: AKM[002] = -42.110 dB (diff=2.88980 dB) */ + 0x02, /* [010] -44.5 dB: AKM[002] = -42.110 dB (diff=2.38980 dB) */ + 0x02, /* [011] -44.0 dB: AKM[002] = -42.110 dB (diff=1.88980 dB) */ + 0x02, /* [012] -43.5 dB: AKM[002] = -42.110 dB (diff=1.38980 dB) */ + 0x02, /* [013] -43.0 dB: AKM[002] = -42.110 dB (diff=0.88980 dB) */ + 0x02, /* [014] -42.5 dB: AKM[002] = -42.110 dB (diff=0.38980 dB) */ + 0x02, /* [015] -42.0 dB: AKM[002] = -42.110 dB (diff=0.11020 dB) */ + 0x02, /* [016] -41.5 dB: AKM[002] = -42.110 dB (diff=0.61020 dB) */ + 0x02, /* [017] -41.0 dB: AKM[002] = -42.110 dB (diff=1.11020 dB) */ + 0x02, /* [018] -40.5 dB: AKM[002] = -42.110 dB (diff=1.61020 dB) */ + 0x03, /* [019] -40.0 dB: AKM[003] = -38.588 dB (diff=1.41162 dB) */ + 0x03, /* [020] -39.5 dB: AKM[003] = -38.588 dB (diff=0.91162 dB) */ + 0x03, /* [021] -39.0 dB: AKM[003] = -38.588 dB (diff=0.41162 dB) */ + 0x03, /* [022] -38.5 dB: AKM[003] = -38.588 dB (diff=0.08838 dB) */ + 0x03, /* [023] -38.0 dB: AKM[003] = -38.588 dB (diff=0.58838 dB) */ + 0x03, /* [024] -37.5 dB: AKM[003] = -38.588 dB (diff=1.08838 dB) */ + 0x04, /* [025] -37.0 dB: AKM[004] = -36.090 dB (diff=0.91040 dB) */ + 0x04, /* [026] -36.5 dB: AKM[004] = -36.090 dB (diff=0.41040 dB) */ + 0x04, /* [027] -36.0 dB: AKM[004] = -36.090 dB (diff=0.08960 dB) */ + 0x04, /* [028] -35.5 dB: AKM[004] = -36.090 dB (diff=0.58960 dB) */ + 0x05, /* [029] -35.0 dB: AKM[005] = -34.151 dB (diff=0.84860 dB) */ + 0x05, /* [030] -34.5 dB: AKM[005] = -34.151 dB (diff=0.34860 dB) */ + 0x05, /* [031] -34.0 dB: AKM[005] = -34.151 dB (diff=0.15140 dB) */ + 0x05, /* [032] -33.5 dB: AKM[005] = -34.151 dB (diff=0.65140 dB) */ + 0x06, /* [033] -33.0 dB: AKM[006] = -32.568 dB (diff=0.43222 dB) */ + 0x06, /* [034] -32.5 dB: AKM[006] = -32.568 dB (diff=0.06778 dB) */ + 0x06, /* [035] -32.0 dB: AKM[006] = -32.568 dB (diff=0.56778 dB) */ + 0x07, /* [036] -31.5 dB: AKM[007] = -31.229 dB (diff=0.27116 dB) */ + 0x07, /* [037] -31.0 dB: AKM[007] = -31.229 dB (diff=0.22884 dB) */ + 0x08, /* [038] -30.5 dB: AKM[008] = -30.069 dB (diff=0.43100 dB) */ + 0x08, /* [039] -30.0 dB: AKM[008] = -30.069 dB (diff=0.06900 dB) */ + 0x09, /* [040] -29.5 dB: AKM[009] = -29.046 dB (diff=0.45405 dB) */ + 0x09, /* [041] -29.0 dB: AKM[009] = -29.046 dB (diff=0.04595 dB) */ + 0x0a, /* [042] -28.5 dB: AKM[010] = -28.131 dB (diff=0.36920 dB) */ + 0x0a, /* [043] -28.0 dB: AKM[010] = -28.131 dB (diff=0.13080 dB) */ + 0x0b, /* [044] -27.5 dB: AKM[011] = -27.303 dB (diff=0.19705 dB) */ + 0x0b, /* [045] -27.0 dB: AKM[011] = -27.303 dB (diff=0.30295 dB) */ + 0x0c, /* [046] -26.5 dB: AKM[012] = -26.547 dB (diff=0.04718 dB) */ + 0x0d, /* [047] -26.0 dB: AKM[013] = -25.852 dB (diff=0.14806 dB) */ + 0x0e, /* [048] -25.5 dB: AKM[014] = -25.208 dB (diff=0.29176 dB) */ + 0x0e, /* [049] -25.0 dB: AKM[014] = -25.208 dB (diff=0.20824 dB) */ + 0x0f, /* [050] -24.5 dB: AKM[015] = -24.609 dB (diff=0.10898 dB) */ + 0x10, /* [051] -24.0 dB: AKM[016] = -24.048 dB (diff=0.04840 dB) */ + 0x11, /* [052] -23.5 dB: AKM[017] = -23.522 dB (diff=0.02183 dB) */ + 0x12, /* [053] -23.0 dB: AKM[018] = -23.025 dB (diff=0.02535 dB) */ + 0x13, /* [054] -22.5 dB: AKM[019] = -22.556 dB (diff=0.05573 dB) */ + 0x14, /* [055] -22.0 dB: AKM[020] = -22.110 dB (diff=0.11020 dB) */ + 0x15, /* [056] -21.5 dB: AKM[021] = -21.686 dB (diff=0.18642 dB) */ + 0x17, /* [057] -21.0 dB: AKM[023] = -20.896 dB (diff=0.10375 dB) */ + 0x18, /* [058] -20.5 dB: AKM[024] = -20.527 dB (diff=0.02658 dB) */ + 0x1a, /* [059] -20.0 dB: AKM[026] = -19.831 dB (diff=0.16866 dB) */ + 0x1b, /* [060] -19.5 dB: AKM[027] = -19.504 dB (diff=0.00353 dB) */ + 0x1d, /* [061] -19.0 dB: AKM[029] = -18.883 dB (diff=0.11716 dB) */ + 0x1e, /* [062] -18.5 dB: AKM[030] = -18.588 dB (diff=0.08838 dB) */ + 0x20, /* [063] -18.0 dB: AKM[032] = -18.028 dB (diff=0.02780 dB) */ + 0x22, /* [064] -17.5 dB: AKM[034] = -17.501 dB (diff=0.00123 dB) */ + 0x24, /* [065] -17.0 dB: AKM[036] = -17.005 dB (diff=0.00475 dB) */ + 0x26, /* [066] -16.5 dB: AKM[038] = -16.535 dB (diff=0.03513 dB) */ + 0x28, /* [067] -16.0 dB: AKM[040] = -16.090 dB (diff=0.08960 dB) */ + 0x2b, /* [068] -15.5 dB: AKM[043] = -15.461 dB (diff=0.03857 dB) */ + 0x2d, /* [069] -15.0 dB: AKM[045] = -15.067 dB (diff=0.06655 dB) */ + 0x30, /* [070] -14.5 dB: AKM[048] = -14.506 dB (diff=0.00598 dB) */ + 0x33, /* [071] -14.0 dB: AKM[051] = -13.979 dB (diff=0.02060 dB) */ + 0x36, /* [072] -13.5 dB: AKM[054] = -13.483 dB (diff=0.01707 dB) */ + 0x39, /* [073] -13.0 dB: AKM[057] = -13.013 dB (diff=0.01331 dB) */ + 0x3c, /* [074] -12.5 dB: AKM[060] = -12.568 dB (diff=0.06778 dB) */ + 0x40, /* [075] -12.0 dB: AKM[064] = -12.007 dB (diff=0.00720 dB) */ + 0x44, /* [076] -11.5 dB: AKM[068] = -11.481 dB (diff=0.01937 dB) */ + 0x48, /* [077] -11.0 dB: AKM[072] = -10.984 dB (diff=0.01585 dB) */ + 0x4c, /* [078] -10.5 dB: AKM[076] = -10.515 dB (diff=0.01453 dB) */ + 0x51, /* [079] -10.0 dB: AKM[081] = -9.961 dB (diff=0.03890 dB) */ + 0x55, /* [080] -9.5 dB: AKM[085] = -9.542 dB (diff=0.04243 dB) */ + 0x5a, /* [081] -9.0 dB: AKM[090] = -9.046 dB (diff=0.04595 dB) */ + 0x60, /* [082] -8.5 dB: AKM[096] = -8.485 dB (diff=0.01462 dB) */ + 0x66, /* [083] -8.0 dB: AKM[102] = -7.959 dB (diff=0.04120 dB) */ + 0x6c, /* [084] -7.5 dB: AKM[108] = -7.462 dB (diff=0.03767 dB) */ + 0x72, /* [085] -7.0 dB: AKM[114] = -6.993 dB (diff=0.00729 dB) */ + 0x79, /* [086] -6.5 dB: AKM[121] = -6.475 dB (diff=0.02490 dB) */ + 0x80, /* [087] -6.0 dB: AKM[128] = -5.987 dB (diff=0.01340 dB) */ + 0x87, /* [088] -5.5 dB: AKM[135] = -5.524 dB (diff=0.02413 dB) */ + 0x8f, /* [089] -5.0 dB: AKM[143] = -5.024 dB (diff=0.02408 dB) */ + 0x98, /* [090] -4.5 dB: AKM[152] = -4.494 dB (diff=0.00607 dB) */ + 0xa1, /* [091] -4.0 dB: AKM[161] = -3.994 dB (diff=0.00571 dB) */ + 0xaa, /* [092] -3.5 dB: AKM[170] = -3.522 dB (diff=0.02183 dB) */ + 0xb5, /* [093] -3.0 dB: AKM[181] = -2.977 dB (diff=0.02277 dB) */ + 0xbf, /* [094] -2.5 dB: AKM[191] = -2.510 dB (diff=0.01014 dB) */ + 0xcb, /* [095] -2.0 dB: AKM[203] = -1.981 dB (diff=0.01912 dB) */ + 0xd7, /* [096] -1.5 dB: AKM[215] = -1.482 dB (diff=0.01797 dB) */ + 0xe3, /* [097] -1.0 dB: AKM[227] = -1.010 dB (diff=0.01029 dB) */ + 0xf1, /* [098] -0.5 dB: AKM[241] = -0.490 dB (diff=0.00954 dB) */ + 0xff, /* [099] +0.0 dB: AKM[255] = +0.000 dB (diff=0.00000 dB) */ +}; + + +static void hr222_config_akm(struct pcxhr_mgr *mgr, unsigned short data) +{ + unsigned short mask = 0x8000; + /* activate access to codec registers */ + PCXHR_INPB(mgr, PCXHR_XLX_HIFREQ); + + while (mask) { + PCXHR_OUTPB(mgr, PCXHR_XLX_DATA, + data & mask ? PCXHR_DATA_CODEC : 0); + mask >>= 1; + } + /* termiate access to codec registers */ + PCXHR_INPB(mgr, PCXHR_XLX_RUER); +} + + +static int hr222_set_hw_playback_level(struct pcxhr_mgr *mgr, + int idx, int level) +{ + unsigned short cmd; + if (idx > 1 || + level < 0 || + level >= ARRAY_SIZE(g_hr222_p_level)) + return -EINVAL; + + if (idx == 0) + cmd = AKM_LEFT_LEVEL_CMD; + else + cmd = AKM_RIGHT_LEVEL_CMD; + + /* conversion from PmBoardCodedLevel to AKM nonlinear programming */ + cmd += g_hr222_p_level[level]; + + hr222_config_akm(mgr, cmd); + return 0; +} + + +static int hr222_set_hw_capture_level(struct pcxhr_mgr *mgr, + int level_l, int level_r, int level_mic) +{ + /* program all input levels at the same time */ + unsigned int data; + int i; + + if (!mgr->capture_chips) + return -EINVAL; /* no PCX22 */ + + data = ((level_mic & 0xff) << 24); /* micro is mono, but apply */ + data |= ((level_mic & 0xff) << 16); /* level on both channels */ + data |= ((level_r & 0xff) << 8); /* line input right channel */ + data |= (level_l & 0xff); /* line input left channel */ + + PCXHR_INPB(mgr, PCXHR_XLX_DATA); /* activate input codec */ + /* send 32 bits (4 x 8 bits) */ + for (i = 0; i < 32; i++, data <<= 1) { + PCXHR_OUTPB(mgr, PCXHR_XLX_DATA, + (data & 0x80000000) ? PCXHR_DATA_CODEC : 0); + } + PCXHR_INPB(mgr, PCXHR_XLX_RUER); /* close input level codec */ + return 0; +} + +static void hr222_micro_boost(struct pcxhr_mgr *mgr, int level); + +int hr222_sub_init(struct pcxhr_mgr *mgr) +{ + unsigned char reg; + + mgr->board_has_analog = 1; /* analog always available */ + mgr->xlx_cfg = PCXHR_CFG_SYNCDSP_MASK; + + reg = PCXHR_INPB(mgr, PCXHR_XLX_STATUS); + if (reg & PCXHR_STAT_MIC_CAPS) + mgr->board_has_mic = 1; /* microphone available */ + dev_dbg(&mgr->pci->dev, + "MIC input available = %d\n", mgr->board_has_mic); + + /* reset codec */ + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, + PCXHR_DSP_RESET_DSP); + msleep(5); + mgr->dsp_reset = PCXHR_DSP_RESET_DSP | + PCXHR_DSP_RESET_MUTE | + PCXHR_DSP_RESET_CODEC; + PCXHR_OUTPB(mgr, PCXHR_DSP_RESET, mgr->dsp_reset); + /* hr222_write_gpo(mgr, 0); does the same */ + msleep(5); + + /* config AKM */ + hr222_config_akm(mgr, AKM_POWER_CONTROL_CMD); + hr222_config_akm(mgr, AKM_CLOCK_INF_55K_CMD); + hr222_config_akm(mgr, AKM_UNMUTE_CMD); + hr222_config_akm(mgr, AKM_RESET_OFF_CMD); + + /* init micro boost */ + hr222_micro_boost(mgr, 0); + + return 0; +} + + +/* 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..5971b9933 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mix22.h @@ -0,0 +1,60 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * low level interface with interrupt ans message handling + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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..d9a1c6c50 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mixer.c @@ -0,0 +1,1259 @@ +#define __NO_VERSION__ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * mixer callbacks + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "pcxhr.h" +#include "pcxhr_hwdep.h" +#include "pcxhr_core.h" +#include +#include +#include +#include "pcxhr_mixer.h" +#include "pcxhr_mix22.h" + +#define PCXHR_LINE_CAPTURE_LEVEL_MIN 0 /* -112.0 dB */ +#define PCXHR_LINE_CAPTURE_LEVEL_MAX 255 /* +15.5 dB */ +#define PCXHR_LINE_CAPTURE_ZERO_LEVEL 224 /* 0.0 dB ( 0 dBu -> 0 dBFS ) */ + +#define PCXHR_LINE_PLAYBACK_LEVEL_MIN 0 /* -104.0 dB */ +#define PCXHR_LINE_PLAYBACK_LEVEL_MAX 128 /* +24.0 dB */ +#define PCXHR_LINE_PLAYBACK_ZERO_LEVEL 104 /* 0.0 dB ( 0 dBFS -> 0 dBu ) */ + +static const DECLARE_TLV_DB_SCALE(db_scale_analog_capture, -11200, 50, 1550); +static const DECLARE_TLV_DB_SCALE(db_scale_analog_playback, -10400, 100, 2400); + +static const DECLARE_TLV_DB_SCALE(db_scale_a_hr222_capture, -11150, 50, 1600); +static const DECLARE_TLV_DB_SCALE(db_scale_a_hr222_playback, -2550, 50, 2400); + +static int pcxhr_update_analog_audio_level(struct snd_pcxhr *chip, + int is_capture, int channel) +{ + int err, vol; + struct pcxhr_rmh rmh; + + pcxhr_init_rmh(&rmh, CMD_ACCESS_IO_WRITE); + if (is_capture) { + rmh.cmd[0] |= IO_NUM_REG_IN_ANA_LEVEL; + rmh.cmd[2] = chip->analog_capture_volume[channel]; + } else { + rmh.cmd[0] |= IO_NUM_REG_OUT_ANA_LEVEL; + if (chip->analog_playback_active[channel]) + vol = chip->analog_playback_volume[channel]; + else + vol = PCXHR_LINE_PLAYBACK_LEVEL_MIN; + /* playback analog levels are inversed */ + rmh.cmd[2] = PCXHR_LINE_PLAYBACK_LEVEL_MAX - vol; + } + rmh.cmd[1] = 1 << ((2 * chip->chip_idx) + channel); /* audio mask */ + rmh.cmd_len = 3; + err = pcxhr_send_msg(chip->mgr, &rmh); + if (err < 0) { + dev_dbg(chip->card->dev, + "error update_analog_audio_level card(%d)" + " is_capture(%d) err(%x)\n", + chip->chip_idx, is_capture, err); + return -EINVAL; + } + return 0; +} + +/* + * analog level control + */ +static int pcxhr_analog_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + if (kcontrol->private_value == 0) { /* playback */ + if (chip->mgr->is_hr_stereo) { + uinfo->value.integer.min = + HR222_LINE_PLAYBACK_LEVEL_MIN; /* -25 dB */ + uinfo->value.integer.max = + HR222_LINE_PLAYBACK_LEVEL_MAX; /* +24 dB */ + } else { + uinfo->value.integer.min = + PCXHR_LINE_PLAYBACK_LEVEL_MIN; /*-104 dB */ + uinfo->value.integer.max = + PCXHR_LINE_PLAYBACK_LEVEL_MAX; /* +24 dB */ + } + } else { /* capture */ + if (chip->mgr->is_hr_stereo) { + uinfo->value.integer.min = + HR222_LINE_CAPTURE_LEVEL_MIN; /*-112 dB */ + uinfo->value.integer.max = + HR222_LINE_CAPTURE_LEVEL_MAX; /* +15.5 dB */ + } else { + uinfo->value.integer.min = + PCXHR_LINE_CAPTURE_LEVEL_MIN; /*-112 dB */ + uinfo->value.integer.max = + PCXHR_LINE_CAPTURE_LEVEL_MAX; /* +15.5 dB */ + } + } + return 0; +} + +static int pcxhr_analog_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcxhr *chip = snd_kcontrol_chip(kcontrol); + 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<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<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..4348d0e55 --- /dev/null +++ b/sound/pci/pcxhr/pcxhr_mixer.h @@ -0,0 +1,29 @@ +/* + * Driver for Digigram pcxhr compatible soundcards + * + * include file for mixer + * + * Copyright (c) 2004 by Digigram + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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..dcd2e64e4 --- /dev/null +++ b/sound/pci/riptide/Makefile @@ -0,0 +1,3 @@ +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..23017e3bc --- /dev/null +++ b/sound/pci/riptide/riptide.c @@ -0,0 +1,2217 @@ +/* + * Driver for the Conexant Riptide Soundchip + * + * Copyright (c) 2004 Peter Gruber + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + History: + - 02/15/2004 first release + + This Driver is based on the OSS Driver version from Linuxant (riptide-0.6lnxtbeta03111100) + credits from the original files: + + MODULE NAME: cnxt_rt.h + AUTHOR: K. Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 02/1/2000 KNL + + MODULE NAME: int_mdl.c + AUTHOR: Konstantin Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/01/99 KNL + + MODULE NAME: riptide.h + AUTHOR: O. Druzhinin (Transcribed by OLD) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/16/97 OLD + + MODULE NAME: Rp_Cmdif.cpp + AUTHOR: O. Druzhinin (Transcribed by OLD) + K. Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Adopted from NT4 driver 6/22/99 OLD + Ported to Linux 9/01/99 KNL + + MODULE NAME: rt_hw.c + AUTHOR: O. Druzhinin (Transcribed by OLD) + C. Lazarev (Transcribed by CNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 11/18/97 OLD + Hardware functions for RipTide 11/24/97 CNL + (ES1) are coded + Hardware functions for RipTide 12/24/97 CNL + (A0) are coded + Hardware functions for RipTide 03/20/98 CNL + (A1) are coded + Boot loader is included 05/07/98 CNL + Redesigned for WDM 07/27/98 CNL + Redesigned for Linux 09/01/99 CNL + + MODULE NAME: rt_hw.h + AUTHOR: C. Lazarev (Transcribed by CNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 11/18/97 CNL + + MODULE NAME: rt_mdl.c + AUTHOR: Konstantin Lazarev (Transcribed by KNL) + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created 10/01/99 KNL + + MODULE NAME: mixer.h + AUTHOR: K. Kenney + HISTORY: Major Revision Date By + ----------------------------- -------- ----- + Created from MS W95 Sample 11/28/95 KRS + RipTide 10/15/97 KRS + Adopted for Windows NT driver 01/20/98 CNL +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK 1 +#endif + +MODULE_AUTHOR("Peter Gruber "); +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 { + unsigned char *noconv; + unsigned char *stereo; + 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 tasklet_struct riptide_tq; + 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; + 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 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 unsigned char lbus_play_opl3[] = { + DIGITAL_MIXER_IN0 + FM_MIXER, 0xff +}; +static unsigned char lbus_play_modem[] = { + DIGITAL_MIXER_IN0 + MODEM_MIXER, 0xff +}; +static unsigned char lbus_play_i2s[] = { + INTER0_IN + I2S_INTDEC, DIGITAL_MIXER_IN0 + I2S_MIXER, 0xff +}; +static unsigned char lbus_play_out[] = { + PDAC2ACLNK, 0xff +}; +static unsigned char lbus_play_outhp[] = { + HNDSPK2ACLNK, 0xff +}; +static unsigned char lbus_play_noconv1[] = { + DIGITAL_MIXER_IN0, 0xff +}; +static unsigned char lbus_play_stereo1[] = { + INTER0_IN, DIGITAL_MIXER_IN0, 0xff +}; +static unsigned char lbus_play_mono1[] = { + INTERM0_IN, DIGITAL_MIXER_IN0, 0xff +}; +static unsigned char lbus_play_noconv2[] = { + DIGITAL_MIXER_IN1, 0xff +}; +static unsigned char lbus_play_stereo2[] = { + INTER1_IN, DIGITAL_MIXER_IN1, 0xff +}; +static unsigned char lbus_play_mono2[] = { + INTERM1_IN, DIGITAL_MIXER_IN1, 0xff +}; +static unsigned char lbus_play_noconv3[] = { + DIGITAL_MIXER_IN2, 0xff +}; +static unsigned char lbus_play_stereo3[] = { + INTER2_IN, DIGITAL_MIXER_IN2, 0xff +}; +static unsigned char lbus_play_mono3[] = { + INTERM2_IN, DIGITAL_MIXER_IN2, 0xff +}; +static unsigned char lbus_rec_noconv1[] = { + LBUS2ARM_FIFO5, 0xff +}; +static unsigned char lbus_rec_stereo1[] = { + DECIM0_IN, LBUS2ARM_FIFO5, 0xff +}; +static unsigned char lbus_rec_mono1[] = { + DECIMM3_IN, LBUS2ARM_FIFO5, 0xff +}; + +static unsigned char play_ids[] = { 4, 1, 2, }; +static unsigned char play_sources[] = { + ARM2LBUS_FIFO4, ARM2LBUS_FIFO1, ARM2LBUS_FIFO2, +}; +static 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, + 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) { + unsigned char *npath = path; + + while (*npath != 0xff) + npath++; + alloclbuspath(cif, source + 1, ++npath, mixer, s); + } + } +} + +static void +freelbuspath(struct cmdif *cif, unsigned char source, 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) { + 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 void riptide_handleirq(unsigned long dev_id) +{ + struct snd_riptide *chip = (void *)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; + + 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]); + } + } + } +} + +#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_pcm_suspend_all(chip->pcm); + 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; + 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, + snd_dma_pci_data(chip->pci), + 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 snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +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 snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_riptide_hw_params, + .hw_free = snd_riptide_hw_free, + .prepare = snd_riptide_prepare, + .page = snd_pcm_sgbuf_ops_page, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_riptide_hw_params, + .hw_free = snd_riptide_hw_free, + .prepare = snd_riptide_prepare, + .page = snd_pcm_sgbuf_ops_page, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 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; + + if (cif) { + chip->received_irqs++; + if (IS_EOBIRQ(cif->hwport) || IS_EOSIRQ(cif->hwport) || + IS_EOCIRQ(cif->hwport)) { + chip->handled_irqs++; + tasklet_schedule(&chip->riptide_tq); + } + if (chip->rmidi && IS_MPUIRQ(cif->hwport)) { + chip->handled_irqs++; + snd_mpu401_uart_interrupt(irq, + chip->rmidi->private_data); + } + SET_AIACK(cif->hwport); + } + return IRQ_HANDLED; +} + +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 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; + tasklet_init(&chip->riptide_tq, riptide_handleirq, (unsigned long)chip); + + 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_irq(pci->irq, snd_riptide_interrupt, 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; + 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) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "riptide", &entry)) + snd_info_set_text_ops(entry, 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 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..f0906ba41 --- /dev/null +++ b/sound/pci/rme32.c @@ -0,0 +1,1996 @@ +/* + * ALSA driver for RME Digi32, Digi32/8 and Digi32 PRO audio interfaces + * + * Copyright (c) 2002-2004 Martin Langer , + * Pilo Chambert + * + * Thanks to : Anders Torger , + * Henk Hesselink + * for writing the digi96-driver + * and RME for all informations. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * **************************************************************************** + * + * Note #1 "Sek'd models" ................................... martin 2002-12-07 + * + * Identical soundcards by Sek'd were labeled: + * RME Digi 32 = Sek'd Prodif 32 + * RME Digi 32 Pro = Sek'd Prodif 96 + * RME Digi 32/8 = Sek'd Prodif Gold + * + * **************************************************************************** + * + * Note #2 "full duplex mode" ............................... martin 2002-12-07 + * + * Full duplex doesn't work. All cards (32, 32/8, 32Pro) are working identical + * in this mode. Rec data and play data are using the same buffer therefore. At + * first you have got the playing bits in the buffer and then (after playing + * them) they were overwitten by the captured sound of the CS8412/14. Both + * modes (play/record) are running harmonically hand in hand in the same buffer + * and you have only one start bit plus one interrupt bit to control this + * paired action. + * This is opposite to the latter rme96 where playing and capturing is totally + * separated and so their full duplex mode is supported by alsa (using two + * start bits and two interrupts for two different buffers). + * But due to the wrong sequence of playing and capturing ALSA shows no solved + * full duplex support for the rme32 at the moment. That's bad, but I'm not + * able to solve it. Are you motivated enough to solve this problem now? Your + * patch would be welcome! + * + * **************************************************************************** + * + * "The story after the long seeking" -- tiwai + * + * Ok, the situation regarding the full duplex is now improved a bit. + * In the fullduplex mode (given by the module parameter), the hardware buffer + * is split to halves for read and write directions at the DMA pointer. + * That is, the half above the current DMA pointer is used for write, and + * the half below is used for read. To mangle this strange behavior, an + * software intermediate buffer is introduced. This is, of course, not good + * from the viewpoint of the data transfer efficiency. However, this allows + * you to use arbitrary buffer sizes, instead of the fixed I/O buffer size. + * + * **************************************************************************** + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +static bool fullduplex[SNDRV_CARDS]; // = {[0 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME Digi32 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME Digi32 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable RME Digi32 soundcard."); +module_param_array(fullduplex, bool, NULL, 0444); +MODULE_PARM_DESC(fullduplex, "Support full-duplex mode."); +MODULE_AUTHOR("Martin Langer , Pilo Chambert "); +MODULE_DESCRIPTION("RME Digi32, Digi32/8, Digi32 PRO"); +MODULE_LICENSE("GPL"); +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), + .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), + .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), + .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), + .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) { + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (err < 0) + return err; + } else { + 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) { + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (err < 0) + return err; + } else { + 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 int snd_rme32_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct rme32 *rme32 = snd_pcm_substream_chip(substream); + if (! rme32->fullduplex_mode) + return 0; + return snd_pcm_lib_free_pages(substream); +} + +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); + } + + /* prefill playback buffer */ + if (cmd == SNDRV_PCM_TRIGGER_START && rme32->fullduplex_mode) { + snd_pcm_group_for_each_entry(s, substream) { + if (s == rme32->playback_substream) { + s->ops->ack(s); + break; + } + } + } + + 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_rme32_playback_hw_params, + .hw_free = snd_rme32_pcm_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_rme32_capture_hw_params, + .hw_free = snd_rme32_pcm_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_rme32_playback_hw_params, + .hw_free = snd_rme32_pcm_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_rme32_capture_hw_params, + .hw_free = snd_rme32_pcm_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_nocache(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; + + /* 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_lib_preallocate_pages_for_all(rme32->spdif_pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 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_lib_preallocate_pages_for_all(rme32->adat_pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(rme32->card, "rme32", &entry)) + snd_info_set_text_ops(entry, 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 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..dcfa4d7a7 --- /dev/null +++ b/sound/pci/rme96.c @@ -0,0 +1,2550 @@ +/* + * ALSA driver for RME Digi96, Digi96/8 and Digi96/8 PRO/PAD/PST audio + * interfaces + * + * Copyright (c) 2000, 2001 Anders Torger + * + * Thanks to Henk Hesselink for the analog volume control + * code. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* note, two last pcis should be equal, it is not a bug */ + +MODULE_AUTHOR("Anders Torger "); +MODULE_DESCRIPTION("RME Digi96, Digi96/8, Digi96/8 PRO, Digi96/8 PST, " + "Digi96/8 PAD"); +MODULE_LICENSE("GPL"); +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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .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_nocache(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; + + /* 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(rme96->card, "rme96", &entry)) + snd_info_set_text_ops(entry, 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 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); + snd_pcm_suspend(rme96->playback_substream); + snd_pcm_suspend(rme96->capture_substream); + + /* 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 +# + +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..a0797fc17 --- /dev/null +++ b/sound/pci/rme9652/hdsp.c @@ -0,0 +1,5413 @@ +/* + * ALSA driver for RME Hammerfall DSP audio interface(s) + * + * Copyright (c) 2002 Paul Davis + * Marcus Andersson + * Thomas Charbonnel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME Hammerfall DSP interface."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME Hammerfall DSP interface."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific Hammerfall DSP soundcards."); +MODULE_AUTHOR("Paul Davis , Marcus Andersson, Thomas Charbonnel "); +MODULE_DESCRIPTION("RME Hammerfall DSP"); +MODULE_LICENSE("GPL"); +MODULE_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; + 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 tasklet_struct midi_tasklet; + int use_midi_tasklet; + 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; + 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 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 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 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 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 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 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) +{ + dmab->dev.type = SNDRV_DMA_TYPE_DEV; + dmab->dev.dev = snd_dma_pci_data(pci); + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + size, dmab) < 0) + return -ENOMEM; + return 0; +} + +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; + tasklet_kill(&hdsp->midi_tasklet); + } + + 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_TASKLET(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, \ + .name = xname, \ + .index = xindex, \ + .info = snd_hdsp_info_use_midi_tasklet, \ + .get = snd_hdsp_get_use_midi_tasklet, \ + .put = snd_hdsp_put_use_midi_tasklet \ +} + +static int hdsp_set_use_midi_tasklet(struct hdsp *hdsp, int use_tasklet) +{ + if (use_tasklet) + hdsp->use_midi_tasklet = 1; + else + hdsp->use_midi_tasklet = 0; + return 0; +} + +#define snd_hdsp_info_use_midi_tasklet snd_ctl_boolean_mono_info + +static int snd_hdsp_get_use_midi_tasklet(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_tasklet; + spin_unlock_irq(&hdsp->lock); + return 0; +} + +static int snd_hdsp_put_use_midi_tasklet(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_tasklet; + hdsp_set_use_midi_tasklet(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 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 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_TASKLET("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 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 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 = -EINVAL; + 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_tasklet ? "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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(hdsp->card, "hdsp", &entry)) + snd_info_set_text_ops(entry, 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_tasklet(unsigned long arg) +{ + struct hdsp *hdsp = (struct hdsp *)arg; + + 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_tasklet) { + /* 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_tasklet) { + /* 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_tasklet && schedule) + tasklet_schedule(&hdsp->midi_tasklet); + 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 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); + 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); + 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); + 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); + 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); + 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 __user *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"); + firmware = (struct hdsp_firmware __user *)argp; + + if (get_user(firmware_data, &firmware->firmware_data)) + return -EFAULT; + + 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); + + tasklet_init(&hdsp->midi_tasklet, hdsp_midi_tasklet, (unsigned long)hdsp); + + 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_nocache(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; + hdsp->precise_ptr = 0; + hdsp->use_midi_tasklet = 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 */ + tasklet_kill(&hdsp->midi_tasklet); + 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..5dfddade1 --- /dev/null +++ b/sound/pci/rme9652/hdspm.c @@ -0,0 +1,7001 @@ +/* + * ALSA driver for RME Hammerfall DSP MADI audio interface(s) + * + * Copyright (c) 2003 Winfried Ritsch (IEM) + * code based on hdsp.c Paul Davis + * Marcus Andersson + * Thomas Charbonnel + * Modified 2006-06-01 for AES32 support by Remy Bruno + * + * + * Modified 2009-04-13 for proper metering by Florian Faber + * + * + * Modified 2009-04-14 for native float support by Florian Faber + * + * + * Modified 2009-04-26 fixed bug in rms metering by Florian Faber + * + * + * Modified 2009-04-30 added hw serial number support by Florian Faber + * + * Modified 2011-01-14 added S/PDIF input on RayDATs by Adrian Knoth + * + * Modified 2011-01-25 variable period sizes on RayDAT/AIO by Adrian Knoth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* ************* Register Documentation ******************************************************* + * + * Work in progress! Documentation is based on the code in this file. + * + * --------- HDSPM_controlRegister --------- + * :7654.3210:7654.3210:7654.3210:7654.3210: bit number per byte + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number + * :1098.7654:3210.9876:5432.1098:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421: hex digit + * : . : . : . : x . : HDSPM_AudioInterruptEnable \_ setting both bits + * : . : . : . : . x: HDSPM_Start / enables audio IO + * : . : . : . : x. : HDSPM_ClockModeMaster - 1: Master, 0: Slave + * : . : . : . : .210 : HDSPM_LatencyMask - 3 Bit value for latency + * : . : . : . : . : 0:64, 1:128, 2:256, 3:512, + * : . : . : . : . : 4:1024, 5:2048, 6:4096, 7:8192 + * :x . : . : . x:xx . : HDSPM_FrequencyMask + * : . : . : . :10 . : HDSPM_Frequency1|HDSPM_Frequency0: 1=32K,2=44.1K,3=48K,0=?? + * : . : . : . x: . : HDSPM_DoubleSpeed + * :x . : . : . : . : HDSPM_QuadSpeed + * : . 3 : . 10: 2 . : . : HDSPM_SyncRefMask : + * : . : . x: . : . : HDSPM_SyncRef0 + * : . : . x : . : . : HDSPM_SyncRef1 + * : . : . : x . : . : HDSPM_SyncRef2 + * : . x : . : . : . : HDSPM_SyncRef3 + * : . : . 10: . : . : sync ref: 0:WC, 1:Madi, 2:TCO, 3:SyncIn + * : . 3 : . 10: 2 . : . : 0:WC, 1:AES1 ... 8:AES8, 9: TCO, 10:SyncIn? + * : . x : . : . : . : HDSPe_FLOAT_FORMAT + * : . : . : x . : . : HDSPM_InputSelect0 : 0=optical,1=coax + * : . : . :x . : . : HDSPM_InputSelect1 + * : . : .x : . : . : HDSPM_clr_tms + * : . : . : . x : . : HDSPM_TX_64ch + * : . : . : . x : . : HDSPM_Emphasis + * : . : . : .x : . : HDSPM_AutoInp + * : . : . x : . : . : HDSPM_SMUX + * : . : .x : . : . : HDSPM_clr_tms + * : . : x. : . : . : HDSPM_taxi_reset + * : . x: . : . : . : HDSPM_LineOut + * : . x: . : . : . : ?????????????????? + * : . : x. : . : . : HDSPM_WCK48 + * : . : . : .x : . : HDSPM_Dolby + * : . : x . : . : . : HDSPM_Midi0InterruptEnable + * : . :x . : . : . : HDSPM_Midi1InterruptEnable + * : . : x . : . : . : HDSPM_Midi2InterruptEnable + * : . x : . : . : . : HDSPM_Midi3InterruptEnable + * : . x : . : . : . : HDSPM_DS_DoubleWire + * : .x : . : . : . : HDSPM_QS_DoubleWire + * : x. : . : . : . : HDSPM_QS_QuadWire + * : . : . : . x : . : HDSPM_Professional + * : x . : . : . : . : HDSPM_wclk_sel + * : . : . : . : . : + * :7654.3210:7654.3210:7654.3210:7654.3210: bit number per byte + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number + * :1098.7654:3210.9876:5432.1098:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421:hex digit + * + * + * + * AIO / RayDAT only + * + * ------------ HDSPM_WR_SETTINGS ---------- + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number per byte + * :1098.7654:3210.9876:5432.1098:7654.3210: + * :||||.||||:||||.||||:||||.||||:||||.||||: bit number + * :7654.3210:7654.3210:7654.3210:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421: hex digit + * : . : . : . : . x: HDSPM_c0Master 1: Master, 0: Slave + * : . : . : . : . x : HDSPM_c0_SyncRef0 + * : . : . : . : . x : HDSPM_c0_SyncRef1 + * : . : . : . : .x : HDSPM_c0_SyncRef2 + * : . : . : . : x. : HDSPM_c0_SyncRef3 + * : . : . : . : 3.210 : HDSPM_c0_SyncRefMask: + * : . : . : . : . : RayDat: 0:WC, 1:AES, 2:SPDIF, 3..6: ADAT1..4, + * : . : . : . : . : 9:TCO, 10:SyncIn + * : . : . : . : . : AIO: 0:WC, 1:AES, 2: SPDIF, 3: ATAT, + * : . : . : . : . : 9:TCO, 10:SyncIn + * : . : . : . : . : + * : . : . : . : . : + * :3322.2222:2222.1111:1111.1100:0000.0000: bit number per byte + * :1098.7654:3210.9876:5432.1098:7654.3210: + * :||||.||||:||||.||||:||||.||||:||||.||||: bit number + * :7654.3210:7654.3210:7654.3210:7654.3210: 0..31 + * :||||.||||:||||.||||:||||.||||:||||.||||: + * :8421.8421:8421.8421:8421.8421:8421.8421: hex digit + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME HDSPM interface."); + +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME HDSPM interface."); + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific HDSPM soundcards."); + + +MODULE_AUTHOR +( + "Winfried Ritsch , " + "Paul Davis , " + "Marcus Andersson, Thomas Charbonnel , " + "Remy Bruno , " + "Florian Faber , " + "Adrian Knoth " +); +MODULE_DESCRIPTION("RME HDSPM"); +MODULE_LICENSE("GPL"); +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 char *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 char *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 char *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 char *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 char *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 char *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 char *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 char *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 char *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 char *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 char *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 char *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 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 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 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 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 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 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 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 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 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 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 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 tasklet_struct midi_tasklet; + + 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; + + signed char *channel_map_in; + signed char *channel_map_out; + + signed char *channel_map_in_ss, *channel_map_in_ds, *channel_map_in_qs; + signed char *channel_map_out_ss, *channel_map_out_ds, *channel_map_out_qs; + + char **port_names_in; + char **port_names_out; + + char **port_names_in_ss, **port_names_in_ds, **port_names_in_qs; + char **port_names_out_ss, **port_names_out_ds, **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_sgbuf(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 arbitary 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_tasklet(unsigned long arg) +{ + struct hdspm *hdspm = (struct hdspm *)arg; + 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 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 . The semantics + * of are depending on the card type, see the comments + * for clarification. + */ +static int hdspm_set_pref_sync_ref(struct hdspm * hdspm, int pref) +{ + int p = 0; + + switch (hdspm->io_type) { + case AES32: + hdspm->control_register &= ~HDSPM_SyncRefMask; + switch (pref) { + case 0: /* WC */ + break; + case 1: /* AES 1 */ + hdspm->control_register |= HDSPM_SyncRef0; + break; + case 2: /* AES 2 */ + hdspm->control_register |= HDSPM_SyncRef1; + break; + case 3: /* AES 3 */ + hdspm->control_register |= + HDSPM_SyncRef1+HDSPM_SyncRef0; + break; + case 4: /* AES 4 */ + hdspm->control_register |= HDSPM_SyncRef2; + break; + case 5: /* AES 5 */ + hdspm->control_register |= + HDSPM_SyncRef2+HDSPM_SyncRef0; + break; + case 6: /* AES 6 */ + hdspm->control_register |= + HDSPM_SyncRef2+HDSPM_SyncRef1; + break; + case 7: /* AES 7 */ + hdspm->control_register |= + HDSPM_SyncRef2+HDSPM_SyncRef1+HDSPM_SyncRef0; + break; + case 8: /* AES 8 */ + hdspm->control_register |= HDSPM_SyncRef3; + break; + case 9: /* TCO */ + hdspm->control_register |= + HDSPM_SyncRef3+HDSPM_SyncRef0; + break; + default: + return -1; + } + + break; + + case MADI: + case MADIface: + hdspm->control_register &= ~HDSPM_SyncRefMask; + if (hdspm->tco) { + switch (pref) { + case 0: /* WC */ + break; + case 1: /* MADI */ + hdspm->control_register |= HDSPM_SyncRef0; + break; + case 2: /* TCO */ + hdspm->control_register |= HDSPM_SyncRef1; + break; + case 3: /* SYNC_IN */ + hdspm->control_register |= + HDSPM_SyncRef0+HDSPM_SyncRef1; + break; + default: + return -1; + } + } else { + switch (pref) { + case 0: /* WC */ + break; + case 1: /* MADI */ + hdspm->control_register |= HDSPM_SyncRef0; + break; + case 2: /* SYNC_IN */ + hdspm->control_register |= + HDSPM_SyncRef0+HDSPM_SyncRef1; + break; + default: + return -1; + } + } + + break; + + case RayDAT: + if (hdspm->tco) { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT 1 */ + case 2: p = 4; break; /* ADAT 2 */ + case 3: p = 5; break; /* ADAT 3 */ + case 4: p = 6; break; /* ADAT 4 */ + case 5: p = 1; break; /* AES */ + case 6: p = 2; break; /* SPDIF */ + case 7: p = 9; break; /* TCO */ + case 8: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } else { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT 1 */ + case 2: p = 4; break; /* ADAT 2 */ + case 3: p = 5; break; /* ADAT 3 */ + case 4: p = 6; break; /* ADAT 4 */ + case 5: p = 1; break; /* AES */ + case 6: p = 2; break; /* SPDIF */ + case 7: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } + break; + + case AIO: + if (hdspm->tco) { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT */ + case 2: p = 1; break; /* AES */ + case 3: p = 2; break; /* SPDIF */ + case 4: p = 9; break; /* TCO */ + case 5: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } else { + switch (pref) { + case 0: p = 0; break; /* WC */ + case 1: p = 3; break; /* ADAT */ + case 2: p = 1; break; /* AES */ + case 3: p = 2; break; /* SPDIF */ + case 4: p = 10; break; /* SYNC_IN */ + default: return -1; + } + } + break; + } + + switch (hdspm->io_type) { + case RayDAT: + case AIO: + hdspm->settings_register &= ~HDSPM_c0_SyncRefMask; + hdspm->settings_register |= HDSPM_c0_SyncRef0 * p; + hdspm_write(hdspm, HDSPM_WR_SETTINGS, hdspm->settings_register); + break; + + case MADI: + case MADIface: + case AES32: + hdspm_write(hdspm, HDSPM_controlRegister, + hdspm->control_register); + } + + return 0; +} + + +static int snd_hdspm_info_pref_sync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + + snd_ctl_enum_info(uinfo, 1, hdspm->texts_autosync_items, hdspm->texts_autosync); + + return 0; +} + +static int snd_hdspm_get_pref_sync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int psf = hdspm_pref_sync_ref(hdspm); + + if (psf >= 0) { + ucontrol->value.enumerated.item[0] = psf; + return 0; + } + + return -1; +} + +static int snd_hdspm_put_pref_sync_ref(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hdspm *hdspm = snd_kcontrol_chip(kcontrol); + int val, change = 0; + + if (!snd_hdspm_use_is_exclusive(hdspm)) + return -EBUSY; + + val = ucontrol->value.enumerated.item[0]; + + if (val < 0) + val = 0; + else if (val >= hdspm->texts_autosync_items) + val = hdspm->texts_autosync_items-1; + + 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; + if ((syncref >= HDSPM_AES32_AUTOSYNC_FROM_WORD) && + (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<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 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 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 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 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 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 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; + 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) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(hdspm->card, "hdspm", &entry)) { + switch (hdspm->io_type) { + case AES32: + snd_info_set_text_ops(entry, hdspm, + snd_hdspm_proc_read_aes32); + break; + case MADI: + snd_info_set_text_ops(entry, hdspm, + snd_hdspm_proc_read_madi); + break; + case MADIface: + /* snd_info_set_text_ops(entry, hdspm, + snd_hdspm_proc_read_madiface); */ + break; + case RayDAT: + snd_info_set_text_ops(entry, hdspm, + snd_hdspm_proc_read_raydat); + break; + case AIO: + break; + } + } + + if (!snd_card_proc_new(hdspm->card, "ports.in", &entry)) { + snd_info_set_text_ops(entry, hdspm, snd_hdspm_proc_ports_in); + } + + if (!snd_card_proc_new(hdspm->card, "ports.out", &entry)) { + snd_info_set_text_ops(entry, hdspm, snd_hdspm_proc_ports_out); + } + +#ifdef CONFIG_SND_DEBUG + /* debug file to read all hdspm registers */ + if (!snd_card_proc_new(hdspm->card, "debug", &entry)) + snd_info_set_text_ops(entry, 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) + tasklet_hi_schedule(&hdspm->midi_tasklet); + } + + 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) { + + hdspm_set_sgbuf(hdspm, substream, HDSPM_pageAddressBufferOut, + params_channels(params)); + + for (i = 0; i < params_channels(params); ++i) + snd_hdspm_enable_out(hdspm, i, 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 { + hdspm_set_sgbuf(hdspm, substream, HDSPM_pageAddressBufferIn, + params_channels(params)); + + for (i = 0; i < params_channels(params); ++i) + snd_hdspm_enable_in(hdspm, i, 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) { + + /* params_channels(params) should be enough, + but to get sure in case of error */ + for (i = 0; i < hdspm->max_channels_out; ++i) + snd_hdspm_enable_out(hdspm, i, 0); + + hdspm->playback_buffer = NULL; + } else { + for (i = 0; i < hdspm->max_channels_in; ++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 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 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, <c, sizeof(ltc)); + if (0 != s) { + /* + dev_err(hdspm->card->dev, "copy_to_user(.., .., %lu): %lu [LTC]\n", sizeof(struct hdspm_ltc), s); */ + return -EFAULT; + } + + break; + + case SNDRV_HDSPM_IOCTL_GET_CONFIG: + + memset(&info, 0, sizeof(info)); + 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, + .page = snd_pcm_sgbuf_ops_page, +}; + +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) +{ + int err; + struct snd_pcm *pcm; + size_t wanted; + + pcm = hdspm->pcm; + + wanted = HDSPM_DMA_AREA_BYTES; + + err = + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(hdspm->pci), + wanted, + wanted); + if (err < 0) { + dev_dbg(hdspm->card->dev, + "Could not preallocate %zd Bytes\n", wanted); + + return err; + } else + dev_dbg(hdspm->card->dev, + " Preallocated %zd Bytes\n", wanted); + + return 0; +} + + +static void hdspm_set_sgbuf(struct hdspm *hdspm, + struct snd_pcm_substream *substream, + unsigned int reg, int channels) +{ + int i; + + /* continuous memory segment */ + for (i = 0; i < (channels * 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, "Initializeing 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); + + 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_nocache(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; + + 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; + + } + + tasklet_init(&hdspm->midi_tasklet, + hdspm_midi_tasklet, (unsigned long) hdspm); + + + 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) { + + /* 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..f82fa5be7 --- /dev/null +++ b/sound/pci/rme9652/rme9652.c @@ -0,0 +1,2655 @@ +/* + * ALSA driver for RME Digi9652 audio interfaces + * + * Copyright (c) 1999 IEM - Winfried Ritsch + * Copyright (c) 1999-2001 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ +static bool precise_ptr[SNDRV_CARDS]; /* Enable precise pointer */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for RME Digi9652 (Hammerfall) soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for RME Digi9652 (Hammerfall) soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable/disable specific RME96{52,36} soundcards."); +module_param_array(precise_ptr, bool, NULL, 0444); +MODULE_PARM_DESC(precise_ptr, "Enable precise pointer (doesn't work reliably)."); +MODULE_AUTHOR("Paul Davis , Winfried Ritsch"); +MODULE_DESCRIPTION("RME Digi9652/Digi9636"); +MODULE_LICENSE("GPL"); +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 */ + + 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 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 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 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 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) +{ + dmab->dev.type = SNDRV_DMA_TYPE_DEV; + dmab->dev.dev = snd_dma_pci_data(pci); + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + size, dmab) < 0) + return -ENOMEM; + return 0; +} + +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 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<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 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 struct snd_kcontrol_new snd_rme9652_adat3_check = +RME9652_ADAT_SYNC("ADAT3 Sync Check", 0, 2); + +static 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(rme9652->card, "rme9652", &entry)) + snd_info_set_text_ops(entry, 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 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); + 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); + 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); + 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); + 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); + 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; + int result = 0; + + spin_lock_irqsave(&rme9652->lock, flags); + if (!rme9652->running) + rme9652_reset_hw_pointer(rme9652); + spin_unlock_irqrestore(&rme9652->lock, flags); + return result; +} + +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_nocache(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; + 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..964acf302 --- /dev/null +++ b/sound/pci/sis7019.c @@ -0,0 +1,1480 @@ +/* + * Driver for SiS7019 Audio Accelerator + * + * Copyright (C) 2004-2007, David Dillow + * Written by David Dillow + * Inspired by the Trident 4D-WaveDX/NX driver. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sis7019.h" + +MODULE_AUTHOR("David Dillow "); +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_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int sis_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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 = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (rc < 0) + 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sis_playback_hw_params, + .hw_free = sis_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sis_capture_hw_params, + .hw_free = sis_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(sis->pci), 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 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); + snd_pcm_suspend_all(sis->pcm); + 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 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_nocache(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; + 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..bc8c76819 --- /dev/null +++ b/sound/pci/sis7019.h @@ -0,0 +1,342 @@ +#ifndef __sis7019_h__ +#define __sis7019_h__ + +/* + * Definitions for SiS7019 Audio Accelerator + * + * Copyright (C) 2004-2007, David Dillow + * Written by David Dillow + * Inspired by the Trident 4D-WaveDX/NX driver. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * 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 + */ + + +/* 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..7218f38b5 --- /dev/null +++ b/sound/pci/sonicvibes.c @@ -0,0 +1,1548 @@ +/* + * Driver for S3 SonicVibes soundcard + * Copyright (c) by Jaroslav Kysela + * + * BUGS: + * It looks like 86c617 rev 3 doesn't supports DDMA buffers above 16MB? + * Driver sometimes hangs... Nobody knows why at this moment... + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("S3 SonicVibes PCI"); +MODULE_LICENSE("GPL"); +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_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_sonicvibes_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sonicvibes_hw_params, + .hw_free = snd_sonicvibes_hw_free, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sonicvibes_hw_params, + .hw_free = snd_sonicvibes_hw_free, + .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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(sonic->pci), 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 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(sonic->card, "sonicvibes", &entry)) + snd_info_set_text_ops(entry, sonic, snd_sonicvibes_proc_read); +} + +/* + + */ + +#ifdef SUPPORT_JOYSTICK +static 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 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; + + 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 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..88676b50f --- /dev/null +++ b/sound/pci/trident/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +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..2f18b1cdc --- /dev/null +++ b/sound/pci/trident/trident.c @@ -0,0 +1,187 @@ +/* + * Driver for Trident 4DWave DX/NX & SiS SI7018 Audio PCI soundcard + * + * Driver was originated by Trident + * Fri Feb 19 15:55:28 MST 1999 + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include "trident.h" +#include + +MODULE_AUTHOR("Jaroslav Kysela , "); +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..2d62c1921 --- /dev/null +++ b/sound/pci/trident/trident.h @@ -0,0 +1,444 @@ +#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 + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#define TRIDENT_DEVICE_ID_DX ((PCI_VENDOR_ID_TRIDENT<<16)|PCI_DEVICE_ID_TRIDENT_4DWAVE_DX) +#define TRIDENT_DEVICE_ID_NX ((PCI_VENDOR_ID_TRIDENT<<16)|PCI_DEVICE_ID_TRIDENT_4DWAVE_NX) +#define TRIDENT_DEVICE_ID_SI7018 ((PCI_VENDOR_ID_SI<<16)|PCI_DEVICE_ID_SI_7018) + +#define SNDRV_TRIDENT_VOICE_TYPE_PCM 0 +#define SNDRV_TRIDENT_VOICE_TYPE_SYNTH 1 +#define SNDRV_TRIDENT_VOICE_TYPE_MIDI 2 + +#define SNDRV_TRIDENT_VFLG_RUNNING (1<<0) + +/* TLB code constants */ +#define SNDRV_TRIDENT_PAGE_SIZE 4096 +#define SNDRV_TRIDENT_PAGE_SHIFT 12 +#define SNDRV_TRIDENT_PAGE_MASK ((1<port + (x)) + +#define ID_4DWAVE_DX 0x2000 +#define ID_4DWAVE_NX 0x2001 + +/* Bank definitions */ + +#define T4D_BANK_A 0 +#define T4D_BANK_B 1 +#define T4D_NUM_BANKS 2 + +/* Register definitions */ + +/* Global registers */ + +enum global_control_bits { + CHANNEL_IDX = 0x0000003f, + OVERRUN_IE = 0x00000400, /* interrupt enable: capture overrun */ + UNDERRUN_IE = 0x00000800, /* interrupt enable: playback underrun */ + ENDLP_IE = 0x00001000, /* interrupt enable: end of buffer */ + MIDLP_IE = 0x00002000, /* interrupt enable: middle buffer */ + ETOG_IE = 0x00004000, /* interrupt enable: envelope toggling */ + EDROP_IE = 0x00008000, /* interrupt enable: envelope drop */ + BANK_B_EN = 0x00010000, /* SiS: enable bank B (64 channels) */ + PCMIN_B_MIX = 0x00020000, /* SiS: PCM IN B mixing enable */ + I2S_OUT_ASSIGN = 0x00040000, /* SiS: I2S Out contains surround PCM */ + SPDIF_OUT_ASSIGN= 0x00080000, /* SiS: 0=S/PDIF L/R | 1=PCM Out FIFO */ + MAIN_OUT_ASSIGN = 0x00100000, /* SiS: 0=PCM Out FIFO | 1=MMC Out buffer */ +}; + +enum miscint_bits { + PB_UNDERRUN_IRQ = 0x00000001, REC_OVERRUN_IRQ = 0x00000002, + SB_IRQ = 0x00000004, MPU401_IRQ = 0x00000008, + OPL3_IRQ = 0x00000010, ADDRESS_IRQ = 0x00000020, + ENVELOPE_IRQ = 0x00000040, PB_UNDERRUN = 0x00000100, + REC_OVERRUN = 0x00000200, MIXER_UNDERFLOW = 0x00000400, + MIXER_OVERFLOW = 0x00000800, NX_SB_IRQ_DISABLE = 0x00001000, + ST_TARGET_REACHED = 0x00008000, + PB_24K_MODE = 0x00010000, ST_IRQ_EN = 0x00800000, + ACGPIO_IRQ = 0x01000000 +}; + +/* T2 legacy dma control registers. */ +#define LEGACY_DMAR0 0x00 // ADR0 +#define LEGACY_DMAR4 0x04 // CNT0 +#define LEGACY_DMAR6 0x06 // CNT0 - High bits +#define LEGACY_DMAR11 0x0b // MOD +#define LEGACY_DMAR15 0x0f // MMR + +#define T4D_START_A 0x80 +#define T4D_STOP_A 0x84 +#define T4D_DLY_A 0x88 +#define T4D_SIGN_CSO_A 0x8c +#define T4D_CSPF_A 0x90 +#define T4D_CSPF_B 0xbc +#define T4D_CEBC_A 0x94 +#define T4D_AINT_A 0x98 +#define T4D_AINTEN_A 0x9c +#define T4D_LFO_GC_CIR 0xa0 +#define T4D_MUSICVOL_WAVEVOL 0xa8 +#define T4D_SBDELTA_DELTA_R 0xac +#define T4D_MISCINT 0xb0 +#define T4D_START_B 0xb4 +#define T4D_STOP_B 0xb8 +#define T4D_SBBL_SBCL 0xc0 +#define T4D_SBCTRL_SBE2R_SBDD 0xc4 +#define T4D_STIMER 0xc8 +#define T4D_AINT_B 0xd8 +#define T4D_AINTEN_B 0xdc +#define T4D_RCI 0x70 + +/* MPU-401 UART */ +#define T4D_MPU401_BASE 0x20 +#define T4D_MPUR0 0x20 +#define T4D_MPUR1 0x21 +#define T4D_MPUR2 0x22 +#define T4D_MPUR3 0x23 + +/* S/PDIF Registers */ +#define NX_SPCTRL_SPCSO 0x24 +#define NX_SPLBA 0x28 +#define NX_SPESO 0x2c +#define NX_SPCSTATUS 0x64 + +/* Joystick */ +#define GAMEPORT_GCR 0x30 +#define GAMEPORT_MODE_ADC 0x80 +#define GAMEPORT_LEGACY 0x31 +#define GAMEPORT_AXES 0x34 + +/* NX Specific Registers */ +#define NX_TLBC 0x6c + +/* Channel Registers */ + +#define CH_START 0xe0 + +#define CH_DX_CSO_ALPHA_FMS 0xe0 +#define CH_DX_ESO_DELTA 0xe8 +#define CH_DX_FMC_RVOL_CVOL 0xec + +#define CH_NX_DELTA_CSO 0xe0 +#define CH_NX_DELTA_ESO 0xe8 +#define CH_NX_ALPHA_FMS_FMC_RVOL_CVOL 0xec + +#define CH_LBA 0xe4 +#define CH_GVSEL_PAN_VOL_CTRL_EC 0xf0 +#define CH_EBUF1 0xf4 +#define CH_EBUF2 0xf8 + +/* AC-97 Registers */ + +#define DX_ACR0_AC97_W 0x40 +#define DX_ACR1_AC97_R 0x44 +#define DX_ACR2_AC97_COM_STAT 0x48 + +#define NX_ACR0_AC97_COM_STAT 0x40 +#define NX_ACR1_AC97_W 0x44 +#define NX_ACR2_AC97_R_PRIMARY 0x48 +#define NX_ACR3_AC97_R_SECONDARY 0x4c + +#define SI_AC97_WRITE 0x40 +#define SI_AC97_READ 0x44 +#define SI_SERIAL_INTF_CTRL 0x48 +#define SI_AC97_GPIO 0x4c +#define SI_ASR0 0x50 +#define SI_SPDIF_CS 0x70 +#define SI_GPIO 0x7c + +enum trident_nx_ac97_bits { + /* ACR1-3 */ + NX_AC97_BUSY_WRITE = 0x0800, + NX_AC97_BUSY_READ = 0x0800, + NX_AC97_BUSY_DATA = 0x0400, + NX_AC97_WRITE_SECONDARY = 0x0100, + /* ACR0 */ + NX_AC97_SECONDARY_READY = 0x0040, + NX_AC97_SECONDARY_RECORD = 0x0020, + NX_AC97_SURROUND_OUTPUT = 0x0010, + NX_AC97_PRIMARY_READY = 0x0008, + NX_AC97_PRIMARY_RECORD = 0x0004, + NX_AC97_PCM_OUTPUT = 0x0002, + NX_AC97_WARM_RESET = 0x0001 +}; + +enum trident_dx_ac97_bits { + DX_AC97_BUSY_WRITE = 0x8000, + DX_AC97_BUSY_READ = 0x8000, + DX_AC97_READY = 0x0010, + DX_AC97_RECORD = 0x0008, + DX_AC97_PLAYBACK = 0x0002 +}; + +enum sis7018_ac97_bits { + SI_AC97_BUSY_WRITE = 0x00008000, + SI_AC97_AUDIO_BUSY = 0x00004000, + SI_AC97_MODEM_BUSY = 0x00002000, + SI_AC97_BUSY_READ = 0x00008000, + SI_AC97_SECONDARY = 0x00000080, +}; + +enum serial_intf_ctrl_bits { + WARM_RESET = 0x00000001, + COLD_RESET = 0x00000002, + I2S_CLOCK = 0x00000004, + PCM_SEC_AC97 = 0x00000008, + AC97_DBL_RATE = 0x00000010, + SPDIF_EN = 0x00000020, + I2S_OUTPUT_EN = 0x00000040, + I2S_INPUT_EN = 0x00000080, + PCMIN = 0x00000100, + LINE1IN = 0x00000200, + MICIN = 0x00000400, + LINE2IN = 0x00000800, + HEAD_SET_IN = 0x00001000, + GPIOIN = 0x00002000, + /* 7018 spec says id = 01 but the demo board routed to 10 + SECONDARY_ID= 0x00004000, */ + SECONDARY_ID = 0x00004000, + PCMOUT = 0x00010000, + SURROUT = 0x00020000, + CENTEROUT = 0x00040000, + LFEOUT = 0x00080000, + LINE1OUT = 0x00100000, + LINE2OUT = 0x00200000, + GPIOOUT = 0x00400000, + SI_AC97_PRIMARY_READY = 0x01000000, + SI_AC97_SECONDARY_READY = 0x02000000, + SI_AC97_POWERDOWN = 0x04000000, +}; + +/* PCM defaults */ + +#define T4D_DEFAULT_PCM_VOL 10 /* 0 - 255 */ +#define T4D_DEFAULT_PCM_PAN 0 /* 0 - 127 */ +#define T4D_DEFAULT_PCM_RVOL 127 /* 0 - 127 */ +#define T4D_DEFAULT_PCM_CVOL 127 /* 0 - 127 */ + +struct snd_trident; +struct snd_trident_voice; +struct snd_trident_pcm_mixer; + +struct snd_trident_port { + struct snd_midi_channel_set * chset; + struct snd_trident * trident; + int mode; /* operation mode */ + int client; /* sequencer client number */ + int port; /* sequencer port number */ + unsigned int midi_has_voices: 1; +}; + +struct snd_trident_memblk_arg { + short first_page, last_page; +}; + +struct snd_trident_tlb { + __le32 *entries; /* 16k-aligned TLB table */ + dma_addr_t entries_dmaaddr; /* 16k-aligned PCI address to TLB table */ + 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..5523e193d --- /dev/null +++ b/sound/pci/trident/trident_main.c @@ -0,0 +1,3958 @@ +/* + * Maintained by Jaroslav Kysela + * Originated by audio@tridentmicro.com + * Fri Feb 19 15:55:28 MST 1999 + * Routines for control of Trident 4DWave (DX and NX) chip + * + * BUGS: + * + * TODO: + * --- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * SiS7018 S/PDIF support by Thomas Winischhofer + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "trident.h" +#include + +static int snd_trident_pcm_mixer_build(struct snd_trident *trident, + struct snd_trident_voice * voice, + struct snd_pcm_substream *substream); +static int snd_trident_pcm_mixer_free(struct snd_trident *trident, + struct snd_trident_voice * voice, + struct snd_pcm_substream *substream); +static irqreturn_t snd_trident_interrupt(int irq, void *dev_id); +static int snd_trident_sis_reset(struct snd_trident *trident); + +static void snd_trident_clear_voices(struct snd_trident * trident, + unsigned short v_min, unsigned short v_max); +static 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_ioctl + + Description: Device I/O control handler for playback/capture parameters. + + Parameters: substream - PCM substream class + cmd - what ioctl message to process + arg - additional message infoarg + + Returns: Error status + + ---------------------------------------------------------------------------*/ + +static int snd_trident_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, + void *arg) +{ + /* FIXME: it seems that with small periods the behaviour of + trident hardware is unpredictable and interrupt generator + is broken */ + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +/*--------------------------------------------------------------------------- + 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; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + if (trident->tlb.entries) { + if (err > 0) { /* change */ + 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; + } + } + snd_pcm_lib_free_pages(substream); + 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) +{ + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + + 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; + + snd_pcm_lib_free_pages(substream); + 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, + .ioctl = snd_trident_ioctl, + .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, + .ioctl = snd_trident_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +static const struct snd_pcm_ops snd_trident_capture_ops = { + .open = snd_trident_capture_open, + .close = snd_trident_capture_close, + .ioctl = snd_trident_ioctl, + .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, + .ioctl = snd_trident_ioctl, + .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, + .ioctl = snd_trident_ioctl, + .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, + .ioctl = snd_trident_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +static const struct snd_pcm_ops snd_trident_spdif_ops = { + .open = snd_trident_spdif_open, + .close = snd_trident_spdif_close, + .ioctl = snd_trident_ioctl, + .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, + .ioctl = snd_trident_ioctl, + .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_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(trident->pci), + 64*1024, 128*1024); + snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci), + 64*1024, 128*1024); + } else { + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(trident->pci), 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_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(trident->pci), 0, 128*1024); + else + snd_pcm_lib_preallocate_pages_for_all(foldback, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(trident->pci), 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_lib_preallocate_pages_for_all(spdif, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(trident->pci), 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 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) +{ + struct snd_info_entry *entry; + const char *s = "trident"; + + if (trident->device == TRIDENT_DEVICE_ID_SI7018) + s = "sis7018"; + if (! snd_card_proc_new(trident->card, s, &entry)) + snd_info_set_text_ops(entry, 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, snd_dma_pci_data(trident->pci), + 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, snd_dma_pci_data(trident->pci), + 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 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; + + /* 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_pcm_suspend_all(trident->pcm); + snd_pcm_suspend_all(trident->foldback); + snd_pcm_suspend_all(trident->spdif); + + 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..b9ebb5189 --- /dev/null +++ b/sound/pci/trident/trident_memory.c @@ -0,0 +1,314 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Copyright (c) by Takashi Iwai + * Copyright (c) by Scott McNab + * + * Trident 4DWave-NX memory page allocation (TLB area) + * Trident chip can handle only 16MByte of the memory at the same time. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#include +#include "trident.h" + +/* page arguments of these two macros are Trident page (4096 bytes), not like + * aligned pages in others + */ +#define __set_tlb_bus(trident,page,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..c488c5afa --- /dev/null +++ b/sound/pci/via82xx.c @@ -0,0 +1,2650 @@ +/* + * ALSA driver for VIA VT82xx (South Bridge) + * + * VT82C686A/B/C, VT8233A/C, VT8235 + * + * Copyright (c) 2000 Jaroslav Kysela + * Tjeerd.Mulder + * 2002 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Changes: + * + * Dec. 19, 2002 Takashi Iwai + * - use the DSX channels for the first pcm playback. + * (on VIA8233, 8233C and 8235 only) + * this will allow you play simultaneously up to 4 streams. + * multi-channel playback is assigned to the second device + * on these chips. + * - support the secondary capture (on VIA8233/C,8235) + * - SPDIF support + * the DSX3 channel can be used for SPDIF output. + * on VIA8233A, this channel is assigned to the second pcm + * playback. + * the card config of alsa-lib will assign the correct + * device for applications. + * - clean up the code, separate low-level initialization + * routines for each chipset. + * + * Sep. 26, 2005 Karsten Wiese + * - Optimize position calculation for the 823x chips. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define POINTER_DEBUG +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("VIA VT82xx audio"); +MODULE_LICENSE("GPL"); +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); + + 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, snd_dma_pci_data(chip->pci), + 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; + 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); + ((u32 *)dev->table.area)[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); + */ + ((u32 *)dev->table.area)[(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; + 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; + int err; + + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return err; + err = build_via_table(viadev, substream, chip->pci, + params_periods(hw_params), + params_period_bytes(hw_params)); + if (err < 0) + return err; + + 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 *chip = snd_pcm_substream_chip(substream); + struct viadev *viadev = substream->runtime->private_data; + + clean_via_table(viadev, substream, chip->pci); + snd_pcm_lib_free_pages(substream); + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* via686 capture callbacks */ +static const struct snd_pcm_ops snd_via686_capture_ops = { + .open = snd_via82xx_capture_open, + .close = snd_via82xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* via823x DSX playback callbacks */ +static const struct snd_pcm_ops snd_via8233_playback_ops = { + .open = snd_via8233_playback_open, + .close = snd_via8233_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* via823x capture callbacks */ +static const struct snd_pcm_ops snd_via8233_capture_ops = { + .open = snd_via82xx_capture_open, + .close = snd_via82xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + + +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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 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 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "via82xx", &entry)) + snd_info_set_text_ops(entry, 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 < 2; i++) + snd_pcm_suspend_all(chip->pcms[i]); + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + synchronize_irq(chip->irq); + 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 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; + if (ac97_clock >= 8000 && ac97_clock <= 48000) + chip->ac97_clock = ac97_clock; + synchronize_irq(chip->irq); + + 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 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 struct snd_pci_quirk dxs_whitelist[] = { + 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_whitelist); + if (w) { + dev_dbg(&pci->dev, "DXS white 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..b13c8688c --- /dev/null +++ b/sound/pci/via82xx_modem.c @@ -0,0 +1,1232 @@ +/* + * ALSA modem driver for VIA VT82xx (South Bridge) + * + * VT82C686A/B/C, VT8233A/C, VT8235 + * + * Copyright (c) 2000 Jaroslav Kysela + * Tjeerd.Mulder + * 2002 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Changes: + * + * Sep. 2, 2004 Sasha Khapyorsky + * Modified from original audio driver 'via82xx.c' to support AC97 + * modems. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define POINTER_DEBUG +#endif + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("VIA VT82xx modem"); +MODULE_LICENSE("GPL"); +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); + + 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, snd_dma_pci_data(chip->pci), + 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; + 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); + ((u32 *)dev->table.area)[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); + */ + ((u32 *)dev->table.area)[(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; + 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 = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return 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); + snd_pcm_lib_free_pages(substream); + 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* via686 capture callbacks */ +static const struct snd_pcm_ops snd_via686_capture_ops = { + .open = snd_via82xx_capture_open, + .close = snd_via82xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .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, + .page = snd_pcm_sgbuf_ops_page, +}; + + +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); + + if ((err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), + 64*1024, 128*1024)) < 0) + return err; + + 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 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "via82xx", &entry)) + snd_info_set_text_ops(entry, 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 < 2; i++) + snd_pcm_suspend_all(chip->pcms[i]); + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + synchronize_irq(chip->irq); + 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 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; + if (ac97_clock >= 8000 && ac97_clock <= 48000) + chip->ac97_clock = ac97_clock; + synchronize_irq(chip->irq); + + 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..a4d08d4de --- /dev/null +++ b/sound/pci/vx222/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +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..55861849d --- /dev/null +++ b/sound/pci/vx222/vx222.c @@ -0,0 +1,292 @@ +/* + * Driver for Digigram VX222 V2/Mic PCI soundcards + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "vx222.h" + +#define CARD_NAME "VX222" + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("Digigram VX222 V2/Mic"); +MODULE_LICENSE("GPL"); +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 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 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 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, + struct snd_vx_hardware *hw, + struct snd_vx222 **rchip) +{ + struct vx_core *chip; + struct snd_vx222 *vx; + int i, err; + static struct snd_device_ops ops = { + .dev_free = snd_vx222_dev_free, + }; + 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; + + 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; + 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..cae355c8e --- /dev/null +++ b/sound/pci/vx222/vx222.h @@ -0,0 +1,116 @@ +/* + * Driver for Digigram VX222 PCI soundcards + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __VX222_H +#define __VX222_H + +#include + +struct snd_vx222 { + + struct vx_core core; + + /* h/w config; for PLX and for DSP */ + struct pci_dev *pci; + unsigned long port[2]; + + unsigned int regCDSP; /* current CDSP register */ + unsigned int regCFG; /* current CFG register */ + unsigned int regSELMIC; /* current SELMIC reg. (for VX222 Mic) */ + + int input_level[2]; /* input level for vx222 mic */ + int mic_level; /* mic level for vx222 mic */ +}; + +#define to_vx222(x) container_of(x, struct snd_vx222, core) + +/* we use a lookup table with 148 values, see vx_mixer.c */ +#define VX2_AKM_LEVEL_MAX 0x93 + +extern struct snd_vx_ops vx222_ops; +extern 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..c0d0bf44f --- /dev/null +++ b/sound/pci/vx222/vx222_ops.c @@ -0,0 +1,1037 @@ +/* + * Driver for Digigram VX222 V2/Mic soundcards + * + * VX222-specific low-level routines + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include "vx222.h" + + +static 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 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 + */ +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 */ +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..bd3d514ed --- /dev/null +++ b/sound/pci/ymfpci/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +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..eafdee384 --- /dev/null +++ b/sound/pci/ymfpci/ymfpci.c @@ -0,0 +1,371 @@ +/* + * The driver for the Yamaha's DS1/DS1E cards + * Copyright (c) by Jaroslav Kysela + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include "ymfpci.h" +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +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 */ + if (!(io_port = pci_resource_start(chip->pci, 2))) + return -ENODEV; + } + } else { + if (io_port == 1) { + /* auto-detect */ + for (io_port = 0x201; io_port <= 0x205; io_port++) { + if (io_port == 0x203) + continue; + if ((r = request_region(io_port, 1, "YMFPCI gameport")) != NULL) + 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"))) { + 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")) != NULL) { + 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")) != NULL) { + 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")) != NULL) { + 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")) != NULL) { + 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); + if ((err = snd_ymfpci_create(card, pci, + old_legacy_ctrl, + &chip)) < 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) { + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_YMFPCI, + mpu_port[dev], + MPU401_INFO_INTEGRATED | + MPU401_INFO_IRQ_HOOK, + -1, &chip->rawmidi)) < 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) { + if ((err = snd_opl3_create(card, + fm_port[dev], + fm_port[dev] + 2, + OPL3_HW_OPL3, 1, &opl3)) < 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 if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 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..e2fa7e360 --- /dev/null +++ b/sound/pci/ymfpci/ymfpci.h @@ -0,0 +1,389 @@ +#ifndef __SOUND_YMFPCI_H +#define __SOUND_YMFPCI_H + +/* + * Copyright (c) by Jaroslav Kysela + * Definitions for Yahama YMF724/740/744/754 chips + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +/* + * Direct registers + */ + +#define YMFREG(chip, reg) (chip->port + YDSXGR_##reg) + +#define YDSXGR_INTFLAG 0x0004 +#define YDSXGR_ACTIVITY 0x0006 +#define YDSXGR_GLOBALCTRL 0x0008 +#define YDSXGR_ZVCTRL 0x000A +#define YDSXGR_TIMERCTRL 0x0010 +#define YDSXGR_TIMERCOUNT 0x0012 +#define YDSXGR_SPDIFOUTCTRL 0x0018 +#define YDSXGR_SPDIFOUTSTATUS 0x001C +#define YDSXGR_EEPROMCTRL 0x0020 +#define YDSXGR_SPDIFINCTRL 0x0034 +#define YDSXGR_SPDIFINSTATUS 0x0038 +#define YDSXGR_DSPPROGRAMDL 0x0048 +#define YDSXGR_DLCNTRL 0x004C +#define YDSXGR_GPIOININTFLAG 0x0050 +#define YDSXGR_GPIOININTENABLE 0x0052 +#define YDSXGR_GPIOINSTATUS 0x0054 +#define YDSXGR_GPIOOUTCTRL 0x0056 +#define YDSXGR_GPIOFUNCENABLE 0x0058 +#define YDSXGR_GPIOTYPECONFIG 0x005A +#define YDSXGR_AC97CMDDATA 0x0060 +#define YDSXGR_AC97CMDADR 0x0062 +#define YDSXGR_PRISTATUSDATA 0x0064 +#define YDSXGR_PRISTATUSADR 0x0066 +#define YDSXGR_SECSTATUSDATA 0x0068 +#define YDSXGR_SECSTATUSADR 0x006A +#define YDSXGR_SECCONFIG 0x0070 +#define YDSXGR_LEGACYOUTVOL 0x0080 +#define YDSXGR_LEGACYOUTVOLL 0x0080 +#define YDSXGR_LEGACYOUTVOLR 0x0082 +#define YDSXGR_NATIVEDACOUTVOL 0x0084 +#define YDSXGR_NATIVEDACOUTVOLL 0x0084 +#define YDSXGR_NATIVEDACOUTVOLR 0x0086 +#define YDSXGR_ZVOUTVOL 0x0088 +#define YDSXGR_ZVOUTVOLL 0x0088 +#define YDSXGR_ZVOUTVOLR 0x008A +#define YDSXGR_SECADCOUTVOL 0x008C +#define YDSXGR_SECADCOUTVOLL 0x008C +#define YDSXGR_SECADCOUTVOLR 0x008E +#define YDSXGR_PRIADCOUTVOL 0x0090 +#define YDSXGR_PRIADCOUTVOLL 0x0090 +#define YDSXGR_PRIADCOUTVOLR 0x0092 +#define YDSXGR_LEGACYLOOPVOL 0x0094 +#define YDSXGR_LEGACYLOOPVOLL 0x0094 +#define YDSXGR_LEGACYLOOPVOLR 0x0096 +#define YDSXGR_NATIVEDACLOOPVOL 0x0098 +#define YDSXGR_NATIVEDACLOOPVOLL 0x0098 +#define YDSXGR_NATIVEDACLOOPVOLR 0x009A +#define YDSXGR_ZVLOOPVOL 0x009C +#define YDSXGR_ZVLOOPVOLL 0x009E +#define YDSXGR_ZVLOOPVOLR 0x009E +#define YDSXGR_SECADCLOOPVOL 0x00A0 +#define YDSXGR_SECADCLOOPVOLL 0x00A0 +#define YDSXGR_SECADCLOOPVOLR 0x00A2 +#define YDSXGR_PRIADCLOOPVOL 0x00A4 +#define YDSXGR_PRIADCLOOPVOLL 0x00A4 +#define YDSXGR_PRIADCLOOPVOLR 0x00A6 +#define YDSXGR_NATIVEADCINVOL 0x00A8 +#define YDSXGR_NATIVEADCINVOLL 0x00A8 +#define YDSXGR_NATIVEADCINVOLR 0x00AA +#define YDSXGR_NATIVEDACINVOL 0x00AC +#define YDSXGR_NATIVEDACINVOLL 0x00AC +#define YDSXGR_NATIVEDACINVOLR 0x00AE +#define YDSXGR_BUF441OUTVOL 0x00B0 +#define YDSXGR_BUF441OUTVOLL 0x00B0 +#define YDSXGR_BUF441OUTVOLR 0x00B2 +#define YDSXGR_BUF441LOOPVOL 0x00B4 +#define YDSXGR_BUF441LOOPVOLL 0x00B4 +#define YDSXGR_BUF441LOOPVOLR 0x00B6 +#define YDSXGR_SPDIFOUTVOL 0x00B8 +#define YDSXGR_SPDIFOUTVOLL 0x00B8 +#define YDSXGR_SPDIFOUTVOLR 0x00BA +#define YDSXGR_SPDIFLOOPVOL 0x00BC +#define YDSXGR_SPDIFLOOPVOLL 0x00BC +#define YDSXGR_SPDIFLOOPVOLR 0x00BE +#define YDSXGR_ADCSLOTSR 0x00C0 +#define YDSXGR_RECSLOTSR 0x00C4 +#define YDSXGR_ADCFORMAT 0x00C8 +#define YDSXGR_RECFORMAT 0x00CC +#define YDSXGR_P44SLOTSR 0x00D0 +#define YDSXGR_STATUS 0x0100 +#define YDSXGR_CTRLSELECT 0x0104 +#define YDSXGR_MODE 0x0108 +#define YDSXGR_SAMPLECOUNT 0x010C +#define YDSXGR_NUMOFSAMPLES 0x0110 +#define YDSXGR_CONFIG 0x0114 +#define YDSXGR_PLAYCTRLSIZE 0x0140 +#define YDSXGR_RECCTRLSIZE 0x0144 +#define YDSXGR_EFFCTRLSIZE 0x0148 +#define YDSXGR_WORKSIZE 0x014C +#define YDSXGR_MAPOFREC 0x0150 +#define YDSXGR_MAPOFEFFECT 0x0154 +#define YDSXGR_PLAYCTRLBASE 0x0158 +#define YDSXGR_RECCTRLBASE 0x015C +#define YDSXGR_EFFCTRLBASE 0x0160 +#define YDSXGR_WORKBASE 0x0164 +#define YDSXGR_DSPINSTRAM 0x1000 +#define YDSXGR_CTRLINSTRAM 0x4000 + +#define YDSXG_AC97READCMD 0x8000 +#define YDSXG_AC97WRITECMD 0x0000 + +#define PCIR_DSXG_LEGACY 0x40 +#define PCIR_DSXG_ELEGACY 0x42 +#define PCIR_DSXG_CTRL 0x48 +#define PCIR_DSXG_PWRCTRL1 0x4a +#define PCIR_DSXG_PWRCTRL2 0x4e +#define PCIR_DSXG_FMBASE 0x60 +#define PCIR_DSXG_SBBASE 0x62 +#define PCIR_DSXG_MPU401BASE 0x64 +#define PCIR_DSXG_JOYBASE 0x66 + +#define YDSXG_DSPLENGTH 0x0080 +#define YDSXG_CTRLLENGTH 0x3000 + +#define YDSXG_DEFAULT_WORK_SIZE 0x0400 + +#define YDSXG_PLAYBACK_VOICES 64 +#define YDSXG_CAPTURE_VOICES 2 +#define YDSXG_EFFECT_VOICES 5 + +#define YMFPCI_LEGACY_SBEN (1 << 0) /* soundblaster enable */ +#define YMFPCI_LEGACY_FMEN (1 << 1) /* OPL3 enable */ +#define YMFPCI_LEGACY_JPEN (1 << 2) /* joystick enable */ +#define YMFPCI_LEGACY_MEN (1 << 3) /* MPU401 enable */ +#define YMFPCI_LEGACY_MIEN (1 << 4) /* MPU RX irq enable */ +#define YMFPCI_LEGACY_IOBITS (1 << 5) /* i/o bits range, 0 = 16bit, 1 =10bit */ +#define YMFPCI_LEGACY_SDMA (3 << 6) /* SB DMA select */ +#define YMFPCI_LEGACY_SBIRQ (7 << 8) /* SB IRQ select */ +#define YMFPCI_LEGACY_MPUIRQ (7 << 11) /* MPU IRQ select */ +#define YMFPCI_LEGACY_SIEN (1 << 14) /* serialized IRQ */ +#define YMFPCI_LEGACY_LAD (1 << 15) /* legacy audio disable */ + +#define YMFPCI_LEGACY2_FMIO (3 << 0) /* OPL3 i/o address (724/740) */ +#define YMFPCI_LEGACY2_SBIO (3 << 2) /* SB i/o address (724/740) */ +#define YMFPCI_LEGACY2_MPUIO (3 << 4) /* MPU401 i/o address (724/740) */ +#define YMFPCI_LEGACY2_JSIO (3 << 6) /* joystick i/o address (724/740) */ +#define YMFPCI_LEGACY2_MAIM (1 << 8) /* MPU401 ack intr mask */ +#define YMFPCI_LEGACY2_SMOD (3 << 11) /* SB DMA mode */ +#define YMFPCI_LEGACY2_SBVER (3 << 13) /* SB version select */ +#define YMFPCI_LEGACY2_IMOD (1 << 15) /* legacy IRQ mode */ +/* SIEN:IMOD 0:0 = legacy irq, 0:1 = INTA, 1:0 = serialized IRQ */ + +#if IS_REACHABLE(CONFIG_GAMEPORT) +#define SUPPORT_JOYSTICK +#endif + +/* + * + */ + +struct snd_ymfpci_playback_bank { + __le32 format; + __le32 loop_default; + __le32 base; /* 32-bit address */ + __le32 loop_start; /* 32-bit offset */ + __le32 loop_end; /* 32-bit offset */ + __le32 loop_frac; /* 8-bit fraction - loop_start */ + __le32 delta_end; /* pitch delta end */ + __le32 lpfK_end; + __le32 eg_gain_end; + __le32 left_gain_end; + __le32 right_gain_end; + __le32 eff1_gain_end; + __le32 eff2_gain_end; + __le32 eff3_gain_end; + __le32 lpfQ; + __le32 status; + __le32 num_of_frames; + __le32 loop_count; + __le32 start; + __le32 start_frac; + __le32 delta; + __le32 lpfK; + __le32 eg_gain; + __le32 left_gain; + __le32 right_gain; + __le32 eff1_gain; + __le32 eff2_gain; + __le32 eff3_gain; + __le32 lpfD1; + __le32 lpfD2; + }; + +struct snd_ymfpci_capture_bank { + __le32 base; /* 32-bit address */ + __le32 loop_end; /* 32-bit offset */ + __le32 start; /* 32-bit offset */ + __le32 num_of_loops; /* counter */ +}; + +struct snd_ymfpci_effect_bank { + __le32 base; /* 32-bit address */ + __le32 loop_end; /* 32-bit offset */ + __le32 start; /* 32-bit offset */ + __le32 temp; +}; + +struct snd_ymfpci_pcm; +struct snd_ymfpci; + +enum snd_ymfpci_voice_type { + YMFPCI_PCM, + YMFPCI_SYNTH, + YMFPCI_MIDI +}; + +struct snd_ymfpci_voice { + struct snd_ymfpci *chip; + int number; + unsigned int use: 1, + pcm: 1, + synth: 1, + midi: 1; + struct snd_ymfpci_playback_bank *bank; + dma_addr_t bank_addr; + void (*interrupt)(struct snd_ymfpci *chip, struct snd_ymfpci_voice *voice); + struct snd_ymfpci_pcm *ypcm; +}; + +enum snd_ymfpci_pcm_type { + PLAYBACK_VOICE, + CAPTURE_REC, + CAPTURE_AC97, + EFFECT_DRY_LEFT, + EFFECT_DRY_RIGHT, + EFFECT_EFF1, + EFFECT_EFF2, + EFFECT_EFF3 +}; + +struct snd_ymfpci_pcm { + struct snd_ymfpci *chip; + enum snd_ymfpci_pcm_type type; + struct snd_pcm_substream *substream; + struct snd_ymfpci_voice *voices[2]; /* playback only */ + unsigned int running: 1, + use_441_slot: 1, + output_front: 1, + output_rear: 1, + swap_rear: 1; + unsigned int update_pcm_vol; + u32 period_size; /* cached from runtime->period_size */ + u32 buffer_size; /* cached from runtime->buffer_size */ + u32 period_pos; + u32 last_pos; + u32 capture_bank_number; + u32 shift; +}; + +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..a4926fb03 --- /dev/null +++ b/sound/pci/ymfpci/ymfpci_main.c @@ -0,0 +1,2458 @@ +/* + * Copyright (c) by Jaroslav Kysela + * Routines for control of YMF724/740/744/754 chips + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "ymfpci.h" +#include +#include + +#include + +/* + * common I/O routines + */ + +static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip); + +static inline 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 u32 def_rate[8] = { + 100, 2000, 8000, 11025, 16000, 22050, 32000, 48000 +}; + +static u32 snd_ymfpci_calc_lpfK(u32 rate) +{ + u32 i; + static u32 val[8] = { + 0x00570000, 0x06AA0000, 0x18B20000, 0x20930000, + 0x2B9A0000, 0x35A10000, 0x3EAA0000, 0x40000000 + }; + + if (rate == 44100) + return 0x40000000; /* FIXME: What's the right value? */ + for (i = 0; i < 8; i++) + if (rate <= def_rate[i]) + return val[i]; + return val[0]; +} + +static u32 snd_ymfpci_calc_lpfQ(u32 rate) +{ + u32 i; + static u32 val[8] = { + 0x35280000, 0x34A70000, 0x32020000, 0x31770000, + 0x31390000, 0x31C90000, 0x33D00000, 0x40000000 + }; + + if (rate == 44100) + return 0x370A0000; + for (i = 0; i < 8; i++) + if (rate <= def_rate[i]) + return val[i]; + return val[0]; +} + +/* + * 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; + + if ((ypcm = voice->ypcm) == NULL) + 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; + } + /* fall through */ + 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, snd_dma_pci_data(chip->pci), + 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; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + if ((err = snd_ymfpci_pcm_voice_alloc(ypcm, params_channels(hw_params))) < 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); + snd_pcm_lib_free_pages(substream); + 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_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +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 snd_pcm_lib_free_pages(substream); +} + +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; + + if ((err = snd_ymfpci_playback_open_1(substream)) < 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; + + if ((err = snd_ymfpci_playback_open_1(substream)) < 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; + + if ((err = snd_ymfpci_playback_open_1(substream)) < 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, + .ioctl = snd_pcm_lib_ioctl, + .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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ymfpci_capture_hw_params, + .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; + + if ((err = snd_pcm_new(chip->card, "YMFPCI", device, 32, 1, &pcm)) < 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ymfpci_capture_hw_params, + .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; + + if ((err = snd_pcm_new(chip->card, "YMFPCI - PCM2", device, 0, 1, &pcm)) < 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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, + .ioctl = snd_pcm_lib_ioctl, + .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; + + if ((err = snd_pcm_new(chip->card, "YMFPCI - IEC958", device, 1, 0, &pcm)) < 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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, + .ioctl = snd_pcm_lib_ioctl, + .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; + + if ((err = snd_pcm_new(chip->card, "YMFPCI - Rear", device, 1, 0, &pcm)) < 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_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), 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 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 struct snd_ac97_bus_ops ops = { + .write = snd_ymfpci_codec_write, + .read = snd_ymfpci_codec_read, + }; + + if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 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; + if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 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++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_controls[idx], chip))) < 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; + if ((err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_spdif_default, chip))) < 0) + return err; + kctl->id.device = chip->pcm_spdif->device; + if ((err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_spdif_mask, chip))) < 0) + return err; + kctl->id.device = chip->pcm_spdif->device; + if ((err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_spdif_stream, chip))) < 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 && + (err = snd_ctl_add(chip->card, kctl = snd_ctl_new1(&snd_ymfpci_drec_source, chip))) < 0) + return err; + + /* + * shared rear/line-in + */ + if (rear_switch) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snd_ymfpci_rear_shared, chip))) < 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; + if ((err = snd_ctl_add(chip->card, kctl)) < 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 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; + if ((err = snd_timer_new(chip->card, "YMFPCI", &tid, &timer)) >= 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) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(card, "ymfpci", &entry)) + snd_info_set_text_ops(entry, chip, snd_ymfpci_proc_read); + return 0; +} + +/* + * 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, snd_dma_pci_data(chip->pci), + 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 + 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 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_pcm_suspend_all(chip->pcm); + snd_pcm_suspend_all(chip->pcm2); + snd_pcm_suspend_all(chip->pcm_spdif); + snd_pcm_suspend_all(chip->pcm_4ch); + 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 struct snd_device_ops ops = { + .dev_free = snd_ymfpci_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; + } + 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_nocache(chip->reg_area_phys, 0x8000); + pci_set_master(pci); + chip->src441_used = -1; + + if ((chip->res_reg_area = request_mem_region(chip->reg_area_phys, 0x8000, "YMFPCI")) == NULL) { + 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; + + 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; +} diff --git a/sound/pcmcia/Kconfig b/sound/pcmcia/Kconfig new file mode 100644 index 000000000..7fbb190ad --- /dev/null +++ b/sound/pcmcia/Kconfig @@ -0,0 +1,33 @@ +# ALSA PCMCIA drivers + +menuconfig SND_PCMCIA + bool "PCMCIA sound devices" + depends on PCMCIA + default y + help + Support for sound devices connected via the PCMCIA bus. + +if SND_PCMCIA && PCMCIA + +config SND_VXPOCKET + tristate "Digigram VXpocket" + select SND_VX_LIB + help + Say Y here to include support for Digigram VXpocket and + VXpocket 440 soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-vxpocket. + +config SND_PDAUDIOCF + tristate "Sound Core PDAudioCF" + select SND_PCM + help + Say Y here to include support for Sound Core PDAudioCF + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-pdaudiocf. + +endif # SND_PCMCIA + diff --git a/sound/pcmcia/Makefile b/sound/pcmcia/Makefile new file mode 100644 index 000000000..beef2e33b --- /dev/null +++ b/sound/pcmcia/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +obj-$(CONFIG_SND) += vx/ pdaudiocf/ diff --git a/sound/pcmcia/pdaudiocf/Makefile b/sound/pcmcia/pdaudiocf/Makefile new file mode 100644 index 000000000..e892d7299 --- /dev/null +++ b/sound/pcmcia/pdaudiocf/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# Copyright (c) 2004 by Jaroslav Kysela +# + +snd-pdaudiocf-objs := pdaudiocf.o pdaudiocf_core.o pdaudiocf_irq.o pdaudiocf_pcm.o + +obj-$(CONFIG_SND_PDAUDIOCF) += snd-pdaudiocf.o diff --git a/sound/pcmcia/pdaudiocf/pdaudiocf.c b/sound/pcmcia/pdaudiocf/pdaudiocf.c new file mode 100644 index 000000000..07f4b33db --- /dev/null +++ b/sound/pcmcia/pdaudiocf/pdaudiocf.c @@ -0,0 +1,303 @@ +/* + * Driver for Sound Core PDAudioCF soundcard + * + * Copyright (c) 2003 by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "pdaudiocf.h" +#include +#include + +/* + */ + +#define CARD_NAME "PDAudio-CF" + +MODULE_AUTHOR("Jaroslav Kysela "); +MODULE_DESCRIPTION("Sound Core " CARD_NAME); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Sound Core," 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 switches */ + +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."); + +/* + */ + +static struct snd_card *card_list[SNDRV_CARDS]; + +/* + * prototypes + */ +static int pdacf_config(struct pcmcia_device *link); +static void snd_pdacf_detach(struct pcmcia_device *p_dev); + +static void pdacf_release(struct pcmcia_device *link) +{ + free_irq(link->irq, link->priv); + pcmcia_disable_device(link); +} + +/* + * destructor + */ +static int snd_pdacf_free(struct snd_pdacf *pdacf) +{ + struct pcmcia_device *link = pdacf->p_dev; + + pdacf_release(link); + + card_list[pdacf->index] = NULL; + pdacf->card = NULL; + + kfree(pdacf); + return 0; +} + +static int snd_pdacf_dev_free(struct snd_device *device) +{ + struct snd_pdacf *chip = device->device_data; + return snd_pdacf_free(chip); +} + +/* + * snd_pdacf_attach - attach callback for cs + */ +static int snd_pdacf_probe(struct pcmcia_device *link) +{ + int i, err; + struct snd_pdacf *pdacf; + struct snd_card *card; + static struct snd_device_ops ops = { + .dev_free = snd_pdacf_dev_free, + }; + + snd_printdd(KERN_DEBUG "pdacf_attach called\n"); + /* find an empty slot from the card list */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (! card_list[i]) + break; + } + if (i >= SNDRV_CARDS) { + snd_printk(KERN_ERR "pdacf: too many cards found\n"); + return -EINVAL; + } + if (! enable[i]) + return -ENODEV; /* disabled explicitly */ + + /* ok, create a card instance */ + err = snd_card_new(&link->dev, index[i], id[i], THIS_MODULE, + 0, &card); + if (err < 0) { + snd_printk(KERN_ERR "pdacf: cannot create a card instance\n"); + return err; + } + + pdacf = snd_pdacf_create(card); + if (!pdacf) { + snd_card_free(card); + return -ENOMEM; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pdacf, &ops); + if (err < 0) { + kfree(pdacf); + snd_card_free(card); + return err; + } + + pdacf->index = i; + card_list[i] = card; + + pdacf->p_dev = link; + link->priv = pdacf; + + link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + link->resource[0]->end = 16; + + link->config_flags = CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ; + link->config_index = 1; + link->config_regs = PRESENT_OPTION; + + return pdacf_config(link); +} + + +/** + * snd_pdacf_assign_resources - initialize the hardware and card instance. + * @port: i/o port for the card + * @irq: irq number for the card + * + * this function assigns the specified port and irq, boot the card, + * create pcm and control instances, and initialize the rest hardware. + * + * returns 0 if successful, or a negative error code. + */ +static int snd_pdacf_assign_resources(struct snd_pdacf *pdacf, int port, int irq) +{ + int err; + struct snd_card *card = pdacf->card; + + snd_printdd(KERN_DEBUG "pdacf assign resources: port = 0x%x, irq = %d\n", port, irq); + pdacf->port = port; + pdacf->irq = irq; + pdacf->chip_status |= PDAUDIOCF_STAT_IS_CONFIGURED; + + err = snd_pdacf_ak4117_create(pdacf); + if (err < 0) + return err; + + strcpy(card->driver, "PDAudio-CF"); + sprintf(card->shortname, "Core Sound %s", card->driver); + sprintf(card->longname, "%s at 0x%x, irq %i", + card->shortname, port, irq); + + err = snd_pdacf_pcm_new(pdacf); + if (err < 0) + return err; + + if ((err = snd_card_register(card)) < 0) + return err; + + return 0; +} + + +/* + * snd_pdacf_detach - detach callback for cs + */ +static void snd_pdacf_detach(struct pcmcia_device *link) +{ + struct snd_pdacf *chip = link->priv; + + snd_printdd(KERN_DEBUG "pdacf_detach called\n"); + + if (chip->chip_status & PDAUDIOCF_STAT_IS_CONFIGURED) + snd_pdacf_powerdown(chip); + chip->chip_status |= PDAUDIOCF_STAT_IS_STALE; /* to be sure */ + snd_card_disconnect(chip->card); + snd_card_free_when_closed(chip->card); +} + +/* + * configuration callback + */ + +static int pdacf_config(struct pcmcia_device *link) +{ + struct snd_pdacf *pdacf = link->priv; + int ret; + + snd_printdd(KERN_DEBUG "pdacf_config called\n"); + link->config_index = 0x5; + link->config_flags |= CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ; + + ret = pcmcia_request_io(link); + if (ret) + goto failed_preirq; + + ret = request_threaded_irq(link->irq, pdacf_interrupt, + pdacf_threaded_irq, + IRQF_SHARED, link->devname, link->priv); + if (ret) + goto failed_preirq; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + if (snd_pdacf_assign_resources(pdacf, link->resource[0]->start, + link->irq) < 0) + goto failed; + + return 0; + + failed: + free_irq(link->irq, link->priv); +failed_preirq: + pcmcia_disable_device(link); + return -ENODEV; +} + +#ifdef CONFIG_PM + +static int pdacf_suspend(struct pcmcia_device *link) +{ + struct snd_pdacf *chip = link->priv; + + snd_printdd(KERN_DEBUG "SUSPEND\n"); + if (chip) { + snd_printdd(KERN_DEBUG "snd_pdacf_suspend calling\n"); + snd_pdacf_suspend(chip); + } + + return 0; +} + +static int pdacf_resume(struct pcmcia_device *link) +{ + struct snd_pdacf *chip = link->priv; + + snd_printdd(KERN_DEBUG "RESUME\n"); + if (pcmcia_dev_present(link)) { + if (chip) { + snd_printdd(KERN_DEBUG "calling snd_pdacf_resume\n"); + snd_pdacf_resume(chip); + } + } + snd_printdd(KERN_DEBUG "resume done!\n"); + + return 0; +} + +#endif + +/* + * Module entry points + */ +static const struct pcmcia_device_id snd_pdacf_ids[] = { + /* this is too general PCMCIA_DEVICE_MANF_CARD(0x015d, 0x4c45), */ + PCMCIA_DEVICE_PROD_ID12("Core Sound","PDAudio-CF",0x396d19d2,0x71717b49), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, snd_pdacf_ids); + +static struct pcmcia_driver pdacf_cs_driver = { + .owner = THIS_MODULE, + .name = "snd-pdaudiocf", + .probe = snd_pdacf_probe, + .remove = snd_pdacf_detach, + .id_table = snd_pdacf_ids, +#ifdef CONFIG_PM + .suspend = pdacf_suspend, + .resume = pdacf_resume, +#endif +}; +module_pcmcia_driver(pdacf_cs_driver); diff --git a/sound/pcmcia/pdaudiocf/pdaudiocf.h b/sound/pcmcia/pdaudiocf/pdaudiocf.h new file mode 100644 index 000000000..e9a7d3a78 --- /dev/null +++ b/sound/pcmcia/pdaudiocf/pdaudiocf.h @@ -0,0 +1,141 @@ +/* + * Driver for Sound Cors PDAudioCF soundcard + * + * Copyright (c) 2003 by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __PDAUDIOCF_H +#define __PDAUDIOCF_H + +#include +#include +#include +#include +#include + +#include + +/* PDAUDIOCF registers */ +#define PDAUDIOCF_REG_MD 0x00 /* music data, R/O */ +#define PDAUDIOCF_REG_WDP 0x02 /* write data pointer / 2, R/O */ +#define PDAUDIOCF_REG_RDP 0x04 /* read data pointer / 2, R/O */ +#define PDAUDIOCF_REG_TCR 0x06 /* test control register W/O */ +#define PDAUDIOCF_REG_SCR 0x08 /* status and control, R/W (see bit description) */ +#define PDAUDIOCF_REG_ISR 0x0a /* interrupt status, R/O */ +#define PDAUDIOCF_REG_IER 0x0c /* interrupt enable, R/W */ +#define PDAUDIOCF_REG_AK_IFR 0x0e /* AK interface register, R/W */ + +/* PDAUDIOCF_REG_TCR */ +#define PDAUDIOCF_ELIMAKMBIT (1<<0) /* simulate AKM music data */ +#define PDAUDIOCF_TESTDATASEL (1<<1) /* test data selection, 0 = 0x55, 1 = pseudo-random */ + +/* PDAUDIOCF_REG_SCR */ +#define PDAUDIOCF_AK_SBP (1<<0) /* serial port busy flag */ +#define PDAUDIOCF_RST (1<<2) /* FPGA, AKM + SRAM buffer reset */ +#define PDAUDIOCF_PDN (1<<3) /* power down bit */ +#define PDAUDIOCF_CLKDIV0 (1<<4) /* choose 24.576Mhz clock divided by 1,2,3 or 4 */ +#define PDAUDIOCF_CLKDIV1 (1<<5) +#define PDAUDIOCF_RECORD (1<<6) /* start capturing to SRAM */ +#define PDAUDIOCF_AK_SDD (1<<7) /* music data detected */ +#define PDAUDIOCF_RED_LED_OFF (1<<8) /* red LED off override */ +#define PDAUDIOCF_BLUE_LED_OFF (1<<9) /* blue LED off override */ +#define PDAUDIOCF_DATAFMT0 (1<<10) /* data format bits: 00 = 16-bit, 01 = 18-bit */ +#define PDAUDIOCF_DATAFMT1 (1<<11) /* 10 = 20-bit, 11 = 24-bit, all right justified */ +#define PDAUDIOCF_FPGAREV(x) ((x>>12)&0x0f) /* FPGA revision */ + +/* PDAUDIOCF_REG_ISR */ +#define PDAUDIOCF_IRQLVL (1<<0) /* Buffer level IRQ */ +#define PDAUDIOCF_IRQOVR (1<<1) /* Overrun IRQ */ +#define PDAUDIOCF_IRQAKM (1<<2) /* AKM IRQ */ + +/* PDAUDIOCF_REG_IER */ +#define PDAUDIOCF_IRQLVLEN0 (1<<0) /* fill threshold levels; 00 = none, 01 = 1/8th of buffer */ +#define PDAUDIOCF_IRQLVLEN1 (1<<1) /* 10 = 1/4th of buffer, 11 = 1/2th of buffer */ +#define PDAUDIOCF_IRQOVREN (1<<2) /* enable overrun IRQ */ +#define PDAUDIOCF_IRQAKMEN (1<<3) /* enable AKM IRQ */ +#define PDAUDIOCF_BLUEDUTY0 (1<<8) /* blue LED duty cycle; 00 = 100%, 01 = 50% */ +#define PDAUDIOCF_BLUEDUTY1 (1<<9) /* 02 = 25%, 11 = 12% */ +#define PDAUDIOCF_REDDUTY0 (1<<10) /* red LED duty cycle; 00 = 100%, 01 = 50% */ +#define PDAUDIOCF_REDDUTY1 (1<<11) /* 02 = 25%, 11 = 12% */ +#define PDAUDIOCF_BLUESDD (1<<12) /* blue LED against SDD bit */ +#define PDAUDIOCF_BLUEMODULATE (1<<13) /* save power when 100% duty cycle selected */ +#define PDAUDIOCF_REDMODULATE (1<<14) /* save power when 100% duty cycle selected */ +#define PDAUDIOCF_HALFRATE (1<<15) /* slow both LED blinks by half (also spdif detect rate) */ + +/* chip status */ +#define PDAUDIOCF_STAT_IS_STALE (1<<0) +#define PDAUDIOCF_STAT_IS_CONFIGURED (1<<1) +#define PDAUDIOCF_STAT_IS_SUSPENDED (1<<2) + +struct snd_pdacf { + struct snd_card *card; + int index; + + unsigned long port; + int irq; + + struct mutex reg_lock; + unsigned short regmap[8]; + unsigned short suspend_reg_scr; + + spinlock_t ak4117_lock; + struct ak4117 *ak4117; + + unsigned int chip_status; + + struct snd_pcm *pcm; + struct snd_pcm_substream *pcm_substream; + unsigned int pcm_running: 1; + unsigned int pcm_channels; + unsigned int pcm_swab; + unsigned int pcm_little; + unsigned int pcm_frame; + unsigned int pcm_sample; + unsigned int pcm_xor; + unsigned int pcm_size; + unsigned int pcm_period; + unsigned int pcm_tdone; + unsigned int pcm_hwptr; + void *pcm_area; + + /* pcmcia stuff */ + struct pcmcia_device *p_dev; +}; + +static inline void pdacf_reg_write(struct snd_pdacf *chip, unsigned char reg, unsigned short val) +{ + outw(chip->regmap[reg>>1] = val, chip->port + reg); +} + +static inline unsigned short pdacf_reg_read(struct snd_pdacf *chip, unsigned char reg) +{ + return inw(chip->port + reg); +} + +struct snd_pdacf *snd_pdacf_create(struct snd_card *card); +int snd_pdacf_ak4117_create(struct snd_pdacf *pdacf); +void snd_pdacf_powerdown(struct snd_pdacf *chip); +#ifdef CONFIG_PM +int snd_pdacf_suspend(struct snd_pdacf *chip); +int snd_pdacf_resume(struct snd_pdacf *chip); +#endif +int snd_pdacf_pcm_new(struct snd_pdacf *chip); +irqreturn_t pdacf_interrupt(int irq, void *dev); +irqreturn_t pdacf_threaded_irq(int irq, void *dev); +void pdacf_reinit(struct snd_pdacf *chip, int resume); + +#endif /* __PDAUDIOCF_H */ diff --git a/sound/pcmcia/pdaudiocf/pdaudiocf_core.c b/sound/pcmcia/pdaudiocf/pdaudiocf_core.c new file mode 100644 index 000000000..d724ab065 --- /dev/null +++ b/sound/pcmcia/pdaudiocf/pdaudiocf_core.c @@ -0,0 +1,296 @@ +/* + * Driver for Sound Core PDAudioCF soundcard + * + * Copyright (c) 2003 by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "pdaudiocf.h" +#include + +/* + * + */ +static unsigned char pdacf_ak4117_read(void *private_data, unsigned char reg) +{ + struct snd_pdacf *chip = private_data; + unsigned long timeout; + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&chip->ak4117_lock, flags); + timeout = 1000; + while (pdacf_reg_read(chip, PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) { + udelay(5); + if (--timeout == 0) { + spin_unlock_irqrestore(&chip->ak4117_lock, flags); + snd_printk(KERN_ERR "AK4117 ready timeout (read)\n"); + return 0; + } + } + pdacf_reg_write(chip, PDAUDIOCF_REG_AK_IFR, (u16)reg << 8); + timeout = 1000; + while (pdacf_reg_read(chip, PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) { + udelay(5); + if (--timeout == 0) { + spin_unlock_irqrestore(&chip->ak4117_lock, flags); + snd_printk(KERN_ERR "AK4117 read timeout (read2)\n"); + return 0; + } + } + res = (unsigned char)pdacf_reg_read(chip, PDAUDIOCF_REG_AK_IFR); + spin_unlock_irqrestore(&chip->ak4117_lock, flags); + return res; +} + +static void pdacf_ak4117_write(void *private_data, unsigned char reg, unsigned char val) +{ + struct snd_pdacf *chip = private_data; + unsigned long timeout; + unsigned long flags; + + spin_lock_irqsave(&chip->ak4117_lock, flags); + timeout = 1000; + while (inw(chip->port + PDAUDIOCF_REG_SCR) & PDAUDIOCF_AK_SBP) { + udelay(5); + if (--timeout == 0) { + spin_unlock_irqrestore(&chip->ak4117_lock, flags); + snd_printk(KERN_ERR "AK4117 ready timeout (write)\n"); + return; + } + } + outw((u16)reg << 8 | val | (1<<13), chip->port + PDAUDIOCF_REG_AK_IFR); + spin_unlock_irqrestore(&chip->ak4117_lock, flags); +} + +#if 0 +void pdacf_dump(struct snd_pdacf *chip) +{ + printk(KERN_DEBUG "PDAUDIOCF DUMP (0x%lx):\n", chip->port); + printk(KERN_DEBUG "WPD : 0x%x\n", + inw(chip->port + PDAUDIOCF_REG_WDP)); + printk(KERN_DEBUG "RDP : 0x%x\n", + inw(chip->port + PDAUDIOCF_REG_RDP)); + printk(KERN_DEBUG "TCR : 0x%x\n", + inw(chip->port + PDAUDIOCF_REG_TCR)); + printk(KERN_DEBUG "SCR : 0x%x\n", + inw(chip->port + PDAUDIOCF_REG_SCR)); + printk(KERN_DEBUG "ISR : 0x%x\n", + inw(chip->port + PDAUDIOCF_REG_ISR)); + printk(KERN_DEBUG "IER : 0x%x\n", + inw(chip->port + PDAUDIOCF_REG_IER)); + printk(KERN_DEBUG "AK_IFR : 0x%x\n", + inw(chip->port + PDAUDIOCF_REG_AK_IFR)); +} +#endif + +static int pdacf_reset(struct snd_pdacf *chip, int powerdown) +{ + u16 val; + + val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); + val |= PDAUDIOCF_PDN; + val &= ~PDAUDIOCF_RECORD; /* for sure */ + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); + udelay(5); + val |= PDAUDIOCF_RST; + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); + udelay(200); + val &= ~PDAUDIOCF_RST; + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); + udelay(5); + if (!powerdown) { + val &= ~PDAUDIOCF_PDN; + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); + udelay(200); + } + return 0; +} + +void pdacf_reinit(struct snd_pdacf *chip, int resume) +{ + pdacf_reset(chip, 0); + if (resume) + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, chip->suspend_reg_scr); + snd_ak4117_reinit(chip->ak4117); + pdacf_reg_write(chip, PDAUDIOCF_REG_TCR, chip->regmap[PDAUDIOCF_REG_TCR>>1]); + pdacf_reg_write(chip, PDAUDIOCF_REG_IER, chip->regmap[PDAUDIOCF_REG_IER>>1]); +} + +static void pdacf_proc_read(struct snd_info_entry * entry, + struct snd_info_buffer *buffer) +{ + struct snd_pdacf *chip = entry->private_data; + u16 tmp; + + snd_iprintf(buffer, "PDAudioCF\n\n"); + tmp = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); + snd_iprintf(buffer, "FPGA revision : 0x%x\n", PDAUDIOCF_FPGAREV(tmp)); + +} + +static void pdacf_proc_init(struct snd_pdacf *chip) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(chip->card, "pdaudiocf", &entry)) + snd_info_set_text_ops(entry, chip, pdacf_proc_read); +} + +struct snd_pdacf *snd_pdacf_create(struct snd_card *card) +{ + struct snd_pdacf *chip; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return NULL; + chip->card = card; + mutex_init(&chip->reg_lock); + spin_lock_init(&chip->ak4117_lock); + card->private_data = chip; + + pdacf_proc_init(chip); + return chip; +} + +static void snd_pdacf_ak4117_change(struct ak4117 *ak4117, unsigned char c0, unsigned char c1) +{ + struct snd_pdacf *chip = ak4117->change_callback_private; + u16 val; + + if (!(c0 & AK4117_UNLCK)) + return; + mutex_lock(&chip->reg_lock); + val = chip->regmap[PDAUDIOCF_REG_SCR>>1]; + if (ak4117->rcs0 & AK4117_UNLCK) + val |= PDAUDIOCF_BLUE_LED_OFF; + else + val &= ~PDAUDIOCF_BLUE_LED_OFF; + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); + mutex_unlock(&chip->reg_lock); +} + +int snd_pdacf_ak4117_create(struct snd_pdacf *chip) +{ + int err; + u16 val; + /* design note: if we unmask PLL unlock, parity, valid, audio or auto bit interrupts */ + /* from AK4117 then INT1 pin from AK4117 will be high all time, because PCMCIA interrupts are */ + /* egde based and FPGA does logical OR for all interrupt sources, we cannot use these */ + /* high-rate sources */ + static unsigned char pgm[5] = { + AK4117_XTL_24_576M | AK4117_EXCT, /* AK4117_REG_PWRDN */ + AK4117_CM_PLL_XTAL | AK4117_PKCS_128fs | AK4117_XCKS_128fs, /* AK4117_REQ_CLOCK */ + AK4117_EFH_1024LRCLK | AK4117_DIF_24R | AK4117_IPS, /* AK4117_REG_IO */ + 0xff, /* AK4117_REG_INT0_MASK */ + AK4117_MAUTO | AK4117_MAUD | AK4117_MULK | AK4117_MPAR | AK4117_MV, /* AK4117_REG_INT1_MASK */ + }; + + err = pdacf_reset(chip, 0); + if (err < 0) + return err; + err = snd_ak4117_create(chip->card, pdacf_ak4117_read, pdacf_ak4117_write, pgm, chip, &chip->ak4117); + if (err < 0) + return err; + + val = pdacf_reg_read(chip, PDAUDIOCF_REG_TCR); +#if 1 /* normal operation */ + val &= ~(PDAUDIOCF_ELIMAKMBIT|PDAUDIOCF_TESTDATASEL); +#else /* debug */ + val |= PDAUDIOCF_ELIMAKMBIT; + val &= ~PDAUDIOCF_TESTDATASEL; +#endif + pdacf_reg_write(chip, PDAUDIOCF_REG_TCR, val); + + /* setup the FPGA to match AK4117 setup */ + val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); + val &= ~(PDAUDIOCF_CLKDIV0 | PDAUDIOCF_CLKDIV1); /* use 24.576Mhz clock */ + val &= ~(PDAUDIOCF_RED_LED_OFF|PDAUDIOCF_BLUE_LED_OFF); + val |= PDAUDIOCF_DATAFMT0 | PDAUDIOCF_DATAFMT1; /* 24-bit data */ + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); + + /* setup LEDs and IRQ */ + val = pdacf_reg_read(chip, PDAUDIOCF_REG_IER); + val &= ~(PDAUDIOCF_IRQLVLEN0 | PDAUDIOCF_IRQLVLEN1); + val &= ~(PDAUDIOCF_BLUEDUTY0 | PDAUDIOCF_REDDUTY0 | PDAUDIOCF_REDDUTY1); + val |= PDAUDIOCF_BLUEDUTY1 | PDAUDIOCF_HALFRATE; + val |= PDAUDIOCF_IRQOVREN | PDAUDIOCF_IRQAKMEN; + pdacf_reg_write(chip, PDAUDIOCF_REG_IER, val); + + chip->ak4117->change_callback_private = chip; + chip->ak4117->change_callback = snd_pdacf_ak4117_change; + + /* update LED status */ + snd_pdacf_ak4117_change(chip->ak4117, AK4117_UNLCK, 0); + + return 0; +} + +void snd_pdacf_powerdown(struct snd_pdacf *chip) +{ + u16 val; + + val = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); + chip->suspend_reg_scr = val; + val |= PDAUDIOCF_RED_LED_OFF | PDAUDIOCF_BLUE_LED_OFF; + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, val); + /* disable interrupts, but use direct write to preserve old register value in chip->regmap */ + val = inw(chip->port + PDAUDIOCF_REG_IER); + val &= ~(PDAUDIOCF_IRQOVREN|PDAUDIOCF_IRQAKMEN|PDAUDIOCF_IRQLVLEN0|PDAUDIOCF_IRQLVLEN1); + outw(val, chip->port + PDAUDIOCF_REG_IER); + pdacf_reset(chip, 1); +} + +#ifdef CONFIG_PM + +int snd_pdacf_suspend(struct snd_pdacf *chip) +{ + u16 val; + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + snd_pcm_suspend_all(chip->pcm); + /* disable interrupts, but use direct write to preserve old register value in chip->regmap */ + val = inw(chip->port + PDAUDIOCF_REG_IER); + val &= ~(PDAUDIOCF_IRQOVREN|PDAUDIOCF_IRQAKMEN|PDAUDIOCF_IRQLVLEN0|PDAUDIOCF_IRQLVLEN1); + outw(val, chip->port + PDAUDIOCF_REG_IER); + chip->chip_status |= PDAUDIOCF_STAT_IS_SUSPENDED; /* ignore interrupts from now */ + snd_pdacf_powerdown(chip); + return 0; +} + +static inline int check_signal(struct snd_pdacf *chip) +{ + return (chip->ak4117->rcs0 & AK4117_UNLCK) == 0; +} + +int snd_pdacf_resume(struct snd_pdacf *chip) +{ + int timeout = 40; + + pdacf_reinit(chip, 1); + /* wait for AK4117's PLL */ + while (timeout-- > 0 && + (snd_ak4117_external_rate(chip->ak4117) <= 0 || !check_signal(chip))) + mdelay(1); + chip->chip_status &= ~PDAUDIOCF_STAT_IS_SUSPENDED; + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif diff --git a/sound/pcmcia/pdaudiocf/pdaudiocf_irq.c b/sound/pcmcia/pdaudiocf/pdaudiocf_irq.c new file mode 100644 index 000000000..ecf0fbd91 --- /dev/null +++ b/sound/pcmcia/pdaudiocf/pdaudiocf_irq.c @@ -0,0 +1,326 @@ +/* + * Driver for Sound Core PDAudioCF soundcard + * + * Copyright (c) 2003 by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include "pdaudiocf.h" +#include +#include + +/* + * + */ +irqreturn_t pdacf_interrupt(int irq, void *dev) +{ + struct snd_pdacf *chip = dev; + unsigned short stat; + bool wake_thread = false; + + if ((chip->chip_status & (PDAUDIOCF_STAT_IS_STALE| + PDAUDIOCF_STAT_IS_CONFIGURED| + PDAUDIOCF_STAT_IS_SUSPENDED)) != PDAUDIOCF_STAT_IS_CONFIGURED) + return IRQ_HANDLED; /* IRQ_NONE here? */ + + stat = inw(chip->port + PDAUDIOCF_REG_ISR); + if (stat & (PDAUDIOCF_IRQLVL|PDAUDIOCF_IRQOVR)) { + if (stat & PDAUDIOCF_IRQOVR) /* should never happen */ + snd_printk(KERN_ERR "PDAUDIOCF SRAM buffer overrun detected!\n"); + if (chip->pcm_substream) + wake_thread = true; + if (!(stat & PDAUDIOCF_IRQAKM)) + stat |= PDAUDIOCF_IRQAKM; /* check rate */ + } + if (get_irq_regs() != NULL) + snd_ak4117_check_rate_and_errors(chip->ak4117, 0); + return wake_thread ? IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +static inline void pdacf_transfer_mono16(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port) +{ + while (size-- > 0) { + *dst++ = inw(rdp_port) ^ xor; + inw(rdp_port); + } +} + +static inline void pdacf_transfer_mono32(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + inw(rdp_port); + *dst++ = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor; + } +} + +static inline void pdacf_transfer_stereo16(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port) +{ + while (size-- > 0) { + *dst++ = inw(rdp_port) ^ xor; + *dst++ = inw(rdp_port) ^ xor; + } +} + +static inline void pdacf_transfer_stereo32(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2, val3; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + val3 = inw(rdp_port); + *dst++ = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor; + *dst++ = (((u32)val3 << 16) | (val2 & 0xff00)) ^ xor; + } +} + +static inline void pdacf_transfer_mono16sw(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port) +{ + while (size-- > 0) { + *dst++ = swab16(inw(rdp_port) ^ xor); + inw(rdp_port); + } +} + +static inline void pdacf_transfer_mono32sw(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + inw(rdp_port); + *dst++ = swab32((((val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor); + } +} + +static inline void pdacf_transfer_stereo16sw(u16 *dst, u16 xor, unsigned int size, unsigned long rdp_port) +{ + while (size-- > 0) { + *dst++ = swab16(inw(rdp_port) ^ xor); + *dst++ = swab16(inw(rdp_port) ^ xor); + } +} + +static inline void pdacf_transfer_stereo32sw(u32 *dst, u32 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2, val3; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + val3 = inw(rdp_port); + *dst++ = swab32((((val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor); + *dst++ = swab32((((u32)val3 << 16) | (val2 & 0xff00)) ^ xor); + } +} + +static inline void pdacf_transfer_mono24le(u8 *dst, u16 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2; + register u32 xval1; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + inw(rdp_port); + xval1 = (((val2 & 0xff) << 8) | (val1 << 16)) ^ xor; + *dst++ = (u8)(xval1 >> 8); + *dst++ = (u8)(xval1 >> 16); + *dst++ = (u8)(xval1 >> 24); + } +} + +static inline void pdacf_transfer_mono24be(u8 *dst, u16 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2; + register u32 xval1; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + inw(rdp_port); + xval1 = (((val2 & 0xff) << 8) | (val1 << 16)) ^ xor; + *dst++ = (u8)(xval1 >> 24); + *dst++ = (u8)(xval1 >> 16); + *dst++ = (u8)(xval1 >> 8); + } +} + +static inline void pdacf_transfer_stereo24le(u8 *dst, u32 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2, val3; + register u32 xval1, xval2; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + val3 = inw(rdp_port); + xval1 = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor; + xval2 = (((u32)val3 << 16) | (val2 & 0xff00)) ^ xor; + *dst++ = (u8)(xval1 >> 8); + *dst++ = (u8)(xval1 >> 16); + *dst++ = (u8)(xval1 >> 24); + *dst++ = (u8)(xval2 >> 8); + *dst++ = (u8)(xval2 >> 16); + *dst++ = (u8)(xval2 >> 24); + } +} + +static inline void pdacf_transfer_stereo24be(u8 *dst, u32 xor, unsigned int size, unsigned long rdp_port) +{ + register u16 val1, val2, val3; + register u32 xval1, xval2; + + while (size-- > 0) { + val1 = inw(rdp_port); + val2 = inw(rdp_port); + val3 = inw(rdp_port); + xval1 = ((((u32)val2 & 0xff) << 24) | ((u32)val1 << 8)) ^ xor; + xval2 = (((u32)val3 << 16) | (val2 & 0xff00)) ^ xor; + *dst++ = (u8)(xval1 >> 24); + *dst++ = (u8)(xval1 >> 16); + *dst++ = (u8)(xval1 >> 8); + *dst++ = (u8)(xval2 >> 24); + *dst++ = (u8)(xval2 >> 16); + *dst++ = (u8)(xval2 >> 8); + } +} + +static void pdacf_transfer(struct snd_pdacf *chip, unsigned int size, unsigned int off) +{ + unsigned long rdp_port = chip->port + PDAUDIOCF_REG_MD; + unsigned int xor = chip->pcm_xor; + + if (chip->pcm_sample == 3) { + if (chip->pcm_little) { + if (chip->pcm_channels == 1) { + pdacf_transfer_mono24le((char *)chip->pcm_area + (off * 3), xor, size, rdp_port); + } else { + pdacf_transfer_stereo24le((char *)chip->pcm_area + (off * 6), xor, size, rdp_port); + } + } else { + if (chip->pcm_channels == 1) { + pdacf_transfer_mono24be((char *)chip->pcm_area + (off * 3), xor, size, rdp_port); + } else { + pdacf_transfer_stereo24be((char *)chip->pcm_area + (off * 6), xor, size, rdp_port); + } + } + return; + } + if (chip->pcm_swab == 0) { + if (chip->pcm_channels == 1) { + if (chip->pcm_frame == 2) { + pdacf_transfer_mono16((u16 *)chip->pcm_area + off, xor, size, rdp_port); + } else { + pdacf_transfer_mono32((u32 *)chip->pcm_area + off, xor, size, rdp_port); + } + } else { + if (chip->pcm_frame == 2) { + pdacf_transfer_stereo16((u16 *)chip->pcm_area + (off * 2), xor, size, rdp_port); + } else { + pdacf_transfer_stereo32((u32 *)chip->pcm_area + (off * 2), xor, size, rdp_port); + } + } + } else { + if (chip->pcm_channels == 1) { + if (chip->pcm_frame == 2) { + pdacf_transfer_mono16sw((u16 *)chip->pcm_area + off, xor, size, rdp_port); + } else { + pdacf_transfer_mono32sw((u32 *)chip->pcm_area + off, xor, size, rdp_port); + } + } else { + if (chip->pcm_frame == 2) { + pdacf_transfer_stereo16sw((u16 *)chip->pcm_area + (off * 2), xor, size, rdp_port); + } else { + pdacf_transfer_stereo32sw((u32 *)chip->pcm_area + (off * 2), xor, size, rdp_port); + } + } + } +} + +irqreturn_t pdacf_threaded_irq(int irq, void *dev) +{ + struct snd_pdacf *chip = dev; + int size, off, cont, rdp, wdp; + + if ((chip->chip_status & (PDAUDIOCF_STAT_IS_STALE|PDAUDIOCF_STAT_IS_CONFIGURED)) != PDAUDIOCF_STAT_IS_CONFIGURED) + return IRQ_HANDLED; + + if (chip->pcm_substream == NULL || chip->pcm_substream->runtime == NULL || !snd_pcm_running(chip->pcm_substream)) + return IRQ_HANDLED; + + rdp = inw(chip->port + PDAUDIOCF_REG_RDP); + wdp = inw(chip->port + PDAUDIOCF_REG_WDP); + /* printk(KERN_DEBUG "TASKLET: rdp = %x, wdp = %x\n", rdp, wdp); */ + size = wdp - rdp; + if (size < 0) + size += 0x10000; + if (size == 0) + size = 0x10000; + size /= chip->pcm_frame; + if (size > 64) + size -= 32; + +#if 0 + chip->pcm_hwptr += size; + chip->pcm_hwptr %= chip->pcm_size; + chip->pcm_tdone += size; + if (chip->pcm_frame == 2) { + unsigned long rdp_port = chip->port + PDAUDIOCF_REG_MD; + while (size-- > 0) { + inw(rdp_port); + inw(rdp_port); + } + } else { + unsigned long rdp_port = chip->port + PDAUDIOCF_REG_MD; + while (size-- > 0) { + inw(rdp_port); + inw(rdp_port); + inw(rdp_port); + } + } +#else + off = chip->pcm_hwptr + chip->pcm_tdone; + off %= chip->pcm_size; + chip->pcm_tdone += size; + while (size > 0) { + cont = chip->pcm_size - off; + if (cont > size) + cont = size; + pdacf_transfer(chip, cont, off); + off += cont; + off %= chip->pcm_size; + size -= cont; + } +#endif + mutex_lock(&chip->reg_lock); + while (chip->pcm_tdone >= chip->pcm_period) { + chip->pcm_hwptr += chip->pcm_period; + chip->pcm_hwptr %= chip->pcm_size; + chip->pcm_tdone -= chip->pcm_period; + mutex_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(chip->pcm_substream); + mutex_lock(&chip->reg_lock); + } + mutex_unlock(&chip->reg_lock); + return IRQ_HANDLED; +} diff --git a/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c b/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c new file mode 100644 index 000000000..98a6863e9 --- /dev/null +++ b/sound/pcmcia/pdaudiocf/pdaudiocf_pcm.c @@ -0,0 +1,307 @@ +/* + * Driver for Sound Core PDAudioCF soundcards + * + * PCM part + * + * Copyright (c) 2003 by Jaroslav Kysela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include "pdaudiocf.h" + + +/* + * clear the SRAM contents + */ +static int pdacf_pcm_clear_sram(struct snd_pdacf *chip) +{ + int max_loop = 64 * 1024; + + while (inw(chip->port + PDAUDIOCF_REG_RDP) != inw(chip->port + PDAUDIOCF_REG_WDP)) { + if (max_loop-- < 0) + return -EIO; + inw(chip->port + PDAUDIOCF_REG_MD); + } + return 0; +} + +/* + * pdacf_pcm_trigger - trigger callback for capture + */ +static int pdacf_pcm_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct snd_pdacf *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + int inc, ret = 0, rate; + unsigned short mask, val, tmp; + + if (chip->chip_status & PDAUDIOCF_STAT_IS_STALE) + return -EBUSY; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + chip->pcm_hwptr = 0; + chip->pcm_tdone = 0; + /* fall thru */ + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + mask = 0; + val = PDAUDIOCF_RECORD; + inc = 1; + rate = snd_ak4117_check_rate_and_errors(chip->ak4117, AK4117_CHECK_NO_STAT|AK4117_CHECK_NO_RATE); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + mask = PDAUDIOCF_RECORD; + val = 0; + inc = -1; + rate = 0; + break; + default: + return -EINVAL; + } + mutex_lock(&chip->reg_lock); + chip->pcm_running += inc; + tmp = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); + if (chip->pcm_running) { + if ((chip->ak4117->rcs0 & AK4117_UNLCK) || runtime->rate != rate) { + chip->pcm_running -= inc; + ret = -EIO; + goto __end; + } + } + tmp &= ~mask; + tmp |= val; + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, tmp); + __end: + mutex_unlock(&chip->reg_lock); + snd_ak4117_check_rate_and_errors(chip->ak4117, AK4117_CHECK_NO_RATE); + return ret; +} + +/* + * pdacf_pcm_hw_params - hw_params callback for playback and capture + */ +static int pdacf_pcm_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_32_buffer + (subs, params_buffer_bytes(hw_params)); +} + +/* + * pdacf_pcm_hw_free - hw_free callback for playback and capture + */ +static int pdacf_pcm_hw_free(struct snd_pcm_substream *subs) +{ + return snd_pcm_lib_free_vmalloc_buffer(subs); +} + +/* + * pdacf_pcm_prepare - prepare callback for playback and capture + */ +static int pdacf_pcm_prepare(struct snd_pcm_substream *subs) +{ + struct snd_pdacf *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + u16 val, nval, aval; + + if (chip->chip_status & PDAUDIOCF_STAT_IS_STALE) + return -EBUSY; + + chip->pcm_channels = runtime->channels; + + chip->pcm_little = snd_pcm_format_little_endian(runtime->format) > 0; +#ifdef SNDRV_LITTLE_ENDIAN + chip->pcm_swab = snd_pcm_format_big_endian(runtime->format) > 0; +#else + chip->pcm_swab = chip->pcm_little; +#endif + + if (snd_pcm_format_unsigned(runtime->format)) + chip->pcm_xor = 0x80008000; + + if (pdacf_pcm_clear_sram(chip) < 0) + return -EIO; + + val = nval = pdacf_reg_read(chip, PDAUDIOCF_REG_SCR); + nval &= ~(PDAUDIOCF_DATAFMT0|PDAUDIOCF_DATAFMT1); + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + break; + default: /* 24-bit */ + nval |= PDAUDIOCF_DATAFMT0 | PDAUDIOCF_DATAFMT1; + break; + } + aval = 0; + chip->pcm_sample = 4; + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + aval = AK4117_DIF_16R; + chip->pcm_frame = 2; + chip->pcm_sample = 2; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S24_3BE: + chip->pcm_sample = 3; + /* fall through */ + default: /* 24-bit */ + aval = AK4117_DIF_24R; + chip->pcm_frame = 3; + chip->pcm_xor &= 0xffff0000; + break; + } + + if (val != nval) { + snd_ak4117_reg_write(chip->ak4117, AK4117_REG_IO, AK4117_DIF2|AK4117_DIF1|AK4117_DIF0, aval); + pdacf_reg_write(chip, PDAUDIOCF_REG_SCR, nval); + } + + val = pdacf_reg_read(chip, PDAUDIOCF_REG_IER); + val &= ~(PDAUDIOCF_IRQLVLEN1); + val |= PDAUDIOCF_IRQLVLEN0; + pdacf_reg_write(chip, PDAUDIOCF_REG_IER, val); + + chip->pcm_size = runtime->buffer_size; + chip->pcm_period = runtime->period_size; + chip->pcm_area = runtime->dma_area; + + return 0; +} + + +/* + * capture hw information + */ + +static const struct snd_pcm_hardware pdacf_pcm_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | + 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_176400 | + SNDRV_PCM_RATE_192000, + .rate_min = 32000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (512*1024), + .period_bytes_min = 8*1024, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = 128, + .fifo_size = 0, +}; + + +/* + * pdacf_pcm_capture_open - open callback for capture + */ +static int pdacf_pcm_capture_open(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct snd_pdacf *chip = snd_pcm_substream_chip(subs); + + if (chip->chip_status & PDAUDIOCF_STAT_IS_STALE) + return -EBUSY; + + runtime->hw = pdacf_pcm_capture_hw; + runtime->private_data = chip; + chip->pcm_substream = subs; + + return 0; +} + +/* + * pdacf_pcm_capture_close - close callback for capture + */ +static int pdacf_pcm_capture_close(struct snd_pcm_substream *subs) +{ + struct snd_pdacf *chip = snd_pcm_substream_chip(subs); + + if (!chip) + return -EINVAL; + pdacf_reinit(chip, 0); + chip->pcm_substream = NULL; + return 0; +} + + +/* + * pdacf_pcm_capture_pointer - pointer callback for capture + */ +static snd_pcm_uframes_t pdacf_pcm_capture_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pdacf *chip = snd_pcm_substream_chip(subs); + return chip->pcm_hwptr; +} + +/* + * operators for PCM capture + */ +static const struct snd_pcm_ops pdacf_pcm_capture_ops = { + .open = pdacf_pcm_capture_open, + .close = pdacf_pcm_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pdacf_pcm_hw_params, + .hw_free = pdacf_pcm_hw_free, + .prepare = pdacf_pcm_prepare, + .trigger = pdacf_pcm_trigger, + .pointer = pdacf_pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + + +/* + * snd_pdacf_pcm_new - create and initialize a pcm + */ +int snd_pdacf_pcm_new(struct snd_pdacf *chip) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(chip->card, "PDAudioCF", 0, 0, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pdacf_pcm_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + pcm->nonatomic = true; + strcpy(pcm->name, chip->card->shortname); + chip->pcm = pcm; + + err = snd_ak4117_build(chip->ak4117, pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/pcmcia/vx/Makefile b/sound/pcmcia/vx/Makefile new file mode 100644 index 000000000..2bb42ea12 --- /dev/null +++ b/sound/pcmcia/vx/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-vxpocket-objs := vxpocket.o vxp_ops.o vxp_mixer.o + +obj-$(CONFIG_SND_VXPOCKET) += snd-vxpocket.o diff --git a/sound/pcmcia/vx/vxp_mixer.c b/sound/pcmcia/vx/vxp_mixer.c new file mode 100644 index 000000000..304b15300 --- /dev/null +++ b/sound/pcmcia/vx/vxp_mixer.c @@ -0,0 +1,151 @@ +/* + * Driver for Digigram VXpocket soundcards + * + * VX-pocket mixer + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include "vxpocket.h" + +#define MIC_LEVEL_MIN 0 +#define MIC_LEVEL_MAX 8 + +/* + * mic level control (for VXPocket) + */ +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_vxpocket *chip = to_vxpocket(_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_vxpocket *chip = to_vxpocket(_chip); + unsigned int val = ucontrol->value.integer.value[0]; + + if (val > MIC_LEVEL_MAX) + return -EINVAL; + mutex_lock(&_chip->mixer_mutex); + if (chip->mic_level != ucontrol->value.integer.value[0]) { + vx_set_mic_level(_chip, ucontrol->value.integer.value[0]); + chip->mic_level = ucontrol->value.integer.value[0]; + mutex_unlock(&_chip->mixer_mutex); + return 1; + } + mutex_unlock(&_chip->mixer_mutex); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_mic, -21, 3, 0); + +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 }, +}; + +/* + * mic boost level control (for VXP440) + */ +#define vx_mic_boost_info snd_ctl_boolean_mono_info + +static int vx_mic_boost_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *_chip = snd_kcontrol_chip(kcontrol); + struct snd_vxpocket *chip = to_vxpocket(_chip); + ucontrol->value.integer.value[0] = chip->mic_level; + return 0; +} + +static int vx_mic_boost_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *_chip = snd_kcontrol_chip(kcontrol); + struct snd_vxpocket *chip = to_vxpocket(_chip); + int val = !!ucontrol->value.integer.value[0]; + mutex_lock(&_chip->mixer_mutex); + if (chip->mic_level != val) { + vx_set_mic_boost(_chip, val); + chip->mic_level = val; + mutex_unlock(&_chip->mixer_mutex); + return 1; + } + mutex_unlock(&_chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_mic_boost = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost", + .info = vx_mic_boost_info, + .get = vx_mic_boost_get, + .put = vx_mic_boost_put, +}; + + +int vxp_add_mic_controls(struct vx_core *_chip) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + int err; + + /* mute input levels */ + chip->mic_level = 0; + switch (_chip->type) { + case VX_TYPE_VXPOCKET: + vx_set_mic_level(_chip, 0); + break; + case VX_TYPE_VXP440: + vx_set_mic_boost(_chip, 0); + break; + } + + /* mic level */ + switch (_chip->type) { + case VX_TYPE_VXPOCKET: + if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_mic_level, chip))) < 0) + return err; + break; + case VX_TYPE_VXP440: + if ((err = snd_ctl_add(_chip->card, snd_ctl_new1(&vx_control_mic_boost, chip))) < 0) + return err; + break; + } + + return 0; +} + diff --git a/sound/pcmcia/vx/vxp_ops.c b/sound/pcmcia/vx/vxp_ops.c new file mode 100644 index 000000000..4c4ef1fec --- /dev/null +++ b/sound/pcmcia/vx/vxp_ops.c @@ -0,0 +1,612 @@ +/* + * Driver for Digigram VXpocket soundcards + * + * lowlevel routines for VXpocket soundcards + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "vxpocket.h" + + +static int vxp_reg_offset[VX_REG_MAX] = { + [VX_ICR] = 0x00, // ICR + [VX_CVR] = 0x01, // CVR + [VX_ISR] = 0x02, // ISR + [VX_IVR] = 0x03, // IVR + [VX_RXH] = 0x05, // RXH + [VX_RXM] = 0x06, // RXM + [VX_RXL] = 0x07, // RXL + [VX_DMA] = 0x04, // DMA + [VX_CDSP] = 0x08, // CDSP + [VX_LOFREQ] = 0x09, // LFREQ + [VX_HIFREQ] = 0x0a, // HFREQ + [VX_DATA] = 0x0b, // DATA + [VX_MICRO] = 0x0c, // MICRO + [VX_DIALOG] = 0x0d, // DIALOG + [VX_CSUER] = 0x0e, // CSUER + [VX_RUER] = 0x0f, // RUER +}; + + +static inline unsigned long vxp_reg_addr(struct vx_core *_chip, int reg) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + return chip->port + vxp_reg_offset[reg]; +} + +/* + * snd_vx_inb - read a byte from the register + * @offset: register offset + */ +static unsigned char vxp_inb(struct vx_core *chip, int offset) +{ + return inb(vxp_reg_addr(chip, offset)); +} + +/* + * snd_vx_outb - write a byte on the register + * @offset: the register offset + * @val: the value to write + */ +static void vxp_outb(struct vx_core *chip, int offset, unsigned char val) +{ + outb(val, vxp_reg_addr(chip, offset)); +} + +/* + * redefine macros to call directly + */ +#undef vx_inb +#define vx_inb(chip,reg) vxp_inb((struct vx_core *)(chip), VX_##reg) +#undef vx_outb +#define vx_outb(chip,reg,val) vxp_outb((struct vx_core *)(chip), VX_##reg,val) + + +/* + * vx_check_magic - check the magic word on xilinx + * + * returns zero if a magic word is detected, or a negative error code. + */ +static int vx_check_magic(struct vx_core *chip) +{ + unsigned long end_time = jiffies + HZ / 5; + int c; + do { + c = vx_inb(chip, CDSP); + if (c == CDSP_MAGIC) + return 0; + msleep(10); + } while (time_after_eq(end_time, jiffies)); + snd_printk(KERN_ERR "cannot find xilinx magic word (%x)\n", c); + return -EIO; +} + + +/* + * vx_reset_dsp - reset the DSP + */ + +#define XX_DSP_RESET_WAIT_TIME 2 /* ms */ + +static void vxp_reset_dsp(struct vx_core *_chip) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + /* set the reset dsp bit to 1 */ + vx_outb(chip, CDSP, chip->regCDSP | VXP_CDSP_DSP_RESET_MASK); + vx_inb(chip, CDSP); + mdelay(XX_DSP_RESET_WAIT_TIME); + /* reset the bit */ + chip->regCDSP &= ~VXP_CDSP_DSP_RESET_MASK; + vx_outb(chip, CDSP, chip->regCDSP); + vx_inb(chip, CDSP); + mdelay(XX_DSP_RESET_WAIT_TIME); +} + +/* + * reset codec bit + */ +static void vxp_reset_codec(struct vx_core *_chip) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + /* Set the reset CODEC bit to 1. */ + vx_outb(chip, CDSP, chip->regCDSP | VXP_CDSP_CODEC_RESET_MASK); + vx_inb(chip, CDSP); + msleep(10); + /* Set the reset CODEC bit to 0. */ + chip->regCDSP &= ~VXP_CDSP_CODEC_RESET_MASK; + vx_outb(chip, CDSP, chip->regCDSP); + vx_inb(chip, CDSP); + msleep(1); +} + +/* + * vx_load_xilinx_binary - load the xilinx binary image + * the binary image is the binary array converted from the bitstream file. + */ +static int vxp_load_xilinx_binary(struct vx_core *_chip, const struct firmware *fw) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + unsigned int i; + int c; + int regCSUER, regRUER; + const unsigned char *image; + unsigned char data; + + /* Switch to programmation mode */ + chip->regDIALOG |= VXP_DLG_XILINX_REPROG_MASK; + vx_outb(chip, DIALOG, chip->regDIALOG); + + /* Save register CSUER and RUER */ + regCSUER = vx_inb(chip, CSUER); + regRUER = vx_inb(chip, RUER); + + /* reset HF0 and HF1 */ + vx_outb(chip, ICR, 0); + + /* Wait for answer HF2 equal to 1 */ + snd_printdd(KERN_DEBUG "check ISR_HF2\n"); + if (vx_check_isr(_chip, ISR_HF2, ISR_HF2, 20) < 0) + goto _error; + + /* set HF1 for loading xilinx binary */ + vx_outb(chip, ICR, ICR_HF1); + image = fw->data; + for (i = 0; i < fw->size; i++, image++) { + data = *image; + if (vx_wait_isr_bit(_chip, ISR_TX_EMPTY) < 0) + goto _error; + vx_outb(chip, TXL, data); + /* wait for reading */ + if (vx_wait_for_rx_full(_chip) < 0) + goto _error; + c = vx_inb(chip, RXL); + if (c != (int)data) + snd_printk(KERN_ERR "vxpocket: load xilinx mismatch at %d: 0x%x != 0x%x\n", i, c, (int)data); + } + + /* reset HF1 */ + vx_outb(chip, ICR, 0); + + /* wait for HF3 */ + if (vx_check_isr(_chip, ISR_HF3, ISR_HF3, 20) < 0) + goto _error; + + /* read the number of bytes received */ + if (vx_wait_for_rx_full(_chip) < 0) + goto _error; + + c = (int)vx_inb(chip, RXH) << 16; + c |= (int)vx_inb(chip, RXM) << 8; + c |= vx_inb(chip, RXL); + + snd_printdd(KERN_DEBUG "xilinx: dsp size received 0x%x, orig 0x%zx\n", c, fw->size); + + vx_outb(chip, ICR, ICR_HF0); + + /* TEMPO 250ms : wait until Xilinx is downloaded */ + msleep(300); + + /* test magical word */ + if (vx_check_magic(_chip) < 0) + goto _error; + + /* Restore register 0x0E and 0x0F (thus replacing COR and FCSR) */ + vx_outb(chip, CSUER, regCSUER); + vx_outb(chip, RUER, regRUER); + + /* Reset the Xilinx's signal enabling IO access */ + chip->regDIALOG |= VXP_DLG_XILINX_REPROG_MASK; + vx_outb(chip, DIALOG, chip->regDIALOG); + vx_inb(chip, DIALOG); + msleep(10); + chip->regDIALOG &= ~VXP_DLG_XILINX_REPROG_MASK; + vx_outb(chip, DIALOG, chip->regDIALOG); + vx_inb(chip, DIALOG); + + /* Reset of the Codec */ + vxp_reset_codec(_chip); + vx_reset_dsp(_chip); + + return 0; + + _error: + vx_outb(chip, CSUER, regCSUER); + vx_outb(chip, RUER, regRUER); + chip->regDIALOG &= ~VXP_DLG_XILINX_REPROG_MASK; + vx_outb(chip, DIALOG, chip->regDIALOG); + return -EIO; +} + + +/* + * vxp_load_dsp - load_dsp callback + */ +static int vxp_load_dsp(struct vx_core *vx, int index, const struct firmware *fw) +{ + int err; + + switch (index) { + case 0: + /* xilinx boot */ + if ((err = vx_check_magic(vx)) < 0) + return err; + if ((err = snd_vx_load_boot_image(vx, fw)) < 0) + return err; + return 0; + case 1: + /* xilinx image */ + return vxp_load_xilinx_binary(vx, fw); + case 2: + /* DSP boot */ + return snd_vx_dsp_boot(vx, fw); + case 3: + /* DSP image */ + return snd_vx_dsp_load(vx, fw); + default: + snd_BUG(); + return -EINVAL; + } +} + + +/* + * vx_test_and_ack - test and acknowledge interrupt + * + * called from irq hander, too + * + * spinlock held! + */ +static int vxp_test_and_ack(struct vx_core *_chip) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + /* not booted yet? */ + if (! (_chip->chip_status & VX_STAT_XILINX_LOADED)) + return -ENXIO; + + if (! (vx_inb(chip, DIALOG) & VXP_DLG_MEMIRQ_MASK)) + return -EIO; + + /* ok, interrupts generated, now ack it */ + /* set ACQUIT bit up and down */ + vx_outb(chip, DIALOG, chip->regDIALOG | VXP_DLG_ACK_MEMIRQ_MASK); + /* useless read just to spend some time and maintain + * the ACQUIT signal up for a while ( a bus cycle ) + */ + vx_inb(chip, DIALOG); + vx_outb(chip, DIALOG, chip->regDIALOG & ~VXP_DLG_ACK_MEMIRQ_MASK); + + return 0; +} + + +/* + * vx_validate_irq - enable/disable IRQ + */ +static void vxp_validate_irq(struct vx_core *_chip, int enable) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + /* Set the interrupt enable bit to 1 in CDSP register */ + if (enable) + chip->regCDSP |= VXP_CDSP_VALID_IRQ_MASK; + else + chip->regCDSP &= ~VXP_CDSP_VALID_IRQ_MASK; + vx_outb(chip, CDSP, chip->regCDSP); +} + +/* + * vx_setup_pseudo_dma - set up the pseudo dma read/write mode. + * @do_write: 0 = read, 1 = set up for DMA write + */ +static void vx_setup_pseudo_dma(struct vx_core *_chip, int do_write) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + /* Interrupt mode and HREQ pin enabled for host transmit / receive data transfers */ + vx_outb(chip, ICR, do_write ? ICR_TREQ : ICR_RREQ); + /* Reset the pseudo-dma register */ + vx_inb(chip, ISR); + vx_outb(chip, ISR, 0); + + /* Select DMA in read/write transfer mode and in 16-bit accesses */ + chip->regDIALOG |= VXP_DLG_DMA16_SEL_MASK; + chip->regDIALOG |= do_write ? VXP_DLG_DMAWRITE_SEL_MASK : VXP_DLG_DMAREAD_SEL_MASK; + vx_outb(chip, DIALOG, chip->regDIALOG); + +} + +/* + * vx_release_pseudo_dma - disable the pseudo-DMA mode + */ +static void vx_release_pseudo_dma(struct vx_core *_chip) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + /* Disable DMA and 16-bit accesses */ + chip->regDIALOG &= ~(VXP_DLG_DMAWRITE_SEL_MASK| + VXP_DLG_DMAREAD_SEL_MASK| + VXP_DLG_DMA16_SEL_MASK); + vx_outb(chip, DIALOG, chip->regDIALOG); + /* HREQ pin disabled. */ + vx_outb(chip, ICR, 0); +} + +/* + * vx_pseudo_dma_write - write bulk data on pseudo-DMA mode + * @count: data length to transfer in bytes + * + * data size must be aligned to 6 bytes to ensure the 24bit alignment on DSP. + * NB: call with a certain lock! + */ +static void vxp_dma_write(struct vx_core *chip, struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe, int count) +{ + long port = vxp_reg_addr(chip, VX_DMA); + int offset = pipe->hw_ptr; + unsigned short *addr = (unsigned short *)(runtime->dma_area + offset); + + vx_setup_pseudo_dma(chip, 1); + if (offset + count >= pipe->buffer_bytes) { + int length = pipe->buffer_bytes - offset; + count -= length; + length >>= 1; /* in 16bit words */ + /* Transfer using pseudo-dma. */ + for (; length > 0; length--) { + outw(*addr, port); + addr++; + } + addr = (unsigned short *)runtime->dma_area; + pipe->hw_ptr = 0; + } + pipe->hw_ptr += count; + count >>= 1; /* in 16bit words */ + /* Transfer using pseudo-dma. */ + for (; count > 0; count--) { + outw(*addr, port); + addr++; + } + vx_release_pseudo_dma(chip); +} + + +/* + * vx_pseudo_dma_read - read bulk data on pseudo DMA mode + * @offset: buffer offset in bytes + * @count: data length to transfer in bytes + * + * the read length must be aligned to 6 bytes, as well as write. + * NB: call with a certain lock! + */ +static void vxp_dma_read(struct vx_core *chip, struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe, int count) +{ + struct snd_vxpocket *pchip = to_vxpocket(chip); + long port = vxp_reg_addr(chip, VX_DMA); + int offset = pipe->hw_ptr; + unsigned short *addr = (unsigned short *)(runtime->dma_area + offset); + + if (snd_BUG_ON(count % 2)) + return; + vx_setup_pseudo_dma(chip, 0); + if (offset + count >= pipe->buffer_bytes) { + int length = pipe->buffer_bytes - offset; + count -= length; + length >>= 1; /* in 16bit words */ + /* Transfer using pseudo-dma. */ + for (; length > 0; length--) + *addr++ = inw(port); + addr = (unsigned short *)runtime->dma_area; + pipe->hw_ptr = 0; + } + pipe->hw_ptr += count; + count >>= 1; /* in 16bit words */ + /* Transfer using pseudo-dma. */ + for (; count > 1; count--) + *addr++ = inw(port); + /* Disable DMA */ + pchip->regDIALOG &= ~VXP_DLG_DMAREAD_SEL_MASK; + vx_outb(chip, DIALOG, pchip->regDIALOG); + /* Read the last word (16 bits) */ + *addr = inw(port); + /* Disable 16-bit accesses */ + pchip->regDIALOG &= ~VXP_DLG_DMA16_SEL_MASK; + vx_outb(chip, DIALOG, pchip->regDIALOG); + /* HREQ pin disabled. */ + vx_outb(chip, ICR, 0); +} + + +/* + * write a codec data (24bit) + */ +static void vxp_write_codec_reg(struct vx_core *chip, int codec, unsigned int data) +{ + int i; + + /* Activate access to the corresponding codec register */ + if (! codec) + vx_inb(chip, LOFREQ); + else + vx_inb(chip, CODEC2); + + /* We have to send 24 bits (3 x 8 bits). Start with most signif. Bit */ + for (i = 0; i < 24; i++, data <<= 1) + vx_outb(chip, DATA, ((data & 0x800000) ? VX_DATA_CODEC_MASK : 0)); + + /* Terminate access to codec registers */ + vx_inb(chip, HIFREQ); +} + + +/* + * vx_set_mic_boost - set mic boost level (on vxp440 only) + * @boost: 0 = 20dB, 1 = +38dB + */ +void vx_set_mic_boost(struct vx_core *chip, int boost) +{ + struct snd_vxpocket *pchip = to_vxpocket(chip); + + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + mutex_lock(&chip->lock); + if (pchip->regCDSP & P24_CDSP_MICS_SEL_MASK) { + if (boost) { + /* boost: 38 dB */ + pchip->regCDSP &= ~P24_CDSP_MIC20_SEL_MASK; + pchip->regCDSP |= P24_CDSP_MIC38_SEL_MASK; + } else { + /* minimum value: 20 dB */ + pchip->regCDSP |= P24_CDSP_MIC20_SEL_MASK; + pchip->regCDSP &= ~P24_CDSP_MIC38_SEL_MASK; + } + vx_outb(chip, CDSP, pchip->regCDSP); + } + mutex_unlock(&chip->lock); +} + +/* + * remap the linear value (0-8) to the actual value (0-15) + */ +static int vx_compute_mic_level(int level) +{ + switch (level) { + case 5: level = 6 ; break; + case 6: level = 8 ; break; + case 7: level = 11; break; + case 8: level = 15; break; + default: break ; + } + return level; +} + +/* + * vx_set_mic_level - set mic level (on vxpocket only) + * @level: the mic level = 0 - 8 (max) + */ +void vx_set_mic_level(struct vx_core *chip, int level) +{ + struct snd_vxpocket *pchip = to_vxpocket(chip); + + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + mutex_lock(&chip->lock); + if (pchip->regCDSP & VXP_CDSP_MIC_SEL_MASK) { + level = vx_compute_mic_level(level); + vx_outb(chip, MICRO, level); + } + mutex_unlock(&chip->lock); +} + + +/* + * change the input audio source + */ +static void vxp_change_audio_source(struct vx_core *_chip, int src) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + switch (src) { + case VX_AUDIO_SRC_DIGITAL: + chip->regCDSP |= VXP_CDSP_DATAIN_SEL_MASK; + vx_outb(chip, CDSP, chip->regCDSP); + break; + case VX_AUDIO_SRC_LINE: + chip->regCDSP &= ~VXP_CDSP_DATAIN_SEL_MASK; + if (_chip->type == VX_TYPE_VXP440) + chip->regCDSP &= ~P24_CDSP_MICS_SEL_MASK; + else + chip->regCDSP &= ~VXP_CDSP_MIC_SEL_MASK; + vx_outb(chip, CDSP, chip->regCDSP); + break; + case VX_AUDIO_SRC_MIC: + chip->regCDSP &= ~VXP_CDSP_DATAIN_SEL_MASK; + /* reset mic levels */ + if (_chip->type == VX_TYPE_VXP440) { + chip->regCDSP &= ~P24_CDSP_MICS_SEL_MASK; + if (chip->mic_level) + chip->regCDSP |= P24_CDSP_MIC38_SEL_MASK; + else + chip->regCDSP |= P24_CDSP_MIC20_SEL_MASK; + vx_outb(chip, CDSP, chip->regCDSP); + } else { + chip->regCDSP |= VXP_CDSP_MIC_SEL_MASK; + vx_outb(chip, CDSP, chip->regCDSP); + vx_outb(chip, MICRO, vx_compute_mic_level(chip->mic_level)); + } + break; + } +} + +/* + * change the clock source + * source = INTERNAL_QUARTZ or UER_SYNC + */ +static void vxp_set_clock_source(struct vx_core *_chip, int source) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + if (source == INTERNAL_QUARTZ) + chip->regCDSP &= ~VXP_CDSP_CLOCKIN_SEL_MASK; + else + chip->regCDSP |= VXP_CDSP_CLOCKIN_SEL_MASK; + vx_outb(chip, CDSP, chip->regCDSP); +} + + +/* + * reset the board + */ +static void vxp_reset_board(struct vx_core *_chip, int cold_reset) +{ + struct snd_vxpocket *chip = to_vxpocket(_chip); + + chip->regCDSP = 0; + chip->regDIALOG = 0; +} + + +/* + * callbacks + */ +/* exported */ +struct snd_vx_ops snd_vxpocket_ops = { + .in8 = vxp_inb, + .out8 = vxp_outb, + .test_and_ack = vxp_test_and_ack, + .validate_irq = vxp_validate_irq, + .write_codec = vxp_write_codec_reg, + .reset_codec = vxp_reset_codec, + .change_audio_source = vxp_change_audio_source, + .set_clock_source = vxp_set_clock_source, + .load_dsp = vxp_load_dsp, + .add_controls = vxp_add_mic_controls, + .reset_dsp = vxp_reset_dsp, + .reset_board = vxp_reset_board, + .dma_write = vxp_dma_write, + .dma_read = vxp_dma_read, +}; diff --git a/sound/pcmcia/vx/vxpocket.c b/sound/pcmcia/vx/vxpocket.c new file mode 100644 index 000000000..ca0d19e72 --- /dev/null +++ b/sound/pcmcia/vx/vxpocket.c @@ -0,0 +1,376 @@ +/* + * Driver for Digigram VXpocket V2/440 soundcards + * + * Copyright (c) 2002 by Takashi Iwai + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include "vxpocket.h" +#include +#include +#include +#include + +/* + */ + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("Digigram VXPocket"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Digigram,VXPocket},{Digigram,VXPocket440}}"); + +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 int ibl[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for VXPocket soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for VXPocket soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable VXPocket soundcard."); +module_param_array(ibl, int, NULL, 0444); +MODULE_PARM_DESC(ibl, "Capture IBL size for VXPocket soundcard."); + + +/* + */ + +static unsigned int card_alloc; + + +/* + */ +static void vxpocket_release(struct pcmcia_device *link) +{ + free_irq(link->irq, link->priv); + pcmcia_disable_device(link); +} + +/* + * destructor, called from snd_card_free_when_closed() + */ +static int snd_vxpocket_dev_free(struct snd_device *device) +{ + struct vx_core *chip = device->device_data; + + snd_vx_free_firmware(chip); + kfree(chip); + return 0; +} + + +/* + * Hardware information + */ + +/* VX-pocket V2 + * + * 1 DSP, 1 sync UER + * 1 programmable clock (NIY) + * 1 stereo analog input (line/micro) + * 1 stereo analog output + * Only output levels can be modified + */ + +static const DECLARE_TLV_DB_SCALE(db_scale_old_vol, -11350, 50, 0); + +static struct snd_vx_hardware vxpocket_hw = { + .name = "VXPocket", + .type = VX_TYPE_VXPOCKET, + + /* hardware 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, +}; + +/* VX-pocket 440 + * + * 1 DSP, 1 sync UER, 1 sync World Clock (NIY) + * SMPTE (NIY) + * 2 stereo analog input (line/micro) + * 2 stereo analog output + * Only output levels can be modified + * UER, but only for the first two inputs and outputs. + */ + +static struct snd_vx_hardware vxp440_hw = { + .name = "VXPocket440", + .type = VX_TYPE_VXP440, + + /* hardware specs */ + .num_codecs = 2, + .num_ins = 2, + .num_outs = 2, + .output_level_max = VX_ANALOG_OUT_LEVEL_MAX, + .output_level_db_scale = db_scale_old_vol, +}; + + +/* + * create vxpocket instance + */ +static int snd_vxpocket_new(struct snd_card *card, int ibl, + struct pcmcia_device *link, + struct snd_vxpocket **chip_ret) +{ + struct vx_core *chip; + struct snd_vxpocket *vxp; + static struct snd_device_ops ops = { + .dev_free = snd_vxpocket_dev_free, + }; + int err; + + chip = snd_vx_create(card, &vxpocket_hw, &snd_vxpocket_ops, + sizeof(struct snd_vxpocket) - sizeof(struct vx_core)); + if (!chip) + return -ENOMEM; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + kfree(chip); + return err; + } + chip->ibl.size = ibl; + + vxp = to_vxpocket(chip); + + vxp->p_dev = link; + link->priv = chip; + + link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + link->resource[0]->end = 16; + + link->config_flags |= CONF_ENABLE_IRQ; + link->config_index = 1; + link->config_regs = PRESENT_OPTION; + + *chip_ret = vxp; + return 0; +} + + +/** + * snd_vxpocket_assign_resources - initialize the hardware and card instance. + * @chip: VX core instance + * @port: i/o port for the card + * @irq: irq number for the card + * + * this function assigns the specified port and irq, boot the card, + * create pcm and control instances, and initialize the rest hardware. + * + * returns 0 if successful, or a negative error code. + */ +static int snd_vxpocket_assign_resources(struct vx_core *chip, int port, int irq) +{ + int err; + struct snd_card *card = chip->card; + struct snd_vxpocket *vxp = to_vxpocket(chip); + + snd_printdd(KERN_DEBUG "vxpocket assign resources: port = 0x%x, irq = %d\n", port, irq); + vxp->port = port; + + sprintf(card->shortname, "Digigram %s", card->driver); + sprintf(card->longname, "%s at 0x%x, irq %i", + card->shortname, port, irq); + + chip->irq = irq; + + if ((err = snd_vx_setup_firmware(chip)) < 0) + return err; + + return 0; +} + + +/* + * configuration callback + */ + +static int vxpocket_config(struct pcmcia_device *link) +{ + struct vx_core *chip = link->priv; + int ret; + + snd_printdd(KERN_DEBUG "vxpocket_config called\n"); + + /* redefine hardware record according to the VERSION1 string */ + if (!strcmp(link->prod_id[1], "VX-POCKET")) { + snd_printdd("VX-pocket is detected\n"); + } else { + snd_printdd("VX-pocket 440 is detected\n"); + /* overwrite the hardware information */ + chip->hw = &vxp440_hw; + chip->type = vxp440_hw.type; + strcpy(chip->card->driver, vxp440_hw.name); + } + + ret = pcmcia_request_io(link); + if (ret) + goto failed_preirq; + + ret = request_threaded_irq(link->irq, snd_vx_irq_handler, + snd_vx_threaded_irq_handler, + IRQF_SHARED, link->devname, link->priv); + if (ret) + goto failed_preirq; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + chip->dev = &link->dev; + + if (snd_vxpocket_assign_resources(chip, link->resource[0]->start, + link->irq) < 0) + goto failed; + + return 0; + + failed: + free_irq(link->irq, link->priv); +failed_preirq: + pcmcia_disable_device(link); + return -ENODEV; +} + +#ifdef CONFIG_PM + +static int vxp_suspend(struct pcmcia_device *link) +{ + struct vx_core *chip = link->priv; + + snd_printdd(KERN_DEBUG "SUSPEND\n"); + if (chip) { + snd_printdd(KERN_DEBUG "snd_vx_suspend calling\n"); + snd_vx_suspend(chip); + } + + return 0; +} + +static int vxp_resume(struct pcmcia_device *link) +{ + struct vx_core *chip = link->priv; + + snd_printdd(KERN_DEBUG "RESUME\n"); + if (pcmcia_dev_present(link)) { + //struct snd_vxpocket *vxp = (struct snd_vxpocket *)chip; + if (chip) { + snd_printdd(KERN_DEBUG "calling snd_vx_resume\n"); + snd_vx_resume(chip); + } + } + snd_printdd(KERN_DEBUG "resume done!\n"); + + return 0; +} + +#endif + + +/* + */ +static int vxpocket_probe(struct pcmcia_device *p_dev) +{ + struct snd_card *card; + struct snd_vxpocket *vxp; + int i, err; + + /* find an empty slot from the card list */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (!(card_alloc & (1 << i))) + break; + } + if (i >= SNDRV_CARDS) { + snd_printk(KERN_ERR "vxpocket: too many cards found\n"); + return -EINVAL; + } + if (! enable[i]) + return -ENODEV; /* disabled explicitly */ + + /* ok, create a card instance */ + err = snd_card_new(&p_dev->dev, index[i], id[i], THIS_MODULE, + 0, &card); + if (err < 0) { + snd_printk(KERN_ERR "vxpocket: cannot create a card instance\n"); + return err; + } + + err = snd_vxpocket_new(card, ibl[i], p_dev, &vxp); + if (err < 0) { + snd_card_free(card); + return err; + } + card->private_data = vxp; + + vxp->index = i; + card_alloc |= 1 << i; + + vxp->p_dev = p_dev; + + return vxpocket_config(p_dev); +} + +static void vxpocket_detach(struct pcmcia_device *link) +{ + struct snd_vxpocket *vxp; + struct vx_core *chip; + + if (! link) + return; + + vxp = link->priv; + chip = (struct vx_core *)vxp; + card_alloc &= ~(1 << vxp->index); + + chip->chip_status |= VX_STAT_IS_STALE; /* to be sure */ + snd_card_disconnect(chip->card); + vxpocket_release(link); + snd_card_free_when_closed(chip->card); +} + +/* + * Module entry points + */ + +static const struct pcmcia_device_id vxp_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x01f1, 0x0100), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, vxp_ids); + +static struct pcmcia_driver vxp_cs_driver = { + .owner = THIS_MODULE, + .name = "snd-vxpocket", + .probe = vxpocket_probe, + .remove = vxpocket_detach, + .id_table = vxp_ids, +#ifdef CONFIG_PM + .suspend = vxp_suspend, + .resume = vxp_resume, +#endif +}; +module_pcmcia_driver(vxp_cs_driver); diff --git a/sound/pcmcia/vx/vxpocket.h b/sound/pcmcia/vx/vxpocket.h new file mode 100644 index 000000000..26f4255e1 --- /dev/null +++ b/sound/pcmcia/vx/vxpocket.h @@ -0,0 +1,92 @@ +/* + * Driver for Digigram VXpocket soundcards + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __VXPOCKET_H +#define __VXPOCKET_H + +#include + +#include +#include + +struct snd_vxpocket { + + struct vx_core core; + + unsigned long port; + + int mic_level; /* analog mic level (or boost) */ + + unsigned int regCDSP; /* current CDSP register */ + unsigned int regDIALOG; /* current DIALOG register */ + + int index; /* card index */ + + /* pcmcia stuff */ + struct pcmcia_device *p_dev; +}; + +#define to_vxpocket(x) container_of(x, struct snd_vxpocket, core) + +extern struct snd_vx_ops snd_vxpocket_ops; + +void vx_set_mic_boost(struct vx_core *chip, int boost); +void vx_set_mic_level(struct vx_core *chip, int level); + +int vxp_add_mic_controls(struct vx_core *chip); + +/* Constants used to access the CDSP register (0x08). */ +#define CDSP_MAGIC 0xA7 /* magic value (for read) */ +/* for write */ +#define VXP_CDSP_CLOCKIN_SEL_MASK 0x80 /* 0 (internal), 1 (AES/EBU) */ +#define VXP_CDSP_DATAIN_SEL_MASK 0x40 /* 0 (analog), 1 (UER) */ +#define VXP_CDSP_SMPTE_SEL_MASK 0x20 +#define VXP_CDSP_RESERVED_MASK 0x10 +#define VXP_CDSP_MIC_SEL_MASK 0x08 +#define VXP_CDSP_VALID_IRQ_MASK 0x04 +#define VXP_CDSP_CODEC_RESET_MASK 0x02 +#define VXP_CDSP_DSP_RESET_MASK 0x01 +/* VXPOCKET 240/440 */ +#define P24_CDSP_MICS_SEL_MASK 0x18 +#define P24_CDSP_MIC20_SEL_MASK 0x10 +#define P24_CDSP_MIC38_SEL_MASK 0x08 + +/* Constants used to access the MEMIRQ register (0x0C). */ +#define P44_MEMIRQ_MASTER_SLAVE_SEL_MASK 0x08 +#define P44_MEMIRQ_SYNCED_ALONE_SEL_MASK 0x04 +#define P44_MEMIRQ_WCLK_OUT_IN_SEL_MASK 0x02 /* Not used */ +#define P44_MEMIRQ_WCLK_UER_SEL_MASK 0x01 /* Not used */ + +/* Micro levels (0x0C) */ + +/* Constants used to access the DIALOG register (0x0D). */ +#define VXP_DLG_XILINX_REPROG_MASK 0x80 /* W */ +#define VXP_DLG_DATA_XICOR_MASK 0x80 /* R */ +#define VXP_DLG_RESERVED4_0_MASK 0x40 +#define VXP_DLG_RESERVED2_0_MASK 0x20 +#define VXP_DLG_RESERVED1_0_MASK 0x10 +#define VXP_DLG_DMAWRITE_SEL_MASK 0x08 /* W */ +#define VXP_DLG_DMAREAD_SEL_MASK 0x04 /* W */ +#define VXP_DLG_MEMIRQ_MASK 0x02 /* R */ +#define VXP_DLG_DMA16_SEL_MASK 0x02 /* W */ +#define VXP_DLG_ACK_MEMIRQ_MASK 0x01 /* R/W */ + + +#endif /* __VXPOCKET_H */ diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig new file mode 100644 index 000000000..0519c60f5 --- /dev/null +++ b/sound/ppc/Kconfig @@ -0,0 +1,52 @@ +# ALSA PowerMac drivers + +menuconfig SND_PPC + bool "PowerPC sound devices" + depends on PPC + default y + help + Support for sound devices specific to PowerPC architectures. + +if SND_PPC + +config SND_POWERMAC + tristate "PowerMac (AWACS, DACA, Burgundy, Tumbler, Keywest)" + depends on I2C && INPUT && PPC_PMAC + select SND_PCM + select SND_VMASTER + help + Say Y here to include support for the integrated sound device. + + To compile this driver as a module, choose M here: the module + will be called snd-powermac. + +config SND_POWERMAC_AUTO_DRC + bool "Toggle DRC automatically at headphone/line plug-in" + depends on SND_POWERMAC + default y + help + Say Y here to enable the automatic toggle of DRC (dynamic + range compression) on Tumbler/Snapper. + If this feature is enabled, DRC is turned off when the + headphone/line jack is plugged, and turned on when unplugged. + + Note that you can turn on/off DRC manually even without this + option. + +config SND_PS3 + tristate "PS3 Audio support" + depends on PS3_PS3AV + select SND_PCM + default m + help + Say Y here to include support for audio on the PS3 + + To compile this driver as a module, choose M here: the module + will be called snd_ps3. + +config SND_PS3_DEFAULT_START_DELAY + int "Startup delay time in ms" + depends on SND_PS3 + default "2000" + +endif # SND_PPC diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile new file mode 100644 index 000000000..679c45a8d --- /dev/null +++ b/sound/ppc/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o +obj-$(CONFIG_SND_PS3) += snd_ps3.o diff --git a/sound/ppc/awacs.c b/sound/ppc/awacs.c new file mode 100644 index 000000000..d1e4ef1c5 --- /dev/null +++ b/sound/ppc/awacs.c @@ -0,0 +1,1147 @@ +/* + * PMac AWACS lowlevel functions + * + * Copyright (c) by Takashi Iwai + * code based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include "pmac.h" + + +#ifdef CONFIG_ADB_CUDA +#define PMAC_AMP_AVAIL +#endif + +#ifdef PMAC_AMP_AVAIL +struct awacs_amp { + unsigned char amp_master; + unsigned char amp_vol[2][2]; + unsigned char amp_tone[2]; +}; + +#define CHECK_CUDA_AMP() (sys_ctrler == SYS_CTRLER_CUDA) + +#endif /* PMAC_AMP_AVAIL */ + + +static void snd_pmac_screamer_wait(struct snd_pmac *chip) +{ + long timeout = 2000; + while (!(in_le32(&chip->awacs->codec_stat) & MASK_VALID)) { + mdelay(1); + if (! --timeout) { + snd_printd("snd_pmac_screamer_wait timeout\n"); + break; + } + } +} + +/* + * write AWACS register + */ +static void +snd_pmac_awacs_write(struct snd_pmac *chip, int val) +{ + long timeout = 5000000; + + if (chip->model == PMAC_SCREAMER) + snd_pmac_screamer_wait(chip); + out_le32(&chip->awacs->codec_ctrl, val | (chip->subframe << 22)); + while (in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) { + if (! --timeout) { + snd_printd("snd_pmac_awacs_write timeout\n"); + break; + } + } +} + +static void +snd_pmac_awacs_write_reg(struct snd_pmac *chip, int reg, int val) +{ + snd_pmac_awacs_write(chip, val | (reg << 12)); + chip->awacs_reg[reg] = val; +} + +static void +snd_pmac_awacs_write_noreg(struct snd_pmac *chip, int reg, int val) +{ + snd_pmac_awacs_write(chip, val | (reg << 12)); +} + +#ifdef CONFIG_PM +/* Recalibrate chip */ +static void screamer_recalibrate(struct snd_pmac *chip) +{ + if (chip->model != PMAC_SCREAMER) + return; + + /* Sorry for the horrible delays... I hope to get that improved + * by making the whole PM process asynchronous in a future version + */ + snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]); + if (chip->manufacturer == 0x1) + /* delay for broken crystal part */ + msleep(750); + snd_pmac_awacs_write_noreg(chip, 1, + chip->awacs_reg[1] | MASK_RECALIBRATE | + MASK_CMUTE | MASK_AMUTE); + snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]); + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); +} + +#else +#define screamer_recalibrate(chip) /* NOP */ +#endif + + +/* + * additional callback to set the pcm format + */ +static void snd_pmac_awacs_set_format(struct snd_pmac *chip) +{ + chip->awacs_reg[1] &= ~MASK_SAMPLERATE; + chip->awacs_reg[1] |= chip->rate_index << 3; + snd_pmac_awacs_write_reg(chip, 1, chip->awacs_reg[1]); +} + + +/* + * AWACS volume callbacks + */ +/* + * volumes: 0-15 stereo + */ +static int snd_pmac_awacs_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 = 15; + return 0; +} + +static int snd_pmac_awacs_get_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int lshift = (kcontrol->private_value >> 8) & 0xff; + int inverted = (kcontrol->private_value >> 16) & 1; + unsigned long flags; + int vol[2]; + + spin_lock_irqsave(&chip->reg_lock, flags); + vol[0] = (chip->awacs_reg[reg] >> lshift) & 0xf; + vol[1] = chip->awacs_reg[reg] & 0xf; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (inverted) { + vol[0] = 0x0f - vol[0]; + vol[1] = 0x0f - vol[1]; + } + ucontrol->value.integer.value[0] = vol[0]; + ucontrol->value.integer.value[1] = vol[1]; + return 0; +} + +static int snd_pmac_awacs_put_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int lshift = (kcontrol->private_value >> 8) & 0xff; + int inverted = (kcontrol->private_value >> 16) & 1; + int val, oldval; + unsigned long flags; + unsigned int vol[2]; + + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] > 0x0f || vol[1] > 0x0f) + return -EINVAL; + if (inverted) { + vol[0] = 0x0f - vol[0]; + vol[1] = 0x0f - vol[1]; + } + vol[0] &= 0x0f; + vol[1] &= 0x0f; + spin_lock_irqsave(&chip->reg_lock, flags); + oldval = chip->awacs_reg[reg]; + val = oldval & ~(0xf | (0xf << lshift)); + val |= vol[0] << lshift; + val |= vol[1]; + if (oldval != val) + snd_pmac_awacs_write_reg(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return oldval != reg; +} + + +#define AWACS_VOLUME(xname, xreg, xshift, xinverted) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = snd_pmac_awacs_info_volume, \ + .get = snd_pmac_awacs_get_volume, \ + .put = snd_pmac_awacs_put_volume, \ + .private_value = (xreg) | ((xshift) << 8) | ((xinverted) << 16) } + +/* + * mute master/ogain for AWACS: mono + */ +static int snd_pmac_awacs_get_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int invert = (kcontrol->private_value >> 16) & 1; + int val; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->awacs_reg[reg] >> shift) & 1; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + val = 1 - val; + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_pmac_awacs_put_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int invert = (kcontrol->private_value >> 16) & 1; + int mask = 1 << shift; + int val, changed; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + val = chip->awacs_reg[reg] & ~mask; + if (ucontrol->value.integer.value[0] != invert) + val |= mask; + changed = chip->awacs_reg[reg] != val; + if (changed) + snd_pmac_awacs_write_reg(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return changed; +} + +#define AWACS_SWITCH(xname, xreg, xshift, xinvert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = snd_pmac_boolean_mono_info, \ + .get = snd_pmac_awacs_get_switch, \ + .put = snd_pmac_awacs_put_switch, \ + .private_value = (xreg) | ((xshift) << 8) | ((xinvert) << 16) } + + +#ifdef PMAC_AMP_AVAIL +/* + * controls for perch/whisper extension cards, e.g. G3 desktop + * + * TDA7433 connected via i2c address 0x45 (= 0x8a), + * accessed through cuda + */ +static void awacs_set_cuda(int reg, int val) +{ + struct adb_request req; + cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, 0x8a, + reg, val); + while (! req.complete) + cuda_poll(); +} + +/* + * level = 0 - 14, 7 = 0 dB + */ +static void awacs_amp_set_tone(struct awacs_amp *amp, int bass, int treble) +{ + amp->amp_tone[0] = bass; + amp->amp_tone[1] = treble; + if (bass > 7) + bass = (14 - bass) + 8; + if (treble > 7) + treble = (14 - treble) + 8; + awacs_set_cuda(2, (bass << 4) | treble); +} + +/* + * vol = 0 - 31 (attenuation), 32 = mute bit, stereo + */ +static int awacs_amp_set_vol(struct awacs_amp *amp, int index, + int lvol, int rvol, int do_check) +{ + if (do_check && amp->amp_vol[index][0] == lvol && + amp->amp_vol[index][1] == rvol) + return 0; + awacs_set_cuda(3 + index, lvol); + awacs_set_cuda(5 + index, rvol); + amp->amp_vol[index][0] = lvol; + amp->amp_vol[index][1] = rvol; + return 1; +} + +/* + * 0 = -79 dB, 79 = 0 dB, 99 = +20 dB + */ +static void awacs_amp_set_master(struct awacs_amp *amp, int vol) +{ + amp->amp_master = vol; + if (vol <= 79) + vol = 32 + (79 - vol); + else + vol = 32 - (vol - 79); + awacs_set_cuda(1, vol); +} + +static void awacs_amp_free(struct snd_pmac *chip) +{ + struct awacs_amp *amp = chip->mixer_data; + if (!amp) + return; + kfree(amp); + chip->mixer_data = NULL; + chip->mixer_free = NULL; +} + + +/* + * mixer controls + */ +static int snd_pmac_awacs_info_volume_amp(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_pmac_awacs_get_volume_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + + ucontrol->value.integer.value[0] = 31 - (amp->amp_vol[index][0] & 31); + ucontrol->value.integer.value[1] = 31 - (amp->amp_vol[index][1] & 31); + return 0; +} + +static int snd_pmac_awacs_put_volume_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int vol[2]; + struct awacs_amp *amp = chip->mixer_data; + + vol[0] = (31 - (ucontrol->value.integer.value[0] & 31)) + | (amp->amp_vol[index][0] & 32); + vol[1] = (31 - (ucontrol->value.integer.value[1] & 31)) + | (amp->amp_vol[index][1] & 32); + return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1); +} + +static int snd_pmac_awacs_get_switch_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + + ucontrol->value.integer.value[0] = (amp->amp_vol[index][0] & 32) + ? 0 : 1; + ucontrol->value.integer.value[1] = (amp->amp_vol[index][1] & 32) + ? 0 : 1; + return 0; +} + +static int snd_pmac_awacs_put_switch_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + int vol[2]; + struct awacs_amp *amp = chip->mixer_data; + + vol[0] = (ucontrol->value.integer.value[0] ? 0 : 32) + | (amp->amp_vol[index][0] & 31); + vol[1] = (ucontrol->value.integer.value[1] ? 0 : 32) + | (amp->amp_vol[index][1] & 31); + return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1); +} + +static int snd_pmac_awacs_info_tone_amp(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 = 14; + return 0; +} + +static int snd_pmac_awacs_get_tone_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + + ucontrol->value.integer.value[0] = amp->amp_tone[index]; + return 0; +} + +static int snd_pmac_awacs_put_tone_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int index = kcontrol->private_value; + struct awacs_amp *amp = chip->mixer_data; + unsigned int val; + + val = ucontrol->value.integer.value[0]; + if (val > 14) + return -EINVAL; + if (val != amp->amp_tone[index]) { + amp->amp_tone[index] = val; + awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]); + return 1; + } + return 0; +} + +static int snd_pmac_awacs_info_master_amp(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 = 99; + return 0; +} + +static int snd_pmac_awacs_get_master_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct awacs_amp *amp = chip->mixer_data; + + ucontrol->value.integer.value[0] = amp->amp_master; + return 0; +} + +static int snd_pmac_awacs_put_master_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct awacs_amp *amp = chip->mixer_data; + unsigned int val; + + val = ucontrol->value.integer.value[0]; + if (val > 99) + return -EINVAL; + if (val != amp->amp_master) { + amp->amp_master = val; + awacs_amp_set_master(amp, amp->amp_master); + return 1; + } + return 0; +} + +#define AMP_CH_SPK 0 +#define AMP_CH_HD 1 + +static struct snd_kcontrol_new snd_pmac_awacs_amp_vol[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speaker Playback Volume", + .info = snd_pmac_awacs_info_volume_amp, + .get = snd_pmac_awacs_get_volume_amp, + .put = snd_pmac_awacs_put_volume_amp, + .private_value = AMP_CH_SPK, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .info = snd_pmac_awacs_info_volume_amp, + .get = snd_pmac_awacs_get_volume_amp, + .put = snd_pmac_awacs_put_volume_amp, + .private_value = AMP_CH_HD, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Tone Control - Bass", + .info = snd_pmac_awacs_info_tone_amp, + .get = snd_pmac_awacs_get_tone_amp, + .put = snd_pmac_awacs_put_tone_amp, + .private_value = 0, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Tone Control - Treble", + .info = snd_pmac_awacs_info_tone_amp, + .get = snd_pmac_awacs_get_tone_amp, + .put = snd_pmac_awacs_put_tone_amp, + .private_value = 1, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Amp Master Playback Volume", + .info = snd_pmac_awacs_info_master_amp, + .get = snd_pmac_awacs_get_master_amp, + .put = snd_pmac_awacs_put_master_amp, + }, +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_amp_hp_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = snd_pmac_awacs_get_switch_amp, + .put = snd_pmac_awacs_put_switch_amp, + .private_value = AMP_CH_HD, +}; + +static const struct snd_kcontrol_new snd_pmac_awacs_amp_spk_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speaker Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = snd_pmac_awacs_get_switch_amp, + .put = snd_pmac_awacs_put_switch_amp, + .private_value = AMP_CH_SPK, +}; + +#endif /* PMAC_AMP_AVAIL */ + + +/* + * mic boost for screamer + */ +static int snd_pmac_screamer_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; + uinfo->value.integer.max = 3; + return 0; +} + +static int snd_pmac_screamer_mic_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int val = 0; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->awacs_reg[6] & MASK_MIC_BOOST) + val |= 2; + if (chip->awacs_reg[0] & MASK_GAINLINE) + val |= 1; + spin_unlock_irqrestore(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_pmac_screamer_mic_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int val0, val6; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + val0 = chip->awacs_reg[0] & ~MASK_GAINLINE; + val6 = chip->awacs_reg[6] & ~MASK_MIC_BOOST; + if (ucontrol->value.integer.value[0] & 1) + val0 |= MASK_GAINLINE; + if (ucontrol->value.integer.value[0] & 2) + val6 |= MASK_MIC_BOOST; + if (val0 != chip->awacs_reg[0]) { + snd_pmac_awacs_write_reg(chip, 0, val0); + changed = 1; + } + if (val6 != chip->awacs_reg[6]) { + snd_pmac_awacs_write_reg(chip, 6, val6); + changed = 1; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return changed; +} + +/* + * lists of mixer elements + */ +static struct snd_kcontrol_new snd_pmac_awacs_mixers[] = { + AWACS_SWITCH("Master Capture Switch", 1, SHIFT_LOOPTHRU, 0), + AWACS_VOLUME("Master Capture Volume", 0, 4, 0), +/* AWACS_SWITCH("Unknown Playback Switch", 6, SHIFT_PAROUT0, 0), */ +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mixers_beige[] = { + AWACS_VOLUME("Master Playback Volume", 2, 6, 1), + AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1), + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_LINE, 0), +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mixers_lo[] = { + AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mixers_imac[] = { + AWACS_VOLUME("Play-through Playback Volume", 5, 6, 1), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mixers_g4agp[] = { + AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), + AWACS_VOLUME("Master Playback Volume", 5, 6, 1), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), +}; + +static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac7500[] = { + AWACS_VOLUME("Line out Playback Volume", 2, 6, 1), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), +}; + +static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac5500[] = { + AWACS_VOLUME("Headphone Playback Volume", 2, 6, 1), +}; + +static struct snd_kcontrol_new snd_pmac_awacs_mixers_pmac[] = { + AWACS_VOLUME("Master Playback Volume", 2, 6, 1), + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), +}; + +/* FIXME: is this correct order? + * screamer (powerbook G3 pismo) seems to have different bits... + */ +static struct snd_kcontrol_new snd_pmac_awacs_mixers2[] = { + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_LINE, 0), + AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_MIC, 0), +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mixers2[] = { + AWACS_SWITCH("Line Capture Switch", 0, SHIFT_MUX_MIC, 0), + AWACS_SWITCH("Mic Capture Switch", 0, SHIFT_MUX_LINE, 0), +}; + +static struct snd_kcontrol_new snd_pmac_awacs_mixers2_pmac5500[] = { + AWACS_SWITCH("CD Capture Switch", 0, SHIFT_MUX_CD, 0), +}; + +static struct snd_kcontrol_new snd_pmac_awacs_master_sw = +AWACS_SWITCH("Master Playback Switch", 1, SHIFT_HDMUTE, 1); + +static struct snd_kcontrol_new snd_pmac_awacs_master_sw_imac = +AWACS_SWITCH("Line out Playback Switch", 1, SHIFT_HDMUTE, 1); + +static struct snd_kcontrol_new snd_pmac_awacs_master_sw_pmac5500 = +AWACS_SWITCH("Headphone Playback Switch", 1, SHIFT_HDMUTE, 1); + +static struct snd_kcontrol_new snd_pmac_awacs_mic_boost[] = { + AWACS_SWITCH("Mic Boost Capture Switch", 0, SHIFT_GAINLINE, 0), +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mic_boost[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost Capture Volume", + .info = snd_pmac_screamer_mic_boost_info, + .get = snd_pmac_screamer_mic_boost_get, + .put = snd_pmac_screamer_mic_boost_put, + }, +}; + +static struct snd_kcontrol_new snd_pmac_awacs_mic_boost_pmac7500[] = +{ + AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_beige[] = +{ + AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), + AWACS_SWITCH("CD Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0), +}; + +static struct snd_kcontrol_new snd_pmac_screamer_mic_boost_imac[] = +{ + AWACS_SWITCH("Line Boost Capture Switch", 0, SHIFT_GAINLINE, 0), + AWACS_SWITCH("Mic Boost Capture Switch", 6, SHIFT_MIC_BOOST, 0), +}; + +static struct snd_kcontrol_new snd_pmac_awacs_speaker_vol[] = { + AWACS_VOLUME("Speaker Playback Volume", 4, 6, 1), +}; + +static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw = +AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_SPKMUTE, 1); + +static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac1 = +AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_PAROUT1, 1); + +static struct snd_kcontrol_new snd_pmac_awacs_speaker_sw_imac2 = +AWACS_SWITCH("Speaker Playback Switch", 1, SHIFT_PAROUT1, 0); + + +/* + * add new mixer elements to the card + */ +static int build_mixers(struct snd_pmac *chip, int nums, + struct snd_kcontrol_new *mixers) +{ + int i, err; + + for (i = 0; i < nums; i++) { + err = snd_ctl_add(chip->card, snd_ctl_new1(&mixers[i], chip)); + if (err < 0) + return err; + } + return 0; +} + + +/* + * restore all registers + */ +static void awacs_restore_all_regs(struct snd_pmac *chip) +{ + snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]); + snd_pmac_awacs_write_noreg(chip, 1, chip->awacs_reg[1]); + snd_pmac_awacs_write_noreg(chip, 2, chip->awacs_reg[2]); + snd_pmac_awacs_write_noreg(chip, 4, chip->awacs_reg[4]); + if (chip->model == PMAC_SCREAMER) { + snd_pmac_awacs_write_noreg(chip, 5, chip->awacs_reg[5]); + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); + snd_pmac_awacs_write_noreg(chip, 7, chip->awacs_reg[7]); + } +} + +#ifdef CONFIG_PM +static void snd_pmac_awacs_suspend(struct snd_pmac *chip) +{ + snd_pmac_awacs_write_noreg(chip, 1, (chip->awacs_reg[1] + | MASK_AMUTE | MASK_CMUTE)); +} + +static void snd_pmac_awacs_resume(struct snd_pmac *chip) +{ + if (of_machine_is_compatible("PowerBook3,1") + || of_machine_is_compatible("PowerBook3,2")) { + msleep(100); + snd_pmac_awacs_write_reg(chip, 1, + chip->awacs_reg[1] & ~MASK_PAROUT); + msleep(300); + } + + awacs_restore_all_regs(chip); + if (chip->model == PMAC_SCREAMER) { + /* reset power bits in reg 6 */ + mdelay(5); + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); + } + screamer_recalibrate(chip); +#ifdef PMAC_AMP_AVAIL + if (chip->mixer_data) { + struct awacs_amp *amp = chip->mixer_data; + awacs_amp_set_vol(amp, 0, + amp->amp_vol[0][0], amp->amp_vol[0][1], 0); + awacs_amp_set_vol(amp, 1, + amp->amp_vol[1][0], amp->amp_vol[1][1], 0); + awacs_amp_set_tone(amp, amp->amp_tone[0], amp->amp_tone[1]); + awacs_amp_set_master(amp, amp->amp_master); + } +#endif +} +#endif /* CONFIG_PM */ + +#define IS_PM7500 (of_machine_is_compatible("AAPL,7500") \ + || of_machine_is_compatible("AAPL,8500") \ + || of_machine_is_compatible("AAPL,9500")) +#define IS_PM5500 (of_machine_is_compatible("AAPL,e411")) +#define IS_BEIGE (of_machine_is_compatible("AAPL,Gossamer")) +#define IS_IMAC1 (of_machine_is_compatible("PowerMac2,1")) +#define IS_IMAC2 (of_machine_is_compatible("PowerMac2,2") \ + || of_machine_is_compatible("PowerMac4,1")) +#define IS_G4AGP (of_machine_is_compatible("PowerMac3,1")) +#define IS_LOMBARD (of_machine_is_compatible("PowerBook1,1")) + +static int imac1, imac2; + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int snd_pmac_awacs_detect_headphone(struct snd_pmac *chip) +{ + return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0; +} + +#ifdef PMAC_AMP_AVAIL +static int toggle_amp_mute(struct awacs_amp *amp, int index, int mute) +{ + int vol[2]; + vol[0] = amp->amp_vol[index][0] & 31; + vol[1] = amp->amp_vol[index][1] & 31; + if (mute) { + vol[0] |= 32; + vol[1] |= 32; + } + return awacs_amp_set_vol(amp, index, vol[0], vol[1], 1); +} +#endif + +static void snd_pmac_awacs_update_automute(struct snd_pmac *chip, int do_notify) +{ + if (chip->auto_mute) { +#ifdef PMAC_AMP_AVAIL + if (chip->mixer_data) { + struct awacs_amp *amp = chip->mixer_data; + int changed; + if (snd_pmac_awacs_detect_headphone(chip)) { + changed = toggle_amp_mute(amp, AMP_CH_HD, 0); + changed |= toggle_amp_mute(amp, AMP_CH_SPK, 1); + } else { + changed = toggle_amp_mute(amp, AMP_CH_HD, 1); + changed |= toggle_amp_mute(amp, AMP_CH_SPK, 0); + } + if (do_notify && ! changed) + return; + } else +#endif + { + int reg = chip->awacs_reg[1] + | (MASK_HDMUTE | MASK_SPKMUTE); + if (imac1) { + reg &= ~MASK_SPKMUTE; + reg |= MASK_PAROUT1; + } else if (imac2) { + reg &= ~MASK_SPKMUTE; + reg &= ~MASK_PAROUT1; + } + if (snd_pmac_awacs_detect_headphone(chip)) + reg &= ~MASK_HDMUTE; + else if (imac1) + reg &= ~MASK_PAROUT1; + else if (imac2) + reg |= MASK_PAROUT1; + else + reg &= ~MASK_SPKMUTE; + if (do_notify && reg == chip->awacs_reg[1]) + return; + snd_pmac_awacs_write_reg(chip, 1, reg); + } + if (do_notify) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->speaker_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hp_detect_ctl->id); + } + } +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + + +/* + * initialize chip + */ +int +snd_pmac_awacs_init(struct snd_pmac *chip) +{ + int pm7500 = IS_PM7500; + int pm5500 = IS_PM5500; + int beige = IS_BEIGE; + int g4agp = IS_G4AGP; + int lombard = IS_LOMBARD; + int imac; + int err, vol; + struct snd_kcontrol *vmaster_sw, *vmaster_vol; + struct snd_kcontrol *master_vol, *speaker_vol; + + imac1 = IS_IMAC1; + imac2 = IS_IMAC2; + imac = imac1 || imac2; + /* looks like MASK_GAINLINE triggers something, so we set here + * as start-up + */ + chip->awacs_reg[0] = MASK_MUX_CD | 0xff | MASK_GAINLINE; + chip->awacs_reg[1] = MASK_CMUTE | MASK_AMUTE; + /* FIXME: Only machines with external SRS module need MASK_PAROUT */ + if (chip->has_iic || chip->device_id == 0x5 || + /* chip->_device_id == 0x8 || */ + chip->device_id == 0xb) + chip->awacs_reg[1] |= MASK_PAROUT; + /* get default volume from nvram */ + // vol = (~nvram_read_byte(0x1308) & 7) << 1; + // vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 ); + vol = 0x0f; /* no, on alsa, muted as default */ + vol = vol + (vol << 6); + chip->awacs_reg[2] = vol; + chip->awacs_reg[4] = vol; + if (chip->model == PMAC_SCREAMER) { + /* FIXME: screamer has loopthru vol control */ + chip->awacs_reg[5] = vol; + /* FIXME: maybe should be vol << 3 for PCMCIA speaker */ + chip->awacs_reg[6] = MASK_MIC_BOOST; + chip->awacs_reg[7] = 0; + } + + awacs_restore_all_regs(chip); + chip->manufacturer = (in_le32(&chip->awacs->codec_stat) >> 8) & 0xf; + screamer_recalibrate(chip); + + chip->revision = (in_le32(&chip->awacs->codec_stat) >> 12) & 0xf; +#ifdef PMAC_AMP_AVAIL + if (chip->revision == 3 && chip->has_iic && CHECK_CUDA_AMP()) { + struct awacs_amp *amp = kzalloc(sizeof(*amp), GFP_KERNEL); + if (! amp) + return -ENOMEM; + chip->mixer_data = amp; + chip->mixer_free = awacs_amp_free; + /* mute and zero vol */ + awacs_amp_set_vol(amp, 0, 63, 63, 0); + awacs_amp_set_vol(amp, 1, 63, 63, 0); + awacs_amp_set_tone(amp, 7, 7); /* 0 dB */ + awacs_amp_set_master(amp, 79); /* 0 dB */ + } +#endif /* PMAC_AMP_AVAIL */ + + if (chip->hp_stat_mask == 0) { + /* set headphone-jack detection bit */ + switch (chip->model) { + case PMAC_AWACS: + chip->hp_stat_mask = pm7500 || pm5500 ? MASK_HDPCONN + : MASK_LOCONN; + break; + case PMAC_SCREAMER: + switch (chip->device_id) { + case 0x08: + case 0x0B: + chip->hp_stat_mask = imac + ? MASK_LOCONN_IMAC | + MASK_HDPLCONN_IMAC | + MASK_HDPRCONN_IMAC + : MASK_HDPCONN; + break; + case 0x00: + case 0x05: + chip->hp_stat_mask = MASK_LOCONN; + break; + default: + chip->hp_stat_mask = MASK_HDPCONN; + break; + } + break; + default: + snd_BUG(); + break; + } + } + + /* + * build mixers + */ + strcpy(chip->card->mixername, "PowerMac AWACS"); + + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers), + snd_pmac_awacs_mixers); + if (err < 0) + return err; + if (beige || g4agp) + ; + else if (chip->model == PMAC_SCREAMER || pm5500) + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_screamer_mixers2), + snd_pmac_screamer_mixers2); + else if (!pm7500) + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mixers2), + snd_pmac_awacs_mixers2); + if (err < 0) + return err; + if (pm5500) { + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mixers2_pmac5500), + snd_pmac_awacs_mixers2_pmac5500); + if (err < 0) + return err; + } + master_vol = NULL; + if (pm7500) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mixers_pmac7500), + snd_pmac_awacs_mixers_pmac7500); + else if (pm5500) + err = snd_ctl_add(chip->card, + (master_vol = snd_ctl_new1(snd_pmac_awacs_mixers_pmac5500, + chip))); + else if (beige) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mixers_beige), + snd_pmac_screamer_mixers_beige); + else if (imac || lombard) { + err = snd_ctl_add(chip->card, + (master_vol = snd_ctl_new1(snd_pmac_screamer_mixers_lo, + chip))); + if (err < 0) + return err; + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mixers_imac), + snd_pmac_screamer_mixers_imac); + } else if (g4agp) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mixers_g4agp), + snd_pmac_screamer_mixers_g4agp); + else + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mixers_pmac), + snd_pmac_awacs_mixers_pmac); + if (err < 0) + return err; + chip->master_sw_ctl = snd_ctl_new1((pm7500 || imac || g4agp || lombard) + ? &snd_pmac_awacs_master_sw_imac + : pm5500 + ? &snd_pmac_awacs_master_sw_pmac5500 + : &snd_pmac_awacs_master_sw, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; +#ifdef PMAC_AMP_AVAIL + if (chip->mixer_data) { + /* use amplifier. the signal is connected from route A + * to the amp. the amp has its headphone and speaker + * volumes and mute switches, so we use them instead of + * screamer registers. + * in this case, it seems the route C is not used. + */ + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_amp_vol), + snd_pmac_awacs_amp_vol); + if (err < 0) + return err; + /* overwrite */ + chip->master_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_hp_sw, + chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&snd_pmac_awacs_amp_spk_sw, + chip); + err = snd_ctl_add(chip->card, chip->speaker_sw_ctl); + if (err < 0) + return err; + } else +#endif /* PMAC_AMP_AVAIL */ + { + /* route A = headphone, route C = speaker */ + err = snd_ctl_add(chip->card, + (speaker_vol = snd_ctl_new1(snd_pmac_awacs_speaker_vol, + chip))); + if (err < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(imac1 + ? &snd_pmac_awacs_speaker_sw_imac1 + : imac2 + ? &snd_pmac_awacs_speaker_sw_imac2 + : &snd_pmac_awacs_speaker_sw, chip); + err = snd_ctl_add(chip->card, chip->speaker_sw_ctl); + if (err < 0) + return err; + } + + if (pm5500 || imac || lombard) { + vmaster_sw = snd_ctl_make_virtual_master( + "Master Playback Switch", (unsigned int *) NULL); + err = snd_ctl_add_slave_uncached(vmaster_sw, + chip->master_sw_ctl); + if (err < 0) + return err; + err = snd_ctl_add_slave_uncached(vmaster_sw, + chip->speaker_sw_ctl); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, vmaster_sw); + if (err < 0) + return err; + vmaster_vol = snd_ctl_make_virtual_master( + "Master Playback Volume", (unsigned int *) NULL); + err = snd_ctl_add_slave(vmaster_vol, master_vol); + if (err < 0) + return err; + err = snd_ctl_add_slave(vmaster_vol, speaker_vol); + if (err < 0) + return err; + err = snd_ctl_add(chip->card, vmaster_vol); + if (err < 0) + return err; + } + + if (beige || g4agp) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mic_boost_beige), + snd_pmac_screamer_mic_boost_beige); + else if (imac) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mic_boost_imac), + snd_pmac_screamer_mic_boost_imac); + else if (chip->model == PMAC_SCREAMER) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_screamer_mic_boost), + snd_pmac_screamer_mic_boost); + else if (pm7500) + err = build_mixers(chip, + ARRAY_SIZE(snd_pmac_awacs_mic_boost_pmac7500), + snd_pmac_awacs_mic_boost_pmac7500); + else + err = build_mixers(chip, ARRAY_SIZE(snd_pmac_awacs_mic_boost), + snd_pmac_awacs_mic_boost); + if (err < 0) + return err; + + /* + * set lowlevel callbacks + */ + chip->set_format = snd_pmac_awacs_set_format; +#ifdef CONFIG_PM + chip->suspend = snd_pmac_awacs_suspend; + chip->resume = snd_pmac_awacs_resume; +#endif +#ifdef PMAC_SUPPORT_AUTOMUTE + err = snd_pmac_add_automute(chip); + if (err < 0) + return err; + chip->detect_headphone = snd_pmac_awacs_detect_headphone; + chip->update_automute = snd_pmac_awacs_update_automute; + snd_pmac_awacs_update_automute(chip, 0); /* update the status only */ +#endif + if (chip->model == PMAC_SCREAMER) { + snd_pmac_awacs_write_noreg(chip, 6, chip->awacs_reg[6]); + snd_pmac_awacs_write_noreg(chip, 0, chip->awacs_reg[0]); + } + + return 0; +} diff --git a/sound/ppc/awacs.h b/sound/ppc/awacs.h new file mode 100644 index 000000000..c33e6a531 --- /dev/null +++ b/sound/ppc/awacs.h @@ -0,0 +1,205 @@ +/* + * Driver for PowerMac AWACS onboard soundchips + * Copyright (c) 2001 by Takashi Iwai + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef __AWACS_H +#define __AWACS_H + +/*******************************/ +/* AWACs Audio Register Layout */ +/*******************************/ + +struct awacs_regs { + unsigned control; /* Audio control register */ + unsigned pad0[3]; + unsigned codec_ctrl; /* Codec control register */ + unsigned pad1[3]; + unsigned codec_stat; /* Codec status register */ + unsigned pad2[3]; + unsigned clip_count; /* Clipping count register */ + unsigned pad3[3]; + unsigned byteswap; /* Data is little-endian if 1 */ +}; + +/*******************/ +/* Audio Bit Masks */ +/*******************/ + +/* Audio Control Reg Bit Masks */ +/* ----- ------- --- --- ----- */ +#define MASK_ISFSEL (0xf) /* Input SubFrame Select */ +#define MASK_OSFSEL (0xf << 4) /* Output SubFrame Select */ +#define MASK_RATE (0x7 << 8) /* Sound Rate */ +#define MASK_CNTLERR (0x1 << 11) /* Error */ +#define MASK_PORTCHG (0x1 << 12) /* Port Change */ +#define MASK_IEE (0x1 << 13) /* Enable Interrupt on Error */ +#define MASK_IEPC (0x1 << 14) /* Enable Interrupt on Port Change */ +#define MASK_SSFSEL (0x3 << 15) /* Status SubFrame Select */ + +/* Audio Codec Control Reg Bit Masks */ +/* ----- ----- ------- --- --- ----- */ +#define MASK_NEWECMD (0x1 << 24) /* Lock: don't write to reg when 1 */ +#define MASK_EMODESEL (0x3 << 22) /* Send info out on which frame? */ +#define MASK_EXMODEADDR (0x3ff << 12) /* Extended Mode Address -- 10 bits */ +#define MASK_EXMODEDATA (0xfff) /* Extended Mode Data -- 12 bits */ + +/* Audio Codec Control Address Values / Masks */ +/* ----- ----- ------- ------- ------ - ----- */ +#define MASK_ADDR0 (0x0 << 12) /* Expanded Data Mode Address 0 */ +#define MASK_ADDR_MUX MASK_ADDR0 /* Mux Control */ +#define MASK_ADDR_GAIN MASK_ADDR0 + +#define MASK_ADDR1 (0x1 << 12) /* Expanded Data Mode Address 1 */ +#define MASK_ADDR_MUTE MASK_ADDR1 +#define MASK_ADDR_RATE MASK_ADDR1 + +#define MASK_ADDR2 (0x2 << 12) /* Expanded Data Mode Address 2 */ +#define MASK_ADDR_VOLA MASK_ADDR2 /* Volume Control A -- Headphones */ +#define MASK_ADDR_VOLHD MASK_ADDR2 + +#define MASK_ADDR4 (0x4 << 12) /* Expanded Data Mode Address 4 */ +#define MASK_ADDR_VOLC MASK_ADDR4 /* Volume Control C -- Speaker */ +#define MASK_ADDR_VOLSPK MASK_ADDR4 + +/* additional registers of screamer */ +#define MASK_ADDR5 (0x5 << 12) /* Expanded Data Mode Address 5 */ +#define MASK_ADDR6 (0x6 << 12) /* Expanded Data Mode Address 6 */ +#define MASK_ADDR7 (0x7 << 12) /* Expanded Data Mode Address 7 */ + +/* Address 0 Bit Masks & Macros */ +/* ------- - --- ----- - ------ */ +#define MASK_GAINRIGHT (0xf) /* Gain Right Mask */ +#define MASK_GAINLEFT (0xf << 4) /* Gain Left Mask */ +#define MASK_GAINLINE (0x1 << 8) /* Disable Mic preamp */ +#define MASK_GAINMIC (0x0 << 8) /* Enable Mic preamp */ +#define MASK_MUX_CD (0x1 << 9) /* Select CD in MUX */ +#define MASK_MUX_MIC (0x1 << 10) /* Select Mic in MUX */ +#define MASK_MUX_AUDIN (0x1 << 11) /* Select Audio In in MUX */ +#define MASK_MUX_LINE MASK_MUX_AUDIN +#define SHIFT_GAINLINE 8 +#define SHIFT_MUX_CD 9 +#define SHIFT_MUX_MIC 10 +#define SHIFT_MUX_LINE 11 + +#define GAINRIGHT(x) ((x) & MASK_GAINRIGHT) +#define GAINLEFT(x) (((x) << 4) & MASK_GAINLEFT) + +/* Address 1 Bit Masks */ +/* ------- - --- ----- */ +#define MASK_ADDR1RES1 (0x3) /* Reserved */ +#define MASK_RECALIBRATE (0x1 << 2) /* Recalibrate */ +#define MASK_SAMPLERATE (0x7 << 3) /* Sample Rate: */ +#define MASK_LOOPTHRU (0x1 << 6) /* Loopthrough Enable */ +#define SHIFT_LOOPTHRU 6 +#define MASK_CMUTE (0x1 << 7) /* Output C (Speaker) Mute when 1 */ +#define MASK_SPKMUTE MASK_CMUTE +#define SHIFT_SPKMUTE 7 +#define MASK_ADDR1RES2 (0x1 << 8) /* Reserved */ +#define MASK_AMUTE (0x1 << 9) /* Output A (Headphone) Mute when 1 */ +#define MASK_HDMUTE MASK_AMUTE +#define SHIFT_HDMUTE 9 +#define MASK_PAROUT (0x3 << 10) /* Parallel Out (???) */ +#define MASK_PAROUT0 (0x1 << 10) /* Parallel Out (???) */ +#define MASK_PAROUT1 (0x1 << 11) /* Parallel Out (enable speaker) */ +#define SHIFT_PAROUT 10 +#define SHIFT_PAROUT0 10 +#define SHIFT_PAROUT1 11 + +#define SAMPLERATE_48000 (0x0 << 3) /* 48 or 44.1 kHz */ +#define SAMPLERATE_32000 (0x1 << 3) /* 32 or 29.4 kHz */ +#define SAMPLERATE_24000 (0x2 << 3) /* 24 or 22.05 kHz */ +#define SAMPLERATE_19200 (0x3 << 3) /* 19.2 or 17.64 kHz */ +#define SAMPLERATE_16000 (0x4 << 3) /* 16 or 14.7 kHz */ +#define SAMPLERATE_12000 (0x5 << 3) /* 12 or 11.025 kHz */ +#define SAMPLERATE_9600 (0x6 << 3) /* 9.6 or 8.82 kHz */ +#define SAMPLERATE_8000 (0x7 << 3) /* 8 or 7.35 kHz */ + +/* Address 2 & 4 Bit Masks & Macros */ +/* ------- - - - --- ----- - ------ */ +#define MASK_OUTVOLRIGHT (0xf) /* Output Right Volume */ +#define MASK_ADDR2RES1 (0x2 << 4) /* Reserved */ +#define MASK_ADDR4RES1 MASK_ADDR2RES1 +#define MASK_OUTVOLLEFT (0xf << 6) /* Output Left Volume */ +#define MASK_ADDR2RES2 (0x2 << 10) /* Reserved */ +#define MASK_ADDR4RES2 MASK_ADDR2RES2 + +#define VOLRIGHT(x) (((~(x)) & MASK_OUTVOLRIGHT)) +#define VOLLEFT(x) (((~(x)) << 6) & MASK_OUTVOLLEFT) + +/* address 6 */ +#define MASK_MIC_BOOST (0x4) /* screamer mic boost */ +#define SHIFT_MIC_BOOST 2 + +/* Audio Codec Status Reg Bit Masks */ +/* ----- ----- ------ --- --- ----- */ +#define MASK_EXTEND (0x1 << 23) /* Extend */ +#define MASK_VALID (0x1 << 22) /* Valid Data? */ +#define MASK_OFLEFT (0x1 << 21) /* Overflow Left */ +#define MASK_OFRIGHT (0x1 << 20) /* Overflow Right */ +#define MASK_ERRCODE (0xf << 16) /* Error Code */ +#define MASK_REVISION (0xf << 12) /* Revision Number */ +#define MASK_MFGID (0xf << 8) /* Mfg. ID */ +#define MASK_CODSTATRES (0xf << 4) /* bits 4 - 7 reserved */ +#define MASK_INSENSE (0xf) /* port sense bits: */ +#define MASK_HDPCONN 8 /* headphone plugged in */ +#define MASK_LOCONN 4 /* line-out plugged in */ +#define MASK_LICONN 2 /* line-in plugged in */ +#define MASK_MICCONN 1 /* microphone plugged in */ +#define MASK_LICONN_IMAC 8 /* line-in plugged in */ +#define MASK_HDPRCONN_IMAC 4 /* headphone right plugged in */ +#define MASK_HDPLCONN_IMAC 2 /* headphone left plugged in */ +#define MASK_LOCONN_IMAC 1 /* line-out plugged in */ + +/* Clipping Count Reg Bit Masks */ +/* -------- ----- --- --- ----- */ +#define MASK_CLIPLEFT (0xff << 7) /* Clipping Count, Left Channel */ +#define MASK_CLIPRIGHT (0xff) /* Clipping Count, Right Channel */ + +/* DBDMA ChannelStatus Bit Masks */ +/* ----- ------------- --- ----- */ +#define MASK_CSERR (0x1 << 7) /* Error */ +#define MASK_EOI (0x1 << 6) /* End of Input -- + only for Input Channel */ +#define MASK_CSUNUSED (0x1f << 1) /* bits 1-5 not used */ +#define MASK_WAIT (0x1) /* Wait */ + +/* Various Rates */ +/* ------- ----- */ +#define RATE_48000 (0x0 << 8) /* 48 kHz */ +#define RATE_44100 (0x0 << 8) /* 44.1 kHz */ +#define RATE_32000 (0x1 << 8) /* 32 kHz */ +#define RATE_29400 (0x1 << 8) /* 29.4 kHz */ +#define RATE_24000 (0x2 << 8) /* 24 kHz */ +#define RATE_22050 (0x2 << 8) /* 22.05 kHz */ +#define RATE_19200 (0x3 << 8) /* 19.2 kHz */ +#define RATE_17640 (0x3 << 8) /* 17.64 kHz */ +#define RATE_16000 (0x4 << 8) /* 16 kHz */ +#define RATE_14700 (0x4 << 8) /* 14.7 kHz */ +#define RATE_12000 (0x5 << 8) /* 12 kHz */ +#define RATE_11025 (0x5 << 8) /* 11.025 kHz */ +#define RATE_9600 (0x6 << 8) /* 9.6 kHz */ +#define RATE_8820 (0x6 << 8) /* 8.82 kHz */ +#define RATE_8000 (0x7 << 8) /* 8 kHz */ +#define RATE_7350 (0x7 << 8) /* 7.35 kHz */ + +#define RATE_LOW 1 /* HIGH = 48kHz, etc; LOW = 44.1kHz, etc. */ + + +#endif /* __AWACS_H */ diff --git a/sound/ppc/beep.c b/sound/ppc/beep.c new file mode 100644 index 000000000..f19eb3e39 --- /dev/null +++ b/sound/ppc/beep.c @@ -0,0 +1,285 @@ +/* + * Beep using pcm + * + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmac.h" + +struct pmac_beep { + int running; /* boolean */ + int volume; /* mixer volume: 0-100 */ + int volume_play; /* currently playing volume */ + int hz; + int nsamples; + short *buf; /* allocated wave buffer */ + dma_addr_t addr; /* physical address of buffer */ + struct input_dev *dev; +}; + +/* + * stop beep if running + */ +void snd_pmac_beep_stop(struct snd_pmac *chip) +{ + struct pmac_beep *beep = chip->beep; + if (beep && beep->running) { + beep->running = 0; + snd_pmac_beep_dma_stop(chip); + } +} + +/* + * Stuff for outputting a beep. The values range from -327 to +327 + * so we can multiply by an amplitude in the range 0..100 to get a + * signed short value to put in the output buffer. + */ +static short beep_wform[256] = { + 0, 40, 79, 117, 153, 187, 218, 245, + 269, 288, 304, 316, 323, 327, 327, 324, + 318, 310, 299, 288, 275, 262, 249, 236, + 224, 213, 204, 196, 190, 186, 183, 182, + 182, 183, 186, 189, 192, 196, 200, 203, + 206, 208, 209, 209, 209, 207, 204, 201, + 197, 193, 188, 183, 179, 174, 170, 166, + 163, 161, 160, 159, 159, 160, 161, 162, + 164, 166, 168, 169, 171, 171, 171, 170, + 169, 167, 163, 159, 155, 150, 144, 139, + 133, 128, 122, 117, 113, 110, 107, 105, + 103, 103, 103, 103, 104, 104, 105, 105, + 105, 103, 101, 97, 92, 86, 78, 68, + 58, 45, 32, 18, 3, -11, -26, -41, + -55, -68, -79, -88, -95, -100, -102, -102, + -99, -93, -85, -75, -62, -48, -33, -16, + 0, 16, 33, 48, 62, 75, 85, 93, + 99, 102, 102, 100, 95, 88, 79, 68, + 55, 41, 26, 11, -3, -18, -32, -45, + -58, -68, -78, -86, -92, -97, -101, -103, + -105, -105, -105, -104, -104, -103, -103, -103, + -103, -105, -107, -110, -113, -117, -122, -128, + -133, -139, -144, -150, -155, -159, -163, -167, + -169, -170, -171, -171, -171, -169, -168, -166, + -164, -162, -161, -160, -159, -159, -160, -161, + -163, -166, -170, -174, -179, -183, -188, -193, + -197, -201, -204, -207, -209, -209, -209, -208, + -206, -203, -200, -196, -192, -189, -186, -183, + -182, -182, -183, -186, -190, -196, -204, -213, + -224, -236, -249, -262, -275, -288, -299, -310, + -318, -324, -327, -327, -323, -316, -304, -288, + -269, -245, -218, -187, -153, -117, -79, -40, +}; + +#define BEEP_SRATE 22050 /* 22050 Hz sample rate */ +#define BEEP_BUFLEN 512 +#define BEEP_VOLUME 15 /* 0 - 100 */ + +static int snd_pmac_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + struct snd_pmac *chip; + struct pmac_beep *beep; + unsigned long flags; + int beep_speed = 0; + int srate; + int period, ncycles, nsamples; + int i, j, f; + short *p; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (hz) hz = 1000; + case SND_TONE: break; + default: return -1; + } + + chip = input_get_drvdata(dev); + if (! chip || (beep = chip->beep) == NULL) + return -1; + + if (! hz) { + spin_lock_irqsave(&chip->reg_lock, flags); + if (beep->running) + snd_pmac_beep_stop(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; + } + + beep_speed = snd_pmac_rate_index(chip, &chip->playback, BEEP_SRATE); + srate = chip->freq_table[beep_speed]; + + if (hz <= srate / BEEP_BUFLEN || hz > srate / 2) + hz = 1000; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->playback.running || chip->capture.running || beep->running) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; + } + beep->running = 1; + spin_unlock_irqrestore(&chip->reg_lock, flags); + + if (hz == beep->hz && beep->volume == beep->volume_play) { + nsamples = beep->nsamples; + } else { + period = srate * 256 / hz; /* fixed point */ + ncycles = BEEP_BUFLEN * 256 / period; + nsamples = (period * ncycles) >> 8; + f = ncycles * 65536 / nsamples; + j = 0; + p = beep->buf; + for (i = 0; i < nsamples; ++i, p += 2) { + p[0] = p[1] = beep_wform[j >> 8] * beep->volume; + j = (j + f) & 0xffff; + } + beep->hz = hz; + beep->volume_play = beep->volume; + beep->nsamples = nsamples; + } + + spin_lock_irqsave(&chip->reg_lock, flags); + snd_pmac_beep_dma_start(chip, beep->nsamples * 4, beep->addr, beep_speed); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +/* + * beep volume mixer + */ + +static int snd_pmac_info_beep(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 = 100; + return 0; +} + +static int snd_pmac_get_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + if (snd_BUG_ON(!chip->beep)) + return -ENXIO; + ucontrol->value.integer.value[0] = chip->beep->volume; + return 0; +} + +static int snd_pmac_put_beep(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int oval, nval; + if (snd_BUG_ON(!chip->beep)) + return -ENXIO; + oval = chip->beep->volume; + nval = ucontrol->value.integer.value[0]; + if (nval > 100) + return -EINVAL; + chip->beep->volume = nval; + return oval != chip->beep->volume; +} + +static const struct snd_kcontrol_new snd_pmac_beep_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Beep Playback Volume", + .info = snd_pmac_info_beep, + .get = snd_pmac_get_beep, + .put = snd_pmac_put_beep, +}; + +/* Initialize beep stuff */ +int snd_pmac_attach_beep(struct snd_pmac *chip) +{ + struct pmac_beep *beep; + struct input_dev *input_dev; + struct snd_kcontrol *beep_ctl; + void *dmabuf; + int err = -ENOMEM; + + beep = kzalloc(sizeof(*beep), GFP_KERNEL); + if (! beep) + return -ENOMEM; + dmabuf = dma_alloc_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4, + &beep->addr, GFP_KERNEL); + input_dev = input_allocate_device(); + if (! dmabuf || ! input_dev) + goto fail1; + + /* FIXME: set more better values */ + input_dev->name = "PowerMac Beep"; + input_dev->phys = "powermac/beep"; + input_dev->id.bustype = BUS_ADB; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = snd_pmac_beep_event; + input_dev->dev.parent = &chip->pdev->dev; + input_set_drvdata(input_dev, chip); + + beep->dev = input_dev; + beep->buf = dmabuf; + beep->volume = BEEP_VOLUME; + beep->running = 0; + + beep_ctl = snd_ctl_new1(&snd_pmac_beep_mixer, chip); + err = snd_ctl_add(chip->card, beep_ctl); + if (err < 0) + goto fail1; + + chip->beep = beep; + + err = input_register_device(beep->dev); + if (err) + goto fail2; + + return 0; + + fail2: snd_ctl_remove(chip->card, beep_ctl); + fail1: input_free_device(input_dev); + if (dmabuf) + dma_free_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4, + dmabuf, beep->addr); + kfree(beep); + return err; +} + +void snd_pmac_detach_beep(struct snd_pmac *chip) +{ + if (chip->beep) { + input_unregister_device(chip->beep->dev); + dma_free_coherent(&chip->pdev->dev, BEEP_BUFLEN * 4, + chip->beep->buf, chip->beep->addr); + kfree(chip->beep); + chip->beep = NULL; + } +} diff --git a/sound/ppc/burgundy.c b/sound/ppc/burgundy.c new file mode 100644 index 000000000..b86159e04 --- /dev/null +++ b/sound/ppc/burgundy.c @@ -0,0 +1,732 @@ +/* + * PMac Burgundy lowlevel functions + * + * Copyright (c) by Takashi Iwai + * code based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "pmac.h" +#include "burgundy.h" + + +/* Waits for busy flag to clear */ +static inline void +snd_pmac_burgundy_busy_wait(struct snd_pmac *chip) +{ + int timeout = 50; + while ((in_le32(&chip->awacs->codec_ctrl) & MASK_NEWECMD) && timeout--) + udelay(1); + if (timeout < 0) + printk(KERN_DEBUG "burgundy_busy_wait: timeout\n"); +} + +static inline void +snd_pmac_burgundy_extend_wait(struct snd_pmac *chip) +{ + int timeout; + timeout = 50; + while (!(in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--) + udelay(1); + if (timeout < 0) + printk(KERN_DEBUG "burgundy_extend_wait: timeout #1\n"); + timeout = 50; + while ((in_le32(&chip->awacs->codec_stat) & MASK_EXTEND) && timeout--) + udelay(1); + if (timeout < 0) + printk(KERN_DEBUG "burgundy_extend_wait: timeout #2\n"); +} + +static void +snd_pmac_burgundy_wcw(struct snd_pmac *chip, unsigned addr, unsigned val) +{ + out_le32(&chip->awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff)); + snd_pmac_burgundy_busy_wait(chip); + out_le32(&chip->awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff)); + snd_pmac_burgundy_busy_wait(chip); + out_le32(&chip->awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff)); + snd_pmac_burgundy_busy_wait(chip); + out_le32(&chip->awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff)); + snd_pmac_burgundy_busy_wait(chip); +} + +static unsigned +snd_pmac_burgundy_rcw(struct snd_pmac *chip, unsigned addr) +{ + unsigned val = 0; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100000); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff; + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100100); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<8; + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100200); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<16; + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100300); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += ((in_le32(&chip->awacs->codec_stat)>>4) & 0xff) <<24; + + spin_unlock_irqrestore(&chip->reg_lock, flags); + + return val; +} + +static void +snd_pmac_burgundy_wcb(struct snd_pmac *chip, unsigned int addr, + unsigned int val) +{ + out_le32(&chip->awacs->codec_ctrl, addr + 0x300000 + (val & 0xff)); + snd_pmac_burgundy_busy_wait(chip); +} + +static unsigned +snd_pmac_burgundy_rcb(struct snd_pmac *chip, unsigned int addr) +{ + unsigned val = 0; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + + out_le32(&chip->awacs->codec_ctrl, addr + 0x100000); + snd_pmac_burgundy_busy_wait(chip); + snd_pmac_burgundy_extend_wait(chip); + val += (in_le32(&chip->awacs->codec_stat) >> 4) & 0xff; + + spin_unlock_irqrestore(&chip->reg_lock, flags); + + return val; +} + +#define BASE2ADDR(base) ((base) << 12) +#define ADDR2BASE(addr) ((addr) >> 12) + +/* + * Burgundy volume: 0 - 100, stereo, word reg + */ +static void +snd_pmac_burgundy_write_volume(struct snd_pmac *chip, unsigned int address, + long *volume, int shift) +{ + int hardvolume, lvolume, rvolume; + + if (volume[0] < 0 || volume[0] > 100 || + volume[1] < 0 || volume[1] > 100) + return; /* -EINVAL */ + lvolume = volume[0] ? volume[0] + BURGUNDY_VOLUME_OFFSET : 0; + rvolume = volume[1] ? volume[1] + BURGUNDY_VOLUME_OFFSET : 0; + + hardvolume = lvolume + (rvolume << shift); + if (shift == 8) + hardvolume |= hardvolume << 16; + + snd_pmac_burgundy_wcw(chip, address, hardvolume); +} + +static void +snd_pmac_burgundy_read_volume(struct snd_pmac *chip, unsigned int address, + long *volume, int shift) +{ + int wvolume; + + wvolume = snd_pmac_burgundy_rcw(chip, address); + + volume[0] = wvolume & 0xff; + if (volume[0] >= BURGUNDY_VOLUME_OFFSET) + volume[0] -= BURGUNDY_VOLUME_OFFSET; + else + volume[0] = 0; + volume[1] = (wvolume >> shift) & 0xff; + if (volume[1] >= BURGUNDY_VOLUME_OFFSET) + volume[1] -= BURGUNDY_VOLUME_OFFSET; + else + volume[1] = 0; +} + +static int snd_pmac_burgundy_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 = 100; + return 0; +} + +static int snd_pmac_burgundy_get_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int shift = (kcontrol->private_value >> 8) & 0xff; + snd_pmac_burgundy_read_volume(chip, addr, + ucontrol->value.integer.value, shift); + return 0; +} + +static int snd_pmac_burgundy_put_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int shift = (kcontrol->private_value >> 8) & 0xff; + long nvoices[2]; + + snd_pmac_burgundy_write_volume(chip, addr, + ucontrol->value.integer.value, shift); + snd_pmac_burgundy_read_volume(chip, addr, nvoices, shift); + return (nvoices[0] != ucontrol->value.integer.value[0] || + nvoices[1] != ucontrol->value.integer.value[1]); +} + +#define BURGUNDY_VOLUME_W(xname, xindex, addr, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_volume,\ + .get = snd_pmac_burgundy_get_volume,\ + .put = snd_pmac_burgundy_put_volume,\ + .private_value = ((ADDR2BASE(addr) & 0xff) | ((shift) << 8)) } + +/* + * Burgundy volume: 0 - 100, stereo, 2-byte reg + */ +static void +snd_pmac_burgundy_write_volume_2b(struct snd_pmac *chip, unsigned int address, + long *volume, int off) +{ + int lvolume, rvolume; + + off |= off << 2; + lvolume = volume[0] ? volume[0] + BURGUNDY_VOLUME_OFFSET : 0; + rvolume = volume[1] ? volume[1] + BURGUNDY_VOLUME_OFFSET : 0; + + snd_pmac_burgundy_wcb(chip, address + off, lvolume); + snd_pmac_burgundy_wcb(chip, address + off + 0x500, rvolume); +} + +static void +snd_pmac_burgundy_read_volume_2b(struct snd_pmac *chip, unsigned int address, + long *volume, int off) +{ + volume[0] = snd_pmac_burgundy_rcb(chip, address + off); + if (volume[0] >= BURGUNDY_VOLUME_OFFSET) + volume[0] -= BURGUNDY_VOLUME_OFFSET; + else + volume[0] = 0; + volume[1] = snd_pmac_burgundy_rcb(chip, address + off + 0x100); + if (volume[1] >= BURGUNDY_VOLUME_OFFSET) + volume[1] -= BURGUNDY_VOLUME_OFFSET; + else + volume[1] = 0; +} + +static int snd_pmac_burgundy_info_volume_2b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int snd_pmac_burgundy_get_volume_2b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int off = kcontrol->private_value & 0x300; + snd_pmac_burgundy_read_volume_2b(chip, addr, + ucontrol->value.integer.value, off); + return 0; +} + +static int snd_pmac_burgundy_put_volume_2b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int off = kcontrol->private_value & 0x300; + long nvoices[2]; + + snd_pmac_burgundy_write_volume_2b(chip, addr, + ucontrol->value.integer.value, off); + snd_pmac_burgundy_read_volume_2b(chip, addr, nvoices, off); + return (nvoices[0] != ucontrol->value.integer.value[0] || + nvoices[1] != ucontrol->value.integer.value[1]); +} + +#define BURGUNDY_VOLUME_2B(xname, xindex, addr, off) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_volume_2b,\ + .get = snd_pmac_burgundy_get_volume_2b,\ + .put = snd_pmac_burgundy_put_volume_2b,\ + .private_value = ((ADDR2BASE(addr) & 0xff) | ((off) << 8)) } + +/* + * Burgundy gain/attenuation: 0 - 15, mono/stereo, byte reg + */ +static int snd_pmac_burgundy_info_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int stereo = (kcontrol->private_value >> 24) & 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 15; + return 0; +} + +static int snd_pmac_burgundy_get_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int atten = (kcontrol->private_value >> 25) & 1; + int oval; + + oval = snd_pmac_burgundy_rcb(chip, addr); + if (atten) + oval = ~oval & 0xff; + ucontrol->value.integer.value[0] = oval & 0xf; + if (stereo) + ucontrol->value.integer.value[1] = (oval >> 4) & 0xf; + return 0; +} + +static int snd_pmac_burgundy_put_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR(kcontrol->private_value & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int atten = (kcontrol->private_value >> 25) & 1; + int oval, val; + + oval = snd_pmac_burgundy_rcb(chip, addr); + if (atten) + oval = ~oval & 0xff; + val = ucontrol->value.integer.value[0]; + if (stereo) + val |= ucontrol->value.integer.value[1] << 4; + else + val |= ucontrol->value.integer.value[0] << 4; + if (atten) + val = ~val & 0xff; + snd_pmac_burgundy_wcb(chip, addr, val); + return val != oval; +} + +#define BURGUNDY_VOLUME_B(xname, xindex, addr, stereo, atten) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_gain,\ + .get = snd_pmac_burgundy_get_gain,\ + .put = snd_pmac_burgundy_put_gain,\ + .private_value = (ADDR2BASE(addr) | ((stereo) << 24) | ((atten) << 25)) } + +/* + * Burgundy switch: 0/1, mono/stereo, word reg + */ +static int snd_pmac_burgundy_info_switch_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int stereo = (kcontrol->private_value >> 24) & 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_pmac_burgundy_get_switch_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + int lmask = 1 << (kcontrol->private_value & 0xff); + int rmask = 1 << ((kcontrol->private_value >> 8) & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int val = snd_pmac_burgundy_rcw(chip, addr); + ucontrol->value.integer.value[0] = (val & lmask) ? 1 : 0; + if (stereo) + ucontrol->value.integer.value[1] = (val & rmask) ? 1 : 0; + return 0; +} + +static int snd_pmac_burgundy_put_switch_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + int lmask = 1 << (kcontrol->private_value & 0xff); + int rmask = 1 << ((kcontrol->private_value >> 8) & 0xff); + int stereo = (kcontrol->private_value >> 24) & 1; + int val, oval; + oval = snd_pmac_burgundy_rcw(chip, addr); + val = oval & ~(lmask | (stereo ? rmask : 0)); + if (ucontrol->value.integer.value[0]) + val |= lmask; + if (stereo && ucontrol->value.integer.value[1]) + val |= rmask; + snd_pmac_burgundy_wcw(chip, addr, val); + return val != oval; +} + +#define BURGUNDY_SWITCH_W(xname, xindex, addr, lbit, rbit, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_switch_w,\ + .get = snd_pmac_burgundy_get_switch_w,\ + .put = snd_pmac_burgundy_put_switch_w,\ + .private_value = ((lbit) | ((rbit) << 8)\ + | (ADDR2BASE(addr) << 16) | ((stereo) << 24)) } + +/* + * Burgundy switch: 0/1, mono/stereo, byte reg, bit mask + */ +static int snd_pmac_burgundy_info_switch_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int stereo = (kcontrol->private_value >> 24) & 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = stereo + 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_pmac_burgundy_get_switch_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + int lmask = kcontrol->private_value & 0xff; + int rmask = (kcontrol->private_value >> 8) & 0xff; + int stereo = (kcontrol->private_value >> 24) & 1; + int val = snd_pmac_burgundy_rcb(chip, addr); + ucontrol->value.integer.value[0] = (val & lmask) ? 1 : 0; + if (stereo) + ucontrol->value.integer.value[1] = (val & rmask) ? 1 : 0; + return 0; +} + +static int snd_pmac_burgundy_put_switch_b(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + unsigned int addr = BASE2ADDR((kcontrol->private_value >> 16) & 0xff); + int lmask = kcontrol->private_value & 0xff; + int rmask = (kcontrol->private_value >> 8) & 0xff; + int stereo = (kcontrol->private_value >> 24) & 1; + int val, oval; + oval = snd_pmac_burgundy_rcb(chip, addr); + val = oval & ~(lmask | rmask); + if (ucontrol->value.integer.value[0]) + val |= lmask; + if (stereo && ucontrol->value.integer.value[1]) + val |= rmask; + snd_pmac_burgundy_wcb(chip, addr, val); + return val != oval; +} + +#define BURGUNDY_SWITCH_B(xname, xindex, addr, lmask, rmask, stereo) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex,\ + .info = snd_pmac_burgundy_info_switch_b,\ + .get = snd_pmac_burgundy_get_switch_b,\ + .put = snd_pmac_burgundy_put_switch_b,\ + .private_value = ((lmask) | ((rmask) << 8)\ + | (ADDR2BASE(addr) << 16) | ((stereo) << 24)) } + +/* + * Burgundy mixers + */ +static struct snd_kcontrol_new snd_pmac_burgundy_mixers[] = { + BURGUNDY_VOLUME_W("Master Playback Volume", 0, + MASK_ADDR_BURGUNDY_MASTER_VOLUME, 8), + BURGUNDY_VOLUME_W("CD Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLCD, 16), + BURGUNDY_VOLUME_2B("Input Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIX01, 2), + BURGUNDY_VOLUME_2B("Mixer Playback Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIX23, 0), + BURGUNDY_VOLUME_B("CD Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINCD, 1, 0), + BURGUNDY_SWITCH_W("Master Capture Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTENABLES, 24, 0, 0), + BURGUNDY_SWITCH_W("CD Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 0, 16, 1), + BURGUNDY_SWITCH_W("CD Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 0, 16, 1), +/* BURGUNDY_SWITCH_W("Loop Capture Switch", 0, + * MASK_ADDR_BURGUNDY_CAPTURESELECTS, 8, 24, 1), + * BURGUNDY_SWITCH_B("Mixer out Capture Switch", 0, + * MASK_ADDR_BURGUNDY_HOSTIFAD, 0x02, 0, 0), + * BURGUNDY_SWITCH_B("Mixer Capture Switch", 0, + * MASK_ADDR_BURGUNDY_HOSTIFAD, 0x01, 0, 0), + * BURGUNDY_SWITCH_B("PCM out Capture Switch", 0, + * MASK_ADDR_BURGUNDY_HOSTIFEH, 0x02, 0, 0), + */ BURGUNDY_SWITCH_B("PCM Capture Switch", 0, + MASK_ADDR_BURGUNDY_HOSTIFEH, 0x01, 0, 0) +}; +static struct snd_kcontrol_new snd_pmac_burgundy_mixers_imac[] = { + BURGUNDY_VOLUME_W("Line in Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLLINE, 16), + BURGUNDY_VOLUME_W("Mic Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIC, 16), + BURGUNDY_VOLUME_B("Line in Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINLINE, 1, 0), + BURGUNDY_VOLUME_B("Mic Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINMIC, 1, 0), + BURGUNDY_VOLUME_B("Speaker Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1, 1), + BURGUNDY_VOLUME_B("Line out Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENLINEOUT, 1, 1), + BURGUNDY_VOLUME_B("Headphone Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENHP, 1, 1), + BURGUNDY_SWITCH_W("Line in Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 1, 17, 1), + BURGUNDY_SWITCH_W("Mic Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 2, 18, 1), + BURGUNDY_SWITCH_W("Line in Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 1, 17, 1), + BURGUNDY_SWITCH_W("Mic Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 2, 18, 1), + BURGUNDY_SWITCH_B("Mic Boost Capture Switch", 0, + MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1) +}; +static struct snd_kcontrol_new snd_pmac_burgundy_mixers_pmac[] = { + BURGUNDY_VOLUME_W("Line in Capture Volume", 0, + MASK_ADDR_BURGUNDY_VOLMIC, 16), + BURGUNDY_VOLUME_B("Line in Gain Capture Volume", 0, + MASK_ADDR_BURGUNDY_GAINMIC, 1, 0), + BURGUNDY_VOLUME_B("Speaker Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENMONO, 0, 1), + BURGUNDY_VOLUME_B("Line out Playback Volume", 0, + MASK_ADDR_BURGUNDY_ATTENSPEAKER, 1, 1), + BURGUNDY_SWITCH_W("Line in Capture Switch", 0, + MASK_ADDR_BURGUNDY_CAPTURESELECTS, 2, 18, 1), + BURGUNDY_SWITCH_W("Line in Playback Switch", 0, + MASK_ADDR_BURGUNDY_OUTPUTSELECTS, 2, 18, 1), +/* BURGUNDY_SWITCH_B("Line in Boost Capture Switch", 0, + * MASK_ADDR_BURGUNDY_INPBOOST, 0x40, 0x80, 1) */ +}; +static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_imac = +BURGUNDY_SWITCH_B("Master Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_LEFT | BURGUNDY_LINEOUT_LEFT | BURGUNDY_HP_LEFT, + BURGUNDY_OUTPUT_RIGHT | BURGUNDY_LINEOUT_RIGHT | BURGUNDY_HP_RIGHT, 1); +static struct snd_kcontrol_new snd_pmac_burgundy_master_sw_pmac = +BURGUNDY_SWITCH_B("Master Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_INTERN + | BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); +static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_imac = +BURGUNDY_SWITCH_B("Speaker Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); +static struct snd_kcontrol_new snd_pmac_burgundy_speaker_sw_pmac = +BURGUNDY_SWITCH_B("Speaker Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_INTERN, 0, 0); +static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_imac = +BURGUNDY_SWITCH_B("Line out Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_LINEOUT_LEFT, BURGUNDY_LINEOUT_RIGHT, 1); +static struct snd_kcontrol_new snd_pmac_burgundy_line_sw_pmac = +BURGUNDY_SWITCH_B("Line out Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_OUTPUT_LEFT, BURGUNDY_OUTPUT_RIGHT, 1); +static struct snd_kcontrol_new snd_pmac_burgundy_hp_sw_imac = +BURGUNDY_SWITCH_B("Headphone Playback Switch", 0, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + BURGUNDY_HP_LEFT, BURGUNDY_HP_RIGHT, 1); + + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int snd_pmac_burgundy_detect_headphone(struct snd_pmac *chip) +{ + return (in_le32(&chip->awacs->codec_stat) & chip->hp_stat_mask) ? 1 : 0; +} + +static void snd_pmac_burgundy_update_automute(struct snd_pmac *chip, int do_notify) +{ + if (chip->auto_mute) { + int imac = of_machine_is_compatible("iMac"); + int reg, oreg; + reg = oreg = snd_pmac_burgundy_rcb(chip, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES); + reg &= imac ? ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT + | BURGUNDY_HP_LEFT | BURGUNDY_HP_RIGHT) + : ~(BURGUNDY_OUTPUT_LEFT | BURGUNDY_OUTPUT_RIGHT + | BURGUNDY_OUTPUT_INTERN); + if (snd_pmac_burgundy_detect_headphone(chip)) + reg |= imac ? (BURGUNDY_HP_LEFT | BURGUNDY_HP_RIGHT) + : (BURGUNDY_OUTPUT_LEFT + | BURGUNDY_OUTPUT_RIGHT); + else + reg |= imac ? (BURGUNDY_OUTPUT_LEFT + | BURGUNDY_OUTPUT_RIGHT) + : (BURGUNDY_OUTPUT_INTERN); + if (do_notify && reg == oreg) + return; + snd_pmac_burgundy_wcb(chip, + MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, reg); + if (do_notify) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->master_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->speaker_sw_ctl->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hp_detect_ctl->id); + } + } +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + + +/* + * initialize burgundy + */ +int snd_pmac_burgundy_init(struct snd_pmac *chip) +{ + int imac = of_machine_is_compatible("iMac"); + int i, err; + + /* Checks to see the chip is alive and kicking */ + if ((in_le32(&chip->awacs->codec_ctrl) & MASK_ERRCODE) == 0xf0000) { + printk(KERN_WARNING "pmac burgundy: disabled by MacOS :-(\n"); + return 1; + } + + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_OUTPUTENABLES, + DEF_BURGUNDY_OUTPUTENABLES); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, + DEF_BURGUNDY_MORE_OUTPUTENABLES); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_OUTPUTSELECTS, + DEF_BURGUNDY_OUTPUTSELECTS); + + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL21, + DEF_BURGUNDY_INPSEL21); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_INPSEL3, + imac ? DEF_BURGUNDY_INPSEL3_IMAC + : DEF_BURGUNDY_INPSEL3_PMAC); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINCD, + DEF_BURGUNDY_GAINCD); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINLINE, + DEF_BURGUNDY_GAINLINE); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMIC, + DEF_BURGUNDY_GAINMIC); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_GAINMODEM, + DEF_BURGUNDY_GAINMODEM); + + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENSPEAKER, + DEF_BURGUNDY_ATTENSPEAKER); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENLINEOUT, + DEF_BURGUNDY_ATTENLINEOUT); + snd_pmac_burgundy_wcb(chip, MASK_ADDR_BURGUNDY_ATTENHP, + DEF_BURGUNDY_ATTENHP); + + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_MASTER_VOLUME, + DEF_BURGUNDY_MASTER_VOLUME); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLCD, + DEF_BURGUNDY_VOLCD); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLLINE, + DEF_BURGUNDY_VOLLINE); + snd_pmac_burgundy_wcw(chip, MASK_ADDR_BURGUNDY_VOLMIC, + DEF_BURGUNDY_VOLMIC); + + if (chip->hp_stat_mask == 0) { + /* set headphone-jack detection bit */ + if (imac) + chip->hp_stat_mask = BURGUNDY_HPDETECT_IMAC_UPPER + | BURGUNDY_HPDETECT_IMAC_LOWER + | BURGUNDY_HPDETECT_IMAC_SIDE; + else + chip->hp_stat_mask = BURGUNDY_HPDETECT_PMAC_BACK; + } + /* + * build burgundy mixers + */ + strcpy(chip->card->mixername, "PowerMac Burgundy"); + + for (i = 0; i < ARRAY_SIZE(snd_pmac_burgundy_mixers); i++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(&snd_pmac_burgundy_mixers[i], chip)); + if (err < 0) + return err; + } + for (i = 0; i < (imac ? ARRAY_SIZE(snd_pmac_burgundy_mixers_imac) + : ARRAY_SIZE(snd_pmac_burgundy_mixers_pmac)); i++) { + err = snd_ctl_add(chip->card, + snd_ctl_new1(imac ? &snd_pmac_burgundy_mixers_imac[i] + : &snd_pmac_burgundy_mixers_pmac[i], chip)); + if (err < 0) + return err; + } + chip->master_sw_ctl = snd_ctl_new1(imac + ? &snd_pmac_burgundy_master_sw_imac + : &snd_pmac_burgundy_master_sw_pmac, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + chip->master_sw_ctl = snd_ctl_new1(imac + ? &snd_pmac_burgundy_line_sw_imac + : &snd_pmac_burgundy_line_sw_pmac, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + if (imac) { + chip->master_sw_ctl = snd_ctl_new1( + &snd_pmac_burgundy_hp_sw_imac, chip); + err = snd_ctl_add(chip->card, chip->master_sw_ctl); + if (err < 0) + return err; + } + chip->speaker_sw_ctl = snd_ctl_new1(imac + ? &snd_pmac_burgundy_speaker_sw_imac + : &snd_pmac_burgundy_speaker_sw_pmac, chip); + err = snd_ctl_add(chip->card, chip->speaker_sw_ctl); + if (err < 0) + return err; +#ifdef PMAC_SUPPORT_AUTOMUTE + err = snd_pmac_add_automute(chip); + if (err < 0) + return err; + + chip->detect_headphone = snd_pmac_burgundy_detect_headphone; + chip->update_automute = snd_pmac_burgundy_update_automute; + snd_pmac_burgundy_update_automute(chip, 0); /* update the status only */ +#endif + + return 0; +} diff --git a/sound/ppc/burgundy.h b/sound/ppc/burgundy.h new file mode 100644 index 000000000..7a7f9cf3d --- /dev/null +++ b/sound/ppc/burgundy.h @@ -0,0 +1,114 @@ +/* + * Driver for PowerMac Burgundy onboard soundchips + * Copyright (c) 2001 by Takashi Iwai + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef __BURGUNDY_H +#define __BURGUNDY_H + +#define MASK_ADDR_BURGUNDY_INPBOOST (0x10 << 12) +#define MASK_ADDR_BURGUNDY_INPSEL21 (0x11 << 12) +#define MASK_ADDR_BURGUNDY_INPSEL3 (0x12 << 12) + +#define MASK_ADDR_BURGUNDY_GAINCH1 (0x13 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH2 (0x14 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH3 (0x15 << 12) +#define MASK_ADDR_BURGUNDY_GAINCH4 (0x16 << 12) + +#define MASK_ADDR_BURGUNDY_VOLCH1 (0x20 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH2 (0x21 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH3 (0x22 << 12) +#define MASK_ADDR_BURGUNDY_VOLCH4 (0x23 << 12) + +#define MASK_ADDR_BURGUNDY_CAPTURESELECTS (0x2A << 12) +#define MASK_ADDR_BURGUNDY_OUTPUTSELECTS (0x2B << 12) +#define MASK_ADDR_BURGUNDY_VOLMIX01 (0x2D << 12) +#define MASK_ADDR_BURGUNDY_VOLMIX23 (0x2E << 12) +#define MASK_ADDR_BURGUNDY_OUTPUTENABLES (0x2F << 12) + +#define MASK_ADDR_BURGUNDY_MASTER_VOLUME (0x30 << 12) + +#define MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES (0x60 << 12) + +#define MASK_ADDR_BURGUNDY_ATTENSPEAKER (0x62 << 12) +#define MASK_ADDR_BURGUNDY_ATTENLINEOUT (0x63 << 12) +#define MASK_ADDR_BURGUNDY_ATTENHP (0x64 << 12) +#define MASK_ADDR_BURGUNDY_ATTENMONO (0x65 << 12) + +#define MASK_ADDR_BURGUNDY_HOSTIFAD (0x78 << 12) +#define MASK_ADDR_BURGUNDY_HOSTIFEH (0x79 << 12) + +#define MASK_ADDR_BURGUNDY_VOLCD (MASK_ADDR_BURGUNDY_VOLCH1) +#define MASK_ADDR_BURGUNDY_VOLLINE (MASK_ADDR_BURGUNDY_VOLCH2) +#define MASK_ADDR_BURGUNDY_VOLMIC (MASK_ADDR_BURGUNDY_VOLCH3) +#define MASK_ADDR_BURGUNDY_VOLMODEM (MASK_ADDR_BURGUNDY_VOLCH4) + +#define MASK_ADDR_BURGUNDY_GAINCD (MASK_ADDR_BURGUNDY_GAINCH1) +#define MASK_ADDR_BURGUNDY_GAINLINE (MASK_ADDR_BURGUNDY_GAINCH2) +#define MASK_ADDR_BURGUNDY_GAINMIC (MASK_ADDR_BURGUNDY_GAINCH3) +#define MASK_ADDR_BURGUNDY_GAINMODEM (MASK_ADDR_BURGUNDY_VOLCH4) + + +/* These are all default values for the burgundy */ +#define DEF_BURGUNDY_INPSEL21 (0xAA) +#define DEF_BURGUNDY_INPSEL3_IMAC (0x0A) +#define DEF_BURGUNDY_INPSEL3_PMAC (0x05) + +#define DEF_BURGUNDY_GAINCD (0x33) +#define DEF_BURGUNDY_GAINLINE (0x44) +#define DEF_BURGUNDY_GAINMIC (0x44) +#define DEF_BURGUNDY_GAINMODEM (0x06) + +/* Remember: lowest volume here is 0x9B (155) */ +#define DEF_BURGUNDY_VOLCD (0xCCCCCCCC) +#define DEF_BURGUNDY_VOLLINE (0x00000000) +#define DEF_BURGUNDY_VOLMIC (0x00000000) +#define DEF_BURGUNDY_VOLMODEM (0xCCCCCCCC) + +#define DEF_BURGUNDY_OUTPUTSELECTS (0x010F010F) +#define DEF_BURGUNDY_OUTPUTENABLES (0x0100000A) + +/* #define DEF_BURGUNDY_MASTER_VOLUME (0xFFFFFFFF) */ /* too loud */ +#define DEF_BURGUNDY_MASTER_VOLUME (0xDDDDDDDD) + +#define DEF_BURGUNDY_MORE_OUTPUTENABLES (0x7E) + +#define DEF_BURGUNDY_ATTENSPEAKER (0x44) +#define DEF_BURGUNDY_ATTENLINEOUT (0xCC) +#define DEF_BURGUNDY_ATTENHP (0xCC) + +/* MORE_OUTPUTENABLES bits */ +#define BURGUNDY_OUTPUT_LEFT 0x02 +#define BURGUNDY_OUTPUT_RIGHT 0x04 +#define BURGUNDY_LINEOUT_LEFT 0x08 +#define BURGUNDY_LINEOUT_RIGHT 0x10 +#define BURGUNDY_HP_LEFT 0x20 +#define BURGUNDY_HP_RIGHT 0x40 +#define BURGUNDY_OUTPUT_INTERN 0x80 + +/* Headphone detection bits */ +#define BURGUNDY_HPDETECT_PMAC_BACK 0x04 +#define BURGUNDY_HPDETECT_IMAC_SIDE 0x04 +#define BURGUNDY_HPDETECT_IMAC_UPPER 0x08 +#define BURGUNDY_HPDETECT_IMAC_LOWER 0x01 + +/* Volume offset */ +#define BURGUNDY_VOLUME_OFFSET 155 + +#endif /* __BURGUNDY_H */ diff --git a/sound/ppc/daca.c b/sound/ppc/daca.c new file mode 100644 index 000000000..b86526223 --- /dev/null +++ b/sound/ppc/daca.c @@ -0,0 +1,282 @@ +/* + * PMac DACA lowlevel functions + * + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include "pmac.h" + +/* i2c address */ +#define DACA_I2C_ADDR 0x4d + +/* registers */ +#define DACA_REG_SR 0x01 +#define DACA_REG_AVOL 0x02 +#define DACA_REG_GCFG 0x03 + +/* maximum volume value */ +#define DACA_VOL_MAX 0x38 + + +struct pmac_daca { + struct pmac_keywest i2c; + int left_vol, right_vol; + unsigned int deemphasis : 1; + unsigned int amp_on : 1; +}; + + +/* + * initialize / detect DACA + */ +static int daca_init_client(struct pmac_keywest *i2c) +{ + unsigned short wdata = 0x00; + /* SR: no swap, 1bit delay, 32-48kHz */ + /* GCFG: power amp inverted, DAC on */ + if (i2c_smbus_write_byte_data(i2c->client, DACA_REG_SR, 0x08) < 0 || + i2c_smbus_write_byte_data(i2c->client, DACA_REG_GCFG, 0x05) < 0) + return -EINVAL; + return i2c_smbus_write_block_data(i2c->client, DACA_REG_AVOL, + 2, (unsigned char*)&wdata); +} + +/* + * update volume + */ +static int daca_set_volume(struct pmac_daca *mix) +{ + unsigned char data[2]; + + if (! mix->i2c.client) + return -ENODEV; + + if (mix->left_vol > DACA_VOL_MAX) + data[0] = DACA_VOL_MAX; + else + data[0] = mix->left_vol; + if (mix->right_vol > DACA_VOL_MAX) + data[1] = DACA_VOL_MAX; + else + data[1] = mix->right_vol; + data[1] |= mix->deemphasis ? 0x40 : 0; + if (i2c_smbus_write_block_data(mix->i2c.client, DACA_REG_AVOL, + 2, data) < 0) { + snd_printk(KERN_ERR "failed to set volume \n"); + return -EINVAL; + } + return 0; +} + + +/* deemphasis switch */ +#define daca_info_deemphasis snd_ctl_boolean_mono_info + +static int daca_get_deemphasis(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->deemphasis ? 1 : 0; + return 0; +} + +static int daca_put_deemphasis(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->deemphasis != ucontrol->value.integer.value[0]; + if (change) { + mix->deemphasis = !!ucontrol->value.integer.value[0]; + daca_set_volume(mix); + } + return change; +} + +/* output volume */ +static int daca_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 = DACA_VOL_MAX; + return 0; +} + +static int daca_get_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->left_vol; + ucontrol->value.integer.value[1] = mix->right_vol; + return 0; +} + +static int daca_put_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + unsigned int vol[2]; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] > DACA_VOL_MAX || vol[1] > DACA_VOL_MAX) + return -EINVAL; + change = mix->left_vol != vol[0] || + mix->right_vol != vol[1]; + if (change) { + mix->left_vol = vol[0]; + mix->right_vol = vol[1]; + daca_set_volume(mix); + } + return change; +} + +/* amplifier switch */ +#define daca_info_amp daca_info_deemphasis + +static int daca_get_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->amp_on ? 1 : 0; + return 0; +} + +static int daca_put_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_daca *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->amp_on != ucontrol->value.integer.value[0]; + if (change) { + mix->amp_on = !!ucontrol->value.integer.value[0]; + i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG, + mix->amp_on ? 0x05 : 0x04); + } + return change; +} + +static struct snd_kcontrol_new daca_mixers[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Deemphasis Switch", + .info = daca_info_deemphasis, + .get = daca_get_deemphasis, + .put = daca_put_deemphasis + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = daca_info_volume, + .get = daca_get_volume, + .put = daca_put_volume + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Power Amplifier Switch", + .info = daca_info_amp, + .get = daca_get_amp, + .put = daca_put_amp + }, +}; + + +#ifdef CONFIG_PM +static void daca_resume(struct snd_pmac *chip) +{ + struct pmac_daca *mix = chip->mixer_data; + i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_SR, 0x08); + i2c_smbus_write_byte_data(mix->i2c.client, DACA_REG_GCFG, + mix->amp_on ? 0x05 : 0x04); + daca_set_volume(mix); +} +#endif /* CONFIG_PM */ + + +static void daca_cleanup(struct snd_pmac *chip) +{ + struct pmac_daca *mix = chip->mixer_data; + if (! mix) + return; + snd_pmac_keywest_cleanup(&mix->i2c); + kfree(mix); + chip->mixer_data = NULL; +} + +/* exported */ +int snd_pmac_daca_init(struct snd_pmac *chip) +{ + int i, err; + struct pmac_daca *mix; + + request_module("i2c-powermac"); + + mix = kzalloc(sizeof(*mix), GFP_KERNEL); + if (! mix) + return -ENOMEM; + chip->mixer_data = mix; + chip->mixer_free = daca_cleanup; + mix->amp_on = 1; /* default on */ + + mix->i2c.addr = DACA_I2C_ADDR; + mix->i2c.init_client = daca_init_client; + mix->i2c.name = "DACA"; + if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0) + return err; + + /* + * build mixers + */ + strcpy(chip->card->mixername, "PowerMac DACA"); + + for (i = 0; i < ARRAY_SIZE(daca_mixers); i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&daca_mixers[i], chip))) < 0) + return err; + } + +#ifdef CONFIG_PM + chip->resume = daca_resume; +#endif + + return 0; +} diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c new file mode 100644 index 000000000..4373615f1 --- /dev/null +++ b/sound/ppc/keywest.c @@ -0,0 +1,175 @@ +/* + * common keywest i2c layer + * + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include "pmac.h" + +/* + * we have to keep a static variable here since i2c attach_adapter + * callback cannot pass a private data. + */ +static struct pmac_keywest *keywest_ctx; + +static bool keywest_probed; + +static int keywest_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + keywest_probed = true; + /* If instantiated via i2c-powermac, we still need to set the client */ + if (!keywest_ctx->client) + keywest_ctx->client = client; + i2c_set_clientdata(client, keywest_ctx); + return 0; +} + +/* + * This is kind of a hack, best would be to turn powermac to fixed i2c + * bus numbers and declare the sound device as part of platform + * initialization + */ +static int keywest_attach_adapter(struct i2c_adapter *adapter) +{ + struct i2c_board_info info; + + if (! keywest_ctx) + return -EINVAL; + + if (strncmp(adapter->name, "mac-io", 6)) + return -EINVAL; /* ignored */ + + memset(&info, 0, sizeof(struct i2c_board_info)); + strlcpy(info.type, "keywest", I2C_NAME_SIZE); + info.addr = keywest_ctx->addr; + keywest_ctx->client = i2c_new_device(adapter, &info); + if (!keywest_ctx->client) + return -ENODEV; + /* + * We know the driver is already loaded, so the device should be + * already bound. If not it means binding failed, and then there + * is no point in keeping the device instantiated. + */ + if (!keywest_ctx->client->dev.driver) { + i2c_unregister_device(keywest_ctx->client); + keywest_ctx->client = NULL; + return -ENODEV; + } + + /* + * Let i2c-core delete that device on driver removal. + * This is safe because i2c-core holds the core_lock mutex for us. + */ + list_add_tail(&keywest_ctx->client->detected, + &to_i2c_driver(keywest_ctx->client->dev.driver)->clients); + return 0; +} + +static int keywest_remove(struct i2c_client *client) +{ + if (! keywest_ctx) + return 0; + if (client == keywest_ctx->client) + keywest_ctx->client = NULL; + + return 0; +} + + +static const struct i2c_device_id keywest_i2c_id[] = { + { "MAC,tas3004", 0 }, /* instantiated by i2c-powermac */ + { "keywest", 0 }, /* instantiated by us if needed */ + { } +}; +MODULE_DEVICE_TABLE(i2c, keywest_i2c_id); + +static struct i2c_driver keywest_driver = { + .driver = { + .name = "PMac Keywest Audio", + }, + .probe = keywest_probe, + .remove = keywest_remove, + .id_table = keywest_i2c_id, +}; + +/* exported */ +void snd_pmac_keywest_cleanup(struct pmac_keywest *i2c) +{ + if (keywest_ctx && keywest_ctx == i2c) { + i2c_del_driver(&keywest_driver); + keywest_ctx = NULL; + } +} + +int snd_pmac_tumbler_post_init(void) +{ + int err; + + if (!keywest_ctx || !keywest_ctx->client) + return -ENXIO; + + if ((err = keywest_ctx->init_client(keywest_ctx)) < 0) { + snd_printk(KERN_ERR "tumbler: %i :cannot initialize the MCS\n", err); + return err; + } + return 0; +} + +/* exported */ +int snd_pmac_keywest_init(struct pmac_keywest *i2c) +{ + struct i2c_adapter *adap; + int err, i = 0; + + if (keywest_ctx) + return -EBUSY; + + adap = i2c_get_adapter(0); + if (!adap) + return -EPROBE_DEFER; + + keywest_ctx = i2c; + + if ((err = i2c_add_driver(&keywest_driver))) { + snd_printk(KERN_ERR "cannot register keywest i2c driver\n"); + i2c_put_adapter(adap); + return err; + } + + /* There was already a device from i2c-powermac. Great, let's return */ + if (keywest_probed) + return 0; + + /* We assume Macs have consecutive I2C bus numbers starting at 0 */ + while (adap) { + /* Scan for devices to be bound to */ + err = keywest_attach_adapter(adap); + if (!err) + return 0; + i2c_put_adapter(adap); + adap = i2c_get_adapter(++i); + } + + return -ENODEV; +} diff --git a/sound/ppc/pmac.c b/sound/ppc/pmac.c new file mode 100644 index 000000000..48dd44f8e --- /dev/null +++ b/sound/ppc/pmac.c @@ -0,0 +1,1405 @@ +/* + * PMac DBDMA lowlevel functions + * + * Copyright (c) by Takashi Iwai + * code based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmac.h" +#include +#include + + +/* fixed frequency table for awacs, screamer, burgundy, DACA (44100 max) */ +static int awacs_freqs[8] = { + 44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350 +}; +/* fixed frequency table for tumbler */ +static int tumbler_freqs[1] = { + 44100 +}; + + +/* + * we will allocate a single 'emergency' dbdma cmd block to use if the + * tx status comes up "DEAD". This happens on some PowerComputing Pmac + * clones, either owing to a bug in dbdma or some interaction between + * IDE and sound. However, this measure would deal with DEAD status if + * it appeared elsewhere. + */ +static struct pmac_dbdma emergency_dbdma; +static int emergency_in_use; + + +/* + * allocate DBDMA command arrays + */ +static int snd_pmac_dbdma_alloc(struct snd_pmac *chip, struct pmac_dbdma *rec, int size) +{ + unsigned int rsize = sizeof(struct dbdma_cmd) * (size + 1); + + rec->space = dma_alloc_coherent(&chip->pdev->dev, rsize, + &rec->dma_base, GFP_KERNEL); + if (rec->space == NULL) + return -ENOMEM; + rec->size = size; + memset(rec->space, 0, rsize); + rec->cmds = (void __iomem *)DBDMA_ALIGN(rec->space); + rec->addr = rec->dma_base + (unsigned long)((char *)rec->cmds - (char *)rec->space); + + return 0; +} + +static void snd_pmac_dbdma_free(struct snd_pmac *chip, struct pmac_dbdma *rec) +{ + if (rec->space) { + unsigned int rsize = sizeof(struct dbdma_cmd) * (rec->size + 1); + + dma_free_coherent(&chip->pdev->dev, rsize, rec->space, rec->dma_base); + } +} + + +/* + * pcm stuff + */ + +/* + * look up frequency table + */ + +unsigned int snd_pmac_rate_index(struct snd_pmac *chip, struct pmac_stream *rec, unsigned int rate) +{ + int i, ok, found; + + ok = rec->cur_freqs; + if (rate > chip->freq_table[0]) + return 0; + found = 0; + for (i = 0; i < chip->num_freqs; i++, ok >>= 1) { + if (! (ok & 1)) continue; + found = i; + if (rate >= chip->freq_table[i]) + break; + } + return found; +} + +/* + * check whether another stream is active + */ +static inline int another_stream(int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; +} + +/* + * allocate buffers + */ +static int snd_pmac_pcm_hw_params(struct snd_pcm_substream *subs, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params)); +} + +/* + * release buffers + */ +static int snd_pmac_pcm_hw_free(struct snd_pcm_substream *subs) +{ + snd_pcm_lib_free_pages(subs); + return 0; +} + +/* + * get a stream of the opposite direction + */ +static struct pmac_stream *snd_pmac_get_stream(struct snd_pmac *chip, int stream) +{ + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + return &chip->playback; + case SNDRV_PCM_STREAM_CAPTURE: + return &chip->capture; + default: + snd_BUG(); + return NULL; + } +} + +/* + * wait while run status is on + */ +static inline void +snd_pmac_wait_ack(struct pmac_stream *rec) +{ + int timeout = 50000; + while ((in_le32(&rec->dma->status) & RUN) && timeout-- > 0) + udelay(1); +} + +/* + * set the format and rate to the chip. + * call the lowlevel function if defined (e.g. for AWACS). + */ +static void snd_pmac_pcm_set_format(struct snd_pmac *chip) +{ + /* set up frequency and format */ + out_le32(&chip->awacs->control, chip->control_mask | (chip->rate_index << 8)); + out_le32(&chip->awacs->byteswap, chip->format == SNDRV_PCM_FORMAT_S16_LE ? 1 : 0); + if (chip->set_format) + chip->set_format(chip); +} + +/* + * stop the DMA transfer + */ +static inline void snd_pmac_dma_stop(struct pmac_stream *rec) +{ + out_le32(&rec->dma->control, (RUN|WAKE|FLUSH|PAUSE) << 16); + snd_pmac_wait_ack(rec); +} + +/* + * set the command pointer address + */ +static inline void snd_pmac_dma_set_command(struct pmac_stream *rec, struct pmac_dbdma *cmd) +{ + out_le32(&rec->dma->cmdptr, cmd->addr); +} + +/* + * start the DMA + */ +static inline void snd_pmac_dma_run(struct pmac_stream *rec, int status) +{ + out_le32(&rec->dma->control, status | (status << 16)); +} + + +/* + * prepare playback/capture stream + */ +static int snd_pmac_pcm_prepare(struct snd_pmac *chip, struct pmac_stream *rec, struct snd_pcm_substream *subs) +{ + int i; + volatile struct dbdma_cmd __iomem *cp; + struct snd_pcm_runtime *runtime = subs->runtime; + int rate_index; + long offset; + struct pmac_stream *astr; + + rec->dma_size = snd_pcm_lib_buffer_bytes(subs); + rec->period_size = snd_pcm_lib_period_bytes(subs); + rec->nperiods = rec->dma_size / rec->period_size; + rec->cur_period = 0; + rate_index = snd_pmac_rate_index(chip, rec, runtime->rate); + + /* set up constraints */ + astr = snd_pmac_get_stream(chip, another_stream(rec->stream)); + if (! astr) + return -EINVAL; + astr->cur_freqs = 1 << rate_index; + astr->cur_formats = 1 << runtime->format; + chip->rate_index = rate_index; + chip->format = runtime->format; + + /* We really want to execute a DMA stop command, after the AWACS + * is initialized. + * For reasons I don't understand, it stops the hissing noise + * common to many PowerBook G3 systems and random noise otherwise + * captured on iBook2's about every third time. -ReneR + */ + spin_lock_irq(&chip->reg_lock); + snd_pmac_dma_stop(rec); + chip->extra_dma.cmds->command = cpu_to_le16(DBDMA_STOP); + snd_pmac_dma_set_command(rec, &chip->extra_dma); + snd_pmac_dma_run(rec, RUN); + spin_unlock_irq(&chip->reg_lock); + mdelay(5); + spin_lock_irq(&chip->reg_lock); + /* continuous DMA memory type doesn't provide the physical address, + * so we need to resolve the address here... + */ + offset = runtime->dma_addr; + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) { + cp->phy_addr = cpu_to_le32(offset); + cp->req_count = cpu_to_le16(rec->period_size); + /*cp->res_count = cpu_to_le16(0);*/ + cp->xfer_status = cpu_to_le16(0); + offset += rec->period_size; + } + /* make loop */ + cp->command = cpu_to_le16(DBDMA_NOP + BR_ALWAYS); + cp->cmd_dep = cpu_to_le32(rec->cmd.addr); + + snd_pmac_dma_stop(rec); + snd_pmac_dma_set_command(rec, &rec->cmd); + spin_unlock_irq(&chip->reg_lock); + + return 0; +} + + +/* + * PCM trigger/stop + */ +static int snd_pmac_pcm_trigger(struct snd_pmac *chip, struct pmac_stream *rec, + struct snd_pcm_substream *subs, int cmd) +{ + volatile struct dbdma_cmd __iomem *cp; + int i, command; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (rec->running) + return -EBUSY; + command = (subs->stream == SNDRV_PCM_STREAM_PLAYBACK ? + OUTPUT_MORE : INPUT_MORE) + INTR_ALWAYS; + spin_lock(&chip->reg_lock); + snd_pmac_beep_stop(chip); + snd_pmac_pcm_set_format(chip); + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) + out_le16(&cp->command, command); + snd_pmac_dma_set_command(rec, &rec->cmd); + (void)in_le32(&rec->dma->status); + snd_pmac_dma_run(rec, RUN|WAKE); + rec->running = 1; + spin_unlock(&chip->reg_lock); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + spin_lock(&chip->reg_lock); + rec->running = 0; + /*printk(KERN_DEBUG "stopped!!\n");*/ + snd_pmac_dma_stop(rec); + for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) + out_le16(&cp->command, DBDMA_STOP); + spin_unlock(&chip->reg_lock); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * return the current pointer + */ +inline +static snd_pcm_uframes_t snd_pmac_pcm_pointer(struct snd_pmac *chip, + struct pmac_stream *rec, + struct snd_pcm_substream *subs) +{ + int count = 0; + +#if 1 /* hmm.. how can we get the current dma pointer?? */ + int stat; + volatile struct dbdma_cmd __iomem *cp = &rec->cmd.cmds[rec->cur_period]; + stat = le16_to_cpu(cp->xfer_status); + if (stat & (ACTIVE|DEAD)) { + count = in_le16(&cp->res_count); + if (count) + count = rec->period_size - count; + } +#endif + count += rec->cur_period * rec->period_size; + /*printk(KERN_DEBUG "pointer=%d\n", count);*/ + return bytes_to_frames(subs->runtime, count); +} + +/* + * playback + */ + +static int snd_pmac_playback_prepare(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_prepare(chip, &chip->playback, subs); +} + +static int snd_pmac_playback_trigger(struct snd_pcm_substream *subs, + int cmd) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_trigger(chip, &chip->playback, subs, cmd); +} + +static snd_pcm_uframes_t snd_pmac_playback_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_pointer(chip, &chip->playback, subs); +} + + +/* + * capture + */ + +static int snd_pmac_capture_prepare(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_prepare(chip, &chip->capture, subs); +} + +static int snd_pmac_capture_trigger(struct snd_pcm_substream *subs, + int cmd) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_trigger(chip, &chip->capture, subs, cmd); +} + +static snd_pcm_uframes_t snd_pmac_capture_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + return snd_pmac_pcm_pointer(chip, &chip->capture, subs); +} + + +/* + * Handle DEAD DMA transfers: + * if the TX status comes up "DEAD" - reported on some Power Computing machines + * we need to re-start the dbdma - but from a different physical start address + * and with a different transfer length. It would get very messy to do this + * with the normal dbdma_cmd blocks - we would have to re-write the buffer start + * addresses each time. So, we will keep a single dbdma_cmd block which can be + * fiddled with. + * When DEAD status is first reported the content of the faulted dbdma block is + * copied into the emergency buffer and we note that the buffer is in use. + * we then bump the start physical address by the amount that was successfully + * output before it died. + * On any subsequent DEAD result we just do the bump-ups (we know that we are + * already using the emergency dbdma_cmd). + * CHECK: this just tries to "do it". It is possible that we should abandon + * xfers when the number of residual bytes gets below a certain value - I can + * see that this might cause a loop-forever if a too small transfer causes + * DEAD status. However this is a TODO for now - we'll see what gets reported. + * When we get a successful transfer result with the emergency buffer we just + * pretend that it completed using the original dmdma_cmd and carry on. The + * 'next_cmd' field will already point back to the original loop of blocks. + */ +static inline void snd_pmac_pcm_dead_xfer(struct pmac_stream *rec, + volatile struct dbdma_cmd __iomem *cp) +{ + unsigned short req, res ; + unsigned int phy ; + + /* printk(KERN_WARNING "snd-powermac: DMA died - patching it up!\n"); */ + + /* to clear DEAD status we must first clear RUN + set it to quiescent to be on the safe side */ + (void)in_le32(&rec->dma->status); + out_le32(&rec->dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); + + if (!emergency_in_use) { /* new problem */ + memcpy((void *)emergency_dbdma.cmds, (void *)cp, + sizeof(struct dbdma_cmd)); + emergency_in_use = 1; + cp->xfer_status = cpu_to_le16(0); + cp->req_count = cpu_to_le16(rec->period_size); + cp = emergency_dbdma.cmds; + } + + /* now bump the values to reflect the amount + we haven't yet shifted */ + req = le16_to_cpu(cp->req_count); + res = le16_to_cpu(cp->res_count); + phy = le32_to_cpu(cp->phy_addr); + phy += (req - res); + cp->req_count = cpu_to_le16(res); + cp->res_count = cpu_to_le16(0); + cp->xfer_status = cpu_to_le16(0); + cp->phy_addr = cpu_to_le32(phy); + + cp->cmd_dep = cpu_to_le32(rec->cmd.addr + + sizeof(struct dbdma_cmd)*((rec->cur_period+1)%rec->nperiods)); + + cp->command = cpu_to_le16(OUTPUT_MORE | BR_ALWAYS | INTR_ALWAYS); + + /* point at our patched up command block */ + out_le32(&rec->dma->cmdptr, emergency_dbdma.addr); + + /* we must re-start the controller */ + (void)in_le32(&rec->dma->status); + /* should complete clearing the DEAD status */ + out_le32(&rec->dma->control, ((RUN|WAKE) << 16) + (RUN|WAKE)); +} + +/* + * update playback/capture pointer from interrupts + */ +static void snd_pmac_pcm_update(struct snd_pmac *chip, struct pmac_stream *rec) +{ + volatile struct dbdma_cmd __iomem *cp; + int c; + int stat; + + spin_lock(&chip->reg_lock); + if (rec->running) { + for (c = 0; c < rec->nperiods; c++) { /* at most all fragments */ + + if (emergency_in_use) /* already using DEAD xfer? */ + cp = emergency_dbdma.cmds; + else + cp = &rec->cmd.cmds[rec->cur_period]; + + stat = le16_to_cpu(cp->xfer_status); + + if (stat & DEAD) { + snd_pmac_pcm_dead_xfer(rec, cp); + break; /* this block is still going */ + } + + if (emergency_in_use) + emergency_in_use = 0 ; /* done that */ + + if (! (stat & ACTIVE)) + break; + + /*printk(KERN_DEBUG "update frag %d\n", rec->cur_period);*/ + cp->xfer_status = cpu_to_le16(0); + cp->req_count = cpu_to_le16(rec->period_size); + /*cp->res_count = cpu_to_le16(0);*/ + rec->cur_period++; + if (rec->cur_period >= rec->nperiods) { + rec->cur_period = 0; + } + + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(rec->substream); + spin_lock(&chip->reg_lock); + } + } + spin_unlock(&chip->reg_lock); +} + + +/* + * hw info + */ + +static const struct snd_pcm_hardware snd_pmac_playback = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_44100, + .rate_min = 7350, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 131072, + .period_bytes_min = 256, + .period_bytes_max = 16384, + .periods_min = 3, + .periods_max = PMAC_MAX_FRAGS, +}; + +static const struct snd_pcm_hardware snd_pmac_capture = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_44100, + .rate_min = 7350, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 131072, + .period_bytes_min = 256, + .period_bytes_max = 16384, + .periods_min = 3, + .periods_max = PMAC_MAX_FRAGS, +}; + + +#if 0 // NYI +static int snd_pmac_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pmac *chip = rule->private; + struct pmac_stream *rec = snd_pmac_get_stream(chip, rule->deps[0]); + int i, freq_table[8], num_freqs; + + if (! rec) + return -EINVAL; + num_freqs = 0; + for (i = chip->num_freqs - 1; i >= 0; i--) { + if (rec->cur_freqs & (1 << i)) + freq_table[num_freqs++] = chip->freq_table[i]; + } + + return snd_interval_list(hw_param_interval(params, rule->var), + num_freqs, freq_table, 0); +} + +static int snd_pmac_hw_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pmac *chip = rule->private; + struct pmac_stream *rec = snd_pmac_get_stream(chip, rule->deps[0]); + + if (! rec) + return -EINVAL; + return snd_mask_refine_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), + rec->cur_formats); +} +#endif // NYI + +static int snd_pmac_pcm_open(struct snd_pmac *chip, struct pmac_stream *rec, + struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + int i; + + /* look up frequency table and fill bit mask */ + runtime->hw.rates = 0; + for (i = 0; i < chip->num_freqs; i++) + if (chip->freqs_ok & (1 << i)) + runtime->hw.rates |= + snd_pcm_rate_to_rate_bit(chip->freq_table[i]); + + /* check for minimum and maximum rates */ + for (i = 0; i < chip->num_freqs; i++) { + if (chip->freqs_ok & (1 << i)) { + runtime->hw.rate_max = chip->freq_table[i]; + break; + } + } + for (i = chip->num_freqs - 1; i >= 0; i--) { + if (chip->freqs_ok & (1 << i)) { + runtime->hw.rate_min = chip->freq_table[i]; + break; + } + } + runtime->hw.formats = chip->formats_ok; + if (chip->can_capture) { + if (! chip->can_duplex) + runtime->hw.info |= SNDRV_PCM_INFO_HALF_DUPLEX; + runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + } + runtime->private_data = rec; + rec->substream = subs; + +#if 0 /* FIXME: still under development.. */ + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pmac_hw_rule_rate, chip, rec->stream, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + snd_pmac_hw_rule_format, chip, rec->stream, -1); +#endif + + runtime->hw.periods_max = rec->cmd.size - 1; + + /* constraints to fix choppy sound */ + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + return 0; +} + +static int snd_pmac_pcm_close(struct snd_pmac *chip, struct pmac_stream *rec, + struct snd_pcm_substream *subs) +{ + struct pmac_stream *astr; + + snd_pmac_dma_stop(rec); + + astr = snd_pmac_get_stream(chip, another_stream(rec->stream)); + if (! astr) + return -EINVAL; + + /* reset constraints */ + astr->cur_freqs = chip->freqs_ok; + astr->cur_formats = chip->formats_ok; + + return 0; +} + +static int snd_pmac_playback_open(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + + subs->runtime->hw = snd_pmac_playback; + return snd_pmac_pcm_open(chip, &chip->playback, subs); +} + +static int snd_pmac_capture_open(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + + subs->runtime->hw = snd_pmac_capture; + return snd_pmac_pcm_open(chip, &chip->capture, subs); +} + +static int snd_pmac_playback_close(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + + return snd_pmac_pcm_close(chip, &chip->playback, subs); +} + +static int snd_pmac_capture_close(struct snd_pcm_substream *subs) +{ + struct snd_pmac *chip = snd_pcm_substream_chip(subs); + + return snd_pmac_pcm_close(chip, &chip->capture, subs); +} + +/* + */ + +static const struct snd_pcm_ops snd_pmac_playback_ops = { + .open = snd_pmac_playback_open, + .close = snd_pmac_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_pmac_pcm_hw_params, + .hw_free = snd_pmac_pcm_hw_free, + .prepare = snd_pmac_playback_prepare, + .trigger = snd_pmac_playback_trigger, + .pointer = snd_pmac_playback_pointer, +}; + +static const struct snd_pcm_ops snd_pmac_capture_ops = { + .open = snd_pmac_capture_open, + .close = snd_pmac_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_pmac_pcm_hw_params, + .hw_free = snd_pmac_pcm_hw_free, + .prepare = snd_pmac_capture_prepare, + .trigger = snd_pmac_capture_trigger, + .pointer = snd_pmac_capture_pointer, +}; + +int snd_pmac_pcm_new(struct snd_pmac *chip) +{ + struct snd_pcm *pcm; + int err; + int num_captures = 1; + + if (! chip->can_capture) + num_captures = 0; + err = snd_pcm_new(chip->card, chip->card->driver, 0, 1, num_captures, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pmac_playback_ops); + if (chip->can_capture) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pmac_capture_ops); + + pcm->private_data = chip; + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + strcpy(pcm->name, chip->card->shortname); + chip->pcm = pcm; + + chip->formats_ok = SNDRV_PCM_FMTBIT_S16_BE; + if (chip->can_byte_swap) + chip->formats_ok |= SNDRV_PCM_FMTBIT_S16_LE; + + chip->playback.cur_formats = chip->formats_ok; + chip->capture.cur_formats = chip->formats_ok; + chip->playback.cur_freqs = chip->freqs_ok; + chip->capture.cur_freqs = chip->freqs_ok; + + /* preallocate 64k buffer */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->pdev->dev, + 64 * 1024, 64 * 1024); + + return 0; +} + + +static void snd_pmac_dbdma_reset(struct snd_pmac *chip) +{ + out_le32(&chip->playback.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); + snd_pmac_wait_ack(&chip->playback); + out_le32(&chip->capture.dma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); + snd_pmac_wait_ack(&chip->capture); +} + + +/* + * handling beep + */ +void snd_pmac_beep_dma_start(struct snd_pmac *chip, int bytes, unsigned long addr, int speed) +{ + struct pmac_stream *rec = &chip->playback; + + snd_pmac_dma_stop(rec); + chip->extra_dma.cmds->req_count = cpu_to_le16(bytes); + chip->extra_dma.cmds->xfer_status = cpu_to_le16(0); + chip->extra_dma.cmds->cmd_dep = cpu_to_le32(chip->extra_dma.addr); + chip->extra_dma.cmds->phy_addr = cpu_to_le32(addr); + chip->extra_dma.cmds->command = cpu_to_le16(OUTPUT_MORE + BR_ALWAYS); + out_le32(&chip->awacs->control, + (in_le32(&chip->awacs->control) & ~0x1f00) + | (speed << 8)); + out_le32(&chip->awacs->byteswap, 0); + snd_pmac_dma_set_command(rec, &chip->extra_dma); + snd_pmac_dma_run(rec, RUN); +} + +void snd_pmac_beep_dma_stop(struct snd_pmac *chip) +{ + snd_pmac_dma_stop(&chip->playback); + chip->extra_dma.cmds->command = cpu_to_le16(DBDMA_STOP); + snd_pmac_pcm_set_format(chip); /* reset format */ +} + + +/* + * interrupt handlers + */ +static irqreturn_t +snd_pmac_tx_intr(int irq, void *devid) +{ + struct snd_pmac *chip = devid; + snd_pmac_pcm_update(chip, &chip->playback); + return IRQ_HANDLED; +} + + +static irqreturn_t +snd_pmac_rx_intr(int irq, void *devid) +{ + struct snd_pmac *chip = devid; + snd_pmac_pcm_update(chip, &chip->capture); + return IRQ_HANDLED; +} + + +static irqreturn_t +snd_pmac_ctrl_intr(int irq, void *devid) +{ + struct snd_pmac *chip = devid; + int ctrl = in_le32(&chip->awacs->control); + + /*printk(KERN_DEBUG "pmac: control interrupt.. 0x%x\n", ctrl);*/ + if (ctrl & MASK_PORTCHG) { + /* do something when headphone is plugged/unplugged? */ + if (chip->update_automute) + chip->update_automute(chip, 1); + } + if (ctrl & MASK_CNTLERR) { + int err = (in_le32(&chip->awacs->codec_stat) & MASK_ERRCODE) >> 16; + if (err && chip->model <= PMAC_SCREAMER) + snd_printk(KERN_DEBUG "error %x\n", err); + } + /* Writing 1s to the CNTLERR and PORTCHG bits clears them... */ + out_le32(&chip->awacs->control, ctrl); + return IRQ_HANDLED; +} + + +/* + * a wrapper to feature call for compatibility + */ +static void snd_pmac_sound_feature(struct snd_pmac *chip, int enable) +{ + if (ppc_md.feature_call) + ppc_md.feature_call(PMAC_FTR_SOUND_CHIP_ENABLE, chip->node, 0, enable); +} + +/* + * release resources + */ + +static int snd_pmac_free(struct snd_pmac *chip) +{ + /* stop sounds */ + if (chip->initialized) { + snd_pmac_dbdma_reset(chip); + /* disable interrupts from awacs interface */ + out_le32(&chip->awacs->control, in_le32(&chip->awacs->control) & 0xfff); + } + + if (chip->node) + snd_pmac_sound_feature(chip, 0); + + /* clean up mixer if any */ + if (chip->mixer_free) + chip->mixer_free(chip); + + snd_pmac_detach_beep(chip); + + /* release resources */ + if (chip->irq >= 0) + free_irq(chip->irq, (void*)chip); + if (chip->tx_irq >= 0) + free_irq(chip->tx_irq, (void*)chip); + if (chip->rx_irq >= 0) + free_irq(chip->rx_irq, (void*)chip); + snd_pmac_dbdma_free(chip, &chip->playback.cmd); + snd_pmac_dbdma_free(chip, &chip->capture.cmd); + snd_pmac_dbdma_free(chip, &chip->extra_dma); + snd_pmac_dbdma_free(chip, &emergency_dbdma); + iounmap(chip->macio_base); + iounmap(chip->latch_base); + iounmap(chip->awacs); + iounmap(chip->playback.dma); + iounmap(chip->capture.dma); + + if (chip->node) { + int i; + for (i = 0; i < 3; i++) { + if (chip->requested & (1 << i)) + release_mem_region(chip->rsrc[i].start, + resource_size(&chip->rsrc[i])); + } + } + + pci_dev_put(chip->pdev); + of_node_put(chip->node); + kfree(chip); + return 0; +} + + +/* + * free the device + */ +static int snd_pmac_dev_free(struct snd_device *device) +{ + struct snd_pmac *chip = device->device_data; + return snd_pmac_free(chip); +} + + +/* + * check the machine support byteswap (little-endian) + */ + +static void detect_byte_swap(struct snd_pmac *chip) +{ + struct device_node *mio; + + /* if seems that Keylargo can't byte-swap */ + for (mio = chip->node->parent; mio; mio = mio->parent) { + if (strcmp(mio->name, "mac-io") == 0) { + if (of_device_is_compatible(mio, "Keylargo")) + chip->can_byte_swap = 0; + break; + } + } + + /* it seems the Pismo & iBook can't byte-swap in hardware. */ + if (of_machine_is_compatible("PowerBook3,1") || + of_machine_is_compatible("PowerBook2,1")) + chip->can_byte_swap = 0 ; + + if (of_machine_is_compatible("PowerBook2,1")) + chip->can_duplex = 0; +} + + +/* + * detect a sound chip + */ +static int snd_pmac_detect(struct snd_pmac *chip) +{ + struct device_node *sound; + struct device_node *dn; + const unsigned int *prop; + unsigned int l; + struct macio_chip* macio; + + if (!machine_is(powermac)) + return -ENODEV; + + chip->subframe = 0; + chip->revision = 0; + chip->freqs_ok = 0xff; /* all ok */ + chip->model = PMAC_AWACS; + chip->can_byte_swap = 1; + chip->can_duplex = 1; + chip->can_capture = 1; + chip->num_freqs = ARRAY_SIZE(awacs_freqs); + chip->freq_table = awacs_freqs; + chip->pdev = NULL; + + chip->control_mask = MASK_IEPC | MASK_IEE | 0x11; /* default */ + + /* check machine type */ + if (of_machine_is_compatible("AAPL,3400/2400") + || of_machine_is_compatible("AAPL,3500")) + chip->is_pbook_3400 = 1; + else if (of_machine_is_compatible("PowerBook1,1") + || of_machine_is_compatible("AAPL,PowerBook1998")) + chip->is_pbook_G3 = 1; + chip->node = of_find_node_by_name(NULL, "awacs"); + sound = of_node_get(chip->node); + + /* + * powermac G3 models have a node called "davbus" + * with a child called "sound". + */ + if (!chip->node) + chip->node = of_find_node_by_name(NULL, "davbus"); + /* + * if we didn't find a davbus device, try 'i2s-a' since + * this seems to be what iBooks have + */ + if (! chip->node) { + chip->node = of_find_node_by_name(NULL, "i2s-a"); + if (chip->node && chip->node->parent && + chip->node->parent->parent) { + if (of_device_is_compatible(chip->node->parent->parent, + "K2-Keylargo")) + chip->is_k2 = 1; + } + } + if (! chip->node) + return -ENODEV; + + if (!sound) { + for_each_node_by_name(sound, "sound") + if (sound->parent == chip->node) + break; + } + if (! sound) { + of_node_put(chip->node); + chip->node = NULL; + return -ENODEV; + } + prop = of_get_property(sound, "sub-frame", NULL); + if (prop && *prop < 16) + chip->subframe = *prop; + prop = of_get_property(sound, "layout-id", NULL); + if (prop) { + /* partly deprecate snd-powermac, for those machines + * that have a layout-id property for now */ + printk(KERN_INFO "snd-powermac no longer handles any " + "machines with a layout-id property " + "in the device-tree, use snd-aoa.\n"); + of_node_put(sound); + of_node_put(chip->node); + chip->node = NULL; + return -ENODEV; + } + /* This should be verified on older screamers */ + if (of_device_is_compatible(sound, "screamer")) { + chip->model = PMAC_SCREAMER; + // chip->can_byte_swap = 0; /* FIXME: check this */ + } + if (of_device_is_compatible(sound, "burgundy")) { + chip->model = PMAC_BURGUNDY; + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (of_device_is_compatible(sound, "daca")) { + chip->model = PMAC_DACA; + chip->can_capture = 0; /* no capture */ + chip->can_duplex = 0; + // chip->can_byte_swap = 0; /* FIXME: check this */ + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (of_device_is_compatible(sound, "tumbler")) { + chip->model = PMAC_TUMBLER; + chip->can_capture = of_machine_is_compatible("PowerMac4,2") + || of_machine_is_compatible("PowerBook3,2") + || of_machine_is_compatible("PowerBook3,3") + || of_machine_is_compatible("PowerBook4,1") + || of_machine_is_compatible("PowerBook4,2") + || of_machine_is_compatible("PowerBook4,3"); + chip->can_duplex = 0; + // chip->can_byte_swap = 0; /* FIXME: check this */ + chip->num_freqs = ARRAY_SIZE(tumbler_freqs); + chip->freq_table = tumbler_freqs; + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + if (of_device_is_compatible(sound, "snapper")) { + chip->model = PMAC_SNAPPER; + // chip->can_byte_swap = 0; /* FIXME: check this */ + chip->num_freqs = ARRAY_SIZE(tumbler_freqs); + chip->freq_table = tumbler_freqs; + chip->control_mask = MASK_IEPC | 0x11; /* disable IEE */ + } + prop = of_get_property(sound, "device-id", NULL); + if (prop) + chip->device_id = *prop; + dn = of_find_node_by_name(NULL, "perch"); + chip->has_iic = (dn != NULL); + of_node_put(dn); + + /* We need the PCI device for DMA allocations, let's use a crude method + * for now ... + */ + macio = macio_find(chip->node, macio_unknown); + if (macio == NULL) + printk(KERN_WARNING "snd-powermac: can't locate macio !\n"); + else { + struct pci_dev *pdev = NULL; + + for_each_pci_dev(pdev) { + struct device_node *np = pci_device_to_OF_node(pdev); + if (np && np == macio->of_node) { + chip->pdev = pdev; + break; + } + } + } + if (chip->pdev == NULL) + printk(KERN_WARNING "snd-powermac: can't locate macio PCI" + " device !\n"); + + detect_byte_swap(chip); + + /* look for a property saying what sample rates + are available */ + prop = of_get_property(sound, "sample-rates", &l); + if (! prop) + prop = of_get_property(sound, "output-frame-rates", &l); + if (prop) { + int i; + chip->freqs_ok = 0; + for (l /= sizeof(int); l > 0; --l) { + unsigned int r = *prop++; + /* Apple 'Fixed' format */ + if (r >= 0x10000) + r >>= 16; + for (i = 0; i < chip->num_freqs; ++i) { + if (r == chip->freq_table[i]) { + chip->freqs_ok |= (1 << i); + break; + } + } + } + } else { + /* assume only 44.1khz */ + chip->freqs_ok = 1; + } + + of_node_put(sound); + return 0; +} + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute + */ +static int pmac_auto_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->auto_mute; + return 0; +} + +static int pmac_auto_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + if (ucontrol->value.integer.value[0] != chip->auto_mute) { + chip->auto_mute = !!ucontrol->value.integer.value[0]; + if (chip->update_automute) + chip->update_automute(chip, 1); + return 1; + } + return 0; +} + +static int pmac_hp_detect_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + if (chip->detect_headphone) + ucontrol->value.integer.value[0] = chip->detect_headphone(chip); + else + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static struct snd_kcontrol_new auto_mute_controls[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Auto Mute Switch", + .info = snd_pmac_boolean_mono_info, + .get = pmac_auto_mute_get, + .put = pmac_auto_mute_put, + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Detection", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_pmac_boolean_mono_info, + .get = pmac_hp_detect_get, + }, +}; + +int snd_pmac_add_automute(struct snd_pmac *chip) +{ + int err; + chip->auto_mute = 1; + err = snd_ctl_add(chip->card, snd_ctl_new1(&auto_mute_controls[0], chip)); + if (err < 0) { + printk(KERN_ERR "snd-powermac: Failed to add automute control\n"); + return err; + } + chip->hp_detect_ctl = snd_ctl_new1(&auto_mute_controls[1], chip); + return snd_ctl_add(chip->card, chip->hp_detect_ctl); +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + +/* + * create and detect a pmac chip record + */ +int snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return) +{ + struct snd_pmac *chip; + struct device_node *np; + int i, err; + unsigned int irq; + unsigned long ctrl_addr, txdma_addr, rxdma_addr; + static struct snd_device_ops ops = { + .dev_free = snd_pmac_dev_free, + }; + + *chip_return = NULL; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->card = card; + + spin_lock_init(&chip->reg_lock); + chip->irq = chip->tx_irq = chip->rx_irq = -1; + + chip->playback.stream = SNDRV_PCM_STREAM_PLAYBACK; + chip->capture.stream = SNDRV_PCM_STREAM_CAPTURE; + + if ((err = snd_pmac_detect(chip)) < 0) + goto __error; + + if (snd_pmac_dbdma_alloc(chip, &chip->playback.cmd, PMAC_MAX_FRAGS + 1) < 0 || + snd_pmac_dbdma_alloc(chip, &chip->capture.cmd, PMAC_MAX_FRAGS + 1) < 0 || + snd_pmac_dbdma_alloc(chip, &chip->extra_dma, 2) < 0 || + snd_pmac_dbdma_alloc(chip, &emergency_dbdma, 2) < 0) { + err = -ENOMEM; + goto __error; + } + + np = chip->node; + chip->requested = 0; + if (chip->is_k2) { + static char *rnames[] = { + "Sound Control", "Sound DMA" }; + for (i = 0; i < 2; i ++) { + if (of_address_to_resource(np->parent, i, + &chip->rsrc[i])) { + printk(KERN_ERR "snd: can't translate rsrc " + " %d (%s)\n", i, rnames[i]); + err = -ENODEV; + goto __error; + } + if (request_mem_region(chip->rsrc[i].start, + resource_size(&chip->rsrc[i]), + rnames[i]) == NULL) { + printk(KERN_ERR "snd: can't request rsrc " + " %d (%s: %pR)\n", + i, rnames[i], &chip->rsrc[i]); + err = -ENODEV; + goto __error; + } + chip->requested |= (1 << i); + } + ctrl_addr = chip->rsrc[0].start; + txdma_addr = chip->rsrc[1].start; + rxdma_addr = txdma_addr + 0x100; + } else { + static char *rnames[] = { + "Sound Control", "Sound Tx DMA", "Sound Rx DMA" }; + for (i = 0; i < 3; i ++) { + if (of_address_to_resource(np, i, + &chip->rsrc[i])) { + printk(KERN_ERR "snd: can't translate rsrc " + " %d (%s)\n", i, rnames[i]); + err = -ENODEV; + goto __error; + } + if (request_mem_region(chip->rsrc[i].start, + resource_size(&chip->rsrc[i]), + rnames[i]) == NULL) { + printk(KERN_ERR "snd: can't request rsrc " + " %d (%s: %pR)\n", + i, rnames[i], &chip->rsrc[i]); + err = -ENODEV; + goto __error; + } + chip->requested |= (1 << i); + } + ctrl_addr = chip->rsrc[0].start; + txdma_addr = chip->rsrc[1].start; + rxdma_addr = chip->rsrc[2].start; + } + + chip->awacs = ioremap(ctrl_addr, 0x1000); + chip->playback.dma = ioremap(txdma_addr, 0x100); + chip->capture.dma = ioremap(rxdma_addr, 0x100); + if (chip->model <= PMAC_BURGUNDY) { + irq = irq_of_parse_and_map(np, 0); + if (request_irq(irq, snd_pmac_ctrl_intr, 0, + "PMac", (void*)chip)) { + snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", + irq); + err = -EBUSY; + goto __error; + } + chip->irq = irq; + } + irq = irq_of_parse_and_map(np, 1); + if (request_irq(irq, snd_pmac_tx_intr, 0, "PMac Output", (void*)chip)){ + snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", irq); + err = -EBUSY; + goto __error; + } + chip->tx_irq = irq; + irq = irq_of_parse_and_map(np, 2); + if (request_irq(irq, snd_pmac_rx_intr, 0, "PMac Input", (void*)chip)) { + snd_printk(KERN_ERR "pmac: unable to grab IRQ %d\n", irq); + err = -EBUSY; + goto __error; + } + chip->rx_irq = irq; + + snd_pmac_sound_feature(chip, 1); + + /* reset & enable interrupts */ + if (chip->model <= PMAC_BURGUNDY) + out_le32(&chip->awacs->control, chip->control_mask); + + /* Powerbooks have odd ways of enabling inputs such as + an expansion-bay CD or sound from an internal modem + or a PC-card modem. */ + if (chip->is_pbook_3400) { + /* Enable CD and PC-card sound inputs. */ + /* This is done by reading from address + * f301a000, + 0x10 to enable the expansion-bay + * CD sound input, + 0x80 to enable the PC-card + * sound input. The 0x100 enables the SCSI bus + * terminator power. + */ + chip->latch_base = ioremap (0xf301a000, 0x1000); + in_8(chip->latch_base + 0x190); + } else if (chip->is_pbook_G3) { + struct device_node* mio; + for (mio = chip->node->parent; mio; mio = mio->parent) { + if (strcmp(mio->name, "mac-io") == 0) { + struct resource r; + if (of_address_to_resource(mio, 0, &r) == 0) + chip->macio_base = + ioremap(r.start, 0x40); + break; + } + } + /* Enable CD sound input. */ + /* The relevant bits for writing to this byte are 0x8f. + * I haven't found out what the 0x80 bit does. + * For the 0xf bits, writing 3 or 7 enables the CD + * input, any other value disables it. Values + * 1, 3, 5, 7 enable the microphone. Values 0, 2, + * 4, 6, 8 - f enable the input from the modem. + */ + if (chip->macio_base) + out_8(chip->macio_base + 0x37, 3); + } + + /* Reset dbdma channels */ + snd_pmac_dbdma_reset(chip); + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) + goto __error; + + *chip_return = chip; + return 0; + + __error: + snd_pmac_free(chip); + return err; +} + + +/* + * sleep notify for powerbook + */ + +#ifdef CONFIG_PM + +/* + * Save state when going to sleep, restore it afterwards. + */ + +void snd_pmac_suspend(struct snd_pmac *chip) +{ + unsigned long flags; + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + if (chip->suspend) + chip->suspend(chip); + snd_pcm_suspend_all(chip->pcm); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_pmac_beep_stop(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (chip->irq >= 0) + disable_irq(chip->irq); + if (chip->tx_irq >= 0) + disable_irq(chip->tx_irq); + if (chip->rx_irq >= 0) + disable_irq(chip->rx_irq); + snd_pmac_sound_feature(chip, 0); +} + +void snd_pmac_resume(struct snd_pmac *chip) +{ + snd_pmac_sound_feature(chip, 1); + if (chip->resume) + chip->resume(chip); + /* enable CD sound input */ + if (chip->macio_base && chip->is_pbook_G3) + out_8(chip->macio_base + 0x37, 3); + else if (chip->is_pbook_3400) + in_8(chip->latch_base + 0x190); + + snd_pmac_pcm_set_format(chip); + + if (chip->irq >= 0) + enable_irq(chip->irq); + if (chip->tx_irq >= 0) + enable_irq(chip->tx_irq); + if (chip->rx_irq >= 0) + enable_irq(chip->rx_irq); + + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); +} + +#endif /* CONFIG_PM */ + diff --git a/sound/ppc/pmac.h b/sound/ppc/pmac.h new file mode 100644 index 000000000..25c512c2d --- /dev/null +++ b/sound/ppc/pmac.h @@ -0,0 +1,210 @@ +/* + * Driver for PowerMac onboard soundchips + * Copyright (c) 2001 by Takashi Iwai + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef __PMAC_H +#define __PMAC_H + +#include +#include +#include "awacs.h" + +#include +#ifdef CONFIG_ADB_CUDA +#include +#endif +#ifdef CONFIG_ADB_PMU +#include +#endif +#include +#include +#include +#include +#include +#include + +/* maximum number of fragments */ +#define PMAC_MAX_FRAGS 32 + + +#define PMAC_SUPPORT_AUTOMUTE + +/* + * DBDMA space + */ +struct pmac_dbdma { + dma_addr_t dma_base; + dma_addr_t addr; + struct dbdma_cmd __iomem *cmds; + void *space; + int size; +}; + +/* + * playback/capture stream + */ +struct pmac_stream { + int running; /* boolean */ + + int stream; /* PLAYBACK/CAPTURE */ + + int dma_size; /* in bytes */ + int period_size; /* in bytes */ + int buffer_size; /* in kbytes */ + int nperiods, cur_period; + + struct pmac_dbdma cmd; + volatile struct dbdma_regs __iomem *dma; + + struct snd_pcm_substream *substream; + + unsigned int cur_freqs; /* currently available frequencies */ + unsigned int cur_formats; /* currently available formats */ +}; + + +/* + */ + +enum snd_pmac_model { + PMAC_AWACS, PMAC_SCREAMER, PMAC_BURGUNDY, PMAC_DACA, PMAC_TUMBLER, + PMAC_SNAPPER +}; + +struct snd_pmac { + struct snd_card *card; + + /* h/w info */ + struct device_node *node; + struct pci_dev *pdev; + unsigned int revision; + unsigned int manufacturer; + unsigned int subframe; + unsigned int device_id; + enum snd_pmac_model model; + + unsigned int has_iic : 1; + unsigned int is_pbook_3400 : 1; + unsigned int is_pbook_G3 : 1; + unsigned int is_k2 : 1; + + unsigned int can_byte_swap : 1; + unsigned int can_duplex : 1; + unsigned int can_capture : 1; + + unsigned int auto_mute : 1; + unsigned int initialized : 1; + unsigned int feature_is_set : 1; + + unsigned int requested; + struct resource rsrc[3]; + + int num_freqs; + int *freq_table; + unsigned int freqs_ok; /* bit flags */ + unsigned int formats_ok; /* pcm hwinfo */ + int active; + int rate_index; + int format; /* current format */ + + spinlock_t reg_lock; + volatile struct awacs_regs __iomem *awacs; + int awacs_reg[8]; /* register cache */ + unsigned int hp_stat_mask; + + unsigned char __iomem *latch_base; + unsigned char __iomem *macio_base; + + struct pmac_stream playback; + struct pmac_stream capture; + + struct pmac_dbdma extra_dma; + + int irq, tx_irq, rx_irq; + + struct snd_pcm *pcm; + + struct pmac_beep *beep; + + unsigned int control_mask; /* control mask */ + + /* mixer stuffs */ + void *mixer_data; + void (*mixer_free)(struct snd_pmac *); + struct snd_kcontrol *master_sw_ctl; + struct snd_kcontrol *speaker_sw_ctl; + struct snd_kcontrol *drc_sw_ctl; /* only used for tumbler -ReneR */ + struct snd_kcontrol *hp_detect_ctl; + struct snd_kcontrol *lineout_sw_ctl; + + /* lowlevel callbacks */ + void (*set_format)(struct snd_pmac *chip); + void (*update_automute)(struct snd_pmac *chip, int do_notify); + int (*detect_headphone)(struct snd_pmac *chip); +#ifdef CONFIG_PM + void (*suspend)(struct snd_pmac *chip); + void (*resume)(struct snd_pmac *chip); +#endif + +}; + + +/* exported functions */ +int snd_pmac_new(struct snd_card *card, struct snd_pmac **chip_return); +int snd_pmac_pcm_new(struct snd_pmac *chip); +int snd_pmac_attach_beep(struct snd_pmac *chip); +void snd_pmac_detach_beep(struct snd_pmac *chip); +void snd_pmac_beep_stop(struct snd_pmac *chip); +unsigned int snd_pmac_rate_index(struct snd_pmac *chip, struct pmac_stream *rec, unsigned int rate); + +void snd_pmac_beep_dma_start(struct snd_pmac *chip, int bytes, unsigned long addr, int speed); +void snd_pmac_beep_dma_stop(struct snd_pmac *chip); + +#ifdef CONFIG_PM +void snd_pmac_suspend(struct snd_pmac *chip); +void snd_pmac_resume(struct snd_pmac *chip); +#endif + +/* initialize mixer */ +int snd_pmac_awacs_init(struct snd_pmac *chip); +int snd_pmac_burgundy_init(struct snd_pmac *chip); +int snd_pmac_daca_init(struct snd_pmac *chip); +int snd_pmac_tumbler_init(struct snd_pmac *chip); +int snd_pmac_tumbler_post_init(void); + +/* i2c functions */ +struct pmac_keywest { + int addr; + struct i2c_client *client; + int id; + int (*init_client)(struct pmac_keywest *i2c); + char *name; +}; + +int snd_pmac_keywest_init(struct pmac_keywest *i2c); +void snd_pmac_keywest_cleanup(struct pmac_keywest *i2c); + +/* misc */ +#define snd_pmac_boolean_stereo_info snd_ctl_boolean_stereo_info +#define snd_pmac_boolean_mono_info snd_ctl_boolean_mono_info + +int snd_pmac_add_automute(struct snd_pmac *chip); + +#endif /* __PMAC_H */ diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c new file mode 100644 index 000000000..7c70ba5e2 --- /dev/null +++ b/sound/ppc/powermac.c @@ -0,0 +1,198 @@ +/* + * Driver for PowerMac AWACS + * Copyright (c) 2001 by Takashi Iwai + * based on dmasound.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "pmac.h" +#include "awacs.h" +#include "burgundy.h" + +#define CHIP_NAME "PMac" + +MODULE_DESCRIPTION("PowerMac"); +MODULE_SUPPORTED_DEVICE("{{Apple,PowerMac}}"); +MODULE_LICENSE("GPL"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static bool enable_beep = 1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for " CHIP_NAME " soundchip."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for " CHIP_NAME " soundchip."); +module_param(enable_beep, bool, 0444); +MODULE_PARM_DESC(enable_beep, "Enable beep using PCM."); + +static struct platform_device *device; + + +/* + */ + +static int snd_pmac_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_pmac *chip; + char *name_ext; + int err; + + err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + if ((err = snd_pmac_new(card, &chip)) < 0) + goto __error; + card->private_data = chip; + + switch (chip->model) { + case PMAC_BURGUNDY: + strcpy(card->driver, "PMac Burgundy"); + strcpy(card->shortname, "PowerMac Burgundy"); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + if ((err = snd_pmac_burgundy_init(chip)) < 0) + goto __error; + break; + case PMAC_DACA: + strcpy(card->driver, "PMac DACA"); + strcpy(card->shortname, "PowerMac DACA"); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + if ((err = snd_pmac_daca_init(chip)) < 0) + goto __error; + break; + case PMAC_TUMBLER: + case PMAC_SNAPPER: + name_ext = chip->model == PMAC_TUMBLER ? "Tumbler" : "Snapper"; + sprintf(card->driver, "PMac %s", name_ext); + sprintf(card->shortname, "PowerMac %s", name_ext); + sprintf(card->longname, "%s (Dev %d) Sub-frame %d", + card->shortname, chip->device_id, chip->subframe); + err = snd_pmac_tumbler_init(chip); + if (err < 0) + goto __error; + err = snd_pmac_tumbler_post_init(); + if (err < 0) + goto __error; + break; + case PMAC_AWACS: + case PMAC_SCREAMER: + name_ext = chip->model == PMAC_SCREAMER ? "Screamer" : "AWACS"; + sprintf(card->driver, "PMac %s", name_ext); + sprintf(card->shortname, "PowerMac %s", name_ext); + if (chip->is_pbook_3400) + name_ext = " [PB3400]"; + else if (chip->is_pbook_G3) + name_ext = " [PBG3]"; + else + name_ext = ""; + sprintf(card->longname, "%s%s Rev %d", + card->shortname, name_ext, chip->revision); + if ((err = snd_pmac_awacs_init(chip)) < 0) + goto __error; + break; + default: + snd_printk(KERN_ERR "unsupported hardware %d\n", chip->model); + err = -EINVAL; + goto __error; + } + + if ((err = snd_pmac_pcm_new(chip)) < 0) + goto __error; + + chip->initialized = 1; + if (enable_beep) + snd_pmac_attach_beep(chip); + + if ((err = snd_card_register(card)) < 0) + goto __error; + + platform_set_drvdata(devptr, card); + return 0; + +__error: + snd_card_free(card); + return err; +} + + +static int snd_pmac_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int snd_pmac_driver_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + snd_pmac_suspend(card->private_data); + return 0; +} + +static int snd_pmac_driver_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + snd_pmac_resume(card->private_data); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_pmac_pm, snd_pmac_driver_suspend, snd_pmac_driver_resume); +#define SND_PMAC_PM_OPS &snd_pmac_pm +#else +#define SND_PMAC_PM_OPS NULL +#endif + +#define SND_PMAC_DRIVER "snd_powermac" + +static struct platform_driver snd_pmac_driver = { + .probe = snd_pmac_probe, + .remove = snd_pmac_remove, + .driver = { + .name = SND_PMAC_DRIVER, + .pm = SND_PMAC_PM_OPS, + }, +}; + +static int __init alsa_card_pmac_init(void) +{ + int err; + + if ((err = platform_driver_register(&snd_pmac_driver)) < 0) + return err; + device = platform_device_register_simple(SND_PMAC_DRIVER, -1, NULL, 0); + return 0; + +} + +static void __exit alsa_card_pmac_exit(void) +{ + if (!IS_ERR(device)) + platform_device_unregister(device); + platform_driver_unregister(&snd_pmac_driver); +} + +module_init(alsa_card_pmac_init) +module_exit(alsa_card_pmac_exit) diff --git a/sound/ppc/snd_ps3.c b/sound/ppc/snd_ps3.c new file mode 100644 index 000000000..abe031c9d --- /dev/null +++ b/sound/ppc/snd_ps3.c @@ -0,0 +1,1164 @@ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All rights reserved. + * Copyright 2006, 2007 Sony Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the Licence. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "snd_ps3.h" +#include "snd_ps3_reg.h" + + +/* + * global + */ +static struct snd_ps3_card_info the_card; + +static int snd_ps3_start_delay = CONFIG_SND_PS3_DEFAULT_START_DELAY; + +module_param_named(start_delay, snd_ps3_start_delay, uint, 0644); +MODULE_PARM_DESC(start_delay, "time to insert silent data in ms"); + +static int index = SNDRV_DEFAULT_IDX1; +static char *id = SNDRV_DEFAULT_STR1; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for PS3 soundchip."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for PS3 soundchip."); + + +/* + * PS3 audio register access + */ +static inline u32 read_reg(unsigned int reg) +{ + return in_be32(the_card.mapped_mmio_vaddr + reg); +} +static inline void write_reg(unsigned int reg, u32 val) +{ + out_be32(the_card.mapped_mmio_vaddr + reg, val); +} +static inline void update_reg(unsigned int reg, u32 or_val) +{ + u32 newval = read_reg(reg) | or_val; + write_reg(reg, newval); +} +static inline void update_mask_reg(unsigned int reg, u32 mask, u32 or_val) +{ + u32 newval = (read_reg(reg) & mask) | or_val; + write_reg(reg, newval); +} + +/* + * ALSA defs + */ +static const struct snd_pcm_hardware snd_ps3_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE), + .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 = 2, /* stereo only */ + .channels_max = 2, + + .buffer_bytes_max = PS3_AUDIO_FIFO_SIZE * 64, + + /* interrupt by four stages */ + .period_bytes_min = PS3_AUDIO_FIFO_STAGE_SIZE * 4, + .period_bytes_max = PS3_AUDIO_FIFO_STAGE_SIZE * 4, + + .periods_min = 16, + .periods_max = 32, /* buffer_size_max/ period_bytes_max */ + + .fifo_size = PS3_AUDIO_FIFO_SIZE +}; + +static int snd_ps3_verify_dma_stop(struct snd_ps3_card_info *card, + int count, int force_stop) +{ + int dma_ch, done, retries, stop_forced = 0; + uint32_t status; + + for (dma_ch = 0; dma_ch < 8; dma_ch++) { + retries = count; + do { + status = read_reg(PS3_AUDIO_KICK(dma_ch)) & + PS3_AUDIO_KICK_STATUS_MASK; + switch (status) { + case PS3_AUDIO_KICK_STATUS_DONE: + case PS3_AUDIO_KICK_STATUS_NOTIFY: + case PS3_AUDIO_KICK_STATUS_CLEAR: + case PS3_AUDIO_KICK_STATUS_ERROR: + done = 1; + break; + default: + done = 0; + udelay(10); + } + } while (!done && --retries); + if (!retries && force_stop) { + pr_info("%s: DMA ch %d is not stopped.", + __func__, dma_ch); + /* last resort. force to stop dma. + * NOTE: this cause DMA done interrupts + */ + update_reg(PS3_AUDIO_CONFIG, PS3_AUDIO_CONFIG_CLEAR); + stop_forced = 1; + } + } + return stop_forced; +} + +/* + * wait for all dma is done. + * NOTE: caller should reset card->running before call. + * If not, the interrupt handler will re-start DMA, + * then DMA is never stopped. + */ +static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card) +{ + int stop_forced; + /* + * wait for the last dma is done + */ + + /* + * expected maximum DMA done time is 5.7ms + something (DMA itself). + * 5.7ms is from 16bit/sample 2ch 44.1Khz; the time next + * DMA kick event would occur. + */ + stop_forced = snd_ps3_verify_dma_stop(card, 700, 1); + + /* + * clear outstanding interrupts. + */ + update_reg(PS3_AUDIO_INTR_0, 0); + update_reg(PS3_AUDIO_AX_IS, 0); + + /* + *revert CLEAR bit since it will not reset automatically after DMA stop + */ + if (stop_forced) + update_mask_reg(PS3_AUDIO_CONFIG, ~PS3_AUDIO_CONFIG_CLEAR, 0); + /* ensure the hardware sees changes */ + wmb(); +} + +static void snd_ps3_kick_dma(struct snd_ps3_card_info *card) +{ + + update_reg(PS3_AUDIO_KICK(0), PS3_AUDIO_KICK_REQUEST); + /* ensure the hardware sees the change */ + wmb(); +} + +/* + * convert virtual addr to ioif bus addr. + */ +static dma_addr_t v_to_bus(struct snd_ps3_card_info *card, void *paddr, int ch) +{ + return card->dma_start_bus_addr[ch] + + (paddr - card->dma_start_vaddr[ch]); +}; + + +/* + * increment ring buffer pointer. + * NOTE: caller must hold write spinlock + */ +static void snd_ps3_bump_buffer(struct snd_ps3_card_info *card, + enum snd_ps3_ch ch, size_t byte_count, + int stage) +{ + if (!stage) + card->dma_last_transfer_vaddr[ch] = + card->dma_next_transfer_vaddr[ch]; + card->dma_next_transfer_vaddr[ch] += byte_count; + if ((card->dma_start_vaddr[ch] + (card->dma_buffer_size / 2)) <= + card->dma_next_transfer_vaddr[ch]) { + card->dma_next_transfer_vaddr[ch] = card->dma_start_vaddr[ch]; + } +} +/* + * setup dmac to send data to audio and attenuate samples on the ring buffer + */ +static int snd_ps3_program_dma(struct snd_ps3_card_info *card, + enum snd_ps3_dma_filltype filltype) +{ + /* this dmac does not support over 4G */ + uint32_t dma_addr; + int fill_stages, dma_ch, stage; + enum snd_ps3_ch ch; + uint32_t ch0_kick_event = 0; /* initialize to mute gcc */ + void *start_vaddr; + unsigned long irqsave; + int silent = 0; + + switch (filltype) { + case SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL: + silent = 1; + /* intentionally fall thru */ + case SND_PS3_DMA_FILLTYPE_FIRSTFILL: + ch0_kick_event = PS3_AUDIO_KICK_EVENT_ALWAYS; + break; + + case SND_PS3_DMA_FILLTYPE_SILENT_RUNNING: + silent = 1; + /* intentionally fall thru */ + case SND_PS3_DMA_FILLTYPE_RUNNING: + ch0_kick_event = PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY; + break; + } + + snd_ps3_verify_dma_stop(card, 700, 0); + fill_stages = 4; + spin_lock_irqsave(&card->dma_lock, irqsave); + for (ch = 0; ch < 2; ch++) { + start_vaddr = card->dma_next_transfer_vaddr[0]; + for (stage = 0; stage < fill_stages; stage++) { + dma_ch = stage * 2 + ch; + if (silent) + dma_addr = card->null_buffer_start_dma_addr; + else + dma_addr = + v_to_bus(card, + card->dma_next_transfer_vaddr[ch], + ch); + + write_reg(PS3_AUDIO_SOURCE(dma_ch), + (PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY | + dma_addr)); + + /* dst: fixed to 3wire#0 */ + if (ch == 0) + write_reg(PS3_AUDIO_DEST(dma_ch), + (PS3_AUDIO_DEST_TARGET_AUDIOFIFO | + PS3_AUDIO_AO_3W_LDATA(0))); + else + write_reg(PS3_AUDIO_DEST(dma_ch), + (PS3_AUDIO_DEST_TARGET_AUDIOFIFO | + PS3_AUDIO_AO_3W_RDATA(0))); + + /* count always 1 DMA block (1/2 stage = 128 bytes) */ + write_reg(PS3_AUDIO_DMASIZE(dma_ch), 0); + /* bump pointer if needed */ + if (!silent) + snd_ps3_bump_buffer(card, ch, + PS3_AUDIO_DMAC_BLOCK_SIZE, + stage); + + /* kick event */ + if (dma_ch == 0) + write_reg(PS3_AUDIO_KICK(dma_ch), + ch0_kick_event); + else + write_reg(PS3_AUDIO_KICK(dma_ch), + PS3_AUDIO_KICK_EVENT_AUDIO_DMA(dma_ch + - 1) | + PS3_AUDIO_KICK_REQUEST); + } + } + /* ensure the hardware sees the change */ + wmb(); + spin_unlock_irqrestore(&card->dma_lock, irqsave); + + return 0; +} + +/* + * Interrupt handler + */ +static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id) +{ + + uint32_t port_intr; + int underflow_occured = 0; + struct snd_ps3_card_info *card = dev_id; + + if (!card->running) { + update_reg(PS3_AUDIO_AX_IS, 0); + update_reg(PS3_AUDIO_INTR_0, 0); + return IRQ_HANDLED; + } + + port_intr = read_reg(PS3_AUDIO_AX_IS); + /* + *serial buffer empty detected (every 4 times), + *program next dma and kick it + */ + if (port_intr & PS3_AUDIO_AX_IE_ASOBEIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBEIE(0)); + if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, port_intr); + underflow_occured = 1; + } + if (card->silent) { + /* we are still in silent time */ + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); + snd_ps3_kick_dma(card); + card->silent--; + } else { + snd_ps3_program_dma(card, + (underflow_occured) ? + SND_PS3_DMA_FILLTYPE_FIRSTFILL : + SND_PS3_DMA_FILLTYPE_RUNNING); + snd_ps3_kick_dma(card); + snd_pcm_period_elapsed(card->substream); + } + } else if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) { + write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBUIE(0)); + /* + * serial out underflow, but buffer empty not detected. + * in this case, fill fifo with 0 to recover. After + * filling dummy data, serial automatically start to + * consume them and then will generate normal buffer + * empty interrupts. + * If both buffer underflow and buffer empty are occurred, + * it is better to do nomal data transfer than empty one + */ + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + } + /* clear interrupt cause */ + return IRQ_HANDLED; +}; + +/* + * audio mute on/off + * mute_on : 0 output enabled + * 1 mute + */ +static int snd_ps3_mute(int mute_on) +{ + return ps3av_audio_mute(mute_on); +} + +/* + * av setting + * NOTE: calling this function may generate audio interrupt. + */ +static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card) +{ + int ret, retries, i; + pr_debug("%s: start\n", __func__); + + ret = ps3av_set_audio_mode(card->avs.avs_audio_ch, + card->avs.avs_audio_rate, + card->avs.avs_audio_width, + card->avs.avs_audio_format, + card->avs.avs_audio_source); + /* + * Reset the following unwanted settings: + */ + + /* disable all 3wire buffers */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOEN(0) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(1) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(2) | + PS3_AUDIO_AO_3WMCTRL_ASOEN(3)), + 0); + wmb(); /* ensure the hardware sees the change */ + /* wait for actually stopped */ + retries = 1000; + while ((read_reg(PS3_AUDIO_AO_3WMCTRL) & + (PS3_AUDIO_AO_3WMCTRL_ASORUN(0) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(1) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(2) | + PS3_AUDIO_AO_3WMCTRL_ASORUN(3))) && + --retries) { + udelay(1); + } + + /* reset buffer pointer */ + for (i = 0; i < 4; i++) { + update_reg(PS3_AUDIO_AO_3WCTRL(i), + PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET); + udelay(10); + } + wmb(); /* ensure the hardware actually start resetting */ + + /* enable 3wire#0 buffer */ + update_reg(PS3_AUDIO_AO_3WMCTRL, PS3_AUDIO_AO_3WMCTRL_ASOEN(0)); + + + /* In 24bit mode,ALSA inserts a zero byte at first byte of per sample */ + update_mask_reg(PS3_AUDIO_AO_3WCTRL(0), + ~PS3_AUDIO_AO_3WCTRL_ASODF, + PS3_AUDIO_AO_3WCTRL_ASODF_LSB); + update_mask_reg(PS3_AUDIO_AO_SPDCTRL(0), + ~PS3_AUDIO_AO_SPDCTRL_SPODF, + PS3_AUDIO_AO_SPDCTRL_SPODF_LSB); + /* ensure all the setting above is written back to register */ + wmb(); + /* avsetting driver altered AX_IE, caller must reset it if you want */ + pr_debug("%s: end\n", __func__); + return ret; +} + +/* + * set sampling rate according to the substream + */ +static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + struct snd_ps3_avsetting_info avs; + int ret; + + avs = card->avs; + + pr_debug("%s: called freq=%d width=%d\n", __func__, + substream->runtime->rate, + snd_pcm_format_width(substream->runtime->format)); + + pr_debug("%s: before freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + /* sample rate */ + switch (substream->runtime->rate) { + case 44100: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_44K; + break; + case 48000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + break; + case 88200: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_88K; + break; + case 96000: + avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_96K; + break; + default: + pr_info("%s: invalid rate %d\n", __func__, + substream->runtime->rate); + return 1; + } + + /* width */ + switch (snd_pcm_format_width(substream->runtime->format)) { + case 16: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + break; + case 24: + avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_24; + break; + default: + pr_info("%s: invalid width %d\n", __func__, + snd_pcm_format_width(substream->runtime->format)); + return 1; + } + + memcpy(avs.avs_cs_info, ps3av_mode_cs_info, 8); + + if (memcmp(&card->avs, &avs, sizeof(avs))) { + pr_debug("%s: after freq=%d width=%d\n", __func__, + card->avs.avs_audio_rate, card->avs.avs_audio_width); + + card->avs = avs; + snd_ps3_change_avsetting(card); + ret = 0; + } else + ret = 1; + + /* check CS non-audio bit and mute accordingly */ + if (avs.avs_cs_info[0] & 0x02) + ps3av_audio_mute_analog(1); /* mute if non-audio */ + else + ps3av_audio_mute_analog(0); + + return ret; +} + +/* + * PCM operators + */ +static int snd_ps3_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + int pcm_index; + + pcm_index = substream->pcm->device; + /* to retrieve substream/runtime in interrupt handler */ + card->substream = substream; + + runtime->hw = snd_ps3_pcm_hw; + + card->start_delay = snd_ps3_start_delay; + + /* mute off */ + snd_ps3_mute(0); /* this function sleep */ + + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PS3_AUDIO_FIFO_STAGE_SIZE * 4 * 2); + return 0; +}; + +static int snd_ps3_pcm_close(struct snd_pcm_substream *substream) +{ + /* mute on */ + snd_ps3_mute(1); + return 0; +}; + +static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + size_t size; + + /* alloc transport buffer */ + size = params_buffer_bytes(hw_params); + snd_pcm_lib_malloc_pages(substream, size); + return 0; +}; + +static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +}; + +static int snd_ps3_delay_to_bytes(struct snd_pcm_substream *substream, + unsigned int delay_ms) +{ + int ret; + int rate ; + + rate = substream->runtime->rate; + ret = snd_pcm_format_size(substream->runtime->format, + rate * delay_ms / 1000) + * substream->runtime->channels; + + pr_debug("%s: time=%d rate=%d bytes=%ld, frames=%d, ret=%d\n", + __func__, + delay_ms, + rate, + snd_pcm_format_size(substream->runtime->format, rate), + rate * delay_ms / 1000, + ret); + + return ret; +}; + +static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + unsigned long irqsave; + + if (!snd_ps3_set_avsetting(substream)) { + /* some parameter changed */ + write_reg(PS3_AUDIO_AX_IE, + PS3_AUDIO_AX_IE_ASOBEIE(0) | + PS3_AUDIO_AX_IE_ASOBUIE(0)); + /* + * let SPDIF device re-lock with SPDIF signal, + * start with some silence + */ + card->silent = snd_ps3_delay_to_bytes(substream, + card->start_delay) / + (PS3_AUDIO_FIFO_STAGE_SIZE * 4); /* every 4 times */ + } + + /* restart ring buffer pointer */ + spin_lock_irqsave(&card->dma_lock, irqsave); + { + card->dma_buffer_size = runtime->dma_bytes; + + card->dma_last_transfer_vaddr[SND_PS3_CH_L] = + card->dma_next_transfer_vaddr[SND_PS3_CH_L] = + card->dma_start_vaddr[SND_PS3_CH_L] = + runtime->dma_area; + card->dma_start_bus_addr[SND_PS3_CH_L] = runtime->dma_addr; + + card->dma_last_transfer_vaddr[SND_PS3_CH_R] = + card->dma_next_transfer_vaddr[SND_PS3_CH_R] = + card->dma_start_vaddr[SND_PS3_CH_R] = + runtime->dma_area + (runtime->dma_bytes / 2); + card->dma_start_bus_addr[SND_PS3_CH_R] = + runtime->dma_addr + (runtime->dma_bytes / 2); + + pr_debug("%s: vaddr=%p bus=%#llx\n", __func__, + card->dma_start_vaddr[SND_PS3_CH_L], + card->dma_start_bus_addr[SND_PS3_CH_L]); + + } + spin_unlock_irqrestore(&card->dma_lock, irqsave); + + /* ensure the hardware sees the change */ + mb(); + + return 0; +}; + +static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* clear outstanding interrupts */ + update_reg(PS3_AUDIO_AX_IS, 0); + + spin_lock(&card->dma_lock); + { + card->running = 1; + } + spin_unlock(&card->dma_lock); + + snd_ps3_program_dma(card, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + while (read_reg(PS3_AUDIO_KICK(7)) & + PS3_AUDIO_KICK_STATUS_MASK) { + udelay(1); + } + snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_RUNNING); + snd_ps3_kick_dma(card); + break; + + case SNDRV_PCM_TRIGGER_STOP: + spin_lock(&card->dma_lock); + { + card->running = 0; + } + spin_unlock(&card->dma_lock); + snd_ps3_wait_for_dma_stop(card); + break; + default: + break; + + } + + return ret; +}; + +/* + * report current pointer + */ +static snd_pcm_uframes_t snd_ps3_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream); + size_t bytes; + snd_pcm_uframes_t ret; + + spin_lock(&card->dma_lock); + { + bytes = (size_t)(card->dma_last_transfer_vaddr[SND_PS3_CH_L] - + card->dma_start_vaddr[SND_PS3_CH_L]); + } + spin_unlock(&card->dma_lock); + + ret = bytes_to_frames(substream->runtime, bytes * 2); + + return ret; +}; + +/* + * SPDIF status bits controls + */ +static int snd_ps3_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; +} + +/* FIXME: ps3av_set_audio_mode() assumes only consumer mode */ +static int snd_ps3_spdif_cmask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0xff, 8); + return 0; +} + +static int snd_ps3_spdif_pmask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int snd_ps3_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memcpy(ucontrol->value.iec958.status, ps3av_mode_cs_info, 8); + return 0; +} + +static int snd_ps3_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (memcmp(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8)) { + memcpy(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8); + return 1; + } + return 0; +} + +static struct snd_kcontrol_new spdif_ctls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .info = snd_ps3_spdif_mask_info, + .get = snd_ps3_spdif_cmask_get, + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK), + .info = snd_ps3_spdif_mask_info, + .get = snd_ps3_spdif_pmask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_ps3_spdif_mask_info, + .get = snd_ps3_spdif_default_get, + .put = snd_ps3_spdif_default_put, + }, +}; + +static const struct snd_pcm_ops snd_ps3_pcm_spdif_ops = { + .open = snd_ps3_pcm_open, + .close = snd_ps3_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ps3_pcm_hw_params, + .hw_free = snd_ps3_pcm_hw_free, + .prepare = snd_ps3_pcm_prepare, + .trigger = snd_ps3_pcm_trigger, + .pointer = snd_ps3_pcm_pointer, +}; + + +static int snd_ps3_map_mmio(void) +{ + the_card.mapped_mmio_vaddr = + ioremap(the_card.ps3_dev->m_region->bus_addr, + the_card.ps3_dev->m_region->len); + + if (!the_card.mapped_mmio_vaddr) { + pr_info("%s: ioremap 0 failed p=%#lx l=%#lx \n", + __func__, the_card.ps3_dev->m_region->lpar_addr, + the_card.ps3_dev->m_region->len); + return -ENXIO; + } + + return 0; +}; + +static void snd_ps3_unmap_mmio(void) +{ + iounmap(the_card.mapped_mmio_vaddr); + the_card.mapped_mmio_vaddr = NULL; +} + +static int snd_ps3_allocate_irq(void) +{ + int ret; + u64 lpar_addr, lpar_size; + u64 __iomem *mapped; + + /* FIXME: move this to device_init (H/W probe) */ + + /* get irq outlet */ + ret = lv1_gpu_device_map(1, &lpar_addr, &lpar_size); + if (ret) { + pr_info("%s: device map 1 failed %d\n", __func__, + ret); + return -ENXIO; + } + + mapped = ioremap(lpar_addr, lpar_size); + if (!mapped) { + pr_info("%s: ioremap 1 failed \n", __func__); + return -ENXIO; + } + + the_card.audio_irq_outlet = in_be64(mapped); + + iounmap(mapped); + ret = lv1_gpu_device_unmap(1); + if (ret) + pr_info("%s: unmap 1 failed\n", __func__); + + /* irq */ + ret = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY, + the_card.audio_irq_outlet, + &the_card.irq_no); + if (ret) { + pr_info("%s:ps3_alloc_irq failed (%d)\n", __func__, ret); + return ret; + } + + ret = request_irq(the_card.irq_no, snd_ps3_interrupt, 0, + SND_PS3_DRIVER_NAME, &the_card); + if (ret) { + pr_info("%s: request_irq failed (%d)\n", __func__, ret); + goto cleanup_irq; + } + + return 0; + + cleanup_irq: + ps3_irq_plug_destroy(the_card.irq_no); + return ret; +}; + +static void snd_ps3_free_irq(void) +{ + free_irq(the_card.irq_no, &the_card); + ps3_irq_plug_destroy(the_card.irq_no); +} + +static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start) +{ + uint64_t val; + int ret; + + val = (ioaddr_start & (0x0fUL << 32)) >> (32 - 20) | + (0x03UL << 24) | + (0x0fUL << 12) | + (PS3_AUDIO_IOID); + + ret = lv1_gpu_attribute(0x100, 0x007, val); + if (ret) + pr_info("%s: gpu_attribute failed %d\n", __func__, + ret); +} + +static void snd_ps3_audio_fixup(struct snd_ps3_card_info *card) +{ + /* + * avsetting driver seems to never change the following + * so, init them here once + */ + + /* no dma interrupt needed */ + write_reg(PS3_AUDIO_INTR_EN_0, 0); + + /* use every 4 buffer empty interrupt */ + update_mask_reg(PS3_AUDIO_AX_IC, + PS3_AUDIO_AX_IC_AASOIMD_MASK, + PS3_AUDIO_AX_IC_AASOIMD_EVERY4); + + /* enable 3wire clocks */ + update_mask_reg(PS3_AUDIO_AO_3WMCTRL, + ~(PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED | + PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED), + 0); + update_reg(PS3_AUDIO_AO_3WMCTRL, + PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT); +} + +static int snd_ps3_init_avsetting(struct snd_ps3_card_info *card) +{ + int ret; + pr_debug("%s: start\n", __func__); + card->avs.avs_audio_ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2; + card->avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K; + card->avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16; + card->avs.avs_audio_format = PS3AV_CMD_AUDIO_FORMAT_PCM; + card->avs.avs_audio_source = PS3AV_CMD_AUDIO_SOURCE_SERIAL; + memcpy(card->avs.avs_cs_info, ps3av_mode_cs_info, 8); + + ret = snd_ps3_change_avsetting(card); + + snd_ps3_audio_fixup(card); + + /* to start to generate SPDIF signal, fill data */ + snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL); + snd_ps3_kick_dma(card); + pr_debug("%s: end\n", __func__); + return ret; +} + +static int snd_ps3_driver_probe(struct ps3_system_bus_device *dev) +{ + int i, ret; + u64 lpar_addr, lpar_size; + static u64 dummy_mask; + + if (WARN_ON(!firmware_has_feature(FW_FEATURE_PS3_LV1))) + return -ENODEV; + if (WARN_ON(dev->match_id != PS3_MATCH_ID_SOUND)) + return -ENODEV; + + the_card.ps3_dev = dev; + + ret = ps3_open_hv_device(dev); + + if (ret) + return -ENXIO; + + /* setup MMIO */ + ret = lv1_gpu_device_map(2, &lpar_addr, &lpar_size); + if (ret) { + pr_info("%s: device map 2 failed %d\n", __func__, ret); + goto clean_open; + } + ps3_mmio_region_init(dev, dev->m_region, lpar_addr, lpar_size, + PAGE_SHIFT); + + ret = snd_ps3_map_mmio(); + if (ret) + goto clean_dev_map; + + /* setup DMA area */ + ps3_dma_region_init(dev, dev->d_region, + PAGE_SHIFT, /* use system page size */ + 0, /* dma type; not used */ + NULL, + _ALIGN_UP(SND_PS3_DMA_REGION_SIZE, PAGE_SIZE)); + dev->d_region->ioid = PS3_AUDIO_IOID; + + ret = ps3_dma_region_create(dev->d_region); + if (ret) { + pr_info("%s: region_create\n", __func__); + goto clean_mmio; + } + + dummy_mask = DMA_BIT_MASK(32); + dev->core.dma_mask = &dummy_mask; + dma_set_coherent_mask(&dev->core, dummy_mask); + + snd_ps3_audio_set_base_addr(dev->d_region->bus_addr); + + /* CONFIG_SND_PS3_DEFAULT_START_DELAY */ + the_card.start_delay = snd_ps3_start_delay; + + /* irq */ + if (snd_ps3_allocate_irq()) { + ret = -ENXIO; + goto clean_dma_region; + } + + /* create card instance */ + ret = snd_card_new(&dev->core, index, id, THIS_MODULE, + 0, &the_card.card); + if (ret < 0) + goto clean_irq; + + strcpy(the_card.card->driver, "PS3"); + strcpy(the_card.card->shortname, "PS3"); + strcpy(the_card.card->longname, "PS3 sound"); + + /* create control elements */ + for (i = 0; i < ARRAY_SIZE(spdif_ctls); i++) { + ret = snd_ctl_add(the_card.card, + snd_ctl_new1(&spdif_ctls[i], &the_card)); + if (ret < 0) + goto clean_card; + } + + /* create PCM devices instance */ + /* NOTE:this driver works assuming pcm:substream = 1:1 */ + ret = snd_pcm_new(the_card.card, + "SPDIF", + 0, /* instance index, will be stored pcm.device*/ + 1, /* output substream */ + 0, /* input substream */ + &(the_card.pcm)); + if (ret) + goto clean_card; + + the_card.pcm->private_data = &the_card; + strcpy(the_card.pcm->name, "SPDIF"); + + /* set pcm ops */ + snd_pcm_set_ops(the_card.pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_ps3_pcm_spdif_ops); + + the_card.pcm->info_flags = SNDRV_PCM_INFO_NONINTERLEAVED; + /* pre-alloc PCM DMA buffer*/ + ret = snd_pcm_lib_preallocate_pages_for_all(the_card.pcm, + SNDRV_DMA_TYPE_DEV, + &dev->core, + SND_PS3_PCM_PREALLOC_SIZE, + SND_PS3_PCM_PREALLOC_SIZE); + if (ret < 0) { + pr_info("%s: prealloc failed\n", __func__); + goto clean_card; + } + + /* + * allocate null buffer + * its size should be lager than PS3_AUDIO_FIFO_STAGE_SIZE * 2 + * PAGE_SIZE is enogh + */ + the_card.null_buffer_start_vaddr = + dma_alloc_coherent(&the_card.ps3_dev->core, + PAGE_SIZE, + &the_card.null_buffer_start_dma_addr, + GFP_KERNEL); + if (!the_card.null_buffer_start_vaddr) { + pr_info("%s: nullbuffer alloc failed\n", __func__); + ret = -ENOMEM; + goto clean_card; + } + pr_debug("%s: null vaddr=%p dma=%#llx\n", __func__, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); + /* set default sample rate/word width */ + snd_ps3_init_avsetting(&the_card); + + /* register the card */ + ret = snd_card_register(the_card.card); + if (ret < 0) + goto clean_dma_map; + + pr_info("%s started. start_delay=%dms\n", + the_card.card->longname, the_card.start_delay); + return 0; + +clean_dma_map: + dma_free_coherent(&the_card.ps3_dev->core, + PAGE_SIZE, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); +clean_card: + snd_card_free(the_card.card); +clean_irq: + snd_ps3_free_irq(); +clean_dma_region: + ps3_dma_region_free(dev->d_region); +clean_mmio: + snd_ps3_unmap_mmio(); +clean_dev_map: + lv1_gpu_device_unmap(2); +clean_open: + ps3_close_hv_device(dev); + /* + * there is no destructor function to pcm. + * midlayer automatically releases if the card removed + */ + return ret; +}; /* snd_ps3_probe */ + +/* called when module removal */ +static int snd_ps3_driver_remove(struct ps3_system_bus_device *dev) +{ + int ret; + pr_info("%s:start id=%d\n", __func__, dev->match_id); + if (dev->match_id != PS3_MATCH_ID_SOUND) + return -ENXIO; + + /* + * ctl and preallocate buffer will be freed in + * snd_card_free + */ + ret = snd_card_free(the_card.card); + if (ret) + pr_info("%s: ctl freecard=%d\n", __func__, ret); + + dma_free_coherent(&dev->core, + PAGE_SIZE, + the_card.null_buffer_start_vaddr, + the_card.null_buffer_start_dma_addr); + + ps3_dma_region_free(dev->d_region); + + snd_ps3_free_irq(); + snd_ps3_unmap_mmio(); + + lv1_gpu_device_unmap(2); + ps3_close_hv_device(dev); + pr_info("%s:end id=%d\n", __func__, dev->match_id); + return 0; +} /* snd_ps3_remove */ + +static struct ps3_system_bus_driver snd_ps3_bus_driver_info = { + .match_id = PS3_MATCH_ID_SOUND, + .probe = snd_ps3_driver_probe, + .remove = snd_ps3_driver_remove, + .shutdown = snd_ps3_driver_remove, + .core = { + .name = SND_PS3_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + +/* + * module/subsystem initialize/terminate + */ +static int __init snd_ps3_init(void) +{ + int ret; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENXIO; + + memset(&the_card, 0, sizeof(the_card)); + spin_lock_init(&the_card.dma_lock); + + /* register systembus DRIVER, this calls our probe() func */ + ret = ps3_system_bus_driver_register(&snd_ps3_bus_driver_info); + + return ret; +} +module_init(snd_ps3_init); + +static void __exit snd_ps3_exit(void) +{ + ps3_system_bus_driver_unregister(&snd_ps3_bus_driver_info); +} +module_exit(snd_ps3_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PS3 sound driver"); +MODULE_AUTHOR("Sony Computer Entertainment Inc."); +MODULE_ALIAS(PS3_MODULE_ALIAS_SOUND); diff --git a/sound/ppc/snd_ps3.h b/sound/ppc/snd_ps3.h new file mode 100644 index 000000000..326fb29e8 --- /dev/null +++ b/sound/ppc/snd_ps3.h @@ -0,0 +1,136 @@ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * All rights reserved. + * Copyright 2006, 2007 Sony Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the Licence. + * + * 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 + */ + +#if !defined(_SND_PS3_H_) +#define _SND_PS3_H_ + +#include + +#define SND_PS3_DRIVER_NAME "snd_ps3" + +enum snd_ps3_out_channel { + SND_PS3_OUT_SPDIF_0, + SND_PS3_OUT_SPDIF_1, + SND_PS3_OUT_SERIAL_0, + SND_PS3_OUT_DEVS +}; + +enum snd_ps3_dma_filltype { + SND_PS3_DMA_FILLTYPE_FIRSTFILL, + SND_PS3_DMA_FILLTYPE_RUNNING, + SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL, + SND_PS3_DMA_FILLTYPE_SILENT_RUNNING +}; + +enum snd_ps3_ch { + SND_PS3_CH_L = 0, + SND_PS3_CH_R = 1, + SND_PS3_CH_MAX = 2 +}; + +struct snd_ps3_avsetting_info { + uint32_t avs_audio_ch; /* fixed */ + uint32_t avs_audio_rate; + uint32_t avs_audio_width; + uint32_t avs_audio_format; /* fixed */ + uint32_t avs_audio_source; /* fixed */ + unsigned char avs_cs_info[8]; +}; +/* + * PS3 audio 'card' instance + * there should be only ONE hardware. + */ +struct snd_ps3_card_info { + struct ps3_system_bus_device *ps3_dev; + struct snd_card *card; + + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + + /* hvc info */ + u64 audio_lpar_addr; + u64 audio_lpar_size; + + /* registers */ + void __iomem *mapped_mmio_vaddr; + + /* irq */ + u64 audio_irq_outlet; + unsigned int irq_no; + + /* remember avsetting */ + struct snd_ps3_avsetting_info avs; + + /* dma buffer management */ + spinlock_t dma_lock; + /* dma_lock start */ + void * dma_start_vaddr[2]; /* 0 for L, 1 for R */ + dma_addr_t dma_start_bus_addr[2]; + size_t dma_buffer_size; + void * dma_last_transfer_vaddr[2]; + void * dma_next_transfer_vaddr[2]; + int silent; + /* dma_lock end */ + + int running; + + /* null buffer */ + void *null_buffer_start_vaddr; + dma_addr_t null_buffer_start_dma_addr; + + /* start delay */ + unsigned int start_delay; + +}; + + +/* PS3 audio DMAC block size in bytes */ +#define PS3_AUDIO_DMAC_BLOCK_SIZE (128) +/* one stage (stereo) of audio FIFO in bytes */ +#define PS3_AUDIO_FIFO_STAGE_SIZE (256) +/* how many stages the fifo have */ +#define PS3_AUDIO_FIFO_STAGE_COUNT (8) +/* fifo size 128 bytes * 8 stages * stereo (2ch) */ +#define PS3_AUDIO_FIFO_SIZE \ + (PS3_AUDIO_FIFO_STAGE_SIZE * PS3_AUDIO_FIFO_STAGE_COUNT) + +/* PS3 audio DMAC max block count in one dma shot = 128 (0x80) blocks*/ +#define PS3_AUDIO_DMAC_MAX_BLOCKS (PS3_AUDIO_DMASIZE_BLOCKS_MASK + 1) + +#define PS3_AUDIO_NORMAL_DMA_START_CH (0) +#define PS3_AUDIO_NORMAL_DMA_COUNT (8) +#define PS3_AUDIO_NULL_DMA_START_CH \ + (PS3_AUDIO_NORMAL_DMA_START_CH + PS3_AUDIO_NORMAL_DMA_COUNT) +#define PS3_AUDIO_NULL_DMA_COUNT (2) + +#define SND_PS3_MAX_VOL (0x0F) +#define SND_PS3_MIN_VOL (0x00) +#define SND_PS3_MIN_ATT SND_PS3_MIN_VOL +#define SND_PS3_MAX_ATT SND_PS3_MAX_VOL + +#define SND_PS3_PCM_PREALLOC_SIZE \ + (PS3_AUDIO_DMAC_BLOCK_SIZE * PS3_AUDIO_DMAC_MAX_BLOCKS * 4) + +#define SND_PS3_DMA_REGION_SIZE \ + (SND_PS3_PCM_PREALLOC_SIZE + PAGE_SIZE) + +#define PS3_AUDIO_IOID (1UL) + +#endif /* _SND_PS3_H_ */ diff --git a/sound/ppc/snd_ps3_reg.h b/sound/ppc/snd_ps3_reg.h new file mode 100644 index 000000000..2e6302079 --- /dev/null +++ b/sound/ppc/snd_ps3_reg.h @@ -0,0 +1,891 @@ +/* + * Audio support for PS3 + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2006, 2007 Sony Corporation + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 of the License. + * + * 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 + */ + +/* + * interrupt / configure registers + */ + +#define PS3_AUDIO_INTR_0 (0x00000100) +#define PS3_AUDIO_INTR_EN_0 (0x00000140) +#define PS3_AUDIO_CONFIG (0x00000200) + +/* + * DMAC registers + * n:0..9 + */ +#define PS3_AUDIO_DMAC_REGBASE(x) (0x0000210 + 0x20 * (x)) + +#define PS3_AUDIO_KICK(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x00) +#define PS3_AUDIO_SOURCE(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x04) +#define PS3_AUDIO_DEST(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x08) +#define PS3_AUDIO_DMASIZE(n) (PS3_AUDIO_DMAC_REGBASE(n) + 0x0C) + +/* + * mute control + */ +#define PS3_AUDIO_AX_MCTRL (0x00004000) +#define PS3_AUDIO_AX_ISBP (0x00004004) +#define PS3_AUDIO_AX_AOBP (0x00004008) +#define PS3_AUDIO_AX_IC (0x00004010) +#define PS3_AUDIO_AX_IE (0x00004014) +#define PS3_AUDIO_AX_IS (0x00004018) + +/* + * three wire serial + * n:0..3 + */ +#define PS3_AUDIO_AO_MCTRL (0x00006000) +#define PS3_AUDIO_AO_3WMCTRL (0x00006004) + +#define PS3_AUDIO_AO_3WCTRL(n) (0x00006200 + 0x200 * (n)) + +/* + * S/PDIF + * n:0..1 + * x:0..11 + * y:0..5 + */ +#define PS3_AUDIO_AO_SPD_REGBASE(n) (0x00007200 + 0x200 * (n)) + +#define PS3_AUDIO_AO_SPDCTRL(n) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x00) +#define PS3_AUDIO_AO_SPDUB(n, x) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x04 + 0x04 * (x)) +#define PS3_AUDIO_AO_SPDCS(n, y) \ + (PS3_AUDIO_AO_SPD_REGBASE(n) + 0x34 + 0x04 * (y)) + + +/* + PS3_AUDIO_INTR_0 register tells an interrupt handler which audio + DMA channel triggered the interrupt. The interrupt status for a channel + can be cleared by writing a '1' to the corresponding bit. A new interrupt + cannot be generated until the previous interrupt has been cleared. + + Note that the status reported by PS3_AUDIO_INTR_0 is independent of the + value of PS3_AUDIO_INTR_EN_0. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +#define PS3_AUDIO_INTR_0_CHAN(n) (1 << ((n) * 2)) +#define PS3_AUDIO_INTR_0_CHAN9 PS3_AUDIO_INTR_0_CHAN(9) +#define PS3_AUDIO_INTR_0_CHAN8 PS3_AUDIO_INTR_0_CHAN(8) +#define PS3_AUDIO_INTR_0_CHAN7 PS3_AUDIO_INTR_0_CHAN(7) +#define PS3_AUDIO_INTR_0_CHAN6 PS3_AUDIO_INTR_0_CHAN(6) +#define PS3_AUDIO_INTR_0_CHAN5 PS3_AUDIO_INTR_0_CHAN(5) +#define PS3_AUDIO_INTR_0_CHAN4 PS3_AUDIO_INTR_0_CHAN(4) +#define PS3_AUDIO_INTR_0_CHAN3 PS3_AUDIO_INTR_0_CHAN(3) +#define PS3_AUDIO_INTR_0_CHAN2 PS3_AUDIO_INTR_0_CHAN(2) +#define PS3_AUDIO_INTR_0_CHAN1 PS3_AUDIO_INTR_0_CHAN(1) +#define PS3_AUDIO_INTR_0_CHAN0 PS3_AUDIO_INTR_0_CHAN(0) + +/* + The PS3_AUDIO_INTR_EN_0 register specifies which DMA channels can generate + an interrupt to the PU. Each bit of PS3_AUDIO_INTR_EN_0 is ANDed with the + corresponding bit in PS3_AUDIO_INTR_0. The resulting bits are OR'd together + to generate the Audio interrupt. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C|0|C| INTR_EN_0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + + Bit assignments are same as PS3_AUDIO_INTR_0 +*/ + +/* + PS3_AUDIO_CONFIG + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 C|0 0 0 0 0 0 0 0| CONFIG + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ + +/* The CLEAR field cancels all pending transfers, and stops any running DMA + transfers. Any interrupts associated with the canceled transfers + will occur as if the transfer had finished. + Since this bit is designed to recover from DMA related issues + which are caused by unpredictable situations, it is preferred to wait + for normal DMA transfer end without using this bit. +*/ +#define PS3_AUDIO_CONFIG_CLEAR (1 << 8) /* RWIVF */ + +/* + PS3_AUDIO_AX_MCTRL: Audio Port Mute Control Register + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|A|A|0 0 0 0 0 0 0|S|S|A|A|A|A| AX_MCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* 3 Wire Audio Serial Output Channel Mutes (0..3) */ +#define PS3_AUDIO_AX_MCTRL_ASOMT(n) (1 << (3 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO3MT (1 << 0) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO2MT (1 << 1) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO1MT (1 << 2) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_ASO0MT (1 << 3) /* RWIVF */ + +/* S/PDIF mutes (0,1)*/ +#define PS3_AUDIO_AX_MCTRL_SPOMT(n) (1 << (5 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_SPO1MT (1 << 4) /* RWIVF */ +#define PS3_AUDIO_AX_MCTRL_SPO0MT (1 << 5) /* RWIVF */ + +/* All 3 Wire Serial Outputs Mute */ +#define PS3_AUDIO_AX_MCTRL_AASOMT (1 << 13) /* RWIVF */ + +/* All S/PDIF Mute */ +#define PS3_AUDIO_AX_MCTRL_ASPOMT (1 << 14) /* RWIVF */ + +/* All Audio Outputs Mute */ +#define PS3_AUDIO_AX_MCTRL_AAOMT (1 << 15) /* RWIVF */ + +/* + S/PDIF Outputs Buffer Read/Write Pointer Register + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B|0 0 0 0 0 0 0 0|0|SPO0B|0|SPO1B| AX_ISBP + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ +/* + S/PDIF Output Channel Read Buffer Numbers + Buffer number is value of field. + Indicates current read access buffer ID from Audio Data + Transfer controller of S/PDIF Output +*/ + +#define PS3_AUDIO_AX_ISBP_SPOBRN_MASK(n) (0x7 << 4 * (1 - (n))) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO1BRN_MASK (0x7 << 0) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO0BRN_MASK (0x7 << 4) /* R-IUF */ + +/* +S/PDIF Output Channel Buffer Write Numbers +Indicates current write access buffer ID from bus master. +*/ +#define PS3_AUDIO_AX_ISBP_SPOBWN_MASK(n) (0x7 << 4 * (5 - (n))) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO1BWN_MASK (0x7 << 16) /* R-IUF */ +#define PS3_AUDIO_AX_ISBP_SPO0BWN_MASK (0x7 << 20) /* R-IUF */ + +/* + 3 Wire Audio Serial Outputs Buffer Read/Write + Pointer Register + Buffer number is value of field + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B|0|ASO0B|0|ASO1B|0|ASO2B|0|ASO3B| AX_AOBP + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +3 Wire Audio Serial Output Channel Buffer Read Numbers +Indicates current read access buffer Id from Audio Data Transfer +Controller of 3 Wire Audio Serial Output Channels +*/ +#define PS3_AUDIO_AX_AOBP_ASOBRN_MASK(n) (0x7 << 4 * (3 - (n))) /* R-IUF */ + +#define PS3_AUDIO_AX_AOBP_ASO3BRN_MASK (0x7 << 0) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO2BRN_MASK (0x7 << 4) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO1BRN_MASK (0x7 << 8) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO0BRN_MASK (0x7 << 12) /* R-IUF */ + +/* +3 Wire Audio Serial Output Channel Buffer Write Numbers +Indicates current write access buffer ID from bus master. +*/ +#define PS3_AUDIO_AX_AOBP_ASOBWN_MASK(n) (0x7 << 4 * (7 - (n))) /* R-IUF */ + +#define PS3_AUDIO_AX_AOBP_ASO3BWN_MASK (0x7 << 16) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO2BWN_MASK (0x7 << 20) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO1BWN_MASK (0x7 << 24) /* R-IUF */ +#define PS3_AUDIO_AX_AOBP_ASO0BWN_MASK (0x7 << 28) /* R-IUF */ + + + +/* +Audio Port Interrupt Condition Register +For the fields in this register, the following values apply: +0 = Interrupt is generated every interrupt event. +1 = Interrupt is generated every 2 interrupt events. +2 = Interrupt is generated every 4 interrupt events. +3 = Reserved + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|0 0|SPO|0 0|SPO|0 0|AAS|0 0 0 0 0 0 0 0 0 0 0 0| AX_IC + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +All 3-Wire Audio Serial Outputs Interrupt Mode +Configures the Interrupt and Signal Notification +condition of all 3-wire Audio Serial Outputs. +*/ +#define PS3_AUDIO_AX_IC_AASOIMD_MASK (0x3 << 12) /* RWIVF */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY1 (0x0 << 12) /* RWI-V */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY2 (0x1 << 12) /* RW--V */ +#define PS3_AUDIO_AX_IC_AASOIMD_EVERY4 (0x2 << 12) /* RW--V */ + +/* +S/PDIF Output Channel Interrupt Modes +Configures the Interrupt and signal Notification +conditions of S/PDIF output channels. +*/ +#define PS3_AUDIO_AX_IC_SPO1IMD_MASK (0x3 << 16) /* RWIVF */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY1 (0x0 << 16) /* RWI-V */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY2 (0x1 << 16) /* RW--V */ +#define PS3_AUDIO_AX_IC_SPO1IMD_EVERY4 (0x2 << 16) /* RW--V */ + +#define PS3_AUDIO_AX_IC_SPO0IMD_MASK (0x3 << 20) /* RWIVF */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY1 (0x0 << 20) /* RWI-V */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY2 (0x1 << 20) /* RW--V */ +#define PS3_AUDIO_AX_IC_SPO0IMD_EVERY4 (0x2 << 20) /* RW--V */ + +/* +Audio Port interrupt Enable Register +Configures whether to enable or disable each Interrupt Generation. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ + +/* +3 Wire Audio Serial Output Channel Buffer Underflow +Interrupt Enables +Select enable/disable of Buffer Underflow Interrupts for +3-Wire Audio Serial Output Channels +DISABLED=Interrupt generation disabled. +*/ +#define PS3_AUDIO_AX_IE_ASOBUIE(n) (1 << (3 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO3BUIE (1 << 0) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO2BUIE (1 << 1) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO1BUIE (1 << 2) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO0BUIE (1 << 3) /* RWIVF */ + +/* S/PDIF Output Channel Buffer Underflow Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBUIE(n) (1 << (7 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BUIE (1 << 6) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BUIE (1 << 7) /* RWIVF */ + +/* S/PDIF Output Channel One Block Transfer Completion Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBTCIE(n) (1 << (11 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BTCIE (1 << 10) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BTCIE (1 << 11) /* RWIVF */ + +/* 3-Wire Audio Serial Output Channel Buffer Empty Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_ASOBEIE(n) (1 << (19 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO3BEIE (1 << 16) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO2BEIE (1 << 17) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO1BEIE (1 << 18) /* RWIVF */ +#define PS3_AUDIO_AX_IE_ASO0BEIE (1 << 19) /* RWIVF */ + +/* S/PDIF Output Channel Buffer Empty Interrupt Enables */ + +#define PS3_AUDIO_AX_IE_SPOBEIE(n) (1 << (23 - (n))) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO1BEIE (1 << 22) /* RWIVF */ +#define PS3_AUDIO_AX_IE_SPO0BEIE (1 << 23) /* RWIVF */ + +/* +Audio Port Interrupt Status Register +Indicates Interrupt status, which interrupt has occurred, and can clear +each interrupt in this register. +Writing 1b to a field containing 1b clears field and de-asserts interrupt. +Writing 0b to a field has no effect. +Field vaules are the following: +0 - Interrupt hasn't occurred. +1 - Interrupt has occurred. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0|S|S|0 0|A|A|A|A|0 0 0 0|S|S|0 0|S|S|0 0|A|A|A|A| AX_IS + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + + Bit assignment are same as AX_IE +*/ + +/* +Audio Output Master Control Register +Configures Master Clock and other master Audio Output Settings + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0|SCKSE|0|SCKSE| MR0 | MR1 |MCL|MCL|0 0 0 0|0 0 0 0 0 0 0 0| AO_MCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +MCLK Output Control +Controls mclko[1] output. +0 - Disable output (fixed at High) +1 - Output clock produced by clock selected +with scksel1 by mr1 +2 - Reserved +3 - Reserved +*/ + +#define PS3_AUDIO_AO_MCTRL_MCLKC1_MASK (0x3 << 12) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_DISABLED (0x0 << 12) /* RWI-V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_ENABLED (0x1 << 12) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD2 (0x2 << 12) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC1_RESVD3 (0x3 << 12) /* RW--V */ + +/* +MCLK Output Control +Controls mclko[0] output. +0 - Disable output (fixed at High) +1 - Output clock produced by clock selected +with SCKSEL0 by MR0 +2 - Reserved +3 - Reserved +*/ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_MASK (0x3 << 14) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_DISABLED (0x0 << 14) /* RWI-V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_ENABLED (0x1 << 14) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD2 (0x2 << 14) /* RW--V */ +#define PS3_AUDIO_AO_MCTRL_MCLKC0_RESVD3 (0x3 << 14) /* RW--V */ +/* +Master Clock Rate 1 +Sets the divide ration of Master Clock1 (clock output from +mclko[1] for the input clock selected by scksel1. +*/ +#define PS3_AUDIO_AO_MCTRL_MR1_MASK (0xf << 16) +#define PS3_AUDIO_AO_MCTRL_MR1_DEFAULT (0x0 << 16) /* RWI-V */ +/* +Master Clock Rate 0 +Sets the divide ratio of Master Clock0 (clock output from +mclko[0] for the input clock selected by scksel0). +*/ +#define PS3_AUDIO_AO_MCTRL_MR0_MASK (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_MR0_DEFAULT (0x0 << 20) /* RWI-V */ +/* +System Clock Select 0/1 +Selects the system clock to be used as Master Clock 0/1 +Input the system clock that is appropriate for the sampling +rate. +*/ +#define PS3_AUDIO_AO_MCTRL_SCKSEL1_MASK (0x7 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_SCKSEL1_DEFAULT (0x2 << 24) /* RWI-V */ + +#define PS3_AUDIO_AO_MCTRL_SCKSEL0_MASK (0x7 << 28) /* RWIVF */ +#define PS3_AUDIO_AO_MCTRL_SCKSEL0_DEFAULT (0x2 << 28) /* RWI-V */ + + +/* +3-Wire Audio Output Master Control Register +Configures clock, 3-Wire Audio Serial Output Enable, and +other 3-Wire Audio Serial Output Master Settings + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |A|A|A|A|0 0 0|A| ASOSR |0 0 0 0|A|A|A|A|A|A|0|1|0 0 0 0 0 0 0 0| AO_3WMCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + + +/* +LRCKO Polarity +0 - Reserved +1 - default +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK (1 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT (1 << 8) /* RW--V */ + +/* LRCK Output Disable */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD (1 << 10) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_ENABLED (0 << 10) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED (1 << 10) /* RWI-V */ + +/* Bit Clock Output Disable */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_ENABLED (0 << 11) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED (1 << 11) /* RWI-V */ + +/* +3-Wire Audio Serial Output Channel 0-3 Operational +Status. Each bit becomes 1 after each 3-Wire Audio +Serial Output Channel N is in action by setting 1 to +asoen. +Each bit becomes 0 after each 3-Wire Audio Serial Output +Channel N is out of action by setting 0 to asoen. +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN(n) (1 << (15 - (n))) /* R-IVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(n) (0 << (15 - (n))) /* R-I-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(n) (1 << (15 - (n))) /* R---V */ +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN0_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(0) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN1_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(1) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN2_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(2) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3 \ + PS3_AUDIO_AO_3WMCTRL_ASORUN(3) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_STOPPED \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_STOPPED(3) +#define PS3_AUDIO_AO_3WMCTRL_ASORUN3_RUNNING \ + PS3_AUDIO_AO_3WMCTRL_ASORUN_RUNNING(3) + +/* +Sampling Rate +Specifies the divide ratio of the bit clock (clock output +from bclko) used by the 3-wire Audio Output Clock, which +is applied to the master clock selected by mcksel. +Data output is synchronized with this clock. +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_MASK (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV2 (0x1 << 20) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV4 (0x2 << 20) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV8 (0x4 << 20) /* RW--V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOSR_DIV12 (0x6 << 20) /* RW--V */ + +/* +Master Clock Select +0 - Master Clock 0 +1 - Master Clock 1 +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL (1 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK0 (0 << 24) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOMCKSEL_CLK1 (1 << 24) /* RW--V */ + +/* +Enables and disables 4ch 3-Wire Audio Serial Output +operation. Each Bit from 0 to 3 corresponds to an +output channel, which means that each output channel +can be enabled or disabled individually. When +multiple channels are enabled at the same time, output +operations are performed in synchronization. +Bit 0 - Output Channel 0 (SDOUT[0]) +Bit 1 - Output Channel 1 (SDOUT[1]) +Bit 2 - Output Channel 2 (SDOUT[2]) +Bit 3 - Output Channel 3 (SDOUT[3]) +*/ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN(n) (1 << (31 - (n))) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(n) (0 << (31 - (n))) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(n) (1 << (31 - (n))) /* RW--V */ + +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(0) /* RWIVF */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(0) /* RWI-V */ +#define PS3_AUDIO_AO_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(0) /* RW--V */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(1) /* RWIVF */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(1) /* RWI-V */ +#define PS3_AUDIO_A1_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(1) /* RW--V */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(2) /* RWIVF */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(2) /* RWI-V */ +#define PS3_AUDIO_A2_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(2) /* RW--V */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0 \ + PS3_AUDIO_AO_3WMCTRL_ASOEN(3) /* RWIVF */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_DISABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_DISABLED(3) /* RWI-V */ +#define PS3_AUDIO_A3_3WMCTRL_ASOEN0_ENABLED \ + PS3_AUDIO_AO_3WMCTRL_ASOEN_ENABLED(3) /* RW--V */ + +/* +3-Wire Audio Serial output Channel 0-3 Control Register +Configures settings for 3-Wire Serial Audio Output Channel 0-3 + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|A|0 0 0 0|A|0|ASO|0 0 0|0|0|0|0|0| AO_3WCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +*/ +/* +Data Bit Mode +Specifies the number of data bits +0 - 16 bits +1 - reserved +2 - 20 bits +3 - 24 bits +*/ +#define PS3_AUDIO_AO_3WCTRL_ASODB_MASK (0x3 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_16BIT (0x0 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_RESVD (0x1 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_20BIT (0x2 << 8) /* RW--V */ +#define PS3_AUDIO_AO_3WCTRL_ASODB_24BIT (0x3 << 8) /* RW--V */ +/* +Data Format Mode +Specifies the data format where (LSB side or MSB) the data(in 20 bit +or 24 bit resolution mode) is put in a 32 bit field. +0 - Data put on LSB side +1 - Data put on MSB side +*/ +#define PS3_AUDIO_AO_3WCTRL_ASODF (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASODF_LSB (0 << 11) /* RWI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASODF_MSB (1 << 11) /* RW--V */ +/* +Buffer Reset +Performs buffer reset. Writing 1 to this bit initializes the +corresponding 3-Wire Audio Output buffers(both L and R). +*/ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST (1 << 16) /* CWIVF */ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST_IDLE (0 << 16) /* -WI-V */ +#define PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET (1 << 16) /* -W--T */ + +/* +S/PDIF Audio Output Channel 0/1 Control Register +Configures settings for S/PDIF Audio Output Channel 0/1. + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |S|0 0 0|S|0 0|S| SPOSR |0 0|SPO|0 0 0 0|S|0|SPO|0 0 0 0 0 0 0|S| AO_SPDCTRL + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +Buffer reset. Writing 1 to this bit initializes the +corresponding S/PDIF output buffer pointer. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST (1 << 0) /* CWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_IDLE (0 << 0) /* -WI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOBRST_RESET (1 << 0) /* -W--T */ + +/* +Data Bit Mode +Specifies number of data bits +0 - 16 bits +1 - Reserved +2 - 20 bits +3 - 24 bits +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_MASK (0x3 << 8) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_16BIT (0x0 << 8) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_RESVD (0x1 << 8) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_20BIT (0x2 << 8) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODB_24BIT (0x3 << 8) /* RW--V */ +/* +Data format Mode +Specifies the data format, where (LSB side or MSB) +the data(in 20 or 24 bit resolution) is put in the +32 bit field. +0 - LSB Side +1 - MSB Side +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPODF (1 << 11) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPODF_LSB (0 << 11) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPODF_MSB (1 << 11) /* RW--V */ +/* +Source Select +Specifies the source of the S/PDIF output. When 0, output +operation is controlled by 3wen[0] of AO_3WMCTRL register. +The SR must have the same setting as the a0_3wmctrl reg. +0 - 3-Wire Audio OUT Ch0 Buffer +1 - S/PDIF buffer +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_MASK (0x3 << 16) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_3WEN (0x0 << 16) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSS_SPDIF (0x1 << 16) /* RW--V */ +/* +Sampling Rate +Specifies the divide ratio of the bit clock (clock output +from bclko) used by the S/PDIF Output Clock, which +is applied to the master clock selected by mcksel. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR (0xf << 20) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV2 (0x1 << 20) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV4 (0x2 << 20) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV8 (0x4 << 20) /* RW--V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOSR_DIV12 (0x6 << 20) /* RW--V */ +/* +Master Clock Select +0 - Master Clock 0 +1 - Master Clock 1 +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL (1 << 24) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK0 (0 << 24) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOMCKSEL_CLK1 (1 << 24) /* RW--V */ + +/* +S/PDIF Output Channel Operational Status +This bit becomes 1 after S/PDIF Output Channel is in +action by setting 1 to spoen. This bit becomes 0 +after S/PDIF Output Channel is out of action by setting +0 to spoen. +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN (1 << 27) /* R-IVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN_STOPPED (0 << 27) /* R-I-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPORUN_RUNNING (1 << 27) /* R---V */ + +/* +S/PDIF Audio Output Channel Output Enable +Enables and disables output operation. This bit is used +only when sposs = 1 +*/ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN (1 << 31) /* RWIVF */ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN_DISABLED (0 << 31) /* RWI-V */ +#define PS3_AUDIO_AO_SPDCTRL_SPOEN_ENABLED (1 << 31) /* RW--V */ + +/* +S/PDIF Audio Output Channel Channel Status +Setting Registers. +Configures channel status bit settings for each block +(192 bits). +Output is performed from the MSB(AO_SPDCS0 register bit 31). +The same value is added for subframes within the same frame. + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | SPOCS | AO_SPDCS + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + +S/PDIF Audio Output Channel User Bit Setting +Configures user bit settings for each block (384 bits). +Output is performed from the MSB(ao_spdub0 register bit 31). + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | SPOUB | AO_SPDUB + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/***************************************************************************** + * + * DMAC register + * + *****************************************************************************/ +/* +The PS3_AUDIO_KICK register is used to initiate a DMA transfer and monitor +its status + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0|STATU|0 0 0| EVENT |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|R| KICK + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ +/* +The REQUEST field is written to ACTIVE to initiate a DMA request when EVENT +occurs. +It will return to the DONE state when the request is completed. +The registers for a DMA channel should only be written if REQUEST is IDLE. +*/ + +#define PS3_AUDIO_KICK_REQUEST (1 << 0) /* RWIVF */ +#define PS3_AUDIO_KICK_REQUEST_IDLE (0 << 0) /* RWI-V */ +#define PS3_AUDIO_KICK_REQUEST_ACTIVE (1 << 0) /* -W--T */ + +/* + *The EVENT field is used to set the event in which + *the DMA request becomes active. + */ +#define PS3_AUDIO_KICK_EVENT_MASK (0x1f << 16) /* RWIVF */ +#define PS3_AUDIO_KICK_EVENT_ALWAYS (0x00 << 16) /* RWI-V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY (0x01 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT0_UNDERFLOW (0x02 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_EMPTY (0x03 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT1_UNDERFLOW (0x04 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_EMPTY (0x05 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT2_UNDERFLOW (0x06 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_EMPTY (0x07 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SERIALOUT3_UNDERFLOW (0x08 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_BLOCKTRANSFERCOMPLETE \ + (0x09 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_UNDERFLOW (0x0A << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF0_EMPTY (0x0B << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_BLOCKTRANSFERCOMPLETE \ + (0x0C << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_UNDERFLOW (0x0D << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_SPDIF1_EMPTY (0x0E << 16) /* RW--V */ + +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA(n) \ + ((0x13 + (n)) << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA0 (0x13 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA1 (0x14 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA2 (0x15 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA3 (0x16 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA4 (0x17 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA5 (0x18 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA6 (0x19 << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA7 (0x1A << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA8 (0x1B << 16) /* RW--V */ +#define PS3_AUDIO_KICK_EVENT_AUDIO_DMA9 (0x1C << 16) /* RW--V */ + +/* +The STATUS field can be used to monitor the progress of a DMA request. +DONE indicates the previous request has completed. +EVENT indicates that the DMA engine is waiting for the EVENT to occur. +PENDING indicates that the DMA engine has not started processing this +request, but the EVENT has occurred. +DMA indicates that the data transfer is in progress. +NOTIFY indicates that the notifier signalling end of transfer is being written. +CLEAR indicated that the previous transfer was cleared. +ERROR indicates the previous transfer requested an unsupported +source/destination combination. +*/ + +#define PS3_AUDIO_KICK_STATUS_MASK (0x7 << 24) /* R-IVF */ +#define PS3_AUDIO_KICK_STATUS_DONE (0x0 << 24) /* R-I-V */ +#define PS3_AUDIO_KICK_STATUS_EVENT (0x1 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_PENDING (0x2 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_DMA (0x3 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_NOTIFY (0x4 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_CLEAR (0x5 << 24) /* R---V */ +#define PS3_AUDIO_KICK_STATUS_ERROR (0x6 << 24) /* R---V */ + +/* +The PS3_AUDIO_SOURCE register specifies the source address for transfers. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | START |0 0 0 0 0|TAR| SOURCE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +The Audio DMA engine uses 128-byte transfers, thus the address must be aligned +to a 128 byte boundary. The low seven bits are assumed to be 0. +*/ + +#define PS3_AUDIO_SOURCE_START_MASK (0x01FFFFFF << 7) /* RWIUF */ + +/* +The TARGET field specifies the memory space containing the source address. +*/ + +#define PS3_AUDIO_SOURCE_TARGET_MASK (3 << 0) /* RWIVF */ +#define PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY (2 << 0) /* RW--V */ + +/* +The PS3_AUDIO_DEST register specifies the destination address for transfers. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + | START |0 0 0 0 0|TAR| DEST + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + +/* +The Audio DMA engine uses 128-byte transfers, thus the address must be aligned +to a 128 byte boundary. The low seven bits are assumed to be 0. +*/ + +#define PS3_AUDIO_DEST_START_MASK (0x01FFFFFF << 7) /* RWIUF */ + +/* +The TARGET field specifies the memory space containing the destination address +AUDIOFIFO = Audio WriteData FIFO, +*/ + +#define PS3_AUDIO_DEST_TARGET_MASK (3 << 0) /* RWIVF */ +#define PS3_AUDIO_DEST_TARGET_AUDIOFIFO (1 << 0) /* RW--V */ + +/* +PS3_AUDIO_DMASIZE specifies the number of 128-byte blocks + 1 to transfer. +So a value of 0 means 128-bytes will get transferred. + + + 31 24 23 16 15 8 7 0 + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0| BLOCKS | DMASIZE + +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ +*/ + + +#define PS3_AUDIO_DMASIZE_BLOCKS_MASK (0x7f << 0) /* RWIUF */ + +/* + * source/destination address for internal fifos + */ +#define PS3_AUDIO_AO_3W_LDATA(n) (0x1000 + (0x100 * (n))) +#define PS3_AUDIO_AO_3W_RDATA(n) (0x1080 + (0x100 * (n))) + +#define PS3_AUDIO_AO_SPD_DATA(n) (0x2000 + (0x400 * (n))) + + +/* + * field attiribute + * + * Read + * ' ' = Other Information + * '-' = Field is part of a write-only register + * 'C' = Value read is always the same, constant value line follows (C) + * 'R' = Value is read + * + * Write + * ' ' = Other Information + * '-' = Must not be written (D), value ignored when written (R,A,F) + * 'W' = Can be written + * + * Internal State + * ' ' = Other Information + * '-' = No internal state + * 'X' = Internal state, initial value is unknown + * 'I' = Internal state, initial value is known and follows (I) + * + * Declaration/Size + * ' ' = Other Information + * '-' = Does Not Apply + * 'V' = Type is void + * 'U' = Type is unsigned integer + * 'S' = Type is signed integer + * 'F' = Type is IEEE floating point + * '1' = Byte size (008) + * '2' = Short size (016) + * '3' = Three byte size (024) + * '4' = Word size (032) + * '8' = Double size (064) + * + * Define Indicator + * ' ' = Other Information + * 'D' = Device + * 'M' = Memory + * 'R' = Register + * 'A' = Array of Registers + * 'F' = Field + * 'V' = Value + * 'T' = Task + */ + diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c new file mode 100644 index 000000000..0779a2912 --- /dev/null +++ b/sound/ppc/tumbler.c @@ -0,0 +1,1491 @@ +/* + * PMac Tumbler/Snapper lowlevel functions + * + * Copyright (c) by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Rene Rebe : + * * update from shadow registers on wakeup and headphone plug + * * automatically toggle DRC on headphone plug + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmac.h" +#include "tumbler_volume.h" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(fmt...) printk(KERN_DEBUG fmt) +#else +#define DBG(fmt...) +#endif + +#define IS_G4DA (of_machine_is_compatible("PowerMac3,4")) + +/* i2c address for tumbler */ +#define TAS_I2C_ADDR 0x34 + +/* registers */ +#define TAS_REG_MCS 0x01 /* main control */ +#define TAS_REG_DRC 0x02 +#define TAS_REG_VOL 0x04 +#define TAS_REG_TREBLE 0x05 +#define TAS_REG_BASS 0x06 +#define TAS_REG_INPUT1 0x07 +#define TAS_REG_INPUT2 0x08 + +/* tas3001c */ +#define TAS_REG_PCM TAS_REG_INPUT1 + +/* tas3004 */ +#define TAS_REG_LMIX TAS_REG_INPUT1 +#define TAS_REG_RMIX TAS_REG_INPUT2 +#define TAS_REG_MCS2 0x43 /* main control 2 */ +#define TAS_REG_ACS 0x40 /* analog control */ + +/* mono volumes for tas3001c/tas3004 */ +enum { + VOL_IDX_PCM_MONO, /* tas3001c only */ + VOL_IDX_BASS, VOL_IDX_TREBLE, + VOL_IDX_LAST_MONO +}; + +/* stereo volumes for tas3004 */ +enum { + VOL_IDX_PCM, VOL_IDX_PCM2, VOL_IDX_ADC, + VOL_IDX_LAST_MIX +}; + +struct pmac_gpio { + unsigned int addr; + u8 active_val; + u8 inactive_val; + u8 active_state; +}; + +struct pmac_tumbler { + struct pmac_keywest i2c; + struct pmac_gpio audio_reset; + struct pmac_gpio amp_mute; + struct pmac_gpio line_mute; + struct pmac_gpio line_detect; + struct pmac_gpio hp_mute; + struct pmac_gpio hp_detect; + int headphone_irq; + int lineout_irq; + unsigned int save_master_vol[2]; + unsigned int master_vol[2]; + unsigned int save_master_switch[2]; + unsigned int master_switch[2]; + unsigned int mono_vol[VOL_IDX_LAST_MONO]; + unsigned int mix_vol[VOL_IDX_LAST_MIX][2]; /* stereo volumes for tas3004 */ + int drc_range; + int drc_enable; + int capture_source; + int anded_reset; + int auto_mute_notify; + int reset_on_sleep; + u8 acs; +}; + + +/* + */ + +static int send_init_client(struct pmac_keywest *i2c, unsigned int *regs) +{ + while (*regs > 0) { + int err, count = 10; + do { + err = i2c_smbus_write_byte_data(i2c->client, + regs[0], regs[1]); + if (err >= 0) + break; + DBG("(W) i2c error %d\n", err); + mdelay(10); + } while (count--); + if (err < 0) + return -ENXIO; + regs += 2; + } + return 0; +} + + +static int tumbler_init_client(struct pmac_keywest *i2c) +{ + static unsigned int regs[] = { + /* normal operation, SCLK=64fps, i2s output, i2s input, 16bit width */ + TAS_REG_MCS, (1<<6)|(2<<4)|(2<<2)|0, + 0, /* terminator */ + }; + DBG("(I) tumbler init client\n"); + return send_init_client(i2c, regs); +} + +static int snapper_init_client(struct pmac_keywest *i2c) +{ + static unsigned int regs[] = { + /* normal operation, SCLK=64fps, i2s output, 16bit width */ + TAS_REG_MCS, (1<<6)|(2<<4)|0, + /* normal operation, all-pass mode */ + TAS_REG_MCS2, (1<<1), + /* normal output, no deemphasis, A input, power-up, line-in */ + TAS_REG_ACS, 0, + 0, /* terminator */ + }; + DBG("(I) snapper init client\n"); + return send_init_client(i2c, regs); +} + +/* + * gpio access + */ +#define do_gpio_write(gp, val) \ + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, (gp)->addr, val) +#define do_gpio_read(gp) \ + pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, (gp)->addr, 0) +#define tumbler_gpio_free(gp) /* NOP */ + +static void write_audio_gpio(struct pmac_gpio *gp, int active) +{ + if (! gp->addr) + return; + active = active ? gp->active_val : gp->inactive_val; + do_gpio_write(gp, active); + DBG("(I) gpio %x write %d\n", gp->addr, active); +} + +static int check_audio_gpio(struct pmac_gpio *gp) +{ + int ret; + + if (! gp->addr) + return 0; + + ret = do_gpio_read(gp); + + return (ret & 0x1) == (gp->active_val & 0x1); +} + +static int read_audio_gpio(struct pmac_gpio *gp) +{ + int ret; + if (! gp->addr) + return 0; + ret = do_gpio_read(gp); + ret = (ret & 0x02) !=0; + return ret == gp->active_state; +} + +/* + * update master volume + */ +static int tumbler_set_master_volume(struct pmac_tumbler *mix) +{ + unsigned char block[6]; + unsigned int left_vol, right_vol; + + if (! mix->i2c.client) + return -ENODEV; + + if (! mix->master_switch[0]) + left_vol = 0; + else { + left_vol = mix->master_vol[0]; + if (left_vol >= ARRAY_SIZE(master_volume_table)) + left_vol = ARRAY_SIZE(master_volume_table) - 1; + left_vol = master_volume_table[left_vol]; + } + if (! mix->master_switch[1]) + right_vol = 0; + else { + right_vol = mix->master_vol[1]; + if (right_vol >= ARRAY_SIZE(master_volume_table)) + right_vol = ARRAY_SIZE(master_volume_table) - 1; + right_vol = master_volume_table[right_vol]; + } + + block[0] = (left_vol >> 16) & 0xff; + block[1] = (left_vol >> 8) & 0xff; + block[2] = (left_vol >> 0) & 0xff; + + block[3] = (right_vol >> 16) & 0xff; + block[4] = (right_vol >> 8) & 0xff; + block[5] = (right_vol >> 0) & 0xff; + + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_VOL, 6, + block) < 0) { + snd_printk(KERN_ERR "failed to set volume \n"); + return -EINVAL; + } + DBG("(I) succeeded to set volume (%u, %u)\n", left_vol, right_vol); + return 0; +} + + +/* output volume */ +static int tumbler_info_master_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 = ARRAY_SIZE(master_volume_table) - 1; + return 0; +} + +static int tumbler_get_master_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + + ucontrol->value.integer.value[0] = mix->master_vol[0]; + ucontrol->value.integer.value[1] = mix->master_vol[1]; + return 0; +} + +static int tumbler_put_master_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + unsigned int vol[2]; + int change; + + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] >= ARRAY_SIZE(master_volume_table) || + vol[1] >= ARRAY_SIZE(master_volume_table)) + return -EINVAL; + change = mix->master_vol[0] != vol[0] || + mix->master_vol[1] != vol[1]; + if (change) { + mix->master_vol[0] = vol[0]; + mix->master_vol[1] = vol[1]; + tumbler_set_master_volume(mix); + } + return change; +} + +/* output switch */ +static int tumbler_get_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + + ucontrol->value.integer.value[0] = mix->master_switch[0]; + ucontrol->value.integer.value[1] = mix->master_switch[1]; + return 0; +} + +static int tumbler_put_master_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + int change; + + change = mix->master_switch[0] != ucontrol->value.integer.value[0] || + mix->master_switch[1] != ucontrol->value.integer.value[1]; + if (change) { + mix->master_switch[0] = !!ucontrol->value.integer.value[0]; + mix->master_switch[1] = !!ucontrol->value.integer.value[1]; + tumbler_set_master_volume(mix); + } + return change; +} + + +/* + * TAS3001c dynamic range compression + */ + +#define TAS3001_DRC_MAX 0x5f + +static int tumbler_set_drc(struct pmac_tumbler *mix) +{ + unsigned char val[2]; + + if (! mix->i2c.client) + return -ENODEV; + + if (mix->drc_enable) { + val[0] = 0xc1; /* enable, 3:1 compression */ + if (mix->drc_range > TAS3001_DRC_MAX) + val[1] = 0xf0; + else if (mix->drc_range < 0) + val[1] = 0x91; + else + val[1] = mix->drc_range + 0x91; + } else { + val[0] = 0; + val[1] = 0; + } + + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_DRC, + 2, val) < 0) { + snd_printk(KERN_ERR "failed to set DRC\n"); + return -EINVAL; + } + DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]); + return 0; +} + +/* + * TAS3004 + */ + +#define TAS3004_DRC_MAX 0xef + +static int snapper_set_drc(struct pmac_tumbler *mix) +{ + unsigned char val[6]; + + if (! mix->i2c.client) + return -ENODEV; + + if (mix->drc_enable) + val[0] = 0x50; /* 3:1 above threshold */ + else + val[0] = 0x51; /* disabled */ + val[1] = 0x02; /* 1:1 below threshold */ + if (mix->drc_range > 0xef) + val[2] = 0xef; + else if (mix->drc_range < 0) + val[2] = 0x00; + else + val[2] = mix->drc_range; + val[3] = 0xb0; + val[4] = 0x60; + val[5] = 0xa0; + + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, TAS_REG_DRC, + 6, val) < 0) { + snd_printk(KERN_ERR "failed to set DRC\n"); + return -EINVAL; + } + DBG("(I) succeeded to set DRC (%u, %u)\n", val[0], val[1]); + return 0; +} + +static int tumbler_info_drc_value(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = + chip->model == PMAC_TUMBLER ? TAS3001_DRC_MAX : TAS3004_DRC_MAX; + return 0; +} + +static int tumbler_get_drc_value(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->drc_range; + return 0; +} + +static int tumbler_put_drc_value(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + unsigned int val; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + val = ucontrol->value.integer.value[0]; + if (chip->model == PMAC_TUMBLER) { + if (val > TAS3001_DRC_MAX) + return -EINVAL; + } else { + if (val > TAS3004_DRC_MAX) + return -EINVAL; + } + change = mix->drc_range != val; + if (change) { + mix->drc_range = val; + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + } + return change; +} + +static int tumbler_get_drc_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->drc_enable; + return 0; +} + +static int tumbler_put_drc_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + change = mix->drc_enable != ucontrol->value.integer.value[0]; + if (change) { + mix->drc_enable = !!ucontrol->value.integer.value[0]; + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + } + return change; +} + + +/* + * mono volumes + */ + +struct tumbler_mono_vol { + int index; + int reg; + int bytes; + unsigned int max; + unsigned int *table; +}; + +static int tumbler_set_mono_volume(struct pmac_tumbler *mix, + struct tumbler_mono_vol *info) +{ + unsigned char block[4]; + unsigned int vol; + int i; + + if (! mix->i2c.client) + return -ENODEV; + + vol = mix->mono_vol[info->index]; + if (vol >= info->max) + vol = info->max - 1; + vol = info->table[vol]; + for (i = 0; i < info->bytes; i++) + block[i] = (vol >> ((info->bytes - i - 1) * 8)) & 0xff; + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, info->reg, + info->bytes, block) < 0) { + snd_printk(KERN_ERR "failed to set mono volume %d\n", + info->index); + return -EINVAL; + } + return 0; +} + +static int tumbler_info_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = info->max - 1; + return 0; +} + +static int tumbler_get_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->mono_vol[info->index]; + return 0; +} + +static int tumbler_put_mono(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct tumbler_mono_vol *info = (struct tumbler_mono_vol *)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + unsigned int vol; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + vol = ucontrol->value.integer.value[0]; + if (vol >= info->max) + return -EINVAL; + change = mix->mono_vol[info->index] != vol; + if (change) { + mix->mono_vol[info->index] = vol; + tumbler_set_mono_volume(mix, info); + } + return change; +} + +/* TAS3001c mono volumes */ +static struct tumbler_mono_vol tumbler_pcm_vol_info = { + .index = VOL_IDX_PCM_MONO, + .reg = TAS_REG_PCM, + .bytes = 3, + .max = ARRAY_SIZE(mixer_volume_table), + .table = mixer_volume_table, +}; + +static struct tumbler_mono_vol tumbler_bass_vol_info = { + .index = VOL_IDX_BASS, + .reg = TAS_REG_BASS, + .bytes = 1, + .max = ARRAY_SIZE(bass_volume_table), + .table = bass_volume_table, +}; + +static struct tumbler_mono_vol tumbler_treble_vol_info = { + .index = VOL_IDX_TREBLE, + .reg = TAS_REG_TREBLE, + .bytes = 1, + .max = ARRAY_SIZE(treble_volume_table), + .table = treble_volume_table, +}; + +/* TAS3004 mono volumes */ +static struct tumbler_mono_vol snapper_bass_vol_info = { + .index = VOL_IDX_BASS, + .reg = TAS_REG_BASS, + .bytes = 1, + .max = ARRAY_SIZE(snapper_bass_volume_table), + .table = snapper_bass_volume_table, +}; + +static struct tumbler_mono_vol snapper_treble_vol_info = { + .index = VOL_IDX_TREBLE, + .reg = TAS_REG_TREBLE, + .bytes = 1, + .max = ARRAY_SIZE(snapper_treble_volume_table), + .table = snapper_treble_volume_table, +}; + + +#define DEFINE_MONO(xname,type) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname, \ + .info = tumbler_info_mono, \ + .get = tumbler_get_mono, \ + .put = tumbler_put_mono, \ + .private_value = (unsigned long)(&tumbler_##type##_vol_info), \ +} + +#define DEFINE_SNAPPER_MONO(xname,type) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname, \ + .info = tumbler_info_mono, \ + .get = tumbler_get_mono, \ + .put = tumbler_put_mono, \ + .private_value = (unsigned long)(&snapper_##type##_vol_info), \ +} + + +/* + * snapper mixer volumes + */ + +static int snapper_set_mix_vol1(struct pmac_tumbler *mix, int idx, int ch, int reg) +{ + int i, j, vol; + unsigned char block[9]; + + vol = mix->mix_vol[idx][ch]; + if (vol >= ARRAY_SIZE(mixer_volume_table)) { + vol = ARRAY_SIZE(mixer_volume_table) - 1; + mix->mix_vol[idx][ch] = vol; + } + + for (i = 0; i < 3; i++) { + vol = mix->mix_vol[i][ch]; + vol = mixer_volume_table[vol]; + for (j = 0; j < 3; j++) + block[i * 3 + j] = (vol >> ((2 - j) * 8)) & 0xff; + } + if (i2c_smbus_write_i2c_block_data(mix->i2c.client, reg, + 9, block) < 0) { + snd_printk(KERN_ERR "failed to set mono volume %d\n", reg); + return -EINVAL; + } + return 0; +} + +static int snapper_set_mix_vol(struct pmac_tumbler *mix, int idx) +{ + if (! mix->i2c.client) + return -ENODEV; + if (snapper_set_mix_vol1(mix, idx, 0, TAS_REG_LMIX) < 0 || + snapper_set_mix_vol1(mix, idx, 1, TAS_REG_RMIX) < 0) + return -EINVAL; + return 0; +} + +static int snapper_info_mix(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 = ARRAY_SIZE(mixer_volume_table) - 1; + return 0; +} + +static int snapper_get_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int idx = (int)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + if (! (mix = chip->mixer_data)) + return -ENODEV; + ucontrol->value.integer.value[0] = mix->mix_vol[idx][0]; + ucontrol->value.integer.value[1] = mix->mix_vol[idx][1]; + return 0; +} + +static int snapper_put_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int idx = (int)kcontrol->private_value; + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + unsigned int vol[2]; + int change; + + if (! (mix = chip->mixer_data)) + return -ENODEV; + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (vol[0] >= ARRAY_SIZE(mixer_volume_table) || + vol[1] >= ARRAY_SIZE(mixer_volume_table)) + return -EINVAL; + change = mix->mix_vol[idx][0] != vol[0] || + mix->mix_vol[idx][1] != vol[1]; + if (change) { + mix->mix_vol[idx][0] = vol[0]; + mix->mix_vol[idx][1] = vol[1]; + snapper_set_mix_vol(mix, idx); + } + return change; +} + + +/* + * mute switches. FIXME: Turn that into software mute when both outputs are muted + * to avoid codec reset on ibook M7 + */ + +enum { TUMBLER_MUTE_HP, TUMBLER_MUTE_AMP, TUMBLER_MUTE_LINE }; + +static int tumbler_get_mute_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + struct pmac_gpio *gp; + if (! (mix = chip->mixer_data)) + return -ENODEV; + switch(kcontrol->private_value) { + case TUMBLER_MUTE_HP: + gp = &mix->hp_mute; break; + case TUMBLER_MUTE_AMP: + gp = &mix->amp_mute; break; + case TUMBLER_MUTE_LINE: + gp = &mix->line_mute; break; + default: + gp = NULL; + } + if (gp == NULL) + return -EINVAL; + ucontrol->value.integer.value[0] = !check_audio_gpio(gp); + return 0; +} + +static int tumbler_put_mute_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix; + struct pmac_gpio *gp; + int val; +#ifdef PMAC_SUPPORT_AUTOMUTE + if (chip->update_automute && chip->auto_mute) + return 0; /* don't touch in the auto-mute mode */ +#endif + if (! (mix = chip->mixer_data)) + return -ENODEV; + switch(kcontrol->private_value) { + case TUMBLER_MUTE_HP: + gp = &mix->hp_mute; break; + case TUMBLER_MUTE_AMP: + gp = &mix->amp_mute; break; + case TUMBLER_MUTE_LINE: + gp = &mix->line_mute; break; + default: + gp = NULL; + } + if (gp == NULL) + return -EINVAL; + val = ! check_audio_gpio(gp); + if (val != ucontrol->value.integer.value[0]) { + write_audio_gpio(gp, ! ucontrol->value.integer.value[0]); + return 1; + } + return 0; +} + +static int snapper_set_capture_source(struct pmac_tumbler *mix) +{ + if (! mix->i2c.client) + return -ENODEV; + if (mix->capture_source) + mix->acs |= 2; + else + mix->acs &= ~2; + return i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS, mix->acs); +} + +static int snapper_info_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "Line", "Mic" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snapper_get_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + + ucontrol->value.enumerated.item[0] = mix->capture_source; + return 0; +} + +static int snapper_put_capture_source(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pmac *chip = snd_kcontrol_chip(kcontrol); + struct pmac_tumbler *mix = chip->mixer_data; + int change; + + change = ucontrol->value.enumerated.item[0] != mix->capture_source; + if (change) { + mix->capture_source = !!ucontrol->value.enumerated.item[0]; + snapper_set_capture_source(mix); + } + return change; +} + +#define DEFINE_SNAPPER_MIX(xname,idx,ofs) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname, \ + .info = snapper_info_mix, \ + .get = snapper_get_mix, \ + .put = snapper_put_mix, \ + .index = idx,\ + .private_value = ofs, \ +} + + +/* + */ +static struct snd_kcontrol_new tumbler_mixers[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = tumbler_info_master_volume, + .get = tumbler_get_master_volume, + .put = tumbler_put_master_volume + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = tumbler_get_master_switch, + .put = tumbler_put_master_switch + }, + DEFINE_MONO("Tone Control - Bass", bass), + DEFINE_MONO("Tone Control - Treble", treble), + DEFINE_MONO("PCM Playback Volume", pcm), + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Range", + .info = tumbler_info_drc_value, + .get = tumbler_get_drc_value, + .put = tumbler_put_drc_value + }, +}; + +static struct snd_kcontrol_new snapper_mixers[] = { + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = tumbler_info_master_volume, + .get = tumbler_get_master_volume, + .put = tumbler_put_master_volume + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_pmac_boolean_stereo_info, + .get = tumbler_get_master_switch, + .put = tumbler_put_master_switch + }, + DEFINE_SNAPPER_MIX("PCM Playback Volume", 0, VOL_IDX_PCM), + /* Alternative PCM is assigned to Mic analog loopback on iBook G4 */ + DEFINE_SNAPPER_MIX("Mic Playback Volume", 0, VOL_IDX_PCM2), + DEFINE_SNAPPER_MIX("Monitor Mix Volume", 0, VOL_IDX_ADC), + DEFINE_SNAPPER_MONO("Tone Control - Bass", bass), + DEFINE_SNAPPER_MONO("Tone Control - Treble", treble), + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Range", + .info = tumbler_info_drc_value, + .get = tumbler_get_drc_value, + .put = tumbler_put_drc_value + }, + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", /* FIXME: "Capture Source" doesn't work properly */ + .info = snapper_info_capture_source, + .get = snapper_get_capture_source, + .put = snapper_put_capture_source + }, +}; + +static const struct snd_kcontrol_new tumbler_hp_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_mute_switch, + .put = tumbler_put_mute_switch, + .private_value = TUMBLER_MUTE_HP, +}; +static const struct snd_kcontrol_new tumbler_speaker_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speaker Playback Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_mute_switch, + .put = tumbler_put_mute_switch, + .private_value = TUMBLER_MUTE_AMP, +}; +static const struct snd_kcontrol_new tumbler_lineout_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Out Playback Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_mute_switch, + .put = tumbler_put_mute_switch, + .private_value = TUMBLER_MUTE_LINE, +}; +static const struct snd_kcontrol_new tumbler_drc_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DRC Switch", + .info = snd_pmac_boolean_mono_info, + .get = tumbler_get_drc_switch, + .put = tumbler_put_drc_switch +}; + + +#ifdef PMAC_SUPPORT_AUTOMUTE +/* + * auto-mute stuffs + */ +static int tumbler_detect_headphone(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + int detect = 0; + + if (mix->hp_detect.addr) + detect |= read_audio_gpio(&mix->hp_detect); + return detect; +} + +static int tumbler_detect_lineout(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + int detect = 0; + + if (mix->line_detect.addr) + detect |= read_audio_gpio(&mix->line_detect); + return detect; +} + +static void check_mute(struct snd_pmac *chip, struct pmac_gpio *gp, int val, int do_notify, + struct snd_kcontrol *sw) +{ + if (check_audio_gpio(gp) != val) { + write_audio_gpio(gp, val); + if (do_notify) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &sw->id); + } +} + +static struct work_struct device_change; +static struct snd_pmac *device_change_chip; + +static void device_change_handler(struct work_struct *work) +{ + struct snd_pmac *chip = device_change_chip; + struct pmac_tumbler *mix; + int headphone, lineout; + + if (!chip) + return; + + mix = chip->mixer_data; + if (snd_BUG_ON(!mix)) + return; + + headphone = tumbler_detect_headphone(chip); + lineout = tumbler_detect_lineout(chip); + + DBG("headphone: %d, lineout: %d\n", headphone, lineout); + + if (headphone || lineout) { + /* unmute headphone/lineout & mute speaker */ + if (headphone) + check_mute(chip, &mix->hp_mute, 0, mix->auto_mute_notify, + chip->master_sw_ctl); + if (lineout && mix->line_mute.addr != 0) + check_mute(chip, &mix->line_mute, 0, mix->auto_mute_notify, + chip->lineout_sw_ctl); + if (mix->anded_reset) + msleep(10); + check_mute(chip, &mix->amp_mute, !IS_G4DA, mix->auto_mute_notify, + chip->speaker_sw_ctl); + } else { + /* unmute speaker, mute others */ + check_mute(chip, &mix->amp_mute, 0, mix->auto_mute_notify, + chip->speaker_sw_ctl); + if (mix->anded_reset) + msleep(10); + check_mute(chip, &mix->hp_mute, 1, mix->auto_mute_notify, + chip->master_sw_ctl); + if (mix->line_mute.addr != 0) + check_mute(chip, &mix->line_mute, 1, mix->auto_mute_notify, + chip->lineout_sw_ctl); + } + if (mix->auto_mute_notify) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->hp_detect_ctl->id); + +#ifdef CONFIG_SND_POWERMAC_AUTO_DRC + mix->drc_enable = ! (headphone || lineout); + if (mix->auto_mute_notify) + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->drc_sw_ctl->id); + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); +#endif + + /* reset the master volume so the correct amplification is applied */ + tumbler_set_master_volume(mix); +} + +static void tumbler_update_automute(struct snd_pmac *chip, int do_notify) +{ + if (chip->auto_mute) { + struct pmac_tumbler *mix; + mix = chip->mixer_data; + if (snd_BUG_ON(!mix)) + return; + mix->auto_mute_notify = do_notify; + schedule_work(&device_change); + } +} +#endif /* PMAC_SUPPORT_AUTOMUTE */ + + +/* interrupt - headphone plug changed */ +static irqreturn_t headphone_intr(int irq, void *devid) +{ + struct snd_pmac *chip = devid; + if (chip->update_automute && chip->initialized) { + chip->update_automute(chip, 1); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +/* look for audio-gpio device */ +static struct device_node *find_audio_device(const char *name) +{ + struct device_node *gpiop; + struct device_node *np; + + gpiop = of_find_node_by_name(NULL, "gpio"); + if (! gpiop) + return NULL; + + for (np = of_get_next_child(gpiop, NULL); np; + np = of_get_next_child(gpiop, np)) { + const char *property = of_get_property(np, "audio-gpio", NULL); + if (property && strcmp(property, name) == 0) + break; + } + of_node_put(gpiop); + return np; +} + +/* look for audio-gpio device */ +static struct device_node *find_compatible_audio_device(const char *name) +{ + struct device_node *gpiop; + struct device_node *np; + + gpiop = of_find_node_by_name(NULL, "gpio"); + if (!gpiop) + return NULL; + + for (np = of_get_next_child(gpiop, NULL); np; + np = of_get_next_child(gpiop, np)) { + if (of_device_is_compatible(np, name)) + break; + } + of_node_put(gpiop); + return np; +} + +/* find an audio device and get its address */ +static long tumbler_find_device(const char *device, const char *platform, + struct pmac_gpio *gp, int is_compatible) +{ + struct device_node *node; + const u32 *base; + u32 addr; + long ret; + + if (is_compatible) + node = find_compatible_audio_device(device); + else + node = find_audio_device(device); + if (! node) { + DBG("(W) cannot find audio device %s !\n", device); + snd_printdd("cannot find device %s\n", device); + return -ENODEV; + } + + base = of_get_property(node, "AAPL,address", NULL); + if (! base) { + base = of_get_property(node, "reg", NULL); + if (!base) { + DBG("(E) cannot find address for device %s !\n", device); + snd_printd("cannot find address for device %s\n", device); + of_node_put(node); + return -ENODEV; + } + addr = *base; + if (addr < 0x50) + addr += 0x50; + } else + addr = *base; + + gp->addr = addr & 0x0000ffff; + /* Try to find the active state, default to 0 ! */ + base = of_get_property(node, "audio-gpio-active-state", NULL); + if (base) { + gp->active_state = *base; + gp->active_val = (*base) ? 0x5 : 0x4; + gp->inactive_val = (*base) ? 0x4 : 0x5; + } else { + const u32 *prop = NULL; + gp->active_state = IS_G4DA + && !strncmp(device, "keywest-gpio1", 13); + gp->active_val = 0x4; + gp->inactive_val = 0x5; + /* Here are some crude hacks to extract the GPIO polarity and + * open collector informations out of the do-platform script + * as we don't yet have an interpreter for these things + */ + if (platform) + prop = of_get_property(node, platform, NULL); + if (prop) { + if (prop[3] == 0x9 && prop[4] == 0x9) { + gp->active_val = 0xd; + gp->inactive_val = 0xc; + } + if (prop[3] == 0x1 && prop[4] == 0x1) { + gp->active_val = 0x5; + gp->inactive_val = 0x4; + } + } + } + + DBG("(I) GPIO device %s found, offset: %x, active state: %d !\n", + device, gp->addr, gp->active_state); + + ret = irq_of_parse_and_map(node, 0); + of_node_put(node); + return ret; +} + +/* reset audio */ +static void tumbler_reset_audio(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + + if (mix->anded_reset) { + DBG("(I) codec anded reset !\n"); + write_audio_gpio(&mix->hp_mute, 0); + write_audio_gpio(&mix->amp_mute, 0); + msleep(200); + write_audio_gpio(&mix->hp_mute, 1); + write_audio_gpio(&mix->amp_mute, 1); + msleep(100); + write_audio_gpio(&mix->hp_mute, 0); + write_audio_gpio(&mix->amp_mute, 0); + msleep(100); + } else { + DBG("(I) codec normal reset !\n"); + + write_audio_gpio(&mix->audio_reset, 0); + msleep(200); + write_audio_gpio(&mix->audio_reset, 1); + msleep(100); + write_audio_gpio(&mix->audio_reset, 0); + msleep(100); + } +} + +#ifdef CONFIG_PM +/* suspend mixer */ +static void tumbler_suspend(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + + if (mix->headphone_irq >= 0) + disable_irq(mix->headphone_irq); + if (mix->lineout_irq >= 0) + disable_irq(mix->lineout_irq); + mix->save_master_switch[0] = mix->master_switch[0]; + mix->save_master_switch[1] = mix->master_switch[1]; + mix->save_master_vol[0] = mix->master_vol[0]; + mix->save_master_vol[1] = mix->master_vol[1]; + mix->master_switch[0] = mix->master_switch[1] = 0; + tumbler_set_master_volume(mix); + if (!mix->anded_reset) { + write_audio_gpio(&mix->amp_mute, 1); + write_audio_gpio(&mix->hp_mute, 1); + } + if (chip->model == PMAC_SNAPPER) { + mix->acs |= 1; + i2c_smbus_write_byte_data(mix->i2c.client, TAS_REG_ACS, mix->acs); + } + if (mix->anded_reset) { + write_audio_gpio(&mix->amp_mute, 1); + write_audio_gpio(&mix->hp_mute, 1); + } else + write_audio_gpio(&mix->audio_reset, 1); +} + +/* resume mixer */ +static void tumbler_resume(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + + mix->acs &= ~1; + mix->master_switch[0] = mix->save_master_switch[0]; + mix->master_switch[1] = mix->save_master_switch[1]; + mix->master_vol[0] = mix->save_master_vol[0]; + mix->master_vol[1] = mix->save_master_vol[1]; + tumbler_reset_audio(chip); + if (mix->i2c.client && mix->i2c.init_client) { + if (mix->i2c.init_client(&mix->i2c) < 0) + printk(KERN_ERR "tumbler_init_client error\n"); + } else + printk(KERN_ERR "tumbler: i2c is not initialized\n"); + if (chip->model == PMAC_TUMBLER) { + tumbler_set_mono_volume(mix, &tumbler_pcm_vol_info); + tumbler_set_mono_volume(mix, &tumbler_bass_vol_info); + tumbler_set_mono_volume(mix, &tumbler_treble_vol_info); + tumbler_set_drc(mix); + } else { + snapper_set_mix_vol(mix, VOL_IDX_PCM); + snapper_set_mix_vol(mix, VOL_IDX_PCM2); + snapper_set_mix_vol(mix, VOL_IDX_ADC); + tumbler_set_mono_volume(mix, &snapper_bass_vol_info); + tumbler_set_mono_volume(mix, &snapper_treble_vol_info); + snapper_set_drc(mix); + snapper_set_capture_source(mix); + } + tumbler_set_master_volume(mix); + if (chip->update_automute) + chip->update_automute(chip, 0); + if (mix->headphone_irq >= 0) { + unsigned char val; + + enable_irq(mix->headphone_irq); + /* activate headphone status interrupts */ + val = do_gpio_read(&mix->hp_detect); + do_gpio_write(&mix->hp_detect, val | 0x80); + } + if (mix->lineout_irq >= 0) + enable_irq(mix->lineout_irq); +} +#endif + +/* initialize tumbler */ +static int tumbler_init(struct snd_pmac *chip) +{ + int irq; + struct pmac_tumbler *mix = chip->mixer_data; + + if (tumbler_find_device("audio-hw-reset", + "platform-do-hw-reset", + &mix->audio_reset, 0) < 0) + tumbler_find_device("hw-reset", + "platform-do-hw-reset", + &mix->audio_reset, 1); + if (tumbler_find_device("amp-mute", + "platform-do-amp-mute", + &mix->amp_mute, 0) < 0) + tumbler_find_device("amp-mute", + "platform-do-amp-mute", + &mix->amp_mute, 1); + if (tumbler_find_device("headphone-mute", + "platform-do-headphone-mute", + &mix->hp_mute, 0) < 0) + tumbler_find_device("headphone-mute", + "platform-do-headphone-mute", + &mix->hp_mute, 1); + if (tumbler_find_device("line-output-mute", + "platform-do-lineout-mute", + &mix->line_mute, 0) < 0) + tumbler_find_device("line-output-mute", + "platform-do-lineout-mute", + &mix->line_mute, 1); + irq = tumbler_find_device("headphone-detect", + NULL, &mix->hp_detect, 0); + if (irq <= 0) + irq = tumbler_find_device("headphone-detect", + NULL, &mix->hp_detect, 1); + if (irq <= 0) + irq = tumbler_find_device("keywest-gpio15", + NULL, &mix->hp_detect, 1); + mix->headphone_irq = irq; + irq = tumbler_find_device("line-output-detect", + NULL, &mix->line_detect, 0); + if (irq <= 0) + irq = tumbler_find_device("line-output-detect", + NULL, &mix->line_detect, 1); + if (IS_G4DA && irq <= 0) + irq = tumbler_find_device("keywest-gpio16", + NULL, &mix->line_detect, 1); + mix->lineout_irq = irq; + + tumbler_reset_audio(chip); + + return 0; +} + +static void tumbler_cleanup(struct snd_pmac *chip) +{ + struct pmac_tumbler *mix = chip->mixer_data; + if (! mix) + return; + + if (mix->headphone_irq >= 0) + free_irq(mix->headphone_irq, chip); + if (mix->lineout_irq >= 0) + free_irq(mix->lineout_irq, chip); + tumbler_gpio_free(&mix->audio_reset); + tumbler_gpio_free(&mix->amp_mute); + tumbler_gpio_free(&mix->hp_mute); + tumbler_gpio_free(&mix->hp_detect); + snd_pmac_keywest_cleanup(&mix->i2c); + kfree(mix); + chip->mixer_data = NULL; +} + +/* exported */ +int snd_pmac_tumbler_init(struct snd_pmac *chip) +{ + int i, err; + struct pmac_tumbler *mix; + const u32 *paddr; + struct device_node *tas_node, *np; + char *chipname; + + request_module("i2c-powermac"); + + mix = kzalloc(sizeof(*mix), GFP_KERNEL); + if (! mix) + return -ENOMEM; + mix->headphone_irq = -1; + + chip->mixer_data = mix; + chip->mixer_free = tumbler_cleanup; + mix->anded_reset = 0; + mix->reset_on_sleep = 1; + + for (np = chip->node->child; np; np = np->sibling) { + if (!strcmp(np->name, "sound")) { + if (of_get_property(np, "has-anded-reset", NULL)) + mix->anded_reset = 1; + if (of_get_property(np, "layout-id", NULL)) + mix->reset_on_sleep = 0; + break; + } + } + if ((err = tumbler_init(chip)) < 0) + return err; + + /* set up TAS */ + tas_node = of_find_node_by_name(NULL, "deq"); + if (tas_node == NULL) + tas_node = of_find_node_by_name(NULL, "codec"); + if (tas_node == NULL) + return -ENODEV; + + paddr = of_get_property(tas_node, "i2c-address", NULL); + if (paddr == NULL) + paddr = of_get_property(tas_node, "reg", NULL); + if (paddr) + mix->i2c.addr = (*paddr) >> 1; + else + mix->i2c.addr = TAS_I2C_ADDR; + of_node_put(tas_node); + + DBG("(I) TAS i2c address is: %x\n", mix->i2c.addr); + + if (chip->model == PMAC_TUMBLER) { + mix->i2c.init_client = tumbler_init_client; + mix->i2c.name = "TAS3001c"; + chipname = "Tumbler"; + } else { + mix->i2c.init_client = snapper_init_client; + mix->i2c.name = "TAS3004"; + chipname = "Snapper"; + } + + if ((err = snd_pmac_keywest_init(&mix->i2c)) < 0) + return err; + + /* + * build mixers + */ + sprintf(chip->card->mixername, "PowerMac %s", chipname); + + if (chip->model == PMAC_TUMBLER) { + for (i = 0; i < ARRAY_SIZE(tumbler_mixers); i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&tumbler_mixers[i], chip))) < 0) + return err; + } + } else { + for (i = 0; i < ARRAY_SIZE(snapper_mixers); i++) { + if ((err = snd_ctl_add(chip->card, snd_ctl_new1(&snapper_mixers[i], chip))) < 0) + return err; + } + } + chip->master_sw_ctl = snd_ctl_new1(&tumbler_hp_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->master_sw_ctl)) < 0) + return err; + chip->speaker_sw_ctl = snd_ctl_new1(&tumbler_speaker_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->speaker_sw_ctl)) < 0) + return err; + if (mix->line_mute.addr != 0) { + chip->lineout_sw_ctl = snd_ctl_new1(&tumbler_lineout_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->lineout_sw_ctl)) < 0) + return err; + } + chip->drc_sw_ctl = snd_ctl_new1(&tumbler_drc_sw, chip); + if ((err = snd_ctl_add(chip->card, chip->drc_sw_ctl)) < 0) + return err; + + /* set initial DRC range to 60% */ + if (chip->model == PMAC_TUMBLER) + mix->drc_range = (TAS3001_DRC_MAX * 6) / 10; + else + mix->drc_range = (TAS3004_DRC_MAX * 6) / 10; + mix->drc_enable = 1; /* will be changed later if AUTO_DRC is set */ + if (chip->model == PMAC_TUMBLER) + tumbler_set_drc(mix); + else + snapper_set_drc(mix); + +#ifdef CONFIG_PM + chip->suspend = tumbler_suspend; + chip->resume = tumbler_resume; +#endif + + INIT_WORK(&device_change, device_change_handler); + device_change_chip = chip; + +#ifdef PMAC_SUPPORT_AUTOMUTE + if ((mix->headphone_irq >=0 || mix->lineout_irq >= 0) + && (err = snd_pmac_add_automute(chip)) < 0) + return err; + chip->detect_headphone = tumbler_detect_headphone; + chip->update_automute = tumbler_update_automute; + tumbler_update_automute(chip, 0); /* update the status only */ + + /* activate headphone status interrupts */ + if (mix->headphone_irq >= 0) { + unsigned char val; + if ((err = request_irq(mix->headphone_irq, headphone_intr, 0, + "Sound Headphone Detection", chip)) < 0) + return 0; + /* activate headphone status interrupts */ + val = do_gpio_read(&mix->hp_detect); + do_gpio_write(&mix->hp_detect, val | 0x80); + } + if (mix->lineout_irq >= 0) { + unsigned char val; + if ((err = request_irq(mix->lineout_irq, headphone_intr, 0, + "Sound Lineout Detection", chip)) < 0) + return 0; + /* activate headphone status interrupts */ + val = do_gpio_read(&mix->line_detect); + do_gpio_write(&mix->line_detect, val | 0x80); + } +#endif + + return 0; +} diff --git a/sound/ppc/tumbler_volume.h b/sound/ppc/tumbler_volume.h new file mode 100644 index 000000000..549ec6a31 --- /dev/null +++ b/sound/ppc/tumbler_volume.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* volume tables, taken from TAS3001c data manual */ +/* volume gain values */ +/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */ +static unsigned int master_volume_table[] = { + 0x00000015, 0x00000016, 0x00000017, + 0x00000019, 0x0000001a, 0x0000001c, + 0x0000001d, 0x0000001f, 0x00000021, + 0x00000023, 0x00000025, 0x00000027, + 0x00000029, 0x0000002c, 0x0000002e, + 0x00000031, 0x00000034, 0x00000037, + 0x0000003a, 0x0000003e, 0x00000042, + 0x00000045, 0x0000004a, 0x0000004e, + 0x00000053, 0x00000057, 0x0000005d, + 0x00000062, 0x00000068, 0x0000006e, + 0x00000075, 0x0000007b, 0x00000083, + 0x0000008b, 0x00000093, 0x0000009b, + 0x000000a5, 0x000000ae, 0x000000b9, + 0x000000c4, 0x000000cf, 0x000000dc, + 0x000000e9, 0x000000f6, 0x00000105, + 0x00000114, 0x00000125, 0x00000136, + 0x00000148, 0x0000015c, 0x00000171, + 0x00000186, 0x0000019e, 0x000001b6, + 0x000001d0, 0x000001eb, 0x00000209, + 0x00000227, 0x00000248, 0x0000026b, + 0x0000028f, 0x000002b6, 0x000002df, + 0x0000030b, 0x00000339, 0x0000036a, + 0x0000039e, 0x000003d5, 0x0000040f, + 0x0000044c, 0x0000048d, 0x000004d2, + 0x0000051c, 0x00000569, 0x000005bb, + 0x00000612, 0x0000066e, 0x000006d0, + 0x00000737, 0x000007a5, 0x00000818, + 0x00000893, 0x00000915, 0x0000099f, + 0x00000a31, 0x00000acc, 0x00000b6f, + 0x00000c1d, 0x00000cd5, 0x00000d97, + 0x00000e65, 0x00000f40, 0x00001027, + 0x0000111c, 0x00001220, 0x00001333, + 0x00001456, 0x0000158a, 0x000016d1, + 0x0000182b, 0x0000199a, 0x00001b1e, + 0x00001cb9, 0x00001e6d, 0x0000203a, + 0x00002223, 0x00002429, 0x0000264e, + 0x00002893, 0x00002afa, 0x00002d86, + 0x00003039, 0x00003314, 0x0000361b, + 0x00003950, 0x00003cb5, 0x0000404e, + 0x0000441d, 0x00004827, 0x00004c6d, + 0x000050f4, 0x000055c0, 0x00005ad5, + 0x00006037, 0x000065ea, 0x00006bf4, + 0x0000725a, 0x00007920, 0x0000804e, + 0x000087e8, 0x00008ff6, 0x0000987d, + 0x0000a186, 0x0000ab19, 0x0000b53c, + 0x0000bff9, 0x0000cb59, 0x0000d766, + 0x0000e429, 0x0000f1ae, 0x00010000, + 0x00010f2b, 0x00011f3d, 0x00013042, + 0x00014249, 0x00015562, 0x0001699c, + 0x00017f09, 0x000195bc, 0x0001adc6, + 0x0001c73d, 0x0001e237, 0x0001feca, + 0x00021d0e, 0x00023d1d, 0x00025f12, + 0x0002830b, 0x0002a925, 0x0002d182, + 0x0002fc42, 0x0003298b, 0x00035983, + 0x00038c53, 0x0003c225, 0x0003fb28, + 0x0004378b, 0x00047783, 0x0004bb44, + 0x0005030a, 0x00054f10, 0x00059f98, + 0x0005f4e5, 0x00064f40, 0x0006aef6, + 0x00071457, 0x00077fbb, 0x0007f17b, +}; + +/* treble table for TAS3001c */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int treble_volume_table[] = { + 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, + 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, + 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, + 0x81, 0x80, 0x7f, + 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, + 0x78, 0x77, 0x76, + 0x75, 0x74, 0x73, + 0x72, 0x71, 0x70, + 0x6e, 0x6d, 0x6c, + 0x6b, 0x69, 0x68, + 0x66, 0x65, 0x63, + 0x62, 0x60, 0x5e, + 0x5c, 0x5a, 0x57, + 0x55, 0x52, 0x4f, + 0x4c, 0x49, 0x45, + 0x42, 0x3e, 0x3a, + 0x36, 0x32, 0x2d, + 0x28, 0x22, 0x1c, + 0x16, 0x10, 0x09, + 0x01, +}; + +/* bass table for TAS3001c */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int bass_volume_table[] = { + 0x86, 0x82, 0x7f, + 0x7d, 0x7a, 0x78, + 0x76, 0x74, 0x72, + 0x70, 0x6e, 0x6d, + 0x6b, 0x69, 0x66, + 0x64, 0x61, 0x5f, + 0x5d, 0x5c, 0x5a, + 0x59, 0x58, 0x56, + 0x55, 0x54, 0x53, + 0x51, 0x4f, 0x4d, + 0x4b, 0x49, 0x46, + 0x44, 0x42, 0x40, + 0x3e, 0x3c, 0x3b, + 0x39, 0x38, 0x36, + 0x35, 0x33, 0x31, + 0x30, 0x2e, 0x2c, + 0x2b, 0x29, 0x28, + 0x26, 0x25, 0x23, + 0x21, 0x1f, 0x1c, + 0x19, 0x18, 0x17, + 0x16, 0x14, 0x13, + 0x12, 0x10, 0x0f, + 0x0d, 0x0b, 0x0a, + 0x08, 0x06, 0x03, + 0x01, +}; + +/* mixer (pcm) volume table */ +/* 0 = -70 dB, 175 = 18.0 dB in 0.5 dB step */ +static unsigned int mixer_volume_table[] = { + 0x00014b, 0x00015f, 0x000174, + 0x00018a, 0x0001a1, 0x0001ba, + 0x0001d4, 0x0001f0, 0x00020d, + 0x00022c, 0x00024d, 0x000270, + 0x000295, 0x0002bc, 0x0002e6, + 0x000312, 0x000340, 0x000372, + 0x0003a6, 0x0003dd, 0x000418, + 0x000456, 0x000498, 0x0004de, + 0x000528, 0x000576, 0x0005c9, + 0x000620, 0x00067d, 0x0006e0, + 0x000748, 0x0007b7, 0x00082c, + 0x0008a8, 0x00092b, 0x0009b6, + 0x000a49, 0x000ae5, 0x000b8b, + 0x000c3a, 0x000cf3, 0x000db8, + 0x000e88, 0x000f64, 0x00104e, + 0x001145, 0x00124b, 0x001361, + 0x001487, 0x0015be, 0x001708, + 0x001865, 0x0019d8, 0x001b60, + 0x001cff, 0x001eb7, 0x002089, + 0x002276, 0x002481, 0x0026ab, + 0x0028f5, 0x002b63, 0x002df5, + 0x0030ae, 0x003390, 0x00369e, + 0x0039db, 0x003d49, 0x0040ea, + 0x0044c3, 0x0048d6, 0x004d27, + 0x0051b9, 0x005691, 0x005bb2, + 0x006121, 0x0066e3, 0x006cfb, + 0x007370, 0x007a48, 0x008186, + 0x008933, 0x009154, 0x0099f1, + 0x00a310, 0x00acba, 0x00b6f6, + 0x00c1cd, 0x00cd49, 0x00d973, + 0x00e655, 0x00f3fb, 0x010270, + 0x0111c0, 0x0121f9, 0x013328, + 0x01455b, 0x0158a2, 0x016d0e, + 0x0182af, 0x019999, 0x01b1de, + 0x01cb94, 0x01e6cf, 0x0203a7, + 0x022235, 0x024293, 0x0264db, + 0x02892c, 0x02afa3, 0x02d862, + 0x03038a, 0x033142, 0x0361af, + 0x0394fa, 0x03cb50, 0x0404de, + 0x0441d5, 0x048268, 0x04c6d0, + 0x050f44, 0x055c04, 0x05ad50, + 0x06036e, 0x065ea5, 0x06bf44, + 0x07259d, 0x079207, 0x0804dc, + 0x087e80, 0x08ff59, 0x0987d5, + 0x0a1866, 0x0ab189, 0x0b53be, + 0x0bff91, 0x0cb591, 0x0d765a, + 0x0e4290, 0x0f1adf, 0x100000, + 0x10f2b4, 0x11f3c9, 0x13041a, + 0x14248e, 0x15561a, 0x1699c0, + 0x17f094, 0x195bb8, 0x1adc61, + 0x1c73d5, 0x1e236d, 0x1fec98, + 0x21d0d9, 0x23d1cd, 0x25f125, + 0x2830af, 0x2a9254, 0x2d1818, + 0x2fc420, 0x3298b0, 0x35982f, + 0x38c528, 0x3c224c, 0x3fb278, + 0x437880, 0x477828, 0x4bb446, + 0x5030a1, 0x54f106, 0x59f980, + 0x5f4e52, 0x64f403, 0x6aef5d, + 0x714575, 0x77fbaa, 0x7f17af, +}; + + +/* treble table for TAS3004 */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int snapper_treble_volume_table[] = { + 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, + 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, + 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, + 0x81, 0x80, 0x7f, + 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, + 0x78, 0x77, 0x76, + 0x75, 0x74, 0x73, + 0x72, 0x71, 0x70, + 0x6f, 0x6d, 0x6c, + 0x6b, 0x69, 0x68, + 0x67, 0x65, 0x63, + 0x62, 0x60, 0x5d, + 0x5b, 0x59, 0x56, + 0x53, 0x51, 0x4d, + 0x4a, 0x47, 0x43, + 0x3f, 0x3b, 0x36, + 0x31, 0x2c, 0x26, + 0x20, 0x1a, 0x13, + 0x08, 0x04, 0x01, + 0x01, +}; + +/* bass table for TAS3004 */ +/* 0 = -18 dB, 72 = 18 dB in 0.5 dB step */ +static unsigned int snapper_bass_volume_table[] = { + 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, + 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, + 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, + 0x81, 0x80, 0x7f, + 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, + 0x78, 0x77, 0x76, + 0x75, 0x74, 0x73, + 0x72, 0x71, 0x6f, + 0x6e, 0x6d, 0x6b, + 0x6a, 0x69, 0x67, + 0x66, 0x65, 0x63, + 0x62, 0x61, 0x5f, + 0x5d, 0x5b, 0x58, + 0x55, 0x52, 0x4f, + 0x4c, 0x49, 0x46, + 0x43, 0x3f, 0x3b, + 0x37, 0x33, 0x2e, + 0x29, 0x24, 0x1e, + 0x18, 0x11, 0x0a, + 0x01, +}; + diff --git a/sound/sh/Kconfig b/sound/sh/Kconfig new file mode 100644 index 000000000..61139f3c1 --- /dev/null +++ b/sound/sh/Kconfig @@ -0,0 +1,31 @@ +# ALSA SH drivers + +menuconfig SND_SUPERH + bool "SUPERH sound devices" + depends on SUPERH + default y + help + Support for sound devices specific to SUPERH architectures. + Drivers that are implemented on ASoC can be found in + "ALSA for SoC audio support" section. + +if SND_SUPERH + +config SND_AICA + tristate "Dreamcast Yamaha AICA sound" + depends on SH_DREAMCAST + select SND_PCM + select G2_DMA + help + ALSA Sound driver for the SEGA Dreamcast console. + +config SND_SH_DAC_AUDIO + tristate "SuperH DAC audio support" + depends on SND + depends on CPU_SH3 && HIGH_RES_TIMERS + select SND_PCM + help + Say Y here to include support for the on-chip DAC. + +endif # SND_SUPERH + diff --git a/sound/sh/Makefile b/sound/sh/Makefile new file mode 100644 index 000000000..7d09b5188 --- /dev/null +++ b/sound/sh/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# + +snd-aica-objs := aica.o +snd-sh_dac_audio-objs := sh_dac_audio.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AICA) += snd-aica.o +obj-$(CONFIG_SND_SH_DAC_AUDIO) += snd-sh_dac_audio.o diff --git a/sound/sh/aica.c b/sound/sh/aica.c new file mode 100644 index 000000000..69ac44b33 --- /dev/null +++ b/sound/sh/aica.c @@ -0,0 +1,667 @@ +/* +* This code is licenced under +* the General Public Licence +* version 2 +* +* Copyright Adrian McMenamin 2005, 2006, 2007 +* +* Requires firmware (BSD licenced) available from: +* http://linuxdc.cvs.sourceforge.net/linuxdc/linux-sh-dc/sound/oss/aica/firmware/ +* or the maintainer +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of version 2 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 +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aica.h" + +MODULE_AUTHOR("Adrian McMenamin "); +MODULE_DESCRIPTION("Dreamcast AICA sound (pcm) driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Yamaha/SEGA, AICA}}"); +MODULE_FIRMWARE("aica_firmware.bin"); + +/* module parameters */ +#define CARD_NAME "AICA" +static int index = -1; +static char *id; +static bool enable = 1; +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(enable, bool, 0644); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); + +/* Simple platform device */ +static struct platform_device *pd; +static struct resource aica_memory_space[2] = { + { + .name = "AICA ARM CONTROL", + .start = ARM_RESET_REGISTER, + .flags = IORESOURCE_MEM, + .end = ARM_RESET_REGISTER + 3, + }, + { + .name = "AICA Sound RAM", + .start = SPU_MEMORY_BASE, + .flags = IORESOURCE_MEM, + .end = SPU_MEMORY_BASE + 0x200000 - 1, + }, +}; + +/* SPU specific functions */ +/* spu_write_wait - wait for G2-SH FIFO to clear */ +static void spu_write_wait(void) +{ + int time_count; + time_count = 0; + while (1) { + if (!(readl(G2_FIFO) & 0x11)) + break; + /* To ensure hardware failure doesn't wedge kernel */ + time_count++; + if (time_count > 0x10000) { + snd_printk + ("WARNING: G2 FIFO appears to be blocked.\n"); + break; + } + } +} + +/* spu_memset - write to memory in SPU address space */ +static void spu_memset(u32 toi, u32 what, int length) +{ + int i; + unsigned long flags; + if (snd_BUG_ON(length % 4)) + return; + for (i = 0; i < length; i++) { + if (!(i % 8)) + spu_write_wait(); + local_irq_save(flags); + writel(what, toi + SPU_MEMORY_BASE); + local_irq_restore(flags); + toi++; + } +} + +/* spu_memload - write to SPU address space */ +static void spu_memload(u32 toi, const void *from, int length) +{ + unsigned long flags; + const u32 *froml = from; + u32 __iomem *to = (u32 __iomem *) (SPU_MEMORY_BASE + toi); + int i; + u32 val; + length = DIV_ROUND_UP(length, 4); + spu_write_wait(); + for (i = 0; i < length; i++) { + if (!(i % 8)) + spu_write_wait(); + val = *froml; + local_irq_save(flags); + writel(val, to); + local_irq_restore(flags); + froml++; + to++; + } +} + +/* spu_disable - set spu registers to stop sound output */ +static void spu_disable(void) +{ + int i; + unsigned long flags; + u32 regval; + spu_write_wait(); + regval = readl(ARM_RESET_REGISTER); + regval |= 1; + spu_write_wait(); + local_irq_save(flags); + writel(regval, ARM_RESET_REGISTER); + local_irq_restore(flags); + for (i = 0; i < 64; i++) { + spu_write_wait(); + regval = readl(SPU_REGISTER_BASE + (i * 0x80)); + regval = (regval & ~0x4000) | 0x8000; + spu_write_wait(); + local_irq_save(flags); + writel(regval, SPU_REGISTER_BASE + (i * 0x80)); + local_irq_restore(flags); + } +} + +/* spu_enable - set spu registers to enable sound output */ +static void spu_enable(void) +{ + unsigned long flags; + u32 regval = readl(ARM_RESET_REGISTER); + regval &= ~1; + spu_write_wait(); + local_irq_save(flags); + writel(regval, ARM_RESET_REGISTER); + local_irq_restore(flags); +} + +/* + * Halt the sound processor, clear the memory, + * load some default ARM7 code, and then restart ARM7 +*/ +static void spu_reset(void) +{ + unsigned long flags; + spu_disable(); + spu_memset(0, 0, 0x200000 / 4); + /* Put ARM7 in endless loop */ + local_irq_save(flags); + __raw_writel(0xea000002, SPU_MEMORY_BASE); + local_irq_restore(flags); + spu_enable(); +} + +/* aica_chn_start - write to spu to start playback */ +static void aica_chn_start(void) +{ + unsigned long flags; + spu_write_wait(); + local_irq_save(flags); + writel(AICA_CMD_KICK | AICA_CMD_START, (u32 *) AICA_CONTROL_POINT); + local_irq_restore(flags); +} + +/* aica_chn_halt - write to spu to halt playback */ +static void aica_chn_halt(void) +{ + unsigned long flags; + spu_write_wait(); + local_irq_save(flags); + writel(AICA_CMD_KICK | AICA_CMD_STOP, (u32 *) AICA_CONTROL_POINT); + local_irq_restore(flags); +} + +/* ALSA code below */ +static const struct snd_pcm_hardware snd_pcm_aica_playback_hw = { + .info = (SNDRV_PCM_INFO_NONINTERLEAVED), + .formats = + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_IMA_ADPCM), + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = AICA_BUFFER_SIZE, + .period_bytes_min = AICA_PERIOD_SIZE, + .period_bytes_max = AICA_PERIOD_SIZE, + .periods_min = AICA_PERIOD_NUMBER, + .periods_max = AICA_PERIOD_NUMBER, +}; + +static int aica_dma_transfer(int channels, int buffer_size, + struct snd_pcm_substream *substream) +{ + int q, err, period_offset; + struct snd_card_aica *dreamcastcard; + struct snd_pcm_runtime *runtime; + unsigned long flags; + err = 0; + dreamcastcard = substream->pcm->private_data; + period_offset = dreamcastcard->clicks; + period_offset %= (AICA_PERIOD_NUMBER / channels); + runtime = substream->runtime; + for (q = 0; q < channels; q++) { + local_irq_save(flags); + err = dma_xfer(AICA_DMA_CHANNEL, + (unsigned long) (runtime->dma_area + + (AICA_BUFFER_SIZE * q) / + channels + + AICA_PERIOD_SIZE * + period_offset), + AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET + + AICA_PERIOD_SIZE * period_offset, + buffer_size / channels, AICA_DMA_MODE); + if (unlikely(err < 0)) { + local_irq_restore(flags); + break; + } + dma_wait_for_completion(AICA_DMA_CHANNEL); + local_irq_restore(flags); + } + return err; +} + +static void startup_aica(struct snd_card_aica *dreamcastcard) +{ + spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, + dreamcastcard->channel, sizeof(struct aica_channel)); + aica_chn_start(); +} + +static void run_spu_dma(struct work_struct *work) +{ + int buffer_size; + struct snd_pcm_runtime *runtime; + struct snd_card_aica *dreamcastcard; + dreamcastcard = + container_of(work, struct snd_card_aica, spu_dma_work); + runtime = dreamcastcard->substream->runtime; + if (unlikely(dreamcastcard->dma_check == 0)) { + buffer_size = + frames_to_bytes(runtime, runtime->buffer_size); + if (runtime->channels > 1) + dreamcastcard->channel->flags |= 0x01; + aica_dma_transfer(runtime->channels, buffer_size, + dreamcastcard->substream); + startup_aica(dreamcastcard); + dreamcastcard->clicks = + buffer_size / (AICA_PERIOD_SIZE * runtime->channels); + return; + } else { + aica_dma_transfer(runtime->channels, + AICA_PERIOD_SIZE * runtime->channels, + dreamcastcard->substream); + snd_pcm_period_elapsed(dreamcastcard->substream); + dreamcastcard->clicks++; + if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER)) + dreamcastcard->clicks %= AICA_PERIOD_NUMBER; + mod_timer(&dreamcastcard->timer, jiffies + 1); + } +} + +static void aica_period_elapsed(struct timer_list *t) +{ + struct snd_card_aica *dreamcastcard = from_timer(dreamcastcard, + t, timer); + struct snd_pcm_substream *substream = dreamcastcard->substream; + /*timer function - so cannot sleep */ + int play_period; + struct snd_pcm_runtime *runtime; + runtime = substream->runtime; + dreamcastcard = substream->pcm->private_data; + /* Have we played out an additional period? */ + play_period = + frames_to_bytes(runtime, + readl + (AICA_CONTROL_CHANNEL_SAMPLE_NUMBER)) / + AICA_PERIOD_SIZE; + if (play_period == dreamcastcard->current_period) { + /* reschedule the timer */ + mod_timer(&(dreamcastcard->timer), jiffies + 1); + return; + } + if (runtime->channels > 1) + dreamcastcard->current_period = play_period; + if (unlikely(dreamcastcard->dma_check == 0)) + dreamcastcard->dma_check = 1; + schedule_work(&(dreamcastcard->spu_dma_work)); +} + +static void spu_begin_dma(struct snd_pcm_substream *substream) +{ + struct snd_card_aica *dreamcastcard; + struct snd_pcm_runtime *runtime; + runtime = substream->runtime; + dreamcastcard = substream->pcm->private_data; + /*get the queue to do the work */ + schedule_work(&(dreamcastcard->spu_dma_work)); + mod_timer(&dreamcastcard->timer, jiffies + 4); +} + +static int snd_aicapcm_pcm_open(struct snd_pcm_substream + *substream) +{ + struct snd_pcm_runtime *runtime; + struct aica_channel *channel; + struct snd_card_aica *dreamcastcard; + if (!enable) + return -ENOENT; + dreamcastcard = substream->pcm->private_data; + channel = kmalloc(sizeof(struct aica_channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + /* set defaults for channel */ + channel->sfmt = SM_8BIT; + channel->cmd = AICA_CMD_START; + channel->vol = dreamcastcard->master_volume; + channel->pan = 0x80; + channel->pos = 0; + channel->flags = 0; /* default to mono */ + dreamcastcard->channel = channel; + runtime = substream->runtime; + runtime->hw = snd_pcm_aica_playback_hw; + spu_enable(); + dreamcastcard->clicks = 0; + dreamcastcard->current_period = 0; + dreamcastcard->dma_check = 0; + return 0; +} + +static int snd_aicapcm_pcm_close(struct snd_pcm_substream + *substream) +{ + struct snd_card_aica *dreamcastcard = substream->pcm->private_data; + flush_work(&(dreamcastcard->spu_dma_work)); + del_timer(&dreamcastcard->timer); + dreamcastcard->substream = NULL; + kfree(dreamcastcard->channel); + spu_disable(); + return 0; +} + +static int snd_aicapcm_pcm_hw_free(struct snd_pcm_substream + *substream) +{ + /* Free the DMA buffer */ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_aicapcm_pcm_hw_params(struct snd_pcm_substream + *substream, struct snd_pcm_hw_params + *hw_params) +{ + /* Allocate a DMA buffer using ALSA built-ins */ + return + snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_aicapcm_pcm_prepare(struct snd_pcm_substream + *substream) +{ + struct snd_card_aica *dreamcastcard = substream->pcm->private_data; + if ((substream->runtime)->format == SNDRV_PCM_FORMAT_S16_LE) + dreamcastcard->channel->sfmt = SM_16BIT; + dreamcastcard->channel->freq = substream->runtime->rate; + dreamcastcard->substream = substream; + return 0; +} + +static int snd_aicapcm_pcm_trigger(struct snd_pcm_substream + *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + spu_begin_dma(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + aica_chn_halt(); + break; + default: + return -EINVAL; + } + return 0; +} + +static unsigned long snd_aicapcm_pcm_pointer(struct snd_pcm_substream + *substream) +{ + return readl(AICA_CONTROL_CHANNEL_SAMPLE_NUMBER); +} + +static const struct snd_pcm_ops snd_aicapcm_playback_ops = { + .open = snd_aicapcm_pcm_open, + .close = snd_aicapcm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_aicapcm_pcm_hw_params, + .hw_free = snd_aicapcm_pcm_hw_free, + .prepare = snd_aicapcm_pcm_prepare, + .trigger = snd_aicapcm_pcm_trigger, + .pointer = snd_aicapcm_pcm_pointer, +}; + +/* TO DO: set up to handle more than one pcm instance */ +static int __init snd_aicapcmchip(struct snd_card_aica + *dreamcastcard, int pcm_index) +{ + struct snd_pcm *pcm; + int err; + /* AICA has no capture ability */ + err = + snd_pcm_new(dreamcastcard->card, "AICA PCM", pcm_index, 1, 0, + &pcm); + if (unlikely(err < 0)) + return err; + pcm->private_data = dreamcastcard; + strcpy(pcm->name, "AICA PCM"); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_aicapcm_playback_ops); + /* Allocate the DMA buffers */ + err = + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), + AICA_BUFFER_SIZE, + AICA_BUFFER_SIZE); + return err; +} + +/* Mixer controls */ +#define aica_pcmswitch_info snd_ctl_boolean_mono_info + +static int aica_pcmswitch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 1; /* TO DO: Fix me */ + return 0; +} + +static int aica_pcmswitch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (ucontrol->value.integer.value[0] == 1) + return 0; /* TO DO: Fix me */ + else + aica_chn_halt(); + return 0; +} + +static int aica_pcmvolume_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 = 0xFF; + return 0; +} + +static int aica_pcmvolume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_aica *dreamcastcard; + dreamcastcard = kcontrol->private_data; + if (unlikely(!dreamcastcard->channel)) + return -ETXTBSY; /* we've not yet been set up */ + ucontrol->value.integer.value[0] = dreamcastcard->channel->vol; + return 0; +} + +static int aica_pcmvolume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_card_aica *dreamcastcard; + unsigned int vol; + dreamcastcard = kcontrol->private_data; + if (unlikely(!dreamcastcard->channel)) + return -ETXTBSY; + vol = ucontrol->value.integer.value[0]; + if (vol > 0xff) + return -EINVAL; + if (unlikely(dreamcastcard->channel->vol == vol)) + return 0; + dreamcastcard->channel->vol = ucontrol->value.integer.value[0]; + dreamcastcard->master_volume = ucontrol->value.integer.value[0]; + spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, + dreamcastcard->channel, sizeof(struct aica_channel)); + return 1; +} + +static const struct snd_kcontrol_new snd_aica_pcmswitch_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .index = 0, + .info = aica_pcmswitch_info, + .get = aica_pcmswitch_get, + .put = aica_pcmswitch_put +}; + +static const struct snd_kcontrol_new snd_aica_pcmvolume_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .info = aica_pcmvolume_info, + .get = aica_pcmvolume_get, + .put = aica_pcmvolume_put +}; + +static int load_aica_firmware(void) +{ + int err; + const struct firmware *fw_entry; + spu_reset(); + err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); + if (unlikely(err)) + return err; + /* write firmware into memory */ + spu_disable(); + spu_memload(0, fw_entry->data, fw_entry->size); + spu_enable(); + release_firmware(fw_entry); + return err; +} + +static int add_aicamixer_controls(struct snd_card_aica *dreamcastcard) +{ + int err; + err = snd_ctl_add + (dreamcastcard->card, + snd_ctl_new1(&snd_aica_pcmvolume_control, dreamcastcard)); + if (unlikely(err < 0)) + return err; + err = snd_ctl_add + (dreamcastcard->card, + snd_ctl_new1(&snd_aica_pcmswitch_control, dreamcastcard)); + if (unlikely(err < 0)) + return err; + return 0; +} + +static int snd_aica_remove(struct platform_device *devptr) +{ + struct snd_card_aica *dreamcastcard; + dreamcastcard = platform_get_drvdata(devptr); + if (unlikely(!dreamcastcard)) + return -ENODEV; + snd_card_free(dreamcastcard->card); + kfree(dreamcastcard); + return 0; +} + +static int snd_aica_probe(struct platform_device *devptr) +{ + int err; + struct snd_card_aica *dreamcastcard; + dreamcastcard = kzalloc(sizeof(struct snd_card_aica), GFP_KERNEL); + if (unlikely(!dreamcastcard)) + return -ENOMEM; + err = snd_card_new(&devptr->dev, index, SND_AICA_DRIVER, + THIS_MODULE, 0, &dreamcastcard->card); + if (unlikely(err < 0)) { + kfree(dreamcastcard); + return err; + } + strcpy(dreamcastcard->card->driver, "snd_aica"); + strcpy(dreamcastcard->card->shortname, SND_AICA_DRIVER); + strcpy(dreamcastcard->card->longname, + "Yamaha AICA Super Intelligent Sound Processor for SEGA Dreamcast"); + /* Prepare to use the queue */ + INIT_WORK(&(dreamcastcard->spu_dma_work), run_spu_dma); + timer_setup(&dreamcastcard->timer, aica_period_elapsed, 0); + /* Load the PCM 'chip' */ + err = snd_aicapcmchip(dreamcastcard, 0); + if (unlikely(err < 0)) + goto freedreamcast; + /* Add basic controls */ + err = add_aicamixer_controls(dreamcastcard); + if (unlikely(err < 0)) + goto freedreamcast; + /* Register the card with ALSA subsystem */ + err = snd_card_register(dreamcastcard->card); + if (unlikely(err < 0)) + goto freedreamcast; + platform_set_drvdata(devptr, dreamcastcard); + snd_printk + ("ALSA Driver for Yamaha AICA Super Intelligent Sound Processor\n"); + return 0; + freedreamcast: + snd_card_free(dreamcastcard->card); + kfree(dreamcastcard); + return err; +} + +static struct platform_driver snd_aica_driver = { + .probe = snd_aica_probe, + .remove = snd_aica_remove, + .driver = { + .name = SND_AICA_DRIVER, + }, +}; + +static int __init aica_init(void) +{ + int err; + err = platform_driver_register(&snd_aica_driver); + if (unlikely(err < 0)) + return err; + pd = platform_device_register_simple(SND_AICA_DRIVER, -1, + aica_memory_space, 2); + if (IS_ERR(pd)) { + platform_driver_unregister(&snd_aica_driver); + return PTR_ERR(pd); + } + /* Load the firmware */ + return load_aica_firmware(); +} + +static void __exit aica_exit(void) +{ + platform_device_unregister(pd); + platform_driver_unregister(&snd_aica_driver); + /* Kill any sound still playing and reset ARM7 to safe state */ + spu_reset(); +} + +module_init(aica_init); +module_exit(aica_exit); diff --git a/sound/sh/aica.h b/sound/sh/aica.h new file mode 100644 index 000000000..d098baaa0 --- /dev/null +++ b/sound/sh/aica.h @@ -0,0 +1,81 @@ +/* aica.h + * Header file for ALSA driver for + * Sega Dreamcast Yamaha AICA sound + * Copyright Adrian McMenamin + * + * 2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 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 + * + */ + +/* SPU memory and register constants etc */ +#define G2_FIFO 0xa05f688c +#define SPU_MEMORY_BASE 0xA0800000 +#define ARM_RESET_REGISTER 0xA0702C00 +#define SPU_REGISTER_BASE 0xA0700000 + +/* AICA channels stuff */ +#define AICA_CONTROL_POINT 0xA0810000 +#define AICA_CONTROL_CHANNEL_SAMPLE_NUMBER 0xA0810008 +#define AICA_CHANNEL0_CONTROL_OFFSET 0x10004 + +/* Command values */ +#define AICA_CMD_KICK 0x80000000 +#define AICA_CMD_NONE 0 +#define AICA_CMD_START 1 +#define AICA_CMD_STOP 2 +#define AICA_CMD_VOL 3 + +/* Sound modes */ +#define SM_8BIT 1 +#define SM_16BIT 0 +#define SM_ADPCM 2 + +/* Buffer and period size */ +#define AICA_BUFFER_SIZE 0x8000 +#define AICA_PERIOD_SIZE 0x800 +#define AICA_PERIOD_NUMBER 16 + +#define AICA_CHANNEL0_OFFSET 0x11000 +#define AICA_CHANNEL1_OFFSET 0x21000 +#define CHANNEL_OFFSET 0x10000 + +#define AICA_DMA_CHANNEL 5 +#define AICA_DMA_MODE 5 + +#define SND_AICA_DRIVER "AICA" + +struct aica_channel { + uint32_t cmd; /* Command ID */ + uint32_t pos; /* Sample position */ + uint32_t length; /* Sample length */ + uint32_t freq; /* Frequency */ + uint32_t vol; /* Volume 0-255 */ + uint32_t pan; /* Pan 0-255 */ + uint32_t sfmt; /* Sound format */ + uint32_t flags; /* Bit flags */ +}; + +struct snd_card_aica { + struct work_struct spu_dma_work; + struct snd_card *card; + struct aica_channel *channel; + struct snd_pcm_substream *substream; + int clicks; + int current_period; + struct timer_list timer; + int master_volume; + int dma_check; +}; diff --git a/sound/sh/sh_dac_audio.c b/sound/sh/sh_dac_audio.c new file mode 100644 index 000000000..6251b5e1b --- /dev/null +++ b/sound/sh/sh_dac_audio.c @@ -0,0 +1,445 @@ +/* + * sh_dac_audio.c - SuperH DAC audio driver for ALSA + * + * Copyright (c) 2009 by Rafael Ignacio Zurita + * + * + * Based on sh_dac_audio.c (Copyright (C) 2004, 2005 by Andriy Skulysh) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Rafael Ignacio Zurita "); +MODULE_DESCRIPTION("SuperH DAC audio driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{SuperH DAC audio support}}"); + +/* Module Parameters */ +static int index = SNDRV_DEFAULT_IDX1; +static char *id = SNDRV_DEFAULT_STR1; +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for SuperH DAC audio."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for SuperH DAC audio."); + +/* main struct */ +struct snd_sh_dac { + struct snd_card *card; + struct snd_pcm_substream *substream; + struct hrtimer hrtimer; + ktime_t wakeups_per_second; + + int rate; + int empty; + char *data_buffer, *buffer_begin, *buffer_end; + int processed; /* bytes proccesed, to compare with period_size */ + int buffer_size; + struct dac_audio_pdata *pdata; +}; + + +static void dac_audio_start_timer(struct snd_sh_dac *chip) +{ + hrtimer_start(&chip->hrtimer, chip->wakeups_per_second, + HRTIMER_MODE_REL); +} + +static void dac_audio_stop_timer(struct snd_sh_dac *chip) +{ + hrtimer_cancel(&chip->hrtimer); +} + +static void dac_audio_reset(struct snd_sh_dac *chip) +{ + dac_audio_stop_timer(chip); + chip->buffer_begin = chip->buffer_end = chip->data_buffer; + chip->processed = 0; + chip->empty = 1; +} + +static void dac_audio_set_rate(struct snd_sh_dac *chip) +{ + chip->wakeups_per_second = 1000000000 / chip->rate; +} + + +/* PCM INTERFACE */ + +static const struct snd_pcm_hardware snd_sh_dac_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_HALF_DUPLEX), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = (48*1024), + .period_bytes_min = 1, + .period_bytes_max = (48*1024), + .periods_min = 1, + .periods_max = 1024, +}; + +static int snd_sh_dac_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw = snd_sh_dac_pcm_hw; + + chip->substream = substream; + chip->buffer_begin = chip->buffer_end = chip->data_buffer; + chip->processed = 0; + chip->empty = 1; + + chip->pdata->start(chip->pdata); + + return 0; +} + +static int snd_sh_dac_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + + chip->substream = NULL; + + dac_audio_stop_timer(chip); + chip->pdata->stop(chip->pdata); + + return 0; +} + +static int snd_sh_dac_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_sh_dac_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_sh_dac_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = chip->substream->runtime; + + chip->buffer_size = runtime->buffer_size; + memset(chip->data_buffer, 0, chip->pdata->buffer_size); + + return 0; +} + +static int snd_sh_dac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dac_audio_start_timer(chip); + break; + case SNDRV_PCM_TRIGGER_STOP: + chip->buffer_begin = chip->buffer_end = chip->data_buffer; + chip->processed = 0; + chip->empty = 1; + dac_audio_stop_timer(chip); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int snd_sh_dac_pcm_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + void __user *src, unsigned long count) +{ + /* channel is not used (interleaved data) */ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + + if (copy_from_user_toio(chip->data_buffer + pos, src, count)) + return -EFAULT; + chip->buffer_end = chip->data_buffer + pos + count; + + if (chip->empty) { + chip->empty = 0; + dac_audio_start_timer(chip); + } + + return 0; +} + +static int snd_sh_dac_pcm_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + void *src, unsigned long count) +{ + /* channel is not used (interleaved data) */ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + + memcpy_toio(chip->data_buffer + pos, src, count); + chip->buffer_end = chip->data_buffer + pos + count; + + if (chip->empty) { + chip->empty = 0; + dac_audio_start_timer(chip); + } + + return 0; +} + +static int snd_sh_dac_pcm_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long count) +{ + /* channel is not used (interleaved data) */ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + + memset_io(chip->data_buffer + pos, 0, count); + chip->buffer_end = chip->data_buffer + pos + count; + + if (chip->empty) { + chip->empty = 0; + dac_audio_start_timer(chip); + } + + return 0; +} + +static +snd_pcm_uframes_t snd_sh_dac_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_sh_dac *chip = snd_pcm_substream_chip(substream); + int pointer = chip->buffer_begin - chip->data_buffer; + + return pointer; +} + +/* pcm ops */ +static const struct snd_pcm_ops snd_sh_dac_pcm_ops = { + .open = snd_sh_dac_pcm_open, + .close = snd_sh_dac_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sh_dac_pcm_hw_params, + .hw_free = snd_sh_dac_pcm_hw_free, + .prepare = snd_sh_dac_pcm_prepare, + .trigger = snd_sh_dac_pcm_trigger, + .pointer = snd_sh_dac_pcm_pointer, + .copy_user = snd_sh_dac_pcm_copy, + .copy_kernel = snd_sh_dac_pcm_copy_kernel, + .fill_silence = snd_sh_dac_pcm_silence, + .mmap = snd_pcm_lib_mmap_iomem, +}; + +static int snd_sh_dac_pcm(struct snd_sh_dac *chip, int device) +{ + int err; + struct snd_pcm *pcm; + + /* device should be always 0 for us */ + err = snd_pcm_new(chip->card, "SH_DAC PCM", device, 1, 0, &pcm); + if (err < 0) + return err; + + pcm->private_data = chip; + strcpy(pcm->name, "SH_DAC PCM"); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sh_dac_pcm_ops); + + /* buffer size=48K */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 48 * 1024, + 48 * 1024); + + return 0; +} +/* END OF PCM INTERFACE */ + + +/* driver .remove -- destructor */ +static int snd_sh_dac_remove(struct platform_device *devptr) +{ + snd_card_free(platform_get_drvdata(devptr)); + return 0; +} + +/* free -- it has been defined by create */ +static int snd_sh_dac_free(struct snd_sh_dac *chip) +{ + /* release the data */ + kfree(chip->data_buffer); + kfree(chip); + + return 0; +} + +static int snd_sh_dac_dev_free(struct snd_device *device) +{ + struct snd_sh_dac *chip = device->device_data; + + return snd_sh_dac_free(chip); +} + +static enum hrtimer_restart sh_dac_audio_timer(struct hrtimer *handle) +{ + struct snd_sh_dac *chip = container_of(handle, struct snd_sh_dac, + hrtimer); + struct snd_pcm_runtime *runtime = chip->substream->runtime; + ssize_t b_ps = frames_to_bytes(runtime, runtime->period_size); + + if (!chip->empty) { + sh_dac_output(*chip->buffer_begin, chip->pdata->channel); + chip->buffer_begin++; + + chip->processed++; + if (chip->processed >= b_ps) { + chip->processed -= b_ps; + snd_pcm_period_elapsed(chip->substream); + } + + if (chip->buffer_begin == (chip->data_buffer + + chip->buffer_size - 1)) + chip->buffer_begin = chip->data_buffer; + + if (chip->buffer_begin == chip->buffer_end) + chip->empty = 1; + + } + + if (!chip->empty) + hrtimer_start(&chip->hrtimer, chip->wakeups_per_second, + HRTIMER_MODE_REL); + + return HRTIMER_NORESTART; +} + +/* create -- chip-specific constructor for the cards components */ +static int snd_sh_dac_create(struct snd_card *card, + struct platform_device *devptr, + struct snd_sh_dac **rchip) +{ + struct snd_sh_dac *chip; + int err; + + static struct snd_device_ops ops = { + .dev_free = snd_sh_dac_dev_free, + }; + + *rchip = NULL; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->card = card; + + hrtimer_init(&chip->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + chip->hrtimer.function = sh_dac_audio_timer; + + dac_audio_reset(chip); + chip->rate = 8000; + dac_audio_set_rate(chip); + + chip->pdata = devptr->dev.platform_data; + + chip->data_buffer = kmalloc(chip->pdata->buffer_size, GFP_KERNEL); + if (chip->data_buffer == NULL) { + kfree(chip); + return -ENOMEM; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err < 0) { + snd_sh_dac_free(chip); + return err; + } + + *rchip = chip; + + return 0; +} + +/* driver .probe -- constructor */ +static int snd_sh_dac_probe(struct platform_device *devptr) +{ + struct snd_sh_dac *chip; + struct snd_card *card; + int err; + + err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) { + snd_printk(KERN_ERR "cannot allocate the card\n"); + return err; + } + + err = snd_sh_dac_create(card, devptr, &chip); + if (err < 0) + goto probe_error; + + err = snd_sh_dac_pcm(chip, 0); + if (err < 0) + goto probe_error; + + strcpy(card->driver, "snd_sh_dac"); + strcpy(card->shortname, "SuperH DAC audio driver"); + printk(KERN_INFO "%s %s", card->longname, card->shortname); + + err = snd_card_register(card); + if (err < 0) + goto probe_error; + + snd_printk(KERN_INFO "ALSA driver for SuperH DAC audio"); + + platform_set_drvdata(devptr, card); + return 0; + +probe_error: + snd_card_free(card); + return err; +} + +/* + * "driver" definition + */ +static struct platform_driver sh_dac_driver = { + .probe = snd_sh_dac_probe, + .remove = snd_sh_dac_remove, + .driver = { + .name = "dac_audio", + }, +}; + +module_platform_driver(sh_dac_driver); diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig new file mode 100644 index 000000000..1cf11cf51 --- /dev/null +++ b/sound/soc/Kconfig @@ -0,0 +1,86 @@ +# +# SoC audio configuration +# + +menuconfig SND_SOC + tristate "ALSA for SoC audio support" + select SND_PCM + select AC97_BUS if SND_SOC_AC97_BUS + select SND_JACK + select REGMAP_I2C if I2C + select REGMAP_SPI if SPI_MASTER + ---help--- + + If you want ASoC support, you should say Y here and also to the + specific driver for your SoC platform below. + + ASoC provides power efficient ALSA support for embedded battery powered + SoC based systems like PDA's, Phones and Personal Media Players. + + This ASoC audio support can also be built as a module. If so, the module + will be called snd-soc-core. + +if SND_SOC + +config SND_SOC_AC97_BUS + bool + +config SND_SOC_GENERIC_DMAENGINE_PCM + bool + select SND_DMAENGINE_PCM + +config SND_SOC_COMPRESS + bool + select SND_COMPRESS_OFFLOAD + +config SND_SOC_TOPOLOGY + bool + +config SND_SOC_ACPI + tristate + +# All the supported SoCs +source "sound/soc/adi/Kconfig" +source "sound/soc/amd/Kconfig" +source "sound/soc/atmel/Kconfig" +source "sound/soc/au1x/Kconfig" +source "sound/soc/bcm/Kconfig" +source "sound/soc/cirrus/Kconfig" +source "sound/soc/davinci/Kconfig" +source "sound/soc/dwc/Kconfig" +source "sound/soc/fsl/Kconfig" +source "sound/soc/hisilicon/Kconfig" +source "sound/soc/jz4740/Kconfig" +source "sound/soc/nuc900/Kconfig" +source "sound/soc/omap/Kconfig" +source "sound/soc/kirkwood/Kconfig" +source "sound/soc/img/Kconfig" +source "sound/soc/intel/Kconfig" +source "sound/soc/mediatek/Kconfig" +source "sound/soc/meson/Kconfig" +source "sound/soc/mxs/Kconfig" +source "sound/soc/pxa/Kconfig" +source "sound/soc/qcom/Kconfig" +source "sound/soc/rockchip/Kconfig" +source "sound/soc/samsung/Kconfig" +source "sound/soc/sh/Kconfig" +source "sound/soc/sirf/Kconfig" +source "sound/soc/spear/Kconfig" +source "sound/soc/sti/Kconfig" +source "sound/soc/stm/Kconfig" +source "sound/soc/sunxi/Kconfig" +source "sound/soc/tegra/Kconfig" +source "sound/soc/txx9/Kconfig" +source "sound/soc/uniphier/Kconfig" +source "sound/soc/ux500/Kconfig" +source "sound/soc/xtensa/Kconfig" +source "sound/soc/zte/Kconfig" + +# Supported codecs +source "sound/soc/codecs/Kconfig" + +# generic frame-work +source "sound/soc/generic/Kconfig" + +endif # SND_SOC + diff --git a/sound/soc/Makefile b/sound/soc/Makefile new file mode 100644 index 000000000..62a5f87c3 --- /dev/null +++ b/sound/soc/Makefile @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-utils.o +snd-soc-core-objs += soc-pcm.o soc-io.o soc-devres.o soc-ops.o +snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o + +ifneq ($(CONFIG_SND_SOC_TOPOLOGY),) +snd-soc-core-objs += soc-topology.o +endif + +ifneq ($(CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM),) +snd-soc-core-objs += soc-generic-dmaengine-pcm.o +endif + +ifneq ($(CONFIG_SND_SOC_AC97_BUS),) +snd-soc-core-objs += soc-ac97.o +endif + +ifneq ($(CONFIG_SND_SOC_ACPI),) +snd-soc-acpi-objs := soc-acpi.o +endif + +obj-$(CONFIG_SND_SOC_ACPI) += snd-soc-acpi.o + +obj-$(CONFIG_SND_SOC) += snd-soc-core.o +obj-$(CONFIG_SND_SOC) += codecs/ +obj-$(CONFIG_SND_SOC) += generic/ +obj-$(CONFIG_SND_SOC) += adi/ +obj-$(CONFIG_SND_SOC) += amd/ +obj-$(CONFIG_SND_SOC) += atmel/ +obj-$(CONFIG_SND_SOC) += au1x/ +obj-$(CONFIG_SND_SOC) += bcm/ +obj-$(CONFIG_SND_SOC) += cirrus/ +obj-$(CONFIG_SND_SOC) += davinci/ +obj-$(CONFIG_SND_SOC) += dwc/ +obj-$(CONFIG_SND_SOC) += fsl/ +obj-$(CONFIG_SND_SOC) += hisilicon/ +obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += img/ +obj-$(CONFIG_SND_SOC) += intel/ +obj-$(CONFIG_SND_SOC) += mediatek/ +obj-$(CONFIG_SND_SOC) += meson/ +obj-$(CONFIG_SND_SOC) += mxs/ +obj-$(CONFIG_SND_SOC) += nuc900/ +obj-$(CONFIG_SND_SOC) += omap/ +obj-$(CONFIG_SND_SOC) += kirkwood/ +obj-$(CONFIG_SND_SOC) += pxa/ +obj-$(CONFIG_SND_SOC) += qcom/ +obj-$(CONFIG_SND_SOC) += rockchip/ +obj-$(CONFIG_SND_SOC) += samsung/ +obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += sirf/ +obj-$(CONFIG_SND_SOC) += spear/ +obj-$(CONFIG_SND_SOC) += sti/ +obj-$(CONFIG_SND_SOC) += stm/ +obj-$(CONFIG_SND_SOC) += sunxi/ +obj-$(CONFIG_SND_SOC) += tegra/ +obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += uniphier/ +obj-$(CONFIG_SND_SOC) += ux500/ +obj-$(CONFIG_SND_SOC) += xtensa/ +obj-$(CONFIG_SND_SOC) += zte/ diff --git a/sound/soc/adi/Kconfig b/sound/soc/adi/Kconfig new file mode 100644 index 000000000..dd763f55e --- /dev/null +++ b/sound/soc/adi/Kconfig @@ -0,0 +1,21 @@ +config SND_SOC_ADI + tristate "Audio support for Analog Devices reference designs" + depends on MICROBLAZE || ARCH_ZYNQ || COMPILE_TEST + help + Audio support for various reference designs by Analog Devices. + +config SND_SOC_ADI_AXI_I2S + tristate "AXI-I2S support" + depends on SND_SOC_ADI + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + ASoC driver for the Analog Devices AXI-I2S softcore peripheral. + +config SND_SOC_ADI_AXI_SPDIF + tristate "AXI-SPDIF support" + depends on SND_SOC_ADI + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + ASoC driver for the Analog Devices AXI-SPDIF softcore peripheral. diff --git a/sound/soc/adi/Makefile b/sound/soc/adi/Makefile new file mode 100644 index 000000000..64456c1e5 --- /dev/null +++ b/sound/soc/adi/Makefile @@ -0,0 +1,5 @@ +snd-soc-adi-axi-i2s-objs := axi-i2s.o +snd-soc-adi-axi-spdif-objs := axi-spdif.o + +obj-$(CONFIG_SND_SOC_ADI_AXI_I2S) += snd-soc-adi-axi-i2s.o +obj-$(CONFIG_SND_SOC_ADI_AXI_SPDIF) += snd-soc-adi-axi-spdif.o diff --git a/sound/soc/adi/axi-i2s.c b/sound/soc/adi/axi-i2s.c new file mode 100644 index 000000000..4c2338172 --- /dev/null +++ b/sound/soc/adi/axi-i2s.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2012-2013, Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define AXI_I2S_REG_RESET 0x00 +#define AXI_I2S_REG_CTRL 0x04 +#define AXI_I2S_REG_CLK_CTRL 0x08 +#define AXI_I2S_REG_STATUS 0x10 + +#define AXI_I2S_REG_RX_FIFO 0x28 +#define AXI_I2S_REG_TX_FIFO 0x2C + +#define AXI_I2S_RESET_GLOBAL BIT(0) +#define AXI_I2S_RESET_TX_FIFO BIT(1) +#define AXI_I2S_RESET_RX_FIFO BIT(2) + +#define AXI_I2S_CTRL_TX_EN BIT(0) +#define AXI_I2S_CTRL_RX_EN BIT(1) + +/* The frame size is configurable, but for now we always set it 64 bit */ +#define AXI_I2S_BITS_PER_FRAME 64 + +struct axi_i2s { + struct regmap *regmap; + struct clk *clk; + struct clk *clk_ref; + + struct snd_soc_dai_driver dai_driver; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + struct snd_ratnum ratnum; + struct snd_pcm_hw_constraint_ratnums rate_constraints; +}; + +static int axi_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mask = AXI_I2S_CTRL_RX_EN; + else + mask = AXI_I2S_CTRL_TX_EN; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = mask; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = 0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, AXI_I2S_REG_CTRL, mask, val); + + return 0; +} + +static int axi_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int bclk_div, word_size; + unsigned int bclk_rate; + + bclk_rate = params_rate(params) * AXI_I2S_BITS_PER_FRAME; + + word_size = AXI_I2S_BITS_PER_FRAME / 2 - 1; + bclk_div = DIV_ROUND_UP(clk_get_rate(i2s->clk_ref), bclk_rate) / 2 - 1; + + regmap_write(i2s->regmap, AXI_I2S_REG_CLK_CTRL, (word_size << 16) | + bclk_div); + + return 0; +} + +static int axi_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); + uint32_t mask; + int ret; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mask = AXI_I2S_RESET_RX_FIFO; + else + mask = AXI_I2S_RESET_TX_FIFO; + + regmap_write(i2s->regmap, AXI_I2S_REG_RESET, mask); + + ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &i2s->rate_constraints); + if (ret) + return ret; + + return clk_prepare_enable(i2s->clk_ref); +} + +static void axi_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(i2s->clk_ref); +} + +static int axi_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, + &i2s->capture_dma_data); + + return 0; +} + +static const struct snd_soc_dai_ops axi_i2s_dai_ops = { + .startup = axi_i2s_startup, + .shutdown = axi_i2s_shutdown, + .trigger = axi_i2s_trigger, + .hw_params = axi_i2s_hw_params, +}; + +static struct snd_soc_dai_driver axi_i2s_dai = { + .probe = axi_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, + }, + .ops = &axi_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver axi_i2s_component = { + .name = "axi-i2s", +}; + +static const struct regmap_config axi_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AXI_I2S_REG_STATUS, +}; + +static int axi_i2s_probe(struct platform_device *pdev) +{ + struct resource *res; + struct axi_i2s *i2s; + void __iomem *base; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + platform_set_drvdata(pdev, i2s); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &axi_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) + return PTR_ERR(i2s->regmap); + + i2s->clk = devm_clk_get(&pdev->dev, "axi"); + if (IS_ERR(i2s->clk)) + return PTR_ERR(i2s->clk); + + i2s->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(i2s->clk_ref)) + return PTR_ERR(i2s->clk_ref); + + ret = clk_prepare_enable(i2s->clk); + if (ret) + return ret; + + i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO; + i2s->playback_dma_data.addr_width = 4; + i2s->playback_dma_data.maxburst = 1; + + i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO; + i2s->capture_dma_data.addr_width = 4; + i2s->capture_dma_data.maxburst = 1; + + i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME; + i2s->ratnum.den_step = 1; + i2s->ratnum.den_min = 1; + i2s->ratnum.den_max = 64; + + i2s->rate_constraints.rats = &i2s->ratnum; + i2s->rate_constraints.nrats = 1; + + regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL); + + ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component, + &axi_i2s_dai, 1); + if (ret) + goto err_clk_disable; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_clk_disable; + + return 0; + +err_clk_disable: + clk_disable_unprepare(i2s->clk); + return ret; +} + +static int axi_i2s_dev_remove(struct platform_device *pdev) +{ + struct axi_i2s *i2s = platform_get_drvdata(pdev); + + clk_disable_unprepare(i2s->clk); + + return 0; +} + +static const struct of_device_id axi_i2s_of_match[] = { + { .compatible = "adi,axi-i2s-1.00.a", }, + {}, +}; +MODULE_DEVICE_TABLE(of, axi_i2s_of_match); + +static struct platform_driver axi_i2s_driver = { + .driver = { + .name = "axi-i2s", + .of_match_table = axi_i2s_of_match, + }, + .probe = axi_i2s_probe, + .remove = axi_i2s_dev_remove, +}; +module_platform_driver(axi_i2s_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("AXI I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/adi/axi-spdif.c b/sound/soc/adi/axi-spdif.c new file mode 100644 index 000000000..d7259d412 --- /dev/null +++ b/sound/soc/adi/axi-spdif.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2012-2013, Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define AXI_SPDIF_REG_CTRL 0x0 +#define AXI_SPDIF_REG_STAT 0x4 +#define AXI_SPDIF_REG_TX_FIFO 0xc + +#define AXI_SPDIF_CTRL_TXDATA BIT(1) +#define AXI_SPDIF_CTRL_TXEN BIT(0) +#define AXI_SPDIF_CTRL_CLKDIV_OFFSET 8 +#define AXI_SPDIF_CTRL_CLKDIV_MASK (0xff << 8) + +#define AXI_SPDIF_FREQ_44100 (0x0 << 6) +#define AXI_SPDIF_FREQ_48000 (0x1 << 6) +#define AXI_SPDIF_FREQ_32000 (0x2 << 6) +#define AXI_SPDIF_FREQ_NA (0x3 << 6) + +struct axi_spdif { + struct regmap *regmap; + struct clk *clk; + struct clk *clk_ref; + + struct snd_dmaengine_dai_dma_data dma_data; + + struct snd_ratnum ratnum; + struct snd_pcm_hw_constraint_ratnums rate_constraints; +}; + +static int axi_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int val; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = AXI_SPDIF_CTRL_TXDATA; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = 0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL, + AXI_SPDIF_CTRL_TXDATA, val); + + return 0; +} + +static int axi_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + unsigned int clkdiv, stat; + + switch (params_rate(params)) { + case 32000: + stat = AXI_SPDIF_FREQ_32000; + break; + case 44100: + stat = AXI_SPDIF_FREQ_44100; + break; + case 48000: + stat = AXI_SPDIF_FREQ_48000; + break; + default: + stat = AXI_SPDIF_FREQ_NA; + break; + } + + clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(spdif->clk_ref), + rate * 64 * 2) - 1; + clkdiv <<= AXI_SPDIF_CTRL_CLKDIV_OFFSET; + + regmap_write(spdif->regmap, AXI_SPDIF_REG_STAT, stat); + regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL, + AXI_SPDIF_CTRL_CLKDIV_MASK, clkdiv); + + return 0; +} + +static int axi_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL); + + return 0; +} + +static int axi_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &spdif->rate_constraints); + if (ret) + return ret; + + ret = clk_prepare_enable(spdif->clk_ref); + if (ret) + return ret; + + regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL, + AXI_SPDIF_CTRL_TXEN, AXI_SPDIF_CTRL_TXEN); + + return 0; +} + +static void axi_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai); + + regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL, + AXI_SPDIF_CTRL_TXEN, 0); + + clk_disable_unprepare(spdif->clk_ref); +} + +static const struct snd_soc_dai_ops axi_spdif_dai_ops = { + .startup = axi_spdif_startup, + .shutdown = axi_spdif_shutdown, + .trigger = axi_spdif_trigger, + .hw_params = axi_spdif_hw_params, +}; + +static struct snd_soc_dai_driver axi_spdif_dai = { + .probe = axi_spdif_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &axi_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver axi_spdif_component = { + .name = "axi-spdif", +}; + +static const struct regmap_config axi_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AXI_SPDIF_REG_STAT, +}; + +static int axi_spdif_probe(struct platform_device *pdev) +{ + struct axi_spdif *spdif; + struct resource *res; + void __iomem *base; + int ret; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &axi_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) + return PTR_ERR(spdif->regmap); + + spdif->clk = devm_clk_get(&pdev->dev, "axi"); + if (IS_ERR(spdif->clk)) + return PTR_ERR(spdif->clk); + + spdif->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(spdif->clk_ref)) + return PTR_ERR(spdif->clk_ref); + + ret = clk_prepare_enable(spdif->clk); + if (ret) + return ret; + + spdif->dma_data.addr = res->start + AXI_SPDIF_REG_TX_FIFO; + spdif->dma_data.addr_width = 4; + spdif->dma_data.maxburst = 1; + + spdif->ratnum.num = clk_get_rate(spdif->clk_ref) / 128; + spdif->ratnum.den_step = 1; + spdif->ratnum.den_min = 1; + spdif->ratnum.den_max = 64; + + spdif->rate_constraints.rats = &spdif->ratnum; + spdif->rate_constraints.nrats = 1; + + ret = devm_snd_soc_register_component(&pdev->dev, &axi_spdif_component, + &axi_spdif_dai, 1); + if (ret) + goto err_clk_disable; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_clk_disable; + + return 0; + +err_clk_disable: + clk_disable_unprepare(spdif->clk); + return ret; +} + +static int axi_spdif_dev_remove(struct platform_device *pdev) +{ + struct axi_spdif *spdif = platform_get_drvdata(pdev); + + clk_disable_unprepare(spdif->clk); + + return 0; +} + +static const struct of_device_id axi_spdif_of_match[] = { + { .compatible = "adi,axi-spdif-tx-1.00.a", }, + {}, +}; +MODULE_DEVICE_TABLE(of, axi_spdif_of_match); + +static struct platform_driver axi_spdif_driver = { + .driver = { + .name = "axi-spdif", + .of_match_table = axi_spdif_of_match, + }, + .probe = axi_spdif_probe, + .remove = axi_spdif_dev_remove, +}; +module_platform_driver(axi_spdif_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("AXI SPDIF driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig new file mode 100644 index 000000000..58c1dcb4d --- /dev/null +++ b/sound/soc/amd/Kconfig @@ -0,0 +1,21 @@ +config SND_SOC_AMD_ACP + tristate "AMD Audio Coprocessor support" + help + This option enables ACP DMA support on AMD platform. + +config SND_SOC_AMD_CZ_DA7219MX98357_MACH + tristate "AMD CZ support for DA7219 and MAX9835" + select SND_SOC_DA7219 + select SND_SOC_MAX98357A + select SND_SOC_ADAU7002 + select REGULATOR + depends on SND_SOC_AMD_ACP && I2C + help + This option enables machine driver for DA7219 and MAX9835. + +config SND_SOC_AMD_CZ_RT5645_MACH + tristate "AMD CZ support for RT5645" + select SND_SOC_RT5645 + depends on SND_SOC_AMD_ACP && I2C + help + This option enables machine driver for rt5645. diff --git a/sound/soc/amd/Makefile b/sound/soc/amd/Makefile new file mode 100644 index 000000000..79b0622fa --- /dev/null +++ b/sound/soc/amd/Makefile @@ -0,0 +1,7 @@ +acp_audio_dma-objs := acp-pcm-dma.o +snd-soc-acp-da7219mx98357-mach-objs := acp-da7219-max98357a.o +snd-soc-acp-rt5645-mach-objs := acp-rt5645.o + +obj-$(CONFIG_SND_SOC_AMD_ACP) += acp_audio_dma.o +obj-$(CONFIG_SND_SOC_AMD_CZ_DA7219MX98357_MACH) += snd-soc-acp-da7219mx98357-mach.o +obj-$(CONFIG_SND_SOC_AMD_CZ_RT5645_MACH) += snd-soc-acp-rt5645-mach.o diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c new file mode 100644 index 000000000..e53b54d77 --- /dev/null +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -0,0 +1,411 @@ +/* + * Machine driver for AMD ACP Audio engine using DA7219 & MAX98357 codec + * + * Copyright 2017 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acp.h" +#include "../codecs/da7219.h" +#include "../codecs/da7219-aad.h" + +#define CZ_PLAT_CLK 48000000 +#define DUAL_CHANNEL 2 + +static struct snd_soc_jack cz_jack; +static struct clk *da7219_dai_clk; +extern int bt_uart_enable; + +static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = codec_dai->component; + + dev_info(rtd->dev, "codec dai name = %s\n", codec_dai->name); + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, + CZ_PLAT_CLK, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL, + CZ_PLAT_CLK, DA7219_PLL_FREQ_OUT_98304); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + da7219_dai_clk = clk_get(component->dev, "da7219-dai-clks"); + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &cz_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(cz_jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + da7219_aad_jack_det(component, &cz_jack); + + return 0; +} + +static int da7219_clk_enable(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + ret = clk_prepare_enable(da7219_dai_clk); + if (ret < 0) { + dev_err(rtd->dev, "can't enable master clock %d\n", ret); + return ret; + } + + return ret; +} + +static void da7219_clk_disable(void) +{ + clk_disable_unprepare(da7219_dai_clk); +} + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int cz_da7219_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + /* + * On this platform for PCM device we support stereo + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraints_rates); + + machine->i2s_instance = I2S_SP_INSTANCE; + machine->capture_channel = CAP_CHANNEL1; + return da7219_clk_enable(substream); +} + +static void cz_da7219_shutdown(struct snd_pcm_substream *substream) +{ + da7219_clk_disable(); +} + +static int cz_max_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->i2s_instance = I2S_BT_INSTANCE; + return da7219_clk_enable(substream); +} + +static void cz_max_shutdown(struct snd_pcm_substream *substream) +{ + da7219_clk_disable(); +} + +static int cz_dmic0_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->i2s_instance = I2S_BT_INSTANCE; + return da7219_clk_enable(substream); +} + +static int cz_dmic1_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct acp_platform_info *machine = snd_soc_card_get_drvdata(card); + + machine->i2s_instance = I2S_SP_INSTANCE; + machine->capture_channel = CAP_CHANNEL0; + return da7219_clk_enable(substream); +} + +static void cz_dmic_shutdown(struct snd_pcm_substream *substream) +{ + da7219_clk_disable(); +} + +static const struct snd_soc_ops cz_da7219_cap_ops = { + .startup = cz_da7219_startup, + .shutdown = cz_da7219_shutdown, +}; + +static const struct snd_soc_ops cz_max_play_ops = { + .startup = cz_max_startup, + .shutdown = cz_max_shutdown, +}; + +static const struct snd_soc_ops cz_dmic0_cap_ops = { + .startup = cz_dmic0_startup, + .shutdown = cz_dmic_shutdown, +}; + +static const struct snd_soc_ops cz_dmic1_cap_ops = { + .startup = cz_dmic1_startup, + .shutdown = cz_dmic_shutdown, +}; + +static struct snd_soc_dai_link cz_dai_7219_98357[] = { + { + .name = "amd-da7219-play", + .stream_name = "Playback", + .platform_name = "acp_audio_dma.0.auto", + .cpu_dai_name = "designware-i2s.1.auto", + .codec_dai_name = "da7219-hifi", + .codec_name = "i2c-DLGS7219:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .init = cz_da7219_init, + .dpcm_playback = 1, + .ops = &cz_da7219_cap_ops, + }, + { + .name = "amd-da7219-cap", + .stream_name = "Capture", + .platform_name = "acp_audio_dma.0.auto", + .cpu_dai_name = "designware-i2s.2.auto", + .codec_dai_name = "da7219-hifi", + .codec_name = "i2c-DLGS7219:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .dpcm_capture = 1, + .ops = &cz_da7219_cap_ops, + }, + { + .name = "amd-max98357-play", + .stream_name = "HiFi Playback", + .platform_name = "acp_audio_dma.0.auto", + .cpu_dai_name = "designware-i2s.3.auto", + .codec_dai_name = "HiFi", + .codec_name = "MX98357A:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .dpcm_playback = 1, + .ops = &cz_max_play_ops, + }, + { + /* C panel DMIC */ + .name = "dmic0", + .stream_name = "DMIC0 Capture", + .platform_name = "acp_audio_dma.0.auto", + .cpu_dai_name = "designware-i2s.3.auto", + .codec_dai_name = "adau7002-hifi", + .codec_name = "ADAU7002:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .dpcm_capture = 1, + .ops = &cz_dmic0_cap_ops, + }, + { + /* A/B panel DMIC */ + .name = "dmic1", + .stream_name = "DMIC1 Capture", + .platform_name = "acp_audio_dma.0.auto", + .cpu_dai_name = "designware-i2s.2.auto", + .codec_dai_name = "adau7002-hifi", + .codec_name = "ADAU7002:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .dpcm_capture = 1, + .ops = &cz_dmic1_cap_ops, + }, +}; + +static const struct snd_soc_dapm_widget cz_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static const struct snd_soc_dapm_route cz_audio_route[] = { + {"Headphones", NULL, "HPL"}, + {"Headphones", NULL, "HPR"}, + {"MIC", NULL, "Headset Mic"}, + {"Speakers", NULL, "Speaker"}, + {"PDM_DAT", NULL, "Int Mic"}, +}; + +static const struct snd_kcontrol_new cz_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static struct snd_soc_card cz_card = { + .name = "acpd7219m98357", + .owner = THIS_MODULE, + .dai_link = cz_dai_7219_98357, + .num_links = ARRAY_SIZE(cz_dai_7219_98357), + .dapm_widgets = cz_widgets, + .num_dapm_widgets = ARRAY_SIZE(cz_widgets), + .dapm_routes = cz_audio_route, + .num_dapm_routes = ARRAY_SIZE(cz_audio_route), + .controls = cz_mc_controls, + .num_controls = ARRAY_SIZE(cz_mc_controls), +}; + +static struct regulator_consumer_supply acp_da7219_supplies[] = { + REGULATOR_SUPPLY("VDD", "i2c-DLGS7219:00"), + REGULATOR_SUPPLY("VDDMIC", "i2c-DLGS7219:00"), + REGULATOR_SUPPLY("VDDIO", "i2c-DLGS7219:00"), + REGULATOR_SUPPLY("IOVDD", "ADAU7002:00"), +}; + +static struct regulator_init_data acp_da7219_data = { + .constraints = { + .always_on = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(acp_da7219_supplies), + .consumer_supplies = acp_da7219_supplies, +}; + +static struct regulator_config acp_da7219_cfg = { + .init_data = &acp_da7219_data, +}; + +static struct regulator_ops acp_da7219_ops = { +}; + +static struct regulator_desc acp_da7219_desc = { + .name = "reg-fixed-1.8V", + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .ops = &acp_da7219_ops, + .fixed_uV = 1800000, /* 1.8V */ + .n_voltages = 1, +}; + +static int cz_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card; + struct acp_platform_info *machine; + struct regulator_dev *rdev; + + acp_da7219_cfg.dev = &pdev->dev; + rdev = devm_regulator_register(&pdev->dev, &acp_da7219_desc, + &acp_da7219_cfg); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "Failed to register regulator: %d\n", + (int)PTR_ERR(rdev)); + return -EINVAL; + } + + machine = devm_kzalloc(&pdev->dev, sizeof(struct acp_platform_info), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + card = &cz_card; + cz_card.dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + ret = devm_snd_soc_register_card(&pdev->dev, &cz_card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + cz_card.name, ret); + return ret; + } + bt_uart_enable = !device_property_read_bool(&pdev->dev, + "bt-pad-enable"); + return 0; +} + +static const struct acpi_device_id cz_audio_acpi_match[] = { + { "AMD7219", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, cz_audio_acpi_match); + +static struct platform_driver cz_pcm_driver = { + .driver = { + .name = "cz-da7219-max98357a", + .acpi_match_table = ACPI_PTR(cz_audio_acpi_match), + .pm = &snd_soc_pm_ops, + }, + .probe = cz_probe, +}; + +module_platform_driver(cz_pcm_driver); + +MODULE_AUTHOR("akshu.agrawal@amd.com"); +MODULE_DESCRIPTION("DA7219 & MAX98357A audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c new file mode 100644 index 000000000..7f376b63a --- /dev/null +++ b/sound/soc/amd/acp-pcm-dma.c @@ -0,0 +1,1434 @@ +/* + * AMD ALSA SoC PCM Driver for ACP 2.x + * + * Copyright 2014-2015 Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "acp.h" + +#define DRV_NAME "acp_audio_dma" + +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 2 +#define PLAYBACK_MAX_PERIOD_SIZE 16384 +#define PLAYBACK_MIN_PERIOD_SIZE 1024 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 2 +#define CAPTURE_MAX_PERIOD_SIZE 16384 +#define CAPTURE_MIN_PERIOD_SIZE 1024 + +#define MAX_BUFFER (PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS) +#define MIN_BUFFER MAX_BUFFER + +#define ST_PLAYBACK_MAX_PERIOD_SIZE 4096 +#define ST_CAPTURE_MAX_PERIOD_SIZE ST_PLAYBACK_MAX_PERIOD_SIZE +#define ST_MAX_BUFFER (ST_PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS) +#define ST_MIN_BUFFER ST_MAX_BUFFER + +#define DRV_NAME "acp_audio_dma" +bool bt_uart_enable = true; +EXPORT_SYMBOL(bt_uart_enable); + +static const struct snd_pcm_hardware acp_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 96000, + .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_st_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 96000, + .buffer_bytes_max = ST_MAX_BUFFER, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = ST_PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp_st_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .buffer_bytes_max = ST_MAX_BUFFER, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = ST_CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, +}; + +static u32 acp_reg_read(void __iomem *acp_mmio, u32 reg) +{ + return readl(acp_mmio + (reg * 4)); +} + +static void acp_reg_write(u32 val, void __iomem *acp_mmio, u32 reg) +{ + writel(val, acp_mmio + (reg * 4)); +} + +/* + * Configure a given dma channel parameters - enable/disable, + * number of descriptors, priority + */ +static void config_acp_dma_channel(void __iomem *acp_mmio, u8 ch_num, + u16 dscr_strt_idx, u16 num_dscrs, + enum acp_dma_priority_level priority_level) +{ + u32 dma_ctrl; + + /* disable the channel run field */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + + /* program a DMA channel with first descriptor to be processed. */ + acp_reg_write((ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx_MASK + & dscr_strt_idx), + acp_mmio, mmACP_DMA_DSCR_STRT_IDX_0 + ch_num); + + /* + * program a DMA channel with the number of descriptors to be + * processed in the transfer + */ + acp_reg_write(ACP_DMA_DSCR_CNT_0__DMAChDscrCnt_MASK & num_dscrs, + acp_mmio, mmACP_DMA_DSCR_CNT_0 + ch_num); + + /* set DMA channel priority */ + acp_reg_write(priority_level, acp_mmio, mmACP_DMA_PRIO_0 + ch_num); +} + +/* Initialize a dma descriptor in SRAM based on descritor information passed */ +static void config_dma_descriptor_in_sram(void __iomem *acp_mmio, + u16 descr_idx, + acp_dma_dscr_transfer_t *descr_info) +{ + u32 sram_offset; + + sram_offset = (descr_idx * sizeof(acp_dma_dscr_transfer_t)); + + /* program the source base address. */ + acp_reg_write(sram_offset, acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + acp_reg_write(descr_info->src, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + /* program the destination base address. */ + acp_reg_write(sram_offset + 4, acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + acp_reg_write(descr_info->dest, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + + /* program the number of bytes to be transferred for this descriptor. */ + acp_reg_write(sram_offset + 8, acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + acp_reg_write(descr_info->xfer_val, acp_mmio, mmACP_SRBM_Targ_Idx_Data); +} + +static void pre_config_reset(void __iomem *acp_mmio, u16 ch_num) +{ + u32 dma_ctrl; + int ret; + + /* clear the reset bit */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + /* check the reset bit before programming configuration registers */ + ret = readl_poll_timeout(acp_mmio + ((mmACP_DMA_CNTL_0 + ch_num) * 4), + dma_ctrl, + !(dma_ctrl & ACP_DMA_CNTL_0__DMAChRst_MASK), + 100, ACP_DMA_RESET_TIME); + if (ret < 0) + pr_err("Failed to clear reset of channel : %d\n", ch_num); +} + +/* + * Initialize the DMA descriptor information for transfer between + * system memory <-> ACP SRAM + */ +static void set_acp_sysmem_dma_descriptors(void __iomem *acp_mmio, + u32 size, int direction, + u32 pte_offset, u16 ch, + u32 sram_bank, u16 dma_dscr_idx, + u32 asic_type) +{ + u16 i; + acp_dma_dscr_transfer_t dmadscr[NUM_DSCRS_PER_CHANNEL]; + + for (i = 0; i < NUM_DSCRS_PER_CHANNEL; i++) { + dmadscr[i].xfer_val = 0; + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + dma_dscr_idx = dma_dscr_idx + i; + dmadscr[i].dest = sram_bank + (i * (size / 2)); + dmadscr[i].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + + (pte_offset * SZ_4K) + (i * (size / 2)); + switch (asic_type) { + case CHIP_STONEY: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_DAGB_GARLIC_TO_SHAREDMEM << 16) | + (size / 2); + break; + default: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_DAGB_ONION_TO_SHAREDMEM << 16) | + (size / 2); + } + } else { + dma_dscr_idx = dma_dscr_idx + i; + dmadscr[i].src = sram_bank + (i * (size / 2)); + dmadscr[i].dest = + ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + + (pte_offset * SZ_4K) + (i * (size / 2)); + switch (asic_type) { + case CHIP_STONEY: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_SHARED_MEM_TO_DAGB_GARLIC << 16) | + (size / 2); + break; + default: + dmadscr[i].xfer_val |= + (ACP_DMA_ATTR_SHAREDMEM_TO_DAGB_ONION << 16) | + (size / 2); + } + } + config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, + &dmadscr[i]); + } + pre_config_reset(acp_mmio, ch); + config_acp_dma_channel(acp_mmio, ch, + dma_dscr_idx - 1, + NUM_DSCRS_PER_CHANNEL, + ACP_DMA_PRIORITY_LEVEL_NORMAL); +} + +/* + * Initialize the DMA descriptor information for transfer between + * ACP SRAM <-> I2S + */ +static void set_acp_to_i2s_dma_descriptors(void __iomem *acp_mmio, u32 size, + int direction, u32 sram_bank, + u16 destination, u16 ch, + u16 dma_dscr_idx, u32 asic_type) +{ + u16 i; + acp_dma_dscr_transfer_t dmadscr[NUM_DSCRS_PER_CHANNEL]; + + for (i = 0; i < NUM_DSCRS_PER_CHANNEL; i++) { + dmadscr[i].xfer_val = 0; + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + dma_dscr_idx = dma_dscr_idx + i; + dmadscr[i].src = sram_bank + (i * (size / 2)); + /* dmadscr[i].dest is unused by hardware. */ + dmadscr[i].dest = 0; + dmadscr[i].xfer_val |= BIT(22) | (destination << 16) | + (size / 2); + } else { + dma_dscr_idx = dma_dscr_idx + i; + /* dmadscr[i].src is unused by hardware. */ + dmadscr[i].src = 0; + dmadscr[i].dest = + sram_bank + (i * (size / 2)); + dmadscr[i].xfer_val |= BIT(22) | + (destination << 16) | (size / 2); + } + config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx, + &dmadscr[i]); + } + pre_config_reset(acp_mmio, ch); + /* Configure the DMA channel with the above descriptore */ + config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1, + NUM_DSCRS_PER_CHANNEL, + ACP_DMA_PRIORITY_LEVEL_NORMAL); +} + +/* Create page table entries in ACP SRAM for the allocated memory */ +static void acp_pte_config(void __iomem *acp_mmio, struct page *pg, + u16 num_of_pages, u32 pte_offset) +{ + u16 page_idx; + u64 addr; + u32 low; + u32 high; + u32 offset; + + offset = ACP_DAGB_GRP_SRBM_SRAM_BASE_OFFSET + (pte_offset * 8); + for (page_idx = 0; page_idx < (num_of_pages); page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + acp_reg_write((offset + (page_idx * 8)), + acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + addr = page_to_phys(pg); + + low = lower_32_bits(addr); + high = upper_32_bits(addr); + + acp_reg_write(low, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + + /* Load the High address of page int ACP SRAM through SRBM */ + acp_reg_write((offset + (page_idx * 8) + 4), + acp_mmio, mmACP_SRBM_Targ_Idx_Addr); + + /* page enable in ACP */ + high |= BIT(31); + acp_reg_write(high, acp_mmio, mmACP_SRBM_Targ_Idx_Data); + + /* Move to next physically contiguos page */ + pg++; + } +} + +static void config_acp_dma(void __iomem *acp_mmio, + struct audio_substream_data *rtd, + u32 asic_type) +{ + u16 ch_acp_sysmem, ch_acp_i2s; + + acp_pte_config(acp_mmio, rtd->pg, rtd->num_of_pages, + rtd->pte_offset); + + if (rtd->direction == SNDRV_PCM_STREAM_PLAYBACK) { + ch_acp_sysmem = rtd->ch1; + ch_acp_i2s = rtd->ch2; + } else { + ch_acp_i2s = rtd->ch1; + ch_acp_sysmem = rtd->ch2; + } + /* Configure System memory <-> ACP SRAM DMA descriptors */ + set_acp_sysmem_dma_descriptors(acp_mmio, rtd->size, + rtd->direction, rtd->pte_offset, + ch_acp_sysmem, rtd->sram_bank, + rtd->dma_dscr_idx_1, asic_type); + /* Configure ACP SRAM <-> I2S DMA descriptors */ + set_acp_to_i2s_dma_descriptors(acp_mmio, rtd->size, + rtd->direction, rtd->sram_bank, + rtd->destination, ch_acp_i2s, + rtd->dma_dscr_idx_2, asic_type); +} + +static void acp_dma_cap_channel_enable(void __iomem *acp_mmio, + u16 cap_channel) +{ + u32 val, ch_reg, imr_reg, res_reg; + + switch (cap_channel) { + case CAP_CHANNEL1: + ch_reg = mmACP_I2SMICSP_RER1; + res_reg = mmACP_I2SMICSP_RCR1; + imr_reg = mmACP_I2SMICSP_IMR1; + break; + case CAP_CHANNEL0: + default: + ch_reg = mmACP_I2SMICSP_RER0; + res_reg = mmACP_I2SMICSP_RCR0; + imr_reg = mmACP_I2SMICSP_IMR0; + break; + } + val = acp_reg_read(acp_mmio, + mmACP_I2S_16BIT_RESOLUTION_EN); + if (val & ACP_I2S_MIC_16BIT_RESOLUTION_EN) { + acp_reg_write(0x0, acp_mmio, ch_reg); + /* Set 16bit resolution on capture */ + acp_reg_write(0x2, acp_mmio, res_reg); + } + val = acp_reg_read(acp_mmio, imr_reg); + val &= ~ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM_MASK; + val &= ~ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM_MASK; + acp_reg_write(val, acp_mmio, imr_reg); + acp_reg_write(0x1, acp_mmio, ch_reg); +} + +static void acp_dma_cap_channel_disable(void __iomem *acp_mmio, + u16 cap_channel) +{ + u32 val, ch_reg, imr_reg; + + switch (cap_channel) { + case CAP_CHANNEL1: + imr_reg = mmACP_I2SMICSP_IMR1; + ch_reg = mmACP_I2SMICSP_RER1; + break; + case CAP_CHANNEL0: + default: + imr_reg = mmACP_I2SMICSP_IMR0; + ch_reg = mmACP_I2SMICSP_RER0; + break; + } + val = acp_reg_read(acp_mmio, imr_reg); + val |= ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM_MASK; + val |= ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM_MASK; + acp_reg_write(val, acp_mmio, imr_reg); + acp_reg_write(0x0, acp_mmio, ch_reg); +} + +/* Start a given DMA channel transfer */ +static void acp_dma_start(void __iomem *acp_mmio, u16 ch_num, bool is_circular) +{ + u32 dma_ctrl; + + /* read the dma control register and disable the channel run field */ + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + + /* Invalidating the DAGB cache */ + acp_reg_write(1, acp_mmio, mmACP_DAGB_ATU_CTRL); + + /* + * configure the DMA channel and start the DMA transfer + * set dmachrun bit to start the transfer and enable the + * interrupt on completion of the dma transfer + */ + dma_ctrl |= ACP_DMA_CNTL_0__DMAChRun_MASK; + + switch (ch_num) { + case ACP_TO_I2S_DMA_CH_NUM: + case I2S_TO_ACP_DMA_CH_NUM: + case ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM: + case I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM: + dma_ctrl |= ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + break; + default: + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + break; + } + + /* enable for ACP to SRAM DMA channel */ + if (is_circular == true) + dma_ctrl |= ACP_DMA_CNTL_0__Circular_DMA_En_MASK; + else + dma_ctrl &= ~ACP_DMA_CNTL_0__Circular_DMA_En_MASK; + + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); +} + +/* Stop a given DMA channel transfer */ +static int acp_dma_stop(void __iomem *acp_mmio, u8 ch_num) +{ + u32 dma_ctrl; + u32 dma_ch_sts; + u32 count = ACP_DMA_RESET_TIME; + + dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + + /* + * clear the dma control register fields before writing zero + * in reset bit + */ + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS); + + if (dma_ch_sts & BIT(ch_num)) { + /* + * set the reset bit for this channel to stop the dma + * transfer + */ + dma_ctrl |= ACP_DMA_CNTL_0__DMAChRst_MASK; + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num); + } + + /* check the channel status bit for some time and return the status */ + while (true) { + dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS); + if (!(dma_ch_sts & BIT(ch_num))) { + /* + * clear the reset flag after successfully stopping + * the dma transfer and break from the loop + */ + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK; + + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + + ch_num); + break; + } + if (--count == 0) { + pr_err("Failed to stop ACP DMA channel : %d\n", ch_num); + return -ETIMEDOUT; + } + udelay(100); + } + return 0; +} + +static void acp_set_sram_bank_state(void __iomem *acp_mmio, u16 bank, + bool power_on) +{ + u32 val, req_reg, sts_reg, sts_reg_mask; + u32 loops = 1000; + + if (bank < 32) { + req_reg = mmACP_MEM_SHUT_DOWN_REQ_LO; + sts_reg = mmACP_MEM_SHUT_DOWN_STS_LO; + sts_reg_mask = 0xFFFFFFFF; + + } else { + bank -= 32; + req_reg = mmACP_MEM_SHUT_DOWN_REQ_HI; + sts_reg = mmACP_MEM_SHUT_DOWN_STS_HI; + sts_reg_mask = 0x0000FFFF; + } + + val = acp_reg_read(acp_mmio, req_reg); + if (val & (1 << bank)) { + /* bank is in off state */ + if (power_on == true) + /* request to on */ + val &= ~(1 << bank); + else + /* request to off */ + return; + } else { + /* bank is in on state */ + if (power_on == false) + /* request to off */ + val |= 1 << bank; + else + /* request to on */ + return; + } + acp_reg_write(val, acp_mmio, req_reg); + + while (acp_reg_read(acp_mmio, sts_reg) != sts_reg_mask) { + if (!loops--) { + pr_err("ACP SRAM bank %d state change failed\n", bank); + break; + } + cpu_relax(); + } +} + +/* Initialize and bring ACP hardware to default state. */ +static int acp_init(void __iomem *acp_mmio, u32 asic_type) +{ + u16 bank; + u32 val, count, sram_pte_offset; + + /* Assert Soft reset of ACP */ + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + + val |= ACP_SOFT_RESET__SoftResetAud_MASK; + acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); + + count = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (true) { + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + if (ACP_SOFT_RESET__SoftResetAudDone_MASK == + (val & ACP_SOFT_RESET__SoftResetAudDone_MASK)) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + + /* Enable clock to ACP and wait until the clock is enabled */ + val = acp_reg_read(acp_mmio, mmACP_CONTROL); + val = val | ACP_CONTROL__ClkEn_MASK; + acp_reg_write(val, acp_mmio, mmACP_CONTROL); + + count = ACP_CLOCK_EN_TIME_OUT_VALUE; + + while (true) { + val = acp_reg_read(acp_mmio, mmACP_STATUS); + if (val & (u32)0x1) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + + /* Deassert the SOFT RESET flags */ + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + val &= ~ACP_SOFT_RESET__SoftResetAud_MASK; + acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); + + /* For BT instance change pins from UART to BT */ + if (!bt_uart_enable) { + val = acp_reg_read(acp_mmio, mmACP_BT_UART_PAD_SEL); + val |= ACP_BT_UART_PAD_SELECT_MASK; + acp_reg_write(val, acp_mmio, mmACP_BT_UART_PAD_SEL); + } + + /* initiailize Onion control DAGB register */ + acp_reg_write(ACP_ONION_CNTL_DEFAULT, acp_mmio, + mmACP_AXI2DAGB_ONION_CNTL); + + /* initiailize Garlic control DAGB registers */ + acp_reg_write(ACP_GARLIC_CNTL_DEFAULT, acp_mmio, + mmACP_AXI2DAGB_GARLIC_CNTL); + + sram_pte_offset = ACP_DAGB_GRP_SRAM_BASE_ADDRESS | + ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBSnoopSel_MASK | + ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBTargetMemSel_MASK | + ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBGrpEnable_MASK; + acp_reg_write(sram_pte_offset, acp_mmio, mmACP_DAGB_BASE_ADDR_GRP_1); + acp_reg_write(ACP_PAGE_SIZE_4K_ENABLE, acp_mmio, + mmACP_DAGB_PAGE_SIZE_GRP_1); + + acp_reg_write(ACP_SRAM_BASE_ADDRESS, acp_mmio, + mmACP_DMA_DESC_BASE_ADDR); + + /* Num of descriptiors in SRAM 0x4, means 256 descriptors;(64 * 4) */ + acp_reg_write(0x4, acp_mmio, mmACP_DMA_DESC_MAX_NUM_DSCR); + acp_reg_write(ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK, + acp_mmio, mmACP_EXTERNAL_INTR_CNTL); + + /* + * When ACP_TILE_P1 is turned on, all SRAM banks get turned on. + * Now, turn off all of them. This can't be done in 'poweron' of + * ACP pm domain, as this requires ACP to be initialized. + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks is ON. + * Setting SRAM bank state code skipped for STONEY platform. + */ + if (asic_type != CHIP_STONEY) { + for (bank = 1; bank < 48; bank++) + acp_set_sram_bank_state(acp_mmio, bank, false); + } + return 0; +} + +/* Deinitialize ACP */ +static int acp_deinit(void __iomem *acp_mmio) +{ + u32 val; + u32 count; + + /* Assert Soft reset of ACP */ + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + + val |= ACP_SOFT_RESET__SoftResetAud_MASK; + acp_reg_write(val, acp_mmio, mmACP_SOFT_RESET); + + count = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (true) { + val = acp_reg_read(acp_mmio, mmACP_SOFT_RESET); + if (ACP_SOFT_RESET__SoftResetAudDone_MASK == + (val & ACP_SOFT_RESET__SoftResetAudDone_MASK)) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + /* Disable ACP clock */ + val = acp_reg_read(acp_mmio, mmACP_CONTROL); + val &= ~ACP_CONTROL__ClkEn_MASK; + acp_reg_write(val, acp_mmio, mmACP_CONTROL); + + count = ACP_CLOCK_EN_TIME_OUT_VALUE; + + while (true) { + val = acp_reg_read(acp_mmio, mmACP_STATUS); + if (!(val & (u32)0x1)) + break; + if (--count == 0) { + pr_err("Failed to reset ACP\n"); + return -ETIMEDOUT; + } + udelay(100); + } + return 0; +} + +/* ACP DMA irq handler routine for playback, capture usecases */ +static irqreturn_t dma_irq_handler(int irq, void *arg) +{ + u16 dscr_idx; + u32 intr_flag, ext_intr_status; + struct audio_drv_data *irq_data; + void __iomem *acp_mmio; + struct device *dev = arg; + bool valid_irq = false; + + irq_data = dev_get_drvdata(dev); + acp_mmio = irq_data->acp_mmio; + + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); + intr_flag = (((ext_intr_status & + ACP_EXTERNAL_INTR_STAT__DMAIOCStat_MASK) >> + ACP_EXTERNAL_INTR_STAT__DMAIOCStat__SHIFT)); + + if ((intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)) != 0) { + valid_irq = true; + snd_pcm_period_elapsed(irq_data->play_i2ssp_stream); + acp_reg_write((intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if ((intr_flag & BIT(ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM)) != 0) { + valid_irq = true; + snd_pcm_period_elapsed(irq_data->play_i2sbt_stream); + acp_reg_write((intr_flag & + BIT(ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if ((intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)) != 0) { + valid_irq = true; + if (acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_14) == + CAPTURE_START_DMA_DESCR_CH15) + dscr_idx = CAPTURE_END_DMA_DESCR_CH14; + else + dscr_idx = CAPTURE_START_DMA_DESCR_CH14; + config_acp_dma_channel(acp_mmio, ACP_TO_SYSRAM_CH_NUM, dscr_idx, + 1, 0); + acp_dma_start(acp_mmio, ACP_TO_SYSRAM_CH_NUM, false); + + snd_pcm_period_elapsed(irq_data->capture_i2ssp_stream); + acp_reg_write((intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if ((intr_flag & BIT(I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM)) != 0) { + valid_irq = true; + if (acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_10) == + CAPTURE_START_DMA_DESCR_CH11) + dscr_idx = CAPTURE_END_DMA_DESCR_CH10; + else + dscr_idx = CAPTURE_START_DMA_DESCR_CH10; + config_acp_dma_channel(acp_mmio, + ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM, + dscr_idx, 1, 0); + acp_dma_start(acp_mmio, ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM, + false); + + snd_pcm_period_elapsed(irq_data->capture_i2sbt_stream); + acp_reg_write((intr_flag & + BIT(I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM)) << 16, + acp_mmio, mmACP_EXTERNAL_INTR_STAT); + } + + if (valid_irq) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static int acp_dma_open(struct snd_pcm_substream *substream) +{ + u16 bank; + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd, + DRV_NAME); + struct audio_drv_data *intr_data = dev_get_drvdata(component->dev); + struct audio_substream_data *adata = + kzalloc(sizeof(struct audio_substream_data), GFP_KERNEL); + if (!adata) + return -ENOMEM; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (intr_data->asic_type) { + case CHIP_STONEY: + runtime->hw = acp_st_pcm_hardware_playback; + break; + default: + runtime->hw = acp_pcm_hardware_playback; + } + } else { + switch (intr_data->asic_type) { + case CHIP_STONEY: + runtime->hw = acp_st_pcm_hardware_capture; + break; + default: + runtime->hw = acp_pcm_hardware_capture; + } + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(component->dev, "set integer constraint failed\n"); + kfree(adata); + return ret; + } + + adata->acp_mmio = intr_data->acp_mmio; + runtime->private_data = adata; + + /* + * Enable ACP irq, when neither playback or capture streams are + * active by the time when a new stream is being opened. + * This enablement is not required for another stream, if current + * stream is not closed + */ + if (!intr_data->play_i2ssp_stream && !intr_data->capture_i2ssp_stream && + !intr_data->play_i2sbt_stream && !intr_data->capture_i2sbt_stream) + acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks is ON. + * Setting SRAM bank state code skipped for STONEY platform. + */ + if (intr_data->asic_type != CHIP_STONEY) { + for (bank = 1; bank <= 4; bank++) + acp_set_sram_bank_state(intr_data->acp_mmio, + bank, true); + } + } else { + if (intr_data->asic_type != CHIP_STONEY) { + for (bank = 5; bank <= 8; bank++) + acp_set_sram_bank_state(intr_data->acp_mmio, + bank, true); + } + } + + return 0; +} + +static int acp_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int status; + uint64_t size; + u32 val = 0; + struct page *pg; + struct snd_pcm_runtime *runtime; + struct audio_substream_data *rtd; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd, + DRV_NAME); + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + struct snd_soc_card *card = prtd->card; + struct acp_platform_info *pinfo = snd_soc_card_get_drvdata(card); + + runtime = substream->runtime; + rtd = runtime->private_data; + + if (WARN_ON(!rtd)) + return -EINVAL; + + if (pinfo) { + rtd->i2s_instance = pinfo->i2s_instance; + rtd->capture_channel = pinfo->capture_channel; + } + if (adata->asic_type == CHIP_STONEY) { + val = acp_reg_read(adata->acp_mmio, + mmACP_I2S_16BIT_RESOLUTION_EN); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + val |= ACP_I2S_BT_16BIT_RESOLUTION_EN; + break; + case I2S_SP_INSTANCE: + default: + val |= ACP_I2S_SP_16BIT_RESOLUTION_EN; + } + } else { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + val |= ACP_I2S_BT_16BIT_RESOLUTION_EN; + break; + case I2S_SP_INSTANCE: + default: + val |= ACP_I2S_MIC_16BIT_RESOLUTION_EN; + } + } + acp_reg_write(val, adata->acp_mmio, + mmACP_I2S_16BIT_RESOLUTION_EN); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + rtd->pte_offset = ACP_ST_BT_PLAYBACK_PTE_OFFSET; + rtd->ch1 = SYSRAM_TO_ACP_BT_INSTANCE_CH_NUM; + rtd->ch2 = ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM; + rtd->sram_bank = ACP_SRAM_BANK_3_ADDRESS; + rtd->destination = TO_BLUETOOTH; + rtd->dma_dscr_idx_1 = PLAYBACK_START_DMA_DESCR_CH8; + rtd->dma_dscr_idx_2 = PLAYBACK_START_DMA_DESCR_CH9; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_BT_TRANSMIT_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_BT_TRANSMIT_BYTE_CNT_LOW; + adata->play_i2sbt_stream = substream; + break; + case I2S_SP_INSTANCE: + default: + switch (adata->asic_type) { + case CHIP_STONEY: + rtd->pte_offset = ACP_ST_PLAYBACK_PTE_OFFSET; + break; + default: + rtd->pte_offset = ACP_PLAYBACK_PTE_OFFSET; + } + rtd->ch1 = SYSRAM_TO_ACP_CH_NUM; + rtd->ch2 = ACP_TO_I2S_DMA_CH_NUM; + rtd->sram_bank = ACP_SRAM_BANK_1_ADDRESS; + rtd->destination = TO_ACP_I2S_1; + rtd->dma_dscr_idx_1 = PLAYBACK_START_DMA_DESCR_CH12; + rtd->dma_dscr_idx_2 = PLAYBACK_START_DMA_DESCR_CH13; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_TRANSMIT_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_TRANSMIT_BYTE_CNT_LOW; + adata->play_i2ssp_stream = substream; + } + } else { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + rtd->pte_offset = ACP_ST_BT_CAPTURE_PTE_OFFSET; + rtd->ch1 = I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM; + rtd->ch2 = ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM; + rtd->sram_bank = ACP_SRAM_BANK_4_ADDRESS; + rtd->destination = FROM_BLUETOOTH; + rtd->dma_dscr_idx_1 = CAPTURE_START_DMA_DESCR_CH10; + rtd->dma_dscr_idx_2 = CAPTURE_START_DMA_DESCR_CH11; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_BT_RECEIVE_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_BT_RECEIVE_BYTE_CNT_LOW; + rtd->dma_curr_dscr = mmACP_DMA_CUR_DSCR_11; + adata->capture_i2sbt_stream = substream; + break; + case I2S_SP_INSTANCE: + default: + rtd->pte_offset = ACP_CAPTURE_PTE_OFFSET; + rtd->ch1 = I2S_TO_ACP_DMA_CH_NUM; + rtd->ch2 = ACP_TO_SYSRAM_CH_NUM; + switch (adata->asic_type) { + case CHIP_STONEY: + rtd->pte_offset = ACP_ST_CAPTURE_PTE_OFFSET; + rtd->sram_bank = ACP_SRAM_BANK_2_ADDRESS; + break; + default: + rtd->pte_offset = ACP_CAPTURE_PTE_OFFSET; + rtd->sram_bank = ACP_SRAM_BANK_5_ADDRESS; + } + rtd->destination = FROM_ACP_I2S_1; + rtd->dma_dscr_idx_1 = CAPTURE_START_DMA_DESCR_CH14; + rtd->dma_dscr_idx_2 = CAPTURE_START_DMA_DESCR_CH15; + rtd->byte_cnt_high_reg_offset = + mmACP_I2S_RECEIVED_BYTE_CNT_HIGH; + rtd->byte_cnt_low_reg_offset = + mmACP_I2S_RECEIVED_BYTE_CNT_LOW; + rtd->dma_curr_dscr = mmACP_DMA_CUR_DSCR_15; + adata->capture_i2ssp_stream = substream; + } + } + + size = params_buffer_bytes(params); + status = snd_pcm_lib_malloc_pages(substream, size); + if (status < 0) + return status; + + memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); + pg = virt_to_page(substream->dma_buffer.area); + + if (pg) { + acp_set_sram_bank_state(rtd->acp_mmio, 0, true); + /* Save for runtime private data */ + rtd->pg = pg; + rtd->order = get_order(size); + + /* Fill the page table entries in ACP SRAM */ + rtd->pg = pg; + rtd->size = size; + rtd->num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + rtd->direction = substream->stream; + + config_acp_dma(rtd->acp_mmio, rtd, adata->asic_type); + status = 0; + } else { + status = -ENOMEM; + } + return status; +} + +static int acp_dma_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static u64 acp_get_byte_count(struct audio_substream_data *rtd) +{ + union acp_dma_count byte_count; + + byte_count.bcount.high = acp_reg_read(rtd->acp_mmio, + rtd->byte_cnt_high_reg_offset); + byte_count.bcount.low = acp_reg_read(rtd->acp_mmio, + rtd->byte_cnt_low_reg_offset); + return byte_count.bytescount; +} + +static snd_pcm_uframes_t acp_dma_pointer(struct snd_pcm_substream *substream) +{ + u32 buffersize; + u32 pos = 0; + u64 bytescount = 0; + u16 dscr; + u32 period_bytes, delay; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + + if (!rtd) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + period_bytes = frames_to_bytes(runtime, runtime->period_size); + bytescount = acp_get_byte_count(rtd); + if (bytescount >= rtd->bytescount) + bytescount -= rtd->bytescount; + if (bytescount < period_bytes) { + pos = 0; + } else { + dscr = acp_reg_read(rtd->acp_mmio, rtd->dma_curr_dscr); + if (dscr == rtd->dma_dscr_idx_1) + pos = period_bytes; + else + pos = 0; + } + if (bytescount > 0) { + delay = do_div(bytescount, period_bytes); + runtime->delay = bytes_to_frames(runtime, delay); + } + } else { + buffersize = frames_to_bytes(runtime, runtime->buffer_size); + bytescount = acp_get_byte_count(rtd); + if (bytescount > rtd->bytescount) + bytescount -= rtd->bytescount; + pos = do_div(bytescount, buffersize); + } + return bytes_to_frames(runtime, pos); +} + +static int acp_dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return snd_pcm_lib_default_mmap(substream, vma); +} + +static int acp_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + u16 ch_acp_sysmem, ch_acp_i2s; + + if (!rtd) + return -EINVAL; + + if (rtd->direction == SNDRV_PCM_STREAM_PLAYBACK) { + ch_acp_sysmem = rtd->ch1; + ch_acp_i2s = rtd->ch2; + } else { + ch_acp_i2s = rtd->ch1; + ch_acp_sysmem = rtd->ch2; + } + config_acp_dma_channel(rtd->acp_mmio, + ch_acp_sysmem, + rtd->dma_dscr_idx_1, + NUM_DSCRS_PER_CHANNEL, 0); + config_acp_dma_channel(rtd->acp_mmio, + ch_acp_i2s, + rtd->dma_dscr_idx_2, + NUM_DSCRS_PER_CHANNEL, 0); + return 0; +} + +static int acp_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + + if (!rtd) + return -EINVAL; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + rtd->bytescount = acp_get_byte_count(rtd); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (rtd->capture_channel == CAP_CHANNEL0) { + acp_dma_cap_channel_disable(rtd->acp_mmio, + CAP_CHANNEL1); + acp_dma_cap_channel_enable(rtd->acp_mmio, + CAP_CHANNEL0); + } + if (rtd->capture_channel == CAP_CHANNEL1) { + acp_dma_cap_channel_disable(rtd->acp_mmio, + CAP_CHANNEL0); + acp_dma_cap_channel_enable(rtd->acp_mmio, + CAP_CHANNEL1); + } + acp_dma_start(rtd->acp_mmio, rtd->ch1, true); + } else { + acp_dma_start(rtd->acp_mmio, rtd->ch1, true); + acp_dma_start(rtd->acp_mmio, rtd->ch2, true); + } + ret = 0; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + acp_dma_stop(rtd->acp_mmio, rtd->ch2); + ret = acp_dma_stop(rtd->acp_mmio, rtd->ch1); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static int acp_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, + DRV_NAME); + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + struct device *parent = component->dev->parent; + + switch (adata->asic_type) { + case CHIP_STONEY: + ret = snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + parent, + ST_MIN_BUFFER, + ST_MAX_BUFFER); + break; + default: + ret = snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + parent, + MIN_BUFFER, + MAX_BUFFER); + break; + } + if (ret < 0) + dev_err(component->dev, + "buffer preallocation failure error:%d\n", ret); + return ret; +} + +static int acp_dma_close(struct snd_pcm_substream *substream) +{ + u16 bank; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd, + DRV_NAME); + struct audio_drv_data *adata = dev_get_drvdata(component->dev); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + adata->play_i2sbt_stream = NULL; + break; + case I2S_SP_INSTANCE: + default: + adata->play_i2ssp_stream = NULL; + /* + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks + * is ON.Setting SRAM bank state code skipped for STONEY + * platform. Added condition checks for Carrizo platform + * only. + */ + if (adata->asic_type != CHIP_STONEY) { + for (bank = 1; bank <= 4; bank++) + acp_set_sram_bank_state(adata->acp_mmio, + bank, false); + } + } + } else { + switch (rtd->i2s_instance) { + case I2S_BT_INSTANCE: + adata->capture_i2sbt_stream = NULL; + break; + case I2S_SP_INSTANCE: + default: + adata->capture_i2ssp_stream = NULL; + if (adata->asic_type != CHIP_STONEY) { + for (bank = 5; bank <= 8; bank++) + acp_set_sram_bank_state(adata->acp_mmio, + bank, false); + } + } + } + + /* + * Disable ACP irq, when the current stream is being closed and + * another stream is also not active. + */ + if (!adata->play_i2ssp_stream && !adata->capture_i2ssp_stream && + !adata->play_i2sbt_stream && !adata->capture_i2sbt_stream) + acp_reg_write(0, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + kfree(rtd); + return 0; +} + +static const struct snd_pcm_ops acp_dma_ops = { + .open = acp_dma_open, + .close = acp_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = acp_dma_hw_params, + .hw_free = acp_dma_hw_free, + .trigger = acp_dma_trigger, + .pointer = acp_dma_pointer, + .mmap = acp_dma_mmap, + .prepare = acp_dma_prepare, +}; + +static const struct snd_soc_component_driver acp_asoc_platform = { + .name = DRV_NAME, + .ops = &acp_dma_ops, + .pcm_new = acp_dma_new, +}; + +static int acp_audio_probe(struct platform_device *pdev) +{ + int status; + struct audio_drv_data *audio_drv_data; + struct resource *res; + const u32 *pdata = pdev->dev.platform_data; + + if (!pdata) { + dev_err(&pdev->dev, "Missing platform data\n"); + return -ENODEV; + } + + audio_drv_data = devm_kzalloc(&pdev->dev, sizeof(struct audio_drv_data), + GFP_KERNEL); + if (!audio_drv_data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + audio_drv_data->acp_mmio = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(audio_drv_data->acp_mmio)) + return PTR_ERR(audio_drv_data->acp_mmio); + + /* + * The following members gets populated in device 'open' + * function. Till then interrupts are disabled in 'acp_init' + * and device doesn't generate any interrupts. + */ + + audio_drv_data->play_i2ssp_stream = NULL; + audio_drv_data->capture_i2ssp_stream = NULL; + audio_drv_data->play_i2sbt_stream = NULL; + audio_drv_data->capture_i2sbt_stream = NULL; + + audio_drv_data->asic_type = *pdata; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n"); + return -ENODEV; + } + + status = devm_request_irq(&pdev->dev, res->start, dma_irq_handler, + 0, "ACP_IRQ", &pdev->dev); + if (status) { + dev_err(&pdev->dev, "ACP IRQ request failed\n"); + return status; + } + + dev_set_drvdata(&pdev->dev, audio_drv_data); + + /* Initialize the ACP */ + status = acp_init(audio_drv_data->acp_mmio, audio_drv_data->asic_type); + if (status) { + dev_err(&pdev->dev, "ACP Init failed status:%d\n", status); + return status; + } + + status = devm_snd_soc_register_component(&pdev->dev, + &acp_asoc_platform, NULL, 0); + if (status != 0) { + dev_err(&pdev->dev, "Fail to register ALSA platform device\n"); + return status; + } + + pm_runtime_set_autosuspend_delay(&pdev->dev, 10000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return status; +} + +static int acp_audio_remove(struct platform_device *pdev) +{ + int status; + struct audio_drv_data *adata = dev_get_drvdata(&pdev->dev); + + status = acp_deinit(adata->acp_mmio); + if (status) + dev_err(&pdev->dev, "ACP Deinit failed status:%d\n", status); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int acp_pcm_resume(struct device *dev) +{ + u16 bank; + int status; + struct audio_substream_data *rtd; + struct audio_drv_data *adata = dev_get_drvdata(dev); + + status = acp_init(adata->acp_mmio, adata->asic_type); + if (status) { + dev_err(dev, "ACP Init failed status:%d\n", status); + return status; + } + + if (adata->play_i2ssp_stream && adata->play_i2ssp_stream->runtime) { + /* + * For Stoney, Memory gating is disabled,i.e SRAM Banks + * won't be turned off. The default state for SRAM banks is ON. + * Setting SRAM bank state code skipped for STONEY platform. + */ + if (adata->asic_type != CHIP_STONEY) { + for (bank = 1; bank <= 4; bank++) + acp_set_sram_bank_state(adata->acp_mmio, bank, + true); + } + rtd = adata->play_i2ssp_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + if (adata->capture_i2ssp_stream && + adata->capture_i2ssp_stream->runtime) { + if (adata->asic_type != CHIP_STONEY) { + for (bank = 5; bank <= 8; bank++) + acp_set_sram_bank_state(adata->acp_mmio, bank, + true); + } + rtd = adata->capture_i2ssp_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + if (adata->asic_type != CHIP_CARRIZO) { + if (adata->play_i2sbt_stream && + adata->play_i2sbt_stream->runtime) { + rtd = adata->play_i2sbt_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + if (adata->capture_i2sbt_stream && + adata->capture_i2sbt_stream->runtime) { + rtd = adata->capture_i2sbt_stream->runtime->private_data; + config_acp_dma(adata->acp_mmio, rtd, adata->asic_type); + } + } + acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + return 0; +} + +static int acp_pcm_runtime_suspend(struct device *dev) +{ + int status; + struct audio_drv_data *adata = dev_get_drvdata(dev); + + status = acp_deinit(adata->acp_mmio); + if (status) + dev_err(dev, "ACP Deinit failed status:%d\n", status); + acp_reg_write(0, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + return 0; +} + +static int acp_pcm_runtime_resume(struct device *dev) +{ + int status; + struct audio_drv_data *adata = dev_get_drvdata(dev); + + status = acp_init(adata->acp_mmio, adata->asic_type); + if (status) { + dev_err(dev, "ACP Init failed status:%d\n", status); + return status; + } + acp_reg_write(1, adata->acp_mmio, mmACP_EXTERNAL_INTR_ENB); + return 0; +} + +static const struct dev_pm_ops acp_pm_ops = { + .resume = acp_pcm_resume, + .runtime_suspend = acp_pcm_runtime_suspend, + .runtime_resume = acp_pcm_runtime_resume, +}; + +static struct platform_driver acp_dma_driver = { + .probe = acp_audio_probe, + .remove = acp_audio_remove, + .driver = { + .name = DRV_NAME, + .pm = &acp_pm_ops, + }, +}; + +module_platform_driver(acp_dma_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com"); +MODULE_DESCRIPTION("AMD ACP PCM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:"DRV_NAME); diff --git a/sound/soc/amd/acp-rt5645.c b/sound/soc/amd/acp-rt5645.c new file mode 100644 index 000000000..b79b922b0 --- /dev/null +++ b/sound/soc/amd/acp-rt5645.c @@ -0,0 +1,199 @@ +/* + * Machine driver for AMD ACP Audio engine using Realtek RT5645 codec + * + * Copyright 2017 Advanced Micro Devices, Inc. + * + * This file is modified from rt288 machine driver + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/rt5645.h" + +#define CZ_PLAT_CLK 24000000 + +static struct snd_soc_jack cz_jack; + +static int cz_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK, + CZ_PLAT_CLK, params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, + params_rate(params) * 512, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return ret; +} + +static int cz_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_card *card; + struct snd_soc_component *codec; + + codec = rtd->codec_dai->component; + card = rtd->card; + + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &cz_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "HP jack creation failed %d\n", ret); + return ret; + } + + rt5645_set_jack_detect(codec, &cz_jack, &cz_jack, &cz_jack); + + return 0; +} + +static struct snd_soc_ops cz_aif1_ops = { + .hw_params = cz_aif1_hw_params, +}; + +static struct snd_soc_dai_link cz_dai_rt5650[] = { + { + .name = "amd-rt5645-play", + .stream_name = "RT5645_AIF1", + .platform_name = "acp_audio_dma.0.auto", + .cpu_dai_name = "designware-i2s.1.auto", + .codec_dai_name = "rt5645-aif1", + .codec_name = "i2c-10EC5650:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .init = cz_init, + .ops = &cz_aif1_ops, + }, + { + .name = "amd-rt5645-cap", + .stream_name = "RT5645_AIF1", + .platform_name = "acp_audio_dma.0.auto", + .cpu_dai_name = "designware-i2s.2.auto", + .codec_dai_name = "rt5645-aif1", + .codec_name = "i2c-10EC5650:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &cz_aif1_ops, + }, +}; + +static const struct snd_soc_dapm_widget cz_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static const struct snd_soc_dapm_route cz_audio_route[] = { + {"Headphones", NULL, "HPOL"}, + {"Headphones", NULL, "HPOR"}, + {"RECMIXL", NULL, "Headset Mic"}, + {"RECMIXR", NULL, "Headset Mic"}, + {"Speakers", NULL, "SPOL"}, + {"Speakers", NULL, "SPOR"}, + {"DMIC L2", NULL, "Int Mic"}, + {"DMIC R2", NULL, "Int Mic"}, +}; + +static const struct snd_kcontrol_new cz_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static struct snd_soc_card cz_card = { + .name = "acprt5650", + .owner = THIS_MODULE, + .dai_link = cz_dai_rt5650, + .num_links = ARRAY_SIZE(cz_dai_rt5650), + .dapm_widgets = cz_widgets, + .num_dapm_widgets = ARRAY_SIZE(cz_widgets), + .dapm_routes = cz_audio_route, + .num_dapm_routes = ARRAY_SIZE(cz_audio_route), + .controls = cz_mc_controls, + .num_controls = ARRAY_SIZE(cz_mc_controls), +}; + +static int cz_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card; + + card = &cz_card; + cz_card.dev = &pdev->dev; + platform_set_drvdata(pdev, card); + ret = devm_snd_soc_register_card(&pdev->dev, &cz_card); + if (ret) { + dev_err(&pdev->dev, + "devm_snd_soc_register_card(%s) failed: %d\n", + cz_card.name, ret); + return ret; + } + return 0; +} + +static const struct acpi_device_id cz_audio_acpi_match[] = { + { "AMDI1002", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, cz_audio_acpi_match); + +static struct platform_driver cz_pcm_driver = { + .driver = { + .name = "cz-rt5645", + .acpi_match_table = ACPI_PTR(cz_audio_acpi_match), + .pm = &snd_soc_pm_ops, + }, + .probe = cz_probe, +}; + +module_platform_driver(cz_pcm_driver); + +MODULE_AUTHOR("akshu.agrawal@amd.com"); +MODULE_DESCRIPTION("cz-rt5645 audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/amd/acp.h b/sound/soc/amd/acp.h new file mode 100644 index 000000000..be3963e8f --- /dev/null +++ b/sound/soc/amd/acp.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ACP_HW_H +#define __ACP_HW_H + +#include "include/acp_2_2_d.h" +#include "include/acp_2_2_sh_mask.h" + +#define ACP_PAGE_SIZE_4K_ENABLE 0x02 + +#define ACP_PLAYBACK_PTE_OFFSET 10 +#define ACP_CAPTURE_PTE_OFFSET 0 + +/* Playback and Capture Offset for Stoney */ +#define ACP_ST_PLAYBACK_PTE_OFFSET 0x04 +#define ACP_ST_CAPTURE_PTE_OFFSET 0x00 +#define ACP_ST_BT_PLAYBACK_PTE_OFFSET 0x08 +#define ACP_ST_BT_CAPTURE_PTE_OFFSET 0x0c + +#define ACP_GARLIC_CNTL_DEFAULT 0x00000FB4 +#define ACP_ONION_CNTL_DEFAULT 0x00000FB4 + +#define ACP_PHYSICAL_BASE 0x14000 + +/* + * In case of I2S SP controller instance, Stoney uses SRAM bank 1 for + * playback and SRAM Bank 2 for capture where as in case of BT I2S + * Instance, Stoney uses SRAM Bank 3 for playback & SRAM Bank 4 will + * be used for capture. Carrizo uses I2S SP controller instance. SRAM Banks + * 1, 2, 3, 4 will be used for playback & SRAM Banks 5, 6, 7, 8 will be used + * for capture scenario. + */ +#define ACP_SRAM_BANK_1_ADDRESS 0x4002000 +#define ACP_SRAM_BANK_2_ADDRESS 0x4004000 +#define ACP_SRAM_BANK_3_ADDRESS 0x4006000 +#define ACP_SRAM_BANK_4_ADDRESS 0x4008000 +#define ACP_SRAM_BANK_5_ADDRESS 0x400A000 + +#define ACP_DMA_RESET_TIME 10000 +#define ACP_CLOCK_EN_TIME_OUT_VALUE 0x000000FF +#define ACP_SOFT_RESET_DONE_TIME_OUT_VALUE 0x000000FF +#define ACP_DMA_COMPLETE_TIME_OUT_VALUE 0x000000FF + +#define ACP_SRAM_BASE_ADDRESS 0x4000000 +#define ACP_DAGB_GRP_SRAM_BASE_ADDRESS 0x4001000 +#define ACP_DAGB_GRP_SRBM_SRAM_BASE_OFFSET 0x1000 +#define ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS 0x00000000 +#define ACP_INTERNAL_APERTURE_WINDOW_4_ADDRESS 0x01800000 + +#define TO_ACP_I2S_1 0x2 +#define TO_ACP_I2S_2 0x4 +#define TO_BLUETOOTH 0x3 +#define FROM_ACP_I2S_1 0xa +#define FROM_ACP_I2S_2 0xb +#define FROM_BLUETOOTH 0xb + +#define I2S_SP_INSTANCE 0x01 +#define I2S_BT_INSTANCE 0x02 +#define CAP_CHANNEL0 0x00 +#define CAP_CHANNEL1 0x01 + +#define ACP_TILE_ON_MASK 0x03 +#define ACP_TILE_OFF_MASK 0x02 +#define ACP_TILE_ON_RETAIN_REG_MASK 0x1f +#define ACP_TILE_OFF_RETAIN_REG_MASK 0x20 + +#define ACP_TILE_P1_MASK 0x3e +#define ACP_TILE_P2_MASK 0x3d +#define ACP_TILE_DSP0_MASK 0x3b +#define ACP_TILE_DSP1_MASK 0x37 + +#define ACP_TILE_DSP2_MASK 0x2f +/* Playback DMA channels */ +#define SYSRAM_TO_ACP_CH_NUM 12 +#define ACP_TO_I2S_DMA_CH_NUM 13 + +/* Capture DMA channels */ +#define I2S_TO_ACP_DMA_CH_NUM 14 +#define ACP_TO_SYSRAM_CH_NUM 15 + +/* Playback DMA Channels for I2S BT instance */ +#define SYSRAM_TO_ACP_BT_INSTANCE_CH_NUM 8 +#define ACP_TO_I2S_DMA_BT_INSTANCE_CH_NUM 9 + +/* Capture DMA Channels for I2S BT Instance */ +#define I2S_TO_ACP_DMA_BT_INSTANCE_CH_NUM 10 +#define ACP_TO_SYSRAM_BT_INSTANCE_CH_NUM 11 + +#define NUM_DSCRS_PER_CHANNEL 2 + +#define PLAYBACK_START_DMA_DESCR_CH12 0 +#define PLAYBACK_END_DMA_DESCR_CH12 1 +#define PLAYBACK_START_DMA_DESCR_CH13 2 +#define PLAYBACK_END_DMA_DESCR_CH13 3 + +#define CAPTURE_START_DMA_DESCR_CH14 4 +#define CAPTURE_END_DMA_DESCR_CH14 5 +#define CAPTURE_START_DMA_DESCR_CH15 6 +#define CAPTURE_END_DMA_DESCR_CH15 7 + +/* I2S BT Instance DMA Descriptors */ +#define PLAYBACK_START_DMA_DESCR_CH8 8 +#define PLAYBACK_END_DMA_DESCR_CH8 9 +#define PLAYBACK_START_DMA_DESCR_CH9 10 +#define PLAYBACK_END_DMA_DESCR_CH9 11 + +#define CAPTURE_START_DMA_DESCR_CH10 12 +#define CAPTURE_END_DMA_DESCR_CH10 13 +#define CAPTURE_START_DMA_DESCR_CH11 14 +#define CAPTURE_END_DMA_DESCR_CH11 15 + +#define mmACP_I2S_16BIT_RESOLUTION_EN 0x5209 +#define ACP_I2S_MIC_16BIT_RESOLUTION_EN 0x01 +#define ACP_I2S_SP_16BIT_RESOLUTION_EN 0x02 +#define ACP_I2S_BT_16BIT_RESOLUTION_EN 0x04 +#define ACP_BT_UART_PAD_SELECT_MASK 0x1 + +enum acp_dma_priority_level { + /* 0x0 Specifies the DMA channel is given normal priority */ + ACP_DMA_PRIORITY_LEVEL_NORMAL = 0x0, + /* 0x1 Specifies the DMA channel is given high priority */ + ACP_DMA_PRIORITY_LEVEL_HIGH = 0x1, + ACP_DMA_PRIORITY_LEVEL_FORCESIZE = 0xFF +}; + +struct audio_substream_data { + struct page *pg; + unsigned int order; + u16 num_of_pages; + u16 i2s_instance; + u16 capture_channel; + u16 direction; + u16 ch1; + u16 ch2; + u16 destination; + u16 dma_dscr_idx_1; + u16 dma_dscr_idx_2; + u32 pte_offset; + u32 sram_bank; + u32 byte_cnt_high_reg_offset; + u32 byte_cnt_low_reg_offset; + u32 dma_curr_dscr; + uint64_t size; + u64 bytescount; + void __iomem *acp_mmio; +}; + +struct audio_drv_data { + struct snd_pcm_substream *play_i2ssp_stream; + struct snd_pcm_substream *capture_i2ssp_stream; + struct snd_pcm_substream *play_i2sbt_stream; + struct snd_pcm_substream *capture_i2sbt_stream; + void __iomem *acp_mmio; + u32 asic_type; +}; + +/* + * this structure used for platform data transfer between machine driver + * and dma driver + */ +struct acp_platform_info { + u16 i2s_instance; + u16 capture_channel; +}; + +union acp_dma_count { + struct { + u32 low; + u32 high; + } bcount; + u64 bytescount; +}; + +enum { + ACP_TILE_P1 = 0, + ACP_TILE_P2, + ACP_TILE_DSP0, + ACP_TILE_DSP1, + ACP_TILE_DSP2, +}; + +enum { + ACP_DMA_ATTR_SHAREDMEM_TO_DAGB_ONION = 0x0, + ACP_DMA_ATTR_SHARED_MEM_TO_DAGB_GARLIC = 0x1, + ACP_DMA_ATTR_DAGB_ONION_TO_SHAREDMEM = 0x8, + ACP_DMA_ATTR_DAGB_GARLIC_TO_SHAREDMEM = 0x9, + ACP_DMA_ATTR_FORCE_SIZE = 0xF +}; + +typedef struct acp_dma_dscr_transfer { + /* Specifies the source memory location for the DMA data transfer. */ + u32 src; + /* + * Specifies the destination memory location to where the data will + * be transferred. + */ + u32 dest; + /* + * Specifies the number of bytes need to be transferred + * from source to destination memory.Transfer direction & IOC enable + */ + u32 xfer_val; + /* Reserved for future use */ + u32 reserved; +} acp_dma_dscr_transfer_t; + +#endif /*__ACP_HW_H */ diff --git a/sound/soc/amd/include/acp_2_2_d.h b/sound/soc/amd/include/acp_2_2_d.h new file mode 100644 index 000000000..0118fe9e6 --- /dev/null +++ b/sound/soc/amd/include/acp_2_2_d.h @@ -0,0 +1,609 @@ +/* + * ACP_2_2 Register documentation + * + * Copyright (C) 2014 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef ACP_2_2_D_H +#define ACP_2_2_D_H + +#define mmACP_DMA_CNTL_0 0x5000 +#define mmACP_DMA_CNTL_1 0x5001 +#define mmACP_DMA_CNTL_2 0x5002 +#define mmACP_DMA_CNTL_3 0x5003 +#define mmACP_DMA_CNTL_4 0x5004 +#define mmACP_DMA_CNTL_5 0x5005 +#define mmACP_DMA_CNTL_6 0x5006 +#define mmACP_DMA_CNTL_7 0x5007 +#define mmACP_DMA_CNTL_8 0x5008 +#define mmACP_DMA_CNTL_9 0x5009 +#define mmACP_DMA_CNTL_10 0x500a +#define mmACP_DMA_CNTL_11 0x500b +#define mmACP_DMA_CNTL_12 0x500c +#define mmACP_DMA_CNTL_13 0x500d +#define mmACP_DMA_CNTL_14 0x500e +#define mmACP_DMA_CNTL_15 0x500f +#define mmACP_DMA_DSCR_STRT_IDX_0 0x5010 +#define mmACP_DMA_DSCR_STRT_IDX_1 0x5011 +#define mmACP_DMA_DSCR_STRT_IDX_2 0x5012 +#define mmACP_DMA_DSCR_STRT_IDX_3 0x5013 +#define mmACP_DMA_DSCR_STRT_IDX_4 0x5014 +#define mmACP_DMA_DSCR_STRT_IDX_5 0x5015 +#define mmACP_DMA_DSCR_STRT_IDX_6 0x5016 +#define mmACP_DMA_DSCR_STRT_IDX_7 0x5017 +#define mmACP_DMA_DSCR_STRT_IDX_8 0x5018 +#define mmACP_DMA_DSCR_STRT_IDX_9 0x5019 +#define mmACP_DMA_DSCR_STRT_IDX_10 0x501a +#define mmACP_DMA_DSCR_STRT_IDX_11 0x501b +#define mmACP_DMA_DSCR_STRT_IDX_12 0x501c +#define mmACP_DMA_DSCR_STRT_IDX_13 0x501d +#define mmACP_DMA_DSCR_STRT_IDX_14 0x501e +#define mmACP_DMA_DSCR_STRT_IDX_15 0x501f +#define mmACP_DMA_DSCR_CNT_0 0x5020 +#define mmACP_DMA_DSCR_CNT_1 0x5021 +#define mmACP_DMA_DSCR_CNT_2 0x5022 +#define mmACP_DMA_DSCR_CNT_3 0x5023 +#define mmACP_DMA_DSCR_CNT_4 0x5024 +#define mmACP_DMA_DSCR_CNT_5 0x5025 +#define mmACP_DMA_DSCR_CNT_6 0x5026 +#define mmACP_DMA_DSCR_CNT_7 0x5027 +#define mmACP_DMA_DSCR_CNT_8 0x5028 +#define mmACP_DMA_DSCR_CNT_9 0x5029 +#define mmACP_DMA_DSCR_CNT_10 0x502a +#define mmACP_DMA_DSCR_CNT_11 0x502b +#define mmACP_DMA_DSCR_CNT_12 0x502c +#define mmACP_DMA_DSCR_CNT_13 0x502d +#define mmACP_DMA_DSCR_CNT_14 0x502e +#define mmACP_DMA_DSCR_CNT_15 0x502f +#define mmACP_DMA_PRIO_0 0x5030 +#define mmACP_DMA_PRIO_1 0x5031 +#define mmACP_DMA_PRIO_2 0x5032 +#define mmACP_DMA_PRIO_3 0x5033 +#define mmACP_DMA_PRIO_4 0x5034 +#define mmACP_DMA_PRIO_5 0x5035 +#define mmACP_DMA_PRIO_6 0x5036 +#define mmACP_DMA_PRIO_7 0x5037 +#define mmACP_DMA_PRIO_8 0x5038 +#define mmACP_DMA_PRIO_9 0x5039 +#define mmACP_DMA_PRIO_10 0x503a +#define mmACP_DMA_PRIO_11 0x503b +#define mmACP_DMA_PRIO_12 0x503c +#define mmACP_DMA_PRIO_13 0x503d +#define mmACP_DMA_PRIO_14 0x503e +#define mmACP_DMA_PRIO_15 0x503f +#define mmACP_DMA_CUR_DSCR_0 0x5040 +#define mmACP_DMA_CUR_DSCR_1 0x5041 +#define mmACP_DMA_CUR_DSCR_2 0x5042 +#define mmACP_DMA_CUR_DSCR_3 0x5043 +#define mmACP_DMA_CUR_DSCR_4 0x5044 +#define mmACP_DMA_CUR_DSCR_5 0x5045 +#define mmACP_DMA_CUR_DSCR_6 0x5046 +#define mmACP_DMA_CUR_DSCR_7 0x5047 +#define mmACP_DMA_CUR_DSCR_8 0x5048 +#define mmACP_DMA_CUR_DSCR_9 0x5049 +#define mmACP_DMA_CUR_DSCR_10 0x504a +#define mmACP_DMA_CUR_DSCR_11 0x504b +#define mmACP_DMA_CUR_DSCR_12 0x504c +#define mmACP_DMA_CUR_DSCR_13 0x504d +#define mmACP_DMA_CUR_DSCR_14 0x504e +#define mmACP_DMA_CUR_DSCR_15 0x504f +#define mmACP_DMA_CUR_TRANS_CNT_0 0x5050 +#define mmACP_DMA_CUR_TRANS_CNT_1 0x5051 +#define mmACP_DMA_CUR_TRANS_CNT_2 0x5052 +#define mmACP_DMA_CUR_TRANS_CNT_3 0x5053 +#define mmACP_DMA_CUR_TRANS_CNT_4 0x5054 +#define mmACP_DMA_CUR_TRANS_CNT_5 0x5055 +#define mmACP_DMA_CUR_TRANS_CNT_6 0x5056 +#define mmACP_DMA_CUR_TRANS_CNT_7 0x5057 +#define mmACP_DMA_CUR_TRANS_CNT_8 0x5058 +#define mmACP_DMA_CUR_TRANS_CNT_9 0x5059 +#define mmACP_DMA_CUR_TRANS_CNT_10 0x505a +#define mmACP_DMA_CUR_TRANS_CNT_11 0x505b +#define mmACP_DMA_CUR_TRANS_CNT_12 0x505c +#define mmACP_DMA_CUR_TRANS_CNT_13 0x505d +#define mmACP_DMA_CUR_TRANS_CNT_14 0x505e +#define mmACP_DMA_CUR_TRANS_CNT_15 0x505f +#define mmACP_DMA_ERR_STS_0 0x5060 +#define mmACP_DMA_ERR_STS_1 0x5061 +#define mmACP_DMA_ERR_STS_2 0x5062 +#define mmACP_DMA_ERR_STS_3 0x5063 +#define mmACP_DMA_ERR_STS_4 0x5064 +#define mmACP_DMA_ERR_STS_5 0x5065 +#define mmACP_DMA_ERR_STS_6 0x5066 +#define mmACP_DMA_ERR_STS_7 0x5067 +#define mmACP_DMA_ERR_STS_8 0x5068 +#define mmACP_DMA_ERR_STS_9 0x5069 +#define mmACP_DMA_ERR_STS_10 0x506a +#define mmACP_DMA_ERR_STS_11 0x506b +#define mmACP_DMA_ERR_STS_12 0x506c +#define mmACP_DMA_ERR_STS_13 0x506d +#define mmACP_DMA_ERR_STS_14 0x506e +#define mmACP_DMA_ERR_STS_15 0x506f +#define mmACP_DMA_DESC_BASE_ADDR 0x5070 +#define mmACP_DMA_DESC_MAX_NUM_DSCR 0x5071 +#define mmACP_DMA_CH_STS 0x5072 +#define mmACP_DMA_CH_GROUP 0x5073 +#define mmACP_DSP0_CACHE_OFFSET0 0x5078 +#define mmACP_DSP0_CACHE_SIZE0 0x5079 +#define mmACP_DSP0_CACHE_OFFSET1 0x507a +#define mmACP_DSP0_CACHE_SIZE1 0x507b +#define mmACP_DSP0_CACHE_OFFSET2 0x507c +#define mmACP_DSP0_CACHE_SIZE2 0x507d +#define mmACP_DSP0_CACHE_OFFSET3 0x507e +#define mmACP_DSP0_CACHE_SIZE3 0x507f +#define mmACP_DSP0_CACHE_OFFSET4 0x5080 +#define mmACP_DSP0_CACHE_SIZE4 0x5081 +#define mmACP_DSP0_CACHE_OFFSET5 0x5082 +#define mmACP_DSP0_CACHE_SIZE5 0x5083 +#define mmACP_DSP0_CACHE_OFFSET6 0x5084 +#define mmACP_DSP0_CACHE_SIZE6 0x5085 +#define mmACP_DSP0_CACHE_OFFSET7 0x5086 +#define mmACP_DSP0_CACHE_SIZE7 0x5087 +#define mmACP_DSP0_CACHE_OFFSET8 0x5088 +#define mmACP_DSP0_CACHE_SIZE8 0x5089 +#define mmACP_DSP0_NONCACHE_OFFSET0 0x508a +#define mmACP_DSP0_NONCACHE_SIZE0 0x508b +#define mmACP_DSP0_NONCACHE_OFFSET1 0x508c +#define mmACP_DSP0_NONCACHE_SIZE1 0x508d +#define mmACP_DSP0_DEBUG_PC 0x508e +#define mmACP_DSP0_NMI_SEL 0x508f +#define mmACP_DSP0_CLKRST_CNTL 0x5090 +#define mmACP_DSP0_RUNSTALL 0x5091 +#define mmACP_DSP0_OCD_HALT_ON_RST 0x5092 +#define mmACP_DSP0_WAIT_MODE 0x5093 +#define mmACP_DSP0_VECT_SEL 0x5094 +#define mmACP_DSP0_DEBUG_REG1 0x5095 +#define mmACP_DSP0_DEBUG_REG2 0x5096 +#define mmACP_DSP0_DEBUG_REG3 0x5097 +#define mmACP_DSP1_CACHE_OFFSET0 0x509d +#define mmACP_DSP1_CACHE_SIZE0 0x509e +#define mmACP_DSP1_CACHE_OFFSET1 0x509f +#define mmACP_DSP1_CACHE_SIZE1 0x50a0 +#define mmACP_DSP1_CACHE_OFFSET2 0x50a1 +#define mmACP_DSP1_CACHE_SIZE2 0x50a2 +#define mmACP_DSP1_CACHE_OFFSET3 0x50a3 +#define mmACP_DSP1_CACHE_SIZE3 0x50a4 +#define mmACP_DSP1_CACHE_OFFSET4 0x50a5 +#define mmACP_DSP1_CACHE_SIZE4 0x50a6 +#define mmACP_DSP1_CACHE_OFFSET5 0x50a7 +#define mmACP_DSP1_CACHE_SIZE5 0x50a8 +#define mmACP_DSP1_CACHE_OFFSET6 0x50a9 +#define mmACP_DSP1_CACHE_SIZE6 0x50aa +#define mmACP_DSP1_CACHE_OFFSET7 0x50ab +#define mmACP_DSP1_CACHE_SIZE7 0x50ac +#define mmACP_DSP1_CACHE_OFFSET8 0x50ad +#define mmACP_DSP1_CACHE_SIZE8 0x50ae +#define mmACP_DSP1_NONCACHE_OFFSET0 0x50af +#define mmACP_DSP1_NONCACHE_SIZE0 0x50b0 +#define mmACP_DSP1_NONCACHE_OFFSET1 0x50b1 +#define mmACP_DSP1_NONCACHE_SIZE1 0x50b2 +#define mmACP_DSP1_DEBUG_PC 0x50b3 +#define mmACP_DSP1_NMI_SEL 0x50b4 +#define mmACP_DSP1_CLKRST_CNTL 0x50b5 +#define mmACP_DSP1_RUNSTALL 0x50b6 +#define mmACP_DSP1_OCD_HALT_ON_RST 0x50b7 +#define mmACP_DSP1_WAIT_MODE 0x50b8 +#define mmACP_DSP1_VECT_SEL 0x50b9 +#define mmACP_DSP1_DEBUG_REG1 0x50ba +#define mmACP_DSP1_DEBUG_REG2 0x50bb +#define mmACP_DSP1_DEBUG_REG3 0x50bc +#define mmACP_DSP2_CACHE_OFFSET0 0x50c2 +#define mmACP_DSP2_CACHE_SIZE0 0x50c3 +#define mmACP_DSP2_CACHE_OFFSET1 0x50c4 +#define mmACP_DSP2_CACHE_SIZE1 0x50c5 +#define mmACP_DSP2_CACHE_OFFSET2 0x50c6 +#define mmACP_DSP2_CACHE_SIZE2 0x50c7 +#define mmACP_DSP2_CACHE_OFFSET3 0x50c8 +#define mmACP_DSP2_CACHE_SIZE3 0x50c9 +#define mmACP_DSP2_CACHE_OFFSET4 0x50ca +#define mmACP_DSP2_CACHE_SIZE4 0x50cb +#define mmACP_DSP2_CACHE_OFFSET5 0x50cc +#define mmACP_DSP2_CACHE_SIZE5 0x50cd +#define mmACP_DSP2_CACHE_OFFSET6 0x50ce +#define mmACP_DSP2_CACHE_SIZE6 0x50cf +#define mmACP_DSP2_CACHE_OFFSET7 0x50d0 +#define mmACP_DSP2_CACHE_SIZE7 0x50d1 +#define mmACP_DSP2_CACHE_OFFSET8 0x50d2 +#define mmACP_DSP2_CACHE_SIZE8 0x50d3 +#define mmACP_DSP2_NONCACHE_OFFSET0 0x50d4 +#define mmACP_DSP2_NONCACHE_SIZE0 0x50d5 +#define mmACP_DSP2_NONCACHE_OFFSET1 0x50d6 +#define mmACP_DSP2_NONCACHE_SIZE1 0x50d7 +#define mmACP_DSP2_DEBUG_PC 0x50d8 +#define mmACP_DSP2_NMI_SEL 0x50d9 +#define mmACP_DSP2_CLKRST_CNTL 0x50da +#define mmACP_DSP2_RUNSTALL 0x50db +#define mmACP_DSP2_OCD_HALT_ON_RST 0x50dc +#define mmACP_DSP2_WAIT_MODE 0x50dd +#define mmACP_DSP2_VECT_SEL 0x50de +#define mmACP_DSP2_DEBUG_REG1 0x50df +#define mmACP_DSP2_DEBUG_REG2 0x50e0 +#define mmACP_DSP2_DEBUG_REG3 0x50e1 +#define mmACP_AXI2DAGB_ONION_CNTL 0x50e7 +#define mmACP_AXI2DAGB_ONION_ERR_STATUS_WR 0x50e8 +#define mmACP_AXI2DAGB_ONION_ERR_STATUS_RD 0x50e9 +#define mmACP_DAGB_Onion_TransPerf_Counter_Control 0x50ea +#define mmACP_DAGB_Onion_Wr_TransPerf_Counter_Current 0x50eb +#define mmACP_DAGB_Onion_Wr_TransPerf_Counter_Peak 0x50ec +#define mmACP_DAGB_Onion_Rd_TransPerf_Counter_Current 0x50ed +#define mmACP_DAGB_Onion_Rd_TransPerf_Counter_Peak 0x50ee +#define mmACP_AXI2DAGB_GARLIC_CNTL 0x50f3 +#define mmACP_AXI2DAGB_GARLIC_ERR_STATUS_WR 0x50f4 +#define mmACP_AXI2DAGB_GARLIC_ERR_STATUS_RD 0x50f5 +#define mmACP_DAGB_Garlic_TransPerf_Counter_Control 0x50f6 +#define mmACP_DAGB_Garlic_Wr_TransPerf_Counter_Current 0x50f7 +#define mmACP_DAGB_Garlic_Wr_TransPerf_Counter_Peak 0x50f8 +#define mmACP_DAGB_Garlic_Rd_TransPerf_Counter_Current 0x50f9 +#define mmACP_DAGB_Garlic_Rd_TransPerf_Counter_Peak 0x50fa +#define mmACP_DAGB_PAGE_SIZE_GRP_1 0x50ff +#define mmACP_DAGB_BASE_ADDR_GRP_1 0x5100 +#define mmACP_DAGB_PAGE_SIZE_GRP_2 0x5101 +#define mmACP_DAGB_BASE_ADDR_GRP_2 0x5102 +#define mmACP_DAGB_PAGE_SIZE_GRP_3 0x5103 +#define mmACP_DAGB_BASE_ADDR_GRP_3 0x5104 +#define mmACP_DAGB_PAGE_SIZE_GRP_4 0x5105 +#define mmACP_DAGB_BASE_ADDR_GRP_4 0x5106 +#define mmACP_DAGB_PAGE_SIZE_GRP_5 0x5107 +#define mmACP_DAGB_BASE_ADDR_GRP_5 0x5108 +#define mmACP_DAGB_PAGE_SIZE_GRP_6 0x5109 +#define mmACP_DAGB_BASE_ADDR_GRP_6 0x510a +#define mmACP_DAGB_PAGE_SIZE_GRP_7 0x510b +#define mmACP_DAGB_BASE_ADDR_GRP_7 0x510c +#define mmACP_DAGB_PAGE_SIZE_GRP_8 0x510d +#define mmACP_DAGB_BASE_ADDR_GRP_8 0x510e +#define mmACP_DAGB_ATU_CTRL 0x510f +#define mmACP_CONTROL 0x5131 +#define mmACP_STATUS 0x5133 +#define mmACP_SOFT_RESET 0x5134 +#define mmACP_PwrMgmt_CNTL 0x5135 +#define mmACP_CAC_INDICATOR_CONTROL 0x5136 +#define mmACP_SMU_MAILBOX 0x5137 +#define mmACP_FUTURE_REG_SCLK_0 0x5138 +#define mmACP_FUTURE_REG_SCLK_1 0x5139 +#define mmACP_FUTURE_REG_SCLK_2 0x513a +#define mmACP_FUTURE_REG_SCLK_3 0x513b +#define mmACP_FUTURE_REG_SCLK_4 0x513c +#define mmACP_DAGB_DEBUG_CNT_ENABLE 0x513d +#define mmACP_DAGBG_WR_ASK_CNT 0x513e +#define mmACP_DAGBG_WR_GO_CNT 0x513f +#define mmACP_DAGBG_WR_EXP_RESP_CNT 0x5140 +#define mmACP_DAGBG_WR_ACTUAL_RESP_CNT 0x5141 +#define mmACP_DAGBG_RD_ASK_CNT 0x5142 +#define mmACP_DAGBG_RD_GO_CNT 0x5143 +#define mmACP_DAGBG_RD_EXP_RESP_CNT 0x5144 +#define mmACP_DAGBG_RD_ACTUAL_RESP_CNT 0x5145 +#define mmACP_DAGBO_WR_ASK_CNT 0x5146 +#define mmACP_DAGBO_WR_GO_CNT 0x5147 +#define mmACP_DAGBO_WR_EXP_RESP_CNT 0x5148 +#define mmACP_DAGBO_WR_ACTUAL_RESP_CNT 0x5149 +#define mmACP_DAGBO_RD_ASK_CNT 0x514a +#define mmACP_DAGBO_RD_GO_CNT 0x514b +#define mmACP_DAGBO_RD_EXP_RESP_CNT 0x514c +#define mmACP_DAGBO_RD_ACTUAL_RESP_CNT 0x514d +#define mmACP_BRB_CONTROL 0x5156 +#define mmACP_EXTERNAL_INTR_ENB 0x5157 +#define mmACP_EXTERNAL_INTR_CNTL 0x5158 +#define mmACP_ERROR_SOURCE_STS 0x5159 +#define mmACP_DSP_SW_INTR_TRIG 0x515a +#define mmACP_DSP_SW_INTR_CNTL 0x515b +#define mmACP_DAGBG_TIMEOUT_CNTL 0x515c +#define mmACP_DAGBO_TIMEOUT_CNTL 0x515d +#define mmACP_EXTERNAL_INTR_STAT 0x515e +#define mmACP_DSP_SW_INTR_STAT 0x515f +#define mmACP_DSP0_INTR_CNTL 0x5160 +#define mmACP_DSP0_INTR_STAT 0x5161 +#define mmACP_DSP0_TIMEOUT_CNTL 0x5162 +#define mmACP_DSP1_INTR_CNTL 0x5163 +#define mmACP_DSP1_INTR_STAT 0x5164 +#define mmACP_DSP1_TIMEOUT_CNTL 0x5165 +#define mmACP_DSP2_INTR_CNTL 0x5166 +#define mmACP_DSP2_INTR_STAT 0x5167 +#define mmACP_DSP2_TIMEOUT_CNTL 0x5168 +#define mmACP_DSP0_EXT_TIMER_CNTL 0x5169 +#define mmACP_DSP1_EXT_TIMER_CNTL 0x516a +#define mmACP_DSP2_EXT_TIMER_CNTL 0x516b +#define mmACP_AXI2DAGB_SEM_0 0x516c +#define mmACP_AXI2DAGB_SEM_1 0x516d +#define mmACP_AXI2DAGB_SEM_2 0x516e +#define mmACP_AXI2DAGB_SEM_3 0x516f +#define mmACP_AXI2DAGB_SEM_4 0x5170 +#define mmACP_AXI2DAGB_SEM_5 0x5171 +#define mmACP_AXI2DAGB_SEM_6 0x5172 +#define mmACP_AXI2DAGB_SEM_7 0x5173 +#define mmACP_AXI2DAGB_SEM_8 0x5174 +#define mmACP_AXI2DAGB_SEM_9 0x5175 +#define mmACP_AXI2DAGB_SEM_10 0x5176 +#define mmACP_AXI2DAGB_SEM_11 0x5177 +#define mmACP_AXI2DAGB_SEM_12 0x5178 +#define mmACP_AXI2DAGB_SEM_13 0x5179 +#define mmACP_AXI2DAGB_SEM_14 0x517a +#define mmACP_AXI2DAGB_SEM_15 0x517b +#define mmACP_AXI2DAGB_SEM_16 0x517c +#define mmACP_AXI2DAGB_SEM_17 0x517d +#define mmACP_AXI2DAGB_SEM_18 0x517e +#define mmACP_AXI2DAGB_SEM_19 0x517f +#define mmACP_AXI2DAGB_SEM_20 0x5180 +#define mmACP_AXI2DAGB_SEM_21 0x5181 +#define mmACP_AXI2DAGB_SEM_22 0x5182 +#define mmACP_AXI2DAGB_SEM_23 0x5183 +#define mmACP_AXI2DAGB_SEM_24 0x5184 +#define mmACP_AXI2DAGB_SEM_25 0x5185 +#define mmACP_AXI2DAGB_SEM_26 0x5186 +#define mmACP_AXI2DAGB_SEM_27 0x5187 +#define mmACP_AXI2DAGB_SEM_28 0x5188 +#define mmACP_AXI2DAGB_SEM_29 0x5189 +#define mmACP_AXI2DAGB_SEM_30 0x518a +#define mmACP_AXI2DAGB_SEM_31 0x518b +#define mmACP_AXI2DAGB_SEM_32 0x518c +#define mmACP_AXI2DAGB_SEM_33 0x518d +#define mmACP_AXI2DAGB_SEM_34 0x518e +#define mmACP_AXI2DAGB_SEM_35 0x518f +#define mmACP_AXI2DAGB_SEM_36 0x5190 +#define mmACP_AXI2DAGB_SEM_37 0x5191 +#define mmACP_AXI2DAGB_SEM_38 0x5192 +#define mmACP_AXI2DAGB_SEM_39 0x5193 +#define mmACP_AXI2DAGB_SEM_40 0x5194 +#define mmACP_AXI2DAGB_SEM_41 0x5195 +#define mmACP_AXI2DAGB_SEM_42 0x5196 +#define mmACP_AXI2DAGB_SEM_43 0x5197 +#define mmACP_AXI2DAGB_SEM_44 0x5198 +#define mmACP_AXI2DAGB_SEM_45 0x5199 +#define mmACP_AXI2DAGB_SEM_46 0x519a +#define mmACP_AXI2DAGB_SEM_47 0x519b +#define mmACP_SRBM_Client_Base_Addr 0x519c +#define mmACP_SRBM_Client_RDDATA 0x519d +#define mmACP_SRBM_Cycle_Sts 0x519e +#define mmACP_SRBM_Targ_Idx_Addr 0x519f +#define mmACP_SRBM_Targ_Idx_Data 0x51a0 +#define mmACP_SEMA_ADDR_LOW 0x51a1 +#define mmACP_SEMA_ADDR_HIGH 0x51a2 +#define mmACP_SEMA_CMD 0x51a3 +#define mmACP_SEMA_STS 0x51a4 +#define mmACP_SEMA_REQ 0x51a5 +#define mmACP_FW_STATUS 0x51a6 +#define mmACP_FUTURE_REG_ACLK_0 0x51a7 +#define mmACP_FUTURE_REG_ACLK_1 0x51a8 +#define mmACP_FUTURE_REG_ACLK_2 0x51a9 +#define mmACP_FUTURE_REG_ACLK_3 0x51aa +#define mmACP_FUTURE_REG_ACLK_4 0x51ab +#define mmACP_TIMER 0x51ac +#define mmACP_TIMER_CNTL 0x51ad +#define mmACP_DSP0_TIMER 0x51ae +#define mmACP_DSP1_TIMER 0x51af +#define mmACP_DSP2_TIMER 0x51b0 +#define mmACP_I2S_TRANSMIT_BYTE_CNT_HIGH 0x51b1 +#define mmACP_I2S_TRANSMIT_BYTE_CNT_LOW 0x51b2 +#define mmACP_I2S_BT_TRANSMIT_BYTE_CNT_HIGH 0x51b3 +#define mmACP_I2S_BT_TRANSMIT_BYTE_CNT_LOW 0x51b4 +#define mmACP_I2S_BT_RECEIVE_BYTE_CNT_HIGH 0x51b5 +#define mmACP_I2S_BT_RECEIVE_BYTE_CNT_LOW 0x51b6 +#define mmACP_DSP0_CS_STATE 0x51b7 +#define mmACP_DSP1_CS_STATE 0x51b8 +#define mmACP_DSP2_CS_STATE 0x51b9 +#define mmACP_SCRATCH_REG_BASE_ADDR 0x51ba +#define mmCC_ACP_EFUSE 0x51c8 +#define mmACP_PGFSM_RETAIN_REG 0x51c9 +#define mmACP_PGFSM_CONFIG_REG 0x51ca +#define mmACP_PGFSM_WRITE_REG 0x51cb +#define mmACP_PGFSM_READ_REG_0 0x51cc +#define mmACP_PGFSM_READ_REG_1 0x51cd +#define mmACP_PGFSM_READ_REG_2 0x51ce +#define mmACP_PGFSM_READ_REG_3 0x51cf +#define mmACP_PGFSM_READ_REG_4 0x51d0 +#define mmACP_PGFSM_READ_REG_5 0x51d1 +#define mmACP_IP_PGFSM_ENABLE 0x51d2 +#define mmACP_I2S_PIN_CONFIG 0x51d3 +#define mmACP_AZALIA_I2S_SELECT 0x51d4 +#define mmACP_CHIP_PKG_FOR_PAD_ISOLATION 0x51d5 +#define mmACP_AUDIO_PAD_PULLUP_PULLDOWN_CTRL 0x51d6 +#define mmACP_BT_UART_PAD_SEL 0x51d7 +#define mmACP_SCRATCH_REG_0 0x52c0 +#define mmACP_SCRATCH_REG_1 0x52c1 +#define mmACP_SCRATCH_REG_2 0x52c2 +#define mmACP_SCRATCH_REG_3 0x52c3 +#define mmACP_SCRATCH_REG_4 0x52c4 +#define mmACP_SCRATCH_REG_5 0x52c5 +#define mmACP_SCRATCH_REG_6 0x52c6 +#define mmACP_SCRATCH_REG_7 0x52c7 +#define mmACP_SCRATCH_REG_8 0x52c8 +#define mmACP_SCRATCH_REG_9 0x52c9 +#define mmACP_SCRATCH_REG_10 0x52ca +#define mmACP_SCRATCH_REG_11 0x52cb +#define mmACP_SCRATCH_REG_12 0x52cc +#define mmACP_SCRATCH_REG_13 0x52cd +#define mmACP_SCRATCH_REG_14 0x52ce +#define mmACP_SCRATCH_REG_15 0x52cf +#define mmACP_SCRATCH_REG_16 0x52d0 +#define mmACP_SCRATCH_REG_17 0x52d1 +#define mmACP_SCRATCH_REG_18 0x52d2 +#define mmACP_SCRATCH_REG_19 0x52d3 +#define mmACP_SCRATCH_REG_20 0x52d4 +#define mmACP_SCRATCH_REG_21 0x52d5 +#define mmACP_SCRATCH_REG_22 0x52d6 +#define mmACP_SCRATCH_REG_23 0x52d7 +#define mmACP_SCRATCH_REG_24 0x52d8 +#define mmACP_SCRATCH_REG_25 0x52d9 +#define mmACP_SCRATCH_REG_26 0x52da +#define mmACP_SCRATCH_REG_27 0x52db +#define mmACP_SCRATCH_REG_28 0x52dc +#define mmACP_SCRATCH_REG_29 0x52dd +#define mmACP_SCRATCH_REG_30 0x52de +#define mmACP_SCRATCH_REG_31 0x52df +#define mmACP_SCRATCH_REG_32 0x52e0 +#define mmACP_SCRATCH_REG_33 0x52e1 +#define mmACP_SCRATCH_REG_34 0x52e2 +#define mmACP_SCRATCH_REG_35 0x52e3 +#define mmACP_SCRATCH_REG_36 0x52e4 +#define mmACP_SCRATCH_REG_37 0x52e5 +#define mmACP_SCRATCH_REG_38 0x52e6 +#define mmACP_SCRATCH_REG_39 0x52e7 +#define mmACP_SCRATCH_REG_40 0x52e8 +#define mmACP_SCRATCH_REG_41 0x52e9 +#define mmACP_SCRATCH_REG_42 0x52ea +#define mmACP_SCRATCH_REG_43 0x52eb +#define mmACP_SCRATCH_REG_44 0x52ec +#define mmACP_SCRATCH_REG_45 0x52ed +#define mmACP_SCRATCH_REG_46 0x52ee +#define mmACP_SCRATCH_REG_47 0x52ef +#define mmACP_VOICE_WAKEUP_ENABLE 0x51e8 +#define mmACP_VOICE_WAKEUP_STATUS 0x51e9 +#define mmI2S_VOICE_WAKEUP_LOWER_THRESHOLD 0x51ea +#define mmI2S_VOICE_WAKEUP_HIGHER_THRESHOLD 0x51eb +#define mmI2S_VOICE_WAKEUP_NO_OF_SAMPLES 0x51ec +#define mmI2S_VOICE_WAKEUP_NO_OF_PEAKS 0x51ed +#define mmI2S_VOICE_WAKEUP_DURATION_OF_N_PEAKS 0x51ee +#define mmI2S_VOICE_WAKEUP_BITCLK_TOGGLE_DETECTION 0x51ef +#define mmI2S_VOICE_WAKEUP_DATA_PATH_SWITCH 0x51f0 +#define mmI2S_VOICE_WAKEUP_DATA_POINTER 0x51f1 +#define mmI2S_VOICE_WAKEUP_AUTH_MATCH 0x51f2 +#define mmI2S_VOICE_WAKEUP_8KB_WRAP 0x51f3 +#define mmACP_I2S_RECEIVED_BYTE_CNT_HIGH 0x51f4 +#define mmACP_I2S_RECEIVED_BYTE_CNT_LOW 0x51f5 +#define mmACP_I2S_MICSP_TRANSMIT_BYTE_CNT_HIGH 0x51f6 +#define mmACP_I2S_MICSP_TRANSMIT_BYTE_CNT_LOW 0x51f7 +#define mmACP_MEM_SHUT_DOWN_REQ_LO 0x51f8 +#define mmACP_MEM_SHUT_DOWN_REQ_HI 0x51f9 +#define mmACP_MEM_SHUT_DOWN_STS_LO 0x51fa +#define mmACP_MEM_SHUT_DOWN_STS_HI 0x51fb +#define mmACP_MEM_DEEP_SLEEP_REQ_LO 0x51fc +#define mmACP_MEM_DEEP_SLEEP_REQ_HI 0x51fd +#define mmACP_MEM_DEEP_SLEEP_STS_LO 0x51fe +#define mmACP_MEM_DEEP_SLEEP_STS_HI 0x51ff +#define mmACP_MEM_WAKEUP_FROM_SHUT_DOWN_LO 0x5200 +#define mmACP_MEM_WAKEUP_FROM_SHUT_DOWN_HI 0x5201 +#define mmACP_MEM_WAKEUP_FROM_SLEEP_LO 0x5202 +#define mmACP_MEM_WAKEUP_FROM_SLEEP_HI 0x5203 +#define mmACP_I2SSP_IER 0x5210 +#define mmACP_I2SSP_IRER 0x5211 +#define mmACP_I2SSP_ITER 0x5212 +#define mmACP_I2SSP_CER 0x5213 +#define mmACP_I2SSP_CCR 0x5214 +#define mmACP_I2SSP_RXFFR 0x5215 +#define mmACP_I2SSP_TXFFR 0x5216 +#define mmACP_I2SSP_LRBR0 0x5218 +#define mmACP_I2SSP_RRBR0 0x5219 +#define mmACP_I2SSP_RER0 0x521a +#define mmACP_I2SSP_TER0 0x521b +#define mmACP_I2SSP_RCR0 0x521c +#define mmACP_I2SSP_TCR0 0x521d +#define mmACP_I2SSP_ISR0 0x521e +#define mmACP_I2SSP_IMR0 0x521f +#define mmACP_I2SSP_ROR0 0x5220 +#define mmACP_I2SSP_TOR0 0x5221 +#define mmACP_I2SSP_RFCR0 0x5222 +#define mmACP_I2SSP_TFCR0 0x5223 +#define mmACP_I2SSP_RFF0 0x5224 +#define mmACP_I2SSP_TFF0 0x5225 +#define mmACP_I2SSP_RXDMA 0x5226 +#define mmACP_I2SSP_RRXDMA 0x5227 +#define mmACP_I2SSP_TXDMA 0x5228 +#define mmACP_I2SSP_RTXDMA 0x5229 +#define mmACP_I2SSP_COMP_PARAM_2 0x522a +#define mmACP_I2SSP_COMP_PARAM_1 0x522b +#define mmACP_I2SSP_COMP_VERSION 0x522c +#define mmACP_I2SSP_COMP_TYPE 0x522d +#define mmACP_I2SMICSP_IER 0x522e +#define mmACP_I2SMICSP_IRER 0x522f +#define mmACP_I2SMICSP_ITER 0x5230 +#define mmACP_I2SMICSP_CER 0x5231 +#define mmACP_I2SMICSP_CCR 0x5232 +#define mmACP_I2SMICSP_RXFFR 0x5233 +#define mmACP_I2SMICSP_TXFFR 0x5234 +#define mmACP_I2SMICSP_LRBR0 0x5236 +#define mmACP_I2SMICSP_RRBR0 0x5237 +#define mmACP_I2SMICSP_RER0 0x5238 +#define mmACP_I2SMICSP_TER0 0x5239 +#define mmACP_I2SMICSP_RCR0 0x523a +#define mmACP_I2SMICSP_TCR0 0x523b +#define mmACP_I2SMICSP_ISR0 0x523c +#define mmACP_I2SMICSP_IMR0 0x523d +#define mmACP_I2SMICSP_ROR0 0x523e +#define mmACP_I2SMICSP_TOR0 0x523f +#define mmACP_I2SMICSP_RFCR0 0x5240 +#define mmACP_I2SMICSP_TFCR0 0x5241 +#define mmACP_I2SMICSP_RFF0 0x5242 +#define mmACP_I2SMICSP_TFF0 0x5243 +#define mmACP_I2SMICSP_LRBR1 0x5246 +#define mmACP_I2SMICSP_RRBR1 0x5247 +#define mmACP_I2SMICSP_RER1 0x5248 +#define mmACP_I2SMICSP_TER1 0x5249 +#define mmACP_I2SMICSP_RCR1 0x524a +#define mmACP_I2SMICSP_TCR1 0x524b +#define mmACP_I2SMICSP_ISR1 0x524c +#define mmACP_I2SMICSP_IMR1 0x524d +#define mmACP_I2SMICSP_ROR1 0x524e +#define mmACP_I2SMICSP_TOR1 0x524f +#define mmACP_I2SMICSP_RFCR1 0x5250 +#define mmACP_I2SMICSP_TFCR1 0x5251 +#define mmACP_I2SMICSP_RFF1 0x5252 +#define mmACP_I2SMICSP_TFF1 0x5253 +#define mmACP_I2SMICSP_RXDMA 0x5254 +#define mmACP_I2SMICSP_RRXDMA 0x5255 +#define mmACP_I2SMICSP_TXDMA 0x5256 +#define mmACP_I2SMICSP_RTXDMA 0x5257 +#define mmACP_I2SMICSP_COMP_PARAM_2 0x5258 +#define mmACP_I2SMICSP_COMP_PARAM_1 0x5259 +#define mmACP_I2SMICSP_COMP_VERSION 0x525a +#define mmACP_I2SMICSP_COMP_TYPE 0x525b +#define mmACP_I2SBT_IER 0x525c +#define mmACP_I2SBT_IRER 0x525d +#define mmACP_I2SBT_ITER 0x525e +#define mmACP_I2SBT_CER 0x525f +#define mmACP_I2SBT_CCR 0x5260 +#define mmACP_I2SBT_RXFFR 0x5261 +#define mmACP_I2SBT_TXFFR 0x5262 +#define mmACP_I2SBT_LRBR0 0x5264 +#define mmACP_I2SBT_RRBR0 0x5265 +#define mmACP_I2SBT_RER0 0x5266 +#define mmACP_I2SBT_TER0 0x5267 +#define mmACP_I2SBT_RCR0 0x5268 +#define mmACP_I2SBT_TCR0 0x5269 +#define mmACP_I2SBT_ISR0 0x526a +#define mmACP_I2SBT_IMR0 0x526b +#define mmACP_I2SBT_ROR0 0x526c +#define mmACP_I2SBT_TOR0 0x526d +#define mmACP_I2SBT_RFCR0 0x526e +#define mmACP_I2SBT_TFCR0 0x526f +#define mmACP_I2SBT_RFF0 0x5270 +#define mmACP_I2SBT_TFF0 0x5271 +#define mmACP_I2SBT_LRBR1 0x5274 +#define mmACP_I2SBT_RRBR1 0x5275 +#define mmACP_I2SBT_RER1 0x5276 +#define mmACP_I2SBT_TER1 0x5277 +#define mmACP_I2SBT_RCR1 0x5278 +#define mmACP_I2SBT_TCR1 0x5279 +#define mmACP_I2SBT_ISR1 0x527a +#define mmACP_I2SBT_IMR1 0x527b +#define mmACP_I2SBT_ROR1 0x527c +#define mmACP_I2SBT_TOR1 0x527d +#define mmACP_I2SBT_RFCR1 0x527e +#define mmACP_I2SBT_TFCR1 0x527f +#define mmACP_I2SBT_RFF1 0x5280 +#define mmACP_I2SBT_TFF1 0x5281 +#define mmACP_I2SBT_RXDMA 0x5282 +#define mmACP_I2SBT_RRXDMA 0x5283 +#define mmACP_I2SBT_TXDMA 0x5284 +#define mmACP_I2SBT_RTXDMA 0x5285 +#define mmACP_I2SBT_COMP_PARAM_2 0x5286 +#define mmACP_I2SBT_COMP_PARAM_1 0x5287 +#define mmACP_I2SBT_COMP_VERSION 0x5288 +#define mmACP_I2SBT_COMP_TYPE 0x5289 + +#endif /* ACP_2_2_D_H */ diff --git a/sound/soc/amd/include/acp_2_2_enum.h b/sound/soc/amd/include/acp_2_2_enum.h new file mode 100644 index 000000000..f3577c851 --- /dev/null +++ b/sound/soc/amd/include/acp_2_2_enum.h @@ -0,0 +1,1068 @@ +/* + * ACP_2_2 Register documentation + * + * Copyright (C) 2014 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef ACP_2_2_ENUM_H +#define ACP_2_2_ENUM_H + +typedef enum DebugBlockId { + DBG_BLOCK_ID_RESERVED = 0x0, + DBG_BLOCK_ID_DBG = 0x1, + DBG_BLOCK_ID_VMC = 0x2, + DBG_BLOCK_ID_PDMA = 0x3, + DBG_BLOCK_ID_CG = 0x4, + DBG_BLOCK_ID_SRBM = 0x5, + DBG_BLOCK_ID_GRBM = 0x6, + DBG_BLOCK_ID_RLC = 0x7, + DBG_BLOCK_ID_CSC = 0x8, + DBG_BLOCK_ID_SEM = 0x9, + DBG_BLOCK_ID_IH = 0xa, + DBG_BLOCK_ID_SC = 0xb, + DBG_BLOCK_ID_SQ = 0xc, + DBG_BLOCK_ID_UVDU = 0xd, + DBG_BLOCK_ID_SQA = 0xe, + DBG_BLOCK_ID_SDMA0 = 0xf, + DBG_BLOCK_ID_SDMA1 = 0x10, + DBG_BLOCK_ID_SPIM = 0x11, + DBG_BLOCK_ID_GDS = 0x12, + DBG_BLOCK_ID_VC0 = 0x13, + DBG_BLOCK_ID_VC1 = 0x14, + DBG_BLOCK_ID_PA0 = 0x15, + DBG_BLOCK_ID_PA1 = 0x16, + DBG_BLOCK_ID_CP0 = 0x17, + DBG_BLOCK_ID_CP1 = 0x18, + DBG_BLOCK_ID_CP2 = 0x19, + DBG_BLOCK_ID_XBR = 0x1a, + DBG_BLOCK_ID_UVDM = 0x1b, + DBG_BLOCK_ID_VGT0 = 0x1c, + DBG_BLOCK_ID_VGT1 = 0x1d, + DBG_BLOCK_ID_IA = 0x1e, + DBG_BLOCK_ID_SXM0 = 0x1f, + DBG_BLOCK_ID_SXM1 = 0x20, + DBG_BLOCK_ID_SCT0 = 0x21, + DBG_BLOCK_ID_SCT1 = 0x22, + DBG_BLOCK_ID_SPM0 = 0x23, + DBG_BLOCK_ID_SPM1 = 0x24, + DBG_BLOCK_ID_UNUSED0 = 0x25, + DBG_BLOCK_ID_UNUSED1 = 0x26, + DBG_BLOCK_ID_TCAA = 0x27, + DBG_BLOCK_ID_TCAB = 0x28, + DBG_BLOCK_ID_TCCA = 0x29, + DBG_BLOCK_ID_TCCB = 0x2a, + DBG_BLOCK_ID_MCC0 = 0x2b, + DBG_BLOCK_ID_MCC1 = 0x2c, + DBG_BLOCK_ID_MCC2 = 0x2d, + DBG_BLOCK_ID_MCC3 = 0x2e, + DBG_BLOCK_ID_SXS0 = 0x2f, + DBG_BLOCK_ID_SXS1 = 0x30, + DBG_BLOCK_ID_SXS2 = 0x31, + DBG_BLOCK_ID_SXS3 = 0x32, + DBG_BLOCK_ID_SXS4 = 0x33, + DBG_BLOCK_ID_SXS5 = 0x34, + DBG_BLOCK_ID_SXS6 = 0x35, + DBG_BLOCK_ID_SXS7 = 0x36, + DBG_BLOCK_ID_SXS8 = 0x37, + DBG_BLOCK_ID_SXS9 = 0x38, + DBG_BLOCK_ID_BCI0 = 0x39, + DBG_BLOCK_ID_BCI1 = 0x3a, + DBG_BLOCK_ID_BCI2 = 0x3b, + DBG_BLOCK_ID_BCI3 = 0x3c, + DBG_BLOCK_ID_MCB = 0x3d, + DBG_BLOCK_ID_UNUSED6 = 0x3e, + DBG_BLOCK_ID_SQA00 = 0x3f, + DBG_BLOCK_ID_SQA01 = 0x40, + DBG_BLOCK_ID_SQA02 = 0x41, + DBG_BLOCK_ID_SQA10 = 0x42, + DBG_BLOCK_ID_SQA11 = 0x43, + DBG_BLOCK_ID_SQA12 = 0x44, + DBG_BLOCK_ID_UNUSED7 = 0x45, + DBG_BLOCK_ID_UNUSED8 = 0x46, + DBG_BLOCK_ID_SQB00 = 0x47, + DBG_BLOCK_ID_SQB01 = 0x48, + DBG_BLOCK_ID_SQB10 = 0x49, + DBG_BLOCK_ID_SQB11 = 0x4a, + DBG_BLOCK_ID_SQ00 = 0x4b, + DBG_BLOCK_ID_SQ01 = 0x4c, + DBG_BLOCK_ID_SQ10 = 0x4d, + DBG_BLOCK_ID_SQ11 = 0x4e, + DBG_BLOCK_ID_CB00 = 0x4f, + DBG_BLOCK_ID_CB01 = 0x50, + DBG_BLOCK_ID_CB02 = 0x51, + DBG_BLOCK_ID_CB03 = 0x52, + DBG_BLOCK_ID_CB04 = 0x53, + DBG_BLOCK_ID_UNUSED9 = 0x54, + DBG_BLOCK_ID_UNUSED10 = 0x55, + DBG_BLOCK_ID_UNUSED11 = 0x56, + DBG_BLOCK_ID_CB10 = 0x57, + DBG_BLOCK_ID_CB11 = 0x58, + DBG_BLOCK_ID_CB12 = 0x59, + DBG_BLOCK_ID_CB13 = 0x5a, + DBG_BLOCK_ID_CB14 = 0x5b, + DBG_BLOCK_ID_UNUSED12 = 0x5c, + DBG_BLOCK_ID_UNUSED13 = 0x5d, + DBG_BLOCK_ID_UNUSED14 = 0x5e, + DBG_BLOCK_ID_TCP0 = 0x5f, + DBG_BLOCK_ID_TCP1 = 0x60, + DBG_BLOCK_ID_TCP2 = 0x61, + DBG_BLOCK_ID_TCP3 = 0x62, + DBG_BLOCK_ID_TCP4 = 0x63, + DBG_BLOCK_ID_TCP5 = 0x64, + DBG_BLOCK_ID_TCP6 = 0x65, + DBG_BLOCK_ID_TCP7 = 0x66, + DBG_BLOCK_ID_TCP8 = 0x67, + DBG_BLOCK_ID_TCP9 = 0x68, + DBG_BLOCK_ID_TCP10 = 0x69, + DBG_BLOCK_ID_TCP11 = 0x6a, + DBG_BLOCK_ID_TCP12 = 0x6b, + DBG_BLOCK_ID_TCP13 = 0x6c, + DBG_BLOCK_ID_TCP14 = 0x6d, + DBG_BLOCK_ID_TCP15 = 0x6e, + DBG_BLOCK_ID_TCP16 = 0x6f, + DBG_BLOCK_ID_TCP17 = 0x70, + DBG_BLOCK_ID_TCP18 = 0x71, + DBG_BLOCK_ID_TCP19 = 0x72, + DBG_BLOCK_ID_TCP20 = 0x73, + DBG_BLOCK_ID_TCP21 = 0x74, + DBG_BLOCK_ID_TCP22 = 0x75, + DBG_BLOCK_ID_TCP23 = 0x76, + DBG_BLOCK_ID_TCP_RESERVED0 = 0x77, + DBG_BLOCK_ID_TCP_RESERVED1 = 0x78, + DBG_BLOCK_ID_TCP_RESERVED2 = 0x79, + DBG_BLOCK_ID_TCP_RESERVED3 = 0x7a, + DBG_BLOCK_ID_TCP_RESERVED4 = 0x7b, + DBG_BLOCK_ID_TCP_RESERVED5 = 0x7c, + DBG_BLOCK_ID_TCP_RESERVED6 = 0x7d, + DBG_BLOCK_ID_TCP_RESERVED7 = 0x7e, + DBG_BLOCK_ID_DB00 = 0x7f, + DBG_BLOCK_ID_DB01 = 0x80, + DBG_BLOCK_ID_DB02 = 0x81, + DBG_BLOCK_ID_DB03 = 0x82, + DBG_BLOCK_ID_DB04 = 0x83, + DBG_BLOCK_ID_UNUSED15 = 0x84, + DBG_BLOCK_ID_UNUSED16 = 0x85, + DBG_BLOCK_ID_UNUSED17 = 0x86, + DBG_BLOCK_ID_DB10 = 0x87, + DBG_BLOCK_ID_DB11 = 0x88, + DBG_BLOCK_ID_DB12 = 0x89, + DBG_BLOCK_ID_DB13 = 0x8a, + DBG_BLOCK_ID_DB14 = 0x8b, + DBG_BLOCK_ID_UNUSED18 = 0x8c, + DBG_BLOCK_ID_UNUSED19 = 0x8d, + DBG_BLOCK_ID_UNUSED20 = 0x8e, + DBG_BLOCK_ID_TCC0 = 0x8f, + DBG_BLOCK_ID_TCC1 = 0x90, + DBG_BLOCK_ID_TCC2 = 0x91, + DBG_BLOCK_ID_TCC3 = 0x92, + DBG_BLOCK_ID_TCC4 = 0x93, + DBG_BLOCK_ID_TCC5 = 0x94, + DBG_BLOCK_ID_TCC6 = 0x95, + DBG_BLOCK_ID_TCC7 = 0x96, + DBG_BLOCK_ID_SPS00 = 0x97, + DBG_BLOCK_ID_SPS01 = 0x98, + DBG_BLOCK_ID_SPS02 = 0x99, + DBG_BLOCK_ID_SPS10 = 0x9a, + DBG_BLOCK_ID_SPS11 = 0x9b, + DBG_BLOCK_ID_SPS12 = 0x9c, + DBG_BLOCK_ID_UNUSED21 = 0x9d, + DBG_BLOCK_ID_UNUSED22 = 0x9e, + DBG_BLOCK_ID_TA00 = 0x9f, + DBG_BLOCK_ID_TA01 = 0xa0, + DBG_BLOCK_ID_TA02 = 0xa1, + DBG_BLOCK_ID_TA03 = 0xa2, + DBG_BLOCK_ID_TA04 = 0xa3, + DBG_BLOCK_ID_TA05 = 0xa4, + DBG_BLOCK_ID_TA06 = 0xa5, + DBG_BLOCK_ID_TA07 = 0xa6, + DBG_BLOCK_ID_TA08 = 0xa7, + DBG_BLOCK_ID_TA09 = 0xa8, + DBG_BLOCK_ID_TA0A = 0xa9, + DBG_BLOCK_ID_TA0B = 0xaa, + DBG_BLOCK_ID_UNUSED23 = 0xab, + DBG_BLOCK_ID_UNUSED24 = 0xac, + DBG_BLOCK_ID_UNUSED25 = 0xad, + DBG_BLOCK_ID_UNUSED26 = 0xae, + DBG_BLOCK_ID_TA10 = 0xaf, + DBG_BLOCK_ID_TA11 = 0xb0, + DBG_BLOCK_ID_TA12 = 0xb1, + DBG_BLOCK_ID_TA13 = 0xb2, + DBG_BLOCK_ID_TA14 = 0xb3, + DBG_BLOCK_ID_TA15 = 0xb4, + DBG_BLOCK_ID_TA16 = 0xb5, + DBG_BLOCK_ID_TA17 = 0xb6, + DBG_BLOCK_ID_TA18 = 0xb7, + DBG_BLOCK_ID_TA19 = 0xb8, + DBG_BLOCK_ID_TA1A = 0xb9, + DBG_BLOCK_ID_TA1B = 0xba, + DBG_BLOCK_ID_UNUSED27 = 0xbb, + DBG_BLOCK_ID_UNUSED28 = 0xbc, + DBG_BLOCK_ID_UNUSED29 = 0xbd, + DBG_BLOCK_ID_UNUSED30 = 0xbe, + DBG_BLOCK_ID_TD00 = 0xbf, + DBG_BLOCK_ID_TD01 = 0xc0, + DBG_BLOCK_ID_TD02 = 0xc1, + DBG_BLOCK_ID_TD03 = 0xc2, + DBG_BLOCK_ID_TD04 = 0xc3, + DBG_BLOCK_ID_TD05 = 0xc4, + DBG_BLOCK_ID_TD06 = 0xc5, + DBG_BLOCK_ID_TD07 = 0xc6, + DBG_BLOCK_ID_TD08 = 0xc7, + DBG_BLOCK_ID_TD09 = 0xc8, + DBG_BLOCK_ID_TD0A = 0xc9, + DBG_BLOCK_ID_TD0B = 0xca, + DBG_BLOCK_ID_UNUSED31 = 0xcb, + DBG_BLOCK_ID_UNUSED32 = 0xcc, + DBG_BLOCK_ID_UNUSED33 = 0xcd, + DBG_BLOCK_ID_UNUSED34 = 0xce, + DBG_BLOCK_ID_TD10 = 0xcf, + DBG_BLOCK_ID_TD11 = 0xd0, + DBG_BLOCK_ID_TD12 = 0xd1, + DBG_BLOCK_ID_TD13 = 0xd2, + DBG_BLOCK_ID_TD14 = 0xd3, + DBG_BLOCK_ID_TD15 = 0xd4, + DBG_BLOCK_ID_TD16 = 0xd5, + DBG_BLOCK_ID_TD17 = 0xd6, + DBG_BLOCK_ID_TD18 = 0xd7, + DBG_BLOCK_ID_TD19 = 0xd8, + DBG_BLOCK_ID_TD1A = 0xd9, + DBG_BLOCK_ID_TD1B = 0xda, + DBG_BLOCK_ID_UNUSED35 = 0xdb, + DBG_BLOCK_ID_UNUSED36 = 0xdc, + DBG_BLOCK_ID_UNUSED37 = 0xdd, + DBG_BLOCK_ID_UNUSED38 = 0xde, + DBG_BLOCK_ID_LDS00 = 0xdf, + DBG_BLOCK_ID_LDS01 = 0xe0, + DBG_BLOCK_ID_LDS02 = 0xe1, + DBG_BLOCK_ID_LDS03 = 0xe2, + DBG_BLOCK_ID_LDS04 = 0xe3, + DBG_BLOCK_ID_LDS05 = 0xe4, + DBG_BLOCK_ID_LDS06 = 0xe5, + DBG_BLOCK_ID_LDS07 = 0xe6, + DBG_BLOCK_ID_LDS08 = 0xe7, + DBG_BLOCK_ID_LDS09 = 0xe8, + DBG_BLOCK_ID_LDS0A = 0xe9, + DBG_BLOCK_ID_LDS0B = 0xea, + DBG_BLOCK_ID_UNUSED39 = 0xeb, + DBG_BLOCK_ID_UNUSED40 = 0xec, + DBG_BLOCK_ID_UNUSED41 = 0xed, + DBG_BLOCK_ID_UNUSED42 = 0xee, + DBG_BLOCK_ID_LDS10 = 0xef, + DBG_BLOCK_ID_LDS11 = 0xf0, + DBG_BLOCK_ID_LDS12 = 0xf1, + DBG_BLOCK_ID_LDS13 = 0xf2, + DBG_BLOCK_ID_LDS14 = 0xf3, + DBG_BLOCK_ID_LDS15 = 0xf4, + DBG_BLOCK_ID_LDS16 = 0xf5, + DBG_BLOCK_ID_LDS17 = 0xf6, + DBG_BLOCK_ID_LDS18 = 0xf7, + DBG_BLOCK_ID_LDS19 = 0xf8, + DBG_BLOCK_ID_LDS1A = 0xf9, + DBG_BLOCK_ID_LDS1B = 0xfa, + DBG_BLOCK_ID_UNUSED43 = 0xfb, + DBG_BLOCK_ID_UNUSED44 = 0xfc, + DBG_BLOCK_ID_UNUSED45 = 0xfd, + DBG_BLOCK_ID_UNUSED46 = 0xfe, +} DebugBlockId; +typedef enum DebugBlockId_BY2 { + DBG_BLOCK_ID_RESERVED_BY2 = 0x0, + DBG_BLOCK_ID_VMC_BY2 = 0x1, + DBG_BLOCK_ID_UNUSED0_BY2 = 0x2, + DBG_BLOCK_ID_GRBM_BY2 = 0x3, + DBG_BLOCK_ID_CSC_BY2 = 0x4, + DBG_BLOCK_ID_IH_BY2 = 0x5, + DBG_BLOCK_ID_SQ_BY2 = 0x6, + DBG_BLOCK_ID_UVD_BY2 = 0x7, + DBG_BLOCK_ID_SDMA0_BY2 = 0x8, + DBG_BLOCK_ID_SPIM_BY2 = 0x9, + DBG_BLOCK_ID_VC0_BY2 = 0xa, + DBG_BLOCK_ID_PA_BY2 = 0xb, + DBG_BLOCK_ID_CP0_BY2 = 0xc, + DBG_BLOCK_ID_CP2_BY2 = 0xd, + DBG_BLOCK_ID_PC0_BY2 = 0xe, + DBG_BLOCK_ID_BCI0_BY2 = 0xf, + DBG_BLOCK_ID_SXM0_BY2 = 0x10, + DBG_BLOCK_ID_SCT0_BY2 = 0x11, + DBG_BLOCK_ID_SPM0_BY2 = 0x12, + DBG_BLOCK_ID_BCI2_BY2 = 0x13, + DBG_BLOCK_ID_TCA_BY2 = 0x14, + DBG_BLOCK_ID_TCCA_BY2 = 0x15, + DBG_BLOCK_ID_MCC_BY2 = 0x16, + DBG_BLOCK_ID_MCC2_BY2 = 0x17, + DBG_BLOCK_ID_MCD_BY2 = 0x18, + DBG_BLOCK_ID_MCD2_BY2 = 0x19, + DBG_BLOCK_ID_MCD4_BY2 = 0x1a, + DBG_BLOCK_ID_MCB_BY2 = 0x1b, + DBG_BLOCK_ID_SQA_BY2 = 0x1c, + DBG_BLOCK_ID_SQA02_BY2 = 0x1d, + DBG_BLOCK_ID_SQA11_BY2 = 0x1e, + DBG_BLOCK_ID_UNUSED8_BY2 = 0x1f, + DBG_BLOCK_ID_SQB_BY2 = 0x20, + DBG_BLOCK_ID_SQB10_BY2 = 0x21, + DBG_BLOCK_ID_UNUSED10_BY2 = 0x22, + DBG_BLOCK_ID_UNUSED12_BY2 = 0x23, + DBG_BLOCK_ID_CB_BY2 = 0x24, + DBG_BLOCK_ID_CB02_BY2 = 0x25, + DBG_BLOCK_ID_CB10_BY2 = 0x26, + DBG_BLOCK_ID_CB12_BY2 = 0x27, + DBG_BLOCK_ID_SXS_BY2 = 0x28, + DBG_BLOCK_ID_SXS2_BY2 = 0x29, + DBG_BLOCK_ID_SXS4_BY2 = 0x2a, + DBG_BLOCK_ID_SXS6_BY2 = 0x2b, + DBG_BLOCK_ID_DB_BY2 = 0x2c, + DBG_BLOCK_ID_DB02_BY2 = 0x2d, + DBG_BLOCK_ID_DB10_BY2 = 0x2e, + DBG_BLOCK_ID_DB12_BY2 = 0x2f, + DBG_BLOCK_ID_TCP_BY2 = 0x30, + DBG_BLOCK_ID_TCP2_BY2 = 0x31, + DBG_BLOCK_ID_TCP4_BY2 = 0x32, + DBG_BLOCK_ID_TCP6_BY2 = 0x33, + DBG_BLOCK_ID_TCP8_BY2 = 0x34, + DBG_BLOCK_ID_TCP10_BY2 = 0x35, + DBG_BLOCK_ID_TCP12_BY2 = 0x36, + DBG_BLOCK_ID_TCP14_BY2 = 0x37, + DBG_BLOCK_ID_TCP16_BY2 = 0x38, + DBG_BLOCK_ID_TCP18_BY2 = 0x39, + DBG_BLOCK_ID_TCP20_BY2 = 0x3a, + DBG_BLOCK_ID_TCP22_BY2 = 0x3b, + DBG_BLOCK_ID_TCP_RESERVED0_BY2 = 0x3c, + DBG_BLOCK_ID_TCP_RESERVED2_BY2 = 0x3d, + DBG_BLOCK_ID_TCP_RESERVED4_BY2 = 0x3e, + DBG_BLOCK_ID_TCP_RESERVED6_BY2 = 0x3f, + DBG_BLOCK_ID_TCC_BY2 = 0x40, + DBG_BLOCK_ID_TCC2_BY2 = 0x41, + DBG_BLOCK_ID_TCC4_BY2 = 0x42, + DBG_BLOCK_ID_TCC6_BY2 = 0x43, + DBG_BLOCK_ID_SPS_BY2 = 0x44, + DBG_BLOCK_ID_SPS02_BY2 = 0x45, + DBG_BLOCK_ID_SPS11_BY2 = 0x46, + DBG_BLOCK_ID_UNUSED14_BY2 = 0x47, + DBG_BLOCK_ID_TA_BY2 = 0x48, + DBG_BLOCK_ID_TA02_BY2 = 0x49, + DBG_BLOCK_ID_TA04_BY2 = 0x4a, + DBG_BLOCK_ID_TA06_BY2 = 0x4b, + DBG_BLOCK_ID_TA08_BY2 = 0x4c, + DBG_BLOCK_ID_TA0A_BY2 = 0x4d, + DBG_BLOCK_ID_UNUSED20_BY2 = 0x4e, + DBG_BLOCK_ID_UNUSED22_BY2 = 0x4f, + DBG_BLOCK_ID_TA10_BY2 = 0x50, + DBG_BLOCK_ID_TA12_BY2 = 0x51, + DBG_BLOCK_ID_TA14_BY2 = 0x52, + DBG_BLOCK_ID_TA16_BY2 = 0x53, + DBG_BLOCK_ID_TA18_BY2 = 0x54, + DBG_BLOCK_ID_TA1A_BY2 = 0x55, + DBG_BLOCK_ID_UNUSED24_BY2 = 0x56, + DBG_BLOCK_ID_UNUSED26_BY2 = 0x57, + DBG_BLOCK_ID_TD_BY2 = 0x58, + DBG_BLOCK_ID_TD02_BY2 = 0x59, + DBG_BLOCK_ID_TD04_BY2 = 0x5a, + DBG_BLOCK_ID_TD06_BY2 = 0x5b, + DBG_BLOCK_ID_TD08_BY2 = 0x5c, + DBG_BLOCK_ID_TD0A_BY2 = 0x5d, + DBG_BLOCK_ID_UNUSED28_BY2 = 0x5e, + DBG_BLOCK_ID_UNUSED30_BY2 = 0x5f, + DBG_BLOCK_ID_TD10_BY2 = 0x60, + DBG_BLOCK_ID_TD12_BY2 = 0x61, + DBG_BLOCK_ID_TD14_BY2 = 0x62, + DBG_BLOCK_ID_TD16_BY2 = 0x63, + DBG_BLOCK_ID_TD18_BY2 = 0x64, + DBG_BLOCK_ID_TD1A_BY2 = 0x65, + DBG_BLOCK_ID_UNUSED32_BY2 = 0x66, + DBG_BLOCK_ID_UNUSED34_BY2 = 0x67, + DBG_BLOCK_ID_LDS_BY2 = 0x68, + DBG_BLOCK_ID_LDS02_BY2 = 0x69, + DBG_BLOCK_ID_LDS04_BY2 = 0x6a, + DBG_BLOCK_ID_LDS06_BY2 = 0x6b, + DBG_BLOCK_ID_LDS08_BY2 = 0x6c, + DBG_BLOCK_ID_LDS0A_BY2 = 0x6d, + DBG_BLOCK_ID_UNUSED36_BY2 = 0x6e, + DBG_BLOCK_ID_UNUSED38_BY2 = 0x6f, + DBG_BLOCK_ID_LDS10_BY2 = 0x70, + DBG_BLOCK_ID_LDS12_BY2 = 0x71, + DBG_BLOCK_ID_LDS14_BY2 = 0x72, + DBG_BLOCK_ID_LDS16_BY2 = 0x73, + DBG_BLOCK_ID_LDS18_BY2 = 0x74, + DBG_BLOCK_ID_LDS1A_BY2 = 0x75, + DBG_BLOCK_ID_UNUSED40_BY2 = 0x76, + DBG_BLOCK_ID_UNUSED42_BY2 = 0x77, +} DebugBlockId_BY2; +typedef enum DebugBlockId_BY4 { + DBG_BLOCK_ID_RESERVED_BY4 = 0x0, + DBG_BLOCK_ID_UNUSED0_BY4 = 0x1, + DBG_BLOCK_ID_CSC_BY4 = 0x2, + DBG_BLOCK_ID_SQ_BY4 = 0x3, + DBG_BLOCK_ID_SDMA0_BY4 = 0x4, + DBG_BLOCK_ID_VC0_BY4 = 0x5, + DBG_BLOCK_ID_CP0_BY4 = 0x6, + DBG_BLOCK_ID_UNUSED1_BY4 = 0x7, + DBG_BLOCK_ID_SXM0_BY4 = 0x8, + DBG_BLOCK_ID_SPM0_BY4 = 0x9, + DBG_BLOCK_ID_TCAA_BY4 = 0xa, + DBG_BLOCK_ID_MCC_BY4 = 0xb, + DBG_BLOCK_ID_MCD_BY4 = 0xc, + DBG_BLOCK_ID_MCD4_BY4 = 0xd, + DBG_BLOCK_ID_SQA_BY4 = 0xe, + DBG_BLOCK_ID_SQA11_BY4 = 0xf, + DBG_BLOCK_ID_SQB_BY4 = 0x10, + DBG_BLOCK_ID_UNUSED10_BY4 = 0x11, + DBG_BLOCK_ID_CB_BY4 = 0x12, + DBG_BLOCK_ID_CB10_BY4 = 0x13, + DBG_BLOCK_ID_SXS_BY4 = 0x14, + DBG_BLOCK_ID_SXS4_BY4 = 0x15, + DBG_BLOCK_ID_DB_BY4 = 0x16, + DBG_BLOCK_ID_DB10_BY4 = 0x17, + DBG_BLOCK_ID_TCP_BY4 = 0x18, + DBG_BLOCK_ID_TCP4_BY4 = 0x19, + DBG_BLOCK_ID_TCP8_BY4 = 0x1a, + DBG_BLOCK_ID_TCP12_BY4 = 0x1b, + DBG_BLOCK_ID_TCP16_BY4 = 0x1c, + DBG_BLOCK_ID_TCP20_BY4 = 0x1d, + DBG_BLOCK_ID_TCP_RESERVED0_BY4 = 0x1e, + DBG_BLOCK_ID_TCP_RESERVED4_BY4 = 0x1f, + DBG_BLOCK_ID_TCC_BY4 = 0x20, + DBG_BLOCK_ID_TCC4_BY4 = 0x21, + DBG_BLOCK_ID_SPS_BY4 = 0x22, + DBG_BLOCK_ID_SPS11_BY4 = 0x23, + DBG_BLOCK_ID_TA_BY4 = 0x24, + DBG_BLOCK_ID_TA04_BY4 = 0x25, + DBG_BLOCK_ID_TA08_BY4 = 0x26, + DBG_BLOCK_ID_UNUSED20_BY4 = 0x27, + DBG_BLOCK_ID_TA10_BY4 = 0x28, + DBG_BLOCK_ID_TA14_BY4 = 0x29, + DBG_BLOCK_ID_TA18_BY4 = 0x2a, + DBG_BLOCK_ID_UNUSED24_BY4 = 0x2b, + DBG_BLOCK_ID_TD_BY4 = 0x2c, + DBG_BLOCK_ID_TD04_BY4 = 0x2d, + DBG_BLOCK_ID_TD08_BY4 = 0x2e, + DBG_BLOCK_ID_UNUSED28_BY4 = 0x2f, + DBG_BLOCK_ID_TD10_BY4 = 0x30, + DBG_BLOCK_ID_TD14_BY4 = 0x31, + DBG_BLOCK_ID_TD18_BY4 = 0x32, + DBG_BLOCK_ID_UNUSED32_BY4 = 0x33, + DBG_BLOCK_ID_LDS_BY4 = 0x34, + DBG_BLOCK_ID_LDS04_BY4 = 0x35, + DBG_BLOCK_ID_LDS08_BY4 = 0x36, + DBG_BLOCK_ID_UNUSED36_BY4 = 0x37, + DBG_BLOCK_ID_LDS10_BY4 = 0x38, + DBG_BLOCK_ID_LDS14_BY4 = 0x39, + DBG_BLOCK_ID_LDS18_BY4 = 0x3a, + DBG_BLOCK_ID_UNUSED40_BY4 = 0x3b, +} DebugBlockId_BY4; +typedef enum DebugBlockId_BY8 { + DBG_BLOCK_ID_RESERVED_BY8 = 0x0, + DBG_BLOCK_ID_CSC_BY8 = 0x1, + DBG_BLOCK_ID_SDMA0_BY8 = 0x2, + DBG_BLOCK_ID_CP0_BY8 = 0x3, + DBG_BLOCK_ID_SXM0_BY8 = 0x4, + DBG_BLOCK_ID_TCA_BY8 = 0x5, + DBG_BLOCK_ID_MCD_BY8 = 0x6, + DBG_BLOCK_ID_SQA_BY8 = 0x7, + DBG_BLOCK_ID_SQB_BY8 = 0x8, + DBG_BLOCK_ID_CB_BY8 = 0x9, + DBG_BLOCK_ID_SXS_BY8 = 0xa, + DBG_BLOCK_ID_DB_BY8 = 0xb, + DBG_BLOCK_ID_TCP_BY8 = 0xc, + DBG_BLOCK_ID_TCP8_BY8 = 0xd, + DBG_BLOCK_ID_TCP16_BY8 = 0xe, + DBG_BLOCK_ID_TCP_RESERVED0_BY8 = 0xf, + DBG_BLOCK_ID_TCC_BY8 = 0x10, + DBG_BLOCK_ID_SPS_BY8 = 0x11, + DBG_BLOCK_ID_TA_BY8 = 0x12, + DBG_BLOCK_ID_TA08_BY8 = 0x13, + DBG_BLOCK_ID_TA10_BY8 = 0x14, + DBG_BLOCK_ID_TA18_BY8 = 0x15, + DBG_BLOCK_ID_TD_BY8 = 0x16, + DBG_BLOCK_ID_TD08_BY8 = 0x17, + DBG_BLOCK_ID_TD10_BY8 = 0x18, + DBG_BLOCK_ID_TD18_BY8 = 0x19, + DBG_BLOCK_ID_LDS_BY8 = 0x1a, + DBG_BLOCK_ID_LDS08_BY8 = 0x1b, + DBG_BLOCK_ID_LDS10_BY8 = 0x1c, + DBG_BLOCK_ID_LDS18_BY8 = 0x1d, +} DebugBlockId_BY8; +typedef enum DebugBlockId_BY16 { + DBG_BLOCK_ID_RESERVED_BY16 = 0x0, + DBG_BLOCK_ID_SDMA0_BY16 = 0x1, + DBG_BLOCK_ID_SXM_BY16 = 0x2, + DBG_BLOCK_ID_MCD_BY16 = 0x3, + DBG_BLOCK_ID_SQB_BY16 = 0x4, + DBG_BLOCK_ID_SXS_BY16 = 0x5, + DBG_BLOCK_ID_TCP_BY16 = 0x6, + DBG_BLOCK_ID_TCP16_BY16 = 0x7, + DBG_BLOCK_ID_TCC_BY16 = 0x8, + DBG_BLOCK_ID_TA_BY16 = 0x9, + DBG_BLOCK_ID_TA10_BY16 = 0xa, + DBG_BLOCK_ID_TD_BY16 = 0xb, + DBG_BLOCK_ID_TD10_BY16 = 0xc, + DBG_BLOCK_ID_LDS_BY16 = 0xd, + DBG_BLOCK_ID_LDS10_BY16 = 0xe, +} DebugBlockId_BY16; +typedef enum SurfaceEndian { + ENDIAN_NONE = 0x0, + ENDIAN_8IN16 = 0x1, + ENDIAN_8IN32 = 0x2, + ENDIAN_8IN64 = 0x3, +} SurfaceEndian; +typedef enum ArrayMode { + ARRAY_LINEAR_GENERAL = 0x0, + ARRAY_LINEAR_ALIGNED = 0x1, + ARRAY_1D_TILED_THIN1 = 0x2, + ARRAY_1D_TILED_THICK = 0x3, + ARRAY_2D_TILED_THIN1 = 0x4, + ARRAY_PRT_TILED_THIN1 = 0x5, + ARRAY_PRT_2D_TILED_THIN1 = 0x6, + ARRAY_2D_TILED_THICK = 0x7, + ARRAY_2D_TILED_XTHICK = 0x8, + ARRAY_PRT_TILED_THICK = 0x9, + ARRAY_PRT_2D_TILED_THICK = 0xa, + ARRAY_PRT_3D_TILED_THIN1 = 0xb, + ARRAY_3D_TILED_THIN1 = 0xc, + ARRAY_3D_TILED_THICK = 0xd, + ARRAY_3D_TILED_XTHICK = 0xe, + ARRAY_PRT_3D_TILED_THICK = 0xf, +} ArrayMode; +typedef enum PipeTiling { + CONFIG_1_PIPE = 0x0, + CONFIG_2_PIPE = 0x1, + CONFIG_4_PIPE = 0x2, + CONFIG_8_PIPE = 0x3, +} PipeTiling; +typedef enum BankTiling { + CONFIG_4_BANK = 0x0, + CONFIG_8_BANK = 0x1, +} BankTiling; +typedef enum GroupInterleave { + CONFIG_256B_GROUP = 0x0, + CONFIG_512B_GROUP = 0x1, +} GroupInterleave; +typedef enum RowTiling { + CONFIG_1KB_ROW = 0x0, + CONFIG_2KB_ROW = 0x1, + CONFIG_4KB_ROW = 0x2, + CONFIG_8KB_ROW = 0x3, + CONFIG_1KB_ROW_OPT = 0x4, + CONFIG_2KB_ROW_OPT = 0x5, + CONFIG_4KB_ROW_OPT = 0x6, + CONFIG_8KB_ROW_OPT = 0x7, +} RowTiling; +typedef enum BankSwapBytes { + CONFIG_128B_SWAPS = 0x0, + CONFIG_256B_SWAPS = 0x1, + CONFIG_512B_SWAPS = 0x2, + CONFIG_1KB_SWAPS = 0x3, +} BankSwapBytes; +typedef enum SampleSplitBytes { + CONFIG_1KB_SPLIT = 0x0, + CONFIG_2KB_SPLIT = 0x1, + CONFIG_4KB_SPLIT = 0x2, + CONFIG_8KB_SPLIT = 0x3, +} SampleSplitBytes; +typedef enum NumPipes { + ADDR_CONFIG_1_PIPE = 0x0, + ADDR_CONFIG_2_PIPE = 0x1, + ADDR_CONFIG_4_PIPE = 0x2, + ADDR_CONFIG_8_PIPE = 0x3, +} NumPipes; +typedef enum PipeInterleaveSize { + ADDR_CONFIG_PIPE_INTERLEAVE_256B = 0x0, + ADDR_CONFIG_PIPE_INTERLEAVE_512B = 0x1, +} PipeInterleaveSize; +typedef enum BankInterleaveSize { + ADDR_CONFIG_BANK_INTERLEAVE_1 = 0x0, + ADDR_CONFIG_BANK_INTERLEAVE_2 = 0x1, + ADDR_CONFIG_BANK_INTERLEAVE_4 = 0x2, + ADDR_CONFIG_BANK_INTERLEAVE_8 = 0x3, +} BankInterleaveSize; +typedef enum NumShaderEngines { + ADDR_CONFIG_1_SHADER_ENGINE = 0x0, + ADDR_CONFIG_2_SHADER_ENGINE = 0x1, +} NumShaderEngines; +typedef enum ShaderEngineTileSize { + ADDR_CONFIG_SE_TILE_16 = 0x0, + ADDR_CONFIG_SE_TILE_32 = 0x1, +} ShaderEngineTileSize; +typedef enum NumGPUs { + ADDR_CONFIG_1_GPU = 0x0, + ADDR_CONFIG_2_GPU = 0x1, + ADDR_CONFIG_4_GPU = 0x2, +} NumGPUs; +typedef enum MultiGPUTileSize { + ADDR_CONFIG_GPU_TILE_16 = 0x0, + ADDR_CONFIG_GPU_TILE_32 = 0x1, + ADDR_CONFIG_GPU_TILE_64 = 0x2, + ADDR_CONFIG_GPU_TILE_128 = 0x3, +} MultiGPUTileSize; +typedef enum RowSize { + ADDR_CONFIG_1KB_ROW = 0x0, + ADDR_CONFIG_2KB_ROW = 0x1, + ADDR_CONFIG_4KB_ROW = 0x2, +} RowSize; +typedef enum NumLowerPipes { + ADDR_CONFIG_1_LOWER_PIPES = 0x0, + ADDR_CONFIG_2_LOWER_PIPES = 0x1, +} NumLowerPipes; +typedef enum ColorTransform { + DCC_CT_AUTO = 0x0, + DCC_CT_NONE = 0x1, + ABGR_TO_A_BG_G_RB = 0x2, + BGRA_TO_BG_G_RB_A = 0x3, +} ColorTransform; +typedef enum CompareRef { + REF_NEVER = 0x0, + REF_LESS = 0x1, + REF_EQUAL = 0x2, + REF_LEQUAL = 0x3, + REF_GREATER = 0x4, + REF_NOTEQUAL = 0x5, + REF_GEQUAL = 0x6, + REF_ALWAYS = 0x7, +} CompareRef; +typedef enum ReadSize { + READ_256_BITS = 0x0, + READ_512_BITS = 0x1, +} ReadSize; +typedef enum DepthFormat { + DEPTH_INVALID = 0x0, + DEPTH_16 = 0x1, + DEPTH_X8_24 = 0x2, + DEPTH_8_24 = 0x3, + DEPTH_X8_24_FLOAT = 0x4, + DEPTH_8_24_FLOAT = 0x5, + DEPTH_32_FLOAT = 0x6, + DEPTH_X24_8_32_FLOAT = 0x7, +} DepthFormat; +typedef enum ZFormat { + Z_INVALID = 0x0, + Z_16 = 0x1, + Z_24 = 0x2, + Z_32_FLOAT = 0x3, +} ZFormat; +typedef enum StencilFormat { + STENCIL_INVALID = 0x0, + STENCIL_8 = 0x1, +} StencilFormat; +typedef enum CmaskMode { + CMASK_CLEAR_NONE = 0x0, + CMASK_CLEAR_ONE = 0x1, + CMASK_CLEAR_ALL = 0x2, + CMASK_ANY_EXPANDED = 0x3, + CMASK_ALPHA0_FRAG1 = 0x4, + CMASK_ALPHA0_FRAG2 = 0x5, + CMASK_ALPHA0_FRAG4 = 0x6, + CMASK_ALPHA0_FRAGS = 0x7, + CMASK_ALPHA1_FRAG1 = 0x8, + CMASK_ALPHA1_FRAG2 = 0x9, + CMASK_ALPHA1_FRAG4 = 0xa, + CMASK_ALPHA1_FRAGS = 0xb, + CMASK_ALPHAX_FRAG1 = 0xc, + CMASK_ALPHAX_FRAG2 = 0xd, + CMASK_ALPHAX_FRAG4 = 0xe, + CMASK_ALPHAX_FRAGS = 0xf, +} CmaskMode; +typedef enum QuadExportFormat { + EXPORT_UNUSED = 0x0, + EXPORT_32_R = 0x1, + EXPORT_32_GR = 0x2, + EXPORT_32_AR = 0x3, + EXPORT_FP16_ABGR = 0x4, + EXPORT_UNSIGNED16_ABGR = 0x5, + EXPORT_SIGNED16_ABGR = 0x6, + EXPORT_32_ABGR = 0x7, +} QuadExportFormat; +typedef enum QuadExportFormatOld { + EXPORT_4P_32BPC_ABGR = 0x0, + EXPORT_4P_16BPC_ABGR = 0x1, + EXPORT_4P_32BPC_GR = 0x2, + EXPORT_4P_32BPC_AR = 0x3, + EXPORT_2P_32BPC_ABGR = 0x4, + EXPORT_8P_32BPC_R = 0x5, +} QuadExportFormatOld; +typedef enum ColorFormat { + COLOR_INVALID = 0x0, + COLOR_8 = 0x1, + COLOR_16 = 0x2, + COLOR_8_8 = 0x3, + COLOR_32 = 0x4, + COLOR_16_16 = 0x5, + COLOR_10_11_11 = 0x6, + COLOR_11_11_10 = 0x7, + COLOR_10_10_10_2 = 0x8, + COLOR_2_10_10_10 = 0x9, + COLOR_8_8_8_8 = 0xa, + COLOR_32_32 = 0xb, + COLOR_16_16_16_16 = 0xc, + COLOR_RESERVED_13 = 0xd, + COLOR_32_32_32_32 = 0xe, + COLOR_RESERVED_15 = 0xf, + COLOR_5_6_5 = 0x10, + COLOR_1_5_5_5 = 0x11, + COLOR_5_5_5_1 = 0x12, + COLOR_4_4_4_4 = 0x13, + COLOR_8_24 = 0x14, + COLOR_24_8 = 0x15, + COLOR_X24_8_32_FLOAT = 0x16, + COLOR_RESERVED_23 = 0x17, +} ColorFormat; +typedef enum SurfaceFormat { + FMT_INVALID = 0x0, + FMT_8 = 0x1, + FMT_16 = 0x2, + FMT_8_8 = 0x3, + FMT_32 = 0x4, + FMT_16_16 = 0x5, + FMT_10_11_11 = 0x6, + FMT_11_11_10 = 0x7, + FMT_10_10_10_2 = 0x8, + FMT_2_10_10_10 = 0x9, + FMT_8_8_8_8 = 0xa, + FMT_32_32 = 0xb, + FMT_16_16_16_16 = 0xc, + FMT_32_32_32 = 0xd, + FMT_32_32_32_32 = 0xe, + FMT_RESERVED_4 = 0xf, + FMT_5_6_5 = 0x10, + FMT_1_5_5_5 = 0x11, + FMT_5_5_5_1 = 0x12, + FMT_4_4_4_4 = 0x13, + FMT_8_24 = 0x14, + FMT_24_8 = 0x15, + FMT_X24_8_32_FLOAT = 0x16, + FMT_RESERVED_33 = 0x17, + FMT_11_11_10_FLOAT = 0x18, + FMT_16_FLOAT = 0x19, + FMT_32_FLOAT = 0x1a, + FMT_16_16_FLOAT = 0x1b, + FMT_8_24_FLOAT = 0x1c, + FMT_24_8_FLOAT = 0x1d, + FMT_32_32_FLOAT = 0x1e, + FMT_10_11_11_FLOAT = 0x1f, + FMT_16_16_16_16_FLOAT = 0x20, + FMT_3_3_2 = 0x21, + FMT_6_5_5 = 0x22, + FMT_32_32_32_32_FLOAT = 0x23, + FMT_RESERVED_36 = 0x24, + FMT_1 = 0x25, + FMT_1_REVERSED = 0x26, + FMT_GB_GR = 0x27, + FMT_BG_RG = 0x28, + FMT_32_AS_8 = 0x29, + FMT_32_AS_8_8 = 0x2a, + FMT_5_9_9_9_SHAREDEXP = 0x2b, + FMT_8_8_8 = 0x2c, + FMT_16_16_16 = 0x2d, + FMT_16_16_16_FLOAT = 0x2e, + FMT_4_4 = 0x2f, + FMT_32_32_32_FLOAT = 0x30, + FMT_BC1 = 0x31, + FMT_BC2 = 0x32, + FMT_BC3 = 0x33, + FMT_BC4 = 0x34, + FMT_BC5 = 0x35, + FMT_BC6 = 0x36, + FMT_BC7 = 0x37, + FMT_32_AS_32_32_32_32 = 0x38, + FMT_APC3 = 0x39, + FMT_APC4 = 0x3a, + FMT_APC5 = 0x3b, + FMT_APC6 = 0x3c, + FMT_APC7 = 0x3d, + FMT_CTX1 = 0x3e, + FMT_RESERVED_63 = 0x3f, +} SurfaceFormat; +typedef enum BUF_DATA_FORMAT { + BUF_DATA_FORMAT_INVALID = 0x0, + BUF_DATA_FORMAT_8 = 0x1, + BUF_DATA_FORMAT_16 = 0x2, + BUF_DATA_FORMAT_8_8 = 0x3, + BUF_DATA_FORMAT_32 = 0x4, + BUF_DATA_FORMAT_16_16 = 0x5, + BUF_DATA_FORMAT_10_11_11 = 0x6, + BUF_DATA_FORMAT_11_11_10 = 0x7, + BUF_DATA_FORMAT_10_10_10_2 = 0x8, + BUF_DATA_FORMAT_2_10_10_10 = 0x9, + BUF_DATA_FORMAT_8_8_8_8 = 0xa, + BUF_DATA_FORMAT_32_32 = 0xb, + BUF_DATA_FORMAT_16_16_16_16 = 0xc, + BUF_DATA_FORMAT_32_32_32 = 0xd, + BUF_DATA_FORMAT_32_32_32_32 = 0xe, + BUF_DATA_FORMAT_RESERVED_15 = 0xf, +} BUF_DATA_FORMAT; +typedef enum IMG_DATA_FORMAT { + IMG_DATA_FORMAT_INVALID = 0x0, + IMG_DATA_FORMAT_8 = 0x1, + IMG_DATA_FORMAT_16 = 0x2, + IMG_DATA_FORMAT_8_8 = 0x3, + IMG_DATA_FORMAT_32 = 0x4, + IMG_DATA_FORMAT_16_16 = 0x5, + IMG_DATA_FORMAT_10_11_11 = 0x6, + IMG_DATA_FORMAT_11_11_10 = 0x7, + IMG_DATA_FORMAT_10_10_10_2 = 0x8, + IMG_DATA_FORMAT_2_10_10_10 = 0x9, + IMG_DATA_FORMAT_8_8_8_8 = 0xa, + IMG_DATA_FORMAT_32_32 = 0xb, + IMG_DATA_FORMAT_16_16_16_16 = 0xc, + IMG_DATA_FORMAT_32_32_32 = 0xd, + IMG_DATA_FORMAT_32_32_32_32 = 0xe, + IMG_DATA_FORMAT_RESERVED_15 = 0xf, + IMG_DATA_FORMAT_5_6_5 = 0x10, + IMG_DATA_FORMAT_1_5_5_5 = 0x11, + IMG_DATA_FORMAT_5_5_5_1 = 0x12, + IMG_DATA_FORMAT_4_4_4_4 = 0x13, + IMG_DATA_FORMAT_8_24 = 0x14, + IMG_DATA_FORMAT_24_8 = 0x15, + IMG_DATA_FORMAT_X24_8_32 = 0x16, + IMG_DATA_FORMAT_RESERVED_23 = 0x17, + IMG_DATA_FORMAT_RESERVED_24 = 0x18, + IMG_DATA_FORMAT_RESERVED_25 = 0x19, + IMG_DATA_FORMAT_RESERVED_26 = 0x1a, + IMG_DATA_FORMAT_RESERVED_27 = 0x1b, + IMG_DATA_FORMAT_RESERVED_28 = 0x1c, + IMG_DATA_FORMAT_RESERVED_29 = 0x1d, + IMG_DATA_FORMAT_RESERVED_30 = 0x1e, + IMG_DATA_FORMAT_RESERVED_31 = 0x1f, + IMG_DATA_FORMAT_GB_GR = 0x20, + IMG_DATA_FORMAT_BG_RG = 0x21, + IMG_DATA_FORMAT_5_9_9_9 = 0x22, + IMG_DATA_FORMAT_BC1 = 0x23, + IMG_DATA_FORMAT_BC2 = 0x24, + IMG_DATA_FORMAT_BC3 = 0x25, + IMG_DATA_FORMAT_BC4 = 0x26, + IMG_DATA_FORMAT_BC5 = 0x27, + IMG_DATA_FORMAT_BC6 = 0x28, + IMG_DATA_FORMAT_BC7 = 0x29, + IMG_DATA_FORMAT_RESERVED_42 = 0x2a, + IMG_DATA_FORMAT_RESERVED_43 = 0x2b, + IMG_DATA_FORMAT_FMASK8_S2_F1 = 0x2c, + IMG_DATA_FORMAT_FMASK8_S4_F1 = 0x2d, + IMG_DATA_FORMAT_FMASK8_S8_F1 = 0x2e, + IMG_DATA_FORMAT_FMASK8_S2_F2 = 0x2f, + IMG_DATA_FORMAT_FMASK8_S4_F2 = 0x30, + IMG_DATA_FORMAT_FMASK8_S4_F4 = 0x31, + IMG_DATA_FORMAT_FMASK16_S16_F1 = 0x32, + IMG_DATA_FORMAT_FMASK16_S8_F2 = 0x33, + IMG_DATA_FORMAT_FMASK32_S16_F2 = 0x34, + IMG_DATA_FORMAT_FMASK32_S8_F4 = 0x35, + IMG_DATA_FORMAT_FMASK32_S8_F8 = 0x36, + IMG_DATA_FORMAT_FMASK64_S16_F4 = 0x37, + IMG_DATA_FORMAT_FMASK64_S16_F8 = 0x38, + IMG_DATA_FORMAT_4_4 = 0x39, + IMG_DATA_FORMAT_6_5_5 = 0x3a, + IMG_DATA_FORMAT_1 = 0x3b, + IMG_DATA_FORMAT_1_REVERSED = 0x3c, + IMG_DATA_FORMAT_32_AS_8 = 0x3d, + IMG_DATA_FORMAT_32_AS_8_8 = 0x3e, + IMG_DATA_FORMAT_32_AS_32_32_32_32 = 0x3f, +} IMG_DATA_FORMAT; +typedef enum BUF_NUM_FORMAT { + BUF_NUM_FORMAT_UNORM = 0x0, + BUF_NUM_FORMAT_SNORM = 0x1, + BUF_NUM_FORMAT_USCALED = 0x2, + BUF_NUM_FORMAT_SSCALED = 0x3, + BUF_NUM_FORMAT_UINT = 0x4, + BUF_NUM_FORMAT_SINT = 0x5, + BUF_NUM_FORMAT_RESERVED_6 = 0x6, + BUF_NUM_FORMAT_FLOAT = 0x7, +} BUF_NUM_FORMAT; +typedef enum IMG_NUM_FORMAT { + IMG_NUM_FORMAT_UNORM = 0x0, + IMG_NUM_FORMAT_SNORM = 0x1, + IMG_NUM_FORMAT_USCALED = 0x2, + IMG_NUM_FORMAT_SSCALED = 0x3, + IMG_NUM_FORMAT_UINT = 0x4, + IMG_NUM_FORMAT_SINT = 0x5, + IMG_NUM_FORMAT_RESERVED_6 = 0x6, + IMG_NUM_FORMAT_FLOAT = 0x7, + IMG_NUM_FORMAT_RESERVED_8 = 0x8, + IMG_NUM_FORMAT_SRGB = 0x9, + IMG_NUM_FORMAT_RESERVED_10 = 0xa, + IMG_NUM_FORMAT_RESERVED_11 = 0xb, + IMG_NUM_FORMAT_RESERVED_12 = 0xc, + IMG_NUM_FORMAT_RESERVED_13 = 0xd, + IMG_NUM_FORMAT_RESERVED_14 = 0xe, + IMG_NUM_FORMAT_RESERVED_15 = 0xf, +} IMG_NUM_FORMAT; +typedef enum TileType { + ARRAY_COLOR_TILE = 0x0, + ARRAY_DEPTH_TILE = 0x1, +} TileType; +typedef enum NonDispTilingOrder { + ADDR_SURF_MICRO_TILING_DISPLAY = 0x0, + ADDR_SURF_MICRO_TILING_NON_DISPLAY = 0x1, +} NonDispTilingOrder; +typedef enum MicroTileMode { + ADDR_SURF_DISPLAY_MICRO_TILING = 0x0, + ADDR_SURF_THIN_MICRO_TILING = 0x1, + ADDR_SURF_DEPTH_MICRO_TILING = 0x2, + ADDR_SURF_ROTATED_MICRO_TILING = 0x3, + ADDR_SURF_THICK_MICRO_TILING = 0x4, +} MicroTileMode; +typedef enum TileSplit { + ADDR_SURF_TILE_SPLIT_64B = 0x0, + ADDR_SURF_TILE_SPLIT_128B = 0x1, + ADDR_SURF_TILE_SPLIT_256B = 0x2, + ADDR_SURF_TILE_SPLIT_512B = 0x3, + ADDR_SURF_TILE_SPLIT_1KB = 0x4, + ADDR_SURF_TILE_SPLIT_2KB = 0x5, + ADDR_SURF_TILE_SPLIT_4KB = 0x6, +} TileSplit; +typedef enum SampleSplit { + ADDR_SURF_SAMPLE_SPLIT_1 = 0x0, + ADDR_SURF_SAMPLE_SPLIT_2 = 0x1, + ADDR_SURF_SAMPLE_SPLIT_4 = 0x2, + ADDR_SURF_SAMPLE_SPLIT_8 = 0x3, +} SampleSplit; +typedef enum PipeConfig { + ADDR_SURF_P2 = 0x0, + ADDR_SURF_P2_RESERVED0 = 0x1, + ADDR_SURF_P2_RESERVED1 = 0x2, + ADDR_SURF_P2_RESERVED2 = 0x3, + ADDR_SURF_P4_8x16 = 0x4, + ADDR_SURF_P4_16x16 = 0x5, + ADDR_SURF_P4_16x32 = 0x6, + ADDR_SURF_P4_32x32 = 0x7, + ADDR_SURF_P8_16x16_8x16 = 0x8, + ADDR_SURF_P8_16x32_8x16 = 0x9, + ADDR_SURF_P8_32x32_8x16 = 0xa, + ADDR_SURF_P8_16x32_16x16 = 0xb, + ADDR_SURF_P8_32x32_16x16 = 0xc, + ADDR_SURF_P8_32x32_16x32 = 0xd, + ADDR_SURF_P8_32x64_32x32 = 0xe, + ADDR_SURF_P8_RESERVED0 = 0xf, + ADDR_SURF_P16_32x32_8x16 = 0x10, + ADDR_SURF_P16_32x32_16x16 = 0x11, +} PipeConfig; +typedef enum NumBanks { + ADDR_SURF_2_BANK = 0x0, + ADDR_SURF_4_BANK = 0x1, + ADDR_SURF_8_BANK = 0x2, + ADDR_SURF_16_BANK = 0x3, +} NumBanks; +typedef enum BankWidth { + ADDR_SURF_BANK_WIDTH_1 = 0x0, + ADDR_SURF_BANK_WIDTH_2 = 0x1, + ADDR_SURF_BANK_WIDTH_4 = 0x2, + ADDR_SURF_BANK_WIDTH_8 = 0x3, +} BankWidth; +typedef enum BankHeight { + ADDR_SURF_BANK_HEIGHT_1 = 0x0, + ADDR_SURF_BANK_HEIGHT_2 = 0x1, + ADDR_SURF_BANK_HEIGHT_4 = 0x2, + ADDR_SURF_BANK_HEIGHT_8 = 0x3, +} BankHeight; +typedef enum BankWidthHeight { + ADDR_SURF_BANK_WH_1 = 0x0, + ADDR_SURF_BANK_WH_2 = 0x1, + ADDR_SURF_BANK_WH_4 = 0x2, + ADDR_SURF_BANK_WH_8 = 0x3, +} BankWidthHeight; +typedef enum MacroTileAspect { + ADDR_SURF_MACRO_ASPECT_1 = 0x0, + ADDR_SURF_MACRO_ASPECT_2 = 0x1, + ADDR_SURF_MACRO_ASPECT_4 = 0x2, + ADDR_SURF_MACRO_ASPECT_8 = 0x3, +} MacroTileAspect; +typedef enum GATCL1RequestType { + GATCL1_TYPE_NORMAL = 0x0, + GATCL1_TYPE_SHOOTDOWN = 0x1, + GATCL1_TYPE_BYPASS = 0x2, +} GATCL1RequestType; +typedef enum TCC_CACHE_POLICIES { + TCC_CACHE_POLICY_LRU = 0x0, + TCC_CACHE_POLICY_STREAM = 0x1, +} TCC_CACHE_POLICIES; +typedef enum MTYPE { + MTYPE_NC_NV = 0x0, + MTYPE_NC = 0x1, + MTYPE_CC = 0x2, + MTYPE_UC = 0x3, +} MTYPE; +typedef enum PERFMON_COUNTER_MODE { + PERFMON_COUNTER_MODE_ACCUM = 0x0, + PERFMON_COUNTER_MODE_ACTIVE_CYCLES = 0x1, + PERFMON_COUNTER_MODE_MAX = 0x2, + PERFMON_COUNTER_MODE_DIRTY = 0x3, + PERFMON_COUNTER_MODE_SAMPLE = 0x4, + PERFMON_COUNTER_MODE_CYCLES_SINCE_FIRST_EVENT = 0x5, + PERFMON_COUNTER_MODE_CYCLES_SINCE_LAST_EVENT = 0x6, + PERFMON_COUNTER_MODE_CYCLES_GE_HI = 0x7, + PERFMON_COUNTER_MODE_CYCLES_EQ_HI = 0x8, + PERFMON_COUNTER_MODE_INACTIVE_CYCLES = 0x9, + PERFMON_COUNTER_MODE_RESERVED = 0xf, +} PERFMON_COUNTER_MODE; +typedef enum PERFMON_SPM_MODE { + PERFMON_SPM_MODE_OFF = 0x0, + PERFMON_SPM_MODE_16BIT_CLAMP = 0x1, + PERFMON_SPM_MODE_16BIT_NO_CLAMP = 0x2, + PERFMON_SPM_MODE_32BIT_CLAMP = 0x3, + PERFMON_SPM_MODE_32BIT_NO_CLAMP = 0x4, + PERFMON_SPM_MODE_RESERVED_5 = 0x5, + PERFMON_SPM_MODE_RESERVED_6 = 0x6, + PERFMON_SPM_MODE_RESERVED_7 = 0x7, + PERFMON_SPM_MODE_TEST_MODE_0 = 0x8, + PERFMON_SPM_MODE_TEST_MODE_1 = 0x9, + PERFMON_SPM_MODE_TEST_MODE_2 = 0xa, +} PERFMON_SPM_MODE; +typedef enum SurfaceTiling { + ARRAY_LINEAR = 0x0, + ARRAY_TILED = 0x1, +} SurfaceTiling; +typedef enum SurfaceArray { + ARRAY_1D = 0x0, + ARRAY_2D = 0x1, + ARRAY_3D = 0x2, + ARRAY_3D_SLICE = 0x3, +} SurfaceArray; +typedef enum ColorArray { + ARRAY_2D_ALT_COLOR = 0x0, + ARRAY_2D_COLOR = 0x1, + ARRAY_3D_SLICE_COLOR = 0x3, +} ColorArray; +typedef enum DepthArray { + ARRAY_2D_ALT_DEPTH = 0x0, + ARRAY_2D_DEPTH = 0x1, +} DepthArray; +typedef enum ENUM_NUM_SIMD_PER_CU { + NUM_SIMD_PER_CU = 0x4, +} ENUM_NUM_SIMD_PER_CU; +typedef enum MEM_PWR_FORCE_CTRL { + NO_FORCE_REQUEST = 0x0, + FORCE_LIGHT_SLEEP_REQUEST = 0x1, + FORCE_DEEP_SLEEP_REQUEST = 0x2, + FORCE_SHUT_DOWN_REQUEST = 0x3, +} MEM_PWR_FORCE_CTRL; +typedef enum MEM_PWR_FORCE_CTRL2 { + NO_FORCE_REQ = 0x0, + FORCE_LIGHT_SLEEP_REQ = 0x1, +} MEM_PWR_FORCE_CTRL2; +typedef enum MEM_PWR_DIS_CTRL { + ENABLE_MEM_PWR_CTRL = 0x0, + DISABLE_MEM_PWR_CTRL = 0x1, +} MEM_PWR_DIS_CTRL; +typedef enum MEM_PWR_SEL_CTRL { + DYNAMIC_SHUT_DOWN_ENABLE = 0x0, + DYNAMIC_DEEP_SLEEP_ENABLE = 0x1, + DYNAMIC_LIGHT_SLEEP_ENABLE = 0x2, +} MEM_PWR_SEL_CTRL; +typedef enum MEM_PWR_SEL_CTRL2 { + DYNAMIC_DEEP_SLEEP_EN = 0x0, + DYNAMIC_LIGHT_SLEEP_EN = 0x1, +} MEM_PWR_SEL_CTRL2; + +#endif /* ACP_2_2_ENUM_H */ diff --git a/sound/soc/amd/include/acp_2_2_sh_mask.h b/sound/soc/amd/include/acp_2_2_sh_mask.h new file mode 100644 index 000000000..32d2d4104 --- /dev/null +++ b/sound/soc/amd/include/acp_2_2_sh_mask.h @@ -0,0 +1,2292 @@ +/* + * ACP_2_2 Register documentation + * + * Copyright (C) 2014 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef ACP_2_2_SH_MASK_H +#define ACP_2_2_SH_MASK_H + +#define ACP_DMA_CNTL_0__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_0__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_0__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_0__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_0__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_0__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_0__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_0__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_0__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_0__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_1__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_1__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_1__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_1__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_1__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_1__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_1__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_1__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_1__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_1__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_2__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_2__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_2__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_2__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_2__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_2__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_2__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_2__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_2__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_2__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_3__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_3__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_3__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_3__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_3__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_3__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_3__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_3__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_3__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_3__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_4__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_4__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_4__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_4__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_4__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_4__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_4__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_4__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_4__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_4__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_5__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_5__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_5__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_5__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_5__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_5__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_5__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_5__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_5__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_5__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_6__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_6__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_6__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_6__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_6__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_6__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_6__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_6__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_6__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_6__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_7__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_7__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_7__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_7__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_7__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_7__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_7__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_7__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_7__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_7__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_8__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_8__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_8__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_8__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_8__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_8__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_8__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_8__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_8__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_8__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_9__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_9__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_9__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_9__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_9__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_9__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_9__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_9__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_9__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_9__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_10__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_10__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_10__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_10__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_10__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_10__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_10__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_10__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_10__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_10__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_11__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_11__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_11__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_11__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_11__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_11__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_11__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_11__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_11__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_11__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_12__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_12__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_12__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_12__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_12__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_12__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_12__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_12__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_12__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_12__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_13__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_13__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_13__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_13__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_13__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_13__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_13__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_13__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_13__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_13__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_14__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_14__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_14__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_14__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_14__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_14__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_14__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_14__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_14__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_14__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_CNTL_15__DMAChRst_MASK 0x1 +#define ACP_DMA_CNTL_15__DMAChRst__SHIFT 0x0 +#define ACP_DMA_CNTL_15__DMAChRun_MASK 0x2 +#define ACP_DMA_CNTL_15__DMAChRun__SHIFT 0x1 +#define ACP_DMA_CNTL_15__DMAChIOCEn_MASK 0x4 +#define ACP_DMA_CNTL_15__DMAChIOCEn__SHIFT 0x2 +#define ACP_DMA_CNTL_15__Circular_DMA_En_MASK 0x8 +#define ACP_DMA_CNTL_15__Circular_DMA_En__SHIFT 0x3 +#define ACP_DMA_CNTL_15__DMAChGracefulRstEn_MASK 0x10 +#define ACP_DMA_CNTL_15__DMAChGracefulRstEn__SHIFT 0x4 +#define ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_1__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_1__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_2__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_2__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_3__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_3__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_4__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_4__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_5__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_5__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_6__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_6__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_7__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_7__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_8__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_8__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_9__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_9__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_10__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_10__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_11__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_11__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_12__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_12__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_13__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_13__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_14__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_14__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_STRT_IDX_15__DMAChDscrStrtIdx_MASK 0x3ff +#define ACP_DMA_DSCR_STRT_IDX_15__DMAChDscrStrtIdx__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_0__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_0__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_1__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_1__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_2__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_2__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_3__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_3__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_4__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_4__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_5__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_5__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_6__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_6__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_7__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_7__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_8__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_8__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_9__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_9__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_10__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_10__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_11__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_11__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_12__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_12__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_13__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_13__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_14__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_14__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_DSCR_CNT_15__DMAChDscrCnt_MASK 0x3ff +#define ACP_DMA_DSCR_CNT_15__DMAChDscrCnt__SHIFT 0x0 +#define ACP_DMA_PRIO_0__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_0__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_1__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_1__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_2__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_2__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_3__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_3__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_4__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_4__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_5__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_5__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_6__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_6__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_7__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_7__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_8__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_8__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_9__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_9__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_10__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_10__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_11__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_11__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_12__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_12__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_13__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_13__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_14__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_14__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_PRIO_15__DMAChPrioLvl_MASK 0x1 +#define ACP_DMA_PRIO_15__DMAChPrioLvl__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_0__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_0__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_1__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_1__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_2__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_2__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_3__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_3__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_4__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_4__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_5__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_5__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_6__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_6__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_7__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_7__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_8__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_8__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_9__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_9__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_10__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_10__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_11__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_11__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_12__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_12__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_13__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_13__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_14__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_14__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_DSCR_15__DMAChCurDscrIdx_MASK 0x3ff +#define ACP_DMA_CUR_DSCR_15__DMAChCurDscrIdx__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_0__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_0__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_1__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_1__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_2__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_2__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_3__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_3__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_4__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_4__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_5__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_5__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_6__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_6__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_7__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_7__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_8__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_8__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_9__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_9__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_10__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_10__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_11__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_11__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_12__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_12__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_13__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_13__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_14__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_14__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_CUR_TRANS_CNT_15__DMAChCurTransCnt_MASK 0x1ffff +#define ACP_DMA_CUR_TRANS_CNT_15__DMAChCurTransCnt__SHIFT 0x0 +#define ACP_DMA_ERR_STS_0__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_0__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_0__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_0__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_1__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_1__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_1__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_1__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_2__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_2__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_2__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_2__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_3__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_3__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_3__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_3__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_4__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_4__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_4__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_4__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_5__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_5__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_5__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_5__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_6__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_6__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_6__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_6__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_7__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_7__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_7__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_7__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_8__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_8__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_8__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_8__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_9__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_9__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_9__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_9__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_10__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_10__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_10__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_10__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_11__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_11__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_11__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_11__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_12__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_12__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_12__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_12__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_13__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_13__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_13__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_13__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_14__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_14__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_14__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_14__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_ERR_STS_15__DMAChTermErr_MASK 0x1 +#define ACP_DMA_ERR_STS_15__DMAChTermErr__SHIFT 0x0 +#define ACP_DMA_ERR_STS_15__DMAChErrCode_MASK 0x1e +#define ACP_DMA_ERR_STS_15__DMAChErrCode__SHIFT 0x1 +#define ACP_DMA_DESC_BASE_ADDR__DescriptorBaseAddr_MASK 0xffffffff +#define ACP_DMA_DESC_BASE_ADDR__DescriptorBaseAddr__SHIFT 0x0 +#define ACP_DMA_DESC_MAX_NUM_DSCR__MaximumNumberDescr_MASK 0xf +#define ACP_DMA_DESC_MAX_NUM_DSCR__MaximumNumberDescr__SHIFT 0x0 +#define ACP_DMA_CH_STS__DMAChSts_MASK 0xffff +#define ACP_DMA_CH_STS__DMAChSts__SHIFT 0x0 +#define ACP_DMA_CH_GROUP__DMAChanelGrouping_MASK 0x1 +#define ACP_DMA_CH_GROUP__DMAChanelGrouping__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET0__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET0__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET0__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET0__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE0__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE0__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE0__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE0__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET1__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET1__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET1__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET1__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE1__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE1__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE1__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE1__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET2__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET2__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET2__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET2__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE2__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE2__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE2__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE2__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET3__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET3__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET3__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET3__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE3__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE3__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE3__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE3__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET4__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET4__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET4__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET4__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE4__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE4__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE4__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE4__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET5__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET5__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET5__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET5__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE5__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE5__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE5__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE5__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET6__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET6__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET6__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET6__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE6__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE6__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE6__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE6__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET7__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET7__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET7__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET7__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE7__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE7__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE7__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE7__PageEnable__SHIFT 0x1f +#define ACP_DSP0_CACHE_OFFSET8__Offset_MASK 0xfffffff +#define ACP_DSP0_CACHE_OFFSET8__Offset__SHIFT 0x0 +#define ACP_DSP0_CACHE_OFFSET8__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_CACHE_OFFSET8__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_CACHE_SIZE8__Size_MASK 0xffffff +#define ACP_DSP0_CACHE_SIZE8__Size__SHIFT 0x0 +#define ACP_DSP0_CACHE_SIZE8__PageEnable_MASK 0x80000000 +#define ACP_DSP0_CACHE_SIZE8__PageEnable__SHIFT 0x1f +#define ACP_DSP0_NONCACHE_OFFSET0__Offset_MASK 0xfffffff +#define ACP_DSP0_NONCACHE_OFFSET0__Offset__SHIFT 0x0 +#define ACP_DSP0_NONCACHE_OFFSET0__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_NONCACHE_OFFSET0__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_NONCACHE_SIZE0__Size_MASK 0xffffff +#define ACP_DSP0_NONCACHE_SIZE0__Size__SHIFT 0x0 +#define ACP_DSP0_NONCACHE_SIZE0__PageEnable_MASK 0x80000000 +#define ACP_DSP0_NONCACHE_SIZE0__PageEnable__SHIFT 0x1f +#define ACP_DSP0_NONCACHE_OFFSET1__Offset_MASK 0xfffffff +#define ACP_DSP0_NONCACHE_OFFSET1__Offset__SHIFT 0x0 +#define ACP_DSP0_NONCACHE_OFFSET1__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP0_NONCACHE_OFFSET1__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP0_NONCACHE_SIZE1__Size_MASK 0xffffff +#define ACP_DSP0_NONCACHE_SIZE1__Size__SHIFT 0x0 +#define ACP_DSP0_NONCACHE_SIZE1__PageEnable_MASK 0x80000000 +#define ACP_DSP0_NONCACHE_SIZE1__PageEnable__SHIFT 0x1f +#define ACP_DSP0_DEBUG_PC__DebugPC_MASK 0xffffffff +#define ACP_DSP0_DEBUG_PC__DebugPC__SHIFT 0x0 +#define ACP_DSP0_NMI_SEL__NMISel_MASK 0x1 +#define ACP_DSP0_NMI_SEL__NMISel__SHIFT 0x0 +#define ACP_DSP0_CLKRST_CNTL__ClkEn_MASK 0x1 +#define ACP_DSP0_CLKRST_CNTL__ClkEn__SHIFT 0x0 +#define ACP_DSP0_CLKRST_CNTL__SoftResetDSP_MASK 0x2 +#define ACP_DSP0_CLKRST_CNTL__SoftResetDSP__SHIFT 0x1 +#define ACP_DSP0_CLKRST_CNTL__InternalSoftResetMode_MASK 0x4 +#define ACP_DSP0_CLKRST_CNTL__InternalSoftResetMode__SHIFT 0x2 +#define ACP_DSP0_CLKRST_CNTL__ExternalSoftResetMode_MASK 0x8 +#define ACP_DSP0_CLKRST_CNTL__ExternalSoftResetMode__SHIFT 0x3 +#define ACP_DSP0_CLKRST_CNTL__SoftResetDSPDone_MASK 0x10 +#define ACP_DSP0_CLKRST_CNTL__SoftResetDSPDone__SHIFT 0x4 +#define ACP_DSP0_CLKRST_CNTL__Clk_ON_Status_MASK 0x20 +#define ACP_DSP0_CLKRST_CNTL__Clk_ON_Status__SHIFT 0x5 +#define ACP_DSP0_RUNSTALL__RunStallCntl_MASK 0x1 +#define ACP_DSP0_RUNSTALL__RunStallCntl__SHIFT 0x0 +#define ACP_DSP0_OCD_HALT_ON_RST__OCD_HALT_ON_RST_MASK 0x1 +#define ACP_DSP0_OCD_HALT_ON_RST__OCD_HALT_ON_RST__SHIFT 0x0 +#define ACP_DSP0_WAIT_MODE__WaitMode_MASK 0x1 +#define ACP_DSP0_WAIT_MODE__WaitMode__SHIFT 0x0 +#define ACP_DSP0_VECT_SEL__StaticVectorSel_MASK 0x1 +#define ACP_DSP0_VECT_SEL__StaticVectorSel__SHIFT 0x0 +#define ACP_DSP0_DEBUG_REG1__ACP_DSP_DEBUG_REG1_MASK 0xffffffff +#define ACP_DSP0_DEBUG_REG1__ACP_DSP_DEBUG_REG1__SHIFT 0x0 +#define ACP_DSP0_DEBUG_REG2__ACP_DSP_DEBUG_REG2_MASK 0xffffffff +#define ACP_DSP0_DEBUG_REG2__ACP_DSP_DEBUG_REG2__SHIFT 0x0 +#define ACP_DSP0_DEBUG_REG3__ACP_DSP_DEBUG_REG3_MASK 0xffffffff +#define ACP_DSP0_DEBUG_REG3__ACP_DSP_DEBUG_REG3__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET0__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET0__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET0__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET0__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE0__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE0__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE0__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE0__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET1__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET1__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET1__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET1__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE1__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE1__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE1__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE1__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET2__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET2__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET2__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET2__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE2__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE2__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE2__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE2__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET3__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET3__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET3__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET3__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE3__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE3__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE3__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE3__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET4__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET4__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET4__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET4__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE4__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE4__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE4__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE4__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET5__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET5__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET5__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET5__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE5__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE5__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE5__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE5__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET6__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET6__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET6__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET6__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE6__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE6__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE6__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE6__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET7__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET7__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET7__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET7__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE7__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE7__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE7__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE7__PageEnable__SHIFT 0x1f +#define ACP_DSP1_CACHE_OFFSET8__Offset_MASK 0xfffffff +#define ACP_DSP1_CACHE_OFFSET8__Offset__SHIFT 0x0 +#define ACP_DSP1_CACHE_OFFSET8__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_CACHE_OFFSET8__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_CACHE_SIZE8__Size_MASK 0xffffff +#define ACP_DSP1_CACHE_SIZE8__Size__SHIFT 0x0 +#define ACP_DSP1_CACHE_SIZE8__PageEnable_MASK 0x80000000 +#define ACP_DSP1_CACHE_SIZE8__PageEnable__SHIFT 0x1f +#define ACP_DSP1_NONCACHE_OFFSET0__Offset_MASK 0xfffffff +#define ACP_DSP1_NONCACHE_OFFSET0__Offset__SHIFT 0x0 +#define ACP_DSP1_NONCACHE_OFFSET0__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_NONCACHE_OFFSET0__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_NONCACHE_SIZE0__Size_MASK 0xffffff +#define ACP_DSP1_NONCACHE_SIZE0__Size__SHIFT 0x0 +#define ACP_DSP1_NONCACHE_SIZE0__PageEnable_MASK 0x80000000 +#define ACP_DSP1_NONCACHE_SIZE0__PageEnable__SHIFT 0x1f +#define ACP_DSP1_NONCACHE_OFFSET1__Offset_MASK 0xfffffff +#define ACP_DSP1_NONCACHE_OFFSET1__Offset__SHIFT 0x0 +#define ACP_DSP1_NONCACHE_OFFSET1__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP1_NONCACHE_OFFSET1__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP1_NONCACHE_SIZE1__Size_MASK 0xffffff +#define ACP_DSP1_NONCACHE_SIZE1__Size__SHIFT 0x0 +#define ACP_DSP1_NONCACHE_SIZE1__PageEnable_MASK 0x80000000 +#define ACP_DSP1_NONCACHE_SIZE1__PageEnable__SHIFT 0x1f +#define ACP_DSP1_DEBUG_PC__DebugPC_MASK 0xffffffff +#define ACP_DSP1_DEBUG_PC__DebugPC__SHIFT 0x0 +#define ACP_DSP1_NMI_SEL__NMISel_MASK 0x1 +#define ACP_DSP1_NMI_SEL__NMISel__SHIFT 0x0 +#define ACP_DSP1_CLKRST_CNTL__ClkEn_MASK 0x1 +#define ACP_DSP1_CLKRST_CNTL__ClkEn__SHIFT 0x0 +#define ACP_DSP1_CLKRST_CNTL__SoftResetDSP_MASK 0x2 +#define ACP_DSP1_CLKRST_CNTL__SoftResetDSP__SHIFT 0x1 +#define ACP_DSP1_CLKRST_CNTL__InternalSoftResetMode_MASK 0x4 +#define ACP_DSP1_CLKRST_CNTL__InternalSoftResetMode__SHIFT 0x2 +#define ACP_DSP1_CLKRST_CNTL__ExternalSoftResetMode_MASK 0x8 +#define ACP_DSP1_CLKRST_CNTL__ExternalSoftResetMode__SHIFT 0x3 +#define ACP_DSP1_CLKRST_CNTL__SoftResetDSPDone_MASK 0x10 +#define ACP_DSP1_CLKRST_CNTL__SoftResetDSPDone__SHIFT 0x4 +#define ACP_DSP1_CLKRST_CNTL__Clk_ON_Status_MASK 0x20 +#define ACP_DSP1_CLKRST_CNTL__Clk_ON_Status__SHIFT 0x5 +#define ACP_DSP1_RUNSTALL__RunStallCntl_MASK 0x1 +#define ACP_DSP1_RUNSTALL__RunStallCntl__SHIFT 0x0 +#define ACP_DSP1_OCD_HALT_ON_RST__OCD_HALT_ON_RST_MASK 0x1 +#define ACP_DSP1_OCD_HALT_ON_RST__OCD_HALT_ON_RST__SHIFT 0x0 +#define ACP_DSP1_WAIT_MODE__WaitMode_MASK 0x1 +#define ACP_DSP1_WAIT_MODE__WaitMode__SHIFT 0x0 +#define ACP_DSP1_VECT_SEL__StaticVectorSel_MASK 0x1 +#define ACP_DSP1_VECT_SEL__StaticVectorSel__SHIFT 0x0 +#define ACP_DSP1_DEBUG_REG1__ACP_DSP_DEBUG_REG1_MASK 0xffffffff +#define ACP_DSP1_DEBUG_REG1__ACP_DSP_DEBUG_REG1__SHIFT 0x0 +#define ACP_DSP1_DEBUG_REG2__ACP_DSP_DEBUG_REG2_MASK 0xffffffff +#define ACP_DSP1_DEBUG_REG2__ACP_DSP_DEBUG_REG2__SHIFT 0x0 +#define ACP_DSP1_DEBUG_REG3__ACP_DSP_DEBUG_REG3_MASK 0xffffffff +#define ACP_DSP1_DEBUG_REG3__ACP_DSP_DEBUG_REG3__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET0__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET0__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET0__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET0__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE0__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE0__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE0__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE0__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET1__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET1__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET1__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET1__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE1__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE1__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE1__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE1__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET2__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET2__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET2__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET2__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE2__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE2__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE2__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE2__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET3__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET3__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET3__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET3__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE3__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE3__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE3__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE3__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET4__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET4__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET4__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET4__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE4__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE4__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE4__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE4__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET5__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET5__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET5__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET5__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE5__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE5__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE5__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE5__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET6__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET6__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET6__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET6__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE6__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE6__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE6__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE6__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET7__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET7__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET7__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET7__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE7__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE7__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE7__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE7__PageEnable__SHIFT 0x1f +#define ACP_DSP2_CACHE_OFFSET8__Offset_MASK 0xfffffff +#define ACP_DSP2_CACHE_OFFSET8__Offset__SHIFT 0x0 +#define ACP_DSP2_CACHE_OFFSET8__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_CACHE_OFFSET8__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_CACHE_SIZE8__Size_MASK 0xffffff +#define ACP_DSP2_CACHE_SIZE8__Size__SHIFT 0x0 +#define ACP_DSP2_CACHE_SIZE8__PageEnable_MASK 0x80000000 +#define ACP_DSP2_CACHE_SIZE8__PageEnable__SHIFT 0x1f +#define ACP_DSP2_NONCACHE_OFFSET0__Offset_MASK 0xfffffff +#define ACP_DSP2_NONCACHE_OFFSET0__Offset__SHIFT 0x0 +#define ACP_DSP2_NONCACHE_OFFSET0__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_NONCACHE_OFFSET0__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_NONCACHE_SIZE0__Size_MASK 0xffffff +#define ACP_DSP2_NONCACHE_SIZE0__Size__SHIFT 0x0 +#define ACP_DSP2_NONCACHE_SIZE0__PageEnable_MASK 0x80000000 +#define ACP_DSP2_NONCACHE_SIZE0__PageEnable__SHIFT 0x1f +#define ACP_DSP2_NONCACHE_OFFSET1__Offset_MASK 0xfffffff +#define ACP_DSP2_NONCACHE_OFFSET1__Offset__SHIFT 0x0 +#define ACP_DSP2_NONCACHE_OFFSET1__OnionGarlicSel_MASK 0x80000000 +#define ACP_DSP2_NONCACHE_OFFSET1__OnionGarlicSel__SHIFT 0x1f +#define ACP_DSP2_NONCACHE_SIZE1__Size_MASK 0xffffff +#define ACP_DSP2_NONCACHE_SIZE1__Size__SHIFT 0x0 +#define ACP_DSP2_NONCACHE_SIZE1__PageEnable_MASK 0x80000000 +#define ACP_DSP2_NONCACHE_SIZE1__PageEnable__SHIFT 0x1f +#define ACP_DSP2_DEBUG_PC__DebugPC_MASK 0xffffffff +#define ACP_DSP2_DEBUG_PC__DebugPC__SHIFT 0x0 +#define ACP_DSP2_NMI_SEL__NMISel_MASK 0x1 +#define ACP_DSP2_NMI_SEL__NMISel__SHIFT 0x0 +#define ACP_DSP2_CLKRST_CNTL__ClkEn_MASK 0x1 +#define ACP_DSP2_CLKRST_CNTL__ClkEn__SHIFT 0x0 +#define ACP_DSP2_CLKRST_CNTL__SoftResetDSP_MASK 0x2 +#define ACP_DSP2_CLKRST_CNTL__SoftResetDSP__SHIFT 0x1 +#define ACP_DSP2_CLKRST_CNTL__InternalSoftResetMode_MASK 0x4 +#define ACP_DSP2_CLKRST_CNTL__InternalSoftResetMode__SHIFT 0x2 +#define ACP_DSP2_CLKRST_CNTL__ExternalSoftResetMode_MASK 0x8 +#define ACP_DSP2_CLKRST_CNTL__ExternalSoftResetMode__SHIFT 0x3 +#define ACP_DSP2_CLKRST_CNTL__SoftResetDSPDone_MASK 0x10 +#define ACP_DSP2_CLKRST_CNTL__SoftResetDSPDone__SHIFT 0x4 +#define ACP_DSP2_CLKRST_CNTL__Clk_ON_Status_MASK 0x20 +#define ACP_DSP2_CLKRST_CNTL__Clk_ON_Status__SHIFT 0x5 +#define ACP_DSP2_RUNSTALL__RunStallCntl_MASK 0x1 +#define ACP_DSP2_RUNSTALL__RunStallCntl__SHIFT 0x0 +#define ACP_DSP2_OCD_HALT_ON_RST__OCD_HALT_ON_RST_MASK 0x1 +#define ACP_DSP2_OCD_HALT_ON_RST__OCD_HALT_ON_RST__SHIFT 0x0 +#define ACP_DSP2_WAIT_MODE__WaitMode_MASK 0x1 +#define ACP_DSP2_WAIT_MODE__WaitMode__SHIFT 0x0 +#define ACP_DSP2_VECT_SEL__StaticVectorSel_MASK 0x1 +#define ACP_DSP2_VECT_SEL__StaticVectorSel__SHIFT 0x0 +#define ACP_DSP2_DEBUG_REG1__ACP_DSP_DEBUG_REG1_MASK 0xffffffff +#define ACP_DSP2_DEBUG_REG1__ACP_DSP_DEBUG_REG1__SHIFT 0x0 +#define ACP_DSP2_DEBUG_REG2__ACP_DSP_DEBUG_REG2_MASK 0xffffffff +#define ACP_DSP2_DEBUG_REG2__ACP_DSP_DEBUG_REG2__SHIFT 0x0 +#define ACP_DSP2_DEBUG_REG3__ACP_DSP_DEBUG_REG3_MASK 0xffffffff +#define ACP_DSP2_DEBUG_REG3__ACP_DSP_DEBUG_REG3__SHIFT 0x0 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBDataSwap_MASK 0x3 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBDataSwap__SHIFT 0x0 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBEnbMultRdReq_MASK 0x4 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBEnbMultRdReq__SHIFT 0x2 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBEnbMultWrReq_MASK 0x18 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBEnbMultWrReq__SHIFT 0x3 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBMaxReadBurst_MASK 0x60 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBMaxReadBurst__SHIFT 0x5 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBStallEnb_MASK 0x80 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBStallEnb__SHIFT 0x7 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBNackChkEnb_MASK 0x100 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBNackChkEnb__SHIFT 0x8 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBAdrWinViolChkEnb_MASK 0x200 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBAdrWinViolChkEnb__SHIFT 0x9 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBUrgEnb_MASK 0x400 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBUrgEnb__SHIFT 0xa +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBUrgCntMult_MASK 0x1800 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBUrgCntMult__SHIFT 0xb +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBStallMode_MASK 0x2000 +#define ACP_AXI2DAGB_ONION_CNTL__AXI2DAGBStallMode__SHIFT 0xd +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBAdrWinViolOver_MASK 0x2000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBAdrWinViolOver__SHIFT 0x19 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBAdrWinViolSource_MASK 0x1c000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBAdrWinViolSource__SHIFT 0x1a +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBAdrWinViol_MASK 0x20000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBAdrWinViol__SHIFT 0x1d +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBNackOver_MASK 0x40000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBNackOver__SHIFT 0x1e +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBNackVal_MASK 0x80000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_WR__AXI2DAGBNackVal__SHIFT 0x1f +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBAdrWinViolOver_MASK 0x2000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBAdrWinViolOver__SHIFT 0x19 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBAdrWinViolSource_MASK 0x1c000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBAdrWinViolSource__SHIFT 0x1a +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBAdrWinViol_MASK 0x20000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBAdrWinViol__SHIFT 0x1d +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBNackOver_MASK 0x40000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBNackOver__SHIFT 0x1e +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBNackVal_MASK 0x80000000 +#define ACP_AXI2DAGB_ONION_ERR_STATUS_RD__AXI2DAGBNackVal__SHIFT 0x1f +#define ACP_DAGB_Onion_TransPerf_Counter_Control__EnbDAGBTransPerfCntr_MASK 0x1 +#define ACP_DAGB_Onion_TransPerf_Counter_Control__EnbDAGBTransPerfCntr__SHIFT 0x0 +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Onion_Wr_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Onion_Rd_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBDataSwap_MASK 0x3 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBDataSwap__SHIFT 0x0 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBEnbMultRdReq_MASK 0x4 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBEnbMultRdReq__SHIFT 0x2 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBEnbMultWrReq_MASK 0x18 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBEnbMultWrReq__SHIFT 0x3 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBMaxReadBurst_MASK 0x60 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBMaxReadBurst__SHIFT 0x5 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBStallEnb_MASK 0x80 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBStallEnb__SHIFT 0x7 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBNackChkEnb_MASK 0x100 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBNackChkEnb__SHIFT 0x8 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBAdrWinViolChkEnb_MASK 0x200 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBAdrWinViolChkEnb__SHIFT 0x9 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBUrgEnb_MASK 0x400 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBUrgEnb__SHIFT 0xa +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBUrgCntMult_MASK 0x1800 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBUrgCntMult__SHIFT 0xb +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBStallMode_MASK 0x2000 +#define ACP_AXI2DAGB_GARLIC_CNTL__AXI2DAGBStallMode__SHIFT 0xd +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBAdrWinViolOver_MASK 0x2000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBAdrWinViolOver__SHIFT 0x19 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBAdrWinViolSource_MASK 0x1c000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBAdrWinViolSource__SHIFT 0x1a +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBAdrWinViol_MASK 0x20000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBAdrWinViol__SHIFT 0x1d +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBNackOver_MASK 0x40000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBNackOver__SHIFT 0x1e +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBNackVal_MASK 0x80000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_WR__AXI2DAGBNackVal__SHIFT 0x1f +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBAdrWinViolOver_MASK 0x2000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBAdrWinViolOver__SHIFT 0x19 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBAdrWinViolSource_MASK 0x1c000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBAdrWinViolSource__SHIFT 0x1a +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBAdrWinViol_MASK 0x20000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBAdrWinViol__SHIFT 0x1d +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBNackOver_MASK 0x40000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBNackOver__SHIFT 0x1e +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBNackVal_MASK 0x80000000 +#define ACP_AXI2DAGB_GARLIC_ERR_STATUS_RD__AXI2DAGBNackVal__SHIFT 0x1f +#define ACP_DAGB_Garlic_TransPerf_Counter_Control__EnbDAGBTransPerfCntr_MASK 0x1 +#define ACP_DAGB_Garlic_TransPerf_Counter_Control__EnbDAGBTransPerfCntr__SHIFT 0x0 +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Garlic_Wr_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Current__CurDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Current__ClrCurDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime_MASK 0x1ffff +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Peak__PeakDAGBTransPerfCntrTime__SHIFT 0x0 +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr_MASK 0x80000000 +#define ACP_DAGB_Garlic_Rd_TransPerf_Counter_Peak__ClrPeakDAGBTransPerfCntr__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_1__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_1__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_2__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_2__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_2__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_3__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_3__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_3__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_4__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_4__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_4__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_5__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_5__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_5__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_6__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_6__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_6__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_7__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_7__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_7__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_PAGE_SIZE_GRP_8__AXI2DAGBPageSize_MASK 0x3 +#define ACP_DAGB_PAGE_SIZE_GRP_8__AXI2DAGBPageSize__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBBaseAddr_MASK 0xfffffff +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBBaseAddr__SHIFT 0x0 +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBSnoopSel_MASK 0x20000000 +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBSnoopSel__SHIFT 0x1d +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBTargetMemSel_MASK 0x40000000 +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBTargetMemSel__SHIFT 0x1e +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBGrpEnable_MASK 0x80000000 +#define ACP_DAGB_BASE_ADDR_GRP_8__AXI2DAGBGrpEnable__SHIFT 0x1f +#define ACP_DAGB_ATU_CTRL__AXI2DAGBCacheInvalidate_MASK 0x1 +#define ACP_DAGB_ATU_CTRL__AXI2DAGBCacheInvalidate__SHIFT 0x0 +#define ACP_CONTROL__ClkEn_MASK 0x1 +#define ACP_CONTROL__ClkEn__SHIFT 0x0 +#define ACP_CONTROL__JtagEn_MASK 0x400 +#define ACP_CONTROL__JtagEn__SHIFT 0xa +#define ACP_STATUS__ClkOn_MASK 0x1 +#define ACP_STATUS__ClkOn__SHIFT 0x0 +#define ACP_STATUS__ACPRefClkSpd_MASK 0x2 +#define ACP_STATUS__ACPRefClkSpd__SHIFT 0x1 +#define ACP_STATUS__SMUStutterLastEdge_MASK 0x4 +#define ACP_STATUS__SMUStutterLastEdge__SHIFT 0x2 +#define ACP_STATUS__MCStutterLastEdge_MASK 0x8 +#define ACP_STATUS__MCStutterLastEdge__SHIFT 0x3 +#define ACP_SOFT_RESET__SoftResetAud_MASK 0x100 +#define ACP_SOFT_RESET__SoftResetAud__SHIFT 0x8 +#define ACP_SOFT_RESET__SoftResetDMA_MASK 0x200 +#define ACP_SOFT_RESET__SoftResetDMA__SHIFT 0x9 +#define ACP_SOFT_RESET__InternalSoftResetMode_MASK 0x4000 +#define ACP_SOFT_RESET__InternalSoftResetMode__SHIFT 0xe +#define ACP_SOFT_RESET__ExternalSoftResetMode_MASK 0x8000 +#define ACP_SOFT_RESET__ExternalSoftResetMode__SHIFT 0xf +#define ACP_SOFT_RESET__SoftResetAudDone_MASK 0x1000000 +#define ACP_SOFT_RESET__SoftResetAudDone__SHIFT 0x18 +#define ACP_SOFT_RESET__SoftResetDMADone_MASK 0x2000000 +#define ACP_SOFT_RESET__SoftResetDMADone__SHIFT 0x19 +#define ACP_PwrMgmt_CNTL__SCLKSleepCntl_MASK 0x3 +#define ACP_PwrMgmt_CNTL__SCLKSleepCntl__SHIFT 0x0 +#define ACP_CAC_INDICATOR_CONTROL__ACP_Cac_Indicator_Counter_MASK 0xffff +#define ACP_CAC_INDICATOR_CONTROL__ACP_Cac_Indicator_Counter__SHIFT 0x0 +#define ACP_SMU_MAILBOX__ACP_SMU_Mailbox_MASK 0xffffffff +#define ACP_SMU_MAILBOX__ACP_SMU_Mailbox__SHIFT 0x0 +#define ACP_FUTURE_REG_SCLK_0__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_SCLK_0__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_SCLK_1__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_SCLK_1__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_SCLK_2__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_SCLK_2__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_SCLK_3__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_SCLK_3__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_SCLK_4__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_SCLK_4__ACPFutureReg__SHIFT 0x0 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_ask_cnt_enable_MASK 0x1 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_ask_cnt_enable__SHIFT 0x0 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_go_cnt_enable_MASK 0x2 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_go_cnt_enable__SHIFT 0x1 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_exp_respcnt_enable_MASK 0x4 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_exp_respcnt_enable__SHIFT 0x2 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_actual_respcnt_enable_MASK 0x8 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_wr_actual_respcnt_enable__SHIFT 0x3 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_ask_cnt_enable_MASK 0x10 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_ask_cnt_enable__SHIFT 0x4 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_go_cnt_enable_MASK 0x20 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_go_cnt_enable__SHIFT 0x5 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_exp_respcnt_enable_MASK 0x40 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_exp_respcnt_enable__SHIFT 0x6 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_actual_respcnt_enable_MASK 0x80 +#define ACP_DAGB_DEBUG_CNT_ENABLE__garlic_rd_actual_respcnt_enable__SHIFT 0x7 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_ask_cnt_enable_MASK 0x100 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_ask_cnt_enable__SHIFT 0x8 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_go_cnt_enable_MASK 0x200 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_go_cnt_enable__SHIFT 0x9 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_exp_respcnt_enable_MASK 0x400 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_exp_respcnt_enable__SHIFT 0xa +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_actual_respcnt_enable_MASK 0x800 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_wr_actual_respcnt_enable__SHIFT 0xb +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_ask_cnt_enable_MASK 0x1000 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_ask_cnt_enable__SHIFT 0xc +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_go_cnt_enable_MASK 0x2000 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_go_cnt_enable__SHIFT 0xd +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_exp_respcnt_enable_MASK 0x4000 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_exp_respcnt_enable__SHIFT 0xe +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_actual_respcnt_enable_MASK 0x8000 +#define ACP_DAGB_DEBUG_CNT_ENABLE__onion_rd_actual_respcnt_enable__SHIFT 0xf +#define ACP_DAGBG_WR_ASK_CNT__garlic_wr_only_ask_cnt_MASK 0xffff +#define ACP_DAGBG_WR_ASK_CNT__garlic_wr_only_ask_cnt__SHIFT 0x0 +#define ACP_DAGBG_WR_GO_CNT__garlic_wr_only_go_cnt_MASK 0xffff +#define ACP_DAGBG_WR_GO_CNT__garlic_wr_only_go_cnt__SHIFT 0x0 +#define ACP_DAGBG_WR_EXP_RESP_CNT__garlic_wr_exp_resp_cnt_MASK 0xffff +#define ACP_DAGBG_WR_EXP_RESP_CNT__garlic_wr_exp_resp_cnt__SHIFT 0x0 +#define ACP_DAGBG_WR_ACTUAL_RESP_CNT__garlic_wr_actual_resp_cnt_MASK 0xffff +#define ACP_DAGBG_WR_ACTUAL_RESP_CNT__garlic_wr_actual_resp_cnt__SHIFT 0x0 +#define ACP_DAGBG_RD_ASK_CNT__garlic_rd_only_ask_cnt_MASK 0xffff +#define ACP_DAGBG_RD_ASK_CNT__garlic_rd_only_ask_cnt__SHIFT 0x0 +#define ACP_DAGBG_RD_GO_CNT__garlic_rd_only_go_cnt_MASK 0xffff +#define ACP_DAGBG_RD_GO_CNT__garlic_rd_only_go_cnt__SHIFT 0x0 +#define ACP_DAGBG_RD_EXP_RESP_CNT__garlic_rd_exp_resp_cnt_MASK 0xffff +#define ACP_DAGBG_RD_EXP_RESP_CNT__garlic_rd_exp_resp_cnt__SHIFT 0x0 +#define ACP_DAGBG_RD_ACTUAL_RESP_CNT__garlic_rd_actual_resp_cnt_MASK 0xffff +#define ACP_DAGBG_RD_ACTUAL_RESP_CNT__garlic_rd_actual_resp_cnt__SHIFT 0x0 +#define ACP_DAGBO_WR_ASK_CNT__onion_wr_only_ask_cnt_MASK 0xffff +#define ACP_DAGBO_WR_ASK_CNT__onion_wr_only_ask_cnt__SHIFT 0x0 +#define ACP_DAGBO_WR_GO_CNT__onion_wr_only_go_cnt_MASK 0xffff +#define ACP_DAGBO_WR_GO_CNT__onion_wr_only_go_cnt__SHIFT 0x0 +#define ACP_DAGBO_WR_EXP_RESP_CNT__onion_wr_exp_resp_cnt_MASK 0xffff +#define ACP_DAGBO_WR_EXP_RESP_CNT__onion_wr_exp_resp_cnt__SHIFT 0x0 +#define ACP_DAGBO_WR_ACTUAL_RESP_CNT__onion_wr_actual_resp_cnt_MASK 0xffff +#define ACP_DAGBO_WR_ACTUAL_RESP_CNT__onion_wr_actual_resp_cnt__SHIFT 0x0 +#define ACP_DAGBO_RD_ASK_CNT__onion_rd_only_ask_cnt_MASK 0xffff +#define ACP_DAGBO_RD_ASK_CNT__onion_rd_only_ask_cnt__SHIFT 0x0 +#define ACP_DAGBO_RD_GO_CNT__onion_rd_only_go_cnt_MASK 0xffff +#define ACP_DAGBO_RD_GO_CNT__onion_rd_only_go_cnt__SHIFT 0x0 +#define ACP_DAGBO_RD_EXP_RESP_CNT__onion_rd_exp_resp_cnt_MASK 0xffff +#define ACP_DAGBO_RD_EXP_RESP_CNT__onion_rd_exp_resp_cnt__SHIFT 0x0 +#define ACP_DAGBO_RD_ACTUAL_RESP_CNT__onion_rd_actual_resp_cnt_MASK 0xffff +#define ACP_DAGBO_RD_ACTUAL_RESP_CNT__onion_rd_actual_resp_cnt__SHIFT 0x0 +#define ACP_BRB_CONTROL__BRB_BlockSharedRAMArbCntrl_MASK 0xf +#define ACP_BRB_CONTROL__BRB_BlockSharedRAMArbCntrl__SHIFT 0x0 +#define ACP_EXTERNAL_INTR_ENB__ACPExtIntrEnb_MASK 0x1 +#define ACP_EXTERNAL_INTR_ENB__ACPExtIntrEnb__SHIFT 0x0 +#define ACP_EXTERNAL_INTR_CNTL__ACPErrMask_MASK 0x1 +#define ACP_EXTERNAL_INTR_CNTL__ACPErrMask__SHIFT 0x0 +#define ACP_EXTERNAL_INTR_CNTL__I2SMicDataAvMask_MASK 0x2 +#define ACP_EXTERNAL_INTR_CNTL__I2SMicDataAvMask__SHIFT 0x1 +#define ACP_EXTERNAL_INTR_CNTL__I2SSpkr0DataEmptyMask_MASK 0x4 +#define ACP_EXTERNAL_INTR_CNTL__I2SSpkr0DataEmptyMask__SHIFT 0x2 +#define ACP_EXTERNAL_INTR_CNTL__I2SSpkr1DataEmptyMask_MASK 0x8 +#define ACP_EXTERNAL_INTR_CNTL__I2SSpkr1DataEmptyMask__SHIFT 0x3 +#define ACP_EXTERNAL_INTR_CNTL__I2SBTDataAvMask_MASK 0x10 +#define ACP_EXTERNAL_INTR_CNTL__I2SBTDataAvMask__SHIFT 0x4 +#define ACP_EXTERNAL_INTR_CNTL__AzaliaIntrMask_MASK 0x40 +#define ACP_EXTERNAL_INTR_CNTL__AzaliaIntrMask__SHIFT 0x6 +#define ACP_EXTERNAL_INTR_CNTL__DSP0TimeoutMask_MASK 0x100 +#define ACP_EXTERNAL_INTR_CNTL__DSP0TimeoutMask__SHIFT 0x8 +#define ACP_EXTERNAL_INTR_CNTL__DSP1TimeoutMask_MASK 0x200 +#define ACP_EXTERNAL_INTR_CNTL__DSP1TimeoutMask__SHIFT 0x9 +#define ACP_EXTERNAL_INTR_CNTL__DSP2TimeoutMask_MASK 0x400 +#define ACP_EXTERNAL_INTR_CNTL__DSP2TimeoutMask__SHIFT 0xa +#define ACP_EXTERNAL_INTR_CNTL__I2SBTDataEmptyMask_MASK 0x800 +#define ACP_EXTERNAL_INTR_CNTL__I2SBTDataEmptyMask__SHIFT 0xb +#define ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK 0xffff0000 +#define ACP_EXTERNAL_INTR_CNTL__DMAIOCMask__SHIFT 0x10 +#define ACP_ERROR_SOURCE_STS__ACPRegUdefADDRErr_MASK 0x1 +#define ACP_ERROR_SOURCE_STS__ACPRegUdefADDRErr__SHIFT 0x0 +#define ACP_ERROR_SOURCE_STS__ACPRegUdefADDRErrSource_MASK 0xe +#define ACP_ERROR_SOURCE_STS__ACPRegUdefADDRErrSource__SHIFT 0x1 +#define ACP_ERROR_SOURCE_STS__ACPRegUdefADDRErrSourceOver_MASK 0x10 +#define ACP_ERROR_SOURCE_STS__ACPRegUdefADDRErrSourceOver__SHIFT 0x4 +#define ACP_ERROR_SOURCE_STS__BRBAddrErr_MASK 0x20 +#define ACP_ERROR_SOURCE_STS__BRBAddrErr__SHIFT 0x5 +#define ACP_ERROR_SOURCE_STS__BRBAddrErrSource_MASK 0x3c0 +#define ACP_ERROR_SOURCE_STS__BRBAddrErrSource__SHIFT 0x6 +#define ACP_ERROR_SOURCE_STS__BRBAddrErrSourceOver_MASK 0x400 +#define ACP_ERROR_SOURCE_STS__BRBAddrErrSourceOver__SHIFT 0xa +#define ACP_ERROR_SOURCE_STS__I2SMicOverFlowErr_MASK 0x800 +#define ACP_ERROR_SOURCE_STS__I2SMicOverFlowErr__SHIFT 0xb +#define ACP_ERROR_SOURCE_STS__I2SSpeaker0OverFlowErr_MASK 0x1000 +#define ACP_ERROR_SOURCE_STS__I2SSpeaker0OverFlowErr__SHIFT 0xc +#define ACP_ERROR_SOURCE_STS__I2SSpeaker1OverFlowErr_MASK 0x2000 +#define ACP_ERROR_SOURCE_STS__I2SSpeaker1OverFlowErr__SHIFT 0xd +#define ACP_ERROR_SOURCE_STS__I2SBTRxFifoOverFlowErr_MASK 0x4000 +#define ACP_ERROR_SOURCE_STS__I2SBTRxFifoOverFlowErr__SHIFT 0xe +#define ACP_ERROR_SOURCE_STS__DSPAdrTransRangeErr_MASK 0x8000 +#define ACP_ERROR_SOURCE_STS__DSPAdrTransRangeErr__SHIFT 0xf +#define ACP_ERROR_SOURCE_STS__DSPAdrTransRangeErrSource_MASK 0x70000 +#define ACP_ERROR_SOURCE_STS__DSPAdrTransRangeErrSource__SHIFT 0x10 +#define ACP_ERROR_SOURCE_STS__DSPAdrTransRangeErrSourceOver_MASK 0x80000 +#define ACP_ERROR_SOURCE_STS__DSPAdrTransRangeErrSourceOver__SHIFT 0x13 +#define ACP_ERROR_SOURCE_STS__DAGBErr_MASK 0x100000 +#define ACP_ERROR_SOURCE_STS__DAGBErr__SHIFT 0x14 +#define ACP_ERROR_SOURCE_STS__DAGBErrSource_MASK 0x1e00000 +#define ACP_ERROR_SOURCE_STS__DAGBErrSource__SHIFT 0x15 +#define ACP_ERROR_SOURCE_STS__DAGBErrSourceOver_MASK 0x2000000 +#define ACP_ERROR_SOURCE_STS__DAGBErrSourceOver__SHIFT 0x19 +#define ACP_ERROR_SOURCE_STS__DMATermOnErr_MASK 0x4000000 +#define ACP_ERROR_SOURCE_STS__DMATermOnErr__SHIFT 0x1a +#define ACP_ERROR_SOURCE_STS__I2SBTTxFifoOverFlowErr_MASK 0x10000000 +#define ACP_ERROR_SOURCE_STS__I2SBTTxFifoOverFlowErr__SHIFT 0x1c +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntHostDSP0_MASK 0x1 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntHostDSP0__SHIFT 0x0 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntHostDSP1_MASK 0x2 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntHostDSP1__SHIFT 0x1 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntHostDSP2_MASK 0x4 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntHostDSP2__SHIFT 0x2 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSPnDSP0_MASK 0x100 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSPnDSP0__SHIFT 0x8 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSPnDSP1_MASK 0x200 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSPnDSP1__SHIFT 0x9 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSPnDSP2_MASK 0x400 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSPnDSP2__SHIFT 0xa +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSP0Host_MASK 0x10000 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSP0Host__SHIFT 0x10 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSP1Host_MASK 0x20000 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSP1Host__SHIFT 0x11 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSP2Host_MASK 0x40000 +#define ACP_DSP_SW_INTR_TRIG__TrigSWIntDSP2Host__SHIFT 0x12 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntHostDSP0_MASK 0x1 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntHostDSP0__SHIFT 0x0 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntHostDSP1_MASK 0x2 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntHostDSP1__SHIFT 0x1 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntHostDSP2_MASK 0x4 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntHostDSP2__SHIFT 0x2 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntDSPnDSP0_MASK 0x100 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntDSPnDSP0__SHIFT 0x8 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntDSPnDSP1_MASK 0x200 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntDSPnDSP1__SHIFT 0x9 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntDSPnDSP2_MASK 0x400 +#define ACP_DSP_SW_INTR_CNTL__EnbSWIntDSPnDSP2__SHIFT 0xa +#define ACP_DSP_SW_INTR_CNTL__EnbKernelIntrDSP0Mask_MASK 0x10000 +#define ACP_DSP_SW_INTR_CNTL__EnbKernelIntrDSP0Mask__SHIFT 0x10 +#define ACP_DSP_SW_INTR_CNTL__EmbKernelIntrDSP1Mask_MASK 0x20000 +#define ACP_DSP_SW_INTR_CNTL__EmbKernelIntrDSP1Mask__SHIFT 0x11 +#define ACP_DSP_SW_INTR_CNTL__EmbKernelIntrDSP2Mask_MASK 0x40000 +#define ACP_DSP_SW_INTR_CNTL__EmbKernelIntrDSP2Mask__SHIFT 0x12 +#define ACP_DAGBG_TIMEOUT_CNTL__DAGBGTimeoutValue_MASK 0x3ffff +#define ACP_DAGBG_TIMEOUT_CNTL__DAGBGTimeoutValue__SHIFT 0x0 +#define ACP_DAGBG_TIMEOUT_CNTL__CntEn_MASK 0x80000000 +#define ACP_DAGBG_TIMEOUT_CNTL__CntEn__SHIFT 0x1f +#define ACP_DAGBO_TIMEOUT_CNTL__DAGBOTimeoutValue_MASK 0x3ffff +#define ACP_DAGBO_TIMEOUT_CNTL__DAGBOTimeoutValue__SHIFT 0x0 +#define ACP_DAGBO_TIMEOUT_CNTL__CntEn_MASK 0x80000000 +#define ACP_DAGBO_TIMEOUT_CNTL__CntEn__SHIFT 0x1f +#define ACP_EXTERNAL_INTR_STAT__ACPErrStat_MASK 0x1 +#define ACP_EXTERNAL_INTR_STAT__ACPErrStat__SHIFT 0x0 +#define ACP_EXTERNAL_INTR_STAT__ACPErrAck_MASK 0x1 +#define ACP_EXTERNAL_INTR_STAT__ACPErrAck__SHIFT 0x0 +#define ACP_EXTERNAL_INTR_STAT__I2SMicDataAvStat_MASK 0x2 +#define ACP_EXTERNAL_INTR_STAT__I2SMicDataAvStat__SHIFT 0x1 +#define ACP_EXTERNAL_INTR_STAT__I2SMicDataAvAck_MASK 0x2 +#define ACP_EXTERNAL_INTR_STAT__I2SMicDataAvAck__SHIFT 0x1 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr0DataEmptyStat_MASK 0x4 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr0DataEmptyStat__SHIFT 0x2 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr0DataEmptyAck_MASK 0x4 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr0DataEmptyAck__SHIFT 0x2 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr1DataEmptyStat_MASK 0x8 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr1DataEmptyStat__SHIFT 0x3 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr1DataEmptyAck_MASK 0x8 +#define ACP_EXTERNAL_INTR_STAT__I2SSpkr1DataEmptyAck__SHIFT 0x3 +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataAvStat_MASK 0x10 +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataAvStat__SHIFT 0x4 +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataAvAck_MASK 0x10 +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataAvAck__SHIFT 0x4 +#define ACP_EXTERNAL_INTR_STAT__AzaliaIntrStat_MASK 0x40 +#define ACP_EXTERNAL_INTR_STAT__AzaliaIntrStat__SHIFT 0x6 +#define ACP_EXTERNAL_INTR_STAT__AzaliaIntrAck_MASK 0x40 +#define ACP_EXTERNAL_INTR_STAT__AzaliaIntrAck__SHIFT 0x6 +#define ACP_EXTERNAL_INTR_STAT__DSP0TimeoutStat_MASK 0x100 +#define ACP_EXTERNAL_INTR_STAT__DSP0TimeoutStat__SHIFT 0x8 +#define ACP_EXTERNAL_INTR_STAT__DSP0TimeoutAck_MASK 0x100 +#define ACP_EXTERNAL_INTR_STAT__DSP0TimeoutAck__SHIFT 0x8 +#define ACP_EXTERNAL_INTR_STAT__DSP1TimeoutStat_MASK 0x200 +#define ACP_EXTERNAL_INTR_STAT__DSP1TimeoutStat__SHIFT 0x9 +#define ACP_EXTERNAL_INTR_STAT__DSP1TimeoutAck_MASK 0x200 +#define ACP_EXTERNAL_INTR_STAT__DSP1TimeoutAck__SHIFT 0x9 +#define ACP_EXTERNAL_INTR_STAT__DSP2TimeoutStat_MASK 0x400 +#define ACP_EXTERNAL_INTR_STAT__DSP2TimeoutStat__SHIFT 0xa +#define ACP_EXTERNAL_INTR_STAT__DSP2TimeoutAck_MASK 0x400 +#define ACP_EXTERNAL_INTR_STAT__DSP2TimeoutAck__SHIFT 0xa +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataEmptyStat_MASK 0x800 +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataEmptyStat__SHIFT 0xb +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataEmptyAck_MASK 0x800 +#define ACP_EXTERNAL_INTR_STAT__I2SBTDataEmptyAck__SHIFT 0xb +#define ACP_EXTERNAL_INTR_STAT__DMAIOCStat_MASK 0xffff0000 +#define ACP_EXTERNAL_INTR_STAT__DMAIOCStat__SHIFT 0x10 +#define ACP_EXTERNAL_INTR_STAT__DMAIOCAck_MASK 0xffff0000 +#define ACP_EXTERNAL_INTR_STAT__DMAIOCAck__SHIFT 0x10 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP0Stat_MASK 0x1 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP0Stat__SHIFT 0x0 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP0Ack_MASK 0x1 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP0Ack__SHIFT 0x0 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP1Stat_MASK 0x2 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP1Stat__SHIFT 0x1 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP1Ack_MASK 0x2 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP1Ack__SHIFT 0x1 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP2Stat_MASK 0x4 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP2Stat__SHIFT 0x2 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP2Ack_MASK 0x4 +#define ACP_DSP_SW_INTR_STAT__SWIntHostDSP2Ack__SHIFT 0x2 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP0Stat_MASK 0x100 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP0Stat__SHIFT 0x8 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP0Ack_MASK 0x100 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP0Ack__SHIFT 0x8 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP1Stat_MASK 0x200 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP1Stat__SHIFT 0x9 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP1Ack_MASK 0x200 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP1Ack__SHIFT 0x9 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP2Stat_MASK 0x400 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP2Stat__SHIFT 0xa +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP2Ack_MASK 0x400 +#define ACP_DSP_SW_INTR_STAT__SWIntDSPnDSP2Ack__SHIFT 0xa +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP0Stat_MASK 0x10000 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP0Stat__SHIFT 0x10 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP0Ack_MASK 0x10000 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP0Ack__SHIFT 0x10 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP1Stat_MASK 0x20000 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP1Stat__SHIFT 0x11 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP1Ack_MASK 0x20000 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP1Ack__SHIFT 0x11 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP2Stat_MASK 0x40000 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP2Stat__SHIFT 0x12 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP2Ack_MASK 0x40000 +#define ACP_DSP_SW_INTR_STAT__SWKernelIntrDSP2Ack__SHIFT 0x12 +#define ACP_DSP0_INTR_CNTL__ACPErrMask_MASK 0x1 +#define ACP_DSP0_INTR_CNTL__ACPErrMask__SHIFT 0x0 +#define ACP_DSP0_INTR_CNTL__I2SMicDataAvMask_MASK 0x2 +#define ACP_DSP0_INTR_CNTL__I2SMicDataAvMask__SHIFT 0x1 +#define ACP_DSP0_INTR_CNTL__I2SSpkr0DataEmptyMask_MASK 0x4 +#define ACP_DSP0_INTR_CNTL__I2SSpkr0DataEmptyMask__SHIFT 0x2 +#define ACP_DSP0_INTR_CNTL__I2SSpkr1DataEmptyMask_MASK 0x8 +#define ACP_DSP0_INTR_CNTL__I2SSpkr1DataEmptyMask__SHIFT 0x3 +#define ACP_DSP0_INTR_CNTL__I2SBTDataAvMask_MASK 0x10 +#define ACP_DSP0_INTR_CNTL__I2SBTDataAvMask__SHIFT 0x4 +#define ACP_DSP0_INTR_CNTL__AzaliaIntrMask_MASK 0x40 +#define ACP_DSP0_INTR_CNTL__AzaliaIntrMask__SHIFT 0x6 +#define ACP_DSP0_INTR_CNTL__SMUMailboxWriteMask_MASK 0x100 +#define ACP_DSP0_INTR_CNTL__SMUMailboxWriteMask__SHIFT 0x8 +#define ACP_DSP0_INTR_CNTL__SMUStutterStatusMask_MASK 0x200 +#define ACP_DSP0_INTR_CNTL__SMUStutterStatusMask__SHIFT 0x9 +#define ACP_DSP0_INTR_CNTL__MCStutterStatusMask_MASK 0x400 +#define ACP_DSP0_INTR_CNTL__MCStutterStatusMask__SHIFT 0xa +#define ACP_DSP0_INTR_CNTL__DSPExtTimerMask_MASK 0x800 +#define ACP_DSP0_INTR_CNTL__DSPExtTimerMask__SHIFT 0xb +#define ACP_DSP0_INTR_CNTL__DSPSemRespMask_MASK 0x1000 +#define ACP_DSP0_INTR_CNTL__DSPSemRespMask__SHIFT 0xc +#define ACP_DSP0_INTR_CNTL__I2SBTDataEmptyMask_MASK 0x2000 +#define ACP_DSP0_INTR_CNTL__I2SBTDataEmptyMask__SHIFT 0xd +#define ACP_DSP0_INTR_CNTL__DMAIOCMask_MASK 0xffff0000 +#define ACP_DSP0_INTR_CNTL__DMAIOCMask__SHIFT 0x10 +#define ACP_DSP0_INTR_STAT__ACPErrStat_MASK 0x1 +#define ACP_DSP0_INTR_STAT__ACPErrStat__SHIFT 0x0 +#define ACP_DSP0_INTR_STAT__ACPErrAck_MASK 0x1 +#define ACP_DSP0_INTR_STAT__ACPErrAck__SHIFT 0x0 +#define ACP_DSP0_INTR_STAT__I2SMicDataAvStat_MASK 0x2 +#define ACP_DSP0_INTR_STAT__I2SMicDataAvStat__SHIFT 0x1 +#define ACP_DSP0_INTR_STAT__I2SMicDataAvAck_MASK 0x2 +#define ACP_DSP0_INTR_STAT__I2SMicDataAvAck__SHIFT 0x1 +#define ACP_DSP0_INTR_STAT__I2SSpkr0DataEmptyStat_MASK 0x4 +#define ACP_DSP0_INTR_STAT__I2SSpkr0DataEmptyStat__SHIFT 0x2 +#define ACP_DSP0_INTR_STAT__I2SSpkr0DataEmptyAck_MASK 0x4 +#define ACP_DSP0_INTR_STAT__I2SSpkr0DataEmptyAck__SHIFT 0x2 +#define ACP_DSP0_INTR_STAT__I2SSpkr1DataEmptyStat_MASK 0x8 +#define ACP_DSP0_INTR_STAT__I2SSpkr1DataEmptyStat__SHIFT 0x3 +#define ACP_DSP0_INTR_STAT__I2SSpkr1DataEmptyAck_MASK 0x8 +#define ACP_DSP0_INTR_STAT__I2SSpkr1DataEmptyAck__SHIFT 0x3 +#define ACP_DSP0_INTR_STAT__I2SBTDataAvStat_MASK 0x10 +#define ACP_DSP0_INTR_STAT__I2SBTDataAvStat__SHIFT 0x4 +#define ACP_DSP0_INTR_STAT__I2SBTDataAvAck_MASK 0x10 +#define ACP_DSP0_INTR_STAT__I2SBTDataAvAck__SHIFT 0x4 +#define ACP_DSP0_INTR_STAT__AzaliaIntrStat_MASK 0x40 +#define ACP_DSP0_INTR_STAT__AzaliaIntrStat__SHIFT 0x6 +#define ACP_DSP0_INTR_STAT__AzaliaIntrAck_MASK 0x40 +#define ACP_DSP0_INTR_STAT__AzaliaIntrAck__SHIFT 0x6 +#define ACP_DSP0_INTR_STAT__SMUMailboxWriteStat_MASK 0x100 +#define ACP_DSP0_INTR_STAT__SMUMailboxWriteStat__SHIFT 0x8 +#define ACP_DSP0_INTR_STAT__SMUMailboxWriteAck_MASK 0x100 +#define ACP_DSP0_INTR_STAT__SMUMailboxWriteAck__SHIFT 0x8 +#define ACP_DSP0_INTR_STAT__SMUStutterStatusStat_MASK 0x200 +#define ACP_DSP0_INTR_STAT__SMUStutterStatusStat__SHIFT 0x9 +#define ACP_DSP0_INTR_STAT__SMUStutterStatusAck_MASK 0x200 +#define ACP_DSP0_INTR_STAT__SMUStutterStatusAck__SHIFT 0x9 +#define ACP_DSP0_INTR_STAT__MCStutterStatusStat_MASK 0x400 +#define ACP_DSP0_INTR_STAT__MCStutterStatusStat__SHIFT 0xa +#define ACP_DSP0_INTR_STAT__MCStutterStatusAck_MASK 0x400 +#define ACP_DSP0_INTR_STAT__MCStutterStatusAck__SHIFT 0xa +#define ACP_DSP0_INTR_STAT__DSPExtTimerStat_MASK 0x800 +#define ACP_DSP0_INTR_STAT__DSPExtTimerStat__SHIFT 0xb +#define ACP_DSP0_INTR_STAT__DSPExtTimerAck_MASK 0x800 +#define ACP_DSP0_INTR_STAT__DSPExtTimerAck__SHIFT 0xb +#define ACP_DSP0_INTR_STAT__DSPSemRespStat_MASK 0x1000 +#define ACP_DSP0_INTR_STAT__DSPSemRespStat__SHIFT 0xc +#define ACP_DSP0_INTR_STAT__DSPSemRespAck_MASK 0x1000 +#define ACP_DSP0_INTR_STAT__DSPSemRespAck__SHIFT 0xc +#define ACP_DSP0_INTR_STAT__I2SBTDataEmptyStat_MASK 0x2000 +#define ACP_DSP0_INTR_STAT__I2SBTDataEmptyStat__SHIFT 0xd +#define ACP_DSP0_INTR_STAT__I2SBTDataEmptyAck_MASK 0x2000 +#define ACP_DSP0_INTR_STAT__I2SBTDataEmptyAck__SHIFT 0xd +#define ACP_DSP0_INTR_STAT__DMAIOCStat_MASK 0xffff0000 +#define ACP_DSP0_INTR_STAT__DMAIOCStat__SHIFT 0x10 +#define ACP_DSP0_INTR_STAT__DMAIOCAck_MASK 0xffff0000 +#define ACP_DSP0_INTR_STAT__DMAIOCAck__SHIFT 0x10 +#define ACP_DSP0_TIMEOUT_CNTL__DSP0TimeoutValue_MASK 0x3ffff +#define ACP_DSP0_TIMEOUT_CNTL__DSP0TimeoutValue__SHIFT 0x0 +#define ACP_DSP0_TIMEOUT_CNTL__CntEn_MASK 0x80000000 +#define ACP_DSP0_TIMEOUT_CNTL__CntEn__SHIFT 0x1f +#define ACP_DSP1_INTR_CNTL__ACPErrMask_MASK 0x1 +#define ACP_DSP1_INTR_CNTL__ACPErrMask__SHIFT 0x0 +#define ACP_DSP1_INTR_CNTL__I2SMicDataAvMask_MASK 0x2 +#define ACP_DSP1_INTR_CNTL__I2SMicDataAvMask__SHIFT 0x1 +#define ACP_DSP1_INTR_CNTL__I2SSpkr0DataEmptyMask_MASK 0x4 +#define ACP_DSP1_INTR_CNTL__I2SSpkr0DataEmptyMask__SHIFT 0x2 +#define ACP_DSP1_INTR_CNTL__I2SSpkr1DataEmptyMask_MASK 0x8 +#define ACP_DSP1_INTR_CNTL__I2SSpkr1DataEmptyMask__SHIFT 0x3 +#define ACP_DSP1_INTR_CNTL__I2SBTDataAvMask_MASK 0x10 +#define ACP_DSP1_INTR_CNTL__I2SBTDataAvMask__SHIFT 0x4 +#define ACP_DSP1_INTR_CNTL__AzaliaIntrMask_MASK 0x40 +#define ACP_DSP1_INTR_CNTL__AzaliaIntrMask__SHIFT 0x6 +#define ACP_DSP1_INTR_CNTL__SMUMailboxWriteMask_MASK 0x100 +#define ACP_DSP1_INTR_CNTL__SMUMailboxWriteMask__SHIFT 0x8 +#define ACP_DSP1_INTR_CNTL__SMUStutterStatusMask_MASK 0x200 +#define ACP_DSP1_INTR_CNTL__SMUStutterStatusMask__SHIFT 0x9 +#define ACP_DSP1_INTR_CNTL__MCStutterStatusMask_MASK 0x400 +#define ACP_DSP1_INTR_CNTL__MCStutterStatusMask__SHIFT 0xa +#define ACP_DSP1_INTR_CNTL__DSPExtTimerMask_MASK 0x800 +#define ACP_DSP1_INTR_CNTL__DSPExtTimerMask__SHIFT 0xb +#define ACP_DSP1_INTR_CNTL__DSPSemRespMask_MASK 0x1000 +#define ACP_DSP1_INTR_CNTL__DSPSemRespMask__SHIFT 0xc +#define ACP_DSP1_INTR_CNTL__I2SBTDataEmptyMask_MASK 0x2000 +#define ACP_DSP1_INTR_CNTL__I2SBTDataEmptyMask__SHIFT 0xd +#define ACP_DSP1_INTR_CNTL__DMAIOCMask_MASK 0xffff0000 +#define ACP_DSP1_INTR_CNTL__DMAIOCMask__SHIFT 0x10 +#define ACP_DSP1_INTR_STAT__ACPErrStat_MASK 0x1 +#define ACP_DSP1_INTR_STAT__ACPErrStat__SHIFT 0x0 +#define ACP_DSP1_INTR_STAT__ACPErrAck_MASK 0x1 +#define ACP_DSP1_INTR_STAT__ACPErrAck__SHIFT 0x0 +#define ACP_DSP1_INTR_STAT__I2SMicDataAvStat_MASK 0x2 +#define ACP_DSP1_INTR_STAT__I2SMicDataAvStat__SHIFT 0x1 +#define ACP_DSP1_INTR_STAT__I2SMicDataAvAck_MASK 0x2 +#define ACP_DSP1_INTR_STAT__I2SMicDataAvAck__SHIFT 0x1 +#define ACP_DSP1_INTR_STAT__I2SSpkr0DataEmptyStat_MASK 0x4 +#define ACP_DSP1_INTR_STAT__I2SSpkr0DataEmptyStat__SHIFT 0x2 +#define ACP_DSP1_INTR_STAT__I2SSpkr0DataEmptyAck_MASK 0x4 +#define ACP_DSP1_INTR_STAT__I2SSpkr0DataEmptyAck__SHIFT 0x2 +#define ACP_DSP1_INTR_STAT__I2SSpkr1DataEmptyStat_MASK 0x8 +#define ACP_DSP1_INTR_STAT__I2SSpkr1DataEmptyStat__SHIFT 0x3 +#define ACP_DSP1_INTR_STAT__I2SSpkr1DataEmptyAck_MASK 0x8 +#define ACP_DSP1_INTR_STAT__I2SSpkr1DataEmptyAck__SHIFT 0x3 +#define ACP_DSP1_INTR_STAT__I2SBTDataAvStat_MASK 0x10 +#define ACP_DSP1_INTR_STAT__I2SBTDataAvStat__SHIFT 0x4 +#define ACP_DSP1_INTR_STAT__I2SBTDataAvAck_MASK 0x10 +#define ACP_DSP1_INTR_STAT__I2SBTDataAvAck__SHIFT 0x4 +#define ACP_DSP1_INTR_STAT__AzaliaIntrStat_MASK 0x40 +#define ACP_DSP1_INTR_STAT__AzaliaIntrStat__SHIFT 0x6 +#define ACP_DSP1_INTR_STAT__AzaliaIntrAck_MASK 0x40 +#define ACP_DSP1_INTR_STAT__AzaliaIntrAck__SHIFT 0x6 +#define ACP_DSP1_INTR_STAT__SMUMailboxWriteStat_MASK 0x100 +#define ACP_DSP1_INTR_STAT__SMUMailboxWriteStat__SHIFT 0x8 +#define ACP_DSP1_INTR_STAT__SMUMailboxWriteAck_MASK 0x100 +#define ACP_DSP1_INTR_STAT__SMUMailboxWriteAck__SHIFT 0x8 +#define ACP_DSP1_INTR_STAT__SMUStutterStatusStat_MASK 0x200 +#define ACP_DSP1_INTR_STAT__SMUStutterStatusStat__SHIFT 0x9 +#define ACP_DSP1_INTR_STAT__SMUStutterStatusAck_MASK 0x200 +#define ACP_DSP1_INTR_STAT__SMUStutterStatusAck__SHIFT 0x9 +#define ACP_DSP1_INTR_STAT__MCStutterStatusStat_MASK 0x400 +#define ACP_DSP1_INTR_STAT__MCStutterStatusStat__SHIFT 0xa +#define ACP_DSP1_INTR_STAT__MCStutterStatusAck_MASK 0x400 +#define ACP_DSP1_INTR_STAT__MCStutterStatusAck__SHIFT 0xa +#define ACP_DSP1_INTR_STAT__DSPExtTimerStat_MASK 0x800 +#define ACP_DSP1_INTR_STAT__DSPExtTimerStat__SHIFT 0xb +#define ACP_DSP1_INTR_STAT__DSPExtTimerAck_MASK 0x800 +#define ACP_DSP1_INTR_STAT__DSPExtTimerAck__SHIFT 0xb +#define ACP_DSP1_INTR_STAT__DSPSemRespStat_MASK 0x1000 +#define ACP_DSP1_INTR_STAT__DSPSemRespStat__SHIFT 0xc +#define ACP_DSP1_INTR_STAT__DSPSemRespAck_MASK 0x1000 +#define ACP_DSP1_INTR_STAT__DSPSemRespAck__SHIFT 0xc +#define ACP_DSP1_INTR_STAT__I2SBTDataEmptyStat_MASK 0x2000 +#define ACP_DSP1_INTR_STAT__I2SBTDataEmptyStat__SHIFT 0xd +#define ACP_DSP1_INTR_STAT__I2SBTDataEmptyAck_MASK 0x2000 +#define ACP_DSP1_INTR_STAT__I2SBTDataEmptyAck__SHIFT 0xd +#define ACP_DSP1_INTR_STAT__DMAIOCStat_MASK 0xffff0000 +#define ACP_DSP1_INTR_STAT__DMAIOCStat__SHIFT 0x10 +#define ACP_DSP1_INTR_STAT__DMAIOCAck_MASK 0xffff0000 +#define ACP_DSP1_INTR_STAT__DMAIOCAck__SHIFT 0x10 +#define ACP_DSP1_TIMEOUT_CNTL__DSP1TimeoutValue_MASK 0x3ffff +#define ACP_DSP1_TIMEOUT_CNTL__DSP1TimeoutValue__SHIFT 0x0 +#define ACP_DSP1_TIMEOUT_CNTL__CntEn_MASK 0x80000000 +#define ACP_DSP1_TIMEOUT_CNTL__CntEn__SHIFT 0x1f +#define ACP_DSP2_INTR_CNTL__ACPErrMask_MASK 0x1 +#define ACP_DSP2_INTR_CNTL__ACPErrMask__SHIFT 0x0 +#define ACP_DSP2_INTR_CNTL__I2SMicDataAvMask_MASK 0x2 +#define ACP_DSP2_INTR_CNTL__I2SMicDataAvMask__SHIFT 0x1 +#define ACP_DSP2_INTR_CNTL__I2SSpkr0DataEmptyMask_MASK 0x4 +#define ACP_DSP2_INTR_CNTL__I2SSpkr0DataEmptyMask__SHIFT 0x2 +#define ACP_DSP2_INTR_CNTL__I2SSpkr1DataEmptyMask_MASK 0x8 +#define ACP_DSP2_INTR_CNTL__I2SSpkr1DataEmptyMask__SHIFT 0x3 +#define ACP_DSP2_INTR_CNTL__I2SBTDataAvMask_MASK 0x10 +#define ACP_DSP2_INTR_CNTL__I2SBTDataAvMask__SHIFT 0x4 +#define ACP_DSP2_INTR_CNTL__AzaliaIntrMask_MASK 0x40 +#define ACP_DSP2_INTR_CNTL__AzaliaIntrMask__SHIFT 0x6 +#define ACP_DSP2_INTR_CNTL__SMUMailboxWriteMask_MASK 0x100 +#define ACP_DSP2_INTR_CNTL__SMUMailboxWriteMask__SHIFT 0x8 +#define ACP_DSP2_INTR_CNTL__SMUStutterStatusMask_MASK 0x200 +#define ACP_DSP2_INTR_CNTL__SMUStutterStatusMask__SHIFT 0x9 +#define ACP_DSP2_INTR_CNTL__MCStutterStatusMask_MASK 0x400 +#define ACP_DSP2_INTR_CNTL__MCStutterStatusMask__SHIFT 0xa +#define ACP_DSP2_INTR_CNTL__DSPExtTimerMask_MASK 0x800 +#define ACP_DSP2_INTR_CNTL__DSPExtTimerMask__SHIFT 0xb +#define ACP_DSP2_INTR_CNTL__DSPSemRespMask_MASK 0x1000 +#define ACP_DSP2_INTR_CNTL__DSPSemRespMask__SHIFT 0xc +#define ACP_DSP2_INTR_CNTL__I2SBTDataEmptyMask_MASK 0x2000 +#define ACP_DSP2_INTR_CNTL__I2SBTDataEmptyMask__SHIFT 0xd +#define ACP_DSP2_INTR_CNTL__DMAIOCMask_MASK 0xffff0000 +#define ACP_DSP2_INTR_CNTL__DMAIOCMask__SHIFT 0x10 +#define ACP_DSP2_INTR_STAT__ACPErrStat_MASK 0x1 +#define ACP_DSP2_INTR_STAT__ACPErrStat__SHIFT 0x0 +#define ACP_DSP2_INTR_STAT__ACPErrAck_MASK 0x1 +#define ACP_DSP2_INTR_STAT__ACPErrAck__SHIFT 0x0 +#define ACP_DSP2_INTR_STAT__I2SMicDataAvStat_MASK 0x2 +#define ACP_DSP2_INTR_STAT__I2SMicDataAvStat__SHIFT 0x1 +#define ACP_DSP2_INTR_STAT__I2SMicDataAvAck_MASK 0x2 +#define ACP_DSP2_INTR_STAT__I2SMicDataAvAck__SHIFT 0x1 +#define ACP_DSP2_INTR_STAT__I2SSpkr0DataEmptyStat_MASK 0x4 +#define ACP_DSP2_INTR_STAT__I2SSpkr0DataEmptyStat__SHIFT 0x2 +#define ACP_DSP2_INTR_STAT__I2SSpkr0DataEmptyAck_MASK 0x4 +#define ACP_DSP2_INTR_STAT__I2SSpkr0DataEmptyAck__SHIFT 0x2 +#define ACP_DSP2_INTR_STAT__I2SSpkr1DataEmptyStat_MASK 0x8 +#define ACP_DSP2_INTR_STAT__I2SSpkr1DataEmptyStat__SHIFT 0x3 +#define ACP_DSP2_INTR_STAT__I2SSpkr1DataEmptyAck_MASK 0x8 +#define ACP_DSP2_INTR_STAT__I2SSpkr1DataEmptyAck__SHIFT 0x3 +#define ACP_DSP2_INTR_STAT__I2SBTDataAvStat_MASK 0x10 +#define ACP_DSP2_INTR_STAT__I2SBTDataAvStat__SHIFT 0x4 +#define ACP_DSP2_INTR_STAT__I2SBTDataAvAck_MASK 0x10 +#define ACP_DSP2_INTR_STAT__I2SBTDataAvAck__SHIFT 0x4 +#define ACP_DSP2_INTR_STAT__AzaliaIntrStat_MASK 0x40 +#define ACP_DSP2_INTR_STAT__AzaliaIntrStat__SHIFT 0x6 +#define ACP_DSP2_INTR_STAT__AzaliaIntrAck_MASK 0x40 +#define ACP_DSP2_INTR_STAT__AzaliaIntrAck__SHIFT 0x6 +#define ACP_DSP2_INTR_STAT__SMUMailboxWriteStat_MASK 0x100 +#define ACP_DSP2_INTR_STAT__SMUMailboxWriteStat__SHIFT 0x8 +#define ACP_DSP2_INTR_STAT__SMUMailboxWriteAck_MASK 0x100 +#define ACP_DSP2_INTR_STAT__SMUMailboxWriteAck__SHIFT 0x8 +#define ACP_DSP2_INTR_STAT__SMUStutterStatusStat_MASK 0x200 +#define ACP_DSP2_INTR_STAT__SMUStutterStatusStat__SHIFT 0x9 +#define ACP_DSP2_INTR_STAT__SMUStutterStatusAck_MASK 0x200 +#define ACP_DSP2_INTR_STAT__SMUStutterStatusAck__SHIFT 0x9 +#define ACP_DSP2_INTR_STAT__MCStutterStatusStat_MASK 0x400 +#define ACP_DSP2_INTR_STAT__MCStutterStatusStat__SHIFT 0xa +#define ACP_DSP2_INTR_STAT__MCStutterStatusAck_MASK 0x400 +#define ACP_DSP2_INTR_STAT__MCStutterStatusAck__SHIFT 0xa +#define ACP_DSP2_INTR_STAT__DSPExtTimerStat_MASK 0x800 +#define ACP_DSP2_INTR_STAT__DSPExtTimerStat__SHIFT 0xb +#define ACP_DSP2_INTR_STAT__DSPExtTimerAck_MASK 0x800 +#define ACP_DSP2_INTR_STAT__DSPExtTimerAck__SHIFT 0xb +#define ACP_DSP2_INTR_STAT__DSPSemRespStat_MASK 0x1000 +#define ACP_DSP2_INTR_STAT__DSPSemRespStat__SHIFT 0xc +#define ACP_DSP2_INTR_STAT__DSPSemRespAck_MASK 0x1000 +#define ACP_DSP2_INTR_STAT__DSPSemRespAck__SHIFT 0xc +#define ACP_DSP2_INTR_STAT__I2SBTDataEmptyStat_MASK 0x2000 +#define ACP_DSP2_INTR_STAT__I2SBTDataEmptyStat__SHIFT 0xd +#define ACP_DSP2_INTR_STAT__I2SBTDataEmptyAck_MASK 0x2000 +#define ACP_DSP2_INTR_STAT__I2SBTDataEmptyAck__SHIFT 0xd +#define ACP_DSP2_INTR_STAT__DMAIOCStat_MASK 0xffff0000 +#define ACP_DSP2_INTR_STAT__DMAIOCStat__SHIFT 0x10 +#define ACP_DSP2_INTR_STAT__DMAIOCAck_MASK 0xffff0000 +#define ACP_DSP2_INTR_STAT__DMAIOCAck__SHIFT 0x10 +#define ACP_DSP2_TIMEOUT_CNTL__DSP2TimeoutValue_MASK 0x3ffff +#define ACP_DSP2_TIMEOUT_CNTL__DSP2TimeoutValue__SHIFT 0x0 +#define ACP_DSP2_TIMEOUT_CNTL__CntEn_MASK 0x80000000 +#define ACP_DSP2_TIMEOUT_CNTL__CntEn__SHIFT 0x1f +#define ACP_DSP0_EXT_TIMER_CNTL__TimerCount_MASK 0xffffff +#define ACP_DSP0_EXT_TIMER_CNTL__TimerCount__SHIFT 0x0 +#define ACP_DSP0_EXT_TIMER_CNTL__TimerCntl_MASK 0xc0000000 +#define ACP_DSP0_EXT_TIMER_CNTL__TimerCntl__SHIFT 0x1e +#define ACP_DSP1_EXT_TIMER_CNTL__TimerCount_MASK 0xffffff +#define ACP_DSP1_EXT_TIMER_CNTL__TimerCount__SHIFT 0x0 +#define ACP_DSP1_EXT_TIMER_CNTL__TimerCntl_MASK 0xc0000000 +#define ACP_DSP1_EXT_TIMER_CNTL__TimerCntl__SHIFT 0x1e +#define ACP_DSP2_EXT_TIMER_CNTL__TimerCount_MASK 0xffffff +#define ACP_DSP2_EXT_TIMER_CNTL__TimerCount__SHIFT 0x0 +#define ACP_DSP2_EXT_TIMER_CNTL__TimerCntl_MASK 0xc0000000 +#define ACP_DSP2_EXT_TIMER_CNTL__TimerCntl__SHIFT 0x1e +#define ACP_AXI2DAGB_SEM_0__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_0__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_1__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_1__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_2__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_2__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_3__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_3__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_4__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_4__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_5__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_5__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_6__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_6__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_7__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_7__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_8__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_8__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_9__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_9__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_10__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_10__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_11__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_11__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_12__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_12__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_13__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_13__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_14__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_14__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_15__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_15__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_16__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_16__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_17__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_17__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_18__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_18__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_19__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_19__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_20__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_20__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_21__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_21__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_22__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_22__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_23__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_23__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_24__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_24__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_25__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_25__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_26__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_26__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_27__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_27__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_28__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_28__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_29__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_29__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_30__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_30__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_31__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_31__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_32__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_32__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_33__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_33__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_34__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_34__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_35__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_35__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_36__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_36__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_37__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_37__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_38__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_38__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_39__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_39__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_40__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_40__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_41__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_41__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_42__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_42__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_43__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_43__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_44__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_44__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_45__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_45__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_46__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_46__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_AXI2DAGB_SEM_47__AXI2DAGBGblSemReg_MASK 0x1 +#define ACP_AXI2DAGB_SEM_47__AXI2DAGBGblSemReg__SHIFT 0x0 +#define ACP_SRBM_Client_Base_Addr__SRBM_Client_base_addr_MASK 0xff +#define ACP_SRBM_Client_Base_Addr__SRBM_Client_base_addr__SHIFT 0x0 +#define ACP_SRBM_Client_RDDATA__ReadData_MASK 0xffffffff +#define ACP_SRBM_Client_RDDATA__ReadData__SHIFT 0x0 +#define ACP_SRBM_Cycle_Sts__SRBM_Client_Sts_MASK 0x1 +#define ACP_SRBM_Cycle_Sts__SRBM_Client_Sts__SHIFT 0x0 +#define ACP_SRBM_Targ_Idx_Addr__SRBM_Targ_Idx_addr_MASK 0x7ffffff +#define ACP_SRBM_Targ_Idx_Addr__SRBM_Targ_Idx_addr__SHIFT 0x0 +#define ACP_SRBM_Targ_Idx_Data__SRBM_Targ_Idx_Data_MASK 0xffffffff +#define ACP_SRBM_Targ_Idx_Data__SRBM_Targ_Idx_Data__SHIFT 0x0 +#define ACP_SEMA_ADDR_LOW__ADDR_9_3_MASK 0x7f +#define ACP_SEMA_ADDR_LOW__ADDR_9_3__SHIFT 0x0 +#define ACP_SEMA_ADDR_HIGH__ADDR_39_10_MASK 0x3fffffff +#define ACP_SEMA_ADDR_HIGH__ADDR_39_10__SHIFT 0x0 +#define ACP_SEMA_CMD__REQ_CMD_MASK 0xf +#define ACP_SEMA_CMD__REQ_CMD__SHIFT 0x0 +#define ACP_SEMA_CMD__WR_PHASE_MASK 0x30 +#define ACP_SEMA_CMD__WR_PHASE__SHIFT 0x4 +#define ACP_SEMA_CMD__VMID_EN_MASK 0x80 +#define ACP_SEMA_CMD__VMID_EN__SHIFT 0x7 +#define ACP_SEMA_CMD__VMID_MASK 0xf00 +#define ACP_SEMA_CMD__VMID__SHIFT 0x8 +#define ACP_SEMA_CMD__ATC_MASK 0x1000 +#define ACP_SEMA_CMD__ATC__SHIFT 0xc +#define ACP_SEMA_STS__REQ_STS_MASK 0x3 +#define ACP_SEMA_STS__REQ_STS__SHIFT 0x0 +#define ACP_SEMA_STS__REQ_RESP_AVAIL_MASK 0x100 +#define ACP_SEMA_STS__REQ_RESP_AVAIL__SHIFT 0x8 +#define ACP_SEMA_REQ__ISSUE_POLL_REQ_MASK 0x1 +#define ACP_SEMA_REQ__ISSUE_POLL_REQ__SHIFT 0x0 +#define ACP_FW_STATUS__RUN_MASK 0x1 +#define ACP_FW_STATUS__RUN__SHIFT 0x0 +#define ACP_FUTURE_REG_ACLK_0__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_ACLK_0__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_ACLK_1__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_ACLK_1__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_ACLK_2__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_ACLK_2__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_ACLK_3__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_ACLK_3__ACPFutureReg__SHIFT 0x0 +#define ACP_FUTURE_REG_ACLK_4__ACPFutureReg_MASK 0xffffffff +#define ACP_FUTURE_REG_ACLK_4__ACPFutureReg__SHIFT 0x0 +#define ACP_TIMER__ACP_Timer_count_MASK 0xffffffff +#define ACP_TIMER__ACP_Timer_count__SHIFT 0x0 +#define ACP_TIMER_CNTL__ACP_Timer_control_MASK 0x1 +#define ACP_TIMER_CNTL__ACP_Timer_control__SHIFT 0x0 +#define ACP_DSP0_TIMER__ACP_DSP0_timer_MASK 0xffffff +#define ACP_DSP0_TIMER__ACP_DSP0_timer__SHIFT 0x0 +#define ACP_DSP1_TIMER__ACP_DSP1_timer_MASK 0xffffff +#define ACP_DSP1_TIMER__ACP_DSP1_timer__SHIFT 0x0 +#define ACP_DSP2_TIMER__ACP_DSP2_timer_MASK 0xffffff +#define ACP_DSP2_TIMER__ACP_DSP2_timer__SHIFT 0x0 +#define ACP_I2S_TRANSMIT_BYTE_CNT_HIGH__i2s_sp_tx_byte_cnt_high_MASK 0xffffffff +#define ACP_I2S_TRANSMIT_BYTE_CNT_HIGH__i2s_sp_tx_byte_cnt_high__SHIFT 0x0 +#define ACP_I2S_TRANSMIT_BYTE_CNT_LOW__i2s_sp_tx_byte_cnt_low_MASK 0xffffffff +#define ACP_I2S_TRANSMIT_BYTE_CNT_LOW__i2s_sp_tx_byte_cnt_low__SHIFT 0x0 +#define ACP_I2S_BT_TRANSMIT_BYTE_CNT_HIGH__i2s_bt_tx_byte_cnt_high_MASK 0xffffffff +#define ACP_I2S_BT_TRANSMIT_BYTE_CNT_HIGH__i2s_bt_tx_byte_cnt_high__SHIFT 0x0 +#define ACP_I2S_BT_TRANSMIT_BYTE_CNT_LOW__i2s_bt_tx_byte_cnt_low_MASK 0xffffffff +#define ACP_I2S_BT_TRANSMIT_BYTE_CNT_LOW__i2s_bt_tx_byte_cnt_low__SHIFT 0x0 +#define ACP_I2S_BT_RECEIVE_BYTE_CNT_HIGH__i2s_bt_rx_byte_cnt_high_MASK 0xffffffff +#define ACP_I2S_BT_RECEIVE_BYTE_CNT_HIGH__i2s_bt_rx_byte_cnt_high__SHIFT 0x0 +#define ACP_I2S_BT_RECEIVE_BYTE_CNT_LOW__i2s_bt_rx_byte_cnt_low_MASK 0xffffffff +#define ACP_I2S_BT_RECEIVE_BYTE_CNT_LOW__i2s_bt_rx_byte_cnt_low__SHIFT 0x0 +#define ACP_DSP0_CS_STATE__DSP0_CS_state_MASK 0x1 +#define ACP_DSP0_CS_STATE__DSP0_CS_state__SHIFT 0x0 +#define ACP_DSP1_CS_STATE__DSP1_CS_state_MASK 0x1 +#define ACP_DSP1_CS_STATE__DSP1_CS_state__SHIFT 0x0 +#define ACP_DSP2_CS_STATE__DSP2_CS_state_MASK 0x1 +#define ACP_DSP2_CS_STATE__DSP2_CS_state__SHIFT 0x0 +#define ACP_SCRATCH_REG_BASE_ADDR__SCRATCH_REG_BASE_ADDR_MASK 0x7ffff +#define ACP_SCRATCH_REG_BASE_ADDR__SCRATCH_REG_BASE_ADDR__SHIFT 0x0 +#define CC_ACP_EFUSE__DSP0_DISABLE_MASK 0x2 +#define CC_ACP_EFUSE__DSP0_DISABLE__SHIFT 0x1 +#define CC_ACP_EFUSE__DSP1_DISABLE_MASK 0x4 +#define CC_ACP_EFUSE__DSP1_DISABLE__SHIFT 0x2 +#define CC_ACP_EFUSE__DSP2_DISABLE_MASK 0x8 +#define CC_ACP_EFUSE__DSP2_DISABLE__SHIFT 0x3 +#define CC_ACP_EFUSE__ACP_DISABLE_MASK 0x10 +#define CC_ACP_EFUSE__ACP_DISABLE__SHIFT 0x4 +#define ACP_PGFSM_RETAIN_REG__ACP_P1_ON_OFF_MASK 0x1 +#define ACP_PGFSM_RETAIN_REG__ACP_P1_ON_OFF__SHIFT 0x0 +#define ACP_PGFSM_RETAIN_REG__ACP_P2_ON_OFF_MASK 0x2 +#define ACP_PGFSM_RETAIN_REG__ACP_P2_ON_OFF__SHIFT 0x1 +#define ACP_PGFSM_RETAIN_REG__ACP_DSP0_ON_OFF_MASK 0x4 +#define ACP_PGFSM_RETAIN_REG__ACP_DSP0_ON_OFF__SHIFT 0x2 +#define ACP_PGFSM_RETAIN_REG__ACP_DSP1_ON_OFF_MASK 0x8 +#define ACP_PGFSM_RETAIN_REG__ACP_DSP1_ON_OFF__SHIFT 0x3 +#define ACP_PGFSM_RETAIN_REG__ACP_DSP2_ON_OFF_MASK 0x10 +#define ACP_PGFSM_RETAIN_REG__ACP_DSP2_ON_OFF__SHIFT 0x4 +#define ACP_PGFSM_RETAIN_REG__ACP_AZ_ON_OFF_MASK 0x20 +#define ACP_PGFSM_RETAIN_REG__ACP_AZ_ON_OFF__SHIFT 0x5 +#define ACP_PGFSM_CONFIG_REG__FSM_ADDR_MASK 0xff +#define ACP_PGFSM_CONFIG_REG__FSM_ADDR__SHIFT 0x0 +#define ACP_PGFSM_CONFIG_REG__Power_Down_MASK 0x100 +#define ACP_PGFSM_CONFIG_REG__Power_Down__SHIFT 0x8 +#define ACP_PGFSM_CONFIG_REG__Power_Up_MASK 0x200 +#define ACP_PGFSM_CONFIG_REG__Power_Up__SHIFT 0x9 +#define ACP_PGFSM_CONFIG_REG__P1_Select_MASK 0x400 +#define ACP_PGFSM_CONFIG_REG__P1_Select__SHIFT 0xa +#define ACP_PGFSM_CONFIG_REG__P2_Select_MASK 0x800 +#define ACP_PGFSM_CONFIG_REG__P2_Select__SHIFT 0xb +#define ACP_PGFSM_CONFIG_REG__Wr_MASK 0x1000 +#define ACP_PGFSM_CONFIG_REG__Wr__SHIFT 0xc +#define ACP_PGFSM_CONFIG_REG__Rd_MASK 0x2000 +#define ACP_PGFSM_CONFIG_REG__Rd__SHIFT 0xd +#define ACP_PGFSM_CONFIG_REG__RdData_Reset_MASK 0x4000 +#define ACP_PGFSM_CONFIG_REG__RdData_Reset__SHIFT 0xe +#define ACP_PGFSM_CONFIG_REG__Short_Format_MASK 0x8000 +#define ACP_PGFSM_CONFIG_REG__Short_Format__SHIFT 0xf +#define ACP_PGFSM_CONFIG_REG__BPM_CG_MG_FGCG_MASK 0x3ff0000 +#define ACP_PGFSM_CONFIG_REG__BPM_CG_MG_FGCG__SHIFT 0x10 +#define ACP_PGFSM_CONFIG_REG__SRBM_override_MASK 0x4000000 +#define ACP_PGFSM_CONFIG_REG__SRBM_override__SHIFT 0x1a +#define ACP_PGFSM_CONFIG_REG__Rsvd_BPM_Addr_MASK 0x8000000 +#define ACP_PGFSM_CONFIG_REG__Rsvd_BPM_Addr__SHIFT 0x1b +#define ACP_PGFSM_CONFIG_REG__REG_ADDR_MASK 0xf0000000 +#define ACP_PGFSM_CONFIG_REG__REG_ADDR__SHIFT 0x1c +#define ACP_PGFSM_WRITE_REG__Write_value_MASK 0xffffffff +#define ACP_PGFSM_WRITE_REG__Write_value__SHIFT 0x0 +#define ACP_PGFSM_READ_REG_0__Read_value_MASK 0xffffff +#define ACP_PGFSM_READ_REG_0__Read_value__SHIFT 0x0 +#define ACP_PGFSM_READ_REG_1__Read_value_MASK 0xffffff +#define ACP_PGFSM_READ_REG_1__Read_value__SHIFT 0x0 +#define ACP_PGFSM_READ_REG_2__Read_value_MASK 0xffffff +#define ACP_PGFSM_READ_REG_2__Read_value__SHIFT 0x0 +#define ACP_PGFSM_READ_REG_3__Read_value_MASK 0xffffff +#define ACP_PGFSM_READ_REG_3__Read_value__SHIFT 0x0 +#define ACP_PGFSM_READ_REG_4__Read_value_MASK 0xffffff +#define ACP_PGFSM_READ_REG_4__Read_value__SHIFT 0x0 +#define ACP_PGFSM_READ_REG_5__Read_value_MASK 0xffffff +#define ACP_PGFSM_READ_REG_5__Read_value__SHIFT 0x0 +#define ACP_IP_PGFSM_ENABLE__ACP_IP_ACCESS_MASK 0x1 +#define ACP_IP_PGFSM_ENABLE__ACP_IP_ACCESS__SHIFT 0x0 +#define ACP_I2S_PIN_CONFIG__ACP_I2S_PIN_CONFIG_MASK 0x3 +#define ACP_I2S_PIN_CONFIG__ACP_I2S_PIN_CONFIG__SHIFT 0x0 +#define ACP_AZALIA_I2S_SELECT__AZ_I2S_SELECT_MASK 0x1 +#define ACP_AZALIA_I2S_SELECT__AZ_I2S_SELECT__SHIFT 0x0 +#define ACP_CHIP_PKG_FOR_PAD_ISOLATION__external_fch_package_MASK 0x1 +#define ACP_CHIP_PKG_FOR_PAD_ISOLATION__external_fch_package__SHIFT 0x0 +#define ACP_AUDIO_PAD_PULLUP_PULLDOWN_CTRL__ACP_AUDIO_PAD_pullup_disable_MASK 0x7ff +#define ACP_AUDIO_PAD_PULLUP_PULLDOWN_CTRL__ACP_AUDIO_PAD_pullup_disable__SHIFT 0x0 +#define ACP_AUDIO_PAD_PULLUP_PULLDOWN_CTRL__ACP_AUDIO_PAD_pulldown_enable_MASK 0x7ff0000 +#define ACP_AUDIO_PAD_PULLUP_PULLDOWN_CTRL__ACP_AUDIO_PAD_pulldown_enable__SHIFT 0x10 +#define ACP_BT_UART_PAD_SEL__ACP_BT_UART_PAD_SEL_MASK 0x1 +#define ACP_BT_UART_PAD_SEL__ACP_BT_UART_PAD_SEL__SHIFT 0x0 +#define ACP_SCRATCH_REG_0__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_0__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_1__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_1__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_2__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_2__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_3__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_3__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_4__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_4__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_5__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_5__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_6__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_6__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_7__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_7__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_8__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_8__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_9__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_9__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_10__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_10__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_11__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_11__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_12__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_12__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_13__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_13__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_14__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_14__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_15__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_15__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_16__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_16__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_17__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_17__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_18__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_18__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_19__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_19__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_20__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_20__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_21__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_21__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_22__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_22__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_23__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_23__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_24__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_24__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_25__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_25__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_26__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_26__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_27__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_27__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_28__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_28__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_29__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_29__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_30__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_30__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_31__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_31__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_32__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_32__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_33__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_33__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_34__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_34__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_35__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_35__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_36__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_36__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_37__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_37__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_38__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_38__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_39__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_39__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_40__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_40__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_41__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_41__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_42__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_42__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_43__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_43__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_44__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_44__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_45__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_45__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_46__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_46__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_SCRATCH_REG_47__ACP_SCRATCH_REG_MASK 0xffffffff +#define ACP_SCRATCH_REG_47__ACP_SCRATCH_REG__SHIFT 0x0 +#define ACP_VOICE_WAKEUP_ENABLE__voice_wakeup_enable_MASK 0x1 +#define ACP_VOICE_WAKEUP_ENABLE__voice_wakeup_enable__SHIFT 0x0 +#define ACP_VOICE_WAKEUP_STATUS__voice_wakeup_status_MASK 0x1 +#define ACP_VOICE_WAKEUP_STATUS__voice_wakeup_status__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_LOWER_THRESHOLD__i2s_voice_wakeup_lower_threshold_MASK 0xffffffff +#define I2S_VOICE_WAKEUP_LOWER_THRESHOLD__i2s_voice_wakeup_lower_threshold__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_HIGHER_THRESHOLD__i2s_voice_wakeup_higher_threshold_MASK 0xffffffff +#define I2S_VOICE_WAKEUP_HIGHER_THRESHOLD__i2s_voice_wakeup_higher_threshold__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_NO_OF_SAMPLES__i2s_voice_wakeup_no_of_samples_MASK 0xffff +#define I2S_VOICE_WAKEUP_NO_OF_SAMPLES__i2s_voice_wakeup_no_of_samples__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_NO_OF_PEAKS__i2s_voice_wakeup_no_of_peaks_MASK 0xffff +#define I2S_VOICE_WAKEUP_NO_OF_PEAKS__i2s_voice_wakeup_no_of_peaks__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_DURATION_OF_N_PEAKS__i2s_voice_wakeup_duration_of_n_peaks_MASK 0xffffffff +#define I2S_VOICE_WAKEUP_DURATION_OF_N_PEAKS__i2s_voice_wakeup_duration_of_n_peaks__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_BITCLK_TOGGLE_DETECTION__i2s_voice_wakeup_bitclk_toggle_wakeup_en_MASK 0x1 +#define I2S_VOICE_WAKEUP_BITCLK_TOGGLE_DETECTION__i2s_voice_wakeup_bitclk_toggle_wakeup_en__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_DATA_PATH_SWITCH__i2s_voice_wakeup_data_path_switch_req_MASK 0x1 +#define I2S_VOICE_WAKEUP_DATA_PATH_SWITCH__i2s_voice_wakeup_data_path_switch_req__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_DATA_PATH_SWITCH__i2s_voice_wakeup_data_path_switch_ack_MASK 0x2 +#define I2S_VOICE_WAKEUP_DATA_PATH_SWITCH__i2s_voice_wakeup_data_path_switch_ack__SHIFT 0x1 +#define I2S_VOICE_WAKEUP_DATA_POINTER__i2s_voice_wakeup_data_pointer_MASK 0xffffffff +#define I2S_VOICE_WAKEUP_DATA_POINTER__i2s_voice_wakeup_data_pointer__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_AUTH_MATCH__i2s_voice_wakeup_authentication_valid_MASK 0x1 +#define I2S_VOICE_WAKEUP_AUTH_MATCH__i2s_voice_wakeup_authentication_valid__SHIFT 0x0 +#define I2S_VOICE_WAKEUP_AUTH_MATCH__i2s_voice_wakeup_authentication_match_MASK 0x2 +#define I2S_VOICE_WAKEUP_AUTH_MATCH__i2s_voice_wakeup_authentication_match__SHIFT 0x1 +#define I2S_VOICE_WAKEUP_8KB_WRAP__i2s_voice_wakeup_8kb_wrap_MASK 0x1 +#define I2S_VOICE_WAKEUP_8KB_WRAP__i2s_voice_wakeup_8kb_wrap__SHIFT 0x0 +#define ACP_I2S_RECEIVED_BYTE_CNT_HIGH__i2s_mic_rx_byte_cnt_high_MASK 0xffffffff +#define ACP_I2S_RECEIVED_BYTE_CNT_HIGH__i2s_mic_rx_byte_cnt_high__SHIFT 0x0 +#define ACP_I2S_RECEIVED_BYTE_CNT_LOW__i2s_mic_rx_byte_cnt_low_MASK 0xffffffff +#define ACP_I2S_RECEIVED_BYTE_CNT_LOW__i2s_mic_rx_byte_cnt_low__SHIFT 0x0 +#define ACP_I2S_MICSP_TRANSMIT_BYTE_CNT_HIGH__i2s_micsp_tx_byte_cnt_high_MASK 0xffffffff +#define ACP_I2S_MICSP_TRANSMIT_BYTE_CNT_HIGH__i2s_micsp_tx_byte_cnt_high__SHIFT 0x0 +#define ACP_I2S_MICSP_TRANSMIT_BYTE_CNT_LOW__i2s_micsp_tx_byte_cnt_low_MASK 0xffffffff +#define ACP_I2S_MICSP_TRANSMIT_BYTE_CNT_LOW__i2s_micsp_tx_byte_cnt_low__SHIFT 0x0 +#define ACP_MEM_SHUT_DOWN_REQ_LO__ACP_ShutDownReq_RAML_MASK 0xffffffff +#define ACP_MEM_SHUT_DOWN_REQ_LO__ACP_ShutDownReq_RAML__SHIFT 0x0 +#define ACP_MEM_SHUT_DOWN_REQ_HI__ACP_ShutDownReq_RAMH_MASK 0xffff +#define ACP_MEM_SHUT_DOWN_REQ_HI__ACP_ShutDownReq_RAMH__SHIFT 0x0 +#define ACP_MEM_SHUT_DOWN_STS_LO__ACP_ShutDownSts_RAML_MASK 0xffffffff +#define ACP_MEM_SHUT_DOWN_STS_LO__ACP_ShutDownSts_RAML__SHIFT 0x0 +#define ACP_MEM_SHUT_DOWN_STS_HI__ACP_ShutDownSts_RAMH_MASK 0xffff +#define ACP_MEM_SHUT_DOWN_STS_HI__ACP_ShutDownSts_RAMH__SHIFT 0x0 +#define ACP_MEM_DEEP_SLEEP_REQ_LO__ACP_DeepSleepReq_RAML_MASK 0xffffffff +#define ACP_MEM_DEEP_SLEEP_REQ_LO__ACP_DeepSleepReq_RAML__SHIFT 0x0 +#define ACP_MEM_DEEP_SLEEP_REQ_HI__ACP_DeepSleepReq_RAMH_MASK 0xffff +#define ACP_MEM_DEEP_SLEEP_REQ_HI__ACP_DeepSleepReq_RAMH__SHIFT 0x0 +#define ACP_MEM_DEEP_SLEEP_STS_LO__ACP_DeepSleepSts_RAML_MASK 0xffffffff +#define ACP_MEM_DEEP_SLEEP_STS_LO__ACP_DeepSleepSts_RAML__SHIFT 0x0 +#define ACP_MEM_DEEP_SLEEP_STS_HI__ACP_DeepSleepSts_RAMH_MASK 0xffff +#define ACP_MEM_DEEP_SLEEP_STS_HI__ACP_DeepSleepSts_RAMH__SHIFT 0x0 +#define ACP_MEM_WAKEUP_FROM_SHUT_DOWN_LO__acp_mem_wakeup_from_shut_down_lo_MASK 0xffffffff +#define ACP_MEM_WAKEUP_FROM_SHUT_DOWN_LO__acp_mem_wakeup_from_shut_down_lo__SHIFT 0x0 +#define ACP_MEM_WAKEUP_FROM_SHUT_DOWN_HI__acp_mem_wakeup_from_shut_down_hi_MASK 0xffff +#define ACP_MEM_WAKEUP_FROM_SHUT_DOWN_HI__acp_mem_wakeup_from_shut_down_hi__SHIFT 0x0 +#define ACP_MEM_WAKEUP_FROM_SLEEP_LO__acp_mem_wakeup_from_sleep_lo_MASK 0xffffffff +#define ACP_MEM_WAKEUP_FROM_SLEEP_LO__acp_mem_wakeup_from_sleep_lo__SHIFT 0x0 +#define ACP_MEM_WAKEUP_FROM_SLEEP_HI__acp_mem_wakeup_from_sleep_hi_MASK 0xffff +#define ACP_MEM_WAKEUP_FROM_SLEEP_HI__acp_mem_wakeup_from_sleep_hi__SHIFT 0x0 +#define ACP_I2SSP_IER__I2SSP_IEN_MASK 0x1 +#define ACP_I2SSP_IER__I2SSP_IEN__SHIFT 0x0 +#define ACP_I2SSP_IRER__I2SSP_RXEN_MASK 0x1 +#define ACP_I2SSP_IRER__I2SSP_RXEN__SHIFT 0x0 +#define ACP_I2SSP_ITER__I2SSP_TXEN_MASK 0x1 +#define ACP_I2SSP_ITER__I2SSP_TXEN__SHIFT 0x0 +#define ACP_I2SSP_CER__I2SSP_CLKEN_MASK 0x1 +#define ACP_I2SSP_CER__I2SSP_CLKEN__SHIFT 0x0 +#define ACP_I2SSP_CCR__I2SSP_SCLKG_MASK 0x7 +#define ACP_I2SSP_CCR__I2SSP_SCLKG__SHIFT 0x0 +#define ACP_I2SSP_CCR__I2SSP_WSS_MASK 0x18 +#define ACP_I2SSP_CCR__I2SSP_WSS__SHIFT 0x3 +#define ACP_I2SSP_RXFFR__I2SSP_RXFFR_MASK 0x1 +#define ACP_I2SSP_RXFFR__I2SSP_RXFFR__SHIFT 0x0 +#define ACP_I2SSP_TXFFR__I2SSP_TXFFR_MASK 0x1 +#define ACP_I2SSP_TXFFR__I2SSP_TXFFR__SHIFT 0x0 +#define ACP_I2SSP_LRBR0__I2SSP_LRBR0_MASK 0xffffffff +#define ACP_I2SSP_LRBR0__I2SSP_LRBR0__SHIFT 0x0 +#define ACP_I2SSP_RRBR0__I2SSP_RRBR0_MASK 0xffffffff +#define ACP_I2SSP_RRBR0__I2SSP_RRBR0__SHIFT 0x0 +#define ACP_I2SSP_RER0__I2SSP_RXCHEN0_MASK 0x1 +#define ACP_I2SSP_RER0__I2SSP_RXCHEN0__SHIFT 0x0 +#define ACP_I2SSP_TER0__I2SSP_TXCHEN0_MASK 0x1 +#define ACP_I2SSP_TER0__I2SSP_TXCHEN0__SHIFT 0x0 +#define ACP_I2SSP_RCR0__I2SSP_WLEN_MASK 0x7 +#define ACP_I2SSP_RCR0__I2SSP_WLEN__SHIFT 0x0 +#define ACP_I2SSP_TCR0__I2SSP_WLEN_MASK 0x7 +#define ACP_I2SSP_TCR0__I2SSP_WLEN__SHIFT 0x0 +#define ACP_I2SSP_ISR0__I2SSP_RXDA_MASK 0x1 +#define ACP_I2SSP_ISR0__I2SSP_RXDA__SHIFT 0x0 +#define ACP_I2SSP_ISR0__I2SSP_RXFO_MASK 0x2 +#define ACP_I2SSP_ISR0__I2SSP_RXFO__SHIFT 0x1 +#define ACP_I2SSP_ISR0__I2SSP_TXFE_MASK 0x10 +#define ACP_I2SSP_ISR0__I2SSP_TXFE__SHIFT 0x4 +#define ACP_I2SSP_ISR0__I2SSP_TXFO_MASK 0x20 +#define ACP_I2SSP_ISR0__I2SSP_TXFO__SHIFT 0x5 +#define ACP_I2SSP_IMR0__I2SSP_RXDAM_MASK 0x1 +#define ACP_I2SSP_IMR0__I2SSP_RXDAM__SHIFT 0x0 +#define ACP_I2SSP_IMR0__I2SSP_RXFOM_MASK 0x2 +#define ACP_I2SSP_IMR0__I2SSP_RXFOM__SHIFT 0x1 +#define ACP_I2SSP_IMR0__I2SSP_TXFEM_MASK 0x10 +#define ACP_I2SSP_IMR0__I2SSP_TXFEM__SHIFT 0x4 +#define ACP_I2SSP_IMR0__I2SSP_TXFOM_MASK 0x20 +#define ACP_I2SSP_IMR0__I2SSP_TXFOM__SHIFT 0x5 +#define ACP_I2SSP_ROR0__I2SSP_RXCHO_MASK 0x1 +#define ACP_I2SSP_ROR0__I2SSP_RXCHO__SHIFT 0x0 +#define ACP_I2SSP_TOR0__I2SSP_TXCHO_MASK 0x1 +#define ACP_I2SSP_TOR0__I2SSP_TXCHO__SHIFT 0x0 +#define ACP_I2SSP_RFCR0__I2SSP_RXCHDT_MASK 0xf +#define ACP_I2SSP_RFCR0__I2SSP_RXCHDT__SHIFT 0x0 +#define ACP_I2SSP_TFCR0__I2SSP_TXCHET_MASK 0xf +#define ACP_I2SSP_TFCR0__I2SSP_TXCHET__SHIFT 0x0 +#define ACP_I2SSP_RFF0__I2SSP_RXCHFR_MASK 0x1 +#define ACP_I2SSP_RFF0__I2SSP_RXCHFR__SHIFT 0x0 +#define ACP_I2SSP_TFF0__I2SSP_TXCHFR_MASK 0x1 +#define ACP_I2SSP_TFF0__I2SSP_TXCHFR__SHIFT 0x0 +#define ACP_I2SSP_RXDMA__I2SSP_RXDMA_MASK 0xffffffff +#define ACP_I2SSP_RXDMA__I2SSP_RXDMA__SHIFT 0x0 +#define ACP_I2SSP_RRXDMA__I2SSP_RRXDMA_MASK 0x1 +#define ACP_I2SSP_RRXDMA__I2SSP_RRXDMA__SHIFT 0x0 +#define ACP_I2SSP_TXDMA__I2SSP_TXDMA_MASK 0xffffffff +#define ACP_I2SSP_TXDMA__I2SSP_TXDMA__SHIFT 0x0 +#define ACP_I2SSP_RTXDMA__I2SSP_RTXDMA_MASK 0x1 +#define ACP_I2SSP_RTXDMA__I2SSP_RTXDMA__SHIFT 0x0 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_0_MASK 0x7 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_0__SHIFT 0x0 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_1_MASK 0x38 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_1__SHIFT 0x3 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_2_MASK 0x380 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_2__SHIFT 0x7 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_3_MASK 0x1c00 +#define ACP_I2SSP_COMP_PARAM_2__I2SSP_RX_WPRDSIZE_3__SHIFT 0xa +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_APB_DATA_WIDTH_MASK 0x3 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_APB_DATA_WIDTH__SHIFT 0x0 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_FIFO_DEPTH_GLOBAL_MASK 0xc +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_FIFO_DEPTH_GLOBAL__SHIFT 0x2 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_MODE_EN_MASK 0x10 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_MODE_EN__SHIFT 0x4 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TRANSMITTER_BLOCK_MASK 0x20 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TRANSMITTER_BLOCK__SHIFT 0x5 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_RECEIVER_BLOCK_MASK 0x40 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_RECEIVER_BLOCK__SHIFT 0x6 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_RX_CHANNLES_MASK 0x180 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_RX_CHANNLES__SHIFT 0x7 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_CHANNLES_MASK 0x600 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_CHANNLES__SHIFT 0x9 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_0_MASK 0x70000 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_0__SHIFT 0x10 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_1_MASK 0x380000 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_1__SHIFT 0x13 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_2_MASK 0x1c00000 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_2__SHIFT 0x16 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_3_MASK 0xe000000 +#define ACP_I2SSP_COMP_PARAM_1__I2SSP_TX_WORDSIZE_3__SHIFT 0x19 +#define ACP_I2SSP_COMP_VERSION__I2SSP_APB_DATA_WIDTH_MASK 0xffffffff +#define ACP_I2SSP_COMP_VERSION__I2SSP_APB_DATA_WIDTH__SHIFT 0x0 +#define ACP_I2SSP_COMP_TYPE__I2SSP_COMP_TYPE_MASK 0xffffffff +#define ACP_I2SSP_COMP_TYPE__I2SSP_COMP_TYPE__SHIFT 0x0 +#define ACP_I2SMICSP_IER__I2SMICSP_IEN_MASK 0x1 +#define ACP_I2SMICSP_IER__I2SMICSP_IEN__SHIFT 0x0 +#define ACP_I2SMICSP_IRER__I2SMICSP_RXEN_MASK 0x1 +#define ACP_I2SMICSP_IRER__I2SMICSP_RXEN__SHIFT 0x0 +#define ACP_I2SMICSP_ITER__I2SMICSP_TXEN_MASK 0x1 +#define ACP_I2SMICSP_ITER__I2SMICSP_TXEN__SHIFT 0x0 +#define ACP_I2SMICSP_CER__I2SMICSP_CLKEN_MASK 0x1 +#define ACP_I2SMICSP_CER__I2SMICSP_CLKEN__SHIFT 0x0 +#define ACP_I2SMICSP_CCR__I2SMICSP_SCLKG_MASK 0x7 +#define ACP_I2SMICSP_CCR__I2SMICSP_SCLKG__SHIFT 0x0 +#define ACP_I2SMICSP_CCR__I2SMICSP_WSS_MASK 0x18 +#define ACP_I2SMICSP_CCR__I2SMICSP_WSS__SHIFT 0x3 +#define ACP_I2SMICSP_RXFFR__I2SMICSP_RXFFR_MASK 0x1 +#define ACP_I2SMICSP_RXFFR__I2SMICSP_RXFFR__SHIFT 0x0 +#define ACP_I2SMICSP_TXFFR__I2SMICSP_TXFFR_MASK 0x1 +#define ACP_I2SMICSP_TXFFR__I2SMICSP_TXFFR__SHIFT 0x0 +#define ACP_I2SMICSP_LRBR0__I2SMICSP_LRBR0_MASK 0xffffffff +#define ACP_I2SMICSP_LRBR0__I2SMICSP_LRBR0__SHIFT 0x0 +#define ACP_I2SMICSP_RRBR0__I2SMICSP_RRBR0_MASK 0xffffffff +#define ACP_I2SMICSP_RRBR0__I2SMICSP_RRBR0__SHIFT 0x0 +#define ACP_I2SMICSP_RER0__I2SMICSP_RXCHEN0_MASK 0x1 +#define ACP_I2SMICSP_RER0__I2SMICSP_RXCHEN0__SHIFT 0x0 +#define ACP_I2SMICSP_TER0__I2SMICSP_TXCHEN0_MASK 0x1 +#define ACP_I2SMICSP_TER0__I2SMICSP_TXCHEN0__SHIFT 0x0 +#define ACP_I2SMICSP_RCR0__I2SMICSP_WLEN_MASK 0x7 +#define ACP_I2SMICSP_RCR0__I2SMICSP_WLEN__SHIFT 0x0 +#define ACP_I2SMICSP_TCR0__I2SMICSP_WLEN_MASK 0x7 +#define ACP_I2SMICSP_TCR0__I2SMICSP_WLEN__SHIFT 0x0 +#define ACP_I2SMICSP_ISR0__I2SMICSP_RXDA_MASK 0x1 +#define ACP_I2SMICSP_ISR0__I2SMICSP_RXDA__SHIFT 0x0 +#define ACP_I2SMICSP_ISR0__I2SMICSP_RXFO_MASK 0x2 +#define ACP_I2SMICSP_ISR0__I2SMICSP_RXFO__SHIFT 0x1 +#define ACP_I2SMICSP_ISR0__I2SMICSP_TXFE_MASK 0x10 +#define ACP_I2SMICSP_ISR0__I2SMICSP_TXFE__SHIFT 0x4 +#define ACP_I2SMICSP_ISR0__I2SMICSP_TXFO_MASK 0x20 +#define ACP_I2SMICSP_ISR0__I2SMICSP_TXFO__SHIFT 0x5 +#define ACP_I2SMICSP_IMR0__I2SMICSP_RXDAM_MASK 0x1 +#define ACP_I2SMICSP_IMR0__I2SMICSP_RXDAM__SHIFT 0x0 +#define ACP_I2SMICSP_IMR0__I2SMICSP_RXFOM_MASK 0x2 +#define ACP_I2SMICSP_IMR0__I2SMICSP_RXFOM__SHIFT 0x1 +#define ACP_I2SMICSP_IMR0__I2SMICSP_TXFEM_MASK 0x10 +#define ACP_I2SMICSP_IMR0__I2SMICSP_TXFEM__SHIFT 0x4 +#define ACP_I2SMICSP_IMR0__I2SMICSP_TXFOM_MASK 0x20 +#define ACP_I2SMICSP_IMR0__I2SMICSP_TXFOM__SHIFT 0x5 +#define ACP_I2SMICSP_ROR0__I2SMICSP_RXCHO_MASK 0x1 +#define ACP_I2SMICSP_ROR0__I2SMICSP_RXCHO__SHIFT 0x0 +#define ACP_I2SMICSP_TOR0__I2SMICSP_TXCHO_MASK 0x1 +#define ACP_I2SMICSP_TOR0__I2SMICSP_TXCHO__SHIFT 0x0 +#define ACP_I2SMICSP_RFCR0__I2SMICSP_RXCHDT_MASK 0xf +#define ACP_I2SMICSP_RFCR0__I2SMICSP_RXCHDT__SHIFT 0x0 +#define ACP_I2SMICSP_TFCR0__I2SMICSP_TXCHET_MASK 0xf +#define ACP_I2SMICSP_TFCR0__I2SMICSP_TXCHET__SHIFT 0x0 +#define ACP_I2SMICSP_RFF0__I2SMICSP_RXCHFR_MASK 0x1 +#define ACP_I2SMICSP_RFF0__I2SMICSP_RXCHFR__SHIFT 0x0 +#define ACP_I2SMICSP_TFF0__I2SMICSP_TXCHFR_MASK 0x1 +#define ACP_I2SMICSP_TFF0__I2SMICSP_TXCHFR__SHIFT 0x0 +#define ACP_I2SMICSP_LRBR1__I2SMICSP_LRBR1_MASK 0xffffffff +#define ACP_I2SMICSP_LRBR1__I2SMICSP_LRBR1__SHIFT 0x0 +#define ACP_I2SMICSP_RRBR1__I2SMICSP_RRBR1_MASK 0xffffffff +#define ACP_I2SMICSP_RRBR1__I2SMICSP_RRBR1__SHIFT 0x0 +#define ACP_I2SMICSP_RER1__I2SMICSP_RXCHEN1_MASK 0x1 +#define ACP_I2SMICSP_RER1__I2SMICSP_RXCHEN1__SHIFT 0x0 +#define ACP_I2SMICSP_TER1__I2SMICSP_TXCHEN1_MASK 0x1 +#define ACP_I2SMICSP_TER1__I2SMICSP_TXCHEN1__SHIFT 0x0 +#define ACP_I2SMICSP_RCR1__I2SMICSP_WLEN_MASK 0x7 +#define ACP_I2SMICSP_RCR1__I2SMICSP_WLEN__SHIFT 0x0 +#define ACP_I2SMICSP_TCR1__I2SMICSP_WLEN_MASK 0x7 +#define ACP_I2SMICSP_TCR1__I2SMICSP_WLEN__SHIFT 0x0 +#define ACP_I2SMICSP_ISR1__I2SMICSP_RXDA_MASK 0x1 +#define ACP_I2SMICSP_ISR1__I2SMICSP_RXDA__SHIFT 0x0 +#define ACP_I2SMICSP_ISR1__I2SMICSP_RXFO_MASK 0x2 +#define ACP_I2SMICSP_ISR1__I2SMICSP_RXFO__SHIFT 0x1 +#define ACP_I2SMICSP_ISR1__I2SMICSP_TXFE_MASK 0x10 +#define ACP_I2SMICSP_ISR1__I2SMICSP_TXFE__SHIFT 0x4 +#define ACP_I2SMICSP_ISR1__I2SMICSP_TXFO_MASK 0x20 +#define ACP_I2SMICSP_ISR1__I2SMICSP_TXFO__SHIFT 0x5 +#define ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM_MASK 0x1 +#define ACP_I2SMICSP_IMR1__I2SMICSP_RXDAM__SHIFT 0x0 +#define ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM_MASK 0x2 +#define ACP_I2SMICSP_IMR1__I2SMICSP_RXFOM__SHIFT 0x1 +#define ACP_I2SMICSP_IMR1__I2SMICSP_TXFEM_MASK 0x10 +#define ACP_I2SMICSP_IMR1__I2SMICSP_TXFEM__SHIFT 0x4 +#define ACP_I2SMICSP_IMR1__I2SMICSP_TXFOM_MASK 0x20 +#define ACP_I2SMICSP_IMR1__I2SMICSP_TXFOM__SHIFT 0x5 +#define ACP_I2SMICSP_ROR1__I2SMICSP_RXCHO_MASK 0x1 +#define ACP_I2SMICSP_ROR1__I2SMICSP_RXCHO__SHIFT 0x0 +#define ACP_I2SMICSP_TOR1__I2SMICSP_TXCHO_MASK 0x1 +#define ACP_I2SMICSP_TOR1__I2SMICSP_TXCHO__SHIFT 0x0 +#define ACP_I2SMICSP_RFCR1__I2SMICSP_RXCHDT_MASK 0xf +#define ACP_I2SMICSP_RFCR1__I2SMICSP_RXCHDT__SHIFT 0x0 +#define ACP_I2SMICSP_TFCR1__I2SMICSP_TXCHET_MASK 0xf +#define ACP_I2SMICSP_TFCR1__I2SMICSP_TXCHET__SHIFT 0x0 +#define ACP_I2SMICSP_RFF1__I2SMICSP_RXCHFR_MASK 0x1 +#define ACP_I2SMICSP_RFF1__I2SMICSP_RXCHFR__SHIFT 0x0 +#define ACP_I2SMICSP_TFF1__I2SMICSP_TXCHFR_MASK 0x1 +#define ACP_I2SMICSP_TFF1__I2SMICSP_TXCHFR__SHIFT 0x0 +#define ACP_I2SMICSP_RXDMA__I2SMICSP_RXDMA_MASK 0xffffffff +#define ACP_I2SMICSP_RXDMA__I2SMICSP_RXDMA__SHIFT 0x0 +#define ACP_I2SMICSP_RRXDMA__I2SMICSP_RRXDMA_MASK 0x1 +#define ACP_I2SMICSP_RRXDMA__I2SMICSP_RRXDMA__SHIFT 0x0 +#define ACP_I2SMICSP_TXDMA__I2SMICSP_TXDMA_MASK 0xffffffff +#define ACP_I2SMICSP_TXDMA__I2SMICSP_TXDMA__SHIFT 0x0 +#define ACP_I2SMICSP_RTXDMA__I2SMICSP_RTXDMA_MASK 0x1 +#define ACP_I2SMICSP_RTXDMA__I2SMICSP_RTXDMA__SHIFT 0x0 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_0_MASK 0x7 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_0__SHIFT 0x0 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_1_MASK 0x38 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_1__SHIFT 0x3 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_2_MASK 0x380 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_2__SHIFT 0x7 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_3_MASK 0x1c00 +#define ACP_I2SMICSP_COMP_PARAM_2__I2SMICSP_RX_WPRDSIZE_3__SHIFT 0xa +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_APB_DATA_WIDTH_MASK 0x3 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_APB_DATA_WIDTH__SHIFT 0x0 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_FIFO_DEPTH_GLOBAL_MASK 0xc +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_FIFO_DEPTH_GLOBAL__SHIFT 0x2 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_MODE_EN_MASK 0x10 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_MODE_EN__SHIFT 0x4 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TRANSMITTER_BLOCK_MASK 0x20 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TRANSMITTER_BLOCK__SHIFT 0x5 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_RECEIVER_BLOCK_MASK 0x40 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_RECEIVER_BLOCK__SHIFT 0x6 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_RX_CHANNLES_MASK 0x180 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_RX_CHANNLES__SHIFT 0x7 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_CHANNLES_MASK 0x600 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_CHANNLES__SHIFT 0x9 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_0_MASK 0x70000 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_0__SHIFT 0x10 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_1_MASK 0x380000 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_1__SHIFT 0x13 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_2_MASK 0x1c00000 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_2__SHIFT 0x16 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_3_MASK 0xe000000 +#define ACP_I2SMICSP_COMP_PARAM_1__I2SMICSP_TX_WORDSIZE_3__SHIFT 0x19 +#define ACP_I2SMICSP_COMP_VERSION__I2SMICSP_APB_DATA_WIDTH_MASK 0xffffffff +#define ACP_I2SMICSP_COMP_VERSION__I2SMICSP_APB_DATA_WIDTH__SHIFT 0x0 +#define ACP_I2SMICSP_COMP_TYPE__I2SMICSP_COMP_TYPE_MASK 0xffffffff +#define ACP_I2SMICSP_COMP_TYPE__I2SMICSP_COMP_TYPE__SHIFT 0x0 +#define ACP_I2SBT_IER__I2SBT_IEN_MASK 0x1 +#define ACP_I2SBT_IER__I2SBT_IEN__SHIFT 0x0 +#define ACP_I2SBT_IRER__I2SBT_RXEN_MASK 0x1 +#define ACP_I2SBT_IRER__I2SBT_RXEN__SHIFT 0x0 +#define ACP_I2SBT_ITER__I2SBT_TXEN_MASK 0x1 +#define ACP_I2SBT_ITER__I2SBT_TXEN__SHIFT 0x0 +#define ACP_I2SBT_CER__I2SBT_CLKEN_MASK 0x1 +#define ACP_I2SBT_CER__I2SBT_CLKEN__SHIFT 0x0 +#define ACP_I2SBT_CCR__I2SBT_SCLKG_MASK 0x7 +#define ACP_I2SBT_CCR__I2SBT_SCLKG__SHIFT 0x0 +#define ACP_I2SBT_CCR__I2SBT_WSS_MASK 0x18 +#define ACP_I2SBT_CCR__I2SBT_WSS__SHIFT 0x3 +#define ACP_I2SBT_RXFFR__I2SBT_RXFFR_MASK 0x1 +#define ACP_I2SBT_RXFFR__I2SBT_RXFFR__SHIFT 0x0 +#define ACP_I2SBT_TXFFR__I2SBT_TXFFR_MASK 0x1 +#define ACP_I2SBT_TXFFR__I2SBT_TXFFR__SHIFT 0x0 +#define ACP_I2SBT_LRBR0__I2SBT_LRBR0_MASK 0xffffffff +#define ACP_I2SBT_LRBR0__I2SBT_LRBR0__SHIFT 0x0 +#define ACP_I2SBT_RRBR0__I2SBT_RRBR0_MASK 0xffffffff +#define ACP_I2SBT_RRBR0__I2SBT_RRBR0__SHIFT 0x0 +#define ACP_I2SBT_RER0__I2SBT_RXCHEN0_MASK 0x1 +#define ACP_I2SBT_RER0__I2SBT_RXCHEN0__SHIFT 0x0 +#define ACP_I2SBT_TER0__I2SBT_TXCHEN0_MASK 0x1 +#define ACP_I2SBT_TER0__I2SBT_TXCHEN0__SHIFT 0x0 +#define ACP_I2SBT_RCR0__I2SBT_WLEN_MASK 0x7 +#define ACP_I2SBT_RCR0__I2SBT_WLEN__SHIFT 0x0 +#define ACP_I2SBT_TCR0__I2SBT_WLEN_MASK 0x7 +#define ACP_I2SBT_TCR0__I2SBT_WLEN__SHIFT 0x0 +#define ACP_I2SBT_ISR0__I2SBT_RXDA_MASK 0x1 +#define ACP_I2SBT_ISR0__I2SBT_RXDA__SHIFT 0x0 +#define ACP_I2SBT_ISR0__I2SBT_RXFO_MASK 0x2 +#define ACP_I2SBT_ISR0__I2SBT_RXFO__SHIFT 0x1 +#define ACP_I2SBT_ISR0__I2SBT_TXFE_MASK 0x10 +#define ACP_I2SBT_ISR0__I2SBT_TXFE__SHIFT 0x4 +#define ACP_I2SBT_ISR0__I2SBT_TXFO_MASK 0x20 +#define ACP_I2SBT_ISR0__I2SBT_TXFO__SHIFT 0x5 +#define ACP_I2SBT_IMR0__I2SBT_RXDAM_MASK 0x1 +#define ACP_I2SBT_IMR0__I2SBT_RXDAM__SHIFT 0x0 +#define ACP_I2SBT_IMR0__I2SBT_RXFOM_MASK 0x2 +#define ACP_I2SBT_IMR0__I2SBT_RXFOM__SHIFT 0x1 +#define ACP_I2SBT_IMR0__I2SBT_TXFEM_MASK 0x10 +#define ACP_I2SBT_IMR0__I2SBT_TXFEM__SHIFT 0x4 +#define ACP_I2SBT_IMR0__I2SBT_TXFOM_MASK 0x20 +#define ACP_I2SBT_IMR0__I2SBT_TXFOM__SHIFT 0x5 +#define ACP_I2SBT_ROR0__I2SBT_RXCHO_MASK 0x1 +#define ACP_I2SBT_ROR0__I2SBT_RXCHO__SHIFT 0x0 +#define ACP_I2SBT_TOR0__I2SBT_TXCHO_MASK 0x1 +#define ACP_I2SBT_TOR0__I2SBT_TXCHO__SHIFT 0x0 +#define ACP_I2SBT_RFCR0__I2SBT_RXCHDT_MASK 0xf +#define ACP_I2SBT_RFCR0__I2SBT_RXCHDT__SHIFT 0x0 +#define ACP_I2SBT_TFCR0__I2SBT_TXCHET_MASK 0xf +#define ACP_I2SBT_TFCR0__I2SBT_TXCHET__SHIFT 0x0 +#define ACP_I2SBT_RFF0__I2SBT_RXCHFR_MASK 0x1 +#define ACP_I2SBT_RFF0__I2SBT_RXCHFR__SHIFT 0x0 +#define ACP_I2SBT_TFF0__I2SBT_TXCHFR_MASK 0x1 +#define ACP_I2SBT_TFF0__I2SBT_TXCHFR__SHIFT 0x0 +#define ACP_I2SBT_LRBR1__I2SBT_LRBR1_MASK 0xffffffff +#define ACP_I2SBT_LRBR1__I2SBT_LRBR1__SHIFT 0x0 +#define ACP_I2SBT_RRBR1__I2SBT_RRBR1_MASK 0xffffffff +#define ACP_I2SBT_RRBR1__I2SBT_RRBR1__SHIFT 0x0 +#define ACP_I2SBT_RER1__I2SBT_RXCHEN1_MASK 0x1 +#define ACP_I2SBT_RER1__I2SBT_RXCHEN1__SHIFT 0x0 +#define ACP_I2SBT_TER1__I2SBT_TXCHEN1_MASK 0x1 +#define ACP_I2SBT_TER1__I2SBT_TXCHEN1__SHIFT 0x0 +#define ACP_I2SBT_RCR1__I2SBT_WLEN_MASK 0x7 +#define ACP_I2SBT_RCR1__I2SBT_WLEN__SHIFT 0x0 +#define ACP_I2SBT_TCR1__I2SBT_WLEN_MASK 0x7 +#define ACP_I2SBT_TCR1__I2SBT_WLEN__SHIFT 0x0 +#define ACP_I2SBT_ISR1__I2SBT_RXDA_MASK 0x1 +#define ACP_I2SBT_ISR1__I2SBT_RXDA__SHIFT 0x0 +#define ACP_I2SBT_ISR1__I2SBT_RXFO_MASK 0x2 +#define ACP_I2SBT_ISR1__I2SBT_RXFO__SHIFT 0x1 +#define ACP_I2SBT_ISR1__I2SBT_TXFE_MASK 0x10 +#define ACP_I2SBT_ISR1__I2SBT_TXFE__SHIFT 0x4 +#define ACP_I2SBT_ISR1__I2SBT_TXFO_MASK 0x20 +#define ACP_I2SBT_ISR1__I2SBT_TXFO__SHIFT 0x5 +#define ACP_I2SBT_IMR1__I2SBT_RXDAM_MASK 0x1 +#define ACP_I2SBT_IMR1__I2SBT_RXDAM__SHIFT 0x0 +#define ACP_I2SBT_IMR1__I2SBT_RXFOM_MASK 0x2 +#define ACP_I2SBT_IMR1__I2SBT_RXFOM__SHIFT 0x1 +#define ACP_I2SBT_IMR1__I2SBT_TXFEM_MASK 0x10 +#define ACP_I2SBT_IMR1__I2SBT_TXFEM__SHIFT 0x4 +#define ACP_I2SBT_IMR1__I2SBT_TXFOM_MASK 0x20 +#define ACP_I2SBT_IMR1__I2SBT_TXFOM__SHIFT 0x5 +#define ACP_I2SBT_ROR1__I2SBT_RXCHO_MASK 0x1 +#define ACP_I2SBT_ROR1__I2SBT_RXCHO__SHIFT 0x0 +#define ACP_I2SBT_TOR1__I2SBT_TXCHO_MASK 0x1 +#define ACP_I2SBT_TOR1__I2SBT_TXCHO__SHIFT 0x0 +#define ACP_I2SBT_RFCR1__I2SBT_RXCHDT_MASK 0xf +#define ACP_I2SBT_RFCR1__I2SBT_RXCHDT__SHIFT 0x0 +#define ACP_I2SBT_TFCR1__I2SBT_TXCHET_MASK 0xf +#define ACP_I2SBT_TFCR1__I2SBT_TXCHET__SHIFT 0x0 +#define ACP_I2SBT_RFF1__I2SBT_RXCHFR_MASK 0x1 +#define ACP_I2SBT_RFF1__I2SBT_RXCHFR__SHIFT 0x0 +#define ACP_I2SBT_TFF1__I2SBT_TXCHFR_MASK 0x1 +#define ACP_I2SBT_TFF1__I2SBT_TXCHFR__SHIFT 0x0 +#define ACP_I2SBT_RXDMA__I2SBT_RXDMA_MASK 0xffffffff +#define ACP_I2SBT_RXDMA__I2SBT_RXDMA__SHIFT 0x0 +#define ACP_I2SBT_RRXDMA__I2SBT_RRXDMA_MASK 0x1 +#define ACP_I2SBT_RRXDMA__I2SBT_RRXDMA__SHIFT 0x0 +#define ACP_I2SBT_TXDMA__I2SBT_TXDMA_MASK 0xffffffff +#define ACP_I2SBT_TXDMA__I2SBT_TXDMA__SHIFT 0x0 +#define ACP_I2SBT_RTXDMA__I2SBT_RTXDMA_MASK 0x1 +#define ACP_I2SBT_RTXDMA__I2SBT_RTXDMA__SHIFT 0x0 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_0_MASK 0x7 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_0__SHIFT 0x0 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_1_MASK 0x38 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_1__SHIFT 0x3 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_2_MASK 0x380 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_2__SHIFT 0x7 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_3_MASK 0x1c00 +#define ACP_I2SBT_COMP_PARAM_2__I2SBT_RX_WPRDSIZE_3__SHIFT 0xa +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_APB_DATA_WIDTH_MASK 0x3 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_APB_DATA_WIDTH__SHIFT 0x0 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_FIFO_DEPTH_GLOBAL_MASK 0xc +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_FIFO_DEPTH_GLOBAL__SHIFT 0x2 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_MODE_EN_MASK 0x10 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_MODE_EN__SHIFT 0x4 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TRANSMITTER_BLOCK_MASK 0x20 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TRANSMITTER_BLOCK__SHIFT 0x5 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_RECEIVER_BLOCK_MASK 0x40 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_RECEIVER_BLOCK__SHIFT 0x6 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_RX_CHANNLES_MASK 0x180 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_RX_CHANNLES__SHIFT 0x7 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_CHANNLES_MASK 0x600 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_CHANNLES__SHIFT 0x9 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_0_MASK 0x70000 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_0__SHIFT 0x10 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_1_MASK 0x380000 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_1__SHIFT 0x13 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_2_MASK 0x1c00000 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_2__SHIFT 0x16 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_3_MASK 0xe000000 +#define ACP_I2SBT_COMP_PARAM_1__I2SBT_TX_WORDSIZE_3__SHIFT 0x19 +#define ACP_I2SBT_COMP_VERSION__I2SBT_APB_DATA_WIDTH_MASK 0xffffffff +#define ACP_I2SBT_COMP_VERSION__I2SBT_APB_DATA_WIDTH__SHIFT 0x0 +#define ACP_I2SBT_COMP_TYPE__I2SBT_COMP_TYPE_MASK 0xffffffff +#define ACP_I2SBT_COMP_TYPE__I2SBT_COMP_TYPE__SHIFT 0x0 + +#endif /* ACP_2_2_SH_MASK_H */ diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig new file mode 100644 index 000000000..dad778e58 --- /dev/null +++ b/sound/soc/atmel/Kconfig @@ -0,0 +1,102 @@ +config SND_ATMEL_SOC + tristate "SoC Audio for the Atmel System-on-Chip" + depends on HAS_IOMEM + help + Say Y or M if you want to add support for codecs attached to + the ATMEL SSC interface. You will also need + to select the audio interfaces to support below. + +if SND_ATMEL_SOC + +config SND_ATMEL_SOC_PDC + tristate + depends on HAS_DMA + default m if SND_ATMEL_SOC_SSC_PDC=m && SND_ATMEL_SOC_SSC=m + default y if SND_ATMEL_SOC_SSC_PDC=y || (SND_ATMEL_SOC_SSC_PDC=m && SND_ATMEL_SOC_SSC=y) + +config SND_ATMEL_SOC_SSC_PDC + tristate + +config SND_ATMEL_SOC_DMA + tristate + select SND_SOC_GENERIC_DMAENGINE_PCM + default m if SND_ATMEL_SOC_SSC_DMA=m && SND_ATMEL_SOC_SSC=m + default y if SND_ATMEL_SOC_SSC_DMA=y || (SND_ATMEL_SOC_SSC_DMA=m && SND_ATMEL_SOC_SSC=y) + +config SND_ATMEL_SOC_SSC_DMA + tristate + select SND_ATMEL_SOC_DMA + select SND_ATMEL_SOC_PDC + +config SND_ATMEL_SOC_SSC + tristate + default y if SND_ATMEL_SOC_SSC_DMA=y || SND_ATMEL_SOC_SSC_PDC=y + default m if SND_ATMEL_SOC_SSC_DMA=m || SND_ATMEL_SOC_SSC_PDC=m + +config SND_AT91_SOC_SAM9G20_WM8731 + tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board" + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && SND_SOC_I2C_AND_SPI + select SND_ATMEL_SOC_SSC_PDC + select SND_SOC_WM8731 + help + Say Y if you want to add support for SoC audio on WM8731-based + AT91sam9g20 evaluation board. + +config SND_ATMEL_SOC_WM8904 + tristate "Atmel ASoC driver for boards using WM8904 codec" + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && I2C + select SND_ATMEL_SOC_SSC_DMA + select SND_SOC_WM8904 + help + Say Y if you want to add support for Atmel ASoC driver for boards using + WM8904 codec. + +config SND_AT91_SOC_SAM9X5_WM8731 + tristate "SoC Audio support for WM8731-based at91sam9x5 board" + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && SND_SOC_I2C_AND_SPI + select SND_ATMEL_SOC_SSC_DMA + select SND_SOC_WM8731 + help + Say Y if you want to add support for audio SoC on an + at91sam9x5 based board that is using WM8731 codec. + +config SND_ATMEL_SOC_CLASSD + tristate "Atmel ASoC driver for boards using CLASSD" + depends on ARCH_AT91 || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to add support for Atmel ASoC driver for boards using + CLASSD. + +config SND_ATMEL_SOC_PDMIC + tristate "Atmel ASoC driver for boards using PDMIC" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to add support for Atmel ASoC driver for boards using + PDMIC. + +config SND_ATMEL_SOC_TSE850_PCM5142 + tristate "ASoC driver for the Axentia TSE-850" + depends on ARCH_AT91 && OF + depends on ATMEL_SSC && I2C + select SND_ATMEL_SOC_SSC_DMA + select SND_SOC_PCM512x_I2C + help + Say Y if you want to add support for the ASoC driver for the + Axentia TSE-850 with a PCM5142 codec. + +config SND_ATMEL_SOC_I2S + tristate "Atmel ASoC driver for boards using I2S" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for Atmel ASoc driver for boards + using I2S. +endif diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile new file mode 100644 index 000000000..cd87cb4bc --- /dev/null +++ b/sound/soc/atmel/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# AT91 Platform Support +snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o +snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o +snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o +snd-soc-atmel-i2s-objs := atmel-i2s.o + +obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o +obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o +obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o +obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o + +# AT91 Machine Support +snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o +snd-atmel-soc-wm8904-objs := atmel_wm8904.o +snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o +snd-atmel-soc-classd-objs := atmel-classd.o +snd-atmel-soc-pdmic-objs := atmel-pdmic.o +snd-atmel-soc-tse850-pcm5142-objs := tse850-pcm5142.o + +obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o +obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o +obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o +obj-$(CONFIG_SND_ATMEL_SOC_CLASSD) += snd-atmel-soc-classd.o +obj-$(CONFIG_SND_ATMEL_SOC_PDMIC) += snd-atmel-soc-pdmic.o +obj-$(CONFIG_SND_ATMEL_SOC_TSE850_PCM5142) += snd-atmel-soc-tse850-pcm5142.o diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c new file mode 100644 index 000000000..3d7006190 --- /dev/null +++ b/sound/soc/atmel/atmel-classd.c @@ -0,0 +1,668 @@ +/* Atmel ALSA SoC Audio Class D Amplifier (CLASSD) driver + * + * Copyright (C) 2015 Atmel + * + * Author: Songjun Wu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or later + * as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "atmel-classd.h" + +struct atmel_classd_pdata { + bool non_overlap_enable; + int non_overlap_time; + int pwm_type; + const char *card_name; +}; + +struct atmel_classd { + dma_addr_t phy_base; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct device *dev; + int irq; + const struct atmel_classd_pdata *pdata; +}; + +#ifdef CONFIG_OF +static const struct of_device_id atmel_classd_of_match[] = { + { + .compatible = "atmel,sama5d2-classd", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, atmel_classd_of_match); + +static struct atmel_classd_pdata *atmel_classd_dt_init(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct atmel_classd_pdata *pdata; + const char *pwm_type; + int ret; + + if (!np) { + dev_err(dev, "device node not found\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_string(np, "atmel,pwm-type", &pwm_type); + if ((ret == 0) && (strcmp(pwm_type, "diff") == 0)) + pdata->pwm_type = CLASSD_MR_PWMTYP_DIFF; + else + pdata->pwm_type = CLASSD_MR_PWMTYP_SINGLE; + + ret = of_property_read_u32(np, + "atmel,non-overlap-time", &pdata->non_overlap_time); + if (ret) + pdata->non_overlap_enable = false; + else + pdata->non_overlap_enable = true; + + ret = of_property_read_string(np, "atmel,model", &pdata->card_name); + if (ret) + pdata->card_name = "CLASSD"; + + return pdata; +} +#else +static inline struct atmel_classd_pdata * +atmel_classd_dt_init(struct device *dev) +{ + return ERR_PTR(-EINVAL); +} +#endif + +#define ATMEL_CLASSD_RATES (SNDRV_PCM_RATE_8000 \ + | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 \ + | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 \ + | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 \ + | SNDRV_PCM_RATE_96000) + +static const struct snd_pcm_hardware atmel_classd_hw = { + .info = SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_RESUME + | SNDRV_PCM_INFO_PAUSE, + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = ATMEL_CLASSD_RATES, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 256, +}; + +#define ATMEL_CLASSD_PREALLOC_BUF_SIZE (64 * 1024) + +/* cpu dai component */ +static int atmel_classd_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + + regmap_write(dd->regmap, CLASSD_THR, 0x0); + + return clk_prepare_enable(dd->pclk); +} + +static void atmel_classd_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + + clk_disable_unprepare(dd->pclk); +} + +static const struct snd_soc_dai_ops atmel_classd_cpu_dai_ops = { + .startup = atmel_classd_cpu_dai_startup, + .shutdown = atmel_classd_cpu_dai_shutdown, +}; + +static struct snd_soc_dai_driver atmel_classd_cpu_dai = { + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_CLASSD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &atmel_classd_cpu_dai_ops, +}; + +static const struct snd_soc_component_driver atmel_classd_cpu_dai_component = { + .name = "atmel-classd", +}; + +/* platform */ +static int +atmel_classd_platform_configure_dma(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + + if (params_physical_width(params) != 16) { + dev_err(dd->dev, + "only supports 16-bit audio data\n"); + return -EINVAL; + } + + if (params_channels(params) == 1) + slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + slave_config->direction = DMA_MEM_TO_DEV; + slave_config->dst_addr = dd->phy_base + CLASSD_THR; + slave_config->dst_maxburst = 1; + slave_config->src_maxburst = 1; + slave_config->device_fc = false; + + return 0; +} + +static const struct snd_dmaengine_pcm_config +atmel_classd_dmaengine_pcm_config = { + .prepare_slave_config = atmel_classd_platform_configure_dma, + .pcm_hardware = &atmel_classd_hw, + .prealloc_buffer_size = ATMEL_CLASSD_PREALLOC_BUF_SIZE, +}; + +/* codec */ +static const char * const mono_mode_text[] = { + "mix", "sat", "left", "right" +}; + +static SOC_ENUM_SINGLE_DECL(classd_mono_mode_enum, + CLASSD_INTPMR, CLASSD_INTPMR_MONO_MODE_SHIFT, + mono_mode_text); + +static const char * const eqcfg_text[] = { + "Treble-12dB", "Treble-6dB", + "Medium-8dB", "Medium-3dB", + "Bass-12dB", "Bass-6dB", + "0 dB", + "Bass+6dB", "Bass+12dB", + "Medium+3dB", "Medium+8dB", + "Treble+6dB", "Treble+12dB", +}; + +static const unsigned int eqcfg_value[] = { + CLASSD_INTPMR_EQCFG_T_CUT_12, CLASSD_INTPMR_EQCFG_T_CUT_6, + CLASSD_INTPMR_EQCFG_M_CUT_8, CLASSD_INTPMR_EQCFG_M_CUT_3, + CLASSD_INTPMR_EQCFG_B_CUT_12, CLASSD_INTPMR_EQCFG_B_CUT_6, + CLASSD_INTPMR_EQCFG_FLAT, + CLASSD_INTPMR_EQCFG_B_BOOST_6, CLASSD_INTPMR_EQCFG_B_BOOST_12, + CLASSD_INTPMR_EQCFG_M_BOOST_3, CLASSD_INTPMR_EQCFG_M_BOOST_8, + CLASSD_INTPMR_EQCFG_T_BOOST_6, CLASSD_INTPMR_EQCFG_T_BOOST_12, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(classd_eqcfg_enum, + CLASSD_INTPMR, CLASSD_INTPMR_EQCFG_SHIFT, 0xf, + eqcfg_text, eqcfg_value); + +static const DECLARE_TLV_DB_SCALE(classd_digital_tlv, -7800, 100, 1); + +static const struct snd_kcontrol_new atmel_classd_snd_controls[] = { +SOC_DOUBLE_TLV("Playback Volume", CLASSD_INTPMR, + CLASSD_INTPMR_ATTL_SHIFT, CLASSD_INTPMR_ATTR_SHIFT, + 78, 1, classd_digital_tlv), + +SOC_SINGLE("Deemphasis Switch", CLASSD_INTPMR, + CLASSD_INTPMR_DEEMP_SHIFT, 1, 0), + +SOC_SINGLE("Mono Switch", CLASSD_INTPMR, CLASSD_INTPMR_MONO_SHIFT, 1, 0), + +SOC_SINGLE("Swap Switch", CLASSD_INTPMR, CLASSD_INTPMR_SWAP_SHIFT, 1, 0), + +SOC_ENUM("Mono Mode", classd_mono_mode_enum), + +SOC_ENUM("EQ", classd_eqcfg_enum), +}; + +static const char * const pwm_type[] = { + "Single ended", "Differential" +}; + +static int atmel_classd_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(component); + struct atmel_classd *dd = snd_soc_card_get_drvdata(card); + const struct atmel_classd_pdata *pdata = dd->pdata; + u32 mask, val; + + mask = CLASSD_MR_PWMTYP_MASK; + val = pdata->pwm_type << CLASSD_MR_PWMTYP_SHIFT; + + mask |= CLASSD_MR_NON_OVERLAP_MASK; + if (pdata->non_overlap_enable) { + val |= (CLASSD_MR_NON_OVERLAP_EN + << CLASSD_MR_NON_OVERLAP_SHIFT); + + mask |= CLASSD_MR_NOVR_VAL_MASK; + switch (pdata->non_overlap_time) { + case 5: + val |= (CLASSD_MR_NOVR_VAL_5NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + case 10: + val |= (CLASSD_MR_NOVR_VAL_10NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + case 15: + val |= (CLASSD_MR_NOVR_VAL_15NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + case 20: + val |= (CLASSD_MR_NOVR_VAL_20NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + default: + val |= (CLASSD_MR_NOVR_VAL_10NS + << CLASSD_MR_NOVR_VAL_SHIFT); + dev_warn(component->dev, + "non-overlapping value %d is invalid, the default value 10 is specified\n", + pdata->non_overlap_time); + break; + } + } + + snd_soc_component_update_bits(component, CLASSD_MR, mask, val); + + dev_info(component->dev, + "PWM modulation type is %s, non-overlapping is %s\n", + pwm_type[pdata->pwm_type], + pdata->non_overlap_enable?"enabled":"disabled"); + + return 0; +} + +static int atmel_classd_component_resume(struct snd_soc_component *component) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(component); + struct atmel_classd *dd = snd_soc_card_get_drvdata(card); + + return regcache_sync(dd->regmap); +} + +static struct snd_soc_component_driver soc_component_dev_classd = { + .probe = atmel_classd_component_probe, + .resume = atmel_classd_component_resume, + .controls = atmel_classd_snd_controls, + .num_controls = ARRAY_SIZE(atmel_classd_snd_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* codec dai component */ +static int atmel_classd_codec_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + + return clk_prepare_enable(dd->gclk); +} + +static int atmel_classd_codec_dai_digital_mute(struct snd_soc_dai *codec_dai, + int mute) +{ + struct snd_soc_component *component = codec_dai->component; + u32 mask, val; + + mask = CLASSD_MR_LMUTE_MASK | CLASSD_MR_RMUTE_MASK; + + if (mute) + val = mask; + else + val = 0; + + snd_soc_component_update_bits(component, CLASSD_MR, mask, val); + + return 0; +} + +#define CLASSD_GCLK_RATE_11M2896_MPY_8 (112896 * 100 * 8) +#define CLASSD_GCLK_RATE_12M288_MPY_8 (12288 * 1000 * 8) + +static struct { + int rate; + int sample_rate; + int dsp_clk; + unsigned long gclk_rate; +} const sample_rates[] = { + { 8000, CLASSD_INTPMR_FRAME_8K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 16000, CLASSD_INTPMR_FRAME_16K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 32000, CLASSD_INTPMR_FRAME_32K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 48000, CLASSD_INTPMR_FRAME_48K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 96000, CLASSD_INTPMR_FRAME_96K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 22050, CLASSD_INTPMR_FRAME_22K, + CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 }, + { 44100, CLASSD_INTPMR_FRAME_44K, + CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 }, + { 88200, CLASSD_INTPMR_FRAME_88K, + CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 }, +}; + +static int +atmel_classd_codec_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = codec_dai->component; + int fs; + int i, best, best_val, cur_val, ret; + u32 mask, val; + + fs = params_rate(params); + + best = 0; + best_val = abs(fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + + dev_dbg(component->dev, + "Selected SAMPLE_RATE of %dHz, GCLK_RATE of %ldHz\n", + sample_rates[best].rate, sample_rates[best].gclk_rate); + + clk_disable_unprepare(dd->gclk); + + ret = clk_set_rate(dd->gclk, sample_rates[best].gclk_rate); + if (ret) + return ret; + + mask = CLASSD_INTPMR_DSP_CLK_FREQ_MASK | CLASSD_INTPMR_FRAME_MASK; + val = (sample_rates[best].dsp_clk << CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT) + | (sample_rates[best].sample_rate << CLASSD_INTPMR_FRAME_SHIFT); + + snd_soc_component_update_bits(component, CLASSD_INTPMR, mask, val); + + return clk_prepare_enable(dd->gclk); +} + +static void +atmel_classd_codec_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + + clk_disable_unprepare(dd->gclk); +} + +static int atmel_classd_codec_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + + snd_soc_component_update_bits(component, CLASSD_MR, + CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK, + (CLASSD_MR_LEN_DIS << CLASSD_MR_LEN_SHIFT) + |(CLASSD_MR_REN_DIS << CLASSD_MR_REN_SHIFT)); + + return 0; +} + +static int atmel_classd_codec_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + u32 mask, val; + + mask = CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = mask; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = (CLASSD_MR_LEN_DIS << CLASSD_MR_LEN_SHIFT) + | (CLASSD_MR_REN_DIS << CLASSD_MR_REN_SHIFT); + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, CLASSD_MR, mask, val); + + return 0; +} + +static const struct snd_soc_dai_ops atmel_classd_codec_dai_ops = { + .digital_mute = atmel_classd_codec_dai_digital_mute, + .startup = atmel_classd_codec_dai_startup, + .shutdown = atmel_classd_codec_dai_shutdown, + .hw_params = atmel_classd_codec_dai_hw_params, + .prepare = atmel_classd_codec_dai_prepare, + .trigger = atmel_classd_codec_dai_trigger, +}; + +#define ATMEL_CLASSD_CODEC_DAI_NAME "atmel-classd-hifi" + +static struct snd_soc_dai_driver atmel_classd_codec_dai = { + .name = ATMEL_CLASSD_CODEC_DAI_NAME, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_CLASSD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &atmel_classd_codec_dai_ops, +}; + +/* ASoC sound card */ +static int atmel_classd_asoc_card_init(struct device *dev, + struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + struct atmel_classd *dd = snd_soc_card_get_drvdata(card); + + dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL); + if (!dai_link) + return -ENOMEM; + + dai_link->name = "CLASSD"; + dai_link->stream_name = "CLASSD PCM"; + dai_link->codec_dai_name = ATMEL_CLASSD_CODEC_DAI_NAME; + dai_link->cpu_dai_name = dev_name(dev); + dai_link->codec_name = dev_name(dev); + dai_link->platform_name = dev_name(dev); + + card->dai_link = dai_link; + card->num_links = 1; + card->name = dd->pdata->card_name; + card->dev = dev; + + return 0; +}; + +/* regmap configuration */ +static const struct reg_default atmel_classd_reg_defaults[] = { + { CLASSD_INTPMR, 0x00301212 }, +}; + +#define ATMEL_CLASSD_REG_MAX 0xE4 +static const struct regmap_config atmel_classd_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_CLASSD_REG_MAX, + + .cache_type = REGCACHE_FLAT, + .reg_defaults = atmel_classd_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(atmel_classd_reg_defaults), +}; + +static int atmel_classd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atmel_classd *dd; + struct resource *res; + void __iomem *io_base; + const struct atmel_classd_pdata *pdata; + struct snd_soc_card *card; + int ret; + + pdata = dev_get_platdata(dev); + if (!pdata) { + pdata = atmel_classd_dt_init(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; + + dd->pdata = pdata; + + dd->irq = platform_get_irq(pdev, 0); + if (dd->irq < 0) { + ret = dd->irq; + dev_err(dev, "failed to could not get irq: %d\n", ret); + return ret; + } + + dd->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dd->pclk)) { + ret = PTR_ERR(dd->pclk); + dev_err(dev, "failed to get peripheral clock: %d\n", ret); + return ret; + } + + dd->gclk = devm_clk_get(dev, "gclk"); + if (IS_ERR(dd->gclk)) { + ret = PTR_ERR(dd->gclk); + dev_err(dev, "failed to get GCK clock: %d\n", ret); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + dd->phy_base = res->start; + dd->dev = dev; + + dd->regmap = devm_regmap_init_mmio(dev, io_base, + &atmel_classd_regmap_config); + if (IS_ERR(dd->regmap)) { + ret = PTR_ERR(dd->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(dev, + &atmel_classd_cpu_dai_component, + &atmel_classd_cpu_dai, 1); + if (ret) { + dev_err(dev, "could not register CPU DAI: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(dev, + &atmel_classd_dmaengine_pcm_config, + 0); + if (ret) { + dev_err(dev, "could not register platform: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(dev, &soc_component_dev_classd, + &atmel_classd_codec_dai, 1); + if (ret) { + dev_err(dev, "could not register component: %d\n", ret); + return ret; + } + + /* register sound card */ + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) { + ret = -ENOMEM; + goto unregister_codec; + } + + snd_soc_card_set_drvdata(card, dd); + + ret = atmel_classd_asoc_card_init(dev, card); + if (ret) { + dev_err(dev, "failed to init sound card\n"); + goto unregister_codec; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + dev_err(dev, "failed to register sound card: %d\n", ret); + goto unregister_codec; + } + + return 0; + +unregister_codec: + return ret; +} + +static int atmel_classd_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver atmel_classd_driver = { + .driver = { + .name = "atmel-classd", + .of_match_table = of_match_ptr(atmel_classd_of_match), + .pm = &snd_soc_pm_ops, + }, + .probe = atmel_classd_probe, + .remove = atmel_classd_remove, +}; +module_platform_driver(atmel_classd_driver); + +MODULE_DESCRIPTION("Atmel ClassD driver under ALSA SoC architecture"); +MODULE_AUTHOR("Songjun Wu "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel-classd.h b/sound/soc/atmel/atmel-classd.h new file mode 100644 index 000000000..0f2e25aeb --- /dev/null +++ b/sound/soc/atmel/atmel-classd.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ATMEL_CLASSD_H_ +#define __ATMEL_CLASSD_H_ + +#define CLASSD_CR 0x00000000 +#define CLASSD_CR_RESET 0x1 + +#define CLASSD_MR 0x00000004 + +#define CLASSD_MR_LEN_DIS 0x0 +#define CLASSD_MR_LEN_EN 0x1 +#define CLASSD_MR_LEN_MASK (0x1 << 0) +#define CLASSD_MR_LEN_SHIFT (0) + +#define CLASSD_MR_LMUTE_DIS 0x0 +#define CLASSD_MR_LMUTE_EN 0x1 +#define CLASSD_MR_LMUTE_SHIFT (0x1) +#define CLASSD_MR_LMUTE_MASK (0x1 << 1) + +#define CLASSD_MR_REN_DIS 0x0 +#define CLASSD_MR_REN_EN 0x1 +#define CLASSD_MR_REN_MASK (0x1 << 4) +#define CLASSD_MR_REN_SHIFT (4) + +#define CLASSD_MR_RMUTE_DIS 0x0 +#define CLASSD_MR_RMUTE_EN 0x1 +#define CLASSD_MR_RMUTE_SHIFT (0x5) +#define CLASSD_MR_RMUTE_MASK (0x1 << 5) + +#define CLASSD_MR_PWMTYP_SINGLE 0x0 +#define CLASSD_MR_PWMTYP_DIFF 0x1 +#define CLASSD_MR_PWMTYP_MASK (0x1 << 8) +#define CLASSD_MR_PWMTYP_SHIFT (8) + +#define CLASSD_MR_NON_OVERLAP_DIS 0x0 +#define CLASSD_MR_NON_OVERLAP_EN 0x1 +#define CLASSD_MR_NON_OVERLAP_MASK (0x1 << 16) +#define CLASSD_MR_NON_OVERLAP_SHIFT (16) + +#define CLASSD_MR_NOVR_VAL_5NS 0x0 +#define CLASSD_MR_NOVR_VAL_10NS 0x1 +#define CLASSD_MR_NOVR_VAL_15NS 0x2 +#define CLASSD_MR_NOVR_VAL_20NS 0x3 +#define CLASSD_MR_NOVR_VAL_MASK (0x3 << 20) +#define CLASSD_MR_NOVR_VAL_SHIFT (20) + +#define CLASSD_INTPMR 0x00000008 + +#define CLASSD_INTPMR_ATTL_MASK (0x3f << 0) +#define CLASSD_INTPMR_ATTL_SHIFT (0) +#define CLASSD_INTPMR_ATTR_MASK (0x3f << 8) +#define CLASSD_INTPMR_ATTR_SHIFT (8) + +#define CLASSD_INTPMR_DSP_CLK_FREQ_12M288 0x0 +#define CLASSD_INTPMR_DSP_CLK_FREQ_11M2896 0x1 +#define CLASSD_INTPMR_DSP_CLK_FREQ_MASK (0x1 << 16) +#define CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT (16) + +#define CLASSD_INTPMR_DEEMP_DIS 0x0 +#define CLASSD_INTPMR_DEEMP_EN 0x1 +#define CLASSD_INTPMR_DEEMP_MASK (0x1 << 18) +#define CLASSD_INTPMR_DEEMP_SHIFT (18) + +#define CLASSD_INTPMR_SWAP_LEFT_ON_LSB 0x0 +#define CLASSD_INTPMR_SWAP_RIGHT_ON_LSB 0x1 +#define CLASSD_INTPMR_SWAP_MASK (0x1 << 19) +#define CLASSD_INTPMR_SWAP_SHIFT (19) + +#define CLASSD_INTPMR_FRAME_8K 0x0 +#define CLASSD_INTPMR_FRAME_16K 0x1 +#define CLASSD_INTPMR_FRAME_32K 0x2 +#define CLASSD_INTPMR_FRAME_48K 0x3 +#define CLASSD_INTPMR_FRAME_96K 0x4 +#define CLASSD_INTPMR_FRAME_22K 0x5 +#define CLASSD_INTPMR_FRAME_44K 0x6 +#define CLASSD_INTPMR_FRAME_88K 0x7 +#define CLASSD_INTPMR_FRAME_MASK (0x7 << 20) +#define CLASSD_INTPMR_FRAME_SHIFT (20) + +#define CLASSD_INTPMR_EQCFG_FLAT 0x0 +#define CLASSD_INTPMR_EQCFG_B_BOOST_12 0x1 +#define CLASSD_INTPMR_EQCFG_B_BOOST_6 0x2 +#define CLASSD_INTPMR_EQCFG_B_CUT_12 0x3 +#define CLASSD_INTPMR_EQCFG_B_CUT_6 0x4 +#define CLASSD_INTPMR_EQCFG_M_BOOST_3 0x5 +#define CLASSD_INTPMR_EQCFG_M_BOOST_8 0x6 +#define CLASSD_INTPMR_EQCFG_M_CUT_3 0x7 +#define CLASSD_INTPMR_EQCFG_M_CUT_8 0x8 +#define CLASSD_INTPMR_EQCFG_T_BOOST_12 0x9 +#define CLASSD_INTPMR_EQCFG_T_BOOST_6 0xa +#define CLASSD_INTPMR_EQCFG_T_CUT_12 0xb +#define CLASSD_INTPMR_EQCFG_T_CUT_6 0xc +#define CLASSD_INTPMR_EQCFG_SHIFT (24) + +#define CLASSD_INTPMR_MONO_DIS 0x0 +#define CLASSD_INTPMR_MONO_EN 0x1 +#define CLASSD_INTPMR_MONO_MASK (0x1 << 28) +#define CLASSD_INTPMR_MONO_SHIFT (28) + +#define CLASSD_INTPMR_MONO_MODE_MIX 0x0 +#define CLASSD_INTPMR_MONO_MODE_SAT 0x1 +#define CLASSD_INTPMR_MONO_MODE_LEFT 0x2 +#define CLASSD_INTPMR_MONO_MODE_RIGHT 0x3 +#define CLASSD_INTPMR_MONO_MODE_MASK (0x3 << 29) +#define CLASSD_INTPMR_MONO_MODE_SHIFT (29) + +#define CLASSD_INTSR 0x0000000c + +#define CLASSD_THR 0x00000010 + +#define CLASSD_IER 0x00000014 + +#define CLASSD_IDR 0x00000018 + +#define CLASSD_IMR 0x0000001c + +#define CLASSD_ISR 0x00000020 + +#define CLASSD_WPMR 0x000000e4 + +#endif diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c new file mode 100644 index 000000000..99cc73150 --- /dev/null +++ b/sound/soc/atmel/atmel-i2s.c @@ -0,0 +1,751 @@ +/* + * Driver for Atmel I2S controller + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Cyrille Pitchen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define ATMEL_I2SC_MAX_TDM_CHANNELS 8 + +/* + * ---- I2S Controller Register map ---- + */ +#define ATMEL_I2SC_CR 0x0000 /* Control Register */ +#define ATMEL_I2SC_MR 0x0004 /* Mode Register */ +#define ATMEL_I2SC_SR 0x0008 /* Status Register */ +#define ATMEL_I2SC_SCR 0x000c /* Status Clear Register */ +#define ATMEL_I2SC_SSR 0x0010 /* Status Set Register */ +#define ATMEL_I2SC_IER 0x0014 /* Interrupt Enable Register */ +#define ATMEL_I2SC_IDR 0x0018 /* Interrupt Disable Register */ +#define ATMEL_I2SC_IMR 0x001c /* Interrupt Mask Register */ +#define ATMEL_I2SC_RHR 0x0020 /* Receiver Holding Register */ +#define ATMEL_I2SC_THR 0x0024 /* Transmitter Holding Register */ +#define ATMEL_I2SC_VERSION 0x0028 /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define ATMEL_I2SC_CR_RXEN BIT(0) /* Receiver Enable */ +#define ATMEL_I2SC_CR_RXDIS BIT(1) /* Receiver Disable */ +#define ATMEL_I2SC_CR_CKEN BIT(2) /* Clock Enable */ +#define ATMEL_I2SC_CR_CKDIS BIT(3) /* Clock Disable */ +#define ATMEL_I2SC_CR_TXEN BIT(4) /* Transmitter Enable */ +#define ATMEL_I2SC_CR_TXDIS BIT(5) /* Transmitter Disable */ +#define ATMEL_I2SC_CR_SWRST BIT(7) /* Software Reset */ + +/* + * ---- Mode Register (Read/Write) ---- + */ +#define ATMEL_I2SC_MR_MODE_MASK GENMASK(0, 0) +#define ATMEL_I2SC_MR_MODE_SLAVE (0 << 0) +#define ATMEL_I2SC_MR_MODE_MASTER (1 << 0) + +#define ATMEL_I2SC_MR_DATALENGTH_MASK GENMASK(4, 2) +#define ATMEL_I2SC_MR_DATALENGTH_32_BITS (0 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_24_BITS (1 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_20_BITS (2 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_18_BITS (3 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS (4 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS_COMPACT (5 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS (6 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS_COMPACT (7 << 2) + +#define ATMEL_I2SC_MR_FORMAT_MASK GENMASK(7, 6) +#define ATMEL_I2SC_MR_FORMAT_I2S (0 << 6) +#define ATMEL_I2SC_MR_FORMAT_LJ (1 << 6) /* Left Justified */ +#define ATMEL_I2SC_MR_FORMAT_TDM (2 << 6) +#define ATMEL_I2SC_MR_FORMAT_TDMLJ (3 << 6) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_RXMONO BIT(8) + +/* Receiver uses one DMA channel ... */ +#define ATMEL_I2SC_MR_RXDMA_MASK GENMASK(9, 9) +#define ATMEL_I2SC_MR_RXDMA_SINGLE (0 << 9) /* for all audio channels */ +#define ATMEL_I2SC_MR_RXDMA_MULTIPLE (1 << 9) /* per audio channel */ + +/* I2SDO output of I2SC is internally connected to I2SDI input */ +#define ATMEL_I2SC_MR_RXLOOP BIT(10) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_TXMONO BIT(12) + +/* Transmitter uses one DMA channel ... */ +#define ATMEL_I2SC_MR_TXDMA_MASK GENMASK(13, 13) +#define ATMEL_I2SC_MR_TXDMA_SINGLE (0 << 13) /* for all audio channels */ +#define ATMEL_I2SC_MR_TXDME_MULTIPLE (1 << 13) /* per audio channel */ + +/* x sample transmitted when underrun */ +#define ATMEL_I2SC_MR_TXSAME_MASK GENMASK(14, 14) +#define ATMEL_I2SC_MR_TXSAME_ZERO (0 << 14) /* Zero sample */ +#define ATMEL_I2SC_MR_TXSAME_PREVIOUS (1 << 14) /* Previous sample */ + +/* Audio Clock to I2SC Master Clock ratio */ +#define ATMEL_I2SC_MR_IMCKDIV_MASK GENMASK(21, 16) +#define ATMEL_I2SC_MR_IMCKDIV(div) \ + (((div) << 16) & ATMEL_I2SC_MR_IMCKDIV_MASK) + +/* Master Clock to fs ratio */ +#define ATMEL_I2SC_MR_IMCKFS_MASK GENMASK(29, 24) +#define ATMEL_I2SC_MR_IMCKFS(fs) \ + (((fs) << 24) & ATMEL_I2SC_MR_IMCKFS_MASK) + +/* Master Clock mode */ +#define ATMEL_I2SC_MR_IMCKMODE_MASK GENMASK(30, 30) +/* 0: No master clock generated (selected clock drives I2SCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SCK (0 << 30) +/* 1: master clock generated (internally generated clock drives I2SMCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SMCK (1 << 30) + +/* Slot Width */ +/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */ +/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */ +#define ATMEL_I2SC_MR_IWS BIT(31) + +/* + * ---- Status Registers ---- + */ +#define ATMEL_I2SC_SR_RXEN BIT(0) /* Receiver Enabled */ +#define ATMEL_I2SC_SR_RXRDY BIT(1) /* Receive Ready */ +#define ATMEL_I2SC_SR_RXOR BIT(2) /* Receive Overrun */ + +#define ATMEL_I2SC_SR_TXEN BIT(4) /* Transmitter Enabled */ +#define ATMEL_I2SC_SR_TXRDY BIT(5) /* Transmit Ready */ +#define ATMEL_I2SC_SR_TXUR BIT(6) /* Transmit Underrun */ + +/* Receive Overrun Channel */ +#define ATMEL_I2SC_SR_RXORCH_MASK GENMASK(15, 8) +#define ATMEL_I2SC_SR_RXORCH(ch) (1 << (((ch) & 0x7) + 8)) + +/* Transmit Underrun Channel */ +#define ATMEL_I2SC_SR_TXURCH_MASK GENMASK(27, 20) +#define ATMEL_I2SC_SR_TXURCH(ch) (1 << (((ch) & 0x7) + 20)) + +/* + * ---- Interrupt Enable/Disable/Mask Registers ---- + */ +#define ATMEL_I2SC_INT_RXRDY ATMEL_I2SC_SR_RXRDY +#define ATMEL_I2SC_INT_RXOR ATMEL_I2SC_SR_RXOR +#define ATMEL_I2SC_INT_TXRDY ATMEL_I2SC_SR_TXRDY +#define ATMEL_I2SC_INT_TXUR ATMEL_I2SC_SR_TXUR + +static const struct regmap_config atmel_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_I2SC_VERSION, +}; + +struct atmel_i2s_gck_param { + int fs; + unsigned long mck; + int imckdiv; + int imckfs; +}; + +#define I2S_MCK_12M288 12288000UL +#define I2S_MCK_11M2896 11289600UL + +/* mck = (32 * (imckfs+1) / (imckdiv+1)) * fs */ +static const struct atmel_i2s_gck_param gck_params[] = { + /* mck = 12.288MHz */ + { 8000, I2S_MCK_12M288, 0, 47}, /* mck = 1536 fs */ + { 16000, I2S_MCK_12M288, 1, 47}, /* mck = 768 fs */ + { 24000, I2S_MCK_12M288, 3, 63}, /* mck = 512 fs */ + { 32000, I2S_MCK_12M288, 3, 47}, /* mck = 384 fs */ + { 48000, I2S_MCK_12M288, 7, 63}, /* mck = 256 fs */ + { 64000, I2S_MCK_12M288, 7, 47}, /* mck = 192 fs */ + { 96000, I2S_MCK_12M288, 7, 31}, /* mck = 128 fs */ + {192000, I2S_MCK_12M288, 7, 15}, /* mck = 64 fs */ + + /* mck = 11.2896MHz */ + { 11025, I2S_MCK_11M2896, 1, 63}, /* mck = 1024 fs */ + { 22050, I2S_MCK_11M2896, 3, 63}, /* mck = 512 fs */ + { 44100, I2S_MCK_11M2896, 7, 63}, /* mck = 256 fs */ + { 88200, I2S_MCK_11M2896, 7, 31}, /* mck = 128 fs */ + {176400, I2S_MCK_11M2896, 7, 15}, /* mck = 64 fs */ +}; + +struct atmel_i2s_dev; + +struct atmel_i2s_caps { + int (*mck_init)(struct atmel_i2s_dev *, struct device_node *np); +}; + +struct atmel_i2s_dev { + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct snd_dmaengine_dai_dma_data playback; + struct snd_dmaengine_dai_dma_data capture; + unsigned int fmt; + const struct atmel_i2s_gck_param *gck_param; + const struct atmel_i2s_caps *caps; + int clk_use_no; +}; + +static irqreturn_t atmel_i2s_interrupt(int irq, void *dev_id) +{ + struct atmel_i2s_dev *dev = dev_id; + unsigned int sr, imr, pending, ch, mask; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + regmap_read(dev->regmap, ATMEL_I2SC_IMR, &imr); + pending = sr & imr; + + if (!pending) + return IRQ_NONE; + + if (pending & ATMEL_I2SC_INT_RXOR) { + mask = ATMEL_I2SC_SR_RXOR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_RXORCH(ch)) { + mask |= ATMEL_I2SC_SR_RXORCH(ch); + dev_err(dev->dev, + "RX overrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + if (pending & ATMEL_I2SC_INT_TXUR) { + mask = ATMEL_I2SC_SR_TXUR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_TXURCH(ch)) { + mask |= ATMEL_I2SC_SR_TXURCH(ch); + dev_err(dev->dev, + "TX underrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + return ret; +} + +#define ATMEL_I2S_RATES SNDRV_PCM_RATE_8000_192000 + +#define ATMEL_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int atmel_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev->fmt = fmt; + return 0; +} + +static int atmel_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int rhr, sr = 0; + + if (is_playback) { + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + if (sr & ATMEL_I2SC_SR_RXRDY) { + /* + * The RX Ready flag should not be set. However if here, + * we flush (read) the Receive Holding Register to start + * from a clean state. + */ + dev_dbg(dev->dev, "RXRDY is set\n"); + regmap_read(dev->regmap, ATMEL_I2SC_RHR, &rhr); + } + } + + return 0; +} + +static int atmel_i2s_get_gck_param(struct atmel_i2s_dev *dev, int fs) +{ + int i, best; + + if (!dev->gclk) { + dev_err(dev->dev, "cannot generate the I2S Master Clock\n"); + return -EINVAL; + } + + /* + * Find the best possible settings to generate the I2S Master Clock + * from the PLL Audio. + */ + dev->gck_param = NULL; + best = INT_MAX; + for (i = 0; i < ARRAY_SIZE(gck_params); ++i) { + const struct atmel_i2s_gck_param *gck_param = &gck_params[i]; + int val = abs(fs - gck_param->fs); + + if (val < best) { + best = val; + dev->gck_param = gck_param; + } + } + + return 0; +} + +static int atmel_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int mr = 0, mr_mask; + int ret; + + mr_mask = ATMEL_I2SC_MR_FORMAT_MASK | ATMEL_I2SC_MR_MODE_MASK | + ATMEL_I2SC_MR_DATALENGTH_MASK; + if (is_playback) + mr_mask |= ATMEL_I2SC_MR_TXMONO; + else + mr_mask |= ATMEL_I2SC_MR_RXMONO; + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mr |= ATMEL_I2SC_MR_FORMAT_I2S; + break; + + default: + dev_err(dev->dev, "unsupported bus format\n"); + return -EINVAL; + } + + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* codec is slave, so cpu is master */ + mr |= ATMEL_I2SC_MR_MODE_MASTER; + ret = atmel_i2s_get_gck_param(dev, params_rate(params)); + if (ret) + return ret; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master, so cpu is slave */ + mr |= ATMEL_I2SC_MR_MODE_SLAVE; + dev->gck_param = NULL; + break; + + default: + dev_err(dev->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + switch (params_channels(params)) { + case 1: + if (is_playback) + mr |= ATMEL_I2SC_MR_TXMONO; + else + mr |= ATMEL_I2SC_MR_RXMONO; + break; + case 2: + break; + default: + dev_err(dev->dev, "unsupported number of audio channels\n"); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mr |= ATMEL_I2SC_MR_DATALENGTH_8_BITS; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_16_BITS; + break; + + case SNDRV_PCM_FORMAT_S18_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_18_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S20_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_20_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_32_BITS; + break; + + default: + dev_err(dev->dev, "unsupported size/endianness for audio samples\n"); + return -EINVAL; + } + + return regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); +} + +static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev, + bool enabled) +{ + unsigned int mr, mr_mask; + unsigned long gclk_rate; + int ret; + + mr = 0; + mr_mask = (ATMEL_I2SC_MR_IMCKDIV_MASK | + ATMEL_I2SC_MR_IMCKFS_MASK | + ATMEL_I2SC_MR_IMCKMODE_MASK); + + if (!enabled) { + /* Disable the I2S Master Clock generator. */ + ret = regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKDIS); + if (ret) + return ret; + + /* Reset the I2S Master Clock generator settings. */ + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, + mr_mask, mr); + if (ret) + return ret; + + /* Disable/unprepare the PMC generated clock. */ + clk_disable_unprepare(dev->gclk); + + return 0; + } + + if (!dev->gck_param) + return -EINVAL; + + gclk_rate = dev->gck_param->mck * (dev->gck_param->imckdiv + 1); + + ret = clk_set_rate(dev->gclk, gclk_rate); + if (ret) + return ret; + + ret = clk_prepare_enable(dev->gclk); + if (ret) + return ret; + + /* Update the Mode Register to generate the I2S Master Clock. */ + mr |= ATMEL_I2SC_MR_IMCKDIV(dev->gck_param->imckdiv); + mr |= ATMEL_I2SC_MR_IMCKFS(dev->gck_param->imckfs); + mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK; + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); + if (ret) + return ret; + + /* Finally enable the I2S Master Clock generator. */ + return regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKEN); +} + +static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + bool is_master, mck_enabled; + unsigned int cr, mr; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + cr = is_playback ? ATMEL_I2SC_CR_TXEN : ATMEL_I2SC_CR_RXEN; + mck_enabled = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + cr = is_playback ? ATMEL_I2SC_CR_TXDIS : ATMEL_I2SC_CR_RXDIS; + mck_enabled = false; + break; + default: + return -EINVAL; + } + + /* Read the Mode Register to retrieve the master/slave state. */ + err = regmap_read(dev->regmap, ATMEL_I2SC_MR, &mr); + if (err) + return err; + is_master = (mr & ATMEL_I2SC_MR_MODE_MASK) == ATMEL_I2SC_MR_MODE_MASTER; + + /* If master starts, enable the audio clock. */ + if (is_master && mck_enabled) { + if (!dev->clk_use_no) { + err = atmel_i2s_switch_mck_generator(dev, true); + if (err) + return err; + } + dev->clk_use_no++; + } + + err = regmap_write(dev->regmap, ATMEL_I2SC_CR, cr); + if (err) + return err; + + /* If master stops, disable the audio clock. */ + if (is_master && !mck_enabled) { + if (dev->clk_use_no == 1) { + err = atmel_i2s_switch_mck_generator(dev, false); + if (err) + return err; + } + dev->clk_use_no--; + } + + return err; +} + +static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, +}; + +static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture); + return 0; +} + +static struct snd_soc_dai_driver atmel_i2s_dai = { + .probe = atmel_i2s_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .ops = &atmel_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver atmel_i2s_component = { + .name = "atmel-i2s", +}; + +static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev, + struct device_node *np) +{ + struct clk *muxclk; + int err; + + if (!dev->gclk) + return 0; + + /* muxclk is optional, so we return error for probe defer only */ + muxclk = devm_clk_get(dev->dev, "muxclk"); + if (IS_ERR(muxclk)) { + err = PTR_ERR(muxclk); + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(dev->dev, + "failed to get the I2S clock control: %d\n", err); + return 0; + } + + return clk_set_parent(muxclk, dev->gclk); +} + +static const struct atmel_i2s_caps atmel_i2s_sama5d2_caps = { + .mck_init = atmel_i2s_sama5d2_mck_init, +}; + +static const struct of_device_id atmel_i2s_dt_ids[] = { + { + .compatible = "atmel,sama5d2-i2s", + .data = (void *)&atmel_i2s_sama5d2_caps, + }, + + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_i2s_dt_ids); + +static int atmel_i2s_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + struct atmel_i2s_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + int irq; + int err = -ENXIO; + unsigned int pcm_flags = 0; + unsigned int version; + + /* Get memory for driver data. */ + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* Get hardware capabilities. */ + match = of_match_node(atmel_i2s_dt_ids, np); + if (match) + dev->caps = match->data; + + /* Map I/O registers. */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + &atmel_i2s_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Request IRQ. */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(&pdev->dev, irq, atmel_i2s_interrupt, 0, + dev_name(&pdev->dev), dev); + if (err) + return err; + + /* Get the peripheral clock. */ + dev->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(dev->pclk)) { + err = PTR_ERR(dev->pclk); + dev_err(&pdev->dev, + "failed to get the peripheral clock: %d\n", err); + return err; + } + + /* Get audio clock to generate the I2S Master Clock (I2S_MCK) */ + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) { + if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + /* Master Mode not supported */ + dev->gclk = NULL; + } + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + /* Do hardware specific settings to initialize I2S_MCK generator */ + if (dev->caps && dev->caps->mck_init) { + err = dev->caps->mck_init(dev, np); + if (err) + return err; + } + + /* Enable the peripheral clock. */ + err = clk_prepare_enable(dev->pclk); + if (err) + return err; + + /* Get IP version. */ + regmap_read(dev->regmap, ATMEL_I2SC_VERSION, &version); + dev_info(&pdev->dev, "hw version: %#x\n", version); + + /* Enable error interrupts. */ + regmap_write(dev->regmap, ATMEL_I2SC_IER, + ATMEL_I2SC_INT_RXOR | ATMEL_I2SC_INT_TXUR); + + err = devm_snd_soc_register_component(&pdev->dev, + &atmel_i2s_component, + &atmel_i2s_dai, 1); + if (err) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + /* Prepare DMA config. */ + dev->playback.addr = (dma_addr_t)mem->start + ATMEL_I2SC_THR; + dev->playback.maxburst = 1; + dev->capture.addr = (dma_addr_t)mem->start + ATMEL_I2SC_RHR; + dev->capture.maxburst = 1; + + if (of_property_match_string(np, "dma-names", "rx-tx") == 0) + pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags); + if (err) { + dev_err(&pdev->dev, "failed to register PCM: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + return 0; +} + +static int atmel_i2s_remove(struct platform_device *pdev) +{ + struct atmel_i2s_dev *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct platform_driver atmel_i2s_driver = { + .driver = { + .name = "atmel_i2s", + .of_match_table = of_match_ptr(atmel_i2s_dt_ids), + }, + .probe = atmel_i2s_probe, + .remove = atmel_i2s_remove, +}; +module_platform_driver(atmel_i2s_driver); + +MODULE_DESCRIPTION("Atmel I2S Controller driver"); +MODULE_AUTHOR("Cyrille Pitchen "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c new file mode 100644 index 000000000..dd57a9eac --- /dev/null +++ b/sound/soc/atmel/atmel-pcm-dma.c @@ -0,0 +1,139 @@ +/* + * atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC. + * + * Copyright (C) 2012 Atmel + * + * Author: Bo Shen + * + * Based on atmel-pcm by: + * Sedji Gaouaou + * Copyright 2008 Atmel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "atmel-pcm.h" + +/*--------------------------------------------------------------------------*\ + * Hardware definition +\*--------------------------------------------------------------------------*/ +static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .period_bytes_min = 256, /* lighting DMA overhead */ + .period_bytes_max = 2 * 0xffff, /* if 2 bytes format */ + .periods_min = 8, + .periods_max = 1024, /* no limit */ + .buffer_bytes_max = 512 * 1024, +}; + +/** + * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC + * + * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to + * check if any overrun occured. + */ +static void atmel_pcm_dma_irq(u32 ssc_sr, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_pcm_dma_params *prtd; + + prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + if (ssc_sr & prtd->mask->ssc_error) { + if (snd_pcm_running(substream)) + pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n", + substream->stream == SNDRV_PCM_STREAM_PLAYBACK + ? "underrun" : "overrun", prtd->name, + ssc_sr); + + /* stop RX and capture: will be enabled again at restart */ + ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable); + snd_pcm_stop_xrun(substream); + + /* now drain RHR and read status to remove xrun condition */ + ssc_readx(prtd->ssc->regs, SSC_RHR); + ssc_readx(prtd->ssc->regs, SSC_SR); + } +} + +static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_pcm_dma_params *prtd; + struct ssc_device *ssc; + int ret; + + prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + ssc = prtd->ssc; + + ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config); + if (ret) { + pr_err("atmel-pcm: hwparams to dma slave configure failed\n"); + return ret; + } + + slave_config->dst_addr = ssc->phybase + SSC_THR; + slave_config->dst_maxburst = 1; + + slave_config->src_addr = ssc->phybase + SSC_RHR; + slave_config->src_maxburst = 1; + + prtd->dma_intr_handler = atmel_pcm_dma_irq; + + return 0; +} + +static const struct snd_dmaengine_pcm_config atmel_dmaengine_pcm_config = { + .prepare_slave_config = atmel_pcm_configure_dma, + .pcm_hardware = &atmel_pcm_dma_hardware, + .prealloc_buffer_size = 64 * 1024, +}; + +int atmel_pcm_dma_platform_register(struct device *dev) +{ + return snd_dmaengine_pcm_register(dev, &atmel_dmaengine_pcm_config, 0); +} +EXPORT_SYMBOL(atmel_pcm_dma_platform_register); + +void atmel_pcm_dma_platform_unregister(struct device *dev) +{ + snd_dmaengine_pcm_unregister(dev); +} +EXPORT_SYMBOL(atmel_pcm_dma_platform_unregister); + +MODULE_AUTHOR("Bo Shen "); +MODULE_DESCRIPTION("Atmel DMA based PCM module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c new file mode 100644 index 000000000..99ca23d52 --- /dev/null +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -0,0 +1,416 @@ +/* + * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC. + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Authors: Sedji Gaouaou + * + * Based on at91-pcm. by: + * Frank Mandarino + * Copyright 2006 Endrelia Technologies Inc. + * + * Based on pxa2xx-pcm.c by: + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: (C) 2004 MontaVista Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "atmel-pcm.h" + + +static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = ATMEL_SSC_DMABUF_SIZE; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n", + (void *)buf->area, (void *)(long)buf->addr, size); + + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static int atmel_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static int atmel_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n"); + ret = atmel_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n"); + ret = atmel_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +static void atmel_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +/*--------------------------------------------------------------------------*\ + * Hardware definition +\*--------------------------------------------------------------------------*/ +/* TODO: These values were taken from the AT91 platform driver, check + * them against real values for AT32 + */ +static const struct snd_pcm_hardware atmel_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, +}; + + +/*--------------------------------------------------------------------------*\ + * Data types +\*--------------------------------------------------------------------------*/ +struct atmel_runtime_data { + struct atmel_pcm_dma_params *params; + dma_addr_t dma_buffer; /* physical address of dma buffer */ + dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ + size_t period_size; + + dma_addr_t period_ptr; /* physical address of next period */ +}; + +/*--------------------------------------------------------------------------*\ + * ISR +\*--------------------------------------------------------------------------*/ +static void atmel_pcm_dma_irq(u32 ssc_sr, + struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + static int count; + + count++; + + if (ssc_sr & params->mask->ssc_endbuf) { + pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n", + substream->stream == SNDRV_PCM_STREAM_PLAYBACK + ? "underrun" : "overrun", + params->name, ssc_sr, count); + + /* re-start the PDC */ + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) + prtd->period_ptr = prtd->dma_buffer; + + ssc_writex(params->ssc->regs, params->pdc->xpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_enable); + } + + if (ssc_sr & params->mask->ssc_endx) { + /* Load the PDC next pointer and counter registers */ + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) + prtd->period_ptr = prtd->dma_buffer; + + ssc_writex(params->ssc->regs, params->pdc->xnpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + } + + snd_pcm_period_elapsed(substream); +} + + +/*--------------------------------------------------------------------------*\ + * PCM operations +\*--------------------------------------------------------------------------*/ +static int atmel_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct atmel_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* this may get called several times by oss emulation + * with different params */ + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + prtd->params->dma_intr_handler = atmel_pcm_dma_irq; + + prtd->dma_buffer = runtime->dma_addr; + prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes; + prtd->period_size = params_period_bytes(params); + + pr_debug("atmel-pcm: " + "hw_params: DMA for %s initialized " + "(dma_bytes=%zu, period_size=%zu)\n", + prtd->params->name, + runtime->dma_bytes, + prtd->period_size); + return 0; +} + +static int atmel_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + + if (params != NULL) { + ssc_writex(params->ssc->regs, SSC_PDC_PTCR, + params->mask->pdc_disable); + prtd->params->dma_intr_handler = NULL; + } + + return 0; +} + +static int atmel_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + + ssc_writex(params->ssc->regs, SSC_IDR, + params->mask->ssc_endx | params->mask->ssc_endbuf); + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + return 0; +} + +static int atmel_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_pcm_runtime *rtd = substream->runtime; + struct atmel_runtime_data *prtd = rtd->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + int ret = 0; + + pr_debug("atmel-pcm:buffer_size = %ld," + "dma_area = %p, dma_bytes = %zu\n", + rtd->buffer_size, rtd->dma_area, rtd->dma_bytes); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->period_ptr = prtd->dma_buffer; + + ssc_writex(params->ssc->regs, params->pdc->xpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + + prtd->period_ptr += prtd->period_size; + ssc_writex(params->ssc->regs, params->pdc->xnpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + + pr_debug("atmel-pcm: trigger: " + "period_ptr=%lx, xpr=%u, " + "xcr=%u, xnpr=%u, xncr=%u\n", + (unsigned long)prtd->period_ptr, + ssc_readx(params->ssc->regs, params->pdc->xpr), + ssc_readx(params->ssc->regs, params->pdc->xcr), + ssc_readx(params->ssc->regs, params->pdc->xnpr), + ssc_readx(params->ssc->regs, params->pdc->xncr)); + + ssc_writex(params->ssc->regs, SSC_IER, + params->mask->ssc_endx | params->mask->ssc_endbuf); + ssc_writex(params->ssc->regs, SSC_PDC_PTCR, + params->mask->pdc_enable); + + pr_debug("sr=%u imr=%u\n", + ssc_readx(params->ssc->regs, SSC_SR), + ssc_readx(params->ssc->regs, SSC_IER)); + break; /* SNDRV_PCM_TRIGGER_START */ + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_enable); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t atmel_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct atmel_runtime_data *prtd = runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + dma_addr_t ptr; + snd_pcm_uframes_t x; + + ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr); + x = bytes_to_frames(runtime, ptr - prtd->dma_buffer); + + if (x == runtime->buffer_size) + x = 0; + + return x; +} + +static int atmel_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct atmel_runtime_data *prtd; + int ret = 0; + + snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + goto out; + } + runtime->private_data = prtd; + + out: + return ret; +} + +static int atmel_pcm_close(struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + + kfree(prtd); + return 0; +} + +static const struct snd_pcm_ops atmel_pcm_ops = { + .open = atmel_pcm_open, + .close = atmel_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = atmel_pcm_hw_params, + .hw_free = atmel_pcm_hw_free, + .prepare = atmel_pcm_prepare, + .trigger = atmel_pcm_trigger, + .pointer = atmel_pcm_pointer, + .mmap = atmel_pcm_mmap, +}; + +static struct snd_soc_component_driver atmel_soc_platform = { + .ops = &atmel_pcm_ops, + .pcm_new = atmel_pcm_new, + .pcm_free = atmel_pcm_free, +}; + +int atmel_pcm_pdc_platform_register(struct device *dev) +{ + return devm_snd_soc_register_component(dev, &atmel_soc_platform, + NULL, 0); +} +EXPORT_SYMBOL(atmel_pcm_pdc_platform_register); + +void atmel_pcm_pdc_platform_unregister(struct device *dev) +{ +} +EXPORT_SYMBOL(atmel_pcm_pdc_platform_unregister); + +MODULE_AUTHOR("Sedji Gaouaou "); +MODULE_DESCRIPTION("Atmel PCM module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel-pcm.h b/sound/soc/atmel/atmel-pcm.h new file mode 100644 index 000000000..4b27aed40 --- /dev/null +++ b/sound/soc/atmel/atmel-pcm.h @@ -0,0 +1,112 @@ +/* + * at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC. + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Authors: Sedji Gaouaou + * + * Based on at91-pcm. by: + * Frank Mandarino + * Copyright 2006 Endrelia Technologies Inc. + * + * Based on pxa2xx-pcm.c by: + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: (C) 2004 MontaVista Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ATMEL_PCM_H +#define _ATMEL_PCM_H + +#include + +#define ATMEL_SSC_DMABUF_SIZE (64 * 1024) + +/* + * Registers and status bits that are required by the PCM driver. + */ +struct atmel_pdc_regs { + unsigned int xpr; /* PDC recv/trans pointer */ + unsigned int xcr; /* PDC recv/trans counter */ + unsigned int xnpr; /* PDC next recv/trans pointer */ + unsigned int xncr; /* PDC next recv/trans counter */ + unsigned int ptcr; /* PDC transfer control */ +}; + +struct atmel_ssc_mask { + u32 ssc_enable; /* SSC recv/trans enable */ + u32 ssc_disable; /* SSC recv/trans disable */ + u32 ssc_error; /* SSC error conditions */ + u32 ssc_endx; /* SSC ENDTX or ENDRX */ + u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */ + u32 pdc_enable; /* PDC recv/trans enable */ + u32 pdc_disable; /* PDC recv/trans disable */ +}; + +/* + * This structure, shared between the PCM driver and the interface, + * contains all information required by the PCM driver to perform the + * PDC DMA operation. All fields except dma_intr_handler() are initialized + * by the interface. The dma_intr_handler() pointer is set by the PCM + * driver and called by the interface SSC interrupt handler if it is + * non-NULL. + */ +struct atmel_pcm_dma_params { + char *name; /* stream identifier */ + int pdc_xfer_size; /* PDC counter increment in bytes */ + struct ssc_device *ssc; /* SSC device for stream */ + struct atmel_pdc_regs *pdc; /* PDC receive or transmit registers */ + struct atmel_ssc_mask *mask; /* SSC & PDC status bits */ + struct snd_pcm_substream *substream; + void (*dma_intr_handler)(u32, struct snd_pcm_substream *); +}; + +/* + * SSC register access (since ssc_writel() / ssc_readl() require literal name) + */ +#define ssc_readx(base, reg) (__raw_readl((base) + (reg))) +#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg)) + +#if IS_ENABLED(CONFIG_SND_ATMEL_SOC_PDC) +int atmel_pcm_pdc_platform_register(struct device *dev); +void atmel_pcm_pdc_platform_unregister(struct device *dev); +#else +static inline int atmel_pcm_pdc_platform_register(struct device *dev) +{ + return 0; +} +static inline void atmel_pcm_pdc_platform_unregister(struct device *dev) +{ +} +#endif + +#if IS_ENABLED(CONFIG_SND_ATMEL_SOC_DMA) +int atmel_pcm_dma_platform_register(struct device *dev); +void atmel_pcm_dma_platform_unregister(struct device *dev); +#else +static inline int atmel_pcm_dma_platform_register(struct device *dev) +{ + return 0; +} +static inline void atmel_pcm_dma_platform_unregister(struct device *dev) +{ +} +#endif + +#endif /* _ATMEL_PCM_H */ diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c new file mode 100644 index 000000000..915c2b064 --- /dev/null +++ b/sound/soc/atmel/atmel-pdmic.c @@ -0,0 +1,735 @@ +/* Atmel PDMIC driver + * + * Copyright (C) 2015 Atmel + * + * Author: Songjun Wu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or later + * as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "atmel-pdmic.h" + +struct atmel_pdmic_pdata { + u32 mic_min_freq; + u32 mic_max_freq; + s32 mic_offset; + const char *card_name; +}; + +struct atmel_pdmic { + dma_addr_t phy_base; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct device *dev; + int irq; + struct snd_pcm_substream *substream; + const struct atmel_pdmic_pdata *pdata; +}; + +static const struct of_device_id atmel_pdmic_of_match[] = { + { + .compatible = "atmel,sama5d2-pdmic", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, atmel_pdmic_of_match); + +#define PDMIC_OFFSET_MAX_VAL S16_MAX +#define PDMIC_OFFSET_MIN_VAL S16_MIN + +static struct atmel_pdmic_pdata *atmel_pdmic_dt_init(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct atmel_pdmic_pdata *pdata; + + if (!np) { + dev_err(dev, "device node not found\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + if (of_property_read_string(np, "atmel,model", &pdata->card_name)) + pdata->card_name = "PDMIC"; + + if (of_property_read_u32(np, "atmel,mic-min-freq", + &pdata->mic_min_freq)) { + dev_err(dev, "failed to get mic-min-freq\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_u32(np, "atmel,mic-max-freq", + &pdata->mic_max_freq)) { + dev_err(dev, "failed to get mic-max-freq\n"); + return ERR_PTR(-EINVAL); + } + + if (pdata->mic_max_freq < pdata->mic_min_freq) { + dev_err(dev, + "mic-max-freq should not be less than mic-min-freq\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_s32(np, "atmel,mic-offset", &pdata->mic_offset)) + pdata->mic_offset = 0; + + if (pdata->mic_offset > PDMIC_OFFSET_MAX_VAL) { + dev_warn(dev, + "mic-offset value %d is larger than the max value %d, the max value is specified\n", + pdata->mic_offset, PDMIC_OFFSET_MAX_VAL); + pdata->mic_offset = PDMIC_OFFSET_MAX_VAL; + } else if (pdata->mic_offset < PDMIC_OFFSET_MIN_VAL) { + dev_warn(dev, + "mic-offset value %d is less than the min value %d, the min value is specified\n", + pdata->mic_offset, PDMIC_OFFSET_MIN_VAL); + pdata->mic_offset = PDMIC_OFFSET_MIN_VAL; + } + + return pdata; +} + +/* cpu dai component */ +static int atmel_pdmic_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + int ret; + + ret = clk_prepare_enable(dd->gclk); + if (ret) + return ret; + + ret = clk_prepare_enable(dd->pclk); + if (ret) { + clk_disable_unprepare(dd->gclk); + return ret; + } + + /* Clear all bits in the Control Register(PDMIC_CR) */ + regmap_write(dd->regmap, PDMIC_CR, 0); + + dd->substream = substream; + + /* Enable the overrun error interrupt */ + regmap_write(dd->regmap, PDMIC_IER, PDMIC_IER_OVRE); + + return 0; +} + +static void atmel_pdmic_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + + /* Disable the overrun error interrupt */ + regmap_write(dd->regmap, PDMIC_IDR, PDMIC_IDR_OVRE); + + clk_disable_unprepare(dd->gclk); + clk_disable_unprepare(dd->pclk); +} + +static int atmel_pdmic_cpu_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + u32 val; + + /* Clean the PDMIC Converted Data Register */ + return regmap_read(dd->regmap, PDMIC_CDR, &val); +} + +static const struct snd_soc_dai_ops atmel_pdmic_cpu_dai_ops = { + .startup = atmel_pdmic_cpu_dai_startup, + .shutdown = atmel_pdmic_cpu_dai_shutdown, + .prepare = atmel_pdmic_cpu_dai_prepare, +}; + +#define ATMEL_PDMIC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver atmel_pdmic_cpu_dai = { + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = ATMEL_PDMIC_FORMATS,}, + .ops = &atmel_pdmic_cpu_dai_ops, +}; + +static const struct snd_soc_component_driver atmel_pdmic_cpu_dai_component = { + .name = "atmel-pdmic", +}; + +/* platform */ +#define ATMEL_PDMIC_MAX_BUF_SIZE (64 * 1024) +#define ATMEL_PDMIC_PREALLOC_BUF_SIZE ATMEL_PDMIC_MAX_BUF_SIZE + +static const struct snd_pcm_hardware atmel_pdmic_hw = { + .info = SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_RESUME + | SNDRV_PCM_INFO_PAUSE, + .formats = ATMEL_PDMIC_FORMATS, + .buffer_bytes_max = ATMEL_PDMIC_MAX_BUF_SIZE, + .period_bytes_min = 256, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 256, +}; + +static int +atmel_pdmic_platform_configure_dma(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + int ret; + + ret = snd_hwparams_to_dma_slave_config(substream, params, + slave_config); + if (ret) { + dev_err(dd->dev, + "hw params to dma slave configure failed\n"); + return ret; + } + + slave_config->src_addr = dd->phy_base + PDMIC_CDR; + slave_config->src_maxburst = 1; + slave_config->dst_maxburst = 1; + + return 0; +} + +static const struct snd_dmaengine_pcm_config +atmel_pdmic_dmaengine_pcm_config = { + .prepare_slave_config = atmel_pdmic_platform_configure_dma, + .pcm_hardware = &atmel_pdmic_hw, + .prealloc_buffer_size = ATMEL_PDMIC_PREALLOC_BUF_SIZE, +}; + +/* codec */ +/* Mic Gain = dgain * 2^(-scale) */ +struct mic_gain { + unsigned int dgain; + unsigned int scale; +}; + +/* range from -90 dB to 90 dB */ +static const struct mic_gain mic_gain_table[] = { +{ 1, 15}, { 1, 14}, /* -90, -84 dB */ +{ 3, 15}, { 1, 13}, { 3, 14}, { 1, 12}, /* -81, -78, -75, -72 dB */ +{ 5, 14}, { 13, 15}, /* -70, -68 dB */ +{ 9, 14}, { 21, 15}, { 23, 15}, { 13, 14}, /* -65 ~ -62 dB */ +{ 29, 15}, { 33, 15}, { 37, 15}, { 41, 15}, /* -61 ~ -58 dB */ +{ 23, 14}, { 13, 13}, { 58, 15}, { 65, 15}, /* -57 ~ -54 dB */ +{ 73, 15}, { 41, 14}, { 23, 13}, { 13, 12}, /* -53 ~ -50 dB */ +{ 29, 13}, { 65, 14}, { 73, 14}, { 41, 13}, /* -49 ~ -46 dB */ +{ 23, 12}, { 207, 15}, { 29, 12}, { 65, 13}, /* -45 ~ -42 dB */ +{ 73, 13}, { 41, 12}, { 23, 11}, { 413, 15}, /* -41 ~ -38 dB */ +{ 463, 15}, { 519, 15}, { 583, 15}, { 327, 14}, /* -37 ~ -34 dB */ +{ 367, 14}, { 823, 15}, { 231, 13}, { 1036, 15}, /* -33 ~ -30 dB */ +{ 1163, 15}, { 1305, 15}, { 183, 12}, { 1642, 15}, /* -29 ~ -26 dB */ +{ 1843, 15}, { 2068, 15}, { 145, 11}, { 2603, 15}, /* -25 ~ -22 dB */ +{ 365, 12}, { 3277, 15}, { 3677, 15}, { 4125, 15}, /* -21 ~ -18 dB */ +{ 4629, 15}, { 5193, 15}, { 5827, 15}, { 3269, 14}, /* -17 ~ -14 dB */ +{ 917, 12}, { 8231, 15}, { 9235, 15}, { 5181, 14}, /* -13 ~ -10 dB */ +{11627, 15}, {13045, 15}, {14637, 15}, {16423, 15}, /* -9 ~ -6 dB */ +{18427, 15}, {20675, 15}, { 5799, 13}, {26029, 15}, /* -5 ~ -2 dB */ +{ 7301, 13}, { 1, 0}, {18383, 14}, {10313, 13}, /* -1 ~ 2 dB */ +{23143, 14}, {25967, 14}, {29135, 14}, {16345, 13}, /* 3 ~ 6 dB */ +{ 4585, 11}, {20577, 13}, { 1443, 9}, {25905, 13}, /* 7 ~ 10 dB */ +{14533, 12}, { 8153, 11}, { 2287, 9}, {20529, 12}, /* 11 ~ 14 dB */ +{11517, 11}, { 6461, 10}, {28997, 12}, { 4067, 9}, /* 15 ~ 18 dB */ +{18253, 11}, { 10, 0}, {22979, 11}, {25783, 11}, /* 19 ~ 22 dB */ +{28929, 11}, {32459, 11}, { 9105, 9}, {20431, 10}, /* 23 ~ 26 dB */ +{22925, 10}, {12861, 9}, { 7215, 8}, {16191, 9}, /* 27 ~ 30 dB */ +{ 9083, 8}, {20383, 9}, {11435, 8}, { 6145, 7}, /* 31 ~ 34 dB */ +{ 3599, 6}, {32305, 9}, {18123, 8}, {20335, 8}, /* 35 ~ 38 dB */ +{ 713, 3}, { 100, 0}, { 7181, 6}, { 8057, 6}, /* 39 ~ 42 dB */ +{ 565, 2}, {20287, 7}, {11381, 6}, {25539, 7}, /* 43 ~ 46 dB */ +{ 1791, 3}, { 4019, 4}, { 9019, 5}, {20239, 6}, /* 47 ~ 50 dB */ +{ 5677, 4}, {25479, 6}, { 7147, 4}, { 8019, 4}, /* 51 ~ 54 dB */ +{17995, 5}, {20191, 5}, {11327, 4}, {12709, 4}, /* 55 ~ 58 dB */ +{ 3565, 2}, { 1000, 0}, { 1122, 0}, { 1259, 0}, /* 59 ~ 62 dB */ +{ 2825, 1}, {12679, 3}, { 7113, 2}, { 7981, 2}, /* 63 ~ 66 dB */ +{ 8955, 2}, {20095, 3}, {22547, 3}, {12649, 2}, /* 67 ~ 70 dB */ +{28385, 3}, { 3981, 0}, {17867, 2}, {20047, 2}, /* 71 ~ 74 dB */ +{11247, 1}, {12619, 1}, {14159, 1}, {31773, 2}, /* 75 ~ 78 dB */ +{17825, 1}, {10000, 0}, {11220, 0}, {12589, 0}, /* 79 ~ 82 dB */ +{28251, 1}, {15849, 0}, {17783, 0}, {19953, 0}, /* 83 ~ 86 dB */ +{22387, 0}, {25119, 0}, {28184, 0}, {31623, 0}, /* 87 ~ 90 dB */ +}; + +static const DECLARE_TLV_DB_RANGE(mic_gain_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-9000, 600, 0), + 2, 5, TLV_DB_SCALE_ITEM(-8100, 300, 0), + 6, 7, TLV_DB_SCALE_ITEM(-7000, 200, 0), + 8, ARRAY_SIZE(mic_gain_table)-1, TLV_DB_SCALE_ITEM(-6500, 100, 0), +); + +static int pdmic_get_mic_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int dgain_val, scale_val; + int i; + + dgain_val = (snd_soc_component_read32(component, PDMIC_DSPR1) & PDMIC_DSPR1_DGAIN_MASK) + >> PDMIC_DSPR1_DGAIN_SHIFT; + + scale_val = (snd_soc_component_read32(component, PDMIC_DSPR0) & PDMIC_DSPR0_SCALE_MASK) + >> PDMIC_DSPR0_SCALE_SHIFT; + + for (i = 0; i < ARRAY_SIZE(mic_gain_table); i++) { + if ((mic_gain_table[i].dgain == dgain_val) && + (mic_gain_table[i].scale == scale_val)) + ucontrol->value.integer.value[0] = i; + } + + return 0; +} + +static int pdmic_put_mic_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int max = mc->max; + unsigned int val; + int ret; + + val = ucontrol->value.integer.value[0]; + + if (val > max) + return -EINVAL; + + ret = snd_soc_component_update_bits(component, PDMIC_DSPR1, PDMIC_DSPR1_DGAIN_MASK, + mic_gain_table[val].dgain << PDMIC_DSPR1_DGAIN_SHIFT); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, PDMIC_DSPR0, PDMIC_DSPR0_SCALE_MASK, + mic_gain_table[val].scale << PDMIC_DSPR0_SCALE_SHIFT); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_kcontrol_new atmel_pdmic_snd_controls[] = { +SOC_SINGLE_EXT_TLV("Mic Capture Volume", PDMIC_DSPR1, PDMIC_DSPR1_DGAIN_SHIFT, + ARRAY_SIZE(mic_gain_table)-1, 0, + pdmic_get_mic_volsw, pdmic_put_mic_volsw, mic_gain_tlv), + +SOC_SINGLE("High Pass Filter Switch", PDMIC_DSPR0, + PDMIC_DSPR0_HPFBYP_SHIFT, 1, 1), + +SOC_SINGLE("SINCC Filter Switch", PDMIC_DSPR0, PDMIC_DSPR0_SINBYP_SHIFT, 1, 1), +}; + +static int atmel_pdmic_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(component); + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(card); + + snd_soc_component_update_bits(component, PDMIC_DSPR1, PDMIC_DSPR1_OFFSET_MASK, + (u32)(dd->pdata->mic_offset << PDMIC_DSPR1_OFFSET_SHIFT)); + + return 0; +} + +static struct snd_soc_component_driver soc_component_dev_pdmic = { + .probe = atmel_pdmic_component_probe, + .controls = atmel_pdmic_snd_controls, + .num_controls = ARRAY_SIZE(atmel_pdmic_snd_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* codec dai component */ +#define PDMIC_MR_PRESCAL_MAX_VAL 127 + +static int +atmel_pdmic_codec_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = codec_dai->component; + unsigned int rate_min = substream->runtime->hw.rate_min; + unsigned int rate_max = substream->runtime->hw.rate_max; + int fs = params_rate(params); + int bits = params_width(params); + unsigned long pclk_rate, gclk_rate; + unsigned int f_pdmic; + u32 mr_val, dspr0_val, pclk_prescal, gclk_prescal; + + if (params_channels(params) != 1) { + dev_err(component->dev, + "only supports one channel\n"); + return -EINVAL; + } + + if ((fs < rate_min) || (fs > rate_max)) { + dev_err(component->dev, + "sample rate is %dHz, min rate is %dHz, max rate is %dHz\n", + fs, rate_min, rate_max); + + return -EINVAL; + } + + switch (bits) { + case 16: + dspr0_val = (PDMIC_DSPR0_SIZE_16_BITS + << PDMIC_DSPR0_SIZE_SHIFT); + break; + case 32: + dspr0_val = (PDMIC_DSPR0_SIZE_32_BITS + << PDMIC_DSPR0_SIZE_SHIFT); + break; + default: + return -EINVAL; + } + + if ((fs << 7) > (rate_max << 6)) { + f_pdmic = fs << 6; + dspr0_val |= PDMIC_DSPR0_OSR_64 << PDMIC_DSPR0_OSR_SHIFT; + } else { + f_pdmic = fs << 7; + dspr0_val |= PDMIC_DSPR0_OSR_128 << PDMIC_DSPR0_OSR_SHIFT; + } + + pclk_rate = clk_get_rate(dd->pclk); + gclk_rate = clk_get_rate(dd->gclk); + + /* PRESCAL = SELCK/(2*f_pdmic) - 1*/ + pclk_prescal = (u32)(pclk_rate/(f_pdmic << 1)) - 1; + gclk_prescal = (u32)(gclk_rate/(f_pdmic << 1)) - 1; + + if ((pclk_prescal > PDMIC_MR_PRESCAL_MAX_VAL) || + (gclk_rate/((gclk_prescal + 1) << 1) < + pclk_rate/((pclk_prescal + 1) << 1))) { + mr_val = gclk_prescal << PDMIC_MR_PRESCAL_SHIFT; + mr_val |= PDMIC_MR_CLKS_GCK << PDMIC_MR_CLKS_SHIFT; + } else { + mr_val = pclk_prescal << PDMIC_MR_PRESCAL_SHIFT; + mr_val |= PDMIC_MR_CLKS_PCK << PDMIC_MR_CLKS_SHIFT; + } + + snd_soc_component_update_bits(component, PDMIC_MR, + PDMIC_MR_PRESCAL_MASK | PDMIC_MR_CLKS_MASK, mr_val); + + snd_soc_component_update_bits(component, PDMIC_DSPR0, + PDMIC_DSPR0_OSR_MASK | PDMIC_DSPR0_SIZE_MASK, dspr0_val); + + return 0; +} + +static int atmel_pdmic_codec_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + + snd_soc_component_update_bits(component, PDMIC_CR, PDMIC_CR_ENPDM_MASK, + PDMIC_CR_ENPDM_DIS << PDMIC_CR_ENPDM_SHIFT); + + return 0; +} + +static int atmel_pdmic_codec_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + u32 val; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = PDMIC_CR_ENPDM_EN << PDMIC_CR_ENPDM_SHIFT; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = PDMIC_CR_ENPDM_DIS << PDMIC_CR_ENPDM_SHIFT; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, PDMIC_CR, PDMIC_CR_ENPDM_MASK, val); + + return 0; +} + +static const struct snd_soc_dai_ops atmel_pdmic_codec_dai_ops = { + .hw_params = atmel_pdmic_codec_dai_hw_params, + .prepare = atmel_pdmic_codec_dai_prepare, + .trigger = atmel_pdmic_codec_dai_trigger, +}; + +#define ATMEL_PDMIC_CODEC_DAI_NAME "atmel-pdmic-hifi" + +static struct snd_soc_dai_driver atmel_pdmic_codec_dai = { + .name = ATMEL_PDMIC_CODEC_DAI_NAME, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = ATMEL_PDMIC_FORMATS, + }, + .ops = &atmel_pdmic_codec_dai_ops, +}; + +/* ASoC sound card */ +static int atmel_pdmic_asoc_card_init(struct device *dev, + struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(card); + + dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL); + if (!dai_link) + return -ENOMEM; + + dai_link->name = "PDMIC"; + dai_link->stream_name = "PDMIC PCM"; + dai_link->codec_dai_name = ATMEL_PDMIC_CODEC_DAI_NAME; + dai_link->cpu_dai_name = dev_name(dev); + dai_link->codec_name = dev_name(dev); + dai_link->platform_name = dev_name(dev); + + card->dai_link = dai_link; + card->num_links = 1; + card->name = dd->pdata->card_name; + card->dev = dev; + + return 0; +} + +static void atmel_pdmic_get_sample_rate(struct atmel_pdmic *dd, + unsigned int *rate_min, unsigned int *rate_max) +{ + u32 mic_min_freq = dd->pdata->mic_min_freq; + u32 mic_max_freq = dd->pdata->mic_max_freq; + u32 clk_max_rate = (u32)(clk_get_rate(dd->pclk) >> 1); + u32 clk_min_rate = (u32)(clk_get_rate(dd->gclk) >> 8); + + if (mic_max_freq > clk_max_rate) + mic_max_freq = clk_max_rate; + + if (mic_min_freq < clk_min_rate) + mic_min_freq = clk_min_rate; + + *rate_min = DIV_ROUND_CLOSEST(mic_min_freq, 128); + *rate_max = mic_max_freq >> 6; +} + +/* PDMIC interrupt handler */ +static irqreturn_t atmel_pdmic_interrupt(int irq, void *dev_id) +{ + struct atmel_pdmic *dd = (struct atmel_pdmic *)dev_id; + u32 pdmic_isr; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dd->regmap, PDMIC_ISR, &pdmic_isr); + + if (pdmic_isr & PDMIC_ISR_OVRE) { + regmap_update_bits(dd->regmap, PDMIC_CR, PDMIC_CR_ENPDM_MASK, + PDMIC_CR_ENPDM_DIS << PDMIC_CR_ENPDM_SHIFT); + + snd_pcm_stop_xrun(dd->substream); + + ret = IRQ_HANDLED; + } + + return ret; +} + +/* regmap configuration */ +#define ATMEL_PDMIC_REG_MAX 0x124 +static const struct regmap_config atmel_pdmic_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_PDMIC_REG_MAX, +}; + +static int atmel_pdmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atmel_pdmic *dd; + struct resource *res; + void __iomem *io_base; + const struct atmel_pdmic_pdata *pdata; + struct snd_soc_card *card; + unsigned int rate_min, rate_max; + int ret; + + pdata = atmel_pdmic_dt_init(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; + + dd->pdata = pdata; + dd->dev = dev; + + dd->irq = platform_get_irq(pdev, 0); + if (dd->irq < 0) { + ret = dd->irq; + dev_err(dev, "failed to get irq: %d\n", ret); + return ret; + } + + dd->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dd->pclk)) { + ret = PTR_ERR(dd->pclk); + dev_err(dev, "failed to get peripheral clock: %d\n", ret); + return ret; + } + + dd->gclk = devm_clk_get(dev, "gclk"); + if (IS_ERR(dd->gclk)) { + ret = PTR_ERR(dd->gclk); + dev_err(dev, "failed to get GCK: %d\n", ret); + return ret; + } + + /* The gclk clock frequency must always be three times + * lower than the pclk clock frequency + */ + ret = clk_set_rate(dd->gclk, clk_get_rate(dd->pclk)/3); + if (ret) { + dev_err(dev, "failed to set GCK clock rate: %d\n", ret); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + dd->phy_base = res->start; + + dd->regmap = devm_regmap_init_mmio(dev, io_base, + &atmel_pdmic_regmap_config); + if (IS_ERR(dd->regmap)) { + ret = PTR_ERR(dd->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + ret = devm_request_irq(dev, dd->irq, atmel_pdmic_interrupt, 0, + "PDMIC", (void *)dd); + if (ret < 0) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + dd->irq, ret); + return ret; + } + + /* Get the minimal and maximal sample rate that the microphone supports */ + atmel_pdmic_get_sample_rate(dd, &rate_min, &rate_max); + + /* register cpu dai */ + atmel_pdmic_cpu_dai.capture.rate_min = rate_min; + atmel_pdmic_cpu_dai.capture.rate_max = rate_max; + ret = devm_snd_soc_register_component(dev, + &atmel_pdmic_cpu_dai_component, + &atmel_pdmic_cpu_dai, 1); + if (ret) { + dev_err(dev, "could not register CPU DAI: %d\n", ret); + return ret; + } + + /* register platform */ + ret = devm_snd_dmaengine_pcm_register(dev, + &atmel_pdmic_dmaengine_pcm_config, + 0); + if (ret) { + dev_err(dev, "could not register platform: %d\n", ret); + return ret; + } + + /* register codec and codec dai */ + atmel_pdmic_codec_dai.capture.rate_min = rate_min; + atmel_pdmic_codec_dai.capture.rate_max = rate_max; + ret = devm_snd_soc_register_component(dev, &soc_component_dev_pdmic, + &atmel_pdmic_codec_dai, 1); + if (ret) { + dev_err(dev, "could not register component: %d\n", ret); + return ret; + } + + /* register sound card */ + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) { + ret = -ENOMEM; + goto unregister_codec; + } + + snd_soc_card_set_drvdata(card, dd); + + ret = atmel_pdmic_asoc_card_init(dev, card); + if (ret) { + dev_err(dev, "failed to init sound card: %d\n", ret); + goto unregister_codec; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + dev_err(dev, "failed to register sound card: %d\n", ret); + goto unregister_codec; + } + + return 0; + +unregister_codec: + return ret; +} + +static int atmel_pdmic_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver atmel_pdmic_driver = { + .driver = { + .name = "atmel-pdmic", + .of_match_table = of_match_ptr(atmel_pdmic_of_match), + .pm = &snd_soc_pm_ops, + }, + .probe = atmel_pdmic_probe, + .remove = atmel_pdmic_remove, +}; +module_platform_driver(atmel_pdmic_driver); + +MODULE_DESCRIPTION("Atmel PDMIC driver under ALSA SoC architecture"); +MODULE_AUTHOR("Songjun Wu "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/atmel-pdmic.h b/sound/soc/atmel/atmel-pdmic.h new file mode 100644 index 000000000..1dd351871 --- /dev/null +++ b/sound/soc/atmel/atmel-pdmic.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ATMEL_PDMIC_H_ +#define __ATMEL_PDMIC_H_ + +#include + +#define PDMIC_CR 0x00000000 + +#define PDMIC_CR_SWRST 0x1 +#define PDMIC_CR_SWRST_MASK BIT(0) +#define PDMIC_CR_SWRST_SHIFT (0) + +#define PDMIC_CR_ENPDM_DIS 0x0 +#define PDMIC_CR_ENPDM_EN 0x1 +#define PDMIC_CR_ENPDM_MASK BIT(4) +#define PDMIC_CR_ENPDM_SHIFT (4) + +#define PDMIC_MR 0x00000004 + +#define PDMIC_MR_CLKS_PCK 0x0 +#define PDMIC_MR_CLKS_GCK 0x1 +#define PDMIC_MR_CLKS_MASK BIT(4) +#define PDMIC_MR_CLKS_SHIFT (4) + +#define PDMIC_MR_PRESCAL_MASK GENMASK(14, 8) +#define PDMIC_MR_PRESCAL_SHIFT (8) + +#define PDMIC_CDR 0x00000014 + +#define PDMIC_IER 0x00000018 +#define PDMIC_IER_OVRE BIT(25) + +#define PDMIC_IDR 0x0000001c +#define PDMIC_IDR_OVRE BIT(25) + +#define PDMIC_IMR 0x00000020 + +#define PDMIC_ISR 0x00000024 +#define PDMIC_ISR_OVRE BIT(25) + +#define PDMIC_DSPR0 0x00000058 + +#define PDMIC_DSPR0_HPFBYP_DIS 0x1 +#define PDMIC_DSPR0_HPFBYP_EN 0x0 +#define PDMIC_DSPR0_HPFBYP_MASK BIT(1) +#define PDMIC_DSPR0_HPFBYP_SHIFT (1) + +#define PDMIC_DSPR0_SINBYP_DIS 0x1 +#define PDMIC_DSPR0_SINBYP_EN 0x0 +#define PDMIC_DSPR0_SINBYP_MASK BIT(2) +#define PDMIC_DSPR0_SINBYP_SHIFT (2) + +#define PDMIC_DSPR0_SIZE_16_BITS 0x0 +#define PDMIC_DSPR0_SIZE_32_BITS 0x1 +#define PDMIC_DSPR0_SIZE_MASK BIT(3) +#define PDMIC_DSPR0_SIZE_SHIFT (3) + +#define PDMIC_DSPR0_OSR_128 0x0 +#define PDMIC_DSPR0_OSR_64 0x1 +#define PDMIC_DSPR0_OSR_MASK GENMASK(6, 4) +#define PDMIC_DSPR0_OSR_SHIFT (4) + +#define PDMIC_DSPR0_SCALE_MASK GENMASK(11, 8) +#define PDMIC_DSPR0_SCALE_SHIFT (8) + +#define PDMIC_DSPR0_SHIFT_MASK GENMASK(15, 12) +#define PDMIC_DSPR0_SHIFT_SHIFT (12) + +#define PDMIC_DSPR1 0x0000005c + +#define PDMIC_DSPR1_DGAIN_MASK GENMASK(14, 0) +#define PDMIC_DSPR1_DGAIN_SHIFT (0) + +#define PDMIC_DSPR1_OFFSET_MASK GENMASK(31, 16) +#define PDMIC_DSPR1_OFFSET_SHIFT (16) + +#define PDMIC_WPMR 0x000000e4 + +#define PDMIC_WPSR 0x000000e8 + +#endif diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c new file mode 100644 index 000000000..7272f0022 --- /dev/null +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -0,0 +1,1084 @@ +/* + * atmel_ssc_dai.c -- ALSA SoC ATMEL SSC Audio Layer Platform driver + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Author: Sedji Gaouaou + * ATMEL CORP. + * + * Based on at91-ssc.c by + * Frank Mandarino + * Based on pxa2xx Platform drivers by + * Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "atmel-pcm.h" +#include "atmel_ssc_dai.h" + + +#define NUM_SSC_DEVICES 3 + +/* + * SSC PDC registers required by the PCM DMA engine. + */ +static struct atmel_pdc_regs pdc_tx_reg = { + .xpr = ATMEL_PDC_TPR, + .xcr = ATMEL_PDC_TCR, + .xnpr = ATMEL_PDC_TNPR, + .xncr = ATMEL_PDC_TNCR, +}; + +static struct atmel_pdc_regs pdc_rx_reg = { + .xpr = ATMEL_PDC_RPR, + .xcr = ATMEL_PDC_RCR, + .xnpr = ATMEL_PDC_RNPR, + .xncr = ATMEL_PDC_RNCR, +}; + +/* + * SSC & PDC status bits for transmit and receive. + */ +static struct atmel_ssc_mask ssc_tx_mask = { + .ssc_enable = SSC_BIT(CR_TXEN), + .ssc_disable = SSC_BIT(CR_TXDIS), + .ssc_endx = SSC_BIT(SR_ENDTX), + .ssc_endbuf = SSC_BIT(SR_TXBUFE), + .ssc_error = SSC_BIT(SR_OVRUN), + .pdc_enable = ATMEL_PDC_TXTEN, + .pdc_disable = ATMEL_PDC_TXTDIS, +}; + +static struct atmel_ssc_mask ssc_rx_mask = { + .ssc_enable = SSC_BIT(CR_RXEN), + .ssc_disable = SSC_BIT(CR_RXDIS), + .ssc_endx = SSC_BIT(SR_ENDRX), + .ssc_endbuf = SSC_BIT(SR_RXBUFF), + .ssc_error = SSC_BIT(SR_OVRUN), + .pdc_enable = ATMEL_PDC_RXTEN, + .pdc_disable = ATMEL_PDC_RXTDIS, +}; + + +/* + * DMA parameters. + */ +static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = { + {{ + .name = "SSC0 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC0 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + } }, + {{ + .name = "SSC1 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC1 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + } }, + {{ + .name = "SSC2 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC2 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + } }, +}; + + +static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = { + { + .name = "ssc0", + .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock), + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, + { + .name = "ssc1", + .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock), + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, + { + .name = "ssc2", + .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock), + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, +}; + + +/* + * SSC interrupt handler. Passes PDC interrupts to the DMA + * interrupt handler in the PCM driver. + */ +static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id) +{ + struct atmel_ssc_info *ssc_p = dev_id; + struct atmel_pcm_dma_params *dma_params; + u32 ssc_sr; + u32 ssc_substream_mask; + int i; + + ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR) + & (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR); + + /* + * Loop through the substreams attached to this SSC. If + * a DMA-related interrupt occurred on that substream, call + * the DMA interrupt handler function, if one has been + * registered in the dma_params structure by the PCM driver. + */ + for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) { + dma_params = ssc_p->dma_params[i]; + + if ((dma_params != NULL) && + (dma_params->dma_intr_handler != NULL)) { + ssc_substream_mask = (dma_params->mask->ssc_endx | + dma_params->mask->ssc_endbuf); + if (ssc_sr & ssc_substream_mask) { + dma_params->dma_intr_handler(ssc_sr, + dma_params-> + substream); + } + } + } + + return IRQ_HANDLED; +} + +/* + * When the bit clock is input, limit the maximum rate according to the + * Serial Clock Ratio Considerations section from the SSC documentation: + * + * The Transmitter and the Receiver can be programmed to operate + * with the clock signals provided on either the TK or RK pins. + * This allows the SSC to support many slave-mode data transfers. + * In this case, the maximum clock speed allowed on the RK pin is: + * - Peripheral clock divided by 2 if Receiver Frame Synchro is input + * - Peripheral clock divided by 3 if Receiver Frame Synchro is output + * In addition, the maximum clock speed allowed on the TK pin is: + * - Peripheral clock divided by 6 if Transmit Frame Synchro is input + * - Peripheral clock divided by 2 if Transmit Frame Synchro is output + * + * When the bit clock is output, limit the rate according to the + * SSC divider restrictions. + */ +static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct atmel_ssc_info *ssc_p = rule->private; + struct ssc_device *ssc = ssc_p->ssc; + struct snd_interval *i = hw_param_interval(params, rule->var); + struct snd_interval t; + struct snd_ratnum r = { + .den_min = 1, + .den_max = 4095, + .den_step = 1, + }; + unsigned int num = 0, den = 0; + int frame_size; + int mck_div = 2; + int ret; + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) + return frame_size; + + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFS: + if ((ssc_p->dir_mask & SSC_DIR_MASK_CAPTURE) + && ssc->clk_from_rk_pin) + /* Receiver Frame Synchro (i.e. capture) + * is output (format is _CFS) and the RK pin + * is used for input (format is _CBM_). + */ + mck_div = 3; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + if ((ssc_p->dir_mask & SSC_DIR_MASK_PLAYBACK) + && !ssc->clk_from_rk_pin) + /* Transmit Frame Synchro (i.e. playback) + * is input (format is _CFM) and the TK pin + * is used for input (format _CBM_ but not + * using the RK pin). + */ + mck_div = 6; + break; + } + + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + r.num = ssc_p->mck_rate / mck_div / frame_size; + + ret = snd_interval_ratnum(i, 1, &r, &num, &den); + if (ret >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + break; + + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBM_CFM: + t.min = 8000; + t.max = ssc_p->mck_rate / mck_div / frame_size; + t.openmin = t.openmax = 0; + t.integer = 0; + ret = snd_interval_refine(i, &t); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/*-------------------------------------------------------------------------*\ + * DAI functions +\*-------------------------------------------------------------------------*/ +/* + * Startup. Only that one substream allowed in each direction. + */ +static int atmel_ssc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir, dir_mask; + int ret; + + pr_debug("atmel_ssc_startup: SSC_SR=0x%x\n", + ssc_readl(ssc_p->ssc->regs, SR)); + + /* Enable PMC peripheral clock for this SSC */ + pr_debug("atmel_ssc_dai: Starting clock\n"); + ret = clk_enable(ssc_p->ssc->clk); + if (ret) + return ret; + + ssc_p->mck_rate = clk_get_rate(ssc_p->ssc->clk); + + /* Reset the SSC unless initialized to keep it in a clean state */ + if (!ssc_p->initialized) + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dir = 0; + dir_mask = SSC_DIR_MASK_PLAYBACK; + } else { + dir = 1; + dir_mask = SSC_DIR_MASK_CAPTURE; + } + + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + atmel_ssc_hw_rule_rate, + ssc_p, + SNDRV_PCM_HW_PARAM_FRAME_BITS, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) { + dev_err(dai->dev, "Failed to specify rate rule: %d\n", ret); + return ret; + } + + dma_params = &ssc_dma_params[pdev->id][dir]; + dma_params->ssc = ssc_p->ssc; + dma_params->substream = substream; + + ssc_p->dma_params[dir] = dma_params; + + snd_soc_dai_set_dma_data(dai, substream, dma_params); + + spin_lock_irq(&ssc_p->lock); + if (ssc_p->dir_mask & dir_mask) { + spin_unlock_irq(&ssc_p->lock); + return -EBUSY; + } + ssc_p->dir_mask |= dir_mask; + spin_unlock_irq(&ssc_p->lock); + + return 0; +} + +/* + * Shutdown. Clear DMA parameters and shutdown the SSC if there + * are no other substreams open. + */ +static void atmel_ssc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir, dir_mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + dma_params = ssc_p->dma_params[dir]; + + if (dma_params != NULL) { + dma_params->ssc = NULL; + dma_params->substream = NULL; + ssc_p->dma_params[dir] = NULL; + } + + dir_mask = 1 << dir; + + spin_lock_irq(&ssc_p->lock); + ssc_p->dir_mask &= ~dir_mask; + if (!ssc_p->dir_mask) { + if (ssc_p->initialized) { + free_irq(ssc_p->ssc->irq, ssc_p); + ssc_p->initialized = 0; + } + + /* Reset the SSC */ + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST)); + /* Clear the SSC dividers */ + ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0; + ssc_p->forced_divider = 0; + } + spin_unlock_irq(&ssc_p->lock); + + /* Shutdown the SSC clock. */ + pr_debug("atmel_ssc_dai: Stopping clock\n"); + clk_disable(ssc_p->ssc->clk); +} + + +/* + * Record the DAI format for use in hw_params(). + */ +static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct platform_device *pdev = to_platform_device(cpu_dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + + ssc_p->daifmt = fmt; + return 0; +} + +/* + * Record SSC clock dividers for use in hw_params(). + */ +static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct platform_device *pdev = to_platform_device(cpu_dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + + switch (div_id) { + case ATMEL_SSC_CMR_DIV: + /* + * The same master clock divider is used for both + * transmit and receive, so if a value has already + * been set, it must match this value. + */ + if (ssc_p->dir_mask != + (SSC_DIR_MASK_PLAYBACK | SSC_DIR_MASK_CAPTURE)) + ssc_p->cmr_div = div; + else if (ssc_p->cmr_div == 0) + ssc_p->cmr_div = div; + else + if (div != ssc_p->cmr_div) + return -EBUSY; + ssc_p->forced_divider |= BIT(ATMEL_SSC_CMR_DIV); + break; + + case ATMEL_SSC_TCMR_PERIOD: + ssc_p->tcmr_period = div; + ssc_p->forced_divider |= BIT(ATMEL_SSC_TCMR_PERIOD); + break; + + case ATMEL_SSC_RCMR_PERIOD: + ssc_p->rcmr_period = div; + ssc_p->forced_divider |= BIT(ATMEL_SSC_RCMR_PERIOD); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* Is the cpu-dai master of the frame clock? */ +static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p) +{ + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFS: + return 1; + } + return 0; +} + +/* Is the cpu-dai master of the bit clock? */ +static int atmel_ssc_cbs(struct atmel_ssc_info *ssc_p) +{ + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + return 1; + } + return 0; +} + +/* + * Configure the SSC. + */ +static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + int id = pdev->id; + struct atmel_ssc_info *ssc_p = &ssc_info[id]; + struct ssc_device *ssc = ssc_p->ssc; + struct atmel_pcm_dma_params *dma_params; + int dir, channels, bits; + u32 tfmr, rfmr, tcmr, rcmr; + int ret; + int fslen, fslen_ext; + u32 cmr_div; + u32 tcmr_period; + u32 rcmr_period; + + /* + * Currently, there is only one set of dma params for + * each direction. If more are added, this code will + * have to be changed to select the proper set. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + /* + * If the cpu dai should provide BCLK, but noone has provided the + * divider needed for that to work, fall back to something sensible. + */ + cmr_div = ssc_p->cmr_div; + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_CMR_DIV)) && + atmel_ssc_cbs(ssc_p)) { + int bclk_rate = snd_soc_params_to_bclk(params); + + if (bclk_rate < 0) { + dev_err(dai->dev, "unable to calculate cmr_div: %d\n", + bclk_rate); + return bclk_rate; + } + + cmr_div = DIV_ROUND_CLOSEST(ssc_p->mck_rate, 2 * bclk_rate); + } + + /* + * If the cpu dai should provide LRCLK, but noone has provided the + * dividers needed for that to work, fall back to something sensible. + */ + tcmr_period = ssc_p->tcmr_period; + rcmr_period = ssc_p->rcmr_period; + if (atmel_ssc_cfs(ssc_p)) { + int frame_size = snd_soc_params_to_frame_size(params); + + if (frame_size < 0) { + dev_err(dai->dev, + "unable to calculate tx/rx cmr_period: %d\n", + frame_size); + return frame_size; + } + + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_TCMR_PERIOD))) + tcmr_period = frame_size / 2 - 1; + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_RCMR_PERIOD))) + rcmr_period = frame_size / 2 - 1; + } + + dma_params = ssc_p->dma_params[dir]; + + channels = params_channels(params); + + /* + * Determine sample size in bits and the PDC increment. + */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + bits = 8; + dma_params->pdc_xfer_size = 1; + break; + case SNDRV_PCM_FORMAT_S16_LE: + bits = 16; + dma_params->pdc_xfer_size = 2; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bits = 24; + dma_params->pdc_xfer_size = 4; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bits = 32; + dma_params->pdc_xfer_size = 4; + break; + default: + printk(KERN_WARNING "atmel_ssc_dai: unsupported PCM format"); + return -EINVAL; + } + + /* + * Compute SSC register settings. + */ + switch (ssc_p->daifmt + & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) { + + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + /* + * I2S format, SSC provides BCLK and LRC clocks. + * + * The SSC transmit and receive clocks are generated + * from the MCK divider, and the BCLK signal + * is output on the SSC TK line. + */ + + if (bits > 16 && !ssc->pdata->has_fslen_ext) { + dev_err(dai->dev, + "sample size %d is too large for SSC device\n", + bits); + return -EINVAL; + } + + fslen_ext = (bits - 1) / 16; + fslen = (bits - 1) % 16; + + rcmr = SSC_BF(RCMR_PERIOD, rcmr_period) + | SSC_BF(RCMR_STTDLY, START_DELAY) + | SSC_BF(RCMR_START, SSC_START_FALLING_RF) + | SSC_BF(RCMR_CKI, SSC_CKI_RISING) + | SSC_BF(RCMR_CKO, SSC_CKO_NONE) + | SSC_BF(RCMR_CKS, SSC_CKS_DIV); + + rfmr = SSC_BF(RFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE) + | SSC_BF(RFMR_FSLEN, fslen) + | SSC_BF(RFMR_DATNB, (channels - 1)) + | SSC_BIT(RFMR_MSBF) + | SSC_BF(RFMR_LOOP, 0) + | SSC_BF(RFMR_DATLEN, (bits - 1)); + + tcmr = SSC_BF(TCMR_PERIOD, tcmr_period) + | SSC_BF(TCMR_STTDLY, START_DELAY) + | SSC_BF(TCMR_START, SSC_START_FALLING_RF) + | SSC_BF(TCMR_CKI, SSC_CKI_FALLING) + | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS) + | SSC_BF(TCMR_CKS, SSC_CKS_DIV); + + tfmr = SSC_BF(TFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(TFMR_FSDEN, 0) + | SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE) + | SSC_BF(TFMR_FSLEN, fslen) + | SSC_BF(TFMR_DATNB, (channels - 1)) + | SSC_BIT(TFMR_MSBF) + | SSC_BF(TFMR_DATDEF, 0) + | SSC_BF(TFMR_DATLEN, (bits - 1)); + break; + + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + /* I2S format, CODEC supplies BCLK and LRC clocks. */ + rcmr = SSC_BF(RCMR_PERIOD, 0) + | SSC_BF(RCMR_STTDLY, START_DELAY) + | SSC_BF(RCMR_START, SSC_START_FALLING_RF) + | SSC_BF(RCMR_CKI, SSC_CKI_RISING) + | SSC_BF(RCMR_CKO, SSC_CKO_NONE) + | SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_PIN : SSC_CKS_CLOCK); + + rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(RFMR_FSOS, SSC_FSOS_NONE) + | SSC_BF(RFMR_FSLEN, 0) + | SSC_BF(RFMR_DATNB, (channels - 1)) + | SSC_BIT(RFMR_MSBF) + | SSC_BF(RFMR_LOOP, 0) + | SSC_BF(RFMR_DATLEN, (bits - 1)); + + tcmr = SSC_BF(TCMR_PERIOD, 0) + | SSC_BF(TCMR_STTDLY, START_DELAY) + | SSC_BF(TCMR_START, SSC_START_FALLING_RF) + | SSC_BF(TCMR_CKI, SSC_CKI_FALLING) + | SSC_BF(TCMR_CKO, SSC_CKO_NONE) + | SSC_BF(TCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_CLOCK : SSC_CKS_PIN); + + tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(TFMR_FSDEN, 0) + | SSC_BF(TFMR_FSOS, SSC_FSOS_NONE) + | SSC_BF(TFMR_FSLEN, 0) + | SSC_BF(TFMR_DATNB, (channels - 1)) + | SSC_BIT(TFMR_MSBF) + | SSC_BF(TFMR_DATDEF, 0) + | SSC_BF(TFMR_DATLEN, (bits - 1)); + break; + + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFS: + /* I2S format, CODEC supplies BCLK, SSC supplies LRCLK. */ + if (bits > 16 && !ssc->pdata->has_fslen_ext) { + dev_err(dai->dev, + "sample size %d is too large for SSC device\n", + bits); + return -EINVAL; + } + + fslen_ext = (bits - 1) / 16; + fslen = (bits - 1) % 16; + + rcmr = SSC_BF(RCMR_PERIOD, rcmr_period) + | SSC_BF(RCMR_STTDLY, START_DELAY) + | SSC_BF(RCMR_START, SSC_START_FALLING_RF) + | SSC_BF(RCMR_CKI, SSC_CKI_RISING) + | SSC_BF(RCMR_CKO, SSC_CKO_NONE) + | SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_PIN : SSC_CKS_CLOCK); + + rfmr = SSC_BF(RFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE) + | SSC_BF(RFMR_FSLEN, fslen) + | SSC_BF(RFMR_DATNB, (channels - 1)) + | SSC_BIT(RFMR_MSBF) + | SSC_BF(RFMR_LOOP, 0) + | SSC_BF(RFMR_DATLEN, (bits - 1)); + + tcmr = SSC_BF(TCMR_PERIOD, tcmr_period) + | SSC_BF(TCMR_STTDLY, START_DELAY) + | SSC_BF(TCMR_START, SSC_START_FALLING_RF) + | SSC_BF(TCMR_CKI, SSC_CKI_FALLING) + | SSC_BF(TCMR_CKO, SSC_CKO_NONE) + | SSC_BF(TCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_CLOCK : SSC_CKS_PIN); + + tfmr = SSC_BF(TFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_NEGATIVE) + | SSC_BF(TFMR_FSDEN, 0) + | SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE) + | SSC_BF(TFMR_FSLEN, fslen) + | SSC_BF(TFMR_DATNB, (channels - 1)) + | SSC_BIT(TFMR_MSBF) + | SSC_BF(TFMR_DATDEF, 0) + | SSC_BF(TFMR_DATLEN, (bits - 1)); + break; + + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + /* + * DSP/PCM Mode A format, SSC provides BCLK and LRC clocks. + * + * The SSC transmit and receive clocks are generated from the + * MCK divider, and the BCLK signal is output + * on the SSC TK line. + */ + rcmr = SSC_BF(RCMR_PERIOD, rcmr_period) + | SSC_BF(RCMR_STTDLY, 1) + | SSC_BF(RCMR_START, SSC_START_RISING_RF) + | SSC_BF(RCMR_CKI, SSC_CKI_RISING) + | SSC_BF(RCMR_CKO, SSC_CKO_NONE) + | SSC_BF(RCMR_CKS, SSC_CKS_DIV); + + rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE) + | SSC_BF(RFMR_FSLEN, 0) + | SSC_BF(RFMR_DATNB, (channels - 1)) + | SSC_BIT(RFMR_MSBF) + | SSC_BF(RFMR_LOOP, 0) + | SSC_BF(RFMR_DATLEN, (bits - 1)); + + tcmr = SSC_BF(TCMR_PERIOD, tcmr_period) + | SSC_BF(TCMR_STTDLY, 1) + | SSC_BF(TCMR_START, SSC_START_RISING_RF) + | SSC_BF(TCMR_CKI, SSC_CKI_FALLING) + | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS) + | SSC_BF(TCMR_CKS, SSC_CKS_DIV); + + tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(TFMR_FSDEN, 0) + | SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE) + | SSC_BF(TFMR_FSLEN, 0) + | SSC_BF(TFMR_DATNB, (channels - 1)) + | SSC_BIT(TFMR_MSBF) + | SSC_BF(TFMR_DATDEF, 0) + | SSC_BF(TFMR_DATLEN, (bits - 1)); + break; + + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + /* + * DSP/PCM Mode A format, CODEC supplies BCLK and LRC clocks. + * + * Data is transferred on first BCLK after LRC pulse rising + * edge.If stereo, the right channel data is contiguous with + * the left channel data. + */ + rcmr = SSC_BF(RCMR_PERIOD, 0) + | SSC_BF(RCMR_STTDLY, START_DELAY) + | SSC_BF(RCMR_START, SSC_START_RISING_RF) + | SSC_BF(RCMR_CKI, SSC_CKI_RISING) + | SSC_BF(RCMR_CKO, SSC_CKO_NONE) + | SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_PIN : SSC_CKS_CLOCK); + + rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(RFMR_FSOS, SSC_FSOS_NONE) + | SSC_BF(RFMR_FSLEN, 0) + | SSC_BF(RFMR_DATNB, (channels - 1)) + | SSC_BIT(RFMR_MSBF) + | SSC_BF(RFMR_LOOP, 0) + | SSC_BF(RFMR_DATLEN, (bits - 1)); + + tcmr = SSC_BF(TCMR_PERIOD, 0) + | SSC_BF(TCMR_STTDLY, START_DELAY) + | SSC_BF(TCMR_START, SSC_START_RISING_RF) + | SSC_BF(TCMR_CKI, SSC_CKI_FALLING) + | SSC_BF(TCMR_CKO, SSC_CKO_NONE) + | SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_CLOCK : SSC_CKS_PIN); + + tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(TFMR_FSDEN, 0) + | SSC_BF(TFMR_FSOS, SSC_FSOS_NONE) + | SSC_BF(TFMR_FSLEN, 0) + | SSC_BF(TFMR_DATNB, (channels - 1)) + | SSC_BIT(TFMR_MSBF) + | SSC_BF(TFMR_DATDEF, 0) + | SSC_BF(TFMR_DATLEN, (bits - 1)); + break; + + default: + printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n", + ssc_p->daifmt); + return -EINVAL; + } + pr_debug("atmel_ssc_hw_params: " + "RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n", + rcmr, rfmr, tcmr, tfmr); + + if (!ssc_p->initialized) { + if (!ssc_p->ssc->pdata->use_dma) { + ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0); + + ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0); + } + + ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0, + ssc_p->name, ssc_p); + if (ret < 0) { + printk(KERN_WARNING + "atmel_ssc_dai: request_irq failure\n"); + pr_debug("Atmel_ssc_dai: Stopping clock\n"); + clk_disable(ssc_p->ssc->clk); + return ret; + } + + ssc_p->initialized = 1; + } + + /* set SSC clock mode register */ + ssc_writel(ssc_p->ssc->regs, CMR, cmr_div); + + /* set receive clock mode and format */ + ssc_writel(ssc_p->ssc->regs, RCMR, rcmr); + ssc_writel(ssc_p->ssc->regs, RFMR, rfmr); + + /* set transmit clock mode and format */ + ssc_writel(ssc_p->ssc->regs, TCMR, tcmr); + ssc_writel(ssc_p->ssc->regs, TFMR, tfmr); + + pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n"); + return 0; +} + + +static int atmel_ssc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + dma_params = ssc_p->dma_params[dir]; + + ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable); + ssc_writel(ssc_p->ssc->regs, IDR, dma_params->mask->ssc_error); + + pr_debug("%s enabled SSC_SR=0x%08x\n", + dir ? "receive" : "transmit", + ssc_readl(ssc_p->ssc->regs, SR)); + return 0; +} + +static int atmel_ssc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + dma_params = ssc_p->dma_params[dir]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable); + break; + default: + ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable); + break; + } + + return 0; +} + +#ifdef CONFIG_PM +static int atmel_ssc_suspend(struct snd_soc_dai *cpu_dai) +{ + struct atmel_ssc_info *ssc_p; + struct platform_device *pdev = to_platform_device(cpu_dai->dev); + + if (!cpu_dai->active) + return 0; + + ssc_p = &ssc_info[pdev->id]; + + /* Save the status register before disabling transmit and receive */ + ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR); + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS)); + + /* Save the current interrupt mask, then disable unmasked interrupts */ + ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR); + ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr); + + ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR); + ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR); + ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR); + ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR); + ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR); + + return 0; +} + + + +static int atmel_ssc_resume(struct snd_soc_dai *cpu_dai) +{ + struct atmel_ssc_info *ssc_p; + struct platform_device *pdev = to_platform_device(cpu_dai->dev); + u32 cr; + + if (!cpu_dai->active) + return 0; + + ssc_p = &ssc_info[pdev->id]; + + /* restore SSC register settings */ + ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr); + ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr); + ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr); + ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr); + ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr); + + /* re-enable interrupts */ + ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr); + + /* Re-enable receive and transmit as appropriate */ + cr = 0; + cr |= + (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0; + cr |= + (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0; + ssc_writel(ssc_p->ssc->regs, CR, cr); + + return 0; +} +#else /* CONFIG_PM */ +# define atmel_ssc_suspend NULL +# define atmel_ssc_resume NULL +#endif /* CONFIG_PM */ + +#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .startup = atmel_ssc_startup, + .shutdown = atmel_ssc_shutdown, + .prepare = atmel_ssc_prepare, + .trigger = atmel_ssc_trigger, + .hw_params = atmel_ssc_hw_params, + .set_fmt = atmel_ssc_set_dai_fmt, + .set_clkdiv = atmel_ssc_set_dai_clkdiv, +}; + +static struct snd_soc_dai_driver atmel_ssc_dai = { + .suspend = atmel_ssc_suspend, + .resume = atmel_ssc_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, + .formats = ATMEL_SSC_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, + .formats = ATMEL_SSC_FORMATS,}, + .ops = &atmel_ssc_dai_ops, +}; + +static const struct snd_soc_component_driver atmel_ssc_component = { + .name = "atmel-ssc", +}; + +static int asoc_ssc_init(struct device *dev) +{ + struct ssc_device *ssc = dev_get_drvdata(dev); + int ret; + + ret = snd_soc_register_component(dev, &atmel_ssc_component, + &atmel_ssc_dai, 1); + if (ret) { + dev_err(dev, "Could not register DAI: %d\n", ret); + goto err; + } + + if (ssc->pdata->use_dma) + ret = atmel_pcm_dma_platform_register(dev); + else + ret = atmel_pcm_pdc_platform_register(dev); + + if (ret) { + dev_err(dev, "Could not register PCM: %d\n", ret); + goto err_unregister_dai; + } + + return 0; + +err_unregister_dai: + snd_soc_unregister_component(dev); +err: + return ret; +} + +static void asoc_ssc_exit(struct device *dev) +{ + struct ssc_device *ssc = dev_get_drvdata(dev); + + if (ssc->pdata->use_dma) + atmel_pcm_dma_platform_unregister(dev); + else + atmel_pcm_pdc_platform_unregister(dev); + + snd_soc_unregister_component(dev); +} + +/** + * atmel_ssc_set_audio - Allocate the specified SSC for audio use. + */ +int atmel_ssc_set_audio(int ssc_id) +{ + struct ssc_device *ssc; + int ret; + + /* If we can grab the SSC briefly to parent the DAI device off it */ + ssc = ssc_request(ssc_id); + if (IS_ERR(ssc)) { + pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n", + PTR_ERR(ssc)); + return PTR_ERR(ssc); + } else { + ssc_info[ssc_id].ssc = ssc; + } + + ret = asoc_ssc_init(&ssc->pdev->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(atmel_ssc_set_audio); + +void atmel_ssc_put_audio(int ssc_id) +{ + struct ssc_device *ssc = ssc_info[ssc_id].ssc; + + asoc_ssc_exit(&ssc->pdev->dev); + ssc_free(ssc); +} +EXPORT_SYMBOL_GPL(atmel_ssc_put_audio); + +/* Module information */ +MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com"); +MODULE_DESCRIPTION("ATMEL SSC ASoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel_ssc_dai.h b/sound/soc/atmel/atmel_ssc_dai.h new file mode 100644 index 000000000..75194f582 --- /dev/null +++ b/sound/soc/atmel/atmel_ssc_dai.h @@ -0,0 +1,125 @@ +/* + * atmel_ssc_dai.h - ALSA SSC interface for the Atmel SoC + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Author: Sedji Gaouaou + * ATMEL CORP. + * + * Based on at91-ssc.c by + * Frank Mandarino + * Based on pxa2xx Platform drivers by + * Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ATMEL_SSC_DAI_H +#define _ATMEL_SSC_DAI_H + +#include +#include + +#include "atmel-pcm.h" + +/* SSC system clock ids */ +#define ATMEL_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */ + +/* SSC divider ids */ +#define ATMEL_SSC_CMR_DIV 0 /* MCK divider for BCLK */ +#define ATMEL_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */ +#define ATMEL_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */ +/* + * SSC direction masks + */ +#define SSC_DIR_MASK_UNUSED 0 +#define SSC_DIR_MASK_PLAYBACK 1 +#define SSC_DIR_MASK_CAPTURE 2 + +/* + * SSC register values that Atmel left out of . These + * are expected to be used with SSC_BF + */ +/* START bit field values */ +#define SSC_START_CONTINUOUS 0 +#define SSC_START_TX_RX 1 +#define SSC_START_LOW_RF 2 +#define SSC_START_HIGH_RF 3 +#define SSC_START_FALLING_RF 4 +#define SSC_START_RISING_RF 5 +#define SSC_START_LEVEL_RF 6 +#define SSC_START_EDGE_RF 7 +#define SSS_START_COMPARE_0 8 + +/* CKI bit field values */ +#define SSC_CKI_FALLING 0 +#define SSC_CKI_RISING 1 + +/* CKO bit field values */ +#define SSC_CKO_NONE 0 +#define SSC_CKO_CONTINUOUS 1 +#define SSC_CKO_TRANSFER 2 + +/* CKS bit field values */ +#define SSC_CKS_DIV 0 +#define SSC_CKS_CLOCK 1 +#define SSC_CKS_PIN 2 + +/* FSEDGE bit field values */ +#define SSC_FSEDGE_POSITIVE 0 +#define SSC_FSEDGE_NEGATIVE 1 + +/* FSOS bit field values */ +#define SSC_FSOS_NONE 0 +#define SSC_FSOS_NEGATIVE 1 +#define SSC_FSOS_POSITIVE 2 +#define SSC_FSOS_LOW 3 +#define SSC_FSOS_HIGH 4 +#define SSC_FSOS_TOGGLE 5 + +#define START_DELAY 1 + +struct atmel_ssc_state { + u32 ssc_cmr; + u32 ssc_rcmr; + u32 ssc_rfmr; + u32 ssc_tcmr; + u32 ssc_tfmr; + u32 ssc_sr; + u32 ssc_imr; +}; + + +struct atmel_ssc_info { + char *name; + struct ssc_device *ssc; + spinlock_t lock; /* lock for dir_mask */ + unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */ + unsigned short initialized; /* true if SSC has been initialized */ + unsigned short daifmt; + unsigned short cmr_div; + unsigned short tcmr_period; + unsigned short rcmr_period; + unsigned int forced_divider; + struct atmel_pcm_dma_params *dma_params[2]; + struct atmel_ssc_state ssc_state; + unsigned long mck_rate; +}; + +int atmel_ssc_set_audio(int ssc_id); +void atmel_ssc_put_audio(int ssc_id); + +#endif /* _AT91_SSC_DAI_H */ diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c new file mode 100644 index 000000000..fbc10f61e --- /dev/null +++ b/sound/soc/atmel/atmel_wm8904.c @@ -0,0 +1,197 @@ +/* + * atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec. + * + * Copyright (C) 2012 Atmel + * + * Author: Bo Shen + * + * GPLv2 or later + */ + +#include +#include +#include +#include + +#include + +#include "../codecs/wm8904.h" +#include "atmel_ssc_dai.h" + +static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, + 32768, params_rate(params) * 256); + if (ret < 0) { + pr_err("%s - failed to set wm8904 codec PLL.", __func__); + return ret; + } + + /* + * As here wm8904 use FLL output as its system clock + * so calling set_sysclk won't care freq parameter + * then we pass 0 + */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("%s -failed to set wm8904 SYSCLK\n", __func__); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops atmel_asoc_wm8904_ops = { + .hw_params = atmel_asoc_wm8904_hw_params, +}; + +static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { + .name = "WM8904", + .stream_name = "WM8904 PCM", + .codec_dai_name = "wm8904-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &atmel_asoc_wm8904_ops, +}; + +static struct snd_soc_card atmel_asoc_wm8904_card = { + .name = "atmel_asoc_wm8904", + .owner = THIS_MODULE, + .dai_link = &atmel_asoc_wm8904_dailink, + .num_links = 1, + .dapm_widgets = atmel_asoc_wm8904_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets), + .fully_routed = true, +}; + +static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int ret; + + if (!np) { + dev_err(&pdev->dev, "only device tree supported\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { + dev_err(&pdev->dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } + dailink->cpu_of_node = cpu_np; + dailink->platform_of_node = cpu_np; + of_node_put(cpu_np); + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } + dailink->codec_of_node = codec_np; + of_node_put(codec_np); + + return 0; +} + +static int atmel_asoc_wm8904_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int id, ret; + + card->dev = &pdev->dev; + ret = atmel_asoc_wm8904_dt_init(pdev); + if (ret) { + dev_err(&pdev->dev, "failed to init dt info\n"); + return ret; + } + + id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc"); + ret = atmel_ssc_set_audio(id); + if (ret != 0) { + dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id); + return ret; + } + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed\n"); + goto err_set_audio; + } + + return 0; + +err_set_audio: + atmel_ssc_put_audio(id); + return ret; +} + +static int atmel_asoc_wm8904_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int id; + + id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc"); + + snd_soc_unregister_card(card); + atmel_ssc_put_audio(id); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = { + { .compatible = "atmel,asoc-wm8904", }, + { } +}; +MODULE_DEVICE_TABLE(of, atmel_asoc_wm8904_dt_ids); +#endif + +static struct platform_driver atmel_asoc_wm8904_driver = { + .driver = { + .name = "atmel-wm8904-audio", + .of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids), + .pm = &snd_soc_pm_ops, + }, + .probe = atmel_asoc_wm8904_probe, + .remove = atmel_asoc_wm8904_remove, +}; + +module_platform_driver(atmel_asoc_wm8904_driver); + +/* Module information */ +MODULE_AUTHOR("Bo Shen "); +MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c new file mode 100644 index 000000000..06d32257d --- /dev/null +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -0,0 +1,226 @@ +/* + * sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based + * ATMEL AT91SAM9G20ek board. + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Authors: Sedji Gaouaou + * + * Based on ati_b1_wm8731.c by: + * Frank Mandarino + * Copyright 2006 Endrelia Technologies Inc. + * Based on corgi.c by: + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "../codecs/wm8731.h" +#include "atmel-pcm.h" +#include "atmel_ssc_dai.h" + +#define MCLK_RATE 12000000 + +/* + * As shipped the board does not have inputs. However, it is relatively + * straightforward to modify the board to hook them up so support is left + * in the driver. + */ +#undef ENABLE_MIC_INPUT + +static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + /* speaker connected to LHPOUT/RHPOUT */ + {"Ext Spk", NULL, "LHPOUT"}, + {"Ext Spk", NULL, "RHPOUT"}, + + /* mic is connected to Mic Jack, with WM8731 Mic Bias */ + {"MICIN", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Int Mic"}, +}; + +/* + * Logic for a wm8731 as connected on a at91sam9g20ek board. + */ +static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct device *dev = rtd->dev; + int ret; + + dev_dbg(dev, "%s called\n", __func__); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK, + MCLK_RATE, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret); + return ret; + } + +#ifndef ENABLE_MIC_INPUT + snd_soc_dapm_nc_pin(&rtd->card->dapm, "Int Mic"); +#endif + + return 0; +} + +static struct snd_soc_dai_link at91sam9g20ek_dai = { + .name = "WM8731", + .stream_name = "WM8731 PCM", + .cpu_dai_name = "at91rm9200_ssc.0", + .codec_dai_name = "wm8731-hifi", + .init = at91sam9g20ek_wm8731_init, + .platform_name = "at91rm9200_ssc.0", + .codec_name = "wm8731.0-001b", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +static struct snd_soc_card snd_soc_at91sam9g20ek = { + .name = "AT91SAMG20-EK", + .owner = THIS_MODULE, + .dai_link = &at91sam9g20ek_dai, + .num_links = 1, + + .dapm_widgets = at91sam9g20ek_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .fully_routed = true, +}; + +static int at91sam9g20ek_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &snd_soc_at91sam9g20ek; + int ret; + + if (!np) { + return -ENODEV; + } + + ret = atmel_ssc_set_audio(0); + if (ret) { + dev_err(&pdev->dev, "ssc channel is not valid\n"); + return -EINVAL; + } + + card->dev = &pdev->dev; + + /* Parse device node info */ + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, + "atmel,audio-routing"); + if (ret) + goto err; + + /* Parse codec info */ + at91sam9g20ek_dai.codec_name = NULL; + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "codec info missing\n"); + return -EINVAL; + } + at91sam9g20ek_dai.codec_of_node = codec_np; + + /* Parse dai and platform info */ + at91sam9g20ek_dai.cpu_dai_name = NULL; + at91sam9g20ek_dai.platform_name = NULL; + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "dai and pcm info missing\n"); + of_node_put(codec_np); + return -EINVAL; + } + at91sam9g20ek_dai.cpu_of_node = cpu_np; + at91sam9g20ek_dai.platform_of_node = cpu_np; + + of_node_put(codec_np); + of_node_put(cpu_np); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed\n"); + } + + return ret; + +err: + atmel_ssc_put_audio(0); + return ret; +} + +static int at91sam9g20ek_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + atmel_ssc_put_audio(0); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = { + { .compatible = "atmel,at91sam9g20ek-wm8731-audio", }, + { } +}; +MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids); +#endif + +static struct platform_driver at91sam9g20ek_audio_driver = { + .driver = { + .name = "at91sam9g20ek-audio", + .of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids), + }, + .probe = at91sam9g20ek_audio_probe, + .remove = at91sam9g20ek_audio_remove, +}; + +module_platform_driver(at91sam9g20ek_audio_driver); + +/* Module information */ +MODULE_AUTHOR("Sedji Gaouaou "); +MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731"); +MODULE_ALIAS("platform:at91sam9g20ek-audio"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/sam9x5_wm8731.c b/sound/soc/atmel/sam9x5_wm8731.c new file mode 100644 index 000000000..e6c303ab8 --- /dev/null +++ b/sound/soc/atmel/sam9x5_wm8731.c @@ -0,0 +1,205 @@ +/* + * sam9x5_wm8731 -- SoC audio for AT91SAM9X5-based boards + * that are using WM8731 as codec. + * + * Copyright (C) 2011 Atmel, + * Nicolas Ferre + * + * Copyright (C) 2013 Paratronic, + * Richard Genoud + * + * Based on sam9g20_wm8731.c by: + * Sedji Gaouaou + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm8731.h" +#include "atmel_ssc_dai.h" + + +#define MCLK_RATE 12288000 + +#define DRV_NAME "sam9x5-snd-wm8731" + +struct sam9x5_drvdata { + int ssc_id; +}; + +/* + * Logic for a wm8731 as connected on a at91sam9x5ek based board. + */ +static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct device *dev = rtd->dev; + int ret; + + dev_dbg(dev, "%s called\n", __func__); + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, + MCLK_RATE, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +/* + * Audio paths on at91sam9x5ek board: + * + * |A| ------------> | | ---R----> Headphone Jack + * |T| <----\ | WM | ---L--/ + * |9| ---> CLK <--> | 8731 | <--R----- Line In Jack + * |1| <------------ | | <--L--/ + */ +static const struct snd_soc_dapm_widget sam9x5_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int sam9x5_wm8731_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card; + struct snd_soc_dai_link *dai; + struct sam9x5_drvdata *priv; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No device node supplied\n"); + return -EINVAL; + } + + card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL); + if (!dai || !card || !priv) { + ret = -ENOMEM; + goto out; + } + + snd_soc_card_set_drvdata(card, priv); + + card->dev = &pdev->dev; + card->owner = THIS_MODULE; + card->dai_link = dai; + card->num_links = 1; + card->dapm_widgets = sam9x5_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sam9x5_dapm_widgets); + dai->name = "WM8731"; + dai->stream_name = "WM8731 PCM"; + dai->codec_dai_name = "wm8731-hifi"; + dai->init = sam9x5_wm8731_init; + dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM; + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { + dev_err(&pdev->dev, "atmel,model node missing\n"); + goto out; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { + dev_err(&pdev->dev, "atmel,audio-routing node missing\n"); + goto out; + } + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "atmel,audio-codec node missing\n"); + ret = -EINVAL; + goto out; + } + + dai->codec_of_node = codec_np; + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "atmel,ssc-controller node missing\n"); + ret = -EINVAL; + goto out; + } + dai->cpu_of_node = cpu_np; + dai->platform_of_node = cpu_np; + + priv->ssc_id = of_alias_get_id(cpu_np, "ssc"); + + ret = atmel_ssc_set_audio(priv->ssc_id); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to set SSC %d for audio: %d\n", + ret, priv->ssc_id); + goto out; + } + + of_node_put(codec_np); + of_node_put(cpu_np); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "Platform device allocation failed\n"); + goto out_put_audio; + } + + dev_dbg(&pdev->dev, "%s ok\n", __func__); + + return ret; + +out_put_audio: + atmel_ssc_put_audio(priv->ssc_id); +out: + return ret; +} + +static int sam9x5_wm8731_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sam9x5_drvdata *priv = card->drvdata; + + snd_soc_unregister_card(card); + atmel_ssc_put_audio(priv->ssc_id); + + return 0; +} + +static const struct of_device_id sam9x5_wm8731_of_match[] = { + { .compatible = "atmel,sam9x5-wm8731-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sam9x5_wm8731_of_match); + +static struct platform_driver sam9x5_wm8731_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(sam9x5_wm8731_of_match), + }, + .probe = sam9x5_wm8731_driver_probe, + .remove = sam9x5_wm8731_driver_remove, +}; +module_platform_driver(sam9x5_wm8731_driver); + +/* Module information */ +MODULE_AUTHOR("Nicolas Ferre "); +MODULE_AUTHOR("Richard Genoud "); +MODULE_DESCRIPTION("ALSA SoC machine driver for AT91SAM9x5 - WM8731"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/atmel/tse850-pcm5142.c b/sound/soc/atmel/tse850-pcm5142.c new file mode 100644 index 000000000..3a1393283 --- /dev/null +++ b/sound/soc/atmel/tse850-pcm5142.c @@ -0,0 +1,455 @@ +/* + * TSE-850 audio - ASoC driver for the Axentia TSE-850 with a PCM5142 codec + * + * Copyright (C) 2016 Axentia Technologies AB + * + * Author: Peter Rosin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * loop1 relays + * IN1 +---o +------------+ o---+ OUT1 + * \ / + * + + + * | / | + * +--o +--. | + * | add | | + * | V | + * | .---. | + * DAC +----------->|Sum|---+ + * | '---' | + * | | + * + + + * + * IN2 +---o--+------------+--o---+ OUT2 + * loop2 relays + * + * The 'loop1' gpio pin controlls two relays, which are either in loop + * position, meaning that input and output are directly connected, or + * they are in mixer position, meaning that the signal is passed through + * the 'Sum' mixer. Similarly for 'loop2'. + * + * In the above, the 'loop1' relays are inactive, thus feeding IN1 to the + * mixer (if 'add' is active) and feeding the mixer output to OUT1. The + * 'loop2' relays are active, short-cutting the TSE-850 from channel 2. + * IN1, IN2, OUT1 and OUT2 are TSE-850 connectors and DAC is the PCB name + * of the (filtered) output from the PCM5142 codec. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct tse850_priv { + struct gpio_desc *add; + struct gpio_desc *loop1; + struct gpio_desc *loop2; + + struct regulator *ana; + + int add_cache; + int loop1_cache; + int loop2_cache; +}; + +static int tse850_get_mux1(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->loop1_cache; + + return 0; +} + +static int tse850_put_mux1(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val >= e->items) + return -EINVAL; + + gpiod_set_value_cansleep(tse850->loop1, val); + tse850->loop1_cache = val; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +static int tse850_get_mux2(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->loop2_cache; + + return 0; +} + +static int tse850_put_mux2(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val >= e->items) + return -EINVAL; + + gpiod_set_value_cansleep(tse850->loop2, val); + tse850->loop2_cache = val; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +int tse850_get_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->add_cache; + + return 0; +} + +int tse850_put_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + int connect = !!ucontrol->value.integer.value[0]; + + if (tse850->add_cache == connect) + return 0; + + /* + * Hmmm, this gpiod_set_value_cansleep call should probably happen + * inside snd_soc_dapm_mixer_update_power in the loop. + */ + gpiod_set_value_cansleep(tse850->add, connect); + tse850->add_cache = connect; + + snd_soc_dapm_mixer_update_power(dapm, kctrl, connect, NULL); + return 1; +} + +int tse850_get_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + int ret; + + ret = regulator_get_voltage(tse850->ana); + if (ret < 0) + return ret; + + /* + * Map regulator output values like so: + * -11.5V to "Low" (enum 0) + * 11.5V-12.5V to "12V" (enum 1) + * 12.5V-13.5V to "13V" (enum 2) + * ... + * 18.5V-19.5V to "19V" (enum 8) + * 19.5V- to "20V" (enum 9) + */ + if (ret < 11000000) + ret = 11000000; + else if (ret > 20000000) + ret = 20000000; + ret -= 11000000; + ret = (ret + 500000) / 1000000; + + ucontrol->value.enumerated.item[0] = ret; + + return 0; +} + +int tse850_put_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int uV = ucontrol->value.enumerated.item[0]; + int ret; + + if (uV >= e->items) + return -EINVAL; + + /* + * Map enum zero (Low) to 2 volts on the regulator, do this since + * the ana regulator is supplied by the system 12V voltage and + * requesting anything below the system voltage causes the system + * voltage to be passed through the regulator. Also, the ana + * regulator induces noise when requesting voltages near the + * system voltage. So, by mapping Low to 2V, that noise is + * eliminated when all that is needed is 12V (the system voltage). + */ + if (uV) + uV = 11000000 + (1000000 * uV); + else + uV = 2000000; + + ret = regulator_set_voltage(tse850->ana, uV, uV); + if (ret < 0) + return ret; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +static const char * const mux_text[] = { "Mixer", "Loop" }; + +static const struct soc_enum mux_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(mux_text), mux_text); + +static const struct snd_kcontrol_new mux1 = + SOC_DAPM_ENUM_EXT("MUX1", mux_enum, tse850_get_mux1, tse850_put_mux1); + +static const struct snd_kcontrol_new mux2 = + SOC_DAPM_ENUM_EXT("MUX2", mux_enum, tse850_get_mux2, tse850_put_mux2); + +#define TSE850_DAPM_SINGLE_EXT(xname, reg, shift, max, invert, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = xget, \ + .put = xput, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) } + +static const struct snd_kcontrol_new mix[] = { + TSE850_DAPM_SINGLE_EXT("IN Switch", SND_SOC_NOPM, 0, 1, 0, + tse850_get_mix, tse850_put_mix), +}; + +static const char * const ana_text[] = { + "Low", "12V", "13V", "14V", "15V", "16V", "17V", "18V", "19V", "20V" +}; + +static const struct soc_enum ana_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(ana_text), ana_text); + +static const struct snd_kcontrol_new out = + SOC_DAPM_ENUM_EXT("ANA", ana_enum, tse850_get_ana, tse850_put_ana); + +static const struct snd_soc_dapm_widget tse850_dapm_widgets[] = { + SND_SOC_DAPM_LINE("OUT1", NULL), + SND_SOC_DAPM_LINE("OUT2", NULL), + SND_SOC_DAPM_LINE("IN1", NULL), + SND_SOC_DAPM_LINE("IN2", NULL), + SND_SOC_DAPM_INPUT("DAC"), + SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + SOC_MIXER_ARRAY("MIX", SND_SOC_NOPM, 0, 0, mix), + SND_SOC_DAPM_MUX("MUX1", SND_SOC_NOPM, 0, 0, &mux1), + SND_SOC_DAPM_MUX("MUX2", SND_SOC_NOPM, 0, 0, &mux2), + SND_SOC_DAPM_OUT_DRV("OUT", SND_SOC_NOPM, 0, 0, &out, 1), +}; + +/* + * These connections are not entirely correct, since both IN1 and IN2 + * are always fed to MIX (if the "IN switch" is set so), i.e. without + * regard to the loop1 and loop2 relays that according to this only + * control MUX1 and MUX2 but in fact also control how the input signals + * are routed. + * But, 1) I don't know how to do it right, and 2) it doesn't seem to + * matter in practice since nothing is powered in those sections anyway. + */ +static const struct snd_soc_dapm_route tse850_intercon[] = { + { "OUT1", NULL, "MUX1" }, + { "OUT2", NULL, "MUX2" }, + + { "MUX1", "Loop", "IN1" }, + { "MUX1", "Mixer", "OUT" }, + + { "MUX2", "Loop", "IN2" }, + { "MUX2", "Mixer", "OUT" }, + + { "OUT", NULL, "MIX" }, + + { "MIX", NULL, "DAC" }, + { "MIX", "IN Switch", "IN1" }, + { "MIX", "IN Switch", "IN2" }, + + /* connect board input to the codec left channel output pin */ + { "DAC", NULL, "OUTL" }, +}; + +static struct snd_soc_dai_link tse850_dailink = { + .name = "TSE-850", + .stream_name = "TSE-850-PCM", + .codec_dai_name = "pcm512x-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFS, +}; + +static struct snd_soc_card tse850_card = { + .name = "TSE-850-ASoC", + .owner = THIS_MODULE, + .dai_link = &tse850_dailink, + .num_links = 1, + .dapm_widgets = tse850_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tse850_dapm_widgets), + .dapm_routes = tse850_intercon, + .num_dapm_routes = ARRAY_SIZE(tse850_intercon), + .fully_routed = true, +}; + +static int tse850_dt_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_dai_link *dailink = &tse850_dailink; + + if (!np) { + dev_err(&pdev->dev, "only device tree supported\n"); + return -EINVAL; + } + + cpu_np = of_parse_phandle(np, "axentia,cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "failed to get cpu dai\n"); + return -EINVAL; + } + dailink->cpu_of_node = cpu_np; + dailink->platform_of_node = cpu_np; + of_node_put(cpu_np); + + codec_np = of_parse_phandle(np, "axentia,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "failed to get codec info\n"); + return -EINVAL; + } + dailink->codec_of_node = codec_np; + of_node_put(codec_np); + + return 0; +} + +static int tse850_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &tse850_card; + struct device *dev = card->dev = &pdev->dev; + struct tse850_priv *tse850; + int ret; + + tse850 = devm_kzalloc(dev, sizeof(*tse850), GFP_KERNEL); + if (!tse850) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, tse850); + + ret = tse850_dt_init(pdev); + if (ret) { + dev_err(dev, "failed to init dt info\n"); + return ret; + } + + tse850->add = devm_gpiod_get(dev, "axentia,add", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->add)) { + if (PTR_ERR(tse850->add) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'add' gpio\n"); + return PTR_ERR(tse850->add); + } + tse850->add_cache = 1; + + tse850->loop1 = devm_gpiod_get(dev, "axentia,loop1", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->loop1)) { + if (PTR_ERR(tse850->loop1) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'loop1' gpio\n"); + return PTR_ERR(tse850->loop1); + } + tse850->loop1_cache = 1; + + tse850->loop2 = devm_gpiod_get(dev, "axentia,loop2", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->loop2)) { + if (PTR_ERR(tse850->loop2) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'loop2' gpio\n"); + return PTR_ERR(tse850->loop2); + } + tse850->loop2_cache = 1; + + tse850->ana = devm_regulator_get(dev, "axentia,ana"); + if (IS_ERR(tse850->ana)) { + if (PTR_ERR(tse850->ana) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'ana' regulator\n"); + return PTR_ERR(tse850->ana); + } + + ret = regulator_enable(tse850->ana); + if (ret < 0) { + dev_err(dev, "failed to enable the 'ana' regulator\n"); + return ret; + } + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(dev, "snd_soc_register_card failed\n"); + goto err_disable_ana; + } + + return 0; + +err_disable_ana: + regulator_disable(tse850->ana); + return ret; +} + +static int tse850_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + regulator_disable(tse850->ana); + + return 0; +} + +static const struct of_device_id tse850_dt_ids[] = { + { .compatible = "axentia,tse850-pcm5142", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tse850_dt_ids); + +static struct platform_driver tse850_driver = { + .driver = { + .name = "axentia-tse850-pcm5142", + .of_match_table = of_match_ptr(tse850_dt_ids), + }, + .probe = tse850_probe, + .remove = tse850_remove, +}; + +module_platform_driver(tse850_driver); + +/* Module information */ +MODULE_AUTHOR("Peter Rosin "); +MODULE_DESCRIPTION("ALSA SoC driver for TSE-850 with PCM5142 codec"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig new file mode 100644 index 000000000..a56104040 --- /dev/null +++ b/sound/soc/au1x/Kconfig @@ -0,0 +1,64 @@ +## +## Au1200/Au1550/Au1300 PSC + DBDMA +## +config SND_SOC_AU1XPSC + tristate "SoC Audio for Au12xx/Au13xx/Au1550" + depends on MIPS_ALCHEMY + help + This option enables support for the Programmable Serial + Controllers in AC97 and I2S mode, and the Descriptor-Based DMA + Controller (DBDMA) as found on the Au12xx/Au13xx/Au1550 SoC. + +config SND_SOC_AU1XPSC_I2S + tristate + +config SND_SOC_AU1XPSC_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + +## +## Au1000/1500/1100 DMA + AC97C/I2SC +## +config SND_SOC_AU1XAUDIO + tristate "SoC Audio for Au1000/Au1500/Au1100" + depends on MIPS_ALCHEMY + help + This is a driver set for the AC97 unit and the + old DMA controller as found on the Au1000/Au1500/Au1100 chips. + +config SND_SOC_AU1XAC97C + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + +config SND_SOC_AU1XI2SC + tristate + + +## +## Boards +## +config SND_SOC_DB1000 + tristate "DB1000 Audio support" + depends on SND_SOC_AU1XAUDIO + select SND_SOC_AU1XAC97C + select SND_SOC_AC97_CODEC + help + Select this option to enable AC97 audio on the early DB1x00 series + of boards (DB1000/DB1500/DB1100). + +config SND_SOC_DB1200 + tristate "DB1200/DB1300/DB1550 Audio support" + depends on SND_SOC_AU1XPSC + select SND_SOC_AU1XPSC_AC97 + select SND_SOC_AC97_CODEC + select SND_SOC_WM9712 + select SND_SOC_AU1XPSC_I2S + select SND_SOC_WM8731 + help + Select this option to enable audio (AC97 and I2S) on the + Alchemy/AMD/RMI/NetLogic Db1200, Db1550 and Db1300 evaluation boards. + If you need Db1300 touchscreen support, you definitely want to say Y. diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile new file mode 100644 index 000000000..33183d7fe --- /dev/null +++ b/sound/soc/au1x/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# Au1200/Au1550 PSC audio +snd-soc-au1xpsc-dbdma-objs := dbdma2.o +snd-soc-au1xpsc-i2s-objs := psc-i2s.o +snd-soc-au1xpsc-ac97-objs := psc-ac97.o + +# Au1000/1500/1100 Audio units +snd-soc-au1x-dma-objs := dma.o +snd-soc-au1x-ac97c-objs := ac97c.o +snd-soc-au1x-i2sc-objs := i2sc.o + +obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o +obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o +obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o +obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o +obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o +obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o + +# Boards +snd-soc-db1000-objs := db1000.o +snd-soc-db1200-objs := db1200.o + +obj-$(CONFIG_SND_SOC_DB1000) += snd-soc-db1000.o +obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c new file mode 100644 index 000000000..66d6c52e7 --- /dev/null +++ b/sound/soc/au1x/ac97c.c @@ -0,0 +1,347 @@ +/* + * Au1000/Au1500/Au1100 AC97C controller driver for ASoC + * + * (c) 2011 Manuel Lauss + * + * based on the old ALSA driver originally written by + * Charles Eidsness + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "psc.h" + +/* register offsets and bits */ +#define AC97_CONFIG 0x00 +#define AC97_STATUS 0x04 +#define AC97_DATA 0x08 +#define AC97_CMDRESP 0x0c +#define AC97_ENABLE 0x10 + +#define CFG_RC(x) (((x) & 0x3ff) << 13) /* valid rx slots mask */ +#define CFG_XS(x) (((x) & 0x3ff) << 3) /* valid tx slots mask */ +#define CFG_SG (1 << 2) /* sync gate */ +#define CFG_SN (1 << 1) /* sync control */ +#define CFG_RS (1 << 0) /* acrst# control */ +#define STAT_XU (1 << 11) /* tx underflow */ +#define STAT_XO (1 << 10) /* tx overflow */ +#define STAT_RU (1 << 9) /* rx underflow */ +#define STAT_RO (1 << 8) /* rx overflow */ +#define STAT_RD (1 << 7) /* codec ready */ +#define STAT_CP (1 << 6) /* command pending */ +#define STAT_TE (1 << 4) /* tx fifo empty */ +#define STAT_TF (1 << 3) /* tx fifo full */ +#define STAT_RE (1 << 1) /* rx fifo empty */ +#define STAT_RF (1 << 0) /* rx fifo full */ +#define CMD_SET_DATA(x) (((x) & 0xffff) << 16) +#define CMD_GET_DATA(x) ((x) & 0xffff) +#define CMD_READ (1 << 7) +#define CMD_WRITE (0 << 7) +#define CMD_IDX(x) ((x) & 0x7f) +#define EN_D (1 << 1) /* DISable bit */ +#define EN_CE (1 << 0) /* clock enable bit */ + +/* how often to retry failed codec register reads/writes */ +#define AC97_RW_RETRIES 5 + +#define AC97_RATES \ + SNDRV_PCM_RATE_CONTINUOUS + +#define AC97_FMTS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE) + +/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only + * once AC97C on early Alchemy chips. The newer ones aren't so lucky. + */ +static struct au1xpsc_audio_data *ac97c_workdata; +#define ac97_to_ctx(x) ac97c_workdata + +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg) +{ + return __raw_readl(ctx->mmio + reg); +} + +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v) +{ + __raw_writel(v, ctx->mmio + reg); + wmb(); +} + +static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97, + unsigned short r) +{ + struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); + unsigned int tmo, retry; + unsigned long data; + + data = ~0; + retry = AC97_RW_RETRIES; + do { + mutex_lock(&ctx->lock); + + tmo = 6; + while ((RD(ctx, AC97_STATUS) & STAT_CP) && --tmo) + udelay(21); /* wait an ac97 frame time */ + if (!tmo) { + pr_debug("ac97rd timeout #1\n"); + goto next; + } + + WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ); + + /* stupid errata: data is only valid for 21us, so + * poll, Forrest, poll... + */ + tmo = 0x10000; + while ((RD(ctx, AC97_STATUS) & STAT_CP) && --tmo) + asm volatile ("nop"); + data = RD(ctx, AC97_CMDRESP); + + if (!tmo) + pr_debug("ac97rd timeout #2\n"); + +next: + mutex_unlock(&ctx->lock); + } while (--retry && !tmo); + + pr_debug("AC97RD %04x %04lx %d\n", r, data, retry); + + return retry ? data & 0xffff : 0xffff; +} + +static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r, + unsigned short v) +{ + struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); + unsigned int tmo, retry; + + retry = AC97_RW_RETRIES; + do { + mutex_lock(&ctx->lock); + + for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--) + udelay(21); + if (!tmo) { + pr_debug("ac97wr timeout #1\n"); + goto next; + } + + WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v)); + + for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--) + udelay(21); + if (!tmo) + pr_debug("ac97wr timeout #2\n"); +next: + mutex_unlock(&ctx->lock); + } while (--retry && !tmo); + + pr_debug("AC97WR %04x %04x %d\n", r, v, retry); +} + +static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); + + WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN); + msleep(20); + WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG); + WR(ctx, AC97_CONFIG, ctx->cfg); +} + +static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); + int i; + + WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS); + msleep(500); + WR(ctx, AC97_CONFIG, ctx->cfg); + + /* wait for codec ready */ + i = 50; + while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i) + msleep(20); + if (!i) + printk(KERN_ERR "ac97c: codec not ready after cold reset\n"); +} + +/* AC97 controller operations */ +static struct snd_ac97_bus_ops ac97c_bus_ops = { + .read = au1xac97c_ac97_read, + .write = au1xac97c_ac97_write, + .reset = au1xac97c_ac97_cold_reset, + .warm_reset = au1xac97c_ac97_warm_reset, +}; + +static int alchemy_ac97c_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]); + return 0; +} + +static const struct snd_soc_dai_ops alchemy_ac97c_ops = { + .startup = alchemy_ac97c_startup, +}; + +static int au1xac97c_dai_probe(struct snd_soc_dai *dai) +{ + return ac97c_workdata ? 0 : -ENODEV; +} + +static struct snd_soc_dai_driver au1xac97c_dai_driver = { + .name = "alchemy-ac97c", + .bus_control = true, + .probe = au1xac97c_dai_probe, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &alchemy_ac97c_ops, +}; + +static const struct snd_soc_component_driver au1xac97c_component = { + .name = "au1xac97c", +}; + +static int au1xac97c_drvprobe(struct platform_device *pdev) +{ + int ret; + struct resource *iores, *dmares; + struct au1xpsc_audio_data *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_init(&ctx->lock); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iores) + return -ENODEV; + + if (!devm_request_mem_region(&pdev->dev, iores->start, + resource_size(iores), + pdev->name)) + return -EBUSY; + + ctx->mmio = devm_ioremap_nocache(&pdev->dev, iores->start, + resource_size(iores)); + if (!ctx->mmio) + return -EBUSY; + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmares) + return -EBUSY; + ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start; + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!dmares) + return -EBUSY; + ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start; + + /* switch it on */ + WR(ctx, AC97_ENABLE, EN_D | EN_CE); + WR(ctx, AC97_ENABLE, EN_CE); + + ctx->cfg = CFG_RC(3) | CFG_XS(3); + WR(ctx, AC97_CONFIG, ctx->cfg); + + platform_set_drvdata(pdev, ctx); + + ret = snd_soc_set_ac97_ops(&ac97c_bus_ops); + if (ret) + return ret; + + ret = snd_soc_register_component(&pdev->dev, &au1xac97c_component, + &au1xac97c_dai_driver, 1); + if (ret) + return ret; + + ac97c_workdata = ctx; + return 0; +} + +static int au1xac97c_drvremove(struct platform_device *pdev) +{ + struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + + WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */ + + ac97c_workdata = NULL; /* MDEV */ + + return 0; +} + +#ifdef CONFIG_PM +static int au1xac97c_drvsuspend(struct device *dev) +{ + struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); + + WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */ + + return 0; +} + +static int au1xac97c_drvresume(struct device *dev) +{ + struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); + + WR(ctx, AC97_ENABLE, EN_D | EN_CE); + WR(ctx, AC97_ENABLE, EN_CE); + WR(ctx, AC97_CONFIG, ctx->cfg); + + return 0; +} + +static const struct dev_pm_ops au1xpscac97_pmops = { + .suspend = au1xac97c_drvsuspend, + .resume = au1xac97c_drvresume, +}; + +#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops) + +#else + +#define AU1XPSCAC97_PMOPS NULL + +#endif + +static struct platform_driver au1xac97c_driver = { + .driver = { + .name = "alchemy-ac97c", + .pm = AU1XPSCAC97_PMOPS, + }, + .probe = au1xac97c_drvprobe, + .remove = au1xac97c_drvremove, +}; + +module_platform_driver(au1xac97c_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c new file mode 100644 index 000000000..e97c32798 --- /dev/null +++ b/sound/soc/au1x/db1000.c @@ -0,0 +1,56 @@ +/* + * DB1000/DB1500/DB1100 ASoC audio fabric support code. + * + * (c) 2011 Manuel Lauss + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "psc.h" + +static struct snd_soc_dai_link db1000_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .codec_dai_name = "ac97-hifi", + .cpu_dai_name = "alchemy-ac97c", + .platform_name = "alchemy-pcm-dma.0", + .codec_name = "ac97-codec", +}; + +static struct snd_soc_card db1000_ac97 = { + .name = "DB1000_AC97", + .owner = THIS_MODULE, + .dai_link = &db1000_ac97_dai, + .num_links = 1, +}; + +static int db1000_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &db1000_ac97; + card->dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static struct platform_driver db1000_audio_driver = { + .driver = { + .name = "db1000-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = db1000_audio_probe, +}; + +module_platform_driver(db1000_audio_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DB1000/DB1500/DB1100 ASoC audio"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c new file mode 100644 index 000000000..301e1fc9a --- /dev/null +++ b/sound/soc/au1x/db1200.c @@ -0,0 +1,197 @@ +/* + * DB1200/DB1300/DB1550 ASoC audio fabric support code. + * + * (c) 2008-2011 Manuel Lauss + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/wm8731.h" +#include "psc.h" + +static const struct platform_device_id db1200_pids[] = { + { + .name = "db1200-ac97", + .driver_data = 0, + }, { + .name = "db1200-i2s", + .driver_data = 1, + }, { + .name = "db1300-ac97", + .driver_data = 2, + }, { + .name = "db1300-i2s", + .driver_data = 3, + }, { + .name = "db1550-ac97", + .driver_data = 4, + }, { + .name = "db1550-i2s", + .driver_data = 5, + }, + {}, +}; + +/*------------------------- AC97 PART ---------------------------*/ + +static struct snd_soc_dai_link db1200_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .codec_dai_name = "ac97-hifi", + .cpu_dai_name = "au1xpsc_ac97.1", + .platform_name = "au1xpsc-pcm.1", + .codec_name = "ac97-codec.1", +}; + +static struct snd_soc_card db1200_ac97_machine = { + .name = "DB1200_AC97", + .owner = THIS_MODULE, + .dai_link = &db1200_ac97_dai, + .num_links = 1, +}; + +static struct snd_soc_dai_link db1300_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .codec_dai_name = "wm9712-hifi", + .cpu_dai_name = "au1xpsc_ac97.1", + .platform_name = "au1xpsc-pcm.1", + .codec_name = "wm9712-codec.1", +}; + +static struct snd_soc_card db1300_ac97_machine = { + .name = "DB1300_AC97", + .owner = THIS_MODULE, + .dai_link = &db1300_ac97_dai, + .num_links = 1, +}; + +static struct snd_soc_card db1550_ac97_machine = { + .name = "DB1550_AC97", + .owner = THIS_MODULE, + .dai_link = &db1200_ac97_dai, + .num_links = 1, +}; + +/*------------------------- I2S PART ---------------------------*/ + +static int db1200_i2s_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* WM8731 has its own 12MHz crystal */ + snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, + 12000000, SND_SOC_CLOCK_IN); + + return 0; +} + +static const struct snd_soc_ops db1200_i2s_wm8731_ops = { + .startup = db1200_i2s_startup, +}; + +static struct snd_soc_dai_link db1200_i2s_dai = { + .name = "WM8731", + .stream_name = "WM8731 PCM", + .codec_dai_name = "wm8731-hifi", + .cpu_dai_name = "au1xpsc_i2s.1", + .platform_name = "au1xpsc-pcm.1", + .codec_name = "wm8731.0-001b", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &db1200_i2s_wm8731_ops, +}; + +static struct snd_soc_card db1200_i2s_machine = { + .name = "DB1200_I2S", + .owner = THIS_MODULE, + .dai_link = &db1200_i2s_dai, + .num_links = 1, +}; + +static struct snd_soc_dai_link db1300_i2s_dai = { + .name = "WM8731", + .stream_name = "WM8731 PCM", + .codec_dai_name = "wm8731-hifi", + .cpu_dai_name = "au1xpsc_i2s.2", + .platform_name = "au1xpsc-pcm.2", + .codec_name = "wm8731.0-001b", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &db1200_i2s_wm8731_ops, +}; + +static struct snd_soc_card db1300_i2s_machine = { + .name = "DB1300_I2S", + .owner = THIS_MODULE, + .dai_link = &db1300_i2s_dai, + .num_links = 1, +}; + +static struct snd_soc_dai_link db1550_i2s_dai = { + .name = "WM8731", + .stream_name = "WM8731 PCM", + .codec_dai_name = "wm8731-hifi", + .cpu_dai_name = "au1xpsc_i2s.3", + .platform_name = "au1xpsc-pcm.3", + .codec_name = "wm8731.0-001b", + .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &db1200_i2s_wm8731_ops, +}; + +static struct snd_soc_card db1550_i2s_machine = { + .name = "DB1550_I2S", + .owner = THIS_MODULE, + .dai_link = &db1550_i2s_dai, + .num_links = 1, +}; + +/*------------------------- COMMON PART ---------------------------*/ + +static struct snd_soc_card *db1200_cards[] = { + &db1200_ac97_machine, + &db1200_i2s_machine, + &db1300_ac97_machine, + &db1300_i2s_machine, + &db1550_ac97_machine, + &db1550_i2s_machine, +}; + +static int db1200_audio_probe(struct platform_device *pdev) +{ + const struct platform_device_id *pid = platform_get_device_id(pdev); + struct snd_soc_card *card; + + card = db1200_cards[pid->driver_data]; + card->dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static struct platform_driver db1200_audio_driver = { + .driver = { + .name = "db1200-ac97", + .pm = &snd_soc_pm_ops, + }, + .id_table = db1200_pids, + .probe = db1200_audio_probe, +}; + +module_platform_driver(db1200_audio_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DB1200/DB1300/DB1550 ASoC audio support"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c new file mode 100644 index 000000000..a906560d0 --- /dev/null +++ b/sound/soc/au1x/dbdma2.c @@ -0,0 +1,365 @@ +/* + * Au12x0/Au1550 PSC ALSA ASoC audio support. + * + * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * Manuel Lauss + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * DMA glue for Au1x-PSC audio. + * + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "psc.h" + +/*#define PCM_DEBUG*/ + +#define DRV_NAME "dbdma2" + +#define MSG(x...) printk(KERN_INFO "au1xpsc_pcm: " x) +#ifdef PCM_DEBUG +#define DBG MSG +#else +#define DBG(x...) do {} while (0) +#endif + +struct au1xpsc_audio_dmadata { + /* DDMA control data */ + unsigned int ddma_id; /* DDMA direction ID for this PSC */ + u32 ddma_chan; /* DDMA context */ + + /* PCM context (for irq handlers) */ + struct snd_pcm_substream *substream; + unsigned long curr_period; /* current segment DDMA is working on */ + unsigned long q_period; /* queue period(s) */ + dma_addr_t dma_area; /* address of queued DMA area */ + dma_addr_t dma_area_s; /* start address of DMA area */ + unsigned long pos; /* current byte position being played */ + unsigned long periods; /* number of SG segments in total */ + unsigned long period_bytes; /* size in bytes of one SG segment */ + + /* runtime data */ + int msbits; +}; + +/* + * These settings are somewhat okay, at least on my machine audio plays + * almost skip-free. Especially the 64kB buffer seems to help a LOT. + */ +#define AU1XPSC_PERIOD_MIN_BYTES 1024 +#define AU1XPSC_BUFFER_MIN_BYTES 65536 + +/* PCM hardware DMA capabilities - platform specific */ +static const struct snd_pcm_hardware au1xpsc_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH, + .period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES, + .period_bytes_max = 4096 * 1024 - 1, + .periods_min = 2, + .periods_max = 4096, /* 2 to as-much-as-you-like */ + .buffer_bytes_max = 4096 * 1024 - 1, + .fifo_size = 16, /* fifo entries of AC97/I2S PSC */ +}; + +static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd) +{ + au1xxx_dbdma_put_source(cd->ddma_chan, cd->dma_area, + cd->period_bytes, DDMA_FLAGS_IE); + + /* update next-to-queue period */ + ++cd->q_period; + cd->dma_area += cd->period_bytes; + if (cd->q_period >= cd->periods) { + cd->q_period = 0; + cd->dma_area = cd->dma_area_s; + } +} + +static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd) +{ + au1xxx_dbdma_put_dest(cd->ddma_chan, cd->dma_area, + cd->period_bytes, DDMA_FLAGS_IE); + + /* update next-to-queue period */ + ++cd->q_period; + cd->dma_area += cd->period_bytes; + if (cd->q_period >= cd->periods) { + cd->q_period = 0; + cd->dma_area = cd->dma_area_s; + } +} + +static void au1x_pcm_dmatx_cb(int irq, void *dev_id) +{ + struct au1xpsc_audio_dmadata *cd = dev_id; + + cd->pos += cd->period_bytes; + if (++cd->curr_period >= cd->periods) { + cd->pos = 0; + cd->curr_period = 0; + } + snd_pcm_period_elapsed(cd->substream); + au1x_pcm_queue_tx(cd); +} + +static void au1x_pcm_dmarx_cb(int irq, void *dev_id) +{ + struct au1xpsc_audio_dmadata *cd = dev_id; + + cd->pos += cd->period_bytes; + if (++cd->curr_period >= cd->periods) { + cd->pos = 0; + cd->curr_period = 0; + } + snd_pcm_period_elapsed(cd->substream); + au1x_pcm_queue_rx(cd); +} + +static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd) +{ + if (pcd->ddma_chan) { + au1xxx_dbdma_stop(pcd->ddma_chan); + au1xxx_dbdma_reset(pcd->ddma_chan); + au1xxx_dbdma_chan_free(pcd->ddma_chan); + pcd->ddma_chan = 0; + pcd->msbits = 0; + } +} + +/* in case of missing DMA ring or changed TX-source / RX-dest bit widths, + * allocate (or reallocate) a 2-descriptor DMA ring with bit depth according + * to ALSA-supplied sample depth. This is due to limitations in the dbdma api + * (cannot adjust source/dest widths of already allocated descriptor ring). + */ +static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd, + int stype, int msbits) +{ + /* DMA only in 8/16/32 bit widths */ + if (msbits == 24) + msbits = 32; + + /* check current config: correct bits and descriptors allocated? */ + if ((pcd->ddma_chan) && (msbits == pcd->msbits)) + goto out; /* all ok! */ + + au1x_pcm_dbdma_free(pcd); + + if (stype == SNDRV_PCM_STREAM_CAPTURE) + pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id, + DSCR_CMD0_ALWAYS, + au1x_pcm_dmarx_cb, (void *)pcd); + else + pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS, + pcd->ddma_id, + au1x_pcm_dmatx_cb, (void *)pcd); + + if (!pcd->ddma_chan) + return -ENOMEM; + + au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits); + au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2); + + pcd->msbits = msbits; + + au1xxx_dbdma_stop(pcd->ddma_chan); + au1xxx_dbdma_reset(pcd->ddma_chan); + +out: + return 0; +} + +static inline struct au1xpsc_audio_dmadata *to_dmadata(struct snd_pcm_substream *ss) +{ + struct snd_soc_pcm_runtime *rtd = ss->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct au1xpsc_audio_dmadata *pcd = snd_soc_component_get_drvdata(component); + return &pcd[ss->stream]; +} + +static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct au1xpsc_audio_dmadata *pcd; + int stype, ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + goto out; + + stype = substream->stream; + pcd = to_dmadata(substream); + + DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %zu " + "runtime->min_align %lu\n", + (unsigned long)runtime->dma_area, + (unsigned long)runtime->dma_addr, runtime->dma_bytes, + runtime->min_align); + + DBG("bits %d frags %d frag_bytes %d is_rx %d\n", params->msbits, + params_periods(params), params_period_bytes(params), stype); + + ret = au1x_pcm_dbdma_realloc(pcd, stype, params->msbits); + if (ret) { + MSG("DDMA channel (re)alloc failed!\n"); + goto out; + } + + pcd->substream = substream; + pcd->period_bytes = params_period_bytes(params); + pcd->periods = params_periods(params); + pcd->dma_area_s = pcd->dma_area = runtime->dma_addr; + pcd->q_period = 0; + pcd->curr_period = 0; + pcd->pos = 0; + + ret = 0; +out: + return ret; +} + +static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream); + + au1xxx_dbdma_reset(pcd->ddma_chan); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + au1x_pcm_queue_rx(pcd); + au1x_pcm_queue_rx(pcd); + } else { + au1x_pcm_queue_tx(pcd); + au1x_pcm_queue_tx(pcd); + } + + return 0; +} + +static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + u32 c = to_dmadata(substream)->ddma_chan; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + au1xxx_dbdma_start(c); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + au1xxx_dbdma_stop(c); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t +au1xpsc_pcm_pointer(struct snd_pcm_substream *substream) +{ + return bytes_to_frames(substream->runtime, to_dmadata(substream)->pos); +} + +static int au1xpsc_pcm_open(struct snd_pcm_substream *substream) +{ + struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int stype = substream->stream, *dmaids; + + dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (!dmaids) + return -ENODEV; /* whoa, has ordering changed? */ + + pcd->ddma_id = dmaids[stype]; + + snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware); + return 0; +} + +static int au1xpsc_pcm_close(struct snd_pcm_substream *substream) +{ + au1x_pcm_dbdma_free(to_dmadata(substream)); + return 0; +} + +static const struct snd_pcm_ops au1xpsc_pcm_ops = { + .open = au1xpsc_pcm_open, + .close = au1xpsc_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = au1xpsc_pcm_hw_params, + .hw_free = au1xpsc_pcm_hw_free, + .prepare = au1xpsc_pcm_prepare, + .trigger = au1xpsc_pcm_trigger, + .pointer = au1xpsc_pcm_pointer, +}; + +static int au1xpsc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1); + + return 0; +} + +/* au1xpsc audio platform */ +static struct snd_soc_component_driver au1xpsc_soc_component = { + .name = DRV_NAME, + .ops = &au1xpsc_pcm_ops, + .pcm_new = au1xpsc_pcm_new, +}; + +static int au1xpsc_pcm_drvprobe(struct platform_device *pdev) +{ + struct au1xpsc_audio_dmadata *dmadata; + + dmadata = devm_kcalloc(&pdev->dev, + 2, sizeof(struct au1xpsc_audio_dmadata), + GFP_KERNEL); + if (!dmadata) + return -ENOMEM; + + platform_set_drvdata(pdev, dmadata); + + return devm_snd_soc_register_component(&pdev->dev, + &au1xpsc_soc_component, NULL, 0); +} + +static struct platform_driver au1xpsc_pcm_driver = { + .driver = { + .name = "au1xpsc-pcm", + }, + .probe = au1xpsc_pcm_drvprobe, +}; + +module_platform_driver(au1xpsc_pcm_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 000000000..efff1e293 --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,334 @@ +/* + * Au1000/Au1500/Au1100 Audio DMA support. + * + * (c) 2011 Manuel Lauss + * + * copied almost verbatim from the old ALSA driver, written by + * Charles Eidsness + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "psc.h" + +#define DRV_NAME "au1x_dma" + +struct pcm_period { + u32 start; + u32 relative_end; /* relative to start of buffer */ + struct pcm_period *next; +}; + +struct audio_stream { + struct snd_pcm_substream *substream; + int dma; + struct pcm_period *buffer; + unsigned int period_size; + unsigned int periods; +}; + +struct alchemy_pcm_ctx { + struct audio_stream stream[2]; /* playback & capture */ +}; + +static void au1000_release_dma_link(struct audio_stream *stream) +{ + struct pcm_period *pointer; + struct pcm_period *pointer_next; + + stream->period_size = 0; + stream->periods = 0; + pointer = stream->buffer; + if (!pointer) + return; + do { + pointer_next = pointer->next; + kfree(pointer); + pointer = pointer_next; + } while (pointer != stream->buffer); + stream->buffer = NULL; +} + +static int au1000_setup_dma_link(struct audio_stream *stream, + unsigned int period_bytes, + unsigned int periods) +{ + struct snd_pcm_substream *substream = stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_period *pointer; + unsigned long dma_start; + int i; + + dma_start = virt_to_phys(runtime->dma_area); + + if (stream->period_size == period_bytes && + stream->periods == periods) + return 0; /* not changed */ + + au1000_release_dma_link(stream); + + stream->period_size = period_bytes; + stream->periods = periods; + + stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL); + if (!stream->buffer) + return -ENOMEM; + pointer = stream->buffer; + for (i = 0; i < periods; i++) { + pointer->start = (u32)(dma_start + (i * period_bytes)); + pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1); + if (i < periods - 1) { + pointer->next = kmalloc(sizeof(struct pcm_period), + GFP_KERNEL); + if (!pointer->next) { + au1000_release_dma_link(stream); + return -ENOMEM; + } + pointer = pointer->next; + } + } + pointer->next = stream->buffer; + return 0; +} + +static void au1000_dma_stop(struct audio_stream *stream) +{ + if (stream->buffer) + disable_dma(stream->dma); +} + +static void au1000_dma_start(struct audio_stream *stream) +{ + if (!stream->buffer) + return; + + init_dma(stream->dma); + if (get_dma_active_buffer(stream->dma) == 0) { + clear_dma_done0(stream->dma); + set_dma_addr0(stream->dma, stream->buffer->start); + set_dma_count0(stream->dma, stream->period_size >> 1); + set_dma_addr1(stream->dma, stream->buffer->next->start); + set_dma_count1(stream->dma, stream->period_size >> 1); + } else { + clear_dma_done1(stream->dma); + set_dma_addr1(stream->dma, stream->buffer->start); + set_dma_count1(stream->dma, stream->period_size >> 1); + set_dma_addr0(stream->dma, stream->buffer->next->start); + set_dma_count0(stream->dma, stream->period_size >> 1); + } + enable_dma_buffers(stream->dma); + start_dma(stream->dma); +} + +static irqreturn_t au1000_dma_interrupt(int irq, void *ptr) +{ + struct audio_stream *stream = (struct audio_stream *)ptr; + struct snd_pcm_substream *substream = stream->substream; + + switch (get_dma_buffer_done(stream->dma)) { + case DMA_D0: + stream->buffer = stream->buffer->next; + clear_dma_done0(stream->dma); + set_dma_addr0(stream->dma, stream->buffer->next->start); + set_dma_count0(stream->dma, stream->period_size >> 1); + enable_dma_buffer0(stream->dma); + break; + case DMA_D1: + stream->buffer = stream->buffer->next; + clear_dma_done1(stream->dma); + set_dma_addr1(stream->dma, stream->buffer->next->start); + set_dma_count1(stream->dma, stream->period_size >> 1); + enable_dma_buffer1(stream->dma); + break; + case (DMA_D0 | DMA_D1): + pr_debug("DMA %d missed interrupt.\n", stream->dma); + au1000_dma_stop(stream); + au1000_dma_start(stream); + break; + case (~DMA_D0 & ~DMA_D1): + pr_debug("DMA %d empty irq.\n", stream->dma); + } + snd_pcm_period_elapsed(substream); + return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware alchemy_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH, + .period_bytes_min = 1024, + .period_bytes_max = 16 * 1024 - 1, + .periods_min = 4, + .periods_max = 255, + .buffer_bytes_max = 128 * 1024, + .fifo_size = 16, +}; + +static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss) +{ + struct snd_soc_pcm_runtime *rtd = ss->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + return snd_soc_component_get_drvdata(component); +} + +static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss) +{ + struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss); + return &(ctx->stream[ss->stream]); +} + +static int alchemy_pcm_open(struct snd_pcm_substream *substream) +{ + struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int *dmaids, s = substream->stream; + char *name; + + dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + if (!dmaids) + return -ENODEV; /* whoa, has ordering changed? */ + + /* DMA setup */ + name = (s == SNDRV_PCM_STREAM_PLAYBACK) ? "audio-tx" : "audio-rx"; + ctx->stream[s].dma = request_au1000_dma(dmaids[s], name, + au1000_dma_interrupt, 0, + &ctx->stream[s]); + set_dma_mode(ctx->stream[s].dma, + get_dma_mode(ctx->stream[s].dma) & ~DMA_NC); + + ctx->stream[s].substream = substream; + ctx->stream[s].buffer = NULL; + snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware); + + return 0; +} + +static int alchemy_pcm_close(struct snd_pcm_substream *substream) +{ + struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream); + int stype = substream->stream; + + ctx->stream[stype].substream = NULL; + free_au1000_dma(ctx->stream[stype].dma); + + return 0; +} + +static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct audio_stream *stream = ss_to_as(substream); + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + err = au1000_setup_dma_link(stream, + params_period_bytes(hw_params), + params_periods(hw_params)); + if (err) + snd_pcm_lib_free_pages(substream); + + return err; +} + +static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct audio_stream *stream = ss_to_as(substream); + au1000_release_dma_link(stream); + return snd_pcm_lib_free_pages(substream); +} + +static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct audio_stream *stream = ss_to_as(substream); + int err = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + au1000_dma_start(stream); + break; + case SNDRV_PCM_TRIGGER_STOP: + au1000_dma_stop(stream); + break; + default: + err = -EINVAL; + break; + } + return err; +} + +static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss) +{ + struct audio_stream *stream = ss_to_as(ss); + long location; + + location = get_dma_residue(stream->dma); + location = stream->buffer->relative_end - location; + if (location == -1) + location = 0; + return bytes_to_frames(ss->runtime, location); +} + +static const struct snd_pcm_ops alchemy_pcm_ops = { + .open = alchemy_pcm_open, + .close = alchemy_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = alchemy_pcm_hw_params, + .hw_free = alchemy_pcm_hw_free, + .trigger = alchemy_pcm_trigger, + .pointer = alchemy_pcm_pointer, +}; + +static int alchemy_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1); + + return 0; +} + +static struct snd_soc_component_driver alchemy_pcm_soc_component = { + .name = DRV_NAME, + .ops = &alchemy_pcm_ops, + .pcm_new = alchemy_pcm_new, +}; + +static int alchemy_pcm_drvprobe(struct platform_device *pdev) +{ + struct alchemy_pcm_ctx *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + platform_set_drvdata(pdev, ctx); + + return devm_snd_soc_register_component(&pdev->dev, + &alchemy_pcm_soc_component, NULL, 0); +} + +static struct platform_driver alchemy_pcmdma_driver = { + .driver = { + .name = "alchemy-pcm-dma", + }, + .probe = alchemy_pcm_drvprobe, +}; + +module_platform_driver(alchemy_pcmdma_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c new file mode 100644 index 000000000..450c842c7 --- /dev/null +++ b/sound/soc/au1x/i2sc.c @@ -0,0 +1,323 @@ +/* + * Au1000/Au1500/Au1100 I2S controller driver for ASoC + * + * (c) 2011 Manuel Lauss + * + * Note: clock supplied to the I2S controller must be 256x samplerate. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "psc.h" + +#define I2S_RXTX 0x00 +#define I2S_CFG 0x04 +#define I2S_ENABLE 0x08 + +#define CFG_XU (1 << 25) /* tx underflow */ +#define CFG_XO (1 << 24) +#define CFG_RU (1 << 23) +#define CFG_RO (1 << 22) +#define CFG_TR (1 << 21) +#define CFG_TE (1 << 20) +#define CFG_TF (1 << 19) +#define CFG_RR (1 << 18) +#define CFG_RF (1 << 17) +#define CFG_ICK (1 << 12) /* clock invert */ +#define CFG_PD (1 << 11) /* set to make I2SDIO INPUT */ +#define CFG_LB (1 << 10) /* loopback */ +#define CFG_IC (1 << 9) /* word select invert */ +#define CFG_FM_I2S (0 << 7) /* I2S format */ +#define CFG_FM_LJ (1 << 7) /* left-justified */ +#define CFG_FM_RJ (2 << 7) /* right-justified */ +#define CFG_FM_MASK (3 << 7) +#define CFG_TN (1 << 6) /* tx fifo en */ +#define CFG_RN (1 << 5) /* rx fifo en */ +#define CFG_SZ_8 (0x08) +#define CFG_SZ_16 (0x10) +#define CFG_SZ_18 (0x12) +#define CFG_SZ_20 (0x14) +#define CFG_SZ_24 (0x18) +#define CFG_SZ_MASK (0x1f) +#define EN_D (1 << 1) /* DISable */ +#define EN_CE (1 << 0) /* clock enable */ + +/* only limited by clock generator and board design */ +#define AU1XI2SC_RATES \ + SNDRV_PCM_RATE_CONTINUOUS + +#define AU1XI2SC_FMTS \ + (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 | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \ + SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE | \ + 0) + +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg) +{ + return __raw_readl(ctx->mmio + reg); +} + +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v) +{ + __raw_writel(v, ctx->mmio + reg); + wmb(); +} + +static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long c; + int ret; + + ret = -EINVAL; + c = ctx->cfg; + + c &= ~CFG_FM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + c |= CFG_FM_I2S; + break; + case SND_SOC_DAIFMT_MSB: + c |= CFG_FM_RJ; + break; + case SND_SOC_DAIFMT_LSB: + c |= CFG_FM_LJ; + break; + default: + goto out; + } + + c &= ~(CFG_IC | CFG_ICK); /* IB-IF */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + c |= CFG_IC | CFG_ICK; + break; + case SND_SOC_DAIFMT_NB_IF: + c |= CFG_IC; + break; + case SND_SOC_DAIFMT_IB_NF: + c |= CFG_ICK; + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + goto out; + } + + /* I2S controller only supports master */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ + break; + default: + goto out; + } + + ret = 0; + ctx->cfg = c; +out: + return ret; +} + +static int au1xi2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); + int stype = SUBSTREAM_TYPE(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* power up */ + WR(ctx, I2S_ENABLE, EN_D | EN_CE); + WR(ctx, I2S_ENABLE, EN_CE); + ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN; + WR(ctx, I2S_CFG, ctx->cfg); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN); + WR(ctx, I2S_CFG, ctx->cfg); + WR(ctx, I2S_ENABLE, EN_D); /* power off */ + break; + default: + return -EINVAL; + } + + return 0; +} + +static unsigned long msbits_to_reg(int msbits) +{ + switch (msbits) { + case 8: + return CFG_SZ_8; + case 16: + return CFG_SZ_16; + case 18: + return CFG_SZ_18; + case 20: + return CFG_SZ_20; + case 24: + return CFG_SZ_24; + } + return 0; +} + +static int au1xi2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); + unsigned long v; + + v = msbits_to_reg(params->msbits); + if (!v) + return -EINVAL; + + ctx->cfg &= ~CFG_SZ_MASK; + ctx->cfg |= v; + return 0; +} + +static int au1xi2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]); + return 0; +} + +static const struct snd_soc_dai_ops au1xi2s_dai_ops = { + .startup = au1xi2s_startup, + .trigger = au1xi2s_trigger, + .hw_params = au1xi2s_hw_params, + .set_fmt = au1xi2s_set_fmt, +}; + +static struct snd_soc_dai_driver au1xi2s_dai_driver = { + .symmetric_rates = 1, + .playback = { + .rates = AU1XI2SC_RATES, + .formats = AU1XI2SC_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AU1XI2SC_RATES, + .formats = AU1XI2SC_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &au1xi2s_dai_ops, +}; + +static const struct snd_soc_component_driver au1xi2s_component = { + .name = "au1xi2s", +}; + +static int au1xi2s_drvprobe(struct platform_device *pdev) +{ + struct resource *iores, *dmares; + struct au1xpsc_audio_data *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iores) + return -ENODEV; + + if (!devm_request_mem_region(&pdev->dev, iores->start, + resource_size(iores), + pdev->name)) + return -EBUSY; + + ctx->mmio = devm_ioremap_nocache(&pdev->dev, iores->start, + resource_size(iores)); + if (!ctx->mmio) + return -EBUSY; + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmares) + return -EBUSY; + ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start; + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!dmares) + return -EBUSY; + ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start; + + platform_set_drvdata(pdev, ctx); + + return snd_soc_register_component(&pdev->dev, &au1xi2s_component, + &au1xi2s_dai_driver, 1); +} + +static int au1xi2s_drvremove(struct platform_device *pdev) +{ + struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + + WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ + + return 0; +} + +#ifdef CONFIG_PM +static int au1xi2s_drvsuspend(struct device *dev) +{ + struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); + + WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ + + return 0; +} + +static int au1xi2s_drvresume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops au1xi2sc_pmops = { + .suspend = au1xi2s_drvsuspend, + .resume = au1xi2s_drvresume, +}; + +#define AU1XI2SC_PMOPS (&au1xi2sc_pmops) + +#else + +#define AU1XI2SC_PMOPS NULL + +#endif + +static struct platform_driver au1xi2s_driver = { + .driver = { + .name = "alchemy-i2sc", + .pm = AU1XI2SC_PMOPS, + }, + .probe = au1xi2s_drvprobe, + .remove = au1xi2s_drvremove, +}; + +module_platform_driver(au1xi2s_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c new file mode 100644 index 000000000..a2050ae5a --- /dev/null +++ b/sound/soc/au1x/psc-ac97.c @@ -0,0 +1,504 @@ +/* + * Au12x0/Au1550 PSC ALSA ASoC audio support. + * + * (c) 2007-2009 MSC Vertriebsges.m.b.H., + * Manuel Lauss + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Au1xxx-PSC AC97 glue. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "psc.h" + +/* how often to retry failed codec register reads/writes */ +#define AC97_RW_RETRIES 5 + +#define AC97_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_48000 + +#define AC97_FMTS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE) + +#define AC97PCR_START(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TS : PSC_AC97PCR_RS) +#define AC97PCR_STOP(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TP : PSC_AC97PCR_RP) +#define AC97PCR_CLRFIFO(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TC : PSC_AC97PCR_RC) + +#define AC97STAT_BUSY(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97STAT_TB : PSC_AC97STAT_RB) + +/* instance data. There can be only one, MacLeod!!!! */ +static struct au1xpsc_audio_data *au1xpsc_ac97_workdata; + +#if 0 + +/* this could theoretically work, but ac97->bus->card->private_data can be NULL + * when snd_ac97_mixer() is called; I don't know if the rest further down the + * chain are always valid either. + */ +static inline struct au1xpsc_audio_data *ac97_to_pscdata(struct snd_ac97 *x) +{ + struct snd_soc_card *c = x->bus->card->private_data; + return snd_soc_dai_get_drvdata(c->rtd->cpu_dai); +} + +#else + +#define ac97_to_pscdata(x) au1xpsc_ac97_workdata + +#endif + +/* AC97 controller reads codec register */ +static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97); + unsigned short retry, tmo; + unsigned long data; + + __raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); + wmb(); /* drain writebuffer */ + + retry = AC97_RW_RETRIES; + do { + mutex_lock(&pscdata->lock); + + __raw_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg), + AC97_CDC(pscdata)); + wmb(); /* drain writebuffer */ + + tmo = 20; + do { + udelay(21); + if (__raw_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD) + break; + } while (--tmo); + + data = __raw_readl(AC97_CDC(pscdata)); + + __raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); + wmb(); /* drain writebuffer */ + + mutex_unlock(&pscdata->lock); + + if (reg != ((data >> 16) & 0x7f)) + tmo = 1; /* wrong register, try again */ + + } while (--retry && !tmo); + + return retry ? data & 0xffff : 0xffff; +} + +/* AC97 controller writes to codec register */ +static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97); + unsigned int tmo, retry; + + __raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); + wmb(); /* drain writebuffer */ + + retry = AC97_RW_RETRIES; + do { + mutex_lock(&pscdata->lock); + + __raw_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff), + AC97_CDC(pscdata)); + wmb(); /* drain writebuffer */ + + tmo = 20; + do { + udelay(21); + if (__raw_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD) + break; + } while (--tmo); + + __raw_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); + wmb(); /* drain writebuffer */ + + mutex_unlock(&pscdata->lock); + } while (--retry && !tmo); +} + +/* AC97 controller asserts a warm reset */ +static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97); + + __raw_writel(PSC_AC97RST_SNC, AC97_RST(pscdata)); + wmb(); /* drain writebuffer */ + msleep(10); + __raw_writel(0, AC97_RST(pscdata)); + wmb(); /* drain writebuffer */ +} + +static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct au1xpsc_audio_data *pscdata = ac97_to_pscdata(ac97); + int i; + + /* disable PSC during cold reset */ + __raw_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata)); + wmb(); /* drain writebuffer */ + + /* issue cold reset */ + __raw_writel(PSC_AC97RST_RST, AC97_RST(pscdata)); + wmb(); /* drain writebuffer */ + msleep(500); + __raw_writel(0, AC97_RST(pscdata)); + wmb(); /* drain writebuffer */ + + /* enable PSC */ + __raw_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata)); + wmb(); /* drain writebuffer */ + + /* wait for PSC to indicate it's ready */ + i = 1000; + while (!((__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i)) + msleep(1); + + if (i == 0) { + printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n"); + return; + } + + /* enable the ac97 function */ + __raw_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); + wmb(); /* drain writebuffer */ + + /* wait for AC97 core to become ready */ + i = 1000; + while (!((__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i)) + msleep(1); + if (i == 0) + printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n"); +} + +/* AC97 controller operations */ +static struct snd_ac97_bus_ops psc_ac97_ops = { + .read = au1xpsc_ac97_read, + .write = au1xpsc_ac97_write, + .reset = au1xpsc_ac97_cold_reset, + .warm_reset = au1xpsc_ac97_warm_reset, +}; + +static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + unsigned long r, ro, stat; + int chans, t, stype = substream->stream; + + chans = params_channels(params); + + r = ro = __raw_readl(AC97_CFG(pscdata)); + stat = __raw_readl(AC97_STAT(pscdata)); + + /* already active? */ + if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) { + /* reject parameters not currently set up */ + if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) || + (pscdata->rate != params_rate(params))) + return -EINVAL; + } else { + + /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */ + r &= ~PSC_AC97CFG_LEN_MASK; + r |= PSC_AC97CFG_SET_LEN(params->msbits); + + /* channels: enable slots for front L/R channel */ + if (stype == SNDRV_PCM_STREAM_PLAYBACK) { + r &= ~PSC_AC97CFG_TXSLOT_MASK; + r |= PSC_AC97CFG_TXSLOT_ENA(3); + r |= PSC_AC97CFG_TXSLOT_ENA(4); + } else { + r &= ~PSC_AC97CFG_RXSLOT_MASK; + r |= PSC_AC97CFG_RXSLOT_ENA(3); + r |= PSC_AC97CFG_RXSLOT_ENA(4); + } + + /* do we need to poke the hardware? */ + if (!(r ^ ro)) + goto out; + + /* ac97 engine is about to be disabled */ + mutex_lock(&pscdata->lock); + + /* disable AC97 device controller first... */ + __raw_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); + wmb(); /* drain writebuffer */ + + /* ...wait for it... */ + t = 100; + while ((__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR) && --t) + msleep(1); + + if (!t) + printk(KERN_ERR "PSC-AC97: can't disable!\n"); + + /* ...write config... */ + __raw_writel(r, AC97_CFG(pscdata)); + wmb(); /* drain writebuffer */ + + /* ...enable the AC97 controller again... */ + __raw_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); + wmb(); /* drain writebuffer */ + + /* ...and wait for ready bit */ + t = 100; + while ((!(__raw_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && --t) + msleep(1); + + if (!t) + printk(KERN_ERR "PSC-AC97: can't enable!\n"); + + mutex_unlock(&pscdata->lock); + + pscdata->cfg = r; + pscdata->rate = params_rate(params); + } + +out: + return 0; +} + +static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + int ret, stype = substream->stream; + + ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + __raw_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata)); + wmb(); /* drain writebuffer */ + __raw_writel(AC97PCR_START(stype), AC97_PCR(pscdata)); + wmb(); /* drain writebuffer */ + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + __raw_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata)); + wmb(); /* drain writebuffer */ + + while (__raw_readl(AC97_STAT(pscdata)) & AC97STAT_BUSY(stype)) + asm volatile ("nop"); + + __raw_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata)); + wmb(); /* drain writebuffer */ + + break; + default: + ret = -EINVAL; + } + return ret; +} + +static int au1xpsc_ac97_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]); + return 0; +} + +static int au1xpsc_ac97_probe(struct snd_soc_dai *dai) +{ + return au1xpsc_ac97_workdata ? 0 : -ENODEV; +} + +static const struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = { + .startup = au1xpsc_ac97_startup, + .trigger = au1xpsc_ac97_trigger, + .hw_params = au1xpsc_ac97_hw_params, +}; + +static const struct snd_soc_dai_driver au1xpsc_ac97_dai_template = { + .bus_control = true, + .probe = au1xpsc_ac97_probe, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &au1xpsc_ac97_dai_ops, +}; + +static const struct snd_soc_component_driver au1xpsc_ac97_component = { + .name = "au1xpsc-ac97", +}; + +static int au1xpsc_ac97_drvprobe(struct platform_device *pdev) +{ + int ret; + struct resource *iores, *dmares; + unsigned long sel; + struct au1xpsc_audio_data *wd; + + wd = devm_kzalloc(&pdev->dev, sizeof(struct au1xpsc_audio_data), + GFP_KERNEL); + if (!wd) + return -ENOMEM; + + mutex_init(&wd->lock); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wd->mmio = devm_ioremap_resource(&pdev->dev, iores); + if (IS_ERR(wd->mmio)) + return PTR_ERR(wd->mmio); + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmares) + return -EBUSY; + wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start; + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!dmares) + return -EBUSY; + wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start; + + /* configuration: max dma trigger threshold, enable ac97 */ + wd->cfg = PSC_AC97CFG_RT_FIFO8 | PSC_AC97CFG_TT_FIFO8 | + PSC_AC97CFG_DE_ENABLE; + + /* preserve PSC clock source set up by platform */ + sel = __raw_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK; + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(0, PSC_SEL(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(wd)); + wmb(); /* drain writebuffer */ + + /* name the DAI like this device instance ("au1xpsc-ac97.PSCINDEX") */ + memcpy(&wd->dai_drv, &au1xpsc_ac97_dai_template, + sizeof(struct snd_soc_dai_driver)); + wd->dai_drv.name = dev_name(&pdev->dev); + + platform_set_drvdata(pdev, wd); + + ret = snd_soc_set_ac97_ops(&psc_ac97_ops); + if (ret) + return ret; + + ret = snd_soc_register_component(&pdev->dev, &au1xpsc_ac97_component, + &wd->dai_drv, 1); + if (ret) + return ret; + + au1xpsc_ac97_workdata = wd; + return 0; +} + +static int au1xpsc_ac97_drvremove(struct platform_device *pdev) +{ + struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + + /* disable PSC completely */ + __raw_writel(0, AC97_CFG(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd)); + wmb(); /* drain writebuffer */ + + au1xpsc_ac97_workdata = NULL; /* MDEV */ + + return 0; +} + +#ifdef CONFIG_PM +static int au1xpsc_ac97_drvsuspend(struct device *dev) +{ + struct au1xpsc_audio_data *wd = dev_get_drvdata(dev); + + /* save interesting registers and disable PSC */ + wd->pm[0] = __raw_readl(PSC_SEL(wd)); + + __raw_writel(0, AC97_CFG(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd)); + wmb(); /* drain writebuffer */ + + return 0; +} + +static int au1xpsc_ac97_drvresume(struct device *dev) +{ + struct au1xpsc_audio_data *wd = dev_get_drvdata(dev); + + /* restore PSC clock config */ + __raw_writel(wd->pm[0] | PSC_SEL_PS_AC97MODE, PSC_SEL(wd)); + wmb(); /* drain writebuffer */ + + /* after this point the ac97 core will cold-reset the codec. + * During cold-reset the PSC is reinitialized and the last + * configuration set up in hw_params() is restored. + */ + return 0; +} + +static const struct dev_pm_ops au1xpscac97_pmops = { + .suspend = au1xpsc_ac97_drvsuspend, + .resume = au1xpsc_ac97_drvresume, +}; + +#define AU1XPSCAC97_PMOPS &au1xpscac97_pmops + +#else + +#define AU1XPSCAC97_PMOPS NULL + +#endif + +static struct platform_driver au1xpsc_ac97_driver = { + .driver = { + .name = "au1xpsc_ac97", + .pm = AU1XPSCAC97_PMOPS, + }, + .probe = au1xpsc_ac97_drvprobe, + .remove = au1xpsc_ac97_drvremove, +}; + +module_platform_driver(au1xpsc_ac97_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver"); +MODULE_AUTHOR("Manuel Lauss"); + diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c new file mode 100644 index 000000000..e6eec081e --- /dev/null +++ b/sound/soc/au1x/psc-i2s.c @@ -0,0 +1,421 @@ +/* + * Au12x0/Au1550 PSC ALSA ASoC audio support. + * + * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * Manuel Lauss + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Au1xxx-PSC I2S glue. + * + * NOTE: so far only PSC slave mode (bit- and frameclock) is supported. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "psc.h" + +/* supported I2S DAI hardware formats */ +#define AU1XPSC_I2S_DAIFMT \ + (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \ + SND_SOC_DAIFMT_NB_NF) + +/* supported I2S direction */ +#define AU1XPSC_I2S_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AU1XPSC_I2S_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AU1XPSC_I2S_FMTS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +#define I2SSTAT_BUSY(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB) +#define I2SPCR_START(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TS : PSC_I2SPCR_RS) +#define I2SPCR_STOP(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TP : PSC_I2SPCR_RP) +#define I2SPCR_CLRFIFO(stype) \ + ((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TC : PSC_I2SPCR_RC) + + +static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long ct; + int ret; + + ret = -EINVAL; + + ct = pscdata->cfg; + + ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ); /* left-justified */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ct |= PSC_I2SCFG_XM; /* enable I2S mode */ + break; + case SND_SOC_DAIFMT_MSB: + break; + case SND_SOC_DAIFMT_LSB: + ct |= PSC_I2SCFG_MLJ; /* LSB (right-) justified */ + break; + default: + goto out; + } + + ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI); /* IB-IF */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI; + break; + case SND_SOC_DAIFMT_NB_IF: + ct |= PSC_I2SCFG_BI; + break; + case SND_SOC_DAIFMT_IB_NF: + ct |= PSC_I2SCFG_WI; + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + goto out; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */ + ct |= PSC_I2SCFG_MS; /* PSC I2S slave mode */ + break; + case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ + ct &= ~PSC_I2SCFG_MS; /* PSC I2S Master mode */ + break; + default: + goto out; + } + + pscdata->cfg = ct; + ret = 0; +out: + return ret; +} + +static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + + int cfgbits; + unsigned long stat; + + /* check if the PSC is already streaming data */ + stat = __raw_readl(I2S_STAT(pscdata)); + if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) { + /* reject parameters not currently set up in hardware */ + cfgbits = __raw_readl(I2S_CFG(pscdata)); + if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) || + (params_rate(params) != pscdata->rate)) + return -EINVAL; + } else { + /* set sample bitdepth */ + pscdata->cfg &= ~(0x1f << 4); + pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits); + /* remember current rate for other stream */ + pscdata->rate = params_rate(params); + } + return 0; +} + +/* Configure PSC late: on my devel systems the codec is I2S master and + * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit. ASoC + * uses aggressive PM and switches the codec off when it is not in use + * which also means the PSC unit doesn't get any clocks and is therefore + * dead. That's why this chunk here gets called from the trigger callback + * because I can be reasonably certain the codec is driving the clocks. + */ +static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata) +{ + unsigned long tmo; + + /* bring PSC out of sleep, and configure I2S unit */ + __raw_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata)); + wmb(); /* drain writebuffer */ + + tmo = 1000000; + while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo) + tmo--; + + if (!tmo) + goto psc_err; + + __raw_writel(0, I2S_CFG(pscdata)); + wmb(); /* drain writebuffer */ + __raw_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata)); + wmb(); /* drain writebuffer */ + + /* wait for I2S controller to become ready */ + tmo = 1000000; + while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo) + tmo--; + + if (tmo) + return 0; + +psc_err: + __raw_writel(0, I2S_CFG(pscdata)); + __raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata)); + wmb(); /* drain writebuffer */ + return -ETIMEDOUT; +} + +static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype) +{ + unsigned long tmo, stat; + int ret; + + ret = 0; + + /* if both TX and RX are idle, configure the PSC */ + stat = __raw_readl(I2S_STAT(pscdata)); + if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) { + ret = au1xpsc_i2s_configure(pscdata); + if (ret) + goto out; + } + + __raw_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata)); + wmb(); /* drain writebuffer */ + __raw_writel(I2SPCR_START(stype), I2S_PCR(pscdata)); + wmb(); /* drain writebuffer */ + + /* wait for start confirmation */ + tmo = 1000000; + while (!(__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo) + tmo--; + + if (!tmo) { + __raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata)); + wmb(); /* drain writebuffer */ + ret = -ETIMEDOUT; + } +out: + return ret; +} + +static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype) +{ + unsigned long tmo, stat; + + __raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata)); + wmb(); /* drain writebuffer */ + + /* wait for stop confirmation */ + tmo = 1000000; + while ((__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo) + tmo--; + + /* if both TX and RX are idle, disable PSC */ + stat = __raw_readl(I2S_STAT(pscdata)); + if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) { + __raw_writel(0, I2S_CFG(pscdata)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata)); + wmb(); /* drain writebuffer */ + } + return 0; +} + +static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + int ret, stype = substream->stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + ret = au1xpsc_i2s_start(pscdata, stype); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + ret = au1xpsc_i2s_stop(pscdata, stype); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]); + return 0; +} + +static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { + .startup = au1xpsc_i2s_startup, + .trigger = au1xpsc_i2s_trigger, + .hw_params = au1xpsc_i2s_hw_params, + .set_fmt = au1xpsc_i2s_set_fmt, +}; + +static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = { + .playback = { + .rates = AU1XPSC_I2S_RATES, + .formats = AU1XPSC_I2S_FMTS, + .channels_min = 2, + .channels_max = 8, /* 2 without external help */ + }, + .capture = { + .rates = AU1XPSC_I2S_RATES, + .formats = AU1XPSC_I2S_FMTS, + .channels_min = 2, + .channels_max = 8, /* 2 without external help */ + }, + .ops = &au1xpsc_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver au1xpsc_i2s_component = { + .name = "au1xpsc-i2s", +}; + +static int au1xpsc_i2s_drvprobe(struct platform_device *pdev) +{ + struct resource *iores, *dmares; + unsigned long sel; + struct au1xpsc_audio_data *wd; + + wd = devm_kzalloc(&pdev->dev, sizeof(struct au1xpsc_audio_data), + GFP_KERNEL); + if (!wd) + return -ENOMEM; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wd->mmio = devm_ioremap_resource(&pdev->dev, iores); + if (IS_ERR(wd->mmio)) + return PTR_ERR(wd->mmio); + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmares) + return -EBUSY; + wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start; + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!dmares) + return -EBUSY; + wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start; + + /* preserve PSC clock source set up by platform (dev.platform_data + * is already occupied by soc layer) + */ + sel = __raw_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK; + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(wd)); + __raw_writel(0, I2S_CFG(wd)); + wmb(); /* drain writebuffer */ + + /* preconfigure: set max rx/tx fifo depths */ + wd->cfg |= PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8; + + /* don't wait for I2S core to become ready now; clocks may not + * be running yet; depending on clock input for PSC a wait might + * time out. + */ + + /* name the DAI like this device instance ("au1xpsc-i2s.PSCINDEX") */ + memcpy(&wd->dai_drv, &au1xpsc_i2s_dai_template, + sizeof(struct snd_soc_dai_driver)); + wd->dai_drv.name = dev_name(&pdev->dev); + + platform_set_drvdata(pdev, wd); + + return snd_soc_register_component(&pdev->dev, &au1xpsc_i2s_component, + &wd->dai_drv, 1); +} + +static int au1xpsc_i2s_drvremove(struct platform_device *pdev) +{ + struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + + __raw_writel(0, I2S_CFG(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd)); + wmb(); /* drain writebuffer */ + + return 0; +} + +#ifdef CONFIG_PM +static int au1xpsc_i2s_drvsuspend(struct device *dev) +{ + struct au1xpsc_audio_data *wd = dev_get_drvdata(dev); + + /* save interesting register and disable PSC */ + wd->pm[0] = __raw_readl(PSC_SEL(wd)); + + __raw_writel(0, I2S_CFG(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd)); + wmb(); /* drain writebuffer */ + + return 0; +} + +static int au1xpsc_i2s_drvresume(struct device *dev) +{ + struct au1xpsc_audio_data *wd = dev_get_drvdata(dev); + + /* select I2S mode and PSC clock */ + __raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(0, PSC_SEL(wd)); + wmb(); /* drain writebuffer */ + __raw_writel(wd->pm[0], PSC_SEL(wd)); + wmb(); /* drain writebuffer */ + + return 0; +} + +static const struct dev_pm_ops au1xpsci2s_pmops = { + .suspend = au1xpsc_i2s_drvsuspend, + .resume = au1xpsc_i2s_drvresume, +}; + +#define AU1XPSCI2S_PMOPS &au1xpsci2s_pmops + +#else + +#define AU1XPSCI2S_PMOPS NULL + +#endif + +static struct platform_driver au1xpsc_i2s_driver = { + .driver = { + .name = "au1xpsc_i2s", + .pm = AU1XPSCI2S_PMOPS, + }, + .probe = au1xpsc_i2s_drvprobe, + .remove = au1xpsc_i2s_drvremove, +}; + +module_platform_driver(au1xpsc_i2s_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h new file mode 100644 index 000000000..74dffeb64 --- /dev/null +++ b/sound/soc/au1x/psc.h @@ -0,0 +1,42 @@ +/* + * Alchemy ALSA ASoC audio support. + * + * (c) 2007-2011 MSC Vertriebsges.m.b.H., + * Manuel Lauss + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _AU1X_PCM_H +#define _AU1X_PCM_H + +struct au1xpsc_audio_data { + void __iomem *mmio; + + unsigned long cfg; + unsigned long rate; + + struct snd_soc_dai_driver dai_drv; + + unsigned long pm[2]; + struct mutex lock; + int dmaids[2]; +}; + +/* easy access macros */ +#define PSC_CTRL(x) ((x)->mmio + PSC_CTRL_OFFSET) +#define PSC_SEL(x) ((x)->mmio + PSC_SEL_OFFSET) +#define I2S_STAT(x) ((x)->mmio + PSC_I2SSTAT_OFFSET) +#define I2S_CFG(x) ((x)->mmio + PSC_I2SCFG_OFFSET) +#define I2S_PCR(x) ((x)->mmio + PSC_I2SPCR_OFFSET) +#define AC97_CFG(x) ((x)->mmio + PSC_AC97CFG_OFFSET) +#define AC97_CDC(x) ((x)->mmio + PSC_AC97CDC_OFFSET) +#define AC97_EVNT(x) ((x)->mmio + PSC_AC97EVNT_OFFSET) +#define AC97_PCR(x) ((x)->mmio + PSC_AC97PCR_OFFSET) +#define AC97_RST(x) ((x)->mmio + PSC_AC97RST_OFFSET) +#define AC97_STAT(x) ((x)->mmio + PSC_AC97STAT_OFFSET) + +#endif diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig new file mode 100644 index 000000000..02f50b7a9 --- /dev/null +++ b/sound/soc/bcm/Kconfig @@ -0,0 +1,18 @@ +config SND_BCM2835_SOC_I2S + tristate "SoC Audio support for the Broadcom BCM2835 I2S module" + depends on ARCH_BCM2835 || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to + the BCM2835 I2S interface. You will also need + to select the audio interfaces to support below. + +config SND_SOC_CYGNUS + tristate "SoC platform audio for Broadcom Cygnus chips" + depends on ARCH_BCM_CYGNUS || COMPILE_TEST + help + Say Y if you want to add support for ASoC audio on Broadcom + Cygnus chips (bcm958300, bcm958305, bcm911360) + + If you don't know what to do here, say N. diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile new file mode 100644 index 000000000..fc739d007 --- /dev/null +++ b/sound/soc/bcm/Makefile @@ -0,0 +1,10 @@ +# BCM2835 Platform Support +snd-soc-bcm2835-i2s-objs := bcm2835-i2s.o + +obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o + +# CYGNUS Platform Support +snd-soc-cygnus-objs := cygnus-pcm.o cygnus-ssp.o + +obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o + diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c new file mode 100644 index 000000000..d5f73a8ab --- /dev/null +++ b/sound/soc/bcm/bcm2835-i2s.c @@ -0,0 +1,942 @@ +/* + * ALSA SoC I2S Audio Layer for Broadcom BCM2835 SoC + * + * Author: Florian Meier + * Copyright 2013 + * + * Based on + * Raspberry Pi PCM I2S ALSA Driver + * Copyright (c) by Phil Poole 2013 + * + * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor + * Vladimir Barinov, + * Copyright (C) 2007 MontaVista Software, Inc., + * + * OMAP ALSA SoC DAI driver using McBSP port + * Copyright (C) 2008 Nokia Corporation + * Contact: Jarkko Nikula + * Peter Ujfalusi + * + * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver + * Author: Timur Tabi + * Copyright 2007-2010 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* I2S registers */ +#define BCM2835_I2S_CS_A_REG 0x00 +#define BCM2835_I2S_FIFO_A_REG 0x04 +#define BCM2835_I2S_MODE_A_REG 0x08 +#define BCM2835_I2S_RXC_A_REG 0x0c +#define BCM2835_I2S_TXC_A_REG 0x10 +#define BCM2835_I2S_DREQ_A_REG 0x14 +#define BCM2835_I2S_INTEN_A_REG 0x18 +#define BCM2835_I2S_INTSTC_A_REG 0x1c +#define BCM2835_I2S_GRAY_REG 0x20 + +/* I2S register settings */ +#define BCM2835_I2S_STBY BIT(25) +#define BCM2835_I2S_SYNC BIT(24) +#define BCM2835_I2S_RXSEX BIT(23) +#define BCM2835_I2S_RXF BIT(22) +#define BCM2835_I2S_TXE BIT(21) +#define BCM2835_I2S_RXD BIT(20) +#define BCM2835_I2S_TXD BIT(19) +#define BCM2835_I2S_RXR BIT(18) +#define BCM2835_I2S_TXW BIT(17) +#define BCM2835_I2S_CS_RXERR BIT(16) +#define BCM2835_I2S_CS_TXERR BIT(15) +#define BCM2835_I2S_RXSYNC BIT(14) +#define BCM2835_I2S_TXSYNC BIT(13) +#define BCM2835_I2S_DMAEN BIT(9) +#define BCM2835_I2S_RXTHR(v) ((v) << 7) +#define BCM2835_I2S_TXTHR(v) ((v) << 5) +#define BCM2835_I2S_RXCLR BIT(4) +#define BCM2835_I2S_TXCLR BIT(3) +#define BCM2835_I2S_TXON BIT(2) +#define BCM2835_I2S_RXON BIT(1) +#define BCM2835_I2S_EN (1) + +#define BCM2835_I2S_CLKDIS BIT(28) +#define BCM2835_I2S_PDMN BIT(27) +#define BCM2835_I2S_PDME BIT(26) +#define BCM2835_I2S_FRXP BIT(25) +#define BCM2835_I2S_FTXP BIT(24) +#define BCM2835_I2S_CLKM BIT(23) +#define BCM2835_I2S_CLKI BIT(22) +#define BCM2835_I2S_FSM BIT(21) +#define BCM2835_I2S_FSI BIT(20) +#define BCM2835_I2S_FLEN(v) ((v) << 10) +#define BCM2835_I2S_FSLEN(v) (v) + +#define BCM2835_I2S_CHWEX BIT(15) +#define BCM2835_I2S_CHEN BIT(14) +#define BCM2835_I2S_CHPOS(v) ((v) << 4) +#define BCM2835_I2S_CHWID(v) (v) +#define BCM2835_I2S_CH1(v) ((v) << 16) +#define BCM2835_I2S_CH2(v) (v) +#define BCM2835_I2S_CH1_POS(v) BCM2835_I2S_CH1(BCM2835_I2S_CHPOS(v)) +#define BCM2835_I2S_CH2_POS(v) BCM2835_I2S_CH2(BCM2835_I2S_CHPOS(v)) + +#define BCM2835_I2S_TX_PANIC(v) ((v) << 24) +#define BCM2835_I2S_RX_PANIC(v) ((v) << 16) +#define BCM2835_I2S_TX(v) ((v) << 8) +#define BCM2835_I2S_RX(v) (v) + +#define BCM2835_I2S_INT_RXERR BIT(3) +#define BCM2835_I2S_INT_TXERR BIT(2) +#define BCM2835_I2S_INT_RXR BIT(1) +#define BCM2835_I2S_INT_TXW BIT(0) + +/* Frame length register is 10 bit, maximum length 1024 */ +#define BCM2835_I2S_MAX_FRAME_LENGTH 1024 + +/* General device struct */ +struct bcm2835_i2s_dev { + struct device *dev; + struct snd_dmaengine_dai_dma_data dma_data[2]; + unsigned int fmt; + unsigned int tdm_slots; + unsigned int rx_mask; + unsigned int tx_mask; + unsigned int slot_width; + unsigned int frame_length; + + struct regmap *i2s_regmap; + struct clk *clk; + bool clk_prepared; + int clk_rate; +}; + +static void bcm2835_i2s_start_clock(struct bcm2835_i2s_dev *dev) +{ + unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK; + + if (dev->clk_prepared) + return; + + switch (master) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + clk_prepare_enable(dev->clk); + dev->clk_prepared = true; + break; + default: + break; + } +} + +static void bcm2835_i2s_stop_clock(struct bcm2835_i2s_dev *dev) +{ + if (dev->clk_prepared) + clk_disable_unprepare(dev->clk); + dev->clk_prepared = false; +} + +static void bcm2835_i2s_clear_fifos(struct bcm2835_i2s_dev *dev, + bool tx, bool rx) +{ + int timeout = 1000; + uint32_t syncval; + uint32_t csreg; + uint32_t i2s_active_state; + bool clk_was_prepared; + uint32_t off; + uint32_t clr; + + off = tx ? BCM2835_I2S_TXON : 0; + off |= rx ? BCM2835_I2S_RXON : 0; + + clr = tx ? BCM2835_I2S_TXCLR : 0; + clr |= rx ? BCM2835_I2S_RXCLR : 0; + + /* Backup the current state */ + regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg); + i2s_active_state = csreg & (BCM2835_I2S_RXON | BCM2835_I2S_TXON); + + /* Start clock if not running */ + clk_was_prepared = dev->clk_prepared; + if (!clk_was_prepared) + bcm2835_i2s_start_clock(dev); + + /* Stop I2S module */ + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, off, 0); + + /* + * Clear the FIFOs + * Requires at least 2 PCM clock cycles to take effect + */ + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, clr, clr); + + /* Wait for 2 PCM clock cycles */ + + /* + * Toggle the SYNC flag. After 2 PCM clock cycles it can be read back + * FIXME: This does not seem to work for slave mode! + */ + regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &syncval); + syncval &= BCM2835_I2S_SYNC; + + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, + BCM2835_I2S_SYNC, ~syncval); + + /* Wait for the SYNC flag changing it's state */ + while (--timeout) { + regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg); + if ((csreg & BCM2835_I2S_SYNC) != syncval) + break; + } + + if (!timeout) + dev_err(dev->dev, "I2S SYNC error!\n"); + + /* Stop clock if it was not running before */ + if (!clk_was_prepared) + bcm2835_i2s_stop_clock(dev); + + /* Restore I2S state */ + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, + BCM2835_I2S_RXON | BCM2835_I2S_TXON, i2s_active_state); +} + +static int bcm2835_i2s_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + dev->fmt = fmt; + return 0; +} + +static int bcm2835_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (!ratio) { + dev->tdm_slots = 0; + return 0; + } + + if (ratio > BCM2835_I2S_MAX_FRAME_LENGTH) + return -EINVAL; + + dev->tdm_slots = 2; + dev->rx_mask = 0x03; + dev->tx_mask = 0x03; + dev->slot_width = ratio / 2; + dev->frame_length = ratio; + + return 0; +} + +static int bcm2835_i2s_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int width) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (slots) { + if (slots < 0 || width < 0) + return -EINVAL; + + /* Limit masks to available slots */ + rx_mask &= GENMASK(slots - 1, 0); + tx_mask &= GENMASK(slots - 1, 0); + + /* + * The driver is limited to 2-channel setups. + * Check that exactly 2 bits are set in the masks. + */ + if (hweight_long((unsigned long) rx_mask) != 2 + || hweight_long((unsigned long) tx_mask) != 2) + return -EINVAL; + + if (slots * width > BCM2835_I2S_MAX_FRAME_LENGTH) + return -EINVAL; + } + + dev->tdm_slots = slots; + + dev->rx_mask = rx_mask; + dev->tx_mask = tx_mask; + dev->slot_width = width; + dev->frame_length = slots * width; + + return 0; +} + +/* + * Convert logical slot number into physical slot number. + * + * If odd_offset is 0 sequential number is identical to logical number. + * This is used for DSP modes with slot numbering 0 1 2 3 ... + * + * Otherwise odd_offset defines the physical offset for odd numbered + * slots. This is used for I2S and left/right justified modes to + * translate from logical slot numbers 0 1 2 3 ... into physical slot + * numbers 0 2 ... 3 4 ... + */ +static int bcm2835_i2s_convert_slot(unsigned int slot, unsigned int odd_offset) +{ + if (!odd_offset) + return slot; + + if (slot & 1) + return (slot >> 1) + odd_offset; + + return slot >> 1; +} + +/* + * Calculate channel position from mask and slot width. + * + * Mask must contain exactly 2 set bits. + * Lowest set bit is channel 1 position, highest set bit channel 2. + * The constant offset is added to both channel positions. + * + * If odd_offset is > 0 slot positions are translated to + * I2S-style TDM slot numbering ( 0 2 ... 3 4 ...) with odd + * logical slot numbers starting at physical slot odd_offset. + */ +static void bcm2835_i2s_calc_channel_pos( + unsigned int *ch1_pos, unsigned int *ch2_pos, + unsigned int mask, unsigned int width, + unsigned int bit_offset, unsigned int odd_offset) +{ + *ch1_pos = bcm2835_i2s_convert_slot((ffs(mask) - 1), odd_offset) + * width + bit_offset; + *ch2_pos = bcm2835_i2s_convert_slot((fls(mask) - 1), odd_offset) + * width + bit_offset; +} + +static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + unsigned int data_length, data_delay, framesync_length; + unsigned int slots, slot_width, odd_slot_offset; + int frame_length, bclk_rate; + unsigned int rx_mask, tx_mask; + unsigned int rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos; + unsigned int mode, format; + bool bit_clock_master = false; + bool frame_sync_master = false; + bool frame_start_falling_edge = false; + uint32_t csreg; + int ret = 0; + + /* + * If a stream is already enabled, + * the registers are already set properly. + */ + regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg); + + if (csreg & (BCM2835_I2S_TXON | BCM2835_I2S_RXON)) + return 0; + + data_length = params_width(params); + data_delay = 0; + odd_slot_offset = 0; + mode = 0; + + if (dev->tdm_slots) { + slots = dev->tdm_slots; + slot_width = dev->slot_width; + frame_length = dev->frame_length; + rx_mask = dev->rx_mask; + tx_mask = dev->tx_mask; + bclk_rate = dev->frame_length * params_rate(params); + } else { + slots = 2; + slot_width = params_width(params); + rx_mask = 0x03; + tx_mask = 0x03; + + frame_length = snd_soc_params_to_frame_size(params); + if (frame_length < 0) + return frame_length; + + bclk_rate = snd_soc_params_to_bclk(params); + if (bclk_rate < 0) + return bclk_rate; + } + + /* Check if data fits into slots */ + if (data_length > slot_width) + return -EINVAL; + + /* Check if CPU is bit clock master */ + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + bit_clock_master = true; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBM_CFM: + bit_clock_master = false; + break; + default: + return -EINVAL; + } + + /* Check if CPU is frame sync master */ + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBM_CFS: + frame_sync_master = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFM: + frame_sync_master = false; + break; + default: + return -EINVAL; + } + + /* Clock should only be set up here if CPU is clock master */ + if (bit_clock_master && + (!dev->clk_prepared || dev->clk_rate != bclk_rate)) { + if (dev->clk_prepared) + bcm2835_i2s_stop_clock(dev); + + if (dev->clk_rate != bclk_rate) { + ret = clk_set_rate(dev->clk, bclk_rate); + if (ret) + return ret; + dev->clk_rate = bclk_rate; + } + + bcm2835_i2s_start_clock(dev); + } + + /* Setup the frame format */ + format = BCM2835_I2S_CHEN; + + if (data_length >= 24) + format |= BCM2835_I2S_CHWEX; + + format |= BCM2835_I2S_CHWID((data_length-8)&0xf); + + /* CH2 format is the same as for CH1 */ + format = BCM2835_I2S_CH1(format) | BCM2835_I2S_CH2(format); + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* I2S mode needs an even number of slots */ + if (slots & 1) + return -EINVAL; + + /* + * Use I2S-style logical slot numbering: even slots + * are in first half of frame, odd slots in second half. + */ + odd_slot_offset = slots >> 1; + + /* MSB starts one cycle after frame start */ + data_delay = 1; + + /* Setup frame sync signal for 50% duty cycle */ + framesync_length = frame_length / 2; + frame_start_falling_edge = true; + break; + case SND_SOC_DAIFMT_LEFT_J: + if (slots & 1) + return -EINVAL; + + odd_slot_offset = slots >> 1; + data_delay = 0; + framesync_length = frame_length / 2; + frame_start_falling_edge = false; + break; + case SND_SOC_DAIFMT_RIGHT_J: + if (slots & 1) + return -EINVAL; + + /* Odd frame lengths aren't supported */ + if (frame_length & 1) + return -EINVAL; + + odd_slot_offset = slots >> 1; + data_delay = slot_width - data_length; + framesync_length = frame_length / 2; + frame_start_falling_edge = false; + break; + case SND_SOC_DAIFMT_DSP_A: + data_delay = 1; + framesync_length = 1; + frame_start_falling_edge = false; + break; + case SND_SOC_DAIFMT_DSP_B: + data_delay = 0; + framesync_length = 1; + frame_start_falling_edge = false; + break; + default: + return -EINVAL; + } + + bcm2835_i2s_calc_channel_pos(&rx_ch1_pos, &rx_ch2_pos, + rx_mask, slot_width, data_delay, odd_slot_offset); + bcm2835_i2s_calc_channel_pos(&tx_ch1_pos, &tx_ch2_pos, + tx_mask, slot_width, data_delay, odd_slot_offset); + + /* + * Transmitting data immediately after frame start, eg + * in left-justified or DSP mode A, only works stable + * if bcm2835 is the frame clock master. + */ + if ((!rx_ch1_pos || !tx_ch1_pos) && !frame_sync_master) + dev_warn(dev->dev, + "Unstable slave config detected, L/R may be swapped"); + + /* + * Set format for both streams. + * We cannot set another frame length + * (and therefore word length) anyway, + * so the format will be the same. + */ + regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG, + format + | BCM2835_I2S_CH1_POS(rx_ch1_pos) + | BCM2835_I2S_CH2_POS(rx_ch2_pos)); + regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG, + format + | BCM2835_I2S_CH1_POS(tx_ch1_pos) + | BCM2835_I2S_CH2_POS(tx_ch2_pos)); + + /* Setup the I2S mode */ + + if (data_length <= 16) { + /* + * Use frame packed mode (2 channels per 32 bit word) + * We cannot set another frame length in the second stream + * (and therefore word length) anyway, + * so the format will be the same. + */ + mode |= BCM2835_I2S_FTXP | BCM2835_I2S_FRXP; + } + + mode |= BCM2835_I2S_FLEN(frame_length - 1); + mode |= BCM2835_I2S_FSLEN(framesync_length); + + /* CLKM selects bcm2835 clock slave mode */ + if (!bit_clock_master) + mode |= BCM2835_I2S_CLKM; + + /* FSM selects bcm2835 frame sync slave mode */ + if (!frame_sync_master) + mode |= BCM2835_I2S_FSM; + + /* CLKI selects normal clocking mode, sampling on rising edge */ + switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + mode |= BCM2835_I2S_CLKI; + break; + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: + break; + default: + return -EINVAL; + } + + /* FSI selects frame start on falling edge */ + switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_IB_NF: + if (frame_start_falling_edge) + mode |= BCM2835_I2S_FSI; + break; + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + if (!frame_start_falling_edge) + mode |= BCM2835_I2S_FSI; + break; + default: + return -EINVAL; + } + + regmap_write(dev->i2s_regmap, BCM2835_I2S_MODE_A_REG, mode); + + /* Setup the DMA parameters */ + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, + BCM2835_I2S_RXTHR(1) + | BCM2835_I2S_TXTHR(1) + | BCM2835_I2S_DMAEN, 0xffffffff); + + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_DREQ_A_REG, + BCM2835_I2S_TX_PANIC(0x10) + | BCM2835_I2S_RX_PANIC(0x30) + | BCM2835_I2S_TX(0x30) + | BCM2835_I2S_RX(0x20), 0xffffffff); + + /* Clear FIFOs */ + bcm2835_i2s_clear_fifos(dev, true, true); + + dev_dbg(dev->dev, + "slots: %d width: %d rx mask: 0x%02x tx_mask: 0x%02x\n", + slots, slot_width, rx_mask, tx_mask); + + dev_dbg(dev->dev, "frame len: %d sync len: %d data len: %d\n", + frame_length, framesync_length, data_length); + + dev_dbg(dev->dev, "rx pos: %d,%d tx pos: %d,%d\n", + rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos); + + dev_dbg(dev->dev, "sampling rate: %d bclk rate: %d\n", + params_rate(params), bclk_rate); + + dev_dbg(dev->dev, "CLKM: %d CLKI: %d FSM: %d FSI: %d frame start: %s edge\n", + !!(mode & BCM2835_I2S_CLKM), + !!(mode & BCM2835_I2S_CLKI), + !!(mode & BCM2835_I2S_FSM), + !!(mode & BCM2835_I2S_FSI), + (mode & BCM2835_I2S_FSI) ? "falling" : "rising"); + + return ret; +} + +static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + uint32_t cs_reg; + + /* + * Clear both FIFOs if the one that should be started + * is not empty at the moment. This should only happen + * after overrun. Otherwise, hw_params would have cleared + * the FIFO. + */ + regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &cs_reg); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK + && !(cs_reg & BCM2835_I2S_TXE)) + bcm2835_i2s_clear_fifos(dev, true, false); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE + && (cs_reg & BCM2835_I2S_RXD)) + bcm2835_i2s_clear_fifos(dev, false, true); + + return 0; +} + +static void bcm2835_i2s_stop(struct bcm2835_i2s_dev *dev, + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + uint32_t mask; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mask = BCM2835_I2S_RXON; + else + mask = BCM2835_I2S_TXON; + + regmap_update_bits(dev->i2s_regmap, + BCM2835_I2S_CS_A_REG, mask, 0); + + /* Stop also the clock when not SND_SOC_DAIFMT_CONT */ + if (!dai->active && !(dev->fmt & SND_SOC_DAIFMT_CONT)) + bcm2835_i2s_stop_clock(dev); +} + +static int bcm2835_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + uint32_t mask; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + bcm2835_i2s_start_clock(dev); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mask = BCM2835_I2S_RXON; + else + mask = BCM2835_I2S_TXON; + + regmap_update_bits(dev->i2s_regmap, + BCM2835_I2S_CS_A_REG, mask, mask); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + bcm2835_i2s_stop(dev, substream, dai); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bcm2835_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (dai->active) + return 0; + + /* Should this still be running stop it */ + bcm2835_i2s_stop_clock(dev); + + /* Enable PCM block */ + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, + BCM2835_I2S_EN, BCM2835_I2S_EN); + + /* + * Disable STBY. + * Requires at least 4 PCM clock cycles to take effect. + */ + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, + BCM2835_I2S_STBY, BCM2835_I2S_STBY); + + return 0; +} + +static void bcm2835_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + bcm2835_i2s_stop(dev, substream, dai); + + /* If both streams are stopped, disable module and clock */ + if (dai->active) + return; + + /* Disable the module */ + regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, + BCM2835_I2S_EN, 0); + + /* + * Stopping clock is necessary, because stop does + * not stop the clock when SND_SOC_DAIFMT_CONT + */ + bcm2835_i2s_stop_clock(dev); +} + +static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .startup = bcm2835_i2s_startup, + .shutdown = bcm2835_i2s_shutdown, + .prepare = bcm2835_i2s_prepare, + .trigger = bcm2835_i2s_trigger, + .hw_params = bcm2835_i2s_hw_params, + .set_fmt = bcm2835_i2s_set_dai_fmt, + .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio, + .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot, +}; + +static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, + &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK], + &dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]); + + return 0; +} + +static struct snd_soc_dai_driver bcm2835_i2s_dai = { + .name = "bcm2835-i2s", + .probe = bcm2835_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &bcm2835_i2s_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, +}; + +static bool bcm2835_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BCM2835_I2S_CS_A_REG: + case BCM2835_I2S_FIFO_A_REG: + case BCM2835_I2S_INTSTC_A_REG: + case BCM2835_I2S_GRAY_REG: + return true; + default: + return false; + }; +} + +static bool bcm2835_i2s_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BCM2835_I2S_FIFO_A_REG: + return true; + default: + return false; + }; +} + +static const struct regmap_config bcm2835_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = BCM2835_I2S_GRAY_REG, + .precious_reg = bcm2835_i2s_precious_reg, + .volatile_reg = bcm2835_i2s_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct snd_soc_component_driver bcm2835_i2s_component = { + .name = "bcm2835-i2s-comp", +}; + +static int bcm2835_i2s_probe(struct platform_device *pdev) +{ + struct bcm2835_i2s_dev *dev; + int ret; + struct resource *mem; + void __iomem *base; + const __be32 *addr; + dma_addr_t dma_base; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), + GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* get the clock */ + dev->clk_prepared = false; + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + dev_err(&pdev->dev, "could not get clk: %ld\n", + PTR_ERR(dev->clk)); + return PTR_ERR(dev->clk); + } + + /* Request ioarea */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + dev->i2s_regmap = devm_regmap_init_mmio(&pdev->dev, base, + &bcm2835_regmap_config); + if (IS_ERR(dev->i2s_regmap)) + return PTR_ERR(dev->i2s_regmap); + + /* Set the DMA address - we have to parse DT ourselves */ + addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL); + if (!addr) { + dev_err(&pdev->dev, "could not get DMA-register address\n"); + return -EINVAL; + } + dma_base = be32_to_cpup(addr); + + dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr = + dma_base + BCM2835_I2S_FIFO_A_REG; + + dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr = + dma_base + BCM2835_I2S_FIFO_A_REG; + + /* Set the bus width */ + dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width = + DMA_SLAVE_BUSWIDTH_4_BYTES; + dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr_width = + DMA_SLAVE_BUSWIDTH_4_BYTES; + + /* Set burst */ + dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].maxburst = 2; + dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].maxburst = 2; + + /* + * Set the PACK flag to enable S16_LE support (2 S16_LE values + * packed into 32-bit transfers). + */ + dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].flags = + SND_DMAENGINE_PCM_DAI_FLAG_PACK; + dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].flags = + SND_DMAENGINE_PCM_DAI_FLAG_PACK; + + /* Store the pdev */ + dev->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, dev); + + ret = devm_snd_soc_register_component(&pdev->dev, + &bcm2835_i2s_component, &bcm2835_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id bcm2835_i2s_of_match[] = { + { .compatible = "brcm,bcm2835-i2s", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, bcm2835_i2s_of_match); + +static struct platform_driver bcm2835_i2s_driver = { + .probe = bcm2835_i2s_probe, + .driver = { + .name = "bcm2835-i2s", + .of_match_table = bcm2835_i2s_of_match, + }, +}; + +module_platform_driver(bcm2835_i2s_driver); + +MODULE_ALIAS("platform:bcm2835-i2s"); +MODULE_DESCRIPTION("BCM2835 I2S interface"); +MODULE_AUTHOR("Florian Meier "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/cygnus-pcm.c b/sound/soc/bcm/cygnus-pcm.c new file mode 100644 index 000000000..123ecf547 --- /dev/null +++ b/sound/soc/bcm/cygnus-pcm.c @@ -0,0 +1,860 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cygnus-ssp.h" + +/* Register offset needed for ASoC PCM module */ + +#define INTH_R5F_STATUS_OFFSET 0x040 +#define INTH_R5F_CLEAR_OFFSET 0x048 +#define INTH_R5F_MASK_SET_OFFSET 0x050 +#define INTH_R5F_MASK_CLEAR_OFFSET 0x054 + +#define BF_REARM_FREE_MARK_OFFSET 0x344 +#define BF_REARM_FULL_MARK_OFFSET 0x348 + +/* Ring Buffer Ctrl Regs --- Start */ +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_RDADDR_REG_BASE */ +#define SRC_RBUF_0_RDADDR_OFFSET 0x500 +#define SRC_RBUF_1_RDADDR_OFFSET 0x518 +#define SRC_RBUF_2_RDADDR_OFFSET 0x530 +#define SRC_RBUF_3_RDADDR_OFFSET 0x548 +#define SRC_RBUF_4_RDADDR_OFFSET 0x560 +#define SRC_RBUF_5_RDADDR_OFFSET 0x578 +#define SRC_RBUF_6_RDADDR_OFFSET 0x590 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_WRADDR_REG_BASE */ +#define SRC_RBUF_0_WRADDR_OFFSET 0x504 +#define SRC_RBUF_1_WRADDR_OFFSET 0x51c +#define SRC_RBUF_2_WRADDR_OFFSET 0x534 +#define SRC_RBUF_3_WRADDR_OFFSET 0x54c +#define SRC_RBUF_4_WRADDR_OFFSET 0x564 +#define SRC_RBUF_5_WRADDR_OFFSET 0x57c +#define SRC_RBUF_6_WRADDR_OFFSET 0x594 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_BASEADDR_REG_BASE */ +#define SRC_RBUF_0_BASEADDR_OFFSET 0x508 +#define SRC_RBUF_1_BASEADDR_OFFSET 0x520 +#define SRC_RBUF_2_BASEADDR_OFFSET 0x538 +#define SRC_RBUF_3_BASEADDR_OFFSET 0x550 +#define SRC_RBUF_4_BASEADDR_OFFSET 0x568 +#define SRC_RBUF_5_BASEADDR_OFFSET 0x580 +#define SRC_RBUF_6_BASEADDR_OFFSET 0x598 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_ENDADDR_REG_BASE */ +#define SRC_RBUF_0_ENDADDR_OFFSET 0x50c +#define SRC_RBUF_1_ENDADDR_OFFSET 0x524 +#define SRC_RBUF_2_ENDADDR_OFFSET 0x53c +#define SRC_RBUF_3_ENDADDR_OFFSET 0x554 +#define SRC_RBUF_4_ENDADDR_OFFSET 0x56c +#define SRC_RBUF_5_ENDADDR_OFFSET 0x584 +#define SRC_RBUF_6_ENDADDR_OFFSET 0x59c + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_FREE_MARK_REG_BASE */ +#define SRC_RBUF_0_FREE_MARK_OFFSET 0x510 +#define SRC_RBUF_1_FREE_MARK_OFFSET 0x528 +#define SRC_RBUF_2_FREE_MARK_OFFSET 0x540 +#define SRC_RBUF_3_FREE_MARK_OFFSET 0x558 +#define SRC_RBUF_4_FREE_MARK_OFFSET 0x570 +#define SRC_RBUF_5_FREE_MARK_OFFSET 0x588 +#define SRC_RBUF_6_FREE_MARK_OFFSET 0x5a0 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_RDADDR_REG_BASE */ +#define DST_RBUF_0_RDADDR_OFFSET 0x5c0 +#define DST_RBUF_1_RDADDR_OFFSET 0x5d8 +#define DST_RBUF_2_RDADDR_OFFSET 0x5f0 +#define DST_RBUF_3_RDADDR_OFFSET 0x608 +#define DST_RBUF_4_RDADDR_OFFSET 0x620 +#define DST_RBUF_5_RDADDR_OFFSET 0x638 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_WRADDR_REG_BASE */ +#define DST_RBUF_0_WRADDR_OFFSET 0x5c4 +#define DST_RBUF_1_WRADDR_OFFSET 0x5dc +#define DST_RBUF_2_WRADDR_OFFSET 0x5f4 +#define DST_RBUF_3_WRADDR_OFFSET 0x60c +#define DST_RBUF_4_WRADDR_OFFSET 0x624 +#define DST_RBUF_5_WRADDR_OFFSET 0x63c + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_BASEADDR_REG_BASE */ +#define DST_RBUF_0_BASEADDR_OFFSET 0x5c8 +#define DST_RBUF_1_BASEADDR_OFFSET 0x5e0 +#define DST_RBUF_2_BASEADDR_OFFSET 0x5f8 +#define DST_RBUF_3_BASEADDR_OFFSET 0x610 +#define DST_RBUF_4_BASEADDR_OFFSET 0x628 +#define DST_RBUF_5_BASEADDR_OFFSET 0x640 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_ENDADDR_REG_BASE */ +#define DST_RBUF_0_ENDADDR_OFFSET 0x5cc +#define DST_RBUF_1_ENDADDR_OFFSET 0x5e4 +#define DST_RBUF_2_ENDADDR_OFFSET 0x5fc +#define DST_RBUF_3_ENDADDR_OFFSET 0x614 +#define DST_RBUF_4_ENDADDR_OFFSET 0x62c +#define DST_RBUF_5_ENDADDR_OFFSET 0x644 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_FULL_MARK_REG_BASE */ +#define DST_RBUF_0_FULL_MARK_OFFSET 0x5d0 +#define DST_RBUF_1_FULL_MARK_OFFSET 0x5e8 +#define DST_RBUF_2_FULL_MARK_OFFSET 0x600 +#define DST_RBUF_3_FULL_MARK_OFFSET 0x618 +#define DST_RBUF_4_FULL_MARK_OFFSET 0x630 +#define DST_RBUF_5_FULL_MARK_OFFSET 0x648 +/* Ring Buffer Ctrl Regs --- End */ + +/* Error Status Regs --- Start */ +/* AUD_FMM_BF_ESR_ESRX_STATUS_REG_BASE */ +#define ESR0_STATUS_OFFSET 0x900 +#define ESR1_STATUS_OFFSET 0x918 +#define ESR2_STATUS_OFFSET 0x930 +#define ESR3_STATUS_OFFSET 0x948 +#define ESR4_STATUS_OFFSET 0x960 + +/* AUD_FMM_BF_ESR_ESRX_STATUS_CLEAR_REG_BASE */ +#define ESR0_STATUS_CLR_OFFSET 0x908 +#define ESR1_STATUS_CLR_OFFSET 0x920 +#define ESR2_STATUS_CLR_OFFSET 0x938 +#define ESR3_STATUS_CLR_OFFSET 0x950 +#define ESR4_STATUS_CLR_OFFSET 0x968 + +/* AUD_FMM_BF_ESR_ESRX_MASK_REG_BASE */ +#define ESR0_MASK_STATUS_OFFSET 0x90c +#define ESR1_MASK_STATUS_OFFSET 0x924 +#define ESR2_MASK_STATUS_OFFSET 0x93c +#define ESR3_MASK_STATUS_OFFSET 0x954 +#define ESR4_MASK_STATUS_OFFSET 0x96c + +/* AUD_FMM_BF_ESR_ESRX_MASK_SET_REG_BASE */ +#define ESR0_MASK_SET_OFFSET 0x910 +#define ESR1_MASK_SET_OFFSET 0x928 +#define ESR2_MASK_SET_OFFSET 0x940 +#define ESR3_MASK_SET_OFFSET 0x958 +#define ESR4_MASK_SET_OFFSET 0x970 + +/* AUD_FMM_BF_ESR_ESRX_MASK_CLEAR_REG_BASE */ +#define ESR0_MASK_CLR_OFFSET 0x914 +#define ESR1_MASK_CLR_OFFSET 0x92c +#define ESR2_MASK_CLR_OFFSET 0x944 +#define ESR3_MASK_CLR_OFFSET 0x95c +#define ESR4_MASK_CLR_OFFSET 0x974 +/* Error Status Regs --- End */ + +#define R5F_ESR0_SHIFT 0 /* esr0 = fifo underflow */ +#define R5F_ESR1_SHIFT 1 /* esr1 = ringbuf underflow */ +#define R5F_ESR2_SHIFT 2 /* esr2 = ringbuf overflow */ +#define R5F_ESR3_SHIFT 3 /* esr3 = freemark */ +#define R5F_ESR4_SHIFT 4 /* esr4 = fullmark */ + + +/* Mask for R5F register. Set all relevant interrupt for playback handler */ +#define ANY_PLAYBACK_IRQ (BIT(R5F_ESR0_SHIFT) | \ + BIT(R5F_ESR1_SHIFT) | \ + BIT(R5F_ESR3_SHIFT)) + +/* Mask for R5F register. Set all relevant interrupt for capture handler */ +#define ANY_CAPTURE_IRQ (BIT(R5F_ESR2_SHIFT) | BIT(R5F_ESR4_SHIFT)) + +/* + * PERIOD_BYTES_MIN is the number of bytes to at which the interrupt will tick. + * This number should be a multiple of 256. Minimum value is 256 + */ +#define PERIOD_BYTES_MIN 0x100 + +static const struct snd_pcm_hardware cygnus_pcm_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + + /* A period is basically an interrupt */ + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = 0x10000, + + /* period_min/max gives range of approx interrupts per buffer */ + .periods_min = 2, + .periods_max = 8, + + /* + * maximum buffer size in bytes = period_bytes_max * periods_max + * We allocate this amount of data for each enabled channel + */ + .buffer_bytes_max = 4 * 0x8000, +}; + +static u64 cygnus_dma_dmamask = DMA_BIT_MASK(32); + +static struct cygnus_aio_port *cygnus_dai_get_dma_data( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + + return snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); +} + +static void ringbuf_set_initial(void __iomem *audio_io, + struct ringbuf_regs *p_rbuf, + bool is_playback, + u32 start, + u32 periodsize, + u32 bufsize) +{ + u32 initial_rd; + u32 initial_wr; + u32 end; + u32 fmark_val; /* free or full mark */ + + p_rbuf->period_bytes = periodsize; + p_rbuf->buf_size = bufsize; + + if (is_playback) { + /* Set the pointers to indicate full (flip uppermost bit) */ + initial_rd = start; + initial_wr = initial_rd ^ BIT(31); + } else { + /* Set the pointers to indicate empty */ + initial_wr = start; + initial_rd = initial_wr; + } + + end = start + bufsize - 1; + + /* + * The interrupt will fire when free/full mark is *exceeded* + * The fmark value must be multiple of PERIOD_BYTES_MIN so set fmark + * to be PERIOD_BYTES_MIN less than the period size. + */ + fmark_val = periodsize - PERIOD_BYTES_MIN; + + writel(start, audio_io + p_rbuf->baseaddr); + writel(end, audio_io + p_rbuf->endaddr); + writel(fmark_val, audio_io + p_rbuf->fmark); + writel(initial_rd, audio_io + p_rbuf->rdaddr); + writel(initial_wr, audio_io + p_rbuf->wraddr); +} + +static int configure_ringbuf_regs(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf; + int status = 0; + + aio = cygnus_dai_get_dma_data(substream); + + /* Map the ssp portnum to a set of ring buffers. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + p_rbuf = &aio->play_rb_regs; + + switch (aio->portnum) { + case 0: + *p_rbuf = RINGBUF_REG_PLAYBACK(0); + break; + case 1: + *p_rbuf = RINGBUF_REG_PLAYBACK(2); + break; + case 2: + *p_rbuf = RINGBUF_REG_PLAYBACK(4); + break; + case 3: /* SPDIF */ + *p_rbuf = RINGBUF_REG_PLAYBACK(6); + break; + default: + status = -EINVAL; + } + } else { + p_rbuf = &aio->capture_rb_regs; + + switch (aio->portnum) { + case 0: + *p_rbuf = RINGBUF_REG_CAPTURE(0); + break; + case 1: + *p_rbuf = RINGBUF_REG_CAPTURE(2); + break; + case 2: + *p_rbuf = RINGBUF_REG_CAPTURE(4); + break; + default: + status = -EINVAL; + } + } + + return status; +} + +static struct ringbuf_regs *get_ringbuf(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_rbuf = &aio->play_rb_regs; + else + p_rbuf = &aio->capture_rb_regs; + + return p_rbuf; +} + +static void enable_intr(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + u32 clear_mask; + + aio = cygnus_dai_get_dma_data(substream); + + /* The port number maps to the bit position to be cleared */ + clear_mask = BIT(aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Clear interrupt status before enabling them */ + writel(clear_mask, aio->cygaud->audio + ESR0_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR1_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR3_STATUS_CLR_OFFSET); + /* Unmask the interrupts of the given port*/ + writel(clear_mask, aio->cygaud->audio + ESR0_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR1_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR3_MASK_CLR_OFFSET); + + writel(ANY_PLAYBACK_IRQ, + aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); + } else { + writel(clear_mask, aio->cygaud->audio + ESR2_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR4_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR2_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR4_MASK_CLR_OFFSET); + + writel(ANY_CAPTURE_IRQ, + aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); + } + +} + +static void disable_intr(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + u32 set_mask; + + aio = cygnus_dai_get_dma_data(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s on port %d\n", __func__, aio->portnum); + + /* The port number maps to the bit position to be set */ + set_mask = BIT(aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Mask the interrupts of the given port*/ + writel(set_mask, aio->cygaud->audio + ESR0_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR1_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR3_MASK_SET_OFFSET); + } else { + writel(set_mask, aio->cygaud->audio + ESR2_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR4_MASK_SET_OFFSET); + } + +} + +static int cygnus_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + enable_intr(substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + disable_intr(substream); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void cygnus_pcm_period_elapsed(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf = NULL; + u32 regval; + + aio = cygnus_dai_get_dma_data(substream); + + p_rbuf = get_ringbuf(substream); + + /* + * If free/full mark interrupt occurs, provide timestamp + * to ALSA and update appropriate idx by period_bytes + */ + snd_pcm_period_elapsed(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Set the ring buffer to full */ + regval = readl(aio->cygaud->audio + p_rbuf->rdaddr); + regval = regval ^ BIT(31); + writel(regval, aio->cygaud->audio + p_rbuf->wraddr); + } else { + /* Set the ring buffer to empty */ + regval = readl(aio->cygaud->audio + p_rbuf->wraddr); + writel(regval, aio->cygaud->audio + p_rbuf->rdaddr); + } +} + +/* + * ESR0/1/3 status Description + * 0x1 I2S0_out port caused interrupt + * 0x2 I2S1_out port caused interrupt + * 0x4 I2S2_out port caused interrupt + * 0x8 SPDIF_out port caused interrupt + */ +static void handle_playback_irq(struct cygnus_audio *cygaud) +{ + void __iomem *audio_io; + u32 port; + u32 esr_status0, esr_status1, esr_status3; + + audio_io = cygaud->audio; + + /* + * ESR status gets updates with/without interrupts enabled. + * So, check the ESR mask, which provides interrupt enable/ + * disable status and use it to determine which ESR status + * should be serviced. + */ + esr_status0 = readl(audio_io + ESR0_STATUS_OFFSET); + esr_status0 &= ~readl(audio_io + ESR0_MASK_STATUS_OFFSET); + esr_status1 = readl(audio_io + ESR1_STATUS_OFFSET); + esr_status1 &= ~readl(audio_io + ESR1_MASK_STATUS_OFFSET); + esr_status3 = readl(audio_io + ESR3_STATUS_OFFSET); + esr_status3 &= ~readl(audio_io + ESR3_MASK_STATUS_OFFSET); + + for (port = 0; port < CYGNUS_MAX_PLAYBACK_PORTS; port++) { + u32 esrmask = BIT(port); + + /* + * Ringbuffer or FIFO underflow + * If we get this interrupt then, it is also true that we have + * not yet responded to the freemark interrupt. + * Log a debug message. The freemark handler below will + * handle getting everything going again. + */ + if ((esrmask & esr_status1) || (esrmask & esr_status0)) { + dev_dbg(cygaud->dev, + "Underrun: esr0=0x%x, esr1=0x%x esr3=0x%x\n", + esr_status0, esr_status1, esr_status3); + } + + /* + * Freemark is hit. This is the normal interrupt. + * In typical operation the read and write regs will be equal + */ + if (esrmask & esr_status3) { + struct snd_pcm_substream *playstr; + + playstr = cygaud->portinfo[port].play_stream; + cygnus_pcm_period_elapsed(playstr); + } + } + + /* Clear ESR interrupt */ + writel(esr_status0, audio_io + ESR0_STATUS_CLR_OFFSET); + writel(esr_status1, audio_io + ESR1_STATUS_CLR_OFFSET); + writel(esr_status3, audio_io + ESR3_STATUS_CLR_OFFSET); + /* Rearm freemark logic by writing 1 to the correct bit */ + writel(esr_status3, audio_io + BF_REARM_FREE_MARK_OFFSET); +} + +/* + * ESR2/4 status Description + * 0x1 I2S0_in port caused interrupt + * 0x2 I2S1_in port caused interrupt + * 0x4 I2S2_in port caused interrupt + */ +static void handle_capture_irq(struct cygnus_audio *cygaud) +{ + void __iomem *audio_io; + u32 port; + u32 esr_status2, esr_status4; + + audio_io = cygaud->audio; + + /* + * ESR status gets updates with/without interrupts enabled. + * So, check the ESR mask, which provides interrupt enable/ + * disable status and use it to determine which ESR status + * should be serviced. + */ + esr_status2 = readl(audio_io + ESR2_STATUS_OFFSET); + esr_status2 &= ~readl(audio_io + ESR2_MASK_STATUS_OFFSET); + esr_status4 = readl(audio_io + ESR4_STATUS_OFFSET); + esr_status4 &= ~readl(audio_io + ESR4_MASK_STATUS_OFFSET); + + for (port = 0; port < CYGNUS_MAX_CAPTURE_PORTS; port++) { + u32 esrmask = BIT(port); + + /* + * Ringbuffer or FIFO overflow + * If we get this interrupt then, it is also true that we have + * not yet responded to the fullmark interrupt. + * Log a debug message. The fullmark handler below will + * handle getting everything going again. + */ + if (esrmask & esr_status2) + dev_dbg(cygaud->dev, + "Overflow: esr2=0x%x\n", esr_status2); + + if (esrmask & esr_status4) { + struct snd_pcm_substream *capstr; + + capstr = cygaud->portinfo[port].capture_stream; + cygnus_pcm_period_elapsed(capstr); + } + } + + writel(esr_status2, audio_io + ESR2_STATUS_CLR_OFFSET); + writel(esr_status4, audio_io + ESR4_STATUS_CLR_OFFSET); + /* Rearm fullmark logic by writing 1 to the correct bit */ + writel(esr_status4, audio_io + BF_REARM_FULL_MARK_OFFSET); +} + +static irqreturn_t cygnus_dma_irq(int irq, void *data) +{ + u32 r5_status; + struct cygnus_audio *cygaud = data; + + /* + * R5 status bits Description + * 0 ESR0 (playback FIFO interrupt) + * 1 ESR1 (playback rbuf interrupt) + * 2 ESR2 (capture rbuf interrupt) + * 3 ESR3 (Freemark play. interrupt) + * 4 ESR4 (Fullmark capt. interrupt) + */ + r5_status = readl(cygaud->audio + INTH_R5F_STATUS_OFFSET); + + if (!(r5_status & (ANY_PLAYBACK_IRQ | ANY_CAPTURE_IRQ))) + return IRQ_NONE; + + /* If playback interrupt happened */ + if (ANY_PLAYBACK_IRQ & r5_status) { + handle_playback_irq(cygaud); + writel(ANY_PLAYBACK_IRQ & r5_status, + cygaud->audio + INTH_R5F_CLEAR_OFFSET); + } + + /* If capture interrupt happened */ + if (ANY_CAPTURE_IRQ & r5_status) { + handle_capture_irq(cygaud); + writel(ANY_CAPTURE_IRQ & r5_status, + cygaud->audio + INTH_R5F_CLEAR_OFFSET); + } + + return IRQ_HANDLED; +} + +static int cygnus_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + int ret; + + aio = cygnus_dai_get_dma_data(substream); + if (!aio) + return -ENODEV; + + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_soc_set_runtime_hwparams(substream, &cygnus_pcm_hw); + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, PERIOD_BYTES_MIN); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, PERIOD_BYTES_MIN); + if (ret < 0) + return ret; + /* + * Keep track of which substream belongs to which port. + * This info is needed by snd_pcm_period_elapsed() in irq_handler + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->play_stream = substream; + else + aio->capture_stream = substream; + + return 0; +} + +static int cygnus_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + + aio = cygnus_dai_get_dma_data(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->play_stream = NULL; + else + aio->capture_stream = NULL; + + if (!aio->play_stream && !aio->capture_stream) + dev_dbg(rtd->cpu_dai->dev, "freed port %d\n", aio->portnum); + + return 0; +} + +static int cygnus_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + int ret = 0; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + return ret; +} + +static int cygnus_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int cygnus_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + unsigned long bufsize, periodsize; + int ret = 0; + bool is_play; + u32 start; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + bufsize = snd_pcm_lib_buffer_bytes(substream); + periodsize = snd_pcm_lib_period_bytes(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s (buf_size %lu) (period_size %lu)\n", + __func__, bufsize, periodsize); + + configure_ringbuf_regs(substream); + + p_rbuf = get_ringbuf(substream); + + start = runtime->dma_addr; + + is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0; + + ringbuf_set_initial(aio->cygaud->audio, p_rbuf, is_play, start, + periodsize, bufsize); + + return ret; +} + +static snd_pcm_uframes_t cygnus_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + unsigned int res = 0, cur = 0, base = 0; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + + /* + * Get the offset of the current read (for playack) or write + * index (for capture). Report this value back to the asoc framework. + */ + p_rbuf = get_ringbuf(substream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cur = readl(aio->cygaud->audio + p_rbuf->rdaddr); + else + cur = readl(aio->cygaud->audio + p_rbuf->wraddr); + + base = readl(aio->cygaud->audio + p_rbuf->baseaddr); + + /* + * Mask off the MSB of the rdaddr,wraddr and baseaddr + * since MSB is not part of the address + */ + res = (cur & 0x7fffffff) - (base & 0x7fffffff); + + return bytes_to_frames(substream->runtime, res); +} + +static int cygnus_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + + size = cygnus_pcm_hw.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + + dev_dbg(rtd->cpu_dai->dev, "%s: size 0x%zx @ %pK\n", + __func__, size, buf->area); + + if (!buf->area) { + dev_err(rtd->cpu_dai->dev, "%s: dma_alloc failed\n", __func__); + return -ENOMEM; + } + buf->bytes = size; + + return 0; +} + + +static const struct snd_pcm_ops cygnus_pcm_ops = { + .open = cygnus_pcm_open, + .close = cygnus_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = cygnus_pcm_hw_params, + .hw_free = cygnus_pcm_hw_free, + .prepare = cygnus_pcm_prepare, + .trigger = cygnus_pcm_trigger, + .pointer = cygnus_pcm_pointer, +}; + +static void cygnus_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + buf = &substream->dma_buffer; + if (buf->area) { + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } + } + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + buf = &substream->dma_buffer; + if (buf->area) { + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } + } +} + +static int cygnus_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &cygnus_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = cygnus_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = cygnus_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) { + cygnus_dma_free_dma_buffers(pcm); + return ret; + } + } + + return 0; +} + +static struct snd_soc_component_driver cygnus_soc_platform = { + .ops = &cygnus_pcm_ops, + .pcm_new = cygnus_dma_new, + .pcm_free = cygnus_dma_free_dma_buffers, +}; + +int cygnus_soc_platform_register(struct device *dev, + struct cygnus_audio *cygaud) +{ + int rc = 0; + + dev_dbg(dev, "%s Enter\n", __func__); + + rc = devm_request_irq(dev, cygaud->irq_num, cygnus_dma_irq, + IRQF_SHARED, "cygnus-audio", cygaud); + if (rc) { + dev_err(dev, "%s request_irq error %d\n", __func__, rc); + return rc; + } + + rc = devm_snd_soc_register_component(dev, &cygnus_soc_platform, + NULL, 0); + if (rc) { + dev_err(dev, "%s failed\n", __func__); + return rc; + } + + return 0; +} + +int cygnus_soc_platform_unregister(struct device *dev) +{ + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Cygnus ASoC PCM module"); diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c new file mode 100644 index 000000000..b733f1446 --- /dev/null +++ b/sound/soc/bcm/cygnus-ssp.c @@ -0,0 +1,1398 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cygnus-ssp.h" + +#define DEFAULT_VCO 1354750204 + +#define CAPTURE_FCI_ID_BASE 0x180 +#define CYGNUS_SSP_TRISTATE_MASK 0x001fff +#define CYGNUS_PLLCLKSEL_MASK 0xf + +/* Used with stream_on field to indicate which streams are active */ +#define PLAYBACK_STREAM_MASK BIT(0) +#define CAPTURE_STREAM_MASK BIT(1) + +#define I2S_STREAM_CFG_MASK 0xff003ff +#define I2S_CAP_STREAM_CFG_MASK 0xf0 +#define SPDIF_STREAM_CFG_MASK 0x3ff +#define CH_GRP_STEREO 0x1 + +/* Begin register offset defines */ +#define AUD_MISC_SEROUT_OE_REG_BASE 0x01c +#define AUD_MISC_SEROUT_SPDIF_OE 12 +#define AUD_MISC_SEROUT_MCLK_OE 3 +#define AUD_MISC_SEROUT_LRCK_OE 2 +#define AUD_MISC_SEROUT_SCLK_OE 1 +#define AUD_MISC_SEROUT_SDAT_OE 0 + +/* AUD_FMM_BF_CTRL_xxx regs */ +#define BF_DST_CFG0_OFFSET 0x100 +#define BF_DST_CFG1_OFFSET 0x104 +#define BF_DST_CFG2_OFFSET 0x108 + +#define BF_DST_CTRL0_OFFSET 0x130 +#define BF_DST_CTRL1_OFFSET 0x134 +#define BF_DST_CTRL2_OFFSET 0x138 + +#define BF_SRC_CFG0_OFFSET 0x148 +#define BF_SRC_CFG1_OFFSET 0x14c +#define BF_SRC_CFG2_OFFSET 0x150 +#define BF_SRC_CFG3_OFFSET 0x154 + +#define BF_SRC_CTRL0_OFFSET 0x1c0 +#define BF_SRC_CTRL1_OFFSET 0x1c4 +#define BF_SRC_CTRL2_OFFSET 0x1c8 +#define BF_SRC_CTRL3_OFFSET 0x1cc + +#define BF_SRC_GRP0_OFFSET 0x1fc +#define BF_SRC_GRP1_OFFSET 0x200 +#define BF_SRC_GRP2_OFFSET 0x204 +#define BF_SRC_GRP3_OFFSET 0x208 + +#define BF_SRC_GRP_EN_OFFSET 0x320 +#define BF_SRC_GRP_FLOWON_OFFSET 0x324 +#define BF_SRC_GRP_SYNC_DIS_OFFSET 0x328 + +/* AUD_FMM_IOP_OUT_I2S_xxx regs */ +#define OUT_I2S_0_STREAM_CFG_OFFSET 0xa00 +#define OUT_I2S_0_CFG_OFFSET 0xa04 +#define OUT_I2S_0_MCLK_CFG_OFFSET 0xa0c + +#define OUT_I2S_1_STREAM_CFG_OFFSET 0xa40 +#define OUT_I2S_1_CFG_OFFSET 0xa44 +#define OUT_I2S_1_MCLK_CFG_OFFSET 0xa4c + +#define OUT_I2S_2_STREAM_CFG_OFFSET 0xa80 +#define OUT_I2S_2_CFG_OFFSET 0xa84 +#define OUT_I2S_2_MCLK_CFG_OFFSET 0xa8c + +/* AUD_FMM_IOP_OUT_SPDIF_xxx regs */ +#define SPDIF_STREAM_CFG_OFFSET 0xac0 +#define SPDIF_CTRL_OFFSET 0xac4 +#define SPDIF_FORMAT_CFG_OFFSET 0xad8 +#define SPDIF_MCLK_CFG_OFFSET 0xadc + +/* AUD_FMM_IOP_PLL_0_xxx regs */ +#define IOP_PLL_0_MACRO_OFFSET 0xb00 +#define IOP_PLL_0_MDIV_Ch0_OFFSET 0xb14 +#define IOP_PLL_0_MDIV_Ch1_OFFSET 0xb18 +#define IOP_PLL_0_MDIV_Ch2_OFFSET 0xb1c + +#define IOP_PLL_0_ACTIVE_MDIV_Ch0_OFFSET 0xb30 +#define IOP_PLL_0_ACTIVE_MDIV_Ch1_OFFSET 0xb34 +#define IOP_PLL_0_ACTIVE_MDIV_Ch2_OFFSET 0xb38 + +/* AUD_FMM_IOP_xxx regs */ +#define IOP_PLL_0_CONTROL_OFFSET 0xb04 +#define IOP_PLL_0_USER_NDIV_OFFSET 0xb08 +#define IOP_PLL_0_ACTIVE_NDIV_OFFSET 0xb20 +#define IOP_PLL_0_RESET_OFFSET 0xb5c + +/* AUD_FMM_IOP_IN_I2S_xxx regs */ +#define IN_I2S_0_STREAM_CFG_OFFSET 0x00 +#define IN_I2S_0_CFG_OFFSET 0x04 +#define IN_I2S_1_STREAM_CFG_OFFSET 0x40 +#define IN_I2S_1_CFG_OFFSET 0x44 +#define IN_I2S_2_STREAM_CFG_OFFSET 0x80 +#define IN_I2S_2_CFG_OFFSET 0x84 + +/* AUD_FMM_IOP_MISC_xxx regs */ +#define IOP_SW_INIT_LOGIC 0x1c0 + +/* End register offset defines */ + + +/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_0_REG */ +#define I2S_OUT_MCLKRATE_SHIFT 16 + +/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_REG */ +#define I2S_OUT_PLLCLKSEL_SHIFT 0 + +/* AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG */ +#define I2S_OUT_STREAM_ENA 31 +#define I2S_OUT_STREAM_CFG_GROUP_ID 20 +#define I2S_OUT_STREAM_CFG_CHANNEL_GROUPING 24 + +/* AUD_FMM_IOP_IN_I2S_x_CAP */ +#define I2S_IN_STREAM_CFG_CAP_ENA 31 +#define I2S_IN_STREAM_CFG_0_GROUP_ID 4 + +/* AUD_FMM_IOP_OUT_I2S_x_I2S_CFG_REG */ +#define I2S_OUT_CFGX_CLK_ENA 0 +#define I2S_OUT_CFGX_DATA_ENABLE 1 +#define I2S_OUT_CFGX_DATA_ALIGNMENT 6 +#define I2S_OUT_CFGX_BITS_PER_SLOT 13 +#define I2S_OUT_CFGX_VALID_SLOT 14 +#define I2S_OUT_CFGX_FSYNC_WIDTH 18 +#define I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32 26 +#define I2S_OUT_CFGX_SLAVE_MODE 30 +#define I2S_OUT_CFGX_TDM_MODE 31 + +/* AUD_FMM_BF_CTRL_SOURCECH_CFGx_REG */ +#define BF_SRC_CFGX_SFIFO_ENA 0 +#define BF_SRC_CFGX_BUFFER_PAIR_ENABLE 1 +#define BF_SRC_CFGX_SAMPLE_CH_MODE 2 +#define BF_SRC_CFGX_SFIFO_SZ_DOUBLE 5 +#define BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY 10 +#define BF_SRC_CFGX_BIT_RES 20 +#define BF_SRC_CFGX_PROCESS_SEQ_ID_VALID 31 + +/* AUD_FMM_BF_CTRL_DESTCH_CFGx_REG */ +#define BF_DST_CFGX_CAP_ENA 0 +#define BF_DST_CFGX_BUFFER_PAIR_ENABLE 1 +#define BF_DST_CFGX_DFIFO_SZ_DOUBLE 2 +#define BF_DST_CFGX_NOT_PAUSE_WHEN_FULL 11 +#define BF_DST_CFGX_FCI_ID 12 +#define BF_DST_CFGX_CAP_MODE 24 +#define BF_DST_CFGX_PROC_SEQ_ID_VALID 31 + +/* AUD_FMM_IOP_OUT_SPDIF_xxx */ +#define SPDIF_0_OUT_DITHER_ENA 3 +#define SPDIF_0_OUT_STREAM_ENA 31 + +/* AUD_FMM_IOP_PLL_0_USER */ +#define IOP_PLL_0_USER_NDIV_FRAC 10 + +/* AUD_FMM_IOP_PLL_0_ACTIVE */ +#define IOP_PLL_0_ACTIVE_NDIV_FRAC 10 + + +#define INIT_SSP_REGS(num) (struct cygnus_ssp_regs){ \ + .i2s_stream_cfg = OUT_I2S_ ##num## _STREAM_CFG_OFFSET, \ + .i2s_cap_stream_cfg = IN_I2S_ ##num## _STREAM_CFG_OFFSET, \ + .i2s_cfg = OUT_I2S_ ##num## _CFG_OFFSET, \ + .i2s_cap_cfg = IN_I2S_ ##num## _CFG_OFFSET, \ + .i2s_mclk_cfg = OUT_I2S_ ##num## _MCLK_CFG_OFFSET, \ + .bf_destch_ctrl = BF_DST_CTRL ##num## _OFFSET, \ + .bf_destch_cfg = BF_DST_CFG ##num## _OFFSET, \ + .bf_sourcech_ctrl = BF_SRC_CTRL ##num## _OFFSET, \ + .bf_sourcech_cfg = BF_SRC_CFG ##num## _OFFSET, \ + .bf_sourcech_grp = BF_SRC_GRP ##num## _OFFSET \ +} + +struct pll_macro_entry { + u32 mclk; + u32 pll_ch_num; +}; + +/* + * PLL has 3 output channels (1x, 2x, and 4x). Below are + * the common MCLK frequencies used by audio driver + */ +static const struct pll_macro_entry pll_predef_mclk[] = { + { 4096000, 0}, + { 8192000, 1}, + {16384000, 2}, + + { 5644800, 0}, + {11289600, 1}, + {22579200, 2}, + + { 6144000, 0}, + {12288000, 1}, + {24576000, 2}, + + {12288000, 0}, + {24576000, 1}, + {49152000, 2}, + + {22579200, 0}, + {45158400, 1}, + {90316800, 2}, + + {24576000, 0}, + {49152000, 1}, + {98304000, 2}, +}; + +#define CYGNUS_RATE_MIN 8000 +#define CYGNUS_RATE_MAX 384000 + +/* List of valid frame sizes for tdm mode */ +static const int ssp_valid_tdm_framesize[] = {32, 64, 128, 256, 512}; + +static const unsigned int cygnus_rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, + 88200, 96000, 176400, 192000, 352800, 384000 +}; + +static const struct snd_pcm_hw_constraint_list cygnus_rate_constraint = { + .count = ARRAY_SIZE(cygnus_rates), + .list = cygnus_rates, +}; + +static struct cygnus_aio_port *cygnus_dai_get_portinfo(struct snd_soc_dai *dai) +{ + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + return &cygaud->portinfo[dai->id]; +} + +static int audio_ssp_init_portregs(struct cygnus_aio_port *aio) +{ + u32 value, fci_id; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value &= ~I2S_STREAM_CFG_MASK; + + /* Set Group ID */ + writel(aio->portnum, + aio->cygaud->audio + aio->regs.bf_sourcech_grp); + + /* Configure the AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG reg */ + value |= aio->portnum << I2S_OUT_STREAM_CFG_GROUP_ID; + value |= aio->portnum; /* FCI ID is the port num */ + value |= CH_GRP_STEREO << I2S_OUT_STREAM_CFG_CHANNEL_GROUPING; + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + /* Configure the AUD_FMM_BF_CTRL_SOURCECH_CFGX reg */ + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY); + value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE); + value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* Configure the AUD_FMM_IOP_IN_I2S_x_CAP_STREAM_CFG_0 reg */ + value = readl(aio->cygaud->i2s_in + + aio->regs.i2s_cap_stream_cfg); + value &= ~I2S_CAP_STREAM_CFG_MASK; + value |= aio->portnum << I2S_IN_STREAM_CFG_0_GROUP_ID; + writel(value, aio->cygaud->i2s_in + + aio->regs.i2s_cap_stream_cfg); + + /* Configure the AUD_FMM_BF_CTRL_DESTCH_CFGX_REG_BASE reg */ + fci_id = CAPTURE_FCI_ID_BASE + aio->portnum; + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_DFIFO_SZ_DOUBLE); + value &= ~BIT(BF_DST_CFGX_NOT_PAUSE_WHEN_FULL); + value |= (fci_id << BF_DST_CFGX_FCI_ID); + value |= BIT(BF_DST_CFGX_PROC_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); + + /* Enable the transmit pin for this port */ + value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + value &= ~BIT((aio->portnum * 4) + AUD_MISC_SEROUT_SDAT_OE); + writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + break; + case PORT_SPDIF: + writel(aio->portnum, aio->cygaud->audio + BF_SRC_GRP3_OFFSET); + + value = readl(aio->cygaud->audio + SPDIF_CTRL_OFFSET); + value |= BIT(SPDIF_0_OUT_DITHER_ENA); + writel(value, aio->cygaud->audio + SPDIF_CTRL_OFFSET); + + /* Enable and set the FCI ID for the SPDIF channel */ + value = readl(aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET); + value &= ~SPDIF_STREAM_CFG_MASK; + value |= aio->portnum; /* FCI ID is the port num */ + value |= BIT(SPDIF_0_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY); + value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE); + value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* Enable the spdif output pin */ + value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + value &= ~BIT(AUD_MISC_SEROUT_SPDIF_OE); + writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + break; + default: + dev_err(aio->cygaud->dev, "Port not supported\n"); + status = -EINVAL; + } + + return status; +} + +static void audio_ssp_in_enable(struct cygnus_aio_port *aio) +{ + u32 value; + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_CAP_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); + + writel(0x1, aio->cygaud->audio + aio->regs.bf_destch_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value |= BIT(I2S_OUT_CFGX_CLK_ENA); + value |= BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + value |= BIT(I2S_IN_STREAM_CFG_CAP_ENA); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + + aio->streams_on |= CAPTURE_STREAM_MASK; +} + +static void audio_ssp_in_disable(struct cygnus_aio_port *aio) +{ + u32 value; + + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + value &= ~BIT(I2S_IN_STREAM_CFG_CAP_ENA); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + + aio->streams_on &= ~CAPTURE_STREAM_MASK; + + /* If both playback and capture are off */ + if (!aio->streams_on) { + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~BIT(I2S_OUT_CFGX_CLK_ENA); + value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + } + + writel(0x0, aio->cygaud->audio + aio->regs.bf_destch_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value &= ~BIT(BF_DST_CFGX_CAP_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); +} + +static int audio_ssp_out_enable(struct cygnus_aio_port *aio) +{ + u32 value; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value |= BIT(I2S_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value |= BIT(I2S_OUT_CFGX_CLK_ENA); + value |= BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value |= BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + aio->streams_on |= PLAYBACK_STREAM_MASK; + break; + case PORT_SPDIF: + value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + value |= 0x3; + writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + + writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value |= BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + break; + default: + dev_err(aio->cygaud->dev, + "Port not supported %d\n", aio->portnum); + status = -EINVAL; + } + + return status; +} + +static int audio_ssp_out_disable(struct cygnus_aio_port *aio) +{ + u32 value; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + aio->streams_on &= ~PLAYBACK_STREAM_MASK; + + /* If both playback and capture are off */ + if (!aio->streams_on) { + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~BIT(I2S_OUT_CFGX_CLK_ENA); + value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + } + + /* set group_sync_dis = 1 */ + value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + value |= BIT(aio->portnum); + writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + + writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* set group_sync_dis = 0 */ + value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + value &= ~BIT(aio->portnum); + writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value &= ~BIT(I2S_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + /* IOP SW INIT on OUT_I2S_x */ + value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + value |= BIT(aio->portnum); + writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + value &= ~BIT(aio->portnum); + writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + break; + case PORT_SPDIF: + value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + value &= ~0x3; + writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + break; + default: + dev_err(aio->cygaud->dev, + "Port not supported %d\n", aio->portnum); + status = -EINVAL; + } + + return status; +} + +static int pll_configure_mclk(struct cygnus_audio *cygaud, u32 mclk, + struct cygnus_aio_port *aio) +{ + int i = 0, error; + bool found = false; + const struct pll_macro_entry *p_entry; + struct clk *ch_clk; + + for (i = 0; i < ARRAY_SIZE(pll_predef_mclk); i++) { + p_entry = &pll_predef_mclk[i]; + if (p_entry->mclk == mclk) { + found = true; + break; + } + } + if (!found) { + dev_err(cygaud->dev, + "%s No valid mclk freq (%u) found!\n", __func__, mclk); + return -EINVAL; + } + + ch_clk = cygaud->audio_clk[p_entry->pll_ch_num]; + + if ((aio->clk_trace.cap_en) && (!aio->clk_trace.cap_clk_en)) { + error = clk_prepare_enable(ch_clk); + if (error) { + dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", + __func__, error); + return error; + } + aio->clk_trace.cap_clk_en = true; + } + + if ((aio->clk_trace.play_en) && (!aio->clk_trace.play_clk_en)) { + error = clk_prepare_enable(ch_clk); + if (error) { + dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", + __func__, error); + return error; + } + aio->clk_trace.play_clk_en = true; + } + + error = clk_set_rate(ch_clk, mclk); + if (error) { + dev_err(cygaud->dev, "%s Set MCLK rate failed: %d\n", + __func__, error); + return error; + } + + return p_entry->pll_ch_num; +} + +static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio) +{ + u32 value; + u32 mask = 0xf; + u32 sclk; + u32 mclk_rate; + unsigned int bit_rate; + unsigned int ratio; + + bit_rate = aio->bit_per_frame * aio->lrclk; + + /* + * Check if the bit clock can be generated from the given MCLK. + * MCLK must be a perfect multiple of bit clock and must be one of the + * following values... (2,4,6,8,10,12,14) + */ + if ((aio->mclk % bit_rate) != 0) + return -EINVAL; + + ratio = aio->mclk / bit_rate; + switch (ratio) { + case 2: + case 4: + case 6: + case 8: + case 10: + case 12: + case 14: + mclk_rate = ratio / 2; + break; + + default: + dev_err(aio->cygaud->dev, + "Invalid combination of MCLK and BCLK\n"); + dev_err(aio->cygaud->dev, "lrclk = %u, bits/frame = %u, mclk = %u\n", + aio->lrclk, aio->bit_per_frame, aio->mclk); + return -EINVAL; + } + + /* Set sclk rate */ + switch (aio->port_type) { + case PORT_TDM: + sclk = aio->bit_per_frame; + if (sclk == 512) + sclk = 0; + + /* sclks_per_1fs_div = sclk cycles/32 */ + sclk /= 32; + + /* Set number of bitclks per frame */ + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~(mask << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32); + value |= sclk << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32; + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + dev_dbg(aio->cygaud->dev, + "SCLKS_PER_1FS_DIV32 = 0x%x\n", value); + break; + case PORT_SPDIF: + break; + default: + dev_err(aio->cygaud->dev, "Unknown port type\n"); + return -EINVAL; + } + + /* Set MCLK_RATE ssp port (spdif and ssp are the same) */ + value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + value &= ~(0xf << I2S_OUT_MCLKRATE_SHIFT); + value |= (mclk_rate << I2S_OUT_MCLKRATE_SHIFT); + writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + + dev_dbg(aio->cygaud->dev, "mclk cfg reg = 0x%x\n", value); + dev_dbg(aio->cygaud->dev, "bits per frame = %u, mclk = %u Hz, lrclk = %u Hz\n", + aio->bit_per_frame, aio->mclk, aio->lrclk); + return 0; +} + +static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + int rate, bitres; + u32 value; + u32 mask = 0x1f; + int ret = 0; + + dev_dbg(aio->cygaud->dev, "%s port = %d\n", __func__, aio->portnum); + dev_dbg(aio->cygaud->dev, "params_channels %d\n", + params_channels(params)); + dev_dbg(aio->cygaud->dev, "rate %d\n", params_rate(params)); + dev_dbg(aio->cygaud->dev, "format %d\n", params_format(params)); + + rate = params_rate(params); + + switch (aio->mode) { + case CYGNUS_SSPMODE_TDM: + if ((rate == 192000) && (params_channels(params) > 4)) { + dev_err(aio->cygaud->dev, "Cannot run %d channels at %dHz\n", + params_channels(params), rate); + return -EINVAL; + } + break; + case CYGNUS_SSPMODE_I2S: + aio->bit_per_frame = 64; /* I2S must be 64 bit per frame */ + break; + default: + dev_err(aio->cygaud->dev, + "%s port running in unknown mode\n", __func__); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_BUFFER_PAIR_ENABLE); + value &= ~BIT(BF_SRC_CFGX_SAMPLE_CH_MODE); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bitres = 16; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + /* 32 bit mode is coded as 0 */ + bitres = 0; + break; + + default: + return -EINVAL; + } + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~(mask << BF_SRC_CFGX_BIT_RES); + value |= (bitres << BF_SRC_CFGX_BIT_RES); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + } else { + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + value = readl(aio->cygaud->audio + + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_CAP_MODE); + writel(value, aio->cygaud->audio + + aio->regs.bf_destch_cfg); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + value = readl(aio->cygaud->audio + + aio->regs.bf_destch_cfg); + value &= ~BIT(BF_DST_CFGX_CAP_MODE); + writel(value, aio->cygaud->audio + + aio->regs.bf_destch_cfg); + break; + + default: + return -EINVAL; + } + } + + aio->lrclk = rate; + + if (!aio->is_slave) + ret = cygnus_ssp_set_clocks(aio); + + return ret; +} + +/* + * This function sets the mclk frequency for pll clock + */ +static int cygnus_ssp_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + int sel; + u32 value; + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + dev_dbg(aio->cygaud->dev, + "%s Enter port = %d\n", __func__, aio->portnum); + sel = pll_configure_mclk(cygaud, freq, aio); + if (sel < 0) { + dev_err(aio->cygaud->dev, + "%s Setting mclk failed.\n", __func__); + return -EINVAL; + } + + aio->mclk = freq; + + dev_dbg(aio->cygaud->dev, "%s Setting MCLKSEL to %d\n", __func__, sel); + value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + value &= ~(0xf << I2S_OUT_PLLCLKSEL_SHIFT); + value |= (sel << I2S_OUT_PLLCLKSEL_SHIFT); + writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + + return 0; +} + +static int cygnus_ssp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + + snd_soc_dai_set_dma_data(dai, substream, aio); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->clk_trace.play_en = true; + else + aio->clk_trace.cap_en = true; + + substream->runtime->hw.rate_min = CYGNUS_RATE_MIN; + substream->runtime->hw.rate_max = CYGNUS_RATE_MAX; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cygnus_rate_constraint); + return 0; +} + +static void cygnus_ssp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->clk_trace.play_en = false; + else + aio->clk_trace.cap_en = false; + + if (!aio->is_slave) { + u32 val; + + val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + val &= CYGNUS_PLLCLKSEL_MASK; + if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { + dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", + val); + return; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (aio->clk_trace.play_clk_en) { + clk_disable_unprepare(aio->cygaud-> + audio_clk[val]); + aio->clk_trace.play_clk_en = false; + } + } else { + if (aio->clk_trace.cap_clk_en) { + clk_disable_unprepare(aio->cygaud-> + audio_clk[val]); + aio->clk_trace.cap_clk_en = false; + } + } + } +} + +/* + * Bit Update Notes + * 31 Yes TDM Mode (1 = TDM, 0 = i2s) + * 30 Yes Slave Mode (1 = Slave, 0 = Master) + * 29:26 No Sclks per frame + * 25:18 Yes FS Width + * 17:14 No Valid Slots + * 13 No Bits (1 = 16 bits, 0 = 32 bits) + * 12:08 No Bits per samp + * 07 Yes Justifcation (1 = LSB, 0 = MSB) + * 06 Yes Alignment (1 = Delay 1 clk, 0 = no delay + * 05 Yes SCLK polarity (1 = Rising, 0 = Falling) + * 04 Yes LRCLK Polarity (1 = High for left, 0 = Low for left) + * 03:02 Yes Reserved - write as zero + * 01 No Data Enable + * 00 No CLK Enable + */ +#define I2S_OUT_CFG_REG_UPDATE_MASK 0x3C03FF03 + +/* Input cfg is same as output, but the FS width is not a valid field */ +#define I2S_IN_CFG_REG_UPDATE_MASK (I2S_OUT_CFG_REG_UPDATE_MASK | 0x03FC0000) + +int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, int len) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + + if ((len > 0) && (len < 256)) { + aio->fsync_width = len; + return 0; + } else { + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(cygnus_ssp_set_custom_fsync_width); + +static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + u32 ssp_curcfg; + u32 ssp_newcfg; + u32 ssp_outcfg; + u32 ssp_incfg; + u32 val; + u32 mask; + + dev_dbg(aio->cygaud->dev, "%s Enter fmt: %x\n", __func__, fmt); + + if (aio->port_type == PORT_SPDIF) + return -EINVAL; + + ssp_newcfg = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ssp_newcfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE); + aio->is_slave = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssp_newcfg &= ~BIT(I2S_OUT_CFGX_SLAVE_MODE); + aio->is_slave = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT); + ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH); + aio->mode = CYGNUS_SSPMODE_I2S; + break; + + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + ssp_newcfg |= BIT(I2S_OUT_CFGX_TDM_MODE); + + /* DSP_A = data after FS, DSP_B = data during FS */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) + ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT); + + if ((aio->fsync_width > 0) && (aio->fsync_width < 256)) + ssp_newcfg |= + (aio->fsync_width << I2S_OUT_CFGX_FSYNC_WIDTH); + else + ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH); + + aio->mode = CYGNUS_SSPMODE_TDM; + break; + + default: + return -EINVAL; + } + + /* + * SSP out cfg. + * Retain bits we do not want to update, then OR in new bits + */ + ssp_curcfg = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + ssp_outcfg = (ssp_curcfg & I2S_OUT_CFG_REG_UPDATE_MASK) | ssp_newcfg; + writel(ssp_outcfg, aio->cygaud->audio + aio->regs.i2s_cfg); + + /* + * SSP in cfg. + * Retain bits we do not want to update, then OR in new bits + */ + ssp_curcfg = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + ssp_incfg = (ssp_curcfg & I2S_IN_CFG_REG_UPDATE_MASK) | ssp_newcfg; + writel(ssp_incfg, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + + val = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + /* + * Configure the word clk and bit clk as output or tristate + * Each port has 4 bits for controlling its pins. + * Shift the mask based upon port number. + */ + mask = BIT(AUD_MISC_SEROUT_LRCK_OE) + | BIT(AUD_MISC_SEROUT_SCLK_OE) + | BIT(AUD_MISC_SEROUT_MCLK_OE); + mask = mask << (aio->portnum * 4); + if (aio->is_slave) + /* Set bit for tri-state */ + val |= mask; + else + /* Clear bit for drive */ + val &= ~mask; + + dev_dbg(aio->cygaud->dev, "%s Set OE bits 0x%x\n", __func__, val); + writel(val, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + return 0; +} + +static int cygnus_ssp_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + dev_dbg(aio->cygaud->dev, + "%s cmd %d at port = %d\n", __func__, cmd, aio->portnum); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_ssp_out_enable(aio); + else + audio_ssp_in_enable(aio); + cygaud->active_ports++; + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_ssp_out_disable(aio); + else + audio_ssp_in_disable(aio); + cygaud->active_ports--; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + u32 value; + int bits_per_slot = 0; /* default to 32-bits per slot */ + int frame_bits; + unsigned int active_slots; + bool found = false; + int i; + + if (tx_mask != rx_mask) { + dev_err(aio->cygaud->dev, + "%s tx_mask must equal rx_mask\n", __func__); + return -EINVAL; + } + + active_slots = hweight32(tx_mask); + + if (active_slots > 16) + return -EINVAL; + + /* Slot value must be even */ + if (active_slots % 2) + return -EINVAL; + + /* We encode 16 slots as 0 in the reg */ + if (active_slots == 16) + active_slots = 0; + + /* Slot Width is either 16 or 32 */ + switch (slot_width) { + case 16: + bits_per_slot = 1; + break; + case 32: + bits_per_slot = 0; + break; + default: + bits_per_slot = 0; + dev_warn(aio->cygaud->dev, + "%s Defaulting Slot Width to 32\n", __func__); + } + + frame_bits = slots * slot_width; + + for (i = 0; i < ARRAY_SIZE(ssp_valid_tdm_framesize); i++) { + if (ssp_valid_tdm_framesize[i] == frame_bits) { + found = true; + break; + } + } + + if (!found) { + dev_err(aio->cygaud->dev, + "%s In TDM mode, frame bits INVALID (%d)\n", + __func__, frame_bits); + return -EINVAL; + } + + aio->bit_per_frame = frame_bits; + + dev_dbg(aio->cygaud->dev, "%s active_slots %u, bits per frame %d\n", + __func__, active_slots, frame_bits); + + /* Set capture side of ssp port */ + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT); + value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT); + value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT); + value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + + /* Set playback side of ssp port */ + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT); + value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT); + value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT); + value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cygnus_ssp_suspend(struct snd_soc_dai *cpu_dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + + if (!aio->is_slave) { + u32 val; + + val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + val &= CYGNUS_PLLCLKSEL_MASK; + if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { + dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", + val); + return -EINVAL; + } + + if (aio->clk_trace.cap_clk_en) + clk_disable_unprepare(aio->cygaud->audio_clk[val]); + if (aio->clk_trace.play_clk_en) + clk_disable_unprepare(aio->cygaud->audio_clk[val]); + + aio->pll_clk_num = val; + } + + return 0; +} + +static int cygnus_ssp_resume(struct snd_soc_dai *cpu_dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + int error; + + if (!aio->is_slave) { + if (aio->clk_trace.cap_clk_en) { + error = clk_prepare_enable(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + if (error) { + dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", + __func__); + return -EINVAL; + } + } + if (aio->clk_trace.play_clk_en) { + error = clk_prepare_enable(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + if (error) { + if (aio->clk_trace.cap_clk_en) + clk_disable_unprepare(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", + __func__); + return -EINVAL; + } + } + } + + return 0; +} +#else +#define cygnus_ssp_suspend NULL +#define cygnus_ssp_resume NULL +#endif + +static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, + .trigger = cygnus_ssp_trigger, + .hw_params = cygnus_ssp_hw_params, + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, +}; + +static const struct snd_soc_dai_ops cygnus_spdif_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, + .trigger = cygnus_ssp_trigger, + .hw_params = cygnus_ssp_hw_params, + .set_sysclk = cygnus_ssp_set_sysclk, +}; + +#define INIT_CPU_DAI(num) { \ + .name = "cygnus-ssp" #num, \ + .playback = { \ + .channels_min = 2, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_KNOT, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .channels_min = 2, \ + .channels_max = 16, \ + .rates = SNDRV_PCM_RATE_KNOT, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &cygnus_ssp_dai_ops, \ + .suspend = cygnus_ssp_suspend, \ + .resume = cygnus_ssp_resume, \ +} + +static const struct snd_soc_dai_driver cygnus_ssp_dai_info[] = { + INIT_CPU_DAI(0), + INIT_CPU_DAI(1), + INIT_CPU_DAI(2), +}; + +static const struct snd_soc_dai_driver cygnus_spdif_dai_info = { + .name = "cygnus-spdif", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &cygnus_spdif_dai_ops, + .suspend = cygnus_ssp_suspend, + .resume = cygnus_ssp_resume, +}; + +static struct snd_soc_dai_driver cygnus_ssp_dai[CYGNUS_MAX_PORTS]; + +static const struct snd_soc_component_driver cygnus_ssp_component = { + .name = "cygnus-audio", +}; + +/* + * Return < 0 if error + * Return 0 if disabled + * Return 1 if enabled and node is parsed successfully + */ +static int parse_ssp_child_node(struct platform_device *pdev, + struct device_node *dn, + struct cygnus_audio *cygaud, + struct snd_soc_dai_driver *p_dai) +{ + struct cygnus_aio_port *aio; + struct cygnus_ssp_regs ssp_regs[3]; + u32 rawval; + int portnum = -1; + enum cygnus_audio_port_type port_type; + + if (of_property_read_u32(dn, "reg", &rawval)) { + dev_err(&pdev->dev, "Missing reg property\n"); + return -EINVAL; + } + + portnum = rawval; + switch (rawval) { + case 0: + ssp_regs[0] = INIT_SSP_REGS(0); + port_type = PORT_TDM; + break; + case 1: + ssp_regs[1] = INIT_SSP_REGS(1); + port_type = PORT_TDM; + break; + case 2: + ssp_regs[2] = INIT_SSP_REGS(2); + port_type = PORT_TDM; + break; + case 3: + port_type = PORT_SPDIF; + break; + default: + dev_err(&pdev->dev, "Bad value for reg %u\n", rawval); + return -EINVAL; + } + + aio = &cygaud->portinfo[portnum]; + aio->cygaud = cygaud; + aio->portnum = portnum; + aio->port_type = port_type; + aio->fsync_width = -1; + + switch (port_type) { + case PORT_TDM: + aio->regs = ssp_regs[portnum]; + *p_dai = cygnus_ssp_dai_info[portnum]; + aio->mode = CYGNUS_SSPMODE_UNKNOWN; + break; + + case PORT_SPDIF: + aio->regs.bf_sourcech_cfg = BF_SRC_CFG3_OFFSET; + aio->regs.bf_sourcech_ctrl = BF_SRC_CTRL3_OFFSET; + aio->regs.i2s_mclk_cfg = SPDIF_MCLK_CFG_OFFSET; + aio->regs.i2s_stream_cfg = SPDIF_STREAM_CFG_OFFSET; + *p_dai = cygnus_spdif_dai_info; + + /* For the purposes of this code SPDIF can be I2S mode */ + aio->mode = CYGNUS_SSPMODE_I2S; + break; + default: + dev_err(&pdev->dev, "Bad value for port_type %d\n", port_type); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "%s portnum = %d\n", __func__, aio->portnum); + aio->streams_on = 0; + aio->cygaud->dev = &pdev->dev; + aio->clk_trace.play_en = false; + aio->clk_trace.cap_en = false; + + audio_ssp_init_portregs(aio); + return 0; +} + +static int audio_clk_init(struct platform_device *pdev, + struct cygnus_audio *cygaud) +{ + int i; + char clk_name[PROP_LEN_MAX]; + + for (i = 0; i < ARRAY_SIZE(cygaud->audio_clk); i++) { + snprintf(clk_name, PROP_LEN_MAX, "ch%d_audio", i); + + cygaud->audio_clk[i] = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(cygaud->audio_clk[i])) + return PTR_ERR(cygaud->audio_clk[i]); + } + + return 0; +} + +static int cygnus_ssp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child_node; + struct resource *res; + struct cygnus_audio *cygaud; + int err = -EINVAL; + int node_count; + int active_port_count; + + cygaud = devm_kzalloc(dev, sizeof(struct cygnus_audio), GFP_KERNEL); + if (!cygaud) + return -ENOMEM; + + dev_set_drvdata(dev, cygaud); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aud"); + cygaud->audio = devm_ioremap_resource(dev, res); + if (IS_ERR(cygaud->audio)) + return PTR_ERR(cygaud->audio); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "i2s_in"); + cygaud->i2s_in = devm_ioremap_resource(dev, res); + if (IS_ERR(cygaud->i2s_in)) + return PTR_ERR(cygaud->i2s_in); + + /* Tri-state all controlable pins until we know that we need them */ + writel(CYGNUS_SSP_TRISTATE_MASK, + cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + node_count = of_get_child_count(pdev->dev.of_node); + if ((node_count < 1) || (node_count > CYGNUS_MAX_PORTS)) { + dev_err(dev, "child nodes is %d. Must be between 1 and %d\n", + node_count, CYGNUS_MAX_PORTS); + return -EINVAL; + } + + active_port_count = 0; + + for_each_available_child_of_node(pdev->dev.of_node, child_node) { + err = parse_ssp_child_node(pdev, child_node, cygaud, + &cygnus_ssp_dai[active_port_count]); + + /* negative is err, 0 is active and good, 1 is disabled */ + if (err < 0) + return err; + else if (!err) { + dev_dbg(dev, "Activating DAI: %s\n", + cygnus_ssp_dai[active_port_count].name); + active_port_count++; + } + } + + cygaud->dev = dev; + cygaud->active_ports = 0; + + dev_dbg(dev, "Registering %d DAIs\n", active_port_count); + err = snd_soc_register_component(dev, &cygnus_ssp_component, + cygnus_ssp_dai, active_port_count); + if (err) { + dev_err(dev, "snd_soc_register_dai failed\n"); + return err; + } + + cygaud->irq_num = platform_get_irq(pdev, 0); + if (cygaud->irq_num <= 0) { + dev_err(dev, "platform_get_irq failed\n"); + err = cygaud->irq_num; + goto err_irq; + } + + err = audio_clk_init(pdev, cygaud); + if (err) { + dev_err(dev, "audio clock initialization failed\n"); + goto err_irq; + } + + err = cygnus_soc_platform_register(dev, cygaud); + if (err) { + dev_err(dev, "platform reg error %d\n", err); + goto err_irq; + } + + return 0; + +err_irq: + snd_soc_unregister_component(dev); + return err; +} + +static int cygnus_ssp_remove(struct platform_device *pdev) +{ + cygnus_soc_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id cygnus_ssp_of_match[] = { + { .compatible = "brcm,cygnus-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cygnus_ssp_of_match); + +static struct platform_driver cygnus_ssp_driver = { + .probe = cygnus_ssp_probe, + .remove = cygnus_ssp_remove, + .driver = { + .name = "cygnus-ssp", + .of_match_table = cygnus_ssp_of_match, + }, +}; + +module_platform_driver(cygnus_ssp_driver); + +MODULE_ALIAS("platform:cygnus-ssp"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Cygnus ASoC SSP Interface"); diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h new file mode 100644 index 000000000..33dd34305 --- /dev/null +++ b/sound/soc/bcm/cygnus-ssp.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __CYGNUS_SSP_H__ +#define __CYGNUS_SSP_H__ + +#define CYGNUS_TDM_DAI_MAX_SLOTS 16 + +#define CYGNUS_MAX_PLAYBACK_PORTS 4 +#define CYGNUS_MAX_CAPTURE_PORTS 3 +#define CYGNUS_MAX_I2S_PORTS 3 +#define CYGNUS_MAX_PORTS CYGNUS_MAX_PLAYBACK_PORTS +#define CYGNUS_AUIDO_MAX_NUM_CLKS 3 + +#define CYGNUS_SSP_FRAMEBITS_DIV 1 + +#define CYGNUS_SSPMODE_I2S 0 +#define CYGNUS_SSPMODE_TDM 1 +#define CYGNUS_SSPMODE_UNKNOWN -1 + +#define CYGNUS_SSP_CLKSRC_PLL 0 + +/* Max string length of our dt property names */ +#define PROP_LEN_MAX 40 + +struct ringbuf_regs { + unsigned rdaddr; + unsigned wraddr; + unsigned baseaddr; + unsigned endaddr; + unsigned fmark; /* freemark for play, fullmark for caputure */ + unsigned period_bytes; + unsigned buf_size; +}; + +#define RINGBUF_REG_PLAYBACK(num) ((struct ringbuf_regs) { \ + .rdaddr = SRC_RBUF_ ##num## _RDADDR_OFFSET, \ + .wraddr = SRC_RBUF_ ##num## _WRADDR_OFFSET, \ + .baseaddr = SRC_RBUF_ ##num## _BASEADDR_OFFSET, \ + .endaddr = SRC_RBUF_ ##num## _ENDADDR_OFFSET, \ + .fmark = SRC_RBUF_ ##num## _FREE_MARK_OFFSET, \ + .period_bytes = 0, \ + .buf_size = 0, \ +}) + +#define RINGBUF_REG_CAPTURE(num) ((struct ringbuf_regs) { \ + .rdaddr = DST_RBUF_ ##num## _RDADDR_OFFSET, \ + .wraddr = DST_RBUF_ ##num## _WRADDR_OFFSET, \ + .baseaddr = DST_RBUF_ ##num## _BASEADDR_OFFSET, \ + .endaddr = DST_RBUF_ ##num## _ENDADDR_OFFSET, \ + .fmark = DST_RBUF_ ##num## _FULL_MARK_OFFSET, \ + .period_bytes = 0, \ + .buf_size = 0, \ +}) + +enum cygnus_audio_port_type { + PORT_TDM, + PORT_SPDIF, +}; + +struct cygnus_ssp_regs { + u32 i2s_stream_cfg; + u32 i2s_cfg; + u32 i2s_cap_stream_cfg; + u32 i2s_cap_cfg; + u32 i2s_mclk_cfg; + + u32 bf_destch_ctrl; + u32 bf_destch_cfg; + u32 bf_sourcech_ctrl; + u32 bf_sourcech_cfg; + u32 bf_sourcech_grp; +}; + +struct cygnus_track_clk { + bool cap_en; + bool play_en; + bool cap_clk_en; + bool play_clk_en; +}; + +struct cygnus_aio_port { + int portnum; + int mode; + bool is_slave; + int streams_on; /* will be 0 if both capture and play are off */ + int fsync_width; + int port_type; + + u32 mclk; + u32 lrclk; + u32 bit_per_frame; + u32 pll_clk_num; + + struct cygnus_audio *cygaud; + struct cygnus_ssp_regs regs; + + struct ringbuf_regs play_rb_regs; + struct ringbuf_regs capture_rb_regs; + + struct snd_pcm_substream *play_stream; + struct snd_pcm_substream *capture_stream; + + struct cygnus_track_clk clk_trace; +}; + + +struct cygnus_audio { + struct cygnus_aio_port portinfo[CYGNUS_MAX_PORTS]; + + int irq_num; + void __iomem *audio; + struct device *dev; + void __iomem *i2s_in; + + struct clk *audio_clk[CYGNUS_AUIDO_MAX_NUM_CLKS]; + int active_ports; + unsigned long vco_rate; +}; + +extern int cygnus_ssp_get_mode(struct snd_soc_dai *cpu_dai); +extern int cygnus_ssp_add_pll_tweak_controls(struct snd_soc_pcm_runtime *rtd); +extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, + int len); +extern int cygnus_soc_platform_register(struct device *dev, + struct cygnus_audio *cygaud); +extern int cygnus_soc_platform_unregister(struct device *dev); +extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, + int len); +#endif diff --git a/sound/soc/cirrus/Kconfig b/sound/soc/cirrus/Kconfig new file mode 100644 index 000000000..e09199124 --- /dev/null +++ b/sound/soc/cirrus/Kconfig @@ -0,0 +1,60 @@ +config SND_EP93XX_SOC + tristate "SoC Audio support for the Cirrus Logic EP93xx series" + depends on ARCH_EP93XX || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to + the EP93xx I2S or AC97 interfaces. + +config SND_EP93XX_SOC_I2S + tristate + +if SND_EP93XX_SOC_I2S + +config SND_EP93XX_SOC_I2S_WATCHDOG + bool "IRQ based underflow watchdog workaround" + default y + help + I2S controller on EP93xx seems to have undocumented HW issue. + Underflow of internal I2S controller FIFO could confuse the + state machine and the whole stream can be shifted by one byte + until I2S is disabled. This option enables IRQ based watchdog + which disables and re-enables I2S in case of underflow and + fills FIFO with zeroes. + + If you are unsure how to answer this question, answer Y. + +endif # if SND_EP93XX_SOC_I2S + +config SND_EP93XX_SOC_AC97 + tristate + select AC97_BUS + select SND_SOC_AC97_BUS + +config SND_EP93XX_SOC_SNAPPERCL15 + tristate "SoC Audio support for Bluewater Systems Snapper CL15 module" + depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15 && I2C + select SND_EP93XX_SOC_I2S + select SND_SOC_TLV320AIC23_I2C + help + Say Y or M here if you want to add support for I2S audio on the + Bluewater Systems Snapper CL15 module. + +config SND_EP93XX_SOC_SIMONE + tristate "SoC Audio support for Simplemachines Sim.One board" + depends on SND_EP93XX_SOC && MACH_SIM_ONE + select SND_EP93XX_SOC_AC97 + select SND_SOC_AC97_CODEC + help + Say Y or M here if you want to add support for AC97 audio on the + Simplemachines Sim.One board. + +config SND_EP93XX_SOC_EDB93XX + tristate "SoC Audio support for Cirrus Logic EDB93xx boards" + depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A) + select SND_EP93XX_SOC_I2S + select SND_SOC_CS4271_I2C if I2C + select SND_SOC_CS4271_SPI if SPI_MASTER + help + Say Y or M here if you want to add support for I2S audio on the + Cirrus Logic EDB93xx boards. diff --git a/sound/soc/cirrus/Makefile b/sound/soc/cirrus/Makefile new file mode 100644 index 000000000..bfb8dc409 --- /dev/null +++ b/sound/soc/cirrus/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# EP93xx Platform Support +snd-soc-ep93xx-objs := ep93xx-pcm.o +snd-soc-ep93xx-i2s-objs := ep93xx-i2s.o +snd-soc-ep93xx-ac97-objs := ep93xx-ac97.o + +obj-$(CONFIG_SND_EP93XX_SOC) += snd-soc-ep93xx.o +obj-$(CONFIG_SND_EP93XX_SOC_I2S) += snd-soc-ep93xx-i2s.o +obj-$(CONFIG_SND_EP93XX_SOC_AC97) += snd-soc-ep93xx-ac97.o + +# EP93XX Machine Support +snd-soc-snappercl15-objs := snappercl15.o +snd-soc-simone-objs := simone.o +snd-soc-edb93xx-objs := edb93xx.o + +obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o +obj-$(CONFIG_SND_EP93XX_SOC_SIMONE) += snd-soc-simone.o +obj-$(CONFIG_SND_EP93XX_SOC_EDB93XX) += snd-soc-edb93xx.o diff --git a/sound/soc/cirrus/edb93xx.c b/sound/soc/cirrus/edb93xx.c new file mode 100644 index 000000000..3d011abaa --- /dev/null +++ b/sound/soc/cirrus/edb93xx.c @@ -0,0 +1,126 @@ +/* + * SoC audio for EDB93xx + * + * Copyright (c) 2010 Alexander Sverdlin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver support CS4271 codec being master or slave, working + * in control port mode, connected either via SPI or I2C. + * The data format accepted is I2S or left-justified. + * DAPM support not implemented. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static int edb93xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int err; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + + /* + * According to CS4271 datasheet we use MCLK/LRCK=256 for + * rates below 50kHz and 128 for higher sample rates + */ + if (rate < 50000) + mclk_rate = rate * 64 * 4; + else + mclk_rate = rate * 64 * 2; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, + SND_SOC_CLOCK_IN); + if (err) + return err; + + return snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, + SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops edb93xx_ops = { + .hw_params = edb93xx_hw_params, +}; + +static struct snd_soc_dai_link edb93xx_dai = { + .name = "CS4271", + .stream_name = "CS4271 HiFi", + .platform_name = "ep93xx-i2s", + .cpu_dai_name = "ep93xx-i2s", + .codec_name = "spi0.0", + .codec_dai_name = "cs4271-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &edb93xx_ops, +}; + +static struct snd_soc_card snd_soc_edb93xx = { + .name = "EDB93XX", + .owner = THIS_MODULE, + .dai_link = &edb93xx_dai, + .num_links = 1, +}; + +static int edb93xx_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_edb93xx; + int ret; + + ret = ep93xx_i2s_acquire(); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + ep93xx_i2s_release(); + } + + return ret; +} + +static int edb93xx_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + ep93xx_i2s_release(); + + return 0; +} + +static struct platform_driver edb93xx_driver = { + .driver = { + .name = "edb93xx-audio", + }, + .probe = edb93xx_probe, + .remove = edb93xx_remove, +}; + +module_platform_driver(edb93xx_driver); + +MODULE_AUTHOR("Alexander Sverdlin "); +MODULE_DESCRIPTION("ALSA SoC EDB93xx"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:edb93xx-audio"); diff --git a/sound/soc/cirrus/ep93xx-ac97.c b/sound/soc/cirrus/ep93xx-ac97.c new file mode 100644 index 000000000..cd5a939ad --- /dev/null +++ b/sound/soc/cirrus/ep93xx-ac97.c @@ -0,0 +1,450 @@ +/* + * ASoC driver for Cirrus Logic EP93xx AC97 controller. + * + * Copyright (c) 2010 Mika Westerberg + * + * Based on s3c-ac97 ASoC driver by Jaswinder Singh. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "ep93xx-pcm.h" + +/* + * Per channel (1-4) registers. + */ +#define AC97CH(n) (((n) - 1) * 0x20) + +#define AC97DR(n) (AC97CH(n) + 0x0000) + +#define AC97RXCR(n) (AC97CH(n) + 0x0004) +#define AC97RXCR_REN BIT(0) +#define AC97RXCR_RX3 BIT(3) +#define AC97RXCR_RX4 BIT(4) +#define AC97RXCR_CM BIT(15) + +#define AC97TXCR(n) (AC97CH(n) + 0x0008) +#define AC97TXCR_TEN BIT(0) +#define AC97TXCR_TX3 BIT(3) +#define AC97TXCR_TX4 BIT(4) +#define AC97TXCR_CM BIT(15) + +#define AC97SR(n) (AC97CH(n) + 0x000c) +#define AC97SR_TXFE BIT(1) +#define AC97SR_TXUE BIT(6) + +#define AC97RISR(n) (AC97CH(n) + 0x0010) +#define AC97ISR(n) (AC97CH(n) + 0x0014) +#define AC97IE(n) (AC97CH(n) + 0x0018) + +/* + * Global AC97 controller registers. + */ +#define AC97S1DATA 0x0080 +#define AC97S2DATA 0x0084 +#define AC97S12DATA 0x0088 + +#define AC97RGIS 0x008c +#define AC97GIS 0x0090 +#define AC97IM 0x0094 +/* + * Common bits for RGIS, GIS and IM registers. + */ +#define AC97_SLOT2RXVALID BIT(1) +#define AC97_CODECREADY BIT(5) +#define AC97_SLOT2TXCOMPLETE BIT(6) + +#define AC97EOI 0x0098 +#define AC97EOI_WINT BIT(0) +#define AC97EOI_CODECREADY BIT(1) + +#define AC97GCR 0x009c +#define AC97GCR_AC97IFE BIT(0) + +#define AC97RESET 0x00a0 +#define AC97RESET_TIMEDRESET BIT(0) + +#define AC97SYNC 0x00a4 +#define AC97SYNC_TIMEDSYNC BIT(0) + +#define AC97_TIMEOUT msecs_to_jiffies(5) + +/** + * struct ep93xx_ac97_info - EP93xx AC97 controller info structure + * @lock: mutex serializing access to the bus (slot 1 & 2 ops) + * @dev: pointer to the platform device dev structure + * @regs: mapped AC97 controller registers + * @done: bus ops wait here for an interrupt + */ +struct ep93xx_ac97_info { + struct mutex lock; + struct device *dev; + void __iomem *regs; + struct completion done; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +/* currently ALSA only supports a single AC97 device */ +static struct ep93xx_ac97_info *ep93xx_ac97_info; + +static struct ep93xx_dma_data ep93xx_ac97_pcm_out = { + .name = "ac97-pcm-out", + .port = EP93XX_DMA_AAC1, + .direction = DMA_MEM_TO_DEV, +}; + +static struct ep93xx_dma_data ep93xx_ac97_pcm_in = { + .name = "ac97-pcm-in", + .port = EP93XX_DMA_AAC1, + .direction = DMA_DEV_TO_MEM, +}; + +static inline unsigned ep93xx_ac97_read_reg(struct ep93xx_ac97_info *info, + unsigned reg) +{ + return __raw_readl(info->regs + reg); +} + +static inline void ep93xx_ac97_write_reg(struct ep93xx_ac97_info *info, + unsigned reg, unsigned val) +{ + __raw_writel(val, info->regs + reg); +} + +static unsigned short ep93xx_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + unsigned short val; + + mutex_lock(&info->lock); + + ep93xx_ac97_write_reg(info, AC97S1DATA, reg); + ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2RXVALID); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) { + dev_warn(info->dev, "timeout reading register %x\n", reg); + mutex_unlock(&info->lock); + return -ETIMEDOUT; + } + val = (unsigned short)ep93xx_ac97_read_reg(info, AC97S2DATA); + + mutex_unlock(&info->lock); + return val; +} + +static void ep93xx_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + + mutex_lock(&info->lock); + + /* + * Writes to the codec need to be done so that slot 2 is filled in + * before slot 1. + */ + ep93xx_ac97_write_reg(info, AC97S2DATA, val); + ep93xx_ac97_write_reg(info, AC97S1DATA, reg); + + ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2TXCOMPLETE); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) + dev_warn(info->dev, "timeout writing register %x\n", reg); + + mutex_unlock(&info->lock); +} + +static void ep93xx_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + + mutex_lock(&info->lock); + + /* + * We are assuming that before this functions gets called, the codec + * BIT_CLK is stopped by forcing the codec into powerdown mode. We can + * control the SYNC signal directly via AC97SYNC register. Using + * TIMEDSYNC the controller will keep the SYNC high > 1us. + */ + ep93xx_ac97_write_reg(info, AC97SYNC, AC97SYNC_TIMEDSYNC); + ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) + dev_warn(info->dev, "codec warm reset timeout\n"); + + mutex_unlock(&info->lock); +} + +static void ep93xx_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + + mutex_lock(&info->lock); + + /* + * For doing cold reset, we disable the AC97 controller interface, clear + * WINT and CODECREADY bits, and finally enable the interface again. + */ + ep93xx_ac97_write_reg(info, AC97GCR, 0); + ep93xx_ac97_write_reg(info, AC97EOI, AC97EOI_CODECREADY | AC97EOI_WINT); + ep93xx_ac97_write_reg(info, AC97GCR, AC97GCR_AC97IFE); + + /* + * Now, assert the reset and wait for the codec to become ready. + */ + ep93xx_ac97_write_reg(info, AC97RESET, AC97RESET_TIMEDRESET); + ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) + dev_warn(info->dev, "codec cold reset timeout\n"); + + /* + * Give the codec some time to come fully out from the reset. This way + * we ensure that the subsequent reads/writes will work. + */ + usleep_range(15000, 20000); + + mutex_unlock(&info->lock); +} + +static irqreturn_t ep93xx_ac97_interrupt(int irq, void *dev_id) +{ + struct ep93xx_ac97_info *info = dev_id; + unsigned status, mask; + + /* + * Just mask out the interrupt and wake up the waiting thread. + * Interrupts are cleared via reading/writing to slot 1 & 2 registers by + * the waiting thread. + */ + status = ep93xx_ac97_read_reg(info, AC97GIS); + mask = ep93xx_ac97_read_reg(info, AC97IM); + mask &= ~status; + ep93xx_ac97_write_reg(info, AC97IM, mask); + + complete(&info->done); + return IRQ_HANDLED; +} + +static struct snd_ac97_bus_ops ep93xx_ac97_ops = { + .read = ep93xx_ac97_read, + .write = ep93xx_ac97_write, + .reset = ep93xx_ac97_cold_reset, + .warm_reset = ep93xx_ac97_warm_reset, +}; + +static int ep93xx_ac97_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai); + unsigned v = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * Enable compact mode, TX slots 3 & 4, and the TX FIFO + * itself. + */ + v |= AC97TXCR_CM; + v |= AC97TXCR_TX3 | AC97TXCR_TX4; + v |= AC97TXCR_TEN; + ep93xx_ac97_write_reg(info, AC97TXCR(1), v); + } else { + /* + * Enable compact mode, RX slots 3 & 4, and the RX FIFO + * itself. + */ + v |= AC97RXCR_CM; + v |= AC97RXCR_RX3 | AC97RXCR_RX4; + v |= AC97RXCR_REN; + ep93xx_ac97_write_reg(info, AC97RXCR(1), v); + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * As per Cirrus EP93xx errata described below: + * + * http://www.cirrus.com/en/pubs/errata/ER667E2B.pdf + * + * we will wait for the TX FIFO to be empty before + * clearing the TEN bit. + */ + unsigned long timeout = jiffies + AC97_TIMEOUT; + + do { + v = ep93xx_ac97_read_reg(info, AC97SR(1)); + if (time_after(jiffies, timeout)) { + dev_warn(info->dev, "TX timeout\n"); + break; + } + } while (!(v & (AC97SR_TXFE | AC97SR_TXUE))); + + /* disable the TX FIFO */ + ep93xx_ac97_write_reg(info, AC97TXCR(1), 0); + } else { + /* disable the RX FIFO */ + ep93xx_ac97_write_reg(info, AC97RXCR(1), 0); + } + break; + + default: + dev_warn(info->dev, "unknown command %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int ep93xx_ac97_dai_probe(struct snd_soc_dai *dai) +{ + struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai); + + info->dma_params_tx.filter_data = &ep93xx_ac97_pcm_out; + info->dma_params_rx.filter_data = &ep93xx_ac97_pcm_in; + + dai->playback_dma_data = &info->dma_params_tx; + dai->capture_dma_data = &info->dma_params_rx; + + return 0; +} + +static const struct snd_soc_dai_ops ep93xx_ac97_dai_ops = { + .trigger = ep93xx_ac97_trigger, +}; + +static struct snd_soc_dai_driver ep93xx_ac97_dai = { + .name = "ep93xx-ac97", + .id = 0, + .bus_control = true, + .probe = ep93xx_ac97_dai_probe, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &ep93xx_ac97_dai_ops, +}; + +static const struct snd_soc_component_driver ep93xx_ac97_component = { + .name = "ep93xx-ac97", +}; + +static int ep93xx_ac97_probe(struct platform_device *pdev) +{ + struct ep93xx_ac97_info *info; + struct resource *res; + int irq; + int ret; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq < 0 ? irq : -ENODEV; + + ret = devm_request_irq(&pdev->dev, irq, ep93xx_ac97_interrupt, + IRQF_TRIGGER_HIGH, pdev->name, info); + if (ret) + goto fail; + + dev_set_drvdata(&pdev->dev, info); + + mutex_init(&info->lock); + init_completion(&info->done); + info->dev = &pdev->dev; + + ep93xx_ac97_info = info; + platform_set_drvdata(pdev, info); + + ret = snd_soc_set_ac97_ops(&ep93xx_ac97_ops); + if (ret) + goto fail; + + ret = snd_soc_register_component(&pdev->dev, &ep93xx_ac97_component, + &ep93xx_ac97_dai, 1); + if (ret) + goto fail; + + ret = devm_ep93xx_pcm_platform_register(&pdev->dev); + if (ret) + goto fail_unregister; + + return 0; + +fail_unregister: + snd_soc_unregister_component(&pdev->dev); +fail: + ep93xx_ac97_info = NULL; + snd_soc_set_ac97_ops(NULL); + return ret; +} + +static int ep93xx_ac97_remove(struct platform_device *pdev) +{ + struct ep93xx_ac97_info *info = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + + /* disable the AC97 controller */ + ep93xx_ac97_write_reg(info, AC97GCR, 0); + + ep93xx_ac97_info = NULL; + + snd_soc_set_ac97_ops(NULL); + + return 0; +} + +static struct platform_driver ep93xx_ac97_driver = { + .probe = ep93xx_ac97_probe, + .remove = ep93xx_ac97_remove, + .driver = { + .name = "ep93xx-ac97", + }, +}; + +module_platform_driver(ep93xx_ac97_driver); + +MODULE_DESCRIPTION("EP93xx AC97 ASoC Driver"); +MODULE_AUTHOR("Mika Westerberg "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ep93xx-ac97"); diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c new file mode 100644 index 000000000..0918c5da5 --- /dev/null +++ b/sound/soc/cirrus/ep93xx-i2s.c @@ -0,0 +1,528 @@ +/* + * linux/sound/soc/ep93xx-i2s.c + * EP93xx I2S driver + * + * Copyright (C) 2010 Ryan Mallon + * + * Based on the original driver by: + * Copyright (C) 2007 Chase Douglas + * Copyright (C) 2006 Lennert Buytenhek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ep93xx-pcm.h" + +#define EP93XX_I2S_TXCLKCFG 0x00 +#define EP93XX_I2S_RXCLKCFG 0x04 +#define EP93XX_I2S_GLSTS 0x08 +#define EP93XX_I2S_GLCTRL 0x0C + +#define EP93XX_I2S_I2STX0LFT 0x10 +#define EP93XX_I2S_I2STX0RT 0x14 + +#define EP93XX_I2S_TXLINCTRLDATA 0x28 +#define EP93XX_I2S_TXCTRL 0x2C +#define EP93XX_I2S_TXWRDLEN 0x30 +#define EP93XX_I2S_TX0EN 0x34 + +#define EP93XX_I2S_RXLINCTRLDATA 0x58 +#define EP93XX_I2S_RXCTRL 0x5C +#define EP93XX_I2S_RXWRDLEN 0x60 +#define EP93XX_I2S_RX0EN 0x64 + +#define EP93XX_I2S_WRDLEN_16 (0 << 0) +#define EP93XX_I2S_WRDLEN_24 (1 << 0) +#define EP93XX_I2S_WRDLEN_32 (2 << 0) + +#define EP93XX_I2S_RXLINCTRLDATA_R_JUST BIT(1) /* Right justify */ + +#define EP93XX_I2S_TXLINCTRLDATA_R_JUST BIT(2) /* Right justify */ + +/* + * Transmit empty interrupt level select: + * 0 - Generate interrupt when FIFO is half empty + * 1 - Generate interrupt when FIFO is empty + */ +#define EP93XX_I2S_TXCTRL_TXEMPTY_LVL BIT(0) +#define EP93XX_I2S_TXCTRL_TXUFIE BIT(1) /* Transmit interrupt enable */ + +#define EP93XX_I2S_CLKCFG_LRS (1 << 0) /* lrclk polarity */ +#define EP93XX_I2S_CLKCFG_CKP (1 << 1) /* Bit clock polarity */ +#define EP93XX_I2S_CLKCFG_REL (1 << 2) /* First bit transition */ +#define EP93XX_I2S_CLKCFG_MASTER (1 << 3) /* Master mode */ +#define EP93XX_I2S_CLKCFG_NBCG (1 << 4) /* Not bit clock gating */ + +#define EP93XX_I2S_GLSTS_TX0_FIFO_FULL BIT(12) + +struct ep93xx_i2s_info { + struct clk *mclk; + struct clk *sclk; + struct clk *lrclk; + void __iomem *regs; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +static struct ep93xx_dma_data ep93xx_i2s_dma_data[] = { + [SNDRV_PCM_STREAM_PLAYBACK] = { + .name = "i2s-pcm-out", + .port = EP93XX_DMA_I2S1, + .direction = DMA_MEM_TO_DEV, + }, + [SNDRV_PCM_STREAM_CAPTURE] = { + .name = "i2s-pcm-in", + .port = EP93XX_DMA_I2S1, + .direction = DMA_DEV_TO_MEM, + }, +}; + +static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info, + unsigned reg, unsigned val) +{ + __raw_writel(val, info->regs + reg); +} + +static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info, + unsigned reg) +{ + return __raw_readl(info->regs + reg); +} + +static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Enable clocks */ + clk_enable(info->mclk); + clk_enable(info->sclk); + clk_enable(info->lrclk); + + /* Enable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1); + } + + /* Enable fifo */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + ep93xx_i2s_write_reg(info, base_reg, 1); + + /* Enable TX IRQs (FIFO empty or underflow) */ + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) && + stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL, + EP93XX_I2S_TXCTRL_TXEMPTY_LVL | + EP93XX_I2S_TXCTRL_TXUFIE); +} + +static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + + /* Disable IRQs */ + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) && + stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL, 0); + + /* Disable fifo */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + ep93xx_i2s_write_reg(info, base_reg, 0); + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Disable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0); + + /* Disable clocks */ + clk_disable(info->lrclk); + clk_disable(info->sclk); + clk_disable(info->mclk); + } +} + +/* + * According to documentation I2S controller can handle underflow conditions + * just fine, but in reality the state machine is sometimes confused so that + * the whole stream is shifted by one byte. The watchdog below disables the TX + * FIFO, fills the buffer with zeroes and re-enables the FIFO. State machine + * is being reset and by filling the buffer we get some time before next + * underflow happens. + */ +static irqreturn_t ep93xx_i2s_interrupt(int irq, void *dev_id) +{ + struct ep93xx_i2s_info *info = dev_id; + + /* Disable FIFO */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 0); + /* + * Fill TX FIFO with zeroes, this way we can defer next IRQs as much as + * possible and get more time for DMA to catch up. Actually there are + * only 8 samples in this FIFO, so even on 8kHz maximum deferral here is + * 1ms. + */ + while (!(ep93xx_i2s_read_reg(info, EP93XX_I2S_GLSTS) & + EP93XX_I2S_GLSTS_TX0_FIFO_FULL)) { + ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0LFT, 0); + ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0RT, 0); + } + /* Re-enable FIFO */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 1); + + return IRQ_HANDLED; +} + +static int ep93xx_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + info->dma_params_tx.filter_data = + &ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + info->dma_params_rx.filter_data = + &ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_CAPTURE]; + + dai->playback_dma_data = &info->dma_params_tx; + dai->capture_dma_data = &info->dma_params_rx; + + return 0; +} + +static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + ep93xx_i2s_disable(info, substream->stream); +} + +static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai); + unsigned int clk_cfg; + unsigned int txlin_ctrl = 0; + unsigned int rxlin_ctrl = 0; + + clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + clk_cfg |= EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_LEFT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_RIGHT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + rxlin_ctrl |= EP93XX_I2S_RXLINCTRLDATA_R_JUST; + txlin_ctrl |= EP93XX_I2S_TXLINCTRLDATA_R_JUST; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + clk_cfg |= EP93XX_I2S_CLKCFG_MASTER; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* Codec is master */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Negative bit clock, lrclk low on left word */ + clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS); + break; + + case SND_SOC_DAIFMT_NB_IF: + /* Negative bit clock, lrclk low on right word */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP; + clk_cfg |= EP93XX_I2S_CLKCFG_LRS; + break; + + case SND_SOC_DAIFMT_IB_NF: + /* Positive bit clock, lrclk low on left word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP; + clk_cfg &= ~EP93XX_I2S_CLKCFG_LRS; + break; + + case SND_SOC_DAIFMT_IB_IF: + /* Positive bit clock, lrclk low on right word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS; + break; + } + + /* Write new register values */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, rxlin_ctrl); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, txlin_ctrl); + return 0; +} + +static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + unsigned word_len, div, sdiv, lrdiv; + int err; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = EP93XX_I2S_WRDLEN_16; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + word_len = EP93XX_I2S_WRDLEN_24; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + word_len = EP93XX_I2S_WRDLEN_32; + break; + + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len); + else + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len); + + /* + * EP93xx I2S module can be setup so SCLK / LRCLK value can be + * 32, 64, 128. MCLK / SCLK value can be 2 and 4. + * We set LRCLK equal to `rate' and minimum SCLK / LRCLK + * value is 64, because our sample size is 32 bit * 2 channels. + * I2S standard permits us to transmit more bits than + * the codec uses. + */ + div = clk_get_rate(info->mclk) / params_rate(params); + sdiv = 4; + if (div > (256 + 512) / 2) { + lrdiv = 128; + } else { + lrdiv = 64; + if (div < (128 + 256) / 2) + sdiv = 2; + } + + err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv); + if (err) + return err; + + err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv); + if (err) + return err; + + ep93xx_i2s_enable(info, substream->stream); + return 0; +} + +static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai); + + if (dir == SND_SOC_CLOCK_IN || clk_id != 0) + return -EINVAL; + + return clk_set_rate(info->mclk, freq); +} + +#ifdef CONFIG_PM +static int ep93xx_i2s_suspend(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + if (!dai->active) + return 0; + + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; +} + +static int ep93xx_i2s_resume(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + if (!dai->active) + return 0; + + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; +} +#else +#define ep93xx_i2s_suspend NULL +#define ep93xx_i2s_resume NULL +#endif + +static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .shutdown = ep93xx_i2s_shutdown, + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, +}; + +#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver ep93xx_i2s_dai = { + .symmetric_rates= 1, + .probe = ep93xx_i2s_dai_probe, + .suspend = ep93xx_i2s_suspend, + .resume = ep93xx_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = EP93XX_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = EP93XX_I2S_FORMATS, + }, + .ops = &ep93xx_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver ep93xx_i2s_component = { + .name = "ep93xx-i2s", +}; + +static int ep93xx_i2s_probe(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info; + struct resource *res; + int err; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG)) { + int irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq < 0 ? irq : -ENODEV; + + err = devm_request_irq(&pdev->dev, irq, ep93xx_i2s_interrupt, 0, + pdev->name, info); + if (err) + return err; + } + + info->mclk = clk_get(&pdev->dev, "mclk"); + if (IS_ERR(info->mclk)) { + err = PTR_ERR(info->mclk); + goto fail; + } + + info->sclk = clk_get(&pdev->dev, "sclk"); + if (IS_ERR(info->sclk)) { + err = PTR_ERR(info->sclk); + goto fail_put_mclk; + } + + info->lrclk = clk_get(&pdev->dev, "lrclk"); + if (IS_ERR(info->lrclk)) { + err = PTR_ERR(info->lrclk); + goto fail_put_sclk; + } + + dev_set_drvdata(&pdev->dev, info); + + err = snd_soc_register_component(&pdev->dev, &ep93xx_i2s_component, + &ep93xx_i2s_dai, 1); + if (err) + goto fail_put_lrclk; + + err = devm_ep93xx_pcm_platform_register(&pdev->dev); + if (err) + goto fail_unregister; + + return 0; + +fail_unregister: + snd_soc_unregister_component(&pdev->dev); +fail_put_lrclk: + clk_put(info->lrclk); +fail_put_sclk: + clk_put(info->sclk); +fail_put_mclk: + clk_put(info->mclk); +fail: + return err; +} + +static int ep93xx_i2s_remove(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + clk_put(info->lrclk); + clk_put(info->sclk); + clk_put(info->mclk); + return 0; +} + +static struct platform_driver ep93xx_i2s_driver = { + .probe = ep93xx_i2s_probe, + .remove = ep93xx_i2s_remove, + .driver = { + .name = "ep93xx-i2s", + }, +}; + +module_platform_driver(ep93xx_i2s_driver); + +MODULE_ALIAS("platform:ep93xx-i2s"); +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("EP93XX I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/cirrus/ep93xx-pcm.c b/sound/soc/cirrus/ep93xx-pcm.c new file mode 100644 index 000000000..67a73330d --- /dev/null +++ b/sound/soc/cirrus/ep93xx-pcm.c @@ -0,0 +1,70 @@ +/* + * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface + * + * Copyright (C) 2006 Lennert Buytenhek + * Copyright (C) 2006 Applied Data Systems + * + * Rewritten for the SoC audio subsystem (Based on PXA2xx code): + * Copyright (c) 2008 Ryan Mallon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "ep93xx-pcm.h" + +static const struct snd_pcm_hardware ep93xx_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .buffer_bytes_max = 131072, + .period_bytes_min = 32, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 32, +}; + +static bool ep93xx_pcm_dma_filter(struct dma_chan *chan, void *filter_param) +{ + struct ep93xx_dma_data *data = filter_param; + + if (data->direction == ep93xx_dma_chan_direction(chan)) { + chan->private = data; + return true; + } + + return false; +} + +static const struct snd_dmaengine_pcm_config ep93xx_dmaengine_pcm_config = { + .pcm_hardware = &ep93xx_pcm_hardware, + .compat_filter_fn = ep93xx_pcm_dma_filter, + .prealloc_buffer_size = 131072, +}; + +int devm_ep93xx_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, + &ep93xx_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_NO_DT | + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(devm_ep93xx_pcm_platform_register); + +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("EP93xx ALSA PCM interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/cirrus/ep93xx-pcm.h b/sound/soc/cirrus/ep93xx-pcm.h new file mode 100644 index 000000000..b7a12a2fa --- /dev/null +++ b/sound/soc/cirrus/ep93xx-pcm.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#ifndef __EP93XX_PCM_H__ +#define __EP93XX_PCM_H__ + +int devm_ep93xx_pcm_platform_register(struct device *dev); + +#endif diff --git a/sound/soc/cirrus/simone.c b/sound/soc/cirrus/simone.c new file mode 100644 index 000000000..1ec661834 --- /dev/null +++ b/sound/soc/cirrus/simone.c @@ -0,0 +1,87 @@ +/* + * simone.c -- ASoC audio for Simplemachines Sim.One board + * + * Copyright (c) 2010 Mika Westerberg + * + * Based on snappercl15 machine driver by Ryan Mallon. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +static struct snd_soc_dai_link simone_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "ep93xx-ac97", + .codec_dai_name = "ac97-hifi", + .codec_name = "ac97-codec", + .platform_name = "ep93xx-ac97", +}; + +static struct snd_soc_card snd_soc_simone = { + .name = "Sim.One", + .owner = THIS_MODULE, + .dai_link = &simone_dai, + .num_links = 1, +}; + +static struct platform_device *simone_snd_ac97_device; + +static int simone_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_simone; + int ret; + + simone_snd_ac97_device = platform_device_register_simple("ac97-codec", + -1, NULL, 0); + if (IS_ERR(simone_snd_ac97_device)) + return PTR_ERR(simone_snd_ac97_device); + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + platform_device_unregister(simone_snd_ac97_device); + } + + return ret; +} + +static int simone_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + platform_device_unregister(simone_snd_ac97_device); + + return 0; +} + +static struct platform_driver simone_driver = { + .driver = { + .name = "simone-audio", + }, + .probe = simone_probe, + .remove = simone_remove, +}; + +module_platform_driver(simone_driver); + +MODULE_DESCRIPTION("ALSA SoC Simplemachines Sim.One"); +MODULE_AUTHOR("Mika Westerberg "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:simone-audio"); diff --git a/sound/soc/cirrus/snappercl15.c b/sound/soc/cirrus/snappercl15.c new file mode 100644 index 000000000..11ff7b267 --- /dev/null +++ b/sound/soc/cirrus/snappercl15.c @@ -0,0 +1,136 @@ +/* + * snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module + * + * Copyright (C) 2008 Bluewater Systems Ltd + * Author: Ryan Mallon + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "../codecs/tlv320aic23.h" + +#define CODEC_CLOCK 5644800 + +static int snappercl15_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int err; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_IN); + if (err) + return err; + + err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_OUT); + if (err) + return err; + + return 0; +} + +static const struct snd_soc_ops snappercl15_ops = { + .hw_params = snappercl15_hw_params, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static struct snd_soc_dai_link snappercl15_dai = { + .name = "tlv320aic23", + .stream_name = "AIC23", + .cpu_dai_name = "ep93xx-i2s", + .codec_dai_name = "tlv320aic23-hifi", + .codec_name = "tlv320aic23-codec.0-001a", + .platform_name = "ep93xx-i2s", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snappercl15_ops, +}; + +static struct snd_soc_card snd_soc_snappercl15 = { + .name = "Snapper CL15", + .owner = THIS_MODULE, + .dai_link = &snappercl15_dai, + .num_links = 1, + + .dapm_widgets = tlv320aic23_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int snappercl15_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_snappercl15; + int ret; + + ret = ep93xx_i2s_acquire(); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + ep93xx_i2s_release(); + } + + return ret; +} + +static int snappercl15_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + ep93xx_i2s_release(); + + return 0; +} + +static struct platform_driver snappercl15_driver = { + .driver = { + .name = "snappercl15-audio", + }, + .probe = snappercl15_probe, + .remove = snappercl15_remove, +}; + +module_platform_driver(snappercl15_driver); + +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("ALSA SoC Snapper CL15"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:snappercl15-audio"); diff --git a/sound/soc/codecs/88pm860x-codec.c b/sound/soc/codecs/88pm860x-codec.c new file mode 100644 index 000000000..3c3ef4228 --- /dev/null +++ b/sound/soc/codecs/88pm860x-codec.c @@ -0,0 +1,1425 @@ +/* + * 88pm860x-codec.c -- 88PM860x ALSA SoC Audio Driver + * + * Copyright 2010 Marvell International Ltd. + * Author: Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "88pm860x-codec.h" + +#define MAX_NAME_LEN 20 +#define REG_CACHE_SIZE 0x40 +#define REG_CACHE_BASE 0xb0 + +/* Status Register 1 (0x01) */ +#define REG_STATUS_1 0x01 +#define MIC_STATUS (1 << 7) +#define HOOK_STATUS (1 << 6) +#define HEADSET_STATUS (1 << 5) + +/* Mic Detection Register (0x37) */ +#define REG_MIC_DET 0x37 +#define CONTINUOUS_POLLING (3 << 1) +#define EN_MIC_DET (1 << 0) +#define MICDET_MASK 0x07 + +/* Headset Detection Register (0x38) */ +#define REG_HS_DET 0x38 +#define EN_HS_DET (1 << 0) + +/* Misc2 Register (0x42) */ +#define REG_MISC2 0x42 +#define AUDIO_PLL (1 << 5) +#define AUDIO_SECTION_RESET (1 << 4) +#define AUDIO_SECTION_ON (1 << 3) + +/* PCM Interface Register 2 (0xb1) */ +#define PCM_INF2_BCLK (1 << 6) /* Bit clock polarity */ +#define PCM_INF2_FS (1 << 5) /* Frame Sync polarity */ +#define PCM_INF2_MASTER (1 << 4) /* Master / Slave */ +#define PCM_INF2_18WL (1 << 3) /* 18 / 16 bits */ +#define PCM_GENERAL_I2S 0 +#define PCM_EXACT_I2S 1 +#define PCM_LEFT_I2S 2 +#define PCM_RIGHT_I2S 3 +#define PCM_SHORT_FS 4 +#define PCM_LONG_FS 5 +#define PCM_MODE_MASK 7 + +/* I2S Interface Register 4 (0xbe) */ +#define I2S_EQU_BYP (1 << 6) + +/* DAC Offset Register (0xcb) */ +#define DAC_MUTE (1 << 7) +#define MUTE_LEFT (1 << 6) +#define MUTE_RIGHT (1 << 2) + +/* ADC Analog Register 1 (0xd0) */ +#define REG_ADC_ANA_1 0xd0 +#define MIC1BIAS_MASK 0x60 + +/* Earpiece/Speaker Control Register 2 (0xda) */ +#define REG_EAR2 0xda +#define RSYNC_CHANGE (1 << 2) + +/* Audio Supplies Register 2 (0xdc) */ +#define REG_SUPPLIES2 0xdc +#define LDO15_READY (1 << 4) +#define LDO15_EN (1 << 3) +#define CPUMP_READY (1 << 2) +#define CPUMP_EN (1 << 1) +#define AUDIO_EN (1 << 0) +#define SUPPLY_MASK (LDO15_EN | CPUMP_EN | AUDIO_EN) + +/* Audio Enable Register 1 (0xdd) */ +#define ADC_MOD_RIGHT (1 << 1) +#define ADC_MOD_LEFT (1 << 0) + +/* Audio Enable Register 2 (0xde) */ +#define ADC_LEFT (1 << 5) +#define ADC_RIGHT (1 << 4) + +/* DAC Enable Register 2 (0xe1) */ +#define DAC_LEFT (1 << 5) +#define DAC_RIGHT (1 << 4) +#define MODULATOR (1 << 3) + +/* Shorts Register (0xeb) */ +#define REG_SHORTS 0xeb +#define CLR_SHORT_LO2 (1 << 7) +#define SHORT_LO2 (1 << 6) +#define CLR_SHORT_LO1 (1 << 5) +#define SHORT_LO1 (1 << 4) +#define CLR_SHORT_HS2 (1 << 3) +#define SHORT_HS2 (1 << 2) +#define CLR_SHORT_HS1 (1 << 1) +#define SHORT_HS1 (1 << 0) + +/* + * This widget should be just after DAC & PGA in DAPM power-on sequence and + * before DAC & PGA in DAPM power-off sequence. + */ +#define PM860X_DAPM_OUTPUT(wname, wevent) \ + SND_SOC_DAPM_PGA_E(wname, SND_SOC_NOPM, 0, 0, NULL, 0, wevent, \ + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD) + +struct pm860x_det { + struct snd_soc_jack *hp_jack; + struct snd_soc_jack *mic_jack; + int hp_det; + int mic_det; + int hook_det; + int hs_shrt; + int lo_shrt; +}; + +struct pm860x_priv { + unsigned int sysclk; + unsigned int pcmclk; + unsigned int dir; + unsigned int filter; + struct snd_soc_component *component; + struct i2c_client *i2c; + struct regmap *regmap; + struct pm860x_chip *chip; + struct pm860x_det det; + + int irq[4]; + unsigned char name[4][MAX_NAME_LEN+1]; +}; + +/* -9450dB to 0dB in 150dB steps ( mute instead of -9450dB) */ +static const DECLARE_TLV_DB_SCALE(dpga_tlv, -9450, 150, 1); + +/* -9dB to 0db in 3dB steps */ +static const DECLARE_TLV_DB_SCALE(adc_tlv, -900, 300, 0); + +/* {-23, -17, -13.5, -11, -9, -6, -3, 0}dB */ +static const DECLARE_TLV_DB_RANGE(mic_tlv, + 0, 0, TLV_DB_SCALE_ITEM(-2300, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(-1700, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(-1350, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(-1100, 0, 0), + 4, 7, TLV_DB_SCALE_ITEM(-900, 300, 0) +); + +/* {0, 0, 0, -6, 0, 6, 12, 18}dB */ +static const DECLARE_TLV_DB_RANGE(aux_tlv, + 0, 2, TLV_DB_SCALE_ITEM(0, 0, 0), + 3, 7, TLV_DB_SCALE_ITEM(-600, 600, 0) +); + +/* {-16, -13, -10, -7, -5.2, -3,3, -2.2, 0}dB, mute instead of -16dB */ +static const DECLARE_TLV_DB_RANGE(out_tlv, + 0, 3, TLV_DB_SCALE_ITEM(-1600, 300, 1), + 4, 4, TLV_DB_SCALE_ITEM(-520, 0, 0), + 5, 5, TLV_DB_SCALE_ITEM(-330, 0, 0), + 6, 7, TLV_DB_SCALE_ITEM(-220, 220, 0) +); + +static const DECLARE_TLV_DB_RANGE(st_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-12041, 602, 0), + 2, 3, TLV_DB_SCALE_ITEM(-11087, 250, 0), + 4, 5, TLV_DB_SCALE_ITEM(-10643, 158, 0), + 6, 7, TLV_DB_SCALE_ITEM(-10351, 116, 0), + 8, 9, TLV_DB_SCALE_ITEM(-10133, 92, 0), + 10, 13, TLV_DB_SCALE_ITEM(-9958, 70, 0), + 14, 17, TLV_DB_SCALE_ITEM(-9689, 53, 0), + 18, 271, TLV_DB_SCALE_ITEM(-9484, 37, 0) +); + +/* Sidetone Gain = M * 2^(-5-N) */ +struct st_gain { + unsigned int db; + unsigned int m; + unsigned int n; +}; + +static struct st_gain st_table[] = { + {-12041, 1, 15}, {-11439, 1, 14}, {-11087, 3, 15}, {-10837, 1, 13}, + {-10643, 5, 15}, {-10485, 3, 14}, {-10351, 7, 15}, {-10235, 1, 12}, + {-10133, 9, 15}, {-10041, 5, 14}, { -9958, 11, 15}, { -9883, 3, 13}, + { -9813, 13, 15}, { -9749, 7, 14}, { -9689, 15, 15}, { -9633, 1, 11}, + { -9580, 17, 15}, { -9531, 9, 14}, { -9484, 19, 15}, { -9439, 5, 13}, + { -9397, 21, 15}, { -9356, 11, 14}, { -9318, 23, 15}, { -9281, 3, 12}, + { -9245, 25, 15}, { -9211, 13, 14}, { -9178, 27, 15}, { -9147, 7, 13}, + { -9116, 29, 15}, { -9087, 15, 14}, { -9058, 31, 15}, { -9031, 1, 10}, + { -8978, 17, 14}, { -8929, 9, 13}, { -8882, 19, 14}, { -8837, 5, 12}, + { -8795, 21, 14}, { -8754, 11, 13}, { -8716, 23, 14}, { -8679, 3, 11}, + { -8643, 25, 14}, { -8609, 13, 13}, { -8576, 27, 14}, { -8545, 7, 12}, + { -8514, 29, 14}, { -8485, 15, 13}, { -8456, 31, 14}, { -8429, 1, 9}, + { -8376, 17, 13}, { -8327, 9, 12}, { -8280, 19, 13}, { -8235, 5, 11}, + { -8193, 21, 13}, { -8152, 11, 12}, { -8114, 23, 13}, { -8077, 3, 10}, + { -8041, 25, 13}, { -8007, 13, 12}, { -7974, 27, 13}, { -7943, 7, 11}, + { -7912, 29, 13}, { -7883, 15, 12}, { -7854, 31, 13}, { -7827, 1, 8}, + { -7774, 17, 12}, { -7724, 9, 11}, { -7678, 19, 12}, { -7633, 5, 10}, + { -7591, 21, 12}, { -7550, 11, 11}, { -7512, 23, 12}, { -7475, 3, 9}, + { -7439, 25, 12}, { -7405, 13, 11}, { -7372, 27, 12}, { -7341, 7, 10}, + { -7310, 29, 12}, { -7281, 15, 11}, { -7252, 31, 12}, { -7225, 1, 7}, + { -7172, 17, 11}, { -7122, 9, 10}, { -7075, 19, 11}, { -7031, 5, 9}, + { -6989, 21, 11}, { -6948, 11, 10}, { -6910, 23, 11}, { -6873, 3, 8}, + { -6837, 25, 11}, { -6803, 13, 10}, { -6770, 27, 11}, { -6739, 7, 9}, + { -6708, 29, 11}, { -6679, 15, 10}, { -6650, 31, 11}, { -6623, 1, 6}, + { -6570, 17, 10}, { -6520, 9, 9}, { -6473, 19, 10}, { -6429, 5, 8}, + { -6386, 21, 10}, { -6346, 11, 9}, { -6307, 23, 10}, { -6270, 3, 7}, + { -6235, 25, 10}, { -6201, 13, 9}, { -6168, 27, 10}, { -6137, 7, 8}, + { -6106, 29, 10}, { -6077, 15, 9}, { -6048, 31, 10}, { -6021, 1, 5}, + { -5968, 17, 9}, { -5918, 9, 8}, { -5871, 19, 9}, { -5827, 5, 7}, + { -5784, 21, 9}, { -5744, 11, 8}, { -5705, 23, 9}, { -5668, 3, 6}, + { -5633, 25, 9}, { -5599, 13, 8}, { -5566, 27, 9}, { -5535, 7, 7}, + { -5504, 29, 9}, { -5475, 15, 8}, { -5446, 31, 9}, { -5419, 1, 4}, + { -5366, 17, 8}, { -5316, 9, 7}, { -5269, 19, 8}, { -5225, 5, 6}, + { -5182, 21, 8}, { -5142, 11, 7}, { -5103, 23, 8}, { -5066, 3, 5}, + { -5031, 25, 8}, { -4997, 13, 7}, { -4964, 27, 8}, { -4932, 7, 6}, + { -4902, 29, 8}, { -4873, 15, 7}, { -4844, 31, 8}, { -4816, 1, 3}, + { -4764, 17, 7}, { -4714, 9, 6}, { -4667, 19, 7}, { -4623, 5, 5}, + { -4580, 21, 7}, { -4540, 11, 6}, { -4501, 23, 7}, { -4464, 3, 4}, + { -4429, 25, 7}, { -4395, 13, 6}, { -4362, 27, 7}, { -4330, 7, 5}, + { -4300, 29, 7}, { -4270, 15, 6}, { -4242, 31, 7}, { -4214, 1, 2}, + { -4162, 17, 6}, { -4112, 9, 5}, { -4065, 19, 6}, { -4021, 5, 4}, + { -3978, 21, 6}, { -3938, 11, 5}, { -3899, 23, 6}, { -3862, 3, 3}, + { -3827, 25, 6}, { -3793, 13, 5}, { -3760, 27, 6}, { -3728, 7, 4}, + { -3698, 29, 6}, { -3668, 15, 5}, { -3640, 31, 6}, { -3612, 1, 1}, + { -3560, 17, 5}, { -3510, 9, 4}, { -3463, 19, 5}, { -3419, 5, 3}, + { -3376, 21, 5}, { -3336, 11, 4}, { -3297, 23, 5}, { -3260, 3, 2}, + { -3225, 25, 5}, { -3191, 13, 4}, { -3158, 27, 5}, { -3126, 7, 3}, + { -3096, 29, 5}, { -3066, 15, 4}, { -3038, 31, 5}, { -3010, 1, 0}, + { -2958, 17, 4}, { -2908, 9, 3}, { -2861, 19, 4}, { -2816, 5, 2}, + { -2774, 21, 4}, { -2734, 11, 3}, { -2695, 23, 4}, { -2658, 3, 1}, + { -2623, 25, 4}, { -2589, 13, 3}, { -2556, 27, 4}, { -2524, 7, 2}, + { -2494, 29, 4}, { -2464, 15, 3}, { -2436, 31, 4}, { -2408, 2, 0}, + { -2356, 17, 3}, { -2306, 9, 2}, { -2259, 19, 3}, { -2214, 5, 1}, + { -2172, 21, 3}, { -2132, 11, 2}, { -2093, 23, 3}, { -2056, 3, 0}, + { -2021, 25, 3}, { -1987, 13, 2}, { -1954, 27, 3}, { -1922, 7, 1}, + { -1892, 29, 3}, { -1862, 15, 2}, { -1834, 31, 3}, { -1806, 4, 0}, + { -1754, 17, 2}, { -1704, 9, 1}, { -1657, 19, 2}, { -1612, 5, 0}, + { -1570, 21, 2}, { -1530, 11, 1}, { -1491, 23, 2}, { -1454, 6, 0}, + { -1419, 25, 2}, { -1384, 13, 1}, { -1352, 27, 2}, { -1320, 7, 0}, + { -1290, 29, 2}, { -1260, 15, 1}, { -1232, 31, 2}, { -1204, 8, 0}, + { -1151, 17, 1}, { -1102, 9, 0}, { -1055, 19, 1}, { -1010, 10, 0}, + { -968, 21, 1}, { -928, 11, 0}, { -889, 23, 1}, { -852, 12, 0}, + { -816, 25, 1}, { -782, 13, 0}, { -750, 27, 1}, { -718, 14, 0}, + { -688, 29, 1}, { -658, 15, 0}, { -630, 31, 1}, { -602, 16, 0}, + { -549, 17, 0}, { -500, 18, 0}, { -453, 19, 0}, { -408, 20, 0}, + { -366, 21, 0}, { -325, 22, 0}, { -287, 23, 0}, { -250, 24, 0}, + { -214, 25, 0}, { -180, 26, 0}, { -148, 27, 0}, { -116, 28, 0}, + { -86, 29, 0}, { -56, 30, 0}, { -28, 31, 0}, { 0, 0, 0}, +}; + +static int snd_soc_get_volsw_2r_st(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int val[2], val2[2], i; + + val[0] = snd_soc_component_read32(component, reg) & 0x3f; + val[1] = (snd_soc_component_read32(component, PM860X_SIDETONE_SHIFT) >> 4) & 0xf; + val2[0] = snd_soc_component_read32(component, reg2) & 0x3f; + val2[1] = (snd_soc_component_read32(component, PM860X_SIDETONE_SHIFT)) & 0xf; + + for (i = 0; i < ARRAY_SIZE(st_table); i++) { + if ((st_table[i].m == val[0]) && (st_table[i].n == val[1])) + ucontrol->value.integer.value[0] = i; + if ((st_table[i].m == val2[0]) && (st_table[i].n == val2[1])) + ucontrol->value.integer.value[1] = i; + } + return 0; +} + +static int snd_soc_put_volsw_2r_st(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int err; + unsigned int val, val2; + + val = ucontrol->value.integer.value[0]; + val2 = ucontrol->value.integer.value[1]; + + if (val >= ARRAY_SIZE(st_table) || val2 >= ARRAY_SIZE(st_table)) + return -EINVAL; + + err = snd_soc_component_update_bits(component, reg, 0x3f, st_table[val].m); + if (err < 0) + return err; + err = snd_soc_component_update_bits(component, PM860X_SIDETONE_SHIFT, 0xf0, + st_table[val].n << 4); + if (err < 0) + return err; + + err = snd_soc_component_update_bits(component, reg2, 0x3f, st_table[val2].m); + if (err < 0) + return err; + err = snd_soc_component_update_bits(component, PM860X_SIDETONE_SHIFT, 0x0f, + st_table[val2].n); + return err; +} + +static int snd_soc_get_volsw_2r_out(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max, val, val2; + unsigned int mask = (1 << fls(max)) - 1; + + val = snd_soc_component_read32(component, reg) >> shift; + val2 = snd_soc_component_read32(component, reg2) >> shift; + ucontrol->value.integer.value[0] = (max - val) & mask; + ucontrol->value.integer.value[1] = (max - val2) & mask; + + return 0; +} + +static int snd_soc_put_volsw_2r_out(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + int err; + unsigned int val, val2, val_mask; + + val_mask = mask << shift; + val = ((max - ucontrol->value.integer.value[0]) & mask); + val2 = ((max - ucontrol->value.integer.value[1]) & mask); + + val = val << shift; + val2 = val2 << shift; + + err = snd_soc_component_update_bits(component, reg, val_mask, val); + if (err < 0) + return err; + + err = snd_soc_component_update_bits(component, reg2, val_mask, val2); + return err; +} + +/* DAPM Widget Events */ +/* + * A lot registers are belong to RSYNC domain. It requires enabling RSYNC bit + * after updating these registers. Otherwise, these updated registers won't + * be effective. + */ +static int pm860x_rsync_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + /* + * In order to avoid current on the load, mute power-on and power-off + * should be transients. + * Unmute by DAC_MUTE. It should be unmuted when DAPM sequence is + * finished. + */ + snd_soc_component_update_bits(component, PM860X_DAC_OFFSET, DAC_MUTE, 0); + snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + return 0; +} + +static int pm860x_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int dac = 0; + int data; + + if (!strcmp(w->name, "Left DAC")) + dac = DAC_LEFT; + if (!strcmp(w->name, "Right DAC")) + dac = DAC_RIGHT; + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (dac) { + /* Auto mute in power-on sequence. */ + dac |= MODULATOR; + snd_soc_component_update_bits(component, PM860X_DAC_OFFSET, + DAC_MUTE, DAC_MUTE); + snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + /* update dac */ + snd_soc_component_update_bits(component, PM860X_DAC_EN_2, + dac, dac); + } + break; + case SND_SOC_DAPM_PRE_PMD: + if (dac) { + /* Auto mute in power-off sequence. */ + snd_soc_component_update_bits(component, PM860X_DAC_OFFSET, + DAC_MUTE, DAC_MUTE); + snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + /* update dac */ + data = snd_soc_component_read32(component, PM860X_DAC_EN_2); + data &= ~dac; + if (!(data & (DAC_LEFT | DAC_RIGHT))) + data &= ~MODULATOR; + snd_soc_component_write(component, PM860X_DAC_EN_2, data); + } + break; + } + return 0; +} + +static const char *pm860x_opamp_texts[] = {"-50%", "-25%", "0%", "75%"}; + +static const char *pm860x_pa_texts[] = {"-33%", "0%", "33%", "66%"}; + +static SOC_ENUM_SINGLE_DECL(pm860x_hs1_opamp_enum, + PM860X_HS1_CTRL, 5, pm860x_opamp_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_hs2_opamp_enum, + PM860X_HS2_CTRL, 5, pm860x_opamp_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_hs1_pa_enum, + PM860X_HS1_CTRL, 3, pm860x_pa_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_hs2_pa_enum, + PM860X_HS2_CTRL, 3, pm860x_pa_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_lo1_opamp_enum, + PM860X_LO1_CTRL, 5, pm860x_opamp_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_lo2_opamp_enum, + PM860X_LO2_CTRL, 5, pm860x_opamp_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_lo1_pa_enum, + PM860X_LO1_CTRL, 3, pm860x_pa_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_lo2_pa_enum, + PM860X_LO2_CTRL, 3, pm860x_pa_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_spk_pa_enum, + PM860X_EAR_CTRL_1, 5, pm860x_pa_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_ear_pa_enum, + PM860X_EAR_CTRL_2, 0, pm860x_pa_texts); + +static SOC_ENUM_SINGLE_DECL(pm860x_spk_ear_opamp_enum, + PM860X_EAR_CTRL_1, 3, pm860x_opamp_texts); + +static const struct snd_kcontrol_new pm860x_snd_controls[] = { + SOC_DOUBLE_R_TLV("ADC Capture Volume", PM860X_ADC_ANA_2, + PM860X_ADC_ANA_3, 6, 3, 0, adc_tlv), + SOC_DOUBLE_TLV("AUX Capture Volume", PM860X_ADC_ANA_3, 0, 3, 7, 0, + aux_tlv), + SOC_SINGLE_TLV("MIC1 Capture Volume", PM860X_ADC_ANA_2, 0, 7, 0, + mic_tlv), + SOC_SINGLE_TLV("MIC3 Capture Volume", PM860X_ADC_ANA_2, 3, 7, 0, + mic_tlv), + SOC_DOUBLE_R_EXT_TLV("Sidetone Volume", PM860X_SIDETONE_L_GAIN, + PM860X_SIDETONE_R_GAIN, 0, ARRAY_SIZE(st_table)-1, + 0, snd_soc_get_volsw_2r_st, + snd_soc_put_volsw_2r_st, st_tlv), + SOC_SINGLE_TLV("Speaker Playback Volume", PM860X_EAR_CTRL_1, + 0, 7, 0, out_tlv), + SOC_DOUBLE_R_TLV("Line Playback Volume", PM860X_LO1_CTRL, + PM860X_LO2_CTRL, 0, 7, 0, out_tlv), + SOC_DOUBLE_R_TLV("Headset Playback Volume", PM860X_HS1_CTRL, + PM860X_HS2_CTRL, 0, 7, 0, out_tlv), + SOC_DOUBLE_R_EXT_TLV("Hifi Left Playback Volume", + PM860X_HIFIL_GAIN_LEFT, + PM860X_HIFIL_GAIN_RIGHT, 0, 63, 0, + snd_soc_get_volsw_2r_out, + snd_soc_put_volsw_2r_out, dpga_tlv), + SOC_DOUBLE_R_EXT_TLV("Hifi Right Playback Volume", + PM860X_HIFIR_GAIN_LEFT, + PM860X_HIFIR_GAIN_RIGHT, 0, 63, 0, + snd_soc_get_volsw_2r_out, + snd_soc_put_volsw_2r_out, dpga_tlv), + SOC_DOUBLE_R_EXT_TLV("Lofi Playback Volume", PM860X_LOFI_GAIN_LEFT, + PM860X_LOFI_GAIN_RIGHT, 0, 63, 0, + snd_soc_get_volsw_2r_out, + snd_soc_put_volsw_2r_out, dpga_tlv), + SOC_ENUM("Headset1 Operational Amplifier Current", + pm860x_hs1_opamp_enum), + SOC_ENUM("Headset2 Operational Amplifier Current", + pm860x_hs2_opamp_enum), + SOC_ENUM("Headset1 Amplifier Current", pm860x_hs1_pa_enum), + SOC_ENUM("Headset2 Amplifier Current", pm860x_hs2_pa_enum), + SOC_ENUM("Lineout1 Operational Amplifier Current", + pm860x_lo1_opamp_enum), + SOC_ENUM("Lineout2 Operational Amplifier Current", + pm860x_lo2_opamp_enum), + SOC_ENUM("Lineout1 Amplifier Current", pm860x_lo1_pa_enum), + SOC_ENUM("Lineout2 Amplifier Current", pm860x_lo2_pa_enum), + SOC_ENUM("Speaker Operational Amplifier Current", + pm860x_spk_ear_opamp_enum), + SOC_ENUM("Speaker Amplifier Current", pm860x_spk_pa_enum), + SOC_ENUM("Earpiece Amplifier Current", pm860x_ear_pa_enum), +}; + +/* + * DAPM Controls + */ + +/* PCM Switch / PCM Interface */ +static const struct snd_kcontrol_new pcm_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_ADC_EN_2, 0, 1, 0); + +/* AUX1 Switch */ +static const struct snd_kcontrol_new aux1_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 4, 1, 0); + +/* AUX2 Switch */ +static const struct snd_kcontrol_new aux2_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 5, 1, 0); + +/* Left Ex. PA Switch */ +static const struct snd_kcontrol_new lepa_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 2, 1, 0); + +/* Right Ex. PA Switch */ +static const struct snd_kcontrol_new repa_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 1, 1, 0); + +/* PCM Mux / Mux7 */ +static const char *aif1_text[] = { + "PCM L", "PCM R", +}; + +static SOC_ENUM_SINGLE_DECL(aif1_enum, + PM860X_PCM_IFACE_3, 6, aif1_text); + +static const struct snd_kcontrol_new aif1_mux = + SOC_DAPM_ENUM("PCM Mux", aif1_enum); + +/* I2S Mux / Mux9 */ +static const char *i2s_din_text[] = { + "DIN", "DIN1", +}; + +static SOC_ENUM_SINGLE_DECL(i2s_din_enum, + PM860X_I2S_IFACE_3, 1, i2s_din_text); + +static const struct snd_kcontrol_new i2s_din_mux = + SOC_DAPM_ENUM("I2S DIN Mux", i2s_din_enum); + +/* I2S Mic Mux / Mux8 */ +static const char *i2s_mic_text[] = { + "Ex PA", "ADC", +}; + +static SOC_ENUM_SINGLE_DECL(i2s_mic_enum, + PM860X_I2S_IFACE_3, 4, i2s_mic_text); + +static const struct snd_kcontrol_new i2s_mic_mux = + SOC_DAPM_ENUM("I2S Mic Mux", i2s_mic_enum); + +/* ADCL Mux / Mux2 */ +static const char *adcl_text[] = { + "ADCR", "ADCL", +}; + +static SOC_ENUM_SINGLE_DECL(adcl_enum, + PM860X_PCM_IFACE_3, 4, adcl_text); + +static const struct snd_kcontrol_new adcl_mux = + SOC_DAPM_ENUM("ADC Left Mux", adcl_enum); + +/* ADCR Mux / Mux3 */ +static const char *adcr_text[] = { + "ADCL", "ADCR", +}; + +static SOC_ENUM_SINGLE_DECL(adcr_enum, + PM860X_PCM_IFACE_3, 2, adcr_text); + +static const struct snd_kcontrol_new adcr_mux = + SOC_DAPM_ENUM("ADC Right Mux", adcr_enum); + +/* ADCR EC Mux / Mux6 */ +static const char *adcr_ec_text[] = { + "ADCR", "EC", +}; + +static SOC_ENUM_SINGLE_DECL(adcr_ec_enum, + PM860X_ADC_EN_2, 3, adcr_ec_text); + +static const struct snd_kcontrol_new adcr_ec_mux = + SOC_DAPM_ENUM("ADCR EC Mux", adcr_ec_enum); + +/* EC Mux / Mux4 */ +static const char *ec_text[] = { + "Left", "Right", "Left + Right", +}; + +static SOC_ENUM_SINGLE_DECL(ec_enum, + PM860X_EC_PATH, 1, ec_text); + +static const struct snd_kcontrol_new ec_mux = + SOC_DAPM_ENUM("EC Mux", ec_enum); + +static const char *dac_text[] = { + "No input", "Right", "Left", "No input", +}; + +/* DAC Headset 1 Mux / Mux10 */ +static SOC_ENUM_SINGLE_DECL(dac_hs1_enum, + PM860X_ANA_INPUT_SEL_1, 0, dac_text); + +static const struct snd_kcontrol_new dac_hs1_mux = + SOC_DAPM_ENUM("DAC HS1 Mux", dac_hs1_enum); + +/* DAC Headset 2 Mux / Mux11 */ +static SOC_ENUM_SINGLE_DECL(dac_hs2_enum, + PM860X_ANA_INPUT_SEL_1, 2, dac_text); + +static const struct snd_kcontrol_new dac_hs2_mux = + SOC_DAPM_ENUM("DAC HS2 Mux", dac_hs2_enum); + +/* DAC Lineout 1 Mux / Mux12 */ +static SOC_ENUM_SINGLE_DECL(dac_lo1_enum, + PM860X_ANA_INPUT_SEL_1, 4, dac_text); + +static const struct snd_kcontrol_new dac_lo1_mux = + SOC_DAPM_ENUM("DAC LO1 Mux", dac_lo1_enum); + +/* DAC Lineout 2 Mux / Mux13 */ +static SOC_ENUM_SINGLE_DECL(dac_lo2_enum, + PM860X_ANA_INPUT_SEL_1, 6, dac_text); + +static const struct snd_kcontrol_new dac_lo2_mux = + SOC_DAPM_ENUM("DAC LO2 Mux", dac_lo2_enum); + +/* DAC Spearker Earphone Mux / Mux14 */ +static SOC_ENUM_SINGLE_DECL(dac_spk_ear_enum, + PM860X_ANA_INPUT_SEL_2, 0, dac_text); + +static const struct snd_kcontrol_new dac_spk_ear_mux = + SOC_DAPM_ENUM("DAC SP Mux", dac_spk_ear_enum); + +/* Headset 1 Mux / Mux15 */ +static const char *in_text[] = { + "Digital", "Analog", +}; + +static SOC_ENUM_SINGLE_DECL(hs1_enum, + PM860X_ANA_TO_ANA, 0, in_text); + +static const struct snd_kcontrol_new hs1_mux = + SOC_DAPM_ENUM("Headset1 Mux", hs1_enum); + +/* Headset 2 Mux / Mux16 */ +static SOC_ENUM_SINGLE_DECL(hs2_enum, + PM860X_ANA_TO_ANA, 1, in_text); + +static const struct snd_kcontrol_new hs2_mux = + SOC_DAPM_ENUM("Headset2 Mux", hs2_enum); + +/* Lineout 1 Mux / Mux17 */ +static SOC_ENUM_SINGLE_DECL(lo1_enum, + PM860X_ANA_TO_ANA, 2, in_text); + +static const struct snd_kcontrol_new lo1_mux = + SOC_DAPM_ENUM("Lineout1 Mux", lo1_enum); + +/* Lineout 2 Mux / Mux18 */ +static SOC_ENUM_SINGLE_DECL(lo2_enum, + PM860X_ANA_TO_ANA, 3, in_text); + +static const struct snd_kcontrol_new lo2_mux = + SOC_DAPM_ENUM("Lineout2 Mux", lo2_enum); + +/* Speaker Earpiece Demux */ +static const char *spk_text[] = { + "Earpiece", "Speaker", +}; + +static SOC_ENUM_SINGLE_DECL(spk_enum, + PM860X_ANA_TO_ANA, 6, spk_text); + +static const struct snd_kcontrol_new spk_demux = + SOC_DAPM_ENUM("Speaker Earpiece Demux", spk_enum); + +/* MIC Mux / Mux1 */ +static const char *mic_text[] = { + "Mic 1", "Mic 2", +}; + +static SOC_ENUM_SINGLE_DECL(mic_enum, + PM860X_ADC_ANA_4, 4, mic_text); + +static const struct snd_kcontrol_new mic_mux = + SOC_DAPM_ENUM("MIC Mux", mic_enum); + +static const struct snd_soc_dapm_widget pm860x_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("PCM SDI", "PCM Playback", 0, + PM860X_ADC_EN_2, 0, 0), + SND_SOC_DAPM_AIF_OUT("PCM SDO", "PCM Capture", 0, + PM860X_PCM_IFACE_3, 1, 1), + + + SND_SOC_DAPM_AIF_IN("I2S DIN", "I2S Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("I2S DIN1", "I2S Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("I2S DOUT", "I2S Capture", 0, + PM860X_I2S_IFACE_3, 5, 1), + SND_SOC_DAPM_SUPPLY("I2S CLK", PM860X_DAC_EN_2, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("I2S Mic Mux", SND_SOC_NOPM, 0, 0, &i2s_mic_mux), + SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adcl_mux), + SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcr_mux), + SND_SOC_DAPM_MUX("EC Mux", SND_SOC_NOPM, 0, 0, &ec_mux), + SND_SOC_DAPM_MUX("ADCR EC Mux", SND_SOC_NOPM, 0, 0, &adcr_ec_mux), + SND_SOC_DAPM_SWITCH("Left EPA", SND_SOC_NOPM, 0, 0, + &lepa_switch_controls), + SND_SOC_DAPM_SWITCH("Right EPA", SND_SOC_NOPM, 0, 0, + &repa_switch_controls), + + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Left ADC MOD", PM860X_ADC_EN_1, + 0, 1, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Right ADC MOD", PM860X_ADC_EN_1, + 1, 1, 1, 0), + SND_SOC_DAPM_ADC("Left ADC", NULL, PM860X_ADC_EN_2, 5, 0), + SND_SOC_DAPM_ADC("Right ADC", NULL, PM860X_ADC_EN_2, 4, 0), + + SND_SOC_DAPM_SWITCH("AUX1 Switch", SND_SOC_NOPM, 0, 0, + &aux1_switch_controls), + SND_SOC_DAPM_SWITCH("AUX2 Switch", SND_SOC_NOPM, 0, 0, + &aux2_switch_controls), + + SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &mic_mux), + SND_SOC_DAPM_MICBIAS("Mic1 Bias", PM860X_ADC_ANA_1, 2, 0), + SND_SOC_DAPM_MICBIAS("Mic3 Bias", PM860X_ADC_ANA_1, 7, 0), + SND_SOC_DAPM_PGA("MIC1 Volume", PM860X_ADC_EN_1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIC3 Volume", PM860X_ADC_EN_1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX1 Volume", PM860X_ADC_EN_1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX2 Volume", PM860X_ADC_EN_1, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Sidetone PGA", PM860X_ADC_EN_2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lofi PGA", PM860X_ADC_EN_2, 2, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("AUX1"), + SND_SOC_DAPM_INPUT("AUX2"), + SND_SOC_DAPM_INPUT("MIC1P"), + SND_SOC_DAPM_INPUT("MIC1N"), + SND_SOC_DAPM_INPUT("MIC2P"), + SND_SOC_DAPM_INPUT("MIC2N"), + SND_SOC_DAPM_INPUT("MIC3P"), + SND_SOC_DAPM_INPUT("MIC3N"), + + SND_SOC_DAPM_DAC_E("Left DAC", NULL, SND_SOC_NOPM, 0, 0, + pm860x_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("Right DAC", NULL, SND_SOC_NOPM, 0, 0, + pm860x_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("I2S DIN Mux", SND_SOC_NOPM, 0, 0, &i2s_din_mux), + SND_SOC_DAPM_MUX("DAC HS1 Mux", SND_SOC_NOPM, 0, 0, &dac_hs1_mux), + SND_SOC_DAPM_MUX("DAC HS2 Mux", SND_SOC_NOPM, 0, 0, &dac_hs2_mux), + SND_SOC_DAPM_MUX("DAC LO1 Mux", SND_SOC_NOPM, 0, 0, &dac_lo1_mux), + SND_SOC_DAPM_MUX("DAC LO2 Mux", SND_SOC_NOPM, 0, 0, &dac_lo2_mux), + SND_SOC_DAPM_MUX("DAC SP Mux", SND_SOC_NOPM, 0, 0, &dac_spk_ear_mux), + SND_SOC_DAPM_MUX("Headset1 Mux", SND_SOC_NOPM, 0, 0, &hs1_mux), + SND_SOC_DAPM_MUX("Headset2 Mux", SND_SOC_NOPM, 0, 0, &hs2_mux), + SND_SOC_DAPM_MUX("Lineout1 Mux", SND_SOC_NOPM, 0, 0, &lo1_mux), + SND_SOC_DAPM_MUX("Lineout2 Mux", SND_SOC_NOPM, 0, 0, &lo2_mux), + SND_SOC_DAPM_MUX("Speaker Earpiece Demux", SND_SOC_NOPM, 0, 0, + &spk_demux), + + + SND_SOC_DAPM_PGA("Headset1 PGA", PM860X_DAC_EN_1, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset2 PGA", PM860X_DAC_EN_1, 1, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("HS1"), + SND_SOC_DAPM_OUTPUT("HS2"), + SND_SOC_DAPM_PGA("Lineout1 PGA", PM860X_DAC_EN_1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout2 PGA", PM860X_DAC_EN_1, 3, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("LINEOUT1"), + SND_SOC_DAPM_OUTPUT("LINEOUT2"), + SND_SOC_DAPM_PGA("Earpiece PGA", PM860X_DAC_EN_1, 4, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("EARP"), + SND_SOC_DAPM_OUTPUT("EARN"), + SND_SOC_DAPM_PGA("Speaker PGA", PM860X_DAC_EN_1, 5, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("LSP"), + SND_SOC_DAPM_OUTPUT("LSN"), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "VCODEC", PM860X_AUDIO_SUPPLIES_2, + 0, SUPPLY_MASK, SUPPLY_MASK, 0), + + PM860X_DAPM_OUTPUT("RSYNC", pm860x_rsync_event), +}; + +static const struct snd_soc_dapm_route pm860x_dapm_routes[] = { + /* supply */ + {"Left DAC", NULL, "VCODEC"}, + {"Right DAC", NULL, "VCODEC"}, + {"Left ADC", NULL, "VCODEC"}, + {"Right ADC", NULL, "VCODEC"}, + {"Left ADC", NULL, "Left ADC MOD"}, + {"Right ADC", NULL, "Right ADC MOD"}, + + /* I2S Clock */ + {"I2S DIN", NULL, "I2S CLK"}, + {"I2S DIN1", NULL, "I2S CLK"}, + {"I2S DOUT", NULL, "I2S CLK"}, + + /* PCM/AIF1 Inputs */ + {"PCM SDO", NULL, "ADC Left Mux"}, + {"PCM SDO", NULL, "ADCR EC Mux"}, + + /* PCM/AFI2 Outputs */ + {"Lofi PGA", NULL, "PCM SDI"}, + {"Lofi PGA", NULL, "Sidetone PGA"}, + {"Left DAC", NULL, "Lofi PGA"}, + {"Right DAC", NULL, "Lofi PGA"}, + + /* I2S/AIF2 Inputs */ + {"MIC Mux", "Mic 1", "MIC1P"}, + {"MIC Mux", "Mic 1", "MIC1N"}, + {"MIC Mux", "Mic 2", "MIC2P"}, + {"MIC Mux", "Mic 2", "MIC2N"}, + {"MIC1 Volume", NULL, "MIC Mux"}, + {"MIC3 Volume", NULL, "MIC3P"}, + {"MIC3 Volume", NULL, "MIC3N"}, + {"Left ADC", NULL, "MIC1 Volume"}, + {"Right ADC", NULL, "MIC3 Volume"}, + {"ADC Left Mux", "ADCR", "Right ADC"}, + {"ADC Left Mux", "ADCL", "Left ADC"}, + {"ADC Right Mux", "ADCL", "Left ADC"}, + {"ADC Right Mux", "ADCR", "Right ADC"}, + {"Left EPA", "Switch", "Left DAC"}, + {"Right EPA", "Switch", "Right DAC"}, + {"EC Mux", "Left", "Left DAC"}, + {"EC Mux", "Right", "Right DAC"}, + {"EC Mux", "Left + Right", "Left DAC"}, + {"EC Mux", "Left + Right", "Right DAC"}, + {"ADCR EC Mux", "ADCR", "ADC Right Mux"}, + {"ADCR EC Mux", "EC", "EC Mux"}, + {"I2S Mic Mux", "Ex PA", "Left EPA"}, + {"I2S Mic Mux", "Ex PA", "Right EPA"}, + {"I2S Mic Mux", "ADC", "ADC Left Mux"}, + {"I2S Mic Mux", "ADC", "ADCR EC Mux"}, + {"I2S DOUT", NULL, "I2S Mic Mux"}, + + /* I2S/AIF2 Outputs */ + {"I2S DIN Mux", "DIN", "I2S DIN"}, + {"I2S DIN Mux", "DIN1", "I2S DIN1"}, + {"Left DAC", NULL, "I2S DIN Mux"}, + {"Right DAC", NULL, "I2S DIN Mux"}, + {"DAC HS1 Mux", "Left", "Left DAC"}, + {"DAC HS1 Mux", "Right", "Right DAC"}, + {"DAC HS2 Mux", "Left", "Left DAC"}, + {"DAC HS2 Mux", "Right", "Right DAC"}, + {"DAC LO1 Mux", "Left", "Left DAC"}, + {"DAC LO1 Mux", "Right", "Right DAC"}, + {"DAC LO2 Mux", "Left", "Left DAC"}, + {"DAC LO2 Mux", "Right", "Right DAC"}, + {"Headset1 Mux", "Digital", "DAC HS1 Mux"}, + {"Headset2 Mux", "Digital", "DAC HS2 Mux"}, + {"Lineout1 Mux", "Digital", "DAC LO1 Mux"}, + {"Lineout2 Mux", "Digital", "DAC LO2 Mux"}, + {"Headset1 PGA", NULL, "Headset1 Mux"}, + {"Headset2 PGA", NULL, "Headset2 Mux"}, + {"Lineout1 PGA", NULL, "Lineout1 Mux"}, + {"Lineout2 PGA", NULL, "Lineout2 Mux"}, + {"DAC SP Mux", "Left", "Left DAC"}, + {"DAC SP Mux", "Right", "Right DAC"}, + {"Speaker Earpiece Demux", "Speaker", "DAC SP Mux"}, + {"Speaker PGA", NULL, "Speaker Earpiece Demux"}, + {"Earpiece PGA", NULL, "Speaker Earpiece Demux"}, + + {"RSYNC", NULL, "Headset1 PGA"}, + {"RSYNC", NULL, "Headset2 PGA"}, + {"RSYNC", NULL, "Lineout1 PGA"}, + {"RSYNC", NULL, "Lineout2 PGA"}, + {"RSYNC", NULL, "Speaker PGA"}, + {"RSYNC", NULL, "Speaker PGA"}, + {"RSYNC", NULL, "Earpiece PGA"}, + {"RSYNC", NULL, "Earpiece PGA"}, + + {"HS1", NULL, "RSYNC"}, + {"HS2", NULL, "RSYNC"}, + {"LINEOUT1", NULL, "RSYNC"}, + {"LINEOUT2", NULL, "RSYNC"}, + {"LSP", NULL, "RSYNC"}, + {"LSN", NULL, "RSYNC"}, + {"EARP", NULL, "RSYNC"}, + {"EARN", NULL, "RSYNC"}, +}; + +/* + * Use MUTE_LEFT & MUTE_RIGHT to implement digital mute. + * These bits can also be used to mute. + */ +static int pm860x_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + int data = 0, mask = MUTE_LEFT | MUTE_RIGHT; + + if (mute) + data = mask; + snd_soc_component_update_bits(component, PM860X_DAC_OFFSET, mask, data); + snd_soc_component_update_bits(component, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + return 0; +} + +static int pm860x_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + unsigned char inf = 0, mask = 0; + + /* bit size */ + switch (params_width(params)) { + case 16: + inf &= ~PCM_INF2_18WL; + break; + case 18: + inf |= PCM_INF2_18WL; + break; + default: + return -EINVAL; + } + mask |= PCM_INF2_18WL; + snd_soc_component_update_bits(component, PM860X_PCM_IFACE_2, mask, inf); + + /* sample rate */ + switch (params_rate(params)) { + case 8000: + inf = 0; + break; + case 16000: + inf = 3; + break; + case 32000: + inf = 6; + break; + case 48000: + inf = 8; + break; + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, PM860X_PCM_RATE, 0x0f, inf); + + return 0; +} + +static int pm860x_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + unsigned char inf = 0, mask = 0; + int ret = -EINVAL; + + mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + if (pm860x->dir == PM860X_CLK_DIR_OUT) { + inf |= PCM_INF2_MASTER; + ret = 0; + } + break; + case SND_SOC_DAIFMT_CBS_CFS: + if (pm860x->dir == PM860X_CLK_DIR_IN) { + inf &= ~PCM_INF2_MASTER; + ret = 0; + } + break; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + inf |= PCM_EXACT_I2S; + ret = 0; + break; + } + mask |= PCM_MODE_MASK; + if (ret) + return ret; + snd_soc_component_update_bits(component, PM860X_PCM_IFACE_2, mask, inf); + return 0; +} + +static int pm860x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + + if (dir == PM860X_CLK_DIR_OUT) + pm860x->dir = PM860X_CLK_DIR_OUT; + else /* Slave mode is not supported */ + return -EINVAL; + + return 0; +} + +static int pm860x_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + unsigned char inf; + + /* bit size */ + switch (params_width(params)) { + case 16: + inf = 0; + break; + case 18: + inf = PCM_INF2_18WL; + break; + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, PM860X_I2S_IFACE_2, PCM_INF2_18WL, inf); + + /* sample rate */ + switch (params_rate(params)) { + case 8000: + inf = 0; + break; + case 11025: + inf = 1; + break; + case 16000: + inf = 3; + break; + case 22050: + inf = 4; + break; + case 32000: + inf = 6; + break; + case 44100: + inf = 7; + break; + case 48000: + inf = 8; + break; + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, PM860X_I2S_IFACE_4, 0xf, inf); + + return 0; +} + +static int pm860x_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + unsigned char inf = 0, mask = 0; + + mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + if (pm860x->dir == PM860X_CLK_DIR_OUT) + inf |= PCM_INF2_MASTER; + else + return -EINVAL; + break; + case SND_SOC_DAIFMT_CBS_CFS: + if (pm860x->dir == PM860X_CLK_DIR_IN) + inf &= ~PCM_INF2_MASTER; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + inf |= PCM_EXACT_I2S; + break; + default: + return -EINVAL; + } + mask |= PCM_MODE_MASK; + snd_soc_component_update_bits(component, PM860X_I2S_IFACE_2, mask, inf); + return 0; +} + +static int pm860x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + int data; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Enable Audio PLL & Audio section */ + data = AUDIO_PLL | AUDIO_SECTION_ON; + pm860x_reg_write(pm860x->i2c, REG_MISC2, data); + udelay(300); + data = AUDIO_PLL | AUDIO_SECTION_RESET + | AUDIO_SECTION_ON; + pm860x_reg_write(pm860x->i2c, REG_MISC2, data); + } + break; + + case SND_SOC_BIAS_OFF: + data = AUDIO_PLL | AUDIO_SECTION_RESET | AUDIO_SECTION_ON; + pm860x_set_bits(pm860x->i2c, REG_MISC2, data, 0); + break; + } + return 0; +} + +static const struct snd_soc_dai_ops pm860x_pcm_dai_ops = { + .digital_mute = pm860x_digital_mute, + .hw_params = pm860x_pcm_hw_params, + .set_fmt = pm860x_pcm_set_dai_fmt, + .set_sysclk = pm860x_set_dai_sysclk, +}; + +static const struct snd_soc_dai_ops pm860x_i2s_dai_ops = { + .digital_mute = pm860x_digital_mute, + .hw_params = pm860x_i2s_hw_params, + .set_fmt = pm860x_i2s_set_dai_fmt, + .set_sysclk = pm860x_set_dai_sysclk, +}; + +#define PM860X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000) + +static struct snd_soc_dai_driver pm860x_dai[] = { + { + /* DAI PCM */ + .name = "88pm860x-pcm", + .id = 1, + .playback = { + .stream_name = "PCM Playback", + .channels_min = 2, + .channels_max = 2, + .rates = PM860X_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE, + }, + .capture = { + .stream_name = "PCM Capture", + .channels_min = 2, + .channels_max = 2, + .rates = PM860X_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE, + }, + .ops = &pm860x_pcm_dai_ops, + }, { + /* DAI I2S */ + .name = "88pm860x-i2s", + .id = 2, + .playback = { + .stream_name = "I2S Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE, + }, + .capture = { + .stream_name = "I2S Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE, + }, + .ops = &pm860x_i2s_dai_ops, + }, +}; + +static irqreturn_t pm860x_component_handler(int irq, void *data) +{ + struct pm860x_priv *pm860x = data; + int status, shrt, report = 0, mic_report = 0; + int mask; + + status = pm860x_reg_read(pm860x->i2c, REG_STATUS_1); + shrt = pm860x_reg_read(pm860x->i2c, REG_SHORTS); + mask = pm860x->det.hs_shrt | pm860x->det.hook_det | pm860x->det.lo_shrt + | pm860x->det.hp_det; + +#ifndef CONFIG_SND_SOC_88PM860X_MODULE + if (status & (HEADSET_STATUS | MIC_STATUS | SHORT_HS1 | SHORT_HS2 | + SHORT_LO1 | SHORT_LO2)) + trace_snd_soc_jack_irq(dev_name(pm860x->component->dev)); +#endif + + if ((pm860x->det.hp_det & SND_JACK_HEADPHONE) + && (status & HEADSET_STATUS)) + report |= SND_JACK_HEADPHONE; + + if ((pm860x->det.mic_det & SND_JACK_MICROPHONE) + && (status & MIC_STATUS)) + mic_report |= SND_JACK_MICROPHONE; + + if (pm860x->det.hs_shrt && (shrt & (SHORT_HS1 | SHORT_HS2))) + report |= pm860x->det.hs_shrt; + + if (pm860x->det.hook_det && (status & HOOK_STATUS)) + report |= pm860x->det.hook_det; + + if (pm860x->det.lo_shrt && (shrt & (SHORT_LO1 | SHORT_LO2))) + report |= pm860x->det.lo_shrt; + + if (report) + snd_soc_jack_report(pm860x->det.hp_jack, report, mask); + if (mic_report) + snd_soc_jack_report(pm860x->det.mic_jack, SND_JACK_MICROPHONE, + SND_JACK_MICROPHONE); + + dev_dbg(pm860x->component->dev, "headphone report:0x%x, mask:%x\n", + report, mask); + dev_dbg(pm860x->component->dev, "microphone report:0x%x\n", mic_report); + return IRQ_HANDLED; +} + +int pm860x_hs_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, + int det, int hook, int hs_shrt, int lo_shrt) +{ + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + int data; + + pm860x->det.hp_jack = jack; + pm860x->det.hp_det = det; + pm860x->det.hook_det = hook; + pm860x->det.hs_shrt = hs_shrt; + pm860x->det.lo_shrt = lo_shrt; + + if (det & SND_JACK_HEADPHONE) + pm860x_set_bits(pm860x->i2c, REG_HS_DET, + EN_HS_DET, EN_HS_DET); + /* headset short detect */ + if (hs_shrt) { + data = CLR_SHORT_HS2 | CLR_SHORT_HS1; + pm860x_set_bits(pm860x->i2c, REG_SHORTS, data, data); + } + /* Lineout short detect */ + if (lo_shrt) { + data = CLR_SHORT_LO2 | CLR_SHORT_LO1; + pm860x_set_bits(pm860x->i2c, REG_SHORTS, data, data); + } + + /* sync status */ + pm860x_component_handler(0, pm860x); + return 0; +} +EXPORT_SYMBOL_GPL(pm860x_hs_jack_detect); + +int pm860x_mic_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, int det) +{ + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + + pm860x->det.mic_jack = jack; + pm860x->det.mic_det = det; + + if (det & SND_JACK_MICROPHONE) + pm860x_set_bits(pm860x->i2c, REG_MIC_DET, + MICDET_MASK, MICDET_MASK); + + /* sync status */ + pm860x_component_handler(0, pm860x); + return 0; +} +EXPORT_SYMBOL_GPL(pm860x_mic_jack_detect); + +static int pm860x_probe(struct snd_soc_component *component) +{ + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + int i, ret; + + pm860x->component = component; + snd_soc_component_init_regmap(component, pm860x->regmap); + + for (i = 0; i < 4; i++) { + ret = request_threaded_irq(pm860x->irq[i], NULL, + pm860x_component_handler, IRQF_ONESHOT, + pm860x->name[i], pm860x); + if (ret < 0) { + dev_err(component->dev, "Failed to request IRQ!\n"); + goto out; + } + } + + return 0; + +out: + while (--i >= 0) + free_irq(pm860x->irq[i], pm860x); + return ret; +} + +static void pm860x_remove(struct snd_soc_component *component) +{ + struct pm860x_priv *pm860x = snd_soc_component_get_drvdata(component); + int i; + + for (i = 3; i >= 0; i--) + free_irq(pm860x->irq[i], pm860x); +} + +static const struct snd_soc_component_driver soc_component_dev_pm860x = { + .probe = pm860x_probe, + .remove = pm860x_remove, + .set_bias_level = pm860x_set_bias_level, + .controls = pm860x_snd_controls, + .num_controls = ARRAY_SIZE(pm860x_snd_controls), + .dapm_widgets = pm860x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pm860x_dapm_widgets), + .dapm_routes = pm860x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pm860x_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int pm860x_codec_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_priv *pm860x; + struct resource *res; + int i, ret; + + pm860x = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_priv), + GFP_KERNEL); + if (pm860x == NULL) + return -ENOMEM; + + pm860x->chip = chip; + pm860x->i2c = (chip->id == CHIP_PM8607) ? chip->client + : chip->companion; + pm860x->regmap = (chip->id == CHIP_PM8607) ? chip->regmap + : chip->regmap_companion; + platform_set_drvdata(pdev, pm860x); + + for (i = 0; i < 4; i++) { + res = platform_get_resource(pdev, IORESOURCE_IRQ, i); + if (!res) { + dev_err(&pdev->dev, "Failed to get IRQ resources\n"); + return -EINVAL; + } + pm860x->irq[i] = res->start + chip->irq_base; + strncpy(pm860x->name[i], res->name, MAX_NAME_LEN); + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_pm860x, + pm860x_dai, ARRAY_SIZE(pm860x_dai)); + if (ret) { + dev_err(&pdev->dev, "Failed to register component\n"); + return -EINVAL; + } + return ret; +} + +static int pm860x_codec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver pm860x_codec_driver = { + .driver = { + .name = "88pm860x-codec", + }, + .probe = pm860x_codec_probe, + .remove = pm860x_codec_remove, +}; + +module_platform_driver(pm860x_codec_driver); + +MODULE_DESCRIPTION("ASoC 88PM860x driver"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:88pm860x-codec"); + diff --git a/sound/soc/codecs/88pm860x-codec.h b/sound/soc/codecs/88pm860x-codec.h new file mode 100644 index 000000000..33aa9ff34 --- /dev/null +++ b/sound/soc/codecs/88pm860x-codec.h @@ -0,0 +1,96 @@ +/* + * 88pm860x-codec.h -- 88PM860x ALSA SoC Audio Driver + * + * Copyright 2010 Marvell International Ltd. + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __88PM860X_H +#define __88PM860X_H + +#define PM860X_PCM_IFACE_1 0xb0 +#define PM860X_PCM_IFACE_2 0xb1 +#define PM860X_PCM_IFACE_3 0xb2 +#define PM860X_PCM_RATE 0xb3 +#define PM860X_EC_PATH 0xb4 +#define PM860X_SIDETONE_L_GAIN 0xb5 +#define PM860X_SIDETONE_R_GAIN 0xb6 +#define PM860X_SIDETONE_SHIFT 0xb7 +#define PM860X_ADC_OFFSET_1 0xb8 +#define PM860X_ADC_OFFSET_2 0xb9 +#define PM860X_DMIC_DELAY 0xba + +#define PM860X_I2S_IFACE_1 0xbb +#define PM860X_I2S_IFACE_2 0xbc +#define PM860X_I2S_IFACE_3 0xbd +#define PM860X_I2S_IFACE_4 0xbe +#define PM860X_EQUALIZER_N0_1 0xbf +#define PM860X_EQUALIZER_N0_2 0xc0 +#define PM860X_EQUALIZER_N1_1 0xc1 +#define PM860X_EQUALIZER_N1_2 0xc2 +#define PM860X_EQUALIZER_D1_1 0xc3 +#define PM860X_EQUALIZER_D1_2 0xc4 +#define PM860X_LOFI_GAIN_LEFT 0xc5 +#define PM860X_LOFI_GAIN_RIGHT 0xc6 +#define PM860X_HIFIL_GAIN_LEFT 0xc7 +#define PM860X_HIFIL_GAIN_RIGHT 0xc8 +#define PM860X_HIFIR_GAIN_LEFT 0xc9 +#define PM860X_HIFIR_GAIN_RIGHT 0xca +#define PM860X_DAC_OFFSET 0xcb +#define PM860X_OFFSET_LEFT_1 0xcc +#define PM860X_OFFSET_LEFT_2 0xcd +#define PM860X_OFFSET_RIGHT_1 0xce +#define PM860X_OFFSET_RIGHT_2 0xcf +#define PM860X_ADC_ANA_1 0xd0 +#define PM860X_ADC_ANA_2 0xd1 +#define PM860X_ADC_ANA_3 0xd2 +#define PM860X_ADC_ANA_4 0xd3 +#define PM860X_ANA_TO_ANA 0xd4 +#define PM860X_HS1_CTRL 0xd5 +#define PM860X_HS2_CTRL 0xd6 +#define PM860X_LO1_CTRL 0xd7 +#define PM860X_LO2_CTRL 0xd8 +#define PM860X_EAR_CTRL_1 0xd9 +#define PM860X_EAR_CTRL_2 0xda +#define PM860X_AUDIO_SUPPLIES_1 0xdb +#define PM860X_AUDIO_SUPPLIES_2 0xdc +#define PM860X_ADC_EN_1 0xdd +#define PM860X_ADC_EN_2 0xde +#define PM860X_DAC_EN_1 0xdf +#define PM860X_DAC_EN_2 0xe1 +#define PM860X_AUDIO_CAL_1 0xe2 +#define PM860X_AUDIO_CAL_2 0xe3 +#define PM860X_AUDIO_CAL_3 0xe4 +#define PM860X_AUDIO_CAL_4 0xe5 +#define PM860X_AUDIO_CAL_5 0xe6 +#define PM860X_ANA_INPUT_SEL_1 0xe7 +#define PM860X_ANA_INPUT_SEL_2 0xe8 + +#define PM860X_PCM_IFACE_4 0xe9 +#define PM860X_I2S_IFACE_5 0xea + +#define PM860X_SHORTS 0x3b +#define PM860X_PLL_ADJ_1 0x3c +#define PM860X_PLL_ADJ_2 0x3d + +/* bits definition */ +#define PM860X_CLK_DIR_IN 0 +#define PM860X_CLK_DIR_OUT 1 + +#define PM860X_DET_HEADSET (1 << 0) +#define PM860X_DET_MIC (1 << 1) +#define PM860X_DET_HOOK (1 << 2) +#define PM860X_SHORT_HEADSET (1 << 3) +#define PM860X_SHORT_LINEOUT (1 << 4) +#define PM860X_DET_MASK 0x1F + +extern int pm860x_hs_jack_detect(struct snd_soc_component *, struct snd_soc_jack *, + int, int, int, int); +extern int pm860x_mic_jack_detect(struct snd_soc_component *, struct snd_soc_jack *, + int); + +#endif /* __88PM860X_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig new file mode 100644 index 000000000..efb095dbc --- /dev/null +++ b/sound/soc/codecs/Kconfig @@ -0,0 +1,1313 @@ +# Helper to resolve issues with configs that have SPI enabled but I2C +# modular, meaning we can't build the codec driver in with I2C support. +# We use an ordered list of conditional defaults to pick the appropriate +# setting - SPI can't be modular so that case doesn't need to be covered. +config SND_SOC_I2C_AND_SPI + tristate + default m if I2C=m + default y if I2C=y + default y if SPI_MASTER=y + +menu "CODEC drivers" + +config SND_SOC_ALL_CODECS + tristate "Build all ASoC CODEC drivers" + depends on COMPILE_TEST + select SND_SOC_88PM860X if MFD_88PM860X + select SND_SOC_L3 + select SND_SOC_AB8500_CODEC if ABX500_CORE + select SND_SOC_AC97_CODEC + select SND_SOC_AD1836 if SPI_MASTER + select SND_SOC_AD193X_SPI if SPI_MASTER + select SND_SOC_AD193X_I2C if I2C + select SND_SOC_AD1980 if SND_SOC_AC97_BUS + select SND_SOC_AD73311 + select SND_SOC_ADAU1373 if I2C + select SND_SOC_ADAU1761_I2C if I2C + select SND_SOC_ADAU1761_SPI if SPI + select SND_SOC_ADAU1781_I2C if I2C + select SND_SOC_ADAU1781_SPI if SPI + select SND_SOC_ADAV801 if SPI_MASTER + select SND_SOC_ADAV803 if I2C + select SND_SOC_ADAU1977_SPI if SPI_MASTER + select SND_SOC_ADAU1977_I2C if I2C + select SND_SOC_ADAU1701 if I2C + select SND_SOC_ADAU7002 + select SND_SOC_ADS117X + select SND_SOC_AK4104 if SPI_MASTER + select SND_SOC_AK4458 if I2C + select SND_SOC_AK4535 if I2C + select SND_SOC_AK4554 + select SND_SOC_AK4613 if I2C + select SND_SOC_AK4641 if I2C + select SND_SOC_AK4642 if I2C + select SND_SOC_AK4671 if I2C + select SND_SOC_AK5386 + select SND_SOC_AK5558 if I2C + select SND_SOC_ALC5623 if I2C + select SND_SOC_ALC5632 if I2C + select SND_SOC_BT_SCO + select SND_SOC_BD28623 + select SND_SOC_CQ0093VC + select SND_SOC_CS35L32 if I2C + select SND_SOC_CS35L33 if I2C + select SND_SOC_CS35L34 if I2C + select SND_SOC_CS35L35 if I2C + select SND_SOC_CS42L42 if I2C + select SND_SOC_CS42L51_I2C if I2C + select SND_SOC_CS42L52 if I2C && INPUT + select SND_SOC_CS42L56 if I2C && INPUT + select SND_SOC_CS42L73 if I2C + select SND_SOC_CS4265 if I2C + select SND_SOC_CS4270 if I2C + select SND_SOC_CS4271_I2C if I2C + select SND_SOC_CS4271_SPI if SPI_MASTER + select SND_SOC_CS42XX8_I2C if I2C + select SND_SOC_CS43130 if I2C + select SND_SOC_CS4349 if I2C + select SND_SOC_CS47L24 if MFD_CS47L24 + select SND_SOC_CS53L30 if I2C + select SND_SOC_CX20442 if TTY + select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI + select SND_SOC_DA7213 if I2C + select SND_SOC_DA7218 if I2C + select SND_SOC_DA7219 if I2C + select SND_SOC_DA732X if I2C + select SND_SOC_DA9055 if I2C + select SND_SOC_DMIC if GPIOLIB + select SND_SOC_ES8316 if I2C + select SND_SOC_ES8328_SPI if SPI_MASTER + select SND_SOC_ES8328_I2C if I2C + select SND_SOC_ES7134 + select SND_SOC_ES7241 + select SND_SOC_GTM601 + select SND_SOC_HDAC_HDMI + select SND_SOC_ICS43432 + select SND_SOC_INNO_RK3036 + select SND_SOC_ISABELLE if I2C + select SND_SOC_JZ4740_CODEC + select SND_SOC_LM4857 if I2C + select SND_SOC_LM49453 if I2C + select SND_SOC_MAX98088 if I2C + select SND_SOC_MAX98090 if I2C + select SND_SOC_MAX98095 if I2C + select SND_SOC_MAX98357A if GPIOLIB + select SND_SOC_MAX98371 if I2C + select SND_SOC_MAX98504 if I2C + select SND_SOC_MAX9867 if I2C + select SND_SOC_MAX98925 if I2C + select SND_SOC_MAX98926 if I2C + select SND_SOC_MAX98927 if I2C + select SND_SOC_MAX98373 if I2C + select SND_SOC_MAX9850 if I2C + select SND_SOC_MAX9860 if I2C + select SND_SOC_MAX9759 + select SND_SOC_MAX9768 if I2C + select SND_SOC_MAX9877 if I2C + select SND_SOC_MC13783 if MFD_MC13XXX + select SND_SOC_ML26124 if I2C + select SND_SOC_MT6351 if MTK_PMIC_WRAP + select SND_SOC_NAU8540 if I2C + select SND_SOC_NAU8810 if I2C + select SND_SOC_NAU8824 if I2C + select SND_SOC_NAU8825 if I2C + select SND_SOC_HDMI_CODEC + select SND_SOC_PCM1681 if I2C + select SND_SOC_PCM1789_I2C if I2C + select SND_SOC_PCM179X_I2C if I2C + select SND_SOC_PCM179X_SPI if SPI_MASTER + select SND_SOC_PCM186X_I2C if I2C + select SND_SOC_PCM186X_SPI if SPI_MASTER + select SND_SOC_PCM3008 + select SND_SOC_PCM3168A_I2C if I2C + select SND_SOC_PCM3168A_SPI if SPI_MASTER + select SND_SOC_PCM5102A + select SND_SOC_PCM512x_I2C if I2C + select SND_SOC_PCM512x_SPI if SPI_MASTER + select SND_SOC_RT274 if I2C + select SND_SOC_RT286 if I2C + select SND_SOC_RT298 if I2C + select SND_SOC_RT1305 if I2C + select SND_SOC_RT5514 if I2C + select SND_SOC_RT5616 if I2C + select SND_SOC_RT5631 if I2C + select SND_SOC_RT5640 if I2C + select SND_SOC_RT5645 if I2C + select SND_SOC_RT5651 if I2C + select SND_SOC_RT5659 if I2C + select SND_SOC_RT5660 if I2C + select SND_SOC_RT5663 if I2C + select SND_SOC_RT5665 if I2C + select SND_SOC_RT5668 if I2C + select SND_SOC_RT5670 if I2C + select SND_SOC_RT5677 if I2C && SPI_MASTER + select SND_SOC_RT5682 if I2C + select SND_SOC_SGTL5000 if I2C + select SND_SOC_SI476X if MFD_SI476X_CORE + select SND_SOC_SIMPLE_AMPLIFIER + select SND_SOC_SIRF_AUDIO_CODEC + select SND_SOC_SPDIF + select SND_SOC_SSM2305 + select SND_SOC_SSM2518 if I2C + select SND_SOC_SSM2602_SPI if SPI_MASTER + select SND_SOC_SSM2602_I2C if I2C + select SND_SOC_SSM4567 if I2C + select SND_SOC_STA32X if I2C + select SND_SOC_STA350 if I2C + select SND_SOC_STA529 if I2C + select SND_SOC_STAC9766 if SND_SOC_AC97_BUS + select SND_SOC_STI_SAS + select SND_SOC_TAS2552 if I2C + select SND_SOC_TAS5086 if I2C + select SND_SOC_TAS571X if I2C + select SND_SOC_TAS5720 if I2C + select SND_SOC_TAS6424 if I2C + select SND_SOC_TDA7419 if I2C + select SND_SOC_TFA9879 if I2C + select SND_SOC_TLV320AIC23_I2C if I2C + select SND_SOC_TLV320AIC23_SPI if SPI_MASTER + select SND_SOC_TLV320AIC26 if SPI_MASTER + select SND_SOC_TLV320AIC31XX if I2C + select SND_SOC_TLV320AIC32X4_I2C if I2C + select SND_SOC_TLV320AIC32X4_SPI if SPI_MASTER + select SND_SOC_TLV320AIC3X if I2C + select SND_SOC_TPA6130A2 if I2C + select SND_SOC_TLV320DAC33 if I2C + select SND_SOC_TSCS42XX if I2C + select SND_SOC_TSCS454 if I2C + select SND_SOC_TS3A227E if I2C + select SND_SOC_TWL4030 if TWL4030_CORE + select SND_SOC_TWL6040 if TWL6040_CORE + select SND_SOC_UDA134X + select SND_SOC_UDA1380 if I2C + select SND_SOC_WL1273 if MFD_WL1273_CORE + select SND_SOC_WM0010 if SPI_MASTER + select SND_SOC_WM1250_EV1 if I2C + select SND_SOC_WM2000 if I2C + select SND_SOC_WM2200 if I2C + select SND_SOC_WM5100 if I2C + select SND_SOC_WM5102 if MFD_WM5102 + select SND_SOC_WM5110 if MFD_WM5110 + select SND_SOC_WM8350 if MFD_WM8350 + select SND_SOC_WM8400 if MFD_WM8400 + select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8523 if I2C + select SND_SOC_WM8524 if GPIOLIB + select SND_SOC_WM8580 if I2C + select SND_SOC_WM8711 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8727 + select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8737 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8741 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8770 if SPI_MASTER + select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8782 + select SND_SOC_WM8804_I2C if I2C + select SND_SOC_WM8804_SPI if SPI_MASTER + select SND_SOC_WM8900 if I2C + select SND_SOC_WM8903 if I2C + select SND_SOC_WM8904 if I2C + select SND_SOC_WM8940 if I2C + select SND_SOC_WM8955 if I2C + select SND_SOC_WM8960 if I2C + select SND_SOC_WM8961 if I2C + select SND_SOC_WM8962 if I2C && INPUT + select SND_SOC_WM8971 if I2C + select SND_SOC_WM8974 if I2C + select SND_SOC_WM8978 if I2C + select SND_SOC_WM8983 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8990 if I2C + select SND_SOC_WM8991 if I2C + select SND_SOC_WM8993 if I2C + select SND_SOC_WM8994 if MFD_WM8994 + select SND_SOC_WM8995 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8996 if I2C + select SND_SOC_WM8997 if MFD_WM8997 + select SND_SOC_WM8998 if MFD_WM8998 + select SND_SOC_WM9081 if I2C + select SND_SOC_WM9090 if I2C + select SND_SOC_WM9705 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW) + select SND_SOC_WM9712 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW) + select SND_SOC_WM9713 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW) + help + Normally ASoC codec drivers are only built if a machine driver which + uses them is also built since they are only usable with a machine + driver. Selecting this option will allow these drivers to be built + without an explicit machine driver for test and development purposes. + + Support for the bus types used to access the codecs to be built must + be selected separately. + + If unsure select "N". + +config SND_SOC_88PM860X + tristate + +config SND_SOC_ARIZONA + tristate + default y if SND_SOC_CS47L24=y + default y if SND_SOC_WM5102=y + default y if SND_SOC_WM5110=y + default y if SND_SOC_WM8997=y + default y if SND_SOC_WM8998=y + default m if SND_SOC_CS47L24=m + default m if SND_SOC_WM5102=m + default m if SND_SOC_WM5110=m + default m if SND_SOC_WM8997=m + default m if SND_SOC_WM8998=m + +config SND_SOC_WM_HUBS + tristate + default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y + default m if SND_SOC_WM8993=m || SND_SOC_WM8994=m + +config SND_SOC_WM_ADSP + tristate + select SND_SOC_COMPRESS + default y if SND_SOC_CS47L24=y + default y if SND_SOC_WM5102=y + default y if SND_SOC_WM5110=y + default y if SND_SOC_WM2200=y + default m if SND_SOC_CS47L24=m + default m if SND_SOC_WM5102=m + default m if SND_SOC_WM5110=m + default m if SND_SOC_WM2200=m + +config SND_SOC_AB8500_CODEC + tristate + +config SND_SOC_AC97_CODEC + tristate "Build generic ASoC AC97 CODEC driver" + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + +config SND_SOC_AD1836 + tristate + +config SND_SOC_AD193X + tristate + +config SND_SOC_AD193X_SPI + tristate + select SND_SOC_AD193X + +config SND_SOC_AD193X_I2C + tristate + select SND_SOC_AD193X + +config SND_SOC_AD1980 + select REGMAP_AC97 + tristate + +config SND_SOC_AD73311 + tristate + +config SND_SOC_ADAU_UTILS + tristate + +config SND_SOC_ADAU1373 + tristate + select SND_SOC_ADAU_UTILS + +config SND_SOC_ADAU1701 + tristate "Analog Devices ADAU1701 CODEC" + depends on I2C + select SND_SOC_SIGMADSP_I2C + +config SND_SOC_ADAU17X1 + tristate + select SND_SOC_SIGMADSP_REGMAP + select SND_SOC_ADAU_UTILS + +config SND_SOC_ADAU1761 + tristate + select SND_SOC_ADAU17X1 + +config SND_SOC_ADAU1761_I2C + tristate "Analog Devices AU1761 CODEC - I2C" + depends on I2C + select SND_SOC_ADAU1761 + select REGMAP_I2C + +config SND_SOC_ADAU1761_SPI + tristate "Analog Devices AU1761 CODEC - SPI" + depends on SPI + select SND_SOC_ADAU1761 + select REGMAP_SPI + +config SND_SOC_ADAU1781 + select SND_SOC_ADAU17X1 + tristate + +config SND_SOC_ADAU1781_I2C + tristate + select SND_SOC_ADAU1781 + select REGMAP_I2C + +config SND_SOC_ADAU1781_SPI + tristate + select SND_SOC_ADAU1781 + select REGMAP_SPI + +config SND_SOC_ADAU1977 + tristate + +config SND_SOC_ADAU1977_SPI + tristate + select SND_SOC_ADAU1977 + select REGMAP_SPI + +config SND_SOC_ADAU1977_I2C + tristate + select SND_SOC_ADAU1977 + select REGMAP_I2C + +config SND_SOC_ADAU7002 + tristate "Analog Devices ADAU7002 Stereo PDM-to-I2S/TDM Converter" + +config SND_SOC_ADAV80X + tristate + +config SND_SOC_ADAV801 + tristate + select SND_SOC_ADAV80X + +config SND_SOC_ADAV803 + tristate + select SND_SOC_ADAV80X + +config SND_SOC_ADS117X + tristate + +config SND_SOC_AK4104 + tristate "AKM AK4104 CODEC" + depends on SPI_MASTER + +config SND_SOC_AK4458 + tristate "AKM AK4458 CODEC" + depends on I2C + select REGMAP_I2C + +config SND_SOC_AK4535 + tristate + +config SND_SOC_AK4554 + tristate "AKM AK4554 CODEC" + +config SND_SOC_AK4613 + tristate "AKM AK4613 CODEC" + depends on I2C + +config SND_SOC_AK4641 + tristate + +config SND_SOC_AK4642 + tristate "AKM AK4642 CODEC" + depends on I2C + +config SND_SOC_AK4671 + tristate + +config SND_SOC_AK5386 + tristate "AKM AK5638 CODEC" + +config SND_SOC_AK5558 + tristate "AKM AK5558 CODEC" + depends on I2C + select REGMAP_I2C + +config SND_SOC_ALC5623 + tristate "Realtek ALC5623 CODEC" + depends on I2C + +config SND_SOC_ALC5632 + tristate + +config SND_SOC_BD28623 + tristate "ROHM BD28623 CODEC" + help + Enable support for ROHM BD28623MUV Class D speaker amplifier. + This codec does not have any control buses such as I2C, it + detect format of I2S automatically. + +config SND_SOC_BT_SCO + tristate "Dummy BT SCO codec driver" + +config SND_SOC_CPCAP + tristate "Motorola CPCAP codec" + depends on MFD_CPCAP + +config SND_SOC_CQ0093VC + tristate + +config SND_SOC_CS35L32 + tristate "Cirrus Logic CS35L32 CODEC" + depends on I2C + +config SND_SOC_CS35L33 + tristate "Cirrus Logic CS35L33 CODEC" + depends on I2C + +config SND_SOC_CS35L34 + tristate "Cirrus Logic CS35L34 CODEC" + depends on I2C + +config SND_SOC_CS35L35 + tristate "Cirrus Logic CS35L35 CODEC" + depends on I2C + +config SND_SOC_CS42L42 + tristate "Cirrus Logic CS42L42 CODEC" + depends on I2C + +config SND_SOC_CS42L51 + tristate + +config SND_SOC_CS42L51_I2C + tristate "Cirrus Logic CS42L51 CODEC (I2C)" + depends on I2C + select SND_SOC_CS42L51 + +config SND_SOC_CS42L52 + tristate "Cirrus Logic CS42L52 CODEC" + depends on I2C && INPUT + +config SND_SOC_CS42L56 + tristate "Cirrus Logic CS42L56 CODEC" + depends on I2C && INPUT + +config SND_SOC_CS42L73 + tristate "Cirrus Logic CS42L73 CODEC" + depends on I2C + +config SND_SOC_CS4265 + tristate "Cirrus Logic CS4265 CODEC" + depends on I2C + select REGMAP_I2C + +# Cirrus Logic CS4270 Codec +config SND_SOC_CS4270 + tristate "Cirrus Logic CS4270 CODEC" + depends on I2C + +# Cirrus Logic CS4270 Codec VD = 3.3V Errata +# Select if you are affected by the errata where the part will not function +# if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will +# not select any sample rates that require MCLK to be divided by 1.5. +config SND_SOC_CS4270_VD33_ERRATA + bool + depends on SND_SOC_CS4270 + +config SND_SOC_CS4271 + tristate + +config SND_SOC_CS4271_I2C + tristate "Cirrus Logic CS4271 CODEC (I2C)" + depends on I2C + select SND_SOC_CS4271 + select REGMAP_I2C + +config SND_SOC_CS4271_SPI + tristate "Cirrus Logic CS4271 CODEC (SPI)" + depends on SPI_MASTER + select SND_SOC_CS4271 + select REGMAP_SPI + +config SND_SOC_CS42XX8 + tristate + +config SND_SOC_CS42XX8_I2C + tristate "Cirrus Logic CS42448/CS42888 CODEC (I2C)" + depends on I2C + select SND_SOC_CS42XX8 + select REGMAP_I2C + +# Cirrus Logic CS43130 HiFi DAC +config SND_SOC_CS43130 + tristate "Cirrus Logic CS43130 CODEC" + depends on I2C + +# Cirrus Logic CS4349 HiFi DAC +config SND_SOC_CS4349 + tristate "Cirrus Logic CS4349 CODEC" + depends on I2C + +config SND_SOC_CS47L24 + tristate + +# Cirrus Logic Quad-Channel ADC +config SND_SOC_CS53L30 + tristate "Cirrus Logic CS53L30 CODEC" + depends on I2C + +config SND_SOC_CX20442 + tristate + depends on TTY + +config SND_SOC_JZ4740_CODEC + select REGMAP_MMIO + tristate + +config SND_SOC_L3 + tristate + +config SND_SOC_DA7210 + tristate + +config SND_SOC_DA7213 + tristate + +config SND_SOC_DA7218 + tristate + +config SND_SOC_DA7219 + tristate + +config SND_SOC_DA732X + tristate + +config SND_SOC_DA9055 + tristate + +config SND_SOC_DMIC + tristate + +config SND_SOC_HDMI_CODEC + tristate + select SND_PCM_ELD + select SND_PCM_IEC958 + select HDMI + +config SND_SOC_ES7134 + tristate "Everest Semi ES7134 CODEC" + +config SND_SOC_ES7241 + tristate "Everest Semi ES7241 CODEC" + +config SND_SOC_ES8316 + tristate "Everest Semi ES8316 CODEC" + depends on I2C + +config SND_SOC_ES8328 + tristate + +config SND_SOC_ES8328_I2C + tristate "Everest Semi ES8328 CODEC (I2C)" + depends on I2C + select SND_SOC_ES8328 + +config SND_SOC_ES8328_SPI + tristate "Everest Semi ES8328 CODEC (SPI)" + depends on SPI_MASTER + select SND_SOC_ES8328 + +config SND_SOC_GTM601 + tristate 'GTM601 UMTS modem audio codec' + +config SND_SOC_HDAC_HDMI + tristate + select SND_HDA_EXT_CORE + select SND_PCM_ELD + select HDMI + +config SND_SOC_ICS43432 + tristate + +config SND_SOC_INNO_RK3036 + tristate "Inno codec driver for RK3036 SoC" + select REGMAP_MMIO + +config SND_SOC_ISABELLE + tristate + +config SND_SOC_LM49453 + tristate + +config SND_SOC_MAX98088 + tristate + +config SND_SOC_MAX98090 + tristate + +config SND_SOC_MAX98095 + tristate + +config SND_SOC_MAX98357A + tristate + +config SND_SOC_MAX98371 + tristate + +config SND_SOC_MAX98504 + tristate "Maxim MAX98504 speaker amplifier" + depends on I2C + +config SND_SOC_MAX9867 + tristate "Maxim MAX9867 CODEC" + depends on I2C + +config SND_SOC_MAX98925 + tristate + +config SND_SOC_MAX98926 + tristate + +config SND_SOC_MAX98927 + tristate "Maxim Integrated MAX98927 Speaker Amplifier" + depends on I2C + +config SND_SOC_MAX98373 + tristate "Maxim Integrated MAX98373 Speaker Amplifier" + depends on I2C + +config SND_SOC_MAX9850 + tristate + +config SND_SOC_MAX9860 + tristate "Maxim MAX9860 Mono Audio Voice Codec" + depends on I2C + select REGMAP_I2C + +config SND_SOC_MSM8916_WCD_ANALOG + tristate "Qualcomm MSM8916 WCD Analog Codec" + depends on SPMI || COMPILE_TEST + +config SND_SOC_MSM8916_WCD_DIGITAL + tristate "Qualcomm MSM8916 WCD DIGITAL Codec" + +config SND_SOC_PCM1681 + tristate "Texas Instruments PCM1681 CODEC" + depends on I2C + +config SND_SOC_PCM1789 + tristate + +config SND_SOC_PCM1789_I2C + tristate "Texas Instruments PCM1789 CODEC (I2C)" + depends on I2C + select SND_SOC_PCM1789 + help + Enable support for Texas Instruments PCM1789 CODEC. + Select this if your PCM1789 is connected via an I2C bus. + +config SND_SOC_PCM179X + tristate + +config SND_SOC_PCM179X_I2C + tristate "Texas Instruments PCM179X CODEC (I2C)" + depends on I2C + select SND_SOC_PCM179X + help + Enable support for Texas Instruments PCM179x CODEC. + Select this if your PCM179x is connected via an I2C bus. + +config SND_SOC_PCM179X_SPI + tristate "Texas Instruments PCM179X CODEC (SPI)" + depends on SPI_MASTER + select SND_SOC_PCM179X + help + Enable support for Texas Instruments PCM179x CODEC. + Select this if your PCM179x is connected via an SPI bus. + +config SND_SOC_PCM186X + tristate + +config SND_SOC_PCM186X_I2C + tristate "Texas Instruments PCM186x CODECs - I2C" + depends on I2C + select SND_SOC_PCM186X + select REGMAP_I2C + +config SND_SOC_PCM186X_SPI + tristate "Texas Instruments PCM186x CODECs - SPI" + depends on SPI_MASTER + select SND_SOC_PCM186X + select REGMAP_SPI + +config SND_SOC_PCM3008 + tristate + +config SND_SOC_PCM3168A + tristate + +config SND_SOC_PCM3168A_I2C + tristate "Texas Instruments PCM3168A CODEC - I2C" + depends on I2C + select SND_SOC_PCM3168A + select REGMAP_I2C + +config SND_SOC_PCM3168A_SPI + tristate "Texas Instruments PCM3168A CODEC - SPI" + depends on SPI_MASTER + select SND_SOC_PCM3168A + select REGMAP_SPI + +config SND_SOC_PCM5102A + tristate + +config SND_SOC_PCM512x + tristate + +config SND_SOC_PCM512x_I2C + tristate "Texas Instruments PCM512x CODECs - I2C" + depends on I2C + select SND_SOC_PCM512x + select REGMAP_I2C + +config SND_SOC_PCM512x_SPI + tristate "Texas Instruments PCM512x CODECs - SPI" + depends on SPI_MASTER + select SND_SOC_PCM512x + select REGMAP_SPI + +config SND_SOC_RL6231 + tristate + default y if SND_SOC_RT5514=y + default y if SND_SOC_RT5616=y + default y if SND_SOC_RT5640=y + default y if SND_SOC_RT5645=y + default y if SND_SOC_RT5651=y + default y if SND_SOC_RT5659=y + default y if SND_SOC_RT5660=y + default y if SND_SOC_RT5663=y + default y if SND_SOC_RT5665=y + default y if SND_SOC_RT5668=y + default y if SND_SOC_RT5670=y + default y if SND_SOC_RT5677=y + default y if SND_SOC_RT5682=y + default y if SND_SOC_RT1305=y + default m if SND_SOC_RT5514=m + default m if SND_SOC_RT5616=m + default m if SND_SOC_RT5640=m + default m if SND_SOC_RT5645=m + default m if SND_SOC_RT5651=m + default m if SND_SOC_RT5659=m + default m if SND_SOC_RT5660=m + default m if SND_SOC_RT5663=m + default m if SND_SOC_RT5665=m + default m if SND_SOC_RT5668=m + default m if SND_SOC_RT5670=m + default m if SND_SOC_RT5677=m + default m if SND_SOC_RT5682=m + default m if SND_SOC_RT1305=m + +config SND_SOC_RL6347A + tristate + default y if SND_SOC_RT274=y + default y if SND_SOC_RT286=y + default y if SND_SOC_RT298=y + default m if SND_SOC_RT274=m + default m if SND_SOC_RT286=m + default m if SND_SOC_RT298=m + +config SND_SOC_RT274 + tristate + depends on I2C + +config SND_SOC_RT286 + tristate + depends on I2C + +config SND_SOC_RT298 + tristate + depends on I2C + +config SND_SOC_RT1305 + tristate + +config SND_SOC_RT5514 + tristate + +config SND_SOC_RT5514_SPI + tristate + +config SND_SOC_RT5514_SPI_BUILTIN + bool # force RT5514_SPI to be built-in to avoid link errors + default SND_SOC_RT5514=y && SND_SOC_RT5514_SPI=m + +config SND_SOC_RT5616 + tristate "Realtek RT5616 CODEC" + depends on I2C + +config SND_SOC_RT5631 + tristate "Realtek ALC5631/RT5631 CODEC" + depends on I2C + +config SND_SOC_RT5640 + tristate + +config SND_SOC_RT5645 + tristate + +config SND_SOC_RT5651 + tristate + +config SND_SOC_RT5659 + tristate + +config SND_SOC_RT5660 + tristate + +config SND_SOC_RT5663 + tristate + +config SND_SOC_RT5665 + tristate + +config SND_SOC_RT5668 + tristate + +config SND_SOC_RT5670 + tristate + +config SND_SOC_RT5677 + tristate + select REGMAP_I2C + select REGMAP_IRQ + +config SND_SOC_RT5677_SPI + tristate + default SND_SOC_RT5677 && SPI + +config SND_SOC_RT5682 + tristate + +#Freescale sgtl5000 codec +config SND_SOC_SGTL5000 + tristate "Freescale SGTL5000 CODEC" + depends on I2C + +config SND_SOC_SI476X + tristate + +config SND_SOC_SIGMADSP + tristate + select CRC32 + +config SND_SOC_SIGMADSP_I2C + tristate + select SND_SOC_SIGMADSP + +config SND_SOC_SIGMADSP_REGMAP + tristate + select SND_SOC_SIGMADSP + +config SND_SOC_SIMPLE_AMPLIFIER + tristate "Simple Audio Amplifier" + select GPIOLIB + +config SND_SOC_SIRF_AUDIO_CODEC + tristate "SiRF SoC internal audio codec" + select REGMAP_MMIO + +config SND_SOC_SPDIF + tristate "S/PDIF CODEC" + +config SND_SOC_SSM2305 + tristate "Analog Devices SSM2305 Class-D Amplifier" + help + Enable support for Analog Devices SSM2305 filterless + high-efficiency mono Class-D audio power amplifiers. + +config SND_SOC_SSM2518 + tristate + +config SND_SOC_SSM2602 + tristate + +config SND_SOC_SSM2602_SPI + tristate "Analog Devices SSM2602 CODEC - SPI" + depends on SPI_MASTER + select SND_SOC_SSM2602 + select REGMAP_SPI + +config SND_SOC_SSM2602_I2C + tristate "Analog Devices SSM2602 CODEC - I2C" + depends on I2C + select SND_SOC_SSM2602 + select REGMAP_I2C + +config SND_SOC_SSM4567 + tristate "Analog Devices ssm4567 amplifier driver support" + depends on I2C + +config SND_SOC_STA32X + tristate "STA326, STA328 and STA329 speaker amplifier" + depends on I2C + select REGMAP_I2C + +config SND_SOC_STA350 + tristate "STA350 speaker amplifier" + depends on I2C + +config SND_SOC_STA529 + tristate + +config SND_SOC_STAC9766 + tristate + +config SND_SOC_STI_SAS + tristate "codec Audio support for STI SAS codec" + +config SND_SOC_TAS2552 + tristate "Texas Instruments TAS2552 Mono Audio amplifier" + depends on I2C + +config SND_SOC_TAS5086 + tristate "Texas Instruments TAS5086 speaker amplifier" + depends on I2C + +config SND_SOC_TAS571X + tristate "Texas Instruments TAS571x power amplifiers" + depends on I2C + help + Enable support for Texas Instruments TAS5707, TAS5711, TAS5717, + TAS5719 and TAS5721 power amplifiers + +config SND_SOC_TAS5720 + tristate "Texas Instruments TAS5720 Mono Audio amplifier" + depends on I2C + help + Enable support for Texas Instruments TAS5720L/M high-efficiency mono + Class-D audio power amplifiers. + +config SND_SOC_TAS6424 + tristate "Texas Instruments TAS6424 Quad-Channel Audio amplifier" + depends on I2C + help + Enable support for Texas Instruments TAS6424 high-efficiency + digital input quad-channel Class-D audio power amplifiers. + +config SND_SOC_TDA7419 + tristate "ST TDA7419 audio processor" + depends on I2C + select REGMAP_I2C + +config SND_SOC_TFA9879 + tristate "NXP Semiconductors TFA9879 amplifier" + depends on I2C + +config SND_SOC_TLV320AIC23 + tristate + +config SND_SOC_TLV320AIC23_I2C + tristate "Texas Instruments TLV320AIC23 audio CODEC - I2C" + depends on I2C + select SND_SOC_TLV320AIC23 + +config SND_SOC_TLV320AIC23_SPI + tristate "Texas Instruments TLV320AIC23 audio CODEC - SPI" + depends on SPI_MASTER + select SND_SOC_TLV320AIC23 + +config SND_SOC_TLV320AIC26 + tristate + depends on SPI + +config SND_SOC_TLV320AIC31XX + tristate "Texas Instruments TLV320AIC31xx CODECs" + depends on I2C + select REGMAP_I2C + +config SND_SOC_TLV320AIC32X4 + tristate + +config SND_SOC_TLV320AIC32X4_I2C + tristate "Texas Instruments TLV320AIC32x4 audio CODECs - I2C" + depends on I2C + select SND_SOC_TLV320AIC32X4 + +config SND_SOC_TLV320AIC32X4_SPI + tristate "Texas Instruments TLV320AIC32x4 audio CODECs - SPI" + depends on SPI_MASTER + select SND_SOC_TLV320AIC32X4 + +config SND_SOC_TLV320AIC3X + tristate "Texas Instruments TLV320AIC3x CODECs" + depends on I2C + +config SND_SOC_TLV320DAC33 + tristate + +config SND_SOC_TS3A227E + tristate "TI Headset/Mic detect and keypress chip" + depends on I2C + +config SND_SOC_TSCS42XX + tristate "Tempo Semiconductor TSCS42xx CODEC" + depends on I2C + select REGMAP_I2C + help + Add support for Tempo Semiconductor's TSCS42xx audio CODEC. + +config SND_SOC_TSCS454 + tristate "Tempo Semiconductor TSCS454 CODEC" + depends on I2C + select REGMAP_I2C + help + Add support for Tempo Semiconductor's TSCS454 audio CODEC. + +config SND_SOC_TWL4030 + select MFD_TWL4030_AUDIO + tristate + +config SND_SOC_TWL6040 + tristate + +config SND_SOC_UDA134X + tristate + +config SND_SOC_UDA1380 + tristate + depends on I2C + +config SND_SOC_WL1273 + tristate + +config SND_SOC_WM0010 + tristate + +config SND_SOC_WM1250_EV1 + tristate + +config SND_SOC_WM2000 + tristate + +config SND_SOC_WM2200 + tristate + +config SND_SOC_WM5100 + tristate + +config SND_SOC_WM5102 + tristate + +config SND_SOC_WM5110 + tristate + +config SND_SOC_WM8350 + tristate + +config SND_SOC_WM8400 + tristate + +config SND_SOC_WM8510 + tristate "Wolfson Microelectronics WM8510 CODEC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8523 + tristate "Wolfson Microelectronics WM8523 DAC" + depends on I2C + +config SND_SOC_WM8524 + tristate "Wolfson Microelectronics WM8524 DAC" + depends on GPIOLIB + +config SND_SOC_WM8580 + tristate "Wolfson Microelectronics WM8580 and WM8581 CODECs" + depends on I2C + +config SND_SOC_WM8711 + tristate "Wolfson Microelectronics WM8711 CODEC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8727 + tristate + +config SND_SOC_WM8728 + tristate "Wolfson Microelectronics WM8728 DAC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8731 + tristate "Wolfson Microelectronics WM8731 CODEC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8737 + tristate "Wolfson Microelectronics WM8737 ADC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8741 + tristate "Wolfson Microelectronics WM8737 DAC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8750 + tristate "Wolfson Microelectronics WM8750 CODEC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8753 + tristate "Wolfson Microelectronics WM8753 CODEC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8770 + tristate "Wolfson Microelectronics WM8770 CODEC" + depends on SPI_MASTER + +config SND_SOC_WM8776 + tristate "Wolfson Microelectronics WM8776 CODEC" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8782 + tristate "Wolfson Microelectronics WM8782 ADC" + +config SND_SOC_WM8804 + tristate + +config SND_SOC_WM8804_I2C + tristate "Wolfson Microelectronics WM8804 S/PDIF transceiver I2C" + depends on I2C + select SND_SOC_WM8804 + select REGMAP_I2C + +config SND_SOC_WM8804_SPI + tristate "Wolfson Microelectronics WM8804 S/PDIF transceiver SPI" + depends on SPI_MASTER + select SND_SOC_WM8804 + select REGMAP_SPI + +config SND_SOC_WM8900 + tristate + +config SND_SOC_WM8903 + tristate "Wolfson Microelectronics WM8903 CODEC" + depends on I2C + +config SND_SOC_WM8904 + tristate + +config SND_SOC_WM8940 + tristate + +config SND_SOC_WM8955 + tristate + +config SND_SOC_WM8960 + tristate "Wolfson Microelectronics WM8960 CODEC" + depends on I2C + +config SND_SOC_WM8961 + tristate + +config SND_SOC_WM8962 + tristate "Wolfson Microelectronics WM8962 CODEC" + depends on I2C && INPUT + +config SND_SOC_WM8971 + tristate + +config SND_SOC_WM8974 + tristate "Wolfson Microelectronics WM8974 codec" + depends on I2C + +config SND_SOC_WM8978 + tristate "Wolfson Microelectronics WM8978 codec" + depends on I2C + +config SND_SOC_WM8983 + tristate + +config SND_SOC_WM8985 + tristate "Wolfson Microelectronics WM8985 and WM8758 codec driver" + depends on SND_SOC_I2C_AND_SPI + +config SND_SOC_WM8988 + tristate + +config SND_SOC_WM8990 + tristate + +config SND_SOC_WM8991 + tristate + +config SND_SOC_WM8993 + tristate + +config SND_SOC_WM8994 + tristate + +config SND_SOC_WM8995 + tristate + +config SND_SOC_WM8996 + tristate + +config SND_SOC_WM8997 + tristate + +config SND_SOC_WM8998 + tristate + +config SND_SOC_WM9081 + tristate + depends on I2C + +config SND_SOC_WM9090 + tristate + +config SND_SOC_WM9705 + tristate + select REGMAP_AC97 + select AC97_BUS_COMPAT if AC97_BUS_NEW + +config SND_SOC_WM9712 + tristate + select REGMAP_AC97 + select AC97_BUS_COMPAT if AC97_BUS_NEW + +config SND_SOC_WM9713 + tristate + select REGMAP_AC97 + select AC97_BUS_COMPAT if AC97_BUS_NEW + +config SND_SOC_ZX_AUD96P22 + tristate "ZTE ZX AUD96P22 CODEC" + depends on I2C + select REGMAP_I2C + +# Amp +config SND_SOC_LM4857 + tristate + +config SND_SOC_MAX9759 + tristate "Maxim MAX9759 speaker Amplifier" + select GPIOLIB + +config SND_SOC_MAX9768 + tristate + +config SND_SOC_MAX9877 + tristate + +config SND_SOC_MC13783 + tristate + +config SND_SOC_ML26124 + tristate + +config SND_SOC_MT6351 + tristate "MediaTek MT6351 Codec" + +config SND_SOC_NAU8540 + tristate "Nuvoton Technology Corporation NAU85L40 CODEC" + depends on I2C + +config SND_SOC_NAU8810 + tristate "Nuvoton Technology Corporation NAU88C10 CODEC" + depends on I2C + +config SND_SOC_NAU8824 + tristate "Nuvoton Technology Corporation NAU88L24 CODEC" + depends on I2C + +config SND_SOC_NAU8825 + tristate + +config SND_SOC_TPA6130A2 + tristate "Texas Instruments TPA6130A2 headphone amplifier" + depends on I2C + +endmenu diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile new file mode 100644 index 000000000..7ae7c85e8 --- /dev/null +++ b/sound/soc/codecs/Makefile @@ -0,0 +1,517 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-88pm860x-objs := 88pm860x-codec.o +snd-soc-ab8500-codec-objs := ab8500-codec.o +snd-soc-ac97-objs := ac97.o +snd-soc-ad1836-objs := ad1836.o +snd-soc-ad193x-objs := ad193x.o +snd-soc-ad193x-spi-objs := ad193x-spi.o +snd-soc-ad193x-i2c-objs := ad193x-i2c.o +snd-soc-ad1980-objs := ad1980.o +snd-soc-ad73311-objs := ad73311.o +snd-soc-adau-utils-objs := adau-utils.o +snd-soc-adau1373-objs := adau1373.o +snd-soc-adau1701-objs := adau1701.o +snd-soc-adau17x1-objs := adau17x1.o +snd-soc-adau1761-objs := adau1761.o +snd-soc-adau1761-i2c-objs := adau1761-i2c.o +snd-soc-adau1761-spi-objs := adau1761-spi.o +snd-soc-adau1781-objs := adau1781.o +snd-soc-adau1781-i2c-objs := adau1781-i2c.o +snd-soc-adau1781-spi-objs := adau1781-spi.o +snd-soc-adau1977-objs := adau1977.o +snd-soc-adau1977-spi-objs := adau1977-spi.o +snd-soc-adau1977-i2c-objs := adau1977-i2c.o +snd-soc-adau7002-objs := adau7002.o +snd-soc-adav80x-objs := adav80x.o +snd-soc-adav801-objs := adav801.o +snd-soc-adav803-objs := adav803.o +snd-soc-ads117x-objs := ads117x.o +snd-soc-ak4104-objs := ak4104.o +snd-soc-ak4458-objs := ak4458.o +snd-soc-ak4535-objs := ak4535.o +snd-soc-ak4554-objs := ak4554.o +snd-soc-ak4613-objs := ak4613.o +snd-soc-ak4641-objs := ak4641.o +snd-soc-ak4642-objs := ak4642.o +snd-soc-ak4671-objs := ak4671.o +snd-soc-ak5386-objs := ak5386.o +snd-soc-ak5558-objs := ak5558.o +snd-soc-arizona-objs := arizona.o +snd-soc-bd28623-objs := bd28623.o +snd-soc-bt-sco-objs := bt-sco.o +snd-soc-cpcap-objs := cpcap.o +snd-soc-cq93vc-objs := cq93vc.o +snd-soc-cs35l32-objs := cs35l32.o +snd-soc-cs35l33-objs := cs35l33.o +snd-soc-cs35l34-objs := cs35l34.o +snd-soc-cs35l35-objs := cs35l35.o +snd-soc-cs42l42-objs := cs42l42.o +snd-soc-cs42l51-objs := cs42l51.o +snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o +snd-soc-cs42l52-objs := cs42l52.o +snd-soc-cs42l56-objs := cs42l56.o +snd-soc-cs42l73-objs := cs42l73.o +snd-soc-cs4265-objs := cs4265.o +snd-soc-cs4270-objs := cs4270.o +snd-soc-cs4271-objs := cs4271.o +snd-soc-cs4271-i2c-objs := cs4271-i2c.o +snd-soc-cs4271-spi-objs := cs4271-spi.o +snd-soc-cs42xx8-objs := cs42xx8.o +snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o +snd-soc-cs43130-objs := cs43130.o +snd-soc-cs4349-objs := cs4349.o +snd-soc-cs47l24-objs := cs47l24.o +snd-soc-cs53l30-objs := cs53l30.o +snd-soc-cx20442-objs := cx20442.o +snd-soc-da7210-objs := da7210.o +snd-soc-da7213-objs := da7213.o +snd-soc-da7218-objs := da7218.o +snd-soc-da7219-objs := da7219.o da7219-aad.o +snd-soc-da732x-objs := da732x.o +snd-soc-da9055-objs := da9055.o +snd-soc-dmic-objs := dmic.o +snd-soc-es7134-objs := es7134.o +snd-soc-es7241-objs := es7241.o +snd-soc-es8316-objs := es8316.o +snd-soc-es8328-objs := es8328.o +snd-soc-es8328-i2c-objs := es8328-i2c.o +snd-soc-es8328-spi-objs := es8328-spi.o +snd-soc-gtm601-objs := gtm601.o +snd-soc-hdac-hdmi-objs := hdac_hdmi.o +snd-soc-ics43432-objs := ics43432.o +snd-soc-inno-rk3036-objs := inno_rk3036.o +snd-soc-isabelle-objs := isabelle.o +snd-soc-jz4740-codec-objs := jz4740.o +snd-soc-l3-objs := l3.o +snd-soc-lm4857-objs := lm4857.o +snd-soc-lm49453-objs := lm49453.o +snd-soc-max9759-objs := max9759.o +snd-soc-max9768-objs := max9768.o +snd-soc-max98088-objs := max98088.o +snd-soc-max98090-objs := max98090.o +snd-soc-max98095-objs := max98095.o +snd-soc-max98357a-objs := max98357a.o +snd-soc-max98371-objs := max98371.o +snd-soc-max9867-objs := max9867.o +snd-soc-max98925-objs := max98925.o +snd-soc-max98926-objs := max98926.o +snd-soc-max98927-objs := max98927.o +snd-soc-max98373-objs := max98373.o +snd-soc-max9850-objs := max9850.o +snd-soc-max9860-objs := max9860.o +snd-soc-mc13783-objs := mc13783.o +snd-soc-ml26124-objs := ml26124.o +snd-soc-msm8916-analog-objs := msm8916-wcd-analog.o +snd-soc-msm8916-digital-objs := msm8916-wcd-digital.o +snd-soc-mt6351-objs := mt6351.o +snd-soc-nau8540-objs := nau8540.o +snd-soc-nau8810-objs := nau8810.o +snd-soc-nau8824-objs := nau8824.o +snd-soc-nau8825-objs := nau8825.o +snd-soc-hdmi-codec-objs := hdmi-codec.o +snd-soc-pcm1681-objs := pcm1681.o +snd-soc-pcm1789-codec-objs := pcm1789.o +snd-soc-pcm1789-i2c-objs := pcm1789-i2c.o +snd-soc-pcm179x-codec-objs := pcm179x.o +snd-soc-pcm179x-i2c-objs := pcm179x-i2c.o +snd-soc-pcm179x-spi-objs := pcm179x-spi.o +snd-soc-pcm186x-objs := pcm186x.o +snd-soc-pcm186x-i2c-objs := pcm186x-i2c.o +snd-soc-pcm186x-spi-objs := pcm186x-spi.o +snd-soc-pcm3008-objs := pcm3008.o +snd-soc-pcm3168a-objs := pcm3168a.o +snd-soc-pcm3168a-i2c-objs := pcm3168a-i2c.o +snd-soc-pcm3168a-spi-objs := pcm3168a-spi.o +snd-soc-pcm5102a-objs := pcm5102a.o +snd-soc-pcm512x-objs := pcm512x.o +snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o +snd-soc-pcm512x-spi-objs := pcm512x-spi.o +snd-soc-rl6231-objs := rl6231.o +snd-soc-rl6347a-objs := rl6347a.o +snd-soc-rt1305-objs := rt1305.o +snd-soc-rt274-objs := rt274.o +snd-soc-rt286-objs := rt286.o +snd-soc-rt298-objs := rt298.o +snd-soc-rt5514-objs := rt5514.o +snd-soc-rt5514-spi-objs := rt5514-spi.o +snd-soc-rt5616-objs := rt5616.o +snd-soc-rt5631-objs := rt5631.o +snd-soc-rt5640-objs := rt5640.o +snd-soc-rt5645-objs := rt5645.o +snd-soc-rt5651-objs := rt5651.o +snd-soc-rt5659-objs := rt5659.o +snd-soc-rt5660-objs := rt5660.o +snd-soc-rt5663-objs := rt5663.o +snd-soc-rt5665-objs := rt5665.o +snd-soc-rt5668-objs := rt5668.o +snd-soc-rt5670-objs := rt5670.o +snd-soc-rt5677-objs := rt5677.o +snd-soc-rt5677-spi-objs := rt5677-spi.o +snd-soc-rt5682-objs := rt5682.o +snd-soc-sgtl5000-objs := sgtl5000.o +snd-soc-alc5623-objs := alc5623.o +snd-soc-alc5632-objs := alc5632.o +snd-soc-sigmadsp-objs := sigmadsp.o +snd-soc-sigmadsp-i2c-objs := sigmadsp-i2c.o +snd-soc-sigmadsp-regmap-objs := sigmadsp-regmap.o +snd-soc-si476x-objs := si476x.o +snd-soc-sirf-audio-codec-objs := sirf-audio-codec.o +snd-soc-spdif-tx-objs := spdif_transmitter.o +snd-soc-spdif-rx-objs := spdif_receiver.o +snd-soc-ssm2305-objs := ssm2305.o +snd-soc-ssm2518-objs := ssm2518.o +snd-soc-ssm2602-objs := ssm2602.o +snd-soc-ssm2602-spi-objs := ssm2602-spi.o +snd-soc-ssm2602-i2c-objs := ssm2602-i2c.o +snd-soc-ssm4567-objs := ssm4567.o +snd-soc-sta32x-objs := sta32x.o +snd-soc-sta350-objs := sta350.o +snd-soc-sta529-objs := sta529.o +snd-soc-stac9766-objs := stac9766.o +snd-soc-sti-sas-objs := sti-sas.o +snd-soc-tas5086-objs := tas5086.o +snd-soc-tas571x-objs := tas571x.o +snd-soc-tas5720-objs := tas5720.o +snd-soc-tas6424-objs := tas6424.o +snd-soc-tda7419-objs := tda7419.o +snd-soc-tfa9879-objs := tfa9879.o +snd-soc-tlv320aic23-objs := tlv320aic23.o +snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o +snd-soc-tlv320aic23-spi-objs := tlv320aic23-spi.o +snd-soc-tlv320aic26-objs := tlv320aic26.o +snd-soc-tlv320aic31xx-objs := tlv320aic31xx.o +snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o +snd-soc-tlv320aic32x4-i2c-objs := tlv320aic32x4-i2c.o +snd-soc-tlv320aic32x4-spi-objs := tlv320aic32x4-spi.o +snd-soc-tlv320aic3x-objs := tlv320aic3x.o +snd-soc-tlv320dac33-objs := tlv320dac33.o +snd-soc-tscs42xx-objs := tscs42xx.o +snd-soc-tscs454-objs := tscs454.o +snd-soc-ts3a227e-objs := ts3a227e.o +snd-soc-twl4030-objs := twl4030.o +snd-soc-twl6040-objs := twl6040.o +snd-soc-uda134x-objs := uda134x.o +snd-soc-uda1380-objs := uda1380.o +snd-soc-wl1273-objs := wl1273.o +snd-soc-wm-adsp-objs := wm_adsp.o +snd-soc-wm0010-objs := wm0010.o +snd-soc-wm1250-ev1-objs := wm1250-ev1.o +snd-soc-wm2000-objs := wm2000.o +snd-soc-wm2200-objs := wm2200.o +snd-soc-wm5100-objs := wm5100.o wm5100-tables.o +snd-soc-wm5102-objs := wm5102.o +snd-soc-wm5110-objs := wm5110.o +snd-soc-wm8350-objs := wm8350.o +snd-soc-wm8400-objs := wm8400.o +snd-soc-wm8510-objs := wm8510.o +snd-soc-wm8523-objs := wm8523.o +snd-soc-wm8524-objs := wm8524.o +snd-soc-wm8580-objs := wm8580.o +snd-soc-wm8711-objs := wm8711.o +snd-soc-wm8727-objs := wm8727.o +snd-soc-wm8728-objs := wm8728.o +snd-soc-wm8731-objs := wm8731.o +snd-soc-wm8737-objs := wm8737.o +snd-soc-wm8741-objs := wm8741.o +snd-soc-wm8750-objs := wm8750.o +snd-soc-wm8753-objs := wm8753.o +snd-soc-wm8770-objs := wm8770.o +snd-soc-wm8776-objs := wm8776.o +snd-soc-wm8782-objs := wm8782.o +snd-soc-wm8804-objs := wm8804.o +snd-soc-wm8804-i2c-objs := wm8804-i2c.o +snd-soc-wm8804-spi-objs := wm8804-spi.o +snd-soc-wm8900-objs := wm8900.o +snd-soc-wm8903-objs := wm8903.o +snd-soc-wm8904-objs := wm8904.o +snd-soc-wm8996-objs := wm8996.o +snd-soc-wm8940-objs := wm8940.o +snd-soc-wm8955-objs := wm8955.o +snd-soc-wm8960-objs := wm8960.o +snd-soc-wm8961-objs := wm8961.o +snd-soc-wm8962-objs := wm8962.o +snd-soc-wm8971-objs := wm8971.o +snd-soc-wm8974-objs := wm8974.o +snd-soc-wm8978-objs := wm8978.o +snd-soc-wm8983-objs := wm8983.o +snd-soc-wm8985-objs := wm8985.o +snd-soc-wm8988-objs := wm8988.o +snd-soc-wm8990-objs := wm8990.o +snd-soc-wm8991-objs := wm8991.o +snd-soc-wm8993-objs := wm8993.o +snd-soc-wm8994-objs := wm8994.o wm8958-dsp2.o +snd-soc-wm8995-objs := wm8995.o +snd-soc-wm8997-objs := wm8997.o +snd-soc-wm8998-objs := wm8998.o +snd-soc-wm9081-objs := wm9081.o +snd-soc-wm9090-objs := wm9090.o +snd-soc-wm9705-objs := wm9705.o +snd-soc-wm9712-objs := wm9712.o +snd-soc-wm9713-objs := wm9713.o +snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-zx-aud96p22-objs := zx_aud96p22.o +# Amp +snd-soc-max9877-objs := max9877.o +snd-soc-max98504-objs := max98504.o +snd-soc-simple-amplifier-objs := simple-amplifier.o +snd-soc-tpa6130a2-objs := tpa6130a2.o +snd-soc-tas2552-objs := tas2552.o + +obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o +obj-$(CONFIG_SND_SOC_AB8500_CODEC) += snd-soc-ab8500-codec.o +obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o +obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o +obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o +obj-$(CONFIG_SND_SOC_AD193X_I2C) += snd-soc-ad193x-i2c.o +obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o +obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_ADAU_UTILS) += snd-soc-adau-utils.o +obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o +obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o +obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o +obj-$(CONFIG_SND_SOC_ADAU1761) += snd-soc-adau1761.o +obj-$(CONFIG_SND_SOC_ADAU1761_I2C) += snd-soc-adau1761-i2c.o +obj-$(CONFIG_SND_SOC_ADAU1761_SPI) += snd-soc-adau1761-spi.o +obj-$(CONFIG_SND_SOC_ADAU1781) += snd-soc-adau1781.o +obj-$(CONFIG_SND_SOC_ADAU1781_I2C) += snd-soc-adau1781-i2c.o +obj-$(CONFIG_SND_SOC_ADAU1781_SPI) += snd-soc-adau1781-spi.o +obj-$(CONFIG_SND_SOC_ADAU1977) += snd-soc-adau1977.o +obj-$(CONFIG_SND_SOC_ADAU1977_SPI) += snd-soc-adau1977-spi.o +obj-$(CONFIG_SND_SOC_ADAU1977_I2C) += snd-soc-adau1977-i2c.o +obj-$(CONFIG_SND_SOC_ADAU7002) += snd-soc-adau7002.o +obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o +obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o +obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o +obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o +obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o +obj-$(CONFIG_SND_SOC_AK4458) += snd-soc-ak4458.o +obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_AK4554) += snd-soc-ak4554.o +obj-$(CONFIG_SND_SOC_AK4613) += snd-soc-ak4613.o +obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o +obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o +obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o +obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o +obj-$(CONFIG_SND_SOC_AK5558) += snd-soc-ak5558.o +obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o +obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o +obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o +obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o +obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o +obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o +obj-$(CONFIG_SND_SOC_CPCAP) += snd-soc-cpcap.o +obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o +obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o +obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o +obj-$(CONFIG_SND_SOC_CS35L35) += snd-soc-cs35l35.o +obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42.o +obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o +obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o +obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o +obj-$(CONFIG_SND_SOC_CS42L56) += snd-soc-cs42l56.o +obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o +obj-$(CONFIG_SND_SOC_CS4265) += snd-soc-cs4265.o +obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o +obj-$(CONFIG_SND_SOC_CS4271_I2C) += snd-soc-cs4271-i2c.o +obj-$(CONFIG_SND_SOC_CS4271_SPI) += snd-soc-cs4271-spi.o +obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o +obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o +obj-$(CONFIG_SND_SOC_CS43130) += snd-soc-cs43130.o +obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o +obj-$(CONFIG_SND_SOC_CS47L24) += snd-soc-cs47l24.o +obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o +obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o +obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o +obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o +obj-$(CONFIG_SND_SOC_DA7218) += snd-soc-da7218.o +obj-$(CONFIG_SND_SOC_DA7219) += snd-soc-da7219.o +obj-$(CONFIG_SND_SOC_DA732X) += snd-soc-da732x.o +obj-$(CONFIG_SND_SOC_DA9055) += snd-soc-da9055.o +obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o +obj-$(CONFIG_SND_SOC_ES7134) += snd-soc-es7134.o +obj-$(CONFIG_SND_SOC_ES7241) += snd-soc-es7241.o +obj-$(CONFIG_SND_SOC_ES8316) += snd-soc-es8316.o +obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o +obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o +obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o +obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o +obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o +obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o +obj-$(CONFIG_SND_SOC_INNO_RK3036) += snd-soc-inno-rk3036.o +obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o +obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o +obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o +obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o +obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o +obj-$(CONFIG_SND_SOC_MAX9759) += snd-soc-max9759.o +obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o +obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o +obj-$(CONFIG_SND_SOC_MAX98090) += snd-soc-max98090.o +obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o +obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o +obj-$(CONFIG_SND_SOC_MAX98371) += snd-soc-max98371.o +obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o +obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o +obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o +obj-$(CONFIG_SND_SOC_MAX98927) += snd-soc-max98927.o +obj-$(CONFIG_SND_SOC_MAX98373) += snd-soc-max98373.o +obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o +obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o +obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o +obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o +obj-$(CONFIG_SND_SOC_MSM8916_WCD_ANALOG) +=snd-soc-msm8916-analog.o +obj-$(CONFIG_SND_SOC_MSM8916_WCD_DIGITAL) +=snd-soc-msm8916-digital.o +obj-$(CONFIG_SND_SOC_MT6351) += snd-soc-mt6351.o +obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o +obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o +obj-$(CONFIG_SND_SOC_NAU8824) += snd-soc-nau8824.o +obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o +obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o +obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o +obj-$(CONFIG_SND_SOC_PCM179X) += snd-soc-pcm179x-codec.o +obj-$(CONFIG_SND_SOC_PCM1789_I2C) += snd-soc-pcm1789-i2c.o +obj-$(CONFIG_SND_SOC_PCM1789) += snd-soc-pcm1789-codec.o +obj-$(CONFIG_SND_SOC_PCM179X_I2C) += snd-soc-pcm179x-i2c.o +obj-$(CONFIG_SND_SOC_PCM179X_SPI) += snd-soc-pcm179x-spi.o +obj-$(CONFIG_SND_SOC_PCM186X) += snd-soc-pcm186x.o +obj-$(CONFIG_SND_SOC_PCM186X_I2C) += snd-soc-pcm186x-i2c.o +obj-$(CONFIG_SND_SOC_PCM186X_SPI) += snd-soc-pcm186x-spi.o +obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o +obj-$(CONFIG_SND_SOC_PCM3168A) += snd-soc-pcm3168a.o +obj-$(CONFIG_SND_SOC_PCM3168A_I2C) += snd-soc-pcm3168a-i2c.o +obj-$(CONFIG_SND_SOC_PCM3168A_SPI) += snd-soc-pcm3168a-spi.o +obj-$(CONFIG_SND_SOC_PCM5102A) += snd-soc-pcm5102a.o +obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o +obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o +obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o +obj-$(CONFIG_SND_SOC_RL6231) += snd-soc-rl6231.o +obj-$(CONFIG_SND_SOC_RL6347A) += snd-soc-rl6347a.o +obj-$(CONFIG_SND_SOC_RT1305) += snd-soc-rt1305.o +obj-$(CONFIG_SND_SOC_RT274) += snd-soc-rt274.o +obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o +obj-$(CONFIG_SND_SOC_RT298) += snd-soc-rt298.o +obj-$(CONFIG_SND_SOC_RT5514) += snd-soc-rt5514.o +obj-$(CONFIG_SND_SOC_RT5514_SPI) += snd-soc-rt5514-spi.o +obj-$(CONFIG_SND_SOC_RT5514_SPI_BUILTIN) += snd-soc-rt5514-spi.o +obj-$(CONFIG_SND_SOC_RT5616) += snd-soc-rt5616.o +obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o +obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o +obj-$(CONFIG_SND_SOC_RT5645) += snd-soc-rt5645.o +obj-$(CONFIG_SND_SOC_RT5651) += snd-soc-rt5651.o +obj-$(CONFIG_SND_SOC_RT5659) += snd-soc-rt5659.o +obj-$(CONFIG_SND_SOC_RT5660) += snd-soc-rt5660.o +obj-$(CONFIG_SND_SOC_RT5663) += snd-soc-rt5663.o +obj-$(CONFIG_SND_SOC_RT5665) += snd-soc-rt5665.o +obj-$(CONFIG_SND_SOC_RT5668) += snd-soc-rt5668.o +obj-$(CONFIG_SND_SOC_RT5670) += snd-soc-rt5670.o +obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o +obj-$(CONFIG_SND_SOC_RT5677_SPI) += snd-soc-rt5677-spi.o +obj-$(CONFIG_SND_SOC_RT5682) += snd-soc-rt5682.o +obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o +obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o +obj-$(CONFIG_SND_SOC_SIGMADSP_I2C) += snd-soc-sigmadsp-i2c.o +obj-$(CONFIG_SND_SOC_SIGMADSP_REGMAP) += snd-soc-sigmadsp-regmap.o +obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o +obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o +obj-$(CONFIG_SND_SOC_SIRF_AUDIO_CODEC) += sirf-audio-codec.o +obj-$(CONFIG_SND_SOC_SSM2305) += snd-soc-ssm2305.o +obj-$(CONFIG_SND_SOC_SSM2518) += snd-soc-ssm2518.o +obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_SSM2602_SPI) += snd-soc-ssm2602-spi.o +obj-$(CONFIG_SND_SOC_SSM2602_I2C) += snd-soc-ssm2602-i2c.o +obj-$(CONFIG_SND_SOC_SSM4567) += snd-soc-ssm4567.o +obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o +obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o +obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o +obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o +obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o +obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o +obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o +obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o +obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o +obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o +obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o +obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o +obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o +obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o +obj-$(CONFIG_SND_SOC_TLV320AIC23_SPI) += snd-soc-tlv320aic23-spi.o +obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o +obj-$(CONFIG_SND_SOC_TLV320AIC31XX) += snd-soc-tlv320aic31xx.o +obj-$(CONFIG_SND_SOC_TLV320AIC32X4) += snd-soc-tlv320aic32x4.o +obj-$(CONFIG_SND_SOC_TLV320AIC32X4_I2C) += snd-soc-tlv320aic32x4-i2c.o +obj-$(CONFIG_SND_SOC_TLV320AIC32X4_SPI) += snd-soc-tlv320aic32x4-spi.o +obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o +obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o +obj-$(CONFIG_SND_SOC_TSCS42XX) += snd-soc-tscs42xx.o +obj-$(CONFIG_SND_SOC_TSCS454) += snd-soc-tscs454.o +obj-$(CONFIG_SND_SOC_TS3A227E) += snd-soc-ts3a227e.o +obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o +obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o +obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o +obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o +obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o +obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o +obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o +obj-$(CONFIG_SND_SOC_WM2200) += snd-soc-wm2200.o +obj-$(CONFIG_SND_SOC_WM5100) += snd-soc-wm5100.o +obj-$(CONFIG_SND_SOC_WM5102) += snd-soc-wm5102.o +obj-$(CONFIG_SND_SOC_WM5110) += snd-soc-wm5110.o +obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o +obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o +obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o +obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o +obj-$(CONFIG_SND_SOC_WM8524) += snd-soc-wm8524.o +obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o +obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o +obj-$(CONFIG_SND_SOC_WM8727) += snd-soc-wm8727.o +obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o +obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o +obj-$(CONFIG_SND_SOC_WM8737) += snd-soc-wm8737.o +obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o +obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o +obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o +obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o +obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o +obj-$(CONFIG_SND_SOC_WM8782) += snd-soc-wm8782.o +obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o +obj-$(CONFIG_SND_SOC_WM8804_I2C) += snd-soc-wm8804-i2c.o +obj-$(CONFIG_SND_SOC_WM8804_SPI) += snd-soc-wm8804-spi.o +obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o +obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o +obj-$(CONFIG_SND_SOC_WM8904) += snd-soc-wm8904.o +obj-$(CONFIG_SND_SOC_WM8996) += snd-soc-wm8996.o +obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o +obj-$(CONFIG_SND_SOC_WM8955) += snd-soc-wm8955.o +obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o +obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o +obj-$(CONFIG_SND_SOC_WM8962) += snd-soc-wm8962.o +obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o +obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o +obj-$(CONFIG_SND_SOC_WM8983) += snd-soc-wm8983.o +obj-$(CONFIG_SND_SOC_WM8985) += snd-soc-wm8985.o +obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o +obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o +obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o +obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o +obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.o +obj-$(CONFIG_SND_SOC_WM8995) += snd-soc-wm8995.o +obj-$(CONFIG_SND_SOC_WM8997) += snd-soc-wm8997.o +obj-$(CONFIG_SND_SOC_WM8998) += snd-soc-wm8998.o +obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o +obj-$(CONFIG_SND_SOC_WM9090) += snd-soc-wm9090.o +obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o +obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o +obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o +obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o +obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o +obj-$(CONFIG_SND_SOC_ZX_AUD96P22) += snd-soc-zx-aud96p22.o + +# Amp +obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o +obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o +obj-$(CONFIG_SND_SOC_SIMPLE_AMPLIFIER) += snd-soc-simple-amplifier.o +obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c new file mode 100644 index 000000000..03bbbcd3b --- /dev/null +++ b/sound/soc/codecs/ab8500-codec.c @@ -0,0 +1,2586 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Kristoffer Karlsson , + * Roger Nilsson , + * for ST-Ericsson. + * + * Based on the early work done by: + * Mikko J. Lehto , + * Mikko Sarmanne , + * Jarmo K. Kuronen , + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ab8500-codec.h" + +/* Macrocell value definitions */ +#define CLK_32K_OUT2_DISABLE 0x01 +#define INACTIVE_RESET_AUDIO 0x02 +#define ENABLE_AUDIO_CLK_TO_AUDIO_BLK 0x10 +#define ENABLE_VINTCORE12_SUPPLY 0x04 +#define GPIO27_DIR_OUTPUT 0x04 +#define GPIO29_DIR_OUTPUT 0x10 +#define GPIO31_DIR_OUTPUT 0x40 + +/* Macrocell register definitions */ +#define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + +/* Nr of FIR/IIR-coeff banks in ANC-block */ +#define AB8500_NR_OF_ANC_COEFF_BANKS 2 + +/* Minimum duration to keep ANC IIR Init bit high or +low before proceeding with the configuration sequence */ +#define AB8500_ANC_SM_DELAY 2000 + +#define AB8500_FILTER_CONTROL(xname, xcount, xmin, xmax) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = filter_control_info, \ + .get = filter_control_get, .put = filter_control_put, \ + .private_value = (unsigned long)&(struct filter_control) \ + {.count = xcount, .min = xmin, .max = xmax} } + +struct filter_control { + long min, max; + unsigned int count; + long value[128]; +}; + +/* Sidetone states */ +static const char * const enum_sid_state[] = { + "Unconfigured", + "Apply FIR", + "FIR is configured", +}; +enum sid_state { + SID_UNCONFIGURED = 0, + SID_APPLY_FIR = 1, + SID_FIR_CONFIGURED = 2, +}; + +static const char * const enum_anc_state[] = { + "Unconfigured", + "Apply FIR and IIR", + "FIR and IIR are configured", + "Apply FIR", + "FIR is configured", + "Apply IIR", + "IIR is configured" +}; +enum anc_state { + ANC_UNCONFIGURED = 0, + ANC_APPLY_FIR_IIR = 1, + ANC_FIR_IIR_CONFIGURED = 2, + ANC_APPLY_FIR = 3, + ANC_FIR_CONFIGURED = 4, + ANC_APPLY_IIR = 5, + ANC_IIR_CONFIGURED = 6 +}; + +/* Analog microphones */ +enum amic_idx { + AMIC_IDX_1A, + AMIC_IDX_1B, + AMIC_IDX_2 +}; + +struct ab8500_codec_drvdata_dbg { + struct regulator *vaud; + struct regulator *vamic1; + struct regulator *vamic2; + struct regulator *vdmic; +}; + +/* Private data for AB8500 device-driver */ +struct ab8500_codec_drvdata { + struct regmap *regmap; + struct mutex ctrl_lock; + + /* Sidetone */ + long *sid_fir_values; + enum sid_state sid_status; + + /* ANC */ + long *anc_fir_values; + long *anc_iir_values; + enum anc_state anc_status; +}; + +static inline const char *amic_micbias_str(enum amic_micbias micbias) +{ + switch (micbias) { + case AMIC_MICBIAS_VAMIC1: + return "VAMIC1"; + case AMIC_MICBIAS_VAMIC2: + return "VAMIC2"; + default: + return "Unknown"; + } +} + +static inline const char *amic_type_str(enum amic_type type) +{ + switch (type) { + case AMIC_TYPE_DIFFERENTIAL: + return "DIFFERENTIAL"; + case AMIC_TYPE_SINGLE_ENDED: + return "SINGLE ENDED"; + default: + return "Unknown"; + } +} + +/* + * Read'n'write functions + */ + +/* Read a register from the audio-bank of AB8500 */ +static int ab8500_codec_read_reg(void *context, unsigned int reg, + unsigned int *value) +{ + struct device *dev = context; + int status; + + u8 value8; + status = abx500_get_register_interruptible(dev, AB8500_AUDIO, + reg, &value8); + *value = (unsigned int)value8; + + return status; +} + +/* Write to a register in the audio-bank of AB8500 */ +static int ab8500_codec_write_reg(void *context, unsigned int reg, + unsigned int value) +{ + struct device *dev = context; + + return abx500_set_register_interruptible(dev, AB8500_AUDIO, + reg, value); +} + +static const struct regmap_config ab8500_codec_regmap = { + .reg_read = ab8500_codec_read_reg, + .reg_write = ab8500_codec_write_reg, +}; + +/* + * Controls - DAPM + */ + +/* Earpiece */ + +/* Earpiece source selector */ +static const char * const enum_ear_lineout_source[] = {"Headset Left", + "Speaker Left"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ear_lineout_source, AB8500_DMICFILTCONF, + AB8500_DMICFILTCONF_DA3TOEAR, enum_ear_lineout_source); +static const struct snd_kcontrol_new dapm_ear_lineout_source = + SOC_DAPM_ENUM("Earpiece or LineOut Mono Source", + dapm_enum_ear_lineout_source); + +/* LineOut */ + +/* LineOut source selector */ +static const char * const enum_lineout_source[] = {"Mono Path", "Stereo Path"}; +static SOC_ENUM_DOUBLE_DECL(dapm_enum_lineout_source, AB8500_ANACONF5, + AB8500_ANACONF5_HSLDACTOLOL, + AB8500_ANACONF5_HSRDACTOLOR, enum_lineout_source); +static const struct snd_kcontrol_new dapm_lineout_source[] = { + SOC_DAPM_ENUM("LineOut Source", dapm_enum_lineout_source), +}; + +/* Handsfree */ + +/* Speaker Left - ANC selector */ +static const char * const enum_HFx_sel[] = {"Audio Path", "ANC"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_HFl_sel, AB8500_DIGMULTCONF2, + AB8500_DIGMULTCONF2_HFLSEL, enum_HFx_sel); +static const struct snd_kcontrol_new dapm_HFl_select[] = { + SOC_DAPM_ENUM("Speaker Left Source", dapm_enum_HFl_sel), +}; + +/* Speaker Right - ANC selector */ +static SOC_ENUM_SINGLE_DECL(dapm_enum_HFr_sel, AB8500_DIGMULTCONF2, + AB8500_DIGMULTCONF2_HFRSEL, enum_HFx_sel); +static const struct snd_kcontrol_new dapm_HFr_select[] = { + SOC_DAPM_ENUM("Speaker Right Source", dapm_enum_HFr_sel), +}; + +/* Mic 1 */ + +/* Mic 1 - Mic 1a or 1b selector */ +static const char * const enum_mic1ab_sel[] = {"Mic 1b", "Mic 1a"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_mic1ab_sel, AB8500_ANACONF3, + AB8500_ANACONF3_MIC1SEL, enum_mic1ab_sel); +static const struct snd_kcontrol_new dapm_mic1ab_mux[] = { + SOC_DAPM_ENUM("Mic 1a or 1b Select", dapm_enum_mic1ab_sel), +}; + +/* Mic 1 - AD3 - Mic 1 or DMic 3 selector */ +static const char * const enum_ad3_sel[] = {"Mic 1", "DMic 3"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad3_sel, AB8500_DIGMULTCONF1, + AB8500_DIGMULTCONF1_AD3SEL, enum_ad3_sel); +static const struct snd_kcontrol_new dapm_ad3_select[] = { + SOC_DAPM_ENUM("AD3 Source Select", dapm_enum_ad3_sel), +}; + +/* Mic 1 - AD6 - Mic 1 or DMic 6 selector */ +static const char * const enum_ad6_sel[] = {"Mic 1", "DMic 6"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad6_sel, AB8500_DIGMULTCONF1, + AB8500_DIGMULTCONF1_AD6SEL, enum_ad6_sel); +static const struct snd_kcontrol_new dapm_ad6_select[] = { + SOC_DAPM_ENUM("AD6 Source Select", dapm_enum_ad6_sel), +}; + +/* Mic 2 */ + +/* Mic 2 - AD5 - Mic 2 or DMic 5 selector */ +static const char * const enum_ad5_sel[] = {"Mic 2", "DMic 5"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad5_sel, AB8500_DIGMULTCONF1, + AB8500_DIGMULTCONF1_AD5SEL, enum_ad5_sel); +static const struct snd_kcontrol_new dapm_ad5_select[] = { + SOC_DAPM_ENUM("AD5 Source Select", dapm_enum_ad5_sel), +}; + +/* LineIn */ + +/* LineIn left - AD1 - LineIn Left or DMic 1 selector */ +static const char * const enum_ad1_sel[] = {"LineIn Left", "DMic 1"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad1_sel, AB8500_DIGMULTCONF1, + AB8500_DIGMULTCONF1_AD1SEL, enum_ad1_sel); +static const struct snd_kcontrol_new dapm_ad1_select[] = { + SOC_DAPM_ENUM("AD1 Source Select", dapm_enum_ad1_sel), +}; + +/* LineIn right - Mic 2 or LineIn Right selector */ +static const char * const enum_mic2lr_sel[] = {"Mic 2", "LineIn Right"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_mic2lr_sel, AB8500_ANACONF3, + AB8500_ANACONF3_LINRSEL, enum_mic2lr_sel); +static const struct snd_kcontrol_new dapm_mic2lr_select[] = { + SOC_DAPM_ENUM("Mic 2 or LINR Select", dapm_enum_mic2lr_sel), +}; + +/* LineIn right - AD2 - LineIn Right or DMic2 selector */ +static const char * const enum_ad2_sel[] = {"LineIn Right", "DMic 2"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_ad2_sel, AB8500_DIGMULTCONF1, + AB8500_DIGMULTCONF1_AD2SEL, enum_ad2_sel); +static const struct snd_kcontrol_new dapm_ad2_select[] = { + SOC_DAPM_ENUM("AD2 Source Select", dapm_enum_ad2_sel), +}; + + +/* ANC */ + +static const char * const enum_anc_in_sel[] = {"Mic 1 / DMic 6", + "Mic 2 / DMic 5"}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_anc_in_sel, AB8500_DMICFILTCONF, + AB8500_DMICFILTCONF_ANCINSEL, enum_anc_in_sel); +static const struct snd_kcontrol_new dapm_anc_in_select[] = { + SOC_DAPM_ENUM("ANC Source", dapm_enum_anc_in_sel), +}; + +/* ANC - Enable/Disable */ +static const struct snd_kcontrol_new dapm_anc_enable[] = { + SOC_DAPM_SINGLE("Switch", AB8500_ANCCONF1, + AB8500_ANCCONF1_ENANC, 0, 0), +}; + +/* ANC to Earpiece - Mute */ +static const struct snd_kcontrol_new dapm_anc_ear_mute[] = { + SOC_DAPM_SINGLE("Switch", AB8500_DIGMULTCONF1, + AB8500_DIGMULTCONF1_ANCSEL, 1, 0), +}; + + + +/* Sidetone left */ + +/* Sidetone left - Input selector */ +static const char * const enum_stfir1_in_sel[] = { + "LineIn Left", "LineIn Right", "Mic 1", "Headset Left" +}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir1_in_sel, AB8500_DIGMULTCONF2, + AB8500_DIGMULTCONF2_FIRSID1SEL, enum_stfir1_in_sel); +static const struct snd_kcontrol_new dapm_stfir1_in_select[] = { + SOC_DAPM_ENUM("Sidetone Left Source", dapm_enum_stfir1_in_sel), +}; + +/* Sidetone right path */ + +/* Sidetone right - Input selector */ +static const char * const enum_stfir2_in_sel[] = { + "LineIn Right", "Mic 1", "DMic 4", "Headset Right" +}; +static SOC_ENUM_SINGLE_DECL(dapm_enum_stfir2_in_sel, AB8500_DIGMULTCONF2, + AB8500_DIGMULTCONF2_FIRSID2SEL, enum_stfir2_in_sel); +static const struct snd_kcontrol_new dapm_stfir2_in_select[] = { + SOC_DAPM_ENUM("Sidetone Right Source", dapm_enum_stfir2_in_sel), +}; + +/* Vibra */ + +static const char * const enum_pwm2vibx[] = {"Audio Path", "PWM Generator"}; + +static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib1, AB8500_PWMGENCONF1, + AB8500_PWMGENCONF1_PWMTOVIB1, enum_pwm2vibx); + +static const struct snd_kcontrol_new dapm_pwm2vib1[] = { + SOC_DAPM_ENUM("Vibra 1 Controller", dapm_enum_pwm2vib1), +}; + +static SOC_ENUM_SINGLE_DECL(dapm_enum_pwm2vib2, AB8500_PWMGENCONF1, + AB8500_PWMGENCONF1_PWMTOVIB2, enum_pwm2vibx); + +static const struct snd_kcontrol_new dapm_pwm2vib2[] = { + SOC_DAPM_ENUM("Vibra 2 Controller", dapm_enum_pwm2vib2), +}; + +/* + * DAPM-widgets + */ + +static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + + /* Clocks */ + SND_SOC_DAPM_CLOCK_SUPPLY("audioclk"), + + /* Regulators */ + SND_SOC_DAPM_REGULATOR_SUPPLY("V-AUD", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC1", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC2", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0), + + /* Power */ + SND_SOC_DAPM_SUPPLY("Audio Power", + AB8500_POWERUP, AB8500_POWERUP_POWERUP, 0, + NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("Audio Analog Power", + AB8500_POWERUP, AB8500_POWERUP_ENANA, 0, + NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Main supply node */ + SND_SOC_DAPM_SUPPLY("Main Supply", SND_SOC_NOPM, 0, 0, + NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* DA/AD */ + + SND_SOC_DAPM_INPUT("ADC Input"), + SND_SOC_DAPM_ADC("ADC", "ab8500_0c", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("DAC Output"), + + SND_SOC_DAPM_AIF_IN("DA_IN1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN4", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN5", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DA_IN6", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT57", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT68", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* Headset path */ + + SND_SOC_DAPM_SUPPLY("Charge Pump", AB8500_ANACONF5, + AB8500_ANACONF5_ENCPHS, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DA1 Enable", "ab8500_0p", + AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA1, 0), + SND_SOC_DAPM_DAC("DA2 Enable", "ab8500_0p", + AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA2, 0), + + SND_SOC_DAPM_PGA("HSL Digital Volume", SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_PGA("HSR Digital Volume", SND_SOC_NOPM, 0, 0, + NULL, 0), + + SND_SOC_DAPM_DAC("HSL DAC", "ab8500_0p", + AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACHSL, 0), + SND_SOC_DAPM_DAC("HSR DAC", "ab8500_0p", + AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACHSR, 0), + SND_SOC_DAPM_MIXER("HSL DAC Mute", AB8500_MUTECONF, + AB8500_MUTECONF_MUTDACHSL, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("HSR DAC Mute", AB8500_MUTECONF, + AB8500_MUTECONF_MUTDACHSR, 1, + NULL, 0), + SND_SOC_DAPM_DAC("HSL DAC Driver", "ab8500_0p", + AB8500_ANACONF3, AB8500_ANACONF3_ENDRVHSL, 0), + SND_SOC_DAPM_DAC("HSR DAC Driver", "ab8500_0p", + AB8500_ANACONF3, AB8500_ANACONF3_ENDRVHSR, 0), + + SND_SOC_DAPM_MIXER("HSL Mute", + AB8500_MUTECONF, AB8500_MUTECONF_MUTHSL, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("HSR Mute", + AB8500_MUTECONF, AB8500_MUTECONF_MUTHSR, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("HSL Enable", + AB8500_ANACONF4, AB8500_ANACONF4_ENHSL, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("HSR Enable", + AB8500_ANACONF4, AB8500_ANACONF4_ENHSR, 0, + NULL, 0), + SND_SOC_DAPM_PGA("HSL Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_PGA("HSR Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + + SND_SOC_DAPM_OUTPUT("Headset Left"), + SND_SOC_DAPM_OUTPUT("Headset Right"), + + /* LineOut path */ + + SND_SOC_DAPM_MUX("LineOut Source", + SND_SOC_NOPM, 0, 0, dapm_lineout_source), + + SND_SOC_DAPM_MIXER("LOL Disable HFL", + AB8500_ANACONF4, AB8500_ANACONF4_ENHFL, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("LOR Disable HFR", + AB8500_ANACONF4, AB8500_ANACONF4_ENHFR, 1, + NULL, 0), + + SND_SOC_DAPM_MIXER("LOL Enable", + AB8500_ANACONF5, AB8500_ANACONF5_ENLOL, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("LOR Enable", + AB8500_ANACONF5, AB8500_ANACONF5_ENLOR, 0, + NULL, 0), + + SND_SOC_DAPM_OUTPUT("LineOut Left"), + SND_SOC_DAPM_OUTPUT("LineOut Right"), + + /* Earpiece path */ + + SND_SOC_DAPM_MUX("Earpiece or LineOut Mono Source", + SND_SOC_NOPM, 0, 0, &dapm_ear_lineout_source), + SND_SOC_DAPM_MIXER("EAR DAC", + AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACEAR, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("EAR Mute", + AB8500_MUTECONF, AB8500_MUTECONF_MUTEAR, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("EAR Enable", + AB8500_ANACONF4, AB8500_ANACONF4_ENEAR, 0, + NULL, 0), + + SND_SOC_DAPM_OUTPUT("Earpiece"), + + /* Handsfree path */ + + SND_SOC_DAPM_MIXER("DA3 Channel Volume", + AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA3, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DA4 Channel Volume", + AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA4, 0, + NULL, 0), + SND_SOC_DAPM_MUX("Speaker Left Source", + SND_SOC_NOPM, 0, 0, dapm_HFl_select), + SND_SOC_DAPM_MUX("Speaker Right Source", + SND_SOC_NOPM, 0, 0, dapm_HFr_select), + SND_SOC_DAPM_MIXER("HFL DAC", AB8500_DAPATHCONF, + AB8500_DAPATHCONF_ENDACHFL, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("HFR DAC", + AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACHFR, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DA4 or ANC path to HfR", + AB8500_DIGMULTCONF2, AB8500_DIGMULTCONF2_DATOHFREN, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DA3 or ANC path to HfL", + AB8500_DIGMULTCONF2, AB8500_DIGMULTCONF2_DATOHFLEN, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("HFL Enable", + AB8500_ANACONF4, AB8500_ANACONF4_ENHFL, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("HFR Enable", + AB8500_ANACONF4, AB8500_ANACONF4_ENHFR, 0, + NULL, 0), + + SND_SOC_DAPM_OUTPUT("Speaker Left"), + SND_SOC_DAPM_OUTPUT("Speaker Right"), + + /* Vibrator path */ + + SND_SOC_DAPM_INPUT("PWMGEN1"), + SND_SOC_DAPM_INPUT("PWMGEN2"), + + SND_SOC_DAPM_MIXER("DA5 Channel Volume", + AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA5, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DA6 Channel Volume", + AB8500_DAPATHENA, AB8500_DAPATHENA_ENDA6, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("VIB1 DAC", + AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACVIB1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("VIB2 DAC", + AB8500_DAPATHCONF, AB8500_DAPATHCONF_ENDACVIB2, 0, + NULL, 0), + SND_SOC_DAPM_MUX("Vibra 1 Controller", + SND_SOC_NOPM, 0, 0, dapm_pwm2vib1), + SND_SOC_DAPM_MUX("Vibra 2 Controller", + SND_SOC_NOPM, 0, 0, dapm_pwm2vib2), + SND_SOC_DAPM_MIXER("VIB1 Enable", + AB8500_ANACONF4, AB8500_ANACONF4_ENVIB1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("VIB2 Enable", + AB8500_ANACONF4, AB8500_ANACONF4_ENVIB2, 0, + NULL, 0), + + SND_SOC_DAPM_OUTPUT("Vibra 1"), + SND_SOC_DAPM_OUTPUT("Vibra 2"), + + /* Mic 1 */ + + SND_SOC_DAPM_INPUT("Mic 1"), + + SND_SOC_DAPM_MUX("Mic 1a or 1b Select", + SND_SOC_NOPM, 0, 0, dapm_mic1ab_mux), + SND_SOC_DAPM_MIXER("MIC1 Mute", + AB8500_ANACONF2, AB8500_ANACONF2_MUTMIC1, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("MIC1A V-AMICx Enable", + AB8500_ANACONF2, AB8500_ANACONF2_ENMIC1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("MIC1B V-AMICx Enable", + AB8500_ANACONF2, AB8500_ANACONF2_ENMIC1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("MIC1 ADC", + AB8500_ANACONF3, AB8500_ANACONF3_ENADCMIC, 0, + NULL, 0), + SND_SOC_DAPM_MUX("AD3 Source Select", + SND_SOC_NOPM, 0, 0, dapm_ad3_select), + SND_SOC_DAPM_MIXER("AD3 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD3 Enable", + AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34, 0, + NULL, 0), + + /* Mic 2 */ + + SND_SOC_DAPM_INPUT("Mic 2"), + + SND_SOC_DAPM_MIXER("MIC2 Mute", + AB8500_ANACONF2, AB8500_ANACONF2_MUTMIC2, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("MIC2 V-AMICx Enable", AB8500_ANACONF2, + AB8500_ANACONF2_ENMIC2, 0, + NULL, 0), + + /* LineIn */ + + SND_SOC_DAPM_INPUT("LineIn Left"), + SND_SOC_DAPM_INPUT("LineIn Right"), + + SND_SOC_DAPM_MIXER("LINL Mute", + AB8500_ANACONF2, AB8500_ANACONF2_MUTLINL, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("LINR Mute", + AB8500_ANACONF2, AB8500_ANACONF2_MUTLINR, 1, + NULL, 0), + SND_SOC_DAPM_MIXER("LINL Enable", AB8500_ANACONF2, + AB8500_ANACONF2_ENLINL, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("LINR Enable", AB8500_ANACONF2, + AB8500_ANACONF2_ENLINR, 0, + NULL, 0), + + /* LineIn Bypass path */ + SND_SOC_DAPM_MIXER("LINL to HSL Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("LINR to HSR Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + + /* LineIn, Mic 2 */ + SND_SOC_DAPM_MUX("Mic 2 or LINR Select", + SND_SOC_NOPM, 0, 0, dapm_mic2lr_select), + SND_SOC_DAPM_MIXER("LINL ADC", AB8500_ANACONF3, + AB8500_ANACONF3_ENADCLINL, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("LINR ADC", AB8500_ANACONF3, + AB8500_ANACONF3_ENADCLINR, 0, + NULL, 0), + SND_SOC_DAPM_MUX("AD1 Source Select", + SND_SOC_NOPM, 0, 0, dapm_ad1_select), + SND_SOC_DAPM_MUX("AD2 Source Select", + SND_SOC_NOPM, 0, 0, dapm_ad2_select), + SND_SOC_DAPM_MIXER("AD1 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD2 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + + SND_SOC_DAPM_MIXER("AD12 Enable", + AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD12, 0, + NULL, 0), + + /* HD Capture path */ + + SND_SOC_DAPM_MUX("AD5 Source Select", + SND_SOC_NOPM, 0, 0, dapm_ad5_select), + SND_SOC_DAPM_MUX("AD6 Source Select", + SND_SOC_NOPM, 0, 0, dapm_ad6_select), + SND_SOC_DAPM_MIXER("AD5 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD6 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD57 Enable", + AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD68 Enable", + AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, + NULL, 0), + + /* Digital Microphone path */ + + SND_SOC_DAPM_INPUT("DMic 1"), + SND_SOC_DAPM_INPUT("DMic 2"), + SND_SOC_DAPM_INPUT("DMic 3"), + SND_SOC_DAPM_INPUT("DMic 4"), + SND_SOC_DAPM_INPUT("DMic 5"), + SND_SOC_DAPM_INPUT("DMic 6"), + + SND_SOC_DAPM_MIXER("DMIC1", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC2", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC3", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC4", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC5", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC6", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD4 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD4 Enable", + AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34, + 0, NULL, 0), + + /* Acoustical Noise Cancellation path */ + + SND_SOC_DAPM_INPUT("ANC Configure Input"), + SND_SOC_DAPM_OUTPUT("ANC Configure Output"), + + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), + SND_SOC_DAPM_SWITCH("ANC", + SND_SOC_NOPM, 0, 0, + dapm_anc_enable), + SND_SOC_DAPM_SWITCH("ANC to Earpiece", + SND_SOC_NOPM, 0, 0, + dapm_anc_ear_mute), + + /* Sidetone Filter path */ + + SND_SOC_DAPM_MUX("Sidetone Left Source", + SND_SOC_NOPM, 0, 0, + dapm_stfir1_in_select), + SND_SOC_DAPM_MUX("Sidetone Right Source", + SND_SOC_NOPM, 0, 0, + dapm_stfir2_in_select), + SND_SOC_DAPM_MIXER("STFIR1 Control", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("STFIR2 Control", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("STFIR1 Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("STFIR2 Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), +}; + +/* + * DAPM-routes + */ +static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + /* Power AB8500 audio-block when AD/DA is active */ + {"Main Supply", NULL, "V-AUD"}, + {"Main Supply", NULL, "audioclk"}, + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + + {"DAC", NULL, "ab8500_0p"}, + {"DAC", NULL, "Main Supply"}, + {"ADC", NULL, "ab8500_0c"}, + {"ADC", NULL, "Main Supply"}, + + /* ANC Configure */ + {"ANC Configure Input", NULL, "Main Supply"}, + {"ANC Configure Output", NULL, "ANC Configure Input"}, + + /* AD/DA */ + {"ADC", NULL, "ADC Input"}, + {"DAC Output", NULL, "DAC"}, + + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, + {"DA_IN1", NULL, "Charge Pump"}, + {"DA_IN2", NULL, "ab8500_0p"}, + {"DA_IN2", NULL, "Charge Pump"}, + + /* Headset path */ + + {"DA1 Enable", NULL, "DA_IN1"}, + {"DA2 Enable", NULL, "DA_IN2"}, + + {"HSL Digital Volume", NULL, "DA1 Enable"}, + {"HSR Digital Volume", NULL, "DA2 Enable"}, + + {"HSL DAC", NULL, "HSL Digital Volume"}, + {"HSR DAC", NULL, "HSR Digital Volume"}, + + {"HSL DAC Mute", NULL, "HSL DAC"}, + {"HSR DAC Mute", NULL, "HSR DAC"}, + + {"HSL DAC Driver", NULL, "HSL DAC Mute"}, + {"HSR DAC Driver", NULL, "HSR DAC Mute"}, + + {"HSL Mute", NULL, "HSL DAC Driver"}, + {"HSR Mute", NULL, "HSR DAC Driver"}, + + {"HSL Enable", NULL, "HSL Mute"}, + {"HSR Enable", NULL, "HSR Mute"}, + + {"HSL Volume", NULL, "HSL Enable"}, + {"HSR Volume", NULL, "HSR Enable"}, + + {"Headset Left", NULL, "HSL Volume"}, + {"Headset Right", NULL, "HSR Volume"}, + + /* HF or LineOut path */ + + {"DA_IN3", NULL, "ab8500_0p"}, + {"DA3 Channel Volume", NULL, "DA_IN3"}, + {"DA_IN4", NULL, "ab8500_0p"}, + {"DA4 Channel Volume", NULL, "DA_IN4"}, + + {"Speaker Left Source", "Audio Path", "DA3 Channel Volume"}, + {"Speaker Right Source", "Audio Path", "DA4 Channel Volume"}, + + {"DA3 or ANC path to HfL", NULL, "Speaker Left Source"}, + {"DA4 or ANC path to HfR", NULL, "Speaker Right Source"}, + + /* HF path */ + + {"HFL DAC", NULL, "DA3 or ANC path to HfL"}, + {"HFR DAC", NULL, "DA4 or ANC path to HfR"}, + + {"HFL Enable", NULL, "HFL DAC"}, + {"HFR Enable", NULL, "HFR DAC"}, + + {"Speaker Left", NULL, "HFL Enable"}, + {"Speaker Right", NULL, "HFR Enable"}, + + /* Earpiece path */ + + {"Earpiece or LineOut Mono Source", "Headset Left", + "HSL Digital Volume"}, + {"Earpiece or LineOut Mono Source", "Speaker Left", + "DA3 or ANC path to HfL"}, + + {"EAR DAC", NULL, "Earpiece or LineOut Mono Source"}, + + {"EAR Mute", NULL, "EAR DAC"}, + + {"EAR Enable", NULL, "EAR Mute"}, + + {"Earpiece", NULL, "EAR Enable"}, + + /* LineOut path stereo */ + + {"LineOut Source", "Stereo Path", "HSL DAC Driver"}, + {"LineOut Source", "Stereo Path", "HSR DAC Driver"}, + + /* LineOut path mono */ + + {"LineOut Source", "Mono Path", "EAR DAC"}, + + /* LineOut path */ + + {"LOL Disable HFL", NULL, "LineOut Source"}, + {"LOR Disable HFR", NULL, "LineOut Source"}, + + {"LOL Enable", NULL, "LOL Disable HFL"}, + {"LOR Enable", NULL, "LOR Disable HFR"}, + + {"LineOut Left", NULL, "LOL Enable"}, + {"LineOut Right", NULL, "LOR Enable"}, + + /* Vibrator path */ + + {"DA_IN5", NULL, "ab8500_0p"}, + {"DA5 Channel Volume", NULL, "DA_IN5"}, + {"DA_IN6", NULL, "ab8500_0p"}, + {"DA6 Channel Volume", NULL, "DA_IN6"}, + + {"VIB1 DAC", NULL, "DA5 Channel Volume"}, + {"VIB2 DAC", NULL, "DA6 Channel Volume"}, + + {"Vibra 1 Controller", "Audio Path", "VIB1 DAC"}, + {"Vibra 2 Controller", "Audio Path", "VIB2 DAC"}, + {"Vibra 1 Controller", "PWM Generator", "PWMGEN1"}, + {"Vibra 2 Controller", "PWM Generator", "PWMGEN2"}, + + {"VIB1 Enable", NULL, "Vibra 1 Controller"}, + {"VIB2 Enable", NULL, "Vibra 2 Controller"}, + + {"Vibra 1", NULL, "VIB1 Enable"}, + {"Vibra 2", NULL, "VIB2 Enable"}, + + + /* Mic 2 */ + + {"MIC2 V-AMICx Enable", NULL, "Mic 2"}, + + /* LineIn */ + {"LINL Mute", NULL, "LineIn Left"}, + {"LINR Mute", NULL, "LineIn Right"}, + + {"LINL Enable", NULL, "LINL Mute"}, + {"LINR Enable", NULL, "LINR Mute"}, + + /* LineIn, Mic 2 */ + {"Mic 2 or LINR Select", "LineIn Right", "LINR Enable"}, + {"Mic 2 or LINR Select", "Mic 2", "MIC2 V-AMICx Enable"}, + + {"LINL ADC", NULL, "LINL Enable"}, + {"LINR ADC", NULL, "Mic 2 or LINR Select"}, + + {"AD1 Source Select", "LineIn Left", "LINL ADC"}, + {"AD2 Source Select", "LineIn Right", "LINR ADC"}, + + {"AD1 Channel Volume", NULL, "AD1 Source Select"}, + {"AD2 Channel Volume", NULL, "AD2 Source Select"}, + + {"AD12 Enable", NULL, "AD1 Channel Volume"}, + {"AD12 Enable", NULL, "AD2 Channel Volume"}, + + {"AD_OUT1", NULL, "ab8500_0c"}, + {"AD_OUT1", NULL, "AD12 Enable"}, + {"AD_OUT2", NULL, "ab8500_0c"}, + {"AD_OUT2", NULL, "AD12 Enable"}, + + /* Mic 1 */ + + {"MIC1 Mute", NULL, "Mic 1"}, + + {"MIC1A V-AMICx Enable", NULL, "MIC1 Mute"}, + {"MIC1B V-AMICx Enable", NULL, "MIC1 Mute"}, + + {"Mic 1a or 1b Select", "Mic 1a", "MIC1A V-AMICx Enable"}, + {"Mic 1a or 1b Select", "Mic 1b", "MIC1B V-AMICx Enable"}, + + {"MIC1 ADC", NULL, "Mic 1a or 1b Select"}, + + {"AD3 Source Select", "Mic 1", "MIC1 ADC"}, + + {"AD3 Channel Volume", NULL, "AD3 Source Select"}, + + {"AD3 Enable", NULL, "AD3 Channel Volume"}, + + {"AD_OUT3", NULL, "ab8500_0c"}, + {"AD_OUT3", NULL, "AD3 Enable"}, + + /* HD Capture path */ + + {"AD5 Source Select", "Mic 2", "LINR ADC"}, + {"AD6 Source Select", "Mic 1", "MIC1 ADC"}, + + {"AD5 Channel Volume", NULL, "AD5 Source Select"}, + {"AD6 Channel Volume", NULL, "AD6 Source Select"}, + + {"AD57 Enable", NULL, "AD5 Channel Volume"}, + {"AD68 Enable", NULL, "AD6 Channel Volume"}, + + {"AD_OUT57", NULL, "ab8500_0c"}, + {"AD_OUT57", NULL, "AD57 Enable"}, + {"AD_OUT68", NULL, "ab8500_0c"}, + {"AD_OUT68", NULL, "AD68 Enable"}, + + /* Digital Microphone path */ + + {"DMic 1", NULL, "V-DMIC"}, + {"DMic 2", NULL, "V-DMIC"}, + {"DMic 3", NULL, "V-DMIC"}, + {"DMic 4", NULL, "V-DMIC"}, + {"DMic 5", NULL, "V-DMIC"}, + {"DMic 6", NULL, "V-DMIC"}, + + {"AD1 Source Select", NULL, "DMic 1"}, + {"AD2 Source Select", NULL, "DMic 2"}, + {"AD3 Source Select", NULL, "DMic 3"}, + {"AD5 Source Select", NULL, "DMic 5"}, + {"AD6 Source Select", NULL, "DMic 6"}, + + {"AD4 Channel Volume", NULL, "DMic 4"}, + {"AD4 Enable", NULL, "AD4 Channel Volume"}, + + {"AD_OUT4", NULL, "ab8500_0c"}, + {"AD_OUT4", NULL, "AD4 Enable"}, + + /* LineIn Bypass path */ + + {"LINL to HSL Volume", NULL, "LINL Enable"}, + {"LINR to HSR Volume", NULL, "LINR Enable"}, + + {"HSL DAC Driver", NULL, "LINL to HSL Volume"}, + {"HSR DAC Driver", NULL, "LINR to HSR Volume"}, + + /* ANC path (Acoustic Noise Cancellation) */ + + {"ANC Source", "Mic 2 / DMic 5", "AD5 Channel Volume"}, + {"ANC Source", "Mic 1 / DMic 6", "AD6 Channel Volume"}, + + {"ANC", "Switch", "ANC Source"}, + + {"Speaker Left Source", "ANC", "ANC"}, + {"Speaker Right Source", "ANC", "ANC"}, + {"ANC to Earpiece", "Switch", "ANC"}, + + {"HSL Digital Volume", NULL, "ANC to Earpiece"}, + + /* Sidetone Filter path */ + + {"Sidetone Left Source", "LineIn Left", "AD12 Enable"}, + {"Sidetone Left Source", "LineIn Right", "AD12 Enable"}, + {"Sidetone Left Source", "Mic 1", "AD3 Enable"}, + {"Sidetone Left Source", "Headset Left", "DA_IN1"}, + {"Sidetone Right Source", "LineIn Right", "AD12 Enable"}, + {"Sidetone Right Source", "Mic 1", "AD3 Enable"}, + {"Sidetone Right Source", "DMic 4", "AD4 Enable"}, + {"Sidetone Right Source", "Headset Right", "DA_IN2"}, + + {"STFIR1 Control", NULL, "Sidetone Left Source"}, + {"STFIR2 Control", NULL, "Sidetone Right Source"}, + + {"STFIR1 Volume", NULL, "STFIR1 Control"}, + {"STFIR2 Volume", NULL, "STFIR2 Control"}, + + {"DA1 Enable", NULL, "STFIR1 Volume"}, + {"DA2 Enable", NULL, "STFIR2 Volume"}, +}; + +static const struct snd_soc_dapm_route ab8500_dapm_routes_mic1a_vamicx[] = { + {"MIC1A V-AMICx Enable", NULL, "V-AMIC1"}, + {"MIC1A V-AMICx Enable", NULL, "V-AMIC2"}, +}; + +static const struct snd_soc_dapm_route ab8500_dapm_routes_mic1b_vamicx[] = { + {"MIC1B V-AMICx Enable", NULL, "V-AMIC1"}, + {"MIC1B V-AMICx Enable", NULL, "V-AMIC2"}, +}; + +static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC1"}, + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, +}; + +/* ANC FIR-coefficients configuration sequence */ +static void anc_fir(struct snd_soc_component *component, + unsigned int bnk, unsigned int par, unsigned int val) +{ + if (par == 0 && bnk == 0) + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ANCFIRUPDATE), + BIT(AB8500_ANCCONF1_ANCFIRUPDATE)); + + snd_soc_component_write(component, AB8500_ANCCONF5, val >> 8 & 0xff); + snd_soc_component_write(component, AB8500_ANCCONF6, val & 0xff); + + if (par == AB8500_ANC_FIR_COEFFS - 1 && bnk == 1) + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ANCFIRUPDATE), 0); +} + +/* ANC IIR-coefficients configuration sequence */ +static void anc_iir(struct snd_soc_component *component, unsigned int bnk, + unsigned int par, unsigned int val) +{ + if (par == 0) { + if (bnk == 0) { + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ANCIIRINIT), + BIT(AB8500_ANCCONF1_ANCIIRINIT)); + usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY); + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ANCIIRINIT), 0); + usleep_range(AB8500_ANC_SM_DELAY, AB8500_ANC_SM_DELAY); + } else { + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ANCIIRUPDATE), + BIT(AB8500_ANCCONF1_ANCIIRUPDATE)); + } + } else if (par > 3) { + snd_soc_component_write(component, AB8500_ANCCONF7, 0); + snd_soc_component_write(component, AB8500_ANCCONF8, val >> 16 & 0xff); + } + + snd_soc_component_write(component, AB8500_ANCCONF7, val >> 8 & 0xff); + snd_soc_component_write(component, AB8500_ANCCONF8, val & 0xff); + + if (par == AB8500_ANC_IIR_COEFFS - 1 && bnk == 1) + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ANCIIRUPDATE), 0); +} + +/* ANC IIR-/FIR-coefficients configuration sequence */ +static void anc_configure(struct snd_soc_component *component, + bool apply_fir, bool apply_iir) +{ + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + unsigned int bnk, par, val; + + dev_dbg(component->dev, "%s: Enter.\n", __func__); + + if (apply_fir) + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ENANC), 0); + + snd_soc_component_update_bits(component, AB8500_ANCCONF1, + BIT(AB8500_ANCCONF1_ENANC), BIT(AB8500_ANCCONF1_ENANC)); + + if (apply_fir) + for (bnk = 0; bnk < AB8500_NR_OF_ANC_COEFF_BANKS; bnk++) + for (par = 0; par < AB8500_ANC_FIR_COEFFS; par++) { + val = snd_soc_component_read32(component, + drvdata->anc_fir_values[par]); + anc_fir(component, bnk, par, val); + } + + if (apply_iir) + for (bnk = 0; bnk < AB8500_NR_OF_ANC_COEFF_BANKS; bnk++) + for (par = 0; par < AB8500_ANC_IIR_COEFFS; par++) { + val = snd_soc_component_read32(component, + drvdata->anc_iir_values[par]); + anc_iir(component, bnk, par, val); + } + + dev_dbg(component->dev, "%s: Exit.\n", __func__); +} + +/* + * Control-events + */ + +static int sid_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + + mutex_lock(&drvdata->ctrl_lock); + ucontrol->value.enumerated.item[0] = drvdata->sid_status; + mutex_unlock(&drvdata->ctrl_lock); + + return 0; +} + +/* Write sidetone FIR-coefficients configuration sequence */ +static int sid_status_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + unsigned int param, sidconf, val; + int status = 1; + + dev_dbg(component->dev, "%s: Enter\n", __func__); + + if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { + dev_err(component->dev, + "%s: ERROR: This control supports '%s' only!\n", + __func__, enum_sid_state[SID_APPLY_FIR]); + return -EIO; + } + + mutex_lock(&drvdata->ctrl_lock); + + sidconf = snd_soc_component_read32(component, AB8500_SIDFIRCONF); + if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { + if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { + dev_err(component->dev, "%s: Sidetone busy while off!\n", + __func__); + status = -EPERM; + } else { + status = -EBUSY; + } + goto out; + } + + snd_soc_component_write(component, AB8500_SIDFIRADR, 0); + + for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { + val = snd_soc_component_read32(component, drvdata->sid_fir_values[param]); + snd_soc_component_write(component, AB8500_SIDFIRCOEF1, val >> 8 & 0xff); + snd_soc_component_write(component, AB8500_SIDFIRCOEF2, val & 0xff); + } + + snd_soc_component_update_bits(component, AB8500_SIDFIRADR, + BIT(AB8500_SIDFIRADR_FIRSIDSET), + BIT(AB8500_SIDFIRADR_FIRSIDSET)); + snd_soc_component_update_bits(component, AB8500_SIDFIRADR, + BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); + + drvdata->sid_status = SID_FIR_CONFIGURED; + +out: + mutex_unlock(&drvdata->ctrl_lock); + + dev_dbg(component->dev, "%s: Exit\n", __func__); + + return status; +} + +static int anc_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + + mutex_lock(&drvdata->ctrl_lock); + ucontrol->value.enumerated.item[0] = drvdata->anc_status; + mutex_unlock(&drvdata->ctrl_lock); + + return 0; +} + +static int anc_status_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); + struct device *dev = component->dev; + bool apply_fir, apply_iir; + unsigned int req; + int status; + + dev_dbg(dev, "%s: Enter.\n", __func__); + + mutex_lock(&drvdata->ctrl_lock); + + req = ucontrol->value.enumerated.item[0]; + if (req >= ARRAY_SIZE(enum_anc_state)) { + status = -EINVAL; + goto cleanup; + } + if (req != ANC_APPLY_FIR_IIR && req != ANC_APPLY_FIR && + req != ANC_APPLY_IIR) { + dev_err(dev, "%s: ERROR: Unsupported status to set '%s'!\n", + __func__, enum_anc_state[req]); + status = -EINVAL; + goto cleanup; + } + apply_fir = req == ANC_APPLY_FIR || req == ANC_APPLY_FIR_IIR; + apply_iir = req == ANC_APPLY_IIR || req == ANC_APPLY_FIR_IIR; + + status = snd_soc_dapm_force_enable_pin(dapm, "ANC Configure Input"); + if (status < 0) { + dev_err(dev, + "%s: ERROR: Failed to enable power (status = %d)!\n", + __func__, status); + goto cleanup; + } + snd_soc_dapm_sync(dapm); + + anc_configure(component, apply_fir, apply_iir); + + if (apply_fir) { + if (drvdata->anc_status == ANC_IIR_CONFIGURED) + drvdata->anc_status = ANC_FIR_IIR_CONFIGURED; + else if (drvdata->anc_status != ANC_FIR_IIR_CONFIGURED) + drvdata->anc_status = ANC_FIR_CONFIGURED; + } + if (apply_iir) { + if (drvdata->anc_status == ANC_FIR_CONFIGURED) + drvdata->anc_status = ANC_FIR_IIR_CONFIGURED; + else if (drvdata->anc_status != ANC_FIR_IIR_CONFIGURED) + drvdata->anc_status = ANC_IIR_CONFIGURED; + } + + status = snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); + snd_soc_dapm_sync(dapm); + +cleanup: + mutex_unlock(&drvdata->ctrl_lock); + + if (status < 0) + dev_err(dev, "%s: Unable to configure ANC! (status = %d)\n", + __func__, status); + + dev_dbg(dev, "%s: Exit.\n", __func__); + + return (status < 0) ? status : 1; +} + +static int filter_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct filter_control *fc = + (struct filter_control *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = fc->count; + uinfo->value.integer.min = fc->min; + uinfo->value.integer.max = fc->max; + + return 0; +} + +static int filter_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ab8500_codec_drvdata *drvdata = snd_soc_component_get_drvdata(component); + struct filter_control *fc = + (struct filter_control *)kcontrol->private_value; + unsigned int i; + + mutex_lock(&drvdata->ctrl_lock); + for (i = 0; i < fc->count; i++) + ucontrol->value.integer.value[i] = fc->value[i]; + mutex_unlock(&drvdata->ctrl_lock); + + return 0; +} + +static int filter_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ab8500_codec_drvdata *drvdata = snd_soc_component_get_drvdata(component); + struct filter_control *fc = + (struct filter_control *)kcontrol->private_value; + unsigned int i; + + mutex_lock(&drvdata->ctrl_lock); + for (i = 0; i < fc->count; i++) + fc->value[i] = ucontrol->value.integer.value[i]; + mutex_unlock(&drvdata->ctrl_lock); + + return 0; +} + +/* + * Controls - Non-DAPM ASoC + */ + +static DECLARE_TLV_DB_SCALE(adx_dig_gain_tlv, -3200, 100, 1); +/* -32dB = Mute */ + +static DECLARE_TLV_DB_SCALE(dax_dig_gain_tlv, -6300, 100, 1); +/* -63dB = Mute */ + +static DECLARE_TLV_DB_SCALE(hs_ear_dig_gain_tlv, -100, 100, 1); +/* -1dB = Mute */ + +static const DECLARE_TLV_DB_RANGE(hs_gain_tlv, + 0, 3, TLV_DB_SCALE_ITEM(-3200, 400, 0), + 4, 15, TLV_DB_SCALE_ITEM(-1800, 200, 0) +); + +static DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0); + +static DECLARE_TLV_DB_SCALE(lin_gain_tlv, -1000, 200, 0); + +static DECLARE_TLV_DB_SCALE(lin2hs_gain_tlv, -3800, 200, 1); +/* -38dB = Mute */ + +static const char * const enum_hsfadspeed[] = {"2ms", "0.5ms", "10.6ms", + "5ms"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_hsfadspeed, + AB8500_DIGMICCONF, AB8500_DIGMICCONF_HSFADSPEED, enum_hsfadspeed); + +static const char * const enum_envdetthre[] = { + "250mV", "300mV", "350mV", "400mV", + "450mV", "500mV", "550mV", "600mV", + "650mV", "700mV", "750mV", "800mV", + "850mV", "900mV", "950mV", "1.00V" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_envdeththre, + AB8500_ENVCPCONF, AB8500_ENVCPCONF_ENVDETHTHRE, enum_envdetthre); +static SOC_ENUM_SINGLE_DECL(soc_enum_envdetlthre, + AB8500_ENVCPCONF, AB8500_ENVCPCONF_ENVDETLTHRE, enum_envdetthre); +static const char * const enum_envdettime[] = { + "26.6us", "53.2us", "106us", "213us", + "426us", "851us", "1.70ms", "3.40ms", + "6.81ms", "13.6ms", "27.2ms", "54.5ms", + "109ms", "218ms", "436ms", "872ms" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_envdettime, + AB8500_SIGENVCONF, AB8500_SIGENVCONF_ENVDETTIME, enum_envdettime); + +static const char * const enum_sinc31[] = {"Sinc 3", "Sinc 1"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_hsesinc, AB8500_HSLEARDIGGAIN, + AB8500_HSLEARDIGGAIN_HSSINC1, enum_sinc31); + +static const char * const enum_fadespeed[] = {"1ms", "4ms", "8ms", "16ms"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_fadespeed, AB8500_HSRDIGGAIN, + AB8500_HSRDIGGAIN_FADESPEED, enum_fadespeed); + +/* Earpiece */ + +static const char * const enum_lowpow[] = {"Normal", "Low Power"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_eardaclowpow, AB8500_ANACONF1, + AB8500_ANACONF1_EARDACLOWPOW, enum_lowpow); +static SOC_ENUM_SINGLE_DECL(soc_enum_eardrvlowpow, AB8500_ANACONF1, + AB8500_ANACONF1_EARDRVLOWPOW, enum_lowpow); + +static const char * const enum_av_mode[] = {"Audio", "Voice"}; +static SOC_ENUM_DOUBLE_DECL(soc_enum_ad12voice, AB8500_ADFILTCONF, + AB8500_ADFILTCONF_AD1VOICE, AB8500_ADFILTCONF_AD2VOICE, enum_av_mode); +static SOC_ENUM_DOUBLE_DECL(soc_enum_ad34voice, AB8500_ADFILTCONF, + AB8500_ADFILTCONF_AD3VOICE, AB8500_ADFILTCONF_AD4VOICE, enum_av_mode); + +/* DA */ + +static SOC_ENUM_SINGLE_DECL(soc_enum_da12voice, + AB8500_DASLOTCONF1, AB8500_DASLOTCONF1_DA12VOICE, + enum_av_mode); +static SOC_ENUM_SINGLE_DECL(soc_enum_da34voice, + AB8500_DASLOTCONF3, AB8500_DASLOTCONF3_DA34VOICE, + enum_av_mode); +static SOC_ENUM_SINGLE_DECL(soc_enum_da56voice, + AB8500_DASLOTCONF5, AB8500_DASLOTCONF5_DA56VOICE, + enum_av_mode); + +static const char * const enum_da2hslr[] = {"Sidetone", "Audio Path"}; +static SOC_ENUM_DOUBLE_DECL(soc_enum_da2hslr, AB8500_DIGMULTCONF1, + AB8500_DIGMULTCONF1_DATOHSLEN, + AB8500_DIGMULTCONF1_DATOHSREN, enum_da2hslr); + +static const char * const enum_sinc53[] = {"Sinc 5", "Sinc 3"}; +static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic12sinc, AB8500_DMICFILTCONF, + AB8500_DMICFILTCONF_DMIC1SINC3, + AB8500_DMICFILTCONF_DMIC2SINC3, enum_sinc53); +static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic34sinc, AB8500_DMICFILTCONF, + AB8500_DMICFILTCONF_DMIC3SINC3, + AB8500_DMICFILTCONF_DMIC4SINC3, enum_sinc53); +static SOC_ENUM_DOUBLE_DECL(soc_enum_dmic56sinc, AB8500_DMICFILTCONF, + AB8500_DMICFILTCONF_DMIC5SINC3, + AB8500_DMICFILTCONF_DMIC6SINC3, enum_sinc53); + +/* Digital interface - DA from slot mapping */ +static const char * const enum_da_from_slot_map[] = {"SLOT0", + "SLOT1", + "SLOT2", + "SLOT3", + "SLOT4", + "SLOT5", + "SLOT6", + "SLOT7", + "SLOT8", + "SLOT9", + "SLOT10", + "SLOT11", + "SLOT12", + "SLOT13", + "SLOT14", + "SLOT15", + "SLOT16", + "SLOT17", + "SLOT18", + "SLOT19", + "SLOT20", + "SLOT21", + "SLOT22", + "SLOT23", + "SLOT24", + "SLOT25", + "SLOT26", + "SLOT27", + "SLOT28", + "SLOT29", + "SLOT30", + "SLOT31"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_da1slotmap, + AB8500_DASLOTCONF1, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da2slotmap, + AB8500_DASLOTCONF2, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da3slotmap, + AB8500_DASLOTCONF3, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da4slotmap, + AB8500_DASLOTCONF4, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da5slotmap, + AB8500_DASLOTCONF5, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da6slotmap, + AB8500_DASLOTCONF6, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da7slotmap, + AB8500_DASLOTCONF7, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_da8slotmap, + AB8500_DASLOTCONF8, AB8500_DASLOTCONFX_SLTODAX_SHIFT, + enum_da_from_slot_map); + +/* Digital interface - AD to slot mapping */ +static const char * const enum_ad_to_slot_map[] = {"AD_OUT1", + "AD_OUT2", + "AD_OUT3", + "AD_OUT4", + "AD_OUT5", + "AD_OUT6", + "AD_OUT7", + "AD_OUT8", + "zeroes", + "zeroes", + "zeroes", + "zeroes", + "tristate", + "tristate", + "tristate", + "tristate"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot0map, + AB8500_ADSLOTSEL1, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot1map, + AB8500_ADSLOTSEL1, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot2map, + AB8500_ADSLOTSEL2, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot3map, + AB8500_ADSLOTSEL2, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot4map, + AB8500_ADSLOTSEL3, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot5map, + AB8500_ADSLOTSEL3, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot6map, + AB8500_ADSLOTSEL4, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot7map, + AB8500_ADSLOTSEL4, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot8map, + AB8500_ADSLOTSEL5, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot9map, + AB8500_ADSLOTSEL5, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot10map, + AB8500_ADSLOTSEL6, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot11map, + AB8500_ADSLOTSEL6, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot12map, + AB8500_ADSLOTSEL7, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot13map, + AB8500_ADSLOTSEL7, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot14map, + AB8500_ADSLOTSEL8, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot15map, + AB8500_ADSLOTSEL8, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot16map, + AB8500_ADSLOTSEL9, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot17map, + AB8500_ADSLOTSEL9, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot18map, + AB8500_ADSLOTSEL10, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot19map, + AB8500_ADSLOTSEL10, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot20map, + AB8500_ADSLOTSEL11, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot21map, + AB8500_ADSLOTSEL11, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot22map, + AB8500_ADSLOTSEL12, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot23map, + AB8500_ADSLOTSEL12, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot24map, + AB8500_ADSLOTSEL13, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot25map, + AB8500_ADSLOTSEL13, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot26map, + AB8500_ADSLOTSEL14, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot27map, + AB8500_ADSLOTSEL14, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot28map, + AB8500_ADSLOTSEL15, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot29map, + AB8500_ADSLOTSEL15, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot30map, + AB8500_ADSLOTSEL16, AB8500_ADSLOTSELX_EVEN_SHIFT, + enum_ad_to_slot_map); +static SOC_ENUM_SINGLE_DECL(soc_enum_adslot31map, + AB8500_ADSLOTSEL16, AB8500_ADSLOTSELX_ODD_SHIFT, + enum_ad_to_slot_map); + +/* Digital interface - Burst mode */ +static const char * const enum_mask[] = {"Unmasked", "Masked"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomask, + AB8500_FIFOCONF1, AB8500_FIFOCONF1_BFIFOMASK, + enum_mask); +static const char * const enum_bitclk0[] = {"19_2_MHz", "38_4_MHz"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bfifo19m2, + AB8500_FIFOCONF1, AB8500_FIFOCONF1_BFIFO19M2, + enum_bitclk0); +static const char * const enum_slavemaster[] = {"Slave", "Master"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + +/* Sidetone */ +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); + +/* ANC */ +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state); + +static struct snd_kcontrol_new ab8500_ctrls[] = { + /* Charge pump */ + SOC_ENUM("Charge Pump High Threshold For Low Voltage", + soc_enum_envdeththre), + SOC_ENUM("Charge Pump Low Threshold For Low Voltage", + soc_enum_envdetlthre), + SOC_SINGLE("Charge Pump Envelope Detection Switch", + AB8500_SIGENVCONF, AB8500_SIGENVCONF_ENVDETCPEN, + 1, 0), + SOC_ENUM("Charge Pump Envelope Detection Decay Time", + soc_enum_envdettime), + + /* Headset */ + SOC_ENUM("Headset Mode", soc_enum_da12voice), + SOC_SINGLE("Headset High Pass Switch", + AB8500_ANACONF1, AB8500_ANACONF1_HSHPEN, + 1, 0), + SOC_SINGLE("Headset Low Power Switch", + AB8500_ANACONF1, AB8500_ANACONF1_HSLOWPOW, + 1, 0), + SOC_SINGLE("Headset DAC Low Power Switch", + AB8500_ANACONF1, AB8500_ANACONF1_DACLOWPOW1, + 1, 0), + SOC_SINGLE("Headset DAC Drv Low Power Switch", + AB8500_ANACONF1, AB8500_ANACONF1_DACLOWPOW0, + 1, 0), + SOC_ENUM("Headset Fade Speed", soc_enum_hsfadspeed), + SOC_ENUM("Headset Source", soc_enum_da2hslr), + SOC_ENUM("Headset Filter", soc_enum_hsesinc), + SOC_DOUBLE_R_TLV("Headset Master Volume", + AB8500_DADIGGAIN1, AB8500_DADIGGAIN2, + 0, AB8500_DADIGGAINX_DAXGAIN_MAX, 1, dax_dig_gain_tlv), + SOC_DOUBLE_R_TLV("Headset Digital Volume", + AB8500_HSLEARDIGGAIN, AB8500_HSRDIGGAIN, + 0, AB8500_HSLEARDIGGAIN_HSLDGAIN_MAX, 1, hs_ear_dig_gain_tlv), + SOC_DOUBLE_TLV("Headset Volume", + AB8500_ANAGAIN3, + AB8500_ANAGAIN3_HSLGAIN, AB8500_ANAGAIN3_HSRGAIN, + AB8500_ANAGAIN3_HSXGAIN_MAX, 1, hs_gain_tlv), + + /* Earpiece */ + SOC_ENUM("Earpiece DAC Mode", + soc_enum_eardaclowpow), + SOC_ENUM("Earpiece DAC Drv Mode", + soc_enum_eardrvlowpow), + + /* HandsFree */ + SOC_ENUM("HF Mode", soc_enum_da34voice), + SOC_SINGLE("HF and Headset Swap Switch", + AB8500_DASLOTCONF1, AB8500_DASLOTCONF1_SWAPDA12_34, + 1, 0), + SOC_DOUBLE("HF Low EMI Mode Switch", + AB8500_CLASSDCONF1, + AB8500_CLASSDCONF1_HFLSWAPEN, AB8500_CLASSDCONF1_HFRSWAPEN, + 1, 0), + SOC_DOUBLE("HF FIR Bypass Switch", + AB8500_CLASSDCONF2, + AB8500_CLASSDCONF2_FIRBYP0, AB8500_CLASSDCONF2_FIRBYP1, + 1, 0), + SOC_DOUBLE("HF High Volume Switch", + AB8500_CLASSDCONF2, + AB8500_CLASSDCONF2_HIGHVOLEN0, AB8500_CLASSDCONF2_HIGHVOLEN1, + 1, 0), + SOC_SINGLE("HF L and R Bridge Switch", + AB8500_CLASSDCONF1, AB8500_CLASSDCONF1_PARLHF, + 1, 0), + SOC_DOUBLE_R_TLV("HF Master Volume", + AB8500_DADIGGAIN3, AB8500_DADIGGAIN4, + 0, AB8500_DADIGGAINX_DAXGAIN_MAX, 1, dax_dig_gain_tlv), + + /* Vibra */ + SOC_DOUBLE("Vibra High Volume Switch", + AB8500_CLASSDCONF2, + AB8500_CLASSDCONF2_HIGHVOLEN2, AB8500_CLASSDCONF2_HIGHVOLEN3, + 1, 0), + SOC_DOUBLE("Vibra Low EMI Mode Switch", + AB8500_CLASSDCONF1, + AB8500_CLASSDCONF1_VIB1SWAPEN, AB8500_CLASSDCONF1_VIB2SWAPEN, + 1, 0), + SOC_DOUBLE("Vibra FIR Bypass Switch", + AB8500_CLASSDCONF2, + AB8500_CLASSDCONF2_FIRBYP2, AB8500_CLASSDCONF2_FIRBYP3, + 1, 0), + SOC_ENUM("Vibra Mode", soc_enum_da56voice), + SOC_DOUBLE_R("Vibra PWM Duty Cycle N", + AB8500_PWMGENCONF3, AB8500_PWMGENCONF5, + AB8500_PWMGENCONFX_PWMVIBXDUTCYC, + AB8500_PWMGENCONFX_PWMVIBXDUTCYC_MAX, 0), + SOC_DOUBLE_R("Vibra PWM Duty Cycle P", + AB8500_PWMGENCONF2, AB8500_PWMGENCONF4, + AB8500_PWMGENCONFX_PWMVIBXDUTCYC, + AB8500_PWMGENCONFX_PWMVIBXDUTCYC_MAX, 0), + SOC_SINGLE("Vibra 1 and 2 Bridge Switch", + AB8500_CLASSDCONF1, AB8500_CLASSDCONF1_PARLVIB, + 1, 0), + SOC_DOUBLE_R_TLV("Vibra Master Volume", + AB8500_DADIGGAIN5, AB8500_DADIGGAIN6, + 0, AB8500_DADIGGAINX_DAXGAIN_MAX, 1, dax_dig_gain_tlv), + + /* HandsFree, Vibra */ + SOC_SINGLE("ClassD High Pass Volume", + AB8500_CLASSDCONF3, AB8500_CLASSDCONF3_DITHHPGAIN, + AB8500_CLASSDCONF3_DITHHPGAIN_MAX, 0), + SOC_SINGLE("ClassD White Volume", + AB8500_CLASSDCONF3, AB8500_CLASSDCONF3_DITHWGAIN, + AB8500_CLASSDCONF3_DITHWGAIN_MAX, 0), + + /* Mic 1, Mic 2, LineIn */ + SOC_DOUBLE_R_TLV("Mic Master Volume", + AB8500_ADDIGGAIN3, AB8500_ADDIGGAIN4, + 0, AB8500_ADDIGGAINX_ADXGAIN_MAX, 1, adx_dig_gain_tlv), + + /* Mic 1 */ + SOC_SINGLE_TLV("Mic 1", + AB8500_ANAGAIN1, + AB8500_ANAGAINX_MICXGAIN, + AB8500_ANAGAINX_MICXGAIN_MAX, 0, mic_gain_tlv), + SOC_SINGLE("Mic 1 Low Power Switch", + AB8500_ANAGAIN1, AB8500_ANAGAINX_LOWPOWMICX, + 1, 0), + + /* Mic 2 */ + SOC_DOUBLE("Mic High Pass Switch", + AB8500_ADFILTCONF, + AB8500_ADFILTCONF_AD3NH, AB8500_ADFILTCONF_AD4NH, + 1, 1), + SOC_ENUM("Mic Mode", soc_enum_ad34voice), + SOC_ENUM("Mic Filter", soc_enum_dmic34sinc), + SOC_SINGLE_TLV("Mic 2", + AB8500_ANAGAIN2, + AB8500_ANAGAINX_MICXGAIN, + AB8500_ANAGAINX_MICXGAIN_MAX, 0, mic_gain_tlv), + SOC_SINGLE("Mic 2 Low Power Switch", + AB8500_ANAGAIN2, AB8500_ANAGAINX_LOWPOWMICX, + 1, 0), + + /* LineIn */ + SOC_DOUBLE("LineIn High Pass Switch", + AB8500_ADFILTCONF, + AB8500_ADFILTCONF_AD1NH, AB8500_ADFILTCONF_AD2NH, + 1, 1), + SOC_ENUM("LineIn Filter", soc_enum_dmic12sinc), + SOC_ENUM("LineIn Mode", soc_enum_ad12voice), + SOC_DOUBLE_R_TLV("LineIn Master Volume", + AB8500_ADDIGGAIN1, AB8500_ADDIGGAIN2, + 0, AB8500_ADDIGGAINX_ADXGAIN_MAX, 1, adx_dig_gain_tlv), + SOC_DOUBLE_TLV("LineIn", + AB8500_ANAGAIN4, + AB8500_ANAGAIN4_LINLGAIN, AB8500_ANAGAIN4_LINRGAIN, + AB8500_ANAGAIN4_LINXGAIN_MAX, 0, lin_gain_tlv), + SOC_DOUBLE_R_TLV("LineIn to Headset Volume", + AB8500_DIGLINHSLGAIN, AB8500_DIGLINHSRGAIN, + AB8500_DIGLINHSXGAIN_LINTOHSXGAIN, + AB8500_DIGLINHSXGAIN_LINTOHSXGAIN_MAX, + 1, lin2hs_gain_tlv), + + /* DMic */ + SOC_ENUM("DMic Filter", soc_enum_dmic56sinc), + SOC_DOUBLE_R_TLV("DMic Master Volume", + AB8500_ADDIGGAIN5, AB8500_ADDIGGAIN6, + 0, AB8500_ADDIGGAINX_ADXGAIN_MAX, 1, adx_dig_gain_tlv), + + /* Digital gains */ + SOC_ENUM("Digital Gain Fade Speed", soc_enum_fadespeed), + + /* Analog loopback */ + SOC_DOUBLE_R_TLV("Analog Loopback Volume", + AB8500_ADDIGLOOPGAIN1, AB8500_ADDIGLOOPGAIN2, + 0, AB8500_ADDIGLOOPGAINX_ADXLBGAIN_MAX, 1, dax_dig_gain_tlv), + + /* Digital interface - DA from slot mapping */ + SOC_ENUM("Digital Interface DA 1 From Slot Map", soc_enum_da1slotmap), + SOC_ENUM("Digital Interface DA 2 From Slot Map", soc_enum_da2slotmap), + SOC_ENUM("Digital Interface DA 3 From Slot Map", soc_enum_da3slotmap), + SOC_ENUM("Digital Interface DA 4 From Slot Map", soc_enum_da4slotmap), + SOC_ENUM("Digital Interface DA 5 From Slot Map", soc_enum_da5slotmap), + SOC_ENUM("Digital Interface DA 6 From Slot Map", soc_enum_da6slotmap), + SOC_ENUM("Digital Interface DA 7 From Slot Map", soc_enum_da7slotmap), + SOC_ENUM("Digital Interface DA 8 From Slot Map", soc_enum_da8slotmap), + + /* Digital interface - AD to slot mapping */ + SOC_ENUM("Digital Interface AD To Slot 0 Map", soc_enum_adslot0map), + SOC_ENUM("Digital Interface AD To Slot 1 Map", soc_enum_adslot1map), + SOC_ENUM("Digital Interface AD To Slot 2 Map", soc_enum_adslot2map), + SOC_ENUM("Digital Interface AD To Slot 3 Map", soc_enum_adslot3map), + SOC_ENUM("Digital Interface AD To Slot 4 Map", soc_enum_adslot4map), + SOC_ENUM("Digital Interface AD To Slot 5 Map", soc_enum_adslot5map), + SOC_ENUM("Digital Interface AD To Slot 6 Map", soc_enum_adslot6map), + SOC_ENUM("Digital Interface AD To Slot 7 Map", soc_enum_adslot7map), + SOC_ENUM("Digital Interface AD To Slot 8 Map", soc_enum_adslot8map), + SOC_ENUM("Digital Interface AD To Slot 9 Map", soc_enum_adslot9map), + SOC_ENUM("Digital Interface AD To Slot 10 Map", soc_enum_adslot10map), + SOC_ENUM("Digital Interface AD To Slot 11 Map", soc_enum_adslot11map), + SOC_ENUM("Digital Interface AD To Slot 12 Map", soc_enum_adslot12map), + SOC_ENUM("Digital Interface AD To Slot 13 Map", soc_enum_adslot13map), + SOC_ENUM("Digital Interface AD To Slot 14 Map", soc_enum_adslot14map), + SOC_ENUM("Digital Interface AD To Slot 15 Map", soc_enum_adslot15map), + SOC_ENUM("Digital Interface AD To Slot 16 Map", soc_enum_adslot16map), + SOC_ENUM("Digital Interface AD To Slot 17 Map", soc_enum_adslot17map), + SOC_ENUM("Digital Interface AD To Slot 18 Map", soc_enum_adslot18map), + SOC_ENUM("Digital Interface AD To Slot 19 Map", soc_enum_adslot19map), + SOC_ENUM("Digital Interface AD To Slot 20 Map", soc_enum_adslot20map), + SOC_ENUM("Digital Interface AD To Slot 21 Map", soc_enum_adslot21map), + SOC_ENUM("Digital Interface AD To Slot 22 Map", soc_enum_adslot22map), + SOC_ENUM("Digital Interface AD To Slot 23 Map", soc_enum_adslot23map), + SOC_ENUM("Digital Interface AD To Slot 24 Map", soc_enum_adslot24map), + SOC_ENUM("Digital Interface AD To Slot 25 Map", soc_enum_adslot25map), + SOC_ENUM("Digital Interface AD To Slot 26 Map", soc_enum_adslot26map), + SOC_ENUM("Digital Interface AD To Slot 27 Map", soc_enum_adslot27map), + SOC_ENUM("Digital Interface AD To Slot 28 Map", soc_enum_adslot28map), + SOC_ENUM("Digital Interface AD To Slot 29 Map", soc_enum_adslot29map), + SOC_ENUM("Digital Interface AD To Slot 30 Map", soc_enum_adslot30map), + SOC_ENUM("Digital Interface AD To Slot 31 Map", soc_enum_adslot31map), + + /* Digital interface - Loopback */ + SOC_SINGLE("Digital Interface AD 1 Loopback Switch", + AB8500_DASLOTCONF1, AB8500_DASLOTCONF1_DAI7TOADO1, + 1, 0), + SOC_SINGLE("Digital Interface AD 2 Loopback Switch", + AB8500_DASLOTCONF2, AB8500_DASLOTCONF2_DAI8TOADO2, + 1, 0), + SOC_SINGLE("Digital Interface AD 3 Loopback Switch", + AB8500_DASLOTCONF3, AB8500_DASLOTCONF3_DAI7TOADO3, + 1, 0), + SOC_SINGLE("Digital Interface AD 4 Loopback Switch", + AB8500_DASLOTCONF4, AB8500_DASLOTCONF4_DAI8TOADO4, + 1, 0), + SOC_SINGLE("Digital Interface AD 5 Loopback Switch", + AB8500_DASLOTCONF5, AB8500_DASLOTCONF5_DAI7TOADO5, + 1, 0), + SOC_SINGLE("Digital Interface AD 6 Loopback Switch", + AB8500_DASLOTCONF6, AB8500_DASLOTCONF6_DAI8TOADO6, + 1, 0), + SOC_SINGLE("Digital Interface AD 7 Loopback Switch", + AB8500_DASLOTCONF7, AB8500_DASLOTCONF7_DAI8TOADO7, + 1, 0), + SOC_SINGLE("Digital Interface AD 8 Loopback Switch", + AB8500_DASLOTCONF8, AB8500_DASLOTCONF8_DAI7TOADO8, + 1, 0), + + /* Digital interface - Burst FIFO */ + SOC_SINGLE("Digital Interface 0 FIFO Enable Switch", + AB8500_DIGIFCONF3, AB8500_DIGIFCONF3_IF0BFIFOEN, + 1, 0), + SOC_ENUM("Burst FIFO Mask", soc_enum_bfifomask), + SOC_ENUM("Burst FIFO Bit-clock Frequency", soc_enum_bfifo19m2), + SOC_SINGLE("Burst FIFO Threshold", + AB8500_FIFOCONF1, AB8500_FIFOCONF1_BFIFOINT_SHIFT, + AB8500_FIFOCONF1_BFIFOINT_MAX, 0), + SOC_SINGLE("Burst FIFO Length", + AB8500_FIFOCONF2, AB8500_FIFOCONF2_BFIFOTX_SHIFT, + AB8500_FIFOCONF2_BFIFOTX_MAX, 0), + SOC_SINGLE("Burst FIFO EOS Extra Slots", + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOEXSL_SHIFT, + AB8500_FIFOCONF3_BFIFOEXSL_MAX, 0), + SOC_SINGLE("Burst FIFO FS Extra Bit-clocks", + AB8500_FIFOCONF3, AB8500_FIFOCONF3_PREBITCLK0_SHIFT, + AB8500_FIFOCONF3_PREBITCLK0_MAX, 0), + SOC_ENUM("Burst FIFO Interface Mode", soc_enum_bfifomast), + + SOC_SINGLE("Burst FIFO Interface Switch", + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFORUN_SHIFT, + 1, 0), + SOC_SINGLE("Burst FIFO Switch Frame Number", + AB8500_FIFOCONF4, AB8500_FIFOCONF4_BFIFOFRAMSW_SHIFT, + AB8500_FIFOCONF4_BFIFOFRAMSW_MAX, 0), + SOC_SINGLE("Burst FIFO Wake Up Delay", + AB8500_FIFOCONF5, AB8500_FIFOCONF5_BFIFOWAKEUP_SHIFT, + AB8500_FIFOCONF5_BFIFOWAKEUP_MAX, 0), + SOC_SINGLE("Burst FIFO Samples In FIFO", + AB8500_FIFOCONF6, AB8500_FIFOCONF6_BFIFOSAMPLE_SHIFT, + AB8500_FIFOCONF6_BFIFOSAMPLE_MAX, 0), + + /* ANC */ + SOC_ENUM_EXT("ANC Status", soc_enum_ancstate, + anc_status_control_get, anc_status_control_put), + SOC_SINGLE_XR_SX("ANC Warp Delay Shift", + AB8500_ANCCONF2, 1, AB8500_ANCCONF2_SHIFT, + AB8500_ANCCONF2_MIN, AB8500_ANCCONF2_MAX, 0), + SOC_SINGLE_XR_SX("ANC FIR Output Shift", + AB8500_ANCCONF3, 1, AB8500_ANCCONF3_SHIFT, + AB8500_ANCCONF3_MIN, AB8500_ANCCONF3_MAX, 0), + SOC_SINGLE_XR_SX("ANC IIR Output Shift", + AB8500_ANCCONF4, 1, AB8500_ANCCONF4_SHIFT, + AB8500_ANCCONF4_MIN, AB8500_ANCCONF4_MAX, 0), + SOC_SINGLE_XR_SX("ANC Warp Delay", + AB8500_ANCCONF9, 2, AB8500_ANC_WARP_DELAY_SHIFT, + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ + SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, + sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), +}; + +static struct snd_kcontrol_new ab8500_filter_controls[] = { + AB8500_FILTER_CONTROL("ANC FIR Coefficients", AB8500_ANC_FIR_COEFFS, + AB8500_ANC_FIR_COEFF_MIN, AB8500_ANC_FIR_COEFF_MAX), + AB8500_FILTER_CONTROL("ANC IIR Coefficients", AB8500_ANC_IIR_COEFFS, + AB8500_ANC_IIR_COEFF_MIN, AB8500_ANC_IIR_COEFF_MAX), + AB8500_FILTER_CONTROL("Sidetone FIR Coefficients", + AB8500_SID_FIR_COEFFS, AB8500_SID_FIR_COEFF_MIN, + AB8500_SID_FIR_COEFF_MAX) +}; +enum ab8500_filter { + AB8500_FILTER_ANC_FIR = 0, + AB8500_FILTER_ANC_IIR = 1, + AB8500_FILTER_SID_FIR = 2, +}; + +/* + * Extended interface for codec-driver + */ + +static int ab8500_audio_init_audioblock(struct snd_soc_component *component) +{ + int status; + + dev_dbg(component->dev, "%s: Enter.\n", __func__); + + /* Reset audio-registers and disable 32kHz-clock output 2 */ + status = ab8500_sysctrl_write(AB8500_STW4500CTRL3, + AB8500_STW4500CTRL3_CLK32KOUT2DIS | + AB8500_STW4500CTRL3_RESETAUDN, + AB8500_STW4500CTRL3_RESETAUDN); + if (status < 0) + return status; + + return 0; +} + +static int ab8500_audio_setup_mics(struct snd_soc_component *component, + struct amic_settings *amics) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + u8 value8; + unsigned int value; + int status; + const struct snd_soc_dapm_route *route; + + dev_dbg(component->dev, "%s: Enter.\n", __func__); + + /* Set DMic-clocks to outputs */ + status = abx500_get_register_interruptible(component->dev, AB8500_MISC, + AB8500_GPIO_DIR4_REG, + &value8); + if (status < 0) + return status; + value = value8 | GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | + GPIO31_DIR_OUTPUT; + status = abx500_set_register_interruptible(component->dev, + AB8500_MISC, + AB8500_GPIO_DIR4_REG, + value); + if (status < 0) + return status; + + /* Attach regulators to AMic DAPM-paths */ + dev_dbg(component->dev, "%s: Mic 1a regulator: %s\n", __func__, + amic_micbias_str(amics->mic1a_micbias)); + route = &ab8500_dapm_routes_mic1a_vamicx[amics->mic1a_micbias]; + status = snd_soc_dapm_add_routes(dapm, route, 1); + dev_dbg(component->dev, "%s: Mic 1b regulator: %s\n", __func__, + amic_micbias_str(amics->mic1b_micbias)); + route = &ab8500_dapm_routes_mic1b_vamicx[amics->mic1b_micbias]; + status |= snd_soc_dapm_add_routes(dapm, route, 1); + dev_dbg(component->dev, "%s: Mic 2 regulator: %s\n", __func__, + amic_micbias_str(amics->mic2_micbias)); + route = &ab8500_dapm_routes_mic2_vamicx[amics->mic2_micbias]; + status |= snd_soc_dapm_add_routes(dapm, route, 1); + if (status < 0) { + dev_err(component->dev, + "%s: Failed to add AMic-regulator DAPM-routes (%d).\n", + __func__, status); + return status; + } + + /* Set AMic-configuration */ + dev_dbg(component->dev, "%s: Mic 1 mic-type: %s\n", __func__, + amic_type_str(amics->mic1_type)); + snd_soc_component_update_bits(component, AB8500_ANAGAIN1, AB8500_ANAGAINX_ENSEMICX, + amics->mic1_type == AMIC_TYPE_DIFFERENTIAL ? + 0 : AB8500_ANAGAINX_ENSEMICX); + dev_dbg(component->dev, "%s: Mic 2 mic-type: %s\n", __func__, + amic_type_str(amics->mic2_type)); + snd_soc_component_update_bits(component, AB8500_ANAGAIN2, AB8500_ANAGAINX_ENSEMICX, + amics->mic2_type == AMIC_TYPE_DIFFERENTIAL ? + 0 : AB8500_ANAGAINX_ENSEMICX); + + return 0; +} + +static int ab8500_audio_set_ear_cmv(struct snd_soc_component *component, + enum ear_cm_voltage ear_cmv) +{ + char *cmv_str; + + switch (ear_cmv) { + case EAR_CMV_0_95V: + cmv_str = "0.95V"; + break; + case EAR_CMV_1_10V: + cmv_str = "1.10V"; + break; + case EAR_CMV_1_27V: + cmv_str = "1.27V"; + break; + case EAR_CMV_1_58V: + cmv_str = "1.58V"; + break; + default: + dev_err(component->dev, + "%s: Unknown earpiece CM-voltage (%d)!\n", + __func__, (int)ear_cmv); + return -EINVAL; + } + dev_dbg(component->dev, "%s: Earpiece CM-voltage: %s\n", __func__, + cmv_str); + snd_soc_component_update_bits(component, AB8500_ANACONF1, AB8500_ANACONF1_EARSELCM, + ear_cmv); + + return 0; +} + +static int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, + unsigned int delay) +{ + unsigned int mask, val; + struct snd_soc_component *component = dai->component; + + mask = BIT(AB8500_DIGIFCONF2_IF0DEL); + val = 0; + + switch (delay) { + case 0: + break; + case 1: + val |= BIT(AB8500_DIGIFCONF2_IF0DEL); + break; + default: + dev_err(dai->component->dev, + "%s: ERROR: Unsupported bit-delay (0x%x)!\n", + __func__, delay); + return -EINVAL; + } + + dev_dbg(dai->component->dev, "%s: IF0 Bit-delay: %d bits.\n", + __func__, delay); + snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); + + return 0; +} + +/* Gates clocking according format mask */ +static int ab8500_codec_set_dai_clock_gate(struct snd_soc_component *component, + unsigned int fmt) +{ + unsigned int mask; + unsigned int val; + + mask = BIT(AB8500_DIGIFCONF1_ENMASTGEN) | + BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); + + val = BIT(AB8500_DIGIFCONF1_ENMASTGEN); + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: /* continuous clock */ + dev_dbg(component->dev, "%s: IF0 Clock is continuous.\n", + __func__); + val |= BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); + break; + case SND_SOC_DAIFMT_GATED: /* clock is gated */ + dev_dbg(component->dev, "%s: IF0 Clock is gated.\n", + __func__); + break; + default: + dev_err(component->dev, + "%s: ERROR: Unsupported clock mask (0x%x)!\n", + __func__, fmt & SND_SOC_DAIFMT_CLOCK_MASK); + return -EINVAL; + } + + snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val); + + return 0; +} + +static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + unsigned int mask; + unsigned int val; + struct snd_soc_component *component = dai->component; + int status; + + dev_dbg(component->dev, "%s: Enter (fmt = 0x%x)\n", __func__, fmt); + + mask = BIT(AB8500_DIGIFCONF3_IF1DATOIF0AD) | + BIT(AB8500_DIGIFCONF3_IF1CLKTOIF0CLK) | + BIT(AB8500_DIGIFCONF3_IF0BFIFOEN) | + BIT(AB8500_DIGIFCONF3_IF0MASTER); + val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & FRM master */ + dev_dbg(dai->component->dev, + "%s: IF0 Master-mode: AB8500 master.\n", __func__); + val |= BIT(AB8500_DIGIFCONF3_IF0MASTER); + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & FRM slave */ + dev_dbg(dai->component->dev, + "%s: IF0 Master-mode: AB8500 slave.\n", __func__); + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & FRM master */ + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + dev_err(dai->component->dev, + "%s: ERROR: The device is either a master or a slave.\n", + __func__); + default: + dev_err(dai->component->dev, + "%s: ERROR: Unsupporter master mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + snd_soc_component_update_bits(component, AB8500_DIGIFCONF3, mask, val); + + /* Set clock gating */ + status = ab8500_codec_set_dai_clock_gate(component, fmt); + if (status) { + dev_err(dai->component->dev, + "%s: ERROR: Failed to set clock gate (%d).\n", + __func__, status); + return status; + } + + /* Setting data transfer format */ + + mask = BIT(AB8500_DIGIFCONF2_IF0FORMAT0) | + BIT(AB8500_DIGIFCONF2_IF0FORMAT1) | + BIT(AB8500_DIGIFCONF2_FSYNC0P) | + BIT(AB8500_DIGIFCONF2_BITCLK0P); + val = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: /* I2S mode */ + dev_dbg(dai->component->dev, "%s: IF0 Protocol: I2S\n", __func__); + val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT1); + ab8500_audio_set_bit_delay(dai, 0); + break; + + case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */ + dev_dbg(dai->component->dev, + "%s: IF0 Protocol: DSP A (TDM)\n", __func__); + val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); + ab8500_audio_set_bit_delay(dai, 1); + break; + + case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */ + dev_dbg(dai->component->dev, + "%s: IF0 Protocol: DSP B (TDM)\n", __func__); + val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); + ab8500_audio_set_bit_delay(dai, 0); + break; + + default: + dev_err(dai->component->dev, + "%s: ERROR: Unsupported format (0x%x)!\n", + __func__, fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ + dev_dbg(dai->component->dev, + "%s: IF0: Normal bit clock, normal frame\n", + __func__); + break; + case SND_SOC_DAIFMT_NB_IF: /* normal BCLK + inv FRM */ + dev_dbg(dai->component->dev, + "%s: IF0: Normal bit clock, inverted frame\n", + __func__); + val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); + break; + case SND_SOC_DAIFMT_IB_NF: /* invert BCLK + nor FRM */ + dev_dbg(dai->component->dev, + "%s: IF0: Inverted bit clock, normal frame\n", + __func__); + val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); + break; + case SND_SOC_DAIFMT_IB_IF: /* invert BCLK + FRM */ + dev_dbg(dai->component->dev, + "%s: IF0: Inverted bit clock, inverted frame\n", + __func__); + val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); + val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); + break; + default: + dev_err(dai->component->dev, + "%s: ERROR: Unsupported INV mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + + snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); + + return 0; +} + +static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val, mask, slot, slots_active; + + mask = BIT(AB8500_DIGIFCONF2_IF0WL0) | + BIT(AB8500_DIGIFCONF2_IF0WL1); + val = 0; + + switch (slot_width) { + case 16: + break; + case 20: + val |= BIT(AB8500_DIGIFCONF2_IF0WL0); + break; + case 24: + val |= BIT(AB8500_DIGIFCONF2_IF0WL1); + break; + case 32: + val |= BIT(AB8500_DIGIFCONF2_IF0WL1) | + BIT(AB8500_DIGIFCONF2_IF0WL0); + break; + default: + dev_err(dai->component->dev, "%s: Unsupported slot-width 0x%x\n", + __func__, slot_width); + return -EINVAL; + } + + dev_dbg(dai->component->dev, "%s: IF0 slot-width: %d bits.\n", + __func__, slot_width); + snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); + + /* Setup TDM clocking according to slot count */ + dev_dbg(dai->component->dev, "%s: Slots, total: %d\n", __func__, slots); + mask = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | + BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); + switch (slots) { + case 2: + val = AB8500_MASK_NONE; + break; + case 4: + val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0); + break; + case 8: + val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); + break; + case 16: + val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | + BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); + break; + default: + dev_err(dai->component->dev, + "%s: ERROR: Unsupported number of slots (%d)!\n", + __func__, slots); + return -EINVAL; + } + snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val); + + /* Setup TDM DA according to active tx slots */ + + if (tx_mask & ~0xff) + return -EINVAL; + + mask = AB8500_DASLOTCONFX_SLTODAX_MASK; + tx_mask = tx_mask << AB8500_DA_DATA0_OFFSET; + slots_active = hweight32(tx_mask); + + dev_dbg(dai->component->dev, "%s: Slots, active, TX: %d\n", __func__, + slots_active); + + switch (slots_active) { + case 0: + break; + case 1: + slot = ffs(tx_mask); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot); + break; + case 2: + slot = ffs(tx_mask); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot); + slot = fls(tx_mask); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot); + snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot); + break; + case 8: + dev_dbg(dai->component->dev, + "%s: In 8-channel mode DA-from-slot mapping is set manually.", + __func__); + break; + default: + dev_err(dai->component->dev, + "%s: Unsupported number of active TX-slots (%d)!\n", + __func__, slots_active); + return -EINVAL; + } + + /* Setup TDM AD according to active RX-slots */ + + if (rx_mask & ~0xff) + return -EINVAL; + + rx_mask = rx_mask << AB8500_AD_DATA0_OFFSET; + slots_active = hweight32(rx_mask); + + dev_dbg(dai->component->dev, "%s: Slots, active, RX: %d\n", __func__, + slots_active); + + switch (slots_active) { + case 0: + break; + case 1: + slot = ffs(rx_mask); + snd_soc_component_update_bits(component, AB8500_ADSLOTSEL(slot), + AB8500_MASK_SLOT(slot), + AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot)); + break; + case 2: + slot = ffs(rx_mask); + snd_soc_component_update_bits(component, + AB8500_ADSLOTSEL(slot), + AB8500_MASK_SLOT(slot), + AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot)); + slot = fls(rx_mask); + snd_soc_component_update_bits(component, + AB8500_ADSLOTSEL(slot), + AB8500_MASK_SLOT(slot), + AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT2, slot)); + break; + case 8: + dev_dbg(dai->component->dev, + "%s: In 8-channel mode AD-to-slot mapping is set manually.", + __func__); + break; + default: + dev_err(dai->component->dev, + "%s: Unsupported number of active RX-slots (%d)!\n", + __func__, slots_active); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops ab8500_codec_ops = { + .set_fmt = ab8500_codec_set_dai_fmt, + .set_tdm_slot = ab8500_codec_set_dai_tdm_slot, +}; + +static struct snd_soc_dai_driver ab8500_codec_dai[] = { + { + .name = "ab8500-codec-dai.0", + .id = 0, + .playback = { + .stream_name = "ab8500_0p", + .channels_min = 1, + .channels_max = 8, + .rates = AB8500_SUPPORTED_RATE, + .formats = AB8500_SUPPORTED_FMT, + }, + .ops = &ab8500_codec_ops, + .symmetric_rates = 1 + }, + { + .name = "ab8500-codec-dai.1", + .id = 1, + .capture = { + .stream_name = "ab8500_0c", + .channels_min = 1, + .channels_max = 8, + .rates = AB8500_SUPPORTED_RATE, + .formats = AB8500_SUPPORTED_FMT, + }, + .ops = &ab8500_codec_ops, + .symmetric_rates = 1 + } +}; + +static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + struct ab8500_codec_platform_data *codec) +{ + u32 value; + + if (of_property_read_bool(np, "stericsson,amic1-type-single-ended")) + codec->amics.mic1_type = AMIC_TYPE_SINGLE_ENDED; + else + codec->amics.mic1_type = AMIC_TYPE_DIFFERENTIAL; + + if (of_property_read_bool(np, "stericsson,amic2-type-single-ended")) + codec->amics.mic2_type = AMIC_TYPE_SINGLE_ENDED; + else + codec->amics.mic2_type = AMIC_TYPE_DIFFERENTIAL; + + /* Has a non-standard Vamic been requested? */ + if (of_property_read_bool(np, "stericsson,amic1a-bias-vamic2")) + codec->amics.mic1a_micbias = AMIC_MICBIAS_VAMIC2; + else + codec->amics.mic1a_micbias = AMIC_MICBIAS_VAMIC1; + + if (of_property_read_bool(np, "stericsson,amic1b-bias-vamic2")) + codec->amics.mic1b_micbias = AMIC_MICBIAS_VAMIC2; + else + codec->amics.mic1b_micbias = AMIC_MICBIAS_VAMIC1; + + if (of_property_read_bool(np, "stericsson,amic2-bias-vamic1")) + codec->amics.mic2_micbias = AMIC_MICBIAS_VAMIC1; + else + codec->amics.mic2_micbias = AMIC_MICBIAS_VAMIC2; + + if (!of_property_read_u32(np, "stericsson,earpeice-cmv", &value)) { + switch (value) { + case 950 : + codec->ear_cmv = EAR_CMV_0_95V; + break; + case 1100 : + codec->ear_cmv = EAR_CMV_1_10V; + break; + case 1270 : + codec->ear_cmv = EAR_CMV_1_27V; + break; + case 1580 : + codec->ear_cmv = EAR_CMV_1_58V; + break; + default : + codec->ear_cmv = EAR_CMV_UNKNOWN; + dev_err(dev, "Unsuitable earpiece voltage found in DT\n"); + } + } else { + dev_warn(dev, "No earpiece voltage found in DT - using default\n"); + codec->ear_cmv = EAR_CMV_0_95V; + } +} + +static int ab8500_codec_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; + struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + struct filter_control *fc; + int status; + + dev_dbg(dev, "%s: Enter.\n", __func__); + + ab8500_codec_of_probe(dev, np, &codec_pdata); + + status = ab8500_audio_setup_mics(component, &codec_pdata.amics); + if (status < 0) { + pr_err("%s: Failed to setup mics (%d)!\n", __func__, status); + return status; + } + status = ab8500_audio_set_ear_cmv(component, codec_pdata.ear_cmv); + if (status < 0) { + pr_err("%s: Failed to set earpiece CM-voltage (%d)!\n", + __func__, status); + return status; + } + + status = ab8500_audio_init_audioblock(component); + if (status < 0) { + dev_err(dev, "%s: failed to init audio-block (%d)!\n", + __func__, status); + return status; + } + + /* Override HW-defaults */ + snd_soc_component_write(component, AB8500_ANACONF5, + BIT(AB8500_ANACONF5_HSAUTOEN)); + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + + /* Add filter controls */ + status = snd_soc_add_component_controls(component, ab8500_filter_controls, + ARRAY_SIZE(ab8500_filter_controls)); + if (status < 0) { + dev_err(dev, + "%s: failed to add ab8500 filter controls (%d).\n", + __func__, status); + return status; + } + fc = (struct filter_control *) + &ab8500_filter_controls[AB8500_FILTER_ANC_FIR].private_value; + drvdata->anc_fir_values = (long *)fc->value; + fc = (struct filter_control *) + &ab8500_filter_controls[AB8500_FILTER_ANC_IIR].private_value; + drvdata->anc_iir_values = (long *)fc->value; + fc = (struct filter_control *) + &ab8500_filter_controls[AB8500_FILTER_SID_FIR].private_value; + drvdata->sid_fir_values = (long *)fc->value; + + snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); + + mutex_init(&drvdata->ctrl_lock); + + return status; +} + +static const struct snd_soc_component_driver ab8500_component_driver = { + .probe = ab8500_codec_probe, + .controls = ab8500_ctrls, + .num_controls = ARRAY_SIZE(ab8500_ctrls), + .dapm_widgets = ab8500_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ab8500_dapm_widgets), + .dapm_routes = ab8500_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ab8500_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ab8500_codec_driver_probe(struct platform_device *pdev) +{ + int status; + struct ab8500_codec_drvdata *drvdata; + + dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); + + /* Create driver private-data struct */ + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct ab8500_codec_drvdata), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + drvdata->sid_status = SID_UNCONFIGURED; + drvdata->anc_status = ANC_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, + &ab8500_codec_regmap); + if (IS_ERR(drvdata->regmap)) { + status = PTR_ERR(drvdata->regmap); + dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", + __func__, status); + return status; + } + + dev_dbg(&pdev->dev, "%s: Register codec.\n", __func__); + status = devm_snd_soc_register_component(&pdev->dev, + &ab8500_component_driver, + ab8500_codec_dai, + ARRAY_SIZE(ab8500_codec_dai)); + if (status < 0) + dev_err(&pdev->dev, + "%s: Error: Failed to register codec (%d).\n", + __func__, status); + + return status; +} + +static struct platform_driver ab8500_codec_platform_driver = { + .driver = { + .name = "ab8500-codec", + }, + .probe = ab8500_codec_driver_probe, +}; +module_platform_driver(ab8500_codec_platform_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ab8500-codec.h b/sound/soc/codecs/ab8500-codec.h new file mode 100644 index 000000000..e2e54425d --- /dev/null +++ b/sound/soc/codecs/ab8500-codec.h @@ -0,0 +1,592 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Kristoffer Karlsson , + * Roger Nilsson , + * for ST-Ericsson. + * + * Based on the early work done by: + * Mikko J. Lehto , + * Mikko Sarmanne , + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef AB8500_CODEC_REGISTERS_H +#define AB8500_CODEC_REGISTERS_H + +#define AB8500_SUPPORTED_RATE (SNDRV_PCM_RATE_48000) +#define AB8500_SUPPORTED_FMT (SNDRV_PCM_FMTBIT_S16_LE) + +/* AB8500 interface slot offset definitions */ + +#define AB8500_AD_DATA0_OFFSET 0 +#define AB8500_DA_DATA0_OFFSET 8 +#define AB8500_AD_DATA1_OFFSET 16 +#define AB8500_DA_DATA1_OFFSET 24 + +/* AB8500 audio bank (0x0d) register definitions */ + +#define AB8500_POWERUP 0x00 +#define AB8500_AUDSWRESET 0x01 +#define AB8500_ADPATHENA 0x02 +#define AB8500_DAPATHENA 0x03 +#define AB8500_ANACONF1 0x04 +#define AB8500_ANACONF2 0x05 +#define AB8500_DIGMICCONF 0x06 +#define AB8500_ANACONF3 0x07 +#define AB8500_ANACONF4 0x08 +#define AB8500_DAPATHCONF 0x09 +#define AB8500_MUTECONF 0x0A +#define AB8500_SHORTCIRCONF 0x0B +#define AB8500_ANACONF5 0x0C +#define AB8500_ENVCPCONF 0x0D +#define AB8500_SIGENVCONF 0x0E +#define AB8500_PWMGENCONF1 0x0F +#define AB8500_PWMGENCONF2 0x10 +#define AB8500_PWMGENCONF3 0x11 +#define AB8500_PWMGENCONF4 0x12 +#define AB8500_PWMGENCONF5 0x13 +#define AB8500_ANAGAIN1 0x14 +#define AB8500_ANAGAIN2 0x15 +#define AB8500_ANAGAIN3 0x16 +#define AB8500_ANAGAIN4 0x17 +#define AB8500_DIGLINHSLGAIN 0x18 +#define AB8500_DIGLINHSRGAIN 0x19 +#define AB8500_ADFILTCONF 0x1A +#define AB8500_DIGIFCONF1 0x1B +#define AB8500_DIGIFCONF2 0x1C +#define AB8500_DIGIFCONF3 0x1D +#define AB8500_DIGIFCONF4 0x1E +#define AB8500_ADSLOTSEL1 0x1F +#define AB8500_ADSLOTSEL2 0x20 +#define AB8500_ADSLOTSEL3 0x21 +#define AB8500_ADSLOTSEL4 0x22 +#define AB8500_ADSLOTSEL5 0x23 +#define AB8500_ADSLOTSEL6 0x24 +#define AB8500_ADSLOTSEL7 0x25 +#define AB8500_ADSLOTSEL8 0x26 +#define AB8500_ADSLOTSEL9 0x27 +#define AB8500_ADSLOTSEL10 0x28 +#define AB8500_ADSLOTSEL11 0x29 +#define AB8500_ADSLOTSEL12 0x2A +#define AB8500_ADSLOTSEL13 0x2B +#define AB8500_ADSLOTSEL14 0x2C +#define AB8500_ADSLOTSEL15 0x2D +#define AB8500_ADSLOTSEL16 0x2E +#define AB8500_ADSLOTSEL(slot) (AB8500_ADSLOTSEL1 + (slot >> 1)) +#define AB8500_ADSLOTHIZCTRL1 0x2F +#define AB8500_ADSLOTHIZCTRL2 0x30 +#define AB8500_ADSLOTHIZCTRL3 0x31 +#define AB8500_ADSLOTHIZCTRL4 0x32 +#define AB8500_DASLOTCONF1 0x33 +#define AB8500_DASLOTCONF2 0x34 +#define AB8500_DASLOTCONF3 0x35 +#define AB8500_DASLOTCONF4 0x36 +#define AB8500_DASLOTCONF5 0x37 +#define AB8500_DASLOTCONF6 0x38 +#define AB8500_DASLOTCONF7 0x39 +#define AB8500_DASLOTCONF8 0x3A +#define AB8500_CLASSDCONF1 0x3B +#define AB8500_CLASSDCONF2 0x3C +#define AB8500_CLASSDCONF3 0x3D +#define AB8500_DMICFILTCONF 0x3E +#define AB8500_DIGMULTCONF1 0x3F +#define AB8500_DIGMULTCONF2 0x40 +#define AB8500_ADDIGGAIN1 0x41 +#define AB8500_ADDIGGAIN2 0x42 +#define AB8500_ADDIGGAIN3 0x43 +#define AB8500_ADDIGGAIN4 0x44 +#define AB8500_ADDIGGAIN5 0x45 +#define AB8500_ADDIGGAIN6 0x46 +#define AB8500_DADIGGAIN1 0x47 +#define AB8500_DADIGGAIN2 0x48 +#define AB8500_DADIGGAIN3 0x49 +#define AB8500_DADIGGAIN4 0x4A +#define AB8500_DADIGGAIN5 0x4B +#define AB8500_DADIGGAIN6 0x4C +#define AB8500_ADDIGLOOPGAIN1 0x4D +#define AB8500_ADDIGLOOPGAIN2 0x4E +#define AB8500_HSLEARDIGGAIN 0x4F +#define AB8500_HSRDIGGAIN 0x50 +#define AB8500_SIDFIRGAIN1 0x51 +#define AB8500_SIDFIRGAIN2 0x52 +#define AB8500_ANCCONF1 0x53 +#define AB8500_ANCCONF2 0x54 +#define AB8500_ANCCONF3 0x55 +#define AB8500_ANCCONF4 0x56 +#define AB8500_ANCCONF5 0x57 +#define AB8500_ANCCONF6 0x58 +#define AB8500_ANCCONF7 0x59 +#define AB8500_ANCCONF8 0x5A +#define AB8500_ANCCONF9 0x5B +#define AB8500_ANCCONF10 0x5C +#define AB8500_ANCCONF11 0x5D +#define AB8500_ANCCONF12 0x5E +#define AB8500_ANCCONF13 0x5F +#define AB8500_ANCCONF14 0x60 +#define AB8500_SIDFIRADR 0x61 +#define AB8500_SIDFIRCOEF1 0x62 +#define AB8500_SIDFIRCOEF2 0x63 +#define AB8500_SIDFIRCONF 0x64 +#define AB8500_AUDINTMASK1 0x65 +#define AB8500_AUDINTSOURCE1 0x66 +#define AB8500_AUDINTMASK2 0x67 +#define AB8500_AUDINTSOURCE2 0x68 +#define AB8500_FIFOCONF1 0x69 +#define AB8500_FIFOCONF2 0x6A +#define AB8500_FIFOCONF3 0x6B +#define AB8500_FIFOCONF4 0x6C +#define AB8500_FIFOCONF5 0x6D +#define AB8500_FIFOCONF6 0x6E +#define AB8500_AUDREV 0x6F + +#define AB8500_FIRST_REG AB8500_POWERUP +#define AB8500_LAST_REG AB8500_AUDREV +#define AB8500_CACHEREGNUM (AB8500_LAST_REG + 1) + +#define AB8500_MASK_ALL 0xFF +#define AB8500_MASK_SLOT(slot) ((slot & 1) ? 0xF0 : 0x0F) +#define AB8500_MASK_NONE 0x00 + +/* AB8500_POWERUP */ +#define AB8500_POWERUP_POWERUP 7 +#define AB8500_POWERUP_ENANA 3 + +/* AB8500_AUDSWRESET */ +#define AB8500_AUDSWRESET_SWRESET 7 + +/* AB8500_ADPATHENA */ +#define AB8500_ADPATHENA_ENAD12 7 +#define AB8500_ADPATHENA_ENAD34 5 +#define AB8500_ADPATHENA_ENAD5768 3 + +/* AB8500_DAPATHENA */ +#define AB8500_DAPATHENA_ENDA1 7 +#define AB8500_DAPATHENA_ENDA2 6 +#define AB8500_DAPATHENA_ENDA3 5 +#define AB8500_DAPATHENA_ENDA4 4 +#define AB8500_DAPATHENA_ENDA5 3 +#define AB8500_DAPATHENA_ENDA6 2 + +/* AB8500_ANACONF1 */ +#define AB8500_ANACONF1_HSLOWPOW 7 +#define AB8500_ANACONF1_DACLOWPOW1 6 +#define AB8500_ANACONF1_DACLOWPOW0 5 +#define AB8500_ANACONF1_EARDACLOWPOW 4 +#define AB8500_ANACONF1_EARSELCM 2 +#define AB8500_ANACONF1_HSHPEN 1 +#define AB8500_ANACONF1_EARDRVLOWPOW 0 + +/* AB8500_ANACONF2 */ +#define AB8500_ANACONF2_ENMIC1 7 +#define AB8500_ANACONF2_ENMIC2 6 +#define AB8500_ANACONF2_ENLINL 5 +#define AB8500_ANACONF2_ENLINR 4 +#define AB8500_ANACONF2_MUTMIC1 3 +#define AB8500_ANACONF2_MUTMIC2 2 +#define AB8500_ANACONF2_MUTLINL 1 +#define AB8500_ANACONF2_MUTLINR 0 + +/* AB8500_DIGMICCONF */ +#define AB8500_DIGMICCONF_ENDMIC1 7 +#define AB8500_DIGMICCONF_ENDMIC2 6 +#define AB8500_DIGMICCONF_ENDMIC3 5 +#define AB8500_DIGMICCONF_ENDMIC4 4 +#define AB8500_DIGMICCONF_ENDMIC5 3 +#define AB8500_DIGMICCONF_ENDMIC6 2 +#define AB8500_DIGMICCONF_HSFADSPEED 0 + +/* AB8500_ANACONF3 */ +#define AB8500_ANACONF3_MIC1SEL 7 +#define AB8500_ANACONF3_LINRSEL 6 +#define AB8500_ANACONF3_ENDRVHSL 5 +#define AB8500_ANACONF3_ENDRVHSR 4 +#define AB8500_ANACONF3_ENADCMIC 2 +#define AB8500_ANACONF3_ENADCLINL 1 +#define AB8500_ANACONF3_ENADCLINR 0 + +/* AB8500_ANACONF4 */ +#define AB8500_ANACONF4_DISPDVSS 7 +#define AB8500_ANACONF4_ENEAR 6 +#define AB8500_ANACONF4_ENHSL 5 +#define AB8500_ANACONF4_ENHSR 4 +#define AB8500_ANACONF4_ENHFL 3 +#define AB8500_ANACONF4_ENHFR 2 +#define AB8500_ANACONF4_ENVIB1 1 +#define AB8500_ANACONF4_ENVIB2 0 + +/* AB8500_DAPATHCONF */ +#define AB8500_DAPATHCONF_ENDACEAR 6 +#define AB8500_DAPATHCONF_ENDACHSL 5 +#define AB8500_DAPATHCONF_ENDACHSR 4 +#define AB8500_DAPATHCONF_ENDACHFL 3 +#define AB8500_DAPATHCONF_ENDACHFR 2 +#define AB8500_DAPATHCONF_ENDACVIB1 1 +#define AB8500_DAPATHCONF_ENDACVIB2 0 + +/* AB8500_MUTECONF */ +#define AB8500_MUTECONF_MUTEAR 6 +#define AB8500_MUTECONF_MUTHSL 5 +#define AB8500_MUTECONF_MUTHSR 4 +#define AB8500_MUTECONF_MUTDACEAR 2 +#define AB8500_MUTECONF_MUTDACHSL 1 +#define AB8500_MUTECONF_MUTDACHSR 0 + +/* AB8500_SHORTCIRCONF */ +#define AB8500_SHORTCIRCONF_ENSHORTPWD 7 +#define AB8500_SHORTCIRCONF_EARSHORTDIS 6 +#define AB8500_SHORTCIRCONF_HSSHORTDIS 5 +#define AB8500_SHORTCIRCONF_HSPULLDEN 4 +#define AB8500_SHORTCIRCONF_HSOSCEN 2 +#define AB8500_SHORTCIRCONF_HSFADDIS 1 +#define AB8500_SHORTCIRCONF_HSZCDDIS 0 +/* Zero cross should be disabled */ + +/* AB8500_ANACONF5 */ +#define AB8500_ANACONF5_ENCPHS 7 +#define AB8500_ANACONF5_HSLDACTOLOL 5 +#define AB8500_ANACONF5_HSRDACTOLOR 4 +#define AB8500_ANACONF5_ENLOL 3 +#define AB8500_ANACONF5_ENLOR 2 +#define AB8500_ANACONF5_HSAUTOEN 0 + +/* AB8500_ENVCPCONF */ +#define AB8500_ENVCPCONF_ENVDETHTHRE 4 +#define AB8500_ENVCPCONF_ENVDETLTHRE 0 +#define AB8500_ENVCPCONF_ENVDETHTHRE_MAX 0x0F +#define AB8500_ENVCPCONF_ENVDETLTHRE_MAX 0x0F + +/* AB8500_SIGENVCONF */ +#define AB8500_SIGENVCONF_CPLVEN 5 +#define AB8500_SIGENVCONF_ENVDETCPEN 4 +#define AB8500_SIGENVCONF_ENVDETTIME 0 +#define AB8500_SIGENVCONF_ENVDETTIME_MAX 0x0F + +/* AB8500_PWMGENCONF1 */ +#define AB8500_PWMGENCONF1_PWMTOVIB1 7 +#define AB8500_PWMGENCONF1_PWMTOVIB2 6 +#define AB8500_PWMGENCONF1_PWM1CTRL 5 +#define AB8500_PWMGENCONF1_PWM2CTRL 4 +#define AB8500_PWMGENCONF1_PWM1NCTRL 3 +#define AB8500_PWMGENCONF1_PWM1PCTRL 2 +#define AB8500_PWMGENCONF1_PWM2NCTRL 1 +#define AB8500_PWMGENCONF1_PWM2PCTRL 0 + +/* AB8500_PWMGENCONF2 */ +/* AB8500_PWMGENCONF3 */ +/* AB8500_PWMGENCONF4 */ +/* AB8500_PWMGENCONF5 */ +#define AB8500_PWMGENCONFX_PWMVIBXPOL 7 +#define AB8500_PWMGENCONFX_PWMVIBXDUTCYC 0 +#define AB8500_PWMGENCONFX_PWMVIBXDUTCYC_MAX 0x64 + +/* AB8500_ANAGAIN1 */ +/* AB8500_ANAGAIN2 */ +#define AB8500_ANAGAINX_ENSEMICX 7 +#define AB8500_ANAGAINX_LOWPOWMICX 6 +#define AB8500_ANAGAINX_MICXGAIN 0 +#define AB8500_ANAGAINX_MICXGAIN_MAX 0x1F + +/* AB8500_ANAGAIN3 */ +#define AB8500_ANAGAIN3_HSLGAIN 4 +#define AB8500_ANAGAIN3_HSRGAIN 0 +#define AB8500_ANAGAIN3_HSXGAIN_MAX 0x0F + +/* AB8500_ANAGAIN4 */ +#define AB8500_ANAGAIN4_LINLGAIN 4 +#define AB8500_ANAGAIN4_LINRGAIN 0 +#define AB8500_ANAGAIN4_LINXGAIN_MAX 0x0F + +/* AB8500_DIGLINHSLGAIN */ +/* AB8500_DIGLINHSRGAIN */ +#define AB8500_DIGLINHSXGAIN_LINTOHSXGAIN 0 +#define AB8500_DIGLINHSXGAIN_LINTOHSXGAIN_MAX 0x13 + +/* AB8500_ADFILTCONF */ +#define AB8500_ADFILTCONF_AD1NH 7 +#define AB8500_ADFILTCONF_AD2NH 6 +#define AB8500_ADFILTCONF_AD3NH 5 +#define AB8500_ADFILTCONF_AD4NH 4 +#define AB8500_ADFILTCONF_AD1VOICE 3 +#define AB8500_ADFILTCONF_AD2VOICE 2 +#define AB8500_ADFILTCONF_AD3VOICE 1 +#define AB8500_ADFILTCONF_AD4VOICE 0 + +/* AB8500_DIGIFCONF1 */ +#define AB8500_DIGIFCONF1_ENMASTGEN 7 +#define AB8500_DIGIFCONF1_IF1BITCLKOS1 6 +#define AB8500_DIGIFCONF1_IF1BITCLKOS0 5 +#define AB8500_DIGIFCONF1_ENFSBITCLK1 4 +#define AB8500_DIGIFCONF1_IF0BITCLKOS1 2 +#define AB8500_DIGIFCONF1_IF0BITCLKOS0 1 +#define AB8500_DIGIFCONF1_ENFSBITCLK0 0 + +/* AB8500_DIGIFCONF2 */ +#define AB8500_DIGIFCONF2_FSYNC0P 6 +#define AB8500_DIGIFCONF2_BITCLK0P 5 +#define AB8500_DIGIFCONF2_IF0DEL 4 +#define AB8500_DIGIFCONF2_IF0FORMAT1 3 +#define AB8500_DIGIFCONF2_IF0FORMAT0 2 +#define AB8500_DIGIFCONF2_IF0WL1 1 +#define AB8500_DIGIFCONF2_IF0WL0 0 + +/* AB8500_DIGIFCONF3 */ +#define AB8500_DIGIFCONF3_IF0DATOIF1AD 7 +#define AB8500_DIGIFCONF3_IF0CLKTOIF1CLK 6 +#define AB8500_DIGIFCONF3_IF1MASTER 5 +#define AB8500_DIGIFCONF3_IF1DATOIF0AD 3 +#define AB8500_DIGIFCONF3_IF1CLKTOIF0CLK 2 +#define AB8500_DIGIFCONF3_IF0MASTER 1 +#define AB8500_DIGIFCONF3_IF0BFIFOEN 0 + +/* AB8500_DIGIFCONF4 */ +#define AB8500_DIGIFCONF4_FSYNC1P 6 +#define AB8500_DIGIFCONF4_BITCLK1P 5 +#define AB8500_DIGIFCONF4_IF1DEL 4 +#define AB8500_DIGIFCONF4_IF1FORMAT1 3 +#define AB8500_DIGIFCONF4_IF1FORMAT0 2 +#define AB8500_DIGIFCONF4_IF1WL1 1 +#define AB8500_DIGIFCONF4_IF1WL0 0 + +/* AB8500_ADSLOTSELX */ +#define AB8500_AD_OUT1 0x0 +#define AB8500_AD_OUT2 0x1 +#define AB8500_AD_OUT3 0x2 +#define AB8500_AD_OUT4 0x3 +#define AB8500_AD_OUT5 0x4 +#define AB8500_AD_OUT6 0x5 +#define AB8500_AD_OUT7 0x6 +#define AB8500_AD_OUT8 0x7 +#define AB8500_ZEROES 0x8 +#define AB8500_TRISTATE 0xF +#define AB8500_ADSLOTSELX_EVEN_SHIFT 0 +#define AB8500_ADSLOTSELX_ODD_SHIFT 4 +#define AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(out, slot) \ + ((out) << (((slot) & 1) ? \ + AB8500_ADSLOTSELX_ODD_SHIFT : AB8500_ADSLOTSELX_EVEN_SHIFT)) + +/* AB8500_ADSLOTHIZCTRL1 */ +/* AB8500_ADSLOTHIZCTRL2 */ +/* AB8500_ADSLOTHIZCTRL3 */ +/* AB8500_ADSLOTHIZCTRL4 */ +/* AB8500_DASLOTCONF1 */ +#define AB8500_DASLOTCONF1_DA12VOICE 7 +#define AB8500_DASLOTCONF1_SWAPDA12_34 6 +#define AB8500_DASLOTCONF1_DAI7TOADO1 5 + +/* AB8500_DASLOTCONF2 */ +#define AB8500_DASLOTCONF2_DAI8TOADO2 5 + +/* AB8500_DASLOTCONF3 */ +#define AB8500_DASLOTCONF3_DA34VOICE 7 +#define AB8500_DASLOTCONF3_DAI7TOADO3 5 + +/* AB8500_DASLOTCONF4 */ +#define AB8500_DASLOTCONF4_DAI8TOADO4 5 + +/* AB8500_DASLOTCONF5 */ +#define AB8500_DASLOTCONF5_DA56VOICE 7 +#define AB8500_DASLOTCONF5_DAI7TOADO5 5 + +/* AB8500_DASLOTCONF6 */ +#define AB8500_DASLOTCONF6_DAI8TOADO6 5 + +/* AB8500_DASLOTCONF7 */ +#define AB8500_DASLOTCONF7_DAI8TOADO7 5 + +/* AB8500_DASLOTCONF8 */ +#define AB8500_DASLOTCONF8_DAI7TOADO8 5 + +#define AB8500_DASLOTCONFX_SLTODAX_SHIFT 0 +#define AB8500_DASLOTCONFX_SLTODAX_MASK 0x1F + +/* AB8500_CLASSDCONF1 */ +#define AB8500_CLASSDCONF1_PARLHF 7 +#define AB8500_CLASSDCONF1_PARLVIB 6 +#define AB8500_CLASSDCONF1_VIB1SWAPEN 3 +#define AB8500_CLASSDCONF1_VIB2SWAPEN 2 +#define AB8500_CLASSDCONF1_HFLSWAPEN 1 +#define AB8500_CLASSDCONF1_HFRSWAPEN 0 + +/* AB8500_CLASSDCONF2 */ +#define AB8500_CLASSDCONF2_FIRBYP3 7 +#define AB8500_CLASSDCONF2_FIRBYP2 6 +#define AB8500_CLASSDCONF2_FIRBYP1 5 +#define AB8500_CLASSDCONF2_FIRBYP0 4 +#define AB8500_CLASSDCONF2_HIGHVOLEN3 3 +#define AB8500_CLASSDCONF2_HIGHVOLEN2 2 +#define AB8500_CLASSDCONF2_HIGHVOLEN1 1 +#define AB8500_CLASSDCONF2_HIGHVOLEN0 0 + +/* AB8500_CLASSDCONF3 */ +#define AB8500_CLASSDCONF3_DITHHPGAIN 4 +#define AB8500_CLASSDCONF3_DITHHPGAIN_MAX 0x0A +#define AB8500_CLASSDCONF3_DITHWGAIN 0 +#define AB8500_CLASSDCONF3_DITHWGAIN_MAX 0x0A + +/* AB8500_DMICFILTCONF */ +#define AB8500_DMICFILTCONF_ANCINSEL 7 +#define AB8500_DMICFILTCONF_DA3TOEAR 6 +#define AB8500_DMICFILTCONF_DMIC1SINC3 5 +#define AB8500_DMICFILTCONF_DMIC2SINC3 4 +#define AB8500_DMICFILTCONF_DMIC3SINC3 3 +#define AB8500_DMICFILTCONF_DMIC4SINC3 2 +#define AB8500_DMICFILTCONF_DMIC5SINC3 1 +#define AB8500_DMICFILTCONF_DMIC6SINC3 0 + +/* AB8500_DIGMULTCONF1 */ +#define AB8500_DIGMULTCONF1_DATOHSLEN 7 +#define AB8500_DIGMULTCONF1_DATOHSREN 6 +#define AB8500_DIGMULTCONF1_AD1SEL 5 +#define AB8500_DIGMULTCONF1_AD2SEL 4 +#define AB8500_DIGMULTCONF1_AD3SEL 3 +#define AB8500_DIGMULTCONF1_AD5SEL 2 +#define AB8500_DIGMULTCONF1_AD6SEL 1 +#define AB8500_DIGMULTCONF1_ANCSEL 0 + +/* AB8500_DIGMULTCONF2 */ +#define AB8500_DIGMULTCONF2_DATOHFREN 7 +#define AB8500_DIGMULTCONF2_DATOHFLEN 6 +#define AB8500_DIGMULTCONF2_HFRSEL 5 +#define AB8500_DIGMULTCONF2_HFLSEL 4 +#define AB8500_DIGMULTCONF2_FIRSID1SEL 2 +#define AB8500_DIGMULTCONF2_FIRSID2SEL 0 + +/* AB8500_ADDIGGAIN1 */ +/* AB8500_ADDIGGAIN2 */ +/* AB8500_ADDIGGAIN3 */ +/* AB8500_ADDIGGAIN4 */ +/* AB8500_ADDIGGAIN5 */ +/* AB8500_ADDIGGAIN6 */ +#define AB8500_ADDIGGAINX_FADEDISADX 6 +#define AB8500_ADDIGGAINX_ADXGAIN_MAX 0x3F + +/* AB8500_DADIGGAIN1 */ +/* AB8500_DADIGGAIN2 */ +/* AB8500_DADIGGAIN3 */ +/* AB8500_DADIGGAIN4 */ +/* AB8500_DADIGGAIN5 */ +/* AB8500_DADIGGAIN6 */ +#define AB8500_DADIGGAINX_FADEDISDAX 6 +#define AB8500_DADIGGAINX_DAXGAIN_MAX 0x3F + +/* AB8500_ADDIGLOOPGAIN1 */ +/* AB8500_ADDIGLOOPGAIN2 */ +#define AB8500_ADDIGLOOPGAINX_FADEDISADXL 6 +#define AB8500_ADDIGLOOPGAINX_ADXLBGAIN_MAX 0x3F + +/* AB8500_HSLEARDIGGAIN */ +#define AB8500_HSLEARDIGGAIN_HSSINC1 7 +#define AB8500_HSLEARDIGGAIN_FADEDISHSL 4 +#define AB8500_HSLEARDIGGAIN_HSLDGAIN_MAX 0x09 + +/* AB8500_HSRDIGGAIN */ +#define AB8500_HSRDIGGAIN_FADESPEED 6 +#define AB8500_HSRDIGGAIN_FADEDISHSR 4 +#define AB8500_HSRDIGGAIN_HSRDGAIN_MAX 0x09 + +/* AB8500_SIDFIRGAIN1 */ +/* AB8500_SIDFIRGAIN2 */ +#define AB8500_SIDFIRGAINX_FIRSIDXGAIN_MAX 0x1F + +/* AB8500_ANCCONF1 */ +#define AB8500_ANCCONF1_ANCIIRUPDATE 3 +#define AB8500_ANCCONF1_ENANC 2 +#define AB8500_ANCCONF1_ANCIIRINIT 1 +#define AB8500_ANCCONF1_ANCFIRUPDATE 0 + +/* AB8500_ANCCONF2 */ +#define AB8500_ANCCONF2_SHIFT 5 +#define AB8500_ANCCONF2_MIN -0x10 +#define AB8500_ANCCONF2_MAX 0xF + +/* AB8500_ANCCONF3 */ +#define AB8500_ANCCONF3_SHIFT 5 +#define AB8500_ANCCONF3_MIN -0x10 +#define AB8500_ANCCONF3_MAX 0xF + +/* AB8500_ANCCONF4 */ +#define AB8500_ANCCONF4_SHIFT 5 +#define AB8500_ANCCONF4_MIN -0x10 +#define AB8500_ANCCONF4_MAX 0xF + +/* AB8500_ANC_FIR_COEFFS */ +#define AB8500_ANC_FIR_COEFF_MIN -0x8000 +#define AB8500_ANC_FIR_COEFF_MAX 0x7FFF +#define AB8500_ANC_FIR_COEFFS 15 + +/* AB8500_ANC_IIR_COEFFS */ +#define AB8500_ANC_IIR_COEFF_MIN -0x800000 +#define AB8500_ANC_IIR_COEFF_MAX 0x7FFFFF +#define AB8500_ANC_IIR_COEFFS 24 +/* AB8500_ANC_WARP_DELAY */ +#define AB8500_ANC_WARP_DELAY_SHIFT 16 +#define AB8500_ANC_WARP_DELAY_MIN 0x0000 +#define AB8500_ANC_WARP_DELAY_MAX 0xFFFF + +/* AB8500_ANCCONF11 */ +/* AB8500_ANCCONF12 */ +/* AB8500_ANCCONF13 */ +/* AB8500_ANCCONF14 */ + +/* AB8500_SIDFIRADR */ +#define AB8500_SIDFIRADR_FIRSIDSET 7 +#define AB8500_SIDFIRADR_ADDRESS_SHIFT 0 +#define AB8500_SIDFIRADR_ADDRESS_MAX 0x7F + +/* AB8500_SIDFIRCOEF1 */ +/* AB8500_SIDFIRCOEF2 */ +#define AB8500_SID_FIR_COEFF_MIN 0 +#define AB8500_SID_FIR_COEFF_MAX 0xFFFF +#define AB8500_SID_FIR_COEFFS 128 + +/* AB8500_SIDFIRCONF */ +#define AB8500_SIDFIRCONF_ENFIRSIDS 2 +#define AB8500_SIDFIRCONF_FIRSIDSTOIF1 1 +#define AB8500_SIDFIRCONF_FIRSIDBUSY 0 + +/* AB8500_AUDINTMASK1 */ +/* AB8500_AUDINTSOURCE1 */ +/* AB8500_AUDINTMASK2 */ +/* AB8500_AUDINTSOURCE2 */ + +/* AB8500_FIFOCONF1 */ +#define AB8500_FIFOCONF1_BFIFOMASK 0x80 +#define AB8500_FIFOCONF1_BFIFO19M2 0x40 +#define AB8500_FIFOCONF1_BFIFOINT_SHIFT 0 +#define AB8500_FIFOCONF1_BFIFOINT_MAX 0x3F + +/* AB8500_FIFOCONF2 */ +#define AB8500_FIFOCONF2_BFIFOTX_SHIFT 0 +#define AB8500_FIFOCONF2_BFIFOTX_MAX 0xFF + +/* AB8500_FIFOCONF3 */ +#define AB8500_FIFOCONF3_BFIFOEXSL_SHIFT 5 +#define AB8500_FIFOCONF3_BFIFOEXSL_MAX 0x5 +#define AB8500_FIFOCONF3_PREBITCLK0_SHIFT 2 +#define AB8500_FIFOCONF3_PREBITCLK0_MAX 0x7 +#define AB8500_FIFOCONF3_BFIFOMAST_SHIFT 1 +#define AB8500_FIFOCONF3_BFIFORUN_SHIFT 0 + +/* AB8500_FIFOCONF4 */ +#define AB8500_FIFOCONF4_BFIFOFRAMSW_SHIFT 0 +#define AB8500_FIFOCONF4_BFIFOFRAMSW_MAX 0xFF + +/* AB8500_FIFOCONF5 */ +#define AB8500_FIFOCONF5_BFIFOWAKEUP_SHIFT 0 +#define AB8500_FIFOCONF5_BFIFOWAKEUP_MAX 0xFF + +/* AB8500_FIFOCONF6 */ +#define AB8500_FIFOCONF6_BFIFOSAMPLE_SHIFT 0 +#define AB8500_FIFOCONF6_BFIFOSAMPLE_MAX 0xFF + +/* AB8500_AUDREV */ + +#endif diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c new file mode 100644 index 000000000..02b4d01ad --- /dev/null +++ b/sound/soc/codecs/ac97.c @@ -0,0 +1,154 @@ +/* + * ac97.c -- ALSA Soc AC97 codec support + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Generic AC97 support. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct snd_soc_dapm_widget ac97_widgets[] = { + SND_SOC_DAPM_INPUT("RX"), + SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route ac97_routes[] = { + { "AC97 Capture", NULL, "RX" }, + { "TX", NULL, "AC97 Playback" }, +}; + +static int ac97_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + + int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE; + return snd_ac97_set_rate(ac97, reg, substream->runtime->rate); +} + +static const struct snd_soc_dai_ops ac97_dai_ops = { + .prepare = ac97_prepare, +}; + +static struct snd_soc_dai_driver ac97_dai = { + .name = "ac97-hifi", + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SND_SOC_STD_AC97_FMTS,}, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SND_SOC_STD_AC97_FMTS,}, + .ops = &ac97_dai_ops, +}; + +static int ac97_soc_probe(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97_template ac97_template; + int ret; + + /* add codec as bus device for standard ac97 */ + ret = snd_ac97_bus(component->card->snd_card, 0, soc_ac97_ops, + NULL, &ac97_bus); + if (ret < 0) + return ret; + + memset(&ac97_template, 0, sizeof(struct snd_ac97_template)); + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97); + if (ret < 0) + return ret; + + snd_soc_component_set_drvdata(component, ac97); + + return 0; +} + +#ifdef CONFIG_PM +static int ac97_soc_suspend(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + + snd_ac97_suspend(ac97); + + return 0; +} + +static int ac97_soc_resume(struct snd_soc_component *component) +{ + + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + + snd_ac97_resume(ac97); + + return 0; +} +#else +#define ac97_soc_suspend NULL +#define ac97_soc_resume NULL +#endif + +static const struct snd_soc_component_driver soc_component_dev_ac97 = { + .probe = ac97_soc_probe, + .suspend = ac97_soc_suspend, + .resume = ac97_soc_resume, + .dapm_widgets = ac97_widgets, + .num_dapm_widgets = ARRAY_SIZE(ac97_widgets), + .dapm_routes = ac97_routes, + .num_dapm_routes = ARRAY_SIZE(ac97_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ac97_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_ac97, &ac97_dai, 1); +} + +static int ac97_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver ac97_codec_driver = { + .driver = { + .name = "ac97-codec", + }, + + .probe = ac97_probe, + .remove = ac97_remove, +}; + +module_platform_driver(ac97_codec_driver); + +MODULE_DESCRIPTION("Soc Generic AC97 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ac97-codec"); diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c new file mode 100644 index 000000000..ada663bf5 --- /dev/null +++ b/sound/soc/codecs/ad1836.c @@ -0,0 +1,413 @@ + /* + * Audio Codec driver supporting: + * AD1835A, AD1836, AD1837A, AD1838A, AD1839A + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ad1836.h" + +enum ad1836_type { + AD1835, + AD1836, + AD1838, +}; + +/* codec private data */ +struct ad1836_priv { + enum ad1836_type type; + struct regmap *regmap; +}; + +/* + * AD1836 volume/mute/de-emphasis etc. controls + */ +static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"}; + +static SOC_ENUM_SINGLE_DECL(ad1836_deemp_enum, + AD1836_DAC_CTRL1, 8, ad1836_deemp); + +#define AD1836_DAC_VOLUME(x) \ + SOC_DOUBLE_R("DAC" #x " Playback Volume", AD1836_DAC_L_VOL(x), \ + AD1836_DAC_R_VOL(x), 0, 0x3FF, 0) + +#define AD1836_DAC_SWITCH(x) \ + SOC_DOUBLE("DAC" #x " Playback Switch", AD1836_DAC_CTRL2, \ + AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1) + +#define AD1836_ADC_SWITCH(x) \ + SOC_DOUBLE("ADC" #x " Capture Switch", AD1836_ADC_CTRL2, \ + AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1) + +static const struct snd_kcontrol_new ad183x_dac_controls[] = { + AD1836_DAC_VOLUME(1), + AD1836_DAC_SWITCH(1), + AD1836_DAC_VOLUME(2), + AD1836_DAC_SWITCH(2), + AD1836_DAC_VOLUME(3), + AD1836_DAC_SWITCH(3), + AD1836_DAC_VOLUME(4), + AD1836_DAC_SWITCH(4), +}; + +static const struct snd_soc_dapm_widget ad183x_dac_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("DAC1OUT"), + SND_SOC_DAPM_OUTPUT("DAC2OUT"), + SND_SOC_DAPM_OUTPUT("DAC3OUT"), + SND_SOC_DAPM_OUTPUT("DAC4OUT"), +}; + +static const struct snd_soc_dapm_route ad183x_dac_routes[] = { + { "DAC1OUT", NULL, "DAC" }, + { "DAC2OUT", NULL, "DAC" }, + { "DAC3OUT", NULL, "DAC" }, + { "DAC4OUT", NULL, "DAC" }, +}; + +static const struct snd_kcontrol_new ad183x_adc_controls[] = { + AD1836_ADC_SWITCH(1), + AD1836_ADC_SWITCH(2), + AD1836_ADC_SWITCH(3), +}; + +static const struct snd_soc_dapm_widget ad183x_adc_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("ADC1IN"), + SND_SOC_DAPM_INPUT("ADC2IN"), +}; + +static const struct snd_soc_dapm_route ad183x_adc_routes[] = { + { "ADC", NULL, "ADC1IN" }, + { "ADC", NULL, "ADC2IN" }, +}; + +static const struct snd_kcontrol_new ad183x_controls[] = { + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1, + AD1836_ADC_HIGHPASS_FILTER, 1, 0), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum), +}; + +static const struct snd_soc_dapm_widget ad183x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1, + AD1836_DAC_POWERDOWN, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1, + AD1836_ADC_POWERDOWN, 1, NULL, 0), +}; + +static const struct snd_soc_dapm_route ad183x_dapm_routes[] = { + { "DAC", NULL, "ADC_PWR" }, + { "ADC", NULL, "ADC_PWR" }, +}; + +static const DECLARE_TLV_DB_SCALE(ad1836_in_tlv, 0, 300, 0); + +static const struct snd_kcontrol_new ad1836_controls[] = { + SOC_DOUBLE_TLV("ADC2 Capture Volume", AD1836_ADC_CTRL1, 3, 0, 4, 0, + ad1836_in_tlv), +}; + +/* + * DAI ops entries + */ + +static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + /* at present, we support adc aux mode to interface with + * blackfin sport tdm mode + */ + case SND_SOC_DAIFMT_DSP_A: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + /* ALCLK,ABCLK are both output, AD1836 can only be master */ + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad1836_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(dai->component); + int word_len = 0; + + /* bit size */ + switch (params_width(params)) { + case 16: + word_len = AD1836_WORD_LEN_16; + break; + case 20: + word_len = AD1836_WORD_LEN_20; + break; + case 24: + case 32: + word_len = AD1836_WORD_LEN_24; + break; + default: + return -EINVAL; + } + + regmap_update_bits(ad1836->regmap, AD1836_DAC_CTRL1, + AD1836_DAC_WORD_LEN_MASK, + word_len << AD1836_DAC_WORD_LEN_OFFSET); + + regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2, + AD1836_ADC_WORD_LEN_MASK, + word_len << AD1836_ADC_WORD_OFFSET); + + return 0; +} + +static const struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, +}; + +#define AD183X_DAI(_name, num_dacs, num_adcs) \ +{ \ + .name = _name "-hifi", \ + .playback = { \ + .stream_name = "Playback", \ + .channels_min = 2, \ + .channels_max = (num_dacs) * 2, \ + .rates = SNDRV_PCM_RATE_48000, \ + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \ + }, \ + .capture = { \ + .stream_name = "Capture", \ + .channels_min = 2, \ + .channels_max = (num_adcs) * 2, \ + .rates = SNDRV_PCM_RATE_48000, \ + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \ + }, \ + .ops = &ad1836_dai_ops, \ +} + +static struct snd_soc_dai_driver ad183x_dais[] = { + [AD1835] = AD183X_DAI("ad1835", 4, 1), + [AD1836] = AD183X_DAI("ad1836", 3, 2), + [AD1838] = AD183X_DAI("ad1838", 3, 1), +}; + +#ifdef CONFIG_PM +static int ad1836_suspend(struct snd_soc_component *component) +{ + struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component); + /* reset clock control mode */ + return regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2, + AD1836_ADC_SERFMT_MASK, 0); +} + +static int ad1836_resume(struct snd_soc_component *component) +{ + struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component); + /* restore clock control mode */ + return regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2, + AD1836_ADC_SERFMT_MASK, AD1836_ADC_AUX); +} +#else +#define ad1836_suspend NULL +#define ad1836_resume NULL +#endif + +static int ad1836_probe(struct snd_soc_component *component) +{ + struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int num_dacs, num_adcs; + int ret = 0; + int i; + + num_dacs = ad183x_dais[ad1836->type].playback.channels_max / 2; + num_adcs = ad183x_dais[ad1836->type].capture.channels_max / 2; + + /* default setting for ad1836 */ + /* de-emphasis: 48kHz, power-on dac */ + regmap_write(ad1836->regmap, AD1836_DAC_CTRL1, 0x300); + /* unmute dac channels */ + regmap_write(ad1836->regmap, AD1836_DAC_CTRL2, 0x0); + /* high-pass filter enable, power-on adc */ + regmap_write(ad1836->regmap, AD1836_ADC_CTRL1, 0x100); + /* unmute adc channles, adc aux mode */ + regmap_write(ad1836->regmap, AD1836_ADC_CTRL2, 0x180); + /* volume */ + for (i = 1; i <= num_dacs; ++i) { + regmap_write(ad1836->regmap, AD1836_DAC_L_VOL(i), 0x3FF); + regmap_write(ad1836->regmap, AD1836_DAC_R_VOL(i), 0x3FF); + } + + if (ad1836->type == AD1836) { + /* left/right diff:PGA/MUX */ + regmap_write(ad1836->regmap, AD1836_ADC_CTRL3, 0x3A); + ret = snd_soc_add_component_controls(component, ad1836_controls, + ARRAY_SIZE(ad1836_controls)); + if (ret) + return ret; + } else { + regmap_write(ad1836->regmap, AD1836_ADC_CTRL3, 0x00); + } + + ret = snd_soc_add_component_controls(component, ad183x_dac_controls, num_dacs * 2); + if (ret) + return ret; + + ret = snd_soc_add_component_controls(component, ad183x_adc_controls, num_adcs); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, ad183x_dac_dapm_widgets, num_dacs); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, ad183x_adc_dapm_widgets, num_adcs); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, ad183x_dac_routes, num_dacs); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, ad183x_adc_routes, num_adcs); + if (ret) + return ret; + + return ret; +} + +/* power down chip */ +static void ad1836_remove(struct snd_soc_component *component) +{ + struct ad1836_priv *ad1836 = snd_soc_component_get_drvdata(component); + /* reset clock control mode */ + regmap_update_bits(ad1836->regmap, AD1836_ADC_CTRL2, + AD1836_ADC_SERFMT_MASK, 0); +} + +static const struct snd_soc_component_driver soc_component_dev_ad1836 = { + .probe = ad1836_probe, + .remove = ad1836_remove, + .suspend = ad1836_suspend, + .resume = ad1836_resume, + .controls = ad183x_controls, + .num_controls = ARRAY_SIZE(ad183x_controls), + .dapm_widgets = ad183x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ad183x_dapm_widgets), + .dapm_routes = ad183x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ad183x_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct reg_default ad1836_reg_defaults[] = { + { AD1836_DAC_CTRL1, 0x0000 }, + { AD1836_DAC_CTRL2, 0x0000 }, + { AD1836_DAC_L_VOL(0), 0x0000 }, + { AD1836_DAC_R_VOL(0), 0x0000 }, + { AD1836_DAC_L_VOL(1), 0x0000 }, + { AD1836_DAC_R_VOL(1), 0x0000 }, + { AD1836_DAC_L_VOL(2), 0x0000 }, + { AD1836_DAC_R_VOL(2), 0x0000 }, + { AD1836_DAC_L_VOL(3), 0x0000 }, + { AD1836_DAC_R_VOL(3), 0x0000 }, + { AD1836_ADC_CTRL1, 0x0000 }, + { AD1836_ADC_CTRL2, 0x0000 }, + { AD1836_ADC_CTRL3, 0x0000 }, +}; + +static const struct regmap_config ad1836_regmap_config = { + .val_bits = 12, + .reg_bits = 4, + .read_flag_mask = 0x08, + + .max_register = AD1836_ADC_CTRL3, + .reg_defaults = ad1836_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ad1836_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int ad1836_spi_probe(struct spi_device *spi) +{ + struct ad1836_priv *ad1836; + int ret; + + ad1836 = devm_kzalloc(&spi->dev, sizeof(struct ad1836_priv), + GFP_KERNEL); + if (ad1836 == NULL) + return -ENOMEM; + + ad1836->regmap = devm_regmap_init_spi(spi, &ad1836_regmap_config); + if (IS_ERR(ad1836->regmap)) + return PTR_ERR(ad1836->regmap); + + ad1836->type = spi_get_device_id(spi)->driver_data; + + spi_set_drvdata(spi, ad1836); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_ad1836, &ad183x_dais[ad1836->type], 1); + return ret; +} + +static const struct spi_device_id ad1836_ids[] = { + { "ad1835", AD1835 }, + { "ad1836", AD1836 }, + { "ad1837", AD1835 }, + { "ad1838", AD1838 }, + { "ad1839", AD1838 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, ad1836_ids); + +static struct spi_driver ad1836_spi_driver = { + .driver = { + .name = "ad1836", + }, + .probe = ad1836_spi_probe, + .id_table = ad1836_ids, +}; + +module_spi_driver(ad1836_spi_driver); + +MODULE_DESCRIPTION("ASoC ad1836 driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1836.h b/sound/soc/codecs/ad1836.h new file mode 100644 index 000000000..dd7be0dbb --- /dev/null +++ b/sound/soc/codecs/ad1836.h @@ -0,0 +1,51 @@ +/* + * Audio Codec driver supporting: + * AD1835A, AD1836, AD1837A, AD1838A, AD1839A + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __AD1836_H__ +#define __AD1836_H__ + +#define AD1836_DAC_CTRL1 0 +#define AD1836_DAC_POWERDOWN 2 +#define AD1836_DAC_SERFMT_MASK 0xE0 +#define AD1836_DAC_SERFMT_PCK256 (0x4 << 5) +#define AD1836_DAC_SERFMT_PCK128 (0x5 << 5) +#define AD1836_DAC_WORD_LEN_MASK 0x18 +#define AD1836_DAC_WORD_LEN_OFFSET 3 + +#define AD1836_DAC_CTRL2 1 + +/* These macros are one-based. So AD183X_MUTE_LEFT(1) will return the mute bit + * for the first ADC/DAC */ +#define AD1836_MUTE_LEFT(x) (((x) * 2) - 2) +#define AD1836_MUTE_RIGHT(x) (((x) * 2) - 1) + +#define AD1836_DAC_L_VOL(x) ((x) * 2) +#define AD1836_DAC_R_VOL(x) (1 + ((x) * 2)) + +#define AD1836_ADC_CTRL1 12 +#define AD1836_ADC_POWERDOWN 7 +#define AD1836_ADC_HIGHPASS_FILTER 8 + +#define AD1836_ADC_CTRL2 13 +#define AD1836_ADC_WORD_LEN_MASK 0x30 +#define AD1836_ADC_WORD_OFFSET 4 +#define AD1836_ADC_SERFMT_MASK (7 << 6) +#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6) +#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6) +#define AD1836_ADC_AUX (0x6 << 6) + +#define AD1836_ADC_CTRL3 14 + +#define AD1836_NUM_REGS 16 + +#define AD1836_WORD_LEN_24 0x0 +#define AD1836_WORD_LEN_20 0x1 +#define AD1836_WORD_LEN_16 0x2 + +#endif diff --git a/sound/soc/codecs/ad193x-i2c.c b/sound/soc/codecs/ad193x-i2c.c new file mode 100644 index 000000000..b9551334f --- /dev/null +++ b/sound/soc/codecs/ad193x-i2c.c @@ -0,0 +1,49 @@ +/* + * AD1936/AD1937 audio driver + * + * Copyright 2014 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include + +#include + +#include "ad193x.h" + +static const struct i2c_device_id ad193x_id[] = { + { "ad1936", AD193X }, + { "ad1937", AD193X }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad193x_id); + +static int ad193x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap_config config; + + config = ad193x_regmap_config; + config.val_bits = 8; + config.reg_bits = 8; + + return ad193x_probe(&client->dev, + devm_regmap_init_i2c(client, &config), + (enum ad193x_type)id->driver_data); +} + +static struct i2c_driver ad193x_i2c_driver = { + .driver = { + .name = "ad193x", + }, + .probe = ad193x_i2c_probe, + .id_table = ad193x_id, +}; +module_i2c_driver(ad193x_i2c_driver); + +MODULE_DESCRIPTION("ASoC AD1936/AD1937 audio CODEC driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad193x-spi.c b/sound/soc/codecs/ad193x-spi.c new file mode 100644 index 000000000..3c1394a80 --- /dev/null +++ b/sound/soc/codecs/ad193x-spi.c @@ -0,0 +1,54 @@ +/* + * AD1938/AD1939 audio driver + * + * Copyright 2014 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include + +#include + +#include "ad193x.h" + +static int ad193x_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap_config config; + + config = ad193x_regmap_config; + config.val_bits = 8; + config.reg_bits = 16; + config.read_flag_mask = 0x09; + config.write_flag_mask = 0x08; + + return ad193x_probe(&spi->dev, devm_regmap_init_spi(spi, &config), + (enum ad193x_type)id->driver_data); +} + +static const struct spi_device_id ad193x_spi_id[] = { + { "ad193x", AD193X }, + { "ad1933", AD1933 }, + { "ad1934", AD1934 }, + { "ad1938", AD193X }, + { "ad1939", AD193X }, + { "adau1328", AD193X }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad193x_spi_id); + +static struct spi_driver ad193x_spi_driver = { + .driver = { + .name = "ad193x", + }, + .probe = ad193x_spi_probe, + .id_table = ad193x_spi_id, +}; +module_spi_driver(ad193x_spi_driver); + +MODULE_DESCRIPTION("ASoC AD1938/AD1939 audio CODEC driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c new file mode 100644 index 000000000..4b60ebee4 --- /dev/null +++ b/sound/soc/codecs/ad193x.c @@ -0,0 +1,454 @@ +/* + * AD193X Audio Codec driver supporting AD1936/7/8/9 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ad193x.h" + +/* codec private data */ +struct ad193x_priv { + struct regmap *regmap; + enum ad193x_type type; + int sysclk; +}; + +/* + * AD193X volume/mute/de-emphasis etc. controls + */ +static const char * const ad193x_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"}; + +static SOC_ENUM_SINGLE_DECL(ad193x_deemp_enum, AD193X_DAC_CTRL2, 1, + ad193x_deemp); + +static const DECLARE_TLV_DB_MINMAX(adau193x_tlv, -9563, 0); + +static const struct snd_kcontrol_new ad193x_snd_controls[] = { + /* DAC volume control */ + SOC_DOUBLE_R_TLV("DAC1 Volume", AD193X_DAC_L1_VOL, + AD193X_DAC_R1_VOL, 0, 0xFF, 1, adau193x_tlv), + SOC_DOUBLE_R_TLV("DAC2 Volume", AD193X_DAC_L2_VOL, + AD193X_DAC_R2_VOL, 0, 0xFF, 1, adau193x_tlv), + SOC_DOUBLE_R_TLV("DAC3 Volume", AD193X_DAC_L3_VOL, + AD193X_DAC_R3_VOL, 0, 0xFF, 1, adau193x_tlv), + SOC_DOUBLE_R_TLV("DAC4 Volume", AD193X_DAC_L4_VOL, + AD193X_DAC_R4_VOL, 0, 0xFF, 1, adau193x_tlv), + + /* DAC switch control */ + SOC_DOUBLE("DAC1 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL1_MUTE, + AD193X_DACR1_MUTE, 1, 1), + SOC_DOUBLE("DAC2 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL2_MUTE, + AD193X_DACR2_MUTE, 1, 1), + SOC_DOUBLE("DAC3 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL3_MUTE, + AD193X_DACR3_MUTE, 1, 1), + SOC_DOUBLE("DAC4 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL4_MUTE, + AD193X_DACR4_MUTE, 1, 1), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", ad193x_deemp_enum), +}; + +static const struct snd_kcontrol_new ad193x_adc_snd_controls[] = { + /* ADC switch control */ + SOC_DOUBLE("ADC1 Switch", AD193X_ADC_CTRL0, AD193X_ADCL1_MUTE, + AD193X_ADCR1_MUTE, 1, 1), + SOC_DOUBLE("ADC2 Switch", AD193X_ADC_CTRL0, AD193X_ADCL2_MUTE, + AD193X_ADCR2_MUTE, 1, 1), + + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", AD193X_ADC_CTRL0, + AD193X_ADC_HIGHPASS_FILTER, 1, 0), +}; + +static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("DAC Output", AD193X_DAC_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("SYSCLK", AD193X_PLL_CLK_CTRL0, 7, 0, NULL, 0), + SND_SOC_DAPM_VMID("VMID"), + SND_SOC_DAPM_OUTPUT("DAC1OUT"), + SND_SOC_DAPM_OUTPUT("DAC2OUT"), + SND_SOC_DAPM_OUTPUT("DAC3OUT"), + SND_SOC_DAPM_OUTPUT("DAC4OUT"), +}; + +static const struct snd_soc_dapm_widget ad193x_adc_widgets[] = { + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_INPUT("ADC1IN"), + SND_SOC_DAPM_INPUT("ADC2IN"), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "SYSCLK" }, + { "DAC Output", NULL, "DAC" }, + { "DAC Output", NULL, "VMID" }, + { "DAC1OUT", NULL, "DAC Output" }, + { "DAC2OUT", NULL, "DAC Output" }, + { "DAC3OUT", NULL, "DAC Output" }, + { "DAC4OUT", NULL, "DAC Output" }, + { "SYSCLK", NULL, "PLL_PWR" }, +}; + +static const struct snd_soc_dapm_route ad193x_adc_audio_paths[] = { + { "ADC", NULL, "SYSCLK" }, + { "ADC", NULL, "ADC_PWR" }, + { "ADC", NULL, "ADC1IN" }, + { "ADC", NULL, "ADC2IN" }, +}; + +static inline bool ad193x_has_adc(const struct ad193x_priv *ad193x) +{ + switch (ad193x->type) { + case AD1933: + case AD1934: + return false; + default: + break; + } + + return true; +} + +/* + * DAI ops entries + */ + +static int ad193x_mute(struct snd_soc_dai *dai, int mute) +{ + struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(dai->component); + + if (mute) + regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2, + AD193X_DAC_MASTER_MUTE, + AD193X_DAC_MASTER_MUTE); + else + regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2, + AD193X_DAC_MASTER_MUTE, 0); + + return 0; +} + +static int ad193x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(dai->component); + unsigned int channels; + + switch (slots) { + case 2: + channels = AD193X_2_CHANNELS; + break; + case 4: + channels = AD193X_4_CHANNELS; + break; + case 8: + channels = AD193X_8_CHANNELS; + break; + case 16: + channels = AD193X_16_CHANNELS; + break; + default: + return -EINVAL; + } + + regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL1, + AD193X_DAC_CHAN_MASK, channels << AD193X_DAC_CHAN_SHFT); + if (ad193x_has_adc(ad193x)) + regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL2, + AD193X_ADC_CHAN_MASK, + channels << AD193X_ADC_CHAN_SHFT); + + return 0; +} + +static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(codec_dai->component); + unsigned int adc_serfmt = 0; + unsigned int adc_fmt = 0; + unsigned int dac_fmt = 0; + + /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S + * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + adc_serfmt |= AD193X_ADC_SERFMT_TDM; + break; + case SND_SOC_DAIFMT_DSP_A: + adc_serfmt |= AD193X_ADC_SERFMT_AUX; + break; + default: + if (ad193x_has_adc(ad193x)) + return -EINVAL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ + break; + case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ + adc_fmt |= AD193X_ADC_LEFT_HIGH; + dac_fmt |= AD193X_DAC_LEFT_HIGH; + break; + case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ + adc_fmt |= AD193X_ADC_BCLK_INV; + dac_fmt |= AD193X_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ + adc_fmt |= AD193X_ADC_LEFT_HIGH; + adc_fmt |= AD193X_ADC_BCLK_INV; + dac_fmt |= AD193X_DAC_LEFT_HIGH; + dac_fmt |= AD193X_DAC_BCLK_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ + adc_fmt |= AD193X_ADC_LCR_MASTER; + adc_fmt |= AD193X_ADC_BCLK_MASTER; + dac_fmt |= AD193X_DAC_LCR_MASTER; + dac_fmt |= AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ + adc_fmt |= AD193X_ADC_LCR_MASTER; + dac_fmt |= AD193X_DAC_LCR_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + adc_fmt |= AD193X_ADC_BCLK_MASTER; + dac_fmt |= AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + break; + default: + return -EINVAL; + } + + if (ad193x_has_adc(ad193x)) { + regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL1, + AD193X_ADC_SERFMT_MASK, adc_serfmt); + regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL2, + AD193X_ADC_FMT_MASK, adc_fmt); + } + regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL1, + AD193X_DAC_FMT_MASK, dac_fmt); + + return 0; +} + +static int ad193x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(component); + switch (freq) { + case 12288000: + case 18432000: + case 24576000: + case 36864000: + ad193x->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int ad193x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int word_len = 0, master_rate = 0; + struct snd_soc_component *component = dai->component; + struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(component); + + /* bit size */ + switch (params_width(params)) { + case 16: + word_len = 3; + break; + case 20: + word_len = 1; + break; + case 24: + case 32: + word_len = 0; + break; + } + + switch (ad193x->sysclk) { + case 12288000: + master_rate = AD193X_PLL_INPUT_256; + break; + case 18432000: + master_rate = AD193X_PLL_INPUT_384; + break; + case 24576000: + master_rate = AD193X_PLL_INPUT_512; + break; + case 36864000: + master_rate = AD193X_PLL_INPUT_768; + break; + } + + regmap_update_bits(ad193x->regmap, AD193X_PLL_CLK_CTRL0, + AD193X_PLL_INPUT_MASK, master_rate); + + regmap_update_bits(ad193x->regmap, AD193X_DAC_CTRL2, + AD193X_DAC_WORD_LEN_MASK, + word_len << AD193X_DAC_WORD_LEN_SHFT); + + if (ad193x_has_adc(ad193x)) + regmap_update_bits(ad193x->regmap, AD193X_ADC_CTRL1, + AD193X_ADC_WORD_LEN_MASK, word_len); + + return 0; +} + +static const struct snd_soc_dai_ops ad193x_dai_ops = { + .hw_params = ad193x_hw_params, + .digital_mute = ad193x_mute, + .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, + .set_fmt = ad193x_set_dai_fmt, +}; + +/* codec DAI instance */ +static struct snd_soc_dai_driver ad193x_dai = { + .name = "ad193x-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ad193x_dai_ops, +}; + +static int ad193x_component_probe(struct snd_soc_component *component) +{ + struct ad193x_priv *ad193x = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int num, ret; + + /* default setting for ad193x */ + + /* unmute dac channels */ + regmap_write(ad193x->regmap, AD193X_DAC_CHNL_MUTE, 0x0); + /* de-emphasis: 48kHz, powedown dac */ + regmap_write(ad193x->regmap, AD193X_DAC_CTRL2, 0x1A); + /* dac in tdm mode */ + regmap_write(ad193x->regmap, AD193X_DAC_CTRL0, 0x40); + + /* adc only */ + if (ad193x_has_adc(ad193x)) { + /* high-pass filter enable */ + regmap_write(ad193x->regmap, AD193X_ADC_CTRL0, 0x3); + /* sata delay=1, adc aux mode */ + regmap_write(ad193x->regmap, AD193X_ADC_CTRL1, 0x43); + } + + /* pll input: mclki/xi */ + regmap_write(ad193x->regmap, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */ + regmap_write(ad193x->regmap, AD193X_PLL_CLK_CTRL1, 0x04); + + /* adc only */ + if (ad193x_has_adc(ad193x)) { + /* add adc controls */ + num = ARRAY_SIZE(ad193x_adc_snd_controls); + ret = snd_soc_add_component_controls(component, + ad193x_adc_snd_controls, + num); + if (ret) + return ret; + + /* add adc widgets */ + num = ARRAY_SIZE(ad193x_adc_widgets); + ret = snd_soc_dapm_new_controls(dapm, + ad193x_adc_widgets, + num); + if (ret) + return ret; + + /* add adc routes */ + num = ARRAY_SIZE(ad193x_adc_audio_paths); + ret = snd_soc_dapm_add_routes(dapm, + ad193x_adc_audio_paths, + num); + if (ret) + return ret; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_ad193x = { + .probe = ad193x_component_probe, + .controls = ad193x_snd_controls, + .num_controls = ARRAY_SIZE(ad193x_snd_controls), + .dapm_widgets = ad193x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ad193x_dapm_widgets), + .dapm_routes = audio_paths, + .num_dapm_routes = ARRAY_SIZE(audio_paths), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +const struct regmap_config ad193x_regmap_config = { + .max_register = AD193X_NUM_REGS - 1, +}; +EXPORT_SYMBOL_GPL(ad193x_regmap_config); + +int ad193x_probe(struct device *dev, struct regmap *regmap, + enum ad193x_type type) +{ + struct ad193x_priv *ad193x; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ad193x = devm_kzalloc(dev, sizeof(*ad193x), GFP_KERNEL); + if (ad193x == NULL) + return -ENOMEM; + + ad193x->regmap = regmap; + ad193x->type = type; + + dev_set_drvdata(dev, ad193x); + + return devm_snd_soc_register_component(dev, &soc_component_dev_ad193x, + &ad193x_dai, 1); +} +EXPORT_SYMBOL_GPL(ad193x_probe); + +MODULE_DESCRIPTION("ASoC ad193x driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h new file mode 100644 index 000000000..8b1e65f92 --- /dev/null +++ b/sound/soc/codecs/ad193x.h @@ -0,0 +1,99 @@ +/* + * AD193X Audio Codec driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __AD193X_H__ +#define __AD193X_H__ + +#include + +struct device; + +enum ad193x_type { + AD193X, + AD1933, + AD1934, +}; + +extern const struct regmap_config ad193x_regmap_config; +int ad193x_probe(struct device *dev, struct regmap *regmap, + enum ad193x_type type); + +#define AD193X_PLL_CLK_CTRL0 0x00 +#define AD193X_PLL_POWERDOWN 0x01 +#define AD193X_PLL_INPUT_MASK 0x6 +#define AD193X_PLL_INPUT_256 (0 << 1) +#define AD193X_PLL_INPUT_384 (1 << 1) +#define AD193X_PLL_INPUT_512 (2 << 1) +#define AD193X_PLL_INPUT_768 (3 << 1) +#define AD193X_PLL_CLK_CTRL1 0x01 +#define AD193X_DAC_CTRL0 0x02 +#define AD193X_DAC_POWERDOWN 0x01 +#define AD193X_DAC_SERFMT_MASK 0xC0 +#define AD193X_DAC_SERFMT_STEREO (0 << 6) +#define AD193X_DAC_SERFMT_TDM (1 << 6) +#define AD193X_DAC_CTRL1 0x03 +#define AD193X_DAC_CHAN_SHFT 1 +#define AD193X_DAC_CHAN_MASK (3 << AD193X_DAC_CHAN_SHFT) +#define AD193X_DAC_LCR_MASTER (1 << 4) +#define AD193X_DAC_BCLK_MASTER (1 << 5) +#define AD193X_DAC_LEFT_HIGH (1 << 3) +#define AD193X_DAC_BCLK_INV (1 << 7) +#define AD193X_DAC_FMT_MASK (AD193X_DAC_LCR_MASTER | \ + AD193X_DAC_BCLK_MASTER | AD193X_DAC_LEFT_HIGH | AD193X_DAC_BCLK_INV) +#define AD193X_DAC_CTRL2 0x04 +#define AD193X_DAC_WORD_LEN_SHFT 3 +#define AD193X_DAC_WORD_LEN_MASK 0x18 +#define AD193X_DAC_MASTER_MUTE 1 +#define AD193X_DAC_CHNL_MUTE 0x05 +#define AD193X_DACL1_MUTE 0 +#define AD193X_DACR1_MUTE 1 +#define AD193X_DACL2_MUTE 2 +#define AD193X_DACR2_MUTE 3 +#define AD193X_DACL3_MUTE 4 +#define AD193X_DACR3_MUTE 5 +#define AD193X_DACL4_MUTE 6 +#define AD193X_DACR4_MUTE 7 +#define AD193X_DAC_L1_VOL 0x06 +#define AD193X_DAC_R1_VOL 0x07 +#define AD193X_DAC_L2_VOL 0x08 +#define AD193X_DAC_R2_VOL 0x09 +#define AD193X_DAC_L3_VOL 0x0a +#define AD193X_DAC_R3_VOL 0x0b +#define AD193X_DAC_L4_VOL 0x0c +#define AD193X_DAC_R4_VOL 0x0d +#define AD193X_ADC_CTRL0 0x0e +#define AD193X_ADC_POWERDOWN 0x01 +#define AD193X_ADC_HIGHPASS_FILTER 1 +#define AD193X_ADCL1_MUTE 2 +#define AD193X_ADCR1_MUTE 3 +#define AD193X_ADCL2_MUTE 4 +#define AD193X_ADCR2_MUTE 5 +#define AD193X_ADC_CTRL1 0x0f +#define AD193X_ADC_SERFMT_MASK 0x60 +#define AD193X_ADC_SERFMT_STEREO (0 << 5) +#define AD193X_ADC_SERFMT_TDM (1 << 5) +#define AD193X_ADC_SERFMT_AUX (2 << 5) +#define AD193X_ADC_WORD_LEN_MASK 0x3 +#define AD193X_ADC_CTRL2 0x10 +#define AD193X_ADC_CHAN_SHFT 4 +#define AD193X_ADC_CHAN_MASK (3 << AD193X_ADC_CHAN_SHFT) +#define AD193X_ADC_LCR_MASTER (1 << 3) +#define AD193X_ADC_BCLK_MASTER (1 << 6) +#define AD193X_ADC_LEFT_HIGH (1 << 2) +#define AD193X_ADC_BCLK_INV (1 << 1) +#define AD193X_ADC_FMT_MASK (AD193X_ADC_LCR_MASTER | \ + AD193X_ADC_BCLK_MASTER | AD193X_ADC_LEFT_HIGH | AD193X_ADC_BCLK_INV) + +#define AD193X_2_CHANNELS 0 +#define AD193X_4_CHANNELS 1 +#define AD193X_8_CHANNELS 2 +#define AD193X_16_CHANNELS 3 + +#define AD193X_NUM_REGS 17 + +#endif diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c new file mode 100644 index 000000000..16dab3f00 --- /dev/null +++ b/sound/soc/codecs/ad1980.c @@ -0,0 +1,330 @@ +/* + * ad1980.c -- ALSA Soc AD1980 codec support + * + * Copyright: Analog Device Inc. + * Author: Roy Huang + * Cliff Cai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +/* + * WARNING: + * + * Because Analog Devices Inc. discontinued the ad1980 sound chip since + * Sep. 2009, this ad1980 driver is not maintained, tested and supported + * by ADI now. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct reg_default ad1980_reg_defaults[] = { + { 0x02, 0x8000 }, + { 0x04, 0x8000 }, + { 0x06, 0x8000 }, + { 0x0c, 0x8008 }, + { 0x0e, 0x8008 }, + { 0x10, 0x8808 }, + { 0x12, 0x8808 }, + { 0x16, 0x8808 }, + { 0x18, 0x8808 }, + { 0x1a, 0x0000 }, + { 0x1c, 0x8000 }, + { 0x20, 0x0000 }, + { 0x28, 0x03c7 }, + { 0x2c, 0xbb80 }, + { 0x2e, 0xbb80 }, + { 0x30, 0xbb80 }, + { 0x32, 0xbb80 }, + { 0x36, 0x8080 }, + { 0x38, 0x8080 }, + { 0x3a, 0x2000 }, + { 0x60, 0x0000 }, + { 0x62, 0x0000 }, + { 0x72, 0x0000 }, + { 0x74, 0x1001 }, + { 0x76, 0x0000 }, +}; + +static bool ad1980_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_RESET ... AC97_MASTER_MONO: + case AC97_PHONE ... AC97_CD: + case AC97_AUX ... AC97_GENERAL_PURPOSE: + case AC97_POWERDOWN ... AC97_PCM_LR_ADC_RATE: + case AC97_SPDIF: + case AC97_CODEC_CLASS_REV: + case AC97_PCI_SVID: + case AC97_AD_CODEC_CFG: + case AC97_AD_JACK_SPDIF: + case AC97_AD_SERIAL_CFG: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool ad1980_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return false; + default: + return ad1980_readable_reg(dev, reg); + } +} + +static const struct regmap_config ad1980_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x7e, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = regmap_ac97_default_volatile, + .readable_reg = ad1980_readable_reg, + .writeable_reg = ad1980_writeable_reg, + + .reg_defaults = ad1980_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ad1980_reg_defaults), +}; + +static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line", + "Stereo Mix", "Mono Mix", "Phone"}; + +static SOC_ENUM_DOUBLE_DECL(ad1980_cap_src, + AC97_REC_SEL, 8, 0, ad1980_rec_sel); + +static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = { +SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1), + +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + +SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), +SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + +SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0), +SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1), + +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + +SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1), +SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1), + +SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1), +SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + +SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0), +SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0), + +SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1), + +SOC_DOUBLE("Center/LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Center/LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 7, 1, 1), + +SOC_ENUM("Capture Source", ad1980_cap_src), + +SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0), +}; + +static const struct snd_soc_dapm_widget ad1980_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2"), +SND_SOC_DAPM_INPUT("CD_L"), +SND_SOC_DAPM_INPUT("CD_R"), +SND_SOC_DAPM_INPUT("AUX_L"), +SND_SOC_DAPM_INPUT("AUX_R"), +SND_SOC_DAPM_INPUT("LINE_IN_L"), +SND_SOC_DAPM_INPUT("LINE_IN_R"), + +SND_SOC_DAPM_OUTPUT("LFE_OUT"), +SND_SOC_DAPM_OUTPUT("CENTER_OUT"), +SND_SOC_DAPM_OUTPUT("LINE_OUT_L"), +SND_SOC_DAPM_OUTPUT("LINE_OUT_R"), +SND_SOC_DAPM_OUTPUT("MONO_OUT"), +SND_SOC_DAPM_OUTPUT("HP_OUT_L"), +SND_SOC_DAPM_OUTPUT("HP_OUT_R"), +}; + +static const struct snd_soc_dapm_route ad1980_dapm_routes[] = { + { "Capture", NULL, "MIC1" }, + { "Capture", NULL, "MIC2" }, + { "Capture", NULL, "CD_L" }, + { "Capture", NULL, "CD_R" }, + { "Capture", NULL, "AUX_L" }, + { "Capture", NULL, "AUX_R" }, + { "Capture", NULL, "LINE_IN_L" }, + { "Capture", NULL, "LINE_IN_R" }, + + { "LFE_OUT", NULL, "Playback" }, + { "CENTER_OUT", NULL, "Playback" }, + { "LINE_OUT_L", NULL, "Playback" }, + { "LINE_OUT_R", NULL, "Playback" }, + { "MONO_OUT", NULL, "Playback" }, + { "HP_OUT_L", NULL, "Playback" }, + { "HP_OUT_R", NULL, "Playback" }, +}; + +static struct snd_soc_dai_driver ad1980_dai = { + .name = "ad1980-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_48000, + .formats = SND_SOC_STD_AC97_FMTS, }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SND_SOC_STD_AC97_FMTS, }, +}; + +#define AD1980_VENDOR_ID 0x41445300 +#define AD1980_VENDOR_MASK 0xffffff00 + +static int ad1980_reset(struct snd_soc_component *component, int try_warm) +{ + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + unsigned int retry_cnt = 0; + int ret; + + do { + ret = snd_ac97_reset(ac97, true, AD1980_VENDOR_ID, + AD1980_VENDOR_MASK); + if (ret >= 0) + return 0; + + /* + * Set bit 16slot in register 74h, then every slot will has only + * 16 bits. This command is sent out in 20bit mode, in which + * case the first nibble of data is eaten by the addr. (Tag is + * always 16 bit) + */ + snd_soc_component_write(component, AC97_AD_SERIAL_CFG, 0x9900); + + } while (retry_cnt++ < 10); + + dev_err(component->dev, "Failed to reset: AC97 link error\n"); + + return -EIO; +} + +static int ad1980_soc_probe(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97; + struct regmap *regmap; + int ret; + u16 vendor_id2; + u16 ext_status; + + ac97 = snd_soc_new_ac97_component(component, 0, 0); + if (IS_ERR(ac97)) { + ret = PTR_ERR(ac97); + dev_err(component->dev, "Failed to register AC97 component: %d\n", ret); + return ret; + } + + regmap = regmap_init_ac97(ac97, &ad1980_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + goto err_free_ac97; + } + + snd_soc_component_init_regmap(component, regmap); + snd_soc_component_set_drvdata(component, ac97); + + ret = ad1980_reset(component, 0); + if (ret < 0) + goto reset_err; + + vendor_id2 = snd_soc_component_read32(component, AC97_VENDOR_ID2); + if (vendor_id2 == 0x5374) { + dev_warn(component->dev, + "Found AD1981 - only 2/2 IN/OUT Channels supported\n"); + } + + /* unmute captures and playbacks volume */ + snd_soc_component_write(component, AC97_MASTER, 0x0000); + snd_soc_component_write(component, AC97_PCM, 0x0000); + snd_soc_component_write(component, AC97_REC_GAIN, 0x0000); + snd_soc_component_write(component, AC97_CENTER_LFE_MASTER, 0x0000); + snd_soc_component_write(component, AC97_SURROUND_MASTER, 0x0000); + + /*power on LFE/CENTER/Surround DACs*/ + ext_status = snd_soc_component_read32(component, AC97_EXTENDED_STATUS); + snd_soc_component_write(component, AC97_EXTENDED_STATUS, ext_status&~0x3800); + + return 0; + +reset_err: + snd_soc_component_exit_regmap(component); +err_free_ac97: + snd_soc_free_ac97_component(ac97); + return ret; +} + +static void ad1980_soc_remove(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + + snd_soc_component_exit_regmap(component); + snd_soc_free_ac97_component(ac97); +} + +static const struct snd_soc_component_driver soc_component_dev_ad1980 = { + .probe = ad1980_soc_probe, + .remove = ad1980_soc_remove, + .controls = ad1980_snd_ac97_controls, + .num_controls = ARRAY_SIZE(ad1980_snd_ac97_controls), + .dapm_widgets = ad1980_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ad1980_dapm_widgets), + .dapm_routes = ad1980_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ad1980_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ad1980_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_ad1980, &ad1980_dai, 1); +} + +static struct platform_driver ad1980_codec_driver = { + .driver = { + .name = "ad1980", + }, + + .probe = ad1980_probe, +}; + +module_platform_driver(ad1980_codec_driver); + +MODULE_DESCRIPTION("ASoC ad1980 driver (Obsolete)"); +MODULE_AUTHOR("Roy Huang, Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c new file mode 100644 index 000000000..03ee571e1 --- /dev/null +++ b/sound/soc/codecs/ad73311.c @@ -0,0 +1,86 @@ +/* + * ad73311.c -- ALSA Soc AD73311 codec support + * + * Copyright: Analog Device Inc. + * Author: Cliff Cai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ad73311.h" + +static const struct snd_soc_dapm_widget ad73311_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("VINP"), +SND_SOC_DAPM_INPUT("VINN"), +SND_SOC_DAPM_OUTPUT("VOUTN"), +SND_SOC_DAPM_OUTPUT("VOUTP"), +}; + +static const struct snd_soc_dapm_route ad73311_dapm_routes[] = { + { "Capture", NULL, "VINP" }, + { "Capture", NULL, "VINN" }, + + { "VOUTN", NULL, "Playback" }, + { "VOUTP", NULL, "Playback" }, +}; + +static struct snd_soc_dai_driver ad73311_dai = { + .name = "ad73311-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; + +static const struct snd_soc_component_driver soc_component_dev_ad73311 = { + .dapm_widgets = ad73311_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ad73311_dapm_widgets), + .dapm_routes = ad73311_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ad73311_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ad73311_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_ad73311, &ad73311_dai, 1); +} + +static struct platform_driver ad73311_codec_driver = { + .driver = { + .name = "ad73311", + }, + + .probe = ad73311_probe, +}; + +module_platform_driver(ad73311_codec_driver); + +MODULE_DESCRIPTION("ASoC ad73311 driver"); +MODULE_AUTHOR("Cliff Cai "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h new file mode 100644 index 000000000..4b353eefc --- /dev/null +++ b/sound/soc/codecs/ad73311.h @@ -0,0 +1,88 @@ +/* + * File: sound/soc/codec/ad73311.h + * Based on: + * Author: Cliff Cai + * + * Created: Thur Sep 25, 2008 + * Description: definitions for AD73311 registers + * + * + * Modified: + * Copyright 2006 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AD73311_H__ +#define __AD73311_H__ + +#define AD_CONTROL 0x8000 +#define AD_DATA 0x0000 +#define AD_READ 0x4000 +#define AD_WRITE 0x0000 + +/* Control register A */ +#define CTRL_REG_A (0 << 8) + +#define REGA_MODE_PRO 0x00 +#define REGA_MODE_DATA 0x01 +#define REGA_MODE_MIXED 0x03 +#define REGA_DLB 0x04 +#define REGA_SLB 0x08 +#define REGA_DEVC(x) ((x & 0x7) << 4) +#define REGA_RESET 0x80 + +/* Control register B */ +#define CTRL_REG_B (1 << 8) + +#define REGB_DIRATE(x) (x & 0x3) +#define REGB_SCDIV(x) ((x & 0x3) << 2) +#define REGB_MCDIV(x) ((x & 0x7) << 4) +#define REGB_CEE (1 << 7) + +/* Control register C */ +#define CTRL_REG_C (2 << 8) + +#define REGC_PUDEV (1 << 0) +#define REGC_PUADC (1 << 3) +#define REGC_PUDAC (1 << 4) +#define REGC_PUREF (1 << 5) +#define REGC_REFUSE (1 << 6) + +/* Control register D */ +#define CTRL_REG_D (3 << 8) + +#define REGD_IGS(x) (x & 0x7) +#define REGD_RMOD (1 << 3) +#define REGD_OGS(x) ((x & 0x7) << 4) +#define REGD_MUTE (1 << 7) + +/* Control register E */ +#define CTRL_REG_E (4 << 8) + +#define REGE_DA(x) (x & 0x1f) +#define REGE_IBYP (1 << 5) + +/* Control register F */ +#define CTRL_REG_F (5 << 8) + +#define REGF_SEEN (1 << 5) +#define REGF_INV (1 << 6) +#define REGF_ALB (1 << 7) + +#endif diff --git a/sound/soc/codecs/adau-utils.c b/sound/soc/codecs/adau-utils.c new file mode 100644 index 000000000..19d6a6f41 --- /dev/null +++ b/sound/soc/codecs/adau-utils.c @@ -0,0 +1,61 @@ +/* + * Shared helper functions for devices from the ADAU family + * + * Copyright 2011-2016 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include + +#include "adau-utils.h" + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]) +{ + unsigned int r, n, m, i, j; + unsigned int div; + + if (!freq_out) { + r = 0; + n = 0; + m = 0; + div = 0; + } else { + if (freq_out % freq_in != 0) { + div = DIV_ROUND_UP(freq_in, 13500000); + freq_in /= div; + r = freq_out / freq_in; + i = freq_out % freq_in; + j = gcd(i, freq_in); + n = i / j; + m = freq_in / j; + div--; + } else { + r = freq_out / freq_in; + n = 0; + m = 0; + div = 0; + } + if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) + return -EINVAL; + } + + regs[0] = m >> 8; + regs[1] = m & 0xff; + regs[2] = n >> 8; + regs[3] = n & 0xff; + regs[4] = (r << 3) | (div << 1); + if (m != 0) + regs[4] |= 1; /* Fractional mode */ + + return 0; +} +EXPORT_SYMBOL_GPL(adau_calc_pll_cfg); + +MODULE_DESCRIPTION("ASoC ADAU audio CODECs shared helper functions"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/adau-utils.h b/sound/soc/codecs/adau-utils.h new file mode 100644 index 000000000..bf5947b35 --- /dev/null +++ b/sound/soc/codecs/adau-utils.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_SOC_CODECS_ADAU_PLL_H +#define SOUND_SOC_CODECS_ADAU_PLL_H + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]); + +#endif diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c new file mode 100644 index 000000000..f22ff9f6a --- /dev/null +++ b/sound/soc/codecs/adau1373.c @@ -0,0 +1,1520 @@ +/* + * Analog Devices ADAU1373 Audio Codec drive + * + * Copyright 2011 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "adau1373.h" +#include "adau-utils.h" + +struct adau1373_dai { + unsigned int clk_src; + unsigned int sysclk; + bool enable_src; + bool master; +}; + +struct adau1373 { + struct regmap *regmap; + struct adau1373_dai dais[3]; +}; + +#define ADAU1373_INPUT_MODE 0x00 +#define ADAU1373_AINL_CTRL(x) (0x01 + (x) * 2) +#define ADAU1373_AINR_CTRL(x) (0x02 + (x) * 2) +#define ADAU1373_LLINE_OUT(x) (0x9 + (x) * 2) +#define ADAU1373_RLINE_OUT(x) (0xa + (x) * 2) +#define ADAU1373_LSPK_OUT 0x0d +#define ADAU1373_RSPK_OUT 0x0e +#define ADAU1373_LHP_OUT 0x0f +#define ADAU1373_RHP_OUT 0x10 +#define ADAU1373_ADC_GAIN 0x11 +#define ADAU1373_LADC_MIXER 0x12 +#define ADAU1373_RADC_MIXER 0x13 +#define ADAU1373_LLINE1_MIX 0x14 +#define ADAU1373_RLINE1_MIX 0x15 +#define ADAU1373_LLINE2_MIX 0x16 +#define ADAU1373_RLINE2_MIX 0x17 +#define ADAU1373_LSPK_MIX 0x18 +#define ADAU1373_RSPK_MIX 0x19 +#define ADAU1373_LHP_MIX 0x1a +#define ADAU1373_RHP_MIX 0x1b +#define ADAU1373_EP_MIX 0x1c +#define ADAU1373_HP_CTRL 0x1d +#define ADAU1373_HP_CTRL2 0x1e +#define ADAU1373_LS_CTRL 0x1f +#define ADAU1373_EP_CTRL 0x21 +#define ADAU1373_MICBIAS_CTRL1 0x22 +#define ADAU1373_MICBIAS_CTRL2 0x23 +#define ADAU1373_OUTPUT_CTRL 0x24 +#define ADAU1373_PWDN_CTRL1 0x25 +#define ADAU1373_PWDN_CTRL2 0x26 +#define ADAU1373_PWDN_CTRL3 0x27 +#define ADAU1373_DPLL_CTRL(x) (0x28 + (x) * 7) +#define ADAU1373_PLL_CTRL1(x) (0x29 + (x) * 7) +#define ADAU1373_PLL_CTRL2(x) (0x2a + (x) * 7) +#define ADAU1373_PLL_CTRL3(x) (0x2b + (x) * 7) +#define ADAU1373_PLL_CTRL4(x) (0x2c + (x) * 7) +#define ADAU1373_PLL_CTRL5(x) (0x2d + (x) * 7) +#define ADAU1373_PLL_CTRL6(x) (0x2e + (x) * 7) +#define ADAU1373_HEADDECT 0x36 +#define ADAU1373_ADC_DAC_STATUS 0x37 +#define ADAU1373_ADC_CTRL 0x3c +#define ADAU1373_DAI(x) (0x44 + (x)) +#define ADAU1373_CLK_SRC_DIV(x) (0x40 + (x) * 2) +#define ADAU1373_BCLKDIV(x) (0x47 + (x)) +#define ADAU1373_SRC_RATIOA(x) (0x4a + (x) * 2) +#define ADAU1373_SRC_RATIOB(x) (0x4b + (x) * 2) +#define ADAU1373_DEEMP_CTRL 0x50 +#define ADAU1373_SRC_DAI_CTRL(x) (0x51 + (x)) +#define ADAU1373_DIN_MIX_CTRL(x) (0x56 + (x)) +#define ADAU1373_DOUT_MIX_CTRL(x) (0x5b + (x)) +#define ADAU1373_DAI_PBL_VOL(x) (0x62 + (x) * 2) +#define ADAU1373_DAI_PBR_VOL(x) (0x63 + (x) * 2) +#define ADAU1373_DAI_RECL_VOL(x) (0x68 + (x) * 2) +#define ADAU1373_DAI_RECR_VOL(x) (0x69 + (x) * 2) +#define ADAU1373_DAC1_PBL_VOL 0x6e +#define ADAU1373_DAC1_PBR_VOL 0x6f +#define ADAU1373_DAC2_PBL_VOL 0x70 +#define ADAU1373_DAC2_PBR_VOL 0x71 +#define ADAU1373_ADC_RECL_VOL 0x72 +#define ADAU1373_ADC_RECR_VOL 0x73 +#define ADAU1373_DMIC_RECL_VOL 0x74 +#define ADAU1373_DMIC_RECR_VOL 0x75 +#define ADAU1373_VOL_GAIN1 0x76 +#define ADAU1373_VOL_GAIN2 0x77 +#define ADAU1373_VOL_GAIN3 0x78 +#define ADAU1373_HPF_CTRL 0x7d +#define ADAU1373_BASS1 0x7e +#define ADAU1373_BASS2 0x7f +#define ADAU1373_DRC(x) (0x80 + (x) * 0x10) +#define ADAU1373_3D_CTRL1 0xc0 +#define ADAU1373_3D_CTRL2 0xc1 +#define ADAU1373_FDSP_SEL1 0xdc +#define ADAU1373_FDSP_SEL2 0xdd +#define ADAU1373_FDSP_SEL3 0xde +#define ADAU1373_FDSP_SEL4 0xdf +#define ADAU1373_DIGMICCTRL 0xe2 +#define ADAU1373_DIGEN 0xeb +#define ADAU1373_SOFT_RESET 0xff + + +#define ADAU1373_PLL_CTRL6_DPLL_BYPASS BIT(1) +#define ADAU1373_PLL_CTRL6_PLL_EN BIT(0) + +#define ADAU1373_DAI_INVERT_BCLK BIT(7) +#define ADAU1373_DAI_MASTER BIT(6) +#define ADAU1373_DAI_INVERT_LRCLK BIT(4) +#define ADAU1373_DAI_WLEN_16 0x0 +#define ADAU1373_DAI_WLEN_20 0x4 +#define ADAU1373_DAI_WLEN_24 0x8 +#define ADAU1373_DAI_WLEN_32 0xc +#define ADAU1373_DAI_WLEN_MASK 0xc +#define ADAU1373_DAI_FORMAT_RIGHT_J 0x0 +#define ADAU1373_DAI_FORMAT_LEFT_J 0x1 +#define ADAU1373_DAI_FORMAT_I2S 0x2 +#define ADAU1373_DAI_FORMAT_DSP 0x3 + +#define ADAU1373_BCLKDIV_SOURCE BIT(5) +#define ADAU1373_BCLKDIV_SR_MASK (0x07 << 2) +#define ADAU1373_BCLKDIV_BCLK_MASK 0x03 +#define ADAU1373_BCLKDIV_32 0x03 +#define ADAU1373_BCLKDIV_64 0x02 +#define ADAU1373_BCLKDIV_128 0x01 +#define ADAU1373_BCLKDIV_256 0x00 + +#define ADAU1373_ADC_CTRL_PEAK_DETECT BIT(0) +#define ADAU1373_ADC_CTRL_RESET BIT(1) +#define ADAU1373_ADC_CTRL_RESET_FORCE BIT(2) + +#define ADAU1373_OUTPUT_CTRL_LDIFF BIT(3) +#define ADAU1373_OUTPUT_CTRL_LNFBEN BIT(2) + +#define ADAU1373_PWDN_CTRL3_PWR_EN BIT(0) + +#define ADAU1373_EP_CTRL_MICBIAS1_OFFSET 4 +#define ADAU1373_EP_CTRL_MICBIAS2_OFFSET 2 + +static const struct reg_default adau1373_reg_defaults[] = { + { ADAU1373_INPUT_MODE, 0x00 }, + { ADAU1373_AINL_CTRL(0), 0x00 }, + { ADAU1373_AINR_CTRL(0), 0x00 }, + { ADAU1373_AINL_CTRL(1), 0x00 }, + { ADAU1373_AINR_CTRL(1), 0x00 }, + { ADAU1373_AINL_CTRL(2), 0x00 }, + { ADAU1373_AINR_CTRL(2), 0x00 }, + { ADAU1373_AINL_CTRL(3), 0x00 }, + { ADAU1373_AINR_CTRL(3), 0x00 }, + { ADAU1373_LLINE_OUT(0), 0x00 }, + { ADAU1373_RLINE_OUT(0), 0x00 }, + { ADAU1373_LLINE_OUT(1), 0x00 }, + { ADAU1373_RLINE_OUT(1), 0x00 }, + { ADAU1373_LSPK_OUT, 0x00 }, + { ADAU1373_RSPK_OUT, 0x00 }, + { ADAU1373_LHP_OUT, 0x00 }, + { ADAU1373_RHP_OUT, 0x00 }, + { ADAU1373_ADC_GAIN, 0x00 }, + { ADAU1373_LADC_MIXER, 0x00 }, + { ADAU1373_RADC_MIXER, 0x00 }, + { ADAU1373_LLINE1_MIX, 0x00 }, + { ADAU1373_RLINE1_MIX, 0x00 }, + { ADAU1373_LLINE2_MIX, 0x00 }, + { ADAU1373_RLINE2_MIX, 0x00 }, + { ADAU1373_LSPK_MIX, 0x00 }, + { ADAU1373_RSPK_MIX, 0x00 }, + { ADAU1373_LHP_MIX, 0x00 }, + { ADAU1373_RHP_MIX, 0x00 }, + { ADAU1373_EP_MIX, 0x00 }, + { ADAU1373_HP_CTRL, 0x00 }, + { ADAU1373_HP_CTRL2, 0x00 }, + { ADAU1373_LS_CTRL, 0x00 }, + { ADAU1373_EP_CTRL, 0x00 }, + { ADAU1373_MICBIAS_CTRL1, 0x00 }, + { ADAU1373_MICBIAS_CTRL2, 0x00 }, + { ADAU1373_OUTPUT_CTRL, 0x00 }, + { ADAU1373_PWDN_CTRL1, 0x00 }, + { ADAU1373_PWDN_CTRL2, 0x00 }, + { ADAU1373_PWDN_CTRL3, 0x00 }, + { ADAU1373_DPLL_CTRL(0), 0x00 }, + { ADAU1373_PLL_CTRL1(0), 0x00 }, + { ADAU1373_PLL_CTRL2(0), 0x00 }, + { ADAU1373_PLL_CTRL3(0), 0x00 }, + { ADAU1373_PLL_CTRL4(0), 0x00 }, + { ADAU1373_PLL_CTRL5(0), 0x00 }, + { ADAU1373_PLL_CTRL6(0), 0x02 }, + { ADAU1373_DPLL_CTRL(1), 0x00 }, + { ADAU1373_PLL_CTRL1(1), 0x00 }, + { ADAU1373_PLL_CTRL2(1), 0x00 }, + { ADAU1373_PLL_CTRL3(1), 0x00 }, + { ADAU1373_PLL_CTRL4(1), 0x00 }, + { ADAU1373_PLL_CTRL5(1), 0x00 }, + { ADAU1373_PLL_CTRL6(1), 0x02 }, + { ADAU1373_HEADDECT, 0x00 }, + { ADAU1373_ADC_CTRL, 0x00 }, + { ADAU1373_CLK_SRC_DIV(0), 0x00 }, + { ADAU1373_CLK_SRC_DIV(1), 0x00 }, + { ADAU1373_DAI(0), 0x0a }, + { ADAU1373_DAI(1), 0x0a }, + { ADAU1373_DAI(2), 0x0a }, + { ADAU1373_BCLKDIV(0), 0x00 }, + { ADAU1373_BCLKDIV(1), 0x00 }, + { ADAU1373_BCLKDIV(2), 0x00 }, + { ADAU1373_SRC_RATIOA(0), 0x00 }, + { ADAU1373_SRC_RATIOB(0), 0x00 }, + { ADAU1373_SRC_RATIOA(1), 0x00 }, + { ADAU1373_SRC_RATIOB(1), 0x00 }, + { ADAU1373_SRC_RATIOA(2), 0x00 }, + { ADAU1373_SRC_RATIOB(2), 0x00 }, + { ADAU1373_DEEMP_CTRL, 0x00 }, + { ADAU1373_SRC_DAI_CTRL(0), 0x08 }, + { ADAU1373_SRC_DAI_CTRL(1), 0x08 }, + { ADAU1373_SRC_DAI_CTRL(2), 0x08 }, + { ADAU1373_DIN_MIX_CTRL(0), 0x00 }, + { ADAU1373_DIN_MIX_CTRL(1), 0x00 }, + { ADAU1373_DIN_MIX_CTRL(2), 0x00 }, + { ADAU1373_DIN_MIX_CTRL(3), 0x00 }, + { ADAU1373_DIN_MIX_CTRL(4), 0x00 }, + { ADAU1373_DOUT_MIX_CTRL(0), 0x00 }, + { ADAU1373_DOUT_MIX_CTRL(1), 0x00 }, + { ADAU1373_DOUT_MIX_CTRL(2), 0x00 }, + { ADAU1373_DOUT_MIX_CTRL(3), 0x00 }, + { ADAU1373_DOUT_MIX_CTRL(4), 0x00 }, + { ADAU1373_DAI_PBL_VOL(0), 0x00 }, + { ADAU1373_DAI_PBR_VOL(0), 0x00 }, + { ADAU1373_DAI_PBL_VOL(1), 0x00 }, + { ADAU1373_DAI_PBR_VOL(1), 0x00 }, + { ADAU1373_DAI_PBL_VOL(2), 0x00 }, + { ADAU1373_DAI_PBR_VOL(2), 0x00 }, + { ADAU1373_DAI_RECL_VOL(0), 0x00 }, + { ADAU1373_DAI_RECR_VOL(0), 0x00 }, + { ADAU1373_DAI_RECL_VOL(1), 0x00 }, + { ADAU1373_DAI_RECR_VOL(1), 0x00 }, + { ADAU1373_DAI_RECL_VOL(2), 0x00 }, + { ADAU1373_DAI_RECR_VOL(2), 0x00 }, + { ADAU1373_DAC1_PBL_VOL, 0x00 }, + { ADAU1373_DAC1_PBR_VOL, 0x00 }, + { ADAU1373_DAC2_PBL_VOL, 0x00 }, + { ADAU1373_DAC2_PBR_VOL, 0x00 }, + { ADAU1373_ADC_RECL_VOL, 0x00 }, + { ADAU1373_ADC_RECR_VOL, 0x00 }, + { ADAU1373_DMIC_RECL_VOL, 0x00 }, + { ADAU1373_DMIC_RECR_VOL, 0x00 }, + { ADAU1373_VOL_GAIN1, 0x00 }, + { ADAU1373_VOL_GAIN2, 0x00 }, + { ADAU1373_VOL_GAIN3, 0x00 }, + { ADAU1373_HPF_CTRL, 0x00 }, + { ADAU1373_BASS1, 0x00 }, + { ADAU1373_BASS2, 0x00 }, + { ADAU1373_DRC(0) + 0x0, 0x78 }, + { ADAU1373_DRC(0) + 0x1, 0x18 }, + { ADAU1373_DRC(0) + 0x2, 0x00 }, + { ADAU1373_DRC(0) + 0x3, 0x00 }, + { ADAU1373_DRC(0) + 0x4, 0x00 }, + { ADAU1373_DRC(0) + 0x5, 0xc0 }, + { ADAU1373_DRC(0) + 0x6, 0x00 }, + { ADAU1373_DRC(0) + 0x7, 0x00 }, + { ADAU1373_DRC(0) + 0x8, 0x00 }, + { ADAU1373_DRC(0) + 0x9, 0xc0 }, + { ADAU1373_DRC(0) + 0xa, 0x88 }, + { ADAU1373_DRC(0) + 0xb, 0x7a }, + { ADAU1373_DRC(0) + 0xc, 0xdf }, + { ADAU1373_DRC(0) + 0xd, 0x20 }, + { ADAU1373_DRC(0) + 0xe, 0x00 }, + { ADAU1373_DRC(0) + 0xf, 0x00 }, + { ADAU1373_DRC(1) + 0x0, 0x78 }, + { ADAU1373_DRC(1) + 0x1, 0x18 }, + { ADAU1373_DRC(1) + 0x2, 0x00 }, + { ADAU1373_DRC(1) + 0x3, 0x00 }, + { ADAU1373_DRC(1) + 0x4, 0x00 }, + { ADAU1373_DRC(1) + 0x5, 0xc0 }, + { ADAU1373_DRC(1) + 0x6, 0x00 }, + { ADAU1373_DRC(1) + 0x7, 0x00 }, + { ADAU1373_DRC(1) + 0x8, 0x00 }, + { ADAU1373_DRC(1) + 0x9, 0xc0 }, + { ADAU1373_DRC(1) + 0xa, 0x88 }, + { ADAU1373_DRC(1) + 0xb, 0x7a }, + { ADAU1373_DRC(1) + 0xc, 0xdf }, + { ADAU1373_DRC(1) + 0xd, 0x20 }, + { ADAU1373_DRC(1) + 0xe, 0x00 }, + { ADAU1373_DRC(1) + 0xf, 0x00 }, + { ADAU1373_DRC(2) + 0x0, 0x78 }, + { ADAU1373_DRC(2) + 0x1, 0x18 }, + { ADAU1373_DRC(2) + 0x2, 0x00 }, + { ADAU1373_DRC(2) + 0x3, 0x00 }, + { ADAU1373_DRC(2) + 0x4, 0x00 }, + { ADAU1373_DRC(2) + 0x5, 0xc0 }, + { ADAU1373_DRC(2) + 0x6, 0x00 }, + { ADAU1373_DRC(2) + 0x7, 0x00 }, + { ADAU1373_DRC(2) + 0x8, 0x00 }, + { ADAU1373_DRC(2) + 0x9, 0xc0 }, + { ADAU1373_DRC(2) + 0xa, 0x88 }, + { ADAU1373_DRC(2) + 0xb, 0x7a }, + { ADAU1373_DRC(2) + 0xc, 0xdf }, + { ADAU1373_DRC(2) + 0xd, 0x20 }, + { ADAU1373_DRC(2) + 0xe, 0x00 }, + { ADAU1373_DRC(2) + 0xf, 0x00 }, + { ADAU1373_3D_CTRL1, 0x00 }, + { ADAU1373_3D_CTRL2, 0x00 }, + { ADAU1373_FDSP_SEL1, 0x00 }, + { ADAU1373_FDSP_SEL2, 0x00 }, + { ADAU1373_FDSP_SEL2, 0x00 }, + { ADAU1373_FDSP_SEL4, 0x00 }, + { ADAU1373_DIGMICCTRL, 0x00 }, + { ADAU1373_DIGEN, 0x00 }, +}; + +static const DECLARE_TLV_DB_RANGE(adau1373_out_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1), + 8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0), + 16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0), + 24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0) +); + +static const DECLARE_TLV_DB_MINMAX(adau1373_digital_tlv, -9563, 0); +static const DECLARE_TLV_DB_SCALE(adau1373_in_pga_tlv, -1300, 100, 1); +static const DECLARE_TLV_DB_SCALE(adau1373_ep_tlv, -600, 600, 1); + +static const DECLARE_TLV_DB_SCALE(adau1373_input_boost_tlv, 0, 2000, 0); +static const DECLARE_TLV_DB_SCALE(adau1373_gain_boost_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(adau1373_speaker_boost_tlv, 1200, 600, 0); + +static const char *adau1373_fdsp_sel_text[] = { + "None", + "Channel 1", + "Channel 2", + "Channel 3", + "Channel 4", + "Channel 5", +}; + +static SOC_ENUM_SINGLE_DECL(adau1373_drc1_channel_enum, + ADAU1373_FDSP_SEL1, 4, adau1373_fdsp_sel_text); +static SOC_ENUM_SINGLE_DECL(adau1373_drc2_channel_enum, + ADAU1373_FDSP_SEL1, 0, adau1373_fdsp_sel_text); +static SOC_ENUM_SINGLE_DECL(adau1373_drc3_channel_enum, + ADAU1373_FDSP_SEL2, 0, adau1373_fdsp_sel_text); +static SOC_ENUM_SINGLE_DECL(adau1373_hpf_channel_enum, + ADAU1373_FDSP_SEL3, 0, adau1373_fdsp_sel_text); +static SOC_ENUM_SINGLE_DECL(adau1373_bass_channel_enum, + ADAU1373_FDSP_SEL4, 4, adau1373_fdsp_sel_text); + +static const char *adau1373_hpf_cutoff_text[] = { + "3.7Hz", "50Hz", "100Hz", "150Hz", "200Hz", "250Hz", "300Hz", "350Hz", + "400Hz", "450Hz", "500Hz", "550Hz", "600Hz", "650Hz", "700Hz", "750Hz", + "800Hz", +}; + +static SOC_ENUM_SINGLE_DECL(adau1373_hpf_cutoff_enum, + ADAU1373_HPF_CTRL, 3, adau1373_hpf_cutoff_text); + +static const char *adau1373_bass_lpf_cutoff_text[] = { + "801Hz", "1001Hz", +}; + +static const char *adau1373_bass_clip_level_text[] = { + "0.125", "0.250", "0.370", "0.500", "0.625", "0.750", "0.875", +}; + +static const unsigned int adau1373_bass_clip_level_values[] = { + 1, 2, 3, 4, 5, 6, 7, +}; + +static const char *adau1373_bass_hpf_cutoff_text[] = { + "158Hz", "232Hz", "347Hz", "520Hz", +}; + +static const DECLARE_TLV_DB_RANGE(adau1373_bass_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-600, 600, 1), + 3, 4, TLV_DB_SCALE_ITEM(950, 250, 0), + 5, 7, TLV_DB_SCALE_ITEM(1400, 150, 0) +); + +static SOC_ENUM_SINGLE_DECL(adau1373_bass_lpf_cutoff_enum, + ADAU1373_BASS1, 5, adau1373_bass_lpf_cutoff_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(adau1373_bass_clip_level_enum, + ADAU1373_BASS1, 2, 7, adau1373_bass_clip_level_text, + adau1373_bass_clip_level_values); + +static SOC_ENUM_SINGLE_DECL(adau1373_bass_hpf_cutoff_enum, + ADAU1373_BASS1, 0, adau1373_bass_hpf_cutoff_text); + +static const char *adau1373_3d_level_text[] = { + "0%", "6.67%", "13.33%", "20%", "26.67%", "33.33%", + "40%", "46.67%", "53.33%", "60%", "66.67%", "73.33%", + "80%", "86.67", "99.33%", "100%" +}; + +static const char *adau1373_3d_cutoff_text[] = { + "No 3D", "0.03125 fs", "0.04583 fs", "0.075 fs", "0.11458 fs", + "0.16875 fs", "0.27083 fs" +}; + +static SOC_ENUM_SINGLE_DECL(adau1373_3d_level_enum, + ADAU1373_3D_CTRL1, 4, adau1373_3d_level_text); +static SOC_ENUM_SINGLE_DECL(adau1373_3d_cutoff_enum, + ADAU1373_3D_CTRL1, 0, adau1373_3d_cutoff_text); + +static const DECLARE_TLV_DB_RANGE(adau1373_3d_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 7, TLV_DB_LINEAR_ITEM(-1800, -120) +); + +static const char *adau1373_lr_mux_text[] = { + "Mute", + "Right Channel (L+R)", + "Left Channel (L+R)", + "Stereo", +}; + +static SOC_ENUM_SINGLE_DECL(adau1373_lineout1_lr_mux_enum, + ADAU1373_OUTPUT_CTRL, 4, adau1373_lr_mux_text); +static SOC_ENUM_SINGLE_DECL(adau1373_lineout2_lr_mux_enum, + ADAU1373_OUTPUT_CTRL, 6, adau1373_lr_mux_text); +static SOC_ENUM_SINGLE_DECL(adau1373_speaker_lr_mux_enum, + ADAU1373_LS_CTRL, 4, adau1373_lr_mux_text); + +static const struct snd_kcontrol_new adau1373_controls[] = { + SOC_DOUBLE_R_TLV("AIF1 Capture Volume", ADAU1373_DAI_RECL_VOL(0), + ADAU1373_DAI_RECR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv), + SOC_DOUBLE_R_TLV("AIF2 Capture Volume", ADAU1373_DAI_RECL_VOL(1), + ADAU1373_DAI_RECR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv), + SOC_DOUBLE_R_TLV("AIF3 Capture Volume", ADAU1373_DAI_RECL_VOL(2), + ADAU1373_DAI_RECR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv), + + SOC_DOUBLE_R_TLV("ADC Capture Volume", ADAU1373_ADC_RECL_VOL, + ADAU1373_ADC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv), + SOC_DOUBLE_R_TLV("DMIC Capture Volume", ADAU1373_DMIC_RECL_VOL, + ADAU1373_DMIC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv), + + SOC_DOUBLE_R_TLV("AIF1 Playback Volume", ADAU1373_DAI_PBL_VOL(0), + ADAU1373_DAI_PBR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv), + SOC_DOUBLE_R_TLV("AIF2 Playback Volume", ADAU1373_DAI_PBL_VOL(1), + ADAU1373_DAI_PBR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv), + SOC_DOUBLE_R_TLV("AIF3 Playback Volume", ADAU1373_DAI_PBL_VOL(2), + ADAU1373_DAI_PBR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv), + + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", ADAU1373_DAC1_PBL_VOL, + ADAU1373_DAC1_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv), + SOC_DOUBLE_R_TLV("DAC2 Playback Volume", ADAU1373_DAC2_PBL_VOL, + ADAU1373_DAC2_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv), + + SOC_DOUBLE_R_TLV("Lineout1 Playback Volume", ADAU1373_LLINE_OUT(0), + ADAU1373_RLINE_OUT(0), 0, 0x1f, 0, adau1373_out_tlv), + SOC_DOUBLE_R_TLV("Speaker Playback Volume", ADAU1373_LSPK_OUT, + ADAU1373_RSPK_OUT, 0, 0x1f, 0, adau1373_out_tlv), + SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1373_LHP_OUT, + ADAU1373_RHP_OUT, 0, 0x1f, 0, adau1373_out_tlv), + + SOC_DOUBLE_R_TLV("Input 1 Capture Volume", ADAU1373_AINL_CTRL(0), + ADAU1373_AINR_CTRL(0), 0, 0x1f, 0, adau1373_in_pga_tlv), + SOC_DOUBLE_R_TLV("Input 2 Capture Volume", ADAU1373_AINL_CTRL(1), + ADAU1373_AINR_CTRL(1), 0, 0x1f, 0, adau1373_in_pga_tlv), + SOC_DOUBLE_R_TLV("Input 3 Capture Volume", ADAU1373_AINL_CTRL(2), + ADAU1373_AINR_CTRL(2), 0, 0x1f, 0, adau1373_in_pga_tlv), + SOC_DOUBLE_R_TLV("Input 4 Capture Volume", ADAU1373_AINL_CTRL(3), + ADAU1373_AINR_CTRL(3), 0, 0x1f, 0, adau1373_in_pga_tlv), + + SOC_SINGLE_TLV("Earpiece Playback Volume", ADAU1373_EP_CTRL, 0, 3, 0, + adau1373_ep_tlv), + + SOC_DOUBLE_TLV("AIF3 Boost Playback Volume", ADAU1373_VOL_GAIN1, 4, 5, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("AIF2 Boost Playback Volume", ADAU1373_VOL_GAIN1, 2, 3, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("AIF1 Boost Playback Volume", ADAU1373_VOL_GAIN1, 0, 1, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("AIF3 Boost Capture Volume", ADAU1373_VOL_GAIN2, 4, 5, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("AIF2 Boost Capture Volume", ADAU1373_VOL_GAIN2, 2, 3, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("AIF1 Boost Capture Volume", ADAU1373_VOL_GAIN2, 0, 1, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("DMIC Boost Capture Volume", ADAU1373_VOL_GAIN3, 6, 7, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("ADC Boost Capture Volume", ADAU1373_VOL_GAIN3, 4, 5, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("DAC2 Boost Playback Volume", ADAU1373_VOL_GAIN3, 2, 3, + 1, 0, adau1373_gain_boost_tlv), + SOC_DOUBLE_TLV("DAC1 Boost Playback Volume", ADAU1373_VOL_GAIN3, 0, 1, + 1, 0, adau1373_gain_boost_tlv), + + SOC_DOUBLE_TLV("Input 1 Boost Capture Volume", ADAU1373_ADC_GAIN, 0, 4, + 1, 0, adau1373_input_boost_tlv), + SOC_DOUBLE_TLV("Input 2 Boost Capture Volume", ADAU1373_ADC_GAIN, 1, 5, + 1, 0, adau1373_input_boost_tlv), + SOC_DOUBLE_TLV("Input 3 Boost Capture Volume", ADAU1373_ADC_GAIN, 2, 6, + 1, 0, adau1373_input_boost_tlv), + SOC_DOUBLE_TLV("Input 4 Boost Capture Volume", ADAU1373_ADC_GAIN, 3, 7, + 1, 0, adau1373_input_boost_tlv), + + SOC_DOUBLE_TLV("Speaker Boost Playback Volume", ADAU1373_LS_CTRL, 2, 3, + 1, 0, adau1373_speaker_boost_tlv), + + SOC_ENUM("Lineout1 LR Mux", adau1373_lineout1_lr_mux_enum), + SOC_ENUM("Speaker LR Mux", adau1373_speaker_lr_mux_enum), + + SOC_ENUM("HPF Cutoff", adau1373_hpf_cutoff_enum), + SOC_DOUBLE("HPF Switch", ADAU1373_HPF_CTRL, 1, 0, 1, 0), + SOC_ENUM("HPF Channel", adau1373_hpf_channel_enum), + + SOC_ENUM("Bass HPF Cutoff", adau1373_bass_hpf_cutoff_enum), + SOC_ENUM("Bass Clip Level Threshold", adau1373_bass_clip_level_enum), + SOC_ENUM("Bass LPF Cutoff", adau1373_bass_lpf_cutoff_enum), + SOC_DOUBLE("Bass Playback Switch", ADAU1373_BASS2, 0, 1, 1, 0), + SOC_SINGLE_TLV("Bass Playback Volume", ADAU1373_BASS2, 2, 7, 0, + adau1373_bass_tlv), + SOC_ENUM("Bass Channel", adau1373_bass_channel_enum), + + SOC_ENUM("3D Freq", adau1373_3d_cutoff_enum), + SOC_ENUM("3D Level", adau1373_3d_level_enum), + SOC_SINGLE("3D Playback Switch", ADAU1373_3D_CTRL2, 0, 1, 0), + SOC_SINGLE_TLV("3D Playback Volume", ADAU1373_3D_CTRL2, 2, 7, 0, + adau1373_3d_tlv), + SOC_ENUM("3D Channel", adau1373_bass_channel_enum), + + SOC_SINGLE("Zero Cross Switch", ADAU1373_PWDN_CTRL3, 7, 1, 0), +}; + +static const struct snd_kcontrol_new adau1373_lineout2_controls[] = { + SOC_DOUBLE_R_TLV("Lineout2 Playback Volume", ADAU1373_LLINE_OUT(1), + ADAU1373_RLINE_OUT(1), 0, 0x1f, 0, adau1373_out_tlv), + SOC_ENUM("Lineout2 LR Mux", adau1373_lineout2_lr_mux_enum), +}; + +static const struct snd_kcontrol_new adau1373_drc_controls[] = { + SOC_ENUM("DRC1 Channel", adau1373_drc1_channel_enum), + SOC_ENUM("DRC2 Channel", adau1373_drc2_channel_enum), + SOC_ENUM("DRC3 Channel", adau1373_drc3_channel_enum), +}; + +static int adau1373_pll_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + unsigned int pll_id = w->name[3] - '1'; + unsigned int val; + + if (SND_SOC_DAPM_EVENT_ON(event)) + val = ADAU1373_PLL_CTRL6_PLL_EN; + else + val = 0; + + regmap_update_bits(adau1373->regmap, ADAU1373_PLL_CTRL6(pll_id), + ADAU1373_PLL_CTRL6_PLL_EN, val); + + if (SND_SOC_DAPM_EVENT_ON(event)) + mdelay(5); + + return 0; +} + +static const char *adau1373_decimator_text[] = { + "ADC", + "DMIC1", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(adau1373_decimator_enum, + adau1373_decimator_text); + +static const struct snd_kcontrol_new adau1373_decimator_mux = + SOC_DAPM_ENUM("Decimator Mux", adau1373_decimator_enum); + +static const struct snd_kcontrol_new adau1373_left_adc_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_LADC_MIXER, 4, 1, 0), + SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_LADC_MIXER, 3, 1, 0), + SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_LADC_MIXER, 2, 1, 0), + SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_LADC_MIXER, 1, 1, 0), + SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_LADC_MIXER, 0, 1, 0), +}; + +static const struct snd_kcontrol_new adau1373_right_adc_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_RADC_MIXER, 4, 1, 0), + SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_RADC_MIXER, 3, 1, 0), + SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_RADC_MIXER, 2, 1, 0), + SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_RADC_MIXER, 1, 1, 0), + SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_RADC_MIXER, 0, 1, 0), +}; + +#define DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(_name, _reg) \ +const struct snd_kcontrol_new _name[] = { \ + SOC_DAPM_SINGLE("Left DAC2 Switch", _reg, 7, 1, 0), \ + SOC_DAPM_SINGLE("Right DAC2 Switch", _reg, 6, 1, 0), \ + SOC_DAPM_SINGLE("Left DAC1 Switch", _reg, 5, 1, 0), \ + SOC_DAPM_SINGLE("Right DAC1 Switch", _reg, 4, 1, 0), \ + SOC_DAPM_SINGLE("Input 4 Bypass Switch", _reg, 3, 1, 0), \ + SOC_DAPM_SINGLE("Input 3 Bypass Switch", _reg, 2, 1, 0), \ + SOC_DAPM_SINGLE("Input 2 Bypass Switch", _reg, 1, 1, 0), \ + SOC_DAPM_SINGLE("Input 1 Bypass Switch", _reg, 0, 1, 0), \ +} + +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line1_mixer_controls, + ADAU1373_LLINE1_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line1_mixer_controls, + ADAU1373_RLINE1_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line2_mixer_controls, + ADAU1373_LLINE2_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line2_mixer_controls, + ADAU1373_RLINE2_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_spk_mixer_controls, + ADAU1373_LSPK_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_spk_mixer_controls, + ADAU1373_RSPK_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_ep_mixer_controls, + ADAU1373_EP_MIX); + +static const struct snd_kcontrol_new adau1373_left_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", ADAU1373_LHP_MIX, 5, 1, 0), + SOC_DAPM_SINGLE("Left DAC2 Switch", ADAU1373_LHP_MIX, 4, 1, 0), + SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_LHP_MIX, 3, 1, 0), + SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_LHP_MIX, 2, 1, 0), + SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_LHP_MIX, 1, 1, 0), + SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_LHP_MIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new adau1373_right_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Right DAC1 Switch", ADAU1373_RHP_MIX, 5, 1, 0), + SOC_DAPM_SINGLE("Right DAC2 Switch", ADAU1373_RHP_MIX, 4, 1, 0), + SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_RHP_MIX, 3, 1, 0), + SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_RHP_MIX, 2, 1, 0), + SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_RHP_MIX, 1, 1, 0), + SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_RHP_MIX, 0, 1, 0), +}; + +#define DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(_name, _reg) \ +const struct snd_kcontrol_new _name[] = { \ + SOC_DAPM_SINGLE("DMIC2 Swapped Switch", _reg, 6, 1, 0), \ + SOC_DAPM_SINGLE("DMIC2 Switch", _reg, 5, 1, 0), \ + SOC_DAPM_SINGLE("ADC/DMIC1 Swapped Switch", _reg, 4, 1, 0), \ + SOC_DAPM_SINGLE("ADC/DMIC1 Switch", _reg, 3, 1, 0), \ + SOC_DAPM_SINGLE("AIF3 Switch", _reg, 2, 1, 0), \ + SOC_DAPM_SINGLE("AIF2 Switch", _reg, 1, 1, 0), \ + SOC_DAPM_SINGLE("AIF1 Switch", _reg, 0, 1, 0), \ +} + +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel1_mixer_controls, + ADAU1373_DIN_MIX_CTRL(0)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel2_mixer_controls, + ADAU1373_DIN_MIX_CTRL(1)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel3_mixer_controls, + ADAU1373_DIN_MIX_CTRL(2)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel4_mixer_controls, + ADAU1373_DIN_MIX_CTRL(3)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel5_mixer_controls, + ADAU1373_DIN_MIX_CTRL(4)); + +#define DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(_name, _reg) \ +const struct snd_kcontrol_new _name[] = { \ + SOC_DAPM_SINGLE("DSP Channel5 Switch", _reg, 4, 1, 0), \ + SOC_DAPM_SINGLE("DSP Channel4 Switch", _reg, 3, 1, 0), \ + SOC_DAPM_SINGLE("DSP Channel3 Switch", _reg, 2, 1, 0), \ + SOC_DAPM_SINGLE("DSP Channel2 Switch", _reg, 1, 1, 0), \ + SOC_DAPM_SINGLE("DSP Channel1 Switch", _reg, 0, 1, 0), \ +} + +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif1_mixer_controls, + ADAU1373_DOUT_MIX_CTRL(0)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif2_mixer_controls, + ADAU1373_DOUT_MIX_CTRL(1)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif3_mixer_controls, + ADAU1373_DOUT_MIX_CTRL(2)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac1_mixer_controls, + ADAU1373_DOUT_MIX_CTRL(3)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac2_mixer_controls, + ADAU1373_DOUT_MIX_CTRL(4)); + +static const struct snd_soc_dapm_widget adau1373_dapm_widgets[] = { + /* Datasheet claims Left ADC is bit 6 and Right ADC is bit 7, but that + * doesn't seem to be the case. */ + SND_SOC_DAPM_ADC("Left ADC", NULL, ADAU1373_PWDN_CTRL1, 7, 0), + SND_SOC_DAPM_ADC("Right ADC", NULL, ADAU1373_PWDN_CTRL1, 6, 0), + + SND_SOC_DAPM_ADC("DMIC1", NULL, ADAU1373_DIGMICCTRL, 0, 0), + SND_SOC_DAPM_ADC("DMIC2", NULL, ADAU1373_DIGMICCTRL, 2, 0), + + SND_SOC_DAPM_MUX("Decimator Mux", SND_SOC_NOPM, 0, 0, + &adau1373_decimator_mux), + + SND_SOC_DAPM_SUPPLY("MICBIAS2", ADAU1373_PWDN_CTRL1, 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS1", ADAU1373_PWDN_CTRL1, 4, 0, NULL, 0), + + SND_SOC_DAPM_PGA("IN4PGA", ADAU1373_PWDN_CTRL1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("IN3PGA", ADAU1373_PWDN_CTRL1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("IN2PGA", ADAU1373_PWDN_CTRL1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("IN1PGA", ADAU1373_PWDN_CTRL1, 0, 0, NULL, 0), + + SND_SOC_DAPM_DAC("Left DAC2", NULL, ADAU1373_PWDN_CTRL2, 7, 0), + SND_SOC_DAPM_DAC("Right DAC2", NULL, ADAU1373_PWDN_CTRL2, 6, 0), + SND_SOC_DAPM_DAC("Left DAC1", NULL, ADAU1373_PWDN_CTRL2, 5, 0), + SND_SOC_DAPM_DAC("Right DAC1", NULL, ADAU1373_PWDN_CTRL2, 4, 0), + + SOC_MIXER_ARRAY("Left ADC Mixer", SND_SOC_NOPM, 0, 0, + adau1373_left_adc_mixer_controls), + SOC_MIXER_ARRAY("Right ADC Mixer", SND_SOC_NOPM, 0, 0, + adau1373_right_adc_mixer_controls), + + SOC_MIXER_ARRAY("Left Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 3, 0, + adau1373_left_line2_mixer_controls), + SOC_MIXER_ARRAY("Right Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 2, 0, + adau1373_right_line2_mixer_controls), + SOC_MIXER_ARRAY("Left Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 1, 0, + adau1373_left_line1_mixer_controls), + SOC_MIXER_ARRAY("Right Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 0, 0, + adau1373_right_line1_mixer_controls), + + SOC_MIXER_ARRAY("Earpiece Mixer", ADAU1373_PWDN_CTRL3, 4, 0, + adau1373_ep_mixer_controls), + SOC_MIXER_ARRAY("Left Speaker Mixer", ADAU1373_PWDN_CTRL3, 3, 0, + adau1373_left_spk_mixer_controls), + SOC_MIXER_ARRAY("Right Speaker Mixer", ADAU1373_PWDN_CTRL3, 2, 0, + adau1373_right_spk_mixer_controls), + SOC_MIXER_ARRAY("Left Headphone Mixer", SND_SOC_NOPM, 0, 0, + adau1373_left_hp_mixer_controls), + SOC_MIXER_ARRAY("Right Headphone Mixer", SND_SOC_NOPM, 0, 0, + adau1373_right_hp_mixer_controls), + SND_SOC_DAPM_SUPPLY("Headphone Enable", ADAU1373_PWDN_CTRL3, 1, 0, + NULL, 0), + + SND_SOC_DAPM_SUPPLY("AIF1 CLK", ADAU1373_SRC_DAI_CTRL(0), 0, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF2 CLK", ADAU1373_SRC_DAI_CTRL(1), 0, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF3 CLK", ADAU1373_SRC_DAI_CTRL(2), 0, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF1 IN SRC", ADAU1373_SRC_DAI_CTRL(0), 2, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF1 OUT SRC", ADAU1373_SRC_DAI_CTRL(0), 1, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF2 IN SRC", ADAU1373_SRC_DAI_CTRL(1), 2, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF2 OUT SRC", ADAU1373_SRC_DAI_CTRL(1), 1, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF3 IN SRC", ADAU1373_SRC_DAI_CTRL(2), 2, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF3 OUT SRC", ADAU1373_SRC_DAI_CTRL(2), 1, 0, + NULL, 0), + + SND_SOC_DAPM_AIF_IN("AIF1 IN", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1 OUT", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2 IN", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2 OUT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF3 IN", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF3 OUT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0), + + SOC_MIXER_ARRAY("DSP Channel1 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_dsp_channel1_mixer_controls), + SOC_MIXER_ARRAY("DSP Channel2 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_dsp_channel2_mixer_controls), + SOC_MIXER_ARRAY("DSP Channel3 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_dsp_channel3_mixer_controls), + SOC_MIXER_ARRAY("DSP Channel4 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_dsp_channel4_mixer_controls), + SOC_MIXER_ARRAY("DSP Channel5 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_dsp_channel5_mixer_controls), + + SOC_MIXER_ARRAY("AIF1 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_aif1_mixer_controls), + SOC_MIXER_ARRAY("AIF2 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_aif2_mixer_controls), + SOC_MIXER_ARRAY("AIF3 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_aif3_mixer_controls), + SOC_MIXER_ARRAY("DAC1 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_dac1_mixer_controls), + SOC_MIXER_ARRAY("DAC2 Mixer", SND_SOC_NOPM, 0, 0, + adau1373_dac2_mixer_controls), + + SND_SOC_DAPM_SUPPLY("DSP", ADAU1373_DIGEN, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Recording Engine B", ADAU1373_DIGEN, 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Recording Engine A", ADAU1373_DIGEN, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Playback Engine B", ADAU1373_DIGEN, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Playback Engine A", ADAU1373_DIGEN, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("PLL1", SND_SOC_NOPM, 0, 0, adau1373_pll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("PLL2", SND_SOC_NOPM, 0, 0, adau1373_pll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("SYSCLK1", ADAU1373_CLK_SRC_DIV(0), 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SYSCLK2", ADAU1373_CLK_SRC_DIV(1), 7, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R"), + SND_SOC_DAPM_INPUT("AIN4L"), + SND_SOC_DAPM_INPUT("AIN4R"), + + SND_SOC_DAPM_INPUT("DMIC1DAT"), + SND_SOC_DAPM_INPUT("DMIC2DAT"), + + SND_SOC_DAPM_OUTPUT("LOUT1L"), + SND_SOC_DAPM_OUTPUT("LOUT1R"), + SND_SOC_DAPM_OUTPUT("LOUT2L"), + SND_SOC_DAPM_OUTPUT("LOUT2R"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("EP"), +}; + +static int adau1373_check_aif_clk(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + unsigned int dai; + const char *clk; + + dai = sink->name[3] - '1'; + + if (!adau1373->dais[dai].master) + return 0; + + if (adau1373->dais[dai].clk_src == ADAU1373_CLK_SRC_PLL1) + clk = "SYSCLK1"; + else + clk = "SYSCLK2"; + + return strcmp(source->name, clk) == 0; +} + +static int adau1373_check_src(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + unsigned int dai; + + dai = sink->name[3] - '1'; + + return adau1373->dais[dai].enable_src; +} + +#define DSP_CHANNEL_MIXER_ROUTES(_sink) \ + { _sink, "DMIC2 Swapped Switch", "DMIC2" }, \ + { _sink, "DMIC2 Switch", "DMIC2" }, \ + { _sink, "ADC/DMIC1 Swapped Switch", "Decimator Mux" }, \ + { _sink, "ADC/DMIC1 Switch", "Decimator Mux" }, \ + { _sink, "AIF1 Switch", "AIF1 IN" }, \ + { _sink, "AIF2 Switch", "AIF2 IN" }, \ + { _sink, "AIF3 Switch", "AIF3 IN" } + +#define DSP_OUTPUT_MIXER_ROUTES(_sink) \ + { _sink, "DSP Channel1 Switch", "DSP Channel1 Mixer" }, \ + { _sink, "DSP Channel2 Switch", "DSP Channel2 Mixer" }, \ + { _sink, "DSP Channel3 Switch", "DSP Channel3 Mixer" }, \ + { _sink, "DSP Channel4 Switch", "DSP Channel4 Mixer" }, \ + { _sink, "DSP Channel5 Switch", "DSP Channel5 Mixer" } + +#define LEFT_OUTPUT_MIXER_ROUTES(_sink) \ + { _sink, "Right DAC2 Switch", "Right DAC2" }, \ + { _sink, "Left DAC2 Switch", "Left DAC2" }, \ + { _sink, "Right DAC1 Switch", "Right DAC1" }, \ + { _sink, "Left DAC1 Switch", "Left DAC1" }, \ + { _sink, "Input 1 Bypass Switch", "IN1PGA" }, \ + { _sink, "Input 2 Bypass Switch", "IN2PGA" }, \ + { _sink, "Input 3 Bypass Switch", "IN3PGA" }, \ + { _sink, "Input 4 Bypass Switch", "IN4PGA" } + +#define RIGHT_OUTPUT_MIXER_ROUTES(_sink) \ + { _sink, "Right DAC2 Switch", "Right DAC2" }, \ + { _sink, "Left DAC2 Switch", "Left DAC2" }, \ + { _sink, "Right DAC1 Switch", "Right DAC1" }, \ + { _sink, "Left DAC1 Switch", "Left DAC1" }, \ + { _sink, "Input 1 Bypass Switch", "IN1PGA" }, \ + { _sink, "Input 2 Bypass Switch", "IN2PGA" }, \ + { _sink, "Input 3 Bypass Switch", "IN3PGA" }, \ + { _sink, "Input 4 Bypass Switch", "IN4PGA" } + +static const struct snd_soc_dapm_route adau1373_dapm_routes[] = { + { "Left ADC Mixer", "DAC1 Switch", "Left DAC1" }, + { "Left ADC Mixer", "Input 1 Switch", "IN1PGA" }, + { "Left ADC Mixer", "Input 2 Switch", "IN2PGA" }, + { "Left ADC Mixer", "Input 3 Switch", "IN3PGA" }, + { "Left ADC Mixer", "Input 4 Switch", "IN4PGA" }, + + { "Right ADC Mixer", "DAC1 Switch", "Right DAC1" }, + { "Right ADC Mixer", "Input 1 Switch", "IN1PGA" }, + { "Right ADC Mixer", "Input 2 Switch", "IN2PGA" }, + { "Right ADC Mixer", "Input 3 Switch", "IN3PGA" }, + { "Right ADC Mixer", "Input 4 Switch", "IN4PGA" }, + + { "Left ADC", NULL, "Left ADC Mixer" }, + { "Right ADC", NULL, "Right ADC Mixer" }, + + { "Decimator Mux", "ADC", "Left ADC" }, + { "Decimator Mux", "ADC", "Right ADC" }, + { "Decimator Mux", "DMIC1", "DMIC1" }, + + DSP_CHANNEL_MIXER_ROUTES("DSP Channel1 Mixer"), + DSP_CHANNEL_MIXER_ROUTES("DSP Channel2 Mixer"), + DSP_CHANNEL_MIXER_ROUTES("DSP Channel3 Mixer"), + DSP_CHANNEL_MIXER_ROUTES("DSP Channel4 Mixer"), + DSP_CHANNEL_MIXER_ROUTES("DSP Channel5 Mixer"), + + DSP_OUTPUT_MIXER_ROUTES("AIF1 Mixer"), + DSP_OUTPUT_MIXER_ROUTES("AIF2 Mixer"), + DSP_OUTPUT_MIXER_ROUTES("AIF3 Mixer"), + DSP_OUTPUT_MIXER_ROUTES("DAC1 Mixer"), + DSP_OUTPUT_MIXER_ROUTES("DAC2 Mixer"), + + { "AIF1 OUT", NULL, "AIF1 Mixer" }, + { "AIF2 OUT", NULL, "AIF2 Mixer" }, + { "AIF3 OUT", NULL, "AIF3 Mixer" }, + { "Left DAC1", NULL, "DAC1 Mixer" }, + { "Right DAC1", NULL, "DAC1 Mixer" }, + { "Left DAC2", NULL, "DAC2 Mixer" }, + { "Right DAC2", NULL, "DAC2 Mixer" }, + + LEFT_OUTPUT_MIXER_ROUTES("Left Lineout1 Mixer"), + RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout1 Mixer"), + LEFT_OUTPUT_MIXER_ROUTES("Left Lineout2 Mixer"), + RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout2 Mixer"), + LEFT_OUTPUT_MIXER_ROUTES("Left Speaker Mixer"), + RIGHT_OUTPUT_MIXER_ROUTES("Right Speaker Mixer"), + + { "Left Headphone Mixer", "Left DAC2 Switch", "Left DAC2" }, + { "Left Headphone Mixer", "Left DAC1 Switch", "Left DAC1" }, + { "Left Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" }, + { "Left Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" }, + { "Left Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" }, + { "Left Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" }, + { "Right Headphone Mixer", "Right DAC2 Switch", "Right DAC2" }, + { "Right Headphone Mixer", "Right DAC1 Switch", "Right DAC1" }, + { "Right Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" }, + { "Right Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" }, + { "Right Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" }, + { "Right Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" }, + + { "Left Headphone Mixer", NULL, "Headphone Enable" }, + { "Right Headphone Mixer", NULL, "Headphone Enable" }, + + { "Earpiece Mixer", "Right DAC2 Switch", "Right DAC2" }, + { "Earpiece Mixer", "Left DAC2 Switch", "Left DAC2" }, + { "Earpiece Mixer", "Right DAC1 Switch", "Right DAC1" }, + { "Earpiece Mixer", "Left DAC1 Switch", "Left DAC1" }, + { "Earpiece Mixer", "Input 1 Bypass Switch", "IN1PGA" }, + { "Earpiece Mixer", "Input 2 Bypass Switch", "IN2PGA" }, + { "Earpiece Mixer", "Input 3 Bypass Switch", "IN3PGA" }, + { "Earpiece Mixer", "Input 4 Bypass Switch", "IN4PGA" }, + + { "LOUT1L", NULL, "Left Lineout1 Mixer" }, + { "LOUT1R", NULL, "Right Lineout1 Mixer" }, + { "LOUT2L", NULL, "Left Lineout2 Mixer" }, + { "LOUT2R", NULL, "Right Lineout2 Mixer" }, + { "SPKL", NULL, "Left Speaker Mixer" }, + { "SPKR", NULL, "Right Speaker Mixer" }, + { "HPL", NULL, "Left Headphone Mixer" }, + { "HPR", NULL, "Right Headphone Mixer" }, + { "EP", NULL, "Earpiece Mixer" }, + + { "IN1PGA", NULL, "AIN1L" }, + { "IN2PGA", NULL, "AIN2L" }, + { "IN3PGA", NULL, "AIN3L" }, + { "IN4PGA", NULL, "AIN4L" }, + { "IN1PGA", NULL, "AIN1R" }, + { "IN2PGA", NULL, "AIN2R" }, + { "IN3PGA", NULL, "AIN3R" }, + { "IN4PGA", NULL, "AIN4R" }, + + { "SYSCLK1", NULL, "PLL1" }, + { "SYSCLK2", NULL, "PLL2" }, + + { "Left DAC1", NULL, "SYSCLK1" }, + { "Right DAC1", NULL, "SYSCLK1" }, + { "Left DAC2", NULL, "SYSCLK1" }, + { "Right DAC2", NULL, "SYSCLK1" }, + { "Left ADC", NULL, "SYSCLK1" }, + { "Right ADC", NULL, "SYSCLK1" }, + + { "DSP", NULL, "SYSCLK1" }, + + { "AIF1 Mixer", NULL, "DSP" }, + { "AIF2 Mixer", NULL, "DSP" }, + { "AIF3 Mixer", NULL, "DSP" }, + { "DAC1 Mixer", NULL, "DSP" }, + { "DAC2 Mixer", NULL, "DSP" }, + { "DAC1 Mixer", NULL, "Playback Engine A" }, + { "DAC2 Mixer", NULL, "Playback Engine B" }, + { "Left ADC Mixer", NULL, "Recording Engine A" }, + { "Right ADC Mixer", NULL, "Recording Engine A" }, + + { "AIF1 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk }, + { "AIF2 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk }, + { "AIF3 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk }, + { "AIF1 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk }, + { "AIF2 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk }, + { "AIF3 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk }, + + { "AIF1 IN", NULL, "AIF1 CLK" }, + { "AIF1 OUT", NULL, "AIF1 CLK" }, + { "AIF2 IN", NULL, "AIF2 CLK" }, + { "AIF2 OUT", NULL, "AIF2 CLK" }, + { "AIF3 IN", NULL, "AIF3 CLK" }, + { "AIF3 OUT", NULL, "AIF3 CLK" }, + { "AIF1 IN", NULL, "AIF1 IN SRC", adau1373_check_src }, + { "AIF1 OUT", NULL, "AIF1 OUT SRC", adau1373_check_src }, + { "AIF2 IN", NULL, "AIF2 IN SRC", adau1373_check_src }, + { "AIF2 OUT", NULL, "AIF2 OUT SRC", adau1373_check_src }, + { "AIF3 IN", NULL, "AIF3 IN SRC", adau1373_check_src }, + { "AIF3 OUT", NULL, "AIF3 OUT SRC", adau1373_check_src }, + + { "DMIC1", NULL, "DMIC1DAT" }, + { "DMIC1", NULL, "SYSCLK1" }, + { "DMIC1", NULL, "Recording Engine A" }, + { "DMIC2", NULL, "DMIC2DAT" }, + { "DMIC2", NULL, "SYSCLK1" }, + { "DMIC2", NULL, "Recording Engine B" }, +}; + +static int adau1373_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id]; + unsigned int div; + unsigned int freq; + unsigned int ctrl; + + freq = adau1373_dai->sysclk; + + if (freq % params_rate(params) != 0) + return -EINVAL; + + switch (freq / params_rate(params)) { + case 1024: /* sysclk / 256 */ + div = 0; + break; + case 1536: /* 2/3 sysclk / 256 */ + div = 1; + break; + case 2048: /* 1/2 sysclk / 256 */ + div = 2; + break; + case 3072: /* 1/3 sysclk / 256 */ + div = 3; + break; + case 4096: /* 1/4 sysclk / 256 */ + div = 4; + break; + case 6144: /* 1/6 sysclk / 256 */ + div = 5; + break; + case 5632: /* 2/11 sysclk / 256 */ + div = 6; + break; + default: + return -EINVAL; + } + + adau1373_dai->enable_src = (div != 0); + + regmap_update_bits(adau1373->regmap, ADAU1373_BCLKDIV(dai->id), + ADAU1373_BCLKDIV_SR_MASK | ADAU1373_BCLKDIV_BCLK_MASK, + (div << 2) | ADAU1373_BCLKDIV_64); + + switch (params_width(params)) { + case 16: + ctrl = ADAU1373_DAI_WLEN_16; + break; + case 20: + ctrl = ADAU1373_DAI_WLEN_20; + break; + case 24: + ctrl = ADAU1373_DAI_WLEN_24; + break; + case 32: + ctrl = ADAU1373_DAI_WLEN_32; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(adau1373->regmap, ADAU1373_DAI(dai->id), + ADAU1373_DAI_WLEN_MASK, ctrl); +} + +static int adau1373_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id]; + unsigned int ctrl; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl = ADAU1373_DAI_MASTER; + adau1373_dai->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ctrl = 0; + adau1373_dai->master = false; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl |= ADAU1373_DAI_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl |= ADAU1373_DAI_FORMAT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl |= ADAU1373_DAI_FORMAT_RIGHT_J; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl |= ADAU1373_DAI_FORMAT_DSP; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl |= ADAU1373_DAI_INVERT_BCLK; + break; + case SND_SOC_DAIFMT_NB_IF: + ctrl |= ADAU1373_DAI_INVERT_LRCLK; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl |= ADAU1373_DAI_INVERT_LRCLK | ADAU1373_DAI_INVERT_BCLK; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau1373->regmap, ADAU1373_DAI(dai->id), + ~ADAU1373_DAI_WLEN_MASK, ctrl); + + return 0; +} + +static int adau1373_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(dai->component); + struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id]; + + switch (clk_id) { + case ADAU1373_CLK_SRC_PLL1: + case ADAU1373_CLK_SRC_PLL2: + break; + default: + return -EINVAL; + } + + adau1373_dai->sysclk = freq; + adau1373_dai->clk_src = clk_id; + + regmap_update_bits(adau1373->regmap, ADAU1373_BCLKDIV(dai->id), + ADAU1373_BCLKDIV_SOURCE, clk_id << 5); + + return 0; +} + +static const struct snd_soc_dai_ops adau1373_dai_ops = { + .hw_params = adau1373_hw_params, + .set_sysclk = adau1373_set_dai_sysclk, + .set_fmt = adau1373_set_dai_fmt, +}; + +#define ADAU1373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver adau1373_dai_driver[] = { + { + .id = 0, + .name = "adau1373-aif1", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ADAU1373_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ADAU1373_FORMATS, + }, + .ops = &adau1373_dai_ops, + .symmetric_rates = 1, + }, + { + .id = 1, + .name = "adau1373-aif2", + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ADAU1373_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ADAU1373_FORMATS, + }, + .ops = &adau1373_dai_ops, + .symmetric_rates = 1, + }, + { + .id = 2, + .name = "adau1373-aif3", + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ADAU1373_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ADAU1373_FORMATS, + }, + .ops = &adau1373_dai_ops, + .symmetric_rates = 1, + }, +}; + +static int adau1373_set_pll(struct snd_soc_component *component, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + unsigned int dpll_div = 0; + uint8_t pll_regs[5]; + int ret; + + switch (pll_id) { + case ADAU1373_PLL1: + case ADAU1373_PLL2: + break; + default: + return -EINVAL; + } + + switch (source) { + case ADAU1373_PLL_SRC_BCLK1: + case ADAU1373_PLL_SRC_BCLK2: + case ADAU1373_PLL_SRC_BCLK3: + case ADAU1373_PLL_SRC_LRCLK1: + case ADAU1373_PLL_SRC_LRCLK2: + case ADAU1373_PLL_SRC_LRCLK3: + case ADAU1373_PLL_SRC_MCLK1: + case ADAU1373_PLL_SRC_MCLK2: + case ADAU1373_PLL_SRC_GPIO1: + case ADAU1373_PLL_SRC_GPIO2: + case ADAU1373_PLL_SRC_GPIO3: + case ADAU1373_PLL_SRC_GPIO4: + break; + default: + return -EINVAL; + } + + if (freq_in < 7813 || freq_in > 27000000) + return -EINVAL; + + if (freq_out < 45158000 || freq_out > 49152000) + return -EINVAL; + + /* APLL input needs to be >= 8Mhz, so in case freq_in is less we use the + * DPLL to get it there. DPLL_out = (DPLL_in / div) * 1024 */ + while (freq_in < 8000000) { + freq_in *= 2; + dpll_div++; + } + + ret = adau_calc_pll_cfg(freq_in, freq_out, pll_regs); + if (ret) + return -EINVAL; + + if (dpll_div) { + dpll_div = 11 - dpll_div; + regmap_update_bits(adau1373->regmap, ADAU1373_PLL_CTRL6(pll_id), + ADAU1373_PLL_CTRL6_DPLL_BYPASS, 0); + } else { + regmap_update_bits(adau1373->regmap, ADAU1373_PLL_CTRL6(pll_id), + ADAU1373_PLL_CTRL6_DPLL_BYPASS, + ADAU1373_PLL_CTRL6_DPLL_BYPASS); + } + + regmap_write(adau1373->regmap, ADAU1373_DPLL_CTRL(pll_id), + (source << 4) | dpll_div); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), pll_regs[0]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), pll_regs[1]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), pll_regs[2]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), pll_regs[3]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), pll_regs[4]); + + /* Set sysclk to pll_rate / 4 */ + regmap_update_bits(adau1373->regmap, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09); + + return 0; +} + +static void adau1373_load_drc_settings(struct adau1373 *adau1373, + unsigned int nr, uint8_t *drc) +{ + unsigned int i; + + for (i = 0; i < ADAU1373_DRC_SIZE; ++i) + regmap_write(adau1373->regmap, ADAU1373_DRC(nr) + i, drc[i]); +} + +static bool adau1373_valid_micbias(enum adau1373_micbias_voltage micbias) +{ + switch (micbias) { + case ADAU1373_MICBIAS_2_9V: + case ADAU1373_MICBIAS_2_2V: + case ADAU1373_MICBIAS_2_6V: + case ADAU1373_MICBIAS_1_8V: + return true; + default: + break; + } + return false; +} + +static int adau1373_probe(struct snd_soc_component *component) +{ + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + struct adau1373_platform_data *pdata = component->dev->platform_data; + bool lineout_differential = false; + unsigned int val; + int i; + + if (pdata) { + if (pdata->num_drc > ARRAY_SIZE(pdata->drc_setting)) + return -EINVAL; + + if (!adau1373_valid_micbias(pdata->micbias1) || + !adau1373_valid_micbias(pdata->micbias2)) + return -EINVAL; + + for (i = 0; i < pdata->num_drc; ++i) { + adau1373_load_drc_settings(adau1373, i, + pdata->drc_setting[i]); + } + + snd_soc_add_component_controls(component, adau1373_drc_controls, + pdata->num_drc); + + val = 0; + for (i = 0; i < 4; ++i) { + if (pdata->input_differential[i]) + val |= BIT(i); + } + regmap_write(adau1373->regmap, ADAU1373_INPUT_MODE, val); + + val = 0; + if (pdata->lineout_differential) + val |= ADAU1373_OUTPUT_CTRL_LDIFF; + if (pdata->lineout_ground_sense) + val |= ADAU1373_OUTPUT_CTRL_LNFBEN; + regmap_write(adau1373->regmap, ADAU1373_OUTPUT_CTRL, val); + + lineout_differential = pdata->lineout_differential; + + regmap_write(adau1373->regmap, ADAU1373_EP_CTRL, + (pdata->micbias1 << ADAU1373_EP_CTRL_MICBIAS1_OFFSET) | + (pdata->micbias2 << ADAU1373_EP_CTRL_MICBIAS2_OFFSET)); + } + + if (!lineout_differential) { + snd_soc_add_component_controls(component, adau1373_lineout2_controls, + ARRAY_SIZE(adau1373_lineout2_controls)); + } + + regmap_write(adau1373->regmap, ADAU1373_ADC_CTRL, + ADAU1373_ADC_CTRL_RESET_FORCE | ADAU1373_ADC_CTRL_PEAK_DETECT); + + return 0; +} + +static int adau1373_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(adau1373->regmap, ADAU1373_PWDN_CTRL3, + ADAU1373_PWDN_CTRL3_PWR_EN, ADAU1373_PWDN_CTRL3_PWR_EN); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(adau1373->regmap, ADAU1373_PWDN_CTRL3, + ADAU1373_PWDN_CTRL3_PWR_EN, 0); + break; + } + return 0; +} + +static int adau1373_resume(struct snd_soc_component *component) +{ + struct adau1373 *adau1373 = snd_soc_component_get_drvdata(component); + + regcache_sync(adau1373->regmap); + + return 0; +} + +static bool adau1373_register_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1373_SOFT_RESET: + case ADAU1373_ADC_DAC_STATUS: + return true; + default: + return false; + } +} + +static const struct regmap_config adau1373_regmap_config = { + .val_bits = 8, + .reg_bits = 8, + + .volatile_reg = adau1373_register_volatile, + .max_register = ADAU1373_SOFT_RESET, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = adau1373_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1373_reg_defaults), +}; + +static const struct snd_soc_component_driver adau1373_component_driver = { + .probe = adau1373_probe, + .resume = adau1373_resume, + .set_bias_level = adau1373_set_bias_level, + .set_pll = adau1373_set_pll, + .controls = adau1373_controls, + .num_controls = ARRAY_SIZE(adau1373_controls), + .dapm_widgets = adau1373_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1373_dapm_widgets), + .dapm_routes = adau1373_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1373_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int adau1373_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adau1373 *adau1373; + int ret; + + adau1373 = devm_kzalloc(&client->dev, sizeof(*adau1373), GFP_KERNEL); + if (!adau1373) + return -ENOMEM; + + adau1373->regmap = devm_regmap_init_i2c(client, + &adau1373_regmap_config); + if (IS_ERR(adau1373->regmap)) + return PTR_ERR(adau1373->regmap); + + regmap_write(adau1373->regmap, ADAU1373_SOFT_RESET, 0x00); + + dev_set_drvdata(&client->dev, adau1373); + + ret = devm_snd_soc_register_component(&client->dev, + &adau1373_component_driver, + adau1373_dai_driver, ARRAY_SIZE(adau1373_dai_driver)); + return ret; +} + +static const struct i2c_device_id adau1373_i2c_id[] = { + { "adau1373", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1373_i2c_id); + +static struct i2c_driver adau1373_i2c_driver = { + .driver = { + .name = "adau1373", + }, + .probe = adau1373_i2c_probe, + .id_table = adau1373_i2c_id, +}; + +module_i2c_driver(adau1373_i2c_driver); + +MODULE_DESCRIPTION("ASoC ADAU1373 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1373.h b/sound/soc/codecs/adau1373.h new file mode 100644 index 000000000..56320d5e3 --- /dev/null +++ b/sound/soc/codecs/adau1373.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ADAU1373_H__ +#define __ADAU1373_H__ + +enum adau1373_pll_src { + ADAU1373_PLL_SRC_MCLK1 = 0, + ADAU1373_PLL_SRC_BCLK1 = 1, + ADAU1373_PLL_SRC_BCLK2 = 2, + ADAU1373_PLL_SRC_BCLK3 = 3, + ADAU1373_PLL_SRC_LRCLK1 = 4, + ADAU1373_PLL_SRC_LRCLK2 = 5, + ADAU1373_PLL_SRC_LRCLK3 = 6, + ADAU1373_PLL_SRC_GPIO1 = 7, + ADAU1373_PLL_SRC_GPIO2 = 8, + ADAU1373_PLL_SRC_GPIO3 = 9, + ADAU1373_PLL_SRC_GPIO4 = 10, + ADAU1373_PLL_SRC_MCLK2 = 11, +}; + +enum adau1373_pll { + ADAU1373_PLL1 = 0, + ADAU1373_PLL2 = 1, +}; + +enum adau1373_clk_src { + ADAU1373_CLK_SRC_PLL1 = 0, + ADAU1373_CLK_SRC_PLL2 = 1, +}; + +#endif diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c new file mode 100644 index 000000000..b5a61741c --- /dev/null +++ b/sound/soc/codecs/adau1701.c @@ -0,0 +1,922 @@ +/* + * Driver for ADAU1701 SigmaDSP processor + * + * Copyright 2011 Analog Devices Inc. + * Author: Lars-Peter Clausen + * based on an inital version by Cliff Cai + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sigmadsp.h" +#include "adau1701.h" + +#define ADAU1701_SAFELOAD_DATA(i) (0x0810 + (i)) +#define ADAU1701_SAFELOAD_ADDR(i) (0x0815 + (i)) + +#define ADAU1701_DSPCTRL 0x081c +#define ADAU1701_SEROCTL 0x081e +#define ADAU1701_SERICTL 0x081f + +#define ADAU1701_AUXNPOW 0x0822 +#define ADAU1701_PINCONF_0 0x0820 +#define ADAU1701_PINCONF_1 0x0821 +#define ADAU1701_AUXNPOW 0x0822 + +#define ADAU1701_OSCIPOW 0x0826 +#define ADAU1701_DACSET 0x0827 + +#define ADAU1701_MAX_REGISTER 0x0828 + +#define ADAU1701_DSPCTRL_CR (1 << 2) +#define ADAU1701_DSPCTRL_DAM (1 << 3) +#define ADAU1701_DSPCTRL_ADM (1 << 4) +#define ADAU1701_DSPCTRL_IST (1 << 5) +#define ADAU1701_DSPCTRL_SR_48 0x00 +#define ADAU1701_DSPCTRL_SR_96 0x01 +#define ADAU1701_DSPCTRL_SR_192 0x02 +#define ADAU1701_DSPCTRL_SR_MASK 0x03 + +#define ADAU1701_SEROCTL_INV_LRCLK 0x2000 +#define ADAU1701_SEROCTL_INV_BCLK 0x1000 +#define ADAU1701_SEROCTL_MASTER 0x0800 + +#define ADAU1701_SEROCTL_OBF16 0x0000 +#define ADAU1701_SEROCTL_OBF8 0x0200 +#define ADAU1701_SEROCTL_OBF4 0x0400 +#define ADAU1701_SEROCTL_OBF2 0x0600 +#define ADAU1701_SEROCTL_OBF_MASK 0x0600 + +#define ADAU1701_SEROCTL_OLF1024 0x0000 +#define ADAU1701_SEROCTL_OLF512 0x0080 +#define ADAU1701_SEROCTL_OLF256 0x0100 +#define ADAU1701_SEROCTL_OLF_MASK 0x0180 + +#define ADAU1701_SEROCTL_MSB_DEALY1 0x0000 +#define ADAU1701_SEROCTL_MSB_DEALY0 0x0004 +#define ADAU1701_SEROCTL_MSB_DEALY8 0x0008 +#define ADAU1701_SEROCTL_MSB_DEALY12 0x000c +#define ADAU1701_SEROCTL_MSB_DEALY16 0x0010 +#define ADAU1701_SEROCTL_MSB_DEALY_MASK 0x001c + +#define ADAU1701_SEROCTL_WORD_LEN_24 0x0000 +#define ADAU1701_SEROCTL_WORD_LEN_20 0x0001 +#define ADAU1701_SEROCTL_WORD_LEN_16 0x0002 +#define ADAU1701_SEROCTL_WORD_LEN_MASK 0x0003 + +#define ADAU1701_AUXNPOW_VBPD 0x40 +#define ADAU1701_AUXNPOW_VRPD 0x20 + +#define ADAU1701_SERICTL_I2S 0 +#define ADAU1701_SERICTL_LEFTJ 1 +#define ADAU1701_SERICTL_TDM 2 +#define ADAU1701_SERICTL_RIGHTJ_24 3 +#define ADAU1701_SERICTL_RIGHTJ_20 4 +#define ADAU1701_SERICTL_RIGHTJ_18 5 +#define ADAU1701_SERICTL_RIGHTJ_16 6 +#define ADAU1701_SERICTL_MODE_MASK 7 +#define ADAU1701_SERICTL_INV_BCLK BIT(3) +#define ADAU1701_SERICTL_INV_LRCLK BIT(4) + +#define ADAU1701_OSCIPOW_OPD 0x04 +#define ADAU1701_DACSET_DACINIT 1 + +#define ADAU1707_CLKDIV_UNSET (-1U) + +#define ADAU1701_FIRMWARE "adau1701.bin" + +static const char * const supply_names[] = { + "dvdd", "avdd" +}; + +struct adau1701 { + int gpio_nreset; + int gpio_pll_mode[2]; + unsigned int dai_fmt; + unsigned int pll_clkdiv; + unsigned int sysclk; + struct regmap *regmap; + struct i2c_client *client; + u8 pin_config[12]; + + struct sigmadsp *sigmadsp; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; +}; + +static const struct snd_kcontrol_new adau1701_controls[] = { + SOC_SINGLE("Master Capture Switch", ADAU1701_DSPCTRL, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget adau1701_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC0", "Playback", ADAU1701_AUXNPOW, 3, 1), + SND_SOC_DAPM_DAC("DAC1", "Playback", ADAU1701_AUXNPOW, 2, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", ADAU1701_AUXNPOW, 1, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", ADAU1701_AUXNPOW, 0, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", ADAU1701_AUXNPOW, 7, 1), + + SND_SOC_DAPM_OUTPUT("OUT0"), + SND_SOC_DAPM_OUTPUT("OUT1"), + SND_SOC_DAPM_OUTPUT("OUT2"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_INPUT("IN0"), + SND_SOC_DAPM_INPUT("IN1"), +}; + +static const struct snd_soc_dapm_route adau1701_dapm_routes[] = { + { "OUT0", NULL, "DAC0" }, + { "OUT1", NULL, "DAC1" }, + { "OUT2", NULL, "DAC2" }, + { "OUT3", NULL, "DAC3" }, + + { "ADC", NULL, "IN0" }, + { "ADC", NULL, "IN1" }, +}; + +static unsigned int adau1701_register_size(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case ADAU1701_PINCONF_0: + case ADAU1701_PINCONF_1: + return 3; + case ADAU1701_DSPCTRL: + case ADAU1701_SEROCTL: + case ADAU1701_AUXNPOW: + case ADAU1701_OSCIPOW: + case ADAU1701_DACSET: + return 2; + case ADAU1701_SERICTL: + return 1; + } + + dev_err(dev, "Unsupported register address: %d\n", reg); + return 0; +} + +static bool adau1701_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1701_DACSET: + case ADAU1701_DSPCTRL: + return true; + default: + return false; + } +} + +static int adau1701_reg_write(void *context, unsigned int reg, + unsigned int value) +{ + struct i2c_client *client = context; + unsigned int i; + unsigned int size; + uint8_t buf[5]; + int ret; + + size = adau1701_register_size(&client->dev, reg); + if (size == 0) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + for (i = size + 1; i >= 2; --i) { + buf[i] = value; + value >>= 8; + } + + ret = i2c_master_send(client, buf, size + 2); + if (ret == size + 2) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static int adau1701_reg_read(void *context, unsigned int reg, + unsigned int *value) +{ + int ret; + unsigned int i; + unsigned int size; + uint8_t send_buf[2], recv_buf[3]; + struct i2c_client *client = context; + struct i2c_msg msgs[2]; + + size = adau1701_register_size(&client->dev, reg); + if (size == 0) + return -EINVAL; + + send_buf[0] = reg >> 8; + send_buf[1] = reg & 0xff; + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(send_buf); + msgs[0].buf = send_buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = size; + msgs[1].buf = recv_buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + else if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *value = 0; + + for (i = 0; i < size; i++) { + *value <<= 8; + *value |= recv_buf[i]; + } + + return 0; +} + +static int adau1701_safeload(struct sigmadsp *sigmadsp, unsigned int addr, + const uint8_t bytes[], size_t len) +{ + struct i2c_client *client = to_i2c_client(sigmadsp->dev); + struct adau1701 *adau1701 = i2c_get_clientdata(client); + unsigned int val; + unsigned int i; + uint8_t buf[10]; + int ret; + + ret = regmap_read(adau1701->regmap, ADAU1701_DSPCTRL, &val); + if (ret) + return ret; + + if (val & ADAU1701_DSPCTRL_IST) + msleep(50); + + for (i = 0; i < len / 4; i++) { + put_unaligned_le16(ADAU1701_SAFELOAD_DATA(i), buf); + buf[2] = 0x00; + memcpy(buf + 3, bytes + i * 4, 4); + ret = i2c_master_send(client, buf, 7); + if (ret < 0) + return ret; + else if (ret != 7) + return -EIO; + + put_unaligned_le16(ADAU1701_SAFELOAD_ADDR(i), buf); + put_unaligned_le16(addr + i, buf + 2); + ret = i2c_master_send(client, buf, 4); + if (ret < 0) + return ret; + else if (ret != 4) + return -EIO; + } + + return regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL, + ADAU1701_DSPCTRL_IST, ADAU1701_DSPCTRL_IST); +} + +static const struct sigmadsp_ops adau1701_sigmadsp_ops = { + .safeload = adau1701_safeload, +}; + +static int adau1701_reset(struct snd_soc_component *component, unsigned int clkdiv, + unsigned int rate) +{ + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + int ret; + + sigmadsp_reset(adau1701->sigmadsp); + + if (clkdiv != ADAU1707_CLKDIV_UNSET && + gpio_is_valid(adau1701->gpio_pll_mode[0]) && + gpio_is_valid(adau1701->gpio_pll_mode[1])) { + switch (clkdiv) { + case 64: + gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 0); + gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 0); + break; + case 256: + gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 0); + gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 1); + break; + case 384: + gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 1); + gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 0); + break; + case 0: /* fallback */ + case 512: + gpio_set_value_cansleep(adau1701->gpio_pll_mode[0], 1); + gpio_set_value_cansleep(adau1701->gpio_pll_mode[1], 1); + break; + } + } + + adau1701->pll_clkdiv = clkdiv; + + if (gpio_is_valid(adau1701->gpio_nreset)) { + gpio_set_value_cansleep(adau1701->gpio_nreset, 0); + /* minimum reset time is 20ns */ + udelay(1); + gpio_set_value_cansleep(adau1701->gpio_nreset, 1); + /* power-up time may be as long as 85ms */ + mdelay(85); + } + + /* + * Postpone the firmware download to a point in time when we + * know the correct PLL setup + */ + if (clkdiv != ADAU1707_CLKDIV_UNSET) { + ret = sigmadsp_setup(adau1701->sigmadsp, rate); + if (ret) { + dev_warn(component->dev, "Failed to load firmware\n"); + return ret; + } + } + + regmap_write(adau1701->regmap, ADAU1701_DACSET, ADAU1701_DACSET_DACINIT); + regmap_write(adau1701->regmap, ADAU1701_DSPCTRL, ADAU1701_DSPCTRL_CR); + + regcache_mark_dirty(adau1701->regmap); + regcache_sync(adau1701->regmap); + + return 0; +} + +static int adau1701_set_capture_pcm_format(struct snd_soc_component *component, + struct snd_pcm_hw_params *params) +{ + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + unsigned int mask = ADAU1701_SEROCTL_WORD_LEN_MASK; + unsigned int val; + + switch (params_width(params)) { + case 16: + val = ADAU1701_SEROCTL_WORD_LEN_16; + break; + case 20: + val = ADAU1701_SEROCTL_WORD_LEN_20; + break; + case 24: + val = ADAU1701_SEROCTL_WORD_LEN_24; + break; + default: + return -EINVAL; + } + + if (adau1701->dai_fmt == SND_SOC_DAIFMT_RIGHT_J) { + switch (params_width(params)) { + case 16: + val |= ADAU1701_SEROCTL_MSB_DEALY16; + break; + case 20: + val |= ADAU1701_SEROCTL_MSB_DEALY12; + break; + case 24: + val |= ADAU1701_SEROCTL_MSB_DEALY8; + break; + } + mask |= ADAU1701_SEROCTL_MSB_DEALY_MASK; + } + + regmap_update_bits(adau1701->regmap, ADAU1701_SEROCTL, mask, val); + + return 0; +} + +static int adau1701_set_playback_pcm_format(struct snd_soc_component *component, + struct snd_pcm_hw_params *params) +{ + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (adau1701->dai_fmt != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (params_width(params)) { + case 16: + val = ADAU1701_SERICTL_RIGHTJ_16; + break; + case 20: + val = ADAU1701_SERICTL_RIGHTJ_20; + break; + case 24: + val = ADAU1701_SERICTL_RIGHTJ_24; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau1701->regmap, ADAU1701_SERICTL, + ADAU1701_SERICTL_MODE_MASK, val); + + return 0; +} + +static int adau1701_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + unsigned int clkdiv = adau1701->sysclk / params_rate(params); + unsigned int val; + int ret; + + /* + * If the mclk/lrclk ratio changes, the chip needs updated PLL + * mode GPIO settings, and a full reset cycle, including a new + * firmware upload. + */ + if (clkdiv != adau1701->pll_clkdiv) { + ret = adau1701_reset(component, clkdiv, params_rate(params)); + if (ret < 0) + return ret; + } + + switch (params_rate(params)) { + case 192000: + val = ADAU1701_DSPCTRL_SR_192; + break; + case 96000: + val = ADAU1701_DSPCTRL_SR_96; + break; + case 48000: + val = ADAU1701_DSPCTRL_SR_48; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL, + ADAU1701_DSPCTRL_SR_MASK, val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return adau1701_set_playback_pcm_format(component, params); + else + return adau1701_set_capture_pcm_format(component, params); +} + +static int adau1701_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + unsigned int serictl = 0x00, seroctl = 0x00; + bool invert_lrclk; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* master, 64-bits per sample, 1 frame per sample */ + seroctl |= ADAU1701_SEROCTL_MASTER | ADAU1701_SEROCTL_OBF16 + | ADAU1701_SEROCTL_OLF1024; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_lrclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + invert_lrclk = true; + break; + case SND_SOC_DAIFMT_IB_NF: + invert_lrclk = false; + serictl |= ADAU1701_SERICTL_INV_BCLK; + seroctl |= ADAU1701_SEROCTL_INV_BCLK; + break; + case SND_SOC_DAIFMT_IB_IF: + invert_lrclk = true; + serictl |= ADAU1701_SERICTL_INV_BCLK; + seroctl |= ADAU1701_SEROCTL_INV_BCLK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + serictl |= ADAU1701_SERICTL_LEFTJ; + seroctl |= ADAU1701_SEROCTL_MSB_DEALY0; + invert_lrclk = !invert_lrclk; + break; + case SND_SOC_DAIFMT_RIGHT_J: + serictl |= ADAU1701_SERICTL_RIGHTJ_24; + seroctl |= ADAU1701_SEROCTL_MSB_DEALY8; + invert_lrclk = !invert_lrclk; + break; + default: + return -EINVAL; + } + + if (invert_lrclk) { + seroctl |= ADAU1701_SEROCTL_INV_LRCLK; + serictl |= ADAU1701_SERICTL_INV_LRCLK; + } + + adau1701->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + regmap_write(adau1701->regmap, ADAU1701_SERICTL, serictl); + regmap_update_bits(adau1701->regmap, ADAU1701_SEROCTL, + ~ADAU1701_SEROCTL_WORD_LEN_MASK, seroctl); + + return 0; +} + +static int adau1701_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + unsigned int mask = ADAU1701_AUXNPOW_VBPD | ADAU1701_AUXNPOW_VRPD; + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* Enable VREF and VREF buffer */ + regmap_update_bits(adau1701->regmap, + ADAU1701_AUXNPOW, mask, 0x00); + break; + case SND_SOC_BIAS_OFF: + /* Disable VREF and VREF buffer */ + regmap_update_bits(adau1701->regmap, + ADAU1701_AUXNPOW, mask, mask); + break; + } + + return 0; +} + +static int adau1701_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + unsigned int mask = ADAU1701_DSPCTRL_DAM; + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (mute) + val = 0; + else + val = mask; + + regmap_update_bits(adau1701->regmap, ADAU1701_DSPCTRL, mask, val); + + return 0; +} + +static int adau1701_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + unsigned int val; + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case ADAU1701_CLK_SRC_OSC: + val = 0x0; + break; + case ADAU1701_CLK_SRC_MCLK: + val = ADAU1701_OSCIPOW_OPD; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau1701->regmap, ADAU1701_OSCIPOW, + ADAU1701_OSCIPOW_OPD, val); + adau1701->sysclk = freq; + + return 0; +} + +static int adau1701_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(dai->component); + + return sigmadsp_restrict_params(adau1701->sigmadsp, substream); +} + +#define ADAU1701_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + +#define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops adau1701_dai_ops = { + .set_fmt = adau1701_set_dai_fmt, + .hw_params = adau1701_hw_params, + .digital_mute = adau1701_digital_mute, + .startup = adau1701_startup, +}; + +static struct snd_soc_dai_driver adau1701_dai = { + .name = "adau1701", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = ADAU1701_RATES, + .formats = ADAU1701_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = ADAU1701_RATES, + .formats = ADAU1701_FORMATS, + }, + .ops = &adau1701_dai_ops, + .symmetric_rates = 1, +}; + +#ifdef CONFIG_OF +static const struct of_device_id adau1701_dt_ids[] = { + { .compatible = "adi,adau1701", }, + { } +}; +MODULE_DEVICE_TABLE(of, adau1701_dt_ids); +#endif + +static int adau1701_probe(struct snd_soc_component *component) +{ + int i, ret; + unsigned int val; + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + + ret = sigmadsp_attach(adau1701->sigmadsp, component); + if (ret) + return ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(adau1701->supplies), + adau1701->supplies); + if (ret < 0) { + dev_err(component->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + /* + * Let the pll_clkdiv variable default to something that won't happen + * at runtime. That way, we can postpone the firmware download from + * adau1701_reset() to a point in time when we know the correct PLL + * mode parameters. + */ + adau1701->pll_clkdiv = ADAU1707_CLKDIV_UNSET; + + /* initalize with pre-configured pll mode settings */ + ret = adau1701_reset(component, adau1701->pll_clkdiv, 0); + if (ret < 0) + goto exit_regulators_disable; + + /* set up pin config */ + val = 0; + for (i = 0; i < 6; i++) + val |= adau1701->pin_config[i] << (i * 4); + + regmap_write(adau1701->regmap, ADAU1701_PINCONF_0, val); + + val = 0; + for (i = 0; i < 6; i++) + val |= adau1701->pin_config[i + 6] << (i * 4); + + regmap_write(adau1701->regmap, ADAU1701_PINCONF_1, val); + + return 0; + +exit_regulators_disable: + + regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies), adau1701->supplies); + return ret; +} + +static void adau1701_remove(struct snd_soc_component *component) +{ + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + + if (gpio_is_valid(adau1701->gpio_nreset)) + gpio_set_value_cansleep(adau1701->gpio_nreset, 0); + + regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies), adau1701->supplies); +} + +#ifdef CONFIG_PM +static int adau1701_suspend(struct snd_soc_component *component) +{ + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + + regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies), + adau1701->supplies); + + return 0; +} + +static int adau1701_resume(struct snd_soc_component *component) +{ + struct adau1701 *adau1701 = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(adau1701->supplies), + adau1701->supplies); + if (ret < 0) { + dev_err(component->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + return adau1701_reset(component, adau1701->pll_clkdiv, 0); +} +#else +#define adau1701_resume NULL +#define adau1701_suspend NULL +#endif /* CONFIG_PM */ + +static const struct snd_soc_component_driver adau1701_component_drv = { + .probe = adau1701_probe, + .remove = adau1701_remove, + .resume = adau1701_resume, + .suspend = adau1701_suspend, + .set_bias_level = adau1701_set_bias_level, + .controls = adau1701_controls, + .num_controls = ARRAY_SIZE(adau1701_controls), + .dapm_widgets = adau1701_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1701_dapm_widgets), + .dapm_routes = adau1701_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1701_dapm_routes), + .set_sysclk = adau1701_set_sysclk, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config adau1701_regmap = { + .reg_bits = 16, + .val_bits = 32, + .max_register = ADAU1701_MAX_REGISTER, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = adau1701_volatile_reg, + .reg_write = adau1701_reg_write, + .reg_read = adau1701_reg_read, +}; + +static int adau1701_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adau1701 *adau1701; + struct device *dev = &client->dev; + int gpio_nreset = -EINVAL; + int gpio_pll_mode[2] = { -EINVAL, -EINVAL }; + int ret, i; + + adau1701 = devm_kzalloc(dev, sizeof(*adau1701), GFP_KERNEL); + if (!adau1701) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + adau1701->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(adau1701->supplies), + adau1701->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(adau1701->supplies), + adau1701->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + adau1701->client = client; + adau1701->regmap = devm_regmap_init(dev, NULL, client, + &adau1701_regmap); + if (IS_ERR(adau1701->regmap)) { + ret = PTR_ERR(adau1701->regmap); + goto exit_regulators_disable; + } + + + if (dev->of_node) { + gpio_nreset = of_get_named_gpio(dev->of_node, "reset-gpio", 0); + if (gpio_nreset < 0 && gpio_nreset != -ENOENT) { + ret = gpio_nreset; + goto exit_regulators_disable; + } + + gpio_pll_mode[0] = of_get_named_gpio(dev->of_node, + "adi,pll-mode-gpios", 0); + if (gpio_pll_mode[0] < 0 && gpio_pll_mode[0] != -ENOENT) { + ret = gpio_pll_mode[0]; + goto exit_regulators_disable; + } + + gpio_pll_mode[1] = of_get_named_gpio(dev->of_node, + "adi,pll-mode-gpios", 1); + if (gpio_pll_mode[1] < 0 && gpio_pll_mode[1] != -ENOENT) { + ret = gpio_pll_mode[1]; + goto exit_regulators_disable; + } + + of_property_read_u32(dev->of_node, "adi,pll-clkdiv", + &adau1701->pll_clkdiv); + + of_property_read_u8_array(dev->of_node, "adi,pin-config", + adau1701->pin_config, + ARRAY_SIZE(adau1701->pin_config)); + } + + if (gpio_is_valid(gpio_nreset)) { + ret = devm_gpio_request_one(dev, gpio_nreset, GPIOF_OUT_INIT_LOW, + "ADAU1701 Reset"); + if (ret < 0) + goto exit_regulators_disable; + } + + if (gpio_is_valid(gpio_pll_mode[0]) && + gpio_is_valid(gpio_pll_mode[1])) { + ret = devm_gpio_request_one(dev, gpio_pll_mode[0], + GPIOF_OUT_INIT_LOW, + "ADAU1701 PLL mode 0"); + if (ret < 0) + goto exit_regulators_disable; + + ret = devm_gpio_request_one(dev, gpio_pll_mode[1], + GPIOF_OUT_INIT_LOW, + "ADAU1701 PLL mode 1"); + if (ret < 0) + goto exit_regulators_disable; + } + + adau1701->gpio_nreset = gpio_nreset; + adau1701->gpio_pll_mode[0] = gpio_pll_mode[0]; + adau1701->gpio_pll_mode[1] = gpio_pll_mode[1]; + + i2c_set_clientdata(client, adau1701); + + adau1701->sigmadsp = devm_sigmadsp_init_i2c(client, + &adau1701_sigmadsp_ops, ADAU1701_FIRMWARE); + if (IS_ERR(adau1701->sigmadsp)) { + ret = PTR_ERR(adau1701->sigmadsp); + goto exit_regulators_disable; + } + + ret = devm_snd_soc_register_component(&client->dev, + &adau1701_component_drv, + &adau1701_dai, 1); + +exit_regulators_disable: + + regulator_bulk_disable(ARRAY_SIZE(adau1701->supplies), adau1701->supplies); + return ret; +} + +static const struct i2c_device_id adau1701_i2c_id[] = { + { "adau1401", 0 }, + { "adau1401a", 0 }, + { "adau1701", 0 }, + { "adau1702", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1701_i2c_id); + +static struct i2c_driver adau1701_i2c_driver = { + .driver = { + .name = "adau1701", + .of_match_table = of_match_ptr(adau1701_dt_ids), + }, + .probe = adau1701_i2c_probe, + .id_table = adau1701_i2c_id, +}; + +module_i2c_driver(adau1701_i2c_driver); + +MODULE_DESCRIPTION("ASoC ADAU1701 SigmaDSP driver"); +MODULE_AUTHOR("Cliff Cai "); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1701.h b/sound/soc/codecs/adau1701.h new file mode 100644 index 000000000..8d0949a2a --- /dev/null +++ b/sound/soc/codecs/adau1701.h @@ -0,0 +1,17 @@ +/* + * header file for ADAU1701 SigmaDSP processor + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADAU1701_H +#define _ADAU1701_H + +enum adau1701_clk_src { + ADAU1701_CLK_SRC_OSC, + ADAU1701_CLK_SRC_MCLK, +}; + +#endif diff --git a/sound/soc/codecs/adau1761-i2c.c b/sound/soc/codecs/adau1761-i2c.c new file mode 100644 index 000000000..9e7f257f1 --- /dev/null +++ b/sound/soc/codecs/adau1761-i2c.c @@ -0,0 +1,71 @@ +/* + * Driver for ADAU1361/ADAU1461/ADAU1761/ADAU1961 codec + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include "adau1761.h" + +static int adau1761_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap_config config; + + config = adau1761_regmap_config; + config.val_bits = 8; + config.reg_bits = 16; + + return adau1761_probe(&client->dev, + devm_regmap_init_i2c(client, &config), + id->driver_data, NULL); +} + +static int adau1761_i2c_remove(struct i2c_client *client) +{ + adau17x1_remove(&client->dev); + return 0; +} + +static const struct i2c_device_id adau1761_i2c_ids[] = { + { "adau1361", ADAU1361 }, + { "adau1461", ADAU1761 }, + { "adau1761", ADAU1761 }, + { "adau1961", ADAU1361 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1761_i2c_ids); + +#if defined(CONFIG_OF) +static const struct of_device_id adau1761_i2c_dt_ids[] = { + { .compatible = "adi,adau1361", }, + { .compatible = "adi,adau1461", }, + { .compatible = "adi,adau1761", }, + { .compatible = "adi,adau1961", }, + { }, +}; +MODULE_DEVICE_TABLE(of, adau1761_i2c_dt_ids); +#endif + +static struct i2c_driver adau1761_i2c_driver = { + .driver = { + .name = "adau1761", + .of_match_table = of_match_ptr(adau1761_i2c_dt_ids), + }, + .probe = adau1761_i2c_probe, + .remove = adau1761_i2c_remove, + .id_table = adau1761_i2c_ids, +}; +module_i2c_driver(adau1761_i2c_driver); + +MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC I2C driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1761-spi.c b/sound/soc/codecs/adau1761-spi.c new file mode 100644 index 000000000..a0b214be7 --- /dev/null +++ b/sound/soc/codecs/adau1761-spi.c @@ -0,0 +1,88 @@ +/* + * Driver for ADAU1361/ADAU1461/ADAU1761/ADAU1961 codec + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include "adau1761.h" + +static void adau1761_spi_switch_mode(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + /* + * To get the device into SPI mode CLATCH has to be pulled low three + * times. Do this by issuing three dummy reads. + */ + spi_w8r8(spi, 0x00); + spi_w8r8(spi, 0x00); + spi_w8r8(spi, 0x00); +} + +static int adau1761_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap_config config; + + if (!id) + return -EINVAL; + + config = adau1761_regmap_config; + config.val_bits = 8; + config.reg_bits = 24; + config.read_flag_mask = 0x1; + + return adau1761_probe(&spi->dev, + devm_regmap_init_spi(spi, &config), + id->driver_data, adau1761_spi_switch_mode); +} + +static int adau1761_spi_remove(struct spi_device *spi) +{ + adau17x1_remove(&spi->dev); + return 0; +} + +static const struct spi_device_id adau1761_spi_id[] = { + { "adau1361", ADAU1361 }, + { "adau1461", ADAU1761 }, + { "adau1761", ADAU1761 }, + { "adau1961", ADAU1361 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adau1761_spi_id); + +#if defined(CONFIG_OF) +static const struct of_device_id adau1761_spi_dt_ids[] = { + { .compatible = "adi,adau1361", }, + { .compatible = "adi,adau1461", }, + { .compatible = "adi,adau1761", }, + { .compatible = "adi,adau1961", }, + { }, +}; +MODULE_DEVICE_TABLE(of, adau1761_spi_dt_ids); +#endif + +static struct spi_driver adau1761_spi_driver = { + .driver = { + .name = "adau1761", + .of_match_table = of_match_ptr(adau1761_spi_dt_ids), + }, + .probe = adau1761_spi_probe, + .remove = adau1761_spi_remove, + .id_table = adau1761_spi_id, +}; +module_spi_driver(adau1761_spi_driver); + +MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC SPI driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1761.c b/sound/soc/codecs/adau1761.c new file mode 100644 index 000000000..be136e981 --- /dev/null +++ b/sound/soc/codecs/adau1761.c @@ -0,0 +1,817 @@ +/* + * Driver for ADAU1361/ADAU1461/ADAU1761/ADAU1961 codec + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adau17x1.h" +#include "adau1761.h" + +#define ADAU1761_DIGMIC_JACKDETECT 0x4008 +#define ADAU1761_REC_MIXER_LEFT0 0x400a +#define ADAU1761_REC_MIXER_LEFT1 0x400b +#define ADAU1761_REC_MIXER_RIGHT0 0x400c +#define ADAU1761_REC_MIXER_RIGHT1 0x400d +#define ADAU1761_LEFT_DIFF_INPUT_VOL 0x400e +#define ADAU1761_RIGHT_DIFF_INPUT_VOL 0x400f +#define ADAU1761_PLAY_LR_MIXER_LEFT 0x4020 +#define ADAU1761_PLAY_MIXER_LEFT0 0x401c +#define ADAU1761_PLAY_MIXER_LEFT1 0x401d +#define ADAU1761_PLAY_MIXER_RIGHT0 0x401e +#define ADAU1761_PLAY_MIXER_RIGHT1 0x401f +#define ADAU1761_PLAY_LR_MIXER_RIGHT 0x4021 +#define ADAU1761_PLAY_MIXER_MONO 0x4022 +#define ADAU1761_PLAY_HP_LEFT_VOL 0x4023 +#define ADAU1761_PLAY_HP_RIGHT_VOL 0x4024 +#define ADAU1761_PLAY_LINE_LEFT_VOL 0x4025 +#define ADAU1761_PLAY_LINE_RIGHT_VOL 0x4026 +#define ADAU1761_PLAY_MONO_OUTPUT_VOL 0x4027 +#define ADAU1761_POP_CLICK_SUPPRESS 0x4028 +#define ADAU1761_JACK_DETECT_PIN 0x4031 +#define ADAU1761_DEJITTER 0x4036 +#define ADAU1761_CLK_ENABLE0 0x40f9 +#define ADAU1761_CLK_ENABLE1 0x40fa + +#define ADAU1761_DIGMIC_JACKDETECT_ACTIVE_LOW BIT(0) +#define ADAU1761_DIGMIC_JACKDETECT_DIGMIC BIT(5) + +#define ADAU1761_DIFF_INPUT_VOL_LDEN BIT(0) + +#define ADAU1761_PLAY_MONO_OUTPUT_VOL_MODE_HP BIT(0) +#define ADAU1761_PLAY_MONO_OUTPUT_VOL_UNMUTE BIT(1) + +#define ADAU1761_PLAY_HP_RIGHT_VOL_MODE_HP BIT(0) + +#define ADAU1761_PLAY_LINE_LEFT_VOL_MODE_HP BIT(0) + +#define ADAU1761_PLAY_LINE_RIGHT_VOL_MODE_HP BIT(0) + + +#define ADAU1761_FIRMWARE "adau1761.bin" + +static const struct reg_default adau1761_reg_defaults[] = { + { ADAU1761_DEJITTER, 0x03 }, + { ADAU1761_DIGMIC_JACKDETECT, 0x00 }, + { ADAU1761_REC_MIXER_LEFT0, 0x00 }, + { ADAU1761_REC_MIXER_LEFT1, 0x00 }, + { ADAU1761_REC_MIXER_RIGHT0, 0x00 }, + { ADAU1761_REC_MIXER_RIGHT1, 0x00 }, + { ADAU1761_LEFT_DIFF_INPUT_VOL, 0x00 }, + { ADAU1761_RIGHT_DIFF_INPUT_VOL, 0x00 }, + { ADAU1761_PLAY_LR_MIXER_LEFT, 0x00 }, + { ADAU1761_PLAY_MIXER_LEFT0, 0x00 }, + { ADAU1761_PLAY_MIXER_LEFT1, 0x00 }, + { ADAU1761_PLAY_MIXER_RIGHT0, 0x00 }, + { ADAU1761_PLAY_MIXER_RIGHT1, 0x00 }, + { ADAU1761_PLAY_LR_MIXER_RIGHT, 0x00 }, + { ADAU1761_PLAY_MIXER_MONO, 0x00 }, + { ADAU1761_PLAY_HP_LEFT_VOL, 0x00 }, + { ADAU1761_PLAY_HP_RIGHT_VOL, 0x00 }, + { ADAU1761_PLAY_LINE_LEFT_VOL, 0x00 }, + { ADAU1761_PLAY_LINE_RIGHT_VOL, 0x00 }, + { ADAU1761_PLAY_MONO_OUTPUT_VOL, 0x00 }, + { ADAU1761_POP_CLICK_SUPPRESS, 0x00 }, + { ADAU1761_JACK_DETECT_PIN, 0x00 }, + { ADAU1761_CLK_ENABLE0, 0x00 }, + { ADAU1761_CLK_ENABLE1, 0x00 }, + { ADAU17X1_CLOCK_CONTROL, 0x00 }, + { ADAU17X1_PLL_CONTROL, 0x00 }, + { ADAU17X1_REC_POWER_MGMT, 0x00 }, + { ADAU17X1_MICBIAS, 0x00 }, + { ADAU17X1_SERIAL_PORT0, 0x00 }, + { ADAU17X1_SERIAL_PORT1, 0x00 }, + { ADAU17X1_CONVERTER0, 0x00 }, + { ADAU17X1_CONVERTER1, 0x00 }, + { ADAU17X1_LEFT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_ADC_CONTROL, 0x00 }, + { ADAU17X1_PLAY_POWER_MGMT, 0x00 }, + { ADAU17X1_DAC_CONTROL0, 0x00 }, + { ADAU17X1_DAC_CONTROL1, 0x00 }, + { ADAU17X1_DAC_CONTROL2, 0x00 }, + { ADAU17X1_SERIAL_PORT_PAD, 0xaa }, + { ADAU17X1_CONTROL_PORT_PAD0, 0xaa }, + { ADAU17X1_CONTROL_PORT_PAD1, 0x00 }, + { ADAU17X1_DSP_SAMPLING_RATE, 0x01 }, + { ADAU17X1_SERIAL_INPUT_ROUTE, 0x00 }, + { ADAU17X1_SERIAL_OUTPUT_ROUTE, 0x00 }, + { ADAU17X1_DSP_ENABLE, 0x00 }, + { ADAU17X1_DSP_RUN, 0x00 }, + { ADAU17X1_SERIAL_SAMPLING_RATE, 0x00 }, +}; + +static const DECLARE_TLV_DB_SCALE(adau1761_sing_in_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(adau1761_diff_in_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(adau1761_out_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(adau1761_sidetone_tlv, -1800, 300, 1); +static const DECLARE_TLV_DB_SCALE(adau1761_boost_tlv, -600, 600, 1); +static const DECLARE_TLV_DB_SCALE(adau1761_pga_boost_tlv, -2000, 2000, 1); + +static const unsigned int adau1761_bias_select_values[] = { + 0, 2, 3, +}; + +static const char * const adau1761_bias_select_text[] = { + "Normal operation", "Enhanced performance", "Power saving", +}; + +static const char * const adau1761_bias_select_extreme_text[] = { + "Normal operation", "Extreme power saving", "Enhanced performance", + "Power saving", +}; + +static SOC_ENUM_SINGLE_DECL(adau1761_adc_bias_enum, + ADAU17X1_REC_POWER_MGMT, 3, adau1761_bias_select_extreme_text); +static SOC_ENUM_SINGLE_DECL(adau1761_hp_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 6, adau1761_bias_select_extreme_text); +static SOC_ENUM_SINGLE_DECL(adau1761_dac_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 4, adau1761_bias_select_extreme_text); +static SOC_VALUE_ENUM_SINGLE_DECL(adau1761_playback_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 2, 0x3, adau1761_bias_select_text, + adau1761_bias_select_values); +static SOC_VALUE_ENUM_SINGLE_DECL(adau1761_capture_bias_enum, + ADAU17X1_REC_POWER_MGMT, 1, 0x3, adau1761_bias_select_text, + adau1761_bias_select_values); + +static const struct snd_kcontrol_new adau1761_jack_detect_controls[] = { + SOC_SINGLE("Speaker Auto-mute Switch", ADAU1761_DIGMIC_JACKDETECT, + 4, 1, 0), +}; + +static const struct snd_kcontrol_new adau1761_differential_mode_controls[] = { + SOC_DOUBLE_R_TLV("Capture Volume", ADAU1761_LEFT_DIFF_INPUT_VOL, + ADAU1761_RIGHT_DIFF_INPUT_VOL, 2, 0x3f, 0, + adau1761_diff_in_tlv), + SOC_DOUBLE_R("Capture Switch", ADAU1761_LEFT_DIFF_INPUT_VOL, + ADAU1761_RIGHT_DIFF_INPUT_VOL, 1, 1, 0), + + SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1761_REC_MIXER_LEFT1, + ADAU1761_REC_MIXER_RIGHT1, 3, 2, 0, adau1761_pga_boost_tlv), +}; + +static const struct snd_kcontrol_new adau1761_single_mode_controls[] = { + SOC_SINGLE_TLV("Input 1 Capture Volume", ADAU1761_REC_MIXER_LEFT0, + 4, 7, 0, adau1761_sing_in_tlv), + SOC_SINGLE_TLV("Input 2 Capture Volume", ADAU1761_REC_MIXER_LEFT0, + 1, 7, 0, adau1761_sing_in_tlv), + SOC_SINGLE_TLV("Input 3 Capture Volume", ADAU1761_REC_MIXER_RIGHT0, + 4, 7, 0, adau1761_sing_in_tlv), + SOC_SINGLE_TLV("Input 4 Capture Volume", ADAU1761_REC_MIXER_RIGHT0, + 1, 7, 0, adau1761_sing_in_tlv), +}; + +static const struct snd_kcontrol_new adau1761_controls[] = { + SOC_DOUBLE_R_TLV("Aux Capture Volume", ADAU1761_REC_MIXER_LEFT1, + ADAU1761_REC_MIXER_RIGHT1, 0, 7, 0, adau1761_sing_in_tlv), + + SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1761_PLAY_HP_LEFT_VOL, + ADAU1761_PLAY_HP_RIGHT_VOL, 2, 0x3f, 0, adau1761_out_tlv), + SOC_DOUBLE_R("Headphone Playback Switch", ADAU1761_PLAY_HP_LEFT_VOL, + ADAU1761_PLAY_HP_RIGHT_VOL, 1, 1, 0), + SOC_DOUBLE_R_TLV("Lineout Playback Volume", ADAU1761_PLAY_LINE_LEFT_VOL, + ADAU1761_PLAY_LINE_RIGHT_VOL, 2, 0x3f, 0, adau1761_out_tlv), + SOC_DOUBLE_R("Lineout Playback Switch", ADAU1761_PLAY_LINE_LEFT_VOL, + ADAU1761_PLAY_LINE_RIGHT_VOL, 1, 1, 0), + + SOC_ENUM("ADC Bias", adau1761_adc_bias_enum), + SOC_ENUM("DAC Bias", adau1761_dac_bias_enum), + SOC_ENUM("Capture Bias", adau1761_capture_bias_enum), + SOC_ENUM("Playback Bias", adau1761_playback_bias_enum), + SOC_ENUM("Headphone Bias", adau1761_hp_bias_enum), +}; + +static const struct snd_kcontrol_new adau1761_mono_controls[] = { + SOC_SINGLE_TLV("Mono Playback Volume", ADAU1761_PLAY_MONO_OUTPUT_VOL, + 2, 0x3f, 0, adau1761_out_tlv), + SOC_SINGLE("Mono Playback Switch", ADAU1761_PLAY_MONO_OUTPUT_VOL, + 1, 1, 0), +}; + +static const struct snd_kcontrol_new adau1761_left_mixer_controls[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch", + ADAU1761_PLAY_MIXER_LEFT0, 5, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch", + ADAU1761_PLAY_MIXER_LEFT0, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Aux Bypass Volume", + ADAU1761_PLAY_MIXER_LEFT0, 1, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Right Bypass Volume", + ADAU1761_PLAY_MIXER_LEFT1, 4, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Left Bypass Volume", + ADAU1761_PLAY_MIXER_LEFT1, 0, 8, 0, adau1761_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1761_right_mixer_controls[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch", + ADAU1761_PLAY_MIXER_RIGHT0, 5, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch", + ADAU1761_PLAY_MIXER_RIGHT0, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Aux Bypass Volume", + ADAU1761_PLAY_MIXER_RIGHT0, 1, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Right Bypass Volume", + ADAU1761_PLAY_MIXER_RIGHT1, 4, 8, 0, adau1761_sidetone_tlv), + SOC_DAPM_SINGLE_TLV("Left Bypass Volume", + ADAU1761_PLAY_MIXER_RIGHT1, 0, 8, 0, adau1761_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1761_left_lr_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Left Volume", + ADAU1761_PLAY_LR_MIXER_LEFT, 1, 2, 0, adau1761_boost_tlv), + SOC_DAPM_SINGLE_TLV("Right Volume", + ADAU1761_PLAY_LR_MIXER_LEFT, 3, 2, 0, adau1761_boost_tlv), +}; + +static const struct snd_kcontrol_new adau1761_right_lr_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Left Volume", + ADAU1761_PLAY_LR_MIXER_RIGHT, 1, 2, 0, adau1761_boost_tlv), + SOC_DAPM_SINGLE_TLV("Right Volume", + ADAU1761_PLAY_LR_MIXER_RIGHT, 3, 2, 0, adau1761_boost_tlv), +}; + +static const char * const adau1761_input_mux_text[] = { + "ADC", "DMIC", +}; + +static SOC_ENUM_SINGLE_DECL(adau1761_input_mux_enum, + ADAU17X1_ADC_CONTROL, 2, adau1761_input_mux_text); + +static const struct snd_kcontrol_new adau1761_input_mux_control = + SOC_DAPM_ENUM("Input Select", adau1761_input_mux_enum); + +static int adau1761_dejitter_fixup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau *adau = snd_soc_component_get_drvdata(component); + + /* After any power changes have been made the dejitter circuit + * has to be reinitialized. */ + regmap_write(adau->regmap, ADAU1761_DEJITTER, 0); + if (!adau->master) + regmap_write(adau->regmap, ADAU1761_DEJITTER, 3); + + return 0; +} + +static const struct snd_soc_dapm_widget adau1x61_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Input Mixer", ADAU1761_REC_MIXER_LEFT0, 0, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("Right Input Mixer", ADAU1761_REC_MIXER_RIGHT0, 0, 0, + NULL, 0), + + SOC_MIXER_ARRAY("Left Playback Mixer", ADAU1761_PLAY_MIXER_LEFT0, + 0, 0, adau1761_left_mixer_controls), + SOC_MIXER_ARRAY("Right Playback Mixer", ADAU1761_PLAY_MIXER_RIGHT0, + 0, 0, adau1761_right_mixer_controls), + SOC_MIXER_ARRAY("Left LR Playback Mixer", ADAU1761_PLAY_LR_MIXER_LEFT, + 0, 0, adau1761_left_lr_mixer_controls), + SOC_MIXER_ARRAY("Right LR Playback Mixer", ADAU1761_PLAY_LR_MIXER_RIGHT, + 0, 0, adau1761_right_lr_mixer_controls), + + SND_SOC_DAPM_SUPPLY("Headphone", ADAU1761_PLAY_HP_LEFT_VOL, + 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("SYSCLK", 2, SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_POST("Dejitter fixup", adau1761_dejitter_fixup), + + SND_SOC_DAPM_INPUT("LAUX"), + SND_SOC_DAPM_INPUT("RAUX"), + SND_SOC_DAPM_INPUT("LINP"), + SND_SOC_DAPM_INPUT("LINN"), + SND_SOC_DAPM_INPUT("RINP"), + SND_SOC_DAPM_INPUT("RINN"), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("LHP"), + SND_SOC_DAPM_OUTPUT("RHP"), +}; + +static const struct snd_soc_dapm_widget adau1761_mono_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Mono Playback Mixer", ADAU1761_PLAY_MIXER_MONO, + 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("MONOOUT"), +}; + +static const struct snd_soc_dapm_widget adau1761_capless_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("Headphone VGND", 1, ADAU1761_PLAY_MIXER_MONO, + 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route adau1x61_dapm_routes[] = { + { "Left Input Mixer", NULL, "LINP" }, + { "Left Input Mixer", NULL, "LINN" }, + { "Left Input Mixer", NULL, "LAUX" }, + + { "Right Input Mixer", NULL, "RINP" }, + { "Right Input Mixer", NULL, "RINN" }, + { "Right Input Mixer", NULL, "RAUX" }, + + { "Left Playback Mixer", NULL, "Left Playback Enable"}, + { "Right Playback Mixer", NULL, "Right Playback Enable"}, + { "Left LR Playback Mixer", NULL, "Left Playback Enable"}, + { "Right LR Playback Mixer", NULL, "Right Playback Enable"}, + + { "Left Playback Mixer", "Left DAC Switch", "Left DAC" }, + { "Left Playback Mixer", "Right DAC Switch", "Right DAC" }, + + { "Right Playback Mixer", "Left DAC Switch", "Left DAC" }, + { "Right Playback Mixer", "Right DAC Switch", "Right DAC" }, + + { "Left LR Playback Mixer", "Left Volume", "Left Playback Mixer" }, + { "Left LR Playback Mixer", "Right Volume", "Right Playback Mixer" }, + + { "Right LR Playback Mixer", "Left Volume", "Left Playback Mixer" }, + { "Right LR Playback Mixer", "Right Volume", "Right Playback Mixer" }, + + { "LHP", NULL, "Left Playback Mixer" }, + { "RHP", NULL, "Right Playback Mixer" }, + + { "LHP", NULL, "Headphone" }, + { "RHP", NULL, "Headphone" }, + + { "LOUT", NULL, "Left LR Playback Mixer" }, + { "ROUT", NULL, "Right LR Playback Mixer" }, + + { "Left Playback Mixer", "Aux Bypass Volume", "LAUX" }, + { "Left Playback Mixer", "Left Bypass Volume", "Left Input Mixer" }, + { "Left Playback Mixer", "Right Bypass Volume", "Right Input Mixer" }, + { "Right Playback Mixer", "Aux Bypass Volume", "RAUX" }, + { "Right Playback Mixer", "Left Bypass Volume", "Left Input Mixer" }, + { "Right Playback Mixer", "Right Bypass Volume", "Right Input Mixer" }, +}; + +static const struct snd_soc_dapm_route adau1761_mono_dapm_routes[] = { + { "Mono Playback Mixer", NULL, "Left Playback Mixer" }, + { "Mono Playback Mixer", NULL, "Right Playback Mixer" }, + + { "MONOOUT", NULL, "Mono Playback Mixer" }, +}; + +static const struct snd_soc_dapm_route adau1761_capless_dapm_routes[] = { + { "Headphone", NULL, "Headphone VGND" }, +}; + +static const struct snd_soc_dapm_widget adau1761_dmic_widgets[] = { + SND_SOC_DAPM_MUX("Left Decimator Mux", SND_SOC_NOPM, 0, 0, + &adau1761_input_mux_control), + SND_SOC_DAPM_MUX("Right Decimator Mux", SND_SOC_NOPM, 0, 0, + &adau1761_input_mux_control), + + SND_SOC_DAPM_INPUT("DMIC"), +}; + +static const struct snd_soc_dapm_route adau1761_dmic_routes[] = { + { "Left Decimator Mux", "ADC", "Left Input Mixer" }, + { "Left Decimator Mux", "DMIC", "DMIC" }, + { "Right Decimator Mux", "ADC", "Right Input Mixer" }, + { "Right Decimator Mux", "DMIC", "DMIC" }, + + { "Left Decimator", NULL, "Left Decimator Mux" }, + { "Right Decimator", NULL, "Right Decimator Mux" }, +}; + +static const struct snd_soc_dapm_route adau1761_no_dmic_routes[] = { + { "Left Decimator", NULL, "Left Input Mixer" }, + { "Right Decimator", NULL, "Right Input Mixer" }, +}; + +static const struct snd_soc_dapm_widget adau1761_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Serial Port Clock", ADAU1761_CLK_ENABLE0, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Input Routing Clock", ADAU1761_CLK_ENABLE0, + 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Output Routing Clock", ADAU1761_CLK_ENABLE0, + 3, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Decimator Resync Clock", ADAU1761_CLK_ENABLE0, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Interpolator Resync Clock", ADAU1761_CLK_ENABLE0, + 2, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Slew Clock", ADAU1761_CLK_ENABLE0, 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ALC Clock", ADAU1761_CLK_ENABLE0, 5, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("Digital Clock 0", 1, ADAU1761_CLK_ENABLE1, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("Digital Clock 1", 1, ADAU1761_CLK_ENABLE1, + 1, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route adau1761_dapm_routes[] = { + { "Left Decimator", NULL, "Digital Clock 0", }, + { "Right Decimator", NULL, "Digital Clock 0", }, + { "Left DAC", NULL, "Digital Clock 0", }, + { "Right DAC", NULL, "Digital Clock 0", }, + + { "AIFCLK", NULL, "Digital Clock 1" }, + + { "Playback", NULL, "Serial Port Clock" }, + { "Capture", NULL, "Serial Port Clock" }, + { "Playback", NULL, "Serial Input Routing Clock" }, + { "Capture", NULL, "Serial Output Routing Clock" }, + + { "Left Decimator", NULL, "Decimator Resync Clock" }, + { "Right Decimator", NULL, "Decimator Resync Clock" }, + { "Left DAC", NULL, "Interpolator Resync Clock" }, + { "Right DAC", NULL, "Interpolator Resync Clock" }, + + { "DSP", NULL, "Digital Clock 0" }, + + { "Slew Clock", NULL, "Digital Clock 0" }, + { "Right Playback Mixer", NULL, "Slew Clock" }, + { "Left Playback Mixer", NULL, "Slew Clock" }, + + { "Left Input Mixer", NULL, "ALC Clock" }, + { "Right Input Mixer", NULL, "ALC Clock" }, + + { "Digital Clock 0", NULL, "SYSCLK" }, + { "Digital Clock 1", NULL, "SYSCLK" }, +}; + +static int adau1761_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct adau *adau = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + regcache_cache_only(adau->regmap, false); + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN); + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + regcache_sync(adau->regmap); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, 0); + regcache_cache_only(adau->regmap, true); + break; + + } + return 0; +} + +static enum adau1761_output_mode adau1761_get_lineout_mode( + struct snd_soc_component *component) +{ + struct adau1761_platform_data *pdata = component->dev->platform_data; + + if (pdata) + return pdata->lineout_mode; + + return ADAU1761_OUTPUT_MODE_LINE; +} + +static int adau1761_setup_digmic_jackdetect(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau1761_platform_data *pdata = component->dev->platform_data; + struct adau *adau = snd_soc_component_get_drvdata(component); + enum adau1761_digmic_jackdet_pin_mode mode; + unsigned int val = 0; + int ret; + + if (pdata) + mode = pdata->digmic_jackdetect_pin_mode; + else + mode = ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE; + + switch (mode) { + case ADAU1761_DIGMIC_JACKDET_PIN_MODE_JACKDETECT: + switch (pdata->jackdetect_debounce_time) { + case ADAU1761_JACKDETECT_DEBOUNCE_5MS: + case ADAU1761_JACKDETECT_DEBOUNCE_10MS: + case ADAU1761_JACKDETECT_DEBOUNCE_20MS: + case ADAU1761_JACKDETECT_DEBOUNCE_40MS: + val |= pdata->jackdetect_debounce_time << 6; + break; + default: + return -EINVAL; + } + if (pdata->jackdetect_active_low) + val |= ADAU1761_DIGMIC_JACKDETECT_ACTIVE_LOW; + + ret = snd_soc_add_component_controls(component, + adau1761_jack_detect_controls, + ARRAY_SIZE(adau1761_jack_detect_controls)); + if (ret) + return ret; + case ADAU1761_DIGMIC_JACKDET_PIN_MODE_NONE: /* fallthrough */ + ret = snd_soc_dapm_add_routes(dapm, adau1761_no_dmic_routes, + ARRAY_SIZE(adau1761_no_dmic_routes)); + if (ret) + return ret; + break; + case ADAU1761_DIGMIC_JACKDET_PIN_MODE_DIGMIC: + ret = snd_soc_dapm_new_controls(dapm, adau1761_dmic_widgets, + ARRAY_SIZE(adau1761_dmic_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, adau1761_dmic_routes, + ARRAY_SIZE(adau1761_dmic_routes)); + if (ret) + return ret; + + val |= ADAU1761_DIGMIC_JACKDETECT_DIGMIC; + break; + default: + return -EINVAL; + } + + regmap_write(adau->regmap, ADAU1761_DIGMIC_JACKDETECT, val); + + return 0; +} + +static int adau1761_setup_headphone_mode(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau *adau = snd_soc_component_get_drvdata(component); + struct adau1761_platform_data *pdata = component->dev->platform_data; + enum adau1761_output_mode mode; + int ret; + + if (pdata) + mode = pdata->headphone_mode; + else + mode = ADAU1761_OUTPUT_MODE_HEADPHONE; + + switch (mode) { + case ADAU1761_OUTPUT_MODE_LINE: + break; + case ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS: + regmap_update_bits(adau->regmap, ADAU1761_PLAY_MONO_OUTPUT_VOL, + ADAU1761_PLAY_MONO_OUTPUT_VOL_MODE_HP | + ADAU1761_PLAY_MONO_OUTPUT_VOL_UNMUTE, + ADAU1761_PLAY_MONO_OUTPUT_VOL_MODE_HP | + ADAU1761_PLAY_MONO_OUTPUT_VOL_UNMUTE); + /* fallthrough */ + case ADAU1761_OUTPUT_MODE_HEADPHONE: + regmap_update_bits(adau->regmap, ADAU1761_PLAY_HP_RIGHT_VOL, + ADAU1761_PLAY_HP_RIGHT_VOL_MODE_HP, + ADAU1761_PLAY_HP_RIGHT_VOL_MODE_HP); + break; + default: + return -EINVAL; + } + + if (mode == ADAU1761_OUTPUT_MODE_HEADPHONE_CAPLESS) { + ret = snd_soc_dapm_new_controls(dapm, + adau1761_capless_dapm_widgets, + ARRAY_SIZE(adau1761_capless_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(dapm, + adau1761_capless_dapm_routes, + ARRAY_SIZE(adau1761_capless_dapm_routes)); + } else { + ret = snd_soc_add_component_controls(component, adau1761_mono_controls, + ARRAY_SIZE(adau1761_mono_controls)); + if (ret) + return ret; + ret = snd_soc_dapm_new_controls(dapm, + adau1761_mono_dapm_widgets, + ARRAY_SIZE(adau1761_mono_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(dapm, + adau1761_mono_dapm_routes, + ARRAY_SIZE(adau1761_mono_dapm_routes)); + } + + return ret; +} + +static bool adau1761_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1761_DIGMIC_JACKDETECT: + case ADAU1761_REC_MIXER_LEFT0: + case ADAU1761_REC_MIXER_LEFT1: + case ADAU1761_REC_MIXER_RIGHT0: + case ADAU1761_REC_MIXER_RIGHT1: + case ADAU1761_LEFT_DIFF_INPUT_VOL: + case ADAU1761_RIGHT_DIFF_INPUT_VOL: + case ADAU1761_PLAY_LR_MIXER_LEFT: + case ADAU1761_PLAY_MIXER_LEFT0: + case ADAU1761_PLAY_MIXER_LEFT1: + case ADAU1761_PLAY_MIXER_RIGHT0: + case ADAU1761_PLAY_MIXER_RIGHT1: + case ADAU1761_PLAY_LR_MIXER_RIGHT: + case ADAU1761_PLAY_MIXER_MONO: + case ADAU1761_PLAY_HP_LEFT_VOL: + case ADAU1761_PLAY_HP_RIGHT_VOL: + case ADAU1761_PLAY_LINE_LEFT_VOL: + case ADAU1761_PLAY_LINE_RIGHT_VOL: + case ADAU1761_PLAY_MONO_OUTPUT_VOL: + case ADAU1761_POP_CLICK_SUPPRESS: + case ADAU1761_JACK_DETECT_PIN: + case ADAU1761_DEJITTER: + case ADAU1761_CLK_ENABLE0: + case ADAU1761_CLK_ENABLE1: + return true; + default: + break; + } + + return adau17x1_readable_register(dev, reg); +} + +static int adau1761_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau1761_platform_data *pdata = component->dev->platform_data; + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + ret = adau17x1_add_widgets(component); + if (ret < 0) + return ret; + + if (pdata && pdata->input_differential) { + regmap_update_bits(adau->regmap, ADAU1761_LEFT_DIFF_INPUT_VOL, + ADAU1761_DIFF_INPUT_VOL_LDEN, + ADAU1761_DIFF_INPUT_VOL_LDEN); + regmap_update_bits(adau->regmap, ADAU1761_RIGHT_DIFF_INPUT_VOL, + ADAU1761_DIFF_INPUT_VOL_LDEN, + ADAU1761_DIFF_INPUT_VOL_LDEN); + ret = snd_soc_add_component_controls(component, + adau1761_differential_mode_controls, + ARRAY_SIZE(adau1761_differential_mode_controls)); + if (ret) + return ret; + } else { + ret = snd_soc_add_component_controls(component, + adau1761_single_mode_controls, + ARRAY_SIZE(adau1761_single_mode_controls)); + if (ret) + return ret; + } + + switch (adau1761_get_lineout_mode(component)) { + case ADAU1761_OUTPUT_MODE_LINE: + break; + case ADAU1761_OUTPUT_MODE_HEADPHONE: + regmap_update_bits(adau->regmap, ADAU1761_PLAY_LINE_LEFT_VOL, + ADAU1761_PLAY_LINE_LEFT_VOL_MODE_HP, + ADAU1761_PLAY_LINE_LEFT_VOL_MODE_HP); + regmap_update_bits(adau->regmap, ADAU1761_PLAY_LINE_RIGHT_VOL, + ADAU1761_PLAY_LINE_RIGHT_VOL_MODE_HP, + ADAU1761_PLAY_LINE_RIGHT_VOL_MODE_HP); + break; + default: + return -EINVAL; + } + + ret = adau1761_setup_headphone_mode(component); + if (ret) + return ret; + + ret = adau1761_setup_digmic_jackdetect(component); + if (ret) + return ret; + + if (adau->type == ADAU1761) { + ret = snd_soc_dapm_new_controls(dapm, adau1761_dapm_widgets, + ARRAY_SIZE(adau1761_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, adau1761_dapm_routes, + ARRAY_SIZE(adau1761_dapm_routes)); + if (ret) + return ret; + } + + ret = adau17x1_add_routes(component); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_component_driver adau1761_component_driver = { + .probe = adau1761_component_probe, + .resume = adau17x1_resume, + .set_bias_level = adau1761_set_bias_level, + .controls = adau1761_controls, + .num_controls = ARRAY_SIZE(adau1761_controls), + .dapm_widgets = adau1x61_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1x61_dapm_widgets), + .dapm_routes = adau1x61_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1x61_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +#define ADAU1761_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver adau1361_dai_driver = { + .name = "adau-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .ops = &adau17x1_dai_ops, +}; + +static struct snd_soc_dai_driver adau1761_dai_driver = { + .name = "adau-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1761_FORMATS, + }, + .ops = &adau17x1_dai_ops, +}; + +int adau1761_probe(struct device *dev, struct regmap *regmap, + enum adau17x1_type type, void (*switch_mode)(struct device *dev)) +{ + struct snd_soc_dai_driver *dai_drv; + const char *firmware_name; + int ret; + + if (type == ADAU1361) { + dai_drv = &adau1361_dai_driver; + firmware_name = NULL; + } else { + dai_drv = &adau1761_dai_driver; + firmware_name = ADAU1761_FIRMWARE; + } + + ret = adau17x1_probe(dev, regmap, type, switch_mode, firmware_name); + if (ret) + return ret; + + /* Enable cache only mode as we could miss writes before bias level + * reaches standby and the core clock is enabled */ + regcache_cache_only(regmap, true); + + return devm_snd_soc_register_component(dev, &adau1761_component_driver, + dai_drv, 1); +} +EXPORT_SYMBOL_GPL(adau1761_probe); + +const struct regmap_config adau1761_regmap_config = { + .val_bits = 8, + .reg_bits = 16, + .max_register = 0x40fa, + .reg_defaults = adau1761_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1761_reg_defaults), + .readable_reg = adau1761_readable_register, + .volatile_reg = adau17x1_volatile_register, + .precious_reg = adau17x1_precious_register, + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(adau1761_regmap_config); + +MODULE_DESCRIPTION("ASoC ADAU1361/ADAU1461/ADAU1761/ADAU1961 CODEC driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1761.h b/sound/soc/codecs/adau1761.h new file mode 100644 index 000000000..a9e0d2883 --- /dev/null +++ b/sound/soc/codecs/adau1761.h @@ -0,0 +1,23 @@ +/* + * ADAU1361/ADAU1461/ADAU1761/ADAU1961 driver + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#ifndef __SOUND_SOC_CODECS_ADAU1761_H__ +#define __SOUND_SOC_CODECS_ADAU1761_H__ + +#include +#include "adau17x1.h" + +struct device; + +int adau1761_probe(struct device *dev, struct regmap *regmap, + enum adau17x1_type type, void (*switch_mode)(struct device *dev)); + +extern const struct regmap_config adau1761_regmap_config; + +#endif diff --git a/sound/soc/codecs/adau1781-i2c.c b/sound/soc/codecs/adau1781-i2c.c new file mode 100644 index 000000000..7b9d1802d --- /dev/null +++ b/sound/soc/codecs/adau1781-i2c.c @@ -0,0 +1,67 @@ +/* + * Driver for ADAU1381/ADAU1781 CODEC + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include "adau1781.h" + +static int adau1781_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap_config config; + + config = adau1781_regmap_config; + config.val_bits = 8; + config.reg_bits = 16; + + return adau1781_probe(&client->dev, + devm_regmap_init_i2c(client, &config), + id->driver_data, NULL); +} + +static int adau1781_i2c_remove(struct i2c_client *client) +{ + adau17x1_remove(&client->dev); + return 0; +} + +static const struct i2c_device_id adau1781_i2c_ids[] = { + { "adau1381", ADAU1381 }, + { "adau1781", ADAU1781 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1781_i2c_ids); + +#if defined(CONFIG_OF) +static const struct of_device_id adau1781_i2c_dt_ids[] = { + { .compatible = "adi,adau1381", }, + { .compatible = "adi,adau1781", }, + { }, +}; +MODULE_DEVICE_TABLE(of, adau1781_i2c_dt_ids); +#endif + +static struct i2c_driver adau1781_i2c_driver = { + .driver = { + .name = "adau1781", + .of_match_table = of_match_ptr(adau1781_i2c_dt_ids), + }, + .probe = adau1781_i2c_probe, + .remove = adau1781_i2c_remove, + .id_table = adau1781_i2c_ids, +}; +module_i2c_driver(adau1781_i2c_driver); + +MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 CODEC I2C driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1781-spi.c b/sound/soc/codecs/adau1781-spi.c new file mode 100644 index 000000000..9b233544d --- /dev/null +++ b/sound/soc/codecs/adau1781-spi.c @@ -0,0 +1,84 @@ +/* + * Driver for ADAU1381/ADAU1781 CODEC + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include "adau1781.h" + +static void adau1781_spi_switch_mode(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + /* + * To get the device into SPI mode CLATCH has to be pulled low three + * times. Do this by issuing three dummy reads. + */ + spi_w8r8(spi, 0x00); + spi_w8r8(spi, 0x00); + spi_w8r8(spi, 0x00); +} + +static int adau1781_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap_config config; + + if (!id) + return -EINVAL; + + config = adau1781_regmap_config; + config.val_bits = 8; + config.reg_bits = 24; + config.read_flag_mask = 0x1; + + return adau1781_probe(&spi->dev, + devm_regmap_init_spi(spi, &config), + id->driver_data, adau1781_spi_switch_mode); +} + +static int adau1781_spi_remove(struct spi_device *spi) +{ + adau17x1_remove(&spi->dev); + return 0; +} + +static const struct spi_device_id adau1781_spi_id[] = { + { "adau1381", ADAU1381 }, + { "adau1781", ADAU1781 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adau1781_spi_id); + +#if defined(CONFIG_OF) +static const struct of_device_id adau1781_spi_dt_ids[] = { + { .compatible = "adi,adau1381", }, + { .compatible = "adi,adau1781", }, + { }, +}; +MODULE_DEVICE_TABLE(of, adau1781_spi_dt_ids); +#endif + +static struct spi_driver adau1781_spi_driver = { + .driver = { + .name = "adau1781", + .of_match_table = of_match_ptr(adau1781_spi_dt_ids), + }, + .probe = adau1781_spi_probe, + .remove = adau1781_spi_remove, + .id_table = adau1781_spi_id, +}; +module_spi_driver(adau1781_spi_driver); + +MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 CODEC SPI driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1781.c b/sound/soc/codecs/adau1781.c new file mode 100644 index 000000000..6a6655730 --- /dev/null +++ b/sound/soc/codecs/adau1781.c @@ -0,0 +1,509 @@ +/* + * Driver for ADAU1381/ADAU1781 codec + * + * Copyright 2011-2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adau17x1.h" +#include "adau1781.h" + +#define ADAU1781_DMIC_BEEP_CTRL 0x4008 +#define ADAU1781_LEFT_PGA 0x400e +#define ADAU1781_RIGHT_PGA 0x400f +#define ADAU1781_LEFT_PLAYBACK_MIXER 0x401c +#define ADAU1781_RIGHT_PLAYBACK_MIXER 0x401e +#define ADAU1781_MONO_PLAYBACK_MIXER 0x401f +#define ADAU1781_LEFT_LINEOUT 0x4025 +#define ADAU1781_RIGHT_LINEOUT 0x4026 +#define ADAU1781_SPEAKER 0x4027 +#define ADAU1781_BEEP_ZC 0x4028 +#define ADAU1781_DEJITTER 0x4032 +#define ADAU1781_DIG_PWDN0 0x4080 +#define ADAU1781_DIG_PWDN1 0x4081 + +#define ADAU1781_INPUT_DIFFERNTIAL BIT(3) + +#define ADAU1381_FIRMWARE "adau1381.bin" +#define ADAU1781_FIRMWARE "adau1781.bin" + +static const struct reg_default adau1781_reg_defaults[] = { + { ADAU1781_DMIC_BEEP_CTRL, 0x00 }, + { ADAU1781_LEFT_PGA, 0xc7 }, + { ADAU1781_RIGHT_PGA, 0xc7 }, + { ADAU1781_LEFT_PLAYBACK_MIXER, 0x00 }, + { ADAU1781_RIGHT_PLAYBACK_MIXER, 0x00 }, + { ADAU1781_MONO_PLAYBACK_MIXER, 0x00 }, + { ADAU1781_LEFT_LINEOUT, 0x00 }, + { ADAU1781_RIGHT_LINEOUT, 0x00 }, + { ADAU1781_SPEAKER, 0x00 }, + { ADAU1781_BEEP_ZC, 0x19 }, + { ADAU1781_DEJITTER, 0x60 }, + { ADAU1781_DIG_PWDN1, 0x0c }, + { ADAU1781_DIG_PWDN1, 0x00 }, + { ADAU17X1_CLOCK_CONTROL, 0x00 }, + { ADAU17X1_PLL_CONTROL, 0x00 }, + { ADAU17X1_REC_POWER_MGMT, 0x00 }, + { ADAU17X1_MICBIAS, 0x04 }, + { ADAU17X1_SERIAL_PORT0, 0x00 }, + { ADAU17X1_SERIAL_PORT1, 0x00 }, + { ADAU17X1_CONVERTER0, 0x00 }, + { ADAU17X1_CONVERTER1, 0x00 }, + { ADAU17X1_LEFT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, 0x00 }, + { ADAU17X1_ADC_CONTROL, 0x00 }, + { ADAU17X1_PLAY_POWER_MGMT, 0x00 }, + { ADAU17X1_DAC_CONTROL0, 0x00 }, + { ADAU17X1_DAC_CONTROL1, 0x00 }, + { ADAU17X1_DAC_CONTROL2, 0x00 }, + { ADAU17X1_SERIAL_PORT_PAD, 0x00 }, + { ADAU17X1_CONTROL_PORT_PAD0, 0x00 }, + { ADAU17X1_CONTROL_PORT_PAD1, 0x00 }, + { ADAU17X1_DSP_SAMPLING_RATE, 0x01 }, + { ADAU17X1_SERIAL_INPUT_ROUTE, 0x00 }, + { ADAU17X1_SERIAL_OUTPUT_ROUTE, 0x00 }, + { ADAU17X1_DSP_ENABLE, 0x00 }, + { ADAU17X1_DSP_RUN, 0x00 }, + { ADAU17X1_SERIAL_SAMPLING_RATE, 0x00 }, +}; + +static const DECLARE_TLV_DB_SCALE(adau1781_speaker_tlv, 0, 200, 0); + +static const DECLARE_TLV_DB_RANGE(adau1781_pga_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 600, 0), + 2, 3, TLV_DB_SCALE_ITEM(1000, 400, 0), + 4, 4, TLV_DB_SCALE_ITEM(1700, 0, 0), + 5, 7, TLV_DB_SCALE_ITEM(2000, 600, 0) +); + +static const DECLARE_TLV_DB_RANGE(adau1781_beep_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 600, 0), + 2, 3, TLV_DB_SCALE_ITEM(1000, 400, 0), + 4, 4, TLV_DB_SCALE_ITEM(-2300, 0, 0), + 5, 7, TLV_DB_SCALE_ITEM(2000, 600, 0) +); + +static const DECLARE_TLV_DB_SCALE(adau1781_sidetone_tlv, -1800, 300, 1); + +static const char * const adau1781_speaker_bias_select_text[] = { + "Normal operation", "Power saving", "Enhanced performance", +}; + +static const char * const adau1781_bias_select_text[] = { + "Normal operation", "Extreme power saving", "Power saving", + "Enhanced performance", +}; + +static SOC_ENUM_SINGLE_DECL(adau1781_adc_bias_enum, + ADAU17X1_REC_POWER_MGMT, 3, adau1781_bias_select_text); +static SOC_ENUM_SINGLE_DECL(adau1781_speaker_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 6, adau1781_speaker_bias_select_text); +static SOC_ENUM_SINGLE_DECL(adau1781_dac_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 4, adau1781_bias_select_text); +static SOC_ENUM_SINGLE_DECL(adau1781_playback_bias_enum, + ADAU17X1_PLAY_POWER_MGMT, 2, adau1781_bias_select_text); +static SOC_ENUM_SINGLE_DECL(adau1781_capture_bias_enum, + ADAU17X1_REC_POWER_MGMT, 1, adau1781_bias_select_text); + +static const struct snd_kcontrol_new adau1781_controls[] = { + SOC_SINGLE_TLV("Beep Capture Volume", ADAU1781_DMIC_BEEP_CTRL, 0, 7, 0, + adau1781_beep_tlv), + SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAU1781_LEFT_PGA, + ADAU1781_RIGHT_PGA, 5, 7, 0, adau1781_pga_tlv), + SOC_DOUBLE_R("PGA Capture Switch", ADAU1781_LEFT_PGA, + ADAU1781_RIGHT_PGA, 1, 1, 0), + + SOC_DOUBLE_R("Lineout Playback Switch", ADAU1781_LEFT_LINEOUT, + ADAU1781_RIGHT_LINEOUT, 1, 1, 0), + SOC_SINGLE("Beep ZC Switch", ADAU1781_BEEP_ZC, 0, 1, 0), + + SOC_SINGLE("Mono Playback Switch", ADAU1781_MONO_PLAYBACK_MIXER, + 0, 1, 0), + SOC_SINGLE_TLV("Mono Playback Volume", ADAU1781_SPEAKER, 6, 3, 0, + adau1781_speaker_tlv), + + SOC_ENUM("ADC Bias", adau1781_adc_bias_enum), + SOC_ENUM("DAC Bias", adau1781_dac_bias_enum), + SOC_ENUM("Capture Bias", adau1781_capture_bias_enum), + SOC_ENUM("Playback Bias", adau1781_playback_bias_enum), + SOC_ENUM("Speaker Bias", adau1781_speaker_bias_enum), +}; + +static const struct snd_kcontrol_new adau1781_beep_mixer_controls[] = { + SOC_DAPM_SINGLE("Beep Capture Switch", ADAU1781_DMIC_BEEP_CTRL, + 3, 1, 0), +}; + +static const struct snd_kcontrol_new adau1781_left_mixer_controls[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Switch", + ADAU1781_LEFT_PLAYBACK_MIXER, 5, 1, 0), + SOC_DAPM_SINGLE_TLV("Beep Playback Volume", + ADAU1781_LEFT_PLAYBACK_MIXER, 1, 8, 0, adau1781_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1781_right_mixer_controls[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Switch", + ADAU1781_RIGHT_PLAYBACK_MIXER, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Beep Playback Volume", + ADAU1781_LEFT_PLAYBACK_MIXER, 1, 8, 0, adau1781_sidetone_tlv), +}; + +static const struct snd_kcontrol_new adau1781_mono_mixer_controls[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Left Switch", + ADAU1781_MONO_PLAYBACK_MIXER, 7, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("Right Switch", + ADAU1781_MONO_PLAYBACK_MIXER, 6, 1, 0), + SOC_DAPM_SINGLE_TLV("Beep Playback Volume", + ADAU1781_MONO_PLAYBACK_MIXER, 2, 8, 0, adau1781_sidetone_tlv), +}; + +static int adau1781_dejitter_fixup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau *adau = snd_soc_component_get_drvdata(component); + + /* After any power changes have been made the dejitter circuit + * has to be reinitialized. */ + regmap_write(adau->regmap, ADAU1781_DEJITTER, 0); + if (!adau->master) + regmap_write(adau->regmap, ADAU1781_DEJITTER, 5); + + return 0; +} + +static const struct snd_soc_dapm_widget adau1781_dapm_widgets[] = { + SND_SOC_DAPM_PGA("Left PGA", ADAU1781_LEFT_PGA, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", ADAU1781_RIGHT_PGA, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUT_DRV("Speaker", ADAU1781_SPEAKER, 0, 0, NULL, 0), + + SOC_MIXER_NAMED_CTL_ARRAY("Beep Mixer", ADAU17X1_MICBIAS, 4, 0, + adau1781_beep_mixer_controls), + + SOC_MIXER_ARRAY("Left Lineout Mixer", SND_SOC_NOPM, 0, 0, + adau1781_left_mixer_controls), + SOC_MIXER_ARRAY("Right Lineout Mixer", SND_SOC_NOPM, 0, 0, + adau1781_right_mixer_controls), + SOC_MIXER_ARRAY("Mono Mixer", SND_SOC_NOPM, 0, 0, + adau1781_mono_mixer_controls), + + SND_SOC_DAPM_SUPPLY("Serial Input Routing", ADAU1781_DIG_PWDN0, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Output Routing", ADAU1781_DIG_PWDN0, + 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Clock Domain Transfer", ADAU1781_DIG_PWDN0, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Serial Ports", ADAU1781_DIG_PWDN0, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Engine", ADAU1781_DIG_PWDN0, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Engine", ADAU1781_DIG_PWDN1, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Digital Mic", ADAU1781_DIG_PWDN1, 1, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Sound Engine", ADAU1781_DIG_PWDN0, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("SYSCLK", 1, ADAU1781_DIG_PWDN0, 1, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Zero Crossing Detector", ADAU1781_DIG_PWDN1, 2, 0, + NULL, 0), + + SND_SOC_DAPM_POST("Dejitter fixup", adau1781_dejitter_fixup), + + SND_SOC_DAPM_INPUT("BEEP"), + + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), + SND_SOC_DAPM_OUTPUT("SP"), + SND_SOC_DAPM_INPUT("LMIC"), + SND_SOC_DAPM_INPUT("RMIC"), +}; + +static const struct snd_soc_dapm_route adau1781_dapm_routes[] = { + { "Left Lineout Mixer", NULL, "Left Playback Enable" }, + { "Right Lineout Mixer", NULL, "Right Playback Enable" }, + + { "Left Lineout Mixer", "Beep Playback Volume", "Beep Mixer" }, + { "Left Lineout Mixer", "Switch", "Left DAC" }, + + { "Right Lineout Mixer", "Beep Playback Volume", "Beep Mixer" }, + { "Right Lineout Mixer", "Switch", "Right DAC" }, + + { "Mono Mixer", "Beep Playback Volume", "Beep Mixer" }, + { "Mono Mixer", "Right Switch", "Right DAC" }, + { "Mono Mixer", "Left Switch", "Left DAC" }, + { "Speaker", NULL, "Mono Mixer" }, + + { "Mono Mixer", NULL, "SYSCLK" }, + { "Left Lineout Mixer", NULL, "SYSCLK" }, + { "Left Lineout Mixer", NULL, "SYSCLK" }, + + { "Beep Mixer", "Beep Capture Switch", "BEEP" }, + { "Beep Mixer", NULL, "Zero Crossing Detector" }, + + { "Left DAC", NULL, "DAC Engine" }, + { "Right DAC", NULL, "DAC Engine" }, + + { "Sound Engine", NULL, "SYSCLK" }, + { "DSP", NULL, "Sound Engine" }, + + { "Left Decimator", NULL, "ADC Engine" }, + { "Right Decimator", NULL, "ADC Engine" }, + + { "AIFCLK", NULL, "SYSCLK" }, + + { "Playback", NULL, "Serial Input Routing" }, + { "Playback", NULL, "Serial Ports" }, + { "Playback", NULL, "Clock Domain Transfer" }, + { "Capture", NULL, "Serial Output Routing" }, + { "Capture", NULL, "Serial Ports" }, + { "Capture", NULL, "Clock Domain Transfer" }, + + { "AOUTL", NULL, "Left Lineout Mixer" }, + { "AOUTR", NULL, "Right Lineout Mixer" }, + { "SP", NULL, "Speaker" }, +}; + +static const struct snd_soc_dapm_route adau1781_adc_dapm_routes[] = { + { "Left PGA", NULL, "LMIC" }, + { "Right PGA", NULL, "RMIC" }, + + { "Left Decimator", NULL, "Left PGA" }, + { "Right Decimator", NULL, "Right PGA" }, +}; + +static const char * const adau1781_dmic_select_text[] = { + "DMIC1", "DMIC2", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(adau1781_dmic_select_enum, + adau1781_dmic_select_text); + +static const struct snd_kcontrol_new adau1781_dmic_mux = + SOC_DAPM_ENUM("DMIC Select", adau1781_dmic_select_enum); + +static const struct snd_soc_dapm_widget adau1781_dmic_dapm_widgets[] = { + SND_SOC_DAPM_MUX("DMIC Select", SND_SOC_NOPM, 0, 0, &adau1781_dmic_mux), + + SND_SOC_DAPM_ADC("DMIC1", NULL, ADAU1781_DMIC_BEEP_CTRL, 4, 0), + SND_SOC_DAPM_ADC("DMIC2", NULL, ADAU1781_DMIC_BEEP_CTRL, 5, 0), +}; + +static const struct snd_soc_dapm_route adau1781_dmic_dapm_routes[] = { + { "DMIC1", NULL, "LMIC" }, + { "DMIC2", NULL, "RMIC" }, + + { "DMIC1", NULL, "Digital Mic" }, + { "DMIC2", NULL, "Digital Mic" }, + + { "DMIC Select", "DMIC1", "DMIC1" }, + { "DMIC Select", "DMIC2", "DMIC2" }, + + { "Left Decimator", NULL, "DMIC Select" }, + { "Right Decimator", NULL, "DMIC Select" }, +}; + +static int adau1781_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct adau *adau = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN); + + /* Precharge */ + regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0x8, 0x8); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0xc, 0x0); + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, 0); + break; + } + + return 0; +} + +static bool adau1781_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1781_DMIC_BEEP_CTRL: + case ADAU1781_LEFT_PGA: + case ADAU1781_RIGHT_PGA: + case ADAU1781_LEFT_PLAYBACK_MIXER: + case ADAU1781_RIGHT_PLAYBACK_MIXER: + case ADAU1781_MONO_PLAYBACK_MIXER: + case ADAU1781_LEFT_LINEOUT: + case ADAU1781_RIGHT_LINEOUT: + case ADAU1781_SPEAKER: + case ADAU1781_BEEP_ZC: + case ADAU1781_DEJITTER: + case ADAU1781_DIG_PWDN0: + case ADAU1781_DIG_PWDN1: + return true; + default: + break; + } + + return adau17x1_readable_register(dev, reg); +} + +static int adau1781_set_input_mode(struct adau *adau, unsigned int reg, + bool differential) +{ + unsigned int val; + + if (differential) + val = ADAU1781_INPUT_DIFFERNTIAL; + else + val = 0; + + return regmap_update_bits(adau->regmap, reg, + ADAU1781_INPUT_DIFFERNTIAL, val); +} + +static int adau1781_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau1781_platform_data *pdata = dev_get_platdata(component->dev); + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + ret = adau17x1_add_widgets(component); + if (ret) + return ret; + + if (pdata) { + ret = adau1781_set_input_mode(adau, ADAU1781_LEFT_PGA, + pdata->left_input_differential); + if (ret) + return ret; + ret = adau1781_set_input_mode(adau, ADAU1781_RIGHT_PGA, + pdata->right_input_differential); + if (ret) + return ret; + } + + if (pdata && pdata->use_dmic) { + ret = snd_soc_dapm_new_controls(dapm, + adau1781_dmic_dapm_widgets, + ARRAY_SIZE(adau1781_dmic_dapm_widgets)); + if (ret) + return ret; + ret = snd_soc_dapm_add_routes(dapm, adau1781_dmic_dapm_routes, + ARRAY_SIZE(adau1781_dmic_dapm_routes)); + if (ret) + return ret; + } else { + ret = snd_soc_dapm_add_routes(dapm, adau1781_adc_dapm_routes, + ARRAY_SIZE(adau1781_adc_dapm_routes)); + if (ret) + return ret; + } + + ret = adau17x1_add_routes(component); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_component_driver adau1781_component_driver = { + .probe = adau1781_component_probe, + .resume = adau17x1_resume, + .set_bias_level = adau1781_set_bias_level, + .controls = adau1781_controls, + .num_controls = ARRAY_SIZE(adau1781_controls), + .dapm_widgets = adau1781_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1781_dapm_widgets), + .dapm_routes = adau1781_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1781_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +#define ADAU1781_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver adau1781_dai_driver = { + .name = "adau-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1781_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = ADAU1781_FORMATS, + }, + .ops = &adau17x1_dai_ops, +}; + +const struct regmap_config adau1781_regmap_config = { + .val_bits = 8, + .reg_bits = 16, + .max_register = 0x40f8, + .reg_defaults = adau1781_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1781_reg_defaults), + .readable_reg = adau1781_readable_register, + .volatile_reg = adau17x1_volatile_register, + .precious_reg = adau17x1_precious_register, + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(adau1781_regmap_config); + +int adau1781_probe(struct device *dev, struct regmap *regmap, + enum adau17x1_type type, void (*switch_mode)(struct device *dev)) +{ + const char *firmware_name; + int ret; + + switch (type) { + case ADAU1381: + firmware_name = ADAU1381_FIRMWARE; + break; + case ADAU1781: + firmware_name = ADAU1781_FIRMWARE; + break; + default: + return -EINVAL; + } + + ret = adau17x1_probe(dev, regmap, type, switch_mode, firmware_name); + if (ret) + return ret; + + return devm_snd_soc_register_component(dev, &adau1781_component_driver, + &adau1781_dai_driver, 1); +} +EXPORT_SYMBOL_GPL(adau1781_probe); + +MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1781.h b/sound/soc/codecs/adau1781.h new file mode 100644 index 000000000..2b96e0a9f --- /dev/null +++ b/sound/soc/codecs/adau1781.h @@ -0,0 +1,23 @@ +/* + * ADAU1381/ADAU1781 driver + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#ifndef __SOUND_SOC_CODECS_ADAU1781_H__ +#define __SOUND_SOC_CODECS_ADAU1781_H__ + +#include +#include "adau17x1.h" + +struct device; + +int adau1781_probe(struct device *dev, struct regmap *regmap, + enum adau17x1_type type, void (*switch_mode)(struct device *dev)); + +extern const struct regmap_config adau1781_regmap_config; + +#endif diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c new file mode 100644 index 000000000..57169b8ff --- /dev/null +++ b/sound/soc/codecs/adau17x1.c @@ -0,0 +1,1032 @@ +/* + * Common code for ADAU1X61 and ADAU1X81 codecs + * + * Copyright 2011-2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sigmadsp.h" +#include "adau17x1.h" +#include "adau-utils.h" + +static const char * const adau17x1_capture_mixer_boost_text[] = { + "Normal operation", "Boost Level 1", "Boost Level 2", "Boost Level 3", +}; + +static SOC_ENUM_SINGLE_DECL(adau17x1_capture_boost_enum, + ADAU17X1_REC_POWER_MGMT, 5, adau17x1_capture_mixer_boost_text); + +static const char * const adau17x1_mic_bias_mode_text[] = { + "Normal operation", "High performance", +}; + +static SOC_ENUM_SINGLE_DECL(adau17x1_mic_bias_mode_enum, + ADAU17X1_MICBIAS, 3, adau17x1_mic_bias_mode_text); + +static const DECLARE_TLV_DB_MINMAX(adau17x1_digital_tlv, -9563, 0); + +static const struct snd_kcontrol_new adau17x1_controls[] = { + SOC_DOUBLE_R_TLV("Digital Capture Volume", + ADAU17X1_LEFT_INPUT_DIGITAL_VOL, + ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, + 0, 0xff, 1, adau17x1_digital_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume", ADAU17X1_DAC_CONTROL1, + ADAU17X1_DAC_CONTROL2, 0, 0xff, 1, adau17x1_digital_tlv), + + SOC_SINGLE("ADC High Pass Filter Switch", ADAU17X1_ADC_CONTROL, + 5, 1, 0), + SOC_SINGLE("Playback De-emphasis Switch", ADAU17X1_DAC_CONTROL0, + 2, 1, 0), + + SOC_ENUM("Capture Boost", adau17x1_capture_boost_enum), + + SOC_ENUM("Mic Bias Mode", adau17x1_mic_bias_mode_enum), +}; + +static int adau17x1_pll_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau *adau = snd_soc_component_get_drvdata(component); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + adau->pll_regs[5] = 1; + } else { + adau->pll_regs[5] = 0; + /* Bypass the PLL when disabled, otherwise registers will become + * inaccessible. */ + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL, 0); + } + + /* The PLL register is 6 bytes long and can only be written at once. */ + regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + mdelay(5); + regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL, + ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL, + ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL); + } + + return 0; +} + +static int adau17x1_adc_fixup(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct adau *adau = snd_soc_component_get_drvdata(component); + + /* + * If we are capturing, toggle the ADOSR bit in Converter Control 0 to + * avoid losing SNR (workaround from ADI). This must be done after + * the ADC(s) have been enabled. According to the data sheet, it is + * normally illegal to set this bit when the sampling rate is 96 kHz, + * but according to ADI it is acceptable for this workaround. + */ + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0, + ADAU17X1_CONVERTER0_ADOSR, ADAU17X1_CONVERTER0_ADOSR); + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0, + ADAU17X1_CONVERTER0_ADOSR, 0); + + return 0; +} + +static const char * const adau17x1_mono_stereo_text[] = { + "Stereo", + "Mono Left Channel (L+R)", + "Mono Right Channel (L+R)", + "Mono (L+R)", +}; + +static SOC_ENUM_SINGLE_DECL(adau17x1_dac_mode_enum, + ADAU17X1_DAC_CONTROL0, 6, adau17x1_mono_stereo_text); + +static const struct snd_kcontrol_new adau17x1_dac_mode_mux = + SOC_DAPM_ENUM("DAC Mono-Stereo-Mode", adau17x1_dac_mode_enum); + +static const struct snd_soc_dapm_widget adau17x1_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("PLL", 3, SND_SOC_NOPM, 0, 0, adau17x1_pll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("AIFCLK", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU17X1_MICBIAS, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Left Playback Enable", ADAU17X1_PLAY_POWER_MGMT, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right Playback Enable", ADAU17X1_PLAY_POWER_MGMT, + 1, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Left DAC Mode Mux", SND_SOC_NOPM, 0, 0, + &adau17x1_dac_mode_mux), + SND_SOC_DAPM_MUX("Right DAC Mode Mux", SND_SOC_NOPM, 0, 0, + &adau17x1_dac_mode_mux), + + SND_SOC_DAPM_ADC_E("Left Decimator", NULL, ADAU17X1_ADC_CONTROL, 0, 0, + adau17x1_adc_fixup, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC("Right Decimator", NULL, ADAU17X1_ADC_CONTROL, 1, 0), + SND_SOC_DAPM_DAC("Left DAC", NULL, ADAU17X1_DAC_CONTROL0, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", NULL, ADAU17X1_DAC_CONTROL0, 1, 0), +}; + +static const struct snd_soc_dapm_route adau17x1_dapm_routes[] = { + { "Left Decimator", NULL, "SYSCLK" }, + { "Right Decimator", NULL, "SYSCLK" }, + { "Left DAC", NULL, "SYSCLK" }, + { "Right DAC", NULL, "SYSCLK" }, + { "Capture", NULL, "SYSCLK" }, + { "Playback", NULL, "SYSCLK" }, + + { "Left DAC", NULL, "Left DAC Mode Mux" }, + { "Right DAC", NULL, "Right DAC Mode Mux" }, + + { "Capture", NULL, "AIFCLK" }, + { "Playback", NULL, "AIFCLK" }, +}; + +static const struct snd_soc_dapm_route adau17x1_dapm_pll_route = { + "SYSCLK", NULL, "PLL", +}; + +/* + * The MUX register for the Capture and Playback MUXs selects either DSP as + * source/destination or one of the TDM slots. The TDM slot is selected via + * snd_soc_dai_set_tdm_slot(), so we only expose whether to go to the DSP or + * directly to the DAI interface with this control. + */ +static int adau17x1_dsp_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau *adau = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_dapm_update update = {}; + unsigned int stream = e->shift_l; + unsigned int val, change; + int reg; + + if (ucontrol->value.enumerated.item[0] >= e->items) + return -EINVAL; + + switch (ucontrol->value.enumerated.item[0]) { + case 0: + val = 0; + adau->dsp_bypass[stream] = false; + break; + default: + val = (adau->tdm_slot[stream] * 2) + 1; + adau->dsp_bypass[stream] = true; + break; + } + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = ADAU17X1_SERIAL_INPUT_ROUTE; + else + reg = ADAU17X1_SERIAL_OUTPUT_ROUTE; + + change = snd_soc_component_test_bits(component, reg, 0xff, val); + if (change) { + update.kcontrol = kcontrol; + update.mask = 0xff; + update.reg = reg; + update.val = val; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, + ucontrol->value.enumerated.item[0], e, &update); + } + + return change; +} + +static int adau17x1_dsp_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct adau *adau = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int stream = e->shift_l; + unsigned int reg, val; + int ret; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = ADAU17X1_SERIAL_INPUT_ROUTE; + else + reg = ADAU17X1_SERIAL_OUTPUT_ROUTE; + + ret = regmap_read(adau->regmap, reg, &val); + if (ret) + return ret; + + if (val != 0) + val = 1; + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +#define DECLARE_ADAU17X1_DSP_MUX_CTRL(_name, _label, _stream, _text) \ + const struct snd_kcontrol_new _name = \ + SOC_DAPM_ENUM_EXT(_label, (const struct soc_enum)\ + SOC_ENUM_SINGLE(SND_SOC_NOPM, _stream, \ + ARRAY_SIZE(_text), _text), \ + adau17x1_dsp_mux_enum_get, adau17x1_dsp_mux_enum_put) + +static const char * const adau17x1_dac_mux_text[] = { + "DSP", + "AIFIN", +}; + +static const char * const adau17x1_capture_mux_text[] = { + "DSP", + "Decimator", +}; + +static DECLARE_ADAU17X1_DSP_MUX_CTRL(adau17x1_dac_mux, "DAC Playback Mux", + SNDRV_PCM_STREAM_PLAYBACK, adau17x1_dac_mux_text); + +static DECLARE_ADAU17X1_DSP_MUX_CTRL(adau17x1_capture_mux, "Capture Mux", + SNDRV_PCM_STREAM_CAPTURE, adau17x1_capture_mux_text); + +static const struct snd_soc_dapm_widget adau17x1_dsp_dapm_widgets[] = { + SND_SOC_DAPM_PGA("DSP", ADAU17X1_DSP_RUN, 0, 0, NULL, 0), + SND_SOC_DAPM_SIGGEN("DSP Siggen"), + + SND_SOC_DAPM_MUX("DAC Playback Mux", SND_SOC_NOPM, 0, 0, + &adau17x1_dac_mux), + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, + &adau17x1_capture_mux), +}; + +static const struct snd_soc_dapm_route adau17x1_dsp_dapm_routes[] = { + { "DAC Playback Mux", "DSP", "DSP" }, + { "DAC Playback Mux", "AIFIN", "Playback" }, + + { "Left DAC Mode Mux", "Stereo", "DAC Playback Mux" }, + { "Left DAC Mode Mux", "Mono (L+R)", "DAC Playback Mux" }, + { "Left DAC Mode Mux", "Mono Left Channel (L+R)", "DAC Playback Mux" }, + { "Right DAC Mode Mux", "Stereo", "DAC Playback Mux" }, + { "Right DAC Mode Mux", "Mono (L+R)", "DAC Playback Mux" }, + { "Right DAC Mode Mux", "Mono Right Channel (L+R)", "DAC Playback Mux" }, + + { "Capture Mux", "DSP", "DSP" }, + { "Capture Mux", "Decimator", "Left Decimator" }, + { "Capture Mux", "Decimator", "Right Decimator" }, + + { "Capture", NULL, "Capture Mux" }, + + { "DSP", NULL, "DSP Siggen" }, + + { "DSP", NULL, "Left Decimator" }, + { "DSP", NULL, "Right Decimator" }, + { "DSP", NULL, "Playback" }, +}; + +static const struct snd_soc_dapm_route adau17x1_no_dsp_dapm_routes[] = { + { "Left DAC Mode Mux", "Stereo", "Playback" }, + { "Left DAC Mode Mux", "Mono (L+R)", "Playback" }, + { "Left DAC Mode Mux", "Mono Left Channel (L+R)", "Playback" }, + { "Right DAC Mode Mux", "Stereo", "Playback" }, + { "Right DAC Mode Mux", "Mono (L+R)", "Playback" }, + { "Right DAC Mode Mux", "Mono Right Channel (L+R)", "Playback" }, + { "Capture", NULL, "Left Decimator" }, + { "Capture", NULL, "Right Decimator" }, +}; + +bool adau17x1_has_dsp(struct adau *adau) +{ + switch (adau->type) { + case ADAU1761: + case ADAU1381: + case ADAU1781: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(adau17x1_has_dsp); + +static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + if (freq_in < 8000000 || freq_in > 27000000) + return -EINVAL; + + ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs); + if (ret < 0) + return ret; + + /* The PLL register is 6 bytes long and can only be written at once. */ + ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + if (ret) + return ret; + + adau->pll_freq = freq_out; + + return 0; +} + +static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(dai->component); + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + bool is_pll; + bool was_pll; + + switch (clk_id) { + case ADAU17X1_CLK_SRC_MCLK: + is_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL_AUTO: + if (!adau->mclk) + return -EINVAL; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: + is_pll = true; + break; + default: + return -EINVAL; + } + + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_MCLK: + was_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL: + case ADAU17X1_CLK_SRC_PLL_AUTO: + was_pll = true; + break; + default: + return -EINVAL; + } + + adau->sysclk = freq; + + if (is_pll != was_pll) { + if (is_pll) { + snd_soc_dapm_add_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } else { + snd_soc_dapm_del_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } + } + + adau->clk_src = clk_id; + + return 0; +} + +static int adau17x1_auto_pll(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct adau *adau = snd_soc_dai_get_drvdata(dai); + unsigned int pll_rate; + + switch (params_rate(params)) { + case 48000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 96000: + pll_rate = 48000 * 1024; + break; + case 44100: + case 7350: + case 11025: + case 14700: + case 22050: + case 29400: + case 88200: + pll_rate = 44100 * 1024; + break; + default: + return -EINVAL; + } + + return adau17x1_set_dai_pll(dai, ADAU17X1_PLL, ADAU17X1_PLL_SRC_MCLK, + clk_get_rate(adau->mclk), pll_rate); +} + +static int adau17x1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adau *adau = snd_soc_component_get_drvdata(component); + unsigned int val, div, dsp_div; + unsigned int freq; + int ret; + + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_PLL_AUTO: + ret = adau17x1_auto_pll(dai, params); + if (ret) + return ret; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: + freq = adau->pll_freq; + break; + default: + freq = adau->sysclk; + break; + } + + if (freq % params_rate(params) != 0) + return -EINVAL; + + switch (freq / params_rate(params)) { + case 1024: /* fs */ + div = 0; + dsp_div = 1; + break; + case 6144: /* fs / 6 */ + div = 1; + dsp_div = 6; + break; + case 4096: /* fs / 4 */ + div = 2; + dsp_div = 5; + break; + case 3072: /* fs / 3 */ + div = 3; + dsp_div = 4; + break; + case 2048: /* fs / 2 */ + div = 4; + dsp_div = 3; + break; + case 1536: /* fs / 1.5 */ + div = 5; + dsp_div = 2; + break; + case 512: /* fs / 0.5 */ + div = 6; + dsp_div = 0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0, + ADAU17X1_CONVERTER0_CONVSR_MASK, div); + if (adau17x1_has_dsp(adau)) { + regmap_write(adau->regmap, ADAU17X1_SERIAL_SAMPLING_RATE, div); + regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, dsp_div); + } + + if (adau->sigmadsp) { + ret = adau17x1_setup_firmware(component, params_rate(params)); + if (ret < 0) + return ret; + } + + if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (params_width(params)) { + case 16: + val = ADAU17X1_SERIAL_PORT1_DELAY16; + break; + case 24: + val = ADAU17X1_SERIAL_PORT1_DELAY8; + break; + case 32: + val = ADAU17X1_SERIAL_PORT1_DELAY0; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT1, + ADAU17X1_SERIAL_PORT1_DELAY_MASK, val); +} + +static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + unsigned int ctrl0, ctrl1; + int lrclk_pol; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl0 = ADAU17X1_SERIAL_PORT0_MASTER; + adau->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ctrl0 = 0; + adau->master = false; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + lrclk_pol = 0; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY1; + break; + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + lrclk_pol = 1; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY0; + break; + case SND_SOC_DAIFMT_DSP_A: + lrclk_pol = 1; + ctrl0 |= ADAU17X1_SERIAL_PORT0_PULSE_MODE; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY1; + break; + case SND_SOC_DAIFMT_DSP_B: + lrclk_pol = 1; + ctrl0 |= ADAU17X1_SERIAL_PORT0_PULSE_MODE; + ctrl1 = ADAU17X1_SERIAL_PORT1_DELAY0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl0 |= ADAU17X1_SERIAL_PORT0_BCLK_POL; + break; + case SND_SOC_DAIFMT_NB_IF: + lrclk_pol = !lrclk_pol; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl0 |= ADAU17X1_SERIAL_PORT0_BCLK_POL; + lrclk_pol = !lrclk_pol; + break; + default: + return -EINVAL; + } + + if (lrclk_pol) + ctrl0 |= ADAU17X1_SERIAL_PORT0_LRCLK_POL; + + regmap_write(adau->regmap, ADAU17X1_SERIAL_PORT0, ctrl0); + regmap_write(adau->regmap, ADAU17X1_SERIAL_PORT1, ctrl1); + + adau->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + return 0; +} + +static int adau17x1_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + unsigned int ser_ctrl0, ser_ctrl1; + unsigned int conv_ctrl0, conv_ctrl1; + + /* I2S mode */ + if (slots == 0) { + slots = 2; + rx_mask = 3; + tx_mask = 3; + slot_width = 32; + } + + switch (slots) { + case 2: + ser_ctrl0 = ADAU17X1_SERIAL_PORT0_STEREO; + break; + case 4: + ser_ctrl0 = ADAU17X1_SERIAL_PORT0_TDM4; + break; + case 8: + if (adau->type == ADAU1361) + return -EINVAL; + + ser_ctrl0 = ADAU17X1_SERIAL_PORT0_TDM8; + break; + default: + return -EINVAL; + } + + switch (slot_width * slots) { + case 32: + if (adau->type == ADAU1761) + return -EINVAL; + + ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK32; + break; + case 64: + ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK64; + break; + case 48: + ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK48; + break; + case 128: + ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK128; + break; + case 256: + if (adau->type == ADAU1361) + return -EINVAL; + + ser_ctrl1 = ADAU17X1_SERIAL_PORT1_BCLK256; + break; + default: + return -EINVAL; + } + + switch (rx_mask) { + case 0x03: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(1); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 0; + break; + case 0x0c: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(2); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 1; + break; + case 0x30: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(3); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 2; + break; + case 0xc0: + conv_ctrl1 = ADAU17X1_CONVERTER1_ADC_PAIR(4); + adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 3; + break; + default: + return -EINVAL; + } + + switch (tx_mask) { + case 0x03: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(1); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 0; + break; + case 0x0c: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(2); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 1; + break; + case 0x30: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(3); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 2; + break; + case 0xc0: + conv_ctrl0 = ADAU17X1_CONVERTER0_DAC_PAIR(4); + adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 3; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER0, + ADAU17X1_CONVERTER0_DAC_PAIR_MASK, conv_ctrl0); + regmap_update_bits(adau->regmap, ADAU17X1_CONVERTER1, + ADAU17X1_CONVERTER1_ADC_PAIR_MASK, conv_ctrl1); + regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT0, + ADAU17X1_SERIAL_PORT0_TDM_MASK, ser_ctrl0); + regmap_update_bits(adau->regmap, ADAU17X1_SERIAL_PORT1, + ADAU17X1_SERIAL_PORT1_BCLK_MASK, ser_ctrl1); + + if (!adau17x1_has_dsp(adau)) + return 0; + + if (adau->dsp_bypass[SNDRV_PCM_STREAM_PLAYBACK]) { + regmap_write(adau->regmap, ADAU17X1_SERIAL_INPUT_ROUTE, + (adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] * 2) + 1); + } + + if (adau->dsp_bypass[SNDRV_PCM_STREAM_CAPTURE]) { + regmap_write(adau->regmap, ADAU17X1_SERIAL_OUTPUT_ROUTE, + (adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] * 2) + 1); + } + + return 0; +} + +static int adau17x1_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct adau *adau = snd_soc_component_get_drvdata(dai->component); + + if (adau->sigmadsp) + return sigmadsp_restrict_params(adau->sigmadsp, substream); + + return 0; +} + +const struct snd_soc_dai_ops adau17x1_dai_ops = { + .hw_params = adau17x1_hw_params, + .set_sysclk = adau17x1_set_dai_sysclk, + .set_fmt = adau17x1_set_dai_fmt, + .set_pll = adau17x1_set_dai_pll, + .set_tdm_slot = adau17x1_set_dai_tdm_slot, + .startup = adau17x1_startup, +}; +EXPORT_SYMBOL_GPL(adau17x1_dai_ops); + +int adau17x1_set_micbias_voltage(struct snd_soc_component *component, + enum adau17x1_micbias_voltage micbias) +{ + struct adau *adau = snd_soc_component_get_drvdata(component); + + switch (micbias) { + case ADAU17X1_MICBIAS_0_90_AVDD: + case ADAU17X1_MICBIAS_0_65_AVDD: + break; + default: + return -EINVAL; + } + + return regmap_write(adau->regmap, ADAU17X1_MICBIAS, micbias << 2); +} +EXPORT_SYMBOL_GPL(adau17x1_set_micbias_voltage); + +bool adau17x1_precious_register(struct device *dev, unsigned int reg) +{ + /* SigmaDSP parameter memory */ + if (reg < 0x400) + return true; + + return false; +} +EXPORT_SYMBOL_GPL(adau17x1_precious_register); + +bool adau17x1_readable_register(struct device *dev, unsigned int reg) +{ + /* SigmaDSP parameter memory */ + if (reg < 0x400) + return true; + + switch (reg) { + case ADAU17X1_CLOCK_CONTROL: + case ADAU17X1_PLL_CONTROL: + case ADAU17X1_REC_POWER_MGMT: + case ADAU17X1_MICBIAS: + case ADAU17X1_SERIAL_PORT0: + case ADAU17X1_SERIAL_PORT1: + case ADAU17X1_CONVERTER0: + case ADAU17X1_CONVERTER1: + case ADAU17X1_LEFT_INPUT_DIGITAL_VOL: + case ADAU17X1_RIGHT_INPUT_DIGITAL_VOL: + case ADAU17X1_ADC_CONTROL: + case ADAU17X1_PLAY_POWER_MGMT: + case ADAU17X1_DAC_CONTROL0: + case ADAU17X1_DAC_CONTROL1: + case ADAU17X1_DAC_CONTROL2: + case ADAU17X1_SERIAL_PORT_PAD: + case ADAU17X1_CONTROL_PORT_PAD0: + case ADAU17X1_CONTROL_PORT_PAD1: + case ADAU17X1_DSP_SAMPLING_RATE: + case ADAU17X1_SERIAL_INPUT_ROUTE: + case ADAU17X1_SERIAL_OUTPUT_ROUTE: + case ADAU17X1_DSP_ENABLE: + case ADAU17X1_DSP_RUN: + case ADAU17X1_SERIAL_SAMPLING_RATE: + return true; + default: + break; + } + return false; +} +EXPORT_SYMBOL_GPL(adau17x1_readable_register); + +bool adau17x1_volatile_register(struct device *dev, unsigned int reg) +{ + /* SigmaDSP parameter and program memory */ + if (reg < 0x4000) + return true; + + switch (reg) { + /* The PLL register is 6 bytes long */ + case ADAU17X1_PLL_CONTROL: + case ADAU17X1_PLL_CONTROL + 1: + case ADAU17X1_PLL_CONTROL + 2: + case ADAU17X1_PLL_CONTROL + 3: + case ADAU17X1_PLL_CONTROL + 4: + case ADAU17X1_PLL_CONTROL + 5: + return true; + default: + break; + } + + return false; +} +EXPORT_SYMBOL_GPL(adau17x1_volatile_register); + +int adau17x1_setup_firmware(struct snd_soc_component *component, + unsigned int rate) +{ + int ret; + int dspsr, dsp_run; + struct adau *adau = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + /* Check if sample rate is the same as before. If it is there is no + * point in performing the below steps as the call to + * sigmadsp_setup(...) will return directly when it finds the sample + * rate to be the same as before. By checking this we can prevent an + * audiable popping noise which occours when toggling DSP_RUN. + */ + if (adau->sigmadsp->current_samplerate == rate) + return 0; + + snd_soc_dapm_mutex_lock(dapm); + + ret = regmap_read(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, &dspsr); + if (ret) + goto err; + + ret = regmap_read(adau->regmap, ADAU17X1_DSP_RUN, &dsp_run); + if (ret) + goto err; + + regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 1); + regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, 0xf); + regmap_write(adau->regmap, ADAU17X1_DSP_RUN, 0); + + ret = sigmadsp_setup(adau->sigmadsp, rate); + if (ret) { + regmap_write(adau->regmap, ADAU17X1_DSP_ENABLE, 0); + goto err; + } + regmap_write(adau->regmap, ADAU17X1_DSP_SAMPLING_RATE, dspsr); + regmap_write(adau->regmap, ADAU17X1_DSP_RUN, dsp_run); + +err: + snd_soc_dapm_mutex_unlock(dapm); + + return ret; +} +EXPORT_SYMBOL_GPL(adau17x1_setup_firmware); + +int adau17x1_add_widgets(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_add_component_controls(component, adau17x1_controls, + ARRAY_SIZE(adau17x1_controls)); + if (ret) + return ret; + ret = snd_soc_dapm_new_controls(dapm, adau17x1_dapm_widgets, + ARRAY_SIZE(adau17x1_dapm_widgets)); + if (ret) + return ret; + + if (adau17x1_has_dsp(adau)) { + ret = snd_soc_dapm_new_controls(dapm, adau17x1_dsp_dapm_widgets, + ARRAY_SIZE(adau17x1_dsp_dapm_widgets)); + if (ret) + return ret; + + if (!adau->sigmadsp) + return 0; + + ret = sigmadsp_attach(adau->sigmadsp, component); + if (ret) { + dev_err(component->dev, "Failed to attach firmware: %d\n", + ret); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_add_widgets); + +int adau17x1_add_routes(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau *adau = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_dapm_add_routes(dapm, adau17x1_dapm_routes, + ARRAY_SIZE(adau17x1_dapm_routes)); + if (ret) + return ret; + + if (adau17x1_has_dsp(adau)) { + ret = snd_soc_dapm_add_routes(dapm, adau17x1_dsp_dapm_routes, + ARRAY_SIZE(adau17x1_dsp_dapm_routes)); + } else { + ret = snd_soc_dapm_add_routes(dapm, adau17x1_no_dsp_dapm_routes, + ARRAY_SIZE(adau17x1_no_dsp_dapm_routes)); + } + + if (adau->clk_src != ADAU17X1_CLK_SRC_MCLK) + snd_soc_dapm_add_routes(dapm, &adau17x1_dapm_pll_route, 1); + + return ret; +} +EXPORT_SYMBOL_GPL(adau17x1_add_routes); + +int adau17x1_resume(struct snd_soc_component *component) +{ + struct adau *adau = snd_soc_component_get_drvdata(component); + + if (adau->switch_mode) + adau->switch_mode(component->dev); + + regcache_sync(adau->regmap); + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_resume); + +int adau17x1_probe(struct device *dev, struct regmap *regmap, + enum adau17x1_type type, void (*switch_mode)(struct device *dev), + const char *firmware_name) +{ + struct adau *adau; + int ret; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + adau = devm_kzalloc(dev, sizeof(*adau), GFP_KERNEL); + if (!adau) + return -ENOMEM; + + adau->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(adau->mclk)) { + if (PTR_ERR(adau->mclk) != -ENOENT) + return PTR_ERR(adau->mclk); + /* Clock is optional (for the driver) */ + adau->mclk = NULL; + } else if (adau->mclk) { + adau->clk_src = ADAU17X1_CLK_SRC_PLL_AUTO; + + /* + * Any valid PLL output rate will work at this point, use one + * that is likely to be chosen later as well. The register will + * be written when the PLL is powered up for the first time. + */ + ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024, + adau->pll_regs); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(adau->mclk); + if (ret) + return ret; + } + + adau->regmap = regmap; + adau->switch_mode = switch_mode; + adau->type = type; + + dev_set_drvdata(dev, adau); + + if (firmware_name) { + adau->sigmadsp = devm_sigmadsp_init_regmap(dev, regmap, NULL, + firmware_name); + if (IS_ERR(adau->sigmadsp)) { + dev_warn(dev, "Could not find firmware file: %ld\n", + PTR_ERR(adau->sigmadsp)); + adau->sigmadsp = NULL; + } + } + + if (switch_mode) + switch_mode(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(adau17x1_probe); + +void adau17x1_remove(struct device *dev) +{ + struct adau *adau = dev_get_drvdata(dev); + + if (adau->mclk) + clk_disable_unprepare(adau->mclk); +} +EXPORT_SYMBOL_GPL(adau17x1_remove); + +MODULE_DESCRIPTION("ASoC ADAU1X61/ADAU1X81 common code"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau17x1.h b/sound/soc/codecs/adau17x1.h new file mode 100644 index 000000000..e6fe87bee --- /dev/null +++ b/sound/soc/codecs/adau17x1.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ADAU17X1_H__ +#define __ADAU17X1_H__ + +#include +#include + +#include "sigmadsp.h" + +enum adau17x1_type { + ADAU1361, + ADAU1761, + ADAU1381, + ADAU1781, +}; + +enum adau17x1_pll { + ADAU17X1_PLL, +}; + +enum adau17x1_pll_src { + ADAU17X1_PLL_SRC_MCLK, +}; + +enum adau17x1_clk_src { + /* Automatically configure PLL based on the sample rate */ + ADAU17X1_CLK_SRC_PLL_AUTO, + ADAU17X1_CLK_SRC_MCLK, + ADAU17X1_CLK_SRC_PLL, +}; + +struct clk; + +struct adau { + unsigned int sysclk; + unsigned int pll_freq; + struct clk *mclk; + + enum adau17x1_clk_src clk_src; + enum adau17x1_type type; + void (*switch_mode)(struct device *dev); + + unsigned int dai_fmt; + + uint8_t pll_regs[6]; + + bool master; + + unsigned int tdm_slot[2]; + bool dsp_bypass[2]; + + struct regmap *regmap; + struct sigmadsp *sigmadsp; +}; + +int adau17x1_add_widgets(struct snd_soc_component *component); +int adau17x1_add_routes(struct snd_soc_component *component); +int adau17x1_probe(struct device *dev, struct regmap *regmap, + enum adau17x1_type type, void (*switch_mode)(struct device *dev), + const char *firmware_name); +void adau17x1_remove(struct device *dev); +int adau17x1_set_micbias_voltage(struct snd_soc_component *component, + enum adau17x1_micbias_voltage micbias); +bool adau17x1_readable_register(struct device *dev, unsigned int reg); +bool adau17x1_volatile_register(struct device *dev, unsigned int reg); +bool adau17x1_precious_register(struct device *dev, unsigned int reg); +int adau17x1_resume(struct snd_soc_component *component); + +extern const struct snd_soc_dai_ops adau17x1_dai_ops; + +int adau17x1_setup_firmware(struct snd_soc_component *component, + unsigned int rate); +bool adau17x1_has_dsp(struct adau *adau); + +#define ADAU17X1_CLOCK_CONTROL 0x4000 +#define ADAU17X1_PLL_CONTROL 0x4002 +#define ADAU17X1_REC_POWER_MGMT 0x4009 +#define ADAU17X1_MICBIAS 0x4010 +#define ADAU17X1_SERIAL_PORT0 0x4015 +#define ADAU17X1_SERIAL_PORT1 0x4016 +#define ADAU17X1_CONVERTER0 0x4017 +#define ADAU17X1_CONVERTER1 0x4018 +#define ADAU17X1_LEFT_INPUT_DIGITAL_VOL 0x401a +#define ADAU17X1_RIGHT_INPUT_DIGITAL_VOL 0x401b +#define ADAU17X1_ADC_CONTROL 0x4019 +#define ADAU17X1_PLAY_POWER_MGMT 0x4029 +#define ADAU17X1_DAC_CONTROL0 0x402a +#define ADAU17X1_DAC_CONTROL1 0x402b +#define ADAU17X1_DAC_CONTROL2 0x402c +#define ADAU17X1_SERIAL_PORT_PAD 0x402d +#define ADAU17X1_CONTROL_PORT_PAD0 0x402f +#define ADAU17X1_CONTROL_PORT_PAD1 0x4030 +#define ADAU17X1_DSP_SAMPLING_RATE 0x40eb +#define ADAU17X1_SERIAL_INPUT_ROUTE 0x40f2 +#define ADAU17X1_SERIAL_OUTPUT_ROUTE 0x40f3 +#define ADAU17X1_DSP_ENABLE 0x40f5 +#define ADAU17X1_DSP_RUN 0x40f6 +#define ADAU17X1_SERIAL_SAMPLING_RATE 0x40f8 + +#define ADAU17X1_SERIAL_PORT0_BCLK_POL BIT(4) +#define ADAU17X1_SERIAL_PORT0_LRCLK_POL BIT(3) +#define ADAU17X1_SERIAL_PORT0_MASTER BIT(0) + +#define ADAU17X1_SERIAL_PORT1_DELAY1 0x00 +#define ADAU17X1_SERIAL_PORT1_DELAY0 0x01 +#define ADAU17X1_SERIAL_PORT1_DELAY8 0x02 +#define ADAU17X1_SERIAL_PORT1_DELAY16 0x03 +#define ADAU17X1_SERIAL_PORT1_DELAY_MASK 0x03 + +#define ADAU17X1_CLOCK_CONTROL_INFREQ_MASK 0x6 +#define ADAU17X1_CLOCK_CONTROL_CORECLK_SRC_PLL BIT(3) +#define ADAU17X1_CLOCK_CONTROL_SYSCLK_EN BIT(0) + +#define ADAU17X1_SERIAL_PORT1_BCLK64 (0x0 << 5) +#define ADAU17X1_SERIAL_PORT1_BCLK32 (0x1 << 5) +#define ADAU17X1_SERIAL_PORT1_BCLK48 (0x2 << 5) +#define ADAU17X1_SERIAL_PORT1_BCLK128 (0x3 << 5) +#define ADAU17X1_SERIAL_PORT1_BCLK256 (0x4 << 5) +#define ADAU17X1_SERIAL_PORT1_BCLK_MASK (0x7 << 5) + +#define ADAU17X1_SERIAL_PORT0_STEREO (0x0 << 1) +#define ADAU17X1_SERIAL_PORT0_TDM4 (0x1 << 1) +#define ADAU17X1_SERIAL_PORT0_TDM8 (0x2 << 1) +#define ADAU17X1_SERIAL_PORT0_TDM_MASK (0x3 << 1) +#define ADAU17X1_SERIAL_PORT0_PULSE_MODE BIT(5) + +#define ADAU17X1_CONVERTER0_DAC_PAIR(x) (((x) - 1) << 5) +#define ADAU17X1_CONVERTER0_DAC_PAIR_MASK (0x3 << 5) +#define ADAU17X1_CONVERTER1_ADC_PAIR(x) ((x) - 1) +#define ADAU17X1_CONVERTER1_ADC_PAIR_MASK 0x3 + +#define ADAU17X1_CONVERTER0_CONVSR_MASK 0x7 + +#define ADAU17X1_CONVERTER0_ADOSR BIT(3) + + +#endif diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c new file mode 100644 index 000000000..e7fe1ee5c --- /dev/null +++ b/sound/soc/codecs/adau1977-i2c.c @@ -0,0 +1,51 @@ +/* + * ADAU1977/ADAU1978/ADAU1979 driver + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include "adau1977.h" + +static int adau1977_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap_config config; + + config = adau1977_regmap_config; + config.val_bits = 8; + config.reg_bits = 8; + + return adau1977_probe(&client->dev, + devm_regmap_init_i2c(client, &config), + id->driver_data, NULL); +} + +static const struct i2c_device_id adau1977_i2c_ids[] = { + { "adau1977", ADAU1977 }, + { "adau1978", ADAU1978 }, + { "adau1979", ADAU1978 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); + +static struct i2c_driver adau1977_i2c_driver = { + .driver = { + .name = "adau1977", + }, + .probe = adau1977_i2c_probe, + .id_table = adau1977_i2c_ids, +}; +module_i2c_driver(adau1977_i2c_driver); + +MODULE_DESCRIPTION("ASoC ADAU1977/ADAU1978/ADAU1979 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c new file mode 100644 index 000000000..84ffbde95 --- /dev/null +++ b/sound/soc/codecs/adau1977-spi.c @@ -0,0 +1,68 @@ +/* + * ADAU1977/ADAU1978/ADAU1979 driver + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include "adau1977.h" + +static void adau1977_spi_switch_mode(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + /* + * To get the device into SPI mode CLATCH has to be pulled low three + * times. Do this by issuing three dummy reads. + */ + spi_w8r8(spi, 0x00); + spi_w8r8(spi, 0x00); + spi_w8r8(spi, 0x00); +} + +static int adau1977_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap_config config; + + if (!id) + return -EINVAL; + + config = adau1977_regmap_config; + config.val_bits = 8; + config.reg_bits = 16; + config.read_flag_mask = 0x1; + + return adau1977_probe(&spi->dev, + devm_regmap_init_spi(spi, &config), + id->driver_data, adau1977_spi_switch_mode); +} + +static const struct spi_device_id adau1977_spi_ids[] = { + { "adau1977", ADAU1977 }, + { "adau1978", ADAU1978 }, + { "adau1979", ADAU1978 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); + +static struct spi_driver adau1977_spi_driver = { + .driver = { + .name = "adau1977", + }, + .probe = adau1977_spi_probe, + .id_table = adau1977_spi_ids, +}; +module_spi_driver(adau1977_spi_driver); + +MODULE_DESCRIPTION("ASoC ADAU1977/ADAU1978/ADAU1979 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1977.c b/sound/soc/codecs/adau1977.c new file mode 100644 index 000000000..116af6a9c --- /dev/null +++ b/sound/soc/codecs/adau1977.c @@ -0,0 +1,1006 @@ +/* + * ADAU1977/ADAU1978/ADAU1979 driver + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "adau1977.h" + +#define ADAU1977_REG_POWER 0x00 +#define ADAU1977_REG_PLL 0x01 +#define ADAU1977_REG_BOOST 0x02 +#define ADAU1977_REG_MICBIAS 0x03 +#define ADAU1977_REG_BLOCK_POWER_SAI 0x04 +#define ADAU1977_REG_SAI_CTRL0 0x05 +#define ADAU1977_REG_SAI_CTRL1 0x06 +#define ADAU1977_REG_CMAP12 0x07 +#define ADAU1977_REG_CMAP34 0x08 +#define ADAU1977_REG_SAI_OVERTEMP 0x09 +#define ADAU1977_REG_POST_ADC_GAIN(x) (0x0a + (x)) +#define ADAU1977_REG_MISC_CONTROL 0x0e +#define ADAU1977_REG_DIAG_CONTROL 0x10 +#define ADAU1977_REG_STATUS(x) (0x11 + (x)) +#define ADAU1977_REG_DIAG_IRQ1 0x15 +#define ADAU1977_REG_DIAG_IRQ2 0x16 +#define ADAU1977_REG_ADJUST1 0x17 +#define ADAU1977_REG_ADJUST2 0x18 +#define ADAU1977_REG_ADC_CLIP 0x19 +#define ADAU1977_REG_DC_HPF_CAL 0x1a + +#define ADAU1977_POWER_RESET BIT(7) +#define ADAU1977_POWER_PWUP BIT(0) + +#define ADAU1977_PLL_CLK_S BIT(4) +#define ADAU1977_PLL_MCS_MASK 0x7 + +#define ADAU1977_MICBIAS_MB_VOLTS_MASK 0xf0 +#define ADAU1977_MICBIAS_MB_VOLTS_OFFSET 4 + +#define ADAU1977_BLOCK_POWER_SAI_LR_POL BIT(7) +#define ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE BIT(6) +#define ADAU1977_BLOCK_POWER_SAI_LDO_EN BIT(5) + +#define ADAU1977_SAI_CTRL0_FMT_MASK (0x3 << 6) +#define ADAU1977_SAI_CTRL0_FMT_I2S (0x0 << 6) +#define ADAU1977_SAI_CTRL0_FMT_LJ (0x1 << 6) +#define ADAU1977_SAI_CTRL0_FMT_RJ_24BIT (0x2 << 6) +#define ADAU1977_SAI_CTRL0_FMT_RJ_16BIT (0x3 << 6) + +#define ADAU1977_SAI_CTRL0_SAI_MASK (0x7 << 3) +#define ADAU1977_SAI_CTRL0_SAI_I2S (0x0 << 3) +#define ADAU1977_SAI_CTRL0_SAI_TDM_2 (0x1 << 3) +#define ADAU1977_SAI_CTRL0_SAI_TDM_4 (0x2 << 3) +#define ADAU1977_SAI_CTRL0_SAI_TDM_8 (0x3 << 3) +#define ADAU1977_SAI_CTRL0_SAI_TDM_16 (0x4 << 3) + +#define ADAU1977_SAI_CTRL0_FS_MASK (0x7) +#define ADAU1977_SAI_CTRL0_FS_8000_12000 (0x0) +#define ADAU1977_SAI_CTRL0_FS_16000_24000 (0x1) +#define ADAU1977_SAI_CTRL0_FS_32000_48000 (0x2) +#define ADAU1977_SAI_CTRL0_FS_64000_96000 (0x3) +#define ADAU1977_SAI_CTRL0_FS_128000_192000 (0x4) + +#define ADAU1977_SAI_CTRL1_SLOT_WIDTH_MASK (0x3 << 5) +#define ADAU1977_SAI_CTRL1_SLOT_WIDTH_32 (0x0 << 5) +#define ADAU1977_SAI_CTRL1_SLOT_WIDTH_24 (0x1 << 5) +#define ADAU1977_SAI_CTRL1_SLOT_WIDTH_16 (0x2 << 5) +#define ADAU1977_SAI_CTRL1_DATA_WIDTH_MASK (0x1 << 4) +#define ADAU1977_SAI_CTRL1_DATA_WIDTH_16BIT (0x1 << 4) +#define ADAU1977_SAI_CTRL1_DATA_WIDTH_24BIT (0x0 << 4) +#define ADAU1977_SAI_CTRL1_LRCLK_PULSE BIT(3) +#define ADAU1977_SAI_CTRL1_MSB BIT(2) +#define ADAU1977_SAI_CTRL1_BCLKRATE_16 (0x1 << 1) +#define ADAU1977_SAI_CTRL1_BCLKRATE_32 (0x0 << 1) +#define ADAU1977_SAI_CTRL1_BCLKRATE_MASK (0x1 << 1) +#define ADAU1977_SAI_CTRL1_MASTER BIT(0) + +#define ADAU1977_SAI_OVERTEMP_DRV_C(x) BIT(4 + (x)) +#define ADAU1977_SAI_OVERTEMP_DRV_HIZ BIT(3) + +#define ADAU1977_MISC_CONTROL_SUM_MODE_MASK (0x3 << 6) +#define ADAU1977_MISC_CONTROL_SUM_MODE_1CH (0x2 << 6) +#define ADAU1977_MISC_CONTROL_SUM_MODE_2CH (0x1 << 6) +#define ADAU1977_MISC_CONTROL_SUM_MODE_4CH (0x0 << 6) +#define ADAU1977_MISC_CONTROL_MMUTE BIT(4) +#define ADAU1977_MISC_CONTROL_DC_CAL BIT(0) + +#define ADAU1977_CHAN_MAP_SECOND_SLOT_OFFSET 4 +#define ADAU1977_CHAN_MAP_FIRST_SLOT_OFFSET 0 + +struct adau1977 { + struct regmap *regmap; + bool right_j; + unsigned int sysclk; + enum adau1977_sysclk_src sysclk_src; + struct gpio_desc *reset_gpio; + enum adau1977_type type; + + struct regulator *avdd_reg; + struct regulator *dvdd_reg; + + struct snd_pcm_hw_constraint_list constraints; + + struct device *dev; + void (*switch_mode)(struct device *dev); + + unsigned int max_master_fs; + unsigned int slot_width; + bool enabled; + bool master; +}; + +static const struct reg_default adau1977_reg_defaults[] = { + { 0x00, 0x00 }, + { 0x01, 0x41 }, + { 0x02, 0x4a }, + { 0x03, 0x7d }, + { 0x04, 0x3d }, + { 0x05, 0x02 }, + { 0x06, 0x00 }, + { 0x07, 0x10 }, + { 0x08, 0x32 }, + { 0x09, 0xf0 }, + { 0x0a, 0xa0 }, + { 0x0b, 0xa0 }, + { 0x0c, 0xa0 }, + { 0x0d, 0xa0 }, + { 0x0e, 0x02 }, + { 0x10, 0x0f }, + { 0x15, 0x20 }, + { 0x16, 0x00 }, + { 0x17, 0x00 }, + { 0x18, 0x00 }, + { 0x1a, 0x00 }, +}; + +static const DECLARE_TLV_DB_MINMAX_MUTE(adau1977_adc_gain, -3562, 6000); + +static const struct snd_soc_dapm_widget adau1977_micbias_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU1977_REG_MICBIAS, + 3, 0, NULL, 0) +}; + +static const struct snd_soc_dapm_widget adau1977_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Vref", ADAU1977_REG_BLOCK_POWER_SAI, + 4, 0, NULL, 0), + + SND_SOC_DAPM_ADC("ADC1", "Capture", ADAU1977_REG_BLOCK_POWER_SAI, 0, 0), + SND_SOC_DAPM_ADC("ADC2", "Capture", ADAU1977_REG_BLOCK_POWER_SAI, 1, 0), + SND_SOC_DAPM_ADC("ADC3", "Capture", ADAU1977_REG_BLOCK_POWER_SAI, 2, 0), + SND_SOC_DAPM_ADC("ADC4", "Capture", ADAU1977_REG_BLOCK_POWER_SAI, 3, 0), + + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + SND_SOC_DAPM_INPUT("AIN3"), + SND_SOC_DAPM_INPUT("AIN4"), + + SND_SOC_DAPM_OUTPUT("VREF"), +}; + +static const struct snd_soc_dapm_route adau1977_dapm_routes[] = { + { "ADC1", NULL, "AIN1" }, + { "ADC2", NULL, "AIN2" }, + { "ADC3", NULL, "AIN3" }, + { "ADC4", NULL, "AIN4" }, + + { "ADC1", NULL, "Vref" }, + { "ADC2", NULL, "Vref" }, + { "ADC3", NULL, "Vref" }, + { "ADC4", NULL, "Vref" }, + + { "VREF", NULL, "Vref" }, +}; + +#define ADAU1977_VOLUME(x) \ + SOC_SINGLE_TLV("ADC" #x " Capture Volume", \ + ADAU1977_REG_POST_ADC_GAIN((x) - 1), \ + 0, 255, 1, adau1977_adc_gain) + +#define ADAU1977_HPF_SWITCH(x) \ + SOC_SINGLE("ADC" #x " Highpass-Filter Capture Switch", \ + ADAU1977_REG_DC_HPF_CAL, (x) - 1, 1, 0) + +#define ADAU1977_DC_SUB_SWITCH(x) \ + SOC_SINGLE("ADC" #x " DC Subtraction Capture Switch", \ + ADAU1977_REG_DC_HPF_CAL, (x) + 3, 1, 0) + +static const struct snd_kcontrol_new adau1977_snd_controls[] = { + ADAU1977_VOLUME(1), + ADAU1977_VOLUME(2), + ADAU1977_VOLUME(3), + ADAU1977_VOLUME(4), + + ADAU1977_HPF_SWITCH(1), + ADAU1977_HPF_SWITCH(2), + ADAU1977_HPF_SWITCH(3), + ADAU1977_HPF_SWITCH(4), + + ADAU1977_DC_SUB_SWITCH(1), + ADAU1977_DC_SUB_SWITCH(2), + ADAU1977_DC_SUB_SWITCH(3), + ADAU1977_DC_SUB_SWITCH(4), +}; + +static int adau1977_reset(struct adau1977 *adau1977) +{ + int ret; + + /* + * The reset bit is obviously volatile, but we need to be able to cache + * the other bits in the register, so we can't just mark the whole + * register as volatile. Since this is the only place where we'll ever + * touch the reset bit just bypass the cache for this operation. + */ + regcache_cache_bypass(adau1977->regmap, true); + ret = regmap_write(adau1977->regmap, ADAU1977_REG_POWER, + ADAU1977_POWER_RESET); + regcache_cache_bypass(adau1977->regmap, false); + if (ret) + return ret; + + return ret; +} + +/* + * Returns the appropriate setting for ths FS field in the CTRL0 register + * depending on the rate. + */ +static int adau1977_lookup_fs(unsigned int rate) +{ + if (rate >= 8000 && rate <= 12000) + return ADAU1977_SAI_CTRL0_FS_8000_12000; + else if (rate >= 16000 && rate <= 24000) + return ADAU1977_SAI_CTRL0_FS_16000_24000; + else if (rate >= 32000 && rate <= 48000) + return ADAU1977_SAI_CTRL0_FS_32000_48000; + else if (rate >= 64000 && rate <= 96000) + return ADAU1977_SAI_CTRL0_FS_64000_96000; + else if (rate >= 128000 && rate <= 192000) + return ADAU1977_SAI_CTRL0_FS_128000_192000; + else + return -EINVAL; +} + +static int adau1977_lookup_mcs(struct adau1977 *adau1977, unsigned int rate, + unsigned int fs) +{ + unsigned int mcs; + + /* + * rate = sysclk / (512 * mcs_lut[mcs]) * 2**fs + * => mcs_lut[mcs] = sysclk / (512 * rate) * 2**fs + * => mcs_lut[mcs] = sysclk / ((512 / 2**fs) * rate) + */ + + rate *= 512 >> fs; + + if (adau1977->sysclk % rate != 0) + return -EINVAL; + + mcs = adau1977->sysclk / rate; + + /* The factors configured by MCS are 1, 2, 3, 4, 6 */ + if (mcs < 1 || mcs > 6 || mcs == 5) + return -EINVAL; + + mcs = mcs - 1; + if (mcs == 5) + mcs = 4; + + return mcs; +} + +static int adau1977_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(component); + unsigned int rate = params_rate(params); + unsigned int slot_width; + unsigned int ctrl0, ctrl0_mask; + unsigned int ctrl1; + int mcs, fs; + int ret; + + fs = adau1977_lookup_fs(rate); + if (fs < 0) + return fs; + + if (adau1977->sysclk_src == ADAU1977_SYSCLK_SRC_MCLK) { + mcs = adau1977_lookup_mcs(adau1977, rate, fs); + if (mcs < 0) + return mcs; + } else { + mcs = 0; + } + + ctrl0_mask = ADAU1977_SAI_CTRL0_FS_MASK; + ctrl0 = fs; + + if (adau1977->right_j) { + switch (params_width(params)) { + case 16: + ctrl0 |= ADAU1977_SAI_CTRL0_FMT_RJ_16BIT; + break; + case 24: + ctrl0 |= ADAU1977_SAI_CTRL0_FMT_RJ_24BIT; + break; + default: + return -EINVAL; + } + ctrl0_mask |= ADAU1977_SAI_CTRL0_FMT_MASK; + } + + if (adau1977->master) { + switch (params_width(params)) { + case 16: + ctrl1 = ADAU1977_SAI_CTRL1_DATA_WIDTH_16BIT; + slot_width = 16; + break; + case 24: + case 32: + ctrl1 = ADAU1977_SAI_CTRL1_DATA_WIDTH_24BIT; + slot_width = 32; + break; + default: + return -EINVAL; + } + + /* In TDM mode there is a fixed slot width */ + if (adau1977->slot_width) + slot_width = adau1977->slot_width; + + if (slot_width == 16) + ctrl1 |= ADAU1977_SAI_CTRL1_BCLKRATE_16; + else + ctrl1 |= ADAU1977_SAI_CTRL1_BCLKRATE_32; + + ret = regmap_update_bits(adau1977->regmap, + ADAU1977_REG_SAI_CTRL1, + ADAU1977_SAI_CTRL1_DATA_WIDTH_MASK | + ADAU1977_SAI_CTRL1_BCLKRATE_MASK, + ctrl1); + if (ret < 0) + return ret; + } + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_CTRL0, + ctrl0_mask, ctrl0); + if (ret < 0) + return ret; + + return regmap_update_bits(adau1977->regmap, ADAU1977_REG_PLL, + ADAU1977_PLL_MCS_MASK, mcs); +} + +static int adau1977_power_disable(struct adau1977 *adau1977) +{ + int ret = 0; + + if (!adau1977->enabled) + return 0; + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_POWER, + ADAU1977_POWER_PWUP, 0); + if (ret) + return ret; + + regcache_mark_dirty(adau1977->regmap); + + gpiod_set_value_cansleep(adau1977->reset_gpio, 0); + + regcache_cache_only(adau1977->regmap, true); + + regulator_disable(adau1977->avdd_reg); + if (adau1977->dvdd_reg) + regulator_disable(adau1977->dvdd_reg); + + adau1977->enabled = false; + + return 0; +} + +static int adau1977_power_enable(struct adau1977 *adau1977) +{ + unsigned int val; + int ret = 0; + + if (adau1977->enabled) + return 0; + + ret = regulator_enable(adau1977->avdd_reg); + if (ret) + return ret; + + if (adau1977->dvdd_reg) { + ret = regulator_enable(adau1977->dvdd_reg); + if (ret) + goto err_disable_avdd; + } + + gpiod_set_value_cansleep(adau1977->reset_gpio, 1); + + regcache_cache_only(adau1977->regmap, false); + + if (adau1977->switch_mode) + adau1977->switch_mode(adau1977->dev); + + ret = adau1977_reset(adau1977); + if (ret) + goto err_disable_dvdd; + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_POWER, + ADAU1977_POWER_PWUP, ADAU1977_POWER_PWUP); + if (ret) + goto err_disable_dvdd; + + ret = regcache_sync(adau1977->regmap); + if (ret) + goto err_disable_dvdd; + + /* + * The PLL register is not affected by the software reset. It is + * possible that the value of the register was changed to the + * default value while we were in cache only mode. In this case + * regcache_sync will skip over it and we have to manually sync + * it. + */ + ret = regmap_read(adau1977->regmap, ADAU1977_REG_PLL, &val); + if (ret) + goto err_disable_dvdd; + + if (val == 0x41) { + regcache_cache_bypass(adau1977->regmap, true); + ret = regmap_write(adau1977->regmap, ADAU1977_REG_PLL, + 0x41); + if (ret) + goto err_disable_dvdd; + regcache_cache_bypass(adau1977->regmap, false); + } + + adau1977->enabled = true; + + return ret; + +err_disable_dvdd: + if (adau1977->dvdd_reg) + regulator_disable(adau1977->dvdd_reg); +err_disable_avdd: + regulator_disable(adau1977->avdd_reg); + return ret; +} + +static int adau1977_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + ret = adau1977_power_enable(adau1977); + break; + case SND_SOC_BIAS_OFF: + ret = adau1977_power_disable(adau1977); + break; + } + + return ret; +} + +static int adau1977_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(dai->component); + unsigned int ctrl0, ctrl1, drv; + unsigned int slot[4]; + unsigned int i; + int ret; + + if (slots == 0) { + /* 0 = No fixed slot width */ + adau1977->slot_width = 0; + adau1977->max_master_fs = 192000; + return regmap_update_bits(adau1977->regmap, + ADAU1977_REG_SAI_CTRL0, ADAU1977_SAI_CTRL0_SAI_MASK, + ADAU1977_SAI_CTRL0_SAI_I2S); + } + + if (rx_mask == 0 || tx_mask != 0) + return -EINVAL; + + drv = 0; + for (i = 0; i < 4; i++) { + slot[i] = __ffs(rx_mask); + drv |= ADAU1977_SAI_OVERTEMP_DRV_C(i); + rx_mask &= ~(1 << slot[i]); + if (slot[i] >= slots) + return -EINVAL; + if (rx_mask == 0) + break; + } + + if (rx_mask != 0) + return -EINVAL; + + switch (width) { + case 16: + ctrl1 = ADAU1977_SAI_CTRL1_SLOT_WIDTH_16; + break; + case 24: + /* We can only generate 16 bit or 32 bit wide slots */ + if (adau1977->master) + return -EINVAL; + ctrl1 = ADAU1977_SAI_CTRL1_SLOT_WIDTH_24; + break; + case 32: + ctrl1 = ADAU1977_SAI_CTRL1_SLOT_WIDTH_32; + break; + default: + return -EINVAL; + } + + switch (slots) { + case 2: + ctrl0 = ADAU1977_SAI_CTRL0_SAI_TDM_2; + break; + case 4: + ctrl0 = ADAU1977_SAI_CTRL0_SAI_TDM_4; + break; + case 8: + ctrl0 = ADAU1977_SAI_CTRL0_SAI_TDM_8; + break; + case 16: + ctrl0 = ADAU1977_SAI_CTRL0_SAI_TDM_16; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_OVERTEMP, + ADAU1977_SAI_OVERTEMP_DRV_C(0) | + ADAU1977_SAI_OVERTEMP_DRV_C(1) | + ADAU1977_SAI_OVERTEMP_DRV_C(2) | + ADAU1977_SAI_OVERTEMP_DRV_C(3), drv); + if (ret) + return ret; + + ret = regmap_write(adau1977->regmap, ADAU1977_REG_CMAP12, + (slot[1] << ADAU1977_CHAN_MAP_SECOND_SLOT_OFFSET) | + (slot[0] << ADAU1977_CHAN_MAP_FIRST_SLOT_OFFSET)); + if (ret) + return ret; + + ret = regmap_write(adau1977->regmap, ADAU1977_REG_CMAP34, + (slot[3] << ADAU1977_CHAN_MAP_SECOND_SLOT_OFFSET) | + (slot[2] << ADAU1977_CHAN_MAP_FIRST_SLOT_OFFSET)); + if (ret) + return ret; + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_CTRL0, + ADAU1977_SAI_CTRL0_SAI_MASK, ctrl0); + if (ret) + return ret; + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_CTRL1, + ADAU1977_SAI_CTRL1_SLOT_WIDTH_MASK, ctrl1); + if (ret) + return ret; + + adau1977->slot_width = width; + + /* In master mode the maximum bitclock is 24.576 MHz */ + adau1977->max_master_fs = min(192000, 24576000 / width / slots); + + return 0; +} + +static int adau1977_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(dai->component); + unsigned int val; + + if (mute) + val = ADAU1977_MISC_CONTROL_MMUTE; + else + val = 0; + + return regmap_update_bits(adau1977->regmap, ADAU1977_REG_MISC_CONTROL, + ADAU1977_MISC_CONTROL_MMUTE, val); +} + +static int adau1977_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(dai->component); + unsigned int ctrl0 = 0, ctrl1 = 0, block_power = 0; + bool invert_lrclk; + int ret; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + adau1977->master = false; + break; + case SND_SOC_DAIFMT_CBM_CFM: + ctrl1 |= ADAU1977_SAI_CTRL1_MASTER; + adau1977->master = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_lrclk = false; + break; + case SND_SOC_DAIFMT_IB_NF: + block_power |= ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE; + invert_lrclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + invert_lrclk = true; + break; + case SND_SOC_DAIFMT_IB_IF: + block_power |= ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE; + invert_lrclk = true; + break; + default: + return -EINVAL; + } + + adau1977->right_j = false; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl0 |= ADAU1977_SAI_CTRL0_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl0 |= ADAU1977_SAI_CTRL0_FMT_LJ; + invert_lrclk = !invert_lrclk; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl0 |= ADAU1977_SAI_CTRL0_FMT_RJ_24BIT; + adau1977->right_j = true; + invert_lrclk = !invert_lrclk; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1 |= ADAU1977_SAI_CTRL1_LRCLK_PULSE; + ctrl0 |= ADAU1977_SAI_CTRL0_FMT_I2S; + invert_lrclk = false; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1 |= ADAU1977_SAI_CTRL1_LRCLK_PULSE; + ctrl0 |= ADAU1977_SAI_CTRL0_FMT_LJ; + invert_lrclk = false; + break; + default: + return -EINVAL; + } + + if (invert_lrclk) + block_power |= ADAU1977_BLOCK_POWER_SAI_LR_POL; + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_BLOCK_POWER_SAI, + ADAU1977_BLOCK_POWER_SAI_LR_POL | + ADAU1977_BLOCK_POWER_SAI_BCLK_EDGE, block_power); + if (ret) + return ret; + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_CTRL0, + ADAU1977_SAI_CTRL0_FMT_MASK, + ctrl0); + if (ret) + return ret; + + return regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_CTRL1, + ADAU1977_SAI_CTRL1_MASTER | ADAU1977_SAI_CTRL1_LRCLK_PULSE, + ctrl1); +} + +static int adau1977_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(dai->component); + u64 formats = 0; + + if (adau1977->slot_width == 16) + formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE; + else if (adau1977->right_j || adau1977->slot_width == 24) + formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &adau1977->constraints); + + if (adau1977->master) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 8000, adau1977->max_master_fs); + + if (formats != 0) + snd_pcm_hw_constraint_mask64(substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, formats); + + return 0; +} + +static int adau1977_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(dai->component); + unsigned int val; + + if (tristate) + val = ADAU1977_SAI_OVERTEMP_DRV_HIZ; + else + val = 0; + + return regmap_update_bits(adau1977->regmap, ADAU1977_REG_SAI_OVERTEMP, + ADAU1977_SAI_OVERTEMP_DRV_HIZ, val); +} + +static const struct snd_soc_dai_ops adau1977_dai_ops = { + .startup = adau1977_startup, + .hw_params = adau1977_hw_params, + .mute_stream = adau1977_mute, + .set_fmt = adau1977_set_dai_fmt, + .set_tdm_slot = adau1977_set_tdm_slot, + .set_tristate = adau1977_set_tristate, +}; + +static struct snd_soc_dai_driver adau1977_dai = { + .name = "adau1977-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 24, + }, + .ops = &adau1977_dai_ops, +}; + +static const unsigned int adau1977_rates[] = { + 8000, 16000, 32000, 64000, 128000, + 11025, 22050, 44100, 88200, 172400, + 12000, 24000, 48000, 96000, 192000, +}; + +#define ADAU1977_RATE_CONSTRAINT_MASK_32000 0x001f +#define ADAU1977_RATE_CONSTRAINT_MASK_44100 0x03e0 +#define ADAU1977_RATE_CONSTRAINT_MASK_48000 0x7c00 +/* All rates >= 32000 */ +#define ADAU1977_RATE_CONSTRAINT_MASK_LRCLK 0x739c + +static bool adau1977_check_sysclk(unsigned int mclk, unsigned int base_freq) +{ + unsigned int mcs; + + if (mclk % (base_freq * 128) != 0) + return false; + + mcs = mclk / (128 * base_freq); + if (mcs < 1 || mcs > 6 || mcs == 5) + return false; + + return true; +} + +static int adau1977_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(component); + unsigned int mask = 0; + unsigned int clk_src; + unsigned int ret; + + if (dir != SND_SOC_CLOCK_IN) + return -EINVAL; + + if (clk_id != ADAU1977_SYSCLK) + return -EINVAL; + + switch (source) { + case ADAU1977_SYSCLK_SRC_MCLK: + clk_src = 0; + break; + case ADAU1977_SYSCLK_SRC_LRCLK: + clk_src = ADAU1977_PLL_CLK_S; + break; + default: + return -EINVAL; + } + + if (freq != 0 && source == ADAU1977_SYSCLK_SRC_MCLK) { + if (freq < 4000000 || freq > 36864000) + return -EINVAL; + + if (adau1977_check_sysclk(freq, 32000)) + mask |= ADAU1977_RATE_CONSTRAINT_MASK_32000; + if (adau1977_check_sysclk(freq, 44100)) + mask |= ADAU1977_RATE_CONSTRAINT_MASK_44100; + if (adau1977_check_sysclk(freq, 48000)) + mask |= ADAU1977_RATE_CONSTRAINT_MASK_48000; + + if (mask == 0) + return -EINVAL; + } else if (source == ADAU1977_SYSCLK_SRC_LRCLK) { + mask = ADAU1977_RATE_CONSTRAINT_MASK_LRCLK; + } + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_PLL, + ADAU1977_PLL_CLK_S, clk_src); + if (ret) + return ret; + + adau1977->constraints.mask = mask; + adau1977->sysclk_src = source; + adau1977->sysclk = freq; + + return 0; +} + +static int adau1977_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adau1977 *adau1977 = snd_soc_component_get_drvdata(component); + int ret; + + switch (adau1977->type) { + case ADAU1977: + ret = snd_soc_dapm_new_controls(dapm, + adau1977_micbias_dapm_widgets, + ARRAY_SIZE(adau1977_micbias_dapm_widgets)); + if (ret < 0) + return ret; + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_component_driver adau1977_component_driver = { + .probe = adau1977_component_probe, + .set_bias_level = adau1977_set_bias_level, + .set_sysclk = adau1977_set_sysclk, + .controls = adau1977_snd_controls, + .num_controls = ARRAY_SIZE(adau1977_snd_controls), + .dapm_widgets = adau1977_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1977_dapm_widgets), + .dapm_routes = adau1977_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1977_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int adau1977_setup_micbias(struct adau1977 *adau1977) +{ + struct adau1977_platform_data *pdata = adau1977->dev->platform_data; + unsigned int micbias; + + if (pdata) { + micbias = pdata->micbias; + if (micbias > ADAU1977_MICBIAS_9V0) + return -EINVAL; + + } else { + micbias = ADAU1977_MICBIAS_8V5; + } + + return regmap_update_bits(adau1977->regmap, ADAU1977_REG_MICBIAS, + ADAU1977_MICBIAS_MB_VOLTS_MASK, + micbias << ADAU1977_MICBIAS_MB_VOLTS_OFFSET); +} + +int adau1977_probe(struct device *dev, struct regmap *regmap, + enum adau1977_type type, void (*switch_mode)(struct device *dev)) +{ + unsigned int power_off_mask; + struct adau1977 *adau1977; + int ret; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + adau1977 = devm_kzalloc(dev, sizeof(*adau1977), GFP_KERNEL); + if (adau1977 == NULL) + return -ENOMEM; + + adau1977->dev = dev; + adau1977->type = type; + adau1977->regmap = regmap; + adau1977->switch_mode = switch_mode; + adau1977->max_master_fs = 192000; + + adau1977->constraints.list = adau1977_rates; + adau1977->constraints.count = ARRAY_SIZE(adau1977_rates); + + adau1977->avdd_reg = devm_regulator_get(dev, "AVDD"); + if (IS_ERR(adau1977->avdd_reg)) + return PTR_ERR(adau1977->avdd_reg); + + adau1977->dvdd_reg = devm_regulator_get_optional(dev, "DVDD"); + if (IS_ERR(adau1977->dvdd_reg)) { + if (PTR_ERR(adau1977->dvdd_reg) != -ENODEV) + return PTR_ERR(adau1977->dvdd_reg); + adau1977->dvdd_reg = NULL; + } + + adau1977->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(adau1977->reset_gpio)) + return PTR_ERR(adau1977->reset_gpio); + + dev_set_drvdata(dev, adau1977); + + if (adau1977->reset_gpio) + ndelay(100); + + ret = adau1977_power_enable(adau1977); + if (ret) + return ret; + + if (type == ADAU1977) { + ret = adau1977_setup_micbias(adau1977); + if (ret) + goto err_poweroff; + } + + if (adau1977->dvdd_reg) + power_off_mask = ~0; + else + power_off_mask = (unsigned int)~ADAU1977_BLOCK_POWER_SAI_LDO_EN; + + ret = regmap_update_bits(adau1977->regmap, ADAU1977_REG_BLOCK_POWER_SAI, + power_off_mask, 0x00); + if (ret) + goto err_poweroff; + + ret = adau1977_power_disable(adau1977); + if (ret) + return ret; + + return devm_snd_soc_register_component(dev, &adau1977_component_driver, + &adau1977_dai, 1); + +err_poweroff: + adau1977_power_disable(adau1977); + return ret; + +} +EXPORT_SYMBOL_GPL(adau1977_probe); + +static bool adau1977_register_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADAU1977_REG_STATUS(0): + case ADAU1977_REG_STATUS(1): + case ADAU1977_REG_STATUS(2): + case ADAU1977_REG_STATUS(3): + case ADAU1977_REG_ADC_CLIP: + return true; + } + + return false; +} + +const struct regmap_config adau1977_regmap_config = { + .max_register = ADAU1977_REG_DC_HPF_CAL, + .volatile_reg = adau1977_register_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = adau1977_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adau1977_reg_defaults), +}; +EXPORT_SYMBOL_GPL(adau1977_regmap_config); + +MODULE_DESCRIPTION("ASoC ADAU1977/ADAU1978/ADAU1979 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1977.h b/sound/soc/codecs/adau1977.h new file mode 100644 index 000000000..95e714345 --- /dev/null +++ b/sound/soc/codecs/adau1977.h @@ -0,0 +1,37 @@ +/* + * ADAU1977/ADAU1978/ADAU1979 driver + * + * Copyright 2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#ifndef __SOUND_SOC_CODECS_ADAU1977_H__ +#define __SOUND_SOC_CODECS_ADAU1977_H__ + +#include + +struct device; + +enum adau1977_type { + ADAU1977, + ADAU1978, + ADAU1979, +}; + +int adau1977_probe(struct device *dev, struct regmap *regmap, + enum adau1977_type type, void (*switch_mode)(struct device *dev)); + +extern const struct regmap_config adau1977_regmap_config; + +enum adau1977_clk_id { + ADAU1977_SYSCLK, +}; + +enum adau1977_sysclk_src { + ADAU1977_SYSCLK_SRC_MCLK, + ADAU1977_SYSCLK_SRC_LRCLK, +}; + +#endif diff --git a/sound/soc/codecs/adau7002.c b/sound/soc/codecs/adau7002.c new file mode 100644 index 000000000..fdff86878 --- /dev/null +++ b/sound/soc/codecs/adau7002.c @@ -0,0 +1,94 @@ +/* + * ADAU7002 Stereo PDM-to-I2S/TDM converter driver + * + * Copyright 2014-2016 Analog Devices + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include + +static const struct snd_soc_dapm_widget adau7002_widgets[] = { + SND_SOC_DAPM_INPUT("PDM_DAT"), + SND_SOC_DAPM_REGULATOR_SUPPLY("IOVDD", 0, 0), +}; + +static const struct snd_soc_dapm_route adau7002_routes[] = { + { "Capture", NULL, "PDM_DAT" }, + { "Capture", NULL, "IOVDD" }, +}; + +static struct snd_soc_dai_driver adau7002_dai = { + .name = "adau7002-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 20, + }, +}; + +static const struct snd_soc_component_driver adau7002_component_driver = { + .dapm_widgets = adau7002_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau7002_widgets), + .dapm_routes = adau7002_routes, + .num_dapm_routes = ARRAY_SIZE(adau7002_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int adau7002_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &adau7002_component_driver, + &adau7002_dai, 1); +} + +static int adau7002_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id adau7002_dt_ids[] = { + { .compatible = "adi,adau7002", }, + { } +}; +MODULE_DEVICE_TABLE(of, adau7002_dt_ids); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id adau7002_acpi_match[] = { + { "ADAU7002", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, adau7002_acpi_match); +#endif + +static struct platform_driver adau7002_driver = { + .driver = { + .name = "adau7002", + .of_match_table = of_match_ptr(adau7002_dt_ids), + .acpi_match_table = ACPI_PTR(adau7002_acpi_match), + }, + .probe = adau7002_probe, + .remove = adau7002_remove, +}; +module_platform_driver(adau7002_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ADAU7002 Stereo PDM-to-I2S/TDM Converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/adav801.c b/sound/soc/codecs/adav801.c new file mode 100644 index 000000000..d82f79dd1 --- /dev/null +++ b/sound/soc/codecs/adav801.c @@ -0,0 +1,45 @@ +/* + * ADAV801 audio driver + * + * Copyright 2014 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include + +#include + +#include "adav80x.h" + +static const struct spi_device_id adav80x_spi_id[] = { + { "adav801", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adav80x_spi_id); + +static int adav80x_spi_probe(struct spi_device *spi) +{ + struct regmap_config config; + + config = adav80x_regmap_config; + config.read_flag_mask = 0x01; + + return adav80x_bus_probe(&spi->dev, devm_regmap_init_spi(spi, &config)); +} + +static struct spi_driver adav80x_spi_driver = { + .driver = { + .name = "adav801", + }, + .probe = adav80x_spi_probe, + .id_table = adav80x_spi_id, +}; +module_spi_driver(adav80x_spi_driver); + +MODULE_DESCRIPTION("ASoC ADAV801 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_AUTHOR("Yi Li >"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adav803.c b/sound/soc/codecs/adav803.c new file mode 100644 index 000000000..deb14bc0f --- /dev/null +++ b/sound/soc/codecs/adav803.c @@ -0,0 +1,42 @@ +/* + * ADAV803 audio driver + * + * Copyright 2014 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include + +#include + +#include "adav80x.h" + +static const struct i2c_device_id adav803_id[] = { + { "adav803", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adav803_id); + +static int adav803_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return adav80x_bus_probe(&client->dev, + devm_regmap_init_i2c(client, &adav80x_regmap_config)); +} + +static struct i2c_driver adav803_driver = { + .driver = { + .name = "adav803", + }, + .probe = adav803_probe, + .id_table = adav803_id, +}; +module_i2c_driver(adav803_driver); + +MODULE_DESCRIPTION("ASoC ADAV803 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_AUTHOR("Yi Li >"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c new file mode 100644 index 000000000..8b9ca7e7a --- /dev/null +++ b/sound/soc/codecs/adav80x.c @@ -0,0 +1,883 @@ +/* + * ADAV80X Audio Codec driver supporting ADAV801, ADAV803 + * + * Copyright 2011 Analog Devices Inc. + * Author: Yi Li + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adav80x.h" + +#define ADAV80X_PLAYBACK_CTRL 0x04 +#define ADAV80X_AUX_IN_CTRL 0x05 +#define ADAV80X_REC_CTRL 0x06 +#define ADAV80X_AUX_OUT_CTRL 0x07 +#define ADAV80X_DPATH_CTRL1 0x62 +#define ADAV80X_DPATH_CTRL2 0x63 +#define ADAV80X_DAC_CTRL1 0x64 +#define ADAV80X_DAC_CTRL2 0x65 +#define ADAV80X_DAC_CTRL3 0x66 +#define ADAV80X_DAC_L_VOL 0x68 +#define ADAV80X_DAC_R_VOL 0x69 +#define ADAV80X_PGA_L_VOL 0x6c +#define ADAV80X_PGA_R_VOL 0x6d +#define ADAV80X_ADC_CTRL1 0x6e +#define ADAV80X_ADC_CTRL2 0x6f +#define ADAV80X_ADC_L_VOL 0x70 +#define ADAV80X_ADC_R_VOL 0x71 +#define ADAV80X_PLL_CTRL1 0x74 +#define ADAV80X_PLL_CTRL2 0x75 +#define ADAV80X_ICLK_CTRL1 0x76 +#define ADAV80X_ICLK_CTRL2 0x77 +#define ADAV80X_PLL_CLK_SRC 0x78 +#define ADAV80X_PLL_OUTE 0x7a + +#define ADAV80X_PLL_CLK_SRC_PLL_XIN(pll) 0x00 +#define ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll) (0x40 << (pll)) +#define ADAV80X_PLL_CLK_SRC_PLL_MASK(pll) (0x40 << (pll)) + +#define ADAV80X_ICLK_CTRL1_DAC_SRC(src) ((src) << 5) +#define ADAV80X_ICLK_CTRL1_ADC_SRC(src) ((src) << 2) +#define ADAV80X_ICLK_CTRL1_ICLK2_SRC(src) (src) +#define ADAV80X_ICLK_CTRL2_ICLK1_SRC(src) ((src) << 3) + +#define ADAV80X_PLL_CTRL1_PLLDIV 0x10 +#define ADAV80X_PLL_CTRL1_PLLPD(pll) (0x04 << (pll)) +#define ADAV80X_PLL_CTRL1_XTLPD 0x02 + +#define ADAV80X_PLL_CTRL2_FIELD(pll, x) ((x) << ((pll) * 4)) + +#define ADAV80X_PLL_CTRL2_FS_48(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x00) +#define ADAV80X_PLL_CTRL2_FS_32(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x08) +#define ADAV80X_PLL_CTRL2_FS_44(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0c) + +#define ADAV80X_PLL_CTRL2_SEL(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x02) +#define ADAV80X_PLL_CTRL2_DOUB(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x01) +#define ADAV80X_PLL_CTRL2_PLL_MASK(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0f) + +#define ADAV80X_ADC_CTRL1_MODULATOR_MASK 0x80 +#define ADAV80X_ADC_CTRL1_MODULATOR_128FS 0x00 +#define ADAV80X_ADC_CTRL1_MODULATOR_64FS 0x80 + +#define ADAV80X_DAC_CTRL1_PD 0x80 + +#define ADAV80X_DAC_CTRL2_DIV1 0x00 +#define ADAV80X_DAC_CTRL2_DIV1_5 0x10 +#define ADAV80X_DAC_CTRL2_DIV2 0x20 +#define ADAV80X_DAC_CTRL2_DIV3 0x30 +#define ADAV80X_DAC_CTRL2_DIV_MASK 0x30 + +#define ADAV80X_DAC_CTRL2_INTERPOL_256FS 0x00 +#define ADAV80X_DAC_CTRL2_INTERPOL_128FS 0x40 +#define ADAV80X_DAC_CTRL2_INTERPOL_64FS 0x80 +#define ADAV80X_DAC_CTRL2_INTERPOL_MASK 0xc0 + +#define ADAV80X_DAC_CTRL2_DEEMPH_NONE 0x00 +#define ADAV80X_DAC_CTRL2_DEEMPH_44 0x01 +#define ADAV80X_DAC_CTRL2_DEEMPH_32 0x02 +#define ADAV80X_DAC_CTRL2_DEEMPH_48 0x03 +#define ADAV80X_DAC_CTRL2_DEEMPH_MASK 0x01 + +#define ADAV80X_CAPTURE_MODE_MASTER 0x20 +#define ADAV80X_CAPTURE_WORD_LEN24 0x00 +#define ADAV80X_CAPTURE_WORD_LEN20 0x04 +#define ADAV80X_CAPTRUE_WORD_LEN18 0x08 +#define ADAV80X_CAPTURE_WORD_LEN16 0x0c +#define ADAV80X_CAPTURE_WORD_LEN_MASK 0x0c + +#define ADAV80X_CAPTURE_MODE_LEFT_J 0x00 +#define ADAV80X_CAPTURE_MODE_I2S 0x01 +#define ADAV80X_CAPTURE_MODE_RIGHT_J 0x03 +#define ADAV80X_CAPTURE_MODE_MASK 0x03 + +#define ADAV80X_PLAYBACK_MODE_MASTER 0x10 +#define ADAV80X_PLAYBACK_MODE_LEFT_J 0x00 +#define ADAV80X_PLAYBACK_MODE_I2S 0x01 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_24 0x04 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_20 0x05 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_18 0x06 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_16 0x07 +#define ADAV80X_PLAYBACK_MODE_MASK 0x07 + +#define ADAV80X_PLL_OUTE_SYSCLKPD(x) BIT(2 - (x)) + +static const struct reg_default adav80x_reg_defaults[] = { + { ADAV80X_PLAYBACK_CTRL, 0x01 }, + { ADAV80X_AUX_IN_CTRL, 0x01 }, + { ADAV80X_REC_CTRL, 0x02 }, + { ADAV80X_AUX_OUT_CTRL, 0x01 }, + { ADAV80X_DPATH_CTRL1, 0xc0 }, + { ADAV80X_DPATH_CTRL2, 0x11 }, + { ADAV80X_DAC_CTRL1, 0x00 }, + { ADAV80X_DAC_CTRL2, 0x00 }, + { ADAV80X_DAC_CTRL3, 0x00 }, + { ADAV80X_DAC_L_VOL, 0xff }, + { ADAV80X_DAC_R_VOL, 0xff }, + { ADAV80X_PGA_L_VOL, 0x00 }, + { ADAV80X_PGA_R_VOL, 0x00 }, + { ADAV80X_ADC_CTRL1, 0x00 }, + { ADAV80X_ADC_CTRL2, 0x00 }, + { ADAV80X_ADC_L_VOL, 0xff }, + { ADAV80X_ADC_R_VOL, 0xff }, + { ADAV80X_PLL_CTRL1, 0x00 }, + { ADAV80X_PLL_CTRL2, 0x00 }, + { ADAV80X_ICLK_CTRL1, 0x00 }, + { ADAV80X_ICLK_CTRL2, 0x00 }, + { ADAV80X_PLL_CLK_SRC, 0x00 }, + { ADAV80X_PLL_OUTE, 0x00 }, +}; + +struct adav80x { + struct regmap *regmap; + + enum adav80x_clk_src clk_src; + unsigned int sysclk; + enum adav80x_pll_src pll_src; + + unsigned int dai_fmt[2]; + unsigned int rate; + bool deemph; + bool sysclk_pd[3]; +}; + +static const char *adav80x_mux_text[] = { + "ADC", + "Playback", + "Aux Playback", +}; + +static const unsigned int adav80x_mux_values[] = { + 0, 2, 3, +}; + +#define ADAV80X_MUX_ENUM_DECL(name, reg, shift) \ + SOC_VALUE_ENUM_DOUBLE_DECL(name, reg, shift, 7, \ + ARRAY_SIZE(adav80x_mux_text), adav80x_mux_text, \ + adav80x_mux_values) + +static ADAV80X_MUX_ENUM_DECL(adav80x_aux_capture_enum, ADAV80X_DPATH_CTRL1, 0); +static ADAV80X_MUX_ENUM_DECL(adav80x_capture_enum, ADAV80X_DPATH_CTRL1, 3); +static ADAV80X_MUX_ENUM_DECL(adav80x_dac_enum, ADAV80X_DPATH_CTRL2, 3); + +static const struct snd_kcontrol_new adav80x_aux_capture_mux_ctrl = + SOC_DAPM_ENUM("Route", adav80x_aux_capture_enum); +static const struct snd_kcontrol_new adav80x_capture_mux_ctrl = + SOC_DAPM_ENUM("Route", adav80x_capture_enum); +static const struct snd_kcontrol_new adav80x_dac_mux_ctrl = + SOC_DAPM_ENUM("Route", adav80x_dac_enum); + +#define ADAV80X_MUX(name, ctrl) \ + SND_SOC_DAPM_MUX(name, SND_SOC_NOPM, 0, 0, ctrl) + +static const struct snd_soc_dapm_widget adav80x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, ADAV80X_DAC_CTRL1, 7, 1), + SND_SOC_DAPM_ADC("ADC", NULL, ADAV80X_ADC_CTRL1, 5, 1), + + SND_SOC_DAPM_PGA("Right PGA", ADAV80X_ADC_CTRL1, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Left PGA", ADAV80X_ADC_CTRL1, 1, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("AIFOUT", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFIN", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT("AIFAUXOUT", "Aux Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFAUXIN", "Aux Playback", 0, SND_SOC_NOPM, 0, 0), + + ADAV80X_MUX("Aux Capture Select", &adav80x_aux_capture_mux_ctrl), + ADAV80X_MUX("Capture Select", &adav80x_capture_mux_ctrl), + ADAV80X_MUX("DAC Select", &adav80x_dac_mux_ctrl), + + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + + SND_SOC_DAPM_SUPPLY("SYSCLK", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", ADAV80X_PLL_CTRL1, 2, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2", ADAV80X_PLL_CTRL1, 3, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("OSC", ADAV80X_PLL_CTRL1, 1, 1, NULL, 0), +}; + +static int adav80x_dapm_sysclk_check(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + const char *clk; + + switch (adav80x->clk_src) { + case ADAV80X_CLK_PLL1: + clk = "PLL1"; + break; + case ADAV80X_CLK_PLL2: + clk = "PLL2"; + break; + case ADAV80X_CLK_XTAL: + clk = "OSC"; + break; + default: + return 0; + } + + return strcmp(source->name, clk) == 0; +} + +static int adav80x_dapm_pll_check(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + + return adav80x->pll_src == ADAV80X_PLL_SRC_XTAL; +} + + +static const struct snd_soc_dapm_route adav80x_dapm_routes[] = { + { "DAC Select", "ADC", "ADC" }, + { "DAC Select", "Playback", "AIFIN" }, + { "DAC Select", "Aux Playback", "AIFAUXIN" }, + { "DAC", NULL, "DAC Select" }, + + { "Capture Select", "ADC", "ADC" }, + { "Capture Select", "Playback", "AIFIN" }, + { "Capture Select", "Aux Playback", "AIFAUXIN" }, + { "AIFOUT", NULL, "Capture Select" }, + + { "Aux Capture Select", "ADC", "ADC" }, + { "Aux Capture Select", "Playback", "AIFIN" }, + { "Aux Capture Select", "Aux Playback", "AIFAUXIN" }, + { "AIFAUXOUT", NULL, "Aux Capture Select" }, + + { "VOUTR", NULL, "DAC" }, + { "VOUTL", NULL, "DAC" }, + + { "Left PGA", NULL, "VINL" }, + { "Right PGA", NULL, "VINR" }, + { "ADC", NULL, "Left PGA" }, + { "ADC", NULL, "Right PGA" }, + + { "SYSCLK", NULL, "PLL1", adav80x_dapm_sysclk_check }, + { "SYSCLK", NULL, "PLL2", adav80x_dapm_sysclk_check }, + { "SYSCLK", NULL, "OSC", adav80x_dapm_sysclk_check }, + { "PLL1", NULL, "OSC", adav80x_dapm_pll_check }, + { "PLL2", NULL, "OSC", adav80x_dapm_pll_check }, + + { "ADC", NULL, "SYSCLK" }, + { "DAC", NULL, "SYSCLK" }, + { "AIFOUT", NULL, "SYSCLK" }, + { "AIFAUXOUT", NULL, "SYSCLK" }, + { "AIFIN", NULL, "SYSCLK" }, + { "AIFAUXIN", NULL, "SYSCLK" }, +}; + +static int adav80x_set_deemph(struct snd_soc_component *component) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (adav80x->deemph) { + switch (adav80x->rate) { + case 32000: + val = ADAV80X_DAC_CTRL2_DEEMPH_32; + break; + case 44100: + val = ADAV80X_DAC_CTRL2_DEEMPH_44; + break; + case 48000: + case 64000: + case 88200: + case 96000: + val = ADAV80X_DAC_CTRL2_DEEMPH_48; + break; + default: + val = ADAV80X_DAC_CTRL2_DEEMPH_NONE; + break; + } + } else { + val = ADAV80X_DAC_CTRL2_DEEMPH_NONE; + } + + return regmap_update_bits(adav80x->regmap, ADAV80X_DAC_CTRL2, + ADAV80X_DAC_CTRL2_DEEMPH_MASK, val); +} + +static int adav80x_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + adav80x->deemph = deemph; + + return adav80x_set_deemph(component); +} + +static int adav80x_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = adav80x->deemph; + return 0; +}; + +static const DECLARE_TLV_DB_SCALE(adav80x_inpga_tlv, 0, 50, 0); +static const DECLARE_TLV_DB_MINMAX(adav80x_digital_tlv, -9563, 0); + +static const struct snd_kcontrol_new adav80x_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", ADAV80X_DAC_L_VOL, + ADAV80X_DAC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv), + SOC_DOUBLE_R_TLV("Master Capture Volume", ADAV80X_ADC_L_VOL, + ADAV80X_ADC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv), + + SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAV80X_PGA_L_VOL, + ADAV80X_PGA_R_VOL, 0, 0x30, 0, adav80x_inpga_tlv), + + SOC_DOUBLE("Master Playback Switch", ADAV80X_DAC_CTRL1, 0, 1, 1, 0), + SOC_DOUBLE("Master Capture Switch", ADAV80X_ADC_CTRL1, 2, 3, 1, 1), + + SOC_SINGLE("ADC High Pass Filter Switch", ADAV80X_ADC_CTRL1, 6, 1, 0), + + SOC_SINGLE_BOOL_EXT("Playback De-emphasis Switch", 0, + adav80x_get_deemph, adav80x_put_deemph), +}; + +static unsigned int adav80x_port_ctrl_regs[2][2] = { + { ADAV80X_REC_CTRL, ADAV80X_PLAYBACK_CTRL, }, + { ADAV80X_AUX_OUT_CTRL, ADAV80X_AUX_IN_CTRL }, +}; + +static int adav80x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int capture = 0x00; + unsigned int playback = 0x00; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + capture |= ADAV80X_CAPTURE_MODE_MASTER; + playback |= ADAV80X_PLAYBACK_MODE_MASTER; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + capture |= ADAV80X_CAPTURE_MODE_I2S; + playback |= ADAV80X_PLAYBACK_MODE_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + capture |= ADAV80X_CAPTURE_MODE_LEFT_J; + playback |= ADAV80X_PLAYBACK_MODE_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + capture |= ADAV80X_CAPTURE_MODE_RIGHT_J; + playback |= ADAV80X_PLAYBACK_MODE_RIGHT_J_24; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + regmap_update_bits(adav80x->regmap, adav80x_port_ctrl_regs[dai->id][0], + ADAV80X_CAPTURE_MODE_MASK | ADAV80X_CAPTURE_MODE_MASTER, + capture); + regmap_write(adav80x->regmap, adav80x_port_ctrl_regs[dai->id][1], + playback); + + adav80x->dai_fmt[dai->id] = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + return 0; +} + +static int adav80x_set_adc_clock(struct snd_soc_component *component, + unsigned int sample_rate) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (sample_rate <= 48000) + val = ADAV80X_ADC_CTRL1_MODULATOR_128FS; + else + val = ADAV80X_ADC_CTRL1_MODULATOR_64FS; + + regmap_update_bits(adav80x->regmap, ADAV80X_ADC_CTRL1, + ADAV80X_ADC_CTRL1_MODULATOR_MASK, val); + + return 0; +} + +static int adav80x_set_dac_clock(struct snd_soc_component *component, + unsigned int sample_rate) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (sample_rate <= 48000) + val = ADAV80X_DAC_CTRL2_DIV1 | ADAV80X_DAC_CTRL2_INTERPOL_256FS; + else + val = ADAV80X_DAC_CTRL2_DIV2 | ADAV80X_DAC_CTRL2_INTERPOL_128FS; + + regmap_update_bits(adav80x->regmap, ADAV80X_DAC_CTRL2, + ADAV80X_DAC_CTRL2_DIV_MASK | ADAV80X_DAC_CTRL2_INTERPOL_MASK, + val); + + return 0; +} + +static int adav80x_set_capture_pcm_format(struct snd_soc_component *component, + struct snd_soc_dai *dai, struct snd_pcm_hw_params *params) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int val; + + switch (params_width(params)) { + case 16: + val = ADAV80X_CAPTURE_WORD_LEN16; + break; + case 18: + val = ADAV80X_CAPTRUE_WORD_LEN18; + break; + case 20: + val = ADAV80X_CAPTURE_WORD_LEN20; + break; + case 24: + val = ADAV80X_CAPTURE_WORD_LEN24; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adav80x->regmap, adav80x_port_ctrl_regs[dai->id][0], + ADAV80X_CAPTURE_WORD_LEN_MASK, val); + + return 0; +} + +static int adav80x_set_playback_pcm_format(struct snd_soc_component *component, + struct snd_soc_dai *dai, struct snd_pcm_hw_params *params) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (adav80x->dai_fmt[dai->id] != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (params_width(params)) { + case 16: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_16; + break; + case 18: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_18; + break; + case 20: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_20; + break; + case 24: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_24; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adav80x->regmap, adav80x_port_ctrl_regs[dai->id][1], + ADAV80X_PLAYBACK_MODE_MASK, val); + + return 0; +} + +static int adav80x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int rate = params_rate(params); + + if (rate * 256 != adav80x->sysclk) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + adav80x_set_playback_pcm_format(component, dai, params); + adav80x_set_dac_clock(component, rate); + } else { + adav80x_set_capture_pcm_format(component, dai, params); + adav80x_set_adc_clock(component, rate); + } + adav80x->rate = rate; + adav80x_set_deemph(component); + + return 0; +} + +static int adav80x_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, + unsigned int freq, int dir) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (dir == SND_SOC_CLOCK_IN) { + switch (clk_id) { + case ADAV80X_CLK_XIN: + case ADAV80X_CLK_XTAL: + case ADAV80X_CLK_MCLKI: + case ADAV80X_CLK_PLL1: + case ADAV80X_CLK_PLL2: + break; + default: + return -EINVAL; + } + + adav80x->sysclk = freq; + + if (adav80x->clk_src != clk_id) { + unsigned int iclk_ctrl1, iclk_ctrl2; + + adav80x->clk_src = clk_id; + if (clk_id == ADAV80X_CLK_XTAL) + clk_id = ADAV80X_CLK_XIN; + + iclk_ctrl1 = ADAV80X_ICLK_CTRL1_DAC_SRC(clk_id) | + ADAV80X_ICLK_CTRL1_ADC_SRC(clk_id) | + ADAV80X_ICLK_CTRL1_ICLK2_SRC(clk_id); + iclk_ctrl2 = ADAV80X_ICLK_CTRL2_ICLK1_SRC(clk_id); + + regmap_write(adav80x->regmap, ADAV80X_ICLK_CTRL1, + iclk_ctrl1); + regmap_write(adav80x->regmap, ADAV80X_ICLK_CTRL2, + iclk_ctrl2); + + snd_soc_dapm_sync(dapm); + } + } else { + unsigned int mask; + + switch (clk_id) { + case ADAV80X_CLK_SYSCLK1: + case ADAV80X_CLK_SYSCLK2: + case ADAV80X_CLK_SYSCLK3: + break; + default: + return -EINVAL; + } + + clk_id -= ADAV80X_CLK_SYSCLK1; + mask = ADAV80X_PLL_OUTE_SYSCLKPD(clk_id); + + if (freq == 0) { + regmap_update_bits(adav80x->regmap, ADAV80X_PLL_OUTE, + mask, mask); + adav80x->sysclk_pd[clk_id] = true; + } else { + regmap_update_bits(adav80x->regmap, ADAV80X_PLL_OUTE, + mask, 0); + adav80x->sysclk_pd[clk_id] = false; + } + + snd_soc_dapm_mutex_lock(dapm); + + if (adav80x->sysclk_pd[0]) + snd_soc_dapm_disable_pin_unlocked(dapm, "PLL1"); + else + snd_soc_dapm_force_enable_pin_unlocked(dapm, "PLL1"); + + if (adav80x->sysclk_pd[1] || adav80x->sysclk_pd[2]) + snd_soc_dapm_disable_pin_unlocked(dapm, "PLL2"); + else + snd_soc_dapm_force_enable_pin_unlocked(dapm, "PLL2"); + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); + } + + return 0; +} + +static int adav80x_set_pll(struct snd_soc_component *component, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int pll_ctrl1 = 0; + unsigned int pll_ctrl2 = 0; + unsigned int pll_src; + + switch (source) { + case ADAV80X_PLL_SRC_XTAL: + case ADAV80X_PLL_SRC_XIN: + case ADAV80X_PLL_SRC_MCLKI: + break; + default: + return -EINVAL; + } + + if (!freq_out) + return 0; + + switch (freq_in) { + case 27000000: + break; + case 54000000: + if (source == ADAV80X_PLL_SRC_XIN) { + pll_ctrl1 |= ADAV80X_PLL_CTRL1_PLLDIV; + break; + } + /* fall through */ + default: + return -EINVAL; + } + + if (freq_out > 12288000) { + pll_ctrl2 |= ADAV80X_PLL_CTRL2_DOUB(pll_id); + freq_out /= 2; + } + + /* freq_out = sample_rate * 256 */ + switch (freq_out) { + case 8192000: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_32(pll_id); + break; + case 11289600: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_44(pll_id); + break; + case 12288000: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_48(pll_id); + break; + default: + return -EINVAL; + } + + regmap_update_bits(adav80x->regmap, ADAV80X_PLL_CTRL1, + ADAV80X_PLL_CTRL1_PLLDIV, pll_ctrl1); + regmap_update_bits(adav80x->regmap, ADAV80X_PLL_CTRL2, + ADAV80X_PLL_CTRL2_PLL_MASK(pll_id), pll_ctrl2); + + if (source != adav80x->pll_src) { + if (source == ADAV80X_PLL_SRC_MCLKI) + pll_src = ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll_id); + else + pll_src = ADAV80X_PLL_CLK_SRC_PLL_XIN(pll_id); + + regmap_update_bits(adav80x->regmap, ADAV80X_PLL_CLK_SRC, + ADAV80X_PLL_CLK_SRC_PLL_MASK(pll_id), pll_src); + + adav80x->pll_src = source; + + snd_soc_dapm_sync(dapm); + } + + return 0; +} + +static int adav80x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + unsigned int mask = ADAV80X_DAC_CTRL1_PD; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(adav80x->regmap, ADAV80X_DAC_CTRL1, mask, + 0x00); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(adav80x->regmap, ADAV80X_DAC_CTRL1, mask, + mask); + break; + } + + return 0; +} + +/* Enforce the same sample rate on all audio interfaces */ +static int adav80x_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + + if (!snd_soc_component_is_active(component) || !adav80x->rate) + return 0; + + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, adav80x->rate); +} + +static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + + if (!snd_soc_component_is_active(component)) + adav80x->rate = 0; +} + +static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, +}; + +#define ADAV80X_PLAYBACK_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) + +#define ADAV80X_CAPTURE_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) + +#define ADAV80X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver adav80x_dais[] = { + { + .name = "adav80x-hifi", + .id = 0, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_PLAYBACK_RATES, + .formats = ADAV80X_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_CAPTURE_RATES, + .formats = ADAV80X_FORMATS, + }, + .ops = &adav80x_dai_ops, + }, + { + .name = "adav80x-aux", + .id = 1, + .playback = { + .stream_name = "Aux Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_PLAYBACK_RATES, + .formats = ADAV80X_FORMATS, + }, + .capture = { + .stream_name = "Aux Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_CAPTURE_RATES, + .formats = ADAV80X_FORMATS, + }, + .ops = &adav80x_dai_ops, + }, +}; + +static int adav80x_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + + /* Force PLLs on for SYSCLK output */ + snd_soc_dapm_force_enable_pin(dapm, "PLL1"); + snd_soc_dapm_force_enable_pin(dapm, "PLL2"); + + /* Power down S/PDIF receiver, since it is currently not supported */ + regmap_write(adav80x->regmap, ADAV80X_PLL_OUTE, 0x20); + /* Disable DAC zero flag */ + regmap_write(adav80x->regmap, ADAV80X_DAC_CTRL3, 0x6); + + return 0; +} + +static int adav80x_resume(struct snd_soc_component *component) +{ + struct adav80x *adav80x = snd_soc_component_get_drvdata(component); + + regcache_sync(adav80x->regmap); + + return 0; +} + +static const struct snd_soc_component_driver adav80x_component_driver = { + .probe = adav80x_probe, + .resume = adav80x_resume, + .set_bias_level = adav80x_set_bias_level, + .set_pll = adav80x_set_pll, + .set_sysclk = adav80x_set_sysclk, + .controls = adav80x_controls, + .num_controls = ARRAY_SIZE(adav80x_controls), + .dapm_widgets = adav80x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adav80x_dapm_widgets), + .dapm_routes = adav80x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adav80x_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +int adav80x_bus_probe(struct device *dev, struct regmap *regmap) +{ + struct adav80x *adav80x; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + adav80x = devm_kzalloc(dev, sizeof(*adav80x), GFP_KERNEL); + if (!adav80x) + return -ENOMEM; + + dev_set_drvdata(dev, adav80x); + adav80x->regmap = regmap; + + return devm_snd_soc_register_component(dev, &adav80x_component_driver, + adav80x_dais, ARRAY_SIZE(adav80x_dais)); +} +EXPORT_SYMBOL_GPL(adav80x_bus_probe); + +const struct regmap_config adav80x_regmap_config = { + .val_bits = 8, + .pad_bits = 1, + .reg_bits = 7, + + .max_register = ADAV80X_PLL_OUTE, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = adav80x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(adav80x_reg_defaults), +}; +EXPORT_SYMBOL_GPL(adav80x_regmap_config); + +MODULE_DESCRIPTION("ASoC ADAV80x driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_AUTHOR("Yi Li >"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adav80x.h b/sound/soc/codecs/adav80x.h new file mode 100644 index 000000000..8a1d7c09d --- /dev/null +++ b/sound/soc/codecs/adav80x.h @@ -0,0 +1,42 @@ +/* + * header file for ADAV80X parts + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADAV80X_H +#define _ADAV80X_H + +#include + +struct device; + +extern const struct regmap_config adav80x_regmap_config; +int adav80x_bus_probe(struct device *dev, struct regmap *regmap); + +enum adav80x_pll_src { + ADAV80X_PLL_SRC_XIN, + ADAV80X_PLL_SRC_XTAL, + ADAV80X_PLL_SRC_MCLKI, +}; + +enum adav80x_pll { + ADAV80X_PLL1 = 0, + ADAV80X_PLL2 = 1, +}; + +enum adav80x_clk_src { + ADAV80X_CLK_XIN = 0, + ADAV80X_CLK_MCLKI = 1, + ADAV80X_CLK_PLL1 = 2, + ADAV80X_CLK_PLL2 = 3, + ADAV80X_CLK_XTAL = 6, + + ADAV80X_CLK_SYSCLK1 = 6, + ADAV80X_CLK_SYSCLK2 = 7, + ADAV80X_CLK_SYSCLK3 = 8, +}; + +#endif diff --git a/sound/soc/codecs/ads117x.c b/sound/soc/codecs/ads117x.c new file mode 100644 index 000000000..bcd45ff5d --- /dev/null +++ b/sound/soc/codecs/ads117x.c @@ -0,0 +1,100 @@ +/* + * ads117x.c -- Driver for ads1174/8 ADC chips + * + * Copyright 2009 ShotSpotter Inc. + * Author: Graeme Gregory + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ADS117X_RATES (SNDRV_PCM_RATE_8000_48000) +#define ADS117X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +static const struct snd_soc_dapm_widget ads117x_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("Input1"), +SND_SOC_DAPM_INPUT("Input2"), +SND_SOC_DAPM_INPUT("Input3"), +SND_SOC_DAPM_INPUT("Input4"), +SND_SOC_DAPM_INPUT("Input5"), +SND_SOC_DAPM_INPUT("Input6"), +SND_SOC_DAPM_INPUT("Input7"), +SND_SOC_DAPM_INPUT("Input8"), +}; + +static const struct snd_soc_dapm_route ads117x_dapm_routes[] = { + { "Capture", NULL, "Input1" }, + { "Capture", NULL, "Input2" }, + { "Capture", NULL, "Input3" }, + { "Capture", NULL, "Input4" }, + { "Capture", NULL, "Input5" }, + { "Capture", NULL, "Input6" }, + { "Capture", NULL, "Input7" }, + { "Capture", NULL, "Input8" }, +}; + +static struct snd_soc_dai_driver ads117x_dai = { +/* ADC */ + .name = "ads117x-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 32, + .rates = ADS117X_RATES, + .formats = ADS117X_FORMATS,}, +}; + +static const struct snd_soc_component_driver soc_component_dev_ads117x = { + .dapm_widgets = ads117x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ads117x_dapm_widgets), + .dapm_routes = ads117x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ads117x_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ads117x_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_ads117x, &ads117x_dai, 1); +} + +#if defined(CONFIG_OF) +static const struct of_device_id ads117x_dt_ids[] = { + { .compatible = "ti,ads1174" }, + { .compatible = "ti,ads1178" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ads117x_dt_ids); +#endif + +static struct platform_driver ads117x_codec_driver = { + .driver = { + .name = "ads117x-codec", + .of_match_table = of_match_ptr(ads117x_dt_ids), + }, + + .probe = ads117x_probe, +}; + +module_platform_driver(ads117x_codec_driver); + +MODULE_DESCRIPTION("ASoC ads117x driver"); +MODULE_AUTHOR("Graeme Gregory"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c new file mode 100644 index 000000000..32bc545c1 --- /dev/null +++ b/sound/soc/codecs/ak4104.c @@ -0,0 +1,356 @@ +/* + * AK4104 ALSA SoC (ASoC) driver + * + * Copyright (c) 2009 Daniel Mack + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AK4104 registers addresses */ +#define AK4104_REG_CONTROL1 0x00 +#define AK4104_REG_RESERVED 0x01 +#define AK4104_REG_CONTROL2 0x02 +#define AK4104_REG_TX 0x03 +#define AK4104_REG_CHN_STATUS(x) ((x) + 0x04) +#define AK4104_NUM_REGS 10 + +#define AK4104_REG_MASK 0x1f +#define AK4104_READ 0xc0 +#define AK4104_WRITE 0xe0 +#define AK4104_RESERVED_VAL 0x5b + +/* Bit masks for AK4104 registers */ +#define AK4104_CONTROL1_RSTN (1 << 0) +#define AK4104_CONTROL1_PW (1 << 1) +#define AK4104_CONTROL1_DIF0 (1 << 2) +#define AK4104_CONTROL1_DIF1 (1 << 3) + +#define AK4104_CONTROL2_SEL0 (1 << 0) +#define AK4104_CONTROL2_SEL1 (1 << 1) +#define AK4104_CONTROL2_MODE (1 << 2) + +#define AK4104_TX_TXE (1 << 0) +#define AK4104_TX_V (1 << 1) + +struct ak4104_private { + struct regmap *regmap; + struct regulator *regulator; +}; + +static const struct snd_soc_dapm_widget ak4104_dapm_widgets[] = { +SND_SOC_DAPM_PGA("TXE", AK4104_REG_TX, AK4104_TX_TXE, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route ak4104_dapm_routes[] = { + { "TXE", NULL, "Playback" }, + { "TX", NULL, "TXE" }, +}; + +static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct ak4104_private *ak4104 = snd_soc_component_get_drvdata(component); + int val = 0; + int ret; + + /* set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= AK4104_CONTROL1_DIF0; + break; + case SND_SOC_DAIFMT_I2S: + val |= AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1; + break; + default: + dev_err(component->dev, "invalid dai format\n"); + return -EINVAL; + } + + /* This device can only be slave */ + if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + + ret = regmap_update_bits(ak4104->regmap, AK4104_REG_CONTROL1, + AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1, + val); + if (ret < 0) + return ret; + + return 0; +} + +static int ak4104_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4104_private *ak4104 = snd_soc_component_get_drvdata(component); + int ret, val = 0; + + /* set the IEC958 bits: consumer mode, no copyright bit */ + val |= IEC958_AES0_CON_NOT_COPYRIGHT; + regmap_write(ak4104->regmap, AK4104_REG_CHN_STATUS(0), val); + + val = 0; + + switch (params_rate(params)) { + case 22050: + val |= IEC958_AES3_CON_FS_22050; + break; + case 24000: + val |= IEC958_AES3_CON_FS_24000; + break; + case 32000: + val |= IEC958_AES3_CON_FS_32000; + break; + case 44100: + val |= IEC958_AES3_CON_FS_44100; + break; + case 48000: + val |= IEC958_AES3_CON_FS_48000; + break; + case 88200: + val |= IEC958_AES3_CON_FS_88200; + break; + case 96000: + val |= IEC958_AES3_CON_FS_96000; + break; + case 176400: + val |= IEC958_AES3_CON_FS_176400; + break; + case 192000: + val |= IEC958_AES3_CON_FS_192000; + break; + default: + dev_err(component->dev, "unsupported sampling rate\n"); + return -EINVAL; + } + + ret = regmap_write(ak4104->regmap, AK4104_REG_CHN_STATUS(3), val); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_dai_ops ak4101_dai_ops = { + .hw_params = ak4104_hw_params, + .set_fmt = ak4104_set_dai_fmt, +}; + +static struct snd_soc_dai_driver ak4104_dai = { + .name = "ak4104-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_22050 | 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, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE + }, + .ops = &ak4101_dai_ops, +}; + +static int ak4104_probe(struct snd_soc_component *component) +{ + struct ak4104_private *ak4104 = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_enable(ak4104->regulator); + if (ret < 0) { + dev_err(component->dev, "Unable to enable regulator: %d\n", ret); + return ret; + } + + /* set power-up and non-reset bits */ + ret = regmap_update_bits(ak4104->regmap, AK4104_REG_CONTROL1, + AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN, + AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN); + if (ret < 0) + goto exit_disable_regulator; + + /* enable transmitter */ + ret = regmap_update_bits(ak4104->regmap, AK4104_REG_TX, + AK4104_TX_TXE, AK4104_TX_TXE); + if (ret < 0) + goto exit_disable_regulator; + + return 0; + +exit_disable_regulator: + regulator_disable(ak4104->regulator); + return ret; +} + +static void ak4104_remove(struct snd_soc_component *component) +{ + struct ak4104_private *ak4104 = snd_soc_component_get_drvdata(component); + + regmap_update_bits(ak4104->regmap, AK4104_REG_CONTROL1, + AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN, 0); + regulator_disable(ak4104->regulator); +} + +#ifdef CONFIG_PM +static int ak4104_soc_suspend(struct snd_soc_component *component) +{ + struct ak4104_private *priv = snd_soc_component_get_drvdata(component); + + regulator_disable(priv->regulator); + + return 0; +} + +static int ak4104_soc_resume(struct snd_soc_component *component) +{ + struct ak4104_private *priv = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_enable(priv->regulator); + if (ret < 0) + return ret; + + return 0; +} +#else +#define ak4104_soc_suspend NULL +#define ak4104_soc_resume NULL +#endif /* CONFIG_PM */ + +static const struct snd_soc_component_driver soc_component_device_ak4104 = { + .probe = ak4104_probe, + .remove = ak4104_remove, + .suspend = ak4104_soc_suspend, + .resume = ak4104_soc_resume, + .dapm_widgets = ak4104_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4104_dapm_widgets), + .dapm_routes = ak4104_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ak4104_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ak4104_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4104_NUM_REGS - 1, + .read_flag_mask = AK4104_READ, + .write_flag_mask = AK4104_WRITE, + + .cache_type = REGCACHE_RBTREE, +}; + +static int ak4104_spi_probe(struct spi_device *spi) +{ + struct device_node *np = spi->dev.of_node; + struct ak4104_private *ak4104; + unsigned int val; + int ret; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + ak4104 = devm_kzalloc(&spi->dev, sizeof(struct ak4104_private), + GFP_KERNEL); + if (ak4104 == NULL) + return -ENOMEM; + + ak4104->regulator = devm_regulator_get(&spi->dev, "vdd"); + if (IS_ERR(ak4104->regulator)) { + ret = PTR_ERR(ak4104->regulator); + dev_err(&spi->dev, "Unable to get Vdd regulator: %d\n", ret); + return ret; + } + + ak4104->regmap = devm_regmap_init_spi(spi, &ak4104_regmap); + if (IS_ERR(ak4104->regmap)) { + ret = PTR_ERR(ak4104->regmap); + return ret; + } + + if (np) { + enum of_gpio_flags flags; + int gpio = of_get_named_gpio_flags(np, "reset-gpio", 0, &flags); + + if (gpio_is_valid(gpio)) { + ret = devm_gpio_request_one(&spi->dev, gpio, + flags & OF_GPIO_ACTIVE_LOW ? + GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH, + "ak4104 reset"); + if (ret < 0) + return ret; + } + } + + /* read the 'reserved' register - according to the datasheet, it + * should contain 0x5b. Not a good way to verify the presence of + * the device, but there is no hardware ID register. */ + ret = regmap_read(ak4104->regmap, AK4104_REG_RESERVED, &val); + if (ret != 0) + return ret; + if (val != AK4104_RESERVED_VAL) + return -ENODEV; + + spi_set_drvdata(spi, ak4104); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_device_ak4104, &ak4104_dai, 1); + return ret; +} + +static const struct of_device_id ak4104_of_match[] = { + { .compatible = "asahi-kasei,ak4104", }, + { } +}; +MODULE_DEVICE_TABLE(of, ak4104_of_match); + +static const struct spi_device_id ak4104_id_table[] = { + { "ak4104", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ak4104_id_table); + +static struct spi_driver ak4104_spi_driver = { + .driver = { + .name = "ak4104", + .of_match_table = ak4104_of_match, + }, + .id_table = ak4104_id_table, + .probe = ak4104_spi_probe, +}; + +module_spi_driver(ak4104_spi_driver); + +MODULE_AUTHOR("Daniel Mack "); +MODULE_DESCRIPTION("Asahi Kasei AK4104 ALSA SoC driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c new file mode 100644 index 000000000..0b35b84ab --- /dev/null +++ b/sound/soc/codecs/ak4458.c @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Audio driver for AK4458 DAC +// +// Copyright (C) 2016 Asahi Kasei Microdevices Corporation +// Copyright 2018 NXP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ak4458.h" + +/* AK4458 Codec Private Data */ +struct ak4458_priv { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *reset_gpiod; + struct gpio_desc *mute_gpiod; + int digfil; /* SSLOW, SD, SLOW bits */ + int fs; /* sampling rate */ + int fmt; + int slots; + int slot_width; +}; + +static const struct reg_default ak4458_reg_defaults[] = { + { 0x00, 0x0C }, /* 0x00 AK4458_00_CONTROL1 */ + { 0x01, 0x22 }, /* 0x01 AK4458_01_CONTROL2 */ + { 0x02, 0x00 }, /* 0x02 AK4458_02_CONTROL3 */ + { 0x03, 0xFF }, /* 0x03 AK4458_03_LCHATT */ + { 0x04, 0xFF }, /* 0x04 AK4458_04_RCHATT */ + { 0x05, 0x00 }, /* 0x05 AK4458_05_CONTROL4 */ + { 0x06, 0x00 }, /* 0x06 AK4458_06_DSD1 */ + { 0x07, 0x03 }, /* 0x07 AK4458_07_CONTROL5 */ + { 0x08, 0x00 }, /* 0x08 AK4458_08_SOUND_CONTROL */ + { 0x09, 0x00 }, /* 0x09 AK4458_09_DSD2 */ + { 0x0A, 0x0D }, /* 0x0A AK4458_0A_CONTROL6 */ + { 0x0B, 0x0C }, /* 0x0B AK4458_0B_CONTROL7 */ + { 0x0C, 0x00 }, /* 0x0C AK4458_0C_CONTROL8 */ + { 0x0D, 0x00 }, /* 0x0D AK4458_0D_CONTROL9 */ + { 0x0E, 0x50 }, /* 0x0E AK4458_0E_CONTROL10 */ + { 0x0F, 0xFF }, /* 0x0F AK4458_0F_L2CHATT */ + { 0x10, 0xFF }, /* 0x10 AK4458_10_R2CHATT */ + { 0x11, 0xFF }, /* 0x11 AK4458_11_L3CHATT */ + { 0x12, 0xFF }, /* 0x12 AK4458_12_R3CHATT */ + { 0x13, 0xFF }, /* 0x13 AK4458_13_L4CHATT */ + { 0x14, 0xFF }, /* 0x14 AK4458_14_R4CHATT */ +}; + +/* + * Volume control: + * from -127 to 0 dB in 0.5 dB steps (mute instead of -127.5 dB) + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); + +/* + * DEM1 bit DEM0 bit Mode + * 0 0 44.1kHz + * 0 1 OFF (default) + * 1 0 48kHz + * 1 1 32kHz + */ +static const char * const ak4458_dem_select_texts[] = { + "44.1kHz", "OFF", "48kHz", "32kHz" +}; + +/* + * SSLOW, SD, SLOW bits Digital Filter Setting + * 0, 0, 0 : Sharp Roll-Off Filter + * 0, 0, 1 : Slow Roll-Off Filter + * 0, 1, 0 : Short delay Sharp Roll-Off Filter + * 0, 1, 1 : Short delay Slow Roll-Off Filter + * 1, *, * : Super Slow Roll-Off Filter + */ +static const char * const ak4458_digfil_select_texts[] = { + "Sharp Roll-Off Filter", + "Slow Roll-Off Filter", + "Short delay Sharp Roll-Off Filter", + "Short delay Slow Roll-Off Filter", + "Super Slow Roll-Off Filter" +}; + +/* + * DZFB: Inverting Enable of DZF + * 0: DZF goes H at Zero Detection + * 1: DZF goes L at Zero Detection + */ +static const char * const ak4458_dzfb_select_texts[] = {"H", "L"}; + +/* + * SC1-0 bits: Sound Mode Setting + * 0 0 : Sound Mode 0 + * 0 1 : Sound Mode 1 + * 1 0 : Sound Mode 2 + * 1 1 : Reserved + */ +static const char * const ak4458_sc_select_texts[] = { + "Sound Mode 0", "Sound Mode 1", "Sound Mode 2" +}; + +/* FIR2-0 bits: FIR Filter Mode Setting */ +static const char * const ak4458_fir_select_texts[] = { + "Mode 0", "Mode 1", "Mode 2", "Mode 3", + "Mode 4", "Mode 5", "Mode 6", "Mode 7", +}; + +/* ATS1-0 bits Attenuation Speed */ +static const char * const ak4458_ats_select_texts[] = { + "4080/fs", "2040/fs", "510/fs", "255/fs", +}; + +/* DIF2 bit Audio Interface Format Setting(BICK fs) */ +static const char * const ak4458_dif_select_texts[] = {"32fs,48fs", "64fs",}; + +static const struct soc_enum ak4458_dac1_dem_enum = + SOC_ENUM_SINGLE(AK4458_01_CONTROL2, 1, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts); +static const struct soc_enum ak4458_dac2_dem_enum = + SOC_ENUM_SINGLE(AK4458_0A_CONTROL6, 0, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts); +static const struct soc_enum ak4458_dac3_dem_enum = + SOC_ENUM_SINGLE(AK4458_0E_CONTROL10, 4, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts); +static const struct soc_enum ak4458_dac4_dem_enum = + SOC_ENUM_SINGLE(AK4458_0E_CONTROL10, 6, + ARRAY_SIZE(ak4458_dem_select_texts), + ak4458_dem_select_texts); +static const struct soc_enum ak4458_digfil_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4458_digfil_select_texts), + ak4458_digfil_select_texts); +static const struct soc_enum ak4458_dzfb_enum = + SOC_ENUM_SINGLE(AK4458_02_CONTROL3, 2, + ARRAY_SIZE(ak4458_dzfb_select_texts), + ak4458_dzfb_select_texts); +static const struct soc_enum ak4458_sm_enum = + SOC_ENUM_SINGLE(AK4458_08_SOUND_CONTROL, 0, + ARRAY_SIZE(ak4458_sc_select_texts), + ak4458_sc_select_texts); +static const struct soc_enum ak4458_fir_enum = + SOC_ENUM_SINGLE(AK4458_0C_CONTROL8, 0, + ARRAY_SIZE(ak4458_fir_select_texts), + ak4458_fir_select_texts); +static const struct soc_enum ak4458_ats_enum = + SOC_ENUM_SINGLE(AK4458_0B_CONTROL7, 6, + ARRAY_SIZE(ak4458_ats_select_texts), + ak4458_ats_select_texts); +static const struct soc_enum ak4458_dif_enum = + SOC_ENUM_SINGLE(AK4458_00_CONTROL1, 3, + ARRAY_SIZE(ak4458_dif_select_texts), + ak4458_dif_select_texts); + +static int get_digfil(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = ak4458->digfil; + + return 0; +} + +static int set_digfil(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + int num; + + num = ucontrol->value.enumerated.item[0]; + if (num > 4) + return -EINVAL; + + ak4458->digfil = num; + + /* write SD bit */ + snd_soc_component_update_bits(component, AK4458_01_CONTROL2, + AK4458_SD_MASK, + ((ak4458->digfil & 0x02) << 4)); + + /* write SLOW bit */ + snd_soc_component_update_bits(component, AK4458_02_CONTROL3, + AK4458_SLOW_MASK, + (ak4458->digfil & 0x01)); + + /* write SSLOW bit */ + snd_soc_component_update_bits(component, AK4458_05_CONTROL4, + AK4458_SSLOW_MASK, + ((ak4458->digfil & 0x04) >> 2)); + + return 0; +} + +static const struct snd_kcontrol_new ak4458_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", AK4458_03_LCHATT, + AK4458_04_RCHATT, 0, 0xFF, 0, dac_tlv), + SOC_DOUBLE_R_TLV("DAC2 Playback Volume", AK4458_0F_L2CHATT, + AK4458_10_R2CHATT, 0, 0xFF, 0, dac_tlv), + SOC_DOUBLE_R_TLV("DAC3 Playback Volume", AK4458_11_L3CHATT, + AK4458_12_R3CHATT, 0, 0xFF, 0, dac_tlv), + SOC_DOUBLE_R_TLV("DAC4 Playback Volume", AK4458_13_L4CHATT, + AK4458_14_R4CHATT, 0, 0xFF, 0, dac_tlv), + SOC_ENUM("AK4458 De-emphasis Response DAC1", ak4458_dac1_dem_enum), + SOC_ENUM("AK4458 De-emphasis Response DAC2", ak4458_dac2_dem_enum), + SOC_ENUM("AK4458 De-emphasis Response DAC3", ak4458_dac3_dem_enum), + SOC_ENUM("AK4458 De-emphasis Response DAC4", ak4458_dac4_dem_enum), + SOC_ENUM_EXT("AK4458 Digital Filter Setting", ak4458_digfil_enum, + get_digfil, set_digfil), + SOC_ENUM("AK4458 Inverting Enable of DZFB", ak4458_dzfb_enum), + SOC_ENUM("AK4458 Sound Mode", ak4458_sm_enum), + SOC_ENUM("AK4458 FIR Filter Mode Setting", ak4458_fir_enum), + SOC_ENUM("AK4458 Attenuation transition Time Setting", + ak4458_ats_enum), + SOC_ENUM("AK4458 BICK fs Setting", ak4458_dif_enum), +}; + +/* ak4458 dapm widgets */ +static const struct snd_soc_dapm_widget ak4458_dapm_widgets[] = { + SND_SOC_DAPM_DAC("AK4458 DAC1", NULL, AK4458_0A_CONTROL6, 2, 0),/*pw*/ + SND_SOC_DAPM_AIF_IN("AK4458 SDTI", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("AK4458 AOUTA"), + + SND_SOC_DAPM_DAC("AK4458 DAC2", NULL, AK4458_0A_CONTROL6, 3, 0),/*pw*/ + SND_SOC_DAPM_OUTPUT("AK4458 AOUTB"), + + SND_SOC_DAPM_DAC("AK4458 DAC3", NULL, AK4458_0B_CONTROL7, 2, 0),/*pw*/ + SND_SOC_DAPM_OUTPUT("AK4458 AOUTC"), + + SND_SOC_DAPM_DAC("AK4458 DAC4", NULL, AK4458_0B_CONTROL7, 3, 0),/*pw*/ + SND_SOC_DAPM_OUTPUT("AK4458 AOUTD"), +}; + +static const struct snd_soc_dapm_route ak4458_intercon[] = { + {"AK4458 DAC1", NULL, "AK4458 SDTI"}, + {"AK4458 AOUTA", NULL, "AK4458 DAC1"}, + + {"AK4458 DAC2", NULL, "AK4458 SDTI"}, + {"AK4458 AOUTB", NULL, "AK4458 DAC2"}, + + {"AK4458 DAC3", NULL, "AK4458 SDTI"}, + {"AK4458 AOUTC", NULL, "AK4458 DAC3"}, + + {"AK4458 DAC4", NULL, "AK4458 SDTI"}, + {"AK4458 AOUTD", NULL, "AK4458 DAC4"}, +}; + +static int ak4458_rstn_control(struct snd_soc_component *component, int bit) +{ + int ret; + + if (bit) + ret = snd_soc_component_update_bits(component, + AK4458_00_CONTROL1, + AK4458_RSTN_MASK, + 0x1); + else + ret = snd_soc_component_update_bits(component, + AK4458_00_CONTROL1, + AK4458_RSTN_MASK, + 0x0); + if (ret < 0) + return ret; + + return 0; +} + +static int ak4458_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + int pcm_width = max(params_physical_width(params), ak4458->slot_width); + int nfs1; + u8 format; + + nfs1 = params_rate(params); + ak4458->fs = nfs1; + + /* Master Clock Frequency Auto Setting Mode Enable */ + snd_soc_component_update_bits(component, AK4458_00_CONTROL1, 0x80, 0x80); + + switch (pcm_width) { + case 16: + if (ak4458->fmt == SND_SOC_DAIFMT_I2S) + format = AK4458_DIF_24BIT_I2S; + else + format = AK4458_DIF_16BIT_LSB; + break; + case 32: + switch (ak4458->fmt) { + case SND_SOC_DAIFMT_I2S: + format = AK4458_DIF_32BIT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = AK4458_DIF_32BIT_MSB; + break; + case SND_SOC_DAIFMT_RIGHT_J: + format = AK4458_DIF_32BIT_LSB; + break; + case SND_SOC_DAIFMT_DSP_B: + format = AK4458_DIF_32BIT_MSB; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, AK4458_00_CONTROL1, + AK4458_DIF_MASK, format); + + ak4458_rstn_control(component, 0); + ak4458_rstn_control(component, 1); + + return 0; +} + +static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */ + break; + case SND_SOC_DAIFMT_CBM_CFM: /* Master Mode is not supported */ + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "Master mode unsupported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_DSP_B: + ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(component->dev, "Audio format 0x%02X unsupported\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + ak4458_rstn_control(component, 0); + ak4458_rstn_control(component, 1); + + return 0; +} + +static const int att_speed[] = { 4080, 2040, 510, 255 }; + +static int ak4458_set_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + int nfs, ndt, ret, reg; + int ats; + + nfs = ak4458->fs; + + reg = snd_soc_component_read32(component, AK4458_0B_CONTROL7); + ats = (reg & AK4458_ATS_MASK) >> AK4458_ATS_SHIFT; + + ndt = att_speed[ats] / (nfs / 1000); + + if (mute) { + ret = snd_soc_component_update_bits(component, AK4458_01_CONTROL2, 0x01, 1); + mdelay(ndt); + if (ak4458->mute_gpiod) + gpiod_set_value_cansleep(ak4458->mute_gpiod, 1); + } else { + if (ak4458->mute_gpiod) + gpiod_set_value_cansleep(ak4458->mute_gpiod, 0); + ret = snd_soc_component_update_bits(component, AK4458_01_CONTROL2, 0x01, 0); + mdelay(ndt); + } + + return 0; +} + +static int ak4458_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + int mode; + + ak4458->slots = slots; + ak4458->slot_width = slot_width; + + switch (slots * slot_width) { + case 128: + mode = AK4458_MODE_TDM128; + break; + case 256: + mode = AK4458_MODE_TDM256; + break; + case 512: + mode = AK4458_MODE_TDM512; + break; + default: + mode = AK4458_MODE_NORMAL; + break; + } + + snd_soc_component_update_bits(component, AK4458_0A_CONTROL6, + AK4458_MODE_MASK, + mode); + + return 0; +} + +#define AK4458_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const unsigned int ak4458_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, + 2822400, +}; + +static const struct snd_pcm_hw_constraint_list ak4458_rate_constraints = { + .count = ARRAY_SIZE(ak4458_rates), + .list = ak4458_rates, +}; + +static int ak4458_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &ak4458_rate_constraints); + + return ret; +} + +static struct snd_soc_dai_ops ak4458_dai_ops = { + .startup = ak4458_startup, + .hw_params = ak4458_hw_params, + .set_fmt = ak4458_set_dai_fmt, + .digital_mute = ak4458_set_dai_mute, + .set_tdm_slot = ak4458_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ak4458_dai = { + .name = "ak4458-aif", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = AK4458_FORMATS, + }, + .ops = &ak4458_dai_ops, +}; + +static void ak4458_power_off(struct ak4458_priv *ak4458) +{ + if (ak4458->reset_gpiod) { + gpiod_set_value_cansleep(ak4458->reset_gpiod, 0); + usleep_range(1000, 2000); + } +} + +static void ak4458_power_on(struct ak4458_priv *ak4458) +{ + if (ak4458->reset_gpiod) { + gpiod_set_value_cansleep(ak4458->reset_gpiod, 1); + usleep_range(1000, 2000); + } +} + +static int ak4458_init(struct snd_soc_component *component) +{ + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + int ret; + + /* External Mute ON */ + if (ak4458->mute_gpiod) + gpiod_set_value_cansleep(ak4458->mute_gpiod, 1); + + ak4458_power_on(ak4458); + + ret = snd_soc_component_update_bits(component, AK4458_00_CONTROL1, + 0x80, 0x80); /* ACKS bit = 1; 10000000 */ + if (ret < 0) + return ret; + + return ak4458_rstn_control(component, 1); +} + +static int ak4458_probe(struct snd_soc_component *component) +{ + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + + ak4458->fs = 48000; + + return ak4458_init(component); +} + +static void ak4458_remove(struct snd_soc_component *component) +{ + struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component); + + ak4458_power_off(ak4458); +} + +#ifdef CONFIG_PM +static int __maybe_unused ak4458_runtime_suspend(struct device *dev) +{ + struct ak4458_priv *ak4458 = dev_get_drvdata(dev); + + regcache_cache_only(ak4458->regmap, true); + + ak4458_power_off(ak4458); + + if (ak4458->mute_gpiod) + gpiod_set_value_cansleep(ak4458->mute_gpiod, 0); + + return 0; +} + +static int __maybe_unused ak4458_runtime_resume(struct device *dev) +{ + struct ak4458_priv *ak4458 = dev_get_drvdata(dev); + + if (ak4458->mute_gpiod) + gpiod_set_value_cansleep(ak4458->mute_gpiod, 1); + + ak4458_power_off(ak4458); + ak4458_power_on(ak4458); + + regcache_cache_only(ak4458->regmap, false); + regcache_mark_dirty(ak4458->regmap); + + return regcache_sync(ak4458->regmap); +} +#endif /* CONFIG_PM */ + +static const struct snd_soc_component_driver soc_codec_dev_ak4458 = { + .probe = ak4458_probe, + .remove = ak4458_remove, + .controls = ak4458_snd_controls, + .num_controls = ARRAY_SIZE(ak4458_snd_controls), + .dapm_widgets = ak4458_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4458_dapm_widgets), + .dapm_routes = ak4458_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4458_intercon), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ak4458_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4458_14_R4CHATT, + .reg_defaults = ak4458_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ak4458_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static const struct dev_pm_ops ak4458_pm = { + SET_RUNTIME_PM_OPS(ak4458_runtime_suspend, ak4458_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static int ak4458_i2c_probe(struct i2c_client *i2c) +{ + struct ak4458_priv *ak4458; + int ret; + + ak4458 = devm_kzalloc(&i2c->dev, sizeof(*ak4458), GFP_KERNEL); + if (!ak4458) + return -ENOMEM; + + ak4458->regmap = devm_regmap_init_i2c(i2c, &ak4458_regmap); + if (IS_ERR(ak4458->regmap)) + return PTR_ERR(ak4458->regmap); + + i2c_set_clientdata(i2c, ak4458); + ak4458->dev = &i2c->dev; + + ak4458->reset_gpiod = devm_gpiod_get_optional(ak4458->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ak4458->reset_gpiod)) + return PTR_ERR(ak4458->reset_gpiod); + + ak4458->mute_gpiod = devm_gpiod_get_optional(ak4458->dev, "mute", + GPIOD_OUT_LOW); + if (IS_ERR(ak4458->mute_gpiod)) + return PTR_ERR(ak4458->mute_gpiod); + + ret = devm_snd_soc_register_component(ak4458->dev, &soc_codec_dev_ak4458, + &ak4458_dai, 1); + if (ret < 0) { + dev_err(ak4458->dev, "Failed to register CODEC: %d\n", ret); + return ret; + } + + pm_runtime_enable(&i2c->dev); + + return 0; +} + +static int ak4458_i2c_remove(struct i2c_client *i2c) +{ + pm_runtime_disable(&i2c->dev); + + return 0; +} + +static const struct of_device_id ak4458_of_match[] = { + { .compatible = "asahi-kasei,ak4458", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ak4458_of_match); + +static struct i2c_driver ak4458_i2c_driver = { + .driver = { + .name = "ak4458", + .pm = &ak4458_pm, + .of_match_table = ak4458_of_match, + }, + .probe_new = ak4458_i2c_probe, + .remove = ak4458_i2c_remove, +}; + +module_i2c_driver(ak4458_i2c_driver); + +MODULE_AUTHOR("Junichi Wakasugi "); +MODULE_AUTHOR("Mihai Serban "); +MODULE_DESCRIPTION("ASoC AK4458 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ak4458.h b/sound/soc/codecs/ak4458.h new file mode 100644 index 000000000..f906215f7 --- /dev/null +++ b/sound/soc/codecs/ak4458.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Audio driver for AK4458 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright 2018 NXP + */ + +#ifndef _AK4458_H +#define _AK4458_H + +#include + +/* Settings */ + +#define AK4458_00_CONTROL1 0x00 +#define AK4458_01_CONTROL2 0x01 +#define AK4458_02_CONTROL3 0x02 +#define AK4458_03_LCHATT 0x03 +#define AK4458_04_RCHATT 0x04 +#define AK4458_05_CONTROL4 0x05 +#define AK4458_06_DSD1 0x06 +#define AK4458_07_CONTROL5 0x07 +#define AK4458_08_SOUND_CONTROL 0x08 +#define AK4458_09_DSD2 0x09 +#define AK4458_0A_CONTROL6 0x0A +#define AK4458_0B_CONTROL7 0x0B +#define AK4458_0C_CONTROL8 0x0C +#define AK4458_0D_CONTROL9 0x0D +#define AK4458_0E_CONTROL10 0x0E +#define AK4458_0F_L2CHATT 0x0F +#define AK4458_10_R2CHATT 0x10 +#define AK4458_11_L3CHATT 0x11 +#define AK4458_12_R3CHATT 0x12 +#define AK4458_13_L4CHATT 0x13 +#define AK4458_14_R4CHATT 0x14 + +/* Bitfield Definitions */ + +/* AK4458_00_CONTROL1 (0x00) Fields + * Addr Register Name D7 D6 D5 D4 D3 D2 D1 D0 + * 00H Control 1 ACKS 0 0 0 DIF2 DIF1 DIF0 RSTN + */ + +/* Digital Filter (SD, SLOW, SSLOW) */ +#define AK4458_SD_MASK GENMASK(5, 5) +#define AK4458_SLOW_MASK GENMASK(0, 0) +#define AK4458_SSLOW_MASK GENMASK(0, 0) + +/* DIF2 1 0 + * x 1 0 MSB justified Figure 3 (default) + * x 1 1 I2S Compliment Figure 4 + */ +#define AK4458_DIF_SHIFT 1 +#define AK4458_DIF_MASK GENMASK(3, 1) + +#define AK4458_DIF_16BIT_LSB (0 << 1) +#define AK4458_DIF_24BIT_I2S (3 << 1) +#define AK4458_DIF_32BIT_LSB (5 << 1) +#define AK4458_DIF_32BIT_MSB (6 << 1) +#define AK4458_DIF_32BIT_I2S (7 << 1) + +/* AK4458_00_CONTROL1 (0x00) D0 bit */ +#define AK4458_RSTN_MASK GENMASK(0, 0) +#define AK4458_RSTN (0x1 << 0) + +/* AK4458_0A_CONTROL6 Mode bits */ +#define AK4458_MODE_SHIFT 6 +#define AK4458_MODE_MASK GENMASK(7, 6) +#define AK4458_MODE_NORMAL (0 << AK4458_MODE_SHIFT) +#define AK4458_MODE_TDM128 (1 << AK4458_MODE_SHIFT) +#define AK4458_MODE_TDM256 (2 << AK4458_MODE_SHIFT) +#define AK4458_MODE_TDM512 (3 << AK4458_MODE_SHIFT) + +/* DAC Digital attenuator transition time setting + * Table 19 + * Mode ATS1 ATS2 ATT speed + * 0 0 0 4080/fs + * 1 0 1 2040/fs + * 2 1 0 510/fs + * 3 1 1 255/fs + * */ +#define AK4458_ATS_SHIFT 6 +#define AK4458_ATS_MASK GENMASK(7, 6) + +#endif /* _AK4458_H */ diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c new file mode 100644 index 000000000..31f609910 --- /dev/null +++ b/sound/soc/codecs/ak4535.c @@ -0,0 +1,453 @@ +/* + * ak4535.c -- AK4535 ALSA Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ak4535.h" + +/* codec private data */ +struct ak4535_priv { + struct regmap *regmap; + unsigned int sysclk; +}; + +/* + * ak4535 register cache + */ +static const struct reg_default ak4535_reg_defaults[] = { + { 0, 0x00 }, + { 1, 0x80 }, + { 2, 0x00 }, + { 3, 0x03 }, + { 4, 0x02 }, + { 5, 0x00 }, + { 6, 0x11 }, + { 7, 0x01 }, + { 8, 0x00 }, + { 9, 0x40 }, + { 10, 0x36 }, + { 11, 0x10 }, + { 12, 0x00 }, + { 13, 0x00 }, + { 14, 0x57 }, +}; + +static bool ak4535_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AK4535_STATUS: + return true; + default: + return false; + } +} + +static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"}; +static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"}; +static const char *ak4535_hp_out[] = {"Stereo", "Mono"}; +static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"}; +static const char *ak4535_mic_select[] = {"Internal", "External"}; + +static const struct soc_enum ak4535_enum[] = { + SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain), + SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out), + SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out), + SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp), + SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select), +}; + +static const struct snd_kcontrol_new ak4535_snd_controls[] = { + SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0), + SOC_ENUM("Mono 1 Output", ak4535_enum[1]), + SOC_ENUM("Mono 1 Gain", ak4535_enum[0]), + SOC_ENUM("Headphone Output", ak4535_enum[2]), + SOC_ENUM("Playback Deemphasis", ak4535_enum[3]), + SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0), + SOC_ENUM("Mic Select", ak4535_enum[4]), + SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0), + SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0), + SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0), + SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0), + SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0), + SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0), + SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1), + SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1), + SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0), + SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0), +}; + +/* Mono 1 Mixer */ +static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0), +}; + +/* Stereo Mixer */ +static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0), + SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0), +}; + +/* Input Mixer */ +static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0), + SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ak4535_input_mux_control = + SOC_DAPM_ENUM("Input Select", ak4535_enum[4]); + +/* HP L switch */ +static const struct snd_kcontrol_new ak4535_hpl_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1); + +/* HP R switch */ +static const struct snd_kcontrol_new ak4535_hpr_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1); + +/* mono 2 switch */ +static const struct snd_kcontrol_new ak4535_mono2_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0); + +/* Line out switch */ +static const struct snd_kcontrol_new ak4535_line_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0); + +/* ak4535 dapm widgets */ +static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_stereo_mixer_controls[0], + ARRAY_SIZE(ak4535_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_mono1_mixer_controls[0], + ARRAY_SIZE(ak4535_mono1_mixer_controls)), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_input_mixer_controls[0], + ARRAY_SIZE(ak4535_input_mixer_controls)), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ak4535_input_mux_control), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0), + SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, + &ak4535_mono2_control), + /* speaker powersave bit */ + SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ak4535_line_control), + SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpl_control), + SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpr_control), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPP"), + SND_SOC_DAPM_OUTPUT("SPN"), + SND_SOC_DAPM_OUTPUT("MOUT1"), + SND_SOC_DAPM_OUTPUT("MOUT2"), + SND_SOC_DAPM_OUTPUT("MICOUT"), + SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0), + SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0), + SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0), + SND_SOC_DAPM_INPUT("MICIN"), + SND_SOC_DAPM_INPUT("MICEXT"), + SND_SOC_DAPM_INPUT("AUX"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const struct snd_soc_dapm_route ak4535_audio_map[] = { + /*stereo mixer */ + {"Stereo Mixer", "Playback Switch", "DAC"}, + {"Stereo Mixer", "Mic Sidetone Switch", "Mic"}, + {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, + + /* mono1 mixer */ + {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"}, + {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, + + /* Mic */ + {"Mic", NULL, "AIN"}, + {"Input Mux", "Internal", "Mic Int Bias"}, + {"Input Mux", "External", "Mic Ext Bias"}, + {"Mic Int Bias", NULL, "MICIN"}, + {"Mic Ext Bias", NULL, "MICEXT"}, + {"MICOUT", NULL, "Input Mux"}, + + /* line out */ + {"LOUT", NULL, "Line Out Enable"}, + {"ROUT", NULL, "Line Out Enable"}, + {"Line Out Enable", "Switch", "Line Out"}, + {"Line Out", NULL, "Stereo Mixer"}, + + /* mono1 out */ + {"MOUT1", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono1 Mixer"}, + + /* left HP */ + {"HPL", NULL, "Left HP Enable"}, + {"Left HP Enable", "Switch", "HP L Amp"}, + {"HP L Amp", NULL, "Stereo Mixer"}, + + /* right HP */ + {"HPR", NULL, "Right HP Enable"}, + {"Right HP Enable", "Switch", "HP R Amp"}, + {"HP R Amp", NULL, "Stereo Mixer"}, + + /* speaker */ + {"SPP", NULL, "Speaker Enable"}, + {"SPN", NULL, "Speaker Enable"}, + {"Speaker Enable", "Switch", "Spk Amp"}, + {"Spk Amp", NULL, "MIN"}, + + /* mono 2 */ + {"MOUT2", NULL, "Mono 2 Enable"}, + {"Mono 2 Enable", "Switch", "Stereo Mixer"}, + + /* Aux In */ + {"Aux In", NULL, "AUX"}, + + /* ADC */ + {"ADC", NULL, "Input Mixer"}, + {"Input Mixer", "Mic Capture Switch", "Mic"}, + {"Input Mixer", "Aux Capture Switch", "Aux In"}, +}; + +static int ak4535_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct ak4535_priv *ak4535 = snd_soc_component_get_drvdata(component); + + ak4535->sysclk = freq; + return 0; +} + +static int ak4535_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4535_priv *ak4535 = snd_soc_component_get_drvdata(component); + u8 mode2 = snd_soc_component_read32(component, AK4535_MODE2) & ~(0x3 << 5); + int rate = params_rate(params), fs = 256; + + if (rate) + fs = ak4535->sysclk / rate; + + /* set fs */ + switch (fs) { + case 1024: + mode2 |= (0x2 << 5); + break; + case 512: + mode2 |= (0x1 << 5); + break; + case 256: + break; + } + + /* set rate */ + snd_soc_component_write(component, AK4535_MODE2, mode2); + return 0; +} + +static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 mode1 = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode1 = 0x0002; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode1 = 0x0001; + break; + default: + return -EINVAL; + } + + /* use 32 fs for BCLK to save power */ + mode1 |= 0x4; + + snd_soc_component_write(component, AK4535_MODE1, mode1); + return 0; +} + +static int ak4535_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, AK4535_DAC); + if (!mute) + snd_soc_component_write(component, AK4535_DAC, mute_reg & ~0x20); + else + snd_soc_component_write(component, AK4535_DAC, mute_reg | 0x20); + return 0; +} + +static int ak4535_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_component_update_bits(component, AK4535_DAC, 0x20, 0); + break; + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, AK4535_DAC, 0x20, 0x20); + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_component_update_bits(component, AK4535_PM1, 0x80, 0x80); + snd_soc_component_update_bits(component, AK4535_PM2, 0x80, 0); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, AK4535_PM1, 0x80, 0); + break; + } + return 0; +} + +#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +static const struct snd_soc_dai_ops ak4535_dai_ops = { + .hw_params = ak4535_hw_params, + .set_fmt = ak4535_set_dai_fmt, + .digital_mute = ak4535_mute, + .set_sysclk = ak4535_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver ak4535_dai = { + .name = "ak4535-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &ak4535_dai_ops, +}; + +static int ak4535_resume(struct snd_soc_component *component) +{ + snd_soc_component_cache_sync(component); + return 0; +} + +static const struct regmap_config ak4535_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4535_STATUS, + .volatile_reg = ak4535_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ak4535_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ak4535_reg_defaults), +}; + +static const struct snd_soc_component_driver soc_component_dev_ak4535 = { + .resume = ak4535_resume, + .set_bias_level = ak4535_set_bias_level, + .controls = ak4535_snd_controls, + .num_controls = ARRAY_SIZE(ak4535_snd_controls), + .dapm_widgets = ak4535_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4535_dapm_widgets), + .dapm_routes = ak4535_audio_map, + .num_dapm_routes = ARRAY_SIZE(ak4535_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ak4535_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ak4535_priv *ak4535; + int ret; + + ak4535 = devm_kzalloc(&i2c->dev, sizeof(struct ak4535_priv), + GFP_KERNEL); + if (ak4535 == NULL) + return -ENOMEM; + + ak4535->regmap = devm_regmap_init_i2c(i2c, &ak4535_regmap); + if (IS_ERR(ak4535->regmap)) { + ret = PTR_ERR(ak4535->regmap); + dev_err(&i2c->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, ak4535); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_ak4535, &ak4535_dai, 1); + + return ret; +} + +static const struct i2c_device_id ak4535_i2c_id[] = { + { "ak4535", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id); + +static struct i2c_driver ak4535_i2c_driver = { + .driver = { + .name = "ak4535", + }, + .probe = ak4535_i2c_probe, + .id_table = ak4535_i2c_id, +}; + +module_i2c_driver(ak4535_i2c_driver); + +MODULE_DESCRIPTION("Soc AK4535 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h new file mode 100644 index 000000000..402de1d27 --- /dev/null +++ b/sound/soc/codecs/ak4535.h @@ -0,0 +1,37 @@ +/* + * ak4535.h -- AK4535 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AK4535_H +#define _AK4535_H + +/* AK4535 register space */ + +#define AK4535_PM1 0x0 +#define AK4535_PM2 0x1 +#define AK4535_SIG1 0x2 +#define AK4535_SIG2 0x3 +#define AK4535_MODE1 0x4 +#define AK4535_MODE2 0x5 +#define AK4535_DAC 0x6 +#define AK4535_MIC 0x7 +#define AK4535_TIMER 0x8 +#define AK4535_ALC1 0x9 +#define AK4535_ALC2 0xa +#define AK4535_PGA 0xb +#define AK4535_LATT 0xc +#define AK4535_RATT 0xd +#define AK4535_VOL 0xe +#define AK4535_STATUS 0xf + +#endif diff --git a/sound/soc/codecs/ak4554.c b/sound/soc/codecs/ak4554.c new file mode 100644 index 000000000..2fa83a1a8 --- /dev/null +++ b/sound/soc/codecs/ak4554.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +// ak4554.c +// +// Copyright (C) 2013 Renesas Solutions Corp. +// Kuninori Morimoto + +#include +#include + +/* + * ak4554 is very simple DA/AD converter which has no setting register. + * + * CAUTION + * + * ak4554 playback format is SND_SOC_DAIFMT_RIGHT_J, + * and, capture format is SND_SOC_DAIFMT_LEFT_J + * on same bit clock, LR clock. + * But, this driver doesn't have snd_soc_dai_ops :: set_fmt + * + * CPU/Codec DAI image + * + * CPU-DAI1 (plaback only fmt = RIGHT_J) --+-- ak4554 + * | + * CPU-DAI2 (capture only fmt = LEFT_J) ---+ + */ + +static const struct snd_soc_dapm_widget ak4554_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), + +SND_SOC_DAPM_OUTPUT("AOUTL"), +SND_SOC_DAPM_OUTPUT("AOUTR"), +}; + +static const struct snd_soc_dapm_route ak4554_dapm_routes[] = { + { "Capture", NULL, "AINL" }, + { "Capture", NULL, "AINR" }, + + { "AOUTL", NULL, "Playback" }, + { "AOUTR", NULL, "Playback" }, +}; + +static struct snd_soc_dai_driver ak4554_dai = { + .name = "ak4554-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver soc_component_dev_ak4554 = { + .dapm_widgets = ak4554_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4554_dapm_widgets), + .dapm_routes = ak4554_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ak4554_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ak4554_soc_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_ak4554, + &ak4554_dai, 1); +} + +static const struct of_device_id ak4554_of_match[] = { + { .compatible = "asahi-kasei,ak4554" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ak4554_of_match); + +static struct platform_driver ak4554_driver = { + .driver = { + .name = "ak4554-adc-dac", + .of_match_table = ak4554_of_match, + }, + .probe = ak4554_soc_probe, +}; +module_platform_driver(ak4554_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SoC AK4554 driver"); +MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c new file mode 100644 index 000000000..c1181a207 --- /dev/null +++ b/sound/soc/codecs/ak4613.c @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ak4613.c -- Asahi Kasei ALSA Soc Audio driver +// +// Copyright (C) 2015 Renesas Electronics Corporation +// Kuninori Morimoto +// +// Based on ak4642.c by Kuninori Morimoto +// Based on wm8731.c by Richard Purdie +// Based on ak4535.c by Richard Purdie +// Based on wm8753.c by Liam Girdwood + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PW_MGMT1 0x00 /* Power Management 1 */ +#define PW_MGMT2 0x01 /* Power Management 2 */ +#define PW_MGMT3 0x02 /* Power Management 3 */ +#define CTRL1 0x03 /* Control 1 */ +#define CTRL2 0x04 /* Control 2 */ +#define DEMP1 0x05 /* De-emphasis1 */ +#define DEMP2 0x06 /* De-emphasis2 */ +#define OFD 0x07 /* Overflow Detect */ +#define ZRD 0x08 /* Zero Detect */ +#define ICTRL 0x09 /* Input Control */ +#define OCTRL 0x0a /* Output Control */ +#define LOUT1 0x0b /* LOUT1 Volume Control */ +#define ROUT1 0x0c /* ROUT1 Volume Control */ +#define LOUT2 0x0d /* LOUT2 Volume Control */ +#define ROUT2 0x0e /* ROUT2 Volume Control */ +#define LOUT3 0x0f /* LOUT3 Volume Control */ +#define ROUT3 0x10 /* ROUT3 Volume Control */ +#define LOUT4 0x11 /* LOUT4 Volume Control */ +#define ROUT4 0x12 /* ROUT4 Volume Control */ +#define LOUT5 0x13 /* LOUT5 Volume Control */ +#define ROUT5 0x14 /* ROUT5 Volume Control */ +#define LOUT6 0x15 /* LOUT6 Volume Control */ +#define ROUT6 0x16 /* ROUT6 Volume Control */ + +/* PW_MGMT1 */ +#define RSTN BIT(0) +#define PMDAC BIT(1) +#define PMADC BIT(2) +#define PMVR BIT(3) + +/* PW_MGMT2 */ +#define PMAD_ALL 0x7 + +/* PW_MGMT3 */ +#define PMDA_ALL 0x3f + +/* CTRL1 */ +#define DIF0 BIT(3) +#define DIF1 BIT(4) +#define DIF2 BIT(5) +#define TDM0 BIT(6) +#define TDM1 BIT(7) +#define NO_FMT (0xff) +#define FMT_MASK (0xf8) + +/* CTRL2 */ +#define DFS_MASK (3 << 2) +#define DFS_NORMAL_SPEED (0 << 2) +#define DFS_DOUBLE_SPEED (1 << 2) +#define DFS_QUAD_SPEED (2 << 2) + +/* ICTRL */ +#define ICTRL_MASK (0x3) + +/* OCTRL */ +#define OCTRL_MASK (0x3F) + +struct ak4613_formats { + unsigned int width; + unsigned int fmt; +}; + +struct ak4613_interface { + struct ak4613_formats capture; + struct ak4613_formats playback; +}; + +struct ak4613_priv { + struct mutex lock; + const struct ak4613_interface *iface; + struct snd_pcm_hw_constraint_list constraint; + struct work_struct dummy_write_work; + struct snd_soc_component *component; + unsigned int rate; + unsigned int sysclk; + + unsigned int fmt; + u8 oc; + u8 ic; + int cnt; +}; + +/* + * Playback Volume + * + * max : 0x00 : 0 dB + * ( 0.5 dB step ) + * min : 0xFE : -127.0 dB + * mute: 0xFF + */ +static const DECLARE_TLV_DB_SCALE(out_tlv, -12750, 50, 1); + +static const struct snd_kcontrol_new ak4613_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume1", LOUT1, ROUT1, + 0, 0xFF, 1, out_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume2", LOUT2, ROUT2, + 0, 0xFF, 1, out_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume3", LOUT3, ROUT3, + 0, 0xFF, 1, out_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume4", LOUT4, ROUT4, + 0, 0xFF, 1, out_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume5", LOUT5, ROUT5, + 0, 0xFF, 1, out_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume6", LOUT6, ROUT6, + 0, 0xFF, 1, out_tlv), +}; + +static const struct reg_default ak4613_reg[] = { + { 0x0, 0x0f }, { 0x1, 0x07 }, { 0x2, 0x3f }, { 0x3, 0x20 }, + { 0x4, 0x20 }, { 0x5, 0x55 }, { 0x6, 0x05 }, { 0x7, 0x07 }, + { 0x8, 0x0f }, { 0x9, 0x07 }, { 0xa, 0x3f }, { 0xb, 0x00 }, + { 0xc, 0x00 }, { 0xd, 0x00 }, { 0xe, 0x00 }, { 0xf, 0x00 }, + { 0x10, 0x00 }, { 0x11, 0x00 }, { 0x12, 0x00 }, { 0x13, 0x00 }, + { 0x14, 0x00 }, { 0x15, 0x00 }, { 0x16, 0x00 }, +}; + +#define AUDIO_IFACE_TO_VAL(fmts) ((fmts - ak4613_iface) << 3) +#define AUDIO_IFACE(b, fmt) { b, SND_SOC_DAIFMT_##fmt } +static const struct ak4613_interface ak4613_iface[] = { + /* capture */ /* playback */ + /* [0] - [2] are not supported */ + [3] = { AUDIO_IFACE(24, LEFT_J), AUDIO_IFACE(24, LEFT_J) }, + [4] = { AUDIO_IFACE(24, I2S), AUDIO_IFACE(24, I2S) }, +}; + +static const struct regmap_config ak4613_regmap_cfg = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x16, + .reg_defaults = ak4613_reg, + .num_reg_defaults = ARRAY_SIZE(ak4613_reg), + .cache_type = REGCACHE_RBTREE, +}; + +static const struct of_device_id ak4613_of_match[] = { + { .compatible = "asahi-kasei,ak4613", .data = &ak4613_regmap_cfg }, + {}, +}; +MODULE_DEVICE_TABLE(of, ak4613_of_match); + +static const struct i2c_device_id ak4613_i2c_id[] = { + { "ak4613", (kernel_ulong_t)&ak4613_regmap_cfg }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4613_i2c_id); + +static const struct snd_soc_dapm_widget ak4613_dapm_widgets[] = { + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("LOUT3"), + SND_SOC_DAPM_OUTPUT("LOUT4"), + SND_SOC_DAPM_OUTPUT("LOUT5"), + SND_SOC_DAPM_OUTPUT("LOUT6"), + + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("ROUT3"), + SND_SOC_DAPM_OUTPUT("ROUT4"), + SND_SOC_DAPM_OUTPUT("ROUT5"), + SND_SOC_DAPM_OUTPUT("ROUT6"), + + /* Inputs */ + SND_SOC_DAPM_INPUT("LIN1"), + SND_SOC_DAPM_INPUT("LIN2"), + + SND_SOC_DAPM_INPUT("RIN1"), + SND_SOC_DAPM_INPUT("RIN2"), + + /* DAC */ + SND_SOC_DAPM_DAC("DAC1", NULL, PW_MGMT3, 0, 0), + SND_SOC_DAPM_DAC("DAC2", NULL, PW_MGMT3, 1, 0), + SND_SOC_DAPM_DAC("DAC3", NULL, PW_MGMT3, 2, 0), + SND_SOC_DAPM_DAC("DAC4", NULL, PW_MGMT3, 3, 0), + SND_SOC_DAPM_DAC("DAC5", NULL, PW_MGMT3, 4, 0), + SND_SOC_DAPM_DAC("DAC6", NULL, PW_MGMT3, 5, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC1", NULL, PW_MGMT2, 0, 0), + SND_SOC_DAPM_ADC("ADC2", NULL, PW_MGMT2, 1, 0), +}; + +static const struct snd_soc_dapm_route ak4613_intercon[] = { + {"LOUT1", NULL, "DAC1"}, + {"LOUT2", NULL, "DAC2"}, + {"LOUT3", NULL, "DAC3"}, + {"LOUT4", NULL, "DAC4"}, + {"LOUT5", NULL, "DAC5"}, + {"LOUT6", NULL, "DAC6"}, + + {"ROUT1", NULL, "DAC1"}, + {"ROUT2", NULL, "DAC2"}, + {"ROUT3", NULL, "DAC3"}, + {"ROUT4", NULL, "DAC4"}, + {"ROUT5", NULL, "DAC5"}, + {"ROUT6", NULL, "DAC6"}, + + {"DAC1", NULL, "Playback"}, + {"DAC2", NULL, "Playback"}, + {"DAC3", NULL, "Playback"}, + {"DAC4", NULL, "Playback"}, + {"DAC5", NULL, "Playback"}, + {"DAC6", NULL, "Playback"}, + + {"Capture", NULL, "ADC1"}, + {"Capture", NULL, "ADC2"}, + + {"ADC1", NULL, "LIN1"}, + {"ADC2", NULL, "LIN2"}, + + {"ADC1", NULL, "RIN1"}, + {"ADC2", NULL, "RIN2"}, +}; + +static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + + mutex_lock(&priv->lock); + priv->cnt--; + if (priv->cnt < 0) { + dev_err(dev, "unexpected counter error\n"); + priv->cnt = 0; + } + if (!priv->cnt) + priv->iface = NULL; + mutex_unlock(&priv->lock); +} + +static void ak4613_hw_constraints(struct ak4613_priv *priv, + struct snd_pcm_runtime *runtime) +{ + static const unsigned int ak4613_rates[] = { + 32000, + 44100, + 48000, + 64000, + 88200, + 96000, + 176400, + 192000, + }; + struct snd_pcm_hw_constraint_list *constraint = &priv->constraint; + unsigned int fs; + int i; + + constraint->list = ak4613_rates; + constraint->mask = 0; + constraint->count = 0; + + /* + * Slave Mode + * Normal: [32kHz, 48kHz] : 256fs,384fs or 512fs + * Double: [64kHz, 96kHz] : 256fs + * Quad : [128kHz,192kHz]: 128fs + * + * Master mode + * Normal: [32kHz, 48kHz] : 256fs or 512fs + * Double: [64kHz, 96kHz] : 256fs + * Quad : [128kHz,192kHz]: 128fs + */ + for (i = 0; i < ARRAY_SIZE(ak4613_rates); i++) { + /* minimum fs on each range */ + fs = (ak4613_rates[i] <= 96000) ? 256 : 128; + + if (priv->sysclk >= ak4613_rates[i] * fs) + constraint->count = i + 1; + } + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, constraint); +} + +static int ak4613_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + + priv->cnt++; + + ak4613_hw_constraints(priv, substream->runtime); + + return 0; +} + +static int ak4613_dai_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + + priv->sysclk = freq; + + return 0; +} + +static int ak4613_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + + fmt &= SND_SOC_DAIFMT_FORMAT_MASK; + + switch (fmt) { + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_I2S: + priv->fmt = fmt; + break; + default: + return -EINVAL; + } + + return 0; +} + +static bool ak4613_dai_fmt_matching(const struct ak4613_interface *iface, + int is_play, + unsigned int fmt, unsigned int width) +{ + const struct ak4613_formats *fmts; + + fmts = (is_play) ? &iface->playback : &iface->capture; + + if (fmts->fmt != fmt) + return false; + + if (fmts->width != width) + return false; + + return true; +} + +static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + const struct ak4613_interface *iface; + struct device *dev = component->dev; + unsigned int width = params_width(params); + unsigned int fmt = priv->fmt; + unsigned int rate; + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i, ret; + u8 fmt_ctrl, ctrl2; + + rate = params_rate(params); + switch (rate) { + case 32000: + case 44100: + case 48000: + ctrl2 = DFS_NORMAL_SPEED; + break; + case 64000: + case 88200: + case 96000: + ctrl2 = DFS_DOUBLE_SPEED; + break; + case 176400: + case 192000: + ctrl2 = DFS_QUAD_SPEED; + break; + default: + return -EINVAL; + } + priv->rate = rate; + + /* + * FIXME + * + * It doesn't support TDM at this point + */ + fmt_ctrl = NO_FMT; + ret = -EINVAL; + iface = NULL; + + mutex_lock(&priv->lock); + if (priv->iface) { + if (ak4613_dai_fmt_matching(priv->iface, is_play, fmt, width)) + iface = priv->iface; + } else { + for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { + if (!ak4613_dai_fmt_matching(ak4613_iface + i, + is_play, + fmt, width)) + continue; + iface = ak4613_iface + i; + break; + } + } + + if ((priv->iface == NULL) || + (priv->iface == iface)) { + priv->iface = iface; + ret = 0; + } + mutex_unlock(&priv->lock); + + if (ret < 0) + goto hw_params_end; + + fmt_ctrl = AUDIO_IFACE_TO_VAL(iface); + + snd_soc_component_update_bits(component, CTRL1, FMT_MASK, fmt_ctrl); + snd_soc_component_update_bits(component, CTRL2, DFS_MASK, ctrl2); + + snd_soc_component_update_bits(component, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_component_update_bits(component, OCTRL, OCTRL_MASK, priv->oc); + +hw_params_end: + if (ret < 0) + dev_warn(dev, "unsupported data width/format combination\n"); + + return ret; +} + +static int ak4613_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + u8 mgmt1 = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + mgmt1 |= RSTN; + /* fall through */ + case SND_SOC_BIAS_PREPARE: + mgmt1 |= PMADC | PMDAC; + /* fall through */ + case SND_SOC_BIAS_STANDBY: + mgmt1 |= PMVR; + /* fall through */ + case SND_SOC_BIAS_OFF: + default: + break; + } + + snd_soc_component_write(component, PW_MGMT1, mgmt1); + + return 0; +} + +static void ak4613_dummy_write(struct work_struct *work) +{ + struct ak4613_priv *priv = container_of(work, + struct ak4613_priv, + dummy_write_work); + struct snd_soc_component *component = priv->component; + unsigned int mgmt1; + unsigned int mgmt3; + + /* + * PW_MGMT1 / PW_MGMT3 needs dummy write at least after 5 LR clocks + * + * Note + * + * To avoid extra delay, we want to avoid preemption here, + * but we can't. Because it uses I2C access which is using IRQ + * and sleep. Thus, delay might be more than 5 LR clocks + * see also + * ak4613_dai_trigger() + */ + udelay(5000000 / priv->rate); + + snd_soc_component_read(component, PW_MGMT1, &mgmt1); + snd_soc_component_read(component, PW_MGMT3, &mgmt3); + + snd_soc_component_write(component, PW_MGMT1, mgmt1); + snd_soc_component_write(component, PW_MGMT3, mgmt3); +} + +static int ak4613_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + + /* + * FIXME + * + * PW_MGMT1 / PW_MGMT3 needs dummy write at least after 5 LR clocks + * from Power Down Release. Otherwise, Playback volume will be 0dB. + * To avoid complex multiple delay/dummy_write method from + * ak4613_set_bias_level() / SND_SOC_DAPM_DAC_E("DACx", ...), + * call it once here. + * + * But, unfortunately, we can't "write" here because here is atomic + * context (It uses I2C access for writing). + * Thus, use schedule_work() to switching to normal context + * immediately. + * + * Note + * + * Calling ak4613_dummy_write() function might be delayed. + * In such case, ak4613 volume might be temporarily 0dB when + * beggining of playback. + * see also + * ak4613_dummy_write() + */ + + if ((cmd != SNDRV_PCM_TRIGGER_START) && + (cmd != SNDRV_PCM_TRIGGER_RESUME)) + return 0; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + priv->component = component; + schedule_work(&priv->dummy_write_work); + + return 0; +} + +static const struct snd_soc_dai_ops ak4613_dai_ops = { + .startup = ak4613_dai_startup, + .shutdown = ak4613_dai_shutdown, + .set_sysclk = ak4613_dai_set_sysclk, + .set_fmt = ak4613_dai_set_fmt, + .trigger = ak4613_dai_trigger, + .hw_params = ak4613_dai_hw_params, +}; + +#define AK4613_PCM_RATE (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 AK4613_PCM_FMTBIT (SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver ak4613_dai = { + .name = "ak4613-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AK4613_PCM_RATE, + .formats = AK4613_PCM_FMTBIT, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AK4613_PCM_RATE, + .formats = AK4613_PCM_FMTBIT, + }, + .ops = &ak4613_dai_ops, + .symmetric_rates = 1, +}; + +static int ak4613_suspend(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + return 0; +} + +static int ak4613_resume(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, false); + return regcache_sync(regmap); +} + +static const struct snd_soc_component_driver soc_component_dev_ak4613 = { + .suspend = ak4613_suspend, + .resume = ak4613_resume, + .set_bias_level = ak4613_set_bias_level, + .controls = ak4613_snd_controls, + .num_controls = ARRAY_SIZE(ak4613_snd_controls), + .dapm_widgets = ak4613_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4613_dapm_widgets), + .dapm_routes = ak4613_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4613_intercon), + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static void ak4613_parse_of(struct ak4613_priv *priv, + struct device *dev) +{ + struct device_node *np = dev->of_node; + char prop[32]; + int i; + + /* Input 1 - 2 */ + for (i = 0; i < 2; i++) { + snprintf(prop, sizeof(prop), "asahi-kasei,in%d-single-end", i + 1); + if (!of_get_property(np, prop, NULL)) + priv->ic |= 1 << i; + } + + /* Output 1 - 6 */ + for (i = 0; i < 6; i++) { + snprintf(prop, sizeof(prop), "asahi-kasei,out%d-single-end", i + 1); + if (!of_get_property(np, prop, NULL)) + priv->oc |= 1 << i; + } +} + +static int ak4613_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct device_node *np = dev->of_node; + const struct regmap_config *regmap_cfg; + struct regmap *regmap; + struct ak4613_priv *priv; + + regmap_cfg = NULL; + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(ak4613_of_match, dev); + if (of_id) + regmap_cfg = of_id->data; + } else { + regmap_cfg = (const struct regmap_config *)id->driver_data; + } + + if (!regmap_cfg) + return -EINVAL; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ak4613_parse_of(priv, dev); + + priv->iface = NULL; + priv->cnt = 0; + priv->sysclk = 0; + INIT_WORK(&priv->dummy_write_work, ak4613_dummy_write); + + mutex_init(&priv->lock); + + i2c_set_clientdata(i2c, priv); + + regmap = devm_regmap_init_i2c(i2c, regmap_cfg); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return devm_snd_soc_register_component(dev, &soc_component_dev_ak4613, + &ak4613_dai, 1); +} + +static int ak4613_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static struct i2c_driver ak4613_i2c_driver = { + .driver = { + .name = "ak4613-codec", + .of_match_table = ak4613_of_match, + }, + .probe = ak4613_i2c_probe, + .remove = ak4613_i2c_remove, + .id_table = ak4613_i2c_id, +}; + +module_i2c_driver(ak4613_i2c_driver); + +MODULE_DESCRIPTION("Soc AK4613 driver"); +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c new file mode 100644 index 000000000..05869beff --- /dev/null +++ b/sound/soc/codecs/ak4641.c @@ -0,0 +1,645 @@ +/* + * ak4641.c -- AK4641 ALSA Soc Audio driver + * + * Copyright (C) 2008 Harald Welte + * Copyright (C) 2011 Dmitry Artamonow + * + * Based on ak4535.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AK4641 register space */ +#define AK4641_PM1 0x00 +#define AK4641_PM2 0x01 +#define AK4641_SIG1 0x02 +#define AK4641_SIG2 0x03 +#define AK4641_MODE1 0x04 +#define AK4641_MODE2 0x05 +#define AK4641_DAC 0x06 +#define AK4641_MIC 0x07 +#define AK4641_TIMER 0x08 +#define AK4641_ALC1 0x09 +#define AK4641_ALC2 0x0a +#define AK4641_PGA 0x0b +#define AK4641_LATT 0x0c +#define AK4641_RATT 0x0d +#define AK4641_VOL 0x0e +#define AK4641_STATUS 0x0f +#define AK4641_EQLO 0x10 +#define AK4641_EQMID 0x11 +#define AK4641_EQHI 0x12 +#define AK4641_BTIF 0x13 + +/* codec private data */ +struct ak4641_priv { + struct regmap *regmap; + unsigned int sysclk; + int deemph; + int playback_fs; +}; + +/* + * ak4641 register cache + */ +static const struct reg_default ak4641_reg_defaults[] = { + { 0, 0x00 }, { 1, 0x80 }, { 2, 0x00 }, { 3, 0x80 }, + { 4, 0x02 }, { 5, 0x00 }, { 6, 0x11 }, { 7, 0x05 }, + { 8, 0x00 }, { 9, 0x00 }, { 10, 0x36 }, { 11, 0x10 }, + { 12, 0x00 }, { 13, 0x00 }, { 14, 0x57 }, { 15, 0x00 }, + { 16, 0x88 }, { 17, 0x88 }, { 18, 0x08 }, { 19, 0x08 } +}; + +static const int deemph_settings[] = {44100, 0, 48000, 32000}; + +static int ak4641_set_deemph(struct snd_soc_component *component) +{ + struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); + int i, best = 0; + + for (i = 0 ; i < ARRAY_SIZE(deemph_settings); i++) { + /* if deemphasis is on, select the nearest available rate */ + if (ak4641->deemph && deemph_settings[i] != 0 && + abs(deemph_settings[i] - ak4641->playback_fs) < + abs(deemph_settings[best] - ak4641->playback_fs)) + best = i; + + if (!ak4641->deemph && deemph_settings[i] == 0) + best = i; + } + + dev_dbg(component->dev, "Set deemphasis %d\n", best); + + return snd_soc_component_update_bits(component, AK4641_DAC, 0x3, best); +} + +static int ak4641_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); + int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + ak4641->deemph = deemph; + + return ak4641_set_deemph(component); +} + +static int ak4641_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = ak4641->deemph; + return 0; +}; + +static const char *ak4641_mono_out[] = {"(L + R)/2", "Hi-Z"}; +static const char *ak4641_hp_out[] = {"Stereo", "Mono"}; +static const char *ak4641_mic_select[] = {"Internal", "External"}; +static const char *ak4641_mic_or_dac[] = {"Microphone", "Voice DAC"}; + + +static const DECLARE_TLV_DB_SCALE(mono_gain_tlv, -1700, 2300, 0); +static const DECLARE_TLV_DB_SCALE(mic_boost_tlv, 0, 2000, 0); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1050, 150, 0); +static const DECLARE_TLV_DB_SCALE(master_tlv, -12750, 50, 0); +static const DECLARE_TLV_DB_SCALE(mic_stereo_sidetone_tlv, -2700, 300, 0); +static const DECLARE_TLV_DB_SCALE(mic_mono_sidetone_tlv, -400, 400, 0); +static const DECLARE_TLV_DB_SCALE(capture_tlv, -800, 50, 0); +static const DECLARE_TLV_DB_SCALE(alc_tlv, -800, 50, 0); +static const DECLARE_TLV_DB_SCALE(aux_in_tlv, -2100, 300, 0); + + +static SOC_ENUM_SINGLE_DECL(ak4641_mono_out_enum, + AK4641_SIG1, 6, ak4641_mono_out); +static SOC_ENUM_SINGLE_DECL(ak4641_hp_out_enum, + AK4641_MODE2, 2, ak4641_hp_out); +static SOC_ENUM_SINGLE_DECL(ak4641_mic_select_enum, + AK4641_MIC, 1, ak4641_mic_select); +static SOC_ENUM_SINGLE_DECL(ak4641_mic_or_dac_enum, + AK4641_BTIF, 4, ak4641_mic_or_dac); + +static const struct snd_kcontrol_new ak4641_snd_controls[] = { + SOC_ENUM("Mono 1 Output", ak4641_mono_out_enum), + SOC_SINGLE_TLV("Mono 1 Gain Volume", AK4641_SIG1, 7, 1, 1, + mono_gain_tlv), + SOC_ENUM("Headphone Output", ak4641_hp_out_enum), + SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0, + ak4641_get_deemph, ak4641_put_deemph), + + SOC_SINGLE_TLV("Mic Boost Volume", AK4641_MIC, 0, 1, 0, mic_boost_tlv), + + SOC_SINGLE("ALC Operation Time", AK4641_TIMER, 0, 3, 0), + SOC_SINGLE("ALC Recovery Time", AK4641_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4641_TIMER, 4, 3, 0), + + SOC_SINGLE("ALC 1 Switch", AK4641_ALC1, 5, 1, 0), + + SOC_SINGLE_TLV("ALC Volume", AK4641_ALC2, 0, 71, 0, alc_tlv), + SOC_SINGLE("Left Out Enable Switch", AK4641_SIG2, 1, 1, 0), + SOC_SINGLE("Right Out Enable Switch", AK4641_SIG2, 0, 1, 0), + + SOC_SINGLE_TLV("Capture Volume", AK4641_PGA, 0, 71, 0, capture_tlv), + + SOC_DOUBLE_R_TLV("Master Playback Volume", AK4641_LATT, + AK4641_RATT, 0, 255, 1, master_tlv), + + SOC_SINGLE_TLV("AUX In Volume", AK4641_VOL, 0, 15, 0, aux_in_tlv), + + SOC_SINGLE("Equalizer Switch", AK4641_DAC, 2, 1, 0), + SOC_SINGLE_TLV("EQ1 100 Hz Volume", AK4641_EQLO, 0, 15, 1, eq_tlv), + SOC_SINGLE_TLV("EQ2 250 Hz Volume", AK4641_EQLO, 4, 15, 1, eq_tlv), + SOC_SINGLE_TLV("EQ3 1 kHz Volume", AK4641_EQMID, 0, 15, 1, eq_tlv), + SOC_SINGLE_TLV("EQ4 3.5 kHz Volume", AK4641_EQMID, 4, 15, 1, eq_tlv), + SOC_SINGLE_TLV("EQ5 10 kHz Volume", AK4641_EQHI, 0, 15, 1, eq_tlv), +}; + +/* Mono 1 Mixer */ +static const struct snd_kcontrol_new ak4641_mono1_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Mic Mono Sidetone Volume", AK4641_VOL, 7, 1, 0, + mic_mono_sidetone_tlv), + SOC_DAPM_SINGLE("Mic Mono Sidetone Switch", AK4641_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mono Playback Switch", AK4641_SIG1, 5, 1, 0), +}; + +/* Stereo Mixer */ +static const struct snd_kcontrol_new ak4641_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("Mic Sidetone Volume", AK4641_VOL, 4, 7, 0, + mic_stereo_sidetone_tlv), + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4641_SIG2, 4, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", AK4641_SIG2, 7, 1, 0), + SOC_DAPM_SINGLE("Aux Bypass Switch", AK4641_SIG2, 5, 1, 0), +}; + +/* Input Mixer */ +static const struct snd_kcontrol_new ak4641_input_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Capture Switch", AK4641_MIC, 2, 1, 0), + SOC_DAPM_SINGLE("Aux Capture Switch", AK4641_MIC, 5, 1, 0), +}; + +/* Mic mux */ +static const struct snd_kcontrol_new ak4641_mic_mux_control = + SOC_DAPM_ENUM("Mic Select", ak4641_mic_select_enum); + +/* Input mux */ +static const struct snd_kcontrol_new ak4641_input_mux_control = + SOC_DAPM_ENUM("Input Select", ak4641_mic_or_dac_enum); + +/* mono 2 switch */ +static const struct snd_kcontrol_new ak4641_mono2_control = + SOC_DAPM_SINGLE("Switch", AK4641_SIG1, 0, 1, 0); + +/* ak4641 dapm widgets */ +static const struct snd_soc_dapm_widget ak4641_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, + &ak4641_stereo_mixer_controls[0], + ARRAY_SIZE(ak4641_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, + &ak4641_mono1_mixer_controls[0], + ARRAY_SIZE(ak4641_mono1_mixer_controls)), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, + &ak4641_input_mixer_controls[0], + ARRAY_SIZE(ak4641_input_mixer_controls)), + SND_SOC_DAPM_MUX("Mic Mux", SND_SOC_NOPM, 0, 0, + &ak4641_mic_mux_control), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ak4641_input_mux_control), + SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, + &ak4641_mono2_control), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("MOUT1"), + SND_SOC_DAPM_OUTPUT("MOUT2"), + SND_SOC_DAPM_OUTPUT("MICOUT"), + + SND_SOC_DAPM_ADC("ADC", "HiFi Capture", AK4641_PM1, 0, 0), + SND_SOC_DAPM_PGA("Mic", AK4641_PM1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX In", AK4641_PM1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", AK4641_PM1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out", AK4641_PM1, 4, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DAC", "HiFi Playback", AK4641_PM2, 0, 0), + SND_SOC_DAPM_PGA("Mono Out 2", AK4641_PM2, 3, 0, NULL, 0), + + SND_SOC_DAPM_ADC("Voice ADC", "Voice Capture", AK4641_BTIF, 0, 0), + SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AK4641_BTIF, 1, 0), + + SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4641_MIC, 3, 0), + SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4641_MIC, 4, 0), + + SND_SOC_DAPM_INPUT("MICIN"), + SND_SOC_DAPM_INPUT("MICEXT"), + SND_SOC_DAPM_INPUT("AUX"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const struct snd_soc_dapm_route ak4641_audio_map[] = { + /* Stereo Mixer */ + {"Stereo Mixer", "Playback Switch", "DAC"}, + {"Stereo Mixer", "Mic Sidetone Switch", "Input Mux"}, + {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, + + /* Mono 1 Mixer */ + {"Mono1 Mixer", "Mic Mono Sidetone Switch", "Input Mux"}, + {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, + + /* Mic */ + {"Mic", NULL, "AIN"}, + {"Mic Mux", "Internal", "Mic Int Bias"}, + {"Mic Mux", "External", "Mic Ext Bias"}, + {"Mic Int Bias", NULL, "MICIN"}, + {"Mic Ext Bias", NULL, "MICEXT"}, + {"MICOUT", NULL, "Mic Mux"}, + + /* Input Mux */ + {"Input Mux", "Microphone", "Mic"}, + {"Input Mux", "Voice DAC", "Voice DAC"}, + + /* Line Out */ + {"LOUT", NULL, "Line Out"}, + {"ROUT", NULL, "Line Out"}, + {"Line Out", NULL, "Stereo Mixer"}, + + /* Mono 1 Out */ + {"MOUT1", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono1 Mixer"}, + + /* Mono 2 Out */ + {"MOUT2", NULL, "Mono 2 Enable"}, + {"Mono 2 Enable", "Switch", "Mono Out 2"}, + {"Mono Out 2", NULL, "Stereo Mixer"}, + + {"Voice ADC", NULL, "Mono 2 Enable"}, + + /* Aux In */ + {"AUX In", NULL, "AUX"}, + + /* ADC */ + {"ADC", NULL, "Input Mixer"}, + {"Input Mixer", "Mic Capture Switch", "Mic"}, + {"Input Mixer", "Aux Capture Switch", "AUX In"}, +}; + +static int ak4641_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); + + ak4641->sysclk = freq; + return 0; +} + +static int ak4641_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); + int rate = params_rate(params), fs = 256; + u8 mode2; + + if (rate) + fs = ak4641->sysclk / rate; + else + return -EINVAL; + + /* set fs */ + switch (fs) { + case 1024: + mode2 = (0x2 << 5); + break; + case 512: + mode2 = (0x1 << 5); + break; + case 256: + mode2 = (0x0 << 5); + break; + default: + dev_err(component->dev, "Error: unsupported fs=%d\n", fs); + return -EINVAL; + } + + snd_soc_component_update_bits(component, AK4641_MODE2, (0x3 << 5), mode2); + + /* Update de-emphasis filter for the new rate */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ak4641->playback_fs = rate; + ak4641_set_deemph(component); + } + + return 0; +} + +static int ak4641_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 btif; + int ret; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + btif = (0x3 << 5); + break; + case SND_SOC_DAIFMT_LEFT_J: + btif = (0x2 << 5); + break; + case SND_SOC_DAIFMT_DSP_A: /* MSB after FRM */ + btif = (0x0 << 5); + break; + case SND_SOC_DAIFMT_DSP_B: /* MSB during FRM */ + btif = (0x1 << 5); + break; + default: + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, AK4641_BTIF, (0x3 << 5), btif); + if (ret < 0) + return ret; + + return 0; +} + +static int ak4641_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 mode1 = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode1 = 0x02; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode1 = 0x01; + break; + default: + return -EINVAL; + } + + return snd_soc_component_write(component, AK4641_MODE1, mode1); +} + +static int ak4641_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + return snd_soc_component_update_bits(component, AK4641_DAC, 0x20, mute ? 0x20 : 0); +} + +static int ak4641_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); + struct ak4641_platform_data *pdata = component->dev->platform_data; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + /* unmute */ + snd_soc_component_update_bits(component, AK4641_DAC, 0x20, 0); + break; + case SND_SOC_BIAS_PREPARE: + /* mute */ + snd_soc_component_update_bits(component, AK4641_DAC, 0x20, 0x20); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + if (pdata && gpio_is_valid(pdata->gpio_power)) + gpio_set_value(pdata->gpio_power, 1); + mdelay(1); + if (pdata && gpio_is_valid(pdata->gpio_npdn)) + gpio_set_value(pdata->gpio_npdn, 1); + mdelay(1); + + ret = regcache_sync(ak4641->regmap); + if (ret) { + dev_err(component->dev, + "Failed to sync cache: %d\n", ret); + return ret; + } + } + snd_soc_component_update_bits(component, AK4641_PM1, 0x80, 0x80); + snd_soc_component_update_bits(component, AK4641_PM2, 0x80, 0); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, AK4641_PM1, 0x80, 0); + if (pdata && gpio_is_valid(pdata->gpio_npdn)) + gpio_set_value(pdata->gpio_npdn, 0); + if (pdata && gpio_is_valid(pdata->gpio_power)) + gpio_set_value(pdata->gpio_power, 0); + regcache_mark_dirty(ak4641->regmap); + break; + } + return 0; +} + +#define AK4641_RATES (SNDRV_PCM_RATE_8000_48000) +#define AK4641_RATES_BT (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000) +#define AK4641_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +static const struct snd_soc_dai_ops ak4641_i2s_dai_ops = { + .hw_params = ak4641_i2s_hw_params, + .set_fmt = ak4641_i2s_set_dai_fmt, + .digital_mute = ak4641_mute, + .set_sysclk = ak4641_set_dai_sysclk, +}; + +static const struct snd_soc_dai_ops ak4641_pcm_dai_ops = { + .hw_params = NULL, /* rates are controlled by BT chip */ + .set_fmt = ak4641_pcm_set_dai_fmt, + .digital_mute = ak4641_mute, + .set_sysclk = ak4641_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver ak4641_dai[] = { +{ + .name = "ak4641-hifi", + .id = 1, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4641_RATES, + .formats = AK4641_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4641_RATES, + .formats = AK4641_FORMATS, + }, + .ops = &ak4641_i2s_dai_ops, + .symmetric_rates = 1, +}, +{ + .name = "ak4641-voice", + .id = 1, + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = AK4641_RATES_BT, + .formats = AK4641_FORMATS, + }, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 1, + .rates = AK4641_RATES_BT, + .formats = AK4641_FORMATS, + }, + .ops = &ak4641_pcm_dai_ops, + .symmetric_rates = 1, +}, +}; + +static const struct snd_soc_component_driver soc_component_dev_ak4641 = { + .controls = ak4641_snd_controls, + .num_controls = ARRAY_SIZE(ak4641_snd_controls), + .dapm_widgets = ak4641_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4641_dapm_widgets), + .dapm_routes = ak4641_audio_map, + .num_dapm_routes = ARRAY_SIZE(ak4641_audio_map), + .set_bias_level = ak4641_set_bias_level, + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ak4641_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4641_BTIF, + .reg_defaults = ak4641_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ak4641_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int ak4641_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ak4641_platform_data *pdata = i2c->dev.platform_data; + struct ak4641_priv *ak4641; + int ret; + + ak4641 = devm_kzalloc(&i2c->dev, sizeof(struct ak4641_priv), + GFP_KERNEL); + if (!ak4641) + return -ENOMEM; + + ak4641->regmap = devm_regmap_init_i2c(i2c, &ak4641_regmap); + if (IS_ERR(ak4641->regmap)) + return PTR_ERR(ak4641->regmap); + + if (pdata) { + if (gpio_is_valid(pdata->gpio_power)) { + ret = gpio_request_one(pdata->gpio_power, + GPIOF_OUT_INIT_LOW, "ak4641 power"); + if (ret) + goto err_out; + } + if (gpio_is_valid(pdata->gpio_npdn)) { + ret = gpio_request_one(pdata->gpio_npdn, + GPIOF_OUT_INIT_LOW, "ak4641 npdn"); + if (ret) + goto err_gpio; + + udelay(1); /* > 150 ns */ + gpio_set_value(pdata->gpio_npdn, 1); + } + } + + i2c_set_clientdata(i2c, ak4641); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_ak4641, + ak4641_dai, ARRAY_SIZE(ak4641_dai)); + if (ret != 0) + goto err_gpio2; + + return 0; + +err_gpio2: + if (pdata) { + if (gpio_is_valid(pdata->gpio_power)) + gpio_set_value(pdata->gpio_power, 0); + if (gpio_is_valid(pdata->gpio_npdn)) + gpio_free(pdata->gpio_npdn); + } +err_gpio: + if (pdata && gpio_is_valid(pdata->gpio_power)) + gpio_free(pdata->gpio_power); +err_out: + return ret; +} + +static int ak4641_i2c_remove(struct i2c_client *i2c) +{ + struct ak4641_platform_data *pdata = i2c->dev.platform_data; + + if (pdata) { + if (gpio_is_valid(pdata->gpio_power)) { + gpio_set_value(pdata->gpio_power, 0); + gpio_free(pdata->gpio_power); + } + if (gpio_is_valid(pdata->gpio_npdn)) + gpio_free(pdata->gpio_npdn); + } + + return 0; +} + +static const struct i2c_device_id ak4641_i2c_id[] = { + { "ak4641", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4641_i2c_id); + +static struct i2c_driver ak4641_i2c_driver = { + .driver = { + .name = "ak4641", + }, + .probe = ak4641_i2c_probe, + .remove = ak4641_i2c_remove, + .id_table = ak4641_i2c_id, +}; + +module_i2c_driver(ak4641_i2c_driver); + +MODULE_DESCRIPTION("SoC AK4641 driver"); +MODULE_AUTHOR("Harald Welte "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c new file mode 100644 index 000000000..353237025 --- /dev/null +++ b/sound/soc/codecs/ak4642.c @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ak4642.c -- AK4642/AK4643 ALSA Soc Audio driver +// +// Copyright (C) 2009 Renesas Solutions Corp. +// Kuninori Morimoto +// +// Based on wm8731.c by Richard Purdie +// Based on ak4535.c by Richard Purdie +// Based on wm8753.c by Liam Girdwood + +/* ** CAUTION ** + * + * This is very simple driver. + * It can use headphone output / stereo input only + * + * AK4642 is tested. + * AK4643 is tested. + * AK4648 is tested. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PW_MGMT1 0x00 +#define PW_MGMT2 0x01 +#define SG_SL1 0x02 +#define SG_SL2 0x03 +#define MD_CTL1 0x04 +#define MD_CTL2 0x05 +#define TIMER 0x06 +#define ALC_CTL1 0x07 +#define ALC_CTL2 0x08 +#define L_IVC 0x09 +#define L_DVC 0x0a +#define ALC_CTL3 0x0b +#define R_IVC 0x0c +#define R_DVC 0x0d +#define MD_CTL3 0x0e +#define MD_CTL4 0x0f +#define PW_MGMT3 0x10 +#define DF_S 0x11 +#define FIL3_0 0x12 +#define FIL3_1 0x13 +#define FIL3_2 0x14 +#define FIL3_3 0x15 +#define EQ_0 0x16 +#define EQ_1 0x17 +#define EQ_2 0x18 +#define EQ_3 0x19 +#define EQ_4 0x1a +#define EQ_5 0x1b +#define FIL1_0 0x1c +#define FIL1_1 0x1d +#define FIL1_2 0x1e +#define FIL1_3 0x1f /* The maximum valid register for ak4642 */ +#define PW_MGMT4 0x20 +#define MD_CTL5 0x21 +#define LO_MS 0x22 +#define HP_MS 0x23 +#define SPK_MS 0x24 /* The maximum valid register for ak4643 */ +#define EQ_FBEQAB 0x25 +#define EQ_FBEQCD 0x26 +#define EQ_FBEQE 0x27 /* The maximum valid register for ak4648 */ + +/* PW_MGMT1*/ +#define PMVCM (1 << 6) /* VCOM Power Management */ +#define PMMIN (1 << 5) /* MIN Input Power Management */ +#define PMDAC (1 << 2) /* DAC Power Management */ +#define PMADL (1 << 0) /* MIC Amp Lch and ADC Lch Power Management */ + +/* PW_MGMT2 */ +#define HPMTN (1 << 6) +#define PMHPL (1 << 5) +#define PMHPR (1 << 4) +#define MS (1 << 3) /* master/slave select */ +#define MCKO (1 << 1) +#define PMPLL (1 << 0) + +#define PMHP_MASK (PMHPL | PMHPR) +#define PMHP PMHP_MASK + +/* PW_MGMT3 */ +#define PMADR (1 << 0) /* MIC L / ADC R Power Management */ + +/* SG_SL1 */ +#define MINS (1 << 6) /* Switch from MIN to Speaker */ +#define DACL (1 << 4) /* Switch from DAC to Stereo or Receiver */ +#define PMMP (1 << 2) /* MPWR pin Power Management */ +#define MGAIN0 (1 << 0) /* MIC amp gain*/ + +/* SG_SL2 */ +#define LOPS (1 << 6) /* Stero Line-out Power Save Mode */ + +/* TIMER */ +#define ZTM(param) ((param & 0x3) << 4) /* ALC Zero Crossing TimeOut */ +#define WTM(param) (((param & 0x4) << 4) | ((param & 0x3) << 2)) + +/* ALC_CTL1 */ +#define ALC (1 << 5) /* ALC Enable */ +#define LMTH0 (1 << 0) /* ALC Limiter / Recovery Level */ + +/* MD_CTL1 */ +#define PLL3 (1 << 7) +#define PLL2 (1 << 6) +#define PLL1 (1 << 5) +#define PLL0 (1 << 4) +#define PLL_MASK (PLL3 | PLL2 | PLL1 | PLL0) + +#define BCKO_MASK (1 << 3) +#define BCKO_64 BCKO_MASK + +#define DIF_MASK (3 << 0) +#define DSP (0 << 0) +#define RIGHT_J (1 << 0) +#define LEFT_J (2 << 0) +#define I2S (3 << 0) + +/* MD_CTL2 */ +#define FSs(val) (((val & 0x7) << 0) | ((val & 0x8) << 2)) +#define PSs(val) ((val & 0x3) << 6) + +/* MD_CTL3 */ +#define BST1 (1 << 3) + +/* MD_CTL4 */ +#define DACH (1 << 0) + +struct ak4642_drvdata { + const struct regmap_config *regmap_config; + int extended_frequencies; +}; + +struct ak4642_priv { + const struct ak4642_drvdata *drvdata; + struct clk *mcko; +}; + +/* + * Playback Volume (table 39) + * + * max : 0x00 : +12.0 dB + * ( 0.5 dB step ) + * min : 0xFE : -115.0 dB + * mute: 0xFF + */ +static const DECLARE_TLV_DB_SCALE(out_tlv, -11550, 50, 1); + +static const struct snd_kcontrol_new ak4642_snd_controls[] = { + + SOC_DOUBLE_R_TLV("Digital Playback Volume", L_DVC, R_DVC, + 0, 0xFF, 1, out_tlv), + SOC_SINGLE("ALC Capture Switch", ALC_CTL1, 5, 1, 0), + SOC_SINGLE("ALC Capture ZC Switch", ALC_CTL1, 4, 1, 1), +}; + +static const struct snd_kcontrol_new ak4642_headphone_control = + SOC_DAPM_SINGLE("Switch", PW_MGMT2, 6, 1, 0); + +static const struct snd_kcontrol_new ak4642_lout_mixer_controls[] = { + SOC_DAPM_SINGLE("DACL", SG_SL1, 4, 1, 0), +}; + +/* event handlers */ +static int ak4642_lout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + case SND_SOC_DAPM_PRE_PMU: + /* Power save mode ON */ + snd_soc_component_update_bits(component, SG_SL2, LOPS, LOPS); + break; + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + /* Power save mode OFF */ + msleep(300); + snd_soc_component_update_bits(component, SG_SL2, LOPS, 0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget ak4642_dapm_widgets[] = { + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("LINEOUT"), + + SND_SOC_DAPM_PGA("HPL Out", PW_MGMT2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPR Out", PW_MGMT2, 4, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("Headphone Enable", SND_SOC_NOPM, 0, 0, + &ak4642_headphone_control), + + SND_SOC_DAPM_PGA("DACH", MD_CTL4, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER_E("LINEOUT Mixer", PW_MGMT1, 3, 0, + &ak4642_lout_mixer_controls[0], + ARRAY_SIZE(ak4642_lout_mixer_controls), + ak4642_lout_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + + /* DAC */ + SND_SOC_DAPM_DAC("DAC", NULL, PW_MGMT1, 2, 0), +}; + +static const struct snd_soc_dapm_route ak4642_intercon[] = { + + /* Outputs */ + {"HPOUTL", NULL, "HPL Out"}, + {"HPOUTR", NULL, "HPR Out"}, + {"LINEOUT", NULL, "LINEOUT Mixer"}, + + {"HPL Out", NULL, "Headphone Enable"}, + {"HPR Out", NULL, "Headphone Enable"}, + + {"Headphone Enable", "Switch", "DACH"}, + + {"DACH", NULL, "DAC"}, + + {"LINEOUT Mixer", "DACL", "DAC"}, + + { "DAC", NULL, "Playback" }, +}; + +/* + * ak4642 register cache + */ +static const struct reg_default ak4643_reg[] = { + { 0, 0x00 }, { 1, 0x00 }, { 2, 0x01 }, { 3, 0x00 }, + { 4, 0x02 }, { 5, 0x00 }, { 6, 0x00 }, { 7, 0x00 }, + { 8, 0xe1 }, { 9, 0xe1 }, { 10, 0x18 }, { 11, 0x00 }, + { 12, 0xe1 }, { 13, 0x18 }, { 14, 0x11 }, { 15, 0x08 }, + { 16, 0x00 }, { 17, 0x00 }, { 18, 0x00 }, { 19, 0x00 }, + { 20, 0x00 }, { 21, 0x00 }, { 22, 0x00 }, { 23, 0x00 }, + { 24, 0x00 }, { 25, 0x00 }, { 26, 0x00 }, { 27, 0x00 }, + { 28, 0x00 }, { 29, 0x00 }, { 30, 0x00 }, { 31, 0x00 }, + { 32, 0x00 }, { 33, 0x00 }, { 34, 0x00 }, { 35, 0x00 }, + { 36, 0x00 }, +}; + +/* The default settings for 0x0 ~ 0x1f registers are the same for ak4642 + and ak4643. So we reuse the ak4643 reg_default for ak4642. + The valid registers for ak4642 are 0x0 ~ 0x1f which is a subset of ak4643, + so define NUM_AK4642_REG_DEFAULTS for ak4642. +*/ +#define ak4642_reg ak4643_reg +#define NUM_AK4642_REG_DEFAULTS (FIL1_3 + 1) + +static const struct reg_default ak4648_reg[] = { + { 0, 0x00 }, { 1, 0x00 }, { 2, 0x01 }, { 3, 0x00 }, + { 4, 0x02 }, { 5, 0x00 }, { 6, 0x00 }, { 7, 0x00 }, + { 8, 0xe1 }, { 9, 0xe1 }, { 10, 0x18 }, { 11, 0x00 }, + { 12, 0xe1 }, { 13, 0x18 }, { 14, 0x11 }, { 15, 0xb8 }, + { 16, 0x00 }, { 17, 0x00 }, { 18, 0x00 }, { 19, 0x00 }, + { 20, 0x00 }, { 21, 0x00 }, { 22, 0x00 }, { 23, 0x00 }, + { 24, 0x00 }, { 25, 0x00 }, { 26, 0x00 }, { 27, 0x00 }, + { 28, 0x00 }, { 29, 0x00 }, { 30, 0x00 }, { 31, 0x00 }, + { 32, 0x00 }, { 33, 0x00 }, { 34, 0x00 }, { 35, 0x00 }, + { 36, 0x00 }, { 37, 0x88 }, { 38, 0x88 }, { 39, 0x08 }, +}; + +static int ak4642_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_component *component = dai->component; + + if (is_play) { + /* + * start headphone output + * + * PLL, Master Mode + * Audio I/F Format :MSB justified (ADC & DAC) + * Bass Boost Level : Middle + * + * This operation came from example code of + * "ASAHI KASEI AK4642" (japanese) manual p97. + */ + snd_soc_component_write(component, L_IVC, 0x91); /* volume */ + snd_soc_component_write(component, R_IVC, 0x91); /* volume */ + } else { + /* + * start stereo input + * + * PLL Master Mode + * Audio I/F Format:MSB justified (ADC & DAC) + * Pre MIC AMP:+20dB + * MIC Power On + * ALC setting:Refer to Table 35 + * ALC bit=“1” + * + * This operation came from example code of + * "ASAHI KASEI AK4642" (japanese) manual p94. + */ + snd_soc_component_update_bits(component, SG_SL1, PMMP | MGAIN0, PMMP | MGAIN0); + snd_soc_component_write(component, TIMER, ZTM(0x3) | WTM(0x3)); + snd_soc_component_write(component, ALC_CTL1, ALC | LMTH0); + snd_soc_component_update_bits(component, PW_MGMT1, PMADL, PMADL); + snd_soc_component_update_bits(component, PW_MGMT3, PMADR, PMADR); + } + + return 0; +} + +static void ak4642_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_component *component = dai->component; + + if (is_play) { + } else { + /* stop stereo input */ + snd_soc_component_update_bits(component, PW_MGMT1, PMADL, 0); + snd_soc_component_update_bits(component, PW_MGMT3, PMADR, 0); + snd_soc_component_update_bits(component, ALC_CTL1, ALC, 0); + } +} + +static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct ak4642_priv *priv = snd_soc_component_get_drvdata(component); + u8 pll; + int extended_freq = 0; + + switch (freq) { + case 11289600: + pll = PLL2; + break; + case 12288000: + pll = PLL2 | PLL0; + break; + case 12000000: + pll = PLL2 | PLL1; + break; + case 24000000: + pll = PLL2 | PLL1 | PLL0; + break; + case 13500000: + pll = PLL3 | PLL2; + break; + case 27000000: + pll = PLL3 | PLL2 | PLL0; + break; + case 19200000: + pll = PLL3; + extended_freq = 1; + break; + case 13000000: + pll = PLL3 | PLL2 | PLL1; + extended_freq = 1; + break; + case 26000000: + pll = PLL3 | PLL2 | PLL1 | PLL0; + extended_freq = 1; + break; + default: + return -EINVAL; + } + + if (extended_freq && !priv->drvdata->extended_frequencies) + return -EINVAL; + + snd_soc_component_update_bits(component, MD_CTL1, PLL_MASK, pll); + + return 0; +} + +static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u8 data; + u8 bcko; + + data = MCKO | PMPLL; /* use MCKO */ + bcko = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + data |= MS; + bcko = BCKO_64; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, PW_MGMT2, MS | MCKO | PMPLL, data); + snd_soc_component_update_bits(component, MD_CTL1, BCKO_MASK, bcko); + + /* format type */ + data = 0; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + data = LEFT_J; + break; + case SND_SOC_DAIFMT_I2S: + data = I2S; + break; + /* FIXME + * Please add RIGHT_J / DSP support here + */ + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, MD_CTL1, DIF_MASK, data); + + return 0; +} + +static int ak4642_set_mcko(struct snd_soc_component *component, + u32 frequency) +{ + static const u32 fs_list[] = { + [0] = 8000, + [1] = 12000, + [2] = 16000, + [3] = 24000, + [4] = 7350, + [5] = 11025, + [6] = 14700, + [7] = 22050, + [10] = 32000, + [11] = 48000, + [14] = 29400, + [15] = 44100, + }; + static const u32 ps_list[] = { + [0] = 256, + [1] = 128, + [2] = 64, + [3] = 32 + }; + int ps, fs; + + for (ps = 0; ps < ARRAY_SIZE(ps_list); ps++) { + for (fs = 0; fs < ARRAY_SIZE(fs_list); fs++) { + if (frequency == ps_list[ps] * fs_list[fs]) { + snd_soc_component_write(component, MD_CTL2, + PSs(ps) | FSs(fs)); + return 0; + } + } + } + + return 0; +} + +static int ak4642_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak4642_priv *priv = snd_soc_component_get_drvdata(component); + u32 rate = clk_get_rate(priv->mcko); + + if (!rate) + rate = params_rate(params) * 256; + + return ak4642_set_mcko(component, rate); +} + +static int ak4642_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, PW_MGMT1, 0x00); + break; + default: + snd_soc_component_update_bits(component, PW_MGMT1, PMVCM, PMVCM); + break; + } + + return 0; +} + +static const struct snd_soc_dai_ops ak4642_dai_ops = { + .startup = ak4642_dai_startup, + .shutdown = ak4642_dai_shutdown, + .set_sysclk = ak4642_dai_set_sysclk, + .set_fmt = ak4642_dai_set_fmt, + .hw_params = ak4642_dai_hw_params, +}; + +static struct snd_soc_dai_driver ak4642_dai = { + .name = "ak4642-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE }, + .ops = &ak4642_dai_ops, + .symmetric_rates = 1, +}; + +static int ak4642_suspend(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + return 0; +} + +static int ak4642_resume(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, false); + regcache_sync(regmap); + return 0; +} +static int ak4642_probe(struct snd_soc_component *component) +{ + struct ak4642_priv *priv = snd_soc_component_get_drvdata(component); + + if (priv->mcko) + ak4642_set_mcko(component, clk_get_rate(priv->mcko)); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_ak4642 = { + .probe = ak4642_probe, + .suspend = ak4642_suspend, + .resume = ak4642_resume, + .set_bias_level = ak4642_set_bias_level, + .controls = ak4642_snd_controls, + .num_controls = ARRAY_SIZE(ak4642_snd_controls), + .dapm_widgets = ak4642_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4642_dapm_widgets), + .dapm_routes = ak4642_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4642_intercon), + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ak4642_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = FIL1_3, + .reg_defaults = ak4642_reg, + .num_reg_defaults = NUM_AK4642_REG_DEFAULTS, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config ak4643_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SPK_MS, + .reg_defaults = ak4643_reg, + .num_reg_defaults = ARRAY_SIZE(ak4643_reg), + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config ak4648_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = EQ_FBEQE, + .reg_defaults = ak4648_reg, + .num_reg_defaults = ARRAY_SIZE(ak4648_reg), + .cache_type = REGCACHE_RBTREE, +}; + +static const struct ak4642_drvdata ak4642_drvdata = { + .regmap_config = &ak4642_regmap, +}; + +static const struct ak4642_drvdata ak4643_drvdata = { + .regmap_config = &ak4643_regmap, +}; + +static const struct ak4642_drvdata ak4648_drvdata = { + .regmap_config = &ak4648_regmap, + .extended_frequencies = 1, +}; + +#ifdef CONFIG_COMMON_CLK +static struct clk *ak4642_of_parse_mcko(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct clk *clk; + const char *clk_name = np->name; + const char *parent_clk_name = NULL; + u32 rate; + + if (of_property_read_u32(np, "clock-frequency", &rate)) + return NULL; + + if (of_property_read_bool(np, "clocks")) + parent_clk_name = of_clk_get_parent_name(np, 0); + + of_property_read_string(np, "clock-output-names", &clk_name); + + clk = clk_register_fixed_rate(dev, clk_name, parent_clk_name, 0, rate); + if (!IS_ERR(clk)) + of_clk_add_provider(np, of_clk_src_simple_get, clk); + + return clk; +} +#else +#define ak4642_of_parse_mcko(d) 0 +#endif + +static const struct of_device_id ak4642_of_match[]; +static int ak4642_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct device_node *np = dev->of_node; + const struct ak4642_drvdata *drvdata = NULL; + struct regmap *regmap; + struct ak4642_priv *priv; + struct clk *mcko = NULL; + + if (np) { + const struct of_device_id *of_id; + + mcko = ak4642_of_parse_mcko(dev); + if (IS_ERR(mcko)) + mcko = NULL; + + of_id = of_match_device(ak4642_of_match, dev); + if (of_id) + drvdata = of_id->data; + } else { + drvdata = (const struct ak4642_drvdata *)id->driver_data; + } + + if (!drvdata) { + dev_err(dev, "Unknown device type\n"); + return -EINVAL; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->drvdata = drvdata; + priv->mcko = mcko; + + i2c_set_clientdata(i2c, priv); + + regmap = devm_regmap_init_i2c(i2c, drvdata->regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return devm_snd_soc_register_component(dev, + &soc_component_dev_ak4642, &ak4642_dai, 1); +} + +static const struct of_device_id ak4642_of_match[] = { + { .compatible = "asahi-kasei,ak4642", .data = &ak4642_drvdata}, + { .compatible = "asahi-kasei,ak4643", .data = &ak4643_drvdata}, + { .compatible = "asahi-kasei,ak4648", .data = &ak4648_drvdata}, + {}, +}; +MODULE_DEVICE_TABLE(of, ak4642_of_match); + +static const struct i2c_device_id ak4642_i2c_id[] = { + { "ak4642", (kernel_ulong_t)&ak4642_drvdata }, + { "ak4643", (kernel_ulong_t)&ak4643_drvdata }, + { "ak4648", (kernel_ulong_t)&ak4648_drvdata }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4642_i2c_id); + +static struct i2c_driver ak4642_i2c_driver = { + .driver = { + .name = "ak4642-codec", + .of_match_table = ak4642_of_match, + }, + .probe = ak4642_i2c_probe, + .id_table = ak4642_i2c_id, +}; + +module_i2c_driver(ak4642_i2c_driver); + +MODULE_DESCRIPTION("Soc AK4642 driver"); +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c new file mode 100644 index 000000000..7133fd69c --- /dev/null +++ b/sound/soc/codecs/ak4671.c @@ -0,0 +1,673 @@ +/* + * ak4671.c -- audio driver for AK4671 + * + * Copyright (C) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ak4671.h" + + +/* ak4671 register cache & default register settings */ +static const struct reg_default ak4671_reg_defaults[] = { + { 0x00, 0x00 }, /* AK4671_AD_DA_POWER_MANAGEMENT (0x00) */ + { 0x01, 0xf6 }, /* AK4671_PLL_MODE_SELECT0 (0x01) */ + { 0x02, 0x00 }, /* AK4671_PLL_MODE_SELECT1 (0x02) */ + { 0x03, 0x02 }, /* AK4671_FORMAT_SELECT (0x03) */ + { 0x04, 0x00 }, /* AK4671_MIC_SIGNAL_SELECT (0x04) */ + { 0x05, 0x55 }, /* AK4671_MIC_AMP_GAIN (0x05) */ + { 0x06, 0x00 }, /* AK4671_MIXING_POWER_MANAGEMENT0 (0x06) */ + { 0x07, 0x00 }, /* AK4671_MIXING_POWER_MANAGEMENT1 (0x07) */ + { 0x08, 0xb5 }, /* AK4671_OUTPUT_VOLUME_CONTROL (0x08) */ + { 0x09, 0x00 }, /* AK4671_LOUT1_SIGNAL_SELECT (0x09) */ + { 0x0a, 0x00 }, /* AK4671_ROUT1_SIGNAL_SELECT (0x0a) */ + { 0x0b, 0x00 }, /* AK4671_LOUT2_SIGNAL_SELECT (0x0b) */ + { 0x0c, 0x00 }, /* AK4671_ROUT2_SIGNAL_SELECT (0x0c) */ + { 0x0d, 0x00 }, /* AK4671_LOUT3_SIGNAL_SELECT (0x0d) */ + { 0x0e, 0x00 }, /* AK4671_ROUT3_SIGNAL_SELECT (0x0e) */ + { 0x0f, 0x00 }, /* AK4671_LOUT1_POWER_MANAGERMENT (0x0f) */ + { 0x10, 0x00 }, /* AK4671_LOUT2_POWER_MANAGERMENT (0x10) */ + { 0x11, 0x80 }, /* AK4671_LOUT3_POWER_MANAGERMENT (0x11) */ + { 0x12, 0x91 }, /* AK4671_LCH_INPUT_VOLUME_CONTROL (0x12) */ + { 0x13, 0x91 }, /* AK4671_RCH_INPUT_VOLUME_CONTROL (0x13) */ + { 0x14, 0xe1 }, /* AK4671_ALC_REFERENCE_SELECT (0x14) */ + { 0x15, 0x00 }, /* AK4671_DIGITAL_MIXING_CONTROL (0x15) */ + { 0x16, 0x00 }, /* AK4671_ALC_TIMER_SELECT (0x16) */ + { 0x17, 0x00 }, /* AK4671_ALC_MODE_CONTROL (0x17) */ + { 0x18, 0x02 }, /* AK4671_MODE_CONTROL1 (0x18) */ + { 0x19, 0x01 }, /* AK4671_MODE_CONTROL2 (0x19) */ + { 0x1a, 0x18 }, /* AK4671_LCH_OUTPUT_VOLUME_CONTROL (0x1a) */ + { 0x1b, 0x18 }, /* AK4671_RCH_OUTPUT_VOLUME_CONTROL (0x1b) */ + { 0x1c, 0x00 }, /* AK4671_SIDETONE_A_CONTROL (0x1c) */ + { 0x1d, 0x02 }, /* AK4671_DIGITAL_FILTER_SELECT (0x1d) */ + { 0x1e, 0x00 }, /* AK4671_FIL3_COEFFICIENT0 (0x1e) */ + { 0x1f, 0x00 }, /* AK4671_FIL3_COEFFICIENT1 (0x1f) */ + { 0x20, 0x00 }, /* AK4671_FIL3_COEFFICIENT2 (0x20) */ + { 0x21, 0x00 }, /* AK4671_FIL3_COEFFICIENT3 (0x21) */ + { 0x22, 0x00 }, /* AK4671_EQ_COEFFICIENT0 (0x22) */ + { 0x23, 0x00 }, /* AK4671_EQ_COEFFICIENT1 (0x23) */ + { 0x24, 0x00 }, /* AK4671_EQ_COEFFICIENT2 (0x24) */ + { 0x25, 0x00 }, /* AK4671_EQ_COEFFICIENT3 (0x25) */ + { 0x26, 0x00 }, /* AK4671_EQ_COEFFICIENT4 (0x26) */ + { 0x27, 0x00 }, /* AK4671_EQ_COEFFICIENT5 (0x27) */ + { 0x28, 0xa9 }, /* AK4671_FIL1_COEFFICIENT0 (0x28) */ + { 0x29, 0x1f }, /* AK4671_FIL1_COEFFICIENT1 (0x29) */ + { 0x2a, 0xad }, /* AK4671_FIL1_COEFFICIENT2 (0x2a) */ + { 0x2b, 0x20 }, /* AK4671_FIL1_COEFFICIENT3 (0x2b) */ + { 0x2c, 0x00 }, /* AK4671_FIL2_COEFFICIENT0 (0x2c) */ + { 0x2d, 0x00 }, /* AK4671_FIL2_COEFFICIENT1 (0x2d) */ + { 0x2e, 0x00 }, /* AK4671_FIL2_COEFFICIENT2 (0x2e) */ + { 0x2f, 0x00 }, /* AK4671_FIL2_COEFFICIENT3 (0x2f) */ + { 0x30, 0x00 }, /* AK4671_DIGITAL_FILTER_SELECT2 (0x30) */ + + { 0x32, 0x00 }, /* AK4671_E1_COEFFICIENT0 (0x32) */ + { 0x33, 0x00 }, /* AK4671_E1_COEFFICIENT1 (0x33) */ + { 0x34, 0x00 }, /* AK4671_E1_COEFFICIENT2 (0x34) */ + { 0x35, 0x00 }, /* AK4671_E1_COEFFICIENT3 (0x35) */ + { 0x36, 0x00 }, /* AK4671_E1_COEFFICIENT4 (0x36) */ + { 0x37, 0x00 }, /* AK4671_E1_COEFFICIENT5 (0x37) */ + { 0x38, 0x00 }, /* AK4671_E2_COEFFICIENT0 (0x38) */ + { 0x39, 0x00 }, /* AK4671_E2_COEFFICIENT1 (0x39) */ + { 0x3a, 0x00 }, /* AK4671_E2_COEFFICIENT2 (0x3a) */ + { 0x3b, 0x00 }, /* AK4671_E2_COEFFICIENT3 (0x3b) */ + { 0x3c, 0x00 }, /* AK4671_E2_COEFFICIENT4 (0x3c) */ + { 0x3d, 0x00 }, /* AK4671_E2_COEFFICIENT5 (0x3d) */ + { 0x3e, 0x00 }, /* AK4671_E3_COEFFICIENT0 (0x3e) */ + { 0x3f, 0x00 }, /* AK4671_E3_COEFFICIENT1 (0x3f) */ + { 0x40, 0x00 }, /* AK4671_E3_COEFFICIENT2 (0x40) */ + { 0x41, 0x00 }, /* AK4671_E3_COEFFICIENT3 (0x41) */ + { 0x42, 0x00 }, /* AK4671_E3_COEFFICIENT4 (0x42) */ + { 0x43, 0x00 }, /* AK4671_E3_COEFFICIENT5 (0x43) */ + { 0x44, 0x00 }, /* AK4671_E4_COEFFICIENT0 (0x44) */ + { 0x45, 0x00 }, /* AK4671_E4_COEFFICIENT1 (0x45) */ + { 0x46, 0x00 }, /* AK4671_E4_COEFFICIENT2 (0x46) */ + { 0x47, 0x00 }, /* AK4671_E4_COEFFICIENT3 (0x47) */ + { 0x48, 0x00 }, /* AK4671_E4_COEFFICIENT4 (0x48) */ + { 0x49, 0x00 }, /* AK4671_E4_COEFFICIENT5 (0x49) */ + { 0x4a, 0x00 }, /* AK4671_E5_COEFFICIENT0 (0x4a) */ + { 0x4b, 0x00 }, /* AK4671_E5_COEFFICIENT1 (0x4b) */ + { 0x4c, 0x00 }, /* AK4671_E5_COEFFICIENT2 (0x4c) */ + { 0x4d, 0x00 }, /* AK4671_E5_COEFFICIENT3 (0x4d) */ + { 0x4e, 0x00 }, /* AK4671_E5_COEFFICIENT4 (0x4e) */ + { 0x4f, 0x00 }, /* AK4671_E5_COEFFICIENT5 (0x4f) */ + { 0x50, 0x88 }, /* AK4671_EQ_CONTROL_250HZ_100HZ (0x50) */ + { 0x51, 0x88 }, /* AK4671_EQ_CONTROL_3500HZ_1KHZ (0x51) */ + { 0x52, 0x08 }, /* AK4671_EQ_CONTRO_10KHZ (0x52) */ + { 0x53, 0x00 }, /* AK4671_PCM_IF_CONTROL0 (0x53) */ + { 0x54, 0x00 }, /* AK4671_PCM_IF_CONTROL1 (0x54) */ + { 0x55, 0x00 }, /* AK4671_PCM_IF_CONTROL2 (0x55) */ + { 0x56, 0x18 }, /* AK4671_DIGITAL_VOLUME_B_CONTROL (0x56) */ + { 0x57, 0x18 }, /* AK4671_DIGITAL_VOLUME_C_CONTROL (0x57) */ + { 0x58, 0x00 }, /* AK4671_SIDETONE_VOLUME_CONTROL (0x58) */ + { 0x59, 0x00 }, /* AK4671_DIGITAL_MIXING_CONTROL2 (0x59) */ + { 0x5a, 0x00 }, /* AK4671_SAR_ADC_CONTROL (0x5a) */ +}; + +/* + * LOUT1/ROUT1 output volume control: + * from -24 to 6 dB in 6 dB steps (mute instead of -30 dB) + */ +static DECLARE_TLV_DB_SCALE(out1_tlv, -3000, 600, 1); + +/* + * LOUT2/ROUT2 output volume control: + * from -33 to 6 dB in 3 dB steps (mute instead of -33 dB) + */ +static DECLARE_TLV_DB_SCALE(out2_tlv, -3300, 300, 1); + +/* + * LOUT3/ROUT3 output volume control: + * from -6 to 3 dB in 3 dB steps + */ +static DECLARE_TLV_DB_SCALE(out3_tlv, -600, 300, 0); + +/* + * Mic amp gain control: + * from -15 to 30 dB in 3 dB steps + * REVISIT: The actual min value(0x01) is -12 dB and the reg value 0x00 is not + * available + */ +static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -1500, 300, 0); + +static const struct snd_kcontrol_new ak4671_snd_controls[] = { + /* Common playback gain controls */ + SOC_SINGLE_TLV("Line Output1 Playback Volume", + AK4671_OUTPUT_VOLUME_CONTROL, 0, 0x6, 0, out1_tlv), + SOC_SINGLE_TLV("Headphone Output2 Playback Volume", + AK4671_OUTPUT_VOLUME_CONTROL, 4, 0xd, 0, out2_tlv), + SOC_SINGLE_TLV("Line Output3 Playback Volume", + AK4671_LOUT3_POWER_MANAGERMENT, 6, 0x3, 0, out3_tlv), + + /* Common capture gain controls */ + SOC_DOUBLE_TLV("Mic Amp Capture Volume", + AK4671_MIC_AMP_GAIN, 0, 4, 0xf, 0, mic_amp_tlv), +}; + +/* event handlers */ +static int ak4671_out2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, AK4671_LOUT2_POWER_MANAGERMENT, + AK4671_MUTEN, AK4671_MUTEN); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, AK4671_LOUT2_POWER_MANAGERMENT, + AK4671_MUTEN, 0); + break; + } + + return 0; +} + +/* Output Mixers */ +static const struct snd_kcontrol_new ak4671_lout1_mixer_controls[] = { + SOC_DAPM_SINGLE("DACL", AK4671_LOUT1_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("LINL1", AK4671_LOUT1_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("LINL2", AK4671_LOUT1_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("LINL3", AK4671_LOUT1_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("LINL4", AK4671_LOUT1_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPL", AK4671_LOUT1_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_rout1_mixer_controls[] = { + SOC_DAPM_SINGLE("DACR", AK4671_ROUT1_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("RINR1", AK4671_ROUT1_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("RINR2", AK4671_ROUT1_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("RINR3", AK4671_ROUT1_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("RINR4", AK4671_ROUT1_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPR", AK4671_ROUT1_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_lout2_mixer_controls[] = { + SOC_DAPM_SINGLE("DACHL", AK4671_LOUT2_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("LINH1", AK4671_LOUT2_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("LINH2", AK4671_LOUT2_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("LINH3", AK4671_LOUT2_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("LINH4", AK4671_LOUT2_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPHL", AK4671_LOUT2_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_rout2_mixer_controls[] = { + SOC_DAPM_SINGLE("DACHR", AK4671_ROUT2_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("RINH1", AK4671_ROUT2_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("RINH2", AK4671_ROUT2_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("RINH3", AK4671_ROUT2_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("RINH4", AK4671_ROUT2_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPHR", AK4671_ROUT2_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_lout3_mixer_controls[] = { + SOC_DAPM_SINGLE("DACSL", AK4671_LOUT3_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("LINS1", AK4671_LOUT3_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("LINS2", AK4671_LOUT3_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("LINS3", AK4671_LOUT3_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("LINS4", AK4671_LOUT3_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPSL", AK4671_LOUT3_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_rout3_mixer_controls[] = { + SOC_DAPM_SINGLE("DACSR", AK4671_ROUT3_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("RINS1", AK4671_ROUT3_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("RINS2", AK4671_ROUT3_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("RINS3", AK4671_ROUT3_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("RINS4", AK4671_ROUT3_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPSR", AK4671_ROUT3_SIGNAL_SELECT, 5, 1, 0), +}; + +/* Input MUXs */ +static const char *ak4671_lin_mux_texts[] = + {"LIN1", "LIN2", "LIN3", "LIN4"}; +static SOC_ENUM_SINGLE_DECL(ak4671_lin_mux_enum, + AK4671_MIC_SIGNAL_SELECT, 0, + ak4671_lin_mux_texts); +static const struct snd_kcontrol_new ak4671_lin_mux_control = + SOC_DAPM_ENUM("Route", ak4671_lin_mux_enum); + +static const char *ak4671_rin_mux_texts[] = + {"RIN1", "RIN2", "RIN3", "RIN4"}; +static SOC_ENUM_SINGLE_DECL(ak4671_rin_mux_enum, + AK4671_MIC_SIGNAL_SELECT, 2, + ak4671_rin_mux_texts); +static const struct snd_kcontrol_new ak4671_rin_mux_control = + SOC_DAPM_ENUM("Route", ak4671_rin_mux_enum); + +static const struct snd_soc_dapm_widget ak4671_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("LIN1"), + SND_SOC_DAPM_INPUT("RIN1"), + SND_SOC_DAPM_INPUT("LIN2"), + SND_SOC_DAPM_INPUT("RIN2"), + SND_SOC_DAPM_INPUT("LIN3"), + SND_SOC_DAPM_INPUT("RIN3"), + SND_SOC_DAPM_INPUT("LIN4"), + SND_SOC_DAPM_INPUT("RIN4"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("LOUT3"), + SND_SOC_DAPM_OUTPUT("ROUT3"), + + /* DAC */ + SND_SOC_DAPM_DAC("DAC Left", "Left HiFi Playback", + AK4671_AD_DA_POWER_MANAGEMENT, 6, 0), + SND_SOC_DAPM_DAC("DAC Right", "Right HiFi Playback", + AK4671_AD_DA_POWER_MANAGEMENT, 7, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC Left", "Left HiFi Capture", + AK4671_AD_DA_POWER_MANAGEMENT, 4, 0), + SND_SOC_DAPM_ADC("ADC Right", "Right HiFi Capture", + AK4671_AD_DA_POWER_MANAGEMENT, 5, 0), + + /* PGA */ + SND_SOC_DAPM_PGA("LOUT2 Mix Amp", + AK4671_LOUT2_POWER_MANAGERMENT, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("ROUT2 Mix Amp", + AK4671_LOUT2_POWER_MANAGERMENT, 6, 0, NULL, 0), + + SND_SOC_DAPM_PGA("LIN1 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN1 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("LIN2 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN2 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("LIN3 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN3 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("LIN4 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN4 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 7, 0, NULL, 0), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("LOUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 0, 0, + &ak4671_lout1_mixer_controls[0], + ARRAY_SIZE(ak4671_lout1_mixer_controls)), + SND_SOC_DAPM_MIXER("ROUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 1, 0, + &ak4671_rout1_mixer_controls[0], + ARRAY_SIZE(ak4671_rout1_mixer_controls)), + SND_SOC_DAPM_MIXER_E("LOUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT, + 0, 0, &ak4671_lout2_mixer_controls[0], + ARRAY_SIZE(ak4671_lout2_mixer_controls), + ak4671_out2_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER_E("ROUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT, + 1, 0, &ak4671_rout2_mixer_controls[0], + ARRAY_SIZE(ak4671_rout2_mixer_controls), + ak4671_out2_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER("LOUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 0, 0, + &ak4671_lout3_mixer_controls[0], + ARRAY_SIZE(ak4671_lout3_mixer_controls)), + SND_SOC_DAPM_MIXER("ROUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 1, 0, + &ak4671_rout3_mixer_controls[0], + ARRAY_SIZE(ak4671_rout3_mixer_controls)), + + /* Input MUXs */ + SND_SOC_DAPM_MUX("LIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 2, 0, + &ak4671_lin_mux_control), + SND_SOC_DAPM_MUX("RIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 3, 0, + &ak4671_rin_mux_control), + + /* Mic Power */ + SND_SOC_DAPM_MICBIAS("Mic Bias", AK4671_AD_DA_POWER_MANAGEMENT, 1, 0), + + /* Supply */ + SND_SOC_DAPM_SUPPLY("PMPLL", AK4671_PLL_MODE_SELECT1, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route ak4671_intercon[] = { + {"DAC Left", NULL, "PMPLL"}, + {"DAC Right", NULL, "PMPLL"}, + {"ADC Left", NULL, "PMPLL"}, + {"ADC Right", NULL, "PMPLL"}, + + /* Outputs */ + {"LOUT1", NULL, "LOUT1 Mixer"}, + {"ROUT1", NULL, "ROUT1 Mixer"}, + {"LOUT2", NULL, "LOUT2 Mix Amp"}, + {"ROUT2", NULL, "ROUT2 Mix Amp"}, + {"LOUT3", NULL, "LOUT3 Mixer"}, + {"ROUT3", NULL, "ROUT3 Mixer"}, + + {"LOUT1 Mixer", "DACL", "DAC Left"}, + {"ROUT1 Mixer", "DACR", "DAC Right"}, + {"LOUT2 Mixer", "DACHL", "DAC Left"}, + {"ROUT2 Mixer", "DACHR", "DAC Right"}, + {"LOUT2 Mix Amp", NULL, "LOUT2 Mixer"}, + {"ROUT2 Mix Amp", NULL, "ROUT2 Mixer"}, + {"LOUT3 Mixer", "DACSL", "DAC Left"}, + {"ROUT3 Mixer", "DACSR", "DAC Right"}, + + /* Inputs */ + {"LIN MUX", "LIN1", "LIN1"}, + {"LIN MUX", "LIN2", "LIN2"}, + {"LIN MUX", "LIN3", "LIN3"}, + {"LIN MUX", "LIN4", "LIN4"}, + + {"RIN MUX", "RIN1", "RIN1"}, + {"RIN MUX", "RIN2", "RIN2"}, + {"RIN MUX", "RIN3", "RIN3"}, + {"RIN MUX", "RIN4", "RIN4"}, + + {"LIN1", NULL, "Mic Bias"}, + {"RIN1", NULL, "Mic Bias"}, + {"LIN2", NULL, "Mic Bias"}, + {"RIN2", NULL, "Mic Bias"}, + + {"ADC Left", NULL, "LIN MUX"}, + {"ADC Right", NULL, "RIN MUX"}, + + /* Analog Loops */ + {"LIN1 Mixing Circuit", NULL, "LIN1"}, + {"RIN1 Mixing Circuit", NULL, "RIN1"}, + {"LIN2 Mixing Circuit", NULL, "LIN2"}, + {"RIN2 Mixing Circuit", NULL, "RIN2"}, + {"LIN3 Mixing Circuit", NULL, "LIN3"}, + {"RIN3 Mixing Circuit", NULL, "RIN3"}, + {"LIN4 Mixing Circuit", NULL, "LIN4"}, + {"RIN4 Mixing Circuit", NULL, "RIN4"}, + + {"LOUT1 Mixer", "LINL1", "LIN1 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR1", "RIN1 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH1", "LIN1 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH1", "RIN1 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS1", "LIN1 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS1", "RIN1 Mixing Circuit"}, + + {"LOUT1 Mixer", "LINL2", "LIN2 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR2", "RIN2 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH2", "LIN2 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH2", "RIN2 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS2", "LIN2 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS2", "RIN2 Mixing Circuit"}, + + {"LOUT1 Mixer", "LINL3", "LIN3 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR3", "RIN3 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH3", "LIN3 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH3", "RIN3 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS3", "LIN3 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS3", "RIN3 Mixing Circuit"}, + + {"LOUT1 Mixer", "LINL4", "LIN4 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR4", "RIN4 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH4", "LIN4 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH4", "RIN4 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS4", "LIN4 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS4", "RIN4 Mixing Circuit"}, +}; + +static int ak4671_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u8 fs; + + fs = snd_soc_component_read32(component, AK4671_PLL_MODE_SELECT0); + fs &= ~AK4671_FS; + + switch (params_rate(params)) { + case 8000: + fs |= AK4671_FS_8KHZ; + break; + case 12000: + fs |= AK4671_FS_12KHZ; + break; + case 16000: + fs |= AK4671_FS_16KHZ; + break; + case 24000: + fs |= AK4671_FS_24KHZ; + break; + case 11025: + fs |= AK4671_FS_11_025KHZ; + break; + case 22050: + fs |= AK4671_FS_22_05KHZ; + break; + case 32000: + fs |= AK4671_FS_32KHZ; + break; + case 44100: + fs |= AK4671_FS_44_1KHZ; + break; + case 48000: + fs |= AK4671_FS_48KHZ; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, AK4671_PLL_MODE_SELECT0, fs); + + return 0; +} + +static int ak4671_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + u8 pll; + + pll = snd_soc_component_read32(component, AK4671_PLL_MODE_SELECT0); + pll &= ~AK4671_PLL; + + switch (freq) { + case 11289600: + pll |= AK4671_PLL_11_2896MHZ; + break; + case 12000000: + pll |= AK4671_PLL_12MHZ; + break; + case 12288000: + pll |= AK4671_PLL_12_288MHZ; + break; + case 13000000: + pll |= AK4671_PLL_13MHZ; + break; + case 13500000: + pll |= AK4671_PLL_13_5MHZ; + break; + case 19200000: + pll |= AK4671_PLL_19_2MHZ; + break; + case 24000000: + pll |= AK4671_PLL_24MHZ; + break; + case 26000000: + pll |= AK4671_PLL_26MHZ; + break; + case 27000000: + pll |= AK4671_PLL_27MHZ; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, AK4671_PLL_MODE_SELECT0, pll); + + return 0; +} + +static int ak4671_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u8 mode; + u8 format; + + /* set master/slave audio interface */ + mode = snd_soc_component_read32(component, AK4671_PLL_MODE_SELECT1); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + mode |= AK4671_M_S; + break; + case SND_SOC_DAIFMT_CBM_CFS: + mode &= ~(AK4671_M_S); + break; + default: + return -EINVAL; + } + + /* interface format */ + format = snd_soc_component_read32(component, AK4671_FORMAT_SELECT); + format &= ~AK4671_DIF; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format |= AK4671_DIF_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + format |= AK4671_DIF_MSB_MODE; + break; + case SND_SOC_DAIFMT_DSP_A: + format |= AK4671_DIF_DSP_MODE; + format |= AK4671_BCKP; + format |= AK4671_MSBS; + break; + default: + return -EINVAL; + } + + /* set mode and format */ + snd_soc_component_write(component, AK4671_PLL_MODE_SELECT1, mode); + snd_soc_component_write(component, AK4671_FORMAT_SELECT, format); + + return 0; +} + +static int ak4671_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + snd_soc_component_update_bits(component, AK4671_AD_DA_POWER_MANAGEMENT, + AK4671_PMVCM, AK4671_PMVCM); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, AK4671_AD_DA_POWER_MANAGEMENT, 0x00); + break; + } + return 0; +} + +#define AK4671_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) + +#define AK4671_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static const struct snd_soc_dai_ops ak4671_dai_ops = { + .hw_params = ak4671_hw_params, + .set_sysclk = ak4671_set_dai_sysclk, + .set_fmt = ak4671_set_dai_fmt, +}; + +static struct snd_soc_dai_driver ak4671_dai = { + .name = "ak4671-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4671_RATES, + .formats = AK4671_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4671_RATES, + .formats = AK4671_FORMATS,}, + .ops = &ak4671_dai_ops, +}; + +static const struct snd_soc_component_driver soc_component_dev_ak4671 = { + .set_bias_level = ak4671_set_bias_level, + .controls = ak4671_snd_controls, + .num_controls = ARRAY_SIZE(ak4671_snd_controls), + .dapm_widgets = ak4671_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak4671_dapm_widgets), + .dapm_routes = ak4671_intercon, + .num_dapm_routes = ARRAY_SIZE(ak4671_intercon), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ak4671_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK4671_SAR_ADC_CONTROL, + .reg_defaults = ak4671_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ak4671_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int ak4671_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &ak4671_regmap); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&client->dev, "Failed to create regmap: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_dev_ak4671, &ak4671_dai, 1); + return ret; +} + +static const struct i2c_device_id ak4671_i2c_id[] = { + { "ak4671", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4671_i2c_id); + +static struct i2c_driver ak4671_i2c_driver = { + .driver = { + .name = "ak4671-codec", + }, + .probe = ak4671_i2c_probe, + .id_table = ak4671_i2c_id, +}; + +module_i2c_driver(ak4671_i2c_driver); + +MODULE_DESCRIPTION("ASoC AK4671 codec driver"); +MODULE_AUTHOR("Joonyoung Shim "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4671.h b/sound/soc/codecs/ak4671.h new file mode 100644 index 000000000..394a34d3f --- /dev/null +++ b/sound/soc/codecs/ak4671.h @@ -0,0 +1,151 @@ +/* + * ak4671.h -- audio driver for AK4671 + * + * Copyright (C) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _AK4671_H +#define _AK4671_H + +#define AK4671_AD_DA_POWER_MANAGEMENT 0x00 +#define AK4671_PLL_MODE_SELECT0 0x01 +#define AK4671_PLL_MODE_SELECT1 0x02 +#define AK4671_FORMAT_SELECT 0x03 +#define AK4671_MIC_SIGNAL_SELECT 0x04 +#define AK4671_MIC_AMP_GAIN 0x05 +#define AK4671_MIXING_POWER_MANAGEMENT0 0x06 +#define AK4671_MIXING_POWER_MANAGEMENT1 0x07 +#define AK4671_OUTPUT_VOLUME_CONTROL 0x08 +#define AK4671_LOUT1_SIGNAL_SELECT 0x09 +#define AK4671_ROUT1_SIGNAL_SELECT 0x0a +#define AK4671_LOUT2_SIGNAL_SELECT 0x0b +#define AK4671_ROUT2_SIGNAL_SELECT 0x0c +#define AK4671_LOUT3_SIGNAL_SELECT 0x0d +#define AK4671_ROUT3_SIGNAL_SELECT 0x0e +#define AK4671_LOUT1_POWER_MANAGERMENT 0x0f +#define AK4671_LOUT2_POWER_MANAGERMENT 0x10 +#define AK4671_LOUT3_POWER_MANAGERMENT 0x11 +#define AK4671_LCH_INPUT_VOLUME_CONTROL 0x12 +#define AK4671_RCH_INPUT_VOLUME_CONTROL 0x13 +#define AK4671_ALC_REFERENCE_SELECT 0x14 +#define AK4671_DIGITAL_MIXING_CONTROL 0x15 +#define AK4671_ALC_TIMER_SELECT 0x16 +#define AK4671_ALC_MODE_CONTROL 0x17 +#define AK4671_MODE_CONTROL1 0x18 +#define AK4671_MODE_CONTROL2 0x19 +#define AK4671_LCH_OUTPUT_VOLUME_CONTROL 0x1a +#define AK4671_RCH_OUTPUT_VOLUME_CONTROL 0x1b +#define AK4671_SIDETONE_A_CONTROL 0x1c +#define AK4671_DIGITAL_FILTER_SELECT 0x1d +#define AK4671_FIL3_COEFFICIENT0 0x1e +#define AK4671_FIL3_COEFFICIENT1 0x1f +#define AK4671_FIL3_COEFFICIENT2 0x20 +#define AK4671_FIL3_COEFFICIENT3 0x21 +#define AK4671_EQ_COEFFICIENT0 0x22 +#define AK4671_EQ_COEFFICIENT1 0x23 +#define AK4671_EQ_COEFFICIENT2 0x24 +#define AK4671_EQ_COEFFICIENT3 0x25 +#define AK4671_EQ_COEFFICIENT4 0x26 +#define AK4671_EQ_COEFFICIENT5 0x27 +#define AK4671_FIL1_COEFFICIENT0 0x28 +#define AK4671_FIL1_COEFFICIENT1 0x29 +#define AK4671_FIL1_COEFFICIENT2 0x2a +#define AK4671_FIL1_COEFFICIENT3 0x2b +#define AK4671_FIL2_COEFFICIENT0 0x2c +#define AK4671_FIL2_COEFFICIENT1 0x2d +#define AK4671_FIL2_COEFFICIENT2 0x2e +#define AK4671_FIL2_COEFFICIENT3 0x2f +#define AK4671_DIGITAL_FILTER_SELECT2 0x30 +#define AK4671_E1_COEFFICIENT0 0x32 +#define AK4671_E1_COEFFICIENT1 0x33 +#define AK4671_E1_COEFFICIENT2 0x34 +#define AK4671_E1_COEFFICIENT3 0x35 +#define AK4671_E1_COEFFICIENT4 0x36 +#define AK4671_E1_COEFFICIENT5 0x37 +#define AK4671_E2_COEFFICIENT0 0x38 +#define AK4671_E2_COEFFICIENT1 0x39 +#define AK4671_E2_COEFFICIENT2 0x3a +#define AK4671_E2_COEFFICIENT3 0x3b +#define AK4671_E2_COEFFICIENT4 0x3c +#define AK4671_E2_COEFFICIENT5 0x3d +#define AK4671_E3_COEFFICIENT0 0x3e +#define AK4671_E3_COEFFICIENT1 0x3f +#define AK4671_E3_COEFFICIENT2 0x40 +#define AK4671_E3_COEFFICIENT3 0x41 +#define AK4671_E3_COEFFICIENT4 0x42 +#define AK4671_E3_COEFFICIENT5 0x43 +#define AK4671_E4_COEFFICIENT0 0x44 +#define AK4671_E4_COEFFICIENT1 0x45 +#define AK4671_E4_COEFFICIENT2 0x46 +#define AK4671_E4_COEFFICIENT3 0x47 +#define AK4671_E4_COEFFICIENT4 0x48 +#define AK4671_E4_COEFFICIENT5 0x49 +#define AK4671_E5_COEFFICIENT0 0x4a +#define AK4671_E5_COEFFICIENT1 0x4b +#define AK4671_E5_COEFFICIENT2 0x4c +#define AK4671_E5_COEFFICIENT3 0x4d +#define AK4671_E5_COEFFICIENT4 0x4e +#define AK4671_E5_COEFFICIENT5 0x4f +#define AK4671_EQ_CONTROL_250HZ_100HZ 0x50 +#define AK4671_EQ_CONTROL_3500HZ_1KHZ 0x51 +#define AK4671_EQ_CONTRO_10KHZ 0x52 +#define AK4671_PCM_IF_CONTROL0 0x53 +#define AK4671_PCM_IF_CONTROL1 0x54 +#define AK4671_PCM_IF_CONTROL2 0x55 +#define AK4671_DIGITAL_VOLUME_B_CONTROL 0x56 +#define AK4671_DIGITAL_VOLUME_C_CONTROL 0x57 +#define AK4671_SIDETONE_VOLUME_CONTROL 0x58 +#define AK4671_DIGITAL_MIXING_CONTROL2 0x59 +#define AK4671_SAR_ADC_CONTROL 0x5a + +/* Bitfield Definitions */ + +/* AK4671_AD_DA_POWER_MANAGEMENT (0x00) Fields */ +#define AK4671_PMVCM 0x01 + +/* AK4671_PLL_MODE_SELECT0 (0x01) Fields */ +#define AK4671_PLL 0x0f +#define AK4671_PLL_11_2896MHZ (4 << 0) +#define AK4671_PLL_12_288MHZ (5 << 0) +#define AK4671_PLL_12MHZ (6 << 0) +#define AK4671_PLL_24MHZ (7 << 0) +#define AK4671_PLL_19_2MHZ (8 << 0) +#define AK4671_PLL_13_5MHZ (12 << 0) +#define AK4671_PLL_27MHZ (13 << 0) +#define AK4671_PLL_13MHZ (14 << 0) +#define AK4671_PLL_26MHZ (15 << 0) +#define AK4671_FS 0xf0 +#define AK4671_FS_8KHZ (0 << 4) +#define AK4671_FS_12KHZ (1 << 4) +#define AK4671_FS_16KHZ (2 << 4) +#define AK4671_FS_24KHZ (3 << 4) +#define AK4671_FS_11_025KHZ (5 << 4) +#define AK4671_FS_22_05KHZ (7 << 4) +#define AK4671_FS_32KHZ (10 << 4) +#define AK4671_FS_48KHZ (11 << 4) +#define AK4671_FS_44_1KHZ (15 << 4) + +/* AK4671_PLL_MODE_SELECT1 (0x02) Fields */ +#define AK4671_PMPLL 0x01 +#define AK4671_M_S 0x02 + +/* AK4671_FORMAT_SELECT (0x03) Fields */ +#define AK4671_DIF 0x03 +#define AK4671_DIF_DSP_MODE (0 << 0) +#define AK4671_DIF_MSB_MODE (2 << 0) +#define AK4671_DIF_I2S_MODE (3 << 0) +#define AK4671_BCKP 0x04 +#define AK4671_MSBS 0x08 +#define AK4671_SDOD 0x10 + +/* AK4671_LOUT2_POWER_MANAGEMENT (0x10) Fields */ +#define AK4671_MUTEN 0x04 + +#endif diff --git a/sound/soc/codecs/ak5386.c b/sound/soc/codecs/ak5386.c new file mode 100644 index 000000000..d212960b4 --- /dev/null +++ b/sound/soc/codecs/ak5386.c @@ -0,0 +1,212 @@ +/* + * ALSA SoC driver for + * Asahi Kasei AK5386 Single-ended 24-Bit 192kHz delta-sigma ADC + * + * (c) 2013 Daniel Mack + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char * const supply_names[] = { + "va", "vd" +}; + +struct ak5386_priv { + int reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; +}; + +static const struct snd_soc_dapm_widget ak5386_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), +}; + +static const struct snd_soc_dapm_route ak5386_dapm_routes[] = { + { "Capture", NULL, "AINL" }, + { "Capture", NULL, "AINR" }, +}; + +static int ak5386_soc_probe(struct snd_soc_component *component) +{ + struct ak5386_priv *priv = snd_soc_component_get_drvdata(component); + return regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +} + +static void ak5386_soc_remove(struct snd_soc_component *component) +{ + struct ak5386_priv *priv = snd_soc_component_get_drvdata(component); + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); +} + +#ifdef CONFIG_PM +static int ak5386_soc_suspend(struct snd_soc_component *component) +{ + struct ak5386_priv *priv = snd_soc_component_get_drvdata(component); + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + return 0; +} + +static int ak5386_soc_resume(struct snd_soc_component *component) +{ + struct ak5386_priv *priv = snd_soc_component_get_drvdata(component); + return regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +} +#else +#define ak5386_soc_suspend NULL +#define ak5386_soc_resume NULL +#endif /* CONFIG_PM */ + +static const struct snd_soc_component_driver soc_component_ak5386 = { + .probe = ak5386_soc_probe, + .remove = ak5386_soc_remove, + .suspend = ak5386_soc_suspend, + .resume = ak5386_soc_resume, + .dapm_widgets = ak5386_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak5386_dapm_widgets), + .dapm_routes = ak5386_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ak5386_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ak5386_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + + format &= SND_SOC_DAIFMT_FORMAT_MASK; + if (format != SND_SOC_DAIFMT_LEFT_J && + format != SND_SOC_DAIFMT_I2S) { + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + return 0; +} + +static int ak5386_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak5386_priv *priv = snd_soc_component_get_drvdata(component); + + /* + * From the datasheet: + * + * All external clocks (MCLK, SCLK and LRCK) must be present unless + * PDN pin = “L”. If these clocks are not provided, the AK5386 may + * draw excess current due to its use of internal dynamically + * refreshed logic. If the external clocks are not present, place + * the AK5386 in power-down mode (PDN pin = “L”). + */ + + if (gpio_is_valid(priv->reset_gpio)) + gpio_set_value(priv->reset_gpio, 1); + + return 0; +} + +static int ak5386_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak5386_priv *priv = snd_soc_component_get_drvdata(component); + + if (gpio_is_valid(priv->reset_gpio)) + gpio_set_value(priv->reset_gpio, 0); + + return 0; +} + +static const struct snd_soc_dai_ops ak5386_dai_ops = { + .set_fmt = ak5386_set_dai_fmt, + .hw_params = ak5386_hw_params, + .hw_free = ak5386_hw_free, +}; + +static struct snd_soc_dai_driver ak5386_dai = { + .name = "ak5386-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE, + }, + .ops = &ak5386_dai_ops, +}; + +#ifdef CONFIG_OF +static const struct of_device_id ak5386_dt_ids[] = { + { .compatible = "asahi-kasei,ak5386", }, + { } +}; +MODULE_DEVICE_TABLE(of, ak5386_dt_ids); +#endif + +static int ak5386_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ak5386_priv *priv; + int ret, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->reset_gpio = -EINVAL; + dev_set_drvdata(dev, priv); + + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + priv->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret < 0) + return ret; + + if (of_match_device(of_match_ptr(ak5386_dt_ids), dev)) + priv->reset_gpio = of_get_named_gpio(dev->of_node, + "reset-gpio", 0); + + if (gpio_is_valid(priv->reset_gpio)) + if (devm_gpio_request_one(dev, priv->reset_gpio, + GPIOF_OUT_INIT_LOW, + "AK5386 Reset")) + priv->reset_gpio = -EINVAL; + + return devm_snd_soc_register_component(dev, &soc_component_ak5386, + &ak5386_dai, 1); +} + +static struct platform_driver ak5386_driver = { + .probe = ak5386_probe, + .driver = { + .name = "ak5386", + .of_match_table = of_match_ptr(ak5386_dt_ids), + }, +}; + +module_platform_driver(ak5386_driver); + +MODULE_DESCRIPTION("ASoC driver for AK5386 ADC"); +MODULE_AUTHOR("Daniel Mack "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c new file mode 100644 index 000000000..dda165b14 --- /dev/null +++ b/sound/soc/codecs/ak5558.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Audio driver for AK5558 ADC +// +// Copyright (C) 2015 Asahi Kasei Microdevices Corporation +// Copyright 2018 NXP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ak5558.h" + +/* AK5558 Codec Private Data */ +struct ak5558_priv { + struct snd_soc_component component; + struct regmap *regmap; + struct i2c_client *i2c; + struct gpio_desc *reset_gpiod; /* Reset & Power down GPIO */ + int slots; + int slot_width; +}; + +/* ak5558 register cache & default register settings */ +static const struct reg_default ak5558_reg[] = { + { 0x0, 0xFF }, /* 0x00 AK5558_00_POWER_MANAGEMENT1 */ + { 0x1, 0x01 }, /* 0x01 AK5558_01_POWER_MANAGEMENT2 */ + { 0x2, 0x01 }, /* 0x02 AK5558_02_CONTROL1 */ + { 0x3, 0x00 }, /* 0x03 AK5558_03_CONTROL2 */ + { 0x4, 0x00 }, /* 0x04 AK5558_04_CONTROL3 */ + { 0x5, 0x00 } /* 0x05 AK5558_05_DSD */ +}; + +static const char * const mono_texts[] = { + "8 Slot", "2 Slot", "4 Slot", "1 Slot", +}; + +static const struct soc_enum ak5558_mono_enum[] = { + SOC_ENUM_SINGLE(AK5558_01_POWER_MANAGEMENT2, 1, + ARRAY_SIZE(mono_texts), mono_texts), +}; + +static const char * const digfil_texts[] = { + "Sharp Roll-Off", "Show Roll-Off", + "Short Delay Sharp Roll-Off", "Short Delay Show Roll-Off", +}; + +static const struct soc_enum ak5558_adcset_enum[] = { + SOC_ENUM_SINGLE(AK5558_04_CONTROL3, 0, + ARRAY_SIZE(digfil_texts), digfil_texts), +}; + +static const struct snd_kcontrol_new ak5558_snd_controls[] = { + SOC_ENUM("AK5558 Monaural Mode", ak5558_mono_enum[0]), + SOC_ENUM("AK5558 Digital Filter", ak5558_adcset_enum[0]), +}; + +static const struct snd_soc_dapm_widget ak5558_dapm_widgets[] = { + /* Analog Input */ + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + SND_SOC_DAPM_INPUT("AIN3"), + SND_SOC_DAPM_INPUT("AIN4"), + SND_SOC_DAPM_INPUT("AIN5"), + SND_SOC_DAPM_INPUT("AIN6"), + SND_SOC_DAPM_INPUT("AIN7"), + SND_SOC_DAPM_INPUT("AIN8"), + + SND_SOC_DAPM_ADC("ADC Ch1", NULL, AK5558_00_POWER_MANAGEMENT1, 0, 0), + SND_SOC_DAPM_ADC("ADC Ch2", NULL, AK5558_00_POWER_MANAGEMENT1, 1, 0), + SND_SOC_DAPM_ADC("ADC Ch3", NULL, AK5558_00_POWER_MANAGEMENT1, 2, 0), + SND_SOC_DAPM_ADC("ADC Ch4", NULL, AK5558_00_POWER_MANAGEMENT1, 3, 0), + SND_SOC_DAPM_ADC("ADC Ch5", NULL, AK5558_00_POWER_MANAGEMENT1, 4, 0), + SND_SOC_DAPM_ADC("ADC Ch6", NULL, AK5558_00_POWER_MANAGEMENT1, 5, 0), + SND_SOC_DAPM_ADC("ADC Ch7", NULL, AK5558_00_POWER_MANAGEMENT1, 6, 0), + SND_SOC_DAPM_ADC("ADC Ch8", NULL, AK5558_00_POWER_MANAGEMENT1, 7, 0), + + SND_SOC_DAPM_AIF_OUT("SDTO", "Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route ak5558_intercon[] = { + {"ADC Ch1", NULL, "AIN1"}, + {"SDTO", NULL, "ADC Ch1"}, + + {"ADC Ch2", NULL, "AIN2"}, + {"SDTO", NULL, "ADC Ch2"}, + + {"ADC Ch3", NULL, "AIN3"}, + {"SDTO", NULL, "ADC Ch3"}, + + {"ADC Ch4", NULL, "AIN4"}, + {"SDTO", NULL, "ADC Ch4"}, + + {"ADC Ch5", NULL, "AIN5"}, + {"SDTO", NULL, "ADC Ch5"}, + + {"ADC Ch6", NULL, "AIN6"}, + {"SDTO", NULL, "ADC Ch6"}, + + {"ADC Ch7", NULL, "AIN7"}, + {"SDTO", NULL, "ADC Ch7"}, + + {"ADC Ch8", NULL, "AIN8"}, + {"SDTO", NULL, "ADC Ch8"}, +}; + +static int ak5558_set_mcki(struct snd_soc_component *component) +{ + return snd_soc_component_update_bits(component, AK5558_02_CONTROL1, AK5558_CKS, + AK5558_CKS_AUTO); +} + +static int ak5558_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ak5558_priv *ak5558 = snd_soc_component_get_drvdata(component); + u8 bits; + int pcm_width = max(params_physical_width(params), ak5558->slot_width); + + /* set master/slave audio interface */ + bits = snd_soc_component_read32(component, AK5558_02_CONTROL1); + bits &= ~AK5558_BITS; + + switch (pcm_width) { + case 16: + bits |= AK5558_DIF_24BIT_MODE; + break; + case 32: + bits |= AK5558_DIF_32BIT_MODE; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, AK5558_02_CONTROL1, AK5558_BITS, bits); + + return 0; +} + +static int ak5558_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u8 format; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(dai->dev, "Clock mode unsupported"); + return -EINVAL; + } + + /* set master/slave audio interface */ + format = snd_soc_component_read32(component, AK5558_02_CONTROL1); + format &= ~AK5558_DIF; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format |= AK5558_DIF_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + format |= AK5558_DIF_MSB_MODE; + break; + case SND_SOC_DAIFMT_DSP_B: + format |= AK5558_DIF_MSB_MODE; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, AK5558_02_CONTROL1, AK5558_DIF, format); + + return 0; +} + +static int ak5558_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct ak5558_priv *ak5558 = snd_soc_component_get_drvdata(component); + int tdm_mode; + + ak5558->slots = slots; + ak5558->slot_width = slot_width; + + switch (slots * slot_width) { + case 128: + tdm_mode = AK5558_MODE_TDM128; + break; + case 256: + tdm_mode = AK5558_MODE_TDM256; + break; + case 512: + tdm_mode = AK5558_MODE_TDM512; + break; + default: + tdm_mode = AK5558_MODE_NORMAL; + break; + } + + snd_soc_component_update_bits(component, AK5558_03_CONTROL2, AK5558_MODE_BITS, + tdm_mode); + return 0; +} + +#define AK5558_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const unsigned int ak5558_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, + 2822400, +}; + +static const struct snd_pcm_hw_constraint_list ak5558_rate_constraints = { + .count = ARRAY_SIZE(ak5558_rates), + .list = ak5558_rates, +}; + +static int ak5558_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &ak5558_rate_constraints); +} + +static struct snd_soc_dai_ops ak5558_dai_ops = { + .startup = ak5558_startup, + .hw_params = ak5558_hw_params, + + .set_fmt = ak5558_set_dai_fmt, + .set_tdm_slot = ak5558_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ak5558_dai = { + .name = "ak5558-aif", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = AK5558_FORMATS, + }, + .ops = &ak5558_dai_ops, +}; + +static void ak5558_power_off(struct ak5558_priv *ak5558) +{ + if (!ak5558->reset_gpiod) + return; + + gpiod_set_value_cansleep(ak5558->reset_gpiod, 1); + usleep_range(1000, 2000); +} + +static void ak5558_power_on(struct ak5558_priv *ak5558) +{ + if (!ak5558->reset_gpiod) + return; + + gpiod_set_value_cansleep(ak5558->reset_gpiod, 0); + usleep_range(1000, 2000); +} + +static int ak5558_probe(struct snd_soc_component *component) +{ + struct ak5558_priv *ak5558 = snd_soc_component_get_drvdata(component); + + ak5558_power_on(ak5558); + return ak5558_set_mcki(component); +} + +static void ak5558_remove(struct snd_soc_component *component) +{ + struct ak5558_priv *ak5558 = snd_soc_component_get_drvdata(component); + + ak5558_power_off(ak5558); +} + +static int __maybe_unused ak5558_runtime_suspend(struct device *dev) +{ + struct ak5558_priv *ak5558 = dev_get_drvdata(dev); + + regcache_cache_only(ak5558->regmap, true); + ak5558_power_off(ak5558); + + return 0; +} + +static int __maybe_unused ak5558_runtime_resume(struct device *dev) +{ + struct ak5558_priv *ak5558 = dev_get_drvdata(dev); + + ak5558_power_off(ak5558); + ak5558_power_on(ak5558); + + regcache_cache_only(ak5558->regmap, false); + regcache_mark_dirty(ak5558->regmap); + + return regcache_sync(ak5558->regmap); +} + +static const struct dev_pm_ops ak5558_pm = { + SET_RUNTIME_PM_OPS(ak5558_runtime_suspend, ak5558_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct snd_soc_component_driver soc_codec_dev_ak5558 = { + .probe = ak5558_probe, + .remove = ak5558_remove, + .controls = ak5558_snd_controls, + .num_controls = ARRAY_SIZE(ak5558_snd_controls), + .dapm_widgets = ak5558_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ak5558_dapm_widgets), + .dapm_routes = ak5558_intercon, + .num_dapm_routes = ARRAY_SIZE(ak5558_intercon), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ak5558_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AK5558_05_DSD, + .reg_defaults = ak5558_reg, + .num_reg_defaults = ARRAY_SIZE(ak5558_reg), + .cache_type = REGCACHE_RBTREE, +}; + +static int ak5558_i2c_probe(struct i2c_client *i2c) +{ + struct ak5558_priv *ak5558; + int ret = 0; + + ak5558 = devm_kzalloc(&i2c->dev, sizeof(*ak5558), GFP_KERNEL); + if (!ak5558) + return -ENOMEM; + + ak5558->regmap = devm_regmap_init_i2c(i2c, &ak5558_regmap); + if (IS_ERR(ak5558->regmap)) + return PTR_ERR(ak5558->regmap); + + i2c_set_clientdata(i2c, ak5558); + ak5558->i2c = i2c; + + ak5558->reset_gpiod = devm_gpiod_get_optional(&i2c->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ak5558->reset_gpiod)) + return PTR_ERR(ak5558->reset_gpiod); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_codec_dev_ak5558, + &ak5558_dai, 1); + if (ret) + return ret; + + pm_runtime_enable(&i2c->dev); + + return 0; +} + +static int ak5558_i2c_remove(struct i2c_client *i2c) +{ + pm_runtime_disable(&i2c->dev); + + return 0; +} + +static const struct of_device_id ak5558_i2c_dt_ids[] = { + { .compatible = "asahi-kasei,ak5558"}, + { } +}; +MODULE_DEVICE_TABLE(of, ak5558_i2c_dt_ids); + +static struct i2c_driver ak5558_i2c_driver = { + .driver = { + .name = "ak5558", + .of_match_table = of_match_ptr(ak5558_i2c_dt_ids), + .pm = &ak5558_pm, + }, + .probe_new = ak5558_i2c_probe, + .remove = ak5558_i2c_remove, +}; + +module_i2c_driver(ak5558_i2c_driver); + +MODULE_AUTHOR("Junichi Wakasugi "); +MODULE_AUTHOR("Mihai Serban "); +MODULE_DESCRIPTION("ASoC AK5558 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ak5558.h b/sound/soc/codecs/ak5558.h new file mode 100644 index 000000000..61059086f --- /dev/null +++ b/sound/soc/codecs/ak5558.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Audio driver header for AK5558 + * + * Copyright (C) 2016 Asahi Kasei Microdevices Corporation + * Copyright 2018 NXP + */ + +#ifndef _AK5558_H +#define _AK5558_H + +#define AK5558_00_POWER_MANAGEMENT1 0x00 +#define AK5558_01_POWER_MANAGEMENT2 0x01 +#define AK5558_02_CONTROL1 0x02 +#define AK5558_03_CONTROL2 0x03 +#define AK5558_04_CONTROL3 0x04 +#define AK5558_05_DSD 0x05 + +/* AK5558_02_CONTROL1 fields */ +#define AK5558_DIF GENMASK(1, 1) +#define AK5558_DIF_MSB_MODE (0 << 1) +#define AK5558_DIF_I2S_MODE (1 << 1) + +#define AK5558_BITS GENMASK(2, 2) +#define AK5558_DIF_24BIT_MODE (0 << 2) +#define AK5558_DIF_32BIT_MODE (1 << 2) + +#define AK5558_CKS GENMASK(6, 3) +#define AK5558_CKS_128FS_192KHZ (0 << 3) +#define AK5558_CKS_192FS_192KHZ (1 << 3) +#define AK5558_CKS_256FS_48KHZ (2 << 3) +#define AK5558_CKS_256FS_96KHZ (3 << 3) +#define AK5558_CKS_384FS_96KHZ (4 << 3) +#define AK5558_CKS_384FS_48KHZ (5 << 3) +#define AK5558_CKS_512FS_48KHZ (6 << 3) +#define AK5558_CKS_768FS_48KHZ (7 << 3) +#define AK5558_CKS_64FS_384KHZ (8 << 3) +#define AK5558_CKS_32FS_768KHZ (9 << 3) +#define AK5558_CKS_96FS_384KHZ (10 << 3) +#define AK5558_CKS_48FS_768KHZ (11 << 3) +#define AK5558_CKS_64FS_768KHZ (12 << 3) +#define AK5558_CKS_1024FS_16KHZ (13 << 3) +#define AK5558_CKS_AUTO (15 << 3) + +/* AK5558_03_CONTROL2 fields */ +#define AK5558_MODE_BITS GENMASK(6, 5) +#define AK5558_MODE_NORMAL (0 << 5) +#define AK5558_MODE_TDM128 (1 << 5) +#define AK5558_MODE_TDM256 (2 << 5) +#define AK5558_MODE_TDM512 (3 << 5) + +#endif diff --git a/sound/soc/codecs/alc5623.c b/sound/soc/codecs/alc5623.c new file mode 100644 index 000000000..981a32973 --- /dev/null +++ b/sound/soc/codecs/alc5623.c @@ -0,0 +1,1095 @@ +/* + * alc5623.c -- alc562[123] ALSA Soc Audio driver + * + * Copyright 2008 Realtek Microelectronics + * Author: flove Ethan + * + * Copyright 2010 Arnaud Patard + * + * + * Based on WM8753.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alc5623.h" + +static int caps_charge = 2000; +module_param(caps_charge, int, 0); +MODULE_PARM_DESC(caps_charge, "ALC5623 cap charge time (msecs)"); + +/* codec private data */ +struct alc5623_priv { + struct regmap *regmap; + u8 id; + unsigned int sysclk; + unsigned int add_ctrl; + unsigned int jack_det_ctrl; +}; + +static inline int alc5623_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, ALC5623_RESET, 0); +} + +static int amp_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + /* to power-on/off class-d amp generators/speaker */ + /* need to write to 'index-46h' register : */ + /* so write index num (here 0x46) to reg 0x6a */ + /* and then 0xffff/0 to reg 0x6c */ + snd_soc_component_write(component, ALC5623_HID_CTRL_INDEX, 0x46); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_write(component, ALC5623_HID_CTRL_DATA, 0xFFFF); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_write(component, ALC5623_HID_CTRL_DATA, 0); + break; + } + + return 0; +} + +/* + * ALC5623 Controls + */ + +static const DECLARE_TLV_DB_SCALE(vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(hp_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_rec_tlv, -1650, 150, 0); +static const DECLARE_TLV_DB_RANGE(boost_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(dig_tlv, 0, 600, 0); + +static const struct snd_kcontrol_new alc5621_vol_snd_controls[] = { + SOC_DOUBLE_TLV("Speaker Playback Volume", + ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Speaker Playback Switch", + ALC5623_SPK_OUT_VOL, 15, 7, 1, 1), + SOC_DOUBLE_TLV("Headphone Playback Volume", + ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Headphone Playback Switch", + ALC5623_HP_OUT_VOL, 15, 7, 1, 1), +}; + +static const struct snd_kcontrol_new alc5622_vol_snd_controls[] = { + SOC_DOUBLE_TLV("Speaker Playback Volume", + ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Speaker Playback Switch", + ALC5623_SPK_OUT_VOL, 15, 7, 1, 1), + SOC_DOUBLE_TLV("Line Playback Volume", + ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Line Playback Switch", + ALC5623_HP_OUT_VOL, 15, 7, 1, 1), +}; + +static const struct snd_kcontrol_new alc5623_vol_snd_controls[] = { + SOC_DOUBLE_TLV("Line Playback Volume", + ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Line Playback Switch", + ALC5623_SPK_OUT_VOL, 15, 7, 1, 1), + SOC_DOUBLE_TLV("Headphone Playback Volume", + ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Headphone Playback Switch", + ALC5623_HP_OUT_VOL, 15, 7, 1, 1), +}; + +static const struct snd_kcontrol_new alc5623_snd_controls[] = { + SOC_DOUBLE_TLV("Auxout Playback Volume", + ALC5623_MONO_AUX_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Auxout Playback Switch", + ALC5623_MONO_AUX_OUT_VOL, 15, 7, 1, 1), + SOC_DOUBLE_TLV("PCM Playback Volume", + ALC5623_STEREO_DAC_VOL, 8, 0, 31, 1, vol_tlv), + SOC_DOUBLE_TLV("AuxI Capture Volume", + ALC5623_AUXIN_VOL, 8, 0, 31, 1, vol_tlv), + SOC_DOUBLE_TLV("LineIn Capture Volume", + ALC5623_LINE_IN_VOL, 8, 0, 31, 1, vol_tlv), + SOC_SINGLE_TLV("Mic1 Capture Volume", + ALC5623_MIC_VOL, 8, 31, 1, vol_tlv), + SOC_SINGLE_TLV("Mic2 Capture Volume", + ALC5623_MIC_VOL, 0, 31, 1, vol_tlv), + SOC_DOUBLE_TLV("Rec Capture Volume", + ALC5623_ADC_REC_GAIN, 7, 0, 31, 0, adc_rec_tlv), + SOC_SINGLE_TLV("Mic 1 Boost Volume", + ALC5623_MIC_CTRL, 10, 2, 0, boost_tlv), + SOC_SINGLE_TLV("Mic 2 Boost Volume", + ALC5623_MIC_CTRL, 8, 2, 0, boost_tlv), + SOC_SINGLE_TLV("Digital Boost Volume", + ALC5623_ADD_CTRL_REG, 4, 3, 0, dig_tlv), +}; + +/* + * DAPM Controls + */ +static const struct snd_kcontrol_new alc5623_hp_mixer_controls[] = { +SOC_DAPM_SINGLE("LI2HP Playback Switch", ALC5623_LINE_IN_VOL, 15, 1, 1), +SOC_DAPM_SINGLE("AUXI2HP Playback Switch", ALC5623_AUXIN_VOL, 15, 1, 1), +SOC_DAPM_SINGLE("MIC12HP Playback Switch", ALC5623_MIC_ROUTING_CTRL, 15, 1, 1), +SOC_DAPM_SINGLE("MIC22HP Playback Switch", ALC5623_MIC_ROUTING_CTRL, 7, 1, 1), +SOC_DAPM_SINGLE("DAC2HP Playback Switch", ALC5623_STEREO_DAC_VOL, 15, 1, 1), +}; + +static const struct snd_kcontrol_new alc5623_hpl_mixer_controls[] = { +SOC_DAPM_SINGLE("ADC2HP_L Playback Switch", ALC5623_ADC_REC_GAIN, 15, 1, 1), +}; + +static const struct snd_kcontrol_new alc5623_hpr_mixer_controls[] = { +SOC_DAPM_SINGLE("ADC2HP_R Playback Switch", ALC5623_ADC_REC_GAIN, 14, 1, 1), +}; + +static const struct snd_kcontrol_new alc5623_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("ADC2MONO_L Playback Switch", ALC5623_ADC_REC_GAIN, 13, 1, 1), +SOC_DAPM_SINGLE("ADC2MONO_R Playback Switch", ALC5623_ADC_REC_GAIN, 12, 1, 1), +SOC_DAPM_SINGLE("LI2MONO Playback Switch", ALC5623_LINE_IN_VOL, 13, 1, 1), +SOC_DAPM_SINGLE("AUXI2MONO Playback Switch", ALC5623_AUXIN_VOL, 13, 1, 1), +SOC_DAPM_SINGLE("MIC12MONO Playback Switch", ALC5623_MIC_ROUTING_CTRL, 13, 1, 1), +SOC_DAPM_SINGLE("MIC22MONO Playback Switch", ALC5623_MIC_ROUTING_CTRL, 5, 1, 1), +SOC_DAPM_SINGLE("DAC2MONO Playback Switch", ALC5623_STEREO_DAC_VOL, 13, 1, 1), +}; + +static const struct snd_kcontrol_new alc5623_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("LI2SPK Playback Switch", ALC5623_LINE_IN_VOL, 14, 1, 1), +SOC_DAPM_SINGLE("AUXI2SPK Playback Switch", ALC5623_AUXIN_VOL, 14, 1, 1), +SOC_DAPM_SINGLE("MIC12SPK Playback Switch", ALC5623_MIC_ROUTING_CTRL, 14, 1, 1), +SOC_DAPM_SINGLE("MIC22SPK Playback Switch", ALC5623_MIC_ROUTING_CTRL, 6, 1, 1), +SOC_DAPM_SINGLE("DAC2SPK Playback Switch", ALC5623_STEREO_DAC_VOL, 14, 1, 1), +}; + +/* Left Record Mixer */ +static const struct snd_kcontrol_new alc5623_captureL_mixer_controls[] = { +SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5623_ADC_REC_MIXER, 14, 1, 1), +SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5623_ADC_REC_MIXER, 13, 1, 1), +SOC_DAPM_SINGLE("LineInL Capture Switch", ALC5623_ADC_REC_MIXER, 12, 1, 1), +SOC_DAPM_SINGLE("Left AuxI Capture Switch", ALC5623_ADC_REC_MIXER, 11, 1, 1), +SOC_DAPM_SINGLE("HPMixerL Capture Switch", ALC5623_ADC_REC_MIXER, 10, 1, 1), +SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5623_ADC_REC_MIXER, 9, 1, 1), +SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5623_ADC_REC_MIXER, 8, 1, 1), +}; + +/* Right Record Mixer */ +static const struct snd_kcontrol_new alc5623_captureR_mixer_controls[] = { +SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5623_ADC_REC_MIXER, 6, 1, 1), +SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5623_ADC_REC_MIXER, 5, 1, 1), +SOC_DAPM_SINGLE("LineInR Capture Switch", ALC5623_ADC_REC_MIXER, 4, 1, 1), +SOC_DAPM_SINGLE("Right AuxI Capture Switch", ALC5623_ADC_REC_MIXER, 3, 1, 1), +SOC_DAPM_SINGLE("HPMixerR Capture Switch", ALC5623_ADC_REC_MIXER, 2, 1, 1), +SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5623_ADC_REC_MIXER, 1, 1, 1), +SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5623_ADC_REC_MIXER, 0, 1, 1), +}; + +static const char *alc5623_spk_n_sour_sel[] = { + "RN/-R", "RP/+R", "LN/-R", "Vmid" }; +static const char *alc5623_hpl_out_input_sel[] = { + "Vmid", "HP Left Mix"}; +static const char *alc5623_hpr_out_input_sel[] = { + "Vmid", "HP Right Mix"}; +static const char *alc5623_spkout_input_sel[] = { + "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"}; +static const char *alc5623_aux_out_input_sel[] = { + "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"}; + +/* auxout output mux */ +static SOC_ENUM_SINGLE_DECL(alc5623_aux_out_input_enum, + ALC5623_OUTPUT_MIXER_CTRL, 6, + alc5623_aux_out_input_sel); +static const struct snd_kcontrol_new alc5623_auxout_mux_controls = +SOC_DAPM_ENUM("Route", alc5623_aux_out_input_enum); + +/* speaker output mux */ +static SOC_ENUM_SINGLE_DECL(alc5623_spkout_input_enum, + ALC5623_OUTPUT_MIXER_CTRL, 10, + alc5623_spkout_input_sel); +static const struct snd_kcontrol_new alc5623_spkout_mux_controls = +SOC_DAPM_ENUM("Route", alc5623_spkout_input_enum); + +/* headphone left output mux */ +static SOC_ENUM_SINGLE_DECL(alc5623_hpl_out_input_enum, + ALC5623_OUTPUT_MIXER_CTRL, 9, + alc5623_hpl_out_input_sel); +static const struct snd_kcontrol_new alc5623_hpl_out_mux_controls = +SOC_DAPM_ENUM("Route", alc5623_hpl_out_input_enum); + +/* headphone right output mux */ +static SOC_ENUM_SINGLE_DECL(alc5623_hpr_out_input_enum, + ALC5623_OUTPUT_MIXER_CTRL, 8, + alc5623_hpr_out_input_sel); +static const struct snd_kcontrol_new alc5623_hpr_out_mux_controls = +SOC_DAPM_ENUM("Route", alc5623_hpr_out_input_enum); + +/* speaker output N select */ +static SOC_ENUM_SINGLE_DECL(alc5623_spk_n_sour_enum, + ALC5623_OUTPUT_MIXER_CTRL, 14, + alc5623_spk_n_sour_sel); +static const struct snd_kcontrol_new alc5623_spkoutn_mux_controls = +SOC_DAPM_ENUM("Route", alc5623_spk_n_sour_enum); + +static const struct snd_soc_dapm_widget alc5623_dapm_widgets[] = { +/* Muxes */ +SND_SOC_DAPM_MUX("AuxOut Mux", SND_SOC_NOPM, 0, 0, + &alc5623_auxout_mux_controls), +SND_SOC_DAPM_MUX("SpeakerOut Mux", SND_SOC_NOPM, 0, 0, + &alc5623_spkout_mux_controls), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, + &alc5623_hpl_out_mux_controls), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, + &alc5623_hpr_out_mux_controls), +SND_SOC_DAPM_MUX("SpeakerOut N Mux", SND_SOC_NOPM, 0, 0, + &alc5623_spkoutn_mux_controls), + +/* output mixers */ +SND_SOC_DAPM_MIXER("HP Mix", SND_SOC_NOPM, 0, 0, + &alc5623_hp_mixer_controls[0], + ARRAY_SIZE(alc5623_hp_mixer_controls)), +SND_SOC_DAPM_MIXER("HPR Mix", ALC5623_PWR_MANAG_ADD2, 4, 0, + &alc5623_hpr_mixer_controls[0], + ARRAY_SIZE(alc5623_hpr_mixer_controls)), +SND_SOC_DAPM_MIXER("HPL Mix", ALC5623_PWR_MANAG_ADD2, 5, 0, + &alc5623_hpl_mixer_controls[0], + ARRAY_SIZE(alc5623_hpl_mixer_controls)), +SND_SOC_DAPM_MIXER("HPOut Mix", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Mono Mix", ALC5623_PWR_MANAG_ADD2, 2, 0, + &alc5623_mono_mixer_controls[0], + ARRAY_SIZE(alc5623_mono_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mix", ALC5623_PWR_MANAG_ADD2, 3, 0, + &alc5623_speaker_mixer_controls[0], + ARRAY_SIZE(alc5623_speaker_mixer_controls)), + +/* input mixers */ +SND_SOC_DAPM_MIXER("Left Capture Mix", ALC5623_PWR_MANAG_ADD2, 1, 0, + &alc5623_captureL_mixer_controls[0], + ARRAY_SIZE(alc5623_captureL_mixer_controls)), +SND_SOC_DAPM_MIXER("Right Capture Mix", ALC5623_PWR_MANAG_ADD2, 0, 0, + &alc5623_captureR_mixer_controls[0], + ARRAY_SIZE(alc5623_captureR_mixer_controls)), + +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", + ALC5623_PWR_MANAG_ADD2, 9, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", + ALC5623_PWR_MANAG_ADD2, 8, 0), +SND_SOC_DAPM_MIXER("I2S Mix", ALC5623_PWR_MANAG_ADD1, 15, 0, NULL, 0), +SND_SOC_DAPM_MIXER("AuxI Mix", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Line Mix", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", + ALC5623_PWR_MANAG_ADD2, 7, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", + ALC5623_PWR_MANAG_ADD2, 6, 0), +SND_SOC_DAPM_PGA("Left Headphone", ALC5623_PWR_MANAG_ADD3, 10, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Headphone", ALC5623_PWR_MANAG_ADD3, 9, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpeakerOut", ALC5623_PWR_MANAG_ADD3, 12, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left AuxOut", ALC5623_PWR_MANAG_ADD3, 14, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right AuxOut", ALC5623_PWR_MANAG_ADD3, 13, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left LineIn", ALC5623_PWR_MANAG_ADD3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right LineIn", ALC5623_PWR_MANAG_ADD3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left AuxI", ALC5623_PWR_MANAG_ADD3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right AuxI", ALC5623_PWR_MANAG_ADD3, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC1 PGA", ALC5623_PWR_MANAG_ADD3, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC2 PGA", ALC5623_PWR_MANAG_ADD3, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC1 Pre Amp", ALC5623_PWR_MANAG_ADD3, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC2 Pre Amp", ALC5623_PWR_MANAG_ADD3, 0, 0, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias1", ALC5623_PWR_MANAG_ADD1, 11, 0), + +SND_SOC_DAPM_OUTPUT("AUXOUTL"), +SND_SOC_DAPM_OUTPUT("AUXOUTR"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("SPKOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +SND_SOC_DAPM_INPUT("LINEINL"), +SND_SOC_DAPM_INPUT("LINEINR"), +SND_SOC_DAPM_INPUT("AUXINL"), +SND_SOC_DAPM_INPUT("AUXINR"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2"), +SND_SOC_DAPM_VMID("Vmid"), +}; + +static const char *alc5623_amp_names[] = {"AB Amp", "D Amp"}; +static SOC_ENUM_SINGLE_DECL(alc5623_amp_enum, + ALC5623_OUTPUT_MIXER_CTRL, 13, + alc5623_amp_names); +static const struct snd_kcontrol_new alc5623_amp_mux_controls = + SOC_DAPM_ENUM("Route", alc5623_amp_enum); + +static const struct snd_soc_dapm_widget alc5623_dapm_amp_widgets[] = { +SND_SOC_DAPM_PGA_E("D Amp", ALC5623_PWR_MANAG_ADD2, 14, 0, NULL, 0, + amp_mixer_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA("AB Amp", ALC5623_PWR_MANAG_ADD2, 15, 0, NULL, 0), +SND_SOC_DAPM_MUX("AB-D Amp Mux", SND_SOC_NOPM, 0, 0, + &alc5623_amp_mux_controls), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* virtual mixer - mixes left & right channels */ + {"I2S Mix", NULL, "Left DAC"}, + {"I2S Mix", NULL, "Right DAC"}, + {"Line Mix", NULL, "Right LineIn"}, + {"Line Mix", NULL, "Left LineIn"}, + {"AuxI Mix", NULL, "Left AuxI"}, + {"AuxI Mix", NULL, "Right AuxI"}, + {"AUXOUTL", NULL, "Left AuxOut"}, + {"AUXOUTR", NULL, "Right AuxOut"}, + + /* HP mixer */ + {"HPL Mix", "ADC2HP_L Playback Switch", "Left Capture Mix"}, + {"HPL Mix", NULL, "HP Mix"}, + {"HPR Mix", "ADC2HP_R Playback Switch", "Right Capture Mix"}, + {"HPR Mix", NULL, "HP Mix"}, + {"HP Mix", "LI2HP Playback Switch", "Line Mix"}, + {"HP Mix", "AUXI2HP Playback Switch", "AuxI Mix"}, + {"HP Mix", "MIC12HP Playback Switch", "MIC1 PGA"}, + {"HP Mix", "MIC22HP Playback Switch", "MIC2 PGA"}, + {"HP Mix", "DAC2HP Playback Switch", "I2S Mix"}, + + /* speaker mixer */ + {"Speaker Mix", "LI2SPK Playback Switch", "Line Mix"}, + {"Speaker Mix", "AUXI2SPK Playback Switch", "AuxI Mix"}, + {"Speaker Mix", "MIC12SPK Playback Switch", "MIC1 PGA"}, + {"Speaker Mix", "MIC22SPK Playback Switch", "MIC2 PGA"}, + {"Speaker Mix", "DAC2SPK Playback Switch", "I2S Mix"}, + + /* mono mixer */ + {"Mono Mix", "ADC2MONO_L Playback Switch", "Left Capture Mix"}, + {"Mono Mix", "ADC2MONO_R Playback Switch", "Right Capture Mix"}, + {"Mono Mix", "LI2MONO Playback Switch", "Line Mix"}, + {"Mono Mix", "AUXI2MONO Playback Switch", "AuxI Mix"}, + {"Mono Mix", "MIC12MONO Playback Switch", "MIC1 PGA"}, + {"Mono Mix", "MIC22MONO Playback Switch", "MIC2 PGA"}, + {"Mono Mix", "DAC2MONO Playback Switch", "I2S Mix"}, + + /* Left record mixer */ + {"Left Capture Mix", "LineInL Capture Switch", "LINEINL"}, + {"Left Capture Mix", "Left AuxI Capture Switch", "AUXINL"}, + {"Left Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"}, + {"Left Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"}, + {"Left Capture Mix", "HPMixerL Capture Switch", "HPL Mix"}, + {"Left Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"}, + {"Left Capture Mix", "MonoMixer Capture Switch", "Mono Mix"}, + + /*Right record mixer */ + {"Right Capture Mix", "LineInR Capture Switch", "LINEINR"}, + {"Right Capture Mix", "Right AuxI Capture Switch", "AUXINR"}, + {"Right Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"}, + {"Right Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"}, + {"Right Capture Mix", "HPMixerR Capture Switch", "HPR Mix"}, + {"Right Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"}, + {"Right Capture Mix", "MonoMixer Capture Switch", "Mono Mix"}, + + /* headphone left mux */ + {"Left Headphone Mux", "HP Left Mix", "HPL Mix"}, + {"Left Headphone Mux", "Vmid", "Vmid"}, + + /* headphone right mux */ + {"Right Headphone Mux", "HP Right Mix", "HPR Mix"}, + {"Right Headphone Mux", "Vmid", "Vmid"}, + + /* speaker out mux */ + {"SpeakerOut Mux", "Vmid", "Vmid"}, + {"SpeakerOut Mux", "HPOut Mix", "HPOut Mix"}, + {"SpeakerOut Mux", "Speaker Mix", "Speaker Mix"}, + {"SpeakerOut Mux", "Mono Mix", "Mono Mix"}, + + /* Mono/Aux Out mux */ + {"AuxOut Mux", "Vmid", "Vmid"}, + {"AuxOut Mux", "HPOut Mix", "HPOut Mix"}, + {"AuxOut Mux", "Speaker Mix", "Speaker Mix"}, + {"AuxOut Mux", "Mono Mix", "Mono Mix"}, + + /* output pga */ + {"HPL", NULL, "Left Headphone"}, + {"Left Headphone", NULL, "Left Headphone Mux"}, + {"HPR", NULL, "Right Headphone"}, + {"Right Headphone", NULL, "Right Headphone Mux"}, + {"Left AuxOut", NULL, "AuxOut Mux"}, + {"Right AuxOut", NULL, "AuxOut Mux"}, + + /* input pga */ + {"Left LineIn", NULL, "LINEINL"}, + {"Right LineIn", NULL, "LINEINR"}, + {"Left AuxI", NULL, "AUXINL"}, + {"Right AuxI", NULL, "AUXINR"}, + {"MIC1 Pre Amp", NULL, "MIC1"}, + {"MIC2 Pre Amp", NULL, "MIC2"}, + {"MIC1 PGA", NULL, "MIC1 Pre Amp"}, + {"MIC2 PGA", NULL, "MIC2 Pre Amp"}, + + /* left ADC */ + {"Left ADC", NULL, "Left Capture Mix"}, + + /* right ADC */ + {"Right ADC", NULL, "Right Capture Mix"}, + + {"SpeakerOut N Mux", "RN/-R", "SpeakerOut"}, + {"SpeakerOut N Mux", "RP/+R", "SpeakerOut"}, + {"SpeakerOut N Mux", "LN/-R", "SpeakerOut"}, + {"SpeakerOut N Mux", "Vmid", "Vmid"}, + + {"SPKOUT", NULL, "SpeakerOut"}, + {"SPKOUTN", NULL, "SpeakerOut N Mux"}, +}; + +static const struct snd_soc_dapm_route intercon_spk[] = { + {"SpeakerOut", NULL, "SpeakerOut Mux"}, +}; + +static const struct snd_soc_dapm_route intercon_amp_spk[] = { + {"AB Amp", NULL, "SpeakerOut Mux"}, + {"D Amp", NULL, "SpeakerOut Mux"}, + {"AB-D Amp Mux", "AB Amp", "AB Amp"}, + {"AB-D Amp Mux", "D Amp", "D Amp"}, + {"SpeakerOut", NULL, "AB-D Amp Mux"}, +}; + +/* PLL divisors */ +struct _pll_div { + u32 pll_in; + u32 pll_out; + u16 regvalue; +}; + +/* Note : pll code from original alc5623 driver. Not sure of how good it is */ +/* useful only for master mode */ +static const struct _pll_div codec_master_pll_div[] = { + + { 2048000, 8192000, 0x0ea0}, + { 3686400, 8192000, 0x4e27}, + { 12000000, 8192000, 0x456b}, + { 13000000, 8192000, 0x495f}, + { 13100000, 8192000, 0x0320}, + { 2048000, 11289600, 0xf637}, + { 3686400, 11289600, 0x2f22}, + { 12000000, 11289600, 0x3e2f}, + { 13000000, 11289600, 0x4d5b}, + { 13100000, 11289600, 0x363b}, + { 2048000, 16384000, 0x1ea0}, + { 3686400, 16384000, 0x9e27}, + { 12000000, 16384000, 0x452b}, + { 13000000, 16384000, 0x542f}, + { 13100000, 16384000, 0x03a0}, + { 2048000, 16934400, 0xe625}, + { 3686400, 16934400, 0x9126}, + { 12000000, 16934400, 0x4d2c}, + { 13000000, 16934400, 0x742f}, + { 13100000, 16934400, 0x3c27}, + { 2048000, 22579200, 0x2aa0}, + { 3686400, 22579200, 0x2f20}, + { 12000000, 22579200, 0x7e2f}, + { 13000000, 22579200, 0x742f}, + { 13100000, 22579200, 0x3c27}, + { 2048000, 24576000, 0x2ea0}, + { 3686400, 24576000, 0xee27}, + { 12000000, 24576000, 0x2915}, + { 13000000, 24576000, 0x772e}, + { 13100000, 24576000, 0x0d20}, +}; + +static const struct _pll_div codec_slave_pll_div[] = { + + { 1024000, 16384000, 0x3ea0}, + { 1411200, 22579200, 0x3ea0}, + { 1536000, 24576000, 0x3ea0}, + { 2048000, 16384000, 0x1ea0}, + { 2822400, 22579200, 0x1ea0}, + { 3072000, 24576000, 0x1ea0}, + +}; + +static int alc5623_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + int i; + struct snd_soc_component *component = codec_dai->component; + int gbl_clk = 0, pll_div = 0; + u16 reg; + + if (pll_id < ALC5623_PLL_FR_MCLK || pll_id > ALC5623_PLL_FR_BCK) + return -ENODEV; + + /* Disable PLL power */ + snd_soc_component_update_bits(component, ALC5623_PWR_MANAG_ADD2, + ALC5623_PWR_ADD2_PLL, + 0); + + /* pll is not used in slave mode */ + reg = snd_soc_component_read32(component, ALC5623_DAI_CONTROL); + if (reg & ALC5623_DAI_SDP_SLAVE_MODE) + return 0; + + if (!freq_in || !freq_out) + return 0; + + switch (pll_id) { + case ALC5623_PLL_FR_MCLK: + for (i = 0; i < ARRAY_SIZE(codec_master_pll_div); i++) { + if (codec_master_pll_div[i].pll_in == freq_in + && codec_master_pll_div[i].pll_out == freq_out) { + /* PLL source from MCLK */ + pll_div = codec_master_pll_div[i].regvalue; + break; + } + } + break; + case ALC5623_PLL_FR_BCK: + for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) { + if (codec_slave_pll_div[i].pll_in == freq_in + && codec_slave_pll_div[i].pll_out == freq_out) { + /* PLL source from Bitclk */ + gbl_clk = ALC5623_GBL_CLK_PLL_SOUR_SEL_BITCLK; + pll_div = codec_slave_pll_div[i].regvalue; + break; + } + } + break; + default: + return -EINVAL; + } + + if (!pll_div) + return -EINVAL; + + snd_soc_component_write(component, ALC5623_GLOBAL_CLK_CTRL_REG, gbl_clk); + snd_soc_component_write(component, ALC5623_PLL_CTRL, pll_div); + snd_soc_component_update_bits(component, ALC5623_PWR_MANAG_ADD2, + ALC5623_PWR_ADD2_PLL, + ALC5623_PWR_ADD2_PLL); + gbl_clk |= ALC5623_GBL_CLK_SYS_SOUR_SEL_PLL; + snd_soc_component_write(component, ALC5623_GLOBAL_CLK_CTRL_REG, gbl_clk); + + return 0; +} + +struct _coeff_div { + u16 fs; + u16 regvalue; +}; + +/* codec hifi mclk (after PLL) clock divider coefficients */ +/* values inspired from column BCLK=32Fs of Appendix A table */ +static const struct _coeff_div coeff_div[] = { + {256*8, 0x3a69}, + {384*8, 0x3c6b}, + {256*4, 0x2a69}, + {384*4, 0x2c6b}, + {256*2, 0x1a69}, + {384*2, 0x1c6b}, + {256*1, 0x0a69}, + {384*1, 0x0c6b}, +}; + +static int get_coeff(struct snd_soc_component *component, int rate) +{ + struct alc5623_priv *alc5623 = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].fs * rate == alc5623->sysclk) + return i; + } + return -EINVAL; +} + +/* + * Clock after PLL and dividers + */ +static int alc5623_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct alc5623_priv *alc5623 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 8192000: + case 11289600: + case 12288000: + case 16384000: + case 16934400: + case 18432000: + case 22579200: + case 24576000: + alc5623->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int alc5623_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = ALC5623_DAI_SDP_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface = ALC5623_DAI_SDP_SLAVE_MODE; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= ALC5623_DAI_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface |= ALC5623_DAI_I2S_DF_RIGHT; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= ALC5623_DAI_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= ALC5623_DAI_I2S_DF_PCM; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= ALC5623_DAI_I2S_DF_PCM | ALC5623_DAI_I2S_PCM_MODE; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= ALC5623_DAI_MAIN_I2S_BCLK_POL_CTRL; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= ALC5623_DAI_MAIN_I2S_BCLK_POL_CTRL; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + default: + return -EINVAL; + } + + return snd_soc_component_write(component, ALC5623_DAI_CONTROL, iface); +} + +static int alc5623_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct alc5623_priv *alc5623 = snd_soc_component_get_drvdata(component); + int coeff, rate; + u16 iface; + + iface = snd_soc_component_read32(component, ALC5623_DAI_CONTROL); + iface &= ~ALC5623_DAI_I2S_DL_MASK; + + /* bit size */ + switch (params_width(params)) { + case 16: + iface |= ALC5623_DAI_I2S_DL_16; + break; + case 20: + iface |= ALC5623_DAI_I2S_DL_20; + break; + case 24: + iface |= ALC5623_DAI_I2S_DL_24; + break; + case 32: + iface |= ALC5623_DAI_I2S_DL_32; + break; + default: + return -EINVAL; + } + + /* set iface & srate */ + snd_soc_component_write(component, ALC5623_DAI_CONTROL, iface); + rate = params_rate(params); + coeff = get_coeff(component, rate); + if (coeff < 0) + return -EINVAL; + + coeff = coeff_div[coeff].regvalue; + dev_dbg(component->dev, "%s: sysclk=%d,rate=%d,coeff=0x%04x\n", + __func__, alc5623->sysclk, rate, coeff); + snd_soc_component_write(component, ALC5623_STEREO_AD_DA_CLK_CTRL, coeff); + + return 0; +} + +static int alc5623_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 hp_mute = ALC5623_MISC_M_DAC_L_INPUT | ALC5623_MISC_M_DAC_R_INPUT; + u16 mute_reg = snd_soc_component_read32(component, ALC5623_MISC_CTRL) & ~hp_mute; + + if (mute) + mute_reg |= hp_mute; + + return snd_soc_component_write(component, ALC5623_MISC_CTRL, mute_reg); +} + +#define ALC5623_ADD2_POWER_EN (ALC5623_PWR_ADD2_VREF \ + | ALC5623_PWR_ADD2_DAC_REF_CIR) + +#define ALC5623_ADD3_POWER_EN (ALC5623_PWR_ADD3_MAIN_BIAS \ + | ALC5623_PWR_ADD3_MIC1_BOOST_AD) + +#define ALC5623_ADD1_POWER_EN \ + (ALC5623_PWR_ADD1_SHORT_CURR_DET_EN | ALC5623_PWR_ADD1_SOFTGEN_EN \ + | ALC5623_PWR_ADD1_DEPOP_BUF_HP | ALC5623_PWR_ADD1_HP_OUT_AMP \ + | ALC5623_PWR_ADD1_HP_OUT_ENH_AMP) + +#define ALC5623_ADD1_POWER_EN_5622 \ + (ALC5623_PWR_ADD1_SHORT_CURR_DET_EN \ + | ALC5623_PWR_ADD1_HP_OUT_AMP) + +static void enable_power_depop(struct snd_soc_component *component) +{ + struct alc5623_priv *alc5623 = snd_soc_component_get_drvdata(component); + + snd_soc_component_update_bits(component, ALC5623_PWR_MANAG_ADD1, + ALC5623_PWR_ADD1_SOFTGEN_EN, + ALC5623_PWR_ADD1_SOFTGEN_EN); + + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD3, ALC5623_ADD3_POWER_EN); + + snd_soc_component_update_bits(component, ALC5623_MISC_CTRL, + ALC5623_MISC_HP_DEPOP_MODE2_EN, + ALC5623_MISC_HP_DEPOP_MODE2_EN); + + msleep(500); + + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD2, ALC5623_ADD2_POWER_EN); + + /* avoid writing '1' into 5622 reserved bits */ + if (alc5623->id == 0x22) + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD1, + ALC5623_ADD1_POWER_EN_5622); + else + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD1, + ALC5623_ADD1_POWER_EN); + + /* disable HP Depop2 */ + snd_soc_component_update_bits(component, ALC5623_MISC_CTRL, + ALC5623_MISC_HP_DEPOP_MODE2_EN, + 0); + +} + +static int alc5623_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + enable_power_depop(component); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD2, + ALC5623_PWR_ADD2_VREF); + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD3, + ALC5623_PWR_ADD3_MAIN_BIAS); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD2, 0); + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD3, 0); + snd_soc_component_write(component, ALC5623_PWR_MANAG_ADD1, 0); + break; + } + return 0; +} + +#define ALC5623_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \ + | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops alc5623_dai_ops = { + .hw_params = alc5623_pcm_hw_params, + .digital_mute = alc5623_mute, + .set_fmt = alc5623_set_dai_fmt, + .set_sysclk = alc5623_set_dai_sysclk, + .set_pll = alc5623_set_dai_pll, +}; + +static struct snd_soc_dai_driver alc5623_dai = { + .name = "alc5623-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ALC5623_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ALC5623_FORMATS,}, + + .ops = &alc5623_dai_ops, +}; + +static int alc5623_suspend(struct snd_soc_component *component) +{ + struct alc5623_priv *alc5623 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(alc5623->regmap, true); + + return 0; +} + +static int alc5623_resume(struct snd_soc_component *component) +{ + struct alc5623_priv *alc5623 = snd_soc_component_get_drvdata(component); + int ret; + + /* Sync reg_cache with the hardware */ + regcache_cache_only(alc5623->regmap, false); + ret = regcache_sync(alc5623->regmap); + if (ret != 0) { + dev_err(component->dev, "Failed to sync register cache: %d\n", + ret); + regcache_cache_only(alc5623->regmap, true); + return ret; + } + + return 0; +} + +static int alc5623_probe(struct snd_soc_component *component) +{ + struct alc5623_priv *alc5623 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + alc5623_reset(component); + + if (alc5623->add_ctrl) { + snd_soc_component_write(component, ALC5623_ADD_CTRL_REG, + alc5623->add_ctrl); + } + + if (alc5623->jack_det_ctrl) { + snd_soc_component_write(component, ALC5623_JACK_DET_CTRL, + alc5623->jack_det_ctrl); + } + + switch (alc5623->id) { + case 0x21: + snd_soc_add_component_controls(component, alc5621_vol_snd_controls, + ARRAY_SIZE(alc5621_vol_snd_controls)); + break; + case 0x22: + snd_soc_add_component_controls(component, alc5622_vol_snd_controls, + ARRAY_SIZE(alc5622_vol_snd_controls)); + break; + case 0x23: + snd_soc_add_component_controls(component, alc5623_vol_snd_controls, + ARRAY_SIZE(alc5623_vol_snd_controls)); + break; + default: + return -EINVAL; + } + + snd_soc_add_component_controls(component, alc5623_snd_controls, + ARRAY_SIZE(alc5623_snd_controls)); + + snd_soc_dapm_new_controls(dapm, alc5623_dapm_widgets, + ARRAY_SIZE(alc5623_dapm_widgets)); + + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); + + switch (alc5623->id) { + case 0x21: + case 0x22: + snd_soc_dapm_new_controls(dapm, alc5623_dapm_amp_widgets, + ARRAY_SIZE(alc5623_dapm_amp_widgets)); + snd_soc_dapm_add_routes(dapm, intercon_amp_spk, + ARRAY_SIZE(intercon_amp_spk)); + break; + case 0x23: + snd_soc_dapm_add_routes(dapm, intercon_spk, + ARRAY_SIZE(intercon_spk)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_device_alc5623 = { + .probe = alc5623_probe, + .suspend = alc5623_suspend, + .resume = alc5623_resume, + .set_bias_level = alc5623_set_bias_level, + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config alc5623_regmap = { + .reg_bits = 8, + .val_bits = 16, + .reg_stride = 2, + + .max_register = ALC5623_VENDOR_ID2, + .cache_type = REGCACHE_RBTREE, +}; + +/* + * ALC5623 2 wire address is determined by A1 pin + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static int alc5623_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct alc5623_platform_data *pdata; + struct alc5623_priv *alc5623; + struct device_node *np; + unsigned int vid1, vid2; + int ret; + u32 val32; + + alc5623 = devm_kzalloc(&client->dev, sizeof(struct alc5623_priv), + GFP_KERNEL); + if (alc5623 == NULL) + return -ENOMEM; + + alc5623->regmap = devm_regmap_init_i2c(client, &alc5623_regmap); + if (IS_ERR(alc5623->regmap)) { + ret = PTR_ERR(alc5623->regmap); + dev_err(&client->dev, "Failed to initialise I/O: %d\n", ret); + return ret; + } + + ret = regmap_read(alc5623->regmap, ALC5623_VENDOR_ID1, &vid1); + if (ret < 0) { + dev_err(&client->dev, "failed to read vendor ID1: %d\n", ret); + return ret; + } + + ret = regmap_read(alc5623->regmap, ALC5623_VENDOR_ID2, &vid2); + if (ret < 0) { + dev_err(&client->dev, "failed to read vendor ID2: %d\n", ret); + return ret; + } + vid2 >>= 8; + + if ((vid1 != 0x10ec) || (vid2 != id->driver_data)) { + dev_err(&client->dev, "unknown or wrong codec\n"); + dev_err(&client->dev, "Expected %x:%lx, got %x:%x\n", + 0x10ec, id->driver_data, + vid1, vid2); + return -ENODEV; + } + + dev_dbg(&client->dev, "Found codec id : alc56%02x\n", vid2); + + pdata = client->dev.platform_data; + if (pdata) { + alc5623->add_ctrl = pdata->add_ctrl; + alc5623->jack_det_ctrl = pdata->jack_det_ctrl; + } else { + if (client->dev.of_node) { + np = client->dev.of_node; + ret = of_property_read_u32(np, "add-ctrl", &val32); + if (!ret) + alc5623->add_ctrl = val32; + ret = of_property_read_u32(np, "jack-det-ctrl", &val32); + if (!ret) + alc5623->jack_det_ctrl = val32; + } + } + + alc5623->id = vid2; + switch (alc5623->id) { + case 0x21: + alc5623_dai.name = "alc5621-hifi"; + break; + case 0x22: + alc5623_dai.name = "alc5622-hifi"; + break; + case 0x23: + alc5623_dai.name = "alc5623-hifi"; + break; + default: + return -EINVAL; + } + + i2c_set_clientdata(client, alc5623); + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_device_alc5623, &alc5623_dai, 1); + if (ret != 0) + dev_err(&client->dev, "Failed to register codec: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id alc5623_i2c_table[] = { + {"alc5621", 0x21}, + {"alc5622", 0x22}, + {"alc5623", 0x23}, + {} +}; +MODULE_DEVICE_TABLE(i2c, alc5623_i2c_table); + +static const struct of_device_id alc5623_of_match[] = { + { .compatible = "realtek,alc5623", }, + { } +}; +MODULE_DEVICE_TABLE(of, alc5623_of_match); + +/* i2c codec control layer */ +static struct i2c_driver alc5623_i2c_driver = { + .driver = { + .name = "alc562x-codec", + .of_match_table = of_match_ptr(alc5623_of_match), + }, + .probe = alc5623_i2c_probe, + .id_table = alc5623_i2c_table, +}; + +module_i2c_driver(alc5623_i2c_driver); + +MODULE_DESCRIPTION("ASoC alc5621/2/3 driver"); +MODULE_AUTHOR("Arnaud Patard "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/alc5623.h b/sound/soc/codecs/alc5623.h new file mode 100644 index 000000000..f3d68260d --- /dev/null +++ b/sound/soc/codecs/alc5623.h @@ -0,0 +1,161 @@ +/* + * alc5623.h -- alc562[123] ALSA Soc Audio driver + * + * Copyright 2008 Realtek Microelectronics + * Copyright 2010 Arnaud Patard + * + * Author: flove + * Arnaud Patard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _ALC5623_H +#define _ALC5623_H + +#define ALC5623_RESET 0x00 +/* 5621 5622 5623 */ +/* speaker output vol 2 2 */ +/* line output vol 4 2 */ +/* HP output vol 4 0 4 */ +#define ALC5623_SPK_OUT_VOL 0x02 +#define ALC5623_HP_OUT_VOL 0x04 +#define ALC5623_MONO_AUX_OUT_VOL 0x06 +#define ALC5623_AUXIN_VOL 0x08 +#define ALC5623_LINE_IN_VOL 0x0A +#define ALC5623_STEREO_DAC_VOL 0x0C +#define ALC5623_MIC_VOL 0x0E +#define ALC5623_MIC_ROUTING_CTRL 0x10 +#define ALC5623_ADC_REC_GAIN 0x12 +#define ALC5623_ADC_REC_MIXER 0x14 +#define ALC5623_SOFT_VOL_CTRL_TIME 0x16 +/* ALC5623_OUTPUT_MIXER_CTRL : */ +/* same remark as for reg 2 line vs speaker */ +#define ALC5623_OUTPUT_MIXER_CTRL 0x1C +#define ALC5623_MIC_CTRL 0x22 + +#define ALC5623_DAI_CONTROL 0x34 +#define ALC5623_DAI_SDP_MASTER_MODE (0 << 15) +#define ALC5623_DAI_SDP_SLAVE_MODE (1 << 15) +#define ALC5623_DAI_I2S_PCM_MODE (1 << 14) +#define ALC5623_DAI_MAIN_I2S_BCLK_POL_CTRL (1 << 7) +#define ALC5623_DAI_ADC_DATA_L_R_SWAP (1 << 5) +#define ALC5623_DAI_DAC_DATA_L_R_SWAP (1 << 4) +#define ALC5623_DAI_I2S_DL_MASK (3 << 2) +#define ALC5623_DAI_I2S_DL_32 (3 << 2) +#define ALC5623_DAI_I2S_DL_24 (2 << 2) +#define ALC5623_DAI_I2S_DL_20 (1 << 2) +#define ALC5623_DAI_I2S_DL_16 (0 << 2) +#define ALC5623_DAI_I2S_DF_PCM (3 << 0) +#define ALC5623_DAI_I2S_DF_LEFT (2 << 0) +#define ALC5623_DAI_I2S_DF_RIGHT (1 << 0) +#define ALC5623_DAI_I2S_DF_I2S (0 << 0) + +#define ALC5623_STEREO_AD_DA_CLK_CTRL 0x36 +#define ALC5623_COMPANDING_CTRL 0x38 + +#define ALC5623_PWR_MANAG_ADD1 0x3A +#define ALC5623_PWR_ADD1_MAIN_I2S_EN (1 << 15) +#define ALC5623_PWR_ADD1_ZC_DET_PD_EN (1 << 14) +#define ALC5623_PWR_ADD1_MIC1_BIAS_EN (1 << 11) +#define ALC5623_PWR_ADD1_SHORT_CURR_DET_EN (1 << 10) +#define ALC5623_PWR_ADD1_SOFTGEN_EN (1 << 8) /* rsvd on 5622 */ +#define ALC5623_PWR_ADD1_DEPOP_BUF_HP (1 << 6) /* rsvd on 5622 */ +#define ALC5623_PWR_ADD1_HP_OUT_AMP (1 << 5) +#define ALC5623_PWR_ADD1_HP_OUT_ENH_AMP (1 << 4) /* rsvd on 5622 */ +#define ALC5623_PWR_ADD1_DEPOP_BUF_AUX (1 << 2) +#define ALC5623_PWR_ADD1_AUX_OUT_AMP (1 << 1) +#define ALC5623_PWR_ADD1_AUX_OUT_ENH_AMP (1 << 0) /* rsvd on 5622 */ + +#define ALC5623_PWR_MANAG_ADD2 0x3C +#define ALC5623_PWR_ADD2_LINEOUT (1 << 15) /* rt5623 */ +#define ALC5623_PWR_ADD2_CLASS_AB (1 << 15) /* rt5621 */ +#define ALC5623_PWR_ADD2_CLASS_D (1 << 14) /* rt5621 */ +#define ALC5623_PWR_ADD2_VREF (1 << 13) +#define ALC5623_PWR_ADD2_PLL (1 << 12) +#define ALC5623_PWR_ADD2_DAC_REF_CIR (1 << 10) +#define ALC5623_PWR_ADD2_L_DAC_CLK (1 << 9) +#define ALC5623_PWR_ADD2_R_DAC_CLK (1 << 8) +#define ALC5623_PWR_ADD2_L_ADC_CLK_GAIN (1 << 7) +#define ALC5623_PWR_ADD2_R_ADC_CLK_GAIN (1 << 6) +#define ALC5623_PWR_ADD2_L_HP_MIXER (1 << 5) +#define ALC5623_PWR_ADD2_R_HP_MIXER (1 << 4) +#define ALC5623_PWR_ADD2_SPK_MIXER (1 << 3) +#define ALC5623_PWR_ADD2_MONO_MIXER (1 << 2) +#define ALC5623_PWR_ADD2_L_ADC_REC_MIXER (1 << 1) +#define ALC5623_PWR_ADD2_R_ADC_REC_MIXER (1 << 0) + +#define ALC5623_PWR_MANAG_ADD3 0x3E +#define ALC5623_PWR_ADD3_MAIN_BIAS (1 << 15) +#define ALC5623_PWR_ADD3_AUXOUT_L_VOL_AMP (1 << 14) +#define ALC5623_PWR_ADD3_AUXOUT_R_VOL_AMP (1 << 13) +#define ALC5623_PWR_ADD3_SPK_OUT (1 << 12) +#define ALC5623_PWR_ADD3_HP_L_OUT_VOL (1 << 10) +#define ALC5623_PWR_ADD3_HP_R_OUT_VOL (1 << 9) +#define ALC5623_PWR_ADD3_LINEIN_L_VOL (1 << 7) +#define ALC5623_PWR_ADD3_LINEIN_R_VOL (1 << 6) +#define ALC5623_PWR_ADD3_AUXIN_L_VOL (1 << 5) +#define ALC5623_PWR_ADD3_AUXIN_R_VOL (1 << 4) +#define ALC5623_PWR_ADD3_MIC1_FUN_CTRL (1 << 3) +#define ALC5623_PWR_ADD3_MIC2_FUN_CTRL (1 << 2) +#define ALC5623_PWR_ADD3_MIC1_BOOST_AD (1 << 1) +#define ALC5623_PWR_ADD3_MIC2_BOOST_AD (1 << 0) + +#define ALC5623_ADD_CTRL_REG 0x40 + +#define ALC5623_GLOBAL_CLK_CTRL_REG 0x42 +#define ALC5623_GBL_CLK_SYS_SOUR_SEL_PLL (1 << 15) +#define ALC5623_GBL_CLK_SYS_SOUR_SEL_MCLK (0 << 15) +#define ALC5623_GBL_CLK_PLL_SOUR_SEL_BITCLK (1 << 14) +#define ALC5623_GBL_CLK_PLL_SOUR_SEL_MCLK (0 << 14) +#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV8 (3 << 1) +#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV4 (2 << 1) +#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV2 (1 << 1) +#define ALC5623_GBL_CLK_PLL_DIV_RATIO_DIV1 (0 << 1) +#define ALC5623_GBL_CLK_PLL_PRE_DIV2 (1 << 0) +#define ALC5623_GBL_CLK_PLL_PRE_DIV1 (0 << 0) + +#define ALC5623_PLL_CTRL 0x44 +#define ALC5623_PLL_CTRL_N_VAL(n) (((n)&0xff) << 8) +#define ALC5623_PLL_CTRL_K_VAL(k) (((k)&0x7) << 4) +#define ALC5623_PLL_CTRL_M_VAL(m) ((m)&0xf) + +#define ALC5623_GPIO_OUTPUT_PIN_CTRL 0x4A +#define ALC5623_GPIO_PIN_CONFIG 0x4C +#define ALC5623_GPIO_PIN_POLARITY 0x4E +#define ALC5623_GPIO_PIN_STICKY 0x50 +#define ALC5623_GPIO_PIN_WAKEUP 0x52 +#define ALC5623_GPIO_PIN_STATUS 0x54 +#define ALC5623_GPIO_PIN_SHARING 0x56 +#define ALC5623_OVER_CURR_STATUS 0x58 +#define ALC5623_JACK_DET_CTRL 0x5A + +#define ALC5623_MISC_CTRL 0x5E +#define ALC5623_MISC_DISABLE_FAST_VREG (1 << 15) +#define ALC5623_MISC_SPK_CLASS_AB_OC_PD (1 << 13) /* 5621 */ +#define ALC5623_MISC_SPK_CLASS_AB_OC_DET (1 << 12) /* 5621 */ +#define ALC5623_MISC_HP_DEPOP_MODE3_EN (1 << 10) +#define ALC5623_MISC_HP_DEPOP_MODE2_EN (1 << 9) +#define ALC5623_MISC_HP_DEPOP_MODE1_EN (1 << 8) +#define ALC5623_MISC_AUXOUT_DEPOP_MODE3_EN (1 << 6) +#define ALC5623_MISC_AUXOUT_DEPOP_MODE2_EN (1 << 5) +#define ALC5623_MISC_AUXOUT_DEPOP_MODE1_EN (1 << 4) +#define ALC5623_MISC_M_DAC_L_INPUT (1 << 3) +#define ALC5623_MISC_M_DAC_R_INPUT (1 << 2) +#define ALC5623_MISC_IRQOUT_INV_CTRL (1 << 0) + +#define ALC5623_PSEDUEO_SPATIAL_CTRL 0x60 +#define ALC5623_EQ_CTRL 0x62 +#define ALC5623_EQ_MODE_ENABLE 0x66 +#define ALC5623_AVC_CTRL 0x68 +#define ALC5623_HID_CTRL_INDEX 0x6A +#define ALC5623_HID_CTRL_DATA 0x6C +#define ALC5623_VENDOR_ID1 0x7C +#define ALC5623_VENDOR_ID2 0x7E + +#define ALC5623_PLL_FR_MCLK 0 +#define ALC5623_PLL_FR_BCK 1 +#endif diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c new file mode 100644 index 000000000..08034a6d4 --- /dev/null +++ b/sound/soc/codecs/alc5632.c @@ -0,0 +1,1192 @@ +/* +* alc5632.c -- ALC5632 ALSA SoC Audio Codec +* +* Copyright (C) 2011 The AC100 Kernel Team +* +* Authors: Leon Romanovsky +* Andrey Danin +* Ilya Petrov +* Marc Dietrich +* +* Based on alc5623.c by Arnaud Patard +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alc5632.h" + +/* + * ALC5632 register cache + */ +static const struct reg_default alc5632_reg_defaults[] = { + { 2, 0x8080 }, /* R2 - Speaker Output Volume */ + { 4, 0x8080 }, /* R4 - Headphone Output Volume */ + { 6, 0x8080 }, /* R6 - AUXOUT Volume */ + { 8, 0xC800 }, /* R8 - Phone Input */ + { 10, 0xE808 }, /* R10 - LINE_IN Volume */ + { 12, 0x1010 }, /* R12 - STEREO DAC Input Volume */ + { 14, 0x0808 }, /* R14 - MIC Input Volume */ + { 16, 0xEE0F }, /* R16 - Stereo DAC and MIC Routing Control */ + { 18, 0xCBCB }, /* R18 - ADC Record Gain */ + { 20, 0x7F7F }, /* R20 - ADC Record Mixer Control */ + { 24, 0xE010 }, /* R24 - Voice DAC Volume */ + { 28, 0x8008 }, /* R28 - Output Mixer Control */ + { 34, 0x0000 }, /* R34 - Microphone Control */ + { 36, 0x00C0 }, /* R36 - Codec Digital MIC/Digital Boost + Control */ + { 46, 0x0000 }, /* R46 - Stereo DAC/Voice DAC/Stereo ADC + Function Select */ + { 52, 0x8000 }, /* R52 - Main Serial Data Port Control + (Stereo I2S) */ + { 54, 0x0000 }, /* R54 - Extend Serial Data Port Control + (VoDAC_I2S/PCM) */ + { 58, 0x0000 }, /* R58 - Power Management Addition 1 */ + { 60, 0x0000 }, /* R60 - Power Management Addition 2 */ + { 62, 0x8000 }, /* R62 - Power Management Addition 3 */ + { 64, 0x0C0A }, /* R64 - General Purpose Control Register 1 */ + { 66, 0x0000 }, /* R66 - General Purpose Control Register 2 */ + { 68, 0x0000 }, /* R68 - PLL1 Control */ + { 70, 0x0000 }, /* R70 - PLL2 Control */ + { 76, 0xBE3E }, /* R76 - GPIO Pin Configuration */ + { 78, 0xBE3E }, /* R78 - GPIO Pin Polarity */ + { 80, 0x0000 }, /* R80 - GPIO Pin Sticky */ + { 82, 0x0000 }, /* R82 - GPIO Pin Wake Up */ + { 86, 0x0000 }, /* R86 - Pin Sharing */ + { 90, 0x0009 }, /* R90 - Soft Volume Control Setting */ + { 92, 0x0000 }, /* R92 - GPIO_Output Pin Control */ + { 94, 0x3000 }, /* R94 - MISC Control */ + { 96, 0x3075 }, /* R96 - Stereo DAC Clock Control_1 */ + { 98, 0x1010 }, /* R98 - Stereo DAC Clock Control_2 */ + { 100, 0x3110 }, /* R100 - VoDAC_PCM Clock Control_1 */ + { 104, 0x0553 }, /* R104 - Pseudo Stereo and Spatial Effect + Block Control */ + { 106, 0x0000 }, /* R106 - Private Register Address */ +}; + +/* codec private data */ +struct alc5632_priv { + struct regmap *regmap; + u8 id; + unsigned int sysclk; +}; + +static bool alc5632_volatile_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case ALC5632_RESET: + case ALC5632_PWR_DOWN_CTRL_STATUS: + case ALC5632_GPIO_PIN_STATUS: + case ALC5632_OVER_CURR_STATUS: + case ALC5632_HID_CTRL_DATA: + case ALC5632_EQ_CTRL: + case ALC5632_VENDOR_ID1: + case ALC5632_VENDOR_ID2: + return true; + + default: + break; + } + + return false; +} + +static inline int alc5632_reset(struct regmap *map) +{ + return regmap_write(map, ALC5632_RESET, 0x59B4); +} + +static int amp_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + /* to power-on/off class-d amp generators/speaker */ + /* need to write to 'index-46h' register : */ + /* so write index num (here 0x46) to reg 0x6a */ + /* and then 0xffff/0 to reg 0x6c */ + snd_soc_component_write(component, ALC5632_HID_CTRL_INDEX, 0x46); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_write(component, ALC5632_HID_CTRL_DATA, 0xFFFF); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_write(component, ALC5632_HID_CTRL_DATA, 0); + break; + } + + return 0; +} + +/* + * ALC5632 Controls + */ + +/* -34.5db min scale, 1.5db steps, no mute */ +static const DECLARE_TLV_DB_SCALE(vol_tlv, -3450, 150, 0); +/* -46.5db min scale, 1.5db steps, no mute */ +static const DECLARE_TLV_DB_SCALE(hp_tlv, -4650, 150, 0); +/* -16.5db min scale, 1.5db steps, no mute */ +static const DECLARE_TLV_DB_SCALE(adc_rec_tlv, -1650, 150, 0); +static const DECLARE_TLV_DB_RANGE(boost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0), + 1, 3, TLV_DB_SCALE_ITEM(2000, 1000, 0) +); +/* 0db min scale, 6 db steps, no mute */ +static const DECLARE_TLV_DB_SCALE(dig_tlv, 0, 600, 0); +/* 0db min scalem 0.75db steps, no mute */ +static const DECLARE_TLV_DB_SCALE(vdac_tlv, -3525, 75, 0); + +static const struct snd_kcontrol_new alc5632_vol_snd_controls[] = { + /* left starts at bit 8, right at bit 0 */ + /* 31 steps (5 bit), -46.5db scale */ + SOC_DOUBLE_TLV("Speaker Playback Volume", + ALC5632_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv), + /* bit 15 mutes left, bit 7 right */ + SOC_DOUBLE("Speaker Playback Switch", + ALC5632_SPK_OUT_VOL, 15, 7, 1, 1), + SOC_DOUBLE_TLV("Headphone Playback Volume", + ALC5632_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Headphone Playback Switch", + ALC5632_HP_OUT_VOL, 15, 7, 1, 1), +}; + +static const struct snd_kcontrol_new alc5632_snd_controls[] = { + SOC_DOUBLE_TLV("Auxout Playback Volume", + ALC5632_AUX_OUT_VOL, 8, 0, 31, 1, hp_tlv), + SOC_DOUBLE("Auxout Playback Switch", + ALC5632_AUX_OUT_VOL, 15, 7, 1, 1), + SOC_SINGLE_TLV("Voice DAC Playback Volume", + ALC5632_VOICE_DAC_VOL, 0, 63, 0, vdac_tlv), + SOC_SINGLE("Voice DAC Playback Switch", + ALC5632_VOICE_DAC_VOL, 12, 1, 1), + SOC_SINGLE_TLV("Phone Playback Volume", + ALC5632_PHONE_IN_VOL, 8, 31, 1, vol_tlv), + SOC_DOUBLE_TLV("LineIn Playback Volume", + ALC5632_LINE_IN_VOL, 8, 0, 31, 1, vol_tlv), + SOC_DOUBLE_TLV("Master Playback Volume", + ALC5632_STEREO_DAC_IN_VOL, 8, 0, 63, 1, vdac_tlv), + SOC_DOUBLE("Master Playback Switch", + ALC5632_STEREO_DAC_IN_VOL, 15, 7, 1, 1), + SOC_SINGLE_TLV("Mic1 Playback Volume", + ALC5632_MIC_VOL, 8, 31, 1, vol_tlv), + SOC_SINGLE_TLV("Mic2 Playback Volume", + ALC5632_MIC_VOL, 0, 31, 1, vol_tlv), + SOC_DOUBLE_TLV("Rec Capture Volume", + ALC5632_ADC_REC_GAIN, 8, 0, 31, 0, adc_rec_tlv), + SOC_SINGLE_TLV("Mic 1 Boost Volume", + ALC5632_MIC_CTRL, 10, 3, 0, boost_tlv), + SOC_SINGLE_TLV("Mic 2 Boost Volume", + ALC5632_MIC_CTRL, 8, 3, 0, boost_tlv), + SOC_SINGLE_TLV("DMIC Boost Capture Volume", + ALC5632_DIGI_BOOST_CTRL, 0, 7, 0, dig_tlv), + SOC_SINGLE("DMIC En Capture Switch", + ALC5632_DIGI_BOOST_CTRL, 15, 1, 0), + SOC_SINGLE("DMIC PreFilter Capture Switch", + ALC5632_DIGI_BOOST_CTRL, 12, 1, 0), +}; + +/* + * DAPM Controls + */ +static const struct snd_kcontrol_new alc5632_hp_mixer_controls[] = { +SOC_DAPM_SINGLE("LI2HP Playback Switch", ALC5632_LINE_IN_VOL, 15, 1, 1), +SOC_DAPM_SINGLE("PHONE2HP Playback Switch", ALC5632_PHONE_IN_VOL, 15, 1, 1), +SOC_DAPM_SINGLE("MIC12HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 15, 1, 1), +SOC_DAPM_SINGLE("MIC22HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 11, 1, 1), +SOC_DAPM_SINGLE("VOICE2HP Playback Switch", ALC5632_VOICE_DAC_VOL, 15, 1, 1), +}; + +static const struct snd_kcontrol_new alc5632_hpl_mixer_controls[] = { +SOC_DAPM_SINGLE("ADC2HP_L Playback Switch", ALC5632_ADC_REC_GAIN, 15, 1, 1), +SOC_DAPM_SINGLE("DACL2HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 3, 1, 1), +}; + +static const struct snd_kcontrol_new alc5632_hpr_mixer_controls[] = { +SOC_DAPM_SINGLE("ADC2HP_R Playback Switch", ALC5632_ADC_REC_GAIN, 7, 1, 1), +SOC_DAPM_SINGLE("DACR2HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 2, 1, 1), +}; + +static const struct snd_kcontrol_new alc5632_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("ADC2MONO_L Playback Switch", ALC5632_ADC_REC_GAIN, 14, 1, 1), +SOC_DAPM_SINGLE("ADC2MONO_R Playback Switch", ALC5632_ADC_REC_GAIN, 6, 1, 1), +SOC_DAPM_SINGLE("LI2MONO Playback Switch", ALC5632_LINE_IN_VOL, 13, 1, 1), +SOC_DAPM_SINGLE("MIC12MONO Playback Switch", + ALC5632_MIC_ROUTING_CTRL, 13, 1, 1), +SOC_DAPM_SINGLE("MIC22MONO Playback Switch", + ALC5632_MIC_ROUTING_CTRL, 9, 1, 1), +SOC_DAPM_SINGLE("DAC2MONO Playback Switch", ALC5632_MIC_ROUTING_CTRL, 0, 1, 1), +SOC_DAPM_SINGLE("VOICE2MONO Playback Switch", ALC5632_VOICE_DAC_VOL, 13, 1, 1), +}; + +static const struct snd_kcontrol_new alc5632_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("LI2SPK Playback Switch", ALC5632_LINE_IN_VOL, 14, 1, 1), +SOC_DAPM_SINGLE("PHONE2SPK Playback Switch", ALC5632_PHONE_IN_VOL, 14, 1, 1), +SOC_DAPM_SINGLE("MIC12SPK Playback Switch", + ALC5632_MIC_ROUTING_CTRL, 14, 1, 1), +SOC_DAPM_SINGLE("MIC22SPK Playback Switch", + ALC5632_MIC_ROUTING_CTRL, 10, 1, 1), +SOC_DAPM_SINGLE("DAC2SPK Playback Switch", ALC5632_MIC_ROUTING_CTRL, 1, 1, 1), +SOC_DAPM_SINGLE("VOICE2SPK Playback Switch", ALC5632_VOICE_DAC_VOL, 14, 1, 1), +}; + +/* Left Record Mixer */ +static const struct snd_kcontrol_new alc5632_captureL_mixer_controls[] = { +SOC_DAPM_SINGLE("MIC12REC_L Capture Switch", ALC5632_ADC_REC_MIXER, 14, 1, 1), +SOC_DAPM_SINGLE("MIC22REC_L Capture Switch", ALC5632_ADC_REC_MIXER, 13, 1, 1), +SOC_DAPM_SINGLE("LIL2REC Capture Switch", ALC5632_ADC_REC_MIXER, 12, 1, 1), +SOC_DAPM_SINGLE("PH2REC_L Capture Switch", ALC5632_ADC_REC_MIXER, 11, 1, 1), +SOC_DAPM_SINGLE("HPL2REC Capture Switch", ALC5632_ADC_REC_MIXER, 10, 1, 1), +SOC_DAPM_SINGLE("SPK2REC_L Capture Switch", ALC5632_ADC_REC_MIXER, 9, 1, 1), +SOC_DAPM_SINGLE("MONO2REC_L Capture Switch", ALC5632_ADC_REC_MIXER, 8, 1, 1), +}; + +/* Right Record Mixer */ +static const struct snd_kcontrol_new alc5632_captureR_mixer_controls[] = { +SOC_DAPM_SINGLE("MIC12REC_R Capture Switch", ALC5632_ADC_REC_MIXER, 6, 1, 1), +SOC_DAPM_SINGLE("MIC22REC_R Capture Switch", ALC5632_ADC_REC_MIXER, 5, 1, 1), +SOC_DAPM_SINGLE("LIR2REC Capture Switch", ALC5632_ADC_REC_MIXER, 4, 1, 1), +SOC_DAPM_SINGLE("PH2REC_R Capture Switch", ALC5632_ADC_REC_MIXER, 3, 1, 1), +SOC_DAPM_SINGLE("HPR2REC Capture Switch", ALC5632_ADC_REC_MIXER, 2, 1, 1), +SOC_DAPM_SINGLE("SPK2REC_R Capture Switch", ALC5632_ADC_REC_MIXER, 1, 1, 1), +SOC_DAPM_SINGLE("MONO2REC_R Capture Switch", ALC5632_ADC_REC_MIXER, 0, 1, 1), +}; + +/* Dmic Mixer */ +static const struct snd_kcontrol_new alc5632_dmicl_mixer_controls[] = { +SOC_DAPM_SINGLE("DMICL2ADC Capture Switch", ALC5632_DIGI_BOOST_CTRL, 7, 1, 1), +}; +static const struct snd_kcontrol_new alc5632_dmicr_mixer_controls[] = { +SOC_DAPM_SINGLE("DMICR2ADC Capture Switch", ALC5632_DIGI_BOOST_CTRL, 6, 1, 1), +}; + +static const char * const alc5632_spk_n_sour_sel[] = { + "RN/-R", "RP/+R", "LN/-R", "Mute"}; +static const char * const alc5632_hpl_out_input_sel[] = { + "Vmid", "HP Left Mix"}; +static const char * const alc5632_hpr_out_input_sel[] = { + "Vmid", "HP Right Mix"}; +static const char * const alc5632_spkout_input_sel[] = { + "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"}; +static const char * const alc5632_aux_out_input_sel[] = { + "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"}; +static const char * const alc5632_adcr_func_sel[] = { + "Stereo ADC", "Voice ADC"}; +static const char * const alc5632_i2s_out_sel[] = { + "ADC LR", "Voice Stereo Digital"}; + +/* auxout output mux */ +static SOC_ENUM_SINGLE_DECL(alc5632_aux_out_input_enum, + ALC5632_OUTPUT_MIXER_CTRL, 6, + alc5632_aux_out_input_sel); +static const struct snd_kcontrol_new alc5632_auxout_mux_controls = +SOC_DAPM_ENUM("AuxOut Mux", alc5632_aux_out_input_enum); + +/* speaker output mux */ +static SOC_ENUM_SINGLE_DECL(alc5632_spkout_input_enum, + ALC5632_OUTPUT_MIXER_CTRL, 10, + alc5632_spkout_input_sel); +static const struct snd_kcontrol_new alc5632_spkout_mux_controls = +SOC_DAPM_ENUM("SpeakerOut Mux", alc5632_spkout_input_enum); + +/* headphone left output mux */ +static SOC_ENUM_SINGLE_DECL(alc5632_hpl_out_input_enum, + ALC5632_OUTPUT_MIXER_CTRL, 9, + alc5632_hpl_out_input_sel); +static const struct snd_kcontrol_new alc5632_hpl_out_mux_controls = +SOC_DAPM_ENUM("Left Headphone Mux", alc5632_hpl_out_input_enum); + +/* headphone right output mux */ +static SOC_ENUM_SINGLE_DECL(alc5632_hpr_out_input_enum, + ALC5632_OUTPUT_MIXER_CTRL, 8, + alc5632_hpr_out_input_sel); +static const struct snd_kcontrol_new alc5632_hpr_out_mux_controls = +SOC_DAPM_ENUM("Right Headphone Mux", alc5632_hpr_out_input_enum); + +/* speaker output N select */ +static SOC_ENUM_SINGLE_DECL(alc5632_spk_n_sour_enum, + ALC5632_OUTPUT_MIXER_CTRL, 14, + alc5632_spk_n_sour_sel); +static const struct snd_kcontrol_new alc5632_spkoutn_mux_controls = +SOC_DAPM_ENUM("SpeakerOut N Mux", alc5632_spk_n_sour_enum); + +/* speaker amplifier */ +static const char *alc5632_amp_names[] = {"AB Amp", "D Amp"}; +static SOC_ENUM_SINGLE_DECL(alc5632_amp_enum, + ALC5632_OUTPUT_MIXER_CTRL, 13, + alc5632_amp_names); +static const struct snd_kcontrol_new alc5632_amp_mux_controls = + SOC_DAPM_ENUM("AB-D Amp Mux", alc5632_amp_enum); + +/* ADC output select */ +static SOC_ENUM_SINGLE_DECL(alc5632_adcr_func_enum, + ALC5632_DAC_FUNC_SELECT, 5, + alc5632_adcr_func_sel); +static const struct snd_kcontrol_new alc5632_adcr_func_controls = + SOC_DAPM_ENUM("ADCR Mux", alc5632_adcr_func_enum); + +/* I2S out select */ +static SOC_ENUM_SINGLE_DECL(alc5632_i2s_out_enum, + ALC5632_I2S_OUT_CTL, 5, + alc5632_i2s_out_sel); +static const struct snd_kcontrol_new alc5632_i2s_out_controls = + SOC_DAPM_ENUM("I2SOut Mux", alc5632_i2s_out_enum); + +static const struct snd_soc_dapm_widget alc5632_dapm_widgets[] = { +/* Muxes */ +SND_SOC_DAPM_MUX("AuxOut Mux", SND_SOC_NOPM, 0, 0, + &alc5632_auxout_mux_controls), +SND_SOC_DAPM_MUX("SpeakerOut Mux", SND_SOC_NOPM, 0, 0, + &alc5632_spkout_mux_controls), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, + &alc5632_hpl_out_mux_controls), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, + &alc5632_hpr_out_mux_controls), +SND_SOC_DAPM_MUX("SpeakerOut N Mux", SND_SOC_NOPM, 0, 0, + &alc5632_spkoutn_mux_controls), +SND_SOC_DAPM_MUX("ADCR Mux", SND_SOC_NOPM, 0, 0, + &alc5632_adcr_func_controls), +SND_SOC_DAPM_MUX("I2SOut Mux", ALC5632_PWR_MANAG_ADD1, 11, 0, + &alc5632_i2s_out_controls), + +/* output mixers */ +SND_SOC_DAPM_MIXER("HP Mix", SND_SOC_NOPM, 0, 0, + &alc5632_hp_mixer_controls[0], + ARRAY_SIZE(alc5632_hp_mixer_controls)), +SND_SOC_DAPM_MIXER("HPR Mix", ALC5632_PWR_MANAG_ADD2, 4, 0, + &alc5632_hpr_mixer_controls[0], + ARRAY_SIZE(alc5632_hpr_mixer_controls)), +SND_SOC_DAPM_MIXER("HPL Mix", ALC5632_PWR_MANAG_ADD2, 5, 0, + &alc5632_hpl_mixer_controls[0], + ARRAY_SIZE(alc5632_hpl_mixer_controls)), +SND_SOC_DAPM_MIXER("HPOut Mix", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Mono Mix", ALC5632_PWR_MANAG_ADD2, 2, 0, + &alc5632_mono_mixer_controls[0], + ARRAY_SIZE(alc5632_mono_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mix", ALC5632_PWR_MANAG_ADD2, 3, 0, + &alc5632_speaker_mixer_controls[0], + ARRAY_SIZE(alc5632_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("DMICL Mix", SND_SOC_NOPM, 0, 0, + &alc5632_dmicl_mixer_controls[0], + ARRAY_SIZE(alc5632_dmicl_mixer_controls)), +SND_SOC_DAPM_MIXER("DMICR Mix", SND_SOC_NOPM, 0, 0, + &alc5632_dmicr_mixer_controls[0], + ARRAY_SIZE(alc5632_dmicr_mixer_controls)), + +/* input mixers */ +SND_SOC_DAPM_MIXER("Left Capture Mix", ALC5632_PWR_MANAG_ADD2, 1, 0, + &alc5632_captureL_mixer_controls[0], + ARRAY_SIZE(alc5632_captureL_mixer_controls)), +SND_SOC_DAPM_MIXER("Right Capture Mix", ALC5632_PWR_MANAG_ADD2, 0, 0, + &alc5632_captureR_mixer_controls[0], + ARRAY_SIZE(alc5632_captureR_mixer_controls)), + +SND_SOC_DAPM_AIF_IN("AIFRXL", "Left HiFi Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFRXR", "Right HiFi Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("VAIFRX", "Voice Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("VAIFTX", "Voice Capture", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_DAC("Voice DAC", NULL, ALC5632_PWR_MANAG_ADD2, 10, 0), +SND_SOC_DAPM_DAC("Left DAC", NULL, ALC5632_PWR_MANAG_ADD2, 9, 0), +SND_SOC_DAPM_DAC("Right DAC", NULL, ALC5632_PWR_MANAG_ADD2, 8, 0), +SND_SOC_DAPM_ADC("Left ADC", NULL, ALC5632_PWR_MANAG_ADD2, 7, 0), +SND_SOC_DAPM_ADC("Right ADC", NULL, ALC5632_PWR_MANAG_ADD2, 6, 0), + +SND_SOC_DAPM_MIXER("DAC Left Channel", ALC5632_PWR_MANAG_ADD1, 15, 0, NULL, 0), +SND_SOC_DAPM_MIXER("DAC Right Channel", + ALC5632_PWR_MANAG_ADD1, 14, 0, NULL, 0), +SND_SOC_DAPM_MIXER("I2S Mix", ALC5632_PWR_MANAG_ADD1, 11, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Phone Mix", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Line Mix", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Voice Mix", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("ADCLR", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Headphone", ALC5632_PWR_MANAG_ADD3, 11, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Headphone", ALC5632_PWR_MANAG_ADD3, 10, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker", ALC5632_PWR_MANAG_ADD3, 13, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker", ALC5632_PWR_MANAG_ADD3, 12, 0, NULL, 0), +SND_SOC_DAPM_PGA("Aux Out", ALC5632_PWR_MANAG_ADD3, 14, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left LineIn", ALC5632_PWR_MANAG_ADD3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right LineIn", ALC5632_PWR_MANAG_ADD3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Phone", ALC5632_PWR_MANAG_ADD3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Phone ADMix", ALC5632_PWR_MANAG_ADD3, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC1 PGA", ALC5632_PWR_MANAG_ADD3, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC2 PGA", ALC5632_PWR_MANAG_ADD3, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC1 Pre Amp", ALC5632_PWR_MANAG_ADD3, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("MIC2 Pre Amp", ALC5632_PWR_MANAG_ADD3, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS1", ALC5632_PWR_MANAG_ADD1, 3, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", ALC5632_PWR_MANAG_ADD1, 2, 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("D Amp", ALC5632_PWR_MANAG_ADD2, 14, 0, NULL, 0, + amp_mixer_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA("AB Amp", ALC5632_PWR_MANAG_ADD2, 15, 0, NULL, 0), +SND_SOC_DAPM_MUX("AB-D Amp Mux", ALC5632_PWR_MANAG_ADD1, 10, 0, + &alc5632_amp_mux_controls), + +SND_SOC_DAPM_OUTPUT("AUXOUT"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("SPKOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), + +SND_SOC_DAPM_INPUT("LINEINL"), +SND_SOC_DAPM_INPUT("LINEINR"), +SND_SOC_DAPM_INPUT("PHONEP"), +SND_SOC_DAPM_INPUT("PHONEN"), +SND_SOC_DAPM_INPUT("DMICDAT"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2"), +SND_SOC_DAPM_VMID("Vmid"), +}; + + +static const struct snd_soc_dapm_route alc5632_dapm_routes[] = { + /* Playback streams */ + {"Left DAC", NULL, "AIFRXL"}, + {"Right DAC", NULL, "AIFRXR"}, + + /* virtual mixer - mixes left & right channels */ + {"I2S Mix", NULL, "Left DAC"}, + {"I2S Mix", NULL, "Right DAC"}, + {"Line Mix", NULL, "Right LineIn"}, + {"Line Mix", NULL, "Left LineIn"}, + {"Phone Mix", NULL, "Phone"}, + {"Phone Mix", NULL, "Phone ADMix"}, + {"AUXOUT", NULL, "Aux Out"}, + + /* DAC */ + {"DAC Right Channel", NULL, "I2S Mix"}, + {"DAC Left Channel", NULL, "I2S Mix"}, + + /* HP mixer */ + {"HPL Mix", "ADC2HP_L Playback Switch", "Left Capture Mix"}, + {"HPL Mix", NULL, "HP Mix"}, + {"HPR Mix", "ADC2HP_R Playback Switch", "Right Capture Mix"}, + {"HPR Mix", NULL, "HP Mix"}, + {"HP Mix", "LI2HP Playback Switch", "Line Mix"}, + {"HP Mix", "PHONE2HP Playback Switch", "Phone Mix"}, + {"HP Mix", "MIC12HP Playback Switch", "MIC1 PGA"}, + {"HP Mix", "MIC22HP Playback Switch", "MIC2 PGA"}, + {"HP Mix", "VOICE2HP Playback Switch", "Voice Mix"}, + {"HPR Mix", "DACR2HP Playback Switch", "DAC Right Channel"}, + {"HPL Mix", "DACL2HP Playback Switch", "DAC Left Channel"}, + {"HPOut Mix", NULL, "HP Mix"}, + {"HPOut Mix", NULL, "HPR Mix"}, + {"HPOut Mix", NULL, "HPL Mix"}, + + /* speaker mixer */ + {"Speaker Mix", "LI2SPK Playback Switch", "Line Mix"}, + {"Speaker Mix", "PHONE2SPK Playback Switch", "Phone Mix"}, + {"Speaker Mix", "MIC12SPK Playback Switch", "MIC1 PGA"}, + {"Speaker Mix", "MIC22SPK Playback Switch", "MIC2 PGA"}, + {"Speaker Mix", "DAC2SPK Playback Switch", "DAC Left Channel"}, + {"Speaker Mix", "VOICE2SPK Playback Switch", "Voice Mix"}, + + /* mono mixer */ + {"Mono Mix", "ADC2MONO_L Playback Switch", "Left Capture Mix"}, + {"Mono Mix", "ADC2MONO_R Playback Switch", "Right Capture Mix"}, + {"Mono Mix", "LI2MONO Playback Switch", "Line Mix"}, + {"Mono Mix", "MIC12MONO Playback Switch", "MIC1 PGA"}, + {"Mono Mix", "MIC22MONO Playback Switch", "MIC2 PGA"}, + {"Mono Mix", "DAC2MONO Playback Switch", "DAC Left Channel"}, + {"Mono Mix", "VOICE2MONO Playback Switch", "Voice Mix"}, + + /* Left record mixer */ + {"Left Capture Mix", "LIL2REC Capture Switch", "LINEINL"}, + {"Left Capture Mix", "PH2REC_L Capture Switch", "PHONEN"}, + {"Left Capture Mix", "MIC12REC_L Capture Switch", "MIC1 Pre Amp"}, + {"Left Capture Mix", "MIC22REC_L Capture Switch", "MIC2 Pre Amp"}, + {"Left Capture Mix", "HPL2REC Capture Switch", "HPL Mix"}, + {"Left Capture Mix", "SPK2REC_L Capture Switch", "Speaker Mix"}, + {"Left Capture Mix", "MONO2REC_L Capture Switch", "Mono Mix"}, + + /*Right record mixer */ + {"Right Capture Mix", "LIR2REC Capture Switch", "LINEINR"}, + {"Right Capture Mix", "PH2REC_R Capture Switch", "PHONEP"}, + {"Right Capture Mix", "MIC12REC_R Capture Switch", "MIC1 Pre Amp"}, + {"Right Capture Mix", "MIC22REC_R Capture Switch", "MIC2 Pre Amp"}, + {"Right Capture Mix", "HPR2REC Capture Switch", "HPR Mix"}, + {"Right Capture Mix", "SPK2REC_R Capture Switch", "Speaker Mix"}, + {"Right Capture Mix", "MONO2REC_R Capture Switch", "Mono Mix"}, + + /* headphone left mux */ + {"Left Headphone Mux", "HP Left Mix", "HPL Mix"}, + {"Left Headphone Mux", "Vmid", "Vmid"}, + + /* headphone right mux */ + {"Right Headphone Mux", "HP Right Mix", "HPR Mix"}, + {"Right Headphone Mux", "Vmid", "Vmid"}, + + /* speaker out mux */ + {"SpeakerOut Mux", "Vmid", "Vmid"}, + {"SpeakerOut Mux", "HPOut Mix", "HPOut Mix"}, + {"SpeakerOut Mux", "Speaker Mix", "Speaker Mix"}, + {"SpeakerOut Mux", "Mono Mix", "Mono Mix"}, + + /* Mono/Aux Out mux */ + {"AuxOut Mux", "Vmid", "Vmid"}, + {"AuxOut Mux", "HPOut Mix", "HPOut Mix"}, + {"AuxOut Mux", "Speaker Mix", "Speaker Mix"}, + {"AuxOut Mux", "Mono Mix", "Mono Mix"}, + + /* output pga */ + {"HPL", NULL, "Left Headphone"}, + {"Left Headphone", NULL, "Left Headphone Mux"}, + {"HPR", NULL, "Right Headphone"}, + {"Right Headphone", NULL, "Right Headphone Mux"}, + {"Aux Out", NULL, "AuxOut Mux"}, + + /* input pga */ + {"Left LineIn", NULL, "LINEINL"}, + {"Right LineIn", NULL, "LINEINR"}, + {"Phone", NULL, "PHONEP"}, + {"MIC1 Pre Amp", NULL, "MIC1"}, + {"MIC2 Pre Amp", NULL, "MIC2"}, + {"MIC1 PGA", NULL, "MIC1 Pre Amp"}, + {"MIC2 PGA", NULL, "MIC2 Pre Amp"}, + + /* left ADC */ + {"Left ADC", NULL, "Left Capture Mix"}, + {"DMICL Mix", "DMICL2ADC Capture Switch", "DMICDAT"}, + {"Left ADC", NULL, "DMICL Mix"}, + {"ADCLR", NULL, "Left ADC"}, + + /* right ADC */ + {"Right ADC", NULL, "Right Capture Mix"}, + {"DMICR Mix", "DMICR2ADC Capture Switch", "DMICDAT"}, + {"Right ADC", NULL, "DMICR Mix"}, + {"ADCR Mux", "Stereo ADC", "Right ADC"}, + {"ADCR Mux", "Voice ADC", "Right ADC"}, + {"ADCLR", NULL, "ADCR Mux"}, + {"VAIFTX", NULL, "ADCR Mux"}, + + /* Digital I2S out */ + {"I2SOut Mux", "ADC LR", "ADCLR"}, + {"I2SOut Mux", "Voice Stereo Digital", "VAIFRX"}, + {"AIFTXL", NULL, "I2SOut Mux"}, + {"AIFTXR", NULL, "I2SOut Mux"}, + + /* Voice Mix */ + {"Voice DAC", NULL, "VAIFRX"}, + {"Voice Mix", NULL, "Voice DAC"}, + + /* Speaker Output */ + {"SpeakerOut N Mux", "RN/-R", "Left Speaker"}, + {"SpeakerOut N Mux", "RP/+R", "Left Speaker"}, + {"SpeakerOut N Mux", "LN/-R", "Left Speaker"}, + {"SpeakerOut N Mux", "Mute", "Vmid"}, + + {"SpeakerOut N Mux", "RN/-R", "Right Speaker"}, + {"SpeakerOut N Mux", "RP/+R", "Right Speaker"}, + {"SpeakerOut N Mux", "LN/-R", "Right Speaker"}, + {"SpeakerOut N Mux", "Mute", "Vmid"}, + + {"AB Amp", NULL, "SpeakerOut Mux"}, + {"D Amp", NULL, "SpeakerOut Mux"}, + {"AB-D Amp Mux", "AB Amp", "AB Amp"}, + {"AB-D Amp Mux", "D Amp", "D Amp"}, + {"Left Speaker", NULL, "AB-D Amp Mux"}, + {"Right Speaker", NULL, "AB-D Amp Mux"}, + + {"SPKOUT", NULL, "Left Speaker"}, + {"SPKOUT", NULL, "Right Speaker"}, + + {"SPKOUTN", NULL, "SpeakerOut N Mux"}, + +}; + +/* PLL divisors */ +struct _pll_div { + u32 pll_in; + u32 pll_out; + u16 regvalue; +}; + +/* Note : pll code from original alc5632 driver. Not sure of how good it is */ +/* useful only for master mode */ +static const struct _pll_div codec_master_pll_div[] = { + + { 2048000, 8192000, 0x0ea0}, + { 3686400, 8192000, 0x4e27}, + { 12000000, 8192000, 0x456b}, + { 13000000, 8192000, 0x495f}, + { 13100000, 8192000, 0x0320}, + { 2048000, 11289600, 0xf637}, + { 3686400, 11289600, 0x2f22}, + { 12000000, 11289600, 0x3e2f}, + { 13000000, 11289600, 0x4d5b}, + { 13100000, 11289600, 0x363b}, + { 2048000, 16384000, 0x1ea0}, + { 3686400, 16384000, 0x9e27}, + { 12000000, 16384000, 0x452b}, + { 13000000, 16384000, 0x542f}, + { 13100000, 16384000, 0x03a0}, + { 2048000, 16934400, 0xe625}, + { 3686400, 16934400, 0x9126}, + { 12000000, 16934400, 0x4d2c}, + { 13000000, 16934400, 0x742f}, + { 13100000, 16934400, 0x3c27}, + { 2048000, 22579200, 0x2aa0}, + { 3686400, 22579200, 0x2f20}, + { 12000000, 22579200, 0x7e2f}, + { 13000000, 22579200, 0x742f}, + { 13100000, 22579200, 0x3c27}, + { 2048000, 24576000, 0x2ea0}, + { 3686400, 24576000, 0xee27}, + { 12000000, 24576000, 0x2915}, + { 13000000, 24576000, 0x772e}, + { 13100000, 24576000, 0x0d20}, +}; + +/* FOUT = MCLK*(N+2)/((M+2)*(K+2)) + N: bit 15:8 (div 2 .. div 257) + K: bit 6:4 typical 2 + M: bit 3:0 (div 2 .. div 17) + + same as for 5623 - thanks! +*/ + +static const struct _pll_div codec_slave_pll_div[] = { + + { 1024000, 16384000, 0x3ea0}, + { 1411200, 22579200, 0x3ea0}, + { 1536000, 24576000, 0x3ea0}, + { 2048000, 16384000, 0x1ea0}, + { 2822400, 22579200, 0x1ea0}, + { 3072000, 24576000, 0x1ea0}, + +}; + +static int alc5632_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + int i; + struct snd_soc_component *component = codec_dai->component; + int gbl_clk = 0, pll_div = 0; + u16 reg; + + if (pll_id < ALC5632_PLL_FR_MCLK || pll_id > ALC5632_PLL_FR_VBCLK) + return -EINVAL; + + /* Disable PLL power */ + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD2, + ALC5632_PWR_ADD2_PLL1, + 0); + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD2, + ALC5632_PWR_ADD2_PLL2, + 0); + + /* pll is not used in slave mode */ + reg = snd_soc_component_read32(component, ALC5632_DAI_CONTROL); + if (reg & ALC5632_DAI_SDP_SLAVE_MODE) + return 0; + + if (!freq_in || !freq_out) + return 0; + + switch (pll_id) { + case ALC5632_PLL_FR_MCLK: + for (i = 0; i < ARRAY_SIZE(codec_master_pll_div); i++) { + if (codec_master_pll_div[i].pll_in == freq_in + && codec_master_pll_div[i].pll_out == freq_out) { + /* PLL source from MCLK */ + pll_div = codec_master_pll_div[i].regvalue; + break; + } + } + break; + case ALC5632_PLL_FR_BCLK: + for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) { + if (codec_slave_pll_div[i].pll_in == freq_in + && codec_slave_pll_div[i].pll_out == freq_out) { + /* PLL source from Bitclk */ + gbl_clk = ALC5632_PLL_FR_BCLK; + pll_div = codec_slave_pll_div[i].regvalue; + break; + } + } + break; + case ALC5632_PLL_FR_VBCLK: + for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) { + if (codec_slave_pll_div[i].pll_in == freq_in + && codec_slave_pll_div[i].pll_out == freq_out) { + /* PLL source from voice clock */ + gbl_clk = ALC5632_PLL_FR_VBCLK; + pll_div = codec_slave_pll_div[i].regvalue; + break; + } + } + break; + default: + return -EINVAL; + } + + if (!pll_div) + return -EINVAL; + + /* choose MCLK/BCLK/VBCLK */ + snd_soc_component_write(component, ALC5632_GPCR2, gbl_clk); + /* choose PLL1 clock rate */ + snd_soc_component_write(component, ALC5632_PLL1_CTRL, pll_div); + /* enable PLL1 */ + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD2, + ALC5632_PWR_ADD2_PLL1, + ALC5632_PWR_ADD2_PLL1); + /* enable PLL2 */ + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD2, + ALC5632_PWR_ADD2_PLL2, + ALC5632_PWR_ADD2_PLL2); + /* use PLL1 as main SYSCLK */ + snd_soc_component_update_bits(component, ALC5632_GPCR1, + ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1, + ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1); + + return 0; +} + +struct _coeff_div { + u16 fs; + u16 regvalue; +}; + +/* codec hifi mclk (after PLL) clock divider coefficients */ +/* values inspired from column BCLK=32Fs of Appendix A table */ +static const struct _coeff_div coeff_div[] = { + {512*1, 0x3075}, +}; + +static int get_coeff(struct snd_soc_component *component, int rate) +{ + struct alc5632_priv *alc5632 = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].fs * rate == alc5632->sysclk) + return i; + } + return -EINVAL; +} + +/* + * Clock after PLL and dividers + */ +static int alc5632_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct alc5632_priv *alc5632 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 4096000: + case 8192000: + case 11289600: + case 12288000: + case 16384000: + case 16934400: + case 18432000: + case 22579200: + case 24576000: + alc5632->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int alc5632_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = ALC5632_DAI_SDP_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface = ALC5632_DAI_SDP_SLAVE_MODE; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= ALC5632_DAI_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= ALC5632_DAI_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= ALC5632_DAI_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= ALC5632_DAI_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + default: + return -EINVAL; + } + + return snd_soc_component_write(component, ALC5632_DAI_CONTROL, iface); +} + +static int alc5632_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int coeff, rate; + u16 iface; + + iface = snd_soc_component_read32(component, ALC5632_DAI_CONTROL); + iface &= ~ALC5632_DAI_I2S_DL_MASK; + + /* bit size */ + switch (params_width(params)) { + case 16: + iface |= ALC5632_DAI_I2S_DL_16; + break; + case 20: + iface |= ALC5632_DAI_I2S_DL_20; + break; + case 24: + iface |= ALC5632_DAI_I2S_DL_24; + break; + default: + return -EINVAL; + } + + /* set iface & srate */ + snd_soc_component_write(component, ALC5632_DAI_CONTROL, iface); + rate = params_rate(params); + coeff = get_coeff(component, rate); + if (coeff < 0) + return -EINVAL; + + coeff = coeff_div[coeff].regvalue; + snd_soc_component_write(component, ALC5632_DAC_CLK_CTRL1, coeff); + + return 0; +} + +static int alc5632_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 hp_mute = ALC5632_MISC_HP_DEPOP_MUTE_L + |ALC5632_MISC_HP_DEPOP_MUTE_R; + u16 mute_reg = snd_soc_component_read32(component, ALC5632_MISC_CTRL) & ~hp_mute; + + if (mute) + mute_reg |= hp_mute; + + return snd_soc_component_write(component, ALC5632_MISC_CTRL, mute_reg); +} + +#define ALC5632_ADD2_POWER_EN (ALC5632_PWR_ADD2_VREF) + +#define ALC5632_ADD3_POWER_EN (ALC5632_PWR_ADD3_MIC1_BOOST_AD) + +#define ALC5632_ADD1_POWER_EN \ + (ALC5632_PWR_ADD1_DAC_REF \ + | ALC5632_PWR_ADD1_SOFTGEN_EN \ + | ALC5632_PWR_ADD1_HP_OUT_AMP \ + | ALC5632_PWR_ADD1_HP_OUT_ENH_AMP \ + | ALC5632_PWR_ADD1_MAIN_BIAS) + +static void enable_power_depop(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD1, + ALC5632_PWR_ADD1_SOFTGEN_EN, + ALC5632_PWR_ADD1_SOFTGEN_EN); + + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD3, + ALC5632_ADD3_POWER_EN, + ALC5632_ADD3_POWER_EN); + + snd_soc_component_update_bits(component, ALC5632_MISC_CTRL, + ALC5632_MISC_HP_DEPOP_MODE2_EN, + ALC5632_MISC_HP_DEPOP_MODE2_EN); + + /* "normal" mode: 0 @ 26 */ + /* set all PR0-7 mixers to 0 */ + snd_soc_component_update_bits(component, ALC5632_PWR_DOWN_CTRL_STATUS, + ALC5632_PWR_DOWN_CTRL_STATUS_MASK, + 0); + + msleep(500); + + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD2, + ALC5632_ADD2_POWER_EN, + ALC5632_ADD2_POWER_EN); + + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD1, + ALC5632_ADD1_POWER_EN, + ALC5632_ADD1_POWER_EN); + + /* disable HP Depop2 */ + snd_soc_component_update_bits(component, ALC5632_MISC_CTRL, + ALC5632_MISC_HP_DEPOP_MODE2_EN, + 0); + +} + +static int alc5632_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + enable_power_depop(component); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD1, + ALC5632_PWR_MANAG_ADD1_MASK, + ALC5632_PWR_ADD1_MAIN_BIAS); + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD2, + ALC5632_PWR_MANAG_ADD2_MASK, + ALC5632_PWR_ADD2_VREF); + /* "normal" mode: 0 @ 26 */ + snd_soc_component_update_bits(component, ALC5632_PWR_DOWN_CTRL_STATUS, + ALC5632_PWR_DOWN_CTRL_STATUS_MASK, + 0xffff ^ (ALC5632_PWR_VREF_PR3 + | ALC5632_PWR_VREF_PR2)); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD2, + ALC5632_PWR_MANAG_ADD2_MASK, 0); + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD3, + ALC5632_PWR_MANAG_ADD3_MASK, 0); + snd_soc_component_update_bits(component, ALC5632_PWR_MANAG_ADD1, + ALC5632_PWR_MANAG_ADD1_MASK, 0); + break; + } + return 0; +} + +#define ALC5632_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \ + | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops alc5632_dai_ops = { + .hw_params = alc5632_pcm_hw_params, + .digital_mute = alc5632_mute, + .set_fmt = alc5632_set_dai_fmt, + .set_sysclk = alc5632_set_dai_sysclk, + .set_pll = alc5632_set_dai_pll, +}; + +static struct snd_soc_dai_driver alc5632_dai = { + .name = "alc5632-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ALC5632_FORMATS,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ALC5632_FORMATS,}, + + .ops = &alc5632_dai_ops, + .symmetric_rates = 1, +}; + +#ifdef CONFIG_PM +static int alc5632_resume(struct snd_soc_component *component) +{ + struct alc5632_priv *alc5632 = snd_soc_component_get_drvdata(component); + + regcache_sync(alc5632->regmap); + + return 0; +} +#else +#define alc5632_resume NULL +#endif + +static int alc5632_probe(struct snd_soc_component *component) +{ + struct alc5632_priv *alc5632 = snd_soc_component_get_drvdata(component); + + switch (alc5632->id) { + case 0x5c: + snd_soc_add_component_controls(component, alc5632_vol_snd_controls, + ARRAY_SIZE(alc5632_vol_snd_controls)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_device_alc5632 = { + .probe = alc5632_probe, + .resume = alc5632_resume, + .set_bias_level = alc5632_set_bias_level, + .controls = alc5632_snd_controls, + .num_controls = ARRAY_SIZE(alc5632_snd_controls), + .dapm_widgets = alc5632_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(alc5632_dapm_widgets), + .dapm_routes = alc5632_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(alc5632_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config alc5632_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = ALC5632_MAX_REGISTER, + .reg_defaults = alc5632_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(alc5632_reg_defaults), + .volatile_reg = alc5632_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +/* + * alc5632 2 wire address is determined by A1 pin + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static int alc5632_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct alc5632_priv *alc5632; + int ret, ret1, ret2; + unsigned int vid1, vid2; + + alc5632 = devm_kzalloc(&client->dev, + sizeof(struct alc5632_priv), GFP_KERNEL); + if (alc5632 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, alc5632); + + alc5632->regmap = devm_regmap_init_i2c(client, &alc5632_regmap); + if (IS_ERR(alc5632->regmap)) { + ret = PTR_ERR(alc5632->regmap); + dev_err(&client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + ret1 = regmap_read(alc5632->regmap, ALC5632_VENDOR_ID1, &vid1); + ret2 = regmap_read(alc5632->regmap, ALC5632_VENDOR_ID2, &vid2); + if (ret1 != 0 || ret2 != 0) { + dev_err(&client->dev, + "Failed to read chip ID: ret1=%d, ret2=%d\n", ret1, ret2); + return -EIO; + } + + vid2 >>= 8; + + if ((vid1 != 0x10EC) || (vid2 != id->driver_data)) { + dev_err(&client->dev, + "Device is not a ALC5632: VID1=0x%x, VID2=0x%x\n", vid1, vid2); + return -EINVAL; + } + + ret = alc5632_reset(alc5632->regmap); + if (ret < 0) { + dev_err(&client->dev, "Failed to issue reset\n"); + return ret; + } + + alc5632->id = vid2; + switch (alc5632->id) { + case 0x5c: + alc5632_dai.name = "alc5632-hifi"; + break; + default: + return -EINVAL; + } + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_device_alc5632, &alc5632_dai, 1); + + if (ret < 0) { + dev_err(&client->dev, "Failed to register component: %d\n", ret); + return ret; + } + + return ret; +} + +static const struct i2c_device_id alc5632_i2c_table[] = { + {"alc5632", 0x5c}, + {} +}; +MODULE_DEVICE_TABLE(i2c, alc5632_i2c_table); + +static const struct of_device_id alc5632_of_match[] = { + { .compatible = "realtek,alc5632", }, + { } +}; +MODULE_DEVICE_TABLE(of, alc5632_of_match); + +/* i2c codec control layer */ +static struct i2c_driver alc5632_i2c_driver = { + .driver = { + .name = "alc5632", + .of_match_table = of_match_ptr(alc5632_of_match), + }, + .probe = alc5632_i2c_probe, + .id_table = alc5632_i2c_table, +}; + +module_i2c_driver(alc5632_i2c_driver); + +MODULE_DESCRIPTION("ASoC ALC5632 driver"); +MODULE_AUTHOR("Leon Romanovsky "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/alc5632.h b/sound/soc/codecs/alc5632.h new file mode 100644 index 000000000..1b5bda594 --- /dev/null +++ b/sound/soc/codecs/alc5632.h @@ -0,0 +1,252 @@ +/* +* alc5632.h -- ALC5632 ALSA SoC Audio Codec +* +* Copyright (C) 2011 The AC100 Kernel Team +* +* Authors: Leon Romanovsky +* Andrey Danin +* Ilya Petrov +* Marc Dietrich +* +* Based on alc5623.h by Arnaud Patard +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +*/ + +#ifndef _ALC5632_H +#define _ALC5632_H + +#define ALC5632_RESET 0x00 +/* speaker output vol 2 2 */ +/* line output vol 4 2 */ +/* HP output vol 4 0 4 */ +#define ALC5632_SPK_OUT_VOL 0x02 /* spe out vol */ +#define ALC5632_SPK_OUT_VOL_STEP 1.5 +#define ALC5632_HP_OUT_VOL 0x04 /* hp out vol */ +#define ALC5632_AUX_OUT_VOL 0x06 /* aux out vol */ +#define ALC5632_PHONE_IN_VOL 0x08 /* phone in vol */ +#define ALC5632_LINE_IN_VOL 0x0A /* line in vol */ +#define ALC5632_STEREO_DAC_IN_VOL 0x0C /* stereo dac in vol */ +#define ALC5632_MIC_VOL 0x0E /* mic in vol */ +/* stero dac/mic routing */ +#define ALC5632_MIC_ROUTING_CTRL 0x10 +#define ALC5632_MIC_ROUTE_MONOMIX (1 << 0) +#define ALC5632_MIC_ROUTE_SPK (1 << 1) +#define ALC5632_MIC_ROUTE_HP (1 << 2) + +#define ALC5632_ADC_REC_GAIN 0x12 /* rec gain */ +#define ALC5632_ADC_REC_GAIN_RANGE 0x1F1F +#define ALC5632_ADC_REC_GAIN_BASE (-16.5) +#define ALC5632_ADC_REC_GAIN_STEP 1.5 + +#define ALC5632_ADC_REC_MIXER 0x14 /* mixer control */ +#define ALC5632_ADC_REC_MIC1 (1 << 6) +#define ALC5632_ADC_REC_MIC2 (1 << 5) +#define ALC5632_ADC_REC_LINE_IN (1 << 4) +#define ALC5632_ADC_REC_AUX (1 << 3) +#define ALC5632_ADC_REC_HP (1 << 2) +#define ALC5632_ADC_REC_SPK (1 << 1) +#define ALC5632_ADC_REC_MONOMIX (1 << 0) + +#define ALC5632_VOICE_DAC_VOL 0x18 /* voice dac vol */ +#define ALC5632_I2S_OUT_CTL 0x1A /* undocumented reg. found in path scheme */ +/* ALC5632_OUTPUT_MIXER_CTRL : */ +/* same remark as for reg 2 line vs speaker */ +#define ALC5632_OUTPUT_MIXER_CTRL 0x1C /* out mix ctrl */ +#define ALC5632_OUTPUT_MIXER_RP (1 << 14) +#define ALC5632_OUTPUT_MIXER_WEEK (1 << 12) +#define ALC5632_OUTPUT_MIXER_HP (1 << 10) +#define ALC5632_OUTPUT_MIXER_AUX_SPK (2 << 6) +#define ALC5632_OUTPUT_MIXER_AUX_HP_LR (1 << 6) +#define ALC5632_OUTPUT_MIXER_HP_R (1 << 8) +#define ALC5632_OUTPUT_MIXER_HP_L (1 << 9) + +#define ALC5632_MIC_CTRL 0x22 /* mic phone ctrl */ +#define ALC5632_MIC_BOOST_BYPASS 0 +#define ALC5632_MIC_BOOST_20DB 1 +#define ALC5632_MIC_BOOST_30DB 2 +#define ALC5632_MIC_BOOST_40DB 3 + +#define ALC5632_DIGI_BOOST_CTRL 0x24 /* digi mic / bost ctl */ +#define ALC5632_MIC_BOOST_RANGE 7 +#define ALC5632_MIC_BOOST_STEP 6 +#define ALC5632_PWR_DOWN_CTRL_STATUS 0x26 +#define ALC5632_PWR_DOWN_CTRL_STATUS_MASK 0xEF00 +#define ALC5632_PWR_VREF_PR3 (1 << 11) +#define ALC5632_PWR_VREF_PR2 (1 << 10) +#define ALC5632_PWR_VREF_STATUS (1 << 3) +#define ALC5632_PWR_AMIX_STATUS (1 << 2) +#define ALC5632_PWR_DAC_STATUS (1 << 1) +#define ALC5632_PWR_ADC_STATUS (1 << 0) +/* stereo/voice DAC / stereo adc func ctrl */ +#define ALC5632_DAC_FUNC_SELECT 0x2E + +/* Main serial data port ctrl (i2s) */ +#define ALC5632_DAI_CONTROL 0x34 + +#define ALC5632_DAI_SDP_MASTER_MODE (0 << 15) +#define ALC5632_DAI_SDP_SLAVE_MODE (1 << 15) +#define ALC5632_DAI_SADLRCK_MODE (1 << 14) +/* 0:voice, 1:main */ +#define ALC5632_DAI_MAIN_I2S_SYSCLK_SEL (1 << 8) +#define ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL (1 << 7) +/* 0:normal, 1:invert */ +#define ALC5632_DAI_MAIN_I2S_LRCK_INV (1 << 6) +#define ALC5632_DAI_I2S_DL_MASK (3 << 2) +#define ALC5632_DAI_I2S_DL_8 (3 << 2) +#define ALC5632_DAI_I2S_DL_24 (2 << 2) +#define ALC5632_DAI_I2S_DL_20 (1 << 2) +#define ALC5632_DAI_I2S_DL_16 (0 << 2) +#define ALC5632_DAI_I2S_DF_MASK (3 << 0) +#define ALC5632_DAI_I2S_DF_PCM_B (3 << 0) +#define ALC5632_DAI_I2S_DF_PCM_A (2 << 0) +#define ALC5632_DAI_I2S_DF_LEFT (1 << 0) +#define ALC5632_DAI_I2S_DF_I2S (0 << 0) +/* extend serial data port control (VoDAC_i2c/pcm) */ +#define ALC5632_DAI_CONTROL2 0x36 +/* 0:gpio func, 1:voice pcm */ +#define ALC5632_DAI_VOICE_PCM_ENABLE (1 << 15) +/* 0:master, 1:slave */ +#define ALC5632_DAI_VOICE_MODE_SEL (1 << 14) +/* 0:disable, 1:enable */ +#define ALC5632_DAI_HPF_CLK_CTRL (1 << 13) +/* 0:main, 1:voice */ +#define ALC5632_DAI_VOICE_I2S_SYSCLK_SEL (1 << 8) +/* 0:normal, 1:invert */ +#define ALC5632_DAI_VOICE_VBCLK_SYSCLK_SEL (1 << 7) +/* 0:normal, 1:invert */ +#define ALC5632_DAI_VOICE_I2S_LR_INV (1 << 6) +#define ALC5632_DAI_VOICE_DL_MASK (3 << 2) +#define ALC5632_DAI_VOICE_DL_16 (0 << 2) +#define ALC5632_DAI_VOICE_DL_20 (1 << 2) +#define ALC5632_DAI_VOICE_DL_24 (2 << 2) +#define ALC5632_DAI_VOICE_DL_8 (3 << 2) +#define ALC5632_DAI_VOICE_DF_MASK (3 << 0) +#define ALC5632_DAI_VOICE_DF_I2S (0 << 0) +#define ALC5632_DAI_VOICE_DF_LEFT (1 << 0) +#define ALC5632_DAI_VOICE_DF_PCM_A (2 << 0) +#define ALC5632_DAI_VOICE_DF_PCM_B (3 << 0) + +#define ALC5632_PWR_MANAG_ADD1 0x3A +#define ALC5632_PWR_MANAG_ADD1_MASK 0xEFFF +#define ALC5632_PWR_ADD1_DAC_L_EN (1 << 15) +#define ALC5632_PWR_ADD1_DAC_R_EN (1 << 14) +#define ALC5632_PWR_ADD1_ZERO_CROSS (1 << 13) +#define ALC5632_PWR_ADD1_MAIN_I2S_EN (1 << 11) +#define ALC5632_PWR_ADD1_SPK_AMP_EN (1 << 10) +#define ALC5632_PWR_ADD1_HP_OUT_AMP (1 << 9) +#define ALC5632_PWR_ADD1_HP_OUT_ENH_AMP (1 << 8) +#define ALC5632_PWR_ADD1_VOICE_DAC_MIX (1 << 7) +#define ALC5632_PWR_ADD1_SOFTGEN_EN (1 << 6) +#define ALC5632_PWR_ADD1_MIC1_SHORT_CURR (1 << 5) +#define ALC5632_PWR_ADD1_MIC2_SHORT_CURR (1 << 4) +#define ALC5632_PWR_ADD1_MIC1_EN (1 << 3) +#define ALC5632_PWR_ADD1_MIC2_EN (1 << 2) +#define ALC5632_PWR_ADD1_MAIN_BIAS (1 << 1) +#define ALC5632_PWR_ADD1_DAC_REF (1 << 0) + +#define ALC5632_PWR_MANAG_ADD2 0x3C +#define ALC5632_PWR_MANAG_ADD2_MASK 0x7FFF +#define ALC5632_PWR_ADD2_PLL1 (1 << 15) +#define ALC5632_PWR_ADD2_PLL2 (1 << 14) +#define ALC5632_PWR_ADD2_VREF (1 << 13) +#define ALC5632_PWR_ADD2_OVT_DET (1 << 12) +#define ALC5632_PWR_ADD2_VOICE_DAC (1 << 10) +#define ALC5632_PWR_ADD2_L_DAC_CLK (1 << 9) +#define ALC5632_PWR_ADD2_R_DAC_CLK (1 << 8) +#define ALC5632_PWR_ADD2_L_ADC_CLK_GAIN (1 << 7) +#define ALC5632_PWR_ADD2_R_ADC_CLK_GAIN (1 << 6) +#define ALC5632_PWR_ADD2_L_HP_MIXER (1 << 5) +#define ALC5632_PWR_ADD2_R_HP_MIXER (1 << 4) +#define ALC5632_PWR_ADD2_SPK_MIXER (1 << 3) +#define ALC5632_PWR_ADD2_MONO_MIXER (1 << 2) +#define ALC5632_PWR_ADD2_L_ADC_REC_MIXER (1 << 1) +#define ALC5632_PWR_ADD2_R_ADC_REC_MIXER (1 << 0) + +#define ALC5632_PWR_MANAG_ADD3 0x3E +#define ALC5632_PWR_MANAG_ADD3_MASK 0x7CFF +#define ALC5632_PWR_ADD3_AUXOUT_VOL (1 << 14) +#define ALC5632_PWR_ADD3_SPK_L_OUT (1 << 13) +#define ALC5632_PWR_ADD3_SPK_R_OUT (1 << 12) +#define ALC5632_PWR_ADD3_HP_L_OUT_VOL (1 << 11) +#define ALC5632_PWR_ADD3_HP_R_OUT_VOL (1 << 10) +#define ALC5632_PWR_ADD3_LINEIN_L_VOL (1 << 7) +#define ALC5632_PWR_ADD3_LINEIN_R_VOL (1 << 6) +#define ALC5632_PWR_ADD3_AUXIN_VOL (1 << 5) +#define ALC5632_PWR_ADD3_AUXIN_MIX (1 << 4) +#define ALC5632_PWR_ADD3_MIC1_VOL (1 << 3) +#define ALC5632_PWR_ADD3_MIC2_VOL (1 << 2) +#define ALC5632_PWR_ADD3_MIC1_BOOST_AD (1 << 1) +#define ALC5632_PWR_ADD3_MIC2_BOOST_AD (1 << 0) + +#define ALC5632_GPCR1 0x40 +#define ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1 (1 << 15) +#define ALC5632_GPCR1_CLK_SYS_SRC_SEL_MCLK (0 << 15) +#define ALC5632_GPCR1_DAC_HI_FLT_EN (1 << 10) +#define ALC5632_GPCR1_SPK_AMP_CTRL (7 << 1) +#define ALC5632_GPCR1_VDD_100 (5 << 1) +#define ALC5632_GPCR1_VDD_125 (4 << 1) +#define ALC5632_GPCR1_VDD_150 (3 << 1) +#define ALC5632_GPCR1_VDD_175 (2 << 1) +#define ALC5632_GPCR1_VDD_200 (1 << 1) +#define ALC5632_GPCR1_VDD_225 (0 << 1) + +#define ALC5632_GPCR2 0x42 +#define ALC5632_GPCR2_PLL1_SOUR_SEL (3 << 12) +#define ALC5632_PLL_FR_MCLK (0 << 12) +#define ALC5632_PLL_FR_BCLK (2 << 12) +#define ALC5632_PLL_FR_VBCLK (3 << 12) +#define ALC5632_GPCR2_CLK_PLL_PRE_DIV1 (0 << 0) + +#define ALC5632_PLL1_CTRL 0x44 +#define ALC5632_PLL1_CTRL_N_VAL(n) (((n) & 0x0f) << 8) +#define ALC5632_PLL1_M_BYPASS (1 << 7) +#define ALC5632_PLL1_CTRL_K_VAL(k) (((k) & 0x07) << 4) +#define ALC5632_PLL1_CTRL_M_VAL(m) (((m) & 0x0f) << 0) + +#define ALC5632_PLL2_CTRL 0x46 +#define ALC5632_PLL2_EN (1 << 15) +#define ALC5632_PLL2_RATIO (0 << 15) + +#define ALC5632_GPIO_PIN_CONFIG 0x4C +#define ALC5632_GPIO_PIN_POLARITY 0x4E +#define ALC5632_GPIO_PIN_STICKY 0x50 +#define ALC5632_GPIO_PIN_WAKEUP 0x52 +#define ALC5632_GPIO_PIN_STATUS 0x54 +#define ALC5632_GPIO_PIN_SHARING 0x56 +#define ALC5632_OVER_CURR_STATUS 0x58 +#define ALC5632_SOFTVOL_CTRL 0x5A +#define ALC5632_GPIO_OUPUT_PIN_CTRL 0x5C + +#define ALC5632_MISC_CTRL 0x5E +#define ALC5632_MISC_DISABLE_FAST_VREG (1 << 15) +#define ALC5632_MISC_AVC_TRGT_SEL (3 << 12) +#define ALC5632_MISC_AVC_TRGT_RIGHT (1 << 12) +#define ALC5632_MISC_AVC_TRGT_LEFT (2 << 12) +#define ALC5632_MISC_AVC_TRGT_BOTH (3 << 12) +#define ALC5632_MISC_HP_DEPOP_MODE1_EN (1 << 9) +#define ALC5632_MISC_HP_DEPOP_MODE2_EN (1 << 8) +#define ALC5632_MISC_HP_DEPOP_MUTE_L (1 << 7) +#define ALC5632_MISC_HP_DEPOP_MUTE_R (1 << 6) +#define ALC5632_MISC_HP_DEPOP_MUTE (1 << 5) +#define ALC5632_MISC_GPIO_WAKEUP_CTRL (1 << 1) +#define ALC5632_MISC_IRQOUT_INV_CTRL (1 << 0) + +#define ALC5632_DAC_CLK_CTRL1 0x60 +#define ALC5632_DAC_CLK_CTRL2 0x62 +#define ALC5632_DAC_CLK_CTRL2_DIV1_2 (1 << 0) +#define ALC5632_VOICE_DAC_PCM_CLK_CTRL1 0x64 +#define ALC5632_PSEUDO_SPATIAL_CTRL 0x68 +#define ALC5632_HID_CTRL_INDEX 0x6A +#define ALC5632_HID_CTRL_DATA 0x6C +#define ALC5632_EQ_CTRL 0x6E + +/* undocumented */ +#define ALC5632_VENDOR_ID1 0x7C +#define ALC5632_VENDOR_ID2 0x7E + +#define ALC5632_MAX_REGISTER 0x7E + +#endif diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c new file mode 100644 index 000000000..5727ea079 --- /dev/null +++ b/sound/soc/codecs/arizona.c @@ -0,0 +1,2863 @@ +/* + * arizona.c - Wolfson Arizona class device shared support + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arizona.h" + +#define ARIZONA_AIF_BCLK_CTRL 0x00 +#define ARIZONA_AIF_TX_PIN_CTRL 0x01 +#define ARIZONA_AIF_RX_PIN_CTRL 0x02 +#define ARIZONA_AIF_RATE_CTRL 0x03 +#define ARIZONA_AIF_FORMAT 0x04 +#define ARIZONA_AIF_TX_BCLK_RATE 0x05 +#define ARIZONA_AIF_RX_BCLK_RATE 0x06 +#define ARIZONA_AIF_FRAME_CTRL_1 0x07 +#define ARIZONA_AIF_FRAME_CTRL_2 0x08 +#define ARIZONA_AIF_FRAME_CTRL_3 0x09 +#define ARIZONA_AIF_FRAME_CTRL_4 0x0A +#define ARIZONA_AIF_FRAME_CTRL_5 0x0B +#define ARIZONA_AIF_FRAME_CTRL_6 0x0C +#define ARIZONA_AIF_FRAME_CTRL_7 0x0D +#define ARIZONA_AIF_FRAME_CTRL_8 0x0E +#define ARIZONA_AIF_FRAME_CTRL_9 0x0F +#define ARIZONA_AIF_FRAME_CTRL_10 0x10 +#define ARIZONA_AIF_FRAME_CTRL_11 0x11 +#define ARIZONA_AIF_FRAME_CTRL_12 0x12 +#define ARIZONA_AIF_FRAME_CTRL_13 0x13 +#define ARIZONA_AIF_FRAME_CTRL_14 0x14 +#define ARIZONA_AIF_FRAME_CTRL_15 0x15 +#define ARIZONA_AIF_FRAME_CTRL_16 0x16 +#define ARIZONA_AIF_FRAME_CTRL_17 0x17 +#define ARIZONA_AIF_FRAME_CTRL_18 0x18 +#define ARIZONA_AIF_TX_ENABLES 0x19 +#define ARIZONA_AIF_RX_ENABLES 0x1A +#define ARIZONA_AIF_FORCE_WRITE 0x1B + +#define ARIZONA_FLL_VCO_CORNER 141900000 +#define ARIZONA_FLL_MAX_FREF 13500000 +#define ARIZONA_FLL_MIN_FVCO 90000000 +#define ARIZONA_FLL_MAX_FRATIO 16 +#define ARIZONA_FLL_MAX_REFDIV 8 +#define ARIZONA_FLL_MIN_OUTDIV 2 +#define ARIZONA_FLL_MAX_OUTDIV 7 + +#define ARIZONA_FMT_DSP_MODE_A 0 +#define ARIZONA_FMT_DSP_MODE_B 1 +#define ARIZONA_FMT_I2S_MODE 2 +#define ARIZONA_FMT_LEFT_JUSTIFIED_MODE 3 + +#define arizona_fll_err(_fll, fmt, ...) \ + dev_err(_fll->arizona->dev, "FLL%d: " fmt, _fll->id, ##__VA_ARGS__) +#define arizona_fll_warn(_fll, fmt, ...) \ + dev_warn(_fll->arizona->dev, "FLL%d: " fmt, _fll->id, ##__VA_ARGS__) +#define arizona_fll_dbg(_fll, fmt, ...) \ + dev_dbg(_fll->arizona->dev, "FLL%d: " fmt, _fll->id, ##__VA_ARGS__) + +#define arizona_aif_err(_dai, fmt, ...) \ + dev_err(_dai->dev, "AIF%d: " fmt, _dai->id, ##__VA_ARGS__) +#define arizona_aif_warn(_dai, fmt, ...) \ + dev_warn(_dai->dev, "AIF%d: " fmt, _dai->id, ##__VA_ARGS__) +#define arizona_aif_dbg(_dai, fmt, ...) \ + dev_dbg(_dai->dev, "AIF%d: " fmt, _dai->id, ##__VA_ARGS__) + +static int arizona_spk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = snd_soc_component_read32(component, + ARIZONA_INTERRUPT_RAW_STATUS_3); + if (val & ARIZONA_SPK_OVERHEAT_STS) { + dev_crit(arizona->dev, + "Speaker not enabled due to temperature\n"); + return -EBUSY; + } + + regmap_update_bits_async(arizona->regmap, + ARIZONA_OUTPUT_ENABLES_1, + 1 << w->shift, 1 << w->shift); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits_async(arizona->regmap, + ARIZONA_OUTPUT_ENABLES_1, + 1 << w->shift, 0); + break; + default: + break; + } + + return arizona_out_ev(w, kcontrol, event); +} + +static irqreturn_t arizona_thermal_warn(int irq, void *data) +{ + struct arizona *arizona = data; + unsigned int val; + int ret; + + ret = regmap_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_3, + &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read thermal status: %d\n", + ret); + } else if (val & ARIZONA_SPK_OVERHEAT_WARN_STS) { + dev_crit(arizona->dev, "Thermal warning\n"); + } + + return IRQ_HANDLED; +} + +static irqreturn_t arizona_thermal_shutdown(int irq, void *data) +{ + struct arizona *arizona = data; + unsigned int val; + int ret; + + ret = regmap_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_3, + &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read thermal status: %d\n", + ret); + } else if (val & ARIZONA_SPK_OVERHEAT_STS) { + dev_crit(arizona->dev, "Thermal shutdown\n"); + ret = regmap_update_bits(arizona->regmap, + ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT4L_ENA | + ARIZONA_OUT4R_ENA, 0); + if (ret != 0) + dev_crit(arizona->dev, + "Failed to disable speaker outputs: %d\n", + ret); + } + + return IRQ_HANDLED; +} + +static const struct snd_soc_dapm_widget arizona_spkl = + SND_SOC_DAPM_PGA_E("OUT4L", SND_SOC_NOPM, + ARIZONA_OUT4L_ENA_SHIFT, 0, NULL, 0, arizona_spk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD); + +static const struct snd_soc_dapm_widget arizona_spkr = + SND_SOC_DAPM_PGA_E("OUT4R", SND_SOC_NOPM, + ARIZONA_OUT4R_ENA_SHIFT, 0, NULL, 0, arizona_spk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD); + +int arizona_init_spk(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int ret; + + ret = snd_soc_dapm_new_controls(dapm, &arizona_spkl, 1); + if (ret != 0) + return ret; + + switch (arizona->type) { + case WM8997: + case CS47L24: + case WM1831: + break; + default: + ret = snd_soc_dapm_new_controls(dapm, &arizona_spkr, 1); + if (ret != 0) + return ret; + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_spk); + +int arizona_init_spk_irqs(struct arizona *arizona) +{ + int ret; + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_SPK_OVERHEAT_WARN, + "Thermal warning", arizona_thermal_warn, + arizona); + if (ret != 0) + dev_err(arizona->dev, + "Failed to get thermal warning IRQ: %d\n", + ret); + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_SPK_OVERHEAT, + "Thermal shutdown", arizona_thermal_shutdown, + arizona); + if (ret != 0) + dev_err(arizona->dev, + "Failed to get thermal shutdown IRQ: %d\n", + ret); + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_spk_irqs); + +int arizona_free_spk_irqs(struct arizona *arizona) +{ + arizona_free_irq(arizona, ARIZONA_IRQ_SPK_OVERHEAT_WARN, arizona); + arizona_free_irq(arizona, ARIZONA_IRQ_SPK_OVERHEAT, arizona); + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_free_spk_irqs); + +static const struct snd_soc_dapm_route arizona_mono_routes[] = { + { "OUT1R", NULL, "OUT1L" }, + { "OUT2R", NULL, "OUT2L" }, + { "OUT3R", NULL, "OUT3L" }, + { "OUT4R", NULL, "OUT4L" }, + { "OUT5R", NULL, "OUT5L" }, + { "OUT6R", NULL, "OUT6L" }, +}; + +int arizona_init_mono(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int i; + + for (i = 0; i < ARIZONA_MAX_OUTPUT; ++i) { + if (arizona->pdata.out_mono[i]) + snd_soc_dapm_add_routes(dapm, + &arizona_mono_routes[i], 1); + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_mono); + +int arizona_init_gpio(struct snd_soc_component *component) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int i; + + switch (arizona->type) { + case WM5110: + case WM8280: + snd_soc_component_disable_pin(component, + "DRC2 Signal Activity"); + break; + default: + break; + } + + snd_soc_component_disable_pin(component, "DRC1 Signal Activity"); + + for (i = 0; i < ARRAY_SIZE(arizona->pdata.gpio_defaults); i++) { + switch (arizona->pdata.gpio_defaults[i] & ARIZONA_GPN_FN_MASK) { + case ARIZONA_GP_FN_DRC1_SIGNAL_DETECT: + snd_soc_component_enable_pin(component, + "DRC1 Signal Activity"); + break; + case ARIZONA_GP_FN_DRC2_SIGNAL_DETECT: + snd_soc_component_enable_pin(component, + "DRC2 Signal Activity"); + break; + default: + break; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_gpio); + +int arizona_init_common(struct arizona *arizona) +{ + struct arizona_pdata *pdata = &arizona->pdata; + unsigned int val, mask; + int i; + + BLOCKING_INIT_NOTIFIER_HEAD(&arizona->notifier); + + for (i = 0; i < ARIZONA_MAX_OUTPUT; ++i) { + /* Default is 0 so noop with defaults */ + if (pdata->out_mono[i]) + val = ARIZONA_OUT1_MONO; + else + val = 0; + + regmap_update_bits(arizona->regmap, + ARIZONA_OUTPUT_PATH_CONFIG_1L + (i * 8), + ARIZONA_OUT1_MONO, val); + } + + for (i = 0; i < ARIZONA_MAX_PDM_SPK; i++) { + if (pdata->spk_mute[i]) + regmap_update_bits(arizona->regmap, + ARIZONA_PDM_SPK1_CTRL_1 + (i * 2), + ARIZONA_SPK1_MUTE_ENDIAN_MASK | + ARIZONA_SPK1_MUTE_SEQ1_MASK, + pdata->spk_mute[i]); + + if (pdata->spk_fmt[i]) + regmap_update_bits(arizona->regmap, + ARIZONA_PDM_SPK1_CTRL_2 + (i * 2), + ARIZONA_SPK1_FMT_MASK, + pdata->spk_fmt[i]); + } + + for (i = 0; i < ARIZONA_MAX_INPUT; i++) { + /* Default for both is 0 so noop with defaults */ + val = pdata->dmic_ref[i] << ARIZONA_IN1_DMIC_SUP_SHIFT; + if (pdata->inmode[i] & ARIZONA_INMODE_DMIC) + val |= 1 << ARIZONA_IN1_MODE_SHIFT; + + switch (arizona->type) { + case WM8998: + case WM1814: + regmap_update_bits(arizona->regmap, + ARIZONA_ADC_DIGITAL_VOLUME_1L + (i * 8), + ARIZONA_IN1L_SRC_SE_MASK, + (pdata->inmode[i] & ARIZONA_INMODE_SE) + << ARIZONA_IN1L_SRC_SE_SHIFT); + + regmap_update_bits(arizona->regmap, + ARIZONA_ADC_DIGITAL_VOLUME_1R + (i * 8), + ARIZONA_IN1R_SRC_SE_MASK, + (pdata->inmode[i] & ARIZONA_INMODE_SE) + << ARIZONA_IN1R_SRC_SE_SHIFT); + + mask = ARIZONA_IN1_DMIC_SUP_MASK | + ARIZONA_IN1_MODE_MASK; + break; + default: + if (pdata->inmode[i] & ARIZONA_INMODE_SE) + val |= 1 << ARIZONA_IN1_SINGLE_ENDED_SHIFT; + + mask = ARIZONA_IN1_DMIC_SUP_MASK | + ARIZONA_IN1_MODE_MASK | + ARIZONA_IN1_SINGLE_ENDED_MASK; + break; + } + + regmap_update_bits(arizona->regmap, + ARIZONA_IN1L_CONTROL + (i * 8), + mask, val); + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_common); + +int arizona_init_vol_limit(struct arizona *arizona) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(arizona->pdata.out_vol_limit); ++i) { + if (arizona->pdata.out_vol_limit[i]) + regmap_update_bits(arizona->regmap, + ARIZONA_DAC_VOLUME_LIMIT_1L + i * 4, + ARIZONA_OUT1L_VOL_LIM_MASK, + arizona->pdata.out_vol_limit[i]); + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_vol_limit); + +const char * const arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS] = { + "None", + "Tone Generator 1", + "Tone Generator 2", + "Haptics", + "AEC", + "AEC2", + "Mic Mute Mixer", + "Noise Generator", + "IN1L", + "IN1R", + "IN2L", + "IN2R", + "IN3L", + "IN3R", + "IN4L", + "IN4R", + "AIF1RX1", + "AIF1RX2", + "AIF1RX3", + "AIF1RX4", + "AIF1RX5", + "AIF1RX6", + "AIF1RX7", + "AIF1RX8", + "AIF2RX1", + "AIF2RX2", + "AIF2RX3", + "AIF2RX4", + "AIF2RX5", + "AIF2RX6", + "AIF3RX1", + "AIF3RX2", + "SLIMRX1", + "SLIMRX2", + "SLIMRX3", + "SLIMRX4", + "SLIMRX5", + "SLIMRX6", + "SLIMRX7", + "SLIMRX8", + "EQ1", + "EQ2", + "EQ3", + "EQ4", + "DRC1L", + "DRC1R", + "DRC2L", + "DRC2R", + "LHPF1", + "LHPF2", + "LHPF3", + "LHPF4", + "DSP1.1", + "DSP1.2", + "DSP1.3", + "DSP1.4", + "DSP1.5", + "DSP1.6", + "DSP2.1", + "DSP2.2", + "DSP2.3", + "DSP2.4", + "DSP2.5", + "DSP2.6", + "DSP3.1", + "DSP3.2", + "DSP3.3", + "DSP3.4", + "DSP3.5", + "DSP3.6", + "DSP4.1", + "DSP4.2", + "DSP4.3", + "DSP4.4", + "DSP4.5", + "DSP4.6", + "ASRC1L", + "ASRC1R", + "ASRC2L", + "ASRC2R", + "ISRC1INT1", + "ISRC1INT2", + "ISRC1INT3", + "ISRC1INT4", + "ISRC1DEC1", + "ISRC1DEC2", + "ISRC1DEC3", + "ISRC1DEC4", + "ISRC2INT1", + "ISRC2INT2", + "ISRC2INT3", + "ISRC2INT4", + "ISRC2DEC1", + "ISRC2DEC2", + "ISRC2DEC3", + "ISRC2DEC4", + "ISRC3INT1", + "ISRC3INT2", + "ISRC3INT3", + "ISRC3INT4", + "ISRC3DEC1", + "ISRC3DEC2", + "ISRC3DEC3", + "ISRC3DEC4", +}; +EXPORT_SYMBOL_GPL(arizona_mixer_texts); + +unsigned int arizona_mixer_values[ARIZONA_NUM_MIXER_INPUTS] = { + 0x00, /* None */ + 0x04, /* Tone */ + 0x05, + 0x06, /* Haptics */ + 0x08, /* AEC */ + 0x09, /* AEC2 */ + 0x0c, /* Noise mixer */ + 0x0d, /* Comfort noise */ + 0x10, /* IN1L */ + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x20, /* AIF1RX1 */ + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, /* AIF2RX1 */ + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x30, /* AIF3RX1 */ + 0x31, + 0x38, /* SLIMRX1 */ + 0x39, + 0x3a, + 0x3b, + 0x3c, + 0x3d, + 0x3e, + 0x3f, + 0x50, /* EQ1 */ + 0x51, + 0x52, + 0x53, + 0x58, /* DRC1L */ + 0x59, + 0x5a, + 0x5b, + 0x60, /* LHPF1 */ + 0x61, + 0x62, + 0x63, + 0x68, /* DSP1.1 */ + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x70, /* DSP2.1 */ + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x78, /* DSP3.1 */ + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x80, /* DSP4.1 */ + 0x81, + 0x82, + 0x83, + 0x84, + 0x85, + 0x90, /* ASRC1L */ + 0x91, + 0x92, + 0x93, + 0xa0, /* ISRC1INT1 */ + 0xa1, + 0xa2, + 0xa3, + 0xa4, /* ISRC1DEC1 */ + 0xa5, + 0xa6, + 0xa7, + 0xa8, /* ISRC2DEC1 */ + 0xa9, + 0xaa, + 0xab, + 0xac, /* ISRC2INT1 */ + 0xad, + 0xae, + 0xaf, + 0xb0, /* ISRC3DEC1 */ + 0xb1, + 0xb2, + 0xb3, + 0xb4, /* ISRC3INT1 */ + 0xb5, + 0xb6, + 0xb7, +}; +EXPORT_SYMBOL_GPL(arizona_mixer_values); + +const DECLARE_TLV_DB_SCALE(arizona_mixer_tlv, -3200, 100, 0); +EXPORT_SYMBOL_GPL(arizona_mixer_tlv); + +const char * const arizona_sample_rate_text[ARIZONA_SAMPLE_RATE_ENUM_SIZE] = { + "12kHz", "24kHz", "48kHz", "96kHz", "192kHz", + "11.025kHz", "22.05kHz", "44.1kHz", "88.2kHz", "176.4kHz", + "4kHz", "8kHz", "16kHz", "32kHz", +}; +EXPORT_SYMBOL_GPL(arizona_sample_rate_text); + +const unsigned int arizona_sample_rate_val[ARIZONA_SAMPLE_RATE_ENUM_SIZE] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x10, 0x11, 0x12, 0x13, +}; +EXPORT_SYMBOL_GPL(arizona_sample_rate_val); + +const char *arizona_sample_rate_val_to_name(unsigned int rate_val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(arizona_sample_rate_val); ++i) { + if (arizona_sample_rate_val[i] == rate_val) + return arizona_sample_rate_text[i]; + } + + return "Illegal"; +} +EXPORT_SYMBOL_GPL(arizona_sample_rate_val_to_name); + +const char * const arizona_rate_text[ARIZONA_RATE_ENUM_SIZE] = { + "SYNCCLK rate", "8kHz", "16kHz", "ASYNCCLK rate", +}; +EXPORT_SYMBOL_GPL(arizona_rate_text); + +const unsigned int arizona_rate_val[ARIZONA_RATE_ENUM_SIZE] = { + 0, 1, 2, 8, +}; +EXPORT_SYMBOL_GPL(arizona_rate_val); + +const struct soc_enum arizona_isrc_fsh[] = { + SOC_VALUE_ENUM_SINGLE(ARIZONA_ISRC_1_CTRL_1, + ARIZONA_ISRC1_FSH_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_ISRC_2_CTRL_1, + ARIZONA_ISRC2_FSH_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_ISRC_3_CTRL_1, + ARIZONA_ISRC3_FSH_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), +}; +EXPORT_SYMBOL_GPL(arizona_isrc_fsh); + +const struct soc_enum arizona_isrc_fsl[] = { + SOC_VALUE_ENUM_SINGLE(ARIZONA_ISRC_1_CTRL_2, + ARIZONA_ISRC1_FSL_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_ISRC_2_CTRL_2, + ARIZONA_ISRC2_FSL_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_ISRC_3_CTRL_2, + ARIZONA_ISRC3_FSL_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), +}; +EXPORT_SYMBOL_GPL(arizona_isrc_fsl); + +const struct soc_enum arizona_asrc_rate1 = + SOC_VALUE_ENUM_SINGLE(ARIZONA_ASRC_RATE1, + ARIZONA_ASRC_RATE1_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE - 1, + arizona_rate_text, arizona_rate_val); +EXPORT_SYMBOL_GPL(arizona_asrc_rate1); + +static const char * const arizona_vol_ramp_text[] = { + "0ms/6dB", "0.5ms/6dB", "1ms/6dB", "2ms/6dB", "4ms/6dB", "8ms/6dB", + "15ms/6dB", "30ms/6dB", +}; + +SOC_ENUM_SINGLE_DECL(arizona_in_vd_ramp, + ARIZONA_INPUT_VOLUME_RAMP, + ARIZONA_IN_VD_RAMP_SHIFT, + arizona_vol_ramp_text); +EXPORT_SYMBOL_GPL(arizona_in_vd_ramp); + +SOC_ENUM_SINGLE_DECL(arizona_in_vi_ramp, + ARIZONA_INPUT_VOLUME_RAMP, + ARIZONA_IN_VI_RAMP_SHIFT, + arizona_vol_ramp_text); +EXPORT_SYMBOL_GPL(arizona_in_vi_ramp); + +SOC_ENUM_SINGLE_DECL(arizona_out_vd_ramp, + ARIZONA_OUTPUT_VOLUME_RAMP, + ARIZONA_OUT_VD_RAMP_SHIFT, + arizona_vol_ramp_text); +EXPORT_SYMBOL_GPL(arizona_out_vd_ramp); + +SOC_ENUM_SINGLE_DECL(arizona_out_vi_ramp, + ARIZONA_OUTPUT_VOLUME_RAMP, + ARIZONA_OUT_VI_RAMP_SHIFT, + arizona_vol_ramp_text); +EXPORT_SYMBOL_GPL(arizona_out_vi_ramp); + +static const char * const arizona_lhpf_mode_text[] = { + "Low-pass", "High-pass" +}; + +SOC_ENUM_SINGLE_DECL(arizona_lhpf1_mode, + ARIZONA_HPLPF1_1, + ARIZONA_LHPF1_MODE_SHIFT, + arizona_lhpf_mode_text); +EXPORT_SYMBOL_GPL(arizona_lhpf1_mode); + +SOC_ENUM_SINGLE_DECL(arizona_lhpf2_mode, + ARIZONA_HPLPF2_1, + ARIZONA_LHPF2_MODE_SHIFT, + arizona_lhpf_mode_text); +EXPORT_SYMBOL_GPL(arizona_lhpf2_mode); + +SOC_ENUM_SINGLE_DECL(arizona_lhpf3_mode, + ARIZONA_HPLPF3_1, + ARIZONA_LHPF3_MODE_SHIFT, + arizona_lhpf_mode_text); +EXPORT_SYMBOL_GPL(arizona_lhpf3_mode); + +SOC_ENUM_SINGLE_DECL(arizona_lhpf4_mode, + ARIZONA_HPLPF4_1, + ARIZONA_LHPF4_MODE_SHIFT, + arizona_lhpf_mode_text); +EXPORT_SYMBOL_GPL(arizona_lhpf4_mode); + +static const char * const arizona_ng_hold_text[] = { + "30ms", "120ms", "250ms", "500ms", +}; + +SOC_ENUM_SINGLE_DECL(arizona_ng_hold, + ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_HOLD_SHIFT, + arizona_ng_hold_text); +EXPORT_SYMBOL_GPL(arizona_ng_hold); + +static const char * const arizona_in_hpf_cut_text[] = { + "2.5Hz", "5Hz", "10Hz", "20Hz", "40Hz" +}; + +SOC_ENUM_SINGLE_DECL(arizona_in_hpf_cut_enum, + ARIZONA_HPF_CONTROL, + ARIZONA_IN_HPF_CUT_SHIFT, + arizona_in_hpf_cut_text); +EXPORT_SYMBOL_GPL(arizona_in_hpf_cut_enum); + +static const char * const arizona_in_dmic_osr_text[] = { + "1.536MHz", "3.072MHz", "6.144MHz", "768kHz", +}; + +const struct soc_enum arizona_in_dmic_osr[] = { + SOC_ENUM_SINGLE(ARIZONA_IN1L_CONTROL, ARIZONA_IN1_OSR_SHIFT, + ARRAY_SIZE(arizona_in_dmic_osr_text), + arizona_in_dmic_osr_text), + SOC_ENUM_SINGLE(ARIZONA_IN2L_CONTROL, ARIZONA_IN2_OSR_SHIFT, + ARRAY_SIZE(arizona_in_dmic_osr_text), + arizona_in_dmic_osr_text), + SOC_ENUM_SINGLE(ARIZONA_IN3L_CONTROL, ARIZONA_IN3_OSR_SHIFT, + ARRAY_SIZE(arizona_in_dmic_osr_text), + arizona_in_dmic_osr_text), + SOC_ENUM_SINGLE(ARIZONA_IN4L_CONTROL, ARIZONA_IN4_OSR_SHIFT, + ARRAY_SIZE(arizona_in_dmic_osr_text), + arizona_in_dmic_osr_text), +}; +EXPORT_SYMBOL_GPL(arizona_in_dmic_osr); + +static const char * const arizona_anc_input_src_text[] = { + "None", "IN1", "IN2", "IN3", "IN4", +}; + +static const char * const arizona_anc_channel_src_text[] = { + "None", "Left", "Right", "Combine", +}; + +const struct soc_enum arizona_anc_input_src[] = { + SOC_ENUM_SINGLE(ARIZONA_ANC_SRC, + ARIZONA_IN_RXANCL_SEL_SHIFT, + ARRAY_SIZE(arizona_anc_input_src_text), + arizona_anc_input_src_text), + SOC_ENUM_SINGLE(ARIZONA_FCL_ADC_REFORMATTER_CONTROL, + ARIZONA_FCL_MIC_MODE_SEL_SHIFT, + ARRAY_SIZE(arizona_anc_channel_src_text), + arizona_anc_channel_src_text), + SOC_ENUM_SINGLE(ARIZONA_ANC_SRC, + ARIZONA_IN_RXANCR_SEL_SHIFT, + ARRAY_SIZE(arizona_anc_input_src_text), + arizona_anc_input_src_text), + SOC_ENUM_SINGLE(ARIZONA_FCR_ADC_REFORMATTER_CONTROL, + ARIZONA_FCR_MIC_MODE_SEL_SHIFT, + ARRAY_SIZE(arizona_anc_channel_src_text), + arizona_anc_channel_src_text), +}; +EXPORT_SYMBOL_GPL(arizona_anc_input_src); + +static const char * const arizona_anc_ng_texts[] = { + "None", + "Internal", + "External", +}; + +SOC_ENUM_SINGLE_DECL(arizona_anc_ng_enum, SND_SOC_NOPM, 0, + arizona_anc_ng_texts); +EXPORT_SYMBOL_GPL(arizona_anc_ng_enum); + +static const char * const arizona_output_anc_src_text[] = { + "None", "RXANCL", "RXANCR", +}; + +const struct soc_enum arizona_output_anc_src[] = { + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_1L, + ARIZONA_OUT1L_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_1R, + ARIZONA_OUT1R_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_2L, + ARIZONA_OUT2L_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_2R, + ARIZONA_OUT2R_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_3L, + ARIZONA_OUT3L_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_DAC_VOLUME_LIMIT_3R, + ARIZONA_OUT3R_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_4L, + ARIZONA_OUT4L_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_4R, + ARIZONA_OUT4R_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_5L, + ARIZONA_OUT5L_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_5R, + ARIZONA_OUT5R_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_6L, + ARIZONA_OUT6L_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), + SOC_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_6R, + ARIZONA_OUT6R_ANC_SRC_SHIFT, + ARRAY_SIZE(arizona_output_anc_src_text), + arizona_output_anc_src_text), +}; +EXPORT_SYMBOL_GPL(arizona_output_anc_src); + +const struct snd_kcontrol_new arizona_voice_trigger_switch[] = { + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 1, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 2, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 3, 1, 0), +}; +EXPORT_SYMBOL_GPL(arizona_voice_trigger_switch); + +static void arizona_in_set_vu(struct snd_soc_component *component, int ena) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int val; + int i; + + if (ena) + val = ARIZONA_IN_VU; + else + val = 0; + + for (i = 0; i < priv->num_inputs; i++) + snd_soc_component_update_bits(component, + ARIZONA_ADC_DIGITAL_VOLUME_1L + (i * 4), + ARIZONA_IN_VU, val); +} + +bool arizona_input_analog(struct snd_soc_component *component, int shift) +{ + unsigned int reg = ARIZONA_IN1L_CONTROL + ((shift / 2) * 8); + unsigned int val = snd_soc_component_read32(component, reg); + + return !(val & ARIZONA_IN1_MODE_MASK); +} +EXPORT_SYMBOL_GPL(arizona_input_analog); + +int arizona_in_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int reg; + + if (w->shift % 2) + reg = ARIZONA_ADC_DIGITAL_VOLUME_1L + ((w->shift / 2) * 8); + else + reg = ARIZONA_ADC_DIGITAL_VOLUME_1R + ((w->shift / 2) * 8); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + priv->in_pending++; + break; + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, reg, + ARIZONA_IN1L_MUTE, 0); + + /* If this is the last input pending then allow VU */ + priv->in_pending--; + if (priv->in_pending == 0) { + msleep(1); + arizona_in_set_vu(component, 1); + } + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, reg, + ARIZONA_IN1L_MUTE | ARIZONA_IN_VU, + ARIZONA_IN1L_MUTE | ARIZONA_IN_VU); + break; + case SND_SOC_DAPM_POST_PMD: + /* Disable volume updates if no inputs are enabled */ + reg = snd_soc_component_read32(component, ARIZONA_INPUT_ENABLES); + if (reg == 0) + arizona_in_set_vu(component, 0); + break; + default: + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_in_ev); + +int arizona_out_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (w->shift) { + case ARIZONA_OUT1L_ENA_SHIFT: + case ARIZONA_OUT1R_ENA_SHIFT: + case ARIZONA_OUT2L_ENA_SHIFT: + case ARIZONA_OUT2R_ENA_SHIFT: + case ARIZONA_OUT3L_ENA_SHIFT: + case ARIZONA_OUT3R_ENA_SHIFT: + priv->out_up_pending++; + priv->out_up_delay += 17; + break; + case ARIZONA_OUT4L_ENA_SHIFT: + case ARIZONA_OUT4R_ENA_SHIFT: + priv->out_up_pending++; + switch (arizona->type) { + case WM5102: + case WM8997: + break; + default: + priv->out_up_delay += 10; + break; + } + break; + default: + break; + } + break; + case SND_SOC_DAPM_POST_PMU: + switch (w->shift) { + case ARIZONA_OUT1L_ENA_SHIFT: + case ARIZONA_OUT1R_ENA_SHIFT: + case ARIZONA_OUT2L_ENA_SHIFT: + case ARIZONA_OUT2R_ENA_SHIFT: + case ARIZONA_OUT3L_ENA_SHIFT: + case ARIZONA_OUT3R_ENA_SHIFT: + case ARIZONA_OUT4L_ENA_SHIFT: + case ARIZONA_OUT4R_ENA_SHIFT: + priv->out_up_pending--; + if (!priv->out_up_pending && priv->out_up_delay) { + dev_dbg(component->dev, "Power up delay: %d\n", + priv->out_up_delay); + msleep(priv->out_up_delay); + priv->out_up_delay = 0; + } + break; + + default: + break; + } + break; + case SND_SOC_DAPM_PRE_PMD: + switch (w->shift) { + case ARIZONA_OUT1L_ENA_SHIFT: + case ARIZONA_OUT1R_ENA_SHIFT: + case ARIZONA_OUT2L_ENA_SHIFT: + case ARIZONA_OUT2R_ENA_SHIFT: + case ARIZONA_OUT3L_ENA_SHIFT: + case ARIZONA_OUT3R_ENA_SHIFT: + priv->out_down_pending++; + priv->out_down_delay++; + break; + case ARIZONA_OUT4L_ENA_SHIFT: + case ARIZONA_OUT4R_ENA_SHIFT: + priv->out_down_pending++; + switch (arizona->type) { + case WM5102: + case WM8997: + break; + case WM8998: + case WM1814: + priv->out_down_delay += 5; + break; + default: + priv->out_down_delay++; + break; + } + default: + break; + } + break; + case SND_SOC_DAPM_POST_PMD: + switch (w->shift) { + case ARIZONA_OUT1L_ENA_SHIFT: + case ARIZONA_OUT1R_ENA_SHIFT: + case ARIZONA_OUT2L_ENA_SHIFT: + case ARIZONA_OUT2R_ENA_SHIFT: + case ARIZONA_OUT3L_ENA_SHIFT: + case ARIZONA_OUT3R_ENA_SHIFT: + case ARIZONA_OUT4L_ENA_SHIFT: + case ARIZONA_OUT4R_ENA_SHIFT: + priv->out_down_pending--; + if (!priv->out_down_pending && priv->out_down_delay) { + dev_dbg(component->dev, "Power down delay: %d\n", + priv->out_down_delay); + msleep(priv->out_down_delay); + priv->out_down_delay = 0; + } + break; + default: + break; + } + break; + default: + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_out_ev); + +int arizona_hp_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + unsigned int mask = 1 << w->shift; + unsigned int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = mask; + break; + case SND_SOC_DAPM_PRE_PMD: + val = 0; + break; + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_POST_PMD: + return arizona_out_ev(w, kcontrol, event); + default: + return -EINVAL; + } + + /* Store the desired state for the HP outputs */ + priv->arizona->hp_ena &= ~mask; + priv->arizona->hp_ena |= val; + + /* Force off if HPDET clamp is active */ + if (priv->arizona->hpdet_clamp) + val = 0; + + regmap_update_bits_async(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, + mask, val); + + return arizona_out_ev(w, kcontrol, event); +} +EXPORT_SYMBOL_GPL(arizona_hp_ev); + +static int arizona_dvfs_enable(struct snd_soc_component *component) +{ + const struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int ret; + + ret = regulator_set_voltage(arizona->dcvdd, 1800000, 1800000); + if (ret) { + dev_err(component->dev, "Failed to boost DCVDD: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(arizona->regmap, + ARIZONA_DYNAMIC_FREQUENCY_SCALING_1, + ARIZONA_SUBSYS_MAX_FREQ, + ARIZONA_SUBSYS_MAX_FREQ); + if (ret) { + dev_err(component->dev, "Failed to enable subsys max: %d\n", ret); + regulator_set_voltage(arizona->dcvdd, 1200000, 1800000); + return ret; + } + + return 0; +} + +static int arizona_dvfs_disable(struct snd_soc_component *component) +{ + const struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int ret; + + ret = regmap_update_bits(arizona->regmap, + ARIZONA_DYNAMIC_FREQUENCY_SCALING_1, + ARIZONA_SUBSYS_MAX_FREQ, 0); + if (ret) { + dev_err(component->dev, "Failed to disable subsys max: %d\n", ret); + return ret; + } + + ret = regulator_set_voltage(arizona->dcvdd, 1200000, 1800000); + if (ret) { + dev_err(component->dev, "Failed to unboost DCVDD: %d\n", ret); + return ret; + } + + return 0; +} + +int arizona_dvfs_up(struct snd_soc_component *component, unsigned int flags) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + + mutex_lock(&priv->dvfs_lock); + + if (!priv->dvfs_cached && !priv->dvfs_reqs) { + ret = arizona_dvfs_enable(component); + if (ret) + goto err; + } + + priv->dvfs_reqs |= flags; +err: + mutex_unlock(&priv->dvfs_lock); + return ret; +} +EXPORT_SYMBOL_GPL(arizona_dvfs_up); + +int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int old_reqs; + int ret = 0; + + mutex_lock(&priv->dvfs_lock); + + old_reqs = priv->dvfs_reqs; + priv->dvfs_reqs &= ~flags; + + if (!priv->dvfs_cached && old_reqs && !priv->dvfs_reqs) + ret = arizona_dvfs_disable(component); + + mutex_unlock(&priv->dvfs_lock); + return ret; +} +EXPORT_SYMBOL_GPL(arizona_dvfs_down); + +int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + + mutex_lock(&priv->dvfs_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (priv->dvfs_reqs) + ret = arizona_dvfs_enable(component); + + priv->dvfs_cached = false; + break; + case SND_SOC_DAPM_PRE_PMD: + /* We must ensure DVFS is disabled before the codec goes into + * suspend so that we are never in an illegal state of DVFS + * enabled without enough DCVDD + */ + priv->dvfs_cached = true; + + if (priv->dvfs_reqs) + ret = arizona_dvfs_disable(component); + break; + default: + break; + } + + mutex_unlock(&priv->dvfs_lock); + return ret; +} +EXPORT_SYMBOL_GPL(arizona_dvfs_sysclk_ev); + +void arizona_init_dvfs(struct arizona_priv *priv) +{ + mutex_init(&priv->dvfs_lock); +} +EXPORT_SYMBOL_GPL(arizona_init_dvfs); + +int arizona_anc_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = 1 << w->shift; + break; + case SND_SOC_DAPM_PRE_PMD: + val = 1 << (w->shift + 1); + break; + default: + return 0; + } + + snd_soc_component_write(component, ARIZONA_CLOCK_CONTROL, val); + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_anc_ev); + +static unsigned int arizona_opclk_ref_48k_rates[] = { + 6144000, + 12288000, + 24576000, + 49152000, +}; + +static unsigned int arizona_opclk_ref_44k1_rates[] = { + 5644800, + 11289600, + 22579200, + 45158400, +}; + +static int arizona_set_opclk(struct snd_soc_component *component, + unsigned int clk, unsigned int freq) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int reg; + unsigned int *rates; + int ref, div, refclk; + + switch (clk) { + case ARIZONA_CLK_OPCLK: + reg = ARIZONA_OUTPUT_SYSTEM_CLOCK; + refclk = priv->sysclk; + break; + case ARIZONA_CLK_ASYNC_OPCLK: + reg = ARIZONA_OUTPUT_ASYNC_CLOCK; + refclk = priv->asyncclk; + break; + default: + return -EINVAL; + } + + if (refclk % 8000) + rates = arizona_opclk_ref_44k1_rates; + else + rates = arizona_opclk_ref_48k_rates; + + for (ref = 0; ref < ARRAY_SIZE(arizona_opclk_ref_48k_rates) && + rates[ref] <= refclk; ref++) { + div = 1; + while (rates[ref] / div >= freq && div < 32) { + if (rates[ref] / div == freq) { + dev_dbg(component->dev, "Configured %dHz OPCLK\n", + freq); + snd_soc_component_update_bits(component, reg, + ARIZONA_OPCLK_DIV_MASK | + ARIZONA_OPCLK_SEL_MASK, + (div << + ARIZONA_OPCLK_DIV_SHIFT) | + ref); + return 0; + } + div++; + } + } + + dev_err(component->dev, "Unable to generate %dHz OPCLK\n", freq); + return -EINVAL; +} + +int arizona_clk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + unsigned int val; + int clk_idx; + int ret; + + ret = regmap_read(arizona->regmap, w->reg, &val); + if (ret) { + dev_err(component->dev, "Failed to check clock source: %d\n", ret); + return ret; + } + + val = (val & ARIZONA_SYSCLK_SRC_MASK) >> ARIZONA_SYSCLK_SRC_SHIFT; + + switch (val) { + case ARIZONA_CLK_SRC_MCLK1: + clk_idx = ARIZONA_MCLK1; + break; + case ARIZONA_CLK_SRC_MCLK2: + clk_idx = ARIZONA_MCLK2; + break; + default: + return 0; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return clk_prepare_enable(arizona->mclk[clk_idx]); + case SND_SOC_DAPM_POST_PMD: + clk_disable_unprepare(arizona->mclk[clk_idx]); + return 0; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(arizona_clk_ev); + +int arizona_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + char *name; + unsigned int reg; + unsigned int mask = ARIZONA_SYSCLK_FREQ_MASK | ARIZONA_SYSCLK_SRC_MASK; + unsigned int val = source << ARIZONA_SYSCLK_SRC_SHIFT; + int *clk; + + switch (clk_id) { + case ARIZONA_CLK_SYSCLK: + name = "SYSCLK"; + reg = ARIZONA_SYSTEM_CLOCK_1; + clk = &priv->sysclk; + mask |= ARIZONA_SYSCLK_FRAC; + break; + case ARIZONA_CLK_ASYNCCLK: + name = "ASYNCCLK"; + reg = ARIZONA_ASYNC_CLOCK_1; + clk = &priv->asyncclk; + break; + case ARIZONA_CLK_OPCLK: + case ARIZONA_CLK_ASYNC_OPCLK: + return arizona_set_opclk(component, clk_id, freq); + default: + return -EINVAL; + } + + switch (freq) { + case 5644800: + case 6144000: + break; + case 11289600: + case 12288000: + val |= ARIZONA_CLK_12MHZ << ARIZONA_SYSCLK_FREQ_SHIFT; + break; + case 22579200: + case 24576000: + val |= ARIZONA_CLK_24MHZ << ARIZONA_SYSCLK_FREQ_SHIFT; + break; + case 45158400: + case 49152000: + val |= ARIZONA_CLK_49MHZ << ARIZONA_SYSCLK_FREQ_SHIFT; + break; + case 67737600: + case 73728000: + val |= ARIZONA_CLK_73MHZ << ARIZONA_SYSCLK_FREQ_SHIFT; + break; + case 90316800: + case 98304000: + val |= ARIZONA_CLK_98MHZ << ARIZONA_SYSCLK_FREQ_SHIFT; + break; + case 135475200: + case 147456000: + val |= ARIZONA_CLK_147MHZ << ARIZONA_SYSCLK_FREQ_SHIFT; + break; + case 0: + dev_dbg(arizona->dev, "%s cleared\n", name); + *clk = freq; + return 0; + default: + return -EINVAL; + } + + *clk = freq; + + if (freq % 6144000) + val |= ARIZONA_SYSCLK_FRAC; + + dev_dbg(arizona->dev, "%s set to %uHz", name, freq); + + return regmap_update_bits(arizona->regmap, reg, mask, val); +} +EXPORT_SYMBOL_GPL(arizona_set_sysclk); + +static int arizona_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int lrclk, bclk, mode, base; + + base = dai->driver->base; + + lrclk = 0; + bclk = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + mode = ARIZONA_FMT_DSP_MODE_A; + break; + case SND_SOC_DAIFMT_DSP_B: + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) + != SND_SOC_DAIFMT_CBM_CFM) { + arizona_aif_err(dai, "DSP_B not valid in slave mode\n"); + return -EINVAL; + } + mode = ARIZONA_FMT_DSP_MODE_B; + break; + case SND_SOC_DAIFMT_I2S: + mode = ARIZONA_FMT_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) + != SND_SOC_DAIFMT_CBM_CFM) { + arizona_aif_err(dai, "LEFT_J not valid in slave mode\n"); + return -EINVAL; + } + mode = ARIZONA_FMT_LEFT_JUSTIFIED_MODE; + break; + default: + arizona_aif_err(dai, "Unsupported DAI format %d\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + lrclk |= ARIZONA_AIF1TX_LRCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + bclk |= ARIZONA_AIF1_BCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + bclk |= ARIZONA_AIF1_BCLK_MSTR; + lrclk |= ARIZONA_AIF1TX_LRCLK_MSTR; + break; + default: + arizona_aif_err(dai, "Unsupported master mode %d\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + bclk |= ARIZONA_AIF1_BCLK_INV; + lrclk |= ARIZONA_AIF1TX_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + bclk |= ARIZONA_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + lrclk |= ARIZONA_AIF1TX_LRCLK_INV; + break; + default: + return -EINVAL; + } + + regmap_update_bits_async(arizona->regmap, base + ARIZONA_AIF_BCLK_CTRL, + ARIZONA_AIF1_BCLK_INV | + ARIZONA_AIF1_BCLK_MSTR, + bclk); + regmap_update_bits_async(arizona->regmap, base + ARIZONA_AIF_TX_PIN_CTRL, + ARIZONA_AIF1TX_LRCLK_INV | + ARIZONA_AIF1TX_LRCLK_MSTR, lrclk); + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_RX_PIN_CTRL, + ARIZONA_AIF1RX_LRCLK_INV | + ARIZONA_AIF1RX_LRCLK_MSTR, lrclk); + regmap_update_bits(arizona->regmap, base + ARIZONA_AIF_FORMAT, + ARIZONA_AIF1_FMT_MASK, mode); + + return 0; +} + +static const int arizona_48k_bclk_rates[] = { + -1, + 48000, + 64000, + 96000, + 128000, + 192000, + 256000, + 384000, + 512000, + 768000, + 1024000, + 1536000, + 2048000, + 3072000, + 4096000, + 6144000, + 8192000, + 12288000, + 24576000, +}; + +static const int arizona_44k1_bclk_rates[] = { + -1, + 44100, + 58800, + 88200, + 117600, + 177640, + 235200, + 352800, + 470400, + 705600, + 940800, + 1411200, + 1881600, + 2822400, + 3763200, + 5644800, + 7526400, + 11289600, + 22579200, +}; + +static const unsigned int arizona_sr_vals[] = { + 0, + 12000, + 24000, + 48000, + 96000, + 192000, + 384000, + 768000, + 0, + 11025, + 22050, + 44100, + 88200, + 176400, + 352800, + 705600, + 4000, + 8000, + 16000, + 32000, + 64000, + 128000, + 256000, + 512000, +}; + +#define ARIZONA_48K_RATE_MASK 0x0F003E +#define ARIZONA_44K1_RATE_MASK 0x003E00 +#define ARIZONA_RATE_MASK (ARIZONA_48K_RATE_MASK | ARIZONA_44K1_RATE_MASK) + +static const struct snd_pcm_hw_constraint_list arizona_constraint = { + .count = ARRAY_SIZE(arizona_sr_vals), + .list = arizona_sr_vals, +}; + +static int arizona_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona_dai_priv *dai_priv = &priv->dai[dai->id - 1]; + unsigned int base_rate; + + if (!substream->runtime) + return 0; + + switch (dai_priv->clk) { + case ARIZONA_CLK_SYSCLK: + base_rate = priv->sysclk; + break; + case ARIZONA_CLK_ASYNCCLK: + base_rate = priv->asyncclk; + break; + default: + return 0; + } + + if (base_rate == 0) + dai_priv->constraint.mask = ARIZONA_RATE_MASK; + else if (base_rate % 8000) + dai_priv->constraint.mask = ARIZONA_44K1_RATE_MASK; + else + dai_priv->constraint.mask = ARIZONA_48K_RATE_MASK; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &dai_priv->constraint); +} + +static void arizona_wm5102_set_dac_comp(struct snd_soc_component *component, + unsigned int rate) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + struct reg_sequence dac_comp[] = { + { 0x80, 0x3 }, + { ARIZONA_DAC_COMP_1, 0 }, + { ARIZONA_DAC_COMP_2, 0 }, + { 0x80, 0x0 }, + }; + + mutex_lock(&arizona->dac_comp_lock); + + dac_comp[1].def = arizona->dac_comp_coeff; + if (rate >= 176400) + dac_comp[2].def = arizona->dac_comp_enabled; + + mutex_unlock(&arizona->dac_comp_lock); + + regmap_multi_reg_write(arizona->regmap, + dac_comp, + ARRAY_SIZE(dac_comp)); +} + +static int arizona_hw_params_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona_dai_priv *dai_priv = &priv->dai[dai->id - 1]; + int base = dai->driver->base; + int i, sr_val, ret; + + /* + * We will need to be more flexible than this in future, + * currently we use a single sample rate for SYSCLK. + */ + for (i = 0; i < ARRAY_SIZE(arizona_sr_vals); i++) + if (arizona_sr_vals[i] == params_rate(params)) + break; + if (i == ARRAY_SIZE(arizona_sr_vals)) { + arizona_aif_err(dai, "Unsupported sample rate %dHz\n", + params_rate(params)); + return -EINVAL; + } + sr_val = i; + + switch (priv->arizona->type) { + case WM5102: + case WM8997: + if (arizona_sr_vals[sr_val] >= 88200) + ret = arizona_dvfs_up(component, ARIZONA_DVFS_SR1_RQ); + else + ret = arizona_dvfs_down(component, ARIZONA_DVFS_SR1_RQ); + + if (ret) { + arizona_aif_err(dai, "Failed to change DVFS %d\n", ret); + return ret; + } + break; + default: + break; + } + + switch (dai_priv->clk) { + case ARIZONA_CLK_SYSCLK: + switch (priv->arizona->type) { + case WM5102: + arizona_wm5102_set_dac_comp(component, + params_rate(params)); + break; + default: + break; + } + + snd_soc_component_update_bits(component, ARIZONA_SAMPLE_RATE_1, + ARIZONA_SAMPLE_RATE_1_MASK, + sr_val); + if (base) + snd_soc_component_update_bits(component, + base + ARIZONA_AIF_RATE_CTRL, + ARIZONA_AIF1_RATE_MASK, 0); + break; + case ARIZONA_CLK_ASYNCCLK: + snd_soc_component_update_bits(component, + ARIZONA_ASYNC_SAMPLE_RATE_1, + ARIZONA_ASYNC_SAMPLE_RATE_1_MASK, + sr_val); + if (base) + snd_soc_component_update_bits(component, + base + ARIZONA_AIF_RATE_CTRL, + ARIZONA_AIF1_RATE_MASK, + 8 << ARIZONA_AIF1_RATE_SHIFT); + break; + default: + arizona_aif_err(dai, "Invalid clock %d\n", dai_priv->clk); + return -EINVAL; + } + + return 0; +} + +static bool arizona_aif_cfg_changed(struct snd_soc_component *component, + int base, int bclk, int lrclk, int frame) +{ + int val; + + val = snd_soc_component_read32(component, base + ARIZONA_AIF_BCLK_CTRL); + if (bclk != (val & ARIZONA_AIF1_BCLK_FREQ_MASK)) + return true; + + val = snd_soc_component_read32(component, base + ARIZONA_AIF_TX_BCLK_RATE); + if (lrclk != (val & ARIZONA_AIF1TX_BCPF_MASK)) + return true; + + val = snd_soc_component_read32(component, base + ARIZONA_AIF_FRAME_CTRL_1); + if (frame != (val & (ARIZONA_AIF1TX_WL_MASK | + ARIZONA_AIF1TX_SLOT_LEN_MASK))) + return true; + + return false; +} + +static int arizona_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int base = dai->driver->base; + const int *rates; + int i, ret, val; + int channels = params_channels(params); + int chan_limit = arizona->pdata.max_channels_clocked[dai->id - 1]; + int tdm_width = arizona->tdm_width[dai->id - 1]; + int tdm_slots = arizona->tdm_slots[dai->id - 1]; + int bclk, lrclk, wl, frame, bclk_target; + bool reconfig; + unsigned int aif_tx_state, aif_rx_state; + + if (params_rate(params) % 4000) + rates = &arizona_44k1_bclk_rates[0]; + else + rates = &arizona_48k_bclk_rates[0]; + + wl = params_width(params); + + if (tdm_slots) { + arizona_aif_dbg(dai, "Configuring for %d %d bit TDM slots\n", + tdm_slots, tdm_width); + bclk_target = tdm_slots * tdm_width * params_rate(params); + channels = tdm_slots; + } else { + bclk_target = snd_soc_params_to_bclk(params); + tdm_width = wl; + } + + if (chan_limit && chan_limit < channels) { + arizona_aif_dbg(dai, "Limiting to %d channels\n", chan_limit); + bclk_target /= channels; + bclk_target *= chan_limit; + } + + /* Force multiple of 2 channels for I2S mode */ + val = snd_soc_component_read32(component, base + ARIZONA_AIF_FORMAT); + val &= ARIZONA_AIF1_FMT_MASK; + if ((channels & 1) && (val == ARIZONA_FMT_I2S_MODE)) { + arizona_aif_dbg(dai, "Forcing stereo mode\n"); + bclk_target /= channels; + bclk_target *= channels + 1; + } + + for (i = 0; i < ARRAY_SIZE(arizona_44k1_bclk_rates); i++) { + if (rates[i] >= bclk_target && + rates[i] % params_rate(params) == 0) { + bclk = i; + break; + } + } + if (i == ARRAY_SIZE(arizona_44k1_bclk_rates)) { + arizona_aif_err(dai, "Unsupported sample rate %dHz\n", + params_rate(params)); + return -EINVAL; + } + + lrclk = rates[bclk] / params_rate(params); + + arizona_aif_dbg(dai, "BCLK %dHz LRCLK %dHz\n", + rates[bclk], rates[bclk] / lrclk); + + frame = wl << ARIZONA_AIF1TX_WL_SHIFT | tdm_width; + + reconfig = arizona_aif_cfg_changed(component, base, bclk, lrclk, frame); + + if (reconfig) { + /* Save AIF TX/RX state */ + aif_tx_state = snd_soc_component_read32(component, + base + ARIZONA_AIF_TX_ENABLES); + aif_rx_state = snd_soc_component_read32(component, + base + ARIZONA_AIF_RX_ENABLES); + /* Disable AIF TX/RX before reconfiguring it */ + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_TX_ENABLES, + 0xff, 0x0); + regmap_update_bits(arizona->regmap, + base + ARIZONA_AIF_RX_ENABLES, 0xff, 0x0); + } + + ret = arizona_hw_params_rate(substream, params, dai); + if (ret != 0) + goto restore_aif; + + if (reconfig) { + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_BCLK_CTRL, + ARIZONA_AIF1_BCLK_FREQ_MASK, bclk); + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_TX_BCLK_RATE, + ARIZONA_AIF1TX_BCPF_MASK, lrclk); + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_RX_BCLK_RATE, + ARIZONA_AIF1RX_BCPF_MASK, lrclk); + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_FRAME_CTRL_1, + ARIZONA_AIF1TX_WL_MASK | + ARIZONA_AIF1TX_SLOT_LEN_MASK, frame); + regmap_update_bits(arizona->regmap, + base + ARIZONA_AIF_FRAME_CTRL_2, + ARIZONA_AIF1RX_WL_MASK | + ARIZONA_AIF1RX_SLOT_LEN_MASK, frame); + } + +restore_aif: + if (reconfig) { + /* Restore AIF TX/RX state */ + regmap_update_bits_async(arizona->regmap, + base + ARIZONA_AIF_TX_ENABLES, + 0xff, aif_tx_state); + regmap_update_bits(arizona->regmap, + base + ARIZONA_AIF_RX_ENABLES, + 0xff, aif_rx_state); + } + return ret; +} + +static const char *arizona_dai_clk_str(int clk_id) +{ + switch (clk_id) { + case ARIZONA_CLK_SYSCLK: + return "SYSCLK"; + case ARIZONA_CLK_ASYNCCLK: + return "ASYNCCLK"; + default: + return "Unknown clock"; + } +} + +static int arizona_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona_dai_priv *dai_priv = &priv->dai[dai->id - 1]; + struct snd_soc_dapm_route routes[2]; + + switch (clk_id) { + case ARIZONA_CLK_SYSCLK: + case ARIZONA_CLK_ASYNCCLK: + break; + default: + return -EINVAL; + } + + if (clk_id == dai_priv->clk) + return 0; + + if (dai->active) { + dev_err(component->dev, "Can't change clock on active DAI %d\n", + dai->id); + return -EBUSY; + } + + dev_dbg(component->dev, "Setting AIF%d to %s\n", dai->id + 1, + arizona_dai_clk_str(clk_id)); + + memset(&routes, 0, sizeof(routes)); + routes[0].sink = dai->driver->capture.stream_name; + routes[1].sink = dai->driver->playback.stream_name; + + routes[0].source = arizona_dai_clk_str(dai_priv->clk); + routes[1].source = arizona_dai_clk_str(dai_priv->clk); + snd_soc_dapm_del_routes(dapm, routes, ARRAY_SIZE(routes)); + + routes[0].source = arizona_dai_clk_str(clk_id); + routes[1].source = arizona_dai_clk_str(clk_id); + snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes)); + + dai_priv->clk = clk_id; + + return snd_soc_dapm_sync(dapm); +} + +static int arizona_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_component *component = dai->component; + int base = dai->driver->base; + unsigned int reg; + + if (tristate) + reg = ARIZONA_AIF1_TRI; + else + reg = 0; + + return snd_soc_component_update_bits(component, + base + ARIZONA_AIF_RATE_CTRL, + ARIZONA_AIF1_TRI, reg); +} + +static void arizona_set_channels_to_mask(struct snd_soc_dai *dai, + unsigned int base, + int channels, unsigned int mask) +{ + struct snd_soc_component *component = dai->component; + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int slot, i; + + for (i = 0; i < channels; ++i) { + slot = ffs(mask) - 1; + if (slot < 0) + return; + + regmap_write(arizona->regmap, base + i, slot); + + mask &= ~(1 << slot); + } + + if (mask) + arizona_aif_warn(dai, "Too many channels in TDM mask\n"); +} + +static int arizona_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + int base = dai->driver->base; + int rx_max_chan = dai->driver->playback.channels_max; + int tx_max_chan = dai->driver->capture.channels_max; + + /* Only support TDM for the physical AIFs */ + if (dai->id > ARIZONA_MAX_AIF) + return -ENOTSUPP; + + if (slots == 0) { + tx_mask = (1 << tx_max_chan) - 1; + rx_mask = (1 << rx_max_chan) - 1; + } + + arizona_set_channels_to_mask(dai, base + ARIZONA_AIF_FRAME_CTRL_3, + tx_max_chan, tx_mask); + arizona_set_channels_to_mask(dai, base + ARIZONA_AIF_FRAME_CTRL_11, + rx_max_chan, rx_mask); + + arizona->tdm_width[dai->id - 1] = slot_width; + arizona->tdm_slots[dai->id - 1] = slots; + + return 0; +} + +const struct snd_soc_dai_ops arizona_dai_ops = { + .startup = arizona_startup, + .set_fmt = arizona_set_fmt, + .set_tdm_slot = arizona_set_tdm_slot, + .hw_params = arizona_hw_params, + .set_sysclk = arizona_dai_set_sysclk, + .set_tristate = arizona_set_tristate, +}; +EXPORT_SYMBOL_GPL(arizona_dai_ops); + +const struct snd_soc_dai_ops arizona_simple_dai_ops = { + .startup = arizona_startup, + .hw_params = arizona_hw_params_rate, + .set_sysclk = arizona_dai_set_sysclk, +}; +EXPORT_SYMBOL_GPL(arizona_simple_dai_ops); + +int arizona_init_dai(struct arizona_priv *priv, int id) +{ + struct arizona_dai_priv *dai_priv = &priv->dai[id]; + + dai_priv->clk = ARIZONA_CLK_SYSCLK; + dai_priv->constraint = arizona_constraint; + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_dai); + +static struct { + unsigned int min; + unsigned int max; + u16 fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static const unsigned int pseudo_fref_max[ARIZONA_FLL_MAX_FRATIO] = { + 13500000, + 6144000, + 6144000, + 3072000, + 3072000, + 2822400, + 2822400, + 1536000, + 1536000, + 1536000, + 1536000, + 1536000, + 1536000, + 1536000, + 1536000, + 768000, +}; + +static struct { + unsigned int min; + unsigned int max; + u16 gain; +} fll_gains[] = { + { 0, 256000, 0 }, + { 256000, 1000000, 2 }, + { 1000000, 13500000, 4 }, +}; + +struct arizona_fll_cfg { + int n; + unsigned int theta; + unsigned int lambda; + int refdiv; + int outdiv; + int fratio; + int gain; +}; + +static int arizona_validate_fll(struct arizona_fll *fll, + unsigned int Fref, + unsigned int Fout) +{ + unsigned int Fvco_min; + + if (fll->fout && Fout != fll->fout) { + arizona_fll_err(fll, + "Can't change output on active FLL\n"); + return -EINVAL; + } + + if (Fref / ARIZONA_FLL_MAX_REFDIV > ARIZONA_FLL_MAX_FREF) { + arizona_fll_err(fll, + "Can't scale %dMHz in to <=13.5MHz\n", + Fref); + return -EINVAL; + } + + Fvco_min = ARIZONA_FLL_MIN_FVCO * fll->vco_mult; + if (Fout * ARIZONA_FLL_MAX_OUTDIV < Fvco_min) { + arizona_fll_err(fll, "No FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + + return 0; +} + +static int arizona_find_fratio(unsigned int Fref, int *fratio) +{ + int i; + + /* Find an appropriate FLL_FRATIO */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + if (fratio) + *fratio = fll_fratios[i].fratio; + return fll_fratios[i].ratio; + } + } + + return -EINVAL; +} + +static int arizona_calc_fratio(struct arizona_fll *fll, + struct arizona_fll_cfg *cfg, + unsigned int target, + unsigned int Fref, bool sync) +{ + int init_ratio, ratio; + int refdiv, div; + + /* Fref must be <=13.5MHz, find initial refdiv */ + div = 1; + cfg->refdiv = 0; + while (Fref > ARIZONA_FLL_MAX_FREF) { + div *= 2; + Fref /= 2; + cfg->refdiv++; + + if (div > ARIZONA_FLL_MAX_REFDIV) + return -EINVAL; + } + + /* Find an appropriate FLL_FRATIO */ + init_ratio = arizona_find_fratio(Fref, &cfg->fratio); + if (init_ratio < 0) { + arizona_fll_err(fll, "Unable to find FRATIO for Fref=%uHz\n", + Fref); + return init_ratio; + } + + switch (fll->arizona->type) { + case WM5102: + case WM8997: + return init_ratio; + case WM5110: + case WM8280: + if (fll->arizona->rev < 3 || sync) + return init_ratio; + break; + default: + if (sync) + return init_ratio; + break; + } + + cfg->fratio = init_ratio - 1; + + /* Adjust FRATIO/refdiv to avoid integer mode if possible */ + refdiv = cfg->refdiv; + + arizona_fll_dbg(fll, "pseudo: initial ratio=%u fref=%u refdiv=%u\n", + init_ratio, Fref, refdiv); + + while (div <= ARIZONA_FLL_MAX_REFDIV) { + /* start from init_ratio because this may already give a + * fractional N.K + */ + for (ratio = init_ratio; ratio > 0; ratio--) { + if (target % (ratio * Fref)) { + cfg->refdiv = refdiv; + cfg->fratio = ratio - 1; + arizona_fll_dbg(fll, + "pseudo: found fref=%u refdiv=%d(%d) ratio=%d\n", + Fref, refdiv, div, ratio); + return ratio; + } + } + + for (ratio = init_ratio + 1; ratio <= ARIZONA_FLL_MAX_FRATIO; + ratio++) { + if ((ARIZONA_FLL_VCO_CORNER / 2) / + (fll->vco_mult * ratio) < Fref) { + arizona_fll_dbg(fll, "pseudo: hit VCO corner\n"); + break; + } + + if (Fref > pseudo_fref_max[ratio - 1]) { + arizona_fll_dbg(fll, + "pseudo: exceeded max fref(%u) for ratio=%u\n", + pseudo_fref_max[ratio - 1], + ratio); + break; + } + + if (target % (ratio * Fref)) { + cfg->refdiv = refdiv; + cfg->fratio = ratio - 1; + arizona_fll_dbg(fll, + "pseudo: found fref=%u refdiv=%d(%d) ratio=%d\n", + Fref, refdiv, div, ratio); + return ratio; + } + } + + div *= 2; + Fref /= 2; + refdiv++; + init_ratio = arizona_find_fratio(Fref, NULL); + arizona_fll_dbg(fll, + "pseudo: change fref=%u refdiv=%d(%d) ratio=%u\n", + Fref, refdiv, div, init_ratio); + } + + arizona_fll_warn(fll, "Falling back to integer mode operation\n"); + return cfg->fratio + 1; +} + +static int arizona_calc_fll(struct arizona_fll *fll, + struct arizona_fll_cfg *cfg, + unsigned int Fref, bool sync) +{ + unsigned int target, div, gcd_fll; + int i, ratio; + + arizona_fll_dbg(fll, "Fref=%u Fout=%u\n", Fref, fll->fout); + + /* Fvco should be over the targt; don't check the upper bound */ + div = ARIZONA_FLL_MIN_OUTDIV; + while (fll->fout * div < ARIZONA_FLL_MIN_FVCO * fll->vco_mult) { + div++; + if (div > ARIZONA_FLL_MAX_OUTDIV) + return -EINVAL; + } + target = fll->fout * div / fll->vco_mult; + cfg->outdiv = div; + + arizona_fll_dbg(fll, "Fvco=%dHz\n", target); + + /* Find an appropriate FLL_FRATIO and refdiv */ + ratio = arizona_calc_fratio(fll, cfg, target, Fref, sync); + if (ratio < 0) + return ratio; + + /* Apply the division for our remaining calculations */ + Fref = Fref / (1 << cfg->refdiv); + + cfg->n = target / (ratio * Fref); + + if (target % (ratio * Fref)) { + gcd_fll = gcd(target, ratio * Fref); + arizona_fll_dbg(fll, "GCD=%u\n", gcd_fll); + + cfg->theta = (target - (cfg->n * ratio * Fref)) + / gcd_fll; + cfg->lambda = (ratio * Fref) / gcd_fll; + } else { + cfg->theta = 0; + cfg->lambda = 0; + } + + /* Round down to 16bit range with cost of accuracy lost. + * Denominator must be bigger than numerator so we only + * take care of it. + */ + while (cfg->lambda >= (1 << 16)) { + cfg->theta >>= 1; + cfg->lambda >>= 1; + } + + for (i = 0; i < ARRAY_SIZE(fll_gains); i++) { + if (fll_gains[i].min <= Fref && Fref <= fll_gains[i].max) { + cfg->gain = fll_gains[i].gain; + break; + } + } + if (i == ARRAY_SIZE(fll_gains)) { + arizona_fll_err(fll, "Unable to find gain for Fref=%uHz\n", + Fref); + return -EINVAL; + } + + arizona_fll_dbg(fll, "N=%d THETA=%d LAMBDA=%d\n", + cfg->n, cfg->theta, cfg->lambda); + arizona_fll_dbg(fll, "FRATIO=0x%x(%d) OUTDIV=%d REFCLK_DIV=0x%x(%d)\n", + cfg->fratio, ratio, cfg->outdiv, + cfg->refdiv, 1 << cfg->refdiv); + arizona_fll_dbg(fll, "GAIN=0x%x(%d)\n", cfg->gain, 1 << cfg->gain); + + return 0; +} + +static void arizona_apply_fll(struct arizona *arizona, unsigned int base, + struct arizona_fll_cfg *cfg, int source, + bool sync) +{ + regmap_update_bits_async(arizona->regmap, base + 3, + ARIZONA_FLL1_THETA_MASK, cfg->theta); + regmap_update_bits_async(arizona->regmap, base + 4, + ARIZONA_FLL1_LAMBDA_MASK, cfg->lambda); + regmap_update_bits_async(arizona->regmap, base + 5, + ARIZONA_FLL1_FRATIO_MASK, + cfg->fratio << ARIZONA_FLL1_FRATIO_SHIFT); + regmap_update_bits_async(arizona->regmap, base + 6, + ARIZONA_FLL1_CLK_REF_DIV_MASK | + ARIZONA_FLL1_CLK_REF_SRC_MASK, + cfg->refdiv << ARIZONA_FLL1_CLK_REF_DIV_SHIFT | + source << ARIZONA_FLL1_CLK_REF_SRC_SHIFT); + + if (sync) { + regmap_update_bits(arizona->regmap, base + 0x7, + ARIZONA_FLL1_GAIN_MASK, + cfg->gain << ARIZONA_FLL1_GAIN_SHIFT); + } else { + regmap_update_bits(arizona->regmap, base + 0x5, + ARIZONA_FLL1_OUTDIV_MASK, + cfg->outdiv << ARIZONA_FLL1_OUTDIV_SHIFT); + regmap_update_bits(arizona->regmap, base + 0x9, + ARIZONA_FLL1_GAIN_MASK, + cfg->gain << ARIZONA_FLL1_GAIN_SHIFT); + } + + regmap_update_bits_async(arizona->regmap, base + 2, + ARIZONA_FLL1_CTRL_UPD | ARIZONA_FLL1_N_MASK, + ARIZONA_FLL1_CTRL_UPD | cfg->n); +} + +static int arizona_is_enabled_fll(struct arizona_fll *fll, int base) +{ + struct arizona *arizona = fll->arizona; + unsigned int reg; + int ret; + + ret = regmap_read(arizona->regmap, base + 1, ®); + if (ret != 0) { + arizona_fll_err(fll, "Failed to read current state: %d\n", + ret); + return ret; + } + + return reg & ARIZONA_FLL1_ENA; +} + +static int arizona_set_fll_clks(struct arizona_fll *fll, int base, bool ena) +{ + struct arizona *arizona = fll->arizona; + unsigned int val; + struct clk *clk; + int ret; + + ret = regmap_read(arizona->regmap, base + 6, &val); + if (ret != 0) { + arizona_fll_err(fll, "Failed to read current source: %d\n", + ret); + return ret; + } + + val &= ARIZONA_FLL1_CLK_REF_SRC_MASK; + val >>= ARIZONA_FLL1_CLK_REF_SRC_SHIFT; + + switch (val) { + case ARIZONA_FLL_SRC_MCLK1: + clk = arizona->mclk[ARIZONA_MCLK1]; + break; + case ARIZONA_FLL_SRC_MCLK2: + clk = arizona->mclk[ARIZONA_MCLK2]; + break; + default: + return 0; + } + + if (ena) { + return clk_prepare_enable(clk); + } else { + clk_disable_unprepare(clk); + return 0; + } +} + +static int arizona_enable_fll(struct arizona_fll *fll) +{ + struct arizona *arizona = fll->arizona; + bool use_sync = false; + int already_enabled = arizona_is_enabled_fll(fll, fll->base); + int sync_enabled = arizona_is_enabled_fll(fll, fll->base + 0x10); + struct arizona_fll_cfg cfg; + int i; + unsigned int val; + + if (already_enabled < 0) + return already_enabled; + if (sync_enabled < 0) + return sync_enabled; + + if (already_enabled) { + /* Facilitate smooth refclk across the transition */ + regmap_update_bits(fll->arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, ARIZONA_FLL1_FREERUN); + udelay(32); + regmap_update_bits_async(fll->arizona->regmap, fll->base + 0x9, + ARIZONA_FLL1_GAIN_MASK, 0); + + if (arizona_is_enabled_fll(fll, fll->base + 0x10) > 0) + arizona_set_fll_clks(fll, fll->base + 0x10, false); + arizona_set_fll_clks(fll, fll->base, false); + } + + /* + * If we have both REFCLK and SYNCCLK then enable both, + * otherwise apply the SYNCCLK settings to REFCLK. + */ + if (fll->ref_src >= 0 && fll->ref_freq && + fll->ref_src != fll->sync_src) { + arizona_calc_fll(fll, &cfg, fll->ref_freq, false); + + /* Ref path hardcodes lambda to 65536 when sync is on */ + if (fll->sync_src >= 0 && cfg.lambda) + cfg.theta = (cfg.theta * (1 << 16)) / cfg.lambda; + + arizona_apply_fll(arizona, fll->base, &cfg, fll->ref_src, + false); + if (fll->sync_src >= 0) { + arizona_calc_fll(fll, &cfg, fll->sync_freq, true); + + arizona_apply_fll(arizona, fll->base + 0x10, &cfg, + fll->sync_src, true); + use_sync = true; + } + } else if (fll->sync_src >= 0) { + arizona_calc_fll(fll, &cfg, fll->sync_freq, false); + + arizona_apply_fll(arizona, fll->base, &cfg, + fll->sync_src, false); + + regmap_update_bits_async(arizona->regmap, fll->base + 0x11, + ARIZONA_FLL1_SYNC_ENA, 0); + } else { + arizona_fll_err(fll, "No clocks provided\n"); + return -EINVAL; + } + + if (already_enabled && !!sync_enabled != use_sync) + arizona_fll_warn(fll, "Synchroniser changed on active FLL\n"); + + /* + * Increase the bandwidth if we're not using a low frequency + * sync source. + */ + if (use_sync && fll->sync_freq > 100000) + regmap_update_bits_async(arizona->regmap, fll->base + 0x17, + ARIZONA_FLL1_SYNC_BW, 0); + else + regmap_update_bits_async(arizona->regmap, fll->base + 0x17, + ARIZONA_FLL1_SYNC_BW, + ARIZONA_FLL1_SYNC_BW); + + if (!already_enabled) + pm_runtime_get_sync(arizona->dev); + + if (use_sync) { + arizona_set_fll_clks(fll, fll->base + 0x10, true); + regmap_update_bits_async(arizona->regmap, fll->base + 0x11, + ARIZONA_FLL1_SYNC_ENA, + ARIZONA_FLL1_SYNC_ENA); + } + arizona_set_fll_clks(fll, fll->base, true); + regmap_update_bits_async(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_ENA, ARIZONA_FLL1_ENA); + + if (already_enabled) + regmap_update_bits_async(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, 0); + + arizona_fll_dbg(fll, "Waiting for FLL lock...\n"); + val = 0; + for (i = 0; i < 15; i++) { + if (i < 5) + usleep_range(200, 400); + else + msleep(20); + + regmap_read(arizona->regmap, + ARIZONA_INTERRUPT_RAW_STATUS_5, + &val); + if (val & (ARIZONA_FLL1_CLOCK_OK_STS << (fll->id - 1))) + break; + } + if (i == 15) + arizona_fll_warn(fll, "Timed out waiting for lock\n"); + else + arizona_fll_dbg(fll, "FLL locked (%d polls)\n", i); + + return 0; +} + +static void arizona_disable_fll(struct arizona_fll *fll) +{ + struct arizona *arizona = fll->arizona; + bool ref_change, sync_change; + + regmap_update_bits_async(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, ARIZONA_FLL1_FREERUN); + regmap_update_bits_check(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_ENA, 0, &ref_change); + regmap_update_bits_check(arizona->regmap, fll->base + 0x11, + ARIZONA_FLL1_SYNC_ENA, 0, &sync_change); + regmap_update_bits_async(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, 0); + + if (sync_change) + arizona_set_fll_clks(fll, fll->base + 0x10, false); + + if (ref_change) { + arizona_set_fll_clks(fll, fll->base, false); + pm_runtime_put_autosuspend(arizona->dev); + } +} + +int arizona_set_fll_refclk(struct arizona_fll *fll, int source, + unsigned int Fref, unsigned int Fout) +{ + int ret = 0; + + if (fll->ref_src == source && fll->ref_freq == Fref) + return 0; + + if (fll->fout && Fref > 0) { + ret = arizona_validate_fll(fll, Fref, fll->fout); + if (ret != 0) + return ret; + } + + fll->ref_src = source; + fll->ref_freq = Fref; + + if (fll->fout && Fref > 0) + ret = arizona_enable_fll(fll); + + return ret; +} +EXPORT_SYMBOL_GPL(arizona_set_fll_refclk); + +int arizona_set_fll(struct arizona_fll *fll, int source, + unsigned int Fref, unsigned int Fout) +{ + int ret = 0; + + if (fll->sync_src == source && + fll->sync_freq == Fref && fll->fout == Fout) + return 0; + + if (Fout) { + if (fll->ref_src >= 0) { + ret = arizona_validate_fll(fll, fll->ref_freq, Fout); + if (ret != 0) + return ret; + } + + ret = arizona_validate_fll(fll, Fref, Fout); + if (ret != 0) + return ret; + } + + fll->sync_src = source; + fll->sync_freq = Fref; + fll->fout = Fout; + + if (Fout) + ret = arizona_enable_fll(fll); + else + arizona_disable_fll(fll); + + return ret; +} +EXPORT_SYMBOL_GPL(arizona_set_fll); + +int arizona_init_fll(struct arizona *arizona, int id, int base, int lock_irq, + int ok_irq, struct arizona_fll *fll) +{ + unsigned int val; + + fll->id = id; + fll->base = base; + fll->arizona = arizona; + fll->sync_src = ARIZONA_FLL_SRC_NONE; + + /* Configure default refclk to 32kHz if we have one */ + regmap_read(arizona->regmap, ARIZONA_CLOCK_32K_1, &val); + switch (val & ARIZONA_CLK_32K_SRC_MASK) { + case ARIZONA_CLK_SRC_MCLK1: + case ARIZONA_CLK_SRC_MCLK2: + fll->ref_src = val & ARIZONA_CLK_32K_SRC_MASK; + break; + default: + fll->ref_src = ARIZONA_FLL_SRC_NONE; + } + fll->ref_freq = 32768; + + snprintf(fll->lock_name, sizeof(fll->lock_name), "FLL%d lock", id); + snprintf(fll->clock_ok_name, sizeof(fll->clock_ok_name), + "FLL%d clock OK", id); + + regmap_update_bits(arizona->regmap, fll->base + 1, + ARIZONA_FLL1_FREERUN, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_fll); + +/** + * arizona_set_output_mode - Set the mode of the specified output + * + * @component: Device to configure + * @output: Output number + * @diff: True to set the output to differential mode + * + * Some systems use external analogue switches to connect more + * analogue devices to the CODEC than are supported by the device. In + * some systems this requires changing the switched output from single + * ended to differential mode dynamically at runtime, an operation + * supported using this function. + * + * Most systems have a single static configuration and should use + * platform data instead. + */ +int arizona_set_output_mode(struct snd_soc_component *component, int output, + bool diff) +{ + unsigned int reg, val; + + if (output < 1 || output > 6) + return -EINVAL; + + reg = ARIZONA_OUTPUT_PATH_CONFIG_1L + (output - 1) * 8; + + if (diff) + val = ARIZONA_OUT1_MONO; + else + val = 0; + + return snd_soc_component_update_bits(component, reg, + ARIZONA_OUT1_MONO, val); +} +EXPORT_SYMBOL_GPL(arizona_set_output_mode); + +static const struct soc_enum arizona_adsp2_rate_enum[] = { + SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP1_CONTROL_1, + ARIZONA_DSP1_RATE_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP2_CONTROL_1, + ARIZONA_DSP1_RATE_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP3_CONTROL_1, + ARIZONA_DSP1_RATE_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP4_CONTROL_1, + ARIZONA_DSP1_RATE_SHIFT, 0xf, + ARIZONA_RATE_ENUM_SIZE, + arizona_rate_text, arizona_rate_val), +}; + +const struct snd_kcontrol_new arizona_adsp2_rate_controls[] = { + SOC_ENUM("DSP1 Rate", arizona_adsp2_rate_enum[0]), + SOC_ENUM("DSP2 Rate", arizona_adsp2_rate_enum[1]), + SOC_ENUM("DSP3 Rate", arizona_adsp2_rate_enum[2]), + SOC_ENUM("DSP4 Rate", arizona_adsp2_rate_enum[3]), +}; +EXPORT_SYMBOL_GPL(arizona_adsp2_rate_controls); + +static bool arizona_eq_filter_unstable(bool mode, __be16 _a, __be16 _b) +{ + s16 a = be16_to_cpu(_a); + s16 b = be16_to_cpu(_b); + + if (!mode) { + return abs(a) >= 4096; + } else { + if (abs(b) >= 4096) + return true; + + return (abs((a << 16) / (4096 - b)) >= 4096 << 4); + } +} + +int arizona_eq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct soc_bytes *params = (void *)kcontrol->private_value; + unsigned int val; + __be16 *data; + int len; + int ret; + + len = params->num_regs * regmap_get_val_bytes(arizona->regmap); + + data = kmemdup(ucontrol->value.bytes.data, len, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + data[0] &= cpu_to_be16(ARIZONA_EQ1_B1_MODE); + + if (arizona_eq_filter_unstable(!!data[0], data[1], data[2]) || + arizona_eq_filter_unstable(true, data[4], data[5]) || + arizona_eq_filter_unstable(true, data[8], data[9]) || + arizona_eq_filter_unstable(true, data[12], data[13]) || + arizona_eq_filter_unstable(false, data[16], data[17])) { + dev_err(arizona->dev, "Rejecting unstable EQ coefficients\n"); + ret = -EINVAL; + goto out; + } + + ret = regmap_read(arizona->regmap, params->base, &val); + if (ret != 0) + goto out; + + val &= ~ARIZONA_EQ1_B1_MODE; + data[0] |= cpu_to_be16(val); + + ret = regmap_raw_write(arizona->regmap, params->base, data, len); + +out: + kfree(data); + return ret; +} +EXPORT_SYMBOL_GPL(arizona_eq_coeff_put); + +int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + __be16 *data = (__be16 *)ucontrol->value.bytes.data; + s16 val = be16_to_cpu(*data); + + if (abs(val) >= 4096) { + dev_err(arizona->dev, "Rejecting unstable LHPF coefficients\n"); + return -EINVAL; + } + + return snd_soc_bytes_put(kcontrol, ucontrol); +} +EXPORT_SYMBOL_GPL(arizona_lhpf_coeff_put); + +int arizona_of_get_audio_pdata(struct arizona *arizona) +{ + struct arizona_pdata *pdata = &arizona->pdata; + struct device_node *np = arizona->dev->of_node; + struct property *prop; + const __be32 *cur; + u32 val; + u32 pdm_val[ARIZONA_MAX_PDM_SPK]; + int ret; + int count = 0; + + count = 0; + of_property_for_each_u32(np, "wlf,inmode", prop, cur, val) { + if (count == ARRAY_SIZE(pdata->inmode)) + break; + + pdata->inmode[count] = val; + count++; + } + + count = 0; + of_property_for_each_u32(np, "wlf,dmic-ref", prop, cur, val) { + if (count == ARRAY_SIZE(pdata->dmic_ref)) + break; + + pdata->dmic_ref[count] = val; + count++; + } + + count = 0; + of_property_for_each_u32(np, "wlf,out-mono", prop, cur, val) { + if (count == ARRAY_SIZE(pdata->out_mono)) + break; + + pdata->out_mono[count] = !!val; + count++; + } + + count = 0; + of_property_for_each_u32(np, "wlf,max-channels-clocked", prop, cur, val) { + if (count == ARRAY_SIZE(pdata->max_channels_clocked)) + break; + + pdata->max_channels_clocked[count] = val; + count++; + } + + count = 0; + of_property_for_each_u32(np, "wlf,out-volume-limit", prop, cur, val) { + if (count == ARRAY_SIZE(pdata->out_vol_limit)) + break; + + pdata->out_vol_limit[count] = val; + count++; + } + + ret = of_property_read_u32_array(np, "wlf,spk-fmt", + pdm_val, ARRAY_SIZE(pdm_val)); + + if (ret >= 0) + for (count = 0; count < ARRAY_SIZE(pdata->spk_fmt); ++count) + pdata->spk_fmt[count] = pdm_val[count]; + + ret = of_property_read_u32_array(np, "wlf,spk-mute", + pdm_val, ARRAY_SIZE(pdm_val)); + + if (ret >= 0) + for (count = 0; count < ARRAY_SIZE(pdata->spk_mute); ++count) + pdata->spk_mute[count] = pdm_val[count]; + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_of_get_audio_pdata); + +MODULE_DESCRIPTION("ASoC Wolfson Arizona class device support"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h new file mode 100644 index 000000000..e3ccee562 --- /dev/null +++ b/sound/soc/codecs/arizona.h @@ -0,0 +1,357 @@ +/* + * arizona.h - Wolfson Arizona class device shared support + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _ASOC_ARIZONA_H +#define _ASOC_ARIZONA_H + +#include +#include +#include + +#include + +#include "wm_adsp.h" + +#define ARIZONA_CLK_SYSCLK 1 +#define ARIZONA_CLK_ASYNCCLK 2 +#define ARIZONA_CLK_OPCLK 3 +#define ARIZONA_CLK_ASYNC_OPCLK 4 + +#define ARIZONA_CLK_SRC_MCLK1 0x0 +#define ARIZONA_CLK_SRC_MCLK2 0x1 +#define ARIZONA_CLK_SRC_FLL1 0x4 +#define ARIZONA_CLK_SRC_FLL2 0x5 +#define ARIZONA_CLK_SRC_AIF1BCLK 0x8 +#define ARIZONA_CLK_SRC_AIF2BCLK 0x9 +#define ARIZONA_CLK_SRC_AIF3BCLK 0xa + +#define ARIZONA_FLL_SRC_NONE -1 +#define ARIZONA_FLL_SRC_MCLK1 0 +#define ARIZONA_FLL_SRC_MCLK2 1 +#define ARIZONA_FLL_SRC_SLIMCLK 3 +#define ARIZONA_FLL_SRC_FLL1 4 +#define ARIZONA_FLL_SRC_FLL2 5 +#define ARIZONA_FLL_SRC_AIF1BCLK 8 +#define ARIZONA_FLL_SRC_AIF2BCLK 9 +#define ARIZONA_FLL_SRC_AIF3BCLK 10 +#define ARIZONA_FLL_SRC_AIF1LRCLK 12 +#define ARIZONA_FLL_SRC_AIF2LRCLK 13 +#define ARIZONA_FLL_SRC_AIF3LRCLK 14 + +#define ARIZONA_MIXER_VOL_MASK 0x00FE +#define ARIZONA_MIXER_VOL_SHIFT 1 +#define ARIZONA_MIXER_VOL_WIDTH 7 + +#define ARIZONA_CLK_6MHZ 0 +#define ARIZONA_CLK_12MHZ 1 +#define ARIZONA_CLK_24MHZ 2 +#define ARIZONA_CLK_49MHZ 3 +#define ARIZONA_CLK_73MHZ 4 +#define ARIZONA_CLK_98MHZ 5 +#define ARIZONA_CLK_147MHZ 6 + +#define ARIZONA_MAX_DAI 10 +#define ARIZONA_MAX_ADSP 4 + +#define ARIZONA_DVFS_SR1_RQ 0x001 +#define ARIZONA_DVFS_ADSP1_RQ 0x100 + +/* Notifier events */ +#define ARIZONA_NOTIFY_VOICE_TRIGGER 0x1 + +struct wm_adsp; + +struct arizona_dai_priv { + int clk; + + struct snd_pcm_hw_constraint_list constraint; +}; + +struct arizona_priv { + struct wm_adsp adsp[ARIZONA_MAX_ADSP]; + struct arizona *arizona; + int sysclk; + int asyncclk; + struct arizona_dai_priv dai[ARIZONA_MAX_DAI]; + + int num_inputs; + unsigned int in_pending; + + unsigned int out_up_pending; + unsigned int out_up_delay; + unsigned int out_down_pending; + unsigned int out_down_delay; + + unsigned int dvfs_reqs; + struct mutex dvfs_lock; + bool dvfs_cached; +}; + +struct arizona_voice_trigger_info { + int core; +}; + +#define ARIZONA_NUM_MIXER_INPUTS 104 + +extern const unsigned int arizona_mixer_tlv[]; +extern const char * const arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS]; +extern unsigned int arizona_mixer_values[ARIZONA_NUM_MIXER_INPUTS]; + +#define ARIZONA_GAINMUX_CONTROLS(name, base) \ + SOC_SINGLE_RANGE_TLV(name " Input Volume", base + 1, \ + ARIZONA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + arizona_mixer_tlv) + +#define ARIZONA_MIXER_CONTROLS(name, base) \ + SOC_SINGLE_RANGE_TLV(name " Input 1 Volume", base + 1, \ + ARIZONA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + arizona_mixer_tlv), \ + SOC_SINGLE_RANGE_TLV(name " Input 2 Volume", base + 3, \ + ARIZONA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + arizona_mixer_tlv), \ + SOC_SINGLE_RANGE_TLV(name " Input 3 Volume", base + 5, \ + ARIZONA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + arizona_mixer_tlv), \ + SOC_SINGLE_RANGE_TLV(name " Input 4 Volume", base + 7, \ + ARIZONA_MIXER_VOL_SHIFT, 0x20, 0x50, 0, \ + arizona_mixer_tlv) + +#define ARIZONA_MUX_ENUM_DECL(name, reg) \ + SOC_VALUE_ENUM_SINGLE_AUTODISABLE_DECL( \ + name, reg, 0, 0xff, arizona_mixer_texts, arizona_mixer_values) + +#define ARIZONA_MUX_CTL_DECL(name) \ + const struct snd_kcontrol_new name##_mux = \ + SOC_DAPM_ENUM("Route", name##_enum) + +#define ARIZONA_MUX_ENUMS(name, base_reg) \ + static ARIZONA_MUX_ENUM_DECL(name##_enum, base_reg); \ + static ARIZONA_MUX_CTL_DECL(name) + +#define ARIZONA_MIXER_ENUMS(name, base_reg) \ + ARIZONA_MUX_ENUMS(name##_in1, base_reg); \ + ARIZONA_MUX_ENUMS(name##_in2, base_reg + 2); \ + ARIZONA_MUX_ENUMS(name##_in3, base_reg + 4); \ + ARIZONA_MUX_ENUMS(name##_in4, base_reg + 6) + +#define ARIZONA_DSP_AUX_ENUMS(name, base_reg) \ + ARIZONA_MUX_ENUMS(name##_aux1, base_reg); \ + ARIZONA_MUX_ENUMS(name##_aux2, base_reg + 8); \ + ARIZONA_MUX_ENUMS(name##_aux3, base_reg + 16); \ + ARIZONA_MUX_ENUMS(name##_aux4, base_reg + 24); \ + ARIZONA_MUX_ENUMS(name##_aux5, base_reg + 32); \ + ARIZONA_MUX_ENUMS(name##_aux6, base_reg + 40) + +#define ARIZONA_MUX(name, ctrl) \ + SND_SOC_DAPM_MUX(name, SND_SOC_NOPM, 0, 0, ctrl) + +#define ARIZONA_MUX_WIDGETS(name, name_str) \ + ARIZONA_MUX(name_str " Input", &name##_mux) + +#define ARIZONA_MIXER_WIDGETS(name, name_str) \ + ARIZONA_MUX(name_str " Input 1", &name##_in1_mux), \ + ARIZONA_MUX(name_str " Input 2", &name##_in2_mux), \ + ARIZONA_MUX(name_str " Input 3", &name##_in3_mux), \ + ARIZONA_MUX(name_str " Input 4", &name##_in4_mux), \ + SND_SOC_DAPM_MIXER(name_str " Mixer", SND_SOC_NOPM, 0, 0, NULL, 0) + +#define ARIZONA_DSP_WIDGETS(name, name_str) \ + ARIZONA_MIXER_WIDGETS(name##L, name_str "L"), \ + ARIZONA_MIXER_WIDGETS(name##R, name_str "R"), \ + ARIZONA_MUX(name_str " Aux 1", &name##_aux1_mux), \ + ARIZONA_MUX(name_str " Aux 2", &name##_aux2_mux), \ + ARIZONA_MUX(name_str " Aux 3", &name##_aux3_mux), \ + ARIZONA_MUX(name_str " Aux 4", &name##_aux4_mux), \ + ARIZONA_MUX(name_str " Aux 5", &name##_aux5_mux), \ + ARIZONA_MUX(name_str " Aux 6", &name##_aux6_mux) + +#define ARIZONA_MUX_ROUTES(widget, name) \ + { widget, NULL, name " Input" }, \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input") + +#define ARIZONA_MIXER_ROUTES(widget, name) \ + { widget, NULL, name " Mixer" }, \ + { name " Mixer", NULL, name " Input 1" }, \ + { name " Mixer", NULL, name " Input 2" }, \ + { name " Mixer", NULL, name " Input 3" }, \ + { name " Mixer", NULL, name " Input 4" }, \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 1"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 2"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 3"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Input 4") + +#define ARIZONA_DSP_ROUTES(name) \ + { name, NULL, name " Preloader"}, \ + { name " Preloader", NULL, "SYSCLK" }, \ + { name " Preload", NULL, name " Preloader"}, \ + { name, NULL, name " Aux 1" }, \ + { name, NULL, name " Aux 2" }, \ + { name, NULL, name " Aux 3" }, \ + { name, NULL, name " Aux 4" }, \ + { name, NULL, name " Aux 5" }, \ + { name, NULL, name " Aux 6" }, \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 1"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 2"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 3"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 4"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 5"), \ + ARIZONA_MIXER_INPUT_ROUTES(name " Aux 6"), \ + ARIZONA_MIXER_ROUTES(name, name "L"), \ + ARIZONA_MIXER_ROUTES(name, name "R") + +#define ARIZONA_EQ_CONTROL(xname, xbase) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ + .put = arizona_eq_coeff_put, .private_value = \ + ((unsigned long)&(struct soc_bytes) { .base = xbase, \ + .num_regs = 20, .mask = ~ARIZONA_EQ1_B1_MODE }) } + +#define ARIZONA_LHPF_CONTROL(xname, xbase) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \ + .put = arizona_lhpf_coeff_put, .private_value = \ + ((unsigned long)&(struct soc_bytes) { .base = xbase, \ + .num_regs = 1 }) } + +#define ARIZONA_RATE_ENUM_SIZE 4 +#define ARIZONA_SAMPLE_RATE_ENUM_SIZE 14 + +extern const char * const arizona_rate_text[ARIZONA_RATE_ENUM_SIZE]; +extern const unsigned int arizona_rate_val[ARIZONA_RATE_ENUM_SIZE]; +extern const char * const arizona_sample_rate_text[ARIZONA_SAMPLE_RATE_ENUM_SIZE]; +extern const unsigned int arizona_sample_rate_val[ARIZONA_SAMPLE_RATE_ENUM_SIZE]; + +extern const struct soc_enum arizona_isrc_fsl[]; +extern const struct soc_enum arizona_isrc_fsh[]; +extern const struct soc_enum arizona_asrc_rate1; + +extern const struct soc_enum arizona_in_vi_ramp; +extern const struct soc_enum arizona_in_vd_ramp; + +extern const struct soc_enum arizona_out_vi_ramp; +extern const struct soc_enum arizona_out_vd_ramp; + +extern const struct soc_enum arizona_lhpf1_mode; +extern const struct soc_enum arizona_lhpf2_mode; +extern const struct soc_enum arizona_lhpf3_mode; +extern const struct soc_enum arizona_lhpf4_mode; + +extern const struct soc_enum arizona_ng_hold; +extern const struct soc_enum arizona_in_hpf_cut_enum; +extern const struct soc_enum arizona_in_dmic_osr[]; + +extern const struct snd_kcontrol_new arizona_adsp2_rate_controls[]; + +extern const struct soc_enum arizona_anc_input_src[]; +extern const struct soc_enum arizona_anc_ng_enum; +extern const struct soc_enum arizona_output_anc_src[]; + +extern const struct snd_kcontrol_new arizona_voice_trigger_switch[]; + +int arizona_in_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, + int event); +int arizona_out_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, + int event); +int arizona_hp_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, + int event); +int arizona_anc_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, + int event); + +int arizona_eq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +int arizona_clk_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, + int event); +int arizona_set_sysclk(struct snd_soc_component *component, int clk_id, int source, + unsigned int freq, int dir); + +extern const struct snd_soc_dai_ops arizona_dai_ops; +extern const struct snd_soc_dai_ops arizona_simple_dai_ops; + +#define ARIZONA_FLL_NAME_LEN 20 + +struct arizona_fll { + struct arizona *arizona; + int id; + unsigned int base; + unsigned int vco_mult; + + unsigned int fout; + int sync_src; + unsigned int sync_freq; + int ref_src; + unsigned int ref_freq; + + char lock_name[ARIZONA_FLL_NAME_LEN]; + char clock_ok_name[ARIZONA_FLL_NAME_LEN]; +}; + +int arizona_dvfs_up(struct snd_soc_component *component, unsigned int flags); +int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags); +int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +void arizona_init_dvfs(struct arizona_priv *priv); + +int arizona_init_fll(struct arizona *arizona, int id, int base, + int lock_irq, int ok_irq, struct arizona_fll *fll); +int arizona_set_fll_refclk(struct arizona_fll *fll, int source, + unsigned int Fref, unsigned int Fout); +int arizona_set_fll(struct arizona_fll *fll, int source, + unsigned int Fref, unsigned int Fout); + +int arizona_init_spk(struct snd_soc_component *component); +int arizona_init_gpio(struct snd_soc_component *component); +int arizona_init_mono(struct snd_soc_component *component); + +int arizona_init_common(struct arizona *arizona); +int arizona_init_vol_limit(struct arizona *arizona); + +int arizona_init_spk_irqs(struct arizona *arizona); +int arizona_free_spk_irqs(struct arizona *arizona); + +int arizona_init_dai(struct arizona_priv *priv, int dai); + +int arizona_set_output_mode(struct snd_soc_component *component, int output, + bool diff); + +bool arizona_input_analog(struct snd_soc_component *component, int shift); + +const char *arizona_sample_rate_val_to_name(unsigned int rate_val); + +static inline int arizona_register_notifier(struct snd_soc_component *component, + struct notifier_block *nb, + int (*notify) + (struct notifier_block *nb, + unsigned long action, void *data)) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + + nb->notifier_call = notify; + + return blocking_notifier_chain_register(&arizona->notifier, nb); +} + +static inline int arizona_unregister_notifier(struct snd_soc_component *component, + struct notifier_block *nb) +{ + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + + return blocking_notifier_chain_unregister(&arizona->notifier, nb); +} + +int arizona_of_get_audio_pdata(struct arizona *arizona); + +#endif diff --git a/sound/soc/codecs/bd28623.c b/sound/soc/codecs/bd28623.c new file mode 100644 index 000000000..31904ef5c --- /dev/null +++ b/sound/soc/codecs/bd28623.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ROHM BD28623MUV class D speaker amplifier codec driver. +// +// Copyright (c) 2018 Socionext Inc. + +#include +#include +#include +#include +#include +#include +#include + +#define BD28623_NUM_SUPPLIES 3 + +static const char *const bd28623_supply_names[BD28623_NUM_SUPPLIES] = { + "VCCA", + "VCCP1", + "VCCP2", +}; + +struct bd28623_priv { + struct device *dev; + struct regulator_bulk_data supplies[BD28623_NUM_SUPPLIES]; + struct gpio_desc *reset_gpio; + struct gpio_desc *mute_gpio; + + int switch_spk; +}; + +static const struct snd_soc_dapm_widget bd28623_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("OUT1P"), + SND_SOC_DAPM_OUTPUT("OUT1N"), + SND_SOC_DAPM_OUTPUT("OUT2P"), + SND_SOC_DAPM_OUTPUT("OUT2N"), +}; + +static const struct snd_soc_dapm_route bd28623_routes[] = { + { "OUT1P", NULL, "DAC" }, + { "OUT1N", NULL, "DAC" }, + { "OUT2P", NULL, "DAC" }, + { "OUT2N", NULL, "DAC" }, +}; + +static int bd28623_power_on(struct bd28623_priv *bd) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(bd->supplies), bd->supplies); + if (ret) { + dev_err(bd->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(bd->reset_gpio, 0); + usleep_range(300000, 400000); + + return 0; +} + +static void bd28623_power_off(struct bd28623_priv *bd) +{ + gpiod_set_value_cansleep(bd->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(bd->supplies), bd->supplies); +} + +static int bd28623_get_switch_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct bd28623_priv *bd = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = bd->switch_spk; + + return 0; +} + +static int bd28623_set_switch_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct bd28623_priv *bd = snd_soc_component_get_drvdata(component); + + if (bd->switch_spk == ucontrol->value.integer.value[0]) + return 0; + + bd->switch_spk = ucontrol->value.integer.value[0]; + + gpiod_set_value_cansleep(bd->mute_gpio, bd->switch_spk ? 0 : 1); + + return 0; +} + +static const struct snd_kcontrol_new bd28623_controls[] = { + SOC_SINGLE_BOOL_EXT("Speaker Switch", 0, + bd28623_get_switch_spk, bd28623_set_switch_spk), +}; + +static int bd28623_codec_probe(struct snd_soc_component *component) +{ + struct bd28623_priv *bd = snd_soc_component_get_drvdata(component); + int ret; + + bd->switch_spk = 1; + + ret = bd28623_power_on(bd); + if (ret) + return ret; + + gpiod_set_value_cansleep(bd->mute_gpio, bd->switch_spk ? 0 : 1); + + return 0; +} + +static void bd28623_codec_remove(struct snd_soc_component *component) +{ + struct bd28623_priv *bd = snd_soc_component_get_drvdata(component); + + bd28623_power_off(bd); +} + +static int bd28623_codec_suspend(struct snd_soc_component *component) +{ + struct bd28623_priv *bd = snd_soc_component_get_drvdata(component); + + bd28623_power_off(bd); + + return 0; +} + +static int bd28623_codec_resume(struct snd_soc_component *component) +{ + struct bd28623_priv *bd = snd_soc_component_get_drvdata(component); + int ret; + + ret = bd28623_power_on(bd); + if (ret) + return ret; + + gpiod_set_value_cansleep(bd->mute_gpio, bd->switch_spk ? 0 : 1); + + return 0; +} + +static const struct snd_soc_component_driver soc_codec_bd = { + .probe = bd28623_codec_probe, + .remove = bd28623_codec_remove, + .suspend = bd28623_codec_suspend, + .resume = bd28623_codec_resume, + .dapm_widgets = bd28623_widgets, + .num_dapm_widgets = ARRAY_SIZE(bd28623_widgets), + .dapm_routes = bd28623_routes, + .num_dapm_routes = ARRAY_SIZE(bd28623_routes), + .controls = bd28623_controls, + .num_controls = ARRAY_SIZE(bd28623_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver soc_dai_bd = { + .name = "bd28623-speaker", + .playback = { + .stream_name = "Playback", + .formats = SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000, + .channels_min = 2, + .channels_max = 2, + }, +}; + +static int bd28623_probe(struct platform_device *pdev) +{ + struct bd28623_priv *bd; + struct device *dev = &pdev->dev; + int i, ret; + + bd = devm_kzalloc(&pdev->dev, sizeof(struct bd28623_priv), GFP_KERNEL); + if (!bd) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(bd->supplies); i++) + bd->supplies[i].supply = bd28623_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(bd->supplies), + bd->supplies); + if (ret) { + dev_err(dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + bd->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(bd->reset_gpio)) { + dev_err(dev, "Failed to request reset_gpio: %ld\n", + PTR_ERR(bd->reset_gpio)); + return PTR_ERR(bd->reset_gpio); + } + + bd->mute_gpio = devm_gpiod_get_optional(dev, "mute", + GPIOD_OUT_HIGH); + if (IS_ERR(bd->mute_gpio)) { + dev_err(dev, "Failed to request mute_gpio: %ld\n", + PTR_ERR(bd->mute_gpio)); + return PTR_ERR(bd->mute_gpio); + } + + platform_set_drvdata(pdev, bd); + bd->dev = dev; + + return devm_snd_soc_register_component(dev, &soc_codec_bd, + &soc_dai_bd, 1); +} + +static const struct of_device_id bd28623_of_match[] = { + { .compatible = "rohm,bd28623", }, + {} +}; +MODULE_DEVICE_TABLE(of, bd28623_of_match); + +static struct platform_driver bd28623_codec_driver = { + .driver = { + .name = "bd28623", + .of_match_table = of_match_ptr(bd28623_of_match), + }, + .probe = bd28623_probe, +}; +module_platform_driver(bd28623_codec_driver); + +MODULE_AUTHOR("Katsuhiro Suzuki "); +MODULE_DESCRIPTION("ROHM BD28623 speaker amplifier driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c new file mode 100644 index 000000000..842204203 --- /dev/null +++ b/sound/soc/codecs/bt-sco.c @@ -0,0 +1,122 @@ +/* + * Driver for generic Bluetooth SCO link + * Copyright 2011 Lars-Peter Clausen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include + +#include + +static const struct snd_soc_dapm_widget bt_sco_widgets[] = { + SND_SOC_DAPM_INPUT("RX"), + SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route bt_sco_routes[] = { + { "Capture", NULL, "RX" }, + { "TX", NULL, "Playback" }, +}; + +static struct snd_soc_dai_driver bt_sco_dai[] = { + { + .name = "bt-sco-pcm", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "bt-sco-pcm-wb", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + } +}; + +static const struct snd_soc_component_driver soc_component_dev_bt_sco = { + .dapm_widgets = bt_sco_widgets, + .num_dapm_widgets = ARRAY_SIZE(bt_sco_widgets), + .dapm_routes = bt_sco_routes, + .num_dapm_routes = ARRAY_SIZE(bt_sco_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int bt_sco_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_bt_sco, + bt_sco_dai, ARRAY_SIZE(bt_sco_dai)); +} + +static int bt_sco_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct platform_device_id bt_sco_driver_ids[] = { + { + .name = "dfbmcs320", + }, + { + .name = "bt-sco", + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); + +#if defined(CONFIG_OF) +static const struct of_device_id bt_sco_codec_of_match[] = { + { .compatible = "delta,dfbmcs320", }, + { .compatible = "linux,bt-sco", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bt_sco_codec_of_match); +#endif + +static struct platform_driver bt_sco_driver = { + .driver = { + .name = "bt-sco", + .of_match_table = of_match_ptr(bt_sco_codec_of_match), + }, + .probe = bt_sco_probe, + .remove = bt_sco_remove, + .id_table = bt_sco_driver_ids, +}; + +module_platform_driver(bt_sco_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ASoC generic bluetooth sco link driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c new file mode 100644 index 000000000..acd88fe38 --- /dev/null +++ b/sound/soc/codecs/cpcap.c @@ -0,0 +1,1564 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC CPCAP codec driver + * + * Copyright (C) 2017 - 2018 Sebastian Reichel + * + * Very loosely based on original driver from Motorola: + * Copyright (C) 2007 - 2009 Motorola, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Register 513 CPCAP_REG_CC --- CODEC */ +#define CPCAP_BIT_CDC_CLK2 15 +#define CPCAP_BIT_CDC_CLK1 14 +#define CPCAP_BIT_CDC_CLK0 13 +#define CPCAP_BIT_CDC_SR3 12 +#define CPCAP_BIT_CDC_SR2 11 +#define CPCAP_BIT_CDC_SR1 10 +#define CPCAP_BIT_CDC_SR0 9 +#define CPCAP_BIT_CDC_CLOCK_TREE_RESET 8 +#define CPCAP_BIT_MIC2_CDC_EN 7 +#define CPCAP_BIT_CDC_EN_RX 6 +#define CPCAP_BIT_DF_RESET 5 +#define CPCAP_BIT_MIC1_CDC_EN 4 +#define CPCAP_BIT_AUDOHPF_1 3 +#define CPCAP_BIT_AUDOHPF_0 2 +#define CPCAP_BIT_AUDIHPF_1 1 +#define CPCAP_BIT_AUDIHPF_0 0 + +/* Register 514 CPCAP_REG_CDI --- CODEC Digital Audio Interface */ +#define CPCAP_BIT_CDC_PLL_SEL 15 +#define CPCAP_BIT_CLK_IN_SEL 13 +#define CPCAP_BIT_DIG_AUD_IN 12 +#define CPCAP_BIT_CDC_CLK_EN 11 +#define CPCAP_BIT_CDC_DIG_AUD_FS1 10 +#define CPCAP_BIT_CDC_DIG_AUD_FS0 9 +#define CPCAP_BIT_MIC2_TIMESLOT2 8 +#define CPCAP_BIT_MIC2_TIMESLOT1 7 +#define CPCAP_BIT_MIC2_TIMESLOT0 6 +#define CPCAP_BIT_MIC1_RX_TIMESLOT2 5 +#define CPCAP_BIT_MIC1_RX_TIMESLOT1 4 +#define CPCAP_BIT_MIC1_RX_TIMESLOT0 3 +#define CPCAP_BIT_FS_INV 2 +#define CPCAP_BIT_CLK_INV 1 +#define CPCAP_BIT_SMB_CDC 0 + +/* Register 515 CPCAP_REG_SDAC --- Stereo DAC */ +#define CPCAP_BIT_FSYNC_CLK_IN_COMMON 11 +#define CPCAP_BIT_SLAVE_PLL_CLK_INPUT 10 +#define CPCAP_BIT_ST_CLOCK_TREE_RESET 9 +#define CPCAP_BIT_DF_RESET_ST_DAC 8 +#define CPCAP_BIT_ST_SR3 7 +#define CPCAP_BIT_ST_SR2 6 +#define CPCAP_BIT_ST_SR1 5 +#define CPCAP_BIT_ST_SR0 4 +#define CPCAP_BIT_ST_DAC_CLK2 3 +#define CPCAP_BIT_ST_DAC_CLK1 2 +#define CPCAP_BIT_ST_DAC_CLK0 1 +#define CPCAP_BIT_ST_DAC_EN 0 + +/* Register 516 CPCAP_REG_SDACDI --- Stereo DAC Digital Audio Interface */ +#define CPCAP_BIT_ST_L_TIMESLOT2 13 +#define CPCAP_BIT_ST_L_TIMESLOT1 12 +#define CPCAP_BIT_ST_L_TIMESLOT0 11 +#define CPCAP_BIT_ST_R_TIMESLOT2 10 +#define CPCAP_BIT_ST_R_TIMESLOT1 9 +#define CPCAP_BIT_ST_R_TIMESLOT0 8 +#define CPCAP_BIT_ST_DAC_CLK_IN_SEL 7 +#define CPCAP_BIT_ST_FS_INV 6 +#define CPCAP_BIT_ST_CLK_INV 5 +#define CPCAP_BIT_ST_DIG_AUD_FS1 4 +#define CPCAP_BIT_ST_DIG_AUD_FS0 3 +#define CPCAP_BIT_DIG_AUD_IN_ST_DAC 2 +#define CPCAP_BIT_ST_CLK_EN 1 +#define CPCAP_BIT_SMB_ST_DAC 0 + +/* Register 517 CPCAP_REG_TXI --- TX Interface */ +#define CPCAP_BIT_PTT_TH 15 +#define CPCAP_BIT_PTT_CMP_EN 14 +#define CPCAP_BIT_HS_ID_TX 13 +#define CPCAP_BIT_MB_ON2 12 +#define CPCAP_BIT_MB_ON1L 11 +#define CPCAP_BIT_MB_ON1R 10 +#define CPCAP_BIT_RX_L_ENCODE 9 +#define CPCAP_BIT_RX_R_ENCODE 8 +#define CPCAP_BIT_MIC2_MUX 7 +#define CPCAP_BIT_MIC2_PGA_EN 6 +#define CPCAP_BIT_CDET_DIS 5 +#define CPCAP_BIT_EMU_MIC_MUX 4 +#define CPCAP_BIT_HS_MIC_MUX 3 +#define CPCAP_BIT_MIC1_MUX 2 +#define CPCAP_BIT_MIC1_PGA_EN 1 +#define CPCAP_BIT_DLM 0 + +/* Register 518 CPCAP_REG_TXMP --- Mic Gain */ +#define CPCAP_BIT_MB_BIAS_R1 11 +#define CPCAP_BIT_MB_BIAS_R0 10 +#define CPCAP_BIT_MIC2_GAIN_4 9 +#define CPCAP_BIT_MIC2_GAIN_3 8 +#define CPCAP_BIT_MIC2_GAIN_2 7 +#define CPCAP_BIT_MIC2_GAIN_1 6 +#define CPCAP_BIT_MIC2_GAIN_0 5 +#define CPCAP_BIT_MIC1_GAIN_4 4 +#define CPCAP_BIT_MIC1_GAIN_3 3 +#define CPCAP_BIT_MIC1_GAIN_2 2 +#define CPCAP_BIT_MIC1_GAIN_1 1 +#define CPCAP_BIT_MIC1_GAIN_0 0 + +/* Register 519 CPCAP_REG_RXOA --- RX Output Amplifier */ +#define CPCAP_BIT_UNUSED_519_15 15 +#define CPCAP_BIT_UNUSED_519_14 14 +#define CPCAP_BIT_UNUSED_519_13 13 +#define CPCAP_BIT_STDAC_LOW_PWR_DISABLE 12 +#define CPCAP_BIT_HS_LOW_PWR 11 +#define CPCAP_BIT_HS_ID_RX 10 +#define CPCAP_BIT_ST_HS_CP_EN 9 +#define CPCAP_BIT_EMU_SPKR_R_EN 8 +#define CPCAP_BIT_EMU_SPKR_L_EN 7 +#define CPCAP_BIT_HS_L_EN 6 +#define CPCAP_BIT_HS_R_EN 5 +#define CPCAP_BIT_A4_LINEOUT_L_EN 4 +#define CPCAP_BIT_A4_LINEOUT_R_EN 3 +#define CPCAP_BIT_A2_LDSP_L_EN 2 +#define CPCAP_BIT_A2_LDSP_R_EN 1 +#define CPCAP_BIT_A1_EAR_EN 0 + +/* Register 520 CPCAP_REG_RXVC --- RX Volume Control */ +#define CPCAP_BIT_VOL_EXT3 15 +#define CPCAP_BIT_VOL_EXT2 14 +#define CPCAP_BIT_VOL_EXT1 13 +#define CPCAP_BIT_VOL_EXT0 12 +#define CPCAP_BIT_VOL_DAC3 11 +#define CPCAP_BIT_VOL_DAC2 10 +#define CPCAP_BIT_VOL_DAC1 9 +#define CPCAP_BIT_VOL_DAC0 8 +#define CPCAP_BIT_VOL_DAC_LSB_1dB1 7 +#define CPCAP_BIT_VOL_DAC_LSB_1dB0 6 +#define CPCAP_BIT_VOL_CDC3 5 +#define CPCAP_BIT_VOL_CDC2 4 +#define CPCAP_BIT_VOL_CDC1 3 +#define CPCAP_BIT_VOL_CDC0 2 +#define CPCAP_BIT_VOL_CDC_LSB_1dB1 1 +#define CPCAP_BIT_VOL_CDC_LSB_1dB0 0 + +/* Register 521 CPCAP_REG_RXCOA --- Codec to Output Amp Switches */ +#define CPCAP_BIT_PGA_CDC_EN 10 +#define CPCAP_BIT_CDC_SW 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_CDC_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_CDC_SW 7 +#define CPCAP_BIT_ALEFT_HS_CDC_SW 6 +#define CPCAP_BIT_ARIGHT_HS_CDC_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_CDC_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_CDC_SW 3 +#define CPCAP_BIT_A2_LDSP_L_CDC_SW 2 +#define CPCAP_BIT_A2_LDSP_R_CDC_SW 1 +#define CPCAP_BIT_A1_EAR_CDC_SW 0 + +/* Register 522 CPCAP_REG_RXSDOA --- RX Stereo DAC to Output Amp Switches */ +#define CPCAP_BIT_PGA_DAC_EN 12 +#define CPCAP_BIT_ST_DAC_SW 11 +#define CPCAP_BIT_MONO_DAC1 10 +#define CPCAP_BIT_MONO_DAC0 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_DAC_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_DAC_SW 7 +#define CPCAP_BIT_ALEFT_HS_DAC_SW 6 +#define CPCAP_BIT_ARIGHT_HS_DAC_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_DAC_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_DAC_SW 3 +#define CPCAP_BIT_A2_LDSP_L_DAC_SW 2 +#define CPCAP_BIT_A2_LDSP_R_DAC_SW 1 +#define CPCAP_BIT_A1_EAR_DAC_SW 0 + +/* Register 523 CPCAP_REG_RXEPOA --- RX External PGA to Output Amp Switches */ +#define CPCAP_BIT_PGA_EXT_L_EN 14 +#define CPCAP_BIT_PGA_EXT_R_EN 13 +#define CPCAP_BIT_PGA_IN_L_SW 12 +#define CPCAP_BIT_PGA_IN_R_SW 11 +#define CPCAP_BIT_MONO_EXT1 10 +#define CPCAP_BIT_MONO_EXT0 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_EXT_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_EXT_SW 7 +#define CPCAP_BIT_ALEFT_HS_EXT_SW 6 +#define CPCAP_BIT_ARIGHT_HS_EXT_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_EXT_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_EXT_SW 3 +#define CPCAP_BIT_A2_LDSP_L_EXT_SW 2 +#define CPCAP_BIT_A2_LDSP_R_EXT_SW 1 +#define CPCAP_BIT_A1_EAR_EXT_SW 0 + +/* Register 525 CPCAP_REG_A2LA --- SPK Amplifier and Clock Config for Headset */ +#define CPCAP_BIT_NCP_CLK_SYNC 7 +#define CPCAP_BIT_A2_CLK_SYNC 6 +#define CPCAP_BIT_A2_FREE_RUN 5 +#define CPCAP_BIT_A2_CLK2 4 +#define CPCAP_BIT_A2_CLK1 3 +#define CPCAP_BIT_A2_CLK0 2 +#define CPCAP_BIT_A2_CLK_IN 1 +#define CPCAP_BIT_A2_CONFIG 0 + +#define SLEEP_ACTIVATE_POWER 2 +#define CLOCK_TREE_RESET_TIME 1 + +/* constants for ST delay workaround */ +#define STM_STDAC_ACTIVATE_RAMP_TIME 1 +#define STM_STDAC_EN_TEST_PRE 0x090C +#define STM_STDAC_EN_TEST_POST 0x0000 +#define STM_STDAC_EN_ST_TEST1_PRE 0x2400 +#define STM_STDAC_EN_ST_TEST1_POST 0x0400 + +struct cpcap_reg_info { + u16 reg; + u16 mask; + u16 val; +}; + +static const struct cpcap_reg_info cpcap_default_regs[] = { + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, + { CPCAP_REG_SDAC, 0x0FFF, 0x0000 }, + { CPCAP_REG_SDACDI, 0x3FFF, 0x0000 }, + { CPCAP_REG_TXI, 0x0FDF, 0x0000 }, + { CPCAP_REG_TXMP, 0x0FFF, 0x0400 }, + { CPCAP_REG_RXOA, 0x01FF, 0x0000 }, + { CPCAP_REG_RXVC, 0xFF3C, 0x0000 }, + { CPCAP_REG_RXCOA, 0x07FF, 0x0000 }, + { CPCAP_REG_RXSDOA, 0x1FFF, 0x0000 }, + { CPCAP_REG_RXEPOA, 0x7FFF, 0x0000 }, + { CPCAP_REG_A2LA, BIT(CPCAP_BIT_A2_FREE_RUN), + BIT(CPCAP_BIT_A2_FREE_RUN) }, +}; + +enum cpcap_dai { + CPCAP_DAI_HIFI, + CPCAP_DAI_VOICE, +}; + +struct cpcap_audio { + struct snd_soc_component *component; + struct regmap *regmap; + + u16 vendor; + + int codec_clk_id; + int codec_freq; + int codec_format; +}; + +static int cpcap_st_workaround(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + int err = 0; + + /* Only CPCAP from ST requires workaround */ + if (cpcap->vendor != CPCAP_VENDOR_ST) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + err = regmap_write(cpcap->regmap, CPCAP_REG_TEST, + STM_STDAC_EN_TEST_PRE); + if (err) + return err; + err = regmap_write(cpcap->regmap, CPCAP_REG_ST_TEST1, + STM_STDAC_EN_ST_TEST1_PRE); + break; + case SND_SOC_DAPM_POST_PMU: + msleep(STM_STDAC_ACTIVATE_RAMP_TIME); + + err = regmap_write(cpcap->regmap, CPCAP_REG_ST_TEST1, + STM_STDAC_EN_ST_TEST1_POST); + if (err) + return err; + err = regmap_write(cpcap->regmap, CPCAP_REG_TEST, + STM_STDAC_EN_TEST_POST); + break; + default: + break; + } + + return err; +} + +/* Capture Gain Control: 0dB to 31dB in 1dB steps */ +static const DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0); + +/* Playback Gain Control: -33dB to 12dB in 3dB steps */ +static const DECLARE_TLV_DB_SCALE(vol_tlv, -3300, 300, 0); + +static const struct snd_kcontrol_new cpcap_snd_controls[] = { + /* Playback Gain */ + SOC_SINGLE_TLV("HiFi Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_DAC0, 0xF, 0, vol_tlv), + SOC_SINGLE_TLV("Voice Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_CDC0, 0xF, 0, vol_tlv), + SOC_SINGLE_TLV("Ext Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_EXT0, 0xF, 0, vol_tlv), + + /* Capture Gain */ + SOC_SINGLE_TLV("Mic1 Capture Volume", + CPCAP_REG_TXMP, CPCAP_BIT_MIC1_GAIN_0, 0x1F, 0, mic_gain_tlv), + SOC_SINGLE_TLV("Mic2 Capture Volume", + CPCAP_REG_TXMP, CPCAP_BIT_MIC2_GAIN_0, 0x1F, 0, mic_gain_tlv), + + /* Phase Invert */ + SOC_SINGLE("Hifi Left Phase Invert Switch", + CPCAP_REG_RXSDOA, CPCAP_BIT_MONO_DAC0, 1, 0), + SOC_SINGLE("Ext Left Phase Invert Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_MONO_EXT0, 1, 0), +}; + +static const char * const cpcap_out_mux_texts[] = { + "Off", "Voice", "HiFi", "Ext" +}; + +static const char * const cpcap_in_right_mux_texts[] = { + "Off", "Mic 1", "Headset Mic", "EMU Mic", "Ext Right" +}; + +static const char * const cpcap_in_left_mux_texts[] = { + "Off", "Mic 2", "Ext Left" +}; + +/* + * input muxes use unusual register layout, so that we need to use custom + * getter/setter methods + */ +static SOC_ENUM_SINGLE_EXT_DECL(cpcap_input_left_mux_enum, + cpcap_in_left_mux_texts); +static SOC_ENUM_SINGLE_EXT_DECL(cpcap_input_right_mux_enum, + cpcap_in_right_mux_texts); + +/* + * mux uses same bit in CPCAP_REG_RXCOA, CPCAP_REG_RXSDOA & CPCAP_REG_RXEPOA; + * even though the register layout makes it look like a mixer, this is a mux. + * Enabling multiple inputs will result in no audio being forwarded. + */ +static SOC_ENUM_SINGLE_DECL(cpcap_earpiece_mux_enum, 0, 0, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_spkr_r_mux_enum, 0, 1, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_spkr_l_mux_enum, 0, 2, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_line_r_mux_enum, 0, 3, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_line_l_mux_enum, 0, 4, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_hs_r_mux_enum, 0, 5, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_hs_l_mux_enum, 0, 6, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_emu_l_mux_enum, 0, 7, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_emu_r_mux_enum, 0, 8, cpcap_out_mux_texts); + +static int cpcap_output_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int shift = e->shift_l; + int reg_voice, reg_hifi, reg_ext, status; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_RXCOA, ®_voice); + if (err) + return err; + err = regmap_read(cpcap->regmap, CPCAP_REG_RXSDOA, ®_hifi); + if (err) + return err; + err = regmap_read(cpcap->regmap, CPCAP_REG_RXEPOA, ®_ext); + if (err) + return err; + + reg_voice = (reg_voice >> shift) & 1; + reg_hifi = (reg_hifi >> shift) & 1; + reg_ext = (reg_ext >> shift) & 1; + status = reg_ext << 2 | reg_hifi << 1 | reg_voice; + + switch (status) { + case 0x04: + ucontrol->value.enumerated.item[0] = 3; + break; + case 0x02: + ucontrol->value.enumerated.item[0] = 2; + break; + case 0x01: + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_output_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + unsigned int mask = BIT(e->shift_l); + u16 reg_voice = 0x00, reg_hifi = 0x00, reg_ext = 0x00; + int err; + + switch (muxval) { + case 1: + reg_voice = mask; + break; + case 2: + reg_hifi = mask; + break; + case 3: + reg_ext = mask; + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, + mask, reg_voice); + if (err) + return err; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXSDOA, + mask, reg_hifi); + if (err) + return err; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXEPOA, + mask, reg_ext); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static int cpcap_input_right_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + int regval, mask; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_TXI, ®val); + if (err) + return err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC1_MUX); + mask |= BIT(CPCAP_BIT_HS_MIC_MUX); + mask |= BIT(CPCAP_BIT_EMU_MIC_MUX); + mask |= BIT(CPCAP_BIT_RX_R_ENCODE); + + switch (regval & mask) { + case BIT(CPCAP_BIT_RX_R_ENCODE): + ucontrol->value.enumerated.item[0] = 4; + break; + case BIT(CPCAP_BIT_EMU_MIC_MUX): + ucontrol->value.enumerated.item[0] = 3; + break; + case BIT(CPCAP_BIT_HS_MIC_MUX): + ucontrol->value.enumerated.item[0] = 2; + break; + case BIT(CPCAP_BIT_MIC1_MUX): + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_input_right_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + int regval = 0, mask; + int err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC1_MUX); + mask |= BIT(CPCAP_BIT_HS_MIC_MUX); + mask |= BIT(CPCAP_BIT_EMU_MIC_MUX); + mask |= BIT(CPCAP_BIT_RX_R_ENCODE); + + switch (muxval) { + case 1: + regval = BIT(CPCAP_BIT_MIC1_MUX); + break; + case 2: + regval = BIT(CPCAP_BIT_HS_MIC_MUX); + break; + case 3: + regval = BIT(CPCAP_BIT_EMU_MIC_MUX); + break; + case 4: + regval = BIT(CPCAP_BIT_RX_R_ENCODE); + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + mask, regval); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static int cpcap_input_left_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + int regval, mask; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_TXI, ®val); + if (err) + return err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC2_MUX); + mask |= BIT(CPCAP_BIT_RX_L_ENCODE); + + switch (regval & mask) { + case BIT(CPCAP_BIT_RX_L_ENCODE): + ucontrol->value.enumerated.item[0] = 2; + break; + case BIT(CPCAP_BIT_MIC2_MUX): + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_input_left_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + int regval = 0, mask; + int err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC2_MUX); + mask |= BIT(CPCAP_BIT_RX_L_ENCODE); + + switch (muxval) { + case 1: + regval = BIT(CPCAP_BIT_MIC2_MUX); + break; + case 2: + regval = BIT(CPCAP_BIT_RX_L_ENCODE); + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + mask, regval); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static const struct snd_kcontrol_new cpcap_input_left_mux = + SOC_DAPM_ENUM_EXT("Input Left", cpcap_input_left_mux_enum, + cpcap_input_left_mux_get_enum, + cpcap_input_left_mux_put_enum); +static const struct snd_kcontrol_new cpcap_input_right_mux = + SOC_DAPM_ENUM_EXT("Input Right", cpcap_input_right_mux_enum, + cpcap_input_right_mux_get_enum, + cpcap_input_right_mux_put_enum); +static const struct snd_kcontrol_new cpcap_emu_left_mux = + SOC_DAPM_ENUM_EXT("EMU Left", cpcap_emu_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_emu_right_mux = + SOC_DAPM_ENUM_EXT("EMU Right", cpcap_emu_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_hs_left_mux = + SOC_DAPM_ENUM_EXT("Headset Left", cpcap_hs_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_hs_right_mux = + SOC_DAPM_ENUM_EXT("Headset Right", cpcap_hs_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_line_left_mux = + SOC_DAPM_ENUM_EXT("Line Left", cpcap_line_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_line_right_mux = + SOC_DAPM_ENUM_EXT("Line Right", cpcap_line_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_speaker_left_mux = + SOC_DAPM_ENUM_EXT("Speaker Left", cpcap_spkr_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_speaker_right_mux = + SOC_DAPM_ENUM_EXT("Speaker Right", cpcap_spkr_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_earpiece_mux = + SOC_DAPM_ENUM_EXT("Earpiece", cpcap_earpiece_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); + +static const struct snd_kcontrol_new cpcap_hifi_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("HiFi Mono Playback Switch", + CPCAP_REG_RXSDOA, CPCAP_BIT_MONO_DAC1, 1, 0), +}; +static const struct snd_kcontrol_new cpcap_ext_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Ext Mono Playback Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_MONO_EXT0, 1, 0), +}; + +static const struct snd_kcontrol_new cpcap_extr_mute_control = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_IN_R_SW, 1, 0); +static const struct snd_kcontrol_new cpcap_extl_mute_control = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_IN_L_SW, 1, 0); + +static const struct snd_kcontrol_new cpcap_voice_loopback = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_TXI, CPCAP_BIT_DLM, 1, 0); + +static const struct snd_soc_dapm_widget cpcap_dapm_widgets[] = { + /* DAIs */ + SND_SOC_DAPM_AIF_IN("HiFi RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("Voice RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("Voice TX", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* Power Supply */ + SND_SOC_DAPM_REGULATOR_SUPPLY("VAUDIO", SLEEP_ACTIVATE_POWER, 0), + + /* Highpass Filters */ + SND_SOC_DAPM_REG(snd_soc_dapm_pga, "Highpass Filter RX", + CPCAP_REG_CC, CPCAP_BIT_AUDIHPF_0, 0x3, 0x3, 0x0), + SND_SOC_DAPM_REG(snd_soc_dapm_pga, "Highpass Filter TX", + CPCAP_REG_CC, CPCAP_BIT_AUDOHPF_0, 0x3, 0x3, 0x0), + + /* Clocks */ + SND_SOC_DAPM_SUPPLY("HiFi DAI Clock", + CPCAP_REG_SDACDI, CPCAP_BIT_ST_CLK_EN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Voice DAI Clock", + CPCAP_REG_CDI, CPCAP_BIT_CDC_CLK_EN, 0, NULL, 0), + + /* Microphone Bias */ + SND_SOC_DAPM_SUPPLY("MIC1R Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON1R, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC1L Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON1L, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON2, 0, NULL, 0), + + /* Inputs */ + SND_SOC_DAPM_INPUT("MICR"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("EMUMIC"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("EXTR"), + SND_SOC_DAPM_INPUT("EXTL"), + + /* Capture Route */ + SND_SOC_DAPM_MUX("Right Capture Route", + SND_SOC_NOPM, 0, 0, &cpcap_input_right_mux), + SND_SOC_DAPM_MUX("Left Capture Route", + SND_SOC_NOPM, 0, 0, &cpcap_input_left_mux), + + /* Capture PGAs */ + SND_SOC_DAPM_PGA("Microphone 1 PGA", + CPCAP_REG_TXI, CPCAP_BIT_MIC1_PGA_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Microphone 2 PGA", + CPCAP_REG_TXI, CPCAP_BIT_MIC2_PGA_EN, 0, NULL, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC Right", NULL, + CPCAP_REG_CC, CPCAP_BIT_MIC1_CDC_EN, 0), + SND_SOC_DAPM_ADC("ADC Left", NULL, + CPCAP_REG_CC, CPCAP_BIT_MIC2_CDC_EN, 0), + + /* DAC */ + SND_SOC_DAPM_DAC_E("DAC HiFi", NULL, + CPCAP_REG_SDAC, CPCAP_BIT_ST_DAC_EN, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_DAC_E("DAC Voice", NULL, + CPCAP_REG_CC, CPCAP_BIT_CDC_EN_RX, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* Playback PGA */ + SND_SOC_DAPM_PGA("HiFi PGA", + CPCAP_REG_RXSDOA, CPCAP_BIT_PGA_DAC_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Voice PGA", + CPCAP_REG_RXCOA, CPCAP_BIT_PGA_CDC_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_E("Ext Right PGA", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_EXT_R_EN, 0, + NULL, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("Ext Left PGA", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_EXT_L_EN, 0, + NULL, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* Playback Switch */ + SND_SOC_DAPM_SWITCH("Ext Right Enable", SND_SOC_NOPM, 0, 0, + &cpcap_extr_mute_control), + SND_SOC_DAPM_SWITCH("Ext Left Enable", SND_SOC_NOPM, 0, 0, + &cpcap_extl_mute_control), + + /* Loopback Switch */ + SND_SOC_DAPM_SWITCH("Voice Loopback", SND_SOC_NOPM, 0, 0, + &cpcap_voice_loopback), + + /* Mono Mixer */ + SOC_MIXER_ARRAY("HiFi Mono Left Mixer", SND_SOC_NOPM, 0, 0, + cpcap_hifi_mono_mixer_controls), + SOC_MIXER_ARRAY("HiFi Mono Right Mixer", SND_SOC_NOPM, 0, 0, + cpcap_hifi_mono_mixer_controls), + SOC_MIXER_ARRAY("Ext Mono Left Mixer", SND_SOC_NOPM, 0, 0, + cpcap_ext_mono_mixer_controls), + SOC_MIXER_ARRAY("Ext Mono Right Mixer", SND_SOC_NOPM, 0, 0, + cpcap_ext_mono_mixer_controls), + + /* Output Routes */ + SND_SOC_DAPM_MUX("Earpiece Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_earpiece_mux), + SND_SOC_DAPM_MUX("Speaker Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_speaker_right_mux), + SND_SOC_DAPM_MUX("Speaker Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_speaker_left_mux), + SND_SOC_DAPM_MUX("Lineout Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_line_right_mux), + SND_SOC_DAPM_MUX("Lineout Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_line_left_mux), + SND_SOC_DAPM_MUX("Headset Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_hs_right_mux), + SND_SOC_DAPM_MUX("Headset Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_hs_left_mux), + SND_SOC_DAPM_MUX("EMU Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_emu_right_mux), + SND_SOC_DAPM_MUX("EMU Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_emu_left_mux), + + /* Output Amplifier */ + SND_SOC_DAPM_PGA("Earpiece PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A1_EAR_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A2_LDSP_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A2_LDSP_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A4_LINEOUT_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A4_LINEOUT_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_HS_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_HS_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("EMU Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_EMU_SPKR_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("EMU Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_EMU_SPKR_L_EN, 0, NULL, 0), + + /* Headet Charge Pump */ + SND_SOC_DAPM_SUPPLY("Headset Charge Pump", + CPCAP_REG_RXOA, CPCAP_BIT_ST_HS_CP_EN, 0, NULL, 0), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("EP"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("LINER"), + SND_SOC_DAPM_OUTPUT("LINEL"), + SND_SOC_DAPM_OUTPUT("HSR"), + SND_SOC_DAPM_OUTPUT("HSL"), + SND_SOC_DAPM_OUTPUT("EMUR"), + SND_SOC_DAPM_OUTPUT("EMUL"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Power Supply */ + {"HiFi PGA", NULL, "VAUDIO"}, + {"Voice PGA", NULL, "VAUDIO"}, + {"Ext Right PGA", NULL, "VAUDIO"}, + {"Ext Left PGA", NULL, "VAUDIO"}, + {"Microphone 1 PGA", NULL, "VAUDIO"}, + {"Microphone 2 PGA", NULL, "VAUDIO"}, + + /* Stream -> AIF */ + {"HiFi RX", NULL, "HiFi Playback"}, + {"Voice RX", NULL, "Voice Playback"}, + {"Voice Capture", NULL, "Voice TX"}, + + /* AIF clocks */ + {"HiFi RX", NULL, "HiFi DAI Clock"}, + {"Voice RX", NULL, "Voice DAI Clock"}, + {"Voice TX", NULL, "Voice DAI Clock"}, + + /* Digital Loopback */ + {"Voice Loopback", "Switch", "Voice TX"}, + {"Voice RX", NULL, "Voice Loopback"}, + + /* Highpass Filters */ + {"Highpass Filter RX", NULL, "Voice RX"}, + {"Voice TX", NULL, "Highpass Filter TX"}, + + /* AIF -> DAC mapping */ + {"DAC HiFi", NULL, "HiFi RX"}, + {"DAC Voice", NULL, "Highpass Filter RX"}, + + /* DAC -> PGA */ + {"HiFi PGA", NULL, "DAC HiFi"}, + {"Voice PGA", NULL, "DAC Voice"}, + + /* Ext Input -> PGA */ + {"Ext Right PGA", NULL, "EXTR"}, + {"Ext Left PGA", NULL, "EXTL"}, + + /* Ext PGA -> Ext Playback Switch */ + {"Ext Right Enable", "Switch", "Ext Right PGA"}, + {"Ext Left Enable", "Switch", "Ext Left PGA"}, + + /* HiFi PGA -> Mono Mixer */ + {"HiFi Mono Left Mixer", NULL, "HiFi PGA"}, + {"HiFi Mono Left Mixer", "HiFi Mono Playback Switch", "HiFi PGA"}, + {"HiFi Mono Right Mixer", NULL, "HiFi PGA"}, + {"HiFi Mono Right Mixer", "HiFi Mono Playback Switch", "HiFi PGA"}, + + /* Ext Playback Switch -> Ext Mono Mixer */ + {"Ext Mono Right Mixer", NULL, "Ext Right Enable"}, + {"Ext Mono Right Mixer", "Ext Mono Playback Switch", "Ext Left Enable"}, + {"Ext Mono Left Mixer", NULL, "Ext Left Enable"}, + {"Ext Mono Left Mixer", "Ext Mono Playback Switch", "Ext Right Enable"}, + + /* HiFi Mono Mixer -> Output Route */ + {"Earpiece Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Speaker Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Speaker Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"Lineout Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Lineout Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"Headset Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Headset Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"EMU Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"EMU Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + + /* Voice PGA -> Output Route */ + {"Earpiece Playback Route", "Voice", "Voice PGA"}, + {"Speaker Right Playback Route", "Voice", "Voice PGA"}, + {"Speaker Left Playback Route", "Voice", "Voice PGA"}, + {"Lineout Right Playback Route", "Voice", "Voice PGA"}, + {"Lineout Left Playback Route", "Voice", "Voice PGA"}, + {"Headset Right Playback Route", "Voice", "Voice PGA"}, + {"Headset Left Playback Route", "Voice", "Voice PGA"}, + {"EMU Right Playback Route", "Voice", "Voice PGA"}, + {"EMU Left Playback Route", "Voice", "Voice PGA"}, + + /* Ext Mono Mixer -> Output Route */ + {"Earpiece Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Speaker Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Speaker Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"Lineout Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Lineout Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"Headset Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Headset Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"EMU Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"EMU Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + + /* Output Route -> Output Amplifier */ + {"Earpiece PGA", NULL, "Earpiece Playback Route"}, + {"Speaker Right PGA", NULL, "Speaker Right Playback Route"}, + {"Speaker Left PGA", NULL, "Speaker Left Playback Route"}, + {"Lineout Right PGA", NULL, "Lineout Right Playback Route"}, + {"Lineout Left PGA", NULL, "Lineout Left Playback Route"}, + {"Headset Right PGA", NULL, "Headset Right Playback Route"}, + {"Headset Left PGA", NULL, "Headset Left Playback Route"}, + {"EMU Right PGA", NULL, "EMU Right Playback Route"}, + {"EMU Left PGA", NULL, "EMU Left Playback Route"}, + + /* Output Amplifier -> Output */ + {"EP", NULL, "Earpiece PGA"}, + {"SPKR", NULL, "Speaker Right PGA"}, + {"SPKL", NULL, "Speaker Left PGA"}, + {"LINER", NULL, "Lineout Right PGA"}, + {"LINEL", NULL, "Lineout Left PGA"}, + {"HSR", NULL, "Headset Right PGA"}, + {"HSL", NULL, "Headset Left PGA"}, + {"EMUR", NULL, "EMU Right PGA"}, + {"EMUL", NULL, "EMU Left PGA"}, + + /* Headset Charge Pump -> Headset */ + {"HSR", NULL, "Headset Charge Pump"}, + {"HSL", NULL, "Headset Charge Pump"}, + + /* Mic -> Mic Route */ + {"Right Capture Route", "Mic 1", "MICR"}, + {"Right Capture Route", "Headset Mic", "HSMIC"}, + {"Right Capture Route", "EMU Mic", "EMUMIC"}, + {"Right Capture Route", "Ext Right", "EXTR"}, + {"Left Capture Route", "Mic 2", "MICL"}, + {"Left Capture Route", "Ext Left", "EXTL"}, + + /* Input Route -> Microphone PGA */ + {"Microphone 1 PGA", NULL, "Right Capture Route"}, + {"Microphone 2 PGA", NULL, "Left Capture Route"}, + + /* Microphone PGA -> ADC */ + {"ADC Right", NULL, "Microphone 1 PGA"}, + {"ADC Left", NULL, "Microphone 2 PGA"}, + + /* ADC -> Stream */ + {"Highpass Filter TX", NULL, "ADC Right"}, + {"Highpass Filter TX", NULL, "ADC Left"}, + + /* Mic Bias */ + {"MICL", NULL, "MIC1L Bias"}, + {"MICR", NULL, "MIC1R Bias"}, +}; + +static int cpcap_set_sysclk(struct cpcap_audio *cpcap, enum cpcap_dai dai, + int clk_id, int freq) +{ + u16 clkfreqreg, clkfreqshift; + u16 clkfreqmask, clkfreqval; + u16 clkidreg, clkidshift; + u16 mask, val; + int err; + + switch (dai) { + case CPCAP_DAI_HIFI: + clkfreqreg = CPCAP_REG_SDAC; + clkfreqshift = CPCAP_BIT_ST_DAC_CLK0; + clkidreg = CPCAP_REG_SDACDI; + clkidshift = CPCAP_BIT_ST_DAC_CLK_IN_SEL; + break; + case CPCAP_DAI_VOICE: + clkfreqreg = CPCAP_REG_CC; + clkfreqshift = CPCAP_BIT_CDC_CLK0; + clkidreg = CPCAP_REG_CDI; + clkidshift = CPCAP_BIT_CLK_IN_SEL; + break; + default: + dev_err(cpcap->component->dev, "invalid DAI: %d", dai); + return -EINVAL; + } + + /* setup clk id */ + if (clk_id < 0 || clk_id > 1) { + dev_err(cpcap->component->dev, "invalid clk id %d", clk_id); + return -EINVAL; + } + err = regmap_update_bits(cpcap->regmap, clkidreg, BIT(clkidshift), + clk_id ? BIT(clkidshift) : 0); + if (err) + return err; + + /* enable PLL for Voice DAI */ + if (dai == CPCAP_DAI_VOICE) { + mask = BIT(CPCAP_BIT_CDC_PLL_SEL); + val = BIT(CPCAP_BIT_CDC_PLL_SEL); + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + mask, val); + if (err) + return err; + } + + /* setup frequency */ + clkfreqmask = 0x7 << clkfreqshift; + switch (freq) { + case 15360000: + clkfreqval = 0x01 << clkfreqshift; + break; + case 16800000: + clkfreqval = 0x02 << clkfreqshift; + break; + case 19200000: + clkfreqval = 0x03 << clkfreqshift; + break; + case 26000000: + clkfreqval = 0x04 << clkfreqshift; + break; + case 33600000: + clkfreqval = 0x05 << clkfreqshift; + break; + case 38400000: + clkfreqval = 0x06 << clkfreqshift; + break; + default: + dev_err(cpcap->component->dev, "unsupported freq %u", freq); + return -EINVAL; + } + + err = regmap_update_bits(cpcap->regmap, clkfreqreg, + clkfreqmask, clkfreqval); + if (err) + return err; + + if (dai == CPCAP_DAI_VOICE) { + cpcap->codec_clk_id = clk_id; + cpcap->codec_freq = freq; + } + + return 0; +} + +static int cpcap_set_samprate(struct cpcap_audio *cpcap, enum cpcap_dai dai, + int samplerate) +{ + struct snd_soc_component *component = cpcap->component; + u16 sampreg, sampmask, sampshift, sampval, sampreset; + int err, sampreadval; + + switch (dai) { + case CPCAP_DAI_HIFI: + sampreg = CPCAP_REG_SDAC; + sampshift = CPCAP_BIT_ST_SR0; + sampreset = BIT(CPCAP_BIT_DF_RESET_ST_DAC) | + BIT(CPCAP_BIT_ST_CLOCK_TREE_RESET); + break; + case CPCAP_DAI_VOICE: + sampreg = CPCAP_REG_CC; + sampshift = CPCAP_BIT_CDC_SR0; + sampreset = BIT(CPCAP_BIT_DF_RESET) | + BIT(CPCAP_BIT_CDC_CLOCK_TREE_RESET); + break; + default: + dev_err(component->dev, "invalid DAI: %d", dai); + return -EINVAL; + } + + sampmask = 0xF << sampshift | sampreset; + switch (samplerate) { + case 48000: + sampval = 0x8 << sampshift; + break; + case 44100: + sampval = 0x7 << sampshift; + break; + case 32000: + sampval = 0x6 << sampshift; + break; + case 24000: + sampval = 0x5 << sampshift; + break; + case 22050: + sampval = 0x4 << sampshift; + break; + case 16000: + sampval = 0x3 << sampshift; + break; + case 12000: + sampval = 0x2 << sampshift; + break; + case 11025: + sampval = 0x1 << sampshift; + break; + case 8000: + sampval = 0x0 << sampshift; + break; + default: + dev_err(component->dev, "unsupported samplerate %d", samplerate); + return -EINVAL; + } + err = regmap_update_bits(cpcap->regmap, sampreg, + sampmask, sampval | sampreset); + if (err) + return err; + + /* Wait for clock tree reset to complete */ + mdelay(CLOCK_TREE_RESET_TIME); + + err = regmap_read(cpcap->regmap, sampreg, &sampreadval); + if (err) + return err; + + if (sampreadval & sampreset) { + dev_err(component->dev, "reset self-clear failed: %04x", + sampreadval); + return -EIO; + } + + return 0; +} + +static int cpcap_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + int rate = params_rate(params); + + dev_dbg(component->dev, "HiFi setup HW params: rate=%d", rate); + return cpcap_set_samprate(cpcap, CPCAP_DAI_HIFI, rate); +} + +static int cpcap_hifi_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + + dev_dbg(dev, "HiFi setup sysclk: clk_id=%u, freq=%u", clk_id, freq); + return cpcap_set_sysclk(cpcap, CPCAP_DAI_HIFI, clk_id, freq); +} + +static int cpcap_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + static const u16 reg = CPCAP_REG_SDACDI; + static const u16 mask = + BIT(CPCAP_BIT_SMB_ST_DAC) | + BIT(CPCAP_BIT_ST_CLK_INV) | + BIT(CPCAP_BIT_ST_FS_INV) | + BIT(CPCAP_BIT_ST_DIG_AUD_FS0) | + BIT(CPCAP_BIT_ST_DIG_AUD_FS1) | + BIT(CPCAP_BIT_ST_L_TIMESLOT0) | + BIT(CPCAP_BIT_ST_L_TIMESLOT1) | + BIT(CPCAP_BIT_ST_L_TIMESLOT2) | + BIT(CPCAP_BIT_ST_R_TIMESLOT0) | + BIT(CPCAP_BIT_ST_R_TIMESLOT1) | + BIT(CPCAP_BIT_ST_R_TIMESLOT2); + u16 val = 0x0000; + + dev_dbg(dev, "HiFi setup dai format (%08x)", fmt); + + /* + * "HiFi Playback" should always be configured as + * SND_SOC_DAIFMT_CBM_CFM - codec clk & frm master + * SND_SOC_DAIFMT_I2S - I2S mode + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val &= ~BIT(CPCAP_BIT_SMB_ST_DAC); + break; + default: + dev_err(dev, "HiFi dai fmt failed: CPCAP should be master"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + val |= BIT(CPCAP_BIT_ST_FS_INV); + val |= BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_IB_NF: + val &= ~BIT(CPCAP_BIT_ST_FS_INV); + val |= BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_NB_IF: + val |= BIT(CPCAP_BIT_ST_FS_INV); + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_NB_NF: + val &= ~BIT(CPCAP_BIT_ST_FS_INV); + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + break; + default: + dev_err(dev, "HiFi dai fmt failed: unsupported clock invert mode"); + return -EINVAL; + } + + if (val & BIT(CPCAP_BIT_ST_CLK_INV)) + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + else + val |= BIT(CPCAP_BIT_ST_CLK_INV); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0); + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS1); + break; + default: + /* 01 - 4 slots network mode */ + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0); + val &= ~BIT(CPCAP_BIT_ST_DIG_AUD_FS1); + /* L on slot 1 */ + val |= BIT(CPCAP_BIT_ST_L_TIMESLOT0); + break; + } + + dev_dbg(dev, "HiFi dai format: val=%04x", val); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +} + +static int cpcap_hifi_set_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + static const u16 reg = CPCAP_REG_RXSDOA; + static const u16 mask = BIT(CPCAP_BIT_ST_DAC_SW); + u16 val; + + if (mute) + val = 0; + else + val = BIT(CPCAP_BIT_ST_DAC_SW); + + dev_dbg(component->dev, "HiFi mute: %d", mute); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +} + +static const struct snd_soc_dai_ops cpcap_dai_hifi_ops = { + .hw_params = cpcap_hifi_hw_params, + .set_sysclk = cpcap_hifi_set_dai_sysclk, + .set_fmt = cpcap_hifi_set_dai_fmt, + .digital_mute = cpcap_hifi_set_mute, +}; + +static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct device *dev = component->dev; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + static const u16 reg_cdi = CPCAP_REG_CDI; + int rate = params_rate(params); + int channels = params_channels(params); + int direction = substream->stream; + u16 val, mask; + int err; + + dev_dbg(dev, "Voice setup HW params: rate=%d, direction=%d, chan=%d", + rate, direction, channels); + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, rate); + if (err) + return err; + + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + mask = 0x0000; + mask |= BIT(CPCAP_BIT_MIC1_RX_TIMESLOT0); + mask |= BIT(CPCAP_BIT_MIC1_RX_TIMESLOT1); + mask |= BIT(CPCAP_BIT_MIC1_RX_TIMESLOT2); + mask |= BIT(CPCAP_BIT_MIC2_TIMESLOT0); + mask |= BIT(CPCAP_BIT_MIC2_TIMESLOT1); + mask |= BIT(CPCAP_BIT_MIC2_TIMESLOT2); + val = 0x0000; + if (channels >= 2) + val = BIT(CPCAP_BIT_MIC1_RX_TIMESLOT0); + err = regmap_update_bits(cpcap->regmap, reg_cdi, mask, val); + if (err) + return err; + } + + return 0; +} + +static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "Voice setup sysclk: clk_id=%u, freq=%u", + clk_id, freq); + return cpcap_set_sysclk(cpcap, CPCAP_DAI_VOICE, clk_id, freq); +} + +static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + static const u16 mask = BIT(CPCAP_BIT_SMB_CDC) | + BIT(CPCAP_BIT_CLK_INV) | + BIT(CPCAP_BIT_FS_INV) | + BIT(CPCAP_BIT_CDC_DIG_AUD_FS0) | + BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + u16 val = 0x0000; + int err; + + dev_dbg(component->dev, "Voice setup dai format (%08x)", fmt); + + /* + * "Voice Playback" and "Voice Capture" should always be + * configured as SND_SOC_DAIFMT_CBM_CFM - codec clk & frm + * master + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val &= ~BIT(CPCAP_BIT_SMB_CDC); + break; + default: + dev_err(component->dev, "Voice dai fmt failed: CPCAP should be the master"); + val &= ~BIT(CPCAP_BIT_SMB_CDC); + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + val |= BIT(CPCAP_BIT_CLK_INV); + val |= BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_IB_NF: + val |= BIT(CPCAP_BIT_CLK_INV); + val &= ~BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_NB_IF: + val &= ~BIT(CPCAP_BIT_CLK_INV); + val |= BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_NB_NF: + val &= ~BIT(CPCAP_BIT_CLK_INV); + val &= ~BIT(CPCAP_BIT_FS_INV); + break; + default: + dev_err(component->dev, "Voice dai fmt failed: unsupported clock invert mode"); + break; + } + + if (val & BIT(CPCAP_BIT_CLK_INV)) + val &= ~BIT(CPCAP_BIT_CLK_INV); + else + val |= BIT(CPCAP_BIT_CLK_INV); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* 11 - true I2S mode */ + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS0); + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + break; + default: + /* 4 timeslots network mode */ + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS0); + val &= ~BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + break; + } + + dev_dbg(component->dev, "Voice dai format: val=%04x", val); + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, mask, val); + if (err) + return err; + + cpcap->codec_format = val; + return 0; +} + +static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + static const u16 reg = CPCAP_REG_RXCOA; + static const u16 mask = BIT(CPCAP_BIT_CDC_SW); + u16 val; + + if (mute) + val = 0; + else + val = BIT(CPCAP_BIT_CDC_SW); + + dev_dbg(component->dev, "Voice mute: %d", mute); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +}; + +static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, + .digital_mute = cpcap_voice_set_mute, +}; + +static struct snd_soc_dai_driver cpcap_dai[] = { +{ + .id = 0, + .name = "cpcap-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE, + }, + .ops = &cpcap_dai_hifi_ops, +}, +{ + .id = 1, + .name = "cpcap-voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &cpcap_dai_voice_ops, +}, +}; + +static int cpcap_dai_mux(struct cpcap_audio *cpcap, bool swap_dai_configuration) +{ + u16 hifi_val, voice_val; + u16 hifi_mask = BIT(CPCAP_BIT_DIG_AUD_IN_ST_DAC); + u16 voice_mask = BIT(CPCAP_BIT_DIG_AUD_IN); + int err; + + + + if (!swap_dai_configuration) { + /* Codec on DAI0, HiFi on DAI1 */ + voice_val = 0; + hifi_val = hifi_mask; + } else { + /* Codec on DAI1, HiFi on DAI0 */ + voice_val = voice_mask; + hifi_val = 0; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + voice_mask, voice_val); + if (err) + return err; + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_SDACDI, + hifi_mask, hifi_val); + if (err) + return err; + + return 0; +} + +static int cpcap_audio_reset(struct snd_soc_component *component, + bool swap_dai_configuration) +{ + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + int i, err = 0; + + dev_dbg(component->dev, "init audio codec"); + + for (i = 0; i < ARRAY_SIZE(cpcap_default_regs); i++) { + err = regmap_update_bits(cpcap->regmap, + cpcap_default_regs[i].reg, + cpcap_default_regs[i].mask, + cpcap_default_regs[i].val); + if (err) + return err; + } + + /* setup default settings */ + err = cpcap_dai_mux(cpcap, swap_dai_configuration); + if (err) + return err; + + err = cpcap_set_sysclk(cpcap, CPCAP_DAI_HIFI, 0, 26000000); + if (err) + return err; + err = cpcap_set_sysclk(cpcap, CPCAP_DAI_VOICE, 0, 26000000); + if (err) + return err; + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_HIFI, 48000); + if (err) + return err; + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, 48000); + if (err) + return err; + + return 0; +} + +static int cpcap_soc_probe(struct snd_soc_component *component) +{ + struct cpcap_audio *cpcap; + int err; + + cpcap = devm_kzalloc(component->dev, sizeof(*cpcap), GFP_KERNEL); + if (!cpcap) + return -ENOMEM; + snd_soc_component_set_drvdata(component, cpcap); + cpcap->component = component; + + cpcap->regmap = dev_get_regmap(component->dev->parent, NULL); + if (!cpcap->regmap) + return -ENODEV; + snd_soc_component_init_regmap(component, cpcap->regmap); + + err = cpcap_get_vendor(component->dev, cpcap->regmap, &cpcap->vendor); + if (err) + return err; + + return cpcap_audio_reset(component, false); +} + +static struct snd_soc_component_driver soc_codec_dev_cpcap = { + .probe = cpcap_soc_probe, + .controls = cpcap_snd_controls, + .num_controls = ARRAY_SIZE(cpcap_snd_controls), + .dapm_widgets = cpcap_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cpcap_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int cpcap_codec_probe(struct platform_device *pdev) +{ + struct device_node *codec_node = + of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec"); + if (!codec_node) + return -ENODEV; + + pdev->dev.of_node = codec_node; + + return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cpcap, + cpcap_dai, ARRAY_SIZE(cpcap_dai)); +} + +static struct platform_driver cpcap_codec_driver = { + .probe = cpcap_codec_probe, + .driver = { + .name = "cpcap-codec", + }, +}; +module_platform_driver(cpcap_codec_driver); + +MODULE_ALIAS("platform:cpcap-codec"); +MODULE_DESCRIPTION("ASoC CPCAP codec driver"); +MODULE_AUTHOR("Sebastian Reichel"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c new file mode 100644 index 000000000..3301861d3 --- /dev/null +++ b/sound/soc/codecs/cq93vc.c @@ -0,0 +1,168 @@ +/* + * ALSA SoC CQ0093 Voice Codec Driver for DaVinci platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static const struct snd_kcontrol_new cq93vc_snd_controls[] = { + SOC_SINGLE("PGA Capture Volume", DAVINCI_VC_REG05, 0, 0x03, 0), + SOC_SINGLE("Mono DAC Playback Volume", DAVINCI_VC_REG09, 0, 0x3f, 0), +}; + +static int cq93vc_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u8 reg; + + if (mute) + reg = DAVINCI_VC_REG09_MUTE; + else + reg = 0; + + snd_soc_component_update_bits(component, DAVINCI_VC_REG09, DAVINCI_VC_REG09_MUTE, + reg); + + return 0; +} + +static int cq93vc_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + switch (freq) { + case 22579200: + case 27000000: + case 33868800: + return 0; + } + + return -EINVAL; +} + +static int cq93vc_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_component_write(component, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_ON); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_component_write(component, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_OFF); + break; + case SND_SOC_BIAS_OFF: + /* force all power off */ + snd_soc_component_write(component, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_OFF); + break; + } + + return 0; +} + +#define CQ93VC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) +#define CQ93VC_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE) + +static const struct snd_soc_dai_ops cq93vc_dai_ops = { + .digital_mute = cq93vc_mute, + .set_sysclk = cq93vc_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver cq93vc_dai = { + .name = "cq93vc-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .ops = &cq93vc_dai_ops, +}; + +static int cq93vc_probe(struct snd_soc_component *component) +{ + struct davinci_vc *davinci_vc = component->dev->platform_data; + + snd_soc_component_init_regmap(component, davinci_vc->regmap); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_cq93vc = { + .set_bias_level = cq93vc_set_bias_level, + .probe = cq93vc_probe, + .controls = cq93vc_snd_controls, + .num_controls = ARRAY_SIZE(cq93vc_snd_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int cq93vc_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_cq93vc, &cq93vc_dai, 1); +} + +static int cq93vc_platform_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver cq93vc_codec_driver = { + .driver = { + .name = "cq93vc-codec", + }, + + .probe = cq93vc_platform_probe, + .remove = cq93vc_platform_remove, +}; + +module_platform_driver(cq93vc_codec_driver); + +MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC CQ0093 Voice Codec Driver"); +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c new file mode 100644 index 000000000..4297058b6 --- /dev/null +++ b/sound/soc/codecs/cs35l32.c @@ -0,0 +1,586 @@ +/* + * cs35l32.c -- CS35L32 ALSA SoC audio driver + * + * Copyright 2014 CirrusLogic, Inc. + * + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs35l32.h" + +#define CS35L32_NUM_SUPPLIES 2 +static const char *const cs35l32_supply_names[CS35L32_NUM_SUPPLIES] = { + "VA", + "VP", +}; + +struct cs35l32_private { + struct regmap *regmap; + struct snd_soc_component *component; + struct regulator_bulk_data supplies[CS35L32_NUM_SUPPLIES]; + struct cs35l32_platform_data pdata; + struct gpio_desc *reset_gpio; +}; + +static const struct reg_default cs35l32_reg_defaults[] = { + + { 0x06, 0x04 }, /* Power Ctl 1 */ + { 0x07, 0xE8 }, /* Power Ctl 2 */ + { 0x08, 0x40 }, /* Clock Ctl */ + { 0x09, 0x20 }, /* Low Battery Threshold */ + { 0x0A, 0x00 }, /* Voltage Monitor [RO] */ + { 0x0B, 0x40 }, /* Conv Peak Curr Protection CTL */ + { 0x0C, 0x07 }, /* IMON Scaling */ + { 0x0D, 0x03 }, /* Audio/LED Pwr Manager */ + { 0x0F, 0x20 }, /* Serial Port Control */ + { 0x10, 0x14 }, /* Class D Amp CTL */ + { 0x11, 0x00 }, /* Protection Release CTL */ + { 0x12, 0xFF }, /* Interrupt Mask 1 */ + { 0x13, 0xFF }, /* Interrupt Mask 2 */ + { 0x14, 0xFF }, /* Interrupt Mask 3 */ + { 0x19, 0x00 }, /* LED Flash Mode Current */ + { 0x1A, 0x00 }, /* LED Movie Mode Current */ + { 0x1B, 0x20 }, /* LED Flash Timer */ + { 0x1C, 0x00 }, /* LED Flash Inhibit Current */ +}; + +static bool cs35l32_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L32_DEVID_AB ... CS35L32_AUDIO_LED_MNGR: + case CS35L32_ADSP_CTL ... CS35L32_FLASH_INHIBIT: + return true; + default: + return false; + } +} + +static bool cs35l32_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L32_DEVID_AB ... CS35L32_REV_ID: + case CS35L32_INT_STATUS_1 ... CS35L32_LED_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l32_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L32_INT_STATUS_1 ... CS35L32_LED_STATUS: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(classd_ctl_tlv, 900, 300, 0); + +static const struct snd_kcontrol_new imon_ctl = + SOC_DAPM_SINGLE("Switch", CS35L32_PWRCTL2, 6, 1, 1); + +static const struct snd_kcontrol_new vmon_ctl = + SOC_DAPM_SINGLE("Switch", CS35L32_PWRCTL2, 7, 1, 1); + +static const struct snd_kcontrol_new vpmon_ctl = + SOC_DAPM_SINGLE("Switch", CS35L32_PWRCTL2, 5, 1, 1); + +static const struct snd_kcontrol_new cs35l32_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", CS35L32_CLASSD_CTL, + 3, 0x04, 1, classd_ctl_tlv), + SOC_SINGLE("Zero Cross Switch", CS35L32_CLASSD_CTL, 2, 1, 0), + SOC_SINGLE("Gain Manager Switch", CS35L32_AUDIO_LED_MNGR, 3, 1, 0), +}; + +static const struct snd_soc_dapm_widget cs35l32_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY("BOOST", CS35L32_PWRCTL1, 2, 1, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Speaker", CS35L32_PWRCTL1, 7, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L32_PWRCTL2, 3, 1), + + SND_SOC_DAPM_INPUT("VP"), + SND_SOC_DAPM_INPUT("ISENSE"), + SND_SOC_DAPM_INPUT("VSENSE"), + + SND_SOC_DAPM_SWITCH("VMON ADC", CS35L32_PWRCTL2, 7, 1, &vmon_ctl), + SND_SOC_DAPM_SWITCH("IMON ADC", CS35L32_PWRCTL2, 6, 1, &imon_ctl), + SND_SOC_DAPM_SWITCH("VPMON ADC", CS35L32_PWRCTL2, 5, 1, &vpmon_ctl), +}; + +static const struct snd_soc_dapm_route cs35l32_audio_map[] = { + + {"Speaker", NULL, "BOOST"}, + + {"VMON ADC", NULL, "VSENSE"}, + {"IMON ADC", NULL, "ISENSE"}, + {"VPMON ADC", NULL, "VP"}, + + {"SDOUT", "Switch", "VMON ADC"}, + {"SDOUT", "Switch", "IMON ADC"}, + {"SDOUT", "Switch", "VPMON ADC"}, + + {"Capture", NULL, "SDOUT"}, +}; + +static int cs35l32_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_component_update_bits(component, CS35L32_ADSP_CTL, + CS35L32_ADSP_MASTER_MASK, + CS35L32_ADSP_MASTER_MASK); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_component_update_bits(component, CS35L32_ADSP_CTL, + CS35L32_ADSP_MASTER_MASK, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cs35l32_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_component *component = dai->component; + + return snd_soc_component_update_bits(component, CS35L32_PWRCTL2, + CS35L32_SDOUT_3ST, tristate << 3); +} + +static const struct snd_soc_dai_ops cs35l32_ops = { + .set_fmt = cs35l32_set_dai_fmt, + .set_tristate = cs35l32_set_tristate, +}; + +static struct snd_soc_dai_driver cs35l32_dai[] = { + { + .name = "cs35l32-monitor", + .id = 0, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = CS35L32_RATES, + .formats = CS35L32_FORMATS, + }, + .ops = &cs35l32_ops, + .symmetric_rates = 1, + } +}; + +static int cs35l32_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + unsigned int val; + + switch (freq) { + case 6000000: + val = CS35L32_MCLK_RATIO; + break; + case 12000000: + val = CS35L32_MCLK_DIV2_MASK | CS35L32_MCLK_RATIO; + break; + case 6144000: + val = 0; + break; + case 12288000: + val = CS35L32_MCLK_DIV2_MASK; + break; + default: + return -EINVAL; + } + + return snd_soc_component_update_bits(component, CS35L32_CLK_CTL, + CS35L32_MCLK_DIV2_MASK | CS35L32_MCLK_RATIO_MASK, val); +} + +static const struct snd_soc_component_driver soc_component_dev_cs35l32 = { + .set_sysclk = cs35l32_component_set_sysclk, + .controls = cs35l32_snd_controls, + .num_controls = ARRAY_SIZE(cs35l32_snd_controls), + .dapm_widgets = cs35l32_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l32_dapm_widgets), + .dapm_routes = cs35l32_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l32_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* Current and threshold powerup sequence Pg37 in datasheet */ +static const struct reg_sequence cs35l32_monitor_patch[] = { + + { 0x00, 0x99 }, + { 0x48, 0x17 }, + { 0x49, 0x56 }, + { 0x43, 0x01 }, + { 0x3B, 0x62 }, + { 0x3C, 0x80 }, + { 0x00, 0x00 }, +}; + +static const struct regmap_config cs35l32_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L32_MAX_REGISTER, + .reg_defaults = cs35l32_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs35l32_reg_defaults), + .volatile_reg = cs35l32_volatile_register, + .readable_reg = cs35l32_readable_register, + .precious_reg = cs35l32_precious_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs35l32_handle_of_data(struct i2c_client *i2c_client, + struct cs35l32_platform_data *pdata) +{ + struct device_node *np = i2c_client->dev.of_node; + unsigned int val; + + if (of_property_read_u32(np, "cirrus,sdout-share", &val) >= 0) + pdata->sdout_share = val; + + if (of_property_read_u32(np, "cirrus,boost-manager", &val)) + val = -1u; + + switch (val) { + case CS35L32_BOOST_MGR_AUTO: + case CS35L32_BOOST_MGR_AUTO_AUDIO: + case CS35L32_BOOST_MGR_BYPASS: + case CS35L32_BOOST_MGR_FIXED: + pdata->boost_mng = val; + break; + case -1u: + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,boost-manager DT value %d\n", val); + pdata->boost_mng = CS35L32_BOOST_MGR_BYPASS; + } + + if (of_property_read_u32(np, "cirrus,sdout-datacfg", &val)) + val = -1u; + switch (val) { + case CS35L32_DATA_CFG_LR_VP: + case CS35L32_DATA_CFG_LR_STAT: + case CS35L32_DATA_CFG_LR: + case CS35L32_DATA_CFG_LR_VPSTAT: + pdata->sdout_datacfg = val; + break; + case -1u: + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,sdout-datacfg DT value %d\n", val); + pdata->sdout_datacfg = CS35L32_DATA_CFG_LR; + } + + if (of_property_read_u32(np, "cirrus,battery-threshold", &val)) + val = -1u; + switch (val) { + case CS35L32_BATT_THRESH_3_1V: + case CS35L32_BATT_THRESH_3_2V: + case CS35L32_BATT_THRESH_3_3V: + case CS35L32_BATT_THRESH_3_4V: + pdata->batt_thresh = val; + break; + case -1u: + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,battery-threshold DT value %d\n", val); + pdata->batt_thresh = CS35L32_BATT_THRESH_3_3V; + } + + if (of_property_read_u32(np, "cirrus,battery-recovery", &val)) + val = -1u; + switch (val) { + case CS35L32_BATT_RECOV_3_1V: + case CS35L32_BATT_RECOV_3_2V: + case CS35L32_BATT_RECOV_3_3V: + case CS35L32_BATT_RECOV_3_4V: + case CS35L32_BATT_RECOV_3_5V: + case CS35L32_BATT_RECOV_3_6V: + pdata->batt_recov = val; + break; + case -1u: + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,battery-recovery DT value %d\n", val); + pdata->batt_recov = CS35L32_BATT_RECOV_3_4V; + } + + return 0; +} + +static int cs35l32_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l32_private *cs35l32; + struct cs35l32_platform_data *pdata = + dev_get_platdata(&i2c_client->dev); + int ret, i; + unsigned int devid = 0; + unsigned int reg; + + cs35l32 = devm_kzalloc(&i2c_client->dev, sizeof(*cs35l32), GFP_KERNEL); + if (!cs35l32) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs35l32); + + cs35l32->regmap = devm_regmap_init_i2c(i2c_client, &cs35l32_regmap); + if (IS_ERR(cs35l32->regmap)) { + ret = PTR_ERR(cs35l32->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + if (pdata) { + cs35l32->pdata = *pdata; + } else { + pdata = devm_kzalloc(&i2c_client->dev, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (i2c_client->dev.of_node) { + ret = cs35l32_handle_of_data(i2c_client, + &cs35l32->pdata); + if (ret != 0) + return ret; + } + } + + for (i = 0; i < ARRAY_SIZE(cs35l32->supplies); i++) + cs35l32->supplies[i].supply = cs35l32_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c_client->dev, + ARRAY_SIZE(cs35l32->supplies), + cs35l32->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs35l32->supplies), + cs35l32->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable supplies: %d\n", ret); + return ret; + } + + /* Reset the Device */ + cs35l32->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(cs35l32->reset_gpio)) + return PTR_ERR(cs35l32->reset_gpio); + + gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + + /* initialize codec */ + ret = regmap_read(cs35l32->regmap, CS35L32_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + + ret = regmap_read(cs35l32->regmap, CS35L32_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + + ret = regmap_read(cs35l32->regmap, CS35L32_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L32_CHIP_ID) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS35L32 Device ID (%X). Expected %X\n", + devid, CS35L32_CHIP_ID); + return ret; + } + + ret = regmap_read(cs35l32->regmap, CS35L32_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed\n"); + return ret; + } + + ret = regmap_register_patch(cs35l32->regmap, cs35l32_monitor_patch, + ARRAY_SIZE(cs35l32_monitor_patch)); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to apply errata patch\n"); + return ret; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS35L32, Revision: %02X\n", reg & 0xFF); + + /* Setup VBOOST Management */ + if (cs35l32->pdata.boost_mng) + regmap_update_bits(cs35l32->regmap, CS35L32_AUDIO_LED_MNGR, + CS35L32_BOOST_MASK, + cs35l32->pdata.boost_mng); + + /* Setup ADSP Format Config */ + if (cs35l32->pdata.sdout_share) + regmap_update_bits(cs35l32->regmap, CS35L32_ADSP_CTL, + CS35L32_ADSP_SHARE_MASK, + cs35l32->pdata.sdout_share << 3); + + /* Setup ADSP Data Configuration */ + if (cs35l32->pdata.sdout_datacfg) + regmap_update_bits(cs35l32->regmap, CS35L32_ADSP_CTL, + CS35L32_ADSP_DATACFG_MASK, + cs35l32->pdata.sdout_datacfg << 4); + + /* Setup Low Battery Recovery */ + if (cs35l32->pdata.batt_recov) + regmap_update_bits(cs35l32->regmap, CS35L32_BATT_THRESHOLD, + CS35L32_BATT_REC_MASK, + cs35l32->pdata.batt_recov << 1); + + /* Setup Low Battery Threshold */ + if (cs35l32->pdata.batt_thresh) + regmap_update_bits(cs35l32->regmap, CS35L32_BATT_THRESHOLD, + CS35L32_BATT_THRESH_MASK, + cs35l32->pdata.batt_thresh << 4); + + /* Power down the AMP */ + regmap_update_bits(cs35l32->regmap, CS35L32_PWRCTL1, CS35L32_PDN_AMP, + CS35L32_PDN_AMP); + + /* Clear MCLK Error Bit since we don't have the clock yet */ + ret = regmap_read(cs35l32->regmap, CS35L32_INT_STATUS_1, ®); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_cs35l32, cs35l32_dai, + ARRAY_SIZE(cs35l32_dai)); + if (ret < 0) + goto err_disable; + + return 0; + +err_disable: + regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), + cs35l32->supplies); + return ret; +} + +static int cs35l32_i2c_remove(struct i2c_client *i2c_client) +{ + struct cs35l32_private *cs35l32 = i2c_get_clientdata(i2c_client); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); + + return 0; +} + +#ifdef CONFIG_PM +static int cs35l32_runtime_suspend(struct device *dev) +{ + struct cs35l32_private *cs35l32 = dev_get_drvdata(dev); + + regcache_cache_only(cs35l32->regmap, true); + regcache_mark_dirty(cs35l32->regmap); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); + + /* remove power */ + regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), + cs35l32->supplies); + + return 0; +} + +static int cs35l32_runtime_resume(struct device *dev) +{ + struct cs35l32_private *cs35l32 = dev_get_drvdata(dev); + int ret; + + /* Enable power */ + ret = regulator_bulk_enable(ARRAY_SIZE(cs35l32->supplies), + cs35l32->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", + ret); + return ret; + } + + gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + + regcache_cache_only(cs35l32->regmap, false); + regcache_sync(cs35l32->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops cs35l32_runtime_pm = { + SET_RUNTIME_PM_OPS(cs35l32_runtime_suspend, cs35l32_runtime_resume, + NULL) +}; + +static const struct of_device_id cs35l32_of_match[] = { + { .compatible = "cirrus,cs35l32", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l32_of_match); + + +static const struct i2c_device_id cs35l32_id[] = { + {"cs35l32", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l32_id); + +static struct i2c_driver cs35l32_i2c_driver = { + .driver = { + .name = "cs35l32", + .pm = &cs35l32_runtime_pm, + .of_match_table = cs35l32_of_match, + }, + .id_table = cs35l32_id, + .probe = cs35l32_i2c_probe, + .remove = cs35l32_i2c_remove, +}; + +module_i2c_driver(cs35l32_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L32 driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l32.h b/sound/soc/codecs/cs35l32.h new file mode 100644 index 000000000..1d6c2508c --- /dev/null +++ b/sound/soc/codecs/cs35l32.h @@ -0,0 +1,93 @@ +/* + * cs35l32.h -- CS35L32 ALSA SoC audio driver + * + * Copyright 2014 CirrusLogic, Inc. + * + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS35L32_H__ +#define __CS35L32_H__ + +struct cs35l32_platform_data { + /* Low Battery Threshold */ + unsigned int batt_thresh; + /* Low Battery Recovery */ + unsigned int batt_recov; + /* LED Current Management*/ + unsigned int led_mng; + /* Audio Gain w/ LED */ + unsigned int audiogain_mng; + /* Boost Management */ + unsigned int boost_mng; + /* Data CFG for DUAL device */ + unsigned int sdout_datacfg; + /* SDOUT Sharing */ + unsigned int sdout_share; +}; + +#define CS35L32_CHIP_ID 0x00035A32 +#define CS35L32_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L32_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L32_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L32_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L32_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L32_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L32_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L32_CLK_CTL 0x08 /* Clock Ctl */ +#define CS35L32_BATT_THRESHOLD 0x09 /* Low Battery Threshold */ +#define CS35L32_VMON 0x0A /* Voltage Monitor [RO] */ +#define CS35L32_BST_CPCP_CTL 0x0B /* Conv Peak Curr Protection CTL */ +#define CS35L32_IMON_SCALING 0x0C /* IMON Scaling */ +#define CS35L32_AUDIO_LED_MNGR 0x0D /* Audio/LED Pwr Manager */ +#define CS35L32_ADSP_CTL 0x0F /* Serial Port Control */ +#define CS35L32_CLASSD_CTL 0x10 /* Class D Amp CTL */ +#define CS35L32_PROTECT_CTL 0x11 /* Protection Release CTL */ +#define CS35L32_INT_MASK_1 0x12 /* Interrupt Mask 1 */ +#define CS35L32_INT_MASK_2 0x13 /* Interrupt Mask 2 */ +#define CS35L32_INT_MASK_3 0x14 /* Interrupt Mask 3 */ +#define CS35L32_INT_STATUS_1 0x15 /* Interrupt Status 1 [RO] */ +#define CS35L32_INT_STATUS_2 0x16 /* Interrupt Status 2 [RO] */ +#define CS35L32_INT_STATUS_3 0x17 /* Interrupt Status 3 [RO] */ +#define CS35L32_LED_STATUS 0x18 /* LED Lighting Status [RO] */ +#define CS35L32_FLASH_MODE 0x19 /* LED Flash Mode Current */ +#define CS35L32_MOVIE_MODE 0x1A /* LED Movie Mode Current */ +#define CS35L32_FLASH_TIMER 0x1B /* LED Flash Timer */ +#define CS35L32_FLASH_INHIBIT 0x1C /* LED Flash Inhibit Current */ +#define CS35L32_MAX_REGISTER 0x1C + +#define CS35L32_MCLK_DIV2 0x01 +#define CS35L32_MCLK_RATIO 0x01 +#define CS35L32_MCLKDIS 0x80 +#define CS35L32_PDN_ALL 0x01 +#define CS35L32_PDN_AMP 0x80 +#define CS35L32_PDN_BOOST 0x04 +#define CS35L32_PDN_IMON 0x40 +#define CS35L32_PDN_VMON 0x80 +#define CS35L32_PDN_VPMON 0x20 +#define CS35L32_PDN_ADSP 0x08 + +#define CS35L32_MCLK_DIV2_MASK 0x40 +#define CS35L32_MCLK_RATIO_MASK 0x01 +#define CS35L32_MCLK_MASK 0x41 +#define CS35L32_ADSP_MASTER_MASK 0x40 +#define CS35L32_BOOST_MASK 0x03 +#define CS35L32_GAIN_MGR_MASK 0x08 +#define CS35L32_ADSP_SHARE_MASK 0x08 +#define CS35L32_ADSP_DATACFG_MASK 0x30 +#define CS35L32_SDOUT_3ST 0x08 +#define CS35L32_BATT_REC_MASK 0x0E +#define CS35L32_BATT_THRESH_MASK 0x30 + +#define CS35L32_RATES (SNDRV_PCM_RATE_48000) +#define CS35L32_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + + +#endif diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c new file mode 100644 index 000000000..73fa78464 --- /dev/null +++ b/sound/soc/codecs/cs35l33.c @@ -0,0 +1,1297 @@ +/* + * cs35l33.c -- CS35L33 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs35l33.h" + +#define CS35L33_BOOT_DELAY 50 + +struct cs35l33_private { + struct snd_soc_component *component; + struct cs35l33_pdata pdata; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + bool amp_cal; + int mclk_int; + struct regulator_bulk_data core_supplies[2]; + int num_core_supplies; + bool is_tdm_mode; + bool enable_soft_ramp; +}; + +static const struct reg_default cs35l33_reg[] = { + {CS35L33_PWRCTL1, 0x85}, + {CS35L33_PWRCTL2, 0xFE}, + {CS35L33_CLK_CTL, 0x0C}, + {CS35L33_BST_PEAK_CTL, 0x90}, + {CS35L33_PROTECT_CTL, 0x55}, + {CS35L33_BST_CTL1, 0x00}, + {CS35L33_BST_CTL2, 0x01}, + {CS35L33_ADSP_CTL, 0x00}, + {CS35L33_ADC_CTL, 0xC8}, + {CS35L33_DAC_CTL, 0x14}, + {CS35L33_DIG_VOL_CTL, 0x00}, + {CS35L33_CLASSD_CTL, 0x04}, + {CS35L33_AMP_CTL, 0x90}, + {CS35L33_INT_MASK_1, 0xFF}, + {CS35L33_INT_MASK_2, 0xFF}, + {CS35L33_DIAG_LOCK, 0x00}, + {CS35L33_DIAG_CTRL_1, 0x40}, + {CS35L33_DIAG_CTRL_2, 0x00}, + {CS35L33_HG_MEMLDO_CTL, 0x62}, + {CS35L33_HG_REL_RATE, 0x03}, + {CS35L33_LDO_DEL, 0x12}, + {CS35L33_HG_HEAD, 0x0A}, + {CS35L33_HG_EN, 0x05}, + {CS35L33_TX_VMON, 0x00}, + {CS35L33_TX_IMON, 0x03}, + {CS35L33_TX_VPMON, 0x02}, + {CS35L33_TX_VBSTMON, 0x05}, + {CS35L33_TX_FLAG, 0x06}, + {CS35L33_TX_EN1, 0x00}, + {CS35L33_TX_EN2, 0x00}, + {CS35L33_TX_EN3, 0x00}, + {CS35L33_TX_EN4, 0x00}, + {CS35L33_RX_AUD, 0x40}, + {CS35L33_RX_SPLY, 0x03}, + {CS35L33_RX_ALIVE, 0x04}, + {CS35L33_BST_CTL4, 0x63}, +}; + +static const struct reg_sequence cs35l33_patch[] = { + { 0x00, 0x99, 0 }, + { 0x59, 0x02, 0 }, + { 0x52, 0x30, 0 }, + { 0x39, 0x45, 0 }, + { 0x57, 0x30, 0 }, + { 0x2C, 0x68, 0 }, + { 0x00, 0x00, 0 }, +}; + +static bool cs35l33_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_HG_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l33_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + /* these are read only registers */ + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_HG_STATUS: + return false; + default: + return true; + } +} + +static bool cs35l33_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_PWRCTL1: + case CS35L33_PWRCTL2: + case CS35L33_CLK_CTL: + case CS35L33_BST_PEAK_CTL: + case CS35L33_PROTECT_CTL: + case CS35L33_BST_CTL1: + case CS35L33_BST_CTL2: + case CS35L33_ADSP_CTL: + case CS35L33_ADC_CTL: + case CS35L33_DAC_CTL: + case CS35L33_DIG_VOL_CTL: + case CS35L33_CLASSD_CTL: + case CS35L33_AMP_CTL: + case CS35L33_INT_MASK_1: + case CS35L33_INT_MASK_2: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_DIAG_LOCK: + case CS35L33_DIAG_CTRL_1: + case CS35L33_DIAG_CTRL_2: + case CS35L33_HG_MEMLDO_CTL: + case CS35L33_HG_REL_RATE: + case CS35L33_LDO_DEL: + case CS35L33_HG_HEAD: + case CS35L33_HG_EN: + case CS35L33_TX_VMON: + case CS35L33_TX_IMON: + case CS35L33_TX_VPMON: + case CS35L33_TX_VBSTMON: + case CS35L33_TX_FLAG: + case CS35L33_TX_EN1: + case CS35L33_TX_EN2: + case CS35L33_TX_EN3: + case CS35L33_TX_EN4: + case CS35L33_RX_AUD: + case CS35L33_RX_SPLY: + case CS35L33_RX_ALIVE: + case CS35L33_BST_CTL4: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(classd_ctl_tlv, 900, 100, 0); +static DECLARE_TLV_DB_SCALE(dac_tlv, -10200, 50, 0); + +static const struct snd_kcontrol_new cs35l33_snd_controls[] = { + + SOC_SINGLE_TLV("SPK Amp Volume", CS35L33_AMP_CTL, + 4, 0x09, 0, classd_ctl_tlv), + SOC_SINGLE_SX_TLV("DAC Volume", CS35L33_DIG_VOL_CTL, + 0, 0x34, 0xE4, dac_tlv), +}; + +static int cs35l33_spkrdrv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!priv->amp_cal) { + usleep_range(8000, 9000); + priv->amp_cal = true; + regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_CAL, 0); + dev_dbg(component->dev, "Amp calibration done\n"); + } + dev_dbg(component->dev, "Amp turned on\n"); + break; + case SND_SOC_DAPM_POST_PMD: + dev_dbg(component->dev, "Amp turned off\n"); + break; + default: + dev_err(component->dev, "Invalid event = 0x%x\n", event); + break; + } + + return 0; +} + +static int cs35l33_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + unsigned int val; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_BST, 0); + val = priv->is_tdm_mode ? 0 : CS35L33_PDN_TDM; + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_PDN_TDM, val); + dev_dbg(component->dev, "BST turned on\n"); + break; + case SND_SOC_DAPM_POST_PMU: + dev_dbg(component->dev, "SDIN turned on\n"); + if (!priv->amp_cal) { + regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_CAL, CS35L33_AMP_CAL); + dev_dbg(component->dev, "Amp calibration started\n"); + usleep_range(10000, 11000); + } + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_PDN_TDM, CS35L33_PDN_TDM); + usleep_range(4000, 4100); + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_BST, CS35L33_PDN_BST); + dev_dbg(component->dev, "BST and SDIN turned off\n"); + break; + default: + dev_err(component->dev, "Invalid event = 0x%x\n", event); + + } + + return 0; +} + +static int cs35l33_sdout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + unsigned int mask = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM; + unsigned int mask2 = CS35L33_SDOUT_3ST_TDM; + unsigned int val, val2; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (priv->is_tdm_mode) { + /* set sdout_3st_i2s and reset pdn_tdm */ + val = CS35L33_SDOUT_3ST_I2S; + /* reset sdout_3st_tdm */ + val2 = 0; + } else { + /* reset sdout_3st_i2s and set pdn_tdm */ + val = CS35L33_PDN_TDM; + /* set sdout_3st_tdm */ + val2 = CS35L33_SDOUT_3ST_TDM; + } + dev_dbg(component->dev, "SDOUT turned on\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + val = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM; + val2 = CS35L33_SDOUT_3ST_TDM; + dev_dbg(component->dev, "SDOUT turned off\n"); + break; + default: + dev_err(component->dev, "Invalid event = 0x%x\n", event); + return 0; + } + + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + mask, val); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + mask2, val2); + + return 0; +} + +static const struct snd_soc_dapm_widget cs35l33_dapm_widgets[] = { + + SND_SOC_DAPM_OUTPUT("SPK"), + SND_SOC_DAPM_OUT_DRV_E("SPKDRV", CS35L33_PWRCTL1, 7, 1, NULL, 0, + cs35l33_spkrdrv_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L33_PWRCTL2, + 2, 1, cs35l33_sdin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("MON"), + + SND_SOC_DAPM_ADC("VMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VMON_SHIFT, 1), + SND_SOC_DAPM_ADC("IMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_IMON_SHIFT, 1), + SND_SOC_DAPM_ADC("VPMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VPMON_SHIFT, 1), + SND_SOC_DAPM_ADC("VBSTMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VBSTMON_SHIFT, 1), + + SND_SOC_DAPM_AIF_OUT_E("SDOUT", NULL, 0, SND_SOC_NOPM, 0, 0, + cs35l33_sdout_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_PRE_PMD), +}; + +static const struct snd_soc_dapm_route cs35l33_audio_map[] = { + {"SDIN", NULL, "CS35L33 Playback"}, + {"SPKDRV", NULL, "SDIN"}, + {"SPK", NULL, "SPKDRV"}, + + {"VMON", NULL, "MON"}, + {"IMON", NULL, "MON"}, + + {"SDOUT", NULL, "VMON"}, + {"SDOUT", NULL, "IMON"}, + {"CS35L33 Capture", NULL, "SDOUT"}, +}; + +static const struct snd_soc_dapm_route cs35l33_vphg_auto_route[] = { + {"SPKDRV", NULL, "VPMON"}, + {"VPMON", NULL, "CS35L33 Playback"}, +}; + +static const struct snd_soc_dapm_route cs35l33_vp_vbst_mon_route[] = { + {"SDOUT", NULL, "VPMON"}, + {"VPMON", NULL, "MON"}, + {"SDOUT", NULL, "VBSTMON"}, + {"VBSTMON", NULL, "MON"}, +}; + +static int cs35l33_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + unsigned int val; + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_ALL, 0); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS, 0); + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_ALL, CS35L33_PDN_ALL); + regmap_read(priv->regmap, CS35L33_INT_STATUS_2, &val); + usleep_range(1000, 1100); + if (val & CS35L33_PDN_DONE) + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS, CS35L33_MCLKDIS); + break; + case SND_SOC_BIAS_OFF: + break; + default: + return -EINVAL; + } + + return 0; +} + +struct cs35l33_mclk_div { + int mclk; + int srate; + u8 adsp_rate; + u8 int_fs_ratio; +}; + +static const struct cs35l33_mclk_div cs35l33_mclk_coeffs[] = { + /* MCLK, Sample Rate, adsp_rate, int_fs_ratio */ + {5644800, 11025, 0x4, CS35L33_INT_FS_RATE}, + {5644800, 22050, 0x8, CS35L33_INT_FS_RATE}, + {5644800, 44100, 0xC, CS35L33_INT_FS_RATE}, + + {6000000, 8000, 0x1, 0}, + {6000000, 11025, 0x2, 0}, + {6000000, 11029, 0x3, 0}, + {6000000, 12000, 0x4, 0}, + {6000000, 16000, 0x5, 0}, + {6000000, 22050, 0x6, 0}, + {6000000, 22059, 0x7, 0}, + {6000000, 24000, 0x8, 0}, + {6000000, 32000, 0x9, 0}, + {6000000, 44100, 0xA, 0}, + {6000000, 44118, 0xB, 0}, + {6000000, 48000, 0xC, 0}, + + {6144000, 8000, 0x1, CS35L33_INT_FS_RATE}, + {6144000, 12000, 0x4, CS35L33_INT_FS_RATE}, + {6144000, 16000, 0x5, CS35L33_INT_FS_RATE}, + {6144000, 24000, 0x8, CS35L33_INT_FS_RATE}, + {6144000, 32000, 0x9, CS35L33_INT_FS_RATE}, + {6144000, 48000, 0xC, CS35L33_INT_FS_RATE}, +}; + +static int cs35l33_get_mclk_coeff(int mclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l33_mclk_coeffs); i++) { + if (cs35l33_mclk_coeffs[i].mclk == mclk && + cs35l33_mclk_coeffs[i].srate == srate) + return i; + } + return -EINVAL; +} + +static int cs35l33_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL, + CS35L33_MS_MASK, CS35L33_MS_MASK); + dev_dbg(component->dev, "Audio port in master mode\n"); + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL, + CS35L33_MS_MASK, 0); + dev_dbg(component->dev, "Audio port in slave mode\n"); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + /* + * tdm mode in cs35l33 resembles dsp-a mode very + * closely, it is dsp-a with fsync shifted left by half bclk + */ + priv->is_tdm_mode = true; + dev_dbg(component->dev, "Audio port in TDM mode\n"); + break; + case SND_SOC_DAIFMT_I2S: + priv->is_tdm_mode = false; + dev_dbg(component->dev, "Audio port in I2S mode\n"); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cs35l33_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + int sample_size = params_width(params); + int coeff = cs35l33_get_mclk_coeff(priv->mclk_int, params_rate(params)); + + if (coeff < 0) + return coeff; + + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_ADSP_FS | CS35L33_INT_FS_RATE, + cs35l33_mclk_coeffs[coeff].int_fs_ratio + | cs35l33_mclk_coeffs[coeff].adsp_rate); + + if (priv->is_tdm_mode) { + sample_size = (sample_size / 8) - 1; + if (sample_size > 2) + sample_size = 2; + regmap_update_bits(priv->regmap, CS35L33_RX_AUD, + CS35L33_AUDIN_RX_DEPTH, + sample_size << CS35L33_AUDIN_RX_DEPTH_SHIFT); + } + + dev_dbg(component->dev, "sample rate=%d, bits per sample=%d\n", + params_rate(params), params_width(params)); + + return 0; +} + +static const unsigned int cs35l33_src_rates[] = { + 8000, 11025, 11029, 12000, 16000, 22050, + 22059, 24000, 32000, 44100, 44118, 48000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l33_constraints = { + .count = ARRAY_SIZE(cs35l33_src_rates), + .list = cs35l33_src_rates, +}; + +static int cs35l33_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l33_constraints); + return 0; +} + +static int cs35l33_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_component *component = dai->component; + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + + if (tristate) { + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_SDOUT_3ST_I2S, CS35L33_SDOUT_3ST_I2S); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_SDOUT_3ST_TDM, CS35L33_SDOUT_3ST_TDM); + } else { + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_SDOUT_3ST_I2S, 0); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_SDOUT_3ST_TDM, 0); + } + + return 0; +} + +static int cs35l33_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + unsigned int reg, bit_pos, i; + int slot, slot_num; + + if (slot_width != 8) + return -EINVAL; + + /* scan rx_mask for aud slot */ + slot = ffs(rx_mask) - 1; + if (slot >= 0) { + regmap_update_bits(priv->regmap, CS35L33_RX_AUD, + CS35L33_X_LOC, slot); + dev_dbg(component->dev, "Audio starts from slots %d", slot); + } + + /* + * scan tx_mask: vmon(2 slots); imon (2 slots); + * vpmon (1 slot) vbstmon (1 slot) + */ + slot = ffs(tx_mask) - 1; + slot_num = 0; + + for (i = 0; i < 2 ; i++) { + /* disable vpmon/vbstmon: enable later if set in tx_mask */ + regmap_update_bits(priv->regmap, CS35L33_TX_VPMON + i, + CS35L33_X_STATE | CS35L33_X_LOC, CS35L33_X_STATE + | CS35L33_X_LOC); + } + + /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + snd_soc_dapm_del_routes(dapm, cs35l33_vp_vbst_mon_route, + ARRAY_SIZE(cs35l33_vp_vbst_mon_route)); + + while (slot >= 0) { + /* configure VMON_TX_LOC */ + if (slot_num == 0) { + regmap_update_bits(priv->regmap, CS35L33_TX_VMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + dev_dbg(component->dev, "VMON enabled in slots %d-%d", + slot, slot + 1); + } + + /* configure IMON_TX_LOC */ + if (slot_num == 3) { + regmap_update_bits(priv->regmap, CS35L33_TX_IMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + dev_dbg(component->dev, "IMON enabled in slots %d-%d", + slot, slot + 1); + } + + /* configure VPMON_TX_LOC */ + if (slot_num == 4) { + regmap_update_bits(priv->regmap, CS35L33_TX_VPMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + snd_soc_dapm_add_routes(dapm, + &cs35l33_vp_vbst_mon_route[0], 2); + dev_dbg(component->dev, "VPMON enabled in slots %d", slot); + } + + /* configure VBSTMON_TX_LOC */ + if (slot_num == 5) { + regmap_update_bits(priv->regmap, CS35L33_TX_VBSTMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + snd_soc_dapm_add_routes(dapm, + &cs35l33_vp_vbst_mon_route[2], 2); + dev_dbg(component->dev, + "VBSTMON enabled in slots %d", slot); + } + + /* Enable the relevant tx slot */ + reg = CS35L33_TX_EN4 - (slot/8); + bit_pos = slot - ((slot / 8) * (8)); + regmap_update_bits(priv->regmap, reg, + 1 << bit_pos, 1 << bit_pos); + + tx_mask &= ~(1 << slot); + slot = ffs(tx_mask) - 1; + slot_num++; + } + + return 0; +} + +static int cs35l33_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct cs35l33_private *cs35l33 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case CS35L33_MCLK_5644: + case CS35L33_MCLK_6: + case CS35L33_MCLK_6144: + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIV2, 0); + cs35l33->mclk_int = freq; + break; + case CS35L33_MCLK_11289: + case CS35L33_MCLK_12: + case CS35L33_MCLK_12288: + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIV2, CS35L33_MCLKDIV2); + cs35l33->mclk_int = freq/2; + break; + default: + cs35l33->mclk_int = 0; + return -EINVAL; + } + + dev_dbg(component->dev, "external mclk freq=%d, internal mclk freq=%d\n", + freq, cs35l33->mclk_int); + + return 0; +} + +static const struct snd_soc_dai_ops cs35l33_ops = { + .startup = cs35l33_pcm_startup, + .set_tristate = cs35l33_set_tristate, + .set_fmt = cs35l33_set_dai_fmt, + .hw_params = cs35l33_pcm_hw_params, + .set_tdm_slot = cs35l33_set_tdm_slot, +}; + +static struct snd_soc_dai_driver cs35l33_dai = { + .name = "cs35l33-dai", + .id = 0, + .playback = { + .stream_name = "CS35L33 Playback", + .channels_min = 1, + .channels_max = 1, + .rates = CS35L33_RATES, + .formats = CS35L33_FORMATS, + }, + .capture = { + .stream_name = "CS35L33 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = CS35L33_RATES, + .formats = CS35L33_FORMATS, + }, + .ops = &cs35l33_ops, + .symmetric_rates = 1, +}; + +static int cs35l33_set_hg_data(struct snd_soc_component *component, + struct cs35l33_pdata *pdata) +{ + struct cs35l33_hg *hg_config = &pdata->hg_config; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct cs35l33_private *priv = snd_soc_component_get_drvdata(component); + + if (hg_config->enable_hg_algo) { + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_MEM_DEPTH_MASK, + hg_config->mem_depth << CS35L33_MEM_DEPTH_SHIFT); + regmap_write(priv->regmap, CS35L33_HG_REL_RATE, + hg_config->release_rate); + regmap_update_bits(priv->regmap, CS35L33_HG_HEAD, + CS35L33_HD_RM_MASK, + hg_config->hd_rm << CS35L33_HD_RM_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_LDO_THLD_MASK, + hg_config->ldo_thld << CS35L33_LDO_THLD_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_LDO_DISABLE_MASK, + hg_config->ldo_path_disable << + CS35L33_LDO_DISABLE_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_LDO_ENTRY_DELAY_MASK, + hg_config->ldo_entry_delay << + CS35L33_LDO_ENTRY_DELAY_SHIFT); + if (hg_config->vp_hg_auto) { + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_VP_HG_AUTO_MASK, + CS35L33_VP_HG_AUTO_MASK); + snd_soc_dapm_add_routes(dapm, cs35l33_vphg_auto_route, + ARRAY_SIZE(cs35l33_vphg_auto_route)); + } + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_VP_HG_MASK, + hg_config->vp_hg << CS35L33_VP_HG_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_VP_HG_RATE_MASK, + hg_config->vp_hg_rate << CS35L33_VP_HG_RATE_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_VP_HG_VA_MASK, + hg_config->vp_hg_va << CS35L33_VP_HG_VA_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_CLASS_HG_EN_MASK, CS35L33_CLASS_HG_EN_MASK); + } + return 0; +} + +static int cs35l33_set_bst_ipk(struct snd_soc_component *component, unsigned int bst) +{ + struct cs35l33_private *cs35l33 = snd_soc_component_get_drvdata(component); + int ret = 0, steps = 0; + + /* Boost current in uA */ + if (bst > 3600000 || bst < 1850000) { + dev_err(component->dev, "Invalid boost current %d\n", bst); + ret = -EINVAL; + goto err; + } + + if (bst % 15625) { + dev_err(component->dev, "Current not a multiple of 15625uA (%d)\n", + bst); + ret = -EINVAL; + goto err; + } + + while (bst > 1850000) { + bst -= 15625; + steps++; + } + + regmap_write(cs35l33->regmap, CS35L33_BST_PEAK_CTL, + steps+0x70); + +err: + return ret; +} + +static int cs35l33_probe(struct snd_soc_component *component) +{ + struct cs35l33_private *cs35l33 = snd_soc_component_get_drvdata(component); + + cs35l33->component = component; + pm_runtime_get_sync(component->dev); + + regmap_update_bits(cs35l33->regmap, CS35L33_PROTECT_CTL, + CS35L33_ALIVE_WD_DIS, 0x8); + regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL2, + CS35L33_ALIVE_WD_DIS2, + CS35L33_ALIVE_WD_DIS2); + + /* Set Platform Data */ + regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL1, + CS35L33_BST_CTL_MASK, cs35l33->pdata.boost_ctl); + regmap_update_bits(cs35l33->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_DRV_SEL_MASK, + cs35l33->pdata.amp_drv_sel << CS35L33_AMP_DRV_SEL_SHIFT); + + if (cs35l33->pdata.boost_ipk) + cs35l33_set_bst_ipk(component, cs35l33->pdata.boost_ipk); + + if (cs35l33->enable_soft_ramp) { + snd_soc_component_update_bits(component, CS35L33_DAC_CTL, + CS35L33_DIGSFT, CS35L33_DIGSFT); + snd_soc_component_update_bits(component, CS35L33_DAC_CTL, + CS35L33_DSR_RATE, cs35l33->pdata.ramp_rate); + } else { + snd_soc_component_update_bits(component, CS35L33_DAC_CTL, + CS35L33_DIGSFT, 0); + } + + /* update IMON scaling rate if different from default of 0x8 */ + if (cs35l33->pdata.imon_adc_scale != 0x8) + snd_soc_component_update_bits(component, CS35L33_ADC_CTL, + CS35L33_IMON_SCALE, cs35l33->pdata.imon_adc_scale); + + cs35l33_set_hg_data(component, &(cs35l33->pdata)); + + /* + * unmask important interrupts that causes the chip to enter + * speaker safe mode and hence deserves user attention + */ + regmap_update_bits(cs35l33->regmap, CS35L33_INT_MASK_1, + CS35L33_M_OTE | CS35L33_M_OTW | CS35L33_M_AMP_SHORT | + CS35L33_M_CAL_ERR, 0); + + pm_runtime_put_sync(component->dev); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_cs35l33 = { + .probe = cs35l33_probe, + .set_bias_level = cs35l33_set_bias_level, + .set_sysclk = cs35l33_component_set_sysclk, + .controls = cs35l33_snd_controls, + .num_controls = ARRAY_SIZE(cs35l33_snd_controls), + .dapm_widgets = cs35l33_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l33_dapm_widgets), + .dapm_routes = cs35l33_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l33_audio_map), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config cs35l33_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L33_MAX_REGISTER, + .reg_defaults = cs35l33_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l33_reg), + .volatile_reg = cs35l33_volatile_register, + .readable_reg = cs35l33_readable_register, + .writeable_reg = cs35l33_writeable_register, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static int __maybe_unused cs35l33_runtime_resume(struct device *dev) +{ + struct cs35l33_private *cs35l33 = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "%s\n", __func__); + + gpiod_set_value_cansleep(cs35l33->reset_gpio, 0); + + ret = regulator_bulk_enable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(cs35l33->regmap, false); + + gpiod_set_value_cansleep(cs35l33->reset_gpio, 1); + + msleep(CS35L33_BOOT_DELAY); + + ret = regcache_sync(cs35l33->regmap); + if (ret != 0) { + dev_err(dev, "Failed to restore register cache\n"); + goto err; + } + + return 0; + +err: + regcache_cache_only(cs35l33->regmap, true); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return ret; +} + +static int __maybe_unused cs35l33_runtime_suspend(struct device *dev) +{ + struct cs35l33_private *cs35l33 = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + /* redo the calibration in next power up */ + cs35l33->amp_cal = false; + + regcache_cache_only(cs35l33->regmap, true); + regcache_mark_dirty(cs35l33->regmap); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return 0; +} + +static const struct dev_pm_ops cs35l33_pm_ops = { + SET_RUNTIME_PM_OPS(cs35l33_runtime_suspend, + cs35l33_runtime_resume, + NULL) +}; + +static int cs35l33_get_hg_data(const struct device_node *np, + struct cs35l33_pdata *pdata) +{ + struct device_node *hg; + struct cs35l33_hg *hg_config = &pdata->hg_config; + u32 val32; + + hg = of_get_child_by_name(np, "cirrus,hg-algo"); + hg_config->enable_hg_algo = hg ? true : false; + + if (hg_config->enable_hg_algo) { + if (of_property_read_u32(hg, "cirrus,mem-depth", &val32) >= 0) + hg_config->mem_depth = val32; + if (of_property_read_u32(hg, "cirrus,release-rate", + &val32) >= 0) + hg_config->release_rate = val32; + if (of_property_read_u32(hg, "cirrus,ldo-thld", &val32) >= 0) + hg_config->ldo_thld = val32; + if (of_property_read_u32(hg, "cirrus,ldo-path-disable", + &val32) >= 0) + hg_config->ldo_path_disable = val32; + if (of_property_read_u32(hg, "cirrus,ldo-entry-delay", + &val32) >= 0) + hg_config->ldo_entry_delay = val32; + + hg_config->vp_hg_auto = of_property_read_bool(hg, + "cirrus,vp-hg-auto"); + + if (of_property_read_u32(hg, "cirrus,vp-hg", &val32) >= 0) + hg_config->vp_hg = val32; + if (of_property_read_u32(hg, "cirrus,vp-hg-rate", &val32) >= 0) + hg_config->vp_hg_rate = val32; + if (of_property_read_u32(hg, "cirrus,vp-hg-va", &val32) >= 0) + hg_config->vp_hg_va = val32; + } + + of_node_put(hg); + + return 0; +} + +static irqreturn_t cs35l33_irq_thread(int irq, void *data) +{ + struct cs35l33_private *cs35l33 = data; + struct snd_soc_component *component = cs35l33->component; + unsigned int sticky_val1, sticky_val2, current_val, mask1, mask2; + + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_2, + &sticky_val2); + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1, + &sticky_val1); + regmap_read(cs35l33->regmap, CS35L33_INT_MASK_2, &mask2); + regmap_read(cs35l33->regmap, CS35L33_INT_MASK_1, &mask1); + + /* Check to see if the unmasked bits are active, + * if not then exit. + */ + if (!(sticky_val1 & ~mask1) && !(sticky_val2 & ~mask2)) + return IRQ_NONE; + + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1, + ¤t_val); + + /* handle the interrupts */ + + if (sticky_val1 & CS35L33_AMP_SHORT) { + dev_crit(component->dev, "Amp short error\n"); + if (!(current_val & CS35L33_AMP_SHORT)) { + dev_dbg(component->dev, + "Amp short error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, + CS35L33_AMP_SHORT_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, + CS35L33_AMP_SHORT_RLS, + CS35L33_AMP_SHORT_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_AMP_SHORT_RLS, + 0); + } + } + + if (sticky_val1 & CS35L33_CAL_ERR) { + dev_err(component->dev, "Cal error\n"); + + /* redo the calibration in next power up */ + cs35l33->amp_cal = false; + + if (!(current_val & CS35L33_CAL_ERR)) { + dev_dbg(component->dev, "Cal error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + CS35L33_CAL_ERR_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + 0); + } + } + + if (sticky_val1 & CS35L33_OTE) { + dev_crit(component->dev, "Over temperature error\n"); + if (!(current_val & CS35L33_OTE)) { + dev_dbg(component->dev, + "Over temperature error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, + CS35L33_OTE_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0); + } + } + + if (sticky_val1 & CS35L33_OTW) { + dev_err(component->dev, "Over temperature warning\n"); + if (!(current_val & CS35L33_OTW)) { + dev_dbg(component->dev, + "Over temperature warning release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, + CS35L33_OTW_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0); + } + } + if (CS35L33_ALIVE_ERR & sticky_val1) + dev_err(component->dev, "ERROR: ADSPCLK Interrupt\n"); + + if (CS35L33_MCLK_ERR & sticky_val1) + dev_err(component->dev, "ERROR: MCLK Interrupt\n"); + + if (CS35L33_VMON_OVFL & sticky_val2) + dev_err(component->dev, + "ERROR: VMON Overflow Interrupt\n"); + + if (CS35L33_IMON_OVFL & sticky_val2) + dev_err(component->dev, + "ERROR: IMON Overflow Interrupt\n"); + + if (CS35L33_VPMON_OVFL & sticky_val2) + dev_err(component->dev, + "ERROR: VPMON Overflow Interrupt\n"); + + return IRQ_HANDLED; +} + +static const char * const cs35l33_core_supplies[] = { + "VA", + "VP", +}; + +static int cs35l33_of_get_pdata(struct device *dev, + struct cs35l33_private *cs35l33) +{ + struct device_node *np = dev->of_node; + struct cs35l33_pdata *pdata = &cs35l33->pdata; + u32 val32; + + if (!np) + return 0; + + if (of_property_read_u32(np, "cirrus,boost-ctl", &val32) >= 0) { + pdata->boost_ctl = val32; + pdata->amp_drv_sel = 1; + } + + if (of_property_read_u32(np, "cirrus,ramp-rate", &val32) >= 0) { + pdata->ramp_rate = val32; + cs35l33->enable_soft_ramp = true; + } + + if (of_property_read_u32(np, "cirrus,boost-ipk", &val32) >= 0) + pdata->boost_ipk = val32; + + if (of_property_read_u32(np, "cirrus,imon-adc-scale", &val32) >= 0) { + if ((val32 == 0x0) || (val32 == 0x7) || (val32 == 0x6)) + pdata->imon_adc_scale = val32; + else + /* use default value */ + pdata->imon_adc_scale = 0x8; + } else { + /* use default value */ + pdata->imon_adc_scale = 0x8; + } + + cs35l33_get_hg_data(np, pdata); + + return 0; +} + +static int cs35l33_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l33_private *cs35l33; + struct cs35l33_pdata *pdata = dev_get_platdata(&i2c_client->dev); + int ret, devid, i; + unsigned int reg; + + cs35l33 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs35l33_private), + GFP_KERNEL); + if (!cs35l33) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs35l33); + cs35l33->regmap = devm_regmap_init_i2c(i2c_client, &cs35l33_regmap); + if (IS_ERR(cs35l33->regmap)) { + ret = PTR_ERR(cs35l33->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + regcache_cache_only(cs35l33->regmap, true); + + for (i = 0; i < ARRAY_SIZE(cs35l33_core_supplies); i++) + cs35l33->core_supplies[i].supply + = cs35l33_core_supplies[i]; + cs35l33->num_core_supplies = ARRAY_SIZE(cs35l33_core_supplies); + + ret = devm_regulator_bulk_get(&i2c_client->dev, + cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request core supplies: %d\n", + ret); + return ret; + } + + if (pdata) { + cs35l33->pdata = *pdata; + } else { + cs35l33_of_get_pdata(&i2c_client->dev, cs35l33); + pdata = &cs35l33->pdata; + } + + ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL, + cs35l33_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs35l33", cs35l33); + if (ret != 0) + dev_warn(&i2c_client->dev, "Failed to request IRQ: %d\n", ret); + + /* We could issue !RST or skip it based on AMP topology */ + cs35l33->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset-gpios", GPIOD_OUT_HIGH); + if (IS_ERR(cs35l33->reset_gpio)) { + dev_err(&i2c_client->dev, "%s ERROR: Can't get reset GPIO\n", + __func__); + return PTR_ERR(cs35l33->reset_gpio); + } + + ret = regulator_bulk_enable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable core supplies: %d\n", + ret); + return ret; + } + + gpiod_set_value_cansleep(cs35l33->reset_gpio, 1); + + msleep(CS35L33_BOOT_DELAY); + regcache_cache_only(cs35l33->regmap, false); + + /* initialize codec */ + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L33_CHIP_ID) { + dev_err(&i2c_client->dev, + "CS35L33 Device ID (%X). Expected ID %X\n", + devid, CS35L33_CHIP_ID); + ret = -EINVAL; + goto err_enable; + } + + ret = regmap_read(cs35l33->regmap, CS35L33_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed\n"); + goto err_enable; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS35L33, Revision: %02X\n", reg & 0xFF); + + ret = regmap_register_patch(cs35l33->regmap, + cs35l33_patch, ARRAY_SIZE(cs35l33_patch)); + if (ret < 0) { + dev_err(&i2c_client->dev, + "Error in applying regmap patch: %d\n", ret); + goto err_enable; + } + + /* disable mclk and tdm */ + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM, + CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM); + + pm_runtime_set_autosuspend_delay(&i2c_client->dev, 100); + pm_runtime_use_autosuspend(&i2c_client->dev); + pm_runtime_set_active(&i2c_client->dev); + pm_runtime_enable(&i2c_client->dev); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_cs35l33, &cs35l33_dai, 1); + if (ret < 0) { + dev_err(&i2c_client->dev, "%s: Register component failed\n", + __func__); + goto err_enable; + } + + return 0; + +err_enable: + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return ret; +} + +static int cs35l33_i2c_remove(struct i2c_client *client) +{ + struct cs35l33_private *cs35l33 = i2c_get_clientdata(client); + + gpiod_set_value_cansleep(cs35l33->reset_gpio, 0); + + pm_runtime_disable(&client->dev); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return 0; +} + +static const struct of_device_id cs35l33_of_match[] = { + { .compatible = "cirrus,cs35l33", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l33_of_match); + +static const struct i2c_device_id cs35l33_id[] = { + {"cs35l33", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l33_id); + +static struct i2c_driver cs35l33_i2c_driver = { + .driver = { + .name = "cs35l33", + .pm = &cs35l33_pm_ops, + .of_match_table = cs35l33_of_match, + + }, + .id_table = cs35l33_id, + .probe = cs35l33_i2c_probe, + .remove = cs35l33_i2c_remove, + +}; +module_i2c_driver(cs35l33_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L33 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l33.h b/sound/soc/codecs/cs35l33.h new file mode 100644 index 000000000..c045737d1 --- /dev/null +++ b/sound/soc/codecs/cs35l33.h @@ -0,0 +1,221 @@ +/* + * cs35l33.h -- CS35L33 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS35L33_H__ +#define __CS35L33_H__ + +#define CS35L33_CHIP_ID 0x00035A33 +#define CS35L33_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L33_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L33_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L33_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L33_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L33_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L33_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L33_CLK_CTL 0x08 /* Clock Ctl */ +#define CS35L33_BST_PEAK_CTL 0x09 /* Max Current for Boost */ +#define CS35L33_PROTECT_CTL 0x0A /* Amp Protection Parameters */ +#define CS35L33_BST_CTL1 0x0B /* Boost Converter CTL1 */ +#define CS35L33_BST_CTL2 0x0C /* Boost Converter CTL2 */ +#define CS35L33_ADSP_CTL 0x0D /* Serial Port Control */ +#define CS35L33_ADC_CTL 0x0E /* ADC Control */ +#define CS35L33_DAC_CTL 0x0F /* DAC Control */ +#define CS35L33_DIG_VOL_CTL 0x10 /* Digital Volume CTL */ +#define CS35L33_CLASSD_CTL 0x11 /* Class D Amp CTL */ +#define CS35L33_AMP_CTL 0x12 /* Amp Gain/Protecton Release CTL */ +#define CS35L33_INT_MASK_1 0x13 /* Interrupt Mask 1 */ +#define CS35L33_INT_MASK_2 0x14 /* Interrupt Mask 2 */ +#define CS35L33_INT_STATUS_1 0x15 /* Interrupt Status 1 [RO] */ +#define CS35L33_INT_STATUS_2 0x16 /* Interrupt Status 2 [RO] */ +#define CS35L33_DIAG_LOCK 0x17 /* Diagnostic Mode Register Lock */ +#define CS35L33_DIAG_CTRL_1 0x18 /* Diagnostic Mode Register Control */ +#define CS35L33_DIAG_CTRL_2 0x19 /* Diagnostic Mode Register Control 2 */ +#define CS35L33_HG_MEMLDO_CTL 0x23 /* H/G Memory/LDO CTL */ +#define CS35L33_HG_REL_RATE 0x24 /* H/G Release Rate */ +#define CS35L33_LDO_DEL 0x25 /* LDO Entry Delay/VPhg Control 1 */ +#define CS35L33_HG_HEAD 0x29 /* H/G Headroom */ +#define CS35L33_HG_EN 0x2A /* H/G Enable/VPhg CNT2 */ +#define CS35L33_TX_VMON 0x2D /* TDM TX Control 1 (VMON) */ +#define CS35L33_TX_IMON 0x2E /* TDM TX Control 2 (IMON) */ +#define CS35L33_TX_VPMON 0x2F /* TDM TX Control 3 (VPMON) */ +#define CS35L33_TX_VBSTMON 0x30 /* TDM TX Control 4 (VBSTMON) */ +#define CS35L33_TX_FLAG 0x31 /* TDM TX Control 5 (FLAG) */ +#define CS35L33_TX_EN1 0x32 /* TDM TX Enable 1 */ +#define CS35L33_TX_EN2 0x33 /* TDM TX Enable 2 */ +#define CS35L33_TX_EN3 0x34 /* TDM TX Enable 3 */ +#define CS35L33_TX_EN4 0x35 /* TDM TX Enable 4 */ +#define CS35L33_RX_AUD 0x36 /* TDM RX Control 1 */ +#define CS35L33_RX_SPLY 0x37 /* TDM RX Control 2 */ +#define CS35L33_RX_ALIVE 0x38 /* TDM RX Control 3 */ +#define CS35L33_BST_CTL4 0x39 /* Boost Converter Control 4 */ +#define CS35L33_HG_STATUS 0x3F /* H/G Status */ +#define CS35L33_MAX_REGISTER 0x59 + +#define CS35L33_MCLK_5644 5644800 +#define CS35L33_MCLK_6144 6144000 +#define CS35L33_MCLK_6 6000000 +#define CS35L33_MCLK_11289 11289600 +#define CS35L33_MCLK_12 12000000 +#define CS35L33_MCLK_12288 12288000 + +/* CS35L33_PWRCTL1 */ +#define CS35L33_PDN_AMP (1 << 7) +#define CS35L33_PDN_BST (1 << 2) +#define CS35L33_PDN_ALL 1 + +/* CS35L33_PWRCTL2 */ +#define CS35L33_PDN_VMON_SHIFT 7 +#define CS35L33_PDN_VMON (1 << CS35L33_PDN_VMON_SHIFT) +#define CS35L33_PDN_IMON_SHIFT 6 +#define CS35L33_PDN_IMON (1 << CS35L33_PDN_IMON_SHIFT) +#define CS35L33_PDN_VPMON_SHIFT 5 +#define CS35L33_PDN_VPMON (1 << CS35L33_PDN_VPMON_SHIFT) +#define CS35L33_PDN_VBSTMON_SHIFT 4 +#define CS35L33_PDN_VBSTMON (1 << CS35L33_PDN_VBSTMON_SHIFT) +#define CS35L33_SDOUT_3ST_I2S_SHIFT 3 +#define CS35L33_SDOUT_3ST_I2S (1 << CS35L33_SDOUT_3ST_I2S_SHIFT) +#define CS35L33_PDN_SDIN_SHIFT 2 +#define CS35L33_PDN_SDIN (1 << CS35L33_PDN_SDIN_SHIFT) +#define CS35L33_PDN_TDM_SHIFT 1 +#define CS35L33_PDN_TDM (1 << CS35L33_PDN_TDM_SHIFT) + +/* CS35L33_CLK_CTL */ +#define CS35L33_MCLKDIS (1 << 7) +#define CS35L33_MCLKDIV2 (1 << 6) +#define CS35L33_SDOUT_3ST_TDM (1 << 5) +#define CS35L33_INT_FS_RATE (1 << 4) +#define CS35L33_ADSP_FS 0xF + +/* CS35L33_PROTECT_CTL */ +#define CS35L33_ALIVE_WD_DIS (3 << 2) + +/* CS35L33_BST_CTL1 */ +#define CS35L33_BST_CTL_SRC (1 << 6) +#define CS35L33_BST_CTL_SHIFT (1 << 5) +#define CS35L33_BST_CTL_MASK 0x3F + +/* CS35L33_BST_CTL2 */ +#define CS35L33_TDM_WD_SEL (1 << 4) +#define CS35L33_ALIVE_WD_DIS2 (1 << 3) +#define CS35L33_VBST_SR_STEP 0x3 + +/* CS35L33_ADSP_CTL */ +#define CS35L33_ADSP_DRIVE (1 << 7) +#define CS35L33_MS_MASK (1 << 6) +#define CS35L33_SDIN_LOC (3 << 4) +#define CS35L33_ALIVE_RATE 0x3 + +/* CS35L33_ADC_CTL */ +#define CS35L33_INV_VMON (1 << 7) +#define CS35L33_INV_IMON (1 << 6) +#define CS35L33_ADC_NOTCH_DIS (1 << 5) +#define CS35L33_IMON_SCALE 0xF + +/* CS35L33_DAC_CTL */ +#define CS35L33_INV_DAC (1 << 7) +#define CS35L33_DAC_NOTCH_DIS (1 << 5) +#define CS35L33_DIGSFT (1 << 4) +#define CS35L33_DSR_RATE 0xF + +/* CS35L33_CLASSD_CTL */ +#define CS35L33_AMP_SD (1 << 6) +#define CS35L33_AMP_DRV_SEL_SRC (1 << 5) +#define CS35L33_AMP_DRV_SEL_MASK 0x10 +#define CS35L33_AMP_DRV_SEL_SHIFT 4 +#define CS35L33_AMP_CAL (1 << 3) +#define CS35L33_GAIN_CHG_ZC_MASK 0x04 +#define CS35L33_GAIN_CHG_ZC_SHIFT 2 +#define CS35L33_CLASS_D_CTL_MASK 0x3F + +/* CS35L33_AMP_CTL */ +#define CS35L33_AMP_GAIN 0xF0 +#define CS35L33_CAL_ERR_RLS (1 << 3) +#define CS35L33_AMP_SHORT_RLS (1 << 2) +#define CS35L33_OTW_RLS (1 << 1) +#define CS35L33_OTE_RLS 1 + +/* CS35L33_INT_MASK_1 */ +#define CS35L33_M_CAL_ERR_SHIFT 6 +#define CS35L33_M_CAL_ERR (1 << CS35L33_M_CAL_ERR_SHIFT) +#define CS35L33_M_ALIVE_ERR_SHIFT 5 +#define CS35L33_M_ALIVE_ERR (1 << CS35L33_M_ALIVE_ERR_SHIFT) +#define CS35L33_M_AMP_SHORT_SHIFT 2 +#define CS35L33_M_AMP_SHORT (1 << CS35L33_M_AMP_SHORT_SHIFT) +#define CS35L33_M_OTW_SHIFT 1 +#define CS35L33_M_OTW (1 << CS35L33_M_OTW_SHIFT) +#define CS35L33_M_OTE_SHIFT 0 +#define CS35L33_M_OTE (1 << CS35L33_M_OTE_SHIFT) + +/* CS35L33_INT_STATUS_1 */ +#define CS35L33_CAL_ERR (1 << 6) +#define CS35L33_ALIVE_ERR (1 << 5) +#define CS35L33_ADSPCLK_ERR (1 << 4) +#define CS35L33_MCLK_ERR (1 << 3) +#define CS35L33_AMP_SHORT (1 << 2) +#define CS35L33_OTW (1 << 1) +#define CS35L33_OTE (1 << 0) + +/* CS35L33_INT_STATUS_2 */ +#define CS35L33_VMON_OVFL (1 << 7) +#define CS35L33_IMON_OVFL (1 << 6) +#define CS35L33_VPMON_OVFL (1 << 5) +#define CS35L33_VBSTMON_OVFL (1 << 4) +#define CS35L33_PDN_DONE 1 + +/* CS35L33_BST_CTL4 */ +#define CS35L33_BST_RGS 0x70 +#define CS35L33_BST_COEFF3 0xF + +/* CS35L33_HG_MEMLDO_CTL */ +#define CS35L33_MEM_DEPTH_SHIFT 5 +#define CS35L33_MEM_DEPTH_MASK (0x3 << CS35L33_MEM_DEPTH_SHIFT) +#define CS35L33_LDO_THLD_SHIFT 1 +#define CS35L33_LDO_THLD_MASK (0xF << CS35L33_LDO_THLD_SHIFT) +#define CS35L33_LDO_DISABLE_SHIFT 0 +#define CS35L33_LDO_DISABLE_MASK (0x1 << CS35L33_LDO_DISABLE_SHIFT) + +/* CS35L33_LDO_DEL */ +#define CS35L33_VP_HG_VA_SHIFT 5 +#define CS35L33_VP_HG_VA_MASK (0x7 << CS35L33_VP_HG_VA_SHIFT) +#define CS35L33_LDO_ENTRY_DELAY_SHIFT 2 +#define CS35L33_LDO_ENTRY_DELAY_MASK (0x7 << CS35L33_LDO_ENTRY_DELAY_SHIFT) +#define CS35L33_VP_HG_RATE_SHIFT 0 +#define CS35L33_VP_HG_RATE_MASK (0x3 << CS35L33_VP_HG_RATE_SHIFT) + +/* CS35L33_HG_HEAD */ +#define CS35L33_HD_RM_SHIFT 0 +#define CS35L33_HD_RM_MASK (0x7F << CS35L33_HD_RM_SHIFT) + +/* CS35L33_HG_EN */ +#define CS35L33_CLASS_HG_ENA_SHIFT 7 +#define CS35L33_CLASS_HG_EN_MASK (0x1 << CS35L33_CLASS_HG_ENA_SHIFT) +#define CS35L33_VP_HG_AUTO_SHIFT 6 +#define CS35L33_VP_HG_AUTO_MASK (0x1 << 6) +#define CS35L33_VP_HG_SHIFT 0 +#define CS35L33_VP_HG_MASK (0x1F << CS35L33_VP_HG_SHIFT) + +#define CS35L33_RATES (SNDRV_PCM_RATE_8000_48000) +#define CS35L33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* CS35L33_{RX,TX}_X */ +#define CS35L33_X_STATE_SHIFT 7 +#define CS35L33_X_STATE (1 << CS35L33_X_STATE_SHIFT) +#define CS35L33_X_LOC_SHIFT 0 +#define CS35L33_X_LOC (0x1F << CS35L33_X_LOC_SHIFT) + +/* CS35L33_RX_AUD */ +#define CS35L33_AUDIN_RX_DEPTH_SHIFT 5 +#define CS35L33_AUDIN_RX_DEPTH (0x7 << CS35L33_AUDIN_RX_DEPTH_SHIFT) + +#endif diff --git a/sound/soc/codecs/cs35l34.c b/sound/soc/codecs/cs35l34.c new file mode 100644 index 000000000..5063c05af --- /dev/null +++ b/sound/soc/codecs/cs35l34.c @@ -0,0 +1,1242 @@ +/* + * cs35l34.c -- CS35l34 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs35l34.h" + +#define PDN_DONE_ATTEMPTS 10 +#define CS35L34_START_DELAY 50 + +struct cs35l34_private { + struct snd_soc_component *component; + struct cs35l34_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data core_supplies[2]; + int num_core_supplies; + int mclk_int; + bool tdm_mode; + struct gpio_desc *reset_gpio; /* Active-low reset GPIO */ +}; + +static const struct reg_default cs35l34_reg[] = { + {CS35L34_PWRCTL1, 0x01}, + {CS35L34_PWRCTL2, 0x19}, + {CS35L34_PWRCTL3, 0x01}, + {CS35L34_ADSP_CLK_CTL, 0x08}, + {CS35L34_MCLK_CTL, 0x11}, + {CS35L34_AMP_INP_DRV_CTL, 0x01}, + {CS35L34_AMP_DIG_VOL_CTL, 0x12}, + {CS35L34_AMP_DIG_VOL, 0x00}, + {CS35L34_AMP_ANLG_GAIN_CTL, 0x0F}, + {CS35L34_PROTECT_CTL, 0x06}, + {CS35L34_AMP_KEEP_ALIVE_CTL, 0x04}, + {CS35L34_BST_CVTR_V_CTL, 0x00}, + {CS35L34_BST_PEAK_I, 0x10}, + {CS35L34_BST_RAMP_CTL, 0x87}, + {CS35L34_BST_CONV_COEF_1, 0x24}, + {CS35L34_BST_CONV_COEF_2, 0x24}, + {CS35L34_BST_CONV_SLOPE_COMP, 0x4E}, + {CS35L34_BST_CONV_SW_FREQ, 0x08}, + {CS35L34_CLASS_H_CTL, 0x0D}, + {CS35L34_CLASS_H_HEADRM_CTL, 0x0D}, + {CS35L34_CLASS_H_RELEASE_RATE, 0x08}, + {CS35L34_CLASS_H_FET_DRIVE_CTL, 0x41}, + {CS35L34_CLASS_H_STATUS, 0x05}, + {CS35L34_VPBR_CTL, 0x0A}, + {CS35L34_VPBR_VOL_CTL, 0x90}, + {CS35L34_VPBR_TIMING_CTL, 0x6A}, + {CS35L34_PRED_MAX_ATTEN_SPK_LOAD, 0x95}, + {CS35L34_PRED_BROWNOUT_THRESH, 0x1C}, + {CS35L34_PRED_BROWNOUT_VOL_CTL, 0x00}, + {CS35L34_PRED_BROWNOUT_RATE_CTL, 0x10}, + {CS35L34_PRED_WAIT_CTL, 0x10}, + {CS35L34_PRED_ZVP_INIT_IMP_CTL, 0x08}, + {CS35L34_PRED_MAN_SAFE_VPI_CTL, 0x80}, + {CS35L34_VPBR_ATTEN_STATUS, 0x00}, + {CS35L34_PRED_BRWNOUT_ATT_STATUS, 0x00}, + {CS35L34_SPKR_MON_CTL, 0xC6}, + {CS35L34_ADSP_I2S_CTL, 0x00}, + {CS35L34_ADSP_TDM_CTL, 0x00}, + {CS35L34_TDM_TX_CTL_1_VMON, 0x00}, + {CS35L34_TDM_TX_CTL_2_IMON, 0x04}, + {CS35L34_TDM_TX_CTL_3_VPMON, 0x03}, + {CS35L34_TDM_TX_CTL_4_VBSTMON, 0x07}, + {CS35L34_TDM_TX_CTL_5_FLAG1, 0x08}, + {CS35L34_TDM_TX_CTL_6_FLAG2, 0x09}, + {CS35L34_TDM_TX_SLOT_EN_1, 0x00}, + {CS35L34_TDM_TX_SLOT_EN_2, 0x00}, + {CS35L34_TDM_TX_SLOT_EN_3, 0x00}, + {CS35L34_TDM_TX_SLOT_EN_4, 0x00}, + {CS35L34_TDM_RX_CTL_1_AUDIN, 0x40}, + {CS35L34_TDM_RX_CTL_3_ALIVE, 0x04}, + {CS35L34_MULT_DEV_SYNCH1, 0x00}, + {CS35L34_MULT_DEV_SYNCH2, 0x80}, + {CS35L34_PROT_RELEASE_CTL, 0x00}, + {CS35L34_DIAG_MODE_REG_LOCK, 0x00}, + {CS35L34_DIAG_MODE_CTL_1, 0x00}, + {CS35L34_DIAG_MODE_CTL_2, 0x00}, + {CS35L34_INT_MASK_1, 0xFF}, + {CS35L34_INT_MASK_2, 0xFF}, + {CS35L34_INT_MASK_3, 0xFF}, + {CS35L34_INT_MASK_4, 0xFF}, + {CS35L34_INT_STATUS_1, 0x30}, + {CS35L34_INT_STATUS_2, 0x05}, + {CS35L34_INT_STATUS_3, 0x00}, + {CS35L34_INT_STATUS_4, 0x00}, + {CS35L34_OTP_TRIM_STATUS, 0x00}, +}; + +static bool cs35l34_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L34_DEVID_AB: + case CS35L34_DEVID_CD: + case CS35L34_DEVID_E: + case CS35L34_FAB_ID: + case CS35L34_REV_ID: + case CS35L34_INT_STATUS_1: + case CS35L34_INT_STATUS_2: + case CS35L34_INT_STATUS_3: + case CS35L34_INT_STATUS_4: + case CS35L34_CLASS_H_STATUS: + case CS35L34_VPBR_ATTEN_STATUS: + case CS35L34_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l34_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L34_DEVID_AB: + case CS35L34_DEVID_CD: + case CS35L34_DEVID_E: + case CS35L34_FAB_ID: + case CS35L34_REV_ID: + case CS35L34_PWRCTL1: + case CS35L34_PWRCTL2: + case CS35L34_PWRCTL3: + case CS35L34_ADSP_CLK_CTL: + case CS35L34_MCLK_CTL: + case CS35L34_AMP_INP_DRV_CTL: + case CS35L34_AMP_DIG_VOL_CTL: + case CS35L34_AMP_DIG_VOL: + case CS35L34_AMP_ANLG_GAIN_CTL: + case CS35L34_PROTECT_CTL: + case CS35L34_AMP_KEEP_ALIVE_CTL: + case CS35L34_BST_CVTR_V_CTL: + case CS35L34_BST_PEAK_I: + case CS35L34_BST_RAMP_CTL: + case CS35L34_BST_CONV_COEF_1: + case CS35L34_BST_CONV_COEF_2: + case CS35L34_BST_CONV_SLOPE_COMP: + case CS35L34_BST_CONV_SW_FREQ: + case CS35L34_CLASS_H_CTL: + case CS35L34_CLASS_H_HEADRM_CTL: + case CS35L34_CLASS_H_RELEASE_RATE: + case CS35L34_CLASS_H_FET_DRIVE_CTL: + case CS35L34_CLASS_H_STATUS: + case CS35L34_VPBR_CTL: + case CS35L34_VPBR_VOL_CTL: + case CS35L34_VPBR_TIMING_CTL: + case CS35L34_PRED_MAX_ATTEN_SPK_LOAD: + case CS35L34_PRED_BROWNOUT_THRESH: + case CS35L34_PRED_BROWNOUT_VOL_CTL: + case CS35L34_PRED_BROWNOUT_RATE_CTL: + case CS35L34_PRED_WAIT_CTL: + case CS35L34_PRED_ZVP_INIT_IMP_CTL: + case CS35L34_PRED_MAN_SAFE_VPI_CTL: + case CS35L34_VPBR_ATTEN_STATUS: + case CS35L34_PRED_BRWNOUT_ATT_STATUS: + case CS35L34_SPKR_MON_CTL: + case CS35L34_ADSP_I2S_CTL: + case CS35L34_ADSP_TDM_CTL: + case CS35L34_TDM_TX_CTL_1_VMON: + case CS35L34_TDM_TX_CTL_2_IMON: + case CS35L34_TDM_TX_CTL_3_VPMON: + case CS35L34_TDM_TX_CTL_4_VBSTMON: + case CS35L34_TDM_TX_CTL_5_FLAG1: + case CS35L34_TDM_TX_CTL_6_FLAG2: + case CS35L34_TDM_TX_SLOT_EN_1: + case CS35L34_TDM_TX_SLOT_EN_2: + case CS35L34_TDM_TX_SLOT_EN_3: + case CS35L34_TDM_TX_SLOT_EN_4: + case CS35L34_TDM_RX_CTL_1_AUDIN: + case CS35L34_TDM_RX_CTL_3_ALIVE: + case CS35L34_MULT_DEV_SYNCH1: + case CS35L34_MULT_DEV_SYNCH2: + case CS35L34_PROT_RELEASE_CTL: + case CS35L34_DIAG_MODE_REG_LOCK: + case CS35L34_DIAG_MODE_CTL_1: + case CS35L34_DIAG_MODE_CTL_2: + case CS35L34_INT_MASK_1: + case CS35L34_INT_MASK_2: + case CS35L34_INT_MASK_3: + case CS35L34_INT_MASK_4: + case CS35L34_INT_STATUS_1: + case CS35L34_INT_STATUS_2: + case CS35L34_INT_STATUS_3: + case CS35L34_INT_STATUS_4: + case CS35L34_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l34_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L34_INT_STATUS_1: + case CS35L34_INT_STATUS_2: + case CS35L34_INT_STATUS_3: + case CS35L34_INT_STATUS_4: + return true; + default: + return false; + } +} + +static int cs35l34_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l34_private *priv = snd_soc_component_get_drvdata(component); + int ret; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (priv->tdm_mode) + regmap_update_bits(priv->regmap, CS35L34_PWRCTL3, + CS35L34_PDN_TDM, 0x00); + + ret = regmap_update_bits(priv->regmap, CS35L34_PWRCTL1, + CS35L34_PDN_ALL, 0); + if (ret < 0) { + dev_err(component->dev, "Cannot set Power bits %d\n", ret); + return ret; + } + usleep_range(5000, 5100); + break; + case SND_SOC_DAPM_POST_PMD: + if (priv->tdm_mode) { + regmap_update_bits(priv->regmap, CS35L34_PWRCTL3, + CS35L34_PDN_TDM, CS35L34_PDN_TDM); + } + ret = regmap_update_bits(priv->regmap, CS35L34_PWRCTL1, + CS35L34_PDN_ALL, CS35L34_PDN_ALL); + break; + default: + pr_err("Invalid event = 0x%x\n", event); + } + return 0; +} + +static int cs35l34_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct cs35l34_private *priv = snd_soc_component_get_drvdata(component); + unsigned int reg, bit_pos; + int slot, slot_num; + + if (slot_width != 8) + return -EINVAL; + + priv->tdm_mode = true; + /* scan rx_mask for aud slot */ + slot = ffs(rx_mask) - 1; + if (slot >= 0) + snd_soc_component_update_bits(component, CS35L34_TDM_RX_CTL_1_AUDIN, + CS35L34_X_LOC, slot); + + /* scan tx_mask: vmon(2 slots); imon (2 slots); vpmon (1 slot) + * vbstmon (1 slot) + */ + slot = ffs(tx_mask) - 1; + slot_num = 0; + + /* disable vpmon/vbstmon: enable later if set in tx_mask */ + snd_soc_component_update_bits(component, CS35L34_TDM_TX_CTL_3_VPMON, + CS35L34_X_STATE | CS35L34_X_LOC, + CS35L34_X_STATE | CS35L34_X_LOC); + snd_soc_component_update_bits(component, CS35L34_TDM_TX_CTL_4_VBSTMON, + CS35L34_X_STATE | CS35L34_X_LOC, + CS35L34_X_STATE | CS35L34_X_LOC); + + /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + while (slot >= 0) { + /* configure VMON_TX_LOC */ + if (slot_num == 0) + snd_soc_component_update_bits(component, CS35L34_TDM_TX_CTL_1_VMON, + CS35L34_X_STATE | CS35L34_X_LOC, slot); + + /* configure IMON_TX_LOC */ + if (slot_num == 4) { + snd_soc_component_update_bits(component, CS35L34_TDM_TX_CTL_2_IMON, + CS35L34_X_STATE | CS35L34_X_LOC, slot); + } + /* configure VPMON_TX_LOC */ + if (slot_num == 3) { + snd_soc_component_update_bits(component, CS35L34_TDM_TX_CTL_3_VPMON, + CS35L34_X_STATE | CS35L34_X_LOC, slot); + } + /* configure VBSTMON_TX_LOC */ + if (slot_num == 7) { + snd_soc_component_update_bits(component, + CS35L34_TDM_TX_CTL_4_VBSTMON, + CS35L34_X_STATE | CS35L34_X_LOC, slot); + } + + /* Enable the relevant tx slot */ + reg = CS35L34_TDM_TX_SLOT_EN_4 - (slot/8); + bit_pos = slot - ((slot / 8) * (8)); + snd_soc_component_update_bits(component, reg, + 1 << bit_pos, 1 << bit_pos); + + tx_mask &= ~(1 << slot); + slot = ffs(tx_mask) - 1; + slot_num++; + } + + return 0; +} + +static int cs35l34_main_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l34_private *priv = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(priv->regmap, CS35L34_BST_CVTR_V_CTL, + CS35L34_BST_CVTL_MASK, priv->pdata.boost_vtge); + usleep_range(5000, 5100); + regmap_update_bits(priv->regmap, CS35L34_PROTECT_CTL, + CS35L34_MUTE, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(priv->regmap, CS35L34_BST_CVTR_V_CTL, + CS35L34_BST_CVTL_MASK, 0); + regmap_update_bits(priv->regmap, CS35L34_PROTECT_CTL, + CS35L34_MUTE, CS35L34_MUTE); + usleep_range(5000, 5100); + break; + default: + pr_err("Invalid event = 0x%x\n", event); + } + return 0; +} + +static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10200, 50, 0); + +static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 300, 100, 0); + + +static const struct snd_kcontrol_new cs35l34_snd_controls[] = { + SOC_SINGLE_SX_TLV("Digital Volume", CS35L34_AMP_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Amp Gain Volume", CS35L34_AMP_ANLG_GAIN_CTL, + 0, 0xF, 0, amp_gain_tlv), +}; + + +static int cs35l34_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l34_private *priv = snd_soc_component_get_drvdata(component); + int ret, i; + unsigned int reg; + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + ret = regmap_read(priv->regmap, CS35L34_AMP_DIG_VOL_CTL, + ®); + if (ret != 0) { + pr_err("%s regmap read failure %d\n", __func__, ret); + return ret; + } + if (reg & CS35L34_AMP_DIGSFT) + msleep(40); + else + usleep_range(2000, 2100); + + for (i = 0; i < PDN_DONE_ATTEMPTS; i++) { + ret = regmap_read(priv->regmap, CS35L34_INT_STATUS_2, + ®); + if (ret != 0) { + pr_err("%s regmap read failure %d\n", + __func__, ret); + return ret; + } + if (reg & CS35L34_PDN_DONE) + break; + + usleep_range(5000, 5100); + } + if (i == PDN_DONE_ATTEMPTS) + pr_err("%s Device did not power down properly\n", + __func__); + break; + default: + pr_err("Invalid event = 0x%x\n", event); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget cs35l34_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L34_PWRCTL3, + 1, 1, cs35l34_sdin_event, + SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L34_PWRCTL3, 2, 1), + + SND_SOC_DAPM_SUPPLY("EXTCLK", CS35L34_PWRCTL3, 7, 1, + cs35l34_mclk_event, SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_OUTPUT("SPK"), + + SND_SOC_DAPM_INPUT("VP"), + SND_SOC_DAPM_INPUT("VPST"), + SND_SOC_DAPM_INPUT("ISENSE"), + SND_SOC_DAPM_INPUT("VSENSE"), + + SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L34_PWRCTL2, 7, 1), + SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L34_PWRCTL2, 6, 1), + SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L34_PWRCTL3, 3, 1), + SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L34_PWRCTL3, 4, 1), + SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L34_PWRCTL2, 5, 1), + SND_SOC_DAPM_ADC("BOOST", NULL, CS35L34_PWRCTL2, 2, 1), + + SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L34_PWRCTL2, 0, 1, NULL, 0, + cs35l34_main_amp_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cs35l34_audio_map[] = { + {"SDIN", NULL, "AMP Playback"}, + {"BOOST", NULL, "SDIN"}, + {"CLASS H", NULL, "BOOST"}, + {"Main AMP", NULL, "CLASS H"}, + {"SPK", NULL, "Main AMP"}, + + {"VPMON ADC", NULL, "CLASS H"}, + {"VBSTMON ADC", NULL, "CLASS H"}, + {"SPK", NULL, "VPMON ADC"}, + {"SPK", NULL, "VBSTMON ADC"}, + + {"IMON ADC", NULL, "ISENSE"}, + {"VMON ADC", NULL, "VSENSE"}, + {"SDOUT", NULL, "IMON ADC"}, + {"SDOUT", NULL, "VMON ADC"}, + {"AMP Capture", NULL, "SDOUT"}, + + {"SDIN", NULL, "EXTCLK"}, + {"SDOUT", NULL, "EXTCLK"}, +}; + +struct cs35l34_mclk_div { + int mclk; + int srate; + u8 adsp_rate; +}; + +static struct cs35l34_mclk_div cs35l34_mclk_coeffs[] = { + + /* MCLK, Sample Rate, adsp_rate */ + + {5644800, 11025, 0x1}, + {5644800, 22050, 0x4}, + {5644800, 44100, 0x7}, + + {6000000, 8000, 0x0}, + {6000000, 11025, 0x1}, + {6000000, 12000, 0x2}, + {6000000, 16000, 0x3}, + {6000000, 22050, 0x4}, + {6000000, 24000, 0x5}, + {6000000, 32000, 0x6}, + {6000000, 44100, 0x7}, + {6000000, 48000, 0x8}, + + {6144000, 8000, 0x0}, + {6144000, 11025, 0x1}, + {6144000, 12000, 0x2}, + {6144000, 16000, 0x3}, + {6144000, 22050, 0x4}, + {6144000, 24000, 0x5}, + {6144000, 32000, 0x6}, + {6144000, 44100, 0x7}, + {6144000, 48000, 0x8}, +}; + +static int cs35l34_get_mclk_coeff(int mclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l34_mclk_coeffs); i++) { + if (cs35l34_mclk_coeffs[i].mclk == mclk && + cs35l34_mclk_coeffs[i].srate == srate) + return i; + } + return -EINVAL; +} + +static int cs35l34_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs35l34_private *priv = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(priv->regmap, CS35L34_ADSP_CLK_CTL, + 0x80, 0x80); + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(priv->regmap, CS35L34_ADSP_CLK_CTL, + 0x80, 0x00); + break; + default: + return -EINVAL; + } + return 0; +} + +static int cs35l34_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs35l34_private *priv = snd_soc_component_get_drvdata(component); + int srate = params_rate(params); + int ret; + + int coeff = cs35l34_get_mclk_coeff(priv->mclk_int, srate); + + if (coeff < 0) { + dev_err(component->dev, "ERROR: Invalid mclk %d and/or srate %d\n", + priv->mclk_int, srate); + return coeff; + } + + ret = regmap_update_bits(priv->regmap, CS35L34_ADSP_CLK_CTL, + CS35L34_ADSP_RATE, cs35l34_mclk_coeffs[coeff].adsp_rate); + if (ret != 0) + dev_err(component->dev, "Failed to set clock state %d\n", ret); + + return ret; +} + +static const unsigned int cs35l34_src_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + + +static const struct snd_pcm_hw_constraint_list cs35l34_constraints = { + .count = ARRAY_SIZE(cs35l34_src_rates), + .list = cs35l34_src_rates, +}; + +static int cs35l34_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cs35l34_constraints); + return 0; +} + + +static int cs35l34_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + + struct snd_soc_component *component = dai->component; + + if (tristate) + snd_soc_component_update_bits(component, CS35L34_PWRCTL3, + CS35L34_PDN_SDOUT, CS35L34_PDN_SDOUT); + else + snd_soc_component_update_bits(component, CS35L34_PWRCTL3, + CS35L34_PDN_SDOUT, 0); + return 0; +} + +static int cs35l34_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct cs35l34_private *cs35l34 = snd_soc_component_get_drvdata(component); + unsigned int value; + + switch (freq) { + case CS35L34_MCLK_5644: + value = CS35L34_MCLK_RATE_5P6448; + cs35l34->mclk_int = freq; + break; + case CS35L34_MCLK_6: + value = CS35L34_MCLK_RATE_6P0000; + cs35l34->mclk_int = freq; + break; + case CS35L34_MCLK_6144: + value = CS35L34_MCLK_RATE_6P1440; + cs35l34->mclk_int = freq; + break; + case CS35L34_MCLK_11289: + value = CS35L34_MCLK_DIV | CS35L34_MCLK_RATE_5P6448; + cs35l34->mclk_int = freq / 2; + break; + case CS35L34_MCLK_12: + value = CS35L34_MCLK_DIV | CS35L34_MCLK_RATE_6P0000; + cs35l34->mclk_int = freq / 2; + break; + case CS35L34_MCLK_12288: + value = CS35L34_MCLK_DIV | CS35L34_MCLK_RATE_6P1440; + cs35l34->mclk_int = freq / 2; + break; + default: + dev_err(component->dev, "ERROR: Invalid Frequency %d\n", freq); + cs35l34->mclk_int = 0; + return -EINVAL; + } + regmap_update_bits(cs35l34->regmap, CS35L34_MCLK_CTL, + CS35L34_MCLK_DIV | CS35L34_MCLK_RATE_MASK, value); + return 0; +} + +static const struct snd_soc_dai_ops cs35l34_ops = { + .startup = cs35l34_pcm_startup, + .set_tristate = cs35l34_set_tristate, + .set_fmt = cs35l34_set_dai_fmt, + .hw_params = cs35l34_pcm_hw_params, + .set_sysclk = cs35l34_dai_set_sysclk, + .set_tdm_slot = cs35l34_set_tdm_slot, +}; + +static struct snd_soc_dai_driver cs35l34_dai = { + .name = "cs35l34", + .id = 0, + .playback = { + .stream_name = "AMP Playback", + .channels_min = 1, + .channels_max = 8, + .rates = CS35L34_RATES, + .formats = CS35L34_FORMATS, + }, + .capture = { + .stream_name = "AMP Capture", + .channels_min = 1, + .channels_max = 8, + .rates = CS35L34_RATES, + .formats = CS35L34_FORMATS, + }, + .ops = &cs35l34_ops, + .symmetric_rates = 1, +}; + +static int cs35l34_boost_inductor(struct cs35l34_private *cs35l34, + unsigned int inductor) +{ + struct snd_soc_component *component = cs35l34->component; + + switch (inductor) { + case 1000: /* 1 uH */ + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_1, 0x24); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_2, 0x24); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SLOPE_COMP, + 0x4E); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SW_FREQ, 0); + break; + case 1200: /* 1.2 uH */ + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_1, 0x20); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_2, 0x20); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SLOPE_COMP, + 0x47); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SW_FREQ, 1); + break; + case 1500: /* 1.5uH */ + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_1, 0x20); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_2, 0x20); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SLOPE_COMP, + 0x3C); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SW_FREQ, 2); + break; + case 2200: /* 2.2uH */ + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_1, 0x19); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_COEF_2, 0x25); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SLOPE_COMP, + 0x23); + regmap_write(cs35l34->regmap, CS35L34_BST_CONV_SW_FREQ, 3); + break; + default: + dev_err(component->dev, "%s Invalid Inductor Value %d uH\n", + __func__, inductor); + return -EINVAL; + } + return 0; +} + +static int cs35l34_probe(struct snd_soc_component *component) +{ + int ret = 0; + struct cs35l34_private *cs35l34 = snd_soc_component_get_drvdata(component); + + pm_runtime_get_sync(component->dev); + + /* Set over temperature warning attenuation to 6 dB */ + regmap_update_bits(cs35l34->regmap, CS35L34_PROTECT_CTL, + CS35L34_OTW_ATTN_MASK, 0x8); + + /* Set Power control registers 2 and 3 to have everything + * powered down at initialization + */ + regmap_write(cs35l34->regmap, CS35L34_PWRCTL2, 0xFD); + regmap_write(cs35l34->regmap, CS35L34_PWRCTL3, 0x1F); + + /* Set mute bit at startup */ + regmap_update_bits(cs35l34->regmap, CS35L34_PROTECT_CTL, + CS35L34_MUTE, CS35L34_MUTE); + + /* Set Platform Data */ + if (cs35l34->pdata.boost_peak) + regmap_update_bits(cs35l34->regmap, CS35L34_BST_PEAK_I, + CS35L34_BST_PEAK_MASK, + cs35l34->pdata.boost_peak); + + if (cs35l34->pdata.gain_zc_disable) + regmap_update_bits(cs35l34->regmap, CS35L34_PROTECT_CTL, + CS35L34_GAIN_ZC_MASK, 0); + else + regmap_update_bits(cs35l34->regmap, CS35L34_PROTECT_CTL, + CS35L34_GAIN_ZC_MASK, CS35L34_GAIN_ZC_MASK); + + if (cs35l34->pdata.aif_half_drv) + regmap_update_bits(cs35l34->regmap, CS35L34_ADSP_CLK_CTL, + CS35L34_ADSP_DRIVE, 0); + + if (cs35l34->pdata.digsft_disable) + regmap_update_bits(cs35l34->regmap, CS35L34_AMP_DIG_VOL_CTL, + CS35L34_AMP_DIGSFT, 0); + + if (cs35l34->pdata.amp_inv) + regmap_update_bits(cs35l34->regmap, CS35L34_AMP_DIG_VOL_CTL, + CS35L34_INV, CS35L34_INV); + + if (cs35l34->pdata.boost_ind) + ret = cs35l34_boost_inductor(cs35l34, cs35l34->pdata.boost_ind); + + if (cs35l34->pdata.i2s_sdinloc) + regmap_update_bits(cs35l34->regmap, CS35L34_ADSP_I2S_CTL, + CS35L34_I2S_LOC_MASK, + cs35l34->pdata.i2s_sdinloc << CS35L34_I2S_LOC_SHIFT); + + if (cs35l34->pdata.tdm_rising_edge) + regmap_update_bits(cs35l34->regmap, CS35L34_ADSP_TDM_CTL, + 1, 1); + + pm_runtime_put_sync(component->dev); + + return ret; +} + + +static const struct snd_soc_component_driver soc_component_dev_cs35l34 = { + .probe = cs35l34_probe, + .dapm_widgets = cs35l34_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l34_dapm_widgets), + .dapm_routes = cs35l34_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l34_audio_map), + .controls = cs35l34_snd_controls, + .num_controls = ARRAY_SIZE(cs35l34_snd_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct regmap_config cs35l34_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L34_MAX_REGISTER, + .reg_defaults = cs35l34_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l34_reg), + .volatile_reg = cs35l34_volatile_register, + .readable_reg = cs35l34_readable_register, + .precious_reg = cs35l34_precious_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs35l34_handle_of_data(struct i2c_client *i2c_client, + struct cs35l34_platform_data *pdata) +{ + struct device_node *np = i2c_client->dev.of_node; + unsigned int val; + + if (of_property_read_u32(np, "cirrus,boost-vtge-millivolt", + &val) >= 0) { + /* Boost Voltage has a maximum of 8V */ + if (val > 8000 || (val < 3300 && val > 0)) { + dev_err(&i2c_client->dev, + "Invalid Boost Voltage %d mV\n", val); + return -EINVAL; + } + if (val == 0) + pdata->boost_vtge = 0; /* Use VP */ + else + pdata->boost_vtge = ((val - 3300)/100) + 1; + } else { + dev_warn(&i2c_client->dev, + "Boost Voltage not specified. Using VP\n"); + } + + if (of_property_read_u32(np, "cirrus,boost-ind-nanohenry", &val) >= 0) { + pdata->boost_ind = val; + } else { + dev_err(&i2c_client->dev, "Inductor not specified.\n"); + return -EINVAL; + } + + if (of_property_read_u32(np, "cirrus,boost-peak-milliamp", &val) >= 0) { + if (val > 3840 || val < 1200) { + dev_err(&i2c_client->dev, + "Invalid Boost Peak Current %d mA\n", val); + return -EINVAL; + } + pdata->boost_peak = ((val - 1200)/80) + 1; + } + + pdata->aif_half_drv = of_property_read_bool(np, + "cirrus,aif-half-drv"); + pdata->digsft_disable = of_property_read_bool(np, + "cirrus,digsft-disable"); + + pdata->gain_zc_disable = of_property_read_bool(np, + "cirrus,gain-zc-disable"); + pdata->amp_inv = of_property_read_bool(np, "cirrus,amp-inv"); + + if (of_property_read_u32(np, "cirrus,i2s-sdinloc", &val) >= 0) + pdata->i2s_sdinloc = val; + if (of_property_read_u32(np, "cirrus,tdm-rising-edge", &val) >= 0) + pdata->tdm_rising_edge = val; + + return 0; +} + +static irqreturn_t cs35l34_irq_thread(int irq, void *data) +{ + struct cs35l34_private *cs35l34 = data; + struct snd_soc_component *component = cs35l34->component; + unsigned int sticky1, sticky2, sticky3, sticky4; + unsigned int mask1, mask2, mask3, mask4, current1; + + + /* ack the irq by reading all status registers */ + regmap_read(cs35l34->regmap, CS35L34_INT_STATUS_4, &sticky4); + regmap_read(cs35l34->regmap, CS35L34_INT_STATUS_3, &sticky3); + regmap_read(cs35l34->regmap, CS35L34_INT_STATUS_2, &sticky2); + regmap_read(cs35l34->regmap, CS35L34_INT_STATUS_1, &sticky1); + + regmap_read(cs35l34->regmap, CS35L34_INT_MASK_4, &mask4); + regmap_read(cs35l34->regmap, CS35L34_INT_MASK_3, &mask3); + regmap_read(cs35l34->regmap, CS35L34_INT_MASK_2, &mask2); + regmap_read(cs35l34->regmap, CS35L34_INT_MASK_1, &mask1); + + if (!(sticky1 & ~mask1) && !(sticky2 & ~mask2) && !(sticky3 & ~mask3) + && !(sticky4 & ~mask4)) + return IRQ_NONE; + + regmap_read(cs35l34->regmap, CS35L34_INT_STATUS_1, ¤t1); + + if (sticky1 & CS35L34_CAL_ERR) { + dev_err(component->dev, "Cal error\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L34_CAL_ERR)) { + dev_dbg(component->dev, "Cal error release\n"); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_CAL_ERR_RLS, 0); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_CAL_ERR_RLS, + CS35L34_CAL_ERR_RLS); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_CAL_ERR_RLS, 0); + /* note: amp will re-calibrate on next resume */ + } + } + + if (sticky1 & CS35L34_ALIVE_ERR) + dev_err(component->dev, "Alive error\n"); + + if (sticky1 & CS35L34_AMP_SHORT) { + dev_crit(component->dev, "Amp short error\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L34_AMP_SHORT)) { + dev_dbg(component->dev, + "Amp short error release\n"); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_SHORT_RLS, 0); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_SHORT_RLS, + CS35L34_SHORT_RLS); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_SHORT_RLS, 0); + } + } + + if (sticky1 & CS35L34_OTW) { + dev_crit(component->dev, "Over temperature warning\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L34_OTW)) { + dev_dbg(component->dev, + "Over temperature warning release\n"); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_OTW_RLS, 0); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_OTW_RLS, + CS35L34_OTW_RLS); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_OTW_RLS, 0); + } + } + + if (sticky1 & CS35L34_OTE) { + dev_crit(component->dev, "Over temperature error\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L34_OTE)) { + dev_dbg(component->dev, + "Over temperature error release\n"); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_OTE_RLS, 0); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_OTE_RLS, + CS35L34_OTE_RLS); + regmap_update_bits(cs35l34->regmap, + CS35L34_PROT_RELEASE_CTL, + CS35L34_OTE_RLS, 0); + } + } + + if (sticky3 & CS35L34_BST_HIGH) { + dev_crit(component->dev, "VBST too high error; powering off!\n"); + regmap_update_bits(cs35l34->regmap, CS35L34_PWRCTL2, + CS35L34_PDN_AMP, CS35L34_PDN_AMP); + regmap_update_bits(cs35l34->regmap, CS35L34_PWRCTL1, + CS35L34_PDN_ALL, CS35L34_PDN_ALL); + } + + if (sticky3 & CS35L34_LBST_SHORT) { + dev_crit(component->dev, "LBST short error; powering off!\n"); + regmap_update_bits(cs35l34->regmap, CS35L34_PWRCTL2, + CS35L34_PDN_AMP, CS35L34_PDN_AMP); + regmap_update_bits(cs35l34->regmap, CS35L34_PWRCTL1, + CS35L34_PDN_ALL, CS35L34_PDN_ALL); + } + + return IRQ_HANDLED; +} + +static const char * const cs35l34_core_supplies[] = { + "VA", + "VP", +}; + +static int cs35l34_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l34_private *cs35l34; + struct cs35l34_platform_data *pdata = + dev_get_platdata(&i2c_client->dev); + int i; + int ret; + unsigned int devid = 0; + unsigned int reg; + + cs35l34 = devm_kzalloc(&i2c_client->dev, sizeof(*cs35l34), GFP_KERNEL); + if (!cs35l34) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs35l34); + cs35l34->regmap = devm_regmap_init_i2c(i2c_client, &cs35l34_regmap); + if (IS_ERR(cs35l34->regmap)) { + ret = PTR_ERR(cs35l34->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + cs35l34->num_core_supplies = ARRAY_SIZE(cs35l34_core_supplies); + for (i = 0; i < ARRAY_SIZE(cs35l34_core_supplies); i++) + cs35l34->core_supplies[i].supply = cs35l34_core_supplies[i]; + + ret = devm_regulator_bulk_get(&i2c_client->dev, + cs35l34->num_core_supplies, + cs35l34->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request core supplies %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(cs35l34->num_core_supplies, + cs35l34->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable core supplies: %d\n", ret); + return ret; + } + + if (pdata) { + cs35l34->pdata = *pdata; + } else { + pdata = devm_kzalloc(&i2c_client->dev, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (i2c_client->dev.of_node) { + ret = cs35l34_handle_of_data(i2c_client, pdata); + if (ret != 0) + return ret; + + } + cs35l34->pdata = *pdata; + } + + ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL, + cs35l34_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs35l34", cs35l34); + if (ret != 0) + dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret); + + cs35l34->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset-gpios", GPIOD_OUT_LOW); + if (IS_ERR(cs35l34->reset_gpio)) + return PTR_ERR(cs35l34->reset_gpio); + + gpiod_set_value_cansleep(cs35l34->reset_gpio, 1); + + msleep(CS35L34_START_DELAY); + + ret = regmap_read(cs35l34->regmap, CS35L34_DEVID_AB, ®); + + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l34->regmap, CS35L34_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l34->regmap, CS35L34_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L34_CHIP_ID) { + dev_err(&i2c_client->dev, + "CS35l34 Device ID (%X). Expected ID %X\n", + devid, CS35L34_CHIP_ID); + ret = -ENODEV; + goto err_regulator; + } + + ret = regmap_read(cs35l34->regmap, CS35L34_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed\n"); + goto err_regulator; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS35l34 (%x), Revision: %02X\n", devid, + reg & 0xFF); + + /* Unmask critical interrupts */ + regmap_update_bits(cs35l34->regmap, CS35L34_INT_MASK_1, + CS35L34_M_CAL_ERR | CS35L34_M_ALIVE_ERR | + CS35L34_M_AMP_SHORT | CS35L34_M_OTW | + CS35L34_M_OTE, 0); + regmap_update_bits(cs35l34->regmap, CS35L34_INT_MASK_3, + CS35L34_M_BST_HIGH | CS35L34_M_LBST_SHORT, 0); + + pm_runtime_set_autosuspend_delay(&i2c_client->dev, 100); + pm_runtime_use_autosuspend(&i2c_client->dev); + pm_runtime_set_active(&i2c_client->dev); + pm_runtime_enable(&i2c_client->dev); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_cs35l34, &cs35l34_dai, 1); + if (ret < 0) { + dev_err(&i2c_client->dev, + "%s: Register component failed\n", __func__); + goto err_regulator; + } + + return 0; + +err_regulator: + regulator_bulk_disable(cs35l34->num_core_supplies, + cs35l34->core_supplies); + + return ret; +} + +static int cs35l34_i2c_remove(struct i2c_client *client) +{ + struct cs35l34_private *cs35l34 = i2c_get_clientdata(client); + + gpiod_set_value_cansleep(cs35l34->reset_gpio, 0); + + pm_runtime_disable(&client->dev); + regulator_bulk_disable(cs35l34->num_core_supplies, + cs35l34->core_supplies); + + return 0; +} + +static int __maybe_unused cs35l34_runtime_resume(struct device *dev) +{ + struct cs35l34_private *cs35l34 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(cs35l34->num_core_supplies, + cs35l34->core_supplies); + + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", + ret); + return ret; + } + + regcache_cache_only(cs35l34->regmap, false); + + gpiod_set_value_cansleep(cs35l34->reset_gpio, 1); + msleep(CS35L34_START_DELAY); + + ret = regcache_sync(cs35l34->regmap); + if (ret != 0) { + dev_err(dev, "Failed to restore register cache\n"); + goto err; + } + return 0; +err: + regcache_cache_only(cs35l34->regmap, true); + regulator_bulk_disable(cs35l34->num_core_supplies, + cs35l34->core_supplies); + + return ret; +} + +static int __maybe_unused cs35l34_runtime_suspend(struct device *dev) +{ + struct cs35l34_private *cs35l34 = dev_get_drvdata(dev); + + regcache_cache_only(cs35l34->regmap, true); + regcache_mark_dirty(cs35l34->regmap); + + gpiod_set_value_cansleep(cs35l34->reset_gpio, 0); + + regulator_bulk_disable(cs35l34->num_core_supplies, + cs35l34->core_supplies); + + return 0; +} + +static const struct dev_pm_ops cs35l34_pm_ops = { + SET_RUNTIME_PM_OPS(cs35l34_runtime_suspend, + cs35l34_runtime_resume, + NULL) +}; + +static const struct of_device_id cs35l34_of_match[] = { + {.compatible = "cirrus,cs35l34"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l34_of_match); + +static const struct i2c_device_id cs35l34_id[] = { + {"cs35l34", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs35l34_id); + +static struct i2c_driver cs35l34_i2c_driver = { + .driver = { + .name = "cs35l34", + .pm = &cs35l34_pm_ops, + .of_match_table = cs35l34_of_match, + + }, + .id_table = cs35l34_id, + .probe = cs35l34_i2c_probe, + .remove = cs35l34_i2c_remove, + +}; + +static int __init cs35l34_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&cs35l34_i2c_driver); + if (ret != 0) { + pr_err("Failed to register CS35l34 I2C driver: %d\n", ret); + return ret; + } + return 0; +} +module_init(cs35l34_modinit); + +static void __exit cs35l34_exit(void) +{ + i2c_del_driver(&cs35l34_i2c_driver); +} +module_exit(cs35l34_exit); + +MODULE_DESCRIPTION("ASoC CS35l34 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l34.h b/sound/soc/codecs/cs35l34.h new file mode 100644 index 000000000..bcd54f127 --- /dev/null +++ b/sound/soc/codecs/cs35l34.h @@ -0,0 +1,269 @@ +/* + * cs35l34.h -- CS35L34 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS35L34_H__ +#define __CS35L34_H__ + +#define CS35L34_CHIP_ID 0x00035A34 +#define CS35L34_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L34_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L34_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L34_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L34_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L34_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L34_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L34_PWRCTL3 0x08 /* Power Ctl 3 */ +#define CS35L34_ADSP_CLK_CTL 0x0A /* (ADSP) Clock Ctl */ +#define CS35L34_MCLK_CTL 0x0B /* Master Clocking Ctl */ +#define CS35L34_AMP_INP_DRV_CTL 0x14 /* Amp Input Drive Ctl */ +#define CS35L34_AMP_DIG_VOL_CTL 0x15 /* Amplifier Dig Volume Ctl */ +#define CS35L34_AMP_DIG_VOL 0x16 /* Amplifier Dig Volume */ +#define CS35L34_AMP_ANLG_GAIN_CTL 0x17 /* Amplifier Analog Gain Ctl */ +#define CS35L34_PROTECT_CTL 0x18 /* Amp Gain - Prot Ctl Param */ +#define CS35L34_AMP_KEEP_ALIVE_CTL 0x1A /* Amplifier Keep Alive Ctl */ +#define CS35L34_BST_CVTR_V_CTL 0x1D /* Boost Conv Voltage Ctl */ +#define CS35L34_BST_PEAK_I 0x1E /* Boost Conv Peak Current */ +#define CS35L34_BST_RAMP_CTL 0x20 /* Boost Conv Soft Ramp Ctl */ +#define CS35L34_BST_CONV_COEF_1 0x21 /* Boost Conv Coefficients 1 */ +#define CS35L34_BST_CONV_COEF_2 0x22 /* Boost Conv Coefficients 2 */ +#define CS35L34_BST_CONV_SLOPE_COMP 0x23 /* Boost Conv Slope Comp */ +#define CS35L34_BST_CONV_SW_FREQ 0x24 /* Boost Conv L BST SW Freq */ +#define CS35L34_CLASS_H_CTL 0x30 /* CLS H Control */ +#define CS35L34_CLASS_H_HEADRM_CTL 0x31 /* CLS H Headroom Ctl */ +#define CS35L34_CLASS_H_RELEASE_RATE 0x32 /* CLS H Release Rate */ +#define CS35L34_CLASS_H_FET_DRIVE_CTL 0x33 /* CLS H Weak FET Drive Ctl */ +#define CS35L34_CLASS_H_STATUS 0x38 /* CLS H Status */ +#define CS35L34_VPBR_CTL 0x3A /* VPBR Ctl */ +#define CS35L34_VPBR_VOL_CTL 0x3B /* VPBR Volume Ctl */ +#define CS35L34_VPBR_TIMING_CTL 0x3C /* VPBR Timing Ctl */ +#define CS35L34_PRED_MAX_ATTEN_SPK_LOAD 0x40 /* PRD Max Atten / Spkr Load */ +#define CS35L34_PRED_BROWNOUT_THRESH 0x41 /* PRD Brownout Threshold */ +#define CS35L34_PRED_BROWNOUT_VOL_CTL 0x42 /* PRD Brownout Volume Ctl */ +#define CS35L34_PRED_BROWNOUT_RATE_CTL 0x43 /* PRD Brownout Rate Ctl */ +#define CS35L34_PRED_WAIT_CTL 0x44 /* PRD Wait Ctl */ +#define CS35L34_PRED_ZVP_INIT_IMP_CTL 0x46 /* PRD ZVP Initial Imp Ctl */ +#define CS35L34_PRED_MAN_SAFE_VPI_CTL 0x47 /* PRD Manual Safe VPI Ctl */ +#define CS35L34_VPBR_ATTEN_STATUS 0x4B /* VPBR Attenuation Status */ +#define CS35L34_PRED_BRWNOUT_ATT_STATUS 0x4C /* PRD Brownout Atten Status */ +#define CS35L34_SPKR_MON_CTL 0x4E /* Speaker Monitoring Ctl */ +#define CS35L34_ADSP_I2S_CTL 0x50 /* ADSP I2S Ctl */ +#define CS35L34_ADSP_TDM_CTL 0x51 /* ADSP TDM Ctl */ +#define CS35L34_TDM_TX_CTL_1_VMON 0x52 /* TDM TX Ctl 1 (VMON) */ +#define CS35L34_TDM_TX_CTL_2_IMON 0x53 /* TDM TX Ctl 2 (IMON) */ +#define CS35L34_TDM_TX_CTL_3_VPMON 0x54 /* TDM TX Ctl 3 (VPMON) */ +#define CS35L34_TDM_TX_CTL_4_VBSTMON 0x55 /* TDM TX Ctl 4 (VBSTMON) */ +#define CS35L34_TDM_TX_CTL_5_FLAG1 0x56 /* TDM TX Ctl 5 (FLAG1) */ +#define CS35L34_TDM_TX_CTL_6_FLAG2 0x57 /* TDM TX Ctl 6 (FLAG2) */ +#define CS35L34_TDM_TX_SLOT_EN_1 0x5A /* TDM TX Slot Enable */ +#define CS35L34_TDM_TX_SLOT_EN_2 0x5B /* TDM TX Slot Enable */ +#define CS35L34_TDM_TX_SLOT_EN_3 0x5C /* TDM TX Slot Enable */ +#define CS35L34_TDM_TX_SLOT_EN_4 0x5D /* TDM TX Slot Enable */ +#define CS35L34_TDM_RX_CTL_1_AUDIN 0x5E /* TDM RX Ctl 1 */ +#define CS35L34_TDM_RX_CTL_3_ALIVE 0x60 /* TDM RX Ctl 3 (ALIVE) */ +#define CS35L34_MULT_DEV_SYNCH1 0x62 /* Multidevice Synch */ +#define CS35L34_MULT_DEV_SYNCH2 0x63 /* Multidevice Synch 2 */ +#define CS35L34_PROT_RELEASE_CTL 0x64 /* Protection Release Ctl */ +#define CS35L34_DIAG_MODE_REG_LOCK 0x68 /* Diagnostic Mode Reg Lock */ +#define CS35L34_DIAG_MODE_CTL_1 0x69 /* Diagnostic Mode Ctl 1 */ +#define CS35L34_DIAG_MODE_CTL_2 0x6A /* Diagnostic Mode Ctl 2 */ +#define CS35L34_INT_MASK_1 0x70 /* Interrupt Mask 1 */ +#define CS35L34_INT_MASK_2 0x71 /* Interrupt Mask 2 */ +#define CS35L34_INT_MASK_3 0x72 /* Interrupt Mask 3 */ +#define CS35L34_INT_MASK_4 0x73 /* Interrupt Mask 4 */ +#define CS35L34_INT_STATUS_1 0x74 /* Interrupt Status 1 */ +#define CS35L34_INT_STATUS_2 0x75 /* Interrupt Status 2 */ +#define CS35L34_INT_STATUS_3 0x76 /* Interrupt Status 3 */ +#define CS35L34_INT_STATUS_4 0x77 /* Interrupt Status 4 */ +#define CS35L34_OTP_TRIM_STATUS 0x7E /* OTP Trim Status */ + +#define CS35L34_MAX_REGISTER 0x7F +#define CS35L34_REGISTER_COUNT 0x4E + +#define CS35L34_MCLK_5644 5644800 +#define CS35L34_MCLK_6144 6144000 +#define CS35L34_MCLK_6 6000000 +#define CS35L34_MCLK_11289 11289600 +#define CS35L34_MCLK_12 12000000 +#define CS35L34_MCLK_12288 12288000 + +/* CS35L34_PWRCTL1 */ +#define CS35L34_SFT_RST (1 << 7) +#define CS35L34_DISCHG_FLT (1 << 1) +#define CS35L34_PDN_ALL 1 + +/* CS35L34_PWRCTL2 */ +#define CS35L34_PDN_VMON (1 << 7) +#define CS35L34_PDN_IMON (1 << 6) +#define CS35L34_PDN_CLASSH (1 << 5) +#define CS35L34_PDN_VPBR (1 << 4) +#define CS35L34_PDN_PRED (1 << 3) +#define CS35L34_PDN_BST (1 << 2) +#define CS35L34_PDN_AMP 1 + +/* CS35L34_PWRCTL3 */ +#define CS35L34_MCLK_DIS (1 << 7) +#define CS35L34_PDN_VBSTMON_OUT (1 << 4) +#define CS35L34_PDN_VMON_OUT (1 << 3) +/* Tristate the ADSP SDOUT when in I2C mode */ +#define CS35L34_PDN_SDOUT (1 << 2) +#define CS35L34_PDN_SDIN (1 << 1) +#define CS35L34_PDN_TDM 1 + +/* CS35L34_ADSP_CLK_CTL */ +#define CS35L34_ADSP_RATE 0xF +#define CS35L34_ADSP_DRIVE (1 << 4) +#define CS35L34_ADSP_M_S (1 << 7) + +/* CS35L34_MCLK_CTL */ +#define CS35L34_MCLK_DIV (1 << 4) +#define CS35L34_MCLK_RATE_MASK 0x7 +#define CS35L34_MCLK_RATE_6P1440 0x2 +#define CS35L34_MCLK_RATE_6P0000 0x1 +#define CS35L34_MCLK_RATE_5P6448 0x0 +#define CS35L34_MCLKDIS (1 << 7) +#define CS35L34_MCLKDIV2 (1 << 6) +#define CS35L34_SDOUT_3ST_TDM (1 << 5) +#define CS35L34_INT_FS_RATE (1 << 4) +#define CS35L34_ADSP_FS 0xF + +/* CS35L34_AMP_INP_DRV_CTL */ +#define CS35L34_DRV_STR_SRC (1 << 1) +#define CS35L34_DRV_STR 1 + +/* CS35L34_AMP_DIG_VOL_CTL */ +#define CS35L34_AMP_DSR_RATE_MASK 0xF0 +#define CS35L34_AMP_DSR_RATE_SHIFT (1 << 4) +#define CS35L34_NOTCH_DIS (1 << 3) +#define CS35L34_AMP_DIGSFT (1 << 1) +#define CS35L34_INV 1 + +/* CS35L34_PROTECT_CTL */ +#define CS35L34_OTW_ATTN_MASK 0xC +#define CS35L34_OTW_THRD_MASK 0x3 +#define CS35L34_MUTE (1 << 5) +#define CS35L34_GAIN_ZC (1 << 4) +#define CS35L34_GAIN_ZC_MASK 0x10 +#define CS35L34_GAIN_ZC_SHIFT 4 + +/* CS35L34_AMP_KEEP_ALIVE_CTL */ +#define CS35L34_ALIVE_WD_DIS (1 << 2) + +/* CS35L34_BST_CVTR_V_CTL */ +#define CS35L34_BST_CVTL_MASK 0x3F + +/* CS35L34_BST_PEAK_I */ +#define CS35L34_BST_PEAK_MASK 0x3F + +/* CS35L34_ADSP_I2S_CTL */ +#define CS35L34_I2S_LOC_MASK 0xC +#define CS35L34_I2S_LOC_SHIFT 2 + +/* CS35L34_MULT_DEV_SYNCH2 */ +#define CS35L34_SYNC2_MASK 0xF + +/* CS35L34_PROT_RELEASE_CTL */ +#define CS35L34_CAL_ERR_RLS (1 << 7) +#define CS35L34_SHORT_RLS (1 << 2) +#define CS35L34_OTW_RLS (1 << 1) +#define CS35L34_OTE_RLS 1 + +/* CS35L34_INT_MASK_1 */ +#define CS35L34_M_CAL_ERR_SHIFT 7 +#define CS35L34_M_CAL_ERR (1 << CS35L34_M_CAL_ERR_SHIFT) +#define CS35L34_M_ALIVE_ERR_SHIFT 5 +#define CS35L34_M_ALIVE_ERR (1 << CS35L34_M_ALIVE_ERR_SHIFT) +#define CS35L34_M_ADSP_CLK_SHIFT 4 +#define CS35L34_M_ADSP_CLK_ERR (1 << CS35L34_M_ADSP_CLK_SHIFT) +#define CS35L34_M_MCLK_SHIFT 3 +#define CS35L34_M_MCLK_ERR (1 << CS35L34_M_MCLK_SHIFT) +#define CS35L34_M_AMP_SHORT_SHIFT 2 +#define CS35L34_M_AMP_SHORT (1 << CS35L34_M_AMP_SHORT_SHIFT) +#define CS35L34_M_OTW_SHIFT 1 +#define CS35L34_M_OTW (1 << CS35L34_M_OTW_SHIFT) +#define CS35L34_M_OTE_SHIFT 0 +#define CS35L34_M_OTE (1 << CS35L34_M_OTE_SHIFT) + +/* CS35L34_INT_MASK_2 */ +#define CS35L34_M_PDN_DONE_SHIFT 4 +#define CS35L34_M_PDN_DONE (1 << CS35L34_M_PDN_DONE_SHIFT) +#define CS35L34_M_PRED_SHIFT 3 +#define CS35L34_M_PRED_ERR (1 << CS35L34_M_PRED_SHIFT) +#define CS35L34_M_PRED_CLR_SHIFT 2 +#define CS35L34_M_PRED_CLR (1 << CS35L34_M_PRED_CLR_SHIFT) +#define CS35L34_M_VPBR_SHIFT 1 +#define CS35L34_M_VPBR_ERR (1 << CS35L34_M_VPBR_SHIFT) +#define CS35L34_M_VPBR_CLR_SHIFT 0 +#define CS35L34_M_VPBR_CLR (1 << CS35L34_M_VPBR_CLR_SHIFT) + +/* CS35L34_INT_MASK_3 */ +#define CS35L34_M_BST_HIGH_SHIFT 4 +#define CS35L34_M_BST_HIGH (1 << CS35L34_M_BST_HIGH_SHIFT) +#define CS35L34_M_BST_HIGH_FLAG_SHIFT 3 +#define CS35L34_M_BST_HIGH_FLAG (1 << CS35L34_M_BST_HIGH_FLAG_SHIFT) +#define CS35L34_M_BST_IPK_FLAG_SHIFT 2 +#define CS35L34_M_BST_IPK_FLAG (1 << CS35L34_M_BST_IPK_FLAG_SHIFT) +#define CS35L34_M_LBST_SHORT_SHIFT 0 +#define CS35L34_M_LBST_SHORT (1 << CS35L34_M_LBST_SHORT_SHIFT) + +/* CS35L34_INT_MASK_4 */ +#define CS35L34_M_VMON_OVFL_SHIFT 3 +#define CS35L34_M_VMON_OVFL (1 << CS35L34_M_VMON_OVFL_SHIFT) +#define CS35L34_M_IMON_OVFL_SHIFT 2 +#define CS35L34_M_IMON_OVFL (1 << CS35L34_M_IMON_OVFL_SHIFT) +#define CS35L34_M_VPMON_OVFL_SHIFT 1 +#define CS35L34_M_VPMON_OVFL (1 << CS35L34_M_VPMON_OVFL_SHIFT) +#define CS35L34_M_VBSTMON_OVFL_SHIFT 1 +#define CS35L34_M_VBSTMON_OVFL (1 << CS35L34_M_VBSTMON_OVFL_SHIFT) + +/* CS35L34_INT_1 */ +#define CS35L34_CAL_ERR (1 << CS35L34_M_CAL_ERR_SHIFT) +#define CS35L34_ALIVE_ERR (1 << CS35L34_M_ALIVE_ERR_SHIFT) +#define CS35L34_M_ADSP_CLK_ERR (1 << CS35L34_M_ADSP_CLK_SHIFT) +#define CS35L34_MCLK_ERR (1 << CS35L34_M_MCLK_SHIFT) +#define CS35L34_AMP_SHORT (1 << CS35L34_M_AMP_SHORT_SHIFT) +#define CS35L34_OTW (1 << CS35L34_M_OTW_SHIFT) +#define CS35L34_OTE (1 << CS35L34_M_OTE_SHIFT) + +/* CS35L34_INT_2 */ +#define CS35L34_PDN_DONE (1 << CS35L34_M_PDN_DONE_SHIFT) +#define CS35L34_PRED_ERR (1 << CS35L34_M_PRED_SHIFT) +#define CS35L34_PRED_CLR (1 << CS35L34_M_PRED_CLR_SHIFT) +#define CS35L34_VPBR_ERR (1 << CS35L34_M_VPBR_SHIFT) +#define CS35L34_VPBR_CLR (1 << CS35L34_M_VPBR_CLR_SHIFT) + +/* CS35L34_INT_3 */ +#define CS35L34_BST_HIGH (1 << CS35L34_M_BST_HIGH_SHIFT) +#define CS35L34_BST_HIGH_FLAG (1 << CS35L34_M_BST_HIGH_FLAG_SHIFT) +#define CS35L34_BST_IPK_FLAG (1 << CS35L34_M_BST_IPK_FLAG_SHIFT) +#define CS35L34_LBST_SHORT (1 << CS35L34_M_LBST_SHORT_SHIFT) + +/* CS35L34_INT_4 */ +#define CS35L34_VMON_OVFL (1 << CS35L34_M_VMON_OVFL_SHIFT) +#define CS35L34_IMON_OVFL (1 << CS35L34_M_IMON_OVFL_SHIFT) +#define CS35L34_VPMON_OVFL (1 << CS35L34_M_VPMON_OVFL_SHIFT) +#define CS35L34_VBSTMON_OVFL (1 << CS35L34_M_VBSTMON_OVFL_SHIFT) + +/* CS35L34_{RX,TX}_X */ +#define CS35L34_X_STATE_SHIFT 7 +#define CS35L34_X_STATE (1 << CS35L34_X_STATE_SHIFT) +#define CS35L34_X_LOC_SHIFT 0 +#define CS35L34_X_LOC (0x1F << CS35L34_X_LOC_SHIFT) + +#define CS35L34_RATES (SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_32000) +#define CS35L34_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#endif diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c new file mode 100644 index 000000000..17e010108 --- /dev/null +++ b/sound/soc/codecs/cs35l35.c @@ -0,0 +1,1674 @@ +/* + * cs35l35.c -- CS35L35 ALSA SoC audio driver + * + * Copyright 2017 Cirrus Logic, Inc. + * + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs35l35.h" + +/* + * Some fields take zero as a valid value so use a high bit flag that won't + * get written to the device to mark those. + */ +#define CS35L35_VALID_PDATA 0x80000000 + +static const struct reg_default cs35l35_reg[] = { + {CS35L35_PWRCTL1, 0x01}, + {CS35L35_PWRCTL2, 0x11}, + {CS35L35_PWRCTL3, 0x00}, + {CS35L35_CLK_CTL1, 0x04}, + {CS35L35_CLK_CTL2, 0x12}, + {CS35L35_CLK_CTL3, 0xCF}, + {CS35L35_SP_FMT_CTL1, 0x20}, + {CS35L35_SP_FMT_CTL2, 0x00}, + {CS35L35_SP_FMT_CTL3, 0x02}, + {CS35L35_MAG_COMP_CTL, 0x00}, + {CS35L35_AMP_INP_DRV_CTL, 0x01}, + {CS35L35_AMP_DIG_VOL_CTL, 0x12}, + {CS35L35_AMP_DIG_VOL, 0x00}, + {CS35L35_ADV_DIG_VOL, 0x00}, + {CS35L35_PROTECT_CTL, 0x06}, + {CS35L35_AMP_GAIN_AUD_CTL, 0x13}, + {CS35L35_AMP_GAIN_PDM_CTL, 0x00}, + {CS35L35_AMP_GAIN_ADV_CTL, 0x00}, + {CS35L35_GPI_CTL, 0x00}, + {CS35L35_BST_CVTR_V_CTL, 0x00}, + {CS35L35_BST_PEAK_I, 0x07}, + {CS35L35_BST_RAMP_CTL, 0x85}, + {CS35L35_BST_CONV_COEF_1, 0x24}, + {CS35L35_BST_CONV_COEF_2, 0x24}, + {CS35L35_BST_CONV_SLOPE_COMP, 0x4E}, + {CS35L35_BST_CONV_SW_FREQ, 0x04}, + {CS35L35_CLASS_H_CTL, 0x0B}, + {CS35L35_CLASS_H_HEADRM_CTL, 0x0B}, + {CS35L35_CLASS_H_RELEASE_RATE, 0x08}, + {CS35L35_CLASS_H_FET_DRIVE_CTL, 0x41}, + {CS35L35_CLASS_H_VP_CTL, 0xC5}, + {CS35L35_VPBR_CTL, 0x0A}, + {CS35L35_VPBR_VOL_CTL, 0x90}, + {CS35L35_VPBR_TIMING_CTL, 0x6A}, + {CS35L35_VPBR_MODE_VOL_CTL, 0x00}, + {CS35L35_SPKR_MON_CTL, 0xC0}, + {CS35L35_IMON_SCALE_CTL, 0x30}, + {CS35L35_AUDIN_RXLOC_CTL, 0x00}, + {CS35L35_ADVIN_RXLOC_CTL, 0x80}, + {CS35L35_VMON_TXLOC_CTL, 0x00}, + {CS35L35_IMON_TXLOC_CTL, 0x80}, + {CS35L35_VPMON_TXLOC_CTL, 0x04}, + {CS35L35_VBSTMON_TXLOC_CTL, 0x84}, + {CS35L35_VPBR_STATUS_TXLOC_CTL, 0x04}, + {CS35L35_ZERO_FILL_LOC_CTL, 0x00}, + {CS35L35_AUDIN_DEPTH_CTL, 0x0F}, + {CS35L35_SPKMON_DEPTH_CTL, 0x0F}, + {CS35L35_SUPMON_DEPTH_CTL, 0x0F}, + {CS35L35_ZEROFILL_DEPTH_CTL, 0x00}, + {CS35L35_MULT_DEV_SYNCH1, 0x02}, + {CS35L35_MULT_DEV_SYNCH2, 0x80}, + {CS35L35_PROT_RELEASE_CTL, 0x00}, + {CS35L35_DIAG_MODE_REG_LOCK, 0x00}, + {CS35L35_DIAG_MODE_CTL_1, 0x40}, + {CS35L35_DIAG_MODE_CTL_2, 0x00}, + {CS35L35_INT_MASK_1, 0xFF}, + {CS35L35_INT_MASK_2, 0xFF}, + {CS35L35_INT_MASK_3, 0xFF}, + {CS35L35_INT_MASK_4, 0xFF}, + +}; + +static bool cs35l35_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_DEVID_AB ... CS35L35_PWRCTL3: + case CS35L35_CLK_CTL1 ... CS35L35_SP_FMT_CTL3: + case CS35L35_MAG_COMP_CTL ... CS35L35_AMP_GAIN_AUD_CTL: + case CS35L35_AMP_GAIN_PDM_CTL ... CS35L35_BST_PEAK_I: + case CS35L35_BST_RAMP_CTL ... CS35L35_BST_CONV_SW_FREQ: + case CS35L35_CLASS_H_CTL ... CS35L35_CLASS_H_VP_CTL: + case CS35L35_CLASS_H_STATUS: + case CS35L35_VPBR_CTL ... CS35L35_VPBR_MODE_VOL_CTL: + case CS35L35_VPBR_ATTEN_STATUS: + case CS35L35_SPKR_MON_CTL: + case CS35L35_IMON_SCALE_CTL ... CS35L35_ZEROFILL_DEPTH_CTL: + case CS35L35_MULT_DEV_SYNCH1 ... CS35L35_PROT_RELEASE_CTL: + case CS35L35_DIAG_MODE_REG_LOCK ... CS35L35_DIAG_MODE_CTL_2: + case CS35L35_INT_MASK_1 ... CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static void cs35l35_reset(struct cs35l35_private *cs35l35) +{ + gpiod_set_value_cansleep(cs35l35->reset_gpio, 0); + usleep_range(2000, 2100); + gpiod_set_value_cansleep(cs35l35->reset_gpio, 1); + usleep_range(1000, 1100); +} + +static int cs35l35_wait_for_pdn(struct cs35l35_private *cs35l35) +{ + int ret; + + if (cs35l35->pdata.ext_bst) { + usleep_range(5000, 5500); + return 0; + } + + reinit_completion(&cs35l35->pdn_done); + + ret = wait_for_completion_timeout(&cs35l35->pdn_done, + msecs_to_jiffies(100)); + if (ret == 0) { + dev_err(cs35l35->dev, "PDN_DONE did not complete\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 0 << CS35L35_MCLK_DIS_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 0 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 1 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 1); + + /* Already muted, so disable volume ramp for faster shutdown */ + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, 0); + + ret = cs35l35_wait_for_pdn(cs35l35); + + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 1 << CS35L35_MCLK_DIS_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, + 1 << CS35L35_AMP_DIGSFT_SHIFT); + break; + default: + dev_err(component->dev, "Invalid event = 0x%x\n", event); + ret = -EINVAL; + } + return ret; +} + +static int cs35l35_main_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + unsigned int reg[4]; + int i; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(5000, 5100); + /* If in PDM mode we must use VP for Voltage control */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + 0 << CS35L35_BST_CTL_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 0); + + for (i = 0; i < 2; i++) + regmap_bulk_read(cs35l35->regmap, CS35L35_INT_STATUS_1, + ®, ARRAY_SIZE(reg)); + + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, + 1 << CS35L35_AMP_MUTE_SHIFT); + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMD: + usleep_range(5000, 5100); + /* + * If PDM mode we should switch back to pdata value + * for Voltage control when we go down + */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl + << CS35L35_BST_CTL_SHIFT); + + break; + default: + dev_err(component->dev, "Invalid event = 0x%x\n", event); + } + return 0; +} + +static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1); +static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10200, 50, 0); + +static const struct snd_kcontrol_new cs35l35_aud_controls[] = { + SOC_SINGLE_SX_TLV("Digital Audio Volume", CS35L35_AMP_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Audio Volume", CS35L35_AMP_GAIN_AUD_CTL, 0, 19, 0, + amp_gain_tlv), + SOC_SINGLE_TLV("PDM Volume", CS35L35_AMP_GAIN_PDM_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_kcontrol_new cs35l35_adv_controls[] = { + SOC_SINGLE_SX_TLV("Digital Advisory Volume", CS35L35_ADV_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Advisory Volume", CS35L35_AMP_GAIN_ADV_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_soc_dapm_widget cs35l35_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L35_PWRCTL3, 1, 1, + cs35l35_sdin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L35_PWRCTL3, 2, 1), + + SND_SOC_DAPM_OUTPUT("SPK"), + + SND_SOC_DAPM_INPUT("VP"), + SND_SOC_DAPM_INPUT("VBST"), + SND_SOC_DAPM_INPUT("ISENSE"), + SND_SOC_DAPM_INPUT("VSENSE"), + + SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L35_PWRCTL2, 7, 1), + SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L35_PWRCTL2, 6, 1), + SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L35_PWRCTL3, 3, 1), + SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L35_PWRCTL3, 4, 1), + SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L35_PWRCTL2, 5, 1), + + SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L35_PWRCTL2, 0, 1, NULL, 0, + cs35l35_main_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), +}; + +static const struct snd_soc_dapm_route cs35l35_audio_map[] = { + {"VPMON ADC", NULL, "VP"}, + {"VBSTMON ADC", NULL, "VBST"}, + {"IMON ADC", NULL, "ISENSE"}, + {"VMON ADC", NULL, "VSENSE"}, + {"SDOUT", NULL, "IMON ADC"}, + {"SDOUT", NULL, "VMON ADC"}, + {"SDOUT", NULL, "VBSTMON ADC"}, + {"SDOUT", NULL, "VPMON ADC"}, + {"AMP Capture", NULL, "SDOUT"}, + + {"SDIN", NULL, "AMP Playback"}, + {"CLASS H", NULL, "SDIN"}, + {"Main AMP", NULL, "CLASS H"}, + {"SPK", NULL, "Main AMP"}, +}; + +static int cs35l35_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 1 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 0 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs35l35->i2s_mode = true; + cs35l35->pdm_mode = false; + break; + case SND_SOC_DAIFMT_PDM: + cs35l35->pdm_mode = true; + cs35l35->i2s_mode = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +struct cs35l35_sysclk_config { + int sysclk; + int srate; + u8 clk_cfg; +}; + +static struct cs35l35_sysclk_config cs35l35_clk_ctl[] = { + + /* SYSCLK, Sample Rate, Serial Port Cfg */ + {5644800, 44100, 0x00}, + {5644800, 88200, 0x40}, + {6144000, 48000, 0x10}, + {6144000, 96000, 0x50}, + {11289600, 44100, 0x01}, + {11289600, 88200, 0x41}, + {11289600, 176400, 0x81}, + {12000000, 44100, 0x03}, + {12000000, 48000, 0x13}, + {12000000, 88200, 0x43}, + {12000000, 96000, 0x53}, + {12000000, 176400, 0x83}, + {12000000, 192000, 0x93}, + {12288000, 48000, 0x11}, + {12288000, 96000, 0x51}, + {12288000, 192000, 0x91}, + {13000000, 44100, 0x07}, + {13000000, 48000, 0x17}, + {13000000, 88200, 0x47}, + {13000000, 96000, 0x57}, + {13000000, 176400, 0x87}, + {13000000, 192000, 0x97}, + {22579200, 44100, 0x02}, + {22579200, 88200, 0x42}, + {22579200, 176400, 0x82}, + {24000000, 44100, 0x0B}, + {24000000, 48000, 0x1B}, + {24000000, 88200, 0x4B}, + {24000000, 96000, 0x5B}, + {24000000, 176400, 0x8B}, + {24000000, 192000, 0x9B}, + {24576000, 48000, 0x12}, + {24576000, 96000, 0x52}, + {24576000, 192000, 0x92}, + {26000000, 44100, 0x0F}, + {26000000, 48000, 0x1F}, + {26000000, 88200, 0x4F}, + {26000000, 96000, 0x5F}, + {26000000, 176400, 0x8F}, + {26000000, 192000, 0x9F}, +}; + +static int cs35l35_get_clk_config(int sysclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l35_clk_ctl); i++) { + if (cs35l35_clk_ctl[i].sysclk == sysclk && + cs35l35_clk_ctl[i].srate == srate) + return cs35l35_clk_ctl[i].clk_cfg; + } + return -EINVAL; +} + +static int cs35l35_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + int srate = params_rate(params); + int ret = 0; + u8 sp_sclks; + int audin_format; + int errata_chk; + + int clk_ctl = cs35l35_get_clk_config(cs35l35->sysclk, srate); + + if (clk_ctl < 0) { + dev_err(component->dev, "Invalid CLK:Rate %d:%d\n", + cs35l35->sysclk, srate); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL2, + CS35L35_CLK_CTL2_MASK, clk_ctl); + if (ret != 0) { + dev_err(component->dev, "Failed to set port config %d\n", ret); + return ret; + } + + /* + * Rev A0 Errata + * When configured for the weak-drive detection path (CH_WKFET_DIS = 0) + * the Class H algorithm does not enable weak-drive operation for + * nonzero values of CH_WKFET_DELAY if SP_RATE = 01 or 10 + */ + errata_chk = clk_ctl & CS35L35_SP_RATE_MASK; + + if (classh->classh_wk_fet_disable == 0x00 && + (errata_chk == 0x01 || errata_chk == 0x03)) { + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + 0 << CS35L35_CH_WKFET_DEL_SHIFT); + if (ret != 0) { + dev_err(component->dev, "Failed to set fet config %d\n", + ret); + return ret; + } + } + + /* + * You can pull more Monitor data from the SDOUT pin than going to SDIN + * Just make sure your SCLK is fast enough to fill the frame + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (params_width(params)) { + case 8: + audin_format = CS35L35_SDIN_DEPTH_8; + break; + case 16: + audin_format = CS35L35_SDIN_DEPTH_16; + break; + case 24: + audin_format = CS35L35_SDIN_DEPTH_24; + break; + default: + dev_err(component->dev, "Unsupported Width %d\n", + params_width(params)); + return -EINVAL; + } + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_AUDIN_DEPTH_MASK, + audin_format << + CS35L35_AUDIN_DEPTH_SHIFT); + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_ADVIN_DEPTH_MASK, + audin_format << + CS35L35_ADVIN_DEPTH_SHIFT); + } + } + + if (cs35l35->i2s_mode) { + /* We have to take the SCLK to derive num sclks + * to configure the CLOCK_CTL3 register correctly + */ + if ((cs35l35->sclk / srate) % 4) { + dev_err(component->dev, "Unsupported sclk/fs ratio %d:%d\n", + cs35l35->sclk, srate); + return -EINVAL; + } + sp_sclks = ((cs35l35->sclk / srate) / 4) - 1; + + /* Only certain ratios are supported in I2S Slave Mode */ + if (cs35l35->slave_mode) { + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_48FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(component->dev, "ratio not supported\n"); + return -EINVAL; + } + } else { + /* Only certain ratios supported in I2S MASTER Mode */ + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(component->dev, "ratio not supported\n"); + return -EINVAL; + } + } + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLK_CTL3, + CS35L35_SP_SCLKS_MASK, sp_sclks << + CS35L35_SP_SCLKS_SHIFT); + if (ret != 0) { + dev_err(component->dev, "Failed to set fsclk %d\n", ret); + return ret; + } + } + + return ret; +} + +static const unsigned int cs35l35_src_rates[] = { + 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_constraints = { + .count = ARRAY_SIZE(cs35l35_src_rates), + .list = cs35l35_src_rates, +}; + +static int cs35l35_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cs35l35_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 0 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static const unsigned int cs35l35_pdm_rates[] = { + 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_pdm_constraints = { + .count = ARRAY_SIZE(cs35l35_pdm_rates), + .list = cs35l35_pdm_rates, +}; + +static int cs35l35_pdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l35_pdm_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 1 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + + /* Need the SCLK Frequency regardless of sysclk source for I2S */ + cs35l35->sclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops cs35l35_ops = { + .startup = cs35l35_pcm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, + .set_sysclk = cs35l35_dai_set_sysclk, +}; + +static const struct snd_soc_dai_ops cs35l35_pdm_ops = { + .startup = cs35l35_pdm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, +}; + +static struct snd_soc_dai_driver cs35l35_dai[] = { + { + .name = "cs35l35-pcm", + .id = 0, + .playback = { + .stream_name = "AMP Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .capture = { + .stream_name = "AMP Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_ops, + .symmetric_rates = 1, + }, + { + .name = "cs35l35-pdm", + .id = 1, + .playback = { + .stream_name = "PDM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_pdm_ops, + }, +}; + +static int cs35l35_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, + int dir) +{ + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + int clksrc; + int ret = 0; + + switch (clk_id) { + case 0: + clksrc = CS35L35_CLK_SOURCE_MCLK; + break; + case 1: + clksrc = CS35L35_CLK_SOURCE_SCLK; + break; + case 2: + clksrc = CS35L35_CLK_SOURCE_PDM; + break; + default: + dev_err(component->dev, "Invalid CLK Source\n"); + return -EINVAL; + } + + switch (freq) { + case 5644800: + case 6144000: + case 11289600: + case 12000000: + case 12288000: + case 13000000: + case 22579200: + case 24000000: + case 24576000: + case 26000000: + cs35l35->sysclk = freq; + break; + default: + dev_err(component->dev, "Invalid CLK Frequency Input : %d\n", freq); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_CLK_SOURCE_MASK, + clksrc << CS35L35_CLK_SOURCE_SHIFT); + if (ret != 0) { + dev_err(component->dev, "Failed to set sysclk %d\n", ret); + return ret; + } + + return ret; +} + +static int cs35l35_boost_inductor(struct cs35l35_private *cs35l35, + int inductor) +{ + struct regmap *regmap = cs35l35->regmap; + unsigned int bst_ipk = 0; + + /* + * Digital Boost Converter Configuration for feedback, + * ramping, switching frequency, and estimation block seeding. + */ + + regmap_update_bits(regmap, CS35L35_BST_CONV_SW_FREQ, + CS35L35_BST_CONV_SWFREQ_MASK, 0x00); + + regmap_read(regmap, CS35L35_BST_PEAK_I, &bst_ipk); + bst_ipk &= CS35L35_BST_IPK_MASK; + + switch (inductor) { + case 1000: /* 1 uH */ + regmap_write(regmap, CS35L35_BST_CONV_COEF_1, 0x24); + regmap_write(regmap, CS35L35_BST_CONV_COEF_2, 0x24); + regmap_update_bits(regmap, CS35L35_BST_CONV_SW_FREQ, + CS35L35_BST_CONV_LBST_MASK, 0x00); + + if (bst_ipk < 0x04) + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x1B); + else + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x4E); + break; + case 1200: /* 1.2 uH */ + regmap_write(regmap, CS35L35_BST_CONV_COEF_1, 0x20); + regmap_write(regmap, CS35L35_BST_CONV_COEF_2, 0x20); + regmap_update_bits(regmap, CS35L35_BST_CONV_SW_FREQ, + CS35L35_BST_CONV_LBST_MASK, 0x01); + + if (bst_ipk < 0x04) + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x1B); + else + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x47); + break; + case 1500: /* 1.5uH */ + regmap_write(regmap, CS35L35_BST_CONV_COEF_1, 0x20); + regmap_write(regmap, CS35L35_BST_CONV_COEF_2, 0x20); + regmap_update_bits(regmap, CS35L35_BST_CONV_SW_FREQ, + CS35L35_BST_CONV_LBST_MASK, 0x02); + + if (bst_ipk < 0x04) + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x1B); + else + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x3C); + break; + case 2200: /* 2.2uH */ + regmap_write(regmap, CS35L35_BST_CONV_COEF_1, 0x19); + regmap_write(regmap, CS35L35_BST_CONV_COEF_2, 0x25); + regmap_update_bits(regmap, CS35L35_BST_CONV_SW_FREQ, + CS35L35_BST_CONV_LBST_MASK, 0x03); + + if (bst_ipk < 0x04) + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x1B); + else + regmap_write(regmap, CS35L35_BST_CONV_SLOPE_COMP, 0x23); + break; + default: + dev_err(cs35l35->dev, "Invalid Inductor Value %d uH\n", + inductor); + return -EINVAL; + } + return 0; +} + +static int cs35l35_component_probe(struct snd_soc_component *component) +{ + struct cs35l35_private *cs35l35 = snd_soc_component_get_drvdata(component); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg; + int ret; + + /* Set Platform Data */ + if (cs35l35->pdata.bst_vctl) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl); + + if (cs35l35->pdata.bst_ipk) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_PEAK_I, + CS35L35_BST_IPK_MASK, + cs35l35->pdata.bst_ipk << + CS35L35_BST_IPK_SHIFT); + + ret = cs35l35_boost_inductor(cs35l35, cs35l35->pdata.boost_ind); + if (ret) + return ret; + + if (cs35l35->pdata.gain_zc) + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_GAIN_ZC_MASK, + cs35l35->pdata.gain_zc << + CS35L35_AMP_GAIN_ZC_SHIFT); + + if (cs35l35->pdata.aud_channel) + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_RXLOC_CTL, + CS35L35_AUD_IN_LR_MASK, + cs35l35->pdata.aud_channel << + CS35L35_AUD_IN_LR_SHIFT); + + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_ADVIN_RXLOC_CTL, + CS35L35_ADV_IN_LR_MASK, + cs35l35->pdata.adv_channel << + CS35L35_ADV_IN_LR_SHIFT); + if (cs35l35->pdata.shared_bst) + regmap_update_bits(cs35l35->regmap, CS35L35_CLASS_H_CTL, + CS35L35_CH_STEREO_MASK, + 1 << CS35L35_CH_STEREO_SHIFT); + ret = snd_soc_add_component_controls(component, cs35l35_adv_controls, + ARRAY_SIZE(cs35l35_adv_controls)); + if (ret) + return ret; + } + + if (cs35l35->pdata.sp_drv_str) + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_SP_DRV_MASK, + cs35l35->pdata.sp_drv_str << + CS35L35_SP_DRV_SHIFT); + if (cs35l35->pdata.sp_drv_unused) + regmap_update_bits(cs35l35->regmap, CS35L35_SP_FMT_CTL3, + CS35L35_SP_I2S_DRV_MASK, + cs35l35->pdata.sp_drv_unused << + CS35L35_SP_I2S_DRV_SHIFT); + + if (classh->classh_algo_enable) { + if (classh->classh_bst_override) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_OVR_MASK, + classh->classh_bst_override << + CS35L35_CH_BST_OVR_SHIFT); + if (classh->classh_bst_max_limit) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_LIM_MASK, + classh->classh_bst_max_limit << + CS35L35_CH_BST_LIM_SHIFT); + if (classh->classh_mem_depth) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_MEM_DEPTH_MASK, + classh->classh_mem_depth << + CS35L35_CH_MEM_DEPTH_SHIFT); + if (classh->classh_headroom) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_HEADRM_CTL, + CS35L35_CH_HDRM_CTL_MASK, + classh->classh_headroom << + CS35L35_CH_HDRM_CTL_SHIFT); + if (classh->classh_release_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_RELEASE_RATE, + CS35L35_CH_REL_RATE_MASK, + classh->classh_release_rate << + CS35L35_CH_REL_RATE_SHIFT); + if (classh->classh_wk_fet_disable) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DIS_MASK, + classh->classh_wk_fet_disable << + CS35L35_CH_WKFET_DIS_SHIFT); + if (classh->classh_wk_fet_delay) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + classh->classh_wk_fet_delay << + CS35L35_CH_WKFET_DEL_SHIFT); + if (classh->classh_wk_fet_thld) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_THLD_MASK, + classh->classh_wk_fet_thld << + CS35L35_CH_WKFET_THLD_SHIFT); + if (classh->classh_vpch_auto) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_AUTO_MASK, + classh->classh_vpch_auto << + CS35L35_CH_VP_AUTO_SHIFT); + if (classh->classh_vpch_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_RATE_MASK, + classh->classh_vpch_rate << + CS35L35_CH_VP_RATE_SHIFT); + if (classh->classh_vpch_man) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_MAN_MASK, + classh->classh_vpch_man << + CS35L35_CH_VP_MAN_SHIFT); + } + + if (monitor_config->is_present) { + if (monitor_config->vmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_VMON_DEPTH_MASK, + monitor_config->vmon_dpth << + CS35L35_VMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->imon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_IMON_DEPTH_MASK, + monitor_config->imon_dpth << + CS35L35_IMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->imon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->imon_frm << + CS35L35_MON_FRM_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_SCALE_CTL, + CS35L35_IMON_SCALE_MASK, + monitor_config->imon_scale << + CS35L35_IMON_SCALE_SHIFT); + } + if (monitor_config->vpmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VPMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vbstmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VBSTMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VBSTMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vbstmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vbstmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vpbrstat_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPBRSTAT_DEPTH_MASK, + monitor_config->vpbrstat_dpth << + CS35L35_VPBRSTAT_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpbrstat_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpbrstat_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->zerofill_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_ZEROFILL_DEPTH_MASK, + monitor_config->zerofill_dpth << + CS35L35_ZEROFILL_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->zerofill_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->zerofill_frm << + CS35L35_MON_FRM_SHIFT); + } + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_cs35l35 = { + .probe = cs35l35_component_probe, + .set_sysclk = cs35l35_component_set_sysclk, + .dapm_widgets = cs35l35_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l35_dapm_widgets), + .dapm_routes = cs35l35_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l35_audio_map), + .controls = cs35l35_aud_controls, + .num_controls = ARRAY_SIZE(cs35l35_aud_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct regmap_config cs35l35_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L35_MAX_REGISTER, + .reg_defaults = cs35l35_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l35_reg), + .volatile_reg = cs35l35_volatile_register, + .readable_reg = cs35l35_readable_register, + .precious_reg = cs35l35_precious_register, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static irqreturn_t cs35l35_irq(int irq, void *data) +{ + struct cs35l35_private *cs35l35 = data; + unsigned int sticky1, sticky2, sticky3, sticky4; + unsigned int mask1, mask2, mask3, mask4, current1; + + /* ack the irq by reading all status registers */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_4, &sticky4); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_3, &sticky3); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_2, &sticky2); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &sticky1); + + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_4, &mask4); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_3, &mask3); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_2, &mask2); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_1, &mask1); + + /* Check to see if unmasked bits are active */ + if (!(sticky1 & ~mask1) && !(sticky2 & ~mask2) && !(sticky3 & ~mask3) + && !(sticky4 & ~mask4)) + return IRQ_NONE; + + if (sticky2 & CS35L35_PDN_DONE) + complete(&cs35l35->pdn_done); + + /* read the current values */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, ¤t1); + + /* handle the interrupts */ + if (sticky1 & CS35L35_CAL_ERR) { + dev_crit(cs35l35->dev, "Calibration Error\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_CAL_ERR)) { + pr_debug("%s : Cal error release\n", __func__); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, + CS35L35_CAL_ERR_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + } + } + + if (sticky1 & CS35L35_AMP_SHORT) { + dev_crit(cs35l35->dev, "AMP Short Error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_AMP_SHORT)) { + dev_dbg(cs35l35->dev, "Amp short error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, + CS35L35_SHORT_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTW) { + dev_warn(cs35l35->dev, "Over temperature warning\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTW)) { + dev_dbg(cs35l35->dev, "Over temperature warn release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, + CS35L35_OTW_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTE) { + dev_crit(cs35l35->dev, "Over temperature error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTE)) { + dev_dbg(cs35l35->dev, "Over temperature error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, + CS35L35_OTE_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + } + } + + if (sticky3 & CS35L35_BST_HIGH) { + dev_crit(cs35l35->dev, "VBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky3 & CS35L35_LBST_SHORT) { + dev_crit(cs35l35->dev, "LBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky2 & CS35L35_VPBR_ERR) + dev_dbg(cs35l35->dev, "Error: Reactive Brownout\n"); + + if (sticky4 & CS35L35_VMON_OVFL) + dev_dbg(cs35l35->dev, "Error: VMON overflow\n"); + + if (sticky4 & CS35L35_IMON_OVFL) + dev_dbg(cs35l35->dev, "Error: IMON overflow\n"); + + return IRQ_HANDLED; +} + + +static int cs35l35_handle_of_data(struct i2c_client *i2c_client, + struct cs35l35_platform_data *pdata) +{ + struct device_node *np = i2c_client->dev.of_node; + struct device_node *classh, *signal_format; + struct classh_cfg *classh_config = &pdata->classh_algo; + struct monitor_cfg *monitor_config = &pdata->mon_cfg; + unsigned int val32 = 0; + u8 monitor_array[4]; + const int imon_array_size = ARRAY_SIZE(monitor_array); + const int mon_array_size = imon_array_size - 1; + int ret = 0; + + if (!np) + return 0; + + pdata->bst_pdn_fet_on = of_property_read_bool(np, + "cirrus,boost-pdn-fet-on"); + + ret = of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val32); + if (ret >= 0) { + if (val32 < 2600 || val32 > 9000) { + dev_err(&i2c_client->dev, + "Invalid Boost Voltage %d mV\n", val32); + return -EINVAL; + } + pdata->bst_vctl = ((val32 - 2600) / 100) + 1; + } + + ret = of_property_read_u32(np, "cirrus,boost-peak-milliamp", &val32); + if (ret >= 0) { + if (val32 < 1680 || val32 > 4480) { + dev_err(&i2c_client->dev, + "Invalid Boost Peak Current %u mA\n", val32); + return -EINVAL; + } + + pdata->bst_ipk = ((val32 - 1680) / 110) | CS35L35_VALID_PDATA; + } + + ret = of_property_read_u32(np, "cirrus,boost-ind-nanohenry", &val32); + if (ret >= 0) { + pdata->boost_ind = val32; + } else { + dev_err(&i2c_client->dev, "Inductor not specified.\n"); + return -EINVAL; + } + + if (of_property_read_u32(np, "cirrus,sp-drv-strength", &val32) >= 0) + pdata->sp_drv_str = val32; + if (of_property_read_u32(np, "cirrus,sp-drv-unused", &val32) >= 0) + pdata->sp_drv_unused = val32 | CS35L35_VALID_PDATA; + + pdata->stereo = of_property_read_bool(np, "cirrus,stereo-config"); + + if (pdata->stereo) { + ret = of_property_read_u32(np, "cirrus,audio-channel", &val32); + if (ret >= 0) + pdata->aud_channel = val32; + + ret = of_property_read_u32(np, "cirrus,advisory-channel", + &val32); + if (ret >= 0) + pdata->adv_channel = val32; + + pdata->shared_bst = of_property_read_bool(np, + "cirrus,shared-boost"); + } + + pdata->ext_bst = of_property_read_bool(np, "cirrus,external-boost"); + + pdata->gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc"); + + classh = of_get_child_by_name(np, "cirrus,classh-internal-algo"); + classh_config->classh_algo_enable = classh ? true : false; + + if (classh_config->classh_algo_enable) { + classh_config->classh_bst_override = + of_property_read_bool(np, "cirrus,classh-bst-overide"); + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-mem-depth", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_mem_depth = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-release-rate", + &val32); + if (ret >= 0) + classh_config->classh_release_rate = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-headroom", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_headroom = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-wk-fet-disable", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_disable = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-delay", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_wk_fet_delay = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-thld", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_thld = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-auto", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_auto = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-rate", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_rate = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-man", + &val32); + if (ret >= 0) + classh_config->classh_vpch_man = val32; + } + of_node_put(classh); + + /* frame depth location */ + signal_format = of_get_child_by_name(np, "cirrus,monitor-signal-format"); + monitor_config->is_present = signal_format ? true : false; + if (monitor_config->is_present) { + ret = of_property_read_u8_array(signal_format, "cirrus,imon", + monitor_array, imon_array_size); + if (!ret) { + monitor_config->imon_specs = true; + monitor_config->imon_dpth = monitor_array[0]; + monitor_config->imon_loc = monitor_array[1]; + monitor_config->imon_frm = monitor_array[2]; + monitor_config->imon_scale = monitor_array[3]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vmon_specs = true; + monitor_config->vmon_dpth = monitor_array[0]; + monitor_config->vmon_loc = monitor_array[1]; + monitor_config->vmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vpmon_specs = true; + monitor_config->vpmon_dpth = monitor_array[0]; + monitor_config->vpmon_loc = monitor_array[1]; + monitor_config->vpmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vbstmon", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vbstmon_specs = true; + monitor_config->vbstmon_dpth = monitor_array[0]; + monitor_config->vbstmon_loc = monitor_array[1]; + monitor_config->vbstmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpbrstat", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->vpbrstat_specs = true; + monitor_config->vpbrstat_dpth = monitor_array[0]; + monitor_config->vpbrstat_loc = monitor_array[1]; + monitor_config->vpbrstat_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,zerofill", + monitor_array, mon_array_size); + if (!ret) { + monitor_config->zerofill_specs = true; + monitor_config->zerofill_dpth = monitor_array[0]; + monitor_config->zerofill_loc = monitor_array[1]; + monitor_config->zerofill_frm = monitor_array[2]; + } + } + of_node_put(signal_format); + + return 0; +} + +/* Errata Rev A0 */ +static const struct reg_sequence cs35l35_errata_patch[] = { + + { 0x7F, 0x99 }, + { 0x00, 0x99 }, + { 0x52, 0x22 }, + { 0x04, 0x14 }, + { 0x6D, 0x44 }, + { 0x24, 0x10 }, + { 0x58, 0xC4 }, + { 0x00, 0x98 }, + { 0x18, 0x08 }, + { 0x00, 0x00 }, + { 0x7F, 0x00 }, +}; + +static int cs35l35_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l35_private *cs35l35; + struct device *dev = &i2c_client->dev; + struct cs35l35_platform_data *pdata = dev_get_platdata(dev); + int i; + int ret; + unsigned int devid = 0; + unsigned int reg; + + cs35l35 = devm_kzalloc(dev, sizeof(struct cs35l35_private), GFP_KERNEL); + if (!cs35l35) + return -ENOMEM; + + cs35l35->dev = dev; + + i2c_set_clientdata(i2c_client, cs35l35); + cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap); + if (IS_ERR(cs35l35->regmap)) { + ret = PTR_ERR(cs35l35->regmap); + dev_err(dev, "regmap_init() failed: %d\n", ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++) + cs35l35->supplies[i].supply = cs35l35_supplies[i]; + + cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies); + + ret = devm_regulator_bulk_get(dev, cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(dev, "Failed to request core supplies: %d\n", ret); + return ret; + } + + if (pdata) { + cs35l35->pdata = *pdata; + } else { + pdata = devm_kzalloc(dev, sizeof(struct cs35l35_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + if (i2c_client->dev.of_node) { + ret = cs35l35_handle_of_data(i2c_client, pdata); + if (ret != 0) + return ret; + + } + cs35l35->pdata = *pdata; + } + + ret = regulator_bulk_enable(cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", ret); + return ret; + } + + /* returning NULL can be valid if in stereo mode */ + cs35l35->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs35l35->reset_gpio)) { + ret = PTR_ERR(cs35l35->reset_gpio); + cs35l35->reset_gpio = NULL; + if (ret == -EBUSY) { + dev_info(dev, + "Reset line busy, assuming shared reset\n"); + } else { + dev_err(dev, "Failed to get reset GPIO: %d\n", ret); + goto err; + } + } + + cs35l35_reset(cs35l35); + + init_completion(&cs35l35->pdn_done); + + ret = devm_request_threaded_irq(dev, i2c_client->irq, NULL, cs35l35_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW | + IRQF_SHARED, "cs35l35", cs35l35); + if (ret != 0) { + dev_err(dev, "Failed to request IRQ: %d\n", ret); + goto err; + } + /* initialize codec */ + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_AB, ®); + + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L35_CHIP_ID) { + dev_err(dev, "CS35L35 Device ID (%X). Expected ID %X\n", + devid, CS35L35_CHIP_ID); + ret = -ENODEV; + goto err; + } + + ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, ®); + if (ret < 0) { + dev_err(dev, "Get Revision ID failed: %d\n", ret); + goto err; + } + + ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch, + ARRAY_SIZE(cs35l35_errata_patch)); + if (ret < 0) { + dev_err(dev, "Failed to apply errata patch: %d\n", ret); + goto err; + } + + dev_info(dev, "Cirrus Logic CS35L35 (%x), Revision: %02X\n", + devid, reg & 0xFF); + + /* Set the INT Masks for critical errors */ + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, + CS35L35_INT1_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_2, + CS35L35_INT2_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_3, + CS35L35_INT3_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_4, + CS35L35_INT4_CRIT_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PWR2_PDN_MASK, + CS35L35_PWR2_PDN_MASK); + + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL3, + CS35L35_PWR3_PDN_MASK, + CS35L35_PWR3_PDN_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT); + + ret = devm_snd_soc_register_component(dev, &soc_component_dev_cs35l35, + cs35l35_dai, ARRAY_SIZE(cs35l35_dai)); + if (ret < 0) { + dev_err(dev, "Failed to register component: %d\n", ret); + goto err; + } + + return 0; + +err: + regulator_bulk_disable(cs35l35->num_supplies, + cs35l35->supplies); + gpiod_set_value_cansleep(cs35l35->reset_gpio, 0); + + return ret; +} + +static int cs35l35_i2c_remove(struct i2c_client *i2c_client) +{ + struct cs35l35_private *cs35l35 = i2c_get_clientdata(i2c_client); + + regulator_bulk_disable(cs35l35->num_supplies, cs35l35->supplies); + gpiod_set_value_cansleep(cs35l35->reset_gpio, 0); + + return 0; +} + +static const struct of_device_id cs35l35_of_match[] = { + {.compatible = "cirrus,cs35l35"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l35_of_match); + +static const struct i2c_device_id cs35l35_id[] = { + {"cs35l35", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l35_id); + +static struct i2c_driver cs35l35_i2c_driver = { + .driver = { + .name = "cs35l35", + .of_match_table = cs35l35_of_match, + }, + .id_table = cs35l35_id, + .probe = cs35l35_i2c_probe, + .remove = cs35l35_i2c_remove, +}; + +module_i2c_driver(cs35l35_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L35 driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h new file mode 100644 index 000000000..621bfef70 --- /dev/null +++ b/sound/soc/codecs/cs35l35.h @@ -0,0 +1,300 @@ +/* + * cs35l35.h -- CS35L35 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS35L35_H__ +#define __CS35L35_H__ + +#define CS35L35_FIRSTREG 0x01 +#define CS35L35_LASTREG 0x7E +#define CS35L35_CHIP_ID 0x00035A35 +#define CS35L35_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L35_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L35_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L35_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L35_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L35_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L35_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L35_PWRCTL3 0x08 /* Power Ctl 3 */ +#define CS35L35_CLK_CTL1 0x0A /* Clocking Ctl 1 */ +#define CS35L35_CLK_CTL2 0x0B /* Clocking Ctl 2 */ +#define CS35L35_CLK_CTL3 0x0C /* Clocking Ctl 3 */ +#define CS35L35_SP_FMT_CTL1 0x0D /* Serial Port Format CTL1 */ +#define CS35L35_SP_FMT_CTL2 0x0E /* Serial Port Format CTL2 */ +#define CS35L35_SP_FMT_CTL3 0x0F /* Serial Port Format CTL3 */ +#define CS35L35_MAG_COMP_CTL 0x13 /* Magnitude Comp CTL */ +#define CS35L35_AMP_INP_DRV_CTL 0x14 /* Amp Input Drive Ctl */ +#define CS35L35_AMP_DIG_VOL_CTL 0x15 /* Amplifier Dig Volume Ctl */ +#define CS35L35_AMP_DIG_VOL 0x16 /* Amplifier Dig Volume */ +#define CS35L35_ADV_DIG_VOL 0x17 /* Advisory Digital Volume */ +#define CS35L35_PROTECT_CTL 0x18 /* Amp Gain - Prot Ctl Param */ +#define CS35L35_AMP_GAIN_AUD_CTL 0x19 /* Amp Serial Port Gain Ctl */ +#define CS35L35_AMP_GAIN_PDM_CTL 0x1A /* Amplifier Gain PDM Ctl */ +#define CS35L35_AMP_GAIN_ADV_CTL 0x1B /* Amplifier Gain Ctl */ +#define CS35L35_GPI_CTL 0x1C /* GPI Ctl */ +#define CS35L35_BST_CVTR_V_CTL 0x1D /* Boost Conv Voltage Ctl */ +#define CS35L35_BST_PEAK_I 0x1E /* Boost Conv Peak Current */ +#define CS35L35_BST_RAMP_CTL 0x20 /* Boost Conv Soft Ramp Ctl */ +#define CS35L35_BST_CONV_COEF_1 0x21 /* Boost Conv Coefficients 1 */ +#define CS35L35_BST_CONV_COEF_2 0x22 /* Boost Conv Coefficients 2 */ +#define CS35L35_BST_CONV_SLOPE_COMP 0x23 /* Boost Conv Slope Comp */ +#define CS35L35_BST_CONV_SW_FREQ 0x24 /* Boost Conv L BST SW Freq */ +#define CS35L35_CLASS_H_CTL 0x30 /* CLS H Control */ +#define CS35L35_CLASS_H_HEADRM_CTL 0x31 /* CLS H Headroom Ctl */ +#define CS35L35_CLASS_H_RELEASE_RATE 0x32 /* CLS H Release Rate */ +#define CS35L35_CLASS_H_FET_DRIVE_CTL 0x33 /* CLS H Weak FET Drive Ctl */ +#define CS35L35_CLASS_H_VP_CTL 0x34 /* CLS H VP Ctl */ +#define CS35L35_CLASS_H_STATUS 0x38 /* CLS H Status */ +#define CS35L35_VPBR_CTL 0x3A /* VPBR Ctl */ +#define CS35L35_VPBR_VOL_CTL 0x3B /* VPBR Volume Ctl */ +#define CS35L35_VPBR_TIMING_CTL 0x3C /* VPBR Timing Ctl */ +#define CS35L35_VPBR_MODE_VOL_CTL 0x3D /* VPBR Mode/Attack Vol Ctl */ +#define CS35L35_VPBR_ATTEN_STATUS 0x4B /* VPBR Attenuation Status */ +#define CS35L35_SPKR_MON_CTL 0x4E /* Speaker Monitoring Ctl */ +#define CS35L35_IMON_SCALE_CTL 0x51 /* IMON Scale Ctl */ +#define CS35L35_AUDIN_RXLOC_CTL 0x52 /* Audio Input RX Loc Ctl */ +#define CS35L35_ADVIN_RXLOC_CTL 0x53 /* Advisory Input RX Loc Ctl */ +#define CS35L35_VMON_TXLOC_CTL 0x54 /* VMON TX Loc Ctl */ +#define CS35L35_IMON_TXLOC_CTL 0x55 /* IMON TX Loc Ctl */ +#define CS35L35_VPMON_TXLOC_CTL 0x56 /* VPMON TX Loc Ctl */ +#define CS35L35_VBSTMON_TXLOC_CTL 0x57 /* VBSTMON TX Loc Ctl */ +#define CS35L35_VPBR_STATUS_TXLOC_CTL 0x58 /* VPBR Status TX Loc Ctl */ +#define CS35L35_ZERO_FILL_LOC_CTL 0x59 /* Zero Fill Loc Ctl */ +#define CS35L35_AUDIN_DEPTH_CTL 0x5A /* Audio Input Depth Ctl */ +#define CS35L35_SPKMON_DEPTH_CTL 0x5B /* SPK Mon Output Depth Ctl */ +#define CS35L35_SUPMON_DEPTH_CTL 0x5C /* Supply Mon Out Depth Ctl */ +#define CS35L35_ZEROFILL_DEPTH_CTL 0x5D /* Zero Fill Mon Output Ctl */ +#define CS35L35_MULT_DEV_SYNCH1 0x62 /* Multidevice Synch */ +#define CS35L35_MULT_DEV_SYNCH2 0x63 /* Multidevice Synch 2 */ +#define CS35L35_PROT_RELEASE_CTL 0x64 /* Protection Release Ctl */ +#define CS35L35_DIAG_MODE_REG_LOCK 0x68 /* Diagnostic Mode Reg Lock */ +#define CS35L35_DIAG_MODE_CTL_1 0x69 /* Diagnostic Mode Ctl 1 */ +#define CS35L35_DIAG_MODE_CTL_2 0x6A /* Diagnostic Mode Ctl 2 */ +#define CS35L35_INT_MASK_1 0x70 /* Interrupt Mask 1 */ +#define CS35L35_INT_MASK_2 0x71 /* Interrupt Mask 2 */ +#define CS35L35_INT_MASK_3 0x72 /* Interrupt Mask 3 */ +#define CS35L35_INT_MASK_4 0x73 /* Interrupt Mask 4 */ +#define CS35L35_INT_STATUS_1 0x74 /* Interrupt Status 1 */ +#define CS35L35_INT_STATUS_2 0x75 /* Interrupt Status 2 */ +#define CS35L35_INT_STATUS_3 0x76 /* Interrupt Status 3 */ +#define CS35L35_INT_STATUS_4 0x77 /* Interrupt Status 4 */ +#define CS35L35_PLL_STATUS 0x78 /* PLL Status */ +#define CS35L35_OTP_TRIM_STATUS 0x7E /* OTP Trim Status */ + +#define CS35L35_MAX_REGISTER 0x7F + +/* CS35L35_PWRCTL1 */ +#define CS35L35_SFT_RST 0x80 +#define CS35L35_DISCHG_FLT 0x02 +#define CS35L35_PDN_ALL 0x01 + +/* CS35L35_PWRCTL2 */ +#define CS35L35_PDN_VMON 0x80 +#define CS35L35_PDN_IMON 0x40 +#define CS35L35_PDN_CLASSH 0x20 +#define CS35L35_PDN_VPBR 0x10 +#define CS35L35_PDN_BST 0x04 +#define CS35L35_PDN_AMP 0x01 + +/* CS35L35_PWRCTL3 */ +#define CS35L35_PDN_VBSTMON_OUT 0x10 +#define CS35L35_PDN_VMON_OUT 0x08 + +#define CS35L35_AUDIN_DEPTH_MASK 0x03 +#define CS35L35_AUDIN_DEPTH_SHIFT 0 +#define CS35L35_ADVIN_DEPTH_MASK 0x0C +#define CS35L35_ADVIN_DEPTH_SHIFT 2 +#define CS35L35_SDIN_DEPTH_8 0x01 +#define CS35L35_SDIN_DEPTH_16 0x02 +#define CS35L35_SDIN_DEPTH_24 0x03 + +#define CS35L35_SDOUT_DEPTH_8 0x01 +#define CS35L35_SDOUT_DEPTH_12 0x02 +#define CS35L35_SDOUT_DEPTH_16 0x03 + +#define CS35L35_AUD_IN_LR_MASK 0x80 +#define CS35L35_AUD_IN_LR_SHIFT 7 +#define CS35L35_ADV_IN_LR_MASK 0x80 +#define CS35L35_ADV_IN_LR_SHIFT 7 +#define CS35L35_AUD_IN_LOC_MASK 0x0F +#define CS35L35_AUD_IN_LOC_SHIFT 0 +#define CS35L35_ADV_IN_LOC_MASK 0x0F +#define CS35L35_ADV_IN_LOC_SHIFT 0 + +#define CS35L35_IMON_DEPTH_MASK 0x03 +#define CS35L35_IMON_DEPTH_SHIFT 0 +#define CS35L35_VMON_DEPTH_MASK 0x0C +#define CS35L35_VMON_DEPTH_SHIFT 2 +#define CS35L35_VBSTMON_DEPTH_MASK 0x03 +#define CS35L35_VBSTMON_DEPTH_SHIFT 0 +#define CS35L35_VPMON_DEPTH_MASK 0x0C +#define CS35L35_VPMON_DEPTH_SHIFT 2 +#define CS35L35_VPBRSTAT_DEPTH_MASK 0x30 +#define CS35L35_VPBRSTAT_DEPTH_SHIFT 4 +#define CS35L35_ZEROFILL_DEPTH_MASK 0x03 +#define CS35L35_ZEROFILL_DEPTH_SHIFT 0x00 + +#define CS35L35_MON_TXLOC_MASK 0x3F +#define CS35L35_MON_TXLOC_SHIFT 0 +#define CS35L35_MON_FRM_MASK 0x80 +#define CS35L35_MON_FRM_SHIFT 7 + +#define CS35L35_IMON_SCALE_MASK 0xF8 +#define CS35L35_IMON_SCALE_SHIFT 3 + +#define CS35L35_MS_MASK 0x80 +#define CS35L35_MS_SHIFT 7 +#define CS35L35_SPMODE_MASK 0x40 +#define CS35L35_SP_DRV_MASK 0x10 +#define CS35L35_SP_DRV_SHIFT 4 +#define CS35L35_CLK_CTL2_MASK 0xFF +#define CS35L35_PDM_MODE_MASK 0x40 +#define CS35L35_PDM_MODE_SHIFT 6 +#define CS35L35_CLK_SOURCE_MASK 0x03 +#define CS35L35_CLK_SOURCE_SHIFT 0 +#define CS35L35_CLK_SOURCE_MCLK 0 +#define CS35L35_CLK_SOURCE_SCLK 1 +#define CS35L35_CLK_SOURCE_PDM 2 + +#define CS35L35_SP_SCLKS_MASK 0x0F +#define CS35L35_SP_SCLKS_SHIFT 0x00 +#define CS35L35_SP_SCLKS_16FS 0x03 +#define CS35L35_SP_SCLKS_32FS 0x07 +#define CS35L35_SP_SCLKS_48FS 0x0B +#define CS35L35_SP_SCLKS_64FS 0x0F +#define CS35L35_SP_RATE_MASK 0xC0 + +#define CS35L35_PDN_BST_MASK 0x06 +#define CS35L35_PDN_BST_FETON_SHIFT 1 +#define CS35L35_PDN_BST_FETOFF_SHIFT 2 +#define CS35L35_PWR2_PDN_MASK 0xE0 +#define CS35L35_PWR3_PDN_MASK 0x1E +#define CS35L35_PDN_ALL_MASK 0x01 +#define CS35L35_DISCHG_FILT_MASK 0x02 +#define CS35L35_DISCHG_FILT_SHIFT 1 +#define CS35L35_MCLK_DIS_MASK 0x04 +#define CS35L35_MCLK_DIS_SHIFT 2 + +#define CS35L35_BST_CTL_MASK 0x7F +#define CS35L35_BST_CTL_SHIFT 0 +#define CS35L35_BST_IPK_MASK 0x1F +#define CS35L35_BST_IPK_SHIFT 0 +#define CS35L35_AMP_MUTE_MASK 0x20 +#define CS35L35_AMP_MUTE_SHIFT 5 +#define CS35L35_AMP_GAIN_ZC_MASK 0x10 +#define CS35L35_AMP_GAIN_ZC_SHIFT 4 + +#define CS35L35_AMP_DIGSFT_MASK 0x02 +#define CS35L35_AMP_DIGSFT_SHIFT 1 + +/* CS35L35_SP_FMT_CTL3 */ +#define CS35L35_SP_I2S_DRV_MASK 0x03 +#define CS35L35_SP_I2S_DRV_SHIFT 0 + +/* Boost Converter Config */ +#define CS35L35_BST_CONV_COEFF_MASK 0xFF +#define CS35L35_BST_CONV_SLOPE_MASK 0xFF +#define CS35L35_BST_CONV_LBST_MASK 0x03 +#define CS35L35_BST_CONV_SWFREQ_MASK 0xF0 + +/* Class H Algorithm Control */ +#define CS35L35_CH_STEREO_MASK 0x40 +#define CS35L35_CH_STEREO_SHIFT 6 +#define CS35L35_CH_BST_OVR_MASK 0x04 +#define CS35L35_CH_BST_OVR_SHIFT 2 +#define CS35L35_CH_BST_LIM_MASK 0x08 +#define CS35L35_CH_BST_LIM_SHIFT 3 +#define CS35L35_CH_MEM_DEPTH_MASK 0x01 +#define CS35L35_CH_MEM_DEPTH_SHIFT 0 +#define CS35L35_CH_HDRM_CTL_MASK 0x3F +#define CS35L35_CH_HDRM_CTL_SHIFT 0 +#define CS35L35_CH_REL_RATE_MASK 0xFF +#define CS35L35_CH_REL_RATE_SHIFT 0 +#define CS35L35_CH_WKFET_DIS_MASK 0x80 +#define CS35L35_CH_WKFET_DIS_SHIFT 7 +#define CS35L35_CH_WKFET_DEL_MASK 0x70 +#define CS35L35_CH_WKFET_DEL_SHIFT 4 +#define CS35L35_CH_WKFET_THLD_MASK 0x0F +#define CS35L35_CH_WKFET_THLD_SHIFT 0 +#define CS35L35_CH_VP_AUTO_MASK 0x80 +#define CS35L35_CH_VP_AUTO_SHIFT 7 +#define CS35L35_CH_VP_RATE_MASK 0x60 +#define CS35L35_CH_VP_RATE_SHIFT 5 +#define CS35L35_CH_VP_MAN_MASK 0x1F +#define CS35L35_CH_VP_MAN_SHIFT 0 + +/* CS35L35_PROT_RELEASE_CTL */ +#define CS35L35_CAL_ERR_RLS 0x80 +#define CS35L35_SHORT_RLS 0x04 +#define CS35L35_OTW_RLS 0x02 +#define CS35L35_OTE_RLS 0x01 + +/* INT Mask Registers */ +#define CS35L35_INT1_CRIT_MASK 0x38 +#define CS35L35_INT2_CRIT_MASK 0xEF +#define CS35L35_INT3_CRIT_MASK 0xEE +#define CS35L35_INT4_CRIT_MASK 0xFF + +/* PDN DONE Masks */ +#define CS35L35_M_PDN_DONE_SHIFT 4 +#define CS35L35_M_PDN_DONE_MASK 0x10 + +/* CS35L35_INT_1 */ +#define CS35L35_CAL_ERR 0x80 +#define CS35L35_OTP_ERR 0x40 +#define CS35L35_LRCLK_ERR 0x20 +#define CS35L35_SPCLK_ERR 0x10 +#define CS35L35_MCLK_ERR 0x08 +#define CS35L35_AMP_SHORT 0x04 +#define CS35L35_OTW 0x02 +#define CS35L35_OTE 0x01 + +/* CS35L35_INT_2 */ +#define CS35L35_PDN_DONE 0x10 +#define CS35L35_VPBR_ERR 0x02 +#define CS35L35_VPBR_CLR 0x01 + +/* CS35L35_INT_3 */ +#define CS35L35_BST_HIGH 0x10 +#define CS35L35_BST_HIGH_FLAG 0x08 +#define CS35L35_BST_IPK_FLAG 0x04 +#define CS35L35_LBST_SHORT 0x01 + +/* CS35L35_INT_4 */ +#define CS35L35_VMON_OVFL 0x08 +#define CS35L35_IMON_OVFL 0x04 + +#define CS35L35_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct cs35l35_private { + struct device *dev; + struct cs35l35_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data supplies[2]; + int num_supplies; + int sysclk; + int sclk; + bool pdm_mode; + bool i2s_mode; + bool slave_mode; + /* GPIO for /RST */ + struct gpio_desc *reset_gpio; + struct completion pdn_done; +}; + +static const char * const cs35l35_supplies[] = { + "VA", + "VP", +}; + +#endif diff --git a/sound/soc/codecs/cs4265.c b/sound/soc/codecs/cs4265.c new file mode 100644 index 000000000..68d18aca3 --- /dev/null +++ b/sound/soc/codecs/cs4265.c @@ -0,0 +1,651 @@ +/* + * cs4265.c -- CS4265 ALSA SoC audio driver + * + * Copyright 2014 Cirrus Logic, Inc. + * + * Author: Paul Handrigan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs4265.h" + +struct cs4265_private { + struct regmap *regmap; + struct gpio_desc *reset_gpio; + u8 format; + u32 sysclk; +}; + +static const struct reg_default cs4265_reg_defaults[] = { + { CS4265_PWRCTL, 0x0F }, + { CS4265_DAC_CTL, 0x08 }, + { CS4265_ADC_CTL, 0x00 }, + { CS4265_MCLK_FREQ, 0x00 }, + { CS4265_SIG_SEL, 0x40 }, + { CS4265_CHB_PGA_CTL, 0x00 }, + { CS4265_CHA_PGA_CTL, 0x00 }, + { CS4265_ADC_CTL2, 0x19 }, + { CS4265_DAC_CHA_VOL, 0x00 }, + { CS4265_DAC_CHB_VOL, 0x00 }, + { CS4265_DAC_CTL2, 0xC0 }, + { CS4265_SPDIF_CTL1, 0x00 }, + { CS4265_SPDIF_CTL2, 0x00 }, + { CS4265_INT_MASK, 0x00 }, + { CS4265_STATUS_MODE_MSB, 0x00 }, + { CS4265_STATUS_MODE_LSB, 0x00 }, +}; + +static bool cs4265_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS4265_CHIP_ID ... CS4265_MAX_REGISTER: + return true; + default: + return false; + } +} + +static bool cs4265_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS4265_INT_STATUS: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(pga_tlv, -1200, 50, 0); + +static DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 0); + +static const char * const digital_input_mux_text[] = { + "SDIN1", "SDIN2" +}; + +static SOC_ENUM_SINGLE_DECL(digital_input_mux_enum, CS4265_SIG_SEL, 7, + digital_input_mux_text); + +static const struct snd_kcontrol_new digital_input_mux = + SOC_DAPM_ENUM("Digital Input Mux", digital_input_mux_enum); + +static const char * const mic_linein_text[] = { + "MIC", "LINEIN" +}; + +static SOC_ENUM_SINGLE_DECL(mic_linein_enum, CS4265_ADC_CTL2, 0, + mic_linein_text); + +static const char * const cam_mode_text[] = { + "One Byte", "Two Byte" +}; + +static SOC_ENUM_SINGLE_DECL(cam_mode_enum, CS4265_SPDIF_CTL1, 5, + cam_mode_text); + +static const char * const cam_mono_stereo_text[] = { + "Stereo", "Mono" +}; + +static SOC_ENUM_SINGLE_DECL(spdif_mono_stereo_enum, CS4265_SPDIF_CTL2, 2, + cam_mono_stereo_text); + +static const char * const mono_select_text[] = { + "Channel A", "Channel B" +}; + +static SOC_ENUM_SINGLE_DECL(spdif_mono_select_enum, CS4265_SPDIF_CTL2, 0, + mono_select_text); + +static const struct snd_kcontrol_new mic_linein_mux = + SOC_DAPM_ENUM("ADC Input Capture Mux", mic_linein_enum); + +static const struct snd_kcontrol_new loopback_ctl = + SOC_DAPM_SINGLE("Switch", CS4265_SIG_SEL, 1, 1, 0); + +static const struct snd_kcontrol_new spdif_switch = + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 0, 0); + +static const struct snd_kcontrol_new dac_switch = + SOC_DAPM_SINGLE("Switch", CS4265_PWRCTL, 1, 1, 0); + +static const struct snd_kcontrol_new cs4265_snd_controls[] = { + + SOC_DOUBLE_R_SX_TLV("PGA Volume", CS4265_CHA_PGA_CTL, + CS4265_CHB_PGA_CTL, 0, 0x28, 0x30, pga_tlv), + SOC_DOUBLE_R_TLV("DAC Volume", CS4265_DAC_CHA_VOL, + CS4265_DAC_CHB_VOL, 0, 0xFF, 1, dac_tlv), + SOC_SINGLE("De-emp 44.1kHz Switch", CS4265_DAC_CTL, 1, + 1, 0), + SOC_SINGLE("DAC INV Switch", CS4265_DAC_CTL2, 5, + 1, 0), + SOC_SINGLE("DAC Zero Cross Switch", CS4265_DAC_CTL2, 6, + 1, 0), + SOC_SINGLE("DAC Soft Ramp Switch", CS4265_DAC_CTL2, 7, + 1, 0), + SOC_SINGLE("ADC HPF Switch", CS4265_ADC_CTL, 1, + 1, 0), + SOC_SINGLE("ADC Zero Cross Switch", CS4265_ADC_CTL2, 3, + 1, 1), + SOC_SINGLE("ADC Soft Ramp Switch", CS4265_ADC_CTL2, 7, + 1, 0), + SOC_SINGLE("E to F Buffer Disable Switch", CS4265_SPDIF_CTL1, + 6, 1, 0), + SOC_ENUM("C Data Access", cam_mode_enum), + SOC_SINGLE("Validity Bit Control Switch", CS4265_SPDIF_CTL2, + 3, 1, 0), + SOC_ENUM("SPDIF Mono/Stereo", spdif_mono_stereo_enum), + SOC_SINGLE("MMTLR Data Switch", CS4265_SPDIF_CTL2, + 0, 1, 0), + SOC_ENUM("Mono Channel Select", spdif_mono_select_enum), + SND_SOC_BYTES("C Data Buffer", CS4265_C_DATA_BUFF, 24), +}; + +static const struct snd_soc_dapm_widget cs4265_dapm_widgets[] = { + + SND_SOC_DAPM_INPUT("LINEINL"), + SND_SOC_DAPM_INPUT("LINEINR"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + + SND_SOC_DAPM_AIF_OUT("DOUT", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SPDIFOUT", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("ADC Mux", SND_SOC_NOPM, 0, 0, &mic_linein_mux), + + SND_SOC_DAPM_ADC("ADC", NULL, CS4265_PWRCTL, 2, 1), + SND_SOC_DAPM_PGA("Pre-amp MIC", CS4265_PWRCTL, 3, + 1, NULL, 0), + + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, + 0, 0, &digital_input_mux), + + SND_SOC_DAPM_MIXER("SDIN1 Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("SDIN2 Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("SPDIF Transmitter", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("Loopback", SND_SOC_NOPM, 0, 0, + &loopback_ctl), + SND_SOC_DAPM_SWITCH("SPDIF", SND_SOC_NOPM, 0, 0, + &spdif_switch), + SND_SOC_DAPM_SWITCH("DAC", CS4265_PWRCTL, 1, 1, + &dac_switch), + + SND_SOC_DAPM_AIF_IN("DIN1", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DIN2", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TXIN", NULL, 0, + CS4265_SPDIF_CTL2, 5, 1), + + SND_SOC_DAPM_OUTPUT("LINEOUTL"), + SND_SOC_DAPM_OUTPUT("LINEOUTR"), + +}; + +static const struct snd_soc_dapm_route cs4265_audio_map[] = { + + {"DIN1", NULL, "DAI1 Playback"}, + {"DIN2", NULL, "DAI2 Playback"}, + {"SDIN1 Input Mixer", NULL, "DIN1"}, + {"SDIN2 Input Mixer", NULL, "DIN2"}, + {"Input Mux", "SDIN1", "SDIN1 Input Mixer"}, + {"Input Mux", "SDIN2", "SDIN2 Input Mixer"}, + {"DAC", "Switch", "Input Mux"}, + {"SPDIF", "Switch", "Input Mux"}, + {"LINEOUTL", NULL, "DAC"}, + {"LINEOUTR", NULL, "DAC"}, + {"SPDIFOUT", NULL, "SPDIF"}, + + {"ADC Mux", "LINEIN", "LINEINL"}, + {"ADC Mux", "LINEIN", "LINEINR"}, + {"ADC Mux", "MIC", "MICL"}, + {"ADC Mux", "MIC", "MICR"}, + {"ADC", NULL, "ADC Mux"}, + {"DOUT", NULL, "ADC"}, + {"DAI1 Capture", NULL, "DOUT"}, + {"DAI2 Capture", NULL, "DOUT"}, + + /* Loopback */ + {"Loopback", "Switch", "ADC"}, + {"DAC", NULL, "Loopback"}, +}; + +struct cs4265_clk_para { + u32 mclk; + u32 rate; + u8 fm_mode; /* values 1, 2, or 4 */ + u8 mclkdiv; +}; + +static const struct cs4265_clk_para clk_map_table[] = { + /*32k*/ + {8192000, 32000, 0, 0}, + {12288000, 32000, 0, 1}, + {16384000, 32000, 0, 2}, + {24576000, 32000, 0, 3}, + {32768000, 32000, 0, 4}, + + /*44.1k*/ + {11289600, 44100, 0, 0}, + {16934400, 44100, 0, 1}, + {22579200, 44100, 0, 2}, + {33868000, 44100, 0, 3}, + {45158400, 44100, 0, 4}, + + /*48k*/ + {12288000, 48000, 0, 0}, + {18432000, 48000, 0, 1}, + {24576000, 48000, 0, 2}, + {36864000, 48000, 0, 3}, + {49152000, 48000, 0, 4}, + + /*64k*/ + {8192000, 64000, 1, 0}, + {12288000, 64000, 1, 1}, + {16934400, 64000, 1, 2}, + {24576000, 64000, 1, 3}, + {32768000, 64000, 1, 4}, + + /* 88.2k */ + {11289600, 88200, 1, 0}, + {16934400, 88200, 1, 1}, + {22579200, 88200, 1, 2}, + {33868000, 88200, 1, 3}, + {45158400, 88200, 1, 4}, + + /* 96k */ + {12288000, 96000, 1, 0}, + {18432000, 96000, 1, 1}, + {24576000, 96000, 1, 2}, + {36864000, 96000, 1, 3}, + {49152000, 96000, 1, 4}, + + /* 128k */ + {8192000, 128000, 2, 0}, + {12288000, 128000, 2, 1}, + {16934400, 128000, 2, 2}, + {24576000, 128000, 2, 3}, + {32768000, 128000, 2, 4}, + + /* 176.4k */ + {11289600, 176400, 2, 0}, + {16934400, 176400, 2, 1}, + {22579200, 176400, 2, 2}, + {33868000, 176400, 2, 3}, + {49152000, 176400, 2, 4}, + + /* 192k */ + {12288000, 192000, 2, 0}, + {18432000, 192000, 2, 1}, + {24576000, 192000, 2, 2}, + {36864000, 192000, 2, 3}, + {49152000, 192000, 2, 4}, +}; + +static int cs4265_get_clk_index(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(clk_map_table); i++) { + if (clk_map_table[i].rate == rate && + clk_map_table[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int cs4265_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs4265_private *cs4265 = snd_soc_component_get_drvdata(component); + int i; + + if (clk_id != 0) { + dev_err(component->dev, "Invalid clk_id %d\n", clk_id); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(clk_map_table); i++) { + if (clk_map_table[i].mclk == freq) { + cs4265->sysclk = freq; + return 0; + } + } + cs4265->sysclk = 0; + dev_err(component->dev, "Invalid freq parameter %d\n", freq); + return -EINVAL; +} + +static int cs4265_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs4265_private *cs4265 = snd_soc_component_get_drvdata(component); + u8 iface = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_component_update_bits(component, CS4265_ADC_CTL, + CS4265_ADC_MASTER, + CS4265_ADC_MASTER); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_component_update_bits(component, CS4265_ADC_CTL, + CS4265_ADC_MASTER, + 0); + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= SND_SOC_DAIFMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface |= SND_SOC_DAIFMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= SND_SOC_DAIFMT_LEFT_J; + break; + default: + return -EINVAL; + } + + cs4265->format = iface; + return 0; +} + +static int cs4265_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + snd_soc_component_update_bits(component, CS4265_DAC_CTL, + CS4265_DAC_CTL_MUTE, + CS4265_DAC_CTL_MUTE); + snd_soc_component_update_bits(component, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_MUTE, + CS4265_SPDIF_CTL2_MUTE); + } else { + snd_soc_component_update_bits(component, CS4265_DAC_CTL, + CS4265_DAC_CTL_MUTE, + 0); + snd_soc_component_update_bits(component, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_MUTE, + 0); + } + return 0; +} + +static int cs4265_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs4265_private *cs4265 = snd_soc_component_get_drvdata(component); + int index; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + ((cs4265->format & SND_SOC_DAIFMT_FORMAT_MASK) + == SND_SOC_DAIFMT_RIGHT_J)) + return -EINVAL; + + index = cs4265_get_clk_index(cs4265->sysclk, params_rate(params)); + if (index >= 0) { + snd_soc_component_update_bits(component, CS4265_ADC_CTL, + CS4265_ADC_FM, clk_map_table[index].fm_mode << 6); + snd_soc_component_update_bits(component, CS4265_MCLK_FREQ, + CS4265_MCLK_FREQ_MASK, + clk_map_table[index].mclkdiv << 4); + + } else { + dev_err(component->dev, "can't get correct mclk\n"); + return -EINVAL; + } + + switch (cs4265->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_soc_component_update_bits(component, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, (1 << 4)); + snd_soc_component_update_bits(component, CS4265_ADC_CTL, + CS4265_ADC_DIF, (1 << 4)); + snd_soc_component_update_bits(component, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_DIF, (1 << 6)); + break; + case SND_SOC_DAIFMT_RIGHT_J: + if (params_width(params) == 16) { + snd_soc_component_update_bits(component, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, (2 << 4)); + snd_soc_component_update_bits(component, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_DIF, (2 << 6)); + } else { + snd_soc_component_update_bits(component, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, (3 << 4)); + snd_soc_component_update_bits(component, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_DIF, (3 << 6)); + } + break; + case SND_SOC_DAIFMT_LEFT_J: + snd_soc_component_update_bits(component, CS4265_DAC_CTL, + CS4265_DAC_CTL_DIF, 0); + snd_soc_component_update_bits(component, CS4265_ADC_CTL, + CS4265_ADC_DIF, 0); + snd_soc_component_update_bits(component, CS4265_SPDIF_CTL2, + CS4265_SPDIF_CTL2_DIF, 0); + + break; + default: + return -EINVAL; + } + return 0; +} + +static int cs4265_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, CS4265_PWRCTL, + CS4265_PWRCTL_PDN, 0); + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_component_update_bits(component, CS4265_PWRCTL, + CS4265_PWRCTL_PDN, + CS4265_PWRCTL_PDN); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, CS4265_PWRCTL, + CS4265_PWRCTL_PDN, + CS4265_PWRCTL_PDN); + break; + } + return 0; +} + +#define CS4265_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) + +#define CS4265_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE) + +static const struct snd_soc_dai_ops cs4265_ops = { + .hw_params = cs4265_pcm_hw_params, + .digital_mute = cs4265_digital_mute, + .set_fmt = cs4265_set_fmt, + .set_sysclk = cs4265_set_sysclk, +}; + +static struct snd_soc_dai_driver cs4265_dai[] = { + { + .name = "cs4265-dai1", + .playback = { + .stream_name = "DAI1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .capture = { + .stream_name = "DAI1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .ops = &cs4265_ops, + }, + { + .name = "cs4265-dai2", + .playback = { + .stream_name = "DAI2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .capture = { + .stream_name = "DAI2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS4265_RATES, + .formats = CS4265_FORMATS, + }, + .ops = &cs4265_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_cs4265 = { + .set_bias_level = cs4265_set_bias_level, + .controls = cs4265_snd_controls, + .num_controls = ARRAY_SIZE(cs4265_snd_controls), + .dapm_widgets = cs4265_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs4265_dapm_widgets), + .dapm_routes = cs4265_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs4265_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config cs4265_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS4265_MAX_REGISTER, + .reg_defaults = cs4265_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs4265_reg_defaults), + .readable_reg = cs4265_readable_register, + .volatile_reg = cs4265_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs4265_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs4265_private *cs4265; + int ret = 0; + unsigned int devid = 0; + unsigned int reg; + + cs4265 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs4265_private), + GFP_KERNEL); + if (cs4265 == NULL) + return -ENOMEM; + + cs4265->regmap = devm_regmap_init_i2c(i2c_client, &cs4265_regmap); + if (IS_ERR(cs4265->regmap)) { + ret = PTR_ERR(cs4265->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + cs4265->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(cs4265->reset_gpio)) + return PTR_ERR(cs4265->reset_gpio); + + if (cs4265->reset_gpio) { + mdelay(1); + gpiod_set_value_cansleep(cs4265->reset_gpio, 1); + } + + i2c_set_clientdata(i2c_client, cs4265); + + ret = regmap_read(cs4265->regmap, CS4265_CHIP_ID, ®); + devid = reg & CS4265_CHIP_ID_MASK; + if (devid != CS4265_CHIP_ID_VAL) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS4265 Device ID (%X). Expected %X\n", + devid, CS4265_CHIP_ID); + return ret; + } + dev_info(&i2c_client->dev, + "CS4265 Version %x\n", + reg & CS4265_REV_ID_MASK); + + regmap_write(cs4265->regmap, CS4265_PWRCTL, 0x0F); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_cs4265, cs4265_dai, + ARRAY_SIZE(cs4265_dai)); + return ret; +} + +static const struct of_device_id cs4265_of_match[] = { + { .compatible = "cirrus,cs4265", }, + { } +}; +MODULE_DEVICE_TABLE(of, cs4265_of_match); + +static const struct i2c_device_id cs4265_id[] = { + { "cs4265", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs4265_id); + +static struct i2c_driver cs4265_i2c_driver = { + .driver = { + .name = "cs4265", + .of_match_table = cs4265_of_match, + }, + .id_table = cs4265_id, + .probe = cs4265_i2c_probe, +}; + +module_i2c_driver(cs4265_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS4265 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4265.h b/sound/soc/codecs/cs4265.h new file mode 100644 index 000000000..0a80a8dce --- /dev/null +++ b/sound/soc/codecs/cs4265.h @@ -0,0 +1,64 @@ +/* + * cs4265.h -- CS4265 ALSA SoC audio driver + * + * Copyright 2014 Cirrus Logic, Inc. + * + * Author: Paul Handrigan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS4265_H__ +#define __CS4265_H__ + +#define CS4265_CHIP_ID 0x1 +#define CS4265_CHIP_ID_VAL 0xD0 +#define CS4265_CHIP_ID_MASK 0xF0 +#define CS4265_REV_ID_MASK 0x0F + +#define CS4265_PWRCTL 0x02 +#define CS4265_PWRCTL_PDN 1 + +#define CS4265_DAC_CTL 0x3 +#define CS4265_DAC_CTL_MUTE (1 << 2) +#define CS4265_DAC_CTL_DIF (3 << 4) + +#define CS4265_ADC_CTL 0x4 +#define CS4265_ADC_MASTER 1 +#define CS4265_ADC_DIF (1 << 4) +#define CS4265_ADC_FM (3 << 6) + +#define CS4265_MCLK_FREQ 0x5 +#define CS4265_MCLK_FREQ_MASK (7 << 4) + +#define CS4265_SIG_SEL 0x6 +#define CS4265_SIG_SEL_LOOP (1 << 1) + +#define CS4265_CHB_PGA_CTL 0x7 +#define CS4265_CHA_PGA_CTL 0x8 + +#define CS4265_ADC_CTL2 0x9 + +#define CS4265_DAC_CHA_VOL 0xA +#define CS4265_DAC_CHB_VOL 0xB + +#define CS4265_DAC_CTL2 0xC + +#define CS4265_INT_STATUS 0xD +#define CS4265_INT_MASK 0xE +#define CS4265_STATUS_MODE_MSB 0xF +#define CS4265_STATUS_MODE_LSB 0x10 + +#define CS4265_SPDIF_CTL1 0x11 + +#define CS4265_SPDIF_CTL2 0x12 +#define CS4265_SPDIF_CTL2_MUTE (1 << 4) +#define CS4265_SPDIF_CTL2_DIF (3 << 6) + +#define CS4265_C_DATA_BUFF 0x13 +#define CS4265_MAX_REGISTER 0x2A + +#endif diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c new file mode 100644 index 000000000..007ce9f48 --- /dev/null +++ b/sound/soc/codecs/cs4270.c @@ -0,0 +1,754 @@ +/* + * CS4270 ALSA SoC (ASoC) codec driver + * + * Author: Timur Tabi + * + * Copyright 2007-2009 Freescale Semiconductor, Inc. This file is licensed + * under the terms of the GNU General Public License version 2. This + * program is licensed "as is" without any warranty of any kind, whether + * express or implied. + * + * This is an ASoC device driver for the Cirrus Logic CS4270 codec. + * + * Current features/limitations: + * + * - Software mode is supported. Stand-alone mode is not supported. + * - Only I2C is supported, not SPI + * - Support for master and slave mode + * - The machine driver's 'startup' function must call + * cs4270_set_dai_sysclk() with the value of MCLK. + * - Only I2S and left-justified modes are supported + * - Power management is supported + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The codec isn't really big-endian or little-endian, since the I2S + * interface requires data to be sent serially with the MSbit first. + * However, to support BE and LE I2S devices, we specify both here. That + * way, ALSA will always match the bit patterns. + */ +#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +/* CS4270 registers addresses */ +#define CS4270_CHIPID 0x01 /* Chip ID */ +#define CS4270_PWRCTL 0x02 /* Power Control */ +#define CS4270_MODE 0x03 /* Mode Control */ +#define CS4270_FORMAT 0x04 /* Serial Format, ADC/DAC Control */ +#define CS4270_TRANS 0x05 /* Transition Control */ +#define CS4270_MUTE 0x06 /* Mute Control */ +#define CS4270_VOLA 0x07 /* DAC Channel A Volume Control */ +#define CS4270_VOLB 0x08 /* DAC Channel B Volume Control */ + +#define CS4270_FIRSTREG 0x01 +#define CS4270_LASTREG 0x08 +#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1) +#define CS4270_I2C_INCR 0x80 + +/* Bit masks for the CS4270 registers */ +#define CS4270_CHIPID_ID 0xF0 +#define CS4270_CHIPID_REV 0x0F +#define CS4270_PWRCTL_FREEZE 0x80 +#define CS4270_PWRCTL_PDN_ADC 0x20 +#define CS4270_PWRCTL_PDN_DAC 0x02 +#define CS4270_PWRCTL_PDN 0x01 +#define CS4270_PWRCTL_PDN_ALL \ + (CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN) +#define CS4270_MODE_SPEED_MASK 0x30 +#define CS4270_MODE_1X 0x00 +#define CS4270_MODE_2X 0x10 +#define CS4270_MODE_4X 0x20 +#define CS4270_MODE_SLAVE 0x30 +#define CS4270_MODE_DIV_MASK 0x0E +#define CS4270_MODE_DIV1 0x00 +#define CS4270_MODE_DIV15 0x02 +#define CS4270_MODE_DIV2 0x04 +#define CS4270_MODE_DIV3 0x06 +#define CS4270_MODE_DIV4 0x08 +#define CS4270_MODE_POPGUARD 0x01 +#define CS4270_FORMAT_FREEZE_A 0x80 +#define CS4270_FORMAT_FREEZE_B 0x40 +#define CS4270_FORMAT_LOOPBACK 0x20 +#define CS4270_FORMAT_DAC_MASK 0x18 +#define CS4270_FORMAT_DAC_LJ 0x00 +#define CS4270_FORMAT_DAC_I2S 0x08 +#define CS4270_FORMAT_DAC_RJ16 0x18 +#define CS4270_FORMAT_DAC_RJ24 0x10 +#define CS4270_FORMAT_ADC_MASK 0x01 +#define CS4270_FORMAT_ADC_LJ 0x00 +#define CS4270_FORMAT_ADC_I2S 0x01 +#define CS4270_TRANS_ONE_VOL 0x80 +#define CS4270_TRANS_SOFT 0x40 +#define CS4270_TRANS_ZERO 0x20 +#define CS4270_TRANS_INV_ADC_A 0x08 +#define CS4270_TRANS_INV_ADC_B 0x10 +#define CS4270_TRANS_INV_DAC_A 0x02 +#define CS4270_TRANS_INV_DAC_B 0x04 +#define CS4270_TRANS_DEEMPH 0x01 +#define CS4270_MUTE_AUTO 0x20 +#define CS4270_MUTE_ADC_A 0x08 +#define CS4270_MUTE_ADC_B 0x10 +#define CS4270_MUTE_POLARITY 0x04 +#define CS4270_MUTE_DAC_A 0x01 +#define CS4270_MUTE_DAC_B 0x02 + +/* Power-on default values for the registers + * + * This array contains the power-on default values of the registers, with the + * exception of the "CHIPID" register (01h). The lower four bits of that + * register contain the hardware revision, so it is treated as volatile. + */ +static const struct reg_default cs4270_reg_defaults[] = { + { 2, 0x00 }, + { 3, 0x30 }, + { 4, 0x00 }, + { 5, 0x60 }, + { 6, 0x20 }, + { 7, 0x00 }, + { 8, 0x00 }, +}; + +static const char *supply_names[] = { + "va", "vd", "vlc" +}; + +/* Private data for the CS4270 */ +struct cs4270_private { + struct regmap *regmap; + unsigned int mclk; /* Input frequency of the MCLK pin */ + unsigned int mode; /* The mode (I2S or left-justified) */ + unsigned int slave_mode; + unsigned int manual_mute; + + /* power domain regulators */ + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; +}; + +static const struct snd_soc_dapm_widget cs4270_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), + +SND_SOC_DAPM_OUTPUT("AOUTL"), +SND_SOC_DAPM_OUTPUT("AOUTR"), +}; + +static const struct snd_soc_dapm_route cs4270_dapm_routes[] = { + { "Capture", NULL, "AINL" }, + { "Capture", NULL, "AINR" }, + + { "AOUTL", NULL, "Playback" }, + { "AOUTR", NULL, "Playback" }, +}; + +/** + * struct cs4270_mode_ratios - clock ratio tables + * @ratio: the ratio of MCLK to the sample rate + * @speed_mode: the Speed Mode bits to set in the Mode Control register for + * this ratio + * @mclk: the Ratio Select bits to set in the Mode Control register for this + * ratio + * + * The data for this chart is taken from Table 5 of the CS4270 reference + * manual. + * + * This table is used to determine how to program the Mode Control register. + * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling + * rates the CS4270 currently supports. + * + * @speed_mode is the corresponding bit pattern to be written to the + * MODE bits of the Mode Control Register + * + * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of + * the Mode Control Register. + * + * In situations where a single ratio is represented by multiple speed + * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick + * double-speed instead of quad-speed. However, the CS4270 errata states + * that divide-By-1.5 can cause failures, so we avoid that mode where + * possible. + * + * Errata: There is an errata for the CS4270 where divide-by-1.5 does not + * work if Vd is 3.3V. If this effects you, select the + * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will + * never select any sample rates that require divide-by-1.5. + */ +struct cs4270_mode_ratios { + unsigned int ratio; + u8 speed_mode; + u8 mclk; +}; + +static struct cs4270_mode_ratios cs4270_mode_ratios[] = { + {64, CS4270_MODE_4X, CS4270_MODE_DIV1}, +#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA + {96, CS4270_MODE_4X, CS4270_MODE_DIV15}, +#endif + {128, CS4270_MODE_2X, CS4270_MODE_DIV1}, + {192, CS4270_MODE_4X, CS4270_MODE_DIV3}, + {256, CS4270_MODE_1X, CS4270_MODE_DIV1}, + {384, CS4270_MODE_2X, CS4270_MODE_DIV3}, + {512, CS4270_MODE_1X, CS4270_MODE_DIV2}, + {768, CS4270_MODE_1X, CS4270_MODE_DIV3}, + {1024, CS4270_MODE_1X, CS4270_MODE_DIV4} +}; + +/* The number of MCLK/LRCK ratios supported by the CS4270 */ +#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios) + +static bool cs4270_reg_is_readable(struct device *dev, unsigned int reg) +{ + return (reg >= CS4270_FIRSTREG) && (reg <= CS4270_LASTREG); +} + +static bool cs4270_reg_is_volatile(struct device *dev, unsigned int reg) +{ + /* Unreadable registers are considered volatile */ + if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG)) + return true; + + return reg == CS4270_CHIPID; +} + +/** + * cs4270_set_dai_sysclk - determine the CS4270 samples rates. + * @codec_dai: the codec DAI + * @clk_id: the clock ID (ignored) + * @freq: the MCLK input frequency + * @dir: the clock direction (ignored) + * + * This function is used to tell the codec driver what the input MCLK + * frequency is. + * + * The value of MCLK is used to determine which sample rates are supported + * by the CS4270. The ratio of MCLK / Fs must be equal to one of nine + * supported values - 64, 96, 128, 192, 256, 384, 512, 768, and 1024. + * + * This function calculates the nine ratios and determines which ones match + * a standard sample rate. If there's a match, then it is added to the list + * of supported sample rates. + * + * This function must be called by the machine driver's 'startup' function, + * otherwise the list of supported sample rates will not be available in + * time for ALSA. + * + * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause + * theoretically possible sample rates to be enabled. Call it again with a + * proper value set one the external clock is set (most probably you would do + * that from a machine's driver 'hw_param' hook. + */ +static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + + cs4270->mclk = freq; + return 0; +} + +/** + * cs4270_set_dai_fmt - configure the codec for the selected audio format + * @codec_dai: the codec DAI + * @format: a SND_SOC_DAIFMT_x value indicating the data format + * + * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the + * codec accordingly. + * + * Currently, this function only supports SND_SOC_DAIFMT_I2S and + * SND_SOC_DAIFMT_LEFT_J. The CS4270 codec also supports right-justified + * data for playback only, but ASoC currently does not support different + * formats for playback vs. record. + */ +static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + + /* set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(component->dev, "invalid dai format\n"); + return -EINVAL; + } + + /* set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs4270->slave_mode = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs4270->slave_mode = 0; + break; + default: + /* all other modes are unsupported by the hardware */ + dev_err(component->dev, "Unknown master/slave configuration\n"); + return -EINVAL; + } + + return 0; +} + +/** + * cs4270_hw_params - program the CS4270 with the given hardware parameters. + * @substream: the audio stream + * @params: the hardware parameters to set + * @dai: the SOC DAI (ignored) + * + * This function programs the hardware with the values provided. + * Specifically, the sample rate and the data format. + * + * The .ops functions are used to provide board-specific data, like input + * frequencies, to this driver. This function takes that information, + * combines it with the hardware parameters provided, and programs the + * hardware accordingly. + */ +static int cs4270_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + int ret; + unsigned int i; + unsigned int rate; + unsigned int ratio; + int reg; + + /* Figure out which MCLK/LRCK ratio to use */ + + rate = params_rate(params); /* Sampling rate, in Hz */ + ratio = cs4270->mclk / rate; /* MCLK/LRCK ratio */ + + for (i = 0; i < NUM_MCLK_RATIOS; i++) { + if (cs4270_mode_ratios[i].ratio == ratio) + break; + } + + if (i == NUM_MCLK_RATIOS) { + /* We did not find a matching ratio */ + dev_err(component->dev, "could not find matching ratio\n"); + return -EINVAL; + } + + /* Set the sample rate */ + + reg = snd_soc_component_read32(component, CS4270_MODE); + reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK); + reg |= cs4270_mode_ratios[i].mclk; + + if (cs4270->slave_mode) + reg |= CS4270_MODE_SLAVE; + else + reg |= cs4270_mode_ratios[i].speed_mode; + + ret = snd_soc_component_write(component, CS4270_MODE, reg); + if (ret < 0) { + dev_err(component->dev, "i2c write failed\n"); + return ret; + } + + /* Set the DAI format */ + + reg = snd_soc_component_read32(component, CS4270_FORMAT); + reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK); + + switch (cs4270->mode) { + case SND_SOC_DAIFMT_I2S: + reg |= CS4270_FORMAT_DAC_I2S | CS4270_FORMAT_ADC_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ; + break; + default: + dev_err(component->dev, "unknown dai format\n"); + return -EINVAL; + } + + ret = snd_soc_component_write(component, CS4270_FORMAT, reg); + if (ret < 0) { + dev_err(component->dev, "i2c write failed\n"); + return ret; + } + + return ret; +} + +/** + * cs4270_dai_mute - enable/disable the CS4270 external mute + * @dai: the SOC DAI + * @mute: 0 = disable mute, 1 = enable mute + * + * This function toggles the mute bits in the MUTE register. The CS4270's + * mute capability is intended for external muting circuitry, so if the + * board does not have the MUTEA or MUTEB pins connected to such circuitry, + * then this function will do nothing. + */ +static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + int reg6; + + reg6 = snd_soc_component_read32(component, CS4270_MUTE); + + if (mute) + reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B; + else { + reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B); + reg6 |= cs4270->manual_mute; + } + + return snd_soc_component_write(component, CS4270_MUTE, reg6); +} + +/** + * cs4270_soc_put_mute - put callback for the 'Master Playback switch' + * alsa control. + * @kcontrol: mixer control + * @ucontrol: control element information + * + * This function basically passes the arguments on to the generic + * snd_soc_put_volsw() function and saves the mute information in + * our private data structure. This is because we want to prevent + * cs4270_dai_mute() neglecting the user's decision to manually + * mute the codec's output. + * + * Returns 0 for success. + */ +static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + int left = !ucontrol->value.integer.value[0]; + int right = !ucontrol->value.integer.value[1]; + + cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) | + (right ? CS4270_MUTE_DAC_B : 0); + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +/* A list of non-DAPM controls that the CS4270 supports */ +static const struct snd_kcontrol_new cs4270_snd_controls[] = { + SOC_DOUBLE_R("Master Playback Volume", + CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1), + SOC_SINGLE("Digital Sidetone Switch", CS4270_FORMAT, 5, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS4270_TRANS, 6, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0), + SOC_SINGLE("De-emphasis filter", CS4270_TRANS, 0, 1, 0), + SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1), + SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0), + SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1), + SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1, + snd_soc_get_volsw, cs4270_soc_put_mute), +}; + +static const struct snd_soc_dai_ops cs4270_dai_ops = { + .hw_params = cs4270_hw_params, + .set_sysclk = cs4270_set_dai_sysclk, + .set_fmt = cs4270_set_dai_fmt, + .digital_mute = cs4270_dai_mute, +}; + +static struct snd_soc_dai_driver cs4270_dai = { + .name = "cs4270-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 4000, + .rate_max = 216000, + .formats = CS4270_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 4000, + .rate_max = 216000, + .formats = CS4270_FORMATS, + }, + .ops = &cs4270_dai_ops, +}; + +/** + * cs4270_probe - ASoC probe function + * @pdev: platform device + * + * This function is called when ASoC has all the pieces it needs to + * instantiate a sound driver. + */ +static int cs4270_probe(struct snd_soc_component *component) +{ + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + int ret; + + /* Disable auto-mute. This feature appears to be buggy. In some + * situations, auto-mute will not deactivate when it should, so we want + * this feature disabled by default. An application (e.g. alsactl) can + * re-enabled it by using the controls. + */ + ret = snd_soc_component_update_bits(component, CS4270_MUTE, CS4270_MUTE_AUTO, 0); + if (ret < 0) { + dev_err(component->dev, "i2c write failed\n"); + return ret; + } + + /* Disable automatic volume control. The hardware enables, and it + * causes volume change commands to be delayed, sometimes until after + * playback has started. An application (e.g. alsactl) can + * re-enabled it by using the controls. + */ + ret = snd_soc_component_update_bits(component, CS4270_TRANS, + CS4270_TRANS_SOFT | CS4270_TRANS_ZERO, 0); + if (ret < 0) { + dev_err(component->dev, "i2c write failed\n"); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies), + cs4270->supplies); + + return ret; +} + +/** + * cs4270_remove - ASoC remove function + * @pdev: platform device + * + * This function is the counterpart to cs4270_probe(). + */ +static void cs4270_remove(struct snd_soc_component *component) +{ + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + + regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies), cs4270->supplies); +}; + +#ifdef CONFIG_PM + +/* This suspend/resume implementation can handle both - a simple standby + * where the codec remains powered, and a full suspend, where the voltage + * domain the codec is connected to is teared down and/or any other hardware + * reset condition is asserted. + * + * The codec's own power saving features are enabled in the suspend callback, + * and all registers are written back to the hardware when resuming. + */ + +static int cs4270_soc_suspend(struct snd_soc_component *component) +{ + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + int reg, ret; + + reg = snd_soc_component_read32(component, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL; + if (reg < 0) + return reg; + + ret = snd_soc_component_write(component, CS4270_PWRCTL, reg); + if (ret < 0) + return ret; + + regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies), + cs4270->supplies); + + return 0; +} + +static int cs4270_soc_resume(struct snd_soc_component *component) +{ + struct cs4270_private *cs4270 = snd_soc_component_get_drvdata(component); + int reg, ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(cs4270->supplies), + cs4270->supplies); + if (ret != 0) + return ret; + + /* In case the device was put to hard reset during sleep, we need to + * wait 500ns here before any I2C communication. */ + ndelay(500); + + /* first restore the entire register cache ... */ + regcache_sync(cs4270->regmap); + + /* ... then disable the power-down bits */ + reg = snd_soc_component_read32(component, CS4270_PWRCTL); + reg &= ~CS4270_PWRCTL_PDN_ALL; + + return snd_soc_component_write(component, CS4270_PWRCTL, reg); +} +#else +#define cs4270_soc_suspend NULL +#define cs4270_soc_resume NULL +#endif /* CONFIG_PM */ + +/* + * ASoC codec driver structure + */ +static const struct snd_soc_component_driver soc_component_device_cs4270 = { + .probe = cs4270_probe, + .remove = cs4270_remove, + .suspend = cs4270_soc_suspend, + .resume = cs4270_soc_resume, + .controls = cs4270_snd_controls, + .num_controls = ARRAY_SIZE(cs4270_snd_controls), + .dapm_widgets = cs4270_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs4270_dapm_widgets), + .dapm_routes = cs4270_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs4270_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* + * cs4270_of_match - the device tree bindings + */ +static const struct of_device_id cs4270_of_match[] = { + { .compatible = "cirrus,cs4270", }, + { } +}; +MODULE_DEVICE_TABLE(of, cs4270_of_match); + +static const struct regmap_config cs4270_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = CS4270_LASTREG, + .reg_defaults = cs4270_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs4270_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .write_flag_mask = CS4270_I2C_INCR, + + .readable_reg = cs4270_reg_is_readable, + .volatile_reg = cs4270_reg_is_volatile, +}; + +/** + * cs4270_i2c_probe - initialize the I2C interface of the CS4270 + * @i2c_client: the I2C client object + * @id: the I2C device ID (ignored) + * + * This function is called whenever the I2C subsystem finds a device that + * matches the device ID given via a prior call to i2c_add_driver(). + */ +static int cs4270_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct device_node *np = i2c_client->dev.of_node; + struct cs4270_private *cs4270; + unsigned int val; + int ret, i; + + cs4270 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs4270_private), + GFP_KERNEL); + if (!cs4270) + return -ENOMEM; + + /* get the power supply regulators */ + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + cs4270->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c_client->dev, + ARRAY_SIZE(cs4270->supplies), + cs4270->supplies); + if (ret < 0) + return ret; + + /* See if we have a way to bring the codec out of reset */ + if (np) { + enum of_gpio_flags flags; + int gpio = of_get_named_gpio_flags(np, "reset-gpio", 0, &flags); + + if (gpio_is_valid(gpio)) { + ret = devm_gpio_request_one(&i2c_client->dev, gpio, + flags & OF_GPIO_ACTIVE_LOW ? + GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH, + "cs4270 reset"); + if (ret < 0) + return ret; + } + } + + cs4270->regmap = devm_regmap_init_i2c(i2c_client, &cs4270_regmap); + if (IS_ERR(cs4270->regmap)) + return PTR_ERR(cs4270->regmap); + + /* Verify that we have a CS4270 */ + ret = regmap_read(cs4270->regmap, CS4270_CHIPID, &val); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to read i2c at addr %X\n", + i2c_client->addr); + return ret; + } + /* The top four bits of the chip ID should be 1100. */ + if ((val & 0xF0) != 0xC0) { + dev_err(&i2c_client->dev, "device at addr %X is not a CS4270\n", + i2c_client->addr); + return -ENODEV; + } + + dev_info(&i2c_client->dev, "found device at i2c address %X\n", + i2c_client->addr); + dev_info(&i2c_client->dev, "hardware revision %X\n", val & 0xF); + + i2c_set_clientdata(i2c_client, cs4270); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_device_cs4270, &cs4270_dai, 1); + return ret; +} + +/* + * cs4270_id - I2C device IDs supported by this driver + */ +static const struct i2c_device_id cs4270_id[] = { + {"cs4270", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs4270_id); + +/* + * cs4270_i2c_driver - I2C device identification + * + * This structure tells the I2C subsystem how to identify and support a + * given I2C device type. + */ +static struct i2c_driver cs4270_i2c_driver = { + .driver = { + .name = "cs4270", + .of_match_table = cs4270_of_match, + }, + .id_table = cs4270_id, + .probe = cs4270_i2c_probe, +}; + +module_i2c_driver(cs4270_i2c_driver); + +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4271-i2c.c b/sound/soc/codecs/cs4271-i2c.c new file mode 100644 index 000000000..ff737304d --- /dev/null +++ b/sound/soc/codecs/cs4271-i2c.c @@ -0,0 +1,54 @@ +/* + * CS4271 I2C audio driver + * + * Copyright (c) 2010 Alexander Sverdlin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include "cs4271.h" + +static int cs4271_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap_config config; + + config = cs4271_regmap_config; + config.reg_bits = 8; + config.val_bits = 8; + + return cs4271_probe(&client->dev, + devm_regmap_init_i2c(client, &config)); +} + +static const struct i2c_device_id cs4271_i2c_id[] = { + { "cs4271", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs4271_i2c_id); + +static struct i2c_driver cs4271_i2c_driver = { + .driver = { + .name = "cs4271", + .of_match_table = of_match_ptr(cs4271_dt_ids), + }, + .probe = cs4271_i2c_probe, + .id_table = cs4271_i2c_id, +}; +module_i2c_driver(cs4271_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS4271 I2C Driver"); +MODULE_AUTHOR("Alexander Sverdlin "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4271-spi.c b/sound/soc/codecs/cs4271-spi.c new file mode 100644 index 000000000..217f6dc86 --- /dev/null +++ b/sound/soc/codecs/cs4271-spi.c @@ -0,0 +1,47 @@ +/* + * CS4271 SPI audio driver + * + * Copyright (c) 2010 Alexander Sverdlin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include "cs4271.h" + +static int cs4271_spi_probe(struct spi_device *spi) +{ + struct regmap_config config; + + config = cs4271_regmap_config; + config.reg_bits = 16; + config.val_bits = 8; + config.read_flag_mask = 0x21; + config.write_flag_mask = 0x20; + + return cs4271_probe(&spi->dev, devm_regmap_init_spi(spi, &config)); +} + +static struct spi_driver cs4271_spi_driver = { + .driver = { + .name = "cs4271", + .of_match_table = of_match_ptr(cs4271_dt_ids), + }, + .probe = cs4271_spi_probe, +}; +module_spi_driver(cs4271_spi_driver); + +MODULE_DESCRIPTION("ASoC CS4271 SPI Driver"); +MODULE_AUTHOR("Alexander Sverdlin "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c new file mode 100644 index 000000000..849fdb2cb --- /dev/null +++ b/sound/soc/codecs/cs4271.c @@ -0,0 +1,730 @@ +/* + * CS4271 ASoC codec driver + * + * Copyright (c) 2010 Alexander Sverdlin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver support CS4271 codec being master or slave, working + * in control port mode, connected either via SPI or I2C. + * The data format accepted is I2S or left-justified. + * DAPM support not implemented. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs4271.h" + +#define CS4271_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define CS4271_PCM_RATES SNDRV_PCM_RATE_8000_192000 + +/* + * CS4271 registers + */ +#define CS4271_MODE1 0x01 /* Mode Control 1 */ +#define CS4271_DACCTL 0x02 /* DAC Control */ +#define CS4271_DACVOL 0x03 /* DAC Volume & Mixing Control */ +#define CS4271_VOLA 0x04 /* DAC Channel A Volume Control */ +#define CS4271_VOLB 0x05 /* DAC Channel B Volume Control */ +#define CS4271_ADCCTL 0x06 /* ADC Control */ +#define CS4271_MODE2 0x07 /* Mode Control 2 */ +#define CS4271_CHIPID 0x08 /* Chip ID */ + +#define CS4271_FIRSTREG CS4271_MODE1 +#define CS4271_LASTREG CS4271_MODE2 +#define CS4271_NR_REGS ((CS4271_LASTREG & 0xFF) + 1) + +/* Bit masks for the CS4271 registers */ +#define CS4271_MODE1_MODE_MASK 0xC0 +#define CS4271_MODE1_MODE_1X 0x00 +#define CS4271_MODE1_MODE_2X 0x80 +#define CS4271_MODE1_MODE_4X 0xC0 + +#define CS4271_MODE1_DIV_MASK 0x30 +#define CS4271_MODE1_DIV_1 0x00 +#define CS4271_MODE1_DIV_15 0x10 +#define CS4271_MODE1_DIV_2 0x20 +#define CS4271_MODE1_DIV_3 0x30 + +#define CS4271_MODE1_MASTER 0x08 + +#define CS4271_MODE1_DAC_DIF_MASK 0x07 +#define CS4271_MODE1_DAC_DIF_LJ 0x00 +#define CS4271_MODE1_DAC_DIF_I2S 0x01 +#define CS4271_MODE1_DAC_DIF_RJ16 0x02 +#define CS4271_MODE1_DAC_DIF_RJ24 0x03 +#define CS4271_MODE1_DAC_DIF_RJ20 0x04 +#define CS4271_MODE1_DAC_DIF_RJ18 0x05 + +#define CS4271_DACCTL_AMUTE 0x80 +#define CS4271_DACCTL_IF_SLOW 0x40 + +#define CS4271_DACCTL_DEM_MASK 0x30 +#define CS4271_DACCTL_DEM_DIS 0x00 +#define CS4271_DACCTL_DEM_441 0x10 +#define CS4271_DACCTL_DEM_48 0x20 +#define CS4271_DACCTL_DEM_32 0x30 + +#define CS4271_DACCTL_SVRU 0x08 +#define CS4271_DACCTL_SRD 0x04 +#define CS4271_DACCTL_INVA 0x02 +#define CS4271_DACCTL_INVB 0x01 + +#define CS4271_DACVOL_BEQUA 0x40 +#define CS4271_DACVOL_SOFT 0x20 +#define CS4271_DACVOL_ZEROC 0x10 + +#define CS4271_DACVOL_ATAPI_MASK 0x0F +#define CS4271_DACVOL_ATAPI_M_M 0x00 +#define CS4271_DACVOL_ATAPI_M_BR 0x01 +#define CS4271_DACVOL_ATAPI_M_BL 0x02 +#define CS4271_DACVOL_ATAPI_M_BLR2 0x03 +#define CS4271_DACVOL_ATAPI_AR_M 0x04 +#define CS4271_DACVOL_ATAPI_AR_BR 0x05 +#define CS4271_DACVOL_ATAPI_AR_BL 0x06 +#define CS4271_DACVOL_ATAPI_AR_BLR2 0x07 +#define CS4271_DACVOL_ATAPI_AL_M 0x08 +#define CS4271_DACVOL_ATAPI_AL_BR 0x09 +#define CS4271_DACVOL_ATAPI_AL_BL 0x0A +#define CS4271_DACVOL_ATAPI_AL_BLR2 0x0B +#define CS4271_DACVOL_ATAPI_ALR2_M 0x0C +#define CS4271_DACVOL_ATAPI_ALR2_BR 0x0D +#define CS4271_DACVOL_ATAPI_ALR2_BL 0x0E +#define CS4271_DACVOL_ATAPI_ALR2_BLR2 0x0F + +#define CS4271_VOLA_MUTE 0x80 +#define CS4271_VOLA_VOL_MASK 0x7F +#define CS4271_VOLB_MUTE 0x80 +#define CS4271_VOLB_VOL_MASK 0x7F + +#define CS4271_ADCCTL_DITHER16 0x20 + +#define CS4271_ADCCTL_ADC_DIF_MASK 0x10 +#define CS4271_ADCCTL_ADC_DIF_LJ 0x00 +#define CS4271_ADCCTL_ADC_DIF_I2S 0x10 + +#define CS4271_ADCCTL_MUTEA 0x08 +#define CS4271_ADCCTL_MUTEB 0x04 +#define CS4271_ADCCTL_HPFDA 0x02 +#define CS4271_ADCCTL_HPFDB 0x01 + +#define CS4271_MODE2_LOOP 0x10 +#define CS4271_MODE2_MUTECAEQUB 0x08 +#define CS4271_MODE2_FREEZE 0x04 +#define CS4271_MODE2_CPEN 0x02 +#define CS4271_MODE2_PDN 0x01 + +#define CS4271_CHIPID_PART_MASK 0xF0 +#define CS4271_CHIPID_REV_MASK 0x0F + +/* + * Default CS4271 power-up configuration + * Array contains non-existing in hw register at address 0 + * Array do not include Chip ID, as codec driver does not use + * registers read operations at all + */ +static const struct reg_default cs4271_reg_defaults[] = { + { CS4271_MODE1, 0, }, + { CS4271_DACCTL, CS4271_DACCTL_AMUTE, }, + { CS4271_DACVOL, CS4271_DACVOL_SOFT | CS4271_DACVOL_ATAPI_AL_BR, }, + { CS4271_VOLA, 0, }, + { CS4271_VOLB, 0, }, + { CS4271_ADCCTL, 0, }, + { CS4271_MODE2, 0, }, +}; + +static bool cs4271_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == CS4271_CHIPID; +} + +static const char * const supply_names[] = { + "vd", "vl", "va" +}; + +struct cs4271_private { + unsigned int mclk; + bool master; + bool deemph; + struct regmap *regmap; + /* Current sample rate for de-emphasis control */ + int rate; + /* GPIO driving Reset pin, if any */ + int gpio_nreset; + /* GPIO that disable serial bus, if any */ + int gpio_disable; + /* enable soft reset workaround */ + bool enable_soft_reset; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; +}; + +static const struct snd_soc_dapm_widget cs4271_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("AINA"), +SND_SOC_DAPM_INPUT("AINB"), + +SND_SOC_DAPM_OUTPUT("AOUTA+"), +SND_SOC_DAPM_OUTPUT("AOUTA-"), +SND_SOC_DAPM_OUTPUT("AOUTB+"), +SND_SOC_DAPM_OUTPUT("AOUTB-"), +}; + +static const struct snd_soc_dapm_route cs4271_dapm_routes[] = { + { "Capture", NULL, "AINA" }, + { "Capture", NULL, "AINB" }, + + { "AOUTA+", NULL, "Playback" }, + { "AOUTA-", NULL, "Playback" }, + { "AOUTB+", NULL, "Playback" }, + { "AOUTB-", NULL, "Playback" }, +}; + +/* + * @freq is the desired MCLK rate + * MCLK rate should (c) be the sample rate, multiplied by one of the + * ratios listed in cs4271_mclk_fs_ratios table + */ +static int cs4271_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + + cs4271->mclk = freq; + return 0; +} + +static int cs4271_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + int ret; + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs4271->master = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs4271->master = 1; + val |= CS4271_MODE1_MASTER; + break; + default: + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + val |= CS4271_MODE1_DAC_DIF_LJ; + ret = regmap_update_bits(cs4271->regmap, CS4271_ADCCTL, + CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_LJ); + if (ret < 0) + return ret; + break; + case SND_SOC_DAIFMT_I2S: + val |= CS4271_MODE1_DAC_DIF_I2S; + ret = regmap_update_bits(cs4271->regmap, CS4271_ADCCTL, + CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_I2S); + if (ret < 0) + return ret; + break; + default: + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE1, + CS4271_MODE1_DAC_DIF_MASK | CS4271_MODE1_MASTER, val); + if (ret < 0) + return ret; + return 0; +} + +static int cs4271_deemph[] = {0, 44100, 48000, 32000}; + +static int cs4271_set_deemph(struct snd_soc_component *component) +{ + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + int i, ret; + int val = CS4271_DACCTL_DEM_DIS; + + if (cs4271->deemph) { + /* Find closest de-emphasis freq */ + val = 1; + for (i = 2; i < ARRAY_SIZE(cs4271_deemph); i++) + if (abs(cs4271_deemph[i] - cs4271->rate) < + abs(cs4271_deemph[val] - cs4271->rate)) + val = i; + val <<= 4; + } + + ret = regmap_update_bits(cs4271->regmap, CS4271_DACCTL, + CS4271_DACCTL_DEM_MASK, val); + if (ret < 0) + return ret; + return 0; +} + +static int cs4271_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = cs4271->deemph; + return 0; +} + +static int cs4271_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + + cs4271->deemph = ucontrol->value.integer.value[0]; + return cs4271_set_deemph(component); +} + +struct cs4271_clk_cfg { + bool master; /* codec mode */ + u8 speed_mode; /* codec speed mode: 1x, 2x, 4x */ + unsigned short ratio; /* MCLK / sample rate */ + u8 ratio_mask; /* ratio bit mask for Master mode */ +}; + +static struct cs4271_clk_cfg cs4271_clk_tab[] = { + {1, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_3}, + {1, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_3}, + {1, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1}, + {1, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_15}, + {1, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_2}, + {1, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_3}, + {0, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_1X, 1024, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_2X, 512, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_1}, + {0, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_2}, + {0, CS4271_MODE1_MODE_4X, 256, CS4271_MODE1_DIV_2}, +}; + +#define CS4171_NR_RATIOS ARRAY_SIZE(cs4271_clk_tab) + +static int cs4271_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + int i, ret; + unsigned int ratio, val; + + if (cs4271->enable_soft_reset) { + /* + * Put the codec in soft reset and back again in case it's not + * currently streaming data. This way of bringing the codec in + * sync to the current clocks is not explicitly documented in + * the data sheet, but it seems to work fine, and in contrast + * to a read hardware reset, we don't have to sync back all + * registers every time. + */ + + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !dai->capture_active) || + (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + !dai->playback_active)) { + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE2, + CS4271_MODE2_PDN, + CS4271_MODE2_PDN); + if (ret < 0) + return ret; + + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE2, + CS4271_MODE2_PDN, 0); + if (ret < 0) + return ret; + } + } + + cs4271->rate = params_rate(params); + + /* Configure DAC */ + if (cs4271->rate < 50000) + val = CS4271_MODE1_MODE_1X; + else if (cs4271->rate < 100000) + val = CS4271_MODE1_MODE_2X; + else + val = CS4271_MODE1_MODE_4X; + + ratio = cs4271->mclk / cs4271->rate; + for (i = 0; i < CS4171_NR_RATIOS; i++) + if ((cs4271_clk_tab[i].master == cs4271->master) && + (cs4271_clk_tab[i].speed_mode == val) && + (cs4271_clk_tab[i].ratio == ratio)) + break; + + if (i == CS4171_NR_RATIOS) { + dev_err(component->dev, "Invalid sample rate\n"); + return -EINVAL; + } + + val |= cs4271_clk_tab[i].ratio_mask; + + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE1, + CS4271_MODE1_MODE_MASK | CS4271_MODE1_DIV_MASK, val); + if (ret < 0) + return ret; + + return cs4271_set_deemph(component); +} + +static int cs4271_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_component *component = dai->component; + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + int ret; + int val_a = 0; + int val_b = 0; + + if (stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + if (mute) { + val_a = CS4271_VOLA_MUTE; + val_b = CS4271_VOLB_MUTE; + } + + ret = regmap_update_bits(cs4271->regmap, CS4271_VOLA, + CS4271_VOLA_MUTE, val_a); + if (ret < 0) + return ret; + + ret = regmap_update_bits(cs4271->regmap, CS4271_VOLB, + CS4271_VOLB_MUTE, val_b); + if (ret < 0) + return ret; + + return 0; +} + +/* CS4271 controls */ +static DECLARE_TLV_DB_SCALE(cs4271_dac_tlv, -12700, 100, 0); + +static const struct snd_kcontrol_new cs4271_snd_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", CS4271_VOLA, CS4271_VOLB, + 0, 0x7F, 1, cs4271_dac_tlv), + SOC_SINGLE("Digital Loopback Switch", CS4271_MODE2, 4, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS4271_DACVOL, 5, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS4271_DACVOL, 4, 1, 0), + SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0, + cs4271_get_deemph, cs4271_put_deemph), + SOC_SINGLE("Auto-Mute Switch", CS4271_DACCTL, 7, 1, 0), + SOC_SINGLE("Slow Roll Off Filter Switch", CS4271_DACCTL, 6, 1, 0), + SOC_SINGLE("Soft Volume Ramp-Up Switch", CS4271_DACCTL, 3, 1, 0), + SOC_SINGLE("Soft Ramp-Down Switch", CS4271_DACCTL, 2, 1, 0), + SOC_SINGLE("Left Channel Inversion Switch", CS4271_DACCTL, 1, 1, 0), + SOC_SINGLE("Right Channel Inversion Switch", CS4271_DACCTL, 0, 1, 0), + SOC_DOUBLE("Master Capture Switch", CS4271_ADCCTL, 3, 2, 1, 1), + SOC_SINGLE("Dither 16-Bit Data Switch", CS4271_ADCCTL, 5, 1, 0), + SOC_DOUBLE("High Pass Filter Switch", CS4271_ADCCTL, 1, 0, 1, 1), + SOC_DOUBLE_R("Master Playback Switch", CS4271_VOLA, CS4271_VOLB, + 7, 1, 1), +}; + +static const struct snd_soc_dai_ops cs4271_dai_ops = { + .hw_params = cs4271_hw_params, + .set_sysclk = cs4271_set_dai_sysclk, + .set_fmt = cs4271_set_dai_fmt, + .mute_stream = cs4271_mute_stream, +}; + +static struct snd_soc_dai_driver cs4271_dai = { + .name = "cs4271-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = CS4271_PCM_RATES, + .formats = CS4271_PCM_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = CS4271_PCM_RATES, + .formats = CS4271_PCM_FORMATS, + }, + .ops = &cs4271_dai_ops, + .symmetric_rates = 1, +}; + +static int cs4271_reset(struct snd_soc_component *component) +{ + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + + if (gpio_is_valid(cs4271->gpio_nreset)) { + gpio_direction_output(cs4271->gpio_nreset, 0); + mdelay(1); + gpio_set_value(cs4271->gpio_nreset, 1); + mdelay(1); + } + + return 0; +} + +#ifdef CONFIG_PM +static int cs4271_soc_suspend(struct snd_soc_component *component) +{ + int ret; + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + + /* Set power-down bit */ + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE2, + CS4271_MODE2_PDN, CS4271_MODE2_PDN); + if (ret < 0) + return ret; + + regcache_mark_dirty(cs4271->regmap); + regulator_bulk_disable(ARRAY_SIZE(cs4271->supplies), cs4271->supplies); + + return 0; +} + +static int cs4271_soc_resume(struct snd_soc_component *component) +{ + int ret; + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + + ret = regulator_bulk_enable(ARRAY_SIZE(cs4271->supplies), + cs4271->supplies); + if (ret < 0) { + dev_err(component->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + /* Do a proper reset after power up */ + cs4271_reset(component); + + /* Restore codec state */ + ret = regcache_sync(cs4271->regmap); + if (ret < 0) + return ret; + + /* then disable the power-down bit */ + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE2, + CS4271_MODE2_PDN, 0); + if (ret < 0) + return ret; + + return 0; +} +#else +#define cs4271_soc_suspend NULL +#define cs4271_soc_resume NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_OF +const struct of_device_id cs4271_dt_ids[] = { + { .compatible = "cirrus,cs4271", }, + { } +}; +MODULE_DEVICE_TABLE(of, cs4271_dt_ids); +EXPORT_SYMBOL_GPL(cs4271_dt_ids); +#endif + +static int cs4271_component_probe(struct snd_soc_component *component) +{ + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + struct cs4271_platform_data *cs4271plat = component->dev->platform_data; + int ret; + bool amutec_eq_bmutec = false; + +#ifdef CONFIG_OF + if (of_match_device(cs4271_dt_ids, component->dev)) { + if (of_get_property(component->dev->of_node, + "cirrus,amutec-eq-bmutec", NULL)) + amutec_eq_bmutec = true; + + if (of_get_property(component->dev->of_node, + "cirrus,enable-soft-reset", NULL)) + cs4271->enable_soft_reset = true; + } +#endif + + ret = regulator_bulk_enable(ARRAY_SIZE(cs4271->supplies), + cs4271->supplies); + if (ret < 0) { + dev_err(component->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + if (cs4271plat) { + amutec_eq_bmutec = cs4271plat->amutec_eq_bmutec; + cs4271->enable_soft_reset = cs4271plat->enable_soft_reset; + } + + /* Reset codec */ + cs4271_reset(component); + + ret = regcache_sync(cs4271->regmap); + if (ret < 0) + return ret; + + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE2, + CS4271_MODE2_PDN | CS4271_MODE2_CPEN, + CS4271_MODE2_PDN | CS4271_MODE2_CPEN); + if (ret < 0) + return ret; + ret = regmap_update_bits(cs4271->regmap, CS4271_MODE2, + CS4271_MODE2_PDN, 0); + if (ret < 0) + return ret; + /* Power-up sequence requires 85 uS */ + udelay(85); + + if (amutec_eq_bmutec) + regmap_update_bits(cs4271->regmap, CS4271_MODE2, + CS4271_MODE2_MUTECAEQUB, + CS4271_MODE2_MUTECAEQUB); + + return 0; +} + +static void cs4271_component_remove(struct snd_soc_component *component) +{ + struct cs4271_private *cs4271 = snd_soc_component_get_drvdata(component); + + if (gpio_is_valid(cs4271->gpio_nreset)) + /* Set codec to the reset state */ + gpio_set_value(cs4271->gpio_nreset, 0); + + regcache_mark_dirty(cs4271->regmap); + regulator_bulk_disable(ARRAY_SIZE(cs4271->supplies), cs4271->supplies); +}; + +static const struct snd_soc_component_driver soc_component_dev_cs4271 = { + .probe = cs4271_component_probe, + .remove = cs4271_component_remove, + .suspend = cs4271_soc_suspend, + .resume = cs4271_soc_resume, + .controls = cs4271_snd_controls, + .num_controls = ARRAY_SIZE(cs4271_snd_controls), + .dapm_widgets = cs4271_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs4271_dapm_widgets), + .dapm_routes = cs4271_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs4271_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int cs4271_common_probe(struct device *dev, + struct cs4271_private **c) +{ + struct cs4271_platform_data *cs4271plat = dev->platform_data; + struct cs4271_private *cs4271; + int i, ret; + + cs4271 = devm_kzalloc(dev, sizeof(*cs4271), GFP_KERNEL); + if (!cs4271) + return -ENOMEM; + + if (of_match_device(cs4271_dt_ids, dev)) + cs4271->gpio_nreset = + of_get_named_gpio(dev->of_node, "reset-gpio", 0); + + if (cs4271plat) + cs4271->gpio_nreset = cs4271plat->gpio_nreset; + + if (gpio_is_valid(cs4271->gpio_nreset)) { + ret = devm_gpio_request(dev, cs4271->gpio_nreset, + "CS4271 Reset"); + if (ret < 0) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + cs4271->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs4271->supplies), + cs4271->supplies); + + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + *c = cs4271; + return 0; +} + +const struct regmap_config cs4271_regmap_config = { + .max_register = CS4271_LASTREG, + + .reg_defaults = cs4271_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs4271_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = cs4271_volatile_reg, +}; +EXPORT_SYMBOL_GPL(cs4271_regmap_config); + +int cs4271_probe(struct device *dev, struct regmap *regmap) +{ + struct cs4271_private *cs4271; + int ret; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = cs4271_common_probe(dev, &cs4271); + if (ret < 0) + return ret; + + dev_set_drvdata(dev, cs4271); + cs4271->regmap = regmap; + + return devm_snd_soc_register_component(dev, &soc_component_dev_cs4271, + &cs4271_dai, 1); +} +EXPORT_SYMBOL_GPL(cs4271_probe); + +MODULE_AUTHOR("Alexander Sverdlin "); +MODULE_DESCRIPTION("Cirrus Logic CS4271 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4271.h b/sound/soc/codecs/cs4271.h new file mode 100644 index 000000000..290283a91 --- /dev/null +++ b/sound/soc/codecs/cs4271.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _CS4271_PRIV_H +#define _CS4271_PRIV_H + +#include + +extern const struct of_device_id cs4271_dt_ids[]; +extern const struct regmap_config cs4271_regmap_config; + +int cs4271_probe(struct device *dev, struct regmap *regmap); + +#endif diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c new file mode 100644 index 000000000..9471ba17e --- /dev/null +++ b/sound/soc/codecs/cs42l42.c @@ -0,0 +1,1965 @@ +/* + * cs42l42.c -- CS42L42 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: James Schulman + * Author: Brian Austin + * Author: Michael White + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs42l42.h" + +static const struct reg_default cs42l42_reg_defaults[] = { + { CS42L42_FRZ_CTL, 0x00 }, + { CS42L42_SRC_CTL, 0x10 }, + { CS42L42_MCLK_STATUS, 0x02 }, + { CS42L42_MCLK_CTL, 0x02 }, + { CS42L42_SFTRAMP_RATE, 0xA4 }, + { CS42L42_I2C_DEBOUNCE, 0x88 }, + { CS42L42_I2C_STRETCH, 0x03 }, + { CS42L42_I2C_TIMEOUT, 0xB7 }, + { CS42L42_PWR_CTL1, 0xFF }, + { CS42L42_PWR_CTL2, 0x84 }, + { CS42L42_PWR_CTL3, 0x20 }, + { CS42L42_RSENSE_CTL1, 0x40 }, + { CS42L42_RSENSE_CTL2, 0x00 }, + { CS42L42_OSC_SWITCH, 0x00 }, + { CS42L42_OSC_SWITCH_STATUS, 0x05 }, + { CS42L42_RSENSE_CTL3, 0x1B }, + { CS42L42_TSENSE_CTL, 0x1B }, + { CS42L42_TSRS_INT_DISABLE, 0x00 }, + { CS42L42_TRSENSE_STATUS, 0x00 }, + { CS42L42_HSDET_CTL1, 0x77 }, + { CS42L42_HSDET_CTL2, 0x00 }, + { CS42L42_HS_SWITCH_CTL, 0xF3 }, + { CS42L42_HS_DET_STATUS, 0x00 }, + { CS42L42_HS_CLAMP_DISABLE, 0x00 }, + { CS42L42_MCLK_SRC_SEL, 0x00 }, + { CS42L42_SPDIF_CLK_CFG, 0x00 }, + { CS42L42_FSYNC_PW_LOWER, 0x00 }, + { CS42L42_FSYNC_PW_UPPER, 0x00 }, + { CS42L42_FSYNC_P_LOWER, 0xF9 }, + { CS42L42_FSYNC_P_UPPER, 0x00 }, + { CS42L42_ASP_CLK_CFG, 0x00 }, + { CS42L42_ASP_FRM_CFG, 0x10 }, + { CS42L42_FS_RATE_EN, 0x00 }, + { CS42L42_IN_ASRC_CLK, 0x00 }, + { CS42L42_OUT_ASRC_CLK, 0x00 }, + { CS42L42_PLL_DIV_CFG1, 0x00 }, + { CS42L42_ADC_OVFL_STATUS, 0x00 }, + { CS42L42_MIXER_STATUS, 0x00 }, + { CS42L42_SRC_STATUS, 0x00 }, + { CS42L42_ASP_RX_STATUS, 0x00 }, + { CS42L42_ASP_TX_STATUS, 0x00 }, + { CS42L42_CODEC_STATUS, 0x00 }, + { CS42L42_DET_INT_STATUS1, 0x00 }, + { CS42L42_DET_INT_STATUS2, 0x00 }, + { CS42L42_SRCPL_INT_STATUS, 0x00 }, + { CS42L42_VPMON_STATUS, 0x00 }, + { CS42L42_PLL_LOCK_STATUS, 0x00 }, + { CS42L42_TSRS_PLUG_STATUS, 0x00 }, + { CS42L42_ADC_OVFL_INT_MASK, 0x01 }, + { CS42L42_MIXER_INT_MASK, 0x0F }, + { CS42L42_SRC_INT_MASK, 0x0F }, + { CS42L42_ASP_RX_INT_MASK, 0x1F }, + { CS42L42_ASP_TX_INT_MASK, 0x0F }, + { CS42L42_CODEC_INT_MASK, 0x03 }, + { CS42L42_SRCPL_INT_MASK, 0x7F }, + { CS42L42_VPMON_INT_MASK, 0x01 }, + { CS42L42_PLL_LOCK_INT_MASK, 0x01 }, + { CS42L42_TSRS_PLUG_INT_MASK, 0x0F }, + { CS42L42_PLL_CTL1, 0x00 }, + { CS42L42_PLL_DIV_FRAC0, 0x00 }, + { CS42L42_PLL_DIV_FRAC1, 0x00 }, + { CS42L42_PLL_DIV_FRAC2, 0x00 }, + { CS42L42_PLL_DIV_INT, 0x40 }, + { CS42L42_PLL_CTL3, 0x10 }, + { CS42L42_PLL_CAL_RATIO, 0x80 }, + { CS42L42_PLL_CTL4, 0x03 }, + { CS42L42_LOAD_DET_RCSTAT, 0x00 }, + { CS42L42_LOAD_DET_DONE, 0x00 }, + { CS42L42_LOAD_DET_EN, 0x00 }, + { CS42L42_HSBIAS_SC_AUTOCTL, 0x03 }, + { CS42L42_WAKE_CTL, 0xC0 }, + { CS42L42_ADC_DISABLE_MUTE, 0x00 }, + { CS42L42_TIPSENSE_CTL, 0x02 }, + { CS42L42_MISC_DET_CTL, 0x03 }, + { CS42L42_MIC_DET_CTL1, 0x1F }, + { CS42L42_MIC_DET_CTL2, 0x2F }, + { CS42L42_DET_STATUS1, 0x00 }, + { CS42L42_DET_STATUS2, 0x00 }, + { CS42L42_DET_INT1_MASK, 0xE0 }, + { CS42L42_DET_INT2_MASK, 0xFF }, + { CS42L42_HS_BIAS_CTL, 0xC2 }, + { CS42L42_ADC_CTL, 0x00 }, + { CS42L42_ADC_VOLUME, 0x00 }, + { CS42L42_ADC_WNF_HPF_CTL, 0x71 }, + { CS42L42_DAC_CTL1, 0x00 }, + { CS42L42_DAC_CTL2, 0x02 }, + { CS42L42_HP_CTL, 0x0D }, + { CS42L42_CLASSH_CTL, 0x07 }, + { CS42L42_MIXER_CHA_VOL, 0x3F }, + { CS42L42_MIXER_ADC_VOL, 0x3F }, + { CS42L42_MIXER_CHB_VOL, 0x3F }, + { CS42L42_EQ_COEF_IN0, 0x00 }, + { CS42L42_EQ_COEF_IN1, 0x00 }, + { CS42L42_EQ_COEF_IN2, 0x00 }, + { CS42L42_EQ_COEF_IN3, 0x00 }, + { CS42L42_EQ_COEF_RW, 0x00 }, + { CS42L42_EQ_COEF_OUT0, 0x00 }, + { CS42L42_EQ_COEF_OUT1, 0x00 }, + { CS42L42_EQ_COEF_OUT2, 0x00 }, + { CS42L42_EQ_COEF_OUT3, 0x00 }, + { CS42L42_EQ_INIT_STAT, 0x00 }, + { CS42L42_EQ_START_FILT, 0x00 }, + { CS42L42_EQ_MUTE_CTL, 0x00 }, + { CS42L42_SP_RX_CH_SEL, 0x04 }, + { CS42L42_SP_RX_ISOC_CTL, 0x04 }, + { CS42L42_SP_RX_FS, 0x8C }, + { CS42l42_SPDIF_CH_SEL, 0x0E }, + { CS42L42_SP_TX_ISOC_CTL, 0x04 }, + { CS42L42_SP_TX_FS, 0xCC }, + { CS42L42_SPDIF_SW_CTL1, 0x3F }, + { CS42L42_SRC_SDIN_FS, 0x40 }, + { CS42L42_SRC_SDOUT_FS, 0x40 }, + { CS42L42_SPDIF_CTL1, 0x01 }, + { CS42L42_SPDIF_CTL2, 0x00 }, + { CS42L42_SPDIF_CTL3, 0x00 }, + { CS42L42_SPDIF_CTL4, 0x42 }, + { CS42L42_ASP_TX_SZ_EN, 0x00 }, + { CS42L42_ASP_TX_CH_EN, 0x00 }, + { CS42L42_ASP_TX_CH_AP_RES, 0x0F }, + { CS42L42_ASP_TX_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_TX_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_TX_HIZ_DLY_CFG, 0x00 }, + { CS42L42_ASP_TX_CH2_BIT_MSB, 0x00 }, + { CS42L42_ASP_TX_CH2_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_EN, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH1_AP_RES, 0x03 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH2_AP_RES, 0x03 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH2_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH3_AP_RES, 0x03 }, + { CS42L42_ASP_RX_DAI0_CH3_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH3_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH4_AP_RES, 0x03 }, + { CS42L42_ASP_RX_DAI0_CH4_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI0_CH4_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI1_CH1_AP_RES, 0x03 }, + { CS42L42_ASP_RX_DAI1_CH1_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI1_CH1_BIT_LSB, 0x00 }, + { CS42L42_ASP_RX_DAI1_CH2_AP_RES, 0x03 }, + { CS42L42_ASP_RX_DAI1_CH2_BIT_MSB, 0x00 }, + { CS42L42_ASP_RX_DAI1_CH2_BIT_LSB, 0x00 }, + { CS42L42_SUB_REVID, 0x03 }, +}; + +static bool cs42l42_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L42_PAGE_REGISTER: + case CS42L42_DEVID_AB: + case CS42L42_DEVID_CD: + case CS42L42_DEVID_E: + case CS42L42_FABID: + case CS42L42_REVID: + case CS42L42_FRZ_CTL: + case CS42L42_SRC_CTL: + case CS42L42_MCLK_STATUS: + case CS42L42_MCLK_CTL: + case CS42L42_SFTRAMP_RATE: + case CS42L42_I2C_DEBOUNCE: + case CS42L42_I2C_STRETCH: + case CS42L42_I2C_TIMEOUT: + case CS42L42_PWR_CTL1: + case CS42L42_PWR_CTL2: + case CS42L42_PWR_CTL3: + case CS42L42_RSENSE_CTL1: + case CS42L42_RSENSE_CTL2: + case CS42L42_OSC_SWITCH: + case CS42L42_OSC_SWITCH_STATUS: + case CS42L42_RSENSE_CTL3: + case CS42L42_TSENSE_CTL: + case CS42L42_TSRS_INT_DISABLE: + case CS42L42_TRSENSE_STATUS: + case CS42L42_HSDET_CTL1: + case CS42L42_HSDET_CTL2: + case CS42L42_HS_SWITCH_CTL: + case CS42L42_HS_DET_STATUS: + case CS42L42_HS_CLAMP_DISABLE: + case CS42L42_MCLK_SRC_SEL: + case CS42L42_SPDIF_CLK_CFG: + case CS42L42_FSYNC_PW_LOWER: + case CS42L42_FSYNC_PW_UPPER: + case CS42L42_FSYNC_P_LOWER: + case CS42L42_FSYNC_P_UPPER: + case CS42L42_ASP_CLK_CFG: + case CS42L42_ASP_FRM_CFG: + case CS42L42_FS_RATE_EN: + case CS42L42_IN_ASRC_CLK: + case CS42L42_OUT_ASRC_CLK: + case CS42L42_PLL_DIV_CFG1: + case CS42L42_ADC_OVFL_STATUS: + case CS42L42_MIXER_STATUS: + case CS42L42_SRC_STATUS: + case CS42L42_ASP_RX_STATUS: + case CS42L42_ASP_TX_STATUS: + case CS42L42_CODEC_STATUS: + case CS42L42_DET_INT_STATUS1: + case CS42L42_DET_INT_STATUS2: + case CS42L42_SRCPL_INT_STATUS: + case CS42L42_VPMON_STATUS: + case CS42L42_PLL_LOCK_STATUS: + case CS42L42_TSRS_PLUG_STATUS: + case CS42L42_ADC_OVFL_INT_MASK: + case CS42L42_MIXER_INT_MASK: + case CS42L42_SRC_INT_MASK: + case CS42L42_ASP_RX_INT_MASK: + case CS42L42_ASP_TX_INT_MASK: + case CS42L42_CODEC_INT_MASK: + case CS42L42_SRCPL_INT_MASK: + case CS42L42_VPMON_INT_MASK: + case CS42L42_PLL_LOCK_INT_MASK: + case CS42L42_TSRS_PLUG_INT_MASK: + case CS42L42_PLL_CTL1: + case CS42L42_PLL_DIV_FRAC0: + case CS42L42_PLL_DIV_FRAC1: + case CS42L42_PLL_DIV_FRAC2: + case CS42L42_PLL_DIV_INT: + case CS42L42_PLL_CTL3: + case CS42L42_PLL_CAL_RATIO: + case CS42L42_PLL_CTL4: + case CS42L42_LOAD_DET_RCSTAT: + case CS42L42_LOAD_DET_DONE: + case CS42L42_LOAD_DET_EN: + case CS42L42_HSBIAS_SC_AUTOCTL: + case CS42L42_WAKE_CTL: + case CS42L42_ADC_DISABLE_MUTE: + case CS42L42_TIPSENSE_CTL: + case CS42L42_MISC_DET_CTL: + case CS42L42_MIC_DET_CTL1: + case CS42L42_MIC_DET_CTL2: + case CS42L42_DET_STATUS1: + case CS42L42_DET_STATUS2: + case CS42L42_DET_INT1_MASK: + case CS42L42_DET_INT2_MASK: + case CS42L42_HS_BIAS_CTL: + case CS42L42_ADC_CTL: + case CS42L42_ADC_VOLUME: + case CS42L42_ADC_WNF_HPF_CTL: + case CS42L42_DAC_CTL1: + case CS42L42_DAC_CTL2: + case CS42L42_HP_CTL: + case CS42L42_CLASSH_CTL: + case CS42L42_MIXER_CHA_VOL: + case CS42L42_MIXER_ADC_VOL: + case CS42L42_MIXER_CHB_VOL: + case CS42L42_EQ_COEF_IN0: + case CS42L42_EQ_COEF_IN1: + case CS42L42_EQ_COEF_IN2: + case CS42L42_EQ_COEF_IN3: + case CS42L42_EQ_COEF_RW: + case CS42L42_EQ_COEF_OUT0: + case CS42L42_EQ_COEF_OUT1: + case CS42L42_EQ_COEF_OUT2: + case CS42L42_EQ_COEF_OUT3: + case CS42L42_EQ_INIT_STAT: + case CS42L42_EQ_START_FILT: + case CS42L42_EQ_MUTE_CTL: + case CS42L42_SP_RX_CH_SEL: + case CS42L42_SP_RX_ISOC_CTL: + case CS42L42_SP_RX_FS: + case CS42l42_SPDIF_CH_SEL: + case CS42L42_SP_TX_ISOC_CTL: + case CS42L42_SP_TX_FS: + case CS42L42_SPDIF_SW_CTL1: + case CS42L42_SRC_SDIN_FS: + case CS42L42_SRC_SDOUT_FS: + case CS42L42_SPDIF_CTL1: + case CS42L42_SPDIF_CTL2: + case CS42L42_SPDIF_CTL3: + case CS42L42_SPDIF_CTL4: + case CS42L42_ASP_TX_SZ_EN: + case CS42L42_ASP_TX_CH_EN: + case CS42L42_ASP_TX_CH_AP_RES: + case CS42L42_ASP_TX_CH1_BIT_MSB: + case CS42L42_ASP_TX_CH1_BIT_LSB: + case CS42L42_ASP_TX_HIZ_DLY_CFG: + case CS42L42_ASP_TX_CH2_BIT_MSB: + case CS42L42_ASP_TX_CH2_BIT_LSB: + case CS42L42_ASP_RX_DAI0_EN: + case CS42L42_ASP_RX_DAI0_CH1_AP_RES: + case CS42L42_ASP_RX_DAI0_CH1_BIT_MSB: + case CS42L42_ASP_RX_DAI0_CH1_BIT_LSB: + case CS42L42_ASP_RX_DAI0_CH2_AP_RES: + case CS42L42_ASP_RX_DAI0_CH2_BIT_MSB: + case CS42L42_ASP_RX_DAI0_CH2_BIT_LSB: + case CS42L42_ASP_RX_DAI0_CH3_AP_RES: + case CS42L42_ASP_RX_DAI0_CH3_BIT_MSB: + case CS42L42_ASP_RX_DAI0_CH3_BIT_LSB: + case CS42L42_ASP_RX_DAI0_CH4_AP_RES: + case CS42L42_ASP_RX_DAI0_CH4_BIT_MSB: + case CS42L42_ASP_RX_DAI0_CH4_BIT_LSB: + case CS42L42_ASP_RX_DAI1_CH1_AP_RES: + case CS42L42_ASP_RX_DAI1_CH1_BIT_MSB: + case CS42L42_ASP_RX_DAI1_CH1_BIT_LSB: + case CS42L42_ASP_RX_DAI1_CH2_AP_RES: + case CS42L42_ASP_RX_DAI1_CH2_BIT_MSB: + case CS42L42_ASP_RX_DAI1_CH2_BIT_LSB: + case CS42L42_SUB_REVID: + return true; + default: + return false; + } +} + +static bool cs42l42_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L42_DEVID_AB: + case CS42L42_DEVID_CD: + case CS42L42_DEVID_E: + case CS42L42_MCLK_STATUS: + case CS42L42_TRSENSE_STATUS: + case CS42L42_HS_DET_STATUS: + case CS42L42_ADC_OVFL_STATUS: + case CS42L42_MIXER_STATUS: + case CS42L42_SRC_STATUS: + case CS42L42_ASP_RX_STATUS: + case CS42L42_ASP_TX_STATUS: + case CS42L42_CODEC_STATUS: + case CS42L42_DET_INT_STATUS1: + case CS42L42_DET_INT_STATUS2: + case CS42L42_SRCPL_INT_STATUS: + case CS42L42_VPMON_STATUS: + case CS42L42_PLL_LOCK_STATUS: + case CS42L42_TSRS_PLUG_STATUS: + case CS42L42_LOAD_DET_RCSTAT: + case CS42L42_LOAD_DET_DONE: + case CS42L42_DET_STATUS1: + case CS42L42_DET_STATUS2: + return true; + default: + return false; + } +} + +static const struct regmap_range_cfg cs42l42_page_range = { + .name = "Pages", + .range_min = 0, + .range_max = CS42L42_MAX_REGISTER, + .selector_reg = CS42L42_PAGE_REGISTER, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 256, +}; + +static const struct regmap_config cs42l42_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = cs42l42_readable_register, + .volatile_reg = cs42l42_volatile_register, + + .ranges = &cs42l42_page_range, + .num_ranges = 1, + + .max_register = CS42L42_MAX_REGISTER, + .reg_defaults = cs42l42_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs42l42_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 100, true); +static DECLARE_TLV_DB_SCALE(mixer_tlv, -6300, 100, true); + +static const char * const cs42l42_hpf_freq_text[] = { + "1.86Hz", "120Hz", "235Hz", "466Hz" +}; + +static SOC_ENUM_SINGLE_DECL(cs42l42_hpf_freq_enum, CS42L42_ADC_WNF_HPF_CTL, + CS42L42_ADC_HPF_CF_SHIFT, + cs42l42_hpf_freq_text); + +static const char * const cs42l42_wnf3_freq_text[] = { + "160Hz", "180Hz", "200Hz", "220Hz", + "240Hz", "260Hz", "280Hz", "300Hz" +}; + +static SOC_ENUM_SINGLE_DECL(cs42l42_wnf3_freq_enum, CS42L42_ADC_WNF_HPF_CTL, + CS42L42_ADC_WNF_CF_SHIFT, + cs42l42_wnf3_freq_text); + +static const struct snd_kcontrol_new cs42l42_snd_controls[] = { + /* ADC Volume and Filter Controls */ + SOC_SINGLE("ADC Notch Switch", CS42L42_ADC_CTL, + CS42L42_ADC_NOTCH_DIS_SHIFT, true, true), + SOC_SINGLE("ADC Weak Force Switch", CS42L42_ADC_CTL, + CS42L42_ADC_FORCE_WEAK_VCM_SHIFT, true, false), + SOC_SINGLE("ADC Invert Switch", CS42L42_ADC_CTL, + CS42L42_ADC_INV_SHIFT, true, false), + SOC_SINGLE("ADC Boost Switch", CS42L42_ADC_CTL, + CS42L42_ADC_DIG_BOOST_SHIFT, true, false), + SOC_SINGLE_S8_TLV("ADC Volume", CS42L42_ADC_VOLUME, -97, 12, adc_tlv), + SOC_SINGLE("ADC WNF Switch", CS42L42_ADC_WNF_HPF_CTL, + CS42L42_ADC_WNF_EN_SHIFT, true, false), + SOC_SINGLE("ADC HPF Switch", CS42L42_ADC_WNF_HPF_CTL, + CS42L42_ADC_HPF_EN_SHIFT, true, false), + SOC_ENUM("HPF Corner Freq", cs42l42_hpf_freq_enum), + SOC_ENUM("WNF 3dB Freq", cs42l42_wnf3_freq_enum), + + /* DAC Volume and Filter Controls */ + SOC_SINGLE("DACA Invert Switch", CS42L42_DAC_CTL1, + CS42L42_DACA_INV_SHIFT, true, false), + SOC_SINGLE("DACB Invert Switch", CS42L42_DAC_CTL1, + CS42L42_DACB_INV_SHIFT, true, false), + SOC_SINGLE("DAC HPF Switch", CS42L42_DAC_CTL2, + CS42L42_DAC_HPF_EN_SHIFT, true, false), + SOC_DOUBLE_R_TLV("Mixer Volume", CS42L42_MIXER_CHA_VOL, + CS42L42_MIXER_CHB_VOL, CS42L42_MIXER_CH_VOL_SHIFT, + 0x3f, 1, mixer_tlv) +}; + +static int cs42l42_hpdrv_evt(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Enable the channels */ + snd_soc_component_update_bits(component, CS42L42_ASP_RX_DAI0_EN, + CS42L42_ASP_RX0_CH_EN_MASK, + (CS42L42_ASP_RX0_CH1_EN | + CS42L42_ASP_RX0_CH2_EN) << + CS42L42_ASP_RX0_CH_EN_SHIFT); + + /* Power up */ + snd_soc_component_update_bits(component, CS42L42_PWR_CTL1, + CS42L42_ASP_DAI_PDN_MASK | CS42L42_MIXER_PDN_MASK | + CS42L42_HP_PDN_MASK, 0); + } else if (event & SND_SOC_DAPM_PRE_PMD) { + /* Disable the channels */ + snd_soc_component_update_bits(component, CS42L42_ASP_RX_DAI0_EN, + CS42L42_ASP_RX0_CH_EN_MASK, 0); + + /* Power down */ + snd_soc_component_update_bits(component, CS42L42_PWR_CTL1, + CS42L42_ASP_DAI_PDN_MASK | CS42L42_MIXER_PDN_MASK | + CS42L42_HP_PDN_MASK, + CS42L42_ASP_DAI_PDN_MASK | CS42L42_MIXER_PDN_MASK | + CS42L42_HP_PDN_MASK); + } else { + dev_err(component->dev, "Invalid event 0x%x\n", event); + } + return 0; +} + +static const struct snd_soc_dapm_widget cs42l42_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_AIF_IN("SDIN", NULL, 0, CS42L42_ASP_CLK_CFG, + CS42L42_ASP_SCLK_EN_SHIFT, false), + SND_SOC_DAPM_OUT_DRV_E("HPDRV", SND_SOC_NOPM, 0, + 0, NULL, 0, cs42l42_hpdrv_evt, + SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD) +}; + +static const struct snd_soc_dapm_route cs42l42_audio_map[] = { + {"SDIN", NULL, "Playback"}, + {"HPDRV", NULL, "SDIN"}, + {"HP", NULL, "HPDRV"} +}; + +static int cs42l42_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_cache_only(cs42l42->regmap, false); + regcache_sync(cs42l42->regmap); + ret = regulator_bulk_enable( + ARRAY_SIZE(cs42l42->supplies), + cs42l42->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable regulators: %d\n", + ret); + return ret; + } + } + break; + case SND_SOC_BIAS_OFF: + + regcache_cache_only(cs42l42->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(cs42l42->supplies), + cs42l42->supplies); + break; + } + + return 0; +} + +static int cs42l42_component_probe(struct snd_soc_component *component) +{ + struct cs42l42_private *cs42l42 = + (struct cs42l42_private *)snd_soc_component_get_drvdata(component); + + cs42l42->component = component; + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_cs42l42 = { + .probe = cs42l42_component_probe, + .set_bias_level = cs42l42_set_bias_level, + .dapm_widgets = cs42l42_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42l42_dapm_widgets), + .dapm_routes = cs42l42_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs42l42_audio_map), + .controls = cs42l42_snd_controls, + .num_controls = ARRAY_SIZE(cs42l42_snd_controls), + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +struct cs42l42_pll_params { + u32 sclk; + u8 mclk_div; + u8 mclk_src_sel; + u8 sclk_prediv; + u8 pll_div_int; + u32 pll_div_frac; + u8 pll_mode; + u8 pll_divout; + u32 mclk_int; + u8 pll_cal_ratio; +}; + +/* + * Common PLL Settings for given SCLK + * Table 4-5 from the Datasheet + */ +static const struct cs42l42_pll_params pll_ratio_table[] = { + { 1536000, 0, 1, 0x00, 0x7D, 0x000000, 0x03, 0x10, 12000000, 125 }, + { 2822400, 0, 1, 0x00, 0x40, 0x000000, 0x03, 0x10, 11289600, 128 }, + { 3000000, 0, 1, 0x00, 0x40, 0x000000, 0x03, 0x10, 12000000, 128 }, + { 3072000, 0, 1, 0x00, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125 }, + { 4000000, 0, 1, 0x00, 0x30, 0x800000, 0x03, 0x10, 12000000, 96 }, + { 4096000, 0, 1, 0x00, 0x2E, 0xE00000, 0x03, 0x10, 12000000, 94 }, + { 5644800, 0, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 11289600, 128 }, + { 6000000, 0, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 12000000, 128 }, + { 6144000, 0, 1, 0x01, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125 }, + { 11289600, 0, 0, 0, 0, 0, 0, 0, 11289600, 0 }, + { 12000000, 0, 0, 0, 0, 0, 0, 0, 12000000, 0 }, + { 12288000, 0, 0, 0, 0, 0, 0, 0, 12288000, 0 }, + { 22579200, 1, 0, 0, 0, 0, 0, 0, 22579200, 0 }, + { 24000000, 1, 0, 0, 0, 0, 0, 0, 24000000, 0 }, + { 24576000, 1, 0, 0, 0, 0, 0, 0, 24576000, 0 } +}; + +static int cs42l42_pll_config(struct snd_soc_component *component) +{ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + int i; + u32 fsync; + + for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) { + if (pll_ratio_table[i].sclk == cs42l42->sclk) { + /* Configure the internal sample rate */ + snd_soc_component_update_bits(component, CS42L42_MCLK_CTL, + CS42L42_INTERNAL_FS_MASK, + ((pll_ratio_table[i].mclk_int != + 12000000) && + (pll_ratio_table[i].mclk_int != + 24000000)) << + CS42L42_INTERNAL_FS_SHIFT); + /* Set the MCLK src (PLL or SCLK) and the divide + * ratio + */ + snd_soc_component_update_bits(component, CS42L42_MCLK_SRC_SEL, + CS42L42_MCLK_SRC_SEL_MASK | + CS42L42_MCLKDIV_MASK, + (pll_ratio_table[i].mclk_src_sel + << CS42L42_MCLK_SRC_SEL_SHIFT) | + (pll_ratio_table[i].mclk_div << + CS42L42_MCLKDIV_SHIFT)); + /* Set up the LRCLK */ + fsync = cs42l42->sclk / cs42l42->srate; + if (((fsync * cs42l42->srate) != cs42l42->sclk) + || ((fsync % 2) != 0)) { + dev_err(component->dev, + "Unsupported sclk %d/sample rate %d\n", + cs42l42->sclk, + cs42l42->srate); + return -EINVAL; + } + /* Set the LRCLK period */ + snd_soc_component_update_bits(component, + CS42L42_FSYNC_P_LOWER, + CS42L42_FSYNC_PERIOD_MASK, + CS42L42_FRAC0_VAL(fsync - 1) << + CS42L42_FSYNC_PERIOD_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_FSYNC_P_UPPER, + CS42L42_FSYNC_PERIOD_MASK, + CS42L42_FRAC1_VAL(fsync - 1) << + CS42L42_FSYNC_PERIOD_SHIFT); + /* Set the LRCLK to 50% duty cycle */ + fsync = fsync / 2; + snd_soc_component_update_bits(component, + CS42L42_FSYNC_PW_LOWER, + CS42L42_FSYNC_PULSE_WIDTH_MASK, + CS42L42_FRAC0_VAL(fsync - 1) << + CS42L42_FSYNC_PULSE_WIDTH_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_FSYNC_PW_UPPER, + CS42L42_FSYNC_PULSE_WIDTH_MASK, + CS42L42_FRAC1_VAL(fsync - 1) << + CS42L42_FSYNC_PULSE_WIDTH_SHIFT); + /* Set the sample rates (96k or lower) */ + snd_soc_component_update_bits(component, CS42L42_FS_RATE_EN, + CS42L42_FS_EN_MASK, + (CS42L42_FS_EN_IASRC_96K | + CS42L42_FS_EN_OASRC_96K) << + CS42L42_FS_EN_SHIFT); + /* Set the input/output internal MCLK clock ~12 MHz */ + snd_soc_component_update_bits(component, CS42L42_IN_ASRC_CLK, + CS42L42_CLK_IASRC_SEL_MASK, + CS42L42_CLK_IASRC_SEL_12 << + CS42L42_CLK_IASRC_SEL_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_OUT_ASRC_CLK, + CS42L42_CLK_OASRC_SEL_MASK, + CS42L42_CLK_OASRC_SEL_12 << + CS42L42_CLK_OASRC_SEL_SHIFT); + if (pll_ratio_table[i].mclk_src_sel == 0) { + /* Pass the clock straight through */ + snd_soc_component_update_bits(component, + CS42L42_PLL_CTL1, + CS42L42_PLL_START_MASK, 0); + } else { + /* Configure PLL per table 4-5 */ + snd_soc_component_update_bits(component, + CS42L42_PLL_DIV_CFG1, + CS42L42_SCLK_PREDIV_MASK, + pll_ratio_table[i].sclk_prediv + << CS42L42_SCLK_PREDIV_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_PLL_DIV_INT, + CS42L42_PLL_DIV_INT_MASK, + pll_ratio_table[i].pll_div_int + << CS42L42_PLL_DIV_INT_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_PLL_DIV_FRAC0, + CS42L42_PLL_DIV_FRAC_MASK, + CS42L42_FRAC0_VAL( + pll_ratio_table[i].pll_div_frac) + << CS42L42_PLL_DIV_FRAC_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_PLL_DIV_FRAC1, + CS42L42_PLL_DIV_FRAC_MASK, + CS42L42_FRAC1_VAL( + pll_ratio_table[i].pll_div_frac) + << CS42L42_PLL_DIV_FRAC_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_PLL_DIV_FRAC2, + CS42L42_PLL_DIV_FRAC_MASK, + CS42L42_FRAC2_VAL( + pll_ratio_table[i].pll_div_frac) + << CS42L42_PLL_DIV_FRAC_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_PLL_CTL4, + CS42L42_PLL_MODE_MASK, + pll_ratio_table[i].pll_mode + << CS42L42_PLL_MODE_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_PLL_CTL3, + CS42L42_PLL_DIVOUT_MASK, + pll_ratio_table[i].pll_divout + << CS42L42_PLL_DIVOUT_SHIFT); + snd_soc_component_update_bits(component, + CS42L42_PLL_CAL_RATIO, + CS42L42_PLL_CAL_RATIO_MASK, + pll_ratio_table[i].pll_cal_ratio + << CS42L42_PLL_CAL_RATIO_SHIFT); + } + return 0; + } + } + + return -EINVAL; +} + +static int cs42l42_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u32 asp_cfg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFM: + asp_cfg_val |= CS42L42_ASP_MASTER_MODE << + CS42L42_ASP_MODE_SHIFT; + break; + case SND_SOC_DAIFMT_CBS_CFS: + asp_cfg_val |= CS42L42_ASP_SLAVE_MODE << + CS42L42_ASP_MODE_SHIFT; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* + * 5050 mode, frame starts on falling edge of LRCLK, + * frame delayed by 1.0 SCLKs + */ + snd_soc_component_update_bits(component, + CS42L42_ASP_FRM_CFG, + CS42L42_ASP_STP_MASK | + CS42L42_ASP_5050_MASK | + CS42L42_ASP_FSD_MASK, + CS42L42_ASP_5050_MASK | + (CS42L42_ASP_FSD_1_0 << + CS42L42_ASP_FSD_SHIFT)); + break; + default: + return -EINVAL; + } + + /* Bitclock/frame inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + asp_cfg_val |= CS42L42_ASP_SCPOL_NOR << CS42L42_ASP_SCPOL_SHIFT; + break; + case SND_SOC_DAIFMT_NB_IF: + asp_cfg_val |= CS42L42_ASP_SCPOL_NOR << CS42L42_ASP_SCPOL_SHIFT; + asp_cfg_val |= CS42L42_ASP_LCPOL_INV << CS42L42_ASP_LCPOL_SHIFT; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + asp_cfg_val |= CS42L42_ASP_LCPOL_INV << CS42L42_ASP_LCPOL_SHIFT; + break; + } + + snd_soc_component_update_bits(component, CS42L42_ASP_CLK_CFG, CS42L42_ASP_MODE_MASK | + CS42L42_ASP_SCPOL_MASK | + CS42L42_ASP_LCPOL_MASK, + asp_cfg_val); + + return 0; +} + +static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + unsigned int width = (params_width(params) / 8) - 1; + unsigned int val = 0; + + cs42l42->srate = params_rate(params); + + switch(substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + val |= width << CS42L42_ASP_RX_CH_RES_SHIFT; + /* channel 1 on low LRCLK */ + snd_soc_component_update_bits(component, CS42L42_ASP_RX_DAI0_CH1_AP_RES, + CS42L42_ASP_RX_CH_AP_MASK | + CS42L42_ASP_RX_CH_RES_MASK, val); + /* Channel 2 on high LRCLK */ + val |= CS42L42_ASP_RX_CH_AP_HI << CS42L42_ASP_RX_CH_AP_SHIFT; + snd_soc_component_update_bits(component, CS42L42_ASP_RX_DAI0_CH2_AP_RES, + CS42L42_ASP_RX_CH_AP_MASK | + CS42L42_ASP_RX_CH_RES_MASK, val); + break; + default: + break; + } + + return cs42l42_pll_config(component); +} + +static int cs42l42_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + + cs42l42->sclk = freq; + + return 0; +} + +static int cs42l42_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + unsigned int regval; + u8 fullScaleVol; + + if (mute) { + /* Mark SCLK as not present to turn on the internal + * oscillator. + */ + snd_soc_component_update_bits(component, CS42L42_OSC_SWITCH, + CS42L42_SCLK_PRESENT_MASK, 0); + + snd_soc_component_update_bits(component, CS42L42_PLL_CTL1, + CS42L42_PLL_START_MASK, + 0 << CS42L42_PLL_START_SHIFT); + + /* Mute the headphone */ + snd_soc_component_update_bits(component, CS42L42_HP_CTL, + CS42L42_HP_ANA_AMUTE_MASK | + CS42L42_HP_ANA_BMUTE_MASK, + CS42L42_HP_ANA_AMUTE_MASK | + CS42L42_HP_ANA_BMUTE_MASK); + } else { + snd_soc_component_update_bits(component, CS42L42_PLL_CTL1, + CS42L42_PLL_START_MASK, + 1 << CS42L42_PLL_START_SHIFT); + /* Read the headphone load */ + regval = snd_soc_component_read32(component, CS42L42_LOAD_DET_RCSTAT); + if (((regval & CS42L42_RLA_STAT_MASK) >> + CS42L42_RLA_STAT_SHIFT) == CS42L42_RLA_STAT_15_OHM) { + fullScaleVol = CS42L42_HP_FULL_SCALE_VOL_MASK; + } else { + fullScaleVol = 0; + } + + /* Un-mute the headphone, set the full scale volume flag */ + snd_soc_component_update_bits(component, CS42L42_HP_CTL, + CS42L42_HP_ANA_AMUTE_MASK | + CS42L42_HP_ANA_BMUTE_MASK | + CS42L42_HP_FULL_SCALE_VOL_MASK, fullScaleVol); + + /* Mark SCLK as present, turn off internal oscillator */ + snd_soc_component_update_bits(component, CS42L42_OSC_SWITCH, + CS42L42_SCLK_PRESENT_MASK, + CS42L42_SCLK_PRESENT_MASK); + } + + return 0; +} + +#define CS42L42_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE ) + + +static const struct snd_soc_dai_ops cs42l42_ops = { + .hw_params = cs42l42_pcm_hw_params, + .set_fmt = cs42l42_set_dai_fmt, + .set_sysclk = cs42l42_set_sysclk, + .digital_mute = cs42l42_digital_mute +}; + +static struct snd_soc_dai_driver cs42l42_dai = { + .name = "cs42l42", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42L42_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42L42_FORMATS, + }, + .ops = &cs42l42_ops, +}; + +static void cs42l42_process_hs_type_detect(struct cs42l42_private *cs42l42) +{ + unsigned int hs_det_status; + unsigned int int_status; + + /* Mask the auto detect interrupt */ + regmap_update_bits(cs42l42->regmap, + CS42L42_CODEC_INT_MASK, + CS42L42_PDN_DONE_MASK | + CS42L42_HSDET_AUTO_DONE_MASK, + (1 << CS42L42_PDN_DONE_SHIFT) | + (1 << CS42L42_HSDET_AUTO_DONE_SHIFT)); + + /* Set hs detect to automatic, disabled mode */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL2, + CS42L42_HSDET_CTRL_MASK | + CS42L42_HSDET_SET_MASK | + CS42L42_HSBIAS_REF_MASK | + CS42L42_HSDET_AUTO_TIME_MASK, + (2 << CS42L42_HSDET_CTRL_SHIFT) | + (2 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (3 << CS42L42_HSDET_AUTO_TIME_SHIFT)); + + /* Read and save the hs detection result */ + regmap_read(cs42l42->regmap, CS42L42_HS_DET_STATUS, &hs_det_status); + + cs42l42->hs_type = (hs_det_status & CS42L42_HSDET_TYPE_MASK) >> + CS42L42_HSDET_TYPE_SHIFT; + + /* Set up button detection */ + if ((cs42l42->hs_type == CS42L42_PLUG_CTIA) || + (cs42l42->hs_type == CS42L42_PLUG_OMTP)) { + /* Set auto HS bias settings to default */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSBIAS_SC_AUTOCTL, + CS42L42_HSBIAS_SENSE_EN_MASK | + CS42L42_AUTO_HSBIAS_HIZ_MASK | + CS42L42_TIP_SENSE_EN_MASK | + CS42L42_HSBIAS_SENSE_TRIP_MASK, + (0 << CS42L42_HSBIAS_SENSE_EN_SHIFT) | + (0 << CS42L42_AUTO_HSBIAS_HIZ_SHIFT) | + (0 << CS42L42_TIP_SENSE_EN_SHIFT) | + (3 << CS42L42_HSBIAS_SENSE_TRIP_SHIFT)); + + /* Set up hs detect level sensitivity */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MIC_DET_CTL1, + CS42L42_LATCH_TO_VP_MASK | + CS42L42_EVENT_STAT_SEL_MASK | + CS42L42_HS_DET_LEVEL_MASK, + (1 << CS42L42_LATCH_TO_VP_SHIFT) | + (0 << CS42L42_EVENT_STAT_SEL_SHIFT) | + (cs42l42->bias_thresholds[0] << + CS42L42_HS_DET_LEVEL_SHIFT)); + + /* Set auto HS bias settings to default */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSBIAS_SC_AUTOCTL, + CS42L42_HSBIAS_SENSE_EN_MASK | + CS42L42_AUTO_HSBIAS_HIZ_MASK | + CS42L42_TIP_SENSE_EN_MASK | + CS42L42_HSBIAS_SENSE_TRIP_MASK, + (1 << CS42L42_HSBIAS_SENSE_EN_SHIFT) | + (1 << CS42L42_AUTO_HSBIAS_HIZ_SHIFT) | + (0 << CS42L42_TIP_SENSE_EN_SHIFT) | + (3 << CS42L42_HSBIAS_SENSE_TRIP_SHIFT)); + + /* Turn on level detect circuitry */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MISC_DET_CTL, + CS42L42_DETECT_MODE_MASK | + CS42L42_HSBIAS_CTL_MASK | + CS42L42_PDN_MIC_LVL_DET_MASK, + (0 << CS42L42_DETECT_MODE_SHIFT) | + (3 << CS42L42_HSBIAS_CTL_SHIFT) | + (0 << CS42L42_PDN_MIC_LVL_DET_SHIFT)); + + msleep(cs42l42->btn_det_init_dbnce); + + /* Clear any button interrupts before unmasking them */ + regmap_read(cs42l42->regmap, CS42L42_DET_INT_STATUS2, + &int_status); + + /* Unmask button detect interrupts */ + regmap_update_bits(cs42l42->regmap, + CS42L42_DET_INT2_MASK, + CS42L42_M_DETECT_TF_MASK | + CS42L42_M_DETECT_FT_MASK | + CS42L42_M_HSBIAS_HIZ_MASK | + CS42L42_M_SHORT_RLS_MASK | + CS42L42_M_SHORT_DET_MASK, + (0 << CS42L42_M_DETECT_TF_SHIFT) | + (0 << CS42L42_M_DETECT_FT_SHIFT) | + (0 << CS42L42_M_HSBIAS_HIZ_SHIFT) | + (1 << CS42L42_M_SHORT_RLS_SHIFT) | + (1 << CS42L42_M_SHORT_DET_SHIFT)); + } else { + /* Make sure button detect and HS bias circuits are off */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MISC_DET_CTL, + CS42L42_DETECT_MODE_MASK | + CS42L42_HSBIAS_CTL_MASK | + CS42L42_PDN_MIC_LVL_DET_MASK, + (0 << CS42L42_DETECT_MODE_SHIFT) | + (1 << CS42L42_HSBIAS_CTL_SHIFT) | + (1 << CS42L42_PDN_MIC_LVL_DET_SHIFT)); + } + + regmap_update_bits(cs42l42->regmap, + CS42L42_DAC_CTL2, + CS42L42_HPOUT_PULLDOWN_MASK | + CS42L42_HPOUT_LOAD_MASK | + CS42L42_HPOUT_CLAMP_MASK | + CS42L42_DAC_HPF_EN_MASK | + CS42L42_DAC_MON_EN_MASK, + (0 << CS42L42_HPOUT_PULLDOWN_SHIFT) | + (0 << CS42L42_HPOUT_LOAD_SHIFT) | + (0 << CS42L42_HPOUT_CLAMP_SHIFT) | + (1 << CS42L42_DAC_HPF_EN_SHIFT) | + (0 << CS42L42_DAC_MON_EN_SHIFT)); + + /* Unmask tip sense interrupts */ + regmap_update_bits(cs42l42->regmap, + CS42L42_TSRS_PLUG_INT_MASK, + CS42L42_RS_PLUG_MASK | + CS42L42_RS_UNPLUG_MASK | + CS42L42_TS_PLUG_MASK | + CS42L42_TS_UNPLUG_MASK, + (1 << CS42L42_RS_PLUG_SHIFT) | + (1 << CS42L42_RS_UNPLUG_SHIFT) | + (0 << CS42L42_TS_PLUG_SHIFT) | + (0 << CS42L42_TS_UNPLUG_SHIFT)); +} + +static void cs42l42_init_hs_type_detect(struct cs42l42_private *cs42l42) +{ + /* Mask tip sense interrupts */ + regmap_update_bits(cs42l42->regmap, + CS42L42_TSRS_PLUG_INT_MASK, + CS42L42_RS_PLUG_MASK | + CS42L42_RS_UNPLUG_MASK | + CS42L42_TS_PLUG_MASK | + CS42L42_TS_UNPLUG_MASK, + (1 << CS42L42_RS_PLUG_SHIFT) | + (1 << CS42L42_RS_UNPLUG_SHIFT) | + (1 << CS42L42_TS_PLUG_SHIFT) | + (1 << CS42L42_TS_UNPLUG_SHIFT)); + + /* Make sure button detect and HS bias circuits are off */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MISC_DET_CTL, + CS42L42_DETECT_MODE_MASK | + CS42L42_HSBIAS_CTL_MASK | + CS42L42_PDN_MIC_LVL_DET_MASK, + (0 << CS42L42_DETECT_MODE_SHIFT) | + (1 << CS42L42_HSBIAS_CTL_SHIFT) | + (1 << CS42L42_PDN_MIC_LVL_DET_SHIFT)); + + /* Set auto HS bias settings to default */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSBIAS_SC_AUTOCTL, + CS42L42_HSBIAS_SENSE_EN_MASK | + CS42L42_AUTO_HSBIAS_HIZ_MASK | + CS42L42_TIP_SENSE_EN_MASK | + CS42L42_HSBIAS_SENSE_TRIP_MASK, + (0 << CS42L42_HSBIAS_SENSE_EN_SHIFT) | + (0 << CS42L42_AUTO_HSBIAS_HIZ_SHIFT) | + (0 << CS42L42_TIP_SENSE_EN_SHIFT) | + (3 << CS42L42_HSBIAS_SENSE_TRIP_SHIFT)); + + /* Set hs detect to manual, disabled mode */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL2, + CS42L42_HSDET_CTRL_MASK | + CS42L42_HSDET_SET_MASK | + CS42L42_HSBIAS_REF_MASK | + CS42L42_HSDET_AUTO_TIME_MASK, + (0 << CS42L42_HSDET_CTRL_SHIFT) | + (2 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (3 << CS42L42_HSDET_AUTO_TIME_SHIFT)); + + regmap_update_bits(cs42l42->regmap, + CS42L42_DAC_CTL2, + CS42L42_HPOUT_PULLDOWN_MASK | + CS42L42_HPOUT_LOAD_MASK | + CS42L42_HPOUT_CLAMP_MASK | + CS42L42_DAC_HPF_EN_MASK | + CS42L42_DAC_MON_EN_MASK, + (8 << CS42L42_HPOUT_PULLDOWN_SHIFT) | + (0 << CS42L42_HPOUT_LOAD_SHIFT) | + (1 << CS42L42_HPOUT_CLAMP_SHIFT) | + (1 << CS42L42_DAC_HPF_EN_SHIFT) | + (1 << CS42L42_DAC_MON_EN_SHIFT)); + + /* Power up HS bias to 2.7V */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MISC_DET_CTL, + CS42L42_DETECT_MODE_MASK | + CS42L42_HSBIAS_CTL_MASK | + CS42L42_PDN_MIC_LVL_DET_MASK, + (0 << CS42L42_DETECT_MODE_SHIFT) | + (3 << CS42L42_HSBIAS_CTL_SHIFT) | + (1 << CS42L42_PDN_MIC_LVL_DET_SHIFT)); + + /* Wait for HS bias to ramp up */ + msleep(cs42l42->hs_bias_ramp_time); + + /* Unmask auto detect interrupt */ + regmap_update_bits(cs42l42->regmap, + CS42L42_CODEC_INT_MASK, + CS42L42_PDN_DONE_MASK | + CS42L42_HSDET_AUTO_DONE_MASK, + (1 << CS42L42_PDN_DONE_SHIFT) | + (0 << CS42L42_HSDET_AUTO_DONE_SHIFT)); + + /* Set hs detect to automatic, enabled mode */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL2, + CS42L42_HSDET_CTRL_MASK | + CS42L42_HSDET_SET_MASK | + CS42L42_HSBIAS_REF_MASK | + CS42L42_HSDET_AUTO_TIME_MASK, + (3 << CS42L42_HSDET_CTRL_SHIFT) | + (2 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (3 << CS42L42_HSDET_AUTO_TIME_SHIFT)); +} + +static void cs42l42_cancel_hs_type_detect(struct cs42l42_private *cs42l42) +{ + /* Mask button detect interrupts */ + regmap_update_bits(cs42l42->regmap, + CS42L42_DET_INT2_MASK, + CS42L42_M_DETECT_TF_MASK | + CS42L42_M_DETECT_FT_MASK | + CS42L42_M_HSBIAS_HIZ_MASK | + CS42L42_M_SHORT_RLS_MASK | + CS42L42_M_SHORT_DET_MASK, + (1 << CS42L42_M_DETECT_TF_SHIFT) | + (1 << CS42L42_M_DETECT_FT_SHIFT) | + (1 << CS42L42_M_HSBIAS_HIZ_SHIFT) | + (1 << CS42L42_M_SHORT_RLS_SHIFT) | + (1 << CS42L42_M_SHORT_DET_SHIFT)); + + /* Ground HS bias */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MISC_DET_CTL, + CS42L42_DETECT_MODE_MASK | + CS42L42_HSBIAS_CTL_MASK | + CS42L42_PDN_MIC_LVL_DET_MASK, + (0 << CS42L42_DETECT_MODE_SHIFT) | + (1 << CS42L42_HSBIAS_CTL_SHIFT) | + (1 << CS42L42_PDN_MIC_LVL_DET_SHIFT)); + + /* Set auto HS bias settings to default */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSBIAS_SC_AUTOCTL, + CS42L42_HSBIAS_SENSE_EN_MASK | + CS42L42_AUTO_HSBIAS_HIZ_MASK | + CS42L42_TIP_SENSE_EN_MASK | + CS42L42_HSBIAS_SENSE_TRIP_MASK, + (0 << CS42L42_HSBIAS_SENSE_EN_SHIFT) | + (0 << CS42L42_AUTO_HSBIAS_HIZ_SHIFT) | + (0 << CS42L42_TIP_SENSE_EN_SHIFT) | + (3 << CS42L42_HSBIAS_SENSE_TRIP_SHIFT)); + + /* Set hs detect to manual, disabled mode */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HSDET_CTL2, + CS42L42_HSDET_CTRL_MASK | + CS42L42_HSDET_SET_MASK | + CS42L42_HSBIAS_REF_MASK | + CS42L42_HSDET_AUTO_TIME_MASK, + (0 << CS42L42_HSDET_CTRL_SHIFT) | + (2 << CS42L42_HSDET_SET_SHIFT) | + (0 << CS42L42_HSBIAS_REF_SHIFT) | + (3 << CS42L42_HSDET_AUTO_TIME_SHIFT)); +} + +static void cs42l42_handle_button_press(struct cs42l42_private *cs42l42) +{ + int bias_level; + unsigned int detect_status; + + /* Mask button detect interrupts */ + regmap_update_bits(cs42l42->regmap, + CS42L42_DET_INT2_MASK, + CS42L42_M_DETECT_TF_MASK | + CS42L42_M_DETECT_FT_MASK | + CS42L42_M_HSBIAS_HIZ_MASK | + CS42L42_M_SHORT_RLS_MASK | + CS42L42_M_SHORT_DET_MASK, + (1 << CS42L42_M_DETECT_TF_SHIFT) | + (1 << CS42L42_M_DETECT_FT_SHIFT) | + (1 << CS42L42_M_HSBIAS_HIZ_SHIFT) | + (1 << CS42L42_M_SHORT_RLS_SHIFT) | + (1 << CS42L42_M_SHORT_DET_SHIFT)); + + usleep_range(cs42l42->btn_det_event_dbnce * 1000, + cs42l42->btn_det_event_dbnce * 2000); + + /* Test all 4 level detect biases */ + bias_level = 1; + do { + /* Adjust button detect level sensitivity */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MIC_DET_CTL1, + CS42L42_LATCH_TO_VP_MASK | + CS42L42_EVENT_STAT_SEL_MASK | + CS42L42_HS_DET_LEVEL_MASK, + (1 << CS42L42_LATCH_TO_VP_SHIFT) | + (0 << CS42L42_EVENT_STAT_SEL_SHIFT) | + (cs42l42->bias_thresholds[bias_level] << + CS42L42_HS_DET_LEVEL_SHIFT)); + + regmap_read(cs42l42->regmap, CS42L42_DET_STATUS2, + &detect_status); + } while ((detect_status & CS42L42_HS_TRUE_MASK) && + (++bias_level < CS42L42_NUM_BIASES)); + + switch (bias_level) { + case 1: /* Function C button press */ + dev_dbg(cs42l42->component->dev, "Function C button press\n"); + break; + case 2: /* Function B button press */ + dev_dbg(cs42l42->component->dev, "Function B button press\n"); + break; + case 3: /* Function D button press */ + dev_dbg(cs42l42->component->dev, "Function D button press\n"); + break; + case 4: /* Function A button press */ + dev_dbg(cs42l42->component->dev, "Function A button press\n"); + break; + } + + /* Set button detect level sensitivity back to default */ + regmap_update_bits(cs42l42->regmap, + CS42L42_MIC_DET_CTL1, + CS42L42_LATCH_TO_VP_MASK | + CS42L42_EVENT_STAT_SEL_MASK | + CS42L42_HS_DET_LEVEL_MASK, + (1 << CS42L42_LATCH_TO_VP_SHIFT) | + (0 << CS42L42_EVENT_STAT_SEL_SHIFT) | + (cs42l42->bias_thresholds[0] << CS42L42_HS_DET_LEVEL_SHIFT)); + + /* Clear any button interrupts before unmasking them */ + regmap_read(cs42l42->regmap, CS42L42_DET_INT_STATUS2, + &detect_status); + + /* Unmask button detect interrupts */ + regmap_update_bits(cs42l42->regmap, + CS42L42_DET_INT2_MASK, + CS42L42_M_DETECT_TF_MASK | + CS42L42_M_DETECT_FT_MASK | + CS42L42_M_HSBIAS_HIZ_MASK | + CS42L42_M_SHORT_RLS_MASK | + CS42L42_M_SHORT_DET_MASK, + (0 << CS42L42_M_DETECT_TF_SHIFT) | + (0 << CS42L42_M_DETECT_FT_SHIFT) | + (0 << CS42L42_M_HSBIAS_HIZ_SHIFT) | + (1 << CS42L42_M_SHORT_RLS_SHIFT) | + (1 << CS42L42_M_SHORT_DET_SHIFT)); +} + +struct cs42l42_irq_params { + u16 status_addr; + u16 mask_addr; + u8 mask; +}; + +static const struct cs42l42_irq_params irq_params_table[] = { + {CS42L42_ADC_OVFL_STATUS, CS42L42_ADC_OVFL_INT_MASK, + CS42L42_ADC_OVFL_VAL_MASK}, + {CS42L42_MIXER_STATUS, CS42L42_MIXER_INT_MASK, + CS42L42_MIXER_VAL_MASK}, + {CS42L42_SRC_STATUS, CS42L42_SRC_INT_MASK, + CS42L42_SRC_VAL_MASK}, + {CS42L42_ASP_RX_STATUS, CS42L42_ASP_RX_INT_MASK, + CS42L42_ASP_RX_VAL_MASK}, + {CS42L42_ASP_TX_STATUS, CS42L42_ASP_TX_INT_MASK, + CS42L42_ASP_TX_VAL_MASK}, + {CS42L42_CODEC_STATUS, CS42L42_CODEC_INT_MASK, + CS42L42_CODEC_VAL_MASK}, + {CS42L42_DET_INT_STATUS1, CS42L42_DET_INT1_MASK, + CS42L42_DET_INT_VAL1_MASK}, + {CS42L42_DET_INT_STATUS2, CS42L42_DET_INT2_MASK, + CS42L42_DET_INT_VAL2_MASK}, + {CS42L42_SRCPL_INT_STATUS, CS42L42_SRCPL_INT_MASK, + CS42L42_SRCPL_VAL_MASK}, + {CS42L42_VPMON_STATUS, CS42L42_VPMON_INT_MASK, + CS42L42_VPMON_VAL_MASK}, + {CS42L42_PLL_LOCK_STATUS, CS42L42_PLL_LOCK_INT_MASK, + CS42L42_PLL_LOCK_VAL_MASK}, + {CS42L42_TSRS_PLUG_STATUS, CS42L42_TSRS_PLUG_INT_MASK, + CS42L42_TSRS_PLUG_VAL_MASK} +}; + +static irqreturn_t cs42l42_irq_thread(int irq, void *data) +{ + struct cs42l42_private *cs42l42 = (struct cs42l42_private *)data; + struct snd_soc_component *component = cs42l42->component; + unsigned int stickies[12]; + unsigned int masks[12]; + unsigned int current_plug_status; + unsigned int current_button_status; + unsigned int i; + + /* Read sticky registers to clear interurpt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + regmap_read(cs42l42->regmap, irq_params_table[i].status_addr, + &(stickies[i])); + regmap_read(cs42l42->regmap, irq_params_table[i].mask_addr, + &(masks[i])); + stickies[i] = stickies[i] & (~masks[i]) & + irq_params_table[i].mask; + } + + /* Read tip sense status before handling type detect */ + current_plug_status = (stickies[11] & + (CS42L42_TS_PLUG_MASK | CS42L42_TS_UNPLUG_MASK)) >> + CS42L42_TS_PLUG_SHIFT; + + /* Read button sense status */ + current_button_status = stickies[7] & + (CS42L42_M_DETECT_TF_MASK | + CS42L42_M_DETECT_FT_MASK | + CS42L42_M_HSBIAS_HIZ_MASK); + + /* Check auto-detect status */ + if ((~masks[5]) & irq_params_table[5].mask) { + if (stickies[5] & CS42L42_HSDET_AUTO_DONE_MASK) { + cs42l42_process_hs_type_detect(cs42l42); + dev_dbg(component->dev, + "Auto detect done (%d)\n", + cs42l42->hs_type); + } + } + + /* Check tip sense status */ + if ((~masks[11]) & irq_params_table[11].mask) { + switch (current_plug_status) { + case CS42L42_TS_PLUG: + if (cs42l42->plug_state != CS42L42_TS_PLUG) { + cs42l42->plug_state = CS42L42_TS_PLUG; + cs42l42_init_hs_type_detect(cs42l42); + } + break; + + case CS42L42_TS_UNPLUG: + if (cs42l42->plug_state != CS42L42_TS_UNPLUG) { + cs42l42->plug_state = CS42L42_TS_UNPLUG; + cs42l42_cancel_hs_type_detect(cs42l42); + dev_dbg(component->dev, + "Unplug event\n"); + } + break; + + default: + if (cs42l42->plug_state != CS42L42_TS_TRANS) + cs42l42->plug_state = CS42L42_TS_TRANS; + } + } + + /* Check button detect status */ + if ((~masks[7]) & irq_params_table[7].mask) { + if (!(current_button_status & + CS42L42_M_HSBIAS_HIZ_MASK)) { + + if (current_button_status & + CS42L42_M_DETECT_TF_MASK) { + dev_dbg(component->dev, + "Button released\n"); + } else if (current_button_status & + CS42L42_M_DETECT_FT_MASK) { + cs42l42_handle_button_press(cs42l42); + } + } + } + + return IRQ_HANDLED; +} + +static void cs42l42_set_interrupt_masks(struct cs42l42_private *cs42l42) +{ + regmap_update_bits(cs42l42->regmap, CS42L42_ADC_OVFL_INT_MASK, + CS42L42_ADC_OVFL_MASK, + (1 << CS42L42_ADC_OVFL_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_MIXER_INT_MASK, + CS42L42_MIX_CHB_OVFL_MASK | + CS42L42_MIX_CHA_OVFL_MASK | + CS42L42_EQ_OVFL_MASK | + CS42L42_EQ_BIQUAD_OVFL_MASK, + (1 << CS42L42_MIX_CHB_OVFL_SHIFT) | + (1 << CS42L42_MIX_CHA_OVFL_SHIFT) | + (1 << CS42L42_EQ_OVFL_SHIFT) | + (1 << CS42L42_EQ_BIQUAD_OVFL_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_SRC_INT_MASK, + CS42L42_SRC_ILK_MASK | + CS42L42_SRC_OLK_MASK | + CS42L42_SRC_IUNLK_MASK | + CS42L42_SRC_OUNLK_MASK, + (1 << CS42L42_SRC_ILK_SHIFT) | + (1 << CS42L42_SRC_OLK_SHIFT) | + (1 << CS42L42_SRC_IUNLK_SHIFT) | + (1 << CS42L42_SRC_OUNLK_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_ASP_RX_INT_MASK, + CS42L42_ASPRX_NOLRCK_MASK | + CS42L42_ASPRX_EARLY_MASK | + CS42L42_ASPRX_LATE_MASK | + CS42L42_ASPRX_ERROR_MASK | + CS42L42_ASPRX_OVLD_MASK, + (1 << CS42L42_ASPRX_NOLRCK_SHIFT) | + (1 << CS42L42_ASPRX_EARLY_SHIFT) | + (1 << CS42L42_ASPRX_LATE_SHIFT) | + (1 << CS42L42_ASPRX_ERROR_SHIFT) | + (1 << CS42L42_ASPRX_OVLD_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_ASP_TX_INT_MASK, + CS42L42_ASPTX_NOLRCK_MASK | + CS42L42_ASPTX_EARLY_MASK | + CS42L42_ASPTX_LATE_MASK | + CS42L42_ASPTX_SMERROR_MASK, + (1 << CS42L42_ASPTX_NOLRCK_SHIFT) | + (1 << CS42L42_ASPTX_EARLY_SHIFT) | + (1 << CS42L42_ASPTX_LATE_SHIFT) | + (1 << CS42L42_ASPTX_SMERROR_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_CODEC_INT_MASK, + CS42L42_PDN_DONE_MASK | + CS42L42_HSDET_AUTO_DONE_MASK, + (1 << CS42L42_PDN_DONE_SHIFT) | + (1 << CS42L42_HSDET_AUTO_DONE_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_SRCPL_INT_MASK, + CS42L42_SRCPL_ADC_LK_MASK | + CS42L42_SRCPL_DAC_LK_MASK | + CS42L42_SRCPL_ADC_UNLK_MASK | + CS42L42_SRCPL_DAC_UNLK_MASK, + (1 << CS42L42_SRCPL_ADC_LK_SHIFT) | + (1 << CS42L42_SRCPL_DAC_LK_SHIFT) | + (1 << CS42L42_SRCPL_ADC_UNLK_SHIFT) | + (1 << CS42L42_SRCPL_DAC_UNLK_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_DET_INT1_MASK, + CS42L42_TIP_SENSE_UNPLUG_MASK | + CS42L42_TIP_SENSE_PLUG_MASK | + CS42L42_HSBIAS_SENSE_MASK, + (1 << CS42L42_TIP_SENSE_UNPLUG_SHIFT) | + (1 << CS42L42_TIP_SENSE_PLUG_SHIFT) | + (1 << CS42L42_HSBIAS_SENSE_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_DET_INT2_MASK, + CS42L42_M_DETECT_TF_MASK | + CS42L42_M_DETECT_FT_MASK | + CS42L42_M_HSBIAS_HIZ_MASK | + CS42L42_M_SHORT_RLS_MASK | + CS42L42_M_SHORT_DET_MASK, + (1 << CS42L42_M_DETECT_TF_SHIFT) | + (1 << CS42L42_M_DETECT_FT_SHIFT) | + (1 << CS42L42_M_HSBIAS_HIZ_SHIFT) | + (1 << CS42L42_M_SHORT_RLS_SHIFT) | + (1 << CS42L42_M_SHORT_DET_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_VPMON_INT_MASK, + CS42L42_VPMON_MASK, + (1 << CS42L42_VPMON_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_PLL_LOCK_INT_MASK, + CS42L42_PLL_LOCK_MASK, + (1 << CS42L42_PLL_LOCK_SHIFT)); + + regmap_update_bits(cs42l42->regmap, CS42L42_TSRS_PLUG_INT_MASK, + CS42L42_RS_PLUG_MASK | + CS42L42_RS_UNPLUG_MASK | + CS42L42_TS_PLUG_MASK | + CS42L42_TS_UNPLUG_MASK, + (1 << CS42L42_RS_PLUG_SHIFT) | + (1 << CS42L42_RS_UNPLUG_SHIFT) | + (0 << CS42L42_TS_PLUG_SHIFT) | + (0 << CS42L42_TS_UNPLUG_SHIFT)); +} + +static void cs42l42_setup_hs_type_detect(struct cs42l42_private *cs42l42) +{ + unsigned int reg; + + cs42l42->hs_type = CS42L42_PLUG_INVALID; + + /* Latch analog controls to VP power domain */ + regmap_update_bits(cs42l42->regmap, CS42L42_MIC_DET_CTL1, + CS42L42_LATCH_TO_VP_MASK | + CS42L42_EVENT_STAT_SEL_MASK | + CS42L42_HS_DET_LEVEL_MASK, + (1 << CS42L42_LATCH_TO_VP_SHIFT) | + (0 << CS42L42_EVENT_STAT_SEL_SHIFT) | + (cs42l42->bias_thresholds[0] << + CS42L42_HS_DET_LEVEL_SHIFT)); + + /* Remove ground noise-suppression clamps */ + regmap_update_bits(cs42l42->regmap, + CS42L42_HS_CLAMP_DISABLE, + CS42L42_HS_CLAMP_DISABLE_MASK, + (1 << CS42L42_HS_CLAMP_DISABLE_SHIFT)); + + /* Enable the tip sense circuit */ + regmap_update_bits(cs42l42->regmap, CS42L42_TIPSENSE_CTL, + CS42L42_TIP_SENSE_CTRL_MASK | + CS42L42_TIP_SENSE_INV_MASK | + CS42L42_TIP_SENSE_DEBOUNCE_MASK, + (3 << CS42L42_TIP_SENSE_CTRL_SHIFT) | + (0 << CS42L42_TIP_SENSE_INV_SHIFT) | + (2 << CS42L42_TIP_SENSE_DEBOUNCE_SHIFT)); + + /* Save the initial status of the tip sense */ + regmap_read(cs42l42->regmap, + CS42L42_TSRS_PLUG_STATUS, + ®); + cs42l42->plug_state = (((char) reg) & + (CS42L42_TS_PLUG_MASK | CS42L42_TS_UNPLUG_MASK)) >> + CS42L42_TS_PLUG_SHIFT; +} + +static const unsigned int threshold_defaults[] = { + CS42L42_HS_DET_LEVEL_15, + CS42L42_HS_DET_LEVEL_8, + CS42L42_HS_DET_LEVEL_4, + CS42L42_HS_DET_LEVEL_1 +}; + +static int cs42l42_handle_device_data(struct i2c_client *i2c_client, + struct cs42l42_private *cs42l42) +{ + struct device_node *np = i2c_client->dev.of_node; + unsigned int val; + unsigned int thresholds[CS42L42_NUM_BIASES]; + int ret; + int i; + + ret = of_property_read_u32(np, "cirrus,ts-inv", &val); + + if (!ret) { + switch (val) { + case CS42L42_TS_INV_EN: + case CS42L42_TS_INV_DIS: + cs42l42->ts_inv = val; + break; + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,ts-inv DT value %d\n", + val); + cs42l42->ts_inv = CS42L42_TS_INV_DIS; + } + } else { + cs42l42->ts_inv = CS42L42_TS_INV_DIS; + } + + regmap_update_bits(cs42l42->regmap, CS42L42_TSENSE_CTL, + CS42L42_TS_INV_MASK, + (cs42l42->ts_inv << CS42L42_TS_INV_SHIFT)); + + ret = of_property_read_u32(np, "cirrus,ts-dbnc-rise", &val); + + if (!ret) { + switch (val) { + case CS42L42_TS_DBNCE_0: + case CS42L42_TS_DBNCE_125: + case CS42L42_TS_DBNCE_250: + case CS42L42_TS_DBNCE_500: + case CS42L42_TS_DBNCE_750: + case CS42L42_TS_DBNCE_1000: + case CS42L42_TS_DBNCE_1250: + case CS42L42_TS_DBNCE_1500: + cs42l42->ts_dbnc_rise = val; + break; + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,ts-dbnc-rise DT value %d\n", + val); + cs42l42->ts_dbnc_rise = CS42L42_TS_DBNCE_1000; + } + } else { + cs42l42->ts_dbnc_rise = CS42L42_TS_DBNCE_1000; + } + + regmap_update_bits(cs42l42->regmap, CS42L42_TSENSE_CTL, + CS42L42_TS_RISE_DBNCE_TIME_MASK, + (cs42l42->ts_dbnc_rise << + CS42L42_TS_RISE_DBNCE_TIME_SHIFT)); + + ret = of_property_read_u32(np, "cirrus,ts-dbnc-fall", &val); + + if (!ret) { + switch (val) { + case CS42L42_TS_DBNCE_0: + case CS42L42_TS_DBNCE_125: + case CS42L42_TS_DBNCE_250: + case CS42L42_TS_DBNCE_500: + case CS42L42_TS_DBNCE_750: + case CS42L42_TS_DBNCE_1000: + case CS42L42_TS_DBNCE_1250: + case CS42L42_TS_DBNCE_1500: + cs42l42->ts_dbnc_fall = val; + break; + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,ts-dbnc-fall DT value %d\n", + val); + cs42l42->ts_dbnc_fall = CS42L42_TS_DBNCE_0; + } + } else { + cs42l42->ts_dbnc_fall = CS42L42_TS_DBNCE_0; + } + + regmap_update_bits(cs42l42->regmap, CS42L42_TSENSE_CTL, + CS42L42_TS_FALL_DBNCE_TIME_MASK, + (cs42l42->ts_dbnc_fall << + CS42L42_TS_FALL_DBNCE_TIME_SHIFT)); + + ret = of_property_read_u32(np, "cirrus,btn-det-init-dbnce", &val); + + if (!ret) { + if ((val >= CS42L42_BTN_DET_INIT_DBNCE_MIN) && + (val <= CS42L42_BTN_DET_INIT_DBNCE_MAX)) + cs42l42->btn_det_init_dbnce = val; + else { + dev_err(&i2c_client->dev, + "Wrong cirrus,btn-det-init-dbnce DT value %d\n", + val); + cs42l42->btn_det_init_dbnce = + CS42L42_BTN_DET_INIT_DBNCE_DEFAULT; + } + } else { + cs42l42->btn_det_init_dbnce = + CS42L42_BTN_DET_INIT_DBNCE_DEFAULT; + } + + ret = of_property_read_u32(np, "cirrus,btn-det-event-dbnce", &val); + + if (!ret) { + if ((val >= CS42L42_BTN_DET_EVENT_DBNCE_MIN) && + (val <= CS42L42_BTN_DET_EVENT_DBNCE_MAX)) + cs42l42->btn_det_event_dbnce = val; + else { + dev_err(&i2c_client->dev, + "Wrong cirrus,btn-det-event-dbnce DT value %d\n", val); + cs42l42->btn_det_event_dbnce = + CS42L42_BTN_DET_EVENT_DBNCE_DEFAULT; + } + } else { + cs42l42->btn_det_event_dbnce = + CS42L42_BTN_DET_EVENT_DBNCE_DEFAULT; + } + + ret = of_property_read_u32_array(np, "cirrus,bias-lvls", + (u32 *)thresholds, CS42L42_NUM_BIASES); + + if (!ret) { + for (i = 0; i < CS42L42_NUM_BIASES; i++) { + if ((thresholds[i] >= CS42L42_HS_DET_LEVEL_MIN) && + (thresholds[i] <= CS42L42_HS_DET_LEVEL_MAX)) + cs42l42->bias_thresholds[i] = thresholds[i]; + else { + dev_err(&i2c_client->dev, + "Wrong cirrus,bias-lvls[%d] DT value %d\n", i, + thresholds[i]); + cs42l42->bias_thresholds[i] = + threshold_defaults[i]; + } + } + } else { + for (i = 0; i < CS42L42_NUM_BIASES; i++) + cs42l42->bias_thresholds[i] = threshold_defaults[i]; + } + + ret = of_property_read_u32(np, "cirrus,hs-bias-ramp-rate", &val); + + if (!ret) { + switch (val) { + case CS42L42_HSBIAS_RAMP_FAST_RISE_SLOW_FALL: + cs42l42->hs_bias_ramp_rate = val; + cs42l42->hs_bias_ramp_time = CS42L42_HSBIAS_RAMP_TIME0; + break; + case CS42L42_HSBIAS_RAMP_FAST: + cs42l42->hs_bias_ramp_rate = val; + cs42l42->hs_bias_ramp_time = CS42L42_HSBIAS_RAMP_TIME1; + break; + case CS42L42_HSBIAS_RAMP_SLOW: + cs42l42->hs_bias_ramp_rate = val; + cs42l42->hs_bias_ramp_time = CS42L42_HSBIAS_RAMP_TIME2; + break; + case CS42L42_HSBIAS_RAMP_SLOWEST: + cs42l42->hs_bias_ramp_rate = val; + cs42l42->hs_bias_ramp_time = CS42L42_HSBIAS_RAMP_TIME3; + break; + default: + dev_err(&i2c_client->dev, + "Wrong cirrus,hs-bias-ramp-rate DT value %d\n", + val); + cs42l42->hs_bias_ramp_rate = CS42L42_HSBIAS_RAMP_SLOW; + cs42l42->hs_bias_ramp_time = CS42L42_HSBIAS_RAMP_TIME2; + } + } else { + cs42l42->hs_bias_ramp_rate = CS42L42_HSBIAS_RAMP_SLOW; + cs42l42->hs_bias_ramp_time = CS42L42_HSBIAS_RAMP_TIME2; + } + + regmap_update_bits(cs42l42->regmap, CS42L42_HS_BIAS_CTL, + CS42L42_HSBIAS_RAMP_MASK, + (cs42l42->hs_bias_ramp_rate << + CS42L42_HSBIAS_RAMP_SHIFT)); + + return 0; +} + +static int cs42l42_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs42l42_private *cs42l42; + int ret, i; + unsigned int devid = 0; + unsigned int reg; + + cs42l42 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs42l42_private), + GFP_KERNEL); + if (!cs42l42) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs42l42); + + cs42l42->regmap = devm_regmap_init_i2c(i2c_client, &cs42l42_regmap); + if (IS_ERR(cs42l42->regmap)) { + ret = PTR_ERR(cs42l42->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(cs42l42->supplies); i++) + cs42l42->supplies[i].supply = cs42l42_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c_client->dev, + ARRAY_SIZE(cs42l42->supplies), + cs42l42->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42l42->supplies), + cs42l42->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable supplies: %d\n", ret); + return ret; + } + + /* Reset the Device */ + cs42l42->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(cs42l42->reset_gpio)) + return PTR_ERR(cs42l42->reset_gpio); + + if (cs42l42->reset_gpio) { + dev_dbg(&i2c_client->dev, "Found reset GPIO\n"); + gpiod_set_value_cansleep(cs42l42->reset_gpio, 1); + } + usleep_range(CS42L42_BOOT_TIME_US, CS42L42_BOOT_TIME_US * 2); + + /* Request IRQ */ + ret = devm_request_threaded_irq(&i2c_client->dev, + i2c_client->irq, + NULL, cs42l42_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs42l42", cs42l42); + if (ret == -EPROBE_DEFER) + goto err_disable; + else if (ret != 0) + dev_err(&i2c_client->dev, + "Failed to request IRQ: %d\n", ret); + + /* initialize codec */ + ret = regmap_read(cs42l42->regmap, CS42L42_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + + ret = regmap_read(cs42l42->regmap, CS42L42_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + + ret = regmap_read(cs42l42->regmap, CS42L42_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS42L42_CHIP_ID) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS42L42 Device ID (%X). Expected %X\n", + devid, CS42L42_CHIP_ID); + return ret; + } + + ret = regmap_read(cs42l42->regmap, CS42L42_REVID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed\n"); + return ret; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS42L42, Revision: %02X\n", reg & 0xFF); + + /* Power up the codec */ + regmap_update_bits(cs42l42->regmap, CS42L42_PWR_CTL1, + CS42L42_ASP_DAO_PDN_MASK | + CS42L42_ASP_DAI_PDN_MASK | + CS42L42_MIXER_PDN_MASK | + CS42L42_EQ_PDN_MASK | + CS42L42_HP_PDN_MASK | + CS42L42_ADC_PDN_MASK | + CS42L42_PDN_ALL_MASK, + (1 << CS42L42_ASP_DAO_PDN_SHIFT) | + (1 << CS42L42_ASP_DAI_PDN_SHIFT) | + (1 << CS42L42_MIXER_PDN_SHIFT) | + (1 << CS42L42_EQ_PDN_SHIFT) | + (1 << CS42L42_HP_PDN_SHIFT) | + (1 << CS42L42_ADC_PDN_SHIFT) | + (0 << CS42L42_PDN_ALL_SHIFT)); + + if (i2c_client->dev.of_node) { + ret = cs42l42_handle_device_data(i2c_client, cs42l42); + if (ret != 0) + return ret; + } + + /* Setup headset detection */ + cs42l42_setup_hs_type_detect(cs42l42); + + /* Mask/Unmask Interrupts */ + cs42l42_set_interrupt_masks(cs42l42); + + /* Register codec for machine driver */ + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_cs42l42, &cs42l42_dai, 1); + if (ret < 0) + goto err_disable; + return 0; + +err_disable: + regulator_bulk_disable(ARRAY_SIZE(cs42l42->supplies), + cs42l42->supplies); + return ret; +} + +static int cs42l42_i2c_remove(struct i2c_client *i2c_client) +{ + struct cs42l42_private *cs42l42 = i2c_get_clientdata(i2c_client); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs42l42->reset_gpio, 0); + + return 0; +} + +#ifdef CONFIG_PM +static int cs42l42_runtime_suspend(struct device *dev) +{ + struct cs42l42_private *cs42l42 = dev_get_drvdata(dev); + + regcache_cache_only(cs42l42->regmap, true); + regcache_mark_dirty(cs42l42->regmap); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs42l42->reset_gpio, 0); + + /* remove power */ + regulator_bulk_disable(ARRAY_SIZE(cs42l42->supplies), + cs42l42->supplies); + + return 0; +} + +static int cs42l42_runtime_resume(struct device *dev) +{ + struct cs42l42_private *cs42l42 = dev_get_drvdata(dev); + int ret; + + /* Enable power */ + ret = regulator_bulk_enable(ARRAY_SIZE(cs42l42->supplies), + cs42l42->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", + ret); + return ret; + } + + gpiod_set_value_cansleep(cs42l42->reset_gpio, 1); + usleep_range(CS42L42_BOOT_TIME_US, CS42L42_BOOT_TIME_US * 2); + + regcache_cache_only(cs42l42->regmap, false); + regcache_sync(cs42l42->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops cs42l42_runtime_pm = { + SET_RUNTIME_PM_OPS(cs42l42_runtime_suspend, cs42l42_runtime_resume, + NULL) +}; + +static const struct of_device_id cs42l42_of_match[] = { + { .compatible = "cirrus,cs42l42", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs42l42_of_match); + + +static const struct i2c_device_id cs42l42_id[] = { + {"cs42l42", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs42l42_id); + +static struct i2c_driver cs42l42_i2c_driver = { + .driver = { + .name = "cs42l42", + .pm = &cs42l42_runtime_pm, + .of_match_table = cs42l42_of_match, + }, + .id_table = cs42l42_id, + .probe = cs42l42_i2c_probe, + .remove = cs42l42_i2c_remove, +}; + +module_i2c_driver(cs42l42_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS42L42 driver"); +MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, "); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_AUTHOR("Michael White, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l42.h b/sound/soc/codecs/cs42l42.h new file mode 100644 index 000000000..5a46e7d4f --- /dev/null +++ b/sound/soc/codecs/cs42l42.h @@ -0,0 +1,777 @@ +/* + * cs42l42.h -- CS42L42 ALSA SoC audio driver header + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: James Schulman + * Author: Brian Austin + * Author: Michael White + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS42L42_H__ +#define __CS42L42_H__ + +#define CS42L42_PAGE_REGISTER 0x00 /* Page Select Register */ +#define CS42L42_WIN_START 0x00 +#define CS42L42_WIN_LEN 0x100 +#define CS42L42_RANGE_MIN 0x00 +#define CS42L42_RANGE_MAX 0x7F + +#define CS42L42_PAGE_10 0x1000 +#define CS42L42_PAGE_11 0x1100 +#define CS42L42_PAGE_12 0x1200 +#define CS42L42_PAGE_13 0x1300 +#define CS42L42_PAGE_15 0x1500 +#define CS42L42_PAGE_19 0x1900 +#define CS42L42_PAGE_1B 0x1B00 +#define CS42L42_PAGE_1C 0x1C00 +#define CS42L42_PAGE_1D 0x1D00 +#define CS42L42_PAGE_1F 0x1F00 +#define CS42L42_PAGE_20 0x2000 +#define CS42L42_PAGE_21 0x2100 +#define CS42L42_PAGE_23 0x2300 +#define CS42L42_PAGE_24 0x2400 +#define CS42L42_PAGE_25 0x2500 +#define CS42L42_PAGE_26 0x2600 +#define CS42L42_PAGE_28 0x2800 +#define CS42L42_PAGE_29 0x2900 +#define CS42L42_PAGE_2A 0x2A00 +#define CS42L42_PAGE_30 0x3000 + +#define CS42L42_CHIP_ID 0x42A42 + +/* Page 0x10 Global Registers */ +#define CS42L42_DEVID_AB (CS42L42_PAGE_10 + 0x01) +#define CS42L42_DEVID_CD (CS42L42_PAGE_10 + 0x02) +#define CS42L42_DEVID_E (CS42L42_PAGE_10 + 0x03) +#define CS42L42_FABID (CS42L42_PAGE_10 + 0x04) +#define CS42L42_REVID (CS42L42_PAGE_10 + 0x05) +#define CS42L42_FRZ_CTL (CS42L42_PAGE_10 + 0x06) + +#define CS42L42_SRC_CTL (CS42L42_PAGE_10 + 0x07) +#define CS42L42_SRC_BYPASS_DAC_SHIFT 1 +#define CS42L42_SRC_BYPASS_DAC_MASK (1 << CS42L42_SRC_BYPASS_DAC_SHIFT) + +#define CS42L42_MCLK_STATUS (CS42L42_PAGE_10 + 0x08) + +#define CS42L42_MCLK_CTL (CS42L42_PAGE_10 + 0x09) +#define CS42L42_INTERNAL_FS_SHIFT 1 +#define CS42L42_INTERNAL_FS_MASK (1 << CS42L42_INTERNAL_FS_SHIFT) + +#define CS42L42_SFTRAMP_RATE (CS42L42_PAGE_10 + 0x0A) +#define CS42L42_I2C_DEBOUNCE (CS42L42_PAGE_10 + 0x0E) +#define CS42L42_I2C_STRETCH (CS42L42_PAGE_10 + 0x0F) +#define CS42L42_I2C_TIMEOUT (CS42L42_PAGE_10 + 0x10) + +/* Page 0x11 Power and Headset Detect Registers */ +#define CS42L42_PWR_CTL1 (CS42L42_PAGE_11 + 0x01) +#define CS42L42_ASP_DAO_PDN_SHIFT 7 +#define CS42L42_ASP_DAO_PDN_MASK (1 << CS42L42_ASP_DAO_PDN_SHIFT) +#define CS42L42_ASP_DAI_PDN_SHIFT 6 +#define CS42L42_ASP_DAI_PDN_MASK (1 << CS42L42_ASP_DAI_PDN_SHIFT) +#define CS42L42_MIXER_PDN_SHIFT 5 +#define CS42L42_MIXER_PDN_MASK (1 << CS42L42_MIXER_PDN_SHIFT) +#define CS42L42_EQ_PDN_SHIFT 4 +#define CS42L42_EQ_PDN_MASK (1 << CS42L42_EQ_PDN_SHIFT) +#define CS42L42_HP_PDN_SHIFT 3 +#define CS42L42_HP_PDN_MASK (1 << CS42L42_HP_PDN_SHIFT) +#define CS42L42_ADC_PDN_SHIFT 2 +#define CS42L42_ADC_PDN_MASK (1 << CS42L42_ADC_PDN_SHIFT) +#define CS42L42_PDN_ALL_SHIFT 0 +#define CS42L42_PDN_ALL_MASK (1 << CS42L42_PDN_ALL_SHIFT) + +#define CS42L42_PWR_CTL2 (CS42L42_PAGE_11 + 0x02) +#define CS42L42_ADC_SRC_PDNB_SHIFT 0 +#define CS42L42_ADC_SRC_PDNB_MASK (1 << CS42L42_ADC_SRC_PDNB_SHIFT) +#define CS42L42_DAC_SRC_PDNB_SHIFT 1 +#define CS42L42_DAC_SRC_PDNB_MASK (1 << CS42L42_DAC_SRC_PDNB_SHIFT) +#define CS42L42_ASP_DAI1_PDN_SHIFT 2 +#define CS42L42_ASP_DAI1_PDN_MASK (1 << CS42L42_ASP_DAI1_PDN_SHIFT) +#define CS42L42_SRC_PDN_OVERRIDE_SHIFT 3 +#define CS42L42_SRC_PDN_OVERRIDE_MASK (1 << CS42L42_SRC_PDN_OVERRIDE_SHIFT) +#define CS42L42_DISCHARGE_FILT_SHIFT 4 +#define CS42L42_DISCHARGE_FILT_MASK (1 << CS42L42_DISCHARGE_FILT_SHIFT) + +#define CS42L42_PWR_CTL3 (CS42L42_PAGE_11 + 0x03) +#define CS42L42_RING_SENSE_PDNB_SHIFT 1 +#define CS42L42_RING_SENSE_PDNB_MASK (1 << \ + CS42L42_RING_SENSE_PDNB_SHIFT) +#define CS42L42_VPMON_PDNB_SHIFT 2 +#define CS42L42_VPMON_PDNB_MASK (1 << \ + CS42L42_VPMON_PDNB_SHIFT) +#define CS42L42_SW_CLK_STP_STAT_SEL_SHIFT 5 +#define CS42L42_SW_CLK_STP_STAT_SEL_MASK (3 << \ + CS42L42_SW_CLK_STP_STAT_SEL_SHIFT) + +#define CS42L42_RSENSE_CTL1 (CS42L42_PAGE_11 + 0x04) +#define CS42L42_RS_TRIM_R_SHIFT 0 +#define CS42L42_RS_TRIM_R_MASK (1 << \ + CS42L42_RS_TRIM_R_SHIFT) +#define CS42L42_RS_TRIM_T_SHIFT 1 +#define CS42L42_RS_TRIM_T_MASK (1 << \ + CS42L42_RS_TRIM_T_SHIFT) +#define CS42L42_HPREF_RS_SHIFT 2 +#define CS42L42_HPREF_RS_MASK (1 << \ + CS42L42_HPREF_RS_SHIFT) +#define CS42L42_HSBIAS_FILT_REF_RS_SHIFT 3 +#define CS42L42_HSBIAS_FILT_REF_RS_MASK (1 << \ + CS42L42_HSBIAS_FILT_REF_RS_SHIFT) +#define CS42L42_RING_SENSE_PU_HIZ_SHIFT 6 +#define CS42L42_RING_SENSE_PU_HIZ_MASK (1 << \ + CS42L42_RING_SENSE_PU_HIZ_SHIFT) + +#define CS42L42_RSENSE_CTL2 (CS42L42_PAGE_11 + 0x05) +#define CS42L42_TS_RS_GATE_SHIFT 7 +#define CS42L42_TS_RS_GATE_MAS (1 << CS42L42_TS_RS_GATE_SHIFT) + +#define CS42L42_OSC_SWITCH (CS42L42_PAGE_11 + 0x07) +#define CS42L42_SCLK_PRESENT_SHIFT 0 +#define CS42L42_SCLK_PRESENT_MASK (1 << CS42L42_SCLK_PRESENT_SHIFT) + +#define CS42L42_OSC_SWITCH_STATUS (CS42L42_PAGE_11 + 0x09) +#define CS42L42_OSC_SW_SEL_STAT_SHIFT 0 +#define CS42L42_OSC_SW_SEL_STAT_MASK (3 << CS42L42_OSC_SW_SEL_STAT_SHIFT) +#define CS42L42_OSC_PDNB_STAT_SHIFT 2 +#define CS42L42_OSC_PDNB_STAT_MASK (1 << CS42L42_OSC_SW_SEL_STAT_SHIFT) + +#define CS42L42_RSENSE_CTL3 (CS42L42_PAGE_11 + 0x12) +#define CS42L42_RS_RISE_DBNCE_TIME_SHIFT 0 +#define CS42L42_RS_RISE_DBNCE_TIME_MASK (7 << \ + CS42L42_RS_RISE_DBNCE_TIME_SHIFT) +#define CS42L42_RS_FALL_DBNCE_TIME_SHIFT 3 +#define CS42L42_RS_FALL_DBNCE_TIME_MASK (7 << \ + CS42L42_RS_FALL_DBNCE_TIME_SHIFT) +#define CS42L42_RS_PU_EN_SHIFT 6 +#define CS42L42_RS_PU_EN_MASK (1 << \ + CS42L42_RS_PU_EN_SHIFT) +#define CS42L42_RS_INV_SHIFT 7 +#define CS42L42_RS_INV_MASK (1 << \ + CS42L42_RS_INV_SHIFT) + +#define CS42L42_TSENSE_CTL (CS42L42_PAGE_11 + 0x13) +#define CS42L42_TS_RISE_DBNCE_TIME_SHIFT 0 +#define CS42L42_TS_RISE_DBNCE_TIME_MASK (7 << \ + CS42L42_TS_RISE_DBNCE_TIME_SHIFT) +#define CS42L42_TS_FALL_DBNCE_TIME_SHIFT 3 +#define CS42L42_TS_FALL_DBNCE_TIME_MASK (7 << \ + CS42L42_TS_FALL_DBNCE_TIME_SHIFT) +#define CS42L42_TS_INV_SHIFT 7 +#define CS42L42_TS_INV_MASK (1 << \ + CS42L42_TS_INV_SHIFT) + +#define CS42L42_TSRS_INT_DISABLE (CS42L42_PAGE_11 + 0x14) +#define CS42L42_D_RS_PLUG_DBNC_SHIFT 0 +#define CS42L42_D_RS_PLUG_DBNC_MASK (1 << CS42L42_D_RS_PLUG_DBNC_SHIFT) +#define CS42L42_D_RS_UNPLUG_DBNC_SHIFT 1 +#define CS42L42_D_RS_UNPLUG_DBNC_MASK (1 << CS42L42_D_RS_UNPLUG_DBNC_SHIFT) +#define CS42L42_D_TS_PLUG_DBNC_SHIFT 2 +#define CS42L42_D_TS_PLUG_DBNC_MASK (1 << CS42L42_D_TS_PLUG_DBNC_SHIFT) +#define CS42L42_D_TS_UNPLUG_DBNC_SHIFT 3 +#define CS42L42_D_TS_UNPLUG_DBNC_MASK (1 << CS42L42_D_TS_UNPLUG_DBNC_SHIFT) + +#define CS42L42_TRSENSE_STATUS (CS42L42_PAGE_11 + 0x15) +#define CS42L42_RS_PLUG_DBNC_SHIFT 0 +#define CS42L42_RS_PLUG_DBNC_MASK (1 << CS42L42_RS_PLUG_DBNC_SHIFT) +#define CS42L42_RS_UNPLUG_DBNC_SHIFT 1 +#define CS42L42_RS_UNPLUG_DBNC_MASK (1 << CS42L42_RS_UNPLUG_DBNC_SHIFT) +#define CS42L42_TS_PLUG_DBNC_SHIFT 2 +#define CS42L42_TS_PLUG_DBNC_MASK (1 << CS42L42_TS_PLUG_DBNC_SHIFT) +#define CS42L42_TS_UNPLUG_DBNC_SHIFT 3 +#define CS42L42_TS_UNPLUG_DBNC_MASK (1 << CS42L42_TS_UNPLUG_DBNC_SHIFT) + +#define CS42L42_HSDET_CTL1 (CS42L42_PAGE_11 + 0x1F) +#define CS42L42_HSDET_COMP1_LVL_SHIFT 0 +#define CS42L42_HSDET_COMP1_LVL_MASK (15 << CS42L42_HSDET_COMP1_LVL_SHIFT) +#define CS42L42_HSDET_COMP2_LVL_SHIFT 4 +#define CS42L42_HSDET_COMP2_LVL_MASK (15 << CS42L42_HSDET_COMP2_LVL_SHIFT) + +#define CS42L42_HSDET_CTL2 (CS42L42_PAGE_11 + 0x20) +#define CS42L42_HSDET_AUTO_TIME_SHIFT 0 +#define CS42L42_HSDET_AUTO_TIME_MASK (3 << CS42L42_HSDET_AUTO_TIME_SHIFT) +#define CS42L42_HSBIAS_REF_SHIFT 3 +#define CS42L42_HSBIAS_REF_MASK (1 << CS42L42_HSBIAS_REF_SHIFT) +#define CS42L42_HSDET_SET_SHIFT 4 +#define CS42L42_HSDET_SET_MASK (3 << CS42L42_HSDET_SET_SHIFT) +#define CS42L42_HSDET_CTRL_SHIFT 6 +#define CS42L42_HSDET_CTRL_MASK (3 << CS42L42_HSDET_CTRL_SHIFT) + +#define CS42L42_HS_SWITCH_CTL (CS42L42_PAGE_11 + 0x21) +#define CS42L42_SW_GNDHS_HS4_SHIFT 0 +#define CS42L42_SW_GNDHS_HS4_MASK (1 << CS42L42_SW_GNDHS_HS4_SHIFT) +#define CS42L42_SW_GNDHS_HS3_SHIFT 1 +#define CS42L42_SW_GNDHS_HS3_MASK (1 << CS42L42_SW_GNDHS_HS3_SHIFT) +#define CS42L42_SW_HSB_HS4_SHIFT 2 +#define CS42L42_SW_HSB_HS4_MASK (1 << CS42L42_SW_HSB_HS4_SHIFT) +#define CS42L42_SW_HSB_HS3_SHIFT 3 +#define CS42L42_SW_HSB_HS3_MASK (1 << CS42L42_SW_HSB_HS3_SHIFT) +#define CS42L42_SW_HSB_FILT_HS4_SHIFT 4 +#define CS42L42_SW_HSB_FILT_HS4_MASK (1 << CS42L42_SW_HSB_FILT_HS4_SHIFT) +#define CS42L42_SW_HSB_FILT_HS3_SHIFT 5 +#define CS42L42_SW_HSB_FILT_HS3_MASK (1 << CS42L42_SW_HSB_FILT_HS3_SHIFT) +#define CS42L42_SW_REF_HS4_SHIFT 6 +#define CS42L42_SW_REF_HS4_MASK (1 << CS42L42_SW_REF_HS4_SHIFT) +#define CS42L42_SW_REF_HS3_SHIFT 7 +#define CS42L42_SW_REF_HS3_MASK (1 << CS42L42_SW_REF_HS3_SHIFT) + +#define CS42L42_HS_DET_STATUS (CS42L42_PAGE_11 + 0x24) +#define CS42L42_HSDET_TYPE_SHIFT 0 +#define CS42L42_HSDET_TYPE_MASK (3 << CS42L42_HSDET_TYPE_SHIFT) +#define CS42L42_HSDET_COMP1_OUT_SHIFT 6 +#define CS42L42_HSDET_COMP1_OUT_MASK (1 << CS42L42_HSDET_COMP1_OUT_SHIFT) +#define CS42L42_HSDET_COMP2_OUT_SHIFT 7 +#define CS42L42_HSDET_COMP2_OUT_MASK (1 << CS42L42_HSDET_COMP2_OUT_SHIFT) +#define CS42L42_PLUG_CTIA 0 +#define CS42L42_PLUG_OMTP 1 +#define CS42L42_PLUG_HEADPHONE 2 +#define CS42L42_PLUG_INVALID 3 + +#define CS42L42_HS_CLAMP_DISABLE (CS42L42_PAGE_11 + 0x29) +#define CS42L42_HS_CLAMP_DISABLE_SHIFT 0 +#define CS42L42_HS_CLAMP_DISABLE_MASK (1 << CS42L42_HS_CLAMP_DISABLE_SHIFT) + +/* Page 0x12 Clocking Registers */ +#define CS42L42_MCLK_SRC_SEL (CS42L42_PAGE_12 + 0x01) +#define CS42L42_MCLKDIV_SHIFT 1 +#define CS42L42_MCLKDIV_MASK (1 << CS42L42_MCLKDIV_SHIFT) +#define CS42L42_MCLK_SRC_SEL_SHIFT 0 +#define CS42L42_MCLK_SRC_SEL_MASK (1 << CS42L42_MCLK_SRC_SEL_SHIFT) + +#define CS42L42_SPDIF_CLK_CFG (CS42L42_PAGE_12 + 0x02) +#define CS42L42_FSYNC_PW_LOWER (CS42L42_PAGE_12 + 0x03) + +#define CS42L42_FSYNC_PW_UPPER (CS42L42_PAGE_12 + 0x04) +#define CS42L42_FSYNC_PULSE_WIDTH_SHIFT 0 +#define CS42L42_FSYNC_PULSE_WIDTH_MASK (0xff << \ + CS42L42_FSYNC_PULSE_WIDTH_SHIFT) + +#define CS42L42_FSYNC_P_LOWER (CS42L42_PAGE_12 + 0x05) + +#define CS42L42_FSYNC_P_UPPER (CS42L42_PAGE_12 + 0x06) +#define CS42L42_FSYNC_PERIOD_SHIFT 0 +#define CS42L42_FSYNC_PERIOD_MASK (0xff << CS42L42_FSYNC_PERIOD_SHIFT) + +#define CS42L42_ASP_CLK_CFG (CS42L42_PAGE_12 + 0x07) +#define CS42L42_ASP_SCLK_EN_SHIFT 5 +#define CS42L42_ASP_SCLK_EN_MASK (1 << CS42L42_ASP_SCLK_EN_SHIFT) +#define CS42L42_ASP_MASTER_MODE 0x01 +#define CS42L42_ASP_SLAVE_MODE 0x00 +#define CS42L42_ASP_MODE_SHIFT 4 +#define CS42L42_ASP_MODE_MASK (1 << CS42L42_ASP_MODE_SHIFT) +#define CS42L42_ASP_SCPOL_SHIFT 2 +#define CS42L42_ASP_SCPOL_MASK (3 << CS42L42_ASP_SCPOL_SHIFT) +#define CS42L42_ASP_SCPOL_NOR 3 +#define CS42L42_ASP_LCPOL_SHIFT 0 +#define CS42L42_ASP_LCPOL_MASK (3 << CS42L42_ASP_LCPOL_SHIFT) +#define CS42L42_ASP_LCPOL_INV 3 + +#define CS42L42_ASP_FRM_CFG (CS42L42_PAGE_12 + 0x08) +#define CS42L42_ASP_STP_SHIFT 4 +#define CS42L42_ASP_STP_MASK (1 << CS42L42_ASP_STP_SHIFT) +#define CS42L42_ASP_5050_SHIFT 3 +#define CS42L42_ASP_5050_MASK (1 << CS42L42_ASP_5050_SHIFT) +#define CS42L42_ASP_FSD_SHIFT 0 +#define CS42L42_ASP_FSD_MASK (7 << CS42L42_ASP_FSD_SHIFT) +#define CS42L42_ASP_FSD_0_5 1 +#define CS42L42_ASP_FSD_1_0 2 +#define CS42L42_ASP_FSD_1_5 3 +#define CS42L42_ASP_FSD_2_0 4 + +#define CS42L42_FS_RATE_EN (CS42L42_PAGE_12 + 0x09) +#define CS42L42_FS_EN_SHIFT 0 +#define CS42L42_FS_EN_MASK (0xf << CS42L42_FS_EN_SHIFT) +#define CS42L42_FS_EN_IASRC_96K 0x1 +#define CS42L42_FS_EN_OASRC_96K 0x2 + +#define CS42L42_IN_ASRC_CLK (CS42L42_PAGE_12 + 0x0A) +#define CS42L42_CLK_IASRC_SEL_SHIFT 0 +#define CS42L42_CLK_IASRC_SEL_MASK (1 << CS42L42_CLK_IASRC_SEL_SHIFT) +#define CS42L42_CLK_IASRC_SEL_12 1 + +#define CS42L42_OUT_ASRC_CLK (CS42L42_PAGE_12 + 0x0B) +#define CS42L42_CLK_OASRC_SEL_SHIFT 0 +#define CS42L42_CLK_OASRC_SEL_MASK (1 << CS42L42_CLK_OASRC_SEL_SHIFT) +#define CS42L42_CLK_OASRC_SEL_12 1 + +#define CS42L42_PLL_DIV_CFG1 (CS42L42_PAGE_12 + 0x0C) +#define CS42L42_SCLK_PREDIV_SHIFT 0 +#define CS42L42_SCLK_PREDIV_MASK (3 << CS42L42_SCLK_PREDIV_SHIFT) + +/* Page 0x13 Interrupt Registers */ +/* Interrupts */ +#define CS42L42_ADC_OVFL_STATUS (CS42L42_PAGE_13 + 0x01) +#define CS42L42_MIXER_STATUS (CS42L42_PAGE_13 + 0x02) +#define CS42L42_SRC_STATUS (CS42L42_PAGE_13 + 0x03) +#define CS42L42_ASP_RX_STATUS (CS42L42_PAGE_13 + 0x04) +#define CS42L42_ASP_TX_STATUS (CS42L42_PAGE_13 + 0x05) +#define CS42L42_CODEC_STATUS (CS42L42_PAGE_13 + 0x08) +#define CS42L42_DET_INT_STATUS1 (CS42L42_PAGE_13 + 0x09) +#define CS42L42_DET_INT_STATUS2 (CS42L42_PAGE_13 + 0x0A) +#define CS42L42_SRCPL_INT_STATUS (CS42L42_PAGE_13 + 0x0B) +#define CS42L42_VPMON_STATUS (CS42L42_PAGE_13 + 0x0D) +#define CS42L42_PLL_LOCK_STATUS (CS42L42_PAGE_13 + 0x0E) +#define CS42L42_TSRS_PLUG_STATUS (CS42L42_PAGE_13 + 0x0F) +/* Masks */ +#define CS42L42_ADC_OVFL_INT_MASK (CS42L42_PAGE_13 + 0x16) +#define CS42L42_ADC_OVFL_SHIFT 0 +#define CS42L42_ADC_OVFL_MASK (1 << CS42L42_ADC_OVFL_SHIFT) +#define CS42L42_ADC_OVFL_VAL_MASK CS42L42_ADC_OVFL_MASK + +#define CS42L42_MIXER_INT_MASK (CS42L42_PAGE_13 + 0x17) +#define CS42L42_MIX_CHB_OVFL_SHIFT 0 +#define CS42L42_MIX_CHB_OVFL_MASK (1 << CS42L42_MIX_CHB_OVFL_SHIFT) +#define CS42L42_MIX_CHA_OVFL_SHIFT 1 +#define CS42L42_MIX_CHA_OVFL_MASK (1 << CS42L42_MIX_CHA_OVFL_SHIFT) +#define CS42L42_EQ_OVFL_SHIFT 2 +#define CS42L42_EQ_OVFL_MASK (1 << CS42L42_EQ_OVFL_SHIFT) +#define CS42L42_EQ_BIQUAD_OVFL_SHIFT 3 +#define CS42L42_EQ_BIQUAD_OVFL_MASK (1 << CS42L42_EQ_BIQUAD_OVFL_SHIFT) +#define CS42L42_MIXER_VAL_MASK (CS42L42_MIX_CHB_OVFL_MASK | \ + CS42L42_MIX_CHA_OVFL_MASK | \ + CS42L42_EQ_OVFL_MASK | \ + CS42L42_EQ_BIQUAD_OVFL_MASK) + +#define CS42L42_SRC_INT_MASK (CS42L42_PAGE_13 + 0x18) +#define CS42L42_SRC_ILK_SHIFT 0 +#define CS42L42_SRC_ILK_MASK (1 << CS42L42_SRC_ILK_SHIFT) +#define CS42L42_SRC_OLK_SHIFT 1 +#define CS42L42_SRC_OLK_MASK (1 << CS42L42_SRC_OLK_SHIFT) +#define CS42L42_SRC_IUNLK_SHIFT 2 +#define CS42L42_SRC_IUNLK_MASK (1 << CS42L42_SRC_IUNLK_SHIFT) +#define CS42L42_SRC_OUNLK_SHIFT 3 +#define CS42L42_SRC_OUNLK_MASK (1 << CS42L42_SRC_OUNLK_SHIFT) +#define CS42L42_SRC_VAL_MASK (CS42L42_SRC_ILK_MASK | \ + CS42L42_SRC_OLK_MASK | \ + CS42L42_SRC_IUNLK_MASK | \ + CS42L42_SRC_OUNLK_MASK) + +#define CS42L42_ASP_RX_INT_MASK (CS42L42_PAGE_13 + 0x19) +#define CS42L42_ASPRX_NOLRCK_SHIFT 0 +#define CS42L42_ASPRX_NOLRCK_MASK (1 << CS42L42_ASPRX_NOLRCK_SHIFT) +#define CS42L42_ASPRX_EARLY_SHIFT 1 +#define CS42L42_ASPRX_EARLY_MASK (1 << CS42L42_ASPRX_EARLY_SHIFT) +#define CS42L42_ASPRX_LATE_SHIFT 2 +#define CS42L42_ASPRX_LATE_MASK (1 << CS42L42_ASPRX_LATE_SHIFT) +#define CS42L42_ASPRX_ERROR_SHIFT 3 +#define CS42L42_ASPRX_ERROR_MASK (1 << CS42L42_ASPRX_ERROR_SHIFT) +#define CS42L42_ASPRX_OVLD_SHIFT 4 +#define CS42L42_ASPRX_OVLD_MASK (1 << CS42L42_ASPRX_OVLD_SHIFT) +#define CS42L42_ASP_RX_VAL_MASK (CS42L42_ASPRX_NOLRCK_MASK | \ + CS42L42_ASPRX_EARLY_MASK | \ + CS42L42_ASPRX_LATE_MASK | \ + CS42L42_ASPRX_ERROR_MASK | \ + CS42L42_ASPRX_OVLD_MASK) + +#define CS42L42_ASP_TX_INT_MASK (CS42L42_PAGE_13 + 0x1A) +#define CS42L42_ASPTX_NOLRCK_SHIFT 0 +#define CS42L42_ASPTX_NOLRCK_MASK (1 << CS42L42_ASPTX_NOLRCK_SHIFT) +#define CS42L42_ASPTX_EARLY_SHIFT 1 +#define CS42L42_ASPTX_EARLY_MASK (1 << CS42L42_ASPTX_EARLY_SHIFT) +#define CS42L42_ASPTX_LATE_SHIFT 2 +#define CS42L42_ASPTX_LATE_MASK (1 << CS42L42_ASPTX_LATE_SHIFT) +#define CS42L42_ASPTX_SMERROR_SHIFT 3 +#define CS42L42_ASPTX_SMERROR_MASK (1 << CS42L42_ASPTX_SMERROR_SHIFT) +#define CS42L42_ASP_TX_VAL_MASK (CS42L42_ASPTX_NOLRCK_MASK | \ + CS42L42_ASPTX_EARLY_MASK | \ + CS42L42_ASPTX_LATE_MASK | \ + CS42L42_ASPTX_SMERROR_MASK) + +#define CS42L42_CODEC_INT_MASK (CS42L42_PAGE_13 + 0x1B) +#define CS42L42_PDN_DONE_SHIFT 0 +#define CS42L42_PDN_DONE_MASK (1 << CS42L42_PDN_DONE_SHIFT) +#define CS42L42_HSDET_AUTO_DONE_SHIFT 1 +#define CS42L42_HSDET_AUTO_DONE_MASK (1 << CS42L42_HSDET_AUTO_DONE_SHIFT) +#define CS42L42_CODEC_VAL_MASK (CS42L42_PDN_DONE_MASK | \ + CS42L42_HSDET_AUTO_DONE_MASK) + +#define CS42L42_SRCPL_INT_MASK (CS42L42_PAGE_13 + 0x1C) +#define CS42L42_SRCPL_ADC_LK_SHIFT 0 +#define CS42L42_SRCPL_ADC_LK_MASK (1 << CS42L42_SRCPL_ADC_LK_SHIFT) +#define CS42L42_SRCPL_DAC_LK_SHIFT 2 +#define CS42L42_SRCPL_DAC_LK_MASK (1 << CS42L42_SRCPL_DAC_LK_SHIFT) +#define CS42L42_SRCPL_ADC_UNLK_SHIFT 5 +#define CS42L42_SRCPL_ADC_UNLK_MASK (1 << CS42L42_SRCPL_ADC_UNLK_SHIFT) +#define CS42L42_SRCPL_DAC_UNLK_SHIFT 6 +#define CS42L42_SRCPL_DAC_UNLK_MASK (1 << CS42L42_SRCPL_DAC_UNLK_SHIFT) +#define CS42L42_SRCPL_VAL_MASK (CS42L42_SRCPL_ADC_LK_MASK | \ + CS42L42_SRCPL_DAC_LK_MASK | \ + CS42L42_SRCPL_ADC_UNLK_MASK | \ + CS42L42_SRCPL_DAC_UNLK_MASK) + +#define CS42L42_VPMON_INT_MASK (CS42L42_PAGE_13 + 0x1E) +#define CS42L42_VPMON_SHIFT 0 +#define CS42L42_VPMON_MASK (1 << CS42L42_VPMON_SHIFT) +#define CS42L42_VPMON_VAL_MASK CS42L42_VPMON_MASK + +#define CS42L42_PLL_LOCK_INT_MASK (CS42L42_PAGE_13 + 0x1F) +#define CS42L42_PLL_LOCK_SHIFT 0 +#define CS42L42_PLL_LOCK_MASK (1 << CS42L42_PLL_LOCK_SHIFT) +#define CS42L42_PLL_LOCK_VAL_MASK CS42L42_PLL_LOCK_MASK + +#define CS42L42_TSRS_PLUG_INT_MASK (CS42L42_PAGE_13 + 0x20) +#define CS42L42_RS_PLUG_SHIFT 0 +#define CS42L42_RS_PLUG_MASK (1 << CS42L42_RS_PLUG_SHIFT) +#define CS42L42_RS_UNPLUG_SHIFT 1 +#define CS42L42_RS_UNPLUG_MASK (1 << CS42L42_RS_UNPLUG_SHIFT) +#define CS42L42_TS_PLUG_SHIFT 2 +#define CS42L42_TS_PLUG_MASK (1 << CS42L42_TS_PLUG_SHIFT) +#define CS42L42_TS_UNPLUG_SHIFT 3 +#define CS42L42_TS_UNPLUG_MASK (1 << CS42L42_TS_UNPLUG_SHIFT) +#define CS42L42_TSRS_PLUG_VAL_MASK (CS42L42_RS_PLUG_MASK | \ + CS42L42_RS_UNPLUG_MASK | \ + CS42L42_TS_PLUG_MASK | \ + CS42L42_TS_UNPLUG_MASK) +#define CS42L42_TS_PLUG 3 +#define CS42L42_TS_UNPLUG 0 +#define CS42L42_TS_TRANS 1 + +/* Page 0x15 Fractional-N PLL Registers */ +#define CS42L42_PLL_CTL1 (CS42L42_PAGE_15 + 0x01) +#define CS42L42_PLL_START_SHIFT 0 +#define CS42L42_PLL_START_MASK (1 << CS42L42_PLL_START_SHIFT) + +#define CS42L42_PLL_DIV_FRAC0 (CS42L42_PAGE_15 + 0x02) +#define CS42L42_PLL_DIV_FRAC_SHIFT 0 +#define CS42L42_PLL_DIV_FRAC_MASK (0xff << CS42L42_PLL_DIV_FRAC_SHIFT) + +#define CS42L42_PLL_DIV_FRAC1 (CS42L42_PAGE_15 + 0x03) +#define CS42L42_PLL_DIV_FRAC2 (CS42L42_PAGE_15 + 0x04) + +#define CS42L42_PLL_DIV_INT (CS42L42_PAGE_15 + 0x05) +#define CS42L42_PLL_DIV_INT_SHIFT 0 +#define CS42L42_PLL_DIV_INT_MASK (0xff << CS42L42_PLL_DIV_INT_SHIFT) + +#define CS42L42_PLL_CTL3 (CS42L42_PAGE_15 + 0x08) +#define CS42L42_PLL_DIVOUT_SHIFT 0 +#define CS42L42_PLL_DIVOUT_MASK (0xff << CS42L42_PLL_DIVOUT_SHIFT) + +#define CS42L42_PLL_CAL_RATIO (CS42L42_PAGE_15 + 0x0A) +#define CS42L42_PLL_CAL_RATIO_SHIFT 0 +#define CS42L42_PLL_CAL_RATIO_MASK (0xff << CS42L42_PLL_CAL_RATIO_SHIFT) + +#define CS42L42_PLL_CTL4 (CS42L42_PAGE_15 + 0x1B) +#define CS42L42_PLL_MODE_SHIFT 0 +#define CS42L42_PLL_MODE_MASK (3 << CS42L42_PLL_MODE_SHIFT) + +/* Page 0x19 HP Load Detect Registers */ +#define CS42L42_LOAD_DET_RCSTAT (CS42L42_PAGE_19 + 0x25) +#define CS42L42_RLA_STAT_SHIFT 0 +#define CS42L42_RLA_STAT_MASK (3 << CS42L42_RLA_STAT_SHIFT) +#define CS42L42_RLA_STAT_15_OHM 0 + +#define CS42L42_LOAD_DET_DONE (CS42L42_PAGE_19 + 0x26) +#define CS42L42_HPLOAD_DET_DONE_SHIFT 0 +#define CS42L42_HPLOAD_DET_DONE_MASK (1 << CS42L42_HPLOAD_DET_DONE_SHIFT) + +#define CS42L42_LOAD_DET_EN (CS42L42_PAGE_19 + 0x27) +#define CS42L42_HP_LD_EN_SHIFT 0 +#define CS42L42_HP_LD_EN_MASK (1 << CS42L42_HP_LD_EN_SHIFT) + +/* Page 0x1B Headset Interface Registers */ +#define CS42L42_HSBIAS_SC_AUTOCTL (CS42L42_PAGE_1B + 0x70) +#define CS42L42_HSBIAS_SENSE_TRIP_SHIFT 0 +#define CS42L42_HSBIAS_SENSE_TRIP_MASK (7 << \ + CS42L42_HSBIAS_SENSE_TRIP_SHIFT) +#define CS42L42_TIP_SENSE_EN_SHIFT 5 +#define CS42L42_TIP_SENSE_EN_MASK (1 << \ + CS42L42_TIP_SENSE_EN_SHIFT) +#define CS42L42_AUTO_HSBIAS_HIZ_SHIFT 6 +#define CS42L42_AUTO_HSBIAS_HIZ_MASK (1 << \ + CS42L42_AUTO_HSBIAS_HIZ_SHIFT) +#define CS42L42_HSBIAS_SENSE_EN_SHIFT 7 +#define CS42L42_HSBIAS_SENSE_EN_MASK (1 << \ + CS42L42_HSBIAS_SENSE_EN_SHIFT) + +#define CS42L42_WAKE_CTL (CS42L42_PAGE_1B + 0x71) +#define CS42L42_WAKEB_CLEAR_SHIFT 0 +#define CS42L42_WAKEB_CLEAR_MASK (1 << CS42L42_WAKEB_CLEAR_SHIFT) +#define CS42L42_WAKEB_MODE_SHIFT 5 +#define CS42L42_WAKEB_MODE_MASK (1 << CS42L42_WAKEB_MODE_SHIFT) +#define CS42L42_M_HP_WAKE_SHIFT 6 +#define CS42L42_M_HP_WAKE_MASK (1 << CS42L42_M_HP_WAKE_SHIFT) +#define CS42L42_M_MIC_WAKE_SHIFT 7 +#define CS42L42_M_MIC_WAKE_MASK (1 << CS42L42_M_MIC_WAKE_SHIFT) + +#define CS42L42_ADC_DISABLE_MUTE (CS42L42_PAGE_1B + 0x72) +#define CS42L42_ADC_DISABLE_S0_MUTE_SHIFT 7 +#define CS42L42_ADC_DISABLE_S0_MUTE_MASK (1 << \ + CS42L42_ADC_DISABLE_S0_MUTE_SHIFT) + +#define CS42L42_TIPSENSE_CTL (CS42L42_PAGE_1B + 0x73) +#define CS42L42_TIP_SENSE_DEBOUNCE_SHIFT 0 +#define CS42L42_TIP_SENSE_DEBOUNCE_MASK (3 << \ + CS42L42_TIP_SENSE_DEBOUNCE_SHIFT) +#define CS42L42_TIP_SENSE_INV_SHIFT 5 +#define CS42L42_TIP_SENSE_INV_MASK (1 << \ + CS42L42_TIP_SENSE_INV_SHIFT) +#define CS42L42_TIP_SENSE_CTRL_SHIFT 6 +#define CS42L42_TIP_SENSE_CTRL_MASK (3 << \ + CS42L42_TIP_SENSE_CTRL_SHIFT) + +#define CS42L42_MISC_DET_CTL (CS42L42_PAGE_1B + 0x74) +#define CS42L42_PDN_MIC_LVL_DET_SHIFT 0 +#define CS42L42_PDN_MIC_LVL_DET_MASK (1 << CS42L42_PDN_MIC_LVL_DET_SHIFT) +#define CS42L42_HSBIAS_CTL_SHIFT 1 +#define CS42L42_HSBIAS_CTL_MASK (3 << CS42L42_HSBIAS_CTL_SHIFT) +#define CS42L42_DETECT_MODE_SHIFT 3 +#define CS42L42_DETECT_MODE_MASK (3 << CS42L42_DETECT_MODE_SHIFT) + +#define CS42L42_MIC_DET_CTL1 (CS42L42_PAGE_1B + 0x75) +#define CS42L42_HS_DET_LEVEL_SHIFT 0 +#define CS42L42_HS_DET_LEVEL_MASK (0x3F << CS42L42_HS_DET_LEVEL_SHIFT) +#define CS42L42_EVENT_STAT_SEL_SHIFT 6 +#define CS42L42_EVENT_STAT_SEL_MASK (1 << CS42L42_EVENT_STAT_SEL_SHIFT) +#define CS42L42_LATCH_TO_VP_SHIFT 7 +#define CS42L42_LATCH_TO_VP_MASK (1 << CS42L42_LATCH_TO_VP_SHIFT) + +#define CS42L42_MIC_DET_CTL2 (CS42L42_PAGE_1B + 0x76) +#define CS42L42_DEBOUNCE_TIME_SHIFT 5 +#define CS42L42_DEBOUNCE_TIME_MASK (0x07 << CS42L42_DEBOUNCE_TIME_SHIFT) + +#define CS42L42_DET_STATUS1 (CS42L42_PAGE_1B + 0x77) +#define CS42L42_HSBIAS_HIZ_MODE_SHIFT 6 +#define CS42L42_HSBIAS_HIZ_MODE_MASK (1 << CS42L42_HSBIAS_HIZ_MODE_SHIFT) +#define CS42L42_TIP_SENSE_SHIFT 7 +#define CS42L42_TIP_SENSE_MASK (1 << CS42L42_TIP_SENSE_SHIFT) + +#define CS42L42_DET_STATUS2 (CS42L42_PAGE_1B + 0x78) +#define CS42L42_SHORT_TRUE_SHIFT 0 +#define CS42L42_SHORT_TRUE_MASK (1 << CS42L42_SHORT_TRUE_SHIFT) +#define CS42L42_HS_TRUE_SHIFT 1 +#define CS42L42_HS_TRUE_MASK (1 << CS42L42_HS_TRUE_SHIFT) + +#define CS42L42_DET_INT1_MASK (CS42L42_PAGE_1B + 0x79) +#define CS42L42_TIP_SENSE_UNPLUG_SHIFT 5 +#define CS42L42_TIP_SENSE_UNPLUG_MASK (1 << CS42L42_TIP_SENSE_UNPLUG_SHIFT) +#define CS42L42_TIP_SENSE_PLUG_SHIFT 6 +#define CS42L42_TIP_SENSE_PLUG_MASK (1 << CS42L42_TIP_SENSE_PLUG_SHIFT) +#define CS42L42_HSBIAS_SENSE_SHIFT 7 +#define CS42L42_HSBIAS_SENSE_MASK (1 << CS42L42_HSBIAS_SENSE_SHIFT) +#define CS42L42_DET_INT_VAL1_MASK (CS42L42_TIP_SENSE_UNPLUG_MASK | \ + CS42L42_TIP_SENSE_PLUG_MASK | \ + CS42L42_HSBIAS_SENSE_MASK) + +#define CS42L42_DET_INT2_MASK (CS42L42_PAGE_1B + 0x7A) +#define CS42L42_M_SHORT_DET_SHIFT 0 +#define CS42L42_M_SHORT_DET_MASK (1 << \ + CS42L42_M_SHORT_DET_SHIFT) +#define CS42L42_M_SHORT_RLS_SHIFT 1 +#define CS42L42_M_SHORT_RLS_MASK (1 << \ + CS42L42_M_SHORT_RLS_SHIFT) +#define CS42L42_M_HSBIAS_HIZ_SHIFT 2 +#define CS42L42_M_HSBIAS_HIZ_MASK (1 << \ + CS42L42_M_HSBIAS_HIZ_SHIFT) +#define CS42L42_M_DETECT_FT_SHIFT 6 +#define CS42L42_M_DETECT_FT_MASK (1 << \ + CS42L42_M_DETECT_FT_SHIFT) +#define CS42L42_M_DETECT_TF_SHIFT 7 +#define CS42L42_M_DETECT_TF_MASK (1 << \ + CS42L42_M_DETECT_TF_SHIFT) +#define CS42L42_DET_INT_VAL2_MASK (CS42L42_M_SHORT_DET_MASK | \ + CS42L42_M_SHORT_RLS_MASK | \ + CS42L42_M_HSBIAS_HIZ_MASK | \ + CS42L42_M_DETECT_FT_MASK | \ + CS42L42_M_DETECT_TF_MASK) + +/* Page 0x1C Headset Bias Registers */ +#define CS42L42_HS_BIAS_CTL (CS42L42_PAGE_1C + 0x03) +#define CS42L42_HSBIAS_RAMP_SHIFT 0 +#define CS42L42_HSBIAS_RAMP_MASK (3 << CS42L42_HSBIAS_RAMP_SHIFT) +#define CS42L42_HSBIAS_PD_SHIFT 4 +#define CS42L42_HSBIAS_PD_MASK (1 << CS42L42_HSBIAS_PD_SHIFT) +#define CS42L42_HSBIAS_CAPLESS_SHIFT 7 +#define CS42L42_HSBIAS_CAPLESS_MASK (1 << CS42L42_HSBIAS_CAPLESS_SHIFT) + +/* Page 0x1D ADC Registers */ +#define CS42L42_ADC_CTL (CS42L42_PAGE_1D + 0x01) +#define CS42L42_ADC_NOTCH_DIS_SHIFT 5 +#define CS42L42_ADC_FORCE_WEAK_VCM_SHIFT 4 +#define CS42L42_ADC_INV_SHIFT 2 +#define CS42L42_ADC_DIG_BOOST_SHIFT 0 + +#define CS42L42_ADC_VOLUME (CS42L42_PAGE_1D + 0x03) +#define CS42L42_ADC_VOL_SHIFT 0 + +#define CS42L42_ADC_WNF_HPF_CTL (CS42L42_PAGE_1D + 0x04) +#define CS42L42_ADC_WNF_CF_SHIFT 4 +#define CS42L42_ADC_WNF_EN_SHIFT 3 +#define CS42L42_ADC_HPF_CF_SHIFT 1 +#define CS42L42_ADC_HPF_EN_SHIFT 0 + +/* Page 0x1F DAC Registers */ +#define CS42L42_DAC_CTL1 (CS42L42_PAGE_1F + 0x01) +#define CS42L42_DACB_INV_SHIFT 1 +#define CS42L42_DACA_INV_SHIFT 0 + +#define CS42L42_DAC_CTL2 (CS42L42_PAGE_1F + 0x06) +#define CS42L42_HPOUT_PULLDOWN_SHIFT 4 +#define CS42L42_HPOUT_PULLDOWN_MASK (15 << CS42L42_HPOUT_PULLDOWN_SHIFT) +#define CS42L42_HPOUT_LOAD_SHIFT 3 +#define CS42L42_HPOUT_LOAD_MASK (1 << CS42L42_HPOUT_LOAD_SHIFT) +#define CS42L42_HPOUT_CLAMP_SHIFT 2 +#define CS42L42_HPOUT_CLAMP_MASK (1 << CS42L42_HPOUT_CLAMP_SHIFT) +#define CS42L42_DAC_HPF_EN_SHIFT 1 +#define CS42L42_DAC_HPF_EN_MASK (1 << CS42L42_DAC_HPF_EN_SHIFT) +#define CS42L42_DAC_MON_EN_SHIFT 0 +#define CS42L42_DAC_MON_EN_MASK (1 << CS42L42_DAC_MON_EN_SHIFT) + +/* Page 0x20 HP CTL Registers */ +#define CS42L42_HP_CTL (CS42L42_PAGE_20 + 0x01) +#define CS42L42_HP_ANA_BMUTE_SHIFT 3 +#define CS42L42_HP_ANA_BMUTE_MASK (1 << CS42L42_HP_ANA_BMUTE_SHIFT) +#define CS42L42_HP_ANA_AMUTE_SHIFT 2 +#define CS42L42_HP_ANA_AMUTE_MASK (1 << CS42L42_HP_ANA_AMUTE_SHIFT) +#define CS42L42_HP_FULL_SCALE_VOL_SHIFT 1 +#define CS42L42_HP_FULL_SCALE_VOL_MASK (1 << CS42L42_HP_FULL_SCALE_VOL_SHIFT) + +/* Page 0x21 Class H Registers */ +#define CS42L42_CLASSH_CTL (CS42L42_PAGE_21 + 0x01) + +/* Page 0x23 Mixer Volume Registers */ +#define CS42L42_MIXER_CHA_VOL (CS42L42_PAGE_23 + 0x01) +#define CS42L42_MIXER_ADC_VOL (CS42L42_PAGE_23 + 0x02) + +#define CS42L42_MIXER_CHB_VOL (CS42L42_PAGE_23 + 0x03) +#define CS42L42_MIXER_CH_VOL_SHIFT 0 +#define CS42L42_MIXER_CH_VOL_MASK (0x3f << CS42L42_MIXER_CH_VOL_SHIFT) + +/* Page 0x24 EQ Registers */ +#define CS42L42_EQ_COEF_IN0 (CS42L42_PAGE_24 + 0x01) +#define CS42L42_EQ_COEF_IN1 (CS42L42_PAGE_24 + 0x02) +#define CS42L42_EQ_COEF_IN2 (CS42L42_PAGE_24 + 0x03) +#define CS42L42_EQ_COEF_IN3 (CS42L42_PAGE_24 + 0x04) +#define CS42L42_EQ_COEF_RW (CS42L42_PAGE_24 + 0x06) +#define CS42L42_EQ_COEF_OUT0 (CS42L42_PAGE_24 + 0x07) +#define CS42L42_EQ_COEF_OUT1 (CS42L42_PAGE_24 + 0x08) +#define CS42L42_EQ_COEF_OUT2 (CS42L42_PAGE_24 + 0x09) +#define CS42L42_EQ_COEF_OUT3 (CS42L42_PAGE_24 + 0x0A) +#define CS42L42_EQ_INIT_STAT (CS42L42_PAGE_24 + 0x0B) +#define CS42L42_EQ_START_FILT (CS42L42_PAGE_24 + 0x0C) +#define CS42L42_EQ_MUTE_CTL (CS42L42_PAGE_24 + 0x0E) + +/* Page 0x25 Audio Port Registers */ +#define CS42L42_SP_RX_CH_SEL (CS42L42_PAGE_25 + 0x01) + +#define CS42L42_SP_RX_ISOC_CTL (CS42L42_PAGE_25 + 0x02) +#define CS42L42_SP_RX_RSYNC_SHIFT 6 +#define CS42L42_SP_RX_RSYNC_MASK (1 << CS42L42_SP_RX_RSYNC_SHIFT) +#define CS42L42_SP_RX_NSB_POS_SHIFT 3 +#define CS42L42_SP_RX_NSB_POS_MASK (7 << CS42L42_SP_RX_NSB_POS_SHIFT) +#define CS42L42_SP_RX_NFS_NSBB_SHIFT 2 +#define CS42L42_SP_RX_NFS_NSBB_MASK (1 << CS42L42_SP_RX_NFS_NSBB_SHIFT) +#define CS42L42_SP_RX_ISOC_MODE_SHIFT 0 +#define CS42L42_SP_RX_ISOC_MODE_MASK (3 << CS42L42_SP_RX_ISOC_MODE_SHIFT) + +#define CS42L42_SP_RX_FS (CS42L42_PAGE_25 + 0x03) +#define CS42l42_SPDIF_CH_SEL (CS42L42_PAGE_25 + 0x04) +#define CS42L42_SP_TX_ISOC_CTL (CS42L42_PAGE_25 + 0x05) +#define CS42L42_SP_TX_FS (CS42L42_PAGE_25 + 0x06) +#define CS42L42_SPDIF_SW_CTL1 (CS42L42_PAGE_25 + 0x07) + +/* Page 0x26 SRC Registers */ +#define CS42L42_SRC_SDIN_FS (CS42L42_PAGE_26 + 0x01) +#define CS42L42_SRC_SDIN_FS_SHIFT 0 +#define CS42L42_SRC_SDIN_FS_MASK (0x1f << CS42L42_SRC_SDIN_FS_SHIFT) + +#define CS42L42_SRC_SDOUT_FS (CS42L42_PAGE_26 + 0x09) + +/* Page 0x28 S/PDIF Registers */ +#define CS42L42_SPDIF_CTL1 (CS42L42_PAGE_28 + 0x01) +#define CS42L42_SPDIF_CTL2 (CS42L42_PAGE_28 + 0x02) +#define CS42L42_SPDIF_CTL3 (CS42L42_PAGE_28 + 0x03) +#define CS42L42_SPDIF_CTL4 (CS42L42_PAGE_28 + 0x04) + +/* Page 0x29 Serial Port TX Registers */ +#define CS42L42_ASP_TX_SZ_EN (CS42L42_PAGE_29 + 0x01) +#define CS42L42_ASP_TX_CH_EN (CS42L42_PAGE_29 + 0x02) +#define CS42L42_ASP_TX_CH_AP_RES (CS42L42_PAGE_29 + 0x03) +#define CS42L42_ASP_TX_CH1_BIT_MSB (CS42L42_PAGE_29 + 0x04) +#define CS42L42_ASP_TX_CH1_BIT_LSB (CS42L42_PAGE_29 + 0x05) +#define CS42L42_ASP_TX_HIZ_DLY_CFG (CS42L42_PAGE_29 + 0x06) +#define CS42L42_ASP_TX_CH2_BIT_MSB (CS42L42_PAGE_29 + 0x0A) +#define CS42L42_ASP_TX_CH2_BIT_LSB (CS42L42_PAGE_29 + 0x0B) + +/* Page 0x2A Serial Port RX Registers */ +#define CS42L42_ASP_RX_DAI0_EN (CS42L42_PAGE_2A + 0x01) +#define CS42L42_ASP_RX0_CH_EN_SHIFT 2 +#define CS42L42_ASP_RX0_CH_EN_MASK (0xf << CS42L42_ASP_RX0_CH_EN_SHIFT) +#define CS42L42_ASP_RX0_CH1_EN 1 +#define CS42L42_ASP_RX0_CH2_EN 2 +#define CS42L42_ASP_RX0_CH3_EN 4 +#define CS42L42_ASP_RX0_CH4_EN 8 + +#define CS42L42_ASP_RX_DAI0_CH1_AP_RES (CS42L42_PAGE_2A + 0x02) +#define CS42L42_ASP_RX_DAI0_CH1_BIT_MSB (CS42L42_PAGE_2A + 0x03) +#define CS42L42_ASP_RX_DAI0_CH1_BIT_LSB (CS42L42_PAGE_2A + 0x04) +#define CS42L42_ASP_RX_DAI0_CH2_AP_RES (CS42L42_PAGE_2A + 0x05) +#define CS42L42_ASP_RX_DAI0_CH2_BIT_MSB (CS42L42_PAGE_2A + 0x06) +#define CS42L42_ASP_RX_DAI0_CH2_BIT_LSB (CS42L42_PAGE_2A + 0x07) +#define CS42L42_ASP_RX_DAI0_CH3_AP_RES (CS42L42_PAGE_2A + 0x08) +#define CS42L42_ASP_RX_DAI0_CH3_BIT_MSB (CS42L42_PAGE_2A + 0x09) +#define CS42L42_ASP_RX_DAI0_CH3_BIT_LSB (CS42L42_PAGE_2A + 0x0A) +#define CS42L42_ASP_RX_DAI0_CH4_AP_RES (CS42L42_PAGE_2A + 0x0B) +#define CS42L42_ASP_RX_DAI0_CH4_BIT_MSB (CS42L42_PAGE_2A + 0x0C) +#define CS42L42_ASP_RX_DAI0_CH4_BIT_LSB (CS42L42_PAGE_2A + 0x0D) +#define CS42L42_ASP_RX_DAI1_CH1_AP_RES (CS42L42_PAGE_2A + 0x0E) +#define CS42L42_ASP_RX_DAI1_CH1_BIT_MSB (CS42L42_PAGE_2A + 0x0F) +#define CS42L42_ASP_RX_DAI1_CH1_BIT_LSB (CS42L42_PAGE_2A + 0x10) +#define CS42L42_ASP_RX_DAI1_CH2_AP_RES (CS42L42_PAGE_2A + 0x11) +#define CS42L42_ASP_RX_DAI1_CH2_BIT_MSB (CS42L42_PAGE_2A + 0x12) +#define CS42L42_ASP_RX_DAI1_CH2_BIT_LSB (CS42L42_PAGE_2A + 0x13) + +#define CS42L42_ASP_RX_CH_AP_SHIFT 6 +#define CS42L42_ASP_RX_CH_AP_MASK (1 << CS42L42_ASP_RX_CH_AP_SHIFT) +#define CS42L42_ASP_RX_CH_AP_LOW 0 +#define CS42L42_ASP_RX_CH_AP_HI 1 +#define CS42L42_ASP_RX_CH_RES_SHIFT 0 +#define CS42L42_ASP_RX_CH_RES_MASK (3 << CS42L42_ASP_RX_CH_RES_SHIFT) +#define CS42L42_ASP_RX_CH_RES_32 3 +#define CS42L42_ASP_RX_CH_RES_16 1 +#define CS42L42_ASP_RX_CH_BIT_ST_SHIFT 0 +#define CS42L42_ASP_RX_CH_BIT_ST_MASK (0xff << CS42L42_ASP_RX_CH_BIT_ST_SHIFT) + +/* Page 0x30 ID Registers */ +#define CS42L42_SUB_REVID (CS42L42_PAGE_30 + 0x14) +#define CS42L42_MAX_REGISTER (CS42L42_PAGE_30 + 0x14) + +/* Defines for fracturing values spread across multiple registers */ +#define CS42L42_FRAC0_VAL(val) ((val) & 0x0000ff) +#define CS42L42_FRAC1_VAL(val) (((val) & 0x00ff00) >> 8) +#define CS42L42_FRAC2_VAL(val) (((val) & 0xff0000) >> 16) + +#define CS42L42_NUM_SUPPLIES 5 +#define CS42L42_BOOT_TIME_US 3000 + +static const char *const cs42l42_supply_names[CS42L42_NUM_SUPPLIES] = { + "VA", + "VP", + "VCP", + "VD_FILT", + "VL", +}; + +struct cs42l42_private { + struct regmap *regmap; + struct snd_soc_component *component; + struct regulator_bulk_data supplies[CS42L42_NUM_SUPPLIES]; + struct gpio_desc *reset_gpio; + struct completion pdn_done; + u32 sclk; + u32 srate; + u8 plug_state; + u8 hs_type; + u8 ts_inv; + u8 ts_dbnc_rise; + u8 ts_dbnc_fall; + u8 btn_det_init_dbnce; + u8 btn_det_event_dbnce; + u8 bias_thresholds[CS42L42_NUM_BIASES]; + u8 hs_bias_ramp_rate; + u8 hs_bias_ramp_time; +}; + +#endif /* __CS42L42_H__ */ diff --git a/sound/soc/codecs/cs42l51-i2c.c b/sound/soc/codecs/cs42l51-i2c.c new file mode 100644 index 000000000..4b5731a41 --- /dev/null +++ b/sound/soc/codecs/cs42l51-i2c.c @@ -0,0 +1,51 @@ +/* + * cs42l56.c -- CS42L51 ALSA SoC I2C audio driver + * + * Copyright 2014 CirrusLogic, Inc. + * + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include + +#include "cs42l51.h" + +static struct i2c_device_id cs42l51_i2c_id[] = { + {"cs42l51", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs42l51_i2c_id); + +static int cs42l51_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap_config config; + + config = cs42l51_regmap; + config.val_bits = 8; + config.reg_bits = 8; + + return cs42l51_probe(&i2c->dev, devm_regmap_init_i2c(i2c, &config)); +} + +static struct i2c_driver cs42l51_i2c_driver = { + .driver = { + .name = "cs42l51", + .of_match_table = cs42l51_of_match, + }, + .probe = cs42l51_i2c_probe, + .id_table = cs42l51_i2c_id, +}; + +module_i2c_driver(cs42l51_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS42L51 I2C Driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c new file mode 100644 index 000000000..5080d7a3c --- /dev/null +++ b/sound/soc/codecs/cs42l51.c @@ -0,0 +1,575 @@ +/* + * cs42l51.c + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard + * + * Based on cs4270.c - Copyright (c) Freescale Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For now: + * - Only I2C is support. Not SPI + * - master mode *NOT* supported + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs42l51.h" + +enum master_slave_mode { + MODE_SLAVE, + MODE_SLAVE_AUTO, + MODE_MASTER, +}; + +struct cs42l51_private { + unsigned int mclk; + unsigned int audio_mode; /* The mode (I2S or left-justified) */ + enum master_slave_mode func; +}; + +#define CS42L51_FORMATS ( \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned long value = snd_soc_component_read32(component, CS42L51_PCM_MIXER)&3; + + switch (value) { + default: + case 0: + ucontrol->value.enumerated.item[0] = 0; + break; + /* same value : (L+R)/2 and (R+L)/2 */ + case 1: + case 2: + ucontrol->value.enumerated.item[0] = 1; + break; + case 3: + ucontrol->value.enumerated.item[0] = 2; + break; + } + + return 0; +} + +#define CHAN_MIX_NORMAL 0x00 +#define CHAN_MIX_BOTH 0x55 +#define CHAN_MIX_SWAP 0xFF + +static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned char val; + + switch (ucontrol->value.enumerated.item[0]) { + default: + case 0: + val = CHAN_MIX_NORMAL; + break; + case 1: + val = CHAN_MIX_BOTH; + break; + case 2: + val = CHAN_MIX_SWAP; + break; + } + + snd_soc_component_write(component, CS42L51_PCM_MIXER, val); + + return 1; +} + +static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); +static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); + +static const DECLARE_TLV_DB_SCALE(aout_tlv, -10200, 50, 0); + +static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); +static const char *chan_mix[] = { + "L R", + "L+R", + "R L", +}; + +static SOC_ENUM_SINGLE_EXT_DECL(cs42l51_chan_mix, chan_mix); + +static const struct snd_kcontrol_new cs42l51_snd_controls[] = { + SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, + 0, 0x19, 0x7F, adc_pcm_tlv), + SOC_DOUBLE_R("PCM Playback Switch", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), + SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", + CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, + 0, 0x34, 0xE4, aout_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, + 0, 0x19, 0x7F, adc_pcm_tlv), + SOC_DOUBLE_R("ADC Mixer Switch", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), + SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), + SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), + SOC_DOUBLE_TLV("Mic Boost Volume", + CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), + SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), + SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), + SOC_ENUM_EXT("PCM channel mixer", + cs42l51_chan_mix, + cs42l51_get_chan_mix, cs42l51_set_chan_mix), +}; + +/* + * to power down, one must: + * 1.) Enable the PDN bit + * 2.) enable power-down for the select channels + * 3.) disable the PDN bit. + */ +static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, CS42L51_POWER_CTL1, + CS42L51_POWER_CTL1_PDN, + CS42L51_POWER_CTL1_PDN); + break; + default: + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, CS42L51_POWER_CTL1, + CS42L51_POWER_CTL1_PDN, 0); + break; + } + + return 0; +} + +static const char *cs42l51_dac_names[] = {"Direct PCM", + "DSP PCM", "ADC"}; +static SOC_ENUM_SINGLE_DECL(cs42l51_dac_mux_enum, + CS42L51_DAC_CTL, 6, cs42l51_dac_names); +static const struct snd_kcontrol_new cs42l51_dac_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); + +static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", + "MIC Left", "MIC+preamp Left"}; +static SOC_ENUM_SINGLE_DECL(cs42l51_adcl_mux_enum, + CS42L51_ADC_INPUT, 4, cs42l51_adcl_names); +static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); + +static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", + "MIC Right", "MIC+preamp Right"}; +static SOC_ENUM_SINGLE_DECL(cs42l51_adcr_mux_enum, + CS42L51_ADC_INPUT, 6, cs42l51_adcr_names); +static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); + +static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), + SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", + CS42L51_POWER_CTL1, 1, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", + CS42L51_POWER_CTL1, 2, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", + CS42L51_POWER_CTL1, 5, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", + CS42L51_POWER_CTL1, 6, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + + /* analog/mic */ + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + + SND_SOC_DAPM_MIXER("Mic Preamp Left", + CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), + SND_SOC_DAPM_MIXER("Mic Preamp Right", + CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), + + /* HP */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + + /* mux */ + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, + &cs42l51_dac_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, + &cs42l51_adcl_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, + &cs42l51_adcr_mux_controls), +}; + +static const struct snd_soc_dapm_route cs42l51_routes[] = { + {"HPL", NULL, "Left DAC"}, + {"HPR", NULL, "Right DAC"}, + + {"Left ADC", NULL, "Left PGA"}, + {"Right ADC", NULL, "Right PGA"}, + + {"Mic Preamp Left", NULL, "MICL"}, + {"Mic Preamp Right", NULL, "MICR"}, + + {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, + {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, + {"PGA-ADC Mux Left", "MIC Left", "MICL" }, + {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, + {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, + {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, + {"PGA-ADC Mux Right", "MIC Right", "MICR" }, + {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, + + {"Left PGA", NULL, "PGA-ADC Mux Left"}, + {"Right PGA", NULL, "PGA-ADC Mux Right"}, +}; + +static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42l51_private *cs42l51 = snd_soc_component_get_drvdata(component); + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(component->dev, "invalid DAI format\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cs42l51->func = MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + cs42l51->func = MODE_SLAVE_AUTO; + break; + default: + dev_err(component->dev, "Unknown master/slave configuration\n"); + return -EINVAL; + } + + return 0; +} + +struct cs42l51_ratios { + unsigned int ratio; + unsigned char speed_mode; + unsigned char mclk; +}; + +static struct cs42l51_ratios slave_ratios[] = { + { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, + { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, + { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, + { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, + { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, + { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, + { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, + { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, + { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, + { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, + { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, +}; + +static struct cs42l51_ratios slave_auto_ratios[] = { + { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, + { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 }, + { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, + { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 }, + { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, + { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 }, + { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, + { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, +}; + +static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42l51_private *cs42l51 = snd_soc_component_get_drvdata(component); + + cs42l51->mclk = freq; + return 0; +} + +static int cs42l51_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs42l51_private *cs42l51 = snd_soc_component_get_drvdata(component); + int ret; + unsigned int i; + unsigned int rate; + unsigned int ratio; + struct cs42l51_ratios *ratios = NULL; + int nr_ratios = 0; + int intf_ctl, power_ctl, fmt; + + switch (cs42l51->func) { + case MODE_MASTER: + return -EINVAL; + case MODE_SLAVE: + ratios = slave_ratios; + nr_ratios = ARRAY_SIZE(slave_ratios); + break; + case MODE_SLAVE_AUTO: + ratios = slave_auto_ratios; + nr_ratios = ARRAY_SIZE(slave_auto_ratios); + break; + } + + /* Figure out which MCLK/LRCK ratio to use */ + rate = params_rate(params); /* Sampling rate, in Hz */ + ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */ + for (i = 0; i < nr_ratios; i++) { + if (ratios[i].ratio == ratio) + break; + } + + if (i == nr_ratios) { + /* We did not find a matching ratio */ + dev_err(component->dev, "could not find matching ratio\n"); + return -EINVAL; + } + + intf_ctl = snd_soc_component_read32(component, CS42L51_INTF_CTL); + power_ctl = snd_soc_component_read32(component, CS42L51_MIC_POWER_CTL); + + intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S + | CS42L51_INTF_CTL_DAC_FORMAT(7)); + power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3) + | CS42L51_MIC_POWER_CTL_MCLK_DIV2); + + switch (cs42l51->func) { + case MODE_MASTER: + intf_ctl |= CS42L51_INTF_CTL_MASTER; + power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE: + power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE_AUTO: + power_ctl |= CS42L51_MIC_POWER_CTL_AUTO; + break; + } + + switch (cs42l51->audio_mode) { + case SND_SOC_DAIFMT_I2S: + intf_ctl |= CS42L51_INTF_CTL_ADC_I2S; + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S); + break; + case SND_SOC_DAIFMT_LEFT_J: + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24); + break; + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_width(params)) { + case 16: + fmt = CS42L51_DAC_DIF_RJ16; + break; + case 18: + fmt = CS42L51_DAC_DIF_RJ18; + break; + case 20: + fmt = CS42L51_DAC_DIF_RJ20; + break; + case 24: + fmt = CS42L51_DAC_DIF_RJ24; + break; + default: + dev_err(component->dev, "unknown format\n"); + return -EINVAL; + } + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt); + break; + default: + dev_err(component->dev, "unknown format\n"); + return -EINVAL; + } + + if (ratios[i].mclk) + power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2; + + ret = snd_soc_component_write(component, CS42L51_INTF_CTL, intf_ctl); + if (ret < 0) + return ret; + + ret = snd_soc_component_write(component, CS42L51_MIC_POWER_CTL, power_ctl); + if (ret < 0) + return ret; + + return 0; +} + +static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + int reg; + int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE; + + reg = snd_soc_component_read32(component, CS42L51_DAC_OUT_CTL); + + if (mute) + reg |= mask; + else + reg &= ~mask; + + return snd_soc_component_write(component, CS42L51_DAC_OUT_CTL, reg); +} + +static const struct snd_soc_dai_ops cs42l51_dai_ops = { + .hw_params = cs42l51_hw_params, + .set_sysclk = cs42l51_set_dai_sysclk, + .set_fmt = cs42l51_set_dai_fmt, + .digital_mute = cs42l51_dai_mute, +}; + +static struct snd_soc_dai_driver cs42l51_dai = { + .name = "cs42l51-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = CS42L51_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = CS42L51_FORMATS, + }, + .ops = &cs42l51_dai_ops, +}; + +static int cs42l51_component_probe(struct snd_soc_component *component) +{ + int ret, reg; + + /* + * DAC configuration + * - Use signal processor + * - auto mute + * - vol changes immediate + * - no de-emphasize + */ + reg = CS42L51_DAC_CTL_DATA_SEL(1) + | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); + ret = snd_soc_component_write(component, CS42L51_DAC_CTL, reg); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_component_driver soc_component_device_cs42l51 = { + .probe = cs42l51_component_probe, + .controls = cs42l51_snd_controls, + .num_controls = ARRAY_SIZE(cs42l51_snd_controls), + .dapm_widgets = cs42l51_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42l51_dapm_widgets), + .dapm_routes = cs42l51_routes, + .num_dapm_routes = ARRAY_SIZE(cs42l51_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +const struct regmap_config cs42l51_regmap = { + .max_register = CS42L51_CHARGE_FREQ, + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(cs42l51_regmap); + +int cs42l51_probe(struct device *dev, struct regmap *regmap) +{ + struct cs42l51_private *cs42l51; + unsigned int val; + int ret; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + cs42l51 = devm_kzalloc(dev, sizeof(struct cs42l51_private), + GFP_KERNEL); + if (!cs42l51) + return -ENOMEM; + + dev_set_drvdata(dev, cs42l51); + + /* Verify that we have a CS42L51 */ + ret = regmap_read(regmap, CS42L51_CHIP_REV_ID, &val); + if (ret < 0) { + dev_err(dev, "failed to read I2C\n"); + goto error; + } + + if ((val != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && + (val != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { + dev_err(dev, "Invalid chip id: %x\n", val); + ret = -ENODEV; + goto error; + } + dev_info(dev, "Cirrus Logic CS42L51, Revision: %02X\n", + val & CS42L51_CHIP_REV_MASK); + + ret = devm_snd_soc_register_component(dev, + &soc_component_device_cs42l51, &cs42l51_dai, 1); +error: + return ret; +} +EXPORT_SYMBOL_GPL(cs42l51_probe); + +const struct of_device_id cs42l51_of_match[] = { + { .compatible = "cirrus,cs42l51", }, + { } +}; +MODULE_DEVICE_TABLE(of, cs42l51_of_match); +EXPORT_SYMBOL_GPL(cs42l51_of_match); + +MODULE_AUTHOR("Arnaud Patard "); +MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h new file mode 100644 index 000000000..0ca805492 --- /dev/null +++ b/sound/soc/codecs/cs42l51.h @@ -0,0 +1,168 @@ +/* + * cs42l51.h + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _CS42L51_H +#define _CS42L51_H + +struct device; + +extern const struct regmap_config cs42l51_regmap; +int cs42l51_probe(struct device *dev, struct regmap *regmap); +extern const struct of_device_id cs42l51_of_match[]; + +#define CS42L51_CHIP_ID 0x1B +#define CS42L51_CHIP_REV_A 0x00 +#define CS42L51_CHIP_REV_B 0x01 +#define CS42L51_CHIP_REV_MASK 0x07 + +#define CS42L51_CHIP_REV_ID 0x01 +#define CS42L51_MK_CHIP_REV(a, b) ((a)<<3|(b)) + +#define CS42L51_POWER_CTL1 0x02 +#define CS42L51_POWER_CTL1_PDN_DACB (1<<6) +#define CS42L51_POWER_CTL1_PDN_DACA (1<<5) +#define CS42L51_POWER_CTL1_PDN_PGAB (1<<4) +#define CS42L51_POWER_CTL1_PDN_PGAA (1<<3) +#define CS42L51_POWER_CTL1_PDN_ADCB (1<<2) +#define CS42L51_POWER_CTL1_PDN_ADCA (1<<1) +#define CS42L51_POWER_CTL1_PDN (1<<0) + +#define CS42L51_MIC_POWER_CTL 0x03 +#define CS42L51_MIC_POWER_CTL_AUTO (1<<7) +#define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5) +#define CS42L51_QSM_MODE 3 +#define CS42L51_HSM_MODE 2 +#define CS42L51_SSM_MODE 1 +#define CS42L51_DSM_MODE 0 +#define CS42L51_MIC_POWER_CTL_3ST_SP (1<<4) +#define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3) +#define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2) +#define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1) +#define CS42L51_MIC_POWER_CTL_MCLK_DIV2 (1<<0) + +#define CS42L51_INTF_CTL 0x04 +#define CS42L51_INTF_CTL_LOOPBACK (1<<7) +#define CS42L51_INTF_CTL_MASTER (1<<6) +#define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3) +#define CS42L51_DAC_DIF_LJ24 0x00 +#define CS42L51_DAC_DIF_I2S 0x01 +#define CS42L51_DAC_DIF_RJ24 0x02 +#define CS42L51_DAC_DIF_RJ20 0x03 +#define CS42L51_DAC_DIF_RJ18 0x04 +#define CS42L51_DAC_DIF_RJ16 0x05 +#define CS42L51_INTF_CTL_ADC_I2S (1<<2) +#define CS42L51_INTF_CTL_DIGMIX (1<<1) +#define CS42L51_INTF_CTL_MICMIX (1<<0) + +#define CS42L51_MIC_CTL 0x05 +#define CS42L51_MIC_CTL_ADC_SNGVOL (1<<7) +#define CS42L51_MIC_CTL_ADCD_DBOOST (1<<6) +#define CS42L51_MIC_CTL_ADCA_DBOOST (1<<5) +#define CS42L51_MIC_CTL_MICBIAS_SEL (1<<4) +#define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2) +#define CS42L51_MIC_CTL_MICB_BOOST (1<<1) +#define CS42L51_MIC_CTL_MICA_BOOST (1<<0) + +#define CS42L51_ADC_CTL 0x06 +#define CS42L51_ADC_CTL_ADCB_HPFEN (1<<7) +#define CS42L51_ADC_CTL_ADCB_HPFRZ (1<<6) +#define CS42L51_ADC_CTL_ADCA_HPFEN (1<<5) +#define CS42L51_ADC_CTL_ADCA_HPFRZ (1<<4) +#define CS42L51_ADC_CTL_SOFTB (1<<3) +#define CS42L51_ADC_CTL_ZCROSSB (1<<2) +#define CS42L51_ADC_CTL_SOFTA (1<<1) +#define CS42L51_ADC_CTL_ZCROSSA (1<<0) + +#define CS42L51_ADC_INPUT 0x07 +#define CS42L51_ADC_INPUT_AINB_MUX(x) (((x)&3)<<6) +#define CS42L51_ADC_INPUT_AINA_MUX(x) (((x)&3)<<4) +#define CS42L51_ADC_INPUT_INV_ADCB (1<<3) +#define CS42L51_ADC_INPUT_INV_ADCA (1<<2) +#define CS42L51_ADC_INPUT_ADCB_MUTE (1<<1) +#define CS42L51_ADC_INPUT_ADCA_MUTE (1<<0) + +#define CS42L51_DAC_OUT_CTL 0x08 +#define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5) +#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4) +#define CS42L51_DAC_OUT_CTL_INV_PCMB (1<<3) +#define CS42L51_DAC_OUT_CTL_INV_PCMA (1<<2) +#define CS42L51_DAC_OUT_CTL_DACB_MUTE (1<<1) +#define CS42L51_DAC_OUT_CTL_DACA_MUTE (1<<0) + +#define CS42L51_DAC_CTL 0x09 +#define CS42L51_DAC_CTL_DATA_SEL(x) (((x)&3)<<6) +#define CS42L51_DAC_CTL_FREEZE (1<<5) +#define CS42L51_DAC_CTL_DEEMPH (1<<3) +#define CS42L51_DAC_CTL_AMUTE (1<<2) +#define CS42L51_DAC_CTL_DACSZ(x) (((x)&3)<<0) + +#define CS42L51_ALC_PGA_CTL 0x0A +#define CS42L51_ALC_PGB_CTL 0x0B +#define CS42L51_ALC_PGX_ALCX_SRDIS (1<<7) +#define CS42L51_ALC_PGX_ALCX_ZCDIS (1<<6) +#define CS42L51_ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0) + +#define CS42L51_ADCA_ATT 0x0C +#define CS42L51_ADCB_ATT 0x0D + +#define CS42L51_ADCA_VOL 0x0E +#define CS42L51_ADCB_VOL 0x0F +#define CS42L51_PCMA_VOL 0x10 +#define CS42L51_PCMB_VOL 0x11 +#define CS42L51_MIX_MUTE_ADCMIX (1<<7) +#define CS42L51_MIX_VOLUME(x) (((x)&0x7f)<<0) + +#define CS42L51_BEEP_FREQ 0x12 +#define CS42L51_BEEP_VOL 0x13 +#define CS42L51_BEEP_CONF 0x14 + +#define CS42L51_TONE_CTL 0x15 +#define CS42L51_TONE_CTL_TREB(x) (((x)&0xf)<<4) +#define CS42L51_TONE_CTL_BASS(x) (((x)&0xf)<<0) + +#define CS42L51_AOUTA_VOL 0x16 +#define CS42L51_AOUTB_VOL 0x17 +#define CS42L51_PCM_MIXER 0x18 +#define CS42L51_LIMIT_THRES_DIS 0x19 +#define CS42L51_LIMIT_REL 0x1A +#define CS42L51_LIMIT_ATT 0x1B +#define CS42L51_ALC_EN 0x1C +#define CS42L51_ALC_REL 0x1D +#define CS42L51_ALC_THRES 0x1E +#define CS42L51_NOISE_CONF 0x1F + +#define CS42L51_STATUS 0x20 +#define CS42L51_STATUS_SP_CLKERR (1<<6) +#define CS42L51_STATUS_SPEA_OVFL (1<<5) +#define CS42L51_STATUS_SPEB_OVFL (1<<4) +#define CS42L51_STATUS_PCMA_OVFL (1<<3) +#define CS42L51_STATUS_PCMB_OVFL (1<<2) +#define CS42L51_STATUS_ADCA_OVFL (1<<1) +#define CS42L51_STATUS_ADCB_OVFL (1<<0) + +#define CS42L51_CHARGE_FREQ 0x21 + +#define CS42L51_FIRSTREG 0x01 +/* + * Hack: with register 0x21, it makes 33 registers. Looks like someone in the + * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using + * 32 regs + */ +#define CS42L51_LASTREG 0x20 +#define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1) + +#endif diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c new file mode 100644 index 000000000..de3112994 --- /dev/null +++ b/sound/soc/codecs/cs42l52.c @@ -0,0 +1,1240 @@ +/* + * cs42l52.c -- CS42L52 ALSA SoC audio driver + * + * Copyright 2012 CirrusLogic, Inc. + * + * Author: Georgi Vlaev + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs42l52.h" + +struct sp_config { + u8 spc, format, spfs; + u32 srate; +}; + +struct cs42l52_private { + struct regmap *regmap; + struct snd_soc_component *component; + struct device *dev; + struct sp_config config; + struct cs42l52_platform_data pdata; + u32 sysclk; + u8 mclksel; + u32 mclk; + u8 flags; + struct input_dev *beep; + struct work_struct beep_work; + int beep_rate; +}; + +static const struct reg_default cs42l52_reg_defaults[] = { + { CS42L52_PWRCTL1, 0x9F }, /* r02 PWRCTL 1 */ + { CS42L52_PWRCTL2, 0x07 }, /* r03 PWRCTL 2 */ + { CS42L52_PWRCTL3, 0xFF }, /* r04 PWRCTL 3 */ + { CS42L52_CLK_CTL, 0xA0 }, /* r05 Clocking Ctl */ + { CS42L52_IFACE_CTL1, 0x00 }, /* r06 Interface Ctl 1 */ + { CS42L52_ADC_PGA_A, 0x80 }, /* r08 Input A Select */ + { CS42L52_ADC_PGA_B, 0x80 }, /* r09 Input B Select */ + { CS42L52_ANALOG_HPF_CTL, 0xA5 }, /* r0A Analog HPF Ctl */ + { CS42L52_ADC_HPF_FREQ, 0x00 }, /* r0B ADC HPF Corner Freq */ + { CS42L52_ADC_MISC_CTL, 0x00 }, /* r0C Misc. ADC Ctl */ + { CS42L52_PB_CTL1, 0x60 }, /* r0D Playback Ctl 1 */ + { CS42L52_MISC_CTL, 0x02 }, /* r0E Misc. Ctl */ + { CS42L52_PB_CTL2, 0x00 }, /* r0F Playback Ctl 2 */ + { CS42L52_MICA_CTL, 0x00 }, /* r10 MICA Amp Ctl */ + { CS42L52_MICB_CTL, 0x00 }, /* r11 MICB Amp Ctl */ + { CS42L52_PGAA_CTL, 0x00 }, /* r12 PGAA Vol, Misc. */ + { CS42L52_PGAB_CTL, 0x00 }, /* r13 PGAB Vol, Misc. */ + { CS42L52_PASSTHRUA_VOL, 0x00 }, /* r14 Bypass A Vol */ + { CS42L52_PASSTHRUB_VOL, 0x00 }, /* r15 Bypass B Vol */ + { CS42L52_ADCA_VOL, 0x00 }, /* r16 ADCA Volume */ + { CS42L52_ADCB_VOL, 0x00 }, /* r17 ADCB Volume */ + { CS42L52_ADCA_MIXER_VOL, 0x80 }, /* r18 ADCA Mixer Volume */ + { CS42L52_ADCB_MIXER_VOL, 0x80 }, /* r19 ADCB Mixer Volume */ + { CS42L52_PCMA_MIXER_VOL, 0x00 }, /* r1A PCMA Mixer Volume */ + { CS42L52_PCMB_MIXER_VOL, 0x00 }, /* r1B PCMB Mixer Volume */ + { CS42L52_BEEP_FREQ, 0x00 }, /* r1C Beep Freq on Time */ + { CS42L52_BEEP_VOL, 0x00 }, /* r1D Beep Volume off Time */ + { CS42L52_BEEP_TONE_CTL, 0x00 }, /* r1E Beep Tone Cfg. */ + { CS42L52_TONE_CTL, 0x00 }, /* r1F Tone Ctl */ + { CS42L52_MASTERA_VOL, 0x00 }, /* r20 Master A Volume */ + { CS42L52_MASTERB_VOL, 0x00 }, /* r21 Master B Volume */ + { CS42L52_HPA_VOL, 0x00 }, /* r22 Headphone A Volume */ + { CS42L52_HPB_VOL, 0x00 }, /* r23 Headphone B Volume */ + { CS42L52_SPKA_VOL, 0x00 }, /* r24 Speaker A Volume */ + { CS42L52_SPKB_VOL, 0x00 }, /* r25 Speaker B Volume */ + { CS42L52_ADC_PCM_MIXER, 0x00 }, /* r26 Channel Mixer and Swap */ + { CS42L52_LIMITER_CTL1, 0x00 }, /* r27 Limit Ctl 1 Thresholds */ + { CS42L52_LIMITER_CTL2, 0x7F }, /* r28 Limit Ctl 2 Release Rate */ + { CS42L52_LIMITER_AT_RATE, 0xC0 }, /* r29 Limiter Attack Rate */ + { CS42L52_ALC_CTL, 0x00 }, /* r2A ALC Ctl 1 Attack Rate */ + { CS42L52_ALC_RATE, 0x3F }, /* r2B ALC Release Rate */ + { CS42L52_ALC_THRESHOLD, 0x3f }, /* r2C ALC Thresholds */ + { CS42L52_NOISE_GATE_CTL, 0x00 }, /* r2D Noise Gate Ctl */ + { CS42L52_CLK_STATUS, 0x00 }, /* r2E Overflow and Clock Status */ + { CS42L52_BATT_COMPEN, 0x00 }, /* r2F battery Compensation */ + { CS42L52_BATT_LEVEL, 0x00 }, /* r30 VP Battery Level */ + { CS42L52_SPK_STATUS, 0x00 }, /* r31 Speaker Status */ + { CS42L52_TEM_CTL, 0x3B }, /* r32 Temp Ctl */ + { CS42L52_THE_FOLDBACK, 0x00 }, /* r33 Foldback */ +}; + +static bool cs42l52_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L52_CHIP ... CS42L52_CHARGE_PUMP: + return true; + default: + return false; + } +} + +static bool cs42l52_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L52_IFACE_CTL2: + case CS42L52_CLK_STATUS: + case CS42L52_BATT_LEVEL: + case CS42L52_SPK_STATUS: + case CS42L52_CHARGE_PUMP: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(hl_tlv, -10200, 50, 0); + +static DECLARE_TLV_DB_SCALE(hpd_tlv, -9600, 50, 1); + +static DECLARE_TLV_DB_SCALE(ipd_tlv, -9600, 100, 0); + +static DECLARE_TLV_DB_SCALE(mic_tlv, 1600, 100, 0); + +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); + +static DECLARE_TLV_DB_SCALE(pass_tlv, -6000, 50, 0); + +static DECLARE_TLV_DB_SCALE(mix_tlv, -5150, 50, 0); + +static DECLARE_TLV_DB_SCALE(beep_tlv, -56, 200, 0); + +static const DECLARE_TLV_DB_RANGE(limiter_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-3000, 600, 0), + 3, 7, TLV_DB_SCALE_ITEM(-1200, 300, 0) +); + +static const char * const cs42l52_adca_text[] = { + "Input1A", "Input2A", "Input3A", "Input4A", "PGA Input Left"}; + +static const char * const cs42l52_adcb_text[] = { + "Input1B", "Input2B", "Input3B", "Input4B", "PGA Input Right"}; + +static SOC_ENUM_SINGLE_DECL(adca_enum, + CS42L52_ADC_PGA_A, 5, cs42l52_adca_text); + +static SOC_ENUM_SINGLE_DECL(adcb_enum, + CS42L52_ADC_PGA_B, 5, cs42l52_adcb_text); + +static const struct snd_kcontrol_new adca_mux = + SOC_DAPM_ENUM("Left ADC Input Capture Mux", adca_enum); + +static const struct snd_kcontrol_new adcb_mux = + SOC_DAPM_ENUM("Right ADC Input Capture Mux", adcb_enum); + +static const char * const mic_bias_level_text[] = { + "0.5 +VA", "0.6 +VA", "0.7 +VA", + "0.8 +VA", "0.83 +VA", "0.91 +VA" +}; + +static SOC_ENUM_SINGLE_DECL(mic_bias_level_enum, + CS42L52_IFACE_CTL2, 0, mic_bias_level_text); + +static const char * const cs42l52_mic_text[] = { "MIC1", "MIC2" }; + +static SOC_ENUM_SINGLE_DECL(mica_enum, + CS42L52_MICA_CTL, 5, cs42l52_mic_text); + +static SOC_ENUM_SINGLE_DECL(micb_enum, + CS42L52_MICB_CTL, 5, cs42l52_mic_text); + +static const char * const digital_output_mux_text[] = {"ADC", "DSP"}; + +static SOC_ENUM_SINGLE_DECL(digital_output_mux_enum, + CS42L52_ADC_MISC_CTL, 6, + digital_output_mux_text); + +static const struct snd_kcontrol_new digital_output_mux = + SOC_DAPM_ENUM("Digital Output Mux", digital_output_mux_enum); + +static const char * const hp_gain_num_text[] = { + "0.3959", "0.4571", "0.5111", "0.6047", + "0.7099", "0.8399", "1.000", "1.1430" +}; + +static SOC_ENUM_SINGLE_DECL(hp_gain_enum, + CS42L52_PB_CTL1, 5, + hp_gain_num_text); + +static const char * const beep_pitch_text[] = { + "C4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", + "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7" +}; + +static SOC_ENUM_SINGLE_DECL(beep_pitch_enum, + CS42L52_BEEP_FREQ, 4, + beep_pitch_text); + +static const char * const beep_ontime_text[] = { + "86 ms", "430 ms", "780 ms", "1.20 s", "1.50 s", + "1.80 s", "2.20 s", "2.50 s", "2.80 s", "3.20 s", + "3.50 s", "3.80 s", "4.20 s", "4.50 s", "4.80 s", "5.20 s" +}; + +static SOC_ENUM_SINGLE_DECL(beep_ontime_enum, + CS42L52_BEEP_FREQ, 0, + beep_ontime_text); + +static const char * const beep_offtime_text[] = { + "1.23 s", "2.58 s", "3.90 s", "5.20 s", + "6.60 s", "8.05 s", "9.35 s", "10.80 s" +}; + +static SOC_ENUM_SINGLE_DECL(beep_offtime_enum, + CS42L52_BEEP_VOL, 5, + beep_offtime_text); + +static const char * const beep_config_text[] = { + "Off", "Single", "Multiple", "Continuous" +}; + +static SOC_ENUM_SINGLE_DECL(beep_config_enum, + CS42L52_BEEP_TONE_CTL, 6, + beep_config_text); + +static const char * const beep_bass_text[] = { + "50 Hz", "100 Hz", "200 Hz", "250 Hz" +}; + +static SOC_ENUM_SINGLE_DECL(beep_bass_enum, + CS42L52_BEEP_TONE_CTL, 1, + beep_bass_text); + +static const char * const beep_treble_text[] = { + "5 kHz", "7 kHz", "10 kHz", " 15 kHz" +}; + +static SOC_ENUM_SINGLE_DECL(beep_treble_enum, + CS42L52_BEEP_TONE_CTL, 3, + beep_treble_text); + +static const char * const ng_threshold_text[] = { + "-34dB", "-37dB", "-40dB", "-43dB", + "-46dB", "-52dB", "-58dB", "-64dB" +}; + +static SOC_ENUM_SINGLE_DECL(ng_threshold_enum, + CS42L52_NOISE_GATE_CTL, 2, + ng_threshold_text); + +static const char * const cs42l52_ng_delay_text[] = { + "50ms", "100ms", "150ms", "200ms"}; + +static SOC_ENUM_SINGLE_DECL(ng_delay_enum, + CS42L52_NOISE_GATE_CTL, 0, + cs42l52_ng_delay_text); + +static const char * const cs42l52_ng_type_text[] = { + "Apply Specific", "Apply All" +}; + +static SOC_ENUM_SINGLE_DECL(ng_type_enum, + CS42L52_NOISE_GATE_CTL, 6, + cs42l52_ng_type_text); + +static const char * const left_swap_text[] = { + "Left", "LR 2", "Right"}; + +static const char * const right_swap_text[] = { + "Right", "LR 2", "Left"}; + +static const unsigned int swap_values[] = { 0, 1, 3 }; + +static const struct soc_enum adca_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 2, 3, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); + +static const struct snd_kcontrol_new adca_mixer = + SOC_DAPM_ENUM("Route", adca_swap_enum); + +static const struct soc_enum pcma_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 6, 3, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); + +static const struct snd_kcontrol_new pcma_mixer = + SOC_DAPM_ENUM("Route", pcma_swap_enum); + +static const struct soc_enum adcb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 0, 3, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); + +static const struct snd_kcontrol_new adcb_mixer = + SOC_DAPM_ENUM("Route", adcb_swap_enum); + +static const struct soc_enum pcmb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 4, 3, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); + +static const struct snd_kcontrol_new pcmb_mixer = + SOC_DAPM_ENUM("Route", pcmb_swap_enum); + + +static const struct snd_kcontrol_new passthrul_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_MISC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new passthrur_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_MISC_CTL, 7, 1, 0); + +static const struct snd_kcontrol_new spkl_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 0, 1, 1); + +static const struct snd_kcontrol_new spkr_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 2, 1, 1); + +static const struct snd_kcontrol_new hpl_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 4, 1, 1); + +static const struct snd_kcontrol_new hpr_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 6, 1, 1); + +static const struct snd_kcontrol_new cs42l52_snd_controls[] = { + + SOC_DOUBLE_R_SX_TLV("Master Volume", CS42L52_MASTERA_VOL, + CS42L52_MASTERB_VOL, 0, 0x34, 0xE4, hl_tlv), + + SOC_DOUBLE_R_SX_TLV("Headphone Volume", CS42L52_HPA_VOL, + CS42L52_HPB_VOL, 0, 0x34, 0xC0, hpd_tlv), + + SOC_ENUM("Headphone Analog Gain", hp_gain_enum), + + SOC_DOUBLE_R_SX_TLV("Speaker Volume", CS42L52_SPKA_VOL, + CS42L52_SPKB_VOL, 0, 0x40, 0xC0, hl_tlv), + + SOC_DOUBLE_R_SX_TLV("Bypass Volume", CS42L52_PASSTHRUA_VOL, + CS42L52_PASSTHRUB_VOL, 0, 0x88, 0x90, pass_tlv), + + SOC_DOUBLE("Bypass Mute", CS42L52_MISC_CTL, 4, 5, 1, 0), + + SOC_DOUBLE_R_TLV("MIC Gain Volume", CS42L52_MICA_CTL, + CS42L52_MICB_CTL, 0, 0x10, 0, mic_tlv), + + SOC_ENUM("MIC Bias Level", mic_bias_level_enum), + + SOC_DOUBLE_R_SX_TLV("ADC Volume", CS42L52_ADCA_VOL, + CS42L52_ADCB_VOL, 0, 0xA0, 0x78, ipd_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + CS42L52_ADCA_MIXER_VOL, CS42L52_ADCB_MIXER_VOL, + 0, 0x19, 0x7F, mix_tlv), + + SOC_DOUBLE("ADC Switch", CS42L52_ADC_MISC_CTL, 0, 1, 1, 0), + + SOC_DOUBLE_R("ADC Mixer Switch", CS42L52_ADCA_MIXER_VOL, + CS42L52_ADCB_MIXER_VOL, 7, 1, 1), + + SOC_DOUBLE_R_SX_TLV("PGA Volume", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 0, 0x28, 0x24, pga_tlv), + + SOC_DOUBLE_R_SX_TLV("PCM Mixer Volume", + CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, + 0, 0x19, 0x7f, mix_tlv), + SOC_DOUBLE_R("PCM Mixer Switch", + CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, 7, 1, 1), + + SOC_ENUM("Beep Config", beep_config_enum), + SOC_ENUM("Beep Pitch", beep_pitch_enum), + SOC_ENUM("Beep on Time", beep_ontime_enum), + SOC_ENUM("Beep off Time", beep_offtime_enum), + SOC_SINGLE_SX_TLV("Beep Volume", CS42L52_BEEP_VOL, + 0, 0x07, 0x1f, beep_tlv), + SOC_SINGLE("Beep Mixer Switch", CS42L52_BEEP_TONE_CTL, 5, 1, 1), + SOC_ENUM("Beep Treble Corner Freq", beep_treble_enum), + SOC_ENUM("Beep Bass Corner Freq", beep_bass_enum), + + SOC_SINGLE("Tone Control Switch", CS42L52_BEEP_TONE_CTL, 0, 1, 1), + SOC_SINGLE_TLV("Treble Gain Volume", + CS42L52_TONE_CTL, 4, 15, 1, hl_tlv), + SOC_SINGLE_TLV("Bass Gain Volume", + CS42L52_TONE_CTL, 0, 15, 1, hl_tlv), + + /* Limiter */ + SOC_SINGLE_TLV("Limiter Max Threshold Volume", + CS42L52_LIMITER_CTL1, 5, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Cushion Threshold Volume", + CS42L52_LIMITER_CTL1, 2, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Release Rate Volume", + CS42L52_LIMITER_CTL2, 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Attack Rate Volume", + CS42L52_LIMITER_AT_RATE, 0, 63, 0, limiter_tlv), + + SOC_SINGLE("Limiter SR Switch", CS42L52_LIMITER_CTL1, 1, 1, 0), + SOC_SINGLE("Limiter ZC Switch", CS42L52_LIMITER_CTL1, 0, 1, 0), + SOC_SINGLE("Limiter Switch", CS42L52_LIMITER_CTL2, 7, 1, 0), + + /* ALC */ + SOC_SINGLE_TLV("ALC Attack Rate Volume", CS42L52_ALC_CTL, + 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Release Rate Volume", CS42L52_ALC_RATE, + 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Max Threshold Volume", CS42L52_ALC_THRESHOLD, + 5, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Min Threshold Volume", CS42L52_ALC_THRESHOLD, + 2, 7, 0, limiter_tlv), + + SOC_DOUBLE_R("ALC SR Capture Switch", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 7, 1, 1), + SOC_DOUBLE_R("ALC ZC Capture Switch", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 6, 1, 1), + SOC_DOUBLE("ALC Capture Switch", CS42L52_ALC_CTL, 6, 7, 1, 0), + + /* Noise gate */ + SOC_ENUM("NG Type Switch", ng_type_enum), + SOC_SINGLE("NG Enable Switch", CS42L52_NOISE_GATE_CTL, 6, 1, 0), + SOC_SINGLE("NG Boost Switch", CS42L52_NOISE_GATE_CTL, 5, 1, 1), + SOC_ENUM("NG Threshold", ng_threshold_enum), + SOC_ENUM("NG Delay", ng_delay_enum), + + SOC_DOUBLE("HPF Switch", CS42L52_ANALOG_HPF_CTL, 5, 7, 1, 0), + + SOC_DOUBLE("Analog SR Switch", CS42L52_ANALOG_HPF_CTL, 1, 3, 1, 1), + SOC_DOUBLE("Analog ZC Switch", CS42L52_ANALOG_HPF_CTL, 0, 2, 1, 1), + SOC_SINGLE("Digital SR Switch", CS42L52_MISC_CTL, 1, 1, 0), + SOC_SINGLE("Digital ZC Switch", CS42L52_MISC_CTL, 0, 1, 0), + SOC_SINGLE("Deemphasis Switch", CS42L52_MISC_CTL, 2, 1, 0), + + SOC_SINGLE("Batt Compensation Switch", CS42L52_BATT_COMPEN, 7, 1, 0), + SOC_SINGLE("Batt VP Monitor Switch", CS42L52_BATT_COMPEN, 6, 1, 0), + SOC_SINGLE("Batt VP ref", CS42L52_BATT_COMPEN, 0, 0x0f, 0), + + SOC_SINGLE("PGA AIN1L Switch", CS42L52_ADC_PGA_A, 0, 1, 0), + SOC_SINGLE("PGA AIN1R Switch", CS42L52_ADC_PGA_B, 0, 1, 0), + SOC_SINGLE("PGA AIN2L Switch", CS42L52_ADC_PGA_A, 1, 1, 0), + SOC_SINGLE("PGA AIN2R Switch", CS42L52_ADC_PGA_B, 1, 1, 0), + + SOC_SINGLE("PGA AIN3L Switch", CS42L52_ADC_PGA_A, 2, 1, 0), + SOC_SINGLE("PGA AIN3R Switch", CS42L52_ADC_PGA_B, 2, 1, 0), + + SOC_SINGLE("PGA AIN4L Switch", CS42L52_ADC_PGA_A, 3, 1, 0), + SOC_SINGLE("PGA AIN4R Switch", CS42L52_ADC_PGA_B, 3, 1, 0), + + SOC_SINGLE("PGA MICA Switch", CS42L52_ADC_PGA_A, 4, 1, 0), + SOC_SINGLE("PGA MICB Switch", CS42L52_ADC_PGA_B, 4, 1, 0), + +}; + +static const struct snd_kcontrol_new cs42l52_mica_controls[] = { + SOC_ENUM("MICA Select", mica_enum), +}; + +static const struct snd_kcontrol_new cs42l52_micb_controls[] = { + SOC_ENUM("MICB Select", micb_enum), +}; + +static int cs42l52_add_mic_controls(struct snd_soc_component *component) +{ + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + struct cs42l52_platform_data *pdata = &cs42l52->pdata; + + if (!pdata->mica_diff_cfg) + snd_soc_add_component_controls(component, cs42l52_mica_controls, + ARRAY_SIZE(cs42l52_mica_controls)); + + if (!pdata->micb_diff_cfg) + snd_soc_add_component_controls(component, cs42l52_micb_controls, + ARRAY_SIZE(cs42l52_micb_controls)); + + return 0; +} + +static const struct snd_soc_dapm_widget cs42l52_dapm_widgets[] = { + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R"), + SND_SOC_DAPM_INPUT("AIN4L"), + SND_SOC_DAPM_INPUT("AIN4R"), + SND_SOC_DAPM_INPUT("MICA"), + SND_SOC_DAPM_INPUT("MICB"), + SND_SOC_DAPM_SIGGEN("Beep"), + + SND_SOC_DAPM_AIF_OUT("AIFOUTL", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIFOUTR", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_ADC("ADC Left", NULL, CS42L52_PWRCTL1, 1, 1), + SND_SOC_DAPM_ADC("ADC Right", NULL, CS42L52_PWRCTL1, 2, 1), + SND_SOC_DAPM_PGA("PGA Left", CS42L52_PWRCTL1, 3, 1, NULL, 0), + SND_SOC_DAPM_PGA("PGA Right", CS42L52_PWRCTL1, 4, 1, NULL, 0), + + SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adca_mux), + SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcb_mux), + + SND_SOC_DAPM_MUX("ADC Left Swap", SND_SOC_NOPM, + 0, 0, &adca_mixer), + SND_SOC_DAPM_MUX("ADC Right Swap", SND_SOC_NOPM, + 0, 0, &adcb_mixer), + + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, + 0, 0, &digital_output_mux), + + SND_SOC_DAPM_PGA("PGA MICA", CS42L52_PWRCTL2, 1, 1, NULL, 0), + SND_SOC_DAPM_PGA("PGA MICB", CS42L52_PWRCTL2, 2, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Mic Bias", CS42L52_PWRCTL2, 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Charge Pump", CS42L52_PWRCTL1, 7, 1, NULL, 0), + + SND_SOC_DAPM_AIF_IN("AIFINL", NULL, 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFINR", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DAC Left", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Right", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SWITCH("Bypass Left", CS42L52_MISC_CTL, + 6, 0, &passthrul_ctl), + SND_SOC_DAPM_SWITCH("Bypass Right", CS42L52_MISC_CTL, + 7, 0, &passthrur_ctl), + + SND_SOC_DAPM_MUX("PCM Left Swap", SND_SOC_NOPM, + 0, 0, &pcma_mixer), + SND_SOC_DAPM_MUX("PCM Right Swap", SND_SOC_NOPM, + 0, 0, &pcmb_mixer), + + SND_SOC_DAPM_SWITCH("HP Left Amp", SND_SOC_NOPM, 0, 0, &hpl_ctl), + SND_SOC_DAPM_SWITCH("HP Right Amp", SND_SOC_NOPM, 0, 0, &hpr_ctl), + + SND_SOC_DAPM_SWITCH("SPK Left Amp", SND_SOC_NOPM, 0, 0, &spkl_ctl), + SND_SOC_DAPM_SWITCH("SPK Right Amp", SND_SOC_NOPM, 0, 0, &spkr_ctl), + + SND_SOC_DAPM_OUTPUT("HPOUTA"), + SND_SOC_DAPM_OUTPUT("HPOUTB"), + SND_SOC_DAPM_OUTPUT("SPKOUTA"), + SND_SOC_DAPM_OUTPUT("SPKOUTB"), + +}; + +static const struct snd_soc_dapm_route cs42l52_audio_map[] = { + + {"Capture", NULL, "AIFOUTL"}, + {"Capture", NULL, "AIFOUTL"}, + + {"AIFOUTL", NULL, "Output Mux"}, + {"AIFOUTR", NULL, "Output Mux"}, + + {"Output Mux", "ADC", "ADC Left"}, + {"Output Mux", "ADC", "ADC Right"}, + + {"ADC Left", NULL, "Charge Pump"}, + {"ADC Right", NULL, "Charge Pump"}, + + {"Charge Pump", NULL, "ADC Left Mux"}, + {"Charge Pump", NULL, "ADC Right Mux"}, + + {"ADC Left Mux", "Input1A", "AIN1L"}, + {"ADC Right Mux", "Input1B", "AIN1R"}, + {"ADC Left Mux", "Input2A", "AIN2L"}, + {"ADC Right Mux", "Input2B", "AIN2R"}, + {"ADC Left Mux", "Input3A", "AIN3L"}, + {"ADC Right Mux", "Input3B", "AIN3R"}, + {"ADC Left Mux", "Input4A", "AIN4L"}, + {"ADC Right Mux", "Input4B", "AIN4R"}, + {"ADC Left Mux", "PGA Input Left", "PGA Left"}, + {"ADC Right Mux", "PGA Input Right" , "PGA Right"}, + + {"PGA Left", "Switch", "AIN1L"}, + {"PGA Right", "Switch", "AIN1R"}, + {"PGA Left", "Switch", "AIN2L"}, + {"PGA Right", "Switch", "AIN2R"}, + {"PGA Left", "Switch", "AIN3L"}, + {"PGA Right", "Switch", "AIN3R"}, + {"PGA Left", "Switch", "AIN4L"}, + {"PGA Right", "Switch", "AIN4R"}, + + {"PGA Left", "Switch", "PGA MICA"}, + {"PGA MICA", NULL, "MICA"}, + + {"PGA Right", "Switch", "PGA MICB"}, + {"PGA MICB", NULL, "MICB"}, + + {"HPOUTA", NULL, "HP Left Amp"}, + {"HPOUTB", NULL, "HP Right Amp"}, + {"HP Left Amp", NULL, "Bypass Left"}, + {"HP Right Amp", NULL, "Bypass Right"}, + {"Bypass Left", "Switch", "PGA Left"}, + {"Bypass Right", "Switch", "PGA Right"}, + {"HP Left Amp", "Switch", "DAC Left"}, + {"HP Right Amp", "Switch", "DAC Right"}, + + {"SPKOUTA", NULL, "SPK Left Amp"}, + {"SPKOUTB", NULL, "SPK Right Amp"}, + + {"SPK Left Amp", NULL, "Beep"}, + {"SPK Right Amp", NULL, "Beep"}, + {"SPK Left Amp", "Switch", "Playback"}, + {"SPK Right Amp", "Switch", "Playback"}, + + {"DAC Left", NULL, "Beep"}, + {"DAC Right", NULL, "Beep"}, + {"DAC Left", NULL, "Playback"}, + {"DAC Right", NULL, "Playback"}, + + {"Output Mux", "DSP", "Playback"}, + {"Output Mux", "DSP", "Playback"}, + + {"AIFINL", NULL, "Playback"}, + {"AIFINR", NULL, "Playback"}, + +}; + +struct cs42l52_clk_para { + u32 mclk; + u32 rate; + u8 speed; + u8 group; + u8 videoclk; + u8 ratio; + u8 mclkdiv2; +}; + +static const struct cs42l52_clk_para clk_map_table[] = { + /*8k*/ + {12288000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 8000, CLK_QS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 0}, + + /*11.025k*/ + {11289600, 11025, CLK_QS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 11025, CLK_QS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /*16k*/ + {12288000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 16000, CLK_HS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 1}, + + /*22.05k*/ + {11289600, 22050, CLK_HS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 22050, CLK_HS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 32k */ + {12288000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 32000, CLK_SS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 0}, + + /* 44.1k */ + {11289600, 44100, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 44100, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 48k */ + {12288000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_27M_MCLK, CLK_R_125, 1}, + + /* 88.2k */ + {11289600, 88200, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 88200, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 96k */ + {12288000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 1}, +}; + +static int cs42l52_get_clk(int mclk, int rate) +{ + int i, ret = -EINVAL; + u_int mclk1, mclk2 = 0; + + for (i = 0; i < ARRAY_SIZE(clk_map_table); i++) { + if (clk_map_table[i].rate == rate) { + mclk1 = clk_map_table[i].mclk; + if (abs(mclk - mclk1) < abs(mclk - mclk2)) { + mclk2 = mclk1; + ret = i; + } + } + } + return ret; +} + +static int cs42l52_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + + if ((freq >= CS42L52_MIN_CLK) && (freq <= CS42L52_MAX_CLK)) { + cs42l52->sysclk = freq; + } else { + dev_err(component->dev, "Invalid freq parameter\n"); + return -EINVAL; + } + return 0; +} + +static int cs42l52_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + u8 iface = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = CS42L52_IFACE_CTL1_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface = CS42L52_IFACE_CTL1_SLAVE; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= CS42L52_IFACE_CTL1_ADC_FMT_I2S | + CS42L52_IFACE_CTL1_DAC_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface |= CS42L52_IFACE_CTL1_DAC_FMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= CS42L52_IFACE_CTL1_ADC_FMT_LEFT_J | + CS42L52_IFACE_CTL1_DAC_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= CS42L52_IFACE_CTL1_DSP_MODE_EN; + break; + case SND_SOC_DAIFMT_DSP_B: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= CS42L52_IFACE_CTL1_INV_SCLK; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= CS42L52_IFACE_CTL1_INV_SCLK; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + default: + return -EINVAL; + } + cs42l52->config.format = iface; + snd_soc_component_write(component, CS42L52_IFACE_CTL1, cs42l52->config.format); + + return 0; +} + +static int cs42l52_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + if (mute) + snd_soc_component_update_bits(component, CS42L52_PB_CTL1, + CS42L52_PB_CTL1_MUTE_MASK, + CS42L52_PB_CTL1_MUTE); + else + snd_soc_component_update_bits(component, CS42L52_PB_CTL1, + CS42L52_PB_CTL1_MUTE_MASK, + CS42L52_PB_CTL1_UNMUTE); + + return 0; +} + +static int cs42l52_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + u32 clk = 0; + int index; + + index = cs42l52_get_clk(cs42l52->sysclk, params_rate(params)); + if (index >= 0) { + cs42l52->sysclk = clk_map_table[index].mclk; + + clk |= (clk_map_table[index].speed << CLK_SPEED_SHIFT) | + (clk_map_table[index].group << CLK_32K_SR_SHIFT) | + (clk_map_table[index].videoclk << CLK_27M_MCLK_SHIFT) | + (clk_map_table[index].ratio << CLK_RATIO_SHIFT) | + clk_map_table[index].mclkdiv2; + + snd_soc_component_write(component, CS42L52_CLK_CTL, clk); + } else { + dev_err(component->dev, "can't get correct mclk\n"); + return -EINVAL; + } + + return 0; +} + +static int cs42l52_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, CS42L52_PWRCTL1, + CS42L52_PWRCTL1_PDN_CODEC, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_cache_only(cs42l52->regmap, false); + regcache_sync(cs42l52->regmap); + } + snd_soc_component_write(component, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL); + regcache_cache_only(cs42l52->regmap, true); + break; + } + + return 0; +} + +#define CS42L52_RATES (SNDRV_PCM_RATE_8000_96000) + +#define CS42L52_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE) + +static const struct snd_soc_dai_ops cs42l52_ops = { + .hw_params = cs42l52_pcm_hw_params, + .digital_mute = cs42l52_digital_mute, + .set_fmt = cs42l52_set_fmt, + .set_sysclk = cs42l52_set_sysclk, +}; + +static struct snd_soc_dai_driver cs42l52_dai = { + .name = "cs42l52", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L52_RATES, + .formats = CS42L52_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L52_RATES, + .formats = CS42L52_FORMATS, + }, + .ops = &cs42l52_ops, +}; + +static int beep_rates[] = { + 261, 522, 585, 667, 706, 774, 889, 1000, + 1043, 1200, 1333, 1412, 1600, 1714, 2000, 2182 +}; + +static void cs42l52_beep_work(struct work_struct *work) +{ + struct cs42l52_private *cs42l52 = + container_of(work, struct cs42l52_private, beep_work); + struct snd_soc_component *component = cs42l52->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int i; + int val = 0; + int best = 0; + + if (cs42l52->beep_rate) { + for (i = 0; i < ARRAY_SIZE(beep_rates); i++) { + if (abs(cs42l52->beep_rate - beep_rates[i]) < + abs(cs42l52->beep_rate - beep_rates[best])) + best = i; + } + + dev_dbg(component->dev, "Set beep rate %dHz for requested %dHz\n", + beep_rates[best], cs42l52->beep_rate); + + val = (best << CS42L52_BEEP_RATE_SHIFT); + + snd_soc_dapm_enable_pin(dapm, "Beep"); + } else { + dev_dbg(component->dev, "Disabling beep\n"); + snd_soc_dapm_disable_pin(dapm, "Beep"); + } + + snd_soc_component_update_bits(component, CS42L52_BEEP_FREQ, + CS42L52_BEEP_RATE_MASK, val); + + snd_soc_dapm_sync(dapm); +} + +/* For usability define a way of injecting beep events for the device - + * many systems will not have a keyboard. + */ +static int cs42l52_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + struct snd_soc_component *component = input_get_drvdata(dev); + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "Beep event %x %x\n", code, hz); + + switch (code) { + case SND_BELL: + if (hz) + hz = 261; + case SND_TONE: + break; + default: + return -1; + } + + /* Kick the beep from a workqueue */ + cs42l52->beep_rate = hz; + schedule_work(&cs42l52->beep_work); + return 0; +} + +static ssize_t cs42l52_beep_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs42l52_private *cs42l52 = dev_get_drvdata(dev); + long int time; + int ret; + + ret = kstrtol(buf, 10, &time); + if (ret != 0) + return ret; + + input_event(cs42l52->beep, EV_SND, SND_TONE, time); + + return count; +} + +static DEVICE_ATTR(beep, 0200, NULL, cs42l52_beep_set); + +static void cs42l52_init_beep(struct snd_soc_component *component) +{ + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + int ret; + + cs42l52->beep = devm_input_allocate_device(component->dev); + if (!cs42l52->beep) { + dev_err(component->dev, "Failed to allocate beep device\n"); + return; + } + + INIT_WORK(&cs42l52->beep_work, cs42l52_beep_work); + cs42l52->beep_rate = 0; + + cs42l52->beep->name = "CS42L52 Beep Generator"; + cs42l52->beep->phys = dev_name(component->dev); + cs42l52->beep->id.bustype = BUS_I2C; + + cs42l52->beep->evbit[0] = BIT_MASK(EV_SND); + cs42l52->beep->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + cs42l52->beep->event = cs42l52_beep_event; + cs42l52->beep->dev.parent = component->dev; + input_set_drvdata(cs42l52->beep, component); + + ret = input_register_device(cs42l52->beep); + if (ret != 0) { + cs42l52->beep = NULL; + dev_err(component->dev, "Failed to register beep device\n"); + } + + ret = device_create_file(component->dev, &dev_attr_beep); + if (ret != 0) { + dev_err(component->dev, "Failed to create keyclick file: %d\n", + ret); + } +} + +static void cs42l52_free_beep(struct snd_soc_component *component) +{ + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + + device_remove_file(component->dev, &dev_attr_beep); + cancel_work_sync(&cs42l52->beep_work); + cs42l52->beep = NULL; + + snd_soc_component_update_bits(component, CS42L52_BEEP_TONE_CTL, + CS42L52_BEEP_EN_MASK, 0); +} + +static int cs42l52_probe(struct snd_soc_component *component) +{ + struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(cs42l52->regmap, true); + + cs42l52_add_mic_controls(component); + + cs42l52_init_beep(component); + + cs42l52->sysclk = CS42L52_DEFAULT_CLK; + cs42l52->config.format = CS42L52_DEFAULT_FORMAT; + + return 0; +} + +static void cs42l52_remove(struct snd_soc_component *component) +{ + cs42l52_free_beep(component); +} + +static const struct snd_soc_component_driver soc_component_dev_cs42l52 = { + .probe = cs42l52_probe, + .remove = cs42l52_remove, + .set_bias_level = cs42l52_set_bias_level, + .controls = cs42l52_snd_controls, + .num_controls = ARRAY_SIZE(cs42l52_snd_controls), + .dapm_widgets = cs42l52_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42l52_dapm_widgets), + .dapm_routes = cs42l52_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs42l52_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* Current and threshold powerup sequence Pg37 */ +static const struct reg_sequence cs42l52_threshold_patch[] = { + + { 0x00, 0x99 }, + { 0x3E, 0xBA }, + { 0x47, 0x80 }, + { 0x32, 0xBB }, + { 0x32, 0x3B }, + { 0x00, 0x00 }, + +}; + +static const struct regmap_config cs42l52_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42L52_MAX_REGISTER, + .reg_defaults = cs42l52_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs42l52_reg_defaults), + .readable_reg = cs42l52_readable_register, + .volatile_reg = cs42l52_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs42l52_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs42l52_private *cs42l52; + struct cs42l52_platform_data *pdata = dev_get_platdata(&i2c_client->dev); + int ret; + unsigned int devid = 0; + unsigned int reg; + u32 val32; + + cs42l52 = devm_kzalloc(&i2c_client->dev, sizeof(*cs42l52), GFP_KERNEL); + if (cs42l52 == NULL) + return -ENOMEM; + cs42l52->dev = &i2c_client->dev; + + cs42l52->regmap = devm_regmap_init_i2c(i2c_client, &cs42l52_regmap); + if (IS_ERR(cs42l52->regmap)) { + ret = PTR_ERR(cs42l52->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + if (pdata) { + cs42l52->pdata = *pdata; + } else { + pdata = devm_kzalloc(&i2c_client->dev, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (i2c_client->dev.of_node) { + if (of_property_read_bool(i2c_client->dev.of_node, + "cirrus,mica-differential-cfg")) + pdata->mica_diff_cfg = true; + + if (of_property_read_bool(i2c_client->dev.of_node, + "cirrus,micb-differential-cfg")) + pdata->micb_diff_cfg = true; + + if (of_property_read_u32(i2c_client->dev.of_node, + "cirrus,micbias-lvl", &val32) >= 0) + pdata->micbias_lvl = val32; + + if (of_property_read_u32(i2c_client->dev.of_node, + "cirrus,chgfreq-divisor", &val32) >= 0) + pdata->chgfreq = val32; + + pdata->reset_gpio = + of_get_named_gpio(i2c_client->dev.of_node, + "cirrus,reset-gpio", 0); + } + cs42l52->pdata = *pdata; + } + + if (cs42l52->pdata.reset_gpio) { + ret = devm_gpio_request_one(&i2c_client->dev, + cs42l52->pdata.reset_gpio, + GPIOF_OUT_INIT_HIGH, + "CS42L52 /RST"); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to request /RST %d: %d\n", + cs42l52->pdata.reset_gpio, ret); + return ret; + } + gpio_set_value_cansleep(cs42l52->pdata.reset_gpio, 0); + gpio_set_value_cansleep(cs42l52->pdata.reset_gpio, 1); + } + + i2c_set_clientdata(i2c_client, cs42l52); + + ret = regmap_register_patch(cs42l52->regmap, cs42l52_threshold_patch, + ARRAY_SIZE(cs42l52_threshold_patch)); + if (ret != 0) + dev_warn(cs42l52->dev, "Failed to apply regmap patch: %d\n", + ret); + + ret = regmap_read(cs42l52->regmap, CS42L52_CHIP, ®); + devid = reg & CS42L52_CHIP_ID_MASK; + if (devid != CS42L52_CHIP_ID) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS42L52 Device ID (%X). Expected %X\n", + devid, CS42L52_CHIP_ID); + return ret; + } + + dev_info(&i2c_client->dev, "Cirrus Logic CS42L52, Revision: %02X\n", + reg & CS42L52_CHIP_REV_MASK); + + /* Set Platform Data */ + if (cs42l52->pdata.mica_diff_cfg) + regmap_update_bits(cs42l52->regmap, CS42L52_MICA_CTL, + CS42L52_MIC_CTL_TYPE_MASK, + cs42l52->pdata.mica_diff_cfg << + CS42L52_MIC_CTL_TYPE_SHIFT); + + if (cs42l52->pdata.micb_diff_cfg) + regmap_update_bits(cs42l52->regmap, CS42L52_MICB_CTL, + CS42L52_MIC_CTL_TYPE_MASK, + cs42l52->pdata.micb_diff_cfg << + CS42L52_MIC_CTL_TYPE_SHIFT); + + if (cs42l52->pdata.chgfreq) + regmap_update_bits(cs42l52->regmap, CS42L52_CHARGE_PUMP, + CS42L52_CHARGE_PUMP_MASK, + cs42l52->pdata.chgfreq << + CS42L52_CHARGE_PUMP_SHIFT); + + if (cs42l52->pdata.micbias_lvl) + regmap_update_bits(cs42l52->regmap, CS42L52_IFACE_CTL2, + CS42L52_IFACE_CTL2_BIAS_LVL, + cs42l52->pdata.micbias_lvl); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_cs42l52, &cs42l52_dai, 1); + if (ret < 0) + return ret; + return 0; +} + +static const struct of_device_id cs42l52_of_match[] = { + { .compatible = "cirrus,cs42l52", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs42l52_of_match); + + +static const struct i2c_device_id cs42l52_id[] = { + { "cs42l52", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs42l52_id); + +static struct i2c_driver cs42l52_i2c_driver = { + .driver = { + .name = "cs42l52", + .of_match_table = cs42l52_of_match, + }, + .id_table = cs42l52_id, + .probe = cs42l52_i2c_probe, +}; + +module_i2c_driver(cs42l52_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS42L52 driver"); +MODULE_AUTHOR("Georgi Vlaev, Nucleus Systems Ltd, "); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l52.h b/sound/soc/codecs/cs42l52.h new file mode 100644 index 000000000..ac445993e --- /dev/null +++ b/sound/soc/codecs/cs42l52.h @@ -0,0 +1,274 @@ +/* + * cs42l52.h -- CS42L52 ALSA SoC audio driver + * + * Copyright 2012 CirrusLogic, Inc. + * + * Author: Georgi Vlaev + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS42L52_H__ +#define __CS42L52_H__ + +#define CS42L52_NAME "CS42L52" +#define CS42L52_DEFAULT_CLK 12000000 +#define CS42L52_MIN_CLK 11000000 +#define CS42L52_MAX_CLK 27000000 +#define CS42L52_DEFAULT_FORMAT SNDRV_PCM_FMTBIT_S16_LE +#define CS42L52_DEFAULT_MAX_CHANS 2 +#define CS42L52_SYSCLK 1 + +#define CS42L52_CHIP_SWICTH (1 << 17) +#define CS42L52_ALL_IN_ONE (1 << 16) +#define CS42L52_CHIP_ONE 0x00 +#define CS42L52_CHIP_TWO 0x01 +#define CS42L52_CHIP_THR 0x02 +#define CS42L52_CHIP_MASK 0x0f + +#define CS42L52_FIX_BITS_CTL 0x00 +#define CS42L52_CHIP 0x01 +#define CS42L52_CHIP_ID 0xE0 +#define CS42L52_CHIP_ID_MASK 0xF8 +#define CS42L52_CHIP_REV_A0 0x00 +#define CS42L52_CHIP_REV_A1 0x01 +#define CS42L52_CHIP_REV_B0 0x02 +#define CS42L52_CHIP_REV_MASK 0x07 + +#define CS42L52_PWRCTL1 0x02 +#define CS42L52_PWRCTL1_PDN_ALL 0x9F +#define CS42L52_PWRCTL1_PDN_CHRG 0x80 +#define CS42L52_PWRCTL1_PDN_PGAB 0x10 +#define CS42L52_PWRCTL1_PDN_PGAA 0x08 +#define CS42L52_PWRCTL1_PDN_ADCB 0x04 +#define CS42L52_PWRCTL1_PDN_ADCA 0x02 +#define CS42L52_PWRCTL1_PDN_CODEC 0x01 + +#define CS42L52_PWRCTL2 0x03 +#define CS42L52_PWRCTL2_OVRDB (1 << 4) +#define CS42L52_PWRCTL2_OVRDA (1 << 3) +#define CS42L52_PWRCTL2_PDN_MICB (1 << 2) +#define CS42L52_PWRCTL2_PDN_MICB_SHIFT 2 +#define CS42L52_PWRCTL2_PDN_MICA (1 << 1) +#define CS42L52_PWRCTL2_PDN_MICA_SHIFT 1 +#define CS42L52_PWRCTL2_PDN_MICBIAS (1 << 0) +#define CS42L52_PWRCTL2_PDN_MICBIAS_SHIFT 0 + +#define CS42L52_PWRCTL3 0x04 +#define CS42L52_PWRCTL3_HPB_PDN_SHIFT 6 +#define CS42L52_PWRCTL3_HPB_ON_LOW 0x00 +#define CS42L52_PWRCTL3_HPB_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_HPB_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_HPB_ALWAYS_OFF 0x03 +#define CS42L52_PWRCTL3_HPA_PDN_SHIFT 4 +#define CS42L52_PWRCTL3_HPA_ON_LOW 0x00 +#define CS42L52_PWRCTL3_HPA_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_HPA_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_HPA_ALWAYS_OFF 0x03 +#define CS42L52_PWRCTL3_SPKB_PDN_SHIFT 2 +#define CS42L52_PWRCTL3_SPKB_ON_LOW 0x00 +#define CS42L52_PWRCTL3_SPKB_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_SPKB_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_PDN_SPKB (1 << 2) +#define CS42L52_PWRCTL3_PDN_SPKA (1 << 0) +#define CS42L52_PWRCTL3_SPKA_PDN_SHIFT 0 +#define CS42L52_PWRCTL3_SPKA_ON_LOW 0x00 +#define CS42L52_PWRCTL3_SPKA_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_SPKA_ALWAYS_ON 0x02 + +#define CS42L52_DEFAULT_OUTPUT_STATE 0x05 +#define CS42L52_PWRCTL3_CONF_MASK 0x03 + +#define CS42L52_CLK_CTL 0x05 +#define CLK_AUTODECT_ENABLE (1 << 7) +#define CLK_SPEED_SHIFT 5 +#define CLK_DS_MODE 0x00 +#define CLK_SS_MODE 0x01 +#define CLK_HS_MODE 0x02 +#define CLK_QS_MODE 0x03 +#define CLK_32K_SR_SHIFT 4 +#define CLK_32K 0x01 +#define CLK_NO_32K 0x00 +#define CLK_27M_MCLK_SHIFT 3 +#define CLK_27M_MCLK 0x01 +#define CLK_NO_27M 0x00 +#define CLK_RATIO_SHIFT 1 +#define CLK_R_128 0x00 +#define CLK_R_125 0x01 +#define CLK_R_132 0x02 +#define CLK_R_136 0x03 + +#define CS42L52_IFACE_CTL1 0x06 +#define CS42L52_IFACE_CTL1_MASTER (1 << 7) +#define CS42L52_IFACE_CTL1_SLAVE (0 << 7) +#define CS42L52_IFACE_CTL1_INV_SCLK (1 << 6) +#define CS42L52_IFACE_CTL1_ADC_FMT_I2S (1 << 5) +#define CS42L52_IFACE_CTL1_ADC_FMT_LEFT_J (0 << 5) +#define CS42L52_IFACE_CTL1_DSP_MODE_EN (1 << 4) +#define CS42L52_IFACE_CTL1_DAC_FMT_LEFT_J (0 << 2) +#define CS42L52_IFACE_CTL1_DAC_FMT_I2S (1 << 2) +#define CS42L52_IFACE_CTL1_DAC_FMT_RIGHT_J (2 << 2) +#define CS42L52_IFACE_CTL1_WL_32BIT (0x00) +#define CS42L52_IFACE_CTL1_WL_24BIT (0x01) +#define CS42L52_IFACE_CTL1_WL_20BIT (0x02) +#define CS42L52_IFACE_CTL1_WL_16BIT (0x03) +#define CS42L52_IFACE_CTL1_WL_MASK 0xFFFF + +#define CS42L52_IFACE_CTL2 0x07 +#define CS42L52_IFACE_CTL2_SC_MC_EQ (1 << 6) +#define CS42L52_IFACE_CTL2_LOOPBACK (1 << 5) +#define CS42L52_IFACE_CTL2_S_MODE_OUTPUT_EN (0 << 4) +#define CS42L52_IFACE_CTL2_S_MODE_OUTPUT_HIZ (1 << 4) +#define CS42L52_IFACE_CTL2_HP_SW_INV (1 << 3) +#define CS42L52_IFACE_CTL2_BIAS_LVL 0x07 + +#define CS42L52_ADC_PGA_A 0x08 +#define CS42L52_ADC_PGA_B 0x09 +#define CS42L52_ADC_SEL_SHIFT 5 +#define CS42L52_ADC_SEL_AIN1 0x00 +#define CS42L52_ADC_SEL_AIN2 0x01 +#define CS42L52_ADC_SEL_AIN3 0x02 +#define CS42L52_ADC_SEL_AIN4 0x03 +#define CS42L52_ADC_SEL_PGA 0x04 + +#define CS42L52_ANALOG_HPF_CTL 0x0A +#define CS42L52_HPF_CTL_ANLGSFTB (1 << 3) +#define CS42L52_HPF_CTL_ANLGSFTA (1 << 0) + +#define CS42L52_ADC_HPF_FREQ 0x0B +#define CS42L52_ADC_MISC_CTL 0x0C +#define CS42L52_ADC_MISC_CTL_SOURCE_DSP (1 << 6) + +#define CS42L52_PB_CTL1 0x0D +#define CS42L52_PB_CTL1_HP_GAIN_SHIFT 5 +#define CS42L52_PB_CTL1_HP_GAIN_03959 0x00 +#define CS42L52_PB_CTL1_HP_GAIN_04571 0x01 +#define CS42L52_PB_CTL1_HP_GAIN_05111 0x02 +#define CS42L52_PB_CTL1_HP_GAIN_06047 0x03 +#define CS42L52_PB_CTL1_HP_GAIN_07099 0x04 +#define CS42L52_PB_CTL1_HP_GAIN_08399 0x05 +#define CS42L52_PB_CTL1_HP_GAIN_10000 0x06 +#define CS42L52_PB_CTL1_HP_GAIN_11430 0x07 +#define CS42L52_PB_CTL1_INV_PCMB (1 << 3) +#define CS42L52_PB_CTL1_INV_PCMA (1 << 2) +#define CS42L52_PB_CTL1_MSTB_MUTE (1 << 1) +#define CS42L52_PB_CTL1_MSTA_MUTE (1 << 0) +#define CS42L52_PB_CTL1_MUTE_MASK 0x03 +#define CS42L52_PB_CTL1_MUTE 3 +#define CS42L52_PB_CTL1_UNMUTE 0 + +#define CS42L52_MISC_CTL 0x0E +#define CS42L52_MISC_CTL_DEEMPH (1 << 2) +#define CS42L52_MISC_CTL_DIGSFT (1 << 1) +#define CS42L52_MISC_CTL_DIGZC (1 << 0) + +#define CS42L52_PB_CTL2 0x0F +#define CS42L52_PB_CTL2_HPB_MUTE (1 << 7) +#define CS42L52_PB_CTL2_HPA_MUTE (1 << 6) +#define CS42L52_PB_CTL2_SPKB_MUTE (1 << 5) +#define CS42L52_PB_CTL2_SPKA_MUTE (1 << 4) +#define CS42L52_PB_CTL2_SPK_SWAP (1 << 2) +#define CS42L52_PB_CTL2_SPK_MONO (1 << 1) +#define CS42L52_PB_CTL2_SPK_MUTE50 (1 << 0) + +#define CS42L52_MICA_CTL 0x10 +#define CS42L52_MICB_CTL 0x11 +#define CS42L52_MIC_CTL_MIC_SEL_MASK 0xBF +#define CS42L52_MIC_CTL_MIC_SEL_SHIFT 6 +#define CS42L52_MIC_CTL_TYPE_MASK 0x20 +#define CS42L52_MIC_CTL_TYPE_SHIFT 5 + + +#define CS42L52_PGAA_CTL 0x12 +#define CS42L52_PGAB_CTL 0x13 +#define CS42L52_PGAX_CTL_VOL_12DB 24 +#define CS42L52_PGAX_CTL_VOL_6DB 12 /*step size 0.5db*/ + +#define CS42L52_PASSTHRUA_VOL 0x14 +#define CS42L52_PASSTHRUB_VOL 0x15 + +#define CS42L52_ADCA_VOL 0x16 +#define CS42L52_ADCB_VOL 0x17 +#define CS42L52_ADCX_VOL_24DB 24 /*step size 1db*/ +#define CS42L52_ADCX_VOL_12DB 12 +#define CS42L52_ADCX_VOL_6DB 6 + +#define CS42L52_ADCA_MIXER_VOL 0x18 +#define CS42L52_ADCB_MIXER_VOL 0x19 +#define CS42L52_ADC_MIXER_VOL_12DB 0x18 + +#define CS42L52_PCMA_MIXER_VOL 0x1A +#define CS42L52_PCMB_MIXER_VOL 0x1B + +#define CS42L52_BEEP_FREQ 0x1C +#define CS42L52_BEEP_VOL 0x1D +#define CS42L52_BEEP_TONE_CTL 0x1E +#define CS42L52_BEEP_RATE_SHIFT 4 +#define CS42L52_BEEP_RATE_MASK 0x0F + +#define CS42L52_TONE_CTL 0x1F +#define CS42L52_BEEP_EN_MASK 0x3F + +#define CS42L52_MASTERA_VOL 0x20 +#define CS42L52_MASTERB_VOL 0x21 + +#define CS42L52_HPA_VOL 0x22 +#define CS42L52_HPB_VOL 0x23 +#define CS42L52_DEFAULT_HP_VOL 0xF0 + +#define CS42L52_SPKA_VOL 0x24 +#define CS42L52_SPKB_VOL 0x25 +#define CS42L52_DEFAULT_SPK_VOL 0xF0 + +#define CS42L52_ADC_PCM_MIXER 0x26 + +#define CS42L52_LIMITER_CTL1 0x27 +#define CS42L52_LIMITER_CTL2 0x28 +#define CS42L52_LIMITER_AT_RATE 0x29 + +#define CS42L52_ALC_CTL 0x2A +#define CS42L52_ALC_CTL_ALCB_ENABLE_SHIFT 7 +#define CS42L52_ALC_CTL_ALCA_ENABLE_SHIFT 6 +#define CS42L52_ALC_CTL_FASTEST_ATTACK 0 + +#define CS42L52_ALC_RATE 0x2B +#define CS42L52_ALC_SLOWEST_RELEASE 0x3F + +#define CS42L52_ALC_THRESHOLD 0x2C +#define CS42L52_ALC_MAX_RATE_SHIFT 5 +#define CS42L52_ALC_MIN_RATE_SHIFT 2 +#define CS42L52_ALC_RATE_0DB 0 +#define CS42L52_ALC_RATE_3DB 1 +#define CS42L52_ALC_RATE_6DB 2 + +#define CS42L52_NOISE_GATE_CTL 0x2D +#define CS42L52_NG_ENABLE_SHIFT 6 +#define CS42L52_NG_THRESHOLD_SHIFT 2 +#define CS42L52_NG_MIN_70DB 2 +#define CS42L52_NG_DELAY_SHIFT 0 +#define CS42L52_NG_DELAY_100MS 1 + +#define CS42L52_CLK_STATUS 0x2E +#define CS42L52_BATT_COMPEN 0x2F + +#define CS42L52_BATT_LEVEL 0x30 +#define CS42L52_SPK_STATUS 0x31 +#define CS42L52_SPK_STATUS_PIN_SHIFT 3 +#define CS42L52_SPK_STATUS_PIN_HIGH 1 + +#define CS42L52_TEM_CTL 0x32 +#define CS42L52_TEM_CTL_SET 0x80 +#define CS42L52_THE_FOLDBACK 0x33 +#define CS42L52_CHARGE_PUMP 0x34 +#define CS42L52_CHARGE_PUMP_MASK 0xF0 +#define CS42L52_CHARGE_PUMP_SHIFT 4 +#define CS42L52_FIX_BITS1 0x3E +#define CS42L52_FIX_BITS2 0x47 + +#define CS42L52_MAX_REGISTER 0x47 + +#endif diff --git a/sound/soc/codecs/cs42l56.c b/sound/soc/codecs/cs42l56.c new file mode 100644 index 000000000..deaad703a --- /dev/null +++ b/sound/soc/codecs/cs42l56.c @@ -0,0 +1,1367 @@ +/* + * cs42l56.c -- CS42L56 ALSA SoC audio driver + * + * Copyright 2014 CirrusLogic, Inc. + * + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs42l56.h" + +#define CS42L56_NUM_SUPPLIES 3 +static const char *const cs42l56_supply_names[CS42L56_NUM_SUPPLIES] = { + "VA", + "VCP", + "VLDO", +}; + +struct cs42l56_private { + struct regmap *regmap; + struct snd_soc_component *component; + struct device *dev; + struct cs42l56_platform_data pdata; + struct regulator_bulk_data supplies[CS42L56_NUM_SUPPLIES]; + u32 mclk; + u8 mclk_prediv; + u8 mclk_div2; + u8 mclk_ratio; + u8 iface; + u8 iface_fmt; + u8 iface_inv; +#if IS_ENABLED(CONFIG_INPUT) + struct input_dev *beep; + struct work_struct beep_work; + int beep_rate; +#endif +}; + +static const struct reg_default cs42l56_reg_defaults[] = { + { 3, 0x7f }, /* r03 - Power Ctl 1 */ + { 4, 0xff }, /* r04 - Power Ctl 2 */ + { 5, 0x00 }, /* ro5 - Clocking Ctl 1 */ + { 6, 0x0b }, /* r06 - Clocking Ctl 2 */ + { 7, 0x00 }, /* r07 - Serial Format */ + { 8, 0x05 }, /* r08 - Class H Ctl */ + { 9, 0x0c }, /* r09 - Misc Ctl */ + { 10, 0x80 }, /* r0a - INT Status */ + { 11, 0x00 }, /* r0b - Playback Ctl */ + { 12, 0x0c }, /* r0c - DSP Mute Ctl */ + { 13, 0x00 }, /* r0d - ADCA Mixer Volume */ + { 14, 0x00 }, /* r0e - ADCB Mixer Volume */ + { 15, 0x00 }, /* r0f - PCMA Mixer Volume */ + { 16, 0x00 }, /* r10 - PCMB Mixer Volume */ + { 17, 0x00 }, /* r11 - Analog Input Advisory Volume */ + { 18, 0x00 }, /* r12 - Digital Input Advisory Volume */ + { 19, 0x00 }, /* r13 - Master A Volume */ + { 20, 0x00 }, /* r14 - Master B Volume */ + { 21, 0x00 }, /* r15 - Beep Freq / On Time */ + { 22, 0x00 }, /* r16 - Beep Volume / Off Time */ + { 23, 0x00 }, /* r17 - Beep Tone Ctl */ + { 24, 0x88 }, /* r18 - Tone Ctl */ + { 25, 0x00 }, /* r19 - Channel Mixer & Swap */ + { 26, 0x00 }, /* r1a - AIN Ref Config / ADC Mux */ + { 27, 0xa0 }, /* r1b - High-Pass Filter Ctl */ + { 28, 0x00 }, /* r1c - Misc ADC Ctl */ + { 29, 0x00 }, /* r1d - Gain & Bias Ctl */ + { 30, 0x00 }, /* r1e - PGAA Mux & Volume */ + { 31, 0x00 }, /* r1f - PGAB Mux & Volume */ + { 32, 0x00 }, /* r20 - ADCA Attenuator */ + { 33, 0x00 }, /* r21 - ADCB Attenuator */ + { 34, 0x00 }, /* r22 - ALC Enable & Attack Rate */ + { 35, 0xbf }, /* r23 - ALC Release Rate */ + { 36, 0x00 }, /* r24 - ALC Threshold */ + { 37, 0x00 }, /* r25 - Noise Gate Ctl */ + { 38, 0x00 }, /* r26 - ALC, Limiter, SFT, ZeroCross */ + { 39, 0x00 }, /* r27 - Analog Mute, LO & HP Mux */ + { 40, 0x00 }, /* r28 - HP A Volume */ + { 41, 0x00 }, /* r29 - HP B Volume */ + { 42, 0x00 }, /* r2a - LINEOUT A Volume */ + { 43, 0x00 }, /* r2b - LINEOUT B Volume */ + { 44, 0x00 }, /* r2c - Limit Threshold Ctl */ + { 45, 0x7f }, /* r2d - Limiter Ctl & Release Rate */ + { 46, 0x00 }, /* r2e - Limiter Attack Rate */ +}; + +static bool cs42l56_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L56_CHIP_ID_1 ... CS42L56_LIM_ATTACK_RATE: + return true; + default: + return false; + } +} + +static bool cs42l56_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L56_INT_STATUS: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(beep_tlv, -5000, 200, 0); +static DECLARE_TLV_DB_SCALE(hl_tlv, -6000, 50, 0); +static DECLARE_TLV_DB_SCALE(adv_tlv, -10200, 50, 0); +static DECLARE_TLV_DB_SCALE(adc_tlv, -9600, 100, 0); +static DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); +static DECLARE_TLV_DB_SCALE(preamp_tlv, 0, 1000, 0); +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); + +static const DECLARE_TLV_DB_RANGE(ngnb_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-8200, 600, 0), + 2, 5, TLV_DB_SCALE_ITEM(-7600, 300, 0) +); +static const DECLARE_TLV_DB_RANGE(ngb_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-6400, 600, 0), + 3, 7, TLV_DB_SCALE_ITEM(-4600, 300, 0) +); +static const DECLARE_TLV_DB_RANGE(alc_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-3000, 600, 0), + 3, 7, TLV_DB_SCALE_ITEM(-1200, 300, 0) +); + +static const char * const beep_config_text[] = { + "Off", "Single", "Multiple", "Continuous" +}; + +static const struct soc_enum beep_config_enum = + SOC_ENUM_SINGLE(CS42L56_BEEP_TONE_CFG, 6, + ARRAY_SIZE(beep_config_text), beep_config_text); + +static const char * const beep_pitch_text[] = { + "C4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", + "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7" +}; + +static const struct soc_enum beep_pitch_enum = + SOC_ENUM_SINGLE(CS42L56_BEEP_FREQ_ONTIME, 4, + ARRAY_SIZE(beep_pitch_text), beep_pitch_text); + +static const char * const beep_ontime_text[] = { + "86 ms", "430 ms", "780 ms", "1.20 s", "1.50 s", + "1.80 s", "2.20 s", "2.50 s", "2.80 s", "3.20 s", + "3.50 s", "3.80 s", "4.20 s", "4.50 s", "4.80 s", "5.20 s" +}; + +static const struct soc_enum beep_ontime_enum = + SOC_ENUM_SINGLE(CS42L56_BEEP_FREQ_ONTIME, 0, + ARRAY_SIZE(beep_ontime_text), beep_ontime_text); + +static const char * const beep_offtime_text[] = { + "1.23 s", "2.58 s", "3.90 s", "5.20 s", + "6.60 s", "8.05 s", "9.35 s", "10.80 s" +}; + +static const struct soc_enum beep_offtime_enum = + SOC_ENUM_SINGLE(CS42L56_BEEP_FREQ_OFFTIME, 5, + ARRAY_SIZE(beep_offtime_text), beep_offtime_text); + +static const char * const beep_treble_text[] = { + "5kHz", "7kHz", "10kHz", "15kHz" +}; + +static const struct soc_enum beep_treble_enum = + SOC_ENUM_SINGLE(CS42L56_BEEP_TONE_CFG, 3, + ARRAY_SIZE(beep_treble_text), beep_treble_text); + +static const char * const beep_bass_text[] = { + "50Hz", "100Hz", "200Hz", "250Hz" +}; + +static const struct soc_enum beep_bass_enum = + SOC_ENUM_SINGLE(CS42L56_BEEP_TONE_CFG, 1, + ARRAY_SIZE(beep_bass_text), beep_bass_text); + +static const char * const adc_swap_text[] = { + "None", "A+B/2", "A-B/2", "Swap" +}; + +static const struct soc_enum adc_swap_enum = + SOC_ENUM_SINGLE(CS42L56_MISC_ADC_CTL, 3, + ARRAY_SIZE(adc_swap_text), adc_swap_text); + +static const char * const pgaa_mux_text[] = { + "AIN1A", "AIN2A", "AIN3A"}; + +static const struct soc_enum pgaa_mux_enum = + SOC_ENUM_SINGLE(CS42L56_PGAA_MUX_VOLUME, 0, + ARRAY_SIZE(pgaa_mux_text), + pgaa_mux_text); + +static const struct snd_kcontrol_new pgaa_mux = + SOC_DAPM_ENUM("Route", pgaa_mux_enum); + +static const char * const pgab_mux_text[] = { + "AIN1B", "AIN2B", "AIN3B"}; + +static const struct soc_enum pgab_mux_enum = + SOC_ENUM_SINGLE(CS42L56_PGAB_MUX_VOLUME, 0, + ARRAY_SIZE(pgab_mux_text), + pgab_mux_text); + +static const struct snd_kcontrol_new pgab_mux = + SOC_DAPM_ENUM("Route", pgab_mux_enum); + +static const char * const adca_mux_text[] = { + "PGAA", "AIN1A", "AIN2A", "AIN3A"}; + +static const struct soc_enum adca_mux_enum = + SOC_ENUM_SINGLE(CS42L56_AIN_REFCFG_ADC_MUX, 0, + ARRAY_SIZE(adca_mux_text), + adca_mux_text); + +static const struct snd_kcontrol_new adca_mux = + SOC_DAPM_ENUM("Route", adca_mux_enum); + +static const char * const adcb_mux_text[] = { + "PGAB", "AIN1B", "AIN2B", "AIN3B"}; + +static const struct soc_enum adcb_mux_enum = + SOC_ENUM_SINGLE(CS42L56_AIN_REFCFG_ADC_MUX, 2, + ARRAY_SIZE(adcb_mux_text), + adcb_mux_text); + +static const struct snd_kcontrol_new adcb_mux = + SOC_DAPM_ENUM("Route", adcb_mux_enum); + +static const char * const left_swap_text[] = { + "Left", "LR 2", "Right"}; + +static const char * const right_swap_text[] = { + "Right", "LR 2", "Left"}; + +static const unsigned int swap_values[] = { 0, 1, 3 }; + +static const struct soc_enum adca_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L56_CHAN_MIX_SWAP, 0, 3, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); +static const struct snd_kcontrol_new adca_swap_mux = + SOC_DAPM_ENUM("Route", adca_swap_enum); + +static const struct soc_enum pcma_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L56_CHAN_MIX_SWAP, 4, 3, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); +static const struct snd_kcontrol_new pcma_swap_mux = + SOC_DAPM_ENUM("Route", pcma_swap_enum); + +static const struct soc_enum adcb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L56_CHAN_MIX_SWAP, 2, 3, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); +static const struct snd_kcontrol_new adcb_swap_mux = + SOC_DAPM_ENUM("Route", adcb_swap_enum); + +static const struct soc_enum pcmb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L56_CHAN_MIX_SWAP, 6, 3, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); +static const struct snd_kcontrol_new pcmb_swap_mux = + SOC_DAPM_ENUM("Route", pcmb_swap_enum); + +static const struct snd_kcontrol_new hpa_switch = + SOC_DAPM_SINGLE("Switch", CS42L56_PWRCTL_2, 6, 1, 1); + +static const struct snd_kcontrol_new hpb_switch = + SOC_DAPM_SINGLE("Switch", CS42L56_PWRCTL_2, 4, 1, 1); + +static const struct snd_kcontrol_new loa_switch = + SOC_DAPM_SINGLE("Switch", CS42L56_PWRCTL_2, 2, 1, 1); + +static const struct snd_kcontrol_new lob_switch = + SOC_DAPM_SINGLE("Switch", CS42L56_PWRCTL_2, 0, 1, 1); + +static const char * const hploa_input_text[] = { + "DACA", "PGAA"}; + +static const struct soc_enum lineouta_input_enum = + SOC_ENUM_SINGLE(CS42L56_AMUTE_HPLO_MUX, 2, + ARRAY_SIZE(hploa_input_text), + hploa_input_text); + +static const struct snd_kcontrol_new lineouta_input = + SOC_DAPM_ENUM("Route", lineouta_input_enum); + +static const struct soc_enum hpa_input_enum = + SOC_ENUM_SINGLE(CS42L56_AMUTE_HPLO_MUX, 0, + ARRAY_SIZE(hploa_input_text), + hploa_input_text); + +static const struct snd_kcontrol_new hpa_input = + SOC_DAPM_ENUM("Route", hpa_input_enum); + +static const char * const hplob_input_text[] = { + "DACB", "PGAB"}; + +static const struct soc_enum lineoutb_input_enum = + SOC_ENUM_SINGLE(CS42L56_AMUTE_HPLO_MUX, 3, + ARRAY_SIZE(hplob_input_text), + hplob_input_text); + +static const struct snd_kcontrol_new lineoutb_input = + SOC_DAPM_ENUM("Route", lineoutb_input_enum); + +static const struct soc_enum hpb_input_enum = + SOC_ENUM_SINGLE(CS42L56_AMUTE_HPLO_MUX, 1, + ARRAY_SIZE(hplob_input_text), + hplob_input_text); + +static const struct snd_kcontrol_new hpb_input = + SOC_DAPM_ENUM("Route", hpb_input_enum); + +static const char * const dig_mux_text[] = { + "ADC", "DSP"}; + +static const struct soc_enum dig_mux_enum = + SOC_ENUM_SINGLE(CS42L56_MISC_CTL, 7, + ARRAY_SIZE(dig_mux_text), + dig_mux_text); + +static const struct snd_kcontrol_new dig_mux = + SOC_DAPM_ENUM("Route", dig_mux_enum); + +static const char * const hpf_freq_text[] = { + "1.8Hz", "119Hz", "236Hz", "464Hz" +}; + +static const struct soc_enum hpfa_freq_enum = + SOC_ENUM_SINGLE(CS42L56_HPF_CTL, 0, + ARRAY_SIZE(hpf_freq_text), hpf_freq_text); + +static const struct soc_enum hpfb_freq_enum = + SOC_ENUM_SINGLE(CS42L56_HPF_CTL, 2, + ARRAY_SIZE(hpf_freq_text), hpf_freq_text); + +static const char * const ng_delay_text[] = { + "50ms", "100ms", "150ms", "200ms" +}; + +static const struct soc_enum ng_delay_enum = + SOC_ENUM_SINGLE(CS42L56_NOISE_GATE_CTL, 0, + ARRAY_SIZE(ng_delay_text), ng_delay_text); + +static const struct snd_kcontrol_new cs42l56_snd_controls[] = { + + SOC_DOUBLE_R_SX_TLV("Master Volume", CS42L56_MASTER_A_VOLUME, + CS42L56_MASTER_B_VOLUME, 0, 0x34, 0xE4, adv_tlv), + SOC_DOUBLE("Master Mute Switch", CS42L56_DSP_MUTE_CTL, 0, 1, 1, 1), + + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", CS42L56_ADCA_MIX_VOLUME, + CS42L56_ADCB_MIX_VOLUME, 0, 0x88, 0x90, hl_tlv), + SOC_DOUBLE("ADC Mixer Mute Switch", CS42L56_DSP_MUTE_CTL, 6, 7, 1, 1), + + SOC_DOUBLE_R_SX_TLV("PCM Mixer Volume", CS42L56_PCMA_MIX_VOLUME, + CS42L56_PCMB_MIX_VOLUME, 0, 0x88, 0x90, hl_tlv), + SOC_DOUBLE("PCM Mixer Mute Switch", CS42L56_DSP_MUTE_CTL, 4, 5, 1, 1), + + SOC_SINGLE_TLV("Analog Advisory Volume", + CS42L56_ANAINPUT_ADV_VOLUME, 0, 0x00, 1, adv_tlv), + SOC_SINGLE_TLV("Digital Advisory Volume", + CS42L56_DIGINPUT_ADV_VOLUME, 0, 0x00, 1, adv_tlv), + + SOC_DOUBLE_R_SX_TLV("PGA Volume", CS42L56_PGAA_MUX_VOLUME, + CS42L56_PGAB_MUX_VOLUME, 0, 0x34, 0x24, pga_tlv), + SOC_DOUBLE_R_TLV("ADC Volume", CS42L56_ADCA_ATTENUATOR, + CS42L56_ADCB_ATTENUATOR, 0, 0x00, 1, adc_tlv), + SOC_DOUBLE("ADC Mute Switch", CS42L56_MISC_ADC_CTL, 2, 3, 1, 1), + SOC_DOUBLE("ADC Boost Switch", CS42L56_GAIN_BIAS_CTL, 3, 2, 1, 1), + + SOC_DOUBLE_R_SX_TLV("Headphone Volume", CS42L56_HPA_VOLUME, + CS42L56_HPB_VOLUME, 0, 0x44, 0x48, hl_tlv), + SOC_DOUBLE_R_SX_TLV("LineOut Volume", CS42L56_LOA_VOLUME, + CS42L56_LOB_VOLUME, 0, 0x44, 0x48, hl_tlv), + + SOC_SINGLE_TLV("Bass Shelving Volume", CS42L56_TONE_CTL, + 0, 0x00, 1, tone_tlv), + SOC_SINGLE_TLV("Treble Shelving Volume", CS42L56_TONE_CTL, + 4, 0x00, 1, tone_tlv), + + SOC_DOUBLE_TLV("PGA Preamp Volume", CS42L56_GAIN_BIAS_CTL, + 4, 6, 0x02, 1, preamp_tlv), + + SOC_SINGLE("DSP Switch", CS42L56_PLAYBACK_CTL, 7, 1, 1), + SOC_SINGLE("Gang Playback Switch", CS42L56_PLAYBACK_CTL, 4, 1, 1), + SOC_SINGLE("Gang ADC Switch", CS42L56_MISC_ADC_CTL, 7, 1, 1), + SOC_SINGLE("Gang PGA Switch", CS42L56_MISC_ADC_CTL, 6, 1, 1), + + SOC_SINGLE("PCMA Invert", CS42L56_PLAYBACK_CTL, 2, 1, 1), + SOC_SINGLE("PCMB Invert", CS42L56_PLAYBACK_CTL, 3, 1, 1), + SOC_SINGLE("ADCA Invert", CS42L56_MISC_ADC_CTL, 2, 1, 1), + SOC_SINGLE("ADCB Invert", CS42L56_MISC_ADC_CTL, 3, 1, 1), + + SOC_DOUBLE("HPF Switch", CS42L56_HPF_CTL, 5, 7, 1, 1), + SOC_DOUBLE("HPF Freeze Switch", CS42L56_HPF_CTL, 4, 6, 1, 1), + SOC_ENUM("HPFA Corner Freq", hpfa_freq_enum), + SOC_ENUM("HPFB Corner Freq", hpfb_freq_enum), + + SOC_SINGLE("Analog Soft Ramp", CS42L56_MISC_CTL, 4, 1, 1), + SOC_DOUBLE("Analog Soft Ramp Disable", CS42L56_ALC_LIM_SFT_ZC, + 7, 5, 1, 1), + SOC_SINGLE("Analog Zero Cross", CS42L56_MISC_CTL, 3, 1, 1), + SOC_DOUBLE("Analog Zero Cross Disable", CS42L56_ALC_LIM_SFT_ZC, + 6, 4, 1, 1), + SOC_SINGLE("Digital Soft Ramp", CS42L56_MISC_CTL, 2, 1, 1), + SOC_SINGLE("Digital Soft Ramp Disable", CS42L56_ALC_LIM_SFT_ZC, + 3, 1, 1), + + SOC_SINGLE("HL Deemphasis", CS42L56_PLAYBACK_CTL, 6, 1, 1), + + SOC_SINGLE("ALC Switch", CS42L56_ALC_EN_ATTACK_RATE, 6, 1, 1), + SOC_SINGLE("ALC Limit All Switch", CS42L56_ALC_RELEASE_RATE, 7, 1, 1), + SOC_SINGLE_RANGE("ALC Attack", CS42L56_ALC_EN_ATTACK_RATE, + 0, 0, 0x3f, 0), + SOC_SINGLE_RANGE("ALC Release", CS42L56_ALC_RELEASE_RATE, + 0, 0x3f, 0, 0), + SOC_SINGLE_TLV("ALC MAX", CS42L56_ALC_THRESHOLD, + 5, 0x07, 1, alc_tlv), + SOC_SINGLE_TLV("ALC MIN", CS42L56_ALC_THRESHOLD, + 2, 0x07, 1, alc_tlv), + + SOC_SINGLE("Limiter Switch", CS42L56_LIM_CTL_RELEASE_RATE, 7, 1, 1), + SOC_SINGLE("Limit All Switch", CS42L56_LIM_CTL_RELEASE_RATE, 6, 1, 1), + SOC_SINGLE_RANGE("Limiter Attack", CS42L56_LIM_ATTACK_RATE, + 0, 0, 0x3f, 0), + SOC_SINGLE_RANGE("Limiter Release", CS42L56_LIM_CTL_RELEASE_RATE, + 0, 0x3f, 0, 0), + SOC_SINGLE_TLV("Limiter MAX", CS42L56_LIM_THRESHOLD_CTL, + 5, 0x07, 1, alc_tlv), + SOC_SINGLE_TLV("Limiter Cushion", CS42L56_ALC_THRESHOLD, + 2, 0x07, 1, alc_tlv), + + SOC_SINGLE("NG Switch", CS42L56_NOISE_GATE_CTL, 6, 1, 1), + SOC_SINGLE("NG All Switch", CS42L56_NOISE_GATE_CTL, 7, 1, 1), + SOC_SINGLE("NG Boost Switch", CS42L56_NOISE_GATE_CTL, 5, 1, 1), + SOC_SINGLE_TLV("NG Unboost Threshold", CS42L56_NOISE_GATE_CTL, + 2, 0x07, 1, ngnb_tlv), + SOC_SINGLE_TLV("NG Boost Threshold", CS42L56_NOISE_GATE_CTL, + 2, 0x07, 1, ngb_tlv), + SOC_ENUM("NG Delay", ng_delay_enum), + + SOC_ENUM("Beep Config", beep_config_enum), + SOC_ENUM("Beep Pitch", beep_pitch_enum), + SOC_ENUM("Beep on Time", beep_ontime_enum), + SOC_ENUM("Beep off Time", beep_offtime_enum), + SOC_SINGLE_SX_TLV("Beep Volume", CS42L56_BEEP_FREQ_OFFTIME, + 0, 0x07, 0x23, beep_tlv), + SOC_SINGLE("Beep Tone Ctl Switch", CS42L56_BEEP_TONE_CFG, 0, 1, 1), + SOC_ENUM("Beep Treble Corner Freq", beep_treble_enum), + SOC_ENUM("Beep Bass Corner Freq", beep_bass_enum), + +}; + +static const struct snd_soc_dapm_widget cs42l56_dapm_widgets[] = { + + SND_SOC_DAPM_SIGGEN("Beep"), + SND_SOC_DAPM_SUPPLY("VBUF", CS42L56_PWRCTL_1, 5, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC1 Bias", CS42L56_PWRCTL_1, 4, 1), + SND_SOC_DAPM_SUPPLY("Charge Pump", CS42L56_PWRCTL_1, 3, 1, NULL, 0), + + SND_SOC_DAPM_INPUT("AIN1A"), + SND_SOC_DAPM_INPUT("AIN2A"), + SND_SOC_DAPM_INPUT("AIN1B"), + SND_SOC_DAPM_INPUT("AIN2B"), + SND_SOC_DAPM_INPUT("AIN3A"), + SND_SOC_DAPM_INPUT("AIN3B"), + + SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("SDIN", NULL, 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("Digital Output Mux", SND_SOC_NOPM, + 0, 0, &dig_mux), + + SND_SOC_DAPM_PGA("PGAA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGAB", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("PGAA Input Mux", + SND_SOC_NOPM, 0, 0, &pgaa_mux), + SND_SOC_DAPM_MUX("PGAB Input Mux", + SND_SOC_NOPM, 0, 0, &pgab_mux), + + SND_SOC_DAPM_MUX("ADCA Mux", SND_SOC_NOPM, + 0, 0, &adca_mux), + SND_SOC_DAPM_MUX("ADCB Mux", SND_SOC_NOPM, + 0, 0, &adcb_mux), + + SND_SOC_DAPM_ADC("ADCA", NULL, CS42L56_PWRCTL_1, 1, 1), + SND_SOC_DAPM_ADC("ADCB", NULL, CS42L56_PWRCTL_1, 2, 1), + + SND_SOC_DAPM_MUX("ADCA Swap Mux", SND_SOC_NOPM, 0, 0, + &adca_swap_mux), + SND_SOC_DAPM_MUX("ADCB Swap Mux", SND_SOC_NOPM, 0, 0, + &adcb_swap_mux), + + SND_SOC_DAPM_MUX("PCMA Swap Mux", SND_SOC_NOPM, 0, 0, + &pcma_swap_mux), + SND_SOC_DAPM_MUX("PCMB Swap Mux", SND_SOC_NOPM, 0, 0, + &pcmb_swap_mux), + + SND_SOC_DAPM_DAC("DACA", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DACB", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_OUTPUT("HPA"), + SND_SOC_DAPM_OUTPUT("LOA"), + SND_SOC_DAPM_OUTPUT("HPB"), + SND_SOC_DAPM_OUTPUT("LOB"), + + SND_SOC_DAPM_SWITCH("Headphone Right", + CS42L56_PWRCTL_2, 4, 1, &hpb_switch), + SND_SOC_DAPM_SWITCH("Headphone Left", + CS42L56_PWRCTL_2, 6, 1, &hpa_switch), + + SND_SOC_DAPM_SWITCH("Lineout Right", + CS42L56_PWRCTL_2, 0, 1, &lob_switch), + SND_SOC_DAPM_SWITCH("Lineout Left", + CS42L56_PWRCTL_2, 2, 1, &loa_switch), + + SND_SOC_DAPM_MUX("LINEOUTA Input Mux", SND_SOC_NOPM, + 0, 0, &lineouta_input), + SND_SOC_DAPM_MUX("LINEOUTB Input Mux", SND_SOC_NOPM, + 0, 0, &lineoutb_input), + SND_SOC_DAPM_MUX("HPA Input Mux", SND_SOC_NOPM, + 0, 0, &hpa_input), + SND_SOC_DAPM_MUX("HPB Input Mux", SND_SOC_NOPM, + 0, 0, &hpb_input), + +}; + +static const struct snd_soc_dapm_route cs42l56_audio_map[] = { + + {"HiFi Capture", "DSP", "Digital Output Mux"}, + {"HiFi Capture", "ADC", "Digital Output Mux"}, + + {"Digital Output Mux", NULL, "ADCA"}, + {"Digital Output Mux", NULL, "ADCB"}, + + {"ADCB", NULL, "ADCB Swap Mux"}, + {"ADCA", NULL, "ADCA Swap Mux"}, + + {"ADCA Swap Mux", NULL, "ADCA"}, + {"ADCB Swap Mux", NULL, "ADCB"}, + + {"DACA", "Left", "ADCA Swap Mux"}, + {"DACA", "LR 2", "ADCA Swap Mux"}, + {"DACA", "Right", "ADCA Swap Mux"}, + + {"DACB", "Left", "ADCB Swap Mux"}, + {"DACB", "LR 2", "ADCB Swap Mux"}, + {"DACB", "Right", "ADCB Swap Mux"}, + + {"ADCA Mux", NULL, "AIN3A"}, + {"ADCA Mux", NULL, "AIN2A"}, + {"ADCA Mux", NULL, "AIN1A"}, + {"ADCA Mux", NULL, "PGAA"}, + {"ADCB Mux", NULL, "AIN3B"}, + {"ADCB Mux", NULL, "AIN2B"}, + {"ADCB Mux", NULL, "AIN1B"}, + {"ADCB Mux", NULL, "PGAB"}, + + {"PGAA", "AIN1A", "PGAA Input Mux"}, + {"PGAA", "AIN2A", "PGAA Input Mux"}, + {"PGAA", "AIN3A", "PGAA Input Mux"}, + {"PGAB", "AIN1B", "PGAB Input Mux"}, + {"PGAB", "AIN2B", "PGAB Input Mux"}, + {"PGAB", "AIN3B", "PGAB Input Mux"}, + + {"PGAA Input Mux", NULL, "AIN1A"}, + {"PGAA Input Mux", NULL, "AIN2A"}, + {"PGAA Input Mux", NULL, "AIN3A"}, + {"PGAB Input Mux", NULL, "AIN1B"}, + {"PGAB Input Mux", NULL, "AIN2B"}, + {"PGAB Input Mux", NULL, "AIN3B"}, + + {"LOB", "Switch", "LINEOUTB Input Mux"}, + {"LOA", "Switch", "LINEOUTA Input Mux"}, + + {"LINEOUTA Input Mux", "PGAA", "PGAA"}, + {"LINEOUTB Input Mux", "PGAB", "PGAB"}, + {"LINEOUTA Input Mux", "DACA", "DACA"}, + {"LINEOUTB Input Mux", "DACB", "DACB"}, + + {"HPA", "Switch", "HPB Input Mux"}, + {"HPB", "Switch", "HPA Input Mux"}, + + {"HPA Input Mux", "PGAA", "PGAA"}, + {"HPB Input Mux", "PGAB", "PGAB"}, + {"HPA Input Mux", "DACA", "DACA"}, + {"HPB Input Mux", "DACB", "DACB"}, + + {"DACA", NULL, "PCMA Swap Mux"}, + {"DACB", NULL, "PCMB Swap Mux"}, + + {"PCMB Swap Mux", "Left", "HiFi Playback"}, + {"PCMB Swap Mux", "LR 2", "HiFi Playback"}, + {"PCMB Swap Mux", "Right", "HiFi Playback"}, + + {"PCMA Swap Mux", "Left", "HiFi Playback"}, + {"PCMA Swap Mux", "LR 2", "HiFi Playback"}, + {"PCMA Swap Mux", "Right", "HiFi Playback"}, + +}; + +struct cs42l56_clk_para { + u32 mclk; + u32 srate; + u8 ratio; +}; + +static const struct cs42l56_clk_para clk_ratio_table[] = { + /* 8k */ + { 6000000, 8000, CS42L56_MCLK_LRCLK_768 }, + { 6144000, 8000, CS42L56_MCLK_LRCLK_750 }, + { 12000000, 8000, CS42L56_MCLK_LRCLK_768 }, + { 12288000, 8000, CS42L56_MCLK_LRCLK_750 }, + { 24000000, 8000, CS42L56_MCLK_LRCLK_768 }, + { 24576000, 8000, CS42L56_MCLK_LRCLK_750 }, + /* 11.025k */ + { 5644800, 11025, CS42L56_MCLK_LRCLK_512}, + { 11289600, 11025, CS42L56_MCLK_LRCLK_512}, + { 22579200, 11025, CS42L56_MCLK_LRCLK_512 }, + /* 11.0294k */ + { 6000000, 110294, CS42L56_MCLK_LRCLK_544 }, + { 12000000, 110294, CS42L56_MCLK_LRCLK_544 }, + { 24000000, 110294, CS42L56_MCLK_LRCLK_544 }, + /* 12k */ + { 6000000, 12000, CS42L56_MCLK_LRCLK_500 }, + { 6144000, 12000, CS42L56_MCLK_LRCLK_512 }, + { 12000000, 12000, CS42L56_MCLK_LRCLK_500 }, + { 12288000, 12000, CS42L56_MCLK_LRCLK_512 }, + { 24000000, 12000, CS42L56_MCLK_LRCLK_500 }, + { 24576000, 12000, CS42L56_MCLK_LRCLK_512 }, + /* 16k */ + { 6000000, 16000, CS42L56_MCLK_LRCLK_375 }, + { 6144000, 16000, CS42L56_MCLK_LRCLK_384 }, + { 12000000, 16000, CS42L56_MCLK_LRCLK_375 }, + { 12288000, 16000, CS42L56_MCLK_LRCLK_384 }, + { 24000000, 16000, CS42L56_MCLK_LRCLK_375 }, + { 24576000, 16000, CS42L56_MCLK_LRCLK_384 }, + /* 22.050k */ + { 5644800, 22050, CS42L56_MCLK_LRCLK_256 }, + { 11289600, 22050, CS42L56_MCLK_LRCLK_256 }, + { 22579200, 22050, CS42L56_MCLK_LRCLK_256 }, + /* 22.0588k */ + { 6000000, 220588, CS42L56_MCLK_LRCLK_272 }, + { 12000000, 220588, CS42L56_MCLK_LRCLK_272 }, + { 24000000, 220588, CS42L56_MCLK_LRCLK_272 }, + /* 24k */ + { 6000000, 24000, CS42L56_MCLK_LRCLK_250 }, + { 6144000, 24000, CS42L56_MCLK_LRCLK_256 }, + { 12000000, 24000, CS42L56_MCLK_LRCLK_250 }, + { 12288000, 24000, CS42L56_MCLK_LRCLK_256 }, + { 24000000, 24000, CS42L56_MCLK_LRCLK_250 }, + { 24576000, 24000, CS42L56_MCLK_LRCLK_256 }, + /* 32k */ + { 6000000, 32000, CS42L56_MCLK_LRCLK_187P5 }, + { 6144000, 32000, CS42L56_MCLK_LRCLK_192 }, + { 12000000, 32000, CS42L56_MCLK_LRCLK_187P5 }, + { 12288000, 32000, CS42L56_MCLK_LRCLK_192 }, + { 24000000, 32000, CS42L56_MCLK_LRCLK_187P5 }, + { 24576000, 32000, CS42L56_MCLK_LRCLK_192 }, + /* 44.118k */ + { 6000000, 44118, CS42L56_MCLK_LRCLK_136 }, + { 12000000, 44118, CS42L56_MCLK_LRCLK_136 }, + { 24000000, 44118, CS42L56_MCLK_LRCLK_136 }, + /* 44.1k */ + { 5644800, 44100, CS42L56_MCLK_LRCLK_128 }, + { 11289600, 44100, CS42L56_MCLK_LRCLK_128 }, + { 22579200, 44100, CS42L56_MCLK_LRCLK_128 }, + /* 48k */ + { 6000000, 48000, CS42L56_MCLK_LRCLK_125 }, + { 6144000, 48000, CS42L56_MCLK_LRCLK_128 }, + { 12000000, 48000, CS42L56_MCLK_LRCLK_125 }, + { 12288000, 48000, CS42L56_MCLK_LRCLK_128 }, + { 24000000, 48000, CS42L56_MCLK_LRCLK_125 }, + { 24576000, 48000, CS42L56_MCLK_LRCLK_128 }, +}; + +static int cs42l56_get_mclk_ratio(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(clk_ratio_table); i++) { + if (clk_ratio_table[i].mclk == mclk && + clk_ratio_table[i].srate == rate) + return clk_ratio_table[i].ratio; + } + return -EINVAL; +} + +static int cs42l56_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case CS42L56_MCLK_5P6448MHZ: + case CS42L56_MCLK_6MHZ: + case CS42L56_MCLK_6P144MHZ: + cs42l56->mclk_div2 = 0; + cs42l56->mclk_prediv = 0; + break; + case CS42L56_MCLK_11P2896MHZ: + case CS42L56_MCLK_12MHZ: + case CS42L56_MCLK_12P288MHZ: + cs42l56->mclk_div2 = CS42L56_MCLK_DIV2; + cs42l56->mclk_prediv = 0; + break; + case CS42L56_MCLK_22P5792MHZ: + case CS42L56_MCLK_24MHZ: + case CS42L56_MCLK_24P576MHZ: + cs42l56->mclk_div2 = CS42L56_MCLK_DIV2; + cs42l56->mclk_prediv = CS42L56_MCLK_PREDIV; + break; + default: + return -EINVAL; + } + cs42l56->mclk = freq; + + snd_soc_component_update_bits(component, CS42L56_CLKCTL_1, + CS42L56_MCLK_PREDIV_MASK, + cs42l56->mclk_prediv); + snd_soc_component_update_bits(component, CS42L56_CLKCTL_1, + CS42L56_MCLK_DIV2_MASK, + cs42l56->mclk_div2); + + return 0; +} + +static int cs42l56_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cs42l56->iface = CS42L56_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + cs42l56->iface = CS42L56_SLAVE_MODE; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs42l56->iface_fmt = CS42L56_DIG_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + cs42l56->iface_fmt = CS42L56_DIG_FMT_LEFT_J; + break; + default: + return -EINVAL; + } + + /* sclk inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + cs42l56->iface_inv = 0; + break; + case SND_SOC_DAIFMT_IB_NF: + cs42l56->iface_inv = CS42L56_SCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, CS42L56_CLKCTL_1, + CS42L56_MS_MODE_MASK, cs42l56->iface); + snd_soc_component_update_bits(component, CS42L56_SERIAL_FMT, + CS42L56_DIG_FMT_MASK, cs42l56->iface_fmt); + snd_soc_component_update_bits(component, CS42L56_CLKCTL_1, + CS42L56_SCLK_INV_MASK, cs42l56->iface_inv); + return 0; +} + +static int cs42l56_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + /* Hit the DSP Mixer first */ + snd_soc_component_update_bits(component, CS42L56_DSP_MUTE_CTL, + CS42L56_ADCAMIX_MUTE_MASK | + CS42L56_ADCBMIX_MUTE_MASK | + CS42L56_PCMAMIX_MUTE_MASK | + CS42L56_PCMBMIX_MUTE_MASK | + CS42L56_MSTB_MUTE_MASK | + CS42L56_MSTA_MUTE_MASK, + CS42L56_MUTE_ALL); + /* Mute ADC's */ + snd_soc_component_update_bits(component, CS42L56_MISC_ADC_CTL, + CS42L56_ADCA_MUTE_MASK | + CS42L56_ADCB_MUTE_MASK, + CS42L56_MUTE_ALL); + /* HP And LO */ + snd_soc_component_update_bits(component, CS42L56_HPA_VOLUME, + CS42L56_HP_MUTE_MASK, CS42L56_MUTE_ALL); + snd_soc_component_update_bits(component, CS42L56_HPB_VOLUME, + CS42L56_HP_MUTE_MASK, CS42L56_MUTE_ALL); + snd_soc_component_update_bits(component, CS42L56_LOA_VOLUME, + CS42L56_LO_MUTE_MASK, CS42L56_MUTE_ALL); + snd_soc_component_update_bits(component, CS42L56_LOB_VOLUME, + CS42L56_LO_MUTE_MASK, CS42L56_MUTE_ALL); + } else { + snd_soc_component_update_bits(component, CS42L56_DSP_MUTE_CTL, + CS42L56_ADCAMIX_MUTE_MASK | + CS42L56_ADCBMIX_MUTE_MASK | + CS42L56_PCMAMIX_MUTE_MASK | + CS42L56_PCMBMIX_MUTE_MASK | + CS42L56_MSTB_MUTE_MASK | + CS42L56_MSTA_MUTE_MASK, + CS42L56_UNMUTE); + + snd_soc_component_update_bits(component, CS42L56_MISC_ADC_CTL, + CS42L56_ADCA_MUTE_MASK | + CS42L56_ADCB_MUTE_MASK, + CS42L56_UNMUTE); + + snd_soc_component_update_bits(component, CS42L56_HPA_VOLUME, + CS42L56_HP_MUTE_MASK, CS42L56_UNMUTE); + snd_soc_component_update_bits(component, CS42L56_HPB_VOLUME, + CS42L56_HP_MUTE_MASK, CS42L56_UNMUTE); + snd_soc_component_update_bits(component, CS42L56_LOA_VOLUME, + CS42L56_LO_MUTE_MASK, CS42L56_UNMUTE); + snd_soc_component_update_bits(component, CS42L56_LOB_VOLUME, + CS42L56_LO_MUTE_MASK, CS42L56_UNMUTE); + } + return 0; +} + +static int cs42l56_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); + int ratio; + + ratio = cs42l56_get_mclk_ratio(cs42l56->mclk, params_rate(params)); + if (ratio >= 0) { + snd_soc_component_update_bits(component, CS42L56_CLKCTL_2, + CS42L56_CLK_RATIO_MASK, ratio); + } else { + dev_err(component->dev, "unsupported mclk/sclk/lrclk ratio\n"); + return -EINVAL; + } + + return 0; +} + +static int cs42l56_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, CS42L56_CLKCTL_1, + CS42L56_MCLK_DIS_MASK, 0); + snd_soc_component_update_bits(component, CS42L56_PWRCTL_1, + CS42L56_PDN_ALL_MASK, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_cache_only(cs42l56->regmap, false); + regcache_sync(cs42l56->regmap); + ret = regulator_bulk_enable(ARRAY_SIZE(cs42l56->supplies), + cs42l56->supplies); + if (ret != 0) { + dev_err(cs42l56->dev, + "Failed to enable regulators: %d\n", + ret); + return ret; + } + } + snd_soc_component_update_bits(component, CS42L56_PWRCTL_1, + CS42L56_PDN_ALL_MASK, 1); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, CS42L56_PWRCTL_1, + CS42L56_PDN_ALL_MASK, 1); + snd_soc_component_update_bits(component, CS42L56_CLKCTL_1, + CS42L56_MCLK_DIS_MASK, 1); + regcache_cache_only(cs42l56->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(cs42l56->supplies), + cs42l56->supplies); + break; + } + + return 0; +} + +#define CS42L56_RATES (SNDRV_PCM_RATE_8000_48000) + +#define CS42L56_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + + +static const struct snd_soc_dai_ops cs42l56_ops = { + .hw_params = cs42l56_pcm_hw_params, + .digital_mute = cs42l56_digital_mute, + .set_fmt = cs42l56_set_dai_fmt, + .set_sysclk = cs42l56_set_sysclk, +}; + +static struct snd_soc_dai_driver cs42l56_dai = { + .name = "cs42l56", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L56_RATES, + .formats = CS42L56_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L56_RATES, + .formats = CS42L56_FORMATS, + }, + .ops = &cs42l56_ops, +}; + +static int beep_freq[] = { + 261, 522, 585, 667, 706, 774, 889, 1000, + 1043, 1200, 1333, 1412, 1600, 1714, 2000, 2182 +}; + +static void cs42l56_beep_work(struct work_struct *work) +{ + struct cs42l56_private *cs42l56 = + container_of(work, struct cs42l56_private, beep_work); + struct snd_soc_component *component = cs42l56->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int i; + int val = 0; + int best = 0; + + if (cs42l56->beep_rate) { + for (i = 0; i < ARRAY_SIZE(beep_freq); i++) { + if (abs(cs42l56->beep_rate - beep_freq[i]) < + abs(cs42l56->beep_rate - beep_freq[best])) + best = i; + } + + dev_dbg(component->dev, "Set beep rate %dHz for requested %dHz\n", + beep_freq[best], cs42l56->beep_rate); + + val = (best << CS42L56_BEEP_RATE_SHIFT); + + snd_soc_dapm_enable_pin(dapm, "Beep"); + } else { + dev_dbg(component->dev, "Disabling beep\n"); + snd_soc_dapm_disable_pin(dapm, "Beep"); + } + + snd_soc_component_update_bits(component, CS42L56_BEEP_FREQ_ONTIME, + CS42L56_BEEP_FREQ_MASK, val); + + snd_soc_dapm_sync(dapm); +} + +/* For usability define a way of injecting beep events for the device - + * many systems will not have a keyboard. + */ +static int cs42l56_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + struct snd_soc_component *component = input_get_drvdata(dev); + struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "Beep event %x %x\n", code, hz); + + switch (code) { + case SND_BELL: + if (hz) + hz = 261; + case SND_TONE: + break; + default: + return -1; + } + + /* Kick the beep from a workqueue */ + cs42l56->beep_rate = hz; + schedule_work(&cs42l56->beep_work); + return 0; +} + +static ssize_t cs42l56_beep_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cs42l56_private *cs42l56 = dev_get_drvdata(dev); + long int time; + int ret; + + ret = kstrtol(buf, 10, &time); + if (ret != 0) + return ret; + + input_event(cs42l56->beep, EV_SND, SND_TONE, time); + + return count; +} + +static DEVICE_ATTR(beep, 0200, NULL, cs42l56_beep_set); + +static void cs42l56_init_beep(struct snd_soc_component *component) +{ + struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); + int ret; + + cs42l56->beep = devm_input_allocate_device(component->dev); + if (!cs42l56->beep) { + dev_err(component->dev, "Failed to allocate beep device\n"); + return; + } + + INIT_WORK(&cs42l56->beep_work, cs42l56_beep_work); + cs42l56->beep_rate = 0; + + cs42l56->beep->name = "CS42L56 Beep Generator"; + cs42l56->beep->phys = dev_name(component->dev); + cs42l56->beep->id.bustype = BUS_I2C; + + cs42l56->beep->evbit[0] = BIT_MASK(EV_SND); + cs42l56->beep->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + cs42l56->beep->event = cs42l56_beep_event; + cs42l56->beep->dev.parent = component->dev; + input_set_drvdata(cs42l56->beep, component); + + ret = input_register_device(cs42l56->beep); + if (ret != 0) { + cs42l56->beep = NULL; + dev_err(component->dev, "Failed to register beep device\n"); + } + + ret = device_create_file(component->dev, &dev_attr_beep); + if (ret != 0) { + dev_err(component->dev, "Failed to create keyclick file: %d\n", + ret); + } +} + +static void cs42l56_free_beep(struct snd_soc_component *component) +{ + struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); + + device_remove_file(component->dev, &dev_attr_beep); + cancel_work_sync(&cs42l56->beep_work); + cs42l56->beep = NULL; + + snd_soc_component_update_bits(component, CS42L56_BEEP_TONE_CFG, + CS42L56_BEEP_EN_MASK, 0); +} + +static int cs42l56_probe(struct snd_soc_component *component) +{ + cs42l56_init_beep(component); + + return 0; +} + +static void cs42l56_remove(struct snd_soc_component *component) +{ + cs42l56_free_beep(component); +} + +static const struct snd_soc_component_driver soc_component_dev_cs42l56 = { + .probe = cs42l56_probe, + .remove = cs42l56_remove, + .set_bias_level = cs42l56_set_bias_level, + .controls = cs42l56_snd_controls, + .num_controls = ARRAY_SIZE(cs42l56_snd_controls), + .dapm_widgets = cs42l56_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42l56_dapm_widgets), + .dapm_routes = cs42l56_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs42l56_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config cs42l56_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42L56_MAX_REGISTER, + .reg_defaults = cs42l56_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs42l56_reg_defaults), + .readable_reg = cs42l56_readable_register, + .volatile_reg = cs42l56_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs42l56_handle_of_data(struct i2c_client *i2c_client, + struct cs42l56_platform_data *pdata) +{ + struct device_node *np = i2c_client->dev.of_node; + u32 val32; + + if (of_property_read_bool(np, "cirrus,ain1a-reference-cfg")) + pdata->ain1a_ref_cfg = true; + + if (of_property_read_bool(np, "cirrus,ain2a-reference-cfg")) + pdata->ain2a_ref_cfg = true; + + if (of_property_read_bool(np, "cirrus,ain1b-reference-cfg")) + pdata->ain1b_ref_cfg = true; + + if (of_property_read_bool(np, "cirrus,ain2b-reference-cfg")) + pdata->ain2b_ref_cfg = true; + + if (of_property_read_u32(np, "cirrus,micbias-lvl", &val32) >= 0) + pdata->micbias_lvl = val32; + + if (of_property_read_u32(np, "cirrus,chgfreq-divisor", &val32) >= 0) + pdata->chgfreq = val32; + + if (of_property_read_u32(np, "cirrus,adaptive-pwr-cfg", &val32) >= 0) + pdata->adaptive_pwr = val32; + + if (of_property_read_u32(np, "cirrus,hpf-left-freq", &val32) >= 0) + pdata->hpfa_freq = val32; + + if (of_property_read_u32(np, "cirrus,hpf-left-freq", &val32) >= 0) + pdata->hpfb_freq = val32; + + pdata->gpio_nreset = of_get_named_gpio(np, "cirrus,gpio-nreset", 0); + + return 0; +} + +static int cs42l56_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs42l56_private *cs42l56; + struct cs42l56_platform_data *pdata = + dev_get_platdata(&i2c_client->dev); + int ret, i; + unsigned int devid = 0; + unsigned int alpha_rev, metal_rev; + unsigned int reg; + + cs42l56 = devm_kzalloc(&i2c_client->dev, sizeof(*cs42l56), GFP_KERNEL); + if (cs42l56 == NULL) + return -ENOMEM; + cs42l56->dev = &i2c_client->dev; + + cs42l56->regmap = devm_regmap_init_i2c(i2c_client, &cs42l56_regmap); + if (IS_ERR(cs42l56->regmap)) { + ret = PTR_ERR(cs42l56->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + if (pdata) { + cs42l56->pdata = *pdata; + } else { + pdata = devm_kzalloc(&i2c_client->dev, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (i2c_client->dev.of_node) { + ret = cs42l56_handle_of_data(i2c_client, + &cs42l56->pdata); + if (ret != 0) + return ret; + } + cs42l56->pdata = *pdata; + } + + if (cs42l56->pdata.gpio_nreset) { + ret = gpio_request_one(cs42l56->pdata.gpio_nreset, + GPIOF_OUT_INIT_HIGH, "CS42L56 /RST"); + if (ret < 0) { + dev_err(&i2c_client->dev, + "Failed to request /RST %d: %d\n", + cs42l56->pdata.gpio_nreset, ret); + return ret; + } + gpio_set_value_cansleep(cs42l56->pdata.gpio_nreset, 0); + gpio_set_value_cansleep(cs42l56->pdata.gpio_nreset, 1); + } + + + i2c_set_clientdata(i2c_client, cs42l56); + + for (i = 0; i < ARRAY_SIZE(cs42l56->supplies); i++) + cs42l56->supplies[i].supply = cs42l56_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c_client->dev, + ARRAY_SIZE(cs42l56->supplies), + cs42l56->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42l56->supplies), + cs42l56->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_read(cs42l56->regmap, CS42L56_CHIP_ID_1, ®); + devid = reg & CS42L56_CHIP_ID_MASK; + if (devid != CS42L56_DEVID) { + dev_err(&i2c_client->dev, + "CS42L56 Device ID (%X). Expected %X\n", + devid, CS42L56_DEVID); + ret = -EINVAL; + goto err_enable; + } + alpha_rev = reg & CS42L56_AREV_MASK; + metal_rev = reg & CS42L56_MTLREV_MASK; + + dev_info(&i2c_client->dev, "Cirrus Logic CS42L56 "); + dev_info(&i2c_client->dev, "Alpha Rev %X Metal Rev %X\n", + alpha_rev, metal_rev); + + if (cs42l56->pdata.ain1a_ref_cfg) + regmap_update_bits(cs42l56->regmap, CS42L56_AIN_REFCFG_ADC_MUX, + CS42L56_AIN1A_REF_MASK, + CS42L56_AIN1A_REF_MASK); + + if (cs42l56->pdata.ain1b_ref_cfg) + regmap_update_bits(cs42l56->regmap, CS42L56_AIN_REFCFG_ADC_MUX, + CS42L56_AIN1B_REF_MASK, + CS42L56_AIN1B_REF_MASK); + + if (cs42l56->pdata.ain2a_ref_cfg) + regmap_update_bits(cs42l56->regmap, CS42L56_AIN_REFCFG_ADC_MUX, + CS42L56_AIN2A_REF_MASK, + CS42L56_AIN2A_REF_MASK); + + if (cs42l56->pdata.ain2b_ref_cfg) + regmap_update_bits(cs42l56->regmap, CS42L56_AIN_REFCFG_ADC_MUX, + CS42L56_AIN2B_REF_MASK, + CS42L56_AIN2B_REF_MASK); + + if (cs42l56->pdata.micbias_lvl) + regmap_update_bits(cs42l56->regmap, CS42L56_GAIN_BIAS_CTL, + CS42L56_MIC_BIAS_MASK, + cs42l56->pdata.micbias_lvl); + + if (cs42l56->pdata.chgfreq) + regmap_update_bits(cs42l56->regmap, CS42L56_CLASSH_CTL, + CS42L56_CHRG_FREQ_MASK, + cs42l56->pdata.chgfreq); + + if (cs42l56->pdata.hpfb_freq) + regmap_update_bits(cs42l56->regmap, CS42L56_HPF_CTL, + CS42L56_HPFB_FREQ_MASK, + cs42l56->pdata.hpfb_freq); + + if (cs42l56->pdata.hpfa_freq) + regmap_update_bits(cs42l56->regmap, CS42L56_HPF_CTL, + CS42L56_HPFA_FREQ_MASK, + cs42l56->pdata.hpfa_freq); + + if (cs42l56->pdata.adaptive_pwr) + regmap_update_bits(cs42l56->regmap, CS42L56_CLASSH_CTL, + CS42L56_ADAPT_PWR_MASK, + cs42l56->pdata.adaptive_pwr); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_cs42l56, &cs42l56_dai, 1); + if (ret < 0) + goto err_enable; + + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(cs42l56->supplies), + cs42l56->supplies); + return ret; +} + +static int cs42l56_i2c_remove(struct i2c_client *client) +{ + struct cs42l56_private *cs42l56 = i2c_get_clientdata(client); + + regulator_bulk_disable(ARRAY_SIZE(cs42l56->supplies), + cs42l56->supplies); + return 0; +} + +static const struct of_device_id cs42l56_of_match[] = { + { .compatible = "cirrus,cs42l56", }, + { } +}; +MODULE_DEVICE_TABLE(of, cs42l56_of_match); + + +static const struct i2c_device_id cs42l56_id[] = { + { "cs42l56", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs42l56_id); + +static struct i2c_driver cs42l56_i2c_driver = { + .driver = { + .name = "cs42l56", + .of_match_table = cs42l56_of_match, + }, + .id_table = cs42l56_id, + .probe = cs42l56_i2c_probe, + .remove = cs42l56_i2c_remove, +}; + +module_i2c_driver(cs42l56_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS42L56 driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l56.h b/sound/soc/codecs/cs42l56.h new file mode 100644 index 000000000..5025ec9be --- /dev/null +++ b/sound/soc/codecs/cs42l56.h @@ -0,0 +1,177 @@ +/* + * cs42l52.h -- CS42L56 ALSA SoC audio driver + * + * Copyright 2014 CirrusLogic, Inc. + * + * Author: Brian Austin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS42L56_H__ +#define __CS42L56_H__ + +#define CS42L56_CHIP_ID_1 0x01 +#define CS42L56_CHIP_ID_2 0x02 +#define CS42L56_PWRCTL_1 0x03 +#define CS42L56_PWRCTL_2 0x04 +#define CS42L56_CLKCTL_1 0x05 +#define CS42L56_CLKCTL_2 0x06 +#define CS42L56_SERIAL_FMT 0x07 +#define CS42L56_CLASSH_CTL 0x08 +#define CS42L56_MISC_CTL 0x09 +#define CS42L56_INT_STATUS 0x0a +#define CS42L56_PLAYBACK_CTL 0x0b +#define CS42L56_DSP_MUTE_CTL 0x0c +#define CS42L56_ADCA_MIX_VOLUME 0x0d +#define CS42L56_ADCB_MIX_VOLUME 0x0e +#define CS42L56_PCMA_MIX_VOLUME 0x0f +#define CS42L56_PCMB_MIX_VOLUME 0x10 +#define CS42L56_ANAINPUT_ADV_VOLUME 0x11 +#define CS42L56_DIGINPUT_ADV_VOLUME 0x12 +#define CS42L56_MASTER_A_VOLUME 0x13 +#define CS42L56_MASTER_B_VOLUME 0x14 +#define CS42L56_BEEP_FREQ_ONTIME 0x15 +#define CS42L56_BEEP_FREQ_OFFTIME 0x16 +#define CS42L56_BEEP_TONE_CFG 0x17 +#define CS42L56_TONE_CTL 0x18 +#define CS42L56_CHAN_MIX_SWAP 0x19 +#define CS42L56_AIN_REFCFG_ADC_MUX 0x1a +#define CS42L56_HPF_CTL 0x1b +#define CS42L56_MISC_ADC_CTL 0x1c +#define CS42L56_GAIN_BIAS_CTL 0x1d +#define CS42L56_PGAA_MUX_VOLUME 0x1e +#define CS42L56_PGAB_MUX_VOLUME 0x1f +#define CS42L56_ADCA_ATTENUATOR 0x20 +#define CS42L56_ADCB_ATTENUATOR 0x21 +#define CS42L56_ALC_EN_ATTACK_RATE 0x22 +#define CS42L56_ALC_RELEASE_RATE 0x23 +#define CS42L56_ALC_THRESHOLD 0x24 +#define CS42L56_NOISE_GATE_CTL 0x25 +#define CS42L56_ALC_LIM_SFT_ZC 0x26 +#define CS42L56_AMUTE_HPLO_MUX 0x27 +#define CS42L56_HPA_VOLUME 0x28 +#define CS42L56_HPB_VOLUME 0x29 +#define CS42L56_LOA_VOLUME 0x2a +#define CS42L56_LOB_VOLUME 0x2b +#define CS42L56_LIM_THRESHOLD_CTL 0x2c +#define CS42L56_LIM_CTL_RELEASE_RATE 0x2d +#define CS42L56_LIM_ATTACK_RATE 0x2e + +/* Device ID and Rev ID Masks */ +#define CS42L56_DEVID 0x56 +#define CS42L56_CHIP_ID_MASK 0xff +#define CS42L56_AREV_MASK 0x1c +#define CS42L56_MTLREV_MASK 0x03 + +/* Power bit masks */ +#define CS42L56_PDN_ALL_MASK 0x01 +#define CS42L56_PDN_ADCA_MASK 0x02 +#define CS42L56_PDN_ADCB_MASK 0x04 +#define CS42L56_PDN_CHRG_MASK 0x08 +#define CS42L56_PDN_BIAS_MASK 0x10 +#define CS42L56_PDN_VBUF_MASK 0x20 +#define CS42L56_PDN_LOA_MASK 0x03 +#define CS42L56_PDN_LOB_MASK 0x0c +#define CS42L56_PDN_HPA_MASK 0x30 +#define CS42L56_PDN_HPB_MASK 0xc0 + +/* serial port and clk masks */ +#define CS42L56_MASTER_MODE 0x40 +#define CS42L56_SLAVE_MODE 0 +#define CS42L56_MS_MODE_MASK 0x40 +#define CS42L56_SCLK_INV 0x20 +#define CS42L56_SCLK_INV_MASK 0x20 +#define CS42L56_SCLK_MCLK_MASK 0x18 +#define CS42L56_MCLK_PREDIV 0x04 +#define CS42L56_MCLK_PREDIV_MASK 0x04 +#define CS42L56_MCLK_DIV2 0x02 +#define CS42L56_MCLK_DIV2_MASK 0x02 +#define CS42L56_MCLK_DIS_MASK 0x01 +#define CS42L56_CLK_AUTO_MASK 0x20 +#define CS42L56_CLK_RATIO_MASK 0x1f +#define CS42L56_DIG_FMT_I2S 0 +#define CS42L56_DIG_FMT_LEFT_J 0x08 +#define CS42L56_DIG_FMT_MASK 0x08 + +/* Class H and misc ctl masks */ +#define CS42L56_ADAPT_PWR_MASK 0xc0 +#define CS42L56_CHRG_FREQ_MASK 0x0f +#define CS42L56_DIG_MUX_MASK 0x80 +#define CS42L56_ANLGSFT_MASK 0x10 +#define CS42L56_ANLGZC_MASK 0x08 +#define CS42L56_DIGSFT_MASK 0x04 +#define CS42L56_FREEZE_MASK 0x01 +#define CS42L56_MIC_BIAS_MASK 0x03 +#define CS42L56_HPFA_FREQ_MASK 0x03 +#define CS42L56_HPFB_FREQ_MASK 0xc0 +#define CS42L56_AIN1A_REF_MASK 0x10 +#define CS42L56_AIN2A_REF_MASK 0x40 +#define CS42L56_AIN1B_REF_MASK 0x20 +#define CS42L56_AIN2B_REF_MASK 0x80 + +/* Playback Capture ctl masks */ +#define CS42L56_PDN_DSP_MASK 0x80 +#define CS42L56_DEEMPH_MASK 0x40 +#define CS42L56_PLYBCK_GANG_MASK 0x10 +#define CS42L56_PCM_INV_MASK 0x0c +#define CS42L56_MUTE_ALL 0xff +#define CS42L56_UNMUTE 0 +#define CS42L56_ADCAMIX_MUTE_MASK 0x40 +#define CS42L56_ADCBMIX_MUTE_MASK 0x80 +#define CS42L56_PCMAMIX_MUTE_MASK 0x10 +#define CS42L56_PCMBMIX_MUTE_MASK 0x20 +#define CS42L56_MSTB_MUTE_MASK 0x02 +#define CS42L56_MSTA_MUTE_MASK 0x01 +#define CS42L56_ADCA_MUTE_MASK 0x01 +#define CS42L56_ADCB_MUTE_MASK 0x02 +#define CS42L56_HP_MUTE_MASK 0x80 +#define CS42L56_LO_MUTE_MASK 0x80 + +/* Beep masks */ +#define CS42L56_BEEP_FREQ_MASK 0xf0 +#define CS42L56_BEEP_ONTIME_MASK 0x0f +#define CS42L56_BEEP_OFFTIME_MASK 0xe0 +#define CS42L56_BEEP_CFG_MASK 0xc0 +#define CS42L56_BEEP_TREBCF_MASK 0x18 +#define CS42L56_BEEP_BASSCF_MASK 0x06 +#define CS42L56_BEEP_TCEN_MASK 0x01 +#define CS42L56_BEEP_RATE_SHIFT 4 +#define CS42L56_BEEP_EN_MASK 0x3f + + +/* Supported MCLKS */ +#define CS42L56_MCLK_5P6448MHZ 5644800 +#define CS42L56_MCLK_6MHZ 6000000 +#define CS42L56_MCLK_6P144MHZ 6144000 +#define CS42L56_MCLK_11P2896MHZ 11289600 +#define CS42L56_MCLK_12MHZ 12000000 +#define CS42L56_MCLK_12P288MHZ 12288000 +#define CS42L56_MCLK_22P5792MHZ 22579200 +#define CS42L56_MCLK_24MHZ 24000000 +#define CS42L56_MCLK_24P576MHZ 24576000 + +/* Clock ratios */ +#define CS42L56_MCLK_LRCLK_128 0x08 +#define CS42L56_MCLK_LRCLK_125 0x09 +#define CS42L56_MCLK_LRCLK_136 0x0b +#define CS42L56_MCLK_LRCLK_192 0x0c +#define CS42L56_MCLK_LRCLK_187P5 0x0d +#define CS42L56_MCLK_LRCLK_256 0x10 +#define CS42L56_MCLK_LRCLK_250 0x11 +#define CS42L56_MCLK_LRCLK_272 0x13 +#define CS42L56_MCLK_LRCLK_384 0x14 +#define CS42L56_MCLK_LRCLK_375 0x15 +#define CS42L56_MCLK_LRCLK_512 0x18 +#define CS42L56_MCLK_LRCLK_500 0x19 +#define CS42L56_MCLK_LRCLK_544 0x1b +#define CS42L56_MCLK_LRCLK_750 0x1c +#define CS42L56_MCLK_LRCLK_768 0x1d + + +#define CS42L56_MAX_REGISTER 0x34 + +#endif diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c new file mode 100644 index 000000000..36b57ee00 --- /dev/null +++ b/sound/soc/codecs/cs42l73.c @@ -0,0 +1,1401 @@ +/* + * cs42l73.c -- CS42L73 ALSA Soc Audio driver + * + * Copyright 2011 Cirrus Logic, Inc. + * + * Authors: Georgi Vlaev, Nucleus Systems Ltd, + * Brian Austin, Cirrus Logic Inc, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs42l73.h" + +struct sp_config { + u8 spc, mmcc, spfs; + u32 srate; +}; +struct cs42l73_private { + struct cs42l73_platform_data pdata; + struct sp_config config[3]; + struct regmap *regmap; + u32 sysclk; + u8 mclksel; + u32 mclk; + int shutdwn_delay; +}; + +static const struct reg_default cs42l73_reg_defaults[] = { + { 6, 0xF1 }, /* r06 - Power Ctl 1 */ + { 7, 0xDF }, /* r07 - Power Ctl 2 */ + { 8, 0x3F }, /* r08 - Power Ctl 3 */ + { 9, 0x50 }, /* r09 - Charge Pump Freq */ + { 10, 0x53 }, /* r0A - Output Load MicBias Short Detect */ + { 11, 0x00 }, /* r0B - DMIC Master Clock Ctl */ + { 12, 0x00 }, /* r0C - Aux PCM Ctl */ + { 13, 0x15 }, /* r0D - Aux PCM Master Clock Ctl */ + { 14, 0x00 }, /* r0E - Audio PCM Ctl */ + { 15, 0x15 }, /* r0F - Audio PCM Master Clock Ctl */ + { 16, 0x00 }, /* r10 - Voice PCM Ctl */ + { 17, 0x15 }, /* r11 - Voice PCM Master Clock Ctl */ + { 18, 0x00 }, /* r12 - Voice/Aux Sample Rate */ + { 19, 0x06 }, /* r13 - Misc I/O Path Ctl */ + { 20, 0x00 }, /* r14 - ADC Input Path Ctl */ + { 21, 0x00 }, /* r15 - MICA Preamp, PGA Volume */ + { 22, 0x00 }, /* r16 - MICB Preamp, PGA Volume */ + { 23, 0x00 }, /* r17 - Input Path A Digital Volume */ + { 24, 0x00 }, /* r18 - Input Path B Digital Volume */ + { 25, 0x00 }, /* r19 - Playback Digital Ctl */ + { 26, 0x00 }, /* r1A - HP/LO Left Digital Volume */ + { 27, 0x00 }, /* r1B - HP/LO Right Digital Volume */ + { 28, 0x00 }, /* r1C - Speakerphone Digital Volume */ + { 29, 0x00 }, /* r1D - Ear/SPKLO Digital Volume */ + { 30, 0x00 }, /* r1E - HP Left Analog Volume */ + { 31, 0x00 }, /* r1F - HP Right Analog Volume */ + { 32, 0x00 }, /* r20 - LO Left Analog Volume */ + { 33, 0x00 }, /* r21 - LO Right Analog Volume */ + { 34, 0x00 }, /* r22 - Stereo Input Path Advisory Volume */ + { 35, 0x00 }, /* r23 - Aux PCM Input Advisory Volume */ + { 36, 0x00 }, /* r24 - Audio PCM Input Advisory Volume */ + { 37, 0x00 }, /* r25 - Voice PCM Input Advisory Volume */ + { 38, 0x00 }, /* r26 - Limiter Attack Rate HP/LO */ + { 39, 0x7F }, /* r27 - Limter Ctl, Release Rate HP/LO */ + { 40, 0x00 }, /* r28 - Limter Threshold HP/LO */ + { 41, 0x00 }, /* r29 - Limiter Attack Rate Speakerphone */ + { 42, 0x3F }, /* r2A - Limter Ctl, Release Rate Speakerphone */ + { 43, 0x00 }, /* r2B - Limter Threshold Speakerphone */ + { 44, 0x00 }, /* r2C - Limiter Attack Rate Ear/SPKLO */ + { 45, 0x3F }, /* r2D - Limter Ctl, Release Rate Ear/SPKLO */ + { 46, 0x00 }, /* r2E - Limter Threshold Ear/SPKLO */ + { 47, 0x00 }, /* r2F - ALC Enable, Attack Rate Left/Right */ + { 48, 0x3F }, /* r30 - ALC Release Rate Left/Right */ + { 49, 0x00 }, /* r31 - ALC Threshold Left/Right */ + { 50, 0x00 }, /* r32 - Noise Gate Ctl Left/Right */ + { 51, 0x00 }, /* r33 - ALC/NG Misc Ctl */ + { 52, 0x18 }, /* r34 - Mixer Ctl */ + { 53, 0x3F }, /* r35 - HP/LO Left Mixer Input Path Volume */ + { 54, 0x3F }, /* r36 - HP/LO Right Mixer Input Path Volume */ + { 55, 0x3F }, /* r37 - HP/LO Left Mixer Aux PCM Volume */ + { 56, 0x3F }, /* r38 - HP/LO Right Mixer Aux PCM Volume */ + { 57, 0x3F }, /* r39 - HP/LO Left Mixer Audio PCM Volume */ + { 58, 0x3F }, /* r3A - HP/LO Right Mixer Audio PCM Volume */ + { 59, 0x3F }, /* r3B - HP/LO Left Mixer Voice PCM Mono Volume */ + { 60, 0x3F }, /* r3C - HP/LO Right Mixer Voice PCM Mono Volume */ + { 61, 0x3F }, /* r3D - Aux PCM Left Mixer Input Path Volume */ + { 62, 0x3F }, /* r3E - Aux PCM Right Mixer Input Path Volume */ + { 63, 0x3F }, /* r3F - Aux PCM Left Mixer Volume */ + { 64, 0x3F }, /* r40 - Aux PCM Left Mixer Volume */ + { 65, 0x3F }, /* r41 - Aux PCM Left Mixer Audio PCM L Volume */ + { 66, 0x3F }, /* r42 - Aux PCM Right Mixer Audio PCM R Volume */ + { 67, 0x3F }, /* r43 - Aux PCM Left Mixer Voice PCM Volume */ + { 68, 0x3F }, /* r44 - Aux PCM Right Mixer Voice PCM Volume */ + { 69, 0x3F }, /* r45 - Audio PCM Left Input Path Volume */ + { 70, 0x3F }, /* r46 - Audio PCM Right Input Path Volume */ + { 71, 0x3F }, /* r47 - Audio PCM Left Mixer Aux PCM L Volume */ + { 72, 0x3F }, /* r48 - Audio PCM Right Mixer Aux PCM R Volume */ + { 73, 0x3F }, /* r49 - Audio PCM Left Mixer Volume */ + { 74, 0x3F }, /* r4A - Audio PCM Right Mixer Volume */ + { 75, 0x3F }, /* r4B - Audio PCM Left Mixer Voice PCM Volume */ + { 76, 0x3F }, /* r4C - Audio PCM Right Mixer Voice PCM Volume */ + { 77, 0x3F }, /* r4D - Voice PCM Left Input Path Volume */ + { 78, 0x3F }, /* r4E - Voice PCM Right Input Path Volume */ + { 79, 0x3F }, /* r4F - Voice PCM Left Mixer Aux PCM L Volume */ + { 80, 0x3F }, /* r50 - Voice PCM Right Mixer Aux PCM R Volume */ + { 81, 0x3F }, /* r51 - Voice PCM Left Mixer Audio PCM L Volume */ + { 82, 0x3F }, /* r52 - Voice PCM Right Mixer Audio PCM R Volume */ + { 83, 0x3F }, /* r53 - Voice PCM Left Mixer Voice PCM Volume */ + { 84, 0x3F }, /* r54 - Voice PCM Right Mixer Voice PCM Volume */ + { 85, 0xAA }, /* r55 - Mono Mixer Ctl */ + { 86, 0x3F }, /* r56 - SPK Mono Mixer Input Path Volume */ + { 87, 0x3F }, /* r57 - SPK Mono Mixer Aux PCM Mono/L/R Volume */ + { 88, 0x3F }, /* r58 - SPK Mono Mixer Audio PCM Mono/L/R Volume */ + { 89, 0x3F }, /* r59 - SPK Mono Mixer Voice PCM Mono Volume */ + { 90, 0x3F }, /* r5A - SPKLO Mono Mixer Input Path Mono Volume */ + { 91, 0x3F }, /* r5B - SPKLO Mono Mixer Aux Mono/L/R Volume */ + { 92, 0x3F }, /* r5C - SPKLO Mono Mixer Audio Mono/L/R Volume */ + { 93, 0x3F }, /* r5D - SPKLO Mono Mixer Voice Mono Volume */ + { 94, 0x00 }, /* r5E - Interrupt Mask 1 */ + { 95, 0x00 }, /* r5F - Interrupt Mask 2 */ +}; + +static bool cs42l73_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L73_IS1: + case CS42L73_IS2: + return true; + default: + return false; + } +} + +static bool cs42l73_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L73_DEVID_AB ... CS42L73_DEVID_E: + case CS42L73_REVID ... CS42L73_IM2: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_RANGE(hpaloa_tlv, + 0, 13, TLV_DB_SCALE_ITEM(-7600, 200, 0), + 14, 75, TLV_DB_SCALE_ITEM(-4900, 100, 0) +); + +static DECLARE_TLV_DB_SCALE(adc_boost_tlv, 0, 2500, 0); + +static DECLARE_TLV_DB_SCALE(hl_tlv, -10200, 50, 0); + +static DECLARE_TLV_DB_SCALE(ipd_tlv, -9600, 100, 0); + +static DECLARE_TLV_DB_SCALE(micpga_tlv, -600, 50, 0); + +static const DECLARE_TLV_DB_RANGE(limiter_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-3000, 600, 0), + 3, 7, TLV_DB_SCALE_ITEM(-1200, 300, 0) +); + +static const DECLARE_TLV_DB_SCALE(attn_tlv, -6300, 100, 1); + +static const char * const cs42l73_pgaa_text[] = { "Line A", "Mic 1" }; +static const char * const cs42l73_pgab_text[] = { "Line B", "Mic 2" }; + +static SOC_ENUM_SINGLE_DECL(pgaa_enum, + CS42L73_ADCIPC, 3, + cs42l73_pgaa_text); + +static SOC_ENUM_SINGLE_DECL(pgab_enum, + CS42L73_ADCIPC, 7, + cs42l73_pgab_text); + +static const struct snd_kcontrol_new pgaa_mux = + SOC_DAPM_ENUM("Left Analog Input Capture Mux", pgaa_enum); + +static const struct snd_kcontrol_new pgab_mux = + SOC_DAPM_ENUM("Right Analog Input Capture Mux", pgab_enum); + +static const struct snd_kcontrol_new input_left_mixer[] = { + SOC_DAPM_SINGLE("ADC Left Input", CS42L73_PWRCTL1, + 5, 1, 1), + SOC_DAPM_SINGLE("DMIC Left Input", CS42L73_PWRCTL1, + 4, 1, 1), +}; + +static const struct snd_kcontrol_new input_right_mixer[] = { + SOC_DAPM_SINGLE("ADC Right Input", CS42L73_PWRCTL1, + 7, 1, 1), + SOC_DAPM_SINGLE("DMIC Right Input", CS42L73_PWRCTL1, + 6, 1, 1), +}; + +static const char * const cs42l73_ng_delay_text[] = { + "50ms", "100ms", "150ms", "200ms" }; + +static SOC_ENUM_SINGLE_DECL(ng_delay_enum, + CS42L73_NGCAB, 0, + cs42l73_ng_delay_text); + +static const char * const cs42l73_mono_mix_texts[] = { + "Left", "Right", "Mono Mix"}; + +static const unsigned int cs42l73_mono_mix_values[] = { 0, 1, 2 }; + +static const struct soc_enum spk_asp_enum = + SOC_VALUE_ENUM_SINGLE(CS42L73_MMIXCTL, 6, 3, + ARRAY_SIZE(cs42l73_mono_mix_texts), + cs42l73_mono_mix_texts, + cs42l73_mono_mix_values); + +static const struct snd_kcontrol_new spk_asp_mixer = + SOC_DAPM_ENUM("Route", spk_asp_enum); + +static const struct soc_enum spk_xsp_enum = + SOC_VALUE_ENUM_SINGLE(CS42L73_MMIXCTL, 4, 3, + ARRAY_SIZE(cs42l73_mono_mix_texts), + cs42l73_mono_mix_texts, + cs42l73_mono_mix_values); + +static const struct snd_kcontrol_new spk_xsp_mixer = + SOC_DAPM_ENUM("Route", spk_xsp_enum); + +static const struct soc_enum esl_asp_enum = + SOC_VALUE_ENUM_SINGLE(CS42L73_MMIXCTL, 2, 3, + ARRAY_SIZE(cs42l73_mono_mix_texts), + cs42l73_mono_mix_texts, + cs42l73_mono_mix_values); + +static const struct snd_kcontrol_new esl_asp_mixer = + SOC_DAPM_ENUM("Route", esl_asp_enum); + +static const struct soc_enum esl_xsp_enum = + SOC_VALUE_ENUM_SINGLE(CS42L73_MMIXCTL, 0, 3, + ARRAY_SIZE(cs42l73_mono_mix_texts), + cs42l73_mono_mix_texts, + cs42l73_mono_mix_values); + +static const struct snd_kcontrol_new esl_xsp_mixer = + SOC_DAPM_ENUM("Route", esl_xsp_enum); + +static const char * const cs42l73_ip_swap_text[] = { + "Stereo", "Mono A", "Mono B", "Swap A-B"}; + +static SOC_ENUM_SINGLE_DECL(ip_swap_enum, + CS42L73_MIOPC, 6, + cs42l73_ip_swap_text); + +static const char * const cs42l73_spo_mixer_text[] = {"Mono", "Stereo"}; + +static SOC_ENUM_SINGLE_DECL(vsp_output_mux_enum, + CS42L73_MIXERCTL, 5, + cs42l73_spo_mixer_text); + +static SOC_ENUM_SINGLE_DECL(xsp_output_mux_enum, + CS42L73_MIXERCTL, 4, + cs42l73_spo_mixer_text); + +static const struct snd_kcontrol_new vsp_output_mux = + SOC_DAPM_ENUM("Route", vsp_output_mux_enum); + +static const struct snd_kcontrol_new xsp_output_mux = + SOC_DAPM_ENUM("Route", xsp_output_mux_enum); + +static const struct snd_kcontrol_new hp_amp_ctl = + SOC_DAPM_SINGLE("Switch", CS42L73_PWRCTL3, 0, 1, 1); + +static const struct snd_kcontrol_new lo_amp_ctl = + SOC_DAPM_SINGLE("Switch", CS42L73_PWRCTL3, 1, 1, 1); + +static const struct snd_kcontrol_new spk_amp_ctl = + SOC_DAPM_SINGLE("Switch", CS42L73_PWRCTL3, 2, 1, 1); + +static const struct snd_kcontrol_new spklo_amp_ctl = + SOC_DAPM_SINGLE("Switch", CS42L73_PWRCTL3, 4, 1, 1); + +static const struct snd_kcontrol_new ear_amp_ctl = + SOC_DAPM_SINGLE("Switch", CS42L73_PWRCTL3, 3, 1, 1); + +static const struct snd_kcontrol_new cs42l73_snd_controls[] = { + SOC_DOUBLE_R_SX_TLV("Headphone Analog Playback Volume", + CS42L73_HPAAVOL, CS42L73_HPBAVOL, 0, + 0x41, 0x4B, hpaloa_tlv), + + SOC_DOUBLE_R_SX_TLV("LineOut Analog Playback Volume", CS42L73_LOAAVOL, + CS42L73_LOBAVOL, 0, 0x41, 0x4B, hpaloa_tlv), + + SOC_DOUBLE_R_SX_TLV("Input PGA Analog Volume", CS42L73_MICAPREPGAAVOL, + CS42L73_MICBPREPGABVOL, 0, 0x34, + 0x24, micpga_tlv), + + SOC_DOUBLE_R("MIC Preamp Switch", CS42L73_MICAPREPGAAVOL, + CS42L73_MICBPREPGABVOL, 6, 1, 1), + + SOC_DOUBLE_R_SX_TLV("Input Path Digital Volume", CS42L73_IPADVOL, + CS42L73_IPBDVOL, 0, 0xA0, 0x6C, ipd_tlv), + + SOC_DOUBLE_R_SX_TLV("HL Digital Playback Volume", + CS42L73_HLADVOL, CS42L73_HLBDVOL, + 0, 0x34, 0xE4, hl_tlv), + + SOC_SINGLE_TLV("ADC A Boost Volume", + CS42L73_ADCIPC, 2, 0x01, 1, adc_boost_tlv), + + SOC_SINGLE_TLV("ADC B Boost Volume", + CS42L73_ADCIPC, 6, 0x01, 1, adc_boost_tlv), + + SOC_SINGLE_SX_TLV("Speakerphone Digital Volume", + CS42L73_SPKDVOL, 0, 0x34, 0xE4, hl_tlv), + + SOC_SINGLE_SX_TLV("Ear Speaker Digital Volume", + CS42L73_ESLDVOL, 0, 0x34, 0xE4, hl_tlv), + + SOC_DOUBLE_R("Headphone Analog Playback Switch", CS42L73_HPAAVOL, + CS42L73_HPBAVOL, 7, 1, 1), + + SOC_DOUBLE_R("LineOut Analog Playback Switch", CS42L73_LOAAVOL, + CS42L73_LOBAVOL, 7, 1, 1), + SOC_DOUBLE("Input Path Digital Switch", CS42L73_ADCIPC, 0, 4, 1, 1), + SOC_DOUBLE("HL Digital Playback Switch", CS42L73_PBDC, 0, + 1, 1, 1), + SOC_SINGLE("Speakerphone Digital Playback Switch", CS42L73_PBDC, 2, 1, + 1), + SOC_SINGLE("Ear Speaker Digital Playback Switch", CS42L73_PBDC, 3, 1, + 1), + + SOC_SINGLE("PGA Soft-Ramp Switch", CS42L73_MIOPC, 3, 1, 0), + SOC_SINGLE("Analog Zero Cross Switch", CS42L73_MIOPC, 2, 1, 0), + SOC_SINGLE("Digital Soft-Ramp Switch", CS42L73_MIOPC, 1, 1, 0), + SOC_SINGLE("Analog Output Soft-Ramp Switch", CS42L73_MIOPC, 0, 1, 0), + + SOC_DOUBLE("ADC Signal Polarity Switch", CS42L73_ADCIPC, 1, 5, 1, + 0), + + SOC_SINGLE("HL Limiter Attack Rate", CS42L73_LIMARATEHL, 0, 0x3F, + 0), + SOC_SINGLE("HL Limiter Release Rate", CS42L73_LIMRRATEHL, 0, + 0x3F, 0), + + + SOC_SINGLE("HL Limiter Switch", CS42L73_LIMRRATEHL, 7, 1, 0), + SOC_SINGLE("HL Limiter All Channels Switch", CS42L73_LIMRRATEHL, 6, 1, + 0), + + SOC_SINGLE_TLV("HL Limiter Max Threshold Volume", CS42L73_LMAXHL, 5, 7, + 1, limiter_tlv), + + SOC_SINGLE_TLV("HL Limiter Cushion Volume", CS42L73_LMAXHL, 2, 7, 1, + limiter_tlv), + + SOC_SINGLE("SPK Limiter Attack Rate Volume", CS42L73_LIMARATESPK, 0, + 0x3F, 0), + SOC_SINGLE("SPK Limiter Release Rate Volume", CS42L73_LIMRRATESPK, 0, + 0x3F, 0), + SOC_SINGLE("SPK Limiter Switch", CS42L73_LIMRRATESPK, 7, 1, 0), + SOC_SINGLE("SPK Limiter All Channels Switch", CS42L73_LIMRRATESPK, + 6, 1, 0), + SOC_SINGLE_TLV("SPK Limiter Max Threshold Volume", CS42L73_LMAXSPK, 5, + 7, 1, limiter_tlv), + + SOC_SINGLE_TLV("SPK Limiter Cushion Volume", CS42L73_LMAXSPK, 2, 7, 1, + limiter_tlv), + + SOC_SINGLE("ESL Limiter Attack Rate Volume", CS42L73_LIMARATEESL, 0, + 0x3F, 0), + SOC_SINGLE("ESL Limiter Release Rate Volume", CS42L73_LIMRRATEESL, 0, + 0x3F, 0), + SOC_SINGLE("ESL Limiter Switch", CS42L73_LIMRRATEESL, 7, 1, 0), + SOC_SINGLE_TLV("ESL Limiter Max Threshold Volume", CS42L73_LMAXESL, 5, + 7, 1, limiter_tlv), + + SOC_SINGLE_TLV("ESL Limiter Cushion Volume", CS42L73_LMAXESL, 2, 7, 1, + limiter_tlv), + + SOC_SINGLE("ALC Attack Rate Volume", CS42L73_ALCARATE, 0, 0x3F, 0), + SOC_SINGLE("ALC Release Rate Volume", CS42L73_ALCRRATE, 0, 0x3F, 0), + SOC_DOUBLE("ALC Switch", CS42L73_ALCARATE, 6, 7, 1, 0), + SOC_SINGLE_TLV("ALC Max Threshold Volume", CS42L73_ALCMINMAX, 5, 7, 0, + limiter_tlv), + SOC_SINGLE_TLV("ALC Min Threshold Volume", CS42L73_ALCMINMAX, 2, 7, 0, + limiter_tlv), + + SOC_DOUBLE("NG Enable Switch", CS42L73_NGCAB, 6, 7, 1, 0), + SOC_SINGLE("NG Boost Switch", CS42L73_NGCAB, 5, 1, 0), + /* + NG Threshold depends on NG_BOOTSAB, which selects + between two threshold scales in decibels. + Set linear values for now .. + */ + SOC_SINGLE("NG Threshold", CS42L73_NGCAB, 2, 7, 0), + SOC_ENUM("NG Delay", ng_delay_enum), + + SOC_DOUBLE_R_TLV("XSP-IP Volume", + CS42L73_XSPAIPAA, CS42L73_XSPBIPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("XSP-XSP Volume", + CS42L73_XSPAXSPAA, CS42L73_XSPBXSPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("XSP-ASP Volume", + CS42L73_XSPAASPAA, CS42L73_XSPAASPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("XSP-VSP Volume", + CS42L73_XSPAVSPMA, CS42L73_XSPBVSPMA, 0, 0x3F, 1, + attn_tlv), + + SOC_DOUBLE_R_TLV("ASP-IP Volume", + CS42L73_ASPAIPAA, CS42L73_ASPBIPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("ASP-XSP Volume", + CS42L73_ASPAXSPAA, CS42L73_ASPBXSPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("ASP-ASP Volume", + CS42L73_ASPAASPAA, CS42L73_ASPBASPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("ASP-VSP Volume", + CS42L73_ASPAVSPMA, CS42L73_ASPBVSPMA, 0, 0x3F, 1, + attn_tlv), + + SOC_DOUBLE_R_TLV("VSP-IP Volume", + CS42L73_VSPAIPAA, CS42L73_VSPBIPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("VSP-XSP Volume", + CS42L73_VSPAXSPAA, CS42L73_VSPBXSPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("VSP-ASP Volume", + CS42L73_VSPAASPAA, CS42L73_VSPBASPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("VSP-VSP Volume", + CS42L73_VSPAVSPMA, CS42L73_VSPBVSPMA, 0, 0x3F, 1, + attn_tlv), + + SOC_DOUBLE_R_TLV("HL-IP Volume", + CS42L73_HLAIPAA, CS42L73_HLBIPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("HL-XSP Volume", + CS42L73_HLAXSPAA, CS42L73_HLBXSPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("HL-ASP Volume", + CS42L73_HLAASPAA, CS42L73_HLBASPBA, 0, 0x3F, 1, + attn_tlv), + SOC_DOUBLE_R_TLV("HL-VSP Volume", + CS42L73_HLAVSPMA, CS42L73_HLBVSPMA, 0, 0x3F, 1, + attn_tlv), + + SOC_SINGLE_TLV("SPK-IP Mono Volume", + CS42L73_SPKMIPMA, 0, 0x3F, 1, attn_tlv), + SOC_SINGLE_TLV("SPK-XSP Mono Volume", + CS42L73_SPKMXSPA, 0, 0x3F, 1, attn_tlv), + SOC_SINGLE_TLV("SPK-ASP Mono Volume", + CS42L73_SPKMASPA, 0, 0x3F, 1, attn_tlv), + SOC_SINGLE_TLV("SPK-VSP Mono Volume", + CS42L73_SPKMVSPMA, 0, 0x3F, 1, attn_tlv), + + SOC_SINGLE_TLV("ESL-IP Mono Volume", + CS42L73_ESLMIPMA, 0, 0x3F, 1, attn_tlv), + SOC_SINGLE_TLV("ESL-XSP Mono Volume", + CS42L73_ESLMXSPA, 0, 0x3F, 1, attn_tlv), + SOC_SINGLE_TLV("ESL-ASP Mono Volume", + CS42L73_ESLMASPA, 0, 0x3F, 1, attn_tlv), + SOC_SINGLE_TLV("ESL-VSP Mono Volume", + CS42L73_ESLMVSPMA, 0, 0x3F, 1, attn_tlv), + + SOC_ENUM("IP Digital Swap/Mono Select", ip_swap_enum), + + SOC_ENUM("VSPOUT Mono/Stereo Select", vsp_output_mux_enum), + SOC_ENUM("XSPOUT Mono/Stereo Select", xsp_output_mux_enum), +}; + +static int cs42l73_spklo_spk_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs42l73_private *priv = snd_soc_component_get_drvdata(component); + switch (event) { + case SND_SOC_DAPM_POST_PMD: + /* 150 ms delay between setting PDN and MCLKDIS */ + priv->shutdwn_delay = 150; + break; + default: + pr_err("Invalid event = 0x%x\n", event); + } + return 0; +} + +static int cs42l73_ear_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs42l73_private *priv = snd_soc_component_get_drvdata(component); + switch (event) { + case SND_SOC_DAPM_POST_PMD: + /* 50 ms delay between setting PDN and MCLKDIS */ + if (priv->shutdwn_delay < 50) + priv->shutdwn_delay = 50; + break; + default: + pr_err("Invalid event = 0x%x\n", event); + } + return 0; +} + + +static int cs42l73_hp_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs42l73_private *priv = snd_soc_component_get_drvdata(component); + switch (event) { + case SND_SOC_DAPM_POST_PMD: + /* 30 ms delay between setting PDN and MCLKDIS */ + if (priv->shutdwn_delay < 30) + priv->shutdwn_delay = 30; + break; + default: + pr_err("Invalid event = 0x%x\n", event); + } + return 0; +} + +static const struct snd_soc_dapm_widget cs42l73_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("DMICA"), + SND_SOC_DAPM_INPUT("DMICB"), + SND_SOC_DAPM_INPUT("LINEINA"), + SND_SOC_DAPM_INPUT("LINEINB"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_SUPPLY("MIC1 Bias", CS42L73_PWRCTL2, 6, 1, NULL, 0), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", CS42L73_PWRCTL2, 7, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("XSPOUTL", NULL, 0, + CS42L73_PWRCTL2, 1, 1), + SND_SOC_DAPM_AIF_OUT("XSPOUTR", NULL, 0, + CS42L73_PWRCTL2, 1, 1), + SND_SOC_DAPM_AIF_OUT("ASPOUTL", NULL, 0, + CS42L73_PWRCTL2, 3, 1), + SND_SOC_DAPM_AIF_OUT("ASPOUTR", NULL, 0, + CS42L73_PWRCTL2, 3, 1), + SND_SOC_DAPM_AIF_OUT("VSPINOUT", NULL, 0, + CS42L73_PWRCTL2, 4, 1), + + SND_SOC_DAPM_PGA("PGA Left", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGA Right", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("PGA Left Mux", SND_SOC_NOPM, 0, 0, &pgaa_mux), + SND_SOC_DAPM_MUX("PGA Right Mux", SND_SOC_NOPM, 0, 0, &pgab_mux), + + SND_SOC_DAPM_ADC("ADC Left", NULL, CS42L73_PWRCTL1, 7, 1), + SND_SOC_DAPM_ADC("ADC Right", NULL, CS42L73_PWRCTL1, 5, 1), + SND_SOC_DAPM_ADC("DMIC Left", NULL, CS42L73_PWRCTL1, 6, 1), + SND_SOC_DAPM_ADC("DMIC Right", NULL, CS42L73_PWRCTL1, 4, 1), + + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Left Capture", SND_SOC_NOPM, + 0, 0, input_left_mixer, + ARRAY_SIZE(input_left_mixer)), + + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Right Capture", SND_SOC_NOPM, + 0, 0, input_right_mixer, + ARRAY_SIZE(input_right_mixer)), + + SND_SOC_DAPM_MIXER("ASPL Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("ASPR Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("XSPL Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("XSPR Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("VSP Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_AIF_IN("XSPINL", NULL, 0, + CS42L73_PWRCTL2, 0, 1), + SND_SOC_DAPM_AIF_IN("XSPINR", NULL, 0, + CS42L73_PWRCTL2, 0, 1), + SND_SOC_DAPM_AIF_IN("XSPINM", NULL, 0, + CS42L73_PWRCTL2, 0, 1), + + SND_SOC_DAPM_AIF_IN("ASPINL", NULL, 0, + CS42L73_PWRCTL2, 2, 1), + SND_SOC_DAPM_AIF_IN("ASPINR", NULL, 0, + CS42L73_PWRCTL2, 2, 1), + SND_SOC_DAPM_AIF_IN("ASPINM", NULL, 0, + CS42L73_PWRCTL2, 2, 1), + + SND_SOC_DAPM_AIF_IN("VSPINOUT", NULL, 0, + CS42L73_PWRCTL2, 4, 1), + + SND_SOC_DAPM_MIXER("HL Left Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("HL Right Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("SPK Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("ESL Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("ESL-XSP Mux", SND_SOC_NOPM, + 0, 0, &esl_xsp_mixer), + + SND_SOC_DAPM_MUX("ESL-ASP Mux", SND_SOC_NOPM, + 0, 0, &esl_asp_mixer), + + SND_SOC_DAPM_MUX("SPK-ASP Mux", SND_SOC_NOPM, + 0, 0, &spk_asp_mixer), + + SND_SOC_DAPM_MUX("SPK-XSP Mux", SND_SOC_NOPM, + 0, 0, &spk_xsp_mixer), + + SND_SOC_DAPM_PGA("HL Left DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HL Right DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPK DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ESL DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH_E("HP Amp", CS42L73_PWRCTL3, 0, 1, + &hp_amp_ctl, cs42l73_hp_amp_event, + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("LO Amp", CS42L73_PWRCTL3, 1, 1, + &lo_amp_ctl), + SND_SOC_DAPM_SWITCH_E("SPK Amp", CS42L73_PWRCTL3, 2, 1, + &spk_amp_ctl, cs42l73_spklo_spk_amp_event, + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH_E("EAR Amp", CS42L73_PWRCTL3, 3, 1, + &ear_amp_ctl, cs42l73_ear_amp_event, + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH_E("SPKLO Amp", CS42L73_PWRCTL3, 4, 1, + &spklo_amp_ctl, cs42l73_spklo_spk_amp_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_OUTPUT("HPOUTA"), + SND_SOC_DAPM_OUTPUT("HPOUTB"), + SND_SOC_DAPM_OUTPUT("LINEOUTA"), + SND_SOC_DAPM_OUTPUT("LINEOUTB"), + SND_SOC_DAPM_OUTPUT("EAROUT"), + SND_SOC_DAPM_OUTPUT("SPKOUT"), + SND_SOC_DAPM_OUTPUT("SPKLINEOUT"), +}; + +static const struct snd_soc_dapm_route cs42l73_audio_map[] = { + + /* SPKLO EARSPK Paths */ + {"EAROUT", NULL, "EAR Amp"}, + {"SPKLINEOUT", NULL, "SPKLO Amp"}, + + {"EAR Amp", "Switch", "ESL DAC"}, + {"SPKLO Amp", "Switch", "ESL DAC"}, + + {"ESL DAC", "ESL-ASP Mono Volume", "ESL Mixer"}, + {"ESL DAC", "ESL-XSP Mono Volume", "ESL Mixer"}, + {"ESL DAC", "ESL-VSP Mono Volume", "VSPINOUT"}, + /* Loopback */ + {"ESL DAC", "ESL-IP Mono Volume", "Input Left Capture"}, + {"ESL DAC", "ESL-IP Mono Volume", "Input Right Capture"}, + + {"ESL Mixer", NULL, "ESL-ASP Mux"}, + {"ESL Mixer", NULL, "ESL-XSP Mux"}, + + {"ESL-ASP Mux", "Left", "ASPINL"}, + {"ESL-ASP Mux", "Right", "ASPINR"}, + {"ESL-ASP Mux", "Mono Mix", "ASPINM"}, + + {"ESL-XSP Mux", "Left", "XSPINL"}, + {"ESL-XSP Mux", "Right", "XSPINR"}, + {"ESL-XSP Mux", "Mono Mix", "XSPINM"}, + + /* Speakerphone Paths */ + {"SPKOUT", NULL, "SPK Amp"}, + {"SPK Amp", "Switch", "SPK DAC"}, + + {"SPK DAC", "SPK-ASP Mono Volume", "SPK Mixer"}, + {"SPK DAC", "SPK-XSP Mono Volume", "SPK Mixer"}, + {"SPK DAC", "SPK-VSP Mono Volume", "VSPINOUT"}, + /* Loopback */ + {"SPK DAC", "SPK-IP Mono Volume", "Input Left Capture"}, + {"SPK DAC", "SPK-IP Mono Volume", "Input Right Capture"}, + + {"SPK Mixer", NULL, "SPK-ASP Mux"}, + {"SPK Mixer", NULL, "SPK-XSP Mux"}, + + {"SPK-ASP Mux", "Left", "ASPINL"}, + {"SPK-ASP Mux", "Mono Mix", "ASPINM"}, + {"SPK-ASP Mux", "Right", "ASPINR"}, + + {"SPK-XSP Mux", "Left", "XSPINL"}, + {"SPK-XSP Mux", "Mono Mix", "XSPINM"}, + {"SPK-XSP Mux", "Right", "XSPINR"}, + + /* HP LineOUT Paths */ + {"HPOUTA", NULL, "HP Amp"}, + {"HPOUTB", NULL, "HP Amp"}, + {"LINEOUTA", NULL, "LO Amp"}, + {"LINEOUTB", NULL, "LO Amp"}, + + {"HP Amp", "Switch", "HL Left DAC"}, + {"HP Amp", "Switch", "HL Right DAC"}, + {"LO Amp", "Switch", "HL Left DAC"}, + {"LO Amp", "Switch", "HL Right DAC"}, + + {"HL Left DAC", "HL-XSP Volume", "HL Left Mixer"}, + {"HL Right DAC", "HL-XSP Volume", "HL Right Mixer"}, + {"HL Left DAC", "HL-ASP Volume", "HL Left Mixer"}, + {"HL Right DAC", "HL-ASP Volume", "HL Right Mixer"}, + {"HL Left DAC", "HL-VSP Volume", "HL Left Mixer"}, + {"HL Right DAC", "HL-VSP Volume", "HL Right Mixer"}, + /* Loopback */ + {"HL Left DAC", "HL-IP Volume", "HL Left Mixer"}, + {"HL Right DAC", "HL-IP Volume", "HL Right Mixer"}, + {"HL Left Mixer", NULL, "Input Left Capture"}, + {"HL Right Mixer", NULL, "Input Right Capture"}, + + {"HL Left Mixer", NULL, "ASPINL"}, + {"HL Right Mixer", NULL, "ASPINR"}, + {"HL Left Mixer", NULL, "XSPINL"}, + {"HL Right Mixer", NULL, "XSPINR"}, + {"HL Left Mixer", NULL, "VSPINOUT"}, + {"HL Right Mixer", NULL, "VSPINOUT"}, + + {"ASPINL", NULL, "ASP Playback"}, + {"ASPINM", NULL, "ASP Playback"}, + {"ASPINR", NULL, "ASP Playback"}, + {"XSPINL", NULL, "XSP Playback"}, + {"XSPINM", NULL, "XSP Playback"}, + {"XSPINR", NULL, "XSP Playback"}, + {"VSPINOUT", NULL, "VSP Playback"}, + + /* Capture Paths */ + {"MIC1", NULL, "MIC1 Bias"}, + {"PGA Left Mux", "Mic 1", "MIC1"}, + {"MIC2", NULL, "MIC2 Bias"}, + {"PGA Right Mux", "Mic 2", "MIC2"}, + + {"PGA Left Mux", "Line A", "LINEINA"}, + {"PGA Right Mux", "Line B", "LINEINB"}, + + {"PGA Left", NULL, "PGA Left Mux"}, + {"PGA Right", NULL, "PGA Right Mux"}, + + {"ADC Left", NULL, "PGA Left"}, + {"ADC Right", NULL, "PGA Right"}, + {"DMIC Left", NULL, "DMICA"}, + {"DMIC Right", NULL, "DMICB"}, + + {"Input Left Capture", "ADC Left Input", "ADC Left"}, + {"Input Right Capture", "ADC Right Input", "ADC Right"}, + {"Input Left Capture", "DMIC Left Input", "DMIC Left"}, + {"Input Right Capture", "DMIC Right Input", "DMIC Right"}, + + /* Audio Capture */ + {"ASPL Output Mixer", NULL, "Input Left Capture"}, + {"ASPR Output Mixer", NULL, "Input Right Capture"}, + + {"ASPOUTL", "ASP-IP Volume", "ASPL Output Mixer"}, + {"ASPOUTR", "ASP-IP Volume", "ASPR Output Mixer"}, + + /* Auxillary Capture */ + {"XSPL Output Mixer", NULL, "Input Left Capture"}, + {"XSPR Output Mixer", NULL, "Input Right Capture"}, + + {"XSPOUTL", "XSP-IP Volume", "XSPL Output Mixer"}, + {"XSPOUTR", "XSP-IP Volume", "XSPR Output Mixer"}, + + {"XSPOUTL", NULL, "XSPL Output Mixer"}, + {"XSPOUTR", NULL, "XSPR Output Mixer"}, + + /* Voice Capture */ + {"VSP Output Mixer", NULL, "Input Left Capture"}, + {"VSP Output Mixer", NULL, "Input Right Capture"}, + + {"VSPINOUT", "VSP-IP Volume", "VSP Output Mixer"}, + + {"VSPINOUT", NULL, "VSP Output Mixer"}, + + {"ASP Capture", NULL, "ASPOUTL"}, + {"ASP Capture", NULL, "ASPOUTR"}, + {"XSP Capture", NULL, "XSPOUTL"}, + {"XSP Capture", NULL, "XSPOUTR"}, + {"VSP Capture", NULL, "VSPINOUT"}, +}; + +struct cs42l73_mclk_div { + u32 mclk; + u32 srate; + u8 mmcc; +}; + +static const struct cs42l73_mclk_div cs42l73_mclk_coeffs[] = { + /* MCLK, Sample Rate, xMMCC[5:0] */ + {5644800, 11025, 0x30}, + {5644800, 22050, 0x20}, + {5644800, 44100, 0x10}, + + {6000000, 8000, 0x39}, + {6000000, 11025, 0x33}, + {6000000, 12000, 0x31}, + {6000000, 16000, 0x29}, + {6000000, 22050, 0x23}, + {6000000, 24000, 0x21}, + {6000000, 32000, 0x19}, + {6000000, 44100, 0x13}, + {6000000, 48000, 0x11}, + + {6144000, 8000, 0x38}, + {6144000, 12000, 0x30}, + {6144000, 16000, 0x28}, + {6144000, 24000, 0x20}, + {6144000, 32000, 0x18}, + {6144000, 48000, 0x10}, + + {6500000, 8000, 0x3C}, + {6500000, 11025, 0x35}, + {6500000, 12000, 0x34}, + {6500000, 16000, 0x2C}, + {6500000, 22050, 0x25}, + {6500000, 24000, 0x24}, + {6500000, 32000, 0x1C}, + {6500000, 44100, 0x15}, + {6500000, 48000, 0x14}, + + {6400000, 8000, 0x3E}, + {6400000, 11025, 0x37}, + {6400000, 12000, 0x36}, + {6400000, 16000, 0x2E}, + {6400000, 22050, 0x27}, + {6400000, 24000, 0x26}, + {6400000, 32000, 0x1E}, + {6400000, 44100, 0x17}, + {6400000, 48000, 0x16}, +}; + +struct cs42l73_mclkx_div { + u32 mclkx; + u8 ratio; + u8 mclkdiv; +}; + +static const struct cs42l73_mclkx_div cs42l73_mclkx_coeffs[] = { + {5644800, 1, 0}, /* 5644800 */ + {6000000, 1, 0}, /* 6000000 */ + {6144000, 1, 0}, /* 6144000 */ + {11289600, 2, 2}, /* 5644800 */ + {12288000, 2, 2}, /* 6144000 */ + {12000000, 2, 2}, /* 6000000 */ + {13000000, 2, 2}, /* 6500000 */ + {19200000, 3, 3}, /* 6400000 */ + {24000000, 4, 4}, /* 6000000 */ + {26000000, 4, 4}, /* 6500000 */ + {38400000, 6, 5} /* 6400000 */ +}; + +static int cs42l73_get_mclkx_coeff(int mclkx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs42l73_mclkx_coeffs); i++) { + if (cs42l73_mclkx_coeffs[i].mclkx == mclkx) + return i; + } + return -EINVAL; +} + +static int cs42l73_get_mclk_coeff(int mclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs42l73_mclk_coeffs); i++) { + if (cs42l73_mclk_coeffs[i].mclk == mclk && + cs42l73_mclk_coeffs[i].srate == srate) + return i; + } + return -EINVAL; + +} + +static int cs42l73_set_mclk(struct snd_soc_dai *dai, unsigned int freq) +{ + struct snd_soc_component *component = dai->component; + struct cs42l73_private *priv = snd_soc_component_get_drvdata(component); + + int mclkx_coeff; + u32 mclk = 0; + u8 dmmcc = 0; + + /* MCLKX -> MCLK */ + mclkx_coeff = cs42l73_get_mclkx_coeff(freq); + if (mclkx_coeff < 0) + return mclkx_coeff; + + mclk = cs42l73_mclkx_coeffs[mclkx_coeff].mclkx / + cs42l73_mclkx_coeffs[mclkx_coeff].ratio; + + dev_dbg(component->dev, "MCLK%u %u <-> internal MCLK %u\n", + priv->mclksel + 1, cs42l73_mclkx_coeffs[mclkx_coeff].mclkx, + mclk); + + dmmcc = (priv->mclksel << 4) | + (cs42l73_mclkx_coeffs[mclkx_coeff].mclkdiv << 1); + + snd_soc_component_write(component, CS42L73_DMMCC, dmmcc); + + priv->sysclk = mclkx_coeff; + priv->mclk = mclk; + + return 0; +} + +static int cs42l73_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct cs42l73_private *priv = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case CS42L73_CLKID_MCLK1: + break; + case CS42L73_CLKID_MCLK2: + break; + default: + return -EINVAL; + } + + if ((cs42l73_set_mclk(dai, freq)) < 0) { + dev_err(component->dev, "Unable to set MCLK for dai %s\n", + dai->name); + return -EINVAL; + } + + priv->mclksel = clk_id; + + return 0; +} + +static int cs42l73_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42l73_private *priv = snd_soc_component_get_drvdata(component); + u8 id = codec_dai->id; + unsigned int inv, format; + u8 spc, mmcc; + + spc = snd_soc_component_read32(component, CS42L73_SPC(id)); + mmcc = snd_soc_component_read32(component, CS42L73_MMCC(id)); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + mmcc |= CS42L73_MS_MASTER; + break; + + case SND_SOC_DAIFMT_CBS_CFS: + mmcc &= ~CS42L73_MS_MASTER; + break; + + default: + return -EINVAL; + } + + format = (fmt & SND_SOC_DAIFMT_FORMAT_MASK); + inv = (fmt & SND_SOC_DAIFMT_INV_MASK); + + switch (format) { + case SND_SOC_DAIFMT_I2S: + spc &= ~CS42L73_SPDIF_PCM; + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + if (mmcc & CS42L73_MS_MASTER) { + dev_err(component->dev, + "PCM format in slave mode only\n"); + return -EINVAL; + } + if (id == CS42L73_ASP) { + dev_err(component->dev, + "PCM format is not supported on ASP port\n"); + return -EINVAL; + } + spc |= CS42L73_SPDIF_PCM; + break; + default: + return -EINVAL; + } + + if (spc & CS42L73_SPDIF_PCM) { + /* Clear PCM mode, clear PCM_BIT_ORDER bit for MSB->LSB */ + spc &= ~(CS42L73_PCM_MODE_MASK | CS42L73_PCM_BIT_ORDER); + switch (format) { + case SND_SOC_DAIFMT_DSP_B: + if (inv == SND_SOC_DAIFMT_IB_IF) + spc |= CS42L73_PCM_MODE0; + if (inv == SND_SOC_DAIFMT_IB_NF) + spc |= CS42L73_PCM_MODE1; + break; + case SND_SOC_DAIFMT_DSP_A: + if (inv == SND_SOC_DAIFMT_IB_IF) + spc |= CS42L73_PCM_MODE1; + break; + default: + return -EINVAL; + } + } + + priv->config[id].spc = spc; + priv->config[id].mmcc = mmcc; + + return 0; +} + +static const unsigned int cs42l73_asrc_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000 +}; + +static unsigned int cs42l73_get_xspfs_coeff(u32 rate) +{ + int i; + for (i = 0; i < ARRAY_SIZE(cs42l73_asrc_rates); i++) { + if (cs42l73_asrc_rates[i] == rate) + return i + 1; + } + return 0; /* 0 = Don't know */ +} + +static void cs42l73_update_asrc(struct snd_soc_component *component, int id, int srate) +{ + u8 spfs = 0; + + if (srate > 0) + spfs = cs42l73_get_xspfs_coeff(srate); + + switch (id) { + case CS42L73_XSP: + snd_soc_component_update_bits(component, CS42L73_VXSPFS, 0x0f, spfs); + break; + case CS42L73_ASP: + snd_soc_component_update_bits(component, CS42L73_ASPC, 0x3c, spfs << 2); + break; + case CS42L73_VSP: + snd_soc_component_update_bits(component, CS42L73_VXSPFS, 0xf0, spfs << 4); + break; + default: + break; + } +} + +static int cs42l73_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs42l73_private *priv = snd_soc_component_get_drvdata(component); + int id = dai->id; + int mclk_coeff; + int srate = params_rate(params); + + if (priv->config[id].mmcc & CS42L73_MS_MASTER) { + /* CS42L73 Master */ + /* MCLK -> srate */ + mclk_coeff = + cs42l73_get_mclk_coeff(priv->mclk, srate); + + if (mclk_coeff < 0) + return -EINVAL; + + dev_dbg(component->dev, + "DAI[%d]: MCLK %u, srate %u, MMCC[5:0] = %x\n", + id, priv->mclk, srate, + cs42l73_mclk_coeffs[mclk_coeff].mmcc); + + priv->config[id].mmcc &= 0xC0; + priv->config[id].mmcc |= cs42l73_mclk_coeffs[mclk_coeff].mmcc; + priv->config[id].spc &= 0xFC; + /* Use SCLK=64*Fs if internal MCLK >= 6.4MHz */ + if (priv->mclk >= 6400000) + priv->config[id].spc |= CS42L73_MCK_SCLK_64FS; + else + priv->config[id].spc |= CS42L73_MCK_SCLK_MCLK; + } else { + /* CS42L73 Slave */ + priv->config[id].spc &= 0xFC; + priv->config[id].spc |= CS42L73_MCK_SCLK_64FS; + } + /* Update ASRCs */ + priv->config[id].srate = srate; + + snd_soc_component_write(component, CS42L73_SPC(id), priv->config[id].spc); + snd_soc_component_write(component, CS42L73_MMCC(id), priv->config[id].mmcc); + + cs42l73_update_asrc(component, id, srate); + + return 0; +} + +static int cs42l73_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct cs42l73_private *cs42l73 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_component_update_bits(component, CS42L73_DMMCC, CS42L73_MCLKDIS, 0); + snd_soc_component_update_bits(component, CS42L73_PWRCTL1, CS42L73_PDN, 0); + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_cache_only(cs42l73->regmap, false); + regcache_sync(cs42l73->regmap); + } + snd_soc_component_update_bits(component, CS42L73_PWRCTL1, CS42L73_PDN, 1); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, CS42L73_PWRCTL1, CS42L73_PDN, 1); + if (cs42l73->shutdwn_delay > 0) { + mdelay(cs42l73->shutdwn_delay); + cs42l73->shutdwn_delay = 0; + } else { + mdelay(15); /* Min amount of time requred to power + * down. + */ + } + snd_soc_component_update_bits(component, CS42L73_DMMCC, CS42L73_MCLKDIS, 1); + break; + } + return 0; +} + +static int cs42l73_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_component *component = dai->component; + int id = dai->id; + + return snd_soc_component_update_bits(component, CS42L73_SPC(id), CS42L73_SP_3ST, + tristate << 7); +} + +static const struct snd_pcm_hw_constraint_list constraints_12_24 = { + .count = ARRAY_SIZE(cs42l73_asrc_rates), + .list = cs42l73_asrc_rates, +}; + +static int cs42l73_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_12_24); + return 0; +} + + +#define CS42L73_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops cs42l73_ops = { + .startup = cs42l73_pcm_startup, + .hw_params = cs42l73_pcm_hw_params, + .set_fmt = cs42l73_set_dai_fmt, + .set_sysclk = cs42l73_set_sysclk, + .set_tristate = cs42l73_set_tristate, +}; + +static struct snd_soc_dai_driver cs42l73_dai[] = { + { + .name = "cs42l73-xsp", + .id = CS42L73_XSP, + .playback = { + .stream_name = "XSP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS42L73_FORMATS, + }, + .capture = { + .stream_name = "XSP Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS42L73_FORMATS, + }, + .ops = &cs42l73_ops, + .symmetric_rates = 1, + }, + { + .name = "cs42l73-asp", + .id = CS42L73_ASP, + .playback = { + .stream_name = "ASP Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS42L73_FORMATS, + }, + .capture = { + .stream_name = "ASP Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS42L73_FORMATS, + }, + .ops = &cs42l73_ops, + .symmetric_rates = 1, + }, + { + .name = "cs42l73-vsp", + .id = CS42L73_VSP, + .playback = { + .stream_name = "VSP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS42L73_FORMATS, + }, + .capture = { + .stream_name = "VSP Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS42L73_FORMATS, + }, + .ops = &cs42l73_ops, + .symmetric_rates = 1, + } +}; + +static int cs42l73_probe(struct snd_soc_component *component) +{ + struct cs42l73_private *cs42l73 = snd_soc_component_get_drvdata(component); + + /* Set Charge Pump Frequency */ + if (cs42l73->pdata.chgfreq) + snd_soc_component_update_bits(component, CS42L73_CPFCHC, + CS42L73_CHARGEPUMP_MASK, + cs42l73->pdata.chgfreq << 4); + + /* MCLK1 as master clk */ + cs42l73->mclksel = CS42L73_CLKID_MCLK1; + cs42l73->mclk = 0; + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_cs42l73 = { + .probe = cs42l73_probe, + .set_bias_level = cs42l73_set_bias_level, + .controls = cs42l73_snd_controls, + .num_controls = ARRAY_SIZE(cs42l73_snd_controls), + .dapm_widgets = cs42l73_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42l73_dapm_widgets), + .dapm_routes = cs42l73_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs42l73_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config cs42l73_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42L73_MAX_REGISTER, + .reg_defaults = cs42l73_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs42l73_reg_defaults), + .volatile_reg = cs42l73_volatile_register, + .readable_reg = cs42l73_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs42l73_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs42l73_private *cs42l73; + struct cs42l73_platform_data *pdata = dev_get_platdata(&i2c_client->dev); + int ret; + unsigned int devid = 0; + unsigned int reg; + u32 val32; + + cs42l73 = devm_kzalloc(&i2c_client->dev, sizeof(*cs42l73), GFP_KERNEL); + if (!cs42l73) + return -ENOMEM; + + cs42l73->regmap = devm_regmap_init_i2c(i2c_client, &cs42l73_regmap); + if (IS_ERR(cs42l73->regmap)) { + ret = PTR_ERR(cs42l73->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + if (pdata) { + cs42l73->pdata = *pdata; + } else { + pdata = devm_kzalloc(&i2c_client->dev, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (i2c_client->dev.of_node) { + if (of_property_read_u32(i2c_client->dev.of_node, + "chgfreq", &val32) >= 0) + pdata->chgfreq = val32; + } + pdata->reset_gpio = of_get_named_gpio(i2c_client->dev.of_node, + "reset-gpio", 0); + cs42l73->pdata = *pdata; + } + + i2c_set_clientdata(i2c_client, cs42l73); + + if (cs42l73->pdata.reset_gpio) { + ret = devm_gpio_request_one(&i2c_client->dev, + cs42l73->pdata.reset_gpio, + GPIOF_OUT_INIT_HIGH, + "CS42L73 /RST"); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to request /RST %d: %d\n", + cs42l73->pdata.reset_gpio, ret); + return ret; + } + gpio_set_value_cansleep(cs42l73->pdata.reset_gpio, 0); + gpio_set_value_cansleep(cs42l73->pdata.reset_gpio, 1); + } + + /* initialize codec */ + ret = regmap_read(cs42l73->regmap, CS42L73_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + + ret = regmap_read(cs42l73->regmap, CS42L73_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + + ret = regmap_read(cs42l73->regmap, CS42L73_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS42L73_DEVID) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS42L73 Device ID (%X). Expected %X\n", + devid, CS42L73_DEVID); + return ret; + } + + ret = regmap_read(cs42l73->regmap, CS42L73_REVID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed\n"); + return ret; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS42L73, Revision: %02X\n", reg & 0xFF); + + ret = devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_cs42l73, cs42l73_dai, + ARRAY_SIZE(cs42l73_dai)); + if (ret < 0) + return ret; + return 0; +} + +static const struct of_device_id cs42l73_of_match[] = { + { .compatible = "cirrus,cs42l73", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs42l73_of_match); + +static const struct i2c_device_id cs42l73_id[] = { + {"cs42l73", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs42l73_id); + +static struct i2c_driver cs42l73_i2c_driver = { + .driver = { + .name = "cs42l73", + .of_match_table = cs42l73_of_match, + }, + .id_table = cs42l73_id, + .probe = cs42l73_i2c_probe, + +}; + +module_i2c_driver(cs42l73_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS42L73 driver"); +MODULE_AUTHOR("Georgi Vlaev, Nucleus Systems Ltd, "); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l73.h b/sound/soc/codecs/cs42l73.h new file mode 100644 index 000000000..45746186a --- /dev/null +++ b/sound/soc/codecs/cs42l73.h @@ -0,0 +1,226 @@ +/* + * ALSA SoC CS42L73 codec driver + * + * Copyright 2011 Cirrus Logic, Inc. + * + * Author: Georgi Vlaev + * Brian Austin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __CS42L73_H__ +#define __CS42L73_H__ + +/* I2C Registers */ +/* I2C Address: 1001010[R/W] - 10010100 = 0x94(Write); 10010101 = 0x95(Read) */ +#define CS42L73_CHIP_ID 0x4a +#define CS42L73_DEVID_AB 0x01 /* Device ID A & B [RO]. */ +#define CS42L73_DEVID_CD 0x02 /* Device ID C & D [RO]. */ +#define CS42L73_DEVID_E 0x03 /* Device ID E [RO]. */ +#define CS42L73_REVID 0x05 /* Revision ID [RO]. */ +#define CS42L73_PWRCTL1 0x06 /* Power Control 1. */ +#define CS42L73_PWRCTL2 0x07 /* Power Control 2. */ +#define CS42L73_PWRCTL3 0x08 /* Power Control 3. */ +#define CS42L73_CPFCHC 0x09 /* Charge Pump Freq. Class H Ctl. */ +#define CS42L73_OLMBMSDC 0x0A /* Output Load, MIC Bias, MIC2 SDT */ +#define CS42L73_DMMCC 0x0B /* Digital MIC & Master Clock Ctl. */ +#define CS42L73_XSPC 0x0C /* Auxiliary Serial Port (XSP) Ctl. */ +#define CS42L73_XSPMMCC 0x0D /* XSP Master Mode Clocking Control. */ +#define CS42L73_ASPC 0x0E /* Audio Serial Port (ASP) Control. */ +#define CS42L73_ASPMMCC 0x0F /* ASP Master Mode Clocking Control. */ +#define CS42L73_VSPC 0x10 /* Voice Serial Port (VSP) Control. */ +#define CS42L73_VSPMMCC 0x11 /* VSP Master Mode Clocking Control. */ +#define CS42L73_VXSPFS 0x12 /* VSP & XSP Sample Rate. */ +#define CS42L73_MIOPC 0x13 /* Misc. Input & Output Path Control. */ +#define CS42L73_ADCIPC 0x14 /* ADC/IP Control. */ +#define CS42L73_MICAPREPGAAVOL 0x15 /* MIC 1 [A] PreAmp, PGAA Vol. */ +#define CS42L73_MICBPREPGABVOL 0x16 /* MIC 2 [B] PreAmp, PGAB Vol. */ +#define CS42L73_IPADVOL 0x17 /* Input Pat7h A Digital Volume. */ +#define CS42L73_IPBDVOL 0x18 /* Input Path B Digital Volume. */ +#define CS42L73_PBDC 0x19 /* Playback Digital Control. */ +#define CS42L73_HLADVOL 0x1A /* HP/Line A Out Digital Vol. */ +#define CS42L73_HLBDVOL 0x1B /* HP/Line B Out Digital Vol. */ +#define CS42L73_SPKDVOL 0x1C /* Spkphone Out [A] Digital Vol. */ +#define CS42L73_ESLDVOL 0x1D /* Ear/Spkphone LO [B] Digital */ +#define CS42L73_HPAAVOL 0x1E /* HP A Analog Volume. */ +#define CS42L73_HPBAVOL 0x1F /* HP B Analog Volume. */ +#define CS42L73_LOAAVOL 0x20 /* Line Out A Analog Volume. */ +#define CS42L73_LOBAVOL 0x21 /* Line Out B Analog Volume. */ +#define CS42L73_STRINV 0x22 /* Stereo Input Path Adv. Vol. */ +#define CS42L73_XSPINV 0x23 /* Auxiliary Port Input Advisory Vol. */ +#define CS42L73_ASPINV 0x24 /* Audio Port Input Advisory Vol. */ +#define CS42L73_VSPINV 0x25 /* Voice Port Input Advisory Vol. */ +#define CS42L73_LIMARATEHL 0x26 /* Lmtr Attack Rate HP/Line. */ +#define CS42L73_LIMRRATEHL 0x27 /* Lmtr Ctl, Rel.Rate HP/Line. */ +#define CS42L73_LMAXHL 0x28 /* Lmtr Thresholds HP/Line. */ +#define CS42L73_LIMARATESPK 0x29 /* Lmtr Attack Rate Spkphone [A]. */ +#define CS42L73_LIMRRATESPK 0x2A /* Lmtr Ctl,Release Rate Spk. [A]. */ +#define CS42L73_LMAXSPK 0x2B /* Lmtr Thresholds Spkphone [A]. */ +#define CS42L73_LIMARATEESL 0x2C /* Lmtr Attack Rate */ +#define CS42L73_LIMRRATEESL 0x2D /* Lmtr Ctl,Release Rate */ +#define CS42L73_LMAXESL 0x2E /* Lmtr Thresholds */ +#define CS42L73_ALCARATE 0x2F /* ALC Enable, Attack Rate AB. */ +#define CS42L73_ALCRRATE 0x30 /* ALC Release Rate AB. */ +#define CS42L73_ALCMINMAX 0x31 /* ALC Thresholds AB. */ +#define CS42L73_NGCAB 0x32 /* Noise Gate Ctl AB. */ +#define CS42L73_ALCNGMC 0x33 /* ALC & Noise Gate Misc Ctl. */ +#define CS42L73_MIXERCTL 0x34 /* Mixer Control. */ +#define CS42L73_HLAIPAA 0x35 /* HP/LO Left Mixer: L. */ +#define CS42L73_HLBIPBA 0x36 /* HP/LO Right Mixer: R. */ +#define CS42L73_HLAXSPAA 0x37 /* HP/LO Left Mixer: XSP L */ +#define CS42L73_HLBXSPBA 0x38 /* HP/LO Right Mixer: XSP R */ +#define CS42L73_HLAASPAA 0x39 /* HP/LO Left Mixer: ASP L */ +#define CS42L73_HLBASPBA 0x3A /* HP/LO Right Mixer: ASP R */ +#define CS42L73_HLAVSPMA 0x3B /* HP/LO Left Mixer: VSP. */ +#define CS42L73_HLBVSPMA 0x3C /* HP/LO Right Mixer: VSP */ +#define CS42L73_XSPAIPAA 0x3D /* XSP Left Mixer: Left */ +#define CS42L73_XSPBIPBA 0x3E /* XSP Rt. Mixer: Right */ +#define CS42L73_XSPAXSPAA 0x3F /* XSP Left Mixer: XSP L */ +#define CS42L73_XSPBXSPBA 0x40 /* XSP Rt. Mixer: XSP R */ +#define CS42L73_XSPAASPAA 0x41 /* XSP Left Mixer: ASP L */ +#define CS42L73_XSPAASPBA 0x42 /* XSP Rt. Mixer: ASP R */ +#define CS42L73_XSPAVSPMA 0x43 /* XSP Left Mixer: VSP */ +#define CS42L73_XSPBVSPMA 0x44 /* XSP Rt. Mixer: VSP */ +#define CS42L73_ASPAIPAA 0x45 /* ASP Left Mixer: Left */ +#define CS42L73_ASPBIPBA 0x46 /* ASP Rt. Mixer: Right */ +#define CS42L73_ASPAXSPAA 0x47 /* ASP Left Mixer: XSP L */ +#define CS42L73_ASPBXSPBA 0x48 /* ASP Rt. Mixer: XSP R */ +#define CS42L73_ASPAASPAA 0x49 /* ASP Left Mixer: ASP L */ +#define CS42L73_ASPBASPBA 0x4A /* ASP Rt. Mixer: ASP R */ +#define CS42L73_ASPAVSPMA 0x4B /* ASP Left Mixer: VSP */ +#define CS42L73_ASPBVSPMA 0x4C /* ASP Rt. Mixer: VSP */ +#define CS42L73_VSPAIPAA 0x4D /* VSP Left Mixer: Left */ +#define CS42L73_VSPBIPBA 0x4E /* VSP Rt. Mixer: Right */ +#define CS42L73_VSPAXSPAA 0x4F /* VSP Left Mixer: XSP L */ +#define CS42L73_VSPBXSPBA 0x50 /* VSP Rt. Mixer: XSP R */ +#define CS42L73_VSPAASPAA 0x51 /* VSP Left Mixer: ASP Left */ +#define CS42L73_VSPBASPBA 0x52 /* VSP Rt. Mixer: ASP Right */ +#define CS42L73_VSPAVSPMA 0x53 /* VSP Left Mixer: VSP */ +#define CS42L73_VSPBVSPMA 0x54 /* VSP Rt. Mixer: VSP */ +#define CS42L73_MMIXCTL 0x55 /* Mono Mixer Controls. */ +#define CS42L73_SPKMIPMA 0x56 /* SPK Mono Mixer: In. Path */ +#define CS42L73_SPKMXSPA 0x57 /* SPK Mono Mixer: XSP Mono/L/R Att. */ +#define CS42L73_SPKMASPA 0x58 /* SPK Mono Mixer: ASP Mono/L/R Att. */ +#define CS42L73_SPKMVSPMA 0x59 /* SPK Mono Mixer: VSP Mono Atten. */ +#define CS42L73_ESLMIPMA 0x5A /* Ear/SpLO Mono Mixer: */ +#define CS42L73_ESLMXSPA 0x5B /* Ear/SpLO Mono Mixer: XSP */ +#define CS42L73_ESLMASPA 0x5C /* Ear/SpLO Mono Mixer: ASP */ +#define CS42L73_ESLMVSPMA 0x5D /* Ear/SpLO Mono Mixer: VSP */ +#define CS42L73_IM1 0x5E /* Interrupt Mask 1. */ +#define CS42L73_IM2 0x5F /* Interrupt Mask 2. */ +#define CS42L73_IS1 0x60 /* Interrupt Status 1 [RO]. */ +#define CS42L73_IS2 0x61 /* Interrupt Status 2 [RO]. */ +#define CS42L73_MAX_REGISTER 0x61 /* Total Registers */ +/* Bitfield Definitions */ + +/* CS42L73_PWRCTL1 */ +#define CS42L73_PDN_ADCB (1 << 7) +#define CS42L73_PDN_DMICB (1 << 6) +#define CS42L73_PDN_ADCA (1 << 5) +#define CS42L73_PDN_DMICA (1 << 4) +#define CS42L73_PDN_LDO (1 << 2) +#define CS42L73_DISCHG_FILT (1 << 1) +#define CS42L73_PDN (1 << 0) + +/* CS42L73_PWRCTL2 */ +#define CS42L73_PDN_MIC2_BIAS (1 << 7) +#define CS42L73_PDN_MIC1_BIAS (1 << 6) +#define CS42L73_PDN_VSP (1 << 4) +#define CS42L73_PDN_ASP_SDOUT (1 << 3) +#define CS42L73_PDN_ASP_SDIN (1 << 2) +#define CS42L73_PDN_XSP_SDOUT (1 << 1) +#define CS42L73_PDN_XSP_SDIN (1 << 0) + +/* CS42L73_PWRCTL3 */ +#define CS42L73_PDN_THMS (1 << 5) +#define CS42L73_PDN_SPKLO (1 << 4) +#define CS42L73_PDN_EAR (1 << 3) +#define CS42L73_PDN_SPK (1 << 2) +#define CS42L73_PDN_LO (1 << 1) +#define CS42L73_PDN_HP (1 << 0) + +/* Thermal Overload Detect. Requires interrupt ... */ +#define CS42L73_THMOVLD_150C 0 +#define CS42L73_THMOVLD_132C 1 +#define CS42L73_THMOVLD_115C 2 +#define CS42L73_THMOVLD_098C 3 + +#define CS42L73_CHARGEPUMP_MASK (0xF0) + +/* CS42L73_ASPC, CS42L73_XSPC, CS42L73_VSPC */ +#define CS42L73_SP_3ST (1 << 7) +#define CS42L73_SPDIF_I2S (0 << 6) +#define CS42L73_SPDIF_PCM (1 << 6) +#define CS42L73_PCM_MODE0 (0 << 4) +#define CS42L73_PCM_MODE1 (1 << 4) +#define CS42L73_PCM_MODE2 (2 << 4) +#define CS42L73_PCM_MODE_MASK (3 << 4) +#define CS42L73_PCM_BIT_ORDER (1 << 3) +#define CS42L73_MCK_SCLK_64FS (0 << 0) +#define CS42L73_MCK_SCLK_MCLK (2 << 0) +#define CS42L73_MCK_SCLK_PREMCLK (3 << 0) + +/* CS42L73_xSPMMCC */ +#define CS42L73_MS_MASTER (1 << 7) + + +/* CS42L73_DMMCC */ +#define CS42L73_MCLKDIS (1 << 0) +#define CS42L73_MCLKSEL_MCLK2 (1 << 4) +#define CS42L73_MCLKSEL_MCLK1 (0 << 4) + +/* CS42L73 MCLK derived from MCLK1 or MCLK2 */ +#define CS42L73_CLKID_MCLK1 0 +#define CS42L73_CLKID_MCLK2 1 + +#define CS42L73_MCLKXDIV 0 +#define CS42L73_MMCCDIV 1 + +#define CS42L73_XSP 0 +#define CS42L73_ASP 1 +#define CS42L73_VSP 2 + +/* IS1, IM1 */ +#define CS42L73_MIC2_SDET (1 << 6) +#define CS42L73_THMOVLD (1 << 4) +#define CS42L73_DIGMIXOVFL (1 << 3) +#define CS42L73_IPBOVFL (1 << 1) +#define CS42L73_IPAOVFL (1 << 0) + +/* Analog Softramp */ +#define CS42L73_ANLGOSFT (1 << 0) + +/* HP A/B Analog Mute */ +#define CS42L73_HPA_MUTE (1 << 7) +/* LO A/B Analog Mute */ +#define CS42L73_LOA_MUTE (1 << 7) +/* Digital Mute */ +#define CS42L73_HLAD_MUTE (1 << 0) +#define CS42L73_HLBD_MUTE (1 << 1) +#define CS42L73_SPKD_MUTE (1 << 2) +#define CS42L73_ESLD_MUTE (1 << 3) + +/* Misc defines for codec */ +#define CS42L73_DEVID 0x00042A73 +#define CS42L73_MCLKX_MIN 5644800 +#define CS42L73_MCLKX_MAX 38400000 + +#define CS42L73_SPC(id) (CS42L73_XSPC + (id << 1)) +#define CS42L73_MMCC(id) (CS42L73_XSPMMCC + (id << 1)) +#define CS42L73_SPFS(id) ((id == CS42L73_ASP) ? CS42L73_ASPC : CS42L73_VXSPFS) + +#endif /* __CS42L73_H__ */ diff --git a/sound/soc/codecs/cs42xx8-i2c.c b/sound/soc/codecs/cs42xx8-i2c.c new file mode 100644 index 000000000..0214e3ab9 --- /dev/null +++ b/sound/soc/codecs/cs42xx8-i2c.c @@ -0,0 +1,63 @@ +/* + * Cirrus Logic CS42448/CS42888 Audio CODEC DAI I2C driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include + +#include "cs42xx8.h" + +static int cs42xx8_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret = cs42xx8_probe(&i2c->dev, + devm_regmap_init_i2c(i2c, &cs42xx8_regmap_config)); + if (ret) + return ret; + + pm_runtime_enable(&i2c->dev); + pm_request_idle(&i2c->dev); + + return 0; +} + +static int cs42xx8_i2c_remove(struct i2c_client *i2c) +{ + pm_runtime_disable(&i2c->dev); + + return 0; +} + +static struct i2c_device_id cs42xx8_i2c_id[] = { + {"cs42448", (kernel_ulong_t)&cs42448_data}, + {"cs42888", (kernel_ulong_t)&cs42888_data}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs42xx8_i2c_id); + +static struct i2c_driver cs42xx8_i2c_driver = { + .driver = { + .name = "cs42xx8", + .pm = &cs42xx8_pm, + .of_match_table = cs42xx8_of_match, + }, + .probe = cs42xx8_i2c_probe, + .remove = cs42xx8_i2c_remove, + .id_table = cs42xx8_i2c_id, +}; + +module_i2c_driver(cs42xx8_i2c_driver); + +MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec I2C Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c new file mode 100644 index 000000000..28a4ac36c --- /dev/null +++ b/sound/soc/codecs/cs42xx8.c @@ -0,0 +1,602 @@ +/* + * Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs42xx8.h" + +#define CS42XX8_NUM_SUPPLIES 4 +static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = { + "VA", + "VD", + "VLS", + "VLC", +}; + +#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +/* codec private data */ +struct cs42xx8_priv { + struct regulator_bulk_data supplies[CS42XX8_NUM_SUPPLIES]; + const struct cs42xx8_driver_data *drvdata; + struct regmap *regmap; + struct clk *clk; + + bool slave_mode; + unsigned long sysclk; + u32 tx_channels; +}; + +/* -127.5dB to 0dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +/* -64dB to 24dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0); + +static const char *const cs42xx8_adc_single[] = { "Differential", "Single-Ended" }; +static const char *const cs42xx8_szc[] = { "Immediate Change", "Zero Cross", + "Soft Ramp", "Soft Ramp on Zero Cross" }; + +static const struct soc_enum adc1_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 4, 2, cs42xx8_adc_single); +static const struct soc_enum adc2_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 3, 2, cs42xx8_adc_single); +static const struct soc_enum adc3_single_enum = + SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 2, 2, cs42xx8_adc_single); +static const struct soc_enum dac_szc_enum = + SOC_ENUM_SINGLE(CS42XX8_TXCTL, 5, 4, cs42xx8_szc); +static const struct soc_enum adc_szc_enum = + SOC_ENUM_SINGLE(CS42XX8_TXCTL, 0, 4, cs42xx8_szc); + +static const struct snd_kcontrol_new cs42xx8_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42XX8_VOLAOUT1, + CS42XX8_VOLAOUT2, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42XX8_VOLAOUT3, + CS42XX8_VOLAOUT4, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42XX8_VOLAOUT5, + CS42XX8_VOLAOUT6, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42XX8_VOLAOUT7, + CS42XX8_VOLAOUT8, 0, 0xff, 1, dac_tlv), + SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42XX8_VOLAIN1, + CS42XX8_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42XX8_VOLAIN3, + CS42XX8_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE("DAC1 Invert Switch", CS42XX8_DACINV, 0, 1, 1, 0), + SOC_DOUBLE("DAC2 Invert Switch", CS42XX8_DACINV, 2, 3, 1, 0), + SOC_DOUBLE("DAC3 Invert Switch", CS42XX8_DACINV, 4, 5, 1, 0), + SOC_DOUBLE("DAC4 Invert Switch", CS42XX8_DACINV, 6, 7, 1, 0), + SOC_DOUBLE("ADC1 Invert Switch", CS42XX8_ADCINV, 0, 1, 1, 0), + SOC_DOUBLE("ADC2 Invert Switch", CS42XX8_ADCINV, 2, 3, 1, 0), + SOC_SINGLE("ADC High-Pass Filter Switch", CS42XX8_ADCCTL, 7, 1, 1), + SOC_SINGLE("DAC De-emphasis Switch", CS42XX8_ADCCTL, 5, 1, 0), + SOC_ENUM("ADC1 Single Ended Mode Switch", adc1_single_enum), + SOC_ENUM("ADC2 Single Ended Mode Switch", adc2_single_enum), + SOC_SINGLE("DAC Single Volume Control Switch", CS42XX8_TXCTL, 7, 1, 0), + SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", dac_szc_enum), + SOC_SINGLE("DAC Auto Mute Switch", CS42XX8_TXCTL, 4, 1, 0), + SOC_SINGLE("Mute ADC Serial Port Switch", CS42XX8_TXCTL, 3, 1, 0), + SOC_SINGLE("ADC Single Volume Control Switch", CS42XX8_TXCTL, 2, 1, 0), + SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", adc_szc_enum), +}; + +static const struct snd_kcontrol_new cs42xx8_adc3_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("ADC3 Capture Volume", CS42XX8_VOLAIN5, + CS42XX8_VOLAIN6, 0, -0x80, 0x30, 7, 0, adc_tlv), + SOC_DOUBLE("ADC3 Invert Switch", CS42XX8_ADCINV, 4, 5, 1, 0), + SOC_ENUM("ADC3 Single Ended Mode Switch", adc3_single_enum), +}; + +static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC1", "Playback", CS42XX8_PWRCTL, 1, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", CS42XX8_PWRCTL, 2, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", CS42XX8_PWRCTL, 3, 1), + SND_SOC_DAPM_DAC("DAC4", "Playback", CS42XX8_PWRCTL, 4, 1), + + SND_SOC_DAPM_OUTPUT("AOUT1L"), + SND_SOC_DAPM_OUTPUT("AOUT1R"), + SND_SOC_DAPM_OUTPUT("AOUT2L"), + SND_SOC_DAPM_OUTPUT("AOUT2R"), + SND_SOC_DAPM_OUTPUT("AOUT3L"), + SND_SOC_DAPM_OUTPUT("AOUT3R"), + SND_SOC_DAPM_OUTPUT("AOUT4L"), + SND_SOC_DAPM_OUTPUT("AOUT4R"), + + SND_SOC_DAPM_ADC("ADC1", "Capture", CS42XX8_PWRCTL, 5, 1), + SND_SOC_DAPM_ADC("ADC2", "Capture", CS42XX8_PWRCTL, 6, 1), + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + + SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0), +}; + +static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC3", "Capture", CS42XX8_PWRCTL, 7, 1), + + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R"), +}; + +static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = { + /* Playback */ + { "AOUT1L", NULL, "DAC1" }, + { "AOUT1R", NULL, "DAC1" }, + { "DAC1", NULL, "PWR" }, + + { "AOUT2L", NULL, "DAC2" }, + { "AOUT2R", NULL, "DAC2" }, + { "DAC2", NULL, "PWR" }, + + { "AOUT3L", NULL, "DAC3" }, + { "AOUT3R", NULL, "DAC3" }, + { "DAC3", NULL, "PWR" }, + + { "AOUT4L", NULL, "DAC4" }, + { "AOUT4R", NULL, "DAC4" }, + { "DAC4", NULL, "PWR" }, + + /* Capture */ + { "ADC1", NULL, "AIN1L" }, + { "ADC1", NULL, "AIN1R" }, + { "ADC1", NULL, "PWR" }, + + { "ADC2", NULL, "AIN2L" }, + { "ADC2", NULL, "AIN2R" }, + { "ADC2", NULL, "PWR" }, +}; + +static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = { + /* Capture */ + { "ADC3", NULL, "AIN3L" }, + { "ADC3", NULL, "AIN3R" }, + { "ADC3", NULL, "PWR" }, +}; + +struct cs42xx8_ratios { + unsigned int ratio; + unsigned char speed; + unsigned char mclk; +}; + +static const struct cs42xx8_ratios cs42xx8_ratios[] = { + { 64, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_256(4) }, + { 96, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_384(4) }, + { 128, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_512(4) }, + { 192, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_768(4) }, + { 256, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_256(1) }, + { 384, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_384(1) }, + { 512, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_512(1) }, + { 768, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_768(1) }, + { 1024, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_1024(1) } +}; + +static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + + cs42xx8->sysclk = freq; + + return 0; +} + +static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + u32 val; + + /* Set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + val = CS42XX8_INTF_DAC_DIF_LEFTJ | CS42XX8_INTF_ADC_DIF_LEFTJ; + break; + case SND_SOC_DAIFMT_I2S: + val = CS42XX8_INTF_DAC_DIF_I2S | CS42XX8_INTF_ADC_DIF_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM; + break; + default: + dev_err(component->dev, "unsupported dai format\n"); + return -EINVAL; + } + + regmap_update_bits(cs42xx8->regmap, CS42XX8_INTF, + CS42XX8_INTF_DAC_DIF_MASK | + CS42XX8_INTF_ADC_DIF_MASK, val); + + /* Set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs42xx8->slave_mode = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs42xx8->slave_mode = false; + break; + default: + dev_err(component->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + return 0; +} + +static int cs42xx8_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 ratio = cs42xx8->sysclk / params_rate(params); + u32 i, fm, val, mask; + + if (tx) + cs42xx8->tx_channels = params_channels(params); + + for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) { + if (cs42xx8_ratios[i].ratio == ratio) + break; + } + + if (i == ARRAY_SIZE(cs42xx8_ratios)) { + dev_err(component->dev, "unsupported sysclk ratio\n"); + return -EINVAL; + } + + mask = CS42XX8_FUNCMOD_MFREQ_MASK; + val = cs42xx8_ratios[i].mclk; + + fm = cs42xx8->slave_mode ? CS42XX8_FM_AUTO : cs42xx8_ratios[i].speed; + + regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD, + CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask, + CS42XX8_FUNCMOD_xC_FM(tx, fm) | val); + + return 0; +} + +static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + u8 dac_unmute = cs42xx8->tx_channels ? + ~((0x1 << cs42xx8->tx_channels) - 1) : 0; + + regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, + mute ? CS42XX8_DACMUTE_ALL : dac_unmute); + + return 0; +} + +static const struct snd_soc_dai_ops cs42xx8_dai_ops = { + .set_fmt = cs42xx8_set_dai_fmt, + .set_sysclk = cs42xx8_set_dai_sysclk, + .hw_params = cs42xx8_hw_params, + .digital_mute = cs42xx8_digital_mute, +}; + +static struct snd_soc_dai_driver cs42xx8_dai = { + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42XX8_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = CS42XX8_FORMATS, + }, + .ops = &cs42xx8_dai_ops, +}; + +static const struct reg_default cs42xx8_reg[] = { + { 0x02, 0x00 }, /* Power Control */ + { 0x03, 0xF0 }, /* Functional Mode */ + { 0x04, 0x46 }, /* Interface Formats */ + { 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */ + { 0x06, 0x10 }, /* Transition Control */ + { 0x07, 0x00 }, /* DAC Channel Mute */ + { 0x08, 0x00 }, /* Volume Control AOUT1 */ + { 0x09, 0x00 }, /* Volume Control AOUT2 */ + { 0x0a, 0x00 }, /* Volume Control AOUT3 */ + { 0x0b, 0x00 }, /* Volume Control AOUT4 */ + { 0x0c, 0x00 }, /* Volume Control AOUT5 */ + { 0x0d, 0x00 }, /* Volume Control AOUT6 */ + { 0x0e, 0x00 }, /* Volume Control AOUT7 */ + { 0x0f, 0x00 }, /* Volume Control AOUT8 */ + { 0x10, 0x00 }, /* DAC Channel Invert */ + { 0x11, 0x00 }, /* Volume Control AIN1 */ + { 0x12, 0x00 }, /* Volume Control AIN2 */ + { 0x13, 0x00 }, /* Volume Control AIN3 */ + { 0x14, 0x00 }, /* Volume Control AIN4 */ + { 0x15, 0x00 }, /* Volume Control AIN5 */ + { 0x16, 0x00 }, /* Volume Control AIN6 */ + { 0x17, 0x00 }, /* ADC Channel Invert */ + { 0x18, 0x00 }, /* Status Control */ + { 0x1a, 0x00 }, /* Status Mask */ + { 0x1b, 0x00 }, /* MUTEC Pin Control */ +}; + +static bool cs42xx8_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42XX8_STATUS: + return true; + default: + return false; + } +} + +static bool cs42xx8_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42XX8_CHIPID: + case CS42XX8_STATUS: + return false; + default: + return true; + } +} + +const struct regmap_config cs42xx8_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42XX8_LASTREG, + .reg_defaults = cs42xx8_reg, + .num_reg_defaults = ARRAY_SIZE(cs42xx8_reg), + .volatile_reg = cs42xx8_volatile_register, + .writeable_reg = cs42xx8_writeable_register, + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(cs42xx8_regmap_config); + +static int cs42xx8_component_probe(struct snd_soc_component *component) +{ + struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + switch (cs42xx8->drvdata->num_adcs) { + case 3: + snd_soc_add_component_controls(component, cs42xx8_adc3_snd_controls, + ARRAY_SIZE(cs42xx8_adc3_snd_controls)); + snd_soc_dapm_new_controls(dapm, cs42xx8_adc3_dapm_widgets, + ARRAY_SIZE(cs42xx8_adc3_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, cs42xx8_adc3_dapm_routes, + ARRAY_SIZE(cs42xx8_adc3_dapm_routes)); + break; + default: + break; + } + + /* Mute all DAC channels */ + regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL); + + return 0; +} + +static const struct snd_soc_component_driver cs42xx8_driver = { + .probe = cs42xx8_component_probe, + .controls = cs42xx8_snd_controls, + .num_controls = ARRAY_SIZE(cs42xx8_snd_controls), + .dapm_widgets = cs42xx8_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42xx8_dapm_widgets), + .dapm_routes = cs42xx8_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs42xx8_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +const struct cs42xx8_driver_data cs42448_data = { + .name = "cs42448", + .num_adcs = 3, +}; +EXPORT_SYMBOL_GPL(cs42448_data); + +const struct cs42xx8_driver_data cs42888_data = { + .name = "cs42888", + .num_adcs = 2, +}; +EXPORT_SYMBOL_GPL(cs42888_data); + +const struct of_device_id cs42xx8_of_match[] = { + { .compatible = "cirrus,cs42448", .data = &cs42448_data, }, + { .compatible = "cirrus,cs42888", .data = &cs42888_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cs42xx8_of_match); +EXPORT_SYMBOL_GPL(cs42xx8_of_match); + +int cs42xx8_probe(struct device *dev, struct regmap *regmap) +{ + const struct of_device_id *of_id; + struct cs42xx8_priv *cs42xx8; + int ret, val, i; + + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(dev, "failed to allocate regmap: %d\n", ret); + return ret; + } + + cs42xx8 = devm_kzalloc(dev, sizeof(*cs42xx8), GFP_KERNEL); + if (cs42xx8 == NULL) + return -ENOMEM; + + cs42xx8->regmap = regmap; + dev_set_drvdata(dev, cs42xx8); + + of_id = of_match_device(cs42xx8_of_match, dev); + if (of_id) + cs42xx8->drvdata = of_id->data; + + if (!cs42xx8->drvdata) { + dev_err(dev, "failed to find driver data\n"); + return -EINVAL; + } + + cs42xx8->clk = devm_clk_get(dev, "mclk"); + if (IS_ERR(cs42xx8->clk)) { + dev_err(dev, "failed to get the clock: %ld\n", + PTR_ERR(cs42xx8->clk)); + return -EINVAL; + } + + cs42xx8->sysclk = clk_get_rate(cs42xx8->clk); + + for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++) + cs42xx8->supplies[i].supply = cs42xx8_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + /* Make sure hardware reset done */ + msleep(5); + + /* Validate the chip ID */ + ret = regmap_read(cs42xx8->regmap, CS42XX8_CHIPID, &val); + if (ret < 0) { + dev_err(dev, "failed to get device ID, ret = %d", ret); + goto err_enable; + } + + /* The top four bits of the chip ID should be 0000 */ + if (((val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4) != 0x00) { + dev_err(dev, "unmatched chip ID: %d\n", + (val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4); + ret = -EINVAL; + goto err_enable; + } + + dev_info(dev, "found device, revision %X\n", + val & CS42XX8_CHIPID_REV_ID_MASK); + + cs42xx8_dai.name = cs42xx8->drvdata->name; + + /* Each adc supports stereo input */ + cs42xx8_dai.capture.channels_max = cs42xx8->drvdata->num_adcs * 2; + + ret = devm_snd_soc_register_component(dev, &cs42xx8_driver, &cs42xx8_dai, 1); + if (ret) { + dev_err(dev, "failed to register component:%d\n", ret); + goto err_enable; + } + + regcache_cache_only(cs42xx8->regmap, true); + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + + return ret; +} +EXPORT_SYMBOL_GPL(cs42xx8_probe); + +#ifdef CONFIG_PM +static int cs42xx8_runtime_resume(struct device *dev) +{ + struct cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(cs42xx8->clk); + if (ret) { + dev_err(dev, "failed to enable mclk: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + goto err_clk; + } + + /* Make sure hardware reset done */ + msleep(5); + + regcache_cache_only(cs42xx8->regmap, false); + regcache_mark_dirty(cs42xx8->regmap); + + ret = regcache_sync(cs42xx8->regmap); + if (ret) { + dev_err(dev, "failed to sync regmap: %d\n", ret); + goto err_bulk; + } + + return 0; + +err_bulk: + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); +err_clk: + clk_disable_unprepare(cs42xx8->clk); + + return ret; +} + +static int cs42xx8_runtime_suspend(struct device *dev) +{ + struct cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev); + + regcache_cache_only(cs42xx8->regmap, true); + + regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies), + cs42xx8->supplies); + + clk_disable_unprepare(cs42xx8->clk); + + return 0; +} +#endif + +const struct dev_pm_ops cs42xx8_pm = { + SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL) +}; +EXPORT_SYMBOL_GPL(cs42xx8_pm); + +MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec Driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42xx8.h b/sound/soc/codecs/cs42xx8.h new file mode 100644 index 000000000..d36c61b6d --- /dev/null +++ b/sound/soc/codecs/cs42xx8.h @@ -0,0 +1,239 @@ +/* + * cs42xx8.h - Cirrus Logic CS42448/CS42888 Audio CODEC driver header file + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _CS42XX8_H +#define _CS42XX8_H + +struct cs42xx8_driver_data { + char name[32]; + int num_adcs; +}; + +extern const struct dev_pm_ops cs42xx8_pm; +extern const struct cs42xx8_driver_data cs42448_data; +extern const struct cs42xx8_driver_data cs42888_data; +extern const struct regmap_config cs42xx8_regmap_config; +extern const struct of_device_id cs42xx8_of_match[]; +int cs42xx8_probe(struct device *dev, struct regmap *regmap); + +/* CS42888 register map */ +#define CS42XX8_CHIPID 0x01 /* Chip ID */ +#define CS42XX8_PWRCTL 0x02 /* Power Control */ +#define CS42XX8_FUNCMOD 0x03 /* Functional Mode */ +#define CS42XX8_INTF 0x04 /* Interface Formats */ +#define CS42XX8_ADCCTL 0x05 /* ADC Control */ +#define CS42XX8_TXCTL 0x06 /* Transition Control */ +#define CS42XX8_DACMUTE 0x07 /* DAC Mute Control */ +#define CS42XX8_VOLAOUT1 0x08 /* Volume Control AOUT1 */ +#define CS42XX8_VOLAOUT2 0x09 /* Volume Control AOUT2 */ +#define CS42XX8_VOLAOUT3 0x0A /* Volume Control AOUT3 */ +#define CS42XX8_VOLAOUT4 0x0B /* Volume Control AOUT4 */ +#define CS42XX8_VOLAOUT5 0x0C /* Volume Control AOUT5 */ +#define CS42XX8_VOLAOUT6 0x0D /* Volume Control AOUT6 */ +#define CS42XX8_VOLAOUT7 0x0E /* Volume Control AOUT7 */ +#define CS42XX8_VOLAOUT8 0x0F /* Volume Control AOUT8 */ +#define CS42XX8_DACINV 0x10 /* DAC Channel Invert */ +#define CS42XX8_VOLAIN1 0x11 /* Volume Control AIN1 */ +#define CS42XX8_VOLAIN2 0x12 /* Volume Control AIN2 */ +#define CS42XX8_VOLAIN3 0x13 /* Volume Control AIN3 */ +#define CS42XX8_VOLAIN4 0x14 /* Volume Control AIN4 */ +#define CS42XX8_VOLAIN5 0x15 /* Volume Control AIN5 */ +#define CS42XX8_VOLAIN6 0x16 /* Volume Control AIN6 */ +#define CS42XX8_ADCINV 0x17 /* ADC Channel Invert */ +#define CS42XX8_STATUSCTL 0x18 /* Status Control */ +#define CS42XX8_STATUS 0x19 /* Status */ +#define CS42XX8_STATUSM 0x1A /* Status Mask */ +#define CS42XX8_MUTEC 0x1B /* MUTEC Pin Control */ + +#define CS42XX8_FIRSTREG CS42XX8_CHIPID +#define CS42XX8_LASTREG CS42XX8_MUTEC +#define CS42XX8_NUMREGS (CS42XX8_LASTREG - CS42XX8_FIRSTREG + 1) +#define CS42XX8_I2C_INCR 0x80 + +/* Chip I.D. and Revision Register (Address 01h) */ +#define CS42XX8_CHIPID_CHIP_ID_MASK 0xF0 +#define CS42XX8_CHIPID_REV_ID_MASK 0x0F + +/* Power Control (Address 02h) */ +#define CS42XX8_PWRCTL_PDN_ADC3_SHIFT 7 +#define CS42XX8_PWRCTL_PDN_ADC3_MASK (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC3 (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC2_SHIFT 6 +#define CS42XX8_PWRCTL_PDN_ADC2_MASK (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC2 (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC1_SHIFT 5 +#define CS42XX8_PWRCTL_PDN_ADC1_MASK (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_ADC1 (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC4_SHIFT 4 +#define CS42XX8_PWRCTL_PDN_DAC4_MASK (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC4 (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC3_SHIFT 3 +#define CS42XX8_PWRCTL_PDN_DAC3_MASK (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC3 (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC2_SHIFT 2 +#define CS42XX8_PWRCTL_PDN_DAC2_MASK (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC2 (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC1_SHIFT 1 +#define CS42XX8_PWRCTL_PDN_DAC1_MASK (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_DAC1 (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT) +#define CS42XX8_PWRCTL_PDN_SHIFT 0 +#define CS42XX8_PWRCTL_PDN_MASK (1 << CS42XX8_PWRCTL_PDN_SHIFT) +#define CS42XX8_PWRCTL_PDN (1 << CS42XX8_PWRCTL_PDN_SHIFT) + +/* Functional Mode (Address 03h) */ +#define CS42XX8_FUNCMOD_DAC_FM_SHIFT 6 +#define CS42XX8_FUNCMOD_DAC_FM_WIDTH 2 +#define CS42XX8_FUNCMOD_DAC_FM_MASK (((1 << CS42XX8_FUNCMOD_DAC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_DAC_FM_SHIFT) +#define CS42XX8_FUNCMOD_DAC_FM(v) ((v) << CS42XX8_FUNCMOD_DAC_FM_SHIFT) +#define CS42XX8_FUNCMOD_ADC_FM_SHIFT 4 +#define CS42XX8_FUNCMOD_ADC_FM_WIDTH 2 +#define CS42XX8_FUNCMOD_ADC_FM_MASK (((1 << CS42XX8_FUNCMOD_ADC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_ADC_FM_SHIFT) +#define CS42XX8_FUNCMOD_ADC_FM(v) ((v) << CS42XX8_FUNCMOD_ADC_FM_SHIFT) +#define CS42XX8_FUNCMOD_xC_FM_MASK(x) ((x) ? CS42XX8_FUNCMOD_DAC_FM_MASK : CS42XX8_FUNCMOD_ADC_FM_MASK) +#define CS42XX8_FUNCMOD_xC_FM(x, v) ((x) ? CS42XX8_FUNCMOD_DAC_FM(v) : CS42XX8_FUNCMOD_ADC_FM(v)) +#define CS42XX8_FUNCMOD_MFREQ_SHIFT 1 +#define CS42XX8_FUNCMOD_MFREQ_WIDTH 3 +#define CS42XX8_FUNCMOD_MFREQ_MASK (((1 << CS42XX8_FUNCMOD_MFREQ_WIDTH) - 1) << CS42XX8_FUNCMOD_MFREQ_SHIFT) +#define CS42XX8_FUNCMOD_MFREQ_256(s) ((0 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_384(s) ((1 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_512(s) ((2 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_768(s) ((3 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) +#define CS42XX8_FUNCMOD_MFREQ_1024(s) ((4 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1)) + +#define CS42XX8_FM_SINGLE 0 +#define CS42XX8_FM_DOUBLE 1 +#define CS42XX8_FM_QUAD 2 +#define CS42XX8_FM_AUTO 3 + +/* Interface Formats (Address 04h) */ +#define CS42XX8_INTF_FREEZE_SHIFT 7 +#define CS42XX8_INTF_FREEZE_MASK (1 << CS42XX8_INTF_FREEZE_SHIFT) +#define CS42XX8_INTF_FREEZE (1 << CS42XX8_INTF_FREEZE_SHIFT) +#define CS42XX8_INTF_AUX_DIF_SHIFT 6 +#define CS42XX8_INTF_AUX_DIF_MASK (1 << CS42XX8_INTF_AUX_DIF_SHIFT) +#define CS42XX8_INTF_AUX_DIF (1 << CS42XX8_INTF_AUX_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_SHIFT 3 +#define CS42XX8_INTF_DAC_DIF_WIDTH 3 +#define CS42XX8_INTF_DAC_DIF_MASK (((1 << CS42XX8_INTF_DAC_DIF_WIDTH) - 1) << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_LEFTJ (0 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_I2S (1 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_RIGHTJ (2 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_ONELINE_20 (4 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (5 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_DAC_DIF_TDM (6 << CS42XX8_INTF_DAC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_SHIFT 0 +#define CS42XX8_INTF_ADC_DIF_WIDTH 3 +#define CS42XX8_INTF_ADC_DIF_MASK (((1 << CS42XX8_INTF_ADC_DIF_WIDTH) - 1) << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_LEFTJ (0 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_I2S (1 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_RIGHTJ (2 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_ONELINE_20 (4 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (5 << CS42XX8_INTF_ADC_DIF_SHIFT) +#define CS42XX8_INTF_ADC_DIF_TDM (6 << CS42XX8_INTF_ADC_DIF_SHIFT) + +/* ADC Control & DAC De-Emphasis (Address 05h) */ +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT 7 +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_MASK (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT) +#define CS42XX8_ADCCTL_ADC_HPF_FREEZE (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT) +#define CS42XX8_ADCCTL_DAC_DEM_SHIFT 5 +#define CS42XX8_ADCCTL_DAC_DEM_MASK (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT) +#define CS42XX8_ADCCTL_DAC_DEM (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT) +#define CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT 4 +#define CS42XX8_ADCCTL_ADC1_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC1_SINGLE (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT 3 +#define CS42XX8_ADCCTL_ADC2_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC2_SINGLE (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT 2 +#define CS42XX8_ADCCTL_ADC3_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_ADC3_SINGLE (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT) +#define CS42XX8_ADCCTL_AIN5_MUX_SHIFT 1 +#define CS42XX8_ADCCTL_AIN5_MUX_MASK (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN5_MUX (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN6_MUX_SHIFT 0 +#define CS42XX8_ADCCTL_AIN6_MUX_MASK (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT) +#define CS42XX8_ADCCTL_AIN6_MUX (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT) + +/* Transition Control (Address 06h) */ +#define CS42XX8_TXCTL_DAC_SNGVOL_SHIFT 7 +#define CS42XX8_TXCTL_DAC_SNGVOL_MASK (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_DAC_SNGVOL (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SHIFT 5 +#define CS42XX8_TXCTL_DAC_SZC_WIDTH 2 +#define CS42XX8_TXCTL_DAC_SZC_MASK (((1 << CS42XX8_TXCTL_DAC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_IC (0 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_ZC (1 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SR (2 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_DAC_SZC_SRZC (3 << CS42XX8_TXCTL_DAC_SZC_SHIFT) +#define CS42XX8_TXCTL_AMUTE_SHIFT 4 +#define CS42XX8_TXCTL_AMUTE_MASK (1 << CS42XX8_TXCTL_AMUTE_SHIFT) +#define CS42XX8_TXCTL_AMUTE (1 << CS42XX8_TXCTL_AMUTE_SHIFT) +#define CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT 3 +#define CS42XX8_TXCTL_MUTE_ADC_SP_MASK (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT) +#define CS42XX8_TXCTL_MUTE_ADC_SP (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT) +#define CS42XX8_TXCTL_ADC_SNGVOL_SHIFT 2 +#define CS42XX8_TXCTL_ADC_SNGVOL_MASK (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_ADC_SNGVOL (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SHIFT 0 +#define CS42XX8_TXCTL_ADC_SZC_MASK (((1 << CS42XX8_TXCTL_ADC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_IC (0 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_ZC (1 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SR (2 << CS42XX8_TXCTL_ADC_SZC_SHIFT) +#define CS42XX8_TXCTL_ADC_SZC_SRZC (3 << CS42XX8_TXCTL_ADC_SZC_SHIFT) + +/* DAC Channel Mute (Address 07h) */ +#define CS42XX8_DACMUTE_AOUT(n) (0x1 << n) +#define CS42XX8_DACMUTE_ALL 0xff + +/* Status Control (Address 18h)*/ +#define CS42XX8_STATUSCTL_INI_SHIFT 2 +#define CS42XX8_STATUSCTL_INI_WIDTH 2 +#define CS42XX8_STATUSCTL_INI_MASK (((1 << CS42XX8_STATUSCTL_INI_WIDTH) - 1) << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_ACTIVE_HIGH (0 << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_ACTIVE_LOW (1 << CS42XX8_STATUSCTL_INI_SHIFT) +#define CS42XX8_STATUSCTL_INT_OPEN_DRAIN (2 << CS42XX8_STATUSCTL_INI_SHIFT) + +/* Status (Address 19h)*/ +#define CS42XX8_STATUS_DAC_CLK_ERR_SHIFT 4 +#define CS42XX8_STATUS_DAC_CLK_ERR_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_SHIFT) +#define CS42XX8_STATUS_ADC_CLK_ERR_SHIFT 3 +#define CS42XX8_STATUS_ADC_CLK_ERR_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_SHIFT) +#define CS42XX8_STATUS_ADC3_OVFL_SHIFT 2 +#define CS42XX8_STATUS_ADC3_OVFL_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_SHIFT) +#define CS42XX8_STATUS_ADC2_OVFL_SHIFT 1 +#define CS42XX8_STATUS_ADC2_OVFL_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_SHIFT) +#define CS42XX8_STATUS_ADC1_OVFL_SHIFT 0 +#define CS42XX8_STATUS_ADC1_OVFL_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_SHIFT) + +/* Status Mask (Address 1Ah) */ +#define CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT 4 +#define CS42XX8_STATUS_DAC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT) +#define CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT 3 +#define CS42XX8_STATUS_ADC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT) +#define CS42XX8_STATUS_ADC3_OVFL_M_SHIFT 2 +#define CS42XX8_STATUS_ADC3_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_M_SHIFT) +#define CS42XX8_STATUS_ADC2_OVFL_M_SHIFT 1 +#define CS42XX8_STATUS_ADC2_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_M_SHIFT) +#define CS42XX8_STATUS_ADC1_OVFL_M_SHIFT 0 +#define CS42XX8_STATUS_ADC1_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_M_SHIFT) + +/* MUTEC Pin Control (Address 1Bh) */ +#define CS42XX8_MUTEC_MCPOLARITY_SHIFT 1 +#define CS42XX8_MUTEC_MCPOLARITY_MASK (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_LOW (0 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_HIGH (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT) +#define CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT 0 +#define CS42XX8_MUTEC_MUTEC_ACTIVE_MASK (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT) +#define CS42XX8_MUTEC_MUTEC_ACTIVE (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT) +#endif /* _CS42XX8_H */ diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c new file mode 100644 index 000000000..cf29dec28 --- /dev/null +++ b/sound/soc/codecs/cs43130.c @@ -0,0 +1,2707 @@ +/* + * cs43130.c -- CS43130 ALSA Soc Audio driver + * + * Copyright 2017 Cirrus Logic, Inc. + * + * Authors: Li Xu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs43130.h" + +static const struct reg_default cs43130_reg_defaults[] = { + {CS43130_SYS_CLK_CTL_1, 0x06}, + {CS43130_SP_SRATE, 0x01}, + {CS43130_SP_BITSIZE, 0x05}, + {CS43130_PAD_INT_CFG, 0x03}, + {CS43130_PWDN_CTL, 0xFE}, + {CS43130_CRYSTAL_SET, 0x04}, + {CS43130_PLL_SET_1, 0x00}, + {CS43130_PLL_SET_2, 0x00}, + {CS43130_PLL_SET_3, 0x00}, + {CS43130_PLL_SET_4, 0x00}, + {CS43130_PLL_SET_5, 0x40}, + {CS43130_PLL_SET_6, 0x10}, + {CS43130_PLL_SET_7, 0x80}, + {CS43130_PLL_SET_8, 0x03}, + {CS43130_PLL_SET_9, 0x02}, + {CS43130_PLL_SET_10, 0x02}, + {CS43130_CLKOUT_CTL, 0x00}, + {CS43130_ASP_NUM_1, 0x01}, + {CS43130_ASP_NUM_2, 0x00}, + {CS43130_ASP_DEN_1, 0x08}, + {CS43130_ASP_DEN_2, 0x00}, + {CS43130_ASP_LRCK_HI_TIME_1, 0x1F}, + {CS43130_ASP_LRCK_HI_TIME_2, 0x00}, + {CS43130_ASP_LRCK_PERIOD_1, 0x3F}, + {CS43130_ASP_LRCK_PERIOD_2, 0x00}, + {CS43130_ASP_CLOCK_CONF, 0x0C}, + {CS43130_ASP_FRAME_CONF, 0x0A}, + {CS43130_XSP_NUM_1, 0x01}, + {CS43130_XSP_NUM_2, 0x00}, + {CS43130_XSP_DEN_1, 0x02}, + {CS43130_XSP_DEN_2, 0x00}, + {CS43130_XSP_LRCK_HI_TIME_1, 0x1F}, + {CS43130_XSP_LRCK_HI_TIME_2, 0x00}, + {CS43130_XSP_LRCK_PERIOD_1, 0x3F}, + {CS43130_XSP_LRCK_PERIOD_2, 0x00}, + {CS43130_XSP_CLOCK_CONF, 0x0C}, + {CS43130_XSP_FRAME_CONF, 0x0A}, + {CS43130_ASP_CH_1_LOC, 0x00}, + {CS43130_ASP_CH_2_LOC, 0x00}, + {CS43130_ASP_CH_1_SZ_EN, 0x06}, + {CS43130_ASP_CH_2_SZ_EN, 0x0E}, + {CS43130_XSP_CH_1_LOC, 0x00}, + {CS43130_XSP_CH_2_LOC, 0x00}, + {CS43130_XSP_CH_1_SZ_EN, 0x06}, + {CS43130_XSP_CH_2_SZ_EN, 0x0E}, + {CS43130_DSD_VOL_B, 0x78}, + {CS43130_DSD_VOL_A, 0x78}, + {CS43130_DSD_PATH_CTL_1, 0xA8}, + {CS43130_DSD_INT_CFG, 0x00}, + {CS43130_DSD_PATH_CTL_2, 0x02}, + {CS43130_DSD_PCM_MIX_CTL, 0x00}, + {CS43130_DSD_PATH_CTL_3, 0x40}, + {CS43130_HP_OUT_CTL_1, 0x30}, + {CS43130_PCM_FILT_OPT, 0x02}, + {CS43130_PCM_VOL_B, 0x78}, + {CS43130_PCM_VOL_A, 0x78}, + {CS43130_PCM_PATH_CTL_1, 0xA8}, + {CS43130_PCM_PATH_CTL_2, 0x00}, + {CS43130_CLASS_H_CTL, 0x1E}, + {CS43130_HP_DETECT, 0x04}, + {CS43130_HP_LOAD_1, 0x00}, + {CS43130_HP_MEAS_LOAD_1, 0x00}, + {CS43130_HP_MEAS_LOAD_2, 0x00}, + {CS43130_INT_MASK_1, 0xFF}, + {CS43130_INT_MASK_2, 0xFF}, + {CS43130_INT_MASK_3, 0xFF}, + {CS43130_INT_MASK_4, 0xFF}, + {CS43130_INT_MASK_5, 0xFF}, +}; + +static bool cs43130_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + case CS43130_HP_DC_STAT_1 ... CS43130_HP_DC_STAT_2: + case CS43130_HP_AC_STAT_1 ... CS43130_HP_AC_STAT_2: + return true; + default: + return false; + } +} + +static bool cs43130_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_DEVID_AB ... CS43130_SYS_CLK_CTL_1: + case CS43130_SP_SRATE ... CS43130_PAD_INT_CFG: + case CS43130_PWDN_CTL: + case CS43130_CRYSTAL_SET: + case CS43130_PLL_SET_1 ... CS43130_PLL_SET_5: + case CS43130_PLL_SET_6: + case CS43130_PLL_SET_7: + case CS43130_PLL_SET_8: + case CS43130_PLL_SET_9: + case CS43130_PLL_SET_10: + case CS43130_CLKOUT_CTL: + case CS43130_ASP_NUM_1 ... CS43130_ASP_FRAME_CONF: + case CS43130_XSP_NUM_1 ... CS43130_XSP_FRAME_CONF: + case CS43130_ASP_CH_1_LOC: + case CS43130_ASP_CH_2_LOC: + case CS43130_ASP_CH_1_SZ_EN: + case CS43130_ASP_CH_2_SZ_EN: + case CS43130_XSP_CH_1_LOC: + case CS43130_XSP_CH_2_LOC: + case CS43130_XSP_CH_1_SZ_EN: + case CS43130_XSP_CH_2_SZ_EN: + case CS43130_DSD_VOL_B ... CS43130_DSD_PATH_CTL_3: + case CS43130_HP_OUT_CTL_1: + case CS43130_PCM_FILT_OPT ... CS43130_PCM_PATH_CTL_2: + case CS43130_CLASS_H_CTL: + case CS43130_HP_DETECT: + case CS43130_HP_STATUS: + case CS43130_HP_LOAD_1: + case CS43130_HP_MEAS_LOAD_1: + case CS43130_HP_MEAS_LOAD_2: + case CS43130_HP_DC_STAT_1: + case CS43130_HP_DC_STAT_2: + case CS43130_HP_AC_STAT_1: + case CS43130_HP_AC_STAT_2: + case CS43130_HP_LOAD_STAT: + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + case CS43130_INT_MASK_1 ... CS43130_INT_MASK_5: + return true; + default: + return false; + } +} + +static bool cs43130_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5: + return true; + default: + return false; + } +} + +struct cs43130_pll_params { + unsigned int pll_in; + u8 sclk_prediv; + u8 pll_div_int; + u32 pll_div_frac; + u8 pll_mode; + u8 pll_divout; + unsigned int pll_out; + u8 pll_cal_ratio; +}; + +static const struct cs43130_pll_params pll_ratio_table[] = { + {9600000, 0x02, 0x49, 0x800000, 0x00, 0x08, 22579200, 151}, + {9600000, 0x02, 0x50, 0x000000, 0x00, 0x08, 24576000, 164}, + + {11289600, 0x02, 0X40, 0, 0x01, 0x08, 22579200, 128}, + {11289600, 0x02, 0x44, 0x06F700, 0x0, 0x08, 24576000, 139}, + + {12000000, 0x02, 0x49, 0x800000, 0x00, 0x0A, 22579200, 120}, + {12000000, 0x02, 0x40, 0x000000, 0x00, 0x08, 24576000, 131}, + + {12288000, 0x02, 0x49, 0x800000, 0x01, 0x0A, 22579200, 118}, + {12288000, 0x02, 0x40, 0x000000, 0x01, 0x08, 24576000, 128}, + + {13000000, 0x02, 0x45, 0x797680, 0x01, 0x0A, 22579200, 111}, + {13000000, 0x02, 0x3C, 0x7EA940, 0x01, 0x08, 24576000, 121}, + + {19200000, 0x03, 0x49, 0x800000, 0x00, 0x08, 22579200, 151}, + {19200000, 0x03, 0x50, 0x000000, 0x00, 0x08, 24576000, 164}, + + {22579200, 0, 0, 0, 0, 0, 22579200, 0}, + {22579200, 0x03, 0x44, 0x06F700, 0x00, 0x08, 24576000, 139}, + + {24000000, 0x03, 0x49, 0x800000, 0x00, 0x0A, 22579200, 120}, + {24000000, 0x03, 0x40, 0x000000, 0x00, 0x08, 24576000, 131}, + + {24576000, 0x03, 0x49, 0x800000, 0x01, 0x0A, 22579200, 118}, + {24576000, 0, 0, 0, 0, 0, 24576000, 0}, + + {26000000, 0x03, 0x45, 0x797680, 0x01, 0x0A, 22579200, 111}, + {26000000, 0x03, 0x3C, 0x7EA940, 0x01, 0x08, 24576000, 121}, +}; + +static const struct cs43130_pll_params *cs43130_get_pll_table( + unsigned int freq_in, unsigned int freq_out) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) { + if (pll_ratio_table[i].pll_in == freq_in && + pll_ratio_table[i].pll_out == freq_out) + return &pll_ratio_table[i]; + } + + return NULL; +} + +static int cs43130_pll_config(struct snd_soc_component *component) +{ + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + const struct cs43130_pll_params *pll_entry; + + dev_dbg(component->dev, "cs43130->mclk = %u, cs43130->mclk_int = %u\n", + cs43130->mclk, cs43130->mclk_int); + + pll_entry = cs43130_get_pll_table(cs43130->mclk, cs43130->mclk_int); + if (!pll_entry) + return -EINVAL; + + if (pll_entry->pll_cal_ratio == 0) { + regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_1, + CS43130_PLL_START_MASK, 0); + + cs43130->pll_bypass = true; + return 0; + } + + cs43130->pll_bypass = false; + + regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_2, + CS43130_PLL_DIV_DATA_MASK, + pll_entry->pll_div_frac >> + CS43130_PLL_DIV_FRAC_0_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_3, + CS43130_PLL_DIV_DATA_MASK, + pll_entry->pll_div_frac >> + CS43130_PLL_DIV_FRAC_1_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_4, + CS43130_PLL_DIV_DATA_MASK, + pll_entry->pll_div_frac >> + CS43130_PLL_DIV_FRAC_2_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_PLL_SET_5, + pll_entry->pll_div_int); + regmap_write(cs43130->regmap, CS43130_PLL_SET_6, pll_entry->pll_divout); + regmap_write(cs43130->regmap, CS43130_PLL_SET_7, + pll_entry->pll_cal_ratio); + regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_8, + CS43130_PLL_MODE_MASK, + pll_entry->pll_mode << CS43130_PLL_MODE_SHIFT); + regmap_write(cs43130->regmap, CS43130_PLL_SET_9, + pll_entry->sclk_prediv); + regmap_update_bits(cs43130->regmap, CS43130_PLL_SET_1, + CS43130_PLL_START_MASK, 1); + + return 0; +} + +static int cs43130_set_pll(struct snd_soc_component *component, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + int ret = 0; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + switch (freq_in) { + case 9600000: + case 11289600: + case 12000000: + case 12288000: + case 13000000: + case 19200000: + case 22579200: + case 24000000: + case 24576000: + case 26000000: + cs43130->mclk = freq_in; + break; + default: + dev_err(component->dev, + "unsupported pll input reference clock:%d\n", freq_in); + return -EINVAL; + } + + switch (freq_out) { + case 22579200: + cs43130->mclk_int = freq_out; + break; + case 24576000: + cs43130->mclk_int = freq_out; + break; + default: + dev_err(component->dev, + "unsupported pll output ref clock: %u\n", freq_out); + return -EINVAL; + } + + ret = cs43130_pll_config(component); + dev_dbg(component->dev, "cs43130->pll_bypass = %d", cs43130->pll_bypass); + return ret; +} + +static int cs43130_change_clksrc(struct snd_soc_component *component, + enum cs43130_mclk_src_sel src) +{ + int ret; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + int mclk_int_decoded; + + if (src == cs43130->mclk_int_src) { + /* clk source has not changed */ + return 0; + } + + switch (cs43130->mclk_int) { + case CS43130_MCLK_22M: + mclk_int_decoded = CS43130_MCLK_22P5; + break; + case CS43130_MCLK_24M: + mclk_int_decoded = CS43130_MCLK_24P5; + break; + default: + dev_err(component->dev, "Invalid MCLK INT freq: %u\n", cs43130->mclk_int); + return -EINVAL; + } + + switch (src) { + case CS43130_MCLK_SRC_EXT: + cs43130->pll_bypass = true; + cs43130->mclk_int_src = CS43130_MCLK_SRC_EXT; + if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) { + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, + 1 << CS43130_PDN_XTAL_SHIFT); + } else { + reinit_completion(&cs43130->xtal_rdy); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, 0); + ret = wait_for_completion_timeout(&cs43130->xtal_rdy, + msecs_to_jiffies(100)); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, + 1 << CS43130_XTAL_RDY_INT_SHIFT); + if (ret == 0) { + dev_err(component->dev, "Timeout waiting for XTAL_READY interrupt\n"); + return -ETIMEDOUT; + } + } + + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_SRC_SEL_MASK, + src << CS43130_MCLK_SRC_SEL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_INT_MASK, + mclk_int_decoded << CS43130_MCLK_INT_SHIFT); + usleep_range(150, 200); + + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_PLL_MASK, + 1 << CS43130_PDN_PLL_SHIFT); + break; + case CS43130_MCLK_SRC_PLL: + cs43130->pll_bypass = false; + cs43130->mclk_int_src = CS43130_MCLK_SRC_PLL; + if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) { + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, + 1 << CS43130_PDN_XTAL_SHIFT); + } else { + reinit_completion(&cs43130->xtal_rdy); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, 0); + ret = wait_for_completion_timeout(&cs43130->xtal_rdy, + msecs_to_jiffies(100)); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_RDY_INT_MASK, + 1 << CS43130_XTAL_RDY_INT_SHIFT); + if (ret == 0) { + dev_err(component->dev, "Timeout waiting for XTAL_READY interrupt\n"); + return -ETIMEDOUT; + } + } + + reinit_completion(&cs43130->pll_rdy); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_PLL_RDY_INT_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_PLL_MASK, 0); + ret = wait_for_completion_timeout(&cs43130->pll_rdy, + msecs_to_jiffies(100)); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_PLL_RDY_INT_MASK, + 1 << CS43130_PLL_RDY_INT_SHIFT); + if (ret == 0) { + dev_err(component->dev, "Timeout waiting for PLL_READY interrupt\n"); + return -ETIMEDOUT; + } + + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_SRC_SEL_MASK, + src << CS43130_MCLK_SRC_SEL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_INT_MASK, + mclk_int_decoded << CS43130_MCLK_INT_SHIFT); + usleep_range(150, 200); + break; + case CS43130_MCLK_SRC_RCO: + cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO; + + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_SRC_SEL_MASK, + src << CS43130_MCLK_SRC_SEL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1, + CS43130_MCLK_INT_MASK, + CS43130_MCLK_22P5 << CS43130_MCLK_INT_SHIFT); + usleep_range(150, 200); + + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_XTAL_MASK, + 1 << CS43130_PDN_XTAL_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL, + CS43130_PDN_PLL_MASK, + 1 << CS43130_PDN_PLL_SHIFT); + break; + default: + dev_err(component->dev, "Invalid MCLK source value\n"); + return -EINVAL; + } + + return 0; +} + +static const struct cs43130_bitwidth_map cs43130_bitwidth_table[] = { + {8, CS43130_SP_BIT_SIZE_8, CS43130_CH_BIT_SIZE_8}, + {16, CS43130_SP_BIT_SIZE_16, CS43130_CH_BIT_SIZE_16}, + {24, CS43130_SP_BIT_SIZE_24, CS43130_CH_BIT_SIZE_24}, + {32, CS43130_SP_BIT_SIZE_32, CS43130_CH_BIT_SIZE_32}, +}; + +static const struct cs43130_bitwidth_map *cs43130_get_bitwidth_table( + unsigned int bitwidth) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs43130_bitwidth_table); i++) { + if (cs43130_bitwidth_table[i].bitwidth == bitwidth) + return &cs43130_bitwidth_table[i]; + } + + return NULL; +} + +static int cs43130_set_bitwidth(int dai_id, unsigned int bitwidth_dai, + struct regmap *regmap) +{ + const struct cs43130_bitwidth_map *bw_map; + + bw_map = cs43130_get_bitwidth_table(bitwidth_dai); + if (!bw_map) + return -EINVAL; + + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_update_bits(regmap, CS43130_ASP_CH_1_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_ASP_CH_2_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_SP_BITSIZE, + CS43130_ASP_BITSIZE_MASK, bw_map->sp_bit); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(regmap, CS43130_XSP_CH_1_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_XSP_CH_2_SZ_EN, + CS43130_CH_BITSIZE_MASK, bw_map->ch_bit); + regmap_update_bits(regmap, CS43130_SP_BITSIZE, + CS43130_XSP_BITSIZE_MASK, bw_map->sp_bit << + CS43130_XSP_BITSIZE_SHIFT); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct cs43130_rate_map cs43130_rate_table[] = { + {32000, CS43130_ASP_SPRATE_32K}, + {44100, CS43130_ASP_SPRATE_44_1K}, + {48000, CS43130_ASP_SPRATE_48K}, + {88200, CS43130_ASP_SPRATE_88_2K}, + {96000, CS43130_ASP_SPRATE_96K}, + {176400, CS43130_ASP_SPRATE_176_4K}, + {192000, CS43130_ASP_SPRATE_192K}, + {352800, CS43130_ASP_SPRATE_352_8K}, + {384000, CS43130_ASP_SPRATE_384K}, +}; + +static const struct cs43130_rate_map *cs43130_get_rate_table(int fs) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs43130_rate_table); i++) { + if (cs43130_rate_table[i].fs == fs) + return &cs43130_rate_table[i]; + } + + return NULL; +} + +static const struct cs43130_clk_gen *cs43130_get_clk_gen(int mclk_int, int fs, + const struct cs43130_clk_gen *clk_gen_table, int len_clk_gen_table) +{ + int i; + + for (i = 0; i < len_clk_gen_table; i++) { + if (clk_gen_table[i].mclk_int == mclk_int && + clk_gen_table[i].fs == fs) + return &clk_gen_table[i]; + } + + return NULL; +} + +static int cs43130_set_sp_fmt(int dai_id, unsigned int bitwidth_sclk, + struct snd_pcm_hw_params *params, + struct cs43130_private *cs43130) +{ + u16 frm_size; + u16 hi_size; + u8 frm_delay; + u8 frm_phase; + u8 frm_data; + u8 sclk_edge; + u8 lrck_edge; + u8 clk_data; + u8 loc_ch1; + u8 loc_ch2; + u8 dai_mode_val; + const struct cs43130_clk_gen *clk_gen; + + switch (cs43130->dais[dai_id].dai_format) { + case SND_SOC_DAIFMT_I2S: + hi_size = bitwidth_sclk; + frm_delay = 2; + frm_phase = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + hi_size = bitwidth_sclk; + frm_delay = 2; + frm_phase = 1; + break; + case SND_SOC_DAIFMT_DSP_A: + hi_size = 1; + frm_delay = 2; + frm_phase = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + hi_size = 1; + frm_delay = 0; + frm_phase = 1; + break; + default: + return -EINVAL; + } + + switch (cs43130->dais[dai_id].dai_mode) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_mode_val = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_mode_val = 1; + break; + default: + return -EINVAL; + } + + frm_size = bitwidth_sclk * params_channels(params); + sclk_edge = 1; + lrck_edge = 0; + loc_ch1 = 0; + loc_ch2 = bitwidth_sclk * (params_channels(params) - 1); + + frm_data = frm_delay & CS43130_SP_FSD_MASK; + frm_data |= (frm_phase << CS43130_SP_STP_SHIFT) & CS43130_SP_STP_MASK; + + clk_data = lrck_edge & CS43130_SP_LCPOL_IN_MASK; + clk_data |= (lrck_edge << CS43130_SP_LCPOL_OUT_SHIFT) & + CS43130_SP_LCPOL_OUT_MASK; + clk_data |= (sclk_edge << CS43130_SP_SCPOL_IN_SHIFT) & + CS43130_SP_SCPOL_IN_MASK; + clk_data |= (sclk_edge << CS43130_SP_SCPOL_OUT_SHIFT) & + CS43130_SP_SCPOL_OUT_MASK; + clk_data |= (dai_mode_val << CS43130_SP_MODE_SHIFT) & + CS43130_SP_MODE_MASK; + + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_1, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_2, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_MSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_1, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_2, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_FRAME_CONF, frm_data); + regmap_write(cs43130->regmap, CS43130_ASP_CH_1_LOC, loc_ch1); + regmap_write(cs43130->regmap, CS43130_ASP_CH_2_LOC, loc_ch2); + regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_1_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_2_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_CLOCK_CONF, clk_data); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_1, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_2, + CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >> + CS43130_SP_LCPR_MSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_1, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_LSB_DATA_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_2, + CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >> + CS43130_SP_LCHI_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_FRAME_CONF, frm_data); + regmap_write(cs43130->regmap, CS43130_XSP_CH_1_LOC, loc_ch1); + regmap_write(cs43130->regmap, CS43130_XSP_CH_2_LOC, loc_ch2); + regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_1_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_2_SZ_EN, + CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_CLOCK_CONF, clk_data); + break; + default: + return -EINVAL; + } + + switch (frm_size) { + case 16: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_16_clk_gen, + ARRAY_SIZE(cs43130_16_clk_gen)); + break; + case 32: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_32_clk_gen, + ARRAY_SIZE(cs43130_32_clk_gen)); + break; + case 48: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_48_clk_gen, + ARRAY_SIZE(cs43130_48_clk_gen)); + break; + case 64: + clk_gen = cs43130_get_clk_gen(cs43130->mclk_int, + params_rate(params), + cs43130_64_clk_gen, + ARRAY_SIZE(cs43130_64_clk_gen)); + break; + default: + return -EINVAL; + } + + if (!clk_gen) + return -EINVAL; + + switch (dai_id) { + case CS43130_ASP_PCM_DAI: + case CS43130_ASP_DOP_DAI: + regmap_write(cs43130->regmap, CS43130_ASP_DEN_1, + (clk_gen->den & CS43130_SP_M_LSB_DATA_MASK) >> + CS43130_SP_M_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_DEN_2, + (clk_gen->den & CS43130_SP_M_MSB_DATA_MASK) >> + CS43130_SP_M_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_NUM_1, + (clk_gen->num & CS43130_SP_N_LSB_DATA_MASK) >> + CS43130_SP_N_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_ASP_NUM_2, + (clk_gen->num & CS43130_SP_N_MSB_DATA_MASK) >> + CS43130_SP_N_MSB_DATA_SHIFT); + break; + case CS43130_XSP_DOP_DAI: + regmap_write(cs43130->regmap, CS43130_XSP_DEN_1, + (clk_gen->den & CS43130_SP_M_LSB_DATA_MASK) >> + CS43130_SP_M_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_DEN_2, + (clk_gen->den & CS43130_SP_M_MSB_DATA_MASK) >> + CS43130_SP_M_MSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_NUM_1, + (clk_gen->num & CS43130_SP_N_LSB_DATA_MASK) >> + CS43130_SP_N_LSB_DATA_SHIFT); + regmap_write(cs43130->regmap, CS43130_XSP_NUM_2, + (clk_gen->num & CS43130_SP_N_MSB_DATA_MASK) >> + CS43130_SP_N_MSB_DATA_SHIFT); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cs43130_pcm_dsd_mix(bool en, struct regmap *regmap) +{ + if (en) { + regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL, + CS43130_MIX_PCM_PREP_MASK, + 1 << CS43130_MIX_PCM_PREP_SHIFT); + usleep_range(6000, 6050); + regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL, + CS43130_MIX_PCM_DSD_MASK, + 1 << CS43130_MIX_PCM_DSD_SHIFT); + } else { + regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL, + CS43130_MIX_PCM_DSD_MASK, + 0 << CS43130_MIX_PCM_DSD_SHIFT); + usleep_range(1600, 1650); + regmap_update_bits(regmap, CS43130_DSD_PCM_MIX_CTL, + CS43130_MIX_PCM_PREP_MASK, + 0 << CS43130_MIX_PCM_PREP_SHIFT); + } + + return 0; +} + +static int cs43130_dsd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + unsigned int required_clk; + u8 dsd_speed; + + mutex_lock(&cs43130->clk_mutex); + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + if (!(CS43130_MCLK_22M % params_rate(params))) + required_clk = CS43130_MCLK_22M; + else + required_clk = CS43130_MCLK_24M; + + cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); + if (cs43130->pll_bypass) + cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); + else + cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); + } + + cs43130->clk_req++; + if (cs43130->clk_req == 2) + cs43130_pcm_dsd_mix(true, cs43130->regmap); + mutex_unlock(&cs43130->clk_mutex); + + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; + break; + case 352800: + dsd_speed = 1; + break; + default: + dev_err(component->dev, "Rate(%u) not supported\n", + params_rate(params)); + return -EINVAL; + } + + if (cs43130->dais[dai->id].dai_mode == SND_SOC_DAIFMT_CBM_CFM) + regmap_update_bits(cs43130->regmap, CS43130_DSD_INT_CFG, + CS43130_DSD_MASTER, CS43130_DSD_MASTER); + else + regmap_update_bits(cs43130->regmap, CS43130_DSD_INT_CFG, + CS43130_DSD_MASTER, 0); + + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SPEED_MASK, + dsd_speed << CS43130_DSD_SPEED_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_DSD << + CS43130_DSD_SRC_SHIFT); + + return 0; +} + +static int cs43130_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + const struct cs43130_rate_map *rate_map; + unsigned int sclk = cs43130->dais[dai->id].sclk; + unsigned int bitwidth_sclk; + unsigned int bitwidth_dai = (unsigned int)(params_width(params)); + unsigned int required_clk; + u8 dsd_speed; + + mutex_lock(&cs43130->clk_mutex); + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + if (!(CS43130_MCLK_22M % params_rate(params))) + required_clk = CS43130_MCLK_22M; + else + required_clk = CS43130_MCLK_24M; + + cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); + if (cs43130->pll_bypass) + cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); + else + cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); + } + + cs43130->clk_req++; + if (cs43130->clk_req == 2) + cs43130_pcm_dsd_mix(true, cs43130->regmap); + mutex_unlock(&cs43130->clk_mutex); + + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: + /* DoP bitwidth is always 24-bit */ + bitwidth_dai = 24; + sclk = params_rate(params) * bitwidth_dai * + params_channels(params); + + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; + break; + case 352800: + dsd_speed = 1; + break; + default: + dev_err(component->dev, "Rate(%u) not supported\n", + params_rate(params)); + return -EINVAL; + } + + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SPEED_MASK, + dsd_speed << CS43130_DSD_SPEED_SHIFT); + break; + case CS43130_ASP_PCM_DAI: + rate_map = cs43130_get_rate_table(params_rate(params)); + if (!rate_map) + return -EINVAL; + + regmap_write(cs43130->regmap, CS43130_SP_SRATE, rate_map->val); + break; + default: + dev_err(component->dev, "Invalid DAI (%d)\n", dai->id); + return -EINVAL; + } + + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_ASP << + CS43130_DSD_SRC_SHIFT); + break; + case CS43130_XSP_DOP_DAI: + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_XSP << + CS43130_DSD_SRC_SHIFT); + break; + } + + if (!sclk && cs43130->dais[dai->id].dai_mode == SND_SOC_DAIFMT_CBM_CFM) + /* Calculate SCLK in master mode if unassigned */ + sclk = params_rate(params) * bitwidth_dai * + params_channels(params); + + if (!sclk) { + /* at this point, SCLK must be set */ + dev_err(component->dev, "SCLK freq is not set\n"); + return -EINVAL; + } + + bitwidth_sclk = (sclk / params_rate(params)) / params_channels(params); + if (bitwidth_sclk < bitwidth_dai) { + dev_err(component->dev, "Format not supported: SCLK freq is too low\n"); + return -EINVAL; + } + + dev_dbg(component->dev, + "sclk = %u, fs = %d, bitwidth_dai = %u\n", + sclk, params_rate(params), bitwidth_dai); + + dev_dbg(component->dev, + "bitwidth_sclk = %u, num_ch = %u\n", + bitwidth_sclk, params_channels(params)); + + cs43130_set_bitwidth(dai->id, bitwidth_dai, cs43130->regmap); + cs43130_set_sp_fmt(dai->id, bitwidth_sclk, params, cs43130); + + return 0; +} + +static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + mutex_lock(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_dsd_mix(false, cs43130->regmap); + } + mutex_unlock(&cs43130->clk_mutex); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(pcm_vol_tlv, -12750, 50, 1); + +static const char * const pcm_ch_text[] = { + "Left-Right Ch", + "Left-Left Ch", + "Right-Left Ch", + "Right-Right Ch", +}; + +static const struct reg_sequence pcm_ch_en_seq[] = { + {CS43130_DXD1, 0x99}, + {0x180005, 0x8C}, + {0x180007, 0xAB}, + {0x180015, 0x31}, + {0x180017, 0xB2}, + {0x180025, 0x30}, + {0x180027, 0x84}, + {0x180035, 0x9C}, + {0x180037, 0xAE}, + {0x18000D, 0x24}, + {0x18000F, 0xA3}, + {0x18001D, 0x05}, + {0x18001F, 0xD4}, + {0x18002D, 0x0B}, + {0x18002F, 0xC7}, + {0x18003D, 0x71}, + {0x18003F, 0xE7}, + {CS43130_DXD1, 0}, +}; + +static const struct reg_sequence pcm_ch_dis_seq[] = { + {CS43130_DXD1, 0x99}, + {0x180005, 0x24}, + {0x180007, 0xA3}, + {0x180015, 0x05}, + {0x180017, 0xD4}, + {0x180025, 0x0B}, + {0x180027, 0xC7}, + {0x180035, 0x71}, + {0x180037, 0xE7}, + {0x18000D, 0x8C}, + {0x18000F, 0xAB}, + {0x18001D, 0x31}, + {0x18001F, 0xB2}, + {0x18002D, 0x30}, + {0x18002F, 0x84}, + {0x18003D, 0x9C}, + {0x18003F, 0xAE}, + {CS43130_DXD1, 0}, +}; + +static int cs43130_pcm_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return snd_soc_get_enum_double(kcontrol, ucontrol); +} + +static int cs43130_pcm_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (item[0] >= e->items) + return -EINVAL; + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + + switch (cs43130->dev_id) { + case CS43131_CHIP_ID: + case CS43198_CHIP_ID: + if (val >= 2) + regmap_multi_reg_write(cs43130->regmap, pcm_ch_en_seq, + ARRAY_SIZE(pcm_ch_en_seq)); + else + regmap_multi_reg_write(cs43130->regmap, pcm_ch_dis_seq, + ARRAY_SIZE(pcm_ch_dis_seq)); + break; + } + + return snd_soc_put_enum_double(kcontrol, ucontrol); +} + +static SOC_ENUM_SINGLE_DECL(pcm_ch_enum, CS43130_PCM_PATH_CTL_2, 0, + pcm_ch_text); + +static const char * const pcm_spd_texts[] = { + "Fast", + "Slow", +}; + +static SOC_ENUM_SINGLE_DECL(pcm_spd_enum, CS43130_PCM_FILT_OPT, 7, + pcm_spd_texts); + +static const char * const dsd_texts[] = { + "Off", + "BCKA Mode", + "BCKD Mode", +}; + +static const unsigned int dsd_values[] = { + CS43130_DSD_SRC_DSD, + CS43130_DSD_SRC_ASP, + CS43130_DSD_SRC_XSP, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(dsd_enum, CS43130_DSD_INT_CFG, 0, 0x03, + dsd_texts, dsd_values); + +static const struct snd_kcontrol_new cs43130_snd_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", + CS43130_PCM_VOL_A, CS43130_PCM_VOL_B, 0, 0xFF, 1, + pcm_vol_tlv), + SOC_DOUBLE_R_TLV("Master DSD Playback Volume", + CS43130_DSD_VOL_A, CS43130_DSD_VOL_B, 0, 0xFF, 1, + pcm_vol_tlv), + SOC_ENUM_EXT("PCM Ch Select", pcm_ch_enum, cs43130_pcm_ch_get, + cs43130_pcm_ch_put), + SOC_ENUM("PCM Filter Speed", pcm_spd_enum), + SOC_SINGLE("PCM Phase Compensation", CS43130_PCM_FILT_OPT, 6, 1, 0), + SOC_SINGLE("PCM Nonoversample Emulate", CS43130_PCM_FILT_OPT, 5, 1, 0), + SOC_SINGLE("PCM High-pass Filter", CS43130_PCM_FILT_OPT, 1, 1, 0), + SOC_SINGLE("PCM De-emphasis Filter", CS43130_PCM_FILT_OPT, 0, 1, 0), + SOC_ENUM("DSD Phase Modulation", dsd_enum), +}; + +static const struct reg_sequence pcm_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD7, 0x01}, + {CS43130_DXD8, 0}, + {CS43130_DXD9, 0x01}, + {CS43130_DXD3, 0x12}, + {CS43130_DXD4, 0}, + {CS43130_DXD10, 0x28}, + {CS43130_DXD11, 0x28}, + {CS43130_DXD1, 0}, +}; + +static const struct reg_sequence dsd_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD7, 0x01}, + {CS43130_DXD8, 0}, + {CS43130_DXD9, 0x01}, + {CS43130_DXD3, 0x12}, + {CS43130_DXD4, 0}, + {CS43130_DXD10, 0x1E}, + {CS43130_DXD11, 0x20}, + {CS43130_DXD1, 0}, +}; + +static const struct reg_sequence pop_free_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD12, 0x0A}, + {CS43130_DXD1, 0}, +}; + +static const struct reg_sequence pop_free_seq2[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD13, 0x20}, + {CS43130_DXD1, 0}, +}; + +static const struct reg_sequence mute_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD3, 0x12}, + {CS43130_DXD5, 0x02}, + {CS43130_DXD4, 0x12}, + {CS43130_DXD1, 0}, +}; + +static const struct reg_sequence unmute_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD3, 0x10}, + {CS43130_DXD5, 0}, + {CS43130_DXD4, 0x16}, + {CS43130_DXD1, 0}, +}; + +static int cs43130_dsd_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, dsd_seq, + ARRAY_SIZE(dsd_seq)); + break; + } + break; + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_1, + CS43130_MUTE_MASK, 0); + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, unmute_seq, + ARRAY_SIZE(unmute_seq)); + break; + } + break; + case SND_SOC_DAPM_PRE_PMD: + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, mute_seq, + ARRAY_SIZE(mute_seq)); + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_1, + CS43130_MUTE_MASK, CS43130_MUTE_EN); + /* + * DSD Power Down Sequence + * According to Design, 130ms is preferred. + */ + msleep(130); + break; + case CS43131_CHIP_ID: + case CS43198_CHIP_ID: + regmap_update_bits(cs43130->regmap, + CS43130_DSD_PATH_CTL_1, + CS43130_MUTE_MASK, CS43130_MUTE_EN); + break; + } + break; + default: + dev_err(component->dev, "Invalid event = 0x%x\n", event); + return -EINVAL; + } + return 0; +} + +static int cs43130_pcm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, pcm_seq, + ARRAY_SIZE(pcm_seq)); + break; + } + break; + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(cs43130->regmap, CS43130_PCM_PATH_CTL_1, + CS43130_MUTE_MASK, 0); + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, unmute_seq, + ARRAY_SIZE(unmute_seq)); + break; + } + break; + case SND_SOC_DAPM_PRE_PMD: + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, mute_seq, + ARRAY_SIZE(mute_seq)); + regmap_update_bits(cs43130->regmap, + CS43130_PCM_PATH_CTL_1, + CS43130_MUTE_MASK, CS43130_MUTE_EN); + /* + * PCM Power Down Sequence + * According to Design, 130ms is preferred. + */ + msleep(130); + break; + case CS43131_CHIP_ID: + case CS43198_CHIP_ID: + regmap_update_bits(cs43130->regmap, + CS43130_PCM_PATH_CTL_1, + CS43130_MUTE_MASK, CS43130_MUTE_EN); + break; + } + break; + default: + dev_err(component->dev, "Invalid event = 0x%x\n", event); + return -EINVAL; + } + return 0; +} + +static const struct reg_sequence dac_postpmu_seq[] = { + {CS43130_DXD9, 0x0C}, + {CS43130_DXD3, 0x10}, + {CS43130_DXD4, 0x20}, +}; + +static const struct reg_sequence dac_postpmd_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD6, 0x01}, + {CS43130_DXD1, 0}, +}; + +static int cs43130_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, pop_free_seq, + ARRAY_SIZE(pop_free_seq)); + break; + case CS43131_CHIP_ID: + case CS43198_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, pop_free_seq2, + ARRAY_SIZE(pop_free_seq2)); + break; + } + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(10000, 10050); + + regmap_write(cs43130->regmap, CS43130_DXD1, 0x99); + + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, dac_postpmu_seq, + ARRAY_SIZE(dac_postpmu_seq)); + /* + * Per datasheet, Sec. PCM Power-Up Sequence. + * According to Design, CS43130_DXD12 must be 0 to meet + * THDN and Dynamic Range spec. + */ + msleep(1000); + regmap_write(cs43130->regmap, CS43130_DXD12, 0); + break; + case CS43131_CHIP_ID: + case CS43198_CHIP_ID: + usleep_range(12000, 12010); + regmap_write(cs43130->regmap, CS43130_DXD13, 0); + break; + } + + regmap_write(cs43130->regmap, CS43130_DXD1, 0); + break; + case SND_SOC_DAPM_POST_PMD: + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + regmap_multi_reg_write(cs43130->regmap, dac_postpmd_seq, + ARRAY_SIZE(dac_postpmd_seq)); + break; + } + break; + default: + dev_err(component->dev, "Invalid DAC event = 0x%x\n", event); + return -EINVAL; + } + return 0; +} + +static const struct reg_sequence hpin_prepmd_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD15, 0x64}, + {CS43130_DXD14, 0}, + {CS43130_DXD2, 0}, + {CS43130_DXD1, 0}, +}; + +static const struct reg_sequence hpin_postpmu_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD2, 1}, + {CS43130_DXD14, 0xDC}, + {CS43130_DXD15, 0xE4}, + {CS43130_DXD1, 0}, +}; + +static int cs43130_hpin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMD: + regmap_multi_reg_write(cs43130->regmap, hpin_prepmd_seq, + ARRAY_SIZE(hpin_prepmd_seq)); + break; + case SND_SOC_DAPM_PRE_PMU: + regmap_multi_reg_write(cs43130->regmap, hpin_postpmu_seq, + ARRAY_SIZE(hpin_postpmu_seq)); + break; + default: + dev_err(component->dev, "Invalid HPIN event = 0x%x\n", event); + return -EINVAL; + } + return 0; +} + +static const struct snd_soc_dapm_widget digital_hp_widgets[] = { + SND_SOC_DAPM_OUTPUT("HPOUTA"), + SND_SOC_DAPM_OUTPUT("HPOUTB"), + + SND_SOC_DAPM_AIF_IN_E("ASPIN PCM", NULL, 0, CS43130_PWDN_CTL, + CS43130_PDN_ASP_SHIFT, 1, cs43130_pcm_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD)), + + SND_SOC_DAPM_AIF_IN_E("ASPIN DoP", NULL, 0, CS43130_PWDN_CTL, + CS43130_PDN_ASP_SHIFT, 1, cs43130_dsd_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD)), + + SND_SOC_DAPM_AIF_IN_E("XSPIN DoP", NULL, 0, CS43130_PWDN_CTL, + CS43130_PDN_XSP_SHIFT, 1, cs43130_dsd_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD)), + + SND_SOC_DAPM_AIF_IN_E("XSPIN DSD", NULL, 0, CS43130_PWDN_CTL, + CS43130_PDN_DSDIF_SHIFT, 1, cs43130_dsd_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD)), + + SND_SOC_DAPM_DAC("DSD", NULL, CS43130_DSD_PATH_CTL_2, + CS43130_DSD_EN_SHIFT, 0), + + SND_SOC_DAPM_DAC_E("HiFi DAC", NULL, CS43130_PWDN_CTL, + CS43130_PDN_HP_SHIFT, 1, cs43130_dac_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD)), +}; + +static const struct snd_soc_dapm_widget analog_hp_widgets[] = { + SND_SOC_DAPM_DAC_E("Analog Playback", NULL, CS43130_HP_OUT_CTL_1, + CS43130_HP_IN_EN_SHIFT, 0, cs43130_hpin_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)), +}; + +static struct snd_soc_dapm_widget all_hp_widgets[ + ARRAY_SIZE(digital_hp_widgets) + + ARRAY_SIZE(analog_hp_widgets)]; + +static const struct snd_soc_dapm_route digital_hp_routes[] = { + {"ASPIN PCM", NULL, "ASP PCM Playback"}, + {"ASPIN DoP", NULL, "ASP DoP Playback"}, + {"XSPIN DoP", NULL, "XSP DoP Playback"}, + {"XSPIN DSD", NULL, "XSP DSD Playback"}, + {"DSD", NULL, "ASPIN DoP"}, + {"DSD", NULL, "XSPIN DoP"}, + {"DSD", NULL, "XSPIN DSD"}, + {"HiFi DAC", NULL, "ASPIN PCM"}, + {"HiFi DAC", NULL, "DSD"}, + {"HPOUTA", NULL, "HiFi DAC"}, + {"HPOUTB", NULL, "HiFi DAC"}, +}; + +static const struct snd_soc_dapm_route analog_hp_routes[] = { + {"HPOUTA", NULL, "Analog Playback"}, + {"HPOUTB", NULL, "Analog Playback"}, +}; + +static struct snd_soc_dapm_route all_hp_routes[ + ARRAY_SIZE(digital_hp_routes) + + ARRAY_SIZE(analog_hp_routes)]; + +static const unsigned int cs43130_asp_src_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 +}; + +static const struct snd_pcm_hw_constraint_list cs43130_asp_constraints = { + .count = ARRAY_SIZE(cs43130_asp_src_rates), + .list = cs43130_asp_src_rates, +}; + +static int cs43130_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs43130_asp_constraints); +} + +static const unsigned int cs43130_dop_src_rates[] = { + 176400, 352800, +}; + +static const struct snd_pcm_hw_constraint_list cs43130_dop_constraints = { + .count = ARRAY_SIZE(cs43130_dop_src_rates), + .list = cs43130_dop_src_rates, +}; + +static int cs43130_dop_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs43130_dop_constraints); +} + +static int cs43130_pcm_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBS_CFS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBM_CFM; + break; + default: + dev_err(component->dev, "unsupported mode\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_LEFT_J; + break; + case SND_SOC_DAIFMT_DSP_A: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_DSP_B; + break; + default: + dev_err(component->dev, + "unsupported audio format\n"); + return -EINVAL; + } + + dev_dbg(component->dev, "dai_id = %d, dai_mode = %u, dai_format = %u\n", + codec_dai->id, + cs43130->dais[codec_dai->id].dai_mode, + cs43130->dais[codec_dai->id].dai_format); + + return 0; +} + +static int cs43130_dsd_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBS_CFS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBM_CFM; + break; + default: + dev_err(component->dev, "Unsupported DAI format.\n"); + return -EINVAL; + } + + dev_dbg(component->dev, "dai_mode = 0x%x\n", + cs43130->dais[codec_dai->id].dai_mode); + + return 0; +} + +static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + cs43130->dais[codec_dai->id].sclk = freq; + dev_dbg(component->dev, "dai_id = %d, sclk = %u\n", codec_dai->id, + cs43130->dais[codec_dai->id].sclk); + + return 0; +} + +static const struct snd_soc_dai_ops cs43130_pcm_ops = { + .startup = cs43130_pcm_startup, + .hw_params = cs43130_hw_params, + .hw_free = cs43130_hw_free, + .set_sysclk = cs43130_set_sysclk, + .set_fmt = cs43130_pcm_set_fmt, +}; + +static const struct snd_soc_dai_ops cs43130_dop_ops = { + .startup = cs43130_dop_startup, + .hw_params = cs43130_hw_params, + .hw_free = cs43130_hw_free, + .set_sysclk = cs43130_set_sysclk, + .set_fmt = cs43130_pcm_set_fmt, +}; + +static const struct snd_soc_dai_ops cs43130_dsd_ops = { + .startup = cs43130_dop_startup, + .hw_params = cs43130_dsd_hw_params, + .hw_free = cs43130_hw_free, + .set_fmt = cs43130_dsd_set_fmt, +}; + +static struct snd_soc_dai_driver cs43130_dai[] = { + { + .name = "cs43130-asp-pcm", + .id = CS43130_ASP_PCM_DAI, + .playback = { + .stream_name = "ASP PCM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS43130_PCM_FORMATS, + }, + .ops = &cs43130_pcm_ops, + .symmetric_rates = 1, + }, + { + .name = "cs43130-asp-dop", + .id = CS43130_ASP_DOP_DAI, + .playback = { + .stream_name = "ASP DoP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS43130_DOP_FORMATS, + }, + .ops = &cs43130_dop_ops, + .symmetric_rates = 1, + }, + { + .name = "cs43130-xsp-dop", + .id = CS43130_XSP_DOP_DAI, + .playback = { + .stream_name = "XSP DoP Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS43130_DOP_FORMATS, + }, + .ops = &cs43130_dop_ops, + .symmetric_rates = 1, + }, + { + .name = "cs43130-xsp-dsd", + .id = CS43130_XSP_DSD_DAI, + .playback = { + .stream_name = "XSP DSD Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS43130_DOP_FORMATS, + }, + .ops = &cs43130_dsd_ops, + }, + +}; + +static int cs43130_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, + int dir) +{ + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "clk_id = %d, source = %d, freq = %d, dir = %d\n", + clk_id, source, freq, dir); + + switch (freq) { + case CS43130_MCLK_22M: + case CS43130_MCLK_24M: + cs43130->mclk = freq; + break; + default: + dev_err(component->dev, "Invalid MCLK INT freq: %u\n", freq); + return -EINVAL; + } + + if (source == CS43130_MCLK_SRC_EXT) { + cs43130->pll_bypass = true; + } else { + dev_err(component->dev, "Invalid MCLK source\n"); + return -EINVAL; + } + + return 0; +} + +static inline u16 cs43130_get_ac_reg_val(u16 ac_freq) +{ + /* AC freq is counted in 5.94Hz step. */ + return ac_freq / 6; +} + +static int cs43130_show_dc(struct device *dev, char *buf, u8 ch) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cs43130_private *cs43130 = i2c_get_clientdata(client); + + if (!cs43130->hpload_done) + return scnprintf(buf, PAGE_SIZE, "NO_HPLOAD\n"); + else + return scnprintf(buf, PAGE_SIZE, "%u\n", + cs43130->hpload_dc[ch]); +} + +static ssize_t cs43130_show_dc_l(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return cs43130_show_dc(dev, buf, HP_LEFT); +} + +static ssize_t cs43130_show_dc_r(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return cs43130_show_dc(dev, buf, HP_RIGHT); +} + +static u16 const cs43130_ac_freq[CS43130_AC_FREQ] = { + 24, + 43, + 93, + 200, + 431, + 928, + 2000, + 4309, + 9283, + 20000, +}; + +static int cs43130_show_ac(struct device *dev, char *buf, u8 ch) +{ + int i, j = 0, tmp; + struct i2c_client *client = to_i2c_client(dev); + struct cs43130_private *cs43130 = i2c_get_clientdata(client); + + if (cs43130->hpload_done && cs43130->ac_meas) { + for (i = 0; i < ARRAY_SIZE(cs43130_ac_freq); i++) { + tmp = scnprintf(buf + j, PAGE_SIZE - j, "%u\n", + cs43130->hpload_ac[i][ch]); + if (!tmp) + break; + + j += tmp; + } + + return j; + } else { + return scnprintf(buf, PAGE_SIZE, "NO_HPLOAD\n"); + } +} + +static ssize_t cs43130_show_ac_l(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return cs43130_show_ac(dev, buf, HP_LEFT); +} + +static ssize_t cs43130_show_ac_r(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return cs43130_show_ac(dev, buf, HP_RIGHT); +} + +static DEVICE_ATTR(hpload_dc_l, 0444, cs43130_show_dc_l, NULL); +static DEVICE_ATTR(hpload_dc_r, 0444, cs43130_show_dc_r, NULL); +static DEVICE_ATTR(hpload_ac_l, 0444, cs43130_show_ac_l, NULL); +static DEVICE_ATTR(hpload_ac_r, 0444, cs43130_show_ac_r, NULL); + +static struct attribute *hpload_attrs[] = { + &dev_attr_hpload_dc_l.attr, + &dev_attr_hpload_dc_r.attr, + &dev_attr_hpload_ac_l.attr, + &dev_attr_hpload_ac_r.attr, +}; +ATTRIBUTE_GROUPS(hpload); + +static struct reg_sequence hp_en_cal_seq[] = { + {CS43130_INT_MASK_4, CS43130_INT_MASK_ALL}, + {CS43130_HP_MEAS_LOAD_1, 0}, + {CS43130_HP_MEAS_LOAD_2, 0}, + {CS43130_INT_MASK_4, 0}, + {CS43130_DXD1, 0x99}, + {CS43130_DXD16, 0xBB}, + {CS43130_DXD12, 0x01}, + {CS43130_DXD19, 0xCB}, + {CS43130_DXD17, 0x95}, + {CS43130_DXD18, 0x0B}, + {CS43130_DXD1, 0}, + {CS43130_HP_LOAD_1, 0x80}, +}; + +static struct reg_sequence hp_en_cal_seq2[] = { + {CS43130_INT_MASK_4, CS43130_INT_MASK_ALL}, + {CS43130_HP_MEAS_LOAD_1, 0}, + {CS43130_HP_MEAS_LOAD_2, 0}, + {CS43130_INT_MASK_4, 0}, + {CS43130_HP_LOAD_1, 0x80}, +}; + +static struct reg_sequence hp_dis_cal_seq[] = { + {CS43130_HP_LOAD_1, 0x80}, + {CS43130_DXD1, 0x99}, + {CS43130_DXD12, 0}, + {CS43130_DXD1, 0}, + {CS43130_HP_LOAD_1, 0}, +}; + +static struct reg_sequence hp_dis_cal_seq2[] = { + {CS43130_HP_LOAD_1, 0x80}, + {CS43130_HP_LOAD_1, 0}, +}; + +static struct reg_sequence hp_dc_ch_l_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD19, 0x0A}, + {CS43130_DXD17, 0x93}, + {CS43130_DXD18, 0x0A}, + {CS43130_DXD1, 0}, + {CS43130_HP_LOAD_1, 0x80}, + {CS43130_HP_LOAD_1, 0x81}, +}; + +static struct reg_sequence hp_dc_ch_l_seq2[] = { + {CS43130_HP_LOAD_1, 0x80}, + {CS43130_HP_LOAD_1, 0x81}, +}; + +static struct reg_sequence hp_dc_ch_r_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD19, 0x8A}, + {CS43130_DXD17, 0x15}, + {CS43130_DXD18, 0x06}, + {CS43130_DXD1, 0}, + {CS43130_HP_LOAD_1, 0x90}, + {CS43130_HP_LOAD_1, 0x91}, +}; + +static struct reg_sequence hp_dc_ch_r_seq2[] = { + {CS43130_HP_LOAD_1, 0x90}, + {CS43130_HP_LOAD_1, 0x91}, +}; + +static struct reg_sequence hp_ac_ch_l_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD19, 0x0A}, + {CS43130_DXD17, 0x93}, + {CS43130_DXD18, 0x0A}, + {CS43130_DXD1, 0}, + {CS43130_HP_LOAD_1, 0x80}, + {CS43130_HP_LOAD_1, 0x82}, +}; + +static struct reg_sequence hp_ac_ch_l_seq2[] = { + {CS43130_HP_LOAD_1, 0x80}, + {CS43130_HP_LOAD_1, 0x82}, +}; + +static struct reg_sequence hp_ac_ch_r_seq[] = { + {CS43130_DXD1, 0x99}, + {CS43130_DXD19, 0x8A}, + {CS43130_DXD17, 0x15}, + {CS43130_DXD18, 0x06}, + {CS43130_DXD1, 0}, + {CS43130_HP_LOAD_1, 0x90}, + {CS43130_HP_LOAD_1, 0x92}, +}; + +static struct reg_sequence hp_ac_ch_r_seq2[] = { + {CS43130_HP_LOAD_1, 0x90}, + {CS43130_HP_LOAD_1, 0x92}, +}; + +static struct reg_sequence hp_cln_seq[] = { + {CS43130_INT_MASK_4, CS43130_INT_MASK_ALL}, + {CS43130_HP_MEAS_LOAD_1, 0}, + {CS43130_HP_MEAS_LOAD_2, 0}, +}; + +struct reg_sequences { + struct reg_sequence *seq; + int size; + unsigned int msk; +}; + +static struct reg_sequences hpload_seq1[] = { + { + .seq = hp_en_cal_seq, + .size = ARRAY_SIZE(hp_en_cal_seq), + .msk = CS43130_HPLOAD_ON_INT, + }, + { + .seq = hp_dc_ch_l_seq, + .size = ARRAY_SIZE(hp_dc_ch_l_seq), + .msk = CS43130_HPLOAD_DC_INT, + }, + { + .seq = hp_ac_ch_l_seq, + .size = ARRAY_SIZE(hp_ac_ch_l_seq), + .msk = CS43130_HPLOAD_AC_INT, + }, + { + .seq = hp_dis_cal_seq, + .size = ARRAY_SIZE(hp_dis_cal_seq), + .msk = CS43130_HPLOAD_OFF_INT, + }, + { + .seq = hp_en_cal_seq, + .size = ARRAY_SIZE(hp_en_cal_seq), + .msk = CS43130_HPLOAD_ON_INT, + }, + { + .seq = hp_dc_ch_r_seq, + .size = ARRAY_SIZE(hp_dc_ch_r_seq), + .msk = CS43130_HPLOAD_DC_INT, + }, + { + .seq = hp_ac_ch_r_seq, + .size = ARRAY_SIZE(hp_ac_ch_r_seq), + .msk = CS43130_HPLOAD_AC_INT, + }, +}; + +static struct reg_sequences hpload_seq2[] = { + { + .seq = hp_en_cal_seq2, + .size = ARRAY_SIZE(hp_en_cal_seq2), + .msk = CS43130_HPLOAD_ON_INT, + }, + { + .seq = hp_dc_ch_l_seq2, + .size = ARRAY_SIZE(hp_dc_ch_l_seq2), + .msk = CS43130_HPLOAD_DC_INT, + }, + { + .seq = hp_ac_ch_l_seq2, + .size = ARRAY_SIZE(hp_ac_ch_l_seq2), + .msk = CS43130_HPLOAD_AC_INT, + }, + { + .seq = hp_dis_cal_seq2, + .size = ARRAY_SIZE(hp_dis_cal_seq2), + .msk = CS43130_HPLOAD_OFF_INT, + }, + { + .seq = hp_en_cal_seq2, + .size = ARRAY_SIZE(hp_en_cal_seq2), + .msk = CS43130_HPLOAD_ON_INT, + }, + { + .seq = hp_dc_ch_r_seq2, + .size = ARRAY_SIZE(hp_dc_ch_r_seq2), + .msk = CS43130_HPLOAD_DC_INT, + }, + { + .seq = hp_ac_ch_r_seq2, + .size = ARRAY_SIZE(hp_ac_ch_r_seq2), + .msk = CS43130_HPLOAD_AC_INT, + }, +}; + +static int cs43130_update_hpload(unsigned int msk, int ac_idx, + struct cs43130_private *cs43130) +{ + bool left_ch = true; + unsigned int reg; + u32 addr; + u16 impedance; + struct snd_soc_component *component = cs43130->component; + + switch (msk) { + case CS43130_HPLOAD_DC_INT: + case CS43130_HPLOAD_AC_INT: + break; + default: + return 0; + } + + regmap_read(cs43130->regmap, CS43130_HP_LOAD_1, ®); + if (reg & CS43130_HPLOAD_CHN_SEL) + left_ch = false; + + if (msk == CS43130_HPLOAD_DC_INT) + addr = CS43130_HP_DC_STAT_1; + else + addr = CS43130_HP_AC_STAT_1; + + regmap_read(cs43130->regmap, addr, ®); + impedance = reg >> 3; + regmap_read(cs43130->regmap, addr + 1, ®); + impedance |= reg << 5; + + if (msk == CS43130_HPLOAD_DC_INT) { + if (left_ch) + cs43130->hpload_dc[HP_LEFT] = impedance; + else + cs43130->hpload_dc[HP_RIGHT] = impedance; + + dev_dbg(component->dev, "HP DC impedance (Ch %u): %u\n", !left_ch, + impedance); + } else { + if (left_ch) + cs43130->hpload_ac[ac_idx][HP_LEFT] = impedance; + else + cs43130->hpload_ac[ac_idx][HP_RIGHT] = impedance; + + dev_dbg(component->dev, "HP AC (%u Hz) impedance (Ch %u): %u\n", + cs43130->ac_freq[ac_idx], !left_ch, impedance); + } + + return 0; +} + +static int cs43130_hpload_proc(struct cs43130_private *cs43130, + struct reg_sequence *seq, int seq_size, + unsigned int rslt_msk, int ac_idx) +{ + int ret; + unsigned int msk; + u16 ac_reg_val; + struct snd_soc_component *component = cs43130->component; + + reinit_completion(&cs43130->hpload_evt); + + if (rslt_msk == CS43130_HPLOAD_AC_INT) { + ac_reg_val = cs43130_get_ac_reg_val(cs43130->ac_freq[ac_idx]); + regmap_update_bits(cs43130->regmap, CS43130_HP_LOAD_1, + CS43130_HPLOAD_AC_START, 0); + regmap_update_bits(cs43130->regmap, CS43130_HP_MEAS_LOAD_1, + CS43130_HP_MEAS_LOAD_MASK, + ac_reg_val >> CS43130_HP_MEAS_LOAD_1_SHIFT); + regmap_update_bits(cs43130->regmap, CS43130_HP_MEAS_LOAD_2, + CS43130_HP_MEAS_LOAD_MASK, + ac_reg_val >> CS43130_HP_MEAS_LOAD_2_SHIFT); + } + + regmap_multi_reg_write(cs43130->regmap, seq, + seq_size); + + ret = wait_for_completion_timeout(&cs43130->hpload_evt, + msecs_to_jiffies(1000)); + regmap_read(cs43130->regmap, CS43130_INT_MASK_4, &msk); + if (!ret) { + dev_err(component->dev, "Timeout waiting for HPLOAD interrupt\n"); + return -1; + } + + dev_dbg(component->dev, "HP load stat: %x, INT_MASK_4: %x\n", + cs43130->hpload_stat, msk); + if ((cs43130->hpload_stat & (CS43130_HPLOAD_NO_DC_INT | + CS43130_HPLOAD_UNPLUG_INT | + CS43130_HPLOAD_OOR_INT)) || + !(cs43130->hpload_stat & rslt_msk)) { + dev_dbg(component->dev, "HP load measure failed\n"); + return -1; + } + + return 0; +} + +static const struct reg_sequence hv_seq[][2] = { + { + {CS43130_CLASS_H_CTL, 0x1C}, + {CS43130_HP_OUT_CTL_1, 0x10}, + }, + { + {CS43130_CLASS_H_CTL, 0x1E}, + {CS43130_HP_OUT_CTL_1, 0x20}, + }, + { + {CS43130_CLASS_H_CTL, 0x1E}, + {CS43130_HP_OUT_CTL_1, 0x30}, + }, +}; + +static int cs43130_set_hv(struct regmap *regmap, u16 hpload_dc, + const u16 *dc_threshold) +{ + int i; + + for (i = 0; i < CS43130_DC_THRESHOLD; i++) { + if (hpload_dc <= dc_threshold[i]) + break; + } + + regmap_multi_reg_write(regmap, hv_seq[i], ARRAY_SIZE(hv_seq[i])); + + return 0; +} + +static void cs43130_imp_meas(struct work_struct *wk) +{ + unsigned int reg, seq_size; + int i, ret, ac_idx; + struct cs43130_private *cs43130; + struct snd_soc_component *component; + struct reg_sequences *hpload_seq; + + cs43130 = container_of(wk, struct cs43130_private, work); + component = cs43130->component; + + if (!cs43130->mclk) + return; + + cs43130->hpload_done = false; + + mutex_lock(&cs43130->clk_mutex); + if (!cs43130->clk_req) { + /* clk not in use */ + cs43130_set_pll(component, 0, 0, cs43130->mclk, CS43130_MCLK_22M); + if (cs43130->pll_bypass) + cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); + else + cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); + } + + cs43130->clk_req++; + mutex_unlock(&cs43130->clk_mutex); + + regmap_read(cs43130->regmap, CS43130_INT_STATUS_4, ®); + + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + hpload_seq = hpload_seq1; + seq_size = ARRAY_SIZE(hpload_seq1); + break; + case CS43131_CHIP_ID: + hpload_seq = hpload_seq2; + seq_size = ARRAY_SIZE(hpload_seq2); + break; + default: + WARN(1, "Invalid dev_id for meas: %d", cs43130->dev_id); + return; + } + + i = 0; + ac_idx = 0; + while (i < seq_size) { + ret = cs43130_hpload_proc(cs43130, hpload_seq[i].seq, + hpload_seq[i].size, + hpload_seq[i].msk, ac_idx); + if (ret < 0) + goto exit; + + cs43130_update_hpload(hpload_seq[i].msk, ac_idx, cs43130); + + if (cs43130->ac_meas && + hpload_seq[i].msk == CS43130_HPLOAD_AC_INT && + ac_idx < CS43130_AC_FREQ - 1) { + ac_idx++; + } else { + ac_idx = 0; + i++; + } + } + cs43130->hpload_done = true; + + if (cs43130->hpload_dc[HP_LEFT] >= CS43130_LINEOUT_LOAD) + snd_soc_jack_report(&cs43130->jack, CS43130_JACK_LINEOUT, + CS43130_JACK_MASK); + else + snd_soc_jack_report(&cs43130->jack, CS43130_JACK_HEADPHONE, + CS43130_JACK_MASK); + + dev_dbg(component->dev, "Set HP output control. DC threshold\n"); + for (i = 0; i < CS43130_DC_THRESHOLD; i++) + dev_dbg(component->dev, "DC threshold[%d]: %u.\n", i, + cs43130->dc_threshold[i]); + + cs43130_set_hv(cs43130->regmap, cs43130->hpload_dc[HP_LEFT], + cs43130->dc_threshold); + +exit: + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + cs43130_hpload_proc(cs43130, hp_dis_cal_seq, + ARRAY_SIZE(hp_dis_cal_seq), + CS43130_HPLOAD_OFF_INT, ac_idx); + break; + case CS43131_CHIP_ID: + cs43130_hpload_proc(cs43130, hp_dis_cal_seq2, + ARRAY_SIZE(hp_dis_cal_seq2), + CS43130_HPLOAD_OFF_INT, ac_idx); + break; + } + + regmap_multi_reg_write(cs43130->regmap, hp_cln_seq, + ARRAY_SIZE(hp_cln_seq)); + + mutex_lock(&cs43130->clk_mutex); + cs43130->clk_req--; + /* clk not in use */ + if (!cs43130->clk_req) + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + mutex_unlock(&cs43130->clk_mutex); +} + +static irqreturn_t cs43130_irq_thread(int irq, void *data) +{ + struct cs43130_private *cs43130 = (struct cs43130_private *)data; + struct snd_soc_component *component = cs43130->component; + unsigned int stickies[CS43130_NUM_INT]; + unsigned int irq_occurrence = 0; + unsigned int masks[CS43130_NUM_INT]; + int i, j; + + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + regmap_read(cs43130->regmap, CS43130_INT_STATUS_1 + i, + &stickies[i]); + regmap_read(cs43130->regmap, CS43130_INT_MASK_1 + i, + &masks[i]); + } + + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + stickies[i] = stickies[i] & (~masks[i]); + for (j = 0; j < 8; j++) + irq_occurrence += (stickies[i] >> j) & 1; + } + dev_dbg(component->dev, "number of interrupts occurred (%u)\n", + irq_occurrence); + + if (!irq_occurrence) + return IRQ_NONE; + + if (stickies[0] & CS43130_XTAL_RDY_INT) { + complete(&cs43130->xtal_rdy); + return IRQ_HANDLED; + } + + if (stickies[0] & CS43130_PLL_RDY_INT) { + complete(&cs43130->pll_rdy); + return IRQ_HANDLED; + } + + if (stickies[3] & CS43130_HPLOAD_NO_DC_INT) { + cs43130->hpload_stat = stickies[3]; + dev_err(component->dev, + "DC load has not completed before AC load (%x)\n", + cs43130->hpload_stat); + complete(&cs43130->hpload_evt); + return IRQ_HANDLED; + } + + if (stickies[3] & CS43130_HPLOAD_UNPLUG_INT) { + cs43130->hpload_stat = stickies[3]; + dev_err(component->dev, "HP unplugged during measurement (%x)\n", + cs43130->hpload_stat); + complete(&cs43130->hpload_evt); + return IRQ_HANDLED; + } + + if (stickies[3] & CS43130_HPLOAD_OOR_INT) { + cs43130->hpload_stat = stickies[3]; + dev_err(component->dev, "HP load out of range (%x)\n", + cs43130->hpload_stat); + complete(&cs43130->hpload_evt); + return IRQ_HANDLED; + } + + if (stickies[3] & CS43130_HPLOAD_AC_INT) { + cs43130->hpload_stat = stickies[3]; + dev_dbg(component->dev, "HP AC load measurement done (%x)\n", + cs43130->hpload_stat); + complete(&cs43130->hpload_evt); + return IRQ_HANDLED; + } + + if (stickies[3] & CS43130_HPLOAD_DC_INT) { + cs43130->hpload_stat = stickies[3]; + dev_dbg(component->dev, "HP DC load measurement done (%x)\n", + cs43130->hpload_stat); + complete(&cs43130->hpload_evt); + return IRQ_HANDLED; + } + + if (stickies[3] & CS43130_HPLOAD_ON_INT) { + cs43130->hpload_stat = stickies[3]; + dev_dbg(component->dev, "HP load state machine on done (%x)\n", + cs43130->hpload_stat); + complete(&cs43130->hpload_evt); + return IRQ_HANDLED; + } + + if (stickies[3] & CS43130_HPLOAD_OFF_INT) { + cs43130->hpload_stat = stickies[3]; + dev_dbg(component->dev, "HP load state machine off done (%x)\n", + cs43130->hpload_stat); + complete(&cs43130->hpload_evt); + return IRQ_HANDLED; + } + + if (stickies[0] & CS43130_XTAL_ERR_INT) { + dev_err(component->dev, "Crystal err: clock is not running\n"); + return IRQ_HANDLED; + } + + if (stickies[0] & CS43130_HP_UNPLUG_INT) { + dev_dbg(component->dev, "HP unplugged\n"); + cs43130->hpload_done = false; + snd_soc_jack_report(&cs43130->jack, 0, CS43130_JACK_MASK); + return IRQ_HANDLED; + } + + if (stickies[0] & CS43130_HP_PLUG_INT) { + if (cs43130->dc_meas && !cs43130->hpload_done && + !work_busy(&cs43130->work)) { + dev_dbg(component->dev, "HP load queue work\n"); + queue_work(cs43130->wq, &cs43130->work); + } + + snd_soc_jack_report(&cs43130->jack, SND_JACK_MECHANICAL, + CS43130_JACK_MASK); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int cs43130_probe(struct snd_soc_component *component) +{ + int ret; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + struct snd_soc_card *card = component->card; + unsigned int reg; + + cs43130->component = component; + + if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED) { + regmap_update_bits(cs43130->regmap, CS43130_CRYSTAL_SET, + CS43130_XTAL_IBIAS_MASK, + cs43130->xtal_ibias); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_ERR_INT, 0); + } + + ret = snd_soc_card_jack_new(card, "Headphone", CS43130_JACK_MASK, + &cs43130->jack, NULL, 0); + if (ret < 0) { + dev_err(component->dev, "Cannot create jack\n"); + return ret; + } + + cs43130->hpload_done = false; + if (cs43130->dc_meas) { + ret = sysfs_create_groups(&component->dev->kobj, hpload_groups); + if (ret) + return ret; + + cs43130->wq = create_singlethread_workqueue("cs43130_hp"); + if (!cs43130->wq) { + sysfs_remove_groups(&component->dev->kobj, hpload_groups); + return -ENOMEM; + } + INIT_WORK(&cs43130->work, cs43130_imp_meas); + } + + regmap_read(cs43130->regmap, CS43130_INT_STATUS_1, ®); + regmap_read(cs43130->regmap, CS43130_HP_STATUS, ®); + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_HP_PLUG_INT | CS43130_HP_UNPLUG_INT, 0); + regmap_update_bits(cs43130->regmap, CS43130_HP_DETECT, + CS43130_HP_DETECT_CTRL_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_HP_DETECT, + CS43130_HP_DETECT_CTRL_MASK, + CS43130_HP_DETECT_CTRL_MASK); + + return 0; +} + +static struct snd_soc_component_driver soc_component_dev_cs43130 = { + .probe = cs43130_probe, + .controls = cs43130_snd_controls, + .num_controls = ARRAY_SIZE(cs43130_snd_controls), + .set_sysclk = cs43130_component_set_sysclk, + .set_pll = cs43130_set_pll, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config cs43130_regmap = { + .reg_bits = 24, + .pad_bits = 8, + .val_bits = 8, + + .max_register = CS43130_LASTREG, + .reg_defaults = cs43130_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs43130_reg_defaults), + .readable_reg = cs43130_readable_register, + .precious_reg = cs43130_precious_register, + .volatile_reg = cs43130_volatile_register, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, /* needed for regcache_sync */ +}; + +static u16 const cs43130_dc_threshold[CS43130_DC_THRESHOLD] = { + 50, + 120, +}; + +static int cs43130_handle_device_data(struct i2c_client *i2c_client, + struct cs43130_private *cs43130) +{ + struct device_node *np = i2c_client->dev.of_node; + unsigned int val; + int i; + + if (of_property_read_u32(np, "cirrus,xtal-ibias", &val) < 0) { + /* Crystal is unused. System clock is used for external MCLK */ + cs43130->xtal_ibias = CS43130_XTAL_UNUSED; + return 0; + } + + switch (val) { + case 1: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_7_5UA; + break; + case 2: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_12_5UA; + break; + case 3: + cs43130->xtal_ibias = CS43130_XTAL_IBIAS_15UA; + break; + default: + dev_err(&i2c_client->dev, + "Invalid cirrus,xtal-ibias value: %d\n", val); + return -EINVAL; + } + + cs43130->dc_meas = of_property_read_bool(np, "cirrus,dc-measure"); + cs43130->ac_meas = of_property_read_bool(np, "cirrus,ac-measure"); + + if (of_property_read_u16_array(np, "cirrus,ac-freq", cs43130->ac_freq, + CS43130_AC_FREQ) < 0) { + for (i = 0; i < CS43130_AC_FREQ; i++) + cs43130->ac_freq[i] = cs43130_ac_freq[i]; + } + + if (of_property_read_u16_array(np, "cirrus,dc-threshold", + cs43130->dc_threshold, + CS43130_DC_THRESHOLD) < 0) { + for (i = 0; i < CS43130_DC_THRESHOLD; i++) + cs43130->dc_threshold[i] = cs43130_dc_threshold[i]; + } + + return 0; +} + +static int cs43130_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs43130_private *cs43130; + int ret; + unsigned int devid = 0; + unsigned int reg; + int i; + + cs43130 = devm_kzalloc(&client->dev, sizeof(*cs43130), GFP_KERNEL); + if (!cs43130) + return -ENOMEM; + + i2c_set_clientdata(client, cs43130); + + cs43130->regmap = devm_regmap_init_i2c(client, &cs43130_regmap); + if (IS_ERR(cs43130->regmap)) { + ret = PTR_ERR(cs43130->regmap); + return ret; + } + + if (client->dev.of_node) { + ret = cs43130_handle_device_data(client, cs43130); + if (ret != 0) + return ret; + } + for (i = 0; i < ARRAY_SIZE(cs43130->supplies); i++) + cs43130->supplies[i].supply = cs43130_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(cs43130->supplies), + cs43130->supplies); + if (ret != 0) { + dev_err(&client->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + ret = regulator_bulk_enable(ARRAY_SIZE(cs43130->supplies), + cs43130->supplies); + if (ret != 0) { + dev_err(&client->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + cs43130->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(cs43130->reset_gpio)) + return PTR_ERR(cs43130->reset_gpio); + + gpiod_set_value_cansleep(cs43130->reset_gpio, 1); + + usleep_range(2000, 2050); + + ret = regmap_read(cs43130->regmap, CS43130_DEVID_AB, ®); + + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs43130->regmap, CS43130_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs43130->regmap, CS43130_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + switch (devid) { + case CS43130_CHIP_ID: + case CS4399_CHIP_ID: + case CS43131_CHIP_ID: + case CS43198_CHIP_ID: + break; + default: + dev_err(&client->dev, + "CS43130 Device ID %X. Expected ID %X, %X, %X or %X\n", + devid, CS43130_CHIP_ID, CS4399_CHIP_ID, + CS43131_CHIP_ID, CS43198_CHIP_ID); + ret = -ENODEV; + goto err; + } + + cs43130->dev_id = devid; + ret = regmap_read(cs43130->regmap, CS43130_REV_ID, ®); + if (ret < 0) { + dev_err(&client->dev, "Get Revision ID failed\n"); + goto err; + } + + dev_info(&client->dev, + "Cirrus Logic CS43130 (%x), Revision: %02X\n", devid, + reg & 0xFF); + + mutex_init(&cs43130->clk_mutex); + + init_completion(&cs43130->xtal_rdy); + init_completion(&cs43130->pll_rdy); + init_completion(&cs43130->hpload_evt); + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, cs43130_irq_thread, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs43130", cs43130); + if (ret != 0) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", ret); + return ret; + } + + cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO; + + pm_runtime_set_autosuspend_delay(&client->dev, 100); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + switch (cs43130->dev_id) { + case CS43130_CHIP_ID: + case CS43131_CHIP_ID: + memcpy(all_hp_widgets, digital_hp_widgets, + sizeof(digital_hp_widgets)); + memcpy(all_hp_widgets + ARRAY_SIZE(digital_hp_widgets), + analog_hp_widgets, sizeof(analog_hp_widgets)); + memcpy(all_hp_routes, digital_hp_routes, + sizeof(digital_hp_routes)); + memcpy(all_hp_routes + ARRAY_SIZE(digital_hp_routes), + analog_hp_routes, sizeof(analog_hp_routes)); + + soc_component_dev_cs43130.dapm_widgets = + all_hp_widgets; + soc_component_dev_cs43130.num_dapm_widgets = + ARRAY_SIZE(all_hp_widgets); + soc_component_dev_cs43130.dapm_routes = + all_hp_routes; + soc_component_dev_cs43130.num_dapm_routes = + ARRAY_SIZE(all_hp_routes); + break; + case CS43198_CHIP_ID: + case CS4399_CHIP_ID: + soc_component_dev_cs43130.dapm_widgets = + digital_hp_widgets; + soc_component_dev_cs43130.num_dapm_widgets = + ARRAY_SIZE(digital_hp_widgets); + soc_component_dev_cs43130.dapm_routes = + digital_hp_routes; + soc_component_dev_cs43130.num_dapm_routes = + ARRAY_SIZE(digital_hp_routes); + break; + } + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_dev_cs43130, + cs43130_dai, ARRAY_SIZE(cs43130_dai)); + if (ret < 0) { + dev_err(&client->dev, + "snd_soc_register_component failed with ret = %d\n", ret); + goto err; + } + + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_ASP_3ST_MASK, 0); + regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG, + CS43130_XSP_3ST_MASK, 0); + + return 0; +err: + return ret; +} + +static int cs43130_i2c_remove(struct i2c_client *client) +{ + struct cs43130_private *cs43130 = i2c_get_clientdata(client); + + if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED) + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_ERR_INT, + 1 << CS43130_XTAL_ERR_INT_SHIFT); + + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_HP_PLUG_INT | CS43130_HP_UNPLUG_INT, + CS43130_HP_PLUG_INT | CS43130_HP_UNPLUG_INT); + + if (cs43130->dc_meas) { + cancel_work_sync(&cs43130->work); + flush_workqueue(cs43130->wq); + + device_remove_file(&client->dev, &dev_attr_hpload_dc_l); + device_remove_file(&client->dev, &dev_attr_hpload_dc_r); + device_remove_file(&client->dev, &dev_attr_hpload_ac_l); + device_remove_file(&client->dev, &dev_attr_hpload_ac_r); + } + + gpiod_set_value_cansleep(cs43130->reset_gpio, 0); + + pm_runtime_disable(&client->dev); + regulator_bulk_disable(CS43130_NUM_SUPPLIES, cs43130->supplies); + + return 0; +} + +static int __maybe_unused cs43130_runtime_suspend(struct device *dev) +{ + struct cs43130_private *cs43130 = dev_get_drvdata(dev); + + if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED) + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_ERR_INT, + 1 << CS43130_XTAL_ERR_INT_SHIFT); + + regcache_cache_only(cs43130->regmap, true); + regcache_mark_dirty(cs43130->regmap); + + gpiod_set_value_cansleep(cs43130->reset_gpio, 0); + + regulator_bulk_disable(CS43130_NUM_SUPPLIES, cs43130->supplies); + + return 0; +} + +static int __maybe_unused cs43130_runtime_resume(struct device *dev) +{ + struct cs43130_private *cs43130 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(CS43130_NUM_SUPPLIES, cs43130->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(cs43130->regmap, false); + + gpiod_set_value_cansleep(cs43130->reset_gpio, 1); + + usleep_range(2000, 2050); + + ret = regcache_sync(cs43130->regmap); + if (ret != 0) { + dev_err(dev, "Failed to restore register cache\n"); + goto err; + } + + if (cs43130->xtal_ibias != CS43130_XTAL_UNUSED) + regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1, + CS43130_XTAL_ERR_INT, 0); + + return 0; +err: + regcache_cache_only(cs43130->regmap, true); + regulator_bulk_disable(CS43130_NUM_SUPPLIES, cs43130->supplies); + + return ret; +} + +static const struct dev_pm_ops cs43130_runtime_pm = { + SET_RUNTIME_PM_OPS(cs43130_runtime_suspend, cs43130_runtime_resume, + NULL) +}; + +static const struct of_device_id cs43130_of_match[] = { + {.compatible = "cirrus,cs43130",}, + {.compatible = "cirrus,cs4399",}, + {.compatible = "cirrus,cs43131",}, + {.compatible = "cirrus,cs43198",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, cs43130_of_match); + +static const struct i2c_device_id cs43130_i2c_id[] = { + {"cs43130", 0}, + {"cs4399", 0}, + {"cs43131", 0}, + {"cs43198", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs43130_i2c_id); + +static struct i2c_driver cs43130_i2c_driver = { + .driver = { + .name = "cs43130", + .of_match_table = cs43130_of_match, + .pm = &cs43130_runtime_pm, + }, + .id_table = cs43130_i2c_id, + .probe = cs43130_i2c_probe, + .remove = cs43130_i2c_remove, +}; + +module_i2c_driver(cs43130_i2c_driver); + +MODULE_AUTHOR("Li Xu "); +MODULE_DESCRIPTION("Cirrus Logic CS43130 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs43130.h b/sound/soc/codecs/cs43130.h new file mode 100644 index 000000000..c3c6eef61 --- /dev/null +++ b/sound/soc/codecs/cs43130.h @@ -0,0 +1,546 @@ +/* + * ALSA SoC CS43130 codec driver + * + * Copyright 2017 Cirrus Logic, Inc. + * + * Author: Li Xu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#ifndef __CS43130_H__ +#define __CS43130_H__ + +/* CS43130 registers addresses */ +/* all reg address is shifted by a byte for control byte to be LSB */ +#define CS43130_FIRSTREG 0x010000 +#define CS43130_LASTREG 0x190000 +#define CS43130_CHIP_ID 0x00043130 +#define CS4399_CHIP_ID 0x00043990 +#define CS43131_CHIP_ID 0x00043131 +#define CS43198_CHIP_ID 0x00043198 +#define CS43130_DEVID_AB 0x010000 /* Device ID A & B [RO] */ +#define CS43130_DEVID_CD 0x010001 /* Device ID C & D [RO] */ +#define CS43130_DEVID_E 0x010002 /* Device ID E [RO] */ +#define CS43130_FAB_ID 0x010003 /* Fab ID [RO] */ +#define CS43130_REV_ID 0x010004 /* Revision ID [RO] */ +#define CS43130_SUBREV_ID 0x010005 /* Subrevision ID */ +#define CS43130_SYS_CLK_CTL_1 0x010006 /* System Clocking Ctl 1 */ +#define CS43130_SP_SRATE 0x01000B /* Serial Port Sample Rate */ +#define CS43130_SP_BITSIZE 0x01000C /* Serial Port Bit Size */ +#define CS43130_PAD_INT_CFG 0x01000D /* Pad Interface Config */ +#define CS43130_DXD1 0x010010 /* DXD1 */ +#define CS43130_DXD7 0x010025 /* DXD7 */ +#define CS43130_DXD19 0x010026 /* DXD19 */ +#define CS43130_DXD17 0x010027 /* DXD17 */ +#define CS43130_DXD18 0x010028 /* DXD18 */ +#define CS43130_DXD12 0x01002C /* DXD12 */ +#define CS43130_DXD8 0x01002E /* DXD8 */ +#define CS43130_PWDN_CTL 0x020000 /* Power Down Ctl */ +#define CS43130_DXD2 0x020019 /* DXD2 */ +#define CS43130_CRYSTAL_SET 0x020052 /* Crystal Setting */ +#define CS43130_PLL_SET_1 0x030001 /* PLL Setting 1 */ +#define CS43130_PLL_SET_2 0x030002 /* PLL Setting 2 */ +#define CS43130_PLL_SET_3 0x030003 /* PLL Setting 3 */ +#define CS43130_PLL_SET_4 0x030004 /* PLL Setting 4 */ +#define CS43130_PLL_SET_5 0x030005 /* PLL Setting 5 */ +#define CS43130_PLL_SET_6 0x030008 /* PLL Setting 6 */ +#define CS43130_PLL_SET_7 0x03000A /* PLL Setting 7 */ +#define CS43130_PLL_SET_8 0x03001B /* PLL Setting 8 */ +#define CS43130_PLL_SET_9 0x040002 /* PLL Setting 9 */ +#define CS43130_PLL_SET_10 0x040003 /* PLL Setting 10 */ +#define CS43130_CLKOUT_CTL 0x040004 /* CLKOUT Ctl */ +#define CS43130_ASP_NUM_1 0x040010 /* ASP Numerator 1 */ +#define CS43130_ASP_NUM_2 0x040011 /* ASP Numerator 2 */ +#define CS43130_ASP_DEN_1 0x040012 /* ASP Denominator 1 */ +#define CS43130_ASP_DEN_2 0x040013 /* ASP Denominator 2 */ +#define CS43130_ASP_LRCK_HI_TIME_1 0x040014 /* ASP LRCK High Time 1 */ +#define CS43130_ASP_LRCK_HI_TIME_2 0x040015 /* ASP LRCK High Time 2 */ +#define CS43130_ASP_LRCK_PERIOD_1 0x040016 /* ASP LRCK Period 1 */ +#define CS43130_ASP_LRCK_PERIOD_2 0x040017 /* ASP LRCK Period 2 */ +#define CS43130_ASP_CLOCK_CONF 0x040018 /* ASP Clock Config */ +#define CS43130_ASP_FRAME_CONF 0x040019 /* ASP Frame Config */ +#define CS43130_XSP_NUM_1 0x040020 /* XSP Numerator 1 */ +#define CS43130_XSP_NUM_2 0x040021 /* XSP Numerator 2 */ +#define CS43130_XSP_DEN_1 0x040022 /* XSP Denominator 1 */ +#define CS43130_XSP_DEN_2 0x040023 /* XSP Denominator 2 */ +#define CS43130_XSP_LRCK_HI_TIME_1 0x040024 /* XSP LRCK High Time 1 */ +#define CS43130_XSP_LRCK_HI_TIME_2 0x040025 /* XSP LRCK High Time 2 */ +#define CS43130_XSP_LRCK_PERIOD_1 0x040026 /* XSP LRCK Period 1 */ +#define CS43130_XSP_LRCK_PERIOD_2 0x040027 /* XSP LRCK Period 2 */ +#define CS43130_XSP_CLOCK_CONF 0x040028 /* XSP Clock Config */ +#define CS43130_XSP_FRAME_CONF 0x040029 /* XSP Frame Config */ +#define CS43130_ASP_CH_1_LOC 0x050000 /* ASP Chan 1 Location */ +#define CS43130_ASP_CH_2_LOC 0x050001 /* ASP Chan 2 Location */ +#define CS43130_ASP_CH_1_SZ_EN 0x05000A /* ASP Chan 1 Size, Enable */ +#define CS43130_ASP_CH_2_SZ_EN 0x05000B /* ASP Chan 2 Size, Enable */ +#define CS43130_XSP_CH_1_LOC 0x060000 /* XSP Chan 1 Location */ +#define CS43130_XSP_CH_2_LOC 0x060001 /* XSP Chan 2 Location */ +#define CS43130_XSP_CH_1_SZ_EN 0x06000A /* XSP Chan 1 Size, Enable */ +#define CS43130_XSP_CH_2_SZ_EN 0x06000B /* XSP Chan 2 Size, Enable */ +#define CS43130_DSD_VOL_B 0x070000 /* DSD Volume B */ +#define CS43130_DSD_VOL_A 0x070001 /* DSD Volume A */ +#define CS43130_DSD_PATH_CTL_1 0x070002 /* DSD Proc Path Sig Ctl 1 */ +#define CS43130_DSD_INT_CFG 0x070003 /* DSD Interface Config */ +#define CS43130_DSD_PATH_CTL_2 0x070004 /* DSD Proc Path Sig Ctl 2 */ +#define CS43130_DSD_PCM_MIX_CTL 0x070005 /* DSD and PCM Mixing Ctl */ +#define CS43130_DSD_PATH_CTL_3 0x070006 /* DSD Proc Path Sig Ctl 3 */ +#define CS43130_HP_OUT_CTL_1 0x080000 /* HP Output Ctl 1 */ +#define CS43130_DXD16 0x080024 /* DXD16 */ +#define CS43130_DXD13 0x080032 /* DXD13 */ +#define CS43130_PCM_FILT_OPT 0x090000 /* PCM Filter Option */ +#define CS43130_PCM_VOL_B 0x090001 /* PCM Volume B */ +#define CS43130_PCM_VOL_A 0x090002 /* PCM Volume A */ +#define CS43130_PCM_PATH_CTL_1 0x090003 /* PCM Path Signal Ctl 1 */ +#define CS43130_PCM_PATH_CTL_2 0x090004 /* PCM Path Signal Ctl 2 */ +#define CS43130_DXD6 0x090097 /* DXD6 */ +#define CS43130_CLASS_H_CTL 0x0B0000 /* Class H Ctl */ +#define CS43130_DXD15 0x0B0005 /* DXD15 */ +#define CS43130_DXD14 0x0B0006 /* DXD14 */ +#define CS43130_DXD3 0x0C0002 /* DXD3 */ +#define CS43130_DXD10 0x0C0003 /* DXD10 */ +#define CS43130_DXD11 0x0C0005 /* DXD11 */ +#define CS43130_DXD9 0x0C0006 /* DXD9 */ +#define CS43130_DXD4 0x0C0009 /* DXD4 */ +#define CS43130_DXD5 0x0C000E /* DXD5 */ +#define CS43130_HP_DETECT 0x0D0000 /* HP Detect */ +#define CS43130_HP_STATUS 0x0D0001 /* HP Status [RO] */ +#define CS43130_HP_LOAD_1 0x0E0000 /* HP Load 1 */ +#define CS43130_HP_MEAS_LOAD_1 0x0E0003 /* HP Load Measurement 1 */ +#define CS43130_HP_MEAS_LOAD_2 0x0E0004 /* HP Load Measurement 2 */ +#define CS43130_HP_DC_STAT_1 0x0E000D /* HP DC Load Status 0 [RO] */ +#define CS43130_HP_DC_STAT_2 0x0E000E /* HP DC Load Status 1 [RO] */ +#define CS43130_HP_AC_STAT_1 0x0E0010 /* HP AC Load Status 0 [RO] */ +#define CS43130_HP_AC_STAT_2 0x0E0011 /* HP AC Load Status 1 [RO] */ +#define CS43130_HP_LOAD_STAT 0x0E001A /* HP Load Status [RO] */ +#define CS43130_INT_STATUS_1 0x0F0000 /* Interrupt Status 1 */ +#define CS43130_INT_STATUS_2 0x0F0001 /* Interrupt Status 2 */ +#define CS43130_INT_STATUS_3 0x0F0002 /* Interrupt Status 3 */ +#define CS43130_INT_STATUS_4 0x0F0003 /* Interrupt Status 4 */ +#define CS43130_INT_STATUS_5 0x0F0004 /* Interrupt Status 5 */ +#define CS43130_INT_MASK_1 0x0F0010 /* Interrupt Mask 1 */ +#define CS43130_INT_MASK_2 0x0F0011 /* Interrupt Mask 2 */ +#define CS43130_INT_MASK_3 0x0F0012 /* Interrupt Mask 3 */ +#define CS43130_INT_MASK_4 0x0F0013 /* Interrupt Mask 4 */ +#define CS43130_INT_MASK_5 0x0F0014 /* Interrupt Mask 5 */ + +#define CS43130_MCLK_SRC_SEL_MASK 0x03 +#define CS43130_MCLK_SRC_SEL_SHIFT 0 +#define CS43130_MCLK_INT_MASK 0x04 +#define CS43130_MCLK_INT_SHIFT 2 +#define CS43130_CH_BITSIZE_MASK 0x03 +#define CS43130_CH_EN_MASK 0x04 +#define CS43130_CH_EN_SHIFT 2 +#define CS43130_ASP_BITSIZE_MASK 0x03 +#define CS43130_XSP_BITSIZE_MASK 0x0C +#define CS43130_XSP_BITSIZE_SHIFT 2 +#define CS43130_SP_BITSIZE_ASP_SHIFT 0 +#define CS43130_HP_DETECT_CTRL_SHIFT 6 +#define CS43130_HP_DETECT_CTRL_MASK (0x03 << CS43130_HP_DETECT_CTRL_SHIFT) +#define CS43130_HP_DETECT_INV_SHIFT 5 +#define CS43130_HP_DETECT_INV_MASK (1 << CS43130_HP_DETECT_INV_SHIFT) + +/* CS43130_INT_MASK_1 */ +#define CS43130_HP_PLUG_INT_SHIFT 6 +#define CS43130_HP_PLUG_INT (1 << CS43130_HP_PLUG_INT_SHIFT) +#define CS43130_HP_UNPLUG_INT_SHIFT 5 +#define CS43130_HP_UNPLUG_INT (1 << CS43130_HP_UNPLUG_INT_SHIFT) +#define CS43130_XTAL_RDY_INT_SHIFT 4 +#define CS43130_XTAL_RDY_INT_MASK 0x10 +#define CS43130_XTAL_RDY_INT (1 << CS43130_XTAL_RDY_INT_SHIFT) +#define CS43130_XTAL_ERR_INT_SHIFT 3 +#define CS43130_XTAL_ERR_INT (1 << CS43130_XTAL_ERR_INT_SHIFT) +#define CS43130_PLL_RDY_INT_MASK 0x04 +#define CS43130_PLL_RDY_INT_SHIFT 2 +#define CS43130_PLL_RDY_INT (1 << CS43130_PLL_RDY_INT_SHIFT) + +/* CS43130_INT_MASK_4 */ +#define CS43130_INT_MASK_ALL 0xFF +#define CS43130_HPLOAD_NO_DC_INT_SHIFT 7 +#define CS43130_HPLOAD_NO_DC_INT (1 << CS43130_HPLOAD_NO_DC_INT_SHIFT) +#define CS43130_HPLOAD_UNPLUG_INT_SHIFT 6 +#define CS43130_HPLOAD_UNPLUG_INT (1 << CS43130_HPLOAD_UNPLUG_INT_SHIFT) +#define CS43130_HPLOAD_OOR_INT_SHIFT 4 +#define CS43130_HPLOAD_OOR_INT (1 << CS43130_HPLOAD_OOR_INT_SHIFT) +#define CS43130_HPLOAD_AC_INT_SHIFT 3 +#define CS43130_HPLOAD_AC_INT (1 << CS43130_HPLOAD_AC_INT_SHIFT) +#define CS43130_HPLOAD_DC_INT_SHIFT 2 +#define CS43130_HPLOAD_DC_INT (1 << CS43130_HPLOAD_DC_INT_SHIFT) +#define CS43130_HPLOAD_OFF_INT_SHIFT 1 +#define CS43130_HPLOAD_OFF_INT (1 << CS43130_HPLOAD_OFF_INT_SHIFT) +#define CS43130_HPLOAD_ON_INT 1 + +/* CS43130_HP_LOAD_1 */ +#define CS43130_HPLOAD_EN_SHIFT 7 +#define CS43130_HPLOAD_EN (1 << CS43130_HPLOAD_EN_SHIFT) +#define CS43130_HPLOAD_CHN_SEL_SHIFT 4 +#define CS43130_HPLOAD_CHN_SEL (1 << CS43130_HPLOAD_CHN_SEL_SHIFT) +#define CS43130_HPLOAD_AC_START_SHIFT 1 +#define CS43130_HPLOAD_AC_START (1 << CS43130_HPLOAD_AC_START_SHIFT) +#define CS43130_HPLOAD_DC_START 1 + +/* Reg CS43130_SP_BITSIZE */ +#define CS43130_SP_BIT_SIZE_8 0x03 +#define CS43130_SP_BIT_SIZE_16 0x02 +#define CS43130_SP_BIT_SIZE_24 0x01 +#define CS43130_SP_BIT_SIZE_32 0x00 + +/* Reg CS43130_SP_CH_SZ_EN */ +#define CS43130_CH_BIT_SIZE_8 0x00 +#define CS43130_CH_BIT_SIZE_16 0x01 +#define CS43130_CH_BIT_SIZE_24 0x02 +#define CS43130_CH_BIT_SIZE_32 0x03 + +/* PLL */ +#define CS43130_PLL_START_MASK 0x01 +#define CS43130_PLL_MODE_MASK 0x02 +#define CS43130_PLL_MODE_SHIFT 1 + +#define CS43130_PLL_REF_PREDIV_MASK 0x3 + +#define CS43130_SP_STP_MASK 0x10 +#define CS43130_SP_STP_SHIFT 4 +#define CS43130_SP_5050_MASK 0x08 +#define CS43130_SP_5050_SHIFT 3 +#define CS43130_SP_FSD_MASK 0x07 + +#define CS43130_SP_MODE_MASK 0x10 +#define CS43130_SP_MODE_SHIFT 4 +#define CS43130_SP_SCPOL_OUT_MASK 0x08 +#define CS43130_SP_SCPOL_OUT_SHIFT 3 +#define CS43130_SP_SCPOL_IN_MASK 0x04 +#define CS43130_SP_SCPOL_IN_SHIFT 2 +#define CS43130_SP_LCPOL_OUT_MASK 0x02 +#define CS43130_SP_LCPOL_OUT_SHIFT 1 +#define CS43130_SP_LCPOL_IN_MASK 0x01 +#define CS43130_SP_LCPOL_IN_SHIFT 0 + +/* Reg CS43130_PWDN_CTL */ +#define CS43130_PDN_XSP_MASK 0x80 +#define CS43130_PDN_XSP_SHIFT 7 +#define CS43130_PDN_ASP_MASK 0x40 +#define CS43130_PDN_ASP_SHIFT 6 +#define CS43130_PDN_DSPIF_MASK 0x20 +#define CS43130_PDN_DSDIF_SHIFT 5 +#define CS43130_PDN_HP_MASK 0x10 +#define CS43130_PDN_HP_SHIFT 4 +#define CS43130_PDN_XTAL_MASK 0x08 +#define CS43130_PDN_XTAL_SHIFT 3 +#define CS43130_PDN_PLL_MASK 0x04 +#define CS43130_PDN_PLL_SHIFT 2 +#define CS43130_PDN_CLKOUT_MASK 0x02 +#define CS43130_PDN_CLKOUT_SHIFT 1 + +/* Reg CS43130_HP_OUT_CTL_1 */ +#define CS43130_HP_IN_EN_SHIFT 3 +#define CS43130_HP_IN_EN_MASK 0x08 + +/* Reg CS43130_PAD_INT_CFG */ +#define CS43130_ASP_3ST_MASK 0x01 +#define CS43130_XSP_3ST_MASK 0x02 + +/* Reg CS43130_PLL_SET_2 */ +#define CS43130_PLL_DIV_DATA_MASK 0x000000FF +#define CS43130_PLL_DIV_FRAC_0_DATA_SHIFT 0 + +/* Reg CS43130_PLL_SET_3 */ +#define CS43130_PLL_DIV_FRAC_1_DATA_SHIFT 8 + +/* Reg CS43130_PLL_SET_4 */ +#define CS43130_PLL_DIV_FRAC_2_DATA_SHIFT 16 + +/* Reg CS43130_SP_DEN_1 */ +#define CS43130_SP_M_LSB_DATA_MASK 0x00FF +#define CS43130_SP_M_LSB_DATA_SHIFT 0 + +/* Reg CS43130_SP_DEN_2 */ +#define CS43130_SP_M_MSB_DATA_MASK 0xFF00 +#define CS43130_SP_M_MSB_DATA_SHIFT 8 + +/* Reg CS43130_SP_NUM_1 */ +#define CS43130_SP_N_LSB_DATA_MASK 0x00FF +#define CS43130_SP_N_LSB_DATA_SHIFT 0 + +/* Reg CS43130_SP_NUM_2 */ +#define CS43130_SP_N_MSB_DATA_MASK 0xFF00 +#define CS43130_SP_N_MSB_DATA_SHIFT 8 + +/* Reg CS43130_SP_LRCK_HI_TIME_1 */ +#define CS43130_SP_LCHI_DATA_MASK 0x00FF +#define CS43130_SP_LCHI_LSB_DATA_SHIFT 0 + +/* Reg CS43130_SP_LRCK_HI_TIME_2 */ +#define CS43130_SP_LCHI_MSB_DATA_SHIFT 8 + +/* Reg CS43130_SP_LRCK_PERIOD_1 */ +#define CS43130_SP_LCPR_DATA_MASK 0x00FF +#define CS43130_SP_LCPR_LSB_DATA_SHIFT 0 + +/* Reg CS43130_SP_LRCK_PERIOD_2 */ +#define CS43130_SP_LCPR_MSB_DATA_SHIFT 8 + +#define CS43130_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define CS43130_DOP_FORMATS (SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U16_BE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* Reg CS43130_CRYSTAL_SET */ +#define CS43130_XTAL_IBIAS_MASK 0x07 + +/* Reg CS43130_PATH_CTL_1 */ +#define CS43130_MUTE_MASK 0x03 +#define CS43130_MUTE_EN 0x03 + +/* Reg CS43130_DSD_INT_CFG */ +#define CS43130_DSD_MASTER 0x04 + +/* Reg CS43130_DSD_PATH_CTL_2 */ +#define CS43130_DSD_SRC_MASK 0x60 +#define CS43130_DSD_SRC_SHIFT 5 +#define CS43130_DSD_EN_SHIFT 4 +#define CS43130_DSD_SPEED_MASK 0x04 +#define CS43130_DSD_SPEED_SHIFT 2 + +/* Reg CS43130_DSD_PCM_MIX_CTL */ +#define CS43130_MIX_PCM_PREP_SHIFT 1 +#define CS43130_MIX_PCM_PREP_MASK 0x02 + +#define CS43130_MIX_PCM_DSD_SHIFT 0 +#define CS43130_MIX_PCM_DSD_MASK 0x01 + +/* Reg CS43130_HP_MEAS_LOAD */ +#define CS43130_HP_MEAS_LOAD_MASK 0x000000FF +#define CS43130_HP_MEAS_LOAD_1_SHIFT 0 +#define CS43130_HP_MEAS_LOAD_2_SHIFT 8 + +#define CS43130_MCLK_22M 22579200 +#define CS43130_MCLK_24M 24576000 + +#define CS43130_LINEOUT_LOAD 5000 +#define CS43130_JACK_LINEOUT (SND_JACK_MECHANICAL | SND_JACK_LINEOUT) +#define CS43130_JACK_HEADPHONE (SND_JACK_MECHANICAL | \ + SND_JACK_HEADPHONE) +#define CS43130_JACK_MASK (SND_JACK_MECHANICAL | \ + SND_JACK_LINEOUT | \ + SND_JACK_HEADPHONE) + +enum cs43130_dsd_src { + CS43130_DSD_SRC_DSD = 0, + CS43130_DSD_SRC_ASP = 2, + CS43130_DSD_SRC_XSP = 3, +}; + +enum cs43130_asp_rate { + CS43130_ASP_SPRATE_32K = 0, + CS43130_ASP_SPRATE_44_1K, + CS43130_ASP_SPRATE_48K, + CS43130_ASP_SPRATE_88_2K, + CS43130_ASP_SPRATE_96K, + CS43130_ASP_SPRATE_176_4K, + CS43130_ASP_SPRATE_192K, + CS43130_ASP_SPRATE_352_8K, + CS43130_ASP_SPRATE_384K, +}; + +enum cs43130_mclk_src_sel { + CS43130_MCLK_SRC_EXT = 0, + CS43130_MCLK_SRC_PLL, + CS43130_MCLK_SRC_RCO +}; + +enum cs43130_mclk_int_freq { + CS43130_MCLK_24P5 = 0, + CS43130_MCLK_22P5, +}; + +enum cs43130_xtal_ibias { + CS43130_XTAL_UNUSED = -1, + CS43130_XTAL_IBIAS_15UA = 2, + CS43130_XTAL_IBIAS_12_5UA = 4, + CS43130_XTAL_IBIAS_7_5UA = 6, +}; + +enum cs43130_dai_id { + CS43130_ASP_PCM_DAI = 0, + CS43130_ASP_DOP_DAI, + CS43130_XSP_DOP_DAI, + CS43130_XSP_DSD_DAI, + CS43130_DAI_ID_MAX, +}; + +struct cs43130_clk_gen { + unsigned int mclk_int; + int fs; + u16 den; + u16 num; +}; + +/* frm_size = 16 */ +static const struct cs43130_clk_gen cs43130_16_clk_gen[] = { + {22579200, 32000, 441, 10,}, + {22579200, 44100, 32, 1,}, + {22579200, 48000, 147, 5,}, + {22579200, 88200, 16, 1,}, + {22579200, 96000, 147, 10,}, + {22579200, 176400, 8, 1,}, + {22579200, 192000, 147, 20,}, + {22579200, 352800, 4, 1,}, + {22579200, 384000, 147, 40,}, + {24576000, 32000, 48, 1,}, + {24576000, 44100, 5120, 147,}, + {24576000, 48000, 32, 1,}, + {24576000, 88200, 2560, 147,}, + {24576000, 96000, 16, 1,}, + {24576000, 176400, 1280, 147,}, + {24576000, 192000, 8, 1,}, + {24576000, 352800, 640, 147,}, + {24576000, 384000, 4, 1,}, +}; + +/* frm_size = 32 */ +static const struct cs43130_clk_gen cs43130_32_clk_gen[] = { + {22579200, 32000, 441, 20,}, + {22579200, 44100, 16, 1,}, + {22579200, 48000, 147, 10,}, + {22579200, 88200, 8, 1,}, + {22579200, 96000, 147, 20,}, + {22579200, 176400, 4, 1,}, + {22579200, 192000, 147, 40,}, + {22579200, 352800, 2, 1,}, + {22579200, 384000, 147, 80,}, + {24576000, 32000, 24, 1,}, + {24576000, 44100, 2560, 147,}, + {24576000, 48000, 16, 1,}, + {24576000, 88200, 1280, 147,}, + {24576000, 96000, 8, 1,}, + {24576000, 176400, 640, 147,}, + {24576000, 192000, 4, 1,}, + {24576000, 352800, 320, 147,}, + {24576000, 384000, 2, 1,}, +}; + +/* frm_size = 48 */ +static const struct cs43130_clk_gen cs43130_48_clk_gen[] = { + {22579200, 32000, 147, 100,}, + {22579200, 44100, 32, 3,}, + {22579200, 48000, 49, 5,}, + {22579200, 88200, 16, 3,}, + {22579200, 96000, 49, 10,}, + {22579200, 176400, 8, 3,}, + {22579200, 192000, 49, 20,}, + {22579200, 352800, 4, 3,}, + {22579200, 384000, 49, 40,}, + {24576000, 32000, 16, 1,}, + {24576000, 44100, 5120, 441,}, + {24576000, 48000, 32, 3,}, + {24576000, 88200, 2560, 441,}, + {24576000, 96000, 16, 3,}, + {24576000, 176400, 1280, 441,}, + {24576000, 192000, 8, 3,}, + {24576000, 352800, 640, 441,}, + {24576000, 384000, 4, 3,}, +}; + +/* frm_size = 64 */ +static const struct cs43130_clk_gen cs43130_64_clk_gen[] = { + {22579200, 32000, 441, 40,}, + {22579200, 44100, 8, 1,}, + {22579200, 48000, 147, 20,}, + {22579200, 88200, 4, 1,}, + {22579200, 96000, 147, 40,}, + {22579200, 176400, 2, 1,}, + {22579200, 192000, 147, 80,}, + {22579200, 352800, 1, 1,}, + {24576000, 32000, 12, 1,}, + {24576000, 44100, 1280, 147,}, + {24576000, 48000, 8, 1,}, + {24576000, 88200, 640, 147,}, + {24576000, 96000, 4, 1,}, + {24576000, 176400, 320, 147,}, + {24576000, 192000, 2, 1,}, + {24576000, 352800, 160, 147,}, + {24576000, 384000, 1, 1,}, +}; + +struct cs43130_bitwidth_map { + unsigned int bitwidth; + u8 sp_bit; + u8 ch_bit; +}; + +struct cs43130_rate_map { + int fs; + int val; +}; + +#define HP_LEFT 0 +#define HP_RIGHT 1 +#define CS43130_AC_FREQ 10 +#define CS43130_DC_THRESHOLD 2 + +#define CS43130_NUM_SUPPLIES 5 +static const char *const cs43130_supply_names[CS43130_NUM_SUPPLIES] = { + "VA", + "VP", + "VCP", + "VD", + "VL", +}; + +#define CS43130_NUM_INT 5 /* number of interrupt status reg */ + +struct cs43130_dai { + unsigned int sclk; + unsigned int dai_format; + unsigned int dai_mode; +}; + +struct cs43130_private { + struct snd_soc_component *component; + struct regmap *regmap; + struct regulator_bulk_data supplies[CS43130_NUM_SUPPLIES]; + struct gpio_desc *reset_gpio; + unsigned int dev_id; /* codec device ID */ + int xtal_ibias; + + /* shared by both DAIs */ + struct mutex clk_mutex; + int clk_req; + bool pll_bypass; + struct completion xtal_rdy; + struct completion pll_rdy; + unsigned int mclk; + unsigned int mclk_int; + int mclk_int_src; + + /* DAI specific */ + struct cs43130_dai dais[CS43130_DAI_ID_MAX]; + + /* HP load specific */ + bool dc_meas; + bool ac_meas; + bool hpload_done; + struct completion hpload_evt; + unsigned int hpload_stat; + u16 hpload_dc[2]; + u16 dc_threshold[CS43130_DC_THRESHOLD]; + u16 ac_freq[CS43130_AC_FREQ]; + u16 hpload_ac[CS43130_AC_FREQ][2]; + struct workqueue_struct *wq; + struct work_struct work; + struct snd_soc_jack jack; +}; + +#endif /* __CS43130_H__ */ diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c new file mode 100644 index 000000000..f08d7a296 --- /dev/null +++ b/sound/soc/codecs/cs4349.c @@ -0,0 +1,395 @@ +/* + * cs4349.c -- CS4349 ALSA Soc Audio driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Authors: Tim Howe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs4349.h" + + +static const struct reg_default cs4349_reg_defaults[] = { + { 2, 0x00 }, /* r02 - Mode Control */ + { 3, 0x09 }, /* r03 - Volume, Mixing and Inversion Control */ + { 4, 0x81 }, /* r04 - Mute Control */ + { 5, 0x00 }, /* r05 - Channel A Volume Control */ + { 6, 0x00 }, /* r06 - Channel B Volume Control */ + { 7, 0xB1 }, /* r07 - Ramp and Filter Control */ + { 8, 0x1C }, /* r08 - Misc. Control */ +}; + +/* Private data for the CS4349 */ +struct cs4349_private { + struct regmap *regmap; + struct gpio_desc *reset_gpio; + unsigned int mode; + int rate; +}; + +static bool cs4349_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS4349_CHIPID ... CS4349_MISC: + return true; + default: + return false; + } +} + +static bool cs4349_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS4349_MODE ... CS4349_MISC: + return true; + default: + return false; + } +} + +static int cs4349_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct cs4349_private *cs4349 = snd_soc_component_get_drvdata(component); + unsigned int fmt; + + fmt = format & SND_SOC_DAIFMT_FORMAT_MASK; + + switch (fmt) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + cs4349->mode = format & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cs4349_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct cs4349_private *cs4349 = snd_soc_component_get_drvdata(component); + int fmt, ret; + + cs4349->rate = params_rate(params); + + switch (cs4349->mode) { + case SND_SOC_DAIFMT_I2S: + fmt = DIF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + fmt = DIF_LEFT_JST; + break; + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_width(params)) { + case 16: + fmt = DIF_RGHT_JST16; + break; + case 24: + fmt = DIF_RGHT_JST24; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, CS4349_MODE, DIF_MASK, + MODE_FORMAT(fmt)); + if (ret < 0) + return ret; + + return 0; +} + +static int cs4349_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + int reg; + + reg = 0; + if (mute) + reg = MUTE_AB_MASK; + + return snd_soc_component_update_bits(component, CS4349_MUTE, MUTE_AB_MASK, reg); +} + +static DECLARE_TLV_DB_SCALE(dig_tlv, -12750, 50, 0); + +static const char * const chan_mix_texts[] = { + "Mute", "MuteA", "MuteA SwapB", "MuteA MonoB", "SwapA MuteB", + "BothR", "Swap", "SwapA MonoB", "MuteB", "Normal", "BothL", + "MonoB", "MonoA MuteB", "MonoA", "MonoA SwapB", "Mono", + /*Normal == Channel A = Left, Channel B = Right*/ +}; + +static const char * const fm_texts[] = { + "Auto", "Single", "Double", "Quad", +}; + +static const char * const deemph_texts[] = { + "None", "44.1k", "48k", "32k", +}; + +static const char * const softr_zeroc_texts[] = { + "Immediate", "Zero Cross", "Soft Ramp", "SR on ZC", +}; + +static int deemph_values[] = { + 0, 4, 8, 12, +}; + +static int softr_zeroc_values[] = { + 0, 64, 128, 192, +}; + +static const struct soc_enum chan_mix_enum = + SOC_ENUM_SINGLE(CS4349_VMI, 0, + ARRAY_SIZE(chan_mix_texts), + chan_mix_texts); + +static const struct soc_enum fm_mode_enum = + SOC_ENUM_SINGLE(CS4349_MODE, 0, + ARRAY_SIZE(fm_texts), + fm_texts); + +static SOC_VALUE_ENUM_SINGLE_DECL(deemph_enum, CS4349_MODE, 0, DEM_MASK, + deemph_texts, deemph_values); + +static SOC_VALUE_ENUM_SINGLE_DECL(softr_zeroc_enum, CS4349_RMPFLT, 0, + SR_ZC_MASK, softr_zeroc_texts, + softr_zeroc_values); + +static const struct snd_kcontrol_new cs4349_snd_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", + CS4349_VOLA, CS4349_VOLB, 0, 0xFF, 1, dig_tlv), + SOC_ENUM("Functional Mode", fm_mode_enum), + SOC_ENUM("De-Emphasis Control", deemph_enum), + SOC_ENUM("Soft Ramp Zero Cross Control", softr_zeroc_enum), + SOC_ENUM("Channel Mixer", chan_mix_enum), + SOC_SINGLE("VolA = VolB Switch", CS4349_VMI, 7, 1, 0), + SOC_SINGLE("InvertA Switch", CS4349_VMI, 6, 1, 0), + SOC_SINGLE("InvertB Switch", CS4349_VMI, 5, 1, 0), + SOC_SINGLE("Auto-Mute Switch", CS4349_MUTE, 7, 1, 0), + SOC_SINGLE("MUTEC A = B Switch", CS4349_MUTE, 5, 1, 0), + SOC_SINGLE("Soft Ramp Up Switch", CS4349_RMPFLT, 5, 1, 0), + SOC_SINGLE("Soft Ramp Down Switch", CS4349_RMPFLT, 4, 1, 0), + SOC_SINGLE("Slow Roll Off Filter Switch", CS4349_RMPFLT, 2, 1, 0), + SOC_SINGLE("Freeze Switch", CS4349_MISC, 5, 1, 0), + SOC_SINGLE("Popguard Switch", CS4349_MISC, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget cs4349_dapm_widgets[] = { + SND_SOC_DAPM_DAC("HiFi DAC", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_OUTPUT("OutputA"), + SND_SOC_DAPM_OUTPUT("OutputB"), +}; + +static const struct snd_soc_dapm_route cs4349_routes[] = { + {"DAC Playback", NULL, "OutputA"}, + {"DAC Playback", NULL, "OutputB"}, + + {"OutputA", NULL, "HiFi DAC"}, + {"OutputB", NULL, "HiFi DAC"}, +}; + +#define CS4349_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define CS4349_PCM_RATES SNDRV_PCM_RATE_8000_192000 + +static const struct snd_soc_dai_ops cs4349_dai_ops = { + .hw_params = cs4349_pcm_hw_params, + .set_fmt = cs4349_set_dai_fmt, + .digital_mute = cs4349_digital_mute, +}; + +static struct snd_soc_dai_driver cs4349_dai = { + .name = "cs4349_hifi", + .playback = { + .stream_name = "DAC Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS4349_PCM_RATES, + .formats = CS4349_PCM_FORMATS, + }, + .ops = &cs4349_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver soc_component_dev_cs4349 = { + .controls = cs4349_snd_controls, + .num_controls = ARRAY_SIZE(cs4349_snd_controls), + .dapm_widgets = cs4349_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs4349_dapm_widgets), + .dapm_routes = cs4349_routes, + .num_dapm_routes = ARRAY_SIZE(cs4349_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config cs4349_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS4349_MISC, + .reg_defaults = cs4349_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs4349_reg_defaults), + .readable_reg = cs4349_readable_register, + .writeable_reg = cs4349_writeable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs4349_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cs4349_private *cs4349; + int ret; + + cs4349 = devm_kzalloc(&client->dev, sizeof(*cs4349), GFP_KERNEL); + if (!cs4349) + return -ENOMEM; + + cs4349->regmap = devm_regmap_init_i2c(client, &cs4349_regmap); + if (IS_ERR(cs4349->regmap)) { + ret = PTR_ERR(cs4349->regmap); + dev_err(&client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + /* Reset the Device */ + cs4349->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(cs4349->reset_gpio)) + return PTR_ERR(cs4349->reset_gpio); + + gpiod_set_value_cansleep(cs4349->reset_gpio, 1); + + i2c_set_clientdata(client, cs4349); + + return devm_snd_soc_register_component(&client->dev, + &soc_component_dev_cs4349, + &cs4349_dai, 1); +} + +static int cs4349_i2c_remove(struct i2c_client *client) +{ + struct cs4349_private *cs4349 = i2c_get_clientdata(client); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs4349->reset_gpio, 0); + + return 0; +} + +#ifdef CONFIG_PM +static int cs4349_runtime_suspend(struct device *dev) +{ + struct cs4349_private *cs4349 = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(cs4349->regmap, CS4349_MISC, PWR_DWN, PWR_DWN); + if (ret < 0) + return ret; + + regcache_cache_only(cs4349->regmap, true); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs4349->reset_gpio, 0); + + return 0; +} + +static int cs4349_runtime_resume(struct device *dev) +{ + struct cs4349_private *cs4349 = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(cs4349->regmap, CS4349_MISC, PWR_DWN, 0); + if (ret < 0) + return ret; + + gpiod_set_value_cansleep(cs4349->reset_gpio, 1); + + regcache_cache_only(cs4349->regmap, false); + regcache_sync(cs4349->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops cs4349_runtime_pm = { + SET_RUNTIME_PM_OPS(cs4349_runtime_suspend, cs4349_runtime_resume, + NULL) +}; + +static const struct of_device_id cs4349_of_match[] = { + { .compatible = "cirrus,cs4349", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cs4349_of_match); + +static const struct i2c_device_id cs4349_i2c_id[] = { + {"cs4349", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs4349_i2c_id); + +static struct i2c_driver cs4349_i2c_driver = { + .driver = { + .name = "cs4349", + .of_match_table = cs4349_of_match, + .pm = &cs4349_runtime_pm, + }, + .id_table = cs4349_i2c_id, + .probe = cs4349_i2c_probe, + .remove = cs4349_i2c_remove, +}; + +module_i2c_driver(cs4349_i2c_driver); + +MODULE_AUTHOR("Tim Howe "); +MODULE_DESCRIPTION("Cirrus Logic CS4349 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4349.h b/sound/soc/codecs/cs4349.h new file mode 100644 index 000000000..d58c06a25 --- /dev/null +++ b/sound/soc/codecs/cs4349.h @@ -0,0 +1,136 @@ +/* + * ALSA SoC CS4349 codec driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Author: Tim Howe + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#ifndef __CS4349_H__ +#define __CS4349_H__ + +/* CS4349 registers addresses */ +#define CS4349_CHIPID 0x01 /* Device and Rev ID, Read Only */ +#define CS4349_MODE 0x02 /* Mode Control */ +#define CS4349_VMI 0x03 /* Volume, Mixing, Inversion Control */ +#define CS4349_MUTE 0x04 /* Mute Control */ +#define CS4349_VOLA 0x05 /* DAC Channel A Volume Control */ +#define CS4349_VOLB 0x06 /* DAC Channel B Volume Control */ +#define CS4349_RMPFLT 0x07 /* Ramp and Filter Control */ +#define CS4349_MISC 0x08 /* Power Down,Freeze Control,Pop Stop*/ + +#define CS4349_I2C_INCR 0x80 + + +/* Device and Revision ID */ +#define CS4349_REVA 0xF0 /* Rev A */ +#define CS4349_REVB 0xF1 /* Rev B */ +#define CS4349_REVC2 0xFF /* Rev C2 */ + + +/* PDN_DONE Poll Maximum + * If soft ramp is set it will take much longer to power down + * the system. + */ +#define PDN_POLL_MAX 900 + + +/* Bitfield Definitions */ + +/* CS4349_MODE */ +/* (Digital Interface Format, De-Emphasis Control, Functional Mode */ +#define DIF2 (1 << 6) +#define DIF1 (1 << 5) +#define DIF0 (1 << 4) +#define DEM1 (1 << 3) +#define DEM0 (1 << 2) +#define FM1 (1 << 1) +#define DIF_LEFT_JST 0x00 +#define DIF_I2S 0x01 +#define DIF_RGHT_JST16 0x02 +#define DIF_RGHT_JST24 0x03 +#define DIF_TDM0 0x04 +#define DIF_TDM1 0x05 +#define DIF_TDM2 0x06 +#define DIF_TDM3 0x07 +#define DIF_MASK 0x70 +#define MODE_FORMAT(x) (((x)&7)<<4) +#define DEM_MASK 0x0C +#define NO_DEM 0x00 +#define DEM_441 0x04 +#define DEM_48K 0x08 +#define DEM_32K 0x0C +#define FM_AUTO 0x00 +#define FM_SNGL 0x01 +#define FM_DBL 0x02 +#define FM_QUAD 0x03 +#define FM_SNGL_MIN 30000 +#define FM_SNGL_MAX 54000 +#define FM_DBL_MAX 108000 +#define FM_QUAD_MAX 216000 +#define FM_MASK 0x03 + +/* CS4349_VMI (VMI = Volume, Mixing and Inversion Controls) */ +#define VOLBISA (1 << 7) +#define VOLAISB (1 << 7) +/* INVERT_A only available for Left Jstfd, Right Jstfd16 and Right Jstfd24 */ +#define INVERT_A (1 << 6) +/* INVERT_B only available for Left Jstfd, Right Jstfd16 and Right Jstfd24 */ +#define INVERT_B (1 << 5) +#define ATAPI3 (1 << 3) +#define ATAPI2 (1 << 2) +#define ATAPI1 (1 << 1) +#define ATAPI0 (1 << 0) +#define MUTEAB 0x00 +#define MUTEA_RIGHTB 0x01 +#define MUTEA_LEFTB 0x02 +#define MUTEA_SUMLRDIV2B 0x03 +#define RIGHTA_MUTEB 0x04 +#define RIGHTA_RIGHTB 0x05 +#define RIGHTA_LEFTB 0x06 +#define RIGHTA_SUMLRDIV2B 0x07 +#define LEFTA_MUTEB 0x08 +#define LEFTA_RIGHTB 0x09 /* Default */ +#define LEFTA_LEFTB 0x0A +#define LEFTA_SUMLRDIV2B 0x0B +#define SUMLRDIV2A_MUTEB 0x0C +#define SUMLRDIV2A_RIGHTB 0x0D +#define SUMLRDIV2A_LEFTB 0x0E +#define SUMLRDIV2_AB 0x0F +#define CHMIX_MASK 0x0F + +/* CS4349_MUTE */ +#define AUTOMUTE (1 << 7) +#define MUTEC_AB (1 << 5) +#define MUTE_A (1 << 4) +#define MUTE_B (1 << 3) +#define MUTE_AB_MASK 0x18 + +/* CS4349_RMPFLT (Ramp and Filter Control) */ +#define SCZ1 (1 << 7) +#define SCZ0 (1 << 6) +#define RMP_UP (1 << 5) +#define RMP_DN (1 << 4) +#define FILT_SEL (1 << 2) +#define IMMDT_CHNG 0x31 +#define ZEROCRSS 0x71 +#define SOFT_RMP 0xB1 +#define SFTRMP_ZEROCRSS 0xF1 +#define SR_ZC_MASK 0xC0 + +/* CS4349_MISC */ +#define PWR_DWN (1 << 7) +#define FREEZE (1 << 5) +#define POPG_EN (1 << 4) + +#endif /* __CS4349_H__ */ diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c new file mode 100644 index 000000000..45e50fe3b --- /dev/null +++ b/sound/soc/codecs/cs47l24.c @@ -0,0 +1,1355 @@ +/* + * cs47l24.h -- ALSA SoC Audio driver for Cirrus Logic CS47L24 + * + * Copyright 2015 Cirrus Logic Inc. + * + * Author: Richard Fitzgerald + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arizona.h" +#include "wm_adsp.h" +#include "cs47l24.h" + +#define DRV_NAME "cs47l24-codec" + +struct cs47l24_priv { + struct arizona_priv core; + struct arizona_fll fll[2]; +}; + +static const struct wm_adsp_region cs47l24_dsp2_regions[] = { + { .type = WMFW_ADSP2_PM, .base = 0x200000 }, + { .type = WMFW_ADSP2_ZM, .base = 0x280000 }, + { .type = WMFW_ADSP2_XM, .base = 0x290000 }, + { .type = WMFW_ADSP2_YM, .base = 0x2a8000 }, +}; + +static const struct wm_adsp_region cs47l24_dsp3_regions[] = { + { .type = WMFW_ADSP2_PM, .base = 0x300000 }, + { .type = WMFW_ADSP2_ZM, .base = 0x380000 }, + { .type = WMFW_ADSP2_XM, .base = 0x390000 }, + { .type = WMFW_ADSP2_YM, .base = 0x3a8000 }, +}; + +static const struct wm_adsp_region *cs47l24_dsp_regions[] = { + cs47l24_dsp2_regions, + cs47l24_dsp3_regions, +}; + +static int cs47l24_adsp_power_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + unsigned int v; + int ret; + + ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &v); + if (ret != 0) { + dev_err(component->dev, "Failed to read SYSCLK state: %d\n", ret); + return ret; + } + + v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT; + + return wm_adsp2_early_event(w, kcontrol, event, v); +} + +static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); +static DECLARE_TLV_DB_SCALE(noise_tlv, -13200, 600, 0); +static DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); + +#define CS47L24_NG_SRC(name, base) \ + SOC_SINGLE(name " NG HPOUT1L Switch", base, 0, 1, 0), \ + SOC_SINGLE(name " NG HPOUT1R Switch", base, 1, 1, 0), \ + SOC_SINGLE(name " NG SPKOUT Switch", base, 6, 1, 0) + +static const struct snd_kcontrol_new cs47l24_snd_controls[] = { +SOC_ENUM("IN1 OSR", arizona_in_dmic_osr[0]), +SOC_ENUM("IN2 OSR", arizona_in_dmic_osr[1]), + +SOC_ENUM("IN HPF Cutoff Frequency", arizona_in_hpf_cut_enum), + +SOC_SINGLE("IN1L HPF Switch", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1L_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN1R HPF Switch", ARIZONA_IN1R_CONTROL, + ARIZONA_IN1R_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN2L HPF Switch", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2L_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN2R HPF Switch", ARIZONA_IN2R_CONTROL, + ARIZONA_IN2R_HPF_SHIFT, 1, 0), + +SOC_SINGLE_TLV("IN1L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1L, + ARIZONA_IN1L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN1R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1R, + ARIZONA_IN1R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2L, + ARIZONA_IN2L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2R, + ARIZONA_IN2R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), + +SOC_ENUM("Input Ramp Up", arizona_in_vi_ramp), +SOC_ENUM("Input Ramp Down", arizona_in_vd_ramp), + +ARIZONA_MIXER_CONTROLS("EQ1", ARIZONA_EQ1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ2", ARIZONA_EQ2MIX_INPUT_1_SOURCE), + +ARIZONA_EQ_CONTROL("EQ1 Coefficients", ARIZONA_EQ1_2), +SOC_SINGLE_TLV("EQ1 B1 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B2 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B3 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B4 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B5 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ2 Coefficients", ARIZONA_EQ2_2), +SOC_SINGLE_TLV("EQ2 B1 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B2 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B3 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B4 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B5 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_MIXER_CONTROLS("DRC1L", ARIZONA_DRC1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC1R", ARIZONA_DRC1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC2L", ARIZONA_DRC2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC2R", ARIZONA_DRC2RMIX_INPUT_1_SOURCE), + +SND_SOC_BYTES_MASK("DRC1", ARIZONA_DRC1_CTRL1, 5, + ARIZONA_DRC1R_ENA | ARIZONA_DRC1L_ENA), +SND_SOC_BYTES_MASK("DRC2", ARIZONA_DRC2_CTRL1, 5, + ARIZONA_DRC2R_ENA | ARIZONA_DRC2L_ENA), + +ARIZONA_MIXER_CONTROLS("LHPF1", ARIZONA_HPLP1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF2", ARIZONA_HPLP2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF3", ARIZONA_HPLP3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF4", ARIZONA_HPLP4MIX_INPUT_1_SOURCE), + +ARIZONA_LHPF_CONTROL("LHPF1 Coefficients", ARIZONA_HPLPF1_2), +ARIZONA_LHPF_CONTROL("LHPF2 Coefficients", ARIZONA_HPLPF2_2), +ARIZONA_LHPF_CONTROL("LHPF3 Coefficients", ARIZONA_HPLPF3_2), +ARIZONA_LHPF_CONTROL("LHPF4 Coefficients", ARIZONA_HPLPF4_2), + +SOC_ENUM("LHPF1 Mode", arizona_lhpf1_mode), +SOC_ENUM("LHPF2 Mode", arizona_lhpf2_mode), +SOC_ENUM("LHPF3 Mode", arizona_lhpf3_mode), +SOC_ENUM("LHPF4 Mode", arizona_lhpf4_mode), + +SOC_ENUM("ISRC1 FSL", arizona_isrc_fsl[0]), +SOC_ENUM("ISRC2 FSL", arizona_isrc_fsl[1]), +SOC_ENUM("ISRC3 FSL", arizona_isrc_fsl[2]), +SOC_ENUM("ISRC1 FSH", arizona_isrc_fsh[0]), +SOC_ENUM("ISRC2 FSH", arizona_isrc_fsh[1]), +SOC_ENUM("ISRC3 FSH", arizona_isrc_fsh[2]), +SOC_ENUM("ASRC RATE 1", arizona_asrc_rate1), + +WM_ADSP2_PRELOAD_SWITCH("DSP2", 2), +WM_ADSP2_PRELOAD_SWITCH("DSP3", 3), + +ARIZONA_MIXER_CONTROLS("DSP2L", ARIZONA_DSP2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP2R", ARIZONA_DSP2RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP3L", ARIZONA_DSP3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP3R", ARIZONA_DSP3RMIX_INPUT_1_SOURCE), + +SOC_SINGLE_TLV("Noise Generator Volume", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_GAIN_SHIFT, 0x16, 0, noise_tlv), + +ARIZONA_MIXER_CONTROLS("HPOUT1L", ARIZONA_OUT1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT1R", ARIZONA_OUT1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUT", ARIZONA_OUT4LMIX_INPUT_1_SOURCE), + +SOC_SINGLE("HPOUT1 SC Protect Switch", ARIZONA_HP1_SHORT_CIRCUIT_CTRL, + ARIZONA_HP1_SC_ENA_SHIFT, 1, 0), + +SOC_DOUBLE_R("HPOUT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_MUTE_SHIFT, 1, 1), +SOC_SINGLE("Speaker Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_OUT4L_MUTE_SHIFT, 1, 1), + +SOC_DOUBLE_R_TLV("HPOUT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("Speaker Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_OUT4L_VOL_SHIFT, 0xbf, 0, digital_tlv), + +SOC_ENUM("Output Ramp Up", arizona_out_vi_ramp), +SOC_ENUM("Output Ramp Down", arizona_out_vd_ramp), + +SOC_SINGLE("Noise Gate Switch", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_ENA_SHIFT, 1, 0), +SOC_SINGLE_TLV("Noise Gate Threshold Volume", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_THR_SHIFT, 7, 1, ng_tlv), +SOC_ENUM("Noise Gate Hold", arizona_ng_hold), + +CS47L24_NG_SRC("HPOUT1L", ARIZONA_NOISE_GATE_SELECT_1L), +CS47L24_NG_SRC("HPOUT1R", ARIZONA_NOISE_GATE_SELECT_1R), +CS47L24_NG_SRC("SPKOUT", ARIZONA_NOISE_GATE_SELECT_4L), + +ARIZONA_MIXER_CONTROLS("AIF1TX1", ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX2", ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX3", ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX4", ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX5", ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX6", ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX7", ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX8", ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF2TX1", ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX2", ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX3", ARIZONA_AIF2TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX4", ARIZONA_AIF2TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX5", ARIZONA_AIF2TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX6", ARIZONA_AIF2TX6MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF3TX1", ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF3TX2", ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE), + +WM_ADSP_FW_CONTROL("DSP2", 1), +WM_ADSP_FW_CONTROL("DSP3", 2), +}; + +ARIZONA_MIXER_ENUMS(EQ1, ARIZONA_EQ1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ2, ARIZONA_EQ2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DRC1L, ARIZONA_DRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC1R, ARIZONA_DRC1RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC2L, ARIZONA_DRC2LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC2R, ARIZONA_DRC2RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(LHPF1, ARIZONA_HPLP1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF2, ARIZONA_HPLP2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF3, ARIZONA_HPLP3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF4, ARIZONA_HPLP4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DSP2L, ARIZONA_DSP2LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DSP2R, ARIZONA_DSP2RMIX_INPUT_1_SOURCE); +ARIZONA_DSP_AUX_ENUMS(DSP2, ARIZONA_DSP2AUX1MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DSP3L, ARIZONA_DSP3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DSP3R, ARIZONA_DSP3RMIX_INPUT_1_SOURCE); +ARIZONA_DSP_AUX_ENUMS(DSP3, ARIZONA_DSP3AUX1MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(PWM1, ARIZONA_PWM1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(PWM2, ARIZONA_PWM2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(OUT1L, ARIZONA_OUT1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT1R, ARIZONA_OUT1RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUT, ARIZONA_OUT4LMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF1TX1, ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX2, ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX3, ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX4, ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX5, ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX6, ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX7, ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX8, ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF2TX1, ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX2, ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX3, ARIZONA_AIF2TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX4, ARIZONA_AIF2TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX5, ARIZONA_AIF2TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX6, ARIZONA_AIF2TX6MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF3TX1, ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF3TX2, ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ASRC1L, ARIZONA_ASRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC1R, ARIZONA_ASRC1RMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2L, ARIZONA_ASRC2LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2R, ARIZONA_ASRC2RMIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1INT1, ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT2, ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT3, ARIZONA_ISRC1INT3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT4, ARIZONA_ISRC1INT4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1DEC1, ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC2, ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC3, ARIZONA_ISRC1DEC3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC4, ARIZONA_ISRC1DEC4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2INT1, ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT2, ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT3, ARIZONA_ISRC2INT3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT4, ARIZONA_ISRC2INT4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2DEC1, ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC2, ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC3, ARIZONA_ISRC2DEC3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC4, ARIZONA_ISRC2DEC4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC3INT1, ARIZONA_ISRC3INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3INT2, ARIZONA_ISRC3INT2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3INT3, ARIZONA_ISRC3INT3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3INT4, ARIZONA_ISRC3INT4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC3DEC1, ARIZONA_ISRC3DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3DEC2, ARIZONA_ISRC3DEC2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3DEC3, ARIZONA_ISRC3DEC3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3DEC4, ARIZONA_ISRC3DEC4MIX_INPUT_1_SOURCE); + +static const char * const cs47l24_aec_loopback_texts[] = { + "HPOUT1L", "HPOUT1R", "SPKOUT", +}; + +static const unsigned int cs47l24_aec_loopback_values[] = { + 0, 1, 6, +}; + +static const struct soc_enum cs47l24_aec_loopback = + SOC_VALUE_ENUM_SINGLE(ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_SRC_SHIFT, 0xf, + ARRAY_SIZE(cs47l24_aec_loopback_texts), + cs47l24_aec_loopback_texts, + cs47l24_aec_loopback_values); + +static const struct snd_kcontrol_new cs47l24_aec_loopback_mux = + SOC_DAPM_ENUM("AEC Loopback", cs47l24_aec_loopback); + +static const struct snd_soc_dapm_widget cs47l24_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, + ARIZONA_SYSCLK_ENA_SHIFT, 0, arizona_clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1, + ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK, + ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK, + ARIZONA_OPCLK_ASYNC_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, SND_SOC_DAPM_REGULATOR_BYPASS), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDD", 0, 0), + +SND_SOC_DAPM_SIGGEN("TONE"), +SND_SOC_DAPM_SIGGEN("NOISE"), +SND_SOC_DAPM_SIGGEN("HAPTICS"), + +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), + +SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), +SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"), + +SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"), + +SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0, + &arizona_voice_trigger_switch[2]), + +SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN1R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_SUPPLY("MICBIAS1", ARIZONA_MIC_BIAS_CTRL_1, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", ARIZONA_MIC_BIAS_CTRL_2, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Noise Generator", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Tone Generator 1", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("Tone Generator 2", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE2_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("EQ1", ARIZONA_EQ1_1, ARIZONA_EQ1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ2", ARIZONA_EQ2_1, ARIZONA_EQ2_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("DRC1L", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC1R", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1R_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC2L", ARIZONA_DRC2_CTRL1, ARIZONA_DRC2L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC2R", ARIZONA_DRC2_CTRL1, ARIZONA_DRC2R_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", ARIZONA_HPLPF1_1, ARIZONA_LHPF1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", ARIZONA_HPLPF2_1, ARIZONA_LHPF2_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF3", ARIZONA_HPLPF3_1, ARIZONA_LHPF3_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF4", ARIZONA_HPLPF4_1, ARIZONA_LHPF4_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("PWM1 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM1_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_PGA("PWM2 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM2_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_PGA("ASRC1L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC1R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1R_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC2L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC2R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2R_ENA_SHIFT, 0, + NULL, 0), + +WM_ADSP2("DSP2", 1, cs47l24_adsp_power_ev), +WM_ADSP2("DSP3", 2, cs47l24_adsp_power_ev), + +SND_SOC_DAPM_PGA("ISRC1INT1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT3", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT4", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1DEC1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC3", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC4", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2INT1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT3", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT4", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2DEC1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC3", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC4", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC3INT1", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3INT2", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3INT3", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3INT4", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC3DEC1", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3DEC2", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3DEC3", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3DEC4", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_MUX("AEC Loopback", ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_ENA_SHIFT, 0, &cs47l24_aec_loopback_mux), + +SND_SOC_DAPM_AIF_OUT("AIF1TX1", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX5", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX6", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX7", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX8", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX1", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX5", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX6", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX7", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX8", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF2TX1", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX2", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX3", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX4", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX5", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX6", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF2RX1", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX2", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX3", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX4", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX5", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX6", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF3TX1", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF3TX2", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF3RX1", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF3RX2", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_PGA_E("OUT1L", SND_SOC_NOPM, + ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT1R", SND_SOC_NOPM, + ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + +ARIZONA_MIXER_WIDGETS(EQ1, "EQ1"), +ARIZONA_MIXER_WIDGETS(EQ2, "EQ2"), + +ARIZONA_MIXER_WIDGETS(DRC1L, "DRC1L"), +ARIZONA_MIXER_WIDGETS(DRC1R, "DRC1R"), +ARIZONA_MIXER_WIDGETS(DRC2L, "DRC2L"), +ARIZONA_MIXER_WIDGETS(DRC2R, "DRC2R"), + +ARIZONA_MIXER_WIDGETS(LHPF1, "LHPF1"), +ARIZONA_MIXER_WIDGETS(LHPF2, "LHPF2"), +ARIZONA_MIXER_WIDGETS(LHPF3, "LHPF3"), +ARIZONA_MIXER_WIDGETS(LHPF4, "LHPF4"), + +ARIZONA_MIXER_WIDGETS(PWM1, "PWM1"), +ARIZONA_MIXER_WIDGETS(PWM2, "PWM2"), + +ARIZONA_MIXER_WIDGETS(OUT1L, "HPOUT1L"), +ARIZONA_MIXER_WIDGETS(OUT1R, "HPOUT1R"), +ARIZONA_MIXER_WIDGETS(SPKOUT, "SPKOUT"), + +ARIZONA_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"), +ARIZONA_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"), +ARIZONA_MIXER_WIDGETS(AIF1TX3, "AIF1TX3"), +ARIZONA_MIXER_WIDGETS(AIF1TX4, "AIF1TX4"), +ARIZONA_MIXER_WIDGETS(AIF1TX5, "AIF1TX5"), +ARIZONA_MIXER_WIDGETS(AIF1TX6, "AIF1TX6"), +ARIZONA_MIXER_WIDGETS(AIF1TX7, "AIF1TX7"), +ARIZONA_MIXER_WIDGETS(AIF1TX8, "AIF1TX8"), + +ARIZONA_MIXER_WIDGETS(AIF2TX1, "AIF2TX1"), +ARIZONA_MIXER_WIDGETS(AIF2TX2, "AIF2TX2"), +ARIZONA_MIXER_WIDGETS(AIF2TX3, "AIF2TX3"), +ARIZONA_MIXER_WIDGETS(AIF2TX4, "AIF2TX4"), +ARIZONA_MIXER_WIDGETS(AIF2TX5, "AIF2TX5"), +ARIZONA_MIXER_WIDGETS(AIF2TX6, "AIF2TX6"), + +ARIZONA_MIXER_WIDGETS(AIF3TX1, "AIF3TX1"), +ARIZONA_MIXER_WIDGETS(AIF3TX2, "AIF3TX2"), + +ARIZONA_MUX_WIDGETS(ASRC1L, "ASRC1L"), +ARIZONA_MUX_WIDGETS(ASRC1R, "ASRC1R"), +ARIZONA_MUX_WIDGETS(ASRC2L, "ASRC2L"), +ARIZONA_MUX_WIDGETS(ASRC2R, "ASRC2R"), + +ARIZONA_DSP_WIDGETS(DSP2, "DSP2"), +ARIZONA_DSP_WIDGETS(DSP3, "DSP3"), + +ARIZONA_MUX_WIDGETS(ISRC1DEC1, "ISRC1DEC1"), +ARIZONA_MUX_WIDGETS(ISRC1DEC2, "ISRC1DEC2"), +ARIZONA_MUX_WIDGETS(ISRC1DEC3, "ISRC1DEC3"), +ARIZONA_MUX_WIDGETS(ISRC1DEC4, "ISRC1DEC4"), + +ARIZONA_MUX_WIDGETS(ISRC1INT1, "ISRC1INT1"), +ARIZONA_MUX_WIDGETS(ISRC1INT2, "ISRC1INT2"), +ARIZONA_MUX_WIDGETS(ISRC1INT3, "ISRC1INT3"), +ARIZONA_MUX_WIDGETS(ISRC1INT4, "ISRC1INT4"), + +ARIZONA_MUX_WIDGETS(ISRC2DEC1, "ISRC2DEC1"), +ARIZONA_MUX_WIDGETS(ISRC2DEC2, "ISRC2DEC2"), +ARIZONA_MUX_WIDGETS(ISRC2DEC3, "ISRC2DEC3"), +ARIZONA_MUX_WIDGETS(ISRC2DEC4, "ISRC2DEC4"), + +ARIZONA_MUX_WIDGETS(ISRC2INT1, "ISRC2INT1"), +ARIZONA_MUX_WIDGETS(ISRC2INT2, "ISRC2INT2"), +ARIZONA_MUX_WIDGETS(ISRC2INT3, "ISRC2INT3"), +ARIZONA_MUX_WIDGETS(ISRC2INT4, "ISRC2INT4"), + +ARIZONA_MUX_WIDGETS(ISRC3DEC1, "ISRC3DEC1"), +ARIZONA_MUX_WIDGETS(ISRC3DEC2, "ISRC3DEC2"), +ARIZONA_MUX_WIDGETS(ISRC3DEC3, "ISRC3DEC3"), +ARIZONA_MUX_WIDGETS(ISRC3DEC4, "ISRC3DEC4"), + +ARIZONA_MUX_WIDGETS(ISRC3INT1, "ISRC3INT1"), +ARIZONA_MUX_WIDGETS(ISRC3INT2, "ISRC3INT2"), +ARIZONA_MUX_WIDGETS(ISRC3INT3, "ISRC3INT3"), +ARIZONA_MUX_WIDGETS(ISRC3INT4, "ISRC3INT4"), + +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), + +SND_SOC_DAPM_OUTPUT("MICSUPP"), +}; + +#define ARIZONA_MIXER_INPUT_ROUTES(name) \ + { name, "Noise Generator", "Noise Generator" }, \ + { name, "Tone Generator 1", "Tone Generator 1" }, \ + { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "Haptics", "HAPTICS" }, \ + { name, "AEC", "AEC Loopback" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2L PGA" }, \ + { name, "IN2R", "IN2R PGA" }, \ + { name, "AIF1RX1", "AIF1RX1" }, \ + { name, "AIF1RX2", "AIF1RX2" }, \ + { name, "AIF1RX3", "AIF1RX3" }, \ + { name, "AIF1RX4", "AIF1RX4" }, \ + { name, "AIF1RX5", "AIF1RX5" }, \ + { name, "AIF1RX6", "AIF1RX6" }, \ + { name, "AIF1RX7", "AIF1RX7" }, \ + { name, "AIF1RX8", "AIF1RX8" }, \ + { name, "AIF2RX1", "AIF2RX1" }, \ + { name, "AIF2RX2", "AIF2RX2" }, \ + { name, "AIF2RX3", "AIF2RX3" }, \ + { name, "AIF2RX4", "AIF2RX4" }, \ + { name, "AIF2RX5", "AIF2RX5" }, \ + { name, "AIF2RX6", "AIF2RX6" }, \ + { name, "AIF3RX1", "AIF3RX1" }, \ + { name, "AIF3RX2", "AIF3RX2" }, \ + { name, "EQ1", "EQ1" }, \ + { name, "EQ2", "EQ2" }, \ + { name, "DRC1L", "DRC1L" }, \ + { name, "DRC1R", "DRC1R" }, \ + { name, "DRC2L", "DRC2L" }, \ + { name, "DRC2R", "DRC2R" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" }, \ + { name, "LHPF3", "LHPF3" }, \ + { name, "LHPF4", "LHPF4" }, \ + { name, "ASRC1L", "ASRC1L" }, \ + { name, "ASRC1R", "ASRC1R" }, \ + { name, "ASRC2L", "ASRC2L" }, \ + { name, "ASRC2R", "ASRC2R" }, \ + { name, "ISRC1DEC1", "ISRC1DEC1" }, \ + { name, "ISRC1DEC2", "ISRC1DEC2" }, \ + { name, "ISRC1DEC3", "ISRC1DEC3" }, \ + { name, "ISRC1DEC4", "ISRC1DEC4" }, \ + { name, "ISRC1INT1", "ISRC1INT1" }, \ + { name, "ISRC1INT2", "ISRC1INT2" }, \ + { name, "ISRC1INT3", "ISRC1INT3" }, \ + { name, "ISRC1INT4", "ISRC1INT4" }, \ + { name, "ISRC2DEC1", "ISRC2DEC1" }, \ + { name, "ISRC2DEC2", "ISRC2DEC2" }, \ + { name, "ISRC2DEC3", "ISRC2DEC3" }, \ + { name, "ISRC2DEC4", "ISRC2DEC4" }, \ + { name, "ISRC2INT1", "ISRC2INT1" }, \ + { name, "ISRC2INT2", "ISRC2INT2" }, \ + { name, "ISRC2INT3", "ISRC2INT3" }, \ + { name, "ISRC2INT4", "ISRC2INT4" }, \ + { name, "ISRC3DEC1", "ISRC3DEC1" }, \ + { name, "ISRC3DEC2", "ISRC3DEC2" }, \ + { name, "ISRC3DEC3", "ISRC3DEC3" }, \ + { name, "ISRC3DEC4", "ISRC3DEC4" }, \ + { name, "ISRC3INT1", "ISRC3INT1" }, \ + { name, "ISRC3INT2", "ISRC3INT2" }, \ + { name, "ISRC3INT3", "ISRC3INT3" }, \ + { name, "ISRC3INT4", "ISRC3INT4" }, \ + { name, "DSP2.1", "DSP2" }, \ + { name, "DSP2.2", "DSP2" }, \ + { name, "DSP2.3", "DSP2" }, \ + { name, "DSP2.4", "DSP2" }, \ + { name, "DSP2.5", "DSP2" }, \ + { name, "DSP2.6", "DSP2" }, \ + { name, "DSP3.1", "DSP3" }, \ + { name, "DSP3.2", "DSP3" }, \ + { name, "DSP3.3", "DSP3" }, \ + { name, "DSP3.4", "DSP3" }, \ + { name, "DSP3.5", "DSP3" }, \ + { name, "DSP3.6", "DSP3" } + +static const struct snd_soc_dapm_route cs47l24_dapm_routes[] = { + { "OUT1L", NULL, "CPVDD" }, + { "OUT1R", NULL, "CPVDD" }, + + { "OUT4L", NULL, "SPKVDD" }, + + { "OUT1L", NULL, "SYSCLK" }, + { "OUT1R", NULL, "SYSCLK" }, + { "OUT4L", NULL, "SYSCLK" }, + + { "IN1L", NULL, "SYSCLK" }, + { "IN1R", NULL, "SYSCLK" }, + { "IN2L", NULL, "SYSCLK" }, + { "IN2R", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "SYSCLK" }, + { "ASRC1R", NULL, "SYSCLK" }, + { "ASRC2L", NULL, "SYSCLK" }, + { "ASRC2R", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "ASYNCCLK" }, + { "ASRC1R", NULL, "ASYNCCLK" }, + { "ASRC2L", NULL, "ASYNCCLK" }, + { "ASRC2R", NULL, "ASYNCCLK" }, + + { "MICBIAS1", NULL, "MICVDD" }, + { "MICBIAS2", NULL, "MICVDD" }, + + { "Noise Generator", NULL, "SYSCLK" }, + { "Tone Generator 1", NULL, "SYSCLK" }, + { "Tone Generator 2", NULL, "SYSCLK" }, + + { "Noise Generator", NULL, "NOISE" }, + { "Tone Generator 1", NULL, "TONE" }, + { "Tone Generator 2", NULL, "TONE" }, + + { "AIF1 Capture", NULL, "AIF1TX1" }, + { "AIF1 Capture", NULL, "AIF1TX2" }, + { "AIF1 Capture", NULL, "AIF1TX3" }, + { "AIF1 Capture", NULL, "AIF1TX4" }, + { "AIF1 Capture", NULL, "AIF1TX5" }, + { "AIF1 Capture", NULL, "AIF1TX6" }, + { "AIF1 Capture", NULL, "AIF1TX7" }, + { "AIF1 Capture", NULL, "AIF1TX8" }, + + { "AIF1RX1", NULL, "AIF1 Playback" }, + { "AIF1RX2", NULL, "AIF1 Playback" }, + { "AIF1RX3", NULL, "AIF1 Playback" }, + { "AIF1RX4", NULL, "AIF1 Playback" }, + { "AIF1RX5", NULL, "AIF1 Playback" }, + { "AIF1RX6", NULL, "AIF1 Playback" }, + { "AIF1RX7", NULL, "AIF1 Playback" }, + { "AIF1RX8", NULL, "AIF1 Playback" }, + + { "AIF2 Capture", NULL, "AIF2TX1" }, + { "AIF2 Capture", NULL, "AIF2TX2" }, + { "AIF2 Capture", NULL, "AIF2TX3" }, + { "AIF2 Capture", NULL, "AIF2TX4" }, + { "AIF2 Capture", NULL, "AIF2TX5" }, + { "AIF2 Capture", NULL, "AIF2TX6" }, + + { "AIF2RX1", NULL, "AIF2 Playback" }, + { "AIF2RX2", NULL, "AIF2 Playback" }, + { "AIF2RX3", NULL, "AIF2 Playback" }, + { "AIF2RX4", NULL, "AIF2 Playback" }, + { "AIF2RX5", NULL, "AIF2 Playback" }, + { "AIF2RX6", NULL, "AIF2 Playback" }, + + { "AIF3 Capture", NULL, "AIF3TX1" }, + { "AIF3 Capture", NULL, "AIF3TX2" }, + + { "AIF3RX1", NULL, "AIF3 Playback" }, + { "AIF3RX2", NULL, "AIF3 Playback" }, + + { "AIF1 Playback", NULL, "SYSCLK" }, + { "AIF2 Playback", NULL, "SYSCLK" }, + { "AIF3 Playback", NULL, "SYSCLK" }, + + { "AIF1 Capture", NULL, "SYSCLK" }, + { "AIF2 Capture", NULL, "SYSCLK" }, + { "AIF3 Capture", NULL, "SYSCLK" }, + + { "Voice Control DSP", NULL, "DSP3" }, + + { "IN1L PGA", NULL, "IN1L" }, + { "IN1R PGA", NULL, "IN1R" }, + + { "IN2L PGA", NULL, "IN2L" }, + { "IN2R PGA", NULL, "IN2R" }, + + { "Audio Trace DSP", NULL, "DSP2" }, + + ARIZONA_MIXER_ROUTES("OUT1L", "HPOUT1L"), + ARIZONA_MIXER_ROUTES("OUT1R", "HPOUT1R"), + + ARIZONA_MIXER_ROUTES("OUT4L", "SPKOUT"), + + ARIZONA_MIXER_ROUTES("PWM1 Driver", "PWM1"), + ARIZONA_MIXER_ROUTES("PWM2 Driver", "PWM2"), + + ARIZONA_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), + ARIZONA_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), + ARIZONA_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), + ARIZONA_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), + ARIZONA_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), + ARIZONA_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), + ARIZONA_MIXER_ROUTES("AIF1TX7", "AIF1TX7"), + ARIZONA_MIXER_ROUTES("AIF1TX8", "AIF1TX8"), + + ARIZONA_MIXER_ROUTES("AIF2TX1", "AIF2TX1"), + ARIZONA_MIXER_ROUTES("AIF2TX2", "AIF2TX2"), + ARIZONA_MIXER_ROUTES("AIF2TX3", "AIF2TX3"), + ARIZONA_MIXER_ROUTES("AIF2TX4", "AIF2TX4"), + ARIZONA_MIXER_ROUTES("AIF2TX5", "AIF2TX5"), + ARIZONA_MIXER_ROUTES("AIF2TX6", "AIF2TX6"), + + ARIZONA_MIXER_ROUTES("AIF3TX1", "AIF3TX1"), + ARIZONA_MIXER_ROUTES("AIF3TX2", "AIF3TX2"), + + ARIZONA_MIXER_ROUTES("EQ1", "EQ1"), + ARIZONA_MIXER_ROUTES("EQ2", "EQ2"), + + ARIZONA_MIXER_ROUTES("DRC1L", "DRC1L"), + ARIZONA_MIXER_ROUTES("DRC1R", "DRC1R"), + ARIZONA_MIXER_ROUTES("DRC2L", "DRC2L"), + ARIZONA_MIXER_ROUTES("DRC2R", "DRC2R"), + + ARIZONA_MIXER_ROUTES("LHPF1", "LHPF1"), + ARIZONA_MIXER_ROUTES("LHPF2", "LHPF2"), + ARIZONA_MIXER_ROUTES("LHPF3", "LHPF3"), + ARIZONA_MIXER_ROUTES("LHPF4", "LHPF4"), + + ARIZONA_MUX_ROUTES("ASRC1L", "ASRC1L"), + ARIZONA_MUX_ROUTES("ASRC1R", "ASRC1R"), + ARIZONA_MUX_ROUTES("ASRC2L", "ASRC2L"), + ARIZONA_MUX_ROUTES("ASRC2R", "ASRC2R"), + + ARIZONA_DSP_ROUTES("DSP2"), + ARIZONA_DSP_ROUTES("DSP3"), + + ARIZONA_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), + ARIZONA_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), + ARIZONA_MUX_ROUTES("ISRC1INT3", "ISRC1INT3"), + ARIZONA_MUX_ROUTES("ISRC1INT4", "ISRC1INT4"), + + ARIZONA_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), + ARIZONA_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), + ARIZONA_MUX_ROUTES("ISRC1DEC3", "ISRC1DEC3"), + ARIZONA_MUX_ROUTES("ISRC1DEC4", "ISRC1DEC4"), + + ARIZONA_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), + ARIZONA_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), + ARIZONA_MUX_ROUTES("ISRC2INT3", "ISRC2INT3"), + ARIZONA_MUX_ROUTES("ISRC2INT4", "ISRC2INT4"), + + ARIZONA_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), + ARIZONA_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), + ARIZONA_MUX_ROUTES("ISRC2DEC3", "ISRC2DEC3"), + ARIZONA_MUX_ROUTES("ISRC2DEC4", "ISRC2DEC4"), + + ARIZONA_MUX_ROUTES("ISRC3INT1", "ISRC3INT1"), + ARIZONA_MUX_ROUTES("ISRC3INT2", "ISRC3INT2"), + ARIZONA_MUX_ROUTES("ISRC3INT3", "ISRC3INT3"), + ARIZONA_MUX_ROUTES("ISRC3INT4", "ISRC3INT4"), + + ARIZONA_MUX_ROUTES("ISRC3DEC1", "ISRC3DEC1"), + ARIZONA_MUX_ROUTES("ISRC3DEC2", "ISRC3DEC2"), + ARIZONA_MUX_ROUTES("ISRC3DEC3", "ISRC3DEC3"), + ARIZONA_MUX_ROUTES("ISRC3DEC4", "ISRC3DEC4"), + + { "AEC Loopback", "HPOUT1L", "OUT1L" }, + { "AEC Loopback", "HPOUT1R", "OUT1R" }, + { "HPOUT1L", NULL, "OUT1L" }, + { "HPOUT1R", NULL, "OUT1R" }, + + { "AEC Loopback", "SPKOUT", "OUT4L" }, + { "SPKOUTN", NULL, "OUT4L" }, + { "SPKOUTP", NULL, "OUT4L" }, + + { "MICSUPP", NULL, "SYSCLK" }, + + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC2 Signal Activity", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "DRC1L" }, + { "DRC1 Signal Activity", NULL, "DRC1R" }, + { "DRC2 Signal Activity", NULL, "DRC2L" }, + { "DRC2 Signal Activity", NULL, "DRC2R" }, + + { "DSP Voice Trigger", NULL, "SYSCLK" }, + { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" }, + { "DSP3 Voice Trigger", "Switch", "DSP3" }, +}; + +static int cs47l24_set_fll(struct snd_soc_component *component, int fll_id, + int source, unsigned int Fref, unsigned int Fout) +{ + struct cs47l24_priv *cs47l24 = snd_soc_component_get_drvdata(component); + + switch (fll_id) { + case CS47L24_FLL1: + return arizona_set_fll(&cs47l24->fll[0], source, Fref, Fout); + case CS47L24_FLL2: + return arizona_set_fll(&cs47l24->fll[1], source, Fref, Fout); + case CS47L24_FLL1_REFCLK: + return arizona_set_fll_refclk(&cs47l24->fll[0], source, Fref, + Fout); + case CS47L24_FLL2_REFCLK: + return arizona_set_fll_refclk(&cs47l24->fll[1], source, Fref, + Fout); + default: + return -EINVAL; + } +} + +#define CS47L24_RATES SNDRV_PCM_RATE_KNOT + +#define CS47L24_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver cs47l24_dai[] = { + { + .name = "cs47l24-aif1", + .id = 1, + .base = ARIZONA_AIF1_BCLK_CTRL, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "cs47l24-aif2", + .id = 2, + .base = ARIZONA_AIF2_BCLK_CTRL, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 6, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "cs47l24-aif3", + .id = 3, + .base = ARIZONA_AIF3_BCLK_CTRL, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "cs47l24-cpu-voicectrl", + .capture = { + .stream_name = "Voice Control CPU", + .channels_min = 1, + .channels_max = 1, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .compress_new = snd_soc_new_compress, + }, + { + .name = "cs47l24-dsp-voicectrl", + .capture = { + .stream_name = "Voice Control DSP", + .channels_min = 1, + .channels_max = 1, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + }, + { + .name = "cs47l24-cpu-trace", + .capture = { + .stream_name = "Audio Trace CPU", + .channels_min = 1, + .channels_max = 6, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + .compress_new = snd_soc_new_compress, + }, + { + .name = "cs47l24-dsp-trace", + .capture = { + .stream_name = "Audio Trace DSP", + .channels_min = 1, + .channels_max = 6, + .rates = CS47L24_RATES, + .formats = CS47L24_FORMATS, + }, + }, +}; + +static int cs47l24_open(struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct cs47l24_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->core.arizona; + int n_adsp; + + if (strcmp(rtd->codec_dai->name, "cs47l24-dsp-voicectrl") == 0) { + n_adsp = 2; + } else if (strcmp(rtd->codec_dai->name, "cs47l24-dsp-trace") == 0) { + n_adsp = 1; + } else { + dev_err(arizona->dev, + "No suitable compressed stream for DAI '%s'\n", + rtd->codec_dai->name); + return -EINVAL; + } + + return wm_adsp_compr_open(&priv->core.adsp[n_adsp], stream); +} + +static irqreturn_t cs47l24_adsp2_irq(int irq, void *data) +{ + struct cs47l24_priv *priv = data; + struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; + int serviced = 0; + int i, ret; + + for (i = 1; i <= 2; ++i) { + ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); + if (ret != -ENODEV) + serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } + } + + if (!serviced) { + dev_err(arizona->dev, "Spurious compressed data IRQ\n"); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int cs47l24_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct cs47l24_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->core.arizona; + int ret; + + arizona->dapm = dapm; + snd_soc_component_init_regmap(component, arizona->regmap); + + ret = arizona_init_spk(component); + if (ret < 0) + return ret; + + arizona_init_gpio(component); + arizona_init_mono(component); + + ret = wm_adsp2_component_probe(&priv->core.adsp[1], component); + if (ret) + goto err_adsp2_codec_probe; + + ret = wm_adsp2_component_probe(&priv->core.adsp[2], component); + if (ret) + goto err_adsp2_codec_probe; + + ret = snd_soc_add_component_controls(component, + &arizona_adsp2_rate_controls[1], + 2); + if (ret) + goto err_adsp2_codec_probe; + + snd_soc_component_disable_pin(component, "HAPTICS"); + + return 0; + +err_adsp2_codec_probe: + wm_adsp2_component_remove(&priv->core.adsp[1], component); + wm_adsp2_component_remove(&priv->core.adsp[2], component); + + return ret; +} + +static void cs47l24_component_remove(struct snd_soc_component *component) +{ + struct cs47l24_priv *priv = snd_soc_component_get_drvdata(component); + + wm_adsp2_component_remove(&priv->core.adsp[1], component); + wm_adsp2_component_remove(&priv->core.adsp[2], component); + + priv->core.arizona->dapm = NULL; +} + +#define CS47L24_DIG_VU 0x0200 + +static unsigned int cs47l24_digital_vu[] = { + ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, + ARIZONA_DAC_DIGITAL_VOLUME_4L, +}; + +static struct snd_compr_ops cs47l24_compr_ops = { + .open = cs47l24_open, + .free = wm_adsp_compr_free, + .set_params = wm_adsp_compr_set_params, + .get_caps = wm_adsp_compr_get_caps, + .trigger = wm_adsp_compr_trigger, + .pointer = wm_adsp_compr_pointer, + .copy = wm_adsp_compr_copy, +}; + +static const struct snd_soc_component_driver soc_component_dev_cs47l24 = { + .probe = cs47l24_component_probe, + .remove = cs47l24_component_remove, + .set_sysclk = arizona_set_sysclk, + .set_pll = cs47l24_set_fll, + .name = DRV_NAME, + .compr_ops = &cs47l24_compr_ops, + .controls = cs47l24_snd_controls, + .num_controls = ARRAY_SIZE(cs47l24_snd_controls), + .dapm_widgets = cs47l24_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs47l24_dapm_widgets), + .dapm_routes = cs47l24_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs47l24_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int cs47l24_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + struct cs47l24_priv *cs47l24; + int i, ret; + + BUILD_BUG_ON(ARRAY_SIZE(cs47l24_dai) > ARIZONA_MAX_DAI); + + cs47l24 = devm_kzalloc(&pdev->dev, sizeof(struct cs47l24_priv), + GFP_KERNEL); + if (!cs47l24) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_OF)) { + if (!dev_get_platdata(arizona->dev)) { + ret = arizona_of_get_audio_pdata(arizona); + if (ret < 0) + return ret; + } + } + + platform_set_drvdata(pdev, cs47l24); + + cs47l24->core.arizona = arizona; + cs47l24->core.num_inputs = 4; + + for (i = 1; i <= 2; i++) { + cs47l24->core.adsp[i].part = "cs47l24"; + cs47l24->core.adsp[i].num = i + 1; + cs47l24->core.adsp[i].type = WMFW_ADSP2; + cs47l24->core.adsp[i].dev = arizona->dev; + cs47l24->core.adsp[i].regmap = arizona->regmap; + + cs47l24->core.adsp[i].base = ARIZONA_DSP1_CONTROL_1 + + (0x100 * i); + cs47l24->core.adsp[i].mem = cs47l24_dsp_regions[i - 1]; + cs47l24->core.adsp[i].num_mems = + ARRAY_SIZE(cs47l24_dsp2_regions); + + ret = wm_adsp2_init(&cs47l24->core.adsp[i]); + if (ret != 0) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(cs47l24->fll); i++) + cs47l24->fll[i].vco_mult = 3; + + arizona_init_fll(arizona, 1, ARIZONA_FLL1_CONTROL_1 - 1, + ARIZONA_IRQ_FLL1_LOCK, ARIZONA_IRQ_FLL1_CLOCK_OK, + &cs47l24->fll[0]); + arizona_init_fll(arizona, 2, ARIZONA_FLL2_CONTROL_1 - 1, + ARIZONA_IRQ_FLL2_LOCK, ARIZONA_IRQ_FLL2_CLOCK_OK, + &cs47l24->fll[1]); + + /* SR2 fixed at 8kHz, SR3 fixed at 16kHz */ + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_2, + ARIZONA_SAMPLE_RATE_2_MASK, 0x11); + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_3, + ARIZONA_SAMPLE_RATE_3_MASK, 0x12); + + for (i = 0; i < ARRAY_SIZE(cs47l24_dai); i++) + arizona_init_dai(&cs47l24->core, i); + + /* Latch volume update bits */ + for (i = 0; i < ARRAY_SIZE(cs47l24_digital_vu); i++) + regmap_update_bits(arizona->regmap, cs47l24_digital_vu[i], + CS47L24_DIG_VU, CS47L24_DIG_VU); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, + "ADSP2 Compressed IRQ", cs47l24_adsp2_irq, + cs47l24); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request DSP IRQ: %d\n", ret); + return ret; + } + + ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 1); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to set compressed IRQ as a wake source: %d\n", + ret); + + arizona_init_common(arizona); + + ret = arizona_init_vol_limit(arizona); + if (ret < 0) + goto err_dsp_irq; + ret = arizona_init_spk_irqs(arizona); + if (ret < 0) + goto err_dsp_irq; + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_cs47l24, + cs47l24_dai, + ARRAY_SIZE(cs47l24_dai)); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register component: %d\n", ret); + goto err_spk_irqs; + } + + return ret; + +err_spk_irqs: + arizona_free_spk_irqs(arizona); +err_dsp_irq: + arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, cs47l24); + + return ret; +} + +static int cs47l24_remove(struct platform_device *pdev) +{ + struct cs47l24_priv *cs47l24 = platform_get_drvdata(pdev); + struct arizona *arizona = cs47l24->core.arizona; + + pm_runtime_disable(&pdev->dev); + + wm_adsp2_remove(&cs47l24->core.adsp[1]); + wm_adsp2_remove(&cs47l24->core.adsp[2]); + + arizona_free_spk_irqs(arizona); + + arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, cs47l24); + + return 0; +} + +static struct platform_driver cs47l24_codec_driver = { + .driver = { + .name = "cs47l24-codec", + }, + .probe = cs47l24_probe, + .remove = cs47l24_remove, +}; + +module_platform_driver(cs47l24_codec_driver); + +MODULE_DESCRIPTION("ASoC CS47L24 driver"); +MODULE_AUTHOR("Richard Fitzgerald "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cs47l24-codec"); diff --git a/sound/soc/codecs/cs47l24.h b/sound/soc/codecs/cs47l24.h new file mode 100644 index 000000000..77ab2b77b --- /dev/null +++ b/sound/soc/codecs/cs47l24.h @@ -0,0 +1,23 @@ +/* + * cs47l24.h -- ALSA SoC Audio driver for Cirrus Logic CS47L24 + * + * Copyright 2015 Cirrus Logic Inc. + * + * Author: Richard Fitzgerald + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _CS47L24_H +#define _CS47L24_H + +#include "arizona.h" + +#define CS47L24_FLL1 1 +#define CS47L24_FLL2 2 +#define CS47L24_FLL1_REFCLK 3 +#define CS47L24_FLL2_REFCLK 4 + +#endif diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c new file mode 100644 index 000000000..86e93904b --- /dev/null +++ b/sound/soc/codecs/cs53l30.c @@ -0,0 +1,1137 @@ +/* + * cs53l30.c -- CS53l30 ALSA Soc Audio driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Authors: Paul Handrigan , + * Tim Howe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs53l30.h" + +#define CS53L30_NUM_SUPPLIES 2 +static const char *const cs53l30_supply_names[CS53L30_NUM_SUPPLIES] = { + "VA", + "VP", +}; + +struct cs53l30_private { + struct regulator_bulk_data supplies[CS53L30_NUM_SUPPLIES]; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct gpio_desc *mute_gpio; + struct clk *mclk; + bool use_sdout2; + u32 mclk_rate; +}; + +static const struct reg_default cs53l30_reg_defaults[] = { + { CS53L30_PWRCTL, CS53L30_PWRCTL_DEFAULT }, + { CS53L30_MCLKCTL, CS53L30_MCLKCTL_DEFAULT }, + { CS53L30_INT_SR_CTL, CS53L30_INT_SR_CTL_DEFAULT }, + { CS53L30_MICBIAS_CTL, CS53L30_MICBIAS_CTL_DEFAULT }, + { CS53L30_ASPCFG_CTL, CS53L30_ASPCFG_CTL_DEFAULT }, + { CS53L30_ASP_CTL1, CS53L30_ASP_CTL1_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL1, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL2, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL3, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL4, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN1, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN2, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN3, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN4, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN5, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN6, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_CTL2, CS53L30_ASP_CTL2_DEFAULT }, + { CS53L30_SFT_RAMP, CS53L30_SFT_RMP_DEFAULT }, + { CS53L30_LRCK_CTL1, CS53L30_LRCK_CTLx_DEFAULT }, + { CS53L30_LRCK_CTL2, CS53L30_LRCK_CTLx_DEFAULT }, + { CS53L30_MUTEP_CTL1, CS53L30_MUTEP_CTL1_DEFAULT }, + { CS53L30_MUTEP_CTL2, CS53L30_MUTEP_CTL2_DEFAULT }, + { CS53L30_INBIAS_CTL1, CS53L30_INBIAS_CTL1_DEFAULT }, + { CS53L30_INBIAS_CTL2, CS53L30_INBIAS_CTL2_DEFAULT }, + { CS53L30_DMIC1_STR_CTL, CS53L30_DMIC1_STR_CTL_DEFAULT }, + { CS53L30_DMIC2_STR_CTL, CS53L30_DMIC2_STR_CTL_DEFAULT }, + { CS53L30_ADCDMIC1_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT }, + { CS53L30_ADCDMIC1_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT }, + { CS53L30_ADC1_CTL3, CS53L30_ADCx_CTL3_DEFAULT }, + { CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT }, + { CS53L30_ADC1A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC1A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADC1B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADCDMIC2_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT }, + { CS53L30_ADCDMIC2_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT }, + { CS53L30_ADC2_CTL3, CS53L30_ADCx_CTL3_DEFAULT }, + { CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT }, + { CS53L30_ADC2A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC2A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADC2B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_INT_MASK, CS53L30_DEVICE_INT_MASK }, +}; + +static bool cs53l30_volatile_register(struct device *dev, unsigned int reg) +{ + if (reg == CS53L30_IS) + return true; + else + return false; +} + +static bool cs53l30_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS53L30_DEVID_AB: + case CS53L30_DEVID_CD: + case CS53L30_DEVID_E: + case CS53L30_REVID: + case CS53L30_IS: + return false; + default: + return true; + } +} + +static bool cs53l30_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS53L30_DEVID_AB: + case CS53L30_DEVID_CD: + case CS53L30_DEVID_E: + case CS53L30_REVID: + case CS53L30_PWRCTL: + case CS53L30_MCLKCTL: + case CS53L30_INT_SR_CTL: + case CS53L30_MICBIAS_CTL: + case CS53L30_ASPCFG_CTL: + case CS53L30_ASP_CTL1: + case CS53L30_ASP_TDMTX_CTL1: + case CS53L30_ASP_TDMTX_CTL2: + case CS53L30_ASP_TDMTX_CTL3: + case CS53L30_ASP_TDMTX_CTL4: + case CS53L30_ASP_TDMTX_EN1: + case CS53L30_ASP_TDMTX_EN2: + case CS53L30_ASP_TDMTX_EN3: + case CS53L30_ASP_TDMTX_EN4: + case CS53L30_ASP_TDMTX_EN5: + case CS53L30_ASP_TDMTX_EN6: + case CS53L30_ASP_CTL2: + case CS53L30_SFT_RAMP: + case CS53L30_LRCK_CTL1: + case CS53L30_LRCK_CTL2: + case CS53L30_MUTEP_CTL1: + case CS53L30_MUTEP_CTL2: + case CS53L30_INBIAS_CTL1: + case CS53L30_INBIAS_CTL2: + case CS53L30_DMIC1_STR_CTL: + case CS53L30_DMIC2_STR_CTL: + case CS53L30_ADCDMIC1_CTL1: + case CS53L30_ADCDMIC1_CTL2: + case CS53L30_ADC1_CTL3: + case CS53L30_ADC1_NG_CTL: + case CS53L30_ADC1A_AFE_CTL: + case CS53L30_ADC1B_AFE_CTL: + case CS53L30_ADC1A_DIG_VOL: + case CS53L30_ADC1B_DIG_VOL: + case CS53L30_ADCDMIC2_CTL1: + case CS53L30_ADCDMIC2_CTL2: + case CS53L30_ADC2_CTL3: + case CS53L30_ADC2_NG_CTL: + case CS53L30_ADC2A_AFE_CTL: + case CS53L30_ADC2B_AFE_CTL: + case CS53L30_ADC2A_DIG_VOL: + case CS53L30_ADC2B_DIG_VOL: + case CS53L30_INT_MASK: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(adc_boost_tlv, 0, 2000, 0); +static DECLARE_TLV_DB_SCALE(adc_ng_boost_tlv, 0, 3000, 0); +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); +static DECLARE_TLV_DB_SCALE(dig_tlv, -9600, 100, 1); +static DECLARE_TLV_DB_SCALE(pga_preamp_tlv, 0, 10000, 0); + +static const char * const input1_sel_text[] = { + "DMIC1 On AB In", + "DMIC1 On A In", + "DMIC1 On B In", + "ADC1 On AB In", + "ADC1 On A In", + "ADC1 On B In", + "DMIC1 Off ADC1 Off", +}; + +static unsigned int const input1_sel_values[] = { + CS53L30_CH_TYPE, + CS53L30_ADCxB_PDN | CS53L30_CH_TYPE, + CS53L30_ADCxA_PDN | CS53L30_CH_TYPE, + CS53L30_DMICx_PDN, + CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, +}; + +static const char * const input2_sel_text[] = { + "DMIC2 On AB In", + "DMIC2 On A In", + "DMIC2 On B In", + "ADC2 On AB In", + "ADC2 On A In", + "ADC2 On B In", + "DMIC2 Off ADC2 Off", +}; + +static unsigned int const input2_sel_values[] = { + 0x0, + CS53L30_ADCxB_PDN, + CS53L30_ADCxA_PDN, + CS53L30_DMICx_PDN, + CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, +}; + +static const char * const input1_route_sel_text[] = { + "ADC1_SEL", "DMIC1_SEL", +}; + +static const struct soc_enum input1_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, CS53L30_CH_TYPE_SHIFT, + ARRAY_SIZE(input1_route_sel_text), + input1_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input1_sel_enum, CS53L30_ADCDMIC1_CTL1, 0, + CS53L30_ADCDMICx_PDN_MASK, input1_sel_text, + input1_sel_values); + +static const struct snd_kcontrol_new input1_route_sel_mux = + SOC_DAPM_ENUM("Input 1 Route", input1_route_sel_enum); + +static const char * const input2_route_sel_text[] = { + "ADC2_SEL", "DMIC2_SEL", +}; + +/* Note: CS53L30_ADCDMIC1_CTL1 CH_TYPE controls inputs 1 and 2 */ +static const struct soc_enum input2_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, 0, + ARRAY_SIZE(input2_route_sel_text), + input2_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input2_sel_enum, CS53L30_ADCDMIC2_CTL1, 0, + CS53L30_ADCDMICx_PDN_MASK, input2_sel_text, + input2_sel_values); + +static const struct snd_kcontrol_new input2_route_sel_mux = + SOC_DAPM_ENUM("Input 2 Route", input2_route_sel_enum); + +/* + * TB = 6144*(MCLK(int) scaling factor)/MCLK(internal) + * TB - Time base + * NOTE: If MCLK_INT_SCALE = 0, then TB=1 + */ +static const char * const cs53l30_ng_delay_text[] = { + "TB*50ms", "TB*100ms", "TB*150ms", "TB*200ms", +}; + +static const struct soc_enum adc1_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT, + ARRAY_SIZE(cs53l30_ng_delay_text), + cs53l30_ng_delay_text); + +static const struct soc_enum adc2_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT, + ARRAY_SIZE(cs53l30_ng_delay_text), + cs53l30_ng_delay_text); + +/* The noise gate threshold selected will depend on NG Boost */ +static const char * const cs53l30_ng_thres_text[] = { + "-64dB/-34dB", "-66dB/-36dB", "-70dB/-40dB", "-73dB/-43dB", + "-76dB/-46dB", "-82dB/-52dB", "-58dB", "-64dB", +}; + +static const struct soc_enum adc1_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT, + ARRAY_SIZE(cs53l30_ng_thres_text), + cs53l30_ng_thres_text); + +static const struct soc_enum adc2_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT, + ARRAY_SIZE(cs53l30_ng_thres_text), + cs53l30_ng_thres_text); + +/* Corner frequencies are with an Fs of 48kHz. */ +static const char * const hpf_corner_freq_text[] = { + "1.86Hz", "120Hz", "235Hz", "466Hz", +}; + +static const struct soc_enum adc1_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_CTL3, CS53L30_ADCx_HPF_CF_SHIFT, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct soc_enum adc2_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_CTL3, CS53L30_ADCx_HPF_CF_SHIFT, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct snd_kcontrol_new cs53l30_snd_controls[] = { + SOC_SINGLE("Digital Soft-Ramp Switch", CS53L30_SFT_RAMP, + CS53L30_DIGSFT_SHIFT, 1, 0), + SOC_SINGLE("ADC1 Noise Gate Ganging Switch", CS53L30_ADC1_CTL3, + CS53L30_ADCx_NG_ALL_SHIFT, 1, 0), + SOC_SINGLE("ADC2 Noise Gate Ganging Switch", CS53L30_ADC2_CTL3, + CS53L30_ADCx_NG_ALL_SHIFT, 1, 0), + SOC_SINGLE("ADC1A Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL, + CS53L30_ADCxA_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC1B Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL, + CS53L30_ADCxB_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC2A Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL, + CS53L30_ADCxA_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC2B Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL, + CS53L30_ADCxB_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC1 Notch Filter Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1), + SOC_SINGLE("ADC2 Notch Filter Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1), + SOC_SINGLE("ADC1A Invert Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxA_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC1B Invert Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxB_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC2A Invert Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxA_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC2B Invert Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxB_INV_SHIFT, 1, 0), + + SOC_SINGLE_TLV("ADC1A Digital Boost Volume", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1B Digital Boost Volume", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2A Digital Boost Volume", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2B Digital Boost Volume", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1 NG Boost Volume", CS53L30_ADC1_NG_CTL, + CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv), + SOC_SINGLE_TLV("ADC2 NG Boost Volume", CS53L30_ADC2_NG_CTL, + CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv), + + SOC_DOUBLE_R_TLV("ADC1 Preamplifier Volume", CS53L30_ADC1A_AFE_CTL, + CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT, + 2, 0, pga_preamp_tlv), + SOC_DOUBLE_R_TLV("ADC2 Preamplifier Volume", CS53L30_ADC2A_AFE_CTL, + CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT, + 2, 0, pga_preamp_tlv), + + SOC_ENUM("Input 1 Channel Select", input1_sel_enum), + SOC_ENUM("Input 2 Channel Select", input2_sel_enum), + + SOC_ENUM("ADC1 HPF Select", adc1_hpf_enum), + SOC_ENUM("ADC2 HPF Select", adc2_hpf_enum), + SOC_ENUM("ADC1 NG Threshold", adc1_ng_thres_enum), + SOC_ENUM("ADC2 NG Threshold", adc2_ng_thres_enum), + SOC_ENUM("ADC1 NG Delay", adc1_ng_delay_enum), + SOC_ENUM("ADC2 NG Delay", adc2_ng_delay_enum), + + SOC_SINGLE_SX_TLV("ADC1A PGA Volume", + CS53L30_ADC1A_AFE_CTL, 0, 0x34, 0x24, pga_tlv), + SOC_SINGLE_SX_TLV("ADC1B PGA Volume", + CS53L30_ADC1B_AFE_CTL, 0, 0x34, 0x24, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2A PGA Volume", + CS53L30_ADC2A_AFE_CTL, 0, 0x34, 0x24, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2B PGA Volume", + CS53L30_ADC2B_AFE_CTL, 0, 0x34, 0x24, pga_tlv), + + SOC_SINGLE_SX_TLV("ADC1A Digital Volume", + CS53L30_ADC1A_DIG_VOL, 0, 0xA0, 0x6C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC1B Digital Volume", + CS53L30_ADC1B_DIG_VOL, 0, 0xA0, 0x6C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2A Digital Volume", + CS53L30_ADC2A_DIG_VOL, 0, 0xA0, 0x6C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2B Digital Volume", + CS53L30_ADC2B_DIG_VOL, 0, 0xA0, 0x6C, dig_tlv), +}; + +static const struct snd_soc_dapm_widget cs53l30_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN1_DMIC1"), + SND_SOC_DAPM_INPUT("IN2"), + SND_SOC_DAPM_INPUT("IN3_DMIC2"), + SND_SOC_DAPM_INPUT("IN4"), + SND_SOC_DAPM_SUPPLY("MIC1 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC1_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC2_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC3 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC3_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC4 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC4_BIAS_PDN_SHIFT, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("ASP_SDOUT1", NULL, 0, CS53L30_ASP_CTL1, + CS53L30_ASP_SDOUTx_PDN_SHIFT, 1), + SND_SOC_DAPM_AIF_OUT("ASP_SDOUT2", NULL, 0, CS53L30_ASP_CTL2, + CS53L30_ASP_SDOUTx_PDN_SHIFT, 1), + + SND_SOC_DAPM_MUX("Input Mux 1", SND_SOC_NOPM, 0, 0, + &input1_route_sel_mux), + SND_SOC_DAPM_MUX("Input Mux 2", SND_SOC_NOPM, 0, 0, + &input2_route_sel_mux), + + SND_SOC_DAPM_ADC("ADC1A", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_ADCxA_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC1B", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_ADCxB_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2A", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_ADCxA_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2B", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_ADCxB_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("DMIC1", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_DMICx_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("DMIC2", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_DMICx_PDN_SHIFT, 1), +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes[] = { + /* ADC Input Paths */ + {"ADC1A", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "ADC1_SEL", "ADC1A"}, + {"ADC1B", NULL, "IN2"}, + + {"ADC2A", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "ADC2_SEL", "ADC2A"}, + {"ADC2B", NULL, "IN4"}, + + /* MIC Bias Paths */ + {"ADC1A", NULL, "MIC1 Bias"}, + {"ADC1B", NULL, "MIC2 Bias"}, + {"ADC2A", NULL, "MIC3 Bias"}, + {"ADC2B", NULL, "MIC4 Bias"}, + + /* DMIC Paths */ + {"DMIC1", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "DMIC1_SEL", "DMIC1"}, + + {"DMIC2", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "DMIC2_SEL", "DMIC2"}, +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout1[] = { + /* Output Paths when using SDOUT1 only */ + {"ASP_SDOUT1", NULL, "ADC1A" }, + {"ASP_SDOUT1", NULL, "Input Mux 1"}, + {"ASP_SDOUT1", NULL, "ADC1B"}, + + {"ASP_SDOUT1", NULL, "ADC2A"}, + {"ASP_SDOUT1", NULL, "Input Mux 2"}, + {"ASP_SDOUT1", NULL, "ADC2B"}, + + {"Capture", NULL, "ASP_SDOUT1"}, +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout2[] = { + /* Output Paths when using both SDOUT1 and SDOUT2 */ + {"ASP_SDOUT1", NULL, "ADC1A" }, + {"ASP_SDOUT1", NULL, "Input Mux 1"}, + {"ASP_SDOUT1", NULL, "ADC1B"}, + + {"ASP_SDOUT2", NULL, "ADC2A"}, + {"ASP_SDOUT2", NULL, "Input Mux 2"}, + {"ASP_SDOUT2", NULL, "ADC2B"}, + + {"Capture", NULL, "ASP_SDOUT1"}, + {"Capture", NULL, "ASP_SDOUT2"}, +}; + +struct cs53l30_mclk_div { + u32 mclk_rate; + u32 srate; + u8 asp_rate; + u8 internal_fs_ratio; + u8 mclk_int_scale; +}; + +static const struct cs53l30_mclk_div cs53l30_mclk_coeffs[] = { + /* NOTE: Enable MCLK_INT_SCALE to save power. */ + + /* MCLK, Sample Rate, asp_rate, internal_fs_ratio, mclk_int_scale */ + {5644800, 11025, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {5644800, 22050, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {5644800, 44100, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + + {6000000, 8000, 0x1, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 11025, 0x2, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 12000, 0x4, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 16000, 0x5, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 22050, 0x6, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 24000, 0x8, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 32000, 0x9, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 44100, 0xA, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 48000, 0xC, 0, CS53L30_MCLK_INT_SCALE}, + + {6144000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + + {6400000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, +}; + +struct cs53l30_mclkx_div { + u32 mclkx; + u8 ratio; + u8 mclkdiv; +}; + +static const struct cs53l30_mclkx_div cs53l30_mclkx_coeffs[] = { + {5644800, 1, CS53L30_MCLK_DIV_BY_1}, + {6000000, 1, CS53L30_MCLK_DIV_BY_1}, + {6144000, 1, CS53L30_MCLK_DIV_BY_1}, + {11289600, 2, CS53L30_MCLK_DIV_BY_2}, + {12288000, 2, CS53L30_MCLK_DIV_BY_2}, + {12000000, 2, CS53L30_MCLK_DIV_BY_2}, + {19200000, 3, CS53L30_MCLK_DIV_BY_3}, +}; + +static int cs53l30_get_mclkx_coeff(int mclkx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclkx_coeffs); i++) { + if (cs53l30_mclkx_coeffs[i].mclkx == mclkx) + return i; + } + + return -EINVAL; +} + +static int cs53l30_get_mclk_coeff(int mclk_rate, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclk_coeffs); i++) { + if (cs53l30_mclk_coeffs[i].mclk_rate == mclk_rate && + cs53l30_mclk_coeffs[i].srate == srate) + return i; + } + + return -EINVAL; +} + +static int cs53l30_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct cs53l30_private *priv = snd_soc_component_get_drvdata(dai->component); + int mclkx_coeff; + u32 mclk_rate; + + /* MCLKX -> MCLK */ + mclkx_coeff = cs53l30_get_mclkx_coeff(freq); + if (mclkx_coeff < 0) + return mclkx_coeff; + + mclk_rate = cs53l30_mclkx_coeffs[mclkx_coeff].mclkx / + cs53l30_mclkx_coeffs[mclkx_coeff].ratio; + + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIV_MASK, + cs53l30_mclkx_coeffs[mclkx_coeff].mclkdiv); + + priv->mclk_rate = mclk_rate; + + return 0; +} + +static int cs53l30_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct cs53l30_private *priv = snd_soc_component_get_drvdata(dai->component); + u8 aspcfg = 0, aspctl1 = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aspcfg |= CS53L30_ASP_MS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* Set TDM_PDN to turn off TDM mode -- Reset default */ + aspctl1 |= CS53L30_ASP_TDM_PDN; + break; + case SND_SOC_DAIFMT_DSP_A: + /* + * Clear TDM_PDN to turn on TDM mode; Use ASP_SCLK_INV = 0 + * with SHIFT_LEFT = 1 combination as Figure 4-13 shows in + * the CS53L30 datasheet + */ + aspctl1 |= CS53L30_SHIFT_LEFT; + break; + default: + return -EINVAL; + } + + /* Check to see if the SCLK is inverted */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: + aspcfg ^= CS53L30_ASP_SCLK_INV; + break; + default: + break; + } + + regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL, + CS53L30_ASP_MS | CS53L30_ASP_SCLK_INV, aspcfg); + + regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1, + CS53L30_ASP_TDM_PDN | CS53L30_SHIFT_LEFT, aspctl1); + + return 0; +} + +static int cs53l30_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cs53l30_private *priv = snd_soc_component_get_drvdata(dai->component); + int srate = params_rate(params); + int mclk_coeff; + + /* MCLK -> srate */ + mclk_coeff = cs53l30_get_mclk_coeff(priv->mclk_rate, srate); + if (mclk_coeff < 0) + return -EINVAL; + + regmap_update_bits(priv->regmap, CS53L30_INT_SR_CTL, + CS53L30_INTRNL_FS_RATIO_MASK, + cs53l30_mclk_coeffs[mclk_coeff].internal_fs_ratio); + + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_INT_SCALE_MASK, + cs53l30_mclk_coeffs[mclk_coeff].mclk_int_scale); + + regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL, + CS53L30_ASP_RATE_MASK, + cs53l30_mclk_coeffs[mclk_coeff].asp_rate); + + return 0; +} + +static int cs53l30_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct cs53l30_private *priv = snd_soc_component_get_drvdata(component); + unsigned int reg; + int i, inter_max_check, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_LP_MASK, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_OFF) { + ret = clk_prepare_enable(priv->mclk); + if (ret) { + dev_err(component->dev, + "failed to enable MCLK: %d\n", ret); + return ret; + } + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS_MASK, 0); + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, 0); + msleep(50); + } else { + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, + CS53L30_PDN_ULP); + } + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, 0); + /* + * If digital softramp is set, the amount of time required + * for power down increases and depends on the digital + * volume setting. + */ + + /* Set the max possible time if digsft is set */ + regmap_read(priv->regmap, CS53L30_SFT_RAMP, ®); + if (reg & CS53L30_DIGSFT_MASK) + inter_max_check = CS53L30_PDN_POLL_MAX; + else + inter_max_check = 10; + + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, + CS53L30_PDN_ULP); + /* PDN_DONE will take a min of 20ms to be set.*/ + msleep(20); + /* Clr status */ + regmap_read(priv->regmap, CS53L30_IS, ®); + for (i = 0; i < inter_max_check; i++) { + if (inter_max_check < 10) { + usleep_range(1000, 1100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } else { + usleep_range(10000, 10100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } + } + /* PDN_DONE is set. We now can disable the MCLK */ + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, CS53L30_PDN_DONE); + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS_MASK, + CS53L30_MCLK_DIS); + clk_disable_unprepare(priv->mclk); + break; + } + + return 0; +} + +static int cs53l30_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct cs53l30_private *priv = snd_soc_component_get_drvdata(dai->component); + u8 val = tristate ? CS53L30_ASP_3ST : 0; + + return regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1, + CS53L30_ASP_3ST_MASK, val); +} + +static unsigned int const cs53l30_src_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static const struct snd_pcm_hw_constraint_list src_constraints = { + .count = ARRAY_SIZE(cs53l30_src_rates), + .list = cs53l30_src_rates, +}; + +static int cs53l30_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &src_constraints); + + return 0; +} + +/* + * Note: CS53L30 counts the slot number per byte while ASoC counts the slot + * number per slot_width. So there is a difference between the slots of ASoC + * and the slots of CS53L30. + */ +static int cs53l30_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs53l30_private *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int loc[CS53L30_TDM_SLOT_MAX] = {48, 48, 48, 48}; + unsigned int slot_next, slot_step; + u64 tx_enable = 0; + int i; + + if (!rx_mask) { + dev_err(dai->dev, "rx masks must not be 0\n"); + return -EINVAL; + } + + /* Assuming slot_width is not supposed to be greater than 64 */ + if (slots <= 0 || slot_width <= 0 || slot_width > 64) { + dev_err(dai->dev, "invalid slot number or slot width\n"); + return -EINVAL; + } + + if (slot_width & 0x7) { + dev_err(dai->dev, "slot width must count in byte\n"); + return -EINVAL; + } + + /* How many bytes in each ASoC slot */ + slot_step = slot_width >> 3; + + for (i = 0; rx_mask && i < CS53L30_TDM_SLOT_MAX; i++) { + /* Find the first slot from LSB */ + slot_next = __ffs(rx_mask); + /* Save the slot location by converting to CS53L30 slot */ + loc[i] = slot_next * slot_step; + /* Create the mask of CS53L30 slot */ + tx_enable |= (u64)((u64)(1 << slot_step) - 1) << (u64)loc[i]; + /* Clear this slot from rx_mask */ + rx_mask &= ~(1 << slot_next); + } + + /* Error out to avoid slot shift */ + if (rx_mask && i == CS53L30_TDM_SLOT_MAX) { + dev_err(dai->dev, "rx_mask exceeds max slot number: %d\n", + CS53L30_TDM_SLOT_MAX); + return -EINVAL; + } + + /* Validate the last active CS53L30 slot */ + slot_next = loc[i - 1] + slot_step - 1; + if (slot_next > 47) { + dev_err(dai->dev, "slot selection out of bounds: %u\n", + slot_next); + return -EINVAL; + } + + for (i = 0; i < CS53L30_TDM_SLOT_MAX && loc[i] != 48; i++) { + regmap_update_bits(priv->regmap, CS53L30_ASP_TDMTX_CTL(i), + CS53L30_ASP_CHx_TX_LOC_MASK, loc[i]); + dev_dbg(dai->dev, "loc[%d]=%x\n", i, loc[i]); + } + + for (i = 0; i < CS53L30_ASP_TDMTX_ENx_MAX && tx_enable; i++) { + regmap_write(priv->regmap, CS53L30_ASP_TDMTX_ENx(i), + tx_enable & 0xff); + tx_enable >>= 8; + dev_dbg(dai->dev, "en_reg=%x, tx_enable=%llx\n", + CS53L30_ASP_TDMTX_ENx(i), tx_enable & 0xff); + } + + return 0; +} + +static int cs53l30_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct cs53l30_private *priv = snd_soc_component_get_drvdata(dai->component); + + gpiod_set_value_cansleep(priv->mute_gpio, mute); + + return 0; +} + +/* SNDRV_PCM_RATE_KNOT -> 12000, 24000 Hz, limit with constraint list */ +#define CS53L30_RATES (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) + +#define CS53L30_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops cs53l30_ops = { + .startup = cs53l30_pcm_startup, + .hw_params = cs53l30_pcm_hw_params, + .set_fmt = cs53l30_set_dai_fmt, + .set_sysclk = cs53l30_set_sysclk, + .set_tristate = cs53l30_set_tristate, + .set_tdm_slot = cs53l30_set_dai_tdm_slot, + .mute_stream = cs53l30_mute_stream, +}; + +static struct snd_soc_dai_driver cs53l30_dai = { + .name = "cs53l30", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = CS53L30_RATES, + .formats = CS53L30_FORMATS, + }, + .ops = &cs53l30_ops, + .symmetric_rates = 1, +}; + +static int cs53l30_component_probe(struct snd_soc_component *component) +{ + struct cs53l30_private *priv = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (priv->use_sdout2) + snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout2, + ARRAY_SIZE(cs53l30_dapm_routes_sdout2)); + else + snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout1, + ARRAY_SIZE(cs53l30_dapm_routes_sdout1)); + + return 0; +} + +static const struct snd_soc_component_driver cs53l30_driver = { + .probe = cs53l30_component_probe, + .set_bias_level = cs53l30_set_bias_level, + .controls = cs53l30_snd_controls, + .num_controls = ARRAY_SIZE(cs53l30_snd_controls), + .dapm_widgets = cs53l30_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs53l30_dapm_widgets), + .dapm_routes = cs53l30_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs53l30_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct regmap_config cs53l30_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS53L30_MAX_REGISTER, + .reg_defaults = cs53l30_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs53l30_reg_defaults), + .volatile_reg = cs53l30_volatile_register, + .writeable_reg = cs53l30_writeable_register, + .readable_reg = cs53l30_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs53l30_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct device_node *np = client->dev.of_node; + struct device *dev = &client->dev; + struct cs53l30_private *cs53l30; + unsigned int devid = 0; + unsigned int reg; + int ret = 0, i; + u8 val; + + cs53l30 = devm_kzalloc(dev, sizeof(*cs53l30), GFP_KERNEL); + if (!cs53l30) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(cs53l30->supplies); i++) + cs53l30->supplies[i].supply = cs53l30_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to get supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + /* Reset the Device */ + cs53l30->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs53l30->reset_gpio)) { + ret = PTR_ERR(cs53l30->reset_gpio); + goto error; + } + + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + i2c_set_clientdata(client, cs53l30); + + cs53l30->mclk_rate = 0; + + cs53l30->regmap = devm_regmap_init_i2c(client, &cs53l30_regmap); + if (IS_ERR(cs53l30->regmap)) { + ret = PTR_ERR(cs53l30->regmap); + dev_err(dev, "regmap_init() failed: %d\n", ret); + goto error; + } + + /* Initialize codec */ + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_AB, ®); + devid = reg << 12; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_CD, ®); + devid |= reg << 4; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS53L30_DEVID) { + ret = -ENODEV; + dev_err(dev, "Device ID (%X). Expected %X\n", + devid, CS53L30_DEVID); + goto error; + } + + ret = regmap_read(cs53l30->regmap, CS53L30_REVID, ®); + if (ret < 0) { + dev_err(dev, "failed to get Revision ID: %d\n", ret); + goto error; + } + + /* Check if MCLK provided */ + cs53l30->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(cs53l30->mclk)) { + if (PTR_ERR(cs53l30->mclk) != -ENOENT) { + ret = PTR_ERR(cs53l30->mclk); + goto error; + } + /* Otherwise mark the mclk pointer to NULL */ + cs53l30->mclk = NULL; + } + + /* Fetch the MUTE control */ + cs53l30->mute_gpio = devm_gpiod_get_optional(dev, "mute", + GPIOD_OUT_HIGH); + if (IS_ERR(cs53l30->mute_gpio)) { + ret = PTR_ERR(cs53l30->mute_gpio); + goto error; + } + + if (cs53l30->mute_gpio) { + /* Enable MUTE controls via MUTE pin */ + regmap_write(cs53l30->regmap, CS53L30_MUTEP_CTL1, + CS53L30_MUTEP_CTL1_MUTEALL); + /* Flip the polarity of MUTE pin */ + if (gpiod_is_active_low(cs53l30->mute_gpio)) + regmap_update_bits(cs53l30->regmap, CS53L30_MUTEP_CTL2, + CS53L30_MUTE_PIN_POLARITY, 0); + } + + if (!of_property_read_u8(np, "cirrus,micbias-lvl", &val)) + regmap_update_bits(cs53l30->regmap, CS53L30_MICBIAS_CTL, + CS53L30_MIC_BIAS_CTRL_MASK, val); + + if (of_property_read_bool(np, "cirrus,use-sdout2")) + cs53l30->use_sdout2 = true; + + dev_info(dev, "Cirrus Logic CS53L30, Revision: %02X\n", reg & 0xFF); + + ret = devm_snd_soc_register_component(dev, &cs53l30_driver, &cs53l30_dai, 1); + if (ret) { + dev_err(dev, "failed to register component: %d\n", ret); + goto error; + } + + return 0; + +error: + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + return ret; +} + +static int cs53l30_i2c_remove(struct i2c_client *client) +{ + struct cs53l30_private *cs53l30 = i2c_get_clientdata(client); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + + return 0; +} + +#ifdef CONFIG_PM +static int cs53l30_runtime_suspend(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + + regcache_cache_only(cs53l30->regmap, true); + + /* Hold down reset */ + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + + return 0; +} + +static int cs53l30_runtime_resume(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + regcache_cache_only(cs53l30->regmap, false); + ret = regcache_sync(cs53l30->regmap); + if (ret) { + dev_err(dev, "failed to synchronize regcache: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops cs53l30_runtime_pm = { + SET_RUNTIME_PM_OPS(cs53l30_runtime_suspend, cs53l30_runtime_resume, + NULL) +}; + +static const struct of_device_id cs53l30_of_match[] = { + { .compatible = "cirrus,cs53l30", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cs53l30_of_match); + +static const struct i2c_device_id cs53l30_id[] = { + { "cs53l30", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs53l30_id); + +static struct i2c_driver cs53l30_i2c_driver = { + .driver = { + .name = "cs53l30", + .of_match_table = cs53l30_of_match, + .pm = &cs53l30_runtime_pm, + }, + .id_table = cs53l30_id, + .probe = cs53l30_i2c_probe, + .remove = cs53l30_i2c_remove, +}; + +module_i2c_driver(cs53l30_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS53L30 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs53l30.h b/sound/soc/codecs/cs53l30.h new file mode 100644 index 000000000..5e39da568 --- /dev/null +++ b/sound/soc/codecs/cs53l30.h @@ -0,0 +1,459 @@ +/* + * ALSA SoC CS53L30 codec driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Author: Paul Handrigan , + * Tim Howe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS53L30_H__ +#define __CS53L30_H__ + +/* I2C Registers */ +#define CS53L30_DEVID_AB 0x01 /* Device ID A & B [RO]. */ +#define CS53L30_DEVID_CD 0x02 /* Device ID C & D [RO]. */ +#define CS53L30_DEVID_E 0x03 /* Device ID E [RO]. */ +#define CS53L30_REVID 0x05 /* Revision ID [RO]. */ +#define CS53L30_PWRCTL 0x06 /* Power Control. */ +#define CS53L30_MCLKCTL 0x07 /* MCLK Control. */ +#define CS53L30_INT_SR_CTL 0x08 /* Internal Sample Rate Control. */ +#define CS53L30_MICBIAS_CTL 0x0A /* Mic Bias Control. */ +#define CS53L30_ASPCFG_CTL 0x0C /* ASP Config Control. */ +#define CS53L30_ASP_CTL1 0x0D /* ASP1 Control. */ +#define CS53L30_ASP_TDMTX_CTL1 0x0E /* ASP1 TDM TX Control 1 */ +#define CS53L30_ASP_TDMTX_CTL2 0x0F /* ASP1 TDM TX Control 2 */ +#define CS53L30_ASP_TDMTX_CTL3 0x10 /* ASP1 TDM TX Control 3 */ +#define CS53L30_ASP_TDMTX_CTL4 0x11 /* ASP1 TDM TX Control 4 */ +#define CS53L30_ASP_TDMTX_EN1 0x12 /* ASP1 TDM TX Enable 1 */ +#define CS53L30_ASP_TDMTX_EN2 0x13 /* ASP1 TDM TX Enable 2 */ +#define CS53L30_ASP_TDMTX_EN3 0x14 /* ASP1 TDM TX Enable 3 */ +#define CS53L30_ASP_TDMTX_EN4 0x15 /* ASP1 TDM TX Enable 4 */ +#define CS53L30_ASP_TDMTX_EN5 0x16 /* ASP1 TDM TX Enable 5 */ +#define CS53L30_ASP_TDMTX_EN6 0x17 /* ASP1 TDM TX Enable 6 */ +#define CS53L30_ASP_CTL2 0x18 /* ASP2 Control. */ +#define CS53L30_SFT_RAMP 0x1A /* Soft Ramp Control. */ +#define CS53L30_LRCK_CTL1 0x1B /* LRCK Control 1. */ +#define CS53L30_LRCK_CTL2 0x1C /* LRCK Control 2. */ +#define CS53L30_MUTEP_CTL1 0x1F /* Mute Pin Control 1. */ +#define CS53L30_MUTEP_CTL2 0x20 /* Mute Pin Control 2. */ +#define CS53L30_INBIAS_CTL1 0x21 /* Input Bias Control 1. */ +#define CS53L30_INBIAS_CTL2 0x22 /* Input Bias Control 2. */ +#define CS53L30_DMIC1_STR_CTL 0x23 /* DMIC1 Stereo Control. */ +#define CS53L30_DMIC2_STR_CTL 0x24 /* DMIC2 Stereo Control. */ +#define CS53L30_ADCDMIC1_CTL1 0x25 /* ADC1/DMIC1 Control 1. */ +#define CS53L30_ADCDMIC1_CTL2 0x26 /* ADC1/DMIC1 Control 2. */ +#define CS53L30_ADC1_CTL3 0x27 /* ADC1 Control 3. */ +#define CS53L30_ADC1_NG_CTL 0x28 /* ADC1 Noise Gate Control. */ +#define CS53L30_ADC1A_AFE_CTL 0x29 /* ADC1A AFE Control. */ +#define CS53L30_ADC1B_AFE_CTL 0x2A /* ADC1B AFE Control. */ +#define CS53L30_ADC1A_DIG_VOL 0x2B /* ADC1A Digital Volume. */ +#define CS53L30_ADC1B_DIG_VOL 0x2C /* ADC1B Digital Volume. */ +#define CS53L30_ADCDMIC2_CTL1 0x2D /* ADC2/DMIC2 Control 1. */ +#define CS53L30_ADCDMIC2_CTL2 0x2E /* ADC2/DMIC2 Control 2. */ +#define CS53L30_ADC2_CTL3 0x2F /* ADC2 Control 3. */ +#define CS53L30_ADC2_NG_CTL 0x30 /* ADC2 Noise Gate Control. */ +#define CS53L30_ADC2A_AFE_CTL 0x31 /* ADC2A AFE Control. */ +#define CS53L30_ADC2B_AFE_CTL 0x32 /* ADC2B AFE Control. */ +#define CS53L30_ADC2A_DIG_VOL 0x33 /* ADC2A Digital Volume. */ +#define CS53L30_ADC2B_DIG_VOL 0x34 /* ADC2B Digital Volume. */ +#define CS53L30_INT_MASK 0x35 /* Interrupt Mask. */ +#define CS53L30_IS 0x36 /* Interrupt Status. */ +#define CS53L30_MAX_REGISTER 0x36 + +#define CS53L30_TDM_SLOT_MAX 4 +#define CS53L30_ASP_TDMTX_CTL(x) (CS53L30_ASP_TDMTX_CTL1 + (x)) +/* x : index for registers; n : index for slot; 8 slots per register */ +#define CS53L30_ASP_TDMTX_ENx(x) (CS53L30_ASP_TDMTX_EN6 - (x)) +#define CS53L30_ASP_TDMTX_ENn(n) CS53L30_ASP_TDMTX_ENx((n) >> 3) +#define CS53L30_ASP_TDMTX_ENx_MAX 6 + +/* Device ID */ +#define CS53L30_DEVID 0x53A30 + +/* PDN_DONE Poll Maximum + * If soft ramp is set it will take much longer to power down + * the system. + */ +#define CS53L30_PDN_POLL_MAX 90 + +/* Bitfield Definitions */ + +/* R6 (0x06) CS53L30_PWRCTL - Power Control */ +#define CS53L30_PDN_ULP_SHIFT 7 +#define CS53L30_PDN_ULP_MASK (1 << CS53L30_PDN_ULP_SHIFT) +#define CS53L30_PDN_ULP (1 << CS53L30_PDN_ULP_SHIFT) +#define CS53L30_PDN_LP_SHIFT 6 +#define CS53L30_PDN_LP_MASK (1 << CS53L30_PDN_LP_SHIFT) +#define CS53L30_PDN_LP (1 << CS53L30_PDN_LP_SHIFT) +#define CS53L30_DISCHARGE_FILT_SHIFT 5 +#define CS53L30_DISCHARGE_FILT_MASK (1 << CS53L30_DISCHARGE_FILT_SHIFT) +#define CS53L30_DISCHARGE_FILT (1 << CS53L30_DISCHARGE_FILT_SHIFT) +#define CS53L30_THMS_PDN_SHIFT 4 +#define CS53L30_THMS_PDN_MASK (1 << CS53L30_THMS_PDN_SHIFT) +#define CS53L30_THMS_PDN (1 << CS53L30_THMS_PDN_SHIFT) + +#define CS53L30_PWRCTL_DEFAULT (CS53L30_THMS_PDN) + +/* R7 (0x07) CS53L30_MCLKCTL - MCLK Control */ +#define CS53L30_MCLK_DIS_SHIFT 7 +#define CS53L30_MCLK_DIS_MASK (1 << CS53L30_MCLK_DIS_SHIFT) +#define CS53L30_MCLK_DIS (1 << CS53L30_MCLK_DIS_SHIFT) +#define CS53L30_MCLK_INT_SCALE_SHIFT 6 +#define CS53L30_MCLK_INT_SCALE_MASK (1 << CS53L30_MCLK_INT_SCALE_SHIFT) +#define CS53L30_MCLK_INT_SCALE (1 << CS53L30_MCLK_INT_SCALE_SHIFT) +#define CS53L30_DMIC_DRIVE_SHIFT 5 +#define CS53L30_DMIC_DRIVE_MASK (1 << CS53L30_DMIC_DRIVE_SHIFT) +#define CS53L30_DMIC_DRIVE (1 << CS53L30_DMIC_DRIVE_SHIFT) +#define CS53L30_MCLK_DIV_SHIFT 2 +#define CS53L30_MCLK_DIV_WIDTH 2 +#define CS53L30_MCLK_DIV_MASK (((1 << CS53L30_MCLK_DIV_WIDTH) - 1) << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_1 (0x0 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_2 (0x1 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_3 (0x2 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_SYNC_EN_SHIFT 1 +#define CS53L30_SYNC_EN_MASK (1 << CS53L30_SYNC_EN_SHIFT) +#define CS53L30_SYNC_EN (1 << CS53L30_SYNC_EN_SHIFT) + +#define CS53L30_MCLKCTL_DEFAULT (CS53L30_MCLK_DIV_BY_2) + +/* R8 (0x08) CS53L30_INT_SR_CTL - Internal Sample Rate Control */ +#define CS53L30_INTRNL_FS_RATIO_SHIFT 4 +#define CS53L30_INTRNL_FS_RATIO_MASK (1 << CS53L30_INTRNL_FS_RATIO_SHIFT) +#define CS53L30_INTRNL_FS_RATIO (1 << CS53L30_INTRNL_FS_RATIO_SHIFT) +#define CS53L30_MCLK_19MHZ_EN_SHIFT 0 +#define CS53L30_MCLK_19MHZ_EN_MASK (1 << CS53L30_MCLK_19MHZ_EN_SHIFT) +#define CS53L30_MCLK_19MHZ_EN (1 << CS53L30_MCLK_19MHZ_EN_SHIFT) + +/* 0x6 << 1 is reserved bits */ +#define CS53L30_INT_SR_CTL_DEFAULT (CS53L30_INTRNL_FS_RATIO | 0x6 << 1) + +/* R10 (0x0A) CS53L30_MICBIAS_CTL - Mic Bias Control */ +#define CS53L30_MIC4_BIAS_PDN_SHIFT 7 +#define CS53L30_MIC4_BIAS_PDN_MASK (1 << CS53L30_MIC4_BIAS_PDN_SHIFT) +#define CS53L30_MIC4_BIAS_PDN (1 << CS53L30_MIC4_BIAS_PDN_SHIFT) +#define CS53L30_MIC3_BIAS_PDN_SHIFT 6 +#define CS53L30_MIC3_BIAS_PDN_MASK (1 << CS53L30_MIC3_BIAS_PDN_SHIFT) +#define CS53L30_MIC3_BIAS_PDN (1 << CS53L30_MIC3_BIAS_PDN_SHIFT) +#define CS53L30_MIC2_BIAS_PDN_SHIFT 5 +#define CS53L30_MIC2_BIAS_PDN_MASK (1 << CS53L30_MIC2_BIAS_PDN_SHIFT) +#define CS53L30_MIC2_BIAS_PDN (1 << CS53L30_MIC2_BIAS_PDN_SHIFT) +#define CS53L30_MIC1_BIAS_PDN_SHIFT 4 +#define CS53L30_MIC1_BIAS_PDN_MASK (1 << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_MIC1_BIAS_PDN (1 << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_MICx_BIAS_PDN (0xf << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_VP_MIN_SHIFT 2 +#define CS53L30_VP_MIN_MASK (1 << CS53L30_VP_MIN_SHIFT) +#define CS53L30_VP_MIN (1 << CS53L30_VP_MIN_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_SHIFT 0 +#define CS53L30_MIC_BIAS_CTRL_WIDTH 2 +#define CS53L30_MIC_BIAS_CTRL_MASK (((1 << CS53L30_MIC_BIAS_CTRL_WIDTH) - 1) << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_HIZ (0 << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_1V8 (1 << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_2V75 (2 << CS53L30_MIC_BIAS_CTRL_SHIFT) + +#define CS53L30_MICBIAS_CTL_DEFAULT (CS53L30_MICx_BIAS_PDN | CS53L30_VP_MIN) + +/* R12 (0x0C) CS53L30_ASPCFG_CTL - ASP Configuration Control */ +#define CS53L30_ASP_MS_SHIFT 7 +#define CS53L30_ASP_MS_MASK (1 << CS53L30_ASP_MS_SHIFT) +#define CS53L30_ASP_MS (1 << CS53L30_ASP_MS_SHIFT) +#define CS53L30_ASP_SCLK_INV_SHIFT 4 +#define CS53L30_ASP_SCLK_INV_MASK (1 << CS53L30_ASP_SCLK_INV_SHIFT) +#define CS53L30_ASP_SCLK_INV (1 << CS53L30_ASP_SCLK_INV_SHIFT) +#define CS53L30_ASP_RATE_SHIFT 0 +#define CS53L30_ASP_RATE_WIDTH 4 +#define CS53L30_ASP_RATE_MASK (((1 << CS53L30_ASP_RATE_WIDTH) - 1) << CS53L30_ASP_RATE_SHIFT) +#define CS53L30_ASP_RATE_48K (0xc << CS53L30_ASP_RATE_SHIFT) + +#define CS53L30_ASPCFG_CTL_DEFAULT (CS53L30_ASP_RATE_48K) + +/* R13/R24 (0x0D/0x18) CS53L30_ASP_CTL1 & CS53L30_ASP_CTL2 - ASP Control 1~2 */ +#define CS53L30_ASP_TDM_PDN_SHIFT 7 +#define CS53L30_ASP_TDM_PDN_MASK (1 << CS53L30_ASP_TDM_PDN_SHIFT) +#define CS53L30_ASP_TDM_PDN (1 << CS53L30_ASP_TDM_PDN_SHIFT) +#define CS53L30_ASP_SDOUTx_PDN_SHIFT 6 +#define CS53L30_ASP_SDOUTx_PDN_MASK (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT) +#define CS53L30_ASP_SDOUTx_PDN (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT) +#define CS53L30_ASP_3ST_SHIFT 5 +#define CS53L30_ASP_3ST_MASK (1 << CS53L30_ASP_3ST_SHIFT) +#define CS53L30_ASP_3ST (1 << CS53L30_ASP_3ST_SHIFT) +#define CS53L30_SHIFT_LEFT_SHIFT 4 +#define CS53L30_SHIFT_LEFT_MASK (1 << CS53L30_SHIFT_LEFT_SHIFT) +#define CS53L30_SHIFT_LEFT (1 << CS53L30_SHIFT_LEFT_SHIFT) +#define CS53L30_ASP_SDOUTx_DRIVE_SHIFT 0 +#define CS53L30_ASP_SDOUTx_DRIVE_MASK (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT) +#define CS53L30_ASP_SDOUTx_DRIVE (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT) + +#define CS53L30_ASP_CTL1_DEFAULT (CS53L30_ASP_TDM_PDN) +#define CS53L30_ASP_CTL2_DEFAULT (0) + +/* R14 (0x0E) ~ R17 (0x11) CS53L30_ASP_TDMTX_CTLx - ASP TDM TX Control 1~4 */ +#define CS53L30_ASP_CHx_TX_STATE_SHIFT 7 +#define CS53L30_ASP_CHx_TX_STATE_MASK (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT) +#define CS53L30_ASP_CHx_TX_STATE (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC_SHIFT 0 +#define CS53L30_ASP_CHx_TX_LOC_WIDTH 6 +#define CS53L30_ASP_CHx_TX_LOC_MASK (((1 << CS53L30_ASP_CHx_TX_LOC_WIDTH) - 1) << CS53L30_ASP_CHx_TX_LOC_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC_MAX (47 << CS53L30_ASP_CHx_TX_LOC_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC(x) ((x) << CS53L30_ASP_CHx_TX_LOC_SHIFT) + +#define CS53L30_ASP_TDMTX_CTLx_DEFAULT (CS53L30_ASP_CHx_TX_LOC_MAX) + +/* R18 (0x12) ~ R23 (0x17) CS53L30_ASP_TDMTX_ENx - ASP TDM TX Enable 1~6 */ +#define CS53L30_ASP_TDMTX_ENx_DEFAULT (0) + +/* R26 (0x1A) CS53L30_SFT_RAMP - Soft Ramp Control */ +#define CS53L30_DIGSFT_SHIFT 5 +#define CS53L30_DIGSFT_MASK (1 << CS53L30_DIGSFT_SHIFT) +#define CS53L30_DIGSFT (1 << CS53L30_DIGSFT_SHIFT) + +#define CS53L30_SFT_RMP_DEFAULT (0) + +/* R28 (0x1C) CS53L30_LRCK_CTL2 - LRCK Control 2 */ +#define CS53L30_LRCK_50_NPW_SHIFT 3 +#define CS53L30_LRCK_50_NPW_MASK (1 << CS53L30_LRCK_50_NPW_SHIFT) +#define CS53L30_LRCK_50_NPW (1 << CS53L30_LRCK_50_NPW_SHIFT) +#define CS53L30_LRCK_TPWH_SHIFT 0 +#define CS53L30_LRCK_TPWH_WIDTH 3 +#define CS53L30_LRCK_TPWH_MASK (((1 << CS53L30_LRCK_TPWH_WIDTH) - 1) << CS53L30_LRCK_TPWH_SHIFT) +#define CS53L30_LRCK_TPWH(x) (((x) << CS53L30_LRCK_TPWH_SHIFT) & CS53L30_LRCK_TPWH_MASK) + +#define CS53L30_LRCK_CTLx_DEFAULT (0) + +/* R31 (0x1F) CS53L30_MUTEP_CTL1 - MUTE Pin Control 1 */ +#define CS53L30_MUTE_PDN_ULP_SHIFT 7 +#define CS53L30_MUTE_PDN_ULP_MASK (1 << CS53L30_MUTE_PDN_ULP_SHIFT) +#define CS53L30_MUTE_PDN_ULP (1 << CS53L30_MUTE_PDN_ULP_SHIFT) +#define CS53L30_MUTE_PDN_LP_SHIFT 6 +#define CS53L30_MUTE_PDN_LP_MASK (1 << CS53L30_MUTE_PDN_LP_SHIFT) +#define CS53L30_MUTE_PDN_LP (1 << CS53L30_MUTE_PDN_LP_SHIFT) +#define CS53L30_MUTE_M4B_PDN_SHIFT 4 +#define CS53L30_MUTE_M4B_PDN_MASK (1 << CS53L30_MUTE_M4B_PDN_SHIFT) +#define CS53L30_MUTE_M4B_PDN (1 << CS53L30_MUTE_M4B_PDN_SHIFT) +#define CS53L30_MUTE_M3B_PDN_SHIFT 3 +#define CS53L30_MUTE_M3B_PDN_MASK (1 << CS53L30_MUTE_M3B_PDN_SHIFT) +#define CS53L30_MUTE_M3B_PDN (1 << CS53L30_MUTE_M3B_PDN_SHIFT) +#define CS53L30_MUTE_M2B_PDN_SHIFT 2 +#define CS53L30_MUTE_M2B_PDN_MASK (1 << CS53L30_MUTE_M2B_PDN_SHIFT) +#define CS53L30_MUTE_M2B_PDN (1 << CS53L30_MUTE_M2B_PDN_SHIFT) +#define CS53L30_MUTE_M1B_PDN_SHIFT 1 +#define CS53L30_MUTE_M1B_PDN_MASK (1 << CS53L30_MUTE_M1B_PDN_SHIFT) +#define CS53L30_MUTE_M1B_PDN (1 << CS53L30_MUTE_M1B_PDN_SHIFT) +/* Note: be careful - x starts from 0 */ +#define CS53L30_MUTE_MxB_PDN_SHIFT(x) (CS53L30_MUTE_M1B_PDN_SHIFT + (x)) +#define CS53L30_MUTE_MxB_PDN_MASK(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x)) +#define CS53L30_MUTE_MxB_PDN(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x)) +#define CS53L30_MUTE_MB_ALL_PDN_SHIFT 0 +#define CS53L30_MUTE_MB_ALL_PDN_MASK (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT) +#define CS53L30_MUTE_MB_ALL_PDN (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT) + +#define CS53L30_MUTEP_CTL1_MUTEALL (0xdf) +#define CS53L30_MUTEP_CTL1_DEFAULT (0) + +/* R32 (0x20) CS53L30_MUTEP_CTL2 - MUTE Pin Control 2 */ +#define CS53L30_MUTE_PIN_POLARITY_SHIFT 7 +#define CS53L30_MUTE_PIN_POLARITY_MASK (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT) +#define CS53L30_MUTE_PIN_POLARITY (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT) +#define CS53L30_MUTE_ASP_TDM_PDN_SHIFT 6 +#define CS53L30_MUTE_ASP_TDM_PDN_MASK (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT) +#define CS53L30_MUTE_ASP_TDM_PDN (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT 5 +#define CS53L30_MUTE_ASP_SDOUT2_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT2_PDN (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT 4 +#define CS53L30_MUTE_ASP_SDOUT1_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT1_PDN (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +/* Note: be careful - x starts from 0 */ +#define CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x) ((x) + CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUTx_PDN_MASK(x) (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x)) +#define CS53L30_MUTE_ASP_SDOUTx_PDN (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x)) +#define CS53L30_MUTE_ADC2B_PDN_SHIFT 3 +#define CS53L30_MUTE_ADC2B_PDN_MASK (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT) +#define CS53L30_MUTE_ADC2B_PDN (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT) +#define CS53L30_MUTE_ADC2A_PDN_SHIFT 2 +#define CS53L30_MUTE_ADC2A_PDN_MASK (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT) +#define CS53L30_MUTE_ADC2A_PDN (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT) +#define CS53L30_MUTE_ADC1B_PDN_SHIFT 1 +#define CS53L30_MUTE_ADC1B_PDN_MASK (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT) +#define CS53L30_MUTE_ADC1B_PDN (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT) +#define CS53L30_MUTE_ADC1A_PDN_SHIFT 0 +#define CS53L30_MUTE_ADC1A_PDN_MASK (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT) +#define CS53L30_MUTE_ADC1A_PDN (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT) + +#define CS53L30_MUTEP_CTL2_DEFAULT (CS53L30_MUTE_PIN_POLARITY) + +/* R33 (0x21) CS53L30_INBIAS_CTL1 - Input Bias Control 1 */ +#define CS53L30_IN4M_BIAS_SHIFT 6 +#define CS53L30_IN4M_BIAS_WIDTH 2 +#define CS53L30_IN4M_BIAS_MASK (((1 << CS53L30_IN4M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_OPEN (0 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_PULL_DOWN (1 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_VCM (2 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_SHIFT 4 +#define CS53L30_IN4P_BIAS_WIDTH 2 +#define CS53L30_IN4P_BIAS_MASK (((1 << CS53L30_IN4P_BIAS_WIDTH) - 1) << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_OPEN (0 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_PULL_DOWN (1 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_VCM (2 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_SHIFT 2 +#define CS53L30_IN3M_BIAS_WIDTH 2 +#define CS53L30_IN3M_BIAS_MASK (((1 << CS53L30_IN3M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_OPEN (0 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_PULL_DOWN (1 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_VCM (2 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_SHIFT 0 +#define CS53L30_IN3P_BIAS_WIDTH 2 +#define CS53L30_IN3P_BIAS_MASK (((1 << CS53L30_IN3P_BIAS_WIDTH) - 1) << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_OPEN (0 << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_PULL_DOWN (1 << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_VCM (2 << CS53L30_IN3P_BIAS_SHIFT) + +#define CS53L30_INBIAS_CTL1_DEFAULT (CS53L30_IN4M_BIAS_VCM | CS53L30_IN4P_BIAS_VCM |\ + CS53L30_IN3M_BIAS_VCM | CS53L30_IN3P_BIAS_VCM) + +/* R34 (0x22) CS53L30_INBIAS_CTL2 - Input Bias Control 2 */ +#define CS53L30_IN2M_BIAS_SHIFT 6 +#define CS53L30_IN2M_BIAS_WIDTH 2 +#define CS53L30_IN2M_BIAS_MASK (((1 << CS53L30_IN2M_BIAS_WIDTH) - 1) << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_OPEN (0 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_PULL_DOWN (1 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_VCM (2 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_SHIFT 4 +#define CS53L30_IN2P_BIAS_WIDTH 2 +#define CS53L30_IN2P_BIAS_MASK (((1 << CS53L30_IN2P_BIAS_WIDTH) - 1) << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_OPEN (0 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_PULL_DOWN (1 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_VCM (2 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_SHIFT 2 +#define CS53L30_IN1M_BIAS_WIDTH 2 +#define CS53L30_IN1M_BIAS_MASK (((1 << CS53L30_IN1M_BIAS_WIDTH) - 1) << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_OPEN (0 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_PULL_DOWN (1 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_VCM (2 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_SHIFT 0 +#define CS53L30_IN1P_BIAS_WIDTH 2 +#define CS53L30_IN1P_BIAS_MASK (((1 << CS53L30_IN1P_BIAS_WIDTH) - 1) << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_OPEN (0 << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_PULL_DOWN (1 << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_VCM (2 << CS53L30_IN1P_BIAS_SHIFT) + +#define CS53L30_INBIAS_CTL2_DEFAULT (CS53L30_IN2M_BIAS_VCM | CS53L30_IN2P_BIAS_VCM |\ + CS53L30_IN1M_BIAS_VCM | CS53L30_IN1P_BIAS_VCM) + +/* R35 (0x23) & R36 (0x24) CS53L30_DMICx_STR_CTL - DMIC1 & DMIC2 Stereo Control */ +#define CS53L30_DMICx_STEREO_ENB_SHIFT 5 +#define CS53L30_DMICx_STEREO_ENB_MASK (1 << CS53L30_DMICx_STEREO_ENB_SHIFT) +#define CS53L30_DMICx_STEREO_ENB (1 << CS53L30_DMICx_STEREO_ENB_SHIFT) + +/* 0x88 and 0xCC are reserved bits */ +#define CS53L30_DMIC1_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0x88) +#define CS53L30_DMIC2_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0xCC) + +/* R37/R45 (0x25/0x2D) CS53L30_ADCDMICx_CTL1 - ADC1/DMIC1 & ADC2/DMIC2 Control 1 */ +#define CS53L30_ADCxB_PDN_SHIFT 7 +#define CS53L30_ADCxB_PDN_MASK (1 << CS53L30_ADCxB_PDN_SHIFT) +#define CS53L30_ADCxB_PDN (1 << CS53L30_ADCxB_PDN_SHIFT) +#define CS53L30_ADCxA_PDN_SHIFT 6 +#define CS53L30_ADCxA_PDN_MASK (1 << CS53L30_ADCxA_PDN_SHIFT) +#define CS53L30_ADCxA_PDN (1 << CS53L30_ADCxA_PDN_SHIFT) +#define CS53L30_DMICx_PDN_SHIFT 2 +#define CS53L30_DMICx_PDN_MASK (1 << CS53L30_DMICx_PDN_SHIFT) +#define CS53L30_DMICx_PDN (1 << CS53L30_DMICx_PDN_SHIFT) +#define CS53L30_DMICx_SCLK_DIV_SHIFT 1 +#define CS53L30_DMICx_SCLK_DIV_MASK (1 << CS53L30_DMICx_SCLK_DIV_SHIFT) +#define CS53L30_DMICx_SCLK_DIV (1 << CS53L30_DMICx_SCLK_DIV_SHIFT) +#define CS53L30_CH_TYPE_SHIFT 0 +#define CS53L30_CH_TYPE_MASK (1 << CS53L30_CH_TYPE_SHIFT) +#define CS53L30_CH_TYPE (1 << CS53L30_CH_TYPE_SHIFT) + +#define CS53L30_ADCDMICx_PDN_MASK 0xFF +#define CS53L30_ADCDMICx_CTL1_DEFAULT (CS53L30_DMICx_PDN) + +/* R38/R46 (0x26/0x2E) CS53L30_ADCDMICx_CTL2 - ADC1/DMIC1 & ADC2/DMIC2 Control 2 */ +#define CS53L30_ADCx_NOTCH_DIS_SHIFT 7 +#define CS53L30_ADCx_NOTCH_DIS_MASK (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT) +#define CS53L30_ADCx_NOTCH_DIS (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT) +#define CS53L30_ADCxB_INV_SHIFT 5 +#define CS53L30_ADCxB_INV_MASK (1 << CS53L30_ADCxB_INV_SHIFT) +#define CS53L30_ADCxB_INV (1 << CS53L30_ADCxB_INV_SHIFT) +#define CS53L30_ADCxA_INV_SHIFT 4 +#define CS53L30_ADCxA_INV_MASK (1 << CS53L30_ADCxA_INV_SHIFT) +#define CS53L30_ADCxA_INV (1 << CS53L30_ADCxA_INV_SHIFT) +#define CS53L30_ADCxB_DIG_BOOST_SHIFT 1 +#define CS53L30_ADCxB_DIG_BOOST_MASK (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT) +#define CS53L30_ADCxB_DIG_BOOST (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT) +#define CS53L30_ADCxA_DIG_BOOST_SHIFT 0 +#define CS53L30_ADCxA_DIG_BOOST_MASK (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT) +#define CS53L30_ADCxA_DIG_BOOST (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT) + +#define CS53L30_ADCDMIC1_CTL2_DEFAULT (0) + +/* R39/R47 (0x27/0x2F) CS53L30_ADCx_CTL3 - ADC1/ADC2 Control 3 */ +#define CS53L30_ADCx_HPF_EN_SHIFT 3 +#define CS53L30_ADCx_HPF_EN_MASK (1 << CS53L30_ADCx_HPF_EN_SHIFT) +#define CS53L30_ADCx_HPF_EN (1 << CS53L30_ADCx_HPF_EN_SHIFT) +#define CS53L30_ADCx_HPF_CF_SHIFT 1 +#define CS53L30_ADCx_HPF_CF_WIDTH 2 +#define CS53L30_ADCx_HPF_CF_MASK (((1 << CS53L30_ADCx_HPF_CF_WIDTH) - 1) << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_1HZ86 (0 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_120HZ (1 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_235HZ (2 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_466HZ (3 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_NG_ALL_SHIFT 0 +#define CS53L30_ADCx_NG_ALL_MASK (1 << CS53L30_ADCx_NG_ALL_SHIFT) +#define CS53L30_ADCx_NG_ALL (1 << CS53L30_ADCx_NG_ALL_SHIFT) + +#define CS53L30_ADCx_CTL3_DEFAULT (CS53L30_ADCx_HPF_EN) + +/* R40/R48 (0x28/0x30) CS53L30_ADCx_NG_CTL - ADC1/ADC2 Noise Gate Control */ +#define CS53L30_ADCxB_NG_SHIFT 7 +#define CS53L30_ADCxB_NG_MASK (1 << CS53L30_ADCxB_NG_SHIFT) +#define CS53L30_ADCxB_NG (1 << CS53L30_ADCxB_NG_SHIFT) +#define CS53L30_ADCxA_NG_SHIFT 6 +#define CS53L30_ADCxA_NG_MASK (1 << CS53L30_ADCxA_NG_SHIFT) +#define CS53L30_ADCxA_NG (1 << CS53L30_ADCxA_NG_SHIFT) +#define CS53L30_ADCx_NG_BOOST_SHIFT 5 +#define CS53L30_ADCx_NG_BOOST_MASK (1 << CS53L30_ADCx_NG_BOOST_SHIFT) +#define CS53L30_ADCx_NG_BOOST (1 << CS53L30_ADCx_NG_BOOST_SHIFT) +#define CS53L30_ADCx_NG_THRESH_SHIFT 2 +#define CS53L30_ADCx_NG_THRESH_WIDTH 3 +#define CS53L30_ADCx_NG_THRESH_MASK (((1 << CS53L30_ADCx_NG_THRESH_WIDTH) - 1) << CS53L30_ADCx_NG_THRESH_SHIFT) +#define CS53L30_ADCx_NG_DELAY_SHIFT 0 +#define CS53L30_ADCx_NG_DELAY_WIDTH 2 +#define CS53L30_ADCx_NG_DELAY_MASK (((1 << CS53L30_ADCx_NG_DELAY_WIDTH) - 1) << CS53L30_ADCx_NG_DELAY_SHIFT) + +#define CS53L30_ADCx_NG_CTL_DEFAULT (0) + +/* R41/R42/R49/R50 (0x29/0x2A/0x31/0x32) CS53L30_ADCxy_AFE_CTL - ADC1A/1B/2A/2B AFE Control */ +#define CS53L30_ADCxy_PREAMP_SHIFT 6 +#define CS53L30_ADCxy_PREAMP_WIDTH 2 +#define CS53L30_ADCxy_PREAMP_MASK (((1 << CS53L30_ADCxy_PREAMP_WIDTH) - 1) << CS53L30_ADCxy_PREAMP_SHIFT) +#define CS53L30_ADCxy_PGA_VOL_SHIFT 0 +#define CS53L30_ADCxy_PGA_VOL_WIDTH 6 +#define CS53L30_ADCxy_PGA_VOL_MASK (((1 << CS53L30_ADCxy_PGA_VOL_WIDTH) - 1) << CS53L30_ADCxy_PGA_VOL_SHIFT) + +#define CS53L30_ADCxy_AFE_CTL_DEFAULT (0) + +/* R43/R44/R51/R52 (0x2B/0x2C/0x33/0x34) CS53L30_ADCxy_DIG_VOL - ADC1A/1B/2A/2B Digital Volume */ +#define CS53L30_ADCxy_VOL_MUTE (0x80) + +#define CS53L30_ADCxy_DIG_VOL_DEFAULT (0x0) + +/* CS53L30_INT */ +#define CS53L30_PDN_DONE (1 << 7) +#define CS53L30_THMS_TRIP (1 << 6) +#define CS53L30_SYNC_DONE (1 << 5) +#define CS53L30_ADC2B_OVFL (1 << 4) +#define CS53L30_ADC2A_OVFL (1 << 3) +#define CS53L30_ADC1B_OVFL (1 << 2) +#define CS53L30_ADC1A_OVFL (1 << 1) +#define CS53L30_MUTE_PIN (1 << 0) +#define CS53L30_DEVICE_INT_MASK 0xFF + +#endif /* __CS53L30_H__ */ diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c new file mode 100644 index 000000000..ab174b511 --- /dev/null +++ b/sound/soc/codecs/cx20442.c @@ -0,0 +1,447 @@ +/* + * cx20442.c -- CX20442 ALSA Soc Audio driver + * + * Copyright 2009 Janusz Krzysztofik + * + * Initially based on sound/soc/codecs/wm8400.c + * Copyright 2008, 2009 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "cx20442.h" + + +struct cx20442_priv { + struct tty_struct *tty; + struct regulator *por; + u8 reg_cache; +}; + +#define CX20442_PM 0x0 + +#define CX20442_TELIN 0 +#define CX20442_TELOUT 1 +#define CX20442_MIC 2 +#define CX20442_SPKOUT 3 +#define CX20442_AGC 4 + +static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("TELOUT"), + SND_SOC_DAPM_OUTPUT("SPKOUT"), + SND_SOC_DAPM_OUTPUT("AGCOUT"), + + SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0), + SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0), + + SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("TELIN"), + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("AGCIN"), +}; + +static const struct snd_soc_dapm_route cx20442_audio_map[] = { + {"TELOUT", NULL, "TELOUT Amp"}, + + {"SPKOUT", NULL, "SPKOUT Mixer"}, + {"SPKOUT Mixer", NULL, "SPKOUT Amp"}, + + {"TELOUT Amp", NULL, "DAC"}, + {"SPKOUT Amp", NULL, "DAC"}, + + {"SPKOUT Mixer", NULL, "SPKOUT AGC"}, + {"SPKOUT AGC", NULL, "AGCIN"}, + + {"AGCOUT", NULL, "MIC AGC"}, + {"MIC AGC", NULL, "MIC"}, + + {"MIC Bias", NULL, "MIC"}, + {"Input Mixer", NULL, "MIC Bias"}, + + {"TELIN Bias", NULL, "TELIN"}, + {"Input Mixer", NULL, "TELIN Bias"}, + + {"ADC", NULL, "Input Mixer"}, +}; + +static unsigned int cx20442_read_reg_cache(struct snd_soc_component *component, + unsigned int reg) +{ + struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); + + if (reg >= 1) + return -EINVAL; + + return cx20442->reg_cache; +} + +enum v253_vls { + V253_VLS_NONE = 0, + V253_VLS_T, + V253_VLS_L, + V253_VLS_LT, + V253_VLS_S, + V253_VLS_ST, + V253_VLS_M, + V253_VLS_MST, + V253_VLS_S1, + V253_VLS_S1T, + V253_VLS_MS1T, + V253_VLS_M1, + V253_VLS_M1ST, + V253_VLS_M1S1T, + V253_VLS_H, + V253_VLS_HT, + V253_VLS_MS, + V253_VLS_MS1, + V253_VLS_M1S, + V253_VLS_M1S1, + V253_VLS_TEST, +}; + +static int cx20442_pm_to_v253_vls(u8 value) +{ + switch (value & ~(1 << CX20442_AGC)) { + case 0: + return V253_VLS_T; + case (1 << CX20442_SPKOUT): + case (1 << CX20442_MIC): + case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): + return V253_VLS_M1S1; + case (1 << CX20442_TELOUT): + case (1 << CX20442_TELIN): + case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN): + return V253_VLS_L; + case (1 << CX20442_TELOUT) | (1 << CX20442_MIC): + return V253_VLS_NONE; + } + return -EINVAL; +} +static int cx20442_pm_to_v253_vsp(u8 value) +{ + switch (value & ~(1 << CX20442_AGC)) { + case (1 << CX20442_SPKOUT): + case (1 << CX20442_MIC): + case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): + return (bool)(value & (1 << CX20442_AGC)); + } + return (value & (1 << CX20442_AGC)) ? -EINVAL : 0; +} + +static int cx20442_write(struct snd_soc_component *component, unsigned int reg, + unsigned int value) +{ + struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); + int vls, vsp, old, len; + char buf[18]; + + if (reg >= 1) + return -EINVAL; + + /* tty and write pointers required for talking to the modem + * are expected to be set by the line discipline initialization code */ + if (!cx20442->tty || !cx20442->tty->ops->write) + return -EIO; + + old = cx20442->reg_cache; + cx20442->reg_cache = value; + + vls = cx20442_pm_to_v253_vls(value); + if (vls < 0) + return vls; + + vsp = cx20442_pm_to_v253_vsp(value); + if (vsp < 0) + return vsp; + + if ((vls == V253_VLS_T) || + (vls == cx20442_pm_to_v253_vls(old))) { + if (vsp == cx20442_pm_to_v253_vsp(old)) + return 0; + len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp); + } else if (vsp == cx20442_pm_to_v253_vsp(old)) + len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls); + else + len = snprintf(buf, ARRAY_SIZE(buf), + "at+vls=%d;+vsp=%d\r", vls, vsp); + + if (unlikely(len > (ARRAY_SIZE(buf) - 1))) + return -ENOMEM; + + dev_dbg(component->dev, "%s: %s\n", __func__, buf); + if (cx20442->tty->ops->write(cx20442->tty, buf, len) != len) + return -EIO; + + return 0; +} + +/* + * Line discpline related code + * + * Any of the callback functions below can be used in two ways: + * 1) registerd by a machine driver as one of line discipline operations, + * 2) called from a machine's provided line discipline callback function + * in case when extra machine specific code must be run as well. + */ + +/* Modem init: echo off, digital speaker off, quiet off, voice mode */ +static const char *v253_init = "ate0m0q0+fclass=8\r"; + +/* Line discipline .open() */ +static int v253_open(struct tty_struct *tty) +{ + int ret, len = strlen(v253_init); + + /* Doesn't make sense without write callback */ + if (!tty->ops->write) + return -EINVAL; + + /* Won't work if no codec pointer has been passed by a card driver */ + if (!tty->disc_data) + return -ENODEV; + + tty->receive_room = 16; + if (tty->ops->write(tty, v253_init, len) != len) { + ret = -EIO; + goto err; + } + /* Actual setup will be performed after the modem responds. */ + return 0; +err: + tty->disc_data = NULL; + return ret; +} + +/* Line discipline .close() */ +static void v253_close(struct tty_struct *tty) +{ + struct snd_soc_component *component = tty->disc_data; + struct cx20442_priv *cx20442; + + tty->disc_data = NULL; + + if (!component) + return; + + cx20442 = snd_soc_component_get_drvdata(component); + + /* Prevent the codec driver from further accessing the modem */ + cx20442->tty = NULL; + component->card->pop_time = 0; +} + +/* Line discipline .hangup() */ +static int v253_hangup(struct tty_struct *tty) +{ + v253_close(tty); + return 0; +} + +/* Line discipline .receive_buf() */ +static void v253_receive(struct tty_struct *tty, + const unsigned char *cp, char *fp, int count) +{ + struct snd_soc_component *component = tty->disc_data; + struct cx20442_priv *cx20442; + + if (!component) + return; + + cx20442 = snd_soc_component_get_drvdata(component); + + if (!cx20442->tty) { + /* First modem response, complete setup procedure */ + + /* Set up codec driver access to modem controls */ + cx20442->tty = tty; + component->card->pop_time = 1; + } +} + +/* Line discipline .write_wakeup() */ +static void v253_wakeup(struct tty_struct *tty) +{ +} + +struct tty_ldisc_ops v253_ops = { + .magic = TTY_LDISC_MAGIC, + .name = "cx20442", + .owner = THIS_MODULE, + .open = v253_open, + .close = v253_close, + .hangup = v253_hangup, + .receive_buf = v253_receive, + .write_wakeup = v253_wakeup, +}; +EXPORT_SYMBOL_GPL(v253_ops); + + +/* + * Codec DAI + */ + +static struct snd_soc_dai_driver cx20442_dai = { + .name = "cx20442-voice", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static int cx20442_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); + int err = 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_STANDBY) + break; + if (IS_ERR(cx20442->por)) + err = PTR_ERR(cx20442->por); + else + err = regulator_enable(cx20442->por); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_PREPARE) + break; + if (IS_ERR(cx20442->por)) + err = PTR_ERR(cx20442->por); + else + err = regulator_disable(cx20442->por); + break; + default: + break; + } + + return err; +} + +static int cx20442_component_probe(struct snd_soc_component *component) +{ + struct cx20442_priv *cx20442; + + cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL); + if (cx20442 == NULL) + return -ENOMEM; + + cx20442->por = regulator_get(component->dev, "POR"); + if (IS_ERR(cx20442->por)) { + int err = PTR_ERR(cx20442->por); + + dev_warn(component->dev, "failed to get POR supply (%d)", err); + /* + * When running on a non-dt platform and requested regulator + * is not available, regulator_get() never returns + * -EPROBE_DEFER as it is not able to justify if the regulator + * may still appear later. On the other hand, the board can + * still set full constraints flag at late_initcall in order + * to instruct regulator_get() to return a dummy one if + * sufficient. Hence, if we get -ENODEV here, let's convert + * it to -EPROBE_DEFER and wait for the board to decide or + * let Deferred Probe infrastructure handle this error. + */ + if (err == -ENODEV) + err = -EPROBE_DEFER; + kfree(cx20442); + return err; + } + + cx20442->tty = NULL; + + snd_soc_component_set_drvdata(component, cx20442); + component->card->pop_time = 0; + + return 0; +} + +/* power down chip */ +static void cx20442_component_remove(struct snd_soc_component *component) +{ + struct cx20442_priv *cx20442 = snd_soc_component_get_drvdata(component); + + if (cx20442->tty) { + struct tty_struct *tty = cx20442->tty; + tty_hangup(tty); + } + + if (!IS_ERR(cx20442->por)) { + /* should be already in STANDBY, hence disabled */ + regulator_put(cx20442->por); + } + + snd_soc_component_set_drvdata(component, NULL); + kfree(cx20442); +} + +static const struct snd_soc_component_driver cx20442_component_dev = { + .probe = cx20442_component_probe, + .remove = cx20442_component_remove, + .set_bias_level = cx20442_set_bias_level, + .read = cx20442_read_reg_cache, + .write = cx20442_write, + .dapm_widgets = cx20442_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cx20442_dapm_widgets), + .dapm_routes = cx20442_audio_map, + .num_dapm_routes = ARRAY_SIZE(cx20442_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int cx20442_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &cx20442_component_dev, &cx20442_dai, 1); +} + +static struct platform_driver cx20442_platform_driver = { + .driver = { + .name = "cx20442-codec", + }, + .probe = cx20442_platform_probe, +}; + +module_platform_driver(cx20442_platform_driver); + +MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver"); +MODULE_AUTHOR("Janusz Krzysztofik"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cx20442-codec"); diff --git a/sound/soc/codecs/cx20442.h b/sound/soc/codecs/cx20442.h new file mode 100644 index 000000000..c7a7c79ef --- /dev/null +++ b/sound/soc/codecs/cx20442.h @@ -0,0 +1,18 @@ +/* + * cx20442.h -- audio driver for CX20442 + * + * Copyright 2009 Janusz Krzysztofik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _CX20442_CODEC_H +#define _CX20442_CODEC_H + +extern struct tty_ldisc_ops v253_ops; + +#endif diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c new file mode 100644 index 000000000..e172913d0 --- /dev/null +++ b/sound/soc/codecs/da7210.c @@ -0,0 +1,1361 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// DA7210 ALSA Soc codec driver +// +// Copyright (c) 2009 Dialog Semiconductor +// Written by David Chen +// +// Copyright (C) 2009 Renesas Solutions Corp. +// Cleanups by Kuninori Morimoto +// +// Tested on SuperH Ecovec24 board with S16/S24 LE in 48KHz using I2S + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DA7210 register space */ +#define DA7210_PAGE_CONTROL 0x00 +#define DA7210_CONTROL 0x01 +#define DA7210_STATUS 0x02 +#define DA7210_STARTUP1 0x03 +#define DA7210_STARTUP2 0x04 +#define DA7210_STARTUP3 0x05 +#define DA7210_MIC_L 0x07 +#define DA7210_MIC_R 0x08 +#define DA7210_AUX1_L 0x09 +#define DA7210_AUX1_R 0x0A +#define DA7210_AUX2 0x0B +#define DA7210_IN_GAIN 0x0C +#define DA7210_INMIX_L 0x0D +#define DA7210_INMIX_R 0x0E +#define DA7210_ADC_HPF 0x0F +#define DA7210_ADC 0x10 +#define DA7210_ADC_EQ1_2 0X11 +#define DA7210_ADC_EQ3_4 0x12 +#define DA7210_ADC_EQ5 0x13 +#define DA7210_DAC_HPF 0x14 +#define DA7210_DAC_L 0x15 +#define DA7210_DAC_R 0x16 +#define DA7210_DAC_SEL 0x17 +#define DA7210_SOFTMUTE 0x18 +#define DA7210_DAC_EQ1_2 0x19 +#define DA7210_DAC_EQ3_4 0x1A +#define DA7210_DAC_EQ5 0x1B +#define DA7210_OUTMIX_L 0x1C +#define DA7210_OUTMIX_R 0x1D +#define DA7210_OUT1_L 0x1E +#define DA7210_OUT1_R 0x1F +#define DA7210_OUT2 0x20 +#define DA7210_HP_L_VOL 0x21 +#define DA7210_HP_R_VOL 0x22 +#define DA7210_HP_CFG 0x23 +#define DA7210_ZERO_CROSS 0x24 +#define DA7210_DAI_SRC_SEL 0x25 +#define DA7210_DAI_CFG1 0x26 +#define DA7210_DAI_CFG3 0x28 +#define DA7210_PLL_DIV1 0x29 +#define DA7210_PLL_DIV2 0x2A +#define DA7210_PLL_DIV3 0x2B +#define DA7210_PLL 0x2C +#define DA7210_ALC_MAX 0x83 +#define DA7210_ALC_MIN 0x84 +#define DA7210_ALC_NOIS 0x85 +#define DA7210_ALC_ATT 0x86 +#define DA7210_ALC_REL 0x87 +#define DA7210_ALC_DEL 0x88 +#define DA7210_A_HID_UNLOCK 0x8A +#define DA7210_A_TEST_UNLOCK 0x8B +#define DA7210_A_PLL1 0x90 +#define DA7210_A_CP_MODE 0xA7 + +/* STARTUP1 bit fields */ +#define DA7210_SC_MST_EN (1 << 0) + +/* MIC_L bit fields */ +#define DA7210_MICBIAS_EN (1 << 6) +#define DA7210_MIC_L_EN (1 << 7) + +/* MIC_R bit fields */ +#define DA7210_MIC_R_EN (1 << 7) + +/* INMIX_L bit fields */ +#define DA7210_IN_L_EN (1 << 7) + +/* INMIX_R bit fields */ +#define DA7210_IN_R_EN (1 << 7) + +/* ADC bit fields */ +#define DA7210_ADC_ALC_EN (1 << 0) +#define DA7210_ADC_L_EN (1 << 3) +#define DA7210_ADC_R_EN (1 << 7) + +/* DAC/ADC HPF fields */ +#define DA7210_VOICE_F0_MASK (0x7 << 4) +#define DA7210_VOICE_F0_25 (1 << 4) +#define DA7210_VOICE_EN (1 << 7) + +/* DAC_SEL bit fields */ +#define DA7210_DAC_L_SRC_DAI_L (4 << 0) +#define DA7210_DAC_L_EN (1 << 3) +#define DA7210_DAC_R_SRC_DAI_R (5 << 4) +#define DA7210_DAC_R_EN (1 << 7) + +/* OUTMIX_L bit fields */ +#define DA7210_OUT_L_EN (1 << 7) + +/* OUTMIX_R bit fields */ +#define DA7210_OUT_R_EN (1 << 7) + +/* HP_CFG bit fields */ +#define DA7210_HP_2CAP_MODE (1 << 1) +#define DA7210_HP_SENSE_EN (1 << 2) +#define DA7210_HP_L_EN (1 << 3) +#define DA7210_HP_MODE (1 << 6) +#define DA7210_HP_R_EN (1 << 7) + +/* DAI_SRC_SEL bit fields */ +#define DA7210_DAI_OUT_L_SRC (6 << 0) +#define DA7210_DAI_OUT_R_SRC (7 << 4) + +/* DAI_CFG1 bit fields */ +#define DA7210_DAI_WORD_S16_LE (0 << 0) +#define DA7210_DAI_WORD_S20_3LE (1 << 0) +#define DA7210_DAI_WORD_S24_LE (2 << 0) +#define DA7210_DAI_WORD_S32_LE (3 << 0) +#define DA7210_DAI_FLEN_64BIT (1 << 2) +#define DA7210_DAI_MODE_SLAVE (0 << 7) +#define DA7210_DAI_MODE_MASTER (1 << 7) + +/* DAI_CFG3 bit fields */ +#define DA7210_DAI_FORMAT_I2SMODE (0 << 0) +#define DA7210_DAI_FORMAT_LEFT_J (1 << 0) +#define DA7210_DAI_FORMAT_RIGHT_J (2 << 0) +#define DA7210_DAI_OE (1 << 3) +#define DA7210_DAI_EN (1 << 7) + +/*PLL_DIV3 bit fields */ +#define DA7210_PLL_DIV_L_MASK (0xF << 0) +#define DA7210_MCLK_RANGE_10_20_MHZ (1 << 4) +#define DA7210_PLL_BYP (1 << 6) + +/* PLL bit fields */ +#define DA7210_PLL_FS_MASK (0xF << 0) +#define DA7210_PLL_FS_8000 (0x1 << 0) +#define DA7210_PLL_FS_11025 (0x2 << 0) +#define DA7210_PLL_FS_12000 (0x3 << 0) +#define DA7210_PLL_FS_16000 (0x5 << 0) +#define DA7210_PLL_FS_22050 (0x6 << 0) +#define DA7210_PLL_FS_24000 (0x7 << 0) +#define DA7210_PLL_FS_32000 (0x9 << 0) +#define DA7210_PLL_FS_44100 (0xA << 0) +#define DA7210_PLL_FS_48000 (0xB << 0) +#define DA7210_PLL_FS_88200 (0xE << 0) +#define DA7210_PLL_FS_96000 (0xF << 0) +#define DA7210_MCLK_DET_EN (0x1 << 5) +#define DA7210_MCLK_SRM_EN (0x1 << 6) +#define DA7210_PLL_EN (0x1 << 7) + +/* SOFTMUTE bit fields */ +#define DA7210_RAMP_EN (1 << 6) + +/* CONTROL bit fields */ +#define DA7210_REG_EN (1 << 0) +#define DA7210_BIAS_EN (1 << 2) +#define DA7210_NOISE_SUP_EN (1 << 3) + +/* IN_GAIN bit fields */ +#define DA7210_INPGA_L_VOL (0x0F << 0) +#define DA7210_INPGA_R_VOL (0xF0 << 0) + +/* ZERO_CROSS bit fields */ +#define DA7210_AUX1_L_ZC (1 << 0) +#define DA7210_AUX1_R_ZC (1 << 1) +#define DA7210_HP_L_ZC (1 << 6) +#define DA7210_HP_R_ZC (1 << 7) + +/* AUX1_L bit fields */ +#define DA7210_AUX1_L_VOL (0x3F << 0) +#define DA7210_AUX1_L_EN (1 << 7) + +/* AUX1_R bit fields */ +#define DA7210_AUX1_R_VOL (0x3F << 0) +#define DA7210_AUX1_R_EN (1 << 7) + +/* AUX2 bit fields */ +#define DA7210_AUX2_EN (1 << 3) + +/* Minimum INPGA and AUX1 volume to enable noise suppression */ +#define DA7210_INPGA_MIN_VOL_NS 0x0A /* 10.5dB */ +#define DA7210_AUX1_MIN_VOL_NS 0x35 /* 6dB */ + +/* OUT1_L bit fields */ +#define DA7210_OUT1_L_EN (1 << 7) + +/* OUT1_R bit fields */ +#define DA7210_OUT1_R_EN (1 << 7) + +/* OUT2 bit fields */ +#define DA7210_OUT2_OUTMIX_R (1 << 5) +#define DA7210_OUT2_OUTMIX_L (1 << 6) +#define DA7210_OUT2_EN (1 << 7) + +struct pll_div { + int fref; + int fout; + u8 div1; + u8 div2; + u8 div3; + u8 mode; /* 0 = slave, 1 = master */ +}; + +/* PLL dividers table */ +static const struct pll_div da7210_pll_div[] = { + /* for MASTER mode, fs = 44.1Khz */ + { 12000000, 2822400, 0xE8, 0x6C, 0x2, 1}, /* MCLK=12Mhz */ + { 13000000, 2822400, 0xDF, 0x28, 0xC, 1}, /* MCLK=13Mhz */ + { 13500000, 2822400, 0xDB, 0x0A, 0xD, 1}, /* MCLK=13.5Mhz */ + { 14400000, 2822400, 0xD4, 0x5A, 0x2, 1}, /* MCLK=14.4Mhz */ + { 19200000, 2822400, 0xBB, 0x43, 0x9, 1}, /* MCLK=19.2Mhz */ + { 19680000, 2822400, 0xB9, 0x6D, 0xA, 1}, /* MCLK=19.68Mhz */ + { 19800000, 2822400, 0xB8, 0xFB, 0xB, 1}, /* MCLK=19.8Mhz */ + /* for MASTER mode, fs = 48Khz */ + { 12000000, 3072000, 0xF3, 0x12, 0x7, 1}, /* MCLK=12Mhz */ + { 13000000, 3072000, 0xE8, 0xFD, 0x5, 1}, /* MCLK=13Mhz */ + { 13500000, 3072000, 0xE4, 0x82, 0x3, 1}, /* MCLK=13.5Mhz */ + { 14400000, 3072000, 0xDD, 0x3A, 0x0, 1}, /* MCLK=14.4Mhz */ + { 19200000, 3072000, 0xC1, 0xEB, 0x8, 1}, /* MCLK=19.2Mhz */ + { 19680000, 3072000, 0xBF, 0xEC, 0x0, 1}, /* MCLK=19.68Mhz */ + { 19800000, 3072000, 0xBF, 0x70, 0x0, 1}, /* MCLK=19.8Mhz */ + /* for SLAVE mode with SRM */ + { 12000000, 2822400, 0xED, 0xBF, 0x5, 0}, /* MCLK=12Mhz */ + { 13000000, 2822400, 0xE4, 0x13, 0x0, 0}, /* MCLK=13Mhz */ + { 13500000, 2822400, 0xDF, 0xC6, 0x8, 0}, /* MCLK=13.5Mhz */ + { 14400000, 2822400, 0xD8, 0xCA, 0x1, 0}, /* MCLK=14.4Mhz */ + { 19200000, 2822400, 0xBE, 0x97, 0x9, 0}, /* MCLK=19.2Mhz */ + { 19680000, 2822400, 0xBC, 0xAC, 0xD, 0}, /* MCLK=19.68Mhz */ + { 19800000, 2822400, 0xBC, 0x35, 0xE, 0}, /* MCLK=19.8Mhz */ +}; + +enum clk_src { + DA7210_CLKSRC_MCLK +}; + +#define DA7210_VERSION "0.0.1" + +/* + * Playback Volume + * + * max : 0x3F (+15.0 dB) + * (1.5 dB step) + * min : 0x11 (-54.0 dB) + * mute : 0x10 + * reserved : 0x00 - 0x0F + * + * Reserved area are considered as "mute". + */ +static const DECLARE_TLV_DB_RANGE(hp_out_tlv, + 0x0, 0x10, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* -54 dB to +15 dB */ + 0x11, 0x3f, TLV_DB_SCALE_ITEM(-5400, 150, 0) +); + +static const DECLARE_TLV_DB_RANGE(lineout_vol_tlv, + 0x0, 0x10, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* -54dB to 15dB */ + 0x11, 0x3f, TLV_DB_SCALE_ITEM(-5400, 150, 0) +); + +static const DECLARE_TLV_DB_RANGE(mono_vol_tlv, + 0x0, 0x2, TLV_DB_SCALE_ITEM(-1800, 0, 1), + /* -18dB to 6dB */ + 0x3, 0x7, TLV_DB_SCALE_ITEM(-1800, 600, 0) +); + +static const DECLARE_TLV_DB_RANGE(aux1_vol_tlv, + 0x0, 0x10, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* -48dB to 21dB */ + 0x11, 0x3f, TLV_DB_SCALE_ITEM(-4800, 150, 0) +); + +static const DECLARE_TLV_DB_SCALE(eq_gain_tlv, -1050, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_eq_master_gain_tlv, -1800, 600, 1); +static const DECLARE_TLV_DB_SCALE(dac_gain_tlv, -7725, 75, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(aux2_vol_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(inpga_gain_tlv, -450, 150, 0); + +/* ADC and DAC high pass filter f0 value */ +static const char * const da7210_hpf_cutoff_txt[] = { + "Fs/8192*pi", "Fs/4096*pi", "Fs/2048*pi", "Fs/1024*pi" +}; + +static SOC_ENUM_SINGLE_DECL(da7210_dac_hpf_cutoff, + DA7210_DAC_HPF, 0, da7210_hpf_cutoff_txt); + +static SOC_ENUM_SINGLE_DECL(da7210_adc_hpf_cutoff, + DA7210_ADC_HPF, 0, da7210_hpf_cutoff_txt); + +/* ADC and DAC voice (8kHz) high pass cutoff value */ +static const char * const da7210_vf_cutoff_txt[] = { + "2.5Hz", "25Hz", "50Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz" +}; + +static SOC_ENUM_SINGLE_DECL(da7210_dac_vf_cutoff, + DA7210_DAC_HPF, 4, da7210_vf_cutoff_txt); + +static SOC_ENUM_SINGLE_DECL(da7210_adc_vf_cutoff, + DA7210_ADC_HPF, 4, da7210_vf_cutoff_txt); + +static const char *da7210_hp_mode_txt[] = { + "Class H", "Class G" +}; + +static SOC_ENUM_SINGLE_DECL(da7210_hp_mode_sel, + DA7210_HP_CFG, 0, da7210_hp_mode_txt); + +/* ALC can be enabled only if noise suppression is disabled */ +static int da7210_put_alc_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + + if (ucontrol->value.integer.value[0]) { + /* Check if noise suppression is enabled */ + if (snd_soc_component_read32(component, DA7210_CONTROL) & DA7210_NOISE_SUP_EN) { + dev_dbg(component->dev, + "Disable noise suppression to enable ALC\n"); + return -EINVAL; + } + } + /* If all conditions are met or we are actually disabling ALC */ + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +/* Noise suppression can be enabled only if following conditions are met + * ALC disabled + * ZC enabled for HP and AUX1 PGA + * INPGA_L_VOL and INPGA_R_VOL >= 10.5 dB + * AUX1_L_VOL and AUX1_R_VOL >= 6 dB + */ +static int da7210_put_noise_sup_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + u8 val; + + if (ucontrol->value.integer.value[0]) { + /* Check if ALC is enabled */ + if (snd_soc_component_read32(component, DA7210_ADC) & DA7210_ADC_ALC_EN) + goto err; + + /* Check ZC for HP and AUX1 PGA */ + if ((snd_soc_component_read32(component, DA7210_ZERO_CROSS) & + (DA7210_AUX1_L_ZC | DA7210_AUX1_R_ZC | DA7210_HP_L_ZC | + DA7210_HP_R_ZC)) != (DA7210_AUX1_L_ZC | + DA7210_AUX1_R_ZC | DA7210_HP_L_ZC | DA7210_HP_R_ZC)) + goto err; + + /* Check INPGA_L_VOL and INPGA_R_VOL */ + val = snd_soc_component_read32(component, DA7210_IN_GAIN); + if (((val & DA7210_INPGA_L_VOL) < DA7210_INPGA_MIN_VOL_NS) || + (((val & DA7210_INPGA_R_VOL) >> 4) < + DA7210_INPGA_MIN_VOL_NS)) + goto err; + + /* Check AUX1_L_VOL and AUX1_R_VOL */ + if (((snd_soc_component_read32(component, DA7210_AUX1_L) & DA7210_AUX1_L_VOL) < + DA7210_AUX1_MIN_VOL_NS) || + ((snd_soc_component_read32(component, DA7210_AUX1_R) & DA7210_AUX1_R_VOL) < + DA7210_AUX1_MIN_VOL_NS)) + goto err; + } + /* If all conditions are met or we are actually disabling Noise sup */ + return snd_soc_put_volsw(kcontrol, ucontrol); + +err: + return -EINVAL; +} + +static const struct snd_kcontrol_new da7210_snd_controls[] = { + + SOC_DOUBLE_R_TLV("HeadPhone Playback Volume", + DA7210_HP_L_VOL, DA7210_HP_R_VOL, + 0, 0x3F, 0, hp_out_tlv), + SOC_DOUBLE_R_TLV("Digital Playback Volume", + DA7210_DAC_L, DA7210_DAC_R, + 0, 0x77, 1, dac_gain_tlv), + SOC_DOUBLE_R_TLV("Lineout Playback Volume", + DA7210_OUT1_L, DA7210_OUT1_R, + 0, 0x3f, 0, lineout_vol_tlv), + SOC_SINGLE_TLV("Mono Playback Volume", DA7210_OUT2, 0, 0x7, 0, + mono_vol_tlv), + + SOC_DOUBLE_R_TLV("Mic Capture Volume", + DA7210_MIC_L, DA7210_MIC_R, + 0, 0x5, 0, mic_vol_tlv), + SOC_DOUBLE_R_TLV("Aux1 Capture Volume", + DA7210_AUX1_L, DA7210_AUX1_R, + 0, 0x3f, 0, aux1_vol_tlv), + SOC_SINGLE_TLV("Aux2 Capture Volume", DA7210_AUX2, 0, 0x3, 0, + aux2_vol_tlv), + SOC_DOUBLE_TLV("In PGA Capture Volume", DA7210_IN_GAIN, 0, 4, 0xF, 0, + inpga_gain_tlv), + + /* DAC Equalizer controls */ + SOC_SINGLE("DAC EQ Switch", DA7210_DAC_EQ5, 7, 1, 0), + SOC_SINGLE_TLV("DAC EQ1 Volume", DA7210_DAC_EQ1_2, 0, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ2 Volume", DA7210_DAC_EQ1_2, 4, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ3 Volume", DA7210_DAC_EQ3_4, 0, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ4 Volume", DA7210_DAC_EQ3_4, 4, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ5 Volume", DA7210_DAC_EQ5, 0, 0xf, 1, + eq_gain_tlv), + + /* ADC Equalizer controls */ + SOC_SINGLE("ADC EQ Switch", DA7210_ADC_EQ5, 7, 1, 0), + SOC_SINGLE_TLV("ADC EQ Master Volume", DA7210_ADC_EQ5, 4, 0x3, + 1, adc_eq_master_gain_tlv), + SOC_SINGLE_TLV("ADC EQ1 Volume", DA7210_ADC_EQ1_2, 0, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("ADC EQ2 Volume", DA7210_ADC_EQ1_2, 4, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("ADC EQ3 Volume", DA7210_ADC_EQ3_4, 0, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("ADC EQ4 Volume", DA7210_ADC_EQ3_4, 4, 0xf, 1, + eq_gain_tlv), + SOC_SINGLE_TLV("ADC EQ5 Volume", DA7210_ADC_EQ5, 0, 0xf, 1, + eq_gain_tlv), + + SOC_SINGLE("DAC HPF Switch", DA7210_DAC_HPF, 3, 1, 0), + SOC_ENUM("DAC HPF Cutoff", da7210_dac_hpf_cutoff), + SOC_SINGLE("DAC Voice Mode Switch", DA7210_DAC_HPF, 7, 1, 0), + SOC_ENUM("DAC Voice Cutoff", da7210_dac_vf_cutoff), + + SOC_SINGLE("ADC HPF Switch", DA7210_ADC_HPF, 3, 1, 0), + SOC_ENUM("ADC HPF Cutoff", da7210_adc_hpf_cutoff), + SOC_SINGLE("ADC Voice Mode Switch", DA7210_ADC_HPF, 7, 1, 0), + SOC_ENUM("ADC Voice Cutoff", da7210_adc_vf_cutoff), + + /* Mute controls */ + SOC_DOUBLE_R("Mic Capture Switch", DA7210_MIC_L, DA7210_MIC_R, 3, 1, 0), + SOC_SINGLE("Aux2 Capture Switch", DA7210_AUX2, 2, 1, 0), + SOC_DOUBLE("ADC Capture Switch", DA7210_ADC, 2, 6, 1, 0), + SOC_SINGLE("Digital Soft Mute Switch", DA7210_SOFTMUTE, 7, 1, 0), + SOC_SINGLE("Digital Soft Mute Rate", DA7210_SOFTMUTE, 0, 0x7, 0), + + /* Zero cross controls */ + SOC_DOUBLE("Aux1 ZC Switch", DA7210_ZERO_CROSS, 0, 1, 1, 0), + SOC_DOUBLE("In PGA ZC Switch", DA7210_ZERO_CROSS, 2, 3, 1, 0), + SOC_DOUBLE("Lineout ZC Switch", DA7210_ZERO_CROSS, 4, 5, 1, 0), + SOC_DOUBLE("Headphone ZC Switch", DA7210_ZERO_CROSS, 6, 7, 1, 0), + + SOC_ENUM("Headphone Class", da7210_hp_mode_sel), + + /* ALC controls */ + SOC_SINGLE_EXT("ALC Enable Switch", DA7210_ADC, 0, 1, 0, + snd_soc_get_volsw, da7210_put_alc_sw), + SOC_SINGLE("ALC Capture Max Volume", DA7210_ALC_MAX, 0, 0x3F, 0), + SOC_SINGLE("ALC Capture Min Volume", DA7210_ALC_MIN, 0, 0x3F, 0), + SOC_SINGLE("ALC Capture Noise Volume", DA7210_ALC_NOIS, 0, 0x3F, 0), + SOC_SINGLE("ALC Capture Attack Rate", DA7210_ALC_ATT, 0, 0xFF, 0), + SOC_SINGLE("ALC Capture Release Rate", DA7210_ALC_REL, 0, 0xFF, 0), + SOC_SINGLE("ALC Capture Release Delay", DA7210_ALC_DEL, 0, 0xFF, 0), + + SOC_SINGLE_EXT("Noise Suppression Enable Switch", DA7210_CONTROL, 3, 1, + 0, snd_soc_get_volsw, da7210_put_noise_sup_sw), +}; + +/* + * DAPM Controls + * + * Current DAPM implementation covers almost all codec components e.g. IOs, + * mixers, PGAs,ADC and DAC. + */ +/* In Mixer Left */ +static const struct snd_kcontrol_new da7210_dapm_inmixl_controls[] = { + SOC_DAPM_SINGLE("Mic Left Switch", DA7210_INMIX_L, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Right Switch", DA7210_INMIX_L, 1, 1, 0), + SOC_DAPM_SINGLE("Aux1 Left Switch", DA7210_INMIX_L, 2, 1, 0), + SOC_DAPM_SINGLE("Aux2 Switch", DA7210_INMIX_L, 3, 1, 0), + SOC_DAPM_SINGLE("Outmix Left Switch", DA7210_INMIX_L, 4, 1, 0), +}; + +/* In Mixer Right */ +static const struct snd_kcontrol_new da7210_dapm_inmixr_controls[] = { + SOC_DAPM_SINGLE("Mic Right Switch", DA7210_INMIX_R, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Left Switch", DA7210_INMIX_R, 1, 1, 0), + SOC_DAPM_SINGLE("Aux1 Right Switch", DA7210_INMIX_R, 2, 1, 0), + SOC_DAPM_SINGLE("Aux2 Switch", DA7210_INMIX_R, 3, 1, 0), + SOC_DAPM_SINGLE("Outmix Right Switch", DA7210_INMIX_R, 4, 1, 0), +}; + +/* Out Mixer Left */ +static const struct snd_kcontrol_new da7210_dapm_outmixl_controls[] = { + SOC_DAPM_SINGLE("Aux1 Left Switch", DA7210_OUTMIX_L, 0, 1, 0), + SOC_DAPM_SINGLE("Aux2 Switch", DA7210_OUTMIX_L, 1, 1, 0), + SOC_DAPM_SINGLE("INPGA Left Switch", DA7210_OUTMIX_L, 2, 1, 0), + SOC_DAPM_SINGLE("INPGA Right Switch", DA7210_OUTMIX_L, 3, 1, 0), + SOC_DAPM_SINGLE("DAC Left Switch", DA7210_OUTMIX_L, 4, 1, 0), +}; + +/* Out Mixer Right */ +static const struct snd_kcontrol_new da7210_dapm_outmixr_controls[] = { + SOC_DAPM_SINGLE("Aux1 Right Switch", DA7210_OUTMIX_R, 0, 1, 0), + SOC_DAPM_SINGLE("Aux2 Switch", DA7210_OUTMIX_R, 1, 1, 0), + SOC_DAPM_SINGLE("INPGA Left Switch", DA7210_OUTMIX_R, 2, 1, 0), + SOC_DAPM_SINGLE("INPGA Right Switch", DA7210_OUTMIX_R, 3, 1, 0), + SOC_DAPM_SINGLE("DAC Right Switch", DA7210_OUTMIX_R, 4, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new da7210_dapm_monomix_controls[] = { + SOC_DAPM_SINGLE("INPGA Right Switch", DA7210_OUT2, 3, 1, 0), + SOC_DAPM_SINGLE("INPGA Left Switch", DA7210_OUT2, 4, 1, 0), + SOC_DAPM_SINGLE("Outmix Right Switch", DA7210_OUT2, 5, 1, 0), + SOC_DAPM_SINGLE("Outmix Left Switch", DA7210_OUT2, 6, 1, 0), +}; + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget da7210_dapm_widgets[] = { + /* Input Side */ + /* Input Lines */ + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + SND_SOC_DAPM_INPUT("AUX1L"), + SND_SOC_DAPM_INPUT("AUX1R"), + SND_SOC_DAPM_INPUT("AUX2"), + + /* Input PGAs */ + SND_SOC_DAPM_PGA("Mic Left", DA7210_STARTUP3, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Mic Right", DA7210_STARTUP3, 1, 1, NULL, 0), + SND_SOC_DAPM_PGA("Aux1 Left", DA7210_STARTUP3, 2, 1, NULL, 0), + SND_SOC_DAPM_PGA("Aux1 Right", DA7210_STARTUP3, 3, 1, NULL, 0), + SND_SOC_DAPM_PGA("Aux2 Mono", DA7210_STARTUP3, 4, 1, NULL, 0), + + SND_SOC_DAPM_PGA("INPGA Left", DA7210_INMIX_L, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("INPGA Right", DA7210_INMIX_R, 7, 0, NULL, 0), + + /* MICBIAS */ + SND_SOC_DAPM_SUPPLY("Mic Bias", DA7210_MIC_L, 6, 0, NULL, 0), + + /* Input Mixers */ + SND_SOC_DAPM_MIXER("In Mixer Left", SND_SOC_NOPM, 0, 0, + &da7210_dapm_inmixl_controls[0], + ARRAY_SIZE(da7210_dapm_inmixl_controls)), + + SND_SOC_DAPM_MIXER("In Mixer Right", SND_SOC_NOPM, 0, 0, + &da7210_dapm_inmixr_controls[0], + ARRAY_SIZE(da7210_dapm_inmixr_controls)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC Left", "Capture", DA7210_STARTUP3, 5, 1), + SND_SOC_DAPM_ADC("ADC Right", "Capture", DA7210_STARTUP3, 6, 1), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("DAC Left", "Playback", DA7210_STARTUP2, 5, 1), + SND_SOC_DAPM_DAC("DAC Right", "Playback", DA7210_STARTUP2, 6, 1), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Out Mixer Left", SND_SOC_NOPM, 0, 0, + &da7210_dapm_outmixl_controls[0], + ARRAY_SIZE(da7210_dapm_outmixl_controls)), + + SND_SOC_DAPM_MIXER("Out Mixer Right", SND_SOC_NOPM, 0, 0, + &da7210_dapm_outmixr_controls[0], + ARRAY_SIZE(da7210_dapm_outmixr_controls)), + + SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, + &da7210_dapm_monomix_controls[0], + ARRAY_SIZE(da7210_dapm_monomix_controls)), + + /* Output PGAs */ + SND_SOC_DAPM_PGA("OUTPGA Left Enable", DA7210_OUTMIX_L, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUTPGA Right Enable", DA7210_OUTMIX_R, 7, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Out1 Left", DA7210_STARTUP2, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Out1 Right", DA7210_STARTUP2, 1, 1, NULL, 0), + SND_SOC_DAPM_PGA("Out2 Mono", DA7210_STARTUP2, 2, 1, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Left", DA7210_STARTUP2, 3, 1, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Right", DA7210_STARTUP2, 4, 1, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("OUT1L"), + SND_SOC_DAPM_OUTPUT("OUT1R"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("OUT2"), +}; + +/* DAPM audio route definition */ +static const struct snd_soc_dapm_route da7210_audio_map[] = { + /* Dest Connecting Widget source */ + /* Input path */ + {"Mic Left", NULL, "MICL"}, + {"Mic Right", NULL, "MICR"}, + {"Aux1 Left", NULL, "AUX1L"}, + {"Aux1 Right", NULL, "AUX1R"}, + {"Aux2 Mono", NULL, "AUX2"}, + + {"In Mixer Left", "Mic Left Switch", "Mic Left"}, + {"In Mixer Left", "Mic Right Switch", "Mic Right"}, + {"In Mixer Left", "Aux1 Left Switch", "Aux1 Left"}, + {"In Mixer Left", "Aux2 Switch", "Aux2 Mono"}, + {"In Mixer Left", "Outmix Left Switch", "Out Mixer Left"}, + + {"In Mixer Right", "Mic Right Switch", "Mic Right"}, + {"In Mixer Right", "Mic Left Switch", "Mic Left"}, + {"In Mixer Right", "Aux1 Right Switch", "Aux1 Right"}, + {"In Mixer Right", "Aux2 Switch", "Aux2 Mono"}, + {"In Mixer Right", "Outmix Right Switch", "Out Mixer Right"}, + + {"INPGA Left", NULL, "In Mixer Left"}, + {"ADC Left", NULL, "INPGA Left"}, + + {"INPGA Right", NULL, "In Mixer Right"}, + {"ADC Right", NULL, "INPGA Right"}, + + /* Output path */ + {"Out Mixer Left", "Aux1 Left Switch", "Aux1 Left"}, + {"Out Mixer Left", "Aux2 Switch", "Aux2 Mono"}, + {"Out Mixer Left", "INPGA Left Switch", "INPGA Left"}, + {"Out Mixer Left", "INPGA Right Switch", "INPGA Right"}, + {"Out Mixer Left", "DAC Left Switch", "DAC Left"}, + + {"Out Mixer Right", "Aux1 Right Switch", "Aux1 Right"}, + {"Out Mixer Right", "Aux2 Switch", "Aux2 Mono"}, + {"Out Mixer Right", "INPGA Right Switch", "INPGA Right"}, + {"Out Mixer Right", "INPGA Left Switch", "INPGA Left"}, + {"Out Mixer Right", "DAC Right Switch", "DAC Right"}, + + {"Mono Mixer", "INPGA Right Switch", "INPGA Right"}, + {"Mono Mixer", "INPGA Left Switch", "INPGA Left"}, + {"Mono Mixer", "Outmix Right Switch", "Out Mixer Right"}, + {"Mono Mixer", "Outmix Left Switch", "Out Mixer Left"}, + + {"OUTPGA Left Enable", NULL, "Out Mixer Left"}, + {"OUTPGA Right Enable", NULL, "Out Mixer Right"}, + + {"Out1 Left", NULL, "OUTPGA Left Enable"}, + {"OUT1L", NULL, "Out1 Left"}, + + {"Out1 Right", NULL, "OUTPGA Right Enable"}, + {"OUT1R", NULL, "Out1 Right"}, + + {"Headphone Left", NULL, "OUTPGA Left Enable"}, + {"HPL", NULL, "Headphone Left"}, + + {"Headphone Right", NULL, "OUTPGA Right Enable"}, + {"HPR", NULL, "Headphone Right"}, + + {"Out2 Mono", NULL, "Mono Mixer"}, + {"OUT2", NULL, "Out2 Mono"}, +}; + +/* Codec private data */ +struct da7210_priv { + struct regmap *regmap; + unsigned int mclk_rate; + int master; +}; + +static const struct reg_default da7210_reg_defaults[] = { + { 0x00, 0x00 }, + { 0x01, 0x11 }, + { 0x03, 0x00 }, + { 0x04, 0x00 }, + { 0x05, 0x00 }, + { 0x06, 0x00 }, + { 0x07, 0x00 }, + { 0x08, 0x00 }, + { 0x09, 0x00 }, + { 0x0a, 0x00 }, + { 0x0b, 0x00 }, + { 0x0c, 0x00 }, + { 0x0d, 0x00 }, + { 0x0e, 0x00 }, + { 0x0f, 0x08 }, + { 0x10, 0x00 }, + { 0x11, 0x00 }, + { 0x12, 0x00 }, + { 0x13, 0x00 }, + { 0x14, 0x08 }, + { 0x15, 0x10 }, + { 0x16, 0x10 }, + { 0x17, 0x54 }, + { 0x18, 0x40 }, + { 0x19, 0x00 }, + { 0x1a, 0x00 }, + { 0x1b, 0x00 }, + { 0x1c, 0x00 }, + { 0x1d, 0x00 }, + { 0x1e, 0x00 }, + { 0x1f, 0x00 }, + { 0x20, 0x00 }, + { 0x21, 0x00 }, + { 0x22, 0x00 }, + { 0x23, 0x02 }, + { 0x24, 0x00 }, + { 0x25, 0x76 }, + { 0x26, 0x00 }, + { 0x27, 0x00 }, + { 0x28, 0x04 }, + { 0x29, 0x00 }, + { 0x2a, 0x00 }, + { 0x2b, 0x30 }, + { 0x2c, 0x2A }, + { 0x83, 0x00 }, + { 0x84, 0x00 }, + { 0x85, 0x00 }, + { 0x86, 0x00 }, + { 0x87, 0x00 }, + { 0x88, 0x00 }, +}; + +static bool da7210_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA7210_A_HID_UNLOCK: + case DA7210_A_TEST_UNLOCK: + case DA7210_A_PLL1: + case DA7210_A_CP_MODE: + return false; + default: + return true; + } +} + +static bool da7210_volatile_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case DA7210_STATUS: + return true; + default: + return false; + } +} + +/* + * Set PCM DAI word length. + */ +static int da7210_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct da7210_priv *da7210 = snd_soc_component_get_drvdata(component); + u32 dai_cfg1; + u32 fs, sysclk; + + /* set DAI source to Left and Right ADC */ + snd_soc_component_write(component, DA7210_DAI_SRC_SEL, + DA7210_DAI_OUT_R_SRC | DA7210_DAI_OUT_L_SRC); + + /* Enable DAI */ + snd_soc_component_write(component, DA7210_DAI_CFG3, DA7210_DAI_OE | DA7210_DAI_EN); + + dai_cfg1 = 0xFC & snd_soc_component_read32(component, DA7210_DAI_CFG1); + + switch (params_width(params)) { + case 16: + dai_cfg1 |= DA7210_DAI_WORD_S16_LE; + break; + case 20: + dai_cfg1 |= DA7210_DAI_WORD_S20_3LE; + break; + case 24: + dai_cfg1 |= DA7210_DAI_WORD_S24_LE; + break; + case 32: + dai_cfg1 |= DA7210_DAI_WORD_S32_LE; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, DA7210_DAI_CFG1, dai_cfg1); + + switch (params_rate(params)) { + case 8000: + fs = DA7210_PLL_FS_8000; + sysclk = 3072000; + break; + case 11025: + fs = DA7210_PLL_FS_11025; + sysclk = 2822400; + break; + case 12000: + fs = DA7210_PLL_FS_12000; + sysclk = 3072000; + break; + case 16000: + fs = DA7210_PLL_FS_16000; + sysclk = 3072000; + break; + case 22050: + fs = DA7210_PLL_FS_22050; + sysclk = 2822400; + break; + case 32000: + fs = DA7210_PLL_FS_32000; + sysclk = 3072000; + break; + case 44100: + fs = DA7210_PLL_FS_44100; + sysclk = 2822400; + break; + case 48000: + fs = DA7210_PLL_FS_48000; + sysclk = 3072000; + break; + case 88200: + fs = DA7210_PLL_FS_88200; + sysclk = 2822400; + break; + case 96000: + fs = DA7210_PLL_FS_96000; + sysclk = 3072000; + break; + default: + return -EINVAL; + } + + /* Disable active mode */ + snd_soc_component_update_bits(component, DA7210_STARTUP1, DA7210_SC_MST_EN, 0); + + snd_soc_component_update_bits(component, DA7210_PLL, DA7210_PLL_FS_MASK, fs); + + if (da7210->mclk_rate && (da7210->mclk_rate != sysclk)) { + /* PLL mode, disable PLL bypass */ + snd_soc_component_update_bits(component, DA7210_PLL_DIV3, DA7210_PLL_BYP, 0); + + if (!da7210->master) { + /* PLL slave mode, also enable SRM */ + snd_soc_component_update_bits(component, DA7210_PLL, + (DA7210_MCLK_SRM_EN | + DA7210_MCLK_DET_EN), + (DA7210_MCLK_SRM_EN | + DA7210_MCLK_DET_EN)); + } + } else { + /* PLL bypass mode, enable PLL bypass and Auto Detection */ + snd_soc_component_update_bits(component, DA7210_PLL, DA7210_MCLK_DET_EN, + DA7210_MCLK_DET_EN); + snd_soc_component_update_bits(component, DA7210_PLL_DIV3, DA7210_PLL_BYP, + DA7210_PLL_BYP); + } + /* Enable active mode */ + snd_soc_component_update_bits(component, DA7210_STARTUP1, + DA7210_SC_MST_EN, DA7210_SC_MST_EN); + + return 0; +} + +/* + * Set DAI mode and Format + */ +static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7210_priv *da7210 = snd_soc_component_get_drvdata(component); + u32 dai_cfg1; + u32 dai_cfg3; + + dai_cfg1 = 0x7f & snd_soc_component_read32(component, DA7210_DAI_CFG1); + dai_cfg3 = 0xfc & snd_soc_component_read32(component, DA7210_DAI_CFG3); + + if ((snd_soc_component_read32(component, DA7210_PLL) & DA7210_PLL_EN) && + (!(snd_soc_component_read32(component, DA7210_PLL_DIV3) & DA7210_PLL_BYP))) + return -EINVAL; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + da7210->master = 1; + dai_cfg1 |= DA7210_DAI_MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + da7210->master = 0; + dai_cfg1 |= DA7210_DAI_MODE_SLAVE; + break; + default: + return -EINVAL; + } + + /* FIXME + * + * It support I2S only now + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dai_cfg3 |= DA7210_DAI_FORMAT_I2SMODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + dai_cfg3 |= DA7210_DAI_FORMAT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dai_cfg3 |= DA7210_DAI_FORMAT_RIGHT_J; + break; + default: + return -EINVAL; + } + + /* FIXME + * + * It support 64bit data transmission only now + */ + dai_cfg1 |= DA7210_DAI_FLEN_64BIT; + + snd_soc_component_write(component, DA7210_DAI_CFG1, dai_cfg1); + snd_soc_component_write(component, DA7210_DAI_CFG3, dai_cfg3); + + return 0; +} + +static int da7210_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u8 mute_reg = snd_soc_component_read32(component, DA7210_DAC_HPF) & 0xFB; + + if (mute) + snd_soc_component_write(component, DA7210_DAC_HPF, mute_reg | 0x4); + else + snd_soc_component_write(component, DA7210_DAC_HPF, mute_reg); + return 0; +} + +#define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int da7210_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7210_priv *da7210 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case DA7210_CLKSRC_MCLK: + switch (freq) { + case 12000000: + case 13000000: + case 13500000: + case 14400000: + case 19200000: + case 19680000: + case 19800000: + da7210->mclk_rate = freq; + return 0; + default: + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; + } + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } +} + +/** + * da7210_set_dai_pll :Configure the codec PLL + * @param codec_dai : pointer to codec DAI + * @param pll_id : da7210 has only one pll, so pll_id is always zero + * @param fref : MCLK frequency, should be < 20MHz + * @param fout : FsDM value, Refer page 44 & 45 of datasheet + * @return int : Zero for success, negative error code for error + * + * Note: Supported PLL input frequencies are 12MHz, 13MHz, 13.5MHz, 14.4MHz, + * 19.2MHz, 19.6MHz and 19.8MHz + */ +static int da7210_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7210_priv *da7210 = snd_soc_component_get_drvdata(component); + + u8 pll_div1, pll_div2, pll_div3, cnt; + + /* In slave mode, there is only one set of divisors */ + if (!da7210->master) + fout = 2822400; + + /* Search pll div array for correct divisors */ + for (cnt = 0; cnt < ARRAY_SIZE(da7210_pll_div); cnt++) { + /* check fref, mode and fout */ + if ((fref == da7210_pll_div[cnt].fref) && + (da7210->master == da7210_pll_div[cnt].mode) && + (fout == da7210_pll_div[cnt].fout)) { + /* all match, pick up divisors */ + pll_div1 = da7210_pll_div[cnt].div1; + pll_div2 = da7210_pll_div[cnt].div2; + pll_div3 = da7210_pll_div[cnt].div3; + break; + } + } + if (cnt >= ARRAY_SIZE(da7210_pll_div)) + goto err; + + /* Disable active mode */ + snd_soc_component_update_bits(component, DA7210_STARTUP1, DA7210_SC_MST_EN, 0); + /* Write PLL dividers */ + snd_soc_component_write(component, DA7210_PLL_DIV1, pll_div1); + snd_soc_component_write(component, DA7210_PLL_DIV2, pll_div2); + snd_soc_component_update_bits(component, DA7210_PLL_DIV3, + DA7210_PLL_DIV_L_MASK, pll_div3); + + /* Enable PLL */ + snd_soc_component_update_bits(component, DA7210_PLL, DA7210_PLL_EN, DA7210_PLL_EN); + + /* Enable active mode */ + snd_soc_component_update_bits(component, DA7210_STARTUP1, DA7210_SC_MST_EN, + DA7210_SC_MST_EN); + return 0; +err: + dev_err(codec_dai->dev, "Unsupported PLL input frequency %d\n", fref); + return -EINVAL; +} + +/* DAI operations */ +static const struct snd_soc_dai_ops da7210_dai_ops = { + .hw_params = da7210_hw_params, + .set_fmt = da7210_set_dai_fmt, + .set_sysclk = da7210_set_dai_sysclk, + .set_pll = da7210_set_dai_pll, + .digital_mute = da7210_mute, +}; + +static struct snd_soc_dai_driver da7210_dai = { + .name = "da7210-hifi", + /* playback capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7210_FORMATS, + }, + /* capture capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7210_FORMATS, + }, + .ops = &da7210_dai_ops, + .symmetric_rates = 1, +}; + +static int da7210_probe(struct snd_soc_component *component) +{ + struct da7210_priv *da7210 = snd_soc_component_get_drvdata(component); + + dev_info(component->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION); + + da7210->mclk_rate = 0; /* This will be set from set_sysclk() */ + da7210->master = 0; /* This will be set from set_fmt() */ + + /* Enable internal regulator & bias current */ + snd_soc_component_write(component, DA7210_CONTROL, DA7210_REG_EN | DA7210_BIAS_EN); + + /* + * ADC settings + */ + + /* Enable Left & Right MIC PGA and Mic Bias */ + snd_soc_component_write(component, DA7210_MIC_L, DA7210_MIC_L_EN | DA7210_MICBIAS_EN); + snd_soc_component_write(component, DA7210_MIC_R, DA7210_MIC_R_EN); + + /* Enable Left and Right input PGA */ + snd_soc_component_write(component, DA7210_INMIX_L, DA7210_IN_L_EN); + snd_soc_component_write(component, DA7210_INMIX_R, DA7210_IN_R_EN); + + /* Enable Left and Right ADC */ + snd_soc_component_write(component, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN); + + /* + * DAC settings + */ + + /* Enable Left and Right DAC */ + snd_soc_component_write(component, DA7210_DAC_SEL, + DA7210_DAC_L_SRC_DAI_L | DA7210_DAC_L_EN | + DA7210_DAC_R_SRC_DAI_R | DA7210_DAC_R_EN); + + /* Enable Left and Right out PGA */ + snd_soc_component_write(component, DA7210_OUTMIX_L, DA7210_OUT_L_EN); + snd_soc_component_write(component, DA7210_OUTMIX_R, DA7210_OUT_R_EN); + + /* Enable Left and Right HeadPhone PGA */ + snd_soc_component_write(component, DA7210_HP_CFG, + DA7210_HP_2CAP_MODE | DA7210_HP_SENSE_EN | + DA7210_HP_L_EN | DA7210_HP_MODE | DA7210_HP_R_EN); + + /* Enable ramp mode for DAC gain update */ + snd_soc_component_write(component, DA7210_SOFTMUTE, DA7210_RAMP_EN); + + /* + * For DA7210 codec, there are two ways to enable/disable analog IOs + * and ADC/DAC, + * (1) Using "Enable Bit" of register associated with that IO + * (or ADC/DAC) + * e.g. Mic Left can be enabled using bit 7 of MIC_L(0x7) reg + * + * (2) Using "Standby Bit" of STARTUP2 or STARTUP3 register + * e.g. Mic left can be put to STANDBY using bit 0 of STARTUP3(0x5) + * + * Out of these two methods, the one using STANDBY bits is preferred + * way to enable/disable individual blocks. This is because STANDBY + * registers are part of system controller which allows system power + * up/down in a controlled, pop-free manner. Also, as per application + * note of DA7210, STANDBY register bits are only effective if a + * particular IO (or ADC/DAC) is already enabled using enable/disable + * register bits. Keeping these things in mind, current DAPM + * implementation manipulates only STANDBY bits. + * + * Overall implementation can be outlined as below, + * + * - "Enable bit" of an IO or ADC/DAC is used to enable it in probe() + * - "STANDBY bit" is controlled by DAPM + */ + + /* Enable Line out amplifiers */ + snd_soc_component_write(component, DA7210_OUT1_L, DA7210_OUT1_L_EN); + snd_soc_component_write(component, DA7210_OUT1_R, DA7210_OUT1_R_EN); + snd_soc_component_write(component, DA7210_OUT2, DA7210_OUT2_EN | + DA7210_OUT2_OUTMIX_L | DA7210_OUT2_OUTMIX_R); + + /* Enable Aux1 */ + snd_soc_component_write(component, DA7210_AUX1_L, DA7210_AUX1_L_EN); + snd_soc_component_write(component, DA7210_AUX1_R, DA7210_AUX1_R_EN); + /* Enable Aux2 */ + snd_soc_component_write(component, DA7210_AUX2, DA7210_AUX2_EN); + + /* Set PLL Master clock range 10-20 MHz, enable PLL bypass */ + snd_soc_component_write(component, DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ | + DA7210_PLL_BYP); + + /* Diable PLL and bypass it */ + snd_soc_component_write(component, DA7210_PLL, DA7210_PLL_FS_48000); + + /* Activate all enabled subsystem */ + snd_soc_component_write(component, DA7210_STARTUP1, DA7210_SC_MST_EN); + + dev_info(component->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_da7210 = { + .probe = da7210_probe, + .controls = da7210_snd_controls, + .num_controls = ARRAY_SIZE(da7210_snd_controls), + .dapm_widgets = da7210_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(da7210_dapm_widgets), + .dapm_routes = da7210_audio_map, + .num_dapm_routes = ARRAY_SIZE(da7210_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +#if IS_ENABLED(CONFIG_I2C) + +static const struct reg_sequence da7210_regmap_i2c_patch[] = { + + /* System controller master disable */ + { DA7210_STARTUP1, 0x00 }, + /* Set PLL Master clock range 10-20 MHz */ + { DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ }, + + /* to unlock */ + { DA7210_A_HID_UNLOCK, 0x8B}, + { DA7210_A_TEST_UNLOCK, 0xB4}, + { DA7210_A_PLL1, 0x01}, + { DA7210_A_CP_MODE, 0x7C}, + /* to re-lock */ + { DA7210_A_HID_UNLOCK, 0x00}, + { DA7210_A_TEST_UNLOCK, 0x00}, +}; + +static const struct regmap_config da7210_regmap_config_i2c = { + .reg_bits = 8, + .val_bits = 8, + + .reg_defaults = da7210_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da7210_reg_defaults), + .volatile_reg = da7210_volatile_register, + .readable_reg = da7210_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int da7210_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da7210_priv *da7210; + int ret; + + da7210 = devm_kzalloc(&i2c->dev, sizeof(struct da7210_priv), + GFP_KERNEL); + if (!da7210) + return -ENOMEM; + + i2c_set_clientdata(i2c, da7210); + + da7210->regmap = devm_regmap_init_i2c(i2c, &da7210_regmap_config_i2c); + if (IS_ERR(da7210->regmap)) { + ret = PTR_ERR(da7210->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + ret = regmap_register_patch(da7210->regmap, da7210_regmap_i2c_patch, + ARRAY_SIZE(da7210_regmap_i2c_patch)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_da7210, &da7210_dai, 1); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register component: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id da7210_i2c_id[] = { + { "da7210", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da7210_i2c_id); + +/* I2C codec control layer */ +static struct i2c_driver da7210_i2c_driver = { + .driver = { + .name = "da7210", + }, + .probe = da7210_i2c_probe, + .id_table = da7210_i2c_id, +}; +#endif + +#if defined(CONFIG_SPI_MASTER) + +static const struct reg_sequence da7210_regmap_spi_patch[] = { + /* Dummy read to give two pulses over nCS for SPI */ + { DA7210_AUX2, 0x00 }, + { DA7210_AUX2, 0x00 }, + + /* System controller master disable */ + { DA7210_STARTUP1, 0x00 }, + /* Set PLL Master clock range 10-20 MHz */ + { DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ }, + + /* to set PAGE1 of SPI register space */ + { DA7210_PAGE_CONTROL, 0x80 }, + /* to unlock */ + { DA7210_A_HID_UNLOCK, 0x8B}, + { DA7210_A_TEST_UNLOCK, 0xB4}, + { DA7210_A_PLL1, 0x01}, + { DA7210_A_CP_MODE, 0x7C}, + /* to re-lock */ + { DA7210_A_HID_UNLOCK, 0x00}, + { DA7210_A_TEST_UNLOCK, 0x00}, + /* to set back PAGE0 of SPI register space */ + { DA7210_PAGE_CONTROL, 0x00 }, +}; + +static const struct regmap_config da7210_regmap_config_spi = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = 0x01, + .write_flag_mask = 0x00, + + .reg_defaults = da7210_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da7210_reg_defaults), + .volatile_reg = da7210_volatile_register, + .readable_reg = da7210_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int da7210_spi_probe(struct spi_device *spi) +{ + struct da7210_priv *da7210; + int ret; + + da7210 = devm_kzalloc(&spi->dev, sizeof(struct da7210_priv), + GFP_KERNEL); + if (!da7210) + return -ENOMEM; + + spi_set_drvdata(spi, da7210); + da7210->regmap = devm_regmap_init_spi(spi, &da7210_regmap_config_spi); + if (IS_ERR(da7210->regmap)) { + ret = PTR_ERR(da7210->regmap); + dev_err(&spi->dev, "Failed to register regmap: %d\n", ret); + return ret; + } + + ret = regmap_register_patch(da7210->regmap, da7210_regmap_spi_patch, + ARRAY_SIZE(da7210_regmap_spi_patch)); + if (ret != 0) + dev_warn(&spi->dev, "Failed to apply regmap patch: %d\n", ret); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_da7210, &da7210_dai, 1); + + return ret; +} + +static struct spi_driver da7210_spi_driver = { + .driver = { + .name = "da7210", + }, + .probe = da7210_spi_probe, +}; +#endif + +static int __init da7210_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&da7210_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&da7210_spi_driver); + if (ret) { + printk(KERN_ERR "Failed to register da7210 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(da7210_modinit); + +static void __exit da7210_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&da7210_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&da7210_spi_driver); +#endif +} +module_exit(da7210_exit); + +MODULE_DESCRIPTION("ASoC DA7210 driver"); +MODULE_AUTHOR("David Chen, Kuninori Morimoto"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c new file mode 100644 index 000000000..92d006a52 --- /dev/null +++ b/sound/soc/codecs/da7213.c @@ -0,0 +1,1900 @@ +/* + * DA7213 ALSA SoC Codec Driver + * + * Copyright (c) 2013 Dialog Semiconductor + * + * Author: Adam Thomson + * Based on DA9055 ALSA SoC codec driver. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "da7213.h" + + +/* Gain and Volume */ +static const DECLARE_TLV_DB_RANGE(aux_vol_tlv, + /* -54dB */ + 0x0, 0x11, TLV_DB_SCALE_ITEM(-5400, 0, 0), + /* -52.5dB to 15dB */ + 0x12, 0x3f, TLV_DB_SCALE_ITEM(-5250, 150, 0) +); + +static const DECLARE_TLV_DB_RANGE(digital_gain_tlv, + 0x0, 0x07, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* -78dB to 12dB */ + 0x08, 0x7f, TLV_DB_SCALE_ITEM(-7800, 75, 0) +); + +static const DECLARE_TLV_DB_RANGE(alc_analog_gain_tlv, + 0x0, 0x0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* 0dB to 36dB */ + 0x01, 0x07, TLV_DB_SCALE_ITEM(0, 600, 0) +); + +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(mixin_gain_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(eq_gain_tlv, -1050, 150, 0); +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(lineout_vol_tlv, -4800, 100, 0); +static const DECLARE_TLV_DB_SCALE(alc_threshold_tlv, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(alc_gain_tlv, 0, 600, 0); + +/* ADC and DAC voice mode (8kHz) high pass cutoff value */ +static const char * const da7213_voice_hpf_corner_txt[] = { + "2.5Hz", "25Hz", "50Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dac_voice_hpf_corner, + DA7213_DAC_FILTERS1, + DA7213_VOICE_HPF_CORNER_SHIFT, + da7213_voice_hpf_corner_txt); + +static SOC_ENUM_SINGLE_DECL(da7213_adc_voice_hpf_corner, + DA7213_ADC_FILTERS1, + DA7213_VOICE_HPF_CORNER_SHIFT, + da7213_voice_hpf_corner_txt); + +/* ADC and DAC high pass filter cutoff value */ +static const char * const da7213_audio_hpf_corner_txt[] = { + "Fs/24000", "Fs/12000", "Fs/6000", "Fs/3000" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dac_audio_hpf_corner, + DA7213_DAC_FILTERS1 + , DA7213_AUDIO_HPF_CORNER_SHIFT, + da7213_audio_hpf_corner_txt); + +static SOC_ENUM_SINGLE_DECL(da7213_adc_audio_hpf_corner, + DA7213_ADC_FILTERS1, + DA7213_AUDIO_HPF_CORNER_SHIFT, + da7213_audio_hpf_corner_txt); + +/* Gain ramping rate value */ +static const char * const da7213_gain_ramp_rate_txt[] = { + "nominal rate * 8", "nominal rate * 16", "nominal rate / 16", + "nominal rate / 32" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_gain_ramp_rate, + DA7213_GAIN_RAMP_CTRL, + DA7213_GAIN_RAMP_RATE_SHIFT, + da7213_gain_ramp_rate_txt); + +/* DAC noise gate setup time value */ +static const char * const da7213_dac_ng_setup_time_txt[] = { + "256 samples", "512 samples", "1024 samples", "2048 samples" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dac_ng_setup_time, + DA7213_DAC_NG_SETUP_TIME, + DA7213_DAC_NG_SETUP_TIME_SHIFT, + da7213_dac_ng_setup_time_txt); + +/* DAC noise gate rampup rate value */ +static const char * const da7213_dac_ng_rampup_txt[] = { + "0.02 ms/dB", "0.16 ms/dB" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dac_ng_rampup_rate, + DA7213_DAC_NG_SETUP_TIME, + DA7213_DAC_NG_RAMPUP_RATE_SHIFT, + da7213_dac_ng_rampup_txt); + +/* DAC noise gate rampdown rate value */ +static const char * const da7213_dac_ng_rampdown_txt[] = { + "0.64 ms/dB", "20.48 ms/dB" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dac_ng_rampdown_rate, + DA7213_DAC_NG_SETUP_TIME, + DA7213_DAC_NG_RAMPDN_RATE_SHIFT, + da7213_dac_ng_rampdown_txt); + +/* DAC soft mute rate value */ +static const char * const da7213_dac_soft_mute_rate_txt[] = { + "1", "2", "4", "8", "16", "32", "64" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dac_soft_mute_rate, + DA7213_DAC_FILTERS5, + DA7213_DAC_SOFTMUTE_RATE_SHIFT, + da7213_dac_soft_mute_rate_txt); + +/* ALC Attack Rate select */ +static const char * const da7213_alc_attack_rate_txt[] = { + "44/fs", "88/fs", "176/fs", "352/fs", "704/fs", "1408/fs", "2816/fs", + "5632/fs", "11264/fs", "22528/fs", "45056/fs", "90112/fs", "180224/fs" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_alc_attack_rate, + DA7213_ALC_CTRL2, + DA7213_ALC_ATTACK_SHIFT, + da7213_alc_attack_rate_txt); + +/* ALC Release Rate select */ +static const char * const da7213_alc_release_rate_txt[] = { + "176/fs", "352/fs", "704/fs", "1408/fs", "2816/fs", "5632/fs", + "11264/fs", "22528/fs", "45056/fs", "90112/fs", "180224/fs" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_alc_release_rate, + DA7213_ALC_CTRL2, + DA7213_ALC_RELEASE_SHIFT, + da7213_alc_release_rate_txt); + +/* ALC Hold Time select */ +static const char * const da7213_alc_hold_time_txt[] = { + "62/fs", "124/fs", "248/fs", "496/fs", "992/fs", "1984/fs", "3968/fs", + "7936/fs", "15872/fs", "31744/fs", "63488/fs", "126976/fs", + "253952/fs", "507904/fs", "1015808/fs", "2031616/fs" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_alc_hold_time, + DA7213_ALC_CTRL3, + DA7213_ALC_HOLD_SHIFT, + da7213_alc_hold_time_txt); + +/* ALC Input Signal Tracking rate select */ +static const char * const da7213_alc_integ_rate_txt[] = { + "1/4", "1/16", "1/256", "1/65536" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_alc_integ_attack_rate, + DA7213_ALC_CTRL3, + DA7213_ALC_INTEG_ATTACK_SHIFT, + da7213_alc_integ_rate_txt); + +static SOC_ENUM_SINGLE_DECL(da7213_alc_integ_release_rate, + DA7213_ALC_CTRL3, + DA7213_ALC_INTEG_RELEASE_SHIFT, + da7213_alc_integ_rate_txt); + + +/* + * Control Functions + */ + +static int da7213_get_alc_data(struct snd_soc_component *component, u8 reg_val) +{ + int mid_data, top_data; + int sum = 0; + u8 iteration; + + for (iteration = 0; iteration < DA7213_ALC_AVG_ITERATIONS; + iteration++) { + /* Select the left or right channel and capture data */ + snd_soc_component_write(component, DA7213_ALC_CIC_OP_LVL_CTRL, reg_val); + + /* Select middle 8 bits for read back from data register */ + snd_soc_component_write(component, DA7213_ALC_CIC_OP_LVL_CTRL, + reg_val | DA7213_ALC_DATA_MIDDLE); + mid_data = snd_soc_component_read32(component, DA7213_ALC_CIC_OP_LVL_DATA); + + /* Select top 8 bits for read back from data register */ + snd_soc_component_write(component, DA7213_ALC_CIC_OP_LVL_CTRL, + reg_val | DA7213_ALC_DATA_TOP); + top_data = snd_soc_component_read32(component, DA7213_ALC_CIC_OP_LVL_DATA); + + sum += ((mid_data << 8) | (top_data << 16)); + } + + return sum / DA7213_ALC_AVG_ITERATIONS; +} + +static void da7213_alc_calib_man(struct snd_soc_component *component) +{ + u8 reg_val; + int avg_left_data, avg_right_data, offset_l, offset_r; + + /* Calculate average for Left and Right data */ + /* Left Data */ + avg_left_data = da7213_get_alc_data(component, + DA7213_ALC_CIC_OP_CHANNEL_LEFT); + /* Right Data */ + avg_right_data = da7213_get_alc_data(component, + DA7213_ALC_CIC_OP_CHANNEL_RIGHT); + + /* Calculate DC offset */ + offset_l = -avg_left_data; + offset_r = -avg_right_data; + + reg_val = (offset_l & DA7213_ALC_OFFSET_15_8) >> 8; + snd_soc_component_write(component, DA7213_ALC_OFFSET_MAN_M_L, reg_val); + reg_val = (offset_l & DA7213_ALC_OFFSET_19_16) >> 16; + snd_soc_component_write(component, DA7213_ALC_OFFSET_MAN_U_L, reg_val); + + reg_val = (offset_r & DA7213_ALC_OFFSET_15_8) >> 8; + snd_soc_component_write(component, DA7213_ALC_OFFSET_MAN_M_R, reg_val); + reg_val = (offset_r & DA7213_ALC_OFFSET_19_16) >> 16; + snd_soc_component_write(component, DA7213_ALC_OFFSET_MAN_U_R, reg_val); + + /* Enable analog/digital gain mode & offset cancellation */ + snd_soc_component_update_bits(component, DA7213_ALC_CTRL1, + DA7213_ALC_OFFSET_EN | DA7213_ALC_SYNC_MODE, + DA7213_ALC_OFFSET_EN | DA7213_ALC_SYNC_MODE); +} + +static void da7213_alc_calib_auto(struct snd_soc_component *component) +{ + u8 alc_ctrl1; + + /* Begin auto calibration and wait for completion */ + snd_soc_component_update_bits(component, DA7213_ALC_CTRL1, DA7213_ALC_AUTO_CALIB_EN, + DA7213_ALC_AUTO_CALIB_EN); + do { + alc_ctrl1 = snd_soc_component_read32(component, DA7213_ALC_CTRL1); + } while (alc_ctrl1 & DA7213_ALC_AUTO_CALIB_EN); + + /* If auto calibration fails, fall back to digital gain only mode */ + if (alc_ctrl1 & DA7213_ALC_CALIB_OVERFLOW) { + dev_warn(component->dev, + "ALC auto calibration failed with overflow\n"); + snd_soc_component_update_bits(component, DA7213_ALC_CTRL1, + DA7213_ALC_OFFSET_EN | DA7213_ALC_SYNC_MODE, + 0); + } else { + /* Enable analog/digital gain mode & offset cancellation */ + snd_soc_component_update_bits(component, DA7213_ALC_CTRL1, + DA7213_ALC_OFFSET_EN | DA7213_ALC_SYNC_MODE, + DA7213_ALC_OFFSET_EN | DA7213_ALC_SYNC_MODE); + } + +} + +static void da7213_alc_calib(struct snd_soc_component *component) +{ + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + u8 adc_l_ctrl, adc_r_ctrl; + u8 mixin_l_sel, mixin_r_sel; + u8 mic_1_ctrl, mic_2_ctrl; + + /* Save current values from ADC control registers */ + adc_l_ctrl = snd_soc_component_read32(component, DA7213_ADC_L_CTRL); + adc_r_ctrl = snd_soc_component_read32(component, DA7213_ADC_R_CTRL); + + /* Save current values from MIXIN_L/R_SELECT registers */ + mixin_l_sel = snd_soc_component_read32(component, DA7213_MIXIN_L_SELECT); + mixin_r_sel = snd_soc_component_read32(component, DA7213_MIXIN_R_SELECT); + + /* Save current values from MIC control registers */ + mic_1_ctrl = snd_soc_component_read32(component, DA7213_MIC_1_CTRL); + mic_2_ctrl = snd_soc_component_read32(component, DA7213_MIC_2_CTRL); + + /* Enable ADC Left and Right */ + snd_soc_component_update_bits(component, DA7213_ADC_L_CTRL, DA7213_ADC_EN, + DA7213_ADC_EN); + snd_soc_component_update_bits(component, DA7213_ADC_R_CTRL, DA7213_ADC_EN, + DA7213_ADC_EN); + + /* Enable MIC paths */ + snd_soc_component_update_bits(component, DA7213_MIXIN_L_SELECT, + DA7213_MIXIN_L_MIX_SELECT_MIC_1 | + DA7213_MIXIN_L_MIX_SELECT_MIC_2, + DA7213_MIXIN_L_MIX_SELECT_MIC_1 | + DA7213_MIXIN_L_MIX_SELECT_MIC_2); + snd_soc_component_update_bits(component, DA7213_MIXIN_R_SELECT, + DA7213_MIXIN_R_MIX_SELECT_MIC_2 | + DA7213_MIXIN_R_MIX_SELECT_MIC_1, + DA7213_MIXIN_R_MIX_SELECT_MIC_2 | + DA7213_MIXIN_R_MIX_SELECT_MIC_1); + + /* Mute MIC PGAs */ + snd_soc_component_update_bits(component, DA7213_MIC_1_CTRL, DA7213_MUTE_EN, + DA7213_MUTE_EN); + snd_soc_component_update_bits(component, DA7213_MIC_2_CTRL, DA7213_MUTE_EN, + DA7213_MUTE_EN); + + /* Perform calibration */ + if (da7213->alc_calib_auto) + da7213_alc_calib_auto(component); + else + da7213_alc_calib_man(component); + + /* Restore MIXIN_L/R_SELECT registers to their original states */ + snd_soc_component_write(component, DA7213_MIXIN_L_SELECT, mixin_l_sel); + snd_soc_component_write(component, DA7213_MIXIN_R_SELECT, mixin_r_sel); + + /* Restore ADC control registers to their original states */ + snd_soc_component_write(component, DA7213_ADC_L_CTRL, adc_l_ctrl); + snd_soc_component_write(component, DA7213_ADC_R_CTRL, adc_r_ctrl); + + /* Restore original values of MIC control registers */ + snd_soc_component_write(component, DA7213_MIC_1_CTRL, mic_1_ctrl); + snd_soc_component_write(component, DA7213_MIC_2_CTRL, mic_2_ctrl); +} + +static int da7213_put_mixin_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + + /* If ALC in operation, make sure calibrated offsets are updated */ + if ((!ret) && (da7213->alc_en)) + da7213_alc_calib(component); + + return ret; +} + +static int da7213_put_alc_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + + /* Force ALC offset calibration if enabling ALC */ + if (ucontrol->value.integer.value[0] || + ucontrol->value.integer.value[1]) { + if (!da7213->alc_en) { + da7213_alc_calib(component); + da7213->alc_en = true; + } + } else { + da7213->alc_en = false; + } + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + + +/* + * KControls + */ + +static const struct snd_kcontrol_new da7213_snd_controls[] = { + + /* Volume controls */ + SOC_SINGLE_TLV("Mic 1 Volume", DA7213_MIC_1_GAIN, + DA7213_MIC_AMP_GAIN_SHIFT, DA7213_MIC_AMP_GAIN_MAX, + DA7213_NO_INVERT, mic_vol_tlv), + SOC_SINGLE_TLV("Mic 2 Volume", DA7213_MIC_2_GAIN, + DA7213_MIC_AMP_GAIN_SHIFT, DA7213_MIC_AMP_GAIN_MAX, + DA7213_NO_INVERT, mic_vol_tlv), + SOC_DOUBLE_R_TLV("Aux Volume", DA7213_AUX_L_GAIN, DA7213_AUX_R_GAIN, + DA7213_AUX_AMP_GAIN_SHIFT, DA7213_AUX_AMP_GAIN_MAX, + DA7213_NO_INVERT, aux_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("Mixin PGA Volume", DA7213_MIXIN_L_GAIN, + DA7213_MIXIN_R_GAIN, DA7213_MIXIN_AMP_GAIN_SHIFT, + DA7213_MIXIN_AMP_GAIN_MAX, DA7213_NO_INVERT, + snd_soc_get_volsw_2r, da7213_put_mixin_gain, + mixin_gain_tlv), + SOC_DOUBLE_R_TLV("ADC Volume", DA7213_ADC_L_GAIN, DA7213_ADC_R_GAIN, + DA7213_ADC_AMP_GAIN_SHIFT, DA7213_ADC_AMP_GAIN_MAX, + DA7213_NO_INVERT, digital_gain_tlv), + SOC_DOUBLE_R_TLV("DAC Volume", DA7213_DAC_L_GAIN, DA7213_DAC_R_GAIN, + DA7213_DAC_AMP_GAIN_SHIFT, DA7213_DAC_AMP_GAIN_MAX, + DA7213_NO_INVERT, digital_gain_tlv), + SOC_DOUBLE_R_TLV("Headphone Volume", DA7213_HP_L_GAIN, DA7213_HP_R_GAIN, + DA7213_HP_AMP_GAIN_SHIFT, DA7213_HP_AMP_GAIN_MAX, + DA7213_NO_INVERT, hp_vol_tlv), + SOC_SINGLE_TLV("Lineout Volume", DA7213_LINE_GAIN, + DA7213_LINE_AMP_GAIN_SHIFT, DA7213_LINE_AMP_GAIN_MAX, + DA7213_NO_INVERT, lineout_vol_tlv), + + /* DAC Equalizer controls */ + SOC_SINGLE("DAC EQ Switch", DA7213_DAC_FILTERS4, DA7213_DAC_EQ_EN_SHIFT, + DA7213_DAC_EQ_EN_MAX, DA7213_NO_INVERT), + SOC_SINGLE_TLV("DAC EQ1 Volume", DA7213_DAC_FILTERS2, + DA7213_DAC_EQ_BAND1_SHIFT, DA7213_DAC_EQ_BAND_MAX, + DA7213_NO_INVERT, eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ2 Volume", DA7213_DAC_FILTERS2, + DA7213_DAC_EQ_BAND2_SHIFT, DA7213_DAC_EQ_BAND_MAX, + DA7213_NO_INVERT, eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ3 Volume", DA7213_DAC_FILTERS3, + DA7213_DAC_EQ_BAND3_SHIFT, DA7213_DAC_EQ_BAND_MAX, + DA7213_NO_INVERT, eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ4 Volume", DA7213_DAC_FILTERS3, + DA7213_DAC_EQ_BAND4_SHIFT, DA7213_DAC_EQ_BAND_MAX, + DA7213_NO_INVERT, eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ5 Volume", DA7213_DAC_FILTERS4, + DA7213_DAC_EQ_BAND5_SHIFT, DA7213_DAC_EQ_BAND_MAX, + DA7213_NO_INVERT, eq_gain_tlv), + + /* High Pass Filter and Voice Mode controls */ + SOC_SINGLE("ADC HPF Switch", DA7213_ADC_FILTERS1, DA7213_HPF_EN_SHIFT, + DA7213_HPF_EN_MAX, DA7213_NO_INVERT), + SOC_ENUM("ADC HPF Cutoff", da7213_adc_audio_hpf_corner), + SOC_SINGLE("ADC Voice Mode Switch", DA7213_ADC_FILTERS1, + DA7213_VOICE_EN_SHIFT, DA7213_VOICE_EN_MAX, + DA7213_NO_INVERT), + SOC_ENUM("ADC Voice Cutoff", da7213_adc_voice_hpf_corner), + + SOC_SINGLE("DAC HPF Switch", DA7213_DAC_FILTERS1, DA7213_HPF_EN_SHIFT, + DA7213_HPF_EN_MAX, DA7213_NO_INVERT), + SOC_ENUM("DAC HPF Cutoff", da7213_dac_audio_hpf_corner), + SOC_SINGLE("DAC Voice Mode Switch", DA7213_DAC_FILTERS1, + DA7213_VOICE_EN_SHIFT, DA7213_VOICE_EN_MAX, + DA7213_NO_INVERT), + SOC_ENUM("DAC Voice Cutoff", da7213_dac_voice_hpf_corner), + + /* Mute controls */ + SOC_SINGLE("Mic 1 Switch", DA7213_MIC_1_CTRL, DA7213_MUTE_EN_SHIFT, + DA7213_MUTE_EN_MAX, DA7213_INVERT), + SOC_SINGLE("Mic 2 Switch", DA7213_MIC_2_CTRL, DA7213_MUTE_EN_SHIFT, + DA7213_MUTE_EN_MAX, DA7213_INVERT), + SOC_DOUBLE_R("Aux Switch", DA7213_AUX_L_CTRL, DA7213_AUX_R_CTRL, + DA7213_MUTE_EN_SHIFT, DA7213_MUTE_EN_MAX, DA7213_INVERT), + SOC_DOUBLE_R("Mixin PGA Switch", DA7213_MIXIN_L_CTRL, + DA7213_MIXIN_R_CTRL, DA7213_MUTE_EN_SHIFT, + DA7213_MUTE_EN_MAX, DA7213_INVERT), + SOC_DOUBLE_R("ADC Switch", DA7213_ADC_L_CTRL, DA7213_ADC_R_CTRL, + DA7213_MUTE_EN_SHIFT, DA7213_MUTE_EN_MAX, DA7213_INVERT), + SOC_DOUBLE_R("Headphone Switch", DA7213_HP_L_CTRL, DA7213_HP_R_CTRL, + DA7213_MUTE_EN_SHIFT, DA7213_MUTE_EN_MAX, DA7213_INVERT), + SOC_SINGLE("Lineout Switch", DA7213_LINE_CTRL, DA7213_MUTE_EN_SHIFT, + DA7213_MUTE_EN_MAX, DA7213_INVERT), + SOC_SINGLE("DAC Soft Mute Switch", DA7213_DAC_FILTERS5, + DA7213_DAC_SOFTMUTE_EN_SHIFT, DA7213_DAC_SOFTMUTE_EN_MAX, + DA7213_NO_INVERT), + SOC_ENUM("DAC Soft Mute Rate", da7213_dac_soft_mute_rate), + + /* Zero Cross controls */ + SOC_DOUBLE_R("Aux ZC Switch", DA7213_AUX_L_CTRL, DA7213_AUX_R_CTRL, + DA7213_ZC_EN_SHIFT, DA7213_ZC_EN_MAX, DA7213_NO_INVERT), + SOC_DOUBLE_R("Mixin PGA ZC Switch", DA7213_MIXIN_L_CTRL, + DA7213_MIXIN_R_CTRL, DA7213_ZC_EN_SHIFT, DA7213_ZC_EN_MAX, + DA7213_NO_INVERT), + SOC_DOUBLE_R("Headphone ZC Switch", DA7213_HP_L_CTRL, DA7213_HP_R_CTRL, + DA7213_ZC_EN_SHIFT, DA7213_ZC_EN_MAX, DA7213_NO_INVERT), + + /* Gain Ramping controls */ + SOC_DOUBLE_R("Aux Gain Ramping Switch", DA7213_AUX_L_CTRL, + DA7213_AUX_R_CTRL, DA7213_GAIN_RAMP_EN_SHIFT, + DA7213_GAIN_RAMP_EN_MAX, DA7213_NO_INVERT), + SOC_DOUBLE_R("Mixin Gain Ramping Switch", DA7213_MIXIN_L_CTRL, + DA7213_MIXIN_R_CTRL, DA7213_GAIN_RAMP_EN_SHIFT, + DA7213_GAIN_RAMP_EN_MAX, DA7213_NO_INVERT), + SOC_DOUBLE_R("ADC Gain Ramping Switch", DA7213_ADC_L_CTRL, + DA7213_ADC_R_CTRL, DA7213_GAIN_RAMP_EN_SHIFT, + DA7213_GAIN_RAMP_EN_MAX, DA7213_NO_INVERT), + SOC_DOUBLE_R("DAC Gain Ramping Switch", DA7213_DAC_L_CTRL, + DA7213_DAC_R_CTRL, DA7213_GAIN_RAMP_EN_SHIFT, + DA7213_GAIN_RAMP_EN_MAX, DA7213_NO_INVERT), + SOC_DOUBLE_R("Headphone Gain Ramping Switch", DA7213_HP_L_CTRL, + DA7213_HP_R_CTRL, DA7213_GAIN_RAMP_EN_SHIFT, + DA7213_GAIN_RAMP_EN_MAX, DA7213_NO_INVERT), + SOC_SINGLE("Lineout Gain Ramping Switch", DA7213_LINE_CTRL, + DA7213_GAIN_RAMP_EN_SHIFT, DA7213_GAIN_RAMP_EN_MAX, + DA7213_NO_INVERT), + SOC_ENUM("Gain Ramping Rate", da7213_gain_ramp_rate), + + /* DAC Noise Gate controls */ + SOC_SINGLE("DAC NG Switch", DA7213_DAC_NG_CTRL, DA7213_DAC_NG_EN_SHIFT, + DA7213_DAC_NG_EN_MAX, DA7213_NO_INVERT), + SOC_ENUM("DAC NG Setup Time", da7213_dac_ng_setup_time), + SOC_ENUM("DAC NG Rampup Rate", da7213_dac_ng_rampup_rate), + SOC_ENUM("DAC NG Rampdown Rate", da7213_dac_ng_rampdown_rate), + SOC_SINGLE("DAC NG OFF Threshold", DA7213_DAC_NG_OFF_THRESHOLD, + DA7213_DAC_NG_THRESHOLD_SHIFT, DA7213_DAC_NG_THRESHOLD_MAX, + DA7213_NO_INVERT), + SOC_SINGLE("DAC NG ON Threshold", DA7213_DAC_NG_ON_THRESHOLD, + DA7213_DAC_NG_THRESHOLD_SHIFT, DA7213_DAC_NG_THRESHOLD_MAX, + DA7213_NO_INVERT), + + /* DAC Routing & Inversion */ + SOC_DOUBLE("DAC Mono Switch", DA7213_DIG_ROUTING_DAC, + DA7213_DAC_L_MONO_SHIFT, DA7213_DAC_R_MONO_SHIFT, + DA7213_DAC_MONO_MAX, DA7213_NO_INVERT), + SOC_DOUBLE("DAC Invert Switch", DA7213_DIG_CTRL, DA7213_DAC_L_INV_SHIFT, + DA7213_DAC_R_INV_SHIFT, DA7213_DAC_INV_MAX, + DA7213_NO_INVERT), + + /* DMIC controls */ + SOC_DOUBLE_R("DMIC Switch", DA7213_MIXIN_L_SELECT, + DA7213_MIXIN_R_SELECT, DA7213_DMIC_EN_SHIFT, + DA7213_DMIC_EN_MAX, DA7213_NO_INVERT), + + /* ALC Controls */ + SOC_DOUBLE_EXT("ALC Switch", DA7213_ALC_CTRL1, DA7213_ALC_L_EN_SHIFT, + DA7213_ALC_R_EN_SHIFT, DA7213_ALC_EN_MAX, + DA7213_NO_INVERT, snd_soc_get_volsw, da7213_put_alc_sw), + SOC_ENUM("ALC Attack Rate", da7213_alc_attack_rate), + SOC_ENUM("ALC Release Rate", da7213_alc_release_rate), + SOC_ENUM("ALC Hold Time", da7213_alc_hold_time), + /* + * Rate at which input signal envelope is tracked as the signal gets + * larger + */ + SOC_ENUM("ALC Integ Attack Rate", da7213_alc_integ_attack_rate), + /* + * Rate at which input signal envelope is tracked as the signal gets + * smaller + */ + SOC_ENUM("ALC Integ Release Rate", da7213_alc_integ_release_rate), + SOC_SINGLE_TLV("ALC Noise Threshold Volume", DA7213_ALC_NOISE, + DA7213_ALC_THRESHOLD_SHIFT, DA7213_ALC_THRESHOLD_MAX, + DA7213_INVERT, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Min Threshold Volume", DA7213_ALC_TARGET_MIN, + DA7213_ALC_THRESHOLD_SHIFT, DA7213_ALC_THRESHOLD_MAX, + DA7213_INVERT, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Threshold Volume", DA7213_ALC_TARGET_MAX, + DA7213_ALC_THRESHOLD_SHIFT, DA7213_ALC_THRESHOLD_MAX, + DA7213_INVERT, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Attenuation Volume", DA7213_ALC_GAIN_LIMITS, + DA7213_ALC_ATTEN_MAX_SHIFT, + DA7213_ALC_ATTEN_GAIN_MAX_MAX, DA7213_NO_INVERT, + alc_gain_tlv), + SOC_SINGLE_TLV("ALC Max Gain Volume", DA7213_ALC_GAIN_LIMITS, + DA7213_ALC_GAIN_MAX_SHIFT, DA7213_ALC_ATTEN_GAIN_MAX_MAX, + DA7213_NO_INVERT, alc_gain_tlv), + SOC_SINGLE_TLV("ALC Min Analog Gain Volume", DA7213_ALC_ANA_GAIN_LIMITS, + DA7213_ALC_ANA_GAIN_MIN_SHIFT, DA7213_ALC_ANA_GAIN_MAX, + DA7213_NO_INVERT, alc_analog_gain_tlv), + SOC_SINGLE_TLV("ALC Max Analog Gain Volume", DA7213_ALC_ANA_GAIN_LIMITS, + DA7213_ALC_ANA_GAIN_MAX_SHIFT, DA7213_ALC_ANA_GAIN_MAX, + DA7213_NO_INVERT, alc_analog_gain_tlv), + SOC_SINGLE("ALC Anticlip Mode Switch", DA7213_ALC_ANTICLIP_CTRL, + DA7213_ALC_ANTICLIP_EN_SHIFT, DA7213_ALC_ANTICLIP_EN_MAX, + DA7213_NO_INVERT), + SOC_SINGLE("ALC Anticlip Level", DA7213_ALC_ANTICLIP_LEVEL, + DA7213_ALC_ANTICLIP_LEVEL_SHIFT, + DA7213_ALC_ANTICLIP_LEVEL_MAX, DA7213_NO_INVERT), +}; + + +/* + * DAPM + */ + +/* + * Enums + */ + +/* MIC PGA source select */ +static const char * const da7213_mic_amp_in_sel_txt[] = { + "Differential", "MIC_P", "MIC_N" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_mic_1_amp_in_sel, + DA7213_MIC_1_CTRL, + DA7213_MIC_AMP_IN_SEL_SHIFT, + da7213_mic_amp_in_sel_txt); +static const struct snd_kcontrol_new da7213_mic_1_amp_in_sel_mux = + SOC_DAPM_ENUM("Mic 1 Amp Source MUX", da7213_mic_1_amp_in_sel); + +static SOC_ENUM_SINGLE_DECL(da7213_mic_2_amp_in_sel, + DA7213_MIC_2_CTRL, + DA7213_MIC_AMP_IN_SEL_SHIFT, + da7213_mic_amp_in_sel_txt); +static const struct snd_kcontrol_new da7213_mic_2_amp_in_sel_mux = + SOC_DAPM_ENUM("Mic 2 Amp Source MUX", da7213_mic_2_amp_in_sel); + +/* DAI routing select */ +static const char * const da7213_dai_src_txt[] = { + "ADC Left", "ADC Right", "DAI Input Left", "DAI Input Right" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dai_l_src, + DA7213_DIG_ROUTING_DAI, + DA7213_DAI_L_SRC_SHIFT, + da7213_dai_src_txt); +static const struct snd_kcontrol_new da7213_dai_l_src_mux = + SOC_DAPM_ENUM("DAI Left Source MUX", da7213_dai_l_src); + +static SOC_ENUM_SINGLE_DECL(da7213_dai_r_src, + DA7213_DIG_ROUTING_DAI, + DA7213_DAI_R_SRC_SHIFT, + da7213_dai_src_txt); +static const struct snd_kcontrol_new da7213_dai_r_src_mux = + SOC_DAPM_ENUM("DAI Right Source MUX", da7213_dai_r_src); + +/* DAC routing select */ +static const char * const da7213_dac_src_txt[] = { + "ADC Output Left", "ADC Output Right", "DAI Input Left", + "DAI Input Right" +}; + +static SOC_ENUM_SINGLE_DECL(da7213_dac_l_src, + DA7213_DIG_ROUTING_DAC, + DA7213_DAC_L_SRC_SHIFT, + da7213_dac_src_txt); +static const struct snd_kcontrol_new da7213_dac_l_src_mux = + SOC_DAPM_ENUM("DAC Left Source MUX", da7213_dac_l_src); + +static SOC_ENUM_SINGLE_DECL(da7213_dac_r_src, + DA7213_DIG_ROUTING_DAC, + DA7213_DAC_R_SRC_SHIFT, + da7213_dac_src_txt); +static const struct snd_kcontrol_new da7213_dac_r_src_mux = + SOC_DAPM_ENUM("DAC Right Source MUX", da7213_dac_r_src); + +/* + * Mixer Controls + */ + +/* Mixin Left */ +static const struct snd_kcontrol_new da7213_dapm_mixinl_controls[] = { + SOC_DAPM_SINGLE("Aux Left Switch", DA7213_MIXIN_L_SELECT, + DA7213_MIXIN_L_MIX_SELECT_AUX_L_SHIFT, + DA7213_MIXIN_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mic 1 Switch", DA7213_MIXIN_L_SELECT, + DA7213_MIXIN_L_MIX_SELECT_MIC_1_SHIFT, + DA7213_MIXIN_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mic 2 Switch", DA7213_MIXIN_L_SELECT, + DA7213_MIXIN_L_MIX_SELECT_MIC_2_SHIFT, + DA7213_MIXIN_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Right Switch", DA7213_MIXIN_L_SELECT, + DA7213_MIXIN_L_MIX_SELECT_MIXIN_R_SHIFT, + DA7213_MIXIN_L_MIX_SELECT_MAX, DA7213_NO_INVERT), +}; + +/* Mixin Right */ +static const struct snd_kcontrol_new da7213_dapm_mixinr_controls[] = { + SOC_DAPM_SINGLE("Aux Right Switch", DA7213_MIXIN_R_SELECT, + DA7213_MIXIN_R_MIX_SELECT_AUX_R_SHIFT, + DA7213_MIXIN_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mic 2 Switch", DA7213_MIXIN_R_SELECT, + DA7213_MIXIN_R_MIX_SELECT_MIC_2_SHIFT, + DA7213_MIXIN_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mic 1 Switch", DA7213_MIXIN_R_SELECT, + DA7213_MIXIN_R_MIX_SELECT_MIC_1_SHIFT, + DA7213_MIXIN_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Left Switch", DA7213_MIXIN_R_SELECT, + DA7213_MIXIN_R_MIX_SELECT_MIXIN_L_SHIFT, + DA7213_MIXIN_R_MIX_SELECT_MAX, DA7213_NO_INVERT), +}; + +/* Mixout Left */ +static const struct snd_kcontrol_new da7213_dapm_mixoutl_controls[] = { + SOC_DAPM_SINGLE("Aux Left Switch", DA7213_MIXOUT_L_SELECT, + DA7213_MIXOUT_L_MIX_SELECT_AUX_L_SHIFT, + DA7213_MIXOUT_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Left Switch", DA7213_MIXOUT_L_SELECT, + DA7213_MIXOUT_L_MIX_SELECT_MIXIN_L_SHIFT, + DA7213_MIXOUT_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Right Switch", DA7213_MIXOUT_L_SELECT, + DA7213_MIXOUT_L_MIX_SELECT_MIXIN_R_SHIFT, + DA7213_MIXOUT_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("DAC Left Switch", DA7213_MIXOUT_L_SELECT, + DA7213_MIXOUT_L_MIX_SELECT_DAC_L_SHIFT, + DA7213_MIXOUT_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Aux Left Invert Switch", DA7213_MIXOUT_L_SELECT, + DA7213_MIXOUT_L_MIX_SELECT_AUX_L_INVERTED_SHIFT, + DA7213_MIXOUT_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Left Invert Switch", DA7213_MIXOUT_L_SELECT, + DA7213_MIXOUT_L_MIX_SELECT_MIXIN_L_INVERTED_SHIFT, + DA7213_MIXOUT_L_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Right Invert Switch", DA7213_MIXOUT_L_SELECT, + DA7213_MIXOUT_L_MIX_SELECT_MIXIN_R_INVERTED_SHIFT, + DA7213_MIXOUT_L_MIX_SELECT_MAX, DA7213_NO_INVERT), +}; + +/* Mixout Right */ +static const struct snd_kcontrol_new da7213_dapm_mixoutr_controls[] = { + SOC_DAPM_SINGLE("Aux Right Switch", DA7213_MIXOUT_R_SELECT, + DA7213_MIXOUT_R_MIX_SELECT_AUX_R_SHIFT, + DA7213_MIXOUT_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Right Switch", DA7213_MIXOUT_R_SELECT, + DA7213_MIXOUT_R_MIX_SELECT_MIXIN_R_SHIFT, + DA7213_MIXOUT_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Left Switch", DA7213_MIXOUT_R_SELECT, + DA7213_MIXOUT_R_MIX_SELECT_MIXIN_L_SHIFT, + DA7213_MIXOUT_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("DAC Right Switch", DA7213_MIXOUT_R_SELECT, + DA7213_MIXOUT_R_MIX_SELECT_DAC_R_SHIFT, + DA7213_MIXOUT_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Aux Right Invert Switch", DA7213_MIXOUT_R_SELECT, + DA7213_MIXOUT_R_MIX_SELECT_AUX_R_INVERTED_SHIFT, + DA7213_MIXOUT_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Right Invert Switch", DA7213_MIXOUT_R_SELECT, + DA7213_MIXOUT_R_MIX_SELECT_MIXIN_R_INVERTED_SHIFT, + DA7213_MIXOUT_R_MIX_SELECT_MAX, DA7213_NO_INVERT), + SOC_DAPM_SINGLE("Mixin Left Invert Switch", DA7213_MIXOUT_R_SELECT, + DA7213_MIXOUT_R_MIX_SELECT_MIXIN_L_INVERTED_SHIFT, + DA7213_MIXOUT_R_MIX_SELECT_MAX, DA7213_NO_INVERT), +}; + + +/* + * DAPM Events + */ + +static int da7213_dai_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + u8 pll_ctrl, pll_status; + int i = 0; + bool srm_lock = false; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable DAI clks for master mode */ + if (da7213->master) + snd_soc_component_update_bits(component, DA7213_DAI_CLK_MODE, + DA7213_DAI_CLK_EN_MASK, + DA7213_DAI_CLK_EN_MASK); + + /* PC synchronised to DAI */ + snd_soc_component_update_bits(component, DA7213_PC_COUNT, + DA7213_PC_FREERUN_MASK, 0); + + /* If SRM not enabled then nothing more to do */ + pll_ctrl = snd_soc_component_read32(component, DA7213_PLL_CTRL); + if (!(pll_ctrl & DA7213_PLL_SRM_EN)) + return 0; + + /* Assist 32KHz mode PLL lock */ + if (pll_ctrl & DA7213_PLL_32K_MODE) { + snd_soc_component_write(component, 0xF0, 0x8B); + snd_soc_component_write(component, 0xF2, 0x03); + snd_soc_component_write(component, 0xF0, 0x00); + } + + /* Check SRM has locked */ + do { + pll_status = snd_soc_component_read32(component, DA7213_PLL_STATUS); + if (pll_status & DA7219_PLL_SRM_LOCK) { + srm_lock = true; + } else { + ++i; + msleep(50); + } + } while ((i < DA7213_SRM_CHECK_RETRIES) && (!srm_lock)); + + if (!srm_lock) + dev_warn(component->dev, "SRM failed to lock\n"); + + return 0; + case SND_SOC_DAPM_POST_PMD: + /* Revert 32KHz PLL lock udpates if applied previously */ + pll_ctrl = snd_soc_component_read32(component, DA7213_PLL_CTRL); + if (pll_ctrl & DA7213_PLL_32K_MODE) { + snd_soc_component_write(component, 0xF0, 0x8B); + snd_soc_component_write(component, 0xF2, 0x01); + snd_soc_component_write(component, 0xF0, 0x00); + } + + /* PC free-running */ + snd_soc_component_update_bits(component, DA7213_PC_COUNT, + DA7213_PC_FREERUN_MASK, + DA7213_PC_FREERUN_MASK); + + /* Disable DAI clks if in master mode */ + if (da7213->master) + snd_soc_component_update_bits(component, DA7213_DAI_CLK_MODE, + DA7213_DAI_CLK_EN_MASK, 0); + return 0; + default: + return -EINVAL; + } +} + + +/* + * DAPM widgets + */ + +static const struct snd_soc_dapm_widget da7213_dapm_widgets[] = { + /* + * Input & Output + */ + + /* Use a supply here as this controls both input & output DAIs */ + SND_SOC_DAPM_SUPPLY("DAI", DA7213_DAI_CTRL, DA7213_DAI_EN_SHIFT, + DA7213_NO_INVERT, da7213_dai_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* + * Input + */ + + /* Input Lines */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), + + /* MUXs for Mic PGA source selection */ + SND_SOC_DAPM_MUX("Mic 1 Amp Source MUX", SND_SOC_NOPM, 0, 0, + &da7213_mic_1_amp_in_sel_mux), + SND_SOC_DAPM_MUX("Mic 2 Amp Source MUX", SND_SOC_NOPM, 0, 0, + &da7213_mic_2_amp_in_sel_mux), + + /* Input PGAs */ + SND_SOC_DAPM_PGA("Mic 1 PGA", DA7213_MIC_1_CTRL, DA7213_AMP_EN_SHIFT, + DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Mic 2 PGA", DA7213_MIC_2_CTRL, DA7213_AMP_EN_SHIFT, + DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Aux Left PGA", DA7213_AUX_L_CTRL, DA7213_AMP_EN_SHIFT, + DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Aux Right PGA", DA7213_AUX_R_CTRL, + DA7213_AMP_EN_SHIFT, DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Mixin Left PGA", DA7213_MIXIN_L_CTRL, + DA7213_AMP_EN_SHIFT, DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Mixin Right PGA", DA7213_MIXIN_R_CTRL, + DA7213_AMP_EN_SHIFT, DA7213_NO_INVERT, NULL, 0), + + /* Mic Biases */ + SND_SOC_DAPM_SUPPLY("Mic Bias 1", DA7213_MICBIAS_CTRL, + DA7213_MICBIAS1_EN_SHIFT, DA7213_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias 2", DA7213_MICBIAS_CTRL, + DA7213_MICBIAS2_EN_SHIFT, DA7213_NO_INVERT, + NULL, 0), + + /* Input Mixers */ + SND_SOC_DAPM_MIXER("Mixin Left", SND_SOC_NOPM, 0, 0, + &da7213_dapm_mixinl_controls[0], + ARRAY_SIZE(da7213_dapm_mixinl_controls)), + SND_SOC_DAPM_MIXER("Mixin Right", SND_SOC_NOPM, 0, 0, + &da7213_dapm_mixinr_controls[0], + ARRAY_SIZE(da7213_dapm_mixinr_controls)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC Left", NULL, DA7213_ADC_L_CTRL, + DA7213_ADC_EN_SHIFT, DA7213_NO_INVERT), + SND_SOC_DAPM_ADC("ADC Right", NULL, DA7213_ADC_R_CTRL, + DA7213_ADC_EN_SHIFT, DA7213_NO_INVERT), + + /* DAI */ + SND_SOC_DAPM_MUX("DAI Left Source MUX", SND_SOC_NOPM, 0, 0, + &da7213_dai_l_src_mux), + SND_SOC_DAPM_MUX("DAI Right Source MUX", SND_SOC_NOPM, 0, 0, + &da7213_dai_r_src_mux), + SND_SOC_DAPM_AIF_OUT("DAIOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DAIOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0), + + /* + * Output + */ + + /* DAI */ + SND_SOC_DAPM_AIF_IN("DAIINL", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DAIINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("DAC Left Source MUX", SND_SOC_NOPM, 0, 0, + &da7213_dac_l_src_mux), + SND_SOC_DAPM_MUX("DAC Right Source MUX", SND_SOC_NOPM, 0, 0, + &da7213_dac_r_src_mux), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC Left", NULL, DA7213_DAC_L_CTRL, + DA7213_DAC_EN_SHIFT, DA7213_NO_INVERT), + SND_SOC_DAPM_DAC("DAC Right", NULL, DA7213_DAC_R_CTRL, + DA7213_DAC_EN_SHIFT, DA7213_NO_INVERT), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Mixout Left", SND_SOC_NOPM, 0, 0, + &da7213_dapm_mixoutl_controls[0], + ARRAY_SIZE(da7213_dapm_mixoutl_controls)), + SND_SOC_DAPM_MIXER("Mixout Right", SND_SOC_NOPM, 0, 0, + &da7213_dapm_mixoutr_controls[0], + ARRAY_SIZE(da7213_dapm_mixoutr_controls)), + + /* Output PGAs */ + SND_SOC_DAPM_PGA("Mixout Left PGA", DA7213_MIXOUT_L_CTRL, + DA7213_AMP_EN_SHIFT, DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Mixout Right PGA", DA7213_MIXOUT_R_CTRL, + DA7213_AMP_EN_SHIFT, DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Lineout PGA", DA7213_LINE_CTRL, DA7213_AMP_EN_SHIFT, + DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Left PGA", DA7213_HP_L_CTRL, + DA7213_AMP_EN_SHIFT, DA7213_NO_INVERT, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Right PGA", DA7213_HP_R_CTRL, + DA7213_AMP_EN_SHIFT, DA7213_NO_INVERT, NULL, 0), + + /* Charge Pump */ + SND_SOC_DAPM_SUPPLY("Charge Pump", DA7213_CP_CTRL, DA7213_CP_EN_SHIFT, + DA7213_NO_INVERT, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LINE"), +}; + + +/* + * DAPM audio route definition + */ + +static const struct snd_soc_dapm_route da7213_audio_map[] = { + /* Dest Connecting Widget source */ + + /* Input path */ + {"MIC1", NULL, "Mic Bias 1"}, + {"MIC2", NULL, "Mic Bias 2"}, + + {"Mic 1 Amp Source MUX", "Differential", "MIC1"}, + {"Mic 1 Amp Source MUX", "MIC_P", "MIC1"}, + {"Mic 1 Amp Source MUX", "MIC_N", "MIC1"}, + + {"Mic 2 Amp Source MUX", "Differential", "MIC2"}, + {"Mic 2 Amp Source MUX", "MIC_P", "MIC2"}, + {"Mic 2 Amp Source MUX", "MIC_N", "MIC2"}, + + {"Mic 1 PGA", NULL, "Mic 1 Amp Source MUX"}, + {"Mic 2 PGA", NULL, "Mic 2 Amp Source MUX"}, + + {"Aux Left PGA", NULL, "AUXL"}, + {"Aux Right PGA", NULL, "AUXR"}, + + {"Mixin Left", "Aux Left Switch", "Aux Left PGA"}, + {"Mixin Left", "Mic 1 Switch", "Mic 1 PGA"}, + {"Mixin Left", "Mic 2 Switch", "Mic 2 PGA"}, + {"Mixin Left", "Mixin Right Switch", "Mixin Right PGA"}, + + {"Mixin Right", "Aux Right Switch", "Aux Right PGA"}, + {"Mixin Right", "Mic 2 Switch", "Mic 2 PGA"}, + {"Mixin Right", "Mic 1 Switch", "Mic 1 PGA"}, + {"Mixin Right", "Mixin Left Switch", "Mixin Left PGA"}, + + {"Mixin Left PGA", NULL, "Mixin Left"}, + {"ADC Left", NULL, "Mixin Left PGA"}, + + {"Mixin Right PGA", NULL, "Mixin Right"}, + {"ADC Right", NULL, "Mixin Right PGA"}, + + {"DAI Left Source MUX", "ADC Left", "ADC Left"}, + {"DAI Left Source MUX", "ADC Right", "ADC Right"}, + {"DAI Left Source MUX", "DAI Input Left", "DAIINL"}, + {"DAI Left Source MUX", "DAI Input Right", "DAIINR"}, + + {"DAI Right Source MUX", "ADC Left", "ADC Left"}, + {"DAI Right Source MUX", "ADC Right", "ADC Right"}, + {"DAI Right Source MUX", "DAI Input Left", "DAIINL"}, + {"DAI Right Source MUX", "DAI Input Right", "DAIINR"}, + + {"DAIOUTL", NULL, "DAI Left Source MUX"}, + {"DAIOUTR", NULL, "DAI Right Source MUX"}, + + {"DAIOUTL", NULL, "DAI"}, + {"DAIOUTR", NULL, "DAI"}, + + /* Output path */ + {"DAIINL", NULL, "DAI"}, + {"DAIINR", NULL, "DAI"}, + + {"DAC Left Source MUX", "ADC Output Left", "ADC Left"}, + {"DAC Left Source MUX", "ADC Output Right", "ADC Right"}, + {"DAC Left Source MUX", "DAI Input Left", "DAIINL"}, + {"DAC Left Source MUX", "DAI Input Right", "DAIINR"}, + + {"DAC Right Source MUX", "ADC Output Left", "ADC Left"}, + {"DAC Right Source MUX", "ADC Output Right", "ADC Right"}, + {"DAC Right Source MUX", "DAI Input Left", "DAIINL"}, + {"DAC Right Source MUX", "DAI Input Right", "DAIINR"}, + + {"DAC Left", NULL, "DAC Left Source MUX"}, + {"DAC Right", NULL, "DAC Right Source MUX"}, + + {"Mixout Left", "Aux Left Switch", "Aux Left PGA"}, + {"Mixout Left", "Mixin Left Switch", "Mixin Left PGA"}, + {"Mixout Left", "Mixin Right Switch", "Mixin Right PGA"}, + {"Mixout Left", "DAC Left Switch", "DAC Left"}, + {"Mixout Left", "Aux Left Invert Switch", "Aux Left PGA"}, + {"Mixout Left", "Mixin Left Invert Switch", "Mixin Left PGA"}, + {"Mixout Left", "Mixin Right Invert Switch", "Mixin Right PGA"}, + + {"Mixout Right", "Aux Right Switch", "Aux Right PGA"}, + {"Mixout Right", "Mixin Right Switch", "Mixin Right PGA"}, + {"Mixout Right", "Mixin Left Switch", "Mixin Left PGA"}, + {"Mixout Right", "DAC Right Switch", "DAC Right"}, + {"Mixout Right", "Aux Right Invert Switch", "Aux Right PGA"}, + {"Mixout Right", "Mixin Right Invert Switch", "Mixin Right PGA"}, + {"Mixout Right", "Mixin Left Invert Switch", "Mixin Left PGA"}, + + {"Mixout Left PGA", NULL, "Mixout Left"}, + {"Mixout Right PGA", NULL, "Mixout Right"}, + + {"Headphone Left PGA", NULL, "Mixout Left PGA"}, + {"Headphone Left PGA", NULL, "Charge Pump"}, + {"HPL", NULL, "Headphone Left PGA"}, + + {"Headphone Right PGA", NULL, "Mixout Right PGA"}, + {"Headphone Right PGA", NULL, "Charge Pump"}, + {"HPR", NULL, "Headphone Right PGA"}, + + {"Lineout PGA", NULL, "Mixout Right PGA"}, + {"LINE", NULL, "Lineout PGA"}, +}; + +static const struct reg_default da7213_reg_defaults[] = { + { DA7213_DIG_ROUTING_DAI, 0x10 }, + { DA7213_SR, 0x0A }, + { DA7213_REFERENCES, 0x80 }, + { DA7213_PLL_FRAC_TOP, 0x00 }, + { DA7213_PLL_FRAC_BOT, 0x00 }, + { DA7213_PLL_INTEGER, 0x20 }, + { DA7213_PLL_CTRL, 0x0C }, + { DA7213_DAI_CLK_MODE, 0x01 }, + { DA7213_DAI_CTRL, 0x08 }, + { DA7213_DIG_ROUTING_DAC, 0x32 }, + { DA7213_AUX_L_GAIN, 0x35 }, + { DA7213_AUX_R_GAIN, 0x35 }, + { DA7213_MIXIN_L_SELECT, 0x00 }, + { DA7213_MIXIN_R_SELECT, 0x00 }, + { DA7213_MIXIN_L_GAIN, 0x03 }, + { DA7213_MIXIN_R_GAIN, 0x03 }, + { DA7213_ADC_L_GAIN, 0x6F }, + { DA7213_ADC_R_GAIN, 0x6F }, + { DA7213_ADC_FILTERS1, 0x80 }, + { DA7213_MIC_1_GAIN, 0x01 }, + { DA7213_MIC_2_GAIN, 0x01 }, + { DA7213_DAC_FILTERS5, 0x00 }, + { DA7213_DAC_FILTERS2, 0x88 }, + { DA7213_DAC_FILTERS3, 0x88 }, + { DA7213_DAC_FILTERS4, 0x08 }, + { DA7213_DAC_FILTERS1, 0x80 }, + { DA7213_DAC_L_GAIN, 0x6F }, + { DA7213_DAC_R_GAIN, 0x6F }, + { DA7213_CP_CTRL, 0x61 }, + { DA7213_HP_L_GAIN, 0x39 }, + { DA7213_HP_R_GAIN, 0x39 }, + { DA7213_LINE_GAIN, 0x30 }, + { DA7213_MIXOUT_L_SELECT, 0x00 }, + { DA7213_MIXOUT_R_SELECT, 0x00 }, + { DA7213_SYSTEM_MODES_INPUT, 0x00 }, + { DA7213_SYSTEM_MODES_OUTPUT, 0x00 }, + { DA7213_AUX_L_CTRL, 0x44 }, + { DA7213_AUX_R_CTRL, 0x44 }, + { DA7213_MICBIAS_CTRL, 0x11 }, + { DA7213_MIC_1_CTRL, 0x40 }, + { DA7213_MIC_2_CTRL, 0x40 }, + { DA7213_MIXIN_L_CTRL, 0x40 }, + { DA7213_MIXIN_R_CTRL, 0x40 }, + { DA7213_ADC_L_CTRL, 0x40 }, + { DA7213_ADC_R_CTRL, 0x40 }, + { DA7213_DAC_L_CTRL, 0x48 }, + { DA7213_DAC_R_CTRL, 0x40 }, + { DA7213_HP_L_CTRL, 0x41 }, + { DA7213_HP_R_CTRL, 0x40 }, + { DA7213_LINE_CTRL, 0x40 }, + { DA7213_MIXOUT_L_CTRL, 0x10 }, + { DA7213_MIXOUT_R_CTRL, 0x10 }, + { DA7213_LDO_CTRL, 0x00 }, + { DA7213_IO_CTRL, 0x00 }, + { DA7213_GAIN_RAMP_CTRL, 0x00}, + { DA7213_MIC_CONFIG, 0x00 }, + { DA7213_PC_COUNT, 0x00 }, + { DA7213_CP_VOL_THRESHOLD1, 0x32 }, + { DA7213_CP_DELAY, 0x95 }, + { DA7213_CP_DETECTOR, 0x00 }, + { DA7213_DAI_OFFSET, 0x00 }, + { DA7213_DIG_CTRL, 0x00 }, + { DA7213_ALC_CTRL2, 0x00 }, + { DA7213_ALC_CTRL3, 0x00 }, + { DA7213_ALC_NOISE, 0x3F }, + { DA7213_ALC_TARGET_MIN, 0x3F }, + { DA7213_ALC_TARGET_MAX, 0x00 }, + { DA7213_ALC_GAIN_LIMITS, 0xFF }, + { DA7213_ALC_ANA_GAIN_LIMITS, 0x71 }, + { DA7213_ALC_ANTICLIP_CTRL, 0x00 }, + { DA7213_ALC_ANTICLIP_LEVEL, 0x00 }, + { DA7213_ALC_OFFSET_MAN_M_L, 0x00 }, + { DA7213_ALC_OFFSET_MAN_U_L, 0x00 }, + { DA7213_ALC_OFFSET_MAN_M_R, 0x00 }, + { DA7213_ALC_OFFSET_MAN_U_R, 0x00 }, + { DA7213_ALC_CIC_OP_LVL_CTRL, 0x00 }, + { DA7213_DAC_NG_SETUP_TIME, 0x00 }, + { DA7213_DAC_NG_OFF_THRESHOLD, 0x00 }, + { DA7213_DAC_NG_ON_THRESHOLD, 0x00 }, + { DA7213_DAC_NG_CTRL, 0x00 }, +}; + +static bool da7213_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA7213_STATUS1: + case DA7213_PLL_STATUS: + case DA7213_AUX_L_GAIN_STATUS: + case DA7213_AUX_R_GAIN_STATUS: + case DA7213_MIC_1_GAIN_STATUS: + case DA7213_MIC_2_GAIN_STATUS: + case DA7213_MIXIN_L_GAIN_STATUS: + case DA7213_MIXIN_R_GAIN_STATUS: + case DA7213_ADC_L_GAIN_STATUS: + case DA7213_ADC_R_GAIN_STATUS: + case DA7213_DAC_L_GAIN_STATUS: + case DA7213_DAC_R_GAIN_STATUS: + case DA7213_HP_L_GAIN_STATUS: + case DA7213_HP_R_GAIN_STATUS: + case DA7213_LINE_GAIN_STATUS: + case DA7213_ALC_CTRL1: + case DA7213_ALC_OFFSET_AUTO_M_L: + case DA7213_ALC_OFFSET_AUTO_U_L: + case DA7213_ALC_OFFSET_AUTO_M_R: + case DA7213_ALC_OFFSET_AUTO_U_R: + case DA7213_ALC_CIC_OP_LVL_DATA: + return true; + default: + return false; + } +} + +static int da7213_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u8 dai_ctrl = 0; + u8 fs; + + /* Set DAI format */ + switch (params_width(params)) { + case 16: + dai_ctrl |= DA7213_DAI_WORD_LENGTH_S16_LE; + break; + case 20: + dai_ctrl |= DA7213_DAI_WORD_LENGTH_S20_LE; + break; + case 24: + dai_ctrl |= DA7213_DAI_WORD_LENGTH_S24_LE; + break; + case 32: + dai_ctrl |= DA7213_DAI_WORD_LENGTH_S32_LE; + break; + default: + return -EINVAL; + } + + /* Set sampling rate */ + switch (params_rate(params)) { + case 8000: + fs = DA7213_SR_8000; + break; + case 11025: + fs = DA7213_SR_11025; + break; + case 12000: + fs = DA7213_SR_12000; + break; + case 16000: + fs = DA7213_SR_16000; + break; + case 22050: + fs = DA7213_SR_22050; + break; + case 32000: + fs = DA7213_SR_32000; + break; + case 44100: + fs = DA7213_SR_44100; + break; + case 48000: + fs = DA7213_SR_48000; + break; + case 88200: + fs = DA7213_SR_88200; + break; + case 96000: + fs = DA7213_SR_96000; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, DA7213_DAI_CTRL, DA7213_DAI_WORD_LENGTH_MASK, + dai_ctrl); + snd_soc_component_write(component, DA7213_SR, fs); + + return 0; +} + +static int da7213_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + u8 dai_clk_mode = 0, dai_ctrl = 0; + u8 dai_offset = 0; + + /* Set master/slave mode */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + da7213->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + da7213->master = false; + break; + default: + return -EINVAL; + } + + /* Set clock normal/inverted */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + dai_clk_mode |= DA7213_DAI_WCLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + dai_clk_mode |= DA7213_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + dai_clk_mode |= DA7213_DAI_WCLK_POL_INV | + DA7213_DAI_CLK_POL_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAI_FORMAT_DSP_A: + case SND_SOC_DAI_FORMAT_DSP_B: + /* The bclk is inverted wrt ASoC conventions */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + dai_clk_mode |= DA7213_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + dai_clk_mode |= DA7213_DAI_WCLK_POL_INV | + DA7213_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + dai_clk_mode |= DA7213_DAI_WCLK_POL_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + /* Only I2S is supported */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dai_ctrl |= DA7213_DAI_FORMAT_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + dai_ctrl |= DA7213_DAI_FORMAT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dai_ctrl |= DA7213_DAI_FORMAT_RIGHT_J; + break; + case SND_SOC_DAI_FORMAT_DSP_A: /* L data MSB after FRM LRC */ + dai_ctrl |= DA7213_DAI_FORMAT_DSP; + dai_offset = 1; + break; + case SND_SOC_DAI_FORMAT_DSP_B: /* L data MSB during FRM LRC */ + dai_ctrl |= DA7213_DAI_FORMAT_DSP; + break; + default: + return -EINVAL; + } + + /* By default only 64 BCLK per WCLK is supported */ + dai_clk_mode |= DA7213_DAI_BCLKS_PER_WCLK_64; + + snd_soc_component_write(component, DA7213_DAI_CLK_MODE, dai_clk_mode); + snd_soc_component_update_bits(component, DA7213_DAI_CTRL, DA7213_DAI_FORMAT_MASK, + dai_ctrl); + snd_soc_component_write(component, DA7213_DAI_OFFSET, dai_offset); + + return 0; +} + +static int da7213_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + snd_soc_component_update_bits(component, DA7213_DAC_L_CTRL, + DA7213_MUTE_EN, DA7213_MUTE_EN); + snd_soc_component_update_bits(component, DA7213_DAC_R_CTRL, + DA7213_MUTE_EN, DA7213_MUTE_EN); + } else { + snd_soc_component_update_bits(component, DA7213_DAC_L_CTRL, + DA7213_MUTE_EN, 0); + snd_soc_component_update_bits(component, DA7213_DAC_R_CTRL, + DA7213_MUTE_EN, 0); + } + + return 0; +} + +#define DA7213_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int da7213_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + int ret = 0; + + if ((da7213->clk_src == clk_id) && (da7213->mclk_rate == freq)) + return 0; + + if (((freq < 5000000) && (freq != 32768)) || (freq > 54000000)) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; + } + + switch (clk_id) { + case DA7213_CLKSRC_MCLK: + snd_soc_component_update_bits(component, DA7213_PLL_CTRL, + DA7213_PLL_MCLK_SQR_EN, 0); + break; + case DA7213_CLKSRC_MCLK_SQR: + snd_soc_component_update_bits(component, DA7213_PLL_CTRL, + DA7213_PLL_MCLK_SQR_EN, + DA7213_PLL_MCLK_SQR_EN); + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } + + da7213->clk_src = clk_id; + + if (da7213->mclk) { + freq = clk_round_rate(da7213->mclk, freq); + ret = clk_set_rate(da7213->mclk, freq); + if (ret) { + dev_err(codec_dai->dev, "Failed to set clock rate %d\n", + freq); + return ret; + } + } + + da7213->mclk_rate = freq; + + return 0; +} + +/* Supported PLL input frequencies are 32KHz, 5MHz - 54MHz. */ +static int da7213_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + + u8 pll_ctrl, indiv_bits, indiv; + u8 pll_frac_top, pll_frac_bot, pll_integer; + u32 freq_ref; + u64 frac_div; + + /* Workout input divider based on MCLK rate */ + if (da7213->mclk_rate == 32768) { + if (!da7213->master) { + dev_err(component->dev, + "32KHz only valid if codec is clock master\n"); + return -EINVAL; + } + + /* 32KHz PLL Mode */ + indiv_bits = DA7213_PLL_INDIV_9_TO_18_MHZ; + indiv = DA7213_PLL_INDIV_9_TO_18_MHZ_VAL; + source = DA7213_SYSCLK_PLL_32KHZ; + freq_ref = 3750000; + + } else { + if (da7213->mclk_rate < 5000000) { + dev_err(component->dev, + "PLL input clock %d below valid range\n", + da7213->mclk_rate); + return -EINVAL; + } else if (da7213->mclk_rate <= 9000000) { + indiv_bits = DA7213_PLL_INDIV_5_TO_9_MHZ; + indiv = DA7213_PLL_INDIV_5_TO_9_MHZ_VAL; + } else if (da7213->mclk_rate <= 18000000) { + indiv_bits = DA7213_PLL_INDIV_9_TO_18_MHZ; + indiv = DA7213_PLL_INDIV_9_TO_18_MHZ_VAL; + } else if (da7213->mclk_rate <= 36000000) { + indiv_bits = DA7213_PLL_INDIV_18_TO_36_MHZ; + indiv = DA7213_PLL_INDIV_18_TO_36_MHZ_VAL; + } else if (da7213->mclk_rate <= 54000000) { + indiv_bits = DA7213_PLL_INDIV_36_TO_54_MHZ; + indiv = DA7213_PLL_INDIV_36_TO_54_MHZ_VAL; + } else { + dev_err(component->dev, + "PLL input clock %d above valid range\n", + da7213->mclk_rate); + return -EINVAL; + } + freq_ref = (da7213->mclk_rate / indiv); + } + + pll_ctrl = indiv_bits; + + /* Configure PLL */ + switch (source) { + case DA7213_SYSCLK_MCLK: + snd_soc_component_update_bits(component, DA7213_PLL_CTRL, + DA7213_PLL_INDIV_MASK | + DA7213_PLL_MODE_MASK, pll_ctrl); + return 0; + case DA7213_SYSCLK_PLL: + break; + case DA7213_SYSCLK_PLL_SRM: + pll_ctrl |= DA7213_PLL_SRM_EN; + fout = DA7213_PLL_FREQ_OUT_94310400; + break; + case DA7213_SYSCLK_PLL_32KHZ: + if (da7213->mclk_rate != 32768) { + dev_err(component->dev, + "32KHz mode only valid with 32KHz MCLK\n"); + return -EINVAL; + } + + pll_ctrl |= DA7213_PLL_32K_MODE | DA7213_PLL_SRM_EN; + fout = DA7213_PLL_FREQ_OUT_94310400; + break; + default: + dev_err(component->dev, "Invalid PLL config\n"); + return -EINVAL; + } + + /* Calculate dividers for PLL */ + pll_integer = fout / freq_ref; + frac_div = (u64)(fout % freq_ref) * 8192ULL; + do_div(frac_div, freq_ref); + pll_frac_top = (frac_div >> DA7213_BYTE_SHIFT) & DA7213_BYTE_MASK; + pll_frac_bot = (frac_div) & DA7213_BYTE_MASK; + + /* Write PLL dividers */ + snd_soc_component_write(component, DA7213_PLL_FRAC_TOP, pll_frac_top); + snd_soc_component_write(component, DA7213_PLL_FRAC_BOT, pll_frac_bot); + snd_soc_component_write(component, DA7213_PLL_INTEGER, pll_integer); + + /* Enable PLL */ + pll_ctrl |= DA7213_PLL_EN; + snd_soc_component_update_bits(component, DA7213_PLL_CTRL, + DA7213_PLL_INDIV_MASK | DA7213_PLL_MODE_MASK, + pll_ctrl); + + /* Assist 32KHz mode PLL lock */ + if (source == DA7213_SYSCLK_PLL_32KHZ) { + snd_soc_component_write(component, 0xF0, 0x8B); + snd_soc_component_write(component, 0xF1, 0x03); + snd_soc_component_write(component, 0xF1, 0x01); + snd_soc_component_write(component, 0xF0, 0x00); + } + + return 0; +} + +/* DAI operations */ +static const struct snd_soc_dai_ops da7213_dai_ops = { + .hw_params = da7213_hw_params, + .set_fmt = da7213_set_dai_fmt, + .set_sysclk = da7213_set_dai_sysclk, + .set_pll = da7213_set_dai_pll, + .digital_mute = da7213_mute, +}; + +static struct snd_soc_dai_driver da7213_dai = { + .name = "da7213-hifi", + /* Playback Capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7213_FORMATS, + }, + /* Capture Capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7213_FORMATS, + }, + .ops = &da7213_dai_ops, + .symmetric_rates = 1, +}; + +static int da7213_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + /* Enable MCLK for transition to ON state */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) { + if (da7213->mclk) { + ret = clk_prepare_enable(da7213->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable mclk\n"); + return ret; + } + } + } + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Enable VMID reference & master bias */ + snd_soc_component_update_bits(component, DA7213_REFERENCES, + DA7213_VMID_EN | DA7213_BIAS_EN, + DA7213_VMID_EN | DA7213_BIAS_EN); + } else { + /* Remove MCLK */ + if (da7213->mclk) + clk_disable_unprepare(da7213->mclk); + } + break; + case SND_SOC_BIAS_OFF: + /* Disable VMID reference & master bias */ + snd_soc_component_update_bits(component, DA7213_REFERENCES, + DA7213_VMID_EN | DA7213_BIAS_EN, 0); + break; + } + return 0; +} + +#if defined(CONFIG_OF) +/* DT */ +static const struct of_device_id da7213_of_match[] = { + { .compatible = "dlg,da7213", }, + { } +}; +MODULE_DEVICE_TABLE(of, da7213_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id da7213_acpi_match[] = { + { "DLGS7212", 0}, + { "DLGS7213", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, da7213_acpi_match); +#endif + +static enum da7213_micbias_voltage + da7213_of_micbias_lvl(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 1600: + return DA7213_MICBIAS_1_6V; + case 2200: + return DA7213_MICBIAS_2_2V; + case 2500: + return DA7213_MICBIAS_2_5V; + case 3000: + return DA7213_MICBIAS_3_0V; + default: + dev_warn(component->dev, "Invalid micbias level\n"); + return DA7213_MICBIAS_2_2V; + } +} + +static enum da7213_dmic_data_sel + da7213_of_dmic_data_sel(struct snd_soc_component *component, const char *str) +{ + if (!strcmp(str, "lrise_rfall")) { + return DA7213_DMIC_DATA_LRISE_RFALL; + } else if (!strcmp(str, "lfall_rrise")) { + return DA7213_DMIC_DATA_LFALL_RRISE; + } else { + dev_warn(component->dev, "Invalid DMIC data select type\n"); + return DA7213_DMIC_DATA_LRISE_RFALL; + } +} + +static enum da7213_dmic_samplephase + da7213_of_dmic_samplephase(struct snd_soc_component *component, const char *str) +{ + if (!strcmp(str, "on_clkedge")) { + return DA7213_DMIC_SAMPLE_ON_CLKEDGE; + } else if (!strcmp(str, "between_clkedge")) { + return DA7213_DMIC_SAMPLE_BETWEEN_CLKEDGE; + } else { + dev_warn(component->dev, "Invalid DMIC sample phase\n"); + return DA7213_DMIC_SAMPLE_ON_CLKEDGE; + } +} + +static enum da7213_dmic_clk_rate + da7213_of_dmic_clkrate(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 1500000: + return DA7213_DMIC_CLK_1_5MHZ; + case 3000000: + return DA7213_DMIC_CLK_3_0MHZ; + default: + dev_warn(component->dev, "Invalid DMIC clock rate\n"); + return DA7213_DMIC_CLK_1_5MHZ; + } +} + +static struct da7213_platform_data + *da7213_fw_to_pdata(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + struct da7213_platform_data *pdata; + const char *fw_str; + u32 fw_val32; + + pdata = devm_kzalloc(component->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + if (device_property_read_u32(dev, "dlg,micbias1-lvl", &fw_val32) >= 0) + pdata->micbias1_lvl = da7213_of_micbias_lvl(component, fw_val32); + else + pdata->micbias1_lvl = DA7213_MICBIAS_2_2V; + + if (device_property_read_u32(dev, "dlg,micbias2-lvl", &fw_val32) >= 0) + pdata->micbias2_lvl = da7213_of_micbias_lvl(component, fw_val32); + else + pdata->micbias2_lvl = DA7213_MICBIAS_2_2V; + + if (!device_property_read_string(dev, "dlg,dmic-data-sel", &fw_str)) + pdata->dmic_data_sel = da7213_of_dmic_data_sel(component, fw_str); + else + pdata->dmic_data_sel = DA7213_DMIC_DATA_LRISE_RFALL; + + if (!device_property_read_string(dev, "dlg,dmic-samplephase", &fw_str)) + pdata->dmic_samplephase = + da7213_of_dmic_samplephase(component, fw_str); + else + pdata->dmic_samplephase = DA7213_DMIC_SAMPLE_ON_CLKEDGE; + + if (device_property_read_u32(dev, "dlg,dmic-clkrate", &fw_val32) >= 0) + pdata->dmic_clk_rate = da7213_of_dmic_clkrate(component, fw_val32); + else + pdata->dmic_clk_rate = DA7213_DMIC_CLK_3_0MHZ; + + return pdata; +} + + +static int da7213_probe(struct snd_soc_component *component) +{ + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); + + /* Default to using ALC auto offset calibration mode. */ + snd_soc_component_update_bits(component, DA7213_ALC_CTRL1, + DA7213_ALC_CALIB_MODE_MAN, 0); + da7213->alc_calib_auto = true; + + /* Default PC counter to free-running */ + snd_soc_component_update_bits(component, DA7213_PC_COUNT, DA7213_PC_FREERUN_MASK, + DA7213_PC_FREERUN_MASK); + + /* Enable all Gain Ramps */ + snd_soc_component_update_bits(component, DA7213_AUX_L_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_AUX_R_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_MIXIN_L_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_MIXIN_R_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_ADC_L_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_ADC_R_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_DAC_L_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_DAC_R_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_HP_L_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_HP_R_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + snd_soc_component_update_bits(component, DA7213_LINE_CTRL, + DA7213_GAIN_RAMP_EN, DA7213_GAIN_RAMP_EN); + + /* + * There are two separate control bits for input and output mixers as + * well as headphone and line outs. + * One to enable corresponding amplifier and other to enable its + * output. As amplifier bits are related to power control, they are + * being managed by DAPM while other (non power related) bits are + * enabled here + */ + snd_soc_component_update_bits(component, DA7213_MIXIN_L_CTRL, + DA7213_MIXIN_MIX_EN, DA7213_MIXIN_MIX_EN); + snd_soc_component_update_bits(component, DA7213_MIXIN_R_CTRL, + DA7213_MIXIN_MIX_EN, DA7213_MIXIN_MIX_EN); + + snd_soc_component_update_bits(component, DA7213_MIXOUT_L_CTRL, + DA7213_MIXOUT_MIX_EN, DA7213_MIXOUT_MIX_EN); + snd_soc_component_update_bits(component, DA7213_MIXOUT_R_CTRL, + DA7213_MIXOUT_MIX_EN, DA7213_MIXOUT_MIX_EN); + + snd_soc_component_update_bits(component, DA7213_HP_L_CTRL, + DA7213_HP_AMP_OE, DA7213_HP_AMP_OE); + snd_soc_component_update_bits(component, DA7213_HP_R_CTRL, + DA7213_HP_AMP_OE, DA7213_HP_AMP_OE); + + snd_soc_component_update_bits(component, DA7213_LINE_CTRL, + DA7213_LINE_AMP_OE, DA7213_LINE_AMP_OE); + + /* Handle DT/Platform data */ + da7213->pdata = dev_get_platdata(component->dev); + if (!da7213->pdata) + da7213->pdata = da7213_fw_to_pdata(component); + + /* Set platform data values */ + if (da7213->pdata) { + struct da7213_platform_data *pdata = da7213->pdata; + u8 micbias_lvl = 0, dmic_cfg = 0; + + /* Set Mic Bias voltages */ + switch (pdata->micbias1_lvl) { + case DA7213_MICBIAS_1_6V: + case DA7213_MICBIAS_2_2V: + case DA7213_MICBIAS_2_5V: + case DA7213_MICBIAS_3_0V: + micbias_lvl |= (pdata->micbias1_lvl << + DA7213_MICBIAS1_LEVEL_SHIFT); + break; + } + switch (pdata->micbias2_lvl) { + case DA7213_MICBIAS_1_6V: + case DA7213_MICBIAS_2_2V: + case DA7213_MICBIAS_2_5V: + case DA7213_MICBIAS_3_0V: + micbias_lvl |= (pdata->micbias2_lvl << + DA7213_MICBIAS2_LEVEL_SHIFT); + break; + } + snd_soc_component_update_bits(component, DA7213_MICBIAS_CTRL, + DA7213_MICBIAS1_LEVEL_MASK | + DA7213_MICBIAS2_LEVEL_MASK, micbias_lvl); + + /* Set DMIC configuration */ + switch (pdata->dmic_data_sel) { + case DA7213_DMIC_DATA_LFALL_RRISE: + case DA7213_DMIC_DATA_LRISE_RFALL: + dmic_cfg |= (pdata->dmic_data_sel << + DA7213_DMIC_DATA_SEL_SHIFT); + break; + } + switch (pdata->dmic_samplephase) { + case DA7213_DMIC_SAMPLE_ON_CLKEDGE: + case DA7213_DMIC_SAMPLE_BETWEEN_CLKEDGE: + dmic_cfg |= (pdata->dmic_samplephase << + DA7213_DMIC_SAMPLEPHASE_SHIFT); + break; + } + switch (pdata->dmic_clk_rate) { + case DA7213_DMIC_CLK_3_0MHZ: + case DA7213_DMIC_CLK_1_5MHZ: + dmic_cfg |= (pdata->dmic_clk_rate << + DA7213_DMIC_CLK_RATE_SHIFT); + break; + } + snd_soc_component_update_bits(component, DA7213_MIC_CONFIG, + DA7213_DMIC_DATA_SEL_MASK | + DA7213_DMIC_SAMPLEPHASE_MASK | + DA7213_DMIC_CLK_RATE_MASK, dmic_cfg); + } + + /* Check if MCLK provided */ + da7213->mclk = devm_clk_get(component->dev, "mclk"); + if (IS_ERR(da7213->mclk)) { + if (PTR_ERR(da7213->mclk) != -ENOENT) + return PTR_ERR(da7213->mclk); + else + da7213->mclk = NULL; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_da7213 = { + .probe = da7213_probe, + .set_bias_level = da7213_set_bias_level, + .controls = da7213_snd_controls, + .num_controls = ARRAY_SIZE(da7213_snd_controls), + .dapm_widgets = da7213_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(da7213_dapm_widgets), + .dapm_routes = da7213_audio_map, + .num_dapm_routes = ARRAY_SIZE(da7213_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config da7213_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .reg_defaults = da7213_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da7213_reg_defaults), + .volatile_reg = da7213_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int da7213_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da7213_priv *da7213; + int ret; + + da7213 = devm_kzalloc(&i2c->dev, sizeof(*da7213), GFP_KERNEL); + if (!da7213) + return -ENOMEM; + + i2c_set_clientdata(i2c, da7213); + + da7213->regmap = devm_regmap_init_i2c(i2c, &da7213_regmap_config); + if (IS_ERR(da7213->regmap)) { + ret = PTR_ERR(da7213->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_da7213, &da7213_dai, 1); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register da7213 component: %d\n", + ret); + } + return ret; +} + +static const struct i2c_device_id da7213_i2c_id[] = { + { "da7213", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da7213_i2c_id); + +/* I2C codec control layer */ +static struct i2c_driver da7213_i2c_driver = { + .driver = { + .name = "da7213", + .of_match_table = of_match_ptr(da7213_of_match), + .acpi_match_table = ACPI_PTR(da7213_acpi_match), + }, + .probe = da7213_i2c_probe, + .id_table = da7213_i2c_id, +}; + +module_i2c_driver(da7213_i2c_driver); + +MODULE_DESCRIPTION("ASoC DA7213 Codec driver"); +MODULE_AUTHOR("Adam Thomson "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da7213.h b/sound/soc/codecs/da7213.h new file mode 100644 index 000000000..5a78dba1d --- /dev/null +++ b/sound/soc/codecs/da7213.h @@ -0,0 +1,537 @@ +/* + * da7213.h - DA7213 ASoC Codec Driver + * + * Copyright (c) 2013 Dialog Semiconductor + * + * Author: Adam Thomson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _DA7213_H +#define _DA7213_H + +#include +#include +#include + +/* + * Registers + */ + +/* Status Registers */ +#define DA7213_STATUS1 0x02 +#define DA7213_PLL_STATUS 0x03 +#define DA7213_AUX_L_GAIN_STATUS 0x04 +#define DA7213_AUX_R_GAIN_STATUS 0x05 +#define DA7213_MIC_1_GAIN_STATUS 0x06 +#define DA7213_MIC_2_GAIN_STATUS 0x07 +#define DA7213_MIXIN_L_GAIN_STATUS 0x08 +#define DA7213_MIXIN_R_GAIN_STATUS 0x09 +#define DA7213_ADC_L_GAIN_STATUS 0x0A +#define DA7213_ADC_R_GAIN_STATUS 0x0B +#define DA7213_DAC_L_GAIN_STATUS 0x0C +#define DA7213_DAC_R_GAIN_STATUS 0x0D +#define DA7213_HP_L_GAIN_STATUS 0x0E +#define DA7213_HP_R_GAIN_STATUS 0x0F +#define DA7213_LINE_GAIN_STATUS 0x10 + +/* System Initialisation Registers */ +#define DA7213_DIG_ROUTING_DAI 0x21 +#define DA7213_SR 0x22 +#define DA7213_REFERENCES 0x23 +#define DA7213_PLL_FRAC_TOP 0x24 +#define DA7213_PLL_FRAC_BOT 0x25 +#define DA7213_PLL_INTEGER 0x26 +#define DA7213_PLL_CTRL 0x27 +#define DA7213_DAI_CLK_MODE 0x28 +#define DA7213_DAI_CTRL 0x29 +#define DA7213_DIG_ROUTING_DAC 0x2A +#define DA7213_ALC_CTRL1 0x2B + +/* Input - Gain, Select and Filter Registers */ +#define DA7213_AUX_L_GAIN 0x30 +#define DA7213_AUX_R_GAIN 0x31 +#define DA7213_MIXIN_L_SELECT 0x32 +#define DA7213_MIXIN_R_SELECT 0x33 +#define DA7213_MIXIN_L_GAIN 0x34 +#define DA7213_MIXIN_R_GAIN 0x35 +#define DA7213_ADC_L_GAIN 0x36 +#define DA7213_ADC_R_GAIN 0x37 +#define DA7213_ADC_FILTERS1 0x38 +#define DA7213_MIC_1_GAIN 0x39 +#define DA7213_MIC_2_GAIN 0x3A + +/* Output - Gain, Select and Filter Registers */ +#define DA7213_DAC_FILTERS5 0x40 +#define DA7213_DAC_FILTERS2 0x41 +#define DA7213_DAC_FILTERS3 0x42 +#define DA7213_DAC_FILTERS4 0x43 +#define DA7213_DAC_FILTERS1 0x44 +#define DA7213_DAC_L_GAIN 0x45 +#define DA7213_DAC_R_GAIN 0x46 +#define DA7213_CP_CTRL 0x47 +#define DA7213_HP_L_GAIN 0x48 +#define DA7213_HP_R_GAIN 0x49 +#define DA7213_LINE_GAIN 0x4A +#define DA7213_MIXOUT_L_SELECT 0x4B +#define DA7213_MIXOUT_R_SELECT 0x4C + +/* System Controller Registers */ +#define DA7213_SYSTEM_MODES_INPUT 0x50 +#define DA7213_SYSTEM_MODES_OUTPUT 0x51 + +/* Control Registers */ +#define DA7213_AUX_L_CTRL 0x60 +#define DA7213_AUX_R_CTRL 0x61 +#define DA7213_MICBIAS_CTRL 0x62 +#define DA7213_MIC_1_CTRL 0x63 +#define DA7213_MIC_2_CTRL 0x64 +#define DA7213_MIXIN_L_CTRL 0x65 +#define DA7213_MIXIN_R_CTRL 0x66 +#define DA7213_ADC_L_CTRL 0x67 +#define DA7213_ADC_R_CTRL 0x68 +#define DA7213_DAC_L_CTRL 0x69 +#define DA7213_DAC_R_CTRL 0x6A +#define DA7213_HP_L_CTRL 0x6B +#define DA7213_HP_R_CTRL 0x6C +#define DA7213_LINE_CTRL 0x6D +#define DA7213_MIXOUT_L_CTRL 0x6E +#define DA7213_MIXOUT_R_CTRL 0x6F + +/* Configuration Registers */ +#define DA7213_LDO_CTRL 0x90 +#define DA7213_IO_CTRL 0x91 +#define DA7213_GAIN_RAMP_CTRL 0x92 +#define DA7213_MIC_CONFIG 0x93 +#define DA7213_PC_COUNT 0x94 +#define DA7213_CP_VOL_THRESHOLD1 0x95 +#define DA7213_CP_DELAY 0x96 +#define DA7213_CP_DETECTOR 0x97 +#define DA7213_DAI_OFFSET 0x98 +#define DA7213_DIG_CTRL 0x99 +#define DA7213_ALC_CTRL2 0x9A +#define DA7213_ALC_CTRL3 0x9B +#define DA7213_ALC_NOISE 0x9C +#define DA7213_ALC_TARGET_MIN 0x9D +#define DA7213_ALC_TARGET_MAX 0x9E +#define DA7213_ALC_GAIN_LIMITS 0x9F +#define DA7213_ALC_ANA_GAIN_LIMITS 0xA0 +#define DA7213_ALC_ANTICLIP_CTRL 0xA1 +#define DA7213_ALC_ANTICLIP_LEVEL 0xA2 + +#define DA7213_ALC_OFFSET_AUTO_M_L 0xA3 +#define DA7213_ALC_OFFSET_AUTO_U_L 0xA4 +#define DA7213_ALC_OFFSET_MAN_M_L 0xA6 +#define DA7213_ALC_OFFSET_MAN_U_L 0xA7 +#define DA7213_ALC_OFFSET_AUTO_M_R 0xA8 +#define DA7213_ALC_OFFSET_AUTO_U_R 0xA9 +#define DA7213_ALC_OFFSET_MAN_M_R 0xAB +#define DA7213_ALC_OFFSET_MAN_U_R 0xAC +#define DA7213_ALC_CIC_OP_LVL_CTRL 0xAD +#define DA7213_ALC_CIC_OP_LVL_DATA 0xAE +#define DA7213_DAC_NG_SETUP_TIME 0xAF +#define DA7213_DAC_NG_OFF_THRESHOLD 0xB0 +#define DA7213_DAC_NG_ON_THRESHOLD 0xB1 +#define DA7213_DAC_NG_CTRL 0xB2 + + +/* + * Bit fields + */ + +/* DA7213_PLL_STATUS = 0x03 */ +#define DA7219_PLL_SRM_LOCK (0x1 << 1) + +/* DA7213_SR = 0x22 */ +#define DA7213_SR_8000 (0x1 << 0) +#define DA7213_SR_11025 (0x2 << 0) +#define DA7213_SR_12000 (0x3 << 0) +#define DA7213_SR_16000 (0x5 << 0) +#define DA7213_SR_22050 (0x6 << 0) +#define DA7213_SR_24000 (0x7 << 0) +#define DA7213_SR_32000 (0x9 << 0) +#define DA7213_SR_44100 (0xA << 0) +#define DA7213_SR_48000 (0xB << 0) +#define DA7213_SR_88200 (0xE << 0) +#define DA7213_SR_96000 (0xF << 0) + +/* DA7213_REFERENCES = 0x23 */ +#define DA7213_BIAS_EN (0x1 << 3) +#define DA7213_VMID_EN (0x1 << 7) + +/* DA7213_PLL_CTRL = 0x27 */ +#define DA7213_PLL_INDIV_5_TO_9_MHZ (0x0 << 2) +#define DA7213_PLL_INDIV_9_TO_18_MHZ (0x1 << 2) +#define DA7213_PLL_INDIV_18_TO_36_MHZ (0x2 << 2) +#define DA7213_PLL_INDIV_36_TO_54_MHZ (0x3 << 2) +#define DA7213_PLL_INDIV_MASK (0x3 << 2) +#define DA7213_PLL_MCLK_SQR_EN (0x1 << 4) +#define DA7213_PLL_32K_MODE (0x1 << 5) +#define DA7213_PLL_SRM_EN (0x1 << 6) +#define DA7213_PLL_EN (0x1 << 7) +#define DA7213_PLL_MODE_MASK (0x7 << 5) + +/* DA7213_DAI_CLK_MODE = 0x28 */ +#define DA7213_DAI_BCLKS_PER_WCLK_32 (0x0 << 0) +#define DA7213_DAI_BCLKS_PER_WCLK_64 (0x1 << 0) +#define DA7213_DAI_BCLKS_PER_WCLK_128 (0x2 << 0) +#define DA7213_DAI_BCLKS_PER_WCLK_256 (0x3 << 0) +#define DA7213_DAI_BCLKS_PER_WCLK_MASK (0x3 << 0) +#define DA7213_DAI_CLK_POL_INV (0x1 << 2) +#define DA7213_DAI_WCLK_POL_INV (0x1 << 3) +#define DA7213_DAI_CLK_EN_MASK (0x1 << 7) + +/* DA7213_DAI_CTRL = 0x29 */ +#define DA7213_DAI_FORMAT_I2S_MODE (0x0 << 0) +#define DA7213_DAI_FORMAT_LEFT_J (0x1 << 0) +#define DA7213_DAI_FORMAT_RIGHT_J (0x2 << 0) +#define DA7213_DAI_FORMAT_DSP (0x3 << 0) +#define DA7213_DAI_FORMAT_MASK (0x3 << 0) +#define DA7213_DAI_WORD_LENGTH_S16_LE (0x0 << 2) +#define DA7213_DAI_WORD_LENGTH_S20_LE (0x1 << 2) +#define DA7213_DAI_WORD_LENGTH_S24_LE (0x2 << 2) +#define DA7213_DAI_WORD_LENGTH_S32_LE (0x3 << 2) +#define DA7213_DAI_WORD_LENGTH_MASK (0x3 << 2) +#define DA7213_DAI_EN_SHIFT 7 + +/* DA7213_DIG_ROUTING_DAI = 0x21 */ +#define DA7213_DAI_L_SRC_SHIFT 0 +#define DA7213_DAI_R_SRC_SHIFT 4 +#define DA7213_DAI_SRC_MAX 4 + +/* DA7213_DIG_ROUTING_DAC = 0x2A */ +#define DA7213_DAC_L_SRC_SHIFT 0 +#define DA7213_DAC_L_MONO_SHIFT 3 +#define DA7213_DAC_R_SRC_SHIFT 4 +#define DA7213_DAC_R_MONO_SHIFT 7 +#define DA7213_DAC_SRC_MAX 4 +#define DA7213_DAC_MONO_MAX 0x1 + +/* DA7213_ALC_CTRL1 = 0x2B */ +#define DA7213_ALC_OFFSET_EN_SHIFT 0 +#define DA7213_ALC_OFFSET_EN_MAX 0x1 +#define DA7213_ALC_OFFSET_EN (0x1 << 0) +#define DA7213_ALC_SYNC_MODE (0x1 << 1) +#define DA7213_ALC_CALIB_MODE_MAN (0x1 << 2) +#define DA7213_ALC_L_EN_SHIFT 3 +#define DA7213_ALC_AUTO_CALIB_EN (0x1 << 4) +#define DA7213_ALC_CALIB_OVERFLOW (0x1 << 5) +#define DA7213_ALC_R_EN_SHIFT 7 +#define DA7213_ALC_EN_MAX 0x1 + +/* DA7213_AUX_L/R_GAIN = 0x30/0x31 */ +#define DA7213_AUX_AMP_GAIN_SHIFT 0 +#define DA7213_AUX_AMP_GAIN_MAX 0x3F + +/* DA7213_MIXIN_L/R_SELECT = 0x32/0x33 */ +#define DA7213_DMIC_EN_SHIFT 7 +#define DA7213_DMIC_EN_MAX 0x1 + +/* DA7213_MIXIN_L_SELECT = 0x32 */ +#define DA7213_MIXIN_L_MIX_SELECT_AUX_L_SHIFT 0 +#define DA7213_MIXIN_L_MIX_SELECT_MIC_1_SHIFT 1 +#define DA7213_MIXIN_L_MIX_SELECT_MIC_1 (0x1 << 1) +#define DA7213_MIXIN_L_MIX_SELECT_MIC_2_SHIFT 2 +#define DA7213_MIXIN_L_MIX_SELECT_MIC_2 (0x1 << 2) +#define DA7213_MIXIN_L_MIX_SELECT_MIXIN_R_SHIFT 3 +#define DA7213_MIXIN_L_MIX_SELECT_MAX 0x1 + +/* DA7213_MIXIN_R_SELECT = 0x33 */ +#define DA7213_MIXIN_R_MIX_SELECT_AUX_R_SHIFT 0 +#define DA7213_MIXIN_R_MIX_SELECT_MIC_2_SHIFT 1 +#define DA7213_MIXIN_R_MIX_SELECT_MIC_2 (0x1 << 1) +#define DA7213_MIXIN_R_MIX_SELECT_MIC_1_SHIFT 2 +#define DA7213_MIXIN_R_MIX_SELECT_MIC_1 (0x1 << 2) +#define DA7213_MIXIN_R_MIX_SELECT_MIXIN_L_SHIFT 3 +#define DA7213_MIXIN_R_MIX_SELECT_MAX 0x1 +#define DA7213_MIC_BIAS_OUTPUT_SELECT_2 (0x1 << 6) + +/* DA7213_MIXIN_L/R_GAIN = 0x34/0x35 */ +#define DA7213_MIXIN_AMP_GAIN_SHIFT 0 +#define DA7213_MIXIN_AMP_GAIN_MAX 0xF + +/* DA7213_ADC_L/R_GAIN = 0x36/0x37 */ +#define DA7213_ADC_AMP_GAIN_SHIFT 0 +#define DA7213_ADC_AMP_GAIN_MAX 0x7F + +/* DA7213_ADC/DAC_FILTERS1 = 0x38/0x44 */ +#define DA7213_VOICE_HPF_CORNER_SHIFT 0 +#define DA7213_VOICE_HPF_CORNER_MAX 8 +#define DA7213_VOICE_EN_SHIFT 3 +#define DA7213_VOICE_EN_MAX 0x1 +#define DA7213_AUDIO_HPF_CORNER_SHIFT 4 +#define DA7213_AUDIO_HPF_CORNER_MAX 4 +#define DA7213_HPF_EN_SHIFT 7 +#define DA7213_HPF_EN_MAX 0x1 + +/* DA7213_MIC_1/2_GAIN = 0x39/0x3A */ +#define DA7213_MIC_AMP_GAIN_SHIFT 0 +#define DA7213_MIC_AMP_GAIN_MAX 0x7 + +/* DA7213_DAC_FILTERS5 = 0x40 */ +#define DA7213_DAC_SOFTMUTE_EN_SHIFT 7 +#define DA7213_DAC_SOFTMUTE_EN_MAX 0x1 +#define DA7213_DAC_SOFTMUTE_RATE_SHIFT 4 +#define DA7213_DAC_SOFTMUTE_RATE_MAX 7 + +/* DA7213_DAC_FILTERS2/3/4 = 0x41/0x42/0x43 */ +#define DA7213_DAC_EQ_BAND_MAX 0xF + +/* DA7213_DAC_FILTERS2 = 0x41 */ +#define DA7213_DAC_EQ_BAND1_SHIFT 0 +#define DA7213_DAC_EQ_BAND2_SHIFT 4 + +/* DA7213_DAC_FILTERS2 = 0x42 */ +#define DA7213_DAC_EQ_BAND3_SHIFT 0 +#define DA7213_DAC_EQ_BAND4_SHIFT 4 + +/* DA7213_DAC_FILTERS4 = 0x43 */ +#define DA7213_DAC_EQ_BAND5_SHIFT 0 +#define DA7213_DAC_EQ_EN_SHIFT 7 +#define DA7213_DAC_EQ_EN_MAX 0x1 + +/* DA7213_DAC_L/R_GAIN = 0x45/0x46 */ +#define DA7213_DAC_AMP_GAIN_SHIFT 0 +#define DA7213_DAC_AMP_GAIN_MAX 0x7F + +/* DA7213_HP_L/R_GAIN = 0x45/0x46 */ +#define DA7213_HP_AMP_GAIN_SHIFT 0 +#define DA7213_HP_AMP_GAIN_MAX 0x3F + +/* DA7213_CP_CTRL = 0x47 */ +#define DA7213_CP_EN_SHIFT 7 + +/* DA7213_LINE_GAIN = 0x4A */ +#define DA7213_LINE_AMP_GAIN_SHIFT 0 +#define DA7213_LINE_AMP_GAIN_MAX 0x3F + +/* DA7213_MIXOUT_L_SELECT = 0x4B */ +#define DA7213_MIXOUT_L_MIX_SELECT_AUX_L_SHIFT 0 +#define DA7213_MIXOUT_L_MIX_SELECT_MIXIN_L_SHIFT 1 +#define DA7213_MIXOUT_L_MIX_SELECT_MIXIN_R_SHIFT 2 +#define DA7213_MIXOUT_L_MIX_SELECT_DAC_L_SHIFT 3 +#define DA7213_MIXOUT_L_MIX_SELECT_AUX_L_INVERTED_SHIFT 4 +#define DA7213_MIXOUT_L_MIX_SELECT_MIXIN_L_INVERTED_SHIFT 5 +#define DA7213_MIXOUT_L_MIX_SELECT_MIXIN_R_INVERTED_SHIFT 6 +#define DA7213_MIXOUT_L_MIX_SELECT_MAX 0x1 + +/* DA7213_MIXOUT_R_SELECT = 0x4C */ +#define DA7213_MIXOUT_R_MIX_SELECT_AUX_R_SHIFT 0 +#define DA7213_MIXOUT_R_MIX_SELECT_MIXIN_R_SHIFT 1 +#define DA7213_MIXOUT_R_MIX_SELECT_MIXIN_L_SHIFT 2 +#define DA7213_MIXOUT_R_MIX_SELECT_DAC_R_SHIFT 3 +#define DA7213_MIXOUT_R_MIX_SELECT_AUX_R_INVERTED_SHIFT 4 +#define DA7213_MIXOUT_R_MIX_SELECT_MIXIN_R_INVERTED_SHIFT 5 +#define DA7213_MIXOUT_R_MIX_SELECT_MIXIN_L_INVERTED_SHIFT 6 +#define DA7213_MIXOUT_R_MIX_SELECT_MAX 0x1 + +/* + * DA7213_AUX_L/R_CTRL = 0x60/0x61, + * DA7213_MIC_1/2_CTRL = 0x63/0x64, + * DA7213_MIXIN_L/R_CTRL = 0x65/0x66, + * DA7213_ADC_L/R_CTRL = 0x65/0x66, + * DA7213_DAC_L/R_CTRL = 0x69/0x6A, + * DA7213_HP_L/R_CTRL = 0x6B/0x6C, + * DA7213_LINE_CTRL = 0x6D + */ +#define DA7213_MUTE_EN_SHIFT 6 +#define DA7213_MUTE_EN_MAX 0x1 +#define DA7213_MUTE_EN (0x1 << 6) + +/* + * DA7213_AUX_L/R_CTRL = 0x60/0x61, + * DA7213_MIXIN_L/R_CTRL = 0x65/0x66, + * DA7213_ADC_L/R_CTRL = 0x65/0x66, + * DA7213_DAC_L/R_CTRL = 0x69/0x6A, + * DA7213_HP_L/R_CTRL = 0x6B/0x6C, + * DA7213_LINE_CTRL = 0x6D + */ +#define DA7213_GAIN_RAMP_EN_SHIFT 5 +#define DA7213_GAIN_RAMP_EN_MAX 0x1 +#define DA7213_GAIN_RAMP_EN (0x1 << 5) + +/* + * DA7213_AUX_L/R_CTRL = 0x60/0x61, + * DA7213_MIXIN_L/R_CTRL = 0x65/0x66, + * DA7213_HP_L/R_CTRL = 0x6B/0x6C, + * DA7213_LINE_CTRL = 0x6D + */ +#define DA7213_ZC_EN_SHIFT 4 +#define DA7213_ZC_EN_MAX 0x1 + +/* + * DA7213_AUX_L/R_CTRL = 0x60/0x61, + * DA7213_MIC_1/2_CTRL = 0x63/0x64, + * DA7213_MIXIN_L/R_CTRL = 0x65/0x66, + * DA7213_HP_L/R_CTRL = 0x6B/0x6C, + * DA7213_MIXOUT_L/R_CTRL = 0x6E/0x6F, + * DA7213_LINE_CTRL = 0x6D + */ +#define DA7213_AMP_EN_SHIFT 7 + +/* DA7213_MIC_1/2_CTRL = 0x63/0x64 */ +#define DA7213_MIC_AMP_IN_SEL_SHIFT 2 +#define DA7213_MIC_AMP_IN_SEL_MAX 3 + +/* DA7213_MICBIAS_CTRL = 0x62 */ +#define DA7213_MICBIAS1_LEVEL_SHIFT 0 +#define DA7213_MICBIAS1_LEVEL_MASK (0x3 << 0) +#define DA7213_MICBIAS1_EN_SHIFT 3 +#define DA7213_MICBIAS2_LEVEL_SHIFT 4 +#define DA7213_MICBIAS2_LEVEL_MASK (0x3 << 4) +#define DA7213_MICBIAS2_EN_SHIFT 7 + +/* DA7213_MIXIN_L/R_CTRL = 0x65/0x66 */ +#define DA7213_MIXIN_MIX_EN (0x1 << 3) + +/* DA7213_ADC_L/R_CTRL = 0x67/0x68 */ +#define DA7213_ADC_EN_SHIFT 7 +#define DA7213_ADC_EN (0x1 << 7) + +/* DA7213_DAC_L/R_CTRL = 0x69/0x6A*/ +#define DA7213_DAC_EN_SHIFT 7 + +/* DA7213_HP_L/R_CTRL = 0x6B/0x6C */ +#define DA7213_HP_AMP_OE (0x1 << 3) + +/* DA7213_LINE_CTRL = 0x6D */ +#define DA7213_LINE_AMP_OE (0x1 << 3) + +/* DA7213_MIXOUT_L/R_CTRL = 0x6E/0x6F */ +#define DA7213_MIXOUT_MIX_EN (0x1 << 3) + +/* DA7213_GAIN_RAMP_CTRL = 0x92 */ +#define DA7213_GAIN_RAMP_RATE_SHIFT 0 +#define DA7213_GAIN_RAMP_RATE_MAX 4 + +/* DA7213_MIC_CONFIG = 0x93 */ +#define DA7213_DMIC_DATA_SEL_SHIFT 0 +#define DA7213_DMIC_DATA_SEL_MASK (0x1 << 0) +#define DA7213_DMIC_SAMPLEPHASE_SHIFT 1 +#define DA7213_DMIC_SAMPLEPHASE_MASK (0x1 << 1) +#define DA7213_DMIC_CLK_RATE_SHIFT 2 +#define DA7213_DMIC_CLK_RATE_MASK (0x1 << 2) + +/* DA7213_PC_COUNT = 0x94 */ +#define DA7213_PC_FREERUN_MASK (0x1 << 0) + +/* DA7213_DIG_CTRL = 0x99 */ +#define DA7213_DAC_L_INV_SHIFT 3 +#define DA7213_DAC_R_INV_SHIFT 7 +#define DA7213_DAC_INV_MAX 0x1 + +/* DA7213_ALC_CTRL2 = 0x9A */ +#define DA7213_ALC_ATTACK_SHIFT 0 +#define DA7213_ALC_ATTACK_MAX 13 +#define DA7213_ALC_RELEASE_SHIFT 4 +#define DA7213_ALC_RELEASE_MAX 11 + +/* DA7213_ALC_CTRL3 = 0x9B */ +#define DA7213_ALC_HOLD_SHIFT 0 +#define DA7213_ALC_HOLD_MAX 16 +#define DA7213_ALC_INTEG_ATTACK_SHIFT 4 +#define DA7213_ALC_INTEG_RELEASE_SHIFT 6 +#define DA7213_ALC_INTEG_MAX 4 + +/* + * DA7213_ALC_NOISE = 0x9C, + * DA7213_ALC_TARGET_MIN/MAX = 0x9D/0x9E + */ +#define DA7213_ALC_THRESHOLD_SHIFT 0 +#define DA7213_ALC_THRESHOLD_MAX 0x3F + +/* DA7213_ALC_GAIN_LIMITS = 0x9F */ +#define DA7213_ALC_ATTEN_MAX_SHIFT 0 +#define DA7213_ALC_GAIN_MAX_SHIFT 4 +#define DA7213_ALC_ATTEN_GAIN_MAX_MAX 0xF + +/* DA7213_ALC_ANA_GAIN_LIMITS = 0xA0 */ +#define DA7213_ALC_ANA_GAIN_MIN_SHIFT 0 +#define DA7213_ALC_ANA_GAIN_MAX_SHIFT 4 +#define DA7213_ALC_ANA_GAIN_MAX 0x7 + +/* DA7213_ALC_ANTICLIP_CTRL = 0xA1 */ +#define DA7213_ALC_ANTICLIP_EN_SHIFT 7 +#define DA7213_ALC_ANTICLIP_EN_MAX 0x1 + +/* DA7213_ALC_ANTICLIP_LEVEL = 0xA2 */ +#define DA7213_ALC_ANTICLIP_LEVEL_SHIFT 0 +#define DA7213_ALC_ANTICLIP_LEVEL_MAX 0x7F + +/* DA7213_ALC_CIC_OP_LVL_CTRL = 0xAD */ +#define DA7213_ALC_DATA_MIDDLE (0x2 << 0) +#define DA7213_ALC_DATA_TOP (0x3 << 0) +#define DA7213_ALC_CIC_OP_CHANNEL_LEFT (0x0 << 7) +#define DA7213_ALC_CIC_OP_CHANNEL_RIGHT (0x1 << 7) + +/* DA7213_DAC_NG_SETUP_TIME = 0xAF */ +#define DA7213_DAC_NG_SETUP_TIME_SHIFT 0 +#define DA7213_DAC_NG_SETUP_TIME_MAX 4 +#define DA7213_DAC_NG_RAMPUP_RATE_SHIFT 2 +#define DA7213_DAC_NG_RAMPDN_RATE_SHIFT 3 +#define DA7213_DAC_NG_RAMP_RATE_MAX 2 + +/* DA7213_DAC_NG_OFF/ON_THRESH = 0xB0/0xB1 */ +#define DA7213_DAC_NG_THRESHOLD_SHIFT 0 +#define DA7213_DAC_NG_THRESHOLD_MAX 0x7 + +/* DA7213_DAC_NG_CTRL = 0xB2 */ +#define DA7213_DAC_NG_EN_SHIFT 7 +#define DA7213_DAC_NG_EN_MAX 0x1 + + +/* + * General defines + */ + +/* Register inversion */ +#define DA7213_NO_INVERT 0 +#define DA7213_INVERT 1 + +/* Byte related defines */ +#define DA7213_BYTE_SHIFT 8 +#define DA7213_BYTE_MASK 0xFF + +/* ALC related */ +#define DA7213_ALC_OFFSET_15_8 0x00FF00 +#define DA7213_ALC_OFFSET_19_16 0x0F0000 +#define DA7213_ALC_AVG_ITERATIONS 5 + +/* PLL related */ +#define DA7213_PLL_FREQ_OUT_90316800 90316800 +#define DA7213_PLL_FREQ_OUT_98304000 98304000 +#define DA7213_PLL_FREQ_OUT_94310400 94310400 +#define DA7213_PLL_INDIV_5_TO_9_MHZ_VAL 2 +#define DA7213_PLL_INDIV_9_TO_18_MHZ_VAL 4 +#define DA7213_PLL_INDIV_18_TO_36_MHZ_VAL 8 +#define DA7213_PLL_INDIV_36_TO_54_MHZ_VAL 16 +#define DA7213_SRM_CHECK_RETRIES 8 + +enum da7213_clk_src { + DA7213_CLKSRC_MCLK = 0, + DA7213_CLKSRC_MCLK_SQR, +}; + +enum da7213_sys_clk { + DA7213_SYSCLK_MCLK = 0, + DA7213_SYSCLK_PLL, + DA7213_SYSCLK_PLL_SRM, + DA7213_SYSCLK_PLL_32KHZ +}; + +/* Codec private data */ +struct da7213_priv { + struct regmap *regmap; + struct clk *mclk; + unsigned int mclk_rate; + int clk_src; + bool master; + bool alc_calib_auto; + bool alc_en; + struct da7213_platform_data *pdata; +}; + +#endif /* _DA7213_H */ diff --git a/sound/soc/codecs/da7218.c b/sound/soc/codecs/da7218.c new file mode 100644 index 000000000..ddc90ac0b --- /dev/null +++ b/sound/soc/codecs/da7218.c @@ -0,0 +1,3326 @@ +/* + * da7218.c - DA7218 ALSA SoC Codec Driver + * + * Copyright (c) 2015 Dialog Semiconductor + * + * Author: Adam Thomson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "da7218.h" + + +/* + * TLVs and Enums + */ + +/* Input TLVs */ +static const DECLARE_TLV_DB_SCALE(da7218_mic_gain_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7218_mixin_gain_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(da7218_in_dig_gain_tlv, -8325, 75, 0); +static const DECLARE_TLV_DB_SCALE(da7218_ags_trigger_tlv, -9000, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7218_ags_att_max_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7218_alc_threshold_tlv, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(da7218_alc_gain_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7218_alc_ana_gain_tlv, 0, 600, 0); + +/* Input/Output TLVs */ +static const DECLARE_TLV_DB_SCALE(da7218_dmix_gain_tlv, -4200, 150, 0); + +/* Output TLVs */ +static const DECLARE_TLV_DB_SCALE(da7218_dgs_trigger_tlv, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(da7218_dgs_anticlip_tlv, -4200, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7218_dgs_signal_tlv, -9000, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7218_out_eq_band_tlv, -1050, 150, 0); +static const DECLARE_TLV_DB_SCALE(da7218_out_dig_gain_tlv, -8325, 75, 0); +static const DECLARE_TLV_DB_SCALE(da7218_dac_ng_threshold_tlv, -10200, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7218_mixout_gain_tlv, -100, 50, 0); +static const DECLARE_TLV_DB_SCALE(da7218_hp_gain_tlv, -5700, 150, 0); + +/* Input Enums */ +static const char * const da7218_alc_attack_rate_txt[] = { + "7.33/fs", "14.66/fs", "29.32/fs", "58.64/fs", "117.3/fs", "234.6/fs", + "469.1/fs", "938.2/fs", "1876/fs", "3753/fs", "7506/fs", "15012/fs", + "30024/fs", +}; + +static const struct soc_enum da7218_alc_attack_rate = + SOC_ENUM_SINGLE(DA7218_ALC_CTRL2, DA7218_ALC_ATTACK_SHIFT, + DA7218_ALC_ATTACK_MAX, da7218_alc_attack_rate_txt); + +static const char * const da7218_alc_release_rate_txt[] = { + "28.66/fs", "57.33/fs", "114.6/fs", "229.3/fs", "458.6/fs", "917.1/fs", + "1834/fs", "3668/fs", "7337/fs", "14674/fs", "29348/fs", +}; + +static const struct soc_enum da7218_alc_release_rate = + SOC_ENUM_SINGLE(DA7218_ALC_CTRL2, DA7218_ALC_RELEASE_SHIFT, + DA7218_ALC_RELEASE_MAX, da7218_alc_release_rate_txt); + +static const char * const da7218_alc_hold_time_txt[] = { + "62/fs", "124/fs", "248/fs", "496/fs", "992/fs", "1984/fs", "3968/fs", + "7936/fs", "15872/fs", "31744/fs", "63488/fs", "126976/fs", + "253952/fs", "507904/fs", "1015808/fs", "2031616/fs" +}; + +static const struct soc_enum da7218_alc_hold_time = + SOC_ENUM_SINGLE(DA7218_ALC_CTRL3, DA7218_ALC_HOLD_SHIFT, + DA7218_ALC_HOLD_MAX, da7218_alc_hold_time_txt); + +static const char * const da7218_alc_anticlip_step_txt[] = { + "0.034dB/fs", "0.068dB/fs", "0.136dB/fs", "0.272dB/fs", +}; + +static const struct soc_enum da7218_alc_anticlip_step = + SOC_ENUM_SINGLE(DA7218_ALC_ANTICLIP_CTRL, + DA7218_ALC_ANTICLIP_STEP_SHIFT, + DA7218_ALC_ANTICLIP_STEP_MAX, + da7218_alc_anticlip_step_txt); + +static const char * const da7218_integ_rate_txt[] = { + "1/4", "1/16", "1/256", "1/65536" +}; + +static const struct soc_enum da7218_integ_attack_rate = + SOC_ENUM_SINGLE(DA7218_ENV_TRACK_CTRL, DA7218_INTEG_ATTACK_SHIFT, + DA7218_INTEG_MAX, da7218_integ_rate_txt); + +static const struct soc_enum da7218_integ_release_rate = + SOC_ENUM_SINGLE(DA7218_ENV_TRACK_CTRL, DA7218_INTEG_RELEASE_SHIFT, + DA7218_INTEG_MAX, da7218_integ_rate_txt); + +/* Input/Output Enums */ +static const char * const da7218_gain_ramp_rate_txt[] = { + "Nominal Rate * 8", "Nominal Rate", "Nominal Rate / 8", + "Nominal Rate / 16", +}; + +static const struct soc_enum da7218_gain_ramp_rate = + SOC_ENUM_SINGLE(DA7218_GAIN_RAMP_CTRL, DA7218_GAIN_RAMP_RATE_SHIFT, + DA7218_GAIN_RAMP_RATE_MAX, da7218_gain_ramp_rate_txt); + +static const char * const da7218_hpf_mode_txt[] = { + "Disabled", "Audio", "Voice", +}; + +static const unsigned int da7218_hpf_mode_val[] = { + DA7218_HPF_DISABLED, DA7218_HPF_AUDIO_EN, DA7218_HPF_VOICE_EN, +}; + +static const struct soc_enum da7218_in1_hpf_mode = + SOC_VALUE_ENUM_SINGLE(DA7218_IN_1_HPF_FILTER_CTRL, + DA7218_HPF_MODE_SHIFT, DA7218_HPF_MODE_MASK, + DA7218_HPF_MODE_MAX, da7218_hpf_mode_txt, + da7218_hpf_mode_val); + +static const struct soc_enum da7218_in2_hpf_mode = + SOC_VALUE_ENUM_SINGLE(DA7218_IN_2_HPF_FILTER_CTRL, + DA7218_HPF_MODE_SHIFT, DA7218_HPF_MODE_MASK, + DA7218_HPF_MODE_MAX, da7218_hpf_mode_txt, + da7218_hpf_mode_val); + +static const struct soc_enum da7218_out1_hpf_mode = + SOC_VALUE_ENUM_SINGLE(DA7218_OUT_1_HPF_FILTER_CTRL, + DA7218_HPF_MODE_SHIFT, DA7218_HPF_MODE_MASK, + DA7218_HPF_MODE_MAX, da7218_hpf_mode_txt, + da7218_hpf_mode_val); + +static const char * const da7218_audio_hpf_corner_txt[] = { + "2Hz", "4Hz", "8Hz", "16Hz", +}; + +static const struct soc_enum da7218_in1_audio_hpf_corner = + SOC_ENUM_SINGLE(DA7218_IN_1_HPF_FILTER_CTRL, + DA7218_IN_1_AUDIO_HPF_CORNER_SHIFT, + DA7218_AUDIO_HPF_CORNER_MAX, + da7218_audio_hpf_corner_txt); + +static const struct soc_enum da7218_in2_audio_hpf_corner = + SOC_ENUM_SINGLE(DA7218_IN_2_HPF_FILTER_CTRL, + DA7218_IN_2_AUDIO_HPF_CORNER_SHIFT, + DA7218_AUDIO_HPF_CORNER_MAX, + da7218_audio_hpf_corner_txt); + +static const struct soc_enum da7218_out1_audio_hpf_corner = + SOC_ENUM_SINGLE(DA7218_OUT_1_HPF_FILTER_CTRL, + DA7218_OUT_1_AUDIO_HPF_CORNER_SHIFT, + DA7218_AUDIO_HPF_CORNER_MAX, + da7218_audio_hpf_corner_txt); + +static const char * const da7218_voice_hpf_corner_txt[] = { + "2.5Hz", "25Hz", "50Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz", +}; + +static const struct soc_enum da7218_in1_voice_hpf_corner = + SOC_ENUM_SINGLE(DA7218_IN_1_HPF_FILTER_CTRL, + DA7218_IN_1_VOICE_HPF_CORNER_SHIFT, + DA7218_VOICE_HPF_CORNER_MAX, + da7218_voice_hpf_corner_txt); + +static const struct soc_enum da7218_in2_voice_hpf_corner = + SOC_ENUM_SINGLE(DA7218_IN_2_HPF_FILTER_CTRL, + DA7218_IN_2_VOICE_HPF_CORNER_SHIFT, + DA7218_VOICE_HPF_CORNER_MAX, + da7218_voice_hpf_corner_txt); + +static const struct soc_enum da7218_out1_voice_hpf_corner = + SOC_ENUM_SINGLE(DA7218_OUT_1_HPF_FILTER_CTRL, + DA7218_OUT_1_VOICE_HPF_CORNER_SHIFT, + DA7218_VOICE_HPF_CORNER_MAX, + da7218_voice_hpf_corner_txt); + +static const char * const da7218_tonegen_dtmf_key_txt[] = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", + "*", "#" +}; + +static const struct soc_enum da7218_tonegen_dtmf_key = + SOC_ENUM_SINGLE(DA7218_TONE_GEN_CFG1, DA7218_DTMF_REG_SHIFT, + DA7218_DTMF_REG_MAX, da7218_tonegen_dtmf_key_txt); + +static const char * const da7218_tonegen_swg_sel_txt[] = { + "Sum", "SWG1", "SWG2", "SWG1_1-Cos" +}; + +static const struct soc_enum da7218_tonegen_swg_sel = + SOC_ENUM_SINGLE(DA7218_TONE_GEN_CFG2, DA7218_SWG_SEL_SHIFT, + DA7218_SWG_SEL_MAX, da7218_tonegen_swg_sel_txt); + +/* Output Enums */ +static const char * const da7218_dgs_rise_coeff_txt[] = { + "1/1", "1/16", "1/64", "1/256", "1/1024", "1/4096", "1/16384", +}; + +static const struct soc_enum da7218_dgs_rise_coeff = + SOC_ENUM_SINGLE(DA7218_DGS_RISE_FALL, DA7218_DGS_RISE_COEFF_SHIFT, + DA7218_DGS_RISE_COEFF_MAX, da7218_dgs_rise_coeff_txt); + +static const char * const da7218_dgs_fall_coeff_txt[] = { + "1/4", "1/16", "1/64", "1/256", "1/1024", "1/4096", "1/16384", "1/65536", +}; + +static const struct soc_enum da7218_dgs_fall_coeff = + SOC_ENUM_SINGLE(DA7218_DGS_RISE_FALL, DA7218_DGS_FALL_COEFF_SHIFT, + DA7218_DGS_FALL_COEFF_MAX, da7218_dgs_fall_coeff_txt); + +static const char * const da7218_dac_ng_setup_time_txt[] = { + "256 Samples", "512 Samples", "1024 Samples", "2048 Samples" +}; + +static const struct soc_enum da7218_dac_ng_setup_time = + SOC_ENUM_SINGLE(DA7218_DAC_NG_SETUP_TIME, + DA7218_DAC_NG_SETUP_TIME_SHIFT, + DA7218_DAC_NG_SETUP_TIME_MAX, + da7218_dac_ng_setup_time_txt); + +static const char * const da7218_dac_ng_rampup_txt[] = { + "0.22ms/dB", "0.0138ms/dB" +}; + +static const struct soc_enum da7218_dac_ng_rampup_rate = + SOC_ENUM_SINGLE(DA7218_DAC_NG_SETUP_TIME, + DA7218_DAC_NG_RAMPUP_RATE_SHIFT, + DA7218_DAC_NG_RAMPUP_RATE_MAX, + da7218_dac_ng_rampup_txt); + +static const char * const da7218_dac_ng_rampdown_txt[] = { + "0.88ms/dB", "14.08ms/dB" +}; + +static const struct soc_enum da7218_dac_ng_rampdown_rate = + SOC_ENUM_SINGLE(DA7218_DAC_NG_SETUP_TIME, + DA7218_DAC_NG_RAMPDN_RATE_SHIFT, + DA7218_DAC_NG_RAMPDN_RATE_MAX, + da7218_dac_ng_rampdown_txt); + +static const char * const da7218_cp_mchange_txt[] = { + "Largest Volume", "DAC Volume", "Signal Magnitude" +}; + +static const unsigned int da7218_cp_mchange_val[] = { + DA7218_CP_MCHANGE_LARGEST_VOL, DA7218_CP_MCHANGE_DAC_VOL, + DA7218_CP_MCHANGE_SIG_MAG +}; + +static const struct soc_enum da7218_cp_mchange = + SOC_VALUE_ENUM_SINGLE(DA7218_CP_CTRL, DA7218_CP_MCHANGE_SHIFT, + DA7218_CP_MCHANGE_REL_MASK, DA7218_CP_MCHANGE_MAX, + da7218_cp_mchange_txt, da7218_cp_mchange_val); + +static const char * const da7218_cp_fcontrol_txt[] = { + "1MHz", "500KHz", "250KHz", "125KHz", "63KHz", "0KHz" +}; + +static const struct soc_enum da7218_cp_fcontrol = + SOC_ENUM_SINGLE(DA7218_CP_DELAY, DA7218_CP_FCONTROL_SHIFT, + DA7218_CP_FCONTROL_MAX, da7218_cp_fcontrol_txt); + +static const char * const da7218_cp_tau_delay_txt[] = { + "0ms", "2ms", "4ms", "16ms", "64ms", "128ms", "256ms", "512ms" +}; + +static const struct soc_enum da7218_cp_tau_delay = + SOC_ENUM_SINGLE(DA7218_CP_DELAY, DA7218_CP_TAU_DELAY_SHIFT, + DA7218_CP_TAU_DELAY_MAX, da7218_cp_tau_delay_txt); + +/* + * Control Functions + */ + +/* ALC */ +static void da7218_alc_calib(struct snd_soc_component *component) +{ + u8 mic_1_ctrl, mic_2_ctrl; + u8 mixin_1_ctrl, mixin_2_ctrl; + u8 in_1l_filt_ctrl, in_1r_filt_ctrl, in_2l_filt_ctrl, in_2r_filt_ctrl; + u8 in_1_hpf_ctrl, in_2_hpf_ctrl; + u8 calib_ctrl; + int i = 0; + bool calibrated = false; + + /* Save current state of MIC control registers */ + mic_1_ctrl = snd_soc_component_read32(component, DA7218_MIC_1_CTRL); + mic_2_ctrl = snd_soc_component_read32(component, DA7218_MIC_2_CTRL); + + /* Save current state of input mixer control registers */ + mixin_1_ctrl = snd_soc_component_read32(component, DA7218_MIXIN_1_CTRL); + mixin_2_ctrl = snd_soc_component_read32(component, DA7218_MIXIN_2_CTRL); + + /* Save current state of input filter control registers */ + in_1l_filt_ctrl = snd_soc_component_read32(component, DA7218_IN_1L_FILTER_CTRL); + in_1r_filt_ctrl = snd_soc_component_read32(component, DA7218_IN_1R_FILTER_CTRL); + in_2l_filt_ctrl = snd_soc_component_read32(component, DA7218_IN_2L_FILTER_CTRL); + in_2r_filt_ctrl = snd_soc_component_read32(component, DA7218_IN_2R_FILTER_CTRL); + + /* Save current state of input HPF control registers */ + in_1_hpf_ctrl = snd_soc_component_read32(component, DA7218_IN_1_HPF_FILTER_CTRL); + in_2_hpf_ctrl = snd_soc_component_read32(component, DA7218_IN_2_HPF_FILTER_CTRL); + + /* Enable then Mute MIC PGAs */ + snd_soc_component_update_bits(component, DA7218_MIC_1_CTRL, DA7218_MIC_1_AMP_EN_MASK, + DA7218_MIC_1_AMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_MIC_2_CTRL, DA7218_MIC_2_AMP_EN_MASK, + DA7218_MIC_2_AMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_MIC_1_CTRL, + DA7218_MIC_1_AMP_MUTE_EN_MASK, + DA7218_MIC_1_AMP_MUTE_EN_MASK); + snd_soc_component_update_bits(component, DA7218_MIC_2_CTRL, + DA7218_MIC_2_AMP_MUTE_EN_MASK, + DA7218_MIC_2_AMP_MUTE_EN_MASK); + + /* Enable input mixers unmuted */ + snd_soc_component_update_bits(component, DA7218_MIXIN_1_CTRL, + DA7218_MIXIN_1_AMP_EN_MASK | + DA7218_MIXIN_1_AMP_MUTE_EN_MASK, + DA7218_MIXIN_1_AMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_MIXIN_2_CTRL, + DA7218_MIXIN_2_AMP_EN_MASK | + DA7218_MIXIN_2_AMP_MUTE_EN_MASK, + DA7218_MIXIN_2_AMP_EN_MASK); + + /* Enable input filters unmuted */ + snd_soc_component_update_bits(component, DA7218_IN_1L_FILTER_CTRL, + DA7218_IN_1L_FILTER_EN_MASK | + DA7218_IN_1L_MUTE_EN_MASK, + DA7218_IN_1L_FILTER_EN_MASK); + snd_soc_component_update_bits(component, DA7218_IN_1R_FILTER_CTRL, + DA7218_IN_1R_FILTER_EN_MASK | + DA7218_IN_1R_MUTE_EN_MASK, + DA7218_IN_1R_FILTER_EN_MASK); + snd_soc_component_update_bits(component, DA7218_IN_2L_FILTER_CTRL, + DA7218_IN_2L_FILTER_EN_MASK | + DA7218_IN_2L_MUTE_EN_MASK, + DA7218_IN_2L_FILTER_EN_MASK); + snd_soc_component_update_bits(component, DA7218_IN_2R_FILTER_CTRL, + DA7218_IN_2R_FILTER_EN_MASK | + DA7218_IN_2R_MUTE_EN_MASK, + DA7218_IN_2R_FILTER_EN_MASK); + + /* + * Make sure input HPFs voice mode is disabled, otherwise for sampling + * rates above 32KHz the ADC signals will be stopped and will cause + * calibration to lock up. + */ + snd_soc_component_update_bits(component, DA7218_IN_1_HPF_FILTER_CTRL, + DA7218_IN_1_VOICE_EN_MASK, 0); + snd_soc_component_update_bits(component, DA7218_IN_2_HPF_FILTER_CTRL, + DA7218_IN_2_VOICE_EN_MASK, 0); + + /* Perform auto calibration */ + snd_soc_component_update_bits(component, DA7218_CALIB_CTRL, DA7218_CALIB_AUTO_EN_MASK, + DA7218_CALIB_AUTO_EN_MASK); + do { + calib_ctrl = snd_soc_component_read32(component, DA7218_CALIB_CTRL); + if (calib_ctrl & DA7218_CALIB_AUTO_EN_MASK) { + ++i; + usleep_range(DA7218_ALC_CALIB_DELAY_MIN, + DA7218_ALC_CALIB_DELAY_MAX); + } else { + calibrated = true; + } + + } while ((i < DA7218_ALC_CALIB_MAX_TRIES) && (!calibrated)); + + /* If auto calibration fails, disable DC offset, hybrid ALC */ + if ((!calibrated) || (calib_ctrl & DA7218_CALIB_OVERFLOW_MASK)) { + dev_warn(component->dev, + "ALC auto calibration failed - %s\n", + (calibrated) ? "overflow" : "timeout"); + snd_soc_component_update_bits(component, DA7218_CALIB_CTRL, + DA7218_CALIB_OFFSET_EN_MASK, 0); + snd_soc_component_update_bits(component, DA7218_ALC_CTRL1, + DA7218_ALC_SYNC_MODE_MASK, 0); + + } else { + /* Enable DC offset cancellation */ + snd_soc_component_update_bits(component, DA7218_CALIB_CTRL, + DA7218_CALIB_OFFSET_EN_MASK, + DA7218_CALIB_OFFSET_EN_MASK); + + /* Enable ALC hybrid mode */ + snd_soc_component_update_bits(component, DA7218_ALC_CTRL1, + DA7218_ALC_SYNC_MODE_MASK, + DA7218_ALC_SYNC_MODE_CH1 | + DA7218_ALC_SYNC_MODE_CH2); + } + + /* Restore input HPF control registers to original states */ + snd_soc_component_write(component, DA7218_IN_1_HPF_FILTER_CTRL, in_1_hpf_ctrl); + snd_soc_component_write(component, DA7218_IN_2_HPF_FILTER_CTRL, in_2_hpf_ctrl); + + /* Restore input filter control registers to original states */ + snd_soc_component_write(component, DA7218_IN_1L_FILTER_CTRL, in_1l_filt_ctrl); + snd_soc_component_write(component, DA7218_IN_1R_FILTER_CTRL, in_1r_filt_ctrl); + snd_soc_component_write(component, DA7218_IN_2L_FILTER_CTRL, in_2l_filt_ctrl); + snd_soc_component_write(component, DA7218_IN_2R_FILTER_CTRL, in_2r_filt_ctrl); + + /* Restore input mixer control registers to original state */ + snd_soc_component_write(component, DA7218_MIXIN_1_CTRL, mixin_1_ctrl); + snd_soc_component_write(component, DA7218_MIXIN_2_CTRL, mixin_2_ctrl); + + /* Restore MIC control registers to original states */ + snd_soc_component_write(component, DA7218_MIC_1_CTRL, mic_1_ctrl); + snd_soc_component_write(component, DA7218_MIC_2_CTRL, mic_2_ctrl); +} + +static int da7218_mixin_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + /* + * If ALC in operation and value of control has been updated, + * make sure calibrated offsets are updated. + */ + if ((ret == 1) && (da7218->alc_en)) + da7218_alc_calib(component); + + return ret; +} + +static int da7218_alc_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *) kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + unsigned int lvalue = ucontrol->value.integer.value[0]; + unsigned int rvalue = ucontrol->value.integer.value[1]; + unsigned int lshift = mc->shift; + unsigned int rshift = mc->rshift; + unsigned int mask = (mc->max << lshift) | (mc->max << rshift); + + /* Force ALC offset calibration if enabling ALC */ + if ((lvalue || rvalue) && (!da7218->alc_en)) + da7218_alc_calib(component); + + /* Update bits to detail which channels are enabled/disabled */ + da7218->alc_en &= ~mask; + da7218->alc_en |= (lvalue << lshift) | (rvalue << rshift); + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +/* ToneGen */ +static int da7218_tonegen_freq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mixer_ctrl = + (struct soc_mixer_control *) kcontrol->private_value; + unsigned int reg = mixer_ctrl->reg; + u16 val; + int ret; + + /* + * Frequency value spans two 8-bit registers, lower then upper byte. + * Therefore we need to convert to host endianness here. + */ + ret = regmap_raw_read(da7218->regmap, reg, &val, 2); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = le16_to_cpu(val); + + return 0; +} + +static int da7218_tonegen_freq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mixer_ctrl = + (struct soc_mixer_control *) kcontrol->private_value; + unsigned int reg = mixer_ctrl->reg; + u16 val; + + /* + * Frequency value spans two 8-bit registers, lower then upper byte. + * Therefore we need to convert to little endian here to align with + * HW registers. + */ + val = cpu_to_le16(ucontrol->value.integer.value[0]); + + return regmap_raw_write(da7218->regmap, reg, &val, 2); +} + +static int da7218_mic_lvl_det_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mixer_ctrl = + (struct soc_mixer_control *) kcontrol->private_value; + unsigned int lvalue = ucontrol->value.integer.value[0]; + unsigned int rvalue = ucontrol->value.integer.value[1]; + unsigned int lshift = mixer_ctrl->shift; + unsigned int rshift = mixer_ctrl->rshift; + unsigned int mask = (mixer_ctrl->max << lshift) | + (mixer_ctrl->max << rshift); + da7218->mic_lvl_det_en &= ~mask; + da7218->mic_lvl_det_en |= (lvalue << lshift) | (rvalue << rshift); + + /* + * Here we only enable the feature on paths which are already + * powered. If a channel is enabled here for level detect, but that path + * isn't powered, then the channel will actually be enabled when we do + * power the path (IN_FILTER widget events). This handling avoids + * unwanted level detect events. + */ + return snd_soc_component_write(component, mixer_ctrl->reg, + (da7218->in_filt_en & da7218->mic_lvl_det_en)); +} + +static int da7218_mic_lvl_det_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mixer_ctrl = + (struct soc_mixer_control *) kcontrol->private_value; + unsigned int lshift = mixer_ctrl->shift; + unsigned int rshift = mixer_ctrl->rshift; + unsigned int lmask = (mixer_ctrl->max << lshift); + unsigned int rmask = (mixer_ctrl->max << rshift); + + ucontrol->value.integer.value[0] = + (da7218->mic_lvl_det_en & lmask) >> lshift; + ucontrol->value.integer.value[1] = + (da7218->mic_lvl_det_en & rmask) >> rshift; + + return 0; +} + +static int da7218_biquad_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + + /* Determine which BiQuads we're setting based on size of config data */ + switch (bytes_ext->max) { + case DA7218_OUT_1_BIQ_5STAGE_CFG_SIZE: + memcpy(ucontrol->value.bytes.data, da7218->biq_5stage_coeff, + bytes_ext->max); + break; + case DA7218_SIDETONE_BIQ_3STAGE_CFG_SIZE: + memcpy(ucontrol->value.bytes.data, da7218->stbiq_3stage_coeff, + bytes_ext->max); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int da7218_biquad_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + u8 reg, out_filt1l; + u8 cfg[DA7218_BIQ_CFG_SIZE]; + int i; + + /* + * Determine which BiQuads we're setting based on size of config data, + * and stored the data for use by get function. + */ + switch (bytes_ext->max) { + case DA7218_OUT_1_BIQ_5STAGE_CFG_SIZE: + reg = DA7218_OUT_1_BIQ_5STAGE_DATA; + memcpy(da7218->biq_5stage_coeff, ucontrol->value.bytes.data, + bytes_ext->max); + break; + case DA7218_SIDETONE_BIQ_3STAGE_CFG_SIZE: + reg = DA7218_SIDETONE_BIQ_3STAGE_DATA; + memcpy(da7218->stbiq_3stage_coeff, ucontrol->value.bytes.data, + bytes_ext->max); + break; + default: + return -EINVAL; + } + + /* Make sure at least out filter1 enabled to allow programming */ + out_filt1l = snd_soc_component_read32(component, DA7218_OUT_1L_FILTER_CTRL); + snd_soc_component_write(component, DA7218_OUT_1L_FILTER_CTRL, + out_filt1l | DA7218_OUT_1L_FILTER_EN_MASK); + + for (i = 0; i < bytes_ext->max; ++i) { + cfg[DA7218_BIQ_CFG_DATA] = ucontrol->value.bytes.data[i]; + cfg[DA7218_BIQ_CFG_ADDR] = i; + regmap_raw_write(da7218->regmap, reg, cfg, DA7218_BIQ_CFG_SIZE); + } + + /* Restore filter to previous setting */ + snd_soc_component_write(component, DA7218_OUT_1L_FILTER_CTRL, out_filt1l); + + return 0; +} + + +/* + * KControls + */ + +static const struct snd_kcontrol_new da7218_snd_controls[] = { + /* Mics */ + SOC_SINGLE_TLV("Mic1 Volume", DA7218_MIC_1_GAIN, + DA7218_MIC_1_AMP_GAIN_SHIFT, DA7218_MIC_AMP_GAIN_MAX, + DA7218_NO_INVERT, da7218_mic_gain_tlv), + SOC_SINGLE("Mic1 Switch", DA7218_MIC_1_CTRL, + DA7218_MIC_1_AMP_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_SINGLE_TLV("Mic2 Volume", DA7218_MIC_2_GAIN, + DA7218_MIC_2_AMP_GAIN_SHIFT, DA7218_MIC_AMP_GAIN_MAX, + DA7218_NO_INVERT, da7218_mic_gain_tlv), + SOC_SINGLE("Mic2 Switch", DA7218_MIC_2_CTRL, + DA7218_MIC_2_AMP_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + + /* Mixer Input */ + SOC_SINGLE_EXT_TLV("Mixin1 Volume", DA7218_MIXIN_1_GAIN, + DA7218_MIXIN_1_AMP_GAIN_SHIFT, + DA7218_MIXIN_AMP_GAIN_MAX, DA7218_NO_INVERT, + snd_soc_get_volsw, da7218_mixin_gain_put, + da7218_mixin_gain_tlv), + SOC_SINGLE("Mixin1 Switch", DA7218_MIXIN_1_CTRL, + DA7218_MIXIN_1_AMP_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_SINGLE("Mixin1 Gain Ramp Switch", DA7218_MIXIN_1_CTRL, + DA7218_MIXIN_1_AMP_RAMP_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("Mixin1 ZC Gain Switch", DA7218_MIXIN_1_CTRL, + DA7218_MIXIN_1_AMP_ZC_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE_EXT_TLV("Mixin2 Volume", DA7218_MIXIN_2_GAIN, + DA7218_MIXIN_2_AMP_GAIN_SHIFT, + DA7218_MIXIN_AMP_GAIN_MAX, DA7218_NO_INVERT, + snd_soc_get_volsw, da7218_mixin_gain_put, + da7218_mixin_gain_tlv), + SOC_SINGLE("Mixin2 Switch", DA7218_MIXIN_2_CTRL, + DA7218_MIXIN_2_AMP_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_SINGLE("Mixin2 Gain Ramp Switch", DA7218_MIXIN_2_CTRL, + DA7218_MIXIN_2_AMP_RAMP_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("Mixin2 ZC Gain Switch", DA7218_MIXIN_2_CTRL, + DA7218_MIXIN_2_AMP_ZC_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + + /* ADCs */ + SOC_SINGLE("ADC1 AAF Switch", DA7218_ADC_1_CTRL, + DA7218_ADC_1_AAF_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("ADC2 AAF Switch", DA7218_ADC_2_CTRL, + DA7218_ADC_2_AAF_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("ADC LP Mode Switch", DA7218_ADC_MODE, + DA7218_ADC_LP_MODE_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + + /* Input Filters */ + SOC_SINGLE_TLV("In Filter1L Volume", DA7218_IN_1L_GAIN, + DA7218_IN_1L_DIGITAL_GAIN_SHIFT, + DA7218_IN_DIGITAL_GAIN_MAX, DA7218_NO_INVERT, + da7218_in_dig_gain_tlv), + SOC_SINGLE("In Filter1L Switch", DA7218_IN_1L_FILTER_CTRL, + DA7218_IN_1L_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_SINGLE("In Filter1L Gain Ramp Switch", DA7218_IN_1L_FILTER_CTRL, + DA7218_IN_1L_RAMP_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE_TLV("In Filter1R Volume", DA7218_IN_1R_GAIN, + DA7218_IN_1R_DIGITAL_GAIN_SHIFT, + DA7218_IN_DIGITAL_GAIN_MAX, DA7218_NO_INVERT, + da7218_in_dig_gain_tlv), + SOC_SINGLE("In Filter1R Switch", DA7218_IN_1R_FILTER_CTRL, + DA7218_IN_1R_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_SINGLE("In Filter1R Gain Ramp Switch", + DA7218_IN_1R_FILTER_CTRL, DA7218_IN_1R_RAMP_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), + SOC_SINGLE_TLV("In Filter2L Volume", DA7218_IN_2L_GAIN, + DA7218_IN_2L_DIGITAL_GAIN_SHIFT, + DA7218_IN_DIGITAL_GAIN_MAX, DA7218_NO_INVERT, + da7218_in_dig_gain_tlv), + SOC_SINGLE("In Filter2L Switch", DA7218_IN_2L_FILTER_CTRL, + DA7218_IN_2L_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_SINGLE("In Filter2L Gain Ramp Switch", DA7218_IN_2L_FILTER_CTRL, + DA7218_IN_2L_RAMP_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE_TLV("In Filter2R Volume", DA7218_IN_2R_GAIN, + DA7218_IN_2R_DIGITAL_GAIN_SHIFT, + DA7218_IN_DIGITAL_GAIN_MAX, DA7218_NO_INVERT, + da7218_in_dig_gain_tlv), + SOC_SINGLE("In Filter2R Switch", DA7218_IN_2R_FILTER_CTRL, + DA7218_IN_2R_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_SINGLE("In Filter2R Gain Ramp Switch", + DA7218_IN_2R_FILTER_CTRL, DA7218_IN_2R_RAMP_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), + + /* AGS */ + SOC_SINGLE_TLV("AGS Trigger", DA7218_AGS_TRIGGER, + DA7218_AGS_TRIGGER_SHIFT, DA7218_AGS_TRIGGER_MAX, + DA7218_INVERT, da7218_ags_trigger_tlv), + SOC_SINGLE_TLV("AGS Max Attenuation", DA7218_AGS_ATT_MAX, + DA7218_AGS_ATT_MAX_SHIFT, DA7218_AGS_ATT_MAX_MAX, + DA7218_NO_INVERT, da7218_ags_att_max_tlv), + SOC_SINGLE("AGS Anticlip Switch", DA7218_AGS_ANTICLIP_CTRL, + DA7218_AGS_ANTICLIP_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("AGS Channel1 Switch", DA7218_AGS_ENABLE, + DA7218_AGS_ENABLE_CHAN1_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("AGS Channel2 Switch", DA7218_AGS_ENABLE, + DA7218_AGS_ENABLE_CHAN2_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + + /* ALC */ + SOC_ENUM("ALC Attack Rate", da7218_alc_attack_rate), + SOC_ENUM("ALC Release Rate", da7218_alc_release_rate), + SOC_ENUM("ALC Hold Time", da7218_alc_hold_time), + SOC_SINGLE_TLV("ALC Noise Threshold", DA7218_ALC_NOISE, + DA7218_ALC_NOISE_SHIFT, DA7218_ALC_THRESHOLD_MAX, + DA7218_INVERT, da7218_alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Min Threshold", DA7218_ALC_TARGET_MIN, + DA7218_ALC_THRESHOLD_MIN_SHIFT, DA7218_ALC_THRESHOLD_MAX, + DA7218_INVERT, da7218_alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Threshold", DA7218_ALC_TARGET_MAX, + DA7218_ALC_THRESHOLD_MAX_SHIFT, DA7218_ALC_THRESHOLD_MAX, + DA7218_INVERT, da7218_alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Attenuation", DA7218_ALC_GAIN_LIMITS, + DA7218_ALC_ATTEN_MAX_SHIFT, DA7218_ALC_ATTEN_GAIN_MAX, + DA7218_NO_INVERT, da7218_alc_gain_tlv), + SOC_SINGLE_TLV("ALC Max Gain", DA7218_ALC_GAIN_LIMITS, + DA7218_ALC_GAIN_MAX_SHIFT, DA7218_ALC_ATTEN_GAIN_MAX, + DA7218_NO_INVERT, da7218_alc_gain_tlv), + SOC_SINGLE_RANGE_TLV("ALC Min Analog Gain", DA7218_ALC_ANA_GAIN_LIMITS, + DA7218_ALC_ANA_GAIN_MIN_SHIFT, + DA7218_ALC_ANA_GAIN_MIN, DA7218_ALC_ANA_GAIN_MAX, + DA7218_NO_INVERT, da7218_alc_ana_gain_tlv), + SOC_SINGLE_RANGE_TLV("ALC Max Analog Gain", DA7218_ALC_ANA_GAIN_LIMITS, + DA7218_ALC_ANA_GAIN_MAX_SHIFT, + DA7218_ALC_ANA_GAIN_MIN, DA7218_ALC_ANA_GAIN_MAX, + DA7218_NO_INVERT, da7218_alc_ana_gain_tlv), + SOC_ENUM("ALC Anticlip Step", da7218_alc_anticlip_step), + SOC_SINGLE("ALC Anticlip Switch", DA7218_ALC_ANTICLIP_CTRL, + DA7218_ALC_ANTICLIP_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_DOUBLE_EXT("ALC Channel1 Switch", DA7218_ALC_CTRL1, + DA7218_ALC_CHAN1_L_EN_SHIFT, DA7218_ALC_CHAN1_R_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT, + snd_soc_get_volsw, da7218_alc_sw_put), + SOC_DOUBLE_EXT("ALC Channel2 Switch", DA7218_ALC_CTRL1, + DA7218_ALC_CHAN2_L_EN_SHIFT, DA7218_ALC_CHAN2_R_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT, + snd_soc_get_volsw, da7218_alc_sw_put), + + /* Envelope Tracking */ + SOC_ENUM("Envelope Tracking Attack Rate", da7218_integ_attack_rate), + SOC_ENUM("Envelope Tracking Release Rate", da7218_integ_release_rate), + + /* Input High-Pass Filters */ + SOC_ENUM("In Filter1 HPF Mode", da7218_in1_hpf_mode), + SOC_ENUM("In Filter1 HPF Corner Audio", da7218_in1_audio_hpf_corner), + SOC_ENUM("In Filter1 HPF Corner Voice", da7218_in1_voice_hpf_corner), + SOC_ENUM("In Filter2 HPF Mode", da7218_in2_hpf_mode), + SOC_ENUM("In Filter2 HPF Corner Audio", da7218_in2_audio_hpf_corner), + SOC_ENUM("In Filter2 HPF Corner Voice", da7218_in2_voice_hpf_corner), + + /* Mic Level Detect */ + SOC_DOUBLE_EXT("Mic Level Detect Channel1 Switch", DA7218_LVL_DET_CTRL, + DA7218_LVL_DET_EN_CHAN1L_SHIFT, + DA7218_LVL_DET_EN_CHAN1R_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT, da7218_mic_lvl_det_sw_get, + da7218_mic_lvl_det_sw_put), + SOC_DOUBLE_EXT("Mic Level Detect Channel2 Switch", DA7218_LVL_DET_CTRL, + DA7218_LVL_DET_EN_CHAN2L_SHIFT, + DA7218_LVL_DET_EN_CHAN2R_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT, da7218_mic_lvl_det_sw_get, + da7218_mic_lvl_det_sw_put), + SOC_SINGLE("Mic Level Detect Level", DA7218_LVL_DET_LEVEL, + DA7218_LVL_DET_LEVEL_SHIFT, DA7218_LVL_DET_LEVEL_MAX, + DA7218_NO_INVERT), + + /* Digital Mixer (Input) */ + SOC_SINGLE_TLV("DMix In Filter1L Out1 DAIL Volume", + DA7218_DMIX_OUTDAI_1L_INFILT_1L_GAIN, + DA7218_OUTDAI_1L_INFILT_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1L Out1 DAIR Volume", + DA7218_DMIX_OUTDAI_1R_INFILT_1L_GAIN, + DA7218_OUTDAI_1R_INFILT_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1L Out2 DAIL Volume", + DA7218_DMIX_OUTDAI_2L_INFILT_1L_GAIN, + DA7218_OUTDAI_2L_INFILT_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1L Out2 DAIR Volume", + DA7218_DMIX_OUTDAI_2R_INFILT_1L_GAIN, + DA7218_OUTDAI_2R_INFILT_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In Filter1R Out1 DAIL Volume", + DA7218_DMIX_OUTDAI_1L_INFILT_1R_GAIN, + DA7218_OUTDAI_1L_INFILT_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1R Out1 DAIR Volume", + DA7218_DMIX_OUTDAI_1R_INFILT_1R_GAIN, + DA7218_OUTDAI_1R_INFILT_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1R Out2 DAIL Volume", + DA7218_DMIX_OUTDAI_2L_INFILT_1R_GAIN, + DA7218_OUTDAI_2L_INFILT_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1R Out2 DAIR Volume", + DA7218_DMIX_OUTDAI_2R_INFILT_1R_GAIN, + DA7218_OUTDAI_2R_INFILT_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In Filter2L Out1 DAIL Volume", + DA7218_DMIX_OUTDAI_1L_INFILT_2L_GAIN, + DA7218_OUTDAI_1L_INFILT_2L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2L Out1 DAIR Volume", + DA7218_DMIX_OUTDAI_1R_INFILT_2L_GAIN, + DA7218_OUTDAI_1R_INFILT_2L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2L Out2 DAIL Volume", + DA7218_DMIX_OUTDAI_2L_INFILT_2L_GAIN, + DA7218_OUTDAI_2L_INFILT_2L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2L Out2 DAIR Volume", + DA7218_DMIX_OUTDAI_2R_INFILT_2L_GAIN, + DA7218_OUTDAI_2R_INFILT_2L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In Filter2R Out1 DAIL Volume", + DA7218_DMIX_OUTDAI_1L_INFILT_2R_GAIN, + DA7218_OUTDAI_1L_INFILT_2R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2R Out1 DAIR Volume", + DA7218_DMIX_OUTDAI_1R_INFILT_2R_GAIN, + DA7218_OUTDAI_1R_INFILT_2R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2R Out2 DAIL Volume", + DA7218_DMIX_OUTDAI_2L_INFILT_2R_GAIN, + DA7218_OUTDAI_2L_INFILT_2R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2R Out2 DAIR Volume", + DA7218_DMIX_OUTDAI_2R_INFILT_2R_GAIN, + DA7218_OUTDAI_2R_INFILT_2R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix ToneGen Out1 DAIL Volume", + DA7218_DMIX_OUTDAI_1L_TONEGEN_GAIN, + DA7218_OUTDAI_1L_TONEGEN_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix ToneGen Out1 DAIR Volume", + DA7218_DMIX_OUTDAI_1R_TONEGEN_GAIN, + DA7218_OUTDAI_1R_TONEGEN_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix ToneGen Out2 DAIL Volume", + DA7218_DMIX_OUTDAI_2L_TONEGEN_GAIN, + DA7218_OUTDAI_2L_TONEGEN_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix ToneGen Out2 DAIR Volume", + DA7218_DMIX_OUTDAI_2R_TONEGEN_GAIN, + DA7218_OUTDAI_2R_TONEGEN_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In DAIL Out1 DAIL Volume", + DA7218_DMIX_OUTDAI_1L_INDAI_1L_GAIN, + DA7218_OUTDAI_1L_INDAI_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIL Out1 DAIR Volume", + DA7218_DMIX_OUTDAI_1R_INDAI_1L_GAIN, + DA7218_OUTDAI_1R_INDAI_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIL Out2 DAIL Volume", + DA7218_DMIX_OUTDAI_2L_INDAI_1L_GAIN, + DA7218_OUTDAI_2L_INDAI_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIL Out2 DAIR Volume", + DA7218_DMIX_OUTDAI_2R_INDAI_1L_GAIN, + DA7218_OUTDAI_2R_INDAI_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In DAIR Out1 DAIL Volume", + DA7218_DMIX_OUTDAI_1L_INDAI_1R_GAIN, + DA7218_OUTDAI_1L_INDAI_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIR Out1 DAIR Volume", + DA7218_DMIX_OUTDAI_1R_INDAI_1R_GAIN, + DA7218_OUTDAI_1R_INDAI_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIR Out2 DAIL Volume", + DA7218_DMIX_OUTDAI_2L_INDAI_1R_GAIN, + DA7218_OUTDAI_2L_INDAI_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIR Out2 DAIR Volume", + DA7218_DMIX_OUTDAI_2R_INDAI_1R_GAIN, + DA7218_OUTDAI_2R_INDAI_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + /* Digital Mixer (Output) */ + SOC_SINGLE_TLV("DMix In Filter1L Out FilterL Volume", + DA7218_DMIX_OUTFILT_1L_INFILT_1L_GAIN, + DA7218_OUTFILT_1L_INFILT_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1L Out FilterR Volume", + DA7218_DMIX_OUTFILT_1R_INFILT_1L_GAIN, + DA7218_OUTFILT_1R_INFILT_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In Filter1R Out FilterL Volume", + DA7218_DMIX_OUTFILT_1L_INFILT_1R_GAIN, + DA7218_OUTFILT_1L_INFILT_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter1R Out FilterR Volume", + DA7218_DMIX_OUTFILT_1R_INFILT_1R_GAIN, + DA7218_OUTFILT_1R_INFILT_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In Filter2L Out FilterL Volume", + DA7218_DMIX_OUTFILT_1L_INFILT_2L_GAIN, + DA7218_OUTFILT_1L_INFILT_2L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2L Out FilterR Volume", + DA7218_DMIX_OUTFILT_1R_INFILT_2L_GAIN, + DA7218_OUTFILT_1R_INFILT_2L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In Filter2R Out FilterL Volume", + DA7218_DMIX_OUTFILT_1L_INFILT_2R_GAIN, + DA7218_OUTFILT_1L_INFILT_2R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In Filter2R Out FilterR Volume", + DA7218_DMIX_OUTFILT_1R_INFILT_2R_GAIN, + DA7218_OUTFILT_1R_INFILT_2R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix ToneGen Out FilterL Volume", + DA7218_DMIX_OUTFILT_1L_TONEGEN_GAIN, + DA7218_OUTFILT_1L_TONEGEN_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix ToneGen Out FilterR Volume", + DA7218_DMIX_OUTFILT_1R_TONEGEN_GAIN, + DA7218_OUTFILT_1R_TONEGEN_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In DAIL Out FilterL Volume", + DA7218_DMIX_OUTFILT_1L_INDAI_1L_GAIN, + DA7218_OUTFILT_1L_INDAI_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIL Out FilterR Volume", + DA7218_DMIX_OUTFILT_1R_INDAI_1L_GAIN, + DA7218_OUTFILT_1R_INDAI_1L_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + SOC_SINGLE_TLV("DMix In DAIR Out FilterL Volume", + DA7218_DMIX_OUTFILT_1L_INDAI_1R_GAIN, + DA7218_OUTFILT_1L_INDAI_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + SOC_SINGLE_TLV("DMix In DAIR Out FilterR Volume", + DA7218_DMIX_OUTFILT_1R_INDAI_1R_GAIN, + DA7218_OUTFILT_1R_INDAI_1R_GAIN_SHIFT, + DA7218_DMIX_GAIN_MAX, DA7218_NO_INVERT, + da7218_dmix_gain_tlv), + + /* Sidetone Filter */ + SND_SOC_BYTES_EXT("Sidetone BiQuad Coefficients", + DA7218_SIDETONE_BIQ_3STAGE_CFG_SIZE, + da7218_biquad_coeff_get, da7218_biquad_coeff_put), + SOC_SINGLE_TLV("Sidetone Volume", DA7218_SIDETONE_GAIN, + DA7218_SIDETONE_GAIN_SHIFT, DA7218_DMIX_GAIN_MAX, + DA7218_NO_INVERT, da7218_dmix_gain_tlv), + SOC_SINGLE("Sidetone Switch", DA7218_SIDETONE_CTRL, + DA7218_SIDETONE_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + + /* Tone Generator */ + SOC_ENUM("ToneGen DTMF Key", da7218_tonegen_dtmf_key), + SOC_SINGLE("ToneGen DTMF Switch", DA7218_TONE_GEN_CFG1, + DA7218_DTMF_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_ENUM("ToneGen Sinewave Gen Type", da7218_tonegen_swg_sel), + SOC_SINGLE_EXT("ToneGen Sinewave1 Freq", DA7218_TONE_GEN_FREQ1_L, + DA7218_FREQ1_L_SHIFT, DA7218_FREQ_MAX, DA7218_NO_INVERT, + da7218_tonegen_freq_get, da7218_tonegen_freq_put), + SOC_SINGLE_EXT("ToneGen Sinewave2 Freq", DA7218_TONE_GEN_FREQ2_L, + DA7218_FREQ2_L_SHIFT, DA7218_FREQ_MAX, DA7218_NO_INVERT, + da7218_tonegen_freq_get, da7218_tonegen_freq_put), + SOC_SINGLE("ToneGen On Time", DA7218_TONE_GEN_ON_PER, + DA7218_BEEP_ON_PER_SHIFT, DA7218_BEEP_ON_OFF_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("ToneGen Off Time", DA7218_TONE_GEN_OFF_PER, + DA7218_BEEP_OFF_PER_SHIFT, DA7218_BEEP_ON_OFF_MAX, + DA7218_NO_INVERT), + + /* Gain ramping */ + SOC_ENUM("Gain Ramp Rate", da7218_gain_ramp_rate), + + /* DGS */ + SOC_SINGLE_TLV("DGS Trigger", DA7218_DGS_TRIGGER, + DA7218_DGS_TRIGGER_LVL_SHIFT, DA7218_DGS_TRIGGER_MAX, + DA7218_INVERT, da7218_dgs_trigger_tlv), + SOC_ENUM("DGS Rise Coefficient", da7218_dgs_rise_coeff), + SOC_ENUM("DGS Fall Coefficient", da7218_dgs_fall_coeff), + SOC_SINGLE("DGS Sync Delay", DA7218_DGS_SYNC_DELAY, + DA7218_DGS_SYNC_DELAY_SHIFT, DA7218_DGS_SYNC_DELAY_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("DGS Fast SR Sync Delay", DA7218_DGS_SYNC_DELAY2, + DA7218_DGS_SYNC_DELAY2_SHIFT, DA7218_DGS_SYNC_DELAY_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("DGS Voice Filter Sync Delay", DA7218_DGS_SYNC_DELAY3, + DA7218_DGS_SYNC_DELAY3_SHIFT, DA7218_DGS_SYNC_DELAY3_MAX, + DA7218_NO_INVERT), + SOC_SINGLE_TLV("DGS Anticlip Level", DA7218_DGS_LEVELS, + DA7218_DGS_ANTICLIP_LVL_SHIFT, + DA7218_DGS_ANTICLIP_LVL_MAX, DA7218_INVERT, + da7218_dgs_anticlip_tlv), + SOC_SINGLE_TLV("DGS Signal Level", DA7218_DGS_LEVELS, + DA7218_DGS_SIGNAL_LVL_SHIFT, DA7218_DGS_SIGNAL_LVL_MAX, + DA7218_INVERT, da7218_dgs_signal_tlv), + SOC_SINGLE("DGS Gain Subrange Switch", DA7218_DGS_GAIN_CTRL, + DA7218_DGS_SUBR_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("DGS Gain Ramp Switch", DA7218_DGS_GAIN_CTRL, + DA7218_DGS_RAMP_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_SINGLE("DGS Gain Steps", DA7218_DGS_GAIN_CTRL, + DA7218_DGS_STEPS_SHIFT, DA7218_DGS_STEPS_MAX, + DA7218_NO_INVERT), + SOC_DOUBLE("DGS Switch", DA7218_DGS_ENABLE, DA7218_DGS_ENABLE_L_SHIFT, + DA7218_DGS_ENABLE_R_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + + /* Output High-Pass Filter */ + SOC_ENUM("Out Filter HPF Mode", da7218_out1_hpf_mode), + SOC_ENUM("Out Filter HPF Corner Audio", da7218_out1_audio_hpf_corner), + SOC_ENUM("Out Filter HPF Corner Voice", da7218_out1_voice_hpf_corner), + + /* 5-Band Equaliser */ + SOC_SINGLE_TLV("Out EQ Band1 Volume", DA7218_OUT_1_EQ_12_FILTER_CTRL, + DA7218_OUT_1_EQ_BAND1_SHIFT, DA7218_OUT_EQ_BAND_MAX, + DA7218_NO_INVERT, da7218_out_eq_band_tlv), + SOC_SINGLE_TLV("Out EQ Band2 Volume", DA7218_OUT_1_EQ_12_FILTER_CTRL, + DA7218_OUT_1_EQ_BAND2_SHIFT, DA7218_OUT_EQ_BAND_MAX, + DA7218_NO_INVERT, da7218_out_eq_band_tlv), + SOC_SINGLE_TLV("Out EQ Band3 Volume", DA7218_OUT_1_EQ_34_FILTER_CTRL, + DA7218_OUT_1_EQ_BAND3_SHIFT, DA7218_OUT_EQ_BAND_MAX, + DA7218_NO_INVERT, da7218_out_eq_band_tlv), + SOC_SINGLE_TLV("Out EQ Band4 Volume", DA7218_OUT_1_EQ_34_FILTER_CTRL, + DA7218_OUT_1_EQ_BAND4_SHIFT, DA7218_OUT_EQ_BAND_MAX, + DA7218_NO_INVERT, da7218_out_eq_band_tlv), + SOC_SINGLE_TLV("Out EQ Band5 Volume", DA7218_OUT_1_EQ_5_FILTER_CTRL, + DA7218_OUT_1_EQ_BAND5_SHIFT, DA7218_OUT_EQ_BAND_MAX, + DA7218_NO_INVERT, da7218_out_eq_band_tlv), + SOC_SINGLE("Out EQ Switch", DA7218_OUT_1_EQ_5_FILTER_CTRL, + DA7218_OUT_1_EQ_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + + /* BiQuad Filters */ + SND_SOC_BYTES_EXT("BiQuad Coefficients", + DA7218_OUT_1_BIQ_5STAGE_CFG_SIZE, + da7218_biquad_coeff_get, da7218_biquad_coeff_put), + SOC_SINGLE("BiQuad Filter Switch", DA7218_OUT_1_BIQ_5STAGE_CTRL, + DA7218_OUT_1_BIQ_5STAGE_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + + /* Output Filters */ + SOC_DOUBLE_R_RANGE_TLV("Out Filter Volume", DA7218_OUT_1L_GAIN, + DA7218_OUT_1R_GAIN, + DA7218_OUT_1L_DIGITAL_GAIN_SHIFT, + DA7218_OUT_DIGITAL_GAIN_MIN, + DA7218_OUT_DIGITAL_GAIN_MAX, DA7218_NO_INVERT, + da7218_out_dig_gain_tlv), + SOC_DOUBLE_R("Out Filter Switch", DA7218_OUT_1L_FILTER_CTRL, + DA7218_OUT_1R_FILTER_CTRL, DA7218_OUT_1L_MUTE_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_INVERT), + SOC_DOUBLE_R("Out Filter Gain Subrange Switch", + DA7218_OUT_1L_FILTER_CTRL, DA7218_OUT_1R_FILTER_CTRL, + DA7218_OUT_1L_SUBRANGE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_NO_INVERT), + SOC_DOUBLE_R("Out Filter Gain Ramp Switch", DA7218_OUT_1L_FILTER_CTRL, + DA7218_OUT_1R_FILTER_CTRL, DA7218_OUT_1L_RAMP_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), + + /* Mixer Output */ + SOC_DOUBLE_R_RANGE_TLV("Mixout Volume", DA7218_MIXOUT_L_GAIN, + DA7218_MIXOUT_R_GAIN, + DA7218_MIXOUT_L_AMP_GAIN_SHIFT, + DA7218_MIXOUT_AMP_GAIN_MIN, + DA7218_MIXOUT_AMP_GAIN_MAX, DA7218_NO_INVERT, + da7218_mixout_gain_tlv), + + /* DAC Noise Gate */ + SOC_ENUM("DAC NG Setup Time", da7218_dac_ng_setup_time), + SOC_ENUM("DAC NG Rampup Rate", da7218_dac_ng_rampup_rate), + SOC_ENUM("DAC NG Rampdown Rate", da7218_dac_ng_rampdown_rate), + SOC_SINGLE_TLV("DAC NG Off Threshold", DA7218_DAC_NG_OFF_THRESH, + DA7218_DAC_NG_OFF_THRESHOLD_SHIFT, + DA7218_DAC_NG_THRESHOLD_MAX, DA7218_NO_INVERT, + da7218_dac_ng_threshold_tlv), + SOC_SINGLE_TLV("DAC NG On Threshold", DA7218_DAC_NG_ON_THRESH, + DA7218_DAC_NG_ON_THRESHOLD_SHIFT, + DA7218_DAC_NG_THRESHOLD_MAX, DA7218_NO_INVERT, + da7218_dac_ng_threshold_tlv), + SOC_SINGLE("DAC NG Switch", DA7218_DAC_NG_CTRL, DA7218_DAC_NG_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), + + /* CP */ + SOC_ENUM("Charge Pump Track Mode", da7218_cp_mchange), + SOC_ENUM("Charge Pump Frequency", da7218_cp_fcontrol), + SOC_ENUM("Charge Pump Decay Rate", da7218_cp_tau_delay), + SOC_SINGLE("Charge Pump Threshold", DA7218_CP_VOL_THRESHOLD1, + DA7218_CP_THRESH_VDD2_SHIFT, DA7218_CP_THRESH_VDD2_MAX, + DA7218_NO_INVERT), + + /* Headphones */ + SOC_DOUBLE_R_RANGE_TLV("Headphone Volume", DA7218_HP_L_GAIN, + DA7218_HP_R_GAIN, DA7218_HP_L_AMP_GAIN_SHIFT, + DA7218_HP_AMP_GAIN_MIN, DA7218_HP_AMP_GAIN_MAX, + DA7218_NO_INVERT, da7218_hp_gain_tlv), + SOC_DOUBLE_R("Headphone Switch", DA7218_HP_L_CTRL, DA7218_HP_R_CTRL, + DA7218_HP_L_AMP_MUTE_EN_SHIFT, DA7218_SWITCH_EN_MAX, + DA7218_INVERT), + SOC_DOUBLE_R("Headphone Gain Ramp Switch", DA7218_HP_L_CTRL, + DA7218_HP_R_CTRL, DA7218_HP_L_AMP_RAMP_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), + SOC_DOUBLE_R("Headphone ZC Gain Switch", DA7218_HP_L_CTRL, + DA7218_HP_R_CTRL, DA7218_HP_L_AMP_ZC_EN_SHIFT, + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), +}; + + +/* + * DAPM Mux Controls + */ + +static const char * const da7218_mic_sel_text[] = { "Analog", "Digital" }; + +static const struct soc_enum da7218_mic1_sel = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(da7218_mic_sel_text), + da7218_mic_sel_text); + +static const struct snd_kcontrol_new da7218_mic1_sel_mux = + SOC_DAPM_ENUM("Mic1 Mux", da7218_mic1_sel); + +static const struct soc_enum da7218_mic2_sel = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(da7218_mic_sel_text), + da7218_mic_sel_text); + +static const struct snd_kcontrol_new da7218_mic2_sel_mux = + SOC_DAPM_ENUM("Mic2 Mux", da7218_mic2_sel); + +static const char * const da7218_sidetone_in_sel_txt[] = { + "In Filter1L", "In Filter1R", "In Filter2L", "In Filter2R" +}; + +static const struct soc_enum da7218_sidetone_in_sel = + SOC_ENUM_SINGLE(DA7218_SIDETONE_IN_SELECT, + DA7218_SIDETONE_IN_SELECT_SHIFT, + DA7218_SIDETONE_IN_SELECT_MAX, + da7218_sidetone_in_sel_txt); + +static const struct snd_kcontrol_new da7218_sidetone_in_sel_mux = + SOC_DAPM_ENUM("Sidetone Mux", da7218_sidetone_in_sel); + +static const char * const da7218_out_filt_biq_sel_txt[] = { + "Bypass", "Enabled" +}; + +static const struct soc_enum da7218_out_filtl_biq_sel = + SOC_ENUM_SINGLE(DA7218_OUT_1L_FILTER_CTRL, + DA7218_OUT_1L_BIQ_5STAGE_SEL_SHIFT, + DA7218_OUT_BIQ_5STAGE_SEL_MAX, + da7218_out_filt_biq_sel_txt); + +static const struct snd_kcontrol_new da7218_out_filtl_biq_sel_mux = + SOC_DAPM_ENUM("Out FilterL BiQuad Mux", da7218_out_filtl_biq_sel); + +static const struct soc_enum da7218_out_filtr_biq_sel = + SOC_ENUM_SINGLE(DA7218_OUT_1R_FILTER_CTRL, + DA7218_OUT_1R_BIQ_5STAGE_SEL_SHIFT, + DA7218_OUT_BIQ_5STAGE_SEL_MAX, + da7218_out_filt_biq_sel_txt); + +static const struct snd_kcontrol_new da7218_out_filtr_biq_sel_mux = + SOC_DAPM_ENUM("Out FilterR BiQuad Mux", da7218_out_filtr_biq_sel); + + +/* + * DAPM Mixer Controls + */ + +#define DA7218_DMIX_CTRLS(reg) \ + SOC_DAPM_SINGLE("In Filter1L Switch", reg, \ + DA7218_DMIX_SRC_INFILT1L, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("In Filter1R Switch", reg, \ + DA7218_DMIX_SRC_INFILT1R, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("In Filter2L Switch", reg, \ + DA7218_DMIX_SRC_INFILT2L, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("In Filter2R Switch", reg, \ + DA7218_DMIX_SRC_INFILT2R, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("ToneGen Switch", reg, \ + DA7218_DMIX_SRC_TONEGEN, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("DAIL Switch", reg, DA7218_DMIX_SRC_DAIL, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("DAIR Switch", reg, DA7218_DMIX_SRC_DAIR, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT) + +static const struct snd_kcontrol_new da7218_out_dai1l_mix_controls[] = { + DA7218_DMIX_CTRLS(DA7218_DROUTING_OUTDAI_1L), +}; + +static const struct snd_kcontrol_new da7218_out_dai1r_mix_controls[] = { + DA7218_DMIX_CTRLS(DA7218_DROUTING_OUTDAI_1R), +}; + +static const struct snd_kcontrol_new da7218_out_dai2l_mix_controls[] = { + DA7218_DMIX_CTRLS(DA7218_DROUTING_OUTDAI_2L), +}; + +static const struct snd_kcontrol_new da7218_out_dai2r_mix_controls[] = { + DA7218_DMIX_CTRLS(DA7218_DROUTING_OUTDAI_2R), +}; + +static const struct snd_kcontrol_new da7218_out_filtl_mix_controls[] = { + DA7218_DMIX_CTRLS(DA7218_DROUTING_OUTFILT_1L), +}; + +static const struct snd_kcontrol_new da7218_out_filtr_mix_controls[] = { + DA7218_DMIX_CTRLS(DA7218_DROUTING_OUTFILT_1R), +}; + +#define DA7218_DMIX_ST_CTRLS(reg) \ + SOC_DAPM_SINGLE("Out FilterL Switch", reg, \ + DA7218_DMIX_ST_SRC_OUTFILT1L, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("Out FilterR Switch", reg, \ + DA7218_DMIX_ST_SRC_OUTFILT1R, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT), \ + SOC_DAPM_SINGLE("Sidetone Switch", reg, \ + DA7218_DMIX_ST_SRC_SIDETONE, \ + DA7218_SWITCH_EN_MAX, DA7218_NO_INVERT) \ + +static const struct snd_kcontrol_new da7218_st_out_filtl_mix_controls[] = { + DA7218_DMIX_ST_CTRLS(DA7218_DROUTING_ST_OUTFILT_1L), +}; + +static const struct snd_kcontrol_new da7218_st_out_filtr_mix_controls[] = { + DA7218_DMIX_ST_CTRLS(DA7218_DROUTING_ST_OUTFILT_1R), +}; + + +/* + * DAPM Events + */ + +/* + * We keep track of which input filters are enabled. This is used in the logic + * for controlling the mic level detect feature. + */ +static int da7218_in_filter_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + u8 mask; + + switch (w->reg) { + case DA7218_IN_1L_FILTER_CTRL: + mask = (1 << DA7218_LVL_DET_EN_CHAN1L_SHIFT); + break; + case DA7218_IN_1R_FILTER_CTRL: + mask = (1 << DA7218_LVL_DET_EN_CHAN1R_SHIFT); + break; + case DA7218_IN_2L_FILTER_CTRL: + mask = (1 << DA7218_LVL_DET_EN_CHAN2L_SHIFT); + break; + case DA7218_IN_2R_FILTER_CTRL: + mask = (1 << DA7218_LVL_DET_EN_CHAN2R_SHIFT); + break; + default: + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + da7218->in_filt_en |= mask; + /* + * If we're enabling path for mic level detect, wait for path + * to settle before enabling feature to avoid incorrect and + * unwanted detect events. + */ + if (mask & da7218->mic_lvl_det_en) + msleep(DA7218_MIC_LVL_DET_DELAY); + break; + case SND_SOC_DAPM_PRE_PMD: + da7218->in_filt_en &= ~mask; + break; + default: + return -EINVAL; + } + + /* Enable configured level detection paths */ + snd_soc_component_write(component, DA7218_LVL_DET_CTRL, + (da7218->in_filt_en & da7218->mic_lvl_det_en)); + + return 0; +} + +static int da7218_dai_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + u8 pll_ctrl, pll_status, refosc_cal; + int i; + bool success; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (da7218->master) + /* Enable DAI clks for master mode */ + snd_soc_component_update_bits(component, DA7218_DAI_CLK_MODE, + DA7218_DAI_CLK_EN_MASK, + DA7218_DAI_CLK_EN_MASK); + + /* Tune reference oscillator */ + snd_soc_component_write(component, DA7218_PLL_REFOSC_CAL, + DA7218_PLL_REFOSC_CAL_START_MASK); + snd_soc_component_write(component, DA7218_PLL_REFOSC_CAL, + DA7218_PLL_REFOSC_CAL_START_MASK | + DA7218_PLL_REFOSC_CAL_EN_MASK); + + /* Check tuning complete */ + i = 0; + success = false; + do { + refosc_cal = snd_soc_component_read32(component, DA7218_PLL_REFOSC_CAL); + if (!(refosc_cal & DA7218_PLL_REFOSC_CAL_START_MASK)) { + success = true; + } else { + ++i; + usleep_range(DA7218_REF_OSC_CHECK_DELAY_MIN, + DA7218_REF_OSC_CHECK_DELAY_MAX); + } + } while ((i < DA7218_REF_OSC_CHECK_TRIES) && (!success)); + + if (!success) + dev_warn(component->dev, + "Reference oscillator failed calibration\n"); + + /* PC synchronised to DAI */ + snd_soc_component_write(component, DA7218_PC_COUNT, + DA7218_PC_RESYNC_AUTO_MASK); + + /* If SRM not enabled, we don't need to check status */ + pll_ctrl = snd_soc_component_read32(component, DA7218_PLL_CTRL); + if ((pll_ctrl & DA7218_PLL_MODE_MASK) != DA7218_PLL_MODE_SRM) + return 0; + + /* Check SRM has locked */ + i = 0; + success = false; + do { + pll_status = snd_soc_component_read32(component, DA7218_PLL_STATUS); + if (pll_status & DA7218_PLL_SRM_STATUS_SRM_LOCK) { + success = true; + } else { + ++i; + msleep(DA7218_SRM_CHECK_DELAY); + } + } while ((i < DA7218_SRM_CHECK_TRIES) && (!success)); + + if (!success) + dev_warn(component->dev, "SRM failed to lock\n"); + + return 0; + case SND_SOC_DAPM_POST_PMD: + /* PC free-running */ + snd_soc_component_write(component, DA7218_PC_COUNT, DA7218_PC_FREERUN_MASK); + + if (da7218->master) + /* Disable DAI clks for master mode */ + snd_soc_component_update_bits(component, DA7218_DAI_CLK_MODE, + DA7218_DAI_CLK_EN_MASK, 0); + + return 0; + default: + return -EINVAL; + } +} + +static int da7218_cp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + + /* + * If this is DA7217 and we're using single supply for differential + * output, we really don't want to touch the charge pump. + */ + if (da7218->hp_single_supply) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, DA7218_CP_CTRL, DA7218_CP_EN_MASK, + DA7218_CP_EN_MASK); + return 0; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, DA7218_CP_CTRL, DA7218_CP_EN_MASK, + 0); + return 0; + default: + return -EINVAL; + } +} + +static int da7218_hp_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Enable headphone output */ + snd_soc_component_update_bits(component, w->reg, DA7218_HP_AMP_OE_MASK, + DA7218_HP_AMP_OE_MASK); + return 0; + case SND_SOC_DAPM_PRE_PMD: + /* Headphone output high impedance */ + snd_soc_component_update_bits(component, w->reg, DA7218_HP_AMP_OE_MASK, 0); + return 0; + default: + return -EINVAL; + } +} + + +/* + * DAPM Widgets + */ + +static const struct snd_soc_dapm_widget da7218_dapm_widgets[] = { + /* Input Supplies */ + SND_SOC_DAPM_SUPPLY("Mic Bias1", DA7218_MICBIAS_EN, + DA7218_MICBIAS_1_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias2", DA7218_MICBIAS_EN, + DA7218_MICBIAS_2_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMic1 Left", DA7218_DMIC_1_CTRL, + DA7218_DMIC_1L_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMic1 Right", DA7218_DMIC_1_CTRL, + DA7218_DMIC_1R_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMic2 Left", DA7218_DMIC_2_CTRL, + DA7218_DMIC_2L_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMic2 Right", DA7218_DMIC_2_CTRL, + DA7218_DMIC_2R_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + + /* Inputs */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC1L"), + SND_SOC_DAPM_INPUT("DMIC1R"), + SND_SOC_DAPM_INPUT("DMIC2L"), + SND_SOC_DAPM_INPUT("DMIC2R"), + + /* Input Mixer Supplies */ + SND_SOC_DAPM_SUPPLY("Mixin1 Supply", DA7218_MIXIN_1_CTRL, + DA7218_MIXIN_1_MIX_SEL_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("Mixin2 Supply", DA7218_MIXIN_2_CTRL, + DA7218_MIXIN_2_MIX_SEL_SHIFT, DA7218_NO_INVERT, + NULL, 0), + + /* Input PGAs */ + SND_SOC_DAPM_PGA("Mic1 PGA", DA7218_MIC_1_CTRL, + DA7218_MIC_1_AMP_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_PGA("Mic2 PGA", DA7218_MIC_2_CTRL, + DA7218_MIC_2_AMP_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_PGA("Mixin1 PGA", DA7218_MIXIN_1_CTRL, + DA7218_MIXIN_1_AMP_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_PGA("Mixin2 PGA", DA7218_MIXIN_2_CTRL, + DA7218_MIXIN_2_AMP_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + + /* Mic/DMic Muxes */ + SND_SOC_DAPM_MUX("Mic1 Mux", SND_SOC_NOPM, 0, 0, &da7218_mic1_sel_mux), + SND_SOC_DAPM_MUX("Mic2 Mux", SND_SOC_NOPM, 0, 0, &da7218_mic2_sel_mux), + + /* Input Filters */ + SND_SOC_DAPM_ADC_E("In Filter1L", NULL, DA7218_IN_1L_FILTER_CTRL, + DA7218_IN_1L_FILTER_EN_SHIFT, DA7218_NO_INVERT, + da7218_in_filter_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("In Filter1R", NULL, DA7218_IN_1R_FILTER_CTRL, + DA7218_IN_1R_FILTER_EN_SHIFT, DA7218_NO_INVERT, + da7218_in_filter_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("In Filter2L", NULL, DA7218_IN_2L_FILTER_CTRL, + DA7218_IN_2L_FILTER_EN_SHIFT, DA7218_NO_INVERT, + da7218_in_filter_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("In Filter2R", NULL, DA7218_IN_2R_FILTER_CTRL, + DA7218_IN_2R_FILTER_EN_SHIFT, DA7218_NO_INVERT, + da7218_in_filter_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + /* Tone Generator */ + SND_SOC_DAPM_SIGGEN("TONE"), + SND_SOC_DAPM_PGA("Tone Generator", DA7218_TONE_GEN_CFG1, + DA7218_START_STOPN_SHIFT, DA7218_NO_INVERT, NULL, 0), + + /* Sidetone Input */ + SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, + &da7218_sidetone_in_sel_mux), + SND_SOC_DAPM_ADC("Sidetone Filter", NULL, DA7218_SIDETONE_CTRL, + DA7218_SIDETONE_FILTER_EN_SHIFT, DA7218_NO_INVERT), + + /* Input Mixers */ + SND_SOC_DAPM_MIXER("Mixer DAI1L", SND_SOC_NOPM, 0, 0, + da7218_out_dai1l_mix_controls, + ARRAY_SIZE(da7218_out_dai1l_mix_controls)), + SND_SOC_DAPM_MIXER("Mixer DAI1R", SND_SOC_NOPM, 0, 0, + da7218_out_dai1r_mix_controls, + ARRAY_SIZE(da7218_out_dai1r_mix_controls)), + SND_SOC_DAPM_MIXER("Mixer DAI2L", SND_SOC_NOPM, 0, 0, + da7218_out_dai2l_mix_controls, + ARRAY_SIZE(da7218_out_dai2l_mix_controls)), + SND_SOC_DAPM_MIXER("Mixer DAI2R", SND_SOC_NOPM, 0, 0, + da7218_out_dai2r_mix_controls, + ARRAY_SIZE(da7218_out_dai2r_mix_controls)), + + /* DAI Supply */ + SND_SOC_DAPM_SUPPLY("DAI", DA7218_DAI_CTRL, DA7218_DAI_EN_SHIFT, + DA7218_NO_INVERT, da7218_dai_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* DAI */ + SND_SOC_DAPM_AIF_OUT("DAIOUT", "Capture", 0, DA7218_DAI_TDM_CTRL, + DA7218_DAI_OE_SHIFT, DA7218_NO_INVERT), + SND_SOC_DAPM_AIF_IN("DAIIN", "Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Mixer Out FilterL", SND_SOC_NOPM, 0, 0, + da7218_out_filtl_mix_controls, + ARRAY_SIZE(da7218_out_filtl_mix_controls)), + SND_SOC_DAPM_MIXER("Mixer Out FilterR", SND_SOC_NOPM, 0, 0, + da7218_out_filtr_mix_controls, + ARRAY_SIZE(da7218_out_filtr_mix_controls)), + + /* BiQuad Filters */ + SND_SOC_DAPM_MUX("Out FilterL BiQuad Mux", SND_SOC_NOPM, 0, 0, + &da7218_out_filtl_biq_sel_mux), + SND_SOC_DAPM_MUX("Out FilterR BiQuad Mux", SND_SOC_NOPM, 0, 0, + &da7218_out_filtr_biq_sel_mux), + SND_SOC_DAPM_DAC("BiQuad Filter", NULL, DA7218_OUT_1_BIQ_5STAGE_CTRL, + DA7218_OUT_1_BIQ_5STAGE_FILTER_EN_SHIFT, + DA7218_NO_INVERT), + + /* Sidetone Mixers */ + SND_SOC_DAPM_MIXER("ST Mixer Out FilterL", SND_SOC_NOPM, 0, 0, + da7218_st_out_filtl_mix_controls, + ARRAY_SIZE(da7218_st_out_filtl_mix_controls)), + SND_SOC_DAPM_MIXER("ST Mixer Out FilterR", SND_SOC_NOPM, 0, 0, + da7218_st_out_filtr_mix_controls, + ARRAY_SIZE(da7218_st_out_filtr_mix_controls)), + + /* Output Filters */ + SND_SOC_DAPM_DAC("Out FilterL", NULL, DA7218_OUT_1L_FILTER_CTRL, + DA7218_OUT_1L_FILTER_EN_SHIFT, DA7218_NO_INVERT), + SND_SOC_DAPM_DAC("Out FilterR", NULL, DA7218_OUT_1R_FILTER_CTRL, + DA7218_IN_1R_FILTER_EN_SHIFT, DA7218_NO_INVERT), + + /* Output PGAs */ + SND_SOC_DAPM_PGA("Mixout Left PGA", DA7218_MIXOUT_L_CTRL, + DA7218_MIXOUT_L_AMP_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_PGA("Mixout Right PGA", DA7218_MIXOUT_R_CTRL, + DA7218_MIXOUT_R_AMP_EN_SHIFT, DA7218_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_PGA_E("Headphone Left PGA", DA7218_HP_L_CTRL, + DA7218_HP_L_AMP_EN_SHIFT, DA7218_NO_INVERT, NULL, 0, + da7218_hp_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Headphone Right PGA", DA7218_HP_R_CTRL, + DA7218_HP_R_AMP_EN_SHIFT, DA7218_NO_INVERT, NULL, 0, + da7218_hp_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + /* Output Supplies */ + SND_SOC_DAPM_SUPPLY("Charge Pump", SND_SOC_NOPM, 0, 0, da7218_cp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; + + +/* + * DAPM Mixer Routes + */ + +#define DA7218_DMIX_ROUTES(name) \ + {name, "In Filter1L Switch", "In Filter1L"}, \ + {name, "In Filter1R Switch", "In Filter1R"}, \ + {name, "In Filter2L Switch", "In Filter2L"}, \ + {name, "In Filter2R Switch", "In Filter2R"}, \ + {name, "ToneGen Switch", "Tone Generator"}, \ + {name, "DAIL Switch", "DAIIN"}, \ + {name, "DAIR Switch", "DAIIN"} + +#define DA7218_DMIX_ST_ROUTES(name) \ + {name, "Out FilterL Switch", "Out FilterL BiQuad Mux"}, \ + {name, "Out FilterR Switch", "Out FilterR BiQuad Mux"}, \ + {name, "Sidetone Switch", "Sidetone Filter"} + + +/* + * DAPM audio route definition + */ + +static const struct snd_soc_dapm_route da7218_audio_map[] = { + /* Input paths */ + {"MIC1", NULL, "Mic Bias1"}, + {"MIC2", NULL, "Mic Bias2"}, + {"DMIC1L", NULL, "Mic Bias1"}, + {"DMIC1L", NULL, "DMic1 Left"}, + {"DMIC1R", NULL, "Mic Bias1"}, + {"DMIC1R", NULL, "DMic1 Right"}, + {"DMIC2L", NULL, "Mic Bias2"}, + {"DMIC2L", NULL, "DMic2 Left"}, + {"DMIC2R", NULL, "Mic Bias2"}, + {"DMIC2R", NULL, "DMic2 Right"}, + + {"Mic1 PGA", NULL, "MIC1"}, + {"Mic2 PGA", NULL, "MIC2"}, + + {"Mixin1 PGA", NULL, "Mixin1 Supply"}, + {"Mixin2 PGA", NULL, "Mixin2 Supply"}, + + {"Mixin1 PGA", NULL, "Mic1 PGA"}, + {"Mixin2 PGA", NULL, "Mic2 PGA"}, + + {"Mic1 Mux", "Analog", "Mixin1 PGA"}, + {"Mic1 Mux", "Digital", "DMIC1L"}, + {"Mic1 Mux", "Digital", "DMIC1R"}, + {"Mic2 Mux", "Analog", "Mixin2 PGA"}, + {"Mic2 Mux", "Digital", "DMIC2L"}, + {"Mic2 Mux", "Digital", "DMIC2R"}, + + {"In Filter1L", NULL, "Mic1 Mux"}, + {"In Filter1R", NULL, "Mic1 Mux"}, + {"In Filter2L", NULL, "Mic2 Mux"}, + {"In Filter2R", NULL, "Mic2 Mux"}, + + {"Tone Generator", NULL, "TONE"}, + + {"Sidetone Mux", "In Filter1L", "In Filter1L"}, + {"Sidetone Mux", "In Filter1R", "In Filter1R"}, + {"Sidetone Mux", "In Filter2L", "In Filter2L"}, + {"Sidetone Mux", "In Filter2R", "In Filter2R"}, + {"Sidetone Filter", NULL, "Sidetone Mux"}, + + DA7218_DMIX_ROUTES("Mixer DAI1L"), + DA7218_DMIX_ROUTES("Mixer DAI1R"), + DA7218_DMIX_ROUTES("Mixer DAI2L"), + DA7218_DMIX_ROUTES("Mixer DAI2R"), + + {"DAIOUT", NULL, "Mixer DAI1L"}, + {"DAIOUT", NULL, "Mixer DAI1R"}, + {"DAIOUT", NULL, "Mixer DAI2L"}, + {"DAIOUT", NULL, "Mixer DAI2R"}, + + {"DAIOUT", NULL, "DAI"}, + + /* Output paths */ + {"DAIIN", NULL, "DAI"}, + + DA7218_DMIX_ROUTES("Mixer Out FilterL"), + DA7218_DMIX_ROUTES("Mixer Out FilterR"), + + {"BiQuad Filter", NULL, "Mixer Out FilterL"}, + {"BiQuad Filter", NULL, "Mixer Out FilterR"}, + + {"Out FilterL BiQuad Mux", "Bypass", "Mixer Out FilterL"}, + {"Out FilterL BiQuad Mux", "Enabled", "BiQuad Filter"}, + {"Out FilterR BiQuad Mux", "Bypass", "Mixer Out FilterR"}, + {"Out FilterR BiQuad Mux", "Enabled", "BiQuad Filter"}, + + DA7218_DMIX_ST_ROUTES("ST Mixer Out FilterL"), + DA7218_DMIX_ST_ROUTES("ST Mixer Out FilterR"), + + {"Out FilterL", NULL, "ST Mixer Out FilterL"}, + {"Out FilterR", NULL, "ST Mixer Out FilterR"}, + + {"Mixout Left PGA", NULL, "Out FilterL"}, + {"Mixout Right PGA", NULL, "Out FilterR"}, + + {"Headphone Left PGA", NULL, "Mixout Left PGA"}, + {"Headphone Right PGA", NULL, "Mixout Right PGA"}, + + {"HPL", NULL, "Headphone Left PGA"}, + {"HPR", NULL, "Headphone Right PGA"}, + + {"HPL", NULL, "Charge Pump"}, + {"HPR", NULL, "Charge Pump"}, +}; + + +/* + * DAI operations + */ + +static int da7218_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + int ret; + + if (da7218->mclk_rate == freq) + return 0; + + if ((freq < 2000000) || (freq > 54000000)) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; + } + + switch (clk_id) { + case DA7218_CLKSRC_MCLK_SQR: + snd_soc_component_update_bits(component, DA7218_PLL_CTRL, + DA7218_PLL_MCLK_SQR_EN_MASK, + DA7218_PLL_MCLK_SQR_EN_MASK); + break; + case DA7218_CLKSRC_MCLK: + snd_soc_component_update_bits(component, DA7218_PLL_CTRL, + DA7218_PLL_MCLK_SQR_EN_MASK, 0); + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } + + if (da7218->mclk) { + freq = clk_round_rate(da7218->mclk, freq); + ret = clk_set_rate(da7218->mclk, freq); + if (ret) { + dev_err(codec_dai->dev, "Failed to set clock rate %d\n", + freq); + return ret; + } + } + + da7218->mclk_rate = freq; + + return 0; +} + +static int da7218_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + + u8 pll_ctrl, indiv_bits, indiv; + u8 pll_frac_top, pll_frac_bot, pll_integer; + u32 freq_ref; + u64 frac_div; + + /* Verify 2MHz - 54MHz MCLK provided, and set input divider */ + if (da7218->mclk_rate < 2000000) { + dev_err(component->dev, "PLL input clock %d below valid range\n", + da7218->mclk_rate); + return -EINVAL; + } else if (da7218->mclk_rate <= 4500000) { + indiv_bits = DA7218_PLL_INDIV_2_TO_4_5_MHZ; + indiv = DA7218_PLL_INDIV_2_TO_4_5_MHZ_VAL; + } else if (da7218->mclk_rate <= 9000000) { + indiv_bits = DA7218_PLL_INDIV_4_5_TO_9_MHZ; + indiv = DA7218_PLL_INDIV_4_5_TO_9_MHZ_VAL; + } else if (da7218->mclk_rate <= 18000000) { + indiv_bits = DA7218_PLL_INDIV_9_TO_18_MHZ; + indiv = DA7218_PLL_INDIV_9_TO_18_MHZ_VAL; + } else if (da7218->mclk_rate <= 36000000) { + indiv_bits = DA7218_PLL_INDIV_18_TO_36_MHZ; + indiv = DA7218_PLL_INDIV_18_TO_36_MHZ_VAL; + } else if (da7218->mclk_rate <= 54000000) { + indiv_bits = DA7218_PLL_INDIV_36_TO_54_MHZ; + indiv = DA7218_PLL_INDIV_36_TO_54_MHZ_VAL; + } else { + dev_err(component->dev, "PLL input clock %d above valid range\n", + da7218->mclk_rate); + return -EINVAL; + } + freq_ref = (da7218->mclk_rate / indiv); + pll_ctrl = indiv_bits; + + /* Configure PLL */ + switch (source) { + case DA7218_SYSCLK_MCLK: + pll_ctrl |= DA7218_PLL_MODE_BYPASS; + snd_soc_component_update_bits(component, DA7218_PLL_CTRL, + DA7218_PLL_INDIV_MASK | + DA7218_PLL_MODE_MASK, pll_ctrl); + return 0; + case DA7218_SYSCLK_PLL: + pll_ctrl |= DA7218_PLL_MODE_NORMAL; + break; + case DA7218_SYSCLK_PLL_SRM: + pll_ctrl |= DA7218_PLL_MODE_SRM; + break; + default: + dev_err(component->dev, "Invalid PLL config\n"); + return -EINVAL; + } + + /* Calculate dividers for PLL */ + pll_integer = fout / freq_ref; + frac_div = (u64)(fout % freq_ref) * 8192ULL; + do_div(frac_div, freq_ref); + pll_frac_top = (frac_div >> DA7218_BYTE_SHIFT) & DA7218_BYTE_MASK; + pll_frac_bot = (frac_div) & DA7218_BYTE_MASK; + + /* Write PLL config & dividers */ + snd_soc_component_write(component, DA7218_PLL_FRAC_TOP, pll_frac_top); + snd_soc_component_write(component, DA7218_PLL_FRAC_BOT, pll_frac_bot); + snd_soc_component_write(component, DA7218_PLL_INTEGER, pll_integer); + snd_soc_component_update_bits(component, DA7218_PLL_CTRL, + DA7218_PLL_MODE_MASK | DA7218_PLL_INDIV_MASK, + pll_ctrl); + + return 0; +} + +static int da7218_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + u8 dai_clk_mode = 0, dai_ctrl = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + da7218->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + da7218->master = false; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + dai_clk_mode |= DA7218_DAI_WCLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + dai_clk_mode |= DA7218_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + dai_clk_mode |= DA7218_DAI_WCLK_POL_INV | + DA7218_DAI_CLK_POL_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_DSP_B: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + dai_clk_mode |= DA7218_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + dai_clk_mode |= DA7218_DAI_WCLK_POL_INV | + DA7218_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + dai_clk_mode |= DA7218_DAI_WCLK_POL_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dai_ctrl |= DA7218_DAI_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + dai_ctrl |= DA7218_DAI_FORMAT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dai_ctrl |= DA7218_DAI_FORMAT_RIGHT_J; + break; + case SND_SOC_DAIFMT_DSP_B: + dai_ctrl |= DA7218_DAI_FORMAT_DSP; + break; + default: + return -EINVAL; + } + + /* By default 64 BCLKs per WCLK is supported */ + dai_clk_mode |= DA7218_DAI_BCLKS_PER_WCLK_64; + + snd_soc_component_write(component, DA7218_DAI_CLK_MODE, dai_clk_mode); + snd_soc_component_update_bits(component, DA7218_DAI_CTRL, DA7218_DAI_FORMAT_MASK, + dai_ctrl); + + return 0; +} + +static int da7218_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + u8 dai_bclks_per_wclk; + u32 frame_size; + + /* No channels enabled so disable TDM, revert to 64-bit frames */ + if (!tx_mask) { + snd_soc_component_update_bits(component, DA7218_DAI_TDM_CTRL, + DA7218_DAI_TDM_CH_EN_MASK | + DA7218_DAI_TDM_MODE_EN_MASK, 0); + snd_soc_component_update_bits(component, DA7218_DAI_CLK_MODE, + DA7218_DAI_BCLKS_PER_WCLK_MASK, + DA7218_DAI_BCLKS_PER_WCLK_64); + return 0; + } + + /* Check we have valid slots */ + if (fls(tx_mask) > DA7218_DAI_TDM_MAX_SLOTS) { + dev_err(component->dev, "Invalid number of slots, max = %d\n", + DA7218_DAI_TDM_MAX_SLOTS); + return -EINVAL; + } + + /* Check we have a valid offset given (first 2 bytes of rx_mask) */ + if (rx_mask >> DA7218_2BYTE_SHIFT) { + dev_err(component->dev, "Invalid slot offset, max = %d\n", + DA7218_2BYTE_MASK); + return -EINVAL; + } + + /* Calculate & validate frame size based on slot info provided. */ + frame_size = slots * slot_width; + switch (frame_size) { + case 32: + dai_bclks_per_wclk = DA7218_DAI_BCLKS_PER_WCLK_32; + break; + case 64: + dai_bclks_per_wclk = DA7218_DAI_BCLKS_PER_WCLK_64; + break; + case 128: + dai_bclks_per_wclk = DA7218_DAI_BCLKS_PER_WCLK_128; + break; + case 256: + dai_bclks_per_wclk = DA7218_DAI_BCLKS_PER_WCLK_256; + break; + default: + dev_err(component->dev, "Invalid frame size\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, DA7218_DAI_CLK_MODE, + DA7218_DAI_BCLKS_PER_WCLK_MASK, + dai_bclks_per_wclk); + snd_soc_component_write(component, DA7218_DAI_OFFSET_LOWER, + (rx_mask & DA7218_BYTE_MASK)); + snd_soc_component_write(component, DA7218_DAI_OFFSET_UPPER, + ((rx_mask >> DA7218_BYTE_SHIFT) & DA7218_BYTE_MASK)); + snd_soc_component_update_bits(component, DA7218_DAI_TDM_CTRL, + DA7218_DAI_TDM_CH_EN_MASK | + DA7218_DAI_TDM_MODE_EN_MASK, + (tx_mask << DA7218_DAI_TDM_CH_EN_SHIFT) | + DA7218_DAI_TDM_MODE_EN_MASK); + + return 0; +} + +static int da7218_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u8 dai_ctrl = 0, fs; + unsigned int channels; + + switch (params_width(params)) { + case 16: + dai_ctrl |= DA7218_DAI_WORD_LENGTH_S16_LE; + break; + case 20: + dai_ctrl |= DA7218_DAI_WORD_LENGTH_S20_LE; + break; + case 24: + dai_ctrl |= DA7218_DAI_WORD_LENGTH_S24_LE; + break; + case 32: + dai_ctrl |= DA7218_DAI_WORD_LENGTH_S32_LE; + break; + default: + return -EINVAL; + } + + channels = params_channels(params); + if ((channels < 1) || (channels > DA7218_DAI_CH_NUM_MAX)) { + dev_err(component->dev, + "Invalid number of channels, only 1 to %d supported\n", + DA7218_DAI_CH_NUM_MAX); + return -EINVAL; + } + dai_ctrl |= channels << DA7218_DAI_CH_NUM_SHIFT; + + switch (params_rate(params)) { + case 8000: + fs = DA7218_SR_8000; + break; + case 11025: + fs = DA7218_SR_11025; + break; + case 12000: + fs = DA7218_SR_12000; + break; + case 16000: + fs = DA7218_SR_16000; + break; + case 22050: + fs = DA7218_SR_22050; + break; + case 24000: + fs = DA7218_SR_24000; + break; + case 32000: + fs = DA7218_SR_32000; + break; + case 44100: + fs = DA7218_SR_44100; + break; + case 48000: + fs = DA7218_SR_48000; + break; + case 88200: + fs = DA7218_SR_88200; + break; + case 96000: + fs = DA7218_SR_96000; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, DA7218_DAI_CTRL, + DA7218_DAI_WORD_LENGTH_MASK | DA7218_DAI_CH_NUM_MASK, + dai_ctrl); + /* SRs tied for ADCs and DACs. */ + snd_soc_component_write(component, DA7218_SR, + (fs << DA7218_SR_DAC_SHIFT) | (fs << DA7218_SR_ADC_SHIFT)); + + return 0; +} + +static const struct snd_soc_dai_ops da7218_dai_ops = { + .hw_params = da7218_hw_params, + .set_sysclk = da7218_set_dai_sysclk, + .set_pll = da7218_set_dai_pll, + .set_fmt = da7218_set_dai_fmt, + .set_tdm_slot = da7218_set_dai_tdm_slot, +}; + +#define DA7218_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver da7218_dai = { + .name = "da7218-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 4, /* Only 2 channels of data */ + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7218_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7218_FORMATS, + }, + .ops = &da7218_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, +}; + + +/* + * HP Detect + */ + +int da7218_hpldet(struct snd_soc_component *component, struct snd_soc_jack *jack) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + + if (da7218->dev_id == DA7217_DEV_ID) + return -EINVAL; + + da7218->jack = jack; + snd_soc_component_update_bits(component, DA7218_HPLDET_JACK, + DA7218_HPLDET_JACK_EN_MASK, + jack ? DA7218_HPLDET_JACK_EN_MASK : 0); + + return 0; +} +EXPORT_SYMBOL_GPL(da7218_hpldet); + +static void da7218_micldet_irq(struct snd_soc_component *component) +{ + char *envp[] = { + "EVENT=MIC_LEVEL_DETECT", + NULL, + }; + + kobject_uevent_env(&component->dev->kobj, KOBJ_CHANGE, envp); +} + +static void da7218_hpldet_irq(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + u8 jack_status; + int report; + + jack_status = snd_soc_component_read32(component, DA7218_EVENT_STATUS); + + if (jack_status & DA7218_HPLDET_JACK_STS_MASK) + report = SND_JACK_HEADPHONE; + else + report = 0; + + snd_soc_jack_report(da7218->jack, report, SND_JACK_HEADPHONE); +} + +/* + * IRQ + */ + +static irqreturn_t da7218_irq_thread(int irq, void *data) +{ + struct snd_soc_component *component = data; + u8 status; + + /* Read IRQ status reg */ + status = snd_soc_component_read32(component, DA7218_EVENT); + if (!status) + return IRQ_NONE; + + /* Mic level detect */ + if (status & DA7218_LVL_DET_EVENT_MASK) + da7218_micldet_irq(component); + + /* HP detect */ + if (status & DA7218_HPLDET_JACK_EVENT_MASK) + da7218_hpldet_irq(component); + + /* Clear interrupts */ + snd_soc_component_write(component, DA7218_EVENT, status); + + return IRQ_HANDLED; +} + +/* + * DT + */ + +static const struct of_device_id da7218_of_match[] = { + { .compatible = "dlg,da7217", .data = (void *) DA7217_DEV_ID }, + { .compatible = "dlg,da7218", .data = (void *) DA7218_DEV_ID }, + { } +}; +MODULE_DEVICE_TABLE(of, da7218_of_match); + +static inline int da7218_of_get_id(struct device *dev) +{ + const struct of_device_id *id = of_match_device(da7218_of_match, dev); + + if (id) + return (uintptr_t)id->data; + else + return -EINVAL; +} + +static enum da7218_micbias_voltage + da7218_of_micbias_lvl(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 1200: + return DA7218_MICBIAS_1_2V; + case 1600: + return DA7218_MICBIAS_1_6V; + case 1800: + return DA7218_MICBIAS_1_8V; + case 2000: + return DA7218_MICBIAS_2_0V; + case 2200: + return DA7218_MICBIAS_2_2V; + case 2400: + return DA7218_MICBIAS_2_4V; + case 2600: + return DA7218_MICBIAS_2_6V; + case 2800: + return DA7218_MICBIAS_2_8V; + case 3000: + return DA7218_MICBIAS_3_0V; + default: + dev_warn(component->dev, "Invalid micbias level"); + return DA7218_MICBIAS_1_6V; + } +} + +static enum da7218_mic_amp_in_sel + da7218_of_mic_amp_in_sel(struct snd_soc_component *component, const char *str) +{ + if (!strcmp(str, "diff")) { + return DA7218_MIC_AMP_IN_SEL_DIFF; + } else if (!strcmp(str, "se_p")) { + return DA7218_MIC_AMP_IN_SEL_SE_P; + } else if (!strcmp(str, "se_n")) { + return DA7218_MIC_AMP_IN_SEL_SE_N; + } else { + dev_warn(component->dev, "Invalid mic input type selection"); + return DA7218_MIC_AMP_IN_SEL_DIFF; + } +} + +static enum da7218_dmic_data_sel + da7218_of_dmic_data_sel(struct snd_soc_component *component, const char *str) +{ + if (!strcmp(str, "lrise_rfall")) { + return DA7218_DMIC_DATA_LRISE_RFALL; + } else if (!strcmp(str, "lfall_rrise")) { + return DA7218_DMIC_DATA_LFALL_RRISE; + } else { + dev_warn(component->dev, "Invalid DMIC data type selection"); + return DA7218_DMIC_DATA_LRISE_RFALL; + } +} + +static enum da7218_dmic_samplephase + da7218_of_dmic_samplephase(struct snd_soc_component *component, const char *str) +{ + if (!strcmp(str, "on_clkedge")) { + return DA7218_DMIC_SAMPLE_ON_CLKEDGE; + } else if (!strcmp(str, "between_clkedge")) { + return DA7218_DMIC_SAMPLE_BETWEEN_CLKEDGE; + } else { + dev_warn(component->dev, "Invalid DMIC sample phase"); + return DA7218_DMIC_SAMPLE_ON_CLKEDGE; + } +} + +static enum da7218_dmic_clk_rate + da7218_of_dmic_clkrate(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 1500000: + return DA7218_DMIC_CLK_1_5MHZ; + case 3000000: + return DA7218_DMIC_CLK_3_0MHZ; + default: + dev_warn(component->dev, "Invalid DMIC clock rate"); + return DA7218_DMIC_CLK_3_0MHZ; + } +} + +static enum da7218_hpldet_jack_rate + da7218_of_jack_rate(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 5: + return DA7218_HPLDET_JACK_RATE_5US; + case 10: + return DA7218_HPLDET_JACK_RATE_10US; + case 20: + return DA7218_HPLDET_JACK_RATE_20US; + case 40: + return DA7218_HPLDET_JACK_RATE_40US; + case 80: + return DA7218_HPLDET_JACK_RATE_80US; + case 160: + return DA7218_HPLDET_JACK_RATE_160US; + case 320: + return DA7218_HPLDET_JACK_RATE_320US; + case 640: + return DA7218_HPLDET_JACK_RATE_640US; + default: + dev_warn(component->dev, "Invalid jack detect rate"); + return DA7218_HPLDET_JACK_RATE_40US; + } +} + +static enum da7218_hpldet_jack_debounce + da7218_of_jack_debounce(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 0: + return DA7218_HPLDET_JACK_DEBOUNCE_OFF; + case 2: + return DA7218_HPLDET_JACK_DEBOUNCE_2; + case 3: + return DA7218_HPLDET_JACK_DEBOUNCE_3; + case 4: + return DA7218_HPLDET_JACK_DEBOUNCE_4; + default: + dev_warn(component->dev, "Invalid jack debounce"); + return DA7218_HPLDET_JACK_DEBOUNCE_2; + } +} + +static enum da7218_hpldet_jack_thr + da7218_of_jack_thr(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 84: + return DA7218_HPLDET_JACK_THR_84PCT; + case 88: + return DA7218_HPLDET_JACK_THR_88PCT; + case 92: + return DA7218_HPLDET_JACK_THR_92PCT; + case 96: + return DA7218_HPLDET_JACK_THR_96PCT; + default: + dev_warn(component->dev, "Invalid jack threshold level"); + return DA7218_HPLDET_JACK_THR_84PCT; + } +} + +static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct device_node *np = component->dev->of_node; + struct device_node *hpldet_np; + struct da7218_pdata *pdata; + struct da7218_hpldet_pdata *hpldet_pdata; + const char *of_str; + u32 of_val32; + + pdata = devm_kzalloc(component->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + if (of_property_read_u32(np, "dlg,micbias1-lvl-millivolt", &of_val32) >= 0) + pdata->micbias1_lvl = da7218_of_micbias_lvl(component, of_val32); + else + pdata->micbias1_lvl = DA7218_MICBIAS_1_6V; + + if (of_property_read_u32(np, "dlg,micbias2-lvl-millivolt", &of_val32) >= 0) + pdata->micbias2_lvl = da7218_of_micbias_lvl(component, of_val32); + else + pdata->micbias2_lvl = DA7218_MICBIAS_1_6V; + + if (!of_property_read_string(np, "dlg,mic1-amp-in-sel", &of_str)) + pdata->mic1_amp_in_sel = + da7218_of_mic_amp_in_sel(component, of_str); + else + pdata->mic1_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; + + if (!of_property_read_string(np, "dlg,mic2-amp-in-sel", &of_str)) + pdata->mic2_amp_in_sel = + da7218_of_mic_amp_in_sel(component, of_str); + else + pdata->mic2_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; + + if (!of_property_read_string(np, "dlg,dmic1-data-sel", &of_str)) + pdata->dmic1_data_sel = da7218_of_dmic_data_sel(component, of_str); + else + pdata->dmic1_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; + + if (!of_property_read_string(np, "dlg,dmic1-samplephase", &of_str)) + pdata->dmic1_samplephase = + da7218_of_dmic_samplephase(component, of_str); + else + pdata->dmic1_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; + + if (of_property_read_u32(np, "dlg,dmic1-clkrate-hz", &of_val32) >= 0) + pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + else + pdata->dmic1_clk_rate = DA7218_DMIC_CLK_3_0MHZ; + + if (!of_property_read_string(np, "dlg,dmic2-data-sel", &of_str)) + pdata->dmic2_data_sel = da7218_of_dmic_data_sel(component, of_str); + else + pdata->dmic2_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; + + if (!of_property_read_string(np, "dlg,dmic2-samplephase", &of_str)) + pdata->dmic2_samplephase = + da7218_of_dmic_samplephase(component, of_str); + else + pdata->dmic2_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; + + if (of_property_read_u32(np, "dlg,dmic2-clkrate-hz", &of_val32) >= 0) + pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + else + pdata->dmic2_clk_rate = DA7218_DMIC_CLK_3_0MHZ; + + if (da7218->dev_id == DA7217_DEV_ID) { + if (of_property_read_bool(np, "dlg,hp-diff-single-supply")) + pdata->hp_diff_single_supply = true; + } + + if (da7218->dev_id == DA7218_DEV_ID) { + hpldet_np = of_get_child_by_name(np, "da7218_hpldet"); + if (!hpldet_np) + return pdata; + + hpldet_pdata = devm_kzalloc(component->dev, sizeof(*hpldet_pdata), + GFP_KERNEL); + if (!hpldet_pdata) { + of_node_put(hpldet_np); + return pdata; + } + pdata->hpldet_pdata = hpldet_pdata; + + if (of_property_read_u32(hpldet_np, "dlg,jack-rate-us", + &of_val32) >= 0) + hpldet_pdata->jack_rate = + da7218_of_jack_rate(component, of_val32); + else + hpldet_pdata->jack_rate = DA7218_HPLDET_JACK_RATE_40US; + + if (of_property_read_u32(hpldet_np, "dlg,jack-debounce", + &of_val32) >= 0) + hpldet_pdata->jack_debounce = + da7218_of_jack_debounce(component, of_val32); + else + hpldet_pdata->jack_debounce = + DA7218_HPLDET_JACK_DEBOUNCE_2; + + if (of_property_read_u32(hpldet_np, "dlg,jack-threshold-pct", + &of_val32) >= 0) + hpldet_pdata->jack_thr = + da7218_of_jack_thr(component, of_val32); + else + hpldet_pdata->jack_thr = DA7218_HPLDET_JACK_THR_84PCT; + + if (of_property_read_bool(hpldet_np, "dlg,comp-inv")) + hpldet_pdata->comp_inv = true; + + if (of_property_read_bool(hpldet_np, "dlg,hyst")) + hpldet_pdata->hyst = true; + + if (of_property_read_bool(hpldet_np, "dlg,discharge")) + hpldet_pdata->discharge = true; + + of_node_put(hpldet_np); + } + + return pdata; +} + + +/* + * Codec driver functions + */ + +static int da7218_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + /* Enable MCLK for transition to ON state */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) { + if (da7218->mclk) { + ret = clk_prepare_enable(da7218->mclk); + if (ret) { + dev_err(component->dev, "Failed to enable mclk\n"); + return ret; + } + } + } + + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Master bias */ + snd_soc_component_update_bits(component, DA7218_REFERENCES, + DA7218_BIAS_EN_MASK, + DA7218_BIAS_EN_MASK); + + /* Internal LDO */ + snd_soc_component_update_bits(component, DA7218_LDO_CTRL, + DA7218_LDO_EN_MASK, + DA7218_LDO_EN_MASK); + } else { + /* Remove MCLK */ + if (da7218->mclk) + clk_disable_unprepare(da7218->mclk); + } + break; + case SND_SOC_BIAS_OFF: + /* Only disable if jack detection disabled */ + if (!da7218->jack) { + /* Internal LDO */ + snd_soc_component_update_bits(component, DA7218_LDO_CTRL, + DA7218_LDO_EN_MASK, 0); + + /* Master bias */ + snd_soc_component_update_bits(component, DA7218_REFERENCES, + DA7218_BIAS_EN_MASK, 0); + } + break; + } + + return 0; +} + +static const char *da7218_supply_names[DA7218_NUM_SUPPLIES] = { + [DA7218_SUPPLY_VDD] = "VDD", + [DA7218_SUPPLY_VDDMIC] = "VDDMIC", + [DA7218_SUPPLY_VDDIO] = "VDDIO", +}; + +static int da7218_handle_supplies(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct regulator *vddio; + u8 io_voltage_lvl = DA7218_IO_VOLTAGE_LEVEL_2_5V_3_6V; + int i, ret; + + /* Get required supplies */ + for (i = 0; i < DA7218_NUM_SUPPLIES; ++i) + da7218->supplies[i].supply = da7218_supply_names[i]; + + ret = devm_regulator_bulk_get(component->dev, DA7218_NUM_SUPPLIES, + da7218->supplies); + if (ret) { + dev_err(component->dev, "Failed to get supplies\n"); + return ret; + } + + /* Determine VDDIO voltage provided */ + vddio = da7218->supplies[DA7218_SUPPLY_VDDIO].consumer; + ret = regulator_get_voltage(vddio); + if (ret < 1500000) + dev_warn(component->dev, "Invalid VDDIO voltage\n"); + else if (ret < 2500000) + io_voltage_lvl = DA7218_IO_VOLTAGE_LEVEL_1_5V_2_5V; + + /* Enable main supplies */ + ret = regulator_bulk_enable(DA7218_NUM_SUPPLIES, da7218->supplies); + if (ret) { + dev_err(component->dev, "Failed to enable supplies\n"); + return ret; + } + + /* Ensure device in active mode */ + snd_soc_component_write(component, DA7218_SYSTEM_ACTIVE, DA7218_SYSTEM_ACTIVE_MASK); + + /* Update IO voltage level range */ + snd_soc_component_write(component, DA7218_IO_CTRL, io_voltage_lvl); + + return 0; +} + +static void da7218_handle_pdata(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + struct da7218_pdata *pdata = da7218->pdata; + + if (pdata) { + u8 micbias_lvl = 0, dmic_cfg = 0; + + /* Mic Bias voltages */ + switch (pdata->micbias1_lvl) { + case DA7218_MICBIAS_1_2V: + micbias_lvl |= DA7218_MICBIAS_1_LP_MODE_MASK; + break; + case DA7218_MICBIAS_1_6V: + case DA7218_MICBIAS_1_8V: + case DA7218_MICBIAS_2_0V: + case DA7218_MICBIAS_2_2V: + case DA7218_MICBIAS_2_4V: + case DA7218_MICBIAS_2_6V: + case DA7218_MICBIAS_2_8V: + case DA7218_MICBIAS_3_0V: + micbias_lvl |= (pdata->micbias1_lvl << + DA7218_MICBIAS_1_LEVEL_SHIFT); + break; + } + + switch (pdata->micbias2_lvl) { + case DA7218_MICBIAS_1_2V: + micbias_lvl |= DA7218_MICBIAS_2_LP_MODE_MASK; + break; + case DA7218_MICBIAS_1_6V: + case DA7218_MICBIAS_1_8V: + case DA7218_MICBIAS_2_0V: + case DA7218_MICBIAS_2_2V: + case DA7218_MICBIAS_2_4V: + case DA7218_MICBIAS_2_6V: + case DA7218_MICBIAS_2_8V: + case DA7218_MICBIAS_3_0V: + micbias_lvl |= (pdata->micbias2_lvl << + DA7218_MICBIAS_2_LEVEL_SHIFT); + break; + } + + snd_soc_component_write(component, DA7218_MICBIAS_CTRL, micbias_lvl); + + /* Mic */ + switch (pdata->mic1_amp_in_sel) { + case DA7218_MIC_AMP_IN_SEL_DIFF: + case DA7218_MIC_AMP_IN_SEL_SE_P: + case DA7218_MIC_AMP_IN_SEL_SE_N: + snd_soc_component_write(component, DA7218_MIC_1_SELECT, + pdata->mic1_amp_in_sel); + break; + } + + switch (pdata->mic2_amp_in_sel) { + case DA7218_MIC_AMP_IN_SEL_DIFF: + case DA7218_MIC_AMP_IN_SEL_SE_P: + case DA7218_MIC_AMP_IN_SEL_SE_N: + snd_soc_component_write(component, DA7218_MIC_2_SELECT, + pdata->mic2_amp_in_sel); + break; + } + + /* DMic */ + switch (pdata->dmic1_data_sel) { + case DA7218_DMIC_DATA_LFALL_RRISE: + case DA7218_DMIC_DATA_LRISE_RFALL: + dmic_cfg |= (pdata->dmic1_data_sel << + DA7218_DMIC_1_DATA_SEL_SHIFT); + break; + } + + switch (pdata->dmic1_samplephase) { + case DA7218_DMIC_SAMPLE_ON_CLKEDGE: + case DA7218_DMIC_SAMPLE_BETWEEN_CLKEDGE: + dmic_cfg |= (pdata->dmic1_samplephase << + DA7218_DMIC_1_SAMPLEPHASE_SHIFT); + break; + } + + switch (pdata->dmic1_clk_rate) { + case DA7218_DMIC_CLK_3_0MHZ: + case DA7218_DMIC_CLK_1_5MHZ: + dmic_cfg |= (pdata->dmic1_clk_rate << + DA7218_DMIC_1_CLK_RATE_SHIFT); + break; + } + + snd_soc_component_update_bits(component, DA7218_DMIC_1_CTRL, + DA7218_DMIC_1_DATA_SEL_MASK | + DA7218_DMIC_1_SAMPLEPHASE_MASK | + DA7218_DMIC_1_CLK_RATE_MASK, dmic_cfg); + + dmic_cfg = 0; + switch (pdata->dmic2_data_sel) { + case DA7218_DMIC_DATA_LFALL_RRISE: + case DA7218_DMIC_DATA_LRISE_RFALL: + dmic_cfg |= (pdata->dmic2_data_sel << + DA7218_DMIC_2_DATA_SEL_SHIFT); + break; + } + + switch (pdata->dmic2_samplephase) { + case DA7218_DMIC_SAMPLE_ON_CLKEDGE: + case DA7218_DMIC_SAMPLE_BETWEEN_CLKEDGE: + dmic_cfg |= (pdata->dmic2_samplephase << + DA7218_DMIC_2_SAMPLEPHASE_SHIFT); + break; + } + + switch (pdata->dmic2_clk_rate) { + case DA7218_DMIC_CLK_3_0MHZ: + case DA7218_DMIC_CLK_1_5MHZ: + dmic_cfg |= (pdata->dmic2_clk_rate << + DA7218_DMIC_2_CLK_RATE_SHIFT); + break; + } + + snd_soc_component_update_bits(component, DA7218_DMIC_2_CTRL, + DA7218_DMIC_2_DATA_SEL_MASK | + DA7218_DMIC_2_SAMPLEPHASE_MASK | + DA7218_DMIC_2_CLK_RATE_MASK, dmic_cfg); + + /* DA7217 Specific */ + if (da7218->dev_id == DA7217_DEV_ID) { + da7218->hp_single_supply = + pdata->hp_diff_single_supply; + + if (da7218->hp_single_supply) { + snd_soc_component_write(component, DA7218_HP_DIFF_UNLOCK, + DA7218_HP_DIFF_UNLOCK_VAL); + snd_soc_component_update_bits(component, DA7218_HP_DIFF_CTRL, + DA7218_HP_AMP_SINGLE_SUPPLY_EN_MASK, + DA7218_HP_AMP_SINGLE_SUPPLY_EN_MASK); + } + } + + /* DA7218 Specific */ + if ((da7218->dev_id == DA7218_DEV_ID) && + (pdata->hpldet_pdata)) { + struct da7218_hpldet_pdata *hpldet_pdata = + pdata->hpldet_pdata; + u8 hpldet_cfg = 0; + + switch (hpldet_pdata->jack_rate) { + case DA7218_HPLDET_JACK_RATE_5US: + case DA7218_HPLDET_JACK_RATE_10US: + case DA7218_HPLDET_JACK_RATE_20US: + case DA7218_HPLDET_JACK_RATE_40US: + case DA7218_HPLDET_JACK_RATE_80US: + case DA7218_HPLDET_JACK_RATE_160US: + case DA7218_HPLDET_JACK_RATE_320US: + case DA7218_HPLDET_JACK_RATE_640US: + hpldet_cfg |= + (hpldet_pdata->jack_rate << + DA7218_HPLDET_JACK_RATE_SHIFT); + break; + } + + switch (hpldet_pdata->jack_debounce) { + case DA7218_HPLDET_JACK_DEBOUNCE_OFF: + case DA7218_HPLDET_JACK_DEBOUNCE_2: + case DA7218_HPLDET_JACK_DEBOUNCE_3: + case DA7218_HPLDET_JACK_DEBOUNCE_4: + hpldet_cfg |= + (hpldet_pdata->jack_debounce << + DA7218_HPLDET_JACK_DEBOUNCE_SHIFT); + break; + } + + switch (hpldet_pdata->jack_thr) { + case DA7218_HPLDET_JACK_THR_84PCT: + case DA7218_HPLDET_JACK_THR_88PCT: + case DA7218_HPLDET_JACK_THR_92PCT: + case DA7218_HPLDET_JACK_THR_96PCT: + hpldet_cfg |= + (hpldet_pdata->jack_thr << + DA7218_HPLDET_JACK_THR_SHIFT); + break; + } + snd_soc_component_update_bits(component, DA7218_HPLDET_JACK, + DA7218_HPLDET_JACK_RATE_MASK | + DA7218_HPLDET_JACK_DEBOUNCE_MASK | + DA7218_HPLDET_JACK_THR_MASK, + hpldet_cfg); + + hpldet_cfg = 0; + if (hpldet_pdata->comp_inv) + hpldet_cfg |= DA7218_HPLDET_COMP_INV_MASK; + + if (hpldet_pdata->hyst) + hpldet_cfg |= DA7218_HPLDET_HYST_EN_MASK; + + if (hpldet_pdata->discharge) + hpldet_cfg |= DA7218_HPLDET_DISCHARGE_EN_MASK; + + snd_soc_component_write(component, DA7218_HPLDET_CTRL, hpldet_cfg); + } + } +} + +static int da7218_probe(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + int ret; + + /* Regulator configuration */ + ret = da7218_handle_supplies(component); + if (ret) + return ret; + + /* Handle DT/Platform data */ + if (component->dev->of_node) + da7218->pdata = da7218_of_to_pdata(component); + else + da7218->pdata = dev_get_platdata(component->dev); + + da7218_handle_pdata(component); + + /* Check if MCLK provided, if not the clock is NULL */ + da7218->mclk = devm_clk_get(component->dev, "mclk"); + if (IS_ERR(da7218->mclk)) { + if (PTR_ERR(da7218->mclk) != -ENOENT) { + ret = PTR_ERR(da7218->mclk); + goto err_disable_reg; + } else { + da7218->mclk = NULL; + } + } + + /* Default PC to free-running */ + snd_soc_component_write(component, DA7218_PC_COUNT, DA7218_PC_FREERUN_MASK); + + /* + * Default Output Filter mixers to off otherwise DAPM will power + * Mic to HP passthrough paths by default at startup. + */ + snd_soc_component_write(component, DA7218_DROUTING_OUTFILT_1L, 0); + snd_soc_component_write(component, DA7218_DROUTING_OUTFILT_1R, 0); + + /* Default CP to normal load, power mode */ + snd_soc_component_update_bits(component, DA7218_CP_CTRL, + DA7218_CP_SMALL_SWITCH_FREQ_EN_MASK, 0); + + /* Default gain ramping */ + snd_soc_component_update_bits(component, DA7218_MIXIN_1_CTRL, + DA7218_MIXIN_1_AMP_RAMP_EN_MASK, + DA7218_MIXIN_1_AMP_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_MIXIN_2_CTRL, + DA7218_MIXIN_2_AMP_RAMP_EN_MASK, + DA7218_MIXIN_2_AMP_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_IN_1L_FILTER_CTRL, + DA7218_IN_1L_RAMP_EN_MASK, + DA7218_IN_1L_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_IN_1R_FILTER_CTRL, + DA7218_IN_1R_RAMP_EN_MASK, + DA7218_IN_1R_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_IN_2L_FILTER_CTRL, + DA7218_IN_2L_RAMP_EN_MASK, + DA7218_IN_2L_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_IN_2R_FILTER_CTRL, + DA7218_IN_2R_RAMP_EN_MASK, + DA7218_IN_2R_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_DGS_GAIN_CTRL, + DA7218_DGS_RAMP_EN_MASK, DA7218_DGS_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_OUT_1L_FILTER_CTRL, + DA7218_OUT_1L_RAMP_EN_MASK, + DA7218_OUT_1L_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_OUT_1R_FILTER_CTRL, + DA7218_OUT_1R_RAMP_EN_MASK, + DA7218_OUT_1R_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_HP_L_CTRL, + DA7218_HP_L_AMP_RAMP_EN_MASK, + DA7218_HP_L_AMP_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7218_HP_R_CTRL, + DA7218_HP_R_AMP_RAMP_EN_MASK, + DA7218_HP_R_AMP_RAMP_EN_MASK); + + /* Default infinite tone gen, start/stop by Kcontrol */ + snd_soc_component_write(component, DA7218_TONE_GEN_CYCLES, DA7218_BEEP_CYCLES_MASK); + + /* DA7217 specific config */ + if (da7218->dev_id == DA7217_DEV_ID) { + snd_soc_component_update_bits(component, DA7218_HP_DIFF_CTRL, + DA7218_HP_AMP_DIFF_MODE_EN_MASK, + DA7218_HP_AMP_DIFF_MODE_EN_MASK); + + /* Only DA7218 supports HP detect, mask off for DA7217 */ + snd_soc_component_write(component, DA7218_EVENT_MASK, + DA7218_HPLDET_JACK_EVENT_IRQ_MSK_MASK); + } + + if (da7218->irq) { + ret = devm_request_threaded_irq(component->dev, da7218->irq, NULL, + da7218_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "da7218", component); + if (ret != 0) { + dev_err(component->dev, "Failed to request IRQ %d: %d\n", + da7218->irq, ret); + goto err_disable_reg; + } + + } + + return 0; + +err_disable_reg: + regulator_bulk_disable(DA7218_NUM_SUPPLIES, da7218->supplies); + + return ret; +} + +static void da7218_remove(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + + regulator_bulk_disable(DA7218_NUM_SUPPLIES, da7218->supplies); +} + +#ifdef CONFIG_PM +static int da7218_suspend(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + + da7218_set_bias_level(component, SND_SOC_BIAS_OFF); + + /* Put device into standby mode if jack detection disabled */ + if (!da7218->jack) + snd_soc_component_write(component, DA7218_SYSTEM_ACTIVE, 0); + + return 0; +} + +static int da7218_resume(struct snd_soc_component *component) +{ + struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + + /* Put device into active mode if previously moved to standby */ + if (!da7218->jack) + snd_soc_component_write(component, DA7218_SYSTEM_ACTIVE, + DA7218_SYSTEM_ACTIVE_MASK); + + da7218_set_bias_level(component, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define da7218_suspend NULL +#define da7218_resume NULL +#endif + +static const struct snd_soc_component_driver soc_component_dev_da7218 = { + .probe = da7218_probe, + .remove = da7218_remove, + .suspend = da7218_suspend, + .resume = da7218_resume, + .set_bias_level = da7218_set_bias_level, + .controls = da7218_snd_controls, + .num_controls = ARRAY_SIZE(da7218_snd_controls), + .dapm_widgets = da7218_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(da7218_dapm_widgets), + .dapm_routes = da7218_audio_map, + .num_dapm_routes = ARRAY_SIZE(da7218_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + + +/* + * Regmap configs + */ + +static struct reg_default da7218_reg_defaults[] = { + { DA7218_SYSTEM_ACTIVE, 0x00 }, + { DA7218_CIF_CTRL, 0x00 }, + { DA7218_SPARE1, 0x00 }, + { DA7218_SR, 0xAA }, + { DA7218_PC_COUNT, 0x02 }, + { DA7218_GAIN_RAMP_CTRL, 0x00 }, + { DA7218_CIF_TIMEOUT_CTRL, 0x01 }, + { DA7218_SYSTEM_MODES_INPUT, 0x00 }, + { DA7218_SYSTEM_MODES_OUTPUT, 0x00 }, + { DA7218_IN_1L_FILTER_CTRL, 0x00 }, + { DA7218_IN_1R_FILTER_CTRL, 0x00 }, + { DA7218_IN_2L_FILTER_CTRL, 0x00 }, + { DA7218_IN_2R_FILTER_CTRL, 0x00 }, + { DA7218_OUT_1L_FILTER_CTRL, 0x40 }, + { DA7218_OUT_1R_FILTER_CTRL, 0x40 }, + { DA7218_OUT_1_HPF_FILTER_CTRL, 0x80 }, + { DA7218_OUT_1_EQ_12_FILTER_CTRL, 0x77 }, + { DA7218_OUT_1_EQ_34_FILTER_CTRL, 0x77 }, + { DA7218_OUT_1_EQ_5_FILTER_CTRL, 0x07 }, + { DA7218_OUT_1_BIQ_5STAGE_CTRL, 0x40 }, + { DA7218_OUT_1_BIQ_5STAGE_DATA, 0x00 }, + { DA7218_OUT_1_BIQ_5STAGE_ADDR, 0x00 }, + { DA7218_MIXIN_1_CTRL, 0x48 }, + { DA7218_MIXIN_1_GAIN, 0x03 }, + { DA7218_MIXIN_2_CTRL, 0x48 }, + { DA7218_MIXIN_2_GAIN, 0x03 }, + { DA7218_ALC_CTRL1, 0x00 }, + { DA7218_ALC_CTRL2, 0x00 }, + { DA7218_ALC_CTRL3, 0x00 }, + { DA7218_ALC_NOISE, 0x3F }, + { DA7218_ALC_TARGET_MIN, 0x3F }, + { DA7218_ALC_TARGET_MAX, 0x00 }, + { DA7218_ALC_GAIN_LIMITS, 0xFF }, + { DA7218_ALC_ANA_GAIN_LIMITS, 0x71 }, + { DA7218_ALC_ANTICLIP_CTRL, 0x00 }, + { DA7218_AGS_ENABLE, 0x00 }, + { DA7218_AGS_TRIGGER, 0x09 }, + { DA7218_AGS_ATT_MAX, 0x00 }, + { DA7218_AGS_TIMEOUT, 0x00 }, + { DA7218_AGS_ANTICLIP_CTRL, 0x00 }, + { DA7218_ENV_TRACK_CTRL, 0x00 }, + { DA7218_LVL_DET_CTRL, 0x00 }, + { DA7218_LVL_DET_LEVEL, 0x7F }, + { DA7218_DGS_TRIGGER, 0x24 }, + { DA7218_DGS_ENABLE, 0x00 }, + { DA7218_DGS_RISE_FALL, 0x50 }, + { DA7218_DGS_SYNC_DELAY, 0xA3 }, + { DA7218_DGS_SYNC_DELAY2, 0x31 }, + { DA7218_DGS_SYNC_DELAY3, 0x11 }, + { DA7218_DGS_LEVELS, 0x01 }, + { DA7218_DGS_GAIN_CTRL, 0x74 }, + { DA7218_DROUTING_OUTDAI_1L, 0x01 }, + { DA7218_DMIX_OUTDAI_1L_INFILT_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1L_INFILT_1R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1L_INFILT_2L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1L_INFILT_2R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1L_TONEGEN_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1L_INDAI_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1L_INDAI_1R_GAIN, 0x1C }, + { DA7218_DROUTING_OUTDAI_1R, 0x04 }, + { DA7218_DMIX_OUTDAI_1R_INFILT_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1R_INFILT_1R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1R_INFILT_2L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1R_INFILT_2R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1R_TONEGEN_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1R_INDAI_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_1R_INDAI_1R_GAIN, 0x1C }, + { DA7218_DROUTING_OUTFILT_1L, 0x01 }, + { DA7218_DMIX_OUTFILT_1L_INFILT_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1L_INFILT_1R_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1L_INFILT_2L_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1L_INFILT_2R_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1L_TONEGEN_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1L_INDAI_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1L_INDAI_1R_GAIN, 0x1C }, + { DA7218_DROUTING_OUTFILT_1R, 0x04 }, + { DA7218_DMIX_OUTFILT_1R_INFILT_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1R_INFILT_1R_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1R_INFILT_2L_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1R_INFILT_2R_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1R_TONEGEN_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1R_INDAI_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTFILT_1R_INDAI_1R_GAIN, 0x1C }, + { DA7218_DROUTING_OUTDAI_2L, 0x04 }, + { DA7218_DMIX_OUTDAI_2L_INFILT_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2L_INFILT_1R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2L_INFILT_2L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2L_INFILT_2R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2L_TONEGEN_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2L_INDAI_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2L_INDAI_1R_GAIN, 0x1C }, + { DA7218_DROUTING_OUTDAI_2R, 0x08 }, + { DA7218_DMIX_OUTDAI_2R_INFILT_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2R_INFILT_1R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2R_INFILT_2L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2R_INFILT_2R_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2R_TONEGEN_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2R_INDAI_1L_GAIN, 0x1C }, + { DA7218_DMIX_OUTDAI_2R_INDAI_1R_GAIN, 0x1C }, + { DA7218_DAI_CTRL, 0x28 }, + { DA7218_DAI_TDM_CTRL, 0x40 }, + { DA7218_DAI_OFFSET_LOWER, 0x00 }, + { DA7218_DAI_OFFSET_UPPER, 0x00 }, + { DA7218_DAI_CLK_MODE, 0x01 }, + { DA7218_PLL_CTRL, 0x04 }, + { DA7218_PLL_FRAC_TOP, 0x00 }, + { DA7218_PLL_FRAC_BOT, 0x00 }, + { DA7218_PLL_INTEGER, 0x20 }, + { DA7218_DAC_NG_CTRL, 0x00 }, + { DA7218_DAC_NG_SETUP_TIME, 0x00 }, + { DA7218_DAC_NG_OFF_THRESH, 0x00 }, + { DA7218_DAC_NG_ON_THRESH, 0x00 }, + { DA7218_TONE_GEN_CFG2, 0x00 }, + { DA7218_TONE_GEN_FREQ1_L, 0x55 }, + { DA7218_TONE_GEN_FREQ1_U, 0x15 }, + { DA7218_TONE_GEN_FREQ2_L, 0x00 }, + { DA7218_TONE_GEN_FREQ2_U, 0x40 }, + { DA7218_TONE_GEN_CYCLES, 0x00 }, + { DA7218_TONE_GEN_ON_PER, 0x02 }, + { DA7218_TONE_GEN_OFF_PER, 0x01 }, + { DA7218_CP_CTRL, 0x60 }, + { DA7218_CP_DELAY, 0x11 }, + { DA7218_CP_VOL_THRESHOLD1, 0x0E }, + { DA7218_MIC_1_CTRL, 0x40 }, + { DA7218_MIC_1_GAIN, 0x01 }, + { DA7218_MIC_1_SELECT, 0x00 }, + { DA7218_MIC_2_CTRL, 0x40 }, + { DA7218_MIC_2_GAIN, 0x01 }, + { DA7218_MIC_2_SELECT, 0x00 }, + { DA7218_IN_1_HPF_FILTER_CTRL, 0x80 }, + { DA7218_IN_2_HPF_FILTER_CTRL, 0x80 }, + { DA7218_ADC_1_CTRL, 0x07 }, + { DA7218_ADC_2_CTRL, 0x07 }, + { DA7218_MIXOUT_L_CTRL, 0x00 }, + { DA7218_MIXOUT_L_GAIN, 0x03 }, + { DA7218_MIXOUT_R_CTRL, 0x00 }, + { DA7218_MIXOUT_R_GAIN, 0x03 }, + { DA7218_HP_L_CTRL, 0x40 }, + { DA7218_HP_L_GAIN, 0x3B }, + { DA7218_HP_R_CTRL, 0x40 }, + { DA7218_HP_R_GAIN, 0x3B }, + { DA7218_HP_DIFF_CTRL, 0x00 }, + { DA7218_HP_DIFF_UNLOCK, 0xC3 }, + { DA7218_HPLDET_JACK, 0x0B }, + { DA7218_HPLDET_CTRL, 0x00 }, + { DA7218_REFERENCES, 0x08 }, + { DA7218_IO_CTRL, 0x00 }, + { DA7218_LDO_CTRL, 0x00 }, + { DA7218_SIDETONE_CTRL, 0x40 }, + { DA7218_SIDETONE_IN_SELECT, 0x00 }, + { DA7218_SIDETONE_GAIN, 0x1C }, + { DA7218_DROUTING_ST_OUTFILT_1L, 0x01 }, + { DA7218_DROUTING_ST_OUTFILT_1R, 0x02 }, + { DA7218_SIDETONE_BIQ_3STAGE_DATA, 0x00 }, + { DA7218_SIDETONE_BIQ_3STAGE_ADDR, 0x00 }, + { DA7218_EVENT_MASK, 0x00 }, + { DA7218_DMIC_1_CTRL, 0x00 }, + { DA7218_DMIC_2_CTRL, 0x00 }, + { DA7218_IN_1L_GAIN, 0x6F }, + { DA7218_IN_1R_GAIN, 0x6F }, + { DA7218_IN_2L_GAIN, 0x6F }, + { DA7218_IN_2R_GAIN, 0x6F }, + { DA7218_OUT_1L_GAIN, 0x6F }, + { DA7218_OUT_1R_GAIN, 0x6F }, + { DA7218_MICBIAS_CTRL, 0x00 }, + { DA7218_MICBIAS_EN, 0x00 }, +}; + +static bool da7218_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA7218_STATUS1: + case DA7218_SOFT_RESET: + case DA7218_SYSTEM_STATUS: + case DA7218_CALIB_CTRL: + case DA7218_CALIB_OFFSET_AUTO_M_1: + case DA7218_CALIB_OFFSET_AUTO_U_1: + case DA7218_CALIB_OFFSET_AUTO_M_2: + case DA7218_CALIB_OFFSET_AUTO_U_2: + case DA7218_PLL_STATUS: + case DA7218_PLL_REFOSC_CAL: + case DA7218_TONE_GEN_CFG1: + case DA7218_ADC_MODE: + case DA7218_HP_SNGL_CTRL: + case DA7218_HPLDET_TEST: + case DA7218_EVENT_STATUS: + case DA7218_EVENT: + return true; + default: + return false; + } +} + +static const struct regmap_config da7218_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = DA7218_MICBIAS_EN, + .reg_defaults = da7218_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da7218_reg_defaults), + .volatile_reg = da7218_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + + +/* + * I2C layer + */ + +static int da7218_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da7218_priv *da7218; + int ret; + + da7218 = devm_kzalloc(&i2c->dev, sizeof(*da7218), GFP_KERNEL); + if (!da7218) + return -ENOMEM; + + i2c_set_clientdata(i2c, da7218); + + if (i2c->dev.of_node) + da7218->dev_id = da7218_of_get_id(&i2c->dev); + else + da7218->dev_id = id->driver_data; + + if ((da7218->dev_id != DA7217_DEV_ID) && + (da7218->dev_id != DA7218_DEV_ID)) { + dev_err(&i2c->dev, "Invalid device Id\n"); + return -EINVAL; + } + + da7218->irq = i2c->irq; + + da7218->regmap = devm_regmap_init_i2c(i2c, &da7218_regmap_config); + if (IS_ERR(da7218->regmap)) { + ret = PTR_ERR(da7218->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_da7218, &da7218_dai, 1); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register da7218 component: %d\n", + ret); + } + return ret; +} + +static const struct i2c_device_id da7218_i2c_id[] = { + { "da7217", DA7217_DEV_ID }, + { "da7218", DA7218_DEV_ID }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da7218_i2c_id); + +static struct i2c_driver da7218_i2c_driver = { + .driver = { + .name = "da7218", + .of_match_table = of_match_ptr(da7218_of_match), + }, + .probe = da7218_i2c_probe, + .id_table = da7218_i2c_id, +}; + +module_i2c_driver(da7218_i2c_driver); + +MODULE_DESCRIPTION("ASoC DA7218 Codec driver"); +MODULE_AUTHOR("Adam Thomson "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da7218.h b/sound/soc/codecs/da7218.h new file mode 100644 index 000000000..19e6cee2e --- /dev/null +++ b/sound/soc/codecs/da7218.h @@ -0,0 +1,1415 @@ +/* + * da7218.h - DA7218 ALSA SoC Codec Driver + * + * Copyright (c) 2015 Dialog Semiconductor + * + * Author: Adam Thomson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _DA7218_H +#define _DA7218_H + +#include +#include +#include + + +/* + * Registers + */ +#define DA7218_SYSTEM_ACTIVE 0x0 +#define DA7218_CIF_CTRL 0x1 +#define DA7218_CHIP_ID1 0x4 +#define DA7218_CHIP_ID2 0x5 +#define DA7218_CHIP_REVISION 0x6 +#define DA7218_SPARE1 0x7 +#define DA7218_STATUS1 0x8 +#define DA7218_SOFT_RESET 0x9 +#define DA7218_SR 0xB +#define DA7218_PC_COUNT 0xC +#define DA7218_GAIN_RAMP_CTRL 0xD +#define DA7218_CIF_TIMEOUT_CTRL 0x10 +#define DA7218_SYSTEM_MODES_INPUT 0x14 +#define DA7218_SYSTEM_MODES_OUTPUT 0x15 +#define DA7218_SYSTEM_STATUS 0x16 +#define DA7218_IN_1L_FILTER_CTRL 0x18 +#define DA7218_IN_1R_FILTER_CTRL 0x19 +#define DA7218_IN_2L_FILTER_CTRL 0x1A +#define DA7218_IN_2R_FILTER_CTRL 0x1B +#define DA7218_OUT_1L_FILTER_CTRL 0x20 +#define DA7218_OUT_1R_FILTER_CTRL 0x21 +#define DA7218_OUT_1_HPF_FILTER_CTRL 0x24 +#define DA7218_OUT_1_EQ_12_FILTER_CTRL 0x25 +#define DA7218_OUT_1_EQ_34_FILTER_CTRL 0x26 +#define DA7218_OUT_1_EQ_5_FILTER_CTRL 0x27 +#define DA7218_OUT_1_BIQ_5STAGE_CTRL 0x28 +#define DA7218_OUT_1_BIQ_5STAGE_DATA 0x29 +#define DA7218_OUT_1_BIQ_5STAGE_ADDR 0x2A +#define DA7218_MIXIN_1_CTRL 0x2C +#define DA7218_MIXIN_1_GAIN 0x2D +#define DA7218_MIXIN_2_CTRL 0x2E +#define DA7218_MIXIN_2_GAIN 0x2F +#define DA7218_ALC_CTRL1 0x30 +#define DA7218_ALC_CTRL2 0x31 +#define DA7218_ALC_CTRL3 0x32 +#define DA7218_ALC_NOISE 0x33 +#define DA7218_ALC_TARGET_MIN 0x34 +#define DA7218_ALC_TARGET_MAX 0x35 +#define DA7218_ALC_GAIN_LIMITS 0x36 +#define DA7218_ALC_ANA_GAIN_LIMITS 0x37 +#define DA7218_ALC_ANTICLIP_CTRL 0x38 +#define DA7218_AGS_ENABLE 0x3C +#define DA7218_AGS_TRIGGER 0x3D +#define DA7218_AGS_ATT_MAX 0x3E +#define DA7218_AGS_TIMEOUT 0x3F +#define DA7218_AGS_ANTICLIP_CTRL 0x40 +#define DA7218_CALIB_CTRL 0x44 +#define DA7218_CALIB_OFFSET_AUTO_M_1 0x45 +#define DA7218_CALIB_OFFSET_AUTO_U_1 0x46 +#define DA7218_CALIB_OFFSET_AUTO_M_2 0x47 +#define DA7218_CALIB_OFFSET_AUTO_U_2 0x48 +#define DA7218_ENV_TRACK_CTRL 0x4C +#define DA7218_LVL_DET_CTRL 0x50 +#define DA7218_LVL_DET_LEVEL 0x51 +#define DA7218_DGS_TRIGGER 0x54 +#define DA7218_DGS_ENABLE 0x55 +#define DA7218_DGS_RISE_FALL 0x56 +#define DA7218_DGS_SYNC_DELAY 0x57 +#define DA7218_DGS_SYNC_DELAY2 0x58 +#define DA7218_DGS_SYNC_DELAY3 0x59 +#define DA7218_DGS_LEVELS 0x5A +#define DA7218_DGS_GAIN_CTRL 0x5B +#define DA7218_DROUTING_OUTDAI_1L 0x5C +#define DA7218_DMIX_OUTDAI_1L_INFILT_1L_GAIN 0x5D +#define DA7218_DMIX_OUTDAI_1L_INFILT_1R_GAIN 0x5E +#define DA7218_DMIX_OUTDAI_1L_INFILT_2L_GAIN 0x5F +#define DA7218_DMIX_OUTDAI_1L_INFILT_2R_GAIN 0x60 +#define DA7218_DMIX_OUTDAI_1L_TONEGEN_GAIN 0x61 +#define DA7218_DMIX_OUTDAI_1L_INDAI_1L_GAIN 0x62 +#define DA7218_DMIX_OUTDAI_1L_INDAI_1R_GAIN 0x63 +#define DA7218_DROUTING_OUTDAI_1R 0x64 +#define DA7218_DMIX_OUTDAI_1R_INFILT_1L_GAIN 0x65 +#define DA7218_DMIX_OUTDAI_1R_INFILT_1R_GAIN 0x66 +#define DA7218_DMIX_OUTDAI_1R_INFILT_2L_GAIN 0x67 +#define DA7218_DMIX_OUTDAI_1R_INFILT_2R_GAIN 0x68 +#define DA7218_DMIX_OUTDAI_1R_TONEGEN_GAIN 0x69 +#define DA7218_DMIX_OUTDAI_1R_INDAI_1L_GAIN 0x6A +#define DA7218_DMIX_OUTDAI_1R_INDAI_1R_GAIN 0x6B +#define DA7218_DROUTING_OUTFILT_1L 0x6C +#define DA7218_DMIX_OUTFILT_1L_INFILT_1L_GAIN 0x6D +#define DA7218_DMIX_OUTFILT_1L_INFILT_1R_GAIN 0x6E +#define DA7218_DMIX_OUTFILT_1L_INFILT_2L_GAIN 0x6F +#define DA7218_DMIX_OUTFILT_1L_INFILT_2R_GAIN 0x70 +#define DA7218_DMIX_OUTFILT_1L_TONEGEN_GAIN 0x71 +#define DA7218_DMIX_OUTFILT_1L_INDAI_1L_GAIN 0x72 +#define DA7218_DMIX_OUTFILT_1L_INDAI_1R_GAIN 0x73 +#define DA7218_DROUTING_OUTFILT_1R 0x74 +#define DA7218_DMIX_OUTFILT_1R_INFILT_1L_GAIN 0x75 +#define DA7218_DMIX_OUTFILT_1R_INFILT_1R_GAIN 0x76 +#define DA7218_DMIX_OUTFILT_1R_INFILT_2L_GAIN 0x77 +#define DA7218_DMIX_OUTFILT_1R_INFILT_2R_GAIN 0x78 +#define DA7218_DMIX_OUTFILT_1R_TONEGEN_GAIN 0x79 +#define DA7218_DMIX_OUTFILT_1R_INDAI_1L_GAIN 0x7A +#define DA7218_DMIX_OUTFILT_1R_INDAI_1R_GAIN 0x7B +#define DA7218_DROUTING_OUTDAI_2L 0x7C +#define DA7218_DMIX_OUTDAI_2L_INFILT_1L_GAIN 0x7D +#define DA7218_DMIX_OUTDAI_2L_INFILT_1R_GAIN 0x7E +#define DA7218_DMIX_OUTDAI_2L_INFILT_2L_GAIN 0x7F +#define DA7218_DMIX_OUTDAI_2L_INFILT_2R_GAIN 0x80 +#define DA7218_DMIX_OUTDAI_2L_TONEGEN_GAIN 0x81 +#define DA7218_DMIX_OUTDAI_2L_INDAI_1L_GAIN 0x82 +#define DA7218_DMIX_OUTDAI_2L_INDAI_1R_GAIN 0x83 +#define DA7218_DROUTING_OUTDAI_2R 0x84 +#define DA7218_DMIX_OUTDAI_2R_INFILT_1L_GAIN 0x85 +#define DA7218_DMIX_OUTDAI_2R_INFILT_1R_GAIN 0x86 +#define DA7218_DMIX_OUTDAI_2R_INFILT_2L_GAIN 0x87 +#define DA7218_DMIX_OUTDAI_2R_INFILT_2R_GAIN 0x88 +#define DA7218_DMIX_OUTDAI_2R_TONEGEN_GAIN 0x89 +#define DA7218_DMIX_OUTDAI_2R_INDAI_1L_GAIN 0x8A +#define DA7218_DMIX_OUTDAI_2R_INDAI_1R_GAIN 0x8B +#define DA7218_DAI_CTRL 0x8C +#define DA7218_DAI_TDM_CTRL 0x8D +#define DA7218_DAI_OFFSET_LOWER 0x8E +#define DA7218_DAI_OFFSET_UPPER 0x8F +#define DA7218_DAI_CLK_MODE 0x90 +#define DA7218_PLL_CTRL 0x91 +#define DA7218_PLL_FRAC_TOP 0x92 +#define DA7218_PLL_FRAC_BOT 0x93 +#define DA7218_PLL_INTEGER 0x94 +#define DA7218_PLL_STATUS 0x95 +#define DA7218_PLL_REFOSC_CAL 0x98 +#define DA7218_DAC_NG_CTRL 0x9C +#define DA7218_DAC_NG_SETUP_TIME 0x9D +#define DA7218_DAC_NG_OFF_THRESH 0x9E +#define DA7218_DAC_NG_ON_THRESH 0x9F +#define DA7218_TONE_GEN_CFG1 0xA0 +#define DA7218_TONE_GEN_CFG2 0xA1 +#define DA7218_TONE_GEN_FREQ1_L 0xA2 +#define DA7218_TONE_GEN_FREQ1_U 0xA3 +#define DA7218_TONE_GEN_FREQ2_L 0xA4 +#define DA7218_TONE_GEN_FREQ2_U 0xA5 +#define DA7218_TONE_GEN_CYCLES 0xA6 +#define DA7218_TONE_GEN_ON_PER 0xA7 +#define DA7218_TONE_GEN_OFF_PER 0xA8 +#define DA7218_CP_CTRL 0xAC +#define DA7218_CP_DELAY 0xAD +#define DA7218_CP_VOL_THRESHOLD1 0xAE +#define DA7218_MIC_1_CTRL 0xB4 +#define DA7218_MIC_1_GAIN 0xB5 +#define DA7218_MIC_1_SELECT 0xB7 +#define DA7218_MIC_2_CTRL 0xB8 +#define DA7218_MIC_2_GAIN 0xB9 +#define DA7218_MIC_2_SELECT 0xBB +#define DA7218_IN_1_HPF_FILTER_CTRL 0xBC +#define DA7218_IN_2_HPF_FILTER_CTRL 0xBD +#define DA7218_ADC_1_CTRL 0xC0 +#define DA7218_ADC_2_CTRL 0xC1 +#define DA7218_ADC_MODE 0xC2 +#define DA7218_MIXOUT_L_CTRL 0xCC +#define DA7218_MIXOUT_L_GAIN 0xCD +#define DA7218_MIXOUT_R_CTRL 0xCE +#define DA7218_MIXOUT_R_GAIN 0xCF +#define DA7218_HP_L_CTRL 0xD0 +#define DA7218_HP_L_GAIN 0xD1 +#define DA7218_HP_R_CTRL 0xD2 +#define DA7218_HP_R_GAIN 0xD3 +#define DA7218_HP_SNGL_CTRL 0xD4 +#define DA7218_HP_DIFF_CTRL 0xD5 +#define DA7218_HP_DIFF_UNLOCK 0xD7 +#define DA7218_HPLDET_JACK 0xD8 +#define DA7218_HPLDET_CTRL 0xD9 +#define DA7218_HPLDET_TEST 0xDA +#define DA7218_REFERENCES 0xDC +#define DA7218_IO_CTRL 0xE0 +#define DA7218_LDO_CTRL 0xE1 +#define DA7218_SIDETONE_CTRL 0xE4 +#define DA7218_SIDETONE_IN_SELECT 0xE5 +#define DA7218_SIDETONE_GAIN 0xE6 +#define DA7218_DROUTING_ST_OUTFILT_1L 0xE8 +#define DA7218_DROUTING_ST_OUTFILT_1R 0xE9 +#define DA7218_SIDETONE_BIQ_3STAGE_DATA 0xEA +#define DA7218_SIDETONE_BIQ_3STAGE_ADDR 0xEB +#define DA7218_EVENT_STATUS 0xEC +#define DA7218_EVENT 0xED +#define DA7218_EVENT_MASK 0xEE +#define DA7218_DMIC_1_CTRL 0xF0 +#define DA7218_DMIC_2_CTRL 0xF1 +#define DA7218_IN_1L_GAIN 0xF4 +#define DA7218_IN_1R_GAIN 0xF5 +#define DA7218_IN_2L_GAIN 0xF6 +#define DA7218_IN_2R_GAIN 0xF7 +#define DA7218_OUT_1L_GAIN 0xF8 +#define DA7218_OUT_1R_GAIN 0xF9 +#define DA7218_MICBIAS_CTRL 0xFC +#define DA7218_MICBIAS_EN 0xFD + + +/* + * Bit Fields + */ + +#define DA7218_SWITCH_EN_MAX 0x1 + +/* DA7218_SYSTEM_ACTIVE = 0x0 */ +#define DA7218_SYSTEM_ACTIVE_SHIFT 0 +#define DA7218_SYSTEM_ACTIVE_MASK (0x1 << 0) + +/* DA7218_CIF_CTRL = 0x1 */ +#define DA7218_CIF_I2C_WRITE_MODE_SHIFT 0 +#define DA7218_CIF_I2C_WRITE_MODE_MASK (0x1 << 0) + +/* DA7218_CHIP_ID1 = 0x4 */ +#define DA7218_CHIP_ID1_SHIFT 0 +#define DA7218_CHIP_ID1_MASK (0xFF << 0) + +/* DA7218_CHIP_ID2 = 0x5 */ +#define DA7218_CHIP_ID2_SHIFT 0 +#define DA7218_CHIP_ID2_MASK (0xFF << 0) + +/* DA7218_CHIP_REVISION = 0x6 */ +#define DA7218_CHIP_MINOR_SHIFT 0 +#define DA7218_CHIP_MINOR_MASK (0xF << 0) +#define DA7218_CHIP_MAJOR_SHIFT 4 +#define DA7218_CHIP_MAJOR_MASK (0xF << 4) + +/* DA7218_SPARE1 = 0x7 */ +#define DA7218_SPARE1_SHIFT 0 +#define DA7218_SPARE1_MASK (0xFF << 0) + +/* DA7218_STATUS1 = 0x8 */ +#define DA7218_STATUS_SPARE1_SHIFT 0 +#define DA7218_STATUS_SPARE1_MASK (0xFF << 0) + +/* DA7218_SOFT_RESET = 0x9 */ +#define DA7218_CIF_REG_SOFT_RESET_SHIFT 7 +#define DA7218_CIF_REG_SOFT_RESET_MASK (0x1 << 7) + +/* DA7218_SR = 0xB */ +#define DA7218_SR_ADC_SHIFT 0 +#define DA7218_SR_ADC_MASK (0xF << 0) +#define DA7218_SR_DAC_SHIFT 4 +#define DA7218_SR_DAC_MASK (0xF << 4) +#define DA7218_SR_8000 0x01 +#define DA7218_SR_11025 0x02 +#define DA7218_SR_12000 0x03 +#define DA7218_SR_16000 0x05 +#define DA7218_SR_22050 0x06 +#define DA7218_SR_24000 0x07 +#define DA7218_SR_32000 0x09 +#define DA7218_SR_44100 0x0A +#define DA7218_SR_48000 0x0B +#define DA7218_SR_88200 0x0E +#define DA7218_SR_96000 0x0F + +/* DA7218_PC_COUNT = 0xC */ +#define DA7218_PC_FREERUN_SHIFT 0 +#define DA7218_PC_FREERUN_MASK (0x1 << 0) +#define DA7218_PC_RESYNC_AUTO_SHIFT 1 +#define DA7218_PC_RESYNC_AUTO_MASK (0x1 << 1) + +/* DA7218_GAIN_RAMP_CTRL = 0xD */ +#define DA7218_GAIN_RAMP_RATE_SHIFT 0 +#define DA7218_GAIN_RAMP_RATE_MASK (0x3 << 0) +#define DA7218_GAIN_RAMP_RATE_MAX 4 + +/* DA7218_CIF_TIMEOUT_CTRL = 0x10 */ +#define DA7218_I2C_TIMEOUT_EN_SHIFT 0 +#define DA7218_I2C_TIMEOUT_EN_MASK (0x1 << 0) + +/* DA7218_SYSTEM_MODES_INPUT = 0x14 */ +#define DA7218_MODE_SUBMIT_SHIFT 0 +#define DA7218_MODE_SUBMIT_MASK (0x1 << 0) +#define DA7218_ADC_MODE_SHIFT 1 +#define DA7218_ADC_MODE_MASK (0x7F << 1) + +/* DA7218_SYSTEM_MODES_OUTPUT = 0x15 */ +#define DA7218_MODE_SUBMIT_SHIFT 0 +#define DA7218_MODE_SUBMIT_MASK (0x1 << 0) +#define DA7218_DAC_MODE_SHIFT 1 +#define DA7218_DAC_MODE_MASK (0x7F << 1) + +/* DA7218_SYSTEM_STATUS = 0x16 */ +#define DA7218_SC1_BUSY_SHIFT 0 +#define DA7218_SC1_BUSY_MASK (0x1 << 0) +#define DA7218_SC2_BUSY_SHIFT 1 +#define DA7218_SC2_BUSY_MASK (0x1 << 1) + +/* DA7218_IN_1L_FILTER_CTRL = 0x18 */ +#define DA7218_IN_1L_RAMP_EN_SHIFT 5 +#define DA7218_IN_1L_RAMP_EN_MASK (0x1 << 5) +#define DA7218_IN_1L_MUTE_EN_SHIFT 6 +#define DA7218_IN_1L_MUTE_EN_MASK (0x1 << 6) +#define DA7218_IN_1L_FILTER_EN_SHIFT 7 +#define DA7218_IN_1L_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_IN_1R_FILTER_CTRL = 0x19 */ +#define DA7218_IN_1R_RAMP_EN_SHIFT 5 +#define DA7218_IN_1R_RAMP_EN_MASK (0x1 << 5) +#define DA7218_IN_1R_MUTE_EN_SHIFT 6 +#define DA7218_IN_1R_MUTE_EN_MASK (0x1 << 6) +#define DA7218_IN_1R_FILTER_EN_SHIFT 7 +#define DA7218_IN_1R_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_IN_2L_FILTER_CTRL = 0x1A */ +#define DA7218_IN_2L_RAMP_EN_SHIFT 5 +#define DA7218_IN_2L_RAMP_EN_MASK (0x1 << 5) +#define DA7218_IN_2L_MUTE_EN_SHIFT 6 +#define DA7218_IN_2L_MUTE_EN_MASK (0x1 << 6) +#define DA7218_IN_2L_FILTER_EN_SHIFT 7 +#define DA7218_IN_2L_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_IN_2R_FILTER_CTRL = 0x1B */ +#define DA7218_IN_2R_RAMP_EN_SHIFT 5 +#define DA7218_IN_2R_RAMP_EN_MASK (0x1 << 5) +#define DA7218_IN_2R_MUTE_EN_SHIFT 6 +#define DA7218_IN_2R_MUTE_EN_MASK (0x1 << 6) +#define DA7218_IN_2R_FILTER_EN_SHIFT 7 +#define DA7218_IN_2R_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_OUT_1L_FILTER_CTRL = 0x20 */ +#define DA7218_OUT_1L_BIQ_5STAGE_SEL_SHIFT 3 +#define DA7218_OUT_1L_BIQ_5STAGE_SEL_MASK (0x1 << 3) +#define DA7218_OUT_BIQ_5STAGE_SEL_MAX 2 +#define DA7218_OUT_1L_SUBRANGE_EN_SHIFT 4 +#define DA7218_OUT_1L_SUBRANGE_EN_MASK (0x1 << 4) +#define DA7218_OUT_1L_RAMP_EN_SHIFT 5 +#define DA7218_OUT_1L_RAMP_EN_MASK (0x1 << 5) +#define DA7218_OUT_1L_MUTE_EN_SHIFT 6 +#define DA7218_OUT_1L_MUTE_EN_MASK (0x1 << 6) +#define DA7218_OUT_1L_FILTER_EN_SHIFT 7 +#define DA7218_OUT_1L_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_OUT_1R_FILTER_CTRL = 0x21 */ +#define DA7218_OUT_1R_BIQ_5STAGE_SEL_SHIFT 3 +#define DA7218_OUT_1R_BIQ_5STAGE_SEL_MASK (0x1 << 3) +#define DA7218_OUT_1R_SUBRANGE_EN_SHIFT 4 +#define DA7218_OUT_1R_SUBRANGE_EN_MASK (0x1 << 4) +#define DA7218_OUT_1R_RAMP_EN_SHIFT 5 +#define DA7218_OUT_1R_RAMP_EN_MASK (0x1 << 5) +#define DA7218_OUT_1R_MUTE_EN_SHIFT 6 +#define DA7218_OUT_1R_MUTE_EN_MASK (0x1 << 6) +#define DA7218_OUT_1R_FILTER_EN_SHIFT 7 +#define DA7218_OUT_1R_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_OUT_1_HPF_FILTER_CTRL = 0x24 */ +#define DA7218_OUT_1_VOICE_HPF_CORNER_SHIFT 0 +#define DA7218_OUT_1_VOICE_HPF_CORNER_MASK (0x7 << 0) +#define DA7218_VOICE_HPF_CORNER_MAX 8 +#define DA7218_OUT_1_VOICE_EN_SHIFT 3 +#define DA7218_OUT_1_VOICE_EN_MASK (0x1 << 3) +#define DA7218_OUT_1_AUDIO_HPF_CORNER_SHIFT 4 +#define DA7218_OUT_1_AUDIO_HPF_CORNER_MASK (0x3 << 4) +#define DA7218_AUDIO_HPF_CORNER_MAX 4 +#define DA7218_OUT_1_HPF_EN_SHIFT 7 +#define DA7218_OUT_1_HPF_EN_MASK (0x1 << 7) +#define DA7218_HPF_MODE_SHIFT 0 +#define DA7218_HPF_DISABLED ((0x0 << 3) | (0x0 << 7)) +#define DA7218_HPF_AUDIO_EN ((0x0 << 3) | (0x1 << 7)) +#define DA7218_HPF_VOICE_EN ((0x1 << 3) | (0x1 << 7)) +#define DA7218_HPF_MODE_MASK ((0x1 << 3) | (0x1 << 7)) +#define DA7218_HPF_MODE_MAX 3 + +/* DA7218_OUT_1_EQ_12_FILTER_CTRL = 0x25 */ +#define DA7218_OUT_1_EQ_BAND1_SHIFT 0 +#define DA7218_OUT_1_EQ_BAND1_MASK (0xF << 0) +#define DA7218_OUT_EQ_BAND_MAX 0xF +#define DA7218_OUT_1_EQ_BAND2_SHIFT 4 +#define DA7218_OUT_1_EQ_BAND2_MASK (0xF << 4) + +/* DA7218_OUT_1_EQ_34_FILTER_CTRL = 0x26 */ +#define DA7218_OUT_1_EQ_BAND3_SHIFT 0 +#define DA7218_OUT_1_EQ_BAND3_MASK (0xF << 0) +#define DA7218_OUT_1_EQ_BAND4_SHIFT 4 +#define DA7218_OUT_1_EQ_BAND4_MASK (0xF << 4) + +/* DA7218_OUT_1_EQ_5_FILTER_CTRL = 0x27 */ +#define DA7218_OUT_1_EQ_BAND5_SHIFT 0 +#define DA7218_OUT_1_EQ_BAND5_MASK (0xF << 0) +#define DA7218_OUT_1_EQ_EN_SHIFT 7 +#define DA7218_OUT_1_EQ_EN_MASK (0x1 << 7) + +/* DA7218_OUT_1_BIQ_5STAGE_CTRL = 0x28 */ +#define DA7218_OUT_1_BIQ_5STAGE_MUTE_EN_SHIFT 6 +#define DA7218_OUT_1_BIQ_5STAGE_MUTE_EN_MASK (0x1 << 6) +#define DA7218_OUT_1_BIQ_5STAGE_FILTER_EN_SHIFT 7 +#define DA7218_OUT_1_BIQ_5STAGE_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_OUT_1_BIQ_5STAGE_DATA = 0x29 */ +#define DA7218_OUT_1_BIQ_5STAGE_DATA_SHIFT 0 +#define DA7218_OUT_1_BIQ_5STAGE_DATA_MASK (0xFF << 0) + +/* DA7218_OUT_1_BIQ_5STAGE_ADDR = 0x2A */ +#define DA7218_OUT_1_BIQ_5STAGE_ADDR_SHIFT 0 +#define DA7218_OUT_1_BIQ_5STAGE_ADDR_MASK (0x3F << 0) +#define DA7218_OUT_1_BIQ_5STAGE_CFG_SIZE 50 + +/* DA7218_MIXIN_1_CTRL = 0x2C */ +#define DA7218_MIXIN_1_MIX_SEL_SHIFT 3 +#define DA7218_MIXIN_1_MIX_SEL_MASK (0x1 << 3) +#define DA7218_MIXIN_1_AMP_ZC_EN_SHIFT 4 +#define DA7218_MIXIN_1_AMP_ZC_EN_MASK (0x1 << 4) +#define DA7218_MIXIN_1_AMP_RAMP_EN_SHIFT 5 +#define DA7218_MIXIN_1_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7218_MIXIN_1_AMP_MUTE_EN_SHIFT 6 +#define DA7218_MIXIN_1_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7218_MIXIN_1_AMP_EN_SHIFT 7 +#define DA7218_MIXIN_1_AMP_EN_MASK (0x1 << 7) + +/* DA7218_MIXIN_1_GAIN = 0x2D */ +#define DA7218_MIXIN_1_AMP_GAIN_SHIFT 0 +#define DA7218_MIXIN_1_AMP_GAIN_MASK (0xF << 0) +#define DA7218_MIXIN_AMP_GAIN_MAX 0xF + +/* DA7218_MIXIN_2_CTRL = 0x2E */ +#define DA7218_MIXIN_2_MIX_SEL_SHIFT 3 +#define DA7218_MIXIN_2_MIX_SEL_MASK (0x1 << 3) +#define DA7218_MIXIN_2_AMP_ZC_EN_SHIFT 4 +#define DA7218_MIXIN_2_AMP_ZC_EN_MASK (0x1 << 4) +#define DA7218_MIXIN_2_AMP_RAMP_EN_SHIFT 5 +#define DA7218_MIXIN_2_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7218_MIXIN_2_AMP_MUTE_EN_SHIFT 6 +#define DA7218_MIXIN_2_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7218_MIXIN_2_AMP_EN_SHIFT 7 +#define DA7218_MIXIN_2_AMP_EN_MASK (0x1 << 7) + +/* DA7218_MIXIN_2_GAIN = 0x2F */ +#define DA7218_MIXIN_2_AMP_GAIN_SHIFT 0 +#define DA7218_MIXIN_2_AMP_GAIN_MASK (0xF << 0) + +/* DA7218_ALC_CTRL1 = 0x30 */ +#define DA7218_ALC_EN_SHIFT 0 +#define DA7218_ALC_EN_MASK (0xF << 0) +#define DA7218_ALC_CHAN1_L_EN_SHIFT 0 +#define DA7218_ALC_CHAN1_R_EN_SHIFT 1 +#define DA7218_ALC_CHAN2_L_EN_SHIFT 2 +#define DA7218_ALC_CHAN2_R_EN_SHIFT 3 +#define DA7218_ALC_SYNC_MODE_SHIFT 4 +#define DA7218_ALC_SYNC_MODE_MASK (0xF << 4) +#define DA7218_ALC_SYNC_MODE_CH1 (0x1 << 4) +#define DA7218_ALC_SYNC_MODE_CH2 (0x4 << 4) + +/* DA7218_ALC_CTRL2 = 0x31 */ +#define DA7218_ALC_ATTACK_SHIFT 0 +#define DA7218_ALC_ATTACK_MASK (0xF << 0) +#define DA7218_ALC_ATTACK_MAX 13 +#define DA7218_ALC_RELEASE_SHIFT 4 +#define DA7218_ALC_RELEASE_MASK (0xF << 4) +#define DA7218_ALC_RELEASE_MAX 11 + +/* DA7218_ALC_CTRL3 = 0x32 */ +#define DA7218_ALC_HOLD_SHIFT 0 +#define DA7218_ALC_HOLD_MASK (0xF << 0) +#define DA7218_ALC_HOLD_MAX 16 + +/* DA7218_ALC_NOISE = 0x33 */ +#define DA7218_ALC_NOISE_SHIFT 0 +#define DA7218_ALC_NOISE_MASK (0x3F << 0) +#define DA7218_ALC_THRESHOLD_MAX 0x3F + +/* DA7218_ALC_TARGET_MIN = 0x34 */ +#define DA7218_ALC_THRESHOLD_MIN_SHIFT 0 +#define DA7218_ALC_THRESHOLD_MIN_MASK (0x3F << 0) + +/* DA7218_ALC_TARGET_MAX = 0x35 */ +#define DA7218_ALC_THRESHOLD_MAX_SHIFT 0 +#define DA7218_ALC_THRESHOLD_MAX_MASK (0x3F << 0) + +/* DA7218_ALC_GAIN_LIMITS = 0x36 */ +#define DA7218_ALC_ATTEN_MAX_SHIFT 0 +#define DA7218_ALC_ATTEN_MAX_MASK (0xF << 0) +#define DA7218_ALC_ATTEN_GAIN_MAX 0xF +#define DA7218_ALC_GAIN_MAX_SHIFT 4 +#define DA7218_ALC_GAIN_MAX_MASK (0xF << 4) + +/* DA7218_ALC_ANA_GAIN_LIMITS = 0x37 */ +#define DA7218_ALC_ANA_GAIN_MIN_SHIFT 0 +#define DA7218_ALC_ANA_GAIN_MIN_MASK (0x7 << 0) +#define DA7218_ALC_ANA_GAIN_MIN 0x1 +#define DA7218_ALC_ANA_GAIN_MAX 0x7 +#define DA7218_ALC_ANA_GAIN_MAX_SHIFT 4 +#define DA7218_ALC_ANA_GAIN_MAX_MASK (0x7 << 4) + +/* DA7218_ALC_ANTICLIP_CTRL = 0x38 */ +#define DA7218_ALC_ANTICLIP_STEP_SHIFT 0 +#define DA7218_ALC_ANTICLIP_STEP_MASK (0x3 << 0) +#define DA7218_ALC_ANTICLIP_STEP_MAX 4 +#define DA7218_ALC_ANTICLIP_EN_SHIFT 7 +#define DA7218_ALC_ANTICLIP_EN_MASK (0x1 << 7) + +/* DA7218_AGS_ENABLE = 0x3C */ +#define DA7218_AGS_ENABLE_SHIFT 0 +#define DA7218_AGS_ENABLE_MASK (0x3 << 0) +#define DA7218_AGS_ENABLE_CHAN1_SHIFT 0 +#define DA7218_AGS_ENABLE_CHAN2_SHIFT 1 + +/* DA7218_AGS_TRIGGER = 0x3D */ +#define DA7218_AGS_TRIGGER_SHIFT 0 +#define DA7218_AGS_TRIGGER_MASK (0xF << 0) +#define DA7218_AGS_TRIGGER_MAX 0xF + +/* DA7218_AGS_ATT_MAX = 0x3E */ +#define DA7218_AGS_ATT_MAX_SHIFT 0 +#define DA7218_AGS_ATT_MAX_MASK (0x7 << 0) +#define DA7218_AGS_ATT_MAX_MAX 0x7 + +/* DA7218_AGS_TIMEOUT = 0x3F */ +#define DA7218_AGS_TIMEOUT_EN_SHIFT 0 +#define DA7218_AGS_TIMEOUT_EN_MASK (0x1 << 0) + +/* DA7218_AGS_ANTICLIP_CTRL = 0x40 */ +#define DA7218_AGS_ANTICLIP_EN_SHIFT 7 +#define DA7218_AGS_ANTICLIP_EN_MASK (0x1 << 7) + +/* DA7218_CALIB_CTRL = 0x44 */ +#define DA7218_CALIB_OFFSET_EN_SHIFT 0 +#define DA7218_CALIB_OFFSET_EN_MASK (0x1 << 0) +#define DA7218_CALIB_AUTO_EN_SHIFT 2 +#define DA7218_CALIB_AUTO_EN_MASK (0x1 << 2) +#define DA7218_CALIB_OVERFLOW_SHIFT 3 +#define DA7218_CALIB_OVERFLOW_MASK (0x1 << 3) + +/* DA7218_CALIB_OFFSET_AUTO_M_1 = 0x45 */ +#define DA7218_CALIB_OFFSET_AUTO_M_1_SHIFT 0 +#define DA7218_CALIB_OFFSET_AUTO_M_1_MASK (0xFF << 0) + +/* DA7218_CALIB_OFFSET_AUTO_U_1 = 0x46 */ +#define DA7218_CALIB_OFFSET_AUTO_U_1_SHIFT 0 +#define DA7218_CALIB_OFFSET_AUTO_U_1_MASK (0xF << 0) + +/* DA7218_CALIB_OFFSET_AUTO_M_2 = 0x47 */ +#define DA7218_CALIB_OFFSET_AUTO_M_2_SHIFT 0 +#define DA7218_CALIB_OFFSET_AUTO_M_2_MASK (0xFF << 0) + +/* DA7218_CALIB_OFFSET_AUTO_U_2 = 0x48 */ +#define DA7218_CALIB_OFFSET_AUTO_U_2_SHIFT 0 +#define DA7218_CALIB_OFFSET_AUTO_U_2_MASK (0xF << 0) + +/* DA7218_ENV_TRACK_CTRL = 0x4C */ +#define DA7218_INTEG_ATTACK_SHIFT 0 +#define DA7218_INTEG_ATTACK_MASK (0x3 << 0) +#define DA7218_INTEG_RELEASE_SHIFT 4 +#define DA7218_INTEG_RELEASE_MASK (0x3 << 4) +#define DA7218_INTEG_MAX 4 + +/* DA7218_LVL_DET_CTRL = 0x50 */ +#define DA7218_LVL_DET_EN_SHIFT 0 +#define DA7218_LVL_DET_EN_MASK (0xF << 0) +#define DA7218_LVL_DET_EN_CHAN1L_SHIFT 0 +#define DA7218_LVL_DET_EN_CHAN1R_SHIFT 1 +#define DA7218_LVL_DET_EN_CHAN2L_SHIFT 2 +#define DA7218_LVL_DET_EN_CHAN2R_SHIFT 3 + +/* DA7218_LVL_DET_LEVEL = 0x51 */ +#define DA7218_LVL_DET_LEVEL_SHIFT 0 +#define DA7218_LVL_DET_LEVEL_MASK (0x7F << 0) +#define DA7218_LVL_DET_LEVEL_MAX 0x7F + +/* DA7218_DGS_TRIGGER = 0x54 */ +#define DA7218_DGS_TRIGGER_LVL_SHIFT 0 +#define DA7218_DGS_TRIGGER_LVL_MASK (0x3F << 0) +#define DA7218_DGS_TRIGGER_MAX 0x3F + +/* DA7218_DGS_ENABLE = 0x55 */ +#define DA7218_DGS_ENABLE_SHIFT 0 +#define DA7218_DGS_ENABLE_MASK (0x3 << 0) +#define DA7218_DGS_ENABLE_L_SHIFT 0 +#define DA7218_DGS_ENABLE_R_SHIFT 1 + +/* DA7218_DGS_RISE_FALL = 0x56 */ +#define DA7218_DGS_RISE_COEFF_SHIFT 0 +#define DA7218_DGS_RISE_COEFF_MASK (0x7 << 0) +#define DA7218_DGS_RISE_COEFF_MAX 7 +#define DA7218_DGS_FALL_COEFF_SHIFT 4 +#define DA7218_DGS_FALL_COEFF_MASK (0x7 << 4) +#define DA7218_DGS_FALL_COEFF_MAX 8 + +/* DA7218_DGS_SYNC_DELAY = 0x57 */ +#define DA7218_DGS_SYNC_DELAY_SHIFT 0 +#define DA7218_DGS_SYNC_DELAY_MASK (0xFF << 0) +#define DA7218_DGS_SYNC_DELAY_MAX 0xFF + +/* DA7218_DGS_SYNC_DELAY2 = 0x58 */ +#define DA7218_DGS_SYNC_DELAY2_SHIFT 0 +#define DA7218_DGS_SYNC_DELAY2_MASK (0xFF << 0) + +/* DA7218_DGS_SYNC_DELAY3 = 0x59 */ +#define DA7218_DGS_SYNC_DELAY3_SHIFT 0 +#define DA7218_DGS_SYNC_DELAY3_MASK (0x7F << 0) +#define DA7218_DGS_SYNC_DELAY3_MAX 0x7F + +/* DA7218_DGS_LEVELS = 0x5A */ +#define DA7218_DGS_ANTICLIP_LVL_SHIFT 0 +#define DA7218_DGS_ANTICLIP_LVL_MASK (0x7 << 0) +#define DA7218_DGS_ANTICLIP_LVL_MAX 0x7 +#define DA7218_DGS_SIGNAL_LVL_SHIFT 4 +#define DA7218_DGS_SIGNAL_LVL_MASK (0xF << 4) +#define DA7218_DGS_SIGNAL_LVL_MAX 0xF + +/* DA7218_DGS_GAIN_CTRL = 0x5B */ +#define DA7218_DGS_STEPS_SHIFT 0 +#define DA7218_DGS_STEPS_MASK (0x1F << 0) +#define DA7218_DGS_STEPS_MAX 0x1F +#define DA7218_DGS_RAMP_EN_SHIFT 5 +#define DA7218_DGS_RAMP_EN_MASK (0x1 << 5) +#define DA7218_DGS_SUBR_EN_SHIFT 6 +#define DA7218_DGS_SUBR_EN_MASK (0x1 << 6) + +/* DA7218_DROUTING_OUTDAI_1L = 0x5C */ +#define DA7218_OUTDAI_1L_SRC_SHIFT 0 +#define DA7218_OUTDAI_1L_SRC_MASK (0x7F << 0) +#define DA7218_DMIX_SRC_INFILT1L 0 +#define DA7218_DMIX_SRC_INFILT1R 1 +#define DA7218_DMIX_SRC_INFILT2L 2 +#define DA7218_DMIX_SRC_INFILT2R 3 +#define DA7218_DMIX_SRC_TONEGEN 4 +#define DA7218_DMIX_SRC_DAIL 5 +#define DA7218_DMIX_SRC_DAIR 6 + +/* DA7218_DMIX_OUTDAI_1L_INFILT_1L_GAIN = 0x5D */ +#define DA7218_OUTDAI_1L_INFILT_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1L_INFILT_1L_GAIN_MASK (0x1F << 0) +#define DA7218_DMIX_GAIN_MAX 0x1F + +/* DA7218_DMIX_OUTDAI_1L_INFILT_1R_GAIN = 0x5E */ +#define DA7218_OUTDAI_1L_INFILT_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1L_INFILT_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1L_INFILT_2L_GAIN = 0x5F */ +#define DA7218_OUTDAI_1L_INFILT_2L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1L_INFILT_2L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1L_INFILT_2R_GAIN = 0x60 */ +#define DA7218_OUTDAI_1L_INFILT_2R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1L_INFILT_2R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1L_TONEGEN_GAIN = 0x61 */ +#define DA7218_OUTDAI_1L_TONEGEN_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1L_TONEGEN_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1L_INDAI_1L_GAIN = 0x62 */ +#define DA7218_OUTDAI_1L_INDAI_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1L_INDAI_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1L_INDAI_1R_GAIN = 0x63 */ +#define DA7218_OUTDAI_1L_INDAI_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1L_INDAI_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DROUTING_OUTDAI_1R = 0x64 */ +#define DA7218_OUTDAI_1R_SRC_SHIFT 0 +#define DA7218_OUTDAI_1R_SRC_MASK (0x7F << 0) + +/* DA7218_DMIX_OUTDAI_1R_INFILT_1L_GAIN = 0x65 */ +#define DA7218_OUTDAI_1R_INFILT_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1R_INFILT_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1R_INFILT_1R_GAIN = 0x66 */ +#define DA7218_OUTDAI_1R_INFILT_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1R_INFILT_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1R_INFILT_2L_GAIN = 0x67 */ +#define DA7218_OUTDAI_1R_INFILT_2L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1R_INFILT_2L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1R_INFILT_2R_GAIN = 0x68 */ +#define DA7218_OUTDAI_1R_INFILT_2R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1R_INFILT_2R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1R_TONEGEN_GAIN = 0x69 */ +#define DA7218_OUTDAI_1R_TONEGEN_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1R_TONEGEN_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1R_INDAI_1L_GAIN = 0x6A */ +#define DA7218_OUTDAI_1R_INDAI_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1R_INDAI_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_1R_INDAI_1R_GAIN = 0x6B */ +#define DA7218_OUTDAI_1R_INDAI_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_1R_INDAI_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DROUTING_OUTFILT_1L = 0x6C */ +#define DA7218_OUTFILT_1L_SRC_SHIFT 0 +#define DA7218_OUTFILT_1L_SRC_MASK (0x7F << 0) + +/* DA7218_DMIX_OUTFILT_1L_INFILT_1L_GAIN = 0x6D */ +#define DA7218_OUTFILT_1L_INFILT_1L_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1L_INFILT_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1L_INFILT_1R_GAIN = 0x6E */ +#define DA7218_OUTFILT_1L_INFILT_1R_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1L_INFILT_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1L_INFILT_2L_GAIN = 0x6F */ +#define DA7218_OUTFILT_1L_INFILT_2L_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1L_INFILT_2L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1L_INFILT_2R_GAIN = 0x70 */ +#define DA7218_OUTFILT_1L_INFILT_2R_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1L_INFILT_2R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1L_TONEGEN_GAIN = 0x71 */ +#define DA7218_OUTFILT_1L_TONEGEN_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1L_TONEGEN_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1L_INDAI_1L_GAIN = 0x72 */ +#define DA7218_OUTFILT_1L_INDAI_1L_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1L_INDAI_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1L_INDAI_1R_GAIN = 0x73 */ +#define DA7218_OUTFILT_1L_INDAI_1R_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1L_INDAI_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DROUTING_OUTFILT_1R = 0x74 */ +#define DA7218_OUTFILT_1R_SRC_SHIFT 0 +#define DA7218_OUTFILT_1R_SRC_MASK (0x7F << 0) + +/* DA7218_DMIX_OUTFILT_1R_INFILT_1L_GAIN = 0x75 */ +#define DA7218_OUTFILT_1R_INFILT_1L_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1R_INFILT_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1R_INFILT_1R_GAIN = 0x76 */ +#define DA7218_OUTFILT_1R_INFILT_1R_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1R_INFILT_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1R_INFILT_2L_GAIN = 0x77 */ +#define DA7218_OUTFILT_1R_INFILT_2L_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1R_INFILT_2L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1R_INFILT_2R_GAIN = 0x78 */ +#define DA7218_OUTFILT_1R_INFILT_2R_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1R_INFILT_2R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1R_TONEGEN_GAIN = 0x79 */ +#define DA7218_OUTFILT_1R_TONEGEN_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1R_TONEGEN_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1R_INDAI_1L_GAIN = 0x7A */ +#define DA7218_OUTFILT_1R_INDAI_1L_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1R_INDAI_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTFILT_1R_INDAI_1R_GAIN = 0x7B */ +#define DA7218_OUTFILT_1R_INDAI_1R_GAIN_SHIFT 0 +#define DA7218_OUTFILT_1R_INDAI_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DROUTING_OUTDAI_2L = 0x7C */ +#define DA7218_OUTDAI_2L_SRC_SHIFT 0 +#define DA7218_OUTDAI_2L_SRC_MASK (0x7F << 0) + +/* DA7218_DMIX_OUTDAI_2L_INFILT_1L_GAIN = 0x7D */ +#define DA7218_OUTDAI_2L_INFILT_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2L_INFILT_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2L_INFILT_1R_GAIN = 0x7E */ +#define DA7218_OUTDAI_2L_INFILT_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2L_INFILT_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2L_INFILT_2L_GAIN = 0x7F */ +#define DA7218_OUTDAI_2L_INFILT_2L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2L_INFILT_2L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2L_INFILT_2R_GAIN = 0x80 */ +#define DA7218_OUTDAI_2L_INFILT_2R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2L_INFILT_2R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2L_TONEGEN_GAIN = 0x81 */ +#define DA7218_OUTDAI_2L_TONEGEN_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2L_TONEGEN_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2L_INDAI_1L_GAIN = 0x82 */ +#define DA7218_OUTDAI_2L_INDAI_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2L_INDAI_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2L_INDAI_1R_GAIN = 0x83 */ +#define DA7218_OUTDAI_2L_INDAI_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2L_INDAI_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DROUTING_OUTDAI_2R = 0x84 */ +#define DA7218_OUTDAI_2R_SRC_SHIFT 0 +#define DA7218_OUTDAI_2R_SRC_MASK (0x7F << 0) + +/* DA7218_DMIX_OUTDAI_2R_INFILT_1L_GAIN = 0x85 */ +#define DA7218_OUTDAI_2R_INFILT_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2R_INFILT_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2R_INFILT_1R_GAIN = 0x86 */ +#define DA7218_OUTDAI_2R_INFILT_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2R_INFILT_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2R_INFILT_2L_GAIN = 0x87 */ +#define DA7218_OUTDAI_2R_INFILT_2L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2R_INFILT_2L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2R_INFILT_2R_GAIN = 0x88 */ +#define DA7218_OUTDAI_2R_INFILT_2R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2R_INFILT_2R_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2R_TONEGEN_GAIN = 0x89 */ +#define DA7218_OUTDAI_2R_TONEGEN_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2R_TONEGEN_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2R_INDAI_1L_GAIN = 0x8A */ +#define DA7218_OUTDAI_2R_INDAI_1L_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2R_INDAI_1L_GAIN_MASK (0x1F << 0) + +/* DA7218_DMIX_OUTDAI_2R_INDAI_1R_GAIN = 0x8B */ +#define DA7218_OUTDAI_2R_INDAI_1R_GAIN_SHIFT 0 +#define DA7218_OUTDAI_2R_INDAI_1R_GAIN_MASK (0x1F << 0) + +/* DA7218_DAI_CTRL = 0x8C */ +#define DA7218_DAI_FORMAT_SHIFT 0 +#define DA7218_DAI_FORMAT_MASK (0x3 << 0) +#define DA7218_DAI_FORMAT_I2S (0x0 << 0) +#define DA7218_DAI_FORMAT_LEFT_J (0x1 << 0) +#define DA7218_DAI_FORMAT_RIGHT_J (0x2 << 0) +#define DA7218_DAI_FORMAT_DSP (0x3 << 0) +#define DA7218_DAI_WORD_LENGTH_SHIFT 2 +#define DA7218_DAI_WORD_LENGTH_MASK (0x3 << 2) +#define DA7218_DAI_WORD_LENGTH_S16_LE (0x0 << 2) +#define DA7218_DAI_WORD_LENGTH_S20_LE (0x1 << 2) +#define DA7218_DAI_WORD_LENGTH_S24_LE (0x2 << 2) +#define DA7218_DAI_WORD_LENGTH_S32_LE (0x3 << 2) +#define DA7218_DAI_CH_NUM_SHIFT 4 +#define DA7218_DAI_CH_NUM_MASK (0x7 << 4) +#define DA7218_DAI_CH_NUM_MAX 4 +#define DA7218_DAI_EN_SHIFT 7 +#define DA7218_DAI_EN_MASK (0x1 << 7) + +/* DA7218_DAI_TDM_CTRL = 0x8D */ +#define DA7218_DAI_TDM_CH_EN_SHIFT 0 +#define DA7218_DAI_TDM_CH_EN_MASK (0xF << 0) +#define DA7218_DAI_TDM_MAX_SLOTS 4 +#define DA7218_DAI_OE_SHIFT 6 +#define DA7218_DAI_OE_MASK (0x1 << 6) +#define DA7218_DAI_TDM_MODE_EN_SHIFT 7 +#define DA7218_DAI_TDM_MODE_EN_MASK (0x1 << 7) + +/* DA7218_DAI_OFFSET_LOWER = 0x8E */ +#define DA7218_DAI_OFFSET_LOWER_SHIFT 0 +#define DA7218_DAI_OFFSET_LOWER_MASK (0xFF << 0) + +/* DA7218_DAI_OFFSET_UPPER = 0x8F */ +#define DA7218_DAI_OFFSET_UPPER_SHIFT 0 +#define DA7218_DAI_OFFSET_UPPER_MASK (0x7 << 0) + +/* DA7218_DAI_CLK_MODE = 0x90 */ +#define DA7218_DAI_BCLKS_PER_WCLK_SHIFT 0 +#define DA7218_DAI_BCLKS_PER_WCLK_MASK (0x3 << 0) +#define DA7218_DAI_BCLKS_PER_WCLK_32 (0x0 << 0) +#define DA7218_DAI_BCLKS_PER_WCLK_64 (0x1 << 0) +#define DA7218_DAI_BCLKS_PER_WCLK_128 (0x2 << 0) +#define DA7218_DAI_BCLKS_PER_WCLK_256 (0x3 << 0) +#define DA7218_DAI_CLK_POL_SHIFT 2 +#define DA7218_DAI_CLK_POL_MASK (0x1 << 2) +#define DA7218_DAI_CLK_POL_INV (0x1 << 2) +#define DA7218_DAI_WCLK_POL_SHIFT 3 +#define DA7218_DAI_WCLK_POL_MASK (0x1 << 3) +#define DA7218_DAI_WCLK_POL_INV (0x1 << 3) +#define DA7218_DAI_WCLK_TRI_STATE_SHIFT 4 +#define DA7218_DAI_WCLK_TRI_STATE_MASK (0x1 << 4) +#define DA7218_DAI_CLK_EN_SHIFT 7 +#define DA7218_DAI_CLK_EN_MASK (0x1 << 7) + +/* DA7218_PLL_CTRL = 0x91 */ +#define DA7218_PLL_INDIV_SHIFT 0 +#define DA7218_PLL_INDIV_MASK (0x7 << 0) +#define DA7218_PLL_INDIV_2_TO_4_5_MHZ (0x0 << 0) +#define DA7218_PLL_INDIV_4_5_TO_9_MHZ (0x1 << 0) +#define DA7218_PLL_INDIV_9_TO_18_MHZ (0x2 << 0) +#define DA7218_PLL_INDIV_18_TO_36_MHZ (0x3 << 0) +#define DA7218_PLL_INDIV_36_TO_54_MHZ (0x4 << 0) +#define DA7218_PLL_MCLK_SQR_EN_SHIFT 4 +#define DA7218_PLL_MCLK_SQR_EN_MASK (0x1 << 4) +#define DA7218_PLL_MODE_SHIFT 6 +#define DA7218_PLL_MODE_MASK (0x3 << 6) +#define DA7218_PLL_MODE_BYPASS (0x0 << 6) +#define DA7218_PLL_MODE_NORMAL (0x1 << 6) +#define DA7218_PLL_MODE_SRM (0x2 << 6) + +/* DA7218_PLL_FRAC_TOP = 0x92 */ +#define DA7218_PLL_FBDIV_FRAC_TOP_SHIFT 0 +#define DA7218_PLL_FBDIV_FRAC_TOP_MASK (0x1F << 0) + +/* DA7218_PLL_FRAC_BOT = 0x93 */ +#define DA7218_PLL_FBDIV_FRAC_BOT_SHIFT 0 +#define DA7218_PLL_FBDIV_FRAC_BOT_MASK (0xFF << 0) + +/* DA7218_PLL_INTEGER = 0x94 */ +#define DA7218_PLL_FBDIV_INTEGER_SHIFT 0 +#define DA7218_PLL_FBDIV_INTEGER_MASK (0x7F << 0) + +/* DA7218_PLL_STATUS = 0x95 */ +#define DA7218_PLL_SRM_STATUS_SHIFT 0 +#define DA7218_PLL_SRM_STATUS_MASK (0xFF << 0) +#define DA7218_PLL_SRM_STATUS_SRM_LOCK (0x1 << 7) + +/* DA7218_PLL_REFOSC_CAL = 0x98 */ +#define DA7218_PLL_REFOSC_CAL_CTRL_SHIFT 0 +#define DA7218_PLL_REFOSC_CAL_CTRL_MASK (0x1F << 0) +#define DA7218_PLL_REFOSC_CAL_START_SHIFT 6 +#define DA7218_PLL_REFOSC_CAL_START_MASK (0x1 << 6) +#define DA7218_PLL_REFOSC_CAL_EN_SHIFT 7 +#define DA7218_PLL_REFOSC_CAL_EN_MASK (0x1 << 7) + +/* DA7218_DAC_NG_CTRL = 0x9C */ +#define DA7218_DAC_NG_EN_SHIFT 7 +#define DA7218_DAC_NG_EN_MASK (0x1 << 7) + +/* DA7218_DAC_NG_SETUP_TIME = 0x9D */ +#define DA7218_DAC_NG_SETUP_TIME_SHIFT 0 +#define DA7218_DAC_NG_SETUP_TIME_MASK (0x3 << 0) +#define DA7218_DAC_NG_SETUP_TIME_MAX 4 +#define DA7218_DAC_NG_RAMPUP_RATE_SHIFT 2 +#define DA7218_DAC_NG_RAMPUP_RATE_MASK (0x1 << 2) +#define DA7218_DAC_NG_RAMPUP_RATE_MAX 2 +#define DA7218_DAC_NG_RAMPDN_RATE_SHIFT 3 +#define DA7218_DAC_NG_RAMPDN_RATE_MASK (0x1 << 3) +#define DA7218_DAC_NG_RAMPDN_RATE_MAX 2 + +/* DA7218_DAC_NG_OFF_THRESH = 0x9E */ +#define DA7218_DAC_NG_OFF_THRESHOLD_SHIFT 0 +#define DA7218_DAC_NG_OFF_THRESHOLD_MASK (0x7 << 0) +#define DA7218_DAC_NG_THRESHOLD_MAX 0x7 + +/* DA7218_DAC_NG_ON_THRESH = 0x9F */ +#define DA7218_DAC_NG_ON_THRESHOLD_SHIFT 0 +#define DA7218_DAC_NG_ON_THRESHOLD_MASK (0x7 << 0) + +/* DA7218_TONE_GEN_CFG1 = 0xA0 */ +#define DA7218_DTMF_REG_SHIFT 0 +#define DA7218_DTMF_REG_MASK (0xF << 0) +#define DA7218_DTMF_REG_MAX 16 +#define DA7218_DTMF_EN_SHIFT 4 +#define DA7218_DTMF_EN_MASK (0x1 << 4) +#define DA7218_START_STOPN_SHIFT 7 +#define DA7218_START_STOPN_MASK (0x1 << 7) + +/* DA7218_TONE_GEN_CFG2 = 0xA1 */ +#define DA7218_SWG_SEL_SHIFT 0 +#define DA7218_SWG_SEL_MASK (0x3 << 0) +#define DA7218_SWG_SEL_MAX 4 + +/* DA7218_TONE_GEN_FREQ1_L = 0xA2 */ +#define DA7218_FREQ1_L_SHIFT 0 +#define DA7218_FREQ1_L_MASK (0xFF << 0) +#define DA7218_FREQ_MAX 0xFFFF + +/* DA7218_TONE_GEN_FREQ1_U = 0xA3 */ +#define DA7218_FREQ1_U_SHIFT 0 +#define DA7218_FREQ1_U_MASK (0xFF << 0) + +/* DA7218_TONE_GEN_FREQ2_L = 0xA4 */ +#define DA7218_FREQ2_L_SHIFT 0 +#define DA7218_FREQ2_L_MASK (0xFF << 0) + +/* DA7218_TONE_GEN_FREQ2_U = 0xA5 */ +#define DA7218_FREQ2_U_SHIFT 0 +#define DA7218_FREQ2_U_MASK (0xFF << 0) + +/* DA7218_TONE_GEN_CYCLES = 0xA6 */ +#define DA7218_BEEP_CYCLES_SHIFT 0 +#define DA7218_BEEP_CYCLES_MASK (0x7 << 0) + +/* DA7218_TONE_GEN_ON_PER = 0xA7 */ +#define DA7218_BEEP_ON_PER_SHIFT 0 +#define DA7218_BEEP_ON_PER_MASK (0x3F << 0) + +/* DA7218_TONE_GEN_OFF_PER = 0xA8 */ +#define DA7218_BEEP_OFF_PER_SHIFT 0 +#define DA7218_BEEP_OFF_PER_MASK (0x3F << 0) +#define DA7218_BEEP_ON_OFF_MAX 0x3F + +/* DA7218_CP_CTRL = 0xAC */ +#define DA7218_CP_MOD_SHIFT 2 +#define DA7218_CP_MOD_MASK (0x3 << 2) +#define DA7218_CP_MCHANGE_SHIFT 4 +#define DA7218_CP_MCHANGE_MASK (0x3 << 4) +#define DA7218_CP_MCHANGE_REL_MASK 0x3 +#define DA7218_CP_MCHANGE_MAX 3 +#define DA7218_CP_MCHANGE_LARGEST_VOL 0x1 +#define DA7218_CP_MCHANGE_DAC_VOL 0x2 +#define DA7218_CP_MCHANGE_SIG_MAG 0x3 +#define DA7218_CP_SMALL_SWITCH_FREQ_EN_SHIFT 6 +#define DA7218_CP_SMALL_SWITCH_FREQ_EN_MASK (0x1 << 6) +#define DA7218_CP_EN_SHIFT 7 +#define DA7218_CP_EN_MASK (0x1 << 7) + +/* DA7218_CP_DELAY = 0xAD */ +#define DA7218_CP_FCONTROL_SHIFT 0 +#define DA7218_CP_FCONTROL_MASK (0x7 << 0) +#define DA7218_CP_FCONTROL_MAX 6 +#define DA7218_CP_TAU_DELAY_SHIFT 3 +#define DA7218_CP_TAU_DELAY_MASK (0x7 << 3) +#define DA7218_CP_TAU_DELAY_MAX 8 + +/* DA7218_CP_VOL_THRESHOLD1 = 0xAE */ +#define DA7218_CP_THRESH_VDD2_SHIFT 0 +#define DA7218_CP_THRESH_VDD2_MASK (0x3F << 0) +#define DA7218_CP_THRESH_VDD2_MAX 0x3F + +/* DA7218_MIC_1_CTRL = 0xB4 */ +#define DA7218_MIC_1_AMP_MUTE_EN_SHIFT 6 +#define DA7218_MIC_1_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7218_MIC_1_AMP_EN_SHIFT 7 +#define DA7218_MIC_1_AMP_EN_MASK (0x1 << 7) + +/* DA7218_MIC_1_GAIN = 0xB5 */ +#define DA7218_MIC_1_AMP_GAIN_SHIFT 0 +#define DA7218_MIC_1_AMP_GAIN_MASK (0x7 << 0) +#define DA7218_MIC_AMP_GAIN_MAX 0x7 + +/* DA7218_MIC_1_SELECT = 0xB7 */ +#define DA7218_MIC_1_AMP_IN_SEL_SHIFT 0 +#define DA7218_MIC_1_AMP_IN_SEL_MASK (0x3 << 0) + +/* DA7218_MIC_2_CTRL = 0xB8 */ +#define DA7218_MIC_2_AMP_MUTE_EN_SHIFT 6 +#define DA7218_MIC_2_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7218_MIC_2_AMP_EN_SHIFT 7 +#define DA7218_MIC_2_AMP_EN_MASK (0x1 << 7) + +/* DA7218_MIC_2_GAIN = 0xB9 */ +#define DA7218_MIC_2_AMP_GAIN_SHIFT 0 +#define DA7218_MIC_2_AMP_GAIN_MASK (0x7 << 0) + +/* DA7218_MIC_2_SELECT = 0xBB */ +#define DA7218_MIC_2_AMP_IN_SEL_SHIFT 0 +#define DA7218_MIC_2_AMP_IN_SEL_MASK (0x3 << 0) + +/* DA7218_IN_1_HPF_FILTER_CTRL = 0xBC */ +#define DA7218_IN_1_VOICE_HPF_CORNER_SHIFT 0 +#define DA7218_IN_1_VOICE_HPF_CORNER_MASK (0x7 << 0) +#define DA7218_IN_VOICE_HPF_CORNER_MAX 8 +#define DA7218_IN_1_VOICE_EN_SHIFT 3 +#define DA7218_IN_1_VOICE_EN_MASK (0x1 << 3) +#define DA7218_IN_1_AUDIO_HPF_CORNER_SHIFT 4 +#define DA7218_IN_1_AUDIO_HPF_CORNER_MASK (0x3 << 4) +#define DA7218_IN_1_HPF_EN_SHIFT 7 +#define DA7218_IN_1_HPF_EN_MASK (0x1 << 7) + +/* DA7218_IN_2_HPF_FILTER_CTRL = 0xBD */ +#define DA7218_IN_2_VOICE_HPF_CORNER_SHIFT 0 +#define DA7218_IN_2_VOICE_HPF_CORNER_MASK (0x7 << 0) +#define DA7218_IN_2_VOICE_EN_SHIFT 3 +#define DA7218_IN_2_VOICE_EN_MASK (0x1 << 3) +#define DA7218_IN_2_AUDIO_HPF_CORNER_SHIFT 4 +#define DA7218_IN_2_AUDIO_HPF_CORNER_MASK (0x3 << 4) +#define DA7218_IN_2_HPF_EN_SHIFT 7 +#define DA7218_IN_2_HPF_EN_MASK (0x1 << 7) + +/* DA7218_ADC_1_CTRL = 0xC0 */ +#define DA7218_ADC_1_AAF_EN_SHIFT 2 +#define DA7218_ADC_1_AAF_EN_MASK (0x1 << 2) + +/* DA7218_ADC_2_CTRL = 0xC1 */ +#define DA7218_ADC_2_AAF_EN_SHIFT 2 +#define DA7218_ADC_2_AAF_EN_MASK (0x1 << 2) + +/* DA7218_ADC_MODE = 0xC2 */ +#define DA7218_ADC_LP_MODE_SHIFT 0 +#define DA7218_ADC_LP_MODE_MASK (0x1 << 0) +#define DA7218_ADC_LVLDET_MODE_SHIFT 1 +#define DA7218_ADC_LVLDET_MODE_MASK (0x1 << 1) +#define DA7218_ADC_LVLDET_AUTO_EXIT_SHIFT 2 +#define DA7218_ADC_LVLDET_AUTO_EXIT_MASK (0x1 << 2) + +/* DA7218_MIXOUT_L_CTRL = 0xCC */ +#define DA7218_MIXOUT_L_AMP_EN_SHIFT 7 +#define DA7218_MIXOUT_L_AMP_EN_MASK (0x1 << 7) + +/* DA7218_MIXOUT_L_GAIN = 0xCD */ +#define DA7218_MIXOUT_L_AMP_GAIN_SHIFT 0 +#define DA7218_MIXOUT_L_AMP_GAIN_MASK (0x3 << 0) +#define DA7218_MIXOUT_AMP_GAIN_MIN 0x1 +#define DA7218_MIXOUT_AMP_GAIN_MAX 0x3 + +/* DA7218_MIXOUT_R_CTRL = 0xCE */ +#define DA7218_MIXOUT_R_AMP_EN_SHIFT 7 +#define DA7218_MIXOUT_R_AMP_EN_MASK (0x1 << 7) + +/* DA7218_MIXOUT_R_GAIN = 0xCF */ +#define DA7218_MIXOUT_R_AMP_GAIN_SHIFT 0 +#define DA7218_MIXOUT_R_AMP_GAIN_MASK (0x3 << 0) + +/* DA7218_HP_L_CTRL = 0xD0 */ +#define DA7218_HP_L_AMP_MIN_GAIN_EN_SHIFT 2 +#define DA7218_HP_L_AMP_MIN_GAIN_EN_MASK (0x1 << 2) +#define DA7218_HP_L_AMP_OE_SHIFT 3 +#define DA7218_HP_L_AMP_OE_MASK (0x1 << 3) +#define DA7218_HP_L_AMP_ZC_EN_SHIFT 4 +#define DA7218_HP_L_AMP_ZC_EN_MASK (0x1 << 4) +#define DA7218_HP_L_AMP_RAMP_EN_SHIFT 5 +#define DA7218_HP_L_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7218_HP_L_AMP_MUTE_EN_SHIFT 6 +#define DA7218_HP_L_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7218_HP_L_AMP_EN_SHIFT 7 +#define DA7218_HP_L_AMP_EN_MASK (0x1 << 7) +#define DA7218_HP_AMP_OE_MASK (0x1 << 3) + +/* DA7218_HP_L_GAIN = 0xD1 */ +#define DA7218_HP_L_AMP_GAIN_SHIFT 0 +#define DA7218_HP_L_AMP_GAIN_MASK (0x3F << 0) +#define DA7218_HP_AMP_GAIN_MIN 0x15 +#define DA7218_HP_AMP_GAIN_MAX 0x3F + +/* DA7218_HP_R_CTRL = 0xD2 */ +#define DA7218_HP_R_AMP_MIN_GAIN_EN_SHIFT 2 +#define DA7218_HP_R_AMP_MIN_GAIN_EN_MASK (0x1 << 2) +#define DA7218_HP_R_AMP_OE_SHIFT 3 +#define DA7218_HP_R_AMP_OE_MASK (0x1 << 3) +#define DA7218_HP_R_AMP_ZC_EN_SHIFT 4 +#define DA7218_HP_R_AMP_ZC_EN_MASK (0x1 << 4) +#define DA7218_HP_R_AMP_RAMP_EN_SHIFT 5 +#define DA7218_HP_R_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7218_HP_R_AMP_MUTE_EN_SHIFT 6 +#define DA7218_HP_R_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7218_HP_R_AMP_EN_SHIFT 7 +#define DA7218_HP_R_AMP_EN_MASK (0x1 << 7) + +/* DA7218_HP_R_GAIN = 0xD3 */ +#define DA7218_HP_R_AMP_GAIN_SHIFT 0 +#define DA7218_HP_R_AMP_GAIN_MASK (0x3F << 0) + +/* DA7218_HP_SNGL_CTRL = 0xD4 */ +#define DA7218_HP_AMP_STEREO_DETECT_STATUS_SHIFT 0 +#define DA7218_HP_AMP_STEREO_DETECT_STATUS_MASK (0x1 << 0) +#define DA7218_HPL_AMP_LOAD_DETECT_STATUS_SHIFT 1 +#define DA7218_HPL_AMP_LOAD_DETECT_STATUS_MASK (0x1 << 1) +#define DA7218_HPR_AMP_LOAD_DETECT_STATUS_SHIFT 2 +#define DA7218_HPR_AMP_LOAD_DETECT_STATUS_MASK (0x1 << 2) +#define DA7218_HP_AMP_LOAD_DETECT_EN_SHIFT 6 +#define DA7218_HP_AMP_LOAD_DETECT_EN_MASK (0x1 << 6) +#define DA7218_HP_AMP_STEREO_DETECT_EN_SHIFT 7 +#define DA7218_HP_AMP_STEREO_DETECT_EN_MASK (0x1 << 7) + +/* DA7218_HP_DIFF_CTRL = 0xD5 */ +#define DA7218_HP_AMP_DIFF_MODE_EN_SHIFT 0 +#define DA7218_HP_AMP_DIFF_MODE_EN_MASK (0x1 << 0) +#define DA7218_HP_AMP_SINGLE_SUPPLY_EN_SHIFT 4 +#define DA7218_HP_AMP_SINGLE_SUPPLY_EN_MASK (0x1 << 4) + +/* DA7218_HP_DIFF_UNLOCK = 0xD7 */ +#define DA7218_HP_DIFF_UNLOCK_SHIFT 0 +#define DA7218_HP_DIFF_UNLOCK_MASK (0x1 << 0) +#define DA7218_HP_DIFF_UNLOCK_VAL 0xC3 + +/* DA7218_HPLDET_JACK = 0xD8 */ +#define DA7218_HPLDET_JACK_RATE_SHIFT 0 +#define DA7218_HPLDET_JACK_RATE_MASK (0x7 << 0) +#define DA7218_HPLDET_JACK_DEBOUNCE_SHIFT 3 +#define DA7218_HPLDET_JACK_DEBOUNCE_MASK (0x3 << 3) +#define DA7218_HPLDET_JACK_THR_SHIFT 5 +#define DA7218_HPLDET_JACK_THR_MASK (0x3 << 5) +#define DA7218_HPLDET_JACK_EN_SHIFT 7 +#define DA7218_HPLDET_JACK_EN_MASK (0x1 << 7) + +/* DA7218_HPLDET_CTRL = 0xD9 */ +#define DA7218_HPLDET_COMP_INV_SHIFT 0 +#define DA7218_HPLDET_COMP_INV_MASK (0x1 << 0) +#define DA7218_HPLDET_HYST_EN_SHIFT 1 +#define DA7218_HPLDET_HYST_EN_MASK (0x1 << 1) +#define DA7218_HPLDET_DISCHARGE_EN_SHIFT 7 +#define DA7218_HPLDET_DISCHARGE_EN_MASK (0x1 << 7) + +/* DA7218_HPLDET_TEST = 0xDA */ +#define DA7218_HPLDET_COMP_STS_SHIFT 4 +#define DA7218_HPLDET_COMP_STS_MASK (0x1 << 4) + +/* DA7218_REFERENCES = 0xDC */ +#define DA7218_BIAS_EN_SHIFT 3 +#define DA7218_BIAS_EN_MASK (0x1 << 3) + +/* DA7218_IO_CTRL = 0xE0 */ +#define DA7218_IO_VOLTAGE_LEVEL_SHIFT 0 +#define DA7218_IO_VOLTAGE_LEVEL_MASK (0x1 << 0) +#define DA7218_IO_VOLTAGE_LEVEL_2_5V_3_6V 0 +#define DA7218_IO_VOLTAGE_LEVEL_1_5V_2_5V 1 + +/* DA7218_LDO_CTRL = 0xE1 */ +#define DA7218_LDO_LEVEL_SELECT_SHIFT 4 +#define DA7218_LDO_LEVEL_SELECT_MASK (0x3 << 4) +#define DA7218_LDO_EN_SHIFT 7 +#define DA7218_LDO_EN_MASK (0x1 << 7) + +/* DA7218_SIDETONE_CTRL = 0xE4 */ +#define DA7218_SIDETONE_MUTE_EN_SHIFT 6 +#define DA7218_SIDETONE_MUTE_EN_MASK (0x1 << 6) +#define DA7218_SIDETONE_FILTER_EN_SHIFT 7 +#define DA7218_SIDETONE_FILTER_EN_MASK (0x1 << 7) + +/* DA7218_SIDETONE_IN_SELECT = 0xE5 */ +#define DA7218_SIDETONE_IN_SELECT_SHIFT 0 +#define DA7218_SIDETONE_IN_SELECT_MASK (0x3 << 0) +#define DA7218_SIDETONE_IN_SELECT_MAX 4 + +/* DA7218_SIDETONE_GAIN = 0xE6 */ +#define DA7218_SIDETONE_GAIN_SHIFT 0 +#define DA7218_SIDETONE_GAIN_MASK (0x1F << 0) + +/* DA7218_DROUTING_ST_OUTFILT_1L = 0xE8 */ +#define DA7218_OUTFILT_ST_1L_SRC_SHIFT 0 +#define DA7218_OUTFILT_ST_1L_SRC_MASK (0x7 << 0) +#define DA7218_DMIX_ST_SRC_OUTFILT1L 0 +#define DA7218_DMIX_ST_SRC_OUTFILT1R 1 +#define DA7218_DMIX_ST_SRC_SIDETONE 2 + +/* DA7218_DROUTING_ST_OUTFILT_1R = 0xE9 */ +#define DA7218_OUTFILT_ST_1R_SRC_SHIFT 0 +#define DA7218_OUTFILT_ST_1R_SRC_MASK (0x7 << 0) + +/* DA7218_SIDETONE_BIQ_3STAGE_DATA = 0xEA */ +#define DA7218_SIDETONE_BIQ_3STAGE_DATA_SHIFT 0 +#define DA7218_SIDETONE_BIQ_3STAGE_DATA_MASK (0xFF << 0) + +/* DA7218_SIDETONE_BIQ_3STAGE_ADDR = 0xEB */ +#define DA7218_SIDETONE_BIQ_3STAGE_ADDR_SHIFT 0 +#define DA7218_SIDETONE_BIQ_3STAGE_ADDR_MASK (0x1F << 0) +#define DA7218_SIDETONE_BIQ_3STAGE_CFG_SIZE 30 + +/* DA7218_EVENT_STATUS = 0xEC */ +#define DA7218_HPLDET_JACK_STS_SHIFT 7 +#define DA7218_HPLDET_JACK_STS_MASK (0x1 << 7) + +/* DA7218_EVENT = 0xED */ +#define DA7218_LVL_DET_EVENT_SHIFT 0 +#define DA7218_LVL_DET_EVENT_MASK (0x1 << 0) +#define DA7218_HPLDET_JACK_EVENT_SHIFT 7 +#define DA7218_HPLDET_JACK_EVENT_MASK (0x1 << 7) + +/* DA7218_EVENT_MASK = 0xEE */ +#define DA7218_LVL_DET_EVENT_MSK_SHIFT 0 +#define DA7218_LVL_DET_EVENT_MSK_MASK (0x1 << 0) +#define DA7218_HPLDET_JACK_EVENT_IRQ_MSK_SHIFT 7 +#define DA7218_HPLDET_JACK_EVENT_IRQ_MSK_MASK (0x1 << 7) + +/* DA7218_DMIC_1_CTRL = 0xF0 */ +#define DA7218_DMIC_1_DATA_SEL_SHIFT 0 +#define DA7218_DMIC_1_DATA_SEL_MASK (0x1 << 0) +#define DA7218_DMIC_1_SAMPLEPHASE_SHIFT 1 +#define DA7218_DMIC_1_SAMPLEPHASE_MASK (0x1 << 1) +#define DA7218_DMIC_1_CLK_RATE_SHIFT 2 +#define DA7218_DMIC_1_CLK_RATE_MASK (0x1 << 2) +#define DA7218_DMIC_1L_EN_SHIFT 6 +#define DA7218_DMIC_1L_EN_MASK (0x1 << 6) +#define DA7218_DMIC_1R_EN_SHIFT 7 +#define DA7218_DMIC_1R_EN_MASK (0x1 << 7) + +/* DA7218_DMIC_2_CTRL = 0xF1 */ +#define DA7218_DMIC_2_DATA_SEL_SHIFT 0 +#define DA7218_DMIC_2_DATA_SEL_MASK (0x1 << 0) +#define DA7218_DMIC_2_SAMPLEPHASE_SHIFT 1 +#define DA7218_DMIC_2_SAMPLEPHASE_MASK (0x1 << 1) +#define DA7218_DMIC_2_CLK_RATE_SHIFT 2 +#define DA7218_DMIC_2_CLK_RATE_MASK (0x1 << 2) +#define DA7218_DMIC_2L_EN_SHIFT 6 +#define DA7218_DMIC_2L_EN_MASK (0x1 << 6) +#define DA7218_DMIC_2R_EN_SHIFT 7 +#define DA7218_DMIC_2R_EN_MASK (0x1 << 7) + +/* DA7218_IN_1L_GAIN = 0xF4 */ +#define DA7218_IN_1L_DIGITAL_GAIN_SHIFT 0 +#define DA7218_IN_1L_DIGITAL_GAIN_MASK (0x7F << 0) +#define DA7218_IN_DIGITAL_GAIN_MAX 0x7F + +/* DA7218_IN_1R_GAIN = 0xF5 */ +#define DA7218_IN_1R_DIGITAL_GAIN_SHIFT 0 +#define DA7218_IN_1R_DIGITAL_GAIN_MASK (0x7F << 0) + +/* DA7218_IN_2L_GAIN = 0xF6 */ +#define DA7218_IN_2L_DIGITAL_GAIN_SHIFT 0 +#define DA7218_IN_2L_DIGITAL_GAIN_MASK (0x7F << 0) + +/* DA7218_IN_2R_GAIN = 0xF7 */ +#define DA7218_IN_2R_DIGITAL_GAIN_SHIFT 0 +#define DA7218_IN_2R_DIGITAL_GAIN_MASK (0x7F << 0) + +/* DA7218_OUT_1L_GAIN = 0xF8 */ +#define DA7218_OUT_1L_DIGITAL_GAIN_SHIFT 0 +#define DA7218_OUT_1L_DIGITAL_GAIN_MASK (0xFF << 0) +#define DA7218_OUT_DIGITAL_GAIN_MIN 0x0 +#define DA7218_OUT_DIGITAL_GAIN_MAX 0x97 + +/* DA7218_OUT_1R_GAIN = 0xF9 */ +#define DA7218_OUT_1R_DIGITAL_GAIN_SHIFT 0 +#define DA7218_OUT_1R_DIGITAL_GAIN_MASK (0xFF << 0) + +/* DA7218_MICBIAS_CTRL = 0xFC */ +#define DA7218_MICBIAS_1_LEVEL_SHIFT 0 +#define DA7218_MICBIAS_1_LEVEL_MASK (0x7 << 0) +#define DA7218_MICBIAS_1_LP_MODE_SHIFT 3 +#define DA7218_MICBIAS_1_LP_MODE_MASK (0x1 << 3) +#define DA7218_MICBIAS_2_LEVEL_SHIFT 4 +#define DA7218_MICBIAS_2_LEVEL_MASK (0x7 << 4) +#define DA7218_MICBIAS_2_LP_MODE_SHIFT 7 +#define DA7218_MICBIAS_2_LP_MODE_MASK (0x1 << 7) + +/* DA7218_MICBIAS_EN = 0xFD */ +#define DA7218_MICBIAS_1_EN_SHIFT 0 +#define DA7218_MICBIAS_1_EN_MASK (0x1 << 0) +#define DA7218_MICBIAS_2_EN_SHIFT 4 +#define DA7218_MICBIAS_2_EN_MASK (0x1 << 4) + + +/* + * General defines & data + */ + +/* Register inversion */ +#define DA7218_NO_INVERT 0 +#define DA7218_INVERT 1 + +/* Byte related defines */ +#define DA7218_BYTE_SHIFT 8 +#define DA7218_BYTE_MASK 0xFF +#define DA7218_2BYTE_SHIFT 16 +#define DA7218_2BYTE_MASK 0xFFFF + +/* PLL Output Frequencies */ +#define DA7218_PLL_FREQ_OUT_90316 90316800 +#define DA7218_PLL_FREQ_OUT_98304 98304000 + +/* PLL Frequency Dividers */ +#define DA7218_PLL_INDIV_2_TO_4_5_MHZ_VAL 1 +#define DA7218_PLL_INDIV_4_5_TO_9_MHZ_VAL 2 +#define DA7218_PLL_INDIV_9_TO_18_MHZ_VAL 4 +#define DA7218_PLL_INDIV_18_TO_36_MHZ_VAL 8 +#define DA7218_PLL_INDIV_36_TO_54_MHZ_VAL 16 + +/* ALC Calibration */ +#define DA7218_ALC_CALIB_DELAY_MIN 2500 +#define DA7218_ALC_CALIB_DELAY_MAX 5000 +#define DA7218_ALC_CALIB_MAX_TRIES 5 + +/* Ref Oscillator */ +#define DA7218_REF_OSC_CHECK_DELAY_MIN 5000 +#define DA7218_REF_OSC_CHECK_DELAY_MAX 10000 +#define DA7218_REF_OSC_CHECK_TRIES 4 + +/* SRM */ +#define DA7218_SRM_CHECK_DELAY 50 +#define DA7218_SRM_CHECK_TRIES 8 + +/* Mic Level Detect */ +#define DA7218_MIC_LVL_DET_DELAY 50 + +enum da7218_biq_cfg { + DA7218_BIQ_CFG_DATA = 0, + DA7218_BIQ_CFG_ADDR, + DA7218_BIQ_CFG_SIZE, +}; + +enum da7218_clk_src { + DA7218_CLKSRC_MCLK = 0, + DA7218_CLKSRC_MCLK_SQR, +}; + +enum da7218_sys_clk { + DA7218_SYSCLK_MCLK = 0, + DA7218_SYSCLK_PLL, + DA7218_SYSCLK_PLL_SRM, +}; + +enum da7218_dev_id { + DA7217_DEV_ID = 0, + DA7218_DEV_ID, +}; + +/* Regulators */ +enum da7218_supplies { + DA7218_SUPPLY_VDD = 0, + DA7218_SUPPLY_VDDMIC, + DA7218_SUPPLY_VDDIO, + DA7218_NUM_SUPPLIES, +}; + +/* Private data */ +struct da7218_priv { + struct da7218_pdata *pdata; + + struct regulator_bulk_data supplies[DA7218_NUM_SUPPLIES]; + struct regmap *regmap; + int dev_id; + + struct snd_soc_jack *jack; + int irq; + + struct clk *mclk; + unsigned int mclk_rate; + + bool hp_single_supply; + bool master; + u8 alc_en; + u8 in_filt_en; + u8 mic_lvl_det_en; + + u8 biq_5stage_coeff[DA7218_OUT_1_BIQ_5STAGE_CFG_SIZE]; + u8 stbiq_3stage_coeff[DA7218_SIDETONE_BIQ_3STAGE_CFG_SIZE]; +}; + +/* HP detect control */ +int da7218_hpldet(struct snd_soc_component *component, struct snd_soc_jack *jack); + +#endif /* _DA7218_H */ diff --git a/sound/soc/codecs/da7219-aad.c b/sound/soc/codecs/da7219-aad.c new file mode 100644 index 000000000..2c7d5088e --- /dev/null +++ b/sound/soc/codecs/da7219-aad.c @@ -0,0 +1,956 @@ +/* + * da7219-aad.c - Dialog DA7219 ALSA SoC AAD Driver + * + * Copyright (c) 2015 Dialog Semiconductor Ltd. + * + * Author: Adam Thomson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "da7219.h" +#include "da7219-aad.h" + + +/* + * Detection control + */ + +void da7219_aad_jack_det(struct snd_soc_component *component, struct snd_soc_jack *jack) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + da7219->aad->jack = jack; + da7219->aad->jack_inserted = false; + + /* Send an initial empty report */ + snd_soc_jack_report(jack, 0, DA7219_AAD_REPORT_ALL_MASK); + + /* Enable/Disable jack detection */ + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1, + DA7219_ACCDET_EN_MASK, + (jack ? DA7219_ACCDET_EN_MASK : 0)); +} +EXPORT_SYMBOL_GPL(da7219_aad_jack_det); + +/* + * Button/HPTest work + */ + +static void da7219_aad_btn_det_work(struct work_struct *work) +{ + struct da7219_aad_priv *da7219_aad = + container_of(work, struct da7219_aad_priv, btn_det_work); + struct snd_soc_component *component = da7219_aad->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + u8 statusa, micbias_ctrl; + bool micbias_up = false; + int retries = 0; + + /* Drive headphones/lineout */ + snd_soc_component_update_bits(component, DA7219_HP_L_CTRL, + DA7219_HP_L_AMP_OE_MASK, + DA7219_HP_L_AMP_OE_MASK); + snd_soc_component_update_bits(component, DA7219_HP_R_CTRL, + DA7219_HP_R_AMP_OE_MASK, + DA7219_HP_R_AMP_OE_MASK); + + /* Make sure mic bias is up */ + snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); + snd_soc_dapm_sync(dapm); + + do { + statusa = snd_soc_component_read32(component, DA7219_ACCDET_STATUS_A); + if (statusa & DA7219_MICBIAS_UP_STS_MASK) + micbias_up = true; + else if (retries++ < DA7219_AAD_MICBIAS_CHK_RETRIES) + msleep(DA7219_AAD_MICBIAS_CHK_DELAY); + } while ((!micbias_up) && (retries < DA7219_AAD_MICBIAS_CHK_RETRIES)); + + if (retries >= DA7219_AAD_MICBIAS_CHK_RETRIES) + dev_warn(component->dev, "Mic bias status check timed out"); + + da7219->micbias_on_event = true; + + /* + * Mic bias pulse required to enable mic, must be done before enabling + * button detection to prevent erroneous button readings. + */ + if (da7219_aad->micbias_pulse_lvl && da7219_aad->micbias_pulse_time) { + /* Pulse higher level voltage */ + micbias_ctrl = snd_soc_component_read32(component, DA7219_MICBIAS_CTRL); + snd_soc_component_update_bits(component, DA7219_MICBIAS_CTRL, + DA7219_MICBIAS1_LEVEL_MASK, + da7219_aad->micbias_pulse_lvl); + msleep(da7219_aad->micbias_pulse_time); + snd_soc_component_write(component, DA7219_MICBIAS_CTRL, micbias_ctrl); + + } + + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1, + DA7219_BUTTON_CONFIG_MASK, + da7219_aad->btn_cfg); +} + +static void da7219_aad_hptest_work(struct work_struct *work) +{ + struct da7219_aad_priv *da7219_aad = + container_of(work, struct da7219_aad_priv, hptest_work); + struct snd_soc_component *component = da7219_aad->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + u16 tonegen_freq_hptest; + u8 pll_srm_sts, pll_ctrl, gain_ramp_ctrl, accdet_cfg8; + int report = 0, ret = 0; + + /* Lock DAPM, Kcontrols affected by this test and the PLL */ + snd_soc_dapm_mutex_lock(dapm); + mutex_lock(&da7219->ctrl_lock); + mutex_lock(&da7219->pll_lock); + + /* Ensure MCLK is available for HP test procedure */ + if (da7219->mclk) { + ret = clk_prepare_enable(da7219->mclk); + if (ret) { + dev_err(component->dev, "Failed to enable mclk - %d\n", ret); + mutex_unlock(&da7219->pll_lock); + mutex_unlock(&da7219->ctrl_lock); + snd_soc_dapm_mutex_unlock(dapm); + return; + } + } + + /* + * If MCLK not present, then we're using the internal oscillator and + * require different frequency settings to achieve the same result. + * + * If MCLK is present, but PLL is not enabled then we enable it here to + * ensure a consistent detection procedure. + */ + pll_srm_sts = snd_soc_component_read32(component, DA7219_PLL_SRM_STS); + if (pll_srm_sts & DA7219_PLL_SRM_STS_MCLK) { + tonegen_freq_hptest = cpu_to_le16(DA7219_AAD_HPTEST_RAMP_FREQ); + + pll_ctrl = snd_soc_component_read32(component, DA7219_PLL_CTRL); + if ((pll_ctrl & DA7219_PLL_MODE_MASK) == DA7219_PLL_MODE_BYPASS) + da7219_set_pll(component, DA7219_SYSCLK_PLL, + DA7219_PLL_FREQ_OUT_98304); + } else { + tonegen_freq_hptest = cpu_to_le16(DA7219_AAD_HPTEST_RAMP_FREQ_INT_OSC); + } + + /* Ensure gain ramping at fastest rate */ + gain_ramp_ctrl = snd_soc_component_read32(component, DA7219_GAIN_RAMP_CTRL); + snd_soc_component_write(component, DA7219_GAIN_RAMP_CTRL, DA7219_GAIN_RAMP_RATE_X8); + + /* Bypass cache so it saves current settings */ + regcache_cache_bypass(da7219->regmap, true); + + /* Make sure Tone Generator is disabled */ + snd_soc_component_write(component, DA7219_TONE_GEN_CFG1, 0); + + /* Enable HPTest block, 1KOhms check */ + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_8, + DA7219_HPTEST_EN_MASK | DA7219_HPTEST_RES_SEL_MASK, + DA7219_HPTEST_EN_MASK | + DA7219_HPTEST_RES_SEL_1KOHMS); + + /* Set gains to 0db */ + snd_soc_component_write(component, DA7219_DAC_L_GAIN, DA7219_DAC_DIGITAL_GAIN_0DB); + snd_soc_component_write(component, DA7219_DAC_R_GAIN, DA7219_DAC_DIGITAL_GAIN_0DB); + snd_soc_component_write(component, DA7219_HP_L_GAIN, DA7219_HP_AMP_GAIN_0DB); + snd_soc_component_write(component, DA7219_HP_R_GAIN, DA7219_HP_AMP_GAIN_0DB); + + /* Disable DAC filters, EQs and soft mute */ + snd_soc_component_update_bits(component, DA7219_DAC_FILTERS1, DA7219_HPF_MODE_MASK, + 0); + snd_soc_component_update_bits(component, DA7219_DAC_FILTERS4, DA7219_DAC_EQ_EN_MASK, + 0); + snd_soc_component_update_bits(component, DA7219_DAC_FILTERS5, + DA7219_DAC_SOFTMUTE_EN_MASK, 0); + + /* Enable HP left & right paths */ + snd_soc_component_update_bits(component, DA7219_CP_CTRL, DA7219_CP_EN_MASK, + DA7219_CP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_DIG_ROUTING_DAC, + DA7219_DAC_L_SRC_MASK | DA7219_DAC_R_SRC_MASK, + DA7219_DAC_L_SRC_TONEGEN | + DA7219_DAC_R_SRC_TONEGEN); + snd_soc_component_update_bits(component, DA7219_DAC_L_CTRL, + DA7219_DAC_L_EN_MASK | DA7219_DAC_L_MUTE_EN_MASK, + DA7219_DAC_L_EN_MASK); + snd_soc_component_update_bits(component, DA7219_DAC_R_CTRL, + DA7219_DAC_R_EN_MASK | DA7219_DAC_R_MUTE_EN_MASK, + DA7219_DAC_R_EN_MASK); + snd_soc_component_update_bits(component, DA7219_MIXOUT_L_SELECT, + DA7219_MIXOUT_L_MIX_SELECT_MASK, + DA7219_MIXOUT_L_MIX_SELECT_MASK); + snd_soc_component_update_bits(component, DA7219_MIXOUT_R_SELECT, + DA7219_MIXOUT_R_MIX_SELECT_MASK, + DA7219_MIXOUT_R_MIX_SELECT_MASK); + snd_soc_component_update_bits(component, DA7219_DROUTING_ST_OUTFILT_1L, + DA7219_OUTFILT_ST_1L_SRC_MASK, + DA7219_DMIX_ST_SRC_OUTFILT1L); + snd_soc_component_update_bits(component, DA7219_DROUTING_ST_OUTFILT_1R, + DA7219_OUTFILT_ST_1R_SRC_MASK, + DA7219_DMIX_ST_SRC_OUTFILT1R); + snd_soc_component_update_bits(component, DA7219_MIXOUT_L_CTRL, + DA7219_MIXOUT_L_AMP_EN_MASK, + DA7219_MIXOUT_L_AMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_MIXOUT_R_CTRL, + DA7219_MIXOUT_R_AMP_EN_MASK, + DA7219_MIXOUT_R_AMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_HP_L_CTRL, + DA7219_HP_L_AMP_OE_MASK | DA7219_HP_L_AMP_EN_MASK, + DA7219_HP_L_AMP_OE_MASK | DA7219_HP_L_AMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_HP_R_CTRL, + DA7219_HP_R_AMP_OE_MASK | DA7219_HP_R_AMP_EN_MASK, + DA7219_HP_R_AMP_OE_MASK | DA7219_HP_R_AMP_EN_MASK); + msleep(DA7219_SETTLING_DELAY); + snd_soc_component_update_bits(component, DA7219_HP_L_CTRL, + DA7219_HP_L_AMP_MUTE_EN_MASK | + DA7219_HP_L_AMP_MIN_GAIN_EN_MASK, 0); + snd_soc_component_update_bits(component, DA7219_HP_R_CTRL, + DA7219_HP_R_AMP_MUTE_EN_MASK | + DA7219_HP_R_AMP_MIN_GAIN_EN_MASK, 0); + + /* + * If we're running from the internal oscillator then give audio paths + * time to settle before running test. + */ + if (!(pll_srm_sts & DA7219_PLL_SRM_STS_MCLK)) + msleep(DA7219_AAD_HPTEST_INT_OSC_PATH_DELAY); + + /* Configure & start Tone Generator */ + snd_soc_component_write(component, DA7219_TONE_GEN_ON_PER, DA7219_BEEP_ON_PER_MASK); + regmap_raw_write(da7219->regmap, DA7219_TONE_GEN_FREQ1_L, + &tonegen_freq_hptest, sizeof(tonegen_freq_hptest)); + snd_soc_component_update_bits(component, DA7219_TONE_GEN_CFG2, + DA7219_SWG_SEL_MASK | DA7219_TONE_GEN_GAIN_MASK, + DA7219_SWG_SEL_SRAMP | + DA7219_TONE_GEN_GAIN_MINUS_15DB); + snd_soc_component_write(component, DA7219_TONE_GEN_CFG1, DA7219_START_STOPN_MASK); + + msleep(DA7219_AAD_HPTEST_PERIOD); + + /* Grab comparator reading */ + accdet_cfg8 = snd_soc_component_read32(component, DA7219_ACCDET_CONFIG_8); + if (accdet_cfg8 & DA7219_HPTEST_COMP_MASK) + report |= SND_JACK_HEADPHONE; + else + report |= SND_JACK_LINEOUT; + + /* Stop tone generator */ + snd_soc_component_write(component, DA7219_TONE_GEN_CFG1, 0); + + msleep(DA7219_AAD_HPTEST_PERIOD); + + /* Restore original settings from cache */ + regcache_mark_dirty(da7219->regmap); + regcache_sync_region(da7219->regmap, DA7219_HP_L_CTRL, + DA7219_HP_R_CTRL); + msleep(DA7219_SETTLING_DELAY); + regcache_sync_region(da7219->regmap, DA7219_MIXOUT_L_CTRL, + DA7219_MIXOUT_R_CTRL); + regcache_sync_region(da7219->regmap, DA7219_DROUTING_ST_OUTFILT_1L, + DA7219_DROUTING_ST_OUTFILT_1R); + regcache_sync_region(da7219->regmap, DA7219_MIXOUT_L_SELECT, + DA7219_MIXOUT_R_SELECT); + regcache_sync_region(da7219->regmap, DA7219_DAC_L_CTRL, + DA7219_DAC_R_CTRL); + regcache_sync_region(da7219->regmap, DA7219_DIG_ROUTING_DAC, + DA7219_DIG_ROUTING_DAC); + regcache_sync_region(da7219->regmap, DA7219_CP_CTRL, DA7219_CP_CTRL); + regcache_sync_region(da7219->regmap, DA7219_DAC_FILTERS5, + DA7219_DAC_FILTERS5); + regcache_sync_region(da7219->regmap, DA7219_DAC_FILTERS4, + DA7219_DAC_FILTERS1); + regcache_sync_region(da7219->regmap, DA7219_HP_L_GAIN, + DA7219_HP_R_GAIN); + regcache_sync_region(da7219->regmap, DA7219_DAC_L_GAIN, + DA7219_DAC_R_GAIN); + regcache_sync_region(da7219->regmap, DA7219_TONE_GEN_ON_PER, + DA7219_TONE_GEN_ON_PER); + regcache_sync_region(da7219->regmap, DA7219_TONE_GEN_FREQ1_L, + DA7219_TONE_GEN_FREQ1_U); + regcache_sync_region(da7219->regmap, DA7219_TONE_GEN_CFG1, + DA7219_TONE_GEN_CFG2); + + regcache_cache_bypass(da7219->regmap, false); + + /* Disable HPTest block */ + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_8, + DA7219_HPTEST_EN_MASK, 0); + + /* + * If we're running from the internal oscillator then give audio paths + * time to settle before allowing headphones to be driven as required. + */ + if (!(pll_srm_sts & DA7219_PLL_SRM_STS_MCLK)) + msleep(DA7219_AAD_HPTEST_INT_OSC_PATH_DELAY); + + /* Restore gain ramping rate */ + snd_soc_component_write(component, DA7219_GAIN_RAMP_CTRL, gain_ramp_ctrl); + + /* Drive Headphones/lineout */ + snd_soc_component_update_bits(component, DA7219_HP_L_CTRL, DA7219_HP_L_AMP_OE_MASK, + DA7219_HP_L_AMP_OE_MASK); + snd_soc_component_update_bits(component, DA7219_HP_R_CTRL, DA7219_HP_R_AMP_OE_MASK, + DA7219_HP_R_AMP_OE_MASK); + + /* Restore PLL to previous configuration, if re-configured */ + if ((pll_srm_sts & DA7219_PLL_SRM_STS_MCLK) && + ((pll_ctrl & DA7219_PLL_MODE_MASK) == DA7219_PLL_MODE_BYPASS)) + da7219_set_pll(component, DA7219_SYSCLK_MCLK, 0); + + /* Remove MCLK, if previously enabled */ + if (da7219->mclk) + clk_disable_unprepare(da7219->mclk); + + mutex_unlock(&da7219->pll_lock); + mutex_unlock(&da7219->ctrl_lock); + snd_soc_dapm_mutex_unlock(dapm); + + /* + * Only send report if jack hasn't been removed during process, + * otherwise it's invalid and we drop it. + */ + if (da7219_aad->jack_inserted) + snd_soc_jack_report(da7219_aad->jack, report, + SND_JACK_HEADSET | SND_JACK_LINEOUT); +} + + +/* + * IRQ + */ + +static irqreturn_t da7219_aad_irq_thread(int irq, void *data) +{ + struct da7219_aad_priv *da7219_aad = data; + struct snd_soc_component *component = da7219_aad->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + u8 events[DA7219_AAD_IRQ_REG_MAX]; + u8 statusa; + int i, report = 0, mask = 0; + + /* Read current IRQ events */ + regmap_bulk_read(da7219->regmap, DA7219_ACCDET_IRQ_EVENT_A, + events, DA7219_AAD_IRQ_REG_MAX); + + if (!events[DA7219_AAD_IRQ_REG_A] && !events[DA7219_AAD_IRQ_REG_B]) + return IRQ_NONE; + + /* Read status register for jack insertion & type status */ + statusa = snd_soc_component_read32(component, DA7219_ACCDET_STATUS_A); + + /* Clear events */ + regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_EVENT_A, + events, DA7219_AAD_IRQ_REG_MAX); + + dev_dbg(component->dev, "IRQ events = 0x%x|0x%x, status = 0x%x\n", + events[DA7219_AAD_IRQ_REG_A], events[DA7219_AAD_IRQ_REG_B], + statusa); + + if (statusa & DA7219_JACK_INSERTION_STS_MASK) { + /* Jack Insertion */ + if (events[DA7219_AAD_IRQ_REG_A] & + DA7219_E_JACK_INSERTED_MASK) { + report |= SND_JACK_MECHANICAL; + mask |= SND_JACK_MECHANICAL; + da7219_aad->jack_inserted = true; + } + + /* Jack type detection */ + if (events[DA7219_AAD_IRQ_REG_A] & + DA7219_E_JACK_DETECT_COMPLETE_MASK) { + /* + * If 4-pole, then enable button detection, else perform + * HP impedance test to determine output type to report. + * + * We schedule work here as the tasks themselves can + * take time to complete, and in particular for hptest + * we want to be able to check if the jack was removed + * during the procedure as this will invalidate the + * result. By doing this as work, the IRQ thread can + * handle a removal, and we can check at the end of + * hptest if we have a valid result or not. + */ + if (statusa & DA7219_JACK_TYPE_STS_MASK) { + report |= SND_JACK_HEADSET; + mask |= SND_JACK_HEADSET | SND_JACK_LINEOUT; + schedule_work(&da7219_aad->btn_det_work); + } else { + schedule_work(&da7219_aad->hptest_work); + } + } + + /* Button support for 4-pole jack */ + if (statusa & DA7219_JACK_TYPE_STS_MASK) { + for (i = 0; i < DA7219_AAD_MAX_BUTTONS; ++i) { + /* Button Press */ + if (events[DA7219_AAD_IRQ_REG_B] & + (DA7219_E_BUTTON_A_PRESSED_MASK << i)) { + report |= SND_JACK_BTN_0 >> i; + mask |= SND_JACK_BTN_0 >> i; + } + } + snd_soc_jack_report(da7219_aad->jack, report, mask); + + for (i = 0; i < DA7219_AAD_MAX_BUTTONS; ++i) { + /* Button Release */ + if (events[DA7219_AAD_IRQ_REG_B] & + (DA7219_E_BUTTON_A_RELEASED_MASK >> i)) { + report &= ~(SND_JACK_BTN_0 >> i); + mask |= SND_JACK_BTN_0 >> i; + } + } + } + } else { + /* Jack removal */ + if (events[DA7219_AAD_IRQ_REG_A] & DA7219_E_JACK_REMOVED_MASK) { + report = 0; + mask |= DA7219_AAD_REPORT_ALL_MASK; + da7219_aad->jack_inserted = false; + + /* Un-drive headphones/lineout */ + snd_soc_component_update_bits(component, DA7219_HP_R_CTRL, + DA7219_HP_R_AMP_OE_MASK, 0); + snd_soc_component_update_bits(component, DA7219_HP_L_CTRL, + DA7219_HP_L_AMP_OE_MASK, 0); + + /* Ensure button detection disabled */ + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1, + DA7219_BUTTON_CONFIG_MASK, 0); + + da7219->micbias_on_event = false; + + /* Disable mic bias */ + snd_soc_dapm_disable_pin(dapm, "Mic Bias"); + snd_soc_dapm_sync(dapm); + + /* Cancel any pending work */ + cancel_work_sync(&da7219_aad->btn_det_work); + cancel_work_sync(&da7219_aad->hptest_work); + } + } + + snd_soc_jack_report(da7219_aad->jack, report, mask); + + return IRQ_HANDLED; +} + +/* + * DT/ACPI to pdata conversion + */ + +static enum da7219_aad_micbias_pulse_lvl + da7219_aad_fw_micbias_pulse_lvl(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 2800: + return DA7219_AAD_MICBIAS_PULSE_LVL_2_8V; + case 2900: + return DA7219_AAD_MICBIAS_PULSE_LVL_2_9V; + default: + dev_warn(component->dev, "Invalid micbias pulse level"); + return DA7219_AAD_MICBIAS_PULSE_LVL_OFF; + } +} + +static enum da7219_aad_btn_cfg + da7219_aad_fw_btn_cfg(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 2: + return DA7219_AAD_BTN_CFG_2MS; + case 5: + return DA7219_AAD_BTN_CFG_5MS; + case 10: + return DA7219_AAD_BTN_CFG_10MS; + case 50: + return DA7219_AAD_BTN_CFG_50MS; + case 100: + return DA7219_AAD_BTN_CFG_100MS; + case 200: + return DA7219_AAD_BTN_CFG_200MS; + case 500: + return DA7219_AAD_BTN_CFG_500MS; + default: + dev_warn(component->dev, "Invalid button config"); + return DA7219_AAD_BTN_CFG_10MS; + } +} + +static enum da7219_aad_mic_det_thr + da7219_aad_fw_mic_det_thr(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 200: + return DA7219_AAD_MIC_DET_THR_200_OHMS; + case 500: + return DA7219_AAD_MIC_DET_THR_500_OHMS; + case 750: + return DA7219_AAD_MIC_DET_THR_750_OHMS; + case 1000: + return DA7219_AAD_MIC_DET_THR_1000_OHMS; + default: + dev_warn(component->dev, "Invalid mic detect threshold"); + return DA7219_AAD_MIC_DET_THR_500_OHMS; + } +} + +static enum da7219_aad_jack_ins_deb + da7219_aad_fw_jack_ins_deb(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 5: + return DA7219_AAD_JACK_INS_DEB_5MS; + case 10: + return DA7219_AAD_JACK_INS_DEB_10MS; + case 20: + return DA7219_AAD_JACK_INS_DEB_20MS; + case 50: + return DA7219_AAD_JACK_INS_DEB_50MS; + case 100: + return DA7219_AAD_JACK_INS_DEB_100MS; + case 200: + return DA7219_AAD_JACK_INS_DEB_200MS; + case 500: + return DA7219_AAD_JACK_INS_DEB_500MS; + case 1000: + return DA7219_AAD_JACK_INS_DEB_1S; + default: + dev_warn(component->dev, "Invalid jack insert debounce"); + return DA7219_AAD_JACK_INS_DEB_20MS; + } +} + +static enum da7219_aad_jack_det_rate + da7219_aad_fw_jack_det_rate(struct snd_soc_component *component, const char *str) +{ + if (!strcmp(str, "32ms_64ms")) { + return DA7219_AAD_JACK_DET_RATE_32_64MS; + } else if (!strcmp(str, "64ms_128ms")) { + return DA7219_AAD_JACK_DET_RATE_64_128MS; + } else if (!strcmp(str, "128ms_256ms")) { + return DA7219_AAD_JACK_DET_RATE_128_256MS; + } else if (!strcmp(str, "256ms_512ms")) { + return DA7219_AAD_JACK_DET_RATE_256_512MS; + } else { + dev_warn(component->dev, "Invalid jack detect rate"); + return DA7219_AAD_JACK_DET_RATE_256_512MS; + } +} + +static enum da7219_aad_jack_rem_deb + da7219_aad_fw_jack_rem_deb(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 1: + return DA7219_AAD_JACK_REM_DEB_1MS; + case 5: + return DA7219_AAD_JACK_REM_DEB_5MS; + case 10: + return DA7219_AAD_JACK_REM_DEB_10MS; + case 20: + return DA7219_AAD_JACK_REM_DEB_20MS; + default: + dev_warn(component->dev, "Invalid jack removal debounce"); + return DA7219_AAD_JACK_REM_DEB_1MS; + } +} + +static enum da7219_aad_btn_avg + da7219_aad_fw_btn_avg(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 1: + return DA7219_AAD_BTN_AVG_1; + case 2: + return DA7219_AAD_BTN_AVG_2; + case 4: + return DA7219_AAD_BTN_AVG_4; + case 8: + return DA7219_AAD_BTN_AVG_8; + default: + dev_warn(component->dev, "Invalid button average value"); + return DA7219_AAD_BTN_AVG_2; + } +} + +static enum da7219_aad_adc_1bit_rpt + da7219_aad_fw_adc_1bit_rpt(struct snd_soc_component *component, u32 val) +{ + switch (val) { + case 1: + return DA7219_AAD_ADC_1BIT_RPT_1; + case 2: + return DA7219_AAD_ADC_1BIT_RPT_2; + case 4: + return DA7219_AAD_ADC_1BIT_RPT_4; + case 8: + return DA7219_AAD_ADC_1BIT_RPT_8; + default: + dev_warn(component->dev, "Invalid ADC 1-bit repeat value"); + return DA7219_AAD_ADC_1BIT_RPT_1; + } +} + +static struct da7219_aad_pdata *da7219_aad_fw_to_pdata(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + struct i2c_client *i2c = to_i2c_client(dev); + struct fwnode_handle *aad_np; + struct da7219_aad_pdata *aad_pdata; + const char *fw_str; + u32 fw_val32; + + aad_np = device_get_named_child_node(dev, "da7219_aad"); + if (!aad_np) + return NULL; + + aad_pdata = devm_kzalloc(dev, sizeof(*aad_pdata), GFP_KERNEL); + if (!aad_pdata) + return NULL; + + aad_pdata->irq = i2c->irq; + + if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-lvl", + &fw_val32) >= 0) + aad_pdata->micbias_pulse_lvl = + da7219_aad_fw_micbias_pulse_lvl(component, fw_val32); + else + aad_pdata->micbias_pulse_lvl = DA7219_AAD_MICBIAS_PULSE_LVL_OFF; + + if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-time", + &fw_val32) >= 0) + aad_pdata->micbias_pulse_time = fw_val32; + + if (fwnode_property_read_u32(aad_np, "dlg,btn-cfg", &fw_val32) >= 0) + aad_pdata->btn_cfg = da7219_aad_fw_btn_cfg(component, fw_val32); + else + aad_pdata->btn_cfg = DA7219_AAD_BTN_CFG_10MS; + + if (fwnode_property_read_u32(aad_np, "dlg,mic-det-thr", &fw_val32) >= 0) + aad_pdata->mic_det_thr = + da7219_aad_fw_mic_det_thr(component, fw_val32); + else + aad_pdata->mic_det_thr = DA7219_AAD_MIC_DET_THR_500_OHMS; + + if (fwnode_property_read_u32(aad_np, "dlg,jack-ins-deb", &fw_val32) >= 0) + aad_pdata->jack_ins_deb = + da7219_aad_fw_jack_ins_deb(component, fw_val32); + else + aad_pdata->jack_ins_deb = DA7219_AAD_JACK_INS_DEB_20MS; + + if (!fwnode_property_read_string(aad_np, "dlg,jack-det-rate", &fw_str)) + aad_pdata->jack_det_rate = + da7219_aad_fw_jack_det_rate(component, fw_str); + else + aad_pdata->jack_det_rate = DA7219_AAD_JACK_DET_RATE_256_512MS; + + if (fwnode_property_read_u32(aad_np, "dlg,jack-rem-deb", &fw_val32) >= 0) + aad_pdata->jack_rem_deb = + da7219_aad_fw_jack_rem_deb(component, fw_val32); + else + aad_pdata->jack_rem_deb = DA7219_AAD_JACK_REM_DEB_1MS; + + if (fwnode_property_read_u32(aad_np, "dlg,a-d-btn-thr", &fw_val32) >= 0) + aad_pdata->a_d_btn_thr = (u8) fw_val32; + else + aad_pdata->a_d_btn_thr = 0xA; + + if (fwnode_property_read_u32(aad_np, "dlg,d-b-btn-thr", &fw_val32) >= 0) + aad_pdata->d_b_btn_thr = (u8) fw_val32; + else + aad_pdata->d_b_btn_thr = 0x16; + + if (fwnode_property_read_u32(aad_np, "dlg,b-c-btn-thr", &fw_val32) >= 0) + aad_pdata->b_c_btn_thr = (u8) fw_val32; + else + aad_pdata->b_c_btn_thr = 0x21; + + if (fwnode_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &fw_val32) >= 0) + aad_pdata->c_mic_btn_thr = (u8) fw_val32; + else + aad_pdata->c_mic_btn_thr = 0x3E; + + if (fwnode_property_read_u32(aad_np, "dlg,btn-avg", &fw_val32) >= 0) + aad_pdata->btn_avg = da7219_aad_fw_btn_avg(component, fw_val32); + else + aad_pdata->btn_avg = DA7219_AAD_BTN_AVG_2; + + if (fwnode_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &fw_val32) >= 0) + aad_pdata->adc_1bit_rpt = + da7219_aad_fw_adc_1bit_rpt(component, fw_val32); + else + aad_pdata->adc_1bit_rpt = DA7219_AAD_ADC_1BIT_RPT_1; + + return aad_pdata; +} + +static void da7219_aad_handle_pdata(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct da7219_aad_priv *da7219_aad = da7219->aad; + struct da7219_pdata *pdata = da7219->pdata; + + if ((pdata) && (pdata->aad_pdata)) { + struct da7219_aad_pdata *aad_pdata = pdata->aad_pdata; + u8 cfg, mask; + + da7219_aad->irq = aad_pdata->irq; + + switch (aad_pdata->micbias_pulse_lvl) { + case DA7219_AAD_MICBIAS_PULSE_LVL_2_8V: + case DA7219_AAD_MICBIAS_PULSE_LVL_2_9V: + da7219_aad->micbias_pulse_lvl = + (aad_pdata->micbias_pulse_lvl << + DA7219_MICBIAS1_LEVEL_SHIFT); + break; + default: + break; + } + + da7219_aad->micbias_pulse_time = aad_pdata->micbias_pulse_time; + + switch (aad_pdata->btn_cfg) { + case DA7219_AAD_BTN_CFG_2MS: + case DA7219_AAD_BTN_CFG_5MS: + case DA7219_AAD_BTN_CFG_10MS: + case DA7219_AAD_BTN_CFG_50MS: + case DA7219_AAD_BTN_CFG_100MS: + case DA7219_AAD_BTN_CFG_200MS: + case DA7219_AAD_BTN_CFG_500MS: + da7219_aad->btn_cfg = (aad_pdata->btn_cfg << + DA7219_BUTTON_CONFIG_SHIFT); + } + + cfg = 0; + mask = 0; + switch (aad_pdata->mic_det_thr) { + case DA7219_AAD_MIC_DET_THR_200_OHMS: + case DA7219_AAD_MIC_DET_THR_500_OHMS: + case DA7219_AAD_MIC_DET_THR_750_OHMS: + case DA7219_AAD_MIC_DET_THR_1000_OHMS: + cfg |= (aad_pdata->mic_det_thr << + DA7219_MIC_DET_THRESH_SHIFT); + mask |= DA7219_MIC_DET_THRESH_MASK; + } + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1, mask, cfg); + + cfg = 0; + mask = 0; + switch (aad_pdata->jack_ins_deb) { + case DA7219_AAD_JACK_INS_DEB_5MS: + case DA7219_AAD_JACK_INS_DEB_10MS: + case DA7219_AAD_JACK_INS_DEB_20MS: + case DA7219_AAD_JACK_INS_DEB_50MS: + case DA7219_AAD_JACK_INS_DEB_100MS: + case DA7219_AAD_JACK_INS_DEB_200MS: + case DA7219_AAD_JACK_INS_DEB_500MS: + case DA7219_AAD_JACK_INS_DEB_1S: + cfg |= (aad_pdata->jack_ins_deb << + DA7219_JACKDET_DEBOUNCE_SHIFT); + mask |= DA7219_JACKDET_DEBOUNCE_MASK; + } + switch (aad_pdata->jack_det_rate) { + case DA7219_AAD_JACK_DET_RATE_32_64MS: + case DA7219_AAD_JACK_DET_RATE_64_128MS: + case DA7219_AAD_JACK_DET_RATE_128_256MS: + case DA7219_AAD_JACK_DET_RATE_256_512MS: + cfg |= (aad_pdata->jack_det_rate << + DA7219_JACK_DETECT_RATE_SHIFT); + mask |= DA7219_JACK_DETECT_RATE_MASK; + } + switch (aad_pdata->jack_rem_deb) { + case DA7219_AAD_JACK_REM_DEB_1MS: + case DA7219_AAD_JACK_REM_DEB_5MS: + case DA7219_AAD_JACK_REM_DEB_10MS: + case DA7219_AAD_JACK_REM_DEB_20MS: + cfg |= (aad_pdata->jack_rem_deb << + DA7219_JACKDET_REM_DEB_SHIFT); + mask |= DA7219_JACKDET_REM_DEB_MASK; + } + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_2, mask, cfg); + + snd_soc_component_write(component, DA7219_ACCDET_CONFIG_3, + aad_pdata->a_d_btn_thr); + snd_soc_component_write(component, DA7219_ACCDET_CONFIG_4, + aad_pdata->d_b_btn_thr); + snd_soc_component_write(component, DA7219_ACCDET_CONFIG_5, + aad_pdata->b_c_btn_thr); + snd_soc_component_write(component, DA7219_ACCDET_CONFIG_6, + aad_pdata->c_mic_btn_thr); + + cfg = 0; + mask = 0; + switch (aad_pdata->btn_avg) { + case DA7219_AAD_BTN_AVG_1: + case DA7219_AAD_BTN_AVG_2: + case DA7219_AAD_BTN_AVG_4: + case DA7219_AAD_BTN_AVG_8: + cfg |= (aad_pdata->btn_avg << + DA7219_BUTTON_AVERAGE_SHIFT); + mask |= DA7219_BUTTON_AVERAGE_MASK; + } + switch (aad_pdata->adc_1bit_rpt) { + case DA7219_AAD_ADC_1BIT_RPT_1: + case DA7219_AAD_ADC_1BIT_RPT_2: + case DA7219_AAD_ADC_1BIT_RPT_4: + case DA7219_AAD_ADC_1BIT_RPT_8: + cfg |= (aad_pdata->adc_1bit_rpt << + DA7219_ADC_1_BIT_REPEAT_SHIFT); + mask |= DA7219_ADC_1_BIT_REPEAT_MASK; + } + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_7, mask, cfg); + } +} + + +/* + * Suspend/Resume + */ + +void da7219_aad_suspend(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct da7219_aad_priv *da7219_aad = da7219->aad; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + u8 micbias_ctrl; + + if (da7219_aad->jack) { + /* Disable jack detection during suspend */ + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1, + DA7219_ACCDET_EN_MASK, 0); + + /* + * If we have a 4-pole jack inserted, then micbias will be + * enabled. We can disable micbias here, and keep a note to + * re-enable it on resume. If jack removal occurred during + * suspend then this will be dealt with through the IRQ handler. + */ + if (da7219_aad->jack_inserted) { + micbias_ctrl = snd_soc_component_read32(component, DA7219_MICBIAS_CTRL); + if (micbias_ctrl & DA7219_MICBIAS1_EN_MASK) { + snd_soc_dapm_disable_pin(dapm, "Mic Bias"); + snd_soc_dapm_sync(dapm); + da7219_aad->micbias_resume_enable = true; + } + } + } +} + +void da7219_aad_resume(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct da7219_aad_priv *da7219_aad = da7219->aad; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (da7219_aad->jack) { + /* Re-enable micbias if previously enabled for 4-pole jack */ + if (da7219_aad->jack_inserted && + da7219_aad->micbias_resume_enable) { + snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); + snd_soc_dapm_sync(dapm); + da7219_aad->micbias_resume_enable = false; + } + + /* Re-enable jack detection */ + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1, + DA7219_ACCDET_EN_MASK, + DA7219_ACCDET_EN_MASK); + } +} + + +/* + * Init/Exit + */ + +int da7219_aad_init(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct da7219_aad_priv *da7219_aad; + u8 mask[DA7219_AAD_IRQ_REG_MAX]; + int ret; + + da7219_aad = devm_kzalloc(component->dev, sizeof(*da7219_aad), GFP_KERNEL); + if (!da7219_aad) + return -ENOMEM; + + da7219->aad = da7219_aad; + da7219_aad->component = component; + + /* Handle any DT/ACPI/platform data */ + if (da7219->pdata && !da7219->pdata->aad_pdata) + da7219->pdata->aad_pdata = da7219_aad_fw_to_pdata(component); + + da7219_aad_handle_pdata(component); + + /* Disable button detection */ + snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_1, + DA7219_BUTTON_CONFIG_MASK, 0); + + INIT_WORK(&da7219_aad->btn_det_work, da7219_aad_btn_det_work); + INIT_WORK(&da7219_aad->hptest_work, da7219_aad_hptest_work); + + ret = request_threaded_irq(da7219_aad->irq, NULL, + da7219_aad_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "da7219-aad", da7219_aad); + if (ret) { + dev_err(component->dev, "Failed to request IRQ: %d\n", ret); + return ret; + } + + /* Unmask AAD IRQs */ + memset(mask, 0, DA7219_AAD_IRQ_REG_MAX); + regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_MASK_A, + &mask, DA7219_AAD_IRQ_REG_MAX); + + return 0; +} +EXPORT_SYMBOL_GPL(da7219_aad_init); + +void da7219_aad_exit(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct da7219_aad_priv *da7219_aad = da7219->aad; + u8 mask[DA7219_AAD_IRQ_REG_MAX]; + + /* Mask off AAD IRQs */ + memset(mask, DA7219_BYTE_MASK, DA7219_AAD_IRQ_REG_MAX); + regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_MASK_A, + mask, DA7219_AAD_IRQ_REG_MAX); + + free_irq(da7219_aad->irq, da7219_aad); + + cancel_work_sync(&da7219_aad->btn_det_work); + cancel_work_sync(&da7219_aad->hptest_work); +} +EXPORT_SYMBOL_GPL(da7219_aad_exit); + +MODULE_DESCRIPTION("ASoC DA7219 AAD Driver"); +MODULE_AUTHOR("Adam Thomson "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da7219-aad.h b/sound/soc/codecs/da7219-aad.h new file mode 100644 index 000000000..b9c4a27e8 --- /dev/null +++ b/sound/soc/codecs/da7219-aad.h @@ -0,0 +1,219 @@ +/* + * da7219-aad.h - DA7322 ASoC AAD Driver + * + * Copyright (c) 2015 Dialog Semiconductor Ltd. + * + * Author: Adam Thomson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __DA7219_AAD_H +#define __DA7219_AAD_H + +#include +#include +#include +#include + +/* + * Registers + */ + +#define DA7219_ACCDET_STATUS_A 0xC0 +#define DA7219_ACCDET_STATUS_B 0xC1 +#define DA7219_ACCDET_IRQ_EVENT_A 0xC2 +#define DA7219_ACCDET_IRQ_EVENT_B 0xC3 +#define DA7219_ACCDET_IRQ_MASK_A 0xC4 +#define DA7219_ACCDET_IRQ_MASK_B 0xC5 +#define DA7219_ACCDET_CONFIG_1 0xC6 +#define DA7219_ACCDET_CONFIG_2 0xC7 +#define DA7219_ACCDET_CONFIG_3 0xC8 +#define DA7219_ACCDET_CONFIG_4 0xC9 +#define DA7219_ACCDET_CONFIG_5 0xCA +#define DA7219_ACCDET_CONFIG_6 0xCB +#define DA7219_ACCDET_CONFIG_7 0xCC +#define DA7219_ACCDET_CONFIG_8 0xCD + + +/* + * Bit Fields + */ + +/* DA7219_ACCDET_STATUS_A = 0xC0 */ +#define DA7219_JACK_INSERTION_STS_SHIFT 0 +#define DA7219_JACK_INSERTION_STS_MASK (0x1 << 0) +#define DA7219_JACK_TYPE_STS_SHIFT 1 +#define DA7219_JACK_TYPE_STS_MASK (0x1 << 1) +#define DA7219_JACK_PIN_ORDER_STS_SHIFT 2 +#define DA7219_JACK_PIN_ORDER_STS_MASK (0x1 << 2) +#define DA7219_MICBIAS_UP_STS_SHIFT 3 +#define DA7219_MICBIAS_UP_STS_MASK (0x1 << 3) + +/* DA7219_ACCDET_STATUS_B = 0xC1 */ +#define DA7219_BUTTON_TYPE_STS_SHIFT 0 +#define DA7219_BUTTON_TYPE_STS_MASK (0xFF << 0) + +/* DA7219_ACCDET_IRQ_EVENT_A = 0xC2 */ +#define DA7219_E_JACK_INSERTED_SHIFT 0 +#define DA7219_E_JACK_INSERTED_MASK (0x1 << 0) +#define DA7219_E_JACK_REMOVED_SHIFT 1 +#define DA7219_E_JACK_REMOVED_MASK (0x1 << 1) +#define DA7219_E_JACK_DETECT_COMPLETE_SHIFT 2 +#define DA7219_E_JACK_DETECT_COMPLETE_MASK (0x1 << 2) + +/* DA7219_ACCDET_IRQ_EVENT_B = 0xC3 */ +#define DA7219_E_BUTTON_A_PRESSED_SHIFT 0 +#define DA7219_E_BUTTON_A_PRESSED_MASK (0x1 << 0) +#define DA7219_E_BUTTON_B_PRESSED_SHIFT 1 +#define DA7219_E_BUTTON_B_PRESSED_MASK (0x1 << 1) +#define DA7219_E_BUTTON_C_PRESSED_SHIFT 2 +#define DA7219_E_BUTTON_C_PRESSED_MASK (0x1 << 2) +#define DA7219_E_BUTTON_D_PRESSED_SHIFT 3 +#define DA7219_E_BUTTON_D_PRESSED_MASK (0x1 << 3) +#define DA7219_E_BUTTON_D_RELEASED_SHIFT 4 +#define DA7219_E_BUTTON_D_RELEASED_MASK (0x1 << 4) +#define DA7219_E_BUTTON_C_RELEASED_SHIFT 5 +#define DA7219_E_BUTTON_C_RELEASED_MASK (0x1 << 5) +#define DA7219_E_BUTTON_B_RELEASED_SHIFT 6 +#define DA7219_E_BUTTON_B_RELEASED_MASK (0x1 << 6) +#define DA7219_E_BUTTON_A_RELEASED_SHIFT 7 +#define DA7219_E_BUTTON_A_RELEASED_MASK (0x1 << 7) + +/* DA7219_ACCDET_IRQ_MASK_A = 0xC4 */ +#define DA7219_M_JACK_INSERTED_SHIFT 0 +#define DA7219_M_JACK_INSERTED_MASK (0x1 << 0) +#define DA7219_M_JACK_REMOVED_SHIFT 1 +#define DA7219_M_JACK_REMOVED_MASK (0x1 << 1) +#define DA7219_M_JACK_DETECT_COMPLETE_SHIFT 2 +#define DA7219_M_JACK_DETECT_COMPLETE_MASK (0x1 << 2) + +/* DA7219_ACCDET_IRQ_MASK_B = 0xC5 */ +#define DA7219_M_BUTTON_A_PRESSED_SHIFT 0 +#define DA7219_M_BUTTON_A_PRESSED_MASK (0x1 << 0) +#define DA7219_M_BUTTON_B_PRESSED_SHIFT 1 +#define DA7219_M_BUTTON_B_PRESSED_MASK (0x1 << 1) +#define DA7219_M_BUTTON_C_PRESSED_SHIFT 2 +#define DA7219_M_BUTTON_C_PRESSED_MASK (0x1 << 2) +#define DA7219_M_BUTTON_D_PRESSED_SHIFT 3 +#define DA7219_M_BUTTON_D_PRESSED_MASK (0x1 << 3) +#define DA7219_M_BUTTON_D_RELEASED_SHIFT 4 +#define DA7219_M_BUTTON_D_RELEASED_MASK (0x1 << 4) +#define DA7219_M_BUTTON_C_RELEASED_SHIFT 5 +#define DA7219_M_BUTTON_C_RELEASED_MASK (0x1 << 5) +#define DA7219_M_BUTTON_B_RELEASED_SHIFT 6 +#define DA7219_M_BUTTON_B_RELEASED_MASK (0x1 << 6) +#define DA7219_M_BUTTON_A_RELEASED_SHIFT 7 +#define DA7219_M_BUTTON_A_RELEASED_MASK (0x1 << 7) + +/* DA7219_ACCDET_CONFIG_1 = 0xC6 */ +#define DA7219_ACCDET_EN_SHIFT 0 +#define DA7219_ACCDET_EN_MASK (0x1 << 0) +#define DA7219_BUTTON_CONFIG_SHIFT 1 +#define DA7219_BUTTON_CONFIG_MASK (0x7 << 1) +#define DA7219_MIC_DET_THRESH_SHIFT 4 +#define DA7219_MIC_DET_THRESH_MASK (0x3 << 4) +#define DA7219_JACK_TYPE_DET_EN_SHIFT 6 +#define DA7219_JACK_TYPE_DET_EN_MASK (0x1 << 6) +#define DA7219_PIN_ORDER_DET_EN_SHIFT 7 +#define DA7219_PIN_ORDER_DET_EN_MASK (0x1 << 7) + +/* DA7219_ACCDET_CONFIG_2 = 0xC7 */ +#define DA7219_ACCDET_PAUSE_SHIFT 0 +#define DA7219_ACCDET_PAUSE_MASK (0x1 << 0) +#define DA7219_JACKDET_DEBOUNCE_SHIFT 1 +#define DA7219_JACKDET_DEBOUNCE_MASK (0x7 << 1) +#define DA7219_JACK_DETECT_RATE_SHIFT 4 +#define DA7219_JACK_DETECT_RATE_MASK (0x3 << 4) +#define DA7219_JACKDET_REM_DEB_SHIFT 6 +#define DA7219_JACKDET_REM_DEB_MASK (0x3 << 6) + +/* DA7219_ACCDET_CONFIG_3 = 0xC8 */ +#define DA7219_A_D_BUTTON_THRESH_SHIFT 0 +#define DA7219_A_D_BUTTON_THRESH_MASK (0xFF << 0) + +/* DA7219_ACCDET_CONFIG_4 = 0xC9 */ +#define DA7219_D_B_BUTTON_THRESH_SHIFT 0 +#define DA7219_D_B_BUTTON_THRESH_MASK (0xFF << 0) + +/* DA7219_ACCDET_CONFIG_5 = 0xCA */ +#define DA7219_B_C_BUTTON_THRESH_SHIFT 0 +#define DA7219_B_C_BUTTON_THRESH_MASK (0xFF << 0) + +/* DA7219_ACCDET_CONFIG_6 = 0xCB */ +#define DA7219_C_MIC_BUTTON_THRESH_SHIFT 0 +#define DA7219_C_MIC_BUTTON_THRESH_MASK (0xFF << 0) + +/* DA7219_ACCDET_CONFIG_7 = 0xCC */ +#define DA7219_BUTTON_AVERAGE_SHIFT 0 +#define DA7219_BUTTON_AVERAGE_MASK (0x3 << 0) +#define DA7219_ADC_1_BIT_REPEAT_SHIFT 2 +#define DA7219_ADC_1_BIT_REPEAT_MASK (0x3 << 2) +#define DA7219_PIN_ORDER_FORCE_SHIFT 4 +#define DA7219_PIN_ORDER_FORCE_MASK (0x1 << 4) +#define DA7219_JACK_TYPE_FORCE_SHIFT 5 +#define DA7219_JACK_TYPE_FORCE_MASK (0x1 << 5) + +/* DA7219_ACCDET_CONFIG_8 = 0xCD */ +#define DA7219_HPTEST_EN_SHIFT 0 +#define DA7219_HPTEST_EN_MASK (0x1 << 0) +#define DA7219_HPTEST_RES_SEL_SHIFT 1 +#define DA7219_HPTEST_RES_SEL_MASK (0x3 << 1) +#define DA7219_HPTEST_RES_SEL_1KOHMS (0x0 << 1) +#define DA7219_HPTEST_COMP_SHIFT 4 +#define DA7219_HPTEST_COMP_MASK (0x1 << 4) + + +#define DA7219_AAD_MAX_BUTTONS 4 +#define DA7219_AAD_REPORT_ALL_MASK (SND_JACK_MECHANICAL | \ + SND_JACK_HEADSET | SND_JACK_LINEOUT | \ + SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3) + +#define DA7219_AAD_MICBIAS_CHK_DELAY 10 +#define DA7219_AAD_MICBIAS_CHK_RETRIES 5 + +#define DA7219_AAD_HPTEST_RAMP_FREQ 0x28 +#define DA7219_AAD_HPTEST_RAMP_FREQ_INT_OSC 0x4D +#define DA7219_AAD_HPTEST_PERIOD 65 +#define DA7219_AAD_HPTEST_INT_OSC_PATH_DELAY 20 + +enum da7219_aad_event_regs { + DA7219_AAD_IRQ_REG_A = 0, + DA7219_AAD_IRQ_REG_B, + DA7219_AAD_IRQ_REG_MAX, +}; + +/* Private data */ +struct da7219_aad_priv { + struct snd_soc_component *component; + int irq; + + u8 micbias_pulse_lvl; + u32 micbias_pulse_time; + + u8 btn_cfg; + + struct work_struct btn_det_work; + struct work_struct hptest_work; + + struct snd_soc_jack *jack; + bool micbias_resume_enable; + bool jack_inserted; +}; + +/* AAD control */ +void da7219_aad_jack_det(struct snd_soc_component *component, struct snd_soc_jack *jack); + +/* Suspend/Resume */ +void da7219_aad_suspend(struct snd_soc_component *component); +void da7219_aad_resume(struct snd_soc_component *component); + +/* Init/Exit */ +int da7219_aad_init(struct snd_soc_component *component); +void da7219_aad_exit(struct snd_soc_component *component); + +#endif /* __DA7219_AAD_H */ diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c new file mode 100644 index 000000000..e46e9f4bc --- /dev/null +++ b/sound/soc/codecs/da7219.c @@ -0,0 +1,2288 @@ +/* + * da7219.c - DA7219 ALSA SoC Codec Driver + * + * Copyright (c) 2015 Dialog Semiconductor + * + * Author: Adam Thomson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "da7219.h" +#include "da7219-aad.h" + + +/* + * TLVs and Enums + */ + +/* Input TLVs */ +static const DECLARE_TLV_DB_SCALE(da7219_mic_gain_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7219_mixin_gain_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(da7219_adc_dig_gain_tlv, -8325, 75, 0); +static const DECLARE_TLV_DB_SCALE(da7219_alc_threshold_tlv, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(da7219_alc_gain_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7219_alc_ana_gain_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7219_sidetone_gain_tlv, -4200, 300, 0); +static const DECLARE_TLV_DB_SCALE(da7219_tonegen_gain_tlv, -4500, 300, 0); + +/* Output TLVs */ +static const DECLARE_TLV_DB_SCALE(da7219_dac_eq_band_tlv, -1050, 150, 0); + +static const DECLARE_TLV_DB_RANGE(da7219_dac_dig_gain_tlv, + 0x0, 0x07, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* -77.25dB to 12dB */ + 0x08, 0x7f, TLV_DB_SCALE_ITEM(-7725, 75, 0) +); + +static const DECLARE_TLV_DB_SCALE(da7219_dac_ng_threshold_tlv, -10200, 600, 0); +static const DECLARE_TLV_DB_SCALE(da7219_hp_gain_tlv, -5700, 100, 0); + +/* Input Enums */ +static const char * const da7219_alc_attack_rate_txt[] = { + "7.33/fs", "14.66/fs", "29.32/fs", "58.64/fs", "117.3/fs", "234.6/fs", + "469.1/fs", "938.2/fs", "1876/fs", "3753/fs", "7506/fs", "15012/fs", + "30024/fs" +}; + +static const struct soc_enum da7219_alc_attack_rate = + SOC_ENUM_SINGLE(DA7219_ALC_CTRL2, DA7219_ALC_ATTACK_SHIFT, + DA7219_ALC_ATTACK_MAX, da7219_alc_attack_rate_txt); + +static const char * const da7219_alc_release_rate_txt[] = { + "28.66/fs", "57.33/fs", "114.6/fs", "229.3/fs", "458.6/fs", "917.1/fs", + "1834/fs", "3668/fs", "7337/fs", "14674/fs", "29348/fs" +}; + +static const struct soc_enum da7219_alc_release_rate = + SOC_ENUM_SINGLE(DA7219_ALC_CTRL2, DA7219_ALC_RELEASE_SHIFT, + DA7219_ALC_RELEASE_MAX, da7219_alc_release_rate_txt); + +static const char * const da7219_alc_hold_time_txt[] = { + "62/fs", "124/fs", "248/fs", "496/fs", "992/fs", "1984/fs", "3968/fs", + "7936/fs", "15872/fs", "31744/fs", "63488/fs", "126976/fs", + "253952/fs", "507904/fs", "1015808/fs", "2031616/fs" +}; + +static const struct soc_enum da7219_alc_hold_time = + SOC_ENUM_SINGLE(DA7219_ALC_CTRL3, DA7219_ALC_HOLD_SHIFT, + DA7219_ALC_HOLD_MAX, da7219_alc_hold_time_txt); + +static const char * const da7219_alc_env_rate_txt[] = { + "1/4", "1/16", "1/256", "1/65536" +}; + +static const struct soc_enum da7219_alc_env_attack_rate = + SOC_ENUM_SINGLE(DA7219_ALC_CTRL3, DA7219_ALC_INTEG_ATTACK_SHIFT, + DA7219_ALC_INTEG_MAX, da7219_alc_env_rate_txt); + +static const struct soc_enum da7219_alc_env_release_rate = + SOC_ENUM_SINGLE(DA7219_ALC_CTRL3, DA7219_ALC_INTEG_RELEASE_SHIFT, + DA7219_ALC_INTEG_MAX, da7219_alc_env_rate_txt); + +static const char * const da7219_alc_anticlip_step_txt[] = { + "0.034dB/fs", "0.068dB/fs", "0.136dB/fs", "0.272dB/fs" +}; + +static const struct soc_enum da7219_alc_anticlip_step = + SOC_ENUM_SINGLE(DA7219_ALC_ANTICLIP_CTRL, + DA7219_ALC_ANTICLIP_STEP_SHIFT, + DA7219_ALC_ANTICLIP_STEP_MAX, + da7219_alc_anticlip_step_txt); + +/* Input/Output Enums */ +static const char * const da7219_gain_ramp_rate_txt[] = { + "Nominal Rate * 8", "Nominal Rate", "Nominal Rate / 8", + "Nominal Rate / 16" +}; + +static const struct soc_enum da7219_gain_ramp_rate = + SOC_ENUM_SINGLE(DA7219_GAIN_RAMP_CTRL, DA7219_GAIN_RAMP_RATE_SHIFT, + DA7219_GAIN_RAMP_RATE_MAX, da7219_gain_ramp_rate_txt); + +static const char * const da7219_hpf_mode_txt[] = { + "Disabled", "Audio", "Voice" +}; + +static const unsigned int da7219_hpf_mode_val[] = { + DA7219_HPF_DISABLED, DA7219_HPF_AUDIO_EN, DA7219_HPF_VOICE_EN, +}; + +static const struct soc_enum da7219_adc_hpf_mode = + SOC_VALUE_ENUM_SINGLE(DA7219_ADC_FILTERS1, DA7219_HPF_MODE_SHIFT, + DA7219_HPF_MODE_MASK, DA7219_HPF_MODE_MAX, + da7219_hpf_mode_txt, da7219_hpf_mode_val); + +static const struct soc_enum da7219_dac_hpf_mode = + SOC_VALUE_ENUM_SINGLE(DA7219_DAC_FILTERS1, DA7219_HPF_MODE_SHIFT, + DA7219_HPF_MODE_MASK, DA7219_HPF_MODE_MAX, + da7219_hpf_mode_txt, da7219_hpf_mode_val); + +static const char * const da7219_audio_hpf_corner_txt[] = { + "2Hz", "4Hz", "8Hz", "16Hz" +}; + +static const struct soc_enum da7219_adc_audio_hpf_corner = + SOC_ENUM_SINGLE(DA7219_ADC_FILTERS1, + DA7219_ADC_AUDIO_HPF_CORNER_SHIFT, + DA7219_AUDIO_HPF_CORNER_MAX, + da7219_audio_hpf_corner_txt); + +static const struct soc_enum da7219_dac_audio_hpf_corner = + SOC_ENUM_SINGLE(DA7219_DAC_FILTERS1, + DA7219_DAC_AUDIO_HPF_CORNER_SHIFT, + DA7219_AUDIO_HPF_CORNER_MAX, + da7219_audio_hpf_corner_txt); + +static const char * const da7219_voice_hpf_corner_txt[] = { + "2.5Hz", "25Hz", "50Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz" +}; + +static const struct soc_enum da7219_adc_voice_hpf_corner = + SOC_ENUM_SINGLE(DA7219_ADC_FILTERS1, + DA7219_ADC_VOICE_HPF_CORNER_SHIFT, + DA7219_VOICE_HPF_CORNER_MAX, + da7219_voice_hpf_corner_txt); + +static const struct soc_enum da7219_dac_voice_hpf_corner = + SOC_ENUM_SINGLE(DA7219_DAC_FILTERS1, + DA7219_DAC_VOICE_HPF_CORNER_SHIFT, + DA7219_VOICE_HPF_CORNER_MAX, + da7219_voice_hpf_corner_txt); + +static const char * const da7219_tonegen_dtmf_key_txt[] = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", + "*", "#" +}; + +static const struct soc_enum da7219_tonegen_dtmf_key = + SOC_ENUM_SINGLE(DA7219_TONE_GEN_CFG1, DA7219_DTMF_REG_SHIFT, + DA7219_DTMF_REG_MAX, da7219_tonegen_dtmf_key_txt); + +static const char * const da7219_tonegen_swg_sel_txt[] = { + "Sum", "SWG1", "SWG2", "SWG1_1-Cos" +}; + +static const struct soc_enum da7219_tonegen_swg_sel = + SOC_ENUM_SINGLE(DA7219_TONE_GEN_CFG2, DA7219_SWG_SEL_SHIFT, + DA7219_SWG_SEL_MAX, da7219_tonegen_swg_sel_txt); + +/* Output Enums */ +static const char * const da7219_dac_softmute_rate_txt[] = { + "1 Sample", "2 Samples", "4 Samples", "8 Samples", "16 Samples", + "32 Samples", "64 Samples" +}; + +static const struct soc_enum da7219_dac_softmute_rate = + SOC_ENUM_SINGLE(DA7219_DAC_FILTERS5, DA7219_DAC_SOFTMUTE_RATE_SHIFT, + DA7219_DAC_SOFTMUTE_RATE_MAX, + da7219_dac_softmute_rate_txt); + +static const char * const da7219_dac_ng_setup_time_txt[] = { + "256 Samples", "512 Samples", "1024 Samples", "2048 Samples" +}; + +static const struct soc_enum da7219_dac_ng_setup_time = + SOC_ENUM_SINGLE(DA7219_DAC_NG_SETUP_TIME, + DA7219_DAC_NG_SETUP_TIME_SHIFT, + DA7219_DAC_NG_SETUP_TIME_MAX, + da7219_dac_ng_setup_time_txt); + +static const char * const da7219_dac_ng_rampup_txt[] = { + "0.22ms/dB", "0.0138ms/dB" +}; + +static const struct soc_enum da7219_dac_ng_rampup_rate = + SOC_ENUM_SINGLE(DA7219_DAC_NG_SETUP_TIME, + DA7219_DAC_NG_RAMPUP_RATE_SHIFT, + DA7219_DAC_NG_RAMP_RATE_MAX, + da7219_dac_ng_rampup_txt); + +static const char * const da7219_dac_ng_rampdown_txt[] = { + "0.88ms/dB", "14.08ms/dB" +}; + +static const struct soc_enum da7219_dac_ng_rampdown_rate = + SOC_ENUM_SINGLE(DA7219_DAC_NG_SETUP_TIME, + DA7219_DAC_NG_RAMPDN_RATE_SHIFT, + DA7219_DAC_NG_RAMP_RATE_MAX, + da7219_dac_ng_rampdown_txt); + + +static const char * const da7219_cp_track_mode_txt[] = { + "Largest Volume", "DAC Volume", "Signal Magnitude" +}; + +static const unsigned int da7219_cp_track_mode_val[] = { + DA7219_CP_MCHANGE_LARGEST_VOL, DA7219_CP_MCHANGE_DAC_VOL, + DA7219_CP_MCHANGE_SIG_MAG +}; + +static const struct soc_enum da7219_cp_track_mode = + SOC_VALUE_ENUM_SINGLE(DA7219_CP_CTRL, DA7219_CP_MCHANGE_SHIFT, + DA7219_CP_MCHANGE_REL_MASK, DA7219_CP_MCHANGE_MAX, + da7219_cp_track_mode_txt, + da7219_cp_track_mode_val); + + +/* + * Control Functions + */ + +/* Locked Kcontrol calls */ +static int da7219_volsw_locked_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&da7219->ctrl_lock); + ret = snd_soc_get_volsw(kcontrol, ucontrol); + mutex_unlock(&da7219->ctrl_lock); + + return ret; +} + +static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&da7219->ctrl_lock); + ret = snd_soc_put_volsw(kcontrol, ucontrol); + mutex_unlock(&da7219->ctrl_lock); + + return ret; +} + +static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&da7219->ctrl_lock); + ret = snd_soc_get_enum_double(kcontrol, ucontrol); + mutex_unlock(&da7219->ctrl_lock); + + return ret; +} + +static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&da7219->ctrl_lock); + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + mutex_unlock(&da7219->ctrl_lock); + + return ret; +} + +/* ALC */ +static void da7219_alc_calib(struct snd_soc_component *component) +{ + u8 mic_ctrl, mixin_ctrl, adc_ctrl, calib_ctrl; + + /* Save current state of mic control register */ + mic_ctrl = snd_soc_component_read32(component, DA7219_MIC_1_CTRL); + + /* Save current state of input mixer control register */ + mixin_ctrl = snd_soc_component_read32(component, DA7219_MIXIN_L_CTRL); + + /* Save current state of input ADC control register */ + adc_ctrl = snd_soc_component_read32(component, DA7219_ADC_L_CTRL); + + /* Enable then Mute MIC PGAs */ + snd_soc_component_update_bits(component, DA7219_MIC_1_CTRL, DA7219_MIC_1_AMP_EN_MASK, + DA7219_MIC_1_AMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_MIC_1_CTRL, + DA7219_MIC_1_AMP_MUTE_EN_MASK, + DA7219_MIC_1_AMP_MUTE_EN_MASK); + + /* Enable input mixers unmuted */ + snd_soc_component_update_bits(component, DA7219_MIXIN_L_CTRL, + DA7219_MIXIN_L_AMP_EN_MASK | + DA7219_MIXIN_L_AMP_MUTE_EN_MASK, + DA7219_MIXIN_L_AMP_EN_MASK); + + /* Enable input filters unmuted */ + snd_soc_component_update_bits(component, DA7219_ADC_L_CTRL, + DA7219_ADC_L_MUTE_EN_MASK | DA7219_ADC_L_EN_MASK, + DA7219_ADC_L_EN_MASK); + + /* Perform auto calibration */ + snd_soc_component_update_bits(component, DA7219_ALC_CTRL1, + DA7219_ALC_AUTO_CALIB_EN_MASK, + DA7219_ALC_AUTO_CALIB_EN_MASK); + do { + calib_ctrl = snd_soc_component_read32(component, DA7219_ALC_CTRL1); + } while (calib_ctrl & DA7219_ALC_AUTO_CALIB_EN_MASK); + + /* If auto calibration fails, disable DC offset, hybrid ALC */ + if (calib_ctrl & DA7219_ALC_CALIB_OVERFLOW_MASK) { + dev_warn(component->dev, + "ALC auto calibration failed with overflow\n"); + snd_soc_component_update_bits(component, DA7219_ALC_CTRL1, + DA7219_ALC_OFFSET_EN_MASK | + DA7219_ALC_SYNC_MODE_MASK, 0); + } else { + /* Enable DC offset cancellation, hybrid mode */ + snd_soc_component_update_bits(component, DA7219_ALC_CTRL1, + DA7219_ALC_OFFSET_EN_MASK | + DA7219_ALC_SYNC_MODE_MASK, + DA7219_ALC_OFFSET_EN_MASK | + DA7219_ALC_SYNC_MODE_MASK); + } + + /* Restore input filter control register to original state */ + snd_soc_component_write(component, DA7219_ADC_L_CTRL, adc_ctrl); + + /* Restore input mixer control registers to original state */ + snd_soc_component_write(component, DA7219_MIXIN_L_CTRL, mixin_ctrl); + + /* Restore MIC control registers to original states */ + snd_soc_component_write(component, DA7219_MIC_1_CTRL, mic_ctrl); +} + +static int da7219_mixin_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + /* + * If ALC in operation and value of control has been updated, + * make sure calibrated offsets are updated. + */ + if ((ret == 1) && (da7219->alc_en)) + da7219_alc_calib(component); + + return ret; +} + +static int da7219_alc_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + + /* Force ALC offset calibration if enabling ALC */ + if ((ucontrol->value.integer.value[0]) && (!da7219->alc_en)) { + da7219_alc_calib(component); + da7219->alc_en = true; + } else { + da7219->alc_en = false; + } + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +/* ToneGen */ +static int da7219_tonegen_freq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mixer_ctrl = + (struct soc_mixer_control *) kcontrol->private_value; + unsigned int reg = mixer_ctrl->reg; + u16 val; + int ret; + + mutex_lock(&da7219->ctrl_lock); + ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); + mutex_unlock(&da7219->ctrl_lock); + + if (ret) + return ret; + + /* + * Frequency value spans two 8-bit registers, lower then upper byte. + * Therefore we need to convert to host endianness here. + */ + ucontrol->value.integer.value[0] = le16_to_cpu(val); + + return 0; +} + +static int da7219_tonegen_freq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mixer_ctrl = + (struct soc_mixer_control *) kcontrol->private_value; + unsigned int reg = mixer_ctrl->reg; + u16 val; + int ret; + + /* + * Frequency value spans two 8-bit registers, lower then upper byte. + * Therefore we need to convert to little endian here to align with + * HW registers. + */ + val = cpu_to_le16(ucontrol->value.integer.value[0]); + + mutex_lock(&da7219->ctrl_lock); + ret = regmap_raw_write(da7219->regmap, reg, &val, sizeof(val)); + mutex_unlock(&da7219->ctrl_lock); + + return ret; +} + + +/* + * KControls + */ + +static const struct snd_kcontrol_new da7219_snd_controls[] = { + /* Mics */ + SOC_SINGLE_TLV("Mic Volume", DA7219_MIC_1_GAIN, + DA7219_MIC_1_AMP_GAIN_SHIFT, DA7219_MIC_1_AMP_GAIN_MAX, + DA7219_NO_INVERT, da7219_mic_gain_tlv), + SOC_SINGLE("Mic Switch", DA7219_MIC_1_CTRL, + DA7219_MIC_1_AMP_MUTE_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_INVERT), + + /* Mixer Input */ + SOC_SINGLE_EXT_TLV("Mixin Volume", DA7219_MIXIN_L_GAIN, + DA7219_MIXIN_L_AMP_GAIN_SHIFT, + DA7219_MIXIN_L_AMP_GAIN_MAX, DA7219_NO_INVERT, + snd_soc_get_volsw, da7219_mixin_gain_put, + da7219_mixin_gain_tlv), + SOC_SINGLE("Mixin Switch", DA7219_MIXIN_L_CTRL, + DA7219_MIXIN_L_AMP_MUTE_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_INVERT), + SOC_SINGLE("Mixin Gain Ramp Switch", DA7219_MIXIN_L_CTRL, + DA7219_MIXIN_L_AMP_RAMP_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_NO_INVERT), + SOC_SINGLE("Mixin ZC Gain Switch", DA7219_MIXIN_L_CTRL, + DA7219_MIXIN_L_AMP_ZC_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_NO_INVERT), + + /* ADC */ + SOC_SINGLE_TLV("Capture Digital Volume", DA7219_ADC_L_GAIN, + DA7219_ADC_L_DIGITAL_GAIN_SHIFT, + DA7219_ADC_L_DIGITAL_GAIN_MAX, DA7219_NO_INVERT, + da7219_adc_dig_gain_tlv), + SOC_SINGLE("Capture Digital Switch", DA7219_ADC_L_CTRL, + DA7219_ADC_L_MUTE_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_INVERT), + SOC_SINGLE("Capture Digital Gain Ramp Switch", DA7219_ADC_L_CTRL, + DA7219_ADC_L_RAMP_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_NO_INVERT), + + /* ALC */ + SOC_ENUM("ALC Attack Rate", da7219_alc_attack_rate), + SOC_ENUM("ALC Release Rate", da7219_alc_release_rate), + SOC_ENUM("ALC Hold Time", da7219_alc_hold_time), + SOC_ENUM("ALC Envelope Attack Rate", da7219_alc_env_attack_rate), + SOC_ENUM("ALC Envelope Release Rate", da7219_alc_env_release_rate), + SOC_SINGLE_TLV("ALC Noise Threshold", DA7219_ALC_NOISE, + DA7219_ALC_NOISE_SHIFT, DA7219_ALC_THRESHOLD_MAX, + DA7219_INVERT, da7219_alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Min Threshold", DA7219_ALC_TARGET_MIN, + DA7219_ALC_THRESHOLD_MIN_SHIFT, DA7219_ALC_THRESHOLD_MAX, + DA7219_INVERT, da7219_alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Threshold", DA7219_ALC_TARGET_MAX, + DA7219_ALC_THRESHOLD_MAX_SHIFT, DA7219_ALC_THRESHOLD_MAX, + DA7219_INVERT, da7219_alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Attenuation", DA7219_ALC_GAIN_LIMITS, + DA7219_ALC_ATTEN_MAX_SHIFT, DA7219_ALC_ATTEN_GAIN_MAX, + DA7219_NO_INVERT, da7219_alc_gain_tlv), + SOC_SINGLE_TLV("ALC Max Volume", DA7219_ALC_GAIN_LIMITS, + DA7219_ALC_GAIN_MAX_SHIFT, DA7219_ALC_ATTEN_GAIN_MAX, + DA7219_NO_INVERT, da7219_alc_gain_tlv), + SOC_SINGLE_RANGE_TLV("ALC Min Analog Volume", DA7219_ALC_ANA_GAIN_LIMITS, + DA7219_ALC_ANA_GAIN_MIN_SHIFT, + DA7219_ALC_ANA_GAIN_MIN, DA7219_ALC_ANA_GAIN_MAX, + DA7219_NO_INVERT, da7219_alc_ana_gain_tlv), + SOC_SINGLE_RANGE_TLV("ALC Max Analog Volume", DA7219_ALC_ANA_GAIN_LIMITS, + DA7219_ALC_ANA_GAIN_MAX_SHIFT, + DA7219_ALC_ANA_GAIN_MIN, DA7219_ALC_ANA_GAIN_MAX, + DA7219_NO_INVERT, da7219_alc_ana_gain_tlv), + SOC_ENUM("ALC Anticlip Step", da7219_alc_anticlip_step), + SOC_SINGLE("ALC Anticlip Switch", DA7219_ALC_ANTICLIP_CTRL, + DA7219_ALC_ANTIPCLIP_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_NO_INVERT), + SOC_SINGLE_EXT("ALC Switch", DA7219_ALC_CTRL1, DA7219_ALC_EN_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT, + snd_soc_get_volsw, da7219_alc_sw_put), + + /* Input High-Pass Filters */ + SOC_ENUM("ADC HPF Mode", da7219_adc_hpf_mode), + SOC_ENUM("ADC HPF Corner Audio", da7219_adc_audio_hpf_corner), + SOC_ENUM("ADC HPF Corner Voice", da7219_adc_voice_hpf_corner), + + /* Sidetone Filter */ + SOC_SINGLE_TLV("Sidetone Volume", DA7219_SIDETONE_GAIN, + DA7219_SIDETONE_GAIN_SHIFT, DA7219_SIDETONE_GAIN_MAX, + DA7219_NO_INVERT, da7219_sidetone_gain_tlv), + SOC_SINGLE("Sidetone Switch", DA7219_SIDETONE_CTRL, + DA7219_SIDETONE_MUTE_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_INVERT), + + /* Tone Generator */ + SOC_SINGLE_EXT_TLV("ToneGen Volume", DA7219_TONE_GEN_CFG2, + DA7219_TONE_GEN_GAIN_SHIFT, DA7219_TONE_GEN_GAIN_MAX, + DA7219_NO_INVERT, da7219_volsw_locked_get, + da7219_volsw_locked_put, da7219_tonegen_gain_tlv), + SOC_ENUM_EXT("ToneGen DTMF Key", da7219_tonegen_dtmf_key, + da7219_enum_locked_get, da7219_enum_locked_put), + SOC_SINGLE_EXT("ToneGen DTMF Switch", DA7219_TONE_GEN_CFG1, + DA7219_DTMF_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_NO_INVERT, da7219_volsw_locked_get, + da7219_volsw_locked_put), + SOC_ENUM_EXT("ToneGen Sinewave Gen Type", da7219_tonegen_swg_sel, + da7219_enum_locked_get, da7219_enum_locked_put), + SOC_SINGLE_EXT("ToneGen Sinewave1 Freq", DA7219_TONE_GEN_FREQ1_L, + DA7219_FREQ1_L_SHIFT, DA7219_FREQ_MAX, DA7219_NO_INVERT, + da7219_tonegen_freq_get, da7219_tonegen_freq_put), + SOC_SINGLE_EXT("ToneGen Sinewave2 Freq", DA7219_TONE_GEN_FREQ2_L, + DA7219_FREQ2_L_SHIFT, DA7219_FREQ_MAX, DA7219_NO_INVERT, + da7219_tonegen_freq_get, da7219_tonegen_freq_put), + SOC_SINGLE_EXT("ToneGen On Time", DA7219_TONE_GEN_ON_PER, + DA7219_BEEP_ON_PER_SHIFT, DA7219_BEEP_ON_OFF_MAX, + DA7219_NO_INVERT, da7219_volsw_locked_get, + da7219_volsw_locked_put), + SOC_SINGLE("ToneGen Off Time", DA7219_TONE_GEN_OFF_PER, + DA7219_BEEP_OFF_PER_SHIFT, DA7219_BEEP_ON_OFF_MAX, + DA7219_NO_INVERT), + + /* Gain ramping */ + SOC_ENUM("Gain Ramp Rate", da7219_gain_ramp_rate), + + /* DAC High-Pass Filter */ + SOC_ENUM_EXT("DAC HPF Mode", da7219_dac_hpf_mode, + da7219_enum_locked_get, da7219_enum_locked_put), + SOC_ENUM("DAC HPF Corner Audio", da7219_dac_audio_hpf_corner), + SOC_ENUM("DAC HPF Corner Voice", da7219_dac_voice_hpf_corner), + + /* DAC 5-Band Equaliser */ + SOC_SINGLE_TLV("DAC EQ Band1 Volume", DA7219_DAC_FILTERS2, + DA7219_DAC_EQ_BAND1_SHIFT, DA7219_DAC_EQ_BAND_MAX, + DA7219_NO_INVERT, da7219_dac_eq_band_tlv), + SOC_SINGLE_TLV("DAC EQ Band2 Volume", DA7219_DAC_FILTERS2, + DA7219_DAC_EQ_BAND2_SHIFT, DA7219_DAC_EQ_BAND_MAX, + DA7219_NO_INVERT, da7219_dac_eq_band_tlv), + SOC_SINGLE_TLV("DAC EQ Band3 Volume", DA7219_DAC_FILTERS3, + DA7219_DAC_EQ_BAND3_SHIFT, DA7219_DAC_EQ_BAND_MAX, + DA7219_NO_INVERT, da7219_dac_eq_band_tlv), + SOC_SINGLE_TLV("DAC EQ Band4 Volume", DA7219_DAC_FILTERS3, + DA7219_DAC_EQ_BAND4_SHIFT, DA7219_DAC_EQ_BAND_MAX, + DA7219_NO_INVERT, da7219_dac_eq_band_tlv), + SOC_SINGLE_TLV("DAC EQ Band5 Volume", DA7219_DAC_FILTERS4, + DA7219_DAC_EQ_BAND5_SHIFT, DA7219_DAC_EQ_BAND_MAX, + DA7219_NO_INVERT, da7219_dac_eq_band_tlv), + SOC_SINGLE_EXT("DAC EQ Switch", DA7219_DAC_FILTERS4, + DA7219_DAC_EQ_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_NO_INVERT, da7219_volsw_locked_get, + da7219_volsw_locked_put), + + /* DAC Softmute */ + SOC_ENUM("DAC Soft Mute Rate", da7219_dac_softmute_rate), + SOC_SINGLE_EXT("DAC Soft Mute Switch", DA7219_DAC_FILTERS5, + DA7219_DAC_SOFTMUTE_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_NO_INVERT, da7219_volsw_locked_get, + da7219_volsw_locked_put), + + /* DAC Noise Gate */ + SOC_ENUM("DAC NG Setup Time", da7219_dac_ng_setup_time), + SOC_ENUM("DAC NG Rampup Rate", da7219_dac_ng_rampup_rate), + SOC_ENUM("DAC NG Rampdown Rate", da7219_dac_ng_rampdown_rate), + SOC_SINGLE_TLV("DAC NG Off Threshold", DA7219_DAC_NG_OFF_THRESH, + DA7219_DAC_NG_OFF_THRESHOLD_SHIFT, + DA7219_DAC_NG_THRESHOLD_MAX, DA7219_NO_INVERT, + da7219_dac_ng_threshold_tlv), + SOC_SINGLE_TLV("DAC NG On Threshold", DA7219_DAC_NG_ON_THRESH, + DA7219_DAC_NG_ON_THRESHOLD_SHIFT, + DA7219_DAC_NG_THRESHOLD_MAX, DA7219_NO_INVERT, + da7219_dac_ng_threshold_tlv), + SOC_SINGLE("DAC NG Switch", DA7219_DAC_NG_CTRL, DA7219_DAC_NG_EN_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), + + /* DACs */ + SOC_DOUBLE_R_EXT_TLV("Playback Digital Volume", DA7219_DAC_L_GAIN, + DA7219_DAC_R_GAIN, DA7219_DAC_L_DIGITAL_GAIN_SHIFT, + DA7219_DAC_DIGITAL_GAIN_MAX, DA7219_NO_INVERT, + da7219_volsw_locked_get, da7219_volsw_locked_put, + da7219_dac_dig_gain_tlv), + SOC_DOUBLE_R_EXT("Playback Digital Switch", DA7219_DAC_L_CTRL, + DA7219_DAC_R_CTRL, DA7219_DAC_L_MUTE_EN_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_INVERT, + da7219_volsw_locked_get, da7219_volsw_locked_put), + SOC_DOUBLE_R("Playback Digital Gain Ramp Switch", DA7219_DAC_L_CTRL, + DA7219_DAC_R_CTRL, DA7219_DAC_L_RAMP_EN_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), + + /* CP */ + SOC_ENUM("Charge Pump Track Mode", da7219_cp_track_mode), + SOC_SINGLE("Charge Pump Threshold", DA7219_CP_VOL_THRESHOLD1, + DA7219_CP_THRESH_VDD2_SHIFT, DA7219_CP_THRESH_VDD2_MAX, + DA7219_NO_INVERT), + + /* Headphones */ + SOC_DOUBLE_R_EXT_TLV("Headphone Volume", DA7219_HP_L_GAIN, + DA7219_HP_R_GAIN, DA7219_HP_L_AMP_GAIN_SHIFT, + DA7219_HP_AMP_GAIN_MAX, DA7219_NO_INVERT, + da7219_volsw_locked_get, da7219_volsw_locked_put, + da7219_hp_gain_tlv), + SOC_DOUBLE_R_EXT("Headphone Switch", DA7219_HP_L_CTRL, DA7219_HP_R_CTRL, + DA7219_HP_L_AMP_MUTE_EN_SHIFT, DA7219_SWITCH_EN_MAX, + DA7219_INVERT, da7219_volsw_locked_get, + da7219_volsw_locked_put), + SOC_DOUBLE_R("Headphone Gain Ramp Switch", DA7219_HP_L_CTRL, + DA7219_HP_R_CTRL, DA7219_HP_L_AMP_RAMP_EN_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), + SOC_DOUBLE_R("Headphone ZC Gain Switch", DA7219_HP_L_CTRL, + DA7219_HP_R_CTRL, DA7219_HP_L_AMP_ZC_EN_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), +}; + + +/* + * DAPM Mux Controls + */ + +static const char * const da7219_out_sel_txt[] = { + "ADC", "Tone Generator", "DAIL", "DAIR" +}; + +static const struct soc_enum da7219_out_dail_sel = + SOC_ENUM_SINGLE(DA7219_DIG_ROUTING_DAI, + DA7219_DAI_L_SRC_SHIFT, + DA7219_OUT_SRC_MAX, + da7219_out_sel_txt); + +static const struct snd_kcontrol_new da7219_out_dail_sel_mux = + SOC_DAPM_ENUM("Out DAIL Mux", da7219_out_dail_sel); + +static const struct soc_enum da7219_out_dair_sel = + SOC_ENUM_SINGLE(DA7219_DIG_ROUTING_DAI, + DA7219_DAI_R_SRC_SHIFT, + DA7219_OUT_SRC_MAX, + da7219_out_sel_txt); + +static const struct snd_kcontrol_new da7219_out_dair_sel_mux = + SOC_DAPM_ENUM("Out DAIR Mux", da7219_out_dair_sel); + +static const struct soc_enum da7219_out_dacl_sel = + SOC_ENUM_SINGLE(DA7219_DIG_ROUTING_DAC, + DA7219_DAC_L_SRC_SHIFT, + DA7219_OUT_SRC_MAX, + da7219_out_sel_txt); + +static const struct snd_kcontrol_new da7219_out_dacl_sel_mux = + SOC_DAPM_ENUM("Out DACL Mux", da7219_out_dacl_sel); + +static const struct soc_enum da7219_out_dacr_sel = + SOC_ENUM_SINGLE(DA7219_DIG_ROUTING_DAC, + DA7219_DAC_R_SRC_SHIFT, + DA7219_OUT_SRC_MAX, + da7219_out_sel_txt); + +static const struct snd_kcontrol_new da7219_out_dacr_sel_mux = + SOC_DAPM_ENUM("Out DACR Mux", da7219_out_dacr_sel); + + +/* + * DAPM Mixer Controls + */ + +static const struct snd_kcontrol_new da7219_mixin_controls[] = { + SOC_DAPM_SINGLE("Mic Switch", DA7219_MIXIN_L_SELECT, + DA7219_MIXIN_L_MIX_SELECT_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), +}; + +static const struct snd_kcontrol_new da7219_mixout_l_controls[] = { + SOC_DAPM_SINGLE("DACL Switch", DA7219_MIXOUT_L_SELECT, + DA7219_MIXOUT_L_MIX_SELECT_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), +}; + +static const struct snd_kcontrol_new da7219_mixout_r_controls[] = { + SOC_DAPM_SINGLE("DACR Switch", DA7219_MIXOUT_R_SELECT, + DA7219_MIXOUT_R_MIX_SELECT_SHIFT, + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), +}; + +#define DA7219_DMIX_ST_CTRLS(reg) \ + SOC_DAPM_SINGLE("Out FilterL Switch", reg, \ + DA7219_DMIX_ST_SRC_OUTFILT1L_SHIFT, \ + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), \ + SOC_DAPM_SINGLE("Out FilterR Switch", reg, \ + DA7219_DMIX_ST_SRC_OUTFILT1R_SHIFT, \ + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT), \ + SOC_DAPM_SINGLE("Sidetone Switch", reg, \ + DA7219_DMIX_ST_SRC_SIDETONE_SHIFT, \ + DA7219_SWITCH_EN_MAX, DA7219_NO_INVERT) \ + +static const struct snd_kcontrol_new da7219_st_out_filtl_mix_controls[] = { + DA7219_DMIX_ST_CTRLS(DA7219_DROUTING_ST_OUTFILT_1L), +}; + +static const struct snd_kcontrol_new da7219_st_out_filtr_mix_controls[] = { + DA7219_DMIX_ST_CTRLS(DA7219_DROUTING_ST_OUTFILT_1R), +}; + + +/* + * DAPM Events + */ + +static int da7219_mic_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (da7219->micbias_on_event) { + /* + * Delay only for first capture after bias enabled to + * avoid possible DC offset related noise. + */ + da7219->micbias_on_event = false; + msleep(da7219->mic_pga_delay); + } + break; + default: + break; + } + + return 0; +} + +static int da7219_dai_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + u8 pll_ctrl, pll_status; + int i = 0, ret; + bool srm_lock = false; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (da7219->master) { + /* Enable DAI clks for master mode */ + if (da7219->dai_clks) { + ret = clk_prepare_enable(da7219->dai_clks); + if (ret) { + dev_err(component->dev, + "Failed to enable dai_clks\n"); + return ret; + } + } else { + snd_soc_component_update_bits(component, + DA7219_DAI_CLK_MODE, + DA7219_DAI_CLK_EN_MASK, + DA7219_DAI_CLK_EN_MASK); + } + } + + /* PC synchronised to DAI */ + snd_soc_component_update_bits(component, DA7219_PC_COUNT, + DA7219_PC_FREERUN_MASK, 0); + + /* Slave mode, if SRM not enabled no need for status checks */ + pll_ctrl = snd_soc_component_read32(component, DA7219_PLL_CTRL); + if ((pll_ctrl & DA7219_PLL_MODE_MASK) != DA7219_PLL_MODE_SRM) + return 0; + + /* Check SRM has locked */ + do { + pll_status = snd_soc_component_read32(component, DA7219_PLL_SRM_STS); + if (pll_status & DA7219_PLL_SRM_STS_SRM_LOCK) { + srm_lock = true; + } else { + ++i; + msleep(50); + } + } while ((i < DA7219_SRM_CHECK_RETRIES) & (!srm_lock)); + + if (!srm_lock) + dev_warn(component->dev, "SRM failed to lock\n"); + + return 0; + case SND_SOC_DAPM_POST_PMD: + /* PC free-running */ + snd_soc_component_update_bits(component, DA7219_PC_COUNT, + DA7219_PC_FREERUN_MASK, + DA7219_PC_FREERUN_MASK); + + /* Disable DAI clks if in master mode */ + if (da7219->master) { + if (da7219->dai_clks) + clk_disable_unprepare(da7219->dai_clks); + else + snd_soc_component_update_bits(component, + DA7219_DAI_CLK_MODE, + DA7219_DAI_CLK_EN_MASK, + 0); + } + + return 0; + default: + return -EINVAL; + } +} + +static int da7219_settling_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + msleep(DA7219_SETTLING_DELAY); + break; + default: + break; + } + + return 0; +} + +static int da7219_mixout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u8 hp_ctrl, min_gain_mask; + + switch (w->reg) { + case DA7219_MIXOUT_L_CTRL: + hp_ctrl = DA7219_HP_L_CTRL; + min_gain_mask = DA7219_HP_L_AMP_MIN_GAIN_EN_MASK; + break; + case DA7219_MIXOUT_R_CTRL: + hp_ctrl = DA7219_HP_R_CTRL; + min_gain_mask = DA7219_HP_R_AMP_MIN_GAIN_EN_MASK; + break; + default: + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + /* Enable minimum gain on HP to avoid pops */ + snd_soc_component_update_bits(component, hp_ctrl, min_gain_mask, + min_gain_mask); + + msleep(DA7219_MIN_GAIN_DELAY); + + break; + case SND_SOC_DAPM_POST_PMU: + /* Remove minimum gain on HP */ + snd_soc_component_update_bits(component, hp_ctrl, min_gain_mask, 0); + + break; + } + + return 0; +} + +static int da7219_gain_ramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_PRE_PMD: + /* Ensure nominal gain ramping for DAPM sequence */ + da7219->gain_ramp_ctrl = + snd_soc_component_read32(component, DA7219_GAIN_RAMP_CTRL); + snd_soc_component_write(component, DA7219_GAIN_RAMP_CTRL, + DA7219_GAIN_RAMP_RATE_NOMINAL); + break; + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_POST_PMD: + /* Restore previous gain ramp settings */ + snd_soc_component_write(component, DA7219_GAIN_RAMP_CTRL, + da7219->gain_ramp_ctrl); + break; + } + + return 0; +} + + +/* + * DAPM Widgets + */ + +static const struct snd_soc_dapm_widget da7219_dapm_widgets[] = { + /* Input Supplies */ + SND_SOC_DAPM_SUPPLY("Mic Bias", DA7219_MICBIAS_CTRL, + DA7219_MICBIAS1_EN_SHIFT, DA7219_NO_INVERT, + NULL, 0), + + /* Inputs */ + SND_SOC_DAPM_INPUT("MIC"), + + /* Input PGAs */ + SND_SOC_DAPM_PGA_E("Mic PGA", DA7219_MIC_1_CTRL, + DA7219_MIC_1_AMP_EN_SHIFT, DA7219_NO_INVERT, + NULL, 0, da7219_mic_pga_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("Mixin PGA", DA7219_MIXIN_L_CTRL, + DA7219_MIXIN_L_AMP_EN_SHIFT, DA7219_NO_INVERT, + NULL, 0, da7219_settling_event, SND_SOC_DAPM_POST_PMU), + + /* Input Filters */ + SND_SOC_DAPM_ADC("ADC", NULL, DA7219_ADC_L_CTRL, DA7219_ADC_L_EN_SHIFT, + DA7219_NO_INVERT), + + /* Tone Generator */ + SND_SOC_DAPM_SIGGEN("TONE"), + SND_SOC_DAPM_PGA("Tone Generator", DA7219_TONE_GEN_CFG1, + DA7219_START_STOPN_SHIFT, DA7219_NO_INVERT, NULL, 0), + + /* Sidetone Input */ + SND_SOC_DAPM_ADC("Sidetone Filter", NULL, DA7219_SIDETONE_CTRL, + DA7219_SIDETONE_EN_SHIFT, DA7219_NO_INVERT), + + /* Input Mixer Supply */ + SND_SOC_DAPM_SUPPLY("Mixer In Supply", DA7219_MIXIN_L_CTRL, + DA7219_MIXIN_L_MIX_EN_SHIFT, DA7219_NO_INVERT, + NULL, 0), + + /* Input Mixer */ + SND_SOC_DAPM_MIXER("Mixer In", SND_SOC_NOPM, 0, 0, + da7219_mixin_controls, + ARRAY_SIZE(da7219_mixin_controls)), + + /* Input Muxes */ + SND_SOC_DAPM_MUX("Out DAIL Mux", SND_SOC_NOPM, 0, 0, + &da7219_out_dail_sel_mux), + SND_SOC_DAPM_MUX("Out DAIR Mux", SND_SOC_NOPM, 0, 0, + &da7219_out_dair_sel_mux), + + /* DAI Supply */ + SND_SOC_DAPM_SUPPLY("DAI", DA7219_DAI_CTRL, DA7219_DAI_EN_SHIFT, + DA7219_NO_INVERT, da7219_dai_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* DAI */ + SND_SOC_DAPM_AIF_OUT("DAIOUT", "Capture", 0, DA7219_DAI_TDM_CTRL, + DA7219_DAI_OE_SHIFT, DA7219_NO_INVERT), + SND_SOC_DAPM_AIF_IN("DAIIN", "Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Output Muxes */ + SND_SOC_DAPM_MUX("Out DACL Mux", SND_SOC_NOPM, 0, 0, + &da7219_out_dacl_sel_mux), + SND_SOC_DAPM_MUX("Out DACR Mux", SND_SOC_NOPM, 0, 0, + &da7219_out_dacr_sel_mux), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Mixer Out FilterL", SND_SOC_NOPM, 0, 0, + da7219_mixout_l_controls, + ARRAY_SIZE(da7219_mixout_l_controls)), + SND_SOC_DAPM_MIXER("Mixer Out FilterR", SND_SOC_NOPM, 0, 0, + da7219_mixout_r_controls, + ARRAY_SIZE(da7219_mixout_r_controls)), + + /* Sidetone Mixers */ + SND_SOC_DAPM_MIXER("ST Mixer Out FilterL", SND_SOC_NOPM, 0, 0, + da7219_st_out_filtl_mix_controls, + ARRAY_SIZE(da7219_st_out_filtl_mix_controls)), + SND_SOC_DAPM_MIXER("ST Mixer Out FilterR", SND_SOC_NOPM, 0, + 0, da7219_st_out_filtr_mix_controls, + ARRAY_SIZE(da7219_st_out_filtr_mix_controls)), + + /* DACs */ + SND_SOC_DAPM_DAC_E("DACL", NULL, DA7219_DAC_L_CTRL, + DA7219_DAC_L_EN_SHIFT, DA7219_NO_INVERT, + da7219_settling_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("DACR", NULL, DA7219_DAC_R_CTRL, + DA7219_DAC_R_EN_SHIFT, DA7219_NO_INVERT, + da7219_settling_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Output PGAs */ + SND_SOC_DAPM_PGA_E("Mixout Left PGA", DA7219_MIXOUT_L_CTRL, + DA7219_MIXOUT_L_AMP_EN_SHIFT, DA7219_NO_INVERT, + NULL, 0, da7219_mixout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Mixout Right PGA", DA7219_MIXOUT_R_CTRL, + DA7219_MIXOUT_R_AMP_EN_SHIFT, DA7219_NO_INVERT, + NULL, 0, da7219_mixout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY_S("Headphone Left PGA", 1, DA7219_HP_L_CTRL, + DA7219_HP_L_AMP_EN_SHIFT, DA7219_NO_INVERT, + da7219_settling_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("Headphone Right PGA", 1, DA7219_HP_R_CTRL, + DA7219_HP_R_AMP_EN_SHIFT, DA7219_NO_INVERT, + da7219_settling_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Output Supplies */ + SND_SOC_DAPM_SUPPLY_S("Charge Pump", 0, DA7219_CP_CTRL, + DA7219_CP_EN_SHIFT, DA7219_NO_INVERT, + da7219_settling_event, + SND_SOC_DAPM_POST_PMU), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + + /* Pre/Post Power */ + SND_SOC_DAPM_PRE("Pre Power Gain Ramp", da7219_gain_ramp_event), + SND_SOC_DAPM_POST("Post Power Gain Ramp", da7219_gain_ramp_event), +}; + + +/* + * DAPM Mux Routes + */ + +#define DA7219_OUT_DAI_MUX_ROUTES(name) \ + {name, "ADC", "Mixer In"}, \ + {name, "Tone Generator", "Tone Generator"}, \ + {name, "DAIL", "DAIOUT"}, \ + {name, "DAIR", "DAIOUT"} + +#define DA7219_OUT_DAC_MUX_ROUTES(name) \ + {name, "ADC", "Mixer In"}, \ + {name, "Tone Generator", "Tone Generator"}, \ + {name, "DAIL", "DAIIN"}, \ + {name, "DAIR", "DAIIN"} + +/* + * DAPM Mixer Routes + */ + +#define DA7219_DMIX_ST_ROUTES(name) \ + {name, "Out FilterL Switch", "Mixer Out FilterL"}, \ + {name, "Out FilterR Switch", "Mixer Out FilterR"}, \ + {name, "Sidetone Switch", "Sidetone Filter"} + + +/* + * DAPM audio route definition + */ + +static const struct snd_soc_dapm_route da7219_audio_map[] = { + /* Input paths */ + {"MIC", NULL, "Mic Bias"}, + {"Mic PGA", NULL, "MIC"}, + {"Mixin PGA", NULL, "Mic PGA"}, + {"ADC", NULL, "Mixin PGA"}, + + {"Mixer In", NULL, "Mixer In Supply"}, + {"Mixer In", "Mic Switch", "ADC"}, + + {"Sidetone Filter", NULL, "Mixer In"}, + + {"Tone Generator", NULL, "TONE"}, + + DA7219_OUT_DAI_MUX_ROUTES("Out DAIL Mux"), + DA7219_OUT_DAI_MUX_ROUTES("Out DAIR Mux"), + + {"DAIOUT", NULL, "Out DAIL Mux"}, + {"DAIOUT", NULL, "Out DAIR Mux"}, + {"DAIOUT", NULL, "DAI"}, + + /* Output paths */ + {"DAIIN", NULL, "DAI"}, + + DA7219_OUT_DAC_MUX_ROUTES("Out DACL Mux"), + DA7219_OUT_DAC_MUX_ROUTES("Out DACR Mux"), + + {"Mixer Out FilterL", "DACL Switch", "Out DACL Mux"}, + {"Mixer Out FilterR", "DACR Switch", "Out DACR Mux"}, + + DA7219_DMIX_ST_ROUTES("ST Mixer Out FilterL"), + DA7219_DMIX_ST_ROUTES("ST Mixer Out FilterR"), + + {"DACL", NULL, "ST Mixer Out FilterL"}, + {"DACR", NULL, "ST Mixer Out FilterR"}, + + {"Mixout Left PGA", NULL, "DACL"}, + {"Mixout Right PGA", NULL, "DACR"}, + + {"HPL", NULL, "Mixout Left PGA"}, + {"HPR", NULL, "Mixout Right PGA"}, + + {"HPL", NULL, "Headphone Left PGA"}, + {"HPR", NULL, "Headphone Right PGA"}, + + {"HPL", NULL, "Charge Pump"}, + {"HPR", NULL, "Charge Pump"}, +}; + + +/* + * DAI operations + */ + +static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret = 0; + + if ((da7219->clk_src == clk_id) && (da7219->mclk_rate == freq)) + return 0; + + if ((freq < 2000000) || (freq > 54000000)) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; + } + + mutex_lock(&da7219->pll_lock); + + switch (clk_id) { + case DA7219_CLKSRC_MCLK_SQR: + snd_soc_component_update_bits(component, DA7219_PLL_CTRL, + DA7219_PLL_MCLK_SQR_EN_MASK, + DA7219_PLL_MCLK_SQR_EN_MASK); + break; + case DA7219_CLKSRC_MCLK: + snd_soc_component_update_bits(component, DA7219_PLL_CTRL, + DA7219_PLL_MCLK_SQR_EN_MASK, 0); + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); + mutex_unlock(&da7219->pll_lock); + return -EINVAL; + } + + da7219->clk_src = clk_id; + + if (da7219->mclk) { + freq = clk_round_rate(da7219->mclk, freq); + ret = clk_set_rate(da7219->mclk, freq); + if (ret) { + dev_err(codec_dai->dev, "Failed to set clock rate %d\n", + freq); + mutex_unlock(&da7219->pll_lock); + return ret; + } + } + + da7219->mclk_rate = freq; + + mutex_unlock(&da7219->pll_lock); + + return 0; +} + +int da7219_set_pll(struct snd_soc_component *component, int source, unsigned int fout) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + u8 pll_ctrl, indiv_bits, indiv; + u8 pll_frac_top, pll_frac_bot, pll_integer; + u32 freq_ref; + u64 frac_div; + + /* Verify 2MHz - 54MHz MCLK provided, and set input divider */ + if (da7219->mclk_rate < 2000000) { + dev_err(component->dev, "PLL input clock %d below valid range\n", + da7219->mclk_rate); + return -EINVAL; + } else if (da7219->mclk_rate <= 4500000) { + indiv_bits = DA7219_PLL_INDIV_2_TO_4_5_MHZ; + indiv = DA7219_PLL_INDIV_2_TO_4_5_MHZ_VAL; + } else if (da7219->mclk_rate <= 9000000) { + indiv_bits = DA7219_PLL_INDIV_4_5_TO_9_MHZ; + indiv = DA7219_PLL_INDIV_4_5_TO_9_MHZ_VAL; + } else if (da7219->mclk_rate <= 18000000) { + indiv_bits = DA7219_PLL_INDIV_9_TO_18_MHZ; + indiv = DA7219_PLL_INDIV_9_TO_18_MHZ_VAL; + } else if (da7219->mclk_rate <= 36000000) { + indiv_bits = DA7219_PLL_INDIV_18_TO_36_MHZ; + indiv = DA7219_PLL_INDIV_18_TO_36_MHZ_VAL; + } else if (da7219->mclk_rate <= 54000000) { + indiv_bits = DA7219_PLL_INDIV_36_TO_54_MHZ; + indiv = DA7219_PLL_INDIV_36_TO_54_MHZ_VAL; + } else { + dev_err(component->dev, "PLL input clock %d above valid range\n", + da7219->mclk_rate); + return -EINVAL; + } + freq_ref = (da7219->mclk_rate / indiv); + pll_ctrl = indiv_bits; + + /* Configure PLL */ + switch (source) { + case DA7219_SYSCLK_MCLK: + pll_ctrl |= DA7219_PLL_MODE_BYPASS; + snd_soc_component_update_bits(component, DA7219_PLL_CTRL, + DA7219_PLL_INDIV_MASK | + DA7219_PLL_MODE_MASK, pll_ctrl); + return 0; + case DA7219_SYSCLK_PLL: + pll_ctrl |= DA7219_PLL_MODE_NORMAL; + break; + case DA7219_SYSCLK_PLL_SRM: + pll_ctrl |= DA7219_PLL_MODE_SRM; + break; + default: + dev_err(component->dev, "Invalid PLL config\n"); + return -EINVAL; + } + + /* Calculate dividers for PLL */ + pll_integer = fout / freq_ref; + frac_div = (u64)(fout % freq_ref) * 8192ULL; + do_div(frac_div, freq_ref); + pll_frac_top = (frac_div >> DA7219_BYTE_SHIFT) & DA7219_BYTE_MASK; + pll_frac_bot = (frac_div) & DA7219_BYTE_MASK; + + /* Write PLL config & dividers */ + snd_soc_component_write(component, DA7219_PLL_FRAC_TOP, pll_frac_top); + snd_soc_component_write(component, DA7219_PLL_FRAC_BOT, pll_frac_bot); + snd_soc_component_write(component, DA7219_PLL_INTEGER, pll_integer); + snd_soc_component_update_bits(component, DA7219_PLL_CTRL, + DA7219_PLL_INDIV_MASK | DA7219_PLL_MODE_MASK, + pll_ctrl); + + return 0; +} + +static int da7219_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&da7219->pll_lock); + ret = da7219_set_pll(component, source, fout); + mutex_unlock(&da7219->pll_lock); + + return ret; +} + +static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + u8 dai_clk_mode = 0, dai_ctrl = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + da7219->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + da7219->master = false; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + dai_clk_mode |= DA7219_DAI_WCLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + dai_clk_mode |= DA7219_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + dai_clk_mode |= DA7219_DAI_WCLK_POL_INV | + DA7219_DAI_CLK_POL_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_DSP_B: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + dai_clk_mode |= DA7219_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + dai_clk_mode |= DA7219_DAI_WCLK_POL_INV | + DA7219_DAI_CLK_POL_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + dai_clk_mode |= DA7219_DAI_WCLK_POL_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dai_ctrl |= DA7219_DAI_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + dai_ctrl |= DA7219_DAI_FORMAT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dai_ctrl |= DA7219_DAI_FORMAT_RIGHT_J; + break; + case SND_SOC_DAIFMT_DSP_B: + dai_ctrl |= DA7219_DAI_FORMAT_DSP; + break; + default: + return -EINVAL; + } + + /* By default 64 BCLKs per WCLK is supported */ + dai_clk_mode |= DA7219_DAI_BCLKS_PER_WCLK_64; + + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, + DA7219_DAI_BCLKS_PER_WCLK_MASK | + DA7219_DAI_CLK_POL_MASK | DA7219_DAI_WCLK_POL_MASK, + dai_clk_mode); + snd_soc_component_update_bits(component, DA7219_DAI_CTRL, DA7219_DAI_FORMAT_MASK, + dai_ctrl); + + return 0; +} + +static int da7219_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + u8 dai_bclks_per_wclk; + u16 offset; + u32 frame_size; + + /* No channels enabled so disable TDM, revert to 64-bit frames */ + if (!tx_mask) { + snd_soc_component_update_bits(component, DA7219_DAI_TDM_CTRL, + DA7219_DAI_TDM_CH_EN_MASK | + DA7219_DAI_TDM_MODE_EN_MASK, 0); + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, + DA7219_DAI_BCLKS_PER_WCLK_MASK, + DA7219_DAI_BCLKS_PER_WCLK_64); + return 0; + } + + /* Check we have valid slots */ + if (fls(tx_mask) > DA7219_DAI_TDM_MAX_SLOTS) { + dev_err(component->dev, "Invalid number of slots, max = %d\n", + DA7219_DAI_TDM_MAX_SLOTS); + return -EINVAL; + } + + /* Check we have a valid offset given */ + if (rx_mask > DA7219_DAI_OFFSET_MAX) { + dev_err(component->dev, "Invalid slot offset, max = %d\n", + DA7219_DAI_OFFSET_MAX); + return -EINVAL; + } + + /* Calculate & validate frame size based on slot info provided. */ + frame_size = slots * slot_width; + switch (frame_size) { + case 32: + dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_32; + break; + case 64: + dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_64; + break; + case 128: + dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_128; + break; + case 256: + dai_bclks_per_wclk = DA7219_DAI_BCLKS_PER_WCLK_256; + break; + default: + dev_err(component->dev, "Invalid frame size %d\n", frame_size); + return -EINVAL; + } + + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, + DA7219_DAI_BCLKS_PER_WCLK_MASK, + dai_bclks_per_wclk); + + offset = cpu_to_le16(rx_mask); + regmap_bulk_write(da7219->regmap, DA7219_DAI_OFFSET_LOWER, + &offset, sizeof(offset)); + + snd_soc_component_update_bits(component, DA7219_DAI_TDM_CTRL, + DA7219_DAI_TDM_CH_EN_MASK | + DA7219_DAI_TDM_MODE_EN_MASK, + (tx_mask << DA7219_DAI_TDM_CH_EN_SHIFT) | + DA7219_DAI_TDM_MODE_EN_MASK); + + return 0; +} + +static int da7219_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u8 dai_ctrl = 0, fs; + unsigned int channels; + + switch (params_width(params)) { + case 16: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S16_LE; + break; + case 20: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S20_LE; + break; + case 24: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S24_LE; + break; + case 32: + dai_ctrl |= DA7219_DAI_WORD_LENGTH_S32_LE; + break; + default: + return -EINVAL; + } + + channels = params_channels(params); + if ((channels < 1) || (channels > DA7219_DAI_CH_NUM_MAX)) { + dev_err(component->dev, + "Invalid number of channels, only 1 to %d supported\n", + DA7219_DAI_CH_NUM_MAX); + return -EINVAL; + } + dai_ctrl |= channels << DA7219_DAI_CH_NUM_SHIFT; + + switch (params_rate(params)) { + case 8000: + fs = DA7219_SR_8000; + break; + case 11025: + fs = DA7219_SR_11025; + break; + case 12000: + fs = DA7219_SR_12000; + break; + case 16000: + fs = DA7219_SR_16000; + break; + case 22050: + fs = DA7219_SR_22050; + break; + case 24000: + fs = DA7219_SR_24000; + break; + case 32000: + fs = DA7219_SR_32000; + break; + case 44100: + fs = DA7219_SR_44100; + break; + case 48000: + fs = DA7219_SR_48000; + break; + case 88200: + fs = DA7219_SR_88200; + break; + case 96000: + fs = DA7219_SR_96000; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, DA7219_DAI_CTRL, + DA7219_DAI_WORD_LENGTH_MASK | + DA7219_DAI_CH_NUM_MASK, + dai_ctrl); + snd_soc_component_write(component, DA7219_SR, fs); + + return 0; +} + +static const struct snd_soc_dai_ops da7219_dai_ops = { + .hw_params = da7219_hw_params, + .set_sysclk = da7219_set_dai_sysclk, + .set_pll = da7219_set_dai_pll, + .set_fmt = da7219_set_dai_fmt, + .set_tdm_slot = da7219_set_dai_tdm_slot, +}; + +#define DA7219_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver da7219_dai = { + .name = "da7219-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = DA7219_DAI_CH_NUM_MAX, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7219_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = DA7219_DAI_CH_NUM_MAX, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7219_FORMATS, + }, + .ops = &da7219_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, +}; + + +/* + * DT/ACPI + */ + +static const struct of_device_id da7219_of_match[] = { + { .compatible = "dlg,da7219", }, + { } +}; +MODULE_DEVICE_TABLE(of, da7219_of_match); + +static const struct acpi_device_id da7219_acpi_match[] = { + { .id = "DLGS7219", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, da7219_acpi_match); + +static enum da7219_micbias_voltage + da7219_fw_micbias_lvl(struct device *dev, u32 val) +{ + switch (val) { + case 1600: + return DA7219_MICBIAS_1_6V; + case 1800: + return DA7219_MICBIAS_1_8V; + case 2000: + return DA7219_MICBIAS_2_0V; + case 2200: + return DA7219_MICBIAS_2_2V; + case 2400: + return DA7219_MICBIAS_2_4V; + case 2600: + return DA7219_MICBIAS_2_6V; + default: + dev_warn(dev, "Invalid micbias level"); + return DA7219_MICBIAS_2_2V; + } +} + +static enum da7219_mic_amp_in_sel + da7219_fw_mic_amp_in_sel(struct device *dev, const char *str) +{ + if (!strcmp(str, "diff")) { + return DA7219_MIC_AMP_IN_SEL_DIFF; + } else if (!strcmp(str, "se_p")) { + return DA7219_MIC_AMP_IN_SEL_SE_P; + } else if (!strcmp(str, "se_n")) { + return DA7219_MIC_AMP_IN_SEL_SE_N; + } else { + dev_warn(dev, "Invalid mic input type selection"); + return DA7219_MIC_AMP_IN_SEL_DIFF; + } +} + +static struct da7219_pdata *da7219_fw_to_pdata(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + struct da7219_pdata *pdata; + const char *of_str; + u32 of_val32; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->wakeup_source = device_property_read_bool(dev, "wakeup-source"); + + pdata->dai_clks_name = "da7219-dai-clks"; + if (device_property_read_string(dev, "clock-output-names", + &pdata->dai_clks_name)) + dev_warn(dev, "Using default clk name: %s\n", + pdata->dai_clks_name); + + if (device_property_read_u32(dev, "dlg,micbias-lvl", &of_val32) >= 0) + pdata->micbias_lvl = da7219_fw_micbias_lvl(dev, of_val32); + else + pdata->micbias_lvl = DA7219_MICBIAS_2_2V; + + if (!device_property_read_string(dev, "dlg,mic-amp-in-sel", &of_str)) + pdata->mic_amp_in_sel = da7219_fw_mic_amp_in_sel(dev, of_str); + else + pdata->mic_amp_in_sel = DA7219_MIC_AMP_IN_SEL_DIFF; + + return pdata; +} + + +/* + * Codec driver functions + */ + +static int da7219_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + /* Enable MCLK for transition to ON state */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) { + if (da7219->mclk) { + ret = clk_prepare_enable(da7219->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable mclk\n"); + return ret; + } + } + } + + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + /* Master bias */ + snd_soc_component_update_bits(component, DA7219_REFERENCES, + DA7219_BIAS_EN_MASK, + DA7219_BIAS_EN_MASK); + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_PREPARE) { + /* Remove MCLK */ + if (da7219->mclk) + clk_disable_unprepare(da7219->mclk); + } + break; + case SND_SOC_BIAS_OFF: + /* Only disable master bias if we're not a wake-up source */ + if (!da7219->wakeup_source) + snd_soc_component_update_bits(component, DA7219_REFERENCES, + DA7219_BIAS_EN_MASK, 0); + + break; + } + + return 0; +} + +static const char *da7219_supply_names[DA7219_NUM_SUPPLIES] = { + [DA7219_SUPPLY_VDD] = "VDD", + [DA7219_SUPPLY_VDDMIC] = "VDDMIC", + [DA7219_SUPPLY_VDDIO] = "VDDIO", +}; + +static int da7219_handle_supplies(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct regulator *vddio; + u8 io_voltage_lvl = DA7219_IO_VOLTAGE_LEVEL_2_5V_3_6V; + int i, ret; + + /* Get required supplies */ + for (i = 0; i < DA7219_NUM_SUPPLIES; ++i) + da7219->supplies[i].supply = da7219_supply_names[i]; + + ret = devm_regulator_bulk_get(component->dev, DA7219_NUM_SUPPLIES, + da7219->supplies); + if (ret) { + dev_err(component->dev, "Failed to get supplies"); + return ret; + } + + /* Determine VDDIO voltage provided */ + vddio = da7219->supplies[DA7219_SUPPLY_VDDIO].consumer; + ret = regulator_get_voltage(vddio); + if (ret < 1200000) + dev_warn(component->dev, "Invalid VDDIO voltage\n"); + else if (ret < 2800000) + io_voltage_lvl = DA7219_IO_VOLTAGE_LEVEL_1_2V_2_8V; + + /* Enable main supplies */ + ret = regulator_bulk_enable(DA7219_NUM_SUPPLIES, da7219->supplies); + if (ret) { + dev_err(component->dev, "Failed to enable supplies"); + return ret; + } + + /* Ensure device in active mode */ + snd_soc_component_write(component, DA7219_SYSTEM_ACTIVE, DA7219_SYSTEM_ACTIVE_MASK); + + /* Update IO voltage level range */ + snd_soc_component_write(component, DA7219_IO_CTRL, io_voltage_lvl); + + return 0; +} + +#ifdef CONFIG_COMMON_CLK +static int da7219_dai_clks_prepare(struct clk_hw *hw) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, dai_clks_hw); + struct snd_soc_component *component = da7219->aad->component; + + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, + DA7219_DAI_CLK_EN_MASK, + DA7219_DAI_CLK_EN_MASK); + + return 0; +} + +static void da7219_dai_clks_unprepare(struct clk_hw *hw) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, dai_clks_hw); + struct snd_soc_component *component = da7219->aad->component; + + snd_soc_component_update_bits(component, DA7219_DAI_CLK_MODE, + DA7219_DAI_CLK_EN_MASK, 0); +} + +static int da7219_dai_clks_is_prepared(struct clk_hw *hw) +{ + struct da7219_priv *da7219 = + container_of(hw, struct da7219_priv, dai_clks_hw); + struct snd_soc_component *component = da7219->aad->component; + u8 clk_reg; + + clk_reg = snd_soc_component_read32(component, DA7219_DAI_CLK_MODE); + + return !!(clk_reg & DA7219_DAI_CLK_EN_MASK); +} + +static const struct clk_ops da7219_dai_clks_ops = { + .prepare = da7219_dai_clks_prepare, + .unprepare = da7219_dai_clks_unprepare, + .is_prepared = da7219_dai_clks_is_prepared, +}; + +static void da7219_register_dai_clks(struct snd_soc_component *component) +{ + struct device *dev = component->dev; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct da7219_pdata *pdata = da7219->pdata; + struct clk_init_data init = {}; + struct clk *dai_clks; + struct clk_lookup *dai_clks_lookup; + + init.parent_names = NULL; + init.num_parents = 0; + init.name = pdata->dai_clks_name; + init.ops = &da7219_dai_clks_ops; + da7219->dai_clks_hw.init = &init; + + dai_clks = devm_clk_register(dev, &da7219->dai_clks_hw); + if (IS_ERR(dai_clks)) { + dev_warn(dev, "Failed to register DAI clocks: %ld\n", + PTR_ERR(dai_clks)); + return; + } + da7219->dai_clks = dai_clks; + + /* If we're using DT, then register as provider accordingly */ + if (dev->of_node) { + devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &da7219->dai_clks_hw); + } else { + dai_clks_lookup = clkdev_create(dai_clks, pdata->dai_clks_name, + "%s", dev_name(dev)); + if (!dai_clks_lookup) + dev_warn(dev, "Failed to create DAI clkdev"); + else + da7219->dai_clks_lookup = dai_clks_lookup; + } +} +#else +static inline void da7219_register_dai_clks(struct snd_soc_component *component) {} +#endif /* CONFIG_COMMON_CLK */ + +static void da7219_handle_pdata(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + struct da7219_pdata *pdata = da7219->pdata; + + if (pdata) { + u8 micbias_lvl = 0; + + da7219->wakeup_source = pdata->wakeup_source; + + da7219_register_dai_clks(component); + + /* Mic Bias voltages */ + switch (pdata->micbias_lvl) { + case DA7219_MICBIAS_1_6V: + case DA7219_MICBIAS_1_8V: + case DA7219_MICBIAS_2_0V: + case DA7219_MICBIAS_2_2V: + case DA7219_MICBIAS_2_4V: + case DA7219_MICBIAS_2_6V: + micbias_lvl |= (pdata->micbias_lvl << + DA7219_MICBIAS1_LEVEL_SHIFT); + break; + } + + snd_soc_component_write(component, DA7219_MICBIAS_CTRL, micbias_lvl); + + /* + * Calculate delay required to compensate for DC offset in + * Mic PGA, based on Mic Bias voltage. + */ + da7219->mic_pga_delay = DA7219_MIC_PGA_BASE_DELAY + + (pdata->micbias_lvl * + DA7219_MIC_PGA_OFFSET_DELAY); + + /* Mic */ + switch (pdata->mic_amp_in_sel) { + case DA7219_MIC_AMP_IN_SEL_DIFF: + case DA7219_MIC_AMP_IN_SEL_SE_P: + case DA7219_MIC_AMP_IN_SEL_SE_N: + snd_soc_component_write(component, DA7219_MIC_1_SELECT, + pdata->mic_amp_in_sel); + break; + } + } +} + +static struct reg_sequence da7219_rev_aa_patch[] = { + { DA7219_REFERENCES, 0x08 }, +}; + +static int da7219_probe(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + unsigned int rev; + int ret; + + mutex_init(&da7219->ctrl_lock); + mutex_init(&da7219->pll_lock); + + /* Regulator configuration */ + ret = da7219_handle_supplies(component); + if (ret) + return ret; + + ret = regmap_read(da7219->regmap, DA7219_CHIP_REVISION, &rev); + if (ret) { + dev_err(component->dev, "Failed to read chip revision: %d\n", ret); + goto err_disable_reg; + } + + switch (rev & DA7219_CHIP_MINOR_MASK) { + case 0: + ret = regmap_register_patch(da7219->regmap, da7219_rev_aa_patch, + ARRAY_SIZE(da7219_rev_aa_patch)); + if (ret) { + dev_err(component->dev, "Failed to register AA patch: %d\n", + ret); + goto err_disable_reg; + } + break; + default: + break; + } + + /* Handle DT/ACPI/Platform data */ + da7219->pdata = dev_get_platdata(component->dev); + if (!da7219->pdata) + da7219->pdata = da7219_fw_to_pdata(component); + + da7219_handle_pdata(component); + + /* Check if MCLK provided */ + da7219->mclk = devm_clk_get(component->dev, "mclk"); + if (IS_ERR(da7219->mclk)) { + if (PTR_ERR(da7219->mclk) != -ENOENT) { + ret = PTR_ERR(da7219->mclk); + goto err_disable_reg; + } else { + da7219->mclk = NULL; + } + } + + /* Default PC counter to free-running */ + snd_soc_component_update_bits(component, DA7219_PC_COUNT, DA7219_PC_FREERUN_MASK, + DA7219_PC_FREERUN_MASK); + + /* Default gain ramping */ + snd_soc_component_update_bits(component, DA7219_MIXIN_L_CTRL, + DA7219_MIXIN_L_AMP_RAMP_EN_MASK, + DA7219_MIXIN_L_AMP_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_ADC_L_CTRL, DA7219_ADC_L_RAMP_EN_MASK, + DA7219_ADC_L_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_DAC_L_CTRL, DA7219_DAC_L_RAMP_EN_MASK, + DA7219_DAC_L_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_DAC_R_CTRL, DA7219_DAC_R_RAMP_EN_MASK, + DA7219_DAC_R_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_HP_L_CTRL, + DA7219_HP_L_AMP_RAMP_EN_MASK, + DA7219_HP_L_AMP_RAMP_EN_MASK); + snd_soc_component_update_bits(component, DA7219_HP_R_CTRL, + DA7219_HP_R_AMP_RAMP_EN_MASK, + DA7219_HP_R_AMP_RAMP_EN_MASK); + + /* Default minimum gain on HP to avoid pops during DAPM sequencing */ + snd_soc_component_update_bits(component, DA7219_HP_L_CTRL, + DA7219_HP_L_AMP_MIN_GAIN_EN_MASK, + DA7219_HP_L_AMP_MIN_GAIN_EN_MASK); + snd_soc_component_update_bits(component, DA7219_HP_R_CTRL, + DA7219_HP_R_AMP_MIN_GAIN_EN_MASK, + DA7219_HP_R_AMP_MIN_GAIN_EN_MASK); + + /* Default infinite tone gen, start/stop by Kcontrol */ + snd_soc_component_write(component, DA7219_TONE_GEN_CYCLES, DA7219_BEEP_CYCLES_MASK); + + /* Initialise AAD block */ + ret = da7219_aad_init(component); + if (ret) + goto err_disable_reg; + + return 0; + +err_disable_reg: + regulator_bulk_disable(DA7219_NUM_SUPPLIES, da7219->supplies); + + return ret; +} + +static void da7219_remove(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + da7219_aad_exit(component); + +#ifdef CONFIG_COMMON_CLK + if (da7219->dai_clks_lookup) + clkdev_drop(da7219->dai_clks_lookup); +#endif + + /* Supplies */ + regulator_bulk_disable(DA7219_NUM_SUPPLIES, da7219->supplies); +} + +#ifdef CONFIG_PM +static int da7219_suspend(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + /* Suspend AAD if we're not a wake-up source */ + if (!da7219->wakeup_source) + da7219_aad_suspend(component); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + return 0; +} + +static int da7219_resume(struct snd_soc_component *component) +{ + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + /* Resume AAD if previously suspended */ + if (!da7219->wakeup_source) + da7219_aad_resume(component); + + return 0; +} +#else +#define da7219_suspend NULL +#define da7219_resume NULL +#endif + +static const struct snd_soc_component_driver soc_component_dev_da7219 = { + .probe = da7219_probe, + .remove = da7219_remove, + .suspend = da7219_suspend, + .resume = da7219_resume, + .set_bias_level = da7219_set_bias_level, + .controls = da7219_snd_controls, + .num_controls = ARRAY_SIZE(da7219_snd_controls), + .dapm_widgets = da7219_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(da7219_dapm_widgets), + .dapm_routes = da7219_audio_map, + .num_dapm_routes = ARRAY_SIZE(da7219_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + + +/* + * Regmap configs + */ + +static struct reg_default da7219_reg_defaults[] = { + { DA7219_MIC_1_SELECT, 0x00 }, + { DA7219_CIF_TIMEOUT_CTRL, 0x01 }, + { DA7219_SR_24_48, 0x00 }, + { DA7219_SR, 0x0A }, + { DA7219_CIF_I2C_ADDR_CFG, 0x02 }, + { DA7219_PLL_CTRL, 0x10 }, + { DA7219_PLL_FRAC_TOP, 0x00 }, + { DA7219_PLL_FRAC_BOT, 0x00 }, + { DA7219_PLL_INTEGER, 0x20 }, + { DA7219_DIG_ROUTING_DAI, 0x10 }, + { DA7219_DAI_CLK_MODE, 0x01 }, + { DA7219_DAI_CTRL, 0x28 }, + { DA7219_DAI_TDM_CTRL, 0x40 }, + { DA7219_DIG_ROUTING_DAC, 0x32 }, + { DA7219_DAI_OFFSET_LOWER, 0x00 }, + { DA7219_DAI_OFFSET_UPPER, 0x00 }, + { DA7219_REFERENCES, 0x08 }, + { DA7219_MIXIN_L_SELECT, 0x00 }, + { DA7219_MIXIN_L_GAIN, 0x03 }, + { DA7219_ADC_L_GAIN, 0x6F }, + { DA7219_ADC_FILTERS1, 0x80 }, + { DA7219_MIC_1_GAIN, 0x01 }, + { DA7219_SIDETONE_CTRL, 0x40 }, + { DA7219_SIDETONE_GAIN, 0x0E }, + { DA7219_DROUTING_ST_OUTFILT_1L, 0x01 }, + { DA7219_DROUTING_ST_OUTFILT_1R, 0x02 }, + { DA7219_DAC_FILTERS5, 0x00 }, + { DA7219_DAC_FILTERS2, 0x88 }, + { DA7219_DAC_FILTERS3, 0x88 }, + { DA7219_DAC_FILTERS4, 0x08 }, + { DA7219_DAC_FILTERS1, 0x80 }, + { DA7219_DAC_L_GAIN, 0x6F }, + { DA7219_DAC_R_GAIN, 0x6F }, + { DA7219_CP_CTRL, 0x20 }, + { DA7219_HP_L_GAIN, 0x39 }, + { DA7219_HP_R_GAIN, 0x39 }, + { DA7219_MIXOUT_L_SELECT, 0x00 }, + { DA7219_MIXOUT_R_SELECT, 0x00 }, + { DA7219_MICBIAS_CTRL, 0x03 }, + { DA7219_MIC_1_CTRL, 0x40 }, + { DA7219_MIXIN_L_CTRL, 0x40 }, + { DA7219_ADC_L_CTRL, 0x40 }, + { DA7219_DAC_L_CTRL, 0x40 }, + { DA7219_DAC_R_CTRL, 0x40 }, + { DA7219_HP_L_CTRL, 0x40 }, + { DA7219_HP_R_CTRL, 0x40 }, + { DA7219_MIXOUT_L_CTRL, 0x10 }, + { DA7219_MIXOUT_R_CTRL, 0x10 }, + { DA7219_CHIP_ID1, 0x23 }, + { DA7219_CHIP_ID2, 0x93 }, + { DA7219_IO_CTRL, 0x00 }, + { DA7219_GAIN_RAMP_CTRL, 0x00 }, + { DA7219_PC_COUNT, 0x02 }, + { DA7219_CP_VOL_THRESHOLD1, 0x0E }, + { DA7219_DIG_CTRL, 0x00 }, + { DA7219_ALC_CTRL2, 0x00 }, + { DA7219_ALC_CTRL3, 0x00 }, + { DA7219_ALC_NOISE, 0x3F }, + { DA7219_ALC_TARGET_MIN, 0x3F }, + { DA7219_ALC_TARGET_MAX, 0x00 }, + { DA7219_ALC_GAIN_LIMITS, 0xFF }, + { DA7219_ALC_ANA_GAIN_LIMITS, 0x71 }, + { DA7219_ALC_ANTICLIP_CTRL, 0x00 }, + { DA7219_ALC_ANTICLIP_LEVEL, 0x00 }, + { DA7219_DAC_NG_SETUP_TIME, 0x00 }, + { DA7219_DAC_NG_OFF_THRESH, 0x00 }, + { DA7219_DAC_NG_ON_THRESH, 0x00 }, + { DA7219_DAC_NG_CTRL, 0x00 }, + { DA7219_TONE_GEN_CFG1, 0x00 }, + { DA7219_TONE_GEN_CFG2, 0x00 }, + { DA7219_TONE_GEN_CYCLES, 0x00 }, + { DA7219_TONE_GEN_FREQ1_L, 0x55 }, + { DA7219_TONE_GEN_FREQ1_U, 0x15 }, + { DA7219_TONE_GEN_FREQ2_L, 0x00 }, + { DA7219_TONE_GEN_FREQ2_U, 0x40 }, + { DA7219_TONE_GEN_ON_PER, 0x02 }, + { DA7219_TONE_GEN_OFF_PER, 0x01 }, + { DA7219_ACCDET_IRQ_MASK_A, 0x00 }, + { DA7219_ACCDET_IRQ_MASK_B, 0x00 }, + { DA7219_ACCDET_CONFIG_1, 0xD6 }, + { DA7219_ACCDET_CONFIG_2, 0x34 }, + { DA7219_ACCDET_CONFIG_3, 0x0A }, + { DA7219_ACCDET_CONFIG_4, 0x16 }, + { DA7219_ACCDET_CONFIG_5, 0x21 }, + { DA7219_ACCDET_CONFIG_6, 0x3E }, + { DA7219_ACCDET_CONFIG_7, 0x01 }, + { DA7219_SYSTEM_ACTIVE, 0x00 }, +}; + +static bool da7219_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA7219_MIC_1_GAIN_STATUS: + case DA7219_MIXIN_L_GAIN_STATUS: + case DA7219_ADC_L_GAIN_STATUS: + case DA7219_DAC_L_GAIN_STATUS: + case DA7219_DAC_R_GAIN_STATUS: + case DA7219_HP_L_GAIN_STATUS: + case DA7219_HP_R_GAIN_STATUS: + case DA7219_CIF_CTRL: + case DA7219_PLL_SRM_STS: + case DA7219_ALC_CTRL1: + case DA7219_SYSTEM_MODES_INPUT: + case DA7219_SYSTEM_MODES_OUTPUT: + case DA7219_ALC_OFFSET_AUTO_M_L: + case DA7219_ALC_OFFSET_AUTO_U_L: + case DA7219_TONE_GEN_CFG1: + case DA7219_ACCDET_STATUS_A: + case DA7219_ACCDET_STATUS_B: + case DA7219_ACCDET_IRQ_EVENT_A: + case DA7219_ACCDET_IRQ_EVENT_B: + case DA7219_ACCDET_CONFIG_8: + case DA7219_SYSTEM_STATUS: + return true; + default: + return false; + } +} + +static const struct regmap_config da7219_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = DA7219_SYSTEM_ACTIVE, + .reg_defaults = da7219_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da7219_reg_defaults), + .volatile_reg = da7219_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + + +/* + * I2C layer + */ + +static int da7219_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da7219_priv *da7219; + unsigned int system_active, system_status; + int i, ret; + + da7219 = devm_kzalloc(&i2c->dev, sizeof(struct da7219_priv), + GFP_KERNEL); + if (!da7219) + return -ENOMEM; + + i2c_set_clientdata(i2c, da7219); + + da7219->regmap = devm_regmap_init_i2c(i2c, &da7219_regmap_config); + if (IS_ERR(da7219->regmap)) { + ret = PTR_ERR(da7219->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + regcache_cache_bypass(da7219->regmap, true); + + /* Disable audio paths if still active from previous start */ + regmap_read(da7219->regmap, DA7219_SYSTEM_ACTIVE, &system_active); + if (system_active) { + regmap_write(da7219->regmap, DA7219_GAIN_RAMP_CTRL, + DA7219_GAIN_RAMP_RATE_NOMINAL); + regmap_write(da7219->regmap, DA7219_SYSTEM_MODES_INPUT, 0x00); + regmap_write(da7219->regmap, DA7219_SYSTEM_MODES_OUTPUT, 0x01); + + for (i = 0; i < DA7219_SYS_STAT_CHECK_RETRIES; ++i) { + regmap_read(da7219->regmap, DA7219_SYSTEM_STATUS, + &system_status); + if (!system_status) + break; + + msleep(DA7219_SYS_STAT_CHECK_DELAY); + } + } + + /* Soft reset component */ + regmap_write_bits(da7219->regmap, DA7219_ACCDET_CONFIG_1, + DA7219_ACCDET_EN_MASK, 0); + regmap_write_bits(da7219->regmap, DA7219_CIF_CTRL, + DA7219_CIF_REG_SOFT_RESET_MASK, + DA7219_CIF_REG_SOFT_RESET_MASK); + regmap_write_bits(da7219->regmap, DA7219_SYSTEM_ACTIVE, + DA7219_SYSTEM_ACTIVE_MASK, 0); + + regcache_cache_bypass(da7219->regmap, false); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_da7219, + &da7219_dai, 1); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register da7219 component: %d\n", + ret); + } + return ret; +} + +static int da7219_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id da7219_i2c_id[] = { + { "da7219", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da7219_i2c_id); + +static struct i2c_driver da7219_i2c_driver = { + .driver = { + .name = "da7219", + .of_match_table = of_match_ptr(da7219_of_match), + .acpi_match_table = ACPI_PTR(da7219_acpi_match), + }, + .probe = da7219_i2c_probe, + .remove = da7219_i2c_remove, + .id_table = da7219_i2c_id, +}; + +module_i2c_driver(da7219_i2c_driver); + +MODULE_DESCRIPTION("ASoC DA7219 Codec Driver"); +MODULE_AUTHOR("Adam Thomson "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da7219.h b/sound/soc/codecs/da7219.h new file mode 100644 index 000000000..3a006862f --- /dev/null +++ b/sound/soc/codecs/da7219.h @@ -0,0 +1,840 @@ +/* + * da7219.h - DA7219 ALSA SoC Codec Driver + * + * Copyright (c) 2015 Dialog Semiconductor + * + * Author: Adam Thomson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __DA7219_H +#define __DA7219_H + +#include +#include +#include +#include +#include +#include + +/* + * Registers + */ + +#define DA7219_MIC_1_GAIN_STATUS 0x6 +#define DA7219_MIXIN_L_GAIN_STATUS 0x8 +#define DA7219_ADC_L_GAIN_STATUS 0xA +#define DA7219_DAC_L_GAIN_STATUS 0xC +#define DA7219_DAC_R_GAIN_STATUS 0xD +#define DA7219_HP_L_GAIN_STATUS 0xE +#define DA7219_HP_R_GAIN_STATUS 0xF +#define DA7219_MIC_1_SELECT 0x10 +#define DA7219_CIF_TIMEOUT_CTRL 0x12 +#define DA7219_CIF_CTRL 0x13 +#define DA7219_SR_24_48 0x16 +#define DA7219_SR 0x17 +#define DA7219_CIF_I2C_ADDR_CFG 0x1B +#define DA7219_PLL_CTRL 0x20 +#define DA7219_PLL_FRAC_TOP 0x22 +#define DA7219_PLL_FRAC_BOT 0x23 +#define DA7219_PLL_INTEGER 0x24 +#define DA7219_PLL_SRM_STS 0x25 +#define DA7219_DIG_ROUTING_DAI 0x2A +#define DA7219_DAI_CLK_MODE 0x2B +#define DA7219_DAI_CTRL 0x2C +#define DA7219_DAI_TDM_CTRL 0x2D +#define DA7219_DIG_ROUTING_DAC 0x2E +#define DA7219_ALC_CTRL1 0x2F +#define DA7219_DAI_OFFSET_LOWER 0x30 +#define DA7219_DAI_OFFSET_UPPER 0x31 +#define DA7219_REFERENCES 0x32 +#define DA7219_MIXIN_L_SELECT 0x33 +#define DA7219_MIXIN_L_GAIN 0x34 +#define DA7219_ADC_L_GAIN 0x36 +#define DA7219_ADC_FILTERS1 0x38 +#define DA7219_MIC_1_GAIN 0x39 +#define DA7219_SIDETONE_CTRL 0x3A +#define DA7219_SIDETONE_GAIN 0x3B +#define DA7219_DROUTING_ST_OUTFILT_1L 0x3C +#define DA7219_DROUTING_ST_OUTFILT_1R 0x3D +#define DA7219_DAC_FILTERS5 0x40 +#define DA7219_DAC_FILTERS2 0x41 +#define DA7219_DAC_FILTERS3 0x42 +#define DA7219_DAC_FILTERS4 0x43 +#define DA7219_DAC_FILTERS1 0x44 +#define DA7219_DAC_L_GAIN 0x45 +#define DA7219_DAC_R_GAIN 0x46 +#define DA7219_CP_CTRL 0x47 +#define DA7219_HP_L_GAIN 0x48 +#define DA7219_HP_R_GAIN 0x49 +#define DA7219_MIXOUT_L_SELECT 0x4B +#define DA7219_MIXOUT_R_SELECT 0x4C +#define DA7219_SYSTEM_MODES_INPUT 0x50 +#define DA7219_SYSTEM_MODES_OUTPUT 0x51 +#define DA7219_MICBIAS_CTRL 0x62 +#define DA7219_MIC_1_CTRL 0x63 +#define DA7219_MIXIN_L_CTRL 0x65 +#define DA7219_ADC_L_CTRL 0x67 +#define DA7219_DAC_L_CTRL 0x69 +#define DA7219_DAC_R_CTRL 0x6A +#define DA7219_HP_L_CTRL 0x6B +#define DA7219_HP_R_CTRL 0x6C +#define DA7219_MIXOUT_L_CTRL 0x6E +#define DA7219_MIXOUT_R_CTRL 0x6F +#define DA7219_CHIP_ID1 0x81 +#define DA7219_CHIP_ID2 0x82 +#define DA7219_CHIP_REVISION 0x83 +#define DA7219_IO_CTRL 0x91 +#define DA7219_GAIN_RAMP_CTRL 0x92 +#define DA7219_PC_COUNT 0x94 +#define DA7219_CP_VOL_THRESHOLD1 0x95 +#define DA7219_CP_DELAY 0x96 +#define DA7219_DIG_CTRL 0x99 +#define DA7219_ALC_CTRL2 0x9A +#define DA7219_ALC_CTRL3 0x9B +#define DA7219_ALC_NOISE 0x9C +#define DA7219_ALC_TARGET_MIN 0x9D +#define DA7219_ALC_TARGET_MAX 0x9E +#define DA7219_ALC_GAIN_LIMITS 0x9F +#define DA7219_ALC_ANA_GAIN_LIMITS 0xA0 +#define DA7219_ALC_ANTICLIP_CTRL 0xA1 +#define DA7219_ALC_ANTICLIP_LEVEL 0xA2 +#define DA7219_ALC_OFFSET_AUTO_M_L 0xA3 +#define DA7219_ALC_OFFSET_AUTO_U_L 0xA4 +#define DA7219_DAC_NG_SETUP_TIME 0xAF +#define DA7219_DAC_NG_OFF_THRESH 0xB0 +#define DA7219_DAC_NG_ON_THRESH 0xB1 +#define DA7219_DAC_NG_CTRL 0xB2 +#define DA7219_TONE_GEN_CFG1 0xB4 +#define DA7219_TONE_GEN_CFG2 0xB5 +#define DA7219_TONE_GEN_CYCLES 0xB6 +#define DA7219_TONE_GEN_FREQ1_L 0xB7 +#define DA7219_TONE_GEN_FREQ1_U 0xB8 +#define DA7219_TONE_GEN_FREQ2_L 0xB9 +#define DA7219_TONE_GEN_FREQ2_U 0xBA +#define DA7219_TONE_GEN_ON_PER 0xBB +#define DA7219_TONE_GEN_OFF_PER 0xBC +#define DA7219_SYSTEM_STATUS 0xE0 +#define DA7219_SYSTEM_ACTIVE 0xFD + + +/* + * Bit Fields + */ + +#define DA7219_SWITCH_EN_MAX 0x1 + +/* DA7219_MIC_1_GAIN_STATUS = 0x6 */ +#define DA7219_MIC_1_AMP_GAIN_STATUS_SHIFT 0 +#define DA7219_MIC_1_AMP_GAIN_STATUS_MASK (0x7 << 0) +#define DA7219_MIC_1_AMP_GAIN_MAX 0x7 + +/* DA7219_MIXIN_L_GAIN_STATUS = 0x8 */ +#define DA7219_MIXIN_L_AMP_GAIN_STATUS_SHIFT 0 +#define DA7219_MIXIN_L_AMP_GAIN_STATUS_MASK (0xF << 0) + +/* DA7219_ADC_L_GAIN_STATUS = 0xA */ +#define DA7219_ADC_L_DIGITAL_GAIN_STATUS_SHIFT 0 +#define DA7219_ADC_L_DIGITAL_GAIN_STATUS_MASK (0x7F << 0) + +/* DA7219_DAC_L_GAIN_STATUS = 0xC */ +#define DA7219_DAC_L_DIGITAL_GAIN_STATUS_SHIFT 0 +#define DA7219_DAC_L_DIGITAL_GAIN_STATUS_MASK (0x7F << 0) + +/* DA7219_DAC_R_GAIN_STATUS = 0xD */ +#define DA7219_DAC_R_DIGITAL_GAIN_STATUS_SHIFT 0 +#define DA7219_DAC_R_DIGITAL_GAIN_STATUS_MASK (0x7F << 0) + +/* DA7219_HP_L_GAIN_STATUS = 0xE */ +#define DA7219_HP_L_AMP_GAIN_STATUS_SHIFT 0 +#define DA7219_HP_L_AMP_GAIN_STATUS_MASK (0x3F << 0) + +/* DA7219_HP_R_GAIN_STATUS = 0xF */ +#define DA7219_HP_R_AMP_GAIN_STATUS_SHIFT 0 +#define DA7219_HP_R_AMP_GAIN_STATUS_MASK (0x3F << 0) + +/* DA7219_MIC_1_SELECT = 0x10 */ +#define DA7219_MIC_1_AMP_IN_SEL_SHIFT 0 +#define DA7219_MIC_1_AMP_IN_SEL_MASK (0x3 << 0) + +/* DA7219_CIF_TIMEOUT_CTRL = 0x12 */ +#define DA7219_I2C_TIMEOUT_EN_SHIFT 0 +#define DA7219_I2C_TIMEOUT_EN_MASK (0x1 << 0) + +/* DA7219_CIF_CTRL = 0x13 */ +#define DA7219_CIF_I2C_WRITE_MODE_SHIFT 0 +#define DA7219_CIF_I2C_WRITE_MODE_MASK (0x1 << 0) +#define DA7219_CIF_REG_SOFT_RESET_SHIFT 7 +#define DA7219_CIF_REG_SOFT_RESET_MASK (0x1 << 7) + +/* DA7219_SR_24_48 = 0x16 */ +#define DA7219_SR_24_48_SHIFT 0 +#define DA7219_SR_24_48_MASK (0x1 << 0) + +/* DA7219_SR = 0x17 */ +#define DA7219_SR_SHIFT 0 +#define DA7219_SR_MASK (0xF << 0) +#define DA7219_SR_8000 (0x01 << 0) +#define DA7219_SR_11025 (0x02 << 0) +#define DA7219_SR_12000 (0x03 << 0) +#define DA7219_SR_16000 (0x05 << 0) +#define DA7219_SR_22050 (0x06 << 0) +#define DA7219_SR_24000 (0x07 << 0) +#define DA7219_SR_32000 (0x09 << 0) +#define DA7219_SR_44100 (0x0A << 0) +#define DA7219_SR_48000 (0x0B << 0) +#define DA7219_SR_88200 (0x0E << 0) +#define DA7219_SR_96000 (0x0F << 0) + +/* DA7219_CIF_I2C_ADDR_CFG = 0x1B */ +#define DA7219_CIF_I2C_ADDR_CFG_SHIFT 0 +#define DA7219_CIF_I2C_ADDR_CFG_MASK (0x3 << 0) + +/* DA7219_PLL_CTRL = 0x20 */ +#define DA7219_PLL_INDIV_SHIFT 2 +#define DA7219_PLL_INDIV_MASK (0x7 << 2) +#define DA7219_PLL_INDIV_2_TO_4_5_MHZ (0x0 << 2) +#define DA7219_PLL_INDIV_4_5_TO_9_MHZ (0x1 << 2) +#define DA7219_PLL_INDIV_9_TO_18_MHZ (0x2 << 2) +#define DA7219_PLL_INDIV_18_TO_36_MHZ (0x3 << 2) +#define DA7219_PLL_INDIV_36_TO_54_MHZ (0x4 << 2) +#define DA7219_PLL_MCLK_SQR_EN_SHIFT 5 +#define DA7219_PLL_MCLK_SQR_EN_MASK (0x1 << 5) +#define DA7219_PLL_MODE_SHIFT 6 +#define DA7219_PLL_MODE_MASK (0x3 << 6) +#define DA7219_PLL_MODE_BYPASS (0x0 << 6) +#define DA7219_PLL_MODE_NORMAL (0x1 << 6) +#define DA7219_PLL_MODE_SRM (0x2 << 6) + +/* DA7219_PLL_FRAC_TOP = 0x22 */ +#define DA7219_PLL_FBDIV_FRAC_TOP_SHIFT 0 +#define DA7219_PLL_FBDIV_FRAC_TOP_MASK (0x1F << 0) + +/* DA7219_PLL_FRAC_BOT = 0x23 */ +#define DA7219_PLL_FBDIV_FRAC_BOT_SHIFT 0 +#define DA7219_PLL_FBDIV_FRAC_BOT_MASK (0xFF << 0) + +/* DA7219_PLL_INTEGER = 0x24 */ +#define DA7219_PLL_FBDIV_INTEGER_SHIFT 0 +#define DA7219_PLL_FBDIV_INTEGER_MASK (0x7F << 0) + +/* DA7219_PLL_SRM_STS = 0x25 */ +#define DA7219_PLL_SRM_STATE_SHIFT 0 +#define DA7219_PLL_SRM_STATE_MASK (0xF << 0) +#define DA7219_PLL_SRM_STATUS_SHIFT 4 +#define DA7219_PLL_SRM_STATUS_MASK (0xF << 4) +#define DA7219_PLL_SRM_STS_MCLK (0x1 << 4) +#define DA7219_PLL_SRM_STS_SRM_LOCK (0x1 << 7) + +/* DA7219_DIG_ROUTING_DAI = 0x2A */ +#define DA7219_DAI_L_SRC_SHIFT 0 +#define DA7219_DAI_L_SRC_MASK (0x3 << 0) +#define DA7219_DAI_R_SRC_SHIFT 4 +#define DA7219_DAI_R_SRC_MASK (0x3 << 4) +#define DA7219_OUT_SRC_MAX 4 + +/* DA7219_DAI_CLK_MODE = 0x2B */ +#define DA7219_DAI_BCLKS_PER_WCLK_SHIFT 0 +#define DA7219_DAI_BCLKS_PER_WCLK_MASK (0x3 << 0) +#define DA7219_DAI_BCLKS_PER_WCLK_32 (0x0 << 0) +#define DA7219_DAI_BCLKS_PER_WCLK_64 (0x1 << 0) +#define DA7219_DAI_BCLKS_PER_WCLK_128 (0x2 << 0) +#define DA7219_DAI_BCLKS_PER_WCLK_256 (0x3 << 0) +#define DA7219_DAI_CLK_POL_SHIFT 2 +#define DA7219_DAI_CLK_POL_MASK (0x1 << 2) +#define DA7219_DAI_CLK_POL_INV (0x1 << 2) +#define DA7219_DAI_WCLK_POL_SHIFT 3 +#define DA7219_DAI_WCLK_POL_MASK (0x1 << 3) +#define DA7219_DAI_WCLK_POL_INV (0x1 << 3) +#define DA7219_DAI_WCLK_TRI_STATE_SHIFT 4 +#define DA7219_DAI_WCLK_TRI_STATE_MASK (0x1 << 4) +#define DA7219_DAI_CLK_EN_SHIFT 7 +#define DA7219_DAI_CLK_EN_MASK (0x1 << 7) + +/* DA7219_DAI_CTRL = 0x2C */ +#define DA7219_DAI_FORMAT_SHIFT 0 +#define DA7219_DAI_FORMAT_MASK (0x3 << 0) +#define DA7219_DAI_FORMAT_I2S (0x0 << 0) +#define DA7219_DAI_FORMAT_LEFT_J (0x1 << 0) +#define DA7219_DAI_FORMAT_RIGHT_J (0x2 << 0) +#define DA7219_DAI_FORMAT_DSP (0x3 << 0) +#define DA7219_DAI_WORD_LENGTH_SHIFT 2 +#define DA7219_DAI_WORD_LENGTH_MASK (0x3 << 2) +#define DA7219_DAI_WORD_LENGTH_S16_LE (0x0 << 2) +#define DA7219_DAI_WORD_LENGTH_S20_LE (0x1 << 2) +#define DA7219_DAI_WORD_LENGTH_S24_LE (0x2 << 2) +#define DA7219_DAI_WORD_LENGTH_S32_LE (0x3 << 2) +#define DA7219_DAI_CH_NUM_SHIFT 4 +#define DA7219_DAI_CH_NUM_MASK (0x3 << 4) +#define DA7219_DAI_CH_NUM_MAX 2 +#define DA7219_DAI_EN_SHIFT 7 +#define DA7219_DAI_EN_MASK (0x1 << 7) + +/* DA7219_DAI_TDM_CTRL = 0x2D */ +#define DA7219_DAI_TDM_CH_EN_SHIFT 0 +#define DA7219_DAI_TDM_CH_EN_MASK (0x3 << 0) +#define DA7219_DAI_OE_SHIFT 6 +#define DA7219_DAI_OE_MASK (0x1 << 6) +#define DA7219_DAI_TDM_MODE_EN_SHIFT 7 +#define DA7219_DAI_TDM_MODE_EN_MASK (0x1 << 7) +#define DA7219_DAI_TDM_MAX_SLOTS 2 + +/* DA7219_DIG_ROUTING_DAC = 0x2E */ +#define DA7219_DAC_L_SRC_SHIFT 0 +#define DA7219_DAC_L_SRC_MASK (0x3 << 0) +#define DA7219_DAC_L_SRC_TONEGEN (0x1 << 0) +#define DA7219_DAC_L_MONO_SHIFT 3 +#define DA7219_DAC_L_MONO_MASK (0x1 << 3) +#define DA7219_DAC_R_SRC_SHIFT 4 +#define DA7219_DAC_R_SRC_MASK (0x3 << 4) +#define DA7219_DAC_R_SRC_TONEGEN (0x1 << 4) +#define DA7219_DAC_R_MONO_SHIFT 7 +#define DA7219_DAC_R_MONO_MASK (0x1 << 7) + +/* DA7219_ALC_CTRL1 = 0x2F */ +#define DA7219_ALC_OFFSET_EN_SHIFT 0 +#define DA7219_ALC_OFFSET_EN_MASK (0x1 << 0) +#define DA7219_ALC_SYNC_MODE_SHIFT 1 +#define DA7219_ALC_SYNC_MODE_MASK (0x1 << 1) +#define DA7219_ALC_EN_SHIFT 3 +#define DA7219_ALC_EN_MASK (0x1 << 3) +#define DA7219_ALC_AUTO_CALIB_EN_SHIFT 4 +#define DA7219_ALC_AUTO_CALIB_EN_MASK (0x1 << 4) +#define DA7219_ALC_CALIB_OVERFLOW_SHIFT 5 +#define DA7219_ALC_CALIB_OVERFLOW_MASK (0x1 << 5) + +/* DA7219_DAI_OFFSET_LOWER = 0x30 */ +#define DA7219_DAI_OFFSET_LOWER_SHIFT 0 +#define DA7219_DAI_OFFSET_LOWER_MASK (0xFF << 0) + +/* DA7219_DAI_OFFSET_UPPER = 0x31 */ +#define DA7219_DAI_OFFSET_UPPER_SHIFT 0 +#define DA7219_DAI_OFFSET_UPPER_MASK (0x7 << 0) +#define DA7219_DAI_OFFSET_MAX 0x2FF + +/* DA7219_REFERENCES = 0x32 */ +#define DA7219_BIAS_EN_SHIFT 3 +#define DA7219_BIAS_EN_MASK (0x1 << 3) +#define DA7219_VMID_FAST_CHARGE_SHIFT 4 +#define DA7219_VMID_FAST_CHARGE_MASK (0x1 << 4) + +/* DA7219_MIXIN_L_SELECT = 0x33 */ +#define DA7219_MIXIN_L_MIX_SELECT_SHIFT 0 +#define DA7219_MIXIN_L_MIX_SELECT_MASK (0x1 << 0) + +/* DA7219_MIXIN_L_GAIN = 0x34 */ +#define DA7219_MIXIN_L_AMP_GAIN_SHIFT 0 +#define DA7219_MIXIN_L_AMP_GAIN_MASK (0xF << 0) +#define DA7219_MIXIN_L_AMP_GAIN_MAX 0xF + +/* DA7219_ADC_L_GAIN = 0x36 */ +#define DA7219_ADC_L_DIGITAL_GAIN_SHIFT 0 +#define DA7219_ADC_L_DIGITAL_GAIN_MASK (0x7F << 0) +#define DA7219_ADC_L_DIGITAL_GAIN_MAX 0x7F + +/* DA7219_ADC_FILTERS1 = 0x38 */ +#define DA7219_ADC_VOICE_HPF_CORNER_SHIFT 0 +#define DA7219_ADC_VOICE_HPF_CORNER_MASK (0x7 << 0) +#define DA7219_VOICE_HPF_CORNER_MAX 8 +#define DA7219_ADC_VOICE_EN_SHIFT 3 +#define DA7219_ADC_VOICE_EN_MASK (0x1 << 3) +#define DA7219_ADC_AUDIO_HPF_CORNER_SHIFT 4 +#define DA7219_ADC_AUDIO_HPF_CORNER_MASK (0x3 << 4) +#define DA7219_AUDIO_HPF_CORNER_MAX 4 +#define DA7219_ADC_HPF_EN_SHIFT 7 +#define DA7219_ADC_HPF_EN_MASK (0x1 << 7) +#define DA7219_HPF_MODE_SHIFT 0 +#define DA7219_HPF_DISABLED ((0x0 << 3) | (0x0 << 7)) +#define DA7219_HPF_AUDIO_EN ((0x0 << 3) | (0x1 << 7)) +#define DA7219_HPF_VOICE_EN ((0x1 << 3) | (0x1 << 7)) +#define DA7219_HPF_MODE_MASK ((0x1 << 3) | (0x1 << 7)) +#define DA7219_HPF_MODE_MAX 3 + +/* DA7219_MIC_1_GAIN = 0x39 */ +#define DA7219_MIC_1_AMP_GAIN_SHIFT 0 +#define DA7219_MIC_1_AMP_GAIN_MASK (0x7 << 0) + +/* DA7219_SIDETONE_CTRL = 0x3A */ +#define DA7219_SIDETONE_MUTE_EN_SHIFT 6 +#define DA7219_SIDETONE_MUTE_EN_MASK (0x1 << 6) +#define DA7219_SIDETONE_EN_SHIFT 7 +#define DA7219_SIDETONE_EN_MASK (0x1 << 7) + +/* DA7219_SIDETONE_GAIN = 0x3B */ +#define DA7219_SIDETONE_GAIN_SHIFT 0 +#define DA7219_SIDETONE_GAIN_MASK (0xF << 0) +#define DA7219_SIDETONE_GAIN_MAX 0xE + +/* DA7219_DROUTING_ST_OUTFILT_1L = 0x3C */ +#define DA7219_OUTFILT_ST_1L_SRC_SHIFT 0 +#define DA7219_OUTFILT_ST_1L_SRC_MASK (0x7 << 0) +#define DA7219_DMIX_ST_SRC_OUTFILT1L_SHIFT 0 +#define DA7219_DMIX_ST_SRC_OUTFILT1R_SHIFT 1 +#define DA7219_DMIX_ST_SRC_SIDETONE_SHIFT 2 +#define DA7219_DMIX_ST_SRC_OUTFILT1L (0x1 << 0) +#define DA7219_DMIX_ST_SRC_OUTFILT1R (0x1 << 1) + +/* DA7219_DROUTING_ST_OUTFILT_1R = 0x3D */ +#define DA7219_OUTFILT_ST_1R_SRC_SHIFT 0 +#define DA7219_OUTFILT_ST_1R_SRC_MASK (0x7 << 0) + +/* DA7219_DAC_FILTERS5 = 0x40 */ +#define DA7219_DAC_SOFTMUTE_RATE_SHIFT 4 +#define DA7219_DAC_SOFTMUTE_RATE_MASK (0x7 << 4) +#define DA7219_DAC_SOFTMUTE_RATE_MAX 7 +#define DA7219_DAC_SOFTMUTE_EN_SHIFT 7 +#define DA7219_DAC_SOFTMUTE_EN_MASK (0x1 << 7) + +/* DA7219_DAC_FILTERS2 = 0x41 */ +#define DA7219_DAC_EQ_BAND1_SHIFT 0 +#define DA7219_DAC_EQ_BAND1_MASK (0xF << 0) +#define DA7219_DAC_EQ_BAND2_SHIFT 4 +#define DA7219_DAC_EQ_BAND2_MASK (0xF << 4) +#define DA7219_DAC_EQ_BAND_MAX 0xF + +/* DA7219_DAC_FILTERS3 = 0x42 */ +#define DA7219_DAC_EQ_BAND3_SHIFT 0 +#define DA7219_DAC_EQ_BAND3_MASK (0xF << 0) +#define DA7219_DAC_EQ_BAND4_SHIFT 4 +#define DA7219_DAC_EQ_BAND4_MASK (0xF << 4) + +/* DA7219_DAC_FILTERS4 = 0x43 */ +#define DA7219_DAC_EQ_BAND5_SHIFT 0 +#define DA7219_DAC_EQ_BAND5_MASK (0xF << 0) +#define DA7219_DAC_EQ_EN_SHIFT 7 +#define DA7219_DAC_EQ_EN_MASK (0x1 << 7) + +/* DA7219_DAC_FILTERS1 = 0x44 */ +#define DA7219_DAC_VOICE_HPF_CORNER_SHIFT 0 +#define DA7219_DAC_VOICE_HPF_CORNER_MASK (0x7 << 0) +#define DA7219_DAC_VOICE_EN_SHIFT 3 +#define DA7219_DAC_VOICE_EN_MASK (0x1 << 3) +#define DA7219_DAC_AUDIO_HPF_CORNER_SHIFT 4 +#define DA7219_DAC_AUDIO_HPF_CORNER_MASK (0x3 << 4) +#define DA7219_DAC_HPF_EN_SHIFT 7 +#define DA7219_DAC_HPF_EN_MASK (0x1 << 7) + +/* DA7219_DAC_L_GAIN = 0x45 */ +#define DA7219_DAC_L_DIGITAL_GAIN_SHIFT 0 +#define DA7219_DAC_L_DIGITAL_GAIN_MASK (0x7F << 0) +#define DA7219_DAC_DIGITAL_GAIN_MAX 0x7F +#define DA7219_DAC_DIGITAL_GAIN_0DB (0x6F << 0) + +/* DA7219_DAC_R_GAIN = 0x46 */ +#define DA7219_DAC_R_DIGITAL_GAIN_SHIFT 0 +#define DA7219_DAC_R_DIGITAL_GAIN_MASK (0x7F << 0) + +/* DA7219_CP_CTRL = 0x47 */ +#define DA7219_CP_MCHANGE_SHIFT 4 +#define DA7219_CP_MCHANGE_MASK (0x3 << 4) +#define DA7219_CP_MCHANGE_REL_MASK 0x3 +#define DA7219_CP_MCHANGE_MAX 3 +#define DA7219_CP_MCHANGE_LARGEST_VOL 0x1 +#define DA7219_CP_MCHANGE_DAC_VOL 0x2 +#define DA7219_CP_MCHANGE_SIG_MAG 0x3 +#define DA7219_CP_EN_SHIFT 7 +#define DA7219_CP_EN_MASK (0x1 << 7) + +/* DA7219_HP_L_GAIN = 0x48 */ +#define DA7219_HP_L_AMP_GAIN_SHIFT 0 +#define DA7219_HP_L_AMP_GAIN_MASK (0x3F << 0) +#define DA7219_HP_AMP_GAIN_MAX 0x3F +#define DA7219_HP_AMP_GAIN_0DB (0x39 << 0) + +/* DA7219_HP_R_GAIN = 0x49 */ +#define DA7219_HP_R_AMP_GAIN_SHIFT 0 +#define DA7219_HP_R_AMP_GAIN_MASK (0x3F << 0) + +/* DA7219_MIXOUT_L_SELECT = 0x4B */ +#define DA7219_MIXOUT_L_MIX_SELECT_SHIFT 0 +#define DA7219_MIXOUT_L_MIX_SELECT_MASK (0x1 << 0) + +/* DA7219_MIXOUT_R_SELECT = 0x4C */ +#define DA7219_MIXOUT_R_MIX_SELECT_SHIFT 0 +#define DA7219_MIXOUT_R_MIX_SELECT_MASK (0x1 << 0) + +/* DA7219_SYSTEM_MODES_INPUT = 0x50 */ +#define DA7219_MODE_SUBMIT_SHIFT 0 +#define DA7219_MODE_SUBMIT_MASK (0x1 << 0) +#define DA7219_ADC_MODE_SHIFT 1 +#define DA7219_ADC_MODE_MASK (0x7F << 1) + +/* DA7219_SYSTEM_MODES_OUTPUT = 0x51 */ +#define DA7219_MODE_SUBMIT_SHIFT 0 +#define DA7219_MODE_SUBMIT_MASK (0x1 << 0) +#define DA7219_DAC_MODE_SHIFT 1 +#define DA7219_DAC_MODE_MASK (0x7F << 1) + +/* DA7219_MICBIAS_CTRL = 0x62 */ +#define DA7219_MICBIAS1_LEVEL_SHIFT 0 +#define DA7219_MICBIAS1_LEVEL_MASK (0x7 << 0) +#define DA7219_MICBIAS1_EN_SHIFT 3 +#define DA7219_MICBIAS1_EN_MASK (0x1 << 3) + +/* DA7219_MIC_1_CTRL = 0x63 */ +#define DA7219_MIC_1_AMP_RAMP_EN_SHIFT 5 +#define DA7219_MIC_1_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7219_MIC_1_AMP_MUTE_EN_SHIFT 6 +#define DA7219_MIC_1_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7219_MIC_1_AMP_EN_SHIFT 7 +#define DA7219_MIC_1_AMP_EN_MASK (0x1 << 7) + +/* DA7219_MIXIN_L_CTRL = 0x65 */ +#define DA7219_MIXIN_L_MIX_EN_SHIFT 3 +#define DA7219_MIXIN_L_MIX_EN_MASK (0x1 << 3) +#define DA7219_MIXIN_L_AMP_ZC_EN_SHIFT 4 +#define DA7219_MIXIN_L_AMP_ZC_EN_MASK (0x1 << 4) +#define DA7219_MIXIN_L_AMP_RAMP_EN_SHIFT 5 +#define DA7219_MIXIN_L_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7219_MIXIN_L_AMP_MUTE_EN_SHIFT 6 +#define DA7219_MIXIN_L_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7219_MIXIN_L_AMP_EN_SHIFT 7 +#define DA7219_MIXIN_L_AMP_EN_MASK (0x1 << 7) + +/* DA7219_ADC_L_CTRL = 0x67 */ +#define DA7219_ADC_L_BIAS_SHIFT 0 +#define DA7219_ADC_L_BIAS_MASK (0x3 << 0) +#define DA7219_ADC_L_RAMP_EN_SHIFT 5 +#define DA7219_ADC_L_RAMP_EN_MASK (0x1 << 5) +#define DA7219_ADC_L_MUTE_EN_SHIFT 6 +#define DA7219_ADC_L_MUTE_EN_MASK (0x1 << 6) +#define DA7219_ADC_L_EN_SHIFT 7 +#define DA7219_ADC_L_EN_MASK (0x1 << 7) + +/* DA7219_DAC_L_CTRL = 0x69 */ +#define DA7219_DAC_L_RAMP_EN_SHIFT 5 +#define DA7219_DAC_L_RAMP_EN_MASK (0x1 << 5) +#define DA7219_DAC_L_MUTE_EN_SHIFT 6 +#define DA7219_DAC_L_MUTE_EN_MASK (0x1 << 6) +#define DA7219_DAC_L_EN_SHIFT 7 +#define DA7219_DAC_L_EN_MASK (0x1 << 7) + +/* DA7219_DAC_R_CTRL = 0x6A */ +#define DA7219_DAC_R_RAMP_EN_SHIFT 5 +#define DA7219_DAC_R_RAMP_EN_MASK (0x1 << 5) +#define DA7219_DAC_R_MUTE_EN_SHIFT 6 +#define DA7219_DAC_R_MUTE_EN_MASK (0x1 << 6) +#define DA7219_DAC_R_EN_SHIFT 7 +#define DA7219_DAC_R_EN_MASK (0x1 << 7) + +/* DA7219_HP_L_CTRL = 0x6B */ +#define DA7219_HP_L_AMP_MIN_GAIN_EN_SHIFT 2 +#define DA7219_HP_L_AMP_MIN_GAIN_EN_MASK (0x1 << 2) +#define DA7219_HP_L_AMP_OE_SHIFT 3 +#define DA7219_HP_L_AMP_OE_MASK (0x1 << 3) +#define DA7219_HP_L_AMP_ZC_EN_SHIFT 4 +#define DA7219_HP_L_AMP_ZC_EN_MASK (0x1 << 4) +#define DA7219_HP_L_AMP_RAMP_EN_SHIFT 5 +#define DA7219_HP_L_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7219_HP_L_AMP_MUTE_EN_SHIFT 6 +#define DA7219_HP_L_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7219_HP_L_AMP_EN_SHIFT 7 +#define DA7219_HP_L_AMP_EN_MASK (0x1 << 7) + +/* DA7219_HP_R_CTRL = 0x6C */ +#define DA7219_HP_R_AMP_MIN_GAIN_EN_SHIFT 2 +#define DA7219_HP_R_AMP_MIN_GAIN_EN_MASK (0x1 << 2) +#define DA7219_HP_R_AMP_OE_SHIFT 3 +#define DA7219_HP_R_AMP_OE_MASK (0x1 << 3) +#define DA7219_HP_R_AMP_ZC_EN_SHIFT 4 +#define DA7219_HP_R_AMP_ZC_EN_MASK (0x1 << 4) +#define DA7219_HP_R_AMP_RAMP_EN_SHIFT 5 +#define DA7219_HP_R_AMP_RAMP_EN_MASK (0x1 << 5) +#define DA7219_HP_R_AMP_MUTE_EN_SHIFT 6 +#define DA7219_HP_R_AMP_MUTE_EN_MASK (0x1 << 6) +#define DA7219_HP_R_AMP_EN_SHIFT 7 +#define DA7219_HP_R_AMP_EN_MASK (0x1 << 7) + +/* DA7219_MIXOUT_L_CTRL = 0x6E */ +#define DA7219_MIXOUT_L_AMP_EN_SHIFT 7 +#define DA7219_MIXOUT_L_AMP_EN_MASK (0x1 << 7) + +/* DA7219_MIXOUT_R_CTRL = 0x6F */ +#define DA7219_MIXOUT_R_AMP_EN_SHIFT 7 +#define DA7219_MIXOUT_R_AMP_EN_MASK (0x1 << 7) + +/* DA7219_CHIP_ID1 = 0x81 */ +#define DA7219_CHIP_ID1_SHIFT 0 +#define DA7219_CHIP_ID1_MASK (0xFF << 0) + +/* DA7219_CHIP_ID2 = 0x82 */ +#define DA7219_CHIP_ID2_SHIFT 0 +#define DA7219_CHIP_ID2_MASK (0xFF << 0) + +/* DA7219_CHIP_REVISION = 0x83 */ +#define DA7219_CHIP_MINOR_SHIFT 0 +#define DA7219_CHIP_MINOR_MASK (0xF << 0) +#define DA7219_CHIP_MAJOR_SHIFT 4 +#define DA7219_CHIP_MAJOR_MASK (0xF << 4) + +/* DA7219_IO_CTRL = 0x91 */ +#define DA7219_IO_VOLTAGE_LEVEL_SHIFT 0 +#define DA7219_IO_VOLTAGE_LEVEL_MASK (0x1 << 0) +#define DA7219_IO_VOLTAGE_LEVEL_2_5V_3_6V 0 +#define DA7219_IO_VOLTAGE_LEVEL_1_2V_2_8V 1 + +/* DA7219_GAIN_RAMP_CTRL = 0x92 */ +#define DA7219_GAIN_RAMP_RATE_SHIFT 0 +#define DA7219_GAIN_RAMP_RATE_MASK (0x3 << 0) +#define DA7219_GAIN_RAMP_RATE_X8 (0x0 << 0) +#define DA7219_GAIN_RAMP_RATE_NOMINAL (0x1 << 0) +#define DA7219_GAIN_RAMP_RATE_MAX 4 + +/* DA7219_PC_COUNT = 0x94 */ +#define DA7219_PC_FREERUN_SHIFT 0 +#define DA7219_PC_FREERUN_MASK (0x1 << 0) +#define DA7219_PC_RESYNC_AUTO_SHIFT 1 +#define DA7219_PC_RESYNC_AUTO_MASK (0x1 << 1) + +/* DA7219_CP_VOL_THRESHOLD1 = 0x95 */ +#define DA7219_CP_THRESH_VDD2_SHIFT 0 +#define DA7219_CP_THRESH_VDD2_MASK (0x3F << 0) +#define DA7219_CP_THRESH_VDD2_MAX 0x3F + +/* DA7219_DIG_CTRL = 0x99 */ +#define DA7219_DAC_L_INV_SHIFT 3 +#define DA7219_DAC_L_INV_MASK (0x1 << 3) +#define DA7219_DAC_R_INV_SHIFT 7 +#define DA7219_DAC_R_INV_MASK (0x1 << 7) + +/* DA7219_ALC_CTRL2 = 0x9A */ +#define DA7219_ALC_ATTACK_SHIFT 0 +#define DA7219_ALC_ATTACK_MASK (0xF << 0) +#define DA7219_ALC_ATTACK_MAX 13 +#define DA7219_ALC_RELEASE_SHIFT 4 +#define DA7219_ALC_RELEASE_MASK (0xF << 4) +#define DA7219_ALC_RELEASE_MAX 11 + +/* DA7219_ALC_CTRL3 = 0x9B */ +#define DA7219_ALC_HOLD_SHIFT 0 +#define DA7219_ALC_HOLD_MASK (0xF << 0) +#define DA7219_ALC_HOLD_MAX 16 +#define DA7219_ALC_INTEG_ATTACK_SHIFT 4 +#define DA7219_ALC_INTEG_ATTACK_MASK (0x3 << 4) +#define DA7219_ALC_INTEG_RELEASE_SHIFT 6 +#define DA7219_ALC_INTEG_RELEASE_MASK (0x3 << 6) +#define DA7219_ALC_INTEG_MAX 4 + +/* DA7219_ALC_NOISE = 0x9C */ +#define DA7219_ALC_NOISE_SHIFT 0 +#define DA7219_ALC_NOISE_MASK (0x3F << 0) +#define DA7219_ALC_THRESHOLD_MAX 0x3F + +/* DA7219_ALC_TARGET_MIN = 0x9D */ +#define DA7219_ALC_THRESHOLD_MIN_SHIFT 0 +#define DA7219_ALC_THRESHOLD_MIN_MASK (0x3F << 0) + +/* DA7219_ALC_TARGET_MAX = 0x9E */ +#define DA7219_ALC_THRESHOLD_MAX_SHIFT 0 +#define DA7219_ALC_THRESHOLD_MAX_MASK (0x3F << 0) + +/* DA7219_ALC_GAIN_LIMITS = 0x9F */ +#define DA7219_ALC_ATTEN_MAX_SHIFT 0 +#define DA7219_ALC_ATTEN_MAX_MASK (0xF << 0) +#define DA7219_ALC_GAIN_MAX_SHIFT 4 +#define DA7219_ALC_GAIN_MAX_MASK (0xF << 4) +#define DA7219_ALC_ATTEN_GAIN_MAX 0xF + +/* DA7219_ALC_ANA_GAIN_LIMITS = 0xA0 */ +#define DA7219_ALC_ANA_GAIN_MIN_SHIFT 0 +#define DA7219_ALC_ANA_GAIN_MIN_MASK (0x7 << 0) +#define DA7219_ALC_ANA_GAIN_MIN 0x1 +#define DA7219_ALC_ANA_GAIN_MAX_SHIFT 4 +#define DA7219_ALC_ANA_GAIN_MAX_MASK (0x7 << 4) +#define DA7219_ALC_ANA_GAIN_MAX 0x7 + +/* DA7219_ALC_ANTICLIP_CTRL = 0xA1 */ +#define DA7219_ALC_ANTICLIP_STEP_SHIFT 0 +#define DA7219_ALC_ANTICLIP_STEP_MASK (0x3 << 0) +#define DA7219_ALC_ANTICLIP_STEP_MAX 4 +#define DA7219_ALC_ANTIPCLIP_EN_SHIFT 7 +#define DA7219_ALC_ANTIPCLIP_EN_MASK (0x1 << 7) + +/* DA7219_ALC_ANTICLIP_LEVEL = 0xA2 */ +#define DA7219_ALC_ANTICLIP_LEVEL_SHIFT 0 +#define DA7219_ALC_ANTICLIP_LEVEL_MASK (0x7F << 0) + +/* DA7219_ALC_OFFSET_AUTO_M_L = 0xA3 */ +#define DA7219_ALC_OFFSET_AUTO_M_L_SHIFT 0 +#define DA7219_ALC_OFFSET_AUTO_M_L_MASK (0xFF << 0) + +/* DA7219_ALC_OFFSET_AUTO_U_L = 0xA4 */ +#define DA7219_ALC_OFFSET_AUTO_U_L_SHIFT 0 +#define DA7219_ALC_OFFSET_AUTO_U_L_MASK (0xF << 0) + +/* DA7219_DAC_NG_SETUP_TIME = 0xAF */ +#define DA7219_DAC_NG_SETUP_TIME_SHIFT 0 +#define DA7219_DAC_NG_SETUP_TIME_MASK (0x3 << 0) +#define DA7219_DAC_NG_SETUP_TIME_MAX 4 +#define DA7219_DAC_NG_RAMPUP_RATE_SHIFT 2 +#define DA7219_DAC_NG_RAMPUP_RATE_MASK (0x1 << 2) +#define DA7219_DAC_NG_RAMPDN_RATE_SHIFT 3 +#define DA7219_DAC_NG_RAMPDN_RATE_MASK (0x1 << 3) +#define DA7219_DAC_NG_RAMP_RATE_MAX 2 + +/* DA7219_DAC_NG_OFF_THRESH = 0xB0 */ +#define DA7219_DAC_NG_OFF_THRESHOLD_SHIFT 0 +#define DA7219_DAC_NG_OFF_THRESHOLD_MASK (0x7 << 0) +#define DA7219_DAC_NG_THRESHOLD_MAX 0x7 + +/* DA7219_DAC_NG_ON_THRESH = 0xB1 */ +#define DA7219_DAC_NG_ON_THRESHOLD_SHIFT 0 +#define DA7219_DAC_NG_ON_THRESHOLD_MASK (0x7 << 0) + +/* DA7219_DAC_NG_CTRL = 0xB2 */ +#define DA7219_DAC_NG_EN_SHIFT 7 +#define DA7219_DAC_NG_EN_MASK (0x1 << 7) + +/* DA7219_TONE_GEN_CFG1 = 0xB4 */ +#define DA7219_DTMF_REG_SHIFT 0 +#define DA7219_DTMF_REG_MASK (0xF << 0) +#define DA7219_DTMF_REG_MAX 16 +#define DA7219_DTMF_EN_SHIFT 4 +#define DA7219_DTMF_EN_MASK (0x1 << 4) +#define DA7219_START_STOPN_SHIFT 7 +#define DA7219_START_STOPN_MASK (0x1 << 7) + +/* DA7219_TONE_GEN_CFG2 = 0xB5 */ +#define DA7219_SWG_SEL_SHIFT 0 +#define DA7219_SWG_SEL_MASK (0x3 << 0) +#define DA7219_SWG_SEL_MAX 4 +#define DA7219_SWG_SEL_SRAMP (0x3 << 0) +#define DA7219_TONE_GEN_GAIN_SHIFT 4 +#define DA7219_TONE_GEN_GAIN_MASK (0xF << 4) +#define DA7219_TONE_GEN_GAIN_MAX 0xF +#define DA7219_TONE_GEN_GAIN_MINUS_9DB (0x3 << 4) +#define DA7219_TONE_GEN_GAIN_MINUS_15DB (0x5 << 4) + +/* DA7219_TONE_GEN_CYCLES = 0xB6 */ +#define DA7219_BEEP_CYCLES_SHIFT 0 +#define DA7219_BEEP_CYCLES_MASK (0x7 << 0) + +/* DA7219_TONE_GEN_FREQ1_L = 0xB7 */ +#define DA7219_FREQ1_L_SHIFT 0 +#define DA7219_FREQ1_L_MASK (0xFF << 0) +#define DA7219_FREQ_MAX 0xFFFF + +/* DA7219_TONE_GEN_FREQ1_U = 0xB8 */ +#define DA7219_FREQ1_U_SHIFT 0 +#define DA7219_FREQ1_U_MASK (0xFF << 0) + +/* DA7219_TONE_GEN_FREQ2_L = 0xB9 */ +#define DA7219_FREQ2_L_SHIFT 0 +#define DA7219_FREQ2_L_MASK (0xFF << 0) + +/* DA7219_TONE_GEN_FREQ2_U = 0xBA */ +#define DA7219_FREQ2_U_SHIFT 0 +#define DA7219_FREQ2_U_MASK (0xFF << 0) + +/* DA7219_TONE_GEN_ON_PER = 0xBB */ +#define DA7219_BEEP_ON_PER_SHIFT 0 +#define DA7219_BEEP_ON_PER_MASK (0x3F << 0) +#define DA7219_BEEP_ON_OFF_MAX 0x3F + +/* DA7219_TONE_GEN_OFF_PER = 0xBC */ +#define DA7219_BEEP_OFF_PER_SHIFT 0 +#define DA7219_BEEP_OFF_PER_MASK (0x3F << 0) + +/* DA7219_SYSTEM_STATUS = 0xE0 */ +#define DA7219_SC1_BUSY_SHIFT 0 +#define DA7219_SC1_BUSY_MASK (0x1 << 0) +#define DA7219_SC2_BUSY_SHIFT 1 +#define DA7219_SC2_BUSY_MASK (0x1 << 1) + +/* DA7219_SYSTEM_ACTIVE = 0xFD */ +#define DA7219_SYSTEM_ACTIVE_SHIFT 0 +#define DA7219_SYSTEM_ACTIVE_MASK (0x1 << 0) + + +/* + * General defines & data + */ + +/* Register inversion */ +#define DA7219_NO_INVERT 0 +#define DA7219_INVERT 1 + +/* Byte related defines */ +#define DA7219_BYTE_SHIFT 8 +#define DA7219_BYTE_MASK 0xFF + +/* PLL Output Frequencies */ +#define DA7219_PLL_FREQ_OUT_90316 90316800 +#define DA7219_PLL_FREQ_OUT_98304 98304000 + +/* PLL Frequency Dividers */ +#define DA7219_PLL_INDIV_2_TO_4_5_MHZ_VAL 1 +#define DA7219_PLL_INDIV_4_5_TO_9_MHZ_VAL 2 +#define DA7219_PLL_INDIV_9_TO_18_MHZ_VAL 4 +#define DA7219_PLL_INDIV_18_TO_36_MHZ_VAL 8 +#define DA7219_PLL_INDIV_36_TO_54_MHZ_VAL 16 + +/* SRM */ +#define DA7219_SRM_CHECK_RETRIES 8 + +/* System Controller */ +#define DA7219_SYS_STAT_CHECK_RETRIES 6 +#define DA7219_SYS_STAT_CHECK_DELAY 50 + +/* Power up/down Delays */ +#define DA7219_SETTLING_DELAY 40 +#define DA7219_MIN_GAIN_DELAY 30 +#define DA7219_MIC_PGA_BASE_DELAY 100 +#define DA7219_MIC_PGA_OFFSET_DELAY 40 + +enum da7219_clk_src { + DA7219_CLKSRC_MCLK = 0, + DA7219_CLKSRC_MCLK_SQR, +}; + +enum da7219_sys_clk { + DA7219_SYSCLK_MCLK = 0, + DA7219_SYSCLK_PLL, + DA7219_SYSCLK_PLL_SRM, +}; + +/* Regulators */ +enum da7219_supplies { + DA7219_SUPPLY_VDD = 0, + DA7219_SUPPLY_VDDMIC, + DA7219_SUPPLY_VDDIO, + DA7219_NUM_SUPPLIES, +}; + +struct da7219_aad_priv; + +/* Private data */ +struct da7219_priv { + struct da7219_aad_priv *aad; + struct da7219_pdata *pdata; + + bool wakeup_source; + struct regulator_bulk_data supplies[DA7219_NUM_SUPPLIES]; + struct regmap *regmap; + struct mutex ctrl_lock; + struct mutex pll_lock; + +#ifdef CONFIG_COMMON_CLK + struct clk_hw dai_clks_hw; +#endif + struct clk_lookup *dai_clks_lookup; + struct clk *dai_clks; + + struct clk *mclk; + unsigned int mclk_rate; + int clk_src; + + bool master; + bool alc_en; + bool micbias_on_event; + unsigned int mic_pga_delay; + u8 gain_ramp_ctrl; +}; + +int da7219_set_pll(struct snd_soc_component *component, int source, unsigned int fout); + +#endif /* __DA7219_H */ diff --git a/sound/soc/codecs/da732x.c b/sound/soc/codecs/da732x.c new file mode 100644 index 000000000..de275df8f --- /dev/null +++ b/sound/soc/codecs/da732x.c @@ -0,0 +1,1583 @@ +/* + * da732x.c --- Dialog DA732X ALSA SoC Audio Driver + * + * Copyright (C) 2012 Dialog Semiconductor GmbH + * + * Author: Michal Hajduk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "da732x.h" +#include "da732x_reg.h" + + +struct da732x_priv { + struct regmap *regmap; + + unsigned int sysclk; + bool pll_en; +}; + +/* + * da732x register cache - default settings + */ +static const struct reg_default da732x_reg_cache[] = { + { DA732X_REG_REF1 , 0x02 }, + { DA732X_REG_BIAS_EN , 0x80 }, + { DA732X_REG_BIAS1 , 0x00 }, + { DA732X_REG_BIAS2 , 0x00 }, + { DA732X_REG_BIAS3 , 0x00 }, + { DA732X_REG_BIAS4 , 0x00 }, + { DA732X_REG_MICBIAS2 , 0x00 }, + { DA732X_REG_MICBIAS1 , 0x00 }, + { DA732X_REG_MICDET , 0x00 }, + { DA732X_REG_MIC1_PRE , 0x01 }, + { DA732X_REG_MIC1 , 0x40 }, + { DA732X_REG_MIC2_PRE , 0x01 }, + { DA732X_REG_MIC2 , 0x40 }, + { DA732X_REG_AUX1L , 0x75 }, + { DA732X_REG_AUX1R , 0x75 }, + { DA732X_REG_MIC3_PRE , 0x01 }, + { DA732X_REG_MIC3 , 0x40 }, + { DA732X_REG_INP_PINBIAS , 0x00 }, + { DA732X_REG_INP_ZC_EN , 0x00 }, + { DA732X_REG_INP_MUX , 0x50 }, + { DA732X_REG_HP_DET , 0x00 }, + { DA732X_REG_HPL_DAC_OFFSET , 0x00 }, + { DA732X_REG_HPL_DAC_OFF_CNTL , 0x00 }, + { DA732X_REG_HPL_OUT_OFFSET , 0x00 }, + { DA732X_REG_HPL , 0x40 }, + { DA732X_REG_HPL_VOL , 0x0F }, + { DA732X_REG_HPR_DAC_OFFSET , 0x00 }, + { DA732X_REG_HPR_DAC_OFF_CNTL , 0x00 }, + { DA732X_REG_HPR_OUT_OFFSET , 0x00 }, + { DA732X_REG_HPR , 0x40 }, + { DA732X_REG_HPR_VOL , 0x0F }, + { DA732X_REG_LIN2 , 0x4F }, + { DA732X_REG_LIN3 , 0x4F }, + { DA732X_REG_LIN4 , 0x4F }, + { DA732X_REG_OUT_ZC_EN , 0x00 }, + { DA732X_REG_HP_LIN1_GNDSEL , 0x00 }, + { DA732X_REG_CP_HP1 , 0x0C }, + { DA732X_REG_CP_HP2 , 0x03 }, + { DA732X_REG_CP_CTRL1 , 0x00 }, + { DA732X_REG_CP_CTRL2 , 0x99 }, + { DA732X_REG_CP_CTRL3 , 0x25 }, + { DA732X_REG_CP_LEVEL_MASK , 0x3F }, + { DA732X_REG_CP_DET , 0x00 }, + { DA732X_REG_CP_STATUS , 0x00 }, + { DA732X_REG_CP_THRESH1 , 0x00 }, + { DA732X_REG_CP_THRESH2 , 0x00 }, + { DA732X_REG_CP_THRESH3 , 0x00 }, + { DA732X_REG_CP_THRESH4 , 0x00 }, + { DA732X_REG_CP_THRESH5 , 0x00 }, + { DA732X_REG_CP_THRESH6 , 0x00 }, + { DA732X_REG_CP_THRESH7 , 0x00 }, + { DA732X_REG_CP_THRESH8 , 0x00 }, + { DA732X_REG_PLL_DIV_LO , 0x00 }, + { DA732X_REG_PLL_DIV_MID , 0x00 }, + { DA732X_REG_PLL_DIV_HI , 0x00 }, + { DA732X_REG_PLL_CTRL , 0x02 }, + { DA732X_REG_CLK_CTRL , 0xaa }, + { DA732X_REG_CLK_DSP , 0x07 }, + { DA732X_REG_CLK_EN1 , 0x00 }, + { DA732X_REG_CLK_EN2 , 0x00 }, + { DA732X_REG_CLK_EN3 , 0x00 }, + { DA732X_REG_CLK_EN4 , 0x00 }, + { DA732X_REG_CLK_EN5 , 0x00 }, + { DA732X_REG_AIF_MCLK , 0x00 }, + { DA732X_REG_AIFA1 , 0x02 }, + { DA732X_REG_AIFA2 , 0x00 }, + { DA732X_REG_AIFA3 , 0x08 }, + { DA732X_REG_AIFB1 , 0x02 }, + { DA732X_REG_AIFB2 , 0x00 }, + { DA732X_REG_AIFB3 , 0x08 }, + { DA732X_REG_PC_CTRL , 0xC0 }, + { DA732X_REG_DATA_ROUTE , 0x00 }, + { DA732X_REG_DSP_CTRL , 0x00 }, + { DA732X_REG_CIF_CTRL2 , 0x00 }, + { DA732X_REG_HANDSHAKE , 0x00 }, + { DA732X_REG_SPARE1_OUT , 0x00 }, + { DA732X_REG_SPARE2_OUT , 0x00 }, + { DA732X_REG_SPARE1_IN , 0x00 }, + { DA732X_REG_ADC1_PD , 0x00 }, + { DA732X_REG_ADC1_HPF , 0x00 }, + { DA732X_REG_ADC1_SEL , 0x00 }, + { DA732X_REG_ADC1_EQ12 , 0x00 }, + { DA732X_REG_ADC1_EQ34 , 0x00 }, + { DA732X_REG_ADC1_EQ5 , 0x00 }, + { DA732X_REG_ADC2_PD , 0x00 }, + { DA732X_REG_ADC2_HPF , 0x00 }, + { DA732X_REG_ADC2_SEL , 0x00 }, + { DA732X_REG_ADC2_EQ12 , 0x00 }, + { DA732X_REG_ADC2_EQ34 , 0x00 }, + { DA732X_REG_ADC2_EQ5 , 0x00 }, + { DA732X_REG_DAC1_HPF , 0x00 }, + { DA732X_REG_DAC1_L_VOL , 0x00 }, + { DA732X_REG_DAC1_R_VOL , 0x00 }, + { DA732X_REG_DAC1_SEL , 0x00 }, + { DA732X_REG_DAC1_SOFTMUTE , 0x00 }, + { DA732X_REG_DAC1_EQ12 , 0x00 }, + { DA732X_REG_DAC1_EQ34 , 0x00 }, + { DA732X_REG_DAC1_EQ5 , 0x00 }, + { DA732X_REG_DAC2_HPF , 0x00 }, + { DA732X_REG_DAC2_L_VOL , 0x00 }, + { DA732X_REG_DAC2_R_VOL , 0x00 }, + { DA732X_REG_DAC2_SEL , 0x00 }, + { DA732X_REG_DAC2_SOFTMUTE , 0x00 }, + { DA732X_REG_DAC2_EQ12 , 0x00 }, + { DA732X_REG_DAC2_EQ34 , 0x00 }, + { DA732X_REG_DAC2_EQ5 , 0x00 }, + { DA732X_REG_DAC3_HPF , 0x00 }, + { DA732X_REG_DAC3_VOL , 0x00 }, + { DA732X_REG_DAC3_SEL , 0x00 }, + { DA732X_REG_DAC3_SOFTMUTE , 0x00 }, + { DA732X_REG_DAC3_EQ12 , 0x00 }, + { DA732X_REG_DAC3_EQ34 , 0x00 }, + { DA732X_REG_DAC3_EQ5 , 0x00 }, + { DA732X_REG_BIQ_BYP , 0x00 }, + { DA732X_REG_DMA_CMD , 0x00 }, + { DA732X_REG_DMA_ADDR0 , 0x00 }, + { DA732X_REG_DMA_ADDR1 , 0x00 }, + { DA732X_REG_DMA_DATA0 , 0x00 }, + { DA732X_REG_DMA_DATA1 , 0x00 }, + { DA732X_REG_DMA_DATA2 , 0x00 }, + { DA732X_REG_DMA_DATA3 , 0x00 }, + { DA732X_REG_UNLOCK , 0x00 }, +}; + +static inline int da732x_get_input_div(struct snd_soc_component *component, int sysclk) +{ + int val; + int ret; + + if (sysclk < DA732X_MCLK_10MHZ) { + val = DA732X_MCLK_RET_0_10MHZ; + ret = DA732X_MCLK_VAL_0_10MHZ; + } else if ((sysclk >= DA732X_MCLK_10MHZ) && + (sysclk < DA732X_MCLK_20MHZ)) { + val = DA732X_MCLK_RET_10_20MHZ; + ret = DA732X_MCLK_VAL_10_20MHZ; + } else if ((sysclk >= DA732X_MCLK_20MHZ) && + (sysclk < DA732X_MCLK_40MHZ)) { + val = DA732X_MCLK_RET_20_40MHZ; + ret = DA732X_MCLK_VAL_20_40MHZ; + } else if ((sysclk >= DA732X_MCLK_40MHZ) && + (sysclk <= DA732X_MCLK_54MHZ)) { + val = DA732X_MCLK_RET_40_54MHZ; + ret = DA732X_MCLK_VAL_40_54MHZ; + } else { + return -EINVAL; + } + + snd_soc_component_write(component, DA732X_REG_PLL_CTRL, val); + + return ret; +} + +static void da732x_set_charge_pump(struct snd_soc_component *component, int state) +{ + switch (state) { + case DA732X_ENABLE_CP: + snd_soc_component_write(component, DA732X_REG_CLK_EN2, DA732X_CP_CLK_EN); + snd_soc_component_write(component, DA732X_REG_CP_HP2, DA732X_HP_CP_EN | + DA732X_HP_CP_REG | DA732X_HP_CP_PULSESKIP); + snd_soc_component_write(component, DA732X_REG_CP_CTRL1, DA732X_CP_EN | + DA732X_CP_CTRL_CPVDD1); + snd_soc_component_write(component, DA732X_REG_CP_CTRL2, + DA732X_CP_MANAGE_MAGNITUDE | DA732X_CP_BOOST); + snd_soc_component_write(component, DA732X_REG_CP_CTRL3, DA732X_CP_1MHZ); + break; + case DA732X_DISABLE_CP: + snd_soc_component_write(component, DA732X_REG_CLK_EN2, DA732X_CP_CLK_DIS); + snd_soc_component_write(component, DA732X_REG_CP_HP2, DA732X_HP_CP_DIS); + snd_soc_component_write(component, DA732X_REG_CP_CTRL1, DA723X_CP_DIS); + break; + default: + pr_err("Wrong charge pump state\n"); + break; + } +} + +static const DECLARE_TLV_DB_SCALE(mic_boost_tlv, DA732X_MIC_PRE_VOL_DB_MIN, + DA732X_MIC_PRE_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(mic_pga_tlv, DA732X_MIC_VOL_DB_MIN, + DA732X_MIC_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(aux_pga_tlv, DA732X_AUX_VOL_DB_MIN, + DA732X_AUX_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(hp_pga_tlv, DA732X_HP_VOL_DB_MIN, + DA732X_AUX_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(lin2_pga_tlv, DA732X_LIN2_VOL_DB_MIN, + DA732X_LIN2_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(lin3_pga_tlv, DA732X_LIN3_VOL_DB_MIN, + DA732X_LIN3_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(lin4_pga_tlv, DA732X_LIN4_VOL_DB_MIN, + DA732X_LIN4_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(adc_pga_tlv, DA732X_ADC_VOL_DB_MIN, + DA732X_ADC_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(dac_pga_tlv, DA732X_DAC_VOL_DB_MIN, + DA732X_DAC_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(eq_band_pga_tlv, DA732X_EQ_BAND_VOL_DB_MIN, + DA732X_EQ_BAND_VOL_DB_INC, 0); + +static const DECLARE_TLV_DB_SCALE(eq_overall_tlv, DA732X_EQ_OVERALL_VOL_DB_MIN, + DA732X_EQ_OVERALL_VOL_DB_INC, 0); + +/* High Pass Filter */ +static const char *da732x_hpf_mode[] = { + "Disable", "Music", "Voice", +}; + +static const char *da732x_hpf_music[] = { + "1.8Hz", "3.75Hz", "7.5Hz", "15Hz", +}; + +static const char *da732x_hpf_voice[] = { + "2.5Hz", "25Hz", "50Hz", "100Hz", + "150Hz", "200Hz", "300Hz", "400Hz" +}; + +static SOC_ENUM_SINGLE_DECL(da732x_dac1_hpf_mode_enum, + DA732X_REG_DAC1_HPF, DA732X_HPF_MODE_SHIFT, + da732x_hpf_mode); + +static SOC_ENUM_SINGLE_DECL(da732x_dac2_hpf_mode_enum, + DA732X_REG_DAC2_HPF, DA732X_HPF_MODE_SHIFT, + da732x_hpf_mode); + +static SOC_ENUM_SINGLE_DECL(da732x_dac3_hpf_mode_enum, + DA732X_REG_DAC3_HPF, DA732X_HPF_MODE_SHIFT, + da732x_hpf_mode); + +static SOC_ENUM_SINGLE_DECL(da732x_adc1_hpf_mode_enum, + DA732X_REG_ADC1_HPF, DA732X_HPF_MODE_SHIFT, + da732x_hpf_mode); + +static SOC_ENUM_SINGLE_DECL(da732x_adc2_hpf_mode_enum, + DA732X_REG_ADC2_HPF, DA732X_HPF_MODE_SHIFT, + da732x_hpf_mode); + +static SOC_ENUM_SINGLE_DECL(da732x_dac1_hp_filter_enum, + DA732X_REG_DAC1_HPF, DA732X_HPF_MUSIC_SHIFT, + da732x_hpf_music); + +static SOC_ENUM_SINGLE_DECL(da732x_dac2_hp_filter_enum, + DA732X_REG_DAC2_HPF, DA732X_HPF_MUSIC_SHIFT, + da732x_hpf_music); + +static SOC_ENUM_SINGLE_DECL(da732x_dac3_hp_filter_enum, + DA732X_REG_DAC3_HPF, DA732X_HPF_MUSIC_SHIFT, + da732x_hpf_music); + +static SOC_ENUM_SINGLE_DECL(da732x_adc1_hp_filter_enum, + DA732X_REG_ADC1_HPF, DA732X_HPF_MUSIC_SHIFT, + da732x_hpf_music); + +static SOC_ENUM_SINGLE_DECL(da732x_adc2_hp_filter_enum, + DA732X_REG_ADC2_HPF, DA732X_HPF_MUSIC_SHIFT, + da732x_hpf_music); + +static SOC_ENUM_SINGLE_DECL(da732x_dac1_voice_filter_enum, + DA732X_REG_DAC1_HPF, DA732X_HPF_VOICE_SHIFT, + da732x_hpf_voice); + +static SOC_ENUM_SINGLE_DECL(da732x_dac2_voice_filter_enum, + DA732X_REG_DAC2_HPF, DA732X_HPF_VOICE_SHIFT, + da732x_hpf_voice); + +static SOC_ENUM_SINGLE_DECL(da732x_dac3_voice_filter_enum, + DA732X_REG_DAC3_HPF, DA732X_HPF_VOICE_SHIFT, + da732x_hpf_voice); + +static SOC_ENUM_SINGLE_DECL(da732x_adc1_voice_filter_enum, + DA732X_REG_ADC1_HPF, DA732X_HPF_VOICE_SHIFT, + da732x_hpf_voice); + +static SOC_ENUM_SINGLE_DECL(da732x_adc2_voice_filter_enum, + DA732X_REG_ADC2_HPF, DA732X_HPF_VOICE_SHIFT, + da732x_hpf_voice); + +static int da732x_hpf_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_enum *enum_ctrl = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = enum_ctrl->reg; + unsigned int sel = ucontrol->value.enumerated.item[0]; + unsigned int bits; + + switch (sel) { + case DA732X_HPF_DISABLED: + bits = DA732X_HPF_DIS; + break; + case DA732X_HPF_VOICE: + bits = DA732X_HPF_VOICE_EN; + break; + case DA732X_HPF_MUSIC: + bits = DA732X_HPF_MUSIC_EN; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, reg, DA732X_HPF_MASK, bits); + + return 0; +} + +static int da732x_hpf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_enum *enum_ctrl = (struct soc_enum *)kcontrol->private_value; + unsigned int reg = enum_ctrl->reg; + int val; + + val = snd_soc_component_read32(component, reg) & DA732X_HPF_MASK; + + switch (val) { + case DA732X_HPF_VOICE_EN: + ucontrol->value.enumerated.item[0] = DA732X_HPF_VOICE; + break; + case DA732X_HPF_MUSIC_EN: + ucontrol->value.enumerated.item[0] = DA732X_HPF_MUSIC; + break; + default: + ucontrol->value.enumerated.item[0] = DA732X_HPF_DISABLED; + break; + } + + return 0; +} + +static const struct snd_kcontrol_new da732x_snd_controls[] = { + /* Input PGAs */ + SOC_SINGLE_RANGE_TLV("MIC1 Boost Volume", DA732X_REG_MIC1_PRE, + DA732X_MICBOOST_SHIFT, DA732X_MICBOOST_MIN, + DA732X_MICBOOST_MAX, 0, mic_boost_tlv), + SOC_SINGLE_RANGE_TLV("MIC2 Boost Volume", DA732X_REG_MIC2_PRE, + DA732X_MICBOOST_SHIFT, DA732X_MICBOOST_MIN, + DA732X_MICBOOST_MAX, 0, mic_boost_tlv), + SOC_SINGLE_RANGE_TLV("MIC3 Boost Volume", DA732X_REG_MIC3_PRE, + DA732X_MICBOOST_SHIFT, DA732X_MICBOOST_MIN, + DA732X_MICBOOST_MAX, 0, mic_boost_tlv), + + /* MICs */ + SOC_SINGLE("MIC1 Switch", DA732X_REG_MIC1, DA732X_MIC_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_RANGE_TLV("MIC1 Volume", DA732X_REG_MIC1, + DA732X_MIC_VOL_SHIFT, DA732X_MIC_VOL_VAL_MIN, + DA732X_MIC_VOL_VAL_MAX, 0, mic_pga_tlv), + SOC_SINGLE("MIC2 Switch", DA732X_REG_MIC2, DA732X_MIC_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_RANGE_TLV("MIC2 Volume", DA732X_REG_MIC2, + DA732X_MIC_VOL_SHIFT, DA732X_MIC_VOL_VAL_MIN, + DA732X_MIC_VOL_VAL_MAX, 0, mic_pga_tlv), + SOC_SINGLE("MIC3 Switch", DA732X_REG_MIC3, DA732X_MIC_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_RANGE_TLV("MIC3 Volume", DA732X_REG_MIC3, + DA732X_MIC_VOL_SHIFT, DA732X_MIC_VOL_VAL_MIN, + DA732X_MIC_VOL_VAL_MAX, 0, mic_pga_tlv), + + /* AUXs */ + SOC_SINGLE("AUX1L Switch", DA732X_REG_AUX1L, DA732X_AUX_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("AUX1L Volume", DA732X_REG_AUX1L, + DA732X_AUX_VOL_SHIFT, DA732X_AUX_VOL_VAL_MAX, + DA732X_NO_INVERT, aux_pga_tlv), + SOC_SINGLE("AUX1R Switch", DA732X_REG_AUX1R, DA732X_AUX_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("AUX1R Volume", DA732X_REG_AUX1R, + DA732X_AUX_VOL_SHIFT, DA732X_AUX_VOL_VAL_MAX, + DA732X_NO_INVERT, aux_pga_tlv), + + /* ADCs */ + SOC_DOUBLE_TLV("ADC1 Volume", DA732X_REG_ADC1_SEL, + DA732X_ADCL_VOL_SHIFT, DA732X_ADCR_VOL_SHIFT, + DA732X_ADC_VOL_VAL_MAX, DA732X_INVERT, adc_pga_tlv), + + SOC_DOUBLE_TLV("ADC2 Volume", DA732X_REG_ADC2_SEL, + DA732X_ADCL_VOL_SHIFT, DA732X_ADCR_VOL_SHIFT, + DA732X_ADC_VOL_VAL_MAX, DA732X_INVERT, adc_pga_tlv), + + /* DACs */ + SOC_DOUBLE("Digital Playback DAC12 Switch", DA732X_REG_DAC1_SEL, + DA732X_DACL_MUTE_SHIFT, DA732X_DACR_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_DOUBLE_R_TLV("Digital Playback DAC12 Volume", DA732X_REG_DAC1_L_VOL, + DA732X_REG_DAC1_R_VOL, DA732X_DAC_VOL_SHIFT, + DA732X_DAC_VOL_VAL_MAX, DA732X_INVERT, dac_pga_tlv), + SOC_SINGLE("Digital Playback DAC3 Switch", DA732X_REG_DAC2_SEL, + DA732X_DACL_MUTE_SHIFT, DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("Digital Playback DAC3 Volume", DA732X_REG_DAC2_L_VOL, + DA732X_DAC_VOL_SHIFT, DA732X_DAC_VOL_VAL_MAX, + DA732X_INVERT, dac_pga_tlv), + SOC_SINGLE("Digital Playback DAC4 Switch", DA732X_REG_DAC2_SEL, + DA732X_DACR_MUTE_SHIFT, DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("Digital Playback DAC4 Volume", DA732X_REG_DAC2_R_VOL, + DA732X_DAC_VOL_SHIFT, DA732X_DAC_VOL_VAL_MAX, + DA732X_INVERT, dac_pga_tlv), + SOC_SINGLE("Digital Playback DAC5 Switch", DA732X_REG_DAC3_SEL, + DA732X_DACL_MUTE_SHIFT, DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("Digital Playback DAC5 Volume", DA732X_REG_DAC3_VOL, + DA732X_DAC_VOL_SHIFT, DA732X_DAC_VOL_VAL_MAX, + DA732X_INVERT, dac_pga_tlv), + + /* High Pass Filters */ + SOC_ENUM_EXT("DAC1 High Pass Filter Mode", + da732x_dac1_hpf_mode_enum, da732x_hpf_get, da732x_hpf_set), + SOC_ENUM("DAC1 High Pass Filter", da732x_dac1_hp_filter_enum), + SOC_ENUM("DAC1 Voice Filter", da732x_dac1_voice_filter_enum), + + SOC_ENUM_EXT("DAC2 High Pass Filter Mode", + da732x_dac2_hpf_mode_enum, da732x_hpf_get, da732x_hpf_set), + SOC_ENUM("DAC2 High Pass Filter", da732x_dac2_hp_filter_enum), + SOC_ENUM("DAC2 Voice Filter", da732x_dac2_voice_filter_enum), + + SOC_ENUM_EXT("DAC3 High Pass Filter Mode", + da732x_dac3_hpf_mode_enum, da732x_hpf_get, da732x_hpf_set), + SOC_ENUM("DAC3 High Pass Filter", da732x_dac3_hp_filter_enum), + SOC_ENUM("DAC3 Filter Mode", da732x_dac3_voice_filter_enum), + + SOC_ENUM_EXT("ADC1 High Pass Filter Mode", + da732x_adc1_hpf_mode_enum, da732x_hpf_get, da732x_hpf_set), + SOC_ENUM("ADC1 High Pass Filter", da732x_adc1_hp_filter_enum), + SOC_ENUM("ADC1 Voice Filter", da732x_adc1_voice_filter_enum), + + SOC_ENUM_EXT("ADC2 High Pass Filter Mode", + da732x_adc2_hpf_mode_enum, da732x_hpf_get, da732x_hpf_set), + SOC_ENUM("ADC2 High Pass Filter", da732x_adc2_hp_filter_enum), + SOC_ENUM("ADC2 Voice Filter", da732x_adc2_voice_filter_enum), + + /* Equalizers */ + SOC_SINGLE("ADC1 EQ Switch", DA732X_REG_ADC1_EQ5, + DA732X_EQ_EN_SHIFT, DA732X_EQ_EN_MAX, DA732X_NO_INVERT), + SOC_SINGLE_TLV("ADC1 EQ Band 1 Volume", DA732X_REG_ADC1_EQ12, + DA732X_EQ_BAND1_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC1 EQ Band 2 Volume", DA732X_REG_ADC1_EQ12, + DA732X_EQ_BAND2_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC1 EQ Band 3 Volume", DA732X_REG_ADC1_EQ34, + DA732X_EQ_BAND3_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC1 EQ Band 4 Volume", DA732X_REG_ADC1_EQ34, + DA732X_EQ_BAND4_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC1 EQ Band 5 Volume", DA732X_REG_ADC1_EQ5, + DA732X_EQ_BAND5_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC1 EQ Overall Volume", DA732X_REG_ADC1_EQ5, + DA732X_EQ_OVERALL_SHIFT, DA732X_EQ_OVERALL_VOL_VAL_MAX, + DA732X_INVERT, eq_overall_tlv), + + SOC_SINGLE("ADC2 EQ Switch", DA732X_REG_ADC2_EQ5, + DA732X_EQ_EN_SHIFT, DA732X_EQ_EN_MAX, DA732X_NO_INVERT), + SOC_SINGLE_TLV("ADC2 EQ Band 1 Volume", DA732X_REG_ADC2_EQ12, + DA732X_EQ_BAND1_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC2 EQ Band 2 Volume", DA732X_REG_ADC2_EQ12, + DA732X_EQ_BAND2_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC2 EQ Band 3 Volume", DA732X_REG_ADC2_EQ34, + DA732X_EQ_BAND3_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ACD2 EQ Band 4 Volume", DA732X_REG_ADC2_EQ34, + DA732X_EQ_BAND4_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ACD2 EQ Band 5 Volume", DA732X_REG_ADC2_EQ5, + DA732X_EQ_BAND5_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("ADC2 EQ Overall Volume", DA732X_REG_ADC1_EQ5, + DA732X_EQ_OVERALL_SHIFT, DA732X_EQ_OVERALL_VOL_VAL_MAX, + DA732X_INVERT, eq_overall_tlv), + + SOC_SINGLE("DAC1 EQ Switch", DA732X_REG_DAC1_EQ5, + DA732X_EQ_EN_SHIFT, DA732X_EQ_EN_MAX, DA732X_NO_INVERT), + SOC_SINGLE_TLV("DAC1 EQ Band 1 Volume", DA732X_REG_DAC1_EQ12, + DA732X_EQ_BAND1_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC1 EQ Band 2 Volume", DA732X_REG_DAC1_EQ12, + DA732X_EQ_BAND2_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC1 EQ Band 3 Volume", DA732X_REG_DAC1_EQ34, + DA732X_EQ_BAND3_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC1 EQ Band 4 Volume", DA732X_REG_DAC1_EQ34, + DA732X_EQ_BAND4_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC1 EQ Band 5 Volume", DA732X_REG_DAC1_EQ5, + DA732X_EQ_BAND5_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + + SOC_SINGLE("DAC2 EQ Switch", DA732X_REG_DAC2_EQ5, + DA732X_EQ_EN_SHIFT, DA732X_EQ_EN_MAX, DA732X_NO_INVERT), + SOC_SINGLE_TLV("DAC2 EQ Band 1 Volume", DA732X_REG_DAC2_EQ12, + DA732X_EQ_BAND1_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC2 EQ Band 2 Volume", DA732X_REG_DAC2_EQ12, + DA732X_EQ_BAND2_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC2 EQ Band 3 Volume", DA732X_REG_DAC2_EQ34, + DA732X_EQ_BAND3_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC2 EQ Band 4 Volume", DA732X_REG_DAC2_EQ34, + DA732X_EQ_BAND4_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC2 EQ Band 5 Volume", DA732X_REG_DAC2_EQ5, + DA732X_EQ_BAND5_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + + SOC_SINGLE("DAC3 EQ Switch", DA732X_REG_DAC3_EQ5, + DA732X_EQ_EN_SHIFT, DA732X_EQ_EN_MAX, DA732X_NO_INVERT), + SOC_SINGLE_TLV("DAC3 EQ Band 1 Volume", DA732X_REG_DAC3_EQ12, + DA732X_EQ_BAND1_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC3 EQ Band 2 Volume", DA732X_REG_DAC3_EQ12, + DA732X_EQ_BAND2_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC3 EQ Band 3 Volume", DA732X_REG_DAC3_EQ34, + DA732X_EQ_BAND3_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC3 EQ Band 4 Volume", DA732X_REG_DAC3_EQ34, + DA732X_EQ_BAND4_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + SOC_SINGLE_TLV("DAC3 EQ Band 5 Volume", DA732X_REG_DAC3_EQ5, + DA732X_EQ_BAND5_SHIFT, DA732X_EQ_VOL_VAL_MAX, + DA732X_INVERT, eq_band_pga_tlv), + + /* Lineout 2 Reciever*/ + SOC_SINGLE("Lineout 2 Switch", DA732X_REG_LIN2, DA732X_LOUT_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("Lineout 2 Volume", DA732X_REG_LIN2, + DA732X_LOUT_VOL_SHIFT, DA732X_LOUT_VOL_VAL_MAX, + DA732X_NO_INVERT, lin2_pga_tlv), + + /* Lineout 3 SPEAKER*/ + SOC_SINGLE("Lineout 3 Switch", DA732X_REG_LIN3, DA732X_LOUT_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("Lineout 3 Volume", DA732X_REG_LIN3, + DA732X_LOUT_VOL_SHIFT, DA732X_LOUT_VOL_VAL_MAX, + DA732X_NO_INVERT, lin3_pga_tlv), + + /* Lineout 4 */ + SOC_SINGLE("Lineout 4 Switch", DA732X_REG_LIN4, DA732X_LOUT_MUTE_SHIFT, + DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_SINGLE_TLV("Lineout 4 Volume", DA732X_REG_LIN4, + DA732X_LOUT_VOL_SHIFT, DA732X_LOUT_VOL_VAL_MAX, + DA732X_NO_INVERT, lin4_pga_tlv), + + /* Headphones */ + SOC_DOUBLE_R("Headphone Switch", DA732X_REG_HPR, DA732X_REG_HPL, + DA732X_HP_MUTE_SHIFT, DA732X_SWITCH_MAX, DA732X_INVERT), + SOC_DOUBLE_R_TLV("Headphone Volume", DA732X_REG_HPL_VOL, + DA732X_REG_HPR_VOL, DA732X_HP_VOL_SHIFT, + DA732X_HP_VOL_VAL_MAX, DA732X_NO_INVERT, hp_pga_tlv), +}; + +static int da732x_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + switch (w->reg) { + case DA732X_REG_ADC1_PD: + snd_soc_component_update_bits(component, DA732X_REG_CLK_EN3, + DA732X_ADCA_BB_CLK_EN, + DA732X_ADCA_BB_CLK_EN); + break; + case DA732X_REG_ADC2_PD: + snd_soc_component_update_bits(component, DA732X_REG_CLK_EN3, + DA732X_ADCC_BB_CLK_EN, + DA732X_ADCC_BB_CLK_EN); + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, w->reg, DA732X_ADC_RST_MASK, + DA732X_ADC_SET_ACT); + snd_soc_component_update_bits(component, w->reg, DA732X_ADC_PD_MASK, + DA732X_ADC_ON); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, w->reg, DA732X_ADC_PD_MASK, + DA732X_ADC_OFF); + snd_soc_component_update_bits(component, w->reg, DA732X_ADC_RST_MASK, + DA732X_ADC_SET_RST); + + switch (w->reg) { + case DA732X_REG_ADC1_PD: + snd_soc_component_update_bits(component, DA732X_REG_CLK_EN3, + DA732X_ADCA_BB_CLK_EN, 0); + break; + case DA732X_REG_ADC2_PD: + snd_soc_component_update_bits(component, DA732X_REG_CLK_EN3, + DA732X_ADCC_BB_CLK_EN, 0); + break; + default: + return -EINVAL; + } + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int da732x_out_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, w->reg, + (1 << w->shift) | DA732X_OUT_HIZ_EN, + (1 << w->shift) | DA732X_OUT_HIZ_EN); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, w->reg, + (1 << w->shift) | DA732X_OUT_HIZ_EN, + (1 << w->shift) | DA732X_OUT_HIZ_DIS); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const char *adcl_text[] = { + "AUX1L", "MIC1" +}; + +static const char *adcr_text[] = { + "AUX1R", "MIC2", "MIC3" +}; + +static const char *enable_text[] = { + "Disabled", + "Enabled" +}; + +/* ADC1LMUX */ +static SOC_ENUM_SINGLE_DECL(adc1l_enum, + DA732X_REG_INP_MUX, DA732X_ADC1L_MUX_SEL_SHIFT, + adcl_text); +static const struct snd_kcontrol_new adc1l_mux = + SOC_DAPM_ENUM("ADC Route", adc1l_enum); + +/* ADC1RMUX */ +static SOC_ENUM_SINGLE_DECL(adc1r_enum, + DA732X_REG_INP_MUX, DA732X_ADC1R_MUX_SEL_SHIFT, + adcr_text); +static const struct snd_kcontrol_new adc1r_mux = + SOC_DAPM_ENUM("ADC Route", adc1r_enum); + +/* ADC2LMUX */ +static SOC_ENUM_SINGLE_DECL(adc2l_enum, + DA732X_REG_INP_MUX, DA732X_ADC2L_MUX_SEL_SHIFT, + adcl_text); +static const struct snd_kcontrol_new adc2l_mux = + SOC_DAPM_ENUM("ADC Route", adc2l_enum); + +/* ADC2RMUX */ +static SOC_ENUM_SINGLE_DECL(adc2r_enum, + DA732X_REG_INP_MUX, DA732X_ADC2R_MUX_SEL_SHIFT, + adcr_text); + +static const struct snd_kcontrol_new adc2r_mux = + SOC_DAPM_ENUM("ADC Route", adc2r_enum); + +static SOC_ENUM_SINGLE_DECL(da732x_hp_left_output, + DA732X_REG_HPL, DA732X_HP_OUT_DAC_EN_SHIFT, + enable_text); + +static const struct snd_kcontrol_new hpl_mux = + SOC_DAPM_ENUM("HPL Switch", da732x_hp_left_output); + +static SOC_ENUM_SINGLE_DECL(da732x_hp_right_output, + DA732X_REG_HPR, DA732X_HP_OUT_DAC_EN_SHIFT, + enable_text); + +static const struct snd_kcontrol_new hpr_mux = + SOC_DAPM_ENUM("HPR Switch", da732x_hp_right_output); + +static SOC_ENUM_SINGLE_DECL(da732x_speaker_output, + DA732X_REG_LIN3, DA732X_LOUT_DAC_EN_SHIFT, + enable_text); + +static const struct snd_kcontrol_new spk_mux = + SOC_DAPM_ENUM("SPK Switch", da732x_speaker_output); + +static SOC_ENUM_SINGLE_DECL(da732x_lout4_output, + DA732X_REG_LIN4, DA732X_LOUT_DAC_EN_SHIFT, + enable_text); + +static const struct snd_kcontrol_new lout4_mux = + SOC_DAPM_ENUM("LOUT4 Switch", da732x_lout4_output); + +static SOC_ENUM_SINGLE_DECL(da732x_lout2_output, + DA732X_REG_LIN2, DA732X_LOUT_DAC_EN_SHIFT, + enable_text); + +static const struct snd_kcontrol_new lout2_mux = + SOC_DAPM_ENUM("LOUT2 Switch", da732x_lout2_output); + +static const struct snd_soc_dapm_widget da732x_dapm_widgets[] = { + /* Supplies */ + SND_SOC_DAPM_SUPPLY("ADC1 Supply", DA732X_REG_ADC1_PD, 0, + DA732X_NO_INVERT, da732x_adc_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("ADC2 Supply", DA732X_REG_ADC2_PD, 0, + DA732X_NO_INVERT, da732x_adc_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("DAC1 CLK", DA732X_REG_CLK_EN4, + DA732X_DACA_BB_CLK_SHIFT, DA732X_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC2 CLK", DA732X_REG_CLK_EN4, + DA732X_DACC_BB_CLK_SHIFT, DA732X_NO_INVERT, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC3 CLK", DA732X_REG_CLK_EN5, + DA732X_DACE_BB_CLK_SHIFT, DA732X_NO_INVERT, + NULL, 0), + + /* Micbias */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", DA732X_REG_MICBIAS1, + DA732X_MICBIAS_EN_SHIFT, + DA732X_NO_INVERT, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", DA732X_REG_MICBIAS2, + DA732X_MICBIAS_EN_SHIFT, + DA732X_NO_INVERT, NULL, 0), + + /* Inputs */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("MIC3"), + SND_SOC_DAPM_INPUT("AUX1L"), + SND_SOC_DAPM_INPUT("AUX1R"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), + SND_SOC_DAPM_OUTPUT("ClassD"), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC1L", NULL, DA732X_REG_ADC1_SEL, + DA732X_ADCL_EN_SHIFT, DA732X_NO_INVERT), + SND_SOC_DAPM_ADC("ADC1R", NULL, DA732X_REG_ADC1_SEL, + DA732X_ADCR_EN_SHIFT, DA732X_NO_INVERT), + SND_SOC_DAPM_ADC("ADC2L", NULL, DA732X_REG_ADC2_SEL, + DA732X_ADCL_EN_SHIFT, DA732X_NO_INVERT), + SND_SOC_DAPM_ADC("ADC2R", NULL, DA732X_REG_ADC2_SEL, + DA732X_ADCR_EN_SHIFT, DA732X_NO_INVERT), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC1L", NULL, DA732X_REG_DAC1_SEL, + DA732X_DACL_EN_SHIFT, DA732X_NO_INVERT), + SND_SOC_DAPM_DAC("DAC1R", NULL, DA732X_REG_DAC1_SEL, + DA732X_DACR_EN_SHIFT, DA732X_NO_INVERT), + SND_SOC_DAPM_DAC("DAC2L", NULL, DA732X_REG_DAC2_SEL, + DA732X_DACL_EN_SHIFT, DA732X_NO_INVERT), + SND_SOC_DAPM_DAC("DAC2R", NULL, DA732X_REG_DAC2_SEL, + DA732X_DACR_EN_SHIFT, DA732X_NO_INVERT), + SND_SOC_DAPM_DAC("DAC3", NULL, DA732X_REG_DAC3_SEL, + DA732X_DACL_EN_SHIFT, DA732X_NO_INVERT), + + /* Input Pgas */ + SND_SOC_DAPM_PGA("MIC1 PGA", DA732X_REG_MIC1, DA732X_MIC_EN_SHIFT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("MIC2 PGA", DA732X_REG_MIC2, DA732X_MIC_EN_SHIFT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("MIC3 PGA", DA732X_REG_MIC3, DA732X_MIC_EN_SHIFT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX1L PGA", DA732X_REG_AUX1L, DA732X_AUX_EN_SHIFT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX1R PGA", DA732X_REG_AUX1R, DA732X_AUX_EN_SHIFT, + 0, NULL, 0), + + SND_SOC_DAPM_PGA_E("HP Left", DA732X_REG_HPL, DA732X_HP_OUT_EN_SHIFT, + 0, NULL, 0, da732x_out_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("HP Right", DA732X_REG_HPR, DA732X_HP_OUT_EN_SHIFT, + 0, NULL, 0, da732x_out_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LIN2", DA732X_REG_LIN2, DA732X_LIN_OUT_EN_SHIFT, + 0, NULL, 0, da732x_out_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LIN3", DA732X_REG_LIN3, DA732X_LIN_OUT_EN_SHIFT, + 0, NULL, 0, da732x_out_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("LIN4", DA732X_REG_LIN4, DA732X_LIN_OUT_EN_SHIFT, + 0, NULL, 0, da732x_out_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* MUXs */ + SND_SOC_DAPM_MUX("ADC1 Left MUX", SND_SOC_NOPM, 0, 0, &adc1l_mux), + SND_SOC_DAPM_MUX("ADC1 Right MUX", SND_SOC_NOPM, 0, 0, &adc1r_mux), + SND_SOC_DAPM_MUX("ADC2 Left MUX", SND_SOC_NOPM, 0, 0, &adc2l_mux), + SND_SOC_DAPM_MUX("ADC2 Right MUX", SND_SOC_NOPM, 0, 0, &adc2r_mux), + + SND_SOC_DAPM_MUX("HP Left MUX", SND_SOC_NOPM, 0, 0, &hpl_mux), + SND_SOC_DAPM_MUX("HP Right MUX", SND_SOC_NOPM, 0, 0, &hpr_mux), + SND_SOC_DAPM_MUX("Speaker MUX", SND_SOC_NOPM, 0, 0, &spk_mux), + SND_SOC_DAPM_MUX("LOUT2 MUX", SND_SOC_NOPM, 0, 0, &lout2_mux), + SND_SOC_DAPM_MUX("LOUT4 MUX", SND_SOC_NOPM, 0, 0, &lout4_mux), + + /* AIF interfaces */ + SND_SOC_DAPM_AIF_OUT("AIFA Output", "AIFA Capture", 0, DA732X_REG_AIFA3, + DA732X_AIF_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_IN("AIFA Input", "AIFA Playback", 0, DA732X_REG_AIFA3, + DA732X_AIF_EN_SHIFT, 0), + + SND_SOC_DAPM_AIF_OUT("AIFB Output", "AIFB Capture", 0, DA732X_REG_AIFB3, + DA732X_AIF_EN_SHIFT, 0), + SND_SOC_DAPM_AIF_IN("AIFB Input", "AIFB Playback", 0, DA732X_REG_AIFB3, + DA732X_AIF_EN_SHIFT, 0), +}; + +static const struct snd_soc_dapm_route da732x_dapm_routes[] = { + /* Inputs */ + {"AUX1L PGA", NULL, "AUX1L"}, + {"AUX1R PGA", NULL, "AUX1R"}, + {"MIC1 PGA", NULL, "MIC1"}, + {"MIC2 PGA", NULL, "MIC2"}, + {"MIC3 PGA", NULL, "MIC3"}, + + /* Capture Path */ + {"ADC1 Left MUX", "MIC1", "MIC1 PGA"}, + {"ADC1 Left MUX", "AUX1L", "AUX1L PGA"}, + + {"ADC1 Right MUX", "AUX1R", "AUX1R PGA"}, + {"ADC1 Right MUX", "MIC2", "MIC2 PGA"}, + {"ADC1 Right MUX", "MIC3", "MIC3 PGA"}, + + {"ADC2 Left MUX", "AUX1L", "AUX1L PGA"}, + {"ADC2 Left MUX", "MIC1", "MIC1 PGA"}, + + {"ADC2 Right MUX", "AUX1R", "AUX1R PGA"}, + {"ADC2 Right MUX", "MIC2", "MIC2 PGA"}, + {"ADC2 Right MUX", "MIC3", "MIC3 PGA"}, + + {"ADC1L", NULL, "ADC1 Supply"}, + {"ADC1R", NULL, "ADC1 Supply"}, + {"ADC2L", NULL, "ADC2 Supply"}, + {"ADC2R", NULL, "ADC2 Supply"}, + + {"ADC1L", NULL, "ADC1 Left MUX"}, + {"ADC1R", NULL, "ADC1 Right MUX"}, + {"ADC2L", NULL, "ADC2 Left MUX"}, + {"ADC2R", NULL, "ADC2 Right MUX"}, + + {"AIFA Output", NULL, "ADC1L"}, + {"AIFA Output", NULL, "ADC1R"}, + {"AIFB Output", NULL, "ADC2L"}, + {"AIFB Output", NULL, "ADC2R"}, + + {"HP Left MUX", "Enabled", "AIFA Input"}, + {"HP Right MUX", "Enabled", "AIFA Input"}, + {"Speaker MUX", "Enabled", "AIFB Input"}, + {"LOUT2 MUX", "Enabled", "AIFB Input"}, + {"LOUT4 MUX", "Enabled", "AIFB Input"}, + + {"DAC1L", NULL, "DAC1 CLK"}, + {"DAC1R", NULL, "DAC1 CLK"}, + {"DAC2L", NULL, "DAC2 CLK"}, + {"DAC2R", NULL, "DAC2 CLK"}, + {"DAC3", NULL, "DAC3 CLK"}, + + {"DAC1L", NULL, "HP Left MUX"}, + {"DAC1R", NULL, "HP Right MUX"}, + {"DAC2L", NULL, "Speaker MUX"}, + {"DAC2R", NULL, "LOUT4 MUX"}, + {"DAC3", NULL, "LOUT2 MUX"}, + + /* Output Pgas */ + {"HP Left", NULL, "DAC1L"}, + {"HP Right", NULL, "DAC1R"}, + {"LIN3", NULL, "DAC2L"}, + {"LIN4", NULL, "DAC2R"}, + {"LIN2", NULL, "DAC3"}, + + /* Outputs */ + {"ClassD", NULL, "LIN3"}, + {"LOUTL", NULL, "LIN2"}, + {"LOUTR", NULL, "LIN4"}, + {"HPL", NULL, "HP Left"}, + {"HPR", NULL, "HP Right"}, +}; + +static int da732x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u32 aif = 0; + u32 reg_aif; + u32 fs; + + reg_aif = dai->driver->base; + + switch (params_width(params)) { + case 16: + aif |= DA732X_AIF_WORD_16; + break; + case 20: + aif |= DA732X_AIF_WORD_20; + break; + case 24: + aif |= DA732X_AIF_WORD_24; + break; + case 32: + aif |= DA732X_AIF_WORD_32; + break; + default: + return -EINVAL; + } + + switch (params_rate(params)) { + case 8000: + fs = DA732X_SR_8KHZ; + break; + case 11025: + fs = DA732X_SR_11_025KHZ; + break; + case 12000: + fs = DA732X_SR_12KHZ; + break; + case 16000: + fs = DA732X_SR_16KHZ; + break; + case 22050: + fs = DA732X_SR_22_05KHZ; + break; + case 24000: + fs = DA732X_SR_24KHZ; + break; + case 32000: + fs = DA732X_SR_32KHZ; + break; + case 44100: + fs = DA732X_SR_44_1KHZ; + break; + case 48000: + fs = DA732X_SR_48KHZ; + break; + case 88100: + fs = DA732X_SR_88_1KHZ; + break; + case 96000: + fs = DA732X_SR_96KHZ; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, reg_aif, DA732X_AIF_WORD_MASK, aif); + snd_soc_component_update_bits(component, DA732X_REG_CLK_CTRL, DA732X_SR1_MASK, fs); + + return 0; +} + +static int da732x_set_dai_fmt(struct snd_soc_dai *dai, u32 fmt) +{ + struct snd_soc_component *component = dai->component; + u32 aif_mclk, pc_count; + u32 reg_aif1, aif1; + u32 reg_aif3, aif3; + + switch (dai->id) { + case DA732X_DAI_ID1: + reg_aif1 = DA732X_REG_AIFA1; + reg_aif3 = DA732X_REG_AIFA3; + pc_count = DA732X_PC_PULSE_AIFA | DA732X_PC_RESYNC_NOT_AUT | + DA732X_PC_SAME; + break; + case DA732X_DAI_ID2: + reg_aif1 = DA732X_REG_AIFB1; + reg_aif3 = DA732X_REG_AIFB3; + pc_count = DA732X_PC_PULSE_AIFB | DA732X_PC_RESYNC_NOT_AUT | + DA732X_PC_SAME; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aif1 = DA732X_AIF_SLAVE; + aif_mclk = DA732X_AIFM_FRAME_64 | DA732X_AIFM_SRC_SEL_AIFA; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 = DA732X_AIF_CLK_FROM_SRC; + aif_mclk = DA732X_CLK_GENERATION_AIF_A; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aif3 = DA732X_AIF_I2S_MODE; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif3 = DA732X_AIF_RIGHT_J_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif3 = DA732X_AIF_LEFT_J_MODE; + break; + case SND_SOC_DAIFMT_DSP_B: + aif3 = DA732X_AIF_DSP_MODE; + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif3 |= DA732X_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif3 |= DA732X_AIF_BCLK_INV | DA732X_AIF_WCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif3 |= DA732X_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif3 |= DA732X_AIF_WCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, DA732X_REG_AIF_MCLK, aif_mclk); + snd_soc_component_update_bits(component, reg_aif1, DA732X_AIF1_CLK_MASK, aif1); + snd_soc_component_update_bits(component, reg_aif3, DA732X_AIF_BCLK_INV | + DA732X_AIF_WCLK_INV | DA732X_AIF_MODE_MASK, aif3); + snd_soc_component_write(component, DA732X_REG_PC_CTRL, pc_count); + + return 0; +} + + + +static int da732x_set_dai_pll(struct snd_soc_component *component, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct da732x_priv *da732x = snd_soc_component_get_drvdata(component); + int fref, indiv; + u8 div_lo, div_mid, div_hi; + u64 frac_div; + + /* Disable PLL */ + if (freq_out == 0) { + snd_soc_component_update_bits(component, DA732X_REG_PLL_CTRL, + DA732X_PLL_EN, 0); + da732x->pll_en = false; + return 0; + } + + if (da732x->pll_en) + return -EBUSY; + + if (source == DA732X_SRCCLK_MCLK) { + /* Validate Sysclk rate */ + switch (da732x->sysclk) { + case 11290000: + case 12288000: + case 22580000: + case 24576000: + case 45160000: + case 49152000: + snd_soc_component_write(component, DA732X_REG_PLL_CTRL, + DA732X_PLL_BYPASS); + return 0; + default: + dev_err(component->dev, + "Cannot use PLL Bypass, invalid SYSCLK rate\n"); + return -EINVAL; + } + } + + indiv = da732x_get_input_div(component, da732x->sysclk); + if (indiv < 0) + return indiv; + + fref = (da732x->sysclk / indiv); + div_hi = freq_out / fref; + frac_div = (u64)(freq_out % fref) * 8192ULL; + do_div(frac_div, fref); + div_mid = (frac_div >> DA732X_1BYTE_SHIFT) & DA732X_U8_MASK; + div_lo = (frac_div) & DA732X_U8_MASK; + + snd_soc_component_write(component, DA732X_REG_PLL_DIV_LO, div_lo); + snd_soc_component_write(component, DA732X_REG_PLL_DIV_MID, div_mid); + snd_soc_component_write(component, DA732X_REG_PLL_DIV_HI, div_hi); + + snd_soc_component_update_bits(component, DA732X_REG_PLL_CTRL, DA732X_PLL_EN, + DA732X_PLL_EN); + + da732x->pll_en = true; + + return 0; +} + +static int da732x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct da732x_priv *da732x = snd_soc_component_get_drvdata(component); + + da732x->sysclk = freq; + + return 0; +} + +#define DA732X_RATES SNDRV_PCM_RATE_8000_96000 + +#define DA732X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops da732x_dai_ops = { + .hw_params = da732x_hw_params, + .set_fmt = da732x_set_dai_fmt, + .set_sysclk = da732x_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver da732x_dai[] = { + { + .name = "DA732X_AIFA", + .id = DA732X_DAI_ID1, + .base = DA732X_REG_AIFA1, + .playback = { + .stream_name = "AIFA Playback", + .channels_min = 1, + .channels_max = 2, + .rates = DA732X_RATES, + .formats = DA732X_FORMATS, + }, + .capture = { + .stream_name = "AIFA Capture", + .channels_min = 1, + .channels_max = 2, + .rates = DA732X_RATES, + .formats = DA732X_FORMATS, + }, + .ops = &da732x_dai_ops, + }, + { + .name = "DA732X_AIFB", + .id = DA732X_DAI_ID2, + .base = DA732X_REG_AIFB1, + .playback = { + .stream_name = "AIFB Playback", + .channels_min = 1, + .channels_max = 2, + .rates = DA732X_RATES, + .formats = DA732X_FORMATS, + }, + .capture = { + .stream_name = "AIFB Capture", + .channels_min = 1, + .channels_max = 2, + .rates = DA732X_RATES, + .formats = DA732X_FORMATS, + }, + .ops = &da732x_dai_ops, + }, +}; + +static bool da732x_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DA732X_REG_HPL_DAC_OFF_CNTL: + case DA732X_REG_HPR_DAC_OFF_CNTL: + return true; + default: + return false; + } +} + +static const struct regmap_config da732x_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = DA732X_MAX_REG, + .volatile_reg = da732x_volatile, + .reg_defaults = da732x_reg_cache, + .num_reg_defaults = ARRAY_SIZE(da732x_reg_cache), + .cache_type = REGCACHE_RBTREE, +}; + + +static void da732x_dac_offset_adjust(struct snd_soc_component *component) +{ + u8 offset[DA732X_HP_DACS]; + u8 sign[DA732X_HP_DACS]; + u8 step = DA732X_DAC_OFFSET_STEP; + + /* Initialize DAC offset calibration circuits and registers */ + snd_soc_component_write(component, DA732X_REG_HPL_DAC_OFFSET, + DA732X_HP_DAC_OFFSET_TRIM_VAL); + snd_soc_component_write(component, DA732X_REG_HPR_DAC_OFFSET, + DA732X_HP_DAC_OFFSET_TRIM_VAL); + snd_soc_component_write(component, DA732X_REG_HPL_DAC_OFF_CNTL, + DA732X_HP_DAC_OFF_CALIBRATION | + DA732X_HP_DAC_OFF_SCALE_STEPS); + snd_soc_component_write(component, DA732X_REG_HPR_DAC_OFF_CNTL, + DA732X_HP_DAC_OFF_CALIBRATION | + DA732X_HP_DAC_OFF_SCALE_STEPS); + + /* Wait for voltage stabilization */ + msleep(DA732X_WAIT_FOR_STABILIZATION); + + /* Check DAC offset sign */ + sign[DA732X_HPL_DAC] = (snd_soc_component_read32(component, DA732X_REG_HPL_DAC_OFF_CNTL) & + DA732X_HP_DAC_OFF_CNTL_COMPO); + sign[DA732X_HPR_DAC] = (snd_soc_component_read32(component, DA732X_REG_HPR_DAC_OFF_CNTL) & + DA732X_HP_DAC_OFF_CNTL_COMPO); + + /* Binary search DAC offset values (both channels at once) */ + offset[DA732X_HPL_DAC] = sign[DA732X_HPL_DAC] << DA732X_HP_DAC_COMPO_SHIFT; + offset[DA732X_HPR_DAC] = sign[DA732X_HPR_DAC] << DA732X_HP_DAC_COMPO_SHIFT; + + do { + offset[DA732X_HPL_DAC] |= step; + offset[DA732X_HPR_DAC] |= step; + snd_soc_component_write(component, DA732X_REG_HPL_DAC_OFFSET, + ~offset[DA732X_HPL_DAC] & DA732X_HP_DAC_OFF_MASK); + snd_soc_component_write(component, DA732X_REG_HPR_DAC_OFFSET, + ~offset[DA732X_HPR_DAC] & DA732X_HP_DAC_OFF_MASK); + + msleep(DA732X_WAIT_FOR_STABILIZATION); + + if ((snd_soc_component_read32(component, DA732X_REG_HPL_DAC_OFF_CNTL) & + DA732X_HP_DAC_OFF_CNTL_COMPO) ^ sign[DA732X_HPL_DAC]) + offset[DA732X_HPL_DAC] &= ~step; + if ((snd_soc_component_read32(component, DA732X_REG_HPR_DAC_OFF_CNTL) & + DA732X_HP_DAC_OFF_CNTL_COMPO) ^ sign[DA732X_HPR_DAC]) + offset[DA732X_HPR_DAC] &= ~step; + + step >>= 1; + } while (step); + + /* Write final DAC offsets to registers */ + snd_soc_component_write(component, DA732X_REG_HPL_DAC_OFFSET, + ~offset[DA732X_HPL_DAC] & DA732X_HP_DAC_OFF_MASK); + snd_soc_component_write(component, DA732X_REG_HPR_DAC_OFFSET, + ~offset[DA732X_HPR_DAC] & DA732X_HP_DAC_OFF_MASK); + + /* End DAC calibration mode */ + snd_soc_component_write(component, DA732X_REG_HPL_DAC_OFF_CNTL, + DA732X_HP_DAC_OFF_SCALE_STEPS); + snd_soc_component_write(component, DA732X_REG_HPR_DAC_OFF_CNTL, + DA732X_HP_DAC_OFF_SCALE_STEPS); +} + +static void da732x_output_offset_adjust(struct snd_soc_component *component) +{ + u8 offset[DA732X_HP_AMPS]; + u8 sign[DA732X_HP_AMPS]; + u8 step = DA732X_OUTPUT_OFFSET_STEP; + + offset[DA732X_HPL_AMP] = DA732X_HP_OUT_TRIM_VAL; + offset[DA732X_HPR_AMP] = DA732X_HP_OUT_TRIM_VAL; + + /* Initialize output offset calibration circuits and registers */ + snd_soc_component_write(component, DA732X_REG_HPL_OUT_OFFSET, DA732X_HP_OUT_TRIM_VAL); + snd_soc_component_write(component, DA732X_REG_HPR_OUT_OFFSET, DA732X_HP_OUT_TRIM_VAL); + snd_soc_component_write(component, DA732X_REG_HPL, + DA732X_HP_OUT_COMP | DA732X_HP_OUT_EN); + snd_soc_component_write(component, DA732X_REG_HPR, + DA732X_HP_OUT_COMP | DA732X_HP_OUT_EN); + + /* Wait for voltage stabilization */ + msleep(DA732X_WAIT_FOR_STABILIZATION); + + /* Check output offset sign */ + sign[DA732X_HPL_AMP] = snd_soc_component_read32(component, DA732X_REG_HPL) & + DA732X_HP_OUT_COMPO; + sign[DA732X_HPR_AMP] = snd_soc_component_read32(component, DA732X_REG_HPR) & + DA732X_HP_OUT_COMPO; + + snd_soc_component_write(component, DA732X_REG_HPL, DA732X_HP_OUT_COMP | + (sign[DA732X_HPL_AMP] >> DA732X_HP_OUT_COMPO_SHIFT) | + DA732X_HP_OUT_EN); + snd_soc_component_write(component, DA732X_REG_HPR, DA732X_HP_OUT_COMP | + (sign[DA732X_HPR_AMP] >> DA732X_HP_OUT_COMPO_SHIFT) | + DA732X_HP_OUT_EN); + + /* Binary search output offset values (both channels at once) */ + do { + offset[DA732X_HPL_AMP] |= step; + offset[DA732X_HPR_AMP] |= step; + snd_soc_component_write(component, DA732X_REG_HPL_OUT_OFFSET, + offset[DA732X_HPL_AMP]); + snd_soc_component_write(component, DA732X_REG_HPR_OUT_OFFSET, + offset[DA732X_HPR_AMP]); + + msleep(DA732X_WAIT_FOR_STABILIZATION); + + if ((snd_soc_component_read32(component, DA732X_REG_HPL) & + DA732X_HP_OUT_COMPO) ^ sign[DA732X_HPL_AMP]) + offset[DA732X_HPL_AMP] &= ~step; + if ((snd_soc_component_read32(component, DA732X_REG_HPR) & + DA732X_HP_OUT_COMPO) ^ sign[DA732X_HPR_AMP]) + offset[DA732X_HPR_AMP] &= ~step; + + step >>= 1; + } while (step); + + /* Write final DAC offsets to registers */ + snd_soc_component_write(component, DA732X_REG_HPL_OUT_OFFSET, offset[DA732X_HPL_AMP]); + snd_soc_component_write(component, DA732X_REG_HPR_OUT_OFFSET, offset[DA732X_HPR_AMP]); +} + +static void da732x_hp_dc_offset_cancellation(struct snd_soc_component *component) +{ + /* Make sure that we have Soft Mute enabled */ + snd_soc_component_write(component, DA732X_REG_DAC1_SOFTMUTE, DA732X_SOFTMUTE_EN | + DA732X_GAIN_RAMPED | DA732X_16_SAMPLES); + snd_soc_component_write(component, DA732X_REG_DAC1_SEL, DA732X_DACL_EN | + DA732X_DACR_EN | DA732X_DACL_SDM | DA732X_DACR_SDM | + DA732X_DACL_MUTE | DA732X_DACR_MUTE); + snd_soc_component_write(component, DA732X_REG_HPL, DA732X_HP_OUT_DAC_EN | + DA732X_HP_OUT_MUTE | DA732X_HP_OUT_EN); + snd_soc_component_write(component, DA732X_REG_HPR, DA732X_HP_OUT_EN | + DA732X_HP_OUT_MUTE | DA732X_HP_OUT_DAC_EN); + + da732x_dac_offset_adjust(component); + da732x_output_offset_adjust(component); + + snd_soc_component_write(component, DA732X_REG_DAC1_SEL, DA732X_DACS_DIS); + snd_soc_component_write(component, DA732X_REG_HPL, DA732X_HP_DIS); + snd_soc_component_write(component, DA732X_REG_HPR, DA732X_HP_DIS); +} + +static int da732x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct da732x_priv *da732x = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_component_update_bits(component, DA732X_REG_BIAS_EN, + DA732X_BIAS_BOOST_MASK, + DA732X_BIAS_BOOST_100PC); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Init Codec */ + snd_soc_component_write(component, DA732X_REG_REF1, + DA732X_VMID_FASTCHG); + snd_soc_component_write(component, DA732X_REG_BIAS_EN, + DA732X_BIAS_EN); + + mdelay(DA732X_STARTUP_DELAY); + + /* Disable Fast Charge and enable DAC ref voltage */ + snd_soc_component_write(component, DA732X_REG_REF1, + DA732X_REFBUFX2_EN); + + /* Enable bypass DSP routing */ + snd_soc_component_write(component, DA732X_REG_DATA_ROUTE, + DA732X_BYPASS_DSP); + + /* Enable Digital subsystem */ + snd_soc_component_write(component, DA732X_REG_DSP_CTRL, + DA732X_DIGITAL_EN); + + snd_soc_component_write(component, DA732X_REG_SPARE1_OUT, + DA732X_HP_DRIVER_EN | + DA732X_HP_GATE_LOW | + DA732X_HP_LOOP_GAIN_CTRL); + snd_soc_component_write(component, DA732X_REG_HP_LIN1_GNDSEL, + DA732X_HP_OUT_GNDSEL); + + da732x_set_charge_pump(component, DA732X_ENABLE_CP); + + snd_soc_component_write(component, DA732X_REG_CLK_EN1, + DA732X_SYS3_CLK_EN | DA732X_PC_CLK_EN); + + /* Enable Zero Crossing */ + snd_soc_component_write(component, DA732X_REG_INP_ZC_EN, + DA732X_MIC1_PRE_ZC_EN | + DA732X_MIC1_ZC_EN | + DA732X_MIC2_PRE_ZC_EN | + DA732X_MIC2_ZC_EN | + DA732X_AUXL_ZC_EN | + DA732X_AUXR_ZC_EN | + DA732X_MIC3_PRE_ZC_EN | + DA732X_MIC3_ZC_EN); + snd_soc_component_write(component, DA732X_REG_OUT_ZC_EN, + DA732X_HPL_ZC_EN | DA732X_HPR_ZC_EN | + DA732X_LIN2_ZC_EN | DA732X_LIN3_ZC_EN | + DA732X_LIN4_ZC_EN); + + da732x_hp_dc_offset_cancellation(component); + + regcache_cache_only(da732x->regmap, false); + regcache_sync(da732x->regmap); + } else { + snd_soc_component_update_bits(component, DA732X_REG_BIAS_EN, + DA732X_BIAS_BOOST_MASK, + DA732X_BIAS_BOOST_50PC); + snd_soc_component_update_bits(component, DA732X_REG_PLL_CTRL, + DA732X_PLL_EN, 0); + da732x->pll_en = false; + } + break; + case SND_SOC_BIAS_OFF: + regcache_cache_only(da732x->regmap, true); + da732x_set_charge_pump(component, DA732X_DISABLE_CP); + snd_soc_component_update_bits(component, DA732X_REG_BIAS_EN, DA732X_BIAS_EN, + DA732X_BIAS_DIS); + da732x->pll_en = false; + break; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_da732x = { + .set_bias_level = da732x_set_bias_level, + .controls = da732x_snd_controls, + .num_controls = ARRAY_SIZE(da732x_snd_controls), + .dapm_widgets = da732x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(da732x_dapm_widgets), + .dapm_routes = da732x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(da732x_dapm_routes), + .set_pll = da732x_set_dai_pll, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int da732x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da732x_priv *da732x; + unsigned int reg; + int ret; + + da732x = devm_kzalloc(&i2c->dev, sizeof(struct da732x_priv), + GFP_KERNEL); + if (!da732x) + return -ENOMEM; + + i2c_set_clientdata(i2c, da732x); + + da732x->regmap = devm_regmap_init_i2c(i2c, &da732x_regmap); + if (IS_ERR(da732x->regmap)) { + ret = PTR_ERR(da732x->regmap); + dev_err(&i2c->dev, "Failed to initialize regmap\n"); + goto err; + } + + ret = regmap_read(da732x->regmap, DA732X_REG_ID, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read ID register: %d\n", ret); + goto err; + } + + dev_info(&i2c->dev, "Revision: %d.%d\n", + (reg & DA732X_ID_MAJOR_MASK) >> 4, + (reg & DA732X_ID_MINOR_MASK)); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_da732x, + da732x_dai, ARRAY_SIZE(da732x_dai)); + if (ret != 0) + dev_err(&i2c->dev, "Failed to register component.\n"); + +err: + return ret; +} + +static int da732x_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id da732x_i2c_id[] = { + { "da7320", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, da732x_i2c_id); + +static struct i2c_driver da732x_i2c_driver = { + .driver = { + .name = "da7320", + }, + .probe = da732x_i2c_probe, + .remove = da732x_i2c_remove, + .id_table = da732x_i2c_id, +}; + +module_i2c_driver(da732x_i2c_driver); + + +MODULE_DESCRIPTION("ASoC DA732X driver"); +MODULE_AUTHOR("Michal Hajduk "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da732x.h b/sound/soc/codecs/da732x.h new file mode 100644 index 000000000..f586cbd30 --- /dev/null +++ b/sound/soc/codecs/da732x.h @@ -0,0 +1,130 @@ +/* + * da732x.h -- Dialog DA732X ALSA SoC Audio Driver Header File + * + * Copyright (C) 2012 Dialog Semiconductor GmbH + * + * Author: Michal Hajduk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DA732X_H_ +#define __DA732X_H_ + +#include + +/* General */ +#define DA732X_U8_MASK 0xFF +#define DA732X_4BYTES 4 +#define DA732X_3BYTES 3 +#define DA732X_2BYTES 2 +#define DA732X_1BYTE 1 +#define DA732X_1BYTE_SHIFT 8 +#define DA732X_2BYTES_SHIFT 16 +#define DA732X_3BYTES_SHIFT 24 +#define DA732X_4BYTES_SHIFT 32 + +#define DA732X_DACS_DIS 0x0 +#define DA732X_HP_DIS 0x0 +#define DA732X_CLEAR_REG 0x0 + +/* Calibration */ +#define DA732X_DAC_OFFSET_STEP 0x20 +#define DA732X_OUTPUT_OFFSET_STEP 0x80 +#define DA732X_HP_OUT_TRIM_VAL 0x0 +#define DA732X_WAIT_FOR_STABILIZATION 1 +#define DA732X_HPL_DAC 0 +#define DA732X_HPR_DAC 1 +#define DA732X_HP_DACS 2 +#define DA732X_HPL_AMP 0 +#define DA732X_HPR_AMP 1 +#define DA732X_HP_AMPS 2 + +/* Clock settings */ +#define DA732X_STARTUP_DELAY 100 +#define DA732X_PLL_OUT_196608 196608000 +#define DA732X_PLL_OUT_180634 180633600 +#define DA732X_PLL_OUT_SRM 188620800 +#define DA732X_MCLK_10MHZ 10000000 +#define DA732X_MCLK_20MHZ 20000000 +#define DA732X_MCLK_40MHZ 40000000 +#define DA732X_MCLK_54MHZ 54000000 +#define DA732X_MCLK_RET_0_10MHZ 0 +#define DA732X_MCLK_VAL_0_10MHZ 1 +#define DA732X_MCLK_RET_10_20MHZ 1 +#define DA732X_MCLK_VAL_10_20MHZ 2 +#define DA732X_MCLK_RET_20_40MHZ 2 +#define DA732X_MCLK_VAL_20_40MHZ 4 +#define DA732X_MCLK_RET_40_54MHZ 3 +#define DA732X_MCLK_VAL_40_54MHZ 8 +#define DA732X_DAI_ID1 0 +#define DA732X_DAI_ID2 1 +#define DA732X_SRCCLK_PLL 0 +#define DA732X_SRCCLK_MCLK 1 + +#define DA732X_LIN_LP_VOL 0x4F +#define DA732X_LP_VOL 0x40 + +/* Kcontrols */ +#define DA732X_DAC_EN_MAX 2 +#define DA732X_ADCL_MUX_MAX 2 +#define DA732X_ADCR_MUX_MAX 3 +#define DA732X_HPF_MODE_MAX 3 +#define DA732X_HPF_MODE_SHIFT 4 +#define DA732X_HPF_MUSIC_SHIFT 0 +#define DA732X_HPF_MUSIC_MAX 4 +#define DA732X_HPF_VOICE_SHIFT 4 +#define DA732X_HPF_VOICE_MAX 8 +#define DA732X_EQ_EN_MAX 1 +#define DA732X_HPF_VOICE 1 +#define DA732X_HPF_MUSIC 2 +#define DA732X_HPF_DISABLED 0 +#define DA732X_NO_INVERT 0 +#define DA732X_INVERT 1 +#define DA732X_SWITCH_MAX 1 +#define DA732X_ENABLE_CP 1 +#define DA732X_DISABLE_CP 0 +#define DA732X_DISABLE_ALL_CLKS 0 +#define DA732X_RESET_ADCS 0 + +/* dB values */ +#define DA732X_MIC_VOL_DB_MIN 0 +#define DA732X_MIC_VOL_DB_INC 50 +#define DA732X_MIC_PRE_VOL_DB_MIN 0 +#define DA732X_MIC_PRE_VOL_DB_INC 600 +#define DA732X_AUX_VOL_DB_MIN -6000 +#define DA732X_AUX_VOL_DB_INC 150 +#define DA732X_HP_VOL_DB_MIN -2250 +#define DA732X_HP_VOL_DB_INC 150 +#define DA732X_LIN2_VOL_DB_MIN -1650 +#define DA732X_LIN2_VOL_DB_INC 150 +#define DA732X_LIN3_VOL_DB_MIN -1650 +#define DA732X_LIN3_VOL_DB_INC 150 +#define DA732X_LIN4_VOL_DB_MIN -2250 +#define DA732X_LIN4_VOL_DB_INC 150 +#define DA732X_EQ_BAND_VOL_DB_MIN -1050 +#define DA732X_EQ_BAND_VOL_DB_INC 150 +#define DA732X_DAC_VOL_DB_MIN -7725 +#define DA732X_DAC_VOL_DB_INC 75 +#define DA732X_ADC_VOL_DB_MIN 0 +#define DA732X_ADC_VOL_DB_INC -1 +#define DA732X_EQ_OVERALL_VOL_DB_MIN -1800 +#define DA732X_EQ_OVERALL_VOL_DB_INC 600 + +enum da732x_sysctl { + DA732X_SR_8KHZ = 0x1, + DA732X_SR_11_025KHZ = 0x2, + DA732X_SR_12KHZ = 0x3, + DA732X_SR_16KHZ = 0x5, + DA732X_SR_22_05KHZ = 0x6, + DA732X_SR_24KHZ = 0x7, + DA732X_SR_32KHZ = 0x9, + DA732X_SR_44_1KHZ = 0xA, + DA732X_SR_48KHZ = 0xB, + DA732X_SR_88_1KHZ = 0xE, + DA732X_SR_96KHZ = 0xF, +}; + +#endif /* __DA732X_H_ */ diff --git a/sound/soc/codecs/da732x_reg.h b/sound/soc/codecs/da732x_reg.h new file mode 100644 index 000000000..bdd03ca4b --- /dev/null +++ b/sound/soc/codecs/da732x_reg.h @@ -0,0 +1,654 @@ +/* + * da732x_reg.h --- Dialog DA732X ALSA SoC Audio Registers Header File + * + * Copyright (C) 2012 Dialog Semiconductor GmbH + * + * Author: Michal Hajduk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DA732X_REG_H_ +#define __DA732X_REG_H_ + +/* DA732X registers */ +#define DA732X_REG_STATUS_EXT 0x00 +#define DA732X_REG_STATUS 0x01 +#define DA732X_REG_REF1 0x02 +#define DA732X_REG_BIAS_EN 0x03 +#define DA732X_REG_BIAS1 0x04 +#define DA732X_REG_BIAS2 0x05 +#define DA732X_REG_BIAS3 0x06 +#define DA732X_REG_BIAS4 0x07 +#define DA732X_REG_MICBIAS2 0x0F +#define DA732X_REG_MICBIAS1 0x10 +#define DA732X_REG_MICDET 0x11 +#define DA732X_REG_MIC1_PRE 0x12 +#define DA732X_REG_MIC1 0x13 +#define DA732X_REG_MIC2_PRE 0x14 +#define DA732X_REG_MIC2 0x15 +#define DA732X_REG_AUX1L 0x16 +#define DA732X_REG_AUX1R 0x17 +#define DA732X_REG_MIC3_PRE 0x18 +#define DA732X_REG_MIC3 0x19 +#define DA732X_REG_INP_PINBIAS 0x1A +#define DA732X_REG_INP_ZC_EN 0x1B +#define DA732X_REG_INP_MUX 0x1D +#define DA732X_REG_HP_DET 0x20 +#define DA732X_REG_HPL_DAC_OFFSET 0x21 +#define DA732X_REG_HPL_DAC_OFF_CNTL 0x22 +#define DA732X_REG_HPL_OUT_OFFSET 0x23 +#define DA732X_REG_HPL 0x24 +#define DA732X_REG_HPL_VOL 0x25 +#define DA732X_REG_HPR_DAC_OFFSET 0x26 +#define DA732X_REG_HPR_DAC_OFF_CNTL 0x27 +#define DA732X_REG_HPR_OUT_OFFSET 0x28 +#define DA732X_REG_HPR 0x29 +#define DA732X_REG_HPR_VOL 0x2A +#define DA732X_REG_LIN2 0x2B +#define DA732X_REG_LIN3 0x2C +#define DA732X_REG_LIN4 0x2D +#define DA732X_REG_OUT_ZC_EN 0x2E +#define DA732X_REG_HP_LIN1_GNDSEL 0x37 +#define DA732X_REG_CP_HP1 0x3A +#define DA732X_REG_CP_HP2 0x3B +#define DA732X_REG_CP_CTRL1 0x40 +#define DA732X_REG_CP_CTRL2 0x41 +#define DA732X_REG_CP_CTRL3 0x42 +#define DA732X_REG_CP_LEVEL_MASK 0x43 +#define DA732X_REG_CP_DET 0x44 +#define DA732X_REG_CP_STATUS 0x45 +#define DA732X_REG_CP_THRESH1 0x46 +#define DA732X_REG_CP_THRESH2 0x47 +#define DA732X_REG_CP_THRESH3 0x48 +#define DA732X_REG_CP_THRESH4 0x49 +#define DA732X_REG_CP_THRESH5 0x4A +#define DA732X_REG_CP_THRESH6 0x4B +#define DA732X_REG_CP_THRESH7 0x4C +#define DA732X_REG_CP_THRESH8 0x4D +#define DA732X_REG_PLL_DIV_LO 0x50 +#define DA732X_REG_PLL_DIV_MID 0x51 +#define DA732X_REG_PLL_DIV_HI 0x52 +#define DA732X_REG_PLL_CTRL 0x53 +#define DA732X_REG_CLK_CTRL 0x54 +#define DA732X_REG_CLK_DSP 0x5A +#define DA732X_REG_CLK_EN1 0x5B +#define DA732X_REG_CLK_EN2 0x5C +#define DA732X_REG_CLK_EN3 0x5D +#define DA732X_REG_CLK_EN4 0x5E +#define DA732X_REG_CLK_EN5 0x5F +#define DA732X_REG_AIF_MCLK 0x60 +#define DA732X_REG_AIFA1 0x61 +#define DA732X_REG_AIFA2 0x62 +#define DA732X_REG_AIFA3 0x63 +#define DA732X_REG_AIFB1 0x64 +#define DA732X_REG_AIFB2 0x65 +#define DA732X_REG_AIFB3 0x66 +#define DA732X_REG_PC_CTRL 0x6A +#define DA732X_REG_DATA_ROUTE 0x70 +#define DA732X_REG_DSP_CTRL 0x71 +#define DA732X_REG_CIF_CTRL2 0x74 +#define DA732X_REG_HANDSHAKE 0x75 +#define DA732X_REG_MBOX0 0x76 +#define DA732X_REG_MBOX1 0x77 +#define DA732X_REG_MBOX2 0x78 +#define DA732X_REG_MBOX_STATUS 0x79 +#define DA732X_REG_SPARE1_OUT 0x7D +#define DA732X_REG_SPARE2_OUT 0x7E +#define DA732X_REG_SPARE1_IN 0x7F +#define DA732X_REG_ID 0x81 +#define DA732X_REG_ADC1_PD 0x90 +#define DA732X_REG_ADC1_HPF 0x93 +#define DA732X_REG_ADC1_SEL 0x94 +#define DA732X_REG_ADC1_EQ12 0x95 +#define DA732X_REG_ADC1_EQ34 0x96 +#define DA732X_REG_ADC1_EQ5 0x97 +#define DA732X_REG_ADC2_PD 0x98 +#define DA732X_REG_ADC2_HPF 0x9B +#define DA732X_REG_ADC2_SEL 0x9C +#define DA732X_REG_ADC2_EQ12 0x9D +#define DA732X_REG_ADC2_EQ34 0x9E +#define DA732X_REG_ADC2_EQ5 0x9F +#define DA732X_REG_DAC1_HPF 0xA0 +#define DA732X_REG_DAC1_L_VOL 0xA1 +#define DA732X_REG_DAC1_R_VOL 0xA2 +#define DA732X_REG_DAC1_SEL 0xA3 +#define DA732X_REG_DAC1_SOFTMUTE 0xA4 +#define DA732X_REG_DAC1_EQ12 0xA5 +#define DA732X_REG_DAC1_EQ34 0xA6 +#define DA732X_REG_DAC1_EQ5 0xA7 +#define DA732X_REG_DAC2_HPF 0xB0 +#define DA732X_REG_DAC2_L_VOL 0xB1 +#define DA732X_REG_DAC2_R_VOL 0xB2 +#define DA732X_REG_DAC2_SEL 0xB3 +#define DA732X_REG_DAC2_SOFTMUTE 0xB4 +#define DA732X_REG_DAC2_EQ12 0xB5 +#define DA732X_REG_DAC2_EQ34 0xB6 +#define DA732X_REG_DAC2_EQ5 0xB7 +#define DA732X_REG_DAC3_HPF 0xC0 +#define DA732X_REG_DAC3_VOL 0xC1 +#define DA732X_REG_DAC3_SEL 0xC3 +#define DA732X_REG_DAC3_SOFTMUTE 0xC4 +#define DA732X_REG_DAC3_EQ12 0xC5 +#define DA732X_REG_DAC3_EQ34 0xC6 +#define DA732X_REG_DAC3_EQ5 0xC7 +#define DA732X_REG_BIQ_BYP 0xD2 +#define DA732X_REG_DMA_CMD 0xD3 +#define DA732X_REG_DMA_ADDR0 0xD4 +#define DA732X_REG_DMA_ADDR1 0xD5 +#define DA732X_REG_DMA_DATA0 0xD6 +#define DA732X_REG_DMA_DATA1 0xD7 +#define DA732X_REG_DMA_DATA2 0xD8 +#define DA732X_REG_DMA_DATA3 0xD9 +#define DA732X_REG_DMA_STATUS 0xDA +#define DA732X_REG_BROWNOUT 0xDF +#define DA732X_REG_UNLOCK 0xE0 + +#define DA732X_MAX_REG DA732X_REG_UNLOCK +/* + * Bits + */ + +/* DA732X_REG_STATUS_EXT (addr=0x00) */ +#define DA732X_STATUS_EXT_DSP (1 << 4) +#define DA732X_STATUS_EXT_CLEAR (0 << 0) + +/* DA732X_REG_STATUS (addr=0x01) */ +#define DA732X_STATUS_PLL_LOCK (1 << 0) +#define DA732X_STATUS_PLL_MCLK_DET (1 << 1) +#define DA732X_STATUS_HPDET_OUT (1 << 2) +#define DA732X_STATUS_INP_MIXDET_1 (1 << 3) +#define DA732X_STATUS_INP_MIXDET_2 (1 << 4) +#define DA732X_STATUS_BO_STATUS (1 << 5) + +/* DA732X_REG_REF1 (addr=0x02) */ +#define DA732X_VMID_FASTCHG (1 << 1) +#define DA732X_VMID_FASTDISCHG (1 << 2) +#define DA732X_REFBUFX2_EN (1 << 6) +#define DA732X_REFBUFX2_DIS (0 << 6) + +/* DA732X_REG_BIAS_EN (addr=0x03) */ +#define DA732X_BIAS_BOOST_MASK (3 << 0) +#define DA732X_BIAS_BOOST_100PC (0 << 0) +#define DA732X_BIAS_BOOST_133PC (1 << 0) +#define DA732X_BIAS_BOOST_88PC (2 << 0) +#define DA732X_BIAS_BOOST_50PC (3 << 0) +#define DA732X_BIAS_EN (1 << 7) +#define DA732X_BIAS_DIS (0 << 7) + +/* DA732X_REG_BIAS1 (addr=0x04) */ +#define DA732X_BIAS1_HP_DAC_BIAS_MASK (3 << 0) +#define DA732X_BIAS1_HP_DAC_BIAS_100PC (0 << 0) +#define DA732X_BIAS1_HP_DAC_BIAS_150PC (1 << 0) +#define DA732X_BIAS1_HP_DAC_BIAS_50PC (2 << 0) +#define DA732X_BIAS1_HP_DAC_BIAS_75PC (3 << 0) +#define DA732X_BIAS1_HP_OUT_BIAS_MASK (7 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_100PC (0 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_125PC (1 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_150PC (2 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_175PC (3 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_200PC (4 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_250PC (5 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_300PC (6 << 4) +#define DA732X_BIAS1_HP_OUT_BIAS_350PC (7 << 4) + +/* DA732X_REG_BIAS2 (addr=0x05) */ +#define DA732X_BIAS2_LINE2_DAC_BIAS_MASK (3 << 0) +#define DA732X_BIAS2_LINE2_DAC_BIAS_100PC (0 << 0) +#define DA732X_BIAS2_LINE2_DAC_BIAS_150PC (1 << 0) +#define DA732X_BIAS2_LINE2_DAC_BIAS_50PC (2 << 0) +#define DA732X_BIAS2_LINE2_DAC_BIAS_75PC (3 << 0) +#define DA732X_BIAS2_LINE2_OUT_BIAS_MASK (7 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_100PC (0 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_125PC (1 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_150PC (2 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_175PC (3 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_200PC (4 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_250PC (5 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_300PC (6 << 4) +#define DA732X_BIAS2_LINE2_OUT_BIAS_350PC (7 << 4) + +/* DA732X_REG_BIAS3 (addr=0x06) */ +#define DA732X_BIAS3_LINE3_DAC_BIAS_MASK (3 << 0) +#define DA732X_BIAS3_LINE3_DAC_BIAS_100PC (0 << 0) +#define DA732X_BIAS3_LINE3_DAC_BIAS_150PC (1 << 0) +#define DA732X_BIAS3_LINE3_DAC_BIAS_50PC (2 << 0) +#define DA732X_BIAS3_LINE3_DAC_BIAS_75PC (3 << 0) +#define DA732X_BIAS3_LINE3_OUT_BIAS_MASK (7 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_100PC (0 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_125PC (1 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_150PC (2 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_175PC (3 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_200PC (4 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_250PC (5 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_300PC (6 << 4) +#define DA732X_BIAS3_LINE3_OUT_BIAS_350PC (7 << 4) + +/* DA732X_REG_BIAS4 (addr=0x07) */ +#define DA732X_BIAS4_LINE4_DAC_BIAS_MASK (3 << 0) +#define DA732X_BIAS4_LINE4_DAC_BIAS_100PC (0 << 0) +#define DA732X_BIAS4_LINE4_DAC_BIAS_150PC (1 << 0) +#define DA732X_BIAS4_LINE4_DAC_BIAS_50PC (2 << 0) +#define DA732X_BIAS4_LINE4_DAC_BIAS_75PC (3 << 0) +#define DA732X_BIAS4_LINE4_OUT_BIAS_MASK (7 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_100PC (0 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_125PC (1 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_150PC (2 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_175PC (3 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_200PC (4 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_250PC (5 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_300PC (6 << 4) +#define DA732X_BIAS4_LINE4_OUT_BIAS_350PC (7 << 4) + +/* DA732X_REG_SIF_VDD_SEL (addr=0x08) */ +#define DA732X_SIF_VDD_SEL_AIFA_VDD2 (1 << 0) +#define DA732X_SIF_VDD_SEL_AIFB_VDD2 (1 << 1) +#define DA732X_SIF_VDD_SEL_CIFA_VDD2 (1 << 4) + +/* DA732X_REG_MICBIAS2/1 (addr=0x0F/0x10) */ +#define DA732X_MICBIAS_VOLTAGE_MASK (0x0F << 0) +#define DA732X_MICBIAS_VOLTAGE_2V (0x00 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V05 (0x01 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V1 (0x02 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V15 (0x03 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V2 (0x04 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V25 (0x05 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V3 (0x06 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V35 (0x07 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V4 (0x08 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V45 (0x09 << 0) +#define DA732X_MICBIAS_VOLTAGE_2V5 (0x0A << 0) +#define DA732X_MICBIAS_EN (1 << 7) +#define DA732X_MICBIAS_EN_SHIFT 7 +#define DA732X_MICBIAS_VOLTAGE_SHIFT 0 +#define DA732X_MICBIAS_VOLTAGE_MAX 0x0B + +/* DA732X_REG_MICDET (addr=0x11) */ +#define DA732X_MICDET_INP_MICRES (1 << 0) +#define DA732X_MICDET_INP_MICHOOK (1 << 1) +#define DA732X_MICDET_INP_DEBOUNCE_PRD_8MS (0 << 0) +#define DA732X_MICDET_INP_DEBOUNCE_PRD_16MS (1 << 0) +#define DA732X_MICDET_INP_DEBOUNCE_PRD_32MS (2 << 0) +#define DA732X_MICDET_INP_DEBOUNCE_PRD_64MS (3 << 0) +#define DA732X_MICDET_INP_MICDET_EN (1 << 7) + +/* DA732X_REG_MIC1/2/3_PRE (addr=0x11/0x14/0x18) */ +#define DA732X_MICBOOST_MASK 0x7 +#define DA732X_MICBOOST_SHIFT 0 +#define DA732X_MICBOOST_MIN 0x1 +#define DA732X_MICBOOST_MAX DA732X_MICBOOST_MASK + +/* DA732X_REG_MIC1/2/3 (addr=0x13/0x15/0x19) */ +#define DA732X_MIC_VOL_SHIFT 0 +#define DA732X_MIC_VOL_VAL_MASK 0x1F +#define DA732X_MIC_MUTE_SHIFT 6 +#define DA732X_MIC_EN_SHIFT 7 +#define DA732X_MIC_VOL_VAL_MIN 0x7 +#define DA732X_MIC_VOL_VAL_MAX DA732X_MIC_VOL_VAL_MASK + +/* DA732X_REG_AUX1L/R (addr=0x16/0x17) */ +#define DA732X_AUX_VOL_SHIFT 0 +#define DA732X_AUX_VOL_MASK 0x7 +#define DA732X_AUX_MUTE_SHIFT 6 +#define DA732X_AUX_EN_SHIFT 7 +#define DA732X_AUX_VOL_VAL_MAX DA732X_AUX_VOL_MASK + +/* DA732X_REG_INP_PINBIAS (addr=0x1A) */ +#define DA732X_INP_MICL_PINBIAS_EN (1 << 0) +#define DA732X_INP_MICR_PINBIAS_EN (1 << 1) +#define DA732X_INP_AUX1L_PINBIAS_EN (1 << 2) +#define DA732X_INP_AUX1R_PINBIAS_EN (1 << 3) +#define DA732X_INP_AUX2_PINBIAS_EN (1 << 4) + +/* DA732X_REG_INP_ZC_EN (addr=0x1B) */ +#define DA732X_MIC1_PRE_ZC_EN (1 << 0) +#define DA732X_MIC1_ZC_EN (1 << 1) +#define DA732X_MIC2_PRE_ZC_EN (1 << 2) +#define DA732X_MIC2_ZC_EN (1 << 3) +#define DA732X_AUXL_ZC_EN (1 << 4) +#define DA732X_AUXR_ZC_EN (1 << 5) +#define DA732X_MIC3_PRE_ZC_EN (1 << 6) +#define DA732X_MIC3_ZC_EN (1 << 7) + +/* DA732X_REG_INP_MUX (addr=0x1D) */ +#define DA732X_INP_ADC1L_MUX_SEL_AUX1L (0 << 0) +#define DA732X_INP_ADC1L_MUX_SEL_MIC1 (1 << 0) +#define DA732X_INP_ADC1R_MUX_SEL_MASK (3 << 2) +#define DA732X_INP_ADC1R_MUX_SEL_AUX1R (0 << 2) +#define DA732X_INP_ADC1R_MUX_SEL_MIC2 (1 << 2) +#define DA732X_INP_ADC1R_MUX_SEL_MIC3 (2 << 2) +#define DA732X_INP_ADC2L_MUX_SEL_AUX1L (0 << 4) +#define DA732X_INP_ADC2L_MUX_SEL_MICL (1 << 4) +#define DA732X_INP_ADC2R_MUX_SEL_MASK (3 << 6) +#define DA732X_INP_ADC2R_MUX_SEL_AUX1R (0 << 6) +#define DA732X_INP_ADC2R_MUX_SEL_MICR (1 << 6) +#define DA732X_INP_ADC2R_MUX_SEL_AUX2 (2 << 6) +#define DA732X_ADC1L_MUX_SEL_SHIFT 0 +#define DA732X_ADC1R_MUX_SEL_SHIFT 2 +#define DA732X_ADC2L_MUX_SEL_SHIFT 4 +#define DA732X_ADC2R_MUX_SEL_SHIFT 6 + +/* DA732X_REG_HP_DET (addr=0x20) */ +#define DA732X_HP_DET_AZ (1 << 0) +#define DA732X_HP_DET_SEL1 (1 << 1) +#define DA732X_HP_DET_IS_MASK (3 << 2) +#define DA732X_HP_DET_IS_0_5UA (0 << 2) +#define DA732X_HP_DET_IS_1UA (1 << 2) +#define DA732X_HP_DET_IS_2UA (2 << 2) +#define DA732X_HP_DET_IS_4UA (3 << 2) +#define DA732X_HP_DET_RS_MASK (3 << 4) +#define DA732X_HP_DET_RS_INFINITE (0 << 4) +#define DA732X_HP_DET_RS_100KOHM (1 << 4) +#define DA732X_HP_DET_RS_10KOHM (2 << 4) +#define DA732X_HP_DET_RS_1KOHM (3 << 4) +#define DA732X_HP_DET_EN (1 << 7) + +/* DA732X_REG_HPL_DAC_OFFSET (addr=0x21/0x26) */ +#define DA732X_HP_DAC_OFFSET_TRIM_MASK (0x3F << 0) +#define DA732X_HP_DAC_OFFSET_DAC_SIGN (1 << 6) + +/* DA732X_REG_HPL_DAC_OFF_CNTL (addr=0x22/0x27) */ +#define DA732X_HP_DAC_OFF_CNTL_CONT_MASK (7 << 0) +#define DA732X_HP_DAC_OFF_CNTL_COMPO (1 << 3) +#define DA732X_HP_DAC_OFF_CALIBRATION (1 << 0) +#define DA732X_HP_DAC_OFF_SCALE_STEPS (1 << 1) +#define DA732X_HP_DAC_OFF_MASK 0x7F +#define DA732X_HP_DAC_COMPO_SHIFT 3 + +/* DA732X_REG_HPL_OUT_OFFSET (addr=0x23/0x28) */ +#define DA732X_HP_OUT_OFFSET_MASK (0xFF << 0) +#define DA732X_HP_DAC_OFFSET_TRIM_VAL 0x7F + +/* DA732X_REG_HPL/R (addr=0x24/0x29) */ +#define DA732X_HP_OUT_SIGN (1 << 0) +#define DA732X_HP_OUT_COMP (1 << 1) +#define DA732X_HP_OUT_RESERVED (1 << 2) +#define DA732X_HP_OUT_COMPO (1 << 3) +#define DA732X_HP_OUT_DAC_EN (1 << 4) +#define DA732X_HP_OUT_HIZ_EN (1 << 5) +#define DA732X_HP_OUT_HIZ_DIS (0 << 5) +#define DA732X_HP_OUT_MUTE (1 << 6) +#define DA732X_HP_OUT_EN (1 << 7) +#define DA732X_HP_OUT_COMPO_SHIFT 3 +#define DA732X_HP_OUT_DAC_EN_SHIFT 4 +#define DA732X_HP_HIZ_SHIFT 5 +#define DA732X_HP_MUTE_SHIFT 6 +#define DA732X_HP_OUT_EN_SHIFT 7 + +#define DA732X_OUT_HIZ_EN (1 << 5) +#define DA732X_OUT_HIZ_DIS (0 << 5) + +/* DA732X_REG_HPL/R_VOL (addr=0x25/0x2A) */ +#define DA732X_HP_VOL_VAL_MASK 0xF +#define DA732X_HP_VOL_SHIFT 0 +#define DA732X_HP_VOL_VAL_MAX DA732X_HP_VOL_VAL_MASK + +/* DA732X_REG_LIN2/3/4 (addr=0x2B/0x2C/0x2D) */ +#define DA732X_LOUT_VOL_SHIFT 0 +#define DA732X_LOUT_VOL_MASK 0x0F +#define DA732X_LOUT_DAC_OFF (0 << 4) +#define DA732X_LOUT_DAC_EN (1 << 4) +#define DA732X_LOUT_HIZ_N_DIS (0 << 5) +#define DA732X_LOUT_HIZ_N_EN (1 << 5) +#define DA732X_LOUT_UNMUTED (0 << 6) +#define DA732X_LOUT_MUTED (1 << 6) +#define DA732X_LOUT_EN (0 << 7) +#define DA732X_LOUT_DIS (1 << 7) +#define DA732X_LOUT_DAC_EN_SHIFT 4 +#define DA732X_LOUT_MUTE_SHIFT 6 +#define DA732X_LIN_OUT_EN_SHIFT 7 +#define DA732X_LOUT_VOL_VAL_MAX DA732X_LOUT_VOL_MASK + +/* DA732X_REG_OUT_ZC_EN (addr=0x2E) */ +#define DA732X_HPL_ZC_EN_SHIFT 0 +#define DA732X_HPR_ZC_EN_SHIFT 1 +#define DA732X_HPL_ZC_EN (1 << 0) +#define DA732X_HPL_ZC_DIS (0 << 0) +#define DA732X_HPR_ZC_EN (1 << 1) +#define DA732X_HPR_ZC_DIS (0 << 1) +#define DA732X_LIN2_ZC_EN (1 << 2) +#define DA732X_LIN2_ZC_DIS (0 << 2) +#define DA732X_LIN3_ZC_EN (1 << 3) +#define DA732X_LIN3_ZC_DIS (0 << 3) +#define DA732X_LIN4_ZC_EN (1 << 4) +#define DA732X_LIN4_ZC_DIS (0 << 4) + +/* DA732X_REG_HP_LIN1_GNDSEL (addr=0x37) */ +#define DA732X_HP_OUT_GNDSEL (1 << 0) + +/* DA732X_REG_CP_HP2 (addr=0x3a) */ +#define DA732X_HP_CP_PULSESKIP (1 << 0) +#define DA732X_HP_CP_REG (1 << 1) +#define DA732X_HP_CP_EN (1 << 3) +#define DA732X_HP_CP_DIS (0 << 3) + +/* DA732X_REG_CP_CTRL1 (addr=0x40) */ +#define DA732X_CP_MODE_MASK (7 << 1) +#define DA732X_CP_CTRL_STANDBY (0 << 1) +#define DA732X_CP_CTRL_CPVDD6 (2 << 1) +#define DA732X_CP_CTRL_CPVDD5 (3 << 1) +#define DA732X_CP_CTRL_CPVDD4 (4 << 1) +#define DA732X_CP_CTRL_CPVDD3 (5 << 1) +#define DA732X_CP_CTRL_CPVDD2 (6 << 1) +#define DA732X_CP_CTRL_CPVDD1 (7 << 1) +#define DA723X_CP_DIS (0 << 7) +#define DA732X_CP_EN (1 << 7) + +/* DA732X_REG_CP_CTRL2 (addr=0x41) */ +#define DA732X_CP_BOOST (1 << 0) +#define DA732X_CP_MANAGE_MAGNITUDE (2 << 2) + +/* DA732X_REG_CP_CTRL3 (addr=0x42) */ +#define DA732X_CP_1MHZ (0 << 0) +#define DA732X_CP_500KHZ (1 << 0) +#define DA732X_CP_250KHZ (2 << 0) +#define DA732X_CP_125KHZ (3 << 0) +#define DA732X_CP_63KHZ (4 << 0) +#define DA732X_CP_0KHZ (5 << 0) + +/* DA732X_REG_PLL_CTRL (addr=0x53) */ +#define DA732X_PLL_INDIV_MASK (3 << 0) +#define DA732X_PLL_SRM_EN (1 << 2) +#define DA732X_PLL_EN (1 << 7) +#define DA732X_PLL_BYPASS (0 << 0) + +/* DA732X_REG_CLK_CTRL (addr=0x54) */ +#define DA732X_SR1_MASK (0xF) +#define DA732X_SR2_MASK (0xF0) + +/* DA732X_REG_CLK_DSP (addr=0x5A) */ +#define DA732X_DSP_FREQ_MASK (7 << 0) +#define DA732X_DSP_FREQ_12MHZ (0 << 0) +#define DA732X_DSP_FREQ_24MHZ (1 << 0) +#define DA732X_DSP_FREQ_36MHZ (2 << 0) +#define DA732X_DSP_FREQ_48MHZ (3 << 0) +#define DA732X_DSP_FREQ_60MHZ (4 << 0) +#define DA732X_DSP_FREQ_72MHZ (5 << 0) +#define DA732X_DSP_FREQ_84MHZ (6 << 0) +#define DA732X_DSP_FREQ_96MHZ (7 << 0) + +/* DA732X_REG_CLK_EN1 (addr=0x5B) */ +#define DA732X_DSP_CLK_EN (1 << 0) +#define DA732X_SYS3_CLK_EN (1 << 1) +#define DA732X_DSP12_CLK_EN (1 << 2) +#define DA732X_PC_CLK_EN (1 << 3) +#define DA732X_MCLK_SQR_EN (1 << 7) + +/* DA732X_REG_CLK_EN2 (addr=0x5C) */ +#define DA732X_UART_CLK_EN (1 << 1) +#define DA732X_CP_CLK_EN (1 << 2) +#define DA732X_CP_CLK_DIS (0 << 2) + +/* DA732X_REG_CLK_EN3 (addr=0x5D) */ +#define DA732X_ADCA_BB_CLK_EN (1 << 0) +#define DA732X_ADCC_BB_CLK_EN (1 << 4) + +/* DA732X_REG_CLK_EN4 (addr=0x5E) */ +#define DA732X_DACA_BB_CLK_EN (1 << 0) +#define DA732X_DACC_BB_CLK_EN (1 << 4) +#define DA732X_DACA_BB_CLK_SHIFT 0 +#define DA732X_DACC_BB_CLK_SHIFT 4 + +/* DA732X_REG_CLK_EN5 (addr=0x5F) */ +#define DA732X_DACE_BB_CLK_EN (1 << 0) +#define DA732X_DACE_BB_CLK_SHIFT 0 + +/* DA732X_REG_AIF_MCLK (addr=0x60) */ +#define DA732X_AIFM_FRAME_64 (1 << 2) +#define DA732X_AIFM_SRC_SEL_AIFA (1 << 6) +#define DA732X_CLK_GENERATION_AIF_A (1 << 4) +#define DA732X_NO_CLK_GENERATION 0x0 + +/* DA732X_REG_AIFA1 (addr=0x61) */ +#define DA732X_AIF_WORD_MASK (0x3 << 0) +#define DA732X_AIF_WORD_16 (0 << 0) +#define DA732X_AIF_WORD_20 (1 << 0) +#define DA732X_AIF_WORD_24 (2 << 0) +#define DA732X_AIF_WORD_32 (3 << 0) +#define DA732X_AIF_TDM_MONO_SHIFT (1 << 6) +#define DA732X_AIF1_CLK_MASK (1 << 7) +#define DA732X_AIF_SLAVE (0 << 7) +#define DA732X_AIF_CLK_FROM_SRC (1 << 7) + +/* DA732X_REG_AIFA3 (addr=0x63) */ +#define DA732X_AIF_MODE_SHIFT 0 +#define DA732X_AIF_MODE_MASK 0x3 +#define DA732X_AIF_I2S_MODE (0 << 0) +#define DA732X_AIF_LEFT_J_MODE (1 << 0) +#define DA732X_AIF_RIGHT_J_MODE (2 << 0) +#define DA732X_AIF_DSP_MODE (3 << 0) +#define DA732X_AIF_WCLK_INV (1 << 4) +#define DA732X_AIF_BCLK_INV (1 << 5) +#define DA732X_AIF_EN (1 << 7) +#define DA732X_AIF_EN_SHIFT 7 + +/* DA732X_REG_PC_CTRL (addr=0x6a) */ +#define DA732X_PC_PULSE_AIFA (0 << 0) +#define DA732X_PC_PULSE_AIFB (1 << 0) +#define DA732X_PC_RESYNC_AUT (1 << 6) +#define DA732X_PC_RESYNC_NOT_AUT (0 << 6) +#define DA732X_PC_SAME (1 << 7) + +/* DA732X_REG_DATA_ROUTE (addr=0x70) */ +#define DA732X_ADC1_TO_AIFA (0 << 0) +#define DA732X_DSP_TO_AIFA (1 << 0) +#define DA732X_ADC2_TO_AIFB (0 << 1) +#define DA732X_DSP_TO_AIFB (1 << 1) +#define DA732X_AIFA_TO_DAC1L (0 << 2) +#define DA732X_DSP_TO_DAC1L (1 << 2) +#define DA732X_AIFA_TO_DAC1R (0 << 3) +#define DA732X_DSP_TO_DAC1R (1 << 3) +#define DA732X_AIFB_TO_DAC2L (0 << 4) +#define DA732X_DSP_TO_DAC2L (1 << 4) +#define DA732X_AIFB_TO_DAC2R (0 << 5) +#define DA732X_DSP_TO_DAC2R (1 << 5) +#define DA732X_AIFB_TO_DAC3 (0 << 6) +#define DA732X_DSP_TO_DAC3 (1 << 6) +#define DA732X_BYPASS_DSP (0 << 0) +#define DA732X_ALL_TO_DSP (0x7F << 0) + +/* DA732X_REG_DSP_CTRL (addr=0x71) */ +#define DA732X_DIGITAL_EN (1 << 0) +#define DA732X_DIGITAL_RESET (0 << 0) +#define DA732X_DSP_CORE_EN (1 << 1) +#define DA732X_DSP_CORE_RESET (0 << 1) + +/* DA732X_REG_SPARE1_OUT (addr=0x7D)*/ +#define DA732X_HP_DRIVER_EN (1 << 0) +#define DA732X_HP_GATE_LOW (1 << 2) +#define DA732X_HP_LOOP_GAIN_CTRL (1 << 3) + +/* DA732X_REG_ID (addr=0x81)*/ +#define DA732X_ID_MINOR_MASK (0xF << 0) +#define DA732X_ID_MAJOR_MASK (0xF << 4) + +/* DA732X_REG_ADC1/2_PD (addr=0x90/0x98) */ +#define DA732X_ADC_RST_MASK (0x3 << 0) +#define DA732X_ADC_PD_MASK (0x3 << 2) +#define DA732X_ADC_SET_ACT (0x3 << 0) +#define DA732X_ADC_SET_RST (0x0 << 0) +#define DA732X_ADC_ON (0x3 << 2) +#define DA732X_ADC_OFF (0x0 << 2) + +/* DA732X_REG_ADC1/2_SEL (addr=0x94/0x9C) */ +#define DA732X_ADC_VOL_VAL_MASK 0x7 +#define DA732X_ADCL_VOL_SHIFT 0 +#define DA732X_ADCR_VOL_SHIFT 4 +#define DA732X_ADCL_EN_SHIFT 2 +#define DA732X_ADCR_EN_SHIFT 3 +#define DA732X_ADCL_EN (1 << 2) +#define DA732X_ADCR_EN (1 << 3) +#define DA732X_ADC_VOL_VAL_MAX DA732X_ADC_VOL_VAL_MASK + +/* + * DA732X_REG_ADC1/2_HPF (addr=0x93/0x9b) + * DA732x_REG_DAC1/2/3_HPG (addr=0xA5/0xB5/0xC5) + */ +#define DA732X_HPF_MUSIC_EN (1 << 3) +#define DA732X_HPF_VOICE_EN ((1 << 3) | (1 << 7)) +#define DA732X_HPF_MASK ((1 << 3) | (1 << 7)) +#define DA732X_HPF_DIS ((0 << 3) | (0 << 7)) + +/* DA732X_REG_DAC1/2/3_VOL */ +#define DA732X_DAC_VOL_VAL_MASK 0x7F +#define DA732X_DAC_VOL_SHIFT 0 +#define DA732X_DAC_VOL_VAL_MAX DA732X_DAC_VOL_VAL_MASK + +/* DA732X_REG_DAC1/2/3_SEL (addr=0xA3/0xB3/0xC3) */ +#define DA732X_DACL_EN_SHIFT 3 +#define DA732X_DACR_EN_SHIFT 7 +#define DA732X_DACL_MUTE_SHIFT 2 +#define DA732X_DACR_MUTE_SHIFT 6 +#define DA732X_DACL_EN (1 << 3) +#define DA732X_DACR_EN (1 << 7) +#define DA732X_DACL_SDM (1 << 0) +#define DA732X_DACR_SDM (1 << 4) +#define DA732X_DACL_MUTE (1 << 2) +#define DA732X_DACR_MUTE (1 << 6) + +/* DA732X_REG_DAC_SOFTMUTE (addr=0xA4/0xB4/0xC4) */ +#define DA732X_SOFTMUTE_EN (1 << 7) +#define DA732X_GAIN_RAMPED (1 << 6) +#define DA732X_16_SAMPLES (4 << 0) +#define DA732X_SOFTMUTE_MASK (1 << 7) +#define DA732X_SOFTMUTE_SHIFT 7 + +/* + * DA732x_REG_ADC1/2_EQ12 (addr=0x95/0x9D) + * DA732x_REG_ADC1/2_EQ34 (addr=0x96/0x9E) + * DA732x_REG_ADC1/2_EQ5 (addr=0x97/0x9F) + * DA732x_REG_DAC1/2/3_EQ12 (addr=0xA5/0xB5/0xC5) + * DA732x_REG_DAC1/2/3_EQ34 (addr=0xA6/0xB6/0xC6) + * DA732x_REG_DAC1/2/3_EQ5 (addr=0xA7/0xB7/0xB7) + */ +#define DA732X_EQ_VOL_VAL_MASK 0xF +#define DA732X_EQ_BAND1_SHIFT 0 +#define DA732X_EQ_BAND2_SHIFT 4 +#define DA732X_EQ_BAND3_SHIFT 0 +#define DA732X_EQ_BAND4_SHIFT 4 +#define DA732X_EQ_BAND5_SHIFT 0 +#define DA732X_EQ_OVERALL_SHIFT 4 +#define DA732X_EQ_OVERALL_VOL_VAL_MASK 0x3 +#define DA732X_EQ_DIS (0 << 7) +#define DA732X_EQ_EN (1 << 7) +#define DA732X_EQ_EN_SHIFT 7 +#define DA732X_EQ_VOL_VAL_MAX DA732X_EQ_VOL_VAL_MASK +#define DA732X_EQ_OVERALL_VOL_VAL_MAX DA732X_EQ_OVERALL_VOL_VAL_MASK + +/* DA732X_REG_DMA_CMD (addr=0xD3) */ +#define DA732X_SEL_DSP_DMA_MASK (3 << 0) +#define DA732X_SEL_DSP_DMA_DIS (0 << 0) +#define DA732X_SEL_DSP_DMA_PMEM (1 << 0) +#define DA732X_SEL_DSP_DMA_XMEM (2 << 0) +#define DA732X_SEL_DSP_DMA_YMEM (3 << 0) +#define DA732X_DSP_RW_MASK (1 << 4) +#define DA732X_DSP_DMA_WRITE (0 << 4) +#define DA732X_DSP_DMA_READ (1 << 4) + +/* DA732X_REG_DMA_STATUS (addr=0xDA) */ +#define DA732X_DSP_DMA_FREE (0 << 0) +#define DA732X_DSP_DMA_BUSY (1 << 0) + +#endif /* __DA732X_REG_H_ */ diff --git a/sound/soc/codecs/da9055.c b/sound/soc/codecs/da9055.c new file mode 100644 index 000000000..f6a7bf956 --- /dev/null +++ b/sound/soc/codecs/da9055.c @@ -0,0 +1,1545 @@ +/* + * DA9055 ALSA Soc codec driver + * + * Copyright (c) 2012 Dialog Semiconductor + * + * Tested on (Samsung SMDK6410 board + DA9055 EVB) using I2S and I2C + * Written by David Chen and + * Ashish Chavan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DA9055 register space */ + +/* Status Registers */ +#define DA9055_STATUS1 0x02 +#define DA9055_PLL_STATUS 0x03 +#define DA9055_AUX_L_GAIN_STATUS 0x04 +#define DA9055_AUX_R_GAIN_STATUS 0x05 +#define DA9055_MIC_L_GAIN_STATUS 0x06 +#define DA9055_MIC_R_GAIN_STATUS 0x07 +#define DA9055_MIXIN_L_GAIN_STATUS 0x08 +#define DA9055_MIXIN_R_GAIN_STATUS 0x09 +#define DA9055_ADC_L_GAIN_STATUS 0x0A +#define DA9055_ADC_R_GAIN_STATUS 0x0B +#define DA9055_DAC_L_GAIN_STATUS 0x0C +#define DA9055_DAC_R_GAIN_STATUS 0x0D +#define DA9055_HP_L_GAIN_STATUS 0x0E +#define DA9055_HP_R_GAIN_STATUS 0x0F +#define DA9055_LINE_GAIN_STATUS 0x10 + +/* System Initialisation Registers */ +#define DA9055_CIF_CTRL 0x20 +#define DA9055_DIG_ROUTING_AIF 0X21 +#define DA9055_SR 0x22 +#define DA9055_REFERENCES 0x23 +#define DA9055_PLL_FRAC_TOP 0x24 +#define DA9055_PLL_FRAC_BOT 0x25 +#define DA9055_PLL_INTEGER 0x26 +#define DA9055_PLL_CTRL 0x27 +#define DA9055_AIF_CLK_MODE 0x28 +#define DA9055_AIF_CTRL 0x29 +#define DA9055_DIG_ROUTING_DAC 0x2A +#define DA9055_ALC_CTRL1 0x2B + +/* Input - Gain, Select and Filter Registers */ +#define DA9055_AUX_L_GAIN 0x30 +#define DA9055_AUX_R_GAIN 0x31 +#define DA9055_MIXIN_L_SELECT 0x32 +#define DA9055_MIXIN_R_SELECT 0x33 +#define DA9055_MIXIN_L_GAIN 0x34 +#define DA9055_MIXIN_R_GAIN 0x35 +#define DA9055_ADC_L_GAIN 0x36 +#define DA9055_ADC_R_GAIN 0x37 +#define DA9055_ADC_FILTERS1 0x38 +#define DA9055_MIC_L_GAIN 0x39 +#define DA9055_MIC_R_GAIN 0x3A + +/* Output - Gain, Select and Filter Registers */ +#define DA9055_DAC_FILTERS5 0x40 +#define DA9055_DAC_FILTERS2 0x41 +#define DA9055_DAC_FILTERS3 0x42 +#define DA9055_DAC_FILTERS4 0x43 +#define DA9055_DAC_FILTERS1 0x44 +#define DA9055_DAC_L_GAIN 0x45 +#define DA9055_DAC_R_GAIN 0x46 +#define DA9055_CP_CTRL 0x47 +#define DA9055_HP_L_GAIN 0x48 +#define DA9055_HP_R_GAIN 0x49 +#define DA9055_LINE_GAIN 0x4A +#define DA9055_MIXOUT_L_SELECT 0x4B +#define DA9055_MIXOUT_R_SELECT 0x4C + +/* System Controller Registers */ +#define DA9055_SYSTEM_MODES_INPUT 0x50 +#define DA9055_SYSTEM_MODES_OUTPUT 0x51 + +/* Control Registers */ +#define DA9055_AUX_L_CTRL 0x60 +#define DA9055_AUX_R_CTRL 0x61 +#define DA9055_MIC_BIAS_CTRL 0x62 +#define DA9055_MIC_L_CTRL 0x63 +#define DA9055_MIC_R_CTRL 0x64 +#define DA9055_MIXIN_L_CTRL 0x65 +#define DA9055_MIXIN_R_CTRL 0x66 +#define DA9055_ADC_L_CTRL 0x67 +#define DA9055_ADC_R_CTRL 0x68 +#define DA9055_DAC_L_CTRL 0x69 +#define DA9055_DAC_R_CTRL 0x6A +#define DA9055_HP_L_CTRL 0x6B +#define DA9055_HP_R_CTRL 0x6C +#define DA9055_LINE_CTRL 0x6D +#define DA9055_MIXOUT_L_CTRL 0x6E +#define DA9055_MIXOUT_R_CTRL 0x6F + +/* Configuration Registers */ +#define DA9055_LDO_CTRL 0x90 +#define DA9055_IO_CTRL 0x91 +#define DA9055_GAIN_RAMP_CTRL 0x92 +#define DA9055_MIC_CONFIG 0x93 +#define DA9055_PC_COUNT 0x94 +#define DA9055_CP_VOL_THRESHOLD1 0x95 +#define DA9055_CP_DELAY 0x96 +#define DA9055_CP_DETECTOR 0x97 +#define DA9055_AIF_OFFSET 0x98 +#define DA9055_DIG_CTRL 0x99 +#define DA9055_ALC_CTRL2 0x9A +#define DA9055_ALC_CTRL3 0x9B +#define DA9055_ALC_NOISE 0x9C +#define DA9055_ALC_TARGET_MIN 0x9D +#define DA9055_ALC_TARGET_MAX 0x9E +#define DA9055_ALC_GAIN_LIMITS 0x9F +#define DA9055_ALC_ANA_GAIN_LIMITS 0xA0 +#define DA9055_ALC_ANTICLIP_CTRL 0xA1 +#define DA9055_ALC_ANTICLIP_LEVEL 0xA2 +#define DA9055_ALC_OFFSET_OP2M_L 0xA6 +#define DA9055_ALC_OFFSET_OP2U_L 0xA7 +#define DA9055_ALC_OFFSET_OP2M_R 0xAB +#define DA9055_ALC_OFFSET_OP2U_R 0xAC +#define DA9055_ALC_CIC_OP_LVL_CTRL 0xAD +#define DA9055_ALC_CIC_OP_LVL_DATA 0xAE +#define DA9055_DAC_NG_SETUP_TIME 0xAF +#define DA9055_DAC_NG_OFF_THRESHOLD 0xB0 +#define DA9055_DAC_NG_ON_THRESHOLD 0xB1 +#define DA9055_DAC_NG_CTRL 0xB2 + +/* SR bit fields */ +#define DA9055_SR_8000 (0x1 << 0) +#define DA9055_SR_11025 (0x2 << 0) +#define DA9055_SR_12000 (0x3 << 0) +#define DA9055_SR_16000 (0x5 << 0) +#define DA9055_SR_22050 (0x6 << 0) +#define DA9055_SR_24000 (0x7 << 0) +#define DA9055_SR_32000 (0x9 << 0) +#define DA9055_SR_44100 (0xA << 0) +#define DA9055_SR_48000 (0xB << 0) +#define DA9055_SR_88200 (0xE << 0) +#define DA9055_SR_96000 (0xF << 0) + +/* REFERENCES bit fields */ +#define DA9055_BIAS_EN (1 << 3) +#define DA9055_VMID_EN (1 << 7) + +/* PLL_CTRL bit fields */ +#define DA9055_PLL_INDIV_10_20_MHZ (1 << 2) +#define DA9055_PLL_SRM_EN (1 << 6) +#define DA9055_PLL_EN (1 << 7) + +/* AIF_CLK_MODE bit fields */ +#define DA9055_AIF_BCLKS_PER_WCLK_32 (0 << 0) +#define DA9055_AIF_BCLKS_PER_WCLK_64 (1 << 0) +#define DA9055_AIF_BCLKS_PER_WCLK_128 (2 << 0) +#define DA9055_AIF_BCLKS_PER_WCLK_256 (3 << 0) +#define DA9055_AIF_CLK_EN_SLAVE_MODE (0 << 7) +#define DA9055_AIF_CLK_EN_MASTER_MODE (1 << 7) + +/* AIF_CTRL bit fields */ +#define DA9055_AIF_FORMAT_I2S_MODE (0 << 0) +#define DA9055_AIF_FORMAT_LEFT_J (1 << 0) +#define DA9055_AIF_FORMAT_RIGHT_J (2 << 0) +#define DA9055_AIF_FORMAT_DSP (3 << 0) +#define DA9055_AIF_WORD_S16_LE (0 << 2) +#define DA9055_AIF_WORD_S20_3LE (1 << 2) +#define DA9055_AIF_WORD_S24_LE (2 << 2) +#define DA9055_AIF_WORD_S32_LE (3 << 2) + +/* MIC_L_CTRL bit fields */ +#define DA9055_MIC_L_MUTE_EN (1 << 6) + +/* MIC_R_CTRL bit fields */ +#define DA9055_MIC_R_MUTE_EN (1 << 6) + +/* MIXIN_L_CTRL bit fields */ +#define DA9055_MIXIN_L_MIX_EN (1 << 3) + +/* MIXIN_R_CTRL bit fields */ +#define DA9055_MIXIN_R_MIX_EN (1 << 3) + +/* ADC_L_CTRL bit fields */ +#define DA9055_ADC_L_EN (1 << 7) + +/* ADC_R_CTRL bit fields */ +#define DA9055_ADC_R_EN (1 << 7) + +/* DAC_L_CTRL bit fields */ +#define DA9055_DAC_L_MUTE_EN (1 << 6) + +/* DAC_R_CTRL bit fields */ +#define DA9055_DAC_R_MUTE_EN (1 << 6) + +/* HP_L_CTRL bit fields */ +#define DA9055_HP_L_AMP_OE (1 << 3) + +/* HP_R_CTRL bit fields */ +#define DA9055_HP_R_AMP_OE (1 << 3) + +/* LINE_CTRL bit fields */ +#define DA9055_LINE_AMP_OE (1 << 3) + +/* MIXOUT_L_CTRL bit fields */ +#define DA9055_MIXOUT_L_MIX_EN (1 << 3) + +/* MIXOUT_R_CTRL bit fields */ +#define DA9055_MIXOUT_R_MIX_EN (1 << 3) + +/* MIC bias select bit fields */ +#define DA9055_MICBIAS2_EN (1 << 6) + +/* ALC_CIC_OP_LEVEL_CTRL bit fields */ +#define DA9055_ALC_DATA_MIDDLE (2 << 0) +#define DA9055_ALC_DATA_TOP (3 << 0) +#define DA9055_ALC_CIC_OP_CHANNEL_LEFT (0 << 7) +#define DA9055_ALC_CIC_OP_CHANNEL_RIGHT (1 << 7) + +#define DA9055_AIF_BCLK_MASK (3 << 0) +#define DA9055_AIF_CLK_MODE_MASK (1 << 7) +#define DA9055_AIF_FORMAT_MASK (3 << 0) +#define DA9055_AIF_WORD_LENGTH_MASK (3 << 2) +#define DA9055_GAIN_RAMPING_EN (1 << 5) +#define DA9055_MICBIAS_LEVEL_MASK (3 << 4) + +#define DA9055_ALC_OFFSET_15_8 0x00FF00 +#define DA9055_ALC_OFFSET_17_16 0x030000 +#define DA9055_ALC_AVG_ITERATIONS 5 + +struct pll_div { + int fref; + int fout; + u8 frac_top; + u8 frac_bot; + u8 integer; + u8 mode; /* 0 = slave, 1 = master */ +}; + +/* PLL divisor table */ +static const struct pll_div da9055_pll_div[] = { + /* for MASTER mode, fs = 44.1Khz and its harmonics */ + {11289600, 2822400, 0x00, 0x00, 0x20, 1}, /* MCLK=11.2896Mhz */ + {12000000, 2822400, 0x03, 0x61, 0x1E, 1}, /* MCLK=12Mhz */ + {12288000, 2822400, 0x0C, 0xCC, 0x1D, 1}, /* MCLK=12.288Mhz */ + {13000000, 2822400, 0x19, 0x45, 0x1B, 1}, /* MCLK=13Mhz */ + {13500000, 2822400, 0x18, 0x56, 0x1A, 1}, /* MCLK=13.5Mhz */ + {14400000, 2822400, 0x02, 0xD0, 0x19, 1}, /* MCLK=14.4Mhz */ + {19200000, 2822400, 0x1A, 0x1C, 0x12, 1}, /* MCLK=19.2Mhz */ + {19680000, 2822400, 0x0B, 0x6D, 0x12, 1}, /* MCLK=19.68Mhz */ + {19800000, 2822400, 0x07, 0xDD, 0x12, 1}, /* MCLK=19.8Mhz */ + /* for MASTER mode, fs = 48Khz and its harmonics */ + {11289600, 3072000, 0x1A, 0x8E, 0x22, 1}, /* MCLK=11.2896Mhz */ + {12000000, 3072000, 0x18, 0x93, 0x20, 1}, /* MCLK=12Mhz */ + {12288000, 3072000, 0x00, 0x00, 0x20, 1}, /* MCLK=12.288Mhz */ + {13000000, 3072000, 0x07, 0xEA, 0x1E, 1}, /* MCLK=13Mhz */ + {13500000, 3072000, 0x04, 0x11, 0x1D, 1}, /* MCLK=13.5Mhz */ + {14400000, 3072000, 0x09, 0xD0, 0x1B, 1}, /* MCLK=14.4Mhz */ + {19200000, 3072000, 0x0F, 0x5C, 0x14, 1}, /* MCLK=19.2Mhz */ + {19680000, 3072000, 0x1F, 0x60, 0x13, 1}, /* MCLK=19.68Mhz */ + {19800000, 3072000, 0x1B, 0x80, 0x13, 1}, /* MCLK=19.8Mhz */ + /* for SLAVE mode with SRM */ + {11289600, 2822400, 0x0D, 0x47, 0x21, 0}, /* MCLK=11.2896Mhz */ + {12000000, 2822400, 0x0D, 0xFA, 0x1F, 0}, /* MCLK=12Mhz */ + {12288000, 2822400, 0x16, 0x66, 0x1E, 0}, /* MCLK=12.288Mhz */ + {13000000, 2822400, 0x00, 0x98, 0x1D, 0}, /* MCLK=13Mhz */ + {13500000, 2822400, 0x1E, 0x33, 0x1B, 0}, /* MCLK=13.5Mhz */ + {14400000, 2822400, 0x06, 0x50, 0x1A, 0}, /* MCLK=14.4Mhz */ + {19200000, 2822400, 0x14, 0xBC, 0x13, 0}, /* MCLK=19.2Mhz */ + {19680000, 2822400, 0x05, 0x66, 0x13, 0}, /* MCLK=19.68Mhz */ + {19800000, 2822400, 0x01, 0xAE, 0x13, 0}, /* MCLK=19.8Mhz */ +}; + +enum clk_src { + DA9055_CLKSRC_MCLK +}; + +/* Gain and Volume */ + +static const DECLARE_TLV_DB_RANGE(aux_vol_tlv, + 0x0, 0x10, TLV_DB_SCALE_ITEM(-5400, 0, 0), + /* -54dB to 15dB */ + 0x11, 0x3f, TLV_DB_SCALE_ITEM(-5400, 150, 0) +); + +static const DECLARE_TLV_DB_RANGE(digital_gain_tlv, + 0x0, 0x07, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* -78dB to 12dB */ + 0x08, 0x7f, TLV_DB_SCALE_ITEM(-7800, 75, 0) +); + +static const DECLARE_TLV_DB_RANGE(alc_analog_gain_tlv, + 0x0, 0x0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + /* 0dB to 36dB */ + 0x01, 0x07, TLV_DB_SCALE_ITEM(0, 600, 0) +); + +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(mixin_gain_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(eq_gain_tlv, -1050, 150, 0); +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(lineout_vol_tlv, -4800, 100, 0); +static const DECLARE_TLV_DB_SCALE(alc_threshold_tlv, -9450, 150, 0); +static const DECLARE_TLV_DB_SCALE(alc_gain_tlv, 0, 600, 0); + +/* ADC and DAC high pass filter cutoff value */ +static const char * const da9055_hpf_cutoff_txt[] = { + "Fs/24000", "Fs/12000", "Fs/6000", "Fs/3000" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_dac_hpf_cutoff, + DA9055_DAC_FILTERS1, 4, da9055_hpf_cutoff_txt); + +static SOC_ENUM_SINGLE_DECL(da9055_adc_hpf_cutoff, + DA9055_ADC_FILTERS1, 4, da9055_hpf_cutoff_txt); + +/* ADC and DAC voice mode (8kHz) high pass cutoff value */ +static const char * const da9055_vf_cutoff_txt[] = { + "2.5Hz", "25Hz", "50Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_dac_vf_cutoff, + DA9055_DAC_FILTERS1, 0, da9055_vf_cutoff_txt); + +static SOC_ENUM_SINGLE_DECL(da9055_adc_vf_cutoff, + DA9055_ADC_FILTERS1, 0, da9055_vf_cutoff_txt); + +/* Gain ramping rate value */ +static const char * const da9055_gain_ramping_txt[] = { + "nominal rate", "nominal rate * 4", "nominal rate * 8", + "nominal rate / 8" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_gain_ramping_rate, + DA9055_GAIN_RAMP_CTRL, 0, da9055_gain_ramping_txt); + +/* DAC noise gate setup time value */ +static const char * const da9055_dac_ng_setup_time_txt[] = { + "256 samples", "512 samples", "1024 samples", "2048 samples" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_dac_ng_setup_time, + DA9055_DAC_NG_SETUP_TIME, 0, + da9055_dac_ng_setup_time_txt); + +/* DAC noise gate rampup rate value */ +static const char * const da9055_dac_ng_rampup_txt[] = { + "0.02 ms/dB", "0.16 ms/dB" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_dac_ng_rampup_rate, + DA9055_DAC_NG_SETUP_TIME, 2, + da9055_dac_ng_rampup_txt); + +/* DAC noise gate rampdown rate value */ +static const char * const da9055_dac_ng_rampdown_txt[] = { + "0.64 ms/dB", "20.48 ms/dB" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_dac_ng_rampdown_rate, + DA9055_DAC_NG_SETUP_TIME, 3, + da9055_dac_ng_rampdown_txt); + +/* DAC soft mute rate value */ +static const char * const da9055_dac_soft_mute_rate_txt[] = { + "1", "2", "4", "8", "16", "32", "64" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_dac_soft_mute_rate, + DA9055_DAC_FILTERS5, 4, + da9055_dac_soft_mute_rate_txt); + +/* DAC routing select */ +static const char * const da9055_dac_src_txt[] = { + "ADC output left", "ADC output right", "AIF input left", + "AIF input right" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_dac_l_src, + DA9055_DIG_ROUTING_DAC, 0, da9055_dac_src_txt); + +static SOC_ENUM_SINGLE_DECL(da9055_dac_r_src, + DA9055_DIG_ROUTING_DAC, 4, da9055_dac_src_txt); + +/* MIC PGA Left source select */ +static const char * const da9055_mic_l_src_txt[] = { + "MIC1_P_N", "MIC1_P", "MIC1_N", "MIC2_L" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_mic_l_src, + DA9055_MIXIN_L_SELECT, 4, da9055_mic_l_src_txt); + +/* MIC PGA Right source select */ +static const char * const da9055_mic_r_src_txt[] = { + "MIC2_R_L", "MIC2_R", "MIC2_L" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_mic_r_src, + DA9055_MIXIN_R_SELECT, 4, da9055_mic_r_src_txt); + +/* ALC Input Signal Tracking rate select */ +static const char * const da9055_signal_tracking_rate_txt[] = { + "1/4", "1/16", "1/256", "1/65536" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_integ_attack_rate, + DA9055_ALC_CTRL3, 4, + da9055_signal_tracking_rate_txt); + +static SOC_ENUM_SINGLE_DECL(da9055_integ_release_rate, + DA9055_ALC_CTRL3, 6, + da9055_signal_tracking_rate_txt); + +/* ALC Attack Rate select */ +static const char * const da9055_attack_rate_txt[] = { + "44/fs", "88/fs", "176/fs", "352/fs", "704/fs", "1408/fs", "2816/fs", + "5632/fs", "11264/fs", "22528/fs", "45056/fs", "90112/fs", "180224/fs" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_attack_rate, + DA9055_ALC_CTRL2, 0, da9055_attack_rate_txt); + +/* ALC Release Rate select */ +static const char * const da9055_release_rate_txt[] = { + "176/fs", "352/fs", "704/fs", "1408/fs", "2816/fs", "5632/fs", + "11264/fs", "22528/fs", "45056/fs", "90112/fs", "180224/fs" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_release_rate, + DA9055_ALC_CTRL2, 4, da9055_release_rate_txt); + +/* ALC Hold Time select */ +static const char * const da9055_hold_time_txt[] = { + "62/fs", "124/fs", "248/fs", "496/fs", "992/fs", "1984/fs", "3968/fs", + "7936/fs", "15872/fs", "31744/fs", "63488/fs", "126976/fs", + "253952/fs", "507904/fs", "1015808/fs", "2031616/fs" +}; + +static SOC_ENUM_SINGLE_DECL(da9055_hold_time, + DA9055_ALC_CTRL3, 0, da9055_hold_time_txt); + +static int da9055_get_alc_data(struct snd_soc_component *component, u8 reg_val) +{ + int mid_data, top_data; + int sum = 0; + u8 iteration; + + for (iteration = 0; iteration < DA9055_ALC_AVG_ITERATIONS; + iteration++) { + /* Select the left or right channel and capture data */ + snd_soc_component_write(component, DA9055_ALC_CIC_OP_LVL_CTRL, reg_val); + + /* Select middle 8 bits for read back from data register */ + snd_soc_component_write(component, DA9055_ALC_CIC_OP_LVL_CTRL, + reg_val | DA9055_ALC_DATA_MIDDLE); + mid_data = snd_soc_component_read32(component, DA9055_ALC_CIC_OP_LVL_DATA); + + /* Select top 8 bits for read back from data register */ + snd_soc_component_write(component, DA9055_ALC_CIC_OP_LVL_CTRL, + reg_val | DA9055_ALC_DATA_TOP); + top_data = snd_soc_component_read32(component, DA9055_ALC_CIC_OP_LVL_DATA); + + sum += ((mid_data << 8) | (top_data << 16)); + } + + return sum / DA9055_ALC_AVG_ITERATIONS; +} + +static int da9055_put_alc_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + u8 reg_val, adc_left, adc_right, mic_left, mic_right; + int avg_left_data, avg_right_data, offset_l, offset_r; + + if (ucontrol->value.integer.value[0]) { + /* + * While enabling ALC (or ALC sync mode), calibration of the DC + * offsets must be done first + */ + + /* Save current values from Mic control registers */ + mic_left = snd_soc_component_read32(component, DA9055_MIC_L_CTRL); + mic_right = snd_soc_component_read32(component, DA9055_MIC_R_CTRL); + + /* Mute Mic PGA Left and Right */ + snd_soc_component_update_bits(component, DA9055_MIC_L_CTRL, + DA9055_MIC_L_MUTE_EN, DA9055_MIC_L_MUTE_EN); + snd_soc_component_update_bits(component, DA9055_MIC_R_CTRL, + DA9055_MIC_R_MUTE_EN, DA9055_MIC_R_MUTE_EN); + + /* Save current values from ADC control registers */ + adc_left = snd_soc_component_read32(component, DA9055_ADC_L_CTRL); + adc_right = snd_soc_component_read32(component, DA9055_ADC_R_CTRL); + + /* Enable ADC Left and Right */ + snd_soc_component_update_bits(component, DA9055_ADC_L_CTRL, + DA9055_ADC_L_EN, DA9055_ADC_L_EN); + snd_soc_component_update_bits(component, DA9055_ADC_R_CTRL, + DA9055_ADC_R_EN, DA9055_ADC_R_EN); + + /* Calculate average for Left and Right data */ + /* Left Data */ + avg_left_data = da9055_get_alc_data(component, + DA9055_ALC_CIC_OP_CHANNEL_LEFT); + /* Right Data */ + avg_right_data = da9055_get_alc_data(component, + DA9055_ALC_CIC_OP_CHANNEL_RIGHT); + + /* Calculate DC offset */ + offset_l = -avg_left_data; + offset_r = -avg_right_data; + + reg_val = (offset_l & DA9055_ALC_OFFSET_15_8) >> 8; + snd_soc_component_write(component, DA9055_ALC_OFFSET_OP2M_L, reg_val); + reg_val = (offset_l & DA9055_ALC_OFFSET_17_16) >> 16; + snd_soc_component_write(component, DA9055_ALC_OFFSET_OP2U_L, reg_val); + + reg_val = (offset_r & DA9055_ALC_OFFSET_15_8) >> 8; + snd_soc_component_write(component, DA9055_ALC_OFFSET_OP2M_R, reg_val); + reg_val = (offset_r & DA9055_ALC_OFFSET_17_16) >> 16; + snd_soc_component_write(component, DA9055_ALC_OFFSET_OP2U_R, reg_val); + + /* Restore original values of ADC control registers */ + snd_soc_component_write(component, DA9055_ADC_L_CTRL, adc_left); + snd_soc_component_write(component, DA9055_ADC_R_CTRL, adc_right); + + /* Restore original values of Mic control registers */ + snd_soc_component_write(component, DA9055_MIC_L_CTRL, mic_left); + snd_soc_component_write(component, DA9055_MIC_R_CTRL, mic_right); + } + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +static const struct snd_kcontrol_new da9055_snd_controls[] = { + + /* Volume controls */ + SOC_DOUBLE_R_TLV("Mic Volume", + DA9055_MIC_L_GAIN, DA9055_MIC_R_GAIN, + 0, 0x7, 0, mic_vol_tlv), + SOC_DOUBLE_R_TLV("Aux Volume", + DA9055_AUX_L_GAIN, DA9055_AUX_R_GAIN, + 0, 0x3f, 0, aux_vol_tlv), + SOC_DOUBLE_R_TLV("Mixin PGA Volume", + DA9055_MIXIN_L_GAIN, DA9055_MIXIN_R_GAIN, + 0, 0xf, 0, mixin_gain_tlv), + SOC_DOUBLE_R_TLV("ADC Volume", + DA9055_ADC_L_GAIN, DA9055_ADC_R_GAIN, + 0, 0x7f, 0, digital_gain_tlv), + + SOC_DOUBLE_R_TLV("DAC Volume", + DA9055_DAC_L_GAIN, DA9055_DAC_R_GAIN, + 0, 0x7f, 0, digital_gain_tlv), + SOC_DOUBLE_R_TLV("Headphone Volume", + DA9055_HP_L_GAIN, DA9055_HP_R_GAIN, + 0, 0x3f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Lineout Volume", DA9055_LINE_GAIN, 0, 0x3f, 0, + lineout_vol_tlv), + + /* DAC Equalizer controls */ + SOC_SINGLE("DAC EQ Switch", DA9055_DAC_FILTERS4, 7, 1, 0), + SOC_SINGLE_TLV("DAC EQ1 Volume", DA9055_DAC_FILTERS2, 0, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ2 Volume", DA9055_DAC_FILTERS2, 4, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ3 Volume", DA9055_DAC_FILTERS3, 0, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ4 Volume", DA9055_DAC_FILTERS3, 4, 0xf, 0, + eq_gain_tlv), + SOC_SINGLE_TLV("DAC EQ5 Volume", DA9055_DAC_FILTERS4, 0, 0xf, 0, + eq_gain_tlv), + + /* High Pass Filter and Voice Mode controls */ + SOC_SINGLE("ADC HPF Switch", DA9055_ADC_FILTERS1, 7, 1, 0), + SOC_ENUM("ADC HPF Cutoff", da9055_adc_hpf_cutoff), + SOC_SINGLE("ADC Voice Mode Switch", DA9055_ADC_FILTERS1, 3, 1, 0), + SOC_ENUM("ADC Voice Cutoff", da9055_adc_vf_cutoff), + + SOC_SINGLE("DAC HPF Switch", DA9055_DAC_FILTERS1, 7, 1, 0), + SOC_ENUM("DAC HPF Cutoff", da9055_dac_hpf_cutoff), + SOC_SINGLE("DAC Voice Mode Switch", DA9055_DAC_FILTERS1, 3, 1, 0), + SOC_ENUM("DAC Voice Cutoff", da9055_dac_vf_cutoff), + + /* Mute controls */ + SOC_DOUBLE_R("Mic Switch", DA9055_MIC_L_CTRL, + DA9055_MIC_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("Aux Switch", DA9055_AUX_L_CTRL, + DA9055_AUX_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("Mixin PGA Switch", DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("ADC Switch", DA9055_ADC_L_CTRL, + DA9055_ADC_R_CTRL, 6, 1, 0), + SOC_DOUBLE_R("Headphone Switch", DA9055_HP_L_CTRL, + DA9055_HP_R_CTRL, 6, 1, 0), + SOC_SINGLE("Lineout Switch", DA9055_LINE_CTRL, 6, 1, 0), + SOC_SINGLE("DAC Soft Mute Switch", DA9055_DAC_FILTERS5, 7, 1, 0), + SOC_ENUM("DAC Soft Mute Rate", da9055_dac_soft_mute_rate), + + /* Zero Cross controls */ + SOC_DOUBLE_R("Aux ZC Switch", DA9055_AUX_L_CTRL, + DA9055_AUX_R_CTRL, 4, 1, 0), + SOC_DOUBLE_R("Mixin PGA ZC Switch", DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_R_CTRL, 4, 1, 0), + SOC_DOUBLE_R("Headphone ZC Switch", DA9055_HP_L_CTRL, + DA9055_HP_R_CTRL, 4, 1, 0), + SOC_SINGLE("Lineout ZC Switch", DA9055_LINE_CTRL, 4, 1, 0), + + /* Gain Ramping controls */ + SOC_DOUBLE_R("Aux Gain Ramping Switch", DA9055_AUX_L_CTRL, + DA9055_AUX_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("Mixin Gain Ramping Switch", DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("ADC Gain Ramping Switch", DA9055_ADC_L_CTRL, + DA9055_ADC_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("DAC Gain Ramping Switch", DA9055_DAC_L_CTRL, + DA9055_DAC_R_CTRL, 5, 1, 0), + SOC_DOUBLE_R("Headphone Gain Ramping Switch", DA9055_HP_L_CTRL, + DA9055_HP_R_CTRL, 5, 1, 0), + SOC_SINGLE("Lineout Gain Ramping Switch", DA9055_LINE_CTRL, 5, 1, 0), + SOC_ENUM("Gain Ramping Rate", da9055_gain_ramping_rate), + + /* DAC Noise Gate controls */ + SOC_SINGLE("DAC NG Switch", DA9055_DAC_NG_CTRL, 7, 1, 0), + SOC_SINGLE("DAC NG ON Threshold", DA9055_DAC_NG_ON_THRESHOLD, + 0, 0x7, 0), + SOC_SINGLE("DAC NG OFF Threshold", DA9055_DAC_NG_OFF_THRESHOLD, + 0, 0x7, 0), + SOC_ENUM("DAC NG Setup Time", da9055_dac_ng_setup_time), + SOC_ENUM("DAC NG Rampup Rate", da9055_dac_ng_rampup_rate), + SOC_ENUM("DAC NG Rampdown Rate", da9055_dac_ng_rampdown_rate), + + /* DAC Invertion control */ + SOC_SINGLE("DAC Left Invert", DA9055_DIG_CTRL, 3, 1, 0), + SOC_SINGLE("DAC Right Invert", DA9055_DIG_CTRL, 7, 1, 0), + + /* DMIC controls */ + SOC_DOUBLE_R("DMIC Switch", DA9055_MIXIN_L_SELECT, + DA9055_MIXIN_R_SELECT, 7, 1, 0), + + /* ALC Controls */ + SOC_DOUBLE_EXT("ALC Switch", DA9055_ALC_CTRL1, 3, 7, 1, 0, + snd_soc_get_volsw, da9055_put_alc_sw), + SOC_SINGLE_EXT("ALC Sync Mode Switch", DA9055_ALC_CTRL1, 1, 1, 0, + snd_soc_get_volsw, da9055_put_alc_sw), + SOC_SINGLE("ALC Offset Switch", DA9055_ALC_CTRL1, 0, 1, 0), + SOC_SINGLE("ALC Anticlip Mode Switch", DA9055_ALC_ANTICLIP_CTRL, + 7, 1, 0), + SOC_SINGLE("ALC Anticlip Level", DA9055_ALC_ANTICLIP_LEVEL, + 0, 0x7f, 0), + SOC_SINGLE_TLV("ALC Min Threshold Volume", DA9055_ALC_TARGET_MIN, + 0, 0x3f, 1, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Threshold Volume", DA9055_ALC_TARGET_MAX, + 0, 0x3f, 1, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Noise Threshold Volume", DA9055_ALC_NOISE, + 0, 0x3f, 1, alc_threshold_tlv), + SOC_SINGLE_TLV("ALC Max Gain Volume", DA9055_ALC_GAIN_LIMITS, + 4, 0xf, 0, alc_gain_tlv), + SOC_SINGLE_TLV("ALC Max Attenuation Volume", DA9055_ALC_GAIN_LIMITS, + 0, 0xf, 0, alc_gain_tlv), + SOC_SINGLE_TLV("ALC Min Analog Gain Volume", + DA9055_ALC_ANA_GAIN_LIMITS, + 0, 0x7, 0, alc_analog_gain_tlv), + SOC_SINGLE_TLV("ALC Max Analog Gain Volume", + DA9055_ALC_ANA_GAIN_LIMITS, + 4, 0x7, 0, alc_analog_gain_tlv), + SOC_ENUM("ALC Attack Rate", da9055_attack_rate), + SOC_ENUM("ALC Release Rate", da9055_release_rate), + SOC_ENUM("ALC Hold Time", da9055_hold_time), + /* + * Rate at which input signal envelope is tracked as the signal gets + * larger + */ + SOC_ENUM("ALC Integ Attack Rate", da9055_integ_attack_rate), + /* + * Rate at which input signal envelope is tracked as the signal gets + * smaller + */ + SOC_ENUM("ALC Integ Release Rate", da9055_integ_release_rate), +}; + +/* DAPM Controls */ + +/* Mic PGA Left Source */ +static const struct snd_kcontrol_new da9055_mic_l_mux_controls = +SOC_DAPM_ENUM("Route", da9055_mic_l_src); + +/* Mic PGA Right Source */ +static const struct snd_kcontrol_new da9055_mic_r_mux_controls = +SOC_DAPM_ENUM("Route", da9055_mic_r_src); + +/* In Mixer Left */ +static const struct snd_kcontrol_new da9055_dapm_mixinl_controls[] = { + SOC_DAPM_SINGLE("Aux Left Switch", DA9055_MIXIN_L_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Left Switch", DA9055_MIXIN_L_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mic Right Switch", DA9055_MIXIN_L_SELECT, 2, 1, 0), +}; + +/* In Mixer Right */ +static const struct snd_kcontrol_new da9055_dapm_mixinr_controls[] = { + SOC_DAPM_SINGLE("Aux Right Switch", DA9055_MIXIN_R_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Right Switch", DA9055_MIXIN_R_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mic Left Switch", DA9055_MIXIN_R_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Switch", DA9055_MIXIN_R_SELECT, 3, 1, 0), +}; + +/* DAC Left Source */ +static const struct snd_kcontrol_new da9055_dac_l_mux_controls = +SOC_DAPM_ENUM("Route", da9055_dac_l_src); + +/* DAC Right Source */ +static const struct snd_kcontrol_new da9055_dac_r_mux_controls = +SOC_DAPM_ENUM("Route", da9055_dac_r_src); + +/* Out Mixer Left */ +static const struct snd_kcontrol_new da9055_dapm_mixoutl_controls[] = { + SOC_DAPM_SINGLE("Aux Left Switch", DA9055_MIXOUT_L_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Switch", DA9055_MIXOUT_L_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Switch", DA9055_MIXOUT_L_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("DAC Left Switch", DA9055_MIXOUT_L_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("Aux Left Invert Switch", DA9055_MIXOUT_L_SELECT, + 4, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Invert Switch", DA9055_MIXOUT_L_SELECT, + 5, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Invert Switch", DA9055_MIXOUT_L_SELECT, + 6, 1, 0), +}; + +/* Out Mixer Right */ +static const struct snd_kcontrol_new da9055_dapm_mixoutr_controls[] = { + SOC_DAPM_SINGLE("Aux Right Switch", DA9055_MIXOUT_R_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Switch", DA9055_MIXOUT_R_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Switch", DA9055_MIXOUT_R_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("DAC Right Switch", DA9055_MIXOUT_R_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("Aux Right Invert Switch", DA9055_MIXOUT_R_SELECT, + 4, 1, 0), + SOC_DAPM_SINGLE("Mixin Right Invert Switch", DA9055_MIXOUT_R_SELECT, + 5, 1, 0), + SOC_DAPM_SINGLE("Mixin Left Invert Switch", DA9055_MIXOUT_R_SELECT, + 6, 1, 0), +}; + +/* Headphone Output Enable */ +static const struct snd_kcontrol_new da9055_dapm_hp_l_control = +SOC_DAPM_SINGLE("Switch", DA9055_HP_L_CTRL, 3, 1, 0); + +static const struct snd_kcontrol_new da9055_dapm_hp_r_control = +SOC_DAPM_SINGLE("Switch", DA9055_HP_R_CTRL, 3, 1, 0); + +/* Lineout Output Enable */ +static const struct snd_kcontrol_new da9055_dapm_lineout_control = +SOC_DAPM_SINGLE("Switch", DA9055_LINE_CTRL, 3, 1, 0); + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget da9055_dapm_widgets[] = { + /* Input Side */ + + /* Input Lines */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), + + /* MUXs for Mic PGA source selection */ + SND_SOC_DAPM_MUX("Mic Left Source", SND_SOC_NOPM, 0, 0, + &da9055_mic_l_mux_controls), + SND_SOC_DAPM_MUX("Mic Right Source", SND_SOC_NOPM, 0, 0, + &da9055_mic_r_mux_controls), + + /* Input PGAs */ + SND_SOC_DAPM_PGA("Mic Left", DA9055_MIC_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic Right", DA9055_MIC_R_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Aux Left", DA9055_AUX_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Aux Right", DA9055_AUX_R_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIXIN Left", DA9055_MIXIN_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIXIN Right", DA9055_MIXIN_R_CTRL, 7, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Mic Bias", DA9055_MIC_BIAS_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF", DA9055_AIF_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Charge Pump", DA9055_CP_CTRL, 7, 0, NULL, 0), + + /* Input Mixers */ + SND_SOC_DAPM_MIXER("In Mixer Left", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixinl_controls[0], + ARRAY_SIZE(da9055_dapm_mixinl_controls)), + SND_SOC_DAPM_MIXER("In Mixer Right", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixinr_controls[0], + ARRAY_SIZE(da9055_dapm_mixinr_controls)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC Left", "Capture", DA9055_ADC_L_CTRL, 7, 0), + SND_SOC_DAPM_ADC("ADC Right", "Capture", DA9055_ADC_R_CTRL, 7, 0), + + /* Output Side */ + + /* MUXs for DAC source selection */ + SND_SOC_DAPM_MUX("DAC Left Source", SND_SOC_NOPM, 0, 0, + &da9055_dac_l_mux_controls), + SND_SOC_DAPM_MUX("DAC Right Source", SND_SOC_NOPM, 0, 0, + &da9055_dac_r_mux_controls), + + /* AIF input */ + SND_SOC_DAPM_AIF_IN("AIFIN Left", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFIN Right", "Playback", 0, SND_SOC_NOPM, 0, 0), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC Left", "Playback", DA9055_DAC_L_CTRL, 7, 0), + SND_SOC_DAPM_DAC("DAC Right", "Playback", DA9055_DAC_R_CTRL, 7, 0), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Out Mixer Left", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixoutl_controls[0], + ARRAY_SIZE(da9055_dapm_mixoutl_controls)), + SND_SOC_DAPM_MIXER("Out Mixer Right", SND_SOC_NOPM, 0, 0, + &da9055_dapm_mixoutr_controls[0], + ARRAY_SIZE(da9055_dapm_mixoutr_controls)), + + /* Output Enable Switches */ + SND_SOC_DAPM_SWITCH("Headphone Left Enable", SND_SOC_NOPM, 0, 0, + &da9055_dapm_hp_l_control), + SND_SOC_DAPM_SWITCH("Headphone Right Enable", SND_SOC_NOPM, 0, 0, + &da9055_dapm_hp_r_control), + SND_SOC_DAPM_SWITCH("Lineout Enable", SND_SOC_NOPM, 0, 0, + &da9055_dapm_lineout_control), + + /* Output PGAs */ + SND_SOC_DAPM_PGA("MIXOUT Left", DA9055_MIXOUT_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIXOUT Right", DA9055_MIXOUT_R_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout", DA9055_LINE_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Left", DA9055_HP_L_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headphone Right", DA9055_HP_R_CTRL, 7, 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LINE"), +}; + +/* DAPM audio route definition */ +static const struct snd_soc_dapm_route da9055_audio_map[] = { + /* Dest Connecting Widget source */ + + /* Input path */ + {"Mic Left Source", "MIC1_P_N", "MIC1"}, + {"Mic Left Source", "MIC1_P", "MIC1"}, + {"Mic Left Source", "MIC1_N", "MIC1"}, + {"Mic Left Source", "MIC2_L", "MIC2"}, + + {"Mic Right Source", "MIC2_R_L", "MIC2"}, + {"Mic Right Source", "MIC2_R", "MIC2"}, + {"Mic Right Source", "MIC2_L", "MIC2"}, + + {"Mic Left", NULL, "Mic Left Source"}, + {"Mic Right", NULL, "Mic Right Source"}, + + {"Aux Left", NULL, "AUXL"}, + {"Aux Right", NULL, "AUXR"}, + + {"In Mixer Left", "Mic Left Switch", "Mic Left"}, + {"In Mixer Left", "Mic Right Switch", "Mic Right"}, + {"In Mixer Left", "Aux Left Switch", "Aux Left"}, + + {"In Mixer Right", "Mic Right Switch", "Mic Right"}, + {"In Mixer Right", "Mic Left Switch", "Mic Left"}, + {"In Mixer Right", "Aux Right Switch", "Aux Right"}, + {"In Mixer Right", "Mixin Left Switch", "MIXIN Left"}, + + {"MIXIN Left", NULL, "In Mixer Left"}, + {"ADC Left", NULL, "MIXIN Left"}, + + {"MIXIN Right", NULL, "In Mixer Right"}, + {"ADC Right", NULL, "MIXIN Right"}, + + {"ADC Left", NULL, "AIF"}, + {"ADC Right", NULL, "AIF"}, + + /* Output path */ + {"AIFIN Left", NULL, "AIF"}, + {"AIFIN Right", NULL, "AIF"}, + + {"DAC Left Source", "ADC output left", "ADC Left"}, + {"DAC Left Source", "ADC output right", "ADC Right"}, + {"DAC Left Source", "AIF input left", "AIFIN Left"}, + {"DAC Left Source", "AIF input right", "AIFIN Right"}, + + {"DAC Right Source", "ADC output left", "ADC Left"}, + {"DAC Right Source", "ADC output right", "ADC Right"}, + {"DAC Right Source", "AIF input left", "AIFIN Left"}, + {"DAC Right Source", "AIF input right", "AIFIN Right"}, + + {"DAC Left", NULL, "DAC Left Source"}, + {"DAC Right", NULL, "DAC Right Source"}, + + {"Out Mixer Left", "Aux Left Switch", "Aux Left"}, + {"Out Mixer Left", "Mixin Left Switch", "MIXIN Left"}, + {"Out Mixer Left", "Mixin Right Switch", "MIXIN Right"}, + {"Out Mixer Left", "Aux Left Invert Switch", "Aux Left"}, + {"Out Mixer Left", "Mixin Left Invert Switch", "MIXIN Left"}, + {"Out Mixer Left", "Mixin Right Invert Switch", "MIXIN Right"}, + {"Out Mixer Left", "DAC Left Switch", "DAC Left"}, + + {"Out Mixer Right", "Aux Right Switch", "Aux Right"}, + {"Out Mixer Right", "Mixin Right Switch", "MIXIN Right"}, + {"Out Mixer Right", "Mixin Left Switch", "MIXIN Left"}, + {"Out Mixer Right", "Aux Right Invert Switch", "Aux Right"}, + {"Out Mixer Right", "Mixin Right Invert Switch", "MIXIN Right"}, + {"Out Mixer Right", "Mixin Left Invert Switch", "MIXIN Left"}, + {"Out Mixer Right", "DAC Right Switch", "DAC Right"}, + + {"MIXOUT Left", NULL, "Out Mixer Left"}, + {"Headphone Left Enable", "Switch", "MIXOUT Left"}, + {"Headphone Left", NULL, "Headphone Left Enable"}, + {"Headphone Left", NULL, "Charge Pump"}, + {"HPL", NULL, "Headphone Left"}, + + {"MIXOUT Right", NULL, "Out Mixer Right"}, + {"Headphone Right Enable", "Switch", "MIXOUT Right"}, + {"Headphone Right", NULL, "Headphone Right Enable"}, + {"Headphone Right", NULL, "Charge Pump"}, + {"HPR", NULL, "Headphone Right"}, + + {"MIXOUT Right", NULL, "Out Mixer Right"}, + {"Lineout Enable", "Switch", "MIXOUT Right"}, + {"Lineout", NULL, "Lineout Enable"}, + {"LINE", NULL, "Lineout"}, +}; + +/* Codec private data */ +struct da9055_priv { + struct regmap *regmap; + unsigned int mclk_rate; + int master; + struct da9055_platform_data *pdata; +}; + +static const struct reg_default da9055_reg_defaults[] = { + { 0x21, 0x10 }, + { 0x22, 0x0A }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x0C }, + { 0x28, 0x01 }, + { 0x29, 0x08 }, + { 0x2A, 0x32 }, + { 0x2B, 0x00 }, + { 0x30, 0x35 }, + { 0x31, 0x35 }, + { 0x32, 0x00 }, + { 0x33, 0x00 }, + { 0x34, 0x03 }, + { 0x35, 0x03 }, + { 0x36, 0x6F }, + { 0x37, 0x6F }, + { 0x38, 0x80 }, + { 0x39, 0x01 }, + { 0x3A, 0x01 }, + { 0x40, 0x00 }, + { 0x41, 0x88 }, + { 0x42, 0x88 }, + { 0x43, 0x08 }, + { 0x44, 0x80 }, + { 0x45, 0x6F }, + { 0x46, 0x6F }, + { 0x47, 0x61 }, + { 0x48, 0x35 }, + { 0x49, 0x35 }, + { 0x4A, 0x35 }, + { 0x4B, 0x00 }, + { 0x4C, 0x00 }, + { 0x60, 0x44 }, + { 0x61, 0x44 }, + { 0x62, 0x00 }, + { 0x63, 0x40 }, + { 0x64, 0x40 }, + { 0x65, 0x40 }, + { 0x66, 0x40 }, + { 0x67, 0x40 }, + { 0x68, 0x40 }, + { 0x69, 0x48 }, + { 0x6A, 0x40 }, + { 0x6B, 0x41 }, + { 0x6C, 0x40 }, + { 0x6D, 0x40 }, + { 0x6E, 0x10 }, + { 0x6F, 0x10 }, + { 0x90, 0x80 }, + { 0x92, 0x02 }, + { 0x93, 0x00 }, + { 0x99, 0x00 }, + { 0x9A, 0x00 }, + { 0x9B, 0x00 }, + { 0x9C, 0x3F }, + { 0x9D, 0x00 }, + { 0x9E, 0x3F }, + { 0x9F, 0xFF }, + { 0xA0, 0x71 }, + { 0xA1, 0x00 }, + { 0xA2, 0x00 }, + { 0xA6, 0x00 }, + { 0xA7, 0x00 }, + { 0xAB, 0x00 }, + { 0xAC, 0x00 }, + { 0xAD, 0x00 }, + { 0xAF, 0x08 }, + { 0xB0, 0x00 }, + { 0xB1, 0x00 }, + { 0xB2, 0x00 }, +}; + +static bool da9055_volatile_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case DA9055_STATUS1: + case DA9055_PLL_STATUS: + case DA9055_AUX_L_GAIN_STATUS: + case DA9055_AUX_R_GAIN_STATUS: + case DA9055_MIC_L_GAIN_STATUS: + case DA9055_MIC_R_GAIN_STATUS: + case DA9055_MIXIN_L_GAIN_STATUS: + case DA9055_MIXIN_R_GAIN_STATUS: + case DA9055_ADC_L_GAIN_STATUS: + case DA9055_ADC_R_GAIN_STATUS: + case DA9055_DAC_L_GAIN_STATUS: + case DA9055_DAC_R_GAIN_STATUS: + case DA9055_HP_L_GAIN_STATUS: + case DA9055_HP_R_GAIN_STATUS: + case DA9055_LINE_GAIN_STATUS: + case DA9055_ALC_CIC_OP_LVL_DATA: + return true; + default: + return false; + } +} + +/* Set DAI word length */ +static int da9055_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct da9055_priv *da9055 = snd_soc_component_get_drvdata(component); + u8 aif_ctrl, fs; + u32 sysclk; + + switch (params_width(params)) { + case 16: + aif_ctrl = DA9055_AIF_WORD_S16_LE; + break; + case 20: + aif_ctrl = DA9055_AIF_WORD_S20_3LE; + break; + case 24: + aif_ctrl = DA9055_AIF_WORD_S24_LE; + break; + case 32: + aif_ctrl = DA9055_AIF_WORD_S32_LE; + break; + default: + return -EINVAL; + } + + /* Set AIF format */ + snd_soc_component_update_bits(component, DA9055_AIF_CTRL, DA9055_AIF_WORD_LENGTH_MASK, + aif_ctrl); + + switch (params_rate(params)) { + case 8000: + fs = DA9055_SR_8000; + sysclk = 3072000; + break; + case 11025: + fs = DA9055_SR_11025; + sysclk = 2822400; + break; + case 12000: + fs = DA9055_SR_12000; + sysclk = 3072000; + break; + case 16000: + fs = DA9055_SR_16000; + sysclk = 3072000; + break; + case 22050: + fs = DA9055_SR_22050; + sysclk = 2822400; + break; + case 32000: + fs = DA9055_SR_32000; + sysclk = 3072000; + break; + case 44100: + fs = DA9055_SR_44100; + sysclk = 2822400; + break; + case 48000: + fs = DA9055_SR_48000; + sysclk = 3072000; + break; + case 88200: + fs = DA9055_SR_88200; + sysclk = 2822400; + break; + case 96000: + fs = DA9055_SR_96000; + sysclk = 3072000; + break; + default: + return -EINVAL; + } + + if (da9055->mclk_rate) { + /* PLL Mode, Write actual FS */ + snd_soc_component_write(component, DA9055_SR, fs); + } else { + /* + * Non-PLL Mode + * When PLL is bypassed, chip assumes constant MCLK of + * 12.288MHz and uses sample rate value to divide this MCLK + * to derive its sys clk. As sys clk has to be 256 * Fs, we + * need to write constant sample rate i.e. 48KHz. + */ + snd_soc_component_write(component, DA9055_SR, DA9055_SR_48000); + } + + if (da9055->mclk_rate && (da9055->mclk_rate != sysclk)) { + /* PLL Mode */ + if (!da9055->master) { + /* PLL slave mode, enable PLL and also SRM */ + snd_soc_component_update_bits(component, DA9055_PLL_CTRL, + DA9055_PLL_EN | DA9055_PLL_SRM_EN, + DA9055_PLL_EN | DA9055_PLL_SRM_EN); + } else { + /* PLL master mode, only enable PLL */ + snd_soc_component_update_bits(component, DA9055_PLL_CTRL, + DA9055_PLL_EN, DA9055_PLL_EN); + } + } else { + /* Non PLL Mode, disable PLL */ + snd_soc_component_update_bits(component, DA9055_PLL_CTRL, DA9055_PLL_EN, 0); + } + + return 0; +} + +/* Set DAI mode and Format */ +static int da9055_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct da9055_priv *da9055 = snd_soc_component_get_drvdata(component); + u8 aif_clk_mode, aif_ctrl, mode; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* DA9055 in I2S Master Mode */ + mode = 1; + aif_clk_mode = DA9055_AIF_CLK_EN_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* DA9055 in I2S Slave Mode */ + mode = 0; + aif_clk_mode = DA9055_AIF_CLK_EN_SLAVE_MODE; + break; + default: + return -EINVAL; + } + + /* Don't allow change of mode if PLL is enabled */ + if ((snd_soc_component_read32(component, DA9055_PLL_CTRL) & DA9055_PLL_EN) && + (da9055->master != mode)) + return -EINVAL; + + da9055->master = mode; + + /* Only I2S is supported */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aif_ctrl = DA9055_AIF_FORMAT_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif_ctrl = DA9055_AIF_FORMAT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif_ctrl = DA9055_AIF_FORMAT_RIGHT_J; + break; + case SND_SOC_DAIFMT_DSP_A: + aif_ctrl = DA9055_AIF_FORMAT_DSP; + break; + default: + return -EINVAL; + } + + /* By default only 32 BCLK per WCLK is supported */ + aif_clk_mode |= DA9055_AIF_BCLKS_PER_WCLK_32; + + snd_soc_component_update_bits(component, DA9055_AIF_CLK_MODE, + (DA9055_AIF_CLK_MODE_MASK | DA9055_AIF_BCLK_MASK), + aif_clk_mode); + snd_soc_component_update_bits(component, DA9055_AIF_CTRL, DA9055_AIF_FORMAT_MASK, + aif_ctrl); + return 0; +} + +static int da9055_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + snd_soc_component_update_bits(component, DA9055_DAC_L_CTRL, + DA9055_DAC_L_MUTE_EN, DA9055_DAC_L_MUTE_EN); + snd_soc_component_update_bits(component, DA9055_DAC_R_CTRL, + DA9055_DAC_R_MUTE_EN, DA9055_DAC_R_MUTE_EN); + } else { + snd_soc_component_update_bits(component, DA9055_DAC_L_CTRL, + DA9055_DAC_L_MUTE_EN, 0); + snd_soc_component_update_bits(component, DA9055_DAC_R_CTRL, + DA9055_DAC_R_MUTE_EN, 0); + } + + return 0; +} + +#define DA9055_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int da9055_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct da9055_priv *da9055 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case DA9055_CLKSRC_MCLK: + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 13000000: + case 13500000: + case 14400000: + case 19200000: + case 19680000: + case 19800000: + da9055->mclk_rate = freq; + return 0; + default: + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; + } + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } +} + +/* + * da9055_set_dai_pll : Configure the codec PLL + * @param codec_dai : Pointer to codec DAI + * @param pll_id : da9055 has only one pll, so pll_id is always zero + * @param fref : Input MCLK frequency + * @param fout : FsDM value + * @return int : Zero for success, negative error code for error + * + * Note: Supported PLL input frequencies are 11.2896MHz, 12MHz, 12.288MHz, + * 13MHz, 13.5MHz, 14.4MHz, 19.2MHz, 19.6MHz and 19.8MHz + */ +static int da9055_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int fref, unsigned int fout) +{ + struct snd_soc_component *component = codec_dai->component; + struct da9055_priv *da9055 = snd_soc_component_get_drvdata(component); + + u8 pll_frac_top, pll_frac_bot, pll_integer, cnt; + + /* Disable PLL before setting the divisors */ + snd_soc_component_update_bits(component, DA9055_PLL_CTRL, DA9055_PLL_EN, 0); + + /* In slave mode, there is only one set of divisors */ + if (!da9055->master && (fout != 2822400)) + goto pll_err; + + /* Search pll div array for correct divisors */ + for (cnt = 0; cnt < ARRAY_SIZE(da9055_pll_div); cnt++) { + /* Check fref, mode and fout */ + if ((fref == da9055_pll_div[cnt].fref) && + (da9055->master == da9055_pll_div[cnt].mode) && + (fout == da9055_pll_div[cnt].fout)) { + /* All match, pick up divisors */ + pll_frac_top = da9055_pll_div[cnt].frac_top; + pll_frac_bot = da9055_pll_div[cnt].frac_bot; + pll_integer = da9055_pll_div[cnt].integer; + break; + } + } + if (cnt >= ARRAY_SIZE(da9055_pll_div)) + goto pll_err; + + /* Write PLL dividers */ + snd_soc_component_write(component, DA9055_PLL_FRAC_TOP, pll_frac_top); + snd_soc_component_write(component, DA9055_PLL_FRAC_BOT, pll_frac_bot); + snd_soc_component_write(component, DA9055_PLL_INTEGER, pll_integer); + + return 0; +pll_err: + dev_err(codec_dai->dev, "Error in setting up PLL\n"); + return -EINVAL; +} + +/* DAI operations */ +static const struct snd_soc_dai_ops da9055_dai_ops = { + .hw_params = da9055_hw_params, + .set_fmt = da9055_set_dai_fmt, + .set_sysclk = da9055_set_dai_sysclk, + .set_pll = da9055_set_dai_pll, + .digital_mute = da9055_mute, +}; + +static struct snd_soc_dai_driver da9055_dai = { + .name = "da9055-hifi", + /* Playback Capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA9055_FORMATS, + }, + /* Capture Capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA9055_FORMATS, + }, + .ops = &da9055_dai_ops, + .symmetric_rates = 1, +}; + +static int da9055_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Enable VMID reference & master bias */ + snd_soc_component_update_bits(component, DA9055_REFERENCES, + DA9055_VMID_EN | DA9055_BIAS_EN, + DA9055_VMID_EN | DA9055_BIAS_EN); + } + break; + case SND_SOC_BIAS_OFF: + /* Disable VMID reference & master bias */ + snd_soc_component_update_bits(component, DA9055_REFERENCES, + DA9055_VMID_EN | DA9055_BIAS_EN, 0); + break; + } + return 0; +} + +static int da9055_probe(struct snd_soc_component *component) +{ + struct da9055_priv *da9055 = snd_soc_component_get_drvdata(component); + + /* Enable all Gain Ramps */ + snd_soc_component_update_bits(component, DA9055_AUX_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_AUX_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_MIXIN_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_MIXIN_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_ADC_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_ADC_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_DAC_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_DAC_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_HP_L_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_HP_R_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + snd_soc_component_update_bits(component, DA9055_LINE_CTRL, + DA9055_GAIN_RAMPING_EN, DA9055_GAIN_RAMPING_EN); + + /* + * There are two separate control bits for input and output mixers. + * One to enable corresponding amplifier and other to enable its + * output. As amplifier bits are related to power control, they are + * being managed by DAPM while other (non power related) bits are + * enabled here + */ + snd_soc_component_update_bits(component, DA9055_MIXIN_L_CTRL, + DA9055_MIXIN_L_MIX_EN, DA9055_MIXIN_L_MIX_EN); + snd_soc_component_update_bits(component, DA9055_MIXIN_R_CTRL, + DA9055_MIXIN_R_MIX_EN, DA9055_MIXIN_R_MIX_EN); + + snd_soc_component_update_bits(component, DA9055_MIXOUT_L_CTRL, + DA9055_MIXOUT_L_MIX_EN, DA9055_MIXOUT_L_MIX_EN); + snd_soc_component_update_bits(component, DA9055_MIXOUT_R_CTRL, + DA9055_MIXOUT_R_MIX_EN, DA9055_MIXOUT_R_MIX_EN); + + /* Set this as per your system configuration */ + snd_soc_component_write(component, DA9055_PLL_CTRL, DA9055_PLL_INDIV_10_20_MHZ); + + /* Set platform data values */ + if (da9055->pdata) { + /* set mic bias source */ + if (da9055->pdata->micbias_source) { + snd_soc_component_update_bits(component, DA9055_MIXIN_R_SELECT, + DA9055_MICBIAS2_EN, + DA9055_MICBIAS2_EN); + } else { + snd_soc_component_update_bits(component, DA9055_MIXIN_R_SELECT, + DA9055_MICBIAS2_EN, 0); + } + /* set mic bias voltage */ + switch (da9055->pdata->micbias) { + case DA9055_MICBIAS_2_2V: + case DA9055_MICBIAS_2_1V: + case DA9055_MICBIAS_1_8V: + case DA9055_MICBIAS_1_6V: + snd_soc_component_update_bits(component, DA9055_MIC_CONFIG, + DA9055_MICBIAS_LEVEL_MASK, + (da9055->pdata->micbias) << 4); + break; + } + } + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_da9055 = { + .probe = da9055_probe, + .set_bias_level = da9055_set_bias_level, + .controls = da9055_snd_controls, + .num_controls = ARRAY_SIZE(da9055_snd_controls), + .dapm_widgets = da9055_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(da9055_dapm_widgets), + .dapm_routes = da9055_audio_map, + .num_dapm_routes = ARRAY_SIZE(da9055_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config da9055_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .reg_defaults = da9055_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(da9055_reg_defaults), + .volatile_reg = da9055_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int da9055_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da9055_priv *da9055; + struct da9055_platform_data *pdata = dev_get_platdata(&i2c->dev); + int ret; + + da9055 = devm_kzalloc(&i2c->dev, sizeof(struct da9055_priv), + GFP_KERNEL); + if (!da9055) + return -ENOMEM; + + if (pdata) + da9055->pdata = pdata; + + i2c_set_clientdata(i2c, da9055); + + da9055->regmap = devm_regmap_init_i2c(i2c, &da9055_regmap_config); + if (IS_ERR(da9055->regmap)) { + ret = PTR_ERR(da9055->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_da9055, &da9055_dai, 1); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register da9055 component: %d\n", + ret); + } + return ret; +} + +/* + * DO NOT change the device Ids. The naming is intentionally specific as both + * the CODEC and PMIC parts of this chip are instantiated separately as I2C + * devices (both have configurable I2C addresses, and are to all intents and + * purposes separate). As a result there are specific DA9055 Ids for CODEC + * and PMIC, which must be different to operate together. + */ +static const struct i2c_device_id da9055_i2c_id[] = { + { "da9055-codec", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da9055_i2c_id); + +static const struct of_device_id da9055_of_match[] = { + { .compatible = "dlg,da9055-codec", }, + { } +}; +MODULE_DEVICE_TABLE(of, da9055_of_match); + +/* I2C codec control layer */ +static struct i2c_driver da9055_i2c_driver = { + .driver = { + .name = "da9055-codec", + .of_match_table = of_match_ptr(da9055_of_match), + }, + .probe = da9055_i2c_probe, + .id_table = da9055_i2c_id, +}; + +module_i2c_driver(da9055_i2c_driver); + +MODULE_DESCRIPTION("ASoC DA9055 Codec driver"); +MODULE_AUTHOR("David Chen, Ashish Chavan"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/dmic.c b/sound/soc/codecs/dmic.c new file mode 100644 index 000000000..8c4926df9 --- /dev/null +++ b/sound/soc/codecs/dmic.c @@ -0,0 +1,164 @@ +/* + * dmic.c -- SoC audio for Generic Digital MICs + * + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dmic { + struct gpio_desc *gpio_en; + int wakeup_delay; +}; + +static int dmic_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct dmic *dmic = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (dmic->gpio_en) + gpiod_set_value(dmic->gpio_en, 1); + + if (dmic->wakeup_delay) + msleep(dmic->wakeup_delay); + break; + case SND_SOC_DAPM_POST_PMD: + if (dmic->gpio_en) + gpiod_set_value(dmic->gpio_en, 0); + break; + } + + return 0; +} + +static struct snd_soc_dai_driver dmic_dai = { + .name = "dmic-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S32_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static int dmic_component_probe(struct snd_soc_component *component) +{ + struct dmic *dmic; + + dmic = devm_kzalloc(component->dev, sizeof(*dmic), GFP_KERNEL); + if (!dmic) + return -ENOMEM; + + dmic->gpio_en = devm_gpiod_get_optional(component->dev, + "dmicen", GPIOD_OUT_LOW); + if (IS_ERR(dmic->gpio_en)) + return PTR_ERR(dmic->gpio_en); + + device_property_read_u32(component->dev, "wakeup-delay-ms", + &dmic->wakeup_delay); + + snd_soc_component_set_drvdata(component, dmic); + + return 0; +} + +static const struct snd_soc_dapm_widget dmic_dapm_widgets[] = { + SND_SOC_DAPM_AIF_OUT_E("DMIC AIF", "Capture", 0, + SND_SOC_NOPM, 0, 0, dmic_aif_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_INPUT("DMic"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"DMIC AIF", NULL, "DMic"}, +}; + +static const struct snd_soc_component_driver soc_dmic = { + .probe = dmic_component_probe, + .dapm_widgets = dmic_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dmic_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int dmic_dev_probe(struct platform_device *pdev) +{ + int err; + u32 chans; + struct snd_soc_dai_driver *dai_drv = &dmic_dai; + + if (pdev->dev.of_node) { + err = of_property_read_u32(pdev->dev.of_node, "num-channels", &chans); + if (err && (err != -EINVAL)) + return err; + + if (!err) { + if (chans < 1 || chans > 8) + return -EINVAL; + + dai_drv = devm_kzalloc(&pdev->dev, sizeof(*dai_drv), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + + memcpy(dai_drv, &dmic_dai, sizeof(*dai_drv)); + dai_drv->capture.channels_max = chans; + } + } + + return devm_snd_soc_register_component(&pdev->dev, + &soc_dmic, dai_drv, 1); +} + +MODULE_ALIAS("platform:dmic-codec"); + +static const struct of_device_id dmic_dev_match[] = { + {.compatible = "dmic-codec"}, + {} +}; + +static struct platform_driver dmic_driver = { + .driver = { + .name = "dmic-codec", + .of_match_table = dmic_dev_match, + }, + .probe = dmic_dev_probe, +}; + +module_platform_driver(dmic_driver); + +MODULE_DESCRIPTION("Generic DMIC driver"); +MODULE_AUTHOR("Liam Girdwood "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es7134.c b/sound/soc/codecs/es7134.c new file mode 100644 index 000000000..6d7bca7b7 --- /dev/null +++ b/sound/soc/codecs/es7134.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Jerome Brunet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 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, see . + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#include +#include +#include + +/* + * The everest 7134 is a very simple DA converter with no register + */ + +struct es7134_clock_mode { + unsigned int rate_min; + unsigned int rate_max; + unsigned int *mclk_fs; + unsigned int mclk_fs_num; +}; + +struct es7134_chip { + struct snd_soc_dai_driver *dai_drv; + const struct es7134_clock_mode *modes; + unsigned int mode_num; + const struct snd_soc_dapm_widget *extra_widgets; + unsigned int extra_widget_num; + const struct snd_soc_dapm_route *extra_routes; + unsigned int extra_route_num; +}; + +struct es7134_data { + unsigned int mclk; + const struct es7134_chip *chip; +}; + +static int es7134_check_mclk(struct snd_soc_dai *dai, + struct es7134_data *priv, + unsigned int rate) +{ + unsigned int mfs = priv->mclk / rate; + int i, j; + + for (i = 0; i < priv->chip->mode_num; i++) { + const struct es7134_clock_mode *mode = &priv->chip->modes[i]; + + if (rate < mode->rate_min || rate > mode->rate_max) + continue; + + for (j = 0; j < mode->mclk_fs_num; j++) { + if (mode->mclk_fs[j] == mfs) + return 0; + } + + dev_err(dai->dev, "unsupported mclk_fs %u for rate %u\n", + mfs, rate); + return -EINVAL; + } + + /* should not happen */ + dev_err(dai->dev, "unsupported rate: %u\n", rate); + return -EINVAL; +} + +static int es7134_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct es7134_data *priv = snd_soc_dai_get_drvdata(dai); + + /* mclk has not been provided, assume it is OK */ + if (!priv->mclk) + return 0; + + return es7134_check_mclk(dai, priv, params_rate(params)); +} + +static int es7134_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct es7134_data *priv = snd_soc_dai_get_drvdata(dai); + + if (dir == SND_SOC_CLOCK_IN && clk_id == 0) { + priv->mclk = freq; + return 0; + } + + return -ENOTSUPP; +} + +static int es7134_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK | + SND_SOC_DAIFMT_MASTER_MASK); + + if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS)) { + dev_err(codec_dai->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + return 0; +} + +static int es7134_component_probe(struct snd_soc_component *c) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(c); + struct es7134_data *priv = snd_soc_component_get_drvdata(c); + const struct es7134_chip *chip = priv->chip; + int ret; + + if (chip->extra_widget_num) { + ret = snd_soc_dapm_new_controls(dapm, chip->extra_widgets, + chip->extra_widget_num); + if (ret) { + dev_err(c->dev, "failed to add extra widgets\n"); + return ret; + } + } + + if (chip->extra_route_num) { + ret = snd_soc_dapm_add_routes(dapm, chip->extra_routes, + chip->extra_route_num); + if (ret) { + dev_err(c->dev, "failed to add extra routes\n"); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_dai_ops es7134_dai_ops = { + .set_fmt = es7134_set_fmt, + .hw_params = es7134_hw_params, + .set_sysclk = es7134_set_sysclk, +}; + +static struct snd_soc_dai_driver es7134_dai = { + .name = "es7134-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &es7134_dai_ops, +}; + +static const struct es7134_clock_mode es7134_modes[] = { + { + /* Single speed mode */ + .rate_min = 8000, + .rate_max = 50000, + .mclk_fs = (unsigned int[]) { 256, 384, 512, 768, 1024 }, + .mclk_fs_num = 5, + }, { + /* Double speed mode */ + .rate_min = 84000, + .rate_max = 100000, + .mclk_fs = (unsigned int[]) { 128, 192, 256, 384, 512 }, + .mclk_fs_num = 5, + }, { + /* Quad speed mode */ + .rate_min = 167000, + .rate_max = 192000, + .mclk_fs = (unsigned int[]) { 128, 192, 256 }, + .mclk_fs_num = 3, + }, +}; + +/* Digital I/O are also supplied by VDD on the es7134 */ +static const struct snd_soc_dapm_route es7134_extra_routes[] = { + { "Playback", NULL, "VDD", } +}; + +static const struct es7134_chip es7134_chip = { + .dai_drv = &es7134_dai, + .modes = es7134_modes, + .mode_num = ARRAY_SIZE(es7134_modes), + .extra_routes = es7134_extra_routes, + .extra_route_num = ARRAY_SIZE(es7134_extra_routes), +}; + +static const struct snd_soc_dapm_widget es7134_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("VDD", 0, 0), +}; + +static const struct snd_soc_dapm_route es7134_dapm_routes[] = { + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, + { "DAC", NULL, "VDD" }, +}; + +static const struct snd_soc_component_driver es7134_component_driver = { + .probe = es7134_component_probe, + .dapm_widgets = es7134_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es7134_dapm_widgets), + .dapm_routes = es7134_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es7134_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver es7154_dai = { + .name = "es7154-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &es7134_dai_ops, +}; + +static const struct es7134_clock_mode es7154_modes[] = { + { + /* Single speed mode */ + .rate_min = 8000, + .rate_max = 50000, + .mclk_fs = (unsigned int[]) { 32, 64, 128, 192, 256, + 384, 512, 768, 1024 }, + .mclk_fs_num = 9, + }, { + /* Double speed mode */ + .rate_min = 84000, + .rate_max = 100000, + .mclk_fs = (unsigned int[]) { 128, 192, 256, 384, 512, + 768, 1024}, + .mclk_fs_num = 7, + } +}; + +/* Es7154 has a separate supply for digital I/O */ +static const struct snd_soc_dapm_widget es7154_extra_widgets[] = { + SND_SOC_DAPM_REGULATOR_SUPPLY("PVDD", 0, 0), +}; + +static const struct snd_soc_dapm_route es7154_extra_routes[] = { + { "Playback", NULL, "PVDD", } +}; + +static const struct es7134_chip es7154_chip = { + .dai_drv = &es7154_dai, + .modes = es7154_modes, + .mode_num = ARRAY_SIZE(es7154_modes), + .extra_routes = es7154_extra_routes, + .extra_route_num = ARRAY_SIZE(es7154_extra_routes), + .extra_widgets = es7154_extra_widgets, + .extra_widget_num = ARRAY_SIZE(es7154_extra_widgets), +}; + +static int es7134_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct es7134_data *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->chip = of_device_get_match_data(dev); + if (!priv->chip) { + dev_err(dev, "failed to match device\n"); + return -ENODEV; + } + + return devm_snd_soc_register_component(&pdev->dev, + &es7134_component_driver, + priv->chip->dai_drv, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id es7134_ids[] = { + { .compatible = "everest,es7134", .data = &es7134_chip }, + { .compatible = "everest,es7144", .data = &es7134_chip }, + { .compatible = "everest,es7154", .data = &es7154_chip }, + { } +}; +MODULE_DEVICE_TABLE(of, es7134_ids); +#endif + +static struct platform_driver es7134_driver = { + .driver = { + .name = "es7134", + .of_match_table = of_match_ptr(es7134_ids), + }, + .probe = es7134_probe, +}; + +module_platform_driver(es7134_driver); + +MODULE_DESCRIPTION("ASoC ES7134 audio codec driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es7241.c b/sound/soc/codecs/es7241.c new file mode 100644 index 000000000..87991bd4a --- /dev/null +++ b/sound/soc/codecs/es7241.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include + +struct es7241_clock_mode { + unsigned int rate_min; + unsigned int rate_max; + unsigned int *slv_mfs; + unsigned int slv_mfs_num; + unsigned int mst_mfs; + unsigned int mst_m0:1; + unsigned int mst_m1:1; +}; + +struct es7241_chip { + const struct es7241_clock_mode *modes; + unsigned int mode_num; +}; + +struct es7241_data { + struct gpio_desc *reset; + struct gpio_desc *m0; + struct gpio_desc *m1; + unsigned int fmt; + unsigned int mclk; + bool is_slave; + const struct es7241_chip *chip; +}; + +static void es7241_set_mode(struct es7241_data *priv, int m0, int m1) +{ + /* put the device in reset */ + gpiod_set_value_cansleep(priv->reset, 0); + + /* set the mode */ + gpiod_set_value_cansleep(priv->m0, m0); + gpiod_set_value_cansleep(priv->m1, m1); + + /* take the device out of reset - datasheet does not specify a delay */ + gpiod_set_value_cansleep(priv->reset, 1); +} + +static int es7241_set_slave_mode(struct es7241_data *priv, + const struct es7241_clock_mode *mode, + unsigned int mfs) +{ + int j; + + if (!mfs) + goto out_ok; + + for (j = 0; j < mode->slv_mfs_num; j++) { + if (mode->slv_mfs[j] == mfs) + goto out_ok; + } + + return -EINVAL; + +out_ok: + es7241_set_mode(priv, 1, 1); + return 0; +} + +static int es7241_set_master_mode(struct es7241_data *priv, + const struct es7241_clock_mode *mode, + unsigned int mfs) +{ + /* + * We can't really set clock ratio, if the mclk/lrclk is different + * from what we provide, then error out + */ + if (mfs && mfs != mode->mst_mfs) + return -EINVAL; + + es7241_set_mode(priv, mode->mst_m0, mode->mst_m1); + + return 0; +} + +static int es7241_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct es7241_data *priv = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + unsigned int mfs = priv->mclk / rate; + int i; + + for (i = 0; i < priv->chip->mode_num; i++) { + const struct es7241_clock_mode *mode = &priv->chip->modes[i]; + + if (rate < mode->rate_min || rate >= mode->rate_max) + continue; + + if (priv->is_slave) + return es7241_set_slave_mode(priv, mode, mfs); + else + return es7241_set_master_mode(priv, mode, mfs); + } + + /* should not happen */ + dev_err(dai->dev, "unsupported rate: %u\n", rate); + return -EINVAL; +} + +static int es7241_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct es7241_data *priv = snd_soc_dai_get_drvdata(dai); + + if (dir == SND_SOC_CLOCK_IN && clk_id == 0) { + priv->mclk = freq; + return 0; + } + + return -ENOTSUPP; +} + +static int es7241_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct es7241_data *priv = snd_soc_dai_get_drvdata(dai); + + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) { + dev_err(dai->dev, "Unsupported dai clock inversion\n"); + return -EINVAL; + } + + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != priv->fmt) { + dev_err(dai->dev, "Invalid dai format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + priv->is_slave = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + priv->is_slave = false; + break; + + default: + dev_err(dai->dev, "Unsupported clock configuration\n"); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops es7241_dai_ops = { + .set_fmt = es7241_set_fmt, + .hw_params = es7241_hw_params, + .set_sysclk = es7241_set_sysclk, +}; + +static struct snd_soc_dai_driver es7241_dai = { + .name = "es7241-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &es7241_dai_ops, +}; + +static const struct es7241_clock_mode es7241_modes[] = { + { + /* Single speed mode */ + .rate_min = 8000, + .rate_max = 50000, + .slv_mfs = (unsigned int[]) { 256, 384, 512, 768, 1024 }, + .slv_mfs_num = 5, + .mst_mfs = 256, + .mst_m0 = 0, + .mst_m1 = 0, + }, { + /* Double speed mode */ + .rate_min = 50000, + .rate_max = 100000, + .slv_mfs = (unsigned int[]) { 128, 192 }, + .slv_mfs_num = 2, + .mst_mfs = 128, + .mst_m0 = 1, + .mst_m1 = 0, + }, { + /* Quad speed mode */ + .rate_min = 100000, + .rate_max = 200000, + .slv_mfs = (unsigned int[]) { 64 }, + .slv_mfs_num = 1, + .mst_mfs = 64, + .mst_m0 = 0, + .mst_m1 = 1, + }, +}; + +static const struct es7241_chip es7241_chip = { + .modes = es7241_modes, + .mode_num = ARRAY_SIZE(es7241_modes), +}; + +static const struct snd_soc_dapm_widget es7241_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("AINL"), + SND_SOC_DAPM_INPUT("AINR"), + SND_SOC_DAPM_DAC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("VDDP", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("VDDD", 0, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("VDDA", 0, 0), +}; + +static const struct snd_soc_dapm_route es7241_dapm_routes[] = { + { "ADC", NULL, "AINL", }, + { "ADC", NULL, "AINR", }, + { "ADC", NULL, "VDDA", }, + { "Capture", NULL, "VDDP", }, + { "Capture", NULL, "VDDD", }, +}; + +static const struct snd_soc_component_driver es7241_component_driver = { + .dapm_widgets = es7241_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es7241_dapm_widgets), + .dapm_routes = es7241_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es7241_dapm_routes), + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static void es7241_parse_fmt(struct device *dev, struct es7241_data *priv) +{ + bool is_leftj; + + /* + * The format is given by a pull resistor on the SDOUT pin: + * pull-up for i2s, pull-down for left justified. + */ + is_leftj = of_property_read_bool(dev->of_node, + "everest,sdout-pull-down"); + if (is_leftj) + priv->fmt = SND_SOC_DAIFMT_LEFT_J; + else + priv->fmt = SND_SOC_DAIFMT_I2S; +} + +static int es7241_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct es7241_data *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->chip = of_device_get_match_data(dev); + if (!priv->chip) { + dev_err(dev, "failed to match device\n"); + return -ENODEV; + } + + es7241_parse_fmt(dev, priv); + + priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(priv->reset)) { + err = PTR_ERR(priv->reset); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'reset' gpio: %d", err); + return err; + } + + priv->m0 = devm_gpiod_get_optional(dev, "m0", GPIOD_OUT_LOW); + if (IS_ERR(priv->m0)) { + err = PTR_ERR(priv->m0); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'm0' gpio: %d", err); + return err; + } + + priv->m1 = devm_gpiod_get_optional(dev, "m1", GPIOD_OUT_LOW); + if (IS_ERR(priv->m1)) { + err = PTR_ERR(priv->m1); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'm1' gpio: %d", err); + return err; + } + + return devm_snd_soc_register_component(&pdev->dev, + &es7241_component_driver, + &es7241_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id es7241_ids[] = { + { .compatible = "everest,es7241", .data = &es7241_chip }, + { } +}; +MODULE_DEVICE_TABLE(of, es7241_ids); +#endif + +static struct platform_driver es7241_driver = { + .driver = { + .name = "es7241", + .of_match_table = of_match_ptr(es7241_ids), + }, + .probe = es7241_probe, +}; + +module_platform_driver(es7241_driver); + +MODULE_DESCRIPTION("ASoC ES7241 audio codec driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c new file mode 100644 index 000000000..57130edaf --- /dev/null +++ b/sound/soc/codecs/es8316.c @@ -0,0 +1,627 @@ +/* + * es8316.c -- es8316 ALSA SoC audio driver + * Copyright Everest Semiconductor Co.,Ltd + * + * Authors: David Yang , + * Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "es8316.h" + +/* In slave mode at single speed, the codec is documented as accepting 5 + * MCLK/LRCK ratios, but we also add ratio 400, which is commonly used on + * Intel Cherry Trail platforms (19.2MHz MCLK, 48kHz LRCK). + */ +#define NR_SUPPORTED_MCLK_LRCK_RATIOS 6 +static const unsigned int supported_mclk_lrck_ratios[] = { + 256, 384, 400, 512, 768, 1024 +}; + +struct es8316_priv { + unsigned int sysclk; + unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS]; + struct snd_pcm_hw_constraint_list sysclk_constraints; +}; + +/* + * ES8316 controls + */ +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(dac_vol_tlv, -9600, 50, 1); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_vol_tlv, -9600, 50, 1); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(alc_max_gain_tlv, -650, 150, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(alc_min_gain_tlv, -1200, 150, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(alc_target_tlv, -1650, 150, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(hpmixer_gain_tlv, + 0, 4, TLV_DB_SCALE_ITEM(-1200, 150, 0), + 8, 11, TLV_DB_SCALE_ITEM(-450, 150, 0), +); + +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(adc_pga_gain_tlv, + 0, 0, TLV_DB_SCALE_ITEM(-350, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(0, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(250, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(450, 0, 0), + 4, 7, TLV_DB_SCALE_ITEM(700, 300, 0), + 8, 10, TLV_DB_SCALE_ITEM(1800, 300, 0), +); + +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(hpout_vol_tlv, + 0, 0, TLV_DB_SCALE_ITEM(-4800, 0, 0), + 1, 3, TLV_DB_SCALE_ITEM(-2400, 1200, 0), +); + +static const char * const ng_type_txt[] = + { "Constant PGA Gain", "Mute ADC Output" }; +static const struct soc_enum ng_type = + SOC_ENUM_SINGLE(ES8316_ADC_ALC_NG, 6, 2, ng_type_txt); + +static const char * const adcpol_txt[] = { "Normal", "Invert" }; +static const struct soc_enum adcpol = + SOC_ENUM_SINGLE(ES8316_ADC_MUTE, 1, 2, adcpol_txt); +static const char *const dacpol_txt[] = + { "Normal", "R Invert", "L Invert", "L + R Invert" }; +static const struct soc_enum dacpol = + SOC_ENUM_SINGLE(ES8316_DAC_SET1, 0, 4, dacpol_txt); + +static const struct snd_kcontrol_new es8316_snd_controls[] = { + SOC_DOUBLE_TLV("Headphone Playback Volume", ES8316_CPHP_ICAL_VOL, + 4, 0, 3, 1, hpout_vol_tlv), + SOC_DOUBLE_TLV("Headphone Mixer Volume", ES8316_HPMIX_VOL, + 0, 4, 11, 0, hpmixer_gain_tlv), + + SOC_ENUM("Playback Polarity", dacpol), + SOC_DOUBLE_R_TLV("DAC Playback Volume", ES8316_DAC_VOLL, + ES8316_DAC_VOLR, 0, 0xc0, 1, dac_vol_tlv), + SOC_SINGLE("DAC Soft Ramp Switch", ES8316_DAC_SET1, 4, 1, 1), + SOC_SINGLE("DAC Soft Ramp Rate", ES8316_DAC_SET1, 2, 4, 0), + SOC_SINGLE("DAC Notch Filter Switch", ES8316_DAC_SET2, 6, 1, 0), + SOC_SINGLE("DAC Double Fs Switch", ES8316_DAC_SET2, 7, 1, 0), + SOC_SINGLE("DAC Stereo Enhancement", ES8316_DAC_SET3, 0, 7, 0), + + SOC_ENUM("Capture Polarity", adcpol), + SOC_SINGLE("Mic Boost Switch", ES8316_ADC_D2SEPGA, 0, 1, 0), + SOC_SINGLE_TLV("ADC Capture Volume", ES8316_ADC_VOLUME, + 0, 0xc0, 1, adc_vol_tlv), + SOC_SINGLE_TLV("ADC PGA Gain Volume", ES8316_ADC_PGAGAIN, + 4, 10, 0, adc_pga_gain_tlv), + SOC_SINGLE("ADC Soft Ramp Switch", ES8316_ADC_MUTE, 4, 1, 0), + SOC_SINGLE("ADC Double Fs Switch", ES8316_ADC_DMIC, 4, 1, 0), + + SOC_SINGLE("ALC Capture Switch", ES8316_ADC_ALC1, 6, 1, 0), + SOC_SINGLE_TLV("ALC Capture Max Volume", ES8316_ADC_ALC1, 0, 28, 0, + alc_max_gain_tlv), + SOC_SINGLE_TLV("ALC Capture Min Volume", ES8316_ADC_ALC2, 0, 28, 0, + alc_min_gain_tlv), + SOC_SINGLE_TLV("ALC Capture Target Volume", ES8316_ADC_ALC3, 4, 10, 0, + alc_target_tlv), + SOC_SINGLE("ALC Capture Hold Time", ES8316_ADC_ALC3, 0, 10, 0), + SOC_SINGLE("ALC Capture Decay Time", ES8316_ADC_ALC4, 4, 10, 0), + SOC_SINGLE("ALC Capture Attack Time", ES8316_ADC_ALC4, 0, 10, 0), + SOC_SINGLE("ALC Capture Noise Gate Switch", ES8316_ADC_ALC_NG, + 5, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Threshold", ES8316_ADC_ALC_NG, + 0, 31, 0), + SOC_ENUM("ALC Capture Noise Gate Type", ng_type), +}; + +/* Analog Input Mux */ +static const char * const es8316_analog_in_txt[] = { + "lin1-rin1", + "lin2-rin2", + "lin1-rin1 with 20db Boost", + "lin2-rin2 with 20db Boost" +}; +static const unsigned int es8316_analog_in_values[] = { 0, 1, 2, 3 }; +static const struct soc_enum es8316_analog_input_enum = + SOC_VALUE_ENUM_SINGLE(ES8316_ADC_PDN_LINSEL, 4, 3, + ARRAY_SIZE(es8316_analog_in_txt), + es8316_analog_in_txt, + es8316_analog_in_values); +static const struct snd_kcontrol_new es8316_analog_in_mux_controls = + SOC_DAPM_ENUM("Route", es8316_analog_input_enum); + +static const char * const es8316_dmic_txt[] = { + "dmic disable", + "dmic data at high level", + "dmic data at low level", +}; +static const unsigned int es8316_dmic_values[] = { 0, 1, 2 }; +static const struct soc_enum es8316_dmic_src_enum = + SOC_VALUE_ENUM_SINGLE(ES8316_ADC_DMIC, 0, 3, + ARRAY_SIZE(es8316_dmic_txt), + es8316_dmic_txt, + es8316_dmic_values); +static const struct snd_kcontrol_new es8316_dmic_src_controls = + SOC_DAPM_ENUM("Route", es8316_dmic_src_enum); + +/* hp mixer mux */ +static const char * const es8316_hpmux_texts[] = { + "lin1-rin1", + "lin2-rin2", + "lin-rin with Boost", + "lin-rin with Boost and PGA" +}; + +static const unsigned int es8316_hpmux_values[] = { 0, 1, 2, 3 }; + +static SOC_ENUM_SINGLE_DECL(es8316_left_hpmux_enum, ES8316_HPMIX_SEL, + 4, es8316_hpmux_texts); + +static const struct snd_kcontrol_new es8316_left_hpmux_controls = + SOC_DAPM_ENUM("Route", es8316_left_hpmux_enum); + +static SOC_ENUM_SINGLE_DECL(es8316_right_hpmux_enum, ES8316_HPMIX_SEL, + 0, es8316_hpmux_texts); + +static const struct snd_kcontrol_new es8316_right_hpmux_controls = + SOC_DAPM_ENUM("Route", es8316_right_hpmux_enum); + +/* headphone Output Mixer */ +static const struct snd_kcontrol_new es8316_out_left_mix[] = { + SOC_DAPM_SINGLE("LLIN Switch", ES8316_HPMIX_SWITCH, 6, 1, 0), + SOC_DAPM_SINGLE("Left DAC Switch", ES8316_HPMIX_SWITCH, 7, 1, 0), +}; +static const struct snd_kcontrol_new es8316_out_right_mix[] = { + SOC_DAPM_SINGLE("RLIN Switch", ES8316_HPMIX_SWITCH, 2, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", ES8316_HPMIX_SWITCH, 3, 1, 0), +}; + +/* DAC data source mux */ +static const char * const es8316_dacsrc_texts[] = { + "LDATA TO LDAC, RDATA TO RDAC", + "LDATA TO LDAC, LDATA TO RDAC", + "RDATA TO LDAC, RDATA TO RDAC", + "RDATA TO LDAC, LDATA TO RDAC", +}; + +static const unsigned int es8316_dacsrc_values[] = { 0, 1, 2, 3 }; + +static SOC_ENUM_SINGLE_DECL(es8316_dacsrc_mux_enum, ES8316_DAC_SET1, + 6, es8316_dacsrc_texts); + +static const struct snd_kcontrol_new es8316_dacsrc_mux_controls = + SOC_DAPM_ENUM("Route", es8316_dacsrc_mux_enum); + +static const struct snd_soc_dapm_widget es8316_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Bias", ES8316_SYS_PDN, 3, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Analog power", ES8316_SYS_PDN, 4, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias", ES8316_SYS_PDN, 5, 1, NULL, 0), + + SND_SOC_DAPM_INPUT("DMIC"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + + /* Input Mux */ + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &es8316_analog_in_mux_controls), + + SND_SOC_DAPM_SUPPLY("ADC Vref", ES8316_SYS_PDN, 1, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC bias", ES8316_SYS_PDN, 2, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Clock", ES8316_CLKMGR_CLKSW, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line input PGA", ES8316_ADC_PDN_LINSEL, + 7, 1, NULL, 0), + SND_SOC_DAPM_ADC("Mono ADC", NULL, ES8316_ADC_PDN_LINSEL, 6, 1), + SND_SOC_DAPM_MUX("Digital Mic Mux", SND_SOC_NOPM, 0, 0, + &es8316_dmic_src_controls), + + /* Digital Interface */ + SND_SOC_DAPM_AIF_OUT("I2S OUT", "I2S1 Capture", 1, + ES8316_SERDATA_ADC, 6, 1), + SND_SOC_DAPM_AIF_IN("I2S IN", "I2S1 Playback", 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("DAC Source Mux", SND_SOC_NOPM, 0, 0, + &es8316_dacsrc_mux_controls), + + SND_SOC_DAPM_SUPPLY("DAC Vref", ES8316_SYS_PDN, 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Clock", ES8316_CLKMGR_CLKSW, 2, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", NULL, ES8316_DAC_PDN, 0, 1), + SND_SOC_DAPM_DAC("Left DAC", NULL, ES8316_DAC_PDN, 4, 1), + + /* Headphone Output Side */ + SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, + &es8316_left_hpmux_controls), + SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, + &es8316_right_hpmux_controls), + SND_SOC_DAPM_MIXER("Left Headphone Mixer", ES8316_HPMIX_PDN, + 5, 1, &es8316_out_left_mix[0], + ARRAY_SIZE(es8316_out_left_mix)), + SND_SOC_DAPM_MIXER("Right Headphone Mixer", ES8316_HPMIX_PDN, + 1, 1, &es8316_out_right_mix[0], + ARRAY_SIZE(es8316_out_right_mix)), + SND_SOC_DAPM_PGA("Left Headphone Mixer Out", ES8316_HPMIX_PDN, + 4, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Headphone Mixer Out", ES8316_HPMIX_PDN, + 0, 1, NULL, 0), + + SND_SOC_DAPM_OUT_DRV("Left Headphone Charge Pump", ES8316_CPHP_OUTEN, + 6, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Right Headphone Charge Pump", ES8316_CPHP_OUTEN, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Headphone Charge Pump", ES8316_CPHP_PDN2, + 5, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Headphone Charge Pump Clock", ES8316_CLKMGR_CLKSW, + 4, 0, NULL, 0), + + SND_SOC_DAPM_OUT_DRV("Left Headphone Driver", ES8316_CPHP_OUTEN, + 5, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Right Headphone Driver", ES8316_CPHP_OUTEN, + 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Headphone Out", ES8316_CPHP_PDN1, 2, 1, NULL, 0), + + /* pdn_Lical and pdn_Rical bits are documented as Reserved, but must + * be explicitly unset in order to enable HP output + */ + SND_SOC_DAPM_SUPPLY("Left Headphone ical", ES8316_CPHP_ICAL_VOL, + 7, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right Headphone ical", ES8316_CPHP_ICAL_VOL, + 3, 1, NULL, 0), + + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route es8316_dapm_routes[] = { + /* Recording */ + {"MIC1", NULL, "Mic Bias"}, + {"MIC2", NULL, "Mic Bias"}, + {"MIC1", NULL, "Bias"}, + {"MIC2", NULL, "Bias"}, + {"MIC1", NULL, "Analog power"}, + {"MIC2", NULL, "Analog power"}, + + {"Differential Mux", "lin1-rin1", "MIC1"}, + {"Differential Mux", "lin2-rin2", "MIC2"}, + {"Line input PGA", NULL, "Differential Mux"}, + + {"Mono ADC", NULL, "ADC Clock"}, + {"Mono ADC", NULL, "ADC Vref"}, + {"Mono ADC", NULL, "ADC bias"}, + {"Mono ADC", NULL, "Line input PGA"}, + + /* It's not clear why, but to avoid recording only silence, + * the DAC clock must be running for the ADC to work. + */ + {"Mono ADC", NULL, "DAC Clock"}, + + {"Digital Mic Mux", "dmic disable", "Mono ADC"}, + + {"I2S OUT", NULL, "Digital Mic Mux"}, + + /* Playback */ + {"DAC Source Mux", "LDATA TO LDAC, RDATA TO RDAC", "I2S IN"}, + + {"Left DAC", NULL, "DAC Clock"}, + {"Right DAC", NULL, "DAC Clock"}, + + {"Left DAC", NULL, "DAC Vref"}, + {"Right DAC", NULL, "DAC Vref"}, + + {"Left DAC", NULL, "DAC Source Mux"}, + {"Right DAC", NULL, "DAC Source Mux"}, + + {"Left Headphone Mux", "lin-rin with Boost and PGA", "Line input PGA"}, + {"Right Headphone Mux", "lin-rin with Boost and PGA", "Line input PGA"}, + + {"Left Headphone Mixer", "LLIN Switch", "Left Headphone Mux"}, + {"Left Headphone Mixer", "Left DAC Switch", "Left DAC"}, + + {"Right Headphone Mixer", "RLIN Switch", "Right Headphone Mux"}, + {"Right Headphone Mixer", "Right DAC Switch", "Right DAC"}, + + {"Left Headphone Mixer Out", NULL, "Left Headphone Mixer"}, + {"Right Headphone Mixer Out", NULL, "Right Headphone Mixer"}, + + {"Left Headphone Charge Pump", NULL, "Left Headphone Mixer Out"}, + {"Right Headphone Charge Pump", NULL, "Right Headphone Mixer Out"}, + + {"Left Headphone Charge Pump", NULL, "Headphone Charge Pump"}, + {"Right Headphone Charge Pump", NULL, "Headphone Charge Pump"}, + + {"Left Headphone Charge Pump", NULL, "Headphone Charge Pump Clock"}, + {"Right Headphone Charge Pump", NULL, "Headphone Charge Pump Clock"}, + + {"Left Headphone Driver", NULL, "Left Headphone Charge Pump"}, + {"Right Headphone Driver", NULL, "Right Headphone Charge Pump"}, + + {"HPOL", NULL, "Left Headphone Driver"}, + {"HPOR", NULL, "Right Headphone Driver"}, + + {"HPOL", NULL, "Left Headphone ical"}, + {"HPOR", NULL, "Right Headphone ical"}, + + {"Headphone Out", NULL, "Bias"}, + {"Headphone Out", NULL, "Analog power"}, + {"HPOL", NULL, "Headphone Out"}, + {"HPOR", NULL, "Headphone Out"}, +}; + +static int es8316_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); + int i; + int count = 0; + + es8316->sysclk = freq; + + if (freq == 0) + return 0; + + /* Limit supported sample rates to ones that can be autodetected + * by the codec running in slave mode. + */ + for (i = 0; i < NR_SUPPORTED_MCLK_LRCK_RATIOS; i++) { + const unsigned int ratio = supported_mclk_lrck_ratios[i]; + + if (freq % ratio == 0) + es8316->allowed_rates[count++] = freq / ratio; + } + + es8316->sysclk_constraints.list = es8316->allowed_rates; + es8316->sysclk_constraints.count = count; + + return 0; +} + +static int es8316_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 serdata1 = 0; + u8 serdata2 = 0; + u8 clksw; + u8 mask; + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(component->dev, "Codec driver only supports slave mode\n"); + return -EINVAL; + } + + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) { + dev_err(component->dev, "Codec driver only supports I2S format\n"); + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + serdata1 |= ES8316_SERDATA1_BCLK_INV; + serdata2 |= ES8316_SERDATA2_ADCLRP; + break; + case SND_SOC_DAIFMT_IB_NF: + serdata1 |= ES8316_SERDATA1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + serdata2 |= ES8316_SERDATA2_ADCLRP; + break; + default: + return -EINVAL; + } + + mask = ES8316_SERDATA1_MASTER | ES8316_SERDATA1_BCLK_INV; + snd_soc_component_update_bits(component, ES8316_SERDATA1, mask, serdata1); + + mask = ES8316_SERDATA2_FMT_MASK | ES8316_SERDATA2_ADCLRP; + snd_soc_component_update_bits(component, ES8316_SERDATA_ADC, mask, serdata2); + snd_soc_component_update_bits(component, ES8316_SERDATA_DAC, mask, serdata2); + + /* Enable BCLK and MCLK inputs in slave mode */ + clksw = ES8316_CLKMGR_CLKSW_MCLK_ON | ES8316_CLKMGR_CLKSW_BCLK_ON; + snd_soc_component_update_bits(component, ES8316_CLKMGR_CLKSW, clksw, clksw); + + return 0; +} + +static int es8316_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); + + if (es8316->sysclk == 0) { + dev_err(component->dev, "No sysclk provided\n"); + return -EINVAL; + } + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC. + */ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &es8316->sysclk_constraints); + + return 0; +} + +static int es8316_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); + u8 wordlen = 0; + + if (!es8316->sysclk) { + dev_err(component->dev, "No MCLK configured\n"); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wordlen = ES8316_SERDATA2_LEN_16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + wordlen = ES8316_SERDATA2_LEN_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + wordlen = ES8316_SERDATA2_LEN_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wordlen = ES8316_SERDATA2_LEN_32; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, ES8316_SERDATA_DAC, + ES8316_SERDATA2_LEN_MASK, wordlen); + snd_soc_component_update_bits(component, ES8316_SERDATA_ADC, + ES8316_SERDATA2_LEN_MASK, wordlen); + return 0; +} + +static int es8316_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, ES8316_DAC_SET1, 0x20, + mute ? 0x20 : 0); + return 0; +} + +#define ES8316_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops es8316_ops = { + .startup = es8316_pcm_startup, + .hw_params = es8316_pcm_hw_params, + .set_fmt = es8316_set_dai_fmt, + .set_sysclk = es8316_set_dai_sysclk, + .digital_mute = es8316_mute, +}; + +static struct snd_soc_dai_driver es8316_dai = { + .name = "ES8316 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ES8316_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = ES8316_FORMATS, + }, + .ops = &es8316_ops, + .symmetric_rates = 1, +}; + +static int es8316_probe(struct snd_soc_component *component) +{ + /* Reset codec and enable current state machine */ + snd_soc_component_write(component, ES8316_RESET, 0x3f); + usleep_range(5000, 5500); + snd_soc_component_write(component, ES8316_RESET, ES8316_RESET_CSM_ON); + msleep(30); + + /* + * Documentation is unclear, but this value from the vendor driver is + * needed otherwise audio output is silent. + */ + snd_soc_component_write(component, ES8316_SYS_VMIDSEL, 0xff); + + /* + * Documentation for this register is unclear and incomplete, + * but here is a vendor-provided value that improves volume + * and quality for Intel CHT platforms. + */ + snd_soc_component_write(component, ES8316_CLKMGR_ADCOSR, 0x32); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_es8316 = { + .probe = es8316_probe, + .controls = es8316_snd_controls, + .num_controls = ARRAY_SIZE(es8316_snd_controls), + .dapm_widgets = es8316_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es8316_dapm_widgets), + .dapm_routes = es8316_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es8316_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config es8316_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x53, + .cache_type = REGCACHE_RBTREE, +}; + +static int es8316_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct es8316_priv *es8316; + struct regmap *regmap; + + es8316 = devm_kzalloc(&i2c_client->dev, sizeof(struct es8316_priv), + GFP_KERNEL); + if (es8316 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, es8316); + + regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return devm_snd_soc_register_component(&i2c_client->dev, + &soc_component_dev_es8316, + &es8316_dai, 1); +} + +static const struct i2c_device_id es8316_i2c_id[] = { + {"es8316", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, es8316_i2c_id); + +static const struct of_device_id es8316_of_match[] = { + { .compatible = "everest,es8316", }, + {}, +}; +MODULE_DEVICE_TABLE(of, es8316_of_match); + +static const struct acpi_device_id es8316_acpi_match[] = { + {"ESSX8316", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, es8316_acpi_match); + +static struct i2c_driver es8316_i2c_driver = { + .driver = { + .name = "es8316", + .acpi_match_table = ACPI_PTR(es8316_acpi_match), + .of_match_table = of_match_ptr(es8316_of_match), + }, + .probe = es8316_i2c_probe, + .id_table = es8316_i2c_id, +}; +module_i2c_driver(es8316_i2c_driver); + +MODULE_DESCRIPTION("Everest Semi ES8316 ALSA SoC Codec Driver"); +MODULE_AUTHOR("David Yang "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/es8316.h b/sound/soc/codecs/es8316.h new file mode 100644 index 000000000..6bcdd63ea --- /dev/null +++ b/sound/soc/codecs/es8316.h @@ -0,0 +1,129 @@ +/* + * Copyright Everest Semiconductor Co.,Ltd + * + * Author: David Yang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _ES8316_H +#define _ES8316_H + +/* + * ES8316 register space + */ + +/* Reset Control */ +#define ES8316_RESET 0x00 + +/* Clock Management */ +#define ES8316_CLKMGR_CLKSW 0x01 +#define ES8316_CLKMGR_CLKSEL 0x02 +#define ES8316_CLKMGR_ADCOSR 0x03 +#define ES8316_CLKMGR_ADCDIV1 0x04 +#define ES8316_CLKMGR_ADCDIV2 0x05 +#define ES8316_CLKMGR_DACDIV1 0x06 +#define ES8316_CLKMGR_DACDIV2 0x07 +#define ES8316_CLKMGR_CPDIV 0x08 + +/* Serial Data Port Control */ +#define ES8316_SERDATA1 0x09 +#define ES8316_SERDATA_ADC 0x0a +#define ES8316_SERDATA_DAC 0x0b + +/* System Control */ +#define ES8316_SYS_VMIDSEL 0x0c +#define ES8316_SYS_PDN 0x0d +#define ES8316_SYS_LP1 0x0e +#define ES8316_SYS_LP2 0x0f +#define ES8316_SYS_VMIDLOW 0x10 +#define ES8316_SYS_VSEL 0x11 +#define ES8316_SYS_REF 0x12 + +/* Headphone Mixer */ +#define ES8316_HPMIX_SEL 0x13 +#define ES8316_HPMIX_SWITCH 0x14 +#define ES8316_HPMIX_PDN 0x15 +#define ES8316_HPMIX_VOL 0x16 + +/* Charge Pump Headphone driver */ +#define ES8316_CPHP_OUTEN 0x17 +#define ES8316_CPHP_ICAL_VOL 0x18 +#define ES8316_CPHP_PDN1 0x19 +#define ES8316_CPHP_PDN2 0x1a +#define ES8316_CPHP_LDOCTL 0x1b + +/* Calibration */ +#define ES8316_CAL_TYPE 0x1c +#define ES8316_CAL_SET 0x1d +#define ES8316_CAL_HPLIV 0x1e +#define ES8316_CAL_HPRIV 0x1f +#define ES8316_CAL_HPLMV 0x20 +#define ES8316_CAL_HPRMV 0x21 + +/* ADC Control */ +#define ES8316_ADC_PDN_LINSEL 0x22 +#define ES8316_ADC_PGAGAIN 0x23 +#define ES8316_ADC_D2SEPGA 0x24 +#define ES8316_ADC_DMIC 0x25 +#define ES8316_ADC_MUTE 0x26 +#define ES8316_ADC_VOLUME 0x27 +#define ES8316_ADC_ALC1 0x29 +#define ES8316_ADC_ALC2 0x2a +#define ES8316_ADC_ALC3 0x2b +#define ES8316_ADC_ALC4 0x2c +#define ES8316_ADC_ALC5 0x2d +#define ES8316_ADC_ALC_NG 0x2e + +/* DAC Control */ +#define ES8316_DAC_PDN 0x2f +#define ES8316_DAC_SET1 0x30 +#define ES8316_DAC_SET2 0x31 +#define ES8316_DAC_SET3 0x32 +#define ES8316_DAC_VOLL 0x33 +#define ES8316_DAC_VOLR 0x34 + +/* GPIO */ +#define ES8316_GPIO_SEL 0x4d +#define ES8316_GPIO_DEBOUNCE 0x4e +#define ES8316_GPIO_FLAG 0x4f + +/* Test mode */ +#define ES8316_TESTMODE 0x50 +#define ES8316_TEST1 0x51 +#define ES8316_TEST2 0x52 +#define ES8316_TEST3 0x53 + +/* + * Field definitions + */ + +/* ES8316_RESET */ +#define ES8316_RESET_CSM_ON 0x80 + +/* ES8316_CLKMGR_CLKSW */ +#define ES8316_CLKMGR_CLKSW_MCLK_ON 0x40 +#define ES8316_CLKMGR_CLKSW_BCLK_ON 0x20 + +/* ES8316_SERDATA1 */ +#define ES8316_SERDATA1_MASTER 0x80 +#define ES8316_SERDATA1_BCLK_INV 0x20 + +/* ES8316_SERDATA_ADC and _DAC */ +#define ES8316_SERDATA2_FMT_MASK 0x3 +#define ES8316_SERDATA2_FMT_I2S 0x00 +#define ES8316_SERDATA2_FMT_LEFTJ 0x01 +#define ES8316_SERDATA2_FMT_RIGHTJ 0x02 +#define ES8316_SERDATA2_FMT_PCM 0x03 +#define ES8316_SERDATA2_ADCLRP 0x20 +#define ES8316_SERDATA2_LEN_MASK 0x1c +#define ES8316_SERDATA2_LEN_24 0x00 +#define ES8316_SERDATA2_LEN_20 0x04 +#define ES8316_SERDATA2_LEN_18 0x08 +#define ES8316_SERDATA2_LEN_16 0x0c +#define ES8316_SERDATA2_LEN_32 0x10 + +#endif diff --git a/sound/soc/codecs/es8328-i2c.c b/sound/soc/codecs/es8328-i2c.c new file mode 100644 index 000000000..19baa3260 --- /dev/null +++ b/sound/soc/codecs/es8328-i2c.c @@ -0,0 +1,55 @@ +/* + * es8328-i2c.c -- ES8328 ALSA SoC I2C Audio driver + * + * Copyright 2014 Sutajio Ko-Usagi PTE LTD + * + * Author: Sean Cross + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include + +#include "es8328.h" + +static const struct i2c_device_id es8328_id[] = { + { "es8328", 0 }, + { "es8388", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, es8328_id); + +static const struct of_device_id es8328_of_match[] = { + { .compatible = "everest,es8328", }, + { .compatible = "everest,es8388", }, + { } +}; +MODULE_DEVICE_TABLE(of, es8328_of_match); + +static int es8328_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + return es8328_probe(&i2c->dev, + devm_regmap_init_i2c(i2c, &es8328_regmap_config)); +} + +static struct i2c_driver es8328_i2c_driver = { + .driver = { + .name = "es8328", + .of_match_table = es8328_of_match, + }, + .probe = es8328_i2c_probe, + .id_table = es8328_id, +}; + +module_i2c_driver(es8328_i2c_driver); + +MODULE_DESCRIPTION("ASoC ES8328 audio CODEC I2C driver"); +MODULE_AUTHOR("Sean Cross "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8328-spi.c b/sound/soc/codecs/es8328-spi.c new file mode 100644 index 000000000..d242bd1f7 --- /dev/null +++ b/sound/soc/codecs/es8328-spi.c @@ -0,0 +1,42 @@ +/* + * es8328.c -- ES8328 ALSA SoC SPI Audio driver + * + * Copyright 2014 Sutajio Ko-Usagi PTE LTD + * + * Author: Sean Cross + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "es8328.h" + +static const struct of_device_id es8328_of_match[] = { + { .compatible = "everest,es8328", }, + { } +}; +MODULE_DEVICE_TABLE(of, es8328_of_match); + +static int es8328_spi_probe(struct spi_device *spi) +{ + return es8328_probe(&spi->dev, + devm_regmap_init_spi(spi, &es8328_regmap_config)); +} + +static struct spi_driver es8328_spi_driver = { + .driver = { + .name = "es8328", + .of_match_table = es8328_of_match, + }, + .probe = es8328_spi_probe, +}; + +module_spi_driver(es8328_spi_driver); +MODULE_DESCRIPTION("ASoC ES8328 audio CODEC SPI driver"); +MODULE_AUTHOR("Sean Cross "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c new file mode 100644 index 000000000..dcb01889e --- /dev/null +++ b/sound/soc/codecs/es8328.c @@ -0,0 +1,887 @@ +/* + * es8328.c -- ES8328 ALSA SoC Audio driver + * + * Copyright 2014 Sutajio Ko-Usagi PTE LTD + * + * Author: Sean Cross + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "es8328.h" + +static const unsigned int rates_12288[] = { + 8000, 12000, 16000, 24000, 32000, 48000, 96000, +}; + +static const int ratios_12288[] = { + 10, 7, 6, 4, 3, 2, 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_12288 = { + .count = ARRAY_SIZE(rates_12288), + .list = rates_12288, +}; + +static const unsigned int rates_11289[] = { + 8018, 11025, 22050, 44100, 88200, +}; + +static const int ratios_11289[] = { + 9, 7, 4, 2, 0, +}; + +static const struct snd_pcm_hw_constraint_list constraints_11289 = { + .count = ARRAY_SIZE(rates_11289), + .list = rates_11289, +}; + +/* regulator supplies for sgtl5000, VDDD is an optional external supply */ +enum sgtl5000_regulator_supplies { + DVDD, + AVDD, + PVDD, + HPVDD, + ES8328_SUPPLY_NUM +}; + +/* vddd is optional supply */ +static const char * const supply_names[ES8328_SUPPLY_NUM] = { + "DVDD", + "AVDD", + "PVDD", + "HPVDD", +}; + +#define ES8328_RATES (SNDRV_PCM_RATE_192000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_8000_48000) +#define ES8328_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct es8328_priv { + struct regmap *regmap; + struct clk *clk; + int playback_fs; + bool deemph; + int mclkdiv2; + const struct snd_pcm_hw_constraint_list *sysclk_constraints; + const int *mclk_ratios; + bool master; + struct regulator_bulk_data supplies[ES8328_SUPPLY_NUM]; +}; + +/* + * ES8328 Controls + */ + +static const char * const adcpol_txt[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static SOC_ENUM_SINGLE_DECL(adcpol, + ES8328_ADCCONTROL6, 6, adcpol_txt); + +static const DECLARE_TLV_DB_SCALE(play_tlv, -3000, 100, 0); +static const DECLARE_TLV_DB_SCALE(dac_adc_tlv, -9600, 50, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 300, 0); + +static const struct { + int rate; + unsigned int val; +} deemph_settings[] = { + { 0, ES8328_DACCONTROL6_DEEMPH_OFF }, + { 32000, ES8328_DACCONTROL6_DEEMPH_32k }, + { 44100, ES8328_DACCONTROL6_DEEMPH_44_1k }, + { 48000, ES8328_DACCONTROL6_DEEMPH_48k }, +}; + +static int es8328_set_deemph(struct snd_soc_component *component) +{ + struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* + * If we're using deemphasis select the nearest available sample + * rate. + */ + if (es8328->deemph) { + best = 0; + for (i = 1; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i].rate - es8328->playback_fs) < + abs(deemph_settings[best].rate - es8328->playback_fs)) + best = i; + } + + val = deemph_settings[best].val; + } else { + val = ES8328_DACCONTROL6_DEEMPH_OFF; + } + + dev_dbg(component->dev, "Set deemphasis %d\n", val); + + return snd_soc_component_update_bits(component, ES8328_DACCONTROL6, + ES8328_DACCONTROL6_DEEMPH_MASK, val); +} + +static int es8328_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = es8328->deemph; + return 0; +} + +static int es8328_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + int ret; + + if (deemph > 1) + return -EINVAL; + + if (es8328->deemph == deemph) + return 0; + + ret = es8328_set_deemph(component); + if (ret < 0) + return ret; + + es8328->deemph = deemph; + + return 1; +} + + + +static const struct snd_kcontrol_new es8328_snd_controls[] = { + SOC_DOUBLE_R_TLV("Capture Digital Volume", + ES8328_ADCCONTROL8, ES8328_ADCCONTROL9, + 0, 0xc0, 1, dac_adc_tlv), + SOC_SINGLE("Capture ZC Switch", ES8328_ADCCONTROL7, 6, 1, 0), + + SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + es8328_get_deemph, es8328_put_deemph), + + SOC_ENUM("Capture Polarity", adcpol), + + SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", + ES8328_DACCONTROL17, 3, 7, 1, bypass_tlv), + SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", + ES8328_DACCONTROL19, 3, 7, 1, bypass_tlv), + SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", + ES8328_DACCONTROL18, 3, 7, 1, bypass_tlv), + SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", + ES8328_DACCONTROL20, 3, 7, 1, bypass_tlv), + + SOC_DOUBLE_R_TLV("PCM Volume", + ES8328_LDACVOL, ES8328_RDACVOL, + 0, ES8328_DACVOL_MAX, 1, dac_adc_tlv), + + SOC_DOUBLE_R_TLV("Output 1 Playback Volume", + ES8328_LOUT1VOL, ES8328_ROUT1VOL, + 0, ES8328_OUT1VOL_MAX, 0, play_tlv), + + SOC_DOUBLE_R_TLV("Output 2 Playback Volume", + ES8328_LOUT2VOL, ES8328_ROUT2VOL, + 0, ES8328_OUT2VOL_MAX, 0, play_tlv), + + SOC_DOUBLE_TLV("Mic PGA Volume", ES8328_ADCCONTROL1, + 4, 0, 8, 0, mic_tlv), +}; + +/* + * DAPM Controls + */ + +static const char * const es8328_line_texts[] = { + "Line 1", "Line 2", "PGA", "Differential"}; + +static const struct soc_enum es8328_lline_enum = + SOC_ENUM_SINGLE(ES8328_DACCONTROL16, 3, + ARRAY_SIZE(es8328_line_texts), + es8328_line_texts); +static const struct snd_kcontrol_new es8328_left_line_controls = + SOC_DAPM_ENUM("Route", es8328_lline_enum); + +static const struct soc_enum es8328_rline_enum = + SOC_ENUM_SINGLE(ES8328_DACCONTROL16, 0, + ARRAY_SIZE(es8328_line_texts), + es8328_line_texts); +static const struct snd_kcontrol_new es8328_right_line_controls = + SOC_DAPM_ENUM("Route", es8328_rline_enum); + +/* Left Mixer */ +static const struct snd_kcontrol_new es8328_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", ES8328_DACCONTROL17, 7, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", ES8328_DACCONTROL17, 6, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", ES8328_DACCONTROL18, 7, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", ES8328_DACCONTROL18, 6, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new es8328_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", ES8328_DACCONTROL19, 7, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", ES8328_DACCONTROL19, 6, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", ES8328_DACCONTROL20, 7, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", ES8328_DACCONTROL20, 6, 1, 0), +}; + +static const char * const es8328_pga_sel[] = { + "Line 1", "Line 2", "Line 3", "Differential"}; + +/* Left PGA Mux */ +static const struct soc_enum es8328_lpga_enum = + SOC_ENUM_SINGLE(ES8328_ADCCONTROL2, 6, + ARRAY_SIZE(es8328_pga_sel), + es8328_pga_sel); +static const struct snd_kcontrol_new es8328_left_pga_controls = + SOC_DAPM_ENUM("Route", es8328_lpga_enum); + +/* Right PGA Mux */ +static const struct soc_enum es8328_rpga_enum = + SOC_ENUM_SINGLE(ES8328_ADCCONTROL2, 4, + ARRAY_SIZE(es8328_pga_sel), + es8328_pga_sel); +static const struct snd_kcontrol_new es8328_right_pga_controls = + SOC_DAPM_ENUM("Route", es8328_rpga_enum); + +/* Differential Mux */ +static const char * const es8328_diff_sel[] = {"Line 1", "Line 2"}; +static SOC_ENUM_SINGLE_DECL(diffmux, + ES8328_ADCCONTROL3, 7, es8328_diff_sel); +static const struct snd_kcontrol_new es8328_diffmux_controls = + SOC_DAPM_ENUM("Route", diffmux); + +/* Mono ADC Mux */ +static const char * const es8328_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static SOC_ENUM_SINGLE_DECL(monomux, + ES8328_ADCCONTROL3, 3, es8328_mono_mux); +static const struct snd_kcontrol_new es8328_monomux_controls = + SOC_DAPM_ENUM("Route", monomux); + +static const struct snd_soc_dapm_widget es8328_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &es8328_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &es8328_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &es8328_monomux_controls), + + SND_SOC_DAPM_MUX("Left PGA Mux", ES8328_ADCPOWER, + ES8328_ADCPOWER_AINL_OFF, 1, + &es8328_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", ES8328_ADCPOWER, + ES8328_ADCPOWER_AINR_OFF, 1, + &es8328_right_pga_controls), + + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &es8328_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &es8328_right_line_controls), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", ES8328_ADCPOWER, + ES8328_ADCPOWER_ADCR_OFF, 1), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", ES8328_ADCPOWER, + ES8328_ADCPOWER_ADCL_OFF, 1), + + SND_SOC_DAPM_SUPPLY("Mic Bias", ES8328_ADCPOWER, + ES8328_ADCPOWER_MIC_BIAS_OFF, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias Gen", ES8328_ADCPOWER, + ES8328_ADCPOWER_ADC_BIAS_GEN_OFF, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DAC STM", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_DACSTM_RESET, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC STM", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_ADCSTM_RESET, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DAC DIG", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_DACDIG_OFF, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC DIG", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_ADCDIG_OFF, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DAC DLL", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_DACDLL_OFF, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC DLL", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_ADCDLL_OFF, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("ADC Vref", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_ADCVREF_OFF, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Vref", ES8328_CHIPPOWER, + ES8328_CHIPPOWER_DACVREF_OFF, 1, NULL, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", ES8328_DACPOWER, + ES8328_DACPOWER_RDAC_OFF, 1), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", ES8328_DACPOWER, + ES8328_DACPOWER_LDAC_OFF, 1), + + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &es8328_left_mixer_controls[0], + ARRAY_SIZE(es8328_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &es8328_right_mixer_controls[0], + ARRAY_SIZE(es8328_right_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", ES8328_DACPOWER, + ES8328_DACPOWER_ROUT2_ON, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", ES8328_DACPOWER, + ES8328_DACPOWER_LOUT2_ON, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", ES8328_DACPOWER, + ES8328_DACPOWER_ROUT1_ON, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", ES8328_DACPOWER, + ES8328_DACPOWER_LOUT1_ON, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), +}; + +static const struct snd_soc_dapm_route es8328_dapm_routes[] = { + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left PGA Mux", "Line 1", "LINPUT1" }, + { "Left PGA Mux", "Line 2", "LINPUT2" }, + { "Left PGA Mux", "Differential", "Differential Mux" }, + + { "Right PGA Mux", "Line 1", "RINPUT1" }, + { "Right PGA Mux", "Line 2", "RINPUT2" }, + { "Right PGA Mux", "Differential", "Differential Mux" }, + + { "Differential Mux", "Line 1", "LINPUT1" }, + { "Differential Mux", "Line 1", "RINPUT1" }, + { "Differential Mux", "Line 2", "LINPUT2" }, + { "Differential Mux", "Line 2", "RINPUT2" }, + + { "Left ADC Mux", "Stereo", "Left PGA Mux" }, + { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" }, + { "Left ADC Mux", "Digital Mono", "Left PGA Mux" }, + + { "Right ADC Mux", "Stereo", "Right PGA Mux" }, + { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" }, + { "Right ADC Mux", "Digital Mono", "Right PGA Mux" }, + + { "Left ADC", NULL, "Left ADC Mux" }, + { "Right ADC", NULL, "Right ADC Mux" }, + + { "ADC DIG", NULL, "ADC STM" }, + { "ADC DIG", NULL, "ADC Vref" }, + { "ADC DIG", NULL, "ADC DLL" }, + + { "Left ADC", NULL, "ADC DIG" }, + { "Right ADC", NULL, "ADC DIG" }, + + { "Mic Bias", NULL, "Mic Bias Gen" }, + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left Out 1", NULL, "Left DAC" }, + { "Right Out 1", NULL, "Right DAC" }, + { "Left Out 2", NULL, "Left DAC" }, + { "Right Out 2", NULL, "Right DAC" }, + + { "Left Mixer", "Playback Switch", "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, + { "Left Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Right Mixer", "Left Playback Switch", "Left DAC" }, + { "Right Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Right Mixer", "Playback Switch", "Right DAC" }, + { "Right Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "DAC DIG", NULL, "DAC STM" }, + { "DAC DIG", NULL, "DAC Vref" }, + { "DAC DIG", NULL, "DAC DLL" }, + + { "Left DAC", NULL, "DAC DIG" }, + { "Right DAC", NULL, "DAC DIG" }, + + { "Left Out 1", NULL, "Left Mixer" }, + { "LOUT1", NULL, "Left Out 1" }, + { "Right Out 1", NULL, "Right Mixer" }, + { "ROUT1", NULL, "Right Out 1" }, + + { "Left Out 2", NULL, "Left Mixer" }, + { "LOUT2", NULL, "Left Out 2" }, + { "Right Out 2", NULL, "Right Mixer" }, + { "ROUT2", NULL, "Right Out 2" }, +}; + +static int es8328_mute(struct snd_soc_dai *dai, int mute) +{ + return snd_soc_component_update_bits(dai->component, ES8328_DACCONTROL3, + ES8328_DACCONTROL3_DACMUTE, + mute ? ES8328_DACCONTROL3_DACMUTE : 0); +} + +static int es8328_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component); + + if (es8328->master && es8328->sysclk_constraints) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + es8328->sysclk_constraints); + + return 0; +} + +static int es8328_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component); + int i; + int reg; + int wl; + int ratio; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = ES8328_DACCONTROL2; + else + reg = ES8328_ADCCONTROL5; + + if (es8328->master) { + if (!es8328->sysclk_constraints) { + dev_err(component->dev, "No MCLK configured\n"); + return -EINVAL; + } + + for (i = 0; i < es8328->sysclk_constraints->count; i++) + if (es8328->sysclk_constraints->list[i] == + params_rate(params)) + break; + + if (i == es8328->sysclk_constraints->count) { + dev_err(component->dev, + "LRCLK %d unsupported with current clock\n", + params_rate(params)); + return -EINVAL; + } + ratio = es8328->mclk_ratios[i]; + } else { + ratio = 0; + es8328->mclkdiv2 = 0; + } + + snd_soc_component_update_bits(component, ES8328_MASTERMODE, + ES8328_MASTERMODE_MCLKDIV2, + es8328->mclkdiv2 ? ES8328_MASTERMODE_MCLKDIV2 : 0); + + switch (params_width(params)) { + case 16: + wl = 3; + break; + case 18: + wl = 2; + break; + case 20: + wl = 1; + break; + case 24: + wl = 0; + break; + case 32: + wl = 4; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + snd_soc_component_update_bits(component, ES8328_DACCONTROL1, + ES8328_DACCONTROL1_DACWL_MASK, + wl << ES8328_DACCONTROL1_DACWL_SHIFT); + + es8328->playback_fs = params_rate(params); + es8328_set_deemph(component); + } else + snd_soc_component_update_bits(component, ES8328_ADCCONTROL4, + ES8328_ADCCONTROL4_ADCWL_MASK, + wl << ES8328_ADCCONTROL4_ADCWL_SHIFT); + + return snd_soc_component_update_bits(component, reg, ES8328_RATEMASK, ratio); +} + +static int es8328_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component); + int mclkdiv2 = 0; + + switch (freq) { + case 0: + es8328->sysclk_constraints = NULL; + es8328->mclk_ratios = NULL; + break; + case 22579200: + mclkdiv2 = 1; + /* fallthru */ + case 11289600: + es8328->sysclk_constraints = &constraints_11289; + es8328->mclk_ratios = ratios_11289; + break; + case 24576000: + mclkdiv2 = 1; + /* fallthru */ + case 12288000: + es8328->sysclk_constraints = &constraints_12288; + es8328->mclk_ratios = ratios_12288; + break; + default: + return -EINVAL; + } + + es8328->mclkdiv2 = mclkdiv2; + return 0; +} + +static int es8328_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct es8328_priv *es8328 = snd_soc_component_get_drvdata(component); + u8 dac_mode = 0; + u8 adc_mode = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* Master serial port mode, with BCLK generated automatically */ + snd_soc_component_update_bits(component, ES8328_MASTERMODE, + ES8328_MASTERMODE_MSC, + ES8328_MASTERMODE_MSC); + es8328->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave serial port mode */ + snd_soc_component_update_bits(component, ES8328_MASTERMODE, + ES8328_MASTERMODE_MSC, 0); + es8328->master = false; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dac_mode |= ES8328_DACCONTROL1_DACFORMAT_I2S; + adc_mode |= ES8328_ADCCONTROL4_ADCFORMAT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dac_mode |= ES8328_DACCONTROL1_DACFORMAT_RJUST; + adc_mode |= ES8328_ADCCONTROL4_ADCFORMAT_RJUST; + break; + case SND_SOC_DAIFMT_LEFT_J: + dac_mode |= ES8328_DACCONTROL1_DACFORMAT_LJUST; + adc_mode |= ES8328_ADCCONTROL4_ADCFORMAT_LJUST; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + + snd_soc_component_update_bits(component, ES8328_DACCONTROL1, + ES8328_DACCONTROL1_DACFORMAT_MASK, dac_mode); + snd_soc_component_update_bits(component, ES8328_ADCCONTROL4, + ES8328_ADCCONTROL4_ADCFORMAT_MASK, adc_mode); + + return 0; +} + +static int es8328_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VREF, VMID=2x50k, digital enabled */ + snd_soc_component_write(component, ES8328_CHIPPOWER, 0); + snd_soc_component_update_bits(component, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + ES8328_CONTROL1_VMIDSEL_50k | + ES8328_CONTROL1_ENREF); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_update_bits(component, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + ES8328_CONTROL1_VMIDSEL_5k | + ES8328_CONTROL1_ENREF); + + /* Charge caps */ + msleep(100); + } + + snd_soc_component_write(component, ES8328_CONTROL2, + ES8328_CONTROL2_OVERCURRENT_ON | + ES8328_CONTROL2_THERMAL_SHUTDOWN_ON); + + /* VREF, VMID=2*500k, digital stopped */ + snd_soc_component_update_bits(component, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + ES8328_CONTROL1_VMIDSEL_500k | + ES8328_CONTROL1_ENREF); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, ES8328_CONTROL1, + ES8328_CONTROL1_VMIDSEL_MASK | + ES8328_CONTROL1_ENREF, + 0); + break; + } + return 0; +} + +static const struct snd_soc_dai_ops es8328_dai_ops = { + .startup = es8328_startup, + .hw_params = es8328_hw_params, + .digital_mute = es8328_mute, + .set_sysclk = es8328_set_sysclk, + .set_fmt = es8328_set_dai_fmt, +}; + +static struct snd_soc_dai_driver es8328_dai = { + .name = "es8328-hifi-analog", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ES8328_RATES, + .formats = ES8328_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ES8328_RATES, + .formats = ES8328_FORMATS, + }, + .ops = &es8328_dai_ops, + .symmetric_rates = 1, +}; + +static int es8328_suspend(struct snd_soc_component *component) +{ + struct es8328_priv *es8328; + int ret; + + es8328 = snd_soc_component_get_drvdata(component); + + clk_disable_unprepare(es8328->clk); + + ret = regulator_bulk_disable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); + if (ret) { + dev_err(component->dev, "unable to disable regulators\n"); + return ret; + } + return 0; +} + +static int es8328_resume(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + struct es8328_priv *es8328; + int ret; + + es8328 = snd_soc_component_get_drvdata(component); + + ret = clk_prepare_enable(es8328->clk); + if (ret) { + dev_err(component->dev, "unable to enable clock\n"); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); + if (ret) { + dev_err(component->dev, "unable to enable regulators\n"); + return ret; + } + + regcache_mark_dirty(regmap); + ret = regcache_sync(regmap); + if (ret) { + dev_err(component->dev, "unable to sync regcache\n"); + return ret; + } + + return 0; +} + +static int es8328_component_probe(struct snd_soc_component *component) +{ + struct es8328_priv *es8328; + int ret; + + es8328 = snd_soc_component_get_drvdata(component); + + ret = regulator_bulk_enable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); + if (ret) { + dev_err(component->dev, "unable to enable regulators\n"); + return ret; + } + + /* Setup clocks */ + es8328->clk = devm_clk_get(component->dev, NULL); + if (IS_ERR(es8328->clk)) { + dev_err(component->dev, "codec clock missing or invalid\n"); + ret = PTR_ERR(es8328->clk); + goto clk_fail; + } + + ret = clk_prepare_enable(es8328->clk); + if (ret) { + dev_err(component->dev, "unable to prepare codec clk\n"); + goto clk_fail; + } + + return 0; + +clk_fail: + regulator_bulk_disable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); + return ret; +} + +static void es8328_remove(struct snd_soc_component *component) +{ + struct es8328_priv *es8328; + + es8328 = snd_soc_component_get_drvdata(component); + + if (es8328->clk) + clk_disable_unprepare(es8328->clk); + + regulator_bulk_disable(ARRAY_SIZE(es8328->supplies), + es8328->supplies); +} + +const struct regmap_config es8328_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ES8328_REG_MAX, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; +EXPORT_SYMBOL_GPL(es8328_regmap_config); + +static const struct snd_soc_component_driver es8328_component_driver = { + .probe = es8328_component_probe, + .remove = es8328_remove, + .suspend = es8328_suspend, + .resume = es8328_resume, + .set_bias_level = es8328_set_bias_level, + .controls = es8328_snd_controls, + .num_controls = ARRAY_SIZE(es8328_snd_controls), + .dapm_widgets = es8328_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(es8328_dapm_widgets), + .dapm_routes = es8328_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(es8328_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +int es8328_probe(struct device *dev, struct regmap *regmap) +{ + struct es8328_priv *es8328; + int ret; + int i; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + es8328 = devm_kzalloc(dev, sizeof(*es8328), GFP_KERNEL); + if (es8328 == NULL) + return -ENOMEM; + + es8328->regmap = regmap; + + for (i = 0; i < ARRAY_SIZE(es8328->supplies); i++) + es8328->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(es8328->supplies), + es8328->supplies); + if (ret) { + dev_err(dev, "unable to get regulators\n"); + return ret; + } + + dev_set_drvdata(dev, es8328); + + return devm_snd_soc_register_component(dev, + &es8328_component_driver, &es8328_dai, 1); +} +EXPORT_SYMBOL_GPL(es8328_probe); + +MODULE_DESCRIPTION("ASoC ES8328 driver"); +MODULE_AUTHOR("Sean Cross "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es8328.h b/sound/soc/codecs/es8328.h new file mode 100644 index 000000000..9109f6b5b --- /dev/null +++ b/sound/soc/codecs/es8328.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * es8328.h -- ES8328 ALSA SoC Audio driver + */ + +#ifndef _ES8328_H +#define _ES8328_H + +#include + +struct device; + +extern const struct regmap_config es8328_regmap_config; +int es8328_probe(struct device *dev, struct regmap *regmap); + +#define ES8328_DACLVOL 46 +#define ES8328_DACRVOL 47 +#define ES8328_DACCTL 28 +#define ES8328_RATEMASK (0x1f << 0) + +#define ES8328_CONTROL1 0x00 +#define ES8328_CONTROL1_VMIDSEL_OFF (0 << 0) +#define ES8328_CONTROL1_VMIDSEL_50k (1 << 0) +#define ES8328_CONTROL1_VMIDSEL_500k (2 << 0) +#define ES8328_CONTROL1_VMIDSEL_5k (3 << 0) +#define ES8328_CONTROL1_VMIDSEL_MASK (3 << 0) +#define ES8328_CONTROL1_ENREF (1 << 2) +#define ES8328_CONTROL1_SEQEN (1 << 3) +#define ES8328_CONTROL1_SAMEFS (1 << 4) +#define ES8328_CONTROL1_DACMCLK_ADC (0 << 5) +#define ES8328_CONTROL1_DACMCLK_DAC (1 << 5) +#define ES8328_CONTROL1_LRCM (1 << 6) +#define ES8328_CONTROL1_SCP_RESET (1 << 7) + +#define ES8328_CONTROL2 0x01 +#define ES8328_CONTROL2_VREF_BUF_OFF (1 << 0) +#define ES8328_CONTROL2_VREF_LOWPOWER (1 << 1) +#define ES8328_CONTROL2_IBIASGEN_OFF (1 << 2) +#define ES8328_CONTROL2_ANALOG_OFF (1 << 3) +#define ES8328_CONTROL2_VREF_BUF_LOWPOWER (1 << 4) +#define ES8328_CONTROL2_VCM_MOD_LOWPOWER (1 << 5) +#define ES8328_CONTROL2_OVERCURRENT_ON (1 << 6) +#define ES8328_CONTROL2_THERMAL_SHUTDOWN_ON (1 << 7) + +#define ES8328_CHIPPOWER 0x02 +#define ES8328_CHIPPOWER_DACVREF_OFF 0 +#define ES8328_CHIPPOWER_ADCVREF_OFF 1 +#define ES8328_CHIPPOWER_DACDLL_OFF 2 +#define ES8328_CHIPPOWER_ADCDLL_OFF 3 +#define ES8328_CHIPPOWER_DACSTM_RESET 4 +#define ES8328_CHIPPOWER_ADCSTM_RESET 5 +#define ES8328_CHIPPOWER_DACDIG_OFF 6 +#define ES8328_CHIPPOWER_ADCDIG_OFF 7 + +#define ES8328_ADCPOWER 0x03 +#define ES8328_ADCPOWER_INT1_LOWPOWER 0 +#define ES8328_ADCPOWER_FLASH_ADC_LOWPOWER 1 +#define ES8328_ADCPOWER_ADC_BIAS_GEN_OFF 2 +#define ES8328_ADCPOWER_MIC_BIAS_OFF 3 +#define ES8328_ADCPOWER_ADCR_OFF 4 +#define ES8328_ADCPOWER_ADCL_OFF 5 +#define ES8328_ADCPOWER_AINR_OFF 6 +#define ES8328_ADCPOWER_AINL_OFF 7 + +#define ES8328_DACPOWER 0x04 +#define ES8328_DACPOWER_OUT3_ON 0 +#define ES8328_DACPOWER_MONO_ON 1 +#define ES8328_DACPOWER_ROUT2_ON 2 +#define ES8328_DACPOWER_LOUT2_ON 3 +#define ES8328_DACPOWER_ROUT1_ON 4 +#define ES8328_DACPOWER_LOUT1_ON 5 +#define ES8328_DACPOWER_RDAC_OFF 6 +#define ES8328_DACPOWER_LDAC_OFF 7 + +#define ES8328_CHIPLOPOW1 0x05 +#define ES8328_CHIPLOPOW2 0x06 +#define ES8328_ANAVOLMANAG 0x07 + +#define ES8328_MASTERMODE 0x08 +#define ES8328_MASTERMODE_BCLKDIV (0 << 0) +#define ES8328_MASTERMODE_BCLK_INV (1 << 5) +#define ES8328_MASTERMODE_MCLKDIV2 (1 << 6) +#define ES8328_MASTERMODE_MSC (1 << 7) + +#define ES8328_ADCCONTROL1 0x09 +#define ES8328_ADCCONTROL2 0x0a +#define ES8328_ADCCONTROL3 0x0b + +#define ES8328_ADCCONTROL4 0x0c +#define ES8328_ADCCONTROL4_ADCFORMAT_MASK (3 << 0) +#define ES8328_ADCCONTROL4_ADCFORMAT_I2S (0 << 0) +#define ES8328_ADCCONTROL4_ADCFORMAT_LJUST (1 << 0) +#define ES8328_ADCCONTROL4_ADCFORMAT_RJUST (2 << 0) +#define ES8328_ADCCONTROL4_ADCFORMAT_PCM (3 << 0) +#define ES8328_ADCCONTROL4_ADCWL_SHIFT 2 +#define ES8328_ADCCONTROL4_ADCWL_MASK (7 << 2) +#define ES8328_ADCCONTROL4_ADCLRP_I2S_POL_NORMAL (0 << 5) +#define ES8328_ADCCONTROL4_ADCLRP_I2S_POL_INV (1 << 5) +#define ES8328_ADCCONTROL4_ADCLRP_PCM_MSB_CLK2 (0 << 5) +#define ES8328_ADCCONTROL4_ADCLRP_PCM_MSB_CLK1 (1 << 5) + +#define ES8328_ADCCONTROL5 0x0d +#define ES8328_ADCCONTROL5_RATEMASK (0x1f << 0) + +#define ES8328_ADCCONTROL6 0x0e + +#define ES8328_ADCCONTROL7 0x0f +#define ES8328_ADCCONTROL7_ADC_MUTE (1 << 2) +#define ES8328_ADCCONTROL7_ADC_LER (1 << 3) +#define ES8328_ADCCONTROL7_ADC_ZERO_CROSS (1 << 4) +#define ES8328_ADCCONTROL7_ADC_SOFT_RAMP (1 << 5) +#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_4 (0 << 6) +#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_8 (1 << 6) +#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_16 (2 << 6) +#define ES8328_ADCCONTROL7_ADC_RAMP_RATE_32 (3 << 6) + +#define ES8328_ADCCONTROL8 0x10 +#define ES8328_ADCCONTROL9 0x11 +#define ES8328_ADCCONTROL10 0x12 +#define ES8328_ADCCONTROL11 0x13 +#define ES8328_ADCCONTROL12 0x14 +#define ES8328_ADCCONTROL13 0x15 +#define ES8328_ADCCONTROL14 0x16 + +#define ES8328_DACCONTROL1 0x17 +#define ES8328_DACCONTROL1_DACFORMAT_MASK (3 << 1) +#define ES8328_DACCONTROL1_DACFORMAT_I2S (0 << 1) +#define ES8328_DACCONTROL1_DACFORMAT_LJUST (1 << 1) +#define ES8328_DACCONTROL1_DACFORMAT_RJUST (2 << 1) +#define ES8328_DACCONTROL1_DACFORMAT_PCM (3 << 1) +#define ES8328_DACCONTROL1_DACWL_SHIFT 3 +#define ES8328_DACCONTROL1_DACWL_MASK (7 << 3) +#define ES8328_DACCONTROL1_DACLRP_I2S_POL_NORMAL (0 << 6) +#define ES8328_DACCONTROL1_DACLRP_I2S_POL_INV (1 << 6) +#define ES8328_DACCONTROL1_DACLRP_PCM_MSB_CLK2 (0 << 6) +#define ES8328_DACCONTROL1_DACLRP_PCM_MSB_CLK1 (1 << 6) +#define ES8328_DACCONTROL1_LRSWAP (1 << 7) + +#define ES8328_DACCONTROL2 0x18 +#define ES8328_DACCONTROL2_RATEMASK (0x1f << 0) +#define ES8328_DACCONTROL2_DOUBLESPEED (1 << 5) + +#define ES8328_DACCONTROL3 0x19 +#define ES8328_DACCONTROL3_AUTOMUTE (1 << 2) +#define ES8328_DACCONTROL3_DACMUTE (1 << 2) +#define ES8328_DACCONTROL3_LEFTGAINVOL (1 << 3) +#define ES8328_DACCONTROL3_DACZEROCROSS (1 << 4) +#define ES8328_DACCONTROL3_DACSOFTRAMP (1 << 5) +#define ES8328_DACCONTROL3_DACRAMPRATE (3 << 6) + +#define ES8328_LDACVOL 0x1a +#define ES8328_LDACVOL_MASK (0 << 0) +#define ES8328_LDACVOL_MAX (0xc0) + +#define ES8328_RDACVOL 0x1b +#define ES8328_RDACVOL_MASK (0 << 0) +#define ES8328_RDACVOL_MAX (0xc0) + +#define ES8328_DACVOL_MAX (0xc0) + +#define ES8328_DACCONTROL4 0x1a +#define ES8328_DACCONTROL5 0x1b + +#define ES8328_DACCONTROL6 0x1c +#define ES8328_DACCONTROL6_CLICKFREE (1 << 3) +#define ES8328_DACCONTROL6_DAC_INVR (1 << 4) +#define ES8328_DACCONTROL6_DAC_INVL (1 << 5) +#define ES8328_DACCONTROL6_DEEMPH_MASK (3 << 6) +#define ES8328_DACCONTROL6_DEEMPH_OFF (0 << 6) +#define ES8328_DACCONTROL6_DEEMPH_32k (1 << 6) +#define ES8328_DACCONTROL6_DEEMPH_44_1k (2 << 6) +#define ES8328_DACCONTROL6_DEEMPH_48k (3 << 6) + +#define ES8328_DACCONTROL7 0x1d +#define ES8328_DACCONTROL7_VPP_SCALE_3p5 (0 << 0) +#define ES8328_DACCONTROL7_VPP_SCALE_4p0 (1 << 0) +#define ES8328_DACCONTROL7_VPP_SCALE_3p0 (2 << 0) +#define ES8328_DACCONTROL7_VPP_SCALE_2p5 (3 << 0) +#define ES8328_DACCONTROL7_SHELVING_STRENGTH (1 << 2) /* In eights */ +#define ES8328_DACCONTROL7_MONO (1 << 5) +#define ES8328_DACCONTROL7_ZEROR (1 << 6) +#define ES8328_DACCONTROL7_ZEROL (1 << 7) + +/* Shelving filter */ +#define ES8328_DACCONTROL8 0x1e +#define ES8328_DACCONTROL9 0x1f +#define ES8328_DACCONTROL10 0x20 +#define ES8328_DACCONTROL11 0x21 +#define ES8328_DACCONTROL12 0x22 +#define ES8328_DACCONTROL13 0x23 +#define ES8328_DACCONTROL14 0x24 +#define ES8328_DACCONTROL15 0x25 + +#define ES8328_DACCONTROL16 0x26 +#define ES8328_DACCONTROL16_RMIXSEL_RIN1 (0 << 0) +#define ES8328_DACCONTROL16_RMIXSEL_RIN2 (1 << 0) +#define ES8328_DACCONTROL16_RMIXSEL_RIN3 (2 << 0) +#define ES8328_DACCONTROL16_RMIXSEL_RADC (3 << 0) +#define ES8328_DACCONTROL16_LMIXSEL_LIN1 (0 << 3) +#define ES8328_DACCONTROL16_LMIXSEL_LIN2 (1 << 3) +#define ES8328_DACCONTROL16_LMIXSEL_LIN3 (2 << 3) +#define ES8328_DACCONTROL16_LMIXSEL_LADC (3 << 3) + +#define ES8328_DACCONTROL17 0x27 +#define ES8328_DACCONTROL17_LI2LOVOL (7 << 3) +#define ES8328_DACCONTROL17_LI2LO (1 << 6) +#define ES8328_DACCONTROL17_LD2LO (1 << 7) + +#define ES8328_DACCONTROL18 0x28 +#define ES8328_DACCONTROL18_RI2LOVOL (7 << 3) +#define ES8328_DACCONTROL18_RI2LO (1 << 6) +#define ES8328_DACCONTROL18_RD2LO (1 << 7) + +#define ES8328_DACCONTROL19 0x29 +#define ES8328_DACCONTROL19_LI2ROVOL (7 << 3) +#define ES8328_DACCONTROL19_LI2RO (1 << 6) +#define ES8328_DACCONTROL19_LD2RO (1 << 7) + +#define ES8328_DACCONTROL20 0x2a +#define ES8328_DACCONTROL20_RI2ROVOL (7 << 3) +#define ES8328_DACCONTROL20_RI2RO (1 << 6) +#define ES8328_DACCONTROL20_RD2RO (1 << 7) + +#define ES8328_DACCONTROL21 0x2b +#define ES8328_DACCONTROL21_LI2MOVOL (7 << 3) +#define ES8328_DACCONTROL21_LI2MO (1 << 6) +#define ES8328_DACCONTROL21_LD2MO (1 << 7) + +#define ES8328_DACCONTROL22 0x2c +#define ES8328_DACCONTROL22_RI2MOVOL (7 << 3) +#define ES8328_DACCONTROL22_RI2MO (1 << 6) +#define ES8328_DACCONTROL22_RD2MO (1 << 7) + +#define ES8328_DACCONTROL23 0x2d +#define ES8328_DACCONTROL23_MOUTINV (1 << 1) +#define ES8328_DACCONTROL23_HPSWPOL (1 << 2) +#define ES8328_DACCONTROL23_HPSWEN (1 << 3) +#define ES8328_DACCONTROL23_VROI_1p5k (0 << 4) +#define ES8328_DACCONTROL23_VROI_40k (1 << 4) +#define ES8328_DACCONTROL23_OUT3_VREF (0 << 5) +#define ES8328_DACCONTROL23_OUT3_ROUT1 (1 << 5) +#define ES8328_DACCONTROL23_OUT3_MONOOUT (2 << 5) +#define ES8328_DACCONTROL23_OUT3_RIGHT_MIXER (3 << 5) +#define ES8328_DACCONTROL23_ROUT2INV (1 << 7) + +/* LOUT1 Amplifier */ +#define ES8328_LOUT1VOL 0x2e +#define ES8328_LOUT1VOL_MASK (0 << 5) +#define ES8328_LOUT1VOL_MAX (0x24) + +/* ROUT1 Amplifier */ +#define ES8328_ROUT1VOL 0x2f +#define ES8328_ROUT1VOL_MASK (0 << 5) +#define ES8328_ROUT1VOL_MAX (0x24) + +#define ES8328_OUT1VOL_MAX (0x24) + +/* LOUT2 Amplifier */ +#define ES8328_LOUT2VOL 0x30 +#define ES8328_LOUT2VOL_MASK (0 << 5) +#define ES8328_LOUT2VOL_MAX (0x24) + +/* ROUT2 Amplifier */ +#define ES8328_ROUT2VOL 0x31 +#define ES8328_ROUT2VOL_MASK (0 << 5) +#define ES8328_ROUT2VOL_MAX (0x24) + +#define ES8328_OUT2VOL_MAX (0x24) + +/* Mono Out Amplifier */ +#define ES8328_MONOOUTVOL 0x32 +#define ES8328_MONOOUTVOL_MASK (0 << 5) +#define ES8328_MONOOUTVOL_MAX (0x24) + +#define ES8328_DACCONTROL29 0x33 +#define ES8328_DACCONTROL30 0x34 + +#define ES8328_SYSCLK 0 + +#define ES8328_REG_MAX 0x35 + +#define ES8328_1536FS 1536 +#define ES8328_1024FS 1024 +#define ES8328_768FS 768 +#define ES8328_512FS 512 +#define ES8328_384FS 384 +#define ES8328_256FS 256 +#define ES8328_128FS 128 + +#endif diff --git a/sound/soc/codecs/gtm601.c b/sound/soc/codecs/gtm601.c new file mode 100644 index 000000000..c11ed60cc --- /dev/null +++ b/sound/soc/codecs/gtm601.c @@ -0,0 +1,91 @@ +/* + * This is a simple driver for the GTM601 Voice PCM interface + * + * Copyright (C) 2015 Goldelico GmbH + * + * Author: Marek Belisko + * + * Based on wm8727.c driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct snd_soc_dapm_widget gtm601_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("AOUT"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const struct snd_soc_dapm_route gtm601_dapm_routes[] = { + { "AOUT", NULL, "Playback" }, + { "Capture", NULL, "AIN" }, +}; + +static struct snd_soc_dai_driver gtm601_dai = { + .name = "gtm601", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_gtm601 = { + .dapm_widgets = gtm601_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(gtm601_dapm_widgets), + .dapm_routes = gtm601_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(gtm601_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int gtm601_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_gtm601, >m601_dai, 1); +} + +#if defined(CONFIG_OF) +static const struct of_device_id gtm601_codec_of_match[] = { + { .compatible = "option,gtm601", }, + {}, +}; +MODULE_DEVICE_TABLE(of, gtm601_codec_of_match); +#endif + +static struct platform_driver gtm601_codec_driver = { + .driver = { + .name = "gtm601", + .of_match_table = of_match_ptr(gtm601_codec_of_match), + }, + .probe = gtm601_platform_probe, +}; + +module_platform_driver(gtm601_codec_driver); + +MODULE_DESCRIPTION("ASoC gtm601 driver"); +MODULE_AUTHOR("Marek Belisko "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gtm601"); diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c new file mode 100644 index 000000000..4594b1447 --- /dev/null +++ b/sound/soc/codecs/hdac_hdmi.c @@ -0,0 +1,2312 @@ +/* + * hdac_hdmi.c - ASoc HDA-HDMI codec driver for Intel platforms + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Samreen Nilofer + * Subhransu S. Prusty + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../hda/local.h" +#include "hdac_hdmi.h" + +#define NAME_SIZE 32 + +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 +#define PIN_OUT (AC_PINCTL_OUT_EN) + +#define HDA_MAX_CONNECTIONS 32 + +#define HDA_MAX_CVTS 3 +#define HDA_MAX_PORTS 3 + +#define ELD_MAX_SIZE 256 +#define ELD_FIXED_BYTES 20 + +#define ELD_VER_CEA_861D 2 +#define ELD_VER_PARTIAL 31 +#define ELD_MAX_MNL 16 + +struct hdac_hdmi_cvt_params { + unsigned int channels_min; + unsigned int channels_max; + u32 rates; + u64 formats; + unsigned int maxbps; +}; + +struct hdac_hdmi_cvt { + struct list_head head; + hda_nid_t nid; + const char *name; + struct hdac_hdmi_cvt_params params; +}; + +/* Currently only spk_alloc, more to be added */ +struct hdac_hdmi_parsed_eld { + u8 spk_alloc; +}; + +struct hdac_hdmi_eld { + bool monitor_present; + bool eld_valid; + int eld_size; + char eld_buffer[ELD_MAX_SIZE]; + struct hdac_hdmi_parsed_eld info; +}; + +struct hdac_hdmi_pin { + struct list_head head; + hda_nid_t nid; + bool mst_capable; + struct hdac_hdmi_port *ports; + int num_ports; + struct hdac_device *hdev; +}; + +struct hdac_hdmi_port { + struct list_head head; + int id; + struct hdac_hdmi_pin *pin; + int num_mux_nids; + hda_nid_t mux_nids[HDA_MAX_CONNECTIONS]; + struct hdac_hdmi_eld eld; + const char *jack_pin; + struct snd_soc_dapm_context *dapm; + const char *output_pin; +}; + +struct hdac_hdmi_pcm { + struct list_head head; + int pcm_id; + struct list_head port_list; + struct hdac_hdmi_cvt *cvt; + struct snd_soc_jack *jack; + int stream_tag; + int channels; + int format; + bool chmap_set; + unsigned char chmap[8]; /* ALSA API channel-map */ + struct mutex lock; + int jack_event; +}; + +struct hdac_hdmi_dai_port_map { + int dai_id; + struct hdac_hdmi_port *port; + struct hdac_hdmi_cvt *cvt; +}; + +struct hdac_hdmi_drv_data { + unsigned int vendor_nid; +}; + +struct hdac_hdmi_priv { + struct hdac_device *hdev; + struct snd_soc_component *component; + struct snd_card *card; + struct hdac_hdmi_dai_port_map dai_map[HDA_MAX_CVTS]; + struct list_head pin_list; + struct list_head cvt_list; + struct list_head pcm_list; + int num_pin; + int num_cvt; + int num_ports; + struct mutex pin_mutex; + struct hdac_chmap chmap; + struct hdac_hdmi_drv_data *drv_data; + struct snd_soc_dai_driver *dai_drv; +}; + +#define hdev_to_hdmi_priv(_hdev) dev_get_drvdata(&(_hdev)->dev) + +static struct hdac_hdmi_pcm * +hdac_hdmi_get_pcm_from_cvt(struct hdac_hdmi_priv *hdmi, + struct hdac_hdmi_cvt *cvt) +{ + struct hdac_hdmi_pcm *pcm; + + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (pcm->cvt == cvt) + return pcm; + } + + return NULL; +} + +static void hdac_hdmi_jack_report(struct hdac_hdmi_pcm *pcm, + struct hdac_hdmi_port *port, bool is_connect) +{ + struct hdac_device *hdev = port->pin->hdev; + + if (is_connect) + snd_soc_dapm_enable_pin(port->dapm, port->jack_pin); + else + snd_soc_dapm_disable_pin(port->dapm, port->jack_pin); + + if (is_connect) { + /* + * Report Jack connect event when a device is connected + * for the first time where same PCM is attached to multiple + * ports. + */ + if (pcm->jack_event == 0) { + dev_dbg(&hdev->dev, + "jack report for pcm=%d\n", + pcm->pcm_id); + snd_soc_jack_report(pcm->jack, SND_JACK_AVOUT, + SND_JACK_AVOUT); + } + pcm->jack_event++; + } else { + /* + * Report Jack disconnect event when a device is disconnected + * is the only last connected device when same PCM is attached + * to multiple ports. + */ + if (pcm->jack_event == 1) + snd_soc_jack_report(pcm->jack, 0, SND_JACK_AVOUT); + if (pcm->jack_event > 0) + pcm->jack_event--; + } + + snd_soc_dapm_sync(port->dapm); +} + +/* MST supported verbs */ +/* + * Get the no devices that can be connected to a port on the Pin widget. + */ +static int hdac_hdmi_get_port_len(struct hdac_device *hdev, hda_nid_t nid) +{ + unsigned int caps; + unsigned int type, param; + + caps = get_wcaps(hdev, nid); + type = get_wcaps_type(caps); + + if (!(caps & AC_WCAP_DIGITAL) || (type != AC_WID_PIN)) + return 0; + + param = snd_hdac_read_parm_uncached(hdev, nid, AC_PAR_DEVLIST_LEN); + if (param == -1) + return param; + + return param & AC_DEV_LIST_LEN_MASK; +} + +/* + * Get the port entry select on the pin. Return the port entry + * id selected on the pin. Return 0 means the first port entry + * is selected or MST is not supported. + */ +static int hdac_hdmi_port_select_get(struct hdac_device *hdev, + struct hdac_hdmi_port *port) +{ + return snd_hdac_codec_read(hdev, port->pin->nid, + 0, AC_VERB_GET_DEVICE_SEL, 0); +} + +/* + * Sets the selected port entry for the configuring Pin widget verb. + * returns error if port set is not equal to port get otherwise success + */ +static int hdac_hdmi_port_select_set(struct hdac_device *hdev, + struct hdac_hdmi_port *port) +{ + int num_ports; + + if (!port->pin->mst_capable) + return 0; + + /* AC_PAR_DEVLIST_LEN is 0 based. */ + num_ports = hdac_hdmi_get_port_len(hdev, port->pin->nid); + if (num_ports < 0) + return -EIO; + /* + * Device List Length is a 0 based integer value indicating the + * number of sink device that a MST Pin Widget can support. + */ + if (num_ports + 1 < port->id) + return 0; + + snd_hdac_codec_write(hdev, port->pin->nid, 0, + AC_VERB_SET_DEVICE_SEL, port->id); + + if (port->id != hdac_hdmi_port_select_get(hdev, port)) + return -EIO; + + dev_dbg(&hdev->dev, "Selected the port=%d\n", port->id); + + return 0; +} + +static struct hdac_hdmi_pcm *get_hdmi_pcm_from_id(struct hdac_hdmi_priv *hdmi, + int pcm_idx) +{ + struct hdac_hdmi_pcm *pcm; + + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (pcm->pcm_id == pcm_idx) + return pcm; + } + + return NULL; +} + +static unsigned int sad_format(const u8 *sad) +{ + return ((sad[0] >> 0x3) & 0x1f); +} + +static unsigned int sad_sample_bits_lpcm(const u8 *sad) +{ + return (sad[2] & 7); +} + +static int hdac_hdmi_eld_limit_formats(struct snd_pcm_runtime *runtime, + void *eld) +{ + u64 formats = SNDRV_PCM_FMTBIT_S16; + int i; + const u8 *sad, *eld_buf = eld; + + sad = drm_eld_sad(eld_buf); + if (!sad) + goto format_constraint; + + for (i = drm_eld_sad_count(eld_buf); i > 0; i--, sad += 3) { + if (sad_format(sad) == 1) { /* AUDIO_CODING_TYPE_LPCM */ + + /* + * the controller support 20 and 24 bits in 32 bit + * container so we set S32 + */ + if (sad_sample_bits_lpcm(sad) & 0x6) + formats |= SNDRV_PCM_FMTBIT_S32; + } + } + +format_constraint: + return snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, + formats); + +} + +static void +hdac_hdmi_set_dip_index(struct hdac_device *hdev, hda_nid_t pin_nid, + int packet_index, int byte_index) +{ + int val; + + val = (packet_index << 5) | (byte_index & 0x1f); + snd_hdac_codec_write(hdev, pin_nid, 0, AC_VERB_SET_HDMI_DIP_INDEX, val); +} + +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; +}; + +static int hdac_hdmi_setup_audio_infoframe(struct hdac_device *hdev, + struct hdac_hdmi_pcm *pcm, struct hdac_hdmi_port *port) +{ + uint8_t buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AUDIO_INFOFRAME_SIZE]; + struct hdmi_audio_infoframe frame; + struct hdac_hdmi_pin *pin = port->pin; + struct dp_audio_infoframe dp_ai; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_cvt *cvt = pcm->cvt; + u8 *dip; + int ret; + int i; + const u8 *eld_buf; + u8 conn_type; + int channels, ca; + + ca = snd_hdac_channel_allocation(hdev, port->eld.info.spk_alloc, + pcm->channels, pcm->chmap_set, true, pcm->chmap); + + channels = snd_hdac_get_active_channels(ca); + hdmi->chmap.ops.set_channel_count(hdev, cvt->nid, channels); + + snd_hdac_setup_channel_mapping(&hdmi->chmap, pin->nid, false, ca, + pcm->channels, pcm->chmap, pcm->chmap_set); + + eld_buf = port->eld.eld_buffer; + conn_type = drm_eld_get_conn_type(eld_buf); + + switch (conn_type) { + case DRM_ELD_CONN_TYPE_HDMI: + hdmi_audio_infoframe_init(&frame); + + frame.channels = channels; + frame.channel_allocation = ca; + + ret = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + break; + + case DRM_ELD_CONN_TYPE_DP: + memset(&dp_ai, 0, sizeof(dp_ai)); + dp_ai.type = 0x84; + dp_ai.len = 0x1b; + dp_ai.ver = 0x11 << 2; + dp_ai.CC02_CT47 = channels - 1; + dp_ai.CA = ca; + + dip = (u8 *)&dp_ai; + break; + + default: + dev_err(&hdev->dev, "Invalid connection type: %d\n", conn_type); + return -EIO; + } + + /* stop infoframe transmission */ + hdac_hdmi_set_dip_index(hdev, pin->nid, 0x0, 0x0); + snd_hdac_codec_write(hdev, pin->nid, 0, + AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_DISABLE); + + + /* Fill infoframe. Index auto-incremented */ + hdac_hdmi_set_dip_index(hdev, pin->nid, 0x0, 0x0); + if (conn_type == DRM_ELD_CONN_TYPE_HDMI) { + for (i = 0; i < sizeof(buffer); i++) + snd_hdac_codec_write(hdev, pin->nid, 0, + AC_VERB_SET_HDMI_DIP_DATA, buffer[i]); + } else { + for (i = 0; i < sizeof(dp_ai); i++) + snd_hdac_codec_write(hdev, pin->nid, 0, + AC_VERB_SET_HDMI_DIP_DATA, dip[i]); + } + + /* Start infoframe */ + hdac_hdmi_set_dip_index(hdev, pin->nid, 0x0, 0x0); + snd_hdac_codec_write(hdev, pin->nid, 0, + AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_BEST); + + return 0; +} + +static int hdac_hdmi_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct hdac_hdmi_priv *hdmi = snd_soc_dai_get_drvdata(dai); + struct hdac_device *hdev = hdmi->hdev; + struct hdac_hdmi_dai_port_map *dai_map; + struct hdac_hdmi_pcm *pcm; + + dev_dbg(&hdev->dev, "%s: strm_tag: %d\n", __func__, tx_mask); + + dai_map = &hdmi->dai_map[dai->id]; + + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + + if (pcm) + pcm->stream_tag = (tx_mask << 4); + + return 0; +} + +static int hdac_hdmi_set_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hparams, struct snd_soc_dai *dai) +{ + struct hdac_hdmi_priv *hdmi = snd_soc_dai_get_drvdata(dai); + struct hdac_device *hdev = hdmi->hdev; + struct hdac_hdmi_dai_port_map *dai_map; + struct hdac_hdmi_port *port; + struct hdac_hdmi_pcm *pcm; + int format; + + dai_map = &hdmi->dai_map[dai->id]; + port = dai_map->port; + + if (!port) + return -ENODEV; + + if ((!port->eld.monitor_present) || (!port->eld.eld_valid)) { + dev_err(&hdev->dev, + "device is not configured for this pin:port%d:%d\n", + port->pin->nid, port->id); + return -ENODEV; + } + + format = snd_hdac_calc_stream_format(params_rate(hparams), + params_channels(hparams), params_format(hparams), + dai->driver->playback.sig_bits, 0); + + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + if (!pcm) + return -EIO; + + pcm->format = format; + pcm->channels = params_channels(hparams); + + return 0; +} + +static int hdac_hdmi_query_port_connlist(struct hdac_device *hdev, + struct hdac_hdmi_pin *pin, + struct hdac_hdmi_port *port) +{ + if (!(get_wcaps(hdev, pin->nid) & AC_WCAP_CONN_LIST)) { + dev_warn(&hdev->dev, + "HDMI: pin %d wcaps %#x does not support connection list\n", + pin->nid, get_wcaps(hdev, pin->nid)); + return -EINVAL; + } + + if (hdac_hdmi_port_select_set(hdev, port) < 0) + return -EIO; + + port->num_mux_nids = snd_hdac_get_connections(hdev, pin->nid, + port->mux_nids, HDA_MAX_CONNECTIONS); + if (port->num_mux_nids == 0) + dev_warn(&hdev->dev, + "No connections found for pin:port %d:%d\n", + pin->nid, port->id); + + dev_dbg(&hdev->dev, "num_mux_nids %d for pin:port %d:%d\n", + port->num_mux_nids, pin->nid, port->id); + + return port->num_mux_nids; +} + +/* + * Query pcm list and return port to which stream is routed. + * + * Also query connection list of the pin, to validate the cvt to port map. + * + * Same stream rendering to multiple ports simultaneously can be done + * possibly, but not supported for now in driver. So return the first port + * connected. + */ +static struct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt( + struct hdac_device *hdev, + struct hdac_hdmi_priv *hdmi, + struct hdac_hdmi_cvt *cvt) +{ + struct hdac_hdmi_pcm *pcm; + struct hdac_hdmi_port *port = NULL; + int ret, i; + + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (pcm->cvt == cvt) { + if (list_empty(&pcm->port_list)) + continue; + + list_for_each_entry(port, &pcm->port_list, head) { + mutex_lock(&pcm->lock); + ret = hdac_hdmi_query_port_connlist(hdev, + port->pin, port); + mutex_unlock(&pcm->lock); + if (ret < 0) + continue; + + for (i = 0; i < port->num_mux_nids; i++) { + if (port->mux_nids[i] == cvt->nid && + port->eld.monitor_present && + port->eld.eld_valid) + return port; + } + } + } + } + + return NULL; +} + +/* + * This tries to get a valid pin and set the HW constraints based on the + * ELD. Even if a valid pin is not found return success so that device open + * doesn't fail. + */ +static int hdac_hdmi_pcm_open(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_hdmi_priv *hdmi = snd_soc_dai_get_drvdata(dai); + struct hdac_device *hdev = hdmi->hdev; + struct hdac_hdmi_dai_port_map *dai_map; + struct hdac_hdmi_cvt *cvt; + struct hdac_hdmi_port *port; + int ret; + + dai_map = &hdmi->dai_map[dai->id]; + + cvt = dai_map->cvt; + port = hdac_hdmi_get_port_from_cvt(hdev, hdmi, cvt); + + /* + * To make PA and other userland happy. + * userland scans devices so returning error does not help. + */ + if (!port) + return 0; + if ((!port->eld.monitor_present) || + (!port->eld.eld_valid)) { + + dev_warn(&hdev->dev, + "Failed: present?:%d ELD valid?:%d pin:port: %d:%d\n", + port->eld.monitor_present, port->eld.eld_valid, + port->pin->nid, port->id); + + return 0; + } + + dai_map->port = port; + + ret = hdac_hdmi_eld_limit_formats(substream->runtime, + port->eld.eld_buffer); + if (ret < 0) + return ret; + + return snd_pcm_hw_constraint_eld(substream->runtime, + port->eld.eld_buffer); +} + +static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_hdmi_priv *hdmi = snd_soc_dai_get_drvdata(dai); + struct hdac_hdmi_dai_port_map *dai_map; + struct hdac_hdmi_pcm *pcm; + + dai_map = &hdmi->dai_map[dai->id]; + + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + + if (pcm) { + mutex_lock(&pcm->lock); + pcm->chmap_set = false; + memset(pcm->chmap, 0, sizeof(pcm->chmap)); + pcm->channels = 0; + mutex_unlock(&pcm->lock); + } + + if (dai_map->port) + dai_map->port = NULL; +} + +static int +hdac_hdmi_query_cvt_params(struct hdac_device *hdev, struct hdac_hdmi_cvt *cvt) +{ + unsigned int chans; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + int err; + + chans = get_wcaps(hdev, cvt->nid); + chans = get_wcaps_channels(chans); + + cvt->params.channels_min = 2; + + cvt->params.channels_max = chans; + if (chans > hdmi->chmap.channels_max) + hdmi->chmap.channels_max = chans; + + err = snd_hdac_query_supported_pcm(hdev, cvt->nid, + &cvt->params.rates, + &cvt->params.formats, + &cvt->params.maxbps); + if (err < 0) + dev_err(&hdev->dev, + "Failed to query pcm params for nid %d: %d\n", + cvt->nid, err); + + return err; +} + +static int hdac_hdmi_fill_widget_info(struct device *dev, + struct snd_soc_dapm_widget *w, enum snd_soc_dapm_type id, + void *priv, const char *wname, const char *stream, + struct snd_kcontrol_new *wc, int numkc, + int (*event)(struct snd_soc_dapm_widget *, + struct snd_kcontrol *, int), unsigned short event_flags) +{ + w->id = id; + w->name = devm_kstrdup(dev, wname, GFP_KERNEL); + if (!w->name) + return -ENOMEM; + + w->sname = stream; + w->reg = SND_SOC_NOPM; + w->shift = 0; + w->kcontrol_news = wc; + w->num_kcontrols = numkc; + w->priv = priv; + w->event = event; + w->event_flags = event_flags; + + return 0; +} + +static void hdac_hdmi_fill_route(struct snd_soc_dapm_route *route, + const char *sink, const char *control, const char *src, + int (*handler)(struct snd_soc_dapm_widget *src, + struct snd_soc_dapm_widget *sink)) +{ + route->sink = sink; + route->source = src; + route->control = control; + route->connected = handler; +} + +static struct hdac_hdmi_pcm *hdac_hdmi_get_pcm(struct hdac_device *hdev, + struct hdac_hdmi_port *port) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm = NULL; + struct hdac_hdmi_port *p; + + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (list_empty(&pcm->port_list)) + continue; + + list_for_each_entry(p, &pcm->port_list, head) { + if (p->id == port->id && port->pin == p->pin) + return pcm; + } + } + + return NULL; +} + +static void hdac_hdmi_set_power_state(struct hdac_device *hdev, + hda_nid_t nid, unsigned int pwr_state) +{ + int count; + unsigned int state; + + if (get_wcaps(hdev, nid) & AC_WCAP_POWER) { + if (!snd_hdac_check_power_state(hdev, nid, pwr_state)) { + for (count = 0; count < 10; count++) { + snd_hdac_codec_read(hdev, nid, 0, + AC_VERB_SET_POWER_STATE, + pwr_state); + state = snd_hdac_sync_power_state(hdev, + nid, pwr_state); + if (!(state & AC_PWRST_ERROR)) + break; + } + } + } +} + +static void hdac_hdmi_set_amp(struct hdac_device *hdev, + hda_nid_t nid, int val) +{ + if (get_wcaps(hdev, nid) & AC_WCAP_OUT_AMP) + snd_hdac_codec_write(hdev, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, val); +} + + +static int hdac_hdmi_pin_output_widget_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_hdmi_port *port = w->priv; + struct hdac_device *hdev = dev_to_hdac_dev(w->dapm->dev); + struct hdac_hdmi_pcm *pcm; + + dev_dbg(&hdev->dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + pcm = hdac_hdmi_get_pcm(hdev, port); + if (!pcm) + return -EIO; + + /* set the device if pin is mst_capable */ + if (hdac_hdmi_port_select_set(hdev, port) < 0) + return -EIO; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + hdac_hdmi_set_power_state(hdev, port->pin->nid, AC_PWRST_D0); + + /* Enable out path for this pin widget */ + snd_hdac_codec_write(hdev, port->pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + hdac_hdmi_set_amp(hdev, port->pin->nid, AMP_OUT_UNMUTE); + + return hdac_hdmi_setup_audio_infoframe(hdev, pcm, port); + + case SND_SOC_DAPM_POST_PMD: + hdac_hdmi_set_amp(hdev, port->pin->nid, AMP_OUT_MUTE); + + /* Disable out path for this pin widget */ + snd_hdac_codec_write(hdev, port->pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0); + + hdac_hdmi_set_power_state(hdev, port->pin->nid, AC_PWRST_D3); + break; + + } + + return 0; +} + +static int hdac_hdmi_cvt_output_widget_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_hdmi_cvt *cvt = w->priv; + struct hdac_device *hdev = dev_to_hdac_dev(w->dapm->dev); + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm; + + dev_dbg(&hdev->dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, cvt); + if (!pcm) + return -EIO; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + hdac_hdmi_set_power_state(hdev, cvt->nid, AC_PWRST_D0); + + /* Enable transmission */ + snd_hdac_codec_write(hdev, cvt->nid, 0, + AC_VERB_SET_DIGI_CONVERT_1, 1); + + /* Category Code (CC) to zero */ + snd_hdac_codec_write(hdev, cvt->nid, 0, + AC_VERB_SET_DIGI_CONVERT_2, 0); + + snd_hdac_codec_write(hdev, cvt->nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, pcm->stream_tag); + snd_hdac_codec_write(hdev, cvt->nid, 0, + AC_VERB_SET_STREAM_FORMAT, pcm->format); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_hdac_codec_write(hdev, cvt->nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, 0); + snd_hdac_codec_write(hdev, cvt->nid, 0, + AC_VERB_SET_STREAM_FORMAT, 0); + + hdac_hdmi_set_power_state(hdev, cvt->nid, AC_PWRST_D3); + break; + + } + + return 0; +} + +static int hdac_hdmi_pin_mux_widget_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_hdmi_port *port = w->priv; + struct hdac_device *hdev = dev_to_hdac_dev(w->dapm->dev); + int mux_idx; + + dev_dbg(&hdev->dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + if (!kc) + kc = w->kcontrols[0]; + + mux_idx = dapm_kcontrol_get_value(kc); + + /* set the device if pin is mst_capable */ + if (hdac_hdmi_port_select_set(hdev, port) < 0) + return -EIO; + + if (mux_idx > 0) { + snd_hdac_codec_write(hdev, port->pin->nid, 0, + AC_VERB_SET_CONNECT_SEL, (mux_idx - 1)); + } + + return 0; +} + +/* + * Based on user selection, map the PINs with the PCMs. + */ +static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret; + struct hdac_hdmi_port *p, *p_next; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol); + struct snd_soc_dapm_context *dapm = w->dapm; + struct hdac_hdmi_port *port = w->priv; + struct hdac_device *hdev = dev_to_hdac_dev(dapm->dev); + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm = NULL; + const char *cvt_name = e->texts[ucontrol->value.enumerated.item[0]]; + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + if (ret < 0) + return ret; + + if (port == NULL) + return -EINVAL; + + mutex_lock(&hdmi->pin_mutex); + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (list_empty(&pcm->port_list)) + continue; + + list_for_each_entry_safe(p, p_next, &pcm->port_list, head) { + if (p == port && p->id == port->id && + p->pin == port->pin) { + hdac_hdmi_jack_report(pcm, port, false); + list_del(&p->head); + } + } + } + + /* + * Jack status is not reported during device probe as the + * PCMs are not registered by then. So report it here. + */ + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (!strcmp(cvt_name, pcm->cvt->name)) { + list_add_tail(&port->head, &pcm->port_list); + if (port->eld.monitor_present && port->eld.eld_valid) { + hdac_hdmi_jack_report(pcm, port, true); + mutex_unlock(&hdmi->pin_mutex); + return ret; + } + } + } + mutex_unlock(&hdmi->pin_mutex); + + return ret; +} + +/* + * Ideally the Mux inputs should be based on the num_muxs enumerated, but + * the display driver seem to be programming the connection list for the pin + * widget runtime. + * + * So programming all the possible inputs for the mux, the user has to take + * care of selecting the right one and leaving all other inputs selected to + * "NONE" + */ +static int hdac_hdmi_create_pin_port_muxs(struct hdac_device *hdev, + struct hdac_hdmi_port *port, + struct snd_soc_dapm_widget *widget, + const char *widget_name) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pin *pin = port->pin; + struct snd_kcontrol_new *kc; + struct hdac_hdmi_cvt *cvt; + struct soc_enum *se; + char kc_name[NAME_SIZE]; + char mux_items[NAME_SIZE]; + /* To hold inputs to the Pin mux */ + char *items[HDA_MAX_CONNECTIONS]; + int i = 0; + int num_items = hdmi->num_cvt + 1; + + kc = devm_kzalloc(&hdev->dev, sizeof(*kc), GFP_KERNEL); + if (!kc) + return -ENOMEM; + + se = devm_kzalloc(&hdev->dev, sizeof(*se), GFP_KERNEL); + if (!se) + return -ENOMEM; + + snprintf(kc_name, NAME_SIZE, "Pin %d port %d Input", + pin->nid, port->id); + kc->name = devm_kstrdup(&hdev->dev, kc_name, GFP_KERNEL); + if (!kc->name) + return -ENOMEM; + + kc->private_value = (long)se; + kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc->access = 0; + kc->info = snd_soc_info_enum_double; + kc->put = hdac_hdmi_set_pin_port_mux; + kc->get = snd_soc_dapm_get_enum_double; + + se->reg = SND_SOC_NOPM; + + /* enum texts: ["NONE", "cvt #", "cvt #", ...] */ + se->items = num_items; + se->mask = roundup_pow_of_two(se->items) - 1; + + sprintf(mux_items, "NONE"); + items[i] = devm_kstrdup(&hdev->dev, mux_items, GFP_KERNEL); + if (!items[i]) + return -ENOMEM; + + list_for_each_entry(cvt, &hdmi->cvt_list, head) { + i++; + sprintf(mux_items, "cvt %d", cvt->nid); + items[i] = devm_kstrdup(&hdev->dev, mux_items, GFP_KERNEL); + if (!items[i]) + return -ENOMEM; + } + + se->texts = devm_kmemdup(&hdev->dev, items, + (num_items * sizeof(char *)), GFP_KERNEL); + if (!se->texts) + return -ENOMEM; + + return hdac_hdmi_fill_widget_info(&hdev->dev, widget, + snd_soc_dapm_mux, port, widget_name, NULL, kc, 1, + hdac_hdmi_pin_mux_widget_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_REG); +} + +/* Add cvt <- input <- mux route map */ +static void hdac_hdmi_add_pinmux_cvt_route(struct hdac_device *hdev, + struct snd_soc_dapm_widget *widgets, + struct snd_soc_dapm_route *route, int rindex) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + const struct snd_kcontrol_new *kc; + struct soc_enum *se; + int mux_index = hdmi->num_cvt + hdmi->num_ports; + int i, j; + + for (i = 0; i < hdmi->num_ports; i++) { + kc = widgets[mux_index].kcontrol_news; + se = (struct soc_enum *)kc->private_value; + for (j = 0; j < hdmi->num_cvt; j++) { + hdac_hdmi_fill_route(&route[rindex], + widgets[mux_index].name, + se->texts[j + 1], + widgets[j].name, NULL); + + rindex++; + } + + mux_index++; + } +} + +/* + * Widgets are added in the below sequence + * Converter widgets for num converters enumerated + * Pin-port widgets for num ports for Pins enumerated + * Pin-port mux widgets to represent connenction list of pin widget + * + * For each port, one Mux and One output widget is added + * Total widgets elements = num_cvt + (num_ports * 2); + * + * Routes are added as below: + * pin-port mux -> pin (based on num_ports) + * cvt -> "Input sel control" -> pin-port_mux + * + * Total route elements: + * num_ports + (pin_muxes * num_cvt) + */ +static int create_fill_widget_route_map(struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_dapm_widget *widgets; + struct snd_soc_dapm_route *route; + struct hdac_device *hdev = dev_to_hdac_dev(dapm->dev); + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct snd_soc_dai_driver *dai_drv = hdmi->dai_drv; + char widget_name[NAME_SIZE]; + struct hdac_hdmi_cvt *cvt; + struct hdac_hdmi_pin *pin; + int ret, i = 0, num_routes = 0, j; + + if (list_empty(&hdmi->cvt_list) || list_empty(&hdmi->pin_list)) + return -EINVAL; + + widgets = devm_kzalloc(dapm->dev, (sizeof(*widgets) * + ((2 * hdmi->num_ports) + hdmi->num_cvt)), + GFP_KERNEL); + + if (!widgets) + return -ENOMEM; + + /* DAPM widgets to represent each converter widget */ + list_for_each_entry(cvt, &hdmi->cvt_list, head) { + sprintf(widget_name, "Converter %d", cvt->nid); + ret = hdac_hdmi_fill_widget_info(dapm->dev, &widgets[i], + snd_soc_dapm_aif_in, cvt, + widget_name, dai_drv[i].playback.stream_name, NULL, 0, + hdac_hdmi_cvt_output_widget_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); + if (ret < 0) + return ret; + i++; + } + + list_for_each_entry(pin, &hdmi->pin_list, head) { + for (j = 0; j < pin->num_ports; j++) { + sprintf(widget_name, "hif%d-%d Output", + pin->nid, pin->ports[j].id); + ret = hdac_hdmi_fill_widget_info(dapm->dev, &widgets[i], + snd_soc_dapm_output, &pin->ports[j], + widget_name, NULL, NULL, 0, + hdac_hdmi_pin_output_widget_event, + SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD); + if (ret < 0) + return ret; + pin->ports[j].output_pin = widgets[i].name; + i++; + } + } + + /* DAPM widgets to represent the connection list to pin widget */ + list_for_each_entry(pin, &hdmi->pin_list, head) { + for (j = 0; j < pin->num_ports; j++) { + sprintf(widget_name, "Pin%d-Port%d Mux", + pin->nid, pin->ports[j].id); + ret = hdac_hdmi_create_pin_port_muxs(hdev, + &pin->ports[j], &widgets[i], + widget_name); + if (ret < 0) + return ret; + i++; + + /* For cvt to pin_mux mapping */ + num_routes += hdmi->num_cvt; + + /* For pin_mux to pin mapping */ + num_routes++; + } + } + + route = devm_kzalloc(dapm->dev, (sizeof(*route) * num_routes), + GFP_KERNEL); + if (!route) + return -ENOMEM; + + i = 0; + /* Add pin <- NULL <- mux route map */ + list_for_each_entry(pin, &hdmi->pin_list, head) { + for (j = 0; j < pin->num_ports; j++) { + int sink_index = i + hdmi->num_cvt; + int src_index = sink_index + pin->num_ports * + hdmi->num_pin; + + hdac_hdmi_fill_route(&route[i], + widgets[sink_index].name, NULL, + widgets[src_index].name, NULL); + i++; + } + } + + hdac_hdmi_add_pinmux_cvt_route(hdev, widgets, route, i); + + snd_soc_dapm_new_controls(dapm, widgets, + ((2 * hdmi->num_ports) + hdmi->num_cvt)); + + snd_soc_dapm_add_routes(dapm, route, num_routes); + snd_soc_dapm_new_widgets(dapm->card); + + return 0; + +} + +static int hdac_hdmi_init_dai_map(struct hdac_device *hdev) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_dai_port_map *dai_map; + struct hdac_hdmi_cvt *cvt; + int dai_id = 0; + + if (list_empty(&hdmi->cvt_list)) + return -EINVAL; + + list_for_each_entry(cvt, &hdmi->cvt_list, head) { + dai_map = &hdmi->dai_map[dai_id]; + dai_map->dai_id = dai_id; + dai_map->cvt = cvt; + + dai_id++; + + if (dai_id == HDA_MAX_CVTS) { + dev_warn(&hdev->dev, + "Max dais supported: %d\n", dai_id); + break; + } + } + + return 0; +} + +static int hdac_hdmi_add_cvt(struct hdac_device *hdev, hda_nid_t nid) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_cvt *cvt; + char name[NAME_SIZE]; + + cvt = kzalloc(sizeof(*cvt), GFP_KERNEL); + if (!cvt) + return -ENOMEM; + + cvt->nid = nid; + sprintf(name, "cvt %d", cvt->nid); + cvt->name = kstrdup(name, GFP_KERNEL); + + list_add_tail(&cvt->head, &hdmi->cvt_list); + hdmi->num_cvt++; + + return hdac_hdmi_query_cvt_params(hdev, cvt); +} + +static int hdac_hdmi_parse_eld(struct hdac_device *hdev, + struct hdac_hdmi_port *port) +{ + unsigned int ver, mnl; + + ver = (port->eld.eld_buffer[DRM_ELD_VER] & DRM_ELD_VER_MASK) + >> DRM_ELD_VER_SHIFT; + + if (ver != ELD_VER_CEA_861D && ver != ELD_VER_PARTIAL) { + dev_err(&hdev->dev, "HDMI: Unknown ELD version %d\n", ver); + return -EINVAL; + } + + mnl = (port->eld.eld_buffer[DRM_ELD_CEA_EDID_VER_MNL] & + DRM_ELD_MNL_MASK) >> DRM_ELD_MNL_SHIFT; + + if (mnl > ELD_MAX_MNL) { + dev_err(&hdev->dev, "HDMI: MNL Invalid %d\n", mnl); + return -EINVAL; + } + + port->eld.info.spk_alloc = port->eld.eld_buffer[DRM_ELD_SPEAKER]; + + return 0; +} + +static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, + struct hdac_hdmi_port *port) +{ + struct hdac_device *hdev = pin->hdev; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm; + int size = 0; + int port_id = -1; + + if (!hdmi) + return; + + /* + * In case of non MST pin, get_eld info API expectes port + * to be -1. + */ + mutex_lock(&hdmi->pin_mutex); + port->eld.monitor_present = false; + + if (pin->mst_capable) + port_id = port->id; + + size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, + &port->eld.monitor_present, + port->eld.eld_buffer, + ELD_MAX_SIZE); + + if (size > 0) { + size = min(size, ELD_MAX_SIZE); + if (hdac_hdmi_parse_eld(hdev, port) < 0) + size = -EINVAL; + } + + if (size > 0) { + port->eld.eld_valid = true; + port->eld.eld_size = size; + } else { + port->eld.eld_valid = false; + port->eld.eld_size = 0; + } + + pcm = hdac_hdmi_get_pcm(hdev, port); + + if (!port->eld.monitor_present || !port->eld.eld_valid) { + + dev_err(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", + __func__, pin->nid, port->id); + + /* + * PCMs are not registered during device probe, so don't + * report jack here. It will be done in usermode mux + * control select. + */ + if (pcm) + hdac_hdmi_jack_report(pcm, port, false); + + mutex_unlock(&hdmi->pin_mutex); + return; + } + + if (port->eld.monitor_present && port->eld.eld_valid) { + if (pcm) + hdac_hdmi_jack_report(pcm, port, true); + + print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, + port->eld.eld_buffer, port->eld.eld_size, false); + + } + mutex_unlock(&hdmi->pin_mutex); +} + +static int hdac_hdmi_add_ports(struct hdac_hdmi_priv *hdmi, + struct hdac_hdmi_pin *pin) +{ + struct hdac_hdmi_port *ports; + int max_ports = HDA_MAX_PORTS; + int i; + + /* + * FIXME: max_port may vary for each platform, so pass this as + * as driver data or query from i915 interface when this API is + * implemented. + */ + + ports = kcalloc(max_ports, sizeof(*ports), GFP_KERNEL); + if (!ports) + return -ENOMEM; + + for (i = 0; i < max_ports; i++) { + ports[i].id = i; + ports[i].pin = pin; + } + pin->ports = ports; + pin->num_ports = max_ports; + return 0; +} + +static int hdac_hdmi_add_pin(struct hdac_device *hdev, hda_nid_t nid) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pin *pin; + int ret; + + pin = kzalloc(sizeof(*pin), GFP_KERNEL); + if (!pin) + return -ENOMEM; + + pin->nid = nid; + pin->mst_capable = false; + pin->hdev = hdev; + ret = hdac_hdmi_add_ports(hdmi, pin); + if (ret < 0) + return ret; + + list_add_tail(&pin->head, &hdmi->pin_list); + hdmi->num_pin++; + hdmi->num_ports += pin->num_ports; + + return 0; +} + +#define INTEL_VENDOR_NID 0x08 +#define INTEL_GLK_VENDOR_NID 0x0b +#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 hdac_hdmi_skl_enable_all_pins(struct hdac_device *hdev) +{ + unsigned int vendor_param; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + unsigned int vendor_nid = hdmi->drv_data->vendor_nid; + + vendor_param = snd_hdac_codec_read(hdev, 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_hdac_codec_read(hdev, vendor_nid, 0, + INTEL_SET_VENDOR_VERB, vendor_param); + if (vendor_param == -1) + return; +} + +static void hdac_hdmi_skl_enable_dp12(struct hdac_device *hdev) +{ + unsigned int vendor_param; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + unsigned int vendor_nid = hdmi->drv_data->vendor_nid; + + vendor_param = snd_hdac_codec_read(hdev, 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; + vendor_param = snd_hdac_codec_read(hdev, vendor_nid, 0, + INTEL_SET_VENDOR_VERB, vendor_param); + if (vendor_param == -1) + return; + +} + +static const struct snd_soc_dai_ops hdmi_dai_ops = { + .startup = hdac_hdmi_pcm_open, + .shutdown = hdac_hdmi_pcm_close, + .hw_params = hdac_hdmi_set_hw_params, + .set_tdm_slot = hdac_hdmi_set_tdm_slot, +}; + +/* + * Each converter can support a stream independently. So a dai is created + * based on the number of converter queried. + */ +static int hdac_hdmi_create_dais(struct hdac_device *hdev, + struct snd_soc_dai_driver **dais, + struct hdac_hdmi_priv *hdmi, int num_dais) +{ + struct snd_soc_dai_driver *hdmi_dais; + struct hdac_hdmi_cvt *cvt; + char name[NAME_SIZE], dai_name[NAME_SIZE]; + int i = 0; + u32 rates, bps; + unsigned int rate_max = 384000, rate_min = 8000; + u64 formats; + int ret; + + hdmi_dais = devm_kzalloc(&hdev->dev, + (sizeof(*hdmi_dais) * num_dais), + GFP_KERNEL); + if (!hdmi_dais) + return -ENOMEM; + + list_for_each_entry(cvt, &hdmi->cvt_list, head) { + ret = snd_hdac_query_supported_pcm(hdev, cvt->nid, + &rates, &formats, &bps); + if (ret) + return ret; + + /* Filter out 44.1, 88.2 and 176.4Khz */ + rates &= ~(SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_176400); + if (!rates) + return -EINVAL; + + sprintf(dai_name, "intel-hdmi-hifi%d", i+1); + hdmi_dais[i].name = devm_kstrdup(&hdev->dev, + dai_name, GFP_KERNEL); + + if (!hdmi_dais[i].name) + return -ENOMEM; + + snprintf(name, sizeof(name), "hifi%d", i+1); + hdmi_dais[i].playback.stream_name = + devm_kstrdup(&hdev->dev, name, GFP_KERNEL); + if (!hdmi_dais[i].playback.stream_name) + return -ENOMEM; + + /* + * Set caps based on capability queried from the converter. + * It will be constrained runtime based on ELD queried. + */ + hdmi_dais[i].playback.formats = formats; + hdmi_dais[i].playback.rates = rates; + hdmi_dais[i].playback.rate_max = rate_max; + hdmi_dais[i].playback.rate_min = rate_min; + hdmi_dais[i].playback.channels_min = 2; + hdmi_dais[i].playback.channels_max = 2; + hdmi_dais[i].playback.sig_bits = bps; + hdmi_dais[i].ops = &hdmi_dai_ops; + i++; + } + + *dais = hdmi_dais; + hdmi->dai_drv = hdmi_dais; + + return 0; +} + +/* + * Parse all nodes and store the cvt/pin nids in array + * Add one time initialization for pin and cvt widgets + */ +static int hdac_hdmi_parse_and_map_nid(struct hdac_device *hdev, + struct snd_soc_dai_driver **dais, int *num_dais) +{ + hda_nid_t nid; + int i, num_nodes; + struct hdac_hdmi_cvt *temp_cvt, *cvt_next; + struct hdac_hdmi_pin *temp_pin, *pin_next; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + int ret; + + hdac_hdmi_skl_enable_all_pins(hdev); + hdac_hdmi_skl_enable_dp12(hdev); + + num_nodes = snd_hdac_get_sub_nodes(hdev, hdev->afg, &nid); + if (!nid || num_nodes <= 0) { + dev_warn(&hdev->dev, "HDMI: failed to get afg sub nodes\n"); + return -EINVAL; + } + + for (i = 0; i < num_nodes; i++, nid++) { + unsigned int caps; + unsigned int type; + + caps = get_wcaps(hdev, nid); + type = get_wcaps_type(caps); + + if (!(caps & AC_WCAP_DIGITAL)) + continue; + + switch (type) { + + case AC_WID_AUD_OUT: + ret = hdac_hdmi_add_cvt(hdev, nid); + if (ret < 0) + goto free_widgets; + break; + + case AC_WID_PIN: + ret = hdac_hdmi_add_pin(hdev, nid); + if (ret < 0) + goto free_widgets; + break; + } + } + + if (!hdmi->num_pin || !hdmi->num_cvt) { + ret = -EIO; + goto free_widgets; + } + + ret = hdac_hdmi_create_dais(hdev, dais, hdmi, hdmi->num_cvt); + if (ret) { + dev_err(&hdev->dev, "Failed to create dais with err: %d\n", + ret); + goto free_widgets; + } + + *num_dais = hdmi->num_cvt; + ret = hdac_hdmi_init_dai_map(hdev); + if (ret < 0) + goto free_widgets; + + return ret; + +free_widgets: + list_for_each_entry_safe(temp_cvt, cvt_next, &hdmi->cvt_list, head) { + list_del(&temp_cvt->head); + kfree(temp_cvt->name); + kfree(temp_cvt); + } + + list_for_each_entry_safe(temp_pin, pin_next, &hdmi->pin_list, head) { + for (i = 0; i < temp_pin->num_ports; i++) + temp_pin->ports[i].pin = NULL; + kfree(temp_pin->ports); + list_del(&temp_pin->head); + kfree(temp_pin); + } + + return ret; +} + +static int hdac_hdmi_pin2port(void *aptr, int pin) +{ + return pin - 4; /* map NID 0x05 -> port #1 */ +} + +static void hdac_hdmi_eld_notify_cb(void *aptr, int port, int pipe) +{ + struct hdac_device *hdev = aptr; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pin *pin = NULL; + struct hdac_hdmi_port *hport = NULL; + struct snd_soc_component *component = hdmi->component; + int i; + + /* Don't know how this mapping is derived */ + hda_nid_t pin_nid = port + 0x04; + + dev_dbg(&hdev->dev, "%s: for pin:%d port=%d\n", __func__, + pin_nid, pipe); + + /* + * skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume. Also since the ELD and + * connection states are updated in anyway at the end of the resume, + * we can skip it when received during PM process. + */ + if (snd_power_get_state(component->card->snd_card) != + SNDRV_CTL_POWER_D0) + return; + + if (atomic_read(&hdev->in_pm)) + return; + + list_for_each_entry(pin, &hdmi->pin_list, head) { + if (pin->nid != pin_nid) + continue; + + /* In case of non MST pin, pipe is -1 */ + if (pipe == -1) { + pin->mst_capable = false; + /* if not MST, default is port[0] */ + hport = &pin->ports[0]; + } else { + for (i = 0; i < pin->num_ports; i++) { + pin->mst_capable = true; + if (pin->ports[i].id == pipe) { + hport = &pin->ports[i]; + break; + } + } + } + + if (hport) + hdac_hdmi_present_sense(pin, hport); + } + +} + +static struct drm_audio_component_audio_ops aops = { + .pin2port = hdac_hdmi_pin2port, + .pin_eld_notify = hdac_hdmi_eld_notify_cb, +}; + +static struct snd_pcm *hdac_hdmi_get_pcm_from_id(struct snd_soc_card *card, + int device) +{ + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + if (rtd->pcm && (rtd->pcm->device == device)) + return rtd->pcm; + } + + return NULL; +} + +/* create jack pin kcontrols */ +static int create_fill_jack_kcontrols(struct snd_soc_card *card, + struct hdac_device *hdev) +{ + struct hdac_hdmi_pin *pin; + struct snd_kcontrol_new *kc; + char kc_name[NAME_SIZE], xname[NAME_SIZE]; + char *name; + int i = 0, j; + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct snd_soc_component *component = hdmi->component; + + kc = devm_kcalloc(component->dev, hdmi->num_ports, + sizeof(*kc), GFP_KERNEL); + + if (!kc) + return -ENOMEM; + + list_for_each_entry(pin, &hdmi->pin_list, head) { + for (j = 0; j < pin->num_ports; j++) { + snprintf(xname, sizeof(xname), "hif%d-%d Jack", + pin->nid, pin->ports[j].id); + name = devm_kstrdup(component->dev, xname, GFP_KERNEL); + if (!name) + return -ENOMEM; + snprintf(kc_name, sizeof(kc_name), "%s Switch", xname); + kc[i].name = devm_kstrdup(component->dev, kc_name, + GFP_KERNEL); + if (!kc[i].name) + return -ENOMEM; + + kc[i].private_value = (unsigned long)name; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].access = 0; + kc[i].info = snd_soc_dapm_info_pin_switch; + kc[i].put = snd_soc_dapm_put_pin_switch; + kc[i].get = snd_soc_dapm_get_pin_switch; + i++; + } + } + + return snd_soc_add_card_controls(card, kc, i); +} + +int hdac_hdmi_jack_port_init(struct snd_soc_component *component, + struct snd_soc_dapm_context *dapm) +{ + struct hdac_hdmi_priv *hdmi = snd_soc_component_get_drvdata(component); + struct hdac_device *hdev = hdmi->hdev; + struct hdac_hdmi_pin *pin; + struct snd_soc_dapm_widget *widgets; + struct snd_soc_dapm_route *route; + char w_name[NAME_SIZE]; + int i = 0, j, ret; + + widgets = devm_kcalloc(dapm->dev, hdmi->num_ports, + sizeof(*widgets), GFP_KERNEL); + + if (!widgets) + return -ENOMEM; + + route = devm_kcalloc(dapm->dev, hdmi->num_ports, + sizeof(*route), GFP_KERNEL); + if (!route) + return -ENOMEM; + + /* create Jack DAPM widget */ + list_for_each_entry(pin, &hdmi->pin_list, head) { + for (j = 0; j < pin->num_ports; j++) { + snprintf(w_name, sizeof(w_name), "hif%d-%d Jack", + pin->nid, pin->ports[j].id); + + ret = hdac_hdmi_fill_widget_info(dapm->dev, &widgets[i], + snd_soc_dapm_spk, NULL, + w_name, NULL, NULL, 0, NULL, 0); + if (ret < 0) + return ret; + + pin->ports[j].jack_pin = widgets[i].name; + pin->ports[j].dapm = dapm; + + /* add to route from Jack widget to output */ + hdac_hdmi_fill_route(&route[i], pin->ports[j].jack_pin, + NULL, pin->ports[j].output_pin, NULL); + + i++; + } + } + + /* Add Route from Jack widget to the output widget */ + ret = snd_soc_dapm_new_controls(dapm, widgets, hdmi->num_ports); + if (ret < 0) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, route, hdmi->num_ports); + if (ret < 0) + return ret; + + ret = snd_soc_dapm_new_widgets(dapm->card); + if (ret < 0) + return ret; + + /* Add Jack Pin switch Kcontrol */ + ret = create_fill_jack_kcontrols(dapm->card, hdev); + + if (ret < 0) + return ret; + + /* default set the Jack Pin switch to OFF */ + list_for_each_entry(pin, &hdmi->pin_list, head) { + for (j = 0; j < pin->num_ports; j++) + snd_soc_dapm_disable_pin(pin->ports[j].dapm, + pin->ports[j].jack_pin); + } + + return 0; +} +EXPORT_SYMBOL_GPL(hdac_hdmi_jack_port_init); + +int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device, + struct snd_soc_jack *jack) +{ + struct snd_soc_component *component = dai->component; + struct hdac_hdmi_priv *hdmi = snd_soc_component_get_drvdata(component); + struct hdac_device *hdev = hdmi->hdev; + struct hdac_hdmi_pcm *pcm; + struct snd_pcm *snd_pcm; + int err; + + /* + * this is a new PCM device, create new pcm and + * add to the pcm list + */ + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + pcm->pcm_id = device; + pcm->cvt = hdmi->dai_map[dai->id].cvt; + pcm->jack_event = 0; + pcm->jack = jack; + mutex_init(&pcm->lock); + INIT_LIST_HEAD(&pcm->port_list); + snd_pcm = hdac_hdmi_get_pcm_from_id(dai->component->card, device); + if (snd_pcm) { + err = snd_hdac_add_chmap_ctls(snd_pcm, device, &hdmi->chmap); + if (err < 0) { + dev_err(&hdev->dev, + "chmap control add failed with err: %d for pcm: %d\n", + err, device); + kfree(pcm); + return err; + } + } + + list_add_tail(&pcm->head, &hdmi->pcm_list); + + return 0; +} +EXPORT_SYMBOL_GPL(hdac_hdmi_jack_init); + +static void hdac_hdmi_present_sense_all_pins(struct hdac_device *hdev, + struct hdac_hdmi_priv *hdmi, bool detect_pin_caps) +{ + int i; + struct hdac_hdmi_pin *pin; + + list_for_each_entry(pin, &hdmi->pin_list, head) { + if (detect_pin_caps) { + + if (hdac_hdmi_get_port_len(hdev, pin->nid) == 0) + pin->mst_capable = false; + else + pin->mst_capable = true; + } + + for (i = 0; i < pin->num_ports; i++) { + if (!pin->mst_capable && i > 0) + continue; + + hdac_hdmi_present_sense(pin, &pin->ports[i]); + } + } +} + +static int hdmi_codec_probe(struct snd_soc_component *component) +{ + struct hdac_hdmi_priv *hdmi = snd_soc_component_get_drvdata(component); + struct hdac_device *hdev = hdmi->hdev; + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct hdac_ext_link *hlink = NULL; + int ret; + + hdmi->component = component; + + /* + * hold the ref while we probe, also no need to drop the ref on + * exit, we call pm_runtime_suspend() so that will do for us + */ + hlink = snd_hdac_ext_bus_get_link(hdev->bus, dev_name(&hdev->dev)); + if (!hlink) { + dev_err(&hdev->dev, "hdac link not found\n"); + return -EIO; + } + + snd_hdac_ext_bus_link_get(hdev->bus, hlink); + + ret = create_fill_widget_route_map(dapm); + if (ret < 0) + return ret; + + aops.audio_ptr = hdev; + ret = snd_hdac_acomp_register_notifier(hdev->bus, &aops); + if (ret < 0) { + dev_err(&hdev->dev, "notifier register failed: err: %d\n", ret); + return ret; + } + + hdac_hdmi_present_sense_all_pins(hdev, hdmi, true); + /* Imp: Store the card pointer in hda_codec */ + hdmi->card = dapm->card->snd_card; + + /* + * Setup a device_link between card device and HDMI codec device. + * The card device is the consumer and the HDMI codec device is + * the supplier. With this setting, we can make sure that the audio + * domain in display power will be always turned on before operating + * on the HDMI audio codec registers. + * Let's use the flag DL_FLAG_AUTOREMOVE_CONSUMER. This can make + * sure the device link is freed when the machine driver is removed. + */ + device_link_add(component->card->dev, &hdev->dev, DL_FLAG_RPM_ACTIVE | + DL_FLAG_AUTOREMOVE_CONSUMER); + /* + * hdac_device core already sets the state to active and calls + * get_noresume. So enable runtime and set the device to suspend. + */ + pm_runtime_enable(&hdev->dev); + pm_runtime_put(&hdev->dev); + pm_runtime_suspend(&hdev->dev); + + return 0; +} + +static void hdmi_codec_remove(struct snd_soc_component *component) +{ + struct hdac_hdmi_priv *hdmi = snd_soc_component_get_drvdata(component); + struct hdac_device *hdev = hdmi->hdev; + int ret; + + ret = snd_hdac_acomp_register_notifier(hdev->bus, NULL); + if (ret < 0) + dev_err(&hdev->dev, "notifier unregister failed: err: %d\n", + ret); + + pm_runtime_disable(&hdev->dev); +} + +#ifdef CONFIG_PM +static int hdmi_codec_prepare(struct device *dev) +{ + struct hdac_device *hdev = dev_to_hdac_dev(dev); + + pm_runtime_get_sync(&hdev->dev); + + /* + * Power down afg. + * codec_read is preferred over codec_write to set the power state. + * This way verb is send to set the power state and response + * is received. So setting power state is ensured without using loop + * to read the state. + */ + snd_hdac_codec_read(hdev, hdev->afg, 0, AC_VERB_SET_POWER_STATE, + AC_PWRST_D3); + + return 0; +} + +static void hdmi_codec_complete(struct device *dev) +{ + struct hdac_device *hdev = dev_to_hdac_dev(dev); + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + + /* Power up afg */ + snd_hdac_codec_read(hdev, hdev->afg, 0, AC_VERB_SET_POWER_STATE, + AC_PWRST_D0); + + hdac_hdmi_skl_enable_all_pins(hdev); + hdac_hdmi_skl_enable_dp12(hdev); + + /* + * As the ELD notify callback request is not entertained while the + * device is in suspend state. Need to manually check detection of + * all pins here. pin capablity change is not support, so use the + * already set pin caps. + */ + hdac_hdmi_present_sense_all_pins(hdev, hdmi, false); + + pm_runtime_put_sync(&hdev->dev); +} +#else +#define hdmi_codec_prepare NULL +#define hdmi_codec_complete NULL +#endif + +static const struct snd_soc_component_driver hdmi_hda_codec = { + .probe = hdmi_codec_probe, + .remove = hdmi_codec_remove, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static void hdac_hdmi_get_chmap(struct hdac_device *hdev, int pcm_idx, + unsigned char *chmap) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx); + + memcpy(chmap, pcm->chmap, ARRAY_SIZE(pcm->chmap)); +} + +static void hdac_hdmi_set_chmap(struct hdac_device *hdev, int pcm_idx, + unsigned char *chmap, int prepared) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx); + struct hdac_hdmi_port *port; + + if (!pcm) + return; + + if (list_empty(&pcm->port_list)) + return; + + mutex_lock(&pcm->lock); + pcm->chmap_set = true; + memcpy(pcm->chmap, chmap, ARRAY_SIZE(pcm->chmap)); + list_for_each_entry(port, &pcm->port_list, head) + if (prepared) + hdac_hdmi_setup_audio_infoframe(hdev, pcm, port); + mutex_unlock(&pcm->lock); +} + +static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdev, int pcm_idx) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx); + + if (!pcm) + return false; + + if (list_empty(&pcm->port_list)) + return false; + + return true; +} + +static int hdac_hdmi_get_spk_alloc(struct hdac_device *hdev, int pcm_idx) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx); + struct hdac_hdmi_port *port; + + if (!pcm) + return 0; + + if (list_empty(&pcm->port_list)) + return 0; + + port = list_first_entry(&pcm->port_list, struct hdac_hdmi_port, head); + + if (!port) + return 0; + + if (!port || !port->eld.eld_valid) + return 0; + + return port->eld.info.spk_alloc; +} + +static struct hdac_hdmi_drv_data intel_glk_drv_data = { + .vendor_nid = INTEL_GLK_VENDOR_NID, +}; + +static struct hdac_hdmi_drv_data intel_drv_data = { + .vendor_nid = INTEL_VENDOR_NID, +}; + +static int hdac_hdmi_dev_probe(struct hdac_device *hdev) +{ + struct hdac_hdmi_priv *hdmi_priv = NULL; + struct snd_soc_dai_driver *hdmi_dais = NULL; + struct hdac_ext_link *hlink = NULL; + int num_dais = 0; + int ret = 0; + struct hdac_driver *hdrv = drv_to_hdac_driver(hdev->dev.driver); + const struct hda_device_id *hdac_id = hdac_get_device_id(hdev, hdrv); + + /* hold the ref while we probe */ + hlink = snd_hdac_ext_bus_get_link(hdev->bus, dev_name(&hdev->dev)); + if (!hlink) { + dev_err(&hdev->dev, "hdac link not found\n"); + return -EIO; + } + + snd_hdac_ext_bus_link_get(hdev->bus, hlink); + + hdmi_priv = devm_kzalloc(&hdev->dev, sizeof(*hdmi_priv), GFP_KERNEL); + if (hdmi_priv == NULL) + return -ENOMEM; + + snd_hdac_register_chmap_ops(hdev, &hdmi_priv->chmap); + hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap; + hdmi_priv->chmap.ops.set_chmap = hdac_hdmi_set_chmap; + hdmi_priv->chmap.ops.is_pcm_attached = is_hdac_hdmi_pcm_attached; + hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc; + hdmi_priv->hdev = hdev; + + if (!hdac_id) + return -ENODEV; + + if (hdac_id->driver_data) + hdmi_priv->drv_data = + (struct hdac_hdmi_drv_data *)hdac_id->driver_data; + else + hdmi_priv->drv_data = &intel_drv_data; + + dev_set_drvdata(&hdev->dev, hdmi_priv); + + INIT_LIST_HEAD(&hdmi_priv->pin_list); + INIT_LIST_HEAD(&hdmi_priv->cvt_list); + INIT_LIST_HEAD(&hdmi_priv->pcm_list); + mutex_init(&hdmi_priv->pin_mutex); + + /* + * Turned off in the runtime_suspend during the first explicit + * pm_runtime_suspend call. + */ + ret = snd_hdac_display_power(hdev->bus, true); + if (ret < 0) { + dev_err(&hdev->dev, + "Cannot turn on display power on i915 err: %d\n", + ret); + return ret; + } + + ret = hdac_hdmi_parse_and_map_nid(hdev, &hdmi_dais, &num_dais); + if (ret < 0) { + dev_err(&hdev->dev, + "Failed in parse and map nid with err: %d\n", ret); + return ret; + } + snd_hdac_refresh_widgets(hdev, true); + + /* ASoC specific initialization */ + ret = devm_snd_soc_register_component(&hdev->dev, &hdmi_hda_codec, + hdmi_dais, num_dais); + + snd_hdac_ext_bus_link_put(hdev->bus, hlink); + + return ret; +} + +static int hdac_hdmi_dev_remove(struct hdac_device *hdev) +{ + struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); + struct hdac_hdmi_pin *pin, *pin_next; + struct hdac_hdmi_cvt *cvt, *cvt_next; + struct hdac_hdmi_pcm *pcm, *pcm_next; + struct hdac_hdmi_port *port, *port_next; + int i; + + list_for_each_entry_safe(pcm, pcm_next, &hdmi->pcm_list, head) { + pcm->cvt = NULL; + if (list_empty(&pcm->port_list)) + continue; + + list_for_each_entry_safe(port, port_next, + &pcm->port_list, head) + list_del(&port->head); + + list_del(&pcm->head); + kfree(pcm); + } + + list_for_each_entry_safe(cvt, cvt_next, &hdmi->cvt_list, head) { + list_del(&cvt->head); + kfree(cvt->name); + kfree(cvt); + } + + list_for_each_entry_safe(pin, pin_next, &hdmi->pin_list, head) { + for (i = 0; i < pin->num_ports; i++) + pin->ports[i].pin = NULL; + kfree(pin->ports); + list_del(&pin->head); + kfree(pin); + } + + return 0; +} + +#ifdef CONFIG_PM +/* + * Power management sequences + * ========================== + * + * The following explains the PM handling of HDAC HDMI with its parent + * device SKL and display power usage + * + * Probe + * ----- + * In SKL probe, + * 1. skl_probe_work() powers up the display (refcount++ -> 1) + * 2. enumerates the codecs on the link + * 3. powers down the display (refcount-- -> 0) + * + * In HDAC HDMI probe, + * 1. hdac_hdmi_dev_probe() powers up the display (refcount++ -> 1) + * 2. probe the codec + * 3. put the HDAC HDMI device to runtime suspend + * 4. hdac_hdmi_runtime_suspend() powers down the display (refcount-- -> 0) + * + * Once children are runtime suspended, SKL device also goes to runtime + * suspend + * + * HDMI Playback + * ------------- + * Open HDMI device, + * 1. skl_runtime_resume() invoked + * 2. hdac_hdmi_runtime_resume() powers up the display (refcount++ -> 1) + * + * Close HDMI device, + * 1. hdac_hdmi_runtime_suspend() powers down the display (refcount-- -> 0) + * 2. skl_runtime_suspend() invoked + * + * S0/S3 Cycle with playback in progress + * ------------------------------------- + * When the device is opened for playback, the device is runtime active + * already and the display refcount is 1 as explained above. + * + * Entering to S3, + * 1. hdmi_codec_prepare() invoke the runtime resume of codec which just + * increments the PM runtime usage count of the codec since the device + * is in use already + * 2. skl_suspend() powers down the display (refcount-- -> 0) + * + * Wakeup from S3, + * 1. skl_resume() powers up the display (refcount++ -> 1) + * 2. hdmi_codec_complete() invokes the runtime suspend of codec which just + * decrements the PM runtime usage count of the codec since the device + * is in use already + * + * Once playback is stopped, the display refcount is set to 0 as explained + * above in the HDMI playback sequence. The PM handlings are designed in + * such way that to balance the refcount of display power when the codec + * device put to S3 while playback is going on. + * + * S0/S3 Cycle without playback in progress + * ---------------------------------------- + * Entering to S3, + * 1. hdmi_codec_prepare() invoke the runtime resume of codec + * 2. skl_runtime_resume() invoked + * 3. hdac_hdmi_runtime_resume() powers up the display (refcount++ -> 1) + * 4. skl_suspend() powers down the display (refcount-- -> 0) + * + * Wakeup from S3, + * 1. skl_resume() powers up the display (refcount++ -> 1) + * 2. hdmi_codec_complete() invokes the runtime suspend of codec + * 3. hdac_hdmi_runtime_suspend() powers down the display (refcount-- -> 0) + * 4. skl_runtime_suspend() invoked + */ +static int hdac_hdmi_runtime_suspend(struct device *dev) +{ + struct hdac_device *hdev = dev_to_hdac_dev(dev); + struct hdac_bus *bus = hdev->bus; + struct hdac_ext_link *hlink = NULL; + int err; + + dev_dbg(dev, "Enter: %s\n", __func__); + + /* controller may not have been initialized for the first time */ + if (!bus) + return 0; + + /* + * Power down afg. + * codec_read is preferred over codec_write to set the power state. + * This way verb is send to set the power state and response + * is received. So setting power state is ensured without using loop + * to read the state. + */ + snd_hdac_codec_read(hdev, hdev->afg, 0, AC_VERB_SET_POWER_STATE, + AC_PWRST_D3); + + hlink = snd_hdac_ext_bus_get_link(bus, dev_name(dev)); + if (!hlink) { + dev_err(dev, "hdac link not found\n"); + return -EIO; + } + + snd_hdac_ext_bus_link_put(bus, hlink); + + err = snd_hdac_display_power(bus, false); + if (err < 0) + dev_err(dev, "Cannot turn off display power on i915\n"); + + return err; +} + +static int hdac_hdmi_runtime_resume(struct device *dev) +{ + struct hdac_device *hdev = dev_to_hdac_dev(dev); + struct hdac_bus *bus = hdev->bus; + struct hdac_ext_link *hlink = NULL; + int err; + + dev_dbg(dev, "Enter: %s\n", __func__); + + /* controller may not have been initialized for the first time */ + if (!bus) + return 0; + + hlink = snd_hdac_ext_bus_get_link(bus, dev_name(dev)); + if (!hlink) { + dev_err(dev, "hdac link not found\n"); + return -EIO; + } + + snd_hdac_ext_bus_link_get(bus, hlink); + + err = snd_hdac_display_power(bus, true); + if (err < 0) { + dev_err(dev, "Cannot turn on display power on i915\n"); + return err; + } + + hdac_hdmi_skl_enable_all_pins(hdev); + hdac_hdmi_skl_enable_dp12(hdev); + + /* Power up afg */ + snd_hdac_codec_read(hdev, hdev->afg, 0, AC_VERB_SET_POWER_STATE, + AC_PWRST_D0); + + return 0; +} +#else +#define hdac_hdmi_runtime_suspend NULL +#define hdac_hdmi_runtime_resume NULL +#endif + +static const struct dev_pm_ops hdac_hdmi_pm = { + SET_RUNTIME_PM_OPS(hdac_hdmi_runtime_suspend, hdac_hdmi_runtime_resume, NULL) + .prepare = hdmi_codec_prepare, + .complete = hdmi_codec_complete, +}; + +static const struct hda_device_id hdmi_list[] = { + HDA_CODEC_EXT_ENTRY(0x80862809, 0x100000, "Skylake HDMI", 0), + HDA_CODEC_EXT_ENTRY(0x8086280a, 0x100000, "Broxton HDMI", 0), + HDA_CODEC_EXT_ENTRY(0x8086280b, 0x100000, "Kabylake HDMI", 0), + HDA_CODEC_EXT_ENTRY(0x8086280c, 0x100000, "Cannonlake HDMI", + &intel_glk_drv_data), + HDA_CODEC_EXT_ENTRY(0x8086280d, 0x100000, "Geminilake HDMI", + &intel_glk_drv_data), + {} +}; + +MODULE_DEVICE_TABLE(hdaudio, hdmi_list); + +static struct hdac_driver hdmi_driver = { + .driver = { + .name = "HDMI HDA Codec", + .pm = &hdac_hdmi_pm, + }, + .id_table = hdmi_list, + .probe = hdac_hdmi_dev_probe, + .remove = hdac_hdmi_dev_remove, +}; + +static int __init hdmi_init(void) +{ + return snd_hda_ext_driver_register(&hdmi_driver); +} + +static void __exit hdmi_exit(void) +{ + snd_hda_ext_driver_unregister(&hdmi_driver); +} + +module_init(hdmi_init); +module_exit(hdmi_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("HDMI HD codec"); +MODULE_AUTHOR("Samreen Nilofer"); +MODULE_AUTHOR("Subhransu S. Prusty"); diff --git a/sound/soc/codecs/hdac_hdmi.h b/sound/soc/codecs/hdac_hdmi.h new file mode 100644 index 000000000..4fa2fc9ee --- /dev/null +++ b/sound/soc/codecs/hdac_hdmi.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __HDAC_HDMI_H__ +#define __HDAC_HDMI_H__ + +int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int pcm, + struct snd_soc_jack *jack); + +int hdac_hdmi_jack_port_init(struct snd_soc_component *component, + struct snd_soc_dapm_context *dapm); +#endif /* __HDAC_HDMI_H__ */ diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c new file mode 100644 index 000000000..7994e8ddc --- /dev/null +++ b/sound/soc/codecs/hdmi-codec.c @@ -0,0 +1,826 @@ +/* + * ALSA SoC codec for HDMI encoder drivers + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Jyri Sarha + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* This is only to get MAX_ELD_BYTES */ + +#define HDMI_CODEC_CHMAP_IDX_UNKNOWN -1 + +struct hdmi_codec_channel_map_table { + unsigned char map; /* ALSA API channel map position */ + unsigned long spk_mask; /* speaker position bit mask */ +}; + +/* + * CEA speaker placement for HDMI 1.4: + * + * FL FLC FC FRC FR FRW + * + * LFE + * + * RL RLC RC RRC RR + * + * Speaker placement has to be extended to support HDMI 2.0 + */ +enum hdmi_codec_cea_spk_placement { + FL = BIT(0), /* Front Left */ + FC = BIT(1), /* Front Center */ + FR = BIT(2), /* Front Right */ + FLC = BIT(3), /* Front Left Center */ + FRC = BIT(4), /* Front Right Center */ + RL = BIT(5), /* Rear Left */ + RC = BIT(6), /* Rear Center */ + RR = BIT(7), /* Rear Right */ + RLC = BIT(8), /* Rear Left Center */ + RRC = BIT(9), /* Rear Right Center */ + LFE = BIT(10), /* Low Frequency Effect */ +}; + +/* + * cea Speaker allocation structure + */ +struct hdmi_codec_cea_spk_alloc { + const int ca_id; + unsigned int n_ch; + unsigned long mask; +}; + +/* Channel maps stereo HDMI */ +static const struct snd_pcm_chmap_elem hdmi_codec_stereo_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { } +}; + +/* Channel maps for multi-channel playbacks, up to 8 n_ch */ +static const struct snd_pcm_chmap_elem hdmi_codec_8ch_chmaps[] = { + { .channels = 2, /* CA_ID 0x00 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, /* CA_ID 0x01 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA } }, + { .channels = 4, /* CA_ID 0x02 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC } }, + { .channels = 4, /* CA_ID 0x03 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC } }, + { .channels = 6, /* CA_ID 0x04 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x05 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x06 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x07 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 6, /* CA_ID 0x08 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, /* CA_ID 0x09 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, /* CA_ID 0x0A */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, /* CA_ID 0x0B */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 8, /* CA_ID 0x0C */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x0D */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x0E */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x0F */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } }, + { .channels = 8, /* CA_ID 0x10 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x11 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x12 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x13 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } }, + { .channels = 8, /* CA_ID 0x14 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x15 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x16 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x17 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x18 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x19 */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1A */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1B */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1C */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1D */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1E */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { .channels = 8, /* CA_ID 0x1F */ + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, + SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } }, + { } +}; + +/* + * hdmi_codec_channel_alloc: speaker configuration available for CEA + * + * This is an ordered list that must match with hdmi_codec_8ch_chmaps struct + * The preceding ones have better chances to be selected by + * hdmi_codec_get_ch_alloc_table_idx(). + */ +static const struct hdmi_codec_cea_spk_alloc hdmi_codec_channel_alloc[] = { + { .ca_id = 0x00, .n_ch = 2, + .mask = FL | FR}, + /* 2.1 */ + { .ca_id = 0x01, .n_ch = 4, + .mask = FL | FR | LFE}, + /* Dolby Surround */ + { .ca_id = 0x02, .n_ch = 4, + .mask = FL | FR | FC }, + /* surround51 */ + { .ca_id = 0x0b, .n_ch = 6, + .mask = FL | FR | LFE | FC | RL | RR}, + /* surround40 */ + { .ca_id = 0x08, .n_ch = 6, + .mask = FL | FR | RL | RR }, + /* surround41 */ + { .ca_id = 0x09, .n_ch = 6, + .mask = FL | FR | LFE | RL | RR }, + /* surround50 */ + { .ca_id = 0x0a, .n_ch = 6, + .mask = FL | FR | FC | RL | RR }, + /* 6.1 */ + { .ca_id = 0x0f, .n_ch = 8, + .mask = FL | FR | LFE | FC | RL | RR | RC }, + /* surround71 */ + { .ca_id = 0x13, .n_ch = 8, + .mask = FL | FR | LFE | FC | RL | RR | RLC | RRC }, + /* others */ + { .ca_id = 0x03, .n_ch = 8, + .mask = FL | FR | LFE | FC }, + { .ca_id = 0x04, .n_ch = 8, + .mask = FL | FR | RC}, + { .ca_id = 0x05, .n_ch = 8, + .mask = FL | FR | LFE | RC }, + { .ca_id = 0x06, .n_ch = 8, + .mask = FL | FR | FC | RC }, + { .ca_id = 0x07, .n_ch = 8, + .mask = FL | FR | LFE | FC | RC }, + { .ca_id = 0x0c, .n_ch = 8, + .mask = FL | FR | RC | RL | RR }, + { .ca_id = 0x0d, .n_ch = 8, + .mask = FL | FR | LFE | RL | RR | RC }, + { .ca_id = 0x0e, .n_ch = 8, + .mask = FL | FR | FC | RL | RR | RC }, + { .ca_id = 0x10, .n_ch = 8, + .mask = FL | FR | RL | RR | RLC | RRC }, + { .ca_id = 0x11, .n_ch = 8, + .mask = FL | FR | LFE | RL | RR | RLC | RRC }, + { .ca_id = 0x12, .n_ch = 8, + .mask = FL | FR | FC | RL | RR | RLC | RRC }, + { .ca_id = 0x14, .n_ch = 8, + .mask = FL | FR | FLC | FRC }, + { .ca_id = 0x15, .n_ch = 8, + .mask = FL | FR | LFE | FLC | FRC }, + { .ca_id = 0x16, .n_ch = 8, + .mask = FL | FR | FC | FLC | FRC }, + { .ca_id = 0x17, .n_ch = 8, + .mask = FL | FR | LFE | FC | FLC | FRC }, + { .ca_id = 0x18, .n_ch = 8, + .mask = FL | FR | RC | FLC | FRC }, + { .ca_id = 0x19, .n_ch = 8, + .mask = FL | FR | LFE | RC | FLC | FRC }, + { .ca_id = 0x1a, .n_ch = 8, + .mask = FL | FR | RC | FC | FLC | FRC }, + { .ca_id = 0x1b, .n_ch = 8, + .mask = FL | FR | LFE | RC | FC | FLC | FRC }, + { .ca_id = 0x1c, .n_ch = 8, + .mask = FL | FR | RL | RR | FLC | FRC }, + { .ca_id = 0x1d, .n_ch = 8, + .mask = FL | FR | LFE | RL | RR | FLC | FRC }, + { .ca_id = 0x1e, .n_ch = 8, + .mask = FL | FR | FC | RL | RR | FLC | FRC }, + { .ca_id = 0x1f, .n_ch = 8, + .mask = FL | FR | LFE | FC | RL | RR | FLC | FRC }, +}; + +struct hdmi_codec_priv { + struct hdmi_codec_pdata hcd; + struct snd_soc_dai_driver *daidrv; + struct hdmi_codec_daifmt daifmt[2]; + struct mutex current_stream_lock; + struct snd_pcm_substream *current_stream; + uint8_t eld[MAX_ELD_BYTES]; + struct snd_pcm_chmap *chmap_info; + unsigned int chmap_idx; +}; + +static const struct snd_soc_dapm_widget hdmi_widgets[] = { + SND_SOC_DAPM_OUTPUT("TX"), +}; + +enum { + DAI_ID_I2S = 0, + DAI_ID_SPDIF, +}; + +static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = FIELD_SIZEOF(struct hdmi_codec_priv, eld); + + return 0; +} + +static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); + + memcpy(ucontrol->value.bytes.data, hcp->eld, sizeof(hcp->eld)); + + return 0; +} + +static unsigned long hdmi_codec_spk_mask_from_alloc(int spk_alloc) +{ + int i; + static const unsigned long hdmi_codec_eld_spk_alloc_bits[] = { + [0] = FL | FR, [1] = LFE, [2] = FC, [3] = RL | RR, + [4] = RC, [5] = FLC | FRC, [6] = RLC | RRC, + }; + unsigned long spk_mask = 0; + + for (i = 0; i < ARRAY_SIZE(hdmi_codec_eld_spk_alloc_bits); i++) { + if (spk_alloc & (1 << i)) + spk_mask |= hdmi_codec_eld_spk_alloc_bits[i]; + } + + return spk_mask; +} + +static void hdmi_codec_eld_chmap(struct hdmi_codec_priv *hcp) +{ + u8 spk_alloc; + unsigned long spk_mask; + + spk_alloc = drm_eld_get_spk_alloc(hcp->eld); + spk_mask = hdmi_codec_spk_mask_from_alloc(spk_alloc); + + /* Detect if only stereo supported, else return 8 channels mappings */ + if ((spk_mask & ~(FL | FR)) && hcp->chmap_info->max_channels > 2) + hcp->chmap_info->chmap = hdmi_codec_8ch_chmaps; + else + hcp->chmap_info->chmap = hdmi_codec_stereo_chmaps; +} + +static int hdmi_codec_get_ch_alloc_table_idx(struct hdmi_codec_priv *hcp, + unsigned char channels) +{ + int i; + u8 spk_alloc; + unsigned long spk_mask; + const struct hdmi_codec_cea_spk_alloc *cap = hdmi_codec_channel_alloc; + + spk_alloc = drm_eld_get_spk_alloc(hcp->eld); + spk_mask = hdmi_codec_spk_mask_from_alloc(spk_alloc); + + for (i = 0; i < ARRAY_SIZE(hdmi_codec_channel_alloc); i++, cap++) { + /* If spk_alloc == 0, HDMI is unplugged return stereo config*/ + if (!spk_alloc && cap->ca_id == 0) + return i; + if (cap->n_ch != channels) + continue; + if (!(cap->mask == (spk_mask & cap->mask))) + continue; + return i; + } + + return -EINVAL; +} +static int hdmi_codec_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned const char *map; + unsigned int i; + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct hdmi_codec_priv *hcp = info->private_data; + + map = info->chmap[hcp->chmap_idx].map; + + for (i = 0; i < info->max_channels; i++) { + if (hcp->chmap_idx == HDMI_CODEC_CHMAP_IDX_UNKNOWN) + ucontrol->value.integer.value[i] = 0; + else + ucontrol->value.integer.value[i] = map[i]; + } + + return 0; +} + +static int hdmi_codec_new_stream(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + mutex_lock(&hcp->current_stream_lock); + if (!hcp->current_stream) { + hcp->current_stream = substream; + } else if (hcp->current_stream != substream) { + dev_err(dai->dev, "Only one simultaneous stream supported!\n"); + ret = -EINVAL; + } + mutex_unlock(&hcp->current_stream_lock); + + return ret; +} + +static int hdmi_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + dev_dbg(dai->dev, "%s()\n", __func__); + + ret = hdmi_codec_new_stream(substream, dai); + if (ret) + return ret; + + if (hcp->hcd.ops->audio_startup) { + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); + if (ret) { + mutex_lock(&hcp->current_stream_lock); + hcp->current_stream = NULL; + mutex_unlock(&hcp->current_stream_lock); + return ret; + } + } + + if (hcp->hcd.ops->get_eld) { + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); + + if (!ret) { + ret = snd_pcm_hw_constraint_eld(substream->runtime, + hcp->eld); + if (ret) { + mutex_lock(&hcp->current_stream_lock); + hcp->current_stream = NULL; + mutex_unlock(&hcp->current_stream_lock); + return ret; + } + } + /* Select chmap supported */ + hdmi_codec_eld_chmap(hcp); + } + return 0; +} + +static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s()\n", __func__); + + WARN_ON(hcp->current_stream != substream); + + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); + + mutex_lock(&hcp->current_stream_lock); + hcp->current_stream = NULL; + mutex_unlock(&hcp->current_stream_lock); +} + +static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + struct hdmi_codec_params hp = { + .iec = { + .status = { 0 }, + .subcode = { 0 }, + .pad = 0, + .dig_subframe = { 0 }, + } + }; + int ret, idx; + + dev_dbg(dai->dev, "%s() width %d rate %d channels %d\n", __func__, + params_width(params), params_rate(params), + params_channels(params)); + + if (params_width(params) > 24) + params->msbits = 24; + + ret = snd_pcm_create_iec958_consumer_hw_params(params, hp.iec.status, + sizeof(hp.iec.status)); + if (ret < 0) { + dev_err(dai->dev, "Creating IEC958 channel status failed %d\n", + ret); + return ret; + } + + ret = hdmi_codec_new_stream(substream, dai); + if (ret) + return ret; + + hdmi_audio_infoframe_init(&hp.cea); + hp.cea.channels = params_channels(params); + hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; + hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; + + /* Select a channel allocation that matches with ELD and pcm channels */ + idx = hdmi_codec_get_ch_alloc_table_idx(hcp, hp.cea.channels); + if (idx < 0) { + dev_err(dai->dev, "Not able to map channels to speakers (%d)\n", + idx); + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + return idx; + } + hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id; + hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; + + hp.sample_width = params_width(params); + hp.sample_rate = params_rate(params); + hp.channels = params_channels(params); + + return hcp->hcd.ops->hw_params(dai->dev->parent, hcp->hcd.data, + &hcp->daifmt[dai->id], &hp); +} + +static int hdmi_codec_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + struct hdmi_codec_daifmt cf = { 0 }; + + dev_dbg(dai->dev, "%s()\n", __func__); + + if (dai->id == DAI_ID_SPDIF) + return 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cf.bit_clk_master = 1; + cf.frame_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFM: + cf.frame_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + cf.bit_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + cf.frame_clk_inv = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + cf.bit_clk_inv = 1; + break; + case SND_SOC_DAIFMT_IB_IF: + cf.frame_clk_inv = 1; + cf.bit_clk_inv = 1; + break; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cf.fmt = HDMI_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + cf.fmt = HDMI_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + cf.fmt = HDMI_DSP_B; + break; + case SND_SOC_DAIFMT_RIGHT_J: + cf.fmt = HDMI_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + cf.fmt = HDMI_LEFT_J; + break; + case SND_SOC_DAIFMT_AC97: + cf.fmt = HDMI_AC97; + break; + default: + dev_err(dai->dev, "Invalid DAI interface format\n"); + return -EINVAL; + } + + hcp->daifmt[dai->id] = cf; + + return 0; +} + +static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s()\n", __func__); + + if (hcp->hcd.ops->digital_mute) + return hcp->hcd.ops->digital_mute(dai->dev->parent, + hcp->hcd.data, mute); + + return 0; +} + +static const struct snd_soc_dai_ops hdmi_dai_ops = { + .startup = hdmi_codec_startup, + .shutdown = hdmi_codec_shutdown, + .hw_params = hdmi_codec_hw_params, + .set_fmt = hdmi_codec_set_fmt, + .digital_mute = hdmi_codec_digital_mute, +}; + + +#define HDMI_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 SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +/* + * This list is only for formats allowed on the I2S bus. So there is + * some formats listed that are not supported by HDMI interface. For + * instance allowing the 32-bit formats enables 24-precision with CPU + * DAIs that do not support 24-bit formats. If the extra formats cause + * problems, we should add the video side driver an option to disable + * them. + */ +#define I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |\ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE) + +static int hdmi_codec_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) +{ + struct snd_soc_dai_driver *drv = dai->driver; + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + struct snd_kcontrol *kctl; + struct snd_kcontrol_new hdmi_eld_ctl = { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "ELD", + .info = hdmi_eld_ctl_info, + .get = hdmi_eld_ctl_get, + .device = rtd->pcm->device, + }; + int ret; + + dev_dbg(dai->dev, "%s()\n", __func__); + + ret = snd_pcm_add_chmap_ctls(rtd->pcm, SNDRV_PCM_STREAM_PLAYBACK, + NULL, drv->playback.channels_max, 0, + &hcp->chmap_info); + if (ret < 0) + return ret; + + /* override handlers */ + hcp->chmap_info->private_data = hcp; + hcp->chmap_info->kctl->get = hdmi_codec_chmap_ctl_get; + + /* default chmap supported is stereo */ + hcp->chmap_info->chmap = hdmi_codec_stereo_chmaps; + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + + /* add ELD ctl with the device number corresponding to the PCM stream */ + kctl = snd_ctl_new1(&hdmi_eld_ctl, dai->component); + if (!kctl) + return -ENOMEM; + + return snd_ctl_add(rtd->card->snd_card, kctl); +} + +static int hdmi_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_dapm_route route = { + .sink = "TX", + .source = dai->driver->playback.stream_name, + }; + + dapm = snd_soc_component_get_dapm(dai->component); + + return snd_soc_dapm_add_routes(dapm, &route, 1); +} + +static const struct snd_soc_dai_driver hdmi_i2s_dai = { + .name = "i2s-hifi", + .id = DAI_ID_I2S, + .probe = hdmi_dai_probe, + .playback = { + .stream_name = "I2S Playback", + .channels_min = 2, + .channels_max = 8, + .rates = HDMI_RATES, + .formats = I2S_FORMATS, + .sig_bits = 24, + }, + .ops = &hdmi_dai_ops, + .pcm_new = hdmi_codec_pcm_new, +}; + +static const struct snd_soc_dai_driver hdmi_spdif_dai = { + .name = "spdif-hifi", + .id = DAI_ID_SPDIF, + .probe = hdmi_dai_probe, + .playback = { + .stream_name = "SPDIF Playback", + .channels_min = 2, + .channels_max = 2, + .rates = HDMI_RATES, + .formats = SPDIF_FORMATS, + }, + .ops = &hdmi_dai_ops, + .pcm_new = hdmi_codec_pcm_new, +}; + +static int hdmi_of_xlate_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); + int ret = -ENOTSUPP; /* see snd_soc_get_dai_id() */ + + if (hcp->hcd.ops->get_dai_id) + ret = hcp->hcd.ops->get_dai_id(component, endpoint); + + return ret; +} + +static const struct snd_soc_component_driver hdmi_driver = { + .dapm_widgets = hdmi_widgets, + .num_dapm_widgets = ARRAY_SIZE(hdmi_widgets), + .of_xlate_dai_id = hdmi_of_xlate_dai_id, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int hdmi_codec_probe(struct platform_device *pdev) +{ + struct hdmi_codec_pdata *hcd = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct hdmi_codec_priv *hcp; + int dai_count, i = 0; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + if (!hcd) { + dev_err(dev, "%s: No plalform data\n", __func__); + return -EINVAL; + } + + dai_count = hcd->i2s + hcd->spdif; + if (dai_count < 1 || !hcd->ops || !hcd->ops->hw_params || + !hcd->ops->audio_shutdown) { + dev_err(dev, "%s: Invalid parameters\n", __func__); + return -EINVAL; + } + + hcp = devm_kzalloc(dev, sizeof(*hcp), GFP_KERNEL); + if (!hcp) + return -ENOMEM; + + hcp->hcd = *hcd; + mutex_init(&hcp->current_stream_lock); + + hcp->daidrv = devm_kcalloc(dev, dai_count, sizeof(*hcp->daidrv), + GFP_KERNEL); + if (!hcp->daidrv) + return -ENOMEM; + + if (hcd->i2s) { + hcp->daidrv[i] = hdmi_i2s_dai; + hcp->daidrv[i].playback.channels_max = + hcd->max_i2s_channels; + i++; + } + + if (hcd->spdif) { + hcp->daidrv[i] = hdmi_spdif_dai; + hcp->daifmt[DAI_ID_SPDIF].fmt = HDMI_SPDIF; + } + + dev_set_drvdata(dev, hcp); + + ret = devm_snd_soc_register_component(dev, &hdmi_driver, hcp->daidrv, + dai_count); + if (ret) { + dev_err(dev, "%s: snd_soc_register_component() failed (%d)\n", + __func__, ret); + return ret; + } + return 0; +} + +static struct platform_driver hdmi_codec_driver = { + .driver = { + .name = HDMI_CODEC_DRV_NAME, + }, + .probe = hdmi_codec_probe, +}; + +module_platform_driver(hdmi_codec_driver); + +MODULE_AUTHOR("Jyri Sarha "); +MODULE_DESCRIPTION("HDMI Audio Codec Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" HDMI_CODEC_DRV_NAME); diff --git a/sound/soc/codecs/ics43432.c b/sound/soc/codecs/ics43432.c new file mode 100644 index 000000000..148d6d6b2 --- /dev/null +++ b/sound/soc/codecs/ics43432.c @@ -0,0 +1,74 @@ +/* + * I2S MEMS microphone driver for InvenSense ICS-43432 + * + * - Non configurable. + * - I2S interface, 64 BCLs per frame, 32 bits per channel, 24 bit data + * + * Copyright (c) 2015 Axis Communications AB + * + * Licensed under GPL v2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ICS43432_RATE_MIN 7190 /* Hz, from data sheet */ +#define ICS43432_RATE_MAX 52800 /* Hz, from data sheet */ + +#define ICS43432_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32) + +static struct snd_soc_dai_driver ics43432_dai = { + .name = "ics43432-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rate_min = ICS43432_RATE_MIN, + .rate_max = ICS43432_RATE_MAX, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = ICS43432_FORMATS, + }, +}; + +static const struct snd_soc_component_driver ics43432_component_driver = { + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int ics43432_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &ics43432_component_driver, + &ics43432_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id ics43432_ids[] = { + { .compatible = "invensense,ics43432", }, + { } +}; +MODULE_DEVICE_TABLE(of, ics43432_ids); +#endif + +static struct platform_driver ics43432_driver = { + .driver = { + .name = "ics43432", + .of_match_table = of_match_ptr(ics43432_ids), + }, + .probe = ics43432_probe, +}; + +module_platform_driver(ics43432_driver); + +MODULE_DESCRIPTION("ASoC ICS43432 driver"); +MODULE_AUTHOR("Ricard Wanderlof "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/inno_rk3036.c b/sound/soc/codecs/inno_rk3036.c new file mode 100644 index 000000000..85a336ba7 --- /dev/null +++ b/sound/soc/codecs/inno_rk3036.c @@ -0,0 +1,492 @@ +/* + * Driver of Inno codec for rk3036 by Rockchip Inc. + * + * Author: Rockchip Inc. + * Author: Zheng ShunQian + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inno_rk3036.h" + +struct rk3036_codec_priv { + void __iomem *base; + struct clk *pclk; + struct regmap *regmap; + struct device *dev; +}; + +static const DECLARE_TLV_DB_MINMAX(rk3036_codec_hp_tlv, -39, 0); + +static int rk3036_codec_antipop_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int rk3036_codec_antipop_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int val, ret, regval; + + ret = snd_soc_component_read(component, INNO_R09, ®val); + if (ret) + return ret; + val = ((regval >> INNO_R09_HPL_ANITPOP_SHIFT) & + INNO_R09_HP_ANTIPOP_MSK) == INNO_R09_HP_ANTIPOP_ON; + ucontrol->value.integer.value[0] = val; + + val = ((regval >> INNO_R09_HPR_ANITPOP_SHIFT) & + INNO_R09_HP_ANTIPOP_MSK) == INNO_R09_HP_ANTIPOP_ON; + ucontrol->value.integer.value[1] = val; + + return 0; +} + +static int rk3036_codec_antipop_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int val, ret, regmsk; + + val = (ucontrol->value.integer.value[0] ? + INNO_R09_HP_ANTIPOP_ON : INNO_R09_HP_ANTIPOP_OFF) << + INNO_R09_HPL_ANITPOP_SHIFT; + val |= (ucontrol->value.integer.value[1] ? + INNO_R09_HP_ANTIPOP_ON : INNO_R09_HP_ANTIPOP_OFF) << + INNO_R09_HPR_ANITPOP_SHIFT; + + regmsk = INNO_R09_HP_ANTIPOP_MSK << INNO_R09_HPL_ANITPOP_SHIFT | + INNO_R09_HP_ANTIPOP_MSK << INNO_R09_HPR_ANITPOP_SHIFT; + + ret = snd_soc_component_update_bits(component, INNO_R09, + regmsk, val); + if (ret < 0) + return ret; + + return 0; +} + +#define SOC_RK3036_CODEC_ANTIPOP_DECL(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = rk3036_codec_antipop_info, .get = rk3036_codec_antipop_get, \ + .put = rk3036_codec_antipop_put, } + +static const struct snd_kcontrol_new rk3036_codec_dapm_controls[] = { + SOC_DOUBLE_R_RANGE_TLV("Headphone Volume", INNO_R07, INNO_R08, + INNO_HP_GAIN_SHIFT, INNO_HP_GAIN_N39DB, + INNO_HP_GAIN_0DB, 0, rk3036_codec_hp_tlv), + SOC_DOUBLE("Zero Cross Switch", INNO_R06, INNO_R06_VOUTL_CZ_SHIFT, + INNO_R06_VOUTR_CZ_SHIFT, 1, 0), + SOC_DOUBLE("Headphone Switch", INNO_R09, INNO_R09_HPL_MUTE_SHIFT, + INNO_R09_HPR_MUTE_SHIFT, 1, 0), + SOC_RK3036_CODEC_ANTIPOP_DECL("Anti-pop Switch"), +}; + +static const struct snd_kcontrol_new rk3036_codec_hpl_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Left Out Switch", INNO_R09, + INNO_R09_DACL_SWITCH_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new rk3036_codec_hpr_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Right Out Switch", INNO_R09, + INNO_R09_DACR_SWITCH_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new rk3036_codec_hpl_switch_controls[] = { + SOC_DAPM_SINGLE("HP Left Out Switch", INNO_R05, + INNO_R05_HPL_WORK_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new rk3036_codec_hpr_switch_controls[] = { + SOC_DAPM_SINGLE("HP Right Out Switch", INNO_R05, + INNO_R05_HPR_WORK_SHIFT, 1, 0), +}; + +static const struct snd_soc_dapm_widget rk3036_codec_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("DAC PWR", 1, INNO_R06, + INNO_R06_DAC_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DACL VREF", 2, INNO_R04, + INNO_R04_DACL_VREF_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DACR VREF", 2, INNO_R04, + INNO_R04_DACR_VREF_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DACL HiLo VREF", 3, INNO_R06, + INNO_R06_DACL_HILO_VREF_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DACR HiLo VREF", 3, INNO_R06, + INNO_R06_DACR_HILO_VREF_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DACR CLK", 3, INNO_R04, + INNO_R04_DACR_CLK_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DACL CLK", 3, INNO_R04, + INNO_R04_DACL_CLK_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DACL", "Left Playback", INNO_R04, + INNO_R04_DACL_SW_SHIFT, 0), + SND_SOC_DAPM_DAC("DACR", "Right Playback", INNO_R04, + INNO_R04_DACR_SW_SHIFT, 0), + + SND_SOC_DAPM_MIXER("Left Headphone Mixer", SND_SOC_NOPM, 0, 0, + rk3036_codec_hpl_mixer_controls, + ARRAY_SIZE(rk3036_codec_hpl_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Headphone Mixer", SND_SOC_NOPM, 0, 0, + rk3036_codec_hpr_mixer_controls, + ARRAY_SIZE(rk3036_codec_hpr_mixer_controls)), + + SND_SOC_DAPM_PGA("HP Left Out", INNO_R05, + INNO_R05_HPL_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP Right Out", INNO_R05, + INNO_R05_HPR_EN_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("HP Left Switch", SND_SOC_NOPM, 0, 0, + rk3036_codec_hpl_switch_controls, + ARRAY_SIZE(rk3036_codec_hpl_switch_controls)), + SND_SOC_DAPM_MIXER("HP Right Switch", SND_SOC_NOPM, 0, 0, + rk3036_codec_hpr_switch_controls, + ARRAY_SIZE(rk3036_codec_hpr_switch_controls)), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_route rk3036_codec_dapm_routes[] = { + {"DACL VREF", NULL, "DAC PWR"}, + {"DACR VREF", NULL, "DAC PWR"}, + {"DACL HiLo VREF", NULL, "DAC PWR"}, + {"DACR HiLo VREF", NULL, "DAC PWR"}, + {"DACL CLK", NULL, "DAC PWR"}, + {"DACR CLK", NULL, "DAC PWR"}, + + {"DACL", NULL, "DACL VREF"}, + {"DACL", NULL, "DACL HiLo VREF"}, + {"DACL", NULL, "DACL CLK"}, + {"DACR", NULL, "DACR VREF"}, + {"DACR", NULL, "DACR HiLo VREF"}, + {"DACR", NULL, "DACR CLK"}, + + {"Left Headphone Mixer", "DAC Left Out Switch", "DACL"}, + {"Right Headphone Mixer", "DAC Right Out Switch", "DACR"}, + {"HP Left Out", NULL, "Left Headphone Mixer"}, + {"HP Right Out", NULL, "Right Headphone Mixer"}, + + {"HP Left Switch", "HP Left Out Switch", "HP Left Out"}, + {"HP Right Switch", "HP Right Out Switch", "HP Right Out"}, + + {"HPL", NULL, "HP Left Switch"}, + {"HPR", NULL, "HP Right Switch"}, +}; + +static int rk3036_codec_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + unsigned int reg01_val = 0, reg02_val = 0, reg03_val = 0; + + dev_dbg(component->dev, "rk3036_codec dai set fmt : %08x\n", fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + reg01_val |= INNO_R01_PINDIR_IN_SLAVE | + INNO_R01_I2SMODE_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + reg01_val |= INNO_R01_PINDIR_OUT_MASTER | + INNO_R01_I2SMODE_MASTER; + break; + default: + dev_err(component->dev, "invalid fmt\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + reg02_val |= INNO_R02_DACM_PCM; + break; + case SND_SOC_DAIFMT_I2S: + reg02_val |= INNO_R02_DACM_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + reg02_val |= INNO_R02_DACM_RJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + reg02_val |= INNO_R02_DACM_LJM; + break; + default: + dev_err(component->dev, "set dai format failed\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + reg02_val |= INNO_R02_LRCP_NORMAL; + reg03_val |= INNO_R03_BCP_NORMAL; + break; + case SND_SOC_DAIFMT_IB_IF: + reg02_val |= INNO_R02_LRCP_REVERSAL; + reg03_val |= INNO_R03_BCP_REVERSAL; + break; + case SND_SOC_DAIFMT_IB_NF: + reg02_val |= INNO_R02_LRCP_REVERSAL; + reg03_val |= INNO_R03_BCP_NORMAL; + break; + case SND_SOC_DAIFMT_NB_IF: + reg02_val |= INNO_R02_LRCP_NORMAL; + reg03_val |= INNO_R03_BCP_REVERSAL; + break; + default: + dev_err(component->dev, "set dai format failed\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, INNO_R01, INNO_R01_I2SMODE_MSK | + INNO_R01_PINDIR_MSK, reg01_val); + snd_soc_component_update_bits(component, INNO_R02, INNO_R02_LRCP_MSK | + INNO_R02_DACM_MSK, reg02_val); + snd_soc_component_update_bits(component, INNO_R03, INNO_R03_BCP_MSK, reg03_val); + + return 0; +} + +static int rk3036_codec_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + unsigned int reg02_val = 0, reg03_val = 0; + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + reg02_val |= INNO_R02_VWL_16BIT; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg02_val |= INNO_R02_VWL_20BIT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg02_val |= INNO_R02_VWL_24BIT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg02_val |= INNO_R02_VWL_32BIT; + break; + default: + return -EINVAL; + } + + reg02_val |= INNO_R02_LRCP_NORMAL; + reg03_val |= INNO_R03_FWL_32BIT | INNO_R03_DACR_WORK; + + snd_soc_component_update_bits(component, INNO_R02, INNO_R02_LRCP_MSK | + INNO_R02_VWL_MSK, reg02_val); + snd_soc_component_update_bits(component, INNO_R03, INNO_R03_DACR_MSK | + INNO_R03_FWL_MSK, reg03_val); + return 0; +} + +#define RK3036_CODEC_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define RK3036_CODEC_FMTS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops rk3036_codec_dai_ops = { + .set_fmt = rk3036_codec_dai_set_fmt, + .hw_params = rk3036_codec_dai_hw_params, +}; + +static struct snd_soc_dai_driver rk3036_codec_dai_driver[] = { + { + .name = "rk3036-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RK3036_CODEC_RATES, + .formats = RK3036_CODEC_FMTS, + }, + .ops = &rk3036_codec_dai_ops, + .symmetric_rates = 1, + }, +}; + +static void rk3036_codec_reset(struct snd_soc_component *component) +{ + snd_soc_component_write(component, INNO_R00, + INNO_R00_CSR_RESET | INNO_R00_CDCR_RESET); + snd_soc_component_write(component, INNO_R00, + INNO_R00_CSR_WORK | INNO_R00_CDCR_WORK); +} + +static int rk3036_codec_probe(struct snd_soc_component *component) +{ + rk3036_codec_reset(component); + return 0; +} + +static void rk3036_codec_remove(struct snd_soc_component *component) +{ + rk3036_codec_reset(component); +} + +static int rk3036_codec_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_STANDBY: + /* set a big current for capacitor charging. */ + snd_soc_component_write(component, INNO_R10, INNO_R10_MAX_CUR); + /* start precharge */ + snd_soc_component_write(component, INNO_R06, INNO_R06_DAC_PRECHARGE); + + break; + + case SND_SOC_BIAS_OFF: + /* set a big current for capacitor discharging. */ + snd_soc_component_write(component, INNO_R10, INNO_R10_MAX_CUR); + /* start discharge. */ + snd_soc_component_write(component, INNO_R06, INNO_R06_DAC_DISCHARGE); + + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_component_driver rk3036_codec_driver = { + .probe = rk3036_codec_probe, + .remove = rk3036_codec_remove, + .set_bias_level = rk3036_codec_set_bias_level, + .controls = rk3036_codec_dapm_controls, + .num_controls = ARRAY_SIZE(rk3036_codec_dapm_controls), + .dapm_routes = rk3036_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rk3036_codec_dapm_routes), + .dapm_widgets = rk3036_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rk3036_codec_dapm_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rk3036_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +#define GRF_SOC_CON0 0x00140 +#define GRF_ACODEC_SEL (BIT(10) | BIT(16 + 10)) + +static int rk3036_codec_platform_probe(struct platform_device *pdev) +{ + struct rk3036_codec_priv *priv; + struct device_node *of_node = pdev->dev.of_node; + struct resource *res; + void __iomem *base; + struct regmap *grf; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->base = base; + priv->regmap = devm_regmap_init_mmio(&pdev->dev, priv->base, + &rk3036_codec_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&pdev->dev, "init regmap failed\n"); + return PTR_ERR(priv->regmap); + } + + grf = syscon_regmap_lookup_by_phandle(of_node, "rockchip,grf"); + if (IS_ERR(grf)) { + dev_err(&pdev->dev, "needs 'rockchip,grf' property\n"); + return PTR_ERR(grf); + } + ret = regmap_write(grf, GRF_SOC_CON0, GRF_ACODEC_SEL); + if (ret) { + dev_err(&pdev->dev, "Could not write to GRF: %d\n", ret); + return ret; + } + + priv->pclk = devm_clk_get(&pdev->dev, "acodec_pclk"); + if (IS_ERR(priv->pclk)) + return PTR_ERR(priv->pclk); + + ret = clk_prepare_enable(priv->pclk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clk\n"); + return ret; + } + + priv->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, priv); + + ret = devm_snd_soc_register_component(&pdev->dev, &rk3036_codec_driver, + rk3036_codec_dai_driver, + ARRAY_SIZE(rk3036_codec_dai_driver)); + if (ret) { + clk_disable_unprepare(priv->pclk); + dev_set_drvdata(&pdev->dev, NULL); + } + + return ret; +} + +static int rk3036_codec_platform_remove(struct platform_device *pdev) +{ + struct rk3036_codec_priv *priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(priv->pclk); + + return 0; +} + +static const struct of_device_id rk3036_codec_of_match[] = { + { .compatible = "rockchip,rk3036-codec", }, + {} +}; +MODULE_DEVICE_TABLE(of, rk3036_codec_of_match); + +static struct platform_driver rk3036_codec_platform_driver = { + .driver = { + .name = "rk3036-codec-platform", + .of_match_table = of_match_ptr(rk3036_codec_of_match), + }, + .probe = rk3036_codec_platform_probe, + .remove = rk3036_codec_platform_remove, +}; + +module_platform_driver(rk3036_codec_platform_driver); + +MODULE_AUTHOR("Rockchip Inc."); +MODULE_DESCRIPTION("Rockchip rk3036 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/inno_rk3036.h b/sound/soc/codecs/inno_rk3036.h new file mode 100644 index 000000000..44bb24041 --- /dev/null +++ b/sound/soc/codecs/inno_rk3036.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver of Inno Codec for rk3036 by Rockchip Inc. + * + * Author: Zheng ShunQian + */ + +#ifndef _INNO_RK3036_CODEC_H +#define _INNO_RK3036_CODEC_H + +/* codec registers */ +#define INNO_R00 0x00 +#define INNO_R01 0x0c +#define INNO_R02 0x10 +#define INNO_R03 0x14 +#define INNO_R04 0x88 +#define INNO_R05 0x8c +#define INNO_R06 0x90 +#define INNO_R07 0x94 +#define INNO_R08 0x98 +#define INNO_R09 0x9c +#define INNO_R10 0xa0 + +/* register bit filed */ +#define INNO_R00_CSR_RESET (0x0 << 0) /*codec system reset*/ +#define INNO_R00_CSR_WORK (0x1 << 0) +#define INNO_R00_CDCR_RESET (0x0 << 1) /*codec digital core reset*/ +#define INNO_R00_CDCR_WORK (0x1 << 1) +#define INNO_R00_PRB_DISABLE (0x0 << 6) /*power reset bypass*/ +#define INNO_R00_PRB_ENABLE (0x1 << 6) + +#define INNO_R01_I2SMODE_MSK (0x1 << 4) +#define INNO_R01_I2SMODE_SLAVE (0x0 << 4) +#define INNO_R01_I2SMODE_MASTER (0x1 << 4) +#define INNO_R01_PINDIR_MSK (0x1 << 5) +#define INNO_R01_PINDIR_IN_SLAVE (0x0 << 5) /*direction of pin*/ +#define INNO_R01_PINDIR_OUT_MASTER (0x1 << 5) + +#define INNO_R02_LRS_MSK (0x1 << 2) +#define INNO_R02_LRS_NORMAL (0x0 << 2) /*DAC Left Right Swap*/ +#define INNO_R02_LRS_SWAP (0x1 << 2) +#define INNO_R02_DACM_MSK (0x3 << 3) +#define INNO_R02_DACM_PCM (0x3 << 3) /*DAC Mode*/ +#define INNO_R02_DACM_I2S (0x2 << 3) +#define INNO_R02_DACM_LJM (0x1 << 3) +#define INNO_R02_DACM_RJM (0x0 << 3) +#define INNO_R02_VWL_MSK (0x3 << 5) +#define INNO_R02_VWL_32BIT (0x3 << 5) /*1/2Frame Valid Word Len*/ +#define INNO_R02_VWL_24BIT (0x2 << 5) +#define INNO_R02_VWL_20BIT (0x1 << 5) +#define INNO_R02_VWL_16BIT (0x0 << 5) +#define INNO_R02_LRCP_MSK (0x1 << 7) +#define INNO_R02_LRCP_NORMAL (0x0 << 7) /*Left Right Polarity*/ +#define INNO_R02_LRCP_REVERSAL (0x1 << 7) + +#define INNO_R03_BCP_MSK (0x1 << 0) +#define INNO_R03_BCP_NORMAL (0x0 << 0) /*DAC bit clock polarity*/ +#define INNO_R03_BCP_REVERSAL (0x1 << 0) +#define INNO_R03_DACR_MSK (0x1 << 1) +#define INNO_R03_DACR_RESET (0x0 << 1) /*DAC Reset*/ +#define INNO_R03_DACR_WORK (0x1 << 1) +#define INNO_R03_FWL_MSK (0x3 << 2) +#define INNO_R03_FWL_32BIT (0x3 << 2) /*1/2Frame Word Length*/ +#define INNO_R03_FWL_24BIT (0x2 << 2) +#define INNO_R03_FWL_20BIT (0x1 << 2) +#define INNO_R03_FWL_16BIT (0x0 << 2) + +#define INNO_R04_DACR_SW_SHIFT 0 +#define INNO_R04_DACL_SW_SHIFT 1 +#define INNO_R04_DACR_CLK_SHIFT 2 +#define INNO_R04_DACL_CLK_SHIFT 3 +#define INNO_R04_DACR_VREF_SHIFT 4 +#define INNO_R04_DACL_VREF_SHIFT 5 + +#define INNO_R05_HPR_EN_SHIFT 0 +#define INNO_R05_HPL_EN_SHIFT 1 +#define INNO_R05_HPR_WORK_SHIFT 2 +#define INNO_R05_HPL_WORK_SHIFT 3 + +#define INNO_R06_VOUTR_CZ_SHIFT 0 +#define INNO_R06_VOUTL_CZ_SHIFT 1 +#define INNO_R06_DACR_HILO_VREF_SHIFT 2 +#define INNO_R06_DACL_HILO_VREF_SHIFT 3 +#define INNO_R06_DAC_EN_SHIFT 5 + +#define INNO_R06_DAC_PRECHARGE (0x0 << 4) /*PreCharge control for DAC*/ +#define INNO_R06_DAC_DISCHARGE (0x1 << 4) + +#define INNO_HP_GAIN_SHIFT 0 +/* Gain of output, 1.5db step: -39db(0x0) ~ 0db(0x1a) ~ 6db(0x1f) */ +#define INNO_HP_GAIN_0DB 0x1a +#define INNO_HP_GAIN_N39DB 0x0 + +#define INNO_R09_HP_ANTIPOP_MSK 0x3 +#define INNO_R09_HP_ANTIPOP_OFF 0x1 +#define INNO_R09_HP_ANTIPOP_ON 0x2 +#define INNO_R09_HPR_ANITPOP_SHIFT 0 +#define INNO_R09_HPL_ANITPOP_SHIFT 2 +#define INNO_R09_HPR_MUTE_SHIFT 4 +#define INNO_R09_HPL_MUTE_SHIFT 5 +#define INNO_R09_DACR_SWITCH_SHIFT 6 +#define INNO_R09_DACL_SWITCH_SHIFT 7 + +#define INNO_R10_CHARGE_SEL_CUR_400I_YES (0x0 << 0) +#define INNO_R10_CHARGE_SEL_CUR_400I_NO (0x1 << 0) +#define INNO_R10_CHARGE_SEL_CUR_260I_YES (0x0 << 1) +#define INNO_R10_CHARGE_SEL_CUR_260I_NO (0x1 << 1) +#define INNO_R10_CHARGE_SEL_CUR_130I_YES (0x0 << 2) +#define INNO_R10_CHARGE_SEL_CUR_130I_NO (0x1 << 2) +#define INNO_R10_CHARGE_SEL_CUR_100I_YES (0x0 << 3) +#define INNO_R10_CHARGE_SEL_CUR_100I_NO (0x1 << 3) +#define INNO_R10_CHARGE_SEL_CUR_050I_YES (0x0 << 4) +#define INNO_R10_CHARGE_SEL_CUR_050I_NO (0x1 << 4) +#define INNO_R10_CHARGE_SEL_CUR_027I_YES (0x0 << 5) +#define INNO_R10_CHARGE_SEL_CUR_027I_NO (0x1 << 5) + +#define INNO_R10_MAX_CUR (INNO_R10_CHARGE_SEL_CUR_400I_YES | \ + INNO_R10_CHARGE_SEL_CUR_260I_YES | \ + INNO_R10_CHARGE_SEL_CUR_130I_YES | \ + INNO_R10_CHARGE_SEL_CUR_100I_YES | \ + INNO_R10_CHARGE_SEL_CUR_050I_YES | \ + INNO_R10_CHARGE_SEL_CUR_027I_YES) + +#endif diff --git a/sound/soc/codecs/isabelle.c b/sound/soc/codecs/isabelle.c new file mode 100644 index 000000000..166420376 --- /dev/null +++ b/sound/soc/codecs/isabelle.c @@ -0,0 +1,1158 @@ +/* + * isabelle.c - Low power high fidelity audio codec driver + * + * Copyright (c) 2012 Texas Instruments, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * + * Initially based on sound/soc/codecs/twl6040.c + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "isabelle.h" + + +/* Register default values for ISABELLE driver. */ +static const struct reg_default isabelle_reg_defs[] = { + { 0, 0x00 }, + { 1, 0x00 }, + { 2, 0x00 }, + { 3, 0x00 }, + { 4, 0x00 }, + { 5, 0x00 }, + { 6, 0x00 }, + { 7, 0x00 }, + { 8, 0x00 }, + { 9, 0x00 }, + { 10, 0x00 }, + { 11, 0x00 }, + { 12, 0x00 }, + { 13, 0x00 }, + { 14, 0x00 }, + { 15, 0x00 }, + { 16, 0x00 }, + { 17, 0x00 }, + { 18, 0x00 }, + { 19, 0x00 }, + { 20, 0x00 }, + { 21, 0x02 }, + { 22, 0x02 }, + { 23, 0x02 }, + { 24, 0x02 }, + { 25, 0x0F }, + { 26, 0x8F }, + { 27, 0x0F }, + { 28, 0x8F }, + { 29, 0x00 }, + { 30, 0x00 }, + { 31, 0x00 }, + { 32, 0x00 }, + { 33, 0x00 }, + { 34, 0x00 }, + { 35, 0x00 }, + { 36, 0x00 }, + { 37, 0x00 }, + { 38, 0x00 }, + { 39, 0x00 }, + { 40, 0x00 }, + { 41, 0x00 }, + { 42, 0x00 }, + { 43, 0x00 }, + { 44, 0x00 }, + { 45, 0x00 }, + { 46, 0x00 }, + { 47, 0x00 }, + { 48, 0x00 }, + { 49, 0x00 }, + { 50, 0x00 }, + { 51, 0x00 }, + { 52, 0x00 }, + { 53, 0x00 }, + { 54, 0x00 }, + { 55, 0x00 }, + { 56, 0x00 }, + { 57, 0x00 }, + { 58, 0x00 }, + { 59, 0x00 }, + { 60, 0x00 }, + { 61, 0x00 }, + { 62, 0x00 }, + { 63, 0x00 }, + { 64, 0x00 }, + { 65, 0x00 }, + { 66, 0x00 }, + { 67, 0x00 }, + { 68, 0x00 }, + { 69, 0x90 }, + { 70, 0x90 }, + { 71, 0x90 }, + { 72, 0x00 }, + { 73, 0x00 }, + { 74, 0x00 }, + { 75, 0x00 }, + { 76, 0x00 }, + { 77, 0x00 }, + { 78, 0x00 }, + { 79, 0x00 }, + { 80, 0x00 }, + { 81, 0x00 }, + { 82, 0x00 }, + { 83, 0x00 }, + { 84, 0x00 }, + { 85, 0x07 }, + { 86, 0x00 }, + { 87, 0x00 }, + { 88, 0x00 }, + { 89, 0x07 }, + { 90, 0x80 }, + { 91, 0x07 }, + { 92, 0x07 }, + { 93, 0x00 }, + { 94, 0x00 }, + { 95, 0x00 }, + { 96, 0x00 }, + { 97, 0x00 }, + { 98, 0x00 }, + { 99, 0x00 }, +}; + +static const char *isabelle_rx1_texts[] = {"VRX1", "ARX1"}; +static const char *isabelle_rx2_texts[] = {"VRX2", "ARX2"}; + +static const struct soc_enum isabelle_rx1_enum[] = { + SOC_ENUM_SINGLE(ISABELLE_VOICE_HPF_CFG_REG, 3, + ARRAY_SIZE(isabelle_rx1_texts), isabelle_rx1_texts), + SOC_ENUM_SINGLE(ISABELLE_AUDIO_HPF_CFG_REG, 5, + ARRAY_SIZE(isabelle_rx1_texts), isabelle_rx1_texts), +}; + +static const struct soc_enum isabelle_rx2_enum[] = { + SOC_ENUM_SINGLE(ISABELLE_VOICE_HPF_CFG_REG, 2, + ARRAY_SIZE(isabelle_rx2_texts), isabelle_rx2_texts), + SOC_ENUM_SINGLE(ISABELLE_AUDIO_HPF_CFG_REG, 4, + ARRAY_SIZE(isabelle_rx2_texts), isabelle_rx2_texts), +}; + +/* Headset DAC playback switches */ +static const struct snd_kcontrol_new rx1_mux_controls = + SOC_DAPM_ENUM("Route", isabelle_rx1_enum); + +static const struct snd_kcontrol_new rx2_mux_controls = + SOC_DAPM_ENUM("Route", isabelle_rx2_enum); + +/* TX input selection */ +static const char *isabelle_atx_texts[] = {"AMIC1", "DMIC"}; +static const char *isabelle_vtx_texts[] = {"AMIC2", "DMIC"}; + +static const struct soc_enum isabelle_atx_enum[] = { + SOC_ENUM_SINGLE(ISABELLE_AMIC_CFG_REG, 7, + ARRAY_SIZE(isabelle_atx_texts), isabelle_atx_texts), + SOC_ENUM_SINGLE(ISABELLE_DMIC_CFG_REG, 0, + ARRAY_SIZE(isabelle_atx_texts), isabelle_atx_texts), +}; + +static const struct soc_enum isabelle_vtx_enum[] = { + SOC_ENUM_SINGLE(ISABELLE_AMIC_CFG_REG, 6, + ARRAY_SIZE(isabelle_vtx_texts), isabelle_vtx_texts), + SOC_ENUM_SINGLE(ISABELLE_DMIC_CFG_REG, 0, + ARRAY_SIZE(isabelle_vtx_texts), isabelle_vtx_texts), +}; + +static const struct snd_kcontrol_new atx_mux_controls = + SOC_DAPM_ENUM("Route", isabelle_atx_enum); + +static const struct snd_kcontrol_new vtx_mux_controls = + SOC_DAPM_ENUM("Route", isabelle_vtx_enum); + +/* Left analog microphone selection */ +static const char *isabelle_amic1_texts[] = { + "Main Mic", "Headset Mic", "Aux/FM Left"}; + +/* Left analog microphone selection */ +static const char *isabelle_amic2_texts[] = {"Sub Mic", "Aux/FM Right"}; + +static SOC_ENUM_SINGLE_DECL(isabelle_amic1_enum, + ISABELLE_AMIC_CFG_REG, 5, + isabelle_amic1_texts); + +static SOC_ENUM_SINGLE_DECL(isabelle_amic2_enum, + ISABELLE_AMIC_CFG_REG, 4, + isabelle_amic2_texts); + +static const struct snd_kcontrol_new amic1_control = + SOC_DAPM_ENUM("Route", isabelle_amic1_enum); + +static const struct snd_kcontrol_new amic2_control = + SOC_DAPM_ENUM("Route", isabelle_amic2_enum); + +static const char *isabelle_st_audio_texts[] = {"ATX1", "ATX2"}; + +static const char *isabelle_st_voice_texts[] = {"VTX1", "VTX2"}; + +static const struct soc_enum isabelle_st_audio_enum[] = { + SOC_ENUM_SINGLE(ISABELLE_ATX_STPGA1_CFG_REG, 7, + ARRAY_SIZE(isabelle_st_audio_texts), + isabelle_st_audio_texts), + SOC_ENUM_SINGLE(ISABELLE_ATX_STPGA2_CFG_REG, 7, + ARRAY_SIZE(isabelle_st_audio_texts), + isabelle_st_audio_texts), +}; + +static const struct soc_enum isabelle_st_voice_enum[] = { + SOC_ENUM_SINGLE(ISABELLE_VTX_STPGA1_CFG_REG, 7, + ARRAY_SIZE(isabelle_st_voice_texts), + isabelle_st_voice_texts), + SOC_ENUM_SINGLE(ISABELLE_VTX2_STPGA2_CFG_REG, 7, + ARRAY_SIZE(isabelle_st_voice_texts), + isabelle_st_voice_texts), +}; + +static const struct snd_kcontrol_new st_audio_control = + SOC_DAPM_ENUM("Route", isabelle_st_audio_enum); + +static const struct snd_kcontrol_new st_voice_control = + SOC_DAPM_ENUM("Route", isabelle_st_voice_enum); + +/* Mixer controls */ +static const struct snd_kcontrol_new isabelle_hs_left_mixer_controls[] = { +SOC_DAPM_SINGLE("DAC1L Playback Switch", ISABELLE_HSDRV_CFG1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("APGA1 Playback Switch", ISABELLE_HSDRV_CFG1_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_hs_right_mixer_controls[] = { +SOC_DAPM_SINGLE("DAC1R Playback Switch", ISABELLE_HSDRV_CFG1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("APGA2 Playback Switch", ISABELLE_HSDRV_CFG1_REG, 4, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_hf_left_mixer_controls[] = { +SOC_DAPM_SINGLE("DAC2L Playback Switch", ISABELLE_HFLPGA_CFG_REG, 7, 1, 0), +SOC_DAPM_SINGLE("APGA1 Playback Switch", ISABELLE_HFLPGA_CFG_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_hf_right_mixer_controls[] = { +SOC_DAPM_SINGLE("DAC2R Playback Switch", ISABELLE_HFRPGA_CFG_REG, 7, 1, 0), +SOC_DAPM_SINGLE("APGA2 Playback Switch", ISABELLE_HFRPGA_CFG_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_ep_mixer_controls[] = { +SOC_DAPM_SINGLE("DAC2L Playback Switch", ISABELLE_EARDRV_CFG1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("APGA1 Playback Switch", ISABELLE_EARDRV_CFG1_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_aux_left_mixer_controls[] = { +SOC_DAPM_SINGLE("DAC3L Playback Switch", ISABELLE_LINEAMP_CFG_REG, 7, 1, 0), +SOC_DAPM_SINGLE("APGA1 Playback Switch", ISABELLE_LINEAMP_CFG_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_aux_right_mixer_controls[] = { +SOC_DAPM_SINGLE("DAC3R Playback Switch", ISABELLE_LINEAMP_CFG_REG, 5, 1, 0), +SOC_DAPM_SINGLE("APGA2 Playback Switch", ISABELLE_LINEAMP_CFG_REG, 4, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_dpga1_left_mixer_controls[] = { +SOC_DAPM_SINGLE("RX1 Playback Switch", ISABELLE_DPGA1LR_IN_SEL_REG, 7, 1, 0), +SOC_DAPM_SINGLE("RX3 Playback Switch", ISABELLE_DPGA1LR_IN_SEL_REG, 6, 1, 0), +SOC_DAPM_SINGLE("RX5 Playback Switch", ISABELLE_DPGA1LR_IN_SEL_REG, 5, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_dpga1_right_mixer_controls[] = { +SOC_DAPM_SINGLE("RX2 Playback Switch", ISABELLE_DPGA1LR_IN_SEL_REG, 3, 1, 0), +SOC_DAPM_SINGLE("RX4 Playback Switch", ISABELLE_DPGA1LR_IN_SEL_REG, 2, 1, 0), +SOC_DAPM_SINGLE("RX6 Playback Switch", ISABELLE_DPGA1LR_IN_SEL_REG, 1, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_dpga2_left_mixer_controls[] = { +SOC_DAPM_SINGLE("RX1 Playback Switch", ISABELLE_DPGA2L_IN_SEL_REG, 7, 1, 0), +SOC_DAPM_SINGLE("RX2 Playback Switch", ISABELLE_DPGA2L_IN_SEL_REG, 6, 1, 0), +SOC_DAPM_SINGLE("RX3 Playback Switch", ISABELLE_DPGA2L_IN_SEL_REG, 5, 1, 0), +SOC_DAPM_SINGLE("RX4 Playback Switch", ISABELLE_DPGA2L_IN_SEL_REG, 4, 1, 0), +SOC_DAPM_SINGLE("RX5 Playback Switch", ISABELLE_DPGA2L_IN_SEL_REG, 3, 1, 0), +SOC_DAPM_SINGLE("RX6 Playback Switch", ISABELLE_DPGA2L_IN_SEL_REG, 2, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_dpga2_right_mixer_controls[] = { +SOC_DAPM_SINGLE("USNC Playback Switch", ISABELLE_DPGA2R_IN_SEL_REG, 7, 1, 0), +SOC_DAPM_SINGLE("RX2 Playback Switch", ISABELLE_DPGA2R_IN_SEL_REG, 3, 1, 0), +SOC_DAPM_SINGLE("RX4 Playback Switch", ISABELLE_DPGA2R_IN_SEL_REG, 2, 1, 0), +SOC_DAPM_SINGLE("RX6 Playback Switch", ISABELLE_DPGA2R_IN_SEL_REG, 1, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_dpga3_left_mixer_controls[] = { +SOC_DAPM_SINGLE("RX1 Playback Switch", ISABELLE_DPGA3LR_IN_SEL_REG, 7, 1, 0), +SOC_DAPM_SINGLE("RX3 Playback Switch", ISABELLE_DPGA3LR_IN_SEL_REG, 6, 1, 0), +SOC_DAPM_SINGLE("RX5 Playback Switch", ISABELLE_DPGA3LR_IN_SEL_REG, 5, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_dpga3_right_mixer_controls[] = { +SOC_DAPM_SINGLE("RX2 Playback Switch", ISABELLE_DPGA3LR_IN_SEL_REG, 3, 1, 0), +SOC_DAPM_SINGLE("RX4 Playback Switch", ISABELLE_DPGA3LR_IN_SEL_REG, 2, 1, 0), +SOC_DAPM_SINGLE("RX6 Playback Switch", ISABELLE_DPGA3LR_IN_SEL_REG, 1, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_rx1_mixer_controls[] = { +SOC_DAPM_SINGLE("ST1 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DL1 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_rx2_mixer_controls[] = { +SOC_DAPM_SINGLE("ST2 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 5, 1, 0), +SOC_DAPM_SINGLE("DL2 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 4, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_rx3_mixer_controls[] = { +SOC_DAPM_SINGLE("ST1 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 3, 1, 0), +SOC_DAPM_SINGLE("DL3 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 2, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_rx4_mixer_controls[] = { +SOC_DAPM_SINGLE("ST2 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DL4 Playback Switch", ISABELLE_RX_INPUT_CFG_REG, 0, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_rx5_mixer_controls[] = { +SOC_DAPM_SINGLE("ST1 Playback Switch", ISABELLE_RX_INPUT_CFG2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DL5 Playback Switch", ISABELLE_RX_INPUT_CFG2_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new isabelle_rx6_mixer_controls[] = { +SOC_DAPM_SINGLE("ST2 Playback Switch", ISABELLE_RX_INPUT_CFG2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("DL6 Playback Switch", ISABELLE_RX_INPUT_CFG2_REG, 4, 1, 0), +}; + +static const struct snd_kcontrol_new ep_path_enable_control = + SOC_DAPM_SINGLE("Switch", ISABELLE_EARDRV_CFG2_REG, 0, 1, 0); + +/* TLV Declarations */ +static const DECLARE_TLV_DB_SCALE(mic_amp_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(afm_amp_tlv, -3300, 300, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -1200, 200, 0); +static const DECLARE_TLV_DB_SCALE(hf_tlv, -5000, 200, 0); + +/* from -63 to 0 dB in 1 dB steps */ +static const DECLARE_TLV_DB_SCALE(dpga_tlv, -6300, 100, 1); + +/* from -63 to 9 dB in 1 dB steps */ +static const DECLARE_TLV_DB_SCALE(rx_tlv, -6300, 100, 1); + +static const DECLARE_TLV_DB_SCALE(st_tlv, -2700, 300, 1); +static const DECLARE_TLV_DB_SCALE(tx_tlv, -600, 100, 0); + +static const struct snd_kcontrol_new isabelle_snd_controls[] = { + SOC_DOUBLE_TLV("Headset Playback Volume", ISABELLE_HSDRV_GAIN_REG, + 4, 0, 0xF, 0, dac_tlv), + SOC_DOUBLE_R_TLV("Handsfree Playback Volume", + ISABELLE_HFLPGA_CFG_REG, ISABELLE_HFRPGA_CFG_REG, + 0, 0x1F, 0, hf_tlv), + SOC_DOUBLE_TLV("Aux Playback Volume", ISABELLE_LINEAMP_GAIN_REG, + 4, 0, 0xF, 0, dac_tlv), + SOC_SINGLE_TLV("Earpiece Playback Volume", ISABELLE_EARDRV_CFG1_REG, + 0, 0xF, 0, dac_tlv), + + SOC_DOUBLE_TLV("Aux FM Volume", ISABELLE_APGA_GAIN_REG, 4, 0, 0xF, 0, + afm_amp_tlv), + SOC_SINGLE_TLV("Mic1 Capture Volume", ISABELLE_MIC1_GAIN_REG, 3, 0x1F, + 0, mic_amp_tlv), + SOC_SINGLE_TLV("Mic2 Capture Volume", ISABELLE_MIC2_GAIN_REG, 3, 0x1F, + 0, mic_amp_tlv), + + SOC_DOUBLE_R_TLV("DPGA1 Volume", ISABELLE_DPGA1L_GAIN_REG, + ISABELLE_DPGA1R_GAIN_REG, 0, 0x3F, 0, dpga_tlv), + SOC_DOUBLE_R_TLV("DPGA2 Volume", ISABELLE_DPGA2L_GAIN_REG, + ISABELLE_DPGA2R_GAIN_REG, 0, 0x3F, 0, dpga_tlv), + SOC_DOUBLE_R_TLV("DPGA3 Volume", ISABELLE_DPGA3L_GAIN_REG, + ISABELLE_DPGA3R_GAIN_REG, 0, 0x3F, 0, dpga_tlv), + + SOC_SINGLE_TLV("Sidetone Audio TX1 Volume", + ISABELLE_ATX_STPGA1_CFG_REG, 0, 0xF, 0, st_tlv), + SOC_SINGLE_TLV("Sidetone Audio TX2 Volume", + ISABELLE_ATX_STPGA2_CFG_REG, 0, 0xF, 0, st_tlv), + SOC_SINGLE_TLV("Sidetone Voice TX1 Volume", + ISABELLE_VTX_STPGA1_CFG_REG, 0, 0xF, 0, st_tlv), + SOC_SINGLE_TLV("Sidetone Voice TX2 Volume", + ISABELLE_VTX2_STPGA2_CFG_REG, 0, 0xF, 0, st_tlv), + + SOC_SINGLE_TLV("Audio TX1 Volume", ISABELLE_ATX1_DPGA_REG, 4, 0xF, 0, + tx_tlv), + SOC_SINGLE_TLV("Audio TX2 Volume", ISABELLE_ATX2_DPGA_REG, 4, 0xF, 0, + tx_tlv), + SOC_SINGLE_TLV("Voice TX1 Volume", ISABELLE_VTX1_DPGA_REG, 4, 0xF, 0, + tx_tlv), + SOC_SINGLE_TLV("Voice TX2 Volume", ISABELLE_VTX2_DPGA_REG, 4, 0xF, 0, + tx_tlv), + + SOC_SINGLE_TLV("RX1 DPGA Volume", ISABELLE_RX1_DPGA_REG, 0, 0x3F, 0, + rx_tlv), + SOC_SINGLE_TLV("RX2 DPGA Volume", ISABELLE_RX2_DPGA_REG, 0, 0x3F, 0, + rx_tlv), + SOC_SINGLE_TLV("RX3 DPGA Volume", ISABELLE_RX3_DPGA_REG, 0, 0x3F, 0, + rx_tlv), + SOC_SINGLE_TLV("RX4 DPGA Volume", ISABELLE_RX4_DPGA_REG, 0, 0x3F, 0, + rx_tlv), + SOC_SINGLE_TLV("RX5 DPGA Volume", ISABELLE_RX5_DPGA_REG, 0, 0x3F, 0, + rx_tlv), + SOC_SINGLE_TLV("RX6 DPGA Volume", ISABELLE_RX6_DPGA_REG, 0, 0x3F, 0, + rx_tlv), + + SOC_SINGLE("Headset Noise Gate", ISABELLE_HS_NG_CFG1_REG, 7, 1, 0), + SOC_SINGLE("Handsfree Noise Gate", ISABELLE_HF_NG_CFG1_REG, 7, 1, 0), + + SOC_SINGLE("ATX1 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 7, 1, 0), + SOC_SINGLE("ATX2 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 6, 1, 0), + SOC_SINGLE("ARX1 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 5, 1, 0), + SOC_SINGLE("ARX2 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 4, 1, 0), + SOC_SINGLE("ARX3 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 3, 1, 0), + SOC_SINGLE("ARX4 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 2, 1, 0), + SOC_SINGLE("ARX5 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 1, 1, 0), + SOC_SINGLE("ARX6 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 0, 1, 0), + SOC_SINGLE("VRX1 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 3, 1, 0), + SOC_SINGLE("VRX2 Filter Bypass Switch", ISABELLE_AUDIO_HPF_CFG_REG, + 2, 1, 0), + + SOC_SINGLE("ATX1 Filter Enable Switch", ISABELLE_ALU_TX_EN_REG, + 7, 1, 0), + SOC_SINGLE("ATX2 Filter Enable Switch", ISABELLE_ALU_TX_EN_REG, + 6, 1, 0), + SOC_SINGLE("VTX1 Filter Enable Switch", ISABELLE_ALU_TX_EN_REG, + 5, 1, 0), + SOC_SINGLE("VTX2 Filter Enable Switch", ISABELLE_ALU_TX_EN_REG, + 4, 1, 0), + SOC_SINGLE("RX1 Filter Enable Switch", ISABELLE_ALU_RX_EN_REG, + 5, 1, 0), + SOC_SINGLE("RX2 Filter Enable Switch", ISABELLE_ALU_RX_EN_REG, + 4, 1, 0), + SOC_SINGLE("RX3 Filter Enable Switch", ISABELLE_ALU_RX_EN_REG, + 3, 1, 0), + SOC_SINGLE("RX4 Filter Enable Switch", ISABELLE_ALU_RX_EN_REG, + 2, 1, 0), + SOC_SINGLE("RX5 Filter Enable Switch", ISABELLE_ALU_RX_EN_REG, + 1, 1, 0), + SOC_SINGLE("RX6 Filter Enable Switch", ISABELLE_ALU_RX_EN_REG, + 0, 1, 0), + + SOC_SINGLE("ULATX12 Capture Switch", ISABELLE_ULATX12_INTF_CFG_REG, + 7, 1, 0), + + SOC_SINGLE("DL12 Playback Switch", ISABELLE_DL12_INTF_CFG_REG, + 7, 1, 0), + SOC_SINGLE("DL34 Playback Switch", ISABELLE_DL34_INTF_CFG_REG, + 7, 1, 0), + SOC_SINGLE("DL56 Playback Switch", ISABELLE_DL56_INTF_CFG_REG, + 7, 1, 0), + + /* DMIC Switch */ + SOC_SINGLE("DMIC Switch", ISABELLE_DMIC_CFG_REG, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget isabelle_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("MAINMIC"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("SUBMIC"), + SND_SOC_DAPM_INPUT("LINEIN1"), + SND_SOC_DAPM_INPUT("LINEIN2"), + SND_SOC_DAPM_INPUT("DMICDAT"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HSOL"), + SND_SOC_DAPM_OUTPUT("HSOR"), + SND_SOC_DAPM_OUTPUT("HFL"), + SND_SOC_DAPM_OUTPUT("HFR"), + SND_SOC_DAPM_OUTPUT("EP"), + SND_SOC_DAPM_OUTPUT("LINEOUT1"), + SND_SOC_DAPM_OUTPUT("LINEOUT2"), + + SND_SOC_DAPM_PGA("DL1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DL2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DL3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DL4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DL5", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DL6", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Analog input muxes for the capture amplifiers */ + SND_SOC_DAPM_MUX("Analog Left Capture Route", + SND_SOC_NOPM, 0, 0, &amic1_control), + SND_SOC_DAPM_MUX("Analog Right Capture Route", + SND_SOC_NOPM, 0, 0, &amic2_control), + + SND_SOC_DAPM_MUX("Sidetone Audio Playback", SND_SOC_NOPM, 0, 0, + &st_audio_control), + SND_SOC_DAPM_MUX("Sidetone Voice Playback", SND_SOC_NOPM, 0, 0, + &st_voice_control), + + /* AIF */ + SND_SOC_DAPM_AIF_IN("INTF1_SDI", NULL, 0, ISABELLE_INTF_EN_REG, 7, 0), + SND_SOC_DAPM_AIF_IN("INTF2_SDI", NULL, 0, ISABELLE_INTF_EN_REG, 6, 0), + + SND_SOC_DAPM_AIF_OUT("INTF1_SDO", NULL, 0, ISABELLE_INTF_EN_REG, 5, 0), + SND_SOC_DAPM_AIF_OUT("INTF2_SDO", NULL, 0, ISABELLE_INTF_EN_REG, 4, 0), + + SND_SOC_DAPM_OUT_DRV("ULATX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("ULATX2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("ULVTX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("ULVTX2", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Analog Capture PGAs */ + SND_SOC_DAPM_PGA("MicAmp1", ISABELLE_AMIC_CFG_REG, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("MicAmp2", ISABELLE_AMIC_CFG_REG, 4, 0, NULL, 0), + + /* Auxiliary FM PGAs */ + SND_SOC_DAPM_PGA("APGA1", ISABELLE_APGA_CFG_REG, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("APGA2", ISABELLE_APGA_CFG_REG, 6, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC1", "Left Front Capture", + ISABELLE_AMIC_CFG_REG, 7, 0), + SND_SOC_DAPM_ADC("ADC2", "Right Front Capture", + ISABELLE_AMIC_CFG_REG, 6, 0), + + /* Microphone Bias */ + SND_SOC_DAPM_SUPPLY("Headset Mic Bias", ISABELLE_ABIAS_CFG_REG, + 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Main Mic Bias", ISABELLE_ABIAS_CFG_REG, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Digital Mic1 Bias", + ISABELLE_DBIAS_CFG_REG, 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Digital Mic2 Bias", + ISABELLE_DBIAS_CFG_REG, 2, 0, NULL, 0), + + /* Mixers */ + SND_SOC_DAPM_MIXER("Headset Left Mixer", SND_SOC_NOPM, 0, 0, + isabelle_hs_left_mixer_controls, + ARRAY_SIZE(isabelle_hs_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Headset Right Mixer", SND_SOC_NOPM, 0, 0, + isabelle_hs_right_mixer_controls, + ARRAY_SIZE(isabelle_hs_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Handsfree Left Mixer", SND_SOC_NOPM, 0, 0, + isabelle_hf_left_mixer_controls, + ARRAY_SIZE(isabelle_hf_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Handsfree Right Mixer", SND_SOC_NOPM, 0, 0, + isabelle_hf_right_mixer_controls, + ARRAY_SIZE(isabelle_hf_right_mixer_controls)), + SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_aux_left_mixer_controls, + ARRAY_SIZE(isabelle_aux_left_mixer_controls)), + SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_aux_right_mixer_controls, + ARRAY_SIZE(isabelle_aux_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Earphone Mixer", SND_SOC_NOPM, 0, 0, + isabelle_ep_mixer_controls, + ARRAY_SIZE(isabelle_ep_mixer_controls)), + + SND_SOC_DAPM_MIXER("DPGA1L Mixer", SND_SOC_NOPM, 0, 0, + isabelle_dpga1_left_mixer_controls, + ARRAY_SIZE(isabelle_dpga1_left_mixer_controls)), + SND_SOC_DAPM_MIXER("DPGA1R Mixer", SND_SOC_NOPM, 0, 0, + isabelle_dpga1_right_mixer_controls, + ARRAY_SIZE(isabelle_dpga1_right_mixer_controls)), + SND_SOC_DAPM_MIXER("DPGA2L Mixer", SND_SOC_NOPM, 0, 0, + isabelle_dpga2_left_mixer_controls, + ARRAY_SIZE(isabelle_dpga2_left_mixer_controls)), + SND_SOC_DAPM_MIXER("DPGA2R Mixer", SND_SOC_NOPM, 0, 0, + isabelle_dpga2_right_mixer_controls, + ARRAY_SIZE(isabelle_dpga2_right_mixer_controls)), + SND_SOC_DAPM_MIXER("DPGA3L Mixer", SND_SOC_NOPM, 0, 0, + isabelle_dpga3_left_mixer_controls, + ARRAY_SIZE(isabelle_dpga3_left_mixer_controls)), + SND_SOC_DAPM_MIXER("DPGA3R Mixer", SND_SOC_NOPM, 0, 0, + isabelle_dpga3_right_mixer_controls, + ARRAY_SIZE(isabelle_dpga3_right_mixer_controls)), + + SND_SOC_DAPM_MIXER("RX1 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_rx1_mixer_controls, + ARRAY_SIZE(isabelle_rx1_mixer_controls)), + SND_SOC_DAPM_MIXER("RX2 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_rx2_mixer_controls, + ARRAY_SIZE(isabelle_rx2_mixer_controls)), + SND_SOC_DAPM_MIXER("RX3 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_rx3_mixer_controls, + ARRAY_SIZE(isabelle_rx3_mixer_controls)), + SND_SOC_DAPM_MIXER("RX4 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_rx4_mixer_controls, + ARRAY_SIZE(isabelle_rx4_mixer_controls)), + SND_SOC_DAPM_MIXER("RX5 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_rx5_mixer_controls, + ARRAY_SIZE(isabelle_rx5_mixer_controls)), + SND_SOC_DAPM_MIXER("RX6 Mixer", SND_SOC_NOPM, 0, 0, + isabelle_rx6_mixer_controls, + ARRAY_SIZE(isabelle_rx6_mixer_controls)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC1L", "Headset Playback", ISABELLE_DAC_CFG_REG, + 5, 0), + SND_SOC_DAPM_DAC("DAC1R", "Headset Playback", ISABELLE_DAC_CFG_REG, + 4, 0), + SND_SOC_DAPM_DAC("DAC2L", "Handsfree Playback", ISABELLE_DAC_CFG_REG, + 3, 0), + SND_SOC_DAPM_DAC("DAC2R", "Handsfree Playback", ISABELLE_DAC_CFG_REG, + 2, 0), + SND_SOC_DAPM_DAC("DAC3L", "Lineout Playback", ISABELLE_DAC_CFG_REG, + 1, 0), + SND_SOC_DAPM_DAC("DAC3R", "Lineout Playback", ISABELLE_DAC_CFG_REG, + 0, 0), + + /* Analog Playback PGAs */ + SND_SOC_DAPM_PGA("Sidetone Audio PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Sidetone Voice PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HF Left PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HF Right PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DPGA1L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DPGA1R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DPGA2L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DPGA2R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DPGA3L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DPGA3R", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Analog Playback Mux */ + SND_SOC_DAPM_MUX("RX1 Playback", ISABELLE_ALU_RX_EN_REG, 5, 0, + &rx1_mux_controls), + SND_SOC_DAPM_MUX("RX2 Playback", ISABELLE_ALU_RX_EN_REG, 4, 0, + &rx2_mux_controls), + + /* TX Select */ + SND_SOC_DAPM_MUX("ATX Select", ISABELLE_TX_INPUT_CFG_REG, + 7, 0, &atx_mux_controls), + SND_SOC_DAPM_MUX("VTX Select", ISABELLE_TX_INPUT_CFG_REG, + 6, 0, &vtx_mux_controls), + + SND_SOC_DAPM_SWITCH("Earphone Playback", SND_SOC_NOPM, 0, 0, + &ep_path_enable_control), + + /* Output Drivers */ + SND_SOC_DAPM_OUT_DRV("HS Left Driver", ISABELLE_HSDRV_CFG2_REG, + 1, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HS Right Driver", ISABELLE_HSDRV_CFG2_REG, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LINEOUT1 Left Driver", ISABELLE_LINEAMP_CFG_REG, + 1, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("LINEOUT2 Right Driver", ISABELLE_LINEAMP_CFG_REG, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Earphone Driver", ISABELLE_EARDRV_CFG2_REG, + 1, 0, NULL, 0), + + SND_SOC_DAPM_OUT_DRV("HF Left Driver", ISABELLE_HFDRV_CFG_REG, + 1, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HF Right Driver", ISABELLE_HFDRV_CFG_REG, + 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route isabelle_intercon[] = { + /* Interface mapping */ + { "DL1", "DL12 Playback Switch", "INTF1_SDI" }, + { "DL2", "DL12 Playback Switch", "INTF1_SDI" }, + { "DL3", "DL34 Playback Switch", "INTF1_SDI" }, + { "DL4", "DL34 Playback Switch", "INTF1_SDI" }, + { "DL5", "DL56 Playback Switch", "INTF1_SDI" }, + { "DL6", "DL56 Playback Switch", "INTF1_SDI" }, + + { "DL1", "DL12 Playback Switch", "INTF2_SDI" }, + { "DL2", "DL12 Playback Switch", "INTF2_SDI" }, + { "DL3", "DL34 Playback Switch", "INTF2_SDI" }, + { "DL4", "DL34 Playback Switch", "INTF2_SDI" }, + { "DL5", "DL56 Playback Switch", "INTF2_SDI" }, + { "DL6", "DL56 Playback Switch", "INTF2_SDI" }, + + /* Input side mapping */ + { "Sidetone Audio PGA", NULL, "Sidetone Audio Playback" }, + { "Sidetone Voice PGA", NULL, "Sidetone Voice Playback" }, + + { "RX1 Mixer", "ST1 Playback Switch", "Sidetone Audio PGA" }, + + { "RX1 Mixer", "ST1 Playback Switch", "Sidetone Voice PGA" }, + { "RX1 Mixer", "DL1 Playback Switch", "DL1" }, + + { "RX2 Mixer", "ST2 Playback Switch", "Sidetone Audio PGA" }, + + { "RX2 Mixer", "ST2 Playback Switch", "Sidetone Voice PGA" }, + { "RX2 Mixer", "DL2 Playback Switch", "DL2" }, + + { "RX3 Mixer", "ST1 Playback Switch", "Sidetone Voice PGA" }, + { "RX3 Mixer", "DL3 Playback Switch", "DL3" }, + + { "RX4 Mixer", "ST2 Playback Switch", "Sidetone Voice PGA" }, + { "RX4 Mixer", "DL4 Playback Switch", "DL4" }, + + { "RX5 Mixer", "ST1 Playback Switch", "Sidetone Voice PGA" }, + { "RX5 Mixer", "DL5 Playback Switch", "DL5" }, + + { "RX6 Mixer", "ST2 Playback Switch", "Sidetone Voice PGA" }, + { "RX6 Mixer", "DL6 Playback Switch", "DL6" }, + + /* Capture path */ + { "Analog Left Capture Route", "Headset Mic", "HSMIC" }, + { "Analog Left Capture Route", "Main Mic", "MAINMIC" }, + { "Analog Left Capture Route", "Aux/FM Left", "LINEIN1" }, + + { "Analog Right Capture Route", "Sub Mic", "SUBMIC" }, + { "Analog Right Capture Route", "Aux/FM Right", "LINEIN2" }, + + { "MicAmp1", NULL, "Analog Left Capture Route" }, + { "MicAmp2", NULL, "Analog Right Capture Route" }, + + { "ADC1", NULL, "MicAmp1" }, + { "ADC2", NULL, "MicAmp2" }, + + { "ATX Select", "AMIC1", "ADC1" }, + { "ATX Select", "DMIC", "DMICDAT" }, + { "ATX Select", "AMIC2", "ADC2" }, + + { "VTX Select", "AMIC1", "ADC1" }, + { "VTX Select", "DMIC", "DMICDAT" }, + { "VTX Select", "AMIC2", "ADC2" }, + + { "ULATX1", "ATX1 Filter Enable Switch", "ATX Select" }, + { "ULATX1", "ATX1 Filter Bypass Switch", "ATX Select" }, + { "ULATX2", "ATX2 Filter Enable Switch", "ATX Select" }, + { "ULATX2", "ATX2 Filter Bypass Switch", "ATX Select" }, + + { "ULVTX1", "VTX1 Filter Enable Switch", "VTX Select" }, + { "ULVTX1", "VTX1 Filter Bypass Switch", "VTX Select" }, + { "ULVTX2", "VTX2 Filter Enable Switch", "VTX Select" }, + { "ULVTX2", "VTX2 Filter Bypass Switch", "VTX Select" }, + + { "INTF1_SDO", "ULATX12 Capture Switch", "ULATX1" }, + { "INTF1_SDO", "ULATX12 Capture Switch", "ULATX2" }, + { "INTF2_SDO", "ULATX12 Capture Switch", "ULATX1" }, + { "INTF2_SDO", "ULATX12 Capture Switch", "ULATX2" }, + + { "INTF1_SDO", NULL, "ULVTX1" }, + { "INTF1_SDO", NULL, "ULVTX2" }, + { "INTF2_SDO", NULL, "ULVTX1" }, + { "INTF2_SDO", NULL, "ULVTX2" }, + + /* AFM Path */ + { "APGA1", NULL, "LINEIN1" }, + { "APGA2", NULL, "LINEIN2" }, + + { "RX1 Playback", "VRX1 Filter Bypass Switch", "RX1 Mixer" }, + { "RX1 Playback", "ARX1 Filter Bypass Switch", "RX1 Mixer" }, + { "RX1 Playback", "RX1 Filter Enable Switch", "RX1 Mixer" }, + + { "RX2 Playback", "VRX2 Filter Bypass Switch", "RX2 Mixer" }, + { "RX2 Playback", "ARX2 Filter Bypass Switch", "RX2 Mixer" }, + { "RX2 Playback", "RX2 Filter Enable Switch", "RX2 Mixer" }, + + { "RX3 Playback", "ARX3 Filter Bypass Switch", "RX3 Mixer" }, + { "RX3 Playback", "RX3 Filter Enable Switch", "RX3 Mixer" }, + + { "RX4 Playback", "ARX4 Filter Bypass Switch", "RX4 Mixer" }, + { "RX4 Playback", "RX4 Filter Enable Switch", "RX4 Mixer" }, + + { "RX5 Playback", "ARX5 Filter Bypass Switch", "RX5 Mixer" }, + { "RX5 Playback", "RX5 Filter Enable Switch", "RX5 Mixer" }, + + { "RX6 Playback", "ARX6 Filter Bypass Switch", "RX6 Mixer" }, + { "RX6 Playback", "RX6 Filter Enable Switch", "RX6 Mixer" }, + + { "DPGA1L Mixer", "RX1 Playback Switch", "RX1 Playback" }, + { "DPGA1L Mixer", "RX3 Playback Switch", "RX3 Playback" }, + { "DPGA1L Mixer", "RX5 Playback Switch", "RX5 Playback" }, + + { "DPGA1R Mixer", "RX2 Playback Switch", "RX2 Playback" }, + { "DPGA1R Mixer", "RX4 Playback Switch", "RX4 Playback" }, + { "DPGA1R Mixer", "RX6 Playback Switch", "RX6 Playback" }, + + { "DPGA1L", NULL, "DPGA1L Mixer" }, + { "DPGA1R", NULL, "DPGA1R Mixer" }, + + { "DAC1L", NULL, "DPGA1L" }, + { "DAC1R", NULL, "DPGA1R" }, + + { "DPGA2L Mixer", "RX1 Playback Switch", "RX1 Playback" }, + { "DPGA2L Mixer", "RX2 Playback Switch", "RX2 Playback" }, + { "DPGA2L Mixer", "RX3 Playback Switch", "RX3 Playback" }, + { "DPGA2L Mixer", "RX4 Playback Switch", "RX4 Playback" }, + { "DPGA2L Mixer", "RX5 Playback Switch", "RX5 Playback" }, + { "DPGA2L Mixer", "RX6 Playback Switch", "RX6 Playback" }, + + { "DPGA2R Mixer", "RX2 Playback Switch", "RX2 Playback" }, + { "DPGA2R Mixer", "RX4 Playback Switch", "RX4 Playback" }, + { "DPGA2R Mixer", "RX6 Playback Switch", "RX6 Playback" }, + + { "DPGA2L", NULL, "DPGA2L Mixer" }, + { "DPGA2R", NULL, "DPGA2R Mixer" }, + + { "DAC2L", NULL, "DPGA2L" }, + { "DAC2R", NULL, "DPGA2R" }, + + { "DPGA3L Mixer", "RX1 Playback Switch", "RX1 Playback" }, + { "DPGA3L Mixer", "RX3 Playback Switch", "RX3 Playback" }, + { "DPGA3L Mixer", "RX5 Playback Switch", "RX5 Playback" }, + + { "DPGA3R Mixer", "RX2 Playback Switch", "RX2 Playback" }, + { "DPGA3R Mixer", "RX4 Playback Switch", "RX4 Playback" }, + { "DPGA3R Mixer", "RX6 Playback Switch", "RX6 Playback" }, + + { "DPGA3L", NULL, "DPGA3L Mixer" }, + { "DPGA3R", NULL, "DPGA3R Mixer" }, + + { "DAC3L", NULL, "DPGA3L" }, + { "DAC3R", NULL, "DPGA3R" }, + + { "Headset Left Mixer", "DAC1L Playback Switch", "DAC1L" }, + { "Headset Left Mixer", "APGA1 Playback Switch", "APGA1" }, + + { "Headset Right Mixer", "DAC1R Playback Switch", "DAC1R" }, + { "Headset Right Mixer", "APGA2 Playback Switch", "APGA2" }, + + { "HS Left Driver", NULL, "Headset Left Mixer" }, + { "HS Right Driver", NULL, "Headset Right Mixer" }, + + { "HSOL", NULL, "HS Left Driver" }, + { "HSOR", NULL, "HS Right Driver" }, + + /* Earphone playback path */ + { "Earphone Mixer", "DAC2L Playback Switch", "DAC2L" }, + { "Earphone Mixer", "APGA1 Playback Switch", "APGA1" }, + + { "Earphone Playback", "Switch", "Earphone Mixer" }, + { "Earphone Driver", NULL, "Earphone Playback" }, + { "EP", NULL, "Earphone Driver" }, + + { "Handsfree Left Mixer", "DAC2L Playback Switch", "DAC2L" }, + { "Handsfree Left Mixer", "APGA1 Playback Switch", "APGA1" }, + + { "Handsfree Right Mixer", "DAC2R Playback Switch", "DAC2R" }, + { "Handsfree Right Mixer", "APGA2 Playback Switch", "APGA2" }, + + { "HF Left PGA", NULL, "Handsfree Left Mixer" }, + { "HF Right PGA", NULL, "Handsfree Right Mixer" }, + + { "HF Left Driver", NULL, "HF Left PGA" }, + { "HF Right Driver", NULL, "HF Right PGA" }, + + { "HFL", NULL, "HF Left Driver" }, + { "HFR", NULL, "HF Right Driver" }, + + { "LINEOUT1 Mixer", "DAC3L Playback Switch", "DAC3L" }, + { "LINEOUT1 Mixer", "APGA1 Playback Switch", "APGA1" }, + + { "LINEOUT2 Mixer", "DAC3R Playback Switch", "DAC3R" }, + { "LINEOUT2 Mixer", "APGA2 Playback Switch", "APGA2" }, + + { "LINEOUT1 Driver", NULL, "LINEOUT1 Mixer" }, + { "LINEOUT2 Driver", NULL, "LINEOUT2 Mixer" }, + + { "LINEOUT1", NULL, "LINEOUT1 Driver" }, + { "LINEOUT2", NULL, "LINEOUT2 Driver" }, +}; + +static int isabelle_hs_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, ISABELLE_DAC1_SOFTRAMP_REG, + BIT(4), (mute ? BIT(4) : 0)); + + return 0; +} + +static int isabelle_hf_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, ISABELLE_DAC2_SOFTRAMP_REG, + BIT(4), (mute ? BIT(4) : 0)); + + return 0; +} + +static int isabelle_line_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, ISABELLE_DAC3_SOFTRAMP_REG, + BIT(4), (mute ? BIT(4) : 0)); + + return 0; +} + +static int isabelle_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_component_update_bits(component, ISABELLE_PWR_EN_REG, + ISABELLE_CHIP_EN, BIT(0)); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, ISABELLE_PWR_EN_REG, + ISABELLE_CHIP_EN, 0); + break; + } + + return 0; +} + +static int isabelle_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 aif = 0; + unsigned int fs_val = 0; + + switch (params_rate(params)) { + case 8000: + fs_val = ISABELLE_FS_RATE_8; + break; + case 11025: + fs_val = ISABELLE_FS_RATE_11; + break; + case 12000: + fs_val = ISABELLE_FS_RATE_12; + break; + case 16000: + fs_val = ISABELLE_FS_RATE_16; + break; + case 22050: + fs_val = ISABELLE_FS_RATE_22; + break; + case 24000: + fs_val = ISABELLE_FS_RATE_24; + break; + case 32000: + fs_val = ISABELLE_FS_RATE_32; + break; + case 44100: + fs_val = ISABELLE_FS_RATE_44; + break; + case 48000: + fs_val = ISABELLE_FS_RATE_48; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, ISABELLE_FS_RATE_CFG_REG, + ISABELLE_FS_RATE_MASK, fs_val); + + /* bit size */ + switch (params_width(params)) { + case 20: + aif |= ISABELLE_AIF_LENGTH_20; + break; + case 32: + aif |= ISABELLE_AIF_LENGTH_32; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, ISABELLE_INTF_CFG_REG, + ISABELLE_AIF_LENGTH_MASK, aif); + + return 0; +} + +static int isabelle_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int aif_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aif_val &= ~ISABELLE_AIF_MS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif_val |= ISABELLE_AIF_MS; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aif_val |= ISABELLE_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif_val |= ISABELLE_LEFT_J_MODE; + break; + case SND_SOC_DAIFMT_PDM: + aif_val |= ISABELLE_PDM_MODE; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, ISABELLE_INTF_CFG_REG, + (ISABELLE_AIF_MS | ISABELLE_AIF_FMT_MASK), aif_val); + + return 0; +} + +/* Rates supported by Isabelle driver */ +#define ISABELLE_RATES SNDRV_PCM_RATE_8000_48000 + +/* Formates supported by Isabelle driver. */ +#define ISABELLE_FORMATS (SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops isabelle_hs_dai_ops = { + .hw_params = isabelle_hw_params, + .set_fmt = isabelle_set_dai_fmt, + .digital_mute = isabelle_hs_mute, +}; + +static const struct snd_soc_dai_ops isabelle_hf_dai_ops = { + .hw_params = isabelle_hw_params, + .set_fmt = isabelle_set_dai_fmt, + .digital_mute = isabelle_hf_mute, +}; + +static const struct snd_soc_dai_ops isabelle_line_dai_ops = { + .hw_params = isabelle_hw_params, + .set_fmt = isabelle_set_dai_fmt, + .digital_mute = isabelle_line_mute, +}; + +static const struct snd_soc_dai_ops isabelle_ul_dai_ops = { + .hw_params = isabelle_hw_params, + .set_fmt = isabelle_set_dai_fmt, +}; + +/* ISABELLE dai structure */ +static struct snd_soc_dai_driver isabelle_dai[] = { + { + .name = "isabelle-dl1", + .playback = { + .stream_name = "Headset Playback", + .channels_min = 1, + .channels_max = 2, + .rates = ISABELLE_RATES, + .formats = ISABELLE_FORMATS, + }, + .ops = &isabelle_hs_dai_ops, + }, + { + .name = "isabelle-dl2", + .playback = { + .stream_name = "Handsfree Playback", + .channels_min = 1, + .channels_max = 2, + .rates = ISABELLE_RATES, + .formats = ISABELLE_FORMATS, + }, + .ops = &isabelle_hf_dai_ops, + }, + { + .name = "isabelle-lineout", + .playback = { + .stream_name = "Lineout Playback", + .channels_min = 1, + .channels_max = 2, + .rates = ISABELLE_RATES, + .formats = ISABELLE_FORMATS, + }, + .ops = &isabelle_line_dai_ops, + }, + { + .name = "isabelle-ul", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = ISABELLE_RATES, + .formats = ISABELLE_FORMATS, + }, + .ops = &isabelle_ul_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_isabelle = { + .set_bias_level = isabelle_set_bias_level, + .controls = isabelle_snd_controls, + .num_controls = ARRAY_SIZE(isabelle_snd_controls), + .dapm_widgets = isabelle_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(isabelle_dapm_widgets), + .dapm_routes = isabelle_intercon, + .num_dapm_routes = ARRAY_SIZE(isabelle_intercon), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config isabelle_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = ISABELLE_MAX_REGISTER, + .reg_defaults = isabelle_reg_defs, + .num_reg_defaults = ARRAY_SIZE(isabelle_reg_defs), + .cache_type = REGCACHE_RBTREE, +}; + +static int isabelle_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *isabelle_regmap; + int ret = 0; + + isabelle_regmap = devm_regmap_init_i2c(i2c, &isabelle_regmap_config); + if (IS_ERR(isabelle_regmap)) { + ret = PTR_ERR(isabelle_regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + i2c_set_clientdata(i2c, isabelle_regmap); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_isabelle, isabelle_dai, + ARRAY_SIZE(isabelle_dai)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register component: %d\n", ret); + return ret; + } + + return ret; +} + +static const struct i2c_device_id isabelle_i2c_id[] = { + { "isabelle", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isabelle_i2c_id); + +static struct i2c_driver isabelle_i2c_driver = { + .driver = { + .name = "isabelle", + }, + .probe = isabelle_i2c_probe, + .id_table = isabelle_i2c_id, +}; + +module_i2c_driver(isabelle_i2c_driver); + +MODULE_DESCRIPTION("ASoC ISABELLE driver"); +MODULE_AUTHOR("Vishwas A Deshpande "); +MODULE_AUTHOR("M R Swami Reddy "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/isabelle.h b/sound/soc/codecs/isabelle.h new file mode 100644 index 000000000..96d839a8c --- /dev/null +++ b/sound/soc/codecs/isabelle.h @@ -0,0 +1,143 @@ +/* + * isabelle.h - Low power high fidelity audio codec driver header file + * + * Copyright (c) 2012 Texas Instruments, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + */ + +#ifndef _ISABELLE_H +#define _ISABELLE_H + +#include + +/* ISABELLE REGISTERS */ + +#define ISABELLE_PWR_CFG_REG 0x01 +#define ISABELLE_PWR_EN_REG 0x02 +#define ISABELLE_PS_EN1_REG 0x03 +#define ISABELLE_INT1_STATUS_REG 0x04 +#define ISABELLE_INT1_MASK_REG 0x05 +#define ISABELLE_INT2_STATUS_REG 0x06 +#define ISABELLE_INT2_MASK_REG 0x07 +#define ISABELLE_HKCTL1_REG 0x08 +#define ISABELLE_HKCTL2_REG 0x09 +#define ISABELLE_HKCTL3_REG 0x0A +#define ISABELLE_ACCDET_STATUS_REG 0x0B +#define ISABELLE_BUTTON_ID_REG 0x0C +#define ISABELLE_PLL_CFG_REG 0x10 +#define ISABELLE_PLL_EN_REG 0x11 +#define ISABELLE_FS_RATE_CFG_REG 0x12 +#define ISABELLE_INTF_CFG_REG 0x13 +#define ISABELLE_INTF_EN_REG 0x14 +#define ISABELLE_ULATX12_INTF_CFG_REG 0x15 +#define ISABELLE_DL12_INTF_CFG_REG 0x16 +#define ISABELLE_DL34_INTF_CFG_REG 0x17 +#define ISABELLE_DL56_INTF_CFG_REG 0x18 +#define ISABELLE_ATX_STPGA1_CFG_REG 0x19 +#define ISABELLE_ATX_STPGA2_CFG_REG 0x1A +#define ISABELLE_VTX_STPGA1_CFG_REG 0x1B +#define ISABELLE_VTX2_STPGA2_CFG_REG 0x1C +#define ISABELLE_ATX1_DPGA_REG 0x1D +#define ISABELLE_ATX2_DPGA_REG 0x1E +#define ISABELLE_VTX1_DPGA_REG 0x1F +#define ISABELLE_VTX2_DPGA_REG 0x20 +#define ISABELLE_TX_INPUT_CFG_REG 0x21 +#define ISABELLE_RX_INPUT_CFG_REG 0x22 +#define ISABELLE_RX_INPUT_CFG2_REG 0x23 +#define ISABELLE_VOICE_HPF_CFG_REG 0x24 +#define ISABELLE_AUDIO_HPF_CFG_REG 0x25 +#define ISABELLE_RX1_DPGA_REG 0x26 +#define ISABELLE_RX2_DPGA_REG 0x27 +#define ISABELLE_RX3_DPGA_REG 0x28 +#define ISABELLE_RX4_DPGA_REG 0x29 +#define ISABELLE_RX5_DPGA_REG 0x2A +#define ISABELLE_RX6_DPGA_REG 0x2B +#define ISABELLE_ALU_TX_EN_REG 0x2C +#define ISABELLE_ALU_RX_EN_REG 0x2D +#define ISABELLE_IIR_RESYNC_REG 0x2E +#define ISABELLE_ABIAS_CFG_REG 0x30 +#define ISABELLE_DBIAS_CFG_REG 0x31 +#define ISABELLE_MIC1_GAIN_REG 0x32 +#define ISABELLE_MIC2_GAIN_REG 0x33 +#define ISABELLE_AMIC_CFG_REG 0x34 +#define ISABELLE_DMIC_CFG_REG 0x35 +#define ISABELLE_APGA_GAIN_REG 0x36 +#define ISABELLE_APGA_CFG_REG 0x37 +#define ISABELLE_TX_GAIN_DLY_REG 0x38 +#define ISABELLE_RX_GAIN_DLY_REG 0x39 +#define ISABELLE_RX_PWR_CTRL_REG 0x3A +#define ISABELLE_DPGA1LR_IN_SEL_REG 0x3B +#define ISABELLE_DPGA1L_GAIN_REG 0x3C +#define ISABELLE_DPGA1R_GAIN_REG 0x3D +#define ISABELLE_DPGA2L_IN_SEL_REG 0x3E +#define ISABELLE_DPGA2R_IN_SEL_REG 0x3F +#define ISABELLE_DPGA2L_GAIN_REG 0x40 +#define ISABELLE_DPGA2R_GAIN_REG 0x41 +#define ISABELLE_DPGA3LR_IN_SEL_REG 0x42 +#define ISABELLE_DPGA3L_GAIN_REG 0x43 +#define ISABELLE_DPGA3R_GAIN_REG 0x44 +#define ISABELLE_DAC1_SOFTRAMP_REG 0x45 +#define ISABELLE_DAC2_SOFTRAMP_REG 0x46 +#define ISABELLE_DAC3_SOFTRAMP_REG 0x47 +#define ISABELLE_DAC_CFG_REG 0x48 +#define ISABELLE_EARDRV_CFG1_REG 0x49 +#define ISABELLE_EARDRV_CFG2_REG 0x4A +#define ISABELLE_HSDRV_GAIN_REG 0x4B +#define ISABELLE_HSDRV_CFG1_REG 0x4C +#define ISABELLE_HSDRV_CFG2_REG 0x4D +#define ISABELLE_HS_NG_CFG1_REG 0x4E +#define ISABELLE_HS_NG_CFG2_REG 0x4F +#define ISABELLE_LINEAMP_GAIN_REG 0x50 +#define ISABELLE_LINEAMP_CFG_REG 0x51 +#define ISABELLE_HFL_VOL_CTRL_REG 0x52 +#define ISABELLE_HFL_SFTVOL_CTRL_REG 0x53 +#define ISABELLE_HFL_LIM_CTRL_1_REG 0x54 +#define ISABELLE_HFL_LIM_CTRL_2_REG 0x55 +#define ISABELLE_HFR_VOL_CTRL_REG 0x56 +#define ISABELLE_HFR_SFTVOL_CTRL_REG 0x57 +#define ISABELLE_HFR_LIM_CTRL_1_REG 0x58 +#define ISABELLE_HFR_LIM_CTRL_2_REG 0x59 +#define ISABELLE_HF_MODE_REG 0x5A +#define ISABELLE_HFLPGA_CFG_REG 0x5B +#define ISABELLE_HFRPGA_CFG_REG 0x5C +#define ISABELLE_HFDRV_CFG_REG 0x5D +#define ISABELLE_PDMOUT_CFG1_REG 0x5E +#define ISABELLE_PDMOUT_CFG2_REG 0x5F +#define ISABELLE_PDMOUT_L_WM_REG 0x60 +#define ISABELLE_PDMOUT_R_WM_REG 0x61 +#define ISABELLE_HF_NG_CFG1_REG 0x62 +#define ISABELLE_HF_NG_CFG2_REG 0x63 + +/* ISABELLE_PWR_EN_REG (0x02h) */ +#define ISABELLE_CHIP_EN BIT(0) + +/* ISABELLE DAI FORMATS */ +#define ISABELLE_AIF_FMT_MASK 0x70 +#define ISABELLE_I2S_MODE 0x0 +#define ISABELLE_LEFT_J_MODE 0x1 +#define ISABELLE_PDM_MODE 0x2 + +#define ISABELLE_AIF_LENGTH_MASK 0x30 +#define ISABELLE_AIF_LENGTH_20 0x00 +#define ISABELLE_AIF_LENGTH_32 0x10 + +#define ISABELLE_AIF_MS 0x80 + +#define ISABELLE_FS_RATE_MASK 0xF +#define ISABELLE_FS_RATE_8 0x0 +#define ISABELLE_FS_RATE_11 0x1 +#define ISABELLE_FS_RATE_12 0x2 +#define ISABELLE_FS_RATE_16 0x4 +#define ISABELLE_FS_RATE_22 0x5 +#define ISABELLE_FS_RATE_24 0x6 +#define ISABELLE_FS_RATE_32 0x8 +#define ISABELLE_FS_RATE_44 0x9 +#define ISABELLE_FS_RATE_48 0xA + +#define ISABELLE_MAX_REGISTER 0xFF + +#endif diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c new file mode 100644 index 000000000..9395b5834 --- /dev/null +++ b/sound/soc/codecs/jz4740.c @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define JZ4740_REG_CODEC_1 0x0 +#define JZ4740_REG_CODEC_2 0x4 + +#define JZ4740_CODEC_1_LINE_ENABLE BIT(29) +#define JZ4740_CODEC_1_MIC_ENABLE BIT(28) +#define JZ4740_CODEC_1_SW1_ENABLE BIT(27) +#define JZ4740_CODEC_1_ADC_ENABLE BIT(26) +#define JZ4740_CODEC_1_SW2_ENABLE BIT(25) +#define JZ4740_CODEC_1_DAC_ENABLE BIT(24) +#define JZ4740_CODEC_1_VREF_DISABLE BIT(20) +#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19) +#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18) +#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17) +#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16) +#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14) +#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13) +#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12) +#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10)) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9) +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8) +#define JZ4740_CODEC_1_SUSPEND BIT(1) +#define JZ4740_CODEC_1_RESET BIT(0) + +#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29 +#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28 +#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27 +#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26 +#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25 +#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24 +#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8 + +#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000 +#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003 + +#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16 +#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0 + +static const struct reg_default jz4740_codec_reg_defaults[] = { + { JZ4740_REG_CODEC_1, 0x021b2302 }, + { JZ4740_REG_CODEC_2, 0x00170803 }, +}; + +struct jz4740_codec { + struct regmap *regmap; +}; + +static const DECLARE_TLV_DB_RANGE(jz4740_mic_tlv, + 0, 2, TLV_DB_SCALE_ITEM(0, 600, 0), + 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0) +); + +static const DECLARE_TLV_DB_SCALE(jz4740_out_tlv, 0, 200, 0); +static const DECLARE_TLV_DB_SCALE(jz4740_in_tlv, -3450, 150, 0); + +static const struct snd_kcontrol_new jz4740_codec_controls[] = { + SOC_SINGLE_TLV("Master Playback Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0, + jz4740_out_tlv), + SOC_SINGLE_TLV("Master Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0, + jz4740_in_tlv), + SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1), + SOC_SINGLE_TLV("Mic Capture Volume", JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0, + jz4740_mic_tlv), +}; + +static const struct snd_kcontrol_new jz4740_codec_output_controls[] = { + SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_kcontrol_new jz4740_codec_input_controls[] = { + SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0), + SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0), +}; + +static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0), + SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0), + + SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1, + jz4740_codec_output_controls, + ARRAY_SIZE(jz4740_codec_output_controls)), + + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0, + jz4740_codec_input_controls, + ARRAY_SIZE(jz4740_codec_input_controls)), + SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_INPUT("RIN"), +}; + +static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = { + {"Line Input", NULL, "LIN"}, + {"Line Input", NULL, "RIN"}, + + {"Input Mixer", "Line Capture Switch", "Line Input"}, + {"Input Mixer", "Mic Capture Switch", "MIC"}, + + {"ADC", NULL, "Input Mixer"}, + + {"Output Mixer", "Bypass Switch", "Input Mixer"}, + {"Output Mixer", "DAC Switch", "DAC"}, + + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, +}; + +static int jz4740_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct jz4740_codec *jz4740_codec = snd_soc_component_get_drvdata(dai->component); + uint32_t val; + + switch (params_rate(params)) { + case 8000: + val = 0; + break; + case 11025: + val = 1; + break; + case 12000: + val = 2; + break; + case 16000: + val = 3; + break; + case 22050: + val = 4; + break; + case 24000: + val = 5; + break; + case 32000: + val = 6; + break; + case 44100: + val = 7; + break; + case 48000: + val = 8; + break; + default: + return -EINVAL; + } + + val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET; + + regmap_update_bits(jz4740_codec->regmap, JZ4740_REG_CODEC_2, + JZ4740_CODEC_2_SAMPLE_RATE_MASK, val); + + return 0; +} + +static const struct snd_soc_dai_ops jz4740_codec_dai_ops = { + .hw_params = jz4740_codec_hw_params, +}; + +static struct snd_soc_dai_driver jz4740_codec_dai = { + .name = "jz4740-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, + }, + .ops = &jz4740_codec_dai_ops, + .symmetric_rates = 1, +}; + +static void jz4740_codec_wakeup(struct regmap *regmap) +{ + regmap_update_bits(regmap, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET); + udelay(2); + + regmap_update_bits(regmap, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0); + + regcache_sync(regmap); +} + +static int jz4740_codec_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct jz4740_codec *jz4740_codec = snd_soc_component_get_drvdata(component); + struct regmap *regmap = jz4740_codec->regmap; + unsigned int mask; + unsigned int value; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = 0; + + regmap_update_bits(regmap, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_STANDBY: + /* The only way to clear the suspend flag is to reset the codec */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + jz4740_codec_wakeup(regmap); + + mask = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + value = JZ4740_CODEC_1_VREF_DISABLE | + JZ4740_CODEC_1_VREF_AMP_DISABLE | + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; + + regmap_update_bits(regmap, JZ4740_REG_CODEC_1, mask, value); + break; + case SND_SOC_BIAS_OFF: + mask = JZ4740_CODEC_1_SUSPEND; + value = JZ4740_CODEC_1_SUSPEND; + + regmap_update_bits(regmap, JZ4740_REG_CODEC_1, mask, value); + regcache_mark_dirty(regmap); + break; + default: + break; + } + + return 0; +} + +static int jz4740_codec_dev_probe(struct snd_soc_component *component) +{ + struct jz4740_codec *jz4740_codec = snd_soc_component_get_drvdata(component); + + regmap_update_bits(jz4740_codec->regmap, JZ4740_REG_CODEC_1, + JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE); + + return 0; +} + +static const struct snd_soc_component_driver soc_codec_dev_jz4740_codec = { + .probe = jz4740_codec_dev_probe, + .set_bias_level = jz4740_codec_set_bias_level, + .controls = jz4740_codec_controls, + .num_controls = ARRAY_SIZE(jz4740_codec_controls), + .dapm_widgets = jz4740_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(jz4740_codec_dapm_widgets), + .dapm_routes = jz4740_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(jz4740_codec_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, + +}; + +static const struct regmap_config jz4740_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = JZ4740_REG_CODEC_2, + + .reg_defaults = jz4740_codec_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(jz4740_codec_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int jz4740_codec_probe(struct platform_device *pdev) +{ + int ret; + struct jz4740_codec *jz4740_codec; + struct resource *mem; + void __iomem *base; + + jz4740_codec = devm_kzalloc(&pdev->dev, sizeof(*jz4740_codec), + GFP_KERNEL); + if (!jz4740_codec) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + jz4740_codec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &jz4740_codec_regmap_config); + if (IS_ERR(jz4740_codec->regmap)) + return PTR_ERR(jz4740_codec->regmap); + + platform_set_drvdata(pdev, jz4740_codec); + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_codec_dev_jz4740_codec, &jz4740_codec_dai, 1); + if (ret) + dev_err(&pdev->dev, "Failed to register codec\n"); + + return ret; +} + +static struct platform_driver jz4740_codec_driver = { + .probe = jz4740_codec_probe, + .driver = { + .name = "jz4740-codec", + }, +}; + +module_platform_driver(jz4740_codec_driver); + +MODULE_DESCRIPTION("JZ4740 SoC internal codec driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:jz4740-codec"); diff --git a/sound/soc/codecs/l3.c b/sound/soc/codecs/l3.c new file mode 100644 index 000000000..a10ea3c71 --- /dev/null +++ b/sound/soc/codecs/l3.c @@ -0,0 +1,138 @@ +/* + * L3 code + * + * Copyright (C) 2008, Christian Pellegrin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * + * based on: + * + * L3 bus algorithm module. + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * + * + */ + +#include +#include +#include +#include +#include + +#include + +/* + * Send one byte of data to the chip. Data is latched into the chip on + * the rising edge of the clock. + */ +static void sendbyte(struct l3_pins *adap, unsigned int byte) +{ + int i; + + for (i = 0; i < 8; i++) { + adap->setclk(adap, 0); + udelay(adap->data_hold); + adap->setdat(adap, byte & 1); + udelay(adap->data_setup); + adap->setclk(adap, 1); + udelay(adap->clock_high); + byte >>= 1; + } +} + +/* + * Send a set of bytes to the chip. We need to pulse the MODE line + * between each byte, but never at the start nor at the end of the + * transfer. + */ +static void sendbytes(struct l3_pins *adap, const u8 *buf, + int len) +{ + int i; + + for (i = 0; i < len; i++) { + if (i) { + udelay(adap->mode_hold); + adap->setmode(adap, 0); + udelay(adap->mode); + } + adap->setmode(adap, 1); + udelay(adap->mode_setup); + sendbyte(adap, buf[i]); + } +} + +int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len) +{ + adap->setclk(adap, 1); + adap->setdat(adap, 1); + adap->setmode(adap, 1); + udelay(adap->mode); + + adap->setmode(adap, 0); + udelay(adap->mode_setup); + sendbyte(adap, addr); + udelay(adap->mode_hold); + + sendbytes(adap, data, len); + + adap->setclk(adap, 1); + adap->setdat(adap, 1); + adap->setmode(adap, 0); + + return len; +} +EXPORT_SYMBOL_GPL(l3_write); + + +static void l3_set_clk(struct l3_pins *adap, int val) +{ + gpio_set_value(adap->gpio_clk, val); +} + +static void l3_set_data(struct l3_pins *adap, int val) +{ + gpio_set_value(adap->gpio_data, val); +} + +static void l3_set_mode(struct l3_pins *adap, int val) +{ + gpio_set_value(adap->gpio_mode, val); +} + +int l3_set_gpio_ops(struct device *dev, struct l3_pins *adap) +{ + int ret; + + if (!adap->use_gpios) + return -EINVAL; + + ret = devm_gpio_request_one(dev, adap->gpio_data, + GPIOF_OUT_INIT_LOW, "l3_data"); + if (ret < 0) + return ret; + adap->setdat = l3_set_data; + + ret = devm_gpio_request_one(dev, adap->gpio_clk, + GPIOF_OUT_INIT_LOW, "l3_clk"); + if (ret < 0) + return ret; + adap->setclk = l3_set_clk; + + ret = devm_gpio_request_one(dev, adap->gpio_mode, + GPIOF_OUT_INIT_LOW, "l3_mode"); + if (ret < 0) + return ret; + adap->setmode = l3_set_mode; + + return 0; +} +EXPORT_SYMBOL_GPL(l3_set_gpio_ops); + +MODULE_DESCRIPTION("L3 bit-banging driver"); +MODULE_AUTHOR("Christian Pellegrin "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/lm4857.c b/sound/soc/codecs/lm4857.c new file mode 100644 index 000000000..1e9640796 --- /dev/null +++ b/sound/soc/codecs/lm4857.c @@ -0,0 +1,154 @@ +/* + * LM4857 AMP driver + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com + * Copyright 2011 Lars-Peter Clausen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static const struct reg_default lm4857_default_regs[] = { + { 0x0, 0x00 }, + { 0x1, 0x00 }, + { 0x2, 0x00 }, + { 0x3, 0x00 }, +}; + +/* The register offsets in the cache array */ +#define LM4857_MVOL 0 +#define LM4857_LVOL 1 +#define LM4857_RVOL 2 +#define LM4857_CTRL 3 + +/* the shifts required to set these bits */ +#define LM4857_3D 5 +#define LM4857_WAKEUP 5 +#define LM4857_EPGAIN 4 + +static const unsigned int lm4857_mode_values[] = { + 0, + 6, + 7, + 8, + 9, +}; + +static const char * const lm4857_mode_texts[] = { + "Off", + "Earpiece", + "Loudspeaker", + "Loudspeaker + Headphone", + "Headphone", +}; + +static SOC_VALUE_ENUM_SINGLE_AUTODISABLE_DECL(lm4857_mode_enum, + LM4857_CTRL, 0, 0xf, lm4857_mode_texts, lm4857_mode_values); + +static const struct snd_kcontrol_new lm4857_mode_ctrl = + SOC_DAPM_ENUM("Mode", lm4857_mode_enum); + +static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN"), + + SND_SOC_DAPM_DEMUX("Mode", SND_SOC_NOPM, 0, 0, &lm4857_mode_ctrl), + + SND_SOC_DAPM_OUTPUT("LS"), + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_OUTPUT("EP"), +}; + +static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); +static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); + +static const struct snd_kcontrol_new lm4857_controls[] = { + SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0, + stereo_tlv), + SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0, + stereo_tlv), + SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0, + mono_tlv), + SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0), + SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0), + SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL, + LM4857_WAKEUP, 1, 0), + SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL, + LM4857_EPGAIN, 1, 0), +}; + +static const struct snd_soc_dapm_route lm4857_routes[] = { + { "Mode", NULL, "IN" }, + { "LS", "Loudspeaker", "Mode" }, + { "LS", "Loudspeaker + Headphone", "Mode" }, + { "HP", "Headphone", "Mode" }, + { "HP", "Loudspeaker + Headphone", "Mode" }, + { "EP", "Earpiece", "Mode" }, +}; + +static const struct snd_soc_component_driver lm4857_component_driver = { + .controls = lm4857_controls, + .num_controls = ARRAY_SIZE(lm4857_controls), + .dapm_widgets = lm4857_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(lm4857_dapm_widgets), + .dapm_routes = lm4857_routes, + .num_dapm_routes = ARRAY_SIZE(lm4857_routes), +}; + +static const struct regmap_config lm4857_regmap_config = { + .val_bits = 6, + .reg_bits = 2, + + .max_register = LM4857_CTRL, + + .cache_type = REGCACHE_FLAT, + .reg_defaults = lm4857_default_regs, + .num_reg_defaults = ARRAY_SIZE(lm4857_default_regs), +}; + +static int lm4857_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &lm4857_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return devm_snd_soc_register_component(&i2c->dev, + &lm4857_component_driver, NULL, 0); +} + +static const struct i2c_device_id lm4857_i2c_id[] = { + { "lm4857", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id); + +static struct i2c_driver lm4857_i2c_driver = { + .driver = { + .name = "lm4857", + }, + .probe = lm4857_i2c_probe, + .id_table = lm4857_i2c_id, +}; + +module_i2c_driver(lm4857_i2c_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("LM4857 amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/lm49453.c b/sound/soc/codecs/lm49453.c new file mode 100644 index 000000000..59a646cc0 --- /dev/null +++ b/sound/soc/codecs/lm49453.c @@ -0,0 +1,1470 @@ +/* + * lm49453.c - LM49453 ALSA Soc Audio driver + * + * Copyright (c) 2012 Texas Instruments, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * Initially based on sound/soc/codecs/wm8350.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm49453.h" + +static const struct reg_default lm49453_reg_defs[] = { + { 0, 0x00 }, + { 1, 0x00 }, + { 2, 0x00 }, + { 3, 0x00 }, + { 4, 0x00 }, + { 5, 0x00 }, + { 6, 0x00 }, + { 7, 0x00 }, + { 8, 0x00 }, + { 9, 0x00 }, + { 10, 0x00 }, + { 11, 0x00 }, + { 12, 0x00 }, + { 13, 0x00 }, + { 14, 0x00 }, + { 15, 0x00 }, + { 16, 0x00 }, + { 17, 0x00 }, + { 18, 0x00 }, + { 19, 0x00 }, + { 20, 0x00 }, + { 21, 0x00 }, + { 22, 0x00 }, + { 23, 0x00 }, + { 32, 0x00 }, + { 33, 0x00 }, + { 35, 0x00 }, + { 36, 0x00 }, + { 37, 0x00 }, + { 46, 0x00 }, + { 48, 0x00 }, + { 49, 0x00 }, + { 51, 0x00 }, + { 56, 0x00 }, + { 58, 0x00 }, + { 59, 0x00 }, + { 60, 0x00 }, + { 61, 0x00 }, + { 62, 0x00 }, + { 63, 0x00 }, + { 64, 0x00 }, + { 65, 0x00 }, + { 66, 0x00 }, + { 67, 0x00 }, + { 68, 0x00 }, + { 69, 0x00 }, + { 70, 0x00 }, + { 71, 0x00 }, + { 72, 0x00 }, + { 73, 0x00 }, + { 74, 0x00 }, + { 75, 0x00 }, + { 76, 0x00 }, + { 77, 0x00 }, + { 78, 0x00 }, + { 79, 0x00 }, + { 80, 0x00 }, + { 81, 0x00 }, + { 82, 0x00 }, + { 83, 0x00 }, + { 85, 0x00 }, + { 85, 0x00 }, + { 86, 0x00 }, + { 87, 0x00 }, + { 88, 0x00 }, + { 89, 0x00 }, + { 90, 0x00 }, + { 91, 0x00 }, + { 92, 0x00 }, + { 93, 0x00 }, + { 94, 0x00 }, + { 95, 0x00 }, + { 96, 0x01 }, + { 97, 0x00 }, + { 98, 0x00 }, + { 99, 0x00 }, + { 100, 0x00 }, + { 101, 0x00 }, + { 102, 0x00 }, + { 103, 0x01 }, + { 104, 0x01 }, + { 105, 0x00 }, + { 106, 0x01 }, + { 107, 0x00 }, + { 108, 0x00 }, + { 109, 0x00 }, + { 110, 0x00 }, + { 111, 0x02 }, + { 112, 0x02 }, + { 113, 0x00 }, + { 121, 0x80 }, + { 122, 0xBB }, + { 123, 0x80 }, + { 124, 0xBB }, + { 128, 0x00 }, + { 130, 0x00 }, + { 131, 0x00 }, + { 132, 0x00 }, + { 133, 0x0A }, + { 134, 0x0A }, + { 135, 0x0A }, + { 136, 0x0F }, + { 137, 0x00 }, + { 138, 0x73 }, + { 139, 0x33 }, + { 140, 0x73 }, + { 141, 0x33 }, + { 142, 0x73 }, + { 143, 0x33 }, + { 144, 0x73 }, + { 145, 0x33 }, + { 146, 0x73 }, + { 147, 0x33 }, + { 148, 0x73 }, + { 149, 0x33 }, + { 150, 0x73 }, + { 151, 0x33 }, + { 152, 0x00 }, + { 153, 0x00 }, + { 154, 0x00 }, + { 155, 0x00 }, + { 176, 0x00 }, + { 177, 0x00 }, + { 178, 0x00 }, + { 179, 0x00 }, + { 180, 0x00 }, + { 181, 0x00 }, + { 182, 0x00 }, + { 183, 0x00 }, + { 184, 0x00 }, + { 185, 0x00 }, + { 186, 0x00 }, + { 187, 0x00 }, + { 188, 0x00 }, + { 189, 0x00 }, + { 208, 0x06 }, + { 209, 0x00 }, + { 210, 0x08 }, + { 211, 0x54 }, + { 212, 0x14 }, + { 213, 0x0d }, + { 214, 0x0d }, + { 215, 0x14 }, + { 216, 0x60 }, + { 221, 0x00 }, + { 222, 0x00 }, + { 223, 0x00 }, + { 224, 0x00 }, + { 248, 0x00 }, + { 249, 0x00 }, + { 250, 0x00 }, + { 255, 0x00 }, +}; + +/* codec private data */ +struct lm49453_priv { + struct regmap *regmap; +}; + +/* capture path controls */ + +static const char *lm49453_mic2mode_text[] = {"Single Ended", "Differential"}; + +static SOC_ENUM_SINGLE_DECL(lm49453_mic2mode_enum, LM49453_P0_MICR_REG, 5, + lm49453_mic2mode_text); + +static const char *lm49453_dmic_cfg_text[] = {"DMICDAT1", "DMICDAT2"}; + +static SOC_ENUM_SINGLE_DECL(lm49453_dmic12_cfg_enum, + LM49453_P0_DIGITAL_MIC1_CONFIG_REG, 7, + lm49453_dmic_cfg_text); + +static SOC_ENUM_SINGLE_DECL(lm49453_dmic34_cfg_enum, + LM49453_P0_DIGITAL_MIC2_CONFIG_REG, 7, + lm49453_dmic_cfg_text); + +/* MUX Controls */ +static const char *lm49453_adcl_mux_text[] = { "MIC1", "Aux_L" }; + +static const char *lm49453_adcr_mux_text[] = { "MIC2", "Aux_R" }; + +static SOC_ENUM_SINGLE_DECL(lm49453_adcl_enum, + LM49453_P0_ANALOG_MIXER_ADC_REG, 0, + lm49453_adcl_mux_text); + +static SOC_ENUM_SINGLE_DECL(lm49453_adcr_enum, + LM49453_P0_ANALOG_MIXER_ADC_REG, 1, + lm49453_adcr_mux_text); + +static const struct snd_kcontrol_new lm49453_adcl_mux_control = + SOC_DAPM_ENUM("ADC Left Mux", lm49453_adcl_enum); + +static const struct snd_kcontrol_new lm49453_adcr_mux_control = + SOC_DAPM_ENUM("ADC Right Mux", lm49453_adcr_enum); + +static const struct snd_kcontrol_new lm49453_headset_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHPL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHPL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHPL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHPL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHPL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHPL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHPL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHPL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHPL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHPL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHPL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHPL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHPL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHPL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHPL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHPL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 0, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_headset_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHPR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHPR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHPR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHPR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHPR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHPR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHPR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHPR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHPR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHPR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHPR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHPR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHPR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHPR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHPR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHPR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 1, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_speaker_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLSL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLSL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLSL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLSL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLSL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLSL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLSL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLSL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLSL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLSL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLSL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLSL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLSL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLSL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLSL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLSL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 2, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_speaker_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLSR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLSR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLSR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLSR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLSR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLSR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLSR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLSR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLSR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLSR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLSR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLSR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLSR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLSR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLSR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLSR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 3, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_haptic_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHAL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHAL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHAL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHAL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHAL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHAL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHAL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHAL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHAL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHAL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHAL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHAL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHAL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHAL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHAL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHAL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 4, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_haptic_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACHAR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACHAR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACHAR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACHAR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACHAR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACHAR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACHAR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACHAR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACHAR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACHAR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACHAR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACHAR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACHAR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACHAR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACHAR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACHAR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 5, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_lineout_left_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLOL1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLOL1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLOL1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLOL1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLOL1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLOL1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLOL1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLOL1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLOL2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLOL2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLOL2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLOL2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLOL2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLOL2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLOL2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLOL2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 6, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_lineout_right_mixer[] = { +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_DACLOR1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_DACLOR1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_DACLOR1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_DACLOR1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_DACLOR1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_DACLOR1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_DACLOR1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_DACLOR1_REG, 7, 1, 0), +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_DACLOR2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_DACLOR2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_DACLOR2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_DACLOR2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_DACLOR2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_DACLOR2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_DACLOR2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_DACLOR2_REG, 7, 1, 0), +SOC_DAPM_SINGLE("Sidetone Switch", LM49453_P0_STN_SEL_REG, 7, 0, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx1_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_PORT1_TX1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_PORT1_TX1_REG, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx2_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_PORT1_TX2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_PORT1_TX2_REG, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx3_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX3_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX3_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX3_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX3_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX3_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX3_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_3 Switch", LM49453_P0_PORT1_TX3_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx4_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX4_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX4_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX4_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX4_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX4_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX4_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_4 Switch", LM49453_P0_PORT1_TX4_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx5_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX5_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX5_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX5_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX5_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX5_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX5_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_5 Switch", LM49453_P0_PORT1_TX5_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx6_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX6_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX6_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX6_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX6_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX6_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX6_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_6 Switch", LM49453_P0_PORT1_TX6_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx7_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX7_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX7_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX7_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX7_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX7_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX7_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_7 Switch", LM49453_P0_PORT1_TX7_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port1_tx8_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT1_TX8_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT1_TX8_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT1_TX8_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT1_TX8_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT1_TX8_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT1_TX8_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_8 Switch", LM49453_P0_PORT1_TX8_REG, 6, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port2_tx1_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT2_TX1_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT2_TX1_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT2_TX1_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT2_TX1_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT2_TX1_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT2_TX1_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_1 Switch", LM49453_P0_PORT2_TX1_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_1 Switch", LM49453_P0_PORT2_TX1_REG, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lm49453_port2_tx2_mixer[] = { +SOC_DAPM_SINGLE("DMIC1L Switch", LM49453_P0_PORT2_TX2_REG, 0, 1, 0), +SOC_DAPM_SINGLE("DMIC1R Switch", LM49453_P0_PORT2_TX2_REG, 1, 1, 0), +SOC_DAPM_SINGLE("DMIC2L Switch", LM49453_P0_PORT2_TX2_REG, 2, 1, 0), +SOC_DAPM_SINGLE("DMIC2R Switch", LM49453_P0_PORT2_TX2_REG, 3, 1, 0), +SOC_DAPM_SINGLE("ADCL Switch", LM49453_P0_PORT2_TX2_REG, 4, 1, 0), +SOC_DAPM_SINGLE("ADCR Switch", LM49453_P0_PORT2_TX2_REG, 5, 1, 0), +SOC_DAPM_SINGLE("Port1_2 Switch", LM49453_P0_PORT2_TX2_REG, 6, 1, 0), +SOC_DAPM_SINGLE("Port2_2 Switch", LM49453_P0_PORT2_TX2_REG, 7, 1, 0), +}; + +/* TLV Declarations */ +static const DECLARE_TLV_DB_SCALE(adc_dac_tlv, -7650, 150, 1); +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 200, 1); +static const DECLARE_TLV_DB_SCALE(port_tlv, -1800, 600, 0); +static const DECLARE_TLV_DB_SCALE(stn_tlv, -7200, 150, 0); + +static const struct snd_kcontrol_new lm49453_sidetone_mixer_controls[] = { +/* Sidetone supports mono only */ +SOC_DAPM_SINGLE_TLV("Sidetone ADCL Volume", LM49453_P0_STN_VOL_ADCL_REG, + 0, 0x3F, 0, stn_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone ADCR Volume", LM49453_P0_STN_VOL_ADCR_REG, + 0, 0x3F, 0, stn_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC1L Volume", LM49453_P0_STN_VOL_DMIC1L_REG, + 0, 0x3F, 0, stn_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC1R Volume", LM49453_P0_STN_VOL_DMIC1R_REG, + 0, 0x3F, 0, stn_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC2L Volume", LM49453_P0_STN_VOL_DMIC2L_REG, + 0, 0x3F, 0, stn_tlv), +SOC_DAPM_SINGLE_TLV("Sidetone DMIC2R Volume", LM49453_P0_STN_VOL_DMIC2R_REG, + 0, 0x3F, 0, stn_tlv), +}; + +static const struct snd_kcontrol_new lm49453_snd_controls[] = { + /* mic1 and mic2 supports mono only */ + SOC_SINGLE_TLV("Mic1 Volume", LM49453_P0_MICL_REG, 0, 15, 0, mic_tlv), + SOC_SINGLE_TLV("Mic2 Volume", LM49453_P0_MICR_REG, 0, 15, 0, mic_tlv), + + SOC_SINGLE_TLV("ADCL Volume", LM49453_P0_ADC_LEVELL_REG, 0, 63, + 0, adc_dac_tlv), + SOC_SINGLE_TLV("ADCR Volume", LM49453_P0_ADC_LEVELR_REG, 0, 63, + 0, adc_dac_tlv), + + SOC_DOUBLE_R_TLV("DMIC1 Volume", LM49453_P0_DMIC1_LEVELL_REG, + LM49453_P0_DMIC1_LEVELR_REG, 0, 63, 0, adc_dac_tlv), + SOC_DOUBLE_R_TLV("DMIC2 Volume", LM49453_P0_DMIC2_LEVELL_REG, + LM49453_P0_DMIC2_LEVELR_REG, 0, 63, 0, adc_dac_tlv), + + SOC_DAPM_ENUM("Mic2Mode", lm49453_mic2mode_enum), + SOC_DAPM_ENUM("DMIC12 SRC", lm49453_dmic12_cfg_enum), + SOC_DAPM_ENUM("DMIC34 SRC", lm49453_dmic34_cfg_enum), + + /* Capture path filter enable */ + SOC_SINGLE("DMIC1 HPFilter Switch", LM49453_P0_ADC_FX_ENABLES_REG, + 0, 1, 0), + SOC_SINGLE("DMIC2 HPFilter Switch", LM49453_P0_ADC_FX_ENABLES_REG, + 1, 1, 0), + SOC_SINGLE("ADC HPFilter Switch", LM49453_P0_ADC_FX_ENABLES_REG, + 2, 1, 0), + + SOC_DOUBLE_R_TLV("DAC HP Volume", LM49453_P0_DAC_HP_LEVELL_REG, + LM49453_P0_DAC_HP_LEVELR_REG, 0, 63, 0, adc_dac_tlv), + SOC_DOUBLE_R_TLV("DAC LO Volume", LM49453_P0_DAC_LO_LEVELL_REG, + LM49453_P0_DAC_LO_LEVELR_REG, 0, 63, 0, adc_dac_tlv), + SOC_DOUBLE_R_TLV("DAC LS Volume", LM49453_P0_DAC_LS_LEVELL_REG, + LM49453_P0_DAC_LS_LEVELR_REG, 0, 63, 0, adc_dac_tlv), + SOC_DOUBLE_R_TLV("DAC HA Volume", LM49453_P0_DAC_HA_LEVELL_REG, + LM49453_P0_DAC_HA_LEVELR_REG, 0, 63, 0, adc_dac_tlv), + + SOC_SINGLE_TLV("EP Volume", LM49453_P0_DAC_LS_LEVELL_REG, + 0, 63, 0, adc_dac_tlv), + + SOC_SINGLE_TLV("PORT1_1_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 0, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_2_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 2, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_3_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 4, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_4_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL1_REG, + 6, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_5_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 0, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_6_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 2, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_7_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 4, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT1_8_RX_LVL Volume", LM49453_P0_PORT1_RX_LVL2_REG, + 6, 3, 0, port_tlv), + + SOC_SINGLE_TLV("PORT2_1_RX_LVL Volume", LM49453_P0_PORT2_RX_LVL_REG, + 0, 3, 0, port_tlv), + SOC_SINGLE_TLV("PORT2_2_RX_LVL Volume", LM49453_P0_PORT2_RX_LVL_REG, + 2, 3, 0, port_tlv), + + SOC_SINGLE("Port1 Playback Switch", LM49453_P0_AUDIO_PORT1_BASIC_REG, + 1, 1, 0), + SOC_SINGLE("Port2 Playback Switch", LM49453_P0_AUDIO_PORT2_BASIC_REG, + 1, 1, 0), + SOC_SINGLE("Port1 Capture Switch", LM49453_P0_AUDIO_PORT1_BASIC_REG, + 2, 1, 0), + SOC_SINGLE("Port2 Capture Switch", LM49453_P0_AUDIO_PORT2_BASIC_REG, + 2, 1, 0) + +}; + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget lm49453_dapm_widgets[] = { + + /* All end points HP,EP, LS, Lineout and Haptic */ + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("EPOUT"), + SND_SOC_DAPM_OUTPUT("LSOUTL"), + SND_SOC_DAPM_OUTPUT("LSOUTR"), + SND_SOC_DAPM_OUTPUT("LOOUTR"), + SND_SOC_DAPM_OUTPUT("LOOUTL"), + SND_SOC_DAPM_OUTPUT("HAOUTL"), + SND_SOC_DAPM_OUTPUT("HAOUTR"), + + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_INPUT("DMIC1DAT"), + SND_SOC_DAPM_INPUT("DMIC2DAT"), + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), + + SND_SOC_DAPM_PGA("PORT1_1_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_2_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_3_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_4_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_5_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_6_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_7_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT1_8_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT2_1_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PORT2_2_RX", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("AMIC1Bias", LM49453_P0_MICL_REG, 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AMIC2Bias", LM49453_P0_MICR_REG, 6, 0, NULL, 0), + + /* playback path driver enables */ + SND_SOC_DAPM_OUT_DRV("Headset Switch", + LM49453_P0_PMC_SETUP_REG, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Earpiece Switch", + LM49453_P0_EP_REG, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Speaker Left Switch", + LM49453_P0_DIS_PKVL_FB_REG, 0, 1, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Speaker Right Switch", + LM49453_P0_DIS_PKVL_FB_REG, 1, 1, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Haptic Left Switch", + LM49453_P0_DIS_PKVL_FB_REG, 2, 1, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Haptic Right Switch", + LM49453_P0_DIS_PKVL_FB_REG, 3, 1, NULL, 0), + + /* DAC */ + SND_SOC_DAPM_DAC("HPL DAC", "Headset", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HPR DAC", "Headset", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LSL DAC", "Speaker", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LSR DAC", "Speaker", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HAL DAC", "Haptic", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HAR DAC", "Haptic", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LOL DAC", "Lineout", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("LOR DAC", "Lineout", SND_SOC_NOPM, 0, 0), + + + SND_SOC_DAPM_PGA("AUXL Input", + LM49453_P0_ANALOG_MIXER_ADC_REG, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUXR Input", + LM49453_P0_ANALOG_MIXER_ADC_REG, 3, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Sidetone", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("DMIC1 Left", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("DMIC1 Right", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("DMIC2 Left", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("DMIC2 Right", "Capture", SND_SOC_NOPM, 1, 0), + + SND_SOC_DAPM_ADC("ADC Left", "Capture", SND_SOC_NOPM, 1, 0), + SND_SOC_DAPM_ADC("ADC Right", "Capture", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("ADCL Mux", SND_SOC_NOPM, 0, 0, + &lm49453_adcl_mux_control), + SND_SOC_DAPM_MUX("ADCR Mux", SND_SOC_NOPM, 0, 0, + &lm49453_adcr_mux_control), + + SND_SOC_DAPM_MUX("Mic1 Input", + SND_SOC_NOPM, 0, 0, &lm49453_adcl_mux_control), + + SND_SOC_DAPM_MUX("Mic2 Input", + SND_SOC_NOPM, 0, 0, &lm49453_adcr_mux_control), + + /* AIF */ + SND_SOC_DAPM_AIF_IN("PORT1_SDI", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 2, 0), + SND_SOC_DAPM_AIF_IN("PORT2_SDI", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 6, 0), + + SND_SOC_DAPM_AIF_OUT("PORT1_SDO", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 3, 0), + SND_SOC_DAPM_AIF_OUT("PORT2_SDO", NULL, 0, + LM49453_P0_PULL_CONFIG1_REG, 7, 0), + + /* Port1 TX controls */ + SND_SOC_DAPM_OUT_DRV("P1_1_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_2_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_3_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_4_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_5_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_6_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_7_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P1_8_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Port2 TX controls */ + SND_SOC_DAPM_OUT_DRV("P2_1_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("P2_2_TX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Sidetone Mixer */ + SND_SOC_DAPM_MIXER("Sidetone Mixer", SND_SOC_NOPM, 0, 0, + lm49453_sidetone_mixer_controls, + ARRAY_SIZE(lm49453_sidetone_mixer_controls)), + + /* DAC MIXERS */ + SND_SOC_DAPM_MIXER("HPL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_headset_left_mixer, + ARRAY_SIZE(lm49453_headset_left_mixer)), + SND_SOC_DAPM_MIXER("HPR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_headset_right_mixer, + ARRAY_SIZE(lm49453_headset_right_mixer)), + SND_SOC_DAPM_MIXER("LOL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_lineout_left_mixer, + ARRAY_SIZE(lm49453_lineout_left_mixer)), + SND_SOC_DAPM_MIXER("LOR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_lineout_right_mixer, + ARRAY_SIZE(lm49453_lineout_right_mixer)), + SND_SOC_DAPM_MIXER("LSL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_speaker_left_mixer, + ARRAY_SIZE(lm49453_speaker_left_mixer)), + SND_SOC_DAPM_MIXER("LSR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_speaker_right_mixer, + ARRAY_SIZE(lm49453_speaker_right_mixer)), + SND_SOC_DAPM_MIXER("HAL Mixer", SND_SOC_NOPM, 0, 0, + lm49453_haptic_left_mixer, + ARRAY_SIZE(lm49453_haptic_left_mixer)), + SND_SOC_DAPM_MIXER("HAR Mixer", SND_SOC_NOPM, 0, 0, + lm49453_haptic_right_mixer, + ARRAY_SIZE(lm49453_haptic_right_mixer)), + + /* Capture Mixer */ + SND_SOC_DAPM_MIXER("Port1_1 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx1_mixer, + ARRAY_SIZE(lm49453_port1_tx1_mixer)), + SND_SOC_DAPM_MIXER("Port1_2 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx2_mixer, + ARRAY_SIZE(lm49453_port1_tx2_mixer)), + SND_SOC_DAPM_MIXER("Port1_3 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx3_mixer, + ARRAY_SIZE(lm49453_port1_tx3_mixer)), + SND_SOC_DAPM_MIXER("Port1_4 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx4_mixer, + ARRAY_SIZE(lm49453_port1_tx4_mixer)), + SND_SOC_DAPM_MIXER("Port1_5 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx5_mixer, + ARRAY_SIZE(lm49453_port1_tx5_mixer)), + SND_SOC_DAPM_MIXER("Port1_6 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx6_mixer, + ARRAY_SIZE(lm49453_port1_tx6_mixer)), + SND_SOC_DAPM_MIXER("Port1_7 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx7_mixer, + ARRAY_SIZE(lm49453_port1_tx7_mixer)), + SND_SOC_DAPM_MIXER("Port1_8 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port1_tx8_mixer, + ARRAY_SIZE(lm49453_port1_tx8_mixer)), + + SND_SOC_DAPM_MIXER("Port2_1 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port2_tx1_mixer, + ARRAY_SIZE(lm49453_port2_tx1_mixer)), + SND_SOC_DAPM_MIXER("Port2_2 Mixer", SND_SOC_NOPM, 0, 0, + lm49453_port2_tx2_mixer, + ARRAY_SIZE(lm49453_port2_tx2_mixer)), +}; + +static const struct snd_soc_dapm_route lm49453_audio_map[] = { + /* Port SDI mapping */ + { "PORT1_1_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_2_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_3_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_4_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_5_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_6_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_7_RX", "Port1 Playback Switch", "PORT1_SDI" }, + { "PORT1_8_RX", "Port1 Playback Switch", "PORT1_SDI" }, + + { "PORT2_1_RX", "Port2 Playback Switch", "PORT2_SDI" }, + { "PORT2_2_RX", "Port2 Playback Switch", "PORT2_SDI" }, + + /* HP mapping */ + { "HPL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HPL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HPL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HPL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HPL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HPL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HPL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HPL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + { "HPL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HPL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HPL Mixer", "ADCL Switch", "ADC Left" }, + { "HPL Mixer", "ADCR Switch", "ADC Right" }, + { "HPL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HPL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HPL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HPL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "HPL Mixer", "Sidetone Switch", "Sidetone" }, + + { "HPL DAC", NULL, "HPL Mixer" }, + + { "HPR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HPR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HPR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HPR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HPR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HPR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HPR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HPR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "HPR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HPR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HPR Mixer", "ADCL Switch", "ADC Left" }, + { "HPR Mixer", "ADCR Switch", "ADC Right" }, + { "HPR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HPR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HPR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HPR Mixer", "DMIC2L Switch", "DMIC2 Right" }, + { "HPR Mixer", "Sidetone Switch", "Sidetone" }, + + { "HPR DAC", NULL, "HPR Mixer" }, + + { "HPOUTL", "Headset Switch", "HPL DAC"}, + { "HPOUTR", "Headset Switch", "HPR DAC"}, + + /* EP map */ + { "EPOUT", "Earpiece Switch", "HPL DAC" }, + + /* Speaker map */ + { "LSL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LSL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LSL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LSL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LSL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LSL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LSL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LSL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LSL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LSL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LSL Mixer", "ADCL Switch", "ADC Left" }, + { "LSL Mixer", "ADCR Switch", "ADC Right" }, + { "LSL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LSL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LSL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LSL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LSL Mixer", "Sidetone Switch", "Sidetone" }, + + { "LSL DAC", NULL, "LSL Mixer" }, + + { "LSR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LSR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LSR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LSR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LSR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LSR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LSR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LSR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LSR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LSR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LSR Mixer", "ADCL Switch", "ADC Left" }, + { "LSR Mixer", "ADCR Switch", "ADC Right" }, + { "LSR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LSR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LSR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LSR Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LSR Mixer", "Sidetone Switch", "Sidetone" }, + + { "LSR DAC", NULL, "LSR Mixer" }, + + { "LSOUTL", "Speaker Left Switch", "LSL DAC"}, + { "LSOUTR", "Speaker Left Switch", "LSR DAC"}, + + /* Haptic map */ + { "HAL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HAL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HAL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HAL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HAL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HAL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HAL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HAL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "HAL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HAL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HAL Mixer", "ADCL Switch", "ADC Left" }, + { "HAL Mixer", "ADCR Switch", "ADC Right" }, + { "HAL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HAL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HAL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HAL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "HAL Mixer", "Sidetone Switch", "Sidetone" }, + + { "HAL DAC", NULL, "HAL Mixer" }, + + { "HAR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "HAR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "HAR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "HAR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "HAR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "HAR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "HAR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "HAR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "HAR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "HAR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "HAR Mixer", "ADCL Switch", "ADC Left" }, + { "HAR Mixer", "ADCR Switch", "ADC Right" }, + { "HAR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "HAR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "HAR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "HAR Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "HAR Mixer", "Sideton Switch", "Sidetone" }, + + { "HAR DAC", NULL, "HAR Mixer" }, + + { "HAOUTL", "Haptic Left Switch", "HAL DAC" }, + { "HAOUTR", "Haptic Right Switch", "HAR DAC" }, + + /* Lineout map */ + { "LOL Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LOL Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LOL Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LOL Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LOL Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LOL Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LOL Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LOL Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LOL Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LOL Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LOL Mixer", "ADCL Switch", "ADC Left" }, + { "LOL Mixer", "ADCR Switch", "ADC Right" }, + { "LOL Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LOL Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LOL Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LOL Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LOL Mixer", "Sidetone Switch", "Sidetone" }, + + { "LOL DAC", NULL, "LOL Mixer" }, + + { "LOR Mixer", "Port1_1 Switch", "PORT1_1_RX" }, + { "LOR Mixer", "Port1_2 Switch", "PORT1_2_RX" }, + { "LOR Mixer", "Port1_3 Switch", "PORT1_3_RX" }, + { "LOR Mixer", "Port1_4 Switch", "PORT1_4_RX" }, + { "LOR Mixer", "Port1_5 Switch", "PORT1_5_RX" }, + { "LOR Mixer", "Port1_6 Switch", "PORT1_6_RX" }, + { "LOR Mixer", "Port1_7 Switch", "PORT1_7_RX" }, + { "LOR Mixer", "Port1_8 Switch", "PORT1_8_RX" }, + + /* Port 2 */ + { "LOR Mixer", "Port2_1 Switch", "PORT2_1_RX" }, + { "LOR Mixer", "Port2_2 Switch", "PORT2_2_RX" }, + + { "LOR Mixer", "ADCL Switch", "ADC Left" }, + { "LOR Mixer", "ADCR Switch", "ADC Right" }, + { "LOR Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "LOR Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "LOR Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "LOR Mixer", "DMIC2R Switch", "DMIC2 Right" }, + { "LOR Mixer", "Sidetone Switch", "Sidetone" }, + + { "LOR DAC", NULL, "LOR Mixer" }, + + { "LOOUTL", NULL, "LOL DAC" }, + { "LOOUTR", NULL, "LOR DAC" }, + + /* TX map */ + /* Port1 mappings */ + { "Port1_1 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_1 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_1 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_1 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_1 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_1 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_2 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_2 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_2 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_2 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_2 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_2 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_3 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_3 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_3 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_3 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_3 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_3 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_4 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_4 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_4 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_4 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_4 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_4 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_5 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_5 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_5 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_5 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_5 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_5 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_6 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_6 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_6 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_6 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_6 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_6 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_7 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_7 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_7 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_7 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_7 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_7 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port1_8 Mixer", "ADCL Switch", "ADC Left" }, + { "Port1_8 Mixer", "ADCR Switch", "ADC Right" }, + { "Port1_8 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port1_8 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port1_8 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port1_8 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port2_1 Mixer", "ADCL Switch", "ADC Left" }, + { "Port2_1 Mixer", "ADCR Switch", "ADC Right" }, + { "Port2_1 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port2_1 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port2_1 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port2_1 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "Port2_2 Mixer", "ADCL Switch", "ADC Left" }, + { "Port2_2 Mixer", "ADCR Switch", "ADC Right" }, + { "Port2_2 Mixer", "DMIC1L Switch", "DMIC1 Left" }, + { "Port2_2 Mixer", "DMIC1R Switch", "DMIC1 Right" }, + { "Port2_2 Mixer", "DMIC2L Switch", "DMIC2 Left" }, + { "Port2_2 Mixer", "DMIC2R Switch", "DMIC2 Right" }, + + { "P1_1_TX", NULL, "Port1_1 Mixer" }, + { "P1_2_TX", NULL, "Port1_2 Mixer" }, + { "P1_3_TX", NULL, "Port1_3 Mixer" }, + { "P1_4_TX", NULL, "Port1_4 Mixer" }, + { "P1_5_TX", NULL, "Port1_5 Mixer" }, + { "P1_6_TX", NULL, "Port1_6 Mixer" }, + { "P1_7_TX", NULL, "Port1_7 Mixer" }, + { "P1_8_TX", NULL, "Port1_8 Mixer" }, + + { "P2_1_TX", NULL, "Port2_1 Mixer" }, + { "P2_2_TX", NULL, "Port2_2 Mixer" }, + + { "PORT1_SDO", "Port1 Capture Switch", "P1_1_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_2_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_3_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_4_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_5_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_6_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_7_TX"}, + { "PORT1_SDO", "Port1 Capture Switch", "P1_8_TX"}, + + { "PORT2_SDO", "Port2 Capture Switch", "P2_1_TX"}, + { "PORT2_SDO", "Port2 Capture Switch", "P2_2_TX"}, + + { "Mic1 Input", NULL, "AMIC1" }, + { "Mic2 Input", NULL, "AMIC2" }, + + { "AUXL Input", NULL, "AUXL" }, + { "AUXR Input", NULL, "AUXR" }, + + /* AUX connections */ + { "ADCL Mux", "Aux_L", "AUXL Input" }, + { "ADCL Mux", "MIC1", "Mic1 Input" }, + + { "ADCR Mux", "Aux_R", "AUXR Input" }, + { "ADCR Mux", "MIC2", "Mic2 Input" }, + + /* ADC connection */ + { "ADC Left", NULL, "ADCL Mux"}, + { "ADC Right", NULL, "ADCR Mux"}, + + { "DMIC1 Left", NULL, "DMIC1DAT"}, + { "DMIC1 Right", NULL, "DMIC1DAT"}, + { "DMIC2 Left", NULL, "DMIC2DAT"}, + { "DMIC2 Right", NULL, "DMIC2DAT"}, + + /* Sidetone map */ + { "Sidetone Mixer", NULL, "ADC Left" }, + { "Sidetone Mixer", NULL, "ADC Right" }, + { "Sidetone Mixer", NULL, "DMIC1 Left" }, + { "Sidetone Mixer", NULL, "DMIC1 Right" }, + { "Sidetone Mixer", NULL, "DMIC2 Left" }, + { "Sidetone Mixer", NULL, "DMIC2 Right" }, + + { "Sidetone", "Sidetone Switch", "Sidetone Mixer" }, +}; + +static int lm49453_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 clk_div = 0; + + /* Setting DAC clock dividers based on substream sample rate. */ + switch (params_rate(params)) { + case 8000: + case 16000: + case 32000: + case 24000: + case 48000: + clk_div = 256; + break; + case 11025: + case 22050: + case 44100: + clk_div = 216; + break; + case 96000: + clk_div = 127; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, LM49453_P0_ADC_CLK_DIV_REG, clk_div); + snd_soc_component_write(component, LM49453_P0_DAC_HP_CLK_DIV_REG, clk_div); + + return 0; +} + +static int lm49453_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + + u16 aif_val; + int mode = 0; + int clk_phase = 0; + int clk_shift = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aif_val = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif_val = LM49453_AUDIO_PORT1_BASIC_SYNC_MS; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif_val = LM49453_AUDIO_PORT1_BASIC_CLK_MS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif_val = LM49453_AUDIO_PORT1_BASIC_CLK_MS | + LM49453_AUDIO_PORT1_BASIC_SYNC_MS; + break; + default: + return -EINVAL; + } + + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_DSP_A: + mode = 1; + clk_phase = (1 << 5); + clk_shift = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + mode = 1; + clk_phase = (1 << 5); + clk_shift = 0; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, LM49453_P0_AUDIO_PORT1_BASIC_REG, + LM49453_AUDIO_PORT1_BASIC_FMT_MASK|BIT(0)|BIT(5), + (aif_val | mode | clk_phase)); + + snd_soc_component_write(component, LM49453_P0_AUDIO_PORT1_RX_MSB_REG, clk_shift); + + return 0; +} + +static int lm49453_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + u16 pll_clk = 0; + + switch (freq) { + case 12288000: + case 26000000: + case 19200000: + /* pll clk slection */ + pll_clk = 0; + break; + case 48000: + case 32576: + /* fll clk slection */ + pll_clk = BIT(4); + return 0; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, LM49453_P0_PMC_SETUP_REG, BIT(4), pll_clk); + + return 0; +} + +static int lm49453_hp_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, LM49453_P0_DAC_DSP_REG, BIT(1)|BIT(0), + (mute ? (BIT(1)|BIT(0)) : 0)); + return 0; +} + +static int lm49453_lo_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, LM49453_P0_DAC_DSP_REG, BIT(3)|BIT(2), + (mute ? (BIT(3)|BIT(2)) : 0)); + return 0; +} + +static int lm49453_ls_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, LM49453_P0_DAC_DSP_REG, BIT(5)|BIT(4), + (mute ? (BIT(5)|BIT(4)) : 0)); + return 0; +} + +static int lm49453_ep_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, LM49453_P0_DAC_DSP_REG, BIT(4), + (mute ? BIT(4) : 0)); + return 0; +} + +static int lm49453_ha_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_component_update_bits(dai->component, LM49453_P0_DAC_DSP_REG, BIT(7)|BIT(6), + (mute ? (BIT(7)|BIT(6)) : 0)); + return 0; +} + +static int lm49453_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct lm49453_priv *lm49453 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + regcache_sync(lm49453->regmap); + + snd_soc_component_update_bits(component, LM49453_P0_PMC_SETUP_REG, + LM49453_PMC_SETUP_CHIP_EN, LM49453_CHIP_EN); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, LM49453_P0_PMC_SETUP_REG, + LM49453_PMC_SETUP_CHIP_EN, 0); + break; + } + + return 0; +} + +/* Formates supported by LM49453 driver. */ +#define LM49453_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops lm49453_headset_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_hp_mute, +}; + +static const struct snd_soc_dai_ops lm49453_speaker_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_ls_mute, +}; + +static const struct snd_soc_dai_ops lm49453_haptic_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_ha_mute, +}; + +static const struct snd_soc_dai_ops lm49453_ep_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_ep_mute, +}; + +static const struct snd_soc_dai_ops lm49453_lineout_dai_ops = { + .hw_params = lm49453_hw_params, + .set_sysclk = lm49453_set_dai_sysclk, + .set_fmt = lm49453_set_dai_fmt, + .digital_mute = lm49453_lo_mute, +}; + +/* LM49453 dai structure. */ +static struct snd_soc_dai_driver lm49453_dai[] = { + { + .name = "LM49453 Headset", + .playback = { + .stream_name = "Headset", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 5, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_headset_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "LM49453 Speaker", + .playback = { + .stream_name = "Speaker", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_speaker_dai_ops, + }, + { + .name = "LM49453 Haptic", + .playback = { + .stream_name = "Haptic", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_haptic_dai_ops, + }, + { + .name = "LM49453 Earpiece", + .playback = { + .stream_name = "Earpiece", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_ep_dai_ops, + }, + { + .name = "LM49453 line out", + .playback = { + .stream_name = "Lineout", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = LM49453_FORMATS, + }, + .ops = &lm49453_lineout_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_lm49453 = { + .set_bias_level = lm49453_set_bias_level, + .controls = lm49453_snd_controls, + .num_controls = ARRAY_SIZE(lm49453_snd_controls), + .dapm_widgets = lm49453_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(lm49453_dapm_widgets), + .dapm_routes = lm49453_audio_map, + .num_dapm_routes = ARRAY_SIZE(lm49453_audio_map), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config lm49453_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = LM49453_MAX_REGISTER, + .reg_defaults = lm49453_reg_defs, + .num_reg_defaults = ARRAY_SIZE(lm49453_reg_defs), + .cache_type = REGCACHE_RBTREE, +}; + +static int lm49453_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lm49453_priv *lm49453; + int ret = 0; + + lm49453 = devm_kzalloc(&i2c->dev, sizeof(struct lm49453_priv), + GFP_KERNEL); + + if (lm49453 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, lm49453); + + lm49453->regmap = devm_regmap_init_i2c(i2c, &lm49453_regmap_config); + if (IS_ERR(lm49453->regmap)) { + ret = PTR_ERR(lm49453->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_lm49453, + lm49453_dai, ARRAY_SIZE(lm49453_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register component: %d\n", ret); + + return ret; +} + +static int lm49453_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id lm49453_i2c_id[] = { + { "lm49453", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm49453_i2c_id); + +static struct i2c_driver lm49453_i2c_driver = { + .driver = { + .name = "lm49453", + }, + .probe = lm49453_i2c_probe, + .remove = lm49453_i2c_remove, + .id_table = lm49453_i2c_id, +}; + +module_i2c_driver(lm49453_i2c_driver); + +MODULE_DESCRIPTION("ASoC LM49453 driver"); +MODULE_AUTHOR("M R Swami Reddy "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/lm49453.h b/sound/soc/codecs/lm49453.h new file mode 100644 index 000000000..a63cfa5c0 --- /dev/null +++ b/sound/soc/codecs/lm49453.h @@ -0,0 +1,380 @@ +/* + * lm49453.h - LM49453 ALSA Soc Audio drive + * + * Copyright (c) 2012 Texas Instruments, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + */ + +#ifndef _LM49453_H +#define _LM49453_H + +#include + +/* LM49453_P0 register space for page0 */ +#define LM49453_P0_PMC_SETUP_REG 0x00 +#define LM49453_P0_PLL_CLK_SEL1_REG 0x01 +#define LM49453_P0_PLL_CLK_SEL2_REG 0x02 +#define LM49453_P0_PMC_CLK_DIV_REG 0x03 +#define LM49453_P0_HSDET_CLK_DIV_REG 0x04 +#define LM49453_P0_DMIC_CLK_DIV_REG 0x05 +#define LM49453_P0_ADC_CLK_DIV_REG 0x06 +#define LM49453_P0_DAC_OT_CLK_DIV_REG 0x07 +#define LM49453_P0_PLL_HF_M_REG 0x08 +#define LM49453_P0_PLL_LF_M_REG 0x09 +#define LM49453_P0_PLL_NL_REG 0x0A +#define LM49453_P0_PLL_N_MODL_REG 0x0B +#define LM49453_P0_PLL_N_MODH_REG 0x0C +#define LM49453_P0_PLL_P1_REG 0x0D +#define LM49453_P0_PLL_P2_REG 0x0E +#define LM49453_P0_FLL_REF_FREQL_REG 0x0F +#define LM49453_P0_FLL_REF_FREQH_REG 0x10 +#define LM49453_P0_VCO_TARGETLL_REG 0x11 +#define LM49453_P0_VCO_TARGETLH_REG 0x12 +#define LM49453_P0_VCO_TARGETHL_REG 0x13 +#define LM49453_P0_VCO_TARGETHH_REG 0x14 +#define LM49453_P0_PLL_CONFIG_REG 0x15 +#define LM49453_P0_DAC_CLK_SEL_REG 0x16 +#define LM49453_P0_DAC_HP_CLK_DIV_REG 0x17 + +/* Analog Mixer Input Stages */ +#define LM49453_P0_MICL_REG 0x20 +#define LM49453_P0_MICR_REG 0x21 +#define LM49453_P0_EP_REG 0x24 +#define LM49453_P0_DIS_PKVL_FB_REG 0x25 + +/* Analog Mixer Output Stages */ +#define LM49453_P0_ANALOG_MIXER_ADC_REG 0x2E + +/*ADC or DAC */ +#define LM49453_P0_ADC_DSP_REG 0x30 +#define LM49453_P0_DAC_DSP_REG 0x31 + +/* EFFECTS ENABLES */ +#define LM49453_P0_ADC_FX_ENABLES_REG 0x33 + +/* GPIO */ +#define LM49453_P0_GPIO1_REG 0x38 +#define LM49453_P0_GPIO2_REG 0x39 +#define LM49453_P0_GPIO3_REG 0x3A +#define LM49453_P0_HAP_CTL_REG 0x3B +#define LM49453_P0_HAP_FREQ_PROG_LEFTL_REG 0x3C +#define LM49453_P0_HAP_FREQ_PROG_LEFTH_REG 0x3D +#define LM49453_P0_HAP_FREQ_PROG_RIGHTL_REG 0x3E +#define LM49453_P0_HAP_FREQ_PROG_RIGHTH_REG 0x3F + +/* DIGITAL MIXER */ +#define LM49453_P0_DMIX_CLK_SEL_REG 0x40 +#define LM49453_P0_PORT1_RX_LVL1_REG 0x41 +#define LM49453_P0_PORT1_RX_LVL2_REG 0x42 +#define LM49453_P0_PORT2_RX_LVL_REG 0x43 +#define LM49453_P0_PORT1_TX1_REG 0x44 +#define LM49453_P0_PORT1_TX2_REG 0x45 +#define LM49453_P0_PORT1_TX3_REG 0x46 +#define LM49453_P0_PORT1_TX4_REG 0x47 +#define LM49453_P0_PORT1_TX5_REG 0x48 +#define LM49453_P0_PORT1_TX6_REG 0x49 +#define LM49453_P0_PORT1_TX7_REG 0x4A +#define LM49453_P0_PORT1_TX8_REG 0x4B +#define LM49453_P0_PORT2_TX1_REG 0x4C +#define LM49453_P0_PORT2_TX2_REG 0x4D +#define LM49453_P0_STN_SEL_REG 0x4F +#define LM49453_P0_DACHPL1_REG 0x50 +#define LM49453_P0_DACHPL2_REG 0x51 +#define LM49453_P0_DACHPR1_REG 0x52 +#define LM49453_P0_DACHPR2_REG 0x53 +#define LM49453_P0_DACLOL1_REG 0x54 +#define LM49453_P0_DACLOL2_REG 0x55 +#define LM49453_P0_DACLOR1_REG 0x56 +#define LM49453_P0_DACLOR2_REG 0x57 +#define LM49453_P0_DACLSL1_REG 0x58 +#define LM49453_P0_DACLSL2_REG 0x59 +#define LM49453_P0_DACLSR1_REG 0x5A +#define LM49453_P0_DACLSR2_REG 0x5B +#define LM49453_P0_DACHAL1_REG 0x5C +#define LM49453_P0_DACHAL2_REG 0x5D +#define LM49453_P0_DACHAR1_REG 0x5E +#define LM49453_P0_DACHAR2_REG 0x5F + +/* AUDIO PORT 1 (TDM) */ +#define LM49453_P0_AUDIO_PORT1_BASIC_REG 0x60 +#define LM49453_P0_AUDIO_PORT1_CLK_GEN1_REG 0x61 +#define LM49453_P0_AUDIO_PORT1_CLK_GEN2_REG 0x62 +#define LM49453_P0_AUDIO_PORT1_CLK_GEN3_REG 0x63 +#define LM49453_P0_AUDIO_PORT1_SYNC_RATE_REG 0x64 +#define LM49453_P0_AUDIO_PORT1_SYNC_SDO_SETUP_REG 0x65 +#define LM49453_P0_AUDIO_PORT1_DATA_WIDTH_REG 0x66 +#define LM49453_P0_AUDIO_PORT1_RX_MSB_REG 0x67 +#define LM49453_P0_AUDIO_PORT1_TX_MSB_REG 0x68 +#define LM49453_P0_AUDIO_PORT1_TDM_CHANNELS_REG 0x69 + +/* AUDIO PORT 2 */ +#define LM49453_P0_AUDIO_PORT2_BASIC_REG 0x6A +#define LM49453_P0_AUDIO_PORT2_CLK_GEN1_REG 0x6B +#define LM49453_P0_AUDIO_PORT2_CLK_GEN2_REG 0x6C +#define LM49453_P0_AUDIO_PORT2_SYNC_GEN_REG 0x6D +#define LM49453_P0_AUDIO_PORT2_DATA_WIDTH_REG 0x6E +#define LM49453_P0_AUDIO_PORT2_RX_MODE_REG 0x6F +#define LM49453_P0_AUDIO_PORT2_TX_MODE_REG 0x70 + +/* SAMPLE RATE */ +#define LM49453_P0_PORT1_SR_LSB_REG 0x79 +#define LM49453_P0_PORT1_SR_MSB_REG 0x7A +#define LM49453_P0_PORT2_SR_LSB_REG 0x7B +#define LM49453_P0_PORT2_SR_MSB_REG 0x7C + +/* EFFECTS - HPFs */ +#define LM49453_P0_HPF_REG 0x80 + +/* EFFECTS ADC ALC */ +#define LM49453_P0_ADC_ALC1_REG 0x82 +#define LM49453_P0_ADC_ALC2_REG 0x83 +#define LM49453_P0_ADC_ALC3_REG 0x84 +#define LM49453_P0_ADC_ALC4_REG 0x85 +#define LM49453_P0_ADC_ALC5_REG 0x86 +#define LM49453_P0_ADC_ALC6_REG 0x87 +#define LM49453_P0_ADC_ALC7_REG 0x88 +#define LM49453_P0_ADC_ALC8_REG 0x89 +#define LM49453_P0_DMIC1_LEVELL_REG 0x8A +#define LM49453_P0_DMIC1_LEVELR_REG 0x8B +#define LM49453_P0_DMIC2_LEVELL_REG 0x8C +#define LM49453_P0_DMIC2_LEVELR_REG 0x8D +#define LM49453_P0_ADC_LEVELL_REG 0x8E +#define LM49453_P0_ADC_LEVELR_REG 0x8F +#define LM49453_P0_DAC_HP_LEVELL_REG 0x90 +#define LM49453_P0_DAC_HP_LEVELR_REG 0x91 +#define LM49453_P0_DAC_LO_LEVELL_REG 0x92 +#define LM49453_P0_DAC_LO_LEVELR_REG 0x93 +#define LM49453_P0_DAC_LS_LEVELL_REG 0x94 +#define LM49453_P0_DAC_LS_LEVELR_REG 0x95 +#define LM49453_P0_DAC_HA_LEVELL_REG 0x96 +#define LM49453_P0_DAC_HA_LEVELR_REG 0x97 +#define LM49453_P0_SOFT_MUTE_REG 0x98 +#define LM49453_P0_DMIC_MUTE_CFG_REG 0x99 +#define LM49453_P0_ADC_MUTE_CFG_REG 0x9A +#define LM49453_P0_DAC_MUTE_CFG_REG 0x9B + +/*DIGITAL MIC1 */ +#define LM49453_P0_DIGITAL_MIC1_CONFIG_REG 0xB0 +#define LM49453_P0_DIGITAL_MIC1_DATA_DELAYL_REG 0xB1 +#define LM49453_P0_DIGITAL_MIC1_DATA_DELAYR_REG 0xB2 + +/*DIGITAL MIC2 */ +#define LM49453_P0_DIGITAL_MIC2_CONFIG_REG 0xB3 +#define LM49453_P0_DIGITAL_MIC2_DATA_DELAYL_REG 0xB4 +#define LM49453_P0_DIGITAL_MIC2_DATA_DELAYR_REG 0xB5 + +/* ADC DECIMATOR */ +#define LM49453_P0_ADC_DECIMATOR_REG 0xB6 + +/* DAC CONFIGURE */ +#define LM49453_P0_DAC_CONFIG_REG 0xB7 + +/* SIDETONE */ +#define LM49453_P0_STN_VOL_ADCL_REG 0xB8 +#define LM49453_P0_STN_VOL_ADCR_REG 0xB9 +#define LM49453_P0_STN_VOL_DMIC1L_REG 0xBA +#define LM49453_P0_STN_VOL_DMIC1R_REG 0xBB +#define LM49453_P0_STN_VOL_DMIC2L_REG 0xBC +#define LM49453_P0_STN_VOL_DMIC2R_REG 0xBD + +/* ADC/DAC CLIPPING MONITORS (Read Only/Write to Clear) */ +#define LM49453_P0_ADC_DEC_CLIP_REG 0xC2 +#define LM49453_P0_ADC_HPF_CLIP_REG 0xC3 +#define LM49453_P0_ADC_LVL_CLIP_REG 0xC4 +#define LM49453_P0_DAC_LVL_CLIP_REG 0xC5 + +/* ADC ALC EFFECT MONITORS (Read Only) */ +#define LM49453_P0_ADC_LVLMONL_REG 0xC8 +#define LM49453_P0_ADC_LVLMONR_REG 0xC9 +#define LM49453_P0_ADC_ALCMONL_REG 0xCA +#define LM49453_P0_ADC_ALCMONR_REG 0xCB +#define LM49453_P0_ADC_MUTED_REG 0xCC +#define LM49453_P0_DAC_MUTED_REG 0xCD + +/* HEADSET DETECT */ +#define LM49453_P0_HSD_PPB_LONG_CNT_LIMITL_REG 0xD0 +#define LM49453_P0_HSD_PPB_LONG_CNT_LIMITR_REG 0xD1 +#define LM49453_P0_HSD_PIN3_4_EX_LOOP_CNT_LIMITL_REG 0xD2 +#define LM49453_P0_HSD_PIN3_4_EX_LOOP_CNT_LIMITH_REG 0xD3 +#define LM49453_P0_HSD_TIMEOUT1_REG 0xD4 +#define LM49453_P0_HSD_TIMEOUT2_REG 0xD5 +#define LM49453_P0_HSD_TIMEOUT3_REG 0xD6 +#define LM49453_P0_HSD_PIN3_4_CFG_REG 0xD7 +#define LM49453_P0_HSD_IRQ1_REG 0xD8 +#define LM49453_P0_HSD_IRQ2_REG 0xD9 +#define LM49453_P0_HSD_IRQ3_REG 0xDA +#define LM49453_P0_HSD_IRQ4_REG 0xDB +#define LM49453_P0_HSD_IRQ_MASK1_REG 0xDC +#define LM49453_P0_HSD_IRQ_MASK2_REG 0xDD +#define LM49453_P0_HSD_IRQ_MASK3_REG 0xDE +#define LM49453_P0_HSD_R_HPLL_REG 0xE0 +#define LM49453_P0_HSD_R_HPLH_REG 0xE1 +#define LM49453_P0_HSD_R_HPLU_REG 0xE2 +#define LM49453_P0_HSD_R_HPRL_REG 0xE3 +#define LM49453_P0_HSD_R_HPRH_REG 0xE4 +#define LM49453_P0_HSD_R_HPRU_REG 0xE5 +#define LM49453_P0_HSD_VEL_L_FINALL_REG 0xE6 +#define LM49453_P0_HSD_VEL_L_FINALH_REG 0xE7 +#define LM49453_P0_HSD_VEL_L_FINALU_REG 0xE8 +#define LM49453_P0_HSD_RO_FINALL_REG 0xE9 +#define LM49453_P0_HSD_RO_FINALH_REG 0xEA +#define LM49453_P0_HSD_RO_FINALU_REG 0xEB +#define LM49453_P0_HSD_VMIC_BIAS_FINALL_REG 0xEC +#define LM49453_P0_HSD_VMIC_BIAS_FINALH_REG 0xED +#define LM49453_P0_HSD_VMIC_BIAS_FINALU_REG 0xEE +#define LM49453_P0_HSD_PIN_CONFIG_REG 0xEF +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATUS1_REG 0xF1 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATUS2_REG 0xF2 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATUS3_REG 0xF3 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATEL_REG 0xF4 +#define LM49453_P0_HSD_PLUG_DETECT_BB_IRQ_STATEH_REG 0xF5 + +/* I/O PULLDOWN CONFIG */ +#define LM49453_P0_PULL_CONFIG1_REG 0xF8 +#define LM49453_P0_PULL_CONFIG2_REG 0xF9 +#define LM49453_P0_PULL_CONFIG3_REG 0xFA + +/* RESET */ +#define LM49453_P0_RESET_REG 0xFE + +/* PAGE */ +#define LM49453_PAGE_REG 0xFF + +#define LM49453_MAX_REGISTER (0xFF+1) + +/* LM49453_P0_PMC_SETUP_REG (0x00h) */ +#define LM49453_PMC_SETUP_CHIP_EN (BIT(1)|BIT(0)) +#define LM49453_PMC_SETUP_PLL_EN BIT(2) +#define LM49453_PMC_SETUP_PLL_P2_EN BIT(3) +#define LM49453_PMC_SETUP_PLL_FLL BIT(4) +#define LM49453_PMC_SETUP_MCLK_OVER BIT(5) +#define LM49453_PMC_SETUP_RTC_CLK_OVER BIT(6) +#define LM49453_PMC_SETUP_CHIP_ACTIVE BIT(7) + +/* Chip Enable bits */ +#define LM49453_CHIP_EN_SHUTDOWN 0x00 +#define LM49453_CHIP_EN 0x01 +#define LM49453_CHIP_EN_HSD_DETECT 0x02 +#define LM49453_CHIP_EN_INVALID_HSD 0x03 + +/* LM49453_P0_PLL_CLK_SEL1_REG (0x01h) */ +#define LM49453_CLK_SEL1_MCLK_SEL 0x11 +#define LM49453_CLK_SEL1_RTC_SEL 0x11 +#define LM49453_CLK_SEL1_PORT1_SEL 0x10 +#define LM49453_CLK_SEL1_PORT2_SEL 0x11 + +/* LM49453_P0_PLL_CLK_SEL2_REG (0x02h) */ +#define LM49453_CLK_SEL2_ADC_CLK_SEL 0x38 + +/* LM49453_P0_FLL_REF_FREQL_REG (0x0F) */ +#define LM49453_FLL_REF_FREQ_VAL 0x8ca0001 + +/* LM49453_P0_VCO_TARGETLL_REG (0x11) */ +#define LM49453_VCO_TARGET_VAL 0x8ca0001 + +/* LM49453_P0_ADC_DSP_REG (0x30h) */ +#define LM49453_ADC_DSP_ADC_MUTEL BIT(0) +#define LM49453_ADC_DSP_ADC_MUTER BIT(1) +#define LM49453_ADC_DSP_DMIC1_MUTEL BIT(2) +#define LM49453_ADC_DSP_DMIC1_MUTER BIT(3) +#define LM49453_ADC_DSP_DMIC2_MUTEL BIT(4) +#define LM49453_ADC_DSP_DMIC2_MUTER BIT(5) +#define LM49453_ADC_DSP_MUTE_ALL 0x3F + +/* LM49453_P0_DAC_DSP_REG (0x31h) */ +#define LM49453_DAC_DSP_MUTE_ALL 0xFF + +/* LM49453_P0_AUDIO_PORT1_BASIC_REG (0x60h) */ +#define LM49453_AUDIO_PORT1_BASIC_FMT_MASK (BIT(4)|BIT(3)) +#define LM49453_AUDIO_PORT1_BASIC_CLK_MS BIT(3) +#define LM49453_AUDIO_PORT1_BASIC_SYNC_MS BIT(4) + +/* LM49453_P0_RESET_REG (0xFEh) */ +#define LM49453_RESET_REG_RST BIT(0) + +/* Page select register bits (0xFF) */ +#define LM49453_PAGE0_SELECT 0x0 +#define LM49453_PAGE1_SELECT 0x1 + +/* LM49453_P0_HSD_PIN3_4_CFG_REG (Jack Pin config - 0xD7) */ +#define LM49453_JACK_DISABLE 0x00 +#define LM49453_JACK_CONFIG1 0x01 +#define LM49453_JACK_CONFIG2 0x02 +#define LM49453_JACK_CONFIG3 0x03 +#define LM49453_JACK_CONFIG4 0x04 +#define LM49453_JACK_CONFIG5 0x05 + +/* Page 1 REGISTERS */ + +/* SIDETONE */ +#define LM49453_P1_SIDETONE_SA0L_REG 0x80 +#define LM49453_P1_SIDETONE_SA0H_REG 0x81 +#define LM49453_P1_SIDETONE_SAB0U_REG 0x82 +#define LM49453_P1_SIDETONE_SB0L_REG 0x83 +#define LM49453_P1_SIDETONE_SB0H_REG 0x84 +#define LM49453_P1_SIDETONE_SH0L_REG 0x85 +#define LM49453_P1_SIDETONE_SH0H_REG 0x86 +#define LM49453_P1_SIDETONE_SH0U_REG 0x87 +#define LM49453_P1_SIDETONE_SA1L_REG 0x88 +#define LM49453_P1_SIDETONE_SA1H_REG 0x89 +#define LM49453_P1_SIDETONE_SAB1U_REG 0x8A +#define LM49453_P1_SIDETONE_SB1L_REG 0x8B +#define LM49453_P1_SIDETONE_SB1H_REG 0x8C +#define LM49453_P1_SIDETONE_SH1L_REG 0x8D +#define LM49453_P1_SIDETONE_SH1H_REG 0x8E +#define LM49453_P1_SIDETONE_SH1U_REG 0x8F +#define LM49453_P1_SIDETONE_SA2L_REG 0x90 +#define LM49453_P1_SIDETONE_SA2H_REG 0x91 +#define LM49453_P1_SIDETONE_SAB2U_REG 0x92 +#define LM49453_P1_SIDETONE_SB2L_REG 0x93 +#define LM49453_P1_SIDETONE_SB2H_REG 0x94 +#define LM49453_P1_SIDETONE_SH2L_REG 0x95 +#define LM49453_P1_SIDETONE_SH2H_REG 0x96 +#define LM49453_P1_SIDETONE_SH2U_REG 0x97 +#define LM49453_P1_SIDETONE_SA3L_REG 0x98 +#define LM49453_P1_SIDETONE_SA3H_REG 0x99 +#define LM49453_P1_SIDETONE_SAB3U_REG 0x9A +#define LM49453_P1_SIDETONE_SB3L_REG 0x9B +#define LM49453_P1_SIDETONE_SB3H_REG 0x9C +#define LM49453_P1_SIDETONE_SH3L_REG 0x9D +#define LM49453_P1_SIDETONE_SH3H_REG 0x9E +#define LM49453_P1_SIDETONE_SH3U_REG 0x9F +#define LM49453_P1_SIDETONE_SA4L_REG 0xA0 +#define LM49453_P1_SIDETONE_SA4H_REG 0xA1 +#define LM49453_P1_SIDETONE_SAB4U_REG 0xA2 +#define LM49453_P1_SIDETONE_SB4L_REG 0xA3 +#define LM49453_P1_SIDETONE_SB4H_REG 0xA4 +#define LM49453_P1_SIDETONE_SH4L_REG 0xA5 +#define LM49453_P1_SIDETONE_SH4H_REG 0xA6 +#define LM49453_P1_SIDETONE_SH4U_REG 0xA7 +#define LM49453_P1_SIDETONE_SA5L_REG 0xA8 +#define LM49453_P1_SIDETONE_SA5H_REG 0xA9 +#define LM49453_P1_SIDETONE_SAB5U_REG 0xAA +#define LM49453_P1_SIDETONE_SB5L_REG 0xAB +#define LM49453_P1_SIDETONE_SB5H_REG 0xAC +#define LM49453_P1_SIDETONE_SH5L_REG 0xAD +#define LM49453_P1_SIDETONE_SH5H_REG 0xAE +#define LM49453_P1_SIDETONE_SH5U_REG 0xAF + +/* CHARGE PUMP CONFIG */ +#define LM49453_P1_CP_CONFIG1_REG 0xB0 +#define LM49453_P1_CP_CONFIG2_REG 0xB1 +#define LM49453_P1_CP_CONFIG3_REG 0xB2 +#define LM49453_P1_CP_CONFIG4_REG 0xB3 +#define LM49453_P1_CP_LA_VTH1L_REG 0xB4 +#define LM49453_P1_CP_LA_VTH1M_REG 0xB5 +#define LM49453_P1_CP_LA_VTH2L_REG 0xB6 +#define LM49453_P1_CP_LA_VTH2M_REG 0xB7 +#define LM49453_P1_CP_LA_VTH3L_REG 0xB8 +#define LM49453_P1_CP_LA_VTH3H_REG 0xB9 +#define LM49453_P1_CP_CLK_DIV_REG 0xBA + +/* DAC */ +#define LM49453_P1_DAC_CHOP_REG 0xC0 + +#define LM49453_CLK_SRC_MCLK 1 +#endif diff --git a/sound/soc/codecs/max9759.c b/sound/soc/codecs/max9759.c new file mode 100644 index 000000000..ec0a482e9 --- /dev/null +++ b/sound/soc/codecs/max9759.c @@ -0,0 +1,208 @@ +// SPDX-Licence-Identifier: GPL-2.0 +/* + * MAX9759 Amplifier Driver + * + * Copyright (c) 2017 BayLibre, SAS. + * Author: Neil Armstrong + */ + +#include +#include +#include +#include +#include + +#define DRV_NAME "max9759" + +struct max9759 { + struct gpio_desc *gpiod_shutdown; + struct gpio_desc *gpiod_mute; + struct gpio_descs *gpiod_gain; + bool is_mute; + unsigned int gain; +}; + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct max9759 *priv = snd_soc_component_get_drvdata(c); + + if (SND_SOC_DAPM_EVENT_ON(event)) + gpiod_set_value_cansleep(priv->gpiod_shutdown, 0); + else + gpiod_set_value_cansleep(priv->gpiod_shutdown, 1); + + return 0; +} + +/* From 6dB to 24dB in steps of 6dB */ +static const DECLARE_TLV_DB_SCALE(speaker_gain_tlv, 600, 600, 0); + +static int speaker_gain_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct max9759 *priv = snd_soc_component_get_drvdata(c); + + ucontrol->value.integer.value[0] = priv->gain; + + return 0; +} + +static const bool speaker_gain_table[4][2] = { + /* G1, G2 */ + {true, true}, /* +6dB */ + {false, true}, /* +12dB */ + {true, false}, /* +18dB */ + {false, false}, /* +24dB */ +}; + +static int speaker_gain_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct max9759 *priv = snd_soc_component_get_drvdata(c); + + if (ucontrol->value.integer.value[0] < 0 || + ucontrol->value.integer.value[0] > 3) + return -EINVAL; + + priv->gain = ucontrol->value.integer.value[0]; + + /* G1 */ + gpiod_set_value_cansleep(priv->gpiod_gain->desc[0], + speaker_gain_table[priv->gain][0]); + /* G2 */ + gpiod_set_value_cansleep(priv->gpiod_gain->desc[1], + speaker_gain_table[priv->gain][1]); + + return 1; +} + +static int speaker_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct max9759 *priv = snd_soc_component_get_drvdata(c); + + ucontrol->value.integer.value[0] = !priv->is_mute; + + return 0; +} + +static int speaker_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct max9759 *priv = snd_soc_component_get_drvdata(c); + + priv->is_mute = !ucontrol->value.integer.value[0]; + + gpiod_set_value_cansleep(priv->gpiod_mute, priv->is_mute); + + return 1; +} + +static const struct snd_kcontrol_new max9759_dapm_controls[] = { + SOC_SINGLE_EXT_TLV("Speaker Gain Volume", 0, 0, 3, 0, + speaker_gain_control_get, speaker_gain_control_put, + speaker_gain_tlv), + SOC_SINGLE_BOOL_EXT("Playback Switch", 0, + speaker_mute_get, speaker_mute_put), +}; + +static const struct snd_soc_dapm_widget max9759_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("INL"), + SND_SOC_DAPM_INPUT("INR"), + SND_SOC_DAPM_PGA_E("PGA", SND_SOC_NOPM, 0, 0, NULL, 0, pga_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD)), + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route max9759_dapm_routes[] = { + { "PGA", NULL, "INL" }, + { "PGA", NULL, "INR" }, + { "OUTL", NULL, "PGA" }, + { "OUTR", NULL, "PGA" }, +}; + +static const struct snd_soc_component_driver max9759_component_driver = { + .controls = max9759_dapm_controls, + .num_controls = ARRAY_SIZE(max9759_dapm_controls), + .dapm_widgets = max9759_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9759_dapm_widgets), + .dapm_routes = max9759_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max9759_dapm_routes), +}; + +static int max9759_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct max9759 *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + priv->gpiod_shutdown = devm_gpiod_get(dev, "shutdown", GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_shutdown)) { + err = PTR_ERR(priv->gpiod_shutdown); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'shutdown' gpio: %d", err); + return err; + } + + priv->gpiod_mute = devm_gpiod_get(dev, "mute", GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_mute)) { + err = PTR_ERR(priv->gpiod_mute); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'mute' gpio: %d", err); + return err; + } + priv->is_mute = true; + + priv->gpiod_gain = devm_gpiod_get_array(dev, "gain", GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_gain)) { + err = PTR_ERR(priv->gpiod_gain); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'gain' gpios: %d", err); + return err; + } + priv->gain = 0; + + if (priv->gpiod_gain->ndescs != 2) { + dev_err(dev, "Invalid 'gain' gpios count: %d", + priv->gpiod_gain->ndescs); + return -EINVAL; + } + + return devm_snd_soc_register_component(dev, &max9759_component_driver, + NULL, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id max9759_ids[] = { + { .compatible = "maxim,max9759", }, + { } +}; +MODULE_DEVICE_TABLE(of, max9759_ids); +#endif + +static struct platform_driver max9759_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(max9759_ids), + }, + .probe = max9759_probe, +}; + +module_platform_driver(max9759_driver); + +MODULE_DESCRIPTION("ASoC MAX9759 amplifier driver"); +MODULE_AUTHOR("Neil Armstrong "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9768.c b/sound/soc/codecs/max9768.c new file mode 100644 index 000000000..7017c0389 --- /dev/null +++ b/sound/soc/codecs/max9768.c @@ -0,0 +1,228 @@ +/* + * MAX9768 AMP driver + * + * Copyright (C) 2011, 2012 by Wolfram Sang, Pengutronix e.K. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* "Registers" */ +#define MAX9768_VOL 0 +#define MAX9768_CTRL 3 + +/* Commands */ +#define MAX9768_CTRL_PWM 0x15 +#define MAX9768_CTRL_FILTERLESS 0x16 + +struct max9768 { + struct regmap *regmap; + int mute_gpio; + int shdn_gpio; + u32 flags; +}; + +static const struct reg_default max9768_default_regs[] = { + { 0, 0 }, + { 3, MAX9768_CTRL_FILTERLESS}, +}; + +static int max9768_get_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct max9768 *max9768 = snd_soc_component_get_drvdata(c); + int val = gpio_get_value_cansleep(max9768->mute_gpio); + + ucontrol->value.integer.value[0] = !val; + + return 0; +} + +static int max9768_set_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct max9768 *max9768 = snd_soc_component_get_drvdata(c); + + gpio_set_value_cansleep(max9768->mute_gpio, !ucontrol->value.integer.value[0]); + + return 0; +} + +static const DECLARE_TLV_DB_RANGE(volume_tlv, + 0, 0, TLV_DB_SCALE_ITEM(-16150, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(-9280, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(-9030, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(-8680, 0, 0), + 4, 4, TLV_DB_SCALE_ITEM(-8430, 0, 0), + 5, 5, TLV_DB_SCALE_ITEM(-8080, 0, 0), + 6, 6, TLV_DB_SCALE_ITEM(-7830, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(-7470, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(-7220, 0, 0), + 9, 9, TLV_DB_SCALE_ITEM(-6870, 0, 0), + 10, 10, TLV_DB_SCALE_ITEM(-6620, 0, 0), + 11, 11, TLV_DB_SCALE_ITEM(-6270, 0, 0), + 12, 12, TLV_DB_SCALE_ITEM(-6020, 0, 0), + 13, 13, TLV_DB_SCALE_ITEM(-5670, 0, 0), + 14, 14, TLV_DB_SCALE_ITEM(-5420, 0, 0), + 15, 17, TLV_DB_SCALE_ITEM(-5060, 250, 0), + 18, 18, TLV_DB_SCALE_ITEM(-4370, 0, 0), + 19, 19, TLV_DB_SCALE_ITEM(-4210, 0, 0), + 20, 20, TLV_DB_SCALE_ITEM(-3960, 0, 0), + 21, 21, TLV_DB_SCALE_ITEM(-3760, 0, 0), + 22, 22, TLV_DB_SCALE_ITEM(-3600, 0, 0), + 23, 23, TLV_DB_SCALE_ITEM(-3340, 0, 0), + 24, 24, TLV_DB_SCALE_ITEM(-3150, 0, 0), + 25, 25, TLV_DB_SCALE_ITEM(-2980, 0, 0), + 26, 26, TLV_DB_SCALE_ITEM(-2720, 0, 0), + 27, 27, TLV_DB_SCALE_ITEM(-2520, 0, 0), + 28, 30, TLV_DB_SCALE_ITEM(-2350, 190, 0), + 31, 31, TLV_DB_SCALE_ITEM(-1750, 0, 0), + 32, 34, TLV_DB_SCALE_ITEM(-1640, 100, 0), + 35, 37, TLV_DB_SCALE_ITEM(-1310, 110, 0), + 38, 39, TLV_DB_SCALE_ITEM(-990, 100, 0), + 40, 40, TLV_DB_SCALE_ITEM(-710, 0, 0), + 41, 41, TLV_DB_SCALE_ITEM(-600, 0, 0), + 42, 42, TLV_DB_SCALE_ITEM(-500, 0, 0), + 43, 43, TLV_DB_SCALE_ITEM(-340, 0, 0), + 44, 44, TLV_DB_SCALE_ITEM(-190, 0, 0), + 45, 45, TLV_DB_SCALE_ITEM(-50, 0, 0), + 46, 46, TLV_DB_SCALE_ITEM(50, 0, 0), + 47, 50, TLV_DB_SCALE_ITEM(120, 40, 0), + 51, 57, TLV_DB_SCALE_ITEM(290, 50, 0), + 58, 58, TLV_DB_SCALE_ITEM(650, 0, 0), + 59, 62, TLV_DB_SCALE_ITEM(700, 60, 0), + 63, 63, TLV_DB_SCALE_ITEM(950, 0, 0) +); + +static const struct snd_kcontrol_new max9768_volume[] = { + SOC_SINGLE_TLV("Playback Volume", MAX9768_VOL, 0, 63, 0, volume_tlv), +}; + +static const struct snd_kcontrol_new max9768_mute[] = { + SOC_SINGLE_BOOL_EXT("Playback Switch", 0, max9768_get_gpio, max9768_set_gpio), +}; + +static const struct snd_soc_dapm_widget max9768_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN"), + +SND_SOC_DAPM_OUTPUT("OUT+"), +SND_SOC_DAPM_OUTPUT("OUT-"), +}; + +static const struct snd_soc_dapm_route max9768_dapm_routes[] = { + { "OUT+", NULL, "IN" }, + { "OUT-", NULL, "IN" }, +}; + +static int max9768_probe(struct snd_soc_component *component) +{ + struct max9768 *max9768 = snd_soc_component_get_drvdata(component); + int ret; + + if (max9768->flags & MAX9768_FLAG_CLASSIC_PWM) { + ret = regmap_write(max9768->regmap, MAX9768_CTRL, + MAX9768_CTRL_PWM); + if (ret) + return ret; + } + + if (gpio_is_valid(max9768->mute_gpio)) { + ret = snd_soc_add_component_controls(component, max9768_mute, + ARRAY_SIZE(max9768_mute)); + if (ret) + return ret; + } + + return 0; +} + +static const struct snd_soc_component_driver max9768_component_driver = { + .probe = max9768_probe, + .controls = max9768_volume, + .num_controls = ARRAY_SIZE(max9768_volume), + .dapm_widgets = max9768_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9768_dapm_widgets), + .dapm_routes = max9768_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max9768_dapm_routes), +}; + +static const struct regmap_config max9768_i2c_regmap_config = { + .reg_bits = 2, + .val_bits = 6, + .max_register = 3, + .reg_defaults = max9768_default_regs, + .num_reg_defaults = ARRAY_SIZE(max9768_default_regs), + .cache_type = REGCACHE_RBTREE, +}; + +static int max9768_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max9768 *max9768; + struct max9768_pdata *pdata = client->dev.platform_data; + int err; + + max9768 = devm_kzalloc(&client->dev, sizeof(*max9768), GFP_KERNEL); + if (!max9768) + return -ENOMEM; + + if (pdata) { + /* Mute on powerup to avoid clicks */ + err = devm_gpio_request_one(&client->dev, pdata->mute_gpio, + GPIOF_INIT_HIGH, "MAX9768 Mute"); + max9768->mute_gpio = err ?: pdata->mute_gpio; + + /* Activate chip by releasing shutdown, enables I2C */ + err = devm_gpio_request_one(&client->dev, pdata->shdn_gpio, + GPIOF_INIT_HIGH, "MAX9768 Shutdown"); + max9768->shdn_gpio = err ?: pdata->shdn_gpio; + + max9768->flags = pdata->flags; + } else { + max9768->shdn_gpio = -EINVAL; + max9768->mute_gpio = -EINVAL; + } + + i2c_set_clientdata(client, max9768); + + max9768->regmap = devm_regmap_init_i2c(client, &max9768_i2c_regmap_config); + if (IS_ERR(max9768->regmap)) + return PTR_ERR(max9768->regmap); + + return devm_snd_soc_register_component(&client->dev, + &max9768_component_driver, NULL, 0); +} + +static const struct i2c_device_id max9768_i2c_id[] = { + { "max9768", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9768_i2c_id); + +static struct i2c_driver max9768_i2c_driver = { + .driver = { + .name = "max9768", + }, + .probe = max9768_i2c_probe, + .id_table = max9768_i2c_id, +}; +module_i2c_driver(max9768_i2c_driver); + +MODULE_AUTHOR("Wolfram Sang "); +MODULE_DESCRIPTION("ASoC MAX9768 amplifier driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c new file mode 100644 index 000000000..fb515aaa5 --- /dev/null +++ b/sound/soc/codecs/max98088.c @@ -0,0 +1,1757 @@ +/* + * max98088.c -- MAX98088 ALSA SoC Audio driver + * + * Copyright 2010 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98088.h" + +enum max98088_type { + MAX98088, + MAX98089, +}; + +struct max98088_cdata { + unsigned int rate; + unsigned int fmt; + int eq_sel; +}; + +struct max98088_priv { + struct regmap *regmap; + enum max98088_type devtype; + struct max98088_pdata *pdata; + unsigned int sysclk; + struct max98088_cdata dai[2]; + int eq_textcnt; + const char **eq_texts; + struct soc_enum eq_enum; + u8 ina_state; + u8 inb_state; + unsigned int ex_mode; + unsigned int digmic; + unsigned int mic1pre; + unsigned int mic2pre; + unsigned int extmic_mode; +}; + +static const struct reg_default max98088_reg[] = { + { 0xf, 0x00 }, /* 0F interrupt enable */ + + { 0x10, 0x00 }, /* 10 master clock */ + { 0x11, 0x00 }, /* 11 DAI1 clock mode */ + { 0x12, 0x00 }, /* 12 DAI1 clock control */ + { 0x13, 0x00 }, /* 13 DAI1 clock control */ + { 0x14, 0x00 }, /* 14 DAI1 format */ + { 0x15, 0x00 }, /* 15 DAI1 clock */ + { 0x16, 0x00 }, /* 16 DAI1 config */ + { 0x17, 0x00 }, /* 17 DAI1 TDM */ + { 0x18, 0x00 }, /* 18 DAI1 filters */ + { 0x19, 0x00 }, /* 19 DAI2 clock mode */ + { 0x1a, 0x00 }, /* 1A DAI2 clock control */ + { 0x1b, 0x00 }, /* 1B DAI2 clock control */ + { 0x1c, 0x00 }, /* 1C DAI2 format */ + { 0x1d, 0x00 }, /* 1D DAI2 clock */ + { 0x1e, 0x00 }, /* 1E DAI2 config */ + { 0x1f, 0x00 }, /* 1F DAI2 TDM */ + + { 0x20, 0x00 }, /* 20 DAI2 filters */ + { 0x21, 0x00 }, /* 21 data config */ + { 0x22, 0x00 }, /* 22 DAC mixer */ + { 0x23, 0x00 }, /* 23 left ADC mixer */ + { 0x24, 0x00 }, /* 24 right ADC mixer */ + { 0x25, 0x00 }, /* 25 left HP mixer */ + { 0x26, 0x00 }, /* 26 right HP mixer */ + { 0x27, 0x00 }, /* 27 HP control */ + { 0x28, 0x00 }, /* 28 left REC mixer */ + { 0x29, 0x00 }, /* 29 right REC mixer */ + { 0x2a, 0x00 }, /* 2A REC control */ + { 0x2b, 0x00 }, /* 2B left SPK mixer */ + { 0x2c, 0x00 }, /* 2C right SPK mixer */ + { 0x2d, 0x00 }, /* 2D SPK control */ + { 0x2e, 0x00 }, /* 2E sidetone */ + { 0x2f, 0x00 }, /* 2F DAI1 playback level */ + + { 0x30, 0x00 }, /* 30 DAI1 playback level */ + { 0x31, 0x00 }, /* 31 DAI2 playback level */ + { 0x32, 0x00 }, /* 32 DAI2 playbakc level */ + { 0x33, 0x00 }, /* 33 left ADC level */ + { 0x34, 0x00 }, /* 34 right ADC level */ + { 0x35, 0x00 }, /* 35 MIC1 level */ + { 0x36, 0x00 }, /* 36 MIC2 level */ + { 0x37, 0x00 }, /* 37 INA level */ + { 0x38, 0x00 }, /* 38 INB level */ + { 0x39, 0x00 }, /* 39 left HP volume */ + { 0x3a, 0x00 }, /* 3A right HP volume */ + { 0x3b, 0x00 }, /* 3B left REC volume */ + { 0x3c, 0x00 }, /* 3C right REC volume */ + { 0x3d, 0x00 }, /* 3D left SPK volume */ + { 0x3e, 0x00 }, /* 3E right SPK volume */ + { 0x3f, 0x00 }, /* 3F MIC config */ + + { 0x40, 0x00 }, /* 40 MIC threshold */ + { 0x41, 0x00 }, /* 41 excursion limiter filter */ + { 0x42, 0x00 }, /* 42 excursion limiter threshold */ + { 0x43, 0x00 }, /* 43 ALC */ + { 0x44, 0x00 }, /* 44 power limiter threshold */ + { 0x45, 0x00 }, /* 45 power limiter config */ + { 0x46, 0x00 }, /* 46 distortion limiter config */ + { 0x47, 0x00 }, /* 47 audio input */ + { 0x48, 0x00 }, /* 48 microphone */ + { 0x49, 0x00 }, /* 49 level control */ + { 0x4a, 0x00 }, /* 4A bypass switches */ + { 0x4b, 0x00 }, /* 4B jack detect */ + { 0x4c, 0x00 }, /* 4C input enable */ + { 0x4d, 0x00 }, /* 4D output enable */ + { 0x4e, 0xF0 }, /* 4E bias control */ + { 0x4f, 0x00 }, /* 4F DAC power */ + + { 0x50, 0x0F }, /* 50 DAC power */ + { 0x51, 0x00 }, /* 51 system */ + { 0x52, 0x00 }, /* 52 DAI1 EQ1 */ + { 0x53, 0x00 }, /* 53 DAI1 EQ1 */ + { 0x54, 0x00 }, /* 54 DAI1 EQ1 */ + { 0x55, 0x00 }, /* 55 DAI1 EQ1 */ + { 0x56, 0x00 }, /* 56 DAI1 EQ1 */ + { 0x57, 0x00 }, /* 57 DAI1 EQ1 */ + { 0x58, 0x00 }, /* 58 DAI1 EQ1 */ + { 0x59, 0x00 }, /* 59 DAI1 EQ1 */ + { 0x5a, 0x00 }, /* 5A DAI1 EQ1 */ + { 0x5b, 0x00 }, /* 5B DAI1 EQ1 */ + { 0x5c, 0x00 }, /* 5C DAI1 EQ2 */ + { 0x5d, 0x00 }, /* 5D DAI1 EQ2 */ + { 0x5e, 0x00 }, /* 5E DAI1 EQ2 */ + { 0x5f, 0x00 }, /* 5F DAI1 EQ2 */ + + { 0x60, 0x00 }, /* 60 DAI1 EQ2 */ + { 0x61, 0x00 }, /* 61 DAI1 EQ2 */ + { 0x62, 0x00 }, /* 62 DAI1 EQ2 */ + { 0x63, 0x00 }, /* 63 DAI1 EQ2 */ + { 0x64, 0x00 }, /* 64 DAI1 EQ2 */ + { 0x65, 0x00 }, /* 65 DAI1 EQ2 */ + { 0x66, 0x00 }, /* 66 DAI1 EQ3 */ + { 0x67, 0x00 }, /* 67 DAI1 EQ3 */ + { 0x68, 0x00 }, /* 68 DAI1 EQ3 */ + { 0x69, 0x00 }, /* 69 DAI1 EQ3 */ + { 0x6a, 0x00 }, /* 6A DAI1 EQ3 */ + { 0x6b, 0x00 }, /* 6B DAI1 EQ3 */ + { 0x6c, 0x00 }, /* 6C DAI1 EQ3 */ + { 0x6d, 0x00 }, /* 6D DAI1 EQ3 */ + { 0x6e, 0x00 }, /* 6E DAI1 EQ3 */ + { 0x6f, 0x00 }, /* 6F DAI1 EQ3 */ + + { 0x70, 0x00 }, /* 70 DAI1 EQ4 */ + { 0x71, 0x00 }, /* 71 DAI1 EQ4 */ + { 0x72, 0x00 }, /* 72 DAI1 EQ4 */ + { 0x73, 0x00 }, /* 73 DAI1 EQ4 */ + { 0x74, 0x00 }, /* 74 DAI1 EQ4 */ + { 0x75, 0x00 }, /* 75 DAI1 EQ4 */ + { 0x76, 0x00 }, /* 76 DAI1 EQ4 */ + { 0x77, 0x00 }, /* 77 DAI1 EQ4 */ + { 0x78, 0x00 }, /* 78 DAI1 EQ4 */ + { 0x79, 0x00 }, /* 79 DAI1 EQ4 */ + { 0x7a, 0x00 }, /* 7A DAI1 EQ5 */ + { 0x7b, 0x00 }, /* 7B DAI1 EQ5 */ + { 0x7c, 0x00 }, /* 7C DAI1 EQ5 */ + { 0x7d, 0x00 }, /* 7D DAI1 EQ5 */ + { 0x7e, 0x00 }, /* 7E DAI1 EQ5 */ + { 0x7f, 0x00 }, /* 7F DAI1 EQ5 */ + + { 0x80, 0x00 }, /* 80 DAI1 EQ5 */ + { 0x81, 0x00 }, /* 81 DAI1 EQ5 */ + { 0x82, 0x00 }, /* 82 DAI1 EQ5 */ + { 0x83, 0x00 }, /* 83 DAI1 EQ5 */ + { 0x84, 0x00 }, /* 84 DAI2 EQ1 */ + { 0x85, 0x00 }, /* 85 DAI2 EQ1 */ + { 0x86, 0x00 }, /* 86 DAI2 EQ1 */ + { 0x87, 0x00 }, /* 87 DAI2 EQ1 */ + { 0x88, 0x00 }, /* 88 DAI2 EQ1 */ + { 0x89, 0x00 }, /* 89 DAI2 EQ1 */ + { 0x8a, 0x00 }, /* 8A DAI2 EQ1 */ + { 0x8b, 0x00 }, /* 8B DAI2 EQ1 */ + { 0x8c, 0x00 }, /* 8C DAI2 EQ1 */ + { 0x8d, 0x00 }, /* 8D DAI2 EQ1 */ + { 0x8e, 0x00 }, /* 8E DAI2 EQ2 */ + { 0x8f, 0x00 }, /* 8F DAI2 EQ2 */ + + { 0x90, 0x00 }, /* 90 DAI2 EQ2 */ + { 0x91, 0x00 }, /* 91 DAI2 EQ2 */ + { 0x92, 0x00 }, /* 92 DAI2 EQ2 */ + { 0x93, 0x00 }, /* 93 DAI2 EQ2 */ + { 0x94, 0x00 }, /* 94 DAI2 EQ2 */ + { 0x95, 0x00 }, /* 95 DAI2 EQ2 */ + { 0x96, 0x00 }, /* 96 DAI2 EQ2 */ + { 0x97, 0x00 }, /* 97 DAI2 EQ2 */ + { 0x98, 0x00 }, /* 98 DAI2 EQ3 */ + { 0x99, 0x00 }, /* 99 DAI2 EQ3 */ + { 0x9a, 0x00 }, /* 9A DAI2 EQ3 */ + { 0x9b, 0x00 }, /* 9B DAI2 EQ3 */ + { 0x9c, 0x00 }, /* 9C DAI2 EQ3 */ + { 0x9d, 0x00 }, /* 9D DAI2 EQ3 */ + { 0x9e, 0x00 }, /* 9E DAI2 EQ3 */ + { 0x9f, 0x00 }, /* 9F DAI2 EQ3 */ + + { 0xa0, 0x00 }, /* A0 DAI2 EQ3 */ + { 0xa1, 0x00 }, /* A1 DAI2 EQ3 */ + { 0xa2, 0x00 }, /* A2 DAI2 EQ4 */ + { 0xa3, 0x00 }, /* A3 DAI2 EQ4 */ + { 0xa4, 0x00 }, /* A4 DAI2 EQ4 */ + { 0xa5, 0x00 }, /* A5 DAI2 EQ4 */ + { 0xa6, 0x00 }, /* A6 DAI2 EQ4 */ + { 0xa7, 0x00 }, /* A7 DAI2 EQ4 */ + { 0xa8, 0x00 }, /* A8 DAI2 EQ4 */ + { 0xa9, 0x00 }, /* A9 DAI2 EQ4 */ + { 0xaa, 0x00 }, /* AA DAI2 EQ4 */ + { 0xab, 0x00 }, /* AB DAI2 EQ4 */ + { 0xac, 0x00 }, /* AC DAI2 EQ5 */ + { 0xad, 0x00 }, /* AD DAI2 EQ5 */ + { 0xae, 0x00 }, /* AE DAI2 EQ5 */ + { 0xaf, 0x00 }, /* AF DAI2 EQ5 */ + + { 0xb0, 0x00 }, /* B0 DAI2 EQ5 */ + { 0xb1, 0x00 }, /* B1 DAI2 EQ5 */ + { 0xb2, 0x00 }, /* B2 DAI2 EQ5 */ + { 0xb3, 0x00 }, /* B3 DAI2 EQ5 */ + { 0xb4, 0x00 }, /* B4 DAI2 EQ5 */ + { 0xb5, 0x00 }, /* B5 DAI2 EQ5 */ + { 0xb6, 0x00 }, /* B6 DAI1 biquad */ + { 0xb7, 0x00 }, /* B7 DAI1 biquad */ + { 0xb8 ,0x00 }, /* B8 DAI1 biquad */ + { 0xb9, 0x00 }, /* B9 DAI1 biquad */ + { 0xba, 0x00 }, /* BA DAI1 biquad */ + { 0xbb, 0x00 }, /* BB DAI1 biquad */ + { 0xbc, 0x00 }, /* BC DAI1 biquad */ + { 0xbd, 0x00 }, /* BD DAI1 biquad */ + { 0xbe, 0x00 }, /* BE DAI1 biquad */ + { 0xbf, 0x00 }, /* BF DAI1 biquad */ + + { 0xc0, 0x00 }, /* C0 DAI2 biquad */ + { 0xc1, 0x00 }, /* C1 DAI2 biquad */ + { 0xc2, 0x00 }, /* C2 DAI2 biquad */ + { 0xc3, 0x00 }, /* C3 DAI2 biquad */ + { 0xc4, 0x00 }, /* C4 DAI2 biquad */ + { 0xc5, 0x00 }, /* C5 DAI2 biquad */ + { 0xc6, 0x00 }, /* C6 DAI2 biquad */ + { 0xc7, 0x00 }, /* C7 DAI2 biquad */ + { 0xc8, 0x00 }, /* C8 DAI2 biquad */ + { 0xc9, 0x00 }, /* C9 DAI2 biquad */ +}; + +static bool max98088_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98088_REG_00_IRQ_STATUS ... 0xC9: + case M98088_REG_FF_REV_ID: + return true; + default: + return false; + } +} + +static bool max98088_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98088_REG_03_BATTERY_VOLTAGE ... 0xC9: + return true; + default: + return false; + } +} + +static bool max98088_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98088_REG_00_IRQ_STATUS ... M98088_REG_03_BATTERY_VOLTAGE: + case M98088_REG_FF_REV_ID: + return true; + default: + return false; + } +} + +static const struct regmap_config max98088_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = max98088_readable_register, + .writeable_reg = max98088_writeable_register, + .volatile_reg = max98088_volatile_register, + .max_register = 0xff, + + .reg_defaults = max98088_reg, + .num_reg_defaults = ARRAY_SIZE(max98088_reg), + .cache_type = REGCACHE_RBTREE, +}; + +/* + * Load equalizer DSP coefficient configurations registers + */ +static void m98088_eq_band(struct snd_soc_component *component, unsigned int dai, + unsigned int band, u16 *coefs) +{ + unsigned int eq_reg; + unsigned int i; + + if (WARN_ON(band > 4) || + WARN_ON(dai > 1)) + return; + + /* Load the base register address */ + eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE; + + /* Add the band address offset, note adjustment for word address */ + eq_reg += band * (M98088_COEFS_PER_BAND << 1); + + /* Step through the registers and coefs */ + for (i = 0; i < M98088_COEFS_PER_BAND; i++) { + snd_soc_component_write(component, eq_reg++, M98088_BYTE1(coefs[i])); + snd_soc_component_write(component, eq_reg++, M98088_BYTE0(coefs[i])); + } +} + +/* + * Excursion limiter modes + */ +static const char *max98088_exmode_texts[] = { + "Off", "100Hz", "400Hz", "600Hz", "800Hz", "1000Hz", "200-400Hz", + "400-600Hz", "400-800Hz", +}; + +static const unsigned int max98088_exmode_values[] = { + 0x00, 0x43, 0x10, 0x20, 0x30, 0x40, 0x11, 0x22, 0x32 +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(max98088_exmode_enum, + M98088_REG_41_SPKDHP, 0, 127, + max98088_exmode_texts, + max98088_exmode_values); + +static const char *max98088_ex_thresh[] = { /* volts PP */ + "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"}; +static SOC_ENUM_SINGLE_DECL(max98088_ex_thresh_enum, + M98088_REG_42_SPKDHP_THRESH, 0, + max98088_ex_thresh); + +static const char *max98088_fltr_mode[] = {"Voice", "Music" }; +static SOC_ENUM_SINGLE_DECL(max98088_filter_mode_enum, + M98088_REG_18_DAI1_FILTERS, 7, + max98088_fltr_mode); + +static const char *max98088_extmic_text[] = { "None", "MIC1", "MIC2" }; + +static SOC_ENUM_SINGLE_DECL(max98088_extmic_enum, + M98088_REG_48_CFG_MIC, 0, + max98088_extmic_text); + +static const struct snd_kcontrol_new max98088_extmic_mux = + SOC_DAPM_ENUM("External MIC Mux", max98088_extmic_enum); + +static const char *max98088_dai1_fltr[] = { + "Off", "fc=258/fs=16k", "fc=500/fs=16k", + "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"}; +static SOC_ENUM_SINGLE_DECL(max98088_dai1_dac_filter_enum, + M98088_REG_18_DAI1_FILTERS, 0, + max98088_dai1_fltr); +static SOC_ENUM_SINGLE_DECL(max98088_dai1_adc_filter_enum, + M98088_REG_18_DAI1_FILTERS, 4, + max98088_dai1_fltr); + +static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + unsigned int sel = ucontrol->value.integer.value[0]; + + max98088->mic1pre = sel; + snd_soc_component_update_bits(component, M98088_REG_35_LVL_MIC1, M98088_MICPRE_MASK, + (1+sel)<value.integer.value[0] = max98088->mic1pre; + return 0; +} + +static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + unsigned int sel = ucontrol->value.integer.value[0]; + + max98088->mic2pre = sel; + snd_soc_component_update_bits(component, M98088_REG_36_LVL_MIC2, M98088_MICPRE_MASK, + (1+sel)<value.integer.value[0] = max98088->mic2pre; + return 0; +} + +static const DECLARE_TLV_DB_RANGE(max98088_micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0), + 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98088_hp_tlv, + 0, 6, TLV_DB_SCALE_ITEM(-6700, 400, 0), + 7, 14, TLV_DB_SCALE_ITEM(-4000, 300, 0), + 15, 21, TLV_DB_SCALE_ITEM(-1700, 200, 0), + 22, 27, TLV_DB_SCALE_ITEM(-400, 100, 0), + 28, 31, TLV_DB_SCALE_ITEM(150, 50, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98088_spk_tlv, + 0, 6, TLV_DB_SCALE_ITEM(-6200, 400, 0), + 7, 14, TLV_DB_SCALE_ITEM(-3500, 300, 0), + 15, 21, TLV_DB_SCALE_ITEM(-1200, 200, 0), + 22, 27, TLV_DB_SCALE_ITEM(100, 100, 0), + 28, 31, TLV_DB_SCALE_ITEM(650, 50, 0) +); + +static const struct snd_kcontrol_new max98088_snd_controls[] = { + + SOC_DOUBLE_R_TLV("Headphone Volume", M98088_REG_39_LVL_HP_L, + M98088_REG_3A_LVL_HP_R, 0, 31, 0, max98088_hp_tlv), + SOC_DOUBLE_R_TLV("Speaker Volume", M98088_REG_3D_LVL_SPK_L, + M98088_REG_3E_LVL_SPK_R, 0, 31, 0, max98088_spk_tlv), + SOC_DOUBLE_R_TLV("Receiver Volume", M98088_REG_3B_LVL_REC_L, + M98088_REG_3C_LVL_REC_R, 0, 31, 0, max98088_spk_tlv), + + SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L, + M98088_REG_3A_LVL_HP_R, 7, 1, 1), + SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L, + M98088_REG_3E_LVL_SPK_R, 7, 1, 1), + SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L, + M98088_REG_3C_LVL_REC_R, 7, 1, 1), + + SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1), + SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1), + + SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", + M98088_REG_35_LVL_MIC1, 5, 2, 0, + max98088_mic1pre_get, max98088_mic1pre_set, + max98088_micboost_tlv), + SOC_SINGLE_EXT_TLV("MIC2 Boost Volume", + M98088_REG_36_LVL_MIC2, 5, 2, 0, + max98088_mic2pre_get, max98088_mic2pre_set, + max98088_micboost_tlv), + + SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1), + SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1), + + SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0), + SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0), + + SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0), + SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0), + + SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0), + SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0), + + SOC_ENUM("EX Limiter Mode", max98088_exmode_enum), + SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum), + + SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum), + SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum), + SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum), + SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS, + 0, 1, 0), + + SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0), + SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0), + SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0), + SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0), + + SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG, + 4, 15, 0), + SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0), + SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0), + SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0), + + SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0), + SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0), +}; + +/* Left speaker mixer switch */ +static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0), +}; + +/* Right speaker mixer switch */ +static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0), +}; + +/* Left headphone mixer switch */ +static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0), +}; + +/* Right headphone mixer switch */ +static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0), +}; + +/* Left earpiece/receiver mixer switch */ +static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0), +}; + +/* Right earpiece/receiver mixer switch */ +static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0), +}; + +/* Left ADC mixer switch */ +static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0), +}; + +/* Right ADC mixer switch */ +static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0), +}; + +static int max98088_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (w->reg == M98088_REG_35_LVL_MIC1) { + snd_soc_component_update_bits(component, w->reg, M98088_MICPRE_MASK, + (1+max98088->mic1pre)<reg, M98088_MICPRE_MASK, + (1+max98088->mic2pre)<reg, M98088_MICPRE_MASK, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * The line inputs are 2-channel stereo inputs with the left + * and right channels sharing a common PGA power control signal. + */ +static int max98088_line_pga(struct snd_soc_dapm_widget *w, + int event, int line, u8 channel) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + u8 *state; + + if (WARN_ON(!(channel == 1 || channel == 2))) + return -EINVAL; + + switch (line) { + case LINE_INA: + state = &max98088->ina_state; + break; + case LINE_INB: + state = &max98088->inb_state; + break; + default: + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + *state |= channel; + snd_soc_component_update_bits(component, w->reg, + (1 << w->shift), (1 << w->shift)); + break; + case SND_SOC_DAPM_POST_PMD: + *state &= ~channel; + if (*state == 0) { + snd_soc_component_update_bits(component, w->reg, + (1 << w->shift), 0); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int max98088_pga_ina1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return max98088_line_pga(w, event, LINE_INA, 1); +} + +static int max98088_pga_ina2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return max98088_line_pga(w, event, LINE_INA, 2); +} + +static int max98088_pga_inb1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return max98088_line_pga(w, event, LINE_INB, 1); +} + +static int max98088_pga_inb2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return max98088_line_pga(w, event, LINE_INB, 2); +} + +static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0), + SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0), + + SND_SOC_DAPM_DAC("DACL1", "HiFi Playback", + M98088_REG_4D_PWR_EN_OUT, 1, 0), + SND_SOC_DAPM_DAC("DACR1", "HiFi Playback", + M98088_REG_4D_PWR_EN_OUT, 0, 0), + SND_SOC_DAPM_DAC("DACL2", "Aux Playback", + M98088_REG_4D_PWR_EN_OUT, 1, 0), + SND_SOC_DAPM_DAC("DACR2", "Aux Playback", + M98088_REG_4D_PWR_EN_OUT, 0, 0), + + SND_SOC_DAPM_PGA("HP Left Out", M98088_REG_4D_PWR_EN_OUT, + 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP Right Out", M98088_REG_4D_PWR_EN_OUT, + 6, 0, NULL, 0), + + SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT, + 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT, + 4, 0, NULL, 0), + + SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT, + 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT, + 2, 0, NULL, 0), + + SND_SOC_DAPM_MUX("External MIC", SND_SOC_NOPM, 0, 0, + &max98088_extmic_mux), + + SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_hp_mixer_controls[0], + ARRAY_SIZE(max98088_left_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_hp_mixer_controls[0], + ARRAY_SIZE(max98088_right_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_speaker_mixer_controls[0], + ARRAY_SIZE(max98088_left_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_speaker_mixer_controls[0], + ARRAY_SIZE(max98088_right_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_rec_mixer_controls[0], + ARRAY_SIZE(max98088_left_rec_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_rec_mixer_controls[0], + ARRAY_SIZE(max98088_right_rec_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_ADC_mixer_controls[0], + ARRAY_SIZE(max98088_left_ADC_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_ADC_mixer_controls[0], + ARRAY_SIZE(max98088_right_ADC_mixer_controls)), + + SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1, + 5, 0, NULL, 0, max98088_mic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2, + 5, 0, NULL, 0, max98088_mic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN, + 7, 0, NULL, 0, max98088_pga_ina1_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN, + 7, 0, NULL, 0, max98088_pga_ina2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN, + 6, 0, NULL, 0, max98088_pga_inb1_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN, + 6, 0, NULL, 0, max98088_pga_inb2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 0), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("RECL"), + SND_SOC_DAPM_OUTPUT("RECR"), + + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("INA1"), + SND_SOC_DAPM_INPUT("INA2"), + SND_SOC_DAPM_INPUT("INB1"), + SND_SOC_DAPM_INPUT("INB2"), +}; + +static const struct snd_soc_dapm_route max98088_audio_map[] = { + /* Left headphone output mixer */ + {"Left HP Mixer", "Left DAC1 Switch", "DACL1"}, + {"Left HP Mixer", "Left DAC2 Switch", "DACL2"}, + {"Left HP Mixer", "Right DAC1 Switch", "DACR1"}, + {"Left HP Mixer", "Right DAC2 Switch", "DACR2"}, + {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left HP Mixer", "INA1 Switch", "INA1 Input"}, + {"Left HP Mixer", "INA2 Switch", "INA2 Input"}, + {"Left HP Mixer", "INB1 Switch", "INB1 Input"}, + {"Left HP Mixer", "INB2 Switch", "INB2 Input"}, + + /* Right headphone output mixer */ + {"Right HP Mixer", "Left DAC1 Switch", "DACL1"}, + {"Right HP Mixer", "Left DAC2 Switch", "DACL2" }, + {"Right HP Mixer", "Right DAC1 Switch", "DACR1"}, + {"Right HP Mixer", "Right DAC2 Switch", "DACR2"}, + {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right HP Mixer", "INA1 Switch", "INA1 Input"}, + {"Right HP Mixer", "INA2 Switch", "INA2 Input"}, + {"Right HP Mixer", "INB1 Switch", "INB1 Input"}, + {"Right HP Mixer", "INB2 Switch", "INB2 Input"}, + + /* Left speaker output mixer */ + {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"}, + {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"}, + {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"}, + {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"}, + {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left SPK Mixer", "INA1 Switch", "INA1 Input"}, + {"Left SPK Mixer", "INA2 Switch", "INA2 Input"}, + {"Left SPK Mixer", "INB1 Switch", "INB1 Input"}, + {"Left SPK Mixer", "INB2 Switch", "INB2 Input"}, + + /* Right speaker output mixer */ + {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"}, + {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"}, + {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"}, + {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"}, + {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right SPK Mixer", "INA1 Switch", "INA1 Input"}, + {"Right SPK Mixer", "INA2 Switch", "INA2 Input"}, + {"Right SPK Mixer", "INB1 Switch", "INB1 Input"}, + {"Right SPK Mixer", "INB2 Switch", "INB2 Input"}, + + /* Earpiece/Receiver output mixer */ + {"Left REC Mixer", "Left DAC1 Switch", "DACL1"}, + {"Left REC Mixer", "Left DAC2 Switch", "DACL2"}, + {"Left REC Mixer", "Right DAC1 Switch", "DACR1"}, + {"Left REC Mixer", "Right DAC2 Switch", "DACR2"}, + {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left REC Mixer", "INA1 Switch", "INA1 Input"}, + {"Left REC Mixer", "INA2 Switch", "INA2 Input"}, + {"Left REC Mixer", "INB1 Switch", "INB1 Input"}, + {"Left REC Mixer", "INB2 Switch", "INB2 Input"}, + + /* Earpiece/Receiver output mixer */ + {"Right REC Mixer", "Left DAC1 Switch", "DACL1"}, + {"Right REC Mixer", "Left DAC2 Switch", "DACL2"}, + {"Right REC Mixer", "Right DAC1 Switch", "DACR1"}, + {"Right REC Mixer", "Right DAC2 Switch", "DACR2"}, + {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right REC Mixer", "INA1 Switch", "INA1 Input"}, + {"Right REC Mixer", "INA2 Switch", "INA2 Input"}, + {"Right REC Mixer", "INB1 Switch", "INB1 Input"}, + {"Right REC Mixer", "INB2 Switch", "INB2 Input"}, + + {"HP Left Out", NULL, "Left HP Mixer"}, + {"HP Right Out", NULL, "Right HP Mixer"}, + {"SPK Left Out", NULL, "Left SPK Mixer"}, + {"SPK Right Out", NULL, "Right SPK Mixer"}, + {"REC Left Out", NULL, "Left REC Mixer"}, + {"REC Right Out", NULL, "Right REC Mixer"}, + + {"HPL", NULL, "HP Left Out"}, + {"HPR", NULL, "HP Right Out"}, + {"SPKL", NULL, "SPK Left Out"}, + {"SPKR", NULL, "SPK Right Out"}, + {"RECL", NULL, "REC Left Out"}, + {"RECR", NULL, "REC Right Out"}, + + /* Left ADC input mixer */ + {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left ADC Mixer", "INA1 Switch", "INA1 Input"}, + {"Left ADC Mixer", "INA2 Switch", "INA2 Input"}, + {"Left ADC Mixer", "INB1 Switch", "INB1 Input"}, + {"Left ADC Mixer", "INB2 Switch", "INB2 Input"}, + + /* Right ADC input mixer */ + {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right ADC Mixer", "INA1 Switch", "INA1 Input"}, + {"Right ADC Mixer", "INA2 Switch", "INA2 Input"}, + {"Right ADC Mixer", "INB1 Switch", "INB1 Input"}, + {"Right ADC Mixer", "INB2 Switch", "INB2 Input"}, + + /* Inputs */ + {"ADCL", NULL, "Left ADC Mixer"}, + {"ADCR", NULL, "Right ADC Mixer"}, + {"INA1 Input", NULL, "INA1"}, + {"INA2 Input", NULL, "INA2"}, + {"INB1 Input", NULL, "INB1"}, + {"INB2 Input", NULL, "INB2"}, + {"MIC1 Input", NULL, "MIC1"}, + {"MIC2 Input", NULL, "MIC2"}, +}; + +/* codec mclk clock divider coefficients */ +static const struct { + u32 rate; + u8 sr; +} rate_table[] = { + {8000, 0x10}, + {11025, 0x20}, + {16000, 0x30}, + {22050, 0x40}, + {24000, 0x50}, + {32000, 0x60}, + {44100, 0x70}, + {48000, 0x80}, + {88200, 0x90}, + {96000, 0xA0}, +}; + +static inline int rate_value(int rate, u8 *value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i].rate >= rate) { + *value = rate_table[i].sr; + return 0; + } + } + *value = rate_table[0].sr; + return -EINVAL; +} + +static int max98088_dai1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_cdata *cdata; + unsigned long long ni; + unsigned int rate; + u8 regval; + + cdata = &max98088->dai[0]; + + rate = params_rate(params); + + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(component, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_WS, 0); + break; + case 24: + snd_soc_component_update_bits(component, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_WS, M98088_DAI_WS); + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0); + + if (rate_value(rate, ®val)) + return -EINVAL; + + snd_soc_component_update_bits(component, M98088_REG_11_DAI1_CLKMODE, + M98088_CLKMODE_MASK, regval); + cdata->rate = rate; + + /* Configure NI when operating as master */ + if (snd_soc_component_read32(component, M98088_REG_14_DAI1_FORMAT) + & M98088_DAI_MAS) { + if (max98088->sysclk == 0) { + dev_err(component->dev, "Invalid system clock frequency\n"); + return -EINVAL; + } + ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate; + do_div(ni, (unsigned long long int)max98088->sysclk); + snd_soc_component_write(component, M98088_REG_12_DAI1_CLKCFG_HI, + (ni >> 8) & 0x7F); + snd_soc_component_write(component, M98088_REG_13_DAI1_CLKCFG_LO, + ni & 0xFF); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_component_update_bits(component, M98088_REG_18_DAI1_FILTERS, + M98088_DAI_DHF, 0); + else + snd_soc_component_update_bits(component, M98088_REG_18_DAI1_FILTERS, + M98088_DAI_DHF, M98088_DAI_DHF); + + snd_soc_component_update_bits(component, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, + M98088_SHDNRUN); + + return 0; +} + +static int max98088_dai2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_cdata *cdata; + unsigned long long ni; + unsigned int rate; + u8 regval; + + cdata = &max98088->dai[1]; + + rate = params_rate(params); + + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(component, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_WS, 0); + break; + case 24: + snd_soc_component_update_bits(component, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_WS, M98088_DAI_WS); + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0); + + if (rate_value(rate, ®val)) + return -EINVAL; + + snd_soc_component_update_bits(component, M98088_REG_19_DAI2_CLKMODE, + M98088_CLKMODE_MASK, regval); + cdata->rate = rate; + + /* Configure NI when operating as master */ + if (snd_soc_component_read32(component, M98088_REG_1C_DAI2_FORMAT) + & M98088_DAI_MAS) { + if (max98088->sysclk == 0) { + dev_err(component->dev, "Invalid system clock frequency\n"); + return -EINVAL; + } + ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate; + do_div(ni, (unsigned long long int)max98088->sysclk); + snd_soc_component_write(component, M98088_REG_1A_DAI2_CLKCFG_HI, + (ni >> 8) & 0x7F); + snd_soc_component_write(component, M98088_REG_1B_DAI2_CLKCFG_LO, + ni & 0xFF); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_component_update_bits(component, M98088_REG_20_DAI2_FILTERS, + M98088_DAI_DHF, 0); + else + snd_soc_component_update_bits(component, M98088_REG_20_DAI2_FILTERS, + M98088_DAI_DHF, M98088_DAI_DHF); + + snd_soc_component_update_bits(component, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, + M98088_SHDNRUN); + + return 0; +} + +static int max98088_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + + /* Requested clock frequency is already setup */ + if (freq == max98088->sysclk) + return 0; + + /* Setup clocks for slave mode, and using the PLL + * PSCLK = 0x01 (when master clk is 10MHz to 20MHz) + * 0x02 (when master clk is 20MHz to 30MHz).. + */ + if ((freq >= 10000000) && (freq < 20000000)) { + snd_soc_component_write(component, M98088_REG_10_SYS_CLK, 0x10); + } else if ((freq >= 20000000) && (freq < 30000000)) { + snd_soc_component_write(component, M98088_REG_10_SYS_CLK, 0x20); + } else { + dev_err(component->dev, "Invalid master clock frequency\n"); + return -EINVAL; + } + + if (snd_soc_component_read32(component, M98088_REG_51_PWR_SYS) & M98088_SHDNRUN) { + snd_soc_component_update_bits(component, M98088_REG_51_PWR_SYS, + M98088_SHDNRUN, 0); + snd_soc_component_update_bits(component, M98088_REG_51_PWR_SYS, + M98088_SHDNRUN, M98088_SHDNRUN); + } + + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + + max98088->sysclk = freq; + return 0; +} + +static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_cdata *cdata; + u8 reg15val; + u8 reg14val = 0; + + cdata = &max98088->dai[0]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave mode PLL */ + snd_soc_component_write(component, M98088_REG_12_DAI1_CLKCFG_HI, + 0x80); + snd_soc_component_write(component, M98088_REG_13_DAI1_CLKCFG_LO, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set to master mode */ + reg14val |= M98088_DAI_MAS; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + reg14val |= M98088_DAI_DLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + reg14val |= M98088_DAI_WCI; + break; + case SND_SOC_DAIFMT_IB_NF: + reg14val |= M98088_DAI_BCI; + break; + case SND_SOC_DAIFMT_IB_IF: + reg14val |= M98088_DAI_BCI|M98088_DAI_WCI; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI | + M98088_DAI_WCI, reg14val); + + reg15val = M98088_DAI_BSEL64; + if (max98088->digmic) + reg15val |= M98088_DAI_OSR64; + snd_soc_component_write(component, M98088_REG_15_DAI1_CLOCK, reg15val); + } + + return 0; +} + +static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_cdata *cdata; + u8 reg1Cval = 0; + + cdata = &max98088->dai[1]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave mode PLL */ + snd_soc_component_write(component, M98088_REG_1A_DAI2_CLKCFG_HI, + 0x80); + snd_soc_component_write(component, M98088_REG_1B_DAI2_CLKCFG_LO, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set to master mode */ + reg1Cval |= M98088_DAI_MAS; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + reg1Cval |= M98088_DAI_DLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + reg1Cval |= M98088_DAI_WCI; + break; + case SND_SOC_DAIFMT_IB_NF: + reg1Cval |= M98088_DAI_BCI; + break; + case SND_SOC_DAIFMT_IB_IF: + reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI | + M98088_DAI_WCI, reg1Cval); + + snd_soc_component_write(component, M98088_REG_1D_DAI2_CLOCK, + M98088_DAI_BSEL64); + } + + return 0; +} + +static int max98088_dai1_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + int reg; + + if (mute) + reg = M98088_DAI_MUTE; + else + reg = 0; + + snd_soc_component_update_bits(component, M98088_REG_2F_LVL_DAI1_PLAY, + M98088_DAI_MUTE_MASK, reg); + return 0; +} + +static int max98088_dai2_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + int reg; + + if (mute) + reg = M98088_DAI_MUTE; + else + reg = 0; + + snd_soc_component_update_bits(component, M98088_REG_31_LVL_DAI2_PLAY, + M98088_DAI_MUTE_MASK, reg); + return 0; +} + +static int max98088_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + regcache_sync(max98088->regmap); + + snd_soc_component_update_bits(component, M98088_REG_4C_PWR_EN_IN, + M98088_MBEN, M98088_MBEN); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, M98088_REG_4C_PWR_EN_IN, + M98088_MBEN, 0); + regcache_mark_dirty(max98088->regmap); + break; + } + return 0; +} + +#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000 +#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops max98088_dai1_ops = { + .set_sysclk = max98088_dai_set_sysclk, + .set_fmt = max98088_dai1_set_fmt, + .hw_params = max98088_dai1_hw_params, + .digital_mute = max98088_dai1_digital_mute, +}; + +static const struct snd_soc_dai_ops max98088_dai2_ops = { + .set_sysclk = max98088_dai_set_sysclk, + .set_fmt = max98088_dai2_set_fmt, + .hw_params = max98088_dai2_hw_params, + .digital_mute = max98088_dai2_digital_mute, +}; + +static struct snd_soc_dai_driver max98088_dai[] = { +{ + .name = "HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98088_RATES, + .formats = MAX98088_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98088_RATES, + .formats = MAX98088_FORMATS, + }, + .ops = &max98088_dai1_ops, +}, +{ + .name = "Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98088_RATES, + .formats = MAX98088_FORMATS, + }, + .ops = &max98088_dai2_ops, +} +}; + +static const char *eq_mode_name[] = {"EQ1 Mode", "EQ2 Mode"}; + +static int max98088_get_channel(struct snd_soc_component *component, const char *name) +{ + int ret; + + ret = match_string(eq_mode_name, ARRAY_SIZE(eq_mode_name), name); + if (ret < 0) + dev_err(component->dev, "Bad EQ channel name '%s'\n", name); + return ret; +} + +static void max98088_setup_eq1(struct snd_soc_component *component) +{ + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_eq_cfg *coef_set; + int best, best_val, save, i, sel, fs; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[0]; + + if (!pdata || !max98088->eq_textcnt) + return; + + /* Find the selected configuration with nearest sample rate */ + fs = cdata->rate; + sel = cdata->eq_sel; + + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->eq_cfgcnt; i++) { + if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 && + abs(pdata->eq_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->eq_cfg[i].rate - fs); + } + } + + dev_dbg(component->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->eq_cfg[best].name, + pdata->eq_cfg[best].rate, fs); + + /* Disable EQ while configuring, and save current on/off state */ + save = snd_soc_component_read32(component, M98088_REG_49_CFG_LEVEL); + snd_soc_component_update_bits(component, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0); + + coef_set = &pdata->eq_cfg[sel]; + + m98088_eq_band(component, 0, 0, coef_set->band1); + m98088_eq_band(component, 0, 1, coef_set->band2); + m98088_eq_band(component, 0, 2, coef_set->band3); + m98088_eq_band(component, 0, 3, coef_set->band4); + m98088_eq_band(component, 0, 4, coef_set->band5); + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save); +} + +static void max98088_setup_eq2(struct snd_soc_component *component) +{ + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_eq_cfg *coef_set; + int best, best_val, save, i, sel, fs; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[1]; + + if (!pdata || !max98088->eq_textcnt) + return; + + /* Find the selected configuration with nearest sample rate */ + fs = cdata->rate; + + sel = cdata->eq_sel; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->eq_cfgcnt; i++) { + if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 && + abs(pdata->eq_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->eq_cfg[i].rate - fs); + } + } + + dev_dbg(component->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->eq_cfg[best].name, + pdata->eq_cfg[best].rate, fs); + + /* Disable EQ while configuring, and save current on/off state */ + save = snd_soc_component_read32(component, M98088_REG_49_CFG_LEVEL); + snd_soc_component_update_bits(component, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0); + + coef_set = &pdata->eq_cfg[sel]; + + m98088_eq_band(component, 1, 0, coef_set->band1); + m98088_eq_band(component, 1, 1, coef_set->band2); + m98088_eq_band(component, 1, 2, coef_set->band3); + m98088_eq_band(component, 1, 3, coef_set->band4); + m98088_eq_band(component, 1, 4, coef_set->band5); + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, + save); +} + +static int max98088_put_eq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_pdata *pdata = max98088->pdata; + int channel = max98088_get_channel(component, kcontrol->id.name); + struct max98088_cdata *cdata; + int sel = ucontrol->value.enumerated.item[0]; + + if (channel < 0) + return channel; + + cdata = &max98088->dai[channel]; + + if (sel >= pdata->eq_cfgcnt) + return -EINVAL; + + cdata->eq_sel = sel; + + switch (channel) { + case 0: + max98088_setup_eq1(component); + break; + case 1: + max98088_setup_eq2(component); + break; + } + + return 0; +} + +static int max98088_get_eq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + int channel = max98088_get_channel(component, kcontrol->id.name); + struct max98088_cdata *cdata; + + if (channel < 0) + return channel; + + cdata = &max98088->dai[channel]; + ucontrol->value.enumerated.item[0] = cdata->eq_sel; + return 0; +} + +static void max98088_handle_eq_pdata(struct snd_soc_component *component) +{ + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_eq_cfg *cfg; + unsigned int cfgcnt; + int i, j; + const char **t; + int ret; + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT((char *)eq_mode_name[0], + max98088->eq_enum, + max98088_get_eq_enum, + max98088_put_eq_enum), + SOC_ENUM_EXT((char *)eq_mode_name[1], + max98088->eq_enum, + max98088_get_eq_enum, + max98088_put_eq_enum), + }; + BUILD_BUG_ON(ARRAY_SIZE(controls) != ARRAY_SIZE(eq_mode_name)); + + cfg = pdata->eq_cfg; + cfgcnt = pdata->eq_cfgcnt; + + /* Setup an array of texts for the equalizer enum. + * This is based on Mark Brown's equalizer driver code. + */ + max98088->eq_textcnt = 0; + max98088->eq_texts = NULL; + for (i = 0; i < cfgcnt; i++) { + for (j = 0; j < max98088->eq_textcnt; j++) { + if (strcmp(cfg[i].name, max98088->eq_texts[j]) == 0) + break; + } + + if (j != max98088->eq_textcnt) + continue; + + /* Expand the array */ + t = krealloc(max98088->eq_texts, + sizeof(char *) * (max98088->eq_textcnt + 1), + GFP_KERNEL); + if (t == NULL) + continue; + + /* Store the new entry */ + t[max98088->eq_textcnt] = cfg[i].name; + max98088->eq_textcnt++; + max98088->eq_texts = t; + } + + /* Now point the soc_enum to .texts array items */ + max98088->eq_enum.texts = max98088->eq_texts; + max98088->eq_enum.items = max98088->eq_textcnt; + + ret = snd_soc_add_component_controls(component, controls, ARRAY_SIZE(controls)); + if (ret != 0) + dev_err(component->dev, "Failed to add EQ control: %d\n", ret); +} + +static void max98088_handle_pdata(struct snd_soc_component *component) +{ + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_pdata *pdata = max98088->pdata; + u8 regval = 0; + + if (!pdata) { + dev_dbg(component->dev, "No platform data\n"); + return; + } + + /* Configure mic for analog/digital mic mode */ + if (pdata->digmic_left_mode) + regval |= M98088_DIGMIC_L; + + if (pdata->digmic_right_mode) + regval |= M98088_DIGMIC_R; + + max98088->digmic = (regval ? 1 : 0); + + snd_soc_component_write(component, M98088_REG_48_CFG_MIC, regval); + + /* Configure receiver output */ + regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0); + snd_soc_component_update_bits(component, M98088_REG_2A_MIC_REC_CNTL, + M98088_REC_LINEMODE_MASK, regval); + + /* Configure equalizers */ + if (pdata->eq_cfgcnt) + max98088_handle_eq_pdata(component); +} + +static int max98088_probe(struct snd_soc_component *component) +{ + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + struct max98088_cdata *cdata; + int ret = 0; + + regcache_mark_dirty(max98088->regmap); + + /* initialize private data */ + + max98088->sysclk = (unsigned)-1; + max98088->eq_textcnt = 0; + + cdata = &max98088->dai[0]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_sel = 0; + + cdata = &max98088->dai[1]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_sel = 0; + + max98088->ina_state = 0; + max98088->inb_state = 0; + max98088->ex_mode = 0; + max98088->digmic = 0; + max98088->mic1pre = 0; + max98088->mic2pre = 0; + + ret = snd_soc_component_read32(component, M98088_REG_FF_REV_ID); + if (ret < 0) { + dev_err(component->dev, "Failed to read device revision: %d\n", + ret); + goto err_access; + } + dev_info(component->dev, "revision %c\n", ret - 0x40 + 'A'); + + snd_soc_component_write(component, M98088_REG_51_PWR_SYS, M98088_PWRSV); + + snd_soc_component_write(component, M98088_REG_0F_IRQ_ENABLE, 0x00); + + snd_soc_component_write(component, M98088_REG_22_MIX_DAC, + M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL| + M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR); + + snd_soc_component_write(component, M98088_REG_4E_BIAS_CNTL, 0xF0); + snd_soc_component_write(component, M98088_REG_50_DAC_BIAS2, 0x0F); + + snd_soc_component_write(component, M98088_REG_16_DAI1_IOCFG, + M98088_S1NORMAL|M98088_SDATA); + + snd_soc_component_write(component, M98088_REG_1E_DAI2_IOCFG, + M98088_S2NORMAL|M98088_SDATA); + + max98088_handle_pdata(component); + +err_access: + return ret; +} + +static void max98088_remove(struct snd_soc_component *component) +{ + struct max98088_priv *max98088 = snd_soc_component_get_drvdata(component); + + kfree(max98088->eq_texts); +} + +static const struct snd_soc_component_driver soc_component_dev_max98088 = { + .probe = max98088_probe, + .remove = max98088_remove, + .set_bias_level = max98088_set_bias_level, + .controls = max98088_snd_controls, + .num_controls = ARRAY_SIZE(max98088_snd_controls), + .dapm_widgets = max98088_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98088_dapm_widgets), + .dapm_routes = max98088_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98088_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int max98088_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max98088_priv *max98088; + int ret; + + max98088 = devm_kzalloc(&i2c->dev, sizeof(struct max98088_priv), + GFP_KERNEL); + if (max98088 == NULL) + return -ENOMEM; + + max98088->regmap = devm_regmap_init_i2c(i2c, &max98088_regmap); + if (IS_ERR(max98088->regmap)) + return PTR_ERR(max98088->regmap); + + max98088->devtype = id->driver_data; + + i2c_set_clientdata(i2c, max98088); + max98088->pdata = i2c->dev.platform_data; + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_max98088, &max98088_dai[0], 2); + return ret; +} + +static const struct i2c_device_id max98088_i2c_id[] = { + { "max98088", MAX98088 }, + { "max98089", MAX98089 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98088_i2c_id); + +static struct i2c_driver max98088_i2c_driver = { + .driver = { + .name = "max98088", + }, + .probe = max98088_i2c_probe, + .id_table = max98088_i2c_id, +}; + +module_i2c_driver(max98088_i2c_driver); + +MODULE_DESCRIPTION("ALSA SoC MAX98088 driver"); +MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h new file mode 100644 index 000000000..efa39bf46 --- /dev/null +++ b/sound/soc/codecs/max98088.h @@ -0,0 +1,206 @@ +/* + * max98088.h -- MAX98088 ALSA SoC Audio driver + * + * Copyright 2010 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MAX98088_H +#define _MAX98088_H + +/* + * MAX98088 Registers Definition + */ +#define M98088_REG_00_IRQ_STATUS 0x00 +#define M98088_REG_01_MIC_STATUS 0x01 +#define M98088_REG_02_JACK_STATUS 0x02 +#define M98088_REG_03_BATTERY_VOLTAGE 0x03 +#define M98088_REG_0F_IRQ_ENABLE 0x0F +#define M98088_REG_10_SYS_CLK 0x10 +#define M98088_REG_11_DAI1_CLKMODE 0x11 +#define M98088_REG_12_DAI1_CLKCFG_HI 0x12 +#define M98088_REG_13_DAI1_CLKCFG_LO 0x13 +#define M98088_REG_14_DAI1_FORMAT 0x14 +#define M98088_REG_15_DAI1_CLOCK 0x15 +#define M98088_REG_16_DAI1_IOCFG 0x16 +#define M98088_REG_17_DAI1_TDM 0x17 +#define M98088_REG_18_DAI1_FILTERS 0x18 +#define M98088_REG_19_DAI2_CLKMODE 0x19 +#define M98088_REG_1A_DAI2_CLKCFG_HI 0x1A +#define M98088_REG_1B_DAI2_CLKCFG_LO 0x1B +#define M98088_REG_1C_DAI2_FORMAT 0x1C +#define M98088_REG_1D_DAI2_CLOCK 0x1D +#define M98088_REG_1E_DAI2_IOCFG 0x1E +#define M98088_REG_1F_DAI2_TDM 0x1F +#define M98088_REG_20_DAI2_FILTERS 0x20 +#define M98088_REG_21_SRC 0x21 +#define M98088_REG_22_MIX_DAC 0x22 +#define M98088_REG_23_MIX_ADC_LEFT 0x23 +#define M98088_REG_24_MIX_ADC_RIGHT 0x24 +#define M98088_REG_25_MIX_HP_LEFT 0x25 +#define M98088_REG_26_MIX_HP_RIGHT 0x26 +#define M98088_REG_27_MIX_HP_CNTL 0x27 +#define M98088_REG_28_MIX_REC_LEFT 0x28 +#define M98088_REG_29_MIX_REC_RIGHT 0x29 +#define M98088_REG_2A_MIC_REC_CNTL 0x2A +#define M98088_REG_2B_MIX_SPK_LEFT 0x2B +#define M98088_REG_2C_MIX_SPK_RIGHT 0x2C +#define M98088_REG_2D_MIX_SPK_CNTL 0x2D +#define M98088_REG_2E_LVL_SIDETONE 0x2E +#define M98088_REG_2F_LVL_DAI1_PLAY 0x2F +#define M98088_REG_30_LVL_DAI1_PLAY_EQ 0x30 +#define M98088_REG_31_LVL_DAI2_PLAY 0x31 +#define M98088_REG_32_LVL_DAI2_PLAY_EQ 0x32 +#define M98088_REG_33_LVL_ADC_L 0x33 +#define M98088_REG_34_LVL_ADC_R 0x34 +#define M98088_REG_35_LVL_MIC1 0x35 +#define M98088_REG_36_LVL_MIC2 0x36 +#define M98088_REG_37_LVL_INA 0x37 +#define M98088_REG_38_LVL_INB 0x38 +#define M98088_REG_39_LVL_HP_L 0x39 +#define M98088_REG_3A_LVL_HP_R 0x3A +#define M98088_REG_3B_LVL_REC_L 0x3B +#define M98088_REG_3C_LVL_REC_R 0x3C +#define M98088_REG_3D_LVL_SPK_L 0x3D +#define M98088_REG_3E_LVL_SPK_R 0x3E +#define M98088_REG_3F_MICAGC_CFG 0x3F +#define M98088_REG_40_MICAGC_THRESH 0x40 +#define M98088_REG_41_SPKDHP 0x41 +#define M98088_REG_42_SPKDHP_THRESH 0x42 +#define M98088_REG_43_SPKALC_COMP 0x43 +#define M98088_REG_44_PWRLMT_CFG 0x44 +#define M98088_REG_45_PWRLMT_TIME 0x45 +#define M98088_REG_46_THDLMT_CFG 0x46 +#define M98088_REG_47_CFG_AUDIO_IN 0x47 +#define M98088_REG_48_CFG_MIC 0x48 +#define M98088_REG_49_CFG_LEVEL 0x49 +#define M98088_REG_4A_CFG_BYPASS 0x4A +#define M98088_REG_4B_CFG_JACKDET 0x4B +#define M98088_REG_4C_PWR_EN_IN 0x4C +#define M98088_REG_4D_PWR_EN_OUT 0x4D +#define M98088_REG_4E_BIAS_CNTL 0x4E +#define M98088_REG_4F_DAC_BIAS1 0x4F +#define M98088_REG_50_DAC_BIAS2 0x50 +#define M98088_REG_51_PWR_SYS 0x51 +#define M98088_REG_52_DAI1_EQ_BASE 0x52 +#define M98088_REG_84_DAI2_EQ_BASE 0x84 +#define M98088_REG_B6_DAI1_BIQUAD_BASE 0xB6 +#define M98088_REG_C0_DAI2_BIQUAD_BASE 0xC0 +#define M98088_REG_FF_REV_ID 0xFF + +#define M98088_REG_CNT (0xFF+1) + +/* MAX98088 Registers Bit Fields */ + +/* M98088_REG_11_DAI1_CLKMODE, M98088_REG_19_DAI2_CLKMODE */ + #define M98088_CLKMODE_MASK 0xFF + +/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */ + #define M98088_DAI_MAS (1<<7) + #define M98088_DAI_WCI (1<<6) + #define M98088_DAI_BCI (1<<5) + #define M98088_DAI_DLY (1<<4) + #define M98088_DAI_TDM (1<<2) + #define M98088_DAI_FSW (1<<1) + #define M98088_DAI_WS (1<<0) + +/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */ + #define M98088_DAI_BSEL64 (1<<0) + #define M98088_DAI_OSR64 (1<<6) + +/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */ + #define M98088_S1NORMAL (1<<6) + #define M98088_S2NORMAL (2<<6) + #define M98088_SDATA (3<<0) + +/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */ + #define M98088_DAI_DHF (1<<3) + +/* M98088_REG_22_MIX_DAC */ + #define M98088_DAI1L_TO_DACL (1<<7) + #define M98088_DAI1R_TO_DACL (1<<6) + #define M98088_DAI2L_TO_DACL (1<<5) + #define M98088_DAI2R_TO_DACL (1<<4) + #define M98088_DAI1L_TO_DACR (1<<3) + #define M98088_DAI1R_TO_DACR (1<<2) + #define M98088_DAI2L_TO_DACR (1<<1) + #define M98088_DAI2R_TO_DACR (1<<0) + +/* M98088_REG_2A_MIC_REC_CNTL */ + #define M98088_REC_LINEMODE (1<<7) + #define M98088_REC_LINEMODE_MASK (1<<7) + +/* M98088_REG_2D_MIX_SPK_CNTL */ + #define M98088_MIX_SPKR_GAIN_MASK (3<<2) + #define M98088_MIX_SPKR_GAIN_SHIFT 2 + #define M98088_MIX_SPKL_GAIN_MASK (3<<0) + #define M98088_MIX_SPKL_GAIN_SHIFT 0 + +/* M98088_REG_2F_LVL_DAI1_PLAY, M98088_REG_31_LVL_DAI2_PLAY */ + #define M98088_DAI_MUTE (1<<7) + #define M98088_DAI_MUTE_MASK (1<<7) + #define M98088_DAI_VOICE_GAIN_MASK (3<<4) + #define M98088_DAI_ATTENUATION_MASK (0xF<<0) + #define M98088_DAI_ATTENUATION_SHIFT 0 + +/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */ + #define M98088_MICPRE_MASK (3<<5) + #define M98088_MICPRE_SHIFT 5 + +/* M98088_REG_3A_LVL_HP_R */ + #define M98088_HP_MUTE (1<<7) + +/* M98088_REG_3C_LVL_REC_R */ + #define M98088_REC_MUTE (1<<7) + +/* M98088_REG_3E_LVL_SPK_R */ + #define M98088_SP_MUTE (1<<7) + +/* M98088_REG_48_CFG_MIC */ + #define M98088_EXTMIC_MASK (3<<0) + #define M98088_DIGMIC_L (1<<5) + #define M98088_DIGMIC_R (1<<4) + +/* M98088_REG_49_CFG_LEVEL */ + #define M98088_VSEN (1<<6) + #define M98088_ZDEN (1<<5) + #define M98088_EQ2EN (1<<1) + #define M98088_EQ1EN (1<<0) + +/* M98088_REG_4C_PWR_EN_IN */ + #define M98088_INAEN (1<<7) + #define M98088_INBEN (1<<6) + #define M98088_MBEN (1<<3) + #define M98088_ADLEN (1<<1) + #define M98088_ADREN (1<<0) + +/* M98088_REG_4D_PWR_EN_OUT */ + #define M98088_HPLEN (1<<7) + #define M98088_HPREN (1<<6) + #define M98088_HPEN ((1<<7)|(1<<6)) + #define M98088_SPLEN (1<<5) + #define M98088_SPREN (1<<4) + #define M98088_RECEN (1<<3) + #define M98088_DALEN (1<<1) + #define M98088_DAREN (1<<0) + +/* M98088_REG_51_PWR_SYS */ + #define M98088_SHDNRUN (1<<7) + #define M98088_PERFMODE (1<<3) + #define M98088_HPPLYBACK (1<<2) + #define M98088_PWRSV8K (1<<1) + #define M98088_PWRSV (1<<0) + +/* Line inputs */ +#define LINE_INA 0 +#define LINE_INB 1 + +#define M98088_COEFS_PER_BAND 5 + +#define M98088_BYTE1(w) ((w >> 8) & 0xff) +#define M98088_BYTE0(w) (w & 0xff) + +#endif diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c new file mode 100644 index 000000000..b9f15a260 --- /dev/null +++ b/sound/soc/codecs/max98090.c @@ -0,0 +1,2717 @@ +/* + * max98090.c -- MAX98090 ALSA SoC Audio driver + * + * Copyright 2011-2012 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98090.h" + +/* Allows for sparsely populated register maps */ +static const struct reg_default max98090_reg[] = { + { 0x00, 0x00 }, /* 00 Software Reset */ + { 0x03, 0x04 }, /* 03 Interrupt Masks */ + { 0x04, 0x00 }, /* 04 System Clock Quick */ + { 0x05, 0x00 }, /* 05 Sample Rate Quick */ + { 0x06, 0x00 }, /* 06 DAI Interface Quick */ + { 0x07, 0x00 }, /* 07 DAC Path Quick */ + { 0x08, 0x00 }, /* 08 Mic/Direct to ADC Quick */ + { 0x09, 0x00 }, /* 09 Line to ADC Quick */ + { 0x0A, 0x00 }, /* 0A Analog Mic Loop Quick */ + { 0x0B, 0x00 }, /* 0B Analog Line Loop Quick */ + { 0x0C, 0x00 }, /* 0C Reserved */ + { 0x0D, 0x00 }, /* 0D Input Config */ + { 0x0E, 0x1B }, /* 0E Line Input Level */ + { 0x0F, 0x00 }, /* 0F Line Config */ + + { 0x10, 0x14 }, /* 10 Mic1 Input Level */ + { 0x11, 0x14 }, /* 11 Mic2 Input Level */ + { 0x12, 0x00 }, /* 12 Mic Bias Voltage */ + { 0x13, 0x00 }, /* 13 Digital Mic Config */ + { 0x14, 0x00 }, /* 14 Digital Mic Mode */ + { 0x15, 0x00 }, /* 15 Left ADC Mixer */ + { 0x16, 0x00 }, /* 16 Right ADC Mixer */ + { 0x17, 0x03 }, /* 17 Left ADC Level */ + { 0x18, 0x03 }, /* 18 Right ADC Level */ + { 0x19, 0x00 }, /* 19 ADC Biquad Level */ + { 0x1A, 0x00 }, /* 1A ADC Sidetone */ + { 0x1B, 0x00 }, /* 1B System Clock */ + { 0x1C, 0x00 }, /* 1C Clock Mode */ + { 0x1D, 0x00 }, /* 1D Any Clock 1 */ + { 0x1E, 0x00 }, /* 1E Any Clock 2 */ + { 0x1F, 0x00 }, /* 1F Any Clock 3 */ + + { 0x20, 0x00 }, /* 20 Any Clock 4 */ + { 0x21, 0x00 }, /* 21 Master Mode */ + { 0x22, 0x00 }, /* 22 Interface Format */ + { 0x23, 0x00 }, /* 23 TDM Format 1*/ + { 0x24, 0x00 }, /* 24 TDM Format 2*/ + { 0x25, 0x00 }, /* 25 I/O Configuration */ + { 0x26, 0x80 }, /* 26 Filter Config */ + { 0x27, 0x00 }, /* 27 DAI Playback Level */ + { 0x28, 0x00 }, /* 28 EQ Playback Level */ + { 0x29, 0x00 }, /* 29 Left HP Mixer */ + { 0x2A, 0x00 }, /* 2A Right HP Mixer */ + { 0x2B, 0x00 }, /* 2B HP Control */ + { 0x2C, 0x1A }, /* 2C Left HP Volume */ + { 0x2D, 0x1A }, /* 2D Right HP Volume */ + { 0x2E, 0x00 }, /* 2E Left Spk Mixer */ + { 0x2F, 0x00 }, /* 2F Right Spk Mixer */ + + { 0x30, 0x00 }, /* 30 Spk Control */ + { 0x31, 0x2C }, /* 31 Left Spk Volume */ + { 0x32, 0x2C }, /* 32 Right Spk Volume */ + { 0x33, 0x00 }, /* 33 ALC Timing */ + { 0x34, 0x00 }, /* 34 ALC Compressor */ + { 0x35, 0x00 }, /* 35 ALC Expander */ + { 0x36, 0x00 }, /* 36 ALC Gain */ + { 0x37, 0x00 }, /* 37 Rcv/Line OutL Mixer */ + { 0x38, 0x00 }, /* 38 Rcv/Line OutL Control */ + { 0x39, 0x15 }, /* 39 Rcv/Line OutL Volume */ + { 0x3A, 0x00 }, /* 3A Line OutR Mixer */ + { 0x3B, 0x00 }, /* 3B Line OutR Control */ + { 0x3C, 0x15 }, /* 3C Line OutR Volume */ + { 0x3D, 0x00 }, /* 3D Jack Detect */ + { 0x3E, 0x00 }, /* 3E Input Enable */ + { 0x3F, 0x00 }, /* 3F Output Enable */ + + { 0x40, 0x00 }, /* 40 Level Control */ + { 0x41, 0x00 }, /* 41 DSP Filter Enable */ + { 0x42, 0x00 }, /* 42 Bias Control */ + { 0x43, 0x00 }, /* 43 DAC Control */ + { 0x44, 0x06 }, /* 44 ADC Control */ + { 0x45, 0x00 }, /* 45 Device Shutdown */ + { 0x46, 0x00 }, /* 46 Equalizer Band 1 Coefficient B0 */ + { 0x47, 0x00 }, /* 47 Equalizer Band 1 Coefficient B0 */ + { 0x48, 0x00 }, /* 48 Equalizer Band 1 Coefficient B0 */ + { 0x49, 0x00 }, /* 49 Equalizer Band 1 Coefficient B1 */ + { 0x4A, 0x00 }, /* 4A Equalizer Band 1 Coefficient B1 */ + { 0x4B, 0x00 }, /* 4B Equalizer Band 1 Coefficient B1 */ + { 0x4C, 0x00 }, /* 4C Equalizer Band 1 Coefficient B2 */ + { 0x4D, 0x00 }, /* 4D Equalizer Band 1 Coefficient B2 */ + { 0x4E, 0x00 }, /* 4E Equalizer Band 1 Coefficient B2 */ + { 0x4F, 0x00 }, /* 4F Equalizer Band 1 Coefficient A1 */ + + { 0x50, 0x00 }, /* 50 Equalizer Band 1 Coefficient A1 */ + { 0x51, 0x00 }, /* 51 Equalizer Band 1 Coefficient A1 */ + { 0x52, 0x00 }, /* 52 Equalizer Band 1 Coefficient A2 */ + { 0x53, 0x00 }, /* 53 Equalizer Band 1 Coefficient A2 */ + { 0x54, 0x00 }, /* 54 Equalizer Band 1 Coefficient A2 */ + { 0x55, 0x00 }, /* 55 Equalizer Band 2 Coefficient B0 */ + { 0x56, 0x00 }, /* 56 Equalizer Band 2 Coefficient B0 */ + { 0x57, 0x00 }, /* 57 Equalizer Band 2 Coefficient B0 */ + { 0x58, 0x00 }, /* 58 Equalizer Band 2 Coefficient B1 */ + { 0x59, 0x00 }, /* 59 Equalizer Band 2 Coefficient B1 */ + { 0x5A, 0x00 }, /* 5A Equalizer Band 2 Coefficient B1 */ + { 0x5B, 0x00 }, /* 5B Equalizer Band 2 Coefficient B2 */ + { 0x5C, 0x00 }, /* 5C Equalizer Band 2 Coefficient B2 */ + { 0x5D, 0x00 }, /* 5D Equalizer Band 2 Coefficient B2 */ + { 0x5E, 0x00 }, /* 5E Equalizer Band 2 Coefficient A1 */ + { 0x5F, 0x00 }, /* 5F Equalizer Band 2 Coefficient A1 */ + + { 0x60, 0x00 }, /* 60 Equalizer Band 2 Coefficient A1 */ + { 0x61, 0x00 }, /* 61 Equalizer Band 2 Coefficient A2 */ + { 0x62, 0x00 }, /* 62 Equalizer Band 2 Coefficient A2 */ + { 0x63, 0x00 }, /* 63 Equalizer Band 2 Coefficient A2 */ + { 0x64, 0x00 }, /* 64 Equalizer Band 3 Coefficient B0 */ + { 0x65, 0x00 }, /* 65 Equalizer Band 3 Coefficient B0 */ + { 0x66, 0x00 }, /* 66 Equalizer Band 3 Coefficient B0 */ + { 0x67, 0x00 }, /* 67 Equalizer Band 3 Coefficient B1 */ + { 0x68, 0x00 }, /* 68 Equalizer Band 3 Coefficient B1 */ + { 0x69, 0x00 }, /* 69 Equalizer Band 3 Coefficient B1 */ + { 0x6A, 0x00 }, /* 6A Equalizer Band 3 Coefficient B2 */ + { 0x6B, 0x00 }, /* 6B Equalizer Band 3 Coefficient B2 */ + { 0x6C, 0x00 }, /* 6C Equalizer Band 3 Coefficient B2 */ + { 0x6D, 0x00 }, /* 6D Equalizer Band 3 Coefficient A1 */ + { 0x6E, 0x00 }, /* 6E Equalizer Band 3 Coefficient A1 */ + { 0x6F, 0x00 }, /* 6F Equalizer Band 3 Coefficient A1 */ + + { 0x70, 0x00 }, /* 70 Equalizer Band 3 Coefficient A2 */ + { 0x71, 0x00 }, /* 71 Equalizer Band 3 Coefficient A2 */ + { 0x72, 0x00 }, /* 72 Equalizer Band 3 Coefficient A2 */ + { 0x73, 0x00 }, /* 73 Equalizer Band 4 Coefficient B0 */ + { 0x74, 0x00 }, /* 74 Equalizer Band 4 Coefficient B0 */ + { 0x75, 0x00 }, /* 75 Equalizer Band 4 Coefficient B0 */ + { 0x76, 0x00 }, /* 76 Equalizer Band 4 Coefficient B1 */ + { 0x77, 0x00 }, /* 77 Equalizer Band 4 Coefficient B1 */ + { 0x78, 0x00 }, /* 78 Equalizer Band 4 Coefficient B1 */ + { 0x79, 0x00 }, /* 79 Equalizer Band 4 Coefficient B2 */ + { 0x7A, 0x00 }, /* 7A Equalizer Band 4 Coefficient B2 */ + { 0x7B, 0x00 }, /* 7B Equalizer Band 4 Coefficient B2 */ + { 0x7C, 0x00 }, /* 7C Equalizer Band 4 Coefficient A1 */ + { 0x7D, 0x00 }, /* 7D Equalizer Band 4 Coefficient A1 */ + { 0x7E, 0x00 }, /* 7E Equalizer Band 4 Coefficient A1 */ + { 0x7F, 0x00 }, /* 7F Equalizer Band 4 Coefficient A2 */ + + { 0x80, 0x00 }, /* 80 Equalizer Band 4 Coefficient A2 */ + { 0x81, 0x00 }, /* 81 Equalizer Band 4 Coefficient A2 */ + { 0x82, 0x00 }, /* 82 Equalizer Band 5 Coefficient B0 */ + { 0x83, 0x00 }, /* 83 Equalizer Band 5 Coefficient B0 */ + { 0x84, 0x00 }, /* 84 Equalizer Band 5 Coefficient B0 */ + { 0x85, 0x00 }, /* 85 Equalizer Band 5 Coefficient B1 */ + { 0x86, 0x00 }, /* 86 Equalizer Band 5 Coefficient B1 */ + { 0x87, 0x00 }, /* 87 Equalizer Band 5 Coefficient B1 */ + { 0x88, 0x00 }, /* 88 Equalizer Band 5 Coefficient B2 */ + { 0x89, 0x00 }, /* 89 Equalizer Band 5 Coefficient B2 */ + { 0x8A, 0x00 }, /* 8A Equalizer Band 5 Coefficient B2 */ + { 0x8B, 0x00 }, /* 8B Equalizer Band 5 Coefficient A1 */ + { 0x8C, 0x00 }, /* 8C Equalizer Band 5 Coefficient A1 */ + { 0x8D, 0x00 }, /* 8D Equalizer Band 5 Coefficient A1 */ + { 0x8E, 0x00 }, /* 8E Equalizer Band 5 Coefficient A2 */ + { 0x8F, 0x00 }, /* 8F Equalizer Band 5 Coefficient A2 */ + + { 0x90, 0x00 }, /* 90 Equalizer Band 5 Coefficient A2 */ + { 0x91, 0x00 }, /* 91 Equalizer Band 6 Coefficient B0 */ + { 0x92, 0x00 }, /* 92 Equalizer Band 6 Coefficient B0 */ + { 0x93, 0x00 }, /* 93 Equalizer Band 6 Coefficient B0 */ + { 0x94, 0x00 }, /* 94 Equalizer Band 6 Coefficient B1 */ + { 0x95, 0x00 }, /* 95 Equalizer Band 6 Coefficient B1 */ + { 0x96, 0x00 }, /* 96 Equalizer Band 6 Coefficient B1 */ + { 0x97, 0x00 }, /* 97 Equalizer Band 6 Coefficient B2 */ + { 0x98, 0x00 }, /* 98 Equalizer Band 6 Coefficient B2 */ + { 0x99, 0x00 }, /* 99 Equalizer Band 6 Coefficient B2 */ + { 0x9A, 0x00 }, /* 9A Equalizer Band 6 Coefficient A1 */ + { 0x9B, 0x00 }, /* 9B Equalizer Band 6 Coefficient A1 */ + { 0x9C, 0x00 }, /* 9C Equalizer Band 6 Coefficient A1 */ + { 0x9D, 0x00 }, /* 9D Equalizer Band 6 Coefficient A2 */ + { 0x9E, 0x00 }, /* 9E Equalizer Band 6 Coefficient A2 */ + { 0x9F, 0x00 }, /* 9F Equalizer Band 6 Coefficient A2 */ + + { 0xA0, 0x00 }, /* A0 Equalizer Band 7 Coefficient B0 */ + { 0xA1, 0x00 }, /* A1 Equalizer Band 7 Coefficient B0 */ + { 0xA2, 0x00 }, /* A2 Equalizer Band 7 Coefficient B0 */ + { 0xA3, 0x00 }, /* A3 Equalizer Band 7 Coefficient B1 */ + { 0xA4, 0x00 }, /* A4 Equalizer Band 7 Coefficient B1 */ + { 0xA5, 0x00 }, /* A5 Equalizer Band 7 Coefficient B1 */ + { 0xA6, 0x00 }, /* A6 Equalizer Band 7 Coefficient B2 */ + { 0xA7, 0x00 }, /* A7 Equalizer Band 7 Coefficient B2 */ + { 0xA8, 0x00 }, /* A8 Equalizer Band 7 Coefficient B2 */ + { 0xA9, 0x00 }, /* A9 Equalizer Band 7 Coefficient A1 */ + { 0xAA, 0x00 }, /* AA Equalizer Band 7 Coefficient A1 */ + { 0xAB, 0x00 }, /* AB Equalizer Band 7 Coefficient A1 */ + { 0xAC, 0x00 }, /* AC Equalizer Band 7 Coefficient A2 */ + { 0xAD, 0x00 }, /* AD Equalizer Band 7 Coefficient A2 */ + { 0xAE, 0x00 }, /* AE Equalizer Band 7 Coefficient A2 */ + { 0xAF, 0x00 }, /* AF ADC Biquad Coefficient B0 */ + + { 0xB0, 0x00 }, /* B0 ADC Biquad Coefficient B0 */ + { 0xB1, 0x00 }, /* B1 ADC Biquad Coefficient B0 */ + { 0xB2, 0x00 }, /* B2 ADC Biquad Coefficient B1 */ + { 0xB3, 0x00 }, /* B3 ADC Biquad Coefficient B1 */ + { 0xB4, 0x00 }, /* B4 ADC Biquad Coefficient B1 */ + { 0xB5, 0x00 }, /* B5 ADC Biquad Coefficient B2 */ + { 0xB6, 0x00 }, /* B6 ADC Biquad Coefficient B2 */ + { 0xB7, 0x00 }, /* B7 ADC Biquad Coefficient B2 */ + { 0xB8, 0x00 }, /* B8 ADC Biquad Coefficient A1 */ + { 0xB9, 0x00 }, /* B9 ADC Biquad Coefficient A1 */ + { 0xBA, 0x00 }, /* BA ADC Biquad Coefficient A1 */ + { 0xBB, 0x00 }, /* BB ADC Biquad Coefficient A2 */ + { 0xBC, 0x00 }, /* BC ADC Biquad Coefficient A2 */ + { 0xBD, 0x00 }, /* BD ADC Biquad Coefficient A2 */ + { 0xBE, 0x00 }, /* BE Digital Mic 3 Volume */ + { 0xBF, 0x00 }, /* BF Digital Mic 4 Volume */ + + { 0xC0, 0x00 }, /* C0 Digital Mic 34 Biquad Pre Atten */ + { 0xC1, 0x00 }, /* C1 Record TDM Slot */ + { 0xC2, 0x00 }, /* C2 Sample Rate */ + { 0xC3, 0x00 }, /* C3 Digital Mic 34 Biquad Coefficient C3 */ + { 0xC4, 0x00 }, /* C4 Digital Mic 34 Biquad Coefficient C4 */ + { 0xC5, 0x00 }, /* C5 Digital Mic 34 Biquad Coefficient C5 */ + { 0xC6, 0x00 }, /* C6 Digital Mic 34 Biquad Coefficient C6 */ + { 0xC7, 0x00 }, /* C7 Digital Mic 34 Biquad Coefficient C7 */ + { 0xC8, 0x00 }, /* C8 Digital Mic 34 Biquad Coefficient C8 */ + { 0xC9, 0x00 }, /* C9 Digital Mic 34 Biquad Coefficient C9 */ + { 0xCA, 0x00 }, /* CA Digital Mic 34 Biquad Coefficient CA */ + { 0xCB, 0x00 }, /* CB Digital Mic 34 Biquad Coefficient CB */ + { 0xCC, 0x00 }, /* CC Digital Mic 34 Biquad Coefficient CC */ + { 0xCD, 0x00 }, /* CD Digital Mic 34 Biquad Coefficient CD */ + { 0xCE, 0x00 }, /* CE Digital Mic 34 Biquad Coefficient CE */ + { 0xCF, 0x00 }, /* CF Digital Mic 34 Biquad Coefficient CF */ + + { 0xD0, 0x00 }, /* D0 Digital Mic 34 Biquad Coefficient D0 */ + { 0xD1, 0x00 }, /* D1 Digital Mic 34 Biquad Coefficient D1 */ +}; + +static bool max98090_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98090_REG_SOFTWARE_RESET: + case M98090_REG_DEVICE_STATUS: + case M98090_REG_JACK_STATUS: + case M98090_REG_REVISION_ID: + return true; + default: + return false; + } +} + +static bool max98090_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98090_REG_DEVICE_STATUS ... M98090_REG_INTERRUPT_S: + case M98090_REG_LINE_INPUT_CONFIG ... 0xD1: + case M98090_REG_REVISION_ID: + return true; + default: + return false; + } +} + +static int max98090_reset(struct max98090_priv *max98090) +{ + int ret; + + /* Reset the codec by writing to this write-only reset register */ + ret = regmap_write(max98090->regmap, M98090_REG_SOFTWARE_RESET, + M98090_SWRESET_MASK); + if (ret < 0) { + dev_err(max98090->component->dev, + "Failed to reset codec: %d\n", ret); + return ret; + } + + msleep(20); + return ret; +} + +static const DECLARE_TLV_DB_RANGE(max98090_micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0), + 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0) +); + +static const DECLARE_TLV_DB_SCALE(max98090_mic_tlv, 0, 100, 0); + +static const DECLARE_TLV_DB_SCALE(max98090_line_single_ended_tlv, + -600, 600, 0); + +static const DECLARE_TLV_DB_RANGE(max98090_line_tlv, + 0, 3, TLV_DB_SCALE_ITEM(-600, 300, 0), + 4, 5, TLV_DB_SCALE_ITEM(1400, 600, 0) +); + +static const DECLARE_TLV_DB_SCALE(max98090_avg_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(max98090_av_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(max98090_dvg_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(max98090_dv_tlv, -1500, 100, 0); + +static const DECLARE_TLV_DB_SCALE(max98090_sidetone_tlv, -6050, 200, 0); + +static const DECLARE_TLV_DB_SCALE(max98090_alc_tlv, -1500, 100, 0); +static const DECLARE_TLV_DB_SCALE(max98090_alcmakeup_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(max98090_alccomp_tlv, -3100, 100, 0); +static const DECLARE_TLV_DB_SCALE(max98090_drcexp_tlv, -6600, 100, 0); +static const DECLARE_TLV_DB_SCALE(max98090_sdg_tlv, 50, 200, 0); + +static const DECLARE_TLV_DB_RANGE(max98090_mixout_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-1200, 250, 0), + 2, 3, TLV_DB_SCALE_ITEM(-600, 600, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98090_hp_tlv, + 0, 6, TLV_DB_SCALE_ITEM(-6700, 400, 0), + 7, 14, TLV_DB_SCALE_ITEM(-4000, 300, 0), + 15, 21, TLV_DB_SCALE_ITEM(-1700, 200, 0), + 22, 27, TLV_DB_SCALE_ITEM(-400, 100, 0), + 28, 31, TLV_DB_SCALE_ITEM(150, 50, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98090_spk_tlv, + 0, 4, TLV_DB_SCALE_ITEM(-4800, 400, 0), + 5, 10, TLV_DB_SCALE_ITEM(-2900, 300, 0), + 11, 14, TLV_DB_SCALE_ITEM(-1200, 200, 0), + 15, 29, TLV_DB_SCALE_ITEM(-500, 100, 0), + 30, 39, TLV_DB_SCALE_ITEM(950, 50, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98090_rcv_lout_tlv, + 0, 6, TLV_DB_SCALE_ITEM(-6200, 400, 0), + 7, 14, TLV_DB_SCALE_ITEM(-3500, 300, 0), + 15, 21, TLV_DB_SCALE_ITEM(-1200, 200, 0), + 22, 27, TLV_DB_SCALE_ITEM(100, 100, 0), + 28, 31, TLV_DB_SCALE_ITEM(650, 50, 0) +); + +static int max98090_get_enab_tlv(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mask = (1 << fls(mc->max)) - 1; + unsigned int val = snd_soc_component_read32(component, mc->reg); + unsigned int *select; + + switch (mc->reg) { + case M98090_REG_MIC1_INPUT_LEVEL: + select = &(max98090->pa1en); + break; + case M98090_REG_MIC2_INPUT_LEVEL: + select = &(max98090->pa2en); + break; + case M98090_REG_ADC_SIDETONE: + select = &(max98090->sidetone); + break; + default: + return -EINVAL; + } + + val = (val >> mc->shift) & mask; + + if (val >= 1) { + /* If on, return the volume */ + val = val - 1; + *select = val; + } else { + /* If off, return last stored value */ + val = *select; + } + + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int max98090_put_enab_tlv(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mask = (1 << fls(mc->max)) - 1; + unsigned int sel = ucontrol->value.integer.value[0]; + unsigned int val = snd_soc_component_read32(component, mc->reg); + unsigned int *select; + + switch (mc->reg) { + case M98090_REG_MIC1_INPUT_LEVEL: + select = &(max98090->pa1en); + break; + case M98090_REG_MIC2_INPUT_LEVEL: + select = &(max98090->pa2en); + break; + case M98090_REG_ADC_SIDETONE: + select = &(max98090->sidetone); + break; + default: + return -EINVAL; + } + + val = (val >> mc->shift) & mask; + + if (sel < 0 || sel > mc->max) + return -EINVAL; + + *select = sel; + + /* Setting a volume is only valid if it is already On */ + if (val >= 1) { + sel = sel + 1; + } else { + /* Write what was already there */ + sel = val; + } + + snd_soc_component_update_bits(component, mc->reg, + mask << mc->shift, + sel << mc->shift); + + return *select != val; +} + +static const char *max98090_perf_pwr_text[] = + { "High Performance", "Low Power" }; +static const char *max98090_pwr_perf_text[] = + { "Low Power", "High Performance" }; + +static SOC_ENUM_SINGLE_DECL(max98090_vcmbandgap_enum, + M98090_REG_BIAS_CONTROL, + M98090_VCM_MODE_SHIFT, + max98090_pwr_perf_text); + +static const char *max98090_osr128_text[] = { "64*fs", "128*fs" }; + +static SOC_ENUM_SINGLE_DECL(max98090_osr128_enum, + M98090_REG_ADC_CONTROL, + M98090_OSR128_SHIFT, + max98090_osr128_text); + +static const char *max98090_mode_text[] = { "Voice", "Music" }; + +static SOC_ENUM_SINGLE_DECL(max98090_mode_enum, + M98090_REG_FILTER_CONFIG, + M98090_MODE_SHIFT, + max98090_mode_text); + +static SOC_ENUM_SINGLE_DECL(max98090_filter_dmic34mode_enum, + M98090_REG_FILTER_CONFIG, + M98090_FLT_DMIC34MODE_SHIFT, + max98090_mode_text); + +static const char *max98090_drcatk_text[] = + { "0.5ms", "1ms", "5ms", "10ms", "25ms", "50ms", "100ms", "200ms" }; + +static SOC_ENUM_SINGLE_DECL(max98090_drcatk_enum, + M98090_REG_DRC_TIMING, + M98090_DRCATK_SHIFT, + max98090_drcatk_text); + +static const char *max98090_drcrls_text[] = + { "8s", "4s", "2s", "1s", "0.5s", "0.25s", "0.125s", "0.0625s" }; + +static SOC_ENUM_SINGLE_DECL(max98090_drcrls_enum, + M98090_REG_DRC_TIMING, + M98090_DRCRLS_SHIFT, + max98090_drcrls_text); + +static const char *max98090_alccmp_text[] = + { "1:1", "1:1.5", "1:2", "1:4", "1:INF" }; + +static SOC_ENUM_SINGLE_DECL(max98090_alccmp_enum, + M98090_REG_DRC_COMPRESSOR, + M98090_DRCCMP_SHIFT, + max98090_alccmp_text); + +static const char *max98090_drcexp_text[] = { "1:1", "2:1", "3:1" }; + +static SOC_ENUM_SINGLE_DECL(max98090_drcexp_enum, + M98090_REG_DRC_EXPANDER, + M98090_DRCEXP_SHIFT, + max98090_drcexp_text); + +static SOC_ENUM_SINGLE_DECL(max98090_dac_perfmode_enum, + M98090_REG_DAC_CONTROL, + M98090_PERFMODE_SHIFT, + max98090_perf_pwr_text); + +static SOC_ENUM_SINGLE_DECL(max98090_dachp_enum, + M98090_REG_DAC_CONTROL, + M98090_DACHP_SHIFT, + max98090_pwr_perf_text); + +static SOC_ENUM_SINGLE_DECL(max98090_adchp_enum, + M98090_REG_ADC_CONTROL, + M98090_ADCHP_SHIFT, + max98090_pwr_perf_text); + +static const struct snd_kcontrol_new max98090_snd_controls[] = { + SOC_ENUM("MIC Bias VCM Bandgap", max98090_vcmbandgap_enum), + + SOC_SINGLE("DMIC MIC Comp Filter Config", M98090_REG_DIGITAL_MIC_CONFIG, + M98090_DMIC_COMP_SHIFT, M98090_DMIC_COMP_NUM - 1, 0), + + SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", + M98090_REG_MIC1_INPUT_LEVEL, M98090_MIC_PA1EN_SHIFT, + M98090_MIC_PA1EN_NUM - 1, 0, max98090_get_enab_tlv, + max98090_put_enab_tlv, max98090_micboost_tlv), + + SOC_SINGLE_EXT_TLV("MIC2 Boost Volume", + M98090_REG_MIC2_INPUT_LEVEL, M98090_MIC_PA2EN_SHIFT, + M98090_MIC_PA2EN_NUM - 1, 0, max98090_get_enab_tlv, + max98090_put_enab_tlv, max98090_micboost_tlv), + + SOC_SINGLE_TLV("MIC1 Volume", M98090_REG_MIC1_INPUT_LEVEL, + M98090_MIC_PGAM1_SHIFT, M98090_MIC_PGAM1_NUM - 1, 1, + max98090_mic_tlv), + + SOC_SINGLE_TLV("MIC2 Volume", M98090_REG_MIC2_INPUT_LEVEL, + M98090_MIC_PGAM2_SHIFT, M98090_MIC_PGAM2_NUM - 1, 1, + max98090_mic_tlv), + + SOC_SINGLE_RANGE_TLV("LINEA Single Ended Volume", + M98090_REG_LINE_INPUT_LEVEL, M98090_MIXG135_SHIFT, 0, + M98090_MIXG135_NUM - 1, 1, max98090_line_single_ended_tlv), + + SOC_SINGLE_RANGE_TLV("LINEB Single Ended Volume", + M98090_REG_LINE_INPUT_LEVEL, M98090_MIXG246_SHIFT, 0, + M98090_MIXG246_NUM - 1, 1, max98090_line_single_ended_tlv), + + SOC_SINGLE_RANGE_TLV("LINEA Volume", M98090_REG_LINE_INPUT_LEVEL, + M98090_LINAPGA_SHIFT, 0, M98090_LINAPGA_NUM - 1, 1, + max98090_line_tlv), + + SOC_SINGLE_RANGE_TLV("LINEB Volume", M98090_REG_LINE_INPUT_LEVEL, + M98090_LINBPGA_SHIFT, 0, M98090_LINBPGA_NUM - 1, 1, + max98090_line_tlv), + + SOC_SINGLE("LINEA Ext Resistor Gain Mode", M98090_REG_INPUT_MODE, + M98090_EXTBUFA_SHIFT, M98090_EXTBUFA_NUM - 1, 0), + SOC_SINGLE("LINEB Ext Resistor Gain Mode", M98090_REG_INPUT_MODE, + M98090_EXTBUFB_SHIFT, M98090_EXTBUFB_NUM - 1, 0), + + SOC_SINGLE_TLV("ADCL Boost Volume", M98090_REG_LEFT_ADC_LEVEL, + M98090_AVLG_SHIFT, M98090_AVLG_NUM - 1, 0, + max98090_avg_tlv), + SOC_SINGLE_TLV("ADCR Boost Volume", M98090_REG_RIGHT_ADC_LEVEL, + M98090_AVRG_SHIFT, M98090_AVLG_NUM - 1, 0, + max98090_avg_tlv), + + SOC_SINGLE_TLV("ADCL Volume", M98090_REG_LEFT_ADC_LEVEL, + M98090_AVL_SHIFT, M98090_AVL_NUM - 1, 1, + max98090_av_tlv), + SOC_SINGLE_TLV("ADCR Volume", M98090_REG_RIGHT_ADC_LEVEL, + M98090_AVR_SHIFT, M98090_AVR_NUM - 1, 1, + max98090_av_tlv), + + SOC_ENUM("ADC Oversampling Rate", max98090_osr128_enum), + SOC_SINGLE("ADC Quantizer Dither", M98090_REG_ADC_CONTROL, + M98090_ADCDITHER_SHIFT, M98090_ADCDITHER_NUM - 1, 0), + SOC_ENUM("ADC High Performance Mode", max98090_adchp_enum), + + SOC_SINGLE("DAC Mono Mode", M98090_REG_IO_CONFIGURATION, + M98090_DMONO_SHIFT, M98090_DMONO_NUM - 1, 0), + SOC_SINGLE("SDIN Mode", M98090_REG_IO_CONFIGURATION, + M98090_SDIEN_SHIFT, M98090_SDIEN_NUM - 1, 0), + SOC_SINGLE("SDOUT Mode", M98090_REG_IO_CONFIGURATION, + M98090_SDOEN_SHIFT, M98090_SDOEN_NUM - 1, 0), + SOC_SINGLE("SDOUT Hi-Z Mode", M98090_REG_IO_CONFIGURATION, + M98090_HIZOFF_SHIFT, M98090_HIZOFF_NUM - 1, 1), + SOC_ENUM("Filter Mode", max98090_mode_enum), + SOC_SINGLE("Record Path DC Blocking", M98090_REG_FILTER_CONFIG, + M98090_AHPF_SHIFT, M98090_AHPF_NUM - 1, 0), + SOC_SINGLE("Playback Path DC Blocking", M98090_REG_FILTER_CONFIG, + M98090_DHPF_SHIFT, M98090_DHPF_NUM - 1, 0), + SOC_SINGLE_TLV("Digital BQ Volume", M98090_REG_ADC_BIQUAD_LEVEL, + M98090_AVBQ_SHIFT, M98090_AVBQ_NUM - 1, 1, max98090_dv_tlv), + SOC_SINGLE_EXT_TLV("Digital Sidetone Volume", + M98090_REG_ADC_SIDETONE, M98090_DVST_SHIFT, + M98090_DVST_NUM - 1, 1, max98090_get_enab_tlv, + max98090_put_enab_tlv, max98090_sdg_tlv), + SOC_SINGLE_TLV("Digital Coarse Volume", M98090_REG_DAI_PLAYBACK_LEVEL, + M98090_DVG_SHIFT, M98090_DVG_NUM - 1, 0, + max98090_dvg_tlv), + SOC_SINGLE_TLV("Digital Volume", M98090_REG_DAI_PLAYBACK_LEVEL, + M98090_DV_SHIFT, M98090_DV_NUM - 1, 1, + max98090_dv_tlv), + SND_SOC_BYTES("EQ Coefficients", M98090_REG_EQUALIZER_BASE, 105), + SOC_SINGLE("Digital EQ 3 Band Switch", M98090_REG_DSP_FILTER_ENABLE, + M98090_EQ3BANDEN_SHIFT, M98090_EQ3BANDEN_NUM - 1, 0), + SOC_SINGLE("Digital EQ 5 Band Switch", M98090_REG_DSP_FILTER_ENABLE, + M98090_EQ5BANDEN_SHIFT, M98090_EQ5BANDEN_NUM - 1, 0), + SOC_SINGLE("Digital EQ 7 Band Switch", M98090_REG_DSP_FILTER_ENABLE, + M98090_EQ7BANDEN_SHIFT, M98090_EQ7BANDEN_NUM - 1, 0), + SOC_SINGLE("Digital EQ Clipping Detection", M98090_REG_DAI_PLAYBACK_LEVEL_EQ, + M98090_EQCLPN_SHIFT, M98090_EQCLPN_NUM - 1, + 1), + SOC_SINGLE_TLV("Digital EQ Volume", M98090_REG_DAI_PLAYBACK_LEVEL_EQ, + M98090_DVEQ_SHIFT, M98090_DVEQ_NUM - 1, 1, + max98090_dv_tlv), + + SOC_SINGLE("ALC Enable", M98090_REG_DRC_TIMING, + M98090_DRCEN_SHIFT, M98090_DRCEN_NUM - 1, 0), + SOC_ENUM("ALC Attack Time", max98090_drcatk_enum), + SOC_ENUM("ALC Release Time", max98090_drcrls_enum), + SOC_SINGLE_TLV("ALC Make Up Volume", M98090_REG_DRC_GAIN, + M98090_DRCG_SHIFT, M98090_DRCG_NUM - 1, 0, + max98090_alcmakeup_tlv), + SOC_ENUM("ALC Compression Ratio", max98090_alccmp_enum), + SOC_ENUM("ALC Expansion Ratio", max98090_drcexp_enum), + SOC_SINGLE_TLV("ALC Compression Threshold Volume", + M98090_REG_DRC_COMPRESSOR, M98090_DRCTHC_SHIFT, + M98090_DRCTHC_NUM - 1, 1, max98090_alccomp_tlv), + SOC_SINGLE_TLV("ALC Expansion Threshold Volume", + M98090_REG_DRC_EXPANDER, M98090_DRCTHE_SHIFT, + M98090_DRCTHE_NUM - 1, 1, max98090_drcexp_tlv), + + SOC_ENUM("DAC HP Playback Performance Mode", + max98090_dac_perfmode_enum), + SOC_ENUM("DAC High Performance Mode", max98090_dachp_enum), + + SOC_SINGLE_TLV("Headphone Left Mixer Volume", + M98090_REG_HP_CONTROL, M98090_MIXHPLG_SHIFT, + M98090_MIXHPLG_NUM - 1, 1, max98090_mixout_tlv), + SOC_SINGLE_TLV("Headphone Right Mixer Volume", + M98090_REG_HP_CONTROL, M98090_MIXHPRG_SHIFT, + M98090_MIXHPRG_NUM - 1, 1, max98090_mixout_tlv), + + SOC_SINGLE_TLV("Speaker Left Mixer Volume", + M98090_REG_SPK_CONTROL, M98090_MIXSPLG_SHIFT, + M98090_MIXSPLG_NUM - 1, 1, max98090_mixout_tlv), + SOC_SINGLE_TLV("Speaker Right Mixer Volume", + M98090_REG_SPK_CONTROL, M98090_MIXSPRG_SHIFT, + M98090_MIXSPRG_NUM - 1, 1, max98090_mixout_tlv), + + SOC_SINGLE_TLV("Receiver Left Mixer Volume", + M98090_REG_RCV_LOUTL_CONTROL, M98090_MIXRCVLG_SHIFT, + M98090_MIXRCVLG_NUM - 1, 1, max98090_mixout_tlv), + SOC_SINGLE_TLV("Receiver Right Mixer Volume", + M98090_REG_LOUTR_CONTROL, M98090_MIXRCVRG_SHIFT, + M98090_MIXRCVRG_NUM - 1, 1, max98090_mixout_tlv), + + SOC_DOUBLE_R_TLV("Headphone Volume", M98090_REG_LEFT_HP_VOLUME, + M98090_REG_RIGHT_HP_VOLUME, M98090_HPVOLL_SHIFT, + M98090_HPVOLL_NUM - 1, 0, max98090_hp_tlv), + + SOC_DOUBLE_R_RANGE_TLV("Speaker Volume", + M98090_REG_LEFT_SPK_VOLUME, M98090_REG_RIGHT_SPK_VOLUME, + M98090_SPVOLL_SHIFT, 24, M98090_SPVOLL_NUM - 1 + 24, + 0, max98090_spk_tlv), + + SOC_DOUBLE_R_TLV("Receiver Volume", M98090_REG_RCV_LOUTL_VOLUME, + M98090_REG_LOUTR_VOLUME, M98090_RCVLVOL_SHIFT, + M98090_RCVLVOL_NUM - 1, 0, max98090_rcv_lout_tlv), + + SOC_SINGLE("Headphone Left Switch", M98090_REG_LEFT_HP_VOLUME, + M98090_HPLM_SHIFT, 1, 1), + SOC_SINGLE("Headphone Right Switch", M98090_REG_RIGHT_HP_VOLUME, + M98090_HPRM_SHIFT, 1, 1), + + SOC_SINGLE("Speaker Left Switch", M98090_REG_LEFT_SPK_VOLUME, + M98090_SPLM_SHIFT, 1, 1), + SOC_SINGLE("Speaker Right Switch", M98090_REG_RIGHT_SPK_VOLUME, + M98090_SPRM_SHIFT, 1, 1), + + SOC_SINGLE("Receiver Left Switch", M98090_REG_RCV_LOUTL_VOLUME, + M98090_RCVLM_SHIFT, 1, 1), + SOC_SINGLE("Receiver Right Switch", M98090_REG_LOUTR_VOLUME, + M98090_RCVRM_SHIFT, 1, 1), + + SOC_SINGLE("Zero-Crossing Detection", M98090_REG_LEVEL_CONTROL, + M98090_ZDENN_SHIFT, M98090_ZDENN_NUM - 1, 1), + SOC_SINGLE("Enhanced Vol Smoothing", M98090_REG_LEVEL_CONTROL, + M98090_VS2ENN_SHIFT, M98090_VS2ENN_NUM - 1, 1), + SOC_SINGLE("Volume Adjustment Smoothing", M98090_REG_LEVEL_CONTROL, + M98090_VSENN_SHIFT, M98090_VSENN_NUM - 1, 1), + + SND_SOC_BYTES("Biquad Coefficients", M98090_REG_RECORD_BIQUAD_BASE, 15), + SOC_SINGLE("Biquad Switch", M98090_REG_DSP_FILTER_ENABLE, + M98090_ADCBQEN_SHIFT, M98090_ADCBQEN_NUM - 1, 0), +}; + +static const struct snd_kcontrol_new max98091_snd_controls[] = { + + SOC_SINGLE("DMIC34 Zeropad", M98090_REG_SAMPLE_RATE, + M98090_DMIC34_ZEROPAD_SHIFT, + M98090_DMIC34_ZEROPAD_NUM - 1, 0), + + SOC_ENUM("Filter DMIC34 Mode", max98090_filter_dmic34mode_enum), + SOC_SINGLE("DMIC34 DC Blocking", M98090_REG_FILTER_CONFIG, + M98090_FLT_DMIC34HPF_SHIFT, + M98090_FLT_DMIC34HPF_NUM - 1, 0), + + SOC_SINGLE_TLV("DMIC3 Boost Volume", M98090_REG_DMIC3_VOLUME, + M98090_DMIC_AV3G_SHIFT, M98090_DMIC_AV3G_NUM - 1, 0, + max98090_avg_tlv), + SOC_SINGLE_TLV("DMIC4 Boost Volume", M98090_REG_DMIC4_VOLUME, + M98090_DMIC_AV4G_SHIFT, M98090_DMIC_AV4G_NUM - 1, 0, + max98090_avg_tlv), + + SOC_SINGLE_TLV("DMIC3 Volume", M98090_REG_DMIC3_VOLUME, + M98090_DMIC_AV3_SHIFT, M98090_DMIC_AV3_NUM - 1, 1, + max98090_av_tlv), + SOC_SINGLE_TLV("DMIC4 Volume", M98090_REG_DMIC4_VOLUME, + M98090_DMIC_AV4_SHIFT, M98090_DMIC_AV4_NUM - 1, 1, + max98090_av_tlv), + + SND_SOC_BYTES("DMIC34 Biquad Coefficients", + M98090_REG_DMIC34_BIQUAD_BASE, 15), + SOC_SINGLE("DMIC34 Biquad Switch", M98090_REG_DSP_FILTER_ENABLE, + M98090_DMIC34BQEN_SHIFT, M98090_DMIC34BQEN_NUM - 1, 0), + + SOC_SINGLE_TLV("DMIC34 BQ PreAttenuation Volume", + M98090_REG_DMIC34_BQ_PREATTEN, M98090_AV34BQ_SHIFT, + M98090_AV34BQ_NUM - 1, 1, max98090_dv_tlv), +}; + +static int max98090_micinput_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + + unsigned int val = snd_soc_component_read32(component, w->reg); + + if (w->reg == M98090_REG_MIC1_INPUT_LEVEL) + val = (val & M98090_MIC_PA1EN_MASK) >> M98090_MIC_PA1EN_SHIFT; + else + val = (val & M98090_MIC_PA2EN_MASK) >> M98090_MIC_PA2EN_SHIFT; + + if (val >= 1) { + if (w->reg == M98090_REG_MIC1_INPUT_LEVEL) { + max98090->pa1en = val - 1; /* Update for volatile */ + } else { + max98090->pa2en = val - 1; /* Update for volatile */ + } + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* If turning on, set to most recently selected volume */ + if (w->reg == M98090_REG_MIC1_INPUT_LEVEL) + val = max98090->pa1en + 1; + else + val = max98090->pa2en + 1; + break; + case SND_SOC_DAPM_POST_PMD: + /* If turning off, turn off */ + val = 0; + break; + default: + return -EINVAL; + } + + if (w->reg == M98090_REG_MIC1_INPUT_LEVEL) + snd_soc_component_update_bits(component, w->reg, M98090_MIC_PA1EN_MASK, + val << M98090_MIC_PA1EN_SHIFT); + else + snd_soc_component_update_bits(component, w->reg, M98090_MIC_PA2EN_MASK, + val << M98090_MIC_PA2EN_SHIFT); + + return 0; +} + +static int max98090_shdn_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + + if (event & SND_SOC_DAPM_POST_PMU) + max98090->shdn_pending = true; + + return 0; + +} + +static const char *mic1_mux_text[] = { "IN12", "IN56" }; + +static SOC_ENUM_SINGLE_DECL(mic1_mux_enum, + M98090_REG_INPUT_MODE, + M98090_EXTMIC1_SHIFT, + mic1_mux_text); + +static const struct snd_kcontrol_new max98090_mic1_mux = + SOC_DAPM_ENUM("MIC1 Mux", mic1_mux_enum); + +static const char *mic2_mux_text[] = { "IN34", "IN56" }; + +static SOC_ENUM_SINGLE_DECL(mic2_mux_enum, + M98090_REG_INPUT_MODE, + M98090_EXTMIC2_SHIFT, + mic2_mux_text); + +static const struct snd_kcontrol_new max98090_mic2_mux = + SOC_DAPM_ENUM("MIC2 Mux", mic2_mux_enum); + +static const char *dmic_mux_text[] = { "ADC", "DMIC" }; + +static SOC_ENUM_SINGLE_VIRT_DECL(dmic_mux_enum, dmic_mux_text); + +static const struct snd_kcontrol_new max98090_dmic_mux = + SOC_DAPM_ENUM("DMIC Mux", dmic_mux_enum); + +static const char *max98090_micpre_text[] = { "Off", "On" }; + +static SOC_ENUM_SINGLE_DECL(max98090_pa1en_enum, + M98090_REG_MIC1_INPUT_LEVEL, + M98090_MIC_PA1EN_SHIFT, + max98090_micpre_text); + +static SOC_ENUM_SINGLE_DECL(max98090_pa2en_enum, + M98090_REG_MIC2_INPUT_LEVEL, + M98090_MIC_PA2EN_SHIFT, + max98090_micpre_text); + +/* LINEA mixer switch */ +static const struct snd_kcontrol_new max98090_linea_mixer_controls[] = { + SOC_DAPM_SINGLE("IN1 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN1SEEN_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN3 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN3SEEN_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN5 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN5SEEN_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN34 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN34DIFF_SHIFT, 1, 0), +}; + +/* LINEB mixer switch */ +static const struct snd_kcontrol_new max98090_lineb_mixer_controls[] = { + SOC_DAPM_SINGLE("IN2 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN2SEEN_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN4 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN4SEEN_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN6 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN6SEEN_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN56 Switch", M98090_REG_LINE_INPUT_CONFIG, + M98090_IN56DIFF_SHIFT, 1, 0), +}; + +/* Left ADC mixer switch */ +static const struct snd_kcontrol_new max98090_left_adc_mixer_controls[] = { + SOC_DAPM_SINGLE("IN12 Switch", M98090_REG_LEFT_ADC_MIXER, + M98090_MIXADL_IN12DIFF_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN34 Switch", M98090_REG_LEFT_ADC_MIXER, + M98090_MIXADL_IN34DIFF_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN56 Switch", M98090_REG_LEFT_ADC_MIXER, + M98090_MIXADL_IN65DIFF_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_LEFT_ADC_MIXER, + M98090_MIXADL_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_LEFT_ADC_MIXER, + M98090_MIXADL_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_LEFT_ADC_MIXER, + M98090_MIXADL_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_LEFT_ADC_MIXER, + M98090_MIXADL_MIC2_SHIFT, 1, 0), +}; + +/* Right ADC mixer switch */ +static const struct snd_kcontrol_new max98090_right_adc_mixer_controls[] = { + SOC_DAPM_SINGLE("IN12 Switch", M98090_REG_RIGHT_ADC_MIXER, + M98090_MIXADR_IN12DIFF_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN34 Switch", M98090_REG_RIGHT_ADC_MIXER, + M98090_MIXADR_IN34DIFF_SHIFT, 1, 0), + SOC_DAPM_SINGLE("IN56 Switch", M98090_REG_RIGHT_ADC_MIXER, + M98090_MIXADR_IN65DIFF_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_RIGHT_ADC_MIXER, + M98090_MIXADR_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_RIGHT_ADC_MIXER, + M98090_MIXADR_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_RIGHT_ADC_MIXER, + M98090_MIXADR_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_RIGHT_ADC_MIXER, + M98090_MIXADR_MIC2_SHIFT, 1, 0), +}; + +static const char *lten_mux_text[] = { "Normal", "Loopthrough" }; + +static SOC_ENUM_SINGLE_DECL(ltenl_mux_enum, + M98090_REG_IO_CONFIGURATION, + M98090_LTEN_SHIFT, + lten_mux_text); + +static SOC_ENUM_SINGLE_DECL(ltenr_mux_enum, + M98090_REG_IO_CONFIGURATION, + M98090_LTEN_SHIFT, + lten_mux_text); + +static const struct snd_kcontrol_new max98090_ltenl_mux = + SOC_DAPM_ENUM("LTENL Mux", ltenl_mux_enum); + +static const struct snd_kcontrol_new max98090_ltenr_mux = + SOC_DAPM_ENUM("LTENR Mux", ltenr_mux_enum); + +static const char *lben_mux_text[] = { "Normal", "Loopback" }; + +static SOC_ENUM_SINGLE_DECL(lbenl_mux_enum, + M98090_REG_IO_CONFIGURATION, + M98090_LBEN_SHIFT, + lben_mux_text); + +static SOC_ENUM_SINGLE_DECL(lbenr_mux_enum, + M98090_REG_IO_CONFIGURATION, + M98090_LBEN_SHIFT, + lben_mux_text); + +static const struct snd_kcontrol_new max98090_lbenl_mux = + SOC_DAPM_ENUM("LBENL Mux", lbenl_mux_enum); + +static const struct snd_kcontrol_new max98090_lbenr_mux = + SOC_DAPM_ENUM("LBENR Mux", lbenr_mux_enum); + +static const char *stenl_mux_text[] = { "Normal", "Sidetone Left" }; + +static const char *stenr_mux_text[] = { "Normal", "Sidetone Right" }; + +static SOC_ENUM_SINGLE_DECL(stenl_mux_enum, + M98090_REG_ADC_SIDETONE, + M98090_DSTSL_SHIFT, + stenl_mux_text); + +static SOC_ENUM_SINGLE_DECL(stenr_mux_enum, + M98090_REG_ADC_SIDETONE, + M98090_DSTSR_SHIFT, + stenr_mux_text); + +static const struct snd_kcontrol_new max98090_stenl_mux = + SOC_DAPM_ENUM("STENL Mux", stenl_mux_enum); + +static const struct snd_kcontrol_new max98090_stenr_mux = + SOC_DAPM_ENUM("STENR Mux", stenr_mux_enum); + +/* Left speaker mixer switch */ +static const struct + snd_kcontrol_new max98090_left_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Switch", M98090_REG_LEFT_SPK_MIXER, + M98090_MIXSPL_DACL_SHIFT, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", M98090_REG_LEFT_SPK_MIXER, + M98090_MIXSPL_DACR_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_LEFT_SPK_MIXER, + M98090_MIXSPL_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_LEFT_SPK_MIXER, + M98090_MIXSPL_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_LEFT_SPK_MIXER, + M98090_MIXSPL_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_LEFT_SPK_MIXER, + M98090_MIXSPL_MIC2_SHIFT, 1, 0), +}; + +/* Right speaker mixer switch */ +static const struct + snd_kcontrol_new max98090_right_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Switch", M98090_REG_RIGHT_SPK_MIXER, + M98090_MIXSPR_DACL_SHIFT, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", M98090_REG_RIGHT_SPK_MIXER, + M98090_MIXSPR_DACR_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_RIGHT_SPK_MIXER, + M98090_MIXSPR_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_RIGHT_SPK_MIXER, + M98090_MIXSPR_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_RIGHT_SPK_MIXER, + M98090_MIXSPR_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_RIGHT_SPK_MIXER, + M98090_MIXSPR_MIC2_SHIFT, 1, 0), +}; + +/* Left headphone mixer switch */ +static const struct snd_kcontrol_new max98090_left_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Switch", M98090_REG_LEFT_HP_MIXER, + M98090_MIXHPL_DACL_SHIFT, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", M98090_REG_LEFT_HP_MIXER, + M98090_MIXHPL_DACR_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_LEFT_HP_MIXER, + M98090_MIXHPL_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_LEFT_HP_MIXER, + M98090_MIXHPL_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_LEFT_HP_MIXER, + M98090_MIXHPL_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_LEFT_HP_MIXER, + M98090_MIXHPL_MIC2_SHIFT, 1, 0), +}; + +/* Right headphone mixer switch */ +static const struct snd_kcontrol_new max98090_right_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Switch", M98090_REG_RIGHT_HP_MIXER, + M98090_MIXHPR_DACL_SHIFT, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", M98090_REG_RIGHT_HP_MIXER, + M98090_MIXHPR_DACR_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_RIGHT_HP_MIXER, + M98090_MIXHPR_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_RIGHT_HP_MIXER, + M98090_MIXHPR_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_RIGHT_HP_MIXER, + M98090_MIXHPR_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_RIGHT_HP_MIXER, + M98090_MIXHPR_MIC2_SHIFT, 1, 0), +}; + +/* Left receiver mixer switch */ +static const struct snd_kcontrol_new max98090_left_rcv_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Switch", M98090_REG_RCV_LOUTL_MIXER, + M98090_MIXRCVL_DACL_SHIFT, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", M98090_REG_RCV_LOUTL_MIXER, + M98090_MIXRCVL_DACR_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_RCV_LOUTL_MIXER, + M98090_MIXRCVL_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_RCV_LOUTL_MIXER, + M98090_MIXRCVL_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_RCV_LOUTL_MIXER, + M98090_MIXRCVL_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_RCV_LOUTL_MIXER, + M98090_MIXRCVL_MIC2_SHIFT, 1, 0), +}; + +/* Right receiver mixer switch */ +static const struct snd_kcontrol_new max98090_right_rcv_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Switch", M98090_REG_LOUTR_MIXER, + M98090_MIXRCVR_DACL_SHIFT, 1, 0), + SOC_DAPM_SINGLE("Right DAC Switch", M98090_REG_LOUTR_MIXER, + M98090_MIXRCVR_DACR_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEA Switch", M98090_REG_LOUTR_MIXER, + M98090_MIXRCVR_LINEA_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LINEB Switch", M98090_REG_LOUTR_MIXER, + M98090_MIXRCVR_LINEB_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98090_REG_LOUTR_MIXER, + M98090_MIXRCVR_MIC1_SHIFT, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98090_REG_LOUTR_MIXER, + M98090_MIXRCVR_MIC2_SHIFT, 1, 0), +}; + +static const char *linmod_mux_text[] = { "Left Only", "Left and Right" }; + +static SOC_ENUM_SINGLE_DECL(linmod_mux_enum, + M98090_REG_LOUTR_MIXER, + M98090_LINMOD_SHIFT, + linmod_mux_text); + +static const struct snd_kcontrol_new max98090_linmod_mux = + SOC_DAPM_ENUM("LINMOD Mux", linmod_mux_enum); + +static const char *mixhpsel_mux_text[] = { "DAC Only", "HP Mixer" }; + +/* + * This is a mux as it selects the HP output, but to DAPM it is a Mixer enable + */ +static SOC_ENUM_SINGLE_DECL(mixhplsel_mux_enum, + M98090_REG_HP_CONTROL, + M98090_MIXHPLSEL_SHIFT, + mixhpsel_mux_text); + +static const struct snd_kcontrol_new max98090_mixhplsel_mux = + SOC_DAPM_ENUM("MIXHPLSEL Mux", mixhplsel_mux_enum); + +static SOC_ENUM_SINGLE_DECL(mixhprsel_mux_enum, + M98090_REG_HP_CONTROL, + M98090_MIXHPRSEL_SHIFT, + mixhpsel_mux_text); + +static const struct snd_kcontrol_new max98090_mixhprsel_mux = + SOC_DAPM_ENUM("MIXHPRSEL Mux", mixhprsel_mux_enum); + +static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMICL"), + SND_SOC_DAPM_INPUT("DMICR"), + SND_SOC_DAPM_INPUT("IN1"), + SND_SOC_DAPM_INPUT("IN2"), + SND_SOC_DAPM_INPUT("IN3"), + SND_SOC_DAPM_INPUT("IN4"), + SND_SOC_DAPM_INPUT("IN5"), + SND_SOC_DAPM_INPUT("IN6"), + SND_SOC_DAPM_INPUT("IN12"), + SND_SOC_DAPM_INPUT("IN34"), + SND_SOC_DAPM_INPUT("IN56"), + + SND_SOC_DAPM_SUPPLY("MICBIAS", M98090_REG_INPUT_ENABLE, + M98090_MBEN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SHDN", M98090_REG_DEVICE_SHUTDOWN, + M98090_SHDNN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SDIEN", M98090_REG_IO_CONFIGURATION, + M98090_SDIEN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SDOEN", M98090_REG_IO_CONFIGURATION, + M98090_SDOEN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMICL_ENA", M98090_REG_DIGITAL_MIC_ENABLE, + M98090_DIGMICL_SHIFT, 0, max98090_shdn_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("DMICR_ENA", M98090_REG_DIGITAL_MIC_ENABLE, + M98090_DIGMICR_SHIFT, 0, max98090_shdn_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("AHPF", M98090_REG_FILTER_CONFIG, + M98090_AHPF_SHIFT, 0, NULL, 0), + +/* + * Note: Sysclk and misc power supplies are taken care of by SHDN + */ + + SND_SOC_DAPM_MUX("MIC1 Mux", SND_SOC_NOPM, + 0, 0, &max98090_mic1_mux), + + SND_SOC_DAPM_MUX("MIC2 Mux", SND_SOC_NOPM, + 0, 0, &max98090_mic2_mux), + + SND_SOC_DAPM_MUX("DMIC Mux", SND_SOC_NOPM, 0, 0, &max98090_dmic_mux), + + SND_SOC_DAPM_PGA_E("MIC1 Input", M98090_REG_MIC1_INPUT_LEVEL, + M98090_MIC_PA1EN_SHIFT, 0, NULL, 0, max98090_micinput_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("MIC2 Input", M98090_REG_MIC2_INPUT_LEVEL, + M98090_MIC_PA2EN_SHIFT, 0, NULL, 0, max98090_micinput_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER("LINEA Mixer", SND_SOC_NOPM, 0, 0, + &max98090_linea_mixer_controls[0], + ARRAY_SIZE(max98090_linea_mixer_controls)), + + SND_SOC_DAPM_MIXER("LINEB Mixer", SND_SOC_NOPM, 0, 0, + &max98090_lineb_mixer_controls[0], + ARRAY_SIZE(max98090_lineb_mixer_controls)), + + SND_SOC_DAPM_PGA("LINEA Input", M98090_REG_INPUT_ENABLE, + M98090_LINEAEN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("LINEB Input", M98090_REG_INPUT_ENABLE, + M98090_LINEBEN_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98090_left_adc_mixer_controls[0], + ARRAY_SIZE(max98090_left_adc_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98090_right_adc_mixer_controls[0], + ARRAY_SIZE(max98090_right_adc_mixer_controls)), + + SND_SOC_DAPM_ADC_E("ADCL", NULL, M98090_REG_INPUT_ENABLE, + M98090_ADLEN_SHIFT, 0, max98090_shdn_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC_E("ADCR", NULL, M98090_REG_INPUT_ENABLE, + M98090_ADREN_SHIFT, 0, max98090_shdn_event, + SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_AIF_OUT("AIFOUTL", "HiFi Capture", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIFOUTR", "HiFi Capture", 1, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("LBENL Mux", SND_SOC_NOPM, + 0, 0, &max98090_lbenl_mux), + + SND_SOC_DAPM_MUX("LBENR Mux", SND_SOC_NOPM, + 0, 0, &max98090_lbenr_mux), + + SND_SOC_DAPM_MUX("LTENL Mux", SND_SOC_NOPM, + 0, 0, &max98090_ltenl_mux), + + SND_SOC_DAPM_MUX("LTENR Mux", SND_SOC_NOPM, + 0, 0, &max98090_ltenr_mux), + + SND_SOC_DAPM_MUX("STENL Mux", SND_SOC_NOPM, + 0, 0, &max98090_stenl_mux), + + SND_SOC_DAPM_MUX("STENR Mux", SND_SOC_NOPM, + 0, 0, &max98090_stenr_mux), + + SND_SOC_DAPM_AIF_IN("AIFINL", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFINR", "HiFi Playback", 1, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DACL", NULL, M98090_REG_OUTPUT_ENABLE, + M98090_DALEN_SHIFT, 0), + SND_SOC_DAPM_DAC("DACR", NULL, M98090_REG_OUTPUT_ENABLE, + M98090_DAREN_SHIFT, 0), + + SND_SOC_DAPM_MIXER("Left Headphone Mixer", SND_SOC_NOPM, 0, 0, + &max98090_left_hp_mixer_controls[0], + ARRAY_SIZE(max98090_left_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Headphone Mixer", SND_SOC_NOPM, 0, 0, + &max98090_right_hp_mixer_controls[0], + ARRAY_SIZE(max98090_right_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Speaker Mixer", SND_SOC_NOPM, 0, 0, + &max98090_left_speaker_mixer_controls[0], + ARRAY_SIZE(max98090_left_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Speaker Mixer", SND_SOC_NOPM, 0, 0, + &max98090_right_speaker_mixer_controls[0], + ARRAY_SIZE(max98090_right_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Receiver Mixer", SND_SOC_NOPM, 0, 0, + &max98090_left_rcv_mixer_controls[0], + ARRAY_SIZE(max98090_left_rcv_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Receiver Mixer", SND_SOC_NOPM, 0, 0, + &max98090_right_rcv_mixer_controls[0], + ARRAY_SIZE(max98090_right_rcv_mixer_controls)), + + SND_SOC_DAPM_MUX("LINMOD Mux", SND_SOC_NOPM, 0, 0, + &max98090_linmod_mux), + + SND_SOC_DAPM_MUX("MIXHPLSEL Mux", SND_SOC_NOPM, 0, 0, + &max98090_mixhplsel_mux), + + SND_SOC_DAPM_MUX("MIXHPRSEL Mux", SND_SOC_NOPM, 0, 0, + &max98090_mixhprsel_mux), + + SND_SOC_DAPM_PGA("HP Left Out", M98090_REG_OUTPUT_ENABLE, + M98090_HPLEN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP Right Out", M98090_REG_OUTPUT_ENABLE, + M98090_HPREN_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_PGA("SPK Left Out", M98090_REG_OUTPUT_ENABLE, + M98090_SPLEN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPK Right Out", M98090_REG_OUTPUT_ENABLE, + M98090_SPREN_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_PGA("RCV Left Out", M98090_REG_OUTPUT_ENABLE, + M98090_RCVLEN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("RCV Right Out", M98090_REG_OUTPUT_ENABLE, + M98090_RCVREN_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("RCVL"), + SND_SOC_DAPM_OUTPUT("RCVR"), +}; + +static const struct snd_soc_dapm_widget max98091_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("DMIC3"), + SND_SOC_DAPM_INPUT("DMIC4"), + + SND_SOC_DAPM_SUPPLY("DMIC3_ENA", M98090_REG_DIGITAL_MIC_ENABLE, + M98090_DIGMIC3_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC4_ENA", M98090_REG_DIGITAL_MIC_ENABLE, + M98090_DIGMIC4_SHIFT, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route max98090_dapm_routes[] = { + {"MIC1 Input", NULL, "MIC1"}, + {"MIC2 Input", NULL, "MIC2"}, + + {"DMICL", NULL, "DMICL_ENA"}, + {"DMICL", NULL, "DMICR_ENA"}, + {"DMICR", NULL, "DMICL_ENA"}, + {"DMICR", NULL, "DMICR_ENA"}, + {"DMICL", NULL, "AHPF"}, + {"DMICR", NULL, "AHPF"}, + + /* MIC1 input mux */ + {"MIC1 Mux", "IN12", "IN12"}, + {"MIC1 Mux", "IN56", "IN56"}, + + /* MIC2 input mux */ + {"MIC2 Mux", "IN34", "IN34"}, + {"MIC2 Mux", "IN56", "IN56"}, + + {"MIC1 Input", NULL, "MIC1 Mux"}, + {"MIC2 Input", NULL, "MIC2 Mux"}, + + /* Left ADC input mixer */ + {"Left ADC Mixer", "IN12 Switch", "IN12"}, + {"Left ADC Mixer", "IN34 Switch", "IN34"}, + {"Left ADC Mixer", "IN56 Switch", "IN56"}, + {"Left ADC Mixer", "LINEA Switch", "LINEA Input"}, + {"Left ADC Mixer", "LINEB Switch", "LINEB Input"}, + {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"}, + + /* Right ADC input mixer */ + {"Right ADC Mixer", "IN12 Switch", "IN12"}, + {"Right ADC Mixer", "IN34 Switch", "IN34"}, + {"Right ADC Mixer", "IN56 Switch", "IN56"}, + {"Right ADC Mixer", "LINEA Switch", "LINEA Input"}, + {"Right ADC Mixer", "LINEB Switch", "LINEB Input"}, + {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"}, + + /* Line A input mixer */ + {"LINEA Mixer", "IN1 Switch", "IN1"}, + {"LINEA Mixer", "IN3 Switch", "IN3"}, + {"LINEA Mixer", "IN5 Switch", "IN5"}, + {"LINEA Mixer", "IN34 Switch", "IN34"}, + + /* Line B input mixer */ + {"LINEB Mixer", "IN2 Switch", "IN2"}, + {"LINEB Mixer", "IN4 Switch", "IN4"}, + {"LINEB Mixer", "IN6 Switch", "IN6"}, + {"LINEB Mixer", "IN56 Switch", "IN56"}, + + {"LINEA Input", NULL, "LINEA Mixer"}, + {"LINEB Input", NULL, "LINEB Mixer"}, + + /* Inputs */ + {"ADCL", NULL, "Left ADC Mixer"}, + {"ADCR", NULL, "Right ADC Mixer"}, + {"ADCL", NULL, "SHDN"}, + {"ADCR", NULL, "SHDN"}, + + {"DMIC Mux", "ADC", "ADCL"}, + {"DMIC Mux", "ADC", "ADCR"}, + {"DMIC Mux", "DMIC", "DMICL"}, + {"DMIC Mux", "DMIC", "DMICR"}, + + {"LBENL Mux", "Normal", "DMIC Mux"}, + {"LBENL Mux", "Loopback", "LTENL Mux"}, + {"LBENR Mux", "Normal", "DMIC Mux"}, + {"LBENR Mux", "Loopback", "LTENR Mux"}, + + {"AIFOUTL", NULL, "LBENL Mux"}, + {"AIFOUTR", NULL, "LBENR Mux"}, + {"AIFOUTL", NULL, "SHDN"}, + {"AIFOUTR", NULL, "SHDN"}, + {"AIFOUTL", NULL, "SDOEN"}, + {"AIFOUTR", NULL, "SDOEN"}, + + {"LTENL Mux", "Normal", "AIFINL"}, + {"LTENL Mux", "Loopthrough", "LBENL Mux"}, + {"LTENR Mux", "Normal", "AIFINR"}, + {"LTENR Mux", "Loopthrough", "LBENR Mux"}, + + {"DACL", NULL, "LTENL Mux"}, + {"DACR", NULL, "LTENR Mux"}, + + {"STENL Mux", "Sidetone Left", "ADCL"}, + {"STENL Mux", "Sidetone Left", "DMICL"}, + {"STENR Mux", "Sidetone Right", "ADCR"}, + {"STENR Mux", "Sidetone Right", "DMICR"}, + {"DACL", NULL, "STENL Mux"}, + {"DACR", NULL, "STENR Mux"}, + + {"AIFINL", NULL, "SHDN"}, + {"AIFINR", NULL, "SHDN"}, + {"AIFINL", NULL, "SDIEN"}, + {"AIFINR", NULL, "SDIEN"}, + {"DACL", NULL, "SHDN"}, + {"DACR", NULL, "SHDN"}, + + /* Left headphone output mixer */ + {"Left Headphone Mixer", "Left DAC Switch", "DACL"}, + {"Left Headphone Mixer", "Right DAC Switch", "DACR"}, + {"Left Headphone Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left Headphone Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left Headphone Mixer", "LINEA Switch", "LINEA Input"}, + {"Left Headphone Mixer", "LINEB Switch", "LINEB Input"}, + + /* Right headphone output mixer */ + {"Right Headphone Mixer", "Left DAC Switch", "DACL"}, + {"Right Headphone Mixer", "Right DAC Switch", "DACR"}, + {"Right Headphone Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right Headphone Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right Headphone Mixer", "LINEA Switch", "LINEA Input"}, + {"Right Headphone Mixer", "LINEB Switch", "LINEB Input"}, + + /* Left speaker output mixer */ + {"Left Speaker Mixer", "Left DAC Switch", "DACL"}, + {"Left Speaker Mixer", "Right DAC Switch", "DACR"}, + {"Left Speaker Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left Speaker Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left Speaker Mixer", "LINEA Switch", "LINEA Input"}, + {"Left Speaker Mixer", "LINEB Switch", "LINEB Input"}, + + /* Right speaker output mixer */ + {"Right Speaker Mixer", "Left DAC Switch", "DACL"}, + {"Right Speaker Mixer", "Right DAC Switch", "DACR"}, + {"Right Speaker Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right Speaker Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right Speaker Mixer", "LINEA Switch", "LINEA Input"}, + {"Right Speaker Mixer", "LINEB Switch", "LINEB Input"}, + + /* Left Receiver output mixer */ + {"Left Receiver Mixer", "Left DAC Switch", "DACL"}, + {"Left Receiver Mixer", "Right DAC Switch", "DACR"}, + {"Left Receiver Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left Receiver Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left Receiver Mixer", "LINEA Switch", "LINEA Input"}, + {"Left Receiver Mixer", "LINEB Switch", "LINEB Input"}, + + /* Right Receiver output mixer */ + {"Right Receiver Mixer", "Left DAC Switch", "DACL"}, + {"Right Receiver Mixer", "Right DAC Switch", "DACR"}, + {"Right Receiver Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right Receiver Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right Receiver Mixer", "LINEA Switch", "LINEA Input"}, + {"Right Receiver Mixer", "LINEB Switch", "LINEB Input"}, + + {"MIXHPLSEL Mux", "HP Mixer", "Left Headphone Mixer"}, + + /* + * Disable this for lowest power if bypassing + * the DAC with an analog signal + */ + {"HP Left Out", NULL, "DACL"}, + {"HP Left Out", NULL, "MIXHPLSEL Mux"}, + + {"MIXHPRSEL Mux", "HP Mixer", "Right Headphone Mixer"}, + + /* + * Disable this for lowest power if bypassing + * the DAC with an analog signal + */ + {"HP Right Out", NULL, "DACR"}, + {"HP Right Out", NULL, "MIXHPRSEL Mux"}, + + {"SPK Left Out", NULL, "Left Speaker Mixer"}, + {"SPK Right Out", NULL, "Right Speaker Mixer"}, + {"RCV Left Out", NULL, "Left Receiver Mixer"}, + + {"LINMOD Mux", "Left and Right", "Right Receiver Mixer"}, + {"LINMOD Mux", "Left Only", "Left Receiver Mixer"}, + {"RCV Right Out", NULL, "LINMOD Mux"}, + + {"HPL", NULL, "HP Left Out"}, + {"HPR", NULL, "HP Right Out"}, + {"SPKL", NULL, "SPK Left Out"}, + {"SPKR", NULL, "SPK Right Out"}, + {"RCVL", NULL, "RCV Left Out"}, + {"RCVR", NULL, "RCV Right Out"}, +}; + +static const struct snd_soc_dapm_route max98091_dapm_routes[] = { + /* DMIC inputs */ + {"DMIC3", NULL, "DMIC3_ENA"}, + {"DMIC4", NULL, "DMIC4_ENA"}, + {"DMIC3", NULL, "AHPF"}, + {"DMIC4", NULL, "AHPF"}, +}; + +static int max98090_add_widgets(struct snd_soc_component *component) +{ + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + snd_soc_add_component_controls(component, max98090_snd_controls, + ARRAY_SIZE(max98090_snd_controls)); + + if (max98090->devtype == MAX98091) { + snd_soc_add_component_controls(component, max98091_snd_controls, + ARRAY_SIZE(max98091_snd_controls)); + } + + snd_soc_dapm_new_controls(dapm, max98090_dapm_widgets, + ARRAY_SIZE(max98090_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, max98090_dapm_routes, + ARRAY_SIZE(max98090_dapm_routes)); + + if (max98090->devtype == MAX98091) { + snd_soc_dapm_new_controls(dapm, max98091_dapm_widgets, + ARRAY_SIZE(max98091_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, max98091_dapm_routes, + ARRAY_SIZE(max98091_dapm_routes)); + } + + return 0; +} + +static const int pclk_rates[] = { + 12000000, 12000000, 13000000, 13000000, + 16000000, 16000000, 19200000, 19200000 +}; + +static const int lrclk_rates[] = { + 8000, 16000, 8000, 16000, + 8000, 16000, 8000, 16000 +}; + +static const int user_pclk_rates[] = { + 13000000, 13000000, 19200000, 19200000, +}; + +static const int user_lrclk_rates[] = { + 44100, 48000, 44100, 48000, +}; + +static const unsigned long long ni_value[] = { + 3528, 768, 441, 8 +}; + +static const unsigned long long mi_value[] = { + 8125, 1625, 1500, 25 +}; + +static void max98090_configure_bclk(struct snd_soc_component *component) +{ + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + unsigned long long ni; + int i; + + if (!max98090->sysclk) { + dev_err(component->dev, "No SYSCLK configured\n"); + return; + } + + if (!max98090->bclk || !max98090->lrclk) { + dev_err(component->dev, "No audio clocks configured\n"); + return; + } + + /* Skip configuration when operating as slave */ + if (!(snd_soc_component_read32(component, M98090_REG_MASTER_MODE) & + M98090_MAS_MASK)) { + return; + } + + /* Check for supported PCLK to LRCLK ratios */ + for (i = 0; i < ARRAY_SIZE(pclk_rates); i++) { + if ((pclk_rates[i] == max98090->sysclk) && + (lrclk_rates[i] == max98090->lrclk)) { + dev_dbg(component->dev, + "Found supported PCLK to LRCLK rates 0x%x\n", + i + 0x8); + + snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE, + M98090_FREQ_MASK, + (i + 0x8) << M98090_FREQ_SHIFT); + snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE, + M98090_USE_M1_MASK, 0); + return; + } + } + + /* Check for user calculated MI and NI ratios */ + for (i = 0; i < ARRAY_SIZE(user_pclk_rates); i++) { + if ((user_pclk_rates[i] == max98090->sysclk) && + (user_lrclk_rates[i] == max98090->lrclk)) { + dev_dbg(component->dev, + "Found user supported PCLK to LRCLK rates\n"); + dev_dbg(component->dev, "i %d ni %lld mi %lld\n", + i, ni_value[i], mi_value[i]); + + snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE, + M98090_FREQ_MASK, 0); + snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE, + M98090_USE_M1_MASK, + 1 << M98090_USE_M1_SHIFT); + + snd_soc_component_write(component, M98090_REG_CLOCK_RATIO_NI_MSB, + (ni_value[i] >> 8) & 0x7F); + snd_soc_component_write(component, M98090_REG_CLOCK_RATIO_NI_LSB, + ni_value[i] & 0xFF); + snd_soc_component_write(component, M98090_REG_CLOCK_RATIO_MI_MSB, + (mi_value[i] >> 8) & 0x7F); + snd_soc_component_write(component, M98090_REG_CLOCK_RATIO_MI_LSB, + mi_value[i] & 0xFF); + + return; + } + } + + /* + * Calculate based on MI = 65536 (not as good as either method above) + */ + snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE, + M98090_FREQ_MASK, 0); + snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE, + M98090_USE_M1_MASK, 0); + + /* + * Configure NI when operating as master + * Note: There is a small, but significant audio quality improvement + * by calculating ni and mi. + */ + ni = 65536ULL * (max98090->lrclk < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)max98090->lrclk; + do_div(ni, (unsigned long long int)max98090->sysclk); + dev_info(component->dev, "No better method found\n"); + dev_info(component->dev, "Calculating ni %lld with mi 65536\n", ni); + snd_soc_component_write(component, M98090_REG_CLOCK_RATIO_NI_MSB, + (ni >> 8) & 0x7F); + snd_soc_component_write(component, M98090_REG_CLOCK_RATIO_NI_LSB, ni & 0xFF); +} + +static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + struct max98090_cdata *cdata; + u8 regval; + + max98090->dai_fmt = fmt; + cdata = &max98090->dai[0]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + regval = 0; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Set to slave mode PLL - MAS mode off */ + snd_soc_component_write(component, + M98090_REG_CLOCK_RATIO_NI_MSB, 0x00); + snd_soc_component_write(component, + M98090_REG_CLOCK_RATIO_NI_LSB, 0x00); + snd_soc_component_update_bits(component, M98090_REG_CLOCK_MODE, + M98090_USE_M1_MASK, 0); + max98090->master = false; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set to master mode */ + if (max98090->tdm_slots == 4) { + /* TDM */ + regval |= M98090_MAS_MASK | + M98090_BSEL_64; + } else if (max98090->tdm_slots == 3) { + /* TDM */ + regval |= M98090_MAS_MASK | + M98090_BSEL_48; + } else { + /* Few TDM slots, or No TDM */ + regval |= M98090_MAS_MASK | + M98090_BSEL_32; + } + max98090->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + snd_soc_component_write(component, M98090_REG_MASTER_MODE, regval); + + regval = 0; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + regval |= M98090_DLY_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + case SND_SOC_DAIFMT_RIGHT_J: + regval |= M98090_RJ_MASK; + break; + case SND_SOC_DAIFMT_DSP_A: + /* Not supported mode */ + default: + dev_err(component->dev, "DAI format unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + regval |= M98090_WCI_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + regval |= M98090_BCI_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + regval |= M98090_BCI_MASK|M98090_WCI_MASK; + break; + default: + dev_err(component->dev, "DAI invert mode unsupported"); + return -EINVAL; + } + + /* + * This accommodates an inverted logic in the MAX98090 chip + * for Bit Clock Invert (BCI). The inverted logic is only + * seen for the case of TDM mode. The remaining cases have + * normal logic. + */ + if (max98090->tdm_slots > 1) + regval ^= M98090_BCI_MASK; + + snd_soc_component_write(component, + M98090_REG_INTERFACE_FORMAT, regval); + } + + return 0; +} + +static int max98090_set_tdm_slot(struct snd_soc_dai *codec_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + struct max98090_cdata *cdata; + cdata = &max98090->dai[0]; + + if (slots < 0 || slots > 4) + return -EINVAL; + + max98090->tdm_slots = slots; + max98090->tdm_width = slot_width; + + if (max98090->tdm_slots > 1) { + /* SLOTL SLOTR SLOTDLY */ + snd_soc_component_write(component, M98090_REG_TDM_FORMAT, + 0 << M98090_TDM_SLOTL_SHIFT | + 1 << M98090_TDM_SLOTR_SHIFT | + 0 << M98090_TDM_SLOTDLY_SHIFT); + + /* FSW TDM */ + snd_soc_component_update_bits(component, M98090_REG_TDM_CONTROL, + M98090_TDM_MASK, + M98090_TDM_MASK); + } + + /* + * Normally advisable to set TDM first, but this permits either order + */ + cdata->fmt = 0; + max98090_dai_set_fmt(codec_dai, max98090->dai_fmt); + + return 0; +} + +static int max98090_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* + * SND_SOC_BIAS_PREPARE is called while preparing for a + * transition to ON or away from ON. If current bias_level + * is SND_SOC_BIAS_ON, then it is preparing for a transition + * away from ON. Disable the clock in that case, otherwise + * enable it. + */ + if (IS_ERR(max98090->mclk)) + break; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(max98090->mclk); + } else { + ret = clk_prepare_enable(max98090->mclk); + if (ret) + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regcache_sync(max98090->regmap); + if (ret != 0) { + dev_err(component->dev, + "Failed to sync cache: %d\n", ret); + return ret; + } + } + break; + + case SND_SOC_BIAS_OFF: + /* Set internal pull-up to lowest power mode */ + snd_soc_component_update_bits(component, M98090_REG_JACK_DETECT, + M98090_JDWK_MASK, M98090_JDWK_MASK); + regcache_mark_dirty(max98090->regmap); + break; + } + return 0; +} + +static const int dmic_divisors[] = { 2, 3, 4, 5, 6, 8 }; + +static const int comp_lrclk_rates[] = { + 8000, 16000, 32000, 44100, 48000, 96000 +}; + +struct dmic_table { + int pclk; + struct { + int freq; + int comp[6]; /* One each for 8, 16, 32, 44.1, 48, and 96 kHz */ + } settings[6]; /* One for each dmic divisor. */ +}; + +static const struct dmic_table dmic_table[] = { /* One for each pclk freq. */ + { + .pclk = 11289600, + .settings = { + { .freq = 2, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 1, .comp = { 7, 8, 2, 2, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 6, 6, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + }, + }, + { + .pclk = 12000000, + .settings = { + { .freq = 2, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 1, .comp = { 7, 8, 2, 2, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 5, 5, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + } + }, + { + .pclk = 12288000, + .settings = { + { .freq = 2, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 1, .comp = { 7, 8, 2, 2, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 6, 6, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 3, 3, 3, 3 } }, + } + }, + { + .pclk = 13000000, + .settings = { + { .freq = 2, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 1, .comp = { 7, 8, 0, 0, 0, 0 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 0, .comp = { 7, 8, 4, 4, 5, 5 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 1, 1 } }, + } + }, + { + .pclk = 19200000, + .settings = { + { .freq = 2, .comp = { 0, 0, 0, 0, 0, 0 } }, + { .freq = 1, .comp = { 7, 8, 1, 1, 1, 1 } }, + { .freq = 0, .comp = { 7, 8, 5, 5, 6, 6 } }, + { .freq = 0, .comp = { 7, 8, 2, 2, 3, 3 } }, + { .freq = 0, .comp = { 7, 8, 1, 1, 2, 2 } }, + { .freq = 0, .comp = { 7, 8, 5, 5, 6, 6 } }, + } + }, +}; + +static int max98090_find_divisor(int target_freq, int pclk) +{ + int current_diff = INT_MAX; + int test_diff = INT_MAX; + int divisor_index = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(dmic_divisors); i++) { + test_diff = abs(target_freq - (pclk / dmic_divisors[i])); + if (test_diff < current_diff) { + current_diff = test_diff; + divisor_index = i; + } + } + + return divisor_index; +} + +static int max98090_find_closest_pclk(int pclk) +{ + int m1; + int m2; + int i; + + for (i = 0; i < ARRAY_SIZE(dmic_table); i++) { + if (pclk == dmic_table[i].pclk) + return i; + if (pclk < dmic_table[i].pclk) { + if (i == 0) + return i; + m1 = pclk - dmic_table[i-1].pclk; + m2 = dmic_table[i].pclk - pclk; + if (m1 < m2) + return i - 1; + else + return i; + } + } + + return -EINVAL; +} + +static int max98090_configure_dmic(struct max98090_priv *max98090, + int target_dmic_clk, int pclk, int fs) +{ + int micclk_index; + int pclk_index; + int dmic_freq; + int dmic_comp; + int i; + + pclk_index = max98090_find_closest_pclk(pclk); + if (pclk_index < 0) + return pclk_index; + + micclk_index = max98090_find_divisor(target_dmic_clk, pclk); + + for (i = 0; i < ARRAY_SIZE(comp_lrclk_rates) - 1; i++) { + if (fs <= (comp_lrclk_rates[i] + comp_lrclk_rates[i+1]) / 2) + break; + } + + dmic_freq = dmic_table[pclk_index].settings[micclk_index].freq; + dmic_comp = dmic_table[pclk_index].settings[micclk_index].comp[i]; + + regmap_update_bits(max98090->regmap, M98090_REG_DIGITAL_MIC_ENABLE, + M98090_MICCLK_MASK, + micclk_index << M98090_MICCLK_SHIFT); + + regmap_update_bits(max98090->regmap, M98090_REG_DIGITAL_MIC_CONFIG, + M98090_DMIC_COMP_MASK | M98090_DMIC_FREQ_MASK, + dmic_comp << M98090_DMIC_COMP_SHIFT | + dmic_freq << M98090_DMIC_FREQ_SHIFT); + + return 0; +} + +static int max98090_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + unsigned int fmt = max98090->dai_fmt; + + /* Remove 24-bit format support if it is not in right justified mode. */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_RIGHT_J) { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(substream->runtime, 0, 16, 16); + } + return 0; +} + +static int max98090_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + struct max98090_cdata *cdata; + + cdata = &max98090->dai[0]; + max98090->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + max98090->bclk *= 2; + + max98090->lrclk = params_rate(params); + + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(component, M98090_REG_INTERFACE_FORMAT, + M98090_WS_MASK, 0); + break; + default: + return -EINVAL; + } + + if (max98090->master) + max98090_configure_bclk(component); + + cdata->rate = max98090->lrclk; + + /* Update filter mode */ + if (max98090->lrclk < 24000) + snd_soc_component_update_bits(component, M98090_REG_FILTER_CONFIG, + M98090_MODE_MASK, 0); + else + snd_soc_component_update_bits(component, M98090_REG_FILTER_CONFIG, + M98090_MODE_MASK, M98090_MODE_MASK); + + /* Update sample rate mode */ + if (max98090->lrclk < 50000) + snd_soc_component_update_bits(component, M98090_REG_FILTER_CONFIG, + M98090_DHF_MASK, 0); + else + snd_soc_component_update_bits(component, M98090_REG_FILTER_CONFIG, + M98090_DHF_MASK, M98090_DHF_MASK); + + max98090_configure_dmic(max98090, max98090->dmic_freq, max98090->pclk, + max98090->lrclk); + + return 0; +} + +/* + * PLL / Sysclk + */ +static int max98090_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + + /* Requested clock frequency is already setup */ + if (freq == max98090->sysclk) + return 0; + + if (!IS_ERR(max98090->mclk)) { + freq = clk_round_rate(max98090->mclk, freq); + clk_set_rate(max98090->mclk, freq); + } + + /* Setup clocks for slave mode, and using the PLL + * PSCLK = 0x01 (when master clk is 10MHz to 20MHz) + * 0x02 (when master clk is 20MHz to 40MHz).. + * 0x03 (when master clk is 40MHz to 60MHz).. + */ + if ((freq >= 10000000) && (freq <= 20000000)) { + snd_soc_component_write(component, M98090_REG_SYSTEM_CLOCK, + M98090_PSCLK_DIV1); + max98090->pclk = freq; + } else if ((freq > 20000000) && (freq <= 40000000)) { + snd_soc_component_write(component, M98090_REG_SYSTEM_CLOCK, + M98090_PSCLK_DIV2); + max98090->pclk = freq >> 1; + } else if ((freq > 40000000) && (freq <= 60000000)) { + snd_soc_component_write(component, M98090_REG_SYSTEM_CLOCK, + M98090_PSCLK_DIV4); + max98090->pclk = freq >> 2; + } else { + dev_err(component->dev, "Invalid master clock frequency\n"); + return -EINVAL; + } + + max98090->sysclk = freq; + + return 0; +} + +static int max98090_dai_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + int regval; + + regval = mute ? M98090_DVM_MASK : 0; + snd_soc_component_update_bits(component, M98090_REG_DAI_PLAYBACK_LEVEL, + M98090_DVM_MASK, regval); + + return 0; +} + +static int max98090_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!max98090->master && dai->active == 1) + queue_delayed_work(system_power_efficient_wq, + &max98090->pll_det_enable_work, + msecs_to_jiffies(10)); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!max98090->master && dai->active == 1) + schedule_work(&max98090->pll_det_disable_work); + break; + default: + break; + } + + return 0; +} + +static void max98090_pll_det_enable_work(struct work_struct *work) +{ + struct max98090_priv *max98090 = + container_of(work, struct max98090_priv, + pll_det_enable_work.work); + struct snd_soc_component *component = max98090->component; + unsigned int status, mask; + + /* + * Clear status register in order to clear possibly already occurred + * PLL unlock. If PLL hasn't still locked, the status will be set + * again and PLL unlock interrupt will occur. + * Note this will clear all status bits + */ + regmap_read(max98090->regmap, M98090_REG_DEVICE_STATUS, &status); + + /* + * Queue jack work in case jack state has just changed but handler + * hasn't run yet + */ + regmap_read(max98090->regmap, M98090_REG_INTERRUPT_S, &mask); + status &= mask; + if (status & M98090_JDET_MASK) + queue_delayed_work(system_power_efficient_wq, + &max98090->jack_work, + msecs_to_jiffies(100)); + + /* Enable PLL unlock interrupt */ + snd_soc_component_update_bits(component, M98090_REG_INTERRUPT_S, + M98090_IULK_MASK, + 1 << M98090_IULK_SHIFT); +} + +static void max98090_pll_det_disable_work(struct work_struct *work) +{ + struct max98090_priv *max98090 = + container_of(work, struct max98090_priv, pll_det_disable_work); + struct snd_soc_component *component = max98090->component; + + cancel_delayed_work_sync(&max98090->pll_det_enable_work); + + /* Disable PLL unlock interrupt */ + snd_soc_component_update_bits(component, M98090_REG_INTERRUPT_S, + M98090_IULK_MASK, 0); +} + +static void max98090_pll_work(struct max98090_priv *max98090) +{ + struct snd_soc_component *component = max98090->component; + + if (!snd_soc_component_is_active(component)) + return; + + dev_info_ratelimited(component->dev, "PLL unlocked\n"); + + /* + * As the datasheet suggested, the maximum PLL lock time should be + * 7 msec. The workaround resets the codec softly by toggling SHDN + * off and on if PLL failed to lock for 10 msec. Notably, there is + * no suggested hold time for SHDN off. + */ + + /* Toggle shutdown OFF then ON */ + snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN, + M98090_SHDNN_MASK, 0); + snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN, + M98090_SHDNN_MASK, M98090_SHDNN_MASK); + + /* Give PLL time to lock */ + msleep(10); +} + +static void max98090_jack_work(struct work_struct *work) +{ + struct max98090_priv *max98090 = container_of(work, + struct max98090_priv, + jack_work.work); + struct snd_soc_component *component = max98090->component; + int status = 0; + int reg; + + /* Read a second time */ + if (max98090->jack_state == M98090_JACK_STATE_NO_HEADSET) { + + /* Strong pull up allows mic detection */ + snd_soc_component_update_bits(component, M98090_REG_JACK_DETECT, + M98090_JDWK_MASK, 0); + + msleep(50); + + reg = snd_soc_component_read32(component, M98090_REG_JACK_STATUS); + + /* Weak pull up allows only insertion detection */ + snd_soc_component_update_bits(component, M98090_REG_JACK_DETECT, + M98090_JDWK_MASK, M98090_JDWK_MASK); + } else { + reg = snd_soc_component_read32(component, M98090_REG_JACK_STATUS); + } + + reg = snd_soc_component_read32(component, M98090_REG_JACK_STATUS); + + switch (reg & (M98090_LSNS_MASK | M98090_JKSNS_MASK)) { + case M98090_LSNS_MASK | M98090_JKSNS_MASK: + dev_dbg(component->dev, "No Headset Detected\n"); + + max98090->jack_state = M98090_JACK_STATE_NO_HEADSET; + + status |= 0; + + break; + + case 0: + if (max98090->jack_state == + M98090_JACK_STATE_HEADSET) { + + dev_dbg(component->dev, + "Headset Button Down Detected\n"); + + /* + * max98090_headset_button_event(codec) + * could be defined, then called here. + */ + + status |= SND_JACK_HEADSET; + status |= SND_JACK_BTN_0; + + break; + } + + /* Line is reported as Headphone */ + /* Nokia Headset is reported as Headphone */ + /* Mono Headphone is reported as Headphone */ + dev_dbg(component->dev, "Headphone Detected\n"); + + max98090->jack_state = M98090_JACK_STATE_HEADPHONE; + + status |= SND_JACK_HEADPHONE; + + break; + + case M98090_JKSNS_MASK: + dev_dbg(component->dev, "Headset Detected\n"); + + max98090->jack_state = M98090_JACK_STATE_HEADSET; + + status |= SND_JACK_HEADSET; + + break; + + default: + dev_dbg(component->dev, "Unrecognized Jack Status\n"); + break; + } + + snd_soc_jack_report(max98090->jack, status, + SND_JACK_HEADSET | SND_JACK_BTN_0); +} + +static irqreturn_t max98090_interrupt(int irq, void *data) +{ + struct max98090_priv *max98090 = data; + struct snd_soc_component *component = max98090->component; + int ret; + unsigned int mask; + unsigned int active; + + /* Treat interrupt before codec is initialized as spurious */ + if (component == NULL) + return IRQ_NONE; + + dev_dbg(component->dev, "***** max98090_interrupt *****\n"); + + ret = regmap_read(max98090->regmap, M98090_REG_INTERRUPT_S, &mask); + + if (ret != 0) { + dev_err(component->dev, + "failed to read M98090_REG_INTERRUPT_S: %d\n", + ret); + return IRQ_NONE; + } + + ret = regmap_read(max98090->regmap, M98090_REG_DEVICE_STATUS, &active); + + if (ret != 0) { + dev_err(component->dev, + "failed to read M98090_REG_DEVICE_STATUS: %d\n", + ret); + return IRQ_NONE; + } + + dev_dbg(component->dev, "active=0x%02x mask=0x%02x -> active=0x%02x\n", + active, mask, active & mask); + + active &= mask; + + if (!active) + return IRQ_NONE; + + if (active & M98090_CLD_MASK) + dev_err(component->dev, "M98090_CLD_MASK\n"); + + if (active & M98090_SLD_MASK) + dev_dbg(component->dev, "M98090_SLD_MASK\n"); + + if (active & M98090_ULK_MASK) { + dev_dbg(component->dev, "M98090_ULK_MASK\n"); + max98090_pll_work(max98090); + } + + if (active & M98090_JDET_MASK) { + dev_dbg(component->dev, "M98090_JDET_MASK\n"); + + pm_wakeup_event(component->dev, 100); + + queue_delayed_work(system_power_efficient_wq, + &max98090->jack_work, + msecs_to_jiffies(100)); + } + + if (active & M98090_DRCACT_MASK) + dev_dbg(component->dev, "M98090_DRCACT_MASK\n"); + + if (active & M98090_DRCCLP_MASK) + dev_err(component->dev, "M98090_DRCCLP_MASK\n"); + + return IRQ_HANDLED; +} + +/** + * max98090_mic_detect - Enable microphone detection via the MAX98090 IRQ + * + * @component: MAX98090 component + * @jack: jack to report detection events on + * + * Enable microphone detection via IRQ on the MAX98090. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for MAX98090 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * If no jack is supplied detection will be disabled. + */ +int max98090_mic_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "max98090_mic_detect\n"); + + max98090->jack = jack; + if (jack) { + snd_soc_component_update_bits(component, M98090_REG_INTERRUPT_S, + M98090_IJDET_MASK, + 1 << M98090_IJDET_SHIFT); + } else { + snd_soc_component_update_bits(component, M98090_REG_INTERRUPT_S, + M98090_IJDET_MASK, + 0); + } + + /* Send an initial empty report */ + snd_soc_jack_report(max98090->jack, 0, + SND_JACK_HEADSET | SND_JACK_BTN_0); + + queue_delayed_work(system_power_efficient_wq, + &max98090->jack_work, + msecs_to_jiffies(100)); + + return 0; +} +EXPORT_SYMBOL_GPL(max98090_mic_detect); + +#define MAX98090_RATES SNDRV_PCM_RATE_8000_96000 +#define MAX98090_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops max98090_dai_ops = { + .startup = max98090_dai_startup, + .set_sysclk = max98090_dai_set_sysclk, + .set_fmt = max98090_dai_set_fmt, + .set_tdm_slot = max98090_set_tdm_slot, + .hw_params = max98090_dai_hw_params, + .digital_mute = max98090_dai_digital_mute, + .trigger = max98090_dai_trigger, +}; + +static struct snd_soc_dai_driver max98090_dai[] = { +{ + .name = "HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = MAX98090_RATES, + .formats = MAX98090_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98090_RATES, + .formats = MAX98090_FORMATS, + }, + .ops = &max98090_dai_ops, +} +}; + +static int max98090_probe(struct snd_soc_component *component) +{ + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + struct max98090_cdata *cdata; + enum max98090_type devtype; + int ret = 0; + int err; + unsigned int micbias; + + dev_dbg(component->dev, "max98090_probe\n"); + + max98090->mclk = devm_clk_get(component->dev, "mclk"); + if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + max98090->component = component; + + /* Reset the codec, the DSP core, and disable all interrupts */ + max98090_reset(max98090); + + /* Initialize private data */ + + max98090->sysclk = (unsigned)-1; + max98090->pclk = (unsigned)-1; + max98090->master = false; + + cdata = &max98090->dai[0]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + + max98090->lin_state = 0; + max98090->pa1en = 0; + max98090->pa2en = 0; + + ret = snd_soc_component_read32(component, M98090_REG_REVISION_ID); + if (ret < 0) { + dev_err(component->dev, "Failed to read device revision: %d\n", + ret); + goto err_access; + } + + if ((ret >= M98090_REVA) && (ret <= M98090_REVA + 0x0f)) { + devtype = MAX98090; + dev_info(component->dev, "MAX98090 REVID=0x%02x\n", ret); + } else if ((ret >= M98091_REVA) && (ret <= M98091_REVA + 0x0f)) { + devtype = MAX98091; + dev_info(component->dev, "MAX98091 REVID=0x%02x\n", ret); + } else { + devtype = MAX98090; + dev_err(component->dev, "Unrecognized revision 0x%02x\n", ret); + } + + if (max98090->devtype != devtype) { + dev_warn(component->dev, "Mismatch in DT specified CODEC type.\n"); + max98090->devtype = devtype; + } + + max98090->jack_state = M98090_JACK_STATE_NO_HEADSET; + + INIT_DELAYED_WORK(&max98090->jack_work, max98090_jack_work); + INIT_DELAYED_WORK(&max98090->pll_det_enable_work, + max98090_pll_det_enable_work); + INIT_WORK(&max98090->pll_det_disable_work, + max98090_pll_det_disable_work); + + /* Enable jack detection */ + snd_soc_component_write(component, M98090_REG_JACK_DETECT, + M98090_JDETEN_MASK | M98090_JDEB_25MS); + + /* + * Clear any old interrupts. + * An old interrupt ocurring prior to installing the ISR + * can keep a new interrupt from generating a trigger. + */ + snd_soc_component_read32(component, M98090_REG_DEVICE_STATUS); + + /* High Performance is default */ + snd_soc_component_update_bits(component, M98090_REG_DAC_CONTROL, + M98090_DACHP_MASK, + 1 << M98090_DACHP_SHIFT); + snd_soc_component_update_bits(component, M98090_REG_DAC_CONTROL, + M98090_PERFMODE_MASK, + 0 << M98090_PERFMODE_SHIFT); + snd_soc_component_update_bits(component, M98090_REG_ADC_CONTROL, + M98090_ADCHP_MASK, + 1 << M98090_ADCHP_SHIFT); + + /* Turn on VCM bandgap reference */ + snd_soc_component_write(component, M98090_REG_BIAS_CONTROL, + M98090_VCM_MODE_MASK); + + err = device_property_read_u32(component->dev, "maxim,micbias", &micbias); + if (err) { + micbias = M98090_MBVSEL_2V8; + dev_info(component->dev, "use default 2.8v micbias\n"); + } else if (micbias > M98090_MBVSEL_2V8) { + dev_err(component->dev, "micbias out of range 0x%x\n", micbias); + micbias = M98090_MBVSEL_2V8; + } + + snd_soc_component_update_bits(component, M98090_REG_MIC_BIAS_VOLTAGE, + M98090_MBVSEL_MASK, micbias); + + max98090_add_widgets(component); + +err_access: + return ret; +} + +static void max98090_remove(struct snd_soc_component *component) +{ + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&max98090->jack_work); + cancel_delayed_work_sync(&max98090->pll_det_enable_work); + cancel_work_sync(&max98090->pll_det_disable_work); + max98090->component = NULL; +} + +static void max98090_seq_notifier(struct snd_soc_component *component, + enum snd_soc_dapm_type event, int subseq) +{ + struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); + + if (max98090->shdn_pending) { + snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN, + M98090_SHDNN_MASK, 0); + msleep(40); + snd_soc_component_update_bits(component, M98090_REG_DEVICE_SHUTDOWN, + M98090_SHDNN_MASK, M98090_SHDNN_MASK); + max98090->shdn_pending = false; + } +} + +static const struct snd_soc_component_driver soc_component_dev_max98090 = { + .probe = max98090_probe, + .remove = max98090_remove, + .seq_notifier = max98090_seq_notifier, + .set_bias_level = max98090_set_bias_level, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config max98090_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX98090_MAX_REGISTER, + .reg_defaults = max98090_reg, + .num_reg_defaults = ARRAY_SIZE(max98090_reg), + .volatile_reg = max98090_volatile_register, + .readable_reg = max98090_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98090_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct max98090_priv *max98090; + const struct acpi_device_id *acpi_id; + kernel_ulong_t driver_data = 0; + int ret; + + pr_debug("max98090_i2c_probe\n"); + + max98090 = devm_kzalloc(&i2c->dev, sizeof(struct max98090_priv), + GFP_KERNEL); + if (max98090 == NULL) + return -ENOMEM; + + if (ACPI_HANDLE(&i2c->dev)) { + acpi_id = acpi_match_device(i2c->dev.driver->acpi_match_table, + &i2c->dev); + if (!acpi_id) { + dev_err(&i2c->dev, "No driver data\n"); + return -EINVAL; + } + driver_data = acpi_id->driver_data; + } else if (i2c_id) { + driver_data = i2c_id->driver_data; + } + + max98090->devtype = driver_data; + i2c_set_clientdata(i2c, max98090); + max98090->pdata = i2c->dev.platform_data; + + ret = of_property_read_u32(i2c->dev.of_node, "maxim,dmic-freq", + &max98090->dmic_freq); + if (ret < 0) + max98090->dmic_freq = MAX98090_DEFAULT_DMIC_FREQ; + + max98090->regmap = devm_regmap_init_i2c(i2c, &max98090_regmap); + if (IS_ERR(max98090->regmap)) { + ret = PTR_ERR(max98090->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + goto err_enable; + } + + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + max98090_interrupt, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max98090_interrupt", max98090); + if (ret < 0) { + dev_err(&i2c->dev, "request_irq failed: %d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_max98090, max98090_dai, + ARRAY_SIZE(max98090_dai)); +err_enable: + return ret; +} + +static void max98090_i2c_shutdown(struct i2c_client *i2c) +{ + struct max98090_priv *max98090 = dev_get_drvdata(&i2c->dev); + + /* + * Enable volume smoothing, disable zero cross. This will cause + * a quick 40ms ramp to mute on shutdown. + */ + regmap_write(max98090->regmap, + M98090_REG_LEVEL_CONTROL, M98090_VSENN_MASK); + regmap_write(max98090->regmap, + M98090_REG_DEVICE_SHUTDOWN, 0x00); + msleep(40); +} + +static int max98090_i2c_remove(struct i2c_client *client) +{ + max98090_i2c_shutdown(client); + + return 0; +} + +#ifdef CONFIG_PM +static int max98090_runtime_resume(struct device *dev) +{ + struct max98090_priv *max98090 = dev_get_drvdata(dev); + + regcache_cache_only(max98090->regmap, false); + + max98090_reset(max98090); + + regcache_sync(max98090->regmap); + + return 0; +} + +static int max98090_runtime_suspend(struct device *dev) +{ + struct max98090_priv *max98090 = dev_get_drvdata(dev); + + regcache_cache_only(max98090->regmap, true); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int max98090_resume(struct device *dev) +{ + struct max98090_priv *max98090 = dev_get_drvdata(dev); + unsigned int status; + + regcache_mark_dirty(max98090->regmap); + + max98090_reset(max98090); + + /* clear IRQ status */ + regmap_read(max98090->regmap, M98090_REG_DEVICE_STATUS, &status); + + regcache_sync(max98090->regmap); + + return 0; +} + +static int max98090_suspend(struct device *dev) +{ + return 0; +} +#endif + +static const struct dev_pm_ops max98090_pm = { + SET_RUNTIME_PM_OPS(max98090_runtime_suspend, + max98090_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(max98090_suspend, max98090_resume) +}; + +static const struct i2c_device_id max98090_i2c_id[] = { + { "max98090", MAX98090 }, + { "max98091", MAX98091 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98090_i2c_id); + +static const struct of_device_id max98090_of_match[] = { + { .compatible = "maxim,max98090", }, + { .compatible = "maxim,max98091", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98090_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max98090_acpi_match[] = { + { "193C9890", MAX98090 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, max98090_acpi_match); +#endif + +static struct i2c_driver max98090_i2c_driver = { + .driver = { + .name = "max98090", + .pm = &max98090_pm, + .of_match_table = of_match_ptr(max98090_of_match), + .acpi_match_table = ACPI_PTR(max98090_acpi_match), + }, + .probe = max98090_i2c_probe, + .shutdown = max98090_i2c_shutdown, + .remove = max98090_i2c_remove, + .id_table = max98090_i2c_id, +}; + +module_i2c_driver(max98090_i2c_driver); + +MODULE_DESCRIPTION("ALSA SoC MAX98090 driver"); +MODULE_AUTHOR("Peter Hsiang, Jesse Marroqin, Jerry Wong"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98090.h b/sound/soc/codecs/max98090.h new file mode 100644 index 000000000..388d2f746 --- /dev/null +++ b/sound/soc/codecs/max98090.h @@ -0,0 +1,1551 @@ +/* + * max98090.h -- MAX98090 ALSA SoC Audio driver + * + * Copyright 2011-2012 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MAX98090_H +#define _MAX98090_H + +/* + * The default operating frequency for a DMIC attached to the codec. + * This can be overridden by a device tree property. + */ +#define MAX98090_DEFAULT_DMIC_FREQ 2500000 + +/* + * MAX98090 Register Definitions + */ + +#define M98090_REG_SOFTWARE_RESET 0x00 +#define M98090_REG_DEVICE_STATUS 0x01 +#define M98090_REG_JACK_STATUS 0x02 +#define M98090_REG_INTERRUPT_S 0x03 +#define M98090_REG_QUICK_SYSTEM_CLOCK 0x04 +#define M98090_REG_QUICK_SAMPLE_RATE 0x05 +#define M98090_REG_DAI_INTERFACE 0x06 +#define M98090_REG_DAC_PATH 0x07 +#define M98090_REG_MIC_DIRECT_TO_ADC 0x08 +#define M98090_REG_LINE_TO_ADC 0x09 +#define M98090_REG_ANALOG_MIC_LOOP 0x0A +#define M98090_REG_ANALOG_LINE_LOOP 0x0B +#define M98090_REG_RESERVED 0x0C +#define M98090_REG_LINE_INPUT_CONFIG 0x0D +#define M98090_REG_LINE_INPUT_LEVEL 0x0E +#define M98090_REG_INPUT_MODE 0x0F +#define M98090_REG_MIC1_INPUT_LEVEL 0x10 +#define M98090_REG_MIC2_INPUT_LEVEL 0x11 +#define M98090_REG_MIC_BIAS_VOLTAGE 0x12 +#define M98090_REG_DIGITAL_MIC_ENABLE 0x13 +#define M98090_REG_DIGITAL_MIC_CONFIG 0x14 +#define M98090_REG_LEFT_ADC_MIXER 0x15 +#define M98090_REG_RIGHT_ADC_MIXER 0x16 +#define M98090_REG_LEFT_ADC_LEVEL 0x17 +#define M98090_REG_RIGHT_ADC_LEVEL 0x18 +#define M98090_REG_ADC_BIQUAD_LEVEL 0x19 +#define M98090_REG_ADC_SIDETONE 0x1A +#define M98090_REG_SYSTEM_CLOCK 0x1B +#define M98090_REG_CLOCK_MODE 0x1C +#define M98090_REG_CLOCK_RATIO_NI_MSB 0x1D +#define M98090_REG_CLOCK_RATIO_NI_LSB 0x1E +#define M98090_REG_CLOCK_RATIO_MI_MSB 0x1F +#define M98090_REG_CLOCK_RATIO_MI_LSB 0x20 +#define M98090_REG_MASTER_MODE 0x21 +#define M98090_REG_INTERFACE_FORMAT 0x22 +#define M98090_REG_TDM_CONTROL 0x23 +#define M98090_REG_TDM_FORMAT 0x24 +#define M98090_REG_IO_CONFIGURATION 0x25 +#define M98090_REG_FILTER_CONFIG 0x26 +#define M98090_REG_DAI_PLAYBACK_LEVEL 0x27 +#define M98090_REG_DAI_PLAYBACK_LEVEL_EQ 0x28 +#define M98090_REG_LEFT_HP_MIXER 0x29 +#define M98090_REG_RIGHT_HP_MIXER 0x2A +#define M98090_REG_HP_CONTROL 0x2B +#define M98090_REG_LEFT_HP_VOLUME 0x2C +#define M98090_REG_RIGHT_HP_VOLUME 0x2D +#define M98090_REG_LEFT_SPK_MIXER 0x2E +#define M98090_REG_RIGHT_SPK_MIXER 0x2F +#define M98090_REG_SPK_CONTROL 0x30 +#define M98090_REG_LEFT_SPK_VOLUME 0x31 +#define M98090_REG_RIGHT_SPK_VOLUME 0x32 +#define M98090_REG_DRC_TIMING 0x33 +#define M98090_REG_DRC_COMPRESSOR 0x34 +#define M98090_REG_DRC_EXPANDER 0x35 +#define M98090_REG_DRC_GAIN 0x36 +#define M98090_REG_RCV_LOUTL_MIXER 0x37 +#define M98090_REG_RCV_LOUTL_CONTROL 0x38 +#define M98090_REG_RCV_LOUTL_VOLUME 0x39 +#define M98090_REG_LOUTR_MIXER 0x3A +#define M98090_REG_LOUTR_CONTROL 0x3B +#define M98090_REG_LOUTR_VOLUME 0x3C +#define M98090_REG_JACK_DETECT 0x3D +#define M98090_REG_INPUT_ENABLE 0x3E +#define M98090_REG_OUTPUT_ENABLE 0x3F +#define M98090_REG_LEVEL_CONTROL 0x40 +#define M98090_REG_DSP_FILTER_ENABLE 0x41 +#define M98090_REG_BIAS_CONTROL 0x42 +#define M98090_REG_DAC_CONTROL 0x43 +#define M98090_REG_ADC_CONTROL 0x44 +#define M98090_REG_DEVICE_SHUTDOWN 0x45 +#define M98090_REG_EQUALIZER_BASE 0x46 +#define M98090_REG_RECORD_BIQUAD_BASE 0xAF +#define M98090_REG_DMIC3_VOLUME 0xBE +#define M98090_REG_DMIC4_VOLUME 0xBF +#define M98090_REG_DMIC34_BQ_PREATTEN 0xC0 +#define M98090_REG_RECORD_TDM_SLOT 0xC1 +#define M98090_REG_SAMPLE_RATE 0xC2 +#define M98090_REG_DMIC34_BIQUAD_BASE 0xC3 +#define M98090_REG_REVISION_ID 0xFF + +#define M98090_REG_CNT (0xFF+1) +#define MAX98090_MAX_REGISTER 0xFF + +/* MAX98090 Register Bit Fields */ + +/* + * M98090_REG_SOFTWARE_RESET + */ +#define M98090_SWRESET_MASK (1<<7) +#define M98090_SWRESET_SHIFT 7 +#define M98090_SWRESET_WIDTH 1 + +/* + * M98090_REG_DEVICE_STATUS + */ +#define M98090_CLD_MASK (1<<7) +#define M98090_CLD_SHIFT 7 +#define M98090_CLD_WIDTH 1 +#define M98090_SLD_MASK (1<<6) +#define M98090_SLD_SHIFT 6 +#define M98090_SLD_WIDTH 1 +#define M98090_ULK_MASK (1<<5) +#define M98090_ULK_SHIFT 5 +#define M98090_ULK_WIDTH 1 +#define M98090_JDET_MASK (1<<2) +#define M98090_JDET_SHIFT 2 +#define M98090_JDET_WIDTH 1 +#define M98090_DRCACT_MASK (1<<1) +#define M98090_DRCACT_SHIFT 1 +#define M98090_DRCACT_WIDTH 1 +#define M98090_DRCCLP_MASK (1<<0) +#define M98090_DRCCLP_SHIFT 0 +#define M98090_DRCCLP_WIDTH 1 + +/* + * M98090_REG_JACK_STATUS + */ +#define M98090_LSNS_MASK (1<<2) +#define M98090_LSNS_SHIFT 2 +#define M98090_LSNS_WIDTH 1 +#define M98090_JKSNS_MASK (1<<1) +#define M98090_JKSNS_SHIFT 1 +#define M98090_JKSNS_WIDTH 1 + +/* + * M98090_REG_INTERRUPT_S + */ +#define M98090_ICLD_MASK (1<<7) +#define M98090_ICLD_SHIFT 7 +#define M98090_ICLD_WIDTH 1 +#define M98090_ISLD_MASK (1<<6) +#define M98090_ISLD_SHIFT 6 +#define M98090_ISLD_WIDTH 1 +#define M98090_IULK_MASK (1<<5) +#define M98090_IULK_SHIFT 5 +#define M98090_IULK_WIDTH 1 +#define M98090_IJDET_MASK (1<<2) +#define M98090_IJDET_SHIFT 2 +#define M98090_IJDET_WIDTH 1 +#define M98090_IDRCACT_MASK (1<<1) +#define M98090_IDRCACT_SHIFT 1 +#define M98090_IDRCACT_WIDTH 1 +#define M98090_IDRCCLP_MASK (1<<0) +#define M98090_IDRCCLP_SHIFT 0 +#define M98090_IDRCCLP_WIDTH 1 + +/* + * M98090_REG_QUICK_SYSTEM_CLOCK + */ +#define M98090_26M_MASK (1<<7) +#define M98090_26M_SHIFT 7 +#define M98090_26M_WIDTH 1 +#define M98090_19P2M_MASK (1<<6) +#define M98090_19P2M_SHIFT 6 +#define M98090_19P2M_WIDTH 1 +#define M98090_13M_MASK (1<<5) +#define M98090_13M_SHIFT 5 +#define M98090_13M_WIDTH 1 +#define M98090_12P288M_MASK (1<<4) +#define M98090_12P288M_SHIFT 4 +#define M98090_12P288M_WIDTH 1 +#define M98090_12M_MASK (1<<3) +#define M98090_12M_SHIFT 3 +#define M98090_12M_WIDTH 1 +#define M98090_11P2896M_MASK (1<<2) +#define M98090_11P2896M_SHIFT 2 +#define M98090_11P2896M_WIDTH 1 +#define M98090_256FS_MASK (1<<0) +#define M98090_256FS_SHIFT 0 +#define M98090_256FS_WIDTH 1 +#define M98090_CLK_ALL_SHIFT 0 +#define M98090_CLK_ALL_WIDTH 8 +#define M98090_CLK_ALL_NUM (1< +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98095.h" + +enum max98095_type { + MAX98095, +}; + +struct max98095_cdata { + unsigned int rate; + unsigned int fmt; + int eq_sel; + int bq_sel; +}; + +struct max98095_priv { + struct regmap *regmap; + enum max98095_type devtype; + struct max98095_pdata *pdata; + struct clk *mclk; + unsigned int sysclk; + struct max98095_cdata dai[3]; + const char **eq_texts; + const char **bq_texts; + struct soc_enum eq_enum; + struct soc_enum bq_enum; + int eq_textcnt; + int bq_textcnt; + u8 lin_state; + unsigned int mic1pre; + unsigned int mic2pre; + struct snd_soc_jack *headphone_jack; + struct snd_soc_jack *mic_jack; + struct mutex lock; +}; + +static const struct reg_default max98095_reg_def[] = { + { 0xf, 0x00 }, /* 0F */ + { 0x10, 0x00 }, /* 10 */ + { 0x11, 0x00 }, /* 11 */ + { 0x12, 0x00 }, /* 12 */ + { 0x13, 0x00 }, /* 13 */ + { 0x14, 0x00 }, /* 14 */ + { 0x15, 0x00 }, /* 15 */ + { 0x16, 0x00 }, /* 16 */ + { 0x17, 0x00 }, /* 17 */ + { 0x18, 0x00 }, /* 18 */ + { 0x19, 0x00 }, /* 19 */ + { 0x1a, 0x00 }, /* 1A */ + { 0x1b, 0x00 }, /* 1B */ + { 0x1c, 0x00 }, /* 1C */ + { 0x1d, 0x00 }, /* 1D */ + { 0x1e, 0x00 }, /* 1E */ + { 0x1f, 0x00 }, /* 1F */ + { 0x20, 0x00 }, /* 20 */ + { 0x21, 0x00 }, /* 21 */ + { 0x22, 0x00 }, /* 22 */ + { 0x23, 0x00 }, /* 23 */ + { 0x24, 0x00 }, /* 24 */ + { 0x25, 0x00 }, /* 25 */ + { 0x26, 0x00 }, /* 26 */ + { 0x27, 0x00 }, /* 27 */ + { 0x28, 0x00 }, /* 28 */ + { 0x29, 0x00 }, /* 29 */ + { 0x2a, 0x00 }, /* 2A */ + { 0x2b, 0x00 }, /* 2B */ + { 0x2c, 0x00 }, /* 2C */ + { 0x2d, 0x00 }, /* 2D */ + { 0x2e, 0x00 }, /* 2E */ + { 0x2f, 0x00 }, /* 2F */ + { 0x30, 0x00 }, /* 30 */ + { 0x31, 0x00 }, /* 31 */ + { 0x32, 0x00 }, /* 32 */ + { 0x33, 0x00 }, /* 33 */ + { 0x34, 0x00 }, /* 34 */ + { 0x35, 0x00 }, /* 35 */ + { 0x36, 0x00 }, /* 36 */ + { 0x37, 0x00 }, /* 37 */ + { 0x38, 0x00 }, /* 38 */ + { 0x39, 0x00 }, /* 39 */ + { 0x3a, 0x00 }, /* 3A */ + { 0x3b, 0x00 }, /* 3B */ + { 0x3c, 0x00 }, /* 3C */ + { 0x3d, 0x00 }, /* 3D */ + { 0x3e, 0x00 }, /* 3E */ + { 0x3f, 0x00 }, /* 3F */ + { 0x40, 0x00 }, /* 40 */ + { 0x41, 0x00 }, /* 41 */ + { 0x42, 0x00 }, /* 42 */ + { 0x43, 0x00 }, /* 43 */ + { 0x44, 0x00 }, /* 44 */ + { 0x45, 0x00 }, /* 45 */ + { 0x46, 0x00 }, /* 46 */ + { 0x47, 0x00 }, /* 47 */ + { 0x48, 0x00 }, /* 48 */ + { 0x49, 0x00 }, /* 49 */ + { 0x4a, 0x00 }, /* 4A */ + { 0x4b, 0x00 }, /* 4B */ + { 0x4c, 0x00 }, /* 4C */ + { 0x4d, 0x00 }, /* 4D */ + { 0x4e, 0x00 }, /* 4E */ + { 0x4f, 0x00 }, /* 4F */ + { 0x50, 0x00 }, /* 50 */ + { 0x51, 0x00 }, /* 51 */ + { 0x52, 0x00 }, /* 52 */ + { 0x53, 0x00 }, /* 53 */ + { 0x54, 0x00 }, /* 54 */ + { 0x55, 0x00 }, /* 55 */ + { 0x56, 0x00 }, /* 56 */ + { 0x57, 0x00 }, /* 57 */ + { 0x58, 0x00 }, /* 58 */ + { 0x59, 0x00 }, /* 59 */ + { 0x5a, 0x00 }, /* 5A */ + { 0x5b, 0x00 }, /* 5B */ + { 0x5c, 0x00 }, /* 5C */ + { 0x5d, 0x00 }, /* 5D */ + { 0x5e, 0x00 }, /* 5E */ + { 0x5f, 0x00 }, /* 5F */ + { 0x60, 0x00 }, /* 60 */ + { 0x61, 0x00 }, /* 61 */ + { 0x62, 0x00 }, /* 62 */ + { 0x63, 0x00 }, /* 63 */ + { 0x64, 0x00 }, /* 64 */ + { 0x65, 0x00 }, /* 65 */ + { 0x66, 0x00 }, /* 66 */ + { 0x67, 0x00 }, /* 67 */ + { 0x68, 0x00 }, /* 68 */ + { 0x69, 0x00 }, /* 69 */ + { 0x6a, 0x00 }, /* 6A */ + { 0x6b, 0x00 }, /* 6B */ + { 0x6c, 0x00 }, /* 6C */ + { 0x6d, 0x00 }, /* 6D */ + { 0x6e, 0x00 }, /* 6E */ + { 0x6f, 0x00 }, /* 6F */ + { 0x70, 0x00 }, /* 70 */ + { 0x71, 0x00 }, /* 71 */ + { 0x72, 0x00 }, /* 72 */ + { 0x73, 0x00 }, /* 73 */ + { 0x74, 0x00 }, /* 74 */ + { 0x75, 0x00 }, /* 75 */ + { 0x76, 0x00 }, /* 76 */ + { 0x77, 0x00 }, /* 77 */ + { 0x78, 0x00 }, /* 78 */ + { 0x79, 0x00 }, /* 79 */ + { 0x7a, 0x00 }, /* 7A */ + { 0x7b, 0x00 }, /* 7B */ + { 0x7c, 0x00 }, /* 7C */ + { 0x7d, 0x00 }, /* 7D */ + { 0x7e, 0x00 }, /* 7E */ + { 0x7f, 0x00 }, /* 7F */ + { 0x80, 0x00 }, /* 80 */ + { 0x81, 0x00 }, /* 81 */ + { 0x82, 0x00 }, /* 82 */ + { 0x83, 0x00 }, /* 83 */ + { 0x84, 0x00 }, /* 84 */ + { 0x85, 0x00 }, /* 85 */ + { 0x86, 0x00 }, /* 86 */ + { 0x87, 0x00 }, /* 87 */ + { 0x88, 0x00 }, /* 88 */ + { 0x89, 0x00 }, /* 89 */ + { 0x8a, 0x00 }, /* 8A */ + { 0x8b, 0x00 }, /* 8B */ + { 0x8c, 0x00 }, /* 8C */ + { 0x8d, 0x00 }, /* 8D */ + { 0x8e, 0x00 }, /* 8E */ + { 0x8f, 0x00 }, /* 8F */ + { 0x90, 0x00 }, /* 90 */ + { 0x91, 0x00 }, /* 91 */ + { 0x92, 0x30 }, /* 92 */ + { 0x93, 0xF0 }, /* 93 */ + { 0x94, 0x00 }, /* 94 */ + { 0x95, 0x00 }, /* 95 */ + { 0x96, 0x3F }, /* 96 */ + { 0x97, 0x00 }, /* 97 */ + { 0xff, 0x00 }, /* FF */ +}; + +static bool max98095_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98095_001_HOST_INT_STS ... M98095_097_PWR_SYS: + case M98095_0FF_REV_ID: + return true; + default: + return false; + } +} + +static bool max98095_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98095_00F_HOST_CFG ... M98095_097_PWR_SYS: + return true; + default: + return false; + } +} + +static bool max98095_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case M98095_000_HOST_DATA ... M98095_00E_TEMP_SENSOR_STS: + case M98095_REG_MAX_CACHED + 1 ... M98095_0FF_REV_ID: + return true; + default: + return false; + } +} + +static const struct regmap_config max98095_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .reg_defaults = max98095_reg_def, + .num_reg_defaults = ARRAY_SIZE(max98095_reg_def), + .max_register = M98095_0FF_REV_ID, + .cache_type = REGCACHE_RBTREE, + + .readable_reg = max98095_readable, + .writeable_reg = max98095_writeable, + .volatile_reg = max98095_volatile, +}; + +/* + * Load equalizer DSP coefficient configurations registers + */ +static void m98095_eq_band(struct snd_soc_component *component, unsigned int dai, + unsigned int band, u16 *coefs) +{ + unsigned int eq_reg; + unsigned int i; + + if (WARN_ON(band > 4) || + WARN_ON(dai > 1)) + return; + + /* Load the base register address */ + eq_reg = dai ? M98095_142_DAI2_EQ_BASE : M98095_110_DAI1_EQ_BASE; + + /* Add the band address offset, note adjustment for word address */ + eq_reg += band * (M98095_COEFS_PER_BAND << 1); + + /* Step through the registers and coefs */ + for (i = 0; i < M98095_COEFS_PER_BAND; i++) { + snd_soc_component_write(component, eq_reg++, M98095_BYTE1(coefs[i])); + snd_soc_component_write(component, eq_reg++, M98095_BYTE0(coefs[i])); + } +} + +/* + * Load biquad filter coefficient configurations registers + */ +static void m98095_biquad_band(struct snd_soc_component *component, unsigned int dai, + unsigned int band, u16 *coefs) +{ + unsigned int bq_reg; + unsigned int i; + + if (WARN_ON(band > 1) || + WARN_ON(dai > 1)) + return; + + /* Load the base register address */ + bq_reg = dai ? M98095_17E_DAI2_BQ_BASE : M98095_174_DAI1_BQ_BASE; + + /* Add the band address offset, note adjustment for word address */ + bq_reg += band * (M98095_COEFS_PER_BAND << 1); + + /* Step through the registers and coefs */ + for (i = 0; i < M98095_COEFS_PER_BAND; i++) { + snd_soc_component_write(component, bq_reg++, M98095_BYTE1(coefs[i])); + snd_soc_component_write(component, bq_reg++, M98095_BYTE0(coefs[i])); + } +} + +static const char * const max98095_fltr_mode[] = { "Voice", "Music" }; +static SOC_ENUM_SINGLE_DECL(max98095_dai1_filter_mode_enum, + M98095_02E_DAI1_FILTERS, 7, + max98095_fltr_mode); +static SOC_ENUM_SINGLE_DECL(max98095_dai2_filter_mode_enum, + M98095_038_DAI2_FILTERS, 7, + max98095_fltr_mode); + +static const char * const max98095_extmic_text[] = { "None", "MIC1", "MIC2" }; + +static SOC_ENUM_SINGLE_DECL(max98095_extmic_enum, + M98095_087_CFG_MIC, 0, + max98095_extmic_text); + +static const struct snd_kcontrol_new max98095_extmic_mux = + SOC_DAPM_ENUM("External MIC Mux", max98095_extmic_enum); + +static const char * const max98095_linein_text[] = { "INA", "INB" }; + +static SOC_ENUM_SINGLE_DECL(max98095_linein_enum, + M98095_086_CFG_LINE, 6, + max98095_linein_text); + +static const struct snd_kcontrol_new max98095_linein_mux = + SOC_DAPM_ENUM("Linein Input Mux", max98095_linein_enum); + +static const char * const max98095_line_mode_text[] = { + "Stereo", "Differential"}; + +static SOC_ENUM_SINGLE_DECL(max98095_linein_mode_enum, + M98095_086_CFG_LINE, 7, + max98095_line_mode_text); + +static SOC_ENUM_SINGLE_DECL(max98095_lineout_mode_enum, + M98095_086_CFG_LINE, 4, + max98095_line_mode_text); + +static const char * const max98095_dai_fltr[] = { + "Off", "Elliptical-HPF-16k", "Butterworth-HPF-16k", + "Elliptical-HPF-8k", "Butterworth-HPF-8k", "Butterworth-HPF-Fs/240"}; +static SOC_ENUM_SINGLE_DECL(max98095_dai1_dac_filter_enum, + M98095_02E_DAI1_FILTERS, 0, + max98095_dai_fltr); +static SOC_ENUM_SINGLE_DECL(max98095_dai2_dac_filter_enum, + M98095_038_DAI2_FILTERS, 0, + max98095_dai_fltr); +static SOC_ENUM_SINGLE_DECL(max98095_dai3_dac_filter_enum, + M98095_042_DAI3_FILTERS, 0, + max98095_dai_fltr); + +static int max98095_mic1pre_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + unsigned int sel = ucontrol->value.integer.value[0]; + + max98095->mic1pre = sel; + snd_soc_component_update_bits(component, M98095_05F_LVL_MIC1, M98095_MICPRE_MASK, + (1+sel)<value.integer.value[0] = max98095->mic1pre; + return 0; +} + +static int max98095_mic2pre_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + unsigned int sel = ucontrol->value.integer.value[0]; + + max98095->mic2pre = sel; + snd_soc_component_update_bits(component, M98095_060_LVL_MIC2, M98095_MICPRE_MASK, + (1+sel)<value.integer.value[0] = max98095->mic2pre; + return 0; +} + +static const DECLARE_TLV_DB_RANGE(max98095_micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0), + 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0) +); + +static const DECLARE_TLV_DB_SCALE(max98095_mic_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(max98095_adc_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(max98095_adcboost_tlv, 0, 600, 0); + +static const DECLARE_TLV_DB_RANGE(max98095_hp_tlv, + 0, 6, TLV_DB_SCALE_ITEM(-6700, 400, 0), + 7, 14, TLV_DB_SCALE_ITEM(-4000, 300, 0), + 15, 21, TLV_DB_SCALE_ITEM(-1700, 200, 0), + 22, 27, TLV_DB_SCALE_ITEM(-400, 100, 0), + 28, 31, TLV_DB_SCALE_ITEM(150, 50, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98095_spk_tlv, + 0, 10, TLV_DB_SCALE_ITEM(-5900, 400, 0), + 11, 18, TLV_DB_SCALE_ITEM(-1700, 200, 0), + 19, 27, TLV_DB_SCALE_ITEM(-200, 100, 0), + 28, 39, TLV_DB_SCALE_ITEM(650, 50, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98095_rcv_lout_tlv, + 0, 6, TLV_DB_SCALE_ITEM(-6200, 400, 0), + 7, 14, TLV_DB_SCALE_ITEM(-3500, 300, 0), + 15, 21, TLV_DB_SCALE_ITEM(-1200, 200, 0), + 22, 27, TLV_DB_SCALE_ITEM(100, 100, 0), + 28, 31, TLV_DB_SCALE_ITEM(650, 50, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98095_lin_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-600, 300, 0), + 3, 3, TLV_DB_SCALE_ITEM(300, 1100, 0), + 4, 5, TLV_DB_SCALE_ITEM(1400, 600, 0) +); + +static const struct snd_kcontrol_new max98095_snd_controls[] = { + + SOC_DOUBLE_R_TLV("Headphone Volume", M98095_064_LVL_HP_L, + M98095_065_LVL_HP_R, 0, 31, 0, max98095_hp_tlv), + + SOC_DOUBLE_R_TLV("Speaker Volume", M98095_067_LVL_SPK_L, + M98095_068_LVL_SPK_R, 0, 39, 0, max98095_spk_tlv), + + SOC_SINGLE_TLV("Receiver Volume", M98095_066_LVL_RCV, + 0, 31, 0, max98095_rcv_lout_tlv), + + SOC_DOUBLE_R_TLV("Lineout Volume", M98095_062_LVL_LINEOUT1, + M98095_063_LVL_LINEOUT2, 0, 31, 0, max98095_rcv_lout_tlv), + + SOC_DOUBLE_R("Headphone Switch", M98095_064_LVL_HP_L, + M98095_065_LVL_HP_R, 7, 1, 1), + + SOC_DOUBLE_R("Speaker Switch", M98095_067_LVL_SPK_L, + M98095_068_LVL_SPK_R, 7, 1, 1), + + SOC_SINGLE("Receiver Switch", M98095_066_LVL_RCV, 7, 1, 1), + + SOC_DOUBLE_R("Lineout Switch", M98095_062_LVL_LINEOUT1, + M98095_063_LVL_LINEOUT2, 7, 1, 1), + + SOC_SINGLE_TLV("MIC1 Volume", M98095_05F_LVL_MIC1, 0, 20, 1, + max98095_mic_tlv), + + SOC_SINGLE_TLV("MIC2 Volume", M98095_060_LVL_MIC2, 0, 20, 1, + max98095_mic_tlv), + + SOC_SINGLE_EXT_TLV("MIC1 Boost Volume", + M98095_05F_LVL_MIC1, 5, 2, 0, + max98095_mic1pre_get, max98095_mic1pre_set, + max98095_micboost_tlv), + SOC_SINGLE_EXT_TLV("MIC2 Boost Volume", + M98095_060_LVL_MIC2, 5, 2, 0, + max98095_mic2pre_get, max98095_mic2pre_set, + max98095_micboost_tlv), + + SOC_SINGLE_TLV("Linein Volume", M98095_061_LVL_LINEIN, 0, 5, 1, + max98095_lin_tlv), + + SOC_SINGLE_TLV("ADCL Volume", M98095_05D_LVL_ADC_L, 0, 15, 1, + max98095_adc_tlv), + SOC_SINGLE_TLV("ADCR Volume", M98095_05E_LVL_ADC_R, 0, 15, 1, + max98095_adc_tlv), + + SOC_SINGLE_TLV("ADCL Boost Volume", M98095_05D_LVL_ADC_L, 4, 3, 0, + max98095_adcboost_tlv), + SOC_SINGLE_TLV("ADCR Boost Volume", M98095_05E_LVL_ADC_R, 4, 3, 0, + max98095_adcboost_tlv), + + SOC_SINGLE("EQ1 Switch", M98095_088_CFG_LEVEL, 0, 1, 0), + SOC_SINGLE("EQ2 Switch", M98095_088_CFG_LEVEL, 1, 1, 0), + + SOC_SINGLE("Biquad1 Switch", M98095_088_CFG_LEVEL, 2, 1, 0), + SOC_SINGLE("Biquad2 Switch", M98095_088_CFG_LEVEL, 3, 1, 0), + + SOC_ENUM("DAI1 Filter Mode", max98095_dai1_filter_mode_enum), + SOC_ENUM("DAI2 Filter Mode", max98095_dai2_filter_mode_enum), + SOC_ENUM("DAI1 DAC Filter", max98095_dai1_dac_filter_enum), + SOC_ENUM("DAI2 DAC Filter", max98095_dai2_dac_filter_enum), + SOC_ENUM("DAI3 DAC Filter", max98095_dai3_dac_filter_enum), + + SOC_ENUM("Linein Mode", max98095_linein_mode_enum), + SOC_ENUM("Lineout Mode", max98095_lineout_mode_enum), +}; + +/* Left speaker mixer switch */ +static const struct snd_kcontrol_new max98095_left_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_050_MIX_SPK_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_050_MIX_SPK_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("Mono DAC2 Switch", M98095_050_MIX_SPK_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("Mono DAC3 Switch", M98095_050_MIX_SPK_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98095_050_MIX_SPK_LEFT, 4, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_050_MIX_SPK_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_050_MIX_SPK_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_050_MIX_SPK_LEFT, 2, 1, 0), +}; + +/* Right speaker mixer switch */ +static const struct snd_kcontrol_new max98095_right_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_051_MIX_SPK_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_051_MIX_SPK_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("Mono DAC2 Switch", M98095_051_MIX_SPK_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("Mono DAC3 Switch", M98095_051_MIX_SPK_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98095_051_MIX_SPK_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_051_MIX_SPK_RIGHT, 4, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_051_MIX_SPK_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_051_MIX_SPK_RIGHT, 2, 1, 0), +}; + +/* Left headphone mixer switch */ +static const struct snd_kcontrol_new max98095_left_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_04C_MIX_HP_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_04C_MIX_HP_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98095_04C_MIX_HP_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_04C_MIX_HP_LEFT, 4, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_04C_MIX_HP_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_04C_MIX_HP_LEFT, 2, 1, 0), +}; + +/* Right headphone mixer switch */ +static const struct snd_kcontrol_new max98095_right_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_04D_MIX_HP_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_04D_MIX_HP_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98095_04D_MIX_HP_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_04D_MIX_HP_RIGHT, 4, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_04D_MIX_HP_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_04D_MIX_HP_RIGHT, 2, 1, 0), +}; + +/* Receiver earpiece mixer switch */ +static const struct snd_kcontrol_new max98095_mono_rcv_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_04F_MIX_RCV, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_04F_MIX_RCV, 5, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98095_04F_MIX_RCV, 3, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_04F_MIX_RCV, 4, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_04F_MIX_RCV, 1, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_04F_MIX_RCV, 2, 1, 0), +}; + +/* Left lineout mixer switch */ +static const struct snd_kcontrol_new max98095_left_lineout_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_053_MIX_LINEOUT1, 5, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_053_MIX_LINEOUT1, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98095_053_MIX_LINEOUT1, 3, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_053_MIX_LINEOUT1, 4, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_053_MIX_LINEOUT1, 1, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_053_MIX_LINEOUT1, 2, 1, 0), +}; + +/* Right lineout mixer switch */ +static const struct snd_kcontrol_new max98095_right_lineout_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1 Switch", M98095_054_MIX_LINEOUT2, 0, 1, 0), + SOC_DAPM_SINGLE("Right DAC1 Switch", M98095_054_MIX_LINEOUT2, 5, 1, 0), + SOC_DAPM_SINGLE("MIC1 Switch", M98095_054_MIX_LINEOUT2, 3, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_054_MIX_LINEOUT2, 4, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_054_MIX_LINEOUT2, 1, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_054_MIX_LINEOUT2, 2, 1, 0), +}; + +/* Left ADC mixer switch */ +static const struct snd_kcontrol_new max98095_left_ADC_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1 Switch", M98095_04A_MIX_ADC_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_04A_MIX_ADC_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_04A_MIX_ADC_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_04A_MIX_ADC_LEFT, 2, 1, 0), +}; + +/* Right ADC mixer switch */ +static const struct snd_kcontrol_new max98095_right_ADC_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1 Switch", M98095_04B_MIX_ADC_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC2 Switch", M98095_04B_MIX_ADC_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("IN1 Switch", M98095_04B_MIX_ADC_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("IN2 Switch", M98095_04B_MIX_ADC_RIGHT, 2, 1, 0), +}; + +static int max98095_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (w->reg == M98095_05F_LVL_MIC1) { + snd_soc_component_update_bits(component, w->reg, M98095_MICPRE_MASK, + (1+max98095->mic1pre)<reg, M98095_MICPRE_MASK, + (1+max98095->mic2pre)<reg, M98095_MICPRE_MASK, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * The line inputs are stereo inputs with the left and right + * channels sharing a common PGA power control signal. + */ +static int max98095_line_pga(struct snd_soc_dapm_widget *w, + int event, u8 channel) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + u8 *state; + + if (WARN_ON(!(channel == 1 || channel == 2))) + return -EINVAL; + + state = &max98095->lin_state; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + *state |= channel; + snd_soc_component_update_bits(component, w->reg, + (1 << w->shift), (1 << w->shift)); + break; + case SND_SOC_DAPM_POST_PMD: + *state &= ~channel; + if (*state == 0) { + snd_soc_component_update_bits(component, w->reg, + (1 << w->shift), 0); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int max98095_pga_in1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return max98095_line_pga(w, event, 1); +} + +static int max98095_pga_in2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return max98095_line_pga(w, event, 2); +} + +/* + * The stereo line out mixer outputs to two stereo line outs. + * The 2nd pair has a separate set of enables. + */ +static int max98095_lineout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, w->reg, + (1 << (w->shift+2)), (1 << (w->shift+2))); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, w->reg, + (1 << (w->shift+2)), 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dapm_widget max98095_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98095_090_PWR_EN_IN, 0, 0), + SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98095_090_PWR_EN_IN, 1, 0), + + SND_SOC_DAPM_DAC("DACL1", "HiFi Playback", + M98095_091_PWR_EN_OUT, 0, 0), + SND_SOC_DAPM_DAC("DACR1", "HiFi Playback", + M98095_091_PWR_EN_OUT, 1, 0), + SND_SOC_DAPM_DAC("DACM2", "Aux Playback", + M98095_091_PWR_EN_OUT, 2, 0), + SND_SOC_DAPM_DAC("DACM3", "Voice Playback", + M98095_091_PWR_EN_OUT, 2, 0), + + SND_SOC_DAPM_PGA("HP Left Out", M98095_091_PWR_EN_OUT, + 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP Right Out", M98095_091_PWR_EN_OUT, + 7, 0, NULL, 0), + + SND_SOC_DAPM_PGA("SPK Left Out", M98095_091_PWR_EN_OUT, + 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPK Right Out", M98095_091_PWR_EN_OUT, + 5, 0, NULL, 0), + + SND_SOC_DAPM_PGA("RCV Mono Out", M98095_091_PWR_EN_OUT, + 3, 0, NULL, 0), + + SND_SOC_DAPM_PGA_E("LINE Left Out", M98095_092_PWR_EN_OUT, + 0, 0, NULL, 0, max98095_lineout_event, SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("LINE Right Out", M98095_092_PWR_EN_OUT, + 1, 0, NULL, 0, max98095_lineout_event, SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("External MIC", SND_SOC_NOPM, 0, 0, + &max98095_extmic_mux), + + SND_SOC_DAPM_MUX("Linein Mux", SND_SOC_NOPM, 0, 0, + &max98095_linein_mux), + + SND_SOC_DAPM_MIXER("Left Headphone Mixer", SND_SOC_NOPM, 0, 0, + &max98095_left_hp_mixer_controls[0], + ARRAY_SIZE(max98095_left_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Headphone Mixer", SND_SOC_NOPM, 0, 0, + &max98095_right_hp_mixer_controls[0], + ARRAY_SIZE(max98095_right_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Speaker Mixer", SND_SOC_NOPM, 0, 0, + &max98095_left_speaker_mixer_controls[0], + ARRAY_SIZE(max98095_left_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Speaker Mixer", SND_SOC_NOPM, 0, 0, + &max98095_right_speaker_mixer_controls[0], + ARRAY_SIZE(max98095_right_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Receiver Mixer", SND_SOC_NOPM, 0, 0, + &max98095_mono_rcv_mixer_controls[0], + ARRAY_SIZE(max98095_mono_rcv_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Lineout Mixer", SND_SOC_NOPM, 0, 0, + &max98095_left_lineout_mixer_controls[0], + ARRAY_SIZE(max98095_left_lineout_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Lineout Mixer", SND_SOC_NOPM, 0, 0, + &max98095_right_lineout_mixer_controls[0], + ARRAY_SIZE(max98095_right_lineout_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98095_left_ADC_mixer_controls[0], + ARRAY_SIZE(max98095_left_ADC_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98095_right_ADC_mixer_controls[0], + ARRAY_SIZE(max98095_right_ADC_mixer_controls)), + + SND_SOC_DAPM_PGA_E("MIC1 Input", M98095_05F_LVL_MIC1, + 5, 0, NULL, 0, max98095_mic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("MIC2 Input", M98095_060_LVL_MIC2, + 5, 0, NULL, 0, max98095_mic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("IN1 Input", M98095_090_PWR_EN_IN, + 7, 0, NULL, 0, max98095_pga_in1_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("IN2 Input", M98095_090_PWR_EN_IN, + 7, 0, NULL, 0, max98095_pga_in2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MICBIAS("MICBIAS1", M98095_090_PWR_EN_IN, 2, 0), + SND_SOC_DAPM_MICBIAS("MICBIAS2", M98095_090_PWR_EN_IN, 3, 0), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("RCV"), + SND_SOC_DAPM_OUTPUT("OUT1"), + SND_SOC_DAPM_OUTPUT("OUT2"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("OUT4"), + + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("INA1"), + SND_SOC_DAPM_INPUT("INA2"), + SND_SOC_DAPM_INPUT("INB1"), + SND_SOC_DAPM_INPUT("INB2"), +}; + +static const struct snd_soc_dapm_route max98095_audio_map[] = { + /* Left headphone output mixer */ + {"Left Headphone Mixer", "Left DAC1 Switch", "DACL1"}, + {"Left Headphone Mixer", "Right DAC1 Switch", "DACR1"}, + {"Left Headphone Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left Headphone Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left Headphone Mixer", "IN1 Switch", "IN1 Input"}, + {"Left Headphone Mixer", "IN2 Switch", "IN2 Input"}, + + /* Right headphone output mixer */ + {"Right Headphone Mixer", "Left DAC1 Switch", "DACL1"}, + {"Right Headphone Mixer", "Right DAC1 Switch", "DACR1"}, + {"Right Headphone Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right Headphone Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right Headphone Mixer", "IN1 Switch", "IN1 Input"}, + {"Right Headphone Mixer", "IN2 Switch", "IN2 Input"}, + + /* Left speaker output mixer */ + {"Left Speaker Mixer", "Left DAC1 Switch", "DACL1"}, + {"Left Speaker Mixer", "Right DAC1 Switch", "DACR1"}, + {"Left Speaker Mixer", "Mono DAC2 Switch", "DACM2"}, + {"Left Speaker Mixer", "Mono DAC3 Switch", "DACM3"}, + {"Left Speaker Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left Speaker Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left Speaker Mixer", "IN1 Switch", "IN1 Input"}, + {"Left Speaker Mixer", "IN2 Switch", "IN2 Input"}, + + /* Right speaker output mixer */ + {"Right Speaker Mixer", "Left DAC1 Switch", "DACL1"}, + {"Right Speaker Mixer", "Right DAC1 Switch", "DACR1"}, + {"Right Speaker Mixer", "Mono DAC2 Switch", "DACM2"}, + {"Right Speaker Mixer", "Mono DAC3 Switch", "DACM3"}, + {"Right Speaker Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right Speaker Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right Speaker Mixer", "IN1 Switch", "IN1 Input"}, + {"Right Speaker Mixer", "IN2 Switch", "IN2 Input"}, + + /* Earpiece/Receiver output mixer */ + {"Receiver Mixer", "Left DAC1 Switch", "DACL1"}, + {"Receiver Mixer", "Right DAC1 Switch", "DACR1"}, + {"Receiver Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Receiver Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Receiver Mixer", "IN1 Switch", "IN1 Input"}, + {"Receiver Mixer", "IN2 Switch", "IN2 Input"}, + + /* Left Lineout output mixer */ + {"Left Lineout Mixer", "Left DAC1 Switch", "DACL1"}, + {"Left Lineout Mixer", "Right DAC1 Switch", "DACR1"}, + {"Left Lineout Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left Lineout Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left Lineout Mixer", "IN1 Switch", "IN1 Input"}, + {"Left Lineout Mixer", "IN2 Switch", "IN2 Input"}, + + /* Right lineout output mixer */ + {"Right Lineout Mixer", "Left DAC1 Switch", "DACL1"}, + {"Right Lineout Mixer", "Right DAC1 Switch", "DACR1"}, + {"Right Lineout Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right Lineout Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right Lineout Mixer", "IN1 Switch", "IN1 Input"}, + {"Right Lineout Mixer", "IN2 Switch", "IN2 Input"}, + + {"HP Left Out", NULL, "Left Headphone Mixer"}, + {"HP Right Out", NULL, "Right Headphone Mixer"}, + {"SPK Left Out", NULL, "Left Speaker Mixer"}, + {"SPK Right Out", NULL, "Right Speaker Mixer"}, + {"RCV Mono Out", NULL, "Receiver Mixer"}, + {"LINE Left Out", NULL, "Left Lineout Mixer"}, + {"LINE Right Out", NULL, "Right Lineout Mixer"}, + + {"HPL", NULL, "HP Left Out"}, + {"HPR", NULL, "HP Right Out"}, + {"SPKL", NULL, "SPK Left Out"}, + {"SPKR", NULL, "SPK Right Out"}, + {"RCV", NULL, "RCV Mono Out"}, + {"OUT1", NULL, "LINE Left Out"}, + {"OUT2", NULL, "LINE Right Out"}, + {"OUT3", NULL, "LINE Left Out"}, + {"OUT4", NULL, "LINE Right Out"}, + + /* Left ADC input mixer */ + {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Left ADC Mixer", "IN1 Switch", "IN1 Input"}, + {"Left ADC Mixer", "IN2 Switch", "IN2 Input"}, + + /* Right ADC input mixer */ + {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"}, + {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"}, + {"Right ADC Mixer", "IN1 Switch", "IN1 Input"}, + {"Right ADC Mixer", "IN2 Switch", "IN2 Input"}, + + /* Inputs */ + {"ADCL", NULL, "Left ADC Mixer"}, + {"ADCR", NULL, "Right ADC Mixer"}, + + {"IN1 Input", NULL, "INA1"}, + {"IN2 Input", NULL, "INA2"}, + + {"MIC1 Input", NULL, "MIC1"}, + {"MIC2 Input", NULL, "MIC2"}, +}; + +/* codec mclk clock divider coefficients */ +static const struct { + u32 rate; + u8 sr; +} rate_table[] = { + {8000, 0x01}, + {11025, 0x02}, + {16000, 0x03}, + {22050, 0x04}, + {24000, 0x05}, + {32000, 0x06}, + {44100, 0x07}, + {48000, 0x08}, + {88200, 0x09}, + {96000, 0x0A}, +}; + +static int rate_value(int rate, u8 *value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i].rate >= rate) { + *value = rate_table[i].sr; + return 0; + } + } + *value = rate_table[0].sr; + return -EINVAL; +} + +static int max98095_dai1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_cdata *cdata; + unsigned long long ni; + unsigned int rate; + u8 regval; + + cdata = &max98095->dai[0]; + + rate = params_rate(params); + + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(component, M98095_02A_DAI1_FORMAT, + M98095_DAI_WS, 0); + break; + case 24: + snd_soc_component_update_bits(component, M98095_02A_DAI1_FORMAT, + M98095_DAI_WS, M98095_DAI_WS); + break; + default: + return -EINVAL; + } + + if (rate_value(rate, ®val)) + return -EINVAL; + + snd_soc_component_update_bits(component, M98095_027_DAI1_CLKMODE, + M98095_CLKMODE_MASK, regval); + cdata->rate = rate; + + /* Configure NI when operating as master */ + if (snd_soc_component_read32(component, M98095_02A_DAI1_FORMAT) & M98095_DAI_MAS) { + if (max98095->sysclk == 0) { + dev_err(component->dev, "Invalid system clock frequency\n"); + return -EINVAL; + } + ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate; + do_div(ni, (unsigned long long int)max98095->sysclk); + snd_soc_component_write(component, M98095_028_DAI1_CLKCFG_HI, + (ni >> 8) & 0x7F); + snd_soc_component_write(component, M98095_029_DAI1_CLKCFG_LO, + ni & 0xFF); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_component_update_bits(component, M98095_02E_DAI1_FILTERS, + M98095_DAI_DHF, 0); + else + snd_soc_component_update_bits(component, M98095_02E_DAI1_FILTERS, + M98095_DAI_DHF, M98095_DAI_DHF); + + return 0; +} + +static int max98095_dai2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_cdata *cdata; + unsigned long long ni; + unsigned int rate; + u8 regval; + + cdata = &max98095->dai[1]; + + rate = params_rate(params); + + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(component, M98095_034_DAI2_FORMAT, + M98095_DAI_WS, 0); + break; + case 24: + snd_soc_component_update_bits(component, M98095_034_DAI2_FORMAT, + M98095_DAI_WS, M98095_DAI_WS); + break; + default: + return -EINVAL; + } + + if (rate_value(rate, ®val)) + return -EINVAL; + + snd_soc_component_update_bits(component, M98095_031_DAI2_CLKMODE, + M98095_CLKMODE_MASK, regval); + cdata->rate = rate; + + /* Configure NI when operating as master */ + if (snd_soc_component_read32(component, M98095_034_DAI2_FORMAT) & M98095_DAI_MAS) { + if (max98095->sysclk == 0) { + dev_err(component->dev, "Invalid system clock frequency\n"); + return -EINVAL; + } + ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate; + do_div(ni, (unsigned long long int)max98095->sysclk); + snd_soc_component_write(component, M98095_032_DAI2_CLKCFG_HI, + (ni >> 8) & 0x7F); + snd_soc_component_write(component, M98095_033_DAI2_CLKCFG_LO, + ni & 0xFF); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_component_update_bits(component, M98095_038_DAI2_FILTERS, + M98095_DAI_DHF, 0); + else + snd_soc_component_update_bits(component, M98095_038_DAI2_FILTERS, + M98095_DAI_DHF, M98095_DAI_DHF); + + return 0; +} + +static int max98095_dai3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_cdata *cdata; + unsigned long long ni; + unsigned int rate; + u8 regval; + + cdata = &max98095->dai[2]; + + rate = params_rate(params); + + switch (params_width(params)) { + case 16: + snd_soc_component_update_bits(component, M98095_03E_DAI3_FORMAT, + M98095_DAI_WS, 0); + break; + case 24: + snd_soc_component_update_bits(component, M98095_03E_DAI3_FORMAT, + M98095_DAI_WS, M98095_DAI_WS); + break; + default: + return -EINVAL; + } + + if (rate_value(rate, ®val)) + return -EINVAL; + + snd_soc_component_update_bits(component, M98095_03B_DAI3_CLKMODE, + M98095_CLKMODE_MASK, regval); + cdata->rate = rate; + + /* Configure NI when operating as master */ + if (snd_soc_component_read32(component, M98095_03E_DAI3_FORMAT) & M98095_DAI_MAS) { + if (max98095->sysclk == 0) { + dev_err(component->dev, "Invalid system clock frequency\n"); + return -EINVAL; + } + ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate; + do_div(ni, (unsigned long long int)max98095->sysclk); + snd_soc_component_write(component, M98095_03C_DAI3_CLKCFG_HI, + (ni >> 8) & 0x7F); + snd_soc_component_write(component, M98095_03D_DAI3_CLKCFG_LO, + ni & 0xFF); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_component_update_bits(component, M98095_042_DAI3_FILTERS, + M98095_DAI_DHF, 0); + else + snd_soc_component_update_bits(component, M98095_042_DAI3_FILTERS, + M98095_DAI_DHF, M98095_DAI_DHF); + + return 0; +} + +static int max98095_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + + /* Requested clock frequency is already setup */ + if (freq == max98095->sysclk) + return 0; + + if (!IS_ERR(max98095->mclk)) { + freq = clk_round_rate(max98095->mclk, freq); + clk_set_rate(max98095->mclk, freq); + } + + /* Setup clocks for slave mode, and using the PLL + * PSCLK = 0x01 (when master clk is 10MHz to 20MHz) + * 0x02 (when master clk is 20MHz to 40MHz).. + * 0x03 (when master clk is 40MHz to 60MHz).. + */ + if ((freq >= 10000000) && (freq < 20000000)) { + snd_soc_component_write(component, M98095_026_SYS_CLK, 0x10); + } else if ((freq >= 20000000) && (freq < 40000000)) { + snd_soc_component_write(component, M98095_026_SYS_CLK, 0x20); + } else if ((freq >= 40000000) && (freq < 60000000)) { + snd_soc_component_write(component, M98095_026_SYS_CLK, 0x30); + } else { + dev_err(component->dev, "Invalid master clock frequency\n"); + return -EINVAL; + } + + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + + max98095->sysclk = freq; + return 0; +} + +static int max98095_dai1_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_cdata *cdata; + u8 regval = 0; + + cdata = &max98095->dai[0]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave mode PLL */ + snd_soc_component_write(component, M98095_028_DAI1_CLKCFG_HI, + 0x80); + snd_soc_component_write(component, M98095_029_DAI1_CLKCFG_LO, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set to master mode */ + regval |= M98095_DAI_MAS; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + regval |= M98095_DAI_DLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + regval |= M98095_DAI_WCI; + break; + case SND_SOC_DAIFMT_IB_NF: + regval |= M98095_DAI_BCI; + break; + case SND_SOC_DAIFMT_IB_IF: + regval |= M98095_DAI_BCI|M98095_DAI_WCI; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, M98095_02A_DAI1_FORMAT, + M98095_DAI_MAS | M98095_DAI_DLY | M98095_DAI_BCI | + M98095_DAI_WCI, regval); + + snd_soc_component_write(component, M98095_02B_DAI1_CLOCK, M98095_DAI_BSEL64); + } + + return 0; +} + +static int max98095_dai2_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_cdata *cdata; + u8 regval = 0; + + cdata = &max98095->dai[1]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave mode PLL */ + snd_soc_component_write(component, M98095_032_DAI2_CLKCFG_HI, + 0x80); + snd_soc_component_write(component, M98095_033_DAI2_CLKCFG_LO, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set to master mode */ + regval |= M98095_DAI_MAS; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + regval |= M98095_DAI_DLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + regval |= M98095_DAI_WCI; + break; + case SND_SOC_DAIFMT_IB_NF: + regval |= M98095_DAI_BCI; + break; + case SND_SOC_DAIFMT_IB_IF: + regval |= M98095_DAI_BCI|M98095_DAI_WCI; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, M98095_034_DAI2_FORMAT, + M98095_DAI_MAS | M98095_DAI_DLY | M98095_DAI_BCI | + M98095_DAI_WCI, regval); + + snd_soc_component_write(component, M98095_035_DAI2_CLOCK, + M98095_DAI_BSEL64); + } + + return 0; +} + +static int max98095_dai3_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_cdata *cdata; + u8 regval = 0; + + cdata = &max98095->dai[2]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Slave mode PLL */ + snd_soc_component_write(component, M98095_03C_DAI3_CLKCFG_HI, + 0x80); + snd_soc_component_write(component, M98095_03D_DAI3_CLKCFG_LO, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Set to master mode */ + regval |= M98095_DAI_MAS; + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "Clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + regval |= M98095_DAI_DLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + regval |= M98095_DAI_WCI; + break; + case SND_SOC_DAIFMT_IB_NF: + regval |= M98095_DAI_BCI; + break; + case SND_SOC_DAIFMT_IB_IF: + regval |= M98095_DAI_BCI|M98095_DAI_WCI; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, M98095_03E_DAI3_FORMAT, + M98095_DAI_MAS | M98095_DAI_DLY | M98095_DAI_BCI | + M98095_DAI_WCI, regval); + + snd_soc_component_write(component, M98095_03F_DAI3_CLOCK, + M98095_DAI_BSEL64); + } + + return 0; +} + +static int max98095_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* + * SND_SOC_BIAS_PREPARE is called while preparing for a + * transition to ON or away from ON. If current bias_level + * is SND_SOC_BIAS_ON, then it is preparing for a transition + * away from ON. Disable the clock in that case, otherwise + * enable it. + */ + if (IS_ERR(max98095->mclk)) + break; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(max98095->mclk); + } else { + ret = clk_prepare_enable(max98095->mclk); + if (ret) + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regcache_sync(max98095->regmap); + + if (ret != 0) { + dev_err(component->dev, "Failed to sync cache: %d\n", ret); + return ret; + } + } + + snd_soc_component_update_bits(component, M98095_090_PWR_EN_IN, + M98095_MBEN, M98095_MBEN); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, M98095_090_PWR_EN_IN, + M98095_MBEN, 0); + regcache_mark_dirty(max98095->regmap); + break; + } + return 0; +} + +#define MAX98095_RATES SNDRV_PCM_RATE_8000_96000 +#define MAX98095_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops max98095_dai1_ops = { + .set_sysclk = max98095_dai_set_sysclk, + .set_fmt = max98095_dai1_set_fmt, + .hw_params = max98095_dai1_hw_params, +}; + +static const struct snd_soc_dai_ops max98095_dai2_ops = { + .set_sysclk = max98095_dai_set_sysclk, + .set_fmt = max98095_dai2_set_fmt, + .hw_params = max98095_dai2_hw_params, +}; + +static const struct snd_soc_dai_ops max98095_dai3_ops = { + .set_sysclk = max98095_dai_set_sysclk, + .set_fmt = max98095_dai3_set_fmt, + .hw_params = max98095_dai3_hw_params, +}; + +static struct snd_soc_dai_driver max98095_dai[] = { +{ + .name = "HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98095_RATES, + .formats = MAX98095_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98095_RATES, + .formats = MAX98095_FORMATS, + }, + .ops = &max98095_dai1_ops, +}, +{ + .name = "Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = MAX98095_RATES, + .formats = MAX98095_FORMATS, + }, + .ops = &max98095_dai2_ops, +}, +{ + .name = "Voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = MAX98095_RATES, + .formats = MAX98095_FORMATS, + }, + .ops = &max98095_dai3_ops, +} + +}; + +static int max98095_get_eq_channel(const char *name) +{ + if (strcmp(name, "EQ1 Mode") == 0) + return 0; + if (strcmp(name, "EQ2 Mode") == 0) + return 1; + return -EINVAL; +} + +static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_pdata *pdata = max98095->pdata; + int channel = max98095_get_eq_channel(kcontrol->id.name); + struct max98095_cdata *cdata; + unsigned int sel = ucontrol->value.enumerated.item[0]; + struct max98095_eq_cfg *coef_set; + int fs, best, best_val, i; + int regmask, regsave; + + if (WARN_ON(channel > 1)) + return -EINVAL; + + if (!pdata || !max98095->eq_textcnt) + return 0; + + if (sel >= pdata->eq_cfgcnt) + return -EINVAL; + + cdata = &max98095->dai[channel]; + cdata->eq_sel = sel; + fs = cdata->rate; + + /* Find the selected configuration with nearest sample rate */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->eq_cfgcnt; i++) { + if (strcmp(pdata->eq_cfg[i].name, max98095->eq_texts[sel]) == 0 && + abs(pdata->eq_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->eq_cfg[i].rate - fs); + } + } + + dev_dbg(component->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->eq_cfg[best].name, + pdata->eq_cfg[best].rate, fs); + + coef_set = &pdata->eq_cfg[best]; + + regmask = (channel == 0) ? M98095_EQ1EN : M98095_EQ2EN; + + /* Disable filter while configuring, and save current on/off state */ + regsave = snd_soc_component_read32(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + + mutex_lock(&max98095->lock); + snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); + m98095_eq_band(component, channel, 0, coef_set->band1); + m98095_eq_band(component, channel, 1, coef_set->band2); + m98095_eq_band(component, channel, 2, coef_set->band3); + m98095_eq_band(component, channel, 3, coef_set->band4); + m98095_eq_band(component, channel, 4, coef_set->band5); + snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); + mutex_unlock(&max98095->lock); + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); + return 0; +} + +static int max98095_get_eq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + int channel = max98095_get_eq_channel(kcontrol->id.name); + struct max98095_cdata *cdata; + + cdata = &max98095->dai[channel]; + ucontrol->value.enumerated.item[0] = cdata->eq_sel; + + return 0; +} + +static void max98095_handle_eq_pdata(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_pdata *pdata = max98095->pdata; + struct max98095_eq_cfg *cfg; + unsigned int cfgcnt; + int i, j; + const char **t; + int ret; + + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT("EQ1 Mode", + max98095->eq_enum, + max98095_get_eq_enum, + max98095_put_eq_enum), + SOC_ENUM_EXT("EQ2 Mode", + max98095->eq_enum, + max98095_get_eq_enum, + max98095_put_eq_enum), + }; + + cfg = pdata->eq_cfg; + cfgcnt = pdata->eq_cfgcnt; + + /* Setup an array of texts for the equalizer enum. + * This is based on Mark Brown's equalizer driver code. + */ + max98095->eq_textcnt = 0; + max98095->eq_texts = NULL; + for (i = 0; i < cfgcnt; i++) { + for (j = 0; j < max98095->eq_textcnt; j++) { + if (strcmp(cfg[i].name, max98095->eq_texts[j]) == 0) + break; + } + + if (j != max98095->eq_textcnt) + continue; + + /* Expand the array */ + t = krealloc(max98095->eq_texts, + sizeof(char *) * (max98095->eq_textcnt + 1), + GFP_KERNEL); + if (t == NULL) + continue; + + /* Store the new entry */ + t[max98095->eq_textcnt] = cfg[i].name; + max98095->eq_textcnt++; + max98095->eq_texts = t; + } + + /* Now point the soc_enum to .texts array items */ + max98095->eq_enum.texts = max98095->eq_texts; + max98095->eq_enum.items = max98095->eq_textcnt; + + ret = snd_soc_add_component_controls(component, controls, ARRAY_SIZE(controls)); + if (ret != 0) + dev_err(component->dev, "Failed to add EQ control: %d\n", ret); +} + +static const char *bq_mode_name[] = {"Biquad1 Mode", "Biquad2 Mode"}; + +static int max98095_get_bq_channel(struct snd_soc_component *component, + const char *name) +{ + int ret; + + ret = match_string(bq_mode_name, ARRAY_SIZE(bq_mode_name), name); + if (ret < 0) + dev_err(component->dev, "Bad biquad channel name '%s'\n", name); + return ret; +} + +static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_pdata *pdata = max98095->pdata; + int channel = max98095_get_bq_channel(component, kcontrol->id.name); + struct max98095_cdata *cdata; + unsigned int sel = ucontrol->value.enumerated.item[0]; + struct max98095_biquad_cfg *coef_set; + int fs, best, best_val, i; + int regmask, regsave; + + if (channel < 0) + return channel; + + if (!pdata || !max98095->bq_textcnt) + return 0; + + if (sel >= pdata->bq_cfgcnt) + return -EINVAL; + + cdata = &max98095->dai[channel]; + cdata->bq_sel = sel; + fs = cdata->rate; + + /* Find the selected configuration with nearest sample rate */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->bq_cfgcnt; i++) { + if (strcmp(pdata->bq_cfg[i].name, max98095->bq_texts[sel]) == 0 && + abs(pdata->bq_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->bq_cfg[i].rate - fs); + } + } + + dev_dbg(component->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->bq_cfg[best].name, + pdata->bq_cfg[best].rate, fs); + + coef_set = &pdata->bq_cfg[best]; + + regmask = (channel == 0) ? M98095_BQ1EN : M98095_BQ2EN; + + /* Disable filter while configuring, and save current on/off state */ + regsave = snd_soc_component_read32(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + + mutex_lock(&max98095->lock); + snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); + m98095_biquad_band(component, channel, 0, coef_set->band1); + m98095_biquad_band(component, channel, 1, coef_set->band2); + snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); + mutex_unlock(&max98095->lock); + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); + return 0; +} + +static int max98095_get_bq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + int channel = max98095_get_bq_channel(component, kcontrol->id.name); + struct max98095_cdata *cdata; + + if (channel < 0) + return channel; + + cdata = &max98095->dai[channel]; + ucontrol->value.enumerated.item[0] = cdata->bq_sel; + + return 0; +} + +static void max98095_handle_bq_pdata(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_pdata *pdata = max98095->pdata; + struct max98095_biquad_cfg *cfg; + unsigned int cfgcnt; + int i, j; + const char **t; + int ret; + + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT((char *)bq_mode_name[0], + max98095->bq_enum, + max98095_get_bq_enum, + max98095_put_bq_enum), + SOC_ENUM_EXT((char *)bq_mode_name[1], + max98095->bq_enum, + max98095_get_bq_enum, + max98095_put_bq_enum), + }; + BUILD_BUG_ON(ARRAY_SIZE(controls) != ARRAY_SIZE(bq_mode_name)); + + cfg = pdata->bq_cfg; + cfgcnt = pdata->bq_cfgcnt; + + /* Setup an array of texts for the biquad enum. + * This is based on Mark Brown's equalizer driver code. + */ + max98095->bq_textcnt = 0; + max98095->bq_texts = NULL; + for (i = 0; i < cfgcnt; i++) { + for (j = 0; j < max98095->bq_textcnt; j++) { + if (strcmp(cfg[i].name, max98095->bq_texts[j]) == 0) + break; + } + + if (j != max98095->bq_textcnt) + continue; + + /* Expand the array */ + t = krealloc(max98095->bq_texts, + sizeof(char *) * (max98095->bq_textcnt + 1), + GFP_KERNEL); + if (t == NULL) + continue; + + /* Store the new entry */ + t[max98095->bq_textcnt] = cfg[i].name; + max98095->bq_textcnt++; + max98095->bq_texts = t; + } + + /* Now point the soc_enum to .texts array items */ + max98095->bq_enum.texts = max98095->bq_texts; + max98095->bq_enum.items = max98095->bq_textcnt; + + ret = snd_soc_add_component_controls(component, controls, ARRAY_SIZE(controls)); + if (ret != 0) + dev_err(component->dev, "Failed to add Biquad control: %d\n", ret); +} + +static void max98095_handle_pdata(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_pdata *pdata = max98095->pdata; + u8 regval = 0; + + if (!pdata) { + dev_dbg(component->dev, "No platform data\n"); + return; + } + + /* Configure mic for analog/digital mic mode */ + if (pdata->digmic_left_mode) + regval |= M98095_DIGMIC_L; + + if (pdata->digmic_right_mode) + regval |= M98095_DIGMIC_R; + + snd_soc_component_write(component, M98095_087_CFG_MIC, regval); + + /* Configure equalizers */ + if (pdata->eq_cfgcnt) + max98095_handle_eq_pdata(component); + + /* Configure bi-quad filters */ + if (pdata->bq_cfgcnt) + max98095_handle_bq_pdata(component); +} + +static irqreturn_t max98095_report_jack(int irq, void *data) +{ + struct snd_soc_component *component = data; + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + unsigned int value; + int hp_report = 0; + int mic_report = 0; + + /* Read the Jack Status Register */ + value = snd_soc_component_read32(component, M98095_007_JACK_AUTO_STS); + + /* If ddone is not set, then detection isn't finished yet */ + if ((value & M98095_DDONE) == 0) + return IRQ_NONE; + + /* if hp, check its bit, and if set, clear it */ + if ((value & M98095_HP_IN || value & M98095_LO_IN) && + max98095->headphone_jack) + hp_report |= SND_JACK_HEADPHONE; + + /* if mic, check its bit, and if set, clear it */ + if ((value & M98095_MIC_IN) && max98095->mic_jack) + mic_report |= SND_JACK_MICROPHONE; + + if (max98095->headphone_jack == max98095->mic_jack) { + snd_soc_jack_report(max98095->headphone_jack, + hp_report | mic_report, + SND_JACK_HEADSET); + } else { + if (max98095->headphone_jack) + snd_soc_jack_report(max98095->headphone_jack, + hp_report, SND_JACK_HEADPHONE); + if (max98095->mic_jack) + snd_soc_jack_report(max98095->mic_jack, + mic_report, SND_JACK_MICROPHONE); + } + + return IRQ_HANDLED; +} + +static int max98095_jack_detect_enable(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + int ret = 0; + int detect_enable = M98095_JDEN; + unsigned int slew = M98095_DEFAULT_SLEW_DELAY; + + if (max98095->pdata->jack_detect_pin5en) + detect_enable |= M98095_PIN5EN; + + if (max98095->pdata->jack_detect_delay) + slew = max98095->pdata->jack_detect_delay; + + ret = snd_soc_component_write(component, M98095_08E_JACK_DC_SLEW, slew); + if (ret < 0) { + dev_err(component->dev, "Failed to cfg auto detect %d\n", ret); + return ret; + } + + /* configure auto detection to be enabled */ + ret = snd_soc_component_write(component, M98095_089_JACK_DET_AUTO, detect_enable); + if (ret < 0) { + dev_err(component->dev, "Failed to cfg auto detect %d\n", ret); + return ret; + } + + return ret; +} + +static int max98095_jack_detect_disable(struct snd_soc_component *component) +{ + int ret = 0; + + /* configure auto detection to be disabled */ + ret = snd_soc_component_write(component, M98095_089_JACK_DET_AUTO, 0x0); + if (ret < 0) { + dev_err(component->dev, "Failed to cfg auto detect %d\n", ret); + return ret; + } + + return ret; +} + +int max98095_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct i2c_client *client = to_i2c_client(component->dev); + int ret = 0; + + max98095->headphone_jack = hp_jack; + max98095->mic_jack = mic_jack; + + /* only progress if we have at least 1 jack pointer */ + if (!hp_jack && !mic_jack) + return -EINVAL; + + max98095_jack_detect_enable(component); + + /* enable interrupts for headphone jack detection */ + ret = snd_soc_component_update_bits(component, M98095_013_JACK_INT_EN, + M98095_IDDONE, M98095_IDDONE); + if (ret < 0) { + dev_err(component->dev, "Failed to cfg jack irqs %d\n", ret); + return ret; + } + + max98095_report_jack(client->irq, component); + return 0; +} +EXPORT_SYMBOL_GPL(max98095_jack_detect); + +#ifdef CONFIG_PM +static int max98095_suspend(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + + if (max98095->headphone_jack || max98095->mic_jack) + max98095_jack_detect_disable(component); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + return 0; +} + +static int max98095_resume(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct i2c_client *client = to_i2c_client(component->dev); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + if (max98095->headphone_jack || max98095->mic_jack) { + max98095_jack_detect_enable(component); + max98095_report_jack(client->irq, component); + } + + return 0; +} +#else +#define max98095_suspend NULL +#define max98095_resume NULL +#endif + +static int max98095_reset(struct snd_soc_component *component) +{ + int i, ret; + + /* Gracefully reset the DSP core and the codec hardware + * in a proper sequence */ + ret = snd_soc_component_write(component, M98095_00F_HOST_CFG, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to reset DSP: %d\n", ret); + return ret; + } + + ret = snd_soc_component_write(component, M98095_097_PWR_SYS, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to reset component: %d\n", ret); + return ret; + } + + /* Reset to hardware default for registers, as there is not + * a soft reset hardware control register */ + for (i = M98095_010_HOST_INT_CFG; i < M98095_REG_MAX_CACHED; i++) { + ret = snd_soc_component_write(component, i, snd_soc_component_read32(component, i)); + if (ret < 0) { + dev_err(component->dev, "Failed to reset: %d\n", ret); + return ret; + } + } + + return ret; +} + +static int max98095_probe(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct max98095_cdata *cdata; + struct i2c_client *client; + int ret = 0; + + max98095->mclk = devm_clk_get(component->dev, "mclk"); + if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + /* reset the codec, the DSP core, and disable all interrupts */ + max98095_reset(component); + + client = to_i2c_client(component->dev); + + /* initialize private data */ + + max98095->sysclk = (unsigned)-1; + max98095->eq_textcnt = 0; + max98095->bq_textcnt = 0; + + cdata = &max98095->dai[0]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_sel = 0; + cdata->bq_sel = 0; + + cdata = &max98095->dai[1]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_sel = 0; + cdata->bq_sel = 0; + + cdata = &max98095->dai[2]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_sel = 0; + cdata->bq_sel = 0; + + max98095->lin_state = 0; + max98095->mic1pre = 0; + max98095->mic2pre = 0; + + if (client->irq) { + /* register an audio interrupt */ + ret = request_threaded_irq(client->irq, NULL, + max98095_report_jack, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "max98095", component); + if (ret) { + dev_err(component->dev, "Failed to request IRQ: %d\n", ret); + goto err_access; + } + } + + ret = snd_soc_component_read32(component, M98095_0FF_REV_ID); + if (ret < 0) { + dev_err(component->dev, "Failure reading hardware revision: %d\n", + ret); + goto err_irq; + } + dev_info(component->dev, "Hardware revision: %c\n", ret - 0x40 + 'A'); + + snd_soc_component_write(component, M98095_097_PWR_SYS, M98095_PWRSV); + + snd_soc_component_write(component, M98095_048_MIX_DAC_LR, + M98095_DAI1L_TO_DACL|M98095_DAI1R_TO_DACR); + + snd_soc_component_write(component, M98095_049_MIX_DAC_M, + M98095_DAI2M_TO_DACM|M98095_DAI3M_TO_DACM); + + snd_soc_component_write(component, M98095_092_PWR_EN_OUT, M98095_SPK_SPREADSPECTRUM); + snd_soc_component_write(component, M98095_045_CFG_DSP, M98095_DSPNORMAL); + snd_soc_component_write(component, M98095_04E_CFG_HP, M98095_HPNORMAL); + + snd_soc_component_write(component, M98095_02C_DAI1_IOCFG, + M98095_S1NORMAL|M98095_SDATA); + + snd_soc_component_write(component, M98095_036_DAI2_IOCFG, + M98095_S2NORMAL|M98095_SDATA); + + snd_soc_component_write(component, M98095_040_DAI3_IOCFG, + M98095_S3NORMAL|M98095_SDATA); + + max98095_handle_pdata(component); + + /* take the codec out of the shut down */ + snd_soc_component_update_bits(component, M98095_097_PWR_SYS, M98095_SHDNRUN, + M98095_SHDNRUN); + + return 0; + +err_irq: + if (client->irq) + free_irq(client->irq, component); +err_access: + return ret; +} + +static void max98095_remove(struct snd_soc_component *component) +{ + struct max98095_priv *max98095 = snd_soc_component_get_drvdata(component); + struct i2c_client *client = to_i2c_client(component->dev); + + if (max98095->headphone_jack || max98095->mic_jack) + max98095_jack_detect_disable(component); + + if (client->irq) + free_irq(client->irq, component); +} + +static const struct snd_soc_component_driver soc_component_dev_max98095 = { + .probe = max98095_probe, + .remove = max98095_remove, + .suspend = max98095_suspend, + .resume = max98095_resume, + .set_bias_level = max98095_set_bias_level, + .controls = max98095_snd_controls, + .num_controls = ARRAY_SIZE(max98095_snd_controls), + .dapm_widgets = max98095_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98095_dapm_widgets), + .dapm_routes = max98095_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98095_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int max98095_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max98095_priv *max98095; + int ret; + + max98095 = devm_kzalloc(&i2c->dev, sizeof(struct max98095_priv), + GFP_KERNEL); + if (max98095 == NULL) + return -ENOMEM; + + mutex_init(&max98095->lock); + + max98095->regmap = devm_regmap_init_i2c(i2c, &max98095_regmap); + if (IS_ERR(max98095->regmap)) { + ret = PTR_ERR(max98095->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + max98095->devtype = id->driver_data; + i2c_set_clientdata(i2c, max98095); + max98095->pdata = i2c->dev.platform_data; + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_max98095, + max98095_dai, ARRAY_SIZE(max98095_dai)); + return ret; +} + +static const struct i2c_device_id max98095_i2c_id[] = { + { "max98095", MAX98095 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98095_i2c_id); + +static const struct of_device_id max98095_of_match[] = { + { .compatible = "maxim,max98095", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98095_of_match); + +static struct i2c_driver max98095_i2c_driver = { + .driver = { + .name = "max98095", + .of_match_table = of_match_ptr(max98095_of_match), + }, + .probe = max98095_i2c_probe, + .id_table = max98095_i2c_id, +}; + +module_i2c_driver(max98095_i2c_driver); + +MODULE_DESCRIPTION("ALSA SoC MAX98095 driver"); +MODULE_AUTHOR("Peter Hsiang"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98095.h b/sound/soc/codecs/max98095.h new file mode 100644 index 000000000..67886cacd --- /dev/null +++ b/sound/soc/codecs/max98095.h @@ -0,0 +1,321 @@ +/* + * max98095.h -- MAX98095 ALSA SoC Audio driver + * + * Copyright 2011 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MAX98095_H +#define _MAX98095_H + +/* + * MAX98095 Registers Definition + */ + +#define M98095_000_HOST_DATA 0x00 +#define M98095_001_HOST_INT_STS 0x01 +#define M98095_002_HOST_RSP_STS 0x02 +#define M98095_003_HOST_CMD_STS 0x03 +#define M98095_004_CODEC_STS 0x04 +#define M98095_005_DAI1_ALC_STS 0x05 +#define M98095_006_DAI2_ALC_STS 0x06 +#define M98095_007_JACK_AUTO_STS 0x07 +#define M98095_008_JACK_MANUAL_STS 0x08 +#define M98095_009_JACK_VBAT_STS 0x09 +#define M98095_00A_ACC_ADC_STS 0x0A +#define M98095_00B_MIC_NG_AGC_STS 0x0B +#define M98095_00C_SPK_L_VOLT_STS 0x0C +#define M98095_00D_SPK_R_VOLT_STS 0x0D +#define M98095_00E_TEMP_SENSOR_STS 0x0E +#define M98095_00F_HOST_CFG 0x0F +#define M98095_010_HOST_INT_CFG 0x10 +#define M98095_011_HOST_INT_EN 0x11 +#define M98095_012_CODEC_INT_EN 0x12 +#define M98095_013_JACK_INT_EN 0x13 +#define M98095_014_JACK_INT_EN 0x14 +#define M98095_015_DEC 0x15 +#define M98095_016_RESERVED 0x16 +#define M98095_017_RESERVED 0x17 +#define M98095_018_KEYCODE3 0x18 +#define M98095_019_KEYCODE2 0x19 +#define M98095_01A_KEYCODE1 0x1A +#define M98095_01B_KEYCODE0 0x1B +#define M98095_01C_OEMCODE1 0x1C +#define M98095_01D_OEMCODE0 0x1D +#define M98095_01E_XCFG1 0x1E +#define M98095_01F_XCFG2 0x1F +#define M98095_020_XCFG3 0x20 +#define M98095_021_XCFG4 0x21 +#define M98095_022_XCFG5 0x22 +#define M98095_023_XCFG6 0x23 +#define M98095_024_XGPIO 0x24 +#define M98095_025_XCLKCFG 0x25 +#define M98095_026_SYS_CLK 0x26 +#define M98095_027_DAI1_CLKMODE 0x27 +#define M98095_028_DAI1_CLKCFG_HI 0x28 +#define M98095_029_DAI1_CLKCFG_LO 0x29 +#define M98095_02A_DAI1_FORMAT 0x2A +#define M98095_02B_DAI1_CLOCK 0x2B +#define M98095_02C_DAI1_IOCFG 0x2C +#define M98095_02D_DAI1_TDM 0x2D +#define M98095_02E_DAI1_FILTERS 0x2E +#define M98095_02F_DAI1_LVL1 0x2F +#define M98095_030_DAI1_LVL2 0x30 +#define M98095_031_DAI2_CLKMODE 0x31 +#define M98095_032_DAI2_CLKCFG_HI 0x32 +#define M98095_033_DAI2_CLKCFG_LO 0x33 +#define M98095_034_DAI2_FORMAT 0x34 +#define M98095_035_DAI2_CLOCK 0x35 +#define M98095_036_DAI2_IOCFG 0x36 +#define M98095_037_DAI2_TDM 0x37 +#define M98095_038_DAI2_FILTERS 0x38 +#define M98095_039_DAI2_LVL1 0x39 +#define M98095_03A_DAI2_LVL2 0x3A +#define M98095_03B_DAI3_CLKMODE 0x3B +#define M98095_03C_DAI3_CLKCFG_HI 0x3C +#define M98095_03D_DAI3_CLKCFG_LO 0x3D +#define M98095_03E_DAI3_FORMAT 0x3E +#define M98095_03F_DAI3_CLOCK 0x3F +#define M98095_040_DAI3_IOCFG 0x40 +#define M98095_041_DAI3_TDM 0x41 +#define M98095_042_DAI3_FILTERS 0x42 +#define M98095_043_DAI3_LVL1 0x43 +#define M98095_044_DAI3_LVL2 0x44 +#define M98095_045_CFG_DSP 0x45 +#define M98095_046_DAC_CTRL1 0x46 +#define M98095_047_DAC_CTRL2 0x47 +#define M98095_048_MIX_DAC_LR 0x48 +#define M98095_049_MIX_DAC_M 0x49 +#define M98095_04A_MIX_ADC_LEFT 0x4A +#define M98095_04B_MIX_ADC_RIGHT 0x4B +#define M98095_04C_MIX_HP_LEFT 0x4C +#define M98095_04D_MIX_HP_RIGHT 0x4D +#define M98095_04E_CFG_HP 0x4E +#define M98095_04F_MIX_RCV 0x4F +#define M98095_050_MIX_SPK_LEFT 0x50 +#define M98095_051_MIX_SPK_RIGHT 0x51 +#define M98095_052_MIX_SPK_CFG 0x52 +#define M98095_053_MIX_LINEOUT1 0x53 +#define M98095_054_MIX_LINEOUT2 0x54 +#define M98095_055_MIX_LINEOUT_CFG 0x55 +#define M98095_056_LVL_SIDETONE_DAI12 0x56 +#define M98095_057_LVL_SIDETONE_DAI3 0x57 +#define M98095_058_LVL_DAI1_PLAY 0x58 +#define M98095_059_LVL_DAI1_EQ 0x59 +#define M98095_05A_LVL_DAI2_PLAY 0x5A +#define M98095_05B_LVL_DAI2_EQ 0x5B +#define M98095_05C_LVL_DAI3_PLAY 0x5C +#define M98095_05D_LVL_ADC_L 0x5D +#define M98095_05E_LVL_ADC_R 0x5E +#define M98095_05F_LVL_MIC1 0x5F +#define M98095_060_LVL_MIC2 0x60 +#define M98095_061_LVL_LINEIN 0x61 +#define M98095_062_LVL_LINEOUT1 0x62 +#define M98095_063_LVL_LINEOUT2 0x63 +#define M98095_064_LVL_HP_L 0x64 +#define M98095_065_LVL_HP_R 0x65 +#define M98095_066_LVL_RCV 0x66 +#define M98095_067_LVL_SPK_L 0x67 +#define M98095_068_LVL_SPK_R 0x68 +#define M98095_069_MICAGC_CFG 0x69 +#define M98095_06A_MICAGC_THRESH 0x6A +#define M98095_06B_SPK_NOISEGATE 0x6B +#define M98095_06C_DAI1_ALC1_TIME 0x6C +#define M98095_06D_DAI1_ALC1_COMP 0x6D +#define M98095_06E_DAI1_ALC1_EXPN 0x6E +#define M98095_06F_DAI1_ALC1_GAIN 0x6F +#define M98095_070_DAI1_ALC2_TIME 0x70 +#define M98095_071_DAI1_ALC2_COMP 0x71 +#define M98095_072_DAI1_ALC2_EXPN 0x72 +#define M98095_073_DAI1_ALC2_GAIN 0x73 +#define M98095_074_DAI1_ALC3_TIME 0x74 +#define M98095_075_DAI1_ALC3_COMP 0x75 +#define M98095_076_DAI1_ALC3_EXPN 0x76 +#define M98095_077_DAI1_ALC3_GAIN 0x77 +#define M98095_078_DAI2_ALC1_TIME 0x78 +#define M98095_079_DAI2_ALC1_COMP 0x79 +#define M98095_07A_DAI2_ALC1_EXPN 0x7A +#define M98095_07B_DAI2_ALC1_GAIN 0x7B +#define M98095_07C_DAI2_ALC2_TIME 0x7C +#define M98095_07D_DAI2_ALC2_COMP 0x7D +#define M98095_07E_DAI2_ALC2_EXPN 0x7E +#define M98095_07F_DAI2_ALC2_GAIN 0x7F +#define M98095_080_DAI2_ALC3_TIME 0x80 +#define M98095_081_DAI2_ALC3_COMP 0x81 +#define M98095_082_DAI2_ALC3_EXPN 0x82 +#define M98095_083_DAI2_ALC3_GAIN 0x83 +#define M98095_084_HP_NOISE_GATE 0x84 +#define M98095_085_AUX_ADC 0x85 +#define M98095_086_CFG_LINE 0x86 +#define M98095_087_CFG_MIC 0x87 +#define M98095_088_CFG_LEVEL 0x88 +#define M98095_089_JACK_DET_AUTO 0x89 +#define M98095_08A_JACK_DET_MANUAL 0x8A +#define M98095_08B_JACK_KEYSCAN_DBC 0x8B +#define M98095_08C_JACK_KEYSCAN_DLY 0x8C +#define M98095_08D_JACK_KEY_THRESH 0x8D +#define M98095_08E_JACK_DC_SLEW 0x8E +#define M98095_08F_JACK_TEST_CFG 0x8F +#define M98095_090_PWR_EN_IN 0x90 +#define M98095_091_PWR_EN_OUT 0x91 +#define M98095_092_PWR_EN_OUT 0x92 +#define M98095_093_BIAS_CTRL 0x93 +#define M98095_094_PWR_DAC_21 0x94 +#define M98095_095_PWR_DAC_03 0x95 +#define M98095_096_PWR_DAC_CK 0x96 +#define M98095_097_PWR_SYS 0x97 + +#define M98095_0FF_REV_ID 0xFF + +#define M98095_REG_CNT (0xFF+1) +#define M98095_REG_MAX_CACHED 0X97 + +/* MAX98095 Registers Bit Fields */ + +/* M98095_007_JACK_AUTO_STS */ + #define M98095_MIC_IN (1<<3) + #define M98095_LO_IN (1<<5) + #define M98095_HP_IN (1<<6) + #define M98095_DDONE (1<<7) + +/* M98095_00F_HOST_CFG */ + #define M98095_SEG (1<<0) + #define M98095_XTEN (1<<1) + #define M98095_MDLLEN (1<<2) + +/* M98095_013_JACK_INT_EN */ + #define M98095_IMIC_IN (1<<3) + #define M98095_ILO_IN (1<<5) + #define M98095_IHP_IN (1<<6) + #define M98095_IDDONE (1<<7) + +/* M98095_027_DAI1_CLKMODE, M98095_031_DAI2_CLKMODE, M98095_03B_DAI3_CLKMODE */ + #define M98095_CLKMODE_MASK 0xFF + +/* M98095_02A_DAI1_FORMAT, M98095_034_DAI2_FORMAT, M98095_03E_DAI3_FORMAT */ + #define M98095_DAI_MAS (1<<7) + #define M98095_DAI_WCI (1<<6) + #define M98095_DAI_BCI (1<<5) + #define M98095_DAI_DLY (1<<4) + #define M98095_DAI_TDM (1<<2) + #define M98095_DAI_FSW (1<<1) + #define M98095_DAI_WS (1<<0) + +/* M98095_02B_DAI1_CLOCK, M98095_035_DAI2_CLOCK, M98095_03F_DAI3_CLOCK */ + #define M98095_DAI_BSEL64 (1<<0) + #define M98095_DAI_DOSR_DIV2 (0<<5) + #define M98095_DAI_DOSR_DIV4 (1<<5) + +/* M98095_02C_DAI1_IOCFG, M98095_036_DAI2_IOCFG, M98095_040_DAI3_IOCFG */ + #define M98095_S1NORMAL (1<<6) + #define M98095_S2NORMAL (2<<6) + #define M98095_S3NORMAL (3<<6) + #define M98095_SDATA (3<<0) + +/* M98095_02E_DAI1_FILTERS, M98095_038_DAI2_FILTERS, M98095_042_DAI3_FILTERS */ + #define M98095_DAI_DHF (1<<3) + +/* M98095_045_DSP_CFG */ + #define M98095_DSPNORMAL (5<<4) + +/* M98095_048_MIX_DAC_LR */ + #define M98095_DAI1L_TO_DACR (1<<7) + #define M98095_DAI1R_TO_DACR (1<<6) + #define M98095_DAI2M_TO_DACR (1<<5) + #define M98095_DAI1L_TO_DACL (1<<3) + #define M98095_DAI1R_TO_DACL (1<<2) + #define M98095_DAI2M_TO_DACL (1<<1) + #define M98095_DAI3M_TO_DACL (1<<0) + +/* M98095_049_MIX_DAC_M */ + #define M98095_DAI1L_TO_DACM (1<<3) + #define M98095_DAI1R_TO_DACM (1<<2) + #define M98095_DAI2M_TO_DACM (1<<1) + #define M98095_DAI3M_TO_DACM (1<<0) + +/* M98095_04E_MIX_HP_CFG */ + #define M98095_HPNORMAL (3<<4) + +/* M98095_05F_LVL_MIC1, M98095_060_LVL_MIC2 */ + #define M98095_MICPRE_MASK (3<<5) + #define M98095_MICPRE_SHIFT 5 + +/* M98095_064_LVL_HP_L, M98095_065_LVL_HP_R */ + #define M98095_HP_MUTE (1<<7) + +/* M98095_066_LVL_RCV */ + #define M98095_REC_MUTE (1<<7) + +/* M98095_067_LVL_SPK_L, M98095_068_LVL_SPK_R */ + #define M98095_SP_MUTE (1<<7) + +/* M98095_087_CFG_MIC */ + #define M98095_MICSEL_MASK (3<<0) + #define M98095_DIGMIC_L (1<<2) + #define M98095_DIGMIC_R (1<<3) + #define M98095_DIGMIC2L (1<<4) + #define M98095_DIGMIC2R (1<<5) + +/* M98095_088_CFG_LEVEL */ + #define M98095_VSEN (1<<6) + #define M98095_ZDEN (1<<5) + #define M98095_BQ2EN (1<<3) + #define M98095_BQ1EN (1<<2) + #define M98095_EQ2EN (1<<1) + #define M98095_EQ1EN (1<<0) + +/* M98095_089_JACK_DET_AUTO */ + #define M98095_PIN5EN (1<<2) + #define M98095_JDEN (1<<7) + +/* M98095_090_PWR_EN_IN */ + #define M98095_INEN (1<<7) + #define M98095_MB2EN (1<<3) + #define M98095_MB1EN (1<<2) + #define M98095_MBEN (3<<2) + #define M98095_ADREN (1<<1) + #define M98095_ADLEN (1<<0) + +/* M98095_091_PWR_EN_OUT */ + #define M98095_HPLEN (1<<7) + #define M98095_HPREN (1<<6) + #define M98095_SPLEN (1<<5) + #define M98095_SPREN (1<<4) + #define M98095_RECEN (1<<3) + #define M98095_DALEN (1<<1) + #define M98095_DAREN (1<<0) + +/* M98095_092_PWR_EN_OUT */ + #define M98095_SPK_FIXEDSPECTRUM (0<<4) + #define M98095_SPK_SPREADSPECTRUM (1<<4) + +/* M98095_097_PWR_SYS */ + #define M98095_SHDNRUN (1<<7) + #define M98095_PERFMODE (1<<3) + #define M98095_HPPLYBACK (1<<2) + #define M98095_PWRSV8K (1<<1) + #define M98095_PWRSV (1<<0) + +#define M98095_COEFS_PER_BAND 5 + +#define M98095_BYTE1(w) ((w >> 8) & 0xff) +#define M98095_BYTE0(w) (w & 0xff) + +/* Equalizer filter coefficients */ +#define M98095_110_DAI1_EQ_BASE 0x10 +#define M98095_142_DAI2_EQ_BASE 0x42 + +/* Biquad filter coefficients */ +#define M98095_174_DAI1_BQ_BASE 0x74 +#define M98095_17E_DAI2_BQ_BASE 0x7E + +/* Default Delay used in Slew Rate Calculation for Jack detection */ +#define M98095_DEFAULT_SLEW_DELAY 0x18 + +extern int max98095_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack); + +#endif diff --git a/sound/soc/codecs/max98357a.c b/sound/soc/codecs/max98357a.c new file mode 100644 index 000000000..d469576b5 --- /dev/null +++ b/sound/soc/codecs/max98357a.c @@ -0,0 +1,150 @@ +/* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * max98357a.c -- MAX98357A ALSA SoC Codec driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int max98357a_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct gpio_desc *sdmode = snd_soc_dai_get_drvdata(dai); + + if (!sdmode) + return 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + gpiod_set_value(sdmode, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + gpiod_set_value(sdmode, 0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget max98357a_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("Speaker"), +}; + +static const struct snd_soc_dapm_route max98357a_dapm_routes[] = { + {"Speaker", NULL, "HiFi Playback"}, +}; + +static int max98357a_component_probe(struct snd_soc_component *component) +{ + struct gpio_desc *sdmode; + + sdmode = devm_gpiod_get_optional(component->dev, "sdmode", GPIOD_OUT_LOW); + if (IS_ERR(sdmode)) + return PTR_ERR(sdmode); + + snd_soc_component_set_drvdata(component, sdmode); + + return 0; +} + +static const struct snd_soc_component_driver max98357a_component_driver = { + .probe = max98357a_component_probe, + .dapm_widgets = max98357a_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98357a_dapm_widgets), + .dapm_routes = max98357a_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max98357a_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct snd_soc_dai_ops max98357a_dai_ops = { + .trigger = max98357a_daiops_trigger, +}; + +static struct snd_soc_dai_driver max98357a_dai_driver = { + .name = "HiFi", + .playback = { + .stream_name = "HiFi Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &max98357a_dai_ops, +}; + +static int max98357a_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &max98357a_component_driver, + &max98357a_dai_driver, 1); +} + +static int max98357a_platform_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id max98357a_device_id[] = { + { .compatible = "maxim,max98357a" }, + {} +}; +MODULE_DEVICE_TABLE(of, max98357a_device_id); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max98357a_acpi_match[] = { + { "MX98357A", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, max98357a_acpi_match); +#endif + +static struct platform_driver max98357a_platform_driver = { + .driver = { + .name = "max98357a", + .of_match_table = of_match_ptr(max98357a_device_id), + .acpi_match_table = ACPI_PTR(max98357a_acpi_match), + }, + .probe = max98357a_platform_probe, + .remove = max98357a_platform_remove, +}; +module_platform_driver(max98357a_platform_driver); + +MODULE_DESCRIPTION("Maxim MAX98357A Codec Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c new file mode 100644 index 000000000..d4ba1392a --- /dev/null +++ b/sound/soc/codecs/max98371.c @@ -0,0 +1,438 @@ +/* + * max98371.c -- ALSA SoC Stereo MAX98371 driver + * + * Copyright 2015-16 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98371.h" + +static const char *const monomix_text[] = { + "Left", "Right", "LeftRightDiv2", +}; + +static const char *const hpf_cutoff_txt[] = { + "Disable", "DC Block", "50Hz", + "100Hz", "200Hz", "400Hz", "800Hz", +}; + +static SOC_ENUM_SINGLE_DECL(max98371_monomix, MAX98371_MONOMIX_CFG, 0, + monomix_text); + +static SOC_ENUM_SINGLE_DECL(max98371_hpf_cutoff, MAX98371_HPF, 0, + hpf_cutoff_txt); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_min_gain, + 0, 1, TLV_DB_SCALE_ITEM(537, 66, 0), + 2, 3, TLV_DB_SCALE_ITEM(677, 82, 0), + 4, 5, TLV_DB_SCALE_ITEM(852, 104, 0), + 6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0), + 8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0), + 10, 11, TLV_DB_SCALE_ITEM(1699, 101, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_max_gain, + 0, 1, TLV_DB_SCALE_ITEM(537, 66, 0), + 2, 3, TLV_DB_SCALE_ITEM(677, 82, 0), + 4, 5, TLV_DB_SCALE_ITEM(852, 104, 0), + 6, 7, TLV_DB_SCALE_ITEM(1072, 131, 0), + 8, 9, TLV_DB_SCALE_ITEM(1350, 165, 0), + 10, 11, TLV_DB_SCALE_ITEM(1699, 208, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98371_dht_rot_gain, + 0, 1, TLV_DB_SCALE_ITEM(-50, -50, 0), + 2, 6, TLV_DB_SCALE_ITEM(-100, -100, 0), + 7, 8, TLV_DB_SCALE_ITEM(-800, -200, 0), + 9, 11, TLV_DB_SCALE_ITEM(-1200, -300, 0), + 12, 13, TLV_DB_SCALE_ITEM(-2000, -200, 0), + 14, 15, TLV_DB_SCALE_ITEM(-2500, -500, 0), +); + +static const struct reg_default max98371_reg[] = { + { 0x01, 0x00 }, + { 0x02, 0x00 }, + { 0x03, 0x00 }, + { 0x04, 0x00 }, + { 0x05, 0x00 }, + { 0x06, 0x00 }, + { 0x07, 0x00 }, + { 0x08, 0x00 }, + { 0x09, 0x00 }, + { 0x0A, 0x00 }, + { 0x10, 0x06 }, + { 0x11, 0x08 }, + { 0x14, 0x80 }, + { 0x15, 0x00 }, + { 0x16, 0x00 }, + { 0x18, 0x00 }, + { 0x19, 0x00 }, + { 0x1C, 0x00 }, + { 0x1D, 0x00 }, + { 0x1E, 0x00 }, + { 0x1F, 0x00 }, + { 0x20, 0x00 }, + { 0x21, 0x00 }, + { 0x22, 0x00 }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x00 }, + { 0x28, 0x00 }, + { 0x29, 0x00 }, + { 0x2A, 0x00 }, + { 0x2B, 0x00 }, + { 0x2C, 0x00 }, + { 0x2D, 0x00 }, + { 0x2E, 0x0B }, + { 0x31, 0x00 }, + { 0x32, 0x18 }, + { 0x33, 0x00 }, + { 0x34, 0x00 }, + { 0x36, 0x00 }, + { 0x37, 0x00 }, + { 0x38, 0x00 }, + { 0x39, 0x00 }, + { 0x3A, 0x00 }, + { 0x3B, 0x00 }, + { 0x3C, 0x00 }, + { 0x3D, 0x00 }, + { 0x3E, 0x00 }, + { 0x3F, 0x00 }, + { 0x40, 0x00 }, + { 0x41, 0x00 }, + { 0x42, 0x00 }, + { 0x43, 0x00 }, + { 0x4A, 0x00 }, + { 0x4B, 0x00 }, + { 0x4C, 0x00 }, + { 0x4D, 0x00 }, + { 0x4E, 0x00 }, + { 0x50, 0x00 }, + { 0x51, 0x00 }, + { 0x55, 0x00 }, + { 0x58, 0x00 }, + { 0x59, 0x00 }, + { 0x5C, 0x00 }, + { 0xFF, 0x43 }, +}; + +static bool max98371_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98371_IRQ_CLEAR1: + case MAX98371_IRQ_CLEAR2: + case MAX98371_IRQ_CLEAR3: + case MAX98371_VERSION: + return true; + default: + return false; + } +} + +static bool max98371_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98371_SOFT_RESET: + return false; + default: + return true; + } +}; + +static const DECLARE_TLV_DB_RANGE(max98371_gain_tlv, + 0, 7, TLV_DB_SCALE_ITEM(0, 50, 0), + 8, 10, TLV_DB_SCALE_ITEM(400, 100, 0) +); + +static const DECLARE_TLV_DB_RANGE(max98371_noload_gain_tlv, + 0, 11, TLV_DB_SCALE_ITEM(950, 100, 0), +); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -6300, 50, 1); + +static const struct snd_kcontrol_new max98371_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98371_GAIN, + MAX98371_GAIN_SHIFT, (1<component; + struct max98371_priv *max98371 = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + dev_err(component->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= 0; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= MAX98371_DAI_RIGHT; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= MAX98371_DAI_LEFT; + break; + default: + dev_err(component->dev, "DAI wrong mode unsupported"); + return -EINVAL; + } + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MODE_MASK, val); + return 0; +} + +static int max98371_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98371_priv *max98371 = snd_soc_component_get_drvdata(component); + int blr_clk_ratio, ch_size, channels = params_channels(params); + int rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16); + ch_size = 8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_16); + ch_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32); + ch_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + regmap_update_bits(max98371->regmap, MAX98371_FMT, + MAX98371_FMT_MASK, MAX98371_DAI_CHANSZ_32); + ch_size = 32; + break; + default: + return -EINVAL; + } + + /* BCLK/LRCLK ratio calculation */ + blr_clk_ratio = channels * ch_size; + switch (blr_clk_ratio) { + case 32: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_32); + break; + case 48: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_48); + break; + case 64: + regmap_update_bits(max98371->regmap, + MAX98371_DAI_CLK, + MAX98371_DAI_BSEL_MASK, MAX98371_DAI_BSEL_64); + break; + default: + return -EINVAL; + } + + switch (rate) { + case 32000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_32); + break; + case 44100: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_44); + break; + case 48000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_48); + break; + case 88200: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_88); + break; + case 96000: + regmap_update_bits(max98371->regmap, + MAX98371_SPK_SR, + MAX98371_SPK_SR_MASK, MAX98371_SPK_SR_96); + break; + default: + return -EINVAL; + } + + /* enabling both the RX channels*/ + regmap_update_bits(max98371->regmap, MAX98371_MONOMIX_SRC, + MAX98371_MONOMIX_SRC_MASK, MONOMIX_RX_0_1); + regmap_update_bits(max98371->regmap, MAX98371_DAI_CHANNEL, + MAX98371_CHANNEL_MASK, MAX98371_CHANNEL_MASK); + return 0; +} + +static const struct snd_soc_dapm_widget max98371_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, MAX98371_SPK_ENABLE, 0, 0), + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98371_GLOBAL_ENABLE, + 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("SPK_OUT"), +}; + +static const struct snd_soc_dapm_route max98371_audio_map[] = { + {"DAC", NULL, "HiFi Playback"}, + {"SPK_OUT", NULL, "DAC"}, + {"SPK_OUT", NULL, "Global Enable"}, +}; + +#define MAX98371_RATES SNDRV_PCM_RATE_8000_48000 +#define MAX98371_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +static const struct snd_soc_dai_ops max98371_dai_ops = { + .set_fmt = max98371_dai_set_fmt, + .hw_params = max98371_dai_hw_params, +}; + +static struct snd_soc_dai_driver max98371_dai[] = { + { + .name = "max98371-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98371_FORMATS, + }, + .ops = &max98371_dai_ops, + } +}; + +static const struct snd_soc_component_driver max98371_component = { + .controls = max98371_snd_controls, + .num_controls = ARRAY_SIZE(max98371_snd_controls), + .dapm_routes = max98371_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98371_audio_map), + .dapm_widgets = max98371_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98371_dapm_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config max98371_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX98371_VERSION, + .reg_defaults = max98371_reg, + .num_reg_defaults = ARRAY_SIZE(max98371_reg), + .volatile_reg = max98371_volatile_register, + .readable_reg = max98371_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98371_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max98371_priv *max98371; + int ret, reg; + + max98371 = devm_kzalloc(&i2c->dev, + sizeof(*max98371), GFP_KERNEL); + if (!max98371) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98371); + max98371->regmap = devm_regmap_init_i2c(i2c, &max98371_regmap); + if (IS_ERR(max98371->regmap)) { + ret = PTR_ERR(max98371->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + ret = regmap_read(max98371->regmap, MAX98371_VERSION, ®); + if (ret < 0) { + dev_info(&i2c->dev, "device error %d\n", ret); + return ret; + } + dev_info(&i2c->dev, "device version %x\n", reg); + + ret = devm_snd_soc_register_component(&i2c->dev, &max98371_component, + max98371_dai, ARRAY_SIZE(max98371_dai)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register component: %d\n", ret); + return ret; + } + return ret; +} + +static const struct i2c_device_id max98371_i2c_id[] = { + { "max98371", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max98371_i2c_id); + +static const struct of_device_id max98371_of_match[] = { + { .compatible = "maxim,max98371", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98371_of_match); + +static struct i2c_driver max98371_i2c_driver = { + .driver = { + .name = "max98371", + .pm = NULL, + .of_match_table = of_match_ptr(max98371_of_match), + }, + .probe = max98371_i2c_probe, + .id_table = max98371_i2c_id, +}; + +module_i2c_driver(max98371_i2c_driver); + +MODULE_AUTHOR("anish kumar "); +MODULE_DESCRIPTION("ALSA SoC MAX98371 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98371.h b/sound/soc/codecs/max98371.h new file mode 100644 index 000000000..06e9ba784 --- /dev/null +++ b/sound/soc/codecs/max98371.h @@ -0,0 +1,66 @@ +/* + * max98371.h -- MAX98371 ALSA SoC Audio driver + * + * Copyright 2011-2012 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MAX98371_H +#define _MAX98371_H + +#define MAX98371_IRQ_CLEAR1 0x01 +#define MAX98371_IRQ_CLEAR2 0x02 +#define MAX98371_IRQ_CLEAR3 0x03 +#define MAX98371_DAI_CLK 0x10 +#define MAX98371_DAI_BSEL_MASK 0xF +#define MAX98371_DAI_BSEL_32 2 +#define MAX98371_DAI_BSEL_48 3 +#define MAX98371_DAI_BSEL_64 4 +#define MAX98371_SPK_SR 0x11 +#define MAX98371_SPK_SR_MASK 0xF +#define MAX98371_SPK_SR_32 6 +#define MAX98371_SPK_SR_44 7 +#define MAX98371_SPK_SR_48 8 +#define MAX98371_SPK_SR_88 10 +#define MAX98371_SPK_SR_96 11 +#define MAX98371_DAI_CHANNEL 0x15 +#define MAX98371_CHANNEL_MASK 0x3 +#define MAX98371_MONOMIX_SRC 0x18 +#define MAX98371_MONOMIX_CFG 0x19 +#define MAX98371_HPF 0x1C +#define MAX98371_MONOMIX_SRC_MASK 0xFF +#define MONOMIX_RX_0_1 ((0x1)<<(4)) +#define M98371_DAI_CHANNEL_I2S 0x3 +#define MAX98371_DIGITAL_GAIN 0x2D +#define MAX98371_DIGITAL_GAIN_WIDTH 0x7 +#define MAX98371_GAIN 0x2E +#define MAX98371_GAIN_SHIFT 0x4 +#define MAX98371_GAIN_WIDTH 0x4 +#define MAX98371_DHT_MAX_WIDTH 4 +#define MAX98371_FMT 0x14 +#define MAX98371_CHANSZ_WIDTH 6 +#define MAX98371_FMT_MASK ((0x3)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_FMT_MODE_MASK ((0x7)<<(3)) +#define MAX98371_DAI_LEFT ((0x1)<<(3)) +#define MAX98371_DAI_RIGHT ((0x2)<<(3)) +#define MAX98371_DAI_CHANSZ_16 ((1)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DAI_CHANSZ_24 ((2)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DAI_CHANSZ_32 ((3)<<(MAX98371_CHANSZ_WIDTH)) +#define MAX98371_DHT 0x32 +#define MAX98371_DHT_STEP 0x3 +#define MAX98371_DHT_GAIN 0x31 +#define MAX98371_DHT_GAIN_WIDTH 0x4 +#define MAX98371_DHT_ROT_WIDTH 0x4 +#define MAX98371_SPK_ENABLE 0x4A +#define MAX98371_GLOBAL_ENABLE 0x50 +#define MAX98371_SOFT_RESET 0x51 +#define MAX98371_VERSION 0xFF + + +struct max98371_priv { + struct regmap *regmap; +}; +#endif diff --git a/sound/soc/codecs/max98373.c b/sound/soc/codecs/max98373.c new file mode 100644 index 000000000..1093f766d --- /dev/null +++ b/sound/soc/codecs/max98373.c @@ -0,0 +1,973 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017, Maxim Integrated + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98373.h" + +static struct reg_default max98373_reg[] = { + {MAX98373_R2000_SW_RESET, 0x00}, + {MAX98373_R2001_INT_RAW1, 0x00}, + {MAX98373_R2002_INT_RAW2, 0x00}, + {MAX98373_R2003_INT_RAW3, 0x00}, + {MAX98373_R2004_INT_STATE1, 0x00}, + {MAX98373_R2005_INT_STATE2, 0x00}, + {MAX98373_R2006_INT_STATE3, 0x00}, + {MAX98373_R2007_INT_FLAG1, 0x00}, + {MAX98373_R2008_INT_FLAG2, 0x00}, + {MAX98373_R2009_INT_FLAG3, 0x00}, + {MAX98373_R200A_INT_EN1, 0x00}, + {MAX98373_R200B_INT_EN2, 0x00}, + {MAX98373_R200C_INT_EN3, 0x00}, + {MAX98373_R200D_INT_FLAG_CLR1, 0x00}, + {MAX98373_R200E_INT_FLAG_CLR2, 0x00}, + {MAX98373_R200F_INT_FLAG_CLR3, 0x00}, + {MAX98373_R2010_IRQ_CTRL, 0x00}, + {MAX98373_R2014_THERM_WARN_THRESH, 0x10}, + {MAX98373_R2015_THERM_SHDN_THRESH, 0x27}, + {MAX98373_R2016_THERM_HYSTERESIS, 0x01}, + {MAX98373_R2017_THERM_FOLDBACK_SET, 0xC0}, + {MAX98373_R2018_THERM_FOLDBACK_EN, 0x00}, + {MAX98373_R201E_PIN_DRIVE_STRENGTH, 0x55}, + {MAX98373_R2020_PCM_TX_HIZ_EN_1, 0xFE}, + {MAX98373_R2021_PCM_TX_HIZ_EN_2, 0xFF}, + {MAX98373_R2022_PCM_TX_SRC_1, 0x00}, + {MAX98373_R2023_PCM_TX_SRC_2, 0x00}, + {MAX98373_R2024_PCM_DATA_FMT_CFG, 0xC0}, + {MAX98373_R2025_AUDIO_IF_MODE, 0x00}, + {MAX98373_R2026_PCM_CLOCK_RATIO, 0x04}, + {MAX98373_R2027_PCM_SR_SETUP_1, 0x08}, + {MAX98373_R2028_PCM_SR_SETUP_2, 0x88}, + {MAX98373_R2029_PCM_TO_SPK_MONO_MIX_1, 0x00}, + {MAX98373_R202A_PCM_TO_SPK_MONO_MIX_2, 0x00}, + {MAX98373_R202B_PCM_RX_EN, 0x00}, + {MAX98373_R202C_PCM_TX_EN, 0x00}, + {MAX98373_R202E_ICC_RX_CH_EN_1, 0x00}, + {MAX98373_R202F_ICC_RX_CH_EN_2, 0x00}, + {MAX98373_R2030_ICC_TX_HIZ_EN_1, 0xFF}, + {MAX98373_R2031_ICC_TX_HIZ_EN_2, 0xFF}, + {MAX98373_R2032_ICC_LINK_EN_CFG, 0x30}, + {MAX98373_R2034_ICC_TX_CNTL, 0x00}, + {MAX98373_R2035_ICC_TX_EN, 0x00}, + {MAX98373_R2036_SOUNDWIRE_CTRL, 0x05}, + {MAX98373_R203D_AMP_DIG_VOL_CTRL, 0x00}, + {MAX98373_R203E_AMP_PATH_GAIN, 0x08}, + {MAX98373_R203F_AMP_DSP_CFG, 0x02}, + {MAX98373_R2040_TONE_GEN_CFG, 0x00}, + {MAX98373_R2041_AMP_CFG, 0x03}, + {MAX98373_R2042_AMP_EDGE_RATE_CFG, 0x00}, + {MAX98373_R2043_AMP_EN, 0x00}, + {MAX98373_R2046_IV_SENSE_ADC_DSP_CFG, 0x04}, + {MAX98373_R2047_IV_SENSE_ADC_EN, 0x00}, + {MAX98373_R2051_MEAS_ADC_SAMPLING_RATE, 0x00}, + {MAX98373_R2052_MEAS_ADC_PVDD_FLT_CFG, 0x00}, + {MAX98373_R2053_MEAS_ADC_THERM_FLT_CFG, 0x00}, + {MAX98373_R2054_MEAS_ADC_PVDD_CH_READBACK, 0x00}, + {MAX98373_R2055_MEAS_ADC_THERM_CH_READBACK, 0x00}, + {MAX98373_R2056_MEAS_ADC_PVDD_CH_EN, 0x00}, + {MAX98373_R2090_BDE_LVL_HOLD, 0x00}, + {MAX98373_R2091_BDE_GAIN_ATK_REL_RATE, 0x00}, + {MAX98373_R2092_BDE_CLIPPER_MODE, 0x00}, + {MAX98373_R2097_BDE_L1_THRESH, 0x00}, + {MAX98373_R2098_BDE_L2_THRESH, 0x00}, + {MAX98373_R2099_BDE_L3_THRESH, 0x00}, + {MAX98373_R209A_BDE_L4_THRESH, 0x00}, + {MAX98373_R209B_BDE_THRESH_HYST, 0x00}, + {MAX98373_R20A8_BDE_L1_CFG_1, 0x00}, + {MAX98373_R20A9_BDE_L1_CFG_2, 0x00}, + {MAX98373_R20AA_BDE_L1_CFG_3, 0x00}, + {MAX98373_R20AB_BDE_L2_CFG_1, 0x00}, + {MAX98373_R20AC_BDE_L2_CFG_2, 0x00}, + {MAX98373_R20AD_BDE_L2_CFG_3, 0x00}, + {MAX98373_R20AE_BDE_L3_CFG_1, 0x00}, + {MAX98373_R20AF_BDE_L3_CFG_2, 0x00}, + {MAX98373_R20B0_BDE_L3_CFG_3, 0x00}, + {MAX98373_R20B1_BDE_L4_CFG_1, 0x00}, + {MAX98373_R20B2_BDE_L4_CFG_2, 0x00}, + {MAX98373_R20B3_BDE_L4_CFG_3, 0x00}, + {MAX98373_R20B4_BDE_INFINITE_HOLD_RELEASE, 0x00}, + {MAX98373_R20B5_BDE_EN, 0x00}, + {MAX98373_R20B6_BDE_CUR_STATE_READBACK, 0x00}, + {MAX98373_R20D1_DHT_CFG, 0x01}, + {MAX98373_R20D2_DHT_ATTACK_CFG, 0x02}, + {MAX98373_R20D3_DHT_RELEASE_CFG, 0x03}, + {MAX98373_R20D4_DHT_EN, 0x00}, + {MAX98373_R20E0_LIMITER_THRESH_CFG, 0x00}, + {MAX98373_R20E1_LIMITER_ATK_REL_RATES, 0x00}, + {MAX98373_R20E2_LIMITER_EN, 0x00}, + {MAX98373_R20FE_DEVICE_AUTO_RESTART_CFG, 0x00}, + {MAX98373_R20FF_GLOBAL_SHDN, 0x00}, + {MAX98373_R21FF_REV_ID, 0x42}, +}; + +static int max98373_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98373_priv *max98373 = snd_soc_component_get_drvdata(component); + unsigned int format = 0; + unsigned int invert = 0; + + dev_dbg(component->dev, "%s: fmt 0x%08X\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + invert = MAX98373_PCM_MODE_CFG_PCM_BCLKEDGE; + break; + default: + dev_err(component->dev, "DAI invert mode unsupported\n"); + return -EINVAL; + } + + regmap_update_bits(max98373->regmap, + MAX98373_R2026_PCM_CLOCK_RATIO, + MAX98373_PCM_MODE_CFG_PCM_BCLKEDGE, + invert); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = MAX98373_PCM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = MAX98373_PCM_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_DSP_A: + format = MAX98373_PCM_FORMAT_TDM_MODE1; + break; + case SND_SOC_DAIFMT_DSP_B: + format = MAX98373_PCM_FORMAT_TDM_MODE0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(max98373->regmap, + MAX98373_R2024_PCM_DATA_FMT_CFG, + MAX98373_PCM_MODE_CFG_FORMAT_MASK, + format << MAX98373_PCM_MODE_CFG_FORMAT_SHIFT); + + return 0; +} + +/* BCLKs per LRCLK */ +static const int bclk_sel_table[] = { + 32, 48, 64, 96, 128, 192, 256, 384, 512, 320, +}; + +static int max98373_get_bclk_sel(int bclk) +{ + int i; + /* match BCLKs per LRCLK */ + for (i = 0; i < ARRAY_SIZE(bclk_sel_table); i++) { + if (bclk_sel_table[i] == bclk) + return i + 2; + } + return 0; +} + +static int max98373_set_clock(struct snd_soc_component *component, + struct snd_pcm_hw_params *params) +{ + struct max98373_priv *max98373 = snd_soc_component_get_drvdata(component); + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98373->ch_size; + int value; + + if (!max98373->tdm_mode) { + /* BCLK configuration */ + value = max98373_get_bclk_sel(blr_clk_ratio); + if (!value) { + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + return -EINVAL; + } + + regmap_update_bits(max98373->regmap, + MAX98373_R2026_PCM_CLOCK_RATIO, + MAX98373_PCM_CLK_SETUP_BSEL_MASK, + value); + } + return 0; +} + +static int max98373_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98373_priv *max98373 = snd_soc_component_get_drvdata(component); + unsigned int sampling_rate = 0; + unsigned int chan_sz = 0; + + /* pcm mode configuration */ + switch (snd_pcm_format_width(params_format(params))) { + case 16: + chan_sz = MAX98373_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98373_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98373_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + goto err; + } + + max98373->ch_size = snd_pcm_format_width(params_format(params)); + + regmap_update_bits(max98373->regmap, + MAX98373_R2024_PCM_DATA_FMT_CFG, + MAX98373_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + dev_dbg(component->dev, "format supported %d", + params_format(params)); + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 8000: + sampling_rate = MAX98373_PCM_SR_SET1_SR_8000; + break; + case 11025: + sampling_rate = MAX98373_PCM_SR_SET1_SR_11025; + break; + case 12000: + sampling_rate = MAX98373_PCM_SR_SET1_SR_12000; + break; + case 16000: + sampling_rate = MAX98373_PCM_SR_SET1_SR_16000; + break; + case 22050: + sampling_rate = MAX98373_PCM_SR_SET1_SR_22050; + break; + case 24000: + sampling_rate = MAX98373_PCM_SR_SET1_SR_24000; + break; + case 32000: + sampling_rate = MAX98373_PCM_SR_SET1_SR_32000; + break; + case 44100: + sampling_rate = MAX98373_PCM_SR_SET1_SR_44100; + break; + case 48000: + sampling_rate = MAX98373_PCM_SR_SET1_SR_48000; + break; + default: + dev_err(component->dev, "rate %d not supported\n", + params_rate(params)); + goto err; + } + + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98373->regmap, + MAX98373_R2027_PCM_SR_SETUP_1, + MAX98373_PCM_SR_SET1_SR_MASK, + sampling_rate); + regmap_update_bits(max98373->regmap, + MAX98373_R2028_PCM_SR_SETUP_2, + MAX98373_PCM_SR_SET2_SR_MASK, + sampling_rate << MAX98373_PCM_SR_SET2_SR_SHIFT); + + /* set sampling rate of IV */ + if (max98373->interleave_mode && + sampling_rate > MAX98373_PCM_SR_SET1_SR_16000) + regmap_update_bits(max98373->regmap, + MAX98373_R2028_PCM_SR_SETUP_2, + MAX98373_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate - 3); + else + regmap_update_bits(max98373->regmap, + MAX98373_R2028_PCM_SR_SETUP_2, + MAX98373_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate); + + return max98373_set_clock(component, params); +err: + return -EINVAL; +} + +static int max98373_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct max98373_priv *max98373 = snd_soc_component_get_drvdata(component); + int bsel = 0; + unsigned int chan_sz = 0; + unsigned int mask; + int x, slot_found; + + if (!tx_mask && !rx_mask && !slots && !slot_width) + max98373->tdm_mode = false; + else + max98373->tdm_mode = true; + + /* BCLK configuration */ + bsel = max98373_get_bclk_sel(slots * slot_width); + if (bsel == 0) { + dev_err(component->dev, "BCLK %d not supported\n", + slots * slot_width); + return -EINVAL; + } + + regmap_update_bits(max98373->regmap, + MAX98373_R2026_PCM_CLOCK_RATIO, + MAX98373_PCM_CLK_SETUP_BSEL_MASK, + bsel); + + /* Channel size configuration */ + switch (slot_width) { + case 16: + chan_sz = MAX98373_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98373_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98373_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + slot_width); + return -EINVAL; + } + + regmap_update_bits(max98373->regmap, + MAX98373_R2024_PCM_DATA_FMT_CFG, + MAX98373_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + /* Rx slot configuration */ + slot_found = 0; + mask = rx_mask; + for (x = 0 ; x < 16 ; x++, mask >>= 1) { + if (mask & 0x1) { + if (slot_found == 0) + regmap_update_bits(max98373->regmap, + MAX98373_R2029_PCM_TO_SPK_MONO_MIX_1, + MAX98373_PCM_TO_SPK_CH0_SRC_MASK, x); + else + regmap_write(max98373->regmap, + MAX98373_R202A_PCM_TO_SPK_MONO_MIX_2, + x); + slot_found++; + if (slot_found > 1) + break; + } + } + + /* Tx slot Hi-Z configuration */ + regmap_write(max98373->regmap, + MAX98373_R2020_PCM_TX_HIZ_EN_1, + ~tx_mask & 0xFF); + regmap_write(max98373->regmap, + MAX98373_R2021_PCM_TX_HIZ_EN_2, + (~tx_mask & 0xFF00) >> 8); + + return 0; +} + +#define MAX98373_RATES SNDRV_PCM_RATE_8000_96000 + +#define MAX98373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops max98373_dai_ops = { + .set_fmt = max98373_dai_set_fmt, + .hw_params = max98373_dai_hw_params, + .set_tdm_slot = max98373_dai_tdm_slot, +}; + +static int max98373_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98373_priv *max98373 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(max98373->regmap, + MAX98373_R20FF_GLOBAL_SHDN, + MAX98373_GLOBAL_EN_MASK, 1); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(max98373->regmap, + MAX98373_R20FF_GLOBAL_SHDN, + MAX98373_GLOBAL_EN_MASK, 0); + max98373->tdm_mode = 0; + break; + default: + return 0; + } + return 0; +} + +static const char * const max98373_switch_text[] = { + "Left", "Right", "LeftRight"}; + +static const struct soc_enum dai_sel_enum = + SOC_ENUM_SINGLE(MAX98373_R2029_PCM_TO_SPK_MONO_MIX_1, + MAX98373_PCM_TO_SPK_MONOMIX_CFG_SHIFT, + 3, max98373_switch_text); + +static const struct snd_kcontrol_new max98373_dai_controls = + SOC_DAPM_ENUM("DAI Sel", dai_sel_enum); + +static const struct snd_kcontrol_new max98373_vi_control = + SOC_DAPM_SINGLE("Switch", MAX98373_R202C_PCM_TX_EN, 0, 1, 0); + +static const struct snd_kcontrol_new max98373_spkfb_control = + SOC_DAPM_SINGLE("Switch", MAX98373_R2043_AMP_EN, 1, 1, 0); + +static const struct snd_soc_dapm_widget max98373_dapm_widgets[] = { +SND_SOC_DAPM_DAC_E("Amp Enable", "HiFi Playback", + MAX98373_R202B_PCM_RX_EN, 0, 0, max98373_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_MUX("DAI Sel Mux", SND_SOC_NOPM, 0, 0, + &max98373_dai_controls), +SND_SOC_DAPM_OUTPUT("BE_OUT"), +SND_SOC_DAPM_AIF_OUT("Voltage Sense", "HiFi Capture", 0, + MAX98373_R2047_IV_SENSE_ADC_EN, 0, 0), +SND_SOC_DAPM_AIF_OUT("Current Sense", "HiFi Capture", 0, + MAX98373_R2047_IV_SENSE_ADC_EN, 1, 0), +SND_SOC_DAPM_AIF_OUT("Speaker FB Sense", "HiFi Capture", 0, + SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_SWITCH("VI Sense", SND_SOC_NOPM, 0, 0, + &max98373_vi_control), +SND_SOC_DAPM_SWITCH("SpkFB Sense", SND_SOC_NOPM, 0, 0, + &max98373_spkfb_control), +SND_SOC_DAPM_SIGGEN("VMON"), +SND_SOC_DAPM_SIGGEN("IMON"), +SND_SOC_DAPM_SIGGEN("FBMON"), +}; + +static DECLARE_TLV_DB_SCALE(max98373_digital_tlv, 0, -50, 0); +static const DECLARE_TLV_DB_RANGE(max98373_spk_tlv, + 0, 8, TLV_DB_SCALE_ITEM(0, 50, 0), + 9, 10, TLV_DB_SCALE_ITEM(500, 100, 0), +); +static const DECLARE_TLV_DB_RANGE(max98373_spkgain_max_tlv, + 0, 9, TLV_DB_SCALE_ITEM(800, 100, 0), +); +static const DECLARE_TLV_DB_RANGE(max98373_dht_step_size_tlv, + 0, 1, TLV_DB_SCALE_ITEM(25, 25, 0), + 2, 4, TLV_DB_SCALE_ITEM(100, 100, 0), +); +static const DECLARE_TLV_DB_RANGE(max98373_dht_spkgain_min_tlv, + 0, 9, TLV_DB_SCALE_ITEM(800, 100, 0), +); +static const DECLARE_TLV_DB_RANGE(max98373_dht_rotation_point_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-50, -50, 0), + 2, 7, TLV_DB_SCALE_ITEM(-200, -100, 0), + 8, 9, TLV_DB_SCALE_ITEM(-1000, -200, 0), + 10, 11, TLV_DB_SCALE_ITEM(-1500, -300, 0), + 12, 13, TLV_DB_SCALE_ITEM(-2000, -200, 0), + 14, 15, TLV_DB_SCALE_ITEM(-2500, -500, 0), +); +static const DECLARE_TLV_DB_RANGE(max98373_limiter_thresh_tlv, + 0, 15, TLV_DB_SCALE_ITEM(0, -100, 0), +); + +static const DECLARE_TLV_DB_RANGE(max98373_bde_gain_tlv, + 0, 60, TLV_DB_SCALE_ITEM(0, -25, 0), +); + +static bool max98373_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98373_R2000_SW_RESET: + case MAX98373_R2001_INT_RAW1 ... MAX98373_R200C_INT_EN3: + case MAX98373_R2010_IRQ_CTRL: + case MAX98373_R2014_THERM_WARN_THRESH + ... MAX98373_R2018_THERM_FOLDBACK_EN: + case MAX98373_R201E_PIN_DRIVE_STRENGTH + ... MAX98373_R2036_SOUNDWIRE_CTRL: + case MAX98373_R203D_AMP_DIG_VOL_CTRL ... MAX98373_R2043_AMP_EN: + case MAX98373_R2046_IV_SENSE_ADC_DSP_CFG + ... MAX98373_R2047_IV_SENSE_ADC_EN: + case MAX98373_R2051_MEAS_ADC_SAMPLING_RATE + ... MAX98373_R2056_MEAS_ADC_PVDD_CH_EN: + case MAX98373_R2090_BDE_LVL_HOLD ... MAX98373_R2092_BDE_CLIPPER_MODE: + case MAX98373_R2097_BDE_L1_THRESH + ... MAX98373_R209B_BDE_THRESH_HYST: + case MAX98373_R20A8_BDE_L1_CFG_1 ... MAX98373_R20B3_BDE_L4_CFG_3: + case MAX98373_R20B5_BDE_EN ... MAX98373_R20B6_BDE_CUR_STATE_READBACK: + case MAX98373_R20D1_DHT_CFG ... MAX98373_R20D4_DHT_EN: + case MAX98373_R20E0_LIMITER_THRESH_CFG ... MAX98373_R20E2_LIMITER_EN: + case MAX98373_R20FE_DEVICE_AUTO_RESTART_CFG + ... MAX98373_R20FF_GLOBAL_SHDN: + case MAX98373_R21FF_REV_ID: + return true; + default: + return false; + } +}; + +static bool max98373_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98373_R2000_SW_RESET ... MAX98373_R2009_INT_FLAG3: + case MAX98373_R203E_AMP_PATH_GAIN: + case MAX98373_R2054_MEAS_ADC_PVDD_CH_READBACK: + case MAX98373_R2055_MEAS_ADC_THERM_CH_READBACK: + case MAX98373_R20B6_BDE_CUR_STATE_READBACK: + case MAX98373_R21FF_REV_ID: + return true; + default: + return false; + } +} + +static const char * const max98373_output_voltage_lvl_text[] = { + "5.43V", "6.09V", "6.83V", "7.67V", "8.60V", + "9.65V", "10.83V", "12.15V", "13.63V", "15.29V" +}; + +static SOC_ENUM_SINGLE_DECL(max98373_out_volt_enum, + MAX98373_R203E_AMP_PATH_GAIN, 0, + max98373_output_voltage_lvl_text); + +static const char * const max98373_dht_attack_rate_text[] = { + "17.5us", "35us", "70us", "140us", + "280us", "560us", "1120us", "2240us" +}; + +static SOC_ENUM_SINGLE_DECL(max98373_dht_attack_rate_enum, + MAX98373_R20D2_DHT_ATTACK_CFG, 0, + max98373_dht_attack_rate_text); + +static const char * const max98373_dht_release_rate_text[] = { + "45ms", "225ms", "450ms", "1150ms", + "2250ms", "3100ms", "4500ms", "6750ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98373_dht_release_rate_enum, + MAX98373_R20D3_DHT_RELEASE_CFG, 0, + max98373_dht_release_rate_text); + +static const char * const max98373_limiter_attack_rate_text[] = { + "10us", "20us", "40us", "80us", + "160us", "320us", "640us", "1.28ms", + "2.56ms", "5.12ms", "10.24ms", "20.48ms", + "40.96ms", "81.92ms", "16.384ms", "32.768ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98373_limiter_attack_rate_enum, + MAX98373_R20E1_LIMITER_ATK_REL_RATES, 4, + max98373_limiter_attack_rate_text); + +static const char * const max98373_limiter_release_rate_text[] = { + "40us", "80us", "160us", "320us", + "640us", "1.28ms", "2.56ms", "5.120ms", + "10.24ms", "20.48ms", "40.96ms", "81.92ms", + "163.84ms", "327.68ms", "655.36ms", "1310.72ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98373_limiter_release_rate_enum, + MAX98373_R20E1_LIMITER_ATK_REL_RATES, 0, + max98373_limiter_release_rate_text); + +static const char * const max98373_ADC_samplerate_text[] = { + "333kHz", "192kHz", "64kHz", "48kHz" +}; + +static SOC_ENUM_SINGLE_DECL(max98373_adc_samplerate_enum, + MAX98373_R2051_MEAS_ADC_SAMPLING_RATE, 0, + max98373_ADC_samplerate_text); + +static const struct snd_kcontrol_new max98373_snd_controls[] = { +SOC_SINGLE("Digital Vol Sel Switch", MAX98373_R203F_AMP_DSP_CFG, + MAX98373_AMP_VOL_SEL_SHIFT, 1, 0), +SOC_SINGLE("Volume Location Switch", MAX98373_R203F_AMP_DSP_CFG, + MAX98373_AMP_VOL_SEL_SHIFT, 1, 0), +SOC_SINGLE("Ramp Up Switch", MAX98373_R203F_AMP_DSP_CFG, + MAX98373_AMP_DSP_CFG_RMP_UP_SHIFT, 1, 0), +SOC_SINGLE("Ramp Down Switch", MAX98373_R203F_AMP_DSP_CFG, + MAX98373_AMP_DSP_CFG_RMP_DN_SHIFT, 1, 0), +SOC_SINGLE("CLK Monitor Switch", MAX98373_R20FE_DEVICE_AUTO_RESTART_CFG, + MAX98373_CLOCK_MON_SHIFT, 1, 0), +SOC_SINGLE("Dither Switch", MAX98373_R203F_AMP_DSP_CFG, + MAX98373_AMP_DSP_CFG_DITH_SHIFT, 1, 0), +SOC_SINGLE("DC Blocker Switch", MAX98373_R203F_AMP_DSP_CFG, + MAX98373_AMP_DSP_CFG_DCBLK_SHIFT, 1, 0), +SOC_SINGLE_TLV("Digital Volume", MAX98373_R203D_AMP_DIG_VOL_CTRL, + 0, 0x7F, 0, max98373_digital_tlv), +SOC_SINGLE_TLV("Speaker Volume", MAX98373_R203E_AMP_PATH_GAIN, + MAX98373_SPK_DIGI_GAIN_SHIFT, 10, 0, max98373_spk_tlv), +SOC_SINGLE_TLV("FS Max Volume", MAX98373_R203E_AMP_PATH_GAIN, + MAX98373_FS_GAIN_MAX_SHIFT, 9, 0, max98373_spkgain_max_tlv), +SOC_ENUM("Output Voltage", max98373_out_volt_enum), +/* Dynamic Headroom Tracking */ +SOC_SINGLE("DHT Switch", MAX98373_R20D4_DHT_EN, + MAX98373_DHT_EN_SHIFT, 1, 0), +SOC_SINGLE_TLV("DHT Min Volume", MAX98373_R20D1_DHT_CFG, + MAX98373_DHT_SPK_GAIN_MIN_SHIFT, 9, 0, max98373_dht_spkgain_min_tlv), +SOC_SINGLE_TLV("DHT Rot Pnt Volume", MAX98373_R20D1_DHT_CFG, + MAX98373_DHT_ROT_PNT_SHIFT, 15, 0, max98373_dht_rotation_point_tlv), +SOC_SINGLE_TLV("DHT Attack Step Volume", MAX98373_R20D2_DHT_ATTACK_CFG, + MAX98373_DHT_ATTACK_STEP_SHIFT, 4, 0, max98373_dht_step_size_tlv), +SOC_SINGLE_TLV("DHT Release Step Volume", MAX98373_R20D3_DHT_RELEASE_CFG, + MAX98373_DHT_RELEASE_STEP_SHIFT, 4, 0, max98373_dht_step_size_tlv), +SOC_ENUM("DHT Attack Rate", max98373_dht_attack_rate_enum), +SOC_ENUM("DHT Release Rate", max98373_dht_release_rate_enum), +/* ADC configuration */ +SOC_SINGLE("ADC PVDD CH Switch", MAX98373_R2056_MEAS_ADC_PVDD_CH_EN, 0, 1, 0), +SOC_SINGLE("ADC PVDD FLT Switch", MAX98373_R2052_MEAS_ADC_PVDD_FLT_CFG, + MAX98373_FLT_EN_SHIFT, 1, 0), +SOC_SINGLE("ADC TEMP FLT Switch", MAX98373_R2053_MEAS_ADC_THERM_FLT_CFG, + MAX98373_FLT_EN_SHIFT, 1, 0), +SOC_SINGLE("ADC PVDD", MAX98373_R2054_MEAS_ADC_PVDD_CH_READBACK, 0, 0xFF, 0), +SOC_SINGLE("ADC TEMP", MAX98373_R2055_MEAS_ADC_THERM_CH_READBACK, 0, 0xFF, 0), +SOC_SINGLE("ADC PVDD FLT Coeff", MAX98373_R2052_MEAS_ADC_PVDD_FLT_CFG, + 0, 0x3, 0), +SOC_SINGLE("ADC TEMP FLT Coeff", MAX98373_R2053_MEAS_ADC_THERM_FLT_CFG, + 0, 0x3, 0), +SOC_ENUM("ADC SampleRate", max98373_adc_samplerate_enum), +/* Brownout Detection Engine */ +SOC_SINGLE("BDE Switch", MAX98373_R20B5_BDE_EN, MAX98373_BDE_EN_SHIFT, 1, 0), +SOC_SINGLE("BDE LVL4 Mute Switch", MAX98373_R20B2_BDE_L4_CFG_2, + MAX98373_LVL4_MUTE_EN_SHIFT, 1, 0), +SOC_SINGLE("BDE LVL4 Hold Switch", MAX98373_R20B2_BDE_L4_CFG_2, + MAX98373_LVL4_HOLD_EN_SHIFT, 1, 0), +SOC_SINGLE("BDE LVL1 Thresh", MAX98373_R2097_BDE_L1_THRESH, 0, 0xFF, 0), +SOC_SINGLE("BDE LVL2 Thresh", MAX98373_R2098_BDE_L2_THRESH, 0, 0xFF, 0), +SOC_SINGLE("BDE LVL3 Thresh", MAX98373_R2099_BDE_L3_THRESH, 0, 0xFF, 0), +SOC_SINGLE("BDE LVL4 Thresh", MAX98373_R209A_BDE_L4_THRESH, 0, 0xFF, 0), +SOC_SINGLE("BDE Active Level", MAX98373_R20B6_BDE_CUR_STATE_READBACK, 0, 8, 0), +SOC_SINGLE("BDE Clip Mode Switch", MAX98373_R2092_BDE_CLIPPER_MODE, 0, 1, 0), +SOC_SINGLE("BDE Thresh Hysteresis", MAX98373_R209B_BDE_THRESH_HYST, 0, 0xFF, 0), +SOC_SINGLE("BDE Hold Time", MAX98373_R2090_BDE_LVL_HOLD, 0, 0xFF, 0), +SOC_SINGLE("BDE Attack Rate", MAX98373_R2091_BDE_GAIN_ATK_REL_RATE, 4, 0xF, 0), +SOC_SINGLE("BDE Release Rate", MAX98373_R2091_BDE_GAIN_ATK_REL_RATE, 0, 0xF, 0), +SOC_SINGLE_TLV("BDE LVL1 Clip Thresh Volume", MAX98373_R20A9_BDE_L1_CFG_2, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL2 Clip Thresh Volume", MAX98373_R20AC_BDE_L2_CFG_2, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL3 Clip Thresh Volume", MAX98373_R20AF_BDE_L3_CFG_2, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL4 Clip Thresh Volume", MAX98373_R20B2_BDE_L4_CFG_2, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL1 Clip Reduction Volume", MAX98373_R20AA_BDE_L1_CFG_3, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL2 Clip Reduction Volume", MAX98373_R20AD_BDE_L2_CFG_3, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL3 Clip Reduction Volume", MAX98373_R20B0_BDE_L3_CFG_3, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL4 Clip Reduction Volume", MAX98373_R20B3_BDE_L4_CFG_3, + 0, 0x3C, 0, max98373_bde_gain_tlv), +SOC_SINGLE_TLV("BDE LVL1 Limiter Thresh Volume", MAX98373_R20A8_BDE_L1_CFG_1, + 0, 0xF, 0, max98373_limiter_thresh_tlv), +SOC_SINGLE_TLV("BDE LVL2 Limiter Thresh Volume", MAX98373_R20AB_BDE_L2_CFG_1, + 0, 0xF, 0, max98373_limiter_thresh_tlv), +SOC_SINGLE_TLV("BDE LVL3 Limiter Thresh Volume", MAX98373_R20AE_BDE_L3_CFG_1, + 0, 0xF, 0, max98373_limiter_thresh_tlv), +SOC_SINGLE_TLV("BDE LVL4 Limiter Thresh Volume", MAX98373_R20B1_BDE_L4_CFG_1, + 0, 0xF, 0, max98373_limiter_thresh_tlv), +/* Limiter */ +SOC_SINGLE("Limiter Switch", MAX98373_R20E2_LIMITER_EN, + MAX98373_LIMITER_EN_SHIFT, 1, 0), +SOC_SINGLE("Limiter Src Switch", MAX98373_R20E0_LIMITER_THRESH_CFG, + MAX98373_LIMITER_THRESH_SRC_SHIFT, 1, 0), +SOC_SINGLE_TLV("Limiter Thresh Volume", MAX98373_R20E0_LIMITER_THRESH_CFG, + MAX98373_LIMITER_THRESH_SHIFT, 15, 0, max98373_limiter_thresh_tlv), +SOC_ENUM("Limiter Attack Rate", max98373_limiter_attack_rate_enum), +SOC_ENUM("Limiter Release Rate", max98373_limiter_release_rate_enum), +}; + +static const struct snd_soc_dapm_route max98373_audio_map[] = { + /* Plabyack */ + {"DAI Sel Mux", "Left", "Amp Enable"}, + {"DAI Sel Mux", "Right", "Amp Enable"}, + {"DAI Sel Mux", "LeftRight", "Amp Enable"}, + {"BE_OUT", NULL, "DAI Sel Mux"}, + /* Capture */ + { "VI Sense", "Switch", "VMON" }, + { "VI Sense", "Switch", "IMON" }, + { "SpkFB Sense", "Switch", "FBMON" }, + { "Voltage Sense", NULL, "VI Sense" }, + { "Current Sense", NULL, "VI Sense" }, + { "Speaker FB Sense", NULL, "SpkFB Sense" }, +}; + +static struct snd_soc_dai_driver max98373_dai[] = { + { + .name = "max98373-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98373_RATES, + .formats = MAX98373_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98373_RATES, + .formats = MAX98373_FORMATS, + }, + .ops = &max98373_dai_ops, + } +}; + +static int max98373_probe(struct snd_soc_component *component) +{ + struct max98373_priv *max98373 = snd_soc_component_get_drvdata(component); + + /* Software Reset */ + regmap_write(max98373->regmap, + MAX98373_R2000_SW_RESET, MAX98373_SOFT_RESET); + usleep_range(10000, 11000); + + /* IV default slot configuration */ + regmap_write(max98373->regmap, + MAX98373_R2020_PCM_TX_HIZ_EN_1, + 0xFF); + regmap_write(max98373->regmap, + MAX98373_R2021_PCM_TX_HIZ_EN_2, + 0xFF); + /* L/R mix configuration */ + regmap_write(max98373->regmap, + MAX98373_R2029_PCM_TO_SPK_MONO_MIX_1, + 0x80); + regmap_write(max98373->regmap, + MAX98373_R202A_PCM_TO_SPK_MONO_MIX_2, + 0x1); + /* Set inital volume (0dB) */ + regmap_write(max98373->regmap, + MAX98373_R203D_AMP_DIG_VOL_CTRL, + 0x00); + regmap_write(max98373->regmap, + MAX98373_R203E_AMP_PATH_GAIN, + 0x00); + /* Enable DC blocker */ + regmap_write(max98373->regmap, + MAX98373_R203F_AMP_DSP_CFG, + 0x3); + /* Enable IMON VMON DC blocker */ + regmap_write(max98373->regmap, + MAX98373_R2046_IV_SENSE_ADC_DSP_CFG, + 0x7); + /* voltage, current slot configuration */ + regmap_write(max98373->regmap, + MAX98373_R2022_PCM_TX_SRC_1, + (max98373->i_slot << MAX98373_PCM_TX_CH_SRC_A_I_SHIFT | + max98373->v_slot) & 0xFF); + if (max98373->v_slot < 8) + regmap_update_bits(max98373->regmap, + MAX98373_R2020_PCM_TX_HIZ_EN_1, + 1 << max98373->v_slot, 0); + else + regmap_update_bits(max98373->regmap, + MAX98373_R2021_PCM_TX_HIZ_EN_2, + 1 << (max98373->v_slot - 8), 0); + + if (max98373->i_slot < 8) + regmap_update_bits(max98373->regmap, + MAX98373_R2020_PCM_TX_HIZ_EN_1, + 1 << max98373->i_slot, 0); + else + regmap_update_bits(max98373->regmap, + MAX98373_R2021_PCM_TX_HIZ_EN_2, + 1 << (max98373->i_slot - 8), 0); + + /* speaker feedback slot configuration */ + regmap_write(max98373->regmap, + MAX98373_R2023_PCM_TX_SRC_2, + max98373->spkfb_slot & 0xFF); + + /* Set interleave mode */ + if (max98373->interleave_mode) + regmap_update_bits(max98373->regmap, + MAX98373_R2024_PCM_DATA_FMT_CFG, + MAX98373_PCM_TX_CH_INTERLEAVE_MASK, + MAX98373_PCM_TX_CH_INTERLEAVE_MASK); + + /* Speaker enable */ + regmap_update_bits(max98373->regmap, + MAX98373_R2043_AMP_EN, + MAX98373_SPK_EN_MASK, 1); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max98373_suspend(struct device *dev) +{ + struct max98373_priv *max98373 = dev_get_drvdata(dev); + + regcache_cache_only(max98373->regmap, true); + regcache_mark_dirty(max98373->regmap); + return 0; +} +static int max98373_resume(struct device *dev) +{ + struct max98373_priv *max98373 = dev_get_drvdata(dev); + + regmap_write(max98373->regmap, + MAX98373_R2000_SW_RESET, MAX98373_SOFT_RESET); + usleep_range(10000, 11000); + regcache_cache_only(max98373->regmap, false); + regcache_sync(max98373->regmap); + return 0; +} +#endif + +static const struct dev_pm_ops max98373_pm = { + SET_SYSTEM_SLEEP_PM_OPS(max98373_suspend, max98373_resume) +}; + +static const struct snd_soc_component_driver soc_codec_dev_max98373 = { + .probe = max98373_probe, + .controls = max98373_snd_controls, + .num_controls = ARRAY_SIZE(max98373_snd_controls), + .dapm_widgets = max98373_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98373_dapm_widgets), + .dapm_routes = max98373_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98373_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config max98373_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98373_R21FF_REV_ID, + .reg_defaults = max98373_reg, + .num_reg_defaults = ARRAY_SIZE(max98373_reg), + .readable_reg = max98373_readable_register, + .volatile_reg = max98373_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static void max98373_slot_config(struct i2c_client *i2c, + struct max98373_priv *max98373) +{ + int value; + struct device *dev = &i2c->dev; + + if (!device_property_read_u32(dev, "maxim,vmon-slot-no", &value)) + max98373->v_slot = value & 0xF; + else + max98373->v_slot = 0; + + if (!device_property_read_u32(dev, "maxim,imon-slot-no", &value)) + max98373->i_slot = value & 0xF; + else + max98373->i_slot = 1; + + if (!device_property_read_u32(dev, "maxim,spkfb-slot-no", &value)) + max98373->spkfb_slot = value & 0xF; + else + max98373->spkfb_slot = 2; +} + +static int max98373_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + + int ret = 0; + int reg = 0; + struct max98373_priv *max98373 = NULL; + + max98373 = devm_kzalloc(&i2c->dev, sizeof(*max98373), GFP_KERNEL); + + if (!max98373) { + ret = -ENOMEM; + return ret; + } + i2c_set_clientdata(i2c, max98373); + + /* update interleave mode info */ + if (device_property_read_bool(&i2c->dev, "maxim,interleave_mode")) + max98373->interleave_mode = 1; + else + max98373->interleave_mode = 0; + + + /* regmap initialization */ + max98373->regmap + = devm_regmap_init_i2c(i2c, &max98373_regmap); + if (IS_ERR(max98373->regmap)) { + ret = PTR_ERR(max98373->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + /* Check Revision ID */ + ret = regmap_read(max98373->regmap, + MAX98373_R21FF_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c->dev, + "Failed to read: 0x%02X\n", MAX98373_R21FF_REV_ID); + return ret; + } + dev_info(&i2c->dev, "MAX98373 revisionID: 0x%02X\n", reg); + + /* voltage/current slot configuration */ + max98373_slot_config(i2c, max98373); + + /* codec registeration */ + ret = devm_snd_soc_register_component(&i2c->dev, &soc_codec_dev_max98373, + max98373_dai, ARRAY_SIZE(max98373_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id max98373_i2c_id[] = { + { "max98373", 0}, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max98373_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id max98373_of_match[] = { + { .compatible = "maxim,max98373", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98373_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max98373_acpi_match[] = { + { "MX98373", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, max98373_acpi_match); +#endif + +static struct i2c_driver max98373_i2c_driver = { + .driver = { + .name = "max98373", + .of_match_table = of_match_ptr(max98373_of_match), + .acpi_match_table = ACPI_PTR(max98373_acpi_match), + .pm = &max98373_pm, + }, + .probe = max98373_i2c_probe, + .id_table = max98373_i2c_id, +}; + +module_i2c_driver(max98373_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98373 driver"); +MODULE_AUTHOR("Ryan Lee "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98373.h b/sound/soc/codecs/max98373.h new file mode 100644 index 000000000..f6a37aa02 --- /dev/null +++ b/sound/soc/codecs/max98373.h @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017, Maxim Integrated + +#ifndef _MAX98373_H +#define _MAX98373_H + +#define MAX98373_R2000_SW_RESET 0x2000 +#define MAX98373_R2001_INT_RAW1 0x2001 +#define MAX98373_R2002_INT_RAW2 0x2002 +#define MAX98373_R2003_INT_RAW3 0x2003 +#define MAX98373_R2004_INT_STATE1 0x2004 +#define MAX98373_R2005_INT_STATE2 0x2005 +#define MAX98373_R2006_INT_STATE3 0x2006 +#define MAX98373_R2007_INT_FLAG1 0x2007 +#define MAX98373_R2008_INT_FLAG2 0x2008 +#define MAX98373_R2009_INT_FLAG3 0x2009 +#define MAX98373_R200A_INT_EN1 0x200A +#define MAX98373_R200B_INT_EN2 0x200B +#define MAX98373_R200C_INT_EN3 0x200C +#define MAX98373_R200D_INT_FLAG_CLR1 0x200D +#define MAX98373_R200E_INT_FLAG_CLR2 0x200E +#define MAX98373_R200F_INT_FLAG_CLR3 0x200F +#define MAX98373_R2010_IRQ_CTRL 0x2010 +#define MAX98373_R2014_THERM_WARN_THRESH 0x2014 +#define MAX98373_R2015_THERM_SHDN_THRESH 0x2015 +#define MAX98373_R2016_THERM_HYSTERESIS 0x2016 +#define MAX98373_R2017_THERM_FOLDBACK_SET 0x2017 +#define MAX98373_R2018_THERM_FOLDBACK_EN 0x2018 +#define MAX98373_R201E_PIN_DRIVE_STRENGTH 0x201E +#define MAX98373_R2020_PCM_TX_HIZ_EN_1 0x2020 +#define MAX98373_R2021_PCM_TX_HIZ_EN_2 0x2021 +#define MAX98373_R2022_PCM_TX_SRC_1 0x2022 +#define MAX98373_R2023_PCM_TX_SRC_2 0x2023 +#define MAX98373_R2024_PCM_DATA_FMT_CFG 0x2024 +#define MAX98373_R2025_AUDIO_IF_MODE 0x2025 +#define MAX98373_R2026_PCM_CLOCK_RATIO 0x2026 +#define MAX98373_R2027_PCM_SR_SETUP_1 0x2027 +#define MAX98373_R2028_PCM_SR_SETUP_2 0x2028 +#define MAX98373_R2029_PCM_TO_SPK_MONO_MIX_1 0x2029 +#define MAX98373_R202A_PCM_TO_SPK_MONO_MIX_2 0x202A +#define MAX98373_R202B_PCM_RX_EN 0x202B +#define MAX98373_R202C_PCM_TX_EN 0x202C +#define MAX98373_R202E_ICC_RX_CH_EN_1 0x202E +#define MAX98373_R202F_ICC_RX_CH_EN_2 0x202F +#define MAX98373_R2030_ICC_TX_HIZ_EN_1 0x2030 +#define MAX98373_R2031_ICC_TX_HIZ_EN_2 0x2031 +#define MAX98373_R2032_ICC_LINK_EN_CFG 0x2032 +#define MAX98373_R2034_ICC_TX_CNTL 0x2034 +#define MAX98373_R2035_ICC_TX_EN 0x2035 +#define MAX98373_R2036_SOUNDWIRE_CTRL 0x2036 +#define MAX98373_R203D_AMP_DIG_VOL_CTRL 0x203D +#define MAX98373_R203E_AMP_PATH_GAIN 0x203E +#define MAX98373_R203F_AMP_DSP_CFG 0x203F +#define MAX98373_R2040_TONE_GEN_CFG 0x2040 +#define MAX98373_R2041_AMP_CFG 0x2041 +#define MAX98373_R2042_AMP_EDGE_RATE_CFG 0x2042 +#define MAX98373_R2043_AMP_EN 0x2043 +#define MAX98373_R2046_IV_SENSE_ADC_DSP_CFG 0x2046 +#define MAX98373_R2047_IV_SENSE_ADC_EN 0x2047 +#define MAX98373_R2051_MEAS_ADC_SAMPLING_RATE 0x2051 +#define MAX98373_R2052_MEAS_ADC_PVDD_FLT_CFG 0x2052 +#define MAX98373_R2053_MEAS_ADC_THERM_FLT_CFG 0x2053 +#define MAX98373_R2054_MEAS_ADC_PVDD_CH_READBACK 0x2054 +#define MAX98373_R2055_MEAS_ADC_THERM_CH_READBACK 0x2055 +#define MAX98373_R2056_MEAS_ADC_PVDD_CH_EN 0x2056 +#define MAX98373_R2090_BDE_LVL_HOLD 0x2090 +#define MAX98373_R2091_BDE_GAIN_ATK_REL_RATE 0x2091 +#define MAX98373_R2092_BDE_CLIPPER_MODE 0x2092 +#define MAX98373_R2097_BDE_L1_THRESH 0x2097 +#define MAX98373_R2098_BDE_L2_THRESH 0x2098 +#define MAX98373_R2099_BDE_L3_THRESH 0x2099 +#define MAX98373_R209A_BDE_L4_THRESH 0x209A +#define MAX98373_R209B_BDE_THRESH_HYST 0x209B +#define MAX98373_R20A8_BDE_L1_CFG_1 0x20A8 +#define MAX98373_R20A9_BDE_L1_CFG_2 0x20A9 +#define MAX98373_R20AA_BDE_L1_CFG_3 0x20AA +#define MAX98373_R20AB_BDE_L2_CFG_1 0x20AB +#define MAX98373_R20AC_BDE_L2_CFG_2 0x20AC +#define MAX98373_R20AD_BDE_L2_CFG_3 0x20AD +#define MAX98373_R20AE_BDE_L3_CFG_1 0x20AE +#define MAX98373_R20AF_BDE_L3_CFG_2 0x20AF +#define MAX98373_R20B0_BDE_L3_CFG_3 0x20B0 +#define MAX98373_R20B1_BDE_L4_CFG_1 0x20B1 +#define MAX98373_R20B2_BDE_L4_CFG_2 0x20B2 +#define MAX98373_R20B3_BDE_L4_CFG_3 0x20B3 +#define MAX98373_R20B4_BDE_INFINITE_HOLD_RELEASE 0x20B4 +#define MAX98373_R20B5_BDE_EN 0x20B5 +#define MAX98373_R20B6_BDE_CUR_STATE_READBACK 0x20B6 +#define MAX98373_R20D1_DHT_CFG 0x20D1 +#define MAX98373_R20D2_DHT_ATTACK_CFG 0x20D2 +#define MAX98373_R20D3_DHT_RELEASE_CFG 0x20D3 +#define MAX98373_R20D4_DHT_EN 0x20D4 +#define MAX98373_R20E0_LIMITER_THRESH_CFG 0x20E0 +#define MAX98373_R20E1_LIMITER_ATK_REL_RATES 0x20E1 +#define MAX98373_R20E2_LIMITER_EN 0x20E2 +#define MAX98373_R20FE_DEVICE_AUTO_RESTART_CFG 0x20FE +#define MAX98373_R20FF_GLOBAL_SHDN 0x20FF +#define MAX98373_R21FF_REV_ID 0x21FF + +/* MAX98373_R2022_PCM_TX_SRC_1 */ +#define MAX98373_PCM_TX_CH_SRC_A_V_SHIFT (0) +#define MAX98373_PCM_TX_CH_SRC_A_I_SHIFT (4) + +/* MAX98373_R2024_PCM_DATA_FMT_CFG */ +#define MAX98373_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3) +#define MAX98373_PCM_MODE_CFG_FORMAT_SHIFT (3) +#define MAX98373_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 2) +#define MAX98373_PCM_FORMAT_I2S (0x0 << 0) +#define MAX98373_PCM_FORMAT_LJ (0x1 << 0) +#define MAX98373_PCM_FORMAT_TDM_MODE0 (0x3 << 0) +#define MAX98373_PCM_FORMAT_TDM_MODE1 (0x4 << 0) +#define MAX98373_PCM_FORMAT_TDM_MODE2 (0x5 << 0) +#define MAX98373_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6) +#define MAX98373_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6) +#define MAX98373_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6) +#define MAX98373_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6) + +/* MAX98373_R2026_PCM_CLOCK_RATIO */ +#define MAX98373_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 4) +#define MAX98373_PCM_CLK_SETUP_BSEL_MASK (0xF << 0) + +/* MAX98373_R2027_PCM_SR_SETUP_1 */ +#define MAX98373_PCM_SR_SET1_SR_MASK (0xF << 0) +#define MAX98373_PCM_SR_SET1_SR_8000 (0x0 << 0) +#define MAX98373_PCM_SR_SET1_SR_11025 (0x1 << 0) +#define MAX98373_PCM_SR_SET1_SR_12000 (0x2 << 0) +#define MAX98373_PCM_SR_SET1_SR_16000 (0x3 << 0) +#define MAX98373_PCM_SR_SET1_SR_22050 (0x4 << 0) +#define MAX98373_PCM_SR_SET1_SR_24000 (0x5 << 0) +#define MAX98373_PCM_SR_SET1_SR_32000 (0x6 << 0) +#define MAX98373_PCM_SR_SET1_SR_44100 (0x7 << 0) +#define MAX98373_PCM_SR_SET1_SR_48000 (0x8 << 0) + +/* MAX98373_R2028_PCM_SR_SETUP_2 */ +#define MAX98373_PCM_SR_SET2_SR_MASK (0xF << 4) +#define MAX98373_PCM_SR_SET2_SR_SHIFT (4) +#define MAX98373_PCM_SR_SET2_IVADC_SR_MASK (0xF << 0) + +/* MAX98373_R2029_PCM_TO_SPK_MONO_MIX_1 */ +#define MAX98373_PCM_TO_SPK_MONOMIX_CFG_MASK (0x3 << 6) +#define MAX98373_PCM_TO_SPK_MONOMIX_CFG_SHIFT (6) +#define MAX98373_PCM_TO_SPK_CH0_SRC_MASK (0xF << 0) + +/* MAX98373_R203E_AMP_PATH_GAIN */ +#define MAX98373_SPK_DIGI_GAIN_MASK (0xF << 4) +#define MAX98373_SPK_DIGI_GAIN_SHIFT (4) +#define MAX98373_FS_GAIN_MAX_MASK (0xF << 0) +#define MAX98373_FS_GAIN_MAX_SHIFT (0) + +/* MAX98373_R203F_AMP_DSP_CFG */ +#define MAX98373_AMP_DSP_CFG_DCBLK_SHIFT (0) +#define MAX98373_AMP_DSP_CFG_DITH_SHIFT (1) +#define MAX98373_AMP_DSP_CFG_RMP_UP_SHIFT (2) +#define MAX98373_AMP_DSP_CFG_RMP_DN_SHIFT (3) +#define MAX98373_AMP_DSP_CFG_DAC_INV_SHIFT (5) +#define MAX98373_AMP_VOL_SEL_SHIFT (7) + +/* MAX98373_R2043_AMP_EN */ +#define MAX98373_SPKFB_EN_MASK (0x1 << 1) +#define MAX98373_SPK_EN_MASK (0x1 << 0) +#define MAX98373_SPKFB_EN_SHIFT (1) + +/*MAX98373_R2052_MEAS_ADC_PVDD_FLT_CFG */ +#define MAX98373_FLT_EN_SHIFT (4) + +/* MAX98373_R20B2_BDE_L4_CFG_2 */ +#define MAX98373_LVL4_MUTE_EN_SHIFT (7) +#define MAX98373_LVL4_HOLD_EN_SHIFT (6) + +/* MAX98373_R20B5_BDE_EN */ +#define MAX98373_BDE_EN_SHIFT (0) + +/* MAX98373_R20D1_DHT_CFG */ +#define MAX98373_DHT_SPK_GAIN_MIN_SHIFT (4) +#define MAX98373_DHT_ROT_PNT_SHIFT (0) + +/* MAX98373_R20D2_DHT_ATTACK_CFG */ +#define MAX98373_DHT_ATTACK_STEP_SHIFT (3) +#define MAX98373_DHT_ATTACK_RATE_SHIFT (0) + +/* MAX98373_R20D3_DHT_RELEASE_CFG */ +#define MAX98373_DHT_RELEASE_STEP_SHIFT (3) +#define MAX98373_DHT_RELEASE_RATE_SHIFT (0) + +/* MAX98373_R20D4_DHT_EN */ +#define MAX98373_DHT_EN_SHIFT (0) + +/* MAX98373_R20E0_LIMITER_THRESH_CFG */ +#define MAX98373_LIMITER_THRESH_SHIFT (2) +#define MAX98373_LIMITER_THRESH_SRC_SHIFT (0) + +/* MAX98373_R20E2_LIMITER_EN */ +#define MAX98373_LIMITER_EN_SHIFT (0) + +/* MAX98373_R20FE_DEVICE_AUTO_RESTART_CFG */ +#define MAX98373_CLOCK_MON_SHIFT (0) + +/* MAX98373_R20FF_GLOBAL_SHDN */ +#define MAX98373_GLOBAL_EN_MASK (0x1 << 0) + +/* MAX98373_R2000_SW_RESET */ +#define MAX98373_SOFT_RESET (0x1 << 0) + +struct max98373_priv { + struct regmap *regmap; + unsigned int v_slot; + unsigned int i_slot; + unsigned int spkfb_slot; + bool interleave_mode; + unsigned int ch_size; + bool tdm_mode; +}; +#endif diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c new file mode 100644 index 000000000..6e6134589 --- /dev/null +++ b/sound/soc/codecs/max9850.c @@ -0,0 +1,360 @@ +/* + * max9850.c -- codec driver for max9850 + * + * Copyright (C) 2011 taskit GmbH + * + * Author: Christian Glindkamp + * + * Initial development of this code was funded by + * MICRONIC Computer Systeme GmbH, http://www.mcsberlin.de/ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "max9850.h" + +struct max9850_priv { + struct regmap *regmap; + unsigned int sysclk; +}; + +/* max9850 register cache */ +static const struct reg_default max9850_reg[] = { + { 2, 0x0c }, + { 3, 0x00 }, + { 4, 0x00 }, + { 5, 0x00 }, + { 6, 0x00 }, + { 7, 0x00 }, + { 8, 0x00 }, + { 9, 0x00 }, + { 10, 0x00 }, +}; + +/* these registers are not used at the moment but provided for the sake of + * completeness */ +static bool max9850_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9850_STATUSA: + case MAX9850_STATUSB: + return true; + default: + return false; + } +} + +static const struct regmap_config max9850_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX9850_DIGITAL_AUDIO, + .volatile_reg = max9850_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static const DECLARE_TLV_DB_RANGE(max9850_tlv, + 0x18, 0x1f, TLV_DB_SCALE_ITEM(-7450, 400, 0), + 0x20, 0x33, TLV_DB_SCALE_ITEM(-4150, 200, 0), + 0x34, 0x37, TLV_DB_SCALE_ITEM(-150, 100, 0), + 0x38, 0x3f, TLV_DB_SCALE_ITEM(250, 50, 0) +); + +static const struct snd_kcontrol_new max9850_controls[] = { +SOC_SINGLE_TLV("Headphone Volume", MAX9850_VOLUME, 0, 0x3f, 1, max9850_tlv), +SOC_SINGLE("Headphone Switch", MAX9850_VOLUME, 7, 1, 1), +SOC_SINGLE("Mono Switch", MAX9850_GENERAL_PURPOSE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new max9850_mixer_controls[] = { + SOC_DAPM_SINGLE("Line In Switch", MAX9850_ENABLE, 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget max9850_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("Charge Pump 1", MAX9850_ENABLE, 4, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("Charge Pump 2", MAX9850_ENABLE, 5, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MCLK", MAX9850_ENABLE, 6, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("SHDN", MAX9850_ENABLE, 7, 0, NULL, 0), +SND_SOC_DAPM_MIXER_NAMED_CTL("Output Mixer", MAX9850_ENABLE, 2, 0, + &max9850_mixer_controls[0], + ARRAY_SIZE(max9850_mixer_controls)), +SND_SOC_DAPM_PGA("Headphone Output", MAX9850_ENABLE, 3, 0, NULL, 0), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", MAX9850_ENABLE, 0, 0), +SND_SOC_DAPM_OUTPUT("OUTL"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("OUTR"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_INPUT("INL"), +SND_SOC_DAPM_INPUT("INR"), +}; + +static const struct snd_soc_dapm_route max9850_dapm_routes[] = { + /* output mixer */ + {"Output Mixer", NULL, "DAC"}, + {"Output Mixer", "Line In Switch", "Line Input"}, + + /* outputs */ + {"Headphone Output", NULL, "Output Mixer"}, + {"HPL", NULL, "Headphone Output"}, + {"HPR", NULL, "Headphone Output"}, + {"OUTL", NULL, "Output Mixer"}, + {"OUTR", NULL, "Output Mixer"}, + + /* inputs */ + {"Line Input", NULL, "INL"}, + {"Line Input", NULL, "INR"}, + + /* supplies */ + {"Output Mixer", NULL, "Charge Pump 1"}, + {"Output Mixer", NULL, "Charge Pump 2"}, + {"Output Mixer", NULL, "SHDN"}, + {"DAC", NULL, "MCLK"}, +}; + +static int max9850_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max9850_priv *max9850 = snd_soc_component_get_drvdata(component); + u64 lrclk_div; + u8 sf, da; + + if (!max9850->sysclk) + return -EINVAL; + + /* lrclk_div = 2^22 * rate / iclk with iclk = mclk / sf */ + sf = (snd_soc_component_read32(component, MAX9850_CLOCK) >> 2) + 1; + lrclk_div = (1 << 22); + lrclk_div *= params_rate(params); + lrclk_div *= sf; + do_div(lrclk_div, max9850->sysclk); + + snd_soc_component_write(component, MAX9850_LRCLK_MSB, (lrclk_div >> 8) & 0x7f); + snd_soc_component_write(component, MAX9850_LRCLK_LSB, lrclk_div & 0xff); + + switch (params_width(params)) { + case 16: + da = 0; + break; + case 20: + da = 0x2; + break; + case 24: + da = 0x3; + break; + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, MAX9850_DIGITAL_AUDIO, 0x3, da); + + return 0; +} + +static int max9850_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct max9850_priv *max9850 = snd_soc_component_get_drvdata(component); + + /* calculate mclk -> iclk divider */ + if (freq <= 13000000) + snd_soc_component_write(component, MAX9850_CLOCK, 0x0); + else if (freq <= 26000000) + snd_soc_component_write(component, MAX9850_CLOCK, 0x4); + else if (freq <= 40000000) + snd_soc_component_write(component, MAX9850_CLOCK, 0x8); + else + return -EINVAL; + + max9850->sysclk = freq; + return 0; +} + +static int max9850_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 da = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + da |= MAX9850_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + da |= MAX9850_DLY; + break; + case SND_SOC_DAIFMT_RIGHT_J: + da |= MAX9850_RTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + da |= MAX9850_BCINV | MAX9850_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + da |= MAX9850_BCINV; + break; + case SND_SOC_DAIFMT_NB_IF: + da |= MAX9850_INV; + break; + default: + return -EINVAL; + } + + /* set da */ + snd_soc_component_write(component, MAX9850_DIGITAL_AUDIO, da); + + return 0; +} + +static int max9850_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct max9850_priv *max9850 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regcache_sync(max9850->regmap); + if (ret) { + dev_err(component->dev, + "Failed to sync cache: %d\n", ret); + return ret; + } + } + break; + case SND_SOC_BIAS_OFF: + break; + } + return 0; +} + +#define MAX9850_RATES SNDRV_PCM_RATE_8000_48000 + +#define MAX9850_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops max9850_dai_ops = { + .hw_params = max9850_hw_params, + .set_sysclk = max9850_set_dai_sysclk, + .set_fmt = max9850_set_dai_fmt, +}; + +static struct snd_soc_dai_driver max9850_dai = { + .name = "max9850-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX9850_RATES, + .formats = MAX9850_FORMATS + }, + .ops = &max9850_dai_ops, +}; + +static int max9850_probe(struct snd_soc_component *component) +{ + /* enable zero-detect */ + snd_soc_component_update_bits(component, MAX9850_GENERAL_PURPOSE, 1, 1); + /* enable slew-rate control */ + snd_soc_component_update_bits(component, MAX9850_VOLUME, 0x40, 0x40); + /* set slew-rate 125ms */ + snd_soc_component_update_bits(component, MAX9850_CHARGE_PUMP, 0xff, 0xc0); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_max9850 = { + .probe = max9850_probe, + .set_bias_level = max9850_set_bias_level, + .controls = max9850_controls, + .num_controls = ARRAY_SIZE(max9850_controls), + .dapm_widgets = max9850_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9850_dapm_widgets), + .dapm_routes = max9850_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max9850_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int max9850_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max9850_priv *max9850; + int ret; + + max9850 = devm_kzalloc(&i2c->dev, sizeof(struct max9850_priv), + GFP_KERNEL); + if (max9850 == NULL) + return -ENOMEM; + + max9850->regmap = devm_regmap_init_i2c(i2c, &max9850_regmap); + if (IS_ERR(max9850->regmap)) + return PTR_ERR(max9850->regmap); + + i2c_set_clientdata(i2c, max9850); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_max9850, &max9850_dai, 1); + return ret; +} + +static const struct i2c_device_id max9850_i2c_id[] = { + { "max9850", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9850_i2c_id); + +static struct i2c_driver max9850_i2c_driver = { + .driver = { + .name = "max9850", + }, + .probe = max9850_i2c_probe, + .id_table = max9850_i2c_id, +}; + +module_i2c_driver(max9850_i2c_driver); + +MODULE_AUTHOR("Christian Glindkamp "); +MODULE_DESCRIPTION("ASoC MAX9850 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9850.h b/sound/soc/codecs/max9850.h new file mode 100644 index 000000000..72b1ddb04 --- /dev/null +++ b/sound/soc/codecs/max9850.h @@ -0,0 +1,38 @@ +/* + * max9850.h -- codec driver for max9850 + * + * Copyright (C) 2011 taskit GmbH + * Author: Christian Glindkamp + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _MAX9850_H +#define _MAX9850_H + +#define MAX9850_STATUSA 0x00 +#define MAX9850_STATUSB 0x01 +#define MAX9850_VOLUME 0x02 +#define MAX9850_GENERAL_PURPOSE 0x03 +#define MAX9850_INTERRUPT 0x04 +#define MAX9850_ENABLE 0x05 +#define MAX9850_CLOCK 0x06 +#define MAX9850_CHARGE_PUMP 0x07 +#define MAX9850_LRCLK_MSB 0x08 +#define MAX9850_LRCLK_LSB 0x09 +#define MAX9850_DIGITAL_AUDIO 0x0a + +#define MAX9850_CACHEREGNUM 11 + +/* MAX9850_DIGITAL_AUDIO */ +#define MAX9850_MASTER (1<<7) +#define MAX9850_INV (1<<6) +#define MAX9850_BCINV (1<<5) +#define MAX9850_DLY (1<<3) +#define MAX9850_RTJ (1<<2) + +#endif diff --git a/sound/soc/codecs/max98504.c b/sound/soc/codecs/max98504.c new file mode 100644 index 000000000..a7320e709 --- /dev/null +++ b/sound/soc/codecs/max98504.c @@ -0,0 +1,383 @@ +/* + * MAX98504 ALSA SoC Audio driver + * + * Copyright 2013 - 2014 Maxim Integrated Products + * Copyright 2016 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "max98504.h" + +static const char * const max98504_supply_names[] = { + "DVDD", + "DIOVDD", + "PVDD", +}; +#define MAX98504_NUM_SUPPLIES ARRAY_SIZE(max98504_supply_names) + +struct max98504_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[MAX98504_NUM_SUPPLIES]; + unsigned int pcm_rx_channels; + bool brownout_enable; + unsigned int brownout_threshold; + unsigned int brownout_attenuation; + unsigned int brownout_attack_hold; + unsigned int brownout_timed_hold; + unsigned int brownout_release_rate; +}; + +static struct reg_default max98504_reg_defaults[] = { + { 0x01, 0}, + { 0x02, 0}, + { 0x03, 0}, + { 0x04, 0}, + { 0x10, 0}, + { 0x11, 0}, + { 0x12, 0}, + { 0x13, 0}, + { 0x14, 0}, + { 0x15, 0}, + { 0x16, 0}, + { 0x17, 0}, + { 0x18, 0}, + { 0x19, 0}, + { 0x1A, 0}, + { 0x20, 0}, + { 0x21, 0}, + { 0x22, 0}, + { 0x23, 0}, + { 0x24, 0}, + { 0x25, 0}, + { 0x26, 0}, + { 0x27, 0}, + { 0x28, 0}, + { 0x30, 0}, + { 0x31, 0}, + { 0x32, 0}, + { 0x33, 0}, + { 0x34, 0}, + { 0x35, 0}, + { 0x36, 0}, + { 0x37, 0}, + { 0x38, 0}, + { 0x39, 0}, + { 0x40, 0}, + { 0x41, 0}, +}; + +static bool max98504_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98504_INTERRUPT_STATUS: + case MAX98504_INTERRUPT_FLAGS: + case MAX98504_INTERRUPT_FLAG_CLEARS: + case MAX98504_WATCHDOG_CLEAR: + case MAX98504_GLOBAL_ENABLE: + case MAX98504_SOFTWARE_RESET: + return true; + default: + return false; + } +} + +static bool max98504_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98504_SOFTWARE_RESET: + case MAX98504_WATCHDOG_CLEAR: + case MAX98504_INTERRUPT_FLAG_CLEARS: + return false; + default: + return true; + } +} + +static int max98504_pcm_rx_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, + max98504->pcm_rx_channels); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, 0); + break; + } + + return 0; +} + +static int max98504_component_probe(struct snd_soc_component *c) +{ + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + struct regmap *map = max98504->regmap; + int ret; + + ret = regulator_bulk_enable(MAX98504_NUM_SUPPLIES, max98504->supplies); + if (ret < 0) + return ret; + + regmap_write(map, MAX98504_SOFTWARE_RESET, 0x1); + msleep(20); + + if (!max98504->brownout_enable) + return 0; + + regmap_write(map, MAX98504_PVDD_BROWNOUT_ENABLE, 0x1); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_1, + (max98504->brownout_threshold & 0x1f) << 3 | + (max98504->brownout_attenuation & 0x3)); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_2, + max98504->brownout_attack_hold & 0xff); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_3, + max98504->brownout_timed_hold & 0xff); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_4, + max98504->brownout_release_rate & 0xff); + + return 0; +} + +static void max98504_component_remove(struct snd_soc_component *c) +{ + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + + regulator_bulk_disable(MAX98504_NUM_SUPPLIES, max98504->supplies); +} + +static const char *spk_source_mux_text[] = { + "PCM Monomix", "Analog In", "PDM Left", "PDM Right" +}; + +static const struct soc_enum spk_source_mux_enum = + SOC_ENUM_SINGLE(MAX98504_SPEAKER_SOURCE_SELECT, + 0, ARRAY_SIZE(spk_source_mux_text), + spk_source_mux_text); + +static const struct snd_kcontrol_new spk_source_mux = + SOC_DAPM_ENUM("SPK Source", spk_source_mux_enum); + +static const struct snd_soc_dapm_route max98504_dapm_routes[] = { + { "SPKOUT", NULL, "Global Enable" }, + { "SPK Source", "PCM Monomix", "DAC PCM" }, + { "SPK Source", "Analog In", "AIN" }, + { "SPK Source", "PDM Left", "DAC PDM" }, + { "SPK Source", "PDM Right", "DAC PDM" }, +}; + +static const struct snd_soc_dapm_widget max98504_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98504_GLOBAL_ENABLE, + 0, 0, NULL, 0), + SND_SOC_DAPM_INPUT("AIN"), + SND_SOC_DAPM_AIF_OUT("AIF2OUTL", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2OUTR", "AIF2 Capture", 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC PCM", NULL, SND_SOC_NOPM, 0, 0, + max98504_pcm_rx_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC("DAC PDM", NULL, MAX98504_PDM_RX_ENABLE, 0, 0), + SND_SOC_DAPM_MUX("SPK Source", SND_SOC_NOPM, 0, 0, &spk_source_mux), + SND_SOC_DAPM_REG(snd_soc_dapm_spk, "SPKOUT", + MAX98504_SPEAKER_ENABLE, 0, 1, 1, 0), +}; + +static int max98504_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai); + struct regmap *map = max98504->regmap; + + + switch (dai->id) { + case MAX98504_DAI_ID_PCM: + regmap_write(map, MAX98504_PCM_TX_ENABLE, tx_mask); + max98504->pcm_rx_channels = rx_mask; + break; + + case MAX98504_DAI_ID_PDM: + regmap_write(map, MAX98504_PDM_TX_ENABLE, tx_mask); + break; + default: + WARN_ON(1); + } + + return 0; +} +static int max98504_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai); + struct regmap *map = max98504->regmap; + unsigned int i, sources = 0; + + for (i = 0; i < tx_num; i++) + if (tx_slot[i]) + sources |= (1 << i); + + switch (dai->id) { + case MAX98504_DAI_ID_PCM: + regmap_write(map, MAX98504_PCM_TX_CHANNEL_SOURCES, + sources); + break; + + case MAX98504_DAI_ID_PDM: + regmap_write(map, MAX98504_PDM_TX_CONTROL, sources); + break; + default: + WARN_ON(1); + } + + regmap_write(map, MAX98504_MEASUREMENT_ENABLE, sources ? 0x3 : 0x01); + + return 0; +} + +static const struct snd_soc_dai_ops max98504_dai_ops = { + .set_tdm_slot = max98504_set_tdm_slot, + .set_channel_map = max98504_set_channel_map, +}; + +#define MAX98504_FORMATS (SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|\ + SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE) +#define MAX98504_PDM_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\ + SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|\ + SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|\ + SNDRV_PCM_RATE_96000) + +static struct snd_soc_dai_driver max98504_dai[] = { + /* TODO: Add the PCM interface definitions */ + { + .name = "max98504-aif2", + .id = MAX98504_DAI_ID_PDM, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98504_PDM_RATES, + .formats = MAX98504_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98504_PDM_RATES, + .formats = MAX98504_FORMATS, + }, + .ops = &max98504_dai_ops, + }, +}; + +static const struct snd_soc_component_driver max98504_component_driver = { + .probe = max98504_component_probe, + .remove = max98504_component_remove, + .dapm_widgets = max98504_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98504_dapm_widgets), + .dapm_routes = max98504_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max98504_dapm_routes), +}; + +static const struct regmap_config max98504_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98504_MAX_REGISTER, + .reg_defaults = max98504_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max98504_reg_defaults), + .volatile_reg = max98504_volatile_register, + .readable_reg = max98504_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98504_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device_node *node = dev->of_node; + struct max98504_priv *max98504; + int i, ret; + + max98504 = devm_kzalloc(dev, sizeof(*max98504), GFP_KERNEL); + if (!max98504) + return -ENOMEM; + + if (node) { + if (!of_property_read_u32(node, "maxim,brownout-threshold", + &max98504->brownout_threshold)) + max98504->brownout_enable = true; + + of_property_read_u32(node, "maxim,brownout-attenuation", + &max98504->brownout_attenuation); + of_property_read_u32(node, "maxim,brownout-attack-hold-ms", + &max98504->brownout_attack_hold); + of_property_read_u32(node, "maxim,brownout-timed-hold-ms", + &max98504->brownout_timed_hold); + of_property_read_u32(node, "maxim,brownout-release-rate-ms", + &max98504->brownout_release_rate); + } + + max98504->regmap = devm_regmap_init_i2c(client, &max98504_regmap); + if (IS_ERR(max98504->regmap)) { + ret = PTR_ERR(max98504->regmap); + dev_err(&client->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + for (i = 0; i < MAX98504_NUM_SUPPLIES; i++) + max98504->supplies[i].supply = max98504_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, MAX98504_NUM_SUPPLIES, + max98504->supplies); + if (ret < 0) + return ret; + + i2c_set_clientdata(client, max98504); + + return devm_snd_soc_register_component(dev, &max98504_component_driver, + max98504_dai, ARRAY_SIZE(max98504_dai)); +} + +#ifdef CONFIG_OF +static const struct of_device_id max98504_of_match[] = { + { .compatible = "maxim,max98504" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max98504_of_match); +#endif + +static const struct i2c_device_id max98504_i2c_id[] = { + { "max98504" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98504_i2c_id); + +static struct i2c_driver max98504_i2c_driver = { + .driver = { + .name = "max98504", + .of_match_table = of_match_ptr(max98504_of_match), + }, + .probe = max98504_i2c_probe, + .id_table = max98504_i2c_id, +}; +module_i2c_driver(max98504_i2c_driver); + +MODULE_DESCRIPTION("ASoC MAX98504 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98504.h b/sound/soc/codecs/max98504.h new file mode 100644 index 000000000..afbefad2d --- /dev/null +++ b/sound/soc/codecs/max98504.h @@ -0,0 +1,59 @@ +/* + * MAX98504 ALSA SoC Audio driver + * + * Copyright 2011 - 2012 Maxim Integrated Products + * Copyright 2016 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef MAX98504_H_ +#define MAX98504_H_ + +/* + * MAX98504 Register Definitions + */ +#define MAX98504_INTERRUPT_STATUS 0x01 +#define MAX98504_INTERRUPT_FLAGS 0x02 +#define MAX98504_INTERRUPT_ENABLE 0x03 +#define MAX98504_INTERRUPT_FLAG_CLEARS 0x04 +#define MAX98504_GPIO_ENABLE 0x10 +#define MAX98504_GPIO_CONFIG 0x11 +#define MAX98504_WATCHDOG_ENABLE 0x12 +#define MAX98504_WATCHDOG_CONFIG 0x13 +#define MAX98504_WATCHDOG_CLEAR 0x14 +#define MAX98504_CLOCK_MONITOR_ENABLE 0x15 +#define MAX98504_PVDD_BROWNOUT_ENABLE 0x16 +#define MAX98504_PVDD_BROWNOUT_CONFIG_1 0x17 +#define MAX98504_PVDD_BROWNOUT_CONFIG_2 0x18 +#define MAX98504_PVDD_BROWNOUT_CONFIG_3 0x19 +#define MAX98504_PVDD_BROWNOUT_CONFIG_4 0x1a +#define MAX98504_PCM_RX_ENABLE 0x20 +#define MAX98504_PCM_TX_ENABLE 0x21 +#define MAX98504_PCM_TX_HIZ_CONTROL 0x22 +#define MAX98504_PCM_TX_CHANNEL_SOURCES 0x23 +#define MAX98504_PCM_MODE_CONFIG 0x24 +#define MAX98504_PCM_DSP_CONFIG 0x25 +#define MAX98504_PCM_CLOCK_SETUP 0x26 +#define MAX98504_PCM_SAMPLE_RATE_SETUP 0x27 +#define MAX98504_PCM_TO_SPEAKER_MONOMIX 0x28 +#define MAX98504_PDM_TX_ENABLE 0x30 +#define MAX98504_PDM_TX_HIZ_CONTROL 0x31 +#define MAX98504_PDM_TX_CONTROL 0x32 +#define MAX98504_PDM_RX_ENABLE 0x33 +#define MAX98504_SPEAKER_ENABLE 0x34 +#define MAX98504_SPEAKER_SOURCE_SELECT 0x35 +#define MAX98504_MEASUREMENT_ENABLE 0x36 +#define MAX98504_ANALOGUE_INPUT_GAIN 0x37 +#define MAX98504_TEMPERATURE_LIMIT_CONFIG 0x38 +#define MAX98504_GLOBAL_ENABLE 0x40 +#define MAX98504_SOFTWARE_RESET 0x41 +#define MAX98504_REV_ID 0x7fff + +#define MAX98504_MAX_REGISTER 0x7fff + +#define MAX98504_DAI_ID_PCM 1 +#define MAX98504_DAI_ID_PDM 2 + +#endif /* MAX98504_H_ */ diff --git a/sound/soc/codecs/max9860.c b/sound/soc/codecs/max9860.c new file mode 100644 index 000000000..de3d44e91 --- /dev/null +++ b/sound/soc/codecs/max9860.c @@ -0,0 +1,745 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for the MAX9860 Mono Audio Voice Codec +// +// https://datasheets.maximintegrated.com/en/ds/MAX9860.pdf +// +// The driver does not support sidetone since the DVST register field is +// backwards with the mute near the maximum level instead of the minimum. +// +// Author: Peter Rosin +// Copyright 2016 Axentia Technologies + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "max9860.h" + +struct max9860_priv { + struct regmap *regmap; + struct regulator *dvddio; + struct notifier_block dvddio_nb; + u8 psclk; + unsigned long pclk_rate; + int fmt; +}; + +static int max9860_dvddio_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct max9860_priv *max9860 = container_of(nb, struct max9860_priv, + dvddio_nb); + if (event & REGULATOR_EVENT_DISABLE) { + regcache_mark_dirty(max9860->regmap); + regcache_cache_only(max9860->regmap, true); + } + + return 0; +} + +static const struct reg_default max9860_reg_defaults[] = { + { MAX9860_PWRMAN, 0x00 }, + { MAX9860_INTEN, 0x00 }, + { MAX9860_SYSCLK, 0x00 }, + { MAX9860_AUDIOCLKHIGH, 0x00 }, + { MAX9860_AUDIOCLKLOW, 0x00 }, + { MAX9860_IFC1A, 0x00 }, + { MAX9860_IFC1B, 0x00 }, + { MAX9860_VOICEFLTR, 0x00 }, + { MAX9860_DACATTN, 0x00 }, + { MAX9860_ADCLEVEL, 0x00 }, + { MAX9860_DACGAIN, 0x00 }, + { MAX9860_MICGAIN, 0x00 }, + { MAX9860_MICADC, 0x00 }, + { MAX9860_NOISEGATE, 0x00 }, +}; + +static bool max9860_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS ... MAX9860_MICGAIN: + case MAX9860_MICADC ... MAX9860_PWRMAN: + case MAX9860_REVISION: + return true; + } + + return false; +} + +static bool max9860_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTEN ... MAX9860_MICGAIN: + case MAX9860_MICADC ... MAX9860_PWRMAN: + return true; + } + + return false; +} + +static bool max9860_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS: + case MAX9860_MICREADBACK: + return true; + } + + return false; +} + +static bool max9860_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS: + return true; + } + + return false; +} + +static const struct regmap_config max9860_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = max9860_readable, + .writeable_reg = max9860_writeable, + .volatile_reg = max9860_volatile, + .precious_reg = max9860_precious, + + .max_register = MAX9860_MAX_REGISTER, + .reg_defaults = max9860_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max9860_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static const DECLARE_TLV_DB_SCALE(dva_tlv, -9100, 100, 1); +static const DECLARE_TLV_DB_SCALE(dvg_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_RANGE(pam_tlv, + 0, MAX9860_PAM_MAX - 1, TLV_DB_SCALE_ITEM(-2000, 2000, 1), + MAX9860_PAM_MAX, MAX9860_PAM_MAX, TLV_DB_SCALE_ITEM(3000, 0, 0)); +static const DECLARE_TLV_DB_SCALE(pgam_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(anth_tlv, -7600, 400, 1); +static const DECLARE_TLV_DB_SCALE(agcth_tlv, -1800, 100, 0); + +static const char * const agchld_text[] = { + "AGC Disabled", "50ms", "100ms", "400ms" +}; + +static SOC_ENUM_SINGLE_DECL(agchld_enum, MAX9860_MICADC, + MAX9860_AGCHLD_SHIFT, agchld_text); + +static const char * const agcsrc_text[] = { + "Left ADC", "Left/Right ADC" +}; + +static SOC_ENUM_SINGLE_DECL(agcsrc_enum, MAX9860_MICADC, + MAX9860_AGCSRC_SHIFT, agcsrc_text); + +static const char * const agcatk_text[] = { + "3ms", "12ms", "50ms", "200ms" +}; + +static SOC_ENUM_SINGLE_DECL(agcatk_enum, MAX9860_MICADC, + MAX9860_AGCATK_SHIFT, agcatk_text); + +static const char * const agcrls_text[] = { + "78ms", "156ms", "312ms", "625ms", + "1.25s", "2.5s", "5s", "10s" +}; + +static SOC_ENUM_SINGLE_DECL(agcrls_enum, MAX9860_MICADC, + MAX9860_AGCRLS_SHIFT, agcrls_text); + +static const char * const filter_text[] = { + "Disabled", + "Elliptical HP 217Hz notch (16kHz)", + "Butterworth HP 500Hz (16kHz)", + "Elliptical HP 217Hz notch (8kHz)", + "Butterworth HP 500Hz (8kHz)", + "Butterworth HP 200Hz (48kHz)" +}; + +static SOC_ENUM_SINGLE_DECL(avflt_enum, MAX9860_VOICEFLTR, + MAX9860_AVFLT_SHIFT, filter_text); + +static SOC_ENUM_SINGLE_DECL(dvflt_enum, MAX9860_VOICEFLTR, + MAX9860_DVFLT_SHIFT, filter_text); + +static const struct snd_kcontrol_new max9860_controls[] = { +SOC_SINGLE_TLV("Master Playback Volume", MAX9860_DACATTN, + MAX9860_DVA_SHIFT, MAX9860_DVA_MUTE, 1, dva_tlv), +SOC_SINGLE_TLV("DAC Gain Volume", MAX9860_DACGAIN, + MAX9860_DVG_SHIFT, MAX9860_DVG_MAX, 0, dvg_tlv), +SOC_DOUBLE_TLV("Line Capture Volume", MAX9860_ADCLEVEL, + MAX9860_ADCLL_SHIFT, MAX9860_ADCRL_SHIFT, MAX9860_ADCxL_MIN, 1, + adc_tlv), + +SOC_ENUM("AGC Hold Time", agchld_enum), +SOC_ENUM("AGC/Noise Gate Source", agcsrc_enum), +SOC_ENUM("AGC Attack Time", agcatk_enum), +SOC_ENUM("AGC Release Time", agcrls_enum), + +SOC_SINGLE_TLV("Noise Gate Threshold Volume", MAX9860_NOISEGATE, + MAX9860_ANTH_SHIFT, MAX9860_ANTH_MAX, 0, anth_tlv), +SOC_SINGLE_TLV("AGC Signal Threshold Volume", MAX9860_NOISEGATE, + MAX9860_AGCTH_SHIFT, MAX9860_AGCTH_MIN, 1, agcth_tlv), + +SOC_SINGLE_TLV("Mic PGA Volume", MAX9860_MICGAIN, + MAX9860_PGAM_SHIFT, MAX9860_PGAM_MIN, 1, pgam_tlv), +SOC_SINGLE_TLV("Mic Preamp Volume", MAX9860_MICGAIN, + MAX9860_PAM_SHIFT, MAX9860_PAM_MAX, 0, pam_tlv), + +SOC_ENUM("ADC Filter", avflt_enum), +SOC_ENUM("DAC Filter", dvflt_enum), +}; + +static const struct snd_soc_dapm_widget max9860_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("MICL"), +SND_SOC_DAPM_INPUT("MICR"), + +SND_SOC_DAPM_ADC("ADCL", NULL, MAX9860_PWRMAN, MAX9860_ADCLEN_SHIFT, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, MAX9860_PWRMAN, MAX9860_ADCREN_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_DAC("DAC", NULL, MAX9860_PWRMAN, MAX9860_DACEN_SHIFT, 0), + +SND_SOC_DAPM_OUTPUT("OUT"), + +SND_SOC_DAPM_SUPPLY("Supply", SND_SOC_NOPM, 0, 0, + NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_REGULATOR_SUPPLY("AVDD", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DVDD", 0, 0), +SND_SOC_DAPM_CLOCK_SUPPLY("mclk"), +}; + +static const struct snd_soc_dapm_route max9860_dapm_routes[] = { + { "ADCL", NULL, "MICL" }, + { "ADCR", NULL, "MICR" }, + { "AIFOUTL", NULL, "ADCL" }, + { "AIFOUTR", NULL, "ADCR" }, + + { "DAC", NULL, "AIFINL" }, + { "DAC", NULL, "AIFINR" }, + { "OUT", NULL, "DAC" }, + + { "Supply", NULL, "AVDD" }, + { "Supply", NULL, "DVDD" }, + { "Supply", NULL, "mclk" }, + + { "DAC", NULL, "Supply" }, + { "ADCL", NULL, "Supply" }, + { "ADCR", NULL, "Supply" }, +}; + +static int max9860_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max9860_priv *max9860 = snd_soc_component_get_drvdata(component); + u8 master; + u8 ifc1a = 0; + u8 ifc1b = 0; + u8 sysclk = 0; + unsigned long n; + int ret; + + dev_dbg(component->dev, "hw_params %u Hz, %u channels\n", + params_rate(params), + params_channels(params)); + + if (params_channels(params) == 2) + ifc1b |= MAX9860_ST; + + switch (max9860->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + master = MAX9860_MASTER; + break; + default: + return -EINVAL; + } + ifc1a |= master; + + if (master) { + if (params_width(params) * params_channels(params) > 48) + ifc1b |= MAX9860_BSEL_64X; + else + ifc1b |= MAX9860_BSEL_48X; + } + + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ifc1a |= MAX9860_DDLY; + ifc1b |= MAX9860_ADLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + ifc1a |= MAX9860_WCI; + break; + case SND_SOC_DAIFMT_DSP_A: + if (params_width(params) != 16) { + dev_err(component->dev, + "DSP_A works for 16 bits per sample only.\n"); + return -EINVAL; + } + ifc1a |= MAX9860_DDLY | MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM; + ifc1b |= MAX9860_ADLY; + break; + case SND_SOC_DAIFMT_DSP_B: + if (params_width(params) != 16) { + dev_err(component->dev, + "DSP_B works for 16 bits per sample only.\n"); + return -EINVAL; + } + ifc1a |= MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM; + break; + default: + return -EINVAL; + } + + switch (max9860->fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + return -EINVAL; + } + ifc1a ^= MAX9860_WCI; + break; + case SND_SOC_DAIFMT_IB_IF: + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + return -EINVAL; + } + ifc1a ^= MAX9860_WCI; + /* fall through */ + case SND_SOC_DAIFMT_IB_NF: + ifc1a ^= MAX9860_DBCI; + ifc1b ^= MAX9860_ABCI; + break; + default: + return -EINVAL; + } + + dev_dbg(component->dev, "IFC1A %02x\n", ifc1a); + ret = regmap_write(max9860->regmap, MAX9860_IFC1A, ifc1a); + if (ret) { + dev_err(component->dev, "Failed to set IFC1A: %d\n", ret); + return ret; + } + dev_dbg(component->dev, "IFC1B %02x\n", ifc1b); + ret = regmap_write(max9860->regmap, MAX9860_IFC1B, ifc1b); + if (ret) { + dev_err(component->dev, "Failed to set IFC1B: %d\n", ret); + return ret; + } + + /* + * Check if Integer Clock Mode is possible, but avoid it in slave mode + * since we then do not know if lrclk is derived from pclk and the + * datasheet mentions that the frequencies have to match exactly in + * order for this to work. + */ + if (params_rate(params) == 8000 || params_rate(params) == 16000) { + if (master) { + switch (max9860->pclk_rate) { + case 12000000: + sysclk = MAX9860_FREQ_12MHZ; + break; + case 13000000: + sysclk = MAX9860_FREQ_13MHZ; + break; + case 19200000: + sysclk = MAX9860_FREQ_19_2MHZ; + break; + default: + /* + * Integer Clock Mode not possible. Leave + * sysclk at zero and fall through to the + * code below for PLL mode. + */ + break; + } + + if (sysclk && params_rate(params) == 16000) + sysclk |= MAX9860_16KHZ; + } + } + + /* + * Largest possible n: + * 65536 * 96 * 48kHz / 10MHz -> 30199 + * Smallest possible n: + * 65536 * 96 * 8kHz / 20MHz -> 2517 + * Both fit nicely in the available 15 bits, no need to apply any mask. + */ + n = DIV_ROUND_CLOSEST_ULL(65536ULL * 96 * params_rate(params), + max9860->pclk_rate); + + if (!sysclk) { + /* PLL mode */ + if (params_rate(params) > 24000) + sysclk |= MAX9860_16KHZ; + + if (!master) + n |= 1; /* trigger rapid pll lock mode */ + } + + sysclk |= max9860->psclk; + dev_dbg(component->dev, "SYSCLK %02x\n", sysclk); + ret = regmap_write(max9860->regmap, + MAX9860_SYSCLK, sysclk); + if (ret) { + dev_err(component->dev, "Failed to set SYSCLK: %d\n", ret); + return ret; + } + dev_dbg(component->dev, "N %lu\n", n); + ret = regmap_write(max9860->regmap, + MAX9860_AUDIOCLKHIGH, n >> 8); + if (ret) { + dev_err(component->dev, "Failed to set NHI: %d\n", ret); + return ret; + } + ret = regmap_write(max9860->regmap, + MAX9860_AUDIOCLKLOW, n & 0xff); + if (ret) { + dev_err(component->dev, "Failed to set NLO: %d\n", ret); + return ret; + } + + if (!master) { + dev_dbg(component->dev, "Enable PLL\n"); + ret = regmap_update_bits(max9860->regmap, MAX9860_AUDIOCLKHIGH, + MAX9860_PLL, MAX9860_PLL); + if (ret) { + dev_err(component->dev, "Failed to enable PLL: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int max9860_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct max9860_priv *max9860 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + max9860->fmt = fmt; + return 0; + + default: + return -EINVAL; + } +} + +static const struct snd_soc_dai_ops max9860_dai_ops = { + .hw_params = max9860_hw_params, + .set_fmt = max9860_set_fmt, +}; + +static struct snd_soc_dai_driver max9860_dai = { + .name = "max9860-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &max9860_dai_ops, + .symmetric_rates = 1, +}; + +static int max9860_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct max9860_priv *max9860 = dev_get_drvdata(component->dev); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN, + MAX9860_SHDN, MAX9860_SHDN); + if (ret) { + dev_err(component->dev, "Failed to remove SHDN: %d\n", + ret); + return ret; + } + break; + + case SND_SOC_BIAS_OFF: + ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN, + MAX9860_SHDN, 0); + if (ret) { + dev_err(component->dev, "Failed to request SHDN: %d\n", + ret); + return ret; + } + break; + } + + return 0; +} + +static const struct snd_soc_component_driver max9860_component_driver = { + .set_bias_level = max9860_set_bias_level, + .controls = max9860_controls, + .num_controls = ARRAY_SIZE(max9860_controls), + .dapm_widgets = max9860_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9860_dapm_widgets), + .dapm_routes = max9860_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max9860_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +#ifdef CONFIG_PM +static int max9860_suspend(struct device *dev) +{ + struct max9860_priv *max9860 = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK, + MAX9860_PSCLK, MAX9860_PSCLK_OFF); + if (ret) { + dev_err(dev, "Failed to disable clock: %d\n", ret); + return ret; + } + + regulator_disable(max9860->dvddio); + + return 0; +} + +static int max9860_resume(struct device *dev) +{ + struct max9860_priv *max9860 = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(max9860->dvddio); + if (ret) { + dev_err(dev, "Failed to enable DVDDIO: %d\n", ret); + return ret; + } + + regcache_cache_only(max9860->regmap, false); + ret = regcache_sync(max9860->regmap); + if (ret) { + dev_err(dev, "Failed to sync cache: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK, + MAX9860_PSCLK, max9860->psclk); + if (ret) { + dev_err(dev, "Failed to enable clock: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops max9860_pm_ops = { + SET_RUNTIME_PM_OPS(max9860_suspend, max9860_resume, NULL) +}; + +static int max9860_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct max9860_priv *max9860; + int ret; + struct clk *mclk; + unsigned long mclk_rate; + int i; + int intr; + + max9860 = devm_kzalloc(dev, sizeof(struct max9860_priv), GFP_KERNEL); + if (!max9860) + return -ENOMEM; + + max9860->dvddio = devm_regulator_get(dev, "DVDDIO"); + if (IS_ERR(max9860->dvddio)) { + ret = PTR_ERR(max9860->dvddio); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get DVDDIO supply: %d\n", ret); + return ret; + } + + max9860->dvddio_nb.notifier_call = max9860_dvddio_event; + + ret = regulator_register_notifier(max9860->dvddio, &max9860->dvddio_nb); + if (ret) + dev_err(dev, "Failed to register DVDDIO notifier: %d\n", ret); + + ret = regulator_enable(max9860->dvddio); + if (ret != 0) { + dev_err(dev, "Failed to enable DVDDIO: %d\n", ret); + return ret; + } + + max9860->regmap = devm_regmap_init_i2c(i2c, &max9860_regmap); + if (IS_ERR(max9860->regmap)) { + ret = PTR_ERR(max9860->regmap); + goto err_regulator; + } + + dev_set_drvdata(dev, max9860); + + /* + * mclk has to be in the 10MHz to 60MHz range. + * psclk is used to scale mclk into pclk so that + * pclk is in the 10MHz to 20MHz range. + */ + mclk = clk_get(dev, "mclk"); + + if (IS_ERR(mclk)) { + ret = PTR_ERR(mclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get MCLK: %d\n", ret); + goto err_regulator; + } + + mclk_rate = clk_get_rate(mclk); + clk_put(mclk); + + if (mclk_rate > 60000000 || mclk_rate < 10000000) { + dev_err(dev, "Bad mclk %luHz (needs 10MHz - 60MHz)\n", + mclk_rate); + ret = -EINVAL; + goto err_regulator; + } + if (mclk_rate >= 40000000) + max9860->psclk = 3; + else if (mclk_rate >= 20000000) + max9860->psclk = 2; + else + max9860->psclk = 1; + max9860->pclk_rate = mclk_rate >> (max9860->psclk - 1); + max9860->psclk <<= MAX9860_PSCLK_SHIFT; + dev_dbg(dev, "mclk %lu pclk %lu\n", mclk_rate, max9860->pclk_rate); + + regcache_cache_bypass(max9860->regmap, true); + for (i = 0; i < max9860_regmap.num_reg_defaults; ++i) { + ret = regmap_write(max9860->regmap, + max9860_regmap.reg_defaults[i].reg, + max9860_regmap.reg_defaults[i].def); + if (ret) { + dev_err(dev, "Failed to initialize register %u: %d\n", + max9860_regmap.reg_defaults[i].reg, ret); + goto err_regulator; + } + } + regcache_cache_bypass(max9860->regmap, false); + + ret = regmap_read(max9860->regmap, MAX9860_INTRSTATUS, &intr); + if (ret) { + dev_err(dev, "Failed to clear INTRSTATUS: %d\n", ret); + goto err_regulator; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = devm_snd_soc_register_component(dev, &max9860_component_driver, + &max9860_dai, 1); + if (ret) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + goto err_pm; + } + + return 0; + +err_pm: + pm_runtime_disable(dev); +err_regulator: + regulator_disable(max9860->dvddio); + return ret; +} + +static int max9860_remove(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct max9860_priv *max9860 = dev_get_drvdata(dev); + + pm_runtime_disable(dev); + regulator_disable(max9860->dvddio); + return 0; +} + +static const struct i2c_device_id max9860_i2c_id[] = { + { "max9860", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9860_i2c_id); + +static const struct of_device_id max9860_of_match[] = { + { .compatible = "maxim,max9860", }, + { } +}; +MODULE_DEVICE_TABLE(of, max9860_of_match); + +static struct i2c_driver max9860_i2c_driver = { + .probe_new = max9860_probe, + .remove = max9860_remove, + .id_table = max9860_i2c_id, + .driver = { + .name = "max9860", + .of_match_table = max9860_of_match, + .pm = &max9860_pm_ops, + }, +}; + +module_i2c_driver(max9860_i2c_driver); + +MODULE_DESCRIPTION("ASoC MAX9860 Mono Audio Voice Codec driver"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/max9860.h b/sound/soc/codecs/max9860.h new file mode 100644 index 000000000..e07b905ea --- /dev/null +++ b/sound/soc/codecs/max9860.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for the MAX9860 Mono Audio Voice Codec + * + * Author: Peter Rosin + * Copyright 2016 Axentia Technologies + */ + +#ifndef _SND_SOC_MAX9860 +#define _SND_SOC_MAX9860 + +#define MAX9860_INTRSTATUS 0x00 +#define MAX9860_MICREADBACK 0x01 +#define MAX9860_INTEN 0x02 +#define MAX9860_SYSCLK 0x03 +#define MAX9860_AUDIOCLKHIGH 0x04 +#define MAX9860_AUDIOCLKLOW 0x05 +#define MAX9860_IFC1A 0x06 +#define MAX9860_IFC1B 0x07 +#define MAX9860_VOICEFLTR 0x08 +#define MAX9860_DACATTN 0x09 +#define MAX9860_ADCLEVEL 0x0a +#define MAX9860_DACGAIN 0x0b +#define MAX9860_MICGAIN 0x0c +#define MAX9860_RESERVED 0x0d +#define MAX9860_MICADC 0x0e +#define MAX9860_NOISEGATE 0x0f +#define MAX9860_PWRMAN 0x10 +#define MAX9860_REVISION 0xff + +#define MAX9860_MAX_REGISTER 0xff + +/* INTRSTATUS */ +#define MAX9860_CLD 0x80 +#define MAX9860_SLD 0x40 +#define MAX9860_ULK 0x20 + +/* MICREADBACK */ +#define MAX9860_NG 0xe0 +#define MAX9860_AGC 0x1f + +/* INTEN */ +#define MAX9860_ICLD 0x80 +#define MAX9860_ISLD 0x40 +#define MAX9860_IULK 0x20 + +/* SYSCLK */ +#define MAX9860_PSCLK 0x30 +#define MAX9860_PSCLK_OFF 0x00 +#define MAX9860_PSCLK_SHIFT 4 +#define MAX9860_FREQ 0x06 +#define MAX9860_FREQ_NORMAL 0x00 +#define MAX9860_FREQ_12MHZ 0x02 +#define MAX9860_FREQ_13MHZ 0x04 +#define MAX9860_FREQ_19_2MHZ 0x06 +#define MAX9860_16KHZ 0x01 + +/* AUDIOCLKHIGH */ +#define MAX9860_PLL 0x80 +#define MAX9860_NHI 0x7f + +/* AUDIOCLKLOW */ +#define MAX9860_NLO 0xff + +/* IFC1A */ +#define MAX9860_MASTER 0x80 +#define MAX9860_WCI 0x40 +#define MAX9860_DBCI 0x20 +#define MAX9860_DDLY 0x10 +#define MAX9860_HIZ 0x08 +#define MAX9860_TDM 0x04 + +/* IFC1B */ +#define MAX9860_ABCI 0x20 +#define MAX9860_ADLY 0x10 +#define MAX9860_ST 0x08 +#define MAX9860_BSEL 0x07 +#define MAX9860_BSEL_OFF 0x00 +#define MAX9860_BSEL_64X 0x01 +#define MAX9860_BSEL_48X 0x02 +#define MAX9860_BSEL_PCLK_2 0x04 +#define MAX9860_BSEL_PCLK_4 0x05 +#define MAX9860_BSEL_PCLK_8 0x06 +#define MAX9860_BSEL_PCLK_16 0x07 + +/* VOICEFLTR */ +#define MAX9860_AVFLT 0xf0 +#define MAX9860_AVFLT_SHIFT 4 +#define MAX9860_AVFLT_COUNT 6 +#define MAX9860_DVFLT 0x0f +#define MAX9860_DVFLT_SHIFT 0 +#define MAX9860_DVFLT_COUNT 6 + +/* DACATTN */ +#define MAX9860_DVA 0xfe +#define MAX9860_DVA_SHIFT 1 +#define MAX9860_DVA_MUTE 0x5e + +/* ADCLEVEL */ +#define MAX9860_ADCRL 0xf0 +#define MAX9860_ADCRL_SHIFT 4 +#define MAX9860_ADCLL 0x0f +#define MAX9860_ADCLL_SHIFT 0 +#define MAX9860_ADCxL_MIN 15 + +/* DACGAIN */ +#define MAX9860_DVG 0x60 +#define MAX9860_DVG_SHIFT 5 +#define MAX9860_DVG_MAX 3 +#define MAX9860_DVST 0x1f +#define MAX9860_DVST_SHIFT 0 +#define MAX9860_DVST_MIN 31 + +/* MICGAIN */ +#define MAX9860_PAM 0x60 +#define MAX9860_PAM_SHIFT 5 +#define MAX9860_PAM_MAX 3 +#define MAX9860_PGAM 0x1f +#define MAX9860_PGAM_SHIFT 0 +#define MAX9860_PGAM_MIN 20 + +/* MICADC */ +#define MAX9860_AGCSRC 0x80 +#define MAX9860_AGCSRC_SHIFT 7 +#define MAX9860_AGCSRC_COUNT 2 +#define MAX9860_AGCRLS 0x70 +#define MAX9860_AGCRLS_SHIFT 4 +#define MAX9860_AGCRLS_COUNT 8 +#define MAX9860_AGCATK 0x0c +#define MAX9860_AGCATK_SHIFT 2 +#define MAX9860_AGCATK_COUNT 4 +#define MAX9860_AGCHLD 0x03 +#define MAX9860_AGCHLD_OFF 0x00 +#define MAX9860_AGCHLD_SHIFT 0 +#define MAX9860_AGCHLD_COUNT 4 + +/* NOISEGATE */ +#define MAX9860_ANTH 0xf0 +#define MAX9860_ANTH_SHIFT 4 +#define MAX9860_ANTH_MAX 15 +#define MAX9860_AGCTH 0x0f +#define MAX9860_AGCTH_SHIFT 0 +#define MAX9860_AGCTH_MIN 15 + +/* PWRMAN */ +#define MAX9860_SHDN 0x80 +#define MAX9860_DACEN 0x08 +#define MAX9860_DACEN_SHIFT 3 +#define MAX9860_ADCLEN 0x02 +#define MAX9860_ADCLEN_SHIFT 1 +#define MAX9860_ADCREN 0x01 +#define MAX9860_ADCREN_SHIFT 0 + +#endif /* _SND_SOC_MAX9860 */ diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c new file mode 100644 index 000000000..e51143df4 --- /dev/null +++ b/sound/soc/codecs/max9867.c @@ -0,0 +1,557 @@ +/* + * max9867.c -- max9867 ALSA SoC Audio driver + * + * Copyright 2013-15 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "max9867.h" + +static const char *const max9867_spmode[] = { + "Stereo Diff", "Mono Diff", + "Stereo Cap", "Mono Cap", + "Stereo Single", "Mono Single", + "Stereo Single Fast", "Mono Single Fast" +}; +static const char *const max9867_sidetone_text[] = { + "None", "Left", "Right", "LeftRight", "LeftRightDiv2", +}; +static const char *const max9867_filter_text[] = {"IIR", "FIR"}; + +static SOC_ENUM_SINGLE_DECL(max9867_filter, MAX9867_CODECFLTR, 7, + max9867_filter_text); +static SOC_ENUM_SINGLE_DECL(max9867_spkmode, MAX9867_MODECONFIG, 0, + max9867_spmode); +static SOC_ENUM_SINGLE_DECL(max9867_sidetone, MAX9867_DACGAIN, 6, + max9867_sidetone_text); +static DECLARE_TLV_DB_SCALE(max9860_capture_tlv, -600, 200, 0); +static DECLARE_TLV_DB_SCALE(max9860_mic_tlv, 2000, 100, 1); +static DECLARE_TLV_DB_SCALE(max9860_adc_left_tlv, -1200, 100, 1); +static DECLARE_TLV_DB_SCALE(max9860_adc_right_tlv, -1200, 100, 1); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(max98088_micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0), + 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0), +); + +static const struct snd_kcontrol_new max9867_snd_controls[] = { + SOC_DOUBLE_R("Master Playback Volume", MAX9867_LEFTVOL, + MAX9867_RIGHTVOL, 0, 63, 1), + SOC_DOUBLE_R_TLV("Capture Volume", MAX9867_LEFTMICGAIN, + MAX9867_RIGHTMICGAIN, + 0, 15, 1, max9860_capture_tlv), + SOC_DOUBLE_R_TLV("Mic Volume", MAX9867_LEFTMICGAIN, + MAX9867_RIGHTMICGAIN, 0, 31, 1, max9860_mic_tlv), + SOC_DOUBLE_R_TLV("Mic Boost Volume", MAX9867_LEFTMICGAIN, + MAX9867_RIGHTMICGAIN, 5, 3, 0, max98088_micboost_tlv), + SOC_ENUM("Digital Sidetone Src", max9867_sidetone), + SOC_SINGLE("Sidetone Volume", MAX9867_DACGAIN, 0, 31, 1), + SOC_SINGLE("DAC Volume", MAX9867_DACLEVEL, 4, 3, 0), + SOC_SINGLE("DAC Attenuation", MAX9867_DACLEVEL, 0, 15, 1), + SOC_SINGLE_TLV("ADC Left Volume", MAX9867_ADCLEVEL, + 4, 15, 1, max9860_adc_left_tlv), + SOC_SINGLE_TLV("ADC Right Volume", MAX9867_ADCLEVEL, + 0, 15, 1, max9860_adc_right_tlv), + SOC_ENUM("Speaker Mode", max9867_spkmode), + SOC_SINGLE("Volume Smoothing Switch", MAX9867_MODECONFIG, 6, 1, 0), + SOC_SINGLE("ZCD Switch", MAX9867_MODECONFIG, 5, 1, 0), + SOC_ENUM("DSP Filter", max9867_filter), +}; + +static const char *const max9867_mux[] = {"None", "Mic", "Line", "Mic_Line"}; + +static SOC_ENUM_SINGLE_DECL(max9867_mux_enum, + MAX9867_INPUTCONFIG, MAX9867_INPUT_SHIFT, + max9867_mux); + +static const struct snd_kcontrol_new max9867_dapm_mux_controls = + SOC_DAPM_ENUM("Route", max9867_mux_enum); + +static const struct snd_kcontrol_new max9867_left_dapm_control = + SOC_DAPM_SINGLE("Switch", MAX9867_PWRMAN, 6, 1, 0); +static const struct snd_kcontrol_new max9867_right_dapm_control = + SOC_DAPM_SINGLE("Switch", MAX9867_PWRMAN, 5, 1, 0); +static const struct snd_kcontrol_new max9867_line_dapm_control = + SOC_DAPM_SINGLE("Switch", MAX9867_LEFTLINELVL, 6, 1, 1); + +static const struct snd_soc_dapm_widget max9867_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAI_OUT", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Left DAC", NULL, MAX9867_PWRMAN, 3, 0), + SND_SOC_DAPM_DAC("Right DAC", NULL, MAX9867_PWRMAN, 2, 0), + SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("HPOUT"), + + SND_SOC_DAPM_AIF_IN("DAI_IN", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("Left ADC", "HiFi Capture", MAX9867_PWRMAN, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "HiFi Capture", MAX9867_PWRMAN, 0, 0), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &max9867_dapm_mux_controls), + + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("Left Line", MAX9867_LEFTLINELVL, 6, 1, + &max9867_left_dapm_control), + SND_SOC_DAPM_SWITCH("Right Line", MAX9867_RIGTHLINELVL, 6, 1, + &max9867_right_dapm_control), + SND_SOC_DAPM_SWITCH("Line Mixer", SND_SOC_NOPM, 0, 0, + &max9867_line_dapm_control), + SND_SOC_DAPM_INPUT("LINE_IN"), +}; + +static const struct snd_soc_dapm_route max9867_audio_map[] = { + {"Left DAC", NULL, "DAI_OUT"}, + {"Right DAC", NULL, "DAI_OUT"}, + {"Output Mixer", NULL, "Left DAC"}, + {"Output Mixer", NULL, "Right DAC"}, + {"HPOUT", NULL, "Output Mixer"}, + + {"Left ADC", NULL, "DAI_IN"}, + {"Right ADC", NULL, "DAI_IN"}, + {"Input Mixer", NULL, "Left ADC"}, + {"Input Mixer", NULL, "Right ADC"}, + {"Input Mux", "Line", "Input Mixer"}, + {"Input Mux", "Mic", "Input Mixer"}, + {"Input Mux", "Mic_Line", "Input Mixer"}, + {"Right Line", "Switch", "Input Mux"}, + {"Left Line", "Switch", "Input Mux"}, + {"LINE_IN", NULL, "Left Line"}, + {"LINE_IN", NULL, "Right Line"}, +}; + +enum rates { + pcm_rate_8, pcm_rate_16, pcm_rate_24, + pcm_rate_32, pcm_rate_44, + pcm_rate_48, max_pcm_rate, +}; + +static const struct ni_div_rates { + u32 mclk; + u16 ni[max_pcm_rate]; +} ni_div[] = { + {11289600, {0x116A, 0x22D4, 0x343F, 0x45A9, 0x6000, 0x687D} }, + {12000000, {0x1062, 0x20C5, 0x3127, 0x4189, 0x5A51, 0x624E} }, + {12288000, {0x1000, 0x2000, 0x3000, 0x4000, 0x5833, 0x6000} }, + {13000000, {0x0F20, 0x1E3F, 0x2D5F, 0x3C7F, 0x535F, 0x5ABE} }, + {19200000, {0x0A3D, 0x147B, 0x1EB8, 0x28F6, 0x3873, 0x3D71} }, + {24000000, {0x1062, 0x20C5, 0x1893, 0x4189, 0x5A51, 0x624E} }, + {26000000, {0x0F20, 0x1E3F, 0x16AF, 0x3C7F, 0x535F, 0x5ABE} }, + {27000000, {0x0E90, 0x1D21, 0x15D8, 0x3A41, 0x5048, 0x5762} }, +}; + +static inline int get_ni_value(int mclk, int rate) +{ + int i, ret = 0; + + /* find the closest rate index*/ + for (i = 0; i < ARRAY_SIZE(ni_div); i++) { + if (ni_div[i].mclk >= mclk) + break; + } + if (i == ARRAY_SIZE(ni_div)) + return -EINVAL; + + switch (rate) { + case 8000: + return ni_div[i].ni[pcm_rate_8]; + case 16000: + return ni_div[i].ni[pcm_rate_16]; + case 32000: + return ni_div[i].ni[pcm_rate_32]; + case 44100: + return ni_div[i].ni[pcm_rate_44]; + case 48000: + return ni_div[i].ni[pcm_rate_48]; + default: + pr_err("%s wrong rate %d\n", __func__, rate); + ret = -EINVAL; + } + return ret; +} + +static int max9867_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max9867_priv *max9867 = snd_soc_component_get_drvdata(component); + unsigned int ni_h, ni_l; + int value; + + value = get_ni_value(max9867->sysclk, params_rate(params)); + if (value < 0) + return value; + + ni_h = (0xFF00 & value) >> 8; + ni_l = 0x00FF & value; + /* set up the ni value */ + regmap_update_bits(max9867->regmap, MAX9867_AUDIOCLKHIGH, + MAX9867_NI_HIGH_MASK, ni_h); + regmap_update_bits(max9867->regmap, MAX9867_AUDIOCLKLOW, + MAX9867_NI_LOW_MASK, ni_l); + if (!max9867->master) { + /* + * digital pll locks on to any externally supplied LRCLK signal + * and also enable rapid lock mode. + */ + regmap_update_bits(max9867->regmap, MAX9867_AUDIOCLKLOW, + MAX9867_RAPID_LOCK, MAX9867_RAPID_LOCK); + regmap_update_bits(max9867->regmap, MAX9867_AUDIOCLKHIGH, + MAX9867_PLL, MAX9867_PLL); + } else { + unsigned long int bclk_rate, pclk_bclk_ratio; + int bclk_value; + + bclk_rate = params_rate(params) * 2 * params_width(params); + pclk_bclk_ratio = max9867->pclk/bclk_rate; + switch (params_width(params)) { + case 8: + case 16: + switch (pclk_bclk_ratio) { + case 2: + bclk_value = MAX9867_IFC1B_PCLK_2; + break; + case 4: + bclk_value = MAX9867_IFC1B_PCLK_4; + break; + case 8: + bclk_value = MAX9867_IFC1B_PCLK_8; + break; + case 16: + bclk_value = MAX9867_IFC1B_PCLK_16; + break; + default: + dev_err(component->dev, + "unsupported sampling rate\n"); + return -EINVAL; + } + break; + case 24: + bclk_value = MAX9867_IFC1B_24BIT; + break; + case 32: + bclk_value = MAX9867_IFC1B_32BIT; + break; + default: + dev_err(component->dev, "unsupported sampling rate\n"); + return -EINVAL; + } + regmap_update_bits(max9867->regmap, MAX9867_IFC1B, + MAX9867_IFC1B_BCLK_MASK, bclk_value); + } + return 0; +} + +static int max9867_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct max9867_priv *max9867 = snd_soc_component_get_drvdata(component); + + if (mute) + regmap_update_bits(max9867->regmap, MAX9867_DACLEVEL, + MAX9867_DAC_MUTE_MASK, MAX9867_DAC_MUTE_MASK); + else + regmap_update_bits(max9867->regmap, MAX9867_DACLEVEL, + MAX9867_DAC_MUTE_MASK, 0); + return 0; +} + +static int max9867_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct max9867_priv *max9867 = snd_soc_component_get_drvdata(component); + int value = 0; + + /* Set the prescaler based on the master clock frequency*/ + if (freq >= 10000000 && freq <= 20000000) { + value |= MAX9867_PSCLK_10_20; + max9867->pclk = freq; + } else if (freq >= 20000000 && freq <= 40000000) { + value |= MAX9867_PSCLK_20_40; + max9867->pclk = freq/2; + } else if (freq >= 40000000 && freq <= 60000000) { + value |= MAX9867_PSCLK_40_60; + max9867->pclk = freq/4; + } else { + dev_err(component->dev, + "Invalid clock frequency %uHz (required 10-60MHz)\n", + freq); + return -EINVAL; + } + value = value << MAX9867_PSCLK_SHIFT; + max9867->sysclk = freq; + /* exact integer mode is not supported */ + value &= ~MAX9867_FREQ_MASK; + regmap_update_bits(max9867->regmap, MAX9867_SYSCLK, + MAX9867_PSCLK_MASK, value); + return 0; +} + +static int max9867_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max9867_priv *max9867 = snd_soc_component_get_drvdata(component); + u8 iface1A = 0, iface1B = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + max9867->master = 1; + iface1A |= MAX9867_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + max9867->master = 0; + iface1A &= ~MAX9867_MASTER; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface1A |= MAX9867_I2S_DLY; + break; + case SND_SOC_DAIFMT_DSP_A: + iface1A |= MAX9867_TDM_MODE | MAX9867_SDOUT_HIZ; + break; + default: + return -EINVAL; + } + + /* Clock inversion bits, BCI and WCI */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface1A |= MAX9867_WCI_MODE | MAX9867_BCI_MODE; + break; + case SND_SOC_DAIFMT_IB_NF: + iface1A |= MAX9867_BCI_MODE; + break; + case SND_SOC_DAIFMT_NB_IF: + iface1A |= MAX9867_WCI_MODE; + break; + default: + return -EINVAL; + } + + regmap_write(max9867->regmap, MAX9867_IFC1A, iface1A); + regmap_write(max9867->regmap, MAX9867_IFC1B, iface1B); + return 0; +} + +static const struct snd_soc_dai_ops max9867_dai_ops = { + .set_fmt = max9867_dai_set_fmt, + .set_sysclk = max9867_set_dai_sysclk, + .digital_mute = max9867_mute, + .hw_params = max9867_dai_hw_params, +}; + +#define MAX9867_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define MAX9867_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_driver max9867_dai[] = { + { + .name = "max9867-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = MAX9867_RATES, + .formats = MAX9867_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 2, + .rates = MAX9867_RATES, + .formats = MAX9867_FORMATS, + }, + .ops = &max9867_dai_ops, + .symmetric_rates = 1, + } +}; + +#ifdef CONFIG_PM +static int max9867_suspend(struct snd_soc_component *component) +{ + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + return 0; +} + +static int max9867_resume(struct snd_soc_component *component) +{ + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define max9867_suspend NULL +#define max9867_resume NULL +#endif + +static int max9867_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int err; + struct max9867_priv *max9867 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + err = regcache_sync(max9867->regmap); + if (err) + return err; + + err = regmap_update_bits(max9867->regmap, MAX9867_PWRMAN, + MAX9867_SHTDOWN, MAX9867_SHTDOWN); + if (err) + return err; + } + break; + case SND_SOC_BIAS_OFF: + err = regmap_update_bits(max9867->regmap, MAX9867_PWRMAN, + MAX9867_SHTDOWN, 0); + if (err) + return err; + + regcache_mark_dirty(max9867->regmap); + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_component_driver max9867_component = { + .controls = max9867_snd_controls, + .num_controls = ARRAY_SIZE(max9867_snd_controls), + .dapm_routes = max9867_audio_map, + .num_dapm_routes = ARRAY_SIZE(max9867_audio_map), + .dapm_widgets = max9867_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9867_dapm_widgets), + .suspend = max9867_suspend, + .resume = max9867_resume, + .set_bias_level = max9867_set_bias_level, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static bool max9867_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9867_STATUS: + case MAX9867_JACKSTATUS: + case MAX9867_AUXHIGH: + case MAX9867_AUXLOW: + return true; + default: + return false; + } +} + +static const struct reg_default max9867_reg[] = { + { 0x04, 0x00 }, + { 0x05, 0x00 }, + { 0x06, 0x00 }, + { 0x07, 0x00 }, + { 0x08, 0x00 }, + { 0x09, 0x00 }, + { 0x0A, 0x00 }, + { 0x0B, 0x00 }, + { 0x0C, 0x00 }, + { 0x0D, 0x00 }, + { 0x0E, 0x00 }, + { 0x0F, 0x00 }, + { 0x10, 0x00 }, + { 0x11, 0x00 }, + { 0x12, 0x00 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x00 }, + { 0x16, 0x00 }, + { 0x17, 0x00 }, +}; + +static const struct regmap_config max9867_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX9867_REVISION, + .reg_defaults = max9867_reg, + .num_reg_defaults = ARRAY_SIZE(max9867_reg), + .volatile_reg = max9867_volatile_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max9867_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max9867_priv *max9867; + int ret = 0, reg; + + max9867 = devm_kzalloc(&i2c->dev, + sizeof(*max9867), GFP_KERNEL); + if (!max9867) + return -ENOMEM; + + i2c_set_clientdata(i2c, max9867); + max9867->regmap = devm_regmap_init_i2c(i2c, &max9867_regmap); + if (IS_ERR(max9867->regmap)) { + ret = PTR_ERR(max9867->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + ret = regmap_read(max9867->regmap, + MAX9867_REVISION, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read: %d\n", ret); + return ret; + } + dev_info(&i2c->dev, "device revision: %x\n", reg); + ret = devm_snd_soc_register_component(&i2c->dev, &max9867_component, + max9867_dai, ARRAY_SIZE(max9867_dai)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register component: %d\n", ret); + return ret; + } + return ret; +} + +static const struct i2c_device_id max9867_i2c_id[] = { + { "max9867", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9867_i2c_id); + +static const struct of_device_id max9867_of_match[] = { + { .compatible = "maxim,max9867", }, + { } +}; +MODULE_DEVICE_TABLE(of, max9867_of_match); + +static struct i2c_driver max9867_i2c_driver = { + .driver = { + .name = "max9867", + .of_match_table = of_match_ptr(max9867_of_match), + }, + .probe = max9867_i2c_probe, + .id_table = max9867_i2c_id, +}; + +module_i2c_driver(max9867_i2c_driver); + +MODULE_AUTHOR("anish kumar "); +MODULE_DESCRIPTION("ALSA SoC MAX9867 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9867.h b/sound/soc/codecs/max9867.h new file mode 100644 index 000000000..d9170850c --- /dev/null +++ b/sound/soc/codecs/max9867.h @@ -0,0 +1,82 @@ +/* + * max9867.h -- MAX9867 ALSA SoC Audio driver + * + * Copyright 2013-2015 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MAX9867_H +#define _MAX9867_H + +/* MAX9867 register space */ + +#define MAX9867_STATUS 0x00 +#define MAX9867_JACKSTATUS 0x01 +#define MAX9867_AUXHIGH 0x02 +#define MAX9867_AUXLOW 0x03 +#define MAX9867_INTEN 0x04 +#define MAX9867_SYSCLK 0x05 +#define MAX9867_FREQ_MASK 0xF +#define MAX9867_PSCLK_SHIFT 0x4 +#define MAX9867_PSCLK_WIDTH 0x2 +#define MAX9867_PSCLK_MASK (0x03< + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "max9877.h" + +static const struct reg_default max9877_regs[] = { + { 0, 0x40 }, + { 1, 0x00 }, + { 2, 0x00 }, + { 3, 0x00 }, + { 4, 0x49 }, +}; + +static const DECLARE_TLV_DB_RANGE(max9877_pgain_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 900, 0), + 2, 2, TLV_DB_SCALE_ITEM(2000, 0, 0) +); + +static const DECLARE_TLV_DB_RANGE(max9877_output_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1), + 8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0), + 16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0), + 24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0) +); + +static const char *max9877_out_mode[] = { + "INA -> SPK", + "INA -> HP", + "INA -> SPK and HP", + "INB -> SPK", + "INB -> HP", + "INB -> SPK and HP", + "INA + INB -> SPK", + "INA + INB -> HP", + "INA + INB -> SPK and HP", +}; + +static const char *max9877_osc_mode[] = { + "1176KHz", + "1100KHz", + "700KHz", +}; + +static const struct soc_enum max9877_enum[] = { + SOC_ENUM_SINGLE(MAX9877_OUTPUT_MODE, 0, ARRAY_SIZE(max9877_out_mode), + max9877_out_mode), + SOC_ENUM_SINGLE(MAX9877_OUTPUT_MODE, MAX9877_OSC_OFFSET, + ARRAY_SIZE(max9877_osc_mode), max9877_osc_mode), +}; + +static const struct snd_kcontrol_new max9877_controls[] = { + SOC_SINGLE_TLV("MAX9877 PGAINA Playback Volume", + MAX9877_INPUT_MODE, 0, 2, 0, max9877_pgain_tlv), + SOC_SINGLE_TLV("MAX9877 PGAINB Playback Volume", + MAX9877_INPUT_MODE, 2, 2, 0, max9877_pgain_tlv), + SOC_SINGLE_TLV("MAX9877 Amp Speaker Playback Volume", + MAX9877_SPK_VOLUME, 0, 31, 0, max9877_output_tlv), + SOC_DOUBLE_R_TLV("MAX9877 Amp HP Playback Volume", + MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0, + max9877_output_tlv), + SOC_SINGLE("MAX9877 INB Stereo Switch", + MAX9877_INPUT_MODE, 4, 1, 1), + SOC_SINGLE("MAX9877 INA Stereo Switch", + MAX9877_INPUT_MODE, 5, 1, 1), + SOC_SINGLE("MAX9877 Zero-crossing detection Switch", + MAX9877_INPUT_MODE, 6, 1, 0), + SOC_SINGLE("MAX9877 Bypass Mode Switch", + MAX9877_OUTPUT_MODE, 6, 1, 0), + SOC_ENUM("MAX9877 Output Mode", max9877_enum[0]), + SOC_ENUM("MAX9877 Oscillator Mode", max9877_enum[1]), +}; + +static const struct snd_soc_dapm_widget max9877_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("INA1"), +SND_SOC_DAPM_INPUT("INA2"), +SND_SOC_DAPM_INPUT("INB1"), +SND_SOC_DAPM_INPUT("INB2"), +SND_SOC_DAPM_INPUT("RXIN+"), +SND_SOC_DAPM_INPUT("RXIN-"), + +SND_SOC_DAPM_PGA("SHDN", MAX9877_OUTPUT_MODE, 7, 1, NULL, 0), + +SND_SOC_DAPM_OUTPUT("OUT+"), +SND_SOC_DAPM_OUTPUT("OUT-"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_route max9877_dapm_routes[] = { + { "SHDN", NULL, "INA1" }, + { "SHDN", NULL, "INA2" }, + { "SHDN", NULL, "INB1" }, + { "SHDN", NULL, "INB2" }, + + { "OUT+", NULL, "RXIN+" }, + { "OUT+", NULL, "SHDN" }, + + { "OUT-", NULL, "SHDN" }, + { "OUT-", NULL, "RXIN-" }, + + { "HPL", NULL, "SHDN" }, + { "HPR", NULL, "SHDN" }, +}; + +static const struct snd_soc_component_driver max9877_component_driver = { + .controls = max9877_controls, + .num_controls = ARRAY_SIZE(max9877_controls), + + .dapm_widgets = max9877_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9877_dapm_widgets), + .dapm_routes = max9877_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max9877_dapm_routes), +}; + +static const struct regmap_config max9877_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .reg_defaults = max9877_regs, + .num_reg_defaults = ARRAY_SIZE(max9877_regs), + .cache_type = REGCACHE_RBTREE, +}; + +static int max9877_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + int i; + + regmap = devm_regmap_init_i2c(client, &max9877_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Ensure the device is in reset state */ + for (i = 0; i < ARRAY_SIZE(max9877_regs); i++) + regmap_write(regmap, max9877_regs[i].reg, max9877_regs[i].def); + + return devm_snd_soc_register_component(&client->dev, + &max9877_component_driver, NULL, 0); +} + +static const struct i2c_device_id max9877_i2c_id[] = { + { "max9877", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9877_i2c_id); + +static struct i2c_driver max9877_i2c_driver = { + .driver = { + .name = "max9877", + }, + .probe = max9877_i2c_probe, + .id_table = max9877_i2c_id, +}; + +module_i2c_driver(max9877_i2c_driver); + +MODULE_DESCRIPTION("ASoC MAX9877 amp driver"); +MODULE_AUTHOR("Joonyoung Shim "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9877.h b/sound/soc/codecs/max9877.h new file mode 100644 index 000000000..368343f29 --- /dev/null +++ b/sound/soc/codecs/max9877.h @@ -0,0 +1,35 @@ +/* + * max9877.h -- amp driver for max9877 + * + * Copyright (C) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _MAX9877_H +#define _MAX9877_H + +#define MAX9877_INPUT_MODE 0x00 +#define MAX9877_SPK_VOLUME 0x01 +#define MAX9877_HPL_VOLUME 0x02 +#define MAX9877_HPR_VOLUME 0x03 +#define MAX9877_OUTPUT_MODE 0x04 + +/* MAX9877_INPUT_MODE */ +#define MAX9877_INB (1 << 4) +#define MAX9877_INA (1 << 5) +#define MAX9877_ZCD (1 << 6) + +/* MAX9877_OUTPUT_MODE */ +#define MAX9877_OUTMODE_MASK (15 << 0) +#define MAX9877_OSC_MASK (3 << 4) +#define MAX9877_OSC_OFFSET 4 +#define MAX9877_BYPASS (1 << 6) +#define MAX9877_SHDN (1 << 7) + +#endif diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c new file mode 100644 index 000000000..29877730a --- /dev/null +++ b/sound/soc/codecs/max98925.c @@ -0,0 +1,652 @@ +/* + * max98925.c -- ALSA SoC Stereo MAX98925 driver + * Copyright 2013-15 Maxim Integrated Products + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98925.h" + +static const char *const dai_text[] = { + "Left", "Right", "LeftRight", "LeftRightDiv2", +}; + +static const char * const max98925_boost_voltage_text[] = { + "8.5V", "8.25V", "8.0V", "7.75V", "7.5V", "7.25V", "7.0V", "6.75V", + "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V" +}; + +static SOC_ENUM_SINGLE_DECL(max98925_boost_voltage, + MAX98925_CONFIGURATION, M98925_BST_VOUT_SHIFT, + max98925_boost_voltage_text); + +static const char *const hpf_text[] = { + "Disable", "DC Block", "100Hz", "200Hz", "400Hz", "800Hz", +}; + +static const struct reg_default max98925_reg[] = { + { 0x0B, 0x00 }, /* IRQ Enable0 */ + { 0x0C, 0x00 }, /* IRQ Enable1 */ + { 0x0D, 0x00 }, /* IRQ Enable2 */ + { 0x0E, 0x00 }, /* IRQ Clear0 */ + { 0x0F, 0x00 }, /* IRQ Clear1 */ + { 0x10, 0x00 }, /* IRQ Clear2 */ + { 0x11, 0xC0 }, /* Map0 */ + { 0x12, 0x00 }, /* Map1 */ + { 0x13, 0x00 }, /* Map2 */ + { 0x14, 0xF0 }, /* Map3 */ + { 0x15, 0x00 }, /* Map4 */ + { 0x16, 0xAB }, /* Map5 */ + { 0x17, 0x89 }, /* Map6 */ + { 0x18, 0x00 }, /* Map7 */ + { 0x19, 0x00 }, /* Map8 */ + { 0x1A, 0x06 }, /* DAI Clock Mode 1 */ + { 0x1B, 0xC0 }, /* DAI Clock Mode 2 */ + { 0x1C, 0x00 }, /* DAI Clock Divider Denominator MSBs */ + { 0x1D, 0x00 }, /* DAI Clock Divider Denominator LSBs */ + { 0x1E, 0xF0 }, /* DAI Clock Divider Numerator MSBs */ + { 0x1F, 0x00 }, /* DAI Clock Divider Numerator LSBs */ + { 0x20, 0x50 }, /* Format */ + { 0x21, 0x00 }, /* TDM Slot Select */ + { 0x22, 0x00 }, /* DOUT Configuration VMON */ + { 0x23, 0x00 }, /* DOUT Configuration IMON */ + { 0x24, 0x00 }, /* DOUT Configuration VBAT */ + { 0x25, 0x00 }, /* DOUT Configuration VBST */ + { 0x26, 0x00 }, /* DOUT Configuration FLAG */ + { 0x27, 0xFF }, /* DOUT HiZ Configuration 1 */ + { 0x28, 0xFF }, /* DOUT HiZ Configuration 2 */ + { 0x29, 0xFF }, /* DOUT HiZ Configuration 3 */ + { 0x2A, 0xFF }, /* DOUT HiZ Configuration 4 */ + { 0x2B, 0x02 }, /* DOUT Drive Strength */ + { 0x2C, 0x90 }, /* Filters */ + { 0x2D, 0x00 }, /* Gain */ + { 0x2E, 0x02 }, /* Gain Ramping */ + { 0x2F, 0x00 }, /* Speaker Amplifier */ + { 0x30, 0x0A }, /* Threshold */ + { 0x31, 0x00 }, /* ALC Attack */ + { 0x32, 0x80 }, /* ALC Atten and Release */ + { 0x33, 0x00 }, /* ALC Infinite Hold Release */ + { 0x34, 0x92 }, /* ALC Configuration */ + { 0x35, 0x01 }, /* Boost Converter */ + { 0x36, 0x00 }, /* Block Enable */ + { 0x37, 0x00 }, /* Configuration */ + { 0x38, 0x00 }, /* Global Enable */ + { 0x3A, 0x00 }, /* Boost Limiter */ +}; + +static const struct soc_enum max98925_dai_enum = + SOC_ENUM_SINGLE(MAX98925_GAIN, 5, ARRAY_SIZE(dai_text), dai_text); + +static const struct soc_enum max98925_hpf_enum = + SOC_ENUM_SINGLE(MAX98925_FILTERS, 0, ARRAY_SIZE(hpf_text), hpf_text); + +static const struct snd_kcontrol_new max98925_hpf_sel_mux = + SOC_DAPM_ENUM("Rc Filter MUX Mux", max98925_hpf_enum); + +static const struct snd_kcontrol_new max98925_dai_sel_mux = + SOC_DAPM_ENUM("DAI IN MUX Mux", max98925_dai_enum); + +static int max98925_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct max98925_priv *max98925 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(max98925->regmap, + MAX98925_BLOCK_ENABLE, + M98925_BST_EN_MASK | + M98925_ADC_IMON_EN_MASK | M98925_ADC_VMON_EN_MASK, + M98925_BST_EN_MASK | + M98925_ADC_IMON_EN_MASK | M98925_ADC_VMON_EN_MASK); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(max98925->regmap, + MAX98925_BLOCK_ENABLE, M98925_BST_EN_MASK | + M98925_ADC_IMON_EN_MASK | M98925_ADC_VMON_EN_MASK, 0); + break; + default: + return 0; + } + return 0; +} + +static const struct snd_soc_dapm_widget max98925_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAI_OUT", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("DAI IN MUX", SND_SOC_NOPM, 0, 0, + &max98925_dai_sel_mux), + SND_SOC_DAPM_MUX("Rc Filter MUX", SND_SOC_NOPM, 0, 0, + &max98925_hpf_sel_mux), + SND_SOC_DAPM_DAC_E("Amp Enable", NULL, MAX98925_BLOCK_ENABLE, + M98925_SPK_EN_SHIFT, 0, max98925_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98925_GLOBAL_ENABLE, + M98925_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("BE_OUT"), +}; + +static const struct snd_soc_dapm_route max98925_audio_map[] = { + {"DAI IN MUX", "Left", "DAI_OUT"}, + {"DAI IN MUX", "Right", "DAI_OUT"}, + {"DAI IN MUX", "LeftRight", "DAI_OUT"}, + {"DAI IN MUX", "LeftRightDiv2", "DAI_OUT"}, + {"Rc Filter MUX", "Disable", "DAI IN MUX"}, + {"Rc Filter MUX", "DC Block", "DAI IN MUX"}, + {"Rc Filter MUX", "100Hz", "DAI IN MUX"}, + {"Rc Filter MUX", "200Hz", "DAI IN MUX"}, + {"Rc Filter MUX", "400Hz", "DAI IN MUX"}, + {"Rc Filter MUX", "800Hz", "DAI IN MUX"}, + {"Amp Enable", NULL, "Rc Filter MUX"}, + {"BE_OUT", NULL, "Amp Enable"}, + {"BE_OUT", NULL, "Global Enable"}, +}; + +static bool max98925_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98925_VBAT_DATA: + case MAX98925_VBST_DATA: + case MAX98925_LIVE_STATUS0: + case MAX98925_LIVE_STATUS1: + case MAX98925_LIVE_STATUS2: + case MAX98925_STATE0: + case MAX98925_STATE1: + case MAX98925_STATE2: + case MAX98925_FLAG0: + case MAX98925_FLAG1: + case MAX98925_FLAG2: + case MAX98925_REV_VERSION: + return true; + default: + return false; + } +} + +static bool max98925_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98925_IRQ_CLEAR0: + case MAX98925_IRQ_CLEAR1: + case MAX98925_IRQ_CLEAR2: + case MAX98925_ALC_HOLD_RLS: + return false; + default: + return true; + } +} + +static DECLARE_TLV_DB_SCALE(max98925_spk_tlv, -600, 100, 0); + +static const struct snd_kcontrol_new max98925_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98925_GAIN, + M98925_SPK_GAIN_SHIFT, (1<= rate) { + *value = rate_table[i].sr; + *n = rate_table[i].divisors[clock][0]; + *m = rate_table[i].divisors[clock][1]; + ret = 0; + break; + } + } + return ret; +} + +static void max98925_set_sense_data(struct max98925_priv *max98925) +{ + /* set VMON slots */ + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_VMON, + M98925_DAI_VMON_EN_MASK, M98925_DAI_VMON_EN_MASK); + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_VMON, + M98925_DAI_VMON_SLOT_MASK, + max98925->v_slot << M98925_DAI_VMON_SLOT_SHIFT); + /* set IMON slots */ + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_IMON, + M98925_DAI_IMON_EN_MASK, M98925_DAI_IMON_EN_MASK); + regmap_update_bits(max98925->regmap, + MAX98925_DOUT_CFG_IMON, + M98925_DAI_IMON_SLOT_MASK, + max98925->i_slot << M98925_DAI_IMON_SLOT_SHIFT); +} + +static int max98925_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98925_priv *max98925 = snd_soc_component_get_drvdata(component); + unsigned int invert = 0; + + dev_dbg(component->dev, "%s: fmt 0x%08X\n", __func__, fmt); + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* set DAI to slave mode */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_MAS_MASK, 0); + max98925_set_sense_data(max98925); + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* + * set left channel DAI to master mode, + * right channel always slave + */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_MAS_MASK, M98925_DAI_MAS_MASK); + break; + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + dev_err(component->dev, "DAI clock mode unsupported"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + invert = M98925_DAI_WCI_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + invert = M98925_DAI_BCI_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + invert = M98925_DAI_BCI_MASK | M98925_DAI_WCI_MASK; + break; + default: + dev_err(component->dev, "DAI invert mode unsupported"); + return -EINVAL; + } + + regmap_update_bits(max98925->regmap, MAX98925_FORMAT, + M98925_DAI_BCI_MASK | M98925_DAI_WCI_MASK, invert); + return 0; +} + +static int max98925_set_clock(struct max98925_priv *max98925, + struct snd_pcm_hw_params *params) +{ + unsigned int dai_sr = 0, clock, mdll, n, m; + struct snd_soc_component *component = max98925->component; + int rate = params_rate(params); + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98925->ch_size; + + switch (blr_clk_ratio) { + case 32: + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_BSEL_MASK, M98925_DAI_BSEL_32); + break; + case 48: + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_BSEL_MASK, M98925_DAI_BSEL_48); + break; + case 64: + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_BSEL_MASK, M98925_DAI_BSEL_64); + break; + default: + return -EINVAL; + } + + switch (max98925->sysclk) { + case 6000000: + clock = 0; + mdll = M98925_MDLL_MULT_MCLKx16; + break; + case 11289600: + clock = 1; + mdll = M98925_MDLL_MULT_MCLKx8; + break; + case 12000000: + clock = 0; + mdll = M98925_MDLL_MULT_MCLKx8; + break; + case 12288000: + clock = 2; + mdll = M98925_MDLL_MULT_MCLKx8; + break; + default: + dev_info(max98925->component->dev, "unsupported sysclk %d\n", + max98925->sysclk); + return -EINVAL; + } + + if (max98925_rate_value(component, rate, clock, &dai_sr, &n, &m)) + return -EINVAL; + + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE2, + M98925_DAI_SR_MASK, dai_sr << M98925_DAI_SR_SHIFT); + /* set DAI m divider */ + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_M_MSBS, m >> 8); + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_M_LSBS, m & 0xFF); + /* set DAI n divider */ + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_N_MSBS, n >> 8); + regmap_write(max98925->regmap, + MAX98925_DAI_CLK_DIV_N_LSBS, n & 0xFF); + /* set MDLL */ + regmap_update_bits(max98925->regmap, MAX98925_DAI_CLK_MODE1, + M98925_MDLL_MULT_MASK, mdll << M98925_MDLL_MULT_SHIFT); + return 0; +} + +static int max98925_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98925_priv *max98925 = snd_soc_component_get_drvdata(component); + + switch (params_width(params)) { + case 16: + regmap_update_bits(max98925->regmap, + MAX98925_FORMAT, + M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_16); + max98925->ch_size = 16; + break; + case 24: + regmap_update_bits(max98925->regmap, + MAX98925_FORMAT, + M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_24); + max98925->ch_size = 24; + break; + case 32: + regmap_update_bits(max98925->regmap, + MAX98925_FORMAT, + M98925_DAI_CHANSZ_MASK, M98925_DAI_CHANSZ_32); + max98925->ch_size = 32; + break; + default: + pr_err("%s: format unsupported %d", + __func__, params_format(params)); + return -EINVAL; + } + dev_dbg(component->dev, "%s: format supported %d", + __func__, params_format(params)); + return max98925_set_clock(max98925, params); +} + +static int max98925_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct max98925_priv *max98925 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case 0: + /* use MCLK for Left channel, right channel always BCLK */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE1, + M98925_DAI_CLK_SOURCE_MASK, 0); + break; + case 1: + /* configure dai clock source to BCLK instead of MCLK */ + regmap_update_bits(max98925->regmap, + MAX98925_DAI_CLK_MODE1, + M98925_DAI_CLK_SOURCE_MASK, + M98925_DAI_CLK_SOURCE_MASK); + break; + default: + return -EINVAL; + } + max98925->sysclk = freq; + return 0; +} + +#define MAX98925_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops max98925_dai_ops = { + .set_sysclk = max98925_dai_set_sysclk, + .set_fmt = max98925_dai_set_fmt, + .hw_params = max98925_dai_hw_params, +}; + +static struct snd_soc_dai_driver max98925_dai[] = { + { + .name = "max98925-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98925_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98925_FORMATS, + }, + .ops = &max98925_dai_ops, + } +}; + +static int max98925_probe(struct snd_soc_component *component) +{ + struct max98925_priv *max98925 = snd_soc_component_get_drvdata(component); + + max98925->component = component; + regmap_write(max98925->regmap, MAX98925_GLOBAL_ENABLE, 0x00); + /* It's not the default but we need to set DAI_DLY */ + regmap_write(max98925->regmap, + MAX98925_FORMAT, M98925_DAI_DLY_MASK); + regmap_write(max98925->regmap, MAX98925_TDM_SLOT_SELECT, 0xC8); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG1, 0xFF); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG2, 0xFF); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG3, 0xFF); + regmap_write(max98925->regmap, MAX98925_DOUT_HIZ_CFG4, 0xF0); + regmap_write(max98925->regmap, MAX98925_FILTERS, 0xD8); + regmap_write(max98925->regmap, MAX98925_ALC_CONFIGURATION, 0xF8); + regmap_write(max98925->regmap, MAX98925_CONFIGURATION, 0xF0); + /* Disable ALC muting */ + regmap_write(max98925->regmap, MAX98925_BOOST_LIMITER, 0xF8); + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_max98925 = { + .probe = max98925_probe, + .controls = max98925_snd_controls, + .num_controls = ARRAY_SIZE(max98925_snd_controls), + .dapm_routes = max98925_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98925_audio_map), + .dapm_widgets = max98925_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98925_dapm_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config max98925_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX98925_REV_VERSION, + .reg_defaults = max98925_reg, + .num_reg_defaults = ARRAY_SIZE(max98925_reg), + .volatile_reg = max98925_volatile_register, + .readable_reg = max98925_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98925_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret, reg; + u32 value; + struct max98925_priv *max98925; + + max98925 = devm_kzalloc(&i2c->dev, + sizeof(*max98925), GFP_KERNEL); + if (!max98925) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98925); + max98925->regmap = devm_regmap_init_i2c(i2c, &max98925_regmap); + if (IS_ERR(max98925->regmap)) { + ret = PTR_ERR(max98925->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + if (!of_property_read_u32(i2c->dev.of_node, "vmon-slot-no", &value)) { + if (value > M98925_DAI_VMON_SLOT_1E_1F) { + dev_err(&i2c->dev, "vmon slot number is wrong:\n"); + return -EINVAL; + } + max98925->v_slot = value; + } + if (!of_property_read_u32(i2c->dev.of_node, "imon-slot-no", &value)) { + if (value > M98925_DAI_IMON_SLOT_1E_1F) { + dev_err(&i2c->dev, "imon slot number is wrong:\n"); + return -EINVAL; + } + max98925->i_slot = value; + } + + ret = regmap_read(max98925->regmap, MAX98925_REV_VERSION, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Read revision failed\n"); + return ret; + } + + if ((reg != MAX98925_VERSION) && (reg != MAX98925_VERSION1)) { + ret = -ENODEV; + dev_err(&i2c->dev, "Invalid revision (%d 0x%02X)\n", + ret, reg); + return ret; + } + + dev_info(&i2c->dev, "device version 0x%02X\n", reg); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_max98925, + max98925_dai, ARRAY_SIZE(max98925_dai)); + if (ret < 0) + dev_err(&i2c->dev, + "Failed to register component: %d\n", ret); + return ret; +} + +static const struct i2c_device_id max98925_i2c_id[] = { + { "max98925", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98925_i2c_id); + +static const struct of_device_id max98925_of_match[] = { + { .compatible = "maxim,max98925", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98925_of_match); + +static struct i2c_driver max98925_i2c_driver = { + .driver = { + .name = "max98925", + .of_match_table = of_match_ptr(max98925_of_match), + .pm = NULL, + }, + .probe = max98925_i2c_probe, + .id_table = max98925_i2c_id, +}; + +module_i2c_driver(max98925_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98925 driver"); +MODULE_AUTHOR("Ralph Birt , Anish kumar "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98925.h b/sound/soc/codecs/max98925.h new file mode 100644 index 000000000..96f970853 --- /dev/null +++ b/sound/soc/codecs/max98925.h @@ -0,0 +1,832 @@ +/* + * max98925.h -- MAX98925 ALSA SoC Audio driver + * + * Copyright 2013-2015 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MAX98925_H +#define _MAX98925_H + +#define MAX98925_VERSION 0x51 +#define MAX98925_VERSION1 0x80 +#define MAX98925_VBAT_DATA 0x00 +#define MAX98925_VBST_DATA 0x01 +#define MAX98925_LIVE_STATUS0 0x02 +#define MAX98925_LIVE_STATUS1 0x03 +#define MAX98925_LIVE_STATUS2 0x04 +#define MAX98925_STATE0 0x05 +#define MAX98925_STATE1 0x06 +#define MAX98925_STATE2 0x07 +#define MAX98925_FLAG0 0x08 +#define MAX98925_FLAG1 0x09 +#define MAX98925_FLAG2 0x0A +#define MAX98925_IRQ_ENABLE0 0x0B +#define MAX98925_IRQ_ENABLE1 0x0C +#define MAX98925_IRQ_ENABLE2 0x0D +#define MAX98925_IRQ_CLEAR0 0x0E +#define MAX98925_IRQ_CLEAR1 0x0F +#define MAX98925_IRQ_CLEAR2 0x10 +#define MAX98925_MAP0 0x11 +#define MAX98925_MAP1 0x12 +#define MAX98925_MAP2 0x13 +#define MAX98925_MAP3 0x14 +#define MAX98925_MAP4 0x15 +#define MAX98925_MAP5 0x16 +#define MAX98925_MAP6 0x17 +#define MAX98925_MAP7 0x18 +#define MAX98925_MAP8 0x19 +#define MAX98925_DAI_CLK_MODE1 0x1A +#define MAX98925_DAI_CLK_MODE2 0x1B +#define MAX98925_DAI_CLK_DIV_M_MSBS 0x1C +#define MAX98925_DAI_CLK_DIV_M_LSBS 0x1D +#define MAX98925_DAI_CLK_DIV_N_MSBS 0x1E +#define MAX98925_DAI_CLK_DIV_N_LSBS 0x1F +#define MAX98925_FORMAT 0x20 +#define MAX98925_TDM_SLOT_SELECT 0x21 +#define MAX98925_DOUT_CFG_VMON 0x22 +#define MAX98925_DOUT_CFG_IMON 0x23 +#define MAX98925_DOUT_CFG_VBAT 0x24 +#define MAX98925_DOUT_CFG_VBST 0x25 +#define MAX98925_DOUT_CFG_FLAG 0x26 +#define MAX98925_DOUT_HIZ_CFG1 0x27 +#define MAX98925_DOUT_HIZ_CFG2 0x28 +#define MAX98925_DOUT_HIZ_CFG3 0x29 +#define MAX98925_DOUT_HIZ_CFG4 0x2A +#define MAX98925_DOUT_DRV_STRENGTH 0x2B +#define MAX98925_FILTERS 0x2C +#define MAX98925_GAIN 0x2D +#define MAX98925_GAIN_RAMPING 0x2E +#define MAX98925_SPK_AMP 0x2F +#define MAX98925_THRESHOLD 0x30 +#define MAX98925_ALC_ATTACK 0x31 +#define MAX98925_ALC_ATTEN_RLS 0x32 +#define MAX98925_ALC_HOLD_RLS 0x33 +#define MAX98925_ALC_CONFIGURATION 0x34 +#define MAX98925_BOOST_CONVERTER 0x35 +#define MAX98925_BLOCK_ENABLE 0x36 +#define MAX98925_CONFIGURATION 0x37 +#define MAX98925_GLOBAL_ENABLE 0x38 +#define MAX98925_BOOST_LIMITER 0x3A +#define MAX98925_REV_VERSION 0xFF + +#define MAX98925_REG_CNT (MAX98925_R03A_BOOST_LIMITER+1) + +/* MAX98925 Register Bit Fields */ + +/* MAX98925_R002_LIVE_STATUS0 */ +#define M98925_THERMWARN_STATUS_MASK (1<<3) +#define M98925_THERMWARN_STATUS_SHIFT 3 +#define M98925_THERMWARN_STATUS_WIDTH 1 +#define M98925_THERMSHDN_STATUS_MASK (1<<1) +#define M98925_THERMSHDN_STATUS_SHIFT 1 +#define M98925_THERMSHDN_STATUS_WIDTH 1 + +/* MAX98925_R003_LIVE_STATUS1 */ +#define M98925_SPKCURNT_STATUS_MASK (1<<5) +#define M98925_SPKCURNT_STATUS_SHIFT 5 +#define M98925_SPKCURNT_STATUS_WIDTH 1 +#define M98925_WATCHFAIL_STATUS_MASK (1<<4) +#define M98925_WATCHFAIL_STATUS_SHIFT 4 +#define M98925_WATCHFAIL_STATUS_WIDTH 1 +#define M98925_ALCINFH_STATUS_MASK (1<<3) +#define M98925_ALCINFH_STATUS_SHIFT 3 +#define M98925_ALCINFH_STATUS_WIDTH 1 +#define M98925_ALCACT_STATUS_MASK (1<<2) +#define M98925_ALCACT_STATUS_SHIFT 2 +#define M98925_ALCACT_STATUS_WIDTH 1 +#define M98925_ALCMUT_STATUS_MASK (1<<1) +#define M98925_ALCMUT_STATUS_SHIFT 1 +#define M98925_ALCMUT_STATUS_WIDTH 1 +#define M98925_ACLP_STATUS_MASK (1<<0) +#define M98925_ACLP_STATUS_SHIFT 0 +#define M98925_ACLP_STATUS_WIDTH 1 + +/* MAX98925_R004_LIVE_STATUS2 */ +#define M98925_SLOTOVRN_STATUS_MASK (1<<6) +#define M98925_SLOTOVRN_STATUS_SHIFT 6 +#define M98925_SLOTOVRN_STATUS_WIDTH 1 +#define M98925_INVALSLOT_STATUS_MASK (1<<5) +#define M98925_INVALSLOT_STATUS_SHIFT 5 +#define M98925_INVALSLOT_STATUS_WIDTH 1 +#define M98925_SLOTCNFLT_STATUS_MASK (1<<4) +#define M98925_SLOTCNFLT_STATUS_SHIFT 4 +#define M98925_SLOTCNFLT_STATUS_WIDTH 1 +#define M98925_VBSTOVFL_STATUS_MASK (1<<3) +#define M98925_VBSTOVFL_STATUS_SHIFT 3 +#define M98925_VBSTOVFL_STATUS_WIDTH 1 +#define M98925_VBATOVFL_STATUS_MASK (1<<2) +#define M98925_VBATOVFL_STATUS_SHIFT 2 +#define M98925_VBATOVFL_STATUS_WIDTH 1 +#define M98925_IMONOVFL_STATUS_MASK (1<<1) +#define M98925_IMONOVFL_STATUS_SHIFT 1 +#define M98925_IMONOVFL_STATUS_WIDTH 1 +#define M98925_VMONOVFL_STATUS_MASK (1<<0) +#define M98925_VMONOVFL_STATUS_SHIFT 0 +#define M98925_VMONOVFL_STATUS_WIDTH 1 + +/* MAX98925_R005_STATE0 */ +#define M98925_THERMWARN_END_STATE_MASK (1<<3) +#define M98925_THERMWARN_END_STATE_SHIFT 3 +#define M98925_THERMWARN_END_STATE_WIDTH 1 +#define M98925_THERMWARN_BGN_STATE_MASK (1<<2) +#define M98925_THERMWARN_BGN_STATE_SHIFT 1 +#define M98925_THERMWARN_BGN_STATE_WIDTH 1 +#define M98925_THERMSHDN_END_STATE_MASK (1<<1) +#define M98925_THERMSHDN_END_STATE_SHIFT 1 +#define M98925_THERMSHDN_END_STATE_WIDTH 1 +#define M98925_THERMSHDN_BGN_STATE_MASK (1<<0) +#define M98925_THERMSHDN_BGN_STATE_SHIFT 0 +#define M98925_THERMSHDN_BGN_STATE_WIDTH 1 + +/* MAX98925_R006_STATE1 */ +#define M98925_SPRCURNT_STATE_MASK (1<<5) +#define M98925_SPRCURNT_STATE_SHIFT 5 +#define M98925_SPRCURNT_STATE_WIDTH 1 +#define M98925_WATCHFAIL_STATE_MASK (1<<4) +#define M98925_WATCHFAIL_STATE_SHIFT 4 +#define M98925_WATCHFAIL_STATE_WIDTH 1 +#define M98925_ALCINFH_STATE_MASK (1<<3) +#define M98925_ALCINFH_STATE_SHIFT 3 +#define M98925_ALCINFH_STATE_WIDTH 1 +#define M98925_ALCACT_STATE_MASK (1<<2) +#define M98925_ALCACT_STATE_SHIFT 2 +#define M98925_ALCACT_STATE_WIDTH 1 +#define M98925_ALCMUT_STATE_MASK (1<<1) +#define M98925_ALCMUT_STATE_SHIFT 1 +#define M98925_ALCMUT_STATE_WIDTH 1 +#define M98925_ALCP_STATE_MASK (1<<0) +#define M98925_ALCP_STATE_SHIFT 0 +#define M98925_ALCP_STATE_WIDTH 1 + +/* MAX98925_R007_STATE2 */ +#define M98925_SLOTOVRN_STATE_MASK (1<<6) +#define M98925_SLOTOVRN_STATE_SHIFT 6 +#define M98925_SLOTOVRN_STATE_WIDTH 1 +#define M98925_INVALSLOT_STATE_MASK (1<<5) +#define M98925_INVALSLOT_STATE_SHIFT 5 +#define M98925_INVALSLOT_STATE_WIDTH 1 +#define M98925_SLOTCNFLT_STATE_MASK (1<<4) +#define M98925_SLOTCNFLT_STATE_SHIFT 4 +#define M98925_SLOTCNFLT_STATE_WIDTH 1 +#define M98925_VBSTOVFL_STATE_MASK (1<<3) +#define M98925_VBSTOVFL_STATE_SHIFT 3 +#define M98925_VBSTOVFL_STATE_WIDTH 1 +#define M98925_VBATOVFL_STATE_MASK (1<<2) +#define M98925_VBATOVFL_STATE_SHIFT 2 +#define M98925_VBATOVFL_STATE_WIDTH 1 +#define M98925_IMONOVFL_STATE_MASK (1<<1) +#define M98925_IMONOVFL_STATE_SHIFT 1 +#define M98925_IMONOVFL_STATE_WIDTH 1 +#define M98925_VMONOVFL_STATE_MASK (1<<0) +#define M98925_VMONOVFL_STATE_SHIFT 0 +#define M98925_VMONOVFL_STATE_WIDTH 1 + +/* MAX98925_R008_FLAG0 */ +#define M98925_THERMWARN_END_FLAG_MASK (1<<3) +#define M98925_THERMWARN_END_FLAG_SHIFT 3 +#define M98925_THERMWARN_END_FLAG_WIDTH 1 +#define M98925_THERMWARN_BGN_FLAG_MASK (1<<2) +#define M98925_THERMWARN_BGN_FLAG_SHIFT 2 +#define M98925_THERMWARN_BGN_FLAG_WIDTH 1 +#define M98925_THERMSHDN_END_FLAG_MASK (1<<1) +#define M98925_THERMSHDN_END_FLAG_SHIFT 1 +#define M98925_THERMSHDN_END_FLAG_WIDTH 1 +#define M98925_THERMSHDN_BGN_FLAG_MASK (1<<0) +#define M98925_THERMSHDN_BGN_FLAG_SHIFT 0 +#define M98925_THERMSHDN_BGN_FLAG_WIDTH 1 + +/* MAX98925_R009_FLAG1 */ +#define M98925_SPKCURNT_FLAG_MASK (1<<5) +#define M98925_SPKCURNT_FLAG_SHIFT 5 +#define M98925_SPKCURNT_FLAG_WIDTH 1 +#define M98925_WATCHFAIL_FLAG_MASK (1<<4) +#define M98925_WATCHFAIL_FLAG_SHIFT 4 +#define M98925_WATCHFAIL_FLAG_WIDTH 1 +#define M98925_ALCINFH_FLAG_MASK (1<<3) +#define M98925_ALCINFH_FLAG_SHIFT 3 +#define M98925_ALCINFH_FLAG_WIDTH 1 +#define M98925_ALCACT_FLAG_MASK (1<<2) +#define M98925_ALCACT_FLAG_SHIFT 2 +#define M98925_ALCACT_FLAG_WIDTH 1 +#define M98925_ALCMUT_FLAG_MASK (1<<1) +#define M98925_ALCMUT_FLAG_SHIFT 1 +#define M98925_ALCMUT_FLAG_WIDTH 1 +#define M98925_ALCP_FLAG_MASK (1<<0) +#define M98925_ALCP_FLAG_SHIFT 0 +#define M98925_ALCP_FLAG_WIDTH 1 + +/* MAX98925_R00A_FLAG2 */ +#define M98925_SLOTOVRN_FLAG_MASK (1<<6) +#define M98925_SLOTOVRN_FLAG_SHIFT 6 +#define M98925_SLOTOVRN_FLAG_WIDTH 1 +#define M98925_INVALSLOT_FLAG_MASK (1<<5) +#define M98925_INVALSLOT_FLAG_SHIFT 5 +#define M98925_INVALSLOT_FLAG_WIDTH 1 +#define M98925_SLOTCNFLT_FLAG_MASK (1<<4) +#define M98925_SLOTCNFLT_FLAG_SHIFT 4 +#define M98925_SLOTCNFLT_FLAG_WIDTH 1 +#define M98925_VBSTOVFL_FLAG_MASK (1<<3) +#define M98925_VBSTOVFL_FLAG_SHIFT 3 +#define M98925_VBSTOVFL_FLAG_WIDTH 1 +#define M98925_VBATOVFL_FLAG_MASK (1<<2) +#define M98925_VBATOVFL_FLAG_SHIFT 2 +#define M98925_VBATOVFL_FLAG_WIDTH 1 +#define M98925_IMONOVFL_FLAG_MASK (1<<1) +#define M98925_IMONOVFL_FLAG_SHIFT 1 +#define M98925_IMONOVFL_FLAG_WIDTH 1 +#define M98925_VMONOVFL_FLAG_MASK (1<<0) +#define M98925_VMONOVFL_FLAG_SHIFT 0 +#define M98925_VMONOVFL_FLAG_WIDTH 1 + +/* MAX98925_R00B_IRQ_ENABLE0 */ +#define M98925_THERMWARN_END_EN_MASK (1<<3) +#define M98925_THERMWARN_END_EN_SHIFT 3 +#define M98925_THERMWARN_END_EN_WIDTH 1 +#define M98925_THERMWARN_BGN_EN_MASK (1<<2) +#define M98925_THERMWARN_BGN_EN_SHIFT 2 +#define M98925_THERMWARN_BGN_EN_WIDTH 1 +#define M98925_THERMSHDN_END_EN_MASK (1<<1) +#define M98925_THERMSHDN_END_EN_SHIFT 1 +#define M98925_THERMSHDN_END_EN_WIDTH 1 +#define M98925_THERMSHDN_BGN_EN_MASK (1<<0) +#define M98925_THERMSHDN_BGN_EN_SHIFT 0 +#define M98925_THERMSHDN_BGN_EN_WIDTH 1 + +/* MAX98925_R00C_IRQ_ENABLE1 */ +#define M98925_SPKCURNT_EN_MASK (1<<5) +#define M98925_SPKCURNT_EN_SHIFT 5 +#define M98925_SPKCURNT_EN_WIDTH 1 +#define M98925_WATCHFAIL_EN_MASK (1<<4) +#define M98925_WATCHFAIL_EN_SHIFT 4 +#define M98925_WATCHFAIL_EN_WIDTH 1 +#define M98925_ALCINFH_EN_MASK (1<<3) +#define M98925_ALCINFH_EN_SHIFT 3 +#define M98925_ALCINFH_EN_WIDTH 1 +#define M98925_ALCACT_EN_MASK (1<<2) +#define M98925_ALCACT_EN_SHIFT 2 +#define M98925_ALCACT_EN_WIDTH 1 +#define M98925_ALCMUT_EN_MASK (1<<1) +#define M98925_ALCMUT_EN_SHIFT 1 +#define M98925_ALCMUT_EN_WIDTH 1 +#define M98925_ALCP_EN_MASK (1<<0) +#define M98925_ALCP_EN_SHIFT 0 +#define M98925_ALCP_EN_WIDTH 1 + +/* MAX98925_R00D_IRQ_ENABLE2 */ +#define M98925_SLOTOVRN_EN_MASK (1<<6) +#define M98925_SLOTOVRN_EN_SHIFT 6 +#define M98925_SLOTOVRN_EN_WIDTH 1 +#define M98925_INVALSLOT_EN_MASK (1<<5) +#define M98925_INVALSLOT_EN_SHIFT 5 +#define M98925_INVALSLOT_EN_WIDTH 1 +#define M98925_SLOTCNFLT_EN_MASK (1<<4) +#define M98925_SLOTCNFLT_EN_SHIFT 4 +#define M98925_SLOTCNFLT_EN_WIDTH 1 +#define M98925_VBSTOVFL_EN_MASK (1<<3) +#define M98925_VBSTOVFL_EN_SHIFT 3 +#define M98925_VBSTOVFL_EN_WIDTH 1 +#define M98925_VBATOVFL_EN_MASK (1<<2) +#define M98925_VBATOVFL_EN_SHIFT 2 +#define M98925_VBATOVFL_EN_WIDTH 1 +#define M98925_IMONOVFL_EN_MASK (1<<1) +#define M98925_IMONOVFL_EN_SHIFT 1 +#define M98925_IMONOVFL_EN_WIDTH 1 +#define M98925_VMONOVFL_EN_MASK (1<<0) +#define M98925_VMONOVFL_EN_SHIFT 0 +#define M98925_VMONOVFL_EN_WIDTH 1 + +/* MAX98925_R00E_IRQ_CLEAR0 */ +#define M98925_THERMWARN_END_CLR_MASK (1<<3) +#define M98925_THERMWARN_END_CLR_SHIFT 3 +#define M98925_THERMWARN_END_CLR_WIDTH 1 +#define M98925_THERMWARN_BGN_CLR_MASK (1<<2) +#define M98925_THERMWARN_BGN_CLR_SHIFT 2 +#define M98925_THERMWARN_BGN_CLR_WIDTH 1 +#define M98925_THERMSHDN_END_CLR_MASK (1<<1) +#define M98925_THERMSHDN_END_CLR_SHIFT 1 +#define M98925_THERMSHDN_END_CLR_WIDTH 1 +#define M98925_THERMSHDN_BGN_CLR_MASK (1<<0) +#define M98925_THERMSHDN_BGN_CLR_SHIFT 0 +#define M98925_THERMSHDN_BGN_CLR_WIDTH 1 + +/* MAX98925_R00F_IRQ_CLEAR1 */ +#define M98925_SPKCURNT_CLR_MASK (1<<5) +#define M98925_SPKCURNT_CLR_SHIFT 5 +#define M98925_SPKCURNT_CLR_WIDTH 1 +#define M98925_WATCHFAIL_CLR_MASK (1<<4) +#define M98925_WATCHFAIL_CLR_SHIFT 4 +#define M98925_WATCHFAIL_CLR_WIDTH 1 +#define M98925_ALCINFH_CLR_MASK (1<<3) +#define M98925_ALCINFH_CLR_SHIFT 3 +#define M98925_ALCINFH_CLR_WIDTH 1 +#define M98925_ALCACT_CLR_MASK (1<<2) +#define M98925_ALCACT_CLR_SHIFT 2 +#define M98925_ALCACT_CLR_WIDTH 1 +#define M98925_ALCMUT_CLR_MASK (1<<1) +#define M98925_ALCMUT_CLR_SHIFT 1 +#define M98925_ALCMUT_CLR_WIDTH 1 +#define M98925_ALCP_CLR_MASK (1<<0) +#define M98925_ALCP_CLR_SHIFT 0 +#define M98925_ALCP_CLR_WIDTH 1 + +/* MAX98925_R010_IRQ_CLEAR2 */ +#define M98925_SLOTOVRN_CLR_MASK (1<<6) +#define M98925_SLOTOVRN_CLR_SHIFT 6 +#define M98925_SLOTOVRN_CLR_WIDTH 1 +#define M98925_INVALSLOT_CLR_MASK (1<<5) +#define M98925_INVALSLOT_CLR_SHIFT 5 +#define M98925_INVALSLOT_CLR_WIDTH 1 +#define M98925_SLOTCNFLT_CLR_MASK (1<<4) +#define M98925_SLOTCNFLT_CLR_SHIFT 4 +#define M98925_SLOTCNFLT_CLR_WIDTH 1 +#define M98925_VBSTOVFL_CLR_MASK (1<<3) +#define M98925_VBSTOVFL_CLR_SHIFT 3 +#define M98925_VBSTOVFL_CLR_WIDTH 1 +#define M98925_VBATOVFL_CLR_MASK (1<<2) +#define M98925_VBATOVFL_CLR_SHIFT 2 +#define M98925_VBATOVFL_CLR_WIDTH 1 +#define M98925_IMONOVFL_CLR_MASK (1<<1) +#define M98925_IMONOVFL_CLR_SHIFT 1 +#define M98925_IMONOVFL_CLR_WIDTH 1 +#define M98925_VMONOVFL_CLR_MASK (1<<0) +#define M98925_VMONOVFL_CLR_SHIFT 0 +#define M98925_VMONOVFL_CLR_WIDTH 1 + +/* MAX98925_R011_MAP0 */ +#define M98925_ER_THERMWARN_EN_MASK (1<<7) +#define M98925_ER_THERMWARN_EN_SHIFT 7 +#define M98925_ER_THERMWARN_EN_WIDTH 1 +#define M98925_ER_THERMWARN_MAP_MASK (0x07<<4) +#define M98925_ER_THERMWARN_MAP_SHIFT 4 +#define M98925_ER_THERMWARN_MAP_WIDTH 3 + +/* MAX98925_R012_MAP1 */ +#define M98925_ER_ALCMUT_EN_MASK (1<<7) +#define M98925_ER_ALCMUT_EN_SHIFT 7 +#define M98925_ER_ALCMUT_EN_WIDTH 1 +#define M98925_ER_ALCMUT_MAP_MASK (0x07<<4) +#define M98925_ER_ALCMUT_MAP_SHIFT 4 +#define M98925_ER_ALCMUT_MAP_WIDTH 3 +#define M98925_ER_ALCP_EN_MASK (1<<3) +#define M98925_ER_ALCP_EN_SHIFT 3 +#define M98925_ER_ALCP_EN_WIDTH 1 +#define M98925_ER_ALCP_MAP_MASK (0x07<<0) +#define M98925_ER_ALCP_MAP_SHIFT 0 +#define M98925_ER_ALCP_MAP_WIDTH 3 + +/* MAX98925_R013_MAP2 */ +#define M98925_ER_ALCINFH_EN_MASK (1<<7) +#define M98925_ER_ALCINFH_EN_SHIFT 7 +#define M98925_ER_ALCINFH_EN_WIDTH 1 +#define M98925_ER_ALCINFH_MAP_MASK (0x07<<4) +#define M98925_ER_ALCINFH_MAP_SHIFT 4 +#define M98925_ER_ALCINFH_MAP_WIDTH 3 +#define M98925_ER_ALCACT_EN_MASK (1<<3) +#define M98925_ER_ALCACT_EN_SHIFT 3 +#define M98925_ER_ALCACT_EN_WIDTH 1 +#define M98925_ER_ALCACT_MAP_MASK (0x07<<0) +#define M98925_ER_ALCACT_MAP_SHIFT 0 +#define M98925_ER_ALCACT_MAP_WIDTH 3 + +/* MAX98925_R014_MAP3 */ +#define M98925_ER_SPKCURNT_EN_MASK (1<<7) +#define M98925_ER_SPKCURNT_EN_SHIFT 7 +#define M98925_ER_SPKCURNT_EN_WIDTH 1 +#define M98925_ER_SPKCURNT_MAP_MASK (0x07<<4) +#define M98925_ER_SPKCURNT_MAP_SHIFT 4 +#define M98925_ER_SPKCURNT_MAP_WIDTH 3 + +/* MAX98925_R015_MAP4 */ +/* RESERVED */ + +/* MAX98925_R016_MAP5 */ +#define M98925_ER_IMONOVFL_EN_MASK (1<<7) +#define M98925_ER_IMONOVFL_EN_SHIFT 7 +#define M98925_ER_IMONOVFL_EN_WIDTH 1 +#define M98925_ER_IMONOVFL_MAP_MASK (0x07<<4) +#define M98925_ER_IMONOVFL_MAP_SHIFT 4 +#define M98925_ER_IMONOVFL_MAP_WIDTH 3 +#define M98925_ER_VMONOVFL_EN_MASK (1<<3) +#define M98925_ER_VMONOVFL_EN_SHIFT 3 +#define M98925_ER_VMONOVFL_EN_WIDTH 1 +#define M98925_ER_VMONOVFL_MAP_MASK (0x07<<0) +#define M98925_ER_VMONOVFL_MAP_SHIFT 0 +#define M98925_ER_VMONOVFL_MAP_WIDTH 3 + +/* MAX98925_R017_MAP6 */ +#define M98925_ER_VBSTOVFL_EN_MASK (1<<7) +#define M98925_ER_VBSTOVFL_EN_SHIFT 7 +#define M98925_ER_VBSTOVFL_EN_WIDTH 1 +#define M98925_ER_VBSTOVFL_MAP_MASK (0x07<<4) +#define M98925_ER_VBSTOVFL_MAP_SHIFT 4 +#define M98925_ER_VBSTOVFL_MAP_WIDTH 3 +#define M98925_ER_VBATOVFL_EN_MASK (1<<3) +#define M98925_ER_VBATOVFL_EN_SHIFT 3 +#define M98925_ER_VBATOVFL_EN_WIDTH 1 +#define M98925_ER_VBATOVFL_MAP_MASK (0x07<<0) +#define M98925_ER_VBATOVFL_MAP_SHIFT 0 +#define M98925_ER_VBATOVFL_MAP_WIDTH 3 + +/* MAX98925_R018_MAP7 */ +#define M98925_ER_INVALSLOT_EN_MASK (1<<7) +#define M98925_ER_INVALSLOT_EN_SHIFT 7 +#define M98925_ER_INVALSLOT_EN_WIDTH 1 +#define M98925_ER_INVALSLOT_MAP_MASK (0x07<<4) +#define M98925_ER_INVALSLOT_MAP_SHIFT 4 +#define M98925_ER_INVALSLOT_MAP_WIDTH 3 +#define M98925_ER_SLOTCNFLT_EN_MASK (1<<3) +#define M98925_ER_SLOTCNFLT_EN_SHIFT 3 +#define M98925_ER_SLOTCNFLT_EN_WIDTH 1 +#define M98925_ER_SLOTCNFLT_MAP_MASK (0x07<<0) +#define M98925_ER_SLOTCNFLT_MAP_SHIFT 0 +#define M98925_ER_SLOTCNFLT_MAP_WIDTH 3 + +/* MAX98925_R019_MAP8 */ +#define M98925_ER_SLOTOVRN_EN_MASK (1<<3) +#define M98925_ER_SLOTOVRN_EN_SHIFT 3 +#define M98925_ER_SLOTOVRN_EN_WIDTH 1 +#define M98925_ER_SLOTOVRN_MAP_MASK (0x07<<0) +#define M98925_ER_SLOTOVRN_MAP_SHIFT 0 +#define M98925_ER_SLOTOVRN_MAP_WIDTH 3 + +/* MAX98925_R01A_DAI_CLK_MODE1 */ +#define M98925_DAI_CLK_SOURCE_MASK (1<<6) +#define M98925_DAI_CLK_SOURCE_SHIFT 6 +#define M98925_DAI_CLK_SOURCE_WIDTH 1 +#define M98925_MDLL_MULT_MASK (0x0F<<0) +#define M98925_MDLL_MULT_SHIFT 0 +#define M98925_MDLL_MULT_WIDTH 4 + +#define M98925_MDLL_MULT_MCLKx8 6 +#define M98925_MDLL_MULT_MCLKx16 8 + +/* MAX98925_R01B_DAI_CLK_MODE2 */ +#define M98925_DAI_SR_MASK (0x0F<<4) +#define M98925_DAI_SR_SHIFT 4 +#define M98925_DAI_SR_WIDTH 4 +#define M98925_DAI_MAS_MASK (1<<3) +#define M98925_DAI_MAS_SHIFT 3 +#define M98925_DAI_MAS_WIDTH 1 +#define M98925_DAI_BSEL_MASK (0x07<<0) +#define M98925_DAI_BSEL_SHIFT 0 +#define M98925_DAI_BSEL_WIDTH 3 + +#define M98925_DAI_BSEL_32 (0 << M98925_DAI_BSEL_SHIFT) +#define M98925_DAI_BSEL_48 (1 << M98925_DAI_BSEL_SHIFT) +#define M98925_DAI_BSEL_64 (2 << M98925_DAI_BSEL_SHIFT) +#define M98925_DAI_BSEL_256 (6 << M98925_DAI_BSEL_SHIFT) + +/* MAX98925_R01C_DAI_CLK_DIV_M_MSBS */ +#define M98925_DAI_M_MSBS_MASK (0xFF<<0) +#define M98925_DAI_M_MSBS_SHIFT 0 +#define M98925_DAI_M_MSBS_WIDTH 8 + +/* MAX98925_R01D_DAI_CLK_DIV_M_LSBS */ +#define M98925_DAI_M_LSBS_MASK (0xFF<<0) +#define M98925_DAI_M_LSBS_SHIFT 0 +#define M98925_DAI_M_LSBS_WIDTH 8 + +/* MAX98925_R01E_DAI_CLK_DIV_N_MSBS */ +#define M98925_DAI_N_MSBS_MASK (0x7F<<0) +#define M98925_DAI_N_MSBS_SHIFT 0 +#define M98925_DAI_N_MSBS_WIDTH 7 + +/* MAX98925_R01F_DAI_CLK_DIV_N_LSBS */ +#define M98925_DAI_N_LSBS_MASK (0xFF<<0) +#define M98925_DAI_N_LSBS_SHIFT 0 +#define M98925_DAI_N_LSBS_WIDTH 8 + +/* MAX98925_R020_FORMAT */ +#define M98925_DAI_CHANSZ_MASK (0x03<<6) +#define M98925_DAI_CHANSZ_SHIFT 6 +#define M98925_DAI_CHANSZ_WIDTH 2 +#define M98925_DAI_EXTBCLK_HIZ_MASK (1<<4) +#define M98925_DAI_EXTBCLK_HIZ_SHIFT 4 +#define M98925_DAI_EXTBCLK_HIZ_WIDTH 1 +#define M98925_DAI_WCI_MASK (1<<3) +#define M98925_DAI_WCI_SHIFT 3 +#define M98925_DAI_WCI_WIDTH 1 +#define M98925_DAI_BCI_MASK (1<<2) +#define M98925_DAI_BCI_SHIFT 2 +#define M98925_DAI_BCI_WIDTH 1 +#define M98925_DAI_DLY_MASK (1<<1) +#define M98925_DAI_DLY_SHIFT 1 +#define M98925_DAI_DLY_WIDTH 1 +#define M98925_DAI_TDM_MASK (1<<0) +#define M98925_DAI_TDM_SHIFT 0 +#define M98925_DAI_TDM_WIDTH 1 + +#define M98925_DAI_CHANSZ_16 (1 << M98925_DAI_CHANSZ_SHIFT) +#define M98925_DAI_CHANSZ_24 (2 << M98925_DAI_CHANSZ_SHIFT) +#define M98925_DAI_CHANSZ_32 (3 << M98925_DAI_CHANSZ_SHIFT) + +/* MAX98925_R021_TDM_SLOT_SELECT */ +#define M98925_DAI_DO_EN_MASK (1<<7) +#define M98925_DAI_DO_EN_SHIFT 7 +#define M98925_DAI_DO_EN_WIDTH 1 +#define M98925_DAI_DIN_EN_MASK (1<<6) +#define M98925_DAI_DIN_EN_SHIFT 6 +#define M98925_DAI_DIN_EN_WIDTH 1 +#define M98925_DAI_INR_SOURCE_MASK (0x07<<3) +#define M98925_DAI_INR_SOURCE_SHIFT 3 +#define M98925_DAI_INR_SOURCE_WIDTH 3 +#define M98925_DAI_INL_SOURCE_MASK (0x07<<0) +#define M98925_DAI_INL_SOURCE_SHIFT 0 +#define M98925_DAI_INL_SOURCE_WIDTH 3 + +/* MAX98925_R022_DOUT_CFG_VMON */ +#define M98925_DAI_VMON_EN_MASK (1<<5) +#define M98925_DAI_VMON_EN_SHIFT 5 +#define M98925_DAI_VMON_EN_WIDTH 1 +#define M98925_DAI_VMON_SLOT_MASK (0x1F<<0) +#define M98925_DAI_VMON_SLOT_SHIFT 0 +#define M98925_DAI_VMON_SLOT_WIDTH 5 + +#define M98925_DAI_VMON_SLOT_00_01 (0 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_01_02 (1 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_02_03 (2 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_03_04 (3 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_04_05 (4 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_05_06 (5 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_06_07 (6 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_07_08 (7 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_08_09 (8 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_09_0A (9 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0A_0B (10 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0B_0C (11 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0C_0D (12 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0D_0E (13 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0E_0F (14 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_0F_10 (15 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_10_11 (16 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_11_12 (17 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_12_13 (18 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_13_14 (19 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_14_15 (20 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_15_16 (21 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_16_17 (22 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_17_18 (23 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_18_19 (24 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_19_1A (25 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1A_1B (26 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1B_1C (27 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1C_1D (28 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1D_1E (29 << M98925_DAI_VMON_SLOT_SHIFT) +#define M98925_DAI_VMON_SLOT_1E_1F (30 << M98925_DAI_VMON_SLOT_SHIFT) + +/* MAX98925_R023_DOUT_CFG_IMON */ +#define M98925_DAI_IMON_EN_MASK (1<<5) +#define M98925_DAI_IMON_EN_SHIFT 5 +#define M98925_DAI_IMON_EN_WIDTH 1 +#define M98925_DAI_IMON_SLOT_MASK (0x1F<<0) +#define M98925_DAI_IMON_SLOT_SHIFT 0 +#define M98925_DAI_IMON_SLOT_WIDTH 5 + +#define M98925_DAI_IMON_SLOT_00_01 (0 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_01_02 (1 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_02_03 (2 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_03_04 (3 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_04_05 (4 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_05_06 (5 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_06_07 (6 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_07_08 (7 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_08_09 (8 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_09_0A (9 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0A_0B (10 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0B_0C (11 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0C_0D (12 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0D_0E (13 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0E_0F (14 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_0F_10 (15 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_10_11 (16 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_11_12 (17 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_12_13 (18 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_13_14 (19 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_14_15 (20 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_15_16 (21 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_16_17 (22 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_17_18 (23 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_18_19 (24 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_19_1A (25 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1A_1B (26 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1B_1C (27 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1C_1D (28 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1D_1E (29 << M98925_DAI_IMON_SLOT_SHIFT) +#define M98925_DAI_IMON_SLOT_1E_1F (30 << M98925_DAI_IMON_SLOT_SHIFT) + +/* MAX98925_R024_DOUT_CFG_VBAT */ +#define M98925_DAI_VBAT_EN_MASK (1<<5) +#define M98925_DAI_VBAT_EN_SHIFT 5 +#define M98925_DAI_VBAT_EN_WIDTH 1 +#define M98925_DAI_VBAT_SLOT_MASK (0x1F<<0) +#define M98925_DAI_VBAT_SLOT_SHIFT 0 +#define M98925_DAI_VBAT_SLOT_WIDTH 5 + +/* MAX98925_R025_DOUT_CFG_VBST */ +#define M98925_DAI_VBST_EN_MASK (1<<5) +#define M98925_DAI_VBST_EN_SHIFT 5 +#define M98925_DAI_VBST_EN_WIDTH 1 +#define M98925_DAI_VBST_SLOT_MASK (0x1F<<0) +#define M98925_DAI_VBST_SLOT_SHIFT 0 +#define M98925_DAI_VBST_SLOT_WIDTH 5 + +/* MAX98925_R026_DOUT_CFG_FLAG */ +#define M98925_DAI_FLAG_EN_MASK (1<<5) +#define M98925_DAI_FLAG_EN_SHIFT 5 +#define M98925_DAI_FLAG_EN_WIDTH 1 +#define M98925_DAI_FLAG_SLOT_MASK (0x1F<<0) +#define M98925_DAI_FLAG_SLOT_SHIFT 0 +#define M98925_DAI_FLAG_SLOT_WIDTH 5 + +/* MAX98925_R027_DOUT_HIZ_CFG1 */ +#define M98925_DAI_SLOT_HIZ_CFG1_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG1_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG1_WIDTH 8 + +/* MAX98925_R028_DOUT_HIZ_CFG2 */ +#define M98925_DAI_SLOT_HIZ_CFG2_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG2_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG2_WIDTH 8 + +/* MAX98925_R029_DOUT_HIZ_CFG3 */ +#define M98925_DAI_SLOT_HIZ_CFG3_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG3_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG3_WIDTH 8 + +/* MAX98925_R02A_DOUT_HIZ_CFG4 */ +#define M98925_DAI_SLOT_HIZ_CFG4_MASK (0xFF<<0) +#define M98925_DAI_SLOT_HIZ_CFG4_SHIFT 0 +#define M98925_DAI_SLOT_HIZ_CFG4_WIDTH 8 + +/* MAX98925_R02B_DOUT_DRV_STRENGTH */ +#define M98925_DAI_OUT_DRIVE_MASK (0x03<<0) +#define M98925_DAI_OUT_DRIVE_SHIFT 0 +#define M98925_DAI_OUT_DRIVE_WIDTH 2 + +/* MAX98925_R02C_FILTERS */ +#define M98925_ADC_DITHER_EN_MASK (1<<7) +#define M98925_ADC_DITHER_EN_SHIFT 7 +#define M98925_ADC_DITHER_EN_WIDTH 1 +#define M98925_IV_DCB_EN_MASK (1<<6) +#define M98925_IV_DCB_EN_SHIFT 6 +#define M98925_IV_DCB_EN_WIDTH 1 +#define M98925_DAC_DITHER_EN_MASK (1<<4) +#define M98925_DAC_DITHER_EN_SHIFT 4 +#define M98925_DAC_DITHER_EN_WIDTH 1 +#define M98925_DAC_FILTER_MODE_MASK (1<<3) +#define M98925_DAC_FILTER_MODE_SHIFT 3 +#define M98925_DAC_FILTER_MODE_WIDTH 1 +#define M98925_DAC_HPF_MASK (0x07<<0) +#define M98925_DAC_HPF_SHIFT 0 +#define M98925_DAC_HPF_WIDTH 3 +#define M98925_DAC_HPF_DISABLE (0 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_DC_BLOCK (1 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_100 (2 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_200 (3 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_400 (4 << M98925_DAC_HPF_SHIFT) +#define M98925_DAC_HPF_EN_800 (5 << M98925_DAC_HPF_SHIFT) + +/* MAX98925_R02D_GAIN */ +#define M98925_DAC_IN_SEL_MASK (0x03<<5) +#define M98925_DAC_IN_SEL_SHIFT 5 +#define M98925_DAC_IN_SEL_WIDTH 2 +#define M98925_SPK_GAIN_MASK (0x1F<<0) +#define M98925_SPK_GAIN_SHIFT 0 +#define M98925_SPK_GAIN_WIDTH 5 + +#define M98925_DAC_IN_SEL_LEFT_DAI (0 << M98925_DAC_IN_SEL_SHIFT) +#define M98925_DAC_IN_SEL_RIGHT_DAI (1 << M98925_DAC_IN_SEL_SHIFT) +#define M98925_DAC_IN_SEL_SUMMED_DAI (2 << M98925_DAC_IN_SEL_SHIFT) +#define M98925_DAC_IN_SEL_DIV2_SUMMED_DAI (3 << M98925_DAC_IN_SEL_SHIFT) + +/* MAX98925_R02E_GAIN_RAMPING */ +#define M98925_SPK_RMP_EN_MASK (1<<1) +#define M98925_SPK_RMP_EN_SHIFT 1 +#define M98925_SPK_RMP_EN_WIDTH 1 +#define M98925_SPK_ZCD_EN_MASK (1<<0) +#define M98925_SPK_ZCD_EN_SHIFT 0 +#define M98925_SPK_ZCD_EN_WIDTH 1 + +/* MAX98925_R02F_SPK_AMP */ +#define M98925_SPK_MODE_MASK (1<<0) +#define M98925_SPK_MODE_SHIFT 0 +#define M98925_SPK_MODE_WIDTH 1 + +/* MAX98925_R030_THRESHOLD */ +#define M98925_ALC_EN_MASK (1<<5) +#define M98925_ALC_EN_SHIFT 5 +#define M98925_ALC_EN_WIDTH 1 +#define M98925_ALC_TH_MASK (0x1F<<0) +#define M98925_ALC_TH_SHIFT 0 +#define M98925_ALC_TH_WIDTH 5 + +/* MAX98925_R031_ALC_ATTACK */ +#define M98925_ALC_ATK_STEP_MASK (0x0F<<4) +#define M98925_ALC_ATK_STEP_SHIFT 4 +#define M98925_ALC_ATK_STEP_WIDTH 4 +#define M98925_ALC_ATK_RATE_MASK (0x7<<0) +#define M98925_ALC_ATK_RATE_SHIFT 0 +#define M98925_ALC_ATK_RATE_WIDTH 3 + +/* MAX98925_R032_ALC_ATTEN_RLS */ +#define M98925_ALC_MAX_ATTEN_MASK (0x0F<<4) +#define M98925_ALC_MAX_ATTEN_SHIFT 4 +#define M98925_ALC_MAX_ATTEN_WIDTH 4 +#define M98925_ALC_RLS_RATE_MASK (0x7<<0) +#define M98925_ALC_RLS_RATE_SHIFT 0 +#define M98925_ALC_RLS_RATE_WIDTH 3 + +/* MAX98925_R033_ALC_HOLD_RLS */ +#define M98925_ALC_RLS_TGR_MASK (1<<0) +#define M98925_ALC_RLS_TGR_SHIFT 0 +#define M98925_ALC_RLS_TGR_WIDTH 1 + +/* MAX98925_R034_ALC_CONFIGURATION */ +#define M98925_ALC_MUTE_EN_MASK (1<<7) +#define M98925_ALC_MUTE_EN_SHIFT 7 +#define M98925_ALC_MUTE_EN_WIDTH 1 +#define M98925_ALC_MUTE_DLY_MASK (0x07<<4) +#define M98925_ALC_MUTE_DLY_SHIFT 4 +#define M98925_ALC_MUTE_DLY_WIDTH 3 +#define M98925_ALC_RLS_DBT_MASK (0x07<<0) +#define M98925_ALC_RLS_DBT_SHIFT 0 +#define M98925_ALC_RLS_DBT_WIDTH 3 + +/* MAX98925_R035_BOOST_CONVERTER */ +#define M98925_BST_SYNC_MASK (1<<7) +#define M98925_BST_SYNC_SHIFT 7 +#define M98925_BST_SYNC_WIDTH 1 +#define M98925_BST_PHASE_MASK (0x03<<4) +#define M98925_BST_PHASE_SHIFT 4 +#define M98925_BST_PHASE_WIDTH 2 +#define M98925_BST_SKIP_MODE_MASK (0x03<<0) +#define M98925_BST_SKIP_MODE_SHIFT 0 +#define M98925_BST_SKIP_MODE_WIDTH 2 + +/* MAX98925_R036_BLOCK_ENABLE */ +#define M98925_BST_EN_MASK (1<<7) +#define M98925_BST_EN_SHIFT 7 +#define M98925_BST_EN_WIDTH 1 +#define M98925_WATCH_EN_MASK (1<<6) +#define M98925_WATCH_EN_SHIFT 6 +#define M98925_WATCH_EN_WIDTH 1 +#define M98925_CLKMON_EN_MASK (1<<5) +#define M98925_CLKMON_EN_SHIFT 5 +#define M98925_CLKMON_EN_WIDTH 1 +#define M98925_SPK_EN_MASK (1<<4) +#define M98925_SPK_EN_SHIFT 4 +#define M98925_SPK_EN_WIDTH 1 +#define M98925_ADC_VBST_EN_MASK (1<<3) +#define M98925_ADC_VBST_EN_SHIFT 3 +#define M98925_ADC_VBST_EN_WIDTH 1 +#define M98925_ADC_VBAT_EN_MASK (1<<2) +#define M98925_ADC_VBAT_EN_SHIFT 2 +#define M98925_ADC_VBAT_EN_WIDTH 1 +#define M98925_ADC_IMON_EN_MASK (1<<1) +#define M98925_ADC_IMON_EN_SHIFT 1 +#define M98925_ADC_IMON_EN_WIDTH 1 +#define M98925_ADC_VMON_EN_MASK (1<<0) +#define M98925_ADC_VMON_EN_SHIFT 0 +#define M98925_ADC_VMON_EN_WIDTH 1 + +/* MAX98925_R037_CONFIGURATION */ +#define M98925_BST_VOUT_MASK (0x0F<<4) +#define M98925_BST_VOUT_SHIFT 4 +#define M98925_BST_VOUT_WIDTH 4 +#define M98925_THERMWARN_LEVEL_MASK (0x03<<2) +#define M98925_THERMWARN_LEVEL_SHIFT 2 +#define M98925_THERMWARN_LEVEL_WIDTH 2 +#define M98925_WATCH_TIME_MASK (0x03<<0) +#define M98925_WATCH_TIME_SHIFT 0 +#define M98925_WATCH_TIME_WIDTH 2 + +/* MAX98925_R038_GLOBAL_ENABLE */ +#define M98925_EN_MASK (1<<7) +#define M98925_EN_SHIFT 7 +#define M98925_EN_WIDTH 1 + +/* MAX98925_R03A_BOOST_LIMITER */ +#define M98925_BST_ILIM_MASK (0x1F<<3) +#define M98925_BST_ILIM_SHIFT 3 +#define M98925_BST_ILIM_WIDTH 5 + +/* MAX98925_R0FF_VERSION */ +#define M98925_REV_ID_MASK (0xFF<<0) +#define M98925_REV_ID_SHIFT 0 +#define M98925_REV_ID_WIDTH 8 + +struct max98925_priv { + struct regmap *regmap; + struct snd_soc_component *component; + struct max98925_pdata *pdata; + unsigned int sysclk; + unsigned int v_slot; + unsigned int i_slot; + unsigned int spk_gain; + unsigned int ch_size; +}; +#endif diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c new file mode 100644 index 000000000..d9b1f68f2 --- /dev/null +++ b/sound/soc/codecs/max98926.c @@ -0,0 +1,604 @@ +/* + * max98926.c -- ALSA SoC MAX98926 driver + * Copyright 2013-15 Maxim Integrated Products + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98926.h" + +static const char * const max98926_boost_voltage_txt[] = { + "8.5V", "8.25V", "8.0V", "7.75V", "7.5V", "7.25V", "7.0V", "6.75V", + "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V", "6.5V" +}; + +static const char * const max98926_boost_current_txt[] = { + "0.6", "0.8", "1.0", "1.2", "1.4", "1.6", "1.8", "2.0", + "2.2", "2.4", "2.6", "2.8", "3.2", "3.6", "4.0", "4.4" +}; + +static const char *const max98926_dai_txt[] = { + "Left", "Right", "LeftRight", "LeftRightDiv2", +}; + +static const char *const max98926_pdm_ch_text[] = { + "Current", "Voltage", +}; + +static const char *const max98926_hpf_cutoff_txt[] = { + "Disable", "DC Block", "100Hz", + "200Hz", "400Hz", "800Hz", +}; + +static const struct reg_default max98926_reg[] = { + { 0x0B, 0x00 }, /* IRQ Enable0 */ + { 0x0C, 0x00 }, /* IRQ Enable1 */ + { 0x0D, 0x00 }, /* IRQ Enable2 */ + { 0x0E, 0x00 }, /* IRQ Clear0 */ + { 0x0F, 0x00 }, /* IRQ Clear1 */ + { 0x10, 0x00 }, /* IRQ Clear2 */ + { 0x11, 0xC0 }, /* Map0 */ + { 0x12, 0x00 }, /* Map1 */ + { 0x13, 0x00 }, /* Map2 */ + { 0x14, 0xF0 }, /* Map3 */ + { 0x15, 0x00 }, /* Map4 */ + { 0x16, 0xAB }, /* Map5 */ + { 0x17, 0x89 }, /* Map6 */ + { 0x18, 0x00 }, /* Map7 */ + { 0x19, 0x00 }, /* Map8 */ + { 0x1A, 0x04 }, /* DAI Clock Mode 1 */ + { 0x1B, 0x00 }, /* DAI Clock Mode 2 */ + { 0x1C, 0x00 }, /* DAI Clock Divider Denominator MSBs */ + { 0x1D, 0x00 }, /* DAI Clock Divider Denominator LSBs */ + { 0x1E, 0xF0 }, /* DAI Clock Divider Numerator MSBs */ + { 0x1F, 0x00 }, /* DAI Clock Divider Numerator LSBs */ + { 0x20, 0x50 }, /* Format */ + { 0x21, 0x00 }, /* TDM Slot Select */ + { 0x22, 0x00 }, /* DOUT Configuration VMON */ + { 0x23, 0x00 }, /* DOUT Configuration IMON */ + { 0x24, 0x00 }, /* DOUT Configuration VBAT */ + { 0x25, 0x00 }, /* DOUT Configuration VBST */ + { 0x26, 0x00 }, /* DOUT Configuration FLAG */ + { 0x27, 0xFF }, /* DOUT HiZ Configuration 1 */ + { 0x28, 0xFF }, /* DOUT HiZ Configuration 2 */ + { 0x29, 0xFF }, /* DOUT HiZ Configuration 3 */ + { 0x2A, 0xFF }, /* DOUT HiZ Configuration 4 */ + { 0x2B, 0x02 }, /* DOUT Drive Strength */ + { 0x2C, 0x90 }, /* Filters */ + { 0x2D, 0x00 }, /* Gain */ + { 0x2E, 0x02 }, /* Gain Ramping */ + { 0x2F, 0x00 }, /* Speaker Amplifier */ + { 0x30, 0x0A }, /* Threshold */ + { 0x31, 0x00 }, /* ALC Attack */ + { 0x32, 0x80 }, /* ALC Atten and Release */ + { 0x33, 0x00 }, /* ALC Infinite Hold Release */ + { 0x34, 0x92 }, /* ALC Configuration */ + { 0x35, 0x01 }, /* Boost Converter */ + { 0x36, 0x00 }, /* Block Enable */ + { 0x37, 0x00 }, /* Configuration */ + { 0x38, 0x00 }, /* Global Enable */ + { 0x3A, 0x00 }, /* Boost Limiter */ +}; + +static const struct soc_enum max98926_voltage_enum[] = { + SOC_ENUM_SINGLE(MAX98926_DAI_CLK_DIV_N_LSBS, 0, + ARRAY_SIZE(max98926_pdm_ch_text), + max98926_pdm_ch_text), +}; + +static const struct snd_kcontrol_new max98926_voltage_control = + SOC_DAPM_ENUM("Route", max98926_voltage_enum); + +static const struct soc_enum max98926_current_enum[] = { + SOC_ENUM_SINGLE(MAX98926_DAI_CLK_DIV_N_LSBS, + MAX98926_PDM_SOURCE_1_SHIFT, + ARRAY_SIZE(max98926_pdm_ch_text), + max98926_pdm_ch_text), +}; + +static const struct snd_kcontrol_new max98926_current_control = + SOC_DAPM_ENUM("Route", max98926_current_enum); + +static const struct snd_kcontrol_new max98926_mixer_controls[] = { + SOC_DAPM_SINGLE("PCM Single Switch", MAX98926_SPK_AMP, + MAX98926_INSELECT_MODE_SHIFT, 0, 0), + SOC_DAPM_SINGLE("PDM Single Switch", MAX98926_SPK_AMP, + MAX98926_INSELECT_MODE_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new max98926_dai_controls[] = { + SOC_DAPM_SINGLE("Left", MAX98926_GAIN, + MAX98926_DAC_IN_SEL_SHIFT, 0, 0), + SOC_DAPM_SINGLE("Right", MAX98926_GAIN, + MAX98926_DAC_IN_SEL_SHIFT, 1, 0), + SOC_DAPM_SINGLE("LeftRight", MAX98926_GAIN, + MAX98926_DAC_IN_SEL_SHIFT, 2, 0), + SOC_DAPM_SINGLE("(Left+Right)/2 Switch", MAX98926_GAIN, + MAX98926_DAC_IN_SEL_SHIFT, 3, 0), +}; + +static const struct snd_soc_dapm_widget max98926_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAI_OUT", "HiFi Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Amp Enable", NULL, MAX98926_BLOCK_ENABLE, + MAX98926_SPK_EN_SHIFT, 0), + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98926_GLOBAL_ENABLE, + MAX98926_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("VI Enable", MAX98926_BLOCK_ENABLE, + MAX98926_ADC_IMON_EN_WIDTH | + MAX98926_ADC_VMON_EN_SHIFT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("BST Enable", MAX98926_BLOCK_ENABLE, + MAX98926_BST_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("BE_OUT"), + SND_SOC_DAPM_MIXER("PCM Sel", MAX98926_SPK_AMP, + MAX98926_INSELECT_MODE_SHIFT, 0, + &max98926_mixer_controls[0], + ARRAY_SIZE(max98926_mixer_controls)), + SND_SOC_DAPM_MIXER("DAI Sel", + MAX98926_GAIN, MAX98926_DAC_IN_SEL_SHIFT, 0, + &max98926_dai_controls[0], + ARRAY_SIZE(max98926_dai_controls)), + SND_SOC_DAPM_MUX("PDM CH1 Source", + MAX98926_DAI_CLK_DIV_N_LSBS, + MAX98926_PDM_CURRENT_SHIFT, + 0, &max98926_current_control), + SND_SOC_DAPM_MUX("PDM CH0 Source", + MAX98926_DAI_CLK_DIV_N_LSBS, + MAX98926_PDM_VOLTAGE_SHIFT, + 0, &max98926_voltage_control), +}; + +static const struct snd_soc_dapm_route max98926_audio_map[] = { + {"VI Enable", NULL, "DAI_OUT"}, + {"DAI Sel", "Left", "VI Enable"}, + {"DAI Sel", "Right", "VI Enable"}, + {"DAI Sel", "LeftRight", "VI Enable"}, + {"DAI Sel", "LeftRightDiv2", "VI Enable"}, + {"PCM Sel", "PCM", "DAI Sel"}, + + {"PDM CH1 Source", "Current", "DAI_OUT"}, + {"PDM CH1 Source", "Voltage", "DAI_OUT"}, + {"PDM CH0 Source", "Current", "DAI_OUT"}, + {"PDM CH0 Source", "Voltage", "DAI_OUT"}, + {"PCM Sel", "Analog", "PDM CH1 Source"}, + {"PCM Sel", "Analog", "PDM CH0 Source"}, + {"Amp Enable", NULL, "PCM Sel"}, + + {"BST Enable", NULL, "Amp Enable"}, + {"BE_OUT", NULL, "BST Enable"}, +}; + +static bool max98926_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98926_VBAT_DATA: + case MAX98926_VBST_DATA: + case MAX98926_LIVE_STATUS0: + case MAX98926_LIVE_STATUS1: + case MAX98926_LIVE_STATUS2: + case MAX98926_STATE0: + case MAX98926_STATE1: + case MAX98926_STATE2: + case MAX98926_FLAG0: + case MAX98926_FLAG1: + case MAX98926_FLAG2: + case MAX98926_VERSION: + return true; + default: + return false; + } +} + +static bool max98926_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98926_IRQ_CLEAR0: + case MAX98926_IRQ_CLEAR1: + case MAX98926_IRQ_CLEAR2: + case MAX98926_ALC_HOLD_RLS: + return false; + default: + return true; + } +}; + +static DECLARE_TLV_DB_SCALE(max98926_spk_tlv, -600, 100, 0); +static DECLARE_TLV_DB_RANGE(max98926_current_tlv, + 0, 11, TLV_DB_SCALE_ITEM(20, 20, 0), + 12, 15, TLV_DB_SCALE_ITEM(320, 40, 0), +); + +static SOC_ENUM_SINGLE_DECL(max98926_dac_hpf_cutoff, + MAX98926_FILTERS, MAX98926_DAC_HPF_SHIFT, + max98926_hpf_cutoff_txt); + +static SOC_ENUM_SINGLE_DECL(max98926_boost_voltage, + MAX98926_CONFIGURATION, MAX98926_BST_VOUT_SHIFT, + max98926_boost_voltage_txt); + +static const struct snd_kcontrol_new max98926_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98926_GAIN, + MAX98926_SPK_GAIN_SHIFT, + (1<regmap, + MAX98926_DOUT_CFG_VMON, + MAX98926_DAI_VMON_EN_MASK, + MAX98926_DAI_VMON_EN_MASK); + regmap_update_bits(max98926->regmap, + MAX98926_DOUT_CFG_IMON, + MAX98926_DAI_IMON_EN_MASK, + MAX98926_DAI_IMON_EN_MASK); + + if (!max98926->interleave_mode) { + /* set VMON slots */ + regmap_update_bits(max98926->regmap, + MAX98926_DOUT_CFG_VMON, + MAX98926_DAI_VMON_SLOT_MASK, + max98926->v_slot); + /* set IMON slots */ + regmap_update_bits(max98926->regmap, + MAX98926_DOUT_CFG_IMON, + MAX98926_DAI_IMON_SLOT_MASK, + max98926->i_slot); + } else { + /* enable interleave mode */ + regmap_update_bits(max98926->regmap, + MAX98926_FORMAT, + MAX98926_DAI_INTERLEAVE_MASK, + MAX98926_DAI_INTERLEAVE_MASK); + /* set interleave slots */ + regmap_update_bits(max98926->regmap, + MAX98926_DOUT_CFG_VBAT, + MAX98926_DAI_INTERLEAVE_SLOT_MASK, + max98926->v_slot); + } +} + +static int max98926_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98926_priv *max98926 = snd_soc_component_get_drvdata(component); + unsigned int invert = 0; + + dev_dbg(component->dev, "%s: fmt 0x%08X\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + max98926_set_sense_data(max98926); + break; + default: + dev_err(component->dev, "DAI clock mode unsupported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + invert = MAX98926_DAI_WCI_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + invert = MAX98926_DAI_BCI_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + invert = MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK; + break; + default: + dev_err(component->dev, "DAI invert mode unsupported\n"); + return -EINVAL; + } + + regmap_write(max98926->regmap, + MAX98926_FORMAT, MAX98926_DAI_DLY_MASK); + regmap_update_bits(max98926->regmap, MAX98926_FORMAT, + MAX98926_DAI_BCI_MASK, invert); + return 0; +} + +static int max98926_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int dai_sr = -EINVAL; + int rate = params_rate(params), i; + struct snd_soc_component *component = dai->component; + struct max98926_priv *max98926 = snd_soc_component_get_drvdata(component); + int blr_clk_ratio; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + regmap_update_bits(max98926->regmap, + MAX98926_FORMAT, + MAX98926_DAI_CHANSZ_MASK, + MAX98926_DAI_CHANSZ_16); + max98926->ch_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + regmap_update_bits(max98926->regmap, + MAX98926_FORMAT, + MAX98926_DAI_CHANSZ_MASK, + MAX98926_DAI_CHANSZ_24); + max98926->ch_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + regmap_update_bits(max98926->regmap, + MAX98926_FORMAT, + MAX98926_DAI_CHANSZ_MASK, + MAX98926_DAI_CHANSZ_32); + max98926->ch_size = 32; + break; + default: + dev_dbg(component->dev, "format unsupported %d\n", + params_format(params)); + return -EINVAL; + } + + /* BCLK/LRCLK ratio calculation */ + blr_clk_ratio = params_channels(params) * max98926->ch_size; + + switch (blr_clk_ratio) { + case 32: + regmap_update_bits(max98926->regmap, + MAX98926_DAI_CLK_MODE2, + MAX98926_DAI_BSEL_MASK, + MAX98926_DAI_BSEL_32); + break; + case 48: + regmap_update_bits(max98926->regmap, + MAX98926_DAI_CLK_MODE2, + MAX98926_DAI_BSEL_MASK, + MAX98926_DAI_BSEL_48); + break; + case 64: + regmap_update_bits(max98926->regmap, + MAX98926_DAI_CLK_MODE2, + MAX98926_DAI_BSEL_MASK, + MAX98926_DAI_BSEL_64); + break; + default: + return -EINVAL; + } + + /* find the closest rate */ + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i].rate >= rate) { + dai_sr = rate_table[i].sr; + break; + } + } + if (dai_sr < 0) + return -EINVAL; + + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98926->regmap, + MAX98926_DAI_CLK_MODE2, + MAX98926_DAI_SR_MASK, dai_sr << MAX98926_DAI_SR_SHIFT); + return 0; +} + +#define MAX98926_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops max98926_dai_ops = { + .set_fmt = max98926_dai_set_fmt, + .hw_params = max98926_dai_hw_params, +}; + +static struct snd_soc_dai_driver max98926_dai[] = { +{ + .name = "max98926-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98926_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = MAX98926_FORMATS, + }, + .ops = &max98926_dai_ops, +} +}; + +static int max98926_probe(struct snd_soc_component *component) +{ + struct max98926_priv *max98926 = snd_soc_component_get_drvdata(component); + + max98926->component = component; + + /* Hi-Z all the slots */ + regmap_write(max98926->regmap, MAX98926_DOUT_HIZ_CFG4, 0xF0); + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_max98926 = { + .probe = max98926_probe, + .controls = max98926_snd_controls, + .num_controls = ARRAY_SIZE(max98926_snd_controls), + .dapm_routes = max98926_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98926_audio_map), + .dapm_widgets = max98926_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98926_dapm_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config max98926_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX98926_VERSION, + .reg_defaults = max98926_reg, + .num_reg_defaults = ARRAY_SIZE(max98926_reg), + .volatile_reg = max98926_volatile_register, + .readable_reg = max98926_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98926_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret, reg; + u32 value; + struct max98926_priv *max98926; + + max98926 = devm_kzalloc(&i2c->dev, + sizeof(*max98926), GFP_KERNEL); + if (!max98926) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98926); + max98926->regmap = devm_regmap_init_i2c(i2c, &max98926_regmap); + if (IS_ERR(max98926->regmap)) { + ret = PTR_ERR(max98926->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + goto err_out; + } + if (of_property_read_bool(i2c->dev.of_node, "interleave-mode")) + max98926->interleave_mode = true; + + if (!of_property_read_u32(i2c->dev.of_node, "vmon-slot-no", &value)) { + if (value > MAX98926_DAI_VMON_SLOT_1E_1F) { + dev_err(&i2c->dev, "vmon slot number is wrong:\n"); + return -EINVAL; + } + max98926->v_slot = value; + } + if (!of_property_read_u32(i2c->dev.of_node, "imon-slot-no", &value)) { + if (value > MAX98926_DAI_IMON_SLOT_1E_1F) { + dev_err(&i2c->dev, "imon slot number is wrong:\n"); + return -EINVAL; + } + max98926->i_slot = value; + } + ret = regmap_read(max98926->regmap, + MAX98926_VERSION, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read: %x\n", reg); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_max98926, + max98926_dai, ARRAY_SIZE(max98926_dai)); + if (ret < 0) + dev_err(&i2c->dev, + "Failed to register component: %d\n", ret); + dev_info(&i2c->dev, "device version: %x\n", reg); +err_out: + return ret; +} + +static const struct i2c_device_id max98926_i2c_id[] = { + { "max98926", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98926_i2c_id); + +static const struct of_device_id max98926_of_match[] = { + { .compatible = "maxim,max98926", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98926_of_match); + +static struct i2c_driver max98926_i2c_driver = { + .driver = { + .name = "max98926", + .of_match_table = of_match_ptr(max98926_of_match), + .pm = NULL, + }, + .probe = max98926_i2c_probe, + .id_table = max98926_i2c_id, +}; + +module_i2c_driver(max98926_i2c_driver) +MODULE_DESCRIPTION("ALSA SoC MAX98926 driver"); +MODULE_AUTHOR("Anish kumar "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98926.h b/sound/soc/codecs/max98926.h new file mode 100644 index 000000000..ccf2c3f66 --- /dev/null +++ b/sound/soc/codecs/max98926.h @@ -0,0 +1,848 @@ +/* + * max98926.h -- MAX98926 ALSA SoC Audio driver + * Copyright 2013-2015 Maxim Integrated Products + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MAX98926_H +#define _MAX98926_H + +#define MAX98926_CHIP_VERSION 0x40 +#define MAX98926_CHIP_VERSION1 0x50 + +#define MAX98926_VBAT_DATA 0x00 +#define MAX98926_VBST_DATA 0x01 +#define MAX98926_LIVE_STATUS0 0x02 +#define MAX98926_LIVE_STATUS1 0x03 +#define MAX98926_LIVE_STATUS2 0x04 +#define MAX98926_STATE0 0x05 +#define MAX98926_STATE1 0x06 +#define MAX98926_STATE2 0x07 +#define MAX98926_FLAG0 0x08 +#define MAX98926_FLAG1 0x09 +#define MAX98926_FLAG2 0x0A +#define MAX98926_IRQ_ENABLE0 0x0B +#define MAX98926_IRQ_ENABLE1 0x0C +#define MAX98926_IRQ_ENABLE2 0x0D +#define MAX98926_IRQ_CLEAR0 0x0E +#define MAX98926_IRQ_CLEAR1 0x0F +#define MAX98926_IRQ_CLEAR2 0x10 +#define MAX98926_MAP0 0x11 +#define MAX98926_MAP1 0x12 +#define MAX98926_MAP2 0x13 +#define MAX98926_MAP3 0x14 +#define MAX98926_MAP4 0x15 +#define MAX98926_MAP5 0x16 +#define MAX98926_MAP6 0x17 +#define MAX98926_MAP7 0x18 +#define MAX98926_MAP8 0x19 +#define MAX98926_DAI_CLK_MODE1 0x1A +#define MAX98926_DAI_CLK_MODE2 0x1B +#define MAX98926_DAI_CLK_DIV_M_MSBS 0x1C +#define MAX98926_DAI_CLK_DIV_M_LSBS 0x1D +#define MAX98926_DAI_CLK_DIV_N_MSBS 0x1E +#define MAX98926_DAI_CLK_DIV_N_LSBS 0x1F +#define MAX98926_FORMAT 0x20 +#define MAX98926_TDM_SLOT_SELECT 0x21 +#define MAX98926_DOUT_CFG_VMON 0x22 +#define MAX98926_DOUT_CFG_IMON 0x23 +#define MAX98926_DOUT_CFG_VBAT 0x24 +#define MAX98926_DOUT_CFG_VBST 0x25 +#define MAX98926_DOUT_CFG_FLAG 0x26 +#define MAX98926_DOUT_HIZ_CFG1 0x27 +#define MAX98926_DOUT_HIZ_CFG2 0x28 +#define MAX98926_DOUT_HIZ_CFG3 0x29 +#define MAX98926_DOUT_HIZ_CFG4 0x2A +#define MAX98926_DOUT_DRV_STRENGTH 0x2B +#define MAX98926_FILTERS 0x2C +#define MAX98926_GAIN 0x2D +#define MAX98926_GAIN_RAMPING 0x2E +#define MAX98926_SPK_AMP 0x2F +#define MAX98926_THRESHOLD 0x30 +#define MAX98926_ALC_ATTACK 0x31 +#define MAX98926_ALC_ATTEN_RLS 0x32 +#define MAX98926_ALC_HOLD_RLS 0x33 +#define MAX98926_ALC_CONFIGURATION 0x34 +#define MAX98926_BOOST_CONVERTER 0x35 +#define MAX98926_BLOCK_ENABLE 0x36 +#define MAX98926_CONFIGURATION 0x37 +#define MAX98926_GLOBAL_ENABLE 0x38 +#define MAX98926_BOOST_LIMITER 0x3A +#define MAX98926_VERSION 0xFF + +#define MAX98926_REG_CNT (MAX98926_R03A_BOOST_LIMITER+1) + +#define MAX98926_PDM_CURRENT_MASK (1<<7) +#define MAX98926_PDM_CURRENT_SHIFT 7 +#define MAX98926_PDM_VOLTAGE_MASK (1<<3) +#define MAX98926_PDM_VOLTAGE_SHIFT 3 +#define MAX98926_PDM_CHANNEL_0_MASK (1<<2) +#define MAX98926_PDM_CHANNEL_0_SHIFT 2 +#define MAX98926_PDM_CHANNEL_1_MASK (1<<6) +#define MAX98926_PDM_CHANNEL_1_SHIFT 6 +#define MAX98926_PDM_CHANNEL_1_HIZ 5 +#define MAX98926_PDM_CHANNEL_0_HIZ 1 +#define MAX98926_PDM_SOURCE_0_SHIFT 0 +#define MAX98926_PDM_SOURCE_0_MASK (1<<0) +#define MAX98926_PDM_SOURCE_1_MASK (1<<4) +#define MAX98926_PDM_SOURCE_1_SHIFT 4 + +/* MAX98926 Register Bit Fields */ + +/* MAX98926_R002_LIVE_STATUS0 */ +#define MAX98926_THERMWARN_STATUS_MASK (1<<3) +#define MAX98926_THERMWARN_STATUS_SHIFT 3 +#define MAX98926_THERMWARN_STATUS_WIDTH 1 +#define MAX98926_THERMSHDN_STATUS_MASK (1<<1) +#define MAX98926_THERMSHDN_STATUS_SHIFT 1 +#define MAX98926_THERMSHDN_STATUS_WIDTH 1 + +/* MAX98926_R003_LIVE_STATUS1 */ +#define MAX98926_SPKCURNT_STATUS_MASK (1<<5) +#define MAX98926_SPKCURNT_STATUS_SHIFT 5 +#define MAX98926_SPKCURNT_STATUS_WIDTH 1 +#define MAX98926_WATCHFAIL_STATUS_MASK (1<<4) +#define MAX98926_WATCHFAIL_STATUS_SHIFT 4 +#define MAX98926_WATCHFAIL_STATUS_WIDTH 1 +#define MAX98926_ALCINFH_STATUS_MASK (1<<3) +#define MAX98926_ALCINFH_STATUS_SHIFT 3 +#define MAX98926_ALCINFH_STATUS_WIDTH 1 +#define MAX98926_ALCACT_STATUS_MASK (1<<2) +#define MAX98926_ALCACT_STATUS_SHIFT 2 +#define MAX98926_ALCACT_STATUS_WIDTH 1 +#define MAX98926_ALCMUT_STATUS_MASK (1<<1) +#define MAX98926_ALCMUT_STATUS_SHIFT 1 +#define MAX98926_ALCMUT_STATUS_WIDTH 1 +#define MAX98926_ACLP_STATUS_MASK (1<<0) +#define MAX98926_ACLP_STATUS_SHIFT 0 +#define MAX98926_ACLP_STATUS_WIDTH 1 + +/* MAX98926_R004_LIVE_STATUS2 */ +#define MAX98926_SLOTOVRN_STATUS_MASK (1<<6) +#define MAX98926_SLOTOVRN_STATUS_SHIFT 6 +#define MAX98926_SLOTOVRN_STATUS_WIDTH 1 +#define MAX98926_INVALSLOT_STATUS_MASK (1<<5) +#define MAX98926_INVALSLOT_STATUS_SHIFT 5 +#define MAX98926_INVALSLOT_STATUS_WIDTH 1 +#define MAX98926_SLOTCNFLT_STATUS_MASK (1<<4) +#define MAX98926_SLOTCNFLT_STATUS_SHIFT 4 +#define MAX98926_SLOTCNFLT_STATUS_WIDTH 1 +#define MAX98926_VBSTOVFL_STATUS_MASK (1<<3) +#define MAX98926_VBSTOVFL_STATUS_SHIFT 3 +#define MAX98926_VBSTOVFL_STATUS_WIDTH 1 +#define MAX98926_VBATOVFL_STATUS_MASK (1<<2) +#define MAX98926_VBATOVFL_STATUS_SHIFT 2 +#define MAX98926_VBATOVFL_STATUS_WIDTH 1 +#define MAX98926_IMONOVFL_STATUS_MASK (1<<1) +#define MAX98926_IMONOVFL_STATUS_SHIFT 1 +#define MAX98926_IMONOVFL_STATUS_WIDTH 1 +#define MAX98926_VMONOVFL_STATUS_MASK (1<<0) +#define MAX98926_VMONOVFL_STATUS_SHIFT 0 +#define MAX98926_VMONOVFL_STATUS_WIDTH 1 + +/* MAX98926_R005_STATE0 */ +#define MAX98926_THERMWARN_END_STATE_MASK (1<<3) +#define MAX98926_THERMWARN_END_STATE_SHIFT 3 +#define MAX98926_THERMWARN_END_STATE_WIDTH 1 +#define MAX98926_THERMWARN_BGN_STATE_MASK (1<<2) +#define MAX98926_THERMWARN_BGN_STATE_SHIFT 1 +#define MAX98926_THERMWARN_BGN_STATE_WIDTH 1 +#define MAX98926_THERMSHDN_END_STATE_MASK (1<<1) +#define MAX98926_THERMSHDN_END_STATE_SHIFT 1 +#define MAX98926_THERMSHDN_END_STATE_WIDTH 1 +#define MAX98926_THERMSHDN_BGN_STATE_MASK (1<<0) +#define MAX98926_THERMSHDN_BGN_STATE_SHIFT 0 +#define MAX98926_THERMSHDN_BGN_STATE_WIDTH 1 + +/* MAX98926_R006_STATE1 */ +#define MAX98926_SPRCURNT_STATE_MASK (1<<5) +#define MAX98926_SPRCURNT_STATE_SHIFT 5 +#define MAX98926_SPRCURNT_STATE_WIDTH 1 +#define MAX98926_WATCHFAIL_STATE_MASK (1<<4) +#define MAX98926_WATCHFAIL_STATE_SHIFT 4 +#define MAX98926_WATCHFAIL_STATE_WIDTH 1 +#define MAX98926_ALCINFH_STATE_MASK (1<<3) +#define MAX98926_ALCINFH_STATE_SHIFT 3 +#define MAX98926_ALCINFH_STATE_WIDTH 1 +#define MAX98926_ALCACT_STATE_MASK (1<<2) +#define MAX98926_ALCACT_STATE_SHIFT 2 +#define MAX98926_ALCACT_STATE_WIDTH 1 +#define MAX98926_ALCMUT_STATE_MASK (1<<1) +#define MAX98926_ALCMUT_STATE_SHIFT 1 +#define MAX98926_ALCMUT_STATE_WIDTH 1 +#define MAX98926_ALCP_STATE_MASK (1<<0) +#define MAX98926_ALCP_STATE_SHIFT 0 +#define MAX98926_ALCP_STATE_WIDTH 1 + +/* MAX98926_R007_STATE2 */ +#define MAX98926_SLOTOVRN_STATE_MASK (1<<6) +#define MAX98926_SLOTOVRN_STATE_SHIFT 6 +#define MAX98926_SLOTOVRN_STATE_WIDTH 1 +#define MAX98926_INVALSLOT_STATE_MASK (1<<5) +#define MAX98926_INVALSLOT_STATE_SHIFT 5 +#define MAX98926_INVALSLOT_STATE_WIDTH 1 +#define MAX98926_SLOTCNFLT_STATE_MASK (1<<4) +#define MAX98926_SLOTCNFLT_STATE_SHIFT 4 +#define MAX98926_SLOTCNFLT_STATE_WIDTH 1 +#define MAX98926_VBSTOVFL_STATE_MASK (1<<3) +#define MAX98926_VBSTOVFL_STATE_SHIFT 3 +#define MAX98926_VBSTOVFL_STATE_WIDTH 1 +#define MAX98926_VBATOVFL_STATE_MASK (1<<2) +#define MAX98926_VBATOVFL_STATE_SHIFT 2 +#define MAX98926_VBATOVFL_STATE_WIDTH 1 +#define MAX98926_IMONOVFL_STATE_MASK (1<<1) +#define MAX98926_IMONOVFL_STATE_SHIFT 1 +#define MAX98926_IMONOVFL_STATE_WIDTH 1 +#define MAX98926_VMONOVFL_STATE_MASK (1<<0) +#define MAX98926_VMONOVFL_STATE_SHIFT 0 +#define MAX98926_VMONOVFL_STATE_WIDTH 1 + +/* MAX98926_R008_FLAG0 */ +#define MAX98926_THERMWARN_END_FLAG_MASK (1<<3) +#define MAX98926_THERMWARN_END_FLAG_SHIFT 3 +#define MAX98926_THERMWARN_END_FLAG_WIDTH 1 +#define MAX98926_THERMWARN_BGN_FLAG_MASK (1<<2) +#define MAX98926_THERMWARN_BGN_FLAG_SHIFT 2 +#define MAX98926_THERMWARN_BGN_FLAG_WIDTH 1 +#define MAX98926_THERMSHDN_END_FLAG_MASK (1<<1) +#define MAX98926_THERMSHDN_END_FLAG_SHIFT 1 +#define MAX98926_THERMSHDN_END_FLAG_WIDTH 1 +#define MAX98926_THERMSHDN_BGN_FLAG_MASK (1<<0) +#define MAX98926_THERMSHDN_BGN_FLAG_SHIFT 0 +#define MAX98926_THERMSHDN_BGN_FLAG_WIDTH 1 + +/* MAX98926_R009_FLAG1 */ +#define MAX98926_SPKCURNT_FLAG_MASK (1<<5) +#define MAX98926_SPKCURNT_FLAG_SHIFT 5 +#define MAX98926_SPKCURNT_FLAG_WIDTH 1 +#define MAX98926_WATCHFAIL_FLAG_MASK (1<<4) +#define MAX98926_WATCHFAIL_FLAG_SHIFT 4 +#define MAX98926_WATCHFAIL_FLAG_WIDTH 1 +#define MAX98926_ALCINFH_FLAG_MASK (1<<3) +#define MAX98926_ALCINFH_FLAG_SHIFT 3 +#define MAX98926_ALCINFH_FLAG_WIDTH 1 +#define MAX98926_ALCACT_FLAG_MASK (1<<2) +#define MAX98926_ALCACT_FLAG_SHIFT 2 +#define MAX98926_ALCACT_FLAG_WIDTH 1 +#define MAX98926_ALCMUT_FLAG_MASK (1<<1) +#define MAX98926_ALCMUT_FLAG_SHIFT 1 +#define MAX98926_ALCMUT_FLAG_WIDTH 1 +#define MAX98926_ALCP_FLAG_MASK (1<<0) +#define MAX98926_ALCP_FLAG_SHIFT 0 +#define MAX98926_ALCP_FLAG_WIDTH 1 + +/* MAX98926_R00A_FLAG2 */ +#define MAX98926_SLOTOVRN_FLAG_MASK (1<<6) +#define MAX98926_SLOTOVRN_FLAG_SHIFT 6 +#define MAX98926_SLOTOVRN_FLAG_WIDTH 1 +#define MAX98926_INVALSLOT_FLAG_MASK (1<<5) +#define MAX98926_INVALSLOT_FLAG_SHIFT 5 +#define MAX98926_INVALSLOT_FLAG_WIDTH 1 +#define MAX98926_SLOTCNFLT_FLAG_MASK (1<<4) +#define MAX98926_SLOTCNFLT_FLAG_SHIFT 4 +#define MAX98926_SLOTCNFLT_FLAG_WIDTH 1 +#define MAX98926_VBSTOVFL_FLAG_MASK (1<<3) +#define MAX98926_VBSTOVFL_FLAG_SHIFT 3 +#define MAX98926_VBSTOVFL_FLAG_WIDTH 1 +#define MAX98926_VBATOVFL_FLAG_MASK (1<<2) +#define MAX98926_VBATOVFL_FLAG_SHIFT 2 +#define MAX98926_VBATOVFL_FLAG_WIDTH 1 +#define MAX98926_IMONOVFL_FLAG_MASK (1<<1) +#define MAX98926_IMONOVFL_FLAG_SHIFT 1 +#define MAX98926_IMONOVFL_FLAG_WIDTH 1 +#define MAX98926_VMONOVFL_FLAG_MASK (1<<0) +#define MAX98926_VMONOVFL_FLAG_SHIFT 0 +#define MAX98926_VMONOVFL_FLAG_WIDTH 1 + +/* MAX98926_R00B_IRQ_ENABLE0 */ +#define MAX98926_THERMWARN_END_EN_MASK (1<<3) +#define MAX98926_THERMWARN_END_EN_SHIFT 3 +#define MAX98926_THERMWARN_END_EN_WIDTH 1 +#define MAX98926_THERMWARN_BGN_EN_MASK (1<<2) +#define MAX98926_THERMWARN_BGN_EN_SHIFT 2 +#define MAX98926_THERMWARN_BGN_EN_WIDTH 1 +#define MAX98926_THERMSHDN_END_EN_MASK (1<<1) +#define MAX98926_THERMSHDN_END_EN_SHIFT 1 +#define MAX98926_THERMSHDN_END_EN_WIDTH 1 +#define MAX98926_THERMSHDN_BGN_EN_MASK (1<<0) +#define MAX98926_THERMSHDN_BGN_EN_SHIFT 0 +#define MAX98926_THERMSHDN_BGN_EN_WIDTH 1 + +/* MAX98926_R00C_IRQ_ENABLE1 */ +#define MAX98926_SPKCURNT_EN_MASK (1<<5) +#define MAX98926_SPKCURNT_EN_SHIFT 5 +#define MAX98926_SPKCURNT_EN_WIDTH 1 +#define MAX98926_WATCHFAIL_EN_MASK (1<<4) +#define MAX98926_WATCHFAIL_EN_SHIFT 4 +#define MAX98926_WATCHFAIL_EN_WIDTH 1 +#define MAX98926_ALCINFH_EN_MASK (1<<3) +#define MAX98926_ALCINFH_EN_SHIFT 3 +#define MAX98926_ALCINFH_EN_WIDTH 1 +#define MAX98926_ALCACT_EN_MASK (1<<2) +#define MAX98926_ALCACT_EN_SHIFT 2 +#define MAX98926_ALCACT_EN_WIDTH 1 +#define MAX98926_ALCMUT_EN_MASK (1<<1) +#define MAX98926_ALCMUT_EN_SHIFT 1 +#define MAX98926_ALCMUT_EN_WIDTH 1 +#define MAX98926_ALCP_EN_MASK (1<<0) +#define MAX98926_ALCP_EN_SHIFT 0 +#define MAX98926_ALCP_EN_WIDTH 1 + +/* MAX98926_R00D_IRQ_ENABLE2 */ +#define MAX98926_SLOTOVRN_EN_MASK (1<<6) +#define MAX98926_SLOTOVRN_EN_SHIFT 6 +#define MAX98926_SLOTOVRN_EN_WIDTH 1 +#define MAX98926_INVALSLOT_EN_MASK (1<<5) +#define MAX98926_INVALSLOT_EN_SHIFT 5 +#define MAX98926_INVALSLOT_EN_WIDTH 1 +#define MAX98926_SLOTCNFLT_EN_MASK (1<<4) +#define MAX98926_SLOTCNFLT_EN_SHIFT 4 +#define MAX98926_SLOTCNFLT_EN_WIDTH 1 +#define MAX98926_VBSTOVFL_EN_MASK (1<<3) +#define MAX98926_VBSTOVFL_EN_SHIFT 3 +#define MAX98926_VBSTOVFL_EN_WIDTH 1 +#define MAX98926_VBATOVFL_EN_MASK (1<<2) +#define MAX98926_VBATOVFL_EN_SHIFT 2 +#define MAX98926_VBATOVFL_EN_WIDTH 1 +#define MAX98926_IMONOVFL_EN_MASK (1<<1) +#define MAX98926_IMONOVFL_EN_SHIFT 1 +#define MAX98926_IMONOVFL_EN_WIDTH 1 +#define MAX98926_VMONOVFL_EN_MASK (1<<0) +#define MAX98926_VMONOVFL_EN_SHIFT 0 +#define MAX98926_VMONOVFL_EN_WIDTH 1 + +/* MAX98926_R00E_IRQ_CLEAR0 */ +#define MAX98926_THERMWARN_END_CLR_MASK (1<<3) +#define MAX98926_THERMWARN_END_CLR_SHIFT 3 +#define MAX98926_THERMWARN_END_CLR_WIDTH 1 +#define MAX98926_THERMWARN_BGN_CLR_MASK (1<<2) +#define MAX98926_THERMWARN_BGN_CLR_SHIFT 2 +#define MAX98926_THERMWARN_BGN_CLR_WIDTH 1 +#define MAX98926_THERMSHDN_END_CLR_MASK (1<<1) +#define MAX98926_THERMSHDN_END_CLR_SHIFT 1 +#define MAX98926_THERMSHDN_END_CLR_WIDTH 1 +#define MAX98926_THERMSHDN_BGN_CLR_MASK (1<<0) +#define MAX98926_THERMSHDN_BGN_CLR_SHIFT 0 +#define MAX98926_THERMSHDN_BGN_CLR_WIDTH 1 + +/* MAX98926_R00F_IRQ_CLEAR1 */ +#define MAX98926_SPKCURNT_CLR_MASK (1<<5) +#define MAX98926_SPKCURNT_CLR_SHIFT 5 +#define MAX98926_SPKCURNT_CLR_WIDTH 1 +#define MAX98926_WATCHFAIL_CLR_MASK (1<<4) +#define MAX98926_WATCHFAIL_CLR_SHIFT 4 +#define MAX98926_WATCHFAIL_CLR_WIDTH 1 +#define MAX98926_ALCINFH_CLR_MASK (1<<3) +#define MAX98926_ALCINFH_CLR_SHIFT 3 +#define MAX98926_ALCINFH_CLR_WIDTH 1 +#define MAX98926_ALCACT_CLR_MASK (1<<2) +#define MAX98926_ALCACT_CLR_SHIFT 2 +#define MAX98926_ALCACT_CLR_WIDTH 1 +#define MAX98926_ALCMUT_CLR_MASK (1<<1) +#define MAX98926_ALCMUT_CLR_SHIFT 1 +#define MAX98926_ALCMUT_CLR_WIDTH 1 +#define MAX98926_ALCP_CLR_MASK (1<<0) +#define MAX98926_ALCP_CLR_SHIFT 0 +#define MAX98926_ALCP_CLR_WIDTH 1 + +/* MAX98926_R010_IRQ_CLEAR2 */ +#define MAX98926_SLOTOVRN_CLR_MASK (1<<6) +#define MAX98926_SLOTOVRN_CLR_SHIFT 6 +#define MAX98926_SLOTOVRN_CLR_WIDTH 1 +#define MAX98926_INVALSLOT_CLR_MASK (1<<5) +#define MAX98926_INVALSLOT_CLR_SHIFT 5 +#define MAX98926_INVALSLOT_CLR_WIDTH 1 +#define MAX98926_SLOTCNFLT_CLR_MASK (1<<4) +#define MAX98926_SLOTCNFLT_CLR_SHIFT 4 +#define MAX98926_SLOTCNFLT_CLR_WIDTH 1 +#define MAX98926_VBSTOVFL_CLR_MASK (1<<3) +#define MAX98926_VBSTOVFL_CLR_SHIFT 3 +#define MAX98926_VBSTOVFL_CLR_WIDTH 1 +#define MAX98926_VBATOVFL_CLR_MASK (1<<2) +#define MAX98926_VBATOVFL_CLR_SHIFT 2 +#define MAX98926_VBATOVFL_CLR_WIDTH 1 +#define MAX98926_IMONOVFL_CLR_MASK (1<<1) +#define MAX98926_IMONOVFL_CLR_SHIFT 1 +#define MAX98926_IMONOVFL_CLR_WIDTH 1 +#define MAX98926_VMONOVFL_CLR_MASK (1<<0) +#define MAX98926_VMONOVFL_CLR_SHIFT 0 +#define MAX98926_VMONOVFL_CLR_WIDTH 1 + +/* MAX98926_R011_MAP0 */ +#define MAX98926_ER_THERMWARN_EN_MASK (1<<7) +#define MAX98926_ER_THERMWARN_EN_SHIFT 7 +#define MAX98926_ER_THERMWARN_EN_WIDTH 1 +#define MAX98926_ER_THERMWARN_MAP_MASK (0x07<<4) +#define MAX98926_ER_THERMWARN_MAP_SHIFT 4 +#define MAX98926_ER_THERMWARN_MAP_WIDTH 3 + +/* MAX98926_R012_MAP1 */ +#define MAX98926_ER_ALCMUT_EN_MASK (1<<7) +#define MAX98926_ER_ALCMUT_EN_SHIFT 7 +#define MAX98926_ER_ALCMUT_EN_WIDTH 1 +#define MAX98926_ER_ALCMUT_MAP_MASK (0x07<<4) +#define MAX98926_ER_ALCMUT_MAP_SHIFT 4 +#define MAX98926_ER_ALCMUT_MAP_WIDTH 3 +#define MAX98926_ER_ALCP_EN_MASK (1<<3) +#define MAX98926_ER_ALCP_EN_SHIFT 3 +#define MAX98926_ER_ALCP_EN_WIDTH 1 +#define MAX98926_ER_ALCP_MAP_MASK (0x07<<0) +#define MAX98926_ER_ALCP_MAP_SHIFT 0 +#define MAX98926_ER_ALCP_MAP_WIDTH 3 + +/* MAX98926_R013_MAP2 */ +#define MAX98926_ER_ALCINFH_EN_MASK (1<<7) +#define MAX98926_ER_ALCINFH_EN_SHIFT 7 +#define MAX98926_ER_ALCINFH_EN_WIDTH 1 +#define MAX98926_ER_ALCINFH_MAP_MASK (0x07<<4) +#define MAX98926_ER_ALCINFH_MAP_SHIFT 4 +#define MAX98926_ER_ALCINFH_MAP_WIDTH 3 +#define MAX98926_ER_ALCACT_EN_MASK (1<<3) +#define MAX98926_ER_ALCACT_EN_SHIFT 3 +#define MAX98926_ER_ALCACT_EN_WIDTH 1 +#define MAX98926_ER_ALCACT_MAP_MASK (0x07<<0) +#define MAX98926_ER_ALCACT_MAP_SHIFT 0 +#define MAX98926_ER_ALCACT_MAP_WIDTH 3 + +/* MAX98926_R014_MAP3 */ +#define MAX98926_ER_SPKCURNT_EN_MASK (1<<7) +#define MAX98926_ER_SPKCURNT_EN_SHIFT 7 +#define MAX98926_ER_SPKCURNT_EN_WIDTH 1 +#define MAX98926_ER_SPKCURNT_MAP_MASK (0x07<<4) +#define MAX98926_ER_SPKCURNT_MAP_SHIFT 4 +#define MAX98926_ER_SPKCURNT_MAP_WIDTH 3 + +/* MAX98926_R015_MAP4 */ +/* RESERVED */ + +/* MAX98926_R016_MAP5 */ +#define MAX98926_ER_IMONOVFL_EN_MASK (1<<7) +#define MAX98926_ER_IMONOVFL_EN_SHIFT 7 +#define MAX98926_ER_IMONOVFL_EN_WIDTH 1 +#define MAX98926_ER_IMONOVFL_MAP_MASK (0x07<<4) +#define MAX98926_ER_IMONOVFL_MAP_SHIFT 4 +#define MAX98926_ER_IMONOVFL_MAP_WIDTH 3 +#define MAX98926_ER_VMONOVFL_EN_MASK (1<<3) +#define MAX98926_ER_VMONOVFL_EN_SHIFT 3 +#define MAX98926_ER_VMONOVFL_EN_WIDTH 1 +#define MAX98926_ER_VMONOVFL_MAP_MASK (0x07<<0) +#define MAX98926_ER_VMONOVFL_MAP_SHIFT 0 +#define MAX98926_ER_VMONOVFL_MAP_WIDTH 3 + +/* MAX98926_R017_MAP6 */ +#define MAX98926_ER_VBSTOVFL_EN_MASK (1<<7) +#define MAX98926_ER_VBSTOVFL_EN_SHIFT 7 +#define MAX98926_ER_VBSTOVFL_EN_WIDTH 1 +#define MAX98926_ER_VBSTOVFL_MAP_MASK (0x07<<4) +#define MAX98926_ER_VBSTOVFL_MAP_SHIFT 4 +#define MAX98926_ER_VBSTOVFL_MAP_WIDTH 3 +#define MAX98926_ER_VBATOVFL_EN_MASK (1<<3) +#define MAX98926_ER_VBATOVFL_EN_SHIFT 3 +#define MAX98926_ER_VBATOVFL_EN_WIDTH 1 +#define MAX98926_ER_VBATOVFL_MAP_MASK (0x07<<0) +#define MAX98926_ER_VBATOVFL_MAP_SHIFT 0 +#define MAX98926_ER_VBATOVFL_MAP_WIDTH 3 + +/* MAX98926_R018_MAP7 */ +#define MAX98926_ER_INVALSLOT_EN_MASK (1<<7) +#define MAX98926_ER_INVALSLOT_EN_SHIFT 7 +#define MAX98926_ER_INVALSLOT_EN_WIDTH 1 +#define MAX98926_ER_INVALSLOT_MAP_MASK (0x07<<4) +#define MAX98926_ER_INVALSLOT_MAP_SHIFT 4 +#define MAX98926_ER_INVALSLOT_MAP_WIDTH 3 +#define MAX98926_ER_SLOTCNFLT_EN_MASK (1<<3) +#define MAX98926_ER_SLOTCNFLT_EN_SHIFT 3 +#define MAX98926_ER_SLOTCNFLT_EN_WIDTH 1 +#define MAX98926_ER_SLOTCNFLT_MAP_MASK (0x07<<0) +#define MAX98926_ER_SLOTCNFLT_MAP_SHIFT 0 +#define MAX98926_ER_SLOTCNFLT_MAP_WIDTH 3 + +/* MAX98926_R019_MAP8 */ +#define MAX98926_ER_SLOTOVRN_EN_MASK (1<<3) +#define MAX98926_ER_SLOTOVRN_EN_SHIFT 3 +#define MAX98926_ER_SLOTOVRN_EN_WIDTH 1 +#define MAX98926_ER_SLOTOVRN_MAP_MASK (0x07<<0) +#define MAX98926_ER_SLOTOVRN_MAP_SHIFT 0 +#define MAX98926_ER_SLOTOVRN_MAP_WIDTH 3 + +/* MAX98926_R01A_DAI_CLK_MODE1 */ +#define MAX98926_DAI_CLK_SOURCE_MASK (1<<6) +#define MAX98926_DAI_CLK_SOURCE_SHIFT 6 +#define MAX98926_DAI_CLK_SOURCE_WIDTH 1 +#define MAX98926_MDLL_MULT_MASK (0x0F<<0) +#define MAX98926_MDLL_MULT_SHIFT 0 +#define MAX98926_MDLL_MULT_WIDTH 4 + +#define MAX98926_MDLL_MULT_MCLKx8 6 +#define MAX98926_MDLL_MULT_MCLKx16 8 + +/* MAX98926_R01B_DAI_CLK_MODE2 */ +#define MAX98926_DAI_SR_MASK (0x0F<<4) +#define MAX98926_DAI_SR_SHIFT 4 +#define MAX98926_DAI_SR_WIDTH 4 +#define MAX98926_DAI_MAS_MASK (1<<3) +#define MAX98926_DAI_MAS_SHIFT 3 +#define MAX98926_DAI_MAS_WIDTH 1 +#define MAX98926_DAI_BSEL_MASK (0x07<<0) +#define MAX98926_DAI_BSEL_SHIFT 0 +#define MAX98926_DAI_BSEL_WIDTH 3 + +#define MAX98926_DAI_BSEL_32 (0 << MAX98926_DAI_BSEL_SHIFT) +#define MAX98926_DAI_BSEL_48 (1 << MAX98926_DAI_BSEL_SHIFT) +#define MAX98926_DAI_BSEL_64 (2 << MAX98926_DAI_BSEL_SHIFT) +#define MAX98926_DAI_BSEL_256 (6 << MAX98926_DAI_BSEL_SHIFT) + +/* MAX98926_R01C_DAI_CLK_DIV_M_MSBS */ +#define MAX98926_DAI_M_MSBS_MASK (0xFF<<0) +#define MAX98926_DAI_M_MSBS_SHIFT 0 +#define MAX98926_DAI_M_MSBS_WIDTH 8 + +/* MAX98926_R01D_DAI_CLK_DIV_M_LSBS */ +#define MAX98926_DAI_M_LSBS_MASK (0xFF<<0) +#define MAX98926_DAI_M_LSBS_SHIFT 0 +#define MAX98926_DAI_M_LSBS_WIDTH 8 + +/* MAX98926_R01E_DAI_CLK_DIV_N_MSBS */ +#define MAX98926_DAI_N_MSBS_MASK (0x7F<<0) +#define MAX98926_DAI_N_MSBS_SHIFT 0 +#define MAX98926_DAI_N_MSBS_WIDTH 7 + +/* MAX98926_R01F_DAI_CLK_DIV_N_LSBS */ +#define MAX98926_DAI_N_LSBS_MASK (0xFF<<0) +#define MAX98926_DAI_N_LSBS_SHIFT 0 +#define MAX98926_DAI_N_LSBS_WIDTH 8 + +/* MAX98926_R020_FORMAT */ +#define MAX98926_DAI_CHANSZ_MASK (0x03<<6) +#define MAX98926_DAI_CHANSZ_SHIFT 6 +#define MAX98926_DAI_CHANSZ_WIDTH 2 +#define MAX98926_DAI_INTERLEAVE_MASK (1<<5) +#define MAX98926_DAI_INTERLEAVE_SHIFT 5 +#define MAX98926_DAI_INTERLEAVE_WIDTH 1 +#define MAX98926_DAI_EXTBCLK_HIZ_MASK (1<<4) +#define MAX98926_DAI_EXTBCLK_HIZ_SHIFT 4 +#define MAX98926_DAI_EXTBCLK_HIZ_WIDTH 1 +#define MAX98926_DAI_WCI_MASK (1<<3) +#define MAX98926_DAI_WCI_SHIFT 3 +#define MAX98926_DAI_WCI_WIDTH 1 +#define MAX98926_DAI_BCI_MASK (1<<2) +#define MAX98926_DAI_BCI_SHIFT 2 +#define MAX98926_DAI_BCI_WIDTH 1 +#define MAX98926_DAI_DLY_MASK (1<<1) +#define MAX98926_DAI_DLY_SHIFT 1 +#define MAX98926_DAI_DLY_WIDTH 1 +#define MAX98926_DAI_TDM_MASK (1<<0) +#define MAX98926_DAI_TDM_SHIFT 0 +#define MAX98926_DAI_TDM_WIDTH 1 + +#define MAX98926_DAI_CHANSZ_16 (1 << MAX98926_DAI_CHANSZ_SHIFT) +#define MAX98926_DAI_CHANSZ_24 (2 << MAX98926_DAI_CHANSZ_SHIFT) +#define MAX98926_DAI_CHANSZ_32 (3 << MAX98926_DAI_CHANSZ_SHIFT) + +/* MAX98926_R021_TDM_SLOT_SELECT */ +#define MAX98926_DAI_DO_EN_MASK (1<<7) +#define MAX98926_DAI_DO_EN_SHIFT 7 +#define MAX98926_DAI_DO_EN_WIDTH 1 +#define MAX98926_DAI_DIN_EN_MASK (1<<6) +#define MAX98926_DAI_DIN_EN_SHIFT 6 +#define MAX98926_DAI_DIN_EN_WIDTH 1 +#define MAX98926_DAI_INR_SOURCE_MASK (0x07<<3) +#define MAX98926_DAI_INR_SOURCE_SHIFT 3 +#define MAX98926_DAI_INR_SOURCE_WIDTH 3 +#define MAX98926_DAI_INL_SOURCE_MASK (0x07<<0) +#define MAX98926_DAI_INL_SOURCE_SHIFT 0 +#define MAX98926_DAI_INL_SOURCE_WIDTH 3 + +/* MAX98926_R022_DOUT_CFG_VMON */ +#define MAX98926_DAI_VMON_EN_MASK (1<<5) +#define MAX98926_DAI_VMON_EN_SHIFT 5 +#define MAX98926_DAI_VMON_EN_WIDTH 1 +#define MAX98926_DAI_VMON_SLOT_MASK (0x1F<<0) +#define MAX98926_DAI_VMON_SLOT_SHIFT 0 +#define MAX98926_DAI_VMON_SLOT_WIDTH 5 + +#define MAX98926_DAI_VMON_SLOT_00_01 (0 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_01_02 (1 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_02_03 (2 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_03_04 (3 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_04_05 (4 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_05_06 (5 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_06_07 (6 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_07_08 (7 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_08_09 (8 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_09_0A (9 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_0A_0B (10 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_0B_0C (11 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_0C_0D (12 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_0D_0E (13 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_0E_0F (14 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_0F_10 (15 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_10_11 (16 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_11_12 (17 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_12_13 (18 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_13_14 (19 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_14_15 (20 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_15_16 (21 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_16_17 (22 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_17_18 (23 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_18_19 (24 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_19_1A (25 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_1A_1B (26 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_1B_1C (27 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_1C_1D (28 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_1D_1E (29 << MAX98926_DAI_VMON_SLOT_SHIFT) +#define MAX98926_DAI_VMON_SLOT_1E_1F (30 << MAX98926_DAI_VMON_SLOT_SHIFT) + +/* MAX98926_R023_DOUT_CFG_IMON */ +#define MAX98926_DAI_IMON_EN_MASK (1<<5) +#define MAX98926_DAI_IMON_EN_SHIFT 5 +#define MAX98926_DAI_IMON_EN_WIDTH 1 +#define MAX98926_DAI_IMON_SLOT_MASK (0x1F<<0) +#define MAX98926_DAI_IMON_SLOT_SHIFT 0 +#define MAX98926_DAI_IMON_SLOT_WIDTH 5 + +#define MAX98926_DAI_IMON_SLOT_00_01 (0 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_01_02 (1 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_02_03 (2 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_03_04 (3 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_04_05 (4 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_05_06 (5 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_06_07 (6 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_07_08 (7 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_08_09 (8 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_09_0A (9 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_0A_0B (10 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_0B_0C (11 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_0C_0D (12 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_0D_0E (13 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_0E_0F (14 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_0F_10 (15 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_10_11 (16 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_11_12 (17 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_12_13 (18 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_13_14 (19 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_14_15 (20 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_15_16 (21 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_16_17 (22 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_17_18 (23 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_18_19 (24 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_19_1A (25 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_1A_1B (26 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_1B_1C (27 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_1C_1D (28 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_1D_1E (29 << MAX98926_DAI_IMON_SLOT_SHIFT) +#define MAX98926_DAI_IMON_SLOT_1E_1F (30 << MAX98926_DAI_IMON_SLOT_SHIFT) + +/* MAX98926_R024_DOUT_CFG_VBAT */ +#define MAX98926_DAI_INTERLEAVE_SLOT_MASK (0x1F<<0) +#define MAX98926_DAI_INTERLEAVE_SLOT_SHIFT 0 +#define MAX98926_DAI_INTERLEAVE_SLOT_WIDTH 5 + +/* MAX98926_R025_DOUT_CFG_VBST */ +#define MAX98926_DAI_VBST_EN_MASK (1<<5) +#define MAX98926_DAI_VBST_EN_SHIFT 5 +#define MAX98926_DAI_VBST_EN_WIDTH 1 +#define MAX98926_DAI_VBST_SLOT_MASK (0x1F<<0) +#define MAX98926_DAI_VBST_SLOT_SHIFT 0 +#define MAX98926_DAI_VBST_SLOT_WIDTH 5 + +/* MAX98926_R026_DOUT_CFG_FLAG */ +#define MAX98926_DAI_FLAG_EN_MASK (1<<5) +#define MAX98926_DAI_FLAG_EN_SHIFT 5 +#define MAX98926_DAI_FLAG_EN_WIDTH 1 +#define MAX98926_DAI_FLAG_SLOT_MASK (0x1F<<0) +#define MAX98926_DAI_FLAG_SLOT_SHIFT 0 +#define MAX98926_DAI_FLAG_SLOT_WIDTH 5 + +/* MAX98926_R027_DOUT_HIZ_CFG1 */ +#define MAX98926_DAI_SLOT_HIZ_CFG1_MASK (0xFF<<0) +#define MAX98926_DAI_SLOT_HIZ_CFG1_SHIFT 0 +#define MAX98926_DAI_SLOT_HIZ_CFG1_WIDTH 8 + +/* MAX98926_R028_DOUT_HIZ_CFG2 */ +#define MAX98926_DAI_SLOT_HIZ_CFG2_MASK (0xFF<<0) +#define MAX98926_DAI_SLOT_HIZ_CFG2_SHIFT 0 +#define MAX98926_DAI_SLOT_HIZ_CFG2_WIDTH 8 + +/* MAX98926_R029_DOUT_HIZ_CFG3 */ +#define MAX98926_DAI_SLOT_HIZ_CFG3_MASK (0xFF<<0) +#define MAX98926_DAI_SLOT_HIZ_CFG3_SHIFT 0 +#define MAX98926_DAI_SLOT_HIZ_CFG3_WIDTH 8 + +/* MAX98926_R02A_DOUT_HIZ_CFG4 */ +#define MAX98926_DAI_SLOT_HIZ_CFG4_MASK (0xFF<<0) +#define MAX98926_DAI_SLOT_HIZ_CFG4_SHIFT 0 +#define MAX98926_DAI_SLOT_HIZ_CFG4_WIDTH 8 + +/* MAX98926_R02B_DOUT_DRV_STRENGTH */ +#define MAX98926_DAI_OUT_DRIVE_MASK (0x03<<0) +#define MAX98926_DAI_OUT_DRIVE_SHIFT 0 +#define MAX98926_DAI_OUT_DRIVE_WIDTH 2 + +/* MAX98926_R02C_FILTERS */ +#define MAX98926_ADC_DITHER_EN_MASK (1<<7) +#define MAX98926_ADC_DITHER_EN_SHIFT 7 +#define MAX98926_ADC_DITHER_EN_WIDTH 1 +#define MAX98926_IV_DCB_EN_MASK (1<<6) +#define MAX98926_IV_DCB_EN_SHIFT 6 +#define MAX98926_IV_DCB_EN_WIDTH 1 +#define MAX98926_DAC_DITHER_EN_MASK (1<<4) +#define MAX98926_DAC_DITHER_EN_SHIFT 4 +#define MAX98926_DAC_DITHER_EN_WIDTH 1 +#define MAX98926_DAC_FILTER_MODE_MASK (1<<3) +#define MAX98926_DAC_FILTER_MODE_SHIFT 3 +#define MAX98926_DAC_FILTER_MODE_WIDTH 1 +#define MAX98926_DAC_HPF_MASK (0x07<<0) +#define MAX98926_DAC_HPF_SHIFT 0 +#define MAX98926_DAC_HPF_WIDTH 3 +#define MAX98926_DAC_HPF_DISABLE (0 << MAX98926_DAC_HPF_SHIFT) +#define MAX98926_DAC_HPF_DC_BLOCK (1 << MAX98926_DAC_HPF_SHIFT) +#define MAX98926_DAC_HPF_EN_100 (2 << MAX98926_DAC_HPF_SHIFT) +#define MAX98926_DAC_HPF_EN_200 (3 << MAX98926_DAC_HPF_SHIFT) +#define MAX98926_DAC_HPF_EN_400 (4 << MAX98926_DAC_HPF_SHIFT) +#define MAX98926_DAC_HPF_EN_800 (5 << MAX98926_DAC_HPF_SHIFT) + +/* MAX98926_R02D_GAIN */ +#define MAX98926_DAC_IN_SEL_MASK (0x03<<5) +#define MAX98926_DAC_IN_SEL_SHIFT 5 +#define MAX98926_DAC_IN_SEL_WIDTH 2 +#define MAX98926_SPK_GAIN_MASK (0x1F<<0) +#define MAX98926_SPK_GAIN_SHIFT 0 +#define MAX98926_SPK_GAIN_WIDTH 5 + +#define MAX98926_DAC_IN_SEL_LEFT_DAI (0 << MAX98926_DAC_IN_SEL_SHIFT) +#define MAX98926_DAC_IN_SEL_RIGHT_DAI (1 << MAX98926_DAC_IN_SEL_SHIFT) +#define MAX98926_DAC_IN_SEL_SUMMED_DAI (2 << MAX98926_DAC_IN_SEL_SHIFT) +#define MAX98926_DAC_IN_SEL_DIV2_SUMMED_DAI (3 << MAX98926_DAC_IN_SEL_SHIFT) + +/* MAX98926_R02E_GAIN_RAMPING */ +#define MAX98926_SPK_RMP_EN_MASK (1<<1) +#define MAX98926_SPK_RMP_EN_SHIFT 1 +#define MAX98926_SPK_RMP_EN_WIDTH 1 +#define MAX98926_SPK_ZCD_EN_MASK (1<<0) +#define MAX98926_SPK_ZCD_EN_SHIFT 0 +#define MAX98926_SPK_ZCD_EN_WIDTH 1 + +/* MAX98926_R02F_SPK_AMP */ +#define MAX98926_SPK_MODE_MASK (1<<0) +#define MAX98926_SPK_MODE_SHIFT 0 +#define MAX98926_SPK_MODE_WIDTH 1 +#define MAX98926_INSELECT_MODE_MASK (1<<1) +#define MAX98926_INSELECT_MODE_SHIFT 1 +#define MAX98926_INSELECT_MODE_WIDTH 1 + +/* MAX98926_R030_THRESHOLD */ +#define MAX98926_ALC_EN_MASK (1<<5) +#define MAX98926_ALC_EN_SHIFT 5 +#define MAX98926_ALC_EN_WIDTH 1 +#define MAX98926_ALC_TH_MASK (0x1F<<0) +#define MAX98926_ALC_TH_SHIFT 0 +#define MAX98926_ALC_TH_WIDTH 5 + +/* MAX98926_R031_ALC_ATTACK */ +#define MAX98926_ALC_ATK_STEP_MASK (0x0F<<4) +#define MAX98926_ALC_ATK_STEP_SHIFT 4 +#define MAX98926_ALC_ATK_STEP_WIDTH 4 +#define MAX98926_ALC_ATK_RATE_MASK (0x7<<0) +#define MAX98926_ALC_ATK_RATE_SHIFT 0 +#define MAX98926_ALC_ATK_RATE_WIDTH 3 + +/* MAX98926_R032_ALC_ATTEN_RLS */ +#define MAX98926_ALC_MAX_ATTEN_MASK (0x0F<<4) +#define MAX98926_ALC_MAX_ATTEN_SHIFT 4 +#define MAX98926_ALC_MAX_ATTEN_WIDTH 4 +#define MAX98926_ALC_RLS_RATE_MASK (0x7<<0) +#define MAX98926_ALC_RLS_RATE_SHIFT 0 +#define MAX98926_ALC_RLS_RATE_WIDTH 3 + +/* MAX98926_R033_ALC_HOLD_RLS */ +#define MAX98926_ALC_RLS_TGR_MASK (1<<0) +#define MAX98926_ALC_RLS_TGR_SHIFT 0 +#define MAX98926_ALC_RLS_TGR_WIDTH 1 + +/* MAX98926_R034_ALC_CONFIGURATION */ +#define MAX98926_ALC_MUTE_EN_MASK (1<<7) +#define MAX98926_ALC_MUTE_EN_SHIFT 7 +#define MAX98926_ALC_MUTE_EN_WIDTH 1 +#define MAX98926_ALC_MUTE_DLY_MASK (0x07<<4) +#define MAX98926_ALC_MUTE_DLY_SHIFT 4 +#define MAX98926_ALC_MUTE_DLY_WIDTH 3 +#define MAX98926_ALC_RLS_DBT_MASK (0x07<<0) +#define MAX98926_ALC_RLS_DBT_SHIFT 0 +#define MAX98926_ALC_RLS_DBT_WIDTH 3 + +/* MAX98926_R035_BOOST_CONVERTER */ +#define MAX98926_BST_SYNC_MASK (1<<7) +#define MAX98926_BST_SYNC_SHIFT 7 +#define MAX98926_BST_SYNC_WIDTH 1 +#define MAX98926_BST_PHASE_MASK (0x03<<4) +#define MAX98926_BST_PHASE_SHIFT 4 +#define MAX98926_BST_PHASE_WIDTH 2 +#define MAX98926_BST_SKIP_MODE_MASK (0x03<<0) +#define MAX98926_BST_SKIP_MODE_SHIFT 0 +#define MAX98926_BST_SKIP_MODE_WIDTH 2 + +/* MAX98926_R036_BLOCK_ENABLE */ +#define MAX98926_BST_EN_MASK (1<<7) +#define MAX98926_BST_EN_SHIFT 7 +#define MAX98926_BST_EN_WIDTH 1 +#define MAX98926_WATCH_EN_MASK (1<<6) +#define MAX98926_WATCH_EN_SHIFT 6 +#define MAX98926_WATCH_EN_WIDTH 1 +#define MAX98926_CLKMON_EN_MASK (1<<5) +#define MAX98926_CLKMON_EN_SHIFT 5 +#define MAX98926_CLKMON_EN_WIDTH 1 +#define MAX98926_SPK_EN_MASK (1<<4) +#define MAX98926_SPK_EN_SHIFT 4 +#define MAX98926_SPK_EN_WIDTH 1 +#define MAX98926_ADC_VBST_EN_MASK (1<<3) +#define MAX98926_ADC_VBST_EN_SHIFT 3 +#define MAX98926_ADC_VBST_EN_WIDTH 1 +#define MAX98926_ADC_VBAT_EN_MASK (1<<2) +#define MAX98926_ADC_VBAT_EN_SHIFT 2 +#define MAX98926_ADC_VBAT_EN_WIDTH 1 +#define MAX98926_ADC_IMON_EN_MASK (1<<1) +#define MAX98926_ADC_IMON_EN_SHIFT 1 +#define MAX98926_ADC_IMON_EN_WIDTH 1 +#define MAX98926_ADC_VMON_EN_MASK (1<<0) +#define MAX98926_ADC_VMON_EN_SHIFT 0 +#define MAX98926_ADC_VMON_EN_WIDTH 1 + +/* MAX98926_R037_CONFIGURATION */ +#define MAX98926_BST_VOUT_MASK (0x0F<<4) +#define MAX98926_BST_VOUT_SHIFT 4 +#define MAX98926_BST_VOUT_WIDTH 4 +#define MAX98926_THERMWARN_LEVEL_MASK (0x03<<2) +#define MAX98926_THERMWARN_LEVEL_SHIFT 2 +#define MAX98926_THERMWARN_LEVEL_WIDTH 2 +#define MAX98926_WATCH_TIME_MASK (0x03<<0) +#define MAX98926_WATCH_TIME_SHIFT 0 +#define MAX98926_WATCH_TIME_WIDTH 2 + +/* MAX98926_R038_GLOBAL_ENABLE */ +#define MAX98926_EN_MASK (1<<7) +#define MAX98926_EN_SHIFT 7 +#define MAX98926_EN_WIDTH 1 + +/* MAX98926_R03A_BOOST_LIMITER */ +#define MAX98926_BST_ILIM_MASK (0xF<<4) +#define MAX98926_BST_ILIM_SHIFT 4 +#define MAX98926_BST_ILIM_WIDTH 4 + +/* MAX98926_R0FF_VERSION */ +#define MAX98926_REV_ID_MASK (0xFF<<0) +#define MAX98926_REV_ID_SHIFT 0 +#define MAX98926_REV_ID_WIDTH 8 + +struct max98926_priv { + struct regmap *regmap; + struct snd_soc_component *component; + unsigned int sysclk; + unsigned int v_slot; + unsigned int i_slot; + unsigned int ch_size; + unsigned int interleave_mode; +}; +#endif diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c new file mode 100644 index 000000000..065303a46 --- /dev/null +++ b/sound/soc/codecs/max98927.c @@ -0,0 +1,966 @@ +/* + * max98927.c -- MAX98927 ALSA Soc Audio driver + * + * Copyright (C) 2016-2017 Maxim Integrated Products + * Author: Ryan Lee + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98927.h" + +static struct reg_default max98927_reg[] = { + {MAX98927_R0001_INT_RAW1, 0x00}, + {MAX98927_R0002_INT_RAW2, 0x00}, + {MAX98927_R0003_INT_RAW3, 0x00}, + {MAX98927_R0004_INT_STATE1, 0x00}, + {MAX98927_R0005_INT_STATE2, 0x00}, + {MAX98927_R0006_INT_STATE3, 0x00}, + {MAX98927_R0007_INT_FLAG1, 0x00}, + {MAX98927_R0008_INT_FLAG2, 0x00}, + {MAX98927_R0009_INT_FLAG3, 0x00}, + {MAX98927_R000A_INT_EN1, 0x00}, + {MAX98927_R000B_INT_EN2, 0x00}, + {MAX98927_R000C_INT_EN3, 0x00}, + {MAX98927_R000D_INT_FLAG_CLR1, 0x00}, + {MAX98927_R000E_INT_FLAG_CLR2, 0x00}, + {MAX98927_R000F_INT_FLAG_CLR3, 0x00}, + {MAX98927_R0010_IRQ_CTRL, 0x00}, + {MAX98927_R0011_CLK_MON, 0x00}, + {MAX98927_R0012_WDOG_CTRL, 0x00}, + {MAX98927_R0013_WDOG_RST, 0x00}, + {MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH, 0x75}, + {MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH, 0x8c}, + {MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS, 0x08}, + {MAX98927_R0017_PIN_CFG, 0x55}, + {MAX98927_R0018_PCM_RX_EN_A, 0x00}, + {MAX98927_R0019_PCM_RX_EN_B, 0x00}, + {MAX98927_R001A_PCM_TX_EN_A, 0x00}, + {MAX98927_R001B_PCM_TX_EN_B, 0x00}, + {MAX98927_R001C_PCM_TX_HIZ_CTRL_A, 0x00}, + {MAX98927_R001D_PCM_TX_HIZ_CTRL_B, 0x00}, + {MAX98927_R001E_PCM_TX_CH_SRC_A, 0x00}, + {MAX98927_R001F_PCM_TX_CH_SRC_B, 0x00}, + {MAX98927_R0020_PCM_MODE_CFG, 0x40}, + {MAX98927_R0021_PCM_MASTER_MODE, 0x00}, + {MAX98927_R0022_PCM_CLK_SETUP, 0x22}, + {MAX98927_R0023_PCM_SR_SETUP1, 0x00}, + {MAX98927_R0024_PCM_SR_SETUP2, 0x00}, + {MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, 0x00}, + {MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, 0x00}, + {MAX98927_R0027_ICC_RX_EN_A, 0x00}, + {MAX98927_R0028_ICC_RX_EN_B, 0x00}, + {MAX98927_R002B_ICC_TX_EN_A, 0x00}, + {MAX98927_R002C_ICC_TX_EN_B, 0x00}, + {MAX98927_R002E_ICC_HIZ_MANUAL_MODE, 0x00}, + {MAX98927_R002F_ICC_TX_HIZ_EN_A, 0x00}, + {MAX98927_R0030_ICC_TX_HIZ_EN_B, 0x00}, + {MAX98927_R0031_ICC_LNK_EN, 0x00}, + {MAX98927_R0032_PDM_TX_EN, 0x00}, + {MAX98927_R0033_PDM_TX_HIZ_CTRL, 0x00}, + {MAX98927_R0034_PDM_TX_CTRL, 0x00}, + {MAX98927_R0035_PDM_RX_CTRL, 0x00}, + {MAX98927_R0036_AMP_VOL_CTRL, 0x00}, + {MAX98927_R0037_AMP_DSP_CFG, 0x02}, + {MAX98927_R0038_TONE_GEN_DC_CFG, 0x00}, + {MAX98927_R0039_DRE_CTRL, 0x01}, + {MAX98927_R003A_AMP_EN, 0x00}, + {MAX98927_R003B_SPK_SRC_SEL, 0x00}, + {MAX98927_R003C_SPK_GAIN, 0x00}, + {MAX98927_R003D_SSM_CFG, 0x04}, + {MAX98927_R003E_MEAS_EN, 0x00}, + {MAX98927_R003F_MEAS_DSP_CFG, 0x04}, + {MAX98927_R0040_BOOST_CTRL0, 0x00}, + {MAX98927_R0041_BOOST_CTRL3, 0x00}, + {MAX98927_R0042_BOOST_CTRL1, 0x00}, + {MAX98927_R0043_MEAS_ADC_CFG, 0x00}, + {MAX98927_R0044_MEAS_ADC_BASE_MSB, 0x01}, + {MAX98927_R0045_MEAS_ADC_BASE_LSB, 0x00}, + {MAX98927_R0046_ADC_CH0_DIVIDE, 0x00}, + {MAX98927_R0047_ADC_CH1_DIVIDE, 0x00}, + {MAX98927_R0048_ADC_CH2_DIVIDE, 0x00}, + {MAX98927_R0049_ADC_CH0_FILT_CFG, 0x00}, + {MAX98927_R004A_ADC_CH1_FILT_CFG, 0x00}, + {MAX98927_R004B_ADC_CH2_FILT_CFG, 0x00}, + {MAX98927_R004C_MEAS_ADC_CH0_READ, 0x00}, + {MAX98927_R004D_MEAS_ADC_CH1_READ, 0x00}, + {MAX98927_R004E_MEAS_ADC_CH2_READ, 0x00}, + {MAX98927_R0051_BROWNOUT_STATUS, 0x00}, + {MAX98927_R0052_BROWNOUT_EN, 0x00}, + {MAX98927_R0053_BROWNOUT_INFINITE_HOLD, 0x00}, + {MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR, 0x00}, + {MAX98927_R0055_BROWNOUT_LVL_HOLD, 0x00}, + {MAX98927_R005A_BROWNOUT_LVL1_THRESH, 0x00}, + {MAX98927_R005B_BROWNOUT_LVL2_THRESH, 0x00}, + {MAX98927_R005C_BROWNOUT_LVL3_THRESH, 0x00}, + {MAX98927_R005D_BROWNOUT_LVL4_THRESH, 0x00}, + {MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS, 0x00}, + {MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL, 0x00}, + {MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL, 0x00}, + {MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE, 0x00}, + {MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT, 0x00}, + {MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1, 0x00}, + {MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2, 0x00}, + {MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3, 0x00}, + {MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT, 0x00}, + {MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1, 0x00}, + {MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2, 0x00}, + {MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3, 0x00}, + {MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT, 0x00}, + {MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1, 0x00}, + {MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2, 0x00}, + {MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3, 0x00}, + {MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT, 0x00}, + {MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1, 0x00}, + {MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2, 0x00}, + {MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3, 0x00}, + {MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM, 0x00}, + {MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY, 0x00}, + {MAX98927_R0084_ENV_TRACK_REL_RATE, 0x00}, + {MAX98927_R0085_ENV_TRACK_HOLD_RATE, 0x00}, + {MAX98927_R0086_ENV_TRACK_CTRL, 0x00}, + {MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ, 0x00}, + {MAX98927_R00FF_GLOBAL_SHDN, 0x00}, + {MAX98927_R0100_SOFT_RESET, 0x00}, + {MAX98927_R01FF_REV_ID, 0x40}, +}; + +static int max98927_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98927_priv *max98927 = snd_soc_component_get_drvdata(component); + unsigned int mode = 0; + unsigned int format = 0; + bool use_pdm = false; + unsigned int invert = 0; + + dev_dbg(component->dev, "%s: fmt 0x%08X\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + mode = MAX98927_PCM_MASTER_MODE_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + max98927->master = true; + mode = MAX98927_PCM_MASTER_MODE_MASTER; + break; + default: + dev_err(component->dev, "DAI clock mode unsupported\n"); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0021_PCM_MASTER_MODE, + MAX98927_PCM_MASTER_MODE_MASK, + mode); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + invert = MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE; + break; + default: + dev_err(component->dev, "DAI invert mode unsupported\n"); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE, + invert); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = MAX98927_PCM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = MAX98927_PCM_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_DSP_A: + format = MAX98927_PCM_FORMAT_TDM_MODE1; + break; + case SND_SOC_DAIFMT_DSP_B: + format = MAX98927_PCM_FORMAT_TDM_MODE0; + break; + case SND_SOC_DAIFMT_PDM: + use_pdm = true; + break; + default: + return -EINVAL; + } + max98927->iface = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + if (!use_pdm) { + /* pcm channel configuration */ + regmap_update_bits(max98927->regmap, + MAX98927_R0018_PCM_RX_EN_A, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN); + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_FORMAT_MASK, + format << MAX98927_PCM_MODE_CFG_FORMAT_SHIFT); + + regmap_update_bits(max98927->regmap, + MAX98927_R003B_SPK_SRC_SEL, + MAX98927_SPK_SRC_MASK, 0); + + regmap_update_bits(max98927->regmap, + MAX98927_R0035_PDM_RX_CTRL, + MAX98927_PDM_RX_EN_MASK, 0); + } else { + /* pdm channel configuration */ + regmap_update_bits(max98927->regmap, + MAX98927_R0035_PDM_RX_CTRL, + MAX98927_PDM_RX_EN_MASK, 1); + + regmap_update_bits(max98927->regmap, + MAX98927_R003B_SPK_SRC_SEL, + MAX98927_SPK_SRC_MASK, 3); + + regmap_update_bits(max98927->regmap, + MAX98927_R0018_PCM_RX_EN_A, + MAX98927_PCM_RX_CH0_EN | MAX98927_PCM_RX_CH1_EN, 0); + } + return 0; +} + +/* codec MCLK rate in master mode */ +static const int rate_table[] = { + 5644800, 6000000, 6144000, 6500000, + 9600000, 11289600, 12000000, 12288000, + 13000000, 19200000, +}; + +/* BCLKs per LRCLK */ +static const int bclk_sel_table[] = { + 32, 48, 64, 96, 128, 192, 256, 384, 512, +}; + +static int max98927_get_bclk_sel(int bclk) +{ + int i; + /* match BCLKs per LRCLK */ + for (i = 0; i < ARRAY_SIZE(bclk_sel_table); i++) { + if (bclk_sel_table[i] == bclk) + return i + 2; + } + return 0; +} +static int max98927_set_clock(struct max98927_priv *max98927, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_component *component = max98927->component; + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98927->ch_size; + int value; + + if (max98927->master) { + int i; + /* match rate to closest value */ + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i] >= max98927->sysclk) + break; + } + if (i == ARRAY_SIZE(rate_table)) { + dev_err(component->dev, "failed to find proper clock rate.\n"); + return -EINVAL; + } + regmap_update_bits(max98927->regmap, + MAX98927_R0021_PCM_MASTER_MODE, + MAX98927_PCM_MASTER_MODE_MCLK_MASK, + i << MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT); + } + + if (!max98927->tdm_mode) { + /* BCLK configuration */ + value = max98927_get_bclk_sel(blr_clk_ratio); + if (!value) { + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0022_PCM_CLK_SETUP, + MAX98927_PCM_CLK_SETUP_BSEL_MASK, + value); + } + return 0; +} + +static int max98927_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98927_priv *max98927 = snd_soc_component_get_drvdata(component); + unsigned int sampling_rate = 0; + unsigned int chan_sz = 0; + + /* pcm mode configuration */ + switch (snd_pcm_format_width(params_format(params))) { + case 16: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + goto err; + } + + max98927->ch_size = snd_pcm_format_width(params_format(params)); + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + dev_dbg(component->dev, "format supported %d", + params_format(params)); + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 8000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_8000; + break; + case 11025: + sampling_rate = MAX98927_PCM_SR_SET1_SR_11025; + break; + case 12000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_12000; + break; + case 16000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_16000; + break; + case 22050: + sampling_rate = MAX98927_PCM_SR_SET1_SR_22050; + break; + case 24000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_24000; + break; + case 32000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_32000; + break; + case 44100: + sampling_rate = MAX98927_PCM_SR_SET1_SR_44100; + break; + case 48000: + sampling_rate = MAX98927_PCM_SR_SET1_SR_48000; + break; + default: + dev_err(component->dev, "rate %d not supported\n", + params_rate(params)); + goto err; + } + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98927->regmap, + MAX98927_R0023_PCM_SR_SETUP1, + MAX98927_PCM_SR_SET1_SR_MASK, + sampling_rate); + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_SR_MASK, + sampling_rate << MAX98927_PCM_SR_SET2_SR_SHIFT); + + /* set sampling rate of IV */ + if (max98927->interleave_mode && + sampling_rate > MAX98927_PCM_SR_SET1_SR_16000) + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate - 3); + else + regmap_update_bits(max98927->regmap, + MAX98927_R0024_PCM_SR_SETUP2, + MAX98927_PCM_SR_SET2_IVADC_SR_MASK, + sampling_rate); + return max98927_set_clock(max98927, params); +err: + return -EINVAL; +} + +static int max98927_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct max98927_priv *max98927 = snd_soc_component_get_drvdata(component); + int bsel = 0; + unsigned int chan_sz = 0; + + max98927->tdm_mode = true; + + /* BCLK configuration */ + bsel = max98927_get_bclk_sel(slots * slot_width); + if (bsel == 0) { + dev_err(component->dev, "BCLK %d not supported\n", + slots * slot_width); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0022_PCM_CLK_SETUP, + MAX98927_PCM_CLK_SETUP_BSEL_MASK, + bsel); + + /* Channel size configuration */ + switch (slot_width) { + case 16: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98927_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + slot_width); + return -EINVAL; + } + + regmap_update_bits(max98927->regmap, + MAX98927_R0020_PCM_MODE_CFG, + MAX98927_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + /* Rx slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R0018_PCM_RX_EN_A, + rx_mask & 0xFF); + regmap_write(max98927->regmap, + MAX98927_R0019_PCM_RX_EN_B, + (rx_mask & 0xFF00) >> 8); + + /* Tx slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R001A_PCM_TX_EN_A, + tx_mask & 0xFF); + regmap_write(max98927->regmap, + MAX98927_R001B_PCM_TX_EN_B, + (tx_mask & 0xFF00) >> 8); + + /* Tx slot Hi-Z configuration */ + regmap_write(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + ~tx_mask & 0xFF); + regmap_write(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + (~tx_mask & 0xFF00) >> 8); + + return 0; +} + +#define MAX98927_RATES SNDRV_PCM_RATE_8000_48000 + +#define MAX98927_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static int max98927_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct max98927_priv *max98927 = snd_soc_component_get_drvdata(component); + + max98927->sysclk = freq; + return 0; +} + +static const struct snd_soc_dai_ops max98927_dai_ops = { + .set_sysclk = max98927_dai_set_sysclk, + .set_fmt = max98927_dai_set_fmt, + .hw_params = max98927_dai_hw_params, + .set_tdm_slot = max98927_dai_tdm_slot, +}; + +static int max98927_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98927_priv *max98927 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + max98927->tdm_mode = 0; + break; + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(max98927->regmap, + MAX98927_R003A_AMP_EN, + MAX98927_AMP_EN_MASK, 1); + regmap_update_bits(max98927->regmap, + MAX98927_R00FF_GLOBAL_SHDN, + MAX98927_GLOBAL_EN_MASK, 1); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(max98927->regmap, + MAX98927_R00FF_GLOBAL_SHDN, + MAX98927_GLOBAL_EN_MASK, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R003A_AMP_EN, + MAX98927_AMP_EN_MASK, 0); + break; + default: + return 0; + } + return 0; +} + +static const char * const max98927_switch_text[] = { + "Left", "Right", "LeftRight"}; + +static const struct soc_enum dai_sel_enum = + SOC_ENUM_SINGLE(MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, + MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT, + 3, max98927_switch_text); + +static const struct snd_kcontrol_new max98927_dai_controls = + SOC_DAPM_ENUM("DAI Sel", dai_sel_enum); + +static const struct snd_kcontrol_new max98927_vi_control = + SOC_DAPM_SINGLE("Switch", MAX98927_R003F_MEAS_DSP_CFG, 2, 1, 0); + +static const struct snd_soc_dapm_widget max98927_dapm_widgets[] = { + SND_SOC_DAPM_DAC_E("Amp Enable", "HiFi Playback", MAX98927_R003A_AMP_EN, + 0, 0, max98927_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("DAI Sel Mux", SND_SOC_NOPM, 0, 0, + &max98927_dai_controls), + SND_SOC_DAPM_OUTPUT("BE_OUT"), + SND_SOC_DAPM_AIF_OUT("Voltage Sense", "HiFi Capture", 0, + MAX98927_R003E_MEAS_EN, 0, 0), + SND_SOC_DAPM_AIF_OUT("Current Sense", "HiFi Capture", 0, + MAX98927_R003E_MEAS_EN, 1, 0), + SND_SOC_DAPM_SWITCH("VI Sense", SND_SOC_NOPM, 0, 0, + &max98927_vi_control), + SND_SOC_DAPM_SIGGEN("VMON"), + SND_SOC_DAPM_SIGGEN("IMON"), +}; + +static DECLARE_TLV_DB_SCALE(max98927_spk_tlv, 300, 300, 0); +static DECLARE_TLV_DB_SCALE(max98927_digital_tlv, -1600, 25, 0); + +static bool max98927_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98927_R0001_INT_RAW1 ... MAX98927_R0028_ICC_RX_EN_B: + case MAX98927_R002B_ICC_TX_EN_A ... MAX98927_R002C_ICC_TX_EN_B: + case MAX98927_R002E_ICC_HIZ_MANUAL_MODE + ... MAX98927_R004E_MEAS_ADC_CH2_READ: + case MAX98927_R0051_BROWNOUT_STATUS + ... MAX98927_R0055_BROWNOUT_LVL_HOLD: + case MAX98927_R005A_BROWNOUT_LVL1_THRESH + ... MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE: + case MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT + ... MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ: + case MAX98927_R00FF_GLOBAL_SHDN: + case MAX98927_R0100_SOFT_RESET: + case MAX98927_R01FF_REV_ID: + return true; + default: + return false; + } +}; + +static bool max98927_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98927_R0001_INT_RAW1 ... MAX98927_R0009_INT_FLAG3: + case MAX98927_R004C_MEAS_ADC_CH0_READ: + case MAX98927_R004D_MEAS_ADC_CH1_READ: + case MAX98927_R004E_MEAS_ADC_CH2_READ: + case MAX98927_R0051_BROWNOUT_STATUS: + case MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ: + case MAX98927_R01FF_REV_ID: + case MAX98927_R0100_SOFT_RESET: + return true; + default: + return false; + } +} + +static const char * const max98927_boost_voltage_text[] = { + "6.5V", "6.625V", "6.75V", "6.875V", "7V", "7.125V", "7.25V", "7.375V", + "7.5V", "7.625V", "7.75V", "7.875V", "8V", "8.125V", "8.25V", "8.375V", + "8.5V", "8.625V", "8.75V", "8.875V", "9V", "9.125V", "9.25V", "9.375V", + "9.5V", "9.625V", "9.75V", "9.875V", "10V" +}; + +static SOC_ENUM_SINGLE_DECL(max98927_boost_voltage, + MAX98927_R0040_BOOST_CTRL0, 0, + max98927_boost_voltage_text); + +static const char * const max98927_current_limit_text[] = { + "1.00A", "1.10A", "1.20A", "1.30A", "1.40A", "1.50A", "1.60A", "1.70A", + "1.80A", "1.90A", "2.00A", "2.10A", "2.20A", "2.30A", "2.40A", "2.50A", + "2.60A", "2.70A", "2.80A", "2.90A", "3.00A", "3.10A", "3.20A", "3.30A", + "3.40A", "3.50A", "3.60A", "3.70A", "3.80A", "3.90A", "4.00A", "4.10A" +}; + +static SOC_ENUM_SINGLE_DECL(max98927_current_limit, + MAX98927_R0042_BOOST_CTRL1, 1, + max98927_current_limit_text); + +static const struct snd_kcontrol_new max98927_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", MAX98927_R003C_SPK_GAIN, + 0, 6, 0, + max98927_spk_tlv), + SOC_SINGLE_TLV("Digital Volume", MAX98927_R0036_AMP_VOL_CTRL, + 0, (1<component = component; + + /* Software Reset */ + regmap_write(max98927->regmap, + MAX98927_R0100_SOFT_RESET, MAX98927_SOFT_RESET); + + /* IV default slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 0xFF); + regmap_write(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 0xFF); + regmap_write(max98927->regmap, + MAX98927_R0025_PCM_TO_SPK_MONOMIX_A, + 0x80); + regmap_write(max98927->regmap, + MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, + 0x1); + /* Set inital volume (+13dB) */ + regmap_write(max98927->regmap, + MAX98927_R0036_AMP_VOL_CTRL, + 0x38); + regmap_write(max98927->regmap, + MAX98927_R003C_SPK_GAIN, + 0x05); + /* Enable DC blocker */ + regmap_write(max98927->regmap, + MAX98927_R0037_AMP_DSP_CFG, + 0x03); + /* Enable IMON VMON DC blocker */ + regmap_write(max98927->regmap, + MAX98927_R003F_MEAS_DSP_CFG, + 0xF7); + /* Boost Output Voltage & Current limit */ + regmap_write(max98927->regmap, + MAX98927_R0040_BOOST_CTRL0, + 0x1C); + regmap_write(max98927->regmap, + MAX98927_R0042_BOOST_CTRL1, + 0x3E); + /* Measurement ADC config */ + regmap_write(max98927->regmap, + MAX98927_R0043_MEAS_ADC_CFG, + 0x04); + regmap_write(max98927->regmap, + MAX98927_R0044_MEAS_ADC_BASE_MSB, + 0x00); + regmap_write(max98927->regmap, + MAX98927_R0045_MEAS_ADC_BASE_LSB, + 0x24); + /* Brownout Level */ + regmap_write(max98927->regmap, + MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1, + 0x06); + /* Envelope Tracking configuration */ + regmap_write(max98927->regmap, + MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM, + 0x08); + regmap_write(max98927->regmap, + MAX98927_R0086_ENV_TRACK_CTRL, + 0x01); + regmap_write(max98927->regmap, + MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ, + 0x10); + + /* voltage, current slot configuration */ + regmap_write(max98927->regmap, + MAX98927_R001E_PCM_TX_CH_SRC_A, + (max98927->i_l_slot<v_l_slot)&0xFF); + + if (max98927->v_l_slot < 8) { + regmap_update_bits(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 1 << max98927->v_l_slot, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001A_PCM_TX_EN_A, + 1 << max98927->v_l_slot, + 1 << max98927->v_l_slot); + } else { + regmap_update_bits(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 1 << (max98927->v_l_slot - 8), 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001B_PCM_TX_EN_B, + 1 << (max98927->v_l_slot - 8), + 1 << (max98927->v_l_slot - 8)); + } + + if (max98927->i_l_slot < 8) { + regmap_update_bits(max98927->regmap, + MAX98927_R001C_PCM_TX_HIZ_CTRL_A, + 1 << max98927->i_l_slot, 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001A_PCM_TX_EN_A, + 1 << max98927->i_l_slot, + 1 << max98927->i_l_slot); + } else { + regmap_update_bits(max98927->regmap, + MAX98927_R001D_PCM_TX_HIZ_CTRL_B, + 1 << (max98927->i_l_slot - 8), 0); + regmap_update_bits(max98927->regmap, + MAX98927_R001B_PCM_TX_EN_B, + 1 << (max98927->i_l_slot - 8), + 1 << (max98927->i_l_slot - 8)); + } + + /* Set interleave mode */ + if (max98927->interleave_mode) + regmap_update_bits(max98927->regmap, + MAX98927_R001F_PCM_TX_CH_SRC_B, + MAX98927_PCM_TX_CH_INTERLEAVE_MASK, + MAX98927_PCM_TX_CH_INTERLEAVE_MASK); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max98927_suspend(struct device *dev) +{ + struct max98927_priv *max98927 = dev_get_drvdata(dev); + + regcache_cache_only(max98927->regmap, true); + regcache_mark_dirty(max98927->regmap); + return 0; +} +static int max98927_resume(struct device *dev) +{ + struct max98927_priv *max98927 = dev_get_drvdata(dev); + + regmap_write(max98927->regmap, + MAX98927_R0100_SOFT_RESET, MAX98927_SOFT_RESET); + regcache_cache_only(max98927->regmap, false); + regcache_sync(max98927->regmap); + return 0; +} +#endif + +static const struct dev_pm_ops max98927_pm = { + SET_SYSTEM_SLEEP_PM_OPS(max98927_suspend, max98927_resume) +}; + +static const struct snd_soc_component_driver soc_component_dev_max98927 = { + .probe = max98927_probe, + .controls = max98927_snd_controls, + .num_controls = ARRAY_SIZE(max98927_snd_controls), + .dapm_widgets = max98927_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98927_dapm_widgets), + .dapm_routes = max98927_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98927_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config max98927_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98927_R01FF_REV_ID, + .reg_defaults = max98927_reg, + .num_reg_defaults = ARRAY_SIZE(max98927_reg), + .readable_reg = max98927_readable_register, + .volatile_reg = max98927_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static void max98927_slot_config(struct i2c_client *i2c, + struct max98927_priv *max98927) +{ + int value; + struct device *dev = &i2c->dev; + + if (!device_property_read_u32(dev, "vmon-slot-no", &value)) + max98927->v_l_slot = value & 0xF; + else + max98927->v_l_slot = 0; + + if (!device_property_read_u32(dev, "imon-slot-no", &value)) + max98927->i_l_slot = value & 0xF; + else + max98927->i_l_slot = 1; +} + +static int max98927_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + + int ret = 0, value; + int reg = 0; + struct max98927_priv *max98927 = NULL; + + max98927 = devm_kzalloc(&i2c->dev, + sizeof(*max98927), GFP_KERNEL); + + if (!max98927) { + ret = -ENOMEM; + return ret; + } + i2c_set_clientdata(i2c, max98927); + + /* update interleave mode info */ + if (!of_property_read_u32(i2c->dev.of_node, + "interleave_mode", &value)) { + if (value > 0) + max98927->interleave_mode = 1; + else + max98927->interleave_mode = 0; + } else + max98927->interleave_mode = 0; + + /* regmap initialization */ + max98927->regmap + = devm_regmap_init_i2c(i2c, &max98927_regmap); + if (IS_ERR(max98927->regmap)) { + ret = PTR_ERR(max98927->regmap); + dev_err(&i2c->dev, + "Failed to allocate regmap: %d\n", ret); + return ret; + } + + /* Check Revision ID */ + ret = regmap_read(max98927->regmap, + MAX98927_R01FF_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c->dev, + "Failed to read: 0x%02X\n", MAX98927_R01FF_REV_ID); + return ret; + } + dev_info(&i2c->dev, "MAX98927 revisionID: 0x%02X\n", reg); + + /* voltage/current slot configuration */ + max98927_slot_config(i2c, max98927); + + /* codec registeration */ + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_max98927, + max98927_dai, ARRAY_SIZE(max98927_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register component: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id max98927_i2c_id[] = { + { "max98927", 0}, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max98927_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id max98927_of_match[] = { + { .compatible = "maxim,max98927", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98927_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max98927_acpi_match[] = { + { "MX98927", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, max98927_acpi_match); +#endif + +static struct i2c_driver max98927_i2c_driver = { + .driver = { + .name = "max98927", + .of_match_table = of_match_ptr(max98927_of_match), + .acpi_match_table = ACPI_PTR(max98927_acpi_match), + .pm = &max98927_pm, + }, + .probe = max98927_i2c_probe, + .id_table = max98927_i2c_id, +}; + +module_i2c_driver(max98927_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98927 driver"); +MODULE_AUTHOR("Ryan Lee "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98927.h b/sound/soc/codecs/max98927.h new file mode 100644 index 000000000..42a9a244a --- /dev/null +++ b/sound/soc/codecs/max98927.h @@ -0,0 +1,275 @@ +/* + * max98927.h -- MAX98927 ALSA Soc Audio driver + * + * Copyright (C) 2016-2017 Maxim Integrated Products + * Author: Ryan Lee + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#ifndef _MAX98927_H +#define _MAX98927_H + +/* Register Values */ +#define MAX98927_R0001_INT_RAW1 0x0001 +#define MAX98927_R0002_INT_RAW2 0x0002 +#define MAX98927_R0003_INT_RAW3 0x0003 +#define MAX98927_R0004_INT_STATE1 0x0004 +#define MAX98927_R0005_INT_STATE2 0x0005 +#define MAX98927_R0006_INT_STATE3 0x0006 +#define MAX98927_R0007_INT_FLAG1 0x0007 +#define MAX98927_R0008_INT_FLAG2 0x0008 +#define MAX98927_R0009_INT_FLAG3 0x0009 +#define MAX98927_R000A_INT_EN1 0x000A +#define MAX98927_R000B_INT_EN2 0x000B +#define MAX98927_R000C_INT_EN3 0x000C +#define MAX98927_R000D_INT_FLAG_CLR1 0x000D +#define MAX98927_R000E_INT_FLAG_CLR2 0x000E +#define MAX98927_R000F_INT_FLAG_CLR3 0x000F +#define MAX98927_R0010_IRQ_CTRL 0x0010 +#define MAX98927_R0011_CLK_MON 0x0011 +#define MAX98927_R0012_WDOG_CTRL 0x0012 +#define MAX98927_R0013_WDOG_RST 0x0013 +#define MAX98927_R0014_MEAS_ADC_THERM_WARN_THRESH 0x0014 +#define MAX98927_R0015_MEAS_ADC_THERM_SHDN_THRESH 0x0015 +#define MAX98927_R0016_MEAS_ADC_THERM_HYSTERESIS 0x0016 +#define MAX98927_R0017_PIN_CFG 0x0017 +#define MAX98927_R0018_PCM_RX_EN_A 0x0018 +#define MAX98927_R0019_PCM_RX_EN_B 0x0019 +#define MAX98927_R001A_PCM_TX_EN_A 0x001A +#define MAX98927_R001B_PCM_TX_EN_B 0x001B +#define MAX98927_R001C_PCM_TX_HIZ_CTRL_A 0x001C +#define MAX98927_R001D_PCM_TX_HIZ_CTRL_B 0x001D +#define MAX98927_R001E_PCM_TX_CH_SRC_A 0x001E +#define MAX98927_R001F_PCM_TX_CH_SRC_B 0x001F +#define MAX98927_R0020_PCM_MODE_CFG 0x0020 +#define MAX98927_R0021_PCM_MASTER_MODE 0x0021 +#define MAX98927_R0022_PCM_CLK_SETUP 0x0022 +#define MAX98927_R0023_PCM_SR_SETUP1 0x0023 +#define MAX98927_R0024_PCM_SR_SETUP2 0x0024 +#define MAX98927_R0025_PCM_TO_SPK_MONOMIX_A 0x0025 +#define MAX98927_R0026_PCM_TO_SPK_MONOMIX_B 0x0026 +#define MAX98927_R0027_ICC_RX_EN_A 0x0027 +#define MAX98927_R0028_ICC_RX_EN_B 0x0028 +#define MAX98927_R002B_ICC_TX_EN_A 0x002B +#define MAX98927_R002C_ICC_TX_EN_B 0x002C +#define MAX98927_R002E_ICC_HIZ_MANUAL_MODE 0x002E +#define MAX98927_R002F_ICC_TX_HIZ_EN_A 0x002F +#define MAX98927_R0030_ICC_TX_HIZ_EN_B 0x0030 +#define MAX98927_R0031_ICC_LNK_EN 0x0031 +#define MAX98927_R0032_PDM_TX_EN 0x0032 +#define MAX98927_R0033_PDM_TX_HIZ_CTRL 0x0033 +#define MAX98927_R0034_PDM_TX_CTRL 0x0034 +#define MAX98927_R0035_PDM_RX_CTRL 0x0035 +#define MAX98927_R0036_AMP_VOL_CTRL 0x0036 +#define MAX98927_R0037_AMP_DSP_CFG 0x0037 +#define MAX98927_R0038_TONE_GEN_DC_CFG 0x0038 +#define MAX98927_R0039_DRE_CTRL 0x0039 +#define MAX98927_R003A_AMP_EN 0x003A +#define MAX98927_R003B_SPK_SRC_SEL 0x003B +#define MAX98927_R003C_SPK_GAIN 0x003C +#define MAX98927_R003D_SSM_CFG 0x003D +#define MAX98927_R003E_MEAS_EN 0x003E +#define MAX98927_R003F_MEAS_DSP_CFG 0x003F +#define MAX98927_R0040_BOOST_CTRL0 0x0040 +#define MAX98927_R0041_BOOST_CTRL3 0x0041 +#define MAX98927_R0042_BOOST_CTRL1 0x0042 +#define MAX98927_R0043_MEAS_ADC_CFG 0x0043 +#define MAX98927_R0044_MEAS_ADC_BASE_MSB 0x0044 +#define MAX98927_R0045_MEAS_ADC_BASE_LSB 0x0045 +#define MAX98927_R0046_ADC_CH0_DIVIDE 0x0046 +#define MAX98927_R0047_ADC_CH1_DIVIDE 0x0047 +#define MAX98927_R0048_ADC_CH2_DIVIDE 0x0048 +#define MAX98927_R0049_ADC_CH0_FILT_CFG 0x0049 +#define MAX98927_R004A_ADC_CH1_FILT_CFG 0x004A +#define MAX98927_R004B_ADC_CH2_FILT_CFG 0x004B +#define MAX98927_R004C_MEAS_ADC_CH0_READ 0x004C +#define MAX98927_R004D_MEAS_ADC_CH1_READ 0x004D +#define MAX98927_R004E_MEAS_ADC_CH2_READ 0x004E +#define MAX98927_R0051_BROWNOUT_STATUS 0x0051 +#define MAX98927_R0052_BROWNOUT_EN 0x0052 +#define MAX98927_R0053_BROWNOUT_INFINITE_HOLD 0x0053 +#define MAX98927_R0054_BROWNOUT_INFINITE_HOLD_CLR 0x0054 +#define MAX98927_R0055_BROWNOUT_LVL_HOLD 0x0055 +#define MAX98927_R005A_BROWNOUT_LVL1_THRESH 0x005A +#define MAX98927_R005B_BROWNOUT_LVL2_THRESH 0x005B +#define MAX98927_R005C_BROWNOUT_LVL3_THRESH 0x005C +#define MAX98927_R005D_BROWNOUT_LVL4_THRESH 0x005D +#define MAX98927_R005E_BROWNOUT_THRESH_HYSTERYSIS 0x005E +#define MAX98927_R005F_BROWNOUT_AMP_LIMITER_ATK_REL 0x005F +#define MAX98927_R0060_BROWNOUT_AMP_GAIN_ATK_REL 0x0060 +#define MAX98927_R0061_BROWNOUT_AMP1_CLIP_MODE 0x0061 +#define MAX98927_R0072_BROWNOUT_LVL1_CUR_LIMIT 0x0072 +#define MAX98927_R0073_BROWNOUT_LVL1_AMP1_CTRL1 0x0073 +#define MAX98927_R0074_BROWNOUT_LVL1_AMP1_CTRL2 0x0074 +#define MAX98927_R0075_BROWNOUT_LVL1_AMP1_CTRL3 0x0075 +#define MAX98927_R0076_BROWNOUT_LVL2_CUR_LIMIT 0x0076 +#define MAX98927_R0077_BROWNOUT_LVL2_AMP1_CTRL1 0x0077 +#define MAX98927_R0078_BROWNOUT_LVL2_AMP1_CTRL2 0x0078 +#define MAX98927_R0079_BROWNOUT_LVL2_AMP1_CTRL3 0x0079 +#define MAX98927_R007A_BROWNOUT_LVL3_CUR_LIMIT 0x007A +#define MAX98927_R007B_BROWNOUT_LVL3_AMP1_CTRL1 0x007B +#define MAX98927_R007C_BROWNOUT_LVL3_AMP1_CTRL2 0x007C +#define MAX98927_R007D_BROWNOUT_LVL3_AMP1_CTRL3 0x007D +#define MAX98927_R007E_BROWNOUT_LVL4_CUR_LIMIT 0x007E +#define MAX98927_R007F_BROWNOUT_LVL4_AMP1_CTRL1 0x007F +#define MAX98927_R0080_BROWNOUT_LVL4_AMP1_CTRL2 0x0080 +#define MAX98927_R0081_BROWNOUT_LVL4_AMP1_CTRL3 0x0081 +#define MAX98927_R0082_ENV_TRACK_VOUT_HEADROOM 0x0082 +#define MAX98927_R0083_ENV_TRACK_BOOST_VOUT_DELAY 0x0083 +#define MAX98927_R0084_ENV_TRACK_REL_RATE 0x0084 +#define MAX98927_R0085_ENV_TRACK_HOLD_RATE 0x0085 +#define MAX98927_R0086_ENV_TRACK_CTRL 0x0086 +#define MAX98927_R0087_ENV_TRACK_BOOST_VOUT_READ 0x0087 +#define MAX98927_R00FF_GLOBAL_SHDN 0x00FF +#define MAX98927_R0100_SOFT_RESET 0x0100 +#define MAX98927_R01FF_REV_ID 0x01FF + +/* MAX98927_R0018_PCM_RX_EN_A */ +#define MAX98927_PCM_RX_CH0_EN (0x1 << 0) +#define MAX98927_PCM_RX_CH1_EN (0x1 << 1) +#define MAX98927_PCM_RX_CH2_EN (0x1 << 2) +#define MAX98927_PCM_RX_CH3_EN (0x1 << 3) +#define MAX98927_PCM_RX_CH4_EN (0x1 << 4) +#define MAX98927_PCM_RX_CH5_EN (0x1 << 5) +#define MAX98927_PCM_RX_CH6_EN (0x1 << 6) +#define MAX98927_PCM_RX_CH7_EN (0x1 << 7) + +/* MAX98927_R001A_PCM_TX_EN_A */ +#define MAX98927_PCM_TX_CH0_EN (0x1 << 0) +#define MAX98927_PCM_TX_CH1_EN (0x1 << 1) +#define MAX98927_PCM_TX_CH2_EN (0x1 << 2) +#define MAX98927_PCM_TX_CH3_EN (0x1 << 3) +#define MAX98927_PCM_TX_CH4_EN (0x1 << 4) +#define MAX98927_PCM_TX_CH5_EN (0x1 << 5) +#define MAX98927_PCM_TX_CH6_EN (0x1 << 6) +#define MAX98927_PCM_TX_CH7_EN (0x1 << 7) + +/* MAX98927_R001E_PCM_TX_CH_SRC_A */ +#define MAX98927_PCM_TX_CH_SRC_A_V_SHIFT (0) +#define MAX98927_PCM_TX_CH_SRC_A_I_SHIFT (4) + +/* MAX98927_R001F_PCM_TX_CH_SRC_B */ +#define MAX98927_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 5) + +/* MAX98927_R0020_PCM_MODE_CFG */ +#define MAX98927_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 2) +#define MAX98927_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3) +#define MAX98927_PCM_MODE_CFG_FORMAT_SHIFT (3) +#define MAX98927_PCM_FORMAT_I2S (0x0 << 0) +#define MAX98927_PCM_FORMAT_LJ (0x1 << 0) +#define MAX98927_PCM_FORMAT_TDM_MODE0 (0x3 << 0) +#define MAX98927_PCM_FORMAT_TDM_MODE1 (0x4 << 0) +#define MAX98927_PCM_FORMAT_TDM_MODE2 (0x5 << 0) +#define MAX98927_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6) +#define MAX98927_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6) + +/* MAX98927_R0021_PCM_MASTER_MODE */ +#define MAX98927_PCM_MASTER_MODE_MASK (0x3 << 0) +#define MAX98927_PCM_MASTER_MODE_SLAVE (0x0 << 0) +#define MAX98927_PCM_MASTER_MODE_MASTER (0x3 << 0) + +#define MAX98927_PCM_MASTER_MODE_MCLK_MASK (0xF << 2) +#define MAX98927_PCM_MASTER_MODE_MCLK_RATE_SHIFT (2) + +/* MAX98927_R0022_PCM_CLK_SETUP */ +#define MAX98927_PCM_CLK_SETUP_BSEL_MASK (0xF << 0) + +/* MAX98927_R0023_PCM_SR_SETUP1 */ +#define MAX98927_PCM_SR_SET1_SR_MASK (0xF << 0) + +#define MAX98927_PCM_SR_SET1_SR_8000 (0x0 << 0) +#define MAX98927_PCM_SR_SET1_SR_11025 (0x1 << 0) +#define MAX98927_PCM_SR_SET1_SR_12000 (0x2 << 0) +#define MAX98927_PCM_SR_SET1_SR_16000 (0x3 << 0) +#define MAX98927_PCM_SR_SET1_SR_22050 (0x4 << 0) +#define MAX98927_PCM_SR_SET1_SR_24000 (0x5 << 0) +#define MAX98927_PCM_SR_SET1_SR_32000 (0x6 << 0) +#define MAX98927_PCM_SR_SET1_SR_44100 (0x7 << 0) +#define MAX98927_PCM_SR_SET1_SR_48000 (0x8 << 0) + +/* MAX98927_R0024_PCM_SR_SETUP2 */ +#define MAX98927_PCM_SR_SET2_SR_MASK (0xF << 4) +#define MAX98927_PCM_SR_SET2_SR_SHIFT (4) +#define MAX98927_PCM_SR_SET2_IVADC_SR_MASK (0xf << 0) + +/* MAX98927_R0025_PCM_TO_SPK_MONOMIX_A */ +#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_MASK (0x3 << 6) +#define MAX98927_PCM_TO_SPK_MONOMIX_CFG_SHIFT (6) + +/* MAX98927_R0035_PDM_RX_CTRL */ +#define MAX98927_PDM_RX_EN_MASK (0x1 << 0) + +/* MAX98927_R0036_AMP_VOL_CTRL */ +#define MAX98927_AMP_VOL_SEL (0x1 << 7) +#define MAX98927_AMP_VOL_SEL_WIDTH (1) +#define MAX98927_AMP_VOL_SEL_SHIFT (7) +#define MAX98927_AMP_VOL_MASK (0x7f << 0) +#define MAX98927_AMP_VOL_WIDTH (7) +#define MAX98927_AMP_VOL_SHIFT (0) + +/* MAX98927_R0037_AMP_DSP_CFG */ +#define MAX98927_AMP_DSP_CFG_DCBLK_EN (0x1 << 0) +#define MAX98927_AMP_DSP_CFG_DITH_EN (0x1 << 1) +#define MAX98927_AMP_DSP_CFG_RMP_BYPASS (0x1 << 4) +#define MAX98927_AMP_DSP_CFG_DAC_INV (0x1 << 5) +#define MAX98927_AMP_DSP_CFG_RMP_SHIFT (4) + +/* MAX98927_R0039_DRE_CTRL */ +#define MAX98927_DRE_CTRL_DRE_EN (0x1 << 0) +#define MAX98927_DRE_EN_SHIFT 0x1 + +/* MAX98927_R003A_AMP_EN */ +#define MAX98927_AMP_EN_MASK (0x1 << 0) + +/* MAX98927_R003B_SPK_SRC_SEL */ +#define MAX98927_SPK_SRC_MASK (0x3 << 0) + +/* MAX98927_R003C_SPK_GAIN */ +#define MAX98927_SPK_PCM_GAIN_MASK (0x7 << 0) +#define MAX98927_SPK_PDM_GAIN_MASK (0x7 << 4) +#define MAX98927_SPK_GAIN_WIDTH (3) + +/* MAX98927_R003E_MEAS_EN */ +#define MAX98927_MEAS_V_EN (0x1 << 0) +#define MAX98927_MEAS_I_EN (0x1 << 1) + +/* MAX98927_R0040_BOOST_CTRL0 */ +#define MAX98927_BOOST_CTRL0_VOUT_MASK (0x1f << 0) +#define MAX98927_BOOST_CTRL0_PVDD_MASK (0x1 << 7) +#define MAX98927_BOOST_CTRL0_PVDD_EN_SHIFT (7) + +/* MAX98927_R0052_BROWNOUT_EN */ +#define MAX98927_BROWNOUT_BDE_EN (0x1 << 0) +#define MAX98927_BROWNOUT_AMP_EN (0x1 << 1) +#define MAX98927_BROWNOUT_DSP_EN (0x1 << 2) +#define MAX98927_BROWNOUT_DSP_SHIFT (2) + +/* MAX98927_R0100_SOFT_RESET */ +#define MAX98927_SOFT_RESET (0x1 << 0) + +/* MAX98927_R00FF_GLOBAL_SHDN */ +#define MAX98927_GLOBAL_EN_MASK (0x1 << 0) + +struct max98927_priv { + struct regmap *regmap; + struct snd_soc_component *component; + struct max98927_pdata *pdata; + unsigned int spk_gain; + unsigned int sysclk; + unsigned int v_l_slot; + unsigned int i_l_slot; + bool interleave_mode; + unsigned int ch_size; + unsigned int rate; + unsigned int iface; + unsigned int master; + unsigned int digital_gain; + bool tdm_mode; +}; +#endif diff --git a/sound/soc/codecs/mc13783.c b/sound/soc/codecs/mc13783.c new file mode 100644 index 000000000..7b0d2610c --- /dev/null +++ b/sound/soc/codecs/mc13783.c @@ -0,0 +1,810 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * Copyright 2009 Sascha Hauer, s.hauer@pengutronix.de + * Copyright 2012 Philippe Retornaz, philippe.retornaz@epfl.ch + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mc13783.h" + +#define AUDIO_RX0_ALSPEN (1 << 5) +#define AUDIO_RX0_ALSPSEL (1 << 7) +#define AUDIO_RX0_ADDCDC (1 << 21) +#define AUDIO_RX0_ADDSTDC (1 << 22) +#define AUDIO_RX0_ADDRXIN (1 << 23) + +#define AUDIO_RX1_PGARXEN (1 << 0); +#define AUDIO_RX1_PGASTEN (1 << 5) +#define AUDIO_RX1_ARXINEN (1 << 10) + +#define AUDIO_TX_AMC1REN (1 << 5) +#define AUDIO_TX_AMC1LEN (1 << 7) +#define AUDIO_TX_AMC2EN (1 << 9) +#define AUDIO_TX_ATXINEN (1 << 11) +#define AUDIO_TX_RXINREC (1 << 13) + +#define SSI_NETWORK_CDCTXRXSLOT(x) (((x) & 0x3) << 2) +#define SSI_NETWORK_CDCTXSECSLOT(x) (((x) & 0x3) << 4) +#define SSI_NETWORK_CDCRXSECSLOT(x) (((x) & 0x3) << 6) +#define SSI_NETWORK_CDCRXSECGAIN(x) (((x) & 0x3) << 8) +#define SSI_NETWORK_CDCSUMGAIN(x) (1 << 10) +#define SSI_NETWORK_CDCFSDLY(x) (1 << 11) +#define SSI_NETWORK_DAC_SLOTS_8 (1 << 12) +#define SSI_NETWORK_DAC_SLOTS_4 (2 << 12) +#define SSI_NETWORK_DAC_SLOTS_2 (3 << 12) +#define SSI_NETWORK_DAC_SLOT_MASK (3 << 12) +#define SSI_NETWORK_DAC_RXSLOT_0_1 (0 << 14) +#define SSI_NETWORK_DAC_RXSLOT_2_3 (1 << 14) +#define SSI_NETWORK_DAC_RXSLOT_4_5 (2 << 14) +#define SSI_NETWORK_DAC_RXSLOT_6_7 (3 << 14) +#define SSI_NETWORK_DAC_RXSLOT_MASK (3 << 14) +#define SSI_NETWORK_STDCRXSECSLOT(x) (((x) & 0x3) << 16) +#define SSI_NETWORK_STDCRXSECGAIN(x) (((x) & 0x3) << 18) +#define SSI_NETWORK_STDCSUMGAIN (1 << 20) + +/* + * MC13783_AUDIO_CODEC and MC13783_AUDIO_DAC mostly share the same + * register layout + */ +#define AUDIO_SSI_SEL (1 << 0) +#define AUDIO_CLK_SEL (1 << 1) +#define AUDIO_CSM (1 << 2) +#define AUDIO_BCL_INV (1 << 3) +#define AUDIO_CFS_INV (1 << 4) +#define AUDIO_CFS(x) (((x) & 0x3) << 5) +#define AUDIO_CLK(x) (((x) & 0x7) << 7) +#define AUDIO_C_EN (1 << 11) +#define AUDIO_C_CLK_EN (1 << 12) +#define AUDIO_C_RESET (1 << 15) + +#define AUDIO_CODEC_CDCFS8K16K (1 << 10) +#define AUDIO_DAC_CFS_DLY_B (1 << 10) + +struct mc13783_priv { + struct mc13xxx *mc13xxx; + struct regmap *regmap; + + enum mc13783_ssi_port adc_ssi_port; + enum mc13783_ssi_port dac_ssi_port; +}; + +/* Mapping between sample rates and register value */ +static unsigned int mc13783_rates[] = { + 8000, 11025, 12000, 16000, + 22050, 24000, 32000, 44100, + 48000, 64000, 96000 +}; + +static int mc13783_pcm_hw_params_dac(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + unsigned int rate = params_rate(params); + int i; + + for (i = 0; i < ARRAY_SIZE(mc13783_rates); i++) { + if (rate == mc13783_rates[i]) { + snd_soc_component_update_bits(component, MC13783_AUDIO_DAC, + 0xf << 17, i << 17); + return 0; + } + } + + return -EINVAL; +} + +static int mc13783_pcm_hw_params_codec(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + unsigned int rate = params_rate(params); + unsigned int val; + + switch (rate) { + case 8000: + val = 0; + break; + case 16000: + val = AUDIO_CODEC_CDCFS8K16K; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, MC13783_AUDIO_CODEC, AUDIO_CODEC_CDCFS8K16K, + val); + + return 0; +} + +static int mc13783_pcm_hw_params_sync(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return mc13783_pcm_hw_params_dac(substream, params, dai); + else + return mc13783_pcm_hw_params_codec(substream, params, dai); +} + +static int mc13783_set_fmt(struct snd_soc_dai *dai, unsigned int fmt, + unsigned int reg) +{ + struct snd_soc_component *component = dai->component; + unsigned int val = 0; + unsigned int mask = AUDIO_CFS(3) | AUDIO_BCL_INV | AUDIO_CFS_INV | + AUDIO_CSM | AUDIO_C_CLK_EN | AUDIO_C_RESET; + + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= AUDIO_CFS(2); + break; + case SND_SOC_DAIFMT_DSP_A: + val |= AUDIO_CFS(1); + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val |= AUDIO_BCL_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + val |= AUDIO_BCL_INV | AUDIO_CFS_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + val |= AUDIO_CFS_INV; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val |= AUDIO_C_CLK_EN; + break; + case SND_SOC_DAIFMT_CBS_CFS: + val |= AUDIO_CSM; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + return -EINVAL; + } + + val |= AUDIO_C_RESET; + + snd_soc_component_update_bits(component, reg, mask, val); + + return 0; +} + +static int mc13783_set_fmt_async(struct snd_soc_dai *dai, unsigned int fmt) +{ + if (dai->id == MC13783_ID_STEREO_DAC) + return mc13783_set_fmt(dai, fmt, MC13783_AUDIO_DAC); + else + return mc13783_set_fmt(dai, fmt, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_fmt_sync(struct snd_soc_dai *dai, unsigned int fmt) +{ + int ret; + + ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_DAC); + if (ret) + return ret; + + /* + * In synchronous mode force the voice codec into slave mode + * so that the clock / framesync from the stereo DAC is used + */ + fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + fmt |= SND_SOC_DAIFMT_CBS_CFS; + ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_CODEC); + + return ret; +} + +static int mc13783_sysclk[] = { + 13000000, + 15360000, + 16800000, + -1, + 26000000, + -1, /* 12000000, invalid for voice codec */ + -1, /* 3686400, invalid for voice codec */ + 33600000, +}; + +static int mc13783_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir, + unsigned int reg) +{ + struct snd_soc_component *component = dai->component; + int clk; + unsigned int val = 0; + unsigned int mask = AUDIO_CLK(0x7) | AUDIO_CLK_SEL; + + for (clk = 0; clk < ARRAY_SIZE(mc13783_sysclk); clk++) { + if (mc13783_sysclk[clk] < 0) + continue; + if (mc13783_sysclk[clk] == freq) + break; + } + + if (clk == ARRAY_SIZE(mc13783_sysclk)) + return -EINVAL; + + if (clk_id == MC13783_CLK_CLIB) + val |= AUDIO_CLK_SEL; + + val |= AUDIO_CLK(clk); + + snd_soc_component_update_bits(component, reg, mask, val); + + return 0; +} + +static int mc13783_set_sysclk_dac(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_DAC); +} + +static int mc13783_set_sysclk_codec(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_sysclk_sync(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + int ret; + + ret = mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_DAC); + if (ret) + return ret; + + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_tdm_slot_dac(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val = 0; + unsigned int mask = SSI_NETWORK_DAC_SLOT_MASK | + SSI_NETWORK_DAC_RXSLOT_MASK; + + switch (slots) { + case 2: + val |= SSI_NETWORK_DAC_SLOTS_2; + break; + case 4: + val |= SSI_NETWORK_DAC_SLOTS_4; + break; + case 8: + val |= SSI_NETWORK_DAC_SLOTS_8; + break; + default: + return -EINVAL; + } + + switch (rx_mask) { + case 0x03: + val |= SSI_NETWORK_DAC_RXSLOT_0_1; + break; + case 0x0c: + val |= SSI_NETWORK_DAC_RXSLOT_2_3; + break; + case 0x30: + val |= SSI_NETWORK_DAC_RXSLOT_4_5; + break; + case 0xc0: + val |= SSI_NETWORK_DAC_RXSLOT_6_7; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, MC13783_SSI_NETWORK, mask, val); + + return 0; +} + +static int mc13783_set_tdm_slot_codec(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val = 0; + unsigned int mask = 0x3f; + + if (slots != 4) + return -EINVAL; + + if (tx_mask != 0x3) + return -EINVAL; + + val |= (0x00 << 2); /* primary timeslot RX/TX(?) is 0 */ + val |= (0x01 << 4); /* secondary timeslot TX is 1 */ + + snd_soc_component_update_bits(component, MC13783_SSI_NETWORK, mask, val); + + return 0; +} + +static int mc13783_set_tdm_slot_sync(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + int ret; + + ret = mc13783_set_tdm_slot_dac(dai, tx_mask, rx_mask, slots, + slot_width); + if (ret) + return ret; + + ret = mc13783_set_tdm_slot_codec(dai, tx_mask, rx_mask, slots, + slot_width); + + return ret; +} + +static const struct snd_kcontrol_new mc1l_amp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_TX, 7, 1, 0); + +static const struct snd_kcontrol_new mc1r_amp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_TX, 5, 1, 0); + +static const struct snd_kcontrol_new mc2_amp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_TX, 9, 1, 0); + +static const struct snd_kcontrol_new atx_amp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_TX, 11, 1, 0); + + +/* Virtual mux. The chip does the input selection automatically + * as soon as we enable one input. */ +static const char * const adcl_enum_text[] = { + "MC1L", "RXINL", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(adcl_enum, adcl_enum_text); + +static const struct snd_kcontrol_new left_input_mux = + SOC_DAPM_ENUM("Route", adcl_enum); + +static const char * const adcr_enum_text[] = { + "MC1R", "MC2", "RXINR", "TXIN", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(adcr_enum, adcr_enum_text); + +static const struct snd_kcontrol_new right_input_mux = + SOC_DAPM_ENUM("Route", adcr_enum); + +static const struct snd_kcontrol_new samp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_RX0, 3, 1, 0); + +static const char * const speaker_amp_source_text[] = { + "CODEC", "Right" +}; +static SOC_ENUM_SINGLE_DECL(speaker_amp_source, MC13783_AUDIO_RX0, 4, + speaker_amp_source_text); +static const struct snd_kcontrol_new speaker_amp_source_mux = + SOC_DAPM_ENUM("Speaker Amp Source MUX", speaker_amp_source); + +static const char * const headset_amp_source_text[] = { + "CODEC", "Mixer" +}; + +static SOC_ENUM_SINGLE_DECL(headset_amp_source, MC13783_AUDIO_RX0, 11, + headset_amp_source_text); +static const struct snd_kcontrol_new headset_amp_source_mux = + SOC_DAPM_ENUM("Headset Amp Source MUX", headset_amp_source); + +static const struct snd_kcontrol_new cdcout_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_RX0, 18, 1, 0); + +static const struct snd_kcontrol_new adc_bypass_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_CODEC, 16, 1, 0); + +static const struct snd_kcontrol_new lamp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_RX0, 5, 1, 0); + +static const struct snd_kcontrol_new hlamp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_RX0, 10, 1, 0); + +static const struct snd_kcontrol_new hramp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_RX0, 9, 1, 0); + +static const struct snd_kcontrol_new llamp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_RX0, 16, 1, 0); + +static const struct snd_kcontrol_new lramp_ctl = + SOC_DAPM_SINGLE("Switch", MC13783_AUDIO_RX0, 15, 1, 0); + +static const struct snd_soc_dapm_widget mc13783_dapm_widgets[] = { +/* Input */ + SND_SOC_DAPM_INPUT("MC1LIN"), + SND_SOC_DAPM_INPUT("MC1RIN"), + SND_SOC_DAPM_INPUT("MC2IN"), + SND_SOC_DAPM_INPUT("RXINR"), + SND_SOC_DAPM_INPUT("RXINL"), + SND_SOC_DAPM_INPUT("TXIN"), + + SND_SOC_DAPM_SUPPLY("MC1 Bias", MC13783_AUDIO_TX, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MC2 Bias", MC13783_AUDIO_TX, 1, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("MC1L Amp", MC13783_AUDIO_TX, 7, 0, &mc1l_amp_ctl), + SND_SOC_DAPM_SWITCH("MC1R Amp", MC13783_AUDIO_TX, 5, 0, &mc1r_amp_ctl), + SND_SOC_DAPM_SWITCH("MC2 Amp", MC13783_AUDIO_TX, 9, 0, &mc2_amp_ctl), + SND_SOC_DAPM_SWITCH("TXIN Amp", MC13783_AUDIO_TX, 11, 0, &atx_amp_ctl), + + SND_SOC_DAPM_MUX("PGA Left Input Mux", SND_SOC_NOPM, 0, 0, + &left_input_mux), + SND_SOC_DAPM_MUX("PGA Right Input Mux", SND_SOC_NOPM, 0, 0, + &right_input_mux), + + SND_SOC_DAPM_MUX("Speaker Amp Source MUX", SND_SOC_NOPM, 0, 0, + &speaker_amp_source_mux), + + SND_SOC_DAPM_MUX("Headset Amp Source MUX", SND_SOC_NOPM, 0, 0, + &headset_amp_source_mux), + + SND_SOC_DAPM_PGA("PGA Left Input", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PGA Right Input", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_ADC("ADC", "Capture", MC13783_AUDIO_CODEC, 11, 0), + SND_SOC_DAPM_SUPPLY("ADC_Reset", MC13783_AUDIO_CODEC, 15, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Voice CODEC PGA", MC13783_AUDIO_RX1, 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("Voice CODEC Bypass", MC13783_AUDIO_CODEC, 16, 0, + &adc_bypass_ctl), + +/* Output */ + SND_SOC_DAPM_SUPPLY("DAC_E", MC13783_AUDIO_DAC, 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC_Reset", MC13783_AUDIO_DAC, 15, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("RXOUTL"), + SND_SOC_DAPM_OUTPUT("RXOUTR"), + SND_SOC_DAPM_OUTPUT("HSL"), + SND_SOC_DAPM_OUTPUT("HSR"), + SND_SOC_DAPM_OUTPUT("LSPL"), + SND_SOC_DAPM_OUTPUT("LSP"), + SND_SOC_DAPM_OUTPUT("SP"), + SND_SOC_DAPM_OUTPUT("CDCOUT"), + + SND_SOC_DAPM_SWITCH("CDCOUT Switch", MC13783_AUDIO_RX0, 18, 0, + &cdcout_ctl), + SND_SOC_DAPM_SWITCH("Speaker Amp Switch", MC13783_AUDIO_RX0, 3, 0, + &samp_ctl), + SND_SOC_DAPM_SWITCH("Loudspeaker Amp", SND_SOC_NOPM, 0, 0, &lamp_ctl), + SND_SOC_DAPM_SWITCH("Headset Amp Left", MC13783_AUDIO_RX0, 10, 0, + &hlamp_ctl), + SND_SOC_DAPM_SWITCH("Headset Amp Right", MC13783_AUDIO_RX0, 9, 0, + &hramp_ctl), + SND_SOC_DAPM_SWITCH("Line out Amp Left", MC13783_AUDIO_RX0, 16, 0, + &llamp_ctl), + SND_SOC_DAPM_SWITCH("Line out Amp Right", MC13783_AUDIO_RX0, 15, 0, + &lramp_ctl), + SND_SOC_DAPM_DAC("DAC", "Playback", MC13783_AUDIO_RX0, 22, 0), + SND_SOC_DAPM_PGA("DAC PGA", MC13783_AUDIO_RX1, 5, 0, NULL, 0), +}; + +static struct snd_soc_dapm_route mc13783_routes[] = { +/* Input */ + { "MC1L Amp", NULL, "MC1LIN"}, + { "MC1R Amp", NULL, "MC1RIN" }, + { "MC2 Amp", NULL, "MC2IN" }, + { "TXIN Amp", NULL, "TXIN"}, + + { "PGA Left Input Mux", "MC1L", "MC1L Amp" }, + { "PGA Left Input Mux", "RXINL", "RXINL"}, + { "PGA Right Input Mux", "MC1R", "MC1R Amp" }, + { "PGA Right Input Mux", "MC2", "MC2 Amp"}, + { "PGA Right Input Mux", "TXIN", "TXIN Amp"}, + { "PGA Right Input Mux", "RXINR", "RXINR"}, + + { "PGA Left Input", NULL, "PGA Left Input Mux"}, + { "PGA Right Input", NULL, "PGA Right Input Mux"}, + + { "ADC", NULL, "PGA Left Input"}, + { "ADC", NULL, "PGA Right Input"}, + { "ADC", NULL, "ADC_Reset"}, + + { "Voice CODEC PGA", "Voice CODEC Bypass", "ADC" }, + + { "Speaker Amp Source MUX", "CODEC", "Voice CODEC PGA"}, + { "Speaker Amp Source MUX", "Right", "DAC PGA"}, + + { "Headset Amp Source MUX", "CODEC", "Voice CODEC PGA"}, + { "Headset Amp Source MUX", "Mixer", "DAC PGA"}, + +/* Output */ + { "HSL", NULL, "Headset Amp Left" }, + { "HSR", NULL, "Headset Amp Right"}, + { "RXOUTL", NULL, "Line out Amp Left"}, + { "RXOUTR", NULL, "Line out Amp Right"}, + { "SP", "Speaker Amp Switch", "Speaker Amp Source MUX"}, + { "LSP", "Loudspeaker Amp", "Speaker Amp Source MUX"}, + { "HSL", "Headset Amp Left", "Headset Amp Source MUX"}, + { "HSR", "Headset Amp Right", "Headset Amp Source MUX"}, + { "Line out Amp Left", NULL, "DAC PGA"}, + { "Line out Amp Right", NULL, "DAC PGA"}, + { "DAC PGA", NULL, "DAC"}, + { "DAC", NULL, "DAC_E"}, + { "CDCOUT", "CDCOUT Switch", "Voice CODEC PGA"}, +}; + +static const char * const mc13783_3d_mixer[] = {"Stereo", "Phase Mix", + "Mono", "Mono Mix"}; + +static SOC_ENUM_SINGLE_DECL(mc13783_enum_3d_mixer, + MC13783_AUDIO_RX1, 16, + mc13783_3d_mixer); + +static struct snd_kcontrol_new mc13783_control_list[] = { + SOC_SINGLE("Loudspeaker enable", MC13783_AUDIO_RX0, 5, 1, 0), + SOC_SINGLE("PCM Playback Volume", MC13783_AUDIO_RX1, 6, 15, 0), + SOC_SINGLE("PCM Playback Switch", MC13783_AUDIO_RX1, 5, 1, 0), + SOC_DOUBLE("PCM Capture Volume", MC13783_AUDIO_TX, 19, 14, 31, 0), + SOC_ENUM("3D Control", mc13783_enum_3d_mixer), + + SOC_SINGLE("CDCOUT Switch", MC13783_AUDIO_RX0, 18, 1, 0), + SOC_SINGLE("Earpiece Amp Switch", MC13783_AUDIO_RX0, 3, 1, 0), + SOC_DOUBLE("Headset Amp Switch", MC13783_AUDIO_RX0, 10, 9, 1, 0), + SOC_DOUBLE("Line out Amp Switch", MC13783_AUDIO_RX0, 16, 15, 1, 0), + + SOC_SINGLE("PCM Capture Mixin Switch", MC13783_AUDIO_RX0, 22, 1, 0), + SOC_SINGLE("Line in Capture Mixin Switch", MC13783_AUDIO_RX0, 23, 1, 0), + + SOC_SINGLE("CODEC Capture Volume", MC13783_AUDIO_RX1, 1, 15, 0), + SOC_SINGLE("CODEC Capture Mixin Switch", MC13783_AUDIO_RX0, 21, 1, 0), + + SOC_SINGLE("Line in Capture Volume", MC13783_AUDIO_RX1, 12, 15, 0), + SOC_SINGLE("Line in Capture Switch", MC13783_AUDIO_RX1, 10, 1, 0), + + SOC_SINGLE("MC1 Capture Bias Switch", MC13783_AUDIO_TX, 0, 1, 0), + SOC_SINGLE("MC2 Capture Bias Switch", MC13783_AUDIO_TX, 1, 1, 0), +}; + +static int mc13783_probe(struct snd_soc_component *component) +{ + struct mc13783_priv *priv = snd_soc_component_get_drvdata(component); + + snd_soc_component_init_regmap(component, + dev_get_regmap(component->dev->parent, NULL)); + + /* these are the reset values */ + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_RX0, 0x25893); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_RX1, 0x00d35A); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_TX, 0x420000); + mc13xxx_reg_write(priv->mc13xxx, MC13783_SSI_NETWORK, 0x013060); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_CODEC, 0x180027); + mc13xxx_reg_write(priv->mc13xxx, MC13783_AUDIO_DAC, 0x0e0004); + + if (priv->adc_ssi_port == MC13783_SSI1_PORT) + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_CODEC, + AUDIO_SSI_SEL, 0); + else + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_CODEC, + AUDIO_SSI_SEL, AUDIO_SSI_SEL); + + if (priv->dac_ssi_port == MC13783_SSI1_PORT) + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_DAC, + AUDIO_SSI_SEL, 0); + else + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_DAC, + AUDIO_SSI_SEL, AUDIO_SSI_SEL); + + return 0; +} + +static void mc13783_remove(struct snd_soc_component *component) +{ + struct mc13783_priv *priv = snd_soc_component_get_drvdata(component); + + /* Make sure VAUDIOON is off */ + mc13xxx_reg_rmw(priv->mc13xxx, MC13783_AUDIO_RX0, 0x3, 0); +} + +#define MC13783_RATES_RECORD (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define MC13783_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops mc13783_ops_dac = { + .hw_params = mc13783_pcm_hw_params_dac, + .set_fmt = mc13783_set_fmt_async, + .set_sysclk = mc13783_set_sysclk_dac, + .set_tdm_slot = mc13783_set_tdm_slot_dac, +}; + +static const struct snd_soc_dai_ops mc13783_ops_codec = { + .hw_params = mc13783_pcm_hw_params_codec, + .set_fmt = mc13783_set_fmt_async, + .set_sysclk = mc13783_set_sysclk_codec, + .set_tdm_slot = mc13783_set_tdm_slot_codec, +}; + +/* + * The mc13783 has two SSI ports, both of them can be routed either + * to the voice codec or the stereo DAC. When two different SSI ports + * are used for the voice codec and the stereo DAC we can do different + * formats and sysclock settings for playback and capture + * (mc13783-hifi-playback and mc13783-hifi-capture). Using the same port + * forces us to use symmetric rates (mc13783-hifi). + */ +static struct snd_soc_dai_driver mc13783_dai_async[] = { + { + .name = "mc13783-hifi-playback", + .id = MC13783_ID_STEREO_DAC, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_dac, + }, { + .name = "mc13783-hifi-capture", + .id = MC13783_ID_STEREO_CODEC, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = MC13783_RATES_RECORD, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_codec, + }, +}; + +static const struct snd_soc_dai_ops mc13783_ops_sync = { + .hw_params = mc13783_pcm_hw_params_sync, + .set_fmt = mc13783_set_fmt_sync, + .set_sysclk = mc13783_set_sysclk_sync, + .set_tdm_slot = mc13783_set_tdm_slot_sync, +}; + +static struct snd_soc_dai_driver mc13783_dai_sync[] = { + { + .name = "mc13783-hifi", + .id = MC13783_ID_SYNC, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MC13783_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = MC13783_RATES_RECORD, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_sync, + .symmetric_rates = 1, + } +}; + +static const struct snd_soc_component_driver soc_component_dev_mc13783 = { + .probe = mc13783_probe, + .remove = mc13783_remove, + .controls = mc13783_control_list, + .num_controls = ARRAY_SIZE(mc13783_control_list), + .dapm_widgets = mc13783_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mc13783_dapm_widgets), + .dapm_routes = mc13783_routes, + .num_dapm_routes = ARRAY_SIZE(mc13783_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int __init mc13783_codec_probe(struct platform_device *pdev) +{ + struct mc13783_priv *priv; + struct mc13xxx_codec_platform_data *pdata = pdev->dev.platform_data; + struct device_node *np; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (pdata) { + priv->adc_ssi_port = pdata->adc_ssi_port; + priv->dac_ssi_port = pdata->dac_ssi_port; + } else { + np = of_get_child_by_name(pdev->dev.parent->of_node, "codec"); + if (!np) + return -ENOSYS; + + ret = of_property_read_u32(np, "adc-port", &priv->adc_ssi_port); + if (ret) { + of_node_put(np); + return ret; + } + + ret = of_property_read_u32(np, "dac-port", &priv->dac_ssi_port); + if (ret) { + of_node_put(np); + return ret; + } + + of_node_put(np); + } + + dev_set_drvdata(&pdev->dev, priv); + priv->mc13xxx = dev_get_drvdata(pdev->dev.parent); + + if (priv->adc_ssi_port == priv->dac_ssi_port) + ret = devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_mc13783, + mc13783_dai_sync, ARRAY_SIZE(mc13783_dai_sync)); + else + ret = devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_mc13783, + mc13783_dai_async, ARRAY_SIZE(mc13783_dai_async)); + + return ret; +} + +static int mc13783_codec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver mc13783_codec_driver = { + .driver = { + .name = "mc13783-codec", + }, + .remove = mc13783_codec_remove, +}; +module_platform_driver_probe(mc13783_codec_driver, mc13783_codec_probe); + +MODULE_DESCRIPTION("ASoC MC13783 driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix "); +MODULE_AUTHOR("Philippe Retornaz "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/mc13783.h b/sound/soc/codecs/mc13783.h new file mode 100644 index 000000000..3a6d1993a --- /dev/null +++ b/sound/soc/codecs/mc13783.h @@ -0,0 +1,28 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MC13783_MIXER_H +#define MC13783_MIXER_H + +#define MC13783_CLK_CLIA 1 +#define MC13783_CLK_CLIB 2 + +#define MC13783_ID_STEREO_DAC 1 +#define MC13783_ID_STEREO_CODEC 2 +#define MC13783_ID_SYNC 3 + +#endif /* MC13783_MIXER_H */ diff --git a/sound/soc/codecs/ml26124.c b/sound/soc/codecs/ml26124.c new file mode 100644 index 000000000..a5fa490dc --- /dev/null +++ b/sound/soc/codecs/ml26124.c @@ -0,0 +1,607 @@ +/* + * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ml26124.h" + +#define DVOL_CTL_DVMUTE_ON BIT(4) /* Digital volume MUTE On */ +#define DVOL_CTL_DVMUTE_OFF 0 /* Digital volume MUTE Off */ +#define ML26124_SAI_NO_DELAY BIT(1) +#define ML26124_SAI_FRAME_SYNC (BIT(5) | BIT(0)) /* For mono (Telecodec) */ +#define ML26134_CACHESIZE 212 +#define ML26124_VMID BIT(1) +#define ML26124_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_48000) +#define ML26124_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) +#define ML26124_NUM_REGISTER ML26134_CACHESIZE + +struct ml26124_priv { + u32 mclk; + u32 rate; + struct regmap *regmap; + int clk_in; + struct snd_pcm_substream *substream; +}; + +struct clk_coeff { + u32 mclk; + u32 rate; + u8 pllnl; + u8 pllnh; + u8 pllml; + u8 pllmh; + u8 plldiv; +}; + +/* ML26124 configuration */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7150, 50, 0); + +static const DECLARE_TLV_DB_SCALE(alclvl, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(mingain, -1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(maxgain, -675, 600, 0); +static const DECLARE_TLV_DB_SCALE(boost_vol, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(ngth, -7650, 150, 0); + +static const char * const ml26124_companding[] = {"16bit PCM", "u-law", + "A-law"}; + +static SOC_ENUM_SINGLE_DECL(ml26124_adc_companding_enum, + ML26124_SAI_TRANS_CTL, 6, ml26124_companding); + +static SOC_ENUM_SINGLE_DECL(ml26124_dac_companding_enum, + ML26124_SAI_RCV_CTL, 6, ml26124_companding); + +static const struct snd_kcontrol_new ml26124_snd_controls[] = { + SOC_SINGLE_TLV("Capture Digital Volume", ML26124_RECORD_DIG_VOL, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("Playback Digital Volume", ML26124_PLBAK_DIG_VOL, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("Digital Boost Volume", ML26124_DIGI_BOOST_VOL, 0, + 0x3f, 0, boost_vol), + SOC_SINGLE_TLV("EQ Band0 Volume", ML26124_EQ_GAIN_BRAND0, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band1 Volume", ML26124_EQ_GAIN_BRAND1, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band2 Volume", ML26124_EQ_GAIN_BRAND2, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band3 Volume", ML26124_EQ_GAIN_BRAND3, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("EQ Band4 Volume", ML26124_EQ_GAIN_BRAND4, 0, + 0xff, 1, digital_tlv), + SOC_SINGLE_TLV("ALC Target Level", ML26124_ALC_TARGET_LEV, 0, + 0xf, 1, alclvl), + SOC_SINGLE_TLV("ALC Min Input Volume", ML26124_ALC_MAXMIN_GAIN, 0, + 7, 0, mingain), + SOC_SINGLE_TLV("ALC Max Input Volume", ML26124_ALC_MAXMIN_GAIN, 4, + 7, 1, maxgain), + SOC_SINGLE_TLV("Playback Limiter Min Input Volume", + ML26124_PL_MAXMIN_GAIN, 0, 7, 0, mingain), + SOC_SINGLE_TLV("Playback Limiter Max Input Volume", + ML26124_PL_MAXMIN_GAIN, 4, 7, 1, maxgain), + SOC_SINGLE_TLV("Playback Boost Volume", ML26124_PLYBAK_BOST_VOL, 0, + 0x3f, 0, boost_vol), + SOC_SINGLE("DC High Pass Filter Switch", ML26124_FILTER_EN, 0, 1, 0), + SOC_SINGLE("Noise High Pass Filter Switch", ML26124_FILTER_EN, 1, 1, 0), + SOC_SINGLE("ZC Switch", ML26124_PW_ZCCMP_PW_MNG, 1, + 1, 0), + SOC_SINGLE("EQ Band0 Switch", ML26124_FILTER_EN, 2, 1, 0), + SOC_SINGLE("EQ Band1 Switch", ML26124_FILTER_EN, 3, 1, 0), + SOC_SINGLE("EQ Band2 Switch", ML26124_FILTER_EN, 4, 1, 0), + SOC_SINGLE("EQ Band3 Switch", ML26124_FILTER_EN, 5, 1, 0), + SOC_SINGLE("EQ Band4 Switch", ML26124_FILTER_EN, 6, 1, 0), + SOC_SINGLE("Play Limiter", ML26124_DVOL_CTL, 0, 1, 0), + SOC_SINGLE("Capture Limiter", ML26124_DVOL_CTL, 1, 1, 0), + SOC_SINGLE("Digital Volume Fade Switch", ML26124_DVOL_CTL, 3, 1, 0), + SOC_SINGLE("Digital Switch", ML26124_DVOL_CTL, 4, 1, 0), + SOC_ENUM("DAC Companding", ml26124_dac_companding_enum), + SOC_ENUM("ADC Companding", ml26124_adc_companding_enum), +}; + +static const struct snd_kcontrol_new ml26124_output_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Switch", ML26124_SPK_AMP_OUT, 1, 1, 0), + SOC_DAPM_SINGLE("Line in loopback Switch", ML26124_SPK_AMP_OUT, 3, 1, + 0), + SOC_DAPM_SINGLE("PGA Switch", ML26124_SPK_AMP_OUT, 5, 1, 0), +}; + +/* Input mux */ +static const char * const ml26124_input_select[] = {"Analog MIC SingleEnded in", + "Digital MIC in", "Analog MIC Differential in"}; + +static SOC_ENUM_SINGLE_DECL(ml26124_insel_enum, + ML26124_MIC_IF_CTL, 0, ml26124_input_select); + +static const struct snd_kcontrol_new ml26124_input_mux_controls = + SOC_DAPM_ENUM("Input Select", ml26124_insel_enum); + +static const struct snd_kcontrol_new ml26124_line_control = + SOC_DAPM_SINGLE("Switch", ML26124_PW_LOUT_PW_MNG, 1, 1, 0); + +static const struct snd_soc_dapm_widget ml26124_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("MCLKEN", ML26124_CLK_EN, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLLEN", ML26124_CLK_EN, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLLOE", ML26124_CLK_EN, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS", ML26124_PW_REF_PW_MNG, 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, + &ml26124_output_mixer_controls[0], + ARRAY_SIZE(ml26124_output_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "Playback", ML26124_PW_DAC_PW_MNG, 1, 0), + SND_SOC_DAPM_ADC("ADC", "Capture", ML26124_PW_IN_PW_MNG, 1, 0), + SND_SOC_DAPM_PGA("PGA", ML26124_PW_IN_PW_MNG, 3, 0, NULL, 0), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ml26124_input_mux_controls), + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ml26124_line_control), + SND_SOC_DAPM_INPUT("MDIN"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_OUTPUT("SPOUT"), + SND_SOC_DAPM_OUTPUT("LOUT"), +}; + +static const struct snd_soc_dapm_route ml26124_intercon[] = { + /* Supply */ + {"DAC", NULL, "MCLKEN"}, + {"ADC", NULL, "MCLKEN"}, + {"DAC", NULL, "PLLEN"}, + {"ADC", NULL, "PLLEN"}, + {"DAC", NULL, "PLLOE"}, + {"ADC", NULL, "PLLOE"}, + + /* output mixer */ + {"Output Mixer", "DAC Switch", "DAC"}, + {"Output Mixer", "Line in loopback Switch", "LIN"}, + + /* outputs */ + {"LOUT", NULL, "Output Mixer"}, + {"SPOUT", NULL, "Output Mixer"}, + {"Line Out Enable", NULL, "LOUT"}, + + /* input */ + {"ADC", NULL, "Input Mux"}, + {"Input Mux", "Analog MIC SingleEnded in", "PGA"}, + {"Input Mux", "Analog MIC Differential in", "PGA"}, + {"PGA", NULL, "MIN"}, +}; + +/* PLLOutputFreq(Hz) = InputMclkFreq(Hz) * PLLM / (PLLN * PLLDIV) */ +static const struct clk_coeff coeff_div[] = { + {12288000, 16000, 0xc, 0x0, 0x20, 0x0, 0x4}, + {12288000, 32000, 0xc, 0x0, 0x20, 0x0, 0x4}, + {12288000, 48000, 0xc, 0x0, 0x30, 0x0, 0x4}, +}; + +static const struct reg_default ml26124_reg[] = { + /* CLOCK control Register */ + {0x00, 0x00 }, /* Sampling Rate */ + {0x02, 0x00}, /* PLL NL */ + {0x04, 0x00}, /* PLLNH */ + {0x06, 0x00}, /* PLLML */ + {0x08, 0x00}, /* MLLMH */ + {0x0a, 0x00}, /* PLLDIV */ + {0x0c, 0x00}, /* Clock Enable */ + {0x0e, 0x00}, /* CLK Input/Output Control */ + + /* System Control Register */ + {0x10, 0x00}, /* Software RESET */ + {0x12, 0x00}, /* Record/Playback Run */ + {0x14, 0x00}, /* Mic Input/Output control */ + + /* Power Management Register */ + {0x20, 0x00}, /* Reference Power Management */ + {0x22, 0x00}, /* Input Power Management */ + {0x24, 0x00}, /* DAC Power Management */ + {0x26, 0x00}, /* SP-AMP Power Management */ + {0x28, 0x00}, /* LINEOUT Power Management */ + {0x2a, 0x00}, /* VIDEO Power Management */ + {0x2e, 0x00}, /* AC-CMP Power Management */ + + /* Analog reference Control Register */ + {0x30, 0x04}, /* MICBIAS Voltage Control */ + + /* Input/Output Amplifier Control Register */ + {0x32, 0x10}, /* MIC Input Volume */ + {0x38, 0x00}, /* Mic Boost Volume */ + {0x3a, 0x33}, /* Speaker AMP Volume */ + {0x48, 0x00}, /* AMP Volume Control Function Enable */ + {0x4a, 0x00}, /* Amplifier Volume Fader Control */ + + /* Analog Path Control Register */ + {0x54, 0x00}, /* Speaker AMP Output Control */ + {0x5a, 0x00}, /* Mic IF Control */ + {0xe8, 0x01}, /* Mic Select Control */ + + /* Audio Interface Control Register */ + {0x60, 0x00}, /* SAI-Trans Control */ + {0x62, 0x00}, /* SAI-Receive Control */ + {0x64, 0x00}, /* SAI Mode select */ + + /* DSP Control Register */ + {0x66, 0x01}, /* Filter Func Enable */ + {0x68, 0x00}, /* Volume Control Func Enable */ + {0x6A, 0x00}, /* Mixer & Volume Control*/ + {0x6C, 0xff}, /* Record Digital Volume */ + {0x70, 0xff}, /* Playback Digital Volume */ + {0x72, 0x10}, /* Digital Boost Volume */ + {0x74, 0xe7}, /* EQ gain Band0 */ + {0x76, 0xe7}, /* EQ gain Band1 */ + {0x78, 0xe7}, /* EQ gain Band2 */ + {0x7A, 0xe7}, /* EQ gain Band3 */ + {0x7C, 0xe7}, /* EQ gain Band4 */ + {0x7E, 0x00}, /* HPF2 CutOff*/ + {0x80, 0x00}, /* EQ Band0 Coef0L */ + {0x82, 0x00}, /* EQ Band0 Coef0H */ + {0x84, 0x00}, /* EQ Band0 Coef0L */ + {0x86, 0x00}, /* EQ Band0 Coef0H */ + {0x88, 0x00}, /* EQ Band1 Coef0L */ + {0x8A, 0x00}, /* EQ Band1 Coef0H */ + {0x8C, 0x00}, /* EQ Band1 Coef0L */ + {0x8E, 0x00}, /* EQ Band1 Coef0H */ + {0x90, 0x00}, /* EQ Band2 Coef0L */ + {0x92, 0x00}, /* EQ Band2 Coef0H */ + {0x94, 0x00}, /* EQ Band2 Coef0L */ + {0x96, 0x00}, /* EQ Band2 Coef0H */ + {0x98, 0x00}, /* EQ Band3 Coef0L */ + {0x9A, 0x00}, /* EQ Band3 Coef0H */ + {0x9C, 0x00}, /* EQ Band3 Coef0L */ + {0x9E, 0x00}, /* EQ Band3 Coef0H */ + {0xA0, 0x00}, /* EQ Band4 Coef0L */ + {0xA2, 0x00}, /* EQ Band4 Coef0H */ + {0xA4, 0x00}, /* EQ Band4 Coef0L */ + {0xA6, 0x00}, /* EQ Band4 Coef0H */ + + /* ALC Control Register */ + {0xb0, 0x00}, /* ALC Mode */ + {0xb2, 0x02}, /* ALC Attack Time */ + {0xb4, 0x03}, /* ALC Decay Time */ + {0xb6, 0x00}, /* ALC Hold Time */ + {0xb8, 0x0b}, /* ALC Target Level */ + {0xba, 0x70}, /* ALC Max/Min Gain */ + {0xbc, 0x00}, /* Noise Gate Threshold */ + {0xbe, 0x00}, /* ALC ZeroCross TimeOut */ + + /* Playback Limiter Control Register */ + {0xc0, 0x04}, /* PL Attack Time */ + {0xc2, 0x05}, /* PL Decay Time */ + {0xc4, 0x0d}, /* PL Target Level */ + {0xc6, 0x70}, /* PL Max/Min Gain */ + {0xc8, 0x10}, /* Playback Boost Volume */ + {0xca, 0x00}, /* PL ZeroCross TimeOut */ + + /* Video Amplifier Control Register */ + {0xd0, 0x01}, /* VIDEO AMP Gain Control */ + {0xd2, 0x01}, /* VIDEO AMP Setup 1 */ + {0xd4, 0x01}, /* VIDEO AMP Control2 */ +}; + +/* Get sampling rate value of sampling rate setting register (0x0) */ +static inline int get_srate(int rate) +{ + int srate; + + switch (rate) { + case 16000: + srate = 3; + break; + case 32000: + srate = 6; + break; + case 48000: + srate = 8; + break; + default: + return -EINVAL; + } + return srate; +} + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int ml26124_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ml26124_priv *priv = snd_soc_component_get_drvdata(component); + int i = get_coeff(priv->mclk, params_rate(hw_params)); + int srate; + + if (i < 0) + return i; + priv->substream = substream; + priv->rate = params_rate(hw_params); + + if (priv->clk_in) { + switch (priv->mclk / params_rate(hw_params)) { + case 256: + snd_soc_component_update_bits(component, ML26124_CLK_CTL, + BIT(0) | BIT(1), 1); + break; + case 512: + snd_soc_component_update_bits(component, ML26124_CLK_CTL, + BIT(0) | BIT(1), 2); + break; + case 1024: + snd_soc_component_update_bits(component, ML26124_CLK_CTL, + BIT(0) | BIT(1), 3); + break; + default: + dev_err(component->dev, "Unsupported MCLKI\n"); + break; + } + } else { + snd_soc_component_update_bits(component, ML26124_CLK_CTL, + BIT(0) | BIT(1), 0); + } + + srate = get_srate(params_rate(hw_params)); + if (srate < 0) + return srate; + + snd_soc_component_update_bits(component, ML26124_SMPLING_RATE, 0xf, srate); + snd_soc_component_update_bits(component, ML26124_PLLNL, 0xff, coeff_div[i].pllnl); + snd_soc_component_update_bits(component, ML26124_PLLNH, 0x1, coeff_div[i].pllnh); + snd_soc_component_update_bits(component, ML26124_PLLML, 0xff, coeff_div[i].pllml); + snd_soc_component_update_bits(component, ML26124_PLLMH, 0x3f, coeff_div[i].pllmh); + snd_soc_component_update_bits(component, ML26124_PLLDIV, 0x1f, coeff_div[i].plldiv); + + return 0; +} + +static int ml26124_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct ml26124_priv *priv = snd_soc_component_get_drvdata(component); + + switch (priv->substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + snd_soc_component_update_bits(component, ML26124_REC_PLYBAK_RUN, BIT(0), 1); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + snd_soc_component_update_bits(component, ML26124_REC_PLYBAK_RUN, BIT(1), 2); + break; + } + + if (mute) + snd_soc_component_update_bits(component, ML26124_DVOL_CTL, BIT(4), + DVOL_CTL_DVMUTE_ON); + else + snd_soc_component_update_bits(component, ML26124_DVOL_CTL, BIT(4), + DVOL_CTL_DVMUTE_OFF); + + return 0; +} + +static int ml26124_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + unsigned char mode; + struct snd_soc_component *component = codec_dai->component; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + mode = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + mode = 0; + break; + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, ML26124_SAI_MODE_SEL, BIT(0), mode); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ml26124_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct ml26124_priv *priv = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case ML26124_USE_PLLOUT: + priv->clk_in = ML26124_USE_PLLOUT; + break; + case ML26124_USE_MCLKI: + priv->clk_in = ML26124_USE_MCLKI; + break; + default: + return -EINVAL; + } + + priv->mclk = freq; + + return 0; +} + +static int ml26124_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct ml26124_priv *priv = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_component_update_bits(component, ML26124_PW_SPAMP_PW_MNG, + ML26124_R26_MASK, ML26124_BLT_PREAMP_ON); + msleep(100); + snd_soc_component_update_bits(component, ML26124_PW_SPAMP_PW_MNG, + ML26124_R26_MASK, + ML26124_MICBEN_ON | ML26124_BLT_ALL_ON); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* VMID ON */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_update_bits(component, ML26124_PW_REF_PW_MNG, + ML26124_VMID, ML26124_VMID); + msleep(500); + regcache_sync(priv->regmap); + } + break; + case SND_SOC_BIAS_OFF: + /* VMID OFF */ + snd_soc_component_update_bits(component, ML26124_PW_REF_PW_MNG, + ML26124_VMID, 0); + break; + } + return 0; +} + +static const struct snd_soc_dai_ops ml26124_dai_ops = { + .hw_params = ml26124_hw_params, + .digital_mute = ml26124_mute, + .set_fmt = ml26124_set_dai_fmt, + .set_sysclk = ml26124_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver ml26124_dai = { + .name = "ml26124-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = ML26124_RATES, + .formats = ML26124_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = ML26124_RATES, + .formats = ML26124_FORMATS,}, + .ops = &ml26124_dai_ops, + .symmetric_rates = 1, +}; + +static int ml26124_probe(struct snd_soc_component *component) +{ + /* Software Reset */ + snd_soc_component_update_bits(component, ML26124_SW_RST, 0x01, 1); + snd_soc_component_update_bits(component, ML26124_SW_RST, 0x01, 0); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_ml26124 = { + .probe = ml26124_probe, + .set_bias_level = ml26124_set_bias_level, + .controls = ml26124_snd_controls, + .num_controls = ARRAY_SIZE(ml26124_snd_controls), + .dapm_widgets = ml26124_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ml26124_dapm_widgets), + .dapm_routes = ml26124_intercon, + .num_dapm_routes = ARRAY_SIZE(ml26124_intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ml26124_i2c_regmap = { + .val_bits = 8, + .reg_bits = 8, + .max_register = ML26124_NUM_REGISTER, + .reg_defaults = ml26124_reg, + .num_reg_defaults = ARRAY_SIZE(ml26124_reg), + .cache_type = REGCACHE_RBTREE, + .write_flag_mask = 0x01, +}; + +static int ml26124_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ml26124_priv *priv; + int ret; + + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(i2c, priv); + + priv->regmap = devm_regmap_init_i2c(i2c, &ml26124_i2c_regmap); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&i2c->dev, "regmap_init_i2c() failed: %d\n", ret); + return ret; + } + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_ml26124, &ml26124_dai, 1); +} + +static const struct i2c_device_id ml26124_i2c_id[] = { + { "ml26124", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ml26124_i2c_id); + +static struct i2c_driver ml26124_i2c_driver = { + .driver = { + .name = "ml26124", + }, + .probe = ml26124_i2c_probe, + .id_table = ml26124_i2c_id, +}; + +module_i2c_driver(ml26124_i2c_driver); + +MODULE_AUTHOR("Tomoya MORINAGA "); +MODULE_DESCRIPTION("LAPIS Semiconductor ML26124 ALSA SoC codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ml26124.h b/sound/soc/codecs/ml26124.h new file mode 100644 index 000000000..5ea0cbb8c --- /dev/null +++ b/sound/soc/codecs/ml26124.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2011 LAPIS Semiconductor Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#ifndef ML26124_H +#define ML26124_H + +/* Clock Control Register */ +#define ML26124_SMPLING_RATE 0x00 +#define ML26124_PLLNL 0x02 +#define ML26124_PLLNH 0x04 +#define ML26124_PLLML 0x06 +#define ML26124_PLLMH 0x08 +#define ML26124_PLLDIV 0x0a +#define ML26124_CLK_EN 0x0c +#define ML26124_CLK_CTL 0x0e + +/* System Control Register */ +#define ML26124_SW_RST 0x10 +#define ML26124_REC_PLYBAK_RUN 0x12 +#define ML26124_MIC_TIM 0x14 + +/* Power Mnagement Register */ +#define ML26124_PW_REF_PW_MNG 0x20 +#define ML26124_PW_IN_PW_MNG 0x22 +#define ML26124_PW_DAC_PW_MNG 0x24 +#define ML26124_PW_SPAMP_PW_MNG 0x26 +#define ML26124_PW_LOUT_PW_MNG 0x28 +#define ML26124_PW_VOUT_PW_MNG 0x2a +#define ML26124_PW_ZCCMP_PW_MNG 0x2e + +/* Analog Reference Control Register */ +#define ML26124_PW_MICBIAS_VOL 0x30 + +/* Input/Output Amplifier Control Register */ +#define ML26124_PW_MIC_IN_VOL 0x32 +#define ML26124_PW_MIC_BOST_VOL 0x38 +#define ML26124_PW_SPK_AMP_VOL 0x3a +#define ML26124_PW_AMP_VOL_FUNC 0x48 +#define ML26124_PW_AMP_VOL_FADE 0x4a + +/* Analog Path Control Register */ +#define ML26124_SPK_AMP_OUT 0x54 +#define ML26124_MIC_IF_CTL 0x5a +#define ML26124_MIC_SELECT 0xe8 + +/* Audio Interface Control Register */ +#define ML26124_SAI_TRANS_CTL 0x60 +#define ML26124_SAI_RCV_CTL 0x62 +#define ML26124_SAI_MODE_SEL 0x64 + +/* DSP Control Register */ +#define ML26124_FILTER_EN 0x66 +#define ML26124_DVOL_CTL 0x68 +#define ML26124_MIXER_VOL_CTL 0x6a +#define ML26124_RECORD_DIG_VOL 0x6c +#define ML26124_PLBAK_DIG_VOL 0x70 +#define ML26124_DIGI_BOOST_VOL 0x72 +#define ML26124_EQ_GAIN_BRAND0 0x74 +#define ML26124_EQ_GAIN_BRAND1 0x76 +#define ML26124_EQ_GAIN_BRAND2 0x78 +#define ML26124_EQ_GAIN_BRAND3 0x7a +#define ML26124_EQ_GAIN_BRAND4 0x7c +#define ML26124_HPF2_CUTOFF 0x7e +#define ML26124_EQBRAND0_F0L 0x80 +#define ML26124_EQBRAND0_F0H 0x82 +#define ML26124_EQBRAND0_F1L 0x84 +#define ML26124_EQBRAND0_F1H 0x86 +#define ML26124_EQBRAND1_F0L 0x88 +#define ML26124_EQBRAND1_F0H 0x8a +#define ML26124_EQBRAND1_F1L 0x8c +#define ML26124_EQBRAND1_F1H 0x8e +#define ML26124_EQBRAND2_F0L 0x90 +#define ML26124_EQBRAND2_F0H 0x92 +#define ML26124_EQBRAND2_F1L 0x94 +#define ML26124_EQBRAND2_F1H 0x96 +#define ML26124_EQBRAND3_F0L 0x98 +#define ML26124_EQBRAND3_F0H 0x9a +#define ML26124_EQBRAND3_F1L 0x9c +#define ML26124_EQBRAND3_F1H 0x9e +#define ML26124_EQBRAND4_F0L 0xa0 +#define ML26124_EQBRAND4_F0H 0xa2 +#define ML26124_EQBRAND4_F1L 0xa4 +#define ML26124_EQBRAND4_F1H 0xa6 + +/* ALC Control Register */ +#define ML26124_ALC_MODE 0xb0 +#define ML26124_ALC_ATTACK_TIM 0xb2 +#define ML26124_ALC_DECAY_TIM 0xb4 +#define ML26124_ALC_HOLD_TIM 0xb6 +#define ML26124_ALC_TARGET_LEV 0xb8 +#define ML26124_ALC_MAXMIN_GAIN 0xba +#define ML26124_NOIS_GATE_THRSH 0xbc +#define ML26124_ALC_ZERO_TIMOUT 0xbe + +/* Playback Limiter Control Register */ +#define ML26124_PL_ATTACKTIME 0xc0 +#define ML26124_PL_DECAYTIME 0xc2 +#define ML26124_PL_TARGETTIME 0xc4 +#define ML26124_PL_MAXMIN_GAIN 0xc6 +#define ML26124_PLYBAK_BOST_VOL 0xc8 +#define ML26124_PL_0CROSS_TIMOUT 0xca + +/* Video Amplifer Control Register */ +#define ML26124_VIDEO_AMP_GAIN_CTL 0xd0 +#define ML26124_VIDEO_AMP_SETUP1 0xd2 +#define ML26124_VIDEO_AMP_CTL2 0xd4 + +/* Clock select for machine driver */ +#define ML26124_USE_PLL 0 +#define ML26124_USE_MCLKI_256FS 1 +#define ML26124_USE_MCLKI_512FS 2 +#define ML26124_USE_MCLKI_1024FS 3 + +/* Register Mask */ +#define ML26124_R0_MASK 0xf +#define ML26124_R2_MASK 0xff +#define ML26124_R4_MASK 0x1 +#define ML26124_R6_MASK 0xf +#define ML26124_R8_MASK 0x3f +#define ML26124_Ra_MASK 0x1f +#define ML26124_Rc_MASK 0x1f +#define ML26124_Re_MASK 0x7 +#define ML26124_R10_MASK 0x1 +#define ML26124_R12_MASK 0x17 +#define ML26124_R14_MASK 0x3f +#define ML26124_R20_MASK 0x47 +#define ML26124_R22_MASK 0xa +#define ML26124_R24_MASK 0x2 +#define ML26124_R26_MASK 0x1f +#define ML26124_R28_MASK 0x2 +#define ML26124_R2a_MASK 0x2 +#define ML26124_R2e_MASK 0x2 +#define ML26124_R30_MASK 0x7 +#define ML26124_R32_MASK 0x3f +#define ML26124_R38_MASK 0x38 +#define ML26124_R3a_MASK 0x3f +#define ML26124_R48_MASK 0x3 +#define ML26124_R4a_MASK 0x7 +#define ML26124_R54_MASK 0x2a +#define ML26124_R5a_MASK 0x3 +#define ML26124_Re8_MASK 0x3 +#define ML26124_R60_MASK 0xff +#define ML26124_R62_MASK 0xff +#define ML26124_R64_MASK 0x1 +#define ML26124_R66_MASK 0xff +#define ML26124_R68_MASK 0x3b +#define ML26124_R6a_MASK 0xf3 +#define ML26124_R6c_MASK 0xff +#define ML26124_R70_MASK 0xff + +#define ML26124_MCLKEN BIT(0) +#define ML26124_PLLEN BIT(1) +#define ML26124_PLLOE BIT(2) +#define ML26124_MCLKOE BIT(3) + +#define ML26124_BLT_ALL_ON 0x1f +#define ML26124_BLT_PREAMP_ON 0x13 + +#define ML26124_MICBEN_ON BIT(2) + +enum ml26124_regs { + ML26124_MCLK = 0, +}; + +enum ml26124_clk_in { + ML26124_USE_PLLOUT = 0, + ML26124_USE_MCLKI, +}; + +#endif diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c new file mode 100644 index 000000000..f4aba065c --- /dev/null +++ b/sound/soc/codecs/msm8916-wcd-analog.c @@ -0,0 +1,1275 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CDC_D_REVISION1 (0xf000) +#define CDC_D_PERPH_SUBTYPE (0xf005) +#define CDC_D_INT_EN_SET (0xf015) +#define CDC_D_INT_EN_CLR (0xf016) +#define MBHC_SWITCH_INT BIT(7) +#define MBHC_MIC_ELECTRICAL_INS_REM_DET BIT(6) +#define MBHC_BUTTON_PRESS_DET BIT(5) +#define MBHC_BUTTON_RELEASE_DET BIT(4) +#define CDC_D_CDC_RST_CTL (0xf046) +#define RST_CTL_DIG_SW_RST_N_MASK BIT(7) +#define RST_CTL_DIG_SW_RST_N_RESET 0 +#define RST_CTL_DIG_SW_RST_N_REMOVE_RESET BIT(7) + +#define CDC_D_CDC_TOP_CLK_CTL (0xf048) +#define TOP_CLK_CTL_A_MCLK_MCLK2_EN_MASK (BIT(2) | BIT(3)) +#define TOP_CLK_CTL_A_MCLK_EN_ENABLE BIT(2) +#define TOP_CLK_CTL_A_MCLK2_EN_ENABLE BIT(3) + +#define CDC_D_CDC_ANA_CLK_CTL (0xf049) +#define ANA_CLK_CTL_EAR_HPHR_CLK_EN_MASK BIT(0) +#define ANA_CLK_CTL_EAR_HPHR_CLK_EN BIT(0) +#define ANA_CLK_CTL_EAR_HPHL_CLK_EN BIT(1) +#define ANA_CLK_CTL_SPKR_CLK_EN_MASK BIT(4) +#define ANA_CLK_CTL_SPKR_CLK_EN BIT(4) +#define ANA_CLK_CTL_TXA_CLK25_EN BIT(5) + +#define CDC_D_CDC_DIG_CLK_CTL (0xf04A) +#define DIG_CLK_CTL_RXD1_CLK_EN BIT(0) +#define DIG_CLK_CTL_RXD2_CLK_EN BIT(1) +#define DIG_CLK_CTL_RXD3_CLK_EN BIT(2) +#define DIG_CLK_CTL_D_MBHC_CLK_EN_MASK BIT(3) +#define DIG_CLK_CTL_D_MBHC_CLK_EN BIT(3) +#define DIG_CLK_CTL_TXD_CLK_EN BIT(4) +#define DIG_CLK_CTL_NCP_CLK_EN_MASK BIT(6) +#define DIG_CLK_CTL_NCP_CLK_EN BIT(6) +#define DIG_CLK_CTL_RXD_PDM_CLK_EN_MASK BIT(7) +#define DIG_CLK_CTL_RXD_PDM_CLK_EN BIT(7) + +#define CDC_D_CDC_CONN_TX1_CTL (0xf050) +#define CONN_TX1_SERIAL_TX1_MUX GENMASK(1, 0) +#define CONN_TX1_SERIAL_TX1_ADC_1 0x0 +#define CONN_TX1_SERIAL_TX1_RX_PDM_LB 0x1 +#define CONN_TX1_SERIAL_TX1_ZERO 0x2 + +#define CDC_D_CDC_CONN_TX2_CTL (0xf051) +#define CONN_TX2_SERIAL_TX2_MUX GENMASK(1, 0) +#define CONN_TX2_SERIAL_TX2_ADC_2 0x0 +#define CONN_TX2_SERIAL_TX2_RX_PDM_LB 0x1 +#define CONN_TX2_SERIAL_TX2_ZERO 0x2 +#define CDC_D_CDC_CONN_HPHR_DAC_CTL (0xf052) +#define CDC_D_CDC_CONN_RX1_CTL (0xf053) +#define CDC_D_CDC_CONN_RX2_CTL (0xf054) +#define CDC_D_CDC_CONN_RX3_CTL (0xf055) +#define CDC_D_CDC_CONN_RX_LB_CTL (0xf056) +#define CDC_D_SEC_ACCESS (0xf0D0) +#define CDC_D_PERPH_RESET_CTL3 (0xf0DA) +#define CDC_D_PERPH_RESET_CTL4 (0xf0DB) +#define CDC_A_REVISION1 (0xf100) +#define CDC_A_REVISION2 (0xf101) +#define CDC_A_REVISION3 (0xf102) +#define CDC_A_REVISION4 (0xf103) +#define CDC_A_PERPH_TYPE (0xf104) +#define CDC_A_PERPH_SUBTYPE (0xf105) +#define CDC_A_INT_RT_STS (0xf110) +#define CDC_A_INT_SET_TYPE (0xf111) +#define CDC_A_INT_POLARITY_HIGH (0xf112) +#define CDC_A_INT_POLARITY_LOW (0xf113) +#define CDC_A_INT_LATCHED_CLR (0xf114) +#define CDC_A_INT_EN_SET (0xf115) +#define CDC_A_INT_EN_CLR (0xf116) +#define CDC_A_INT_LATCHED_STS (0xf118) +#define CDC_A_INT_PENDING_STS (0xf119) +#define CDC_A_INT_MID_SEL (0xf11A) +#define CDC_A_INT_PRIORITY (0xf11B) +#define CDC_A_MICB_1_EN (0xf140) +#define MICB_1_EN_MICB_ENABLE BIT(7) +#define MICB_1_EN_BYP_CAP_MASK BIT(6) +#define MICB_1_EN_NO_EXT_BYP_CAP BIT(6) +#define MICB_1_EN_EXT_BYP_CAP 0 +#define MICB_1_EN_PULL_DOWN_EN_MASK BIT(5) +#define MICB_1_EN_PULL_DOWN_EN_ENABLE BIT(5) +#define MICB_1_EN_OPA_STG2_TAIL_CURR_MASK GENMASK(3, 1) +#define MICB_1_EN_OPA_STG2_TAIL_CURR_1_60UA (0x4) +#define MICB_1_EN_PULL_UP_EN_MASK BIT(4) +#define MICB_1_EN_TX3_GND_SEL_MASK BIT(0) +#define MICB_1_EN_TX3_GND_SEL_TX_GND 0 + +#define CDC_A_MICB_1_VAL (0xf141) +#define MICB_MIN_VAL 1600 +#define MICB_STEP_SIZE 50 +#define MICB_VOLTAGE_REGVAL(v) (((v - MICB_MIN_VAL)/MICB_STEP_SIZE) << 3) +#define MICB_1_VAL_MICB_OUT_VAL_MASK GENMASK(7, 3) +#define MICB_1_VAL_MICB_OUT_VAL_V2P70V ((0x16) << 3) +#define MICB_1_VAL_MICB_OUT_VAL_V1P80V ((0x4) << 3) +#define CDC_A_MICB_1_CTL (0xf142) + +#define MICB_1_CTL_CFILT_REF_SEL_MASK BIT(1) +#define MICB_1_CTL_CFILT_REF_SEL_HPF_REF BIT(1) +#define MICB_1_CTL_EXT_PRECHARG_EN_MASK BIT(5) +#define MICB_1_CTL_EXT_PRECHARG_EN_ENABLE BIT(5) +#define MICB_1_CTL_INT_PRECHARG_BYP_MASK BIT(6) +#define MICB_1_CTL_INT_PRECHARG_BYP_EXT_PRECHRG_SEL BIT(6) + +#define CDC_A_MICB_1_INT_RBIAS (0xf143) +#define MICB_1_INT_TX1_INT_RBIAS_EN_MASK BIT(7) +#define MICB_1_INT_TX1_INT_RBIAS_EN_ENABLE BIT(7) +#define MICB_1_INT_TX1_INT_RBIAS_EN_DISABLE 0 + +#define MICB_1_INT_TX1_INT_PULLUP_EN_MASK BIT(6) +#define MICB_1_INT_TX1_INT_PULLUP_EN_TX1N_TO_MICBIAS BIT(6) +#define MICB_1_INT_TX1_INT_PULLUP_EN_TX1N_TO_GND 0 + +#define MICB_1_INT_TX2_INT_RBIAS_EN_MASK BIT(4) +#define MICB_1_INT_TX2_INT_RBIAS_EN_ENABLE BIT(4) +#define MICB_1_INT_TX2_INT_RBIAS_EN_DISABLE 0 +#define MICB_1_INT_TX2_INT_PULLUP_EN_MASK BIT(3) +#define MICB_1_INT_TX2_INT_PULLUP_EN_TX1N_TO_MICBIAS BIT(3) +#define MICB_1_INT_TX2_INT_PULLUP_EN_TX1N_TO_GND 0 + +#define MICB_1_INT_TX3_INT_RBIAS_EN_MASK BIT(1) +#define MICB_1_INT_TX3_INT_RBIAS_EN_ENABLE BIT(1) +#define MICB_1_INT_TX3_INT_RBIAS_EN_DISABLE 0 +#define MICB_1_INT_TX3_INT_PULLUP_EN_MASK BIT(0) +#define MICB_1_INT_TX3_INT_PULLUP_EN_TX1N_TO_MICBIAS BIT(0) +#define MICB_1_INT_TX3_INT_PULLUP_EN_TX1N_TO_GND 0 + +#define CDC_A_MICB_2_EN (0xf144) +#define CDC_A_MICB_2_EN_ENABLE BIT(7) +#define CDC_A_MICB_2_PULL_DOWN_EN_MASK BIT(5) +#define CDC_A_MICB_2_PULL_DOWN_EN BIT(5) +#define CDC_A_TX_1_2_ATEST_CTL_2 (0xf145) +#define CDC_A_MASTER_BIAS_CTL (0xf146) +#define CDC_A_MBHC_DET_CTL_1 (0xf147) +#define CDC_A_MBHC_DET_CTL_L_DET_EN BIT(7) +#define CDC_A_MBHC_DET_CTL_GND_DET_EN BIT(6) +#define CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_INSERTION BIT(5) +#define CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_REMOVAL (0) +#define CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_MASK BIT(5) +#define CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_SHIFT (5) +#define CDC_A_MBHC_DET_CTL_MIC_CLAMP_CTL_AUTO BIT(4) +#define CDC_A_MBHC_DET_CTL_MIC_CLAMP_CTL_MANUAL BIT(3) +#define CDC_A_MBHC_DET_CTL_MIC_CLAMP_CTL_MASK GENMASK(4, 3) +#define CDC_A_MBHC_DET_CTL_MBHC_BIAS_EN BIT(2) +#define CDC_A_MBHC_DET_CTL_2 (0xf150) +#define CDC_A_MBHC_DET_CTL_HS_L_DET_PULL_UP_CTRL_I_3P0 (BIT(7) | BIT(6)) +#define CDC_A_MBHC_DET_CTL_HS_L_DET_COMPA_CTRL_V0P9_VDD BIT(5) +#define CDC_A_PLUG_TYPE_MASK GENMASK(4, 3) +#define CDC_A_HPHL_PLUG_TYPE_NO BIT(4) +#define CDC_A_GND_PLUG_TYPE_NO BIT(3) +#define CDC_A_MBHC_DET_CTL_HPHL_100K_TO_GND_EN_MASK BIT(0) +#define CDC_A_MBHC_DET_CTL_HPHL_100K_TO_GND_EN BIT(0) +#define CDC_A_MBHC_FSM_CTL (0xf151) +#define CDC_A_MBHC_FSM_CTL_MBHC_FSM_EN BIT(7) +#define CDC_A_MBHC_FSM_CTL_MBHC_FSM_EN_MASK BIT(7) +#define CDC_A_MBHC_FSM_CTL_BTN_ISRC_CTRL_I_100UA (0x3 << 4) +#define CDC_A_MBHC_FSM_CTL_BTN_ISRC_CTRL_MASK GENMASK(6, 4) +#define CDC_A_MBHC_DBNC_TIMER (0xf152) +#define CDC_A_MBHC_DBNC_TIMER_BTN_DBNC_T_16MS BIT(3) +#define CDC_A_MBHC_DBNC_TIMER_INSREM_DBNC_T_256_MS (0x9 << 4) +#define CDC_A_MBHC_BTN0_ZDET_CTL_0 (0xf153) +#define CDC_A_MBHC_BTN1_ZDET_CTL_1 (0xf154) +#define CDC_A_MBHC_BTN2_ZDET_CTL_2 (0xf155) +#define CDC_A_MBHC_BTN3_CTL (0xf156) +#define CDC_A_MBHC_BTN4_CTL (0xf157) +#define CDC_A_MBHC_BTN_VREF_FINE_SHIFT (2) +#define CDC_A_MBHC_BTN_VREF_FINE_MASK GENMASK(4, 2) +#define CDC_A_MBHC_BTN_VREF_COARSE_MASK GENMASK(7, 5) +#define CDC_A_MBHC_BTN_VREF_COARSE_SHIFT (5) +#define CDC_A_MBHC_BTN_VREF_MASK (CDC_A_MBHC_BTN_VREF_COARSE_MASK | \ + CDC_A_MBHC_BTN_VREF_FINE_MASK) +#define CDC_A_MBHC_RESULT_1 (0xf158) +#define CDC_A_MBHC_RESULT_1_BTN_RESULT_MASK GENMASK(4, 0) +#define CDC_A_TX_1_EN (0xf160) +#define CDC_A_TX_2_EN (0xf161) +#define CDC_A_TX_1_2_TEST_CTL_1 (0xf162) +#define CDC_A_TX_1_2_TEST_CTL_2 (0xf163) +#define CDC_A_TX_1_2_ATEST_CTL (0xf164) +#define CDC_A_TX_1_2_OPAMP_BIAS (0xf165) +#define CDC_A_TX_3_EN (0xf167) +#define CDC_A_NCP_EN (0xf180) +#define CDC_A_NCP_CLK (0xf181) +#define CDC_A_NCP_FBCTRL (0xf183) +#define CDC_A_NCP_FBCTRL_FB_CLK_INV_MASK BIT(5) +#define CDC_A_NCP_FBCTRL_FB_CLK_INV BIT(5) +#define CDC_A_NCP_BIAS (0xf184) +#define CDC_A_NCP_VCTRL (0xf185) +#define CDC_A_NCP_TEST (0xf186) +#define CDC_A_NCP_CLIM_ADDR (0xf187) +#define CDC_A_RX_CLOCK_DIVIDER (0xf190) +#define CDC_A_RX_COM_OCP_CTL (0xf191) +#define CDC_A_RX_COM_OCP_COUNT (0xf192) +#define CDC_A_RX_COM_BIAS_DAC (0xf193) +#define RX_COM_BIAS_DAC_RX_BIAS_EN_MASK BIT(7) +#define RX_COM_BIAS_DAC_RX_BIAS_EN_ENABLE BIT(7) +#define RX_COM_BIAS_DAC_DAC_REF_EN_MASK BIT(0) +#define RX_COM_BIAS_DAC_DAC_REF_EN_ENABLE BIT(0) + +#define CDC_A_RX_HPH_BIAS_PA (0xf194) +#define CDC_A_RX_HPH_BIAS_LDO_OCP (0xf195) +#define CDC_A_RX_HPH_BIAS_CNP (0xf196) +#define CDC_A_RX_HPH_CNP_EN (0xf197) +#define CDC_A_RX_HPH_L_PA_DAC_CTL (0xf19B) +#define RX_HPA_L_PA_DAC_CTL_DATA_RESET_MASK BIT(1) +#define RX_HPA_L_PA_DAC_CTL_DATA_RESET_RESET BIT(1) +#define CDC_A_RX_HPH_R_PA_DAC_CTL (0xf19D) +#define RX_HPH_R_PA_DAC_CTL_DATA_RESET BIT(1) +#define RX_HPH_R_PA_DAC_CTL_DATA_RESET_MASK BIT(1) + +#define CDC_A_RX_EAR_CTL (0xf19E) +#define RX_EAR_CTL_SPK_VBAT_LDO_EN_MASK BIT(0) +#define RX_EAR_CTL_SPK_VBAT_LDO_EN_ENABLE BIT(0) + +#define CDC_A_SPKR_DAC_CTL (0xf1B0) +#define SPKR_DAC_CTL_DAC_RESET_MASK BIT(4) +#define SPKR_DAC_CTL_DAC_RESET_NORMAL 0 + +#define CDC_A_SPKR_DRV_CTL (0xf1B2) +#define SPKR_DRV_CTL_DEF_MASK 0xEF +#define SPKR_DRV_CLASSD_PA_EN_MASK BIT(7) +#define SPKR_DRV_CLASSD_PA_EN_ENABLE BIT(7) +#define SPKR_DRV_CAL_EN BIT(6) +#define SPKR_DRV_SETTLE_EN BIT(5) +#define SPKR_DRV_FW_EN BIT(3) +#define SPKR_DRV_BOOST_SET BIT(2) +#define SPKR_DRV_CMFB_SET BIT(1) +#define SPKR_DRV_GAIN_SET BIT(0) +#define SPKR_DRV_CTL_DEF_VAL (SPKR_DRV_CLASSD_PA_EN_ENABLE | \ + SPKR_DRV_CAL_EN | SPKR_DRV_SETTLE_EN | \ + SPKR_DRV_FW_EN | SPKR_DRV_BOOST_SET | \ + SPKR_DRV_CMFB_SET | SPKR_DRV_GAIN_SET) +#define CDC_A_SPKR_OCP_CTL (0xf1B4) +#define CDC_A_SPKR_PWRSTG_CTL (0xf1B5) +#define SPKR_PWRSTG_CTL_DAC_EN_MASK BIT(0) +#define SPKR_PWRSTG_CTL_DAC_EN BIT(0) +#define SPKR_PWRSTG_CTL_MASK 0xE0 +#define SPKR_PWRSTG_CTL_BBM_MASK BIT(7) +#define SPKR_PWRSTG_CTL_BBM_EN BIT(7) +#define SPKR_PWRSTG_CTL_HBRDGE_EN_MASK BIT(6) +#define SPKR_PWRSTG_CTL_HBRDGE_EN BIT(6) +#define SPKR_PWRSTG_CTL_CLAMP_EN_MASK BIT(5) +#define SPKR_PWRSTG_CTL_CLAMP_EN BIT(5) + +#define CDC_A_SPKR_DRV_DBG (0xf1B7) +#define CDC_A_CURRENT_LIMIT (0xf1C0) +#define CDC_A_BOOST_EN_CTL (0xf1C3) +#define CDC_A_SLOPE_COMP_IP_ZERO (0xf1C4) +#define CDC_A_SEC_ACCESS (0xf1D0) +#define CDC_A_PERPH_RESET_CTL3 (0xf1DA) +#define CDC_A_PERPH_RESET_CTL4 (0xf1DB) + +#define MSM8916_WCD_ANALOG_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000) +#define MSM8916_WCD_ANALOG_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static int btn_mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_BTN_4; +static int hs_jack_mask = SND_JACK_HEADPHONE | SND_JACK_HEADSET; + +static const char * const supply_names[] = { + "vdd-cdc-io", + "vdd-cdc-tx-rx-cx", +}; + +#define MBHC_MAX_BUTTONS (5) + +struct pm8916_wcd_analog_priv { + u16 pmic_rev; + u16 codec_version; + bool mbhc_btn_enabled; + /* special event to detect accessory type */ + int mbhc_btn0_released; + bool detect_accessory_type; + struct clk *mclk; + struct snd_soc_component *component; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; + struct snd_soc_jack *jack; + bool hphl_jack_type_normally_open; + bool gnd_jack_type_normally_open; + /* Voltage threshold when internal current source of 100uA is used */ + u32 vref_btn_cs[MBHC_MAX_BUTTONS]; + /* Voltage threshold when microphone bias is ON */ + u32 vref_btn_micb[MBHC_MAX_BUTTONS]; + unsigned int micbias1_cap_mode; + unsigned int micbias2_cap_mode; + unsigned int micbias_mv; +}; + +static const char *const adc2_mux_text[] = { "ZERO", "INP2", "INP3" }; +static const char *const rdac2_mux_text[] = { "RX1", "RX2" }; +static const char *const hph_text[] = { "ZERO", "Switch", }; + +static const struct soc_enum hph_enum = SOC_ENUM_SINGLE_VIRT( + ARRAY_SIZE(hph_text), hph_text); + +static const struct snd_kcontrol_new hphl_mux = SOC_DAPM_ENUM("HPHL", hph_enum); +static const struct snd_kcontrol_new hphr_mux = SOC_DAPM_ENUM("HPHR", hph_enum); + +/* ADC2 MUX */ +static const struct soc_enum adc2_enum = SOC_ENUM_SINGLE_VIRT( + ARRAY_SIZE(adc2_mux_text), adc2_mux_text); + +/* RDAC2 MUX */ +static const struct soc_enum rdac2_mux_enum = SOC_ENUM_SINGLE( + CDC_D_CDC_CONN_HPHR_DAC_CTL, 0, 2, rdac2_mux_text); + +static const struct snd_kcontrol_new spkr_switch[] = { + SOC_DAPM_SINGLE("Switch", CDC_A_SPKR_DAC_CTL, 7, 1, 0) +}; + +static const struct snd_kcontrol_new rdac2_mux = SOC_DAPM_ENUM( + "RDAC2 MUX Mux", rdac2_mux_enum); +static const struct snd_kcontrol_new tx_adc2_mux = SOC_DAPM_ENUM( + "ADC2 MUX Mux", adc2_enum); + +/* Analog Gain control 0 dB to +24 dB in 6 dB steps */ +static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 600, 0); + +static const struct snd_kcontrol_new pm8916_wcd_analog_snd_controls[] = { + SOC_SINGLE_TLV("ADC1 Volume", CDC_A_TX_1_EN, 3, 8, 0, analog_gain), + SOC_SINGLE_TLV("ADC2 Volume", CDC_A_TX_2_EN, 3, 8, 0, analog_gain), + SOC_SINGLE_TLV("ADC3 Volume", CDC_A_TX_3_EN, 3, 8, 0, analog_gain), +}; + +static void pm8916_wcd_analog_micbias_enable(struct snd_soc_component *component) +{ + struct pm8916_wcd_analog_priv *wcd = snd_soc_component_get_drvdata(component); + + snd_soc_component_update_bits(component, CDC_A_MICB_1_CTL, + MICB_1_CTL_EXT_PRECHARG_EN_MASK | + MICB_1_CTL_INT_PRECHARG_BYP_MASK, + MICB_1_CTL_INT_PRECHARG_BYP_EXT_PRECHRG_SEL + | MICB_1_CTL_EXT_PRECHARG_EN_ENABLE); + + if (wcd->micbias_mv) { + snd_soc_component_update_bits(component, CDC_A_MICB_1_VAL, + MICB_1_VAL_MICB_OUT_VAL_MASK, + MICB_VOLTAGE_REGVAL(wcd->micbias_mv)); + /* + * Special headset needs MICBIAS as 2.7V so wait for + * 50 msec for the MICBIAS to reach 2.7 volts. + */ + if (wcd->micbias_mv >= 2700) + msleep(50); + } + + snd_soc_component_update_bits(component, CDC_A_MICB_1_CTL, + MICB_1_CTL_EXT_PRECHARG_EN_MASK | + MICB_1_CTL_INT_PRECHARG_BYP_MASK, 0); + +} + +static int pm8916_wcd_analog_enable_micbias_ext(struct snd_soc_component + *component, int event, + int reg, unsigned int cap_mode) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + pm8916_wcd_analog_micbias_enable(component); + snd_soc_component_update_bits(component, CDC_A_MICB_1_EN, + MICB_1_EN_BYP_CAP_MASK, cap_mode); + break; + } + + return 0; +} + +static int pm8916_wcd_analog_enable_micbias_int(struct snd_soc_component + *component, int event, + int reg, u32 cap_mode) +{ + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, reg, MICB_1_EN_PULL_DOWN_EN_MASK, 0); + snd_soc_component_update_bits(component, CDC_A_MICB_1_EN, + MICB_1_EN_OPA_STG2_TAIL_CURR_MASK, + MICB_1_EN_OPA_STG2_TAIL_CURR_1_60UA); + + break; + case SND_SOC_DAPM_POST_PMU: + pm8916_wcd_analog_micbias_enable(component); + snd_soc_component_update_bits(component, CDC_A_MICB_1_EN, + MICB_1_EN_BYP_CAP_MASK, cap_mode); + break; + } + + return 0; +} + +static int pm8916_wcd_analog_enable_micbias_ext1(struct + snd_soc_dapm_widget + *w, struct snd_kcontrol + *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct pm8916_wcd_analog_priv *wcd = snd_soc_component_get_drvdata(component); + + return pm8916_wcd_analog_enable_micbias_ext(component, event, w->reg, + wcd->micbias1_cap_mode); +} + +static int pm8916_wcd_analog_enable_micbias_ext2(struct + snd_soc_dapm_widget + *w, struct snd_kcontrol + *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct pm8916_wcd_analog_priv *wcd = snd_soc_component_get_drvdata(component); + + return pm8916_wcd_analog_enable_micbias_ext(component, event, w->reg, + wcd->micbias2_cap_mode); + +} + +static int pm8916_wcd_analog_enable_micbias_int1(struct + snd_soc_dapm_widget + *w, struct snd_kcontrol + *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct pm8916_wcd_analog_priv *wcd = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, CDC_A_MICB_1_INT_RBIAS, + MICB_1_INT_TX1_INT_RBIAS_EN_MASK, + MICB_1_INT_TX1_INT_RBIAS_EN_ENABLE); + break; + } + + return pm8916_wcd_analog_enable_micbias_int(component, event, w->reg, + wcd->micbias1_cap_mode); +} + +static int pm8916_mbhc_configure_bias(struct pm8916_wcd_analog_priv *priv, + bool micbias2_enabled) +{ + struct snd_soc_component *component = priv->component; + u32 coarse, fine, reg_val, reg_addr; + int *vrefs, i; + + if (!micbias2_enabled) { /* use internal 100uA Current source */ + /* Enable internal 2.2k Internal Rbias Resistor */ + snd_soc_component_update_bits(component, CDC_A_MICB_1_INT_RBIAS, + MICB_1_INT_TX2_INT_RBIAS_EN_MASK, + MICB_1_INT_TX2_INT_RBIAS_EN_ENABLE); + /* Remove pull down on MIC BIAS2 */ + snd_soc_component_update_bits(component, CDC_A_MICB_2_EN, + CDC_A_MICB_2_PULL_DOWN_EN_MASK, + 0); + /* enable 100uA internal current source */ + snd_soc_component_update_bits(component, CDC_A_MBHC_FSM_CTL, + CDC_A_MBHC_FSM_CTL_BTN_ISRC_CTRL_MASK, + CDC_A_MBHC_FSM_CTL_BTN_ISRC_CTRL_I_100UA); + } + snd_soc_component_update_bits(component, CDC_A_MBHC_FSM_CTL, + CDC_A_MBHC_FSM_CTL_MBHC_FSM_EN_MASK, + CDC_A_MBHC_FSM_CTL_MBHC_FSM_EN); + + if (micbias2_enabled) + vrefs = &priv->vref_btn_micb[0]; + else + vrefs = &priv->vref_btn_cs[0]; + + /* program vref ranges for all the buttons */ + reg_addr = CDC_A_MBHC_BTN0_ZDET_CTL_0; + for (i = 0; i < MBHC_MAX_BUTTONS; i++) { + /* split mv in to coarse parts of 100mv & fine parts of 12mv */ + coarse = (vrefs[i] / 100); + fine = ((vrefs[i] % 100) / 12); + reg_val = (coarse << CDC_A_MBHC_BTN_VREF_COARSE_SHIFT) | + (fine << CDC_A_MBHC_BTN_VREF_FINE_SHIFT); + snd_soc_component_update_bits(component, reg_addr, + CDC_A_MBHC_BTN_VREF_MASK, + reg_val); + reg_addr++; + } + + return 0; +} + +static void pm8916_wcd_setup_mbhc(struct pm8916_wcd_analog_priv *wcd) +{ + struct snd_soc_component *component = wcd->component; + bool micbias_enabled = false; + u32 plug_type = 0; + u32 int_en_mask; + + snd_soc_component_write(component, CDC_A_MBHC_DET_CTL_1, + CDC_A_MBHC_DET_CTL_L_DET_EN | + CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_INSERTION | + CDC_A_MBHC_DET_CTL_MIC_CLAMP_CTL_AUTO | + CDC_A_MBHC_DET_CTL_MBHC_BIAS_EN); + + if (wcd->hphl_jack_type_normally_open) + plug_type |= CDC_A_HPHL_PLUG_TYPE_NO; + + if (wcd->gnd_jack_type_normally_open) + plug_type |= CDC_A_GND_PLUG_TYPE_NO; + + snd_soc_component_write(component, CDC_A_MBHC_DET_CTL_2, + CDC_A_MBHC_DET_CTL_HS_L_DET_PULL_UP_CTRL_I_3P0 | + CDC_A_MBHC_DET_CTL_HS_L_DET_COMPA_CTRL_V0P9_VDD | + plug_type | + CDC_A_MBHC_DET_CTL_HPHL_100K_TO_GND_EN); + + + snd_soc_component_write(component, CDC_A_MBHC_DBNC_TIMER, + CDC_A_MBHC_DBNC_TIMER_INSREM_DBNC_T_256_MS | + CDC_A_MBHC_DBNC_TIMER_BTN_DBNC_T_16MS); + + /* enable MBHC clock */ + snd_soc_component_update_bits(component, CDC_D_CDC_DIG_CLK_CTL, + DIG_CLK_CTL_D_MBHC_CLK_EN_MASK, + DIG_CLK_CTL_D_MBHC_CLK_EN); + + if (snd_soc_component_read32(component, CDC_A_MICB_2_EN) & CDC_A_MICB_2_EN_ENABLE) + micbias_enabled = true; + + pm8916_mbhc_configure_bias(wcd, micbias_enabled); + + int_en_mask = MBHC_SWITCH_INT; + if (wcd->mbhc_btn_enabled) + int_en_mask |= MBHC_BUTTON_PRESS_DET | MBHC_BUTTON_RELEASE_DET; + + snd_soc_component_update_bits(component, CDC_D_INT_EN_CLR, int_en_mask, 0); + snd_soc_component_update_bits(component, CDC_D_INT_EN_SET, int_en_mask, int_en_mask); + wcd->mbhc_btn0_released = false; + wcd->detect_accessory_type = true; +} + +static int pm8916_wcd_analog_enable_micbias_int2(struct + snd_soc_dapm_widget + *w, struct snd_kcontrol + *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct pm8916_wcd_analog_priv *wcd = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, CDC_A_MICB_1_INT_RBIAS, + MICB_1_INT_TX2_INT_RBIAS_EN_MASK, + MICB_1_INT_TX2_INT_RBIAS_EN_ENABLE); + break; + case SND_SOC_DAPM_POST_PMU: + pm8916_mbhc_configure_bias(wcd, true); + break; + case SND_SOC_DAPM_POST_PMD: + pm8916_mbhc_configure_bias(wcd, false); + break; + } + + return pm8916_wcd_analog_enable_micbias_int(component, event, w->reg, + wcd->micbias2_cap_mode); +} + +static int pm8916_wcd_analog_enable_adc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 adc_reg = CDC_A_TX_1_2_TEST_CTL_2; + u8 init_bit_shift; + + if (w->reg == CDC_A_TX_1_EN) + init_bit_shift = 5; + else + init_bit_shift = 4; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (w->reg == CDC_A_TX_2_EN) + snd_soc_component_update_bits(component, CDC_A_MICB_1_CTL, + MICB_1_CTL_CFILT_REF_SEL_MASK, + MICB_1_CTL_CFILT_REF_SEL_HPF_REF); + /* + * Add delay of 10 ms to give sufficient time for the voltage + * to shoot up and settle so that the txfe init does not + * happen when the input voltage is changing too much. + */ + usleep_range(10000, 10010); + snd_soc_component_update_bits(component, adc_reg, 1 << init_bit_shift, + 1 << init_bit_shift); + switch (w->reg) { + case CDC_A_TX_1_EN: + snd_soc_component_update_bits(component, CDC_D_CDC_CONN_TX1_CTL, + CONN_TX1_SERIAL_TX1_MUX, + CONN_TX1_SERIAL_TX1_ADC_1); + break; + case CDC_A_TX_2_EN: + case CDC_A_TX_3_EN: + snd_soc_component_update_bits(component, CDC_D_CDC_CONN_TX2_CTL, + CONN_TX2_SERIAL_TX2_MUX, + CONN_TX2_SERIAL_TX2_ADC_2); + break; + } + break; + case SND_SOC_DAPM_POST_PMU: + /* + * Add delay of 12 ms before deasserting the init + * to reduce the tx pop + */ + usleep_range(12000, 12010); + snd_soc_component_update_bits(component, adc_reg, 1 << init_bit_shift, 0x00); + break; + case SND_SOC_DAPM_POST_PMD: + switch (w->reg) { + case CDC_A_TX_1_EN: + snd_soc_component_update_bits(component, CDC_D_CDC_CONN_TX1_CTL, + CONN_TX1_SERIAL_TX1_MUX, + CONN_TX1_SERIAL_TX1_ZERO); + break; + case CDC_A_TX_2_EN: + snd_soc_component_update_bits(component, CDC_A_MICB_1_CTL, + MICB_1_CTL_CFILT_REF_SEL_MASK, 0); + /* fall through */ + case CDC_A_TX_3_EN: + snd_soc_component_update_bits(component, CDC_D_CDC_CONN_TX2_CTL, + CONN_TX2_SERIAL_TX2_MUX, + CONN_TX2_SERIAL_TX2_ZERO); + break; + } + + + break; + } + return 0; +} + +static int pm8916_wcd_analog_enable_spk_pa(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, CDC_A_SPKR_PWRSTG_CTL, + SPKR_PWRSTG_CTL_DAC_EN_MASK | + SPKR_PWRSTG_CTL_BBM_MASK | + SPKR_PWRSTG_CTL_HBRDGE_EN_MASK | + SPKR_PWRSTG_CTL_CLAMP_EN_MASK, + SPKR_PWRSTG_CTL_DAC_EN| + SPKR_PWRSTG_CTL_BBM_EN | + SPKR_PWRSTG_CTL_HBRDGE_EN | + SPKR_PWRSTG_CTL_CLAMP_EN); + + snd_soc_component_update_bits(component, CDC_A_RX_EAR_CTL, + RX_EAR_CTL_SPK_VBAT_LDO_EN_MASK, + RX_EAR_CTL_SPK_VBAT_LDO_EN_ENABLE); + break; + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, CDC_A_SPKR_DRV_CTL, + SPKR_DRV_CTL_DEF_MASK, + SPKR_DRV_CTL_DEF_VAL); + snd_soc_component_update_bits(component, w->reg, + SPKR_DRV_CLASSD_PA_EN_MASK, + SPKR_DRV_CLASSD_PA_EN_ENABLE); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, CDC_A_SPKR_PWRSTG_CTL, + SPKR_PWRSTG_CTL_DAC_EN_MASK| + SPKR_PWRSTG_CTL_BBM_MASK | + SPKR_PWRSTG_CTL_HBRDGE_EN_MASK | + SPKR_PWRSTG_CTL_CLAMP_EN_MASK, 0); + + snd_soc_component_update_bits(component, CDC_A_SPKR_DAC_CTL, + SPKR_DAC_CTL_DAC_RESET_MASK, + SPKR_DAC_CTL_DAC_RESET_NORMAL); + snd_soc_component_update_bits(component, CDC_A_RX_EAR_CTL, + RX_EAR_CTL_SPK_VBAT_LDO_EN_MASK, 0); + break; + } + return 0; +} + +static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_RX_COM_OCP_CTL, 0xD1}, + {CDC_A_RX_COM_OCP_COUNT, 0xFF}, + {CDC_D_SEC_ACCESS, 0xA5}, + {CDC_D_PERPH_RESET_CTL3, 0x0F}, + {CDC_A_TX_1_2_OPAMP_BIAS, 0x4F}, + {CDC_A_NCP_FBCTRL, 0x28}, + {CDC_A_SPKR_DRV_CTL, 0x69}, + {CDC_A_SPKR_DRV_DBG, 0x01}, + {CDC_A_BOOST_EN_CTL, 0x5F}, + {CDC_A_SLOPE_COMP_IP_ZERO, 0x88}, + {CDC_A_SEC_ACCESS, 0xA5}, + {CDC_A_PERPH_RESET_CTL3, 0x0F}, + {CDC_A_CURRENT_LIMIT, 0x82}, + {CDC_A_SPKR_DAC_CTL, 0x03}, + {CDC_A_SPKR_OCP_CTL, 0xE1}, + {CDC_A_MASTER_BIAS_CTL, 0x30}, +}; + +static int pm8916_wcd_analog_probe(struct snd_soc_component *component) +{ + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); + int err, reg; + + err = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); + if (err != 0) { + dev_err(component->dev, "failed to enable regulators (%d)\n", err); + return err; + } + + snd_soc_component_init_regmap(component, + dev_get_regmap(component->dev->parent, NULL)); + snd_soc_component_set_drvdata(component, priv); + priv->pmic_rev = snd_soc_component_read32(component, CDC_D_REVISION1); + priv->codec_version = snd_soc_component_read32(component, CDC_D_PERPH_SUBTYPE); + + dev_info(component->dev, "PMIC REV: %d\t CODEC Version: %d\n", + priv->pmic_rev, priv->codec_version); + + snd_soc_component_write(component, CDC_D_PERPH_RESET_CTL4, 0x01); + snd_soc_component_write(component, CDC_A_PERPH_RESET_CTL4, 0x01); + + for (reg = 0; reg < ARRAY_SIZE(wcd_reg_defaults_2_0); reg++) + snd_soc_component_write(component, wcd_reg_defaults_2_0[reg].reg, + wcd_reg_defaults_2_0[reg].def); + + priv->component = component; + + snd_soc_component_update_bits(component, CDC_D_CDC_RST_CTL, + RST_CTL_DIG_SW_RST_N_MASK, + RST_CTL_DIG_SW_RST_N_REMOVE_RESET); + + pm8916_wcd_setup_mbhc(priv); + + return 0; +} + +static void pm8916_wcd_analog_remove(struct snd_soc_component *component) +{ + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); + + snd_soc_component_update_bits(component, CDC_D_CDC_RST_CTL, + RST_CTL_DIG_SW_RST_N_MASK, 0); + + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); +} + +static const struct snd_soc_dapm_route pm8916_wcd_analog_audio_map[] = { + + {"PDM_RX1", NULL, "PDM Playback"}, + {"PDM_RX2", NULL, "PDM Playback"}, + {"PDM_RX3", NULL, "PDM Playback"}, + {"PDM Capture", NULL, "PDM_TX"}, + + /* ADC Connections */ + {"PDM_TX", NULL, "ADC2"}, + {"PDM_TX", NULL, "ADC3"}, + {"ADC2", NULL, "ADC2 MUX"}, + {"ADC3", NULL, "ADC2 MUX"}, + {"ADC2 MUX", "INP2", "ADC2_INP2"}, + {"ADC2 MUX", "INP3", "ADC2_INP3"}, + + {"PDM_TX", NULL, "ADC1"}, + {"ADC1", NULL, "AMIC1"}, + {"ADC2_INP2", NULL, "AMIC2"}, + {"ADC2_INP3", NULL, "AMIC3"}, + + /* RDAC Connections */ + {"HPHR DAC", NULL, "RDAC2 MUX"}, + {"RDAC2 MUX", "RX1", "PDM_RX1"}, + {"RDAC2 MUX", "RX2", "PDM_RX2"}, + {"HPHL DAC", NULL, "PDM_RX1"}, + {"PDM_RX1", NULL, "RXD1_CLK"}, + {"PDM_RX2", NULL, "RXD2_CLK"}, + {"PDM_RX3", NULL, "RXD3_CLK"}, + + {"PDM_RX1", NULL, "RXD_PDM_CLK"}, + {"PDM_RX2", NULL, "RXD_PDM_CLK"}, + {"PDM_RX3", NULL, "RXD_PDM_CLK"}, + + {"ADC1", NULL, "TXD_CLK"}, + {"ADC2", NULL, "TXD_CLK"}, + {"ADC3", NULL, "TXD_CLK"}, + + {"ADC1", NULL, "TXA_CLK25"}, + {"ADC2", NULL, "TXA_CLK25"}, + {"ADC3", NULL, "TXA_CLK25"}, + + {"PDM_RX1", NULL, "A_MCLK2"}, + {"PDM_RX2", NULL, "A_MCLK2"}, + {"PDM_RX3", NULL, "A_MCLK2"}, + + {"PDM_TX", NULL, "A_MCLK2"}, + {"A_MCLK2", NULL, "A_MCLK"}, + + /* Headset (RX MIX1 and RX MIX2) */ + {"HEADPHONE", NULL, "HPHL PA"}, + {"HEADPHONE", NULL, "HPHR PA"}, + + {"HPHL PA", NULL, "EAR_HPHL_CLK"}, + {"HPHR PA", NULL, "EAR_HPHR_CLK"}, + + {"CP", NULL, "NCP_CLK"}, + + {"HPHL PA", NULL, "HPHL"}, + {"HPHR PA", NULL, "HPHR"}, + {"HPHL PA", NULL, "CP"}, + {"HPHL PA", NULL, "RX_BIAS"}, + {"HPHR PA", NULL, "CP"}, + {"HPHR PA", NULL, "RX_BIAS"}, + {"HPHL", "Switch", "HPHL DAC"}, + {"HPHR", "Switch", "HPHR DAC"}, + + {"RX_BIAS", NULL, "DAC_REF"}, + + {"SPK_OUT", NULL, "SPK PA"}, + {"SPK PA", NULL, "RX_BIAS"}, + {"SPK PA", NULL, "SPKR_CLK"}, + {"SPK PA", NULL, "SPK DAC"}, + {"SPK DAC", "Switch", "PDM_RX3"}, + + {"MIC BIAS Internal1", NULL, "INT_LDO_H"}, + {"MIC BIAS Internal2", NULL, "INT_LDO_H"}, + {"MIC BIAS External1", NULL, "INT_LDO_H"}, + {"MIC BIAS External2", NULL, "INT_LDO_H"}, + {"MIC BIAS Internal1", NULL, "vdd-micbias"}, + {"MIC BIAS Internal2", NULL, "vdd-micbias"}, + {"MIC BIAS External1", NULL, "vdd-micbias"}, + {"MIC BIAS External2", NULL, "vdd-micbias"}, +}; + +static const struct snd_soc_dapm_widget pm8916_wcd_analog_dapm_widgets[] = { + + SND_SOC_DAPM_AIF_IN("PDM_RX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PDM_RX2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PDM_RX3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PDM_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_INPUT("AMIC3"), + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_OUTPUT("HEADPHONE"), + + /* RX stuff */ + SND_SOC_DAPM_SUPPLY("INT_LDO_H", SND_SOC_NOPM, 1, 0, NULL, 0), + + SND_SOC_DAPM_PGA("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0), + SND_SOC_DAPM_MUX("HPHL", SND_SOC_NOPM, 0, 0, &hphl_mux), + SND_SOC_DAPM_MIXER("HPHL DAC", CDC_A_RX_HPH_L_PA_DAC_CTL, 3, 0, NULL, + 0), + SND_SOC_DAPM_PGA("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0), + SND_SOC_DAPM_MUX("HPHR", SND_SOC_NOPM, 0, 0, &hphr_mux), + SND_SOC_DAPM_MIXER("HPHR DAC", CDC_A_RX_HPH_R_PA_DAC_CTL, 3, 0, NULL, + 0), + SND_SOC_DAPM_MIXER("SPK DAC", SND_SOC_NOPM, 0, 0, + spkr_switch, ARRAY_SIZE(spkr_switch)), + + /* Speaker */ + SND_SOC_DAPM_OUTPUT("SPK_OUT"), + SND_SOC_DAPM_PGA_E("SPK PA", CDC_A_SPKR_DRV_CTL, + 6, 0, NULL, 0, + pm8916_wcd_analog_enable_spk_pa, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_REGULATOR_SUPPLY("vdd-micbias", 0, 0), + SND_SOC_DAPM_SUPPLY("CP", CDC_A_NCP_EN, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DAC_REF", CDC_A_RX_COM_BIAS_DAC, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RX_BIAS", CDC_A_RX_COM_BIAS_DAC, 7, 0, NULL, 0), + + /* TX */ + SND_SOC_DAPM_SUPPLY("MIC BIAS Internal1", CDC_A_MICB_1_EN, 7, 0, + pm8916_wcd_analog_enable_micbias_int1, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("MIC BIAS Internal2", CDC_A_MICB_2_EN, 7, 0, + pm8916_wcd_analog_enable_micbias_int2, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("MIC BIAS External1", CDC_A_MICB_1_EN, 7, 0, + pm8916_wcd_analog_enable_micbias_ext1, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("MIC BIAS External2", CDC_A_MICB_2_EN, 7, 0, + pm8916_wcd_analog_enable_micbias_ext2, + SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_ADC_E("ADC1", NULL, CDC_A_TX_1_EN, 7, 0, + pm8916_wcd_analog_enable_adc, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC2_INP2", NULL, CDC_A_TX_2_EN, 7, 0, + pm8916_wcd_analog_enable_adc, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC2_INP3", NULL, CDC_A_TX_3_EN, 7, 0, + pm8916_wcd_analog_enable_adc, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER("ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("ADC2 MUX", SND_SOC_NOPM, 0, 0, &tx_adc2_mux), + SND_SOC_DAPM_MUX("RDAC2 MUX", SND_SOC_NOPM, 0, 0, &rdac2_mux), + + /* Analog path clocks */ + SND_SOC_DAPM_SUPPLY("EAR_HPHR_CLK", CDC_D_CDC_ANA_CLK_CTL, 0, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY("EAR_HPHL_CLK", CDC_D_CDC_ANA_CLK_CTL, 1, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY("SPKR_CLK", CDC_D_CDC_ANA_CLK_CTL, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("TXA_CLK25", CDC_D_CDC_ANA_CLK_CTL, 5, 0, NULL, 0), + + /* Digital path clocks */ + + SND_SOC_DAPM_SUPPLY("RXD1_CLK", CDC_D_CDC_DIG_CLK_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RXD2_CLK", CDC_D_CDC_DIG_CLK_CTL, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RXD3_CLK", CDC_D_CDC_DIG_CLK_CTL, 2, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("TXD_CLK", CDC_D_CDC_DIG_CLK_CTL, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("NCP_CLK", CDC_D_CDC_DIG_CLK_CTL, 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RXD_PDM_CLK", CDC_D_CDC_DIG_CLK_CTL, 7, 0, NULL, + 0), + + /* System Clock source */ + SND_SOC_DAPM_SUPPLY("A_MCLK", CDC_D_CDC_TOP_CLK_CTL, 2, 0, NULL, 0), + /* TX ADC and RX DAC Clock source. */ + SND_SOC_DAPM_SUPPLY("A_MCLK2", CDC_D_CDC_TOP_CLK_CTL, 3, 0, NULL, 0), +}; + +static int pm8916_wcd_analog_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, + void *data) +{ + struct pm8916_wcd_analog_priv *wcd = snd_soc_component_get_drvdata(component); + + wcd->jack = jack; + + return 0; +} + +static irqreturn_t mbhc_btn_release_irq_handler(int irq, void *arg) +{ + struct pm8916_wcd_analog_priv *priv = arg; + + if (priv->detect_accessory_type) { + struct snd_soc_component *component = priv->component; + u32 val = snd_soc_component_read32(component, CDC_A_MBHC_RESULT_1); + + /* check if its BTN0 thats released */ + if ((val != -1) && !(val & CDC_A_MBHC_RESULT_1_BTN_RESULT_MASK)) + priv->mbhc_btn0_released = true; + + } else { + snd_soc_jack_report(priv->jack, 0, btn_mask); + } + + return IRQ_HANDLED; +} + +static irqreturn_t mbhc_btn_press_irq_handler(int irq, void *arg) +{ + struct pm8916_wcd_analog_priv *priv = arg; + struct snd_soc_component *component = priv->component; + u32 btn_result; + + btn_result = snd_soc_component_read32(component, CDC_A_MBHC_RESULT_1) & + CDC_A_MBHC_RESULT_1_BTN_RESULT_MASK; + + switch (btn_result) { + case 0xf: + snd_soc_jack_report(priv->jack, SND_JACK_BTN_4, btn_mask); + break; + case 0x7: + snd_soc_jack_report(priv->jack, SND_JACK_BTN_3, btn_mask); + break; + case 0x3: + snd_soc_jack_report(priv->jack, SND_JACK_BTN_2, btn_mask); + break; + case 0x1: + snd_soc_jack_report(priv->jack, SND_JACK_BTN_1, btn_mask); + break; + case 0x0: + /* handle BTN_0 specially for type detection */ + if (!priv->detect_accessory_type) + snd_soc_jack_report(priv->jack, + SND_JACK_BTN_0, btn_mask); + break; + default: + dev_err(component->dev, + "Unexpected button press result (%x)", btn_result); + break; + } + + return IRQ_HANDLED; +} + +static irqreturn_t pm8916_mbhc_switch_irq_handler(int irq, void *arg) +{ + struct pm8916_wcd_analog_priv *priv = arg; + struct snd_soc_component *component = priv->component; + bool ins = false; + + if (snd_soc_component_read32(component, CDC_A_MBHC_DET_CTL_1) & + CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_MASK) + ins = true; + + /* Set the detection type appropriately */ + snd_soc_component_update_bits(component, CDC_A_MBHC_DET_CTL_1, + CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_MASK, + (!ins << CDC_A_MBHC_DET_CTL_MECH_DET_TYPE_SHIFT)); + + + if (ins) { /* hs insertion */ + bool micbias_enabled = false; + + if (snd_soc_component_read32(component, CDC_A_MICB_2_EN) & + CDC_A_MICB_2_EN_ENABLE) + micbias_enabled = true; + + pm8916_mbhc_configure_bias(priv, micbias_enabled); + + /* + * if only a btn0 press event is receive just before + * insert event then its a 3 pole headphone else if + * both press and release event received then its + * a headset. + */ + if (priv->mbhc_btn0_released) + snd_soc_jack_report(priv->jack, + SND_JACK_HEADSET, hs_jack_mask); + else + snd_soc_jack_report(priv->jack, + SND_JACK_HEADPHONE, hs_jack_mask); + + priv->detect_accessory_type = false; + + } else { /* removal */ + snd_soc_jack_report(priv->jack, 0, hs_jack_mask); + priv->detect_accessory_type = true; + priv->mbhc_btn0_released = false; + } + + return IRQ_HANDLED; +} + +static struct snd_soc_dai_driver pm8916_wcd_analog_dai[] = { + [0] = { + .name = "pm8916_wcd_analog_pdm_rx", + .id = 0, + .playback = { + .stream_name = "PDM Playback", + .rates = MSM8916_WCD_ANALOG_RATES, + .formats = MSM8916_WCD_ANALOG_FORMATS, + .channels_min = 1, + .channels_max = 3, + }, + }, + [1] = { + .name = "pm8916_wcd_analog_pdm_tx", + .id = 1, + .capture = { + .stream_name = "PDM Capture", + .rates = MSM8916_WCD_ANALOG_RATES, + .formats = MSM8916_WCD_ANALOG_FORMATS, + .channels_min = 1, + .channels_max = 4, + }, + }, +}; + +static const struct snd_soc_component_driver pm8916_wcd_analog = { + .probe = pm8916_wcd_analog_probe, + .remove = pm8916_wcd_analog_remove, + .set_jack = pm8916_wcd_analog_set_jack, + .controls = pm8916_wcd_analog_snd_controls, + .num_controls = ARRAY_SIZE(pm8916_wcd_analog_snd_controls), + .dapm_widgets = pm8916_wcd_analog_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pm8916_wcd_analog_dapm_widgets), + .dapm_routes = pm8916_wcd_analog_audio_map, + .num_dapm_routes = ARRAY_SIZE(pm8916_wcd_analog_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int pm8916_wcd_analog_parse_dt(struct device *dev, + struct pm8916_wcd_analog_priv *priv) +{ + int rval; + + if (of_property_read_bool(dev->of_node, "qcom,micbias1-ext-cap")) + priv->micbias1_cap_mode = MICB_1_EN_EXT_BYP_CAP; + else + priv->micbias1_cap_mode = MICB_1_EN_NO_EXT_BYP_CAP; + + if (of_property_read_bool(dev->of_node, "qcom,micbias2-ext-cap")) + priv->micbias2_cap_mode = MICB_1_EN_EXT_BYP_CAP; + else + priv->micbias2_cap_mode = MICB_1_EN_NO_EXT_BYP_CAP; + + of_property_read_u32(dev->of_node, "qcom,micbias-lvl", + &priv->micbias_mv); + + if (of_property_read_bool(dev->of_node, + "qcom,hphl-jack-type-normally-open")) + priv->hphl_jack_type_normally_open = true; + else + priv->hphl_jack_type_normally_open = false; + + if (of_property_read_bool(dev->of_node, + "qcom,gnd-jack-type-normally-open")) + priv->gnd_jack_type_normally_open = true; + else + priv->gnd_jack_type_normally_open = false; + + priv->mbhc_btn_enabled = true; + rval = of_property_read_u32_array(dev->of_node, + "qcom,mbhc-vthreshold-low", + &priv->vref_btn_cs[0], + MBHC_MAX_BUTTONS); + if (rval < 0) { + priv->mbhc_btn_enabled = false; + } else { + rval = of_property_read_u32_array(dev->of_node, + "qcom,mbhc-vthreshold-high", + &priv->vref_btn_micb[0], + MBHC_MAX_BUTTONS); + if (rval < 0) + priv->mbhc_btn_enabled = false; + } + + if (!priv->mbhc_btn_enabled) + dev_err(dev, + "DT property missing, MBHC btn detection disabled\n"); + + + return 0; +} + +static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) +{ + struct pm8916_wcd_analog_priv *priv; + struct device *dev = &pdev->dev; + int ret, i, irq; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = pm8916_wcd_analog_parse_dt(dev, priv); + if (ret < 0) + return ret; + + priv->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(priv->mclk)) { + dev_err(dev, "failed to get mclk\n"); + return PTR_ERR(priv->mclk); + } + + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + priv->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) { + dev_err(dev, "Failed to get regulator supplies %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(dev, "failed to enable mclk %d\n", ret); + return ret; + } + + irq = platform_get_irq_byname(pdev, "mbhc_switch_int"); + if (irq < 0) { + dev_err(dev, "failed to get mbhc switch irq\n"); + return irq; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + pm8916_mbhc_switch_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "mbhc switch irq", priv); + if (ret) + dev_err(dev, "cannot request mbhc switch irq\n"); + + if (priv->mbhc_btn_enabled) { + irq = platform_get_irq_byname(pdev, "mbhc_but_press_det"); + if (irq < 0) { + dev_err(dev, "failed to get button press irq\n"); + return irq; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + mbhc_btn_press_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "mbhc btn press irq", priv); + if (ret) + dev_err(dev, "cannot request mbhc button press irq\n"); + + irq = platform_get_irq_byname(pdev, "mbhc_but_rel_det"); + if (irq < 0) { + dev_err(dev, "failed to get button release irq\n"); + return irq; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + mbhc_btn_release_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "mbhc btn release irq", priv); + if (ret) + dev_err(dev, "cannot request mbhc button release irq\n"); + + } + + dev_set_drvdata(dev, priv); + + return devm_snd_soc_register_component(dev, &pm8916_wcd_analog, + pm8916_wcd_analog_dai, + ARRAY_SIZE(pm8916_wcd_analog_dai)); +} + +static int pm8916_wcd_analog_spmi_remove(struct platform_device *pdev) +{ + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(priv->mclk); + + return 0; +} + +static const struct of_device_id pm8916_wcd_analog_spmi_match_table[] = { + { .compatible = "qcom,pm8916-wcd-analog-codec", }, + { } +}; + +MODULE_DEVICE_TABLE(of, pm8916_wcd_analog_spmi_match_table); + +static struct platform_driver pm8916_wcd_analog_spmi_driver = { + .driver = { + .name = "qcom,pm8916-wcd-spmi-codec", + .of_match_table = pm8916_wcd_analog_spmi_match_table, + }, + .probe = pm8916_wcd_analog_spmi_probe, + .remove = pm8916_wcd_analog_spmi_remove, +}; + +module_platform_driver(pm8916_wcd_analog_spmi_driver); + +MODULE_AUTHOR("Srinivas Kandagatla "); +MODULE_DESCRIPTION("PMIC PM8916 WCD Analog Codec driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/msm8916-wcd-digital.c b/sound/soc/codecs/msm8916-wcd-digital.c new file mode 100644 index 000000000..fa813ec32 --- /dev/null +++ b/sound/soc/codecs/msm8916-wcd-digital.c @@ -0,0 +1,971 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LPASS_CDC_CLK_RX_RESET_CTL (0x000) +#define LPASS_CDC_CLK_TX_RESET_B1_CTL (0x004) +#define CLK_RX_RESET_B1_CTL_TX1_RESET_MASK BIT(0) +#define CLK_RX_RESET_B1_CTL_TX2_RESET_MASK BIT(1) +#define LPASS_CDC_CLK_DMIC_B1_CTL (0x008) +#define DMIC_B1_CTL_DMIC0_CLK_SEL_MASK GENMASK(3, 1) +#define DMIC_B1_CTL_DMIC0_CLK_SEL_DIV2 (0x0 << 1) +#define DMIC_B1_CTL_DMIC0_CLK_SEL_DIV3 (0x1 << 1) +#define DMIC_B1_CTL_DMIC0_CLK_SEL_DIV4 (0x2 << 1) +#define DMIC_B1_CTL_DMIC0_CLK_SEL_DIV6 (0x3 << 1) +#define DMIC_B1_CTL_DMIC0_CLK_SEL_DIV16 (0x4 << 1) +#define DMIC_B1_CTL_DMIC0_CLK_EN_MASK BIT(0) +#define DMIC_B1_CTL_DMIC0_CLK_EN_ENABLE BIT(0) + +#define LPASS_CDC_CLK_RX_I2S_CTL (0x00C) +#define RX_I2S_CTL_RX_I2S_MODE_MASK BIT(5) +#define RX_I2S_CTL_RX_I2S_MODE_16 BIT(5) +#define RX_I2S_CTL_RX_I2S_MODE_32 0 +#define RX_I2S_CTL_RX_I2S_FS_RATE_MASK GENMASK(2, 0) +#define RX_I2S_CTL_RX_I2S_FS_RATE_F_8_KHZ 0x0 +#define RX_I2S_CTL_RX_I2S_FS_RATE_F_16_KHZ 0x1 +#define RX_I2S_CTL_RX_I2S_FS_RATE_F_32_KHZ 0x2 +#define RX_I2S_CTL_RX_I2S_FS_RATE_F_48_KHZ 0x3 +#define RX_I2S_CTL_RX_I2S_FS_RATE_F_96_KHZ 0x4 +#define RX_I2S_CTL_RX_I2S_FS_RATE_F_192_KHZ 0x5 +#define LPASS_CDC_CLK_TX_I2S_CTL (0x010) +#define TX_I2S_CTL_TX_I2S_MODE_MASK BIT(5) +#define TX_I2S_CTL_TX_I2S_MODE_16 BIT(5) +#define TX_I2S_CTL_TX_I2S_MODE_32 0 +#define TX_I2S_CTL_TX_I2S_FS_RATE_MASK GENMASK(2, 0) +#define TX_I2S_CTL_TX_I2S_FS_RATE_F_8_KHZ 0x0 +#define TX_I2S_CTL_TX_I2S_FS_RATE_F_16_KHZ 0x1 +#define TX_I2S_CTL_TX_I2S_FS_RATE_F_32_KHZ 0x2 +#define TX_I2S_CTL_TX_I2S_FS_RATE_F_48_KHZ 0x3 +#define TX_I2S_CTL_TX_I2S_FS_RATE_F_96_KHZ 0x4 +#define TX_I2S_CTL_TX_I2S_FS_RATE_F_192_KHZ 0x5 + +#define LPASS_CDC_CLK_OTHR_RESET_B1_CTL (0x014) +#define LPASS_CDC_CLK_TX_CLK_EN_B1_CTL (0x018) +#define LPASS_CDC_CLK_OTHR_CTL (0x01C) +#define LPASS_CDC_CLK_RX_B1_CTL (0x020) +#define LPASS_CDC_CLK_MCLK_CTL (0x024) +#define MCLK_CTL_MCLK_EN_MASK BIT(0) +#define MCLK_CTL_MCLK_EN_ENABLE BIT(0) +#define MCLK_CTL_MCLK_EN_DISABLE 0 +#define LPASS_CDC_CLK_PDM_CTL (0x028) +#define LPASS_CDC_CLK_PDM_CTL_PDM_EN_MASK BIT(0) +#define LPASS_CDC_CLK_PDM_CTL_PDM_EN BIT(0) +#define LPASS_CDC_CLK_PDM_CTL_PDM_CLK_SEL_MASK BIT(1) +#define LPASS_CDC_CLK_PDM_CTL_PDM_CLK_SEL_FB BIT(1) +#define LPASS_CDC_CLK_PDM_CTL_PDM_CLK_PDM_CLK 0 + +#define LPASS_CDC_CLK_SD_CTL (0x02C) +#define LPASS_CDC_RX1_B1_CTL (0x040) +#define LPASS_CDC_RX2_B1_CTL (0x060) +#define LPASS_CDC_RX3_B1_CTL (0x080) +#define LPASS_CDC_RX1_B2_CTL (0x044) +#define LPASS_CDC_RX2_B2_CTL (0x064) +#define LPASS_CDC_RX3_B2_CTL (0x084) +#define LPASS_CDC_RX1_B3_CTL (0x048) +#define LPASS_CDC_RX2_B3_CTL (0x068) +#define LPASS_CDC_RX3_B3_CTL (0x088) +#define LPASS_CDC_RX1_B4_CTL (0x04C) +#define LPASS_CDC_RX2_B4_CTL (0x06C) +#define LPASS_CDC_RX3_B4_CTL (0x08C) +#define LPASS_CDC_RX1_B5_CTL (0x050) +#define LPASS_CDC_RX2_B5_CTL (0x070) +#define LPASS_CDC_RX3_B5_CTL (0x090) +#define LPASS_CDC_RX1_B6_CTL (0x054) +#define RXn_B6_CTL_MUTE_MASK BIT(0) +#define RXn_B6_CTL_MUTE_ENABLE BIT(0) +#define RXn_B6_CTL_MUTE_DISABLE 0 +#define LPASS_CDC_RX2_B6_CTL (0x074) +#define LPASS_CDC_RX3_B6_CTL (0x094) +#define LPASS_CDC_RX1_VOL_CTL_B1_CTL (0x058) +#define LPASS_CDC_RX2_VOL_CTL_B1_CTL (0x078) +#define LPASS_CDC_RX3_VOL_CTL_B1_CTL (0x098) +#define LPASS_CDC_RX1_VOL_CTL_B2_CTL (0x05C) +#define LPASS_CDC_RX2_VOL_CTL_B2_CTL (0x07C) +#define LPASS_CDC_RX3_VOL_CTL_B2_CTL (0x09C) +#define LPASS_CDC_TOP_GAIN_UPDATE (0x0A0) +#define LPASS_CDC_TOP_CTL (0x0A4) +#define TOP_CTL_DIG_MCLK_FREQ_MASK BIT(0) +#define TOP_CTL_DIG_MCLK_FREQ_F_12_288MHZ 0 +#define TOP_CTL_DIG_MCLK_FREQ_F_9_6MHZ BIT(0) + +#define LPASS_CDC_DEBUG_DESER1_CTL (0x0E0) +#define LPASS_CDC_DEBUG_DESER2_CTL (0x0E4) +#define LPASS_CDC_DEBUG_B1_CTL_CFG (0x0E8) +#define LPASS_CDC_DEBUG_B2_CTL_CFG (0x0EC) +#define LPASS_CDC_DEBUG_B3_CTL_CFG (0x0F0) +#define LPASS_CDC_IIR1_GAIN_B1_CTL (0x100) +#define LPASS_CDC_IIR2_GAIN_B1_CTL (0x140) +#define LPASS_CDC_IIR1_GAIN_B2_CTL (0x104) +#define LPASS_CDC_IIR2_GAIN_B2_CTL (0x144) +#define LPASS_CDC_IIR1_GAIN_B3_CTL (0x108) +#define LPASS_CDC_IIR2_GAIN_B3_CTL (0x148) +#define LPASS_CDC_IIR1_GAIN_B4_CTL (0x10C) +#define LPASS_CDC_IIR2_GAIN_B4_CTL (0x14C) +#define LPASS_CDC_IIR1_GAIN_B5_CTL (0x110) +#define LPASS_CDC_IIR2_GAIN_B5_CTL (0x150) +#define LPASS_CDC_IIR1_GAIN_B6_CTL (0x114) +#define LPASS_CDC_IIR2_GAIN_B6_CTL (0x154) +#define LPASS_CDC_IIR1_GAIN_B7_CTL (0x118) +#define LPASS_CDC_IIR2_GAIN_B7_CTL (0x158) +#define LPASS_CDC_IIR1_GAIN_B8_CTL (0x11C) +#define LPASS_CDC_IIR2_GAIN_B8_CTL (0x15C) +#define LPASS_CDC_IIR1_CTL (0x120) +#define LPASS_CDC_IIR2_CTL (0x160) +#define LPASS_CDC_IIR1_GAIN_TIMER_CTL (0x124) +#define LPASS_CDC_IIR2_GAIN_TIMER_CTL (0x164) +#define LPASS_CDC_IIR1_COEF_B1_CTL (0x128) +#define LPASS_CDC_IIR2_COEF_B1_CTL (0x168) +#define LPASS_CDC_IIR1_COEF_B2_CTL (0x12C) +#define LPASS_CDC_IIR2_COEF_B2_CTL (0x16C) +#define LPASS_CDC_CONN_RX1_B1_CTL (0x180) +#define LPASS_CDC_CONN_RX1_B2_CTL (0x184) +#define LPASS_CDC_CONN_RX1_B3_CTL (0x188) +#define LPASS_CDC_CONN_RX2_B1_CTL (0x18C) +#define LPASS_CDC_CONN_RX2_B2_CTL (0x190) +#define LPASS_CDC_CONN_RX2_B3_CTL (0x194) +#define LPASS_CDC_CONN_RX3_B1_CTL (0x198) +#define LPASS_CDC_CONN_RX3_B2_CTL (0x19C) +#define LPASS_CDC_CONN_TX_B1_CTL (0x1A0) +#define LPASS_CDC_CONN_EQ1_B1_CTL (0x1A8) +#define LPASS_CDC_CONN_EQ1_B2_CTL (0x1AC) +#define LPASS_CDC_CONN_EQ1_B3_CTL (0x1B0) +#define LPASS_CDC_CONN_EQ1_B4_CTL (0x1B4) +#define LPASS_CDC_CONN_EQ2_B1_CTL (0x1B8) +#define LPASS_CDC_CONN_EQ2_B2_CTL (0x1BC) +#define LPASS_CDC_CONN_EQ2_B3_CTL (0x1C0) +#define LPASS_CDC_CONN_EQ2_B4_CTL (0x1C4) +#define LPASS_CDC_CONN_TX_I2S_SD1_CTL (0x1C8) +#define LPASS_CDC_TX1_VOL_CTL_TIMER (0x280) +#define LPASS_CDC_TX2_VOL_CTL_TIMER (0x2A0) +#define LPASS_CDC_TX1_VOL_CTL_GAIN (0x284) +#define LPASS_CDC_TX2_VOL_CTL_GAIN (0x2A4) +#define LPASS_CDC_TX1_VOL_CTL_CFG (0x288) +#define TX_VOL_CTL_CFG_MUTE_EN_MASK BIT(0) +#define TX_VOL_CTL_CFG_MUTE_EN_ENABLE BIT(0) + +#define LPASS_CDC_TX2_VOL_CTL_CFG (0x2A8) +#define LPASS_CDC_TX1_MUX_CTL (0x28C) +#define TX_MUX_CTL_CUT_OFF_FREQ_MASK GENMASK(5, 4) +#define TX_MUX_CTL_CUT_OFF_FREQ_SHIFT 4 +#define TX_MUX_CTL_CF_NEG_3DB_4HZ (0x0 << 4) +#define TX_MUX_CTL_CF_NEG_3DB_75HZ (0x1 << 4) +#define TX_MUX_CTL_CF_NEG_3DB_150HZ (0x2 << 4) +#define TX_MUX_CTL_HPF_BP_SEL_MASK BIT(3) +#define TX_MUX_CTL_HPF_BP_SEL_BYPASS BIT(3) +#define TX_MUX_CTL_HPF_BP_SEL_NO_BYPASS 0 + +#define LPASS_CDC_TX2_MUX_CTL (0x2AC) +#define LPASS_CDC_TX1_CLK_FS_CTL (0x290) +#define LPASS_CDC_TX2_CLK_FS_CTL (0x2B0) +#define LPASS_CDC_TX1_DMIC_CTL (0x294) +#define LPASS_CDC_TX2_DMIC_CTL (0x2B4) +#define TXN_DMIC_CTL_CLK_SEL_MASK GENMASK(2, 0) +#define TXN_DMIC_CTL_CLK_SEL_DIV2 0x0 +#define TXN_DMIC_CTL_CLK_SEL_DIV3 0x1 +#define TXN_DMIC_CTL_CLK_SEL_DIV4 0x2 +#define TXN_DMIC_CTL_CLK_SEL_DIV6 0x3 +#define TXN_DMIC_CTL_CLK_SEL_DIV16 0x4 + +#define MSM8916_WCD_DIGITAL_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_48000) +#define MSM8916_WCD_DIGITAL_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +struct msm8916_wcd_digital_priv { + struct clk *ahbclk, *mclk; +}; + +static const unsigned long rx_gain_reg[] = { + LPASS_CDC_RX1_VOL_CTL_B2_CTL, + LPASS_CDC_RX2_VOL_CTL_B2_CTL, + LPASS_CDC_RX3_VOL_CTL_B2_CTL, +}; + +static const unsigned long tx_gain_reg[] = { + LPASS_CDC_TX1_VOL_CTL_GAIN, + LPASS_CDC_TX2_VOL_CTL_GAIN, +}; + +static const char *const rx_mix1_text[] = { + "ZERO", "IIR1", "IIR2", "RX1", "RX2", "RX3" +}; + +static const char *const dec_mux_text[] = { + "ZERO", "ADC1", "ADC2", "ADC3", "DMIC1", "DMIC2" +}; + +static const char *const cic_mux_text[] = { "AMIC", "DMIC" }; +static const char *const rx_mix2_text[] = { "ZERO", "IIR1", "IIR2" }; +static const char *const adc2_mux_text[] = { "ZERO", "INP2", "INP3" }; + +/* RX1 MIX1 */ +static const struct soc_enum rx_mix1_inp_enum[] = { + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX1_B1_CTL, 0, 6, rx_mix1_text), + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX1_B1_CTL, 3, 6, rx_mix1_text), + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX1_B2_CTL, 0, 6, rx_mix1_text), +}; + +/* RX1 MIX2 */ +static const struct soc_enum rx_mix2_inp1_chain_enum = SOC_ENUM_SINGLE( + LPASS_CDC_CONN_RX1_B3_CTL, 0, 3, rx_mix2_text); + +/* RX2 MIX1 */ +static const struct soc_enum rx2_mix1_inp_enum[] = { + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX2_B1_CTL, 0, 6, rx_mix1_text), + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX2_B1_CTL, 3, 6, rx_mix1_text), + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX2_B2_CTL, 0, 6, rx_mix1_text), +}; + +/* RX2 MIX2 */ +static const struct soc_enum rx2_mix2_inp1_chain_enum = SOC_ENUM_SINGLE( + LPASS_CDC_CONN_RX2_B3_CTL, 0, 3, rx_mix2_text); + +/* RX3 MIX1 */ +static const struct soc_enum rx3_mix1_inp_enum[] = { + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX3_B1_CTL, 0, 6, rx_mix1_text), + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX3_B1_CTL, 3, 6, rx_mix1_text), + SOC_ENUM_SINGLE(LPASS_CDC_CONN_RX3_B2_CTL, 0, 6, rx_mix1_text), +}; + +/* DEC */ +static const struct soc_enum dec1_mux_enum = SOC_ENUM_SINGLE( + LPASS_CDC_CONN_TX_B1_CTL, 0, 6, dec_mux_text); +static const struct soc_enum dec2_mux_enum = SOC_ENUM_SINGLE( + LPASS_CDC_CONN_TX_B1_CTL, 3, 6, dec_mux_text); + +/* CIC */ +static const struct soc_enum cic1_mux_enum = SOC_ENUM_SINGLE( + LPASS_CDC_TX1_MUX_CTL, 0, 2, cic_mux_text); +static const struct soc_enum cic2_mux_enum = SOC_ENUM_SINGLE( + LPASS_CDC_TX2_MUX_CTL, 0, 2, cic_mux_text); + +/* RDAC2 MUX */ +static const struct snd_kcontrol_new dec1_mux = SOC_DAPM_ENUM( + "DEC1 MUX Mux", dec1_mux_enum); +static const struct snd_kcontrol_new dec2_mux = SOC_DAPM_ENUM( + "DEC2 MUX Mux", dec2_mux_enum); +static const struct snd_kcontrol_new cic1_mux = SOC_DAPM_ENUM( + "CIC1 MUX Mux", cic1_mux_enum); +static const struct snd_kcontrol_new cic2_mux = SOC_DAPM_ENUM( + "CIC2 MUX Mux", cic2_mux_enum); +static const struct snd_kcontrol_new rx_mix1_inp1_mux = SOC_DAPM_ENUM( + "RX1 MIX1 INP1 Mux", rx_mix1_inp_enum[0]); +static const struct snd_kcontrol_new rx_mix1_inp2_mux = SOC_DAPM_ENUM( + "RX1 MIX1 INP2 Mux", rx_mix1_inp_enum[1]); +static const struct snd_kcontrol_new rx_mix1_inp3_mux = SOC_DAPM_ENUM( + "RX1 MIX1 INP3 Mux", rx_mix1_inp_enum[2]); +static const struct snd_kcontrol_new rx2_mix1_inp1_mux = SOC_DAPM_ENUM( + "RX2 MIX1 INP1 Mux", rx2_mix1_inp_enum[0]); +static const struct snd_kcontrol_new rx2_mix1_inp2_mux = SOC_DAPM_ENUM( + "RX2 MIX1 INP2 Mux", rx2_mix1_inp_enum[1]); +static const struct snd_kcontrol_new rx2_mix1_inp3_mux = SOC_DAPM_ENUM( + "RX2 MIX1 INP3 Mux", rx2_mix1_inp_enum[2]); +static const struct snd_kcontrol_new rx3_mix1_inp1_mux = SOC_DAPM_ENUM( + "RX3 MIX1 INP1 Mux", rx3_mix1_inp_enum[0]); +static const struct snd_kcontrol_new rx3_mix1_inp2_mux = SOC_DAPM_ENUM( + "RX3 MIX1 INP2 Mux", rx3_mix1_inp_enum[1]); +static const struct snd_kcontrol_new rx3_mix1_inp3_mux = SOC_DAPM_ENUM( + "RX3 MIX1 INP3 Mux", rx3_mix1_inp_enum[2]); + +/* Digital Gain control -38.4 dB to +38.4 dB in 0.3 dB steps */ +static const DECLARE_TLV_DB_SCALE(digital_gain, -3840, 30, 0); + +/* Cutoff Freq for High Pass Filter at -3dB */ +static const char * const hpf_cutoff_text[] = { + "4Hz", "75Hz", "150Hz", +}; + +static SOC_ENUM_SINGLE_DECL(tx1_hpf_cutoff_enum, LPASS_CDC_TX1_MUX_CTL, 4, + hpf_cutoff_text); +static SOC_ENUM_SINGLE_DECL(tx2_hpf_cutoff_enum, LPASS_CDC_TX2_MUX_CTL, 4, + hpf_cutoff_text); + +/* cut off for dc blocker inside rx chain */ +static const char * const dc_blocker_cutoff_text[] = { + "4Hz", "75Hz", "150Hz", +}; + +static SOC_ENUM_SINGLE_DECL(rx1_dcb_cutoff_enum, LPASS_CDC_RX1_B4_CTL, 0, + dc_blocker_cutoff_text); +static SOC_ENUM_SINGLE_DECL(rx2_dcb_cutoff_enum, LPASS_CDC_RX2_B4_CTL, 0, + dc_blocker_cutoff_text); +static SOC_ENUM_SINGLE_DECL(rx3_dcb_cutoff_enum, LPASS_CDC_RX3_B4_CTL, 0, + dc_blocker_cutoff_text); + +static const struct snd_kcontrol_new msm8916_wcd_digital_snd_controls[] = { + SOC_SINGLE_S8_TLV("RX1 Digital Volume", LPASS_CDC_RX1_VOL_CTL_B2_CTL, + -128, 127, digital_gain), + SOC_SINGLE_S8_TLV("RX2 Digital Volume", LPASS_CDC_RX2_VOL_CTL_B2_CTL, + -128, 127, digital_gain), + SOC_SINGLE_S8_TLV("RX3 Digital Volume", LPASS_CDC_RX3_VOL_CTL_B2_CTL, + -128, 127, digital_gain), + SOC_SINGLE_S8_TLV("TX1 Digital Volume", LPASS_CDC_TX1_VOL_CTL_GAIN, + -128, 127, digital_gain), + SOC_SINGLE_S8_TLV("TX2 Digital Volume", LPASS_CDC_TX2_VOL_CTL_GAIN, + -128, 127, digital_gain), + SOC_ENUM("TX1 HPF Cutoff", tx1_hpf_cutoff_enum), + SOC_ENUM("TX2 HPF Cutoff", tx2_hpf_cutoff_enum), + SOC_SINGLE("TX1 HPF Switch", LPASS_CDC_TX1_MUX_CTL, 3, 1, 0), + SOC_SINGLE("TX2 HPF Switch", LPASS_CDC_TX2_MUX_CTL, 3, 1, 0), + SOC_ENUM("RX1 DCB Cutoff", rx1_dcb_cutoff_enum), + SOC_ENUM("RX2 DCB Cutoff", rx2_dcb_cutoff_enum), + SOC_ENUM("RX3 DCB Cutoff", rx3_dcb_cutoff_enum), + SOC_SINGLE("RX1 DCB Switch", LPASS_CDC_RX1_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX2 DCB Switch", LPASS_CDC_RX2_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX3 DCB Switch", LPASS_CDC_RX3_B5_CTL, 2, 1, 0), + SOC_SINGLE("RX1 Mute Switch", LPASS_CDC_RX1_B6_CTL, 0, 1, 0), + SOC_SINGLE("RX2 Mute Switch", LPASS_CDC_RX2_B6_CTL, 0, 1, 0), + SOC_SINGLE("RX3 Mute Switch", LPASS_CDC_RX3_B6_CTL, 0, 1, 0), +}; + +static int msm8916_wcd_digital_enable_interpolator( + struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* apply the digital gain after the interpolator is enabled */ + usleep_range(10000, 10100); + snd_soc_component_write(component, rx_gain_reg[w->shift], + snd_soc_component_read32(component, rx_gain_reg[w->shift])); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, LPASS_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 1 << w->shift); + snd_soc_component_update_bits(component, LPASS_CDC_CLK_RX_RESET_CTL, + 1 << w->shift, 0x0); + break; + } + return 0; +} + +static int msm8916_wcd_digital_enable_dec(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int decimator = w->shift + 1; + u16 dec_reset_reg, tx_vol_ctl_reg, tx_mux_ctl_reg; + u8 dec_hpf_cut_of_freq; + + dec_reset_reg = LPASS_CDC_CLK_TX_RESET_B1_CTL; + tx_vol_ctl_reg = LPASS_CDC_TX1_VOL_CTL_CFG + 32 * (decimator - 1); + tx_mux_ctl_reg = LPASS_CDC_TX1_MUX_CTL + 32 * (decimator - 1); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable TX digital mute */ + snd_soc_component_update_bits(component, tx_vol_ctl_reg, + TX_VOL_CTL_CFG_MUTE_EN_MASK, + TX_VOL_CTL_CFG_MUTE_EN_ENABLE); + dec_hpf_cut_of_freq = snd_soc_component_read32(component, tx_mux_ctl_reg) & + TX_MUX_CTL_CUT_OFF_FREQ_MASK; + dec_hpf_cut_of_freq >>= TX_MUX_CTL_CUT_OFF_FREQ_SHIFT; + if (dec_hpf_cut_of_freq != TX_MUX_CTL_CF_NEG_3DB_150HZ) { + /* set cut of freq to CF_MIN_3DB_150HZ (0x1) */ + snd_soc_component_update_bits(component, tx_mux_ctl_reg, + TX_MUX_CTL_CUT_OFF_FREQ_MASK, + TX_MUX_CTL_CF_NEG_3DB_150HZ); + } + break; + case SND_SOC_DAPM_POST_PMU: + /* enable HPF */ + snd_soc_component_update_bits(component, tx_mux_ctl_reg, + TX_MUX_CTL_HPF_BP_SEL_MASK, + TX_MUX_CTL_HPF_BP_SEL_NO_BYPASS); + /* apply the digital gain after the decimator is enabled */ + snd_soc_component_write(component, tx_gain_reg[w->shift], + snd_soc_component_read32(component, tx_gain_reg[w->shift])); + snd_soc_component_update_bits(component, tx_vol_ctl_reg, + TX_VOL_CTL_CFG_MUTE_EN_MASK, 0); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, tx_vol_ctl_reg, + TX_VOL_CTL_CFG_MUTE_EN_MASK, + TX_VOL_CTL_CFG_MUTE_EN_ENABLE); + snd_soc_component_update_bits(component, tx_mux_ctl_reg, + TX_MUX_CTL_HPF_BP_SEL_MASK, + TX_MUX_CTL_HPF_BP_SEL_BYPASS); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, dec_reset_reg, 1 << w->shift, + 1 << w->shift); + snd_soc_component_update_bits(component, dec_reset_reg, 1 << w->shift, 0x0); + snd_soc_component_update_bits(component, tx_mux_ctl_reg, + TX_MUX_CTL_HPF_BP_SEL_MASK, + TX_MUX_CTL_HPF_BP_SEL_BYPASS); + snd_soc_component_update_bits(component, tx_vol_ctl_reg, + TX_VOL_CTL_CFG_MUTE_EN_MASK, 0); + break; + } + + return 0; +} + +static int msm8916_wcd_digital_enable_dmic(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int dmic; + int ret; + /* get dmic number out of widget name */ + char *dmic_num = strpbrk(w->name, "12"); + + if (dmic_num == NULL) { + dev_err(component->dev, "Invalid DMIC\n"); + return -EINVAL; + } + ret = kstrtouint(dmic_num, 10, &dmic); + if (ret < 0 || dmic > 2) { + dev_err(component->dev, "Invalid DMIC line on the component\n"); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, LPASS_CDC_CLK_DMIC_B1_CTL, + DMIC_B1_CTL_DMIC0_CLK_SEL_MASK, + DMIC_B1_CTL_DMIC0_CLK_SEL_DIV3); + switch (dmic) { + case 1: + snd_soc_component_update_bits(component, LPASS_CDC_TX1_DMIC_CTL, + TXN_DMIC_CTL_CLK_SEL_MASK, + TXN_DMIC_CTL_CLK_SEL_DIV3); + break; + case 2: + snd_soc_component_update_bits(component, LPASS_CDC_TX2_DMIC_CTL, + TXN_DMIC_CTL_CLK_SEL_MASK, + TXN_DMIC_CTL_CLK_SEL_DIV3); + break; + } + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget msm8916_wcd_digital_dapm_widgets[] = { + /*RX stuff */ + SND_SOC_DAPM_AIF_IN("I2S RX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("I2S RX2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("I2S RX3", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_OUTPUT("PDM_RX1"), + SND_SOC_DAPM_OUTPUT("PDM_RX2"), + SND_SOC_DAPM_OUTPUT("PDM_RX3"), + + SND_SOC_DAPM_INPUT("LPASS_PDM_TX"), + + SND_SOC_DAPM_MIXER("RX1 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX2 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX3 MIX1", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Interpolator */ + SND_SOC_DAPM_MIXER_E("RX1 INT", LPASS_CDC_CLK_RX_B1_CTL, 0, 0, NULL, + 0, msm8916_wcd_digital_enable_interpolator, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER_E("RX2 INT", LPASS_CDC_CLK_RX_B1_CTL, 1, 0, NULL, + 0, msm8916_wcd_digital_enable_interpolator, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER_E("RX3 INT", LPASS_CDC_CLK_RX_B1_CTL, 2, 0, NULL, + 0, msm8916_wcd_digital_enable_interpolator, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("RX1 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX1 MIX1 INP3", SND_SOC_NOPM, 0, 0, + &rx_mix1_inp3_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX2 MIX1 INP3", SND_SOC_NOPM, 0, 0, + &rx2_mix1_inp3_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP1", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp1_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP2", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp2_mux), + SND_SOC_DAPM_MUX("RX3 MIX1 INP3", SND_SOC_NOPM, 0, 0, + &rx3_mix1_inp3_mux), + + SND_SOC_DAPM_MUX("CIC1 MUX", SND_SOC_NOPM, 0, 0, &cic1_mux), + SND_SOC_DAPM_MUX("CIC2 MUX", SND_SOC_NOPM, 0, 0, &cic2_mux), + /* TX */ + SND_SOC_DAPM_MIXER("ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX_E("DEC1 MUX", LPASS_CDC_CLK_TX_CLK_EN_B1_CTL, 0, 0, + &dec1_mux, msm8916_wcd_digital_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX_E("DEC2 MUX", LPASS_CDC_CLK_TX_CLK_EN_B1_CTL, 1, 0, + &dec2_mux, msm8916_wcd_digital_enable_dec, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("I2S TX1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("I2S TX2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("I2S TX3", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* Digital Mic Inputs */ + SND_SOC_DAPM_ADC_E("DMIC1", NULL, SND_SOC_NOPM, 0, 0, + msm8916_wcd_digital_enable_dmic, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("DMIC2", NULL, SND_SOC_NOPM, 0, 0, + msm8916_wcd_digital_enable_dmic, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("DMIC_CLK", LPASS_CDC_CLK_DMIC_B1_CTL, 0, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("RX_I2S_CLK", LPASS_CDC_CLK_RX_I2S_CTL, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("TX_I2S_CLK", LPASS_CDC_CLK_TX_I2S_CTL, 4, 0, + NULL, 0), + + SND_SOC_DAPM_SUPPLY("MCLK", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PDM_CLK", LPASS_CDC_CLK_PDM_CTL, 0, 0, NULL, 0), + /* Connectivity Clock */ + SND_SOC_DAPM_SUPPLY_S("CDC_CONN", -2, LPASS_CDC_CLK_OTHR_CTL, 2, 0, + NULL, 0), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), + +}; + +static int msm8916_wcd_digital_get_clks(struct platform_device *pdev, + struct msm8916_wcd_digital_priv *priv) +{ + struct device *dev = &pdev->dev; + + priv->ahbclk = devm_clk_get(dev, "ahbix-clk"); + if (IS_ERR(priv->ahbclk)) { + dev_err(dev, "failed to get ahbix clk\n"); + return PTR_ERR(priv->ahbclk); + } + + priv->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(priv->mclk)) { + dev_err(dev, "failed to get mclk\n"); + return PTR_ERR(priv->mclk); + } + + return 0; +} + +static int msm8916_wcd_digital_component_probe(struct snd_soc_component *component) +{ + struct msm8916_wcd_digital_priv *priv = dev_get_drvdata(component->dev); + + snd_soc_component_set_drvdata(component, priv); + + return 0; +} + +static int msm8916_wcd_digital_component_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, + unsigned int freq, int dir) +{ + struct msm8916_wcd_digital_priv *p = dev_get_drvdata(component->dev); + + return clk_set_rate(p->mclk, freq); +} + +static int msm8916_wcd_digital_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + u8 tx_fs_rate; + u8 rx_fs_rate; + + switch (params_rate(params)) { + case 8000: + tx_fs_rate = TX_I2S_CTL_TX_I2S_FS_RATE_F_8_KHZ; + rx_fs_rate = RX_I2S_CTL_RX_I2S_FS_RATE_F_8_KHZ; + break; + case 16000: + tx_fs_rate = TX_I2S_CTL_TX_I2S_FS_RATE_F_16_KHZ; + rx_fs_rate = RX_I2S_CTL_RX_I2S_FS_RATE_F_16_KHZ; + break; + case 32000: + tx_fs_rate = TX_I2S_CTL_TX_I2S_FS_RATE_F_32_KHZ; + rx_fs_rate = RX_I2S_CTL_RX_I2S_FS_RATE_F_32_KHZ; + break; + case 48000: + tx_fs_rate = TX_I2S_CTL_TX_I2S_FS_RATE_F_48_KHZ; + rx_fs_rate = RX_I2S_CTL_RX_I2S_FS_RATE_F_48_KHZ; + break; + default: + dev_err(dai->component->dev, "Invalid sampling rate %d\n", + params_rate(params)); + return -EINVAL; + } + + switch (substream->stream) { + case SNDRV_PCM_STREAM_CAPTURE: + snd_soc_component_update_bits(dai->component, LPASS_CDC_CLK_TX_I2S_CTL, + TX_I2S_CTL_TX_I2S_FS_RATE_MASK, tx_fs_rate); + break; + case SNDRV_PCM_STREAM_PLAYBACK: + snd_soc_component_update_bits(dai->component, LPASS_CDC_CLK_RX_I2S_CTL, + RX_I2S_CTL_RX_I2S_FS_RATE_MASK, rx_fs_rate); + break; + default: + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_component_update_bits(dai->component, LPASS_CDC_CLK_TX_I2S_CTL, + TX_I2S_CTL_TX_I2S_MODE_MASK, + TX_I2S_CTL_TX_I2S_MODE_16); + snd_soc_component_update_bits(dai->component, LPASS_CDC_CLK_RX_I2S_CTL, + RX_I2S_CTL_RX_I2S_MODE_MASK, + RX_I2S_CTL_RX_I2S_MODE_16); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + snd_soc_component_update_bits(dai->component, LPASS_CDC_CLK_TX_I2S_CTL, + TX_I2S_CTL_TX_I2S_MODE_MASK, + TX_I2S_CTL_TX_I2S_MODE_32); + snd_soc_component_update_bits(dai->component, LPASS_CDC_CLK_RX_I2S_CTL, + RX_I2S_CTL_RX_I2S_MODE_MASK, + RX_I2S_CTL_RX_I2S_MODE_32); + break; + default: + dev_err(dai->dev, "%s: wrong format selected\n", __func__); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dapm_route msm8916_wcd_digital_audio_map[] = { + + {"I2S RX1", NULL, "AIF1 Playback"}, + {"I2S RX2", NULL, "AIF1 Playback"}, + {"I2S RX3", NULL, "AIF1 Playback"}, + + {"AIF1 Capture", NULL, "I2S TX1"}, + {"AIF1 Capture", NULL, "I2S TX2"}, + {"AIF1 Capture", NULL, "I2S TX3"}, + + {"CIC1 MUX", "DMIC", "DEC1 MUX"}, + {"CIC1 MUX", "AMIC", "DEC1 MUX"}, + {"CIC2 MUX", "DMIC", "DEC2 MUX"}, + {"CIC2 MUX", "AMIC", "DEC2 MUX"}, + + /* Decimator Inputs */ + {"DEC1 MUX", "DMIC1", "DMIC1"}, + {"DEC1 MUX", "DMIC2", "DMIC2"}, + {"DEC1 MUX", "ADC1", "ADC1"}, + {"DEC1 MUX", "ADC2", "ADC2"}, + {"DEC1 MUX", "ADC3", "ADC3"}, + {"DEC1 MUX", NULL, "CDC_CONN"}, + + {"DEC2 MUX", "DMIC1", "DMIC1"}, + {"DEC2 MUX", "DMIC2", "DMIC2"}, + {"DEC2 MUX", "ADC1", "ADC1"}, + {"DEC2 MUX", "ADC2", "ADC2"}, + {"DEC2 MUX", "ADC3", "ADC3"}, + {"DEC2 MUX", NULL, "CDC_CONN"}, + + {"DMIC1", NULL, "DMIC_CLK"}, + {"DMIC2", NULL, "DMIC_CLK"}, + + {"I2S TX1", NULL, "CIC1 MUX"}, + {"I2S TX2", NULL, "CIC2 MUX"}, + + {"I2S TX1", NULL, "TX_I2S_CLK"}, + {"I2S TX2", NULL, "TX_I2S_CLK"}, + + {"TX_I2S_CLK", NULL, "MCLK"}, + {"TX_I2S_CLK", NULL, "PDM_CLK"}, + + {"ADC1", NULL, "LPASS_PDM_TX"}, + {"ADC2", NULL, "LPASS_PDM_TX"}, + {"ADC3", NULL, "LPASS_PDM_TX"}, + + {"I2S RX1", NULL, "RX_I2S_CLK"}, + {"I2S RX2", NULL, "RX_I2S_CLK"}, + {"I2S RX3", NULL, "RX_I2S_CLK"}, + + {"RX_I2S_CLK", NULL, "PDM_CLK"}, + {"RX_I2S_CLK", NULL, "MCLK"}, + {"RX_I2S_CLK", NULL, "CDC_CONN"}, + + /* RX1 PATH.. */ + {"PDM_RX1", NULL, "RX1 INT"}, + {"RX1 INT", NULL, "RX1 MIX1"}, + + {"RX1 MIX1", NULL, "RX1 MIX1 INP1"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP2"}, + {"RX1 MIX1", NULL, "RX1 MIX1 INP3"}, + + {"RX1 MIX1 INP1", "RX1", "I2S RX1"}, + {"RX1 MIX1 INP1", "RX2", "I2S RX2"}, + {"RX1 MIX1 INP1", "RX3", "I2S RX3"}, + + {"RX1 MIX1 INP2", "RX1", "I2S RX1"}, + {"RX1 MIX1 INP2", "RX2", "I2S RX2"}, + {"RX1 MIX1 INP2", "RX3", "I2S RX3"}, + + {"RX1 MIX1 INP3", "RX1", "I2S RX1"}, + {"RX1 MIX1 INP3", "RX2", "I2S RX2"}, + {"RX1 MIX1 INP3", "RX3", "I2S RX3"}, + + /* RX2 PATH */ + {"PDM_RX2", NULL, "RX2 INT"}, + {"RX2 INT", NULL, "RX2 MIX1"}, + + {"RX2 MIX1", NULL, "RX2 MIX1 INP1"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP2"}, + {"RX2 MIX1", NULL, "RX2 MIX1 INP3"}, + + {"RX2 MIX1 INP1", "RX1", "I2S RX1"}, + {"RX2 MIX1 INP1", "RX2", "I2S RX2"}, + {"RX2 MIX1 INP1", "RX3", "I2S RX3"}, + + {"RX2 MIX1 INP2", "RX1", "I2S RX1"}, + {"RX2 MIX1 INP2", "RX2", "I2S RX2"}, + {"RX2 MIX1 INP2", "RX3", "I2S RX3"}, + + {"RX2 MIX1 INP3", "RX1", "I2S RX1"}, + {"RX2 MIX1 INP3", "RX2", "I2S RX2"}, + {"RX2 MIX1 INP3", "RX3", "I2S RX3"}, + + /* RX3 PATH */ + {"PDM_RX3", NULL, "RX3 INT"}, + {"RX3 INT", NULL, "RX3 MIX1"}, + + {"RX3 MIX1", NULL, "RX3 MIX1 INP1"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP2"}, + {"RX3 MIX1", NULL, "RX3 MIX1 INP3"}, + + {"RX3 MIX1 INP1", "RX1", "I2S RX1"}, + {"RX3 MIX1 INP1", "RX2", "I2S RX2"}, + {"RX3 MIX1 INP1", "RX3", "I2S RX3"}, + + {"RX3 MIX1 INP2", "RX1", "I2S RX1"}, + {"RX3 MIX1 INP2", "RX2", "I2S RX2"}, + {"RX3 MIX1 INP2", "RX3", "I2S RX3"}, + + {"RX3 MIX1 INP3", "RX1", "I2S RX1"}, + {"RX3 MIX1 INP3", "RX2", "I2S RX2"}, + {"RX3 MIX1 INP3", "RX3", "I2S RX3"}, + +}; + +static int msm8916_wcd_digital_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct msm8916_wcd_digital_priv *msm8916_wcd; + unsigned long mclk_rate; + + msm8916_wcd = snd_soc_component_get_drvdata(component); + snd_soc_component_update_bits(component, LPASS_CDC_CLK_MCLK_CTL, + MCLK_CTL_MCLK_EN_MASK, + MCLK_CTL_MCLK_EN_ENABLE); + snd_soc_component_update_bits(component, LPASS_CDC_CLK_PDM_CTL, + LPASS_CDC_CLK_PDM_CTL_PDM_CLK_SEL_MASK, + LPASS_CDC_CLK_PDM_CTL_PDM_CLK_SEL_FB); + + mclk_rate = clk_get_rate(msm8916_wcd->mclk); + switch (mclk_rate) { + case 12288000: + snd_soc_component_update_bits(component, LPASS_CDC_TOP_CTL, + TOP_CTL_DIG_MCLK_FREQ_MASK, + TOP_CTL_DIG_MCLK_FREQ_F_12_288MHZ); + break; + case 9600000: + snd_soc_component_update_bits(component, LPASS_CDC_TOP_CTL, + TOP_CTL_DIG_MCLK_FREQ_MASK, + TOP_CTL_DIG_MCLK_FREQ_F_9_6MHZ); + break; + default: + dev_err(component->dev, "Invalid mclk rate %ld\n", mclk_rate); + break; + } + return 0; +} + +static void msm8916_wcd_digital_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_component_update_bits(dai->component, LPASS_CDC_CLK_PDM_CTL, + LPASS_CDC_CLK_PDM_CTL_PDM_CLK_SEL_MASK, 0); +} + +static const struct snd_soc_dai_ops msm8916_wcd_digital_dai_ops = { + .startup = msm8916_wcd_digital_startup, + .shutdown = msm8916_wcd_digital_shutdown, + .hw_params = msm8916_wcd_digital_hw_params, +}; + +static struct snd_soc_dai_driver msm8916_wcd_digital_dai[] = { + [0] = { + .name = "msm8916_wcd_digital_i2s_rx1", + .id = 0, + .playback = { + .stream_name = "AIF1 Playback", + .rates = MSM8916_WCD_DIGITAL_RATES, + .formats = MSM8916_WCD_DIGITAL_FORMATS, + .channels_min = 1, + .channels_max = 3, + }, + .ops = &msm8916_wcd_digital_dai_ops, + }, + [1] = { + .name = "msm8916_wcd_digital_i2s_tx1", + .id = 1, + .capture = { + .stream_name = "AIF1 Capture", + .rates = MSM8916_WCD_DIGITAL_RATES, + .formats = MSM8916_WCD_DIGITAL_FORMATS, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &msm8916_wcd_digital_dai_ops, + }, +}; + +static const struct snd_soc_component_driver msm8916_wcd_digital = { + .probe = msm8916_wcd_digital_component_probe, + .set_sysclk = msm8916_wcd_digital_component_set_sysclk, + .controls = msm8916_wcd_digital_snd_controls, + .num_controls = ARRAY_SIZE(msm8916_wcd_digital_snd_controls), + .dapm_widgets = msm8916_wcd_digital_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(msm8916_wcd_digital_dapm_widgets), + .dapm_routes = msm8916_wcd_digital_audio_map, + .num_dapm_routes = ARRAY_SIZE(msm8916_wcd_digital_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config msm8916_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = LPASS_CDC_TX2_DMIC_CTL, + .cache_type = REGCACHE_FLAT, +}; + +static int msm8916_wcd_digital_probe(struct platform_device *pdev) +{ + struct msm8916_wcd_digital_priv *priv; + struct device *dev = &pdev->dev; + void __iomem *base; + struct resource *mem_res; + struct regmap *digital_map; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem_res); + if (IS_ERR(base)) + return PTR_ERR(base); + + digital_map = + devm_regmap_init_mmio(&pdev->dev, base, + &msm8916_codec_regmap_config); + if (IS_ERR(digital_map)) + return PTR_ERR(digital_map); + + ret = msm8916_wcd_digital_get_clks(pdev, priv); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(priv->ahbclk); + if (ret < 0) { + dev_err(dev, "failed to enable ahbclk %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(dev, "failed to enable mclk %d\n", ret); + goto err_clk; + } + + dev_set_drvdata(dev, priv); + + ret = devm_snd_soc_register_component(dev, &msm8916_wcd_digital, + msm8916_wcd_digital_dai, + ARRAY_SIZE(msm8916_wcd_digital_dai)); + if (ret) + goto err_mclk; + + return 0; + +err_mclk: + clk_disable_unprepare(priv->mclk); +err_clk: + clk_disable_unprepare(priv->ahbclk); + return ret; +} + +static int msm8916_wcd_digital_remove(struct platform_device *pdev) +{ + struct msm8916_wcd_digital_priv *priv = dev_get_drvdata(&pdev->dev); + + clk_disable_unprepare(priv->mclk); + clk_disable_unprepare(priv->ahbclk); + + return 0; +} + +static const struct of_device_id msm8916_wcd_digital_match_table[] = { + { .compatible = "qcom,msm8916-wcd-digital-codec" }, + { } +}; + +MODULE_DEVICE_TABLE(of, msm8916_wcd_digital_match_table); + +static struct platform_driver msm8916_wcd_digital_driver = { + .driver = { + .name = "msm8916-wcd-digital-codec", + .of_match_table = msm8916_wcd_digital_match_table, + }, + .probe = msm8916_wcd_digital_probe, + .remove = msm8916_wcd_digital_remove, +}; + +module_platform_driver(msm8916_wcd_digital_driver); + +MODULE_AUTHOR("Srinivas Kandagatla "); +MODULE_DESCRIPTION("MSM8916 WCD Digital Codec driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/mt6351.c b/sound/soc/codecs/mt6351.c new file mode 100644 index 000000000..f73dcd753 --- /dev/null +++ b/sound/soc/codecs/mt6351.c @@ -0,0 +1,1505 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt6351.c -- mt6351 ALSA SoC audio codec driver +// +// Copyright (c) 2018 MediaTek Inc. +// Author: KaiChieh Chuang + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mt6351.h" + +/* MT6351_TOP_CLKSQ */ +#define RG_CLKSQ_EN_AUD_BIT (0) + +/* MT6351_TOP_CKPDN_CON0 */ +#define RG_AUDNCP_CK_PDN_BIT (12) +#define RG_AUDIF_CK_PDN_BIT (13) +#define RG_AUD_CK_PDN_BIT (14) +#define RG_ZCD13M_CK_PDN_BIT (15) + +/* MT6351_AUDDEC_ANA_CON0 */ +#define RG_AUDDACLPWRUP_VAUDP32_BIT (0) +#define RG_AUDDACRPWRUP_VAUDP32_BIT (1) +#define RG_AUD_DAC_PWR_UP_VA32_BIT (2) +#define RG_AUD_DAC_PWL_UP_VA32_BIT (3) + +#define RG_AUDHSPWRUP_VAUDP32_BIT (4) + +#define RG_AUDHPLPWRUP_VAUDP32_BIT (5) +#define RG_AUDHPRPWRUP_VAUDP32_BIT (6) + +#define RG_AUDHSMUXINPUTSEL_VAUDP32_SFT (7) +#define RG_AUDHSMUXINPUTSEL_VAUDP32_MASK (0x3) + +#define RG_AUDHPLMUXINPUTSEL_VAUDP32_SFT (9) +#define RG_AUDHPLMUXINPUTSEL_VAUDP32_MASK (0x3) + +#define RG_AUDHPRMUXINPUTSEL_VAUDP32_SFT (11) +#define RG_AUDHPRMUXINPUTSEL_VAUDP32_MASK (0x3) + +#define RG_AUDHSSCDISABLE_VAUDP32 (13) +#define RG_AUDHPLSCDISABLE_VAUDP32_BIT (14) +#define RG_AUDHPRSCDISABLE_VAUDP32_BIT (15) + +/* MT6351_AUDDEC_ANA_CON1 */ +#define RG_HSOUTPUTSTBENH_VAUDP32_BIT (8) + +/* MT6351_AUDDEC_ANA_CON3 */ +#define RG_AUDLOLPWRUP_VAUDP32_BIT (2) + +#define RG_AUDLOLMUXINPUTSEL_VAUDP32_SFT (3) +#define RG_AUDLOLMUXINPUTSEL_VAUDP32_MASK (0x3) + +#define RG_AUDLOLSCDISABLE_VAUDP32_BIT (5) +#define RG_LOOUTPUTSTBENH_VAUDP32_BIT (9) + +/* MT6351_AUDDEC_ANA_CON6 */ +#define RG_ABIDEC_RSVD0_VAUDP32_HPL_BIT (8) +#define RG_ABIDEC_RSVD0_VAUDP32_HPR_BIT (9) +#define RG_ABIDEC_RSVD0_VAUDP32_HS_BIT (10) +#define RG_ABIDEC_RSVD0_VAUDP32_LOL_BIT (11) + +/* MT6351_AUDDEC_ANA_CON9 */ +#define RG_AUDIBIASPWRDN_VAUDP32_BIT (8) +#define RG_RSTB_DECODER_VA32_BIT (9) +#define RG_AUDGLB_PWRDN_VA32_BIT (12) + +#define RG_LCLDO_DEC_EN_VA32_BIT (13) +#define RG_LCLDO_DEC_REMOTE_SENSE_VA18_BIT (15) +/* MT6351_AUDDEC_ANA_CON10 */ +#define RG_NVREG_EN_VAUDP32_BIT (8) + +#define RG_AUDGLB_LP2_VOW_EN_VA32 10 + +/* MT6351_AFE_UL_DL_CON0 */ +#define RG_AFE_ON_BIT (0) + +/* MT6351_AFE_DL_SRC2_CON0_L */ +#define RG_DL_2_SRC_ON_TMP_CTL_PRE_BIT (0) + +/* MT6351_AFE_UL_SRC_CON0_L */ +#define UL_SRC_ON_TMP_CTL (0) + +/* MT6351_AFE_TOP_CON0 */ +#define RG_DL_SINE_ON_SFT (0) +#define RG_DL_SINE_ON_MASK (0x1) + +#define RG_UL_SINE_ON_SFT (1) +#define RG_UL_SINE_ON_MASK (0x1) + +/* MT6351_AUDIO_TOP_CON0 */ +#define AUD_TOP_PDN_RESERVED_BIT 0 +#define AUD_TOP_PWR_CLK_DIS_CTL_BIT 2 +#define AUD_TOP_PDN_ADC_CTL_BIT 5 +#define AUD_TOP_PDN_DAC_CTL_BIT 6 +#define AUD_TOP_PDN_AFE_CTL_BIT 7 + +/* MT6351_AFE_SGEN_CFG0 */ +#define SGEN_C_MUTE_SW_CTL_BIT 6 +#define SGEN_C_DAC_EN_CTL_BIT 7 + +/* MT6351_AFE_NCP_CFG0 */ +#define RG_NCP_ON_BIT 0 + +/* MT6351_LDO_VUSB33_CON0 */ +#define RG_VUSB33_EN 1 +#define RG_VUSB33_ON_CTRL 3 + +/* MT6351_LDO_VA18_CON0 */ +#define RG_VA18_EN 1 +#define RG_VA18_ON_CTRL 3 + +/* MT6351_AUDENC_ANA_CON0 */ +#define RG_AUDPREAMPLON 0 +#define RG_AUDPREAMPLDCCEN 1 +#define RG_AUDPREAMPLDCPRECHARGE 2 + +#define RG_AUDPREAMPLINPUTSEL_SFT (4) +#define RG_AUDPREAMPLINPUTSEL_MASK (0x3) + +#define RG_AUDADCLPWRUP 12 + +#define RG_AUDADCLINPUTSEL_SFT (13) +#define RG_AUDADCLINPUTSEL_MASK (0x3) + +/* MT6351_AUDENC_ANA_CON1 */ +#define RG_AUDPREAMPRON 0 +#define RG_AUDPREAMPRDCCEN 1 +#define RG_AUDPREAMPRDCPRECHARGE 2 + +#define RG_AUDPREAMPRINPUTSEL_SFT (4) +#define RG_AUDPREAMPRINPUTSEL_MASK (0x3) + +#define RG_AUDADCRPWRUP 12 + +#define RG_AUDADCRINPUTSEL_SFT (13) +#define RG_AUDADCRINPUTSEL_MASK (0x3) + +/* MT6351_AUDENC_ANA_CON3 */ +#define RG_AUDADCCLKRSTB 6 + +/* MT6351_AUDENC_ANA_CON9 */ +#define RG_AUDPWDBMICBIAS0 0 +#define RG_AUDMICBIAS0VREF 4 +#define RG_AUDMICBIAS0LOWPEN 7 + +#define RG_AUDPWDBMICBIAS2 8 +#define RG_AUDMICBIAS2VREF 12 +#define RG_AUDMICBIAS2LOWPEN 15 + +/* MT6351_AUDENC_ANA_CON10 */ +#define RG_AUDPWDBMICBIAS1 0 +#define RG_AUDMICBIAS1DCSW1NEN 2 +#define RG_AUDMICBIAS1VREF 4 +#define RG_AUDMICBIAS1LOWPEN 7 + +enum { + AUDIO_ANALOG_VOLUME_HSOUTL, + AUDIO_ANALOG_VOLUME_HSOUTR, + AUDIO_ANALOG_VOLUME_HPOUTL, + AUDIO_ANALOG_VOLUME_HPOUTR, + AUDIO_ANALOG_VOLUME_LINEOUTL, + AUDIO_ANALOG_VOLUME_LINEOUTR, + AUDIO_ANALOG_VOLUME_MICAMP1, + AUDIO_ANALOG_VOLUME_MICAMP2, + AUDIO_ANALOG_VOLUME_TYPE_MAX +}; + +/* Supply subseq */ +enum { + SUPPLY_SUBSEQ_SETTING, + SUPPLY_SUBSEQ_ENABLE, + SUPPLY_SUBSEQ_MICBIAS, +}; + +#define REG_STRIDE 2 + +struct mt6351_priv { + struct device *dev; + struct regmap *regmap; + + unsigned int dl_rate; + unsigned int ul_rate; + + int ana_gain[AUDIO_ANALOG_VOLUME_TYPE_MAX]; + + int hp_en_counter; +}; + +static void set_hp_gain_zero(struct snd_soc_component *cmpnt) +{ + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON2, + 0x1f << 7, 0x8 << 7); + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON2, + 0x1f << 0, 0x8 << 0); +} + +static unsigned int get_cap_reg_val(struct snd_soc_component *cmpnt, + unsigned int rate) +{ + switch (rate) { + case 8000: + return 0; + case 16000: + return 1; + case 32000: + return 2; + case 48000: + return 3; + case 96000: + return 4; + case 192000: + return 5; + default: + dev_warn(cmpnt->dev, "%s(), error rate %d, return 3", + __func__, rate); + return 3; + } +} + +static unsigned int get_play_reg_val(struct snd_soc_component *cmpnt, + unsigned int rate) +{ + switch (rate) { + case 8000: + return 0; + case 11025: + return 1; + case 12000: + return 2; + case 16000: + return 3; + case 22050: + return 4; + case 24000: + return 5; + case 32000: + return 6; + case 44100: + return 7; + case 48000: + case 96000: + case 192000: + return 8; + default: + dev_warn(cmpnt->dev, "%s(), error rate %d, return 8", + __func__, rate); + return 8; + } +} + +static int mt6351_codec_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *cmpnt = dai->component; + struct mt6351_priv *priv = snd_soc_component_get_drvdata(cmpnt); + unsigned int rate = params_rate(params); + + dev_dbg(priv->dev, "%s(), substream->stream %d, rate %d\n", + __func__, substream->stream, rate); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + priv->dl_rate = rate; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + priv->ul_rate = rate; + + return 0; +} + +static const struct snd_soc_dai_ops mt6351_codec_dai_ops = { + .hw_params = mt6351_codec_dai_hw_params, +}; + +#define MT6351_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |\ + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |\ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE |\ + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE) + +static struct snd_soc_dai_driver mt6351_dai_driver[] = { + { + .name = "mt6351-snd-codec-aif1", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = MT6351_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = MT6351_FORMATS, + }, + .ops = &mt6351_codec_dai_ops, + }, +}; + +enum { + HP_GAIN_SET_ZERO, + HP_GAIN_RESTORE, +}; + +static void hp_gain_ramp_set(struct snd_soc_component *cmpnt, int hp_gain_ctl) +{ + struct mt6351_priv *priv = snd_soc_component_get_drvdata(cmpnt); + int idx, old_idx, offset, reg_idx; + + if (hp_gain_ctl == HP_GAIN_SET_ZERO) { + idx = 8; /* 0dB */ + old_idx = priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL]; + } else { + idx = priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL]; + old_idx = 8; /* 0dB */ + } + dev_dbg(priv->dev, "%s(), idx %d, old_idx %d\n", + __func__, idx, old_idx); + + if (idx > old_idx) + offset = idx - old_idx; + else + offset = old_idx - idx; + + reg_idx = old_idx; + + while (offset > 0) { + reg_idx = idx > old_idx ? reg_idx + 1 : reg_idx - 1; + + /* check valid range, and set value */ + if ((reg_idx >= 0 && reg_idx <= 0x12) || reg_idx == 0x1f) { + regmap_update_bits(cmpnt->regmap, + MT6351_ZCD_CON2, + 0xf9f, + (reg_idx << 7) | reg_idx); + usleep_range(100, 120); + } + offset--; + } +} + +static void hp_zcd_enable(struct snd_soc_component *cmpnt) +{ + /* Enable ZCD, for minimize pop noise */ + /* when adjust gain during HP buffer on */ + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON0, 0x7 << 8, 0x1 << 8); + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON0, 0x1 << 7, 0x0 << 7); + + /* timeout, 1=5ms, 0=30ms */ + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON0, 0x1 << 6, 0x1 << 6); + + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON0, 0x3 << 4, 0x0 << 4); + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON0, 0x7 << 1, 0x5 << 1); + regmap_update_bits(cmpnt->regmap, MT6351_ZCD_CON0, 0x1 << 0, 0x1 << 0); +} + +static void hp_zcd_disable(struct snd_soc_component *cmpnt) +{ + regmap_write(cmpnt->regmap, MT6351_ZCD_CON0, 0x0000); +} + +static const DECLARE_TLV_DB_SCALE(playback_tlv, -1000, 100, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, 0, 600, 0); + +static const struct snd_kcontrol_new mt6351_snd_controls[] = { + /* dl pga gain */ + SOC_DOUBLE_TLV("Headphone Volume", + MT6351_ZCD_CON2, 0, 7, 0x12, 1, + playback_tlv), + SOC_DOUBLE_TLV("Lineout Volume", + MT6351_ZCD_CON1, 0, 7, 0x12, 1, + playback_tlv), + SOC_SINGLE_TLV("Handset Volume", + MT6351_ZCD_CON3, 0, 0x12, 1, + playback_tlv), + /* ul pga gain */ + SOC_DOUBLE_R_TLV("PGA Volume", + MT6351_AUDENC_ANA_CON0, MT6351_AUDENC_ANA_CON1, + 8, 4, 0, + pga_tlv), +}; + +/* MUX */ + +/* LOL MUX */ +static const char *const lo_in_mux_map[] = { + "Open", "Mute", "Playback", "Test Mode", +}; + +static int lo_in_mux_map_value[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(lo_in_mux_map_enum, + MT6351_AUDDEC_ANA_CON3, + RG_AUDLOLMUXINPUTSEL_VAUDP32_SFT, + RG_AUDLOLMUXINPUTSEL_VAUDP32_MASK, + lo_in_mux_map, + lo_in_mux_map_value); + +static const struct snd_kcontrol_new lo_in_mux_control = + SOC_DAPM_ENUM("In Select", lo_in_mux_map_enum); + +/*HP MUX */ +static const char *const hp_in_mux_map[] = { + "Open", "LoudSPK Playback", "Audio Playback", "Test Mode", +}; + +static int hp_in_mux_map_value[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(hpl_in_mux_map_enum, + MT6351_AUDDEC_ANA_CON0, + RG_AUDHPLMUXINPUTSEL_VAUDP32_SFT, + RG_AUDHPLMUXINPUTSEL_VAUDP32_MASK, + hp_in_mux_map, + hp_in_mux_map_value); + +static const struct snd_kcontrol_new hpl_in_mux_control = + SOC_DAPM_ENUM("HPL Select", hpl_in_mux_map_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL(hpr_in_mux_map_enum, + MT6351_AUDDEC_ANA_CON0, + RG_AUDHPRMUXINPUTSEL_VAUDP32_SFT, + RG_AUDHPRMUXINPUTSEL_VAUDP32_MASK, + hp_in_mux_map, + hp_in_mux_map_value); + +static const struct snd_kcontrol_new hpr_in_mux_control = + SOC_DAPM_ENUM("HPR Select", hpr_in_mux_map_enum); + +/* RCV MUX */ +static const char *const rcv_in_mux_map[] = { + "Open", "Mute", "Voice Playback", "Test Mode", +}; + +static int rcv_in_mux_map_value[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rcv_in_mux_map_enum, + MT6351_AUDDEC_ANA_CON0, + RG_AUDHSMUXINPUTSEL_VAUDP32_SFT, + RG_AUDHSMUXINPUTSEL_VAUDP32_MASK, + rcv_in_mux_map, + rcv_in_mux_map_value); + +static const struct snd_kcontrol_new rcv_in_mux_control = + SOC_DAPM_ENUM("RCV Select", rcv_in_mux_map_enum); + +/* DAC In MUX */ +static const char *const dac_in_mux_map[] = { + "Normal Path", "Sgen", +}; + +static int dac_in_mux_map_value[] = { + 0x0, 0x1, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(dac_in_mux_map_enum, + MT6351_AFE_TOP_CON0, + RG_DL_SINE_ON_SFT, + RG_DL_SINE_ON_MASK, + dac_in_mux_map, + dac_in_mux_map_value); + +static const struct snd_kcontrol_new dac_in_mux_control = + SOC_DAPM_ENUM("DAC Select", dac_in_mux_map_enum); + +/* AIF Out MUX */ +static SOC_VALUE_ENUM_SINGLE_DECL(aif_out_mux_map_enum, + MT6351_AFE_TOP_CON0, + RG_UL_SINE_ON_SFT, + RG_UL_SINE_ON_MASK, + dac_in_mux_map, + dac_in_mux_map_value); + +static const struct snd_kcontrol_new aif_out_mux_control = + SOC_DAPM_ENUM("AIF Out Select", aif_out_mux_map_enum); + +/* ADC L MUX */ +static const char *const adc_left_mux_map[] = { + "Idle", "AIN0", "Left Preamplifier", "Idle_1", +}; + +static int adc_left_mux_map_value[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(adc_left_mux_map_enum, + MT6351_AUDENC_ANA_CON0, + RG_AUDADCLINPUTSEL_SFT, + RG_AUDADCLINPUTSEL_MASK, + adc_left_mux_map, + adc_left_mux_map_value); + +static const struct snd_kcontrol_new adc_left_mux_control = + SOC_DAPM_ENUM("ADC L Select", adc_left_mux_map_enum); + +/* ADC R MUX */ +static const char *const adc_right_mux_map[] = { + "Idle", "AIN0", "Right Preamplifier", "Idle_1", +}; + +static int adc_right_mux_map_value[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(adc_right_mux_map_enum, + MT6351_AUDENC_ANA_CON1, + RG_AUDADCRINPUTSEL_SFT, + RG_AUDADCRINPUTSEL_MASK, + adc_right_mux_map, + adc_right_mux_map_value); + +static const struct snd_kcontrol_new adc_right_mux_control = + SOC_DAPM_ENUM("ADC R Select", adc_right_mux_map_enum); + +/* PGA L MUX */ +static const char *const pga_left_mux_map[] = { + "None", "AIN0", "AIN1", "AIN2", +}; + +static int pga_left_mux_map_value[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(pga_left_mux_map_enum, + MT6351_AUDENC_ANA_CON0, + RG_AUDPREAMPLINPUTSEL_SFT, + RG_AUDPREAMPLINPUTSEL_MASK, + pga_left_mux_map, + pga_left_mux_map_value); + +static const struct snd_kcontrol_new pga_left_mux_control = + SOC_DAPM_ENUM("PGA L Select", pga_left_mux_map_enum); + +/* PGA R MUX */ +static const char *const pga_right_mux_map[] = { + "None", "AIN0", "AIN3", "AIN2", +}; + +static int pga_right_mux_map_value[] = { + 0x0, 0x1, 0x2, 0x3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(pga_right_mux_map_enum, + MT6351_AUDENC_ANA_CON1, + RG_AUDPREAMPRINPUTSEL_SFT, + RG_AUDPREAMPRINPUTSEL_MASK, + pga_right_mux_map, + pga_right_mux_map_value); + +static const struct snd_kcontrol_new pga_right_mux_control = + SOC_DAPM_ENUM("PGA R Select", pga_right_mux_map_enum); + +static int mt_reg_set_clr_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (w->on_val) { + /* SET REG */ + regmap_update_bits(cmpnt->regmap, + w->reg + REG_STRIDE, + 0x1 << w->shift, + 0x1 << w->shift); + } else { + /* CLR REG */ + regmap_update_bits(cmpnt->regmap, + w->reg + REG_STRIDE * 2, + 0x1 << w->shift, + 0x1 << w->shift); + } + break; + case SND_SOC_DAPM_PRE_PMD: + if (w->off_val) { + /* SET REG */ + regmap_update_bits(cmpnt->regmap, + w->reg + REG_STRIDE, + 0x1 << w->shift, + 0x1 << w->shift); + } else { + /* CLR REG */ + regmap_update_bits(cmpnt->regmap, + w->reg + REG_STRIDE * 2, + 0x1 << w->shift, + 0x1 << w->shift); + } + break; + default: + break; + } + + return 0; +} + +static int mt_ncp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(cmpnt->regmap, MT6351_AFE_NCP_CFG1, + 0xffff, 0x1515); + /* NCP: ck1 and ck2 clock frequecy adjust configure */ + regmap_update_bits(cmpnt->regmap, MT6351_AFE_NCP_CFG0, + 0xfffe, 0x8C00); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(250, 270); + break; + default: + break; + } + + return 0; +} + +static int mt_sgen_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(cmpnt->regmap, MT6351_AFE_SGEN_CFG0, + 0xffef, 0x0008); + regmap_update_bits(cmpnt->regmap, MT6351_AFE_SGEN_CFG1, + 0xffff, 0x0101); + break; + default: + break; + } + + return 0; +} + +static int mt_aif_in_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mt6351_priv *priv = snd_soc_component_get_drvdata(cmpnt); + + dev_dbg(priv->dev, "%s(), event 0x%x, rate %d\n", + __func__, event, priv->dl_rate); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* sdm audio fifo clock power on */ + regmap_update_bits(cmpnt->regmap, MT6351_AFUNC_AUD_CON2, + 0xffff, 0x0006); + /* scrambler clock on enable */ + regmap_update_bits(cmpnt->regmap, MT6351_AFUNC_AUD_CON0, + 0xffff, 0xC3A1); + /* sdm power on */ + regmap_update_bits(cmpnt->regmap, MT6351_AFUNC_AUD_CON2, + 0xffff, 0x0003); + /* sdm fifo enable */ + regmap_update_bits(cmpnt->regmap, MT6351_AFUNC_AUD_CON2, + 0xffff, 0x000B); + /* set attenuation gain */ + regmap_update_bits(cmpnt->regmap, MT6351_AFE_DL_SDM_CON1, + 0xffff, 0x001E); + + regmap_write(cmpnt->regmap, MT6351_AFE_PMIC_NEWIF_CFG0, + (get_play_reg_val(cmpnt, priv->dl_rate) << 12) | + 0x330); + regmap_write(cmpnt->regmap, MT6351_AFE_DL_SRC2_CON0_H, + (get_play_reg_val(cmpnt, priv->dl_rate) << 12) | + 0x300); + + regmap_update_bits(cmpnt->regmap, MT6351_AFE_PMIC_NEWIF_CFG2, + 0x8000, 0x8000); + break; + default: + break; + } + + return 0; +} + +static int mt_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mt6351_priv *priv = snd_soc_component_get_drvdata(cmpnt); + int reg; + + dev_dbg(priv->dev, "%s(), event 0x%x, hp_en_counter %d\n", + __func__, event, priv->hp_en_counter); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + priv->hp_en_counter++; + if (priv->hp_en_counter > 1) + break; /* already enabled, do nothing */ + else if (priv->hp_en_counter <= 0) + dev_err(priv->dev, "%s(), hp_en_counter %d <= 0\n", + __func__, + priv->hp_en_counter); + + hp_zcd_disable(cmpnt); + + /* from yoyo HQA script */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON6, + 0x0700, 0x0700); + + /* save target gain to restore after hardware open complete */ + regmap_read(cmpnt->regmap, MT6351_ZCD_CON2, ®); + priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL] = reg & 0x1f; + priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTR] = (reg >> 7) & 0x1f; + + /* Set HPR/HPL gain as minimum (~ -40dB) */ + regmap_update_bits(cmpnt->regmap, + MT6351_ZCD_CON2, 0xffff, 0x0F9F); + /* Set HS gain as minimum (~ -40dB) */ + regmap_update_bits(cmpnt->regmap, + MT6351_ZCD_CON3, 0xffff, 0x001F); + /* De_OSC of HP */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON2, + 0x0001, 0x0001); + /* enable output STBENH */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON1, + 0xffff, 0x2000); + /* De_OSC of voice, enable output STBENH */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON1, + 0xffff, 0x2100); + /* Enable voice driver */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON0, + 0x0010, 0xE090); + /* Enable pre-charge buffer */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON1, + 0xffff, 0x2140); + + usleep_range(50, 60); + + /* Apply digital DC compensation value to DAC */ + set_hp_gain_zero(cmpnt); + + /* Enable HPR/HPL */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON1, + 0xffff, 0x2100); + /* Disable pre-charge buffer */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON1, + 0xffff, 0x2000); + /* Disable De_OSC of voice */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON0, + 0x0010, 0xF4EF); + /* Disable voice buffer */ + + /* from yoyo HQ */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON6, + 0x0700, 0x0300); + + /* Enable ZCD, for minimize pop noise */ + /* when adjust gain during HP buffer on */ + hp_zcd_enable(cmpnt); + + /* apply volume setting */ + hp_gain_ramp_set(cmpnt, HP_GAIN_RESTORE); + + break; + case SND_SOC_DAPM_PRE_PMD: + priv->hp_en_counter--; + if (priv->hp_en_counter > 0) + break; /* still being used, don't close */ + else if (priv->hp_en_counter < 0) + dev_err(priv->dev, "%s(), hp_en_counter %d <= 0\n", + __func__, + priv->hp_en_counter); + + /* Disable AUD_ZCD */ + hp_zcd_disable(cmpnt); + + /* Set HPR/HPL gain as -1dB, step by step */ + hp_gain_ramp_set(cmpnt, HP_GAIN_SET_ZERO); + + set_hp_gain_zero(cmpnt); + break; + case SND_SOC_DAPM_POST_PMD: + if (priv->hp_en_counter > 0) + break; /* still being used, don't close */ + else if (priv->hp_en_counter < 0) + dev_err(priv->dev, "%s(), hp_en_counter %d <= 0\n", + __func__, + priv->hp_en_counter); + + /* reset*/ + regmap_update_bits(cmpnt->regmap, + MT6351_AUDDEC_ANA_CON6, + 0x0700, + 0x0000); + /* De_OSC of HP */ + regmap_update_bits(cmpnt->regmap, + MT6351_AUDDEC_ANA_CON2, + 0x0001, + 0x0000); + + /* apply volume setting */ + hp_gain_ramp_set(cmpnt, HP_GAIN_RESTORE); + break; + default: + break; + } + + return 0; +} + +static int mt_aif_out_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mt6351_priv *priv = snd_soc_component_get_drvdata(cmpnt); + + dev_dbg(priv->dev, "%s(), event 0x%x, rate %d\n", + __func__, event, priv->ul_rate); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* dcclk_div=11'b00100000011, dcclk_ref_ck_sel=2'b00 */ + regmap_update_bits(cmpnt->regmap, MT6351_AFE_DCCLK_CFG0, + 0xffff, 0x2062); + /* dcclk_pdn=1'b0 */ + regmap_update_bits(cmpnt->regmap, MT6351_AFE_DCCLK_CFG0, + 0xffff, 0x2060); + /* dcclk_gen_on=1'b1 */ + regmap_update_bits(cmpnt->regmap, MT6351_AFE_DCCLK_CFG0, + 0xffff, 0x2061); + + /* UL sample rate and mode configure */ + regmap_update_bits(cmpnt->regmap, MT6351_AFE_UL_SRC_CON0_H, + 0x000E, + get_cap_reg_val(cmpnt, priv->ul_rate) << 1); + + /* fixed 260k path for 8/16/32/48 */ + if (priv->ul_rate <= 48000) { + /* anc ul path src on */ + regmap_update_bits(cmpnt->regmap, + MT6351_AFE_HPANC_CFG0, + 0x1 << 1, + 0x1 << 1); + /* ANC clk pdn release */ + regmap_update_bits(cmpnt->regmap, + MT6351_AFE_HPANC_CFG0, + 0x1 << 0, + 0x0 << 0); + } + break; + case SND_SOC_DAPM_PRE_PMD: + /* fixed 260k path for 8/16/32/48 */ + if (priv->ul_rate <= 48000) { + /* anc ul path src on */ + regmap_update_bits(cmpnt->regmap, + MT6351_AFE_HPANC_CFG0, + 0x1 << 1, + 0x0 << 1); + /* ANC clk pdn release */ + regmap_update_bits(cmpnt->regmap, + MT6351_AFE_HPANC_CFG0, + 0x1 << 0, + 0x1 << 0); + } + break; + default: + break; + } + + return 0; +} + +static int mt_adc_clkgen_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Audio ADC clock gen. mode: 00_divided by 2 (Normal) */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON3, + 0x3 << 4, 0x0); + break; + case SND_SOC_DAPM_POST_PMU: + /* ADC CLK from: 00_13MHz from CLKSQ (Default) */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON3, + 0x3 << 2, 0x0); + break; + default: + break; + } + return 0; +} + +static int mt_pga_left_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Audio L PGA precharge on */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON0, + 0x3 << RG_AUDPREAMPLDCPRECHARGE, + 0x1 << RG_AUDPREAMPLDCPRECHARGE); + /* Audio L PGA mode: 1_DCC */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON0, + 0x3 << RG_AUDPREAMPLDCCEN, + 0x1 << RG_AUDPREAMPLDCCEN); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(100, 120); + /* Audio L PGA precharge off */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON0, + 0x3 << RG_AUDPREAMPLDCPRECHARGE, + 0x0 << RG_AUDPREAMPLDCPRECHARGE); + break; + default: + break; + } + return 0; +} + +static int mt_pga_right_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Audio R PGA precharge on */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON1, + 0x3 << RG_AUDPREAMPRDCPRECHARGE, + 0x1 << RG_AUDPREAMPRDCPRECHARGE); + /* Audio R PGA mode: 1_DCC */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON1, + 0x3 << RG_AUDPREAMPRDCCEN, + 0x1 << RG_AUDPREAMPRDCCEN); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(100, 120); + /* Audio R PGA precharge off */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON1, + 0x3 << RG_AUDPREAMPRDCPRECHARGE, + 0x0 << RG_AUDPREAMPRDCPRECHARGE); + break; + default: + break; + } + return 0; +} + +static int mt_mic_bias_0_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* MIC Bias 0 LowPower: 0_Normal */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON9, + 0x3 << RG_AUDMICBIAS0LOWPEN, 0x0); + /* MISBIAS0 = 1P9V */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON9, + 0x7 << RG_AUDMICBIAS0VREF, + 0x2 << RG_AUDMICBIAS0VREF); + break; + case SND_SOC_DAPM_POST_PMD: + /* MISBIAS0 = 1P97 */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON9, + 0x7 << RG_AUDMICBIAS0VREF, + 0x0 << RG_AUDMICBIAS0VREF); + break; + default: + break; + } + return 0; +} + +static int mt_mic_bias_1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* MIC Bias 1 LowPower: 0_Normal */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON10, + 0x3 << RG_AUDMICBIAS1LOWPEN, 0x0); + /* MISBIAS1 = 2P7V */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON10, + 0x7 << RG_AUDMICBIAS1VREF, + 0x7 << RG_AUDMICBIAS1VREF); + break; + case SND_SOC_DAPM_POST_PMD: + /* MISBIAS1 = 1P7V */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON10, + 0x7 << RG_AUDMICBIAS1VREF, + 0x0 << RG_AUDMICBIAS1VREF); + break; + default: + break; + } + return 0; +} + +static int mt_mic_bias_2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* MIC Bias 2 LowPower: 0_Normal */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON9, + 0x3 << RG_AUDMICBIAS2LOWPEN, 0x0); + /* MISBIAS2 = 1P9V */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON9, + 0x7 << RG_AUDMICBIAS2VREF, + 0x2 << RG_AUDMICBIAS2VREF); + break; + case SND_SOC_DAPM_POST_PMD: + /* MISBIAS2 = 1P97 */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDENC_ANA_CON9, + 0x7 << RG_AUDMICBIAS2VREF, + 0x0 << RG_AUDMICBIAS2VREF); + break; + default: + break; + } + return 0; +} + +/* DAPM Kcontrols */ +static const struct snd_kcontrol_new mt_lineout_control = + SOC_DAPM_SINGLE("Switch", MT6351_AUDDEC_ANA_CON3, + RG_AUDLOLPWRUP_VAUDP32_BIT, 1, 0); + +/* DAPM Widgets */ +static const struct snd_soc_dapm_widget mt6351_dapm_widgets[] = { + /* Digital Clock */ + SND_SOC_DAPM_SUPPLY("AUDIO_TOP_AFE_CTL", MT6351_AUDIO_TOP_CON0, + AUD_TOP_PDN_AFE_CTL_BIT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("AUDIO_TOP_DAC_CTL", MT6351_AUDIO_TOP_CON0, + AUD_TOP_PDN_DAC_CTL_BIT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("AUDIO_TOP_ADC_CTL", MT6351_AUDIO_TOP_CON0, + AUD_TOP_PDN_ADC_CTL_BIT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("AUDIO_TOP_PWR_CLK", MT6351_AUDIO_TOP_CON0, + AUD_TOP_PWR_CLK_DIS_CTL_BIT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("AUDIO_TOP_PDN_RESERVED", MT6351_AUDIO_TOP_CON0, + AUD_TOP_PDN_RESERVED_BIT, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("NCP", MT6351_AFE_NCP_CFG0, + RG_NCP_ON_BIT, 0, + mt_ncp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY("DL Digital Clock", SND_SOC_NOPM, + 0, 0, NULL, 0), + + /* Global Supply*/ + SND_SOC_DAPM_SUPPLY("AUDGLB", MT6351_AUDDEC_ANA_CON9, + RG_AUDGLB_PWRDN_VA32_BIT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKSQ Audio", MT6351_TOP_CLKSQ, + RG_CLKSQ_EN_AUD_BIT, 0, + mt_reg_set_clr_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("ZCD13M_CK", MT6351_TOP_CKPDN_CON0, + RG_ZCD13M_CK_PDN_BIT, 1, + mt_reg_set_clr_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("AUD_CK", MT6351_TOP_CKPDN_CON0, + RG_AUD_CK_PDN_BIT, 1, + mt_reg_set_clr_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("AUDIF_CK", MT6351_TOP_CKPDN_CON0, + RG_AUDIF_CK_PDN_BIT, 1, + mt_reg_set_clr_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("AUDNCP_CK", MT6351_TOP_CKPDN_CON0, + RG_AUDNCP_CK_PDN_BIT, 1, + mt_reg_set_clr_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_SUPPLY("AFE_ON", MT6351_AFE_UL_DL_CON0, RG_AFE_ON_BIT, 0, + NULL, 0), + + /* AIF Rx*/ + SND_SOC_DAPM_AIF_IN_E("AIF_RX", "AIF1 Playback", 0, + MT6351_AFE_DL_SRC2_CON0_L, + RG_DL_2_SRC_ON_TMP_CTL_PRE_BIT, 0, + mt_aif_in_event, SND_SOC_DAPM_PRE_PMU), + + /* DL Supply */ + SND_SOC_DAPM_SUPPLY("DL Power Supply", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("NV Regulator", MT6351_AUDDEC_ANA_CON10, + RG_NVREG_EN_VAUDP32_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AUD_CLK", MT6351_AUDDEC_ANA_CON9, + RG_RSTB_DECODER_VA32_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("IBIST", MT6351_AUDDEC_ANA_CON9, + RG_AUDIBIASPWRDN_VAUDP32_BIT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO", MT6351_AUDDEC_ANA_CON9, + RG_LCLDO_DEC_EN_VA32_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO_REMOTE_SENSE", MT6351_AUDDEC_ANA_CON9, + RG_LCLDO_DEC_REMOTE_SENSE_VA18_BIT, 0, NULL, 0), + + /* DAC */ + SND_SOC_DAPM_MUX("DAC In Mux", SND_SOC_NOPM, 0, 0, &dac_in_mux_control), + + SND_SOC_DAPM_DAC("DACL", NULL, MT6351_AUDDEC_ANA_CON0, + RG_AUDDACLPWRUP_VAUDP32_BIT, 0), + SND_SOC_DAPM_SUPPLY("DACL_BIASGEN", MT6351_AUDDEC_ANA_CON0, + RG_AUD_DAC_PWL_UP_VA32_BIT, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DACR", NULL, MT6351_AUDDEC_ANA_CON0, + RG_AUDDACRPWRUP_VAUDP32_BIT, 0), + SND_SOC_DAPM_SUPPLY("DACR_BIASGEN", MT6351_AUDDEC_ANA_CON0, + RG_AUD_DAC_PWR_UP_VA32_BIT, 0, NULL, 0), + /* LOL */ + SND_SOC_DAPM_MUX("LOL Mux", SND_SOC_NOPM, 0, 0, &lo_in_mux_control), + + SND_SOC_DAPM_SUPPLY("LO Stability Enh", MT6351_AUDDEC_ANA_CON3, + RG_LOOUTPUTSTBENH_VAUDP32_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LOL Bias Gen", MT6351_AUDDEC_ANA_CON6, + RG_ABIDEC_RSVD0_VAUDP32_LOL_BIT, 0, NULL, 0), + + SND_SOC_DAPM_OUT_DRV("LOL Buffer", MT6351_AUDDEC_ANA_CON3, + RG_AUDLOLPWRUP_VAUDP32_BIT, 0, NULL, 0), + + /* Headphone */ + SND_SOC_DAPM_MUX("HPL Mux", SND_SOC_NOPM, 0, 0, &hpl_in_mux_control), + SND_SOC_DAPM_MUX("HPR Mux", SND_SOC_NOPM, 0, 0, &hpr_in_mux_control), + + SND_SOC_DAPM_OUT_DRV_E("HPL Power", MT6351_AUDDEC_ANA_CON0, + RG_AUDHPLPWRUP_VAUDP32_BIT, 0, NULL, 0, + mt_hp_event, + SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("HPR Power", MT6351_AUDDEC_ANA_CON0, + RG_AUDHPRPWRUP_VAUDP32_BIT, 0, NULL, 0, + mt_hp_event, + SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMD), + + /* Receiver */ + SND_SOC_DAPM_MUX("RCV Mux", SND_SOC_NOPM, 0, 0, &rcv_in_mux_control), + + SND_SOC_DAPM_SUPPLY("RCV Stability Enh", MT6351_AUDDEC_ANA_CON1, + RG_HSOUTPUTSTBENH_VAUDP32_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RCV Bias Gen", MT6351_AUDDEC_ANA_CON6, + RG_ABIDEC_RSVD0_VAUDP32_HS_BIT, 0, NULL, 0), + + SND_SOC_DAPM_OUT_DRV("RCV Buffer", MT6351_AUDDEC_ANA_CON0, + RG_AUDHSPWRUP_VAUDP32_BIT, 0, NULL, 0), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("Receiver"), + SND_SOC_DAPM_OUTPUT("Headphone L"), + SND_SOC_DAPM_OUTPUT("Headphone R"), + SND_SOC_DAPM_OUTPUT("LINEOUT L"), + + /* SGEN */ + SND_SOC_DAPM_SUPPLY("SGEN DL Enable", MT6351_AFE_SGEN_CFG0, + SGEN_C_DAC_EN_CTL_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SGEN MUTE", MT6351_AFE_SGEN_CFG0, + SGEN_C_MUTE_SW_CTL_BIT, 1, + mt_sgen_event, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("SGEN DL SRC", MT6351_AFE_DL_SRC2_CON0_L, + RG_DL_2_SRC_ON_TMP_CTL_PRE_BIT, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("SGEN DL"), + + /* Uplinks */ + SND_SOC_DAPM_AIF_OUT_E("AIF1TX", "AIF1 Capture", 0, + MT6351_AFE_UL_SRC_CON0_L, + UL_SRC_ON_TMP_CTL, 0, + mt_aif_out_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_SUPPLY_S("VUSB33_LDO", SUPPLY_SUBSEQ_ENABLE, + MT6351_LDO_VUSB33_CON0, RG_VUSB33_EN, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("VUSB33_LDO_CTRL", SUPPLY_SUBSEQ_SETTING, + MT6351_LDO_VUSB33_CON0, RG_VUSB33_ON_CTRL, 1, + NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("VA18_LDO", SUPPLY_SUBSEQ_ENABLE, + MT6351_LDO_VA18_CON0, RG_VA18_EN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("VA18_LDO_CTRL", SUPPLY_SUBSEQ_SETTING, + MT6351_LDO_VA18_CON0, RG_VA18_ON_CTRL, 1, + NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("ADC CLKGEN", SUPPLY_SUBSEQ_ENABLE, + MT6351_AUDENC_ANA_CON3, RG_AUDADCCLKRSTB, 0, + mt_adc_clkgen_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* Uplinks MUX */ + SND_SOC_DAPM_MUX("AIF Out Mux", SND_SOC_NOPM, 0, 0, + &aif_out_mux_control), + + SND_SOC_DAPM_MUX("ADC L Mux", SND_SOC_NOPM, 0, 0, + &adc_left_mux_control), + SND_SOC_DAPM_MUX("ADC R Mux", SND_SOC_NOPM, 0, 0, + &adc_right_mux_control), + + SND_SOC_DAPM_ADC("ADC L", NULL, + MT6351_AUDENC_ANA_CON0, RG_AUDADCLPWRUP, 0), + SND_SOC_DAPM_ADC("ADC R", NULL, + MT6351_AUDENC_ANA_CON1, RG_AUDADCRPWRUP, 0), + + SND_SOC_DAPM_MUX("PGA L Mux", SND_SOC_NOPM, 0, 0, + &pga_left_mux_control), + SND_SOC_DAPM_MUX("PGA R Mux", SND_SOC_NOPM, 0, 0, + &pga_right_mux_control), + + SND_SOC_DAPM_PGA_E("PGA L", MT6351_AUDENC_ANA_CON0, RG_AUDPREAMPLON, 0, + NULL, 0, + mt_pga_left_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("PGA R", MT6351_AUDENC_ANA_CON1, RG_AUDPREAMPRON, 0, + NULL, 0, + mt_pga_right_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* main mic mic bias */ + SND_SOC_DAPM_SUPPLY_S("Mic Bias 0", SUPPLY_SUBSEQ_MICBIAS, + MT6351_AUDENC_ANA_CON9, RG_AUDPWDBMICBIAS0, 0, + mt_mic_bias_0_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /* ref mic mic bias */ + SND_SOC_DAPM_SUPPLY_S("Mic Bias 2", SUPPLY_SUBSEQ_MICBIAS, + MT6351_AUDENC_ANA_CON9, RG_AUDPWDBMICBIAS2, 0, + mt_mic_bias_2_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /* headset mic1/2 mic bias */ + SND_SOC_DAPM_SUPPLY_S("Mic Bias 1", SUPPLY_SUBSEQ_MICBIAS, + MT6351_AUDENC_ANA_CON10, RG_AUDPWDBMICBIAS1, 0, + mt_mic_bias_1_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("Mic Bias 1 DCC pull high", SUPPLY_SUBSEQ_MICBIAS, + MT6351_AUDENC_ANA_CON10, + RG_AUDMICBIAS1DCSW1NEN, 0, + NULL, 0), + + /* UL input */ + SND_SOC_DAPM_INPUT("AIN0"), + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + SND_SOC_DAPM_INPUT("AIN3"), +}; + +static const struct snd_soc_dapm_route mt6351_dapm_routes[] = { + /* Capture */ + {"AIF1TX", NULL, "AIF Out Mux"}, + {"AIF1TX", NULL, "VUSB33_LDO"}, + {"VUSB33_LDO", NULL, "VUSB33_LDO_CTRL"}, + {"AIF1TX", NULL, "VA18_LDO"}, + {"VA18_LDO", NULL, "VA18_LDO_CTRL"}, + + {"AIF1TX", NULL, "AUDGLB"}, + {"AIF1TX", NULL, "CLKSQ Audio"}, + + {"AIF1TX", NULL, "AFE_ON"}, + + {"AIF1TX", NULL, "AUDIO_TOP_AFE_CTL"}, + {"AIF1TX", NULL, "AUDIO_TOP_ADC_CTL"}, + {"AIF1TX", NULL, "AUDIO_TOP_PWR_CLK"}, + {"AIF1TX", NULL, "AUDIO_TOP_PDN_RESERVED"}, + + {"AIF Out Mux", "Normal Path", "ADC L"}, + {"AIF Out Mux", "Normal Path", "ADC R"}, + + {"ADC L", NULL, "ADC L Mux"}, + {"ADC L", NULL, "AUD_CK"}, + {"ADC L", NULL, "AUDIF_CK"}, + {"ADC L", NULL, "ADC CLKGEN"}, + {"ADC R", NULL, "ADC R Mux"}, + {"ADC R", NULL, "AUD_CK"}, + {"ADC R", NULL, "AUDIF_CK"}, + {"ADC R", NULL, "ADC CLKGEN"}, + + {"ADC L Mux", "AIN0", "AIN0"}, + {"ADC L Mux", "Left Preamplifier", "PGA L"}, + + {"ADC R Mux", "AIN0", "AIN0"}, + {"ADC R Mux", "Right Preamplifier", "PGA R"}, + + {"PGA L", NULL, "PGA L Mux"}, + {"PGA R", NULL, "PGA R Mux"}, + + {"PGA L Mux", "AIN0", "AIN0"}, + {"PGA L Mux", "AIN1", "AIN1"}, + {"PGA L Mux", "AIN2", "AIN2"}, + + {"PGA R Mux", "AIN0", "AIN0"}, + {"PGA R Mux", "AIN3", "AIN3"}, + {"PGA R Mux", "AIN2", "AIN2"}, + + {"AIN0", NULL, "Mic Bias 0"}, + {"AIN2", NULL, "Mic Bias 2"}, + + {"AIN1", NULL, "Mic Bias 1"}, + {"AIN1", NULL, "Mic Bias 1 DCC pull high"}, + + /* DL Supply */ + {"DL Power Supply", NULL, "AUDGLB"}, + {"DL Power Supply", NULL, "CLKSQ Audio"}, + {"DL Power Supply", NULL, "ZCD13M_CK"}, + {"DL Power Supply", NULL, "AUD_CK"}, + {"DL Power Supply", NULL, "AUDIF_CK"}, + {"DL Power Supply", NULL, "AUDNCP_CK"}, + + {"DL Power Supply", NULL, "NV Regulator"}, + {"DL Power Supply", NULL, "AUD_CLK"}, + {"DL Power Supply", NULL, "IBIST"}, + {"DL Power Supply", NULL, "LDO"}, + {"LDO", NULL, "LDO_REMOTE_SENSE"}, + + /* DL Digital Supply */ + {"DL Digital Clock", NULL, "AUDIO_TOP_AFE_CTL"}, + {"DL Digital Clock", NULL, "AUDIO_TOP_DAC_CTL"}, + {"DL Digital Clock", NULL, "AUDIO_TOP_PWR_CLK"}, + {"DL Digital Clock", NULL, "AUDIO_TOP_PDN_RESERVED"}, + {"DL Digital Clock", NULL, "NCP"}, + {"DL Digital Clock", NULL, "AFE_ON"}, + + {"AIF_RX", NULL, "DL Digital Clock"}, + + /* DL Path */ + {"DAC In Mux", "Normal Path", "AIF_RX"}, + + {"DAC In Mux", "Sgen", "SGEN DL"}, + {"SGEN DL", NULL, "SGEN DL SRC"}, + {"SGEN DL", NULL, "SGEN MUTE"}, + {"SGEN DL", NULL, "SGEN DL Enable"}, + {"SGEN DL", NULL, "DL Digital Clock"}, + + {"DACL", NULL, "DAC In Mux"}, + {"DACL", NULL, "DL Power Supply"}, + {"DACL", NULL, "DACL_BIASGEN"}, + + {"DACR", NULL, "DAC In Mux"}, + {"DACR", NULL, "DL Power Supply"}, + {"DACR", NULL, "DACR_BIASGEN"}, + + {"LOL Mux", "Playback", "DACL"}, + + {"LOL Buffer", NULL, "LOL Mux"}, + {"LOL Buffer", NULL, "LO Stability Enh"}, + {"LOL Buffer", NULL, "LOL Bias Gen"}, + + {"LINEOUT L", NULL, "LOL Buffer"}, + + /* Headphone Path */ + {"HPL Mux", "Audio Playback", "DACL"}, + {"HPR Mux", "Audio Playback", "DACR"}, + + {"HPL Mux", "LoudSPK Playback", "DACL"}, + {"HPR Mux", "LoudSPK Playback", "DACR"}, + + {"HPL Power", NULL, "HPL Mux"}, + {"HPR Power", NULL, "HPR Mux"}, + + {"Headphone L", NULL, "HPL Power"}, + {"Headphone R", NULL, "HPR Power"}, + + /* Receiver Path */ + {"RCV Mux", "Voice Playback", "DACL"}, + + {"RCV Buffer", NULL, "RCV Mux"}, + {"RCV Buffer", NULL, "RCV Stability Enh"}, + {"RCV Buffer", NULL, "RCV Bias Gen"}, + + {"Receiver", NULL, "RCV Buffer"}, +}; + +static int mt6351_codec_init_reg(struct snd_soc_component *cmpnt) +{ + int ret = 0; + + /* Disable CLKSQ 26MHz */ + regmap_update_bits(cmpnt->regmap, MT6351_TOP_CLKSQ, 0x0001, 0x0); + /* disable AUDGLB */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON9, + 0x1000, 0x1000); + /* Turn off AUDNCP_CLKDIV engine clock,Turn off AUD 26M */ + regmap_update_bits(cmpnt->regmap, MT6351_TOP_CKPDN_CON0_SET, + 0x3800, 0x3800); + /* Disable HeadphoneL/HeadphoneR/voice short circuit protection */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON0, + 0xe000, 0xe000); + /* [5] = 1, disable LO buffer left short circuit protection */ + regmap_update_bits(cmpnt->regmap, MT6351_AUDDEC_ANA_CON3, + 0x20, 0x20); + /* Reverse the PMIC clock*/ + regmap_update_bits(cmpnt->regmap, MT6351_AFE_PMIC_NEWIF_CFG2, + 0x8000, 0x8000); + return ret; +} + +static int mt6351_codec_probe(struct snd_soc_component *cmpnt) +{ + struct mt6351_priv *priv = snd_soc_component_get_drvdata(cmpnt); + + snd_soc_component_init_regmap(cmpnt, priv->regmap); + + mt6351_codec_init_reg(cmpnt); + return 0; +} + +static const struct snd_soc_component_driver mt6351_soc_component_driver = { + .probe = mt6351_codec_probe, + .controls = mt6351_snd_controls, + .num_controls = ARRAY_SIZE(mt6351_snd_controls), + .dapm_widgets = mt6351_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt6351_dapm_widgets), + .dapm_routes = mt6351_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(mt6351_dapm_routes), +}; + +static int mt6351_codec_driver_probe(struct platform_device *pdev) +{ + struct mt6351_priv *priv; + + priv = devm_kzalloc(&pdev->dev, + sizeof(struct mt6351_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, priv); + + priv->dev = &pdev->dev; + + priv->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!priv->regmap) + return -ENODEV; + + dev_dbg(priv->dev, "%s(), dev name %s\n", + __func__, dev_name(&pdev->dev)); + + return devm_snd_soc_register_component(&pdev->dev, + &mt6351_soc_component_driver, + mt6351_dai_driver, + ARRAY_SIZE(mt6351_dai_driver)); +} + +static const struct of_device_id mt6351_of_match[] = { + {.compatible = "mediatek,mt6351-sound",}, + {} +}; + +static struct platform_driver mt6351_codec_driver = { + .driver = { + .name = "mt6351-sound", + .of_match_table = mt6351_of_match, + }, + .probe = mt6351_codec_driver_probe, +}; + +module_platform_driver(mt6351_codec_driver) + +/* Module information */ +MODULE_DESCRIPTION("MT6351 ALSA SoC codec driver"); +MODULE_AUTHOR("KaiChieh Chuang "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/mt6351.h b/sound/soc/codecs/mt6351.h new file mode 100644 index 000000000..04b2ab694 --- /dev/null +++ b/sound/soc/codecs/mt6351.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt6351.h -- mt6351 ALSA SoC audio codec driver + * + * Copyright (c) 2018 MediaTek Inc. + * Author: KaiChieh Chuang + */ + +#ifndef __MT6351_H__ +#define __MT6351_H__ + +#define MT6351_AFE_UL_DL_CON0 (0x2000 + 0x0000) +#define MT6351_AFE_DL_SRC2_CON0_H (0x2000 + 0x0002) +#define MT6351_AFE_DL_SRC2_CON0_L (0x2000 + 0x0004) +#define MT6351_AFE_DL_SDM_CON0 (0x2000 + 0x0006) +#define MT6351_AFE_DL_SDM_CON1 (0x2000 + 0x0008) +#define MT6351_AFE_UL_SRC_CON0_H (0x2000 + 0x000a) +#define MT6351_AFE_UL_SRC_CON0_L (0x2000 + 0x000c) +#define MT6351_AFE_UL_SRC_CON1_H (0x2000 + 0x000e) +#define MT6351_AFE_UL_SRC_CON1_L (0x2000 + 0x0010) +#define MT6351_AFE_TOP_CON0 (0x2000 + 0x0012) +#define MT6351_AUDIO_TOP_CON0 (0x2000 + 0x0014) +#define MT6351_AFE_DL_SRC_MON0 (0x2000 + 0x0016) +#define MT6351_AFE_DL_SDM_TEST0 (0x2000 + 0x0018) +#define MT6351_AFE_MON_DEBUG0 (0x2000 + 0x001a) +#define MT6351_AFUNC_AUD_CON0 (0x2000 + 0x001c) +#define MT6351_AFUNC_AUD_CON1 (0x2000 + 0x001e) +#define MT6351_AFUNC_AUD_CON2 (0x2000 + 0x0020) +#define MT6351_AFUNC_AUD_CON3 (0x2000 + 0x0022) +#define MT6351_AFUNC_AUD_CON4 (0x2000 + 0x0024) +#define MT6351_AFUNC_AUD_MON0 (0x2000 + 0x0026) +#define MT6351_AFUNC_AUD_MON1 (0x2000 + 0x0028) +#define MT6351_AFE_UP8X_FIFO_CFG0 (0x2000 + 0x002c) +#define MT6351_AFE_UP8X_FIFO_LOG_MON0 (0x2000 + 0x002e) +#define MT6351_AFE_UP8X_FIFO_LOG_MON1 (0x2000 + 0x0030) +#define MT6351_AFE_DL_DC_COMP_CFG0 (0x2000 + 0x0032) +#define MT6351_AFE_DL_DC_COMP_CFG1 (0x2000 + 0x0034) +#define MT6351_AFE_DL_DC_COMP_CFG2 (0x2000 + 0x0036) +#define MT6351_AFE_PMIC_NEWIF_CFG0 (0x2000 + 0x0038) +#define MT6351_AFE_PMIC_NEWIF_CFG1 (0x2000 + 0x003a) +#define MT6351_AFE_PMIC_NEWIF_CFG2 (0x2000 + 0x003c) +#define MT6351_AFE_PMIC_NEWIF_CFG3 (0x2000 + 0x003e) +#define MT6351_AFE_SGEN_CFG0 (0x2000 + 0x0040) +#define MT6351_AFE_SGEN_CFG1 (0x2000 + 0x0042) +#define MT6351_AFE_ADDA2_UP8X_FIFO_LOG_MON0 (0x2000 + 0x004c) +#define MT6351_AFE_ADDA2_UP8X_FIFO_LOG_MON1 (0x2000 + 0x004e) +#define MT6351_AFE_ADDA2_PMIC_NEWIF_CFG0 (0x2000 + 0x0050) +#define MT6351_AFE_ADDA2_PMIC_NEWIF_CFG1 (0x2000 + 0x0052) +#define MT6351_AFE_ADDA2_PMIC_NEWIF_CFG2 (0x2000 + 0x0054) +#define MT6351_AFE_DCCLK_CFG0 (0x2000 + 0x0090) +#define MT6351_AFE_DCCLK_CFG1 (0x2000 + 0x0092) +#define MT6351_AFE_HPANC_CFG0 (0x2000 + 0x0094) +#define MT6351_AFE_NCP_CFG0 (0x2000 + 0x0096) +#define MT6351_AFE_NCP_CFG1 (0x2000 + 0x0098) + +#define MT6351_TOP_CKPDN_CON0 0x023A +#define MT6351_TOP_CKPDN_CON0_SET 0x023C +#define MT6351_TOP_CKPDN_CON0_CLR 0x023E + +#define MT6351_TOP_CLKSQ 0x029A +#define MT6351_TOP_CLKSQ_SET 0x029C +#define MT6351_TOP_CLKSQ_CLR 0x029E + +#define MT6351_ZCD_CON0 0x0800 +#define MT6351_ZCD_CON1 0x0802 +#define MT6351_ZCD_CON2 0x0804 +#define MT6351_ZCD_CON3 0x0806 +#define MT6351_ZCD_CON4 0x0808 +#define MT6351_ZCD_CON5 0x080A + +#define MT6351_LDO_VA18_CON0 0x0A00 +#define MT6351_LDO_VA18_CON1 0x0A02 +#define MT6351_LDO_VUSB33_CON0 0x0A16 +#define MT6351_LDO_VUSB33_CON1 0x0A18 + +#define MT6351_AUDDEC_ANA_CON0 0x0CF2 +#define MT6351_AUDDEC_ANA_CON1 0x0CF4 +#define MT6351_AUDDEC_ANA_CON2 0x0CF6 +#define MT6351_AUDDEC_ANA_CON3 0x0CF8 +#define MT6351_AUDDEC_ANA_CON4 0x0CFA +#define MT6351_AUDDEC_ANA_CON5 0x0CFC +#define MT6351_AUDDEC_ANA_CON6 0x0CFE +#define MT6351_AUDDEC_ANA_CON7 0x0D00 +#define MT6351_AUDDEC_ANA_CON8 0x0D02 +#define MT6351_AUDDEC_ANA_CON9 0x0D04 +#define MT6351_AUDDEC_ANA_CON10 0x0D06 + +#define MT6351_AUDENC_ANA_CON0 0x0D08 +#define MT6351_AUDENC_ANA_CON1 0x0D0A +#define MT6351_AUDENC_ANA_CON2 0x0D0C +#define MT6351_AUDENC_ANA_CON3 0x0D0E +#define MT6351_AUDENC_ANA_CON4 0x0D10 +#define MT6351_AUDENC_ANA_CON5 0x0D12 +#define MT6351_AUDENC_ANA_CON6 0x0D14 +#define MT6351_AUDENC_ANA_CON7 0x0D16 +#define MT6351_AUDENC_ANA_CON8 0x0D18 +#define MT6351_AUDENC_ANA_CON9 0x0D1A +#define MT6351_AUDENC_ANA_CON10 0x0D1C +#define MT6351_AUDENC_ANA_CON11 0x0D1E +#define MT6351_AUDENC_ANA_CON12 0x0D20 +#define MT6351_AUDENC_ANA_CON13 0x0D22 +#define MT6351_AUDENC_ANA_CON14 0x0D24 +#define MT6351_AUDENC_ANA_CON15 0x0D26 +#define MT6351_AUDENC_ANA_CON16 0x0D28 +#endif diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c new file mode 100644 index 000000000..4dd1a6097 --- /dev/null +++ b/sound/soc/codecs/nau8540.c @@ -0,0 +1,887 @@ +/* + * NAU85L40 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nau8540.h" + + +#define NAU_FREF_MAX 13500000 +#define NAU_FVCO_MAX 100000000 +#define NAU_FVCO_MIN 90000000 + +/* the maximum frequency of CLK_ADC */ +#define CLK_ADC_MAX 6144000 + +/* scaling for mclk from sysclk_src output */ +static const struct nau8540_fll_attr mclk_src_scaling[] = { + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, +}; + +/* ratio for input clk freq */ +static const struct nau8540_fll_attr fll_ratio[] = { + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, +}; + +static const struct nau8540_fll_attr fll_pre_scalar[] = { + { 1, 0x0 }, + { 2, 0x1 }, + { 4, 0x2 }, + { 8, 0x3 }, +}; + +/* over sampling rate */ +static const struct nau8540_osr_attr osr_adc_sel[] = { + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ +}; + +static const struct reg_default nau8540_reg_defaults[] = { + {NAU8540_REG_POWER_MANAGEMENT, 0x0000}, + {NAU8540_REG_CLOCK_CTRL, 0x0000}, + {NAU8540_REG_CLOCK_SRC, 0x0000}, + {NAU8540_REG_FLL1, 0x0001}, + {NAU8540_REG_FLL2, 0x3126}, + {NAU8540_REG_FLL3, 0x0008}, + {NAU8540_REG_FLL4, 0x0010}, + {NAU8540_REG_FLL5, 0xC000}, + {NAU8540_REG_FLL6, 0x6000}, + {NAU8540_REG_FLL_VCO_RSV, 0xF13C}, + {NAU8540_REG_PCM_CTRL0, 0x000B}, + {NAU8540_REG_PCM_CTRL1, 0x3010}, + {NAU8540_REG_PCM_CTRL2, 0x0800}, + {NAU8540_REG_PCM_CTRL3, 0x0000}, + {NAU8540_REG_PCM_CTRL4, 0x000F}, + {NAU8540_REG_ALC_CONTROL_1, 0x0000}, + {NAU8540_REG_ALC_CONTROL_2, 0x700B}, + {NAU8540_REG_ALC_CONTROL_3, 0x0022}, + {NAU8540_REG_ALC_CONTROL_4, 0x1010}, + {NAU8540_REG_ALC_CONTROL_5, 0x1010}, + {NAU8540_REG_NOTCH_FIL1_CH1, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH1, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH2, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH2, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH3, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH3, 0x0000}, + {NAU8540_REG_NOTCH_FIL1_CH4, 0x0000}, + {NAU8540_REG_NOTCH_FIL2_CH4, 0x0000}, + {NAU8540_REG_HPF_FILTER_CH12, 0x0000}, + {NAU8540_REG_HPF_FILTER_CH34, 0x0000}, + {NAU8540_REG_ADC_SAMPLE_RATE, 0x0002}, + {NAU8540_REG_DIGITAL_GAIN_CH1, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH2, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH3, 0x0400}, + {NAU8540_REG_DIGITAL_GAIN_CH4, 0x0400}, + {NAU8540_REG_DIGITAL_MUX, 0x00E4}, + {NAU8540_REG_GPIO_CTRL, 0x0000}, + {NAU8540_REG_MISC_CTRL, 0x0000}, + {NAU8540_REG_I2C_CTRL, 0xEFFF}, + {NAU8540_REG_VMID_CTRL, 0x0000}, + {NAU8540_REG_MUTE, 0x0000}, + {NAU8540_REG_ANALOG_ADC1, 0x0011}, + {NAU8540_REG_ANALOG_ADC2, 0x0020}, + {NAU8540_REG_ANALOG_PWR, 0x0000}, + {NAU8540_REG_MIC_BIAS, 0x0004}, + {NAU8540_REG_REFERENCE, 0x0000}, + {NAU8540_REG_FEPGA1, 0x0000}, + {NAU8540_REG_FEPGA2, 0x0000}, + {NAU8540_REG_FEPGA3, 0x0101}, + {NAU8540_REG_FEPGA4, 0x0101}, + {NAU8540_REG_PWR, 0x0000}, +}; + +static bool nau8540_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8540_REG_POWER_MANAGEMENT ... NAU8540_REG_FLL_VCO_RSV: + case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: + case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: + case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ADC_SAMPLE_RATE: + case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: + case NAU8540_REG_P2P_CH1 ... NAU8540_REG_I2C_CTRL: + case NAU8540_REG_I2C_DEVICE_ID: + case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: + case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: + return true; + default: + return false; + } + +} + +static bool nau8540_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8540_REG_SW_RESET ... NAU8540_REG_FLL_VCO_RSV: + case NAU8540_REG_PCM_CTRL0 ... NAU8540_REG_PCM_CTRL4: + case NAU8540_REG_ALC_CONTROL_1 ... NAU8540_REG_ALC_CONTROL_5: + case NAU8540_REG_NOTCH_FIL1_CH1 ... NAU8540_REG_ADC_SAMPLE_RATE: + case NAU8540_REG_DIGITAL_GAIN_CH1 ... NAU8540_REG_DIGITAL_MUX: + case NAU8540_REG_GPIO_CTRL ... NAU8540_REG_I2C_CTRL: + case NAU8540_REG_RST: + case NAU8540_REG_VMID_CTRL ... NAU8540_REG_MUTE: + case NAU8540_REG_ANALOG_ADC1 ... NAU8540_REG_PWR: + return true; + default: + return false; + } +} + +static bool nau8540_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8540_REG_SW_RESET: + case NAU8540_REG_ALC_GAIN_CH12 ... NAU8540_REG_ALC_STATUS: + case NAU8540_REG_P2P_CH1 ... NAU8540_REG_PEAK_CH4: + case NAU8540_REG_I2C_DEVICE_ID: + case NAU8540_REG_RST: + return true; + default: + return false; + } +} + + +static const DECLARE_TLV_DB_MINMAX(adc_vol_tlv, -12800, 3600); +static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600); + +static const struct snd_kcontrol_new nau8540_snd_controls[] = { + SOC_SINGLE_TLV("Mic1 Volume", NAU8540_REG_DIGITAL_GAIN_CH1, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic2 Volume", NAU8540_REG_DIGITAL_GAIN_CH2, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic3 Volume", NAU8540_REG_DIGITAL_GAIN_CH3, + 0, 0x520, 0, adc_vol_tlv), + SOC_SINGLE_TLV("Mic4 Volume", NAU8540_REG_DIGITAL_GAIN_CH4, + 0, 0x520, 0, adc_vol_tlv), + + SOC_SINGLE_TLV("Frontend PGA1 Volume", NAU8540_REG_FEPGA3, + 0, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA2 Volume", NAU8540_REG_FEPGA3, + 8, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA3 Volume", NAU8540_REG_FEPGA4, + 0, 0x25, 0, fepga_gain_tlv), + SOC_SINGLE_TLV("Frontend PGA4 Volume", NAU8540_REG_FEPGA4, + 8, 0x25, 0, fepga_gain_tlv), +}; + +static const char * const adc_channel[] = { + "ADC channel 1", "ADC channel 2", "ADC channel 3", "ADC channel 4" +}; +static SOC_ENUM_SINGLE_DECL( + digital_ch4_enum, NAU8540_REG_DIGITAL_MUX, 6, adc_channel); + +static const struct snd_kcontrol_new digital_ch4_mux = + SOC_DAPM_ENUM("Digital CH4 Select", digital_ch4_enum); + +static SOC_ENUM_SINGLE_DECL( + digital_ch3_enum, NAU8540_REG_DIGITAL_MUX, 4, adc_channel); + +static const struct snd_kcontrol_new digital_ch3_mux = + SOC_DAPM_ENUM("Digital CH3 Select", digital_ch3_enum); + +static SOC_ENUM_SINGLE_DECL( + digital_ch2_enum, NAU8540_REG_DIGITAL_MUX, 2, adc_channel); + +static const struct snd_kcontrol_new digital_ch2_mux = + SOC_DAPM_ENUM("Digital CH2 Select", digital_ch2_enum); + +static SOC_ENUM_SINGLE_DECL( + digital_ch1_enum, NAU8540_REG_DIGITAL_MUX, 0, adc_channel); + +static const struct snd_kcontrol_new digital_ch1_mux = + SOC_DAPM_ENUM("Digital CH1 Select", digital_ch1_enum); + +static int adc_power_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + msleep(300); + /* DO12 and DO34 pad output enable */ + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_DO12_TRI, 0); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_TRI, 0); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_DO12_TRI, NAU8540_I2S_DO12_TRI); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_TRI, NAU8540_I2S_DO34_TRI); + } + return 0; +} + +static int aiftx_power_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + regmap_write(nau8540->regmap, NAU8540_REG_RST, 0x0001); + regmap_write(nau8540->regmap, NAU8540_REG_RST, 0x0000); + } + return 0; +} + +static const struct snd_soc_dapm_widget nau8540_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("MICBIAS2", NAU8540_REG_MIC_BIAS, 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS1", NAU8540_REG_MIC_BIAS, 10, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("MIC3"), + SND_SOC_DAPM_INPUT("MIC4"), + + SND_SOC_DAPM_PGA("Frontend PGA1", NAU8540_REG_PWR, 12, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA2", NAU8540_REG_PWR, 13, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA3", NAU8540_REG_PWR, 14, 0, NULL, 0), + SND_SOC_DAPM_PGA("Frontend PGA4", NAU8540_REG_PWR, 15, 0, NULL, 0), + + SND_SOC_DAPM_ADC_E("ADC1", NULL, + NAU8540_REG_POWER_MANAGEMENT, 0, 0, adc_power_control, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC2", NULL, + NAU8540_REG_POWER_MANAGEMENT, 1, 0, adc_power_control, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC3", NULL, + NAU8540_REG_POWER_MANAGEMENT, 2, 0, adc_power_control, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("ADC4", NULL, + NAU8540_REG_POWER_MANAGEMENT, 3, 0, adc_power_control, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA("ADC CH1", NAU8540_REG_ANALOG_PWR, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH2", NAU8540_REG_ANALOG_PWR, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH3", NAU8540_REG_ANALOG_PWR, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC CH4", NAU8540_REG_ANALOG_PWR, 3, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Digital CH4 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch4_mux), + SND_SOC_DAPM_MUX("Digital CH3 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch3_mux), + SND_SOC_DAPM_MUX("Digital CH2 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch2_mux), + SND_SOC_DAPM_MUX("Digital CH1 Mux", + SND_SOC_NOPM, 0, 0, &digital_ch1_mux), + + SND_SOC_DAPM_AIF_OUT_E("AIFTX", "Capture", 0, SND_SOC_NOPM, 0, 0, + aiftx_power_control, SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route nau8540_dapm_routes[] = { + {"Frontend PGA1", NULL, "MIC1"}, + {"Frontend PGA2", NULL, "MIC2"}, + {"Frontend PGA3", NULL, "MIC3"}, + {"Frontend PGA4", NULL, "MIC4"}, + + {"ADC1", NULL, "Frontend PGA1"}, + {"ADC2", NULL, "Frontend PGA2"}, + {"ADC3", NULL, "Frontend PGA3"}, + {"ADC4", NULL, "Frontend PGA4"}, + + {"ADC CH1", NULL, "ADC1"}, + {"ADC CH2", NULL, "ADC2"}, + {"ADC CH3", NULL, "ADC3"}, + {"ADC CH4", NULL, "ADC4"}, + + {"ADC1", NULL, "MICBIAS1"}, + {"ADC2", NULL, "MICBIAS1"}, + {"ADC3", NULL, "MICBIAS2"}, + {"ADC4", NULL, "MICBIAS2"}, + + {"Digital CH1 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH1 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH1 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH1 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH2 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH2 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH2 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH2 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH3 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH3 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH3 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH3 Mux", "ADC channel 4", "ADC CH4"}, + + {"Digital CH4 Mux", "ADC channel 1", "ADC CH1"}, + {"Digital CH4 Mux", "ADC channel 2", "ADC CH2"}, + {"Digital CH4 Mux", "ADC channel 3", "ADC CH3"}, + {"Digital CH4 Mux", "ADC channel 4", "ADC CH4"}, + + {"AIFTX", NULL, "Digital CH1 Mux"}, + {"AIFTX", NULL, "Digital CH2 Mux"}, + {"AIFTX", NULL, "Digital CH3 Mux"}, + {"AIFTX", NULL, "Digital CH4 Mux"}, +}; + +static int nau8540_clock_check(struct nau8540 *nau8540, int rate, int osr) +{ + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + + if (rate * osr > CLK_ADC_MAX) { + dev_err(nau8540->dev, "exceed the maximum frequency of CLK_ADC\n"); + return -EINVAL; + } + + return 0; +} + +static int nau8540_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, osr; + + /* CLK_ADC = OSR * FS + * ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + regmap_read(nau8540->regmap, NAU8540_REG_ADC_SAMPLE_RATE, &osr); + osr &= NAU8540_ADC_OSR_MASK; + if (nau8540_clock_check(nau8540, params_rate(params), osr)) + return -EINVAL; + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8540_CLK_ADC_SRC_SFT); + + switch (params_width(params)) { + case 16: + val_len |= NAU8540_I2S_DL_16; + break; + case 20: + val_len |= NAU8540_I2S_DL_20; + break; + case 24: + val_len |= NAU8540_I2S_DL_24; + break; + case 32: + val_len |= NAU8540_I2S_DL_32; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, + NAU8540_I2S_DL_MASK, val_len); + + return 0; +} + +static int nau8540_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8540_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8540_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8540_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8540_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8540_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8540_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8540_I2S_DF_PCM_AB; + ctrl1_val |= NAU8540_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL0, + NAU8540_I2S_DL_MASK | NAU8540_I2S_DF_MASK | + NAU8540_I2S_BP_INV | NAU8540_I2S_PCMB_EN, ctrl1_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_MS_MASK | NAU8540_I2S_DO12_OE, ctrl2_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_OE, 0); + + return 0; +} + +/** + * nau8540_set_tdm_slot - configure DAI TX TDM. + * @dai: DAI + * @tx_mask: bitmask representing active TX slots. Ex. + * 0xf for normal 4 channel TDM. + * 0xf0 for shifted 4 channel TDM + * @rx_mask: no used. + * @slots: Number of slots in use. + * @slot_width: Width in bits for each slot. + * + * Configures a DAI for TDM operation. Only support 4 slots TDM. + */ +static int nau8540_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + unsigned int ctrl2_val = 0, ctrl4_val = 0; + + if (slots > 4 || ((tx_mask & 0xf0) && (tx_mask & 0xf))) + return -EINVAL; + + ctrl4_val |= (NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN); + if (tx_mask & 0xf0) { + ctrl2_val = 4 * slot_width; + ctrl4_val |= (tx_mask >> 4); + } else { + ctrl4_val |= tx_mask; + } + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL4, + NAU8540_TDM_MODE | NAU8540_TDM_OFFSET_EN | + NAU8540_TDM_TX_MASK, ctrl4_val); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_DO12_OE, NAU8540_I2S_DO12_OE); + regmap_update_bits(nau8540->regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_OE | NAU8540_I2S_TSLOT_L_MASK, + NAU8540_I2S_DO34_OE | ctrl2_val); + + return 0; +} + + +static const struct snd_soc_dai_ops nau8540_dai_ops = { + .hw_params = nau8540_hw_params, + .set_fmt = nau8540_set_fmt, + .set_tdm_slot = nau8540_set_tdm_slot, +}; + +#define NAU8540_RATES SNDRV_PCM_RATE_8000_48000 +#define NAU8540_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8540_dai = { + .name = "nau8540-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = NAU8540_RATES, + .formats = NAU8540_FORMATS, + }, + .ops = &nau8540_dai_ops, +}; + +/** + * nau8540_calc_fll_param - Calculate FLL parameters. + * @fll_in: external clock provided to codec. + * @fs: sampling rate. + * @fll_param: Pointer to structure of FLL parameters. + * + * Calculate FLL parameters to configure codec. + * + * Returns 0 for success or negative error code. + */ +static int nau8540_calc_fll_param(unsigned int fll_in, + unsigned int fs, struct nau8540_fll *fll_param) +{ + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing + * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8540_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in / fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256ULL * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 16) & 0x3FF; + fll_param->fll_frac = fvco & 0xFFFF; + return 0; +} + +static void nau8540_fll_apply(struct regmap *regmap, + struct nau8540_fll *fll_param) +{ + regmap_update_bits(regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK | NAU8540_CLK_MCLK_SRC_MASK, + NAU8540_CLK_SRC_MCLK | fll_param->mclk_src); + regmap_update_bits(regmap, NAU8540_REG_FLL1, + NAU8540_FLL_RATIO_MASK | NAU8540_ICTRL_LATCH_MASK, + fll_param->ratio | (0x6 << NAU8540_ICTRL_LATCH_SFT)); + /* FLL 16-bit fractional input */ + regmap_write(regmap, NAU8540_REG_FLL2, fll_param->fll_frac); + /* FLL 10-bit integer input */ + regmap_update_bits(regmap, NAU8540_REG_FLL3, + NAU8540_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(regmap, NAU8540_REG_FLL4, + NAU8540_FLL_REF_DIV_MASK, + fll_param->clk_ref_div << NAU8540_FLL_REF_DIV_SFT); + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_CLK_SW_MASK, NAU8540_FLL_CLK_SW_REF); + regmap_update_bits(regmap, + NAU8540_REG_FLL6, NAU8540_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_MASK, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_FILTER); + regmap_update_bits(regmap, NAU8540_REG_FLL6, + NAU8540_SDM_EN | NAU8540_CUTOFF500, + NAU8540_SDM_EN | NAU8540_CUTOFF500); + } else { + regmap_update_bits(regmap, NAU8540_REG_FLL5, + NAU8540_FLL_PDB_DAC_EN | NAU8540_FLL_LOOP_FTR_EN | + NAU8540_FLL_FTR_SW_MASK, NAU8540_FLL_FTR_SW_ACCU); + regmap_update_bits(regmap, NAU8540_REG_FLL6, + NAU8540_SDM_EN | NAU8540_CUTOFF500, 0); + } +} + +/* freq_out must be 256*Fs in order to achieve the best performance */ +static int nau8540_set_pll(struct snd_soc_component *component, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + struct nau8540_fll fll_param; + int ret, fs; + + switch (pll_id) { + case NAU8540_CLK_FLL_MCLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK | NAU8540_GAIN_ERR_MASK, + NAU8540_FLL_CLK_SRC_MCLK | 0); + break; + + case NAU8540_CLK_FLL_BLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK | NAU8540_GAIN_ERR_MASK, + NAU8540_FLL_CLK_SRC_BLK | + (0xf << NAU8540_GAIN_ERR_SFT)); + break; + + case NAU8540_CLK_FLL_FS: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL3, + NAU8540_FLL_CLK_SRC_MASK | NAU8540_GAIN_ERR_MASK, + NAU8540_FLL_CLK_SRC_FS | + (0xf << NAU8540_GAIN_ERR_SFT)); + break; + + default: + dev_err(nau8540->dev, "Invalid clock id (%d)\n", pll_id); + return -EINVAL; + } + dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", + freq_out, pll_id); + + fs = freq_out / 256; + ret = nau8540_calc_fll_param(freq_in, fs, &fll_param); + if (ret < 0) { + dev_err(nau8540->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_dbg(nau8540->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, + fll_param.fll_int, fll_param.clk_ref_div); + + nau8540_fll_apply(nau8540->regmap, &fll_param); + mdelay(2); + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); + + return 0; +} + +static int nau8540_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case NAU8540_CLK_DIS: + case NAU8540_CLK_MCLK: + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_MCLK); + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, + NAU8540_DCO_EN, 0); + break; + + case NAU8540_CLK_INTERNAL: + regmap_update_bits(nau8540->regmap, NAU8540_REG_FLL6, + NAU8540_DCO_EN, NAU8540_DCO_EN); + regmap_update_bits(nau8540->regmap, NAU8540_REG_CLOCK_SRC, + NAU8540_CLK_SRC_MASK, NAU8540_CLK_SRC_VCO); + break; + + default: + dev_err(nau8540->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + dev_dbg(nau8540->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; +} + +static void nau8540_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); + regmap_write(regmap, NAU8540_REG_SW_RESET, 0x00); +} + +static void nau8540_init_regs(struct nau8540 *nau8540) +{ + struct regmap *regmap = nau8540->regmap; + + /* Enable Bias/VMID/VMID Tieoff */ + regmap_update_bits(regmap, NAU8540_REG_VMID_CTRL, + NAU8540_VMID_EN | NAU8540_VMID_SEL_MASK, + NAU8540_VMID_EN | (0x2 << NAU8540_VMID_SEL_SFT)); + regmap_update_bits(regmap, NAU8540_REG_REFERENCE, + NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN, + NAU8540_PRECHARGE_DIS | NAU8540_GLOBAL_BIAS_EN); + mdelay(2); + regmap_update_bits(regmap, NAU8540_REG_MIC_BIAS, + NAU8540_PU_PRE, NAU8540_PU_PRE); + regmap_update_bits(regmap, NAU8540_REG_CLOCK_CTRL, + NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN, + NAU8540_CLK_ADC_EN | NAU8540_CLK_I2S_EN); + /* ADC OSR selection, CLK_ADC = Fs * OSR; + * Channel time alignment enable. + */ + regmap_update_bits(regmap, NAU8540_REG_ADC_SAMPLE_RATE, + NAU8540_CH_SYNC | NAU8540_ADC_OSR_MASK, + NAU8540_CH_SYNC | NAU8540_ADC_OSR_64); + /* PGA input mode selection */ + regmap_update_bits(regmap, NAU8540_REG_FEPGA1, + NAU8540_FEPGA1_MODCH2_SHT | NAU8540_FEPGA1_MODCH1_SHT, + NAU8540_FEPGA1_MODCH2_SHT | NAU8540_FEPGA1_MODCH1_SHT); + regmap_update_bits(regmap, NAU8540_REG_FEPGA2, + NAU8540_FEPGA2_MODCH4_SHT | NAU8540_FEPGA2_MODCH3_SHT, + NAU8540_FEPGA2_MODCH4_SHT | NAU8540_FEPGA2_MODCH3_SHT); + /* DO12 and DO34 pad output disable */ + regmap_update_bits(regmap, NAU8540_REG_PCM_CTRL1, + NAU8540_I2S_DO12_TRI, NAU8540_I2S_DO12_TRI); + regmap_update_bits(regmap, NAU8540_REG_PCM_CTRL2, + NAU8540_I2S_DO34_TRI, NAU8540_I2S_DO34_TRI); +} + +static int __maybe_unused nau8540_suspend(struct snd_soc_component *component) +{ + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(nau8540->regmap, true); + regcache_mark_dirty(nau8540->regmap); + + return 0; +} + +static int __maybe_unused nau8540_resume(struct snd_soc_component *component) +{ + struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(nau8540->regmap, false); + regcache_sync(nau8540->regmap); + + return 0; +} + +static const struct snd_soc_component_driver nau8540_component_driver = { + .set_sysclk = nau8540_set_sysclk, + .set_pll = nau8540_set_pll, + .suspend = nau8540_suspend, + .resume = nau8540_resume, + .controls = nau8540_snd_controls, + .num_controls = ARRAY_SIZE(nau8540_snd_controls), + .dapm_widgets = nau8540_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8540_dapm_widgets), + .dapm_routes = nau8540_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8540_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config nau8540_regmap_config = { + .val_bits = 16, + .reg_bits = 16, + + .max_register = NAU8540_REG_MAX, + .readable_reg = nau8540_readable_reg, + .writeable_reg = nau8540_writeable_reg, + .volatile_reg = nau8540_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8540_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8540_reg_defaults), +}; + +static int nau8540_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8540 *nau8540 = dev_get_platdata(dev); + int ret, value; + + if (!nau8540) { + nau8540 = devm_kzalloc(dev, sizeof(*nau8540), GFP_KERNEL); + if (!nau8540) + return -ENOMEM; + } + i2c_set_clientdata(i2c, nau8540); + + nau8540->regmap = devm_regmap_init_i2c(i2c, &nau8540_regmap_config); + if (IS_ERR(nau8540->regmap)) + return PTR_ERR(nau8540->regmap); + ret = regmap_read(nau8540->regmap, NAU8540_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU85L40: %d\n", + ret); + return ret; + } + + nau8540->dev = dev; + nau8540_reset_chip(nau8540->regmap); + nau8540_init_regs(nau8540); + + return devm_snd_soc_register_component(dev, + &nau8540_component_driver, &nau8540_dai, 1); +} + +static const struct i2c_device_id nau8540_i2c_ids[] = { + { "nau8540", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8540_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id nau8540_of_ids[] = { + { .compatible = "nuvoton,nau8540", }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8540_of_ids); +#endif + +static struct i2c_driver nau8540_i2c_driver = { + .driver = { + .name = "nau8540", + .of_match_table = of_match_ptr(nau8540_of_ids), + }, + .probe = nau8540_i2c_probe, + .id_table = nau8540_i2c_ids, +}; +module_i2c_driver(nau8540_i2c_driver); + +MODULE_DESCRIPTION("ASoC NAU85L40 driver"); +MODULE_AUTHOR("John Hsu "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/nau8540.h b/sound/soc/codecs/nau8540.h new file mode 100644 index 000000000..732b490ed --- /dev/null +++ b/sound/soc/codecs/nau8540.h @@ -0,0 +1,242 @@ +/* + * NAU85L40 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __NAU8540_H__ +#define __NAU8540_H__ + +#define NAU8540_REG_SW_RESET 0x00 +#define NAU8540_REG_POWER_MANAGEMENT 0x01 +#define NAU8540_REG_CLOCK_CTRL 0x02 +#define NAU8540_REG_CLOCK_SRC 0x03 +#define NAU8540_REG_FLL1 0x04 +#define NAU8540_REG_FLL2 0x05 +#define NAU8540_REG_FLL3 0x06 +#define NAU8540_REG_FLL4 0x07 +#define NAU8540_REG_FLL5 0x08 +#define NAU8540_REG_FLL6 0x09 +#define NAU8540_REG_FLL_VCO_RSV 0x0A +#define NAU8540_REG_PCM_CTRL0 0x10 +#define NAU8540_REG_PCM_CTRL1 0x11 +#define NAU8540_REG_PCM_CTRL2 0x12 +#define NAU8540_REG_PCM_CTRL3 0x13 +#define NAU8540_REG_PCM_CTRL4 0x14 +#define NAU8540_REG_ALC_CONTROL_1 0x20 +#define NAU8540_REG_ALC_CONTROL_2 0x21 +#define NAU8540_REG_ALC_CONTROL_3 0x22 +#define NAU8540_REG_ALC_CONTROL_4 0x23 +#define NAU8540_REG_ALC_CONTROL_5 0x24 +#define NAU8540_REG_ALC_GAIN_CH12 0x2D +#define NAU8540_REG_ALC_GAIN_CH34 0x2E +#define NAU8540_REG_ALC_STATUS 0x2F +#define NAU8540_REG_NOTCH_FIL1_CH1 0x30 +#define NAU8540_REG_NOTCH_FIL2_CH1 0x31 +#define NAU8540_REG_NOTCH_FIL1_CH2 0x32 +#define NAU8540_REG_NOTCH_FIL2_CH2 0x33 +#define NAU8540_REG_NOTCH_FIL1_CH3 0x34 +#define NAU8540_REG_NOTCH_FIL2_CH3 0x35 +#define NAU8540_REG_NOTCH_FIL1_CH4 0x36 +#define NAU8540_REG_NOTCH_FIL2_CH4 0x37 +#define NAU8540_REG_HPF_FILTER_CH12 0x38 +#define NAU8540_REG_HPF_FILTER_CH34 0x39 +#define NAU8540_REG_ADC_SAMPLE_RATE 0x3A +#define NAU8540_REG_DIGITAL_GAIN_CH1 0x40 +#define NAU8540_REG_DIGITAL_GAIN_CH2 0x41 +#define NAU8540_REG_DIGITAL_GAIN_CH3 0x42 +#define NAU8540_REG_DIGITAL_GAIN_CH4 0x43 +#define NAU8540_REG_DIGITAL_MUX 0x44 +#define NAU8540_REG_P2P_CH1 0x48 +#define NAU8540_REG_P2P_CH2 0x49 +#define NAU8540_REG_P2P_CH3 0x4A +#define NAU8540_REG_P2P_CH4 0x4B +#define NAU8540_REG_PEAK_CH1 0x4C +#define NAU8540_REG_PEAK_CH2 0x4D +#define NAU8540_REG_PEAK_CH3 0x4E +#define NAU8540_REG_PEAK_CH4 0x4F +#define NAU8540_REG_GPIO_CTRL 0x50 +#define NAU8540_REG_MISC_CTRL 0x51 +#define NAU8540_REG_I2C_CTRL 0x52 +#define NAU8540_REG_I2C_DEVICE_ID 0x58 +#define NAU8540_REG_RST 0x5A +#define NAU8540_REG_VMID_CTRL 0x60 +#define NAU8540_REG_MUTE 0x61 +#define NAU8540_REG_ANALOG_ADC1 0x64 +#define NAU8540_REG_ANALOG_ADC2 0x65 +#define NAU8540_REG_ANALOG_PWR 0x66 +#define NAU8540_REG_MIC_BIAS 0x67 +#define NAU8540_REG_REFERENCE 0x68 +#define NAU8540_REG_FEPGA1 0x69 +#define NAU8540_REG_FEPGA2 0x6A +#define NAU8540_REG_FEPGA3 0x6B +#define NAU8540_REG_FEPGA4 0x6C +#define NAU8540_REG_PWR 0x6D +#define NAU8540_REG_MAX NAU8540_REG_PWR + + +/* POWER_MANAGEMENT (0x01) */ +#define NAU8540_ADC4_EN (0x1 << 3) +#define NAU8540_ADC3_EN (0x1 << 2) +#define NAU8540_ADC2_EN (0x1 << 1) +#define NAU8540_ADC1_EN 0x1 + +/* CLOCK_CTRL (0x02) */ +#define NAU8540_CLK_ADC_EN (0x1 << 15) +#define NAU8540_CLK_I2S_EN (0x1 << 1) + +/* CLOCK_SRC (0x03) */ +#define NAU8540_CLK_SRC_SFT 15 +#define NAU8540_CLK_SRC_MASK (1 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_SRC_VCO (1 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_SRC_MCLK (0 << NAU8540_CLK_SRC_SFT) +#define NAU8540_CLK_ADC_SRC_SFT 6 +#define NAU8540_CLK_ADC_SRC_MASK (0x3 << NAU8540_CLK_ADC_SRC_SFT) +#define NAU8540_CLK_MCLK_SRC_MASK 0xf + +/* FLL1 (0x04) */ +#define NAU8540_ICTRL_LATCH_SFT 10 +#define NAU8540_ICTRL_LATCH_MASK (0x7 << NAU8540_ICTRL_LATCH_SFT) +#define NAU8540_FLL_RATIO_MASK 0x7f + +/* FLL3 (0x06) */ +#define NAU8540_GAIN_ERR_SFT 12 +#define NAU8540_GAIN_ERR_MASK (0xf << NAU8540_GAIN_ERR_SFT) +#define NAU8540_FLL_CLK_SRC_SFT 10 +#define NAU8540_FLL_CLK_SRC_MASK (0x3 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_MCLK (0 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_BLK (0x2 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_CLK_SRC_FS (0x3 << NAU8540_FLL_CLK_SRC_SFT) +#define NAU8540_FLL_INTEGER_MASK 0x3ff + +/* FLL4 (0x07) */ +#define NAU8540_FLL_REF_DIV_SFT 10 +#define NAU8540_FLL_REF_DIV_MASK (0x3 << NAU8540_FLL_REF_DIV_SFT) + +/* FLL5 (0x08) */ +#define NAU8540_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8540_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8540_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8540_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8540_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8540_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8540_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8540_FLL_FTR_SW_FILTER (0x0 << 12) + +/* FLL6 (0x9) */ +#define NAU8540_DCO_EN (0x1 << 15) +#define NAU8540_SDM_EN (0x1 << 14) +#define NAU8540_CUTOFF500 (0x1 << 13) + +/* PCM_CTRL0 (0x10) */ +#define NAU8540_I2S_BP_SFT 7 +#define NAU8540_I2S_BP_INV (0x1 << NAU8540_I2S_BP_SFT) +#define NAU8540_I2S_PCMB_SFT 6 +#define NAU8540_I2S_PCMB_EN (0x1 << NAU8540_I2S_PCMB_SFT) +#define NAU8540_I2S_DL_SFT 2 +#define NAU8540_I2S_DL_MASK (0x3 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_16 (0 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_20 (0x1 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_24 (0x2 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DL_32 (0x3 << NAU8540_I2S_DL_SFT) +#define NAU8540_I2S_DF_MASK 0x3 +#define NAU8540_I2S_DF_RIGTH 0 +#define NAU8540_I2S_DF_LEFT 0x1 +#define NAU8540_I2S_DF_I2S 0x2 +#define NAU8540_I2S_DF_PCM_AB 0x3 + +/* PCM_CTRL1 (0x11) */ +#define NAU8540_I2S_DO12_TRI (0x1 << 15) +#define NAU8540_I2S_LRC_DIV_SFT 12 +#define NAU8540_I2S_LRC_DIV_MASK (0x3 << NAU8540_I2S_LRC_DIV_SFT) +#define NAU8540_I2S_DO12_OE (0x1 << 4) +#define NAU8540_I2S_MS_SFT 3 +#define NAU8540_I2S_MS_MASK (0x1 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_MS_MASTER (0x1 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_MS_SLAVE (0x0 << NAU8540_I2S_MS_SFT) +#define NAU8540_I2S_BLK_DIV_MASK 0x7 + +/* PCM_CTRL1 (0x12) */ +#define NAU8540_I2S_DO34_TRI (0x1 << 15) +#define NAU8540_I2S_DO34_OE (0x1 << 11) +#define NAU8540_I2S_TSLOT_L_MASK 0x3ff + +/* PCM_CTRL4 (0x14) */ +#define NAU8540_TDM_MODE (0x1 << 15) +#define NAU8540_TDM_OFFSET_EN (0x1 << 14) +#define NAU8540_TDM_TX_MASK 0xf + +/* ADC_SAMPLE_RATE (0x3A) */ +#define NAU8540_CH_SYNC (0x1 << 14) +#define NAU8540_ADC_OSR_MASK 0x3 +#define NAU8540_ADC_OSR_256 0x3 +#define NAU8540_ADC_OSR_128 0x2 +#define NAU8540_ADC_OSR_64 0x1 +#define NAU8540_ADC_OSR_32 0x0 + +/* VMID_CTRL (0x60) */ +#define NAU8540_VMID_EN (1 << 6) +#define NAU8540_VMID_SEL_SFT 4 +#define NAU8540_VMID_SEL_MASK (0x3 << NAU8540_VMID_SEL_SFT) + +/* MIC_BIAS (0x67) */ +#define NAU8540_PU_PRE (0x1 << 8) + +/* REFERENCE (0x68) */ +#define NAU8540_PRECHARGE_DIS (0x1 << 13) +#define NAU8540_GLOBAL_BIAS_EN (0x1 << 12) + +/* FEPGA1 (0x69) */ +#define NAU8540_FEPGA1_MODCH2_SHT_SFT 7 +#define NAU8540_FEPGA1_MODCH2_SHT (0x1 << NAU8540_FEPGA1_MODCH2_SHT_SFT) +#define NAU8540_FEPGA1_MODCH1_SHT_SFT 3 +#define NAU8540_FEPGA1_MODCH1_SHT (0x1 << NAU8540_FEPGA1_MODCH1_SHT_SFT) + +/* FEPGA2 (0x6A) */ +#define NAU8540_FEPGA2_MODCH4_SHT_SFT 7 +#define NAU8540_FEPGA2_MODCH4_SHT (0x1 << NAU8540_FEPGA2_MODCH4_SHT_SFT) +#define NAU8540_FEPGA2_MODCH3_SHT_SFT 3 +#define NAU8540_FEPGA2_MODCH3_SHT (0x1 << NAU8540_FEPGA2_MODCH3_SHT_SFT) + + +/* System Clock Source */ +enum { + NAU8540_CLK_DIS, + NAU8540_CLK_MCLK, + NAU8540_CLK_INTERNAL, + NAU8540_CLK_FLL_MCLK, + NAU8540_CLK_FLL_BLK, + NAU8540_CLK_FLL_FS, +}; + +struct nau8540 { + struct device *dev; + struct regmap *regmap; +}; + +struct nau8540_fll { + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; +}; + +struct nau8540_fll_attr { + unsigned int param; + unsigned int val; +}; + +/* over sampling rate */ +struct nau8540_osr_attr { + unsigned int osr; + unsigned int clk_src; +}; + + +#endif /* __NAU8540_H__ */ diff --git a/sound/soc/codecs/nau8810.c b/sound/soc/codecs/nau8810.c new file mode 100644 index 000000000..645aa0794 --- /dev/null +++ b/sound/soc/codecs/nau8810.c @@ -0,0 +1,874 @@ +/* + * nau8810.c -- NAU8810 ALSA Soc Audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * + * Author: David Lin + * + * Based on WM8974.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nau8810.h" + +#define NAU_PLL_FREQ_MAX 100000000 +#define NAU_PLL_FREQ_MIN 90000000 +#define NAU_PLL_REF_MAX 33000000 +#define NAU_PLL_REF_MIN 8000000 +#define NAU_PLL_OPTOP_MIN 6 + + +static const int nau8810_mclk_scaler[] = { 10, 15, 20, 30, 40, 60, 80, 120 }; + +static const struct reg_default nau8810_reg_defaults[] = { + { NAU8810_REG_POWER1, 0x0000 }, + { NAU8810_REG_POWER2, 0x0000 }, + { NAU8810_REG_POWER3, 0x0000 }, + { NAU8810_REG_IFACE, 0x0050 }, + { NAU8810_REG_COMP, 0x0000 }, + { NAU8810_REG_CLOCK, 0x0140 }, + { NAU8810_REG_SMPLR, 0x0000 }, + { NAU8810_REG_DAC, 0x0000 }, + { NAU8810_REG_DACGAIN, 0x00FF }, + { NAU8810_REG_ADC, 0x0100 }, + { NAU8810_REG_ADCGAIN, 0x00FF }, + { NAU8810_REG_EQ1, 0x012C }, + { NAU8810_REG_EQ2, 0x002C }, + { NAU8810_REG_EQ3, 0x002C }, + { NAU8810_REG_EQ4, 0x002C }, + { NAU8810_REG_EQ5, 0x002C }, + { NAU8810_REG_DACLIM1, 0x0032 }, + { NAU8810_REG_DACLIM2, 0x0000 }, + { NAU8810_REG_NOTCH1, 0x0000 }, + { NAU8810_REG_NOTCH2, 0x0000 }, + { NAU8810_REG_NOTCH3, 0x0000 }, + { NAU8810_REG_NOTCH4, 0x0000 }, + { NAU8810_REG_ALC1, 0x0038 }, + { NAU8810_REG_ALC2, 0x000B }, + { NAU8810_REG_ALC3, 0x0032 }, + { NAU8810_REG_NOISEGATE, 0x0000 }, + { NAU8810_REG_PLLN, 0x0008 }, + { NAU8810_REG_PLLK1, 0x000C }, + { NAU8810_REG_PLLK2, 0x0093 }, + { NAU8810_REG_PLLK3, 0x00E9 }, + { NAU8810_REG_ATTEN, 0x0000 }, + { NAU8810_REG_INPUT_SIGNAL, 0x0003 }, + { NAU8810_REG_PGAGAIN, 0x0010 }, + { NAU8810_REG_ADCBOOST, 0x0100 }, + { NAU8810_REG_OUTPUT, 0x0002 }, + { NAU8810_REG_SPKMIX, 0x0001 }, + { NAU8810_REG_SPKGAIN, 0x0039 }, + { NAU8810_REG_MONOMIX, 0x0001 }, + { NAU8810_REG_POWER4, 0x0000 }, + { NAU8810_REG_TSLOTCTL1, 0x0000 }, + { NAU8810_REG_TSLOTCTL2, 0x0020 }, + { NAU8810_REG_DEVICE_REVID, 0x0000 }, + { NAU8810_REG_I2C_DEVICEID, 0x001A }, + { NAU8810_REG_ADDITIONID, 0x00CA }, + { NAU8810_REG_RESERVE, 0x0124 }, + { NAU8810_REG_OUTCTL, 0x0001 }, + { NAU8810_REG_ALC1ENHAN1, 0x0010 }, + { NAU8810_REG_ALC1ENHAN2, 0x0000 }, + { NAU8810_REG_MISCCTL, 0x0000 }, + { NAU8810_REG_OUTTIEOFF, 0x0000 }, + { NAU8810_REG_AGCP2POUT, 0x0000 }, + { NAU8810_REG_AGCPOUT, 0x0000 }, + { NAU8810_REG_AMTCTL, 0x0000 }, + { NAU8810_REG_OUTTIEOFFMAN, 0x0000 }, +}; + +static bool nau8810_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8810_REG_RESET ... NAU8810_REG_SMPLR: + case NAU8810_REG_DAC ... NAU8810_REG_DACGAIN: + case NAU8810_REG_ADC ... NAU8810_REG_ADCGAIN: + case NAU8810_REG_EQ1 ... NAU8810_REG_EQ5: + case NAU8810_REG_DACLIM1 ... NAU8810_REG_DACLIM2: + case NAU8810_REG_NOTCH1 ... NAU8810_REG_NOTCH4: + case NAU8810_REG_ALC1 ... NAU8810_REG_ATTEN: + case NAU8810_REG_INPUT_SIGNAL ... NAU8810_REG_PGAGAIN: + case NAU8810_REG_ADCBOOST: + case NAU8810_REG_OUTPUT ... NAU8810_REG_SPKMIX: + case NAU8810_REG_SPKGAIN: + case NAU8810_REG_MONOMIX: + case NAU8810_REG_POWER4 ... NAU8810_REG_TSLOTCTL2: + case NAU8810_REG_DEVICE_REVID ... NAU8810_REG_RESERVE: + case NAU8810_REG_OUTCTL ... NAU8810_REG_ALC1ENHAN2: + case NAU8810_REG_MISCCTL: + case NAU8810_REG_OUTTIEOFF ... NAU8810_REG_OUTTIEOFFMAN: + return true; + default: + return false; + } +} + +static bool nau8810_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8810_REG_RESET ... NAU8810_REG_SMPLR: + case NAU8810_REG_DAC ... NAU8810_REG_DACGAIN: + case NAU8810_REG_ADC ... NAU8810_REG_ADCGAIN: + case NAU8810_REG_EQ1 ... NAU8810_REG_EQ5: + case NAU8810_REG_DACLIM1 ... NAU8810_REG_DACLIM2: + case NAU8810_REG_NOTCH1 ... NAU8810_REG_NOTCH4: + case NAU8810_REG_ALC1 ... NAU8810_REG_ATTEN: + case NAU8810_REG_INPUT_SIGNAL ... NAU8810_REG_PGAGAIN: + case NAU8810_REG_ADCBOOST: + case NAU8810_REG_OUTPUT ... NAU8810_REG_SPKMIX: + case NAU8810_REG_SPKGAIN: + case NAU8810_REG_MONOMIX: + case NAU8810_REG_POWER4 ... NAU8810_REG_TSLOTCTL2: + case NAU8810_REG_OUTCTL ... NAU8810_REG_ALC1ENHAN2: + case NAU8810_REG_MISCCTL: + case NAU8810_REG_OUTTIEOFF ... NAU8810_REG_OUTTIEOFFMAN: + return true; + default: + return false; + } +} + +static bool nau8810_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8810_REG_RESET: + case NAU8810_REG_DEVICE_REVID ... NAU8810_REG_RESERVE: + return true; + default: + return false; + } +} + +/* The EQ parameters get function is to get the 5 band equalizer control. + * The regmap raw read can't work here because regmap doesn't provide + * value format for value width of 9 bits. Therefore, the driver reads data + * from cache and makes value format according to the endianness of + * bytes type control element. + */ +static int nau8810_eq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + int i, reg, reg_val; + u16 *val; + + val = (u16 *)ucontrol->value.bytes.data; + reg = NAU8810_REG_EQ1; + for (i = 0; i < params->max / sizeof(u16); i++) { + regmap_read(nau8810->regmap, reg + i, ®_val); + /* conversion of 16-bit integers between native CPU format + * and big endian format + */ + reg_val = cpu_to_be16(reg_val); + memcpy(val + i, ®_val, sizeof(reg_val)); + } + + return 0; +} + +/* The EQ parameters put function is to make configuration of 5 band equalizer + * control. These configuration includes central frequency, equalizer gain, + * cut-off frequency, bandwidth control, and equalizer path. + * The regmap raw write can't work here because regmap doesn't provide + * register and value format for register with address 7 bits and value 9 bits. + * Therefore, the driver makes value format according to the endianness of + * bytes type control element and writes data to codec. + */ +static int nau8810_eq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + void *data; + u16 *val, value; + int i, reg, ret; + + data = kmemdup(ucontrol->value.bytes.data, + params->max, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + val = (u16 *)data; + reg = NAU8810_REG_EQ1; + for (i = 0; i < params->max / sizeof(u16); i++) { + /* conversion of 16-bit integers between native CPU format + * and big endian format + */ + value = be16_to_cpu(*(val + i)); + ret = regmap_write(nau8810->regmap, reg + i, value); + if (ret) { + dev_err(component->dev, "EQ configuration fail, register: %x ret: %d\n", + reg + i, ret); + kfree(data); + return ret; + } + } + kfree(data); + + return 0; +} + +static const char * const nau8810_companding[] = { + "Off", "NC", "u-law", "A-law" }; + +static const struct soc_enum nau8810_companding_adc_enum = + SOC_ENUM_SINGLE(NAU8810_REG_COMP, NAU8810_ADCCM_SFT, + ARRAY_SIZE(nau8810_companding), nau8810_companding); + +static const struct soc_enum nau8810_companding_dac_enum = + SOC_ENUM_SINGLE(NAU8810_REG_COMP, NAU8810_DACCM_SFT, + ARRAY_SIZE(nau8810_companding), nau8810_companding); + +static const char * const nau8810_deemp[] = { + "None", "32kHz", "44.1kHz", "48kHz" }; + +static const struct soc_enum nau8810_deemp_enum = + SOC_ENUM_SINGLE(NAU8810_REG_DAC, NAU8810_DEEMP_SFT, + ARRAY_SIZE(nau8810_deemp), nau8810_deemp); + +static const char * const nau8810_eqmode[] = {"Capture", "Playback" }; + +static const struct soc_enum nau8810_eqmode_enum = + SOC_ENUM_SINGLE(NAU8810_REG_EQ1, NAU8810_EQM_SFT, + ARRAY_SIZE(nau8810_eqmode), nau8810_eqmode); + +static const char * const nau8810_alc[] = {"Normal", "Limiter" }; + +static const struct soc_enum nau8810_alc_enum = + SOC_ENUM_SINGLE(NAU8810_REG_ALC3, NAU8810_ALCM_SFT, + ARRAY_SIZE(nau8810_alc), nau8810_alc); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0); + +static const struct snd_kcontrol_new nau8810_snd_controls[] = { + SOC_ENUM("ADC Companding", nau8810_companding_adc_enum), + SOC_ENUM("DAC Companding", nau8810_companding_dac_enum), + SOC_ENUM("DAC De-emphasis", nau8810_deemp_enum), + + SOC_ENUM("EQ Function", nau8810_eqmode_enum), + SND_SOC_BYTES_EXT("EQ Parameters", 10, + nau8810_eq_get, nau8810_eq_put), + + SOC_SINGLE("DAC Inversion Switch", NAU8810_REG_DAC, + NAU8810_DACPL_SFT, 1, 0), + SOC_SINGLE_TLV("Playback Volume", NAU8810_REG_DACGAIN, + NAU8810_DACGAIN_SFT, 0xff, 0, digital_tlv), + + SOC_SINGLE("High Pass Filter Switch", NAU8810_REG_ADC, + NAU8810_HPFEN_SFT, 1, 0), + SOC_SINGLE("High Pass Cut Off", NAU8810_REG_ADC, + NAU8810_HPF_SFT, 0x7, 0), + + SOC_SINGLE("ADC Inversion Switch", NAU8810_REG_ADC, + NAU8810_ADCPL_SFT, 1, 0), + SOC_SINGLE_TLV("Capture Volume", NAU8810_REG_ADCGAIN, + NAU8810_ADCGAIN_SFT, 0xff, 0, digital_tlv), + + SOC_SINGLE_TLV("EQ1 Volume", NAU8810_REG_EQ1, + NAU8810_EQ1GC_SFT, 0x18, 1, eq_tlv), + SOC_SINGLE_TLV("EQ2 Volume", NAU8810_REG_EQ2, + NAU8810_EQ2GC_SFT, 0x18, 1, eq_tlv), + SOC_SINGLE_TLV("EQ3 Volume", NAU8810_REG_EQ3, + NAU8810_EQ3GC_SFT, 0x18, 1, eq_tlv), + SOC_SINGLE_TLV("EQ4 Volume", NAU8810_REG_EQ4, + NAU8810_EQ4GC_SFT, 0x18, 1, eq_tlv), + SOC_SINGLE_TLV("EQ5 Volume", NAU8810_REG_EQ5, + NAU8810_EQ5GC_SFT, 0x18, 1, eq_tlv), + + SOC_SINGLE("DAC Limiter Switch", NAU8810_REG_DACLIM1, + NAU8810_DACLIMEN_SFT, 1, 0), + SOC_SINGLE("DAC Limiter Decay", NAU8810_REG_DACLIM1, + NAU8810_DACLIMDCY_SFT, 0xf, 0), + SOC_SINGLE("DAC Limiter Attack", NAU8810_REG_DACLIM1, + NAU8810_DACLIMATK_SFT, 0xf, 0), + SOC_SINGLE("DAC Limiter Threshold", NAU8810_REG_DACLIM2, + NAU8810_DACLIMTHL_SFT, 0x7, 0), + SOC_SINGLE("DAC Limiter Boost", NAU8810_REG_DACLIM2, + NAU8810_DACLIMBST_SFT, 0xf, 0), + + SOC_ENUM("ALC Mode", nau8810_alc_enum), + SOC_SINGLE("ALC Enable Switch", NAU8810_REG_ALC1, + NAU8810_ALCEN_SFT, 1, 0), + SOC_SINGLE("ALC Max Volume", NAU8810_REG_ALC1, + NAU8810_ALCMXGAIN_SFT, 0x7, 0), + SOC_SINGLE("ALC Min Volume", NAU8810_REG_ALC1, + NAU8810_ALCMINGAIN_SFT, 0x7, 0), + SOC_SINGLE("ALC ZC Switch", NAU8810_REG_ALC2, + NAU8810_ALCZC_SFT, 1, 0), + SOC_SINGLE("ALC Hold", NAU8810_REG_ALC2, + NAU8810_ALCHT_SFT, 0xf, 0), + SOC_SINGLE("ALC Target", NAU8810_REG_ALC2, + NAU8810_ALCSL_SFT, 0xf, 0), + SOC_SINGLE("ALC Decay", NAU8810_REG_ALC3, + NAU8810_ALCDCY_SFT, 0xf, 0), + SOC_SINGLE("ALC Attack", NAU8810_REG_ALC3, + NAU8810_ALCATK_SFT, 0xf, 0), + SOC_SINGLE("ALC Noise Gate Switch", NAU8810_REG_NOISEGATE, + NAU8810_ALCNEN_SFT, 1, 0), + SOC_SINGLE("ALC Noise Gate Threshold", NAU8810_REG_NOISEGATE, + NAU8810_ALCNTH_SFT, 0x7, 0), + + SOC_SINGLE("PGA ZC Switch", NAU8810_REG_PGAGAIN, + NAU8810_PGAZC_SFT, 1, 0), + SOC_SINGLE_TLV("PGA Volume", NAU8810_REG_PGAGAIN, + NAU8810_PGAGAIN_SFT, 0x3f, 0, inpga_tlv), + + SOC_SINGLE("Speaker ZC Switch", NAU8810_REG_SPKGAIN, + NAU8810_SPKZC_SFT, 1, 0), + SOC_SINGLE("Speaker Mute Switch", NAU8810_REG_SPKGAIN, + NAU8810_SPKMT_SFT, 1, 0), + SOC_SINGLE_TLV("Speaker Volume", NAU8810_REG_SPKGAIN, + NAU8810_SPKGAIN_SFT, 0x3f, 0, spk_tlv), + + SOC_SINGLE("Capture Boost(+20dB)", NAU8810_REG_ADCBOOST, + NAU8810_PGABST_SFT, 1, 0), + SOC_SINGLE("Mono Mute Switch", NAU8810_REG_MONOMIX, + NAU8810_MOUTMXMT_SFT, 1, 0), + + SOC_SINGLE("DAC Oversampling Rate(128x) Switch", NAU8810_REG_DAC, + NAU8810_DACOS_SFT, 1, 0), + SOC_SINGLE("ADC Oversampling Rate(128x) Switch", NAU8810_REG_ADC, + NAU8810_ADCOS_SFT, 1, 0), +}; + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new nau8810_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", NAU8810_REG_SPKMIX, + NAU8810_BYPSPK_SFT, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", NAU8810_REG_SPKMIX, + NAU8810_DACSPK_SFT, 1, 0), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new nau8810_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", NAU8810_REG_MONOMIX, + NAU8810_BYPMOUT_SFT, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", NAU8810_REG_MONOMIX, + NAU8810_DACMOUT_SFT, 1, 0), +}; + +/* PGA Mute */ +static const struct snd_kcontrol_new nau8810_pgaboost_mixer_controls[] = { + SOC_DAPM_SINGLE("PGA Mute Switch", NAU8810_REG_PGAGAIN, + NAU8810_PGAMT_SFT, 1, 1), + SOC_DAPM_SINGLE("PMIC PGA Switch", NAU8810_REG_ADCBOOST, + NAU8810_PMICBSTGAIN_SFT, 0x7, 0), +}; + +/* Input PGA */ +static const struct snd_kcontrol_new nau8810_inpga[] = { + SOC_DAPM_SINGLE("MicN Switch", NAU8810_REG_INPUT_SIGNAL, + NAU8810_NMICPGA_SFT, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", NAU8810_REG_INPUT_SIGNAL, + NAU8810_PMICPGA_SFT, 1, 0), +}; + +/* Loopback Switch */ +static const struct snd_kcontrol_new nau8810_loopback = + SOC_DAPM_SINGLE("Switch", NAU8810_REG_COMP, + NAU8810_ADDAP_SFT, 1, 0); + +static int check_mclk_select_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + unsigned int value; + + regmap_read(nau8810->regmap, NAU8810_REG_CLOCK, &value); + return (value & NAU8810_CLKM_MASK); +} + +static const struct snd_soc_dapm_widget nau8810_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Speaker Mixer", NAU8810_REG_POWER3, + NAU8810_SPKMX_EN_SFT, 0, &nau8810_speaker_mixer_controls[0], + ARRAY_SIZE(nau8810_speaker_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", NAU8810_REG_POWER3, + NAU8810_MOUTMX_EN_SFT, 0, &nau8810_mono_mixer_controls[0], + ARRAY_SIZE(nau8810_mono_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "Playback", NAU8810_REG_POWER3, + NAU8810_DAC_EN_SFT, 0), + SND_SOC_DAPM_ADC("ADC", "Capture", NAU8810_REG_POWER2, + NAU8810_ADC_EN_SFT, 0), + SND_SOC_DAPM_PGA("SpkN Out", NAU8810_REG_POWER3, + NAU8810_NSPK_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SpkP Out", NAU8810_REG_POWER3, + NAU8810_PSPK_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", NAU8810_REG_POWER3, + NAU8810_MOUT_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("Input PGA", NAU8810_REG_POWER2, + NAU8810_PGA_EN_SFT, 0, nau8810_inpga, + ARRAY_SIZE(nau8810_inpga)), + SND_SOC_DAPM_MIXER("Input Boost Stage", NAU8810_REG_POWER2, + NAU8810_BST_EN_SFT, 0, nau8810_pgaboost_mixer_controls, + ARRAY_SIZE(nau8810_pgaboost_mixer_controls)), + + SND_SOC_DAPM_SUPPLY("Mic Bias", NAU8810_REG_POWER1, + NAU8810_MICBIAS_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL", NAU8810_REG_POWER1, + NAU8810_PLL_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("Digital Loopback", SND_SOC_NOPM, 0, 0, + &nau8810_loopback), + + SND_SOC_DAPM_INPUT("MICN"), + SND_SOC_DAPM_INPUT("MICP"), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_OUTPUT("SPKOUTP"), + SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const struct snd_soc_dapm_route nau8810_dapm_routes[] = { + {"DAC", NULL, "PLL", check_mclk_select_pll}, + + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Line Bypass Switch", "Input Boost Stage"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Line Bypass Switch", "Input Boost Stage"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Input Boost Stage */ + {"ADC", NULL, "Input Boost Stage"}, + {"ADC", NULL, "PLL", check_mclk_select_pll}, + {"Input Boost Stage", "PGA Mute Switch", "Input PGA"}, + {"Input Boost Stage", "PMIC PGA Switch", "MICP"}, + + /* Input PGA */ + {"Input PGA", NULL, "Mic Bias"}, + {"Input PGA", "MicN Switch", "MICN"}, + {"Input PGA", "MicP Switch", "MICP"}, + + /* Digital Looptack */ + {"Digital Loopback", "Switch", "ADC"}, + {"DAC", NULL, "Digital Loopback"}, +}; + +static int nau8810_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + + nau8810->clk_id = clk_id; + nau8810->sysclk = freq; + dev_dbg(nau8810->dev, "master sysclk %dHz, source %s\n", + freq, clk_id == NAU8810_SCLK_PLL ? "PLL" : "MCLK"); + + return 0; +} + +static int nau88l0_calc_pll(unsigned int pll_in, + unsigned int fs, struct nau8810_pll *pll_param) +{ + u64 f2, f2_max, pll_ratio; + int i, scal_sel; + + if (pll_in > NAU_PLL_REF_MAX || pll_in < NAU_PLL_REF_MIN) + return -EINVAL; + + f2_max = 0; + scal_sel = ARRAY_SIZE(nau8810_mclk_scaler); + for (i = 0; i < ARRAY_SIZE(nau8810_mclk_scaler); i++) { + f2 = 256 * fs * 4 * nau8810_mclk_scaler[i] / 10; + if (f2 > NAU_PLL_FREQ_MIN && f2 < NAU_PLL_FREQ_MAX && + f2_max < f2) { + f2_max = f2; + scal_sel = i; + } + } + if (ARRAY_SIZE(nau8810_mclk_scaler) == scal_sel) + return -EINVAL; + pll_param->mclk_scaler = scal_sel; + f2 = f2_max; + + /* Calculate the PLL 4-bit integer input and the PLL 24-bit fractional + * input; round up the 24+4bit. + */ + pll_ratio = div_u64(f2 << 28, pll_in); + pll_param->pre_factor = 0; + if (((pll_ratio >> 28) & 0xF) < NAU_PLL_OPTOP_MIN) { + pll_ratio <<= 1; + pll_param->pre_factor = 1; + } + pll_param->pll_int = (pll_ratio >> 28) & 0xF; + pll_param->pll_frac = ((pll_ratio & 0xFFFFFFF) >> 4); + + return 0; +} + +static int nau8810_set_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + struct regmap *map = nau8810->regmap; + struct nau8810_pll *pll_param = &nau8810->pll; + int ret, fs; + + fs = freq_out / 256; + ret = nau88l0_calc_pll(freq_in, fs, pll_param); + if (ret < 0) { + dev_err(nau8810->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_info(nau8810->dev, "pll_int=%x pll_frac=%x mclk_scaler=%x pre_factor=%x\n", + pll_param->pll_int, pll_param->pll_frac, pll_param->mclk_scaler, + pll_param->pre_factor); + + regmap_update_bits(map, NAU8810_REG_PLLN, + NAU8810_PLLMCLK_DIV2 | NAU8810_PLLN_MASK, + (pll_param->pre_factor ? NAU8810_PLLMCLK_DIV2 : 0) | + pll_param->pll_int); + regmap_write(map, NAU8810_REG_PLLK1, + (pll_param->pll_frac >> NAU8810_PLLK1_SFT) & + NAU8810_PLLK1_MASK); + regmap_write(map, NAU8810_REG_PLLK2, + (pll_param->pll_frac >> NAU8810_PLLK2_SFT) & + NAU8810_PLLK2_MASK); + regmap_write(map, NAU8810_REG_PLLK3, + pll_param->pll_frac & NAU8810_PLLK3_MASK); + regmap_update_bits(map, NAU8810_REG_CLOCK, NAU8810_MCLKSEL_MASK, + pll_param->mclk_scaler << NAU8810_MCLKSEL_SFT); + regmap_update_bits(map, NAU8810_REG_CLOCK, + NAU8810_CLKM_MASK, NAU8810_CLKM_PLL); + + return 0; +} + +static int nau8810_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + u16 ctrl1_val = 0, ctrl2_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8810_CLKIO_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8810_AIFMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8810_AIFMT_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8810_AIFMT_PCM_A; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl1_val |= NAU8810_BCLKP_IB | NAU8810_FSP_IF; + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8810_BCLKP_IB; + break; + case SND_SOC_DAIFMT_NB_IF: + ctrl1_val |= NAU8810_FSP_IF; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8810->regmap, NAU8810_REG_IFACE, + NAU8810_AIFMT_MASK | NAU8810_FSP_IF | + NAU8810_BCLKP_IB, ctrl1_val); + regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK, + NAU8810_CLKIO_MASK, ctrl2_val); + + return 0; +} + +static int nau8810_mclk_clkdiv(struct nau8810 *nau8810, int rate) +{ + int i, sclk, imclk = rate * 256, div = 0; + + if (!nau8810->sysclk) { + dev_err(nau8810->dev, "Make mclk div configuration fail because of invalid system clock\n"); + return -EINVAL; + } + + /* Configure the master clock prescaler div to make system + * clock to approximate the internal master clock (IMCLK); + * and large or equal to IMCLK. + */ + for (i = 1; i < ARRAY_SIZE(nau8810_mclk_scaler); i++) { + sclk = (nau8810->sysclk * 10) / + nau8810_mclk_scaler[i]; + if (sclk < imclk) + break; + div = i; + } + dev_dbg(nau8810->dev, + "master clock prescaler %x for fs %d\n", div, rate); + + /* master clock from MCLK and disable PLL */ + regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK, + NAU8810_MCLKSEL_MASK, (div << NAU8810_MCLKSEL_SFT)); + regmap_update_bits(nau8810->regmap, NAU8810_REG_CLOCK, + NAU8810_CLKM_MASK, NAU8810_CLKM_MCLK); + + return 0; +} + +static int nau8810_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + int val_len = 0, val_rate = 0, ret = 0; + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len |= NAU8810_WLEN_20; + break; + case 24: + val_len |= NAU8810_WLEN_24; + break; + case 32: + val_len |= NAU8810_WLEN_32; + break; + } + + switch (params_rate(params)) { + case 8000: + val_rate |= NAU8810_SMPLR_8K; + break; + case 11025: + val_rate |= NAU8810_SMPLR_12K; + break; + case 16000: + val_rate |= NAU8810_SMPLR_16K; + break; + case 22050: + val_rate |= NAU8810_SMPLR_24K; + break; + case 32000: + val_rate |= NAU8810_SMPLR_32K; + break; + case 44100: + case 48000: + break; + } + + regmap_update_bits(nau8810->regmap, NAU8810_REG_IFACE, + NAU8810_WLEN_MASK, val_len); + regmap_update_bits(nau8810->regmap, NAU8810_REG_SMPLR, + NAU8810_SMPLR_MASK, val_rate); + + /* If the master clock is from MCLK, provide the runtime FS for driver + * to get the master clock prescaler configuration. + */ + if (nau8810->clk_id == NAU8810_SCLK_MCLK) { + ret = nau8810_mclk_clkdiv(nau8810, params_rate(params)); + if (ret < 0) + dev_err(nau8810->dev, "MCLK div configuration fail\n"); + } + + return ret; +} + +static int nau8810_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct nau8810 *nau8810 = snd_soc_component_get_drvdata(component); + struct regmap *map = nau8810->regmap; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(map, NAU8810_REG_POWER1, + NAU8810_REFIMP_MASK, NAU8810_REFIMP_80K); + break; + + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(map, NAU8810_REG_POWER1, + NAU8810_IOBUF_EN | NAU8810_ABIAS_EN, + NAU8810_IOBUF_EN | NAU8810_ABIAS_EN); + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(map); + regmap_update_bits(map, NAU8810_REG_POWER1, + NAU8810_REFIMP_MASK, NAU8810_REFIMP_3K); + mdelay(100); + } + regmap_update_bits(map, NAU8810_REG_POWER1, + NAU8810_REFIMP_MASK, NAU8810_REFIMP_300K); + break; + + case SND_SOC_BIAS_OFF: + regmap_write(map, NAU8810_REG_POWER1, 0); + regmap_write(map, NAU8810_REG_POWER2, 0); + regmap_write(map, NAU8810_REG_POWER3, 0); + break; + } + + return 0; +} + + +#define NAU8810_RATES (SNDRV_PCM_RATE_8000_48000) + +#define NAU8810_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops nau8810_ops = { + .hw_params = nau8810_pcm_hw_params, + .set_fmt = nau8810_set_dai_fmt, + .set_sysclk = nau8810_set_sysclk, + .set_pll = nau8810_set_pll, +}; + +static struct snd_soc_dai_driver nau8810_dai = { + .name = "nau8810-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, /* Only 1 channel of data */ + .rates = NAU8810_RATES, + .formats = NAU8810_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, /* Only 1 channel of data */ + .rates = NAU8810_RATES, + .formats = NAU8810_FORMATS, + }, + .ops = &nau8810_ops, + .symmetric_rates = 1, +}; + +static const struct regmap_config nau8810_regmap_config = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = NAU8810_REG_MAX, + .readable_reg = nau8810_readable_reg, + .writeable_reg = nau8810_writeable_reg, + .volatile_reg = nau8810_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8810_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8810_reg_defaults), +}; + +static const struct snd_soc_component_driver nau8810_component_driver = { + .set_bias_level = nau8810_set_bias_level, + .controls = nau8810_snd_controls, + .num_controls = ARRAY_SIZE(nau8810_snd_controls), + .dapm_widgets = nau8810_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8810_dapm_widgets), + .dapm_routes = nau8810_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8810_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int nau8810_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8810 *nau8810 = dev_get_platdata(dev); + + if (!nau8810) { + nau8810 = devm_kzalloc(dev, sizeof(*nau8810), GFP_KERNEL); + if (!nau8810) + return -ENOMEM; + } + i2c_set_clientdata(i2c, nau8810); + + nau8810->regmap = devm_regmap_init_i2c(i2c, &nau8810_regmap_config); + if (IS_ERR(nau8810->regmap)) + return PTR_ERR(nau8810->regmap); + nau8810->dev = dev; + + regmap_write(nau8810->regmap, NAU8810_REG_RESET, 0x00); + + return devm_snd_soc_register_component(dev, + &nau8810_component_driver, &nau8810_dai, 1); +} + +static const struct i2c_device_id nau8810_i2c_id[] = { + { "nau8810", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8810_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id nau8810_of_match[] = { + { .compatible = "nuvoton,nau8810", }, + { } +}; +MODULE_DEVICE_TABLE(of, nau8810_of_match); +#endif + +static struct i2c_driver nau8810_i2c_driver = { + .driver = { + .name = "nau8810", + .of_match_table = of_match_ptr(nau8810_of_match), + }, + .probe = nau8810_i2c_probe, + .id_table = nau8810_i2c_id, +}; + +module_i2c_driver(nau8810_i2c_driver); + +MODULE_DESCRIPTION("ASoC NAU8810 driver"); +MODULE_AUTHOR("David Lin "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/nau8810.h b/sound/soc/codecs/nau8810.h new file mode 100644 index 000000000..df882658c --- /dev/null +++ b/sound/soc/codecs/nau8810.h @@ -0,0 +1,281 @@ +/* + * NAU8810 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: David Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __NAU8810_H__ +#define __NAU8810_H__ + +#define NAU8810_REG_RESET 0x00 +#define NAU8810_REG_POWER1 0x01 +#define NAU8810_REG_POWER2 0x02 +#define NAU8810_REG_POWER3 0x03 +#define NAU8810_REG_IFACE 0x04 +#define NAU8810_REG_COMP 0x05 +#define NAU8810_REG_CLOCK 0x06 +#define NAU8810_REG_SMPLR 0x07 +#define NAU8810_REG_DAC 0x0A +#define NAU8810_REG_DACGAIN 0x0B +#define NAU8810_REG_ADC 0x0E +#define NAU8810_REG_ADCGAIN 0x0F +#define NAU8810_REG_EQ1 0x12 +#define NAU8810_REG_EQ2 0x13 +#define NAU8810_REG_EQ3 0x14 +#define NAU8810_REG_EQ4 0x15 +#define NAU8810_REG_EQ5 0x16 +#define NAU8810_REG_DACLIM1 0x18 +#define NAU8810_REG_DACLIM2 0x19 +#define NAU8810_REG_NOTCH1 0x1B +#define NAU8810_REG_NOTCH2 0x1C +#define NAU8810_REG_NOTCH3 0x1D +#define NAU8810_REG_NOTCH4 0x1E +#define NAU8810_REG_ALC1 0x20 +#define NAU8810_REG_ALC2 0x21 +#define NAU8810_REG_ALC3 0x22 +#define NAU8810_REG_NOISEGATE 0x23 +#define NAU8810_REG_PLLN 0x24 +#define NAU8810_REG_PLLK1 0x25 +#define NAU8810_REG_PLLK2 0x26 +#define NAU8810_REG_PLLK3 0x27 +#define NAU8810_REG_ATTEN 0x28 +#define NAU8810_REG_INPUT_SIGNAL 0x2C +#define NAU8810_REG_PGAGAIN 0x2D +#define NAU8810_REG_ADCBOOST 0x2F +#define NAU8810_REG_OUTPUT 0x31 +#define NAU8810_REG_SPKMIX 0x32 +#define NAU8810_REG_SPKGAIN 0x36 +#define NAU8810_REG_MONOMIX 0x38 +#define NAU8810_REG_POWER4 0x3A +#define NAU8810_REG_TSLOTCTL1 0x3B +#define NAU8810_REG_TSLOTCTL2 0x3C +#define NAU8810_REG_DEVICE_REVID 0x3E +#define NAU8810_REG_I2C_DEVICEID 0x3F +#define NAU8810_REG_ADDITIONID 0x40 +#define NAU8810_REG_RESERVE 0x41 +#define NAU8810_REG_OUTCTL 0x45 +#define NAU8810_REG_ALC1ENHAN1 0x46 +#define NAU8810_REG_ALC1ENHAN2 0x47 +#define NAU8810_REG_MISCCTL 0x49 +#define NAU8810_REG_OUTTIEOFF 0x4B +#define NAU8810_REG_AGCP2POUT 0x4C +#define NAU8810_REG_AGCPOUT 0x4D +#define NAU8810_REG_AMTCTL 0x4E +#define NAU8810_REG_OUTTIEOFFMAN 0x4F +#define NAU8810_REG_MAX NAU8810_REG_OUTTIEOFFMAN + + +/* NAU8810_REG_POWER1 (0x1) */ +#define NAU8810_DCBUF_EN (0x1 << 8) +#define NAU8810_PLL_EN_SFT 5 +#define NAU8810_MICBIAS_EN_SFT 4 +#define NAU8810_ABIAS_EN (0x1 << 3) +#define NAU8810_IOBUF_EN (0x1 << 2) +#define NAU8810_REFIMP_MASK 0x3 +#define NAU8810_REFIMP_DIS 0x0 +#define NAU8810_REFIMP_80K 0x1 +#define NAU8810_REFIMP_300K 0x2 +#define NAU8810_REFIMP_3K 0x3 + +/* NAU8810_REG_POWER2 (0x2) */ +#define NAU8810_BST_EN_SFT 4 +#define NAU8810_PGA_EN_SFT 2 +#define NAU8810_ADC_EN_SFT 0 + +/* NAU8810_REG_POWER3 (0x3) */ +#define NAU8810_DAC_EN_SFT 0 +#define NAU8810_SPKMX_EN_SFT 2 +#define NAU8810_MOUTMX_EN_SFT 3 +#define NAU8810_PSPK_EN_SFT 5 +#define NAU8810_NSPK_EN_SFT 6 +#define NAU8810_MOUT_EN_SFT 7 + +/* NAU8810_REG_IFACE (0x4) */ +#define NAU8810_AIFMT_SFT 3 +#define NAU8810_AIFMT_MASK (0x3 << NAU8810_AIFMT_SFT) +#define NAU8810_AIFMT_RIGHT (0x0 << NAU8810_AIFMT_SFT) +#define NAU8810_AIFMT_LEFT (0x1 << NAU8810_AIFMT_SFT) +#define NAU8810_AIFMT_I2S (0x2 << NAU8810_AIFMT_SFT) +#define NAU8810_AIFMT_PCM_A (0x3 << NAU8810_AIFMT_SFT) +#define NAU8810_WLEN_SFT 5 +#define NAU8810_WLEN_MASK (0x3 << NAU8810_WLEN_SFT) +#define NAU8810_WLEN_16 (0x0 << NAU8810_WLEN_SFT) +#define NAU8810_WLEN_20 (0x1 << NAU8810_WLEN_SFT) +#define NAU8810_WLEN_24 (0x2 << NAU8810_WLEN_SFT) +#define NAU8810_WLEN_32 (0x3 << NAU8810_WLEN_SFT) +#define NAU8810_FSP_IF (0x1 << 7) +#define NAU8810_BCLKP_IB (0x1 << 8) + +/* NAU8810_REG_COMP (0x5) */ +#define NAU8810_ADDAP_SFT 0 +#define NAU8810_ADCCM_SFT 1 +#define NAU8810_DACCM_SFT 3 + +/* NAU8810_REG_CLOCK (0x6) */ +#define NAU8810_CLKIO_MASK 0x1 +#define NAU8810_CLKIO_SLAVE 0x0 +#define NAU8810_CLKIO_MASTER 0x1 +#define NAU8810_BCLKSEL_SFT 2 +#define NAU8810_BCLKSEL_MASK (0x7 << NAU8810_BCLKSEL_SFT) +#define NAU8810_BCLKDIV_1 (0x0 << NAU8810_BCLKSEL_SFT) +#define NAU8810_BCLKDIV_2 (0x1 << NAU8810_BCLKSEL_SFT) +#define NAU8810_BCLKDIV_4 (0x2 << NAU8810_BCLKSEL_SFT) +#define NAU8810_BCLKDIV_8 (0x3 << NAU8810_BCLKSEL_SFT) +#define NAU8810_BCLKDIV_16 (0x4 << NAU8810_BCLKSEL_SFT) +#define NAU8810_BCLKDIV_32 (0x5 << NAU8810_BCLKSEL_SFT) +#define NAU8810_MCLKSEL_SFT 5 +#define NAU8810_MCLKSEL_MASK (0x7 << NAU8810_MCLKSEL_SFT) +#define NAU8810_CLKM_SFT 8 +#define NAU8810_CLKM_MASK (0x1 << NAU8810_CLKM_SFT) +#define NAU8810_CLKM_MCLK (0x0 << NAU8810_CLKM_SFT) +#define NAU8810_CLKM_PLL (0x1 << NAU8810_CLKM_SFT) + +/* NAU8810_REG_SMPLR (0x7) */ +#define NAU8810_SMPLR_SFT 1 +#define NAU8810_SMPLR_MASK (0x7 << NAU8810_SMPLR_SFT) +#define NAU8810_SMPLR_48K (0x0 << NAU8810_SMPLR_SFT) +#define NAU8810_SMPLR_32K (0x1 << NAU8810_SMPLR_SFT) +#define NAU8810_SMPLR_24K (0x2 << NAU8810_SMPLR_SFT) +#define NAU8810_SMPLR_16K (0x3 << NAU8810_SMPLR_SFT) +#define NAU8810_SMPLR_12K (0x4 << NAU8810_SMPLR_SFT) +#define NAU8810_SMPLR_8K (0x5 << NAU8810_SMPLR_SFT) + +/* NAU8810_REG_DAC (0xA) */ +#define NAU8810_DACPL_SFT 0 +#define NAU8810_DACOS_SFT 3 +#define NAU8810_DEEMP_SFT 4 + +/* NAU8810_REG_DACGAIN (0xB) */ +#define NAU8810_DACGAIN_SFT 0 + +/* NAU8810_REG_ADC (0xE) */ +#define NAU8810_ADCPL_SFT 0 +#define NAU8810_ADCOS_SFT 3 +#define NAU8810_HPF_SFT 4 +#define NAU8810_HPFEN_SFT 8 + +/* NAU8810_REG_ADCGAIN (0xF) */ +#define NAU8810_ADCGAIN_SFT 0 + +/* NAU8810_REG_EQ1 (0x12) */ +#define NAU8810_EQ1GC_SFT 0 +#define NAU8810_EQ1CF_SFT 5 +#define NAU8810_EQM_SFT 8 + +/* NAU8810_REG_EQ2 (0x13) */ +#define NAU8810_EQ2GC_SFT 0 +#define NAU8810_EQ2CF_SFT 5 +#define NAU8810_EQ2BW_SFT 8 + +/* NAU8810_REG_EQ3 (0x14) */ +#define NAU8810_EQ3GC_SFT 0 +#define NAU8810_EQ3CF_SFT 5 +#define NAU8810_EQ3BW_SFT 8 + +/* NAU8810_REG_EQ4 (0x15) */ +#define NAU8810_EQ4GC_SFT 0 +#define NAU8810_EQ4CF_SFT 5 +#define NAU8810_EQ4BW_SFT 8 + +/* NAU8810_REG_EQ5 (0x16) */ +#define NAU8810_EQ5GC_SFT 0 +#define NAU8810_EQ5CF_SFT 5 + +/* NAU8810_REG_DACLIM1 (0x18) */ +#define NAU8810_DACLIMATK_SFT 0 +#define NAU8810_DACLIMDCY_SFT 4 +#define NAU8810_DACLIMEN_SFT 8 + +/* NAU8810_REG_DACLIM2 (0x19) */ +#define NAU8810_DACLIMBST_SFT 0 +#define NAU8810_DACLIMTHL_SFT 4 + +/* NAU8810_REG_ALC1 (0x20) */ +#define NAU8810_ALCMINGAIN_SFT 0 +#define NAU8810_ALCMXGAIN_SFT 3 +#define NAU8810_ALCEN_SFT 8 + +/* NAU8810_REG_ALC2 (0x21) */ +#define NAU8810_ALCSL_SFT 0 +#define NAU8810_ALCHT_SFT 4 +#define NAU8810_ALCZC_SFT 8 + +/* NAU8810_REG_ALC3 (0x22) */ +#define NAU8810_ALCATK_SFT 0 +#define NAU8810_ALCDCY_SFT 4 +#define NAU8810_ALCM_SFT 8 + +/* NAU8810_REG_NOISEGATE (0x23) */ +#define NAU8810_ALCNTH_SFT 0 +#define NAU8810_ALCNEN_SFT 3 + +/* NAU8810_REG_PLLN (0x24) */ +#define NAU8810_PLLN_MASK 0xF +#define NAU8810_PLLMCLK_DIV2 (0x1 << 4) + +/* NAU8810_REG_PLLK1 (0x25) */ +#define NAU8810_PLLK1_SFT 18 +#define NAU8810_PLLK1_MASK 0x3F + +/* NAU8810_REG_PLLK2 (0x26) */ +#define NAU8810_PLLK2_SFT 9 +#define NAU8810_PLLK2_MASK 0x1FF + +/* NAU8810_REG_PLLK3 (0x27) */ +#define NAU8810_PLLK3_MASK 0x1FF + +/* NAU8810_REG_INPUT_SIGNAL (0x2C) */ +#define NAU8810_PMICPGA_SFT 0 +#define NAU8810_NMICPGA_SFT 1 + +/* NAU8810_REG_PGAGAIN (0x2D) */ +#define NAU8810_PGAGAIN_SFT 0 +#define NAU8810_PGAMT_SFT 6 +#define NAU8810_PGAZC_SFT 7 + +/* NAU8810_REG_ADCBOOST (0x2F) */ +#define NAU8810_PMICBSTGAIN_SFT 4 +#define NAU8810_PGABST_SFT 8 + +/* NAU8810_REG_SPKMIX (0x32) */ +#define NAU8810_DACSPK_SFT 0 +#define NAU8810_BYPSPK_SFT 1 + +/* NAU8810_REG_SPKGAIN (0x36) */ +#define NAU8810_SPKGAIN_SFT 0 +#define NAU8810_SPKMT_SFT 6 +#define NAU8810_SPKZC_SFT 7 + +/* NAU8810_REG_MONOMIX (0x38) */ +#define NAU8810_DACMOUT_SFT 0 +#define NAU8810_BYPMOUT_SFT 1 +#define NAU8810_MOUTMXMT_SFT 6 + + +/* System Clock Source */ +enum { + NAU8810_SCLK_MCLK, + NAU8810_SCLK_PLL, +}; + +struct nau8810_pll { + int pre_factor; + int mclk_scaler; + int pll_frac; + int pll_int; +}; + +struct nau8810 { + struct device *dev; + struct regmap *regmap; + struct nau8810_pll pll; + int sysclk; + int clk_id; +}; + +#endif diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c new file mode 100644 index 000000000..4af87340b --- /dev/null +++ b/sound/soc/codecs/nau8824.c @@ -0,0 +1,1999 @@ +/* + * NAU88L24 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "nau8824.h" + +#define NAU8824_JD_ACTIVE_HIGH BIT(0) + +static int nau8824_quirk; +static int quirk_override = -1; +module_param_named(quirk, quirk_override, uint, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); + +static int nau8824_config_sysclk(struct nau8824 *nau8824, + int clk_id, unsigned int freq); +static bool nau8824_is_jack_inserted(struct nau8824 *nau8824); + +/* the ADC threshold of headset */ +#define DMIC_CLK 3072000 + +/* the ADC threshold of headset */ +#define HEADSET_SARADC_THD 0x80 + +/* the parameter threshold of FLL */ +#define NAU_FREF_MAX 13500000 +#define NAU_FVCO_MAX 100000000 +#define NAU_FVCO_MIN 90000000 + +/* scaling for mclk from sysclk_src output */ +static const struct nau8824_fll_attr mclk_src_scaling[] = { + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, +}; + +/* ratio for input clk freq */ +static const struct nau8824_fll_attr fll_ratio[] = { + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, +}; + +static const struct nau8824_fll_attr fll_pre_scalar[] = { + { 1, 0x0 }, + { 2, 0x1 }, + { 4, 0x2 }, + { 8, 0x3 }, +}; + +/* the maximum frequency of CLK_ADC and CLK_DAC */ +#define CLK_DA_AD_MAX 6144000 + +/* over sampling rate */ +static const struct nau8824_osr_attr osr_dac_sel[] = { + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 256, 0 }, /* OSR 256, SRC 1 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 0, 0 }, + { 32, 3 }, /* OSR 32, SRC 1/8 */ +}; + +static const struct nau8824_osr_attr osr_adc_sel[] = { + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ +}; + +static const struct reg_default nau8824_reg_defaults[] = { + { NAU8824_REG_ENA_CTRL, 0x0000 }, + { NAU8824_REG_CLK_GATING_ENA, 0x0000 }, + { NAU8824_REG_CLK_DIVIDER, 0x0000 }, + { NAU8824_REG_FLL1, 0x0000 }, + { NAU8824_REG_FLL2, 0x3126 }, + { NAU8824_REG_FLL3, 0x0008 }, + { NAU8824_REG_FLL4, 0x0010 }, + { NAU8824_REG_FLL5, 0xC000 }, + { NAU8824_REG_FLL6, 0x6000 }, + { NAU8824_REG_FLL_VCO_RSV, 0xF13C }, + { NAU8824_REG_JACK_DET_CTRL, 0x0000 }, + { NAU8824_REG_INTERRUPT_SETTING_1, 0x0000 }, + { NAU8824_REG_IRQ, 0x0000 }, + { NAU8824_REG_CLEAR_INT_REG, 0x0000 }, + { NAU8824_REG_INTERRUPT_SETTING, 0x1000 }, + { NAU8824_REG_SAR_ADC, 0x0015 }, + { NAU8824_REG_VDET_COEFFICIENT, 0x0110 }, + { NAU8824_REG_VDET_THRESHOLD_1, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_2, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_3, 0x0000 }, + { NAU8824_REG_VDET_THRESHOLD_4, 0x0000 }, + { NAU8824_REG_GPIO_SEL, 0x0000 }, + { NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 0x000B }, + { NAU8824_REG_PORT0_I2S_PCM_CTRL_2, 0x0010 }, + { NAU8824_REG_PORT0_LEFT_TIME_SLOT, 0x0000 }, + { NAU8824_REG_PORT0_RIGHT_TIME_SLOT, 0x0000 }, + { NAU8824_REG_TDM_CTRL, 0x0000 }, + { NAU8824_REG_ADC_HPF_FILTER, 0x0000 }, + { NAU8824_REG_ADC_FILTER_CTRL, 0x0002 }, + { NAU8824_REG_DAC_FILTER_CTRL_1, 0x0000 }, + { NAU8824_REG_DAC_FILTER_CTRL_2, 0x0000 }, + { NAU8824_REG_NOTCH_FILTER_1, 0x0000 }, + { NAU8824_REG_NOTCH_FILTER_2, 0x0000 }, + { NAU8824_REG_EQ1_LOW, 0x112C }, + { NAU8824_REG_EQ2_EQ3, 0x2C2C }, + { NAU8824_REG_EQ4_EQ5, 0x2C2C }, + { NAU8824_REG_ADC_CH0_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH1_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH2_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_CH3_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_DAC_MUTE_CTRL, 0x0000 }, + { NAU8824_REG_DAC_CH0_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_DAC_CH1_DGAIN_CTRL, 0x0100 }, + { NAU8824_REG_ADC_TO_DAC_ST, 0x0000 }, + { NAU8824_REG_DRC_KNEE_IP12_ADC_CH01, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_ADC_CH01, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_ADC_CH01, 0x25FF }, + { NAU8824_REG_DRC_ATKDCY_ADC_CH01, 0x3457 }, + { NAU8824_REG_DRC_KNEE_IP12_ADC_CH23, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_ADC_CH23, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_ADC_CH23, 0x25FF }, + { NAU8824_REG_DRC_ATKDCY_ADC_CH23, 0x3457 }, + { NAU8824_REG_DRC_GAINL_ADC0, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC1, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC2, 0x0200 }, + { NAU8824_REG_DRC_GAINL_ADC3, 0x0200 }, + { NAU8824_REG_DRC_KNEE_IP12_DAC, 0x1486 }, + { NAU8824_REG_DRC_KNEE_IP34_DAC, 0x0F12 }, + { NAU8824_REG_DRC_SLOPE_DAC, 0x25F9 }, + { NAU8824_REG_DRC_ATKDCY_DAC, 0x3457 }, + { NAU8824_REG_DRC_GAIN_DAC_CH0, 0x0200 }, + { NAU8824_REG_DRC_GAIN_DAC_CH1, 0x0200 }, + { NAU8824_REG_MODE, 0x0000 }, + { NAU8824_REG_MODE1, 0x0000 }, + { NAU8824_REG_MODE2, 0x0000 }, + { NAU8824_REG_CLASSG, 0x0000 }, + { NAU8824_REG_OTP_EFUSE, 0x0000 }, + { NAU8824_REG_OTPDOUT_1, 0x0000 }, + { NAU8824_REG_OTPDOUT_2, 0x0000 }, + { NAU8824_REG_MISC_CTRL, 0x0000 }, + { NAU8824_REG_I2C_TIMEOUT, 0xEFFF }, + { NAU8824_REG_TEST_MODE, 0x0000 }, + { NAU8824_REG_I2C_DEVICE_ID, 0x1AF1 }, + { NAU8824_REG_SAR_ADC_DATA_OUT, 0x00FF }, + { NAU8824_REG_BIAS_ADJ, 0x0000 }, + { NAU8824_REG_PGA_GAIN, 0x0000 }, + { NAU8824_REG_TRIM_SETTINGS, 0x0000 }, + { NAU8824_REG_ANALOG_CONTROL_1, 0x0000 }, + { NAU8824_REG_ANALOG_CONTROL_2, 0x0000 }, + { NAU8824_REG_ENABLE_LO, 0x0000 }, + { NAU8824_REG_GAIN_LO, 0x0000 }, + { NAU8824_REG_CLASSD_GAIN_1, 0x0000 }, + { NAU8824_REG_CLASSD_GAIN_2, 0x0000 }, + { NAU8824_REG_ANALOG_ADC_1, 0x0011 }, + { NAU8824_REG_ANALOG_ADC_2, 0x0020 }, + { NAU8824_REG_RDAC, 0x0008 }, + { NAU8824_REG_MIC_BIAS, 0x0006 }, + { NAU8824_REG_HS_VOLUME_CONTROL, 0x0000 }, + { NAU8824_REG_BOOST, 0x0000 }, + { NAU8824_REG_FEPGA, 0x0000 }, + { NAU8824_REG_FEPGA_II, 0x0000 }, + { NAU8824_REG_FEPGA_SE, 0x0000 }, + { NAU8824_REG_FEPGA_ATTENUATION, 0x0000 }, + { NAU8824_REG_ATT_PORT0, 0x0000 }, + { NAU8824_REG_ATT_PORT1, 0x0000 }, + { NAU8824_REG_POWER_UP_CONTROL, 0x0000 }, + { NAU8824_REG_CHARGE_PUMP_CONTROL, 0x0300 }, + { NAU8824_REG_CHARGE_PUMP_INPUT, 0x0013 }, +}; + +static int nau8824_sema_acquire(struct nau8824 *nau8824, long timeout) +{ + int ret; + + if (timeout) { + ret = down_timeout(&nau8824->jd_sem, timeout); + if (ret < 0) + dev_warn(nau8824->dev, "Acquire semaphore timeout\n"); + } else { + ret = down_interruptible(&nau8824->jd_sem); + if (ret < 0) + dev_warn(nau8824->dev, "Acquire semaphore fail\n"); + } + + return ret; +} + +static inline void nau8824_sema_release(struct nau8824 *nau8824) +{ + up(&nau8824->jd_sem); +} + +static bool nau8824_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_ENA_CTRL ... NAU8824_REG_FLL_VCO_RSV: + case NAU8824_REG_JACK_DET_CTRL: + case NAU8824_REG_INTERRUPT_SETTING_1: + case NAU8824_REG_IRQ: + case NAU8824_REG_CLEAR_INT_REG ... NAU8824_REG_VDET_THRESHOLD_4: + case NAU8824_REG_GPIO_SEL: + case NAU8824_REG_PORT0_I2S_PCM_CTRL_1 ... NAU8824_REG_TDM_CTRL: + case NAU8824_REG_ADC_HPF_FILTER ... NAU8824_REG_EQ4_EQ5: + case NAU8824_REG_ADC_CH0_DGAIN_CTRL ... NAU8824_REG_ADC_TO_DAC_ST: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH01 ... NAU8824_REG_DRC_GAINL_ADC3: + case NAU8824_REG_DRC_KNEE_IP12_DAC ... NAU8824_REG_DRC_GAIN_DAC_CH1: + case NAU8824_REG_CLASSG ... NAU8824_REG_OTP_EFUSE: + case NAU8824_REG_OTPDOUT_1 ... NAU8824_REG_OTPDOUT_2: + case NAU8824_REG_I2C_TIMEOUT: + case NAU8824_REG_I2C_DEVICE_ID ... NAU8824_REG_SAR_ADC_DATA_OUT: + case NAU8824_REG_BIAS_ADJ ... NAU8824_REG_CLASSD_GAIN_2: + case NAU8824_REG_ANALOG_ADC_1 ... NAU8824_REG_ATT_PORT1: + case NAU8824_REG_POWER_UP_CONTROL ... NAU8824_REG_CHARGE_PUMP_INPUT: + return true; + default: + return false; + } + +} + +static bool nau8824_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_RESET ... NAU8824_REG_FLL_VCO_RSV: + case NAU8824_REG_JACK_DET_CTRL: + case NAU8824_REG_INTERRUPT_SETTING_1: + case NAU8824_REG_CLEAR_INT_REG ... NAU8824_REG_VDET_THRESHOLD_4: + case NAU8824_REG_GPIO_SEL: + case NAU8824_REG_PORT0_I2S_PCM_CTRL_1 ... NAU8824_REG_TDM_CTRL: + case NAU8824_REG_ADC_HPF_FILTER ... NAU8824_REG_EQ4_EQ5: + case NAU8824_REG_ADC_CH0_DGAIN_CTRL ... NAU8824_REG_ADC_TO_DAC_ST: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH01: + case NAU8824_REG_DRC_KNEE_IP34_ADC_CH01: + case NAU8824_REG_DRC_SLOPE_ADC_CH01: + case NAU8824_REG_DRC_ATKDCY_ADC_CH01: + case NAU8824_REG_DRC_KNEE_IP12_ADC_CH23: + case NAU8824_REG_DRC_KNEE_IP34_ADC_CH23: + case NAU8824_REG_DRC_SLOPE_ADC_CH23: + case NAU8824_REG_DRC_ATKDCY_ADC_CH23: + case NAU8824_REG_DRC_KNEE_IP12_DAC ... NAU8824_REG_DRC_ATKDCY_DAC: + case NAU8824_REG_CLASSG ... NAU8824_REG_OTP_EFUSE: + case NAU8824_REG_I2C_TIMEOUT: + case NAU8824_REG_BIAS_ADJ ... NAU8824_REG_CLASSD_GAIN_2: + case NAU8824_REG_ANALOG_ADC_1 ... NAU8824_REG_ATT_PORT1: + case NAU8824_REG_POWER_UP_CONTROL ... NAU8824_REG_CHARGE_PUMP_CONTROL: + return true; + default: + return false; + } +} + +static bool nau8824_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8824_REG_RESET: + case NAU8824_REG_IRQ ... NAU8824_REG_CLEAR_INT_REG: + case NAU8824_REG_DRC_GAINL_ADC0 ... NAU8824_REG_DRC_GAINL_ADC3: + case NAU8824_REG_DRC_GAIN_DAC_CH0 ... NAU8824_REG_DRC_GAIN_DAC_CH1: + case NAU8824_REG_OTPDOUT_1 ... NAU8824_REG_OTPDOUT_2: + case NAU8824_REG_I2C_DEVICE_ID ... NAU8824_REG_SAR_ADC_DATA_OUT: + case NAU8824_REG_CHARGE_PUMP_INPUT: + return true; + default: + return false; + } +} + +static const char * const nau8824_companding[] = { + "Off", "NC", "u-law", "A-law" }; + +static const struct soc_enum nau8824_companding_adc_enum = + SOC_ENUM_SINGLE(NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 12, + ARRAY_SIZE(nau8824_companding), nau8824_companding); + +static const struct soc_enum nau8824_companding_dac_enum = + SOC_ENUM_SINGLE(NAU8824_REG_PORT0_I2S_PCM_CTRL_1, 14, + ARRAY_SIZE(nau8824_companding), nau8824_companding); + +static const char * const nau8824_adc_decimation[] = { + "32", "64", "128", "256" }; + +static const struct soc_enum nau8824_adc_decimation_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_FILTER_CTRL, 0, + ARRAY_SIZE(nau8824_adc_decimation), nau8824_adc_decimation); + +static const char * const nau8824_dac_oversampl[] = { + "64", "256", "128", "", "32" }; + +static const struct soc_enum nau8824_dac_oversampl_enum = + SOC_ENUM_SINGLE(NAU8824_REG_DAC_FILTER_CTRL_1, 0, + ARRAY_SIZE(nau8824_dac_oversampl), nau8824_dac_oversampl); + +static const char * const nau8824_input_channel[] = { + "Input CH0", "Input CH1", "Input CH2", "Input CH3" }; + +static const struct soc_enum nau8824_adc_ch0_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH0_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch1_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH1_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch2_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH2_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const struct soc_enum nau8824_adc_ch3_enum = + SOC_ENUM_SINGLE(NAU8824_REG_ADC_CH3_DGAIN_CTRL, 9, + ARRAY_SIZE(nau8824_input_channel), nau8824_input_channel); + +static const char * const nau8824_tdm_slot[] = { + "Slot 0", "Slot 1", "Slot 2", "Slot 3" }; + +static const struct soc_enum nau8824_dac_left_sel_enum = + SOC_ENUM_SINGLE(NAU8824_REG_TDM_CTRL, 6, + ARRAY_SIZE(nau8824_tdm_slot), nau8824_tdm_slot); + +static const struct soc_enum nau8824_dac_right_sel_enum = + SOC_ENUM_SINGLE(NAU8824_REG_TDM_CTRL, 4, + ARRAY_SIZE(nau8824_tdm_slot), nau8824_tdm_slot); + +static const DECLARE_TLV_DB_MINMAX_MUTE(spk_vol_tlv, 0, 2400); +static const DECLARE_TLV_DB_MINMAX(hp_vol_tlv, -3000, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 200, 0); +static const DECLARE_TLV_DB_SCALE(dmic_vol_tlv, -12800, 50, 0); + +static const struct snd_kcontrol_new nau8824_snd_controls[] = { + SOC_ENUM("ADC Companding", nau8824_companding_adc_enum), + SOC_ENUM("DAC Companding", nau8824_companding_dac_enum), + + SOC_ENUM("ADC Decimation Rate", nau8824_adc_decimation_enum), + SOC_ENUM("DAC Oversampling Rate", nau8824_dac_oversampl_enum), + + SOC_SINGLE_TLV("Speaker Right DACR Volume", + NAU8824_REG_CLASSD_GAIN_1, 8, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Left DACL Volume", + NAU8824_REG_CLASSD_GAIN_2, 0, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Left DACR Volume", + NAU8824_REG_CLASSD_GAIN_1, 0, 0x1f, 0, spk_vol_tlv), + SOC_SINGLE_TLV("Speaker Right DACL Volume", + NAU8824_REG_CLASSD_GAIN_2, 8, 0x1f, 0, spk_vol_tlv), + + SOC_SINGLE_TLV("Headphone Right DACR Volume", + NAU8824_REG_ATT_PORT0, 8, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Left DACL Volume", + NAU8824_REG_ATT_PORT0, 0, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Right DACL Volume", + NAU8824_REG_ATT_PORT1, 8, 0x1f, 0, hp_vol_tlv), + SOC_SINGLE_TLV("Headphone Left DACR Volume", + NAU8824_REG_ATT_PORT1, 0, 0x1f, 0, hp_vol_tlv), + + SOC_SINGLE_TLV("MIC1 Volume", NAU8824_REG_FEPGA_II, + NAU8824_FEPGA_GAINL_SFT, 0x12, 0, mic_vol_tlv), + SOC_SINGLE_TLV("MIC2 Volume", NAU8824_REG_FEPGA_II, + NAU8824_FEPGA_GAINR_SFT, 0x12, 0, mic_vol_tlv), + + SOC_SINGLE_TLV("DMIC1 Volume", NAU8824_REG_ADC_CH0_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC2 Volume", NAU8824_REG_ADC_CH1_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC3 Volume", NAU8824_REG_ADC_CH2_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + SOC_SINGLE_TLV("DMIC4 Volume", NAU8824_REG_ADC_CH3_DGAIN_CTRL, + 0, 0x164, 0, dmic_vol_tlv), + + SOC_ENUM("ADC CH0 Select", nau8824_adc_ch0_enum), + SOC_ENUM("ADC CH1 Select", nau8824_adc_ch1_enum), + SOC_ENUM("ADC CH2 Select", nau8824_adc_ch2_enum), + SOC_ENUM("ADC CH3 Select", nau8824_adc_ch3_enum), + + SOC_SINGLE("ADC CH0 TX Switch", NAU8824_REG_TDM_CTRL, 0, 1, 0), + SOC_SINGLE("ADC CH1 TX Switch", NAU8824_REG_TDM_CTRL, 1, 1, 0), + SOC_SINGLE("ADC CH2 TX Switch", NAU8824_REG_TDM_CTRL, 2, 1, 0), + SOC_SINGLE("ADC CH3 TX Switch", NAU8824_REG_TDM_CTRL, 3, 1, 0), + + SOC_ENUM("DACL Channel Source", nau8824_dac_left_sel_enum), + SOC_ENUM("DACR Channel Source", nau8824_dac_right_sel_enum), + + SOC_SINGLE("DACL LR Mix", NAU8824_REG_DAC_MUTE_CTRL, 0, 1, 0), + SOC_SINGLE("DACR LR Mix", NAU8824_REG_DAC_MUTE_CTRL, 1, 1, 0), + + SOC_SINGLE("THD for key media", + NAU8824_REG_VDET_THRESHOLD_1, 8, 0xff, 0), + SOC_SINGLE("THD for key voice command", + NAU8824_REG_VDET_THRESHOLD_1, 0, 0xff, 0), + SOC_SINGLE("THD for key volume up", + NAU8824_REG_VDET_THRESHOLD_2, 8, 0xff, 0), + SOC_SINGLE("THD for key volume down", + NAU8824_REG_VDET_THRESHOLD_2, 0, 0xff, 0), +}; + +static int nau8824_output_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Disables the TESTDAC to let DAC signal pass through. */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, NAU8824_TEST_DAC_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8824_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_ANALOG_CONTROL_2, + NAU8824_CLASSD_CLAMP_DIS, NAU8824_CLASSD_CLAMP_DIS); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_ANALOG_CONTROL_2, + NAU8824_CLASSD_CLAMP_DIS, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8824_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Prevent startup click by letting charge pump to ramp up */ + msleep(10); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_JAMNODCLOW, NAU8824_JAMNODCLOW); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_JAMNODCLOW, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int system_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8824->regmap; + unsigned int value; + bool clk_fll, error; + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + dev_dbg(nau8824->dev, "system clock control : POWER OFF\n"); + /* Set clock source to disable or internal clock before the + * playback or capture end. Codec needs clock for Jack + * detection and button press if jack inserted; otherwise, + * the clock should be closed. + */ + if (nau8824_is_jack_inserted(nau8824)) { + nau8824_config_sysclk(nau8824, + NAU8824_CLK_INTERNAL, 0); + } else { + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); + } + } else { + dev_dbg(nau8824->dev, "system clock control : POWER ON\n"); + /* Check the clock source setting is proper or not + * no matter the source is from FLL or MCLK. + */ + regmap_read(regmap, NAU8824_REG_FLL1, &value); + clk_fll = value & NAU8824_FLL_RATIO_MASK; + /* It's error to use internal clock when playback */ + regmap_read(regmap, NAU8824_REG_FLL6, &value); + error = value & NAU8824_DCO_EN; + if (!error) { + /* Check error depending on source is FLL or MCLK. */ + regmap_read(regmap, NAU8824_REG_CLK_DIVIDER, &value); + if (clk_fll) + error = !(value & NAU8824_CLK_SRC_VCO); + else + error = value & NAU8824_CLK_SRC_VCO; + } + /* Recover the clock source setting if error. */ + if (error) { + if (clk_fll) { + regmap_update_bits(regmap, + NAU8824_REG_FLL6, NAU8824_DCO_EN, 0); + regmap_update_bits(regmap, + NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, + NAU8824_CLK_SRC_VCO); + } else { + nau8824_config_sysclk(nau8824, + NAU8824_CLK_MCLK, 0); + } + } + } + + return 0; +} + +static int dmic_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + int src; + + /* The DMIC clock is gotten from system clock (256fs) divided by + * DMIC_SRC (1, 2, 4, 8, 16, 32). The clock has to be equal or + * less than 3.072 MHz. + */ + for (src = 0; src < 5; src++) { + if ((0x1 << (8 - src)) * nau8824->fs <= DMIC_CLK) + break; + } + dev_dbg(nau8824->dev, "dmic src %d for mclk %d\n", src, nau8824->fs * 256); + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_DMIC_SRC_MASK, (src << NAU8824_CLK_DMIC_SRC_SFT)); + + return 0; +} + +static const struct snd_kcontrol_new nau8824_adc_ch0_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH0_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch1_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH1_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch2_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH2_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_ch3_dmic = + SOC_DAPM_SINGLE("Switch", NAU8824_REG_ENA_CTRL, + NAU8824_ADC_CH3_DMIC_SFT, 1, 0); + +static const struct snd_kcontrol_new nau8824_adc_left_mixer[] = { + SOC_DAPM_SINGLE("MIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_MIC1_SFT, 1, 0), + SOC_DAPM_SINGLE("HSMIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_HSMIC_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_adc_right_mixer[] = { + SOC_DAPM_SINGLE("MIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODER_MIC2_SFT, 1, 0), + SOC_DAPM_SINGLE("HSMIC Switch", NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODER_HSMIC_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_hp_left_mixer[] = { + SOC_DAPM_SINGLE("DAC Right Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPL_EN_SFT, 1, 0), + SOC_DAPM_SINGLE("DAC Left Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACL_HPL_EN_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new nau8824_hp_right_mixer[] = { + SOC_DAPM_SINGLE("DAC Left Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACL_HPR_EN_SFT, 1, 0), + SOC_DAPM_SINGLE("DAC Right Switch", NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPR_EN_SFT, 1, 0), +}; + +static const char * const nau8824_dac_src[] = { "DACL", "DACR" }; + +static SOC_ENUM_SINGLE_DECL( + nau8824_dacl_enum, NAU8824_REG_DAC_CH0_DGAIN_CTRL, + NAU8824_DAC_CH0_SEL_SFT, nau8824_dac_src); + +static SOC_ENUM_SINGLE_DECL( + nau8824_dacr_enum, NAU8824_REG_DAC_CH1_DGAIN_CTRL, + NAU8824_DAC_CH1_SEL_SFT, nau8824_dac_src); + +static const struct snd_kcontrol_new nau8824_dacl_mux = + SOC_DAPM_ENUM("DACL Source", nau8824_dacl_enum); + +static const struct snd_kcontrol_new nau8824_dacr_mux = + SOC_DAPM_ENUM("DACR Source", nau8824_dacr_enum); + + +static const struct snd_soc_dapm_widget nau8824_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("System Clock", SND_SOC_NOPM, 0, 0, + system_clock_control, SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_INPUT("HSMIC1"), + SND_SOC_DAPM_INPUT("HSMIC2"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("DMIC3"), + SND_SOC_DAPM_INPUT("DMIC4"), + + SND_SOC_DAPM_SUPPLY("SAR", NAU8824_REG_SAR_ADC, + NAU8824_SAR_ADC_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS", NAU8824_REG_MIC_BIAS, + NAU8824_MICBIAS_POWERUP_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC12 Power", NAU8824_REG_BIAS_ADJ, + NAU8824_DMIC1_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC34 Power", NAU8824_REG_BIAS_ADJ, + NAU8824_DMIC2_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC Clock", SND_SOC_NOPM, 0, 0, + dmic_clock_control, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SWITCH("DMIC1 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch0_dmic), + SND_SOC_DAPM_SWITCH("DMIC2 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch1_dmic), + SND_SOC_DAPM_SWITCH("DMIC3 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch2_dmic), + SND_SOC_DAPM_SWITCH("DMIC4 Enable", SND_SOC_NOPM, + 0, 0, &nau8824_adc_ch3_dmic), + + SND_SOC_DAPM_MIXER("Left ADC", NAU8824_REG_POWER_UP_CONTROL, + 12, 0, nau8824_adc_left_mixer, + ARRAY_SIZE(nau8824_adc_left_mixer)), + SND_SOC_DAPM_MIXER("Right ADC", NAU8824_REG_POWER_UP_CONTROL, + 13, 0, nau8824_adc_right_mixer, + ARRAY_SIZE(nau8824_adc_right_mixer)), + + SND_SOC_DAPM_ADC("ADCL", NULL, NAU8824_REG_ANALOG_ADC_2, + NAU8824_ADCL_EN_SFT, 0), + SND_SOC_DAPM_ADC("ADCR", NULL, NAU8824_REG_ANALOG_ADC_2, + NAU8824_ADCR_EN_SFT, 0), + + SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DACL", NULL, NAU8824_REG_RDAC, + NAU8824_DACL_EN_SFT, 0), + SND_SOC_DAPM_SUPPLY("DACL Clock", NAU8824_REG_RDAC, + NAU8824_DACL_CLK_SFT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DACR", NULL, NAU8824_REG_RDAC, + NAU8824_DACR_EN_SFT, 0), + SND_SOC_DAPM_SUPPLY("DACR Clock", NAU8824_REG_RDAC, + NAU8824_DACR_CLK_SFT, 0, NULL, 0), + + SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &nau8824_dacl_mux), + SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &nau8824_dacr_mux), + + SND_SOC_DAPM_PGA_S("Output DACL", 0, NAU8824_REG_CHARGE_PUMP_CONTROL, + 8, 1, nau8824_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("Output DACR", 0, NAU8824_REG_CHARGE_PUMP_CONTROL, + 9, 1, nau8824_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("ClassD", 0, NAU8824_REG_CLASSD_GAIN_1, + NAU8824_CLASSD_EN_SFT, 0, nau8824_spk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER("Left Headphone", NAU8824_REG_CLASSG, + NAU8824_CLASSG_LDAC_EN_SFT, 0, nau8824_hp_left_mixer, + ARRAY_SIZE(nau8824_hp_left_mixer)), + SND_SOC_DAPM_MIXER("Right Headphone", NAU8824_REG_CLASSG, + NAU8824_CLASSG_RDAC_EN_SFT, 0, nau8824_hp_right_mixer, + ARRAY_SIZE(nau8824_hp_right_mixer)), + SND_SOC_DAPM_PGA_S("Charge Pump", 1, NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_CHARGE_PUMP_EN_SFT, 0, nau8824_pump_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA("Output Driver L", + NAU8824_REG_POWER_UP_CONTROL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Output Driver R", + NAU8824_REG_POWER_UP_CONTROL, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Main Driver L", + NAU8824_REG_POWER_UP_CONTROL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Main Driver R", + NAU8824_REG_POWER_UP_CONTROL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP Boost Driver", NAU8824_REG_BOOST, + NAU8824_HP_BOOST_DIS_SFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Class G", NAU8824_REG_CLASSG, + NAU8824_CLASSG_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("SPKOUTL"), + SND_SOC_DAPM_OUTPUT("SPKOUTR"), + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route nau8824_dapm_routes[] = { + {"DMIC1 Enable", "Switch", "DMIC1"}, + {"DMIC2 Enable", "Switch", "DMIC2"}, + {"DMIC3 Enable", "Switch", "DMIC3"}, + {"DMIC4 Enable", "Switch", "DMIC4"}, + + {"DMIC1", NULL, "DMIC12 Power"}, + {"DMIC2", NULL, "DMIC12 Power"}, + {"DMIC3", NULL, "DMIC34 Power"}, + {"DMIC4", NULL, "DMIC34 Power"}, + {"DMIC12 Power", NULL, "DMIC Clock"}, + {"DMIC34 Power", NULL, "DMIC Clock"}, + + {"Left ADC", "MIC Switch", "MIC1"}, + {"Left ADC", "HSMIC Switch", "HSMIC1"}, + {"Right ADC", "MIC Switch", "MIC2"}, + {"Right ADC", "HSMIC Switch", "HSMIC2"}, + + {"ADCL", NULL, "Left ADC"}, + {"ADCR", NULL, "Right ADC"}, + + {"AIFTX", NULL, "MICBIAS"}, + {"AIFTX", NULL, "ADCL"}, + {"AIFTX", NULL, "ADCR"}, + {"AIFTX", NULL, "DMIC1 Enable"}, + {"AIFTX", NULL, "DMIC2 Enable"}, + {"AIFTX", NULL, "DMIC3 Enable"}, + {"AIFTX", NULL, "DMIC4 Enable"}, + + {"AIFTX", NULL, "System Clock"}, + {"AIFRX", NULL, "System Clock"}, + + {"DACL", NULL, "AIFRX"}, + {"DACL", NULL, "DACL Clock"}, + {"DACR", NULL, "AIFRX"}, + {"DACR", NULL, "DACR Clock"}, + + {"DACL Mux", "DACL", "DACL"}, + {"DACL Mux", "DACR", "DACR"}, + {"DACR Mux", "DACL", "DACL"}, + {"DACR Mux", "DACR", "DACR"}, + + {"Output DACL", NULL, "DACL Mux"}, + {"Output DACR", NULL, "DACR Mux"}, + + {"ClassD", NULL, "Output DACL"}, + {"ClassD", NULL, "Output DACR"}, + + {"Left Headphone", "DAC Left Switch", "Output DACL"}, + {"Left Headphone", "DAC Right Switch", "Output DACR"}, + {"Right Headphone", "DAC Left Switch", "Output DACL"}, + {"Right Headphone", "DAC Right Switch", "Output DACR"}, + + {"Charge Pump", NULL, "Left Headphone"}, + {"Charge Pump", NULL, "Right Headphone"}, + {"Output Driver L", NULL, "Charge Pump"}, + {"Output Driver R", NULL, "Charge Pump"}, + {"Main Driver L", NULL, "Output Driver L"}, + {"Main Driver R", NULL, "Output Driver R"}, + {"Class G", NULL, "Main Driver L"}, + {"Class G", NULL, "Main Driver R"}, + {"HP Boost Driver", NULL, "Class G"}, + + {"SPKOUTL", NULL, "ClassD"}, + {"SPKOUTR", NULL, "ClassD"}, + {"HPOL", NULL, "HP Boost Driver"}, + {"HPOR", NULL, "HP Boost Driver"}, +}; + +static bool nau8824_is_jack_inserted(struct nau8824 *nau8824) +{ + struct snd_soc_jack *jack = nau8824->jack; + bool insert = FALSE; + + if (nau8824->irq && jack) + insert = jack->status & SND_JACK_HEADPHONE; + + return insert; +} + +static void nau8824_int_status_clear_all(struct regmap *regmap) +{ + int active_irq, clear_irq, i; + + /* Reset the intrruption status from rightmost bit if the corres- + * ponding irq event occurs. + */ + regmap_read(regmap, NAU8824_REG_IRQ, &active_irq); + for (i = 0; i < NAU8824_REG_DATA_LEN; i++) { + clear_irq = (0x1 << i); + if (active_irq & clear_irq) + regmap_write(regmap, + NAU8824_REG_CLEAR_INT_REG, clear_irq); + } +} + +static void nau8824_dapm_disable_pin(struct nau8824 *nau8824, const char *pin) +{ + struct snd_soc_dapm_context *dapm = nau8824->dapm; + const char *prefix = dapm->component->name_prefix; + char prefixed_pin[80]; + + if (prefix) { + snprintf(prefixed_pin, sizeof(prefixed_pin), "%s %s", + prefix, pin); + snd_soc_dapm_disable_pin(dapm, prefixed_pin); + } else { + snd_soc_dapm_disable_pin(dapm, pin); + } +} + +static void nau8824_dapm_enable_pin(struct nau8824 *nau8824, const char *pin) +{ + struct snd_soc_dapm_context *dapm = nau8824->dapm; + const char *prefix = dapm->component->name_prefix; + char prefixed_pin[80]; + + if (prefix) { + snprintf(prefixed_pin, sizeof(prefixed_pin), "%s %s", + prefix, pin); + snd_soc_dapm_force_enable_pin(dapm, prefixed_pin); + } else { + snd_soc_dapm_force_enable_pin(dapm, pin); + } +} + +static void nau8824_eject_jack(struct nau8824 *nau8824) +{ + struct snd_soc_dapm_context *dapm = nau8824->dapm; + struct regmap *regmap = nau8824->regmap; + + /* Clear all interruption status */ + nau8824_int_status_clear_all(regmap); + + nau8824_dapm_disable_pin(nau8824, "SAR"); + nau8824_dapm_disable_pin(nau8824, "MICBIAS"); + snd_soc_dapm_sync(dapm); + + /* Enable the insertion interruption, disable the ejection + * interruption, and then bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_KEY_RELEASE_DIS | NAU8824_IRQ_KEY_SHORT_PRESS_DIS | + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_INSERT_DIS, + NAU8824_IRQ_KEY_RELEASE_DIS | NAU8824_IRQ_KEY_SHORT_PRESS_DIS | + NAU8824_IRQ_EJECT_DIS); + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN | NAU8824_IRQ_EJECT_EN, + NAU8824_IRQ_INSERT_EN); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + + /* Close clock for jack type detection at manual mode */ + if (dapm->bias_level < SND_SOC_BIAS_PREPARE) + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); +} + +static void nau8824_jdet_work(struct work_struct *work) +{ + struct nau8824 *nau8824 = container_of( + work, struct nau8824, jdet_work); + struct snd_soc_dapm_context *dapm = nau8824->dapm; + struct regmap *regmap = nau8824->regmap; + int adc_value, event = 0, event_mask = 0; + + nau8824_dapm_enable_pin(nau8824, "MICBIAS"); + nau8824_dapm_enable_pin(nau8824, "SAR"); + snd_soc_dapm_sync(dapm); + + msleep(100); + + regmap_read(regmap, NAU8824_REG_SAR_ADC_DATA_OUT, &adc_value); + adc_value = adc_value & NAU8824_SAR_ADC_DATA_MASK; + dev_dbg(nau8824->dev, "SAR ADC data 0x%02x\n", adc_value); + if (adc_value < HEADSET_SARADC_THD) { + event |= SND_JACK_HEADPHONE; + + nau8824_dapm_disable_pin(nau8824, "SAR"); + nau8824_dapm_disable_pin(nau8824, "MICBIAS"); + snd_soc_dapm_sync(dapm); + } else { + event |= SND_JACK_HEADSET; + } + event_mask |= SND_JACK_HEADSET; + snd_soc_jack_report(nau8824->jack, event, event_mask); + + /* Enable short key press and release interruption. */ + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_KEY_RELEASE_DIS | + NAU8824_IRQ_KEY_SHORT_PRESS_DIS, 0); + + nau8824_sema_release(nau8824); +} + +static void nau8824_setup_auto_irq(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + /* Enable jack ejection interruption. */ + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN | NAU8824_IRQ_EJECT_EN, + NAU8824_IRQ_EJECT_EN); + regmap_update_bits(regmap, NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_EJECT_DIS, 0); + /* Enable internal VCO needed for interruptions */ + if (nau8824->dapm->bias_level < SND_SOC_BIAS_PREPARE) + nau8824_config_sysclk(nau8824, NAU8824_CLK_INTERNAL, 0); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, 0); +} + +static int nau8824_button_decode(int value) +{ + int buttons = 0; + + /* The chip supports up to 8 buttons, but ALSA defines + * only 6 buttons. + */ + if (value & BIT(0)) + buttons |= SND_JACK_BTN_0; + if (value & BIT(1)) + buttons |= SND_JACK_BTN_1; + if (value & BIT(2)) + buttons |= SND_JACK_BTN_2; + if (value & BIT(3)) + buttons |= SND_JACK_BTN_3; + if (value & BIT(4)) + buttons |= SND_JACK_BTN_4; + if (value & BIT(5)) + buttons |= SND_JACK_BTN_5; + + return buttons; +} + +#define NAU8824_BUTTONS (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3) + +static irqreturn_t nau8824_interrupt(int irq, void *data) +{ + struct nau8824 *nau8824 = (struct nau8824 *)data; + struct regmap *regmap = nau8824->regmap; + int active_irq, clear_irq = 0, event = 0, event_mask = 0; + + if (regmap_read(regmap, NAU8824_REG_IRQ, &active_irq)) { + dev_err(nau8824->dev, "failed to read irq status\n"); + return IRQ_NONE; + } + dev_dbg(nau8824->dev, "IRQ %x\n", active_irq); + + if (active_irq & NAU8824_JACK_EJECTION_DETECTED) { + nau8824_eject_jack(nau8824); + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8824_JACK_EJECTION_DETECTED; + /* release semaphore held after resume, + * and cancel jack detection + */ + nau8824_sema_release(nau8824); + cancel_work_sync(&nau8824->jdet_work); + } else if (active_irq & NAU8824_KEY_SHORT_PRESS_IRQ) { + int key_status, button_pressed; + + regmap_read(regmap, NAU8824_REG_CLEAR_INT_REG, + &key_status); + + /* lower 8 bits of the register are for pressed keys */ + button_pressed = nau8824_button_decode(key_status); + + event |= button_pressed; + dev_dbg(nau8824->dev, "button %x pressed\n", event); + event_mask |= NAU8824_BUTTONS; + clear_irq = NAU8824_KEY_SHORT_PRESS_IRQ; + } else if (active_irq & NAU8824_KEY_RELEASE_IRQ) { + event_mask = NAU8824_BUTTONS; + clear_irq = NAU8824_KEY_RELEASE_IRQ; + } else if (active_irq & NAU8824_JACK_INSERTION_DETECTED) { + /* Turn off insertion interruption at manual mode */ + regmap_update_bits(regmap, + NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_INSERT_DIS, + NAU8824_IRQ_INSERT_DIS); + regmap_update_bits(regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_INSERT_EN, 0); + /* detect microphone and jack type */ + cancel_work_sync(&nau8824->jdet_work); + schedule_work(&nau8824->jdet_work); + + /* Enable interruption for jack type detection at audo + * mode which can detect microphone and jack type. + */ + nau8824_setup_auto_irq(nau8824); + } + + if (!clear_irq) + clear_irq = active_irq; + /* clears the rightmost interruption */ + regmap_write(regmap, NAU8824_REG_CLEAR_INT_REG, clear_irq); + + if (event_mask) + snd_soc_jack_report(nau8824->jack, event, event_mask); + + return IRQ_HANDLED; +} + +static int nau8824_clock_check(struct nau8824 *nau8824, + int stream, int rate, int osr) +{ + int osrate; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (osr >= ARRAY_SIZE(osr_dac_sel)) + return -EINVAL; + osrate = osr_dac_sel[osr].osr; + } else { + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + osrate = osr_adc_sel[osr].osr; + } + + if (!osrate || rate * osr > CLK_DA_AD_MAX) { + dev_err(nau8824->dev, "exceed the maximum frequency of CLK_ADC or CLK_DAC\n"); + return -EINVAL; + } + + return 0; +} + +static int nau8824_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, osr, ctrl_val, bclk_fs, bclk_div; + + nau8824_sema_acquire(nau8824, HZ); + + /* CLK_DAC or CLK_ADC = OSR * FS + * DAC or ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + nau8824->fs = params_rate(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_read(nau8824->regmap, + NAU8824_REG_DAC_FILTER_CTRL_1, &osr); + osr &= NAU8824_DAC_OVERSAMPLE_MASK; + if (nau8824_clock_check(nau8824, substream->stream, + nau8824->fs, osr)) + return -EINVAL; + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_DAC_SRC_MASK, + osr_dac_sel[osr].clk_src << NAU8824_CLK_DAC_SRC_SFT); + } else { + regmap_read(nau8824->regmap, + NAU8824_REG_ADC_FILTER_CTRL, &osr); + osr &= NAU8824_ADC_SYNC_DOWN_MASK; + if (nau8824_clock_check(nau8824, substream->stream, + nau8824->fs, osr)) + return -EINVAL; + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8824_CLK_ADC_SRC_SFT); + } + + /* make BCLK and LRC divde configuration if the codec as master. */ + regmap_read(nau8824->regmap, + NAU8824_REG_PORT0_I2S_PCM_CTRL_2, &ctrl_val); + if (ctrl_val & NAU8824_I2S_MS_MASTER) { + /* get the bclk and fs ratio */ + bclk_fs = snd_soc_params_to_bclk(params) / nau8824->fs; + if (bclk_fs <= 32) + bclk_div = 0x3; + else if (bclk_fs <= 64) + bclk_div = 0x2; + else if (bclk_fs <= 128) + bclk_div = 0x1; + else if (bclk_fs <= 256) + bclk_div = 0; + else + return -EINVAL; + regmap_update_bits(nau8824->regmap, + NAU8824_REG_PORT0_I2S_PCM_CTRL_2, + NAU8824_I2S_LRC_DIV_MASK | NAU8824_I2S_BLK_DIV_MASK, + (bclk_div << NAU8824_I2S_LRC_DIV_SFT) | bclk_div); + } + + switch (params_width(params)) { + case 16: + val_len |= NAU8824_I2S_DL_16; + break; + case 20: + val_len |= NAU8824_I2S_DL_20; + break; + case 24: + val_len |= NAU8824_I2S_DL_24; + break; + case 32: + val_len |= NAU8824_I2S_DL_32; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_1, + NAU8824_I2S_DL_MASK, val_len); + + nau8824_sema_release(nau8824); + + return 0; +} + +static int nau8824_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + nau8824_sema_acquire(nau8824, HZ); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8824_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8824_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8824_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8824_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8824_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8824_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8824_I2S_DF_PCM_AB; + ctrl1_val |= NAU8824_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_1, + NAU8824_I2S_DF_MASK | NAU8824_I2S_BP_MASK | + NAU8824_I2S_PCMB_EN, ctrl1_val); + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_I2S_PCM_CTRL_2, + NAU8824_I2S_MS_MASK, ctrl2_val); + + nau8824_sema_release(nau8824); + + return 0; +} + +/** + * nau8824_set_tdm_slot - configure DAI TDM. + * @dai: DAI + * @tx_mask: Bitmask representing active TX slots. Ex. + * 0xf for normal 4 channel TDM. + * 0xf0 for shifted 4 channel TDM + * @rx_mask: Bitmask [0:1] representing active DACR RX slots. + * Bitmask [2:3] representing active DACL RX slots. + * 00=CH0,01=CH1,10=CH2,11=CH3. Ex. + * 0xf for DACL/R selecting TDM CH3. + * 0xf0 for DACL/R selecting shifted TDM CH3. + * @slots: Number of slots in use. + * @slot_width: Width in bits for each slot. + * + * Configures a DAI for TDM operation. Only support 4 slots TDM. + */ +static int nau8824_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + unsigned int tslot_l = 0, ctrl_val = 0; + + if (slots > 4 || ((tx_mask & 0xf0) && (tx_mask & 0xf)) || + ((rx_mask & 0xf0) && (rx_mask & 0xf)) || + ((rx_mask & 0xf0) && (tx_mask & 0xf)) || + ((rx_mask & 0xf) && (tx_mask & 0xf0))) + return -EINVAL; + + ctrl_val |= (NAU8824_TDM_MODE | NAU8824_TDM_OFFSET_EN); + if (tx_mask & 0xf0) { + tslot_l = 4 * slot_width; + ctrl_val |= (tx_mask >> 4); + } else { + ctrl_val |= tx_mask; + } + if (rx_mask & 0xf0) + ctrl_val |= ((rx_mask >> 4) << NAU8824_TDM_DACR_RX_SFT); + else + ctrl_val |= (rx_mask << NAU8824_TDM_DACR_RX_SFT); + + regmap_update_bits(nau8824->regmap, NAU8824_REG_TDM_CTRL, + NAU8824_TDM_MODE | NAU8824_TDM_OFFSET_EN | + NAU8824_TDM_DACL_RX_MASK | NAU8824_TDM_DACR_RX_MASK | + NAU8824_TDM_TX_MASK, ctrl_val); + regmap_update_bits(nau8824->regmap, NAU8824_REG_PORT0_LEFT_TIME_SLOT, + NAU8824_TSLOT_L_MASK, tslot_l); + + return 0; +} + +/** + * nau8824_calc_fll_param - Calculate FLL parameters. + * @fll_in: external clock provided to codec. + * @fs: sampling rate. + * @fll_param: Pointer to structure of FLL parameters. + * + * Calculate FLL parameters to configure codec. + * + * Returns 0 for success or negative error code. + */ +static int nau8824_calc_fll_param(unsigned int fll_in, + unsigned int fs, struct nau8824_fll *fll_param) +{ + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing + * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8824_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in / fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256ULL * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 16) & 0x3FF; + fll_param->fll_frac = fvco & 0xFFFF; + return 0; +} + +static void nau8824_fll_apply(struct regmap *regmap, + struct nau8824_fll *fll_param) +{ + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK | NAU8824_CLK_MCLK_SRC_MASK, + NAU8824_CLK_SRC_MCLK | fll_param->mclk_src); + regmap_update_bits(regmap, NAU8824_REG_FLL1, + NAU8824_FLL_RATIO_MASK, fll_param->ratio); + /* FLL 16-bit fractional input */ + regmap_write(regmap, NAU8824_REG_FLL2, fll_param->fll_frac); + /* FLL 10-bit integer input */ + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(regmap, NAU8824_REG_FLL4, + NAU8824_FLL_REF_DIV_MASK, + fll_param->clk_ref_div << NAU8824_FLL_REF_DIV_SFT); + /* select divided VCO input */ + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_CLK_SW_MASK, NAU8824_FLL_CLK_SW_REF); + /* Disable free-running mode */ + regmap_update_bits(regmap, + NAU8824_REG_FLL6, NAU8824_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_MASK, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_FILTER); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_SDM_EN, NAU8824_SDM_EN); + } else { + regmap_update_bits(regmap, NAU8824_REG_FLL5, + NAU8824_FLL_PDB_DAC_EN | NAU8824_FLL_LOOP_FTR_EN | + NAU8824_FLL_FTR_SW_MASK, NAU8824_FLL_FTR_SW_ACCU); + regmap_update_bits(regmap, + NAU8824_REG_FLL6, NAU8824_SDM_EN, 0); + } +} + +/* freq_out must be 256*Fs in order to achieve the best performance */ +static int nau8824_set_pll(struct snd_soc_component *component, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + struct nau8824_fll fll_param; + int ret, fs; + + fs = freq_out / 256; + ret = nau8824_calc_fll_param(freq_in, fs, &fll_param); + if (ret < 0) { + dev_err(nau8824->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_dbg(nau8824->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, + fll_param.fll_int, fll_param.clk_ref_div); + + nau8824_fll_apply(nau8824->regmap, &fll_param); + mdelay(2); + regmap_update_bits(nau8824->regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_VCO); + + return 0; +} + +static int nau8824_config_sysclk(struct nau8824 *nau8824, + int clk_id, unsigned int freq) +{ + struct regmap *regmap = nau8824->regmap; + + switch (clk_id) { + case NAU8824_CLK_DIS: + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, 0); + break; + + case NAU8824_CLK_MCLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, 0); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_INTERNAL: + regmap_update_bits(regmap, NAU8824_REG_FLL6, + NAU8824_DCO_EN, NAU8824_DCO_EN); + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_SRC_MASK, NAU8824_CLK_SRC_VCO); + break; + + case NAU8824_CLK_FLL_MCLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_MCLK); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_FLL_BLK: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_BLK); + nau8824_sema_release(nau8824); + break; + + case NAU8824_CLK_FLL_FS: + nau8824_sema_acquire(nau8824, HZ); + regmap_update_bits(regmap, NAU8824_REG_FLL3, + NAU8824_FLL_CLK_SRC_MASK, NAU8824_FLL_CLK_SRC_FS); + nau8824_sema_release(nau8824); + break; + + default: + dev_err(nau8824->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + dev_dbg(nau8824->dev, "Sysclk is %dHz and clock id is %d\n", freq, + clk_id); + + return 0; +} + +static int nau8824_set_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + + return nau8824_config_sysclk(nau8824, clk_id, freq); +} + +static void nau8824_resume_setup(struct nau8824 *nau8824) +{ + nau8824_config_sysclk(nau8824, NAU8824_CLK_DIS, 0); + if (nau8824->irq) { + /* Clear all interruption status */ + nau8824_int_status_clear_all(nau8824->regmap); + /* Enable jack detection at sleep mode, insertion detection, + * and ejection detection. + */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, + NAU8824_IRQ_EJECT_DIS | NAU8824_IRQ_INSERT_DIS, 0); + } +} + +static int nau8824_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Setup codec configuration after resume */ + nau8824_resume_setup(nau8824); + } + break; + + case SND_SOC_BIAS_OFF: + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, 0x3ff, 0x3ff); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, 0); + break; + } + + return 0; +} + +static int nau8824_component_probe(struct snd_soc_component *component) +{ + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + nau8824->dapm = dapm; + + return 0; +} + +static int __maybe_unused nau8824_suspend(struct snd_soc_component *component) +{ + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + + if (nau8824->irq) { + disable_irq(nau8824->irq); + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + } + regcache_cache_only(nau8824->regmap, true); + regcache_mark_dirty(nau8824->regmap); + + return 0; +} + +static int __maybe_unused nau8824_resume(struct snd_soc_component *component) +{ + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(nau8824->regmap, false); + regcache_sync(nau8824->regmap); + if (nau8824->irq) { + /* Hold semaphore to postpone playback happening + * until jack detection done. + */ + nau8824_sema_acquire(nau8824, 0); + enable_irq(nau8824->irq); + } + + return 0; +} + +static const struct snd_soc_component_driver nau8824_component_driver = { + .probe = nau8824_component_probe, + .set_sysclk = nau8824_set_sysclk, + .set_pll = nau8824_set_pll, + .set_bias_level = nau8824_set_bias_level, + .suspend = nau8824_suspend, + .resume = nau8824_resume, + .controls = nau8824_snd_controls, + .num_controls = ARRAY_SIZE(nau8824_snd_controls), + .dapm_widgets = nau8824_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8824_dapm_widgets), + .dapm_routes = nau8824_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8824_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct snd_soc_dai_ops nau8824_dai_ops = { + .hw_params = nau8824_hw_params, + .set_fmt = nau8824_set_fmt, + .set_tdm_slot = nau8824_set_tdm_slot, +}; + +#define NAU8824_RATES SNDRV_PCM_RATE_8000_192000 +#define NAU8824_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8824_dai = { + .name = NAU8824_CODEC_DAI, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8824_RATES, + .formats = NAU8824_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8824_RATES, + .formats = NAU8824_FORMATS, + }, + .ops = &nau8824_dai_ops, +}; + +static const struct regmap_config nau8824_regmap_config = { + .val_bits = NAU8824_REG_ADDR_LEN, + .reg_bits = NAU8824_REG_DATA_LEN, + + .max_register = NAU8824_REG_MAX, + .readable_reg = nau8824_readable_reg, + .writeable_reg = nau8824_writeable_reg, + .volatile_reg = nau8824_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8824_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8824_reg_defaults), +}; + +/** + * nau8824_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int nau8824_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); + int ret; + + nau8824->jack = jack; + /* Initiate jack detection work queue */ + INIT_WORK(&nau8824->jdet_work, nau8824_jdet_work); + ret = devm_request_threaded_irq(nau8824->dev, nau8824->irq, NULL, + nau8824_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "nau8824", nau8824); + if (ret) { + dev_err(nau8824->dev, "Cannot request irq %d (%d)\n", + nau8824->irq, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(nau8824_enable_jack_detect); + +static void nau8824_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8824_REG_RESET, 0x00); + regmap_write(regmap, NAU8824_REG_RESET, 0x00); +} + +static void nau8824_setup_buttons(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_TRACKING_GAIN_MASK, + nau8824->sar_voltage << NAU8824_SAR_TRACKING_GAIN_SFT); + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_COMPARE_TIME_MASK, + nau8824->sar_compare_time << NAU8824_SAR_COMPARE_TIME_SFT); + regmap_update_bits(regmap, NAU8824_REG_SAR_ADC, + NAU8824_SAR_SAMPLING_TIME_MASK, + nau8824->sar_sampling_time << NAU8824_SAR_SAMPLING_TIME_SFT); + + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_LEVELS_NR_MASK, + (nau8824->sar_threshold_num - 1) << NAU8824_LEVELS_NR_SFT); + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_HYSTERESIS_MASK, + nau8824->sar_hysteresis << NAU8824_HYSTERESIS_SFT); + regmap_update_bits(regmap, NAU8824_REG_VDET_COEFFICIENT, + NAU8824_SHORTKEY_DEBOUNCE_MASK, + nau8824->key_debounce << NAU8824_SHORTKEY_DEBOUNCE_SFT); + + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_1, + (nau8824->sar_threshold[0] << 8) | nau8824->sar_threshold[1]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_2, + (nau8824->sar_threshold[2] << 8) | nau8824->sar_threshold[3]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_3, + (nau8824->sar_threshold[4] << 8) | nau8824->sar_threshold[5]); + regmap_write(regmap, NAU8824_REG_VDET_THRESHOLD_4, + (nau8824->sar_threshold[6] << 8) | nau8824->sar_threshold[7]); +} + +static void nau8824_init_regs(struct nau8824 *nau8824) +{ + struct regmap *regmap = nau8824->regmap; + + /* Enable Bias/VMID/VMID Tieoff */ + regmap_update_bits(regmap, NAU8824_REG_BIAS_ADJ, + NAU8824_VMID | NAU8824_VMID_SEL_MASK, NAU8824_VMID | + (nau8824->vref_impedance << NAU8824_VMID_SEL_SFT)); + regmap_update_bits(regmap, NAU8824_REG_BOOST, + NAU8824_GLOBAL_BIAS_EN, NAU8824_GLOBAL_BIAS_EN); + mdelay(2); + regmap_update_bits(regmap, NAU8824_REG_MIC_BIAS, + NAU8824_MICBIAS_VOLTAGE_MASK, nau8824->micbias_voltage); + /* Disable Boost Driver, Automatic Short circuit protection enable */ + regmap_update_bits(regmap, NAU8824_REG_BOOST, + NAU8824_PRECHARGE_DIS | NAU8824_HP_BOOST_DIS | + NAU8824_HP_BOOST_G_DIS | NAU8824_SHORT_SHUTDOWN_EN, + NAU8824_PRECHARGE_DIS | NAU8824_HP_BOOST_DIS | + NAU8824_HP_BOOST_G_DIS | NAU8824_SHORT_SHUTDOWN_EN); + /* Scaling for ADC and DAC clock */ + regmap_update_bits(regmap, NAU8824_REG_CLK_DIVIDER, + NAU8824_CLK_ADC_SRC_MASK | NAU8824_CLK_DAC_SRC_MASK, + (0x1 << NAU8824_CLK_ADC_SRC_SFT) | + (0x1 << NAU8824_CLK_DAC_SRC_SFT)); + regmap_update_bits(regmap, NAU8824_REG_DAC_MUTE_CTRL, + NAU8824_DAC_ZC_EN, NAU8824_DAC_ZC_EN); + regmap_update_bits(regmap, NAU8824_REG_ENA_CTRL, + NAU8824_DAC_CH1_EN | NAU8824_DAC_CH0_EN | + NAU8824_ADC_CH0_EN | NAU8824_ADC_CH1_EN | + NAU8824_ADC_CH2_EN | NAU8824_ADC_CH3_EN, + NAU8824_DAC_CH1_EN | NAU8824_DAC_CH0_EN | + NAU8824_ADC_CH0_EN | NAU8824_ADC_CH1_EN | + NAU8824_ADC_CH2_EN | NAU8824_ADC_CH3_EN); + regmap_update_bits(regmap, NAU8824_REG_CLK_GATING_ENA, + NAU8824_CLK_ADC_CH23_EN | NAU8824_CLK_ADC_CH01_EN | + NAU8824_CLK_DAC_CH1_EN | NAU8824_CLK_DAC_CH0_EN | + NAU8824_CLK_I2S_EN | NAU8824_CLK_GAIN_EN | + NAU8824_CLK_SAR_EN | NAU8824_CLK_DMIC_CH23_EN, + NAU8824_CLK_ADC_CH23_EN | NAU8824_CLK_ADC_CH01_EN | + NAU8824_CLK_DAC_CH1_EN | NAU8824_CLK_DAC_CH0_EN | + NAU8824_CLK_I2S_EN | NAU8824_CLK_GAIN_EN | + NAU8824_CLK_SAR_EN | NAU8824_CLK_DMIC_CH23_EN); + /* Class G timer 64ms */ + regmap_update_bits(regmap, NAU8824_REG_CLASSG, + NAU8824_CLASSG_TIMER_MASK, + 0x20 << NAU8824_CLASSG_TIMER_SFT); + regmap_update_bits(regmap, NAU8824_REG_TRIM_SETTINGS, + NAU8824_DRV_CURR_INC, NAU8824_DRV_CURR_INC); + /* Disable DACR/L power */ + regmap_update_bits(regmap, NAU8824_REG_CHARGE_PUMP_CONTROL, + NAU8824_SPKR_PULL_DOWN | NAU8824_SPKL_PULL_DOWN | + NAU8824_POWER_DOWN_DACR | NAU8824_POWER_DOWN_DACL, + NAU8824_SPKR_PULL_DOWN | NAU8824_SPKL_PULL_DOWN | + NAU8824_POWER_DOWN_DACR | NAU8824_POWER_DOWN_DACL); + /* Enable TESTDAC. This sets the analog DAC inputs to a '0' input + * signal to avoid any glitches due to power up transients in both + * the analog and digital DAC circuit. + */ + regmap_update_bits(regmap, NAU8824_REG_ENABLE_LO, + NAU8824_TEST_DAC_EN, NAU8824_TEST_DAC_EN); + /* Config L/R channel */ + regmap_update_bits(regmap, NAU8824_REG_DAC_CH0_DGAIN_CTRL, + NAU8824_DAC_CH0_SEL_MASK, NAU8824_DAC_CH0_SEL_I2S0); + regmap_update_bits(regmap, NAU8824_REG_DAC_CH1_DGAIN_CTRL, + NAU8824_DAC_CH1_SEL_MASK, NAU8824_DAC_CH1_SEL_I2S1); + regmap_update_bits(regmap, NAU8824_REG_ENABLE_LO, + NAU8824_DACR_HPR_EN | NAU8824_DACL_HPL_EN, + NAU8824_DACR_HPR_EN | NAU8824_DACL_HPL_EN); + /* Default oversampling/decimations settings are unusable + * (audible hiss). Set it to something better. + */ + regmap_update_bits(regmap, NAU8824_REG_ADC_FILTER_CTRL, + NAU8824_ADC_SYNC_DOWN_MASK, NAU8824_ADC_SYNC_DOWN_64); + regmap_update_bits(regmap, NAU8824_REG_DAC_FILTER_CTRL_1, + NAU8824_DAC_CICCLP_OFF | NAU8824_DAC_OVERSAMPLE_MASK, + NAU8824_DAC_CICCLP_OFF | NAU8824_DAC_OVERSAMPLE_64); + /* DAC clock delay 2ns, VREF */ + regmap_update_bits(regmap, NAU8824_REG_RDAC, + NAU8824_RDAC_CLK_DELAY_MASK | NAU8824_RDAC_VREF_MASK, + (0x2 << NAU8824_RDAC_CLK_DELAY_SFT) | + (0x3 << NAU8824_RDAC_VREF_SFT)); + /* PGA input mode selection */ + regmap_update_bits(regmap, NAU8824_REG_FEPGA, + NAU8824_FEPGA_MODEL_SHORT_EN | NAU8824_FEPGA_MODER_SHORT_EN, + NAU8824_FEPGA_MODEL_SHORT_EN | NAU8824_FEPGA_MODER_SHORT_EN); + /* Digital microphone control */ + regmap_update_bits(regmap, NAU8824_REG_ANALOG_CONTROL_1, + NAU8824_DMIC_CLK_DRV_STRG | NAU8824_DMIC_CLK_SLEW_FAST, + NAU8824_DMIC_CLK_DRV_STRG | NAU8824_DMIC_CLK_SLEW_FAST); + regmap_update_bits(regmap, NAU8824_REG_JACK_DET_CTRL, + NAU8824_JACK_LOGIC, + /* jkdet_polarity - 1 is for active-low */ + nau8824->jkdet_polarity ? 0 : NAU8824_JACK_LOGIC); + regmap_update_bits(regmap, + NAU8824_REG_JACK_DET_CTRL, NAU8824_JACK_EJECT_DT_MASK, + (nau8824->jack_eject_debounce << NAU8824_JACK_EJECT_DT_SFT)); + if (nau8824->sar_threshold_num) + nau8824_setup_buttons(nau8824); +} + +static int nau8824_setup_irq(struct nau8824 *nau8824) +{ + /* Disable interruption before codec initiation done */ + regmap_update_bits(nau8824->regmap, NAU8824_REG_ENA_CTRL, + NAU8824_JD_SLEEP_MODE, NAU8824_JD_SLEEP_MODE); + regmap_update_bits(nau8824->regmap, + NAU8824_REG_INTERRUPT_SETTING, 0x3ff, 0x3ff); + regmap_update_bits(nau8824->regmap, NAU8824_REG_INTERRUPT_SETTING_1, + NAU8824_IRQ_EJECT_EN | NAU8824_IRQ_INSERT_EN, 0); + + return 0; +} + +static void nau8824_print_device_properties(struct nau8824 *nau8824) +{ + struct device *dev = nau8824->dev; + int i; + + dev_dbg(dev, "jkdet-polarity: %d\n", nau8824->jkdet_polarity); + dev_dbg(dev, "micbias-voltage: %d\n", nau8824->micbias_voltage); + dev_dbg(dev, "vref-impedance: %d\n", nau8824->vref_impedance); + + dev_dbg(dev, "sar-threshold-num: %d\n", nau8824->sar_threshold_num); + for (i = 0; i < nau8824->sar_threshold_num; i++) + dev_dbg(dev, "sar-threshold[%d]=%x\n", i, + nau8824->sar_threshold[i]); + + dev_dbg(dev, "sar-hysteresis: %d\n", nau8824->sar_hysteresis); + dev_dbg(dev, "sar-voltage: %d\n", nau8824->sar_voltage); + dev_dbg(dev, "sar-compare-time: %d\n", nau8824->sar_compare_time); + dev_dbg(dev, "sar-sampling-time: %d\n", nau8824->sar_sampling_time); + dev_dbg(dev, "short-key-debounce: %d\n", nau8824->key_debounce); + dev_dbg(dev, "jack-eject-debounce: %d\n", + nau8824->jack_eject_debounce); +} + +static int nau8824_read_device_properties(struct device *dev, + struct nau8824 *nau8824) { + int ret; + + ret = device_property_read_u32(dev, "nuvoton,jkdet-polarity", + &nau8824->jkdet_polarity); + if (ret) + nau8824->jkdet_polarity = 1; + ret = device_property_read_u32(dev, "nuvoton,micbias-voltage", + &nau8824->micbias_voltage); + if (ret) + nau8824->micbias_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,vref-impedance", + &nau8824->vref_impedance); + if (ret) + nau8824->vref_impedance = 2; + ret = device_property_read_u32(dev, "nuvoton,sar-threshold-num", + &nau8824->sar_threshold_num); + if (ret) + nau8824->sar_threshold_num = 4; + ret = device_property_read_u32_array(dev, "nuvoton,sar-threshold", + nau8824->sar_threshold, nau8824->sar_threshold_num); + if (ret) { + nau8824->sar_threshold[0] = 0x0a; + nau8824->sar_threshold[1] = 0x14; + nau8824->sar_threshold[2] = 0x26; + nau8824->sar_threshold[3] = 0x73; + } + ret = device_property_read_u32(dev, "nuvoton,sar-hysteresis", + &nau8824->sar_hysteresis); + if (ret) + nau8824->sar_hysteresis = 0; + ret = device_property_read_u32(dev, "nuvoton,sar-voltage", + &nau8824->sar_voltage); + if (ret) + nau8824->sar_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,sar-compare-time", + &nau8824->sar_compare_time); + if (ret) + nau8824->sar_compare_time = 1; + ret = device_property_read_u32(dev, "nuvoton,sar-sampling-time", + &nau8824->sar_sampling_time); + if (ret) + nau8824->sar_sampling_time = 1; + ret = device_property_read_u32(dev, "nuvoton,short-key-debounce", + &nau8824->key_debounce); + if (ret) + nau8824->key_debounce = 0; + ret = device_property_read_u32(dev, "nuvoton,jack-eject-debounce", + &nau8824->jack_eject_debounce); + if (ret) + nau8824->jack_eject_debounce = 1; + + return 0; +} + +/* Please keep this list alphabetically sorted */ +static const struct dmi_system_id nau8824_quirk_table[] = { + { + /* Cyberbook T116 rugged tablet */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Default string"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "20170531"), + }, + .driver_data = (void *)(NAU8824_JD_ACTIVE_HIGH), + }, + {} +}; + +static void nau8824_check_quirks(void) +{ + const struct dmi_system_id *dmi_id; + + if (quirk_override != -1) { + nau8824_quirk = quirk_override; + return; + } + + dmi_id = dmi_first_match(nau8824_quirk_table); + if (dmi_id) + nau8824_quirk = (unsigned long)dmi_id->driver_data; +} + +static int nau8824_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8824 *nau8824 = dev_get_platdata(dev); + int ret, value; + + if (!nau8824) { + nau8824 = devm_kzalloc(dev, sizeof(*nau8824), GFP_KERNEL); + if (!nau8824) + return -ENOMEM; + ret = nau8824_read_device_properties(dev, nau8824); + if (ret) + return ret; + } + i2c_set_clientdata(i2c, nau8824); + + nau8824->regmap = devm_regmap_init_i2c(i2c, &nau8824_regmap_config); + if (IS_ERR(nau8824->regmap)) + return PTR_ERR(nau8824->regmap); + nau8824->dev = dev; + nau8824->irq = i2c->irq; + sema_init(&nau8824->jd_sem, 1); + + nau8824_check_quirks(); + + if (nau8824_quirk & NAU8824_JD_ACTIVE_HIGH) + nau8824->jkdet_polarity = 0; + + nau8824_print_device_properties(nau8824); + + ret = regmap_read(nau8824->regmap, NAU8824_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU8824: %d\n", + ret); + return ret; + } + nau8824_reset_chip(nau8824->regmap); + nau8824_init_regs(nau8824); + + if (i2c->irq) + nau8824_setup_irq(nau8824); + + return devm_snd_soc_register_component(dev, + &nau8824_component_driver, &nau8824_dai, 1); +} + +static const struct i2c_device_id nau8824_i2c_ids[] = { + { "nau8824", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8824_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id nau8824_of_ids[] = { + { .compatible = "nuvoton,nau8824", }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8824_of_ids); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id nau8824_acpi_match[] = { + { "10508824", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, nau8824_acpi_match); +#endif + +static struct i2c_driver nau8824_i2c_driver = { + .driver = { + .name = "nau8824", + .of_match_table = of_match_ptr(nau8824_of_ids), + .acpi_match_table = ACPI_PTR(nau8824_acpi_match), + }, + .probe = nau8824_i2c_probe, + .id_table = nau8824_i2c_ids, +}; +module_i2c_driver(nau8824_i2c_driver); + + +MODULE_DESCRIPTION("ASoC NAU88L24 driver"); +MODULE_AUTHOR("John Hsu "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/nau8824.h b/sound/soc/codecs/nau8824.h new file mode 100644 index 000000000..6184a2b5c --- /dev/null +++ b/sound/soc/codecs/nau8824.h @@ -0,0 +1,478 @@ +/* + * NAU88L24 ALSA SoC audio driver + * + * Copyright 2016 Nuvoton Technology Corp. + * Author: John Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __NAU8824_H__ +#define __NAU8824_H__ + +#define NAU8824_REG_RESET 0x00 +#define NAU8824_REG_ENA_CTRL 0x01 +#define NAU8824_REG_CLK_GATING_ENA 0x02 +#define NAU8824_REG_CLK_DIVIDER 0x03 +#define NAU8824_REG_FLL1 0x04 +#define NAU8824_REG_FLL2 0x05 +#define NAU8824_REG_FLL3 0x06 +#define NAU8824_REG_FLL4 0x07 +#define NAU8824_REG_FLL5 0x08 +#define NAU8824_REG_FLL6 0x09 +#define NAU8824_REG_FLL_VCO_RSV 0x0A +#define NAU8824_REG_JACK_DET_CTRL 0x0D +#define NAU8824_REG_INTERRUPT_SETTING_1 0x0F +#define NAU8824_REG_IRQ 0x10 +#define NAU8824_REG_CLEAR_INT_REG 0x11 +#define NAU8824_REG_INTERRUPT_SETTING 0x12 +#define NAU8824_REG_SAR_ADC 0x13 +#define NAU8824_REG_VDET_COEFFICIENT 0x14 +#define NAU8824_REG_VDET_THRESHOLD_1 0x15 +#define NAU8824_REG_VDET_THRESHOLD_2 0x16 +#define NAU8824_REG_VDET_THRESHOLD_3 0x17 +#define NAU8824_REG_VDET_THRESHOLD_4 0x18 +#define NAU8824_REG_GPIO_SEL 0x1A +#define NAU8824_REG_PORT0_I2S_PCM_CTRL_1 0x1C +#define NAU8824_REG_PORT0_I2S_PCM_CTRL_2 0x1D +#define NAU8824_REG_PORT0_LEFT_TIME_SLOT 0x1E +#define NAU8824_REG_PORT0_RIGHT_TIME_SLOT 0x1F +#define NAU8824_REG_TDM_CTRL 0x20 +#define NAU8824_REG_ADC_HPF_FILTER 0x23 +#define NAU8824_REG_ADC_FILTER_CTRL 0x24 +#define NAU8824_REG_DAC_FILTER_CTRL_1 0x25 +#define NAU8824_REG_DAC_FILTER_CTRL_2 0x26 +#define NAU8824_REG_NOTCH_FILTER_1 0x27 +#define NAU8824_REG_NOTCH_FILTER_2 0x28 +#define NAU8824_REG_EQ1_LOW 0x29 +#define NAU8824_REG_EQ2_EQ3 0x2A +#define NAU8824_REG_EQ4_EQ5 0x2B +#define NAU8824_REG_ADC_CH0_DGAIN_CTRL 0x2D +#define NAU8824_REG_ADC_CH1_DGAIN_CTRL 0x2E +#define NAU8824_REG_ADC_CH2_DGAIN_CTRL 0x2F +#define NAU8824_REG_ADC_CH3_DGAIN_CTRL 0x30 +#define NAU8824_REG_DAC_MUTE_CTRL 0x31 +#define NAU8824_REG_DAC_CH0_DGAIN_CTRL 0x32 +#define NAU8824_REG_DAC_CH1_DGAIN_CTRL 0x33 +#define NAU8824_REG_ADC_TO_DAC_ST 0x34 +#define NAU8824_REG_DRC_KNEE_IP12_ADC_CH01 0x38 +#define NAU8824_REG_DRC_KNEE_IP34_ADC_CH01 0x39 +#define NAU8824_REG_DRC_SLOPE_ADC_CH01 0x3A +#define NAU8824_REG_DRC_ATKDCY_ADC_CH01 0x3B +#define NAU8824_REG_DRC_KNEE_IP12_ADC_CH23 0x3C +#define NAU8824_REG_DRC_KNEE_IP34_ADC_CH23 0x3D +#define NAU8824_REG_DRC_SLOPE_ADC_CH23 0x3E +#define NAU8824_REG_DRC_ATKDCY_ADC_CH23 0x3F +#define NAU8824_REG_DRC_GAINL_ADC0 0x40 +#define NAU8824_REG_DRC_GAINL_ADC1 0x41 +#define NAU8824_REG_DRC_GAINL_ADC2 0x42 +#define NAU8824_REG_DRC_GAINL_ADC3 0x43 +#define NAU8824_REG_DRC_KNEE_IP12_DAC 0x45 +#define NAU8824_REG_DRC_KNEE_IP34_DAC 0x46 +#define NAU8824_REG_DRC_SLOPE_DAC 0x47 +#define NAU8824_REG_DRC_ATKDCY_DAC 0x48 +#define NAU8824_REG_DRC_GAIN_DAC_CH0 0x49 +#define NAU8824_REG_DRC_GAIN_DAC_CH1 0x4A +#define NAU8824_REG_MODE 0x4C +#define NAU8824_REG_MODE1 0x4D +#define NAU8824_REG_MODE2 0x4E +#define NAU8824_REG_CLASSG 0x50 +#define NAU8824_REG_OTP_EFUSE 0x51 +#define NAU8824_REG_OTPDOUT_1 0x53 +#define NAU8824_REG_OTPDOUT_2 0x54 +#define NAU8824_REG_MISC_CTRL 0x55 +#define NAU8824_REG_I2C_TIMEOUT 0x56 +#define NAU8824_REG_TEST_MODE 0x57 +#define NAU8824_REG_I2C_DEVICE_ID 0x58 +#define NAU8824_REG_SAR_ADC_DATA_OUT 0x59 +#define NAU8824_REG_BIAS_ADJ 0x66 +#define NAU8824_REG_PGA_GAIN 0x67 +#define NAU8824_REG_TRIM_SETTINGS 0x68 +#define NAU8824_REG_ANALOG_CONTROL_1 0x69 +#define NAU8824_REG_ANALOG_CONTROL_2 0x6A +#define NAU8824_REG_ENABLE_LO 0x6B +#define NAU8824_REG_GAIN_LO 0x6C +#define NAU8824_REG_CLASSD_GAIN_1 0x6D +#define NAU8824_REG_CLASSD_GAIN_2 0x6E +#define NAU8824_REG_ANALOG_ADC_1 0x71 +#define NAU8824_REG_ANALOG_ADC_2 0x72 +#define NAU8824_REG_RDAC 0x73 +#define NAU8824_REG_MIC_BIAS 0x74 +#define NAU8824_REG_HS_VOLUME_CONTROL 0x75 +#define NAU8824_REG_BOOST 0x76 +#define NAU8824_REG_FEPGA 0x77 +#define NAU8824_REG_FEPGA_II 0x78 +#define NAU8824_REG_FEPGA_SE 0x79 +#define NAU8824_REG_FEPGA_ATTENUATION 0x7A +#define NAU8824_REG_ATT_PORT0 0x7B +#define NAU8824_REG_ATT_PORT1 0x7C +#define NAU8824_REG_POWER_UP_CONTROL 0x7F +#define NAU8824_REG_CHARGE_PUMP_CONTROL 0x80 +#define NAU8824_REG_CHARGE_PUMP_INPUT 0x81 +#define NAU8824_REG_MAX NAU8824_REG_CHARGE_PUMP_INPUT +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8824_REG_ADDR_LEN 16 +#define NAU8824_REG_DATA_LEN 16 + + +/* ENA_CTRL (0x1) */ +#define NAU8824_DMIC_LCH_EDGE_CH23 (0x1 << 12) +#define NAU8824_DMIC_LCH_EDGE_CH01 (0x1 << 11) +#define NAU8824_JD_SLEEP_MODE (0x1 << 10) +#define NAU8824_ADC_CH3_DMIC_SFT 9 +#define NAU8824_ADC_CH3_DMIC_EN (0x1 << NAU8824_ADC_CH3_DMIC_SFT) +#define NAU8824_ADC_CH2_DMIC_SFT 8 +#define NAU8824_ADC_CH2_DMIC_EN (0x1 << NAU8824_ADC_CH2_DMIC_SFT) +#define NAU8824_ADC_CH1_DMIC_SFT 7 +#define NAU8824_ADC_CH1_DMIC_EN (0x1 << NAU8824_ADC_CH1_DMIC_SFT) +#define NAU8824_ADC_CH0_DMIC_SFT 6 +#define NAU8824_ADC_CH0_DMIC_EN (0x1 << NAU8824_ADC_CH0_DMIC_SFT) +#define NAU8824_DAC_CH1_EN (0x1 << 5) +#define NAU8824_DAC_CH0_EN (0x1 << 4) +#define NAU8824_ADC_CH3_EN (0x1 << 3) +#define NAU8824_ADC_CH2_EN (0x1 << 2) +#define NAU8824_ADC_CH1_EN (0x1 << 1) +#define NAU8824_ADC_CH0_EN 0x1 + +/* CLK_GATING_ENA (0x02) */ +#define NAU8824_CLK_ADC_CH23_EN (0x1 << 15) +#define NAU8824_CLK_ADC_CH01_EN (0x1 << 14) +#define NAU8824_CLK_DAC_CH1_EN (0x1 << 13) +#define NAU8824_CLK_DAC_CH0_EN (0x1 << 12) +#define NAU8824_CLK_I2S_EN (0x1 << 7) +#define NAU8824_CLK_GAIN_EN (0x1 << 5) +#define NAU8824_CLK_SAR_EN (0x1 << 3) +#define NAU8824_CLK_DMIC_CH23_EN (0x1 << 1) + +/* CLK_DIVIDER (0x3) */ +#define NAU8824_CLK_SRC_SFT 15 +#define NAU8824_CLK_SRC_MASK (1 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_SRC_VCO (1 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_SRC_MCLK (0 << NAU8824_CLK_SRC_SFT) +#define NAU8824_CLK_MCLK_SRC_MASK (0xf << 0) +#define NAU8824_CLK_DMIC_SRC_SFT 10 +#define NAU8824_CLK_DMIC_SRC_MASK (0x7 << NAU8824_CLK_DMIC_SRC_SFT) +#define NAU8824_CLK_ADC_SRC_SFT 6 +#define NAU8824_CLK_ADC_SRC_MASK (0x3 << NAU8824_CLK_ADC_SRC_SFT) +#define NAU8824_CLK_DAC_SRC_SFT 4 +#define NAU8824_CLK_DAC_SRC_MASK (0x3 << NAU8824_CLK_DAC_SRC_SFT) + +/* FLL1 (0x04) */ +#define NAU8824_FLL_RATIO_MASK (0x7f << 0) + +/* FLL3 (0x06) */ +#define NAU8824_FLL_INTEGER_MASK (0x3ff << 0) +#define NAU8824_FLL_CLK_SRC_SFT 10 +#define NAU8824_FLL_CLK_SRC_MASK (0x3 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_MCLK (0 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_BLK (0x2 << NAU8824_FLL_CLK_SRC_SFT) +#define NAU8824_FLL_CLK_SRC_FS (0x3 << NAU8824_FLL_CLK_SRC_SFT) + +/* FLL4 (0x07) */ +#define NAU8824_FLL_REF_DIV_SFT 10 +#define NAU8824_FLL_REF_DIV_MASK (0x3 << NAU8824_FLL_REF_DIV_SFT) + +/* FLL5 (0x08) */ +#define NAU8824_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8824_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8824_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8824_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8824_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8824_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8824_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8824_FLL_FTR_SW_FILTER (0x0 << 12) + +/* FLL6 (0x9) */ +#define NAU8824_DCO_EN (0x1 << 15) +#define NAU8824_SDM_EN (0x1 << 14) + +/* IRQ (0x10) */ +#define NAU8824_SHORT_CIRCUIT_IRQ (0x1 << 7) +#define NAU8824_IMPEDANCE_MEAS_IRQ (0x1 << 6) +#define NAU8824_KEY_RELEASE_IRQ (0x1 << 5) +#define NAU8824_KEY_LONG_PRESS_IRQ (0x1 << 4) +#define NAU8824_KEY_SHORT_PRESS_IRQ (0x1 << 3) +#define NAU8824_JACK_EJECTION_DETECTED (0x1 << 1) +#define NAU8824_JACK_INSERTION_DETECTED 0x1 + +/* JACK_DET_CTRL (0x0D) */ +#define NAU8824_JACK_EJECT_DT_SFT 2 +#define NAU8824_JACK_EJECT_DT_MASK (0x3 << NAU8824_JACK_EJECT_DT_SFT) +#define NAU8824_JACK_LOGIC 0x1 + + +/* INTERRUPT_SETTING_1 (0x0F) */ +#define NAU8824_IRQ_EJECT_EN (0x1 << 9) +#define NAU8824_IRQ_INSERT_EN (0x1 << 8) + +/* INTERRUPT_SETTING (0x12) */ +#define NAU8824_IRQ_KEY_RELEASE_DIS (0x1 << 5) +#define NAU8824_IRQ_KEY_SHORT_PRESS_DIS (0x1 << 3) +#define NAU8824_IRQ_EJECT_DIS (0x1 << 1) +#define NAU8824_IRQ_INSERT_DIS 0x1 + +/* SAR_ADC (0x13) */ +#define NAU8824_SAR_ADC_EN_SFT 12 +#define NAU8824_SAR_TRACKING_GAIN_SFT 8 +#define NAU8824_SAR_TRACKING_GAIN_MASK (0x7 << NAU8824_SAR_TRACKING_GAIN_SFT) +#define NAU8824_SAR_COMPARE_TIME_SFT 2 +#define NAU8824_SAR_COMPARE_TIME_MASK (3 << 2) +#define NAU8824_SAR_SAMPLING_TIME_SFT 0 +#define NAU8824_SAR_SAMPLING_TIME_MASK (3 << 0) + +/* VDET_COEFFICIENT (0x14) */ +#define NAU8824_SHORTKEY_DEBOUNCE_SFT 12 +#define NAU8824_SHORTKEY_DEBOUNCE_MASK (0x3 << NAU8824_SHORTKEY_DEBOUNCE_SFT) +#define NAU8824_LEVELS_NR_SFT 8 +#define NAU8824_LEVELS_NR_MASK (0x7 << 8) +#define NAU8824_HYSTERESIS_SFT 0 +#define NAU8824_HYSTERESIS_MASK 0xf + +/* PORT0_I2S_PCM_CTRL_1 (0x1C) */ +#define NAU8824_I2S_BP_SFT 7 +#define NAU8824_I2S_BP_MASK (1 << NAU8824_I2S_BP_SFT) +#define NAU8824_I2S_BP_INV (1 << NAU8824_I2S_BP_SFT) +#define NAU8824_I2S_PCMB_SFT 6 +#define NAU8824_I2S_PCMB_EN (1 << NAU8824_I2S_PCMB_SFT) +#define NAU8824_I2S_DL_SFT 2 +#define NAU8824_I2S_DL_MASK (0x3 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_16 (0 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_20 (1 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_24 (2 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DL_32 (3 << NAU8824_I2S_DL_SFT) +#define NAU8824_I2S_DF_MASK 0x3 +#define NAU8824_I2S_DF_RIGTH 0 +#define NAU8824_I2S_DF_LEFT 1 +#define NAU8824_I2S_DF_I2S 2 +#define NAU8824_I2S_DF_PCM_AB 3 + + +/* PORT0_I2S_PCM_CTRL_2 (0x1D) */ +#define NAU8824_I2S_LRC_DIV_SFT 12 +#define NAU8824_I2S_LRC_DIV_MASK (0x3 << NAU8824_I2S_LRC_DIV_SFT) +#define NAU8824_I2S_MS_SFT 3 +#define NAU8824_I2S_MS_MASK (1 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_MS_MASTER (1 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_MS_SLAVE (0 << NAU8824_I2S_MS_SFT) +#define NAU8824_I2S_BLK_DIV_MASK 0x7 + +/* PORT0_LEFT_TIME_SLOT (0x1E) */ +#define NAU8824_TSLOT_L_MASK 0x3ff + +/* TDM_CTRL (0x20) */ +#define NAU8824_TDM_MODE (0x1 << 15) +#define NAU8824_TDM_OFFSET_EN (0x1 << 14) +#define NAU8824_TDM_DACL_RX_SFT 6 +#define NAU8824_TDM_DACL_RX_MASK (0x3 << NAU8824_TDM_DACL_RX_SFT) +#define NAU8824_TDM_DACR_RX_SFT 4 +#define NAU8824_TDM_DACR_RX_MASK (0x3 << NAU8824_TDM_DACR_RX_SFT) +#define NAU8824_TDM_TX_MASK 0xf + +/* ADC_FILTER_CTRL (0x24) */ +#define NAU8824_ADC_SYNC_DOWN_MASK 0x3 +#define NAU8824_ADC_SYNC_DOWN_32 0 +#define NAU8824_ADC_SYNC_DOWN_64 1 +#define NAU8824_ADC_SYNC_DOWN_128 2 +#define NAU8824_ADC_SYNC_DOWN_256 3 + +/* DAC_FILTER_CTRL_1 (0x25) */ +#define NAU8824_DAC_CICCLP_OFF (0x1 << 7) +#define NAU8824_DAC_OVERSAMPLE_MASK 0x7 +#define NAU8824_DAC_OVERSAMPLE_64 0 +#define NAU8824_DAC_OVERSAMPLE_256 1 +#define NAU8824_DAC_OVERSAMPLE_128 2 +#define NAU8824_DAC_OVERSAMPLE_32 4 + +/* DAC_MUTE_CTRL (0x31) */ +#define NAU8824_DAC_CH01_MIX 0x3 +#define NAU8824_DAC_ZC_EN (0x1 << 11) + +/* DAC_CH0_DGAIN_CTRL (0x32) */ +#define NAU8824_DAC_CH0_SEL_SFT 9 +#define NAU8824_DAC_CH0_SEL_MASK (0x1 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_SEL_I2S0 (0x0 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_SEL_I2S1 (0x1 << NAU8824_DAC_CH0_SEL_SFT) +#define NAU8824_DAC_CH0_VOL_MASK 0x1ff + +/* DAC_CH1_DGAIN_CTRL (0x33) */ +#define NAU8824_DAC_CH1_SEL_SFT 9 +#define NAU8824_DAC_CH1_SEL_MASK (0x1 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_SEL_I2S0 (0x0 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_SEL_I2S1 (0x1 << NAU8824_DAC_CH1_SEL_SFT) +#define NAU8824_DAC_CH1_VOL_MASK 0x1ff + +/* CLASSG (0x50) */ +#define NAU8824_CLASSG_TIMER_SFT 8 +#define NAU8824_CLASSG_TIMER_MASK (0x3f << NAU8824_CLASSG_TIMER_SFT) +#define NAU8824_CLASSG_LDAC_EN_SFT 2 +#define NAU8824_CLASSG_RDAC_EN_SFT 1 +#define NAU8824_CLASSG_EN_SFT 0 + +/* SAR_ADC_DATA_OUT (0x59) */ +#define NAU8824_SAR_ADC_DATA_MASK 0xff + +/* BIAS_ADJ (0x66) */ +#define NAU8824_VMID (1 << 6) +#define NAU8824_VMID_SEL_SFT 4 +#define NAU8824_VMID_SEL_MASK (3 << NAU8824_VMID_SEL_SFT) +#define NAU8824_DMIC2_EN_SFT 3 +#define NAU8824_DMIC1_EN_SFT 2 + +/* TRIM_SETTINGS (0x68) */ +#define NAU8824_DRV_CURR_INC (1 << 15) + +/* ANALOG_CONTROL_1 (0x69) */ +#define NAU8824_DMIC_CLK_DRV_STRG (1 << 3) +#define NAU8824_DMIC_CLK_SLEW_FAST (0x7) + +/* ANALOG_CONTROL_2 (0x6A) */ +#define NAU8824_CLASSD_CLAMP_DIS_SFT 3 +#define NAU8824_CLASSD_CLAMP_DIS (0x1 << NAU8824_CLASSD_CLAMP_DIS_SFT) + +/* ENABLE_LO (0x6B) */ +#define NAU8824_TEST_DAC_SFT 14 +#define NAU8824_TEST_DAC_EN (0x3 << NAU8824_TEST_DAC_SFT) +#define NAU8824_DACL_HPR_EN_SFT 3 +#define NAU8824_DACL_HPR_EN (0x1 << NAU8824_DACL_HPR_EN_SFT) +#define NAU8824_DACR_HPR_EN_SFT 2 +#define NAU8824_DACR_HPR_EN (0x1 << NAU8824_DACR_HPR_EN_SFT) +#define NAU8824_DACR_HPL_EN_SFT 1 +#define NAU8824_DACR_HPL_EN (0x1 << NAU8824_DACR_HPL_EN_SFT) +#define NAU8824_DACL_HPL_EN_SFT 0 +#define NAU8824_DACL_HPL_EN 0x1 + +/* CLASSD_GAIN_1 (0x6D) */ +#define NAU8824_CLASSD_GAIN_1R_SFT 8 +#define NAU8824_CLASSD_GAIN_1R_MASK (0x1f << NAU8824_CLASSD_GAIN_1R_SFT) +#define NAU8824_CLASSD_EN_SFT 7 +#define NAU8824_CLASSD_EN (0x1 << NAU8824_CLASSD_EN_SFT) +#define NAU8824_CLASSD_GAIN_1L_MASK 0x1f + +/* CLASSD_GAIN_2 (0x6E) */ +#define NAU8824_CLASSD_GAIN_2R_SFT 8 +#define NAU8824_CLASSD_GAIN_2R_MASK (0x1f << NAU8824_CLASSD_GAIN_1R_SFT) +#define NAU8824_CLASSD_EN_SFT 7 +#define NAU8824_CLASSD_EN (0x1 << NAU8824_CLASSD_EN_SFT) +#define NAU8824_CLASSD_GAIN_2L_MASK 0x1f + +/* ANALOG_ADC_2 (0x72) */ +#define NAU8824_ADCR_EN_SFT 7 +#define NAU8824_ADCL_EN_SFT 6 + +/* RDAC (0x73) */ +#define NAU8824_DACR_EN_SFT 13 +#define NAU8824_DACL_EN_SFT 12 +#define NAU8824_DACR_CLK_SFT 9 +#define NAU8824_DACL_CLK_SFT 8 +#define NAU8824_RDAC_CLK_DELAY_SFT 4 +#define NAU8824_RDAC_CLK_DELAY_MASK (0x7 << NAU8824_RDAC_CLK_DELAY_SFT) +#define NAU8824_RDAC_VREF_SFT 2 +#define NAU8824_RDAC_VREF_MASK (0x3 << NAU8824_RDAC_VREF_SFT) + +/* MIC_BIAS (0x74) */ +#define NAU8824_MICBIAS_JKSLV (1 << 14) +#define NAU8824_MICBIAS_JKR2 (1 << 12) +#define NAU8824_MICBIAS_POWERUP_SFT 8 +#define NAU8824_MICBIAS_VOLTAGE_SFT 0 +#define NAU8824_MICBIAS_VOLTAGE_MASK 0x7 + +/* BOOST (0x76) */ +#define NAU8824_PRECHARGE_DIS (0x1 << 13) +#define NAU8824_GLOBAL_BIAS_EN (0x1 << 12) +#define NAU8824_HP_BOOST_DIS_SFT 9 +#define NAU8824_HP_BOOST_DIS (0x1 << NAU8824_HP_BOOST_DIS_SFT) +#define NAU8824_HP_BOOST_G_DIS_SFT 8 +#define NAU8824_HP_BOOST_G_DIS (0x1 << NAU8824_HP_BOOST_G_DIS_SFT) +#define NAU8824_SHORT_SHUTDOWN_DIG_EN (1 << 7) +#define NAU8824_SHORT_SHUTDOWN_EN (1 << 6) + +/* FEPGA (0x77) */ +#define NAU8824_FEPGA_MODER_SHORT_SFT 7 +#define NAU8824_FEPGA_MODER_SHORT_EN (0x1 << NAU8824_FEPGA_MODER_SHORT_SFT) +#define NAU8824_FEPGA_MODER_MIC2_SFT 5 +#define NAU8824_FEPGA_MODER_MIC2_EN (0x1 << NAU8824_FEPGA_MODER_MIC2_SFT) +#define NAU8824_FEPGA_MODER_HSMIC_SFT 4 +#define NAU8824_FEPGA_MODER_HSMIC_EN (0x1 << NAU8824_FEPGA_MODER_HSMIC_SFT) +#define NAU8824_FEPGA_MODEL_SHORT_SFT 3 +#define NAU8824_FEPGA_MODEL_SHORT_EN (0x1 << NAU8824_FEPGA_MODEL_SHORT_SFT) +#define NAU8824_FEPGA_MODEL_MIC1_SFT 1 +#define NAU8824_FEPGA_MODEL_MIC1_EN (0x1 << NAU8824_FEPGA_MODEL_MIC1_SFT) +#define NAU8824_FEPGA_MODEL_HSMIC_SFT 0 +#define NAU8824_FEPGA_MODEL_HSMIC_EN (0x1 << NAU8824_FEPGA_MODEL_HSMIC_SFT) + +/* FEPGA_II (0x78) */ +#define NAU8824_FEPGA_GAINR_SFT 5 +#define NAU8824_FEPGA_GAINR_MASK (0x1f << NAU8824_FEPGA_GAINR_SFT) +#define NAU8824_FEPGA_GAINL_SFT 0 +#define NAU8824_FEPGA_GAINL_MASK 0x1f + +/* CHARGE_PUMP_CONTROL (0x80) */ +#define NAU8824_JAMNODCLOW (0x1 << 15) +#define NAU8824_SPKR_PULL_DOWN (0x1 << 13) +#define NAU8824_SPKL_PULL_DOWN (0x1 << 12) +#define NAU8824_POWER_DOWN_DACR (0x1 << 9) +#define NAU8824_POWER_DOWN_DACL (0x1 << 8) +#define NAU8824_CHARGE_PUMP_EN_SFT 5 +#define NAU8824_CHARGE_PUMP_EN (0x1 << NAU8824_CHARGE_PUMP_EN_SFT) + + +#define NAU8824_CODEC_DAI "nau8824-hifi" + +/* System Clock Source */ +enum { + NAU8824_CLK_DIS, + NAU8824_CLK_MCLK, + NAU8824_CLK_INTERNAL, + NAU8824_CLK_FLL_MCLK, + NAU8824_CLK_FLL_BLK, + NAU8824_CLK_FLL_FS, +}; + +struct nau8824 { + struct device *dev; + struct regmap *regmap; + struct snd_soc_dapm_context *dapm; + struct snd_soc_jack *jack; + struct work_struct jdet_work; + struct semaphore jd_sem; + int fs; + int irq; + int micbias_voltage; + int vref_impedance; + int jkdet_polarity; + int sar_threshold_num; + int sar_threshold[8]; + int sar_hysteresis; + int sar_voltage; + int sar_compare_time; + int sar_sampling_time; + int key_debounce; + int jack_eject_debounce; +}; + +struct nau8824_fll { + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; +}; + +struct nau8824_fll_attr { + unsigned int param; + unsigned int val; +}; + +struct nau8824_osr_attr { + unsigned int osr; + unsigned int clk_src; +}; + + +int nau8824_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack); + +#endif /* _NAU8824_H */ + diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c new file mode 100644 index 000000000..b9fed99d8 --- /dev/null +++ b/sound/soc/codecs/nau8825.c @@ -0,0 +1,2667 @@ +/* + * Nuvoton NAU8825 audio codec driver + * + * Copyright 2015 Google Chromium project. + * Author: Anatol Pomozov + * Copyright 2015 Nuvoton Technology Corp. + * Co-author: Meng-Huang Kuo + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +#include "nau8825.h" + + +#define NUVOTON_CODEC_DAI "nau8825-hifi" + +#define NAU_FREF_MAX 13500000 +#define NAU_FVCO_MAX 124000000 +#define NAU_FVCO_MIN 90000000 + +/* cross talk suppression detection */ +#define LOG10_MAGIC 646456993 +#define GAIN_AUGMENT 22500 +#define SIDETONE_BASE 207000 + +/* the maximum frequency of CLK_ADC and CLK_DAC */ +#define CLK_DA_AD_MAX 6144000 + +static int nau8825_configure_sysclk(struct nau8825 *nau8825, + int clk_id, unsigned int freq); + +struct nau8825_fll { + int mclk_src; + int ratio; + int fll_frac; + int fll_int; + int clk_ref_div; +}; + +struct nau8825_fll_attr { + unsigned int param; + unsigned int val; +}; + +/* scaling for mclk from sysclk_src output */ +static const struct nau8825_fll_attr mclk_src_scaling[] = { + { 1, 0x0 }, + { 2, 0x2 }, + { 4, 0x3 }, + { 8, 0x4 }, + { 16, 0x5 }, + { 32, 0x6 }, + { 3, 0x7 }, + { 6, 0xa }, + { 12, 0xb }, + { 24, 0xc }, + { 48, 0xd }, + { 96, 0xe }, + { 5, 0xf }, +}; + +/* ratio for input clk freq */ +static const struct nau8825_fll_attr fll_ratio[] = { + { 512000, 0x01 }, + { 256000, 0x02 }, + { 128000, 0x04 }, + { 64000, 0x08 }, + { 32000, 0x10 }, + { 8000, 0x20 }, + { 4000, 0x40 }, +}; + +static const struct nau8825_fll_attr fll_pre_scalar[] = { + { 1, 0x0 }, + { 2, 0x1 }, + { 4, 0x2 }, + { 8, 0x3 }, +}; + +/* over sampling rate */ +struct nau8825_osr_attr { + unsigned int osr; + unsigned int clk_src; +}; + +static const struct nau8825_osr_attr osr_dac_sel[] = { + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 256, 0 }, /* OSR 256, SRC 1 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 0, 0 }, + { 32, 3 }, /* OSR 32, SRC 1/8 */ +}; + +static const struct nau8825_osr_attr osr_adc_sel[] = { + { 32, 3 }, /* OSR 32, SRC 1/8 */ + { 64, 2 }, /* OSR 64, SRC 1/4 */ + { 128, 1 }, /* OSR 128, SRC 1/2 */ + { 256, 0 }, /* OSR 256, SRC 1 */ +}; + +static const struct reg_default nau8825_reg_defaults[] = { + { NAU8825_REG_ENA_CTRL, 0x00ff }, + { NAU8825_REG_IIC_ADDR_SET, 0x0 }, + { NAU8825_REG_CLK_DIVIDER, 0x0050 }, + { NAU8825_REG_FLL1, 0x0 }, + { NAU8825_REG_FLL2, 0x3126 }, + { NAU8825_REG_FLL3, 0x0008 }, + { NAU8825_REG_FLL4, 0x0010 }, + { NAU8825_REG_FLL5, 0x0 }, + { NAU8825_REG_FLL6, 0x6000 }, + { NAU8825_REG_FLL_VCO_RSV, 0xf13c }, + { NAU8825_REG_HSD_CTRL, 0x000c }, + { NAU8825_REG_JACK_DET_CTRL, 0x0 }, + { NAU8825_REG_INTERRUPT_MASK, 0x0 }, + { NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff }, + { NAU8825_REG_SAR_CTRL, 0x0015 }, + { NAU8825_REG_KEYDET_CTRL, 0x0110 }, + { NAU8825_REG_VDET_THRESHOLD_1, 0x0 }, + { NAU8825_REG_VDET_THRESHOLD_2, 0x0 }, + { NAU8825_REG_VDET_THRESHOLD_3, 0x0 }, + { NAU8825_REG_VDET_THRESHOLD_4, 0x0 }, + { NAU8825_REG_GPIO34_CTRL, 0x0 }, + { NAU8825_REG_GPIO12_CTRL, 0x0 }, + { NAU8825_REG_TDM_CTRL, 0x0 }, + { NAU8825_REG_I2S_PCM_CTRL1, 0x000b }, + { NAU8825_REG_I2S_PCM_CTRL2, 0x8010 }, + { NAU8825_REG_LEFT_TIME_SLOT, 0x0 }, + { NAU8825_REG_RIGHT_TIME_SLOT, 0x0 }, + { NAU8825_REG_BIQ_CTRL, 0x0 }, + { NAU8825_REG_BIQ_COF1, 0x0 }, + { NAU8825_REG_BIQ_COF2, 0x0 }, + { NAU8825_REG_BIQ_COF3, 0x0 }, + { NAU8825_REG_BIQ_COF4, 0x0 }, + { NAU8825_REG_BIQ_COF5, 0x0 }, + { NAU8825_REG_BIQ_COF6, 0x0 }, + { NAU8825_REG_BIQ_COF7, 0x0 }, + { NAU8825_REG_BIQ_COF8, 0x0 }, + { NAU8825_REG_BIQ_COF9, 0x0 }, + { NAU8825_REG_BIQ_COF10, 0x0 }, + { NAU8825_REG_ADC_RATE, 0x0010 }, + { NAU8825_REG_DAC_CTRL1, 0x0001 }, + { NAU8825_REG_DAC_CTRL2, 0x0 }, + { NAU8825_REG_DAC_DGAIN_CTRL, 0x0 }, + { NAU8825_REG_ADC_DGAIN_CTRL, 0x00cf }, + { NAU8825_REG_MUTE_CTRL, 0x0 }, + { NAU8825_REG_HSVOL_CTRL, 0x0 }, + { NAU8825_REG_DACL_CTRL, 0x02cf }, + { NAU8825_REG_DACR_CTRL, 0x00cf }, + { NAU8825_REG_ADC_DRC_KNEE_IP12, 0x1486 }, + { NAU8825_REG_ADC_DRC_KNEE_IP34, 0x0f12 }, + { NAU8825_REG_ADC_DRC_SLOPES, 0x25ff }, + { NAU8825_REG_ADC_DRC_ATKDCY, 0x3457 }, + { NAU8825_REG_DAC_DRC_KNEE_IP12, 0x1486 }, + { NAU8825_REG_DAC_DRC_KNEE_IP34, 0x0f12 }, + { NAU8825_REG_DAC_DRC_SLOPES, 0x25f9 }, + { NAU8825_REG_DAC_DRC_ATKDCY, 0x3457 }, + { NAU8825_REG_IMM_MODE_CTRL, 0x0 }, + { NAU8825_REG_CLASSG_CTRL, 0x0 }, + { NAU8825_REG_OPT_EFUSE_CTRL, 0x0 }, + { NAU8825_REG_MISC_CTRL, 0x0 }, + { NAU8825_REG_BIAS_ADJ, 0x0 }, + { NAU8825_REG_TRIM_SETTINGS, 0x0 }, + { NAU8825_REG_ANALOG_CONTROL_1, 0x0 }, + { NAU8825_REG_ANALOG_CONTROL_2, 0x0 }, + { NAU8825_REG_ANALOG_ADC_1, 0x0011 }, + { NAU8825_REG_ANALOG_ADC_2, 0x0020 }, + { NAU8825_REG_RDAC, 0x0008 }, + { NAU8825_REG_MIC_BIAS, 0x0006 }, + { NAU8825_REG_BOOST, 0x0 }, + { NAU8825_REG_FEPGA, 0x0 }, + { NAU8825_REG_POWER_UP_CONTROL, 0x0 }, + { NAU8825_REG_CHARGE_PUMP, 0x0 }, +}; + +/* register backup table when cross talk detection */ +static struct reg_default nau8825_xtalk_baktab[] = { + { NAU8825_REG_ADC_DGAIN_CTRL, 0x00cf }, + { NAU8825_REG_HSVOL_CTRL, 0 }, + { NAU8825_REG_DACL_CTRL, 0x00cf }, + { NAU8825_REG_DACR_CTRL, 0x02cf }, +}; + +static const unsigned short logtable[256] = { + 0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7, + 0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508, + 0x1664, 0x17bf, 0x1919, 0x1a71, 0x1bc8, 0x1d1e, 0x1e73, 0x1fc6, + 0x2119, 0x226a, 0x23ba, 0x2508, 0x2656, 0x27a2, 0x28ed, 0x2a37, + 0x2b80, 0x2cc8, 0x2e0f, 0x2f54, 0x3098, 0x31dc, 0x331e, 0x345f, + 0x359f, 0x36de, 0x381b, 0x3958, 0x3a94, 0x3bce, 0x3d08, 0x3e41, + 0x3f78, 0x40af, 0x41e4, 0x4319, 0x444c, 0x457f, 0x46b0, 0x47e1, + 0x4910, 0x4a3f, 0x4b6c, 0x4c99, 0x4dc5, 0x4eef, 0x5019, 0x5142, + 0x526a, 0x5391, 0x54b7, 0x55dc, 0x5700, 0x5824, 0x5946, 0x5a68, + 0x5b89, 0x5ca8, 0x5dc7, 0x5ee5, 0x6003, 0x611f, 0x623a, 0x6355, + 0x646f, 0x6588, 0x66a0, 0x67b7, 0x68ce, 0x69e4, 0x6af8, 0x6c0c, + 0x6d20, 0x6e32, 0x6f44, 0x7055, 0x7165, 0x7274, 0x7383, 0x7490, + 0x759d, 0x76aa, 0x77b5, 0x78c0, 0x79ca, 0x7ad3, 0x7bdb, 0x7ce3, + 0x7dea, 0x7ef0, 0x7ff6, 0x80fb, 0x81ff, 0x8302, 0x8405, 0x8507, + 0x8608, 0x8709, 0x8809, 0x8908, 0x8a06, 0x8b04, 0x8c01, 0x8cfe, + 0x8dfa, 0x8ef5, 0x8fef, 0x90e9, 0x91e2, 0x92db, 0x93d2, 0x94ca, + 0x95c0, 0x96b6, 0x97ab, 0x98a0, 0x9994, 0x9a87, 0x9b7a, 0x9c6c, + 0x9d5e, 0x9e4f, 0x9f3f, 0xa02e, 0xa11e, 0xa20c, 0xa2fa, 0xa3e7, + 0xa4d4, 0xa5c0, 0xa6ab, 0xa796, 0xa881, 0xa96a, 0xaa53, 0xab3c, + 0xac24, 0xad0c, 0xadf2, 0xaed9, 0xafbe, 0xb0a4, 0xb188, 0xb26c, + 0xb350, 0xb433, 0xb515, 0xb5f7, 0xb6d9, 0xb7ba, 0xb89a, 0xb97a, + 0xba59, 0xbb38, 0xbc16, 0xbcf4, 0xbdd1, 0xbead, 0xbf8a, 0xc065, + 0xc140, 0xc21b, 0xc2f5, 0xc3cf, 0xc4a8, 0xc580, 0xc658, 0xc730, + 0xc807, 0xc8de, 0xc9b4, 0xca8a, 0xcb5f, 0xcc34, 0xcd08, 0xcddc, + 0xceaf, 0xcf82, 0xd054, 0xd126, 0xd1f7, 0xd2c8, 0xd399, 0xd469, + 0xd538, 0xd607, 0xd6d6, 0xd7a4, 0xd872, 0xd93f, 0xda0c, 0xdad9, + 0xdba5, 0xdc70, 0xdd3b, 0xde06, 0xded0, 0xdf9a, 0xe063, 0xe12c, + 0xe1f5, 0xe2bd, 0xe385, 0xe44c, 0xe513, 0xe5d9, 0xe69f, 0xe765, + 0xe82a, 0xe8ef, 0xe9b3, 0xea77, 0xeb3b, 0xebfe, 0xecc1, 0xed83, + 0xee45, 0xef06, 0xefc8, 0xf088, 0xf149, 0xf209, 0xf2c8, 0xf387, + 0xf446, 0xf505, 0xf5c3, 0xf680, 0xf73e, 0xf7fb, 0xf8b7, 0xf973, + 0xfa2f, 0xfaea, 0xfba5, 0xfc60, 0xfd1a, 0xfdd4, 0xfe8e, 0xff47 +}; + +/** + * nau8825_sema_acquire - acquire the semaphore of nau88l25 + * @nau8825: component to register the codec private data with + * @timeout: how long in jiffies to wait before failure or zero to wait + * until release + * + * Attempts to acquire the semaphore with number of jiffies. If no more + * tasks are allowed to acquire the semaphore, calling this function will + * put the task to sleep. If the semaphore is not released within the + * specified number of jiffies, this function returns. + * If the semaphore is not released within the specified number of jiffies, + * this function returns -ETIME. If the sleep is interrupted by a signal, + * this function will return -EINTR. It returns 0 if the semaphore was + * acquired successfully. + * + * Acquires the semaphore without jiffies. Try to acquire the semaphore + * atomically. Returns 0 if the semaphore has been acquired successfully + * or 1 if it it cannot be acquired. + */ +static int nau8825_sema_acquire(struct nau8825 *nau8825, long timeout) +{ + int ret; + + if (timeout) { + ret = down_timeout(&nau8825->xtalk_sem, timeout); + if (ret < 0) + dev_warn(nau8825->dev, "Acquire semaphore timeout\n"); + } else { + ret = down_trylock(&nau8825->xtalk_sem); + if (ret) + dev_warn(nau8825->dev, "Acquire semaphore fail\n"); + } + + return ret; +} + +/** + * nau8825_sema_release - release the semaphore of nau88l25 + * @nau8825: component to register the codec private data with + * + * Release the semaphore which may be called from any context and + * even by tasks which have never called down(). + */ +static inline void nau8825_sema_release(struct nau8825 *nau8825) +{ + up(&nau8825->xtalk_sem); +} + +/** + * nau8825_sema_reset - reset the semaphore for nau88l25 + * @nau8825: component to register the codec private data with + * + * Reset the counter of the semaphore. Call this function to restart + * a new round task management. + */ +static inline void nau8825_sema_reset(struct nau8825 *nau8825) +{ + nau8825->xtalk_sem.count = 1; +} + +/** + * Ramp up the headphone volume change gradually to target level. + * + * @nau8825: component to register the codec private data with + * @vol_from: the volume to start up + * @vol_to: the target volume + * @step: the volume span to move on + * + * The headphone volume is from 0dB to minimum -54dB and -1dB per step. + * If the volume changes sharp, there is a pop noise heard in headphone. We + * provide the function to ramp up the volume up or down by delaying 10ms + * per step. + */ +static void nau8825_hpvol_ramp(struct nau8825 *nau8825, + unsigned int vol_from, unsigned int vol_to, unsigned int step) +{ + unsigned int value, volume, ramp_up, from, to; + + if (vol_from == vol_to || step == 0) { + return; + } else if (vol_from < vol_to) { + ramp_up = true; + from = vol_from; + to = vol_to; + } else { + ramp_up = false; + from = vol_to; + to = vol_from; + } + /* only handle volume from 0dB to minimum -54dB */ + if (to > NAU8825_HP_VOL_MIN) + to = NAU8825_HP_VOL_MIN; + + for (volume = from; volume < to; volume += step) { + if (ramp_up) + value = volume; + else + value = to - volume + from; + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL, + NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK, + (value << NAU8825_HPL_VOL_SFT) | value); + usleep_range(10000, 10500); + } + if (ramp_up) + value = to; + else + value = from; + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL, + NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK, + (value << NAU8825_HPL_VOL_SFT) | value); +} + +/** + * Computes log10 of a value; the result is round off to 3 decimal. This func- + * tion takes reference to dvb-math. The source code locates as the following. + * Linux/drivers/media/dvb-core/dvb_math.c + * + * return log10(value) * 1000 + */ +static u32 nau8825_intlog10_dec3(u32 value) +{ + u32 msb, logentry, significand, interpolation, log10val; + u64 log2val; + + /* first detect the msb (count begins at 0) */ + msb = fls(value) - 1; + /** + * now we use a logtable after the following method: + * + * log2(2^x * y) * 2^24 = x * 2^24 + log2(y) * 2^24 + * where x = msb and therefore 1 <= y < 2 + * first y is determined by shifting the value left + * so that msb is bit 31 + * 0x00231f56 -> 0x8C7D5800 + * the result is y * 2^31 -> "significand" + * then the highest 9 bits are used for a table lookup + * the highest bit is discarded because it's always set + * the highest nine bits in our example are 100011000 + * so we would use the entry 0x18 + */ + significand = value << (31 - msb); + logentry = (significand >> 23) & 0xff; + /** + * last step we do is interpolation because of the + * limitations of the log table the error is that part of + * the significand which isn't used for lookup then we + * compute the ratio between the error and the next table entry + * and interpolate it between the log table entry used and the + * next one the biggest error possible is 0x7fffff + * (in our example it's 0x7D5800) + * needed value for next table entry is 0x800000 + * so the interpolation is + * (error / 0x800000) * (logtable_next - logtable_current) + * in the implementation the division is moved to the end for + * better accuracy there is also an overflow correction if + * logtable_next is 256 + */ + interpolation = ((significand & 0x7fffff) * + ((logtable[(logentry + 1) & 0xff] - + logtable[logentry]) & 0xffff)) >> 15; + + log2val = ((msb << 24) + (logtable[logentry] << 8) + interpolation); + /** + * log10(x) = log2(x) * log10(2) + */ + log10val = (log2val * LOG10_MAGIC) >> 31; + /** + * the result is round off to 3 decimal + */ + return log10val / ((1 << 24) / 1000); +} + +/** + * computes cross talk suppression sidetone gain. + * + * @sig_org: orignal signal level + * @sig_cros: cross talk signal level + * + * The orignal and cross talk signal vlues need to be characterized. + * Once these values have been characterized, this sidetone value + * can be converted to decibel with the equation below. + * sidetone = 20 * log (original signal level / crosstalk signal level) + * + * return cross talk sidetone gain + */ +static u32 nau8825_xtalk_sidetone(u32 sig_org, u32 sig_cros) +{ + u32 gain, sidetone; + + if (unlikely(sig_org == 0) || unlikely(sig_cros == 0)) { + WARN_ON(1); + return 0; + } + + sig_org = nau8825_intlog10_dec3(sig_org); + sig_cros = nau8825_intlog10_dec3(sig_cros); + if (sig_org >= sig_cros) + gain = (sig_org - sig_cros) * 20 + GAIN_AUGMENT; + else + gain = (sig_cros - sig_org) * 20 + GAIN_AUGMENT; + sidetone = SIDETONE_BASE - gain * 2; + sidetone /= 1000; + + return sidetone; +} + +static int nau8825_xtalk_baktab_index_by_reg(unsigned int reg) +{ + int index; + + for (index = 0; index < ARRAY_SIZE(nau8825_xtalk_baktab); index++) + if (nau8825_xtalk_baktab[index].reg == reg) + return index; + return -EINVAL; +} + +static void nau8825_xtalk_backup(struct nau8825 *nau8825) +{ + int i; + + if (nau8825->xtalk_baktab_initialized) + return; + + /* Backup some register values to backup table */ + for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) + regmap_read(nau8825->regmap, nau8825_xtalk_baktab[i].reg, + &nau8825_xtalk_baktab[i].def); + + nau8825->xtalk_baktab_initialized = true; +} + +static void nau8825_xtalk_restore(struct nau8825 *nau8825, bool cause_cancel) +{ + int i, volume; + + if (!nau8825->xtalk_baktab_initialized) + return; + + /* Restore register values from backup table; When the driver restores + * the headphone volume in XTALK_DONE state, it needs recover to + * original level gradually with 3dB per step for less pop noise. + * Otherwise, the restore should do ASAP. + */ + for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) { + if (!cause_cancel && nau8825_xtalk_baktab[i].reg == + NAU8825_REG_HSVOL_CTRL) { + /* Ramping up the volume change to reduce pop noise */ + volume = nau8825_xtalk_baktab[i].def & + NAU8825_HPR_VOL_MASK; + nau8825_hpvol_ramp(nau8825, 0, volume, 3); + continue; + } + regmap_write(nau8825->regmap, nau8825_xtalk_baktab[i].reg, + nau8825_xtalk_baktab[i].def); + } + + nau8825->xtalk_baktab_initialized = false; +} + +static void nau8825_xtalk_prepare_dac(struct nau8825 *nau8825) +{ + /* Enable power of DAC path */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL | + NAU8825_ENABLE_ADC | NAU8825_ENABLE_ADC_CLK | + NAU8825_ENABLE_DAC_CLK, NAU8825_ENABLE_DACR | + NAU8825_ENABLE_DACL | NAU8825_ENABLE_ADC | + NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK); + /* Prevent startup click by letting charge pump to ramp up and + * change bump enable + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN); + /* Enable clock sync of DAC and DAC clock */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN | + NAU8825_RDAC_FS_BCLK_ENB, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN); + /* Power up output driver with 2 stage */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L); + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L); + /* HP outputs not shouted to ground */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, 0); + /* Enable HP boost driver */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_HP_BOOST_DIS, NAU8825_HP_BOOST_DIS); + /* Enable class G compare path to supply 1.8V or 0.9V. */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLASSG_CTRL, + NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN, + NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN); +} + +static void nau8825_xtalk_prepare_adc(struct nau8825 *nau8825) +{ + /* Power up left ADC and raise 5dB than Vmid for Vref */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB); +} + +static void nau8825_xtalk_clock(struct nau8825 *nau8825) +{ + /* Recover FLL default value */ + regmap_write(nau8825->regmap, NAU8825_REG_FLL1, 0x0); + regmap_write(nau8825->regmap, NAU8825_REG_FLL2, 0x3126); + regmap_write(nau8825->regmap, NAU8825_REG_FLL3, 0x0008); + regmap_write(nau8825->regmap, NAU8825_REG_FLL4, 0x0010); + regmap_write(nau8825->regmap, NAU8825_REG_FLL5, 0x0); + regmap_write(nau8825->regmap, NAU8825_REG_FLL6, 0x6000); + /* Enable internal VCO clock for detection signal generated */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, + NAU8825_DCO_EN); + /* Given specific clock frequency of internal clock to + * generate signal. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_MCLK_SRC_MASK, 0xf); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1, + NAU8825_FLL_RATIO_MASK, 0x10); +} + +static void nau8825_xtalk_prepare(struct nau8825 *nau8825) +{ + int volume, index; + + /* Backup those registers changed by cross talk detection */ + nau8825_xtalk_backup(nau8825); + /* Config IIS as master to output signal by codec */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK | NAU8825_I2S_LRC_DIV_MASK | + NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_MASTER | + (0x2 << NAU8825_I2S_LRC_DIV_SFT) | 0x1); + /* Ramp up headphone volume to 0dB to get better performance and + * avoid pop noise in headphone. + */ + index = nau8825_xtalk_baktab_index_by_reg(NAU8825_REG_HSVOL_CTRL); + if (index != -EINVAL) { + volume = nau8825_xtalk_baktab[index].def & + NAU8825_HPR_VOL_MASK; + nau8825_hpvol_ramp(nau8825, volume, 0, 3); + } + nau8825_xtalk_clock(nau8825); + nau8825_xtalk_prepare_dac(nau8825); + nau8825_xtalk_prepare_adc(nau8825); + /* Config channel path and digital gain */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_MASK | NAU8825_DACL_CH_VOL_MASK, + NAU8825_DACL_CH_SEL_L | 0xab); + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL, + NAU8825_DACR_CH_SEL_MASK | NAU8825_DACR_CH_VOL_MASK, + NAU8825_DACR_CH_SEL_R | 0xab); + /* Config cross talk parameters and generate the 23Hz sine wave with + * 1/16 full scale of signal level for impedance measurement. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, + NAU8825_IMM_THD_MASK | NAU8825_IMM_GEN_VOL_MASK | + NAU8825_IMM_CYC_MASK | NAU8825_IMM_DAC_SRC_MASK, + (0x9 << NAU8825_IMM_THD_SFT) | NAU8825_IMM_GEN_VOL_1_16th | + NAU8825_IMM_CYC_8192 | NAU8825_IMM_DAC_SRC_SIN); + /* RMS intrruption enable */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_INTERRUPT_MASK, NAU8825_IRQ_RMS_EN, 0); + /* Power up left and right DAC */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); +} + +static void nau8825_xtalk_clean_dac(struct nau8825 *nau8825) +{ + /* Disable HP boost driver */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_HP_BOOST_DIS, 0); + /* HP outputs shouted to ground */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); + /* Power down left and right DAC */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + /* Enable the TESTDAC and disable L/R HP impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP | + NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN); + /* Power down output driver with 2 stage */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, 0); + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, 0); + /* Disable clock sync of DAC and DAC clock */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN, 0); + /* Disable charge pump ramp up function and change bump */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, 0); + /* Disable power of DAC path */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL | + NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK, 0); + if (!nau8825->irq) + regmap_update_bits(nau8825->regmap, + NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0); +} + +static void nau8825_xtalk_clean_adc(struct nau8825 *nau8825) +{ + /* Power down left ADC and restore voltage to Vmid */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, 0); +} + +static void nau8825_xtalk_clean(struct nau8825 *nau8825, bool cause_cancel) +{ + /* Enable internal VCO needed for interruptions */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); + nau8825_xtalk_clean_dac(nau8825); + nau8825_xtalk_clean_adc(nau8825); + /* Clear cross talk parameters and disable */ + regmap_write(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, 0); + /* RMS intrruption disable */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_RMS_EN, NAU8825_IRQ_RMS_EN); + /* Recover default value for IIS */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK | NAU8825_I2S_LRC_DIV_MASK | + NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_SLAVE); + /* Restore value of specific register for cross talk */ + nau8825_xtalk_restore(nau8825, cause_cancel); +} + +static void nau8825_xtalk_imm_start(struct nau8825 *nau8825, int vol) +{ + /* Apply ADC volume for better cross talk performance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ADC_DGAIN_CTRL, + NAU8825_ADC_DIG_VOL_MASK, vol); + /* Disables JKTIP(HPL) DAC channel for right to left measurement. + * Do it before sending signal in order to erase pop noise. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_TESTDACR_EN | NAU8825_BIAS_TESTDACL_EN, + NAU8825_BIAS_TESTDACL_EN); + switch (nau8825->xtalk_state) { + case NAU8825_XTALK_HPR_R2L: + /* Enable right headphone impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP, + NAU8825_BIAS_HPR_IMP); + break; + case NAU8825_XTALK_HPL_R2L: + /* Enable left headphone impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP, + NAU8825_BIAS_HPL_IMP); + break; + default: + break; + } + msleep(100); + /* Impedance measurement mode enable */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, + NAU8825_IMM_EN, NAU8825_IMM_EN); +} + +static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825) +{ + /* Impedance measurement mode disable */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_IMM_MODE_CTRL, NAU8825_IMM_EN, 0); +} + +/* The cross talk measurement function can reduce cross talk across the + * JKTIP(HPL) and JKR1(HPR) outputs which measures the cross talk signal + * level to determine what cross talk reduction gain is. This system works by + * sending a 23Hz -24dBV sine wave into the headset output DAC and through + * the PGA. The output of the PGA is then connected to an internal current + * sense which measures the attenuated 23Hz signal and passing the output to + * an ADC which converts the measurement to a binary code. With two separated + * measurement, one for JKR1(HPR) and the other JKTIP(HPL), measurement data + * can be separated read in IMM_RMS_L for HSR and HSL after each measurement. + * Thus, the measurement function has four states to complete whole sequence. + * 1. Prepare state : Prepare the resource for detection and transfer to HPR + * IMM stat to make JKR1(HPR) impedance measure. + * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer + * to HPL IMM state to make JKTIP(HPL) impedance measure. + * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and + * transfer to IMM state to determine suppression sidetone gain. + * 4. IMM state : Computes cross talk suppression sidetone gain with orignal + * and cross talk signal level. Apply this gain and then restore codec + * configuration. Then transfer to Done state for ending. + */ +static void nau8825_xtalk_measure(struct nau8825 *nau8825) +{ + u32 sidetone; + + switch (nau8825->xtalk_state) { + case NAU8825_XTALK_PREPARE: + /* In prepare state, set up clock, intrruption, DAC path, ADC + * path and cross talk detection parameters for preparation. + */ + nau8825_xtalk_prepare(nau8825); + msleep(280); + /* Trigger right headphone impedance detection */ + nau8825->xtalk_state = NAU8825_XTALK_HPR_R2L; + nau8825_xtalk_imm_start(nau8825, 0x00d2); + break; + case NAU8825_XTALK_HPR_R2L: + /* In right headphone IMM state, read out right headphone + * impedance measure result, and then start up left side. + */ + regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L, + &nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]); + dev_dbg(nau8825->dev, "HPR_R2L imm: %x\n", + nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]); + /* Disable then re-enable IMM mode to update */ + nau8825_xtalk_imm_stop(nau8825); + /* Trigger left headphone impedance detection */ + nau8825->xtalk_state = NAU8825_XTALK_HPL_R2L; + nau8825_xtalk_imm_start(nau8825, 0x00ff); + break; + case NAU8825_XTALK_HPL_R2L: + /* In left headphone IMM state, read out left headphone + * impedance measure result, and delay some time to wait + * detection sine wave output finish. Then, we can calculate + * the cross talk suppresstion side tone according to the L/R + * headphone imedance. + */ + regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L, + &nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + dev_dbg(nau8825->dev, "HPL_R2L imm: %x\n", + nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + nau8825_xtalk_imm_stop(nau8825); + msleep(150); + nau8825->xtalk_state = NAU8825_XTALK_IMM; + break; + case NAU8825_XTALK_IMM: + /* In impedance measure state, the orignal and cross talk + * signal level vlues are ready. The side tone gain is deter- + * mined with these signal level. After all, restore codec + * configuration. + */ + sidetone = nau8825_xtalk_sidetone( + nau8825->imp_rms[NAU8825_XTALK_HPR_R2L], + nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + dev_dbg(nau8825->dev, "cross talk sidetone: %x\n", sidetone); + regmap_write(nau8825->regmap, NAU8825_REG_DAC_DGAIN_CTRL, + (sidetone << 8) | sidetone); + nau8825_xtalk_clean(nau8825, false); + nau8825->xtalk_state = NAU8825_XTALK_DONE; + break; + default: + break; + } +} + +static void nau8825_xtalk_work(struct work_struct *work) +{ + struct nau8825 *nau8825 = container_of( + work, struct nau8825, xtalk_work); + + nau8825_xtalk_measure(nau8825); + /* To determine the cross talk side tone gain when reach + * the impedance measure state. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_IMM) + nau8825_xtalk_measure(nau8825); + + /* Delay jack report until cross talk detection process + * completed. It can avoid application to do playback + * preparation before cross talk detection is still working. + * Meanwhile, the protection of the cross talk detection + * is released. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_DONE) { + snd_soc_jack_report(nau8825->jack, nau8825->xtalk_event, + nau8825->xtalk_event_mask); + nau8825_sema_release(nau8825); + nau8825->xtalk_protect = false; + } +} + +static void nau8825_xtalk_cancel(struct nau8825 *nau8825) +{ + /* If the crosstalk is eanbled and the process is on going, + * the driver forces to cancel the crosstalk task and + * restores the configuration to original status. + */ + if (nau8825->xtalk_enable && nau8825->xtalk_state != + NAU8825_XTALK_DONE) { + cancel_work_sync(&nau8825->xtalk_work); + nau8825_xtalk_clean(nau8825, true); + } + /* Reset parameters for cross talk suppression function */ + nau8825_sema_reset(nau8825); + nau8825->xtalk_state = NAU8825_XTALK_DONE; + nau8825->xtalk_protect = false; +} + +static bool nau8825_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8825_REG_ENA_CTRL ... NAU8825_REG_FLL_VCO_RSV: + case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL: + case NAU8825_REG_INTERRUPT_MASK ... NAU8825_REG_KEYDET_CTRL: + case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL: + case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY: + case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY: + case NAU8825_REG_IMM_MODE_CTRL ... NAU8825_REG_IMM_RMS_R: + case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: + case NAU8825_REG_MISC_CTRL: + case NAU8825_REG_I2C_DEVICE_ID ... NAU8825_REG_SARDOUT_RAM_STATUS: + case NAU8825_REG_BIAS_ADJ: + case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: + case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: + case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA: + case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_GENERAL_STATUS: + return true; + default: + return false; + } + +} + +static bool nau8825_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8825_REG_RESET ... NAU8825_REG_FLL_VCO_RSV: + case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL: + case NAU8825_REG_INTERRUPT_MASK: + case NAU8825_REG_INT_CLR_KEY_STATUS ... NAU8825_REG_KEYDET_CTRL: + case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL: + case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY: + case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY: + case NAU8825_REG_IMM_MODE_CTRL: + case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: + case NAU8825_REG_MISC_CTRL: + case NAU8825_REG_BIAS_ADJ: + case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: + case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: + case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA: + case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_CHARGE_PUMP: + return true; + default: + return false; + } +} + +static bool nau8825_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8825_REG_RESET: + case NAU8825_REG_IRQ_STATUS: + case NAU8825_REG_INT_CLR_KEY_STATUS: + case NAU8825_REG_IMM_RMS_L: + case NAU8825_REG_IMM_RMS_R: + case NAU8825_REG_I2C_DEVICE_ID: + case NAU8825_REG_SARDOUT_RAM_STATUS: + case NAU8825_REG_CHARGE_PUMP_INPUT_READ: + case NAU8825_REG_GENERAL_STATUS: + case NAU8825_REG_BIQ_CTRL ... NAU8825_REG_BIQ_COF10: + return true; + default: + return false; + } +} + +static int nau8825_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(125); + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC); + break; + case SND_SOC_DAPM_POST_PMD: + if (!nau8825->irq) + regmap_update_bits(nau8825->regmap, + NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8825_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Prevent startup click by letting charge pump to ramp up */ + msleep(10); + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW, NAU8825_JAMNODCLOW); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8825_output_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Disables the TESTDAC to let DAC signal pass through. */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_TESTDAC_EN, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int nau8825_biq_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + + if (!component->regmap) + return -EINVAL; + + regmap_raw_read(component->regmap, NAU8825_REG_BIQ_COF1, + ucontrol->value.bytes.data, params->max); + return 0; +} + +static int nau8825_biq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + void *data; + + if (!component->regmap) + return -EINVAL; + + data = kmemdup(ucontrol->value.bytes.data, + params->max, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL, + NAU8825_BIQ_WRT_EN, 0); + regmap_raw_write(component->regmap, NAU8825_REG_BIQ_COF1, + data, params->max); + regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL, + NAU8825_BIQ_WRT_EN, NAU8825_BIQ_WRT_EN); + + kfree(data); + return 0; +} + +static const char * const nau8825_biq_path[] = { + "ADC", "DAC" +}; + +static const struct soc_enum nau8825_biq_path_enum = + SOC_ENUM_SINGLE(NAU8825_REG_BIQ_CTRL, NAU8825_BIQ_PATH_SFT, + ARRAY_SIZE(nau8825_biq_path), nau8825_biq_path); + +static const char * const nau8825_adc_decimation[] = { + "32", "64", "128", "256" +}; + +static const struct soc_enum nau8825_adc_decimation_enum = + SOC_ENUM_SINGLE(NAU8825_REG_ADC_RATE, NAU8825_ADC_SYNC_DOWN_SFT, + ARRAY_SIZE(nau8825_adc_decimation), nau8825_adc_decimation); + +static const char * const nau8825_dac_oversampl[] = { + "64", "256", "128", "", "32" +}; + +static const struct soc_enum nau8825_dac_oversampl_enum = + SOC_ENUM_SINGLE(NAU8825_REG_DAC_CTRL1, NAU8825_DAC_OVERSAMPLE_SFT, + ARRAY_SIZE(nau8825_dac_oversampl), nau8825_dac_oversampl); + +static const DECLARE_TLV_DB_MINMAX_MUTE(adc_vol_tlv, -10300, 2400); +static const DECLARE_TLV_DB_MINMAX_MUTE(sidetone_vol_tlv, -4200, 0); +static const DECLARE_TLV_DB_MINMAX(dac_vol_tlv, -5400, 0); +static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600); +static const DECLARE_TLV_DB_MINMAX_MUTE(crosstalk_vol_tlv, -9600, 2400); + +static const struct snd_kcontrol_new nau8825_controls[] = { + SOC_SINGLE_TLV("Mic Volume", NAU8825_REG_ADC_DGAIN_CTRL, + 0, 0xff, 0, adc_vol_tlv), + SOC_DOUBLE_TLV("Headphone Bypass Volume", NAU8825_REG_ADC_DGAIN_CTRL, + 12, 8, 0x0f, 0, sidetone_vol_tlv), + SOC_DOUBLE_TLV("Headphone Volume", NAU8825_REG_HSVOL_CTRL, + 6, 0, 0x3f, 1, dac_vol_tlv), + SOC_SINGLE_TLV("Frontend PGA Volume", NAU8825_REG_POWER_UP_CONTROL, + 8, 37, 0, fepga_gain_tlv), + SOC_DOUBLE_TLV("Headphone Crosstalk Volume", NAU8825_REG_DAC_DGAIN_CTRL, + 0, 8, 0xff, 0, crosstalk_vol_tlv), + + SOC_ENUM("ADC Decimation Rate", nau8825_adc_decimation_enum), + SOC_ENUM("DAC Oversampling Rate", nau8825_dac_oversampl_enum), + /* programmable biquad filter */ + SOC_ENUM("BIQ Path Select", nau8825_biq_path_enum), + SND_SOC_BYTES_EXT("BIQ Coefficients", 20, + nau8825_biq_coeff_get, nau8825_biq_coeff_put), +}; + +/* DAC Mux 0x33[9] and 0x34[9] */ +static const char * const nau8825_dac_src[] = { + "DACL", "DACR", +}; + +static SOC_ENUM_SINGLE_DECL( + nau8825_dacl_enum, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_SFT, nau8825_dac_src); + +static SOC_ENUM_SINGLE_DECL( + nau8825_dacr_enum, NAU8825_REG_DACR_CTRL, + NAU8825_DACR_CH_SEL_SFT, nau8825_dac_src); + +static const struct snd_kcontrol_new nau8825_dacl_mux = + SOC_DAPM_ENUM("DACL Source", nau8825_dacl_enum); + +static const struct snd_kcontrol_new nau8825_dacr_mux = + SOC_DAPM_ENUM("DACR Source", nau8825_dacr_enum); + + +static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = { + SND_SOC_DAPM_AIF_OUT("AIFTX", "Capture", 0, NAU8825_REG_I2S_PCM_CTRL2, + 15, 1), + + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_MICBIAS("MICBIAS", NAU8825_REG_MIC_BIAS, 8, 0), + + SND_SOC_DAPM_PGA("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0, + NULL, 0), + + SND_SOC_DAPM_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0, + nau8825_adc_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("ADC Clock", NAU8825_REG_ENA_CTRL, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Power", NAU8825_REG_ANALOG_ADC_2, 6, 0, NULL, + 0), + + /* ADC for button press detection. A dapm supply widget is used to + * prevent dapm_power_widgets keeping the codec at SND_SOC_BIAS_ON + * during suspend. + */ + SND_SOC_DAPM_SUPPLY("SAR", NAU8825_REG_SAR_CTRL, + NAU8825_SAR_ADC_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_PGA_S("ADACL", 2, NAU8825_REG_RDAC, 12, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADACR", 2, NAU8825_REG_RDAC, 13, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADACL Clock", 3, NAU8825_REG_RDAC, 8, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("ADACR Clock", 3, NAU8825_REG_RDAC, 9, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DDACR", NULL, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR_SFT, 0), + SND_SOC_DAPM_DAC("DDACL", NULL, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACL_SFT, 0), + SND_SOC_DAPM_SUPPLY("DDAC Clock", NAU8825_REG_ENA_CTRL, 6, 0, NULL, 0), + + SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &nau8825_dacl_mux), + SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &nau8825_dacr_mux), + + SND_SOC_DAPM_PGA_S("HP amp L", 0, + NAU8825_REG_CLASSG_CTRL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("HP amp R", 0, + NAU8825_REG_CLASSG_CTRL, 2, 0, NULL, 0), + + SND_SOC_DAPM_PGA_S("Charge Pump", 1, NAU8825_REG_CHARGE_PUMP, 5, 0, + nau8825_pump_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA_S("Output Driver R Stage 1", 4, + NAU8825_REG_POWER_UP_CONTROL, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver L Stage 1", 4, + NAU8825_REG_POWER_UP_CONTROL, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver R Stage 2", 5, + NAU8825_REG_POWER_UP_CONTROL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver L Stage 2", 5, + NAU8825_REG_POWER_UP_CONTROL, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver R Stage 3", 6, + NAU8825_REG_POWER_UP_CONTROL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Output Driver L Stage 3", 6, + NAU8825_REG_POWER_UP_CONTROL, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA_S("Output DACL", 7, + NAU8825_REG_CHARGE_PUMP, 8, 1, nau8825_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("Output DACR", 7, + NAU8825_REG_CHARGE_PUMP, 9, 1, nau8825_output_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* HPOL/R are ungrounded by disabling 16 Ohm pull-downs on playback */ + SND_SOC_DAPM_PGA_S("HPOL Pulldown", 8, + NAU8825_REG_HSD_CTRL, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA_S("HPOR Pulldown", 8, + NAU8825_REG_HSD_CTRL, 1, 1, NULL, 0), + + /* High current HPOL/R boost driver */ + SND_SOC_DAPM_PGA_S("HP Boost Driver", 9, + NAU8825_REG_BOOST, 9, 1, NULL, 0), + + /* Class G operation control*/ + SND_SOC_DAPM_PGA_S("Class G", 10, + NAU8825_REG_CLASSG_CTRL, 0, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_route nau8825_dapm_routes[] = { + {"Frontend PGA", NULL, "MIC"}, + {"ADC", NULL, "Frontend PGA"}, + {"ADC", NULL, "ADC Clock"}, + {"ADC", NULL, "ADC Power"}, + {"AIFTX", NULL, "ADC"}, + + {"DDACL", NULL, "Playback"}, + {"DDACR", NULL, "Playback"}, + {"DDACL", NULL, "DDAC Clock"}, + {"DDACR", NULL, "DDAC Clock"}, + {"DACL Mux", "DACL", "DDACL"}, + {"DACL Mux", "DACR", "DDACR"}, + {"DACR Mux", "DACL", "DDACL"}, + {"DACR Mux", "DACR", "DDACR"}, + {"HP amp L", NULL, "DACL Mux"}, + {"HP amp R", NULL, "DACR Mux"}, + {"Charge Pump", NULL, "HP amp L"}, + {"Charge Pump", NULL, "HP amp R"}, + {"ADACL", NULL, "Charge Pump"}, + {"ADACR", NULL, "Charge Pump"}, + {"ADACL Clock", NULL, "ADACL"}, + {"ADACR Clock", NULL, "ADACR"}, + {"Output Driver L Stage 1", NULL, "ADACL Clock"}, + {"Output Driver R Stage 1", NULL, "ADACR Clock"}, + {"Output Driver L Stage 2", NULL, "Output Driver L Stage 1"}, + {"Output Driver R Stage 2", NULL, "Output Driver R Stage 1"}, + {"Output Driver L Stage 3", NULL, "Output Driver L Stage 2"}, + {"Output Driver R Stage 3", NULL, "Output Driver R Stage 2"}, + {"Output DACL", NULL, "Output Driver L Stage 3"}, + {"Output DACR", NULL, "Output Driver R Stage 3"}, + {"HPOL Pulldown", NULL, "Output DACL"}, + {"HPOR Pulldown", NULL, "Output DACR"}, + {"HP Boost Driver", NULL, "HPOL Pulldown"}, + {"HP Boost Driver", NULL, "HPOR Pulldown"}, + {"Class G", NULL, "HP Boost Driver"}, + {"HPOL", NULL, "Class G"}, + {"HPOR", NULL, "Class G"}, +}; + +static int nau8825_clock_check(struct nau8825 *nau8825, + int stream, int rate, int osr) +{ + int osrate; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (osr >= ARRAY_SIZE(osr_dac_sel)) + return -EINVAL; + osrate = osr_dac_sel[osr].osr; + } else { + if (osr >= ARRAY_SIZE(osr_adc_sel)) + return -EINVAL; + osrate = osr_adc_sel[osr].osr; + } + + if (!osrate || rate * osr > CLK_DA_AD_MAX) { + dev_err(nau8825->dev, "exceed the maximum frequency of CLK_ADC or CLK_DAC\n"); + return -EINVAL; + } + + return 0; +} + +static int nau8825_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, osr, ctrl_val, bclk_fs, bclk_div; + + nau8825_sema_acquire(nau8825, 3 * HZ); + + /* CLK_DAC or CLK_ADC = OSR * FS + * DAC or ADC clock frequency is defined as Over Sampling Rate (OSR) + * multiplied by the audio sample rate (Fs). Note that the OSR and Fs + * values must be selected such that the maximum frequency is less + * than 6.144 MHz. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_read(nau8825->regmap, NAU8825_REG_DAC_CTRL1, &osr); + osr &= NAU8825_DAC_OVERSAMPLE_MASK; + if (nau8825_clock_check(nau8825, substream->stream, + params_rate(params), osr)) { + nau8825_sema_release(nau8825); + return -EINVAL; + } + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_DAC_SRC_MASK, + osr_dac_sel[osr].clk_src << NAU8825_CLK_DAC_SRC_SFT); + } else { + regmap_read(nau8825->regmap, NAU8825_REG_ADC_RATE, &osr); + osr &= NAU8825_ADC_SYNC_DOWN_MASK; + if (nau8825_clock_check(nau8825, substream->stream, + params_rate(params), osr)) { + nau8825_sema_release(nau8825); + return -EINVAL; + } + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_ADC_SRC_MASK, + osr_adc_sel[osr].clk_src << NAU8825_CLK_ADC_SRC_SFT); + } + + /* make BCLK and LRC divde configuration if the codec as master. */ + regmap_read(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, &ctrl_val); + if (ctrl_val & NAU8825_I2S_MS_MASTER) { + /* get the bclk and fs ratio */ + bclk_fs = snd_soc_params_to_bclk(params) / params_rate(params); + if (bclk_fs <= 32) + bclk_div = 2; + else if (bclk_fs <= 64) + bclk_div = 1; + else if (bclk_fs <= 128) + bclk_div = 0; + else { + nau8825_sema_release(nau8825); + return -EINVAL; + } + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_LRC_DIV_MASK | NAU8825_I2S_BLK_DIV_MASK, + ((bclk_div + 1) << NAU8825_I2S_LRC_DIV_SFT) | bclk_div); + } + + switch (params_width(params)) { + case 16: + val_len |= NAU8825_I2S_DL_16; + break; + case 20: + val_len |= NAU8825_I2S_DL_20; + break; + case 24: + val_len |= NAU8825_I2S_DL_24; + break; + case 32: + val_len |= NAU8825_I2S_DL_32; + break; + default: + nau8825_sema_release(nau8825); + return -EINVAL; + } + + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL1, + NAU8825_I2S_DL_MASK, val_len); + + /* Release the semaphore. */ + nau8825_sema_release(nau8825); + + return 0; +} + +static int nau8825_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + unsigned int ctrl1_val = 0, ctrl2_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ctrl2_val |= NAU8825_I2S_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1_val |= NAU8825_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1_val |= NAU8825_I2S_DF_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1_val |= NAU8825_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1_val |= NAU8825_I2S_DF_RIGTH; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1_val |= NAU8825_I2S_DF_PCM_AB; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1_val |= NAU8825_I2S_DF_PCM_AB; + ctrl1_val |= NAU8825_I2S_PCMB_EN; + break; + default: + return -EINVAL; + } + + nau8825_sema_acquire(nau8825, 3 * HZ); + + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL1, + NAU8825_I2S_DL_MASK | NAU8825_I2S_DF_MASK | + NAU8825_I2S_BP_MASK | NAU8825_I2S_PCMB_MASK, + ctrl1_val); + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, ctrl2_val); + + /* Release the semaphore. */ + nau8825_sema_release(nau8825); + + return 0; +} + +static const struct snd_soc_dai_ops nau8825_dai_ops = { + .hw_params = nau8825_hw_params, + .set_fmt = nau8825_set_dai_fmt, +}; + +#define NAU8825_RATES SNDRV_PCM_RATE_8000_192000 +#define NAU8825_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8825_dai = { + .name = "nau8825-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = NAU8825_RATES, + .formats = NAU8825_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = NAU8825_RATES, + .formats = NAU8825_FORMATS, + }, + .ops = &nau8825_dai_ops, +}; + +/** + * nau8825_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int nau8825_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8825->regmap; + + nau8825->jack = jack; + + /* Ground HP Outputs[1:0], needed for headset auto detection + * Enable Automatic Mic/Gnd switching reading on insert interrupt[6] + */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, + NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, + NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); + + return 0; +} +EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect); + + +static bool nau8825_is_jack_inserted(struct regmap *regmap) +{ + bool active_high, is_high; + int status, jkdet; + + regmap_read(regmap, NAU8825_REG_JACK_DET_CTRL, &jkdet); + active_high = jkdet & NAU8825_JACK_POLARITY; + regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status); + is_high = status & NAU8825_GPIO2JD1; + /* return jack connection status according to jack insertion logic + * active high or active low. + */ + return active_high == is_high; +} + +static void nau8825_restart_jack_detection(struct regmap *regmap) +{ + /* this will restart the entire jack detection process including MIC/GND + * switching and create interrupts. We have to go from 0 to 1 and back + * to 0 to restart. + */ + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_RESTART, NAU8825_JACK_DET_RESTART); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_RESTART, 0); +} + +static void nau8825_int_status_clear_all(struct regmap *regmap) +{ + int active_irq, clear_irq, i; + + /* Reset the intrruption status from rightmost bit if the corres- + * ponding irq event occurs. + */ + regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq); + for (i = 0; i < NAU8825_REG_DATA_LEN; i++) { + clear_irq = (0x1 << i); + if (active_irq & clear_irq) + regmap_write(regmap, + NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq); + } +} + +static void nau8825_eject_jack(struct nau8825 *nau8825) +{ + struct snd_soc_dapm_context *dapm = nau8825->dapm; + struct regmap *regmap = nau8825->regmap; + + /* Force to cancel the cross talk detection process */ + nau8825_xtalk_cancel(nau8825); + + snd_soc_dapm_disable_pin(dapm, "SAR"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + /* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */ + regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, 0); + /* ground HPL/HPR, MICGRND1/2 */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0xf, 0xf); + + snd_soc_dapm_sync(dapm); + + /* Clear all interruption status */ + nau8825_int_status_clear_all(regmap); + + /* Enable the insertion interruption, disable the ejection inter- + * ruption, and then bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_EJECT_DIS | NAU8825_IRQ_INSERT_DIS, + NAU8825_IRQ_EJECT_DIS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN | + NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_INSERT_EN, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN | + NAU8825_IRQ_HEADSET_COMPLETE_EN); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS); + + /* Disable ADC needed for interruptions at audo mode */ + regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, 0); + + /* Close clock for jack type detection at manual mode */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0); +} + +/* Enable audo mode interruptions with internal clock. */ +static void nau8825_setup_auto_irq(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + /* Enable headset jack type detection complete interruption and + * jack ejection interruption. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0); + + /* Enable internal VCO needed for interruptions */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); + + /* Enable ADC needed for interruptions */ + regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC); + + /* Chip needs one FSCLK cycle in order to generate interruptions, + * as we cannot guarantee one will be provided by the system. Turning + * master mode on then off enables us to generate that FSCLK cycle + * with a minimum of contention on the clock bus. + */ + regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER); + regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE); + + /* Not bypass de-bounce circuit */ + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, 0); + + /* Unmask all interruptions */ + regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0); + + /* Restart the jack detection process at auto mode */ + nau8825_restart_jack_detection(regmap); +} + +static int nau8825_button_decode(int value) +{ + int buttons = 0; + + /* The chip supports up to 8 buttons, but ALSA defines only 6 buttons */ + if (value & BIT(0)) + buttons |= SND_JACK_BTN_0; + if (value & BIT(1)) + buttons |= SND_JACK_BTN_1; + if (value & BIT(2)) + buttons |= SND_JACK_BTN_2; + if (value & BIT(3)) + buttons |= SND_JACK_BTN_3; + if (value & BIT(4)) + buttons |= SND_JACK_BTN_4; + if (value & BIT(5)) + buttons |= SND_JACK_BTN_5; + + return buttons; +} + +static int nau8825_jack_insert(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + struct snd_soc_dapm_context *dapm = nau8825->dapm; + int jack_status_reg, mic_detected; + int type = 0; + + regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg); + mic_detected = (jack_status_reg >> 10) & 3; + /* The JKSLV and JKR2 all detected in high impedance headset */ + if (mic_detected == 0x3) + nau8825->high_imped = true; + else + nau8825->high_imped = false; + + switch (mic_detected) { + case 0: + /* no mic */ + type = SND_JACK_HEADPHONE; + break; + case 1: + dev_dbg(nau8825->dev, "OMTP (micgnd1) mic connected\n"); + type = SND_JACK_HEADSET; + + /* Unground MICGND1 */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 3 << 2, + 1 << 2); + /* Attach 2kOhm Resistor from MICBIAS to MICGND1 */ + regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, + NAU8825_MICBIAS_JKR2); + /* Attach SARADC to MICGND1 */ + regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_INPUT_MASK, + NAU8825_SAR_INPUT_JKR2); + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_force_enable_pin(dapm, "SAR"); + snd_soc_dapm_sync(dapm); + break; + case 2: + dev_dbg(nau8825->dev, "CTIA (micgnd2) mic connected\n"); + type = SND_JACK_HEADSET; + + /* Unground MICGND2 */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 3 << 2, + 2 << 2); + /* Attach 2kOhm Resistor from MICBIAS to MICGND2 */ + regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, + NAU8825_MICBIAS_JKSLV); + /* Attach SARADC to MICGND2 */ + regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_INPUT_MASK, + NAU8825_SAR_INPUT_JKSLV); + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_force_enable_pin(dapm, "SAR"); + snd_soc_dapm_sync(dapm); + break; + case 3: + /* detect error case */ + dev_err(nau8825->dev, "detection error; disable mic function\n"); + type = SND_JACK_HEADPHONE; + break; + } + + /* Leaving HPOL/R grounded after jack insert by default. They will be + * ungrounded as part of the widget power up sequence at the beginning + * of playback to reduce pop. + */ + return type; +} + +#define NAU8825_BUTTONS (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3) + +static irqreturn_t nau8825_interrupt(int irq, void *data) +{ + struct nau8825 *nau8825 = (struct nau8825 *)data; + struct regmap *regmap = nau8825->regmap; + int active_irq, clear_irq = 0, event = 0, event_mask = 0; + + if (regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq)) { + dev_err(nau8825->dev, "failed to read irq status\n"); + return IRQ_NONE; + } + + if ((active_irq & NAU8825_JACK_EJECTION_IRQ_MASK) == + NAU8825_JACK_EJECTION_DETECTED) { + + nau8825_eject_jack(nau8825); + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8825_JACK_EJECTION_IRQ_MASK; + } else if (active_irq & NAU8825_KEY_SHORT_PRESS_IRQ) { + int key_status; + + regmap_read(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, + &key_status); + + /* upper 8 bits of the register are for short pressed keys, + * lower 8 bits - for long pressed buttons + */ + nau8825->button_pressed = nau8825_button_decode( + key_status >> 8); + + event |= nau8825->button_pressed; + event_mask |= NAU8825_BUTTONS; + clear_irq = NAU8825_KEY_SHORT_PRESS_IRQ; + } else if (active_irq & NAU8825_KEY_RELEASE_IRQ) { + event_mask = NAU8825_BUTTONS; + clear_irq = NAU8825_KEY_RELEASE_IRQ; + } else if (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) { + if (nau8825_is_jack_inserted(regmap)) { + event |= nau8825_jack_insert(nau8825); + if (nau8825->xtalk_enable && !nau8825->high_imped) { + /* Apply the cross talk suppression in the + * headset without high impedance. + */ + if (!nau8825->xtalk_protect) { + /* Raise protection for cross talk de- + * tection if no protection before. + * The driver has to cancel the pro- + * cess and restore changes if process + * is ongoing when ejection. + */ + int ret; + nau8825->xtalk_protect = true; + ret = nau8825_sema_acquire(nau8825, 0); + if (ret) + nau8825->xtalk_protect = false; + } + /* Startup cross talk detection process */ + if (nau8825->xtalk_protect) { + nau8825->xtalk_state = + NAU8825_XTALK_PREPARE; + schedule_work(&nau8825->xtalk_work); + } + } else { + /* The cross talk suppression shouldn't apply + * in the headset with high impedance. Thus, + * relieve the protection raised before. + */ + if (nau8825->xtalk_protect) { + nau8825_sema_release(nau8825); + nau8825->xtalk_protect = false; + } + } + } else { + dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n"); + nau8825_eject_jack(nau8825); + } + + event_mask |= SND_JACK_HEADSET; + clear_irq = NAU8825_HEADSET_COMPLETION_IRQ; + /* Record the interruption report event for driver to report + * the event later. The jack report will delay until cross + * talk detection process is done. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_PREPARE) { + nau8825->xtalk_event = event; + nau8825->xtalk_event_mask = event_mask; + } + } else if (active_irq & NAU8825_IMPEDANCE_MEAS_IRQ) { + /* crosstalk detection enable and process on going */ + if (nau8825->xtalk_enable && nau8825->xtalk_protect) + schedule_work(&nau8825->xtalk_work); + clear_irq = NAU8825_IMPEDANCE_MEAS_IRQ; + } else if ((active_irq & NAU8825_JACK_INSERTION_IRQ_MASK) == + NAU8825_JACK_INSERTION_DETECTED) { + /* One more step to check GPIO status directly. Thus, the + * driver can confirm the real insertion interruption because + * the intrruption at manual mode has bypassed debounce + * circuit which can get rid of unstable status. + */ + if (nau8825_is_jack_inserted(regmap)) { + /* Turn off insertion interruption at manual mode */ + regmap_update_bits(regmap, + NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_INSERT_DIS, + NAU8825_IRQ_INSERT_DIS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_INSERT_EN, NAU8825_IRQ_INSERT_EN); + /* Enable interruption for jack type detection at audo + * mode which can detect microphone and jack type. + */ + nau8825_setup_auto_irq(nau8825); + } + } + + if (!clear_irq) + clear_irq = active_irq; + /* clears the rightmost interruption */ + regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq); + + /* Delay jack report until cross talk detection is done. It can avoid + * application to do playback preparation when cross talk detection + * process is still working. Otherwise, the resource like clock and + * power will be issued by them at the same time and conflict happens. + */ + if (event_mask && nau8825->xtalk_state == NAU8825_XTALK_DONE) + snd_soc_jack_report(nau8825->jack, event, event_mask); + + return IRQ_HANDLED; +} + +static void nau8825_setup_buttons(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_TRACKING_GAIN_MASK, + nau8825->sar_voltage << NAU8825_SAR_TRACKING_GAIN_SFT); + regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_COMPARE_TIME_MASK, + nau8825->sar_compare_time << NAU8825_SAR_COMPARE_TIME_SFT); + regmap_update_bits(regmap, NAU8825_REG_SAR_CTRL, + NAU8825_SAR_SAMPLING_TIME_MASK, + nau8825->sar_sampling_time << NAU8825_SAR_SAMPLING_TIME_SFT); + + regmap_update_bits(regmap, NAU8825_REG_KEYDET_CTRL, + NAU8825_KEYDET_LEVELS_NR_MASK, + (nau8825->sar_threshold_num - 1) << NAU8825_KEYDET_LEVELS_NR_SFT); + regmap_update_bits(regmap, NAU8825_REG_KEYDET_CTRL, + NAU8825_KEYDET_HYSTERESIS_MASK, + nau8825->sar_hysteresis << NAU8825_KEYDET_HYSTERESIS_SFT); + regmap_update_bits(regmap, NAU8825_REG_KEYDET_CTRL, + NAU8825_KEYDET_SHORTKEY_DEBOUNCE_MASK, + nau8825->key_debounce << NAU8825_KEYDET_SHORTKEY_DEBOUNCE_SFT); + + regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_1, + (nau8825->sar_threshold[0] << 8) | nau8825->sar_threshold[1]); + regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_2, + (nau8825->sar_threshold[2] << 8) | nau8825->sar_threshold[3]); + regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_3, + (nau8825->sar_threshold[4] << 8) | nau8825->sar_threshold[5]); + regmap_write(regmap, NAU8825_REG_VDET_THRESHOLD_4, + (nau8825->sar_threshold[6] << 8) | nau8825->sar_threshold[7]); + + /* Enable short press and release interruptions */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_KEY_SHORT_PRESS_EN | NAU8825_IRQ_KEY_RELEASE_EN, + 0); +} + +static void nau8825_init_regs(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + /* Latch IIC LSB value */ + regmap_write(regmap, NAU8825_REG_IIC_ADDR_SET, 0x0001); + /* Enable Bias/Vmid */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_VMID, NAU8825_BIAS_VMID); + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_GLOBAL_BIAS_EN, NAU8825_GLOBAL_BIAS_EN); + + /* VMID Tieoff */ + regmap_update_bits(regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_VMID_SEL_MASK, + nau8825->vref_impedance << NAU8825_BIAS_VMID_SEL_SFT); + /* Disable Boost Driver, Automatic Short circuit protection enable */ + regmap_update_bits(regmap, NAU8825_REG_BOOST, + NAU8825_PRECHARGE_DIS | NAU8825_HP_BOOST_DIS | + NAU8825_HP_BOOST_G_DIS | NAU8825_SHORT_SHUTDOWN_EN, + NAU8825_PRECHARGE_DIS | NAU8825_HP_BOOST_DIS | + NAU8825_HP_BOOST_G_DIS | NAU8825_SHORT_SHUTDOWN_EN); + + regmap_update_bits(regmap, NAU8825_REG_GPIO12_CTRL, + NAU8825_JKDET_OUTPUT_EN, + nau8825->jkdet_enable ? 0 : NAU8825_JKDET_OUTPUT_EN); + regmap_update_bits(regmap, NAU8825_REG_GPIO12_CTRL, + NAU8825_JKDET_PULL_EN, + nau8825->jkdet_pull_enable ? 0 : NAU8825_JKDET_PULL_EN); + regmap_update_bits(regmap, NAU8825_REG_GPIO12_CTRL, + NAU8825_JKDET_PULL_UP, + nau8825->jkdet_pull_up ? NAU8825_JKDET_PULL_UP : 0); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_POLARITY, + /* jkdet_polarity - 1 is for active-low */ + nau8825->jkdet_polarity ? 0 : NAU8825_JACK_POLARITY); + + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_INSERT_DEBOUNCE_MASK, + nau8825->jack_insert_debounce << NAU8825_JACK_INSERT_DEBOUNCE_SFT); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_EJECT_DEBOUNCE_MASK, + nau8825->jack_eject_debounce << NAU8825_JACK_EJECT_DEBOUNCE_SFT); + + /* Mask unneeded IRQs: 1 - disable, 0 - enable */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, 0x7ff, 0x7ff); + + regmap_update_bits(regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_VOLTAGE_MASK, nau8825->micbias_voltage); + + if (nau8825->sar_threshold_num) + nau8825_setup_buttons(nau8825); + + /* Default oversampling/decimations settings are unusable + * (audible hiss). Set it to something better. + */ + regmap_update_bits(regmap, NAU8825_REG_ADC_RATE, + NAU8825_ADC_SYNC_DOWN_MASK | NAU8825_ADC_SINC4_EN, + NAU8825_ADC_SYNC_DOWN_64); + regmap_update_bits(regmap, NAU8825_REG_DAC_CTRL1, + NAU8825_DAC_OVERSAMPLE_MASK, NAU8825_DAC_OVERSAMPLE_64); + /* Disable DACR/L power */ + regmap_update_bits(regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + /* Enable TESTDAC. This sets the analog DAC inputs to a '0' input + * signal to avoid any glitches due to power up transients in both + * the analog and digital DAC circuit. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN); + /* CICCLP off */ + regmap_update_bits(regmap, NAU8825_REG_DAC_CTRL1, + NAU8825_DAC_CLIP_OFF, NAU8825_DAC_CLIP_OFF); + + /* Class AB bias current to 2x, DAC Capacitor enable MSB/LSB */ + regmap_update_bits(regmap, NAU8825_REG_ANALOG_CONTROL_2, + NAU8825_HP_NON_CLASSG_CURRENT_2xADJ | + NAU8825_DAC_CAPACITOR_MSB | NAU8825_DAC_CAPACITOR_LSB, + NAU8825_HP_NON_CLASSG_CURRENT_2xADJ | + NAU8825_DAC_CAPACITOR_MSB | NAU8825_DAC_CAPACITOR_LSB); + /* Class G timer 64ms */ + regmap_update_bits(regmap, NAU8825_REG_CLASSG_CTRL, + NAU8825_CLASSG_TIMER_MASK, + 0x20 << NAU8825_CLASSG_TIMER_SFT); + /* DAC clock delay 2ns, VREF */ + regmap_update_bits(regmap, NAU8825_REG_RDAC, + NAU8825_RDAC_CLK_DELAY_MASK | NAU8825_RDAC_VREF_MASK, + (0x2 << NAU8825_RDAC_CLK_DELAY_SFT) | + (0x3 << NAU8825_RDAC_VREF_SFT)); + /* Config L/R channel */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_L); + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL, + NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_R); + /* Disable short Frame Sync detection logic */ + regmap_update_bits(regmap, NAU8825_REG_LEFT_TIME_SLOT, + NAU8825_DIS_FS_SHORT_DET, NAU8825_DIS_FS_SHORT_DET); +} + +static const struct regmap_config nau8825_regmap_config = { + .val_bits = NAU8825_REG_DATA_LEN, + .reg_bits = NAU8825_REG_ADDR_LEN, + + .max_register = NAU8825_REG_MAX, + .readable_reg = nau8825_readable_reg, + .writeable_reg = nau8825_writeable_reg, + .volatile_reg = nau8825_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = nau8825_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8825_reg_defaults), +}; + +static int nau8825_component_probe(struct snd_soc_component *component) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + nau8825->dapm = dapm; + + return 0; +} + +static void nau8825_component_remove(struct snd_soc_component *component) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + + /* Cancel and reset cross tak suppresstion detection funciton */ + nau8825_xtalk_cancel(nau8825); +} + +/** + * nau8825_calc_fll_param - Calculate FLL parameters. + * @fll_in: external clock provided to codec. + * @fs: sampling rate. + * @fll_param: Pointer to structure of FLL parameters. + * + * Calculate FLL parameters to configure codec. + * + * Returns 0 for success or negative error code. + */ +static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs, + struct nau8825_fll *fll_param) +{ + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; + + /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing + * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. + * FREF = freq_in / NAU8825_FLL_REF_DIV_MASK + */ + for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) { + fref = fll_in / fll_pre_scalar[i].param; + if (fref <= NAU_FREF_MAX) + break; + } + if (i == ARRAY_SIZE(fll_pre_scalar)) + return -EINVAL; + fll_param->clk_ref_div = fll_pre_scalar[i].val; + + /* Choose the FLL ratio based on FREF */ + for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { + if (fref >= fll_ratio[i].param) + break; + } + if (i == ARRAY_SIZE(fll_ratio)) + return -EINVAL; + fll_param->ratio = fll_ratio[i].val; + + /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be + * guaranteed across the full range of operation. + * FDCO = freq_out * 2 * mclk_src_scaling + */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); + for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { + fvco = 256ULL * fs * 2 * mclk_src_scaling[i].param; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } + } + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) + return -EINVAL; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; + + /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional + * input based on FDCO, FREF and FLL ratio. + */ + fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> 16) & 0x3FF; + fll_param->fll_frac = fvco & 0xFFFF; + return 0; +} + +static void nau8825_fll_apply(struct nau8825 *nau8825, + struct nau8825_fll *fll_param) +{ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK | NAU8825_CLK_MCLK_SRC_MASK, + NAU8825_CLK_SRC_MCLK | fll_param->mclk_src); + /* Make DSP operate at high speed for better performance. */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1, + NAU8825_FLL_RATIO_MASK | NAU8825_ICTRL_LATCH_MASK, + fll_param->ratio | (0x6 << NAU8825_ICTRL_LATCH_SFT)); + /* FLL 16-bit fractional input */ + regmap_write(nau8825->regmap, NAU8825_REG_FLL2, fll_param->fll_frac); + /* FLL 10-bit integer input */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL3, + NAU8825_FLL_INTEGER_MASK, fll_param->fll_int); + /* FLL pre-scaler */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL4, + NAU8825_FLL_REF_DIV_MASK, + fll_param->clk_ref_div << NAU8825_FLL_REF_DIV_SFT); + /* select divided VCO input */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, + NAU8825_FLL_CLK_SW_MASK, NAU8825_FLL_CLK_SW_REF); + /* Disable free-running mode */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_FLL6, NAU8825_DCO_EN, 0); + if (fll_param->fll_frac) { + /* set FLL loop filter enable and cutoff frequency at 500Khz */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_MASK, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_FILTER); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, + NAU8825_SDM_EN | NAU8825_CUTOFF500, + NAU8825_SDM_EN | NAU8825_CUTOFF500); + } else { + /* disable FLL loop filter and cutoff frequency */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_MASK, NAU8825_FLL_FTR_SW_ACCU); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, + NAU8825_SDM_EN | NAU8825_CUTOFF500, 0); + } +} + +/* freq_out must be 256*Fs in order to achieve the best performance */ +static int nau8825_set_pll(struct snd_soc_component *component, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + struct nau8825_fll fll_param; + int ret, fs; + + fs = freq_out / 256; + ret = nau8825_calc_fll_param(freq_in, fs, &fll_param); + if (ret < 0) { + dev_err(component->dev, "Unsupported input clock %d\n", freq_in); + return ret; + } + dev_dbg(component->dev, "mclk_src=%x ratio=%x fll_frac=%x fll_int=%x clk_ref_div=%x\n", + fll_param.mclk_src, fll_param.ratio, fll_param.fll_frac, + fll_param.fll_int, fll_param.clk_ref_div); + + nau8825_fll_apply(nau8825, &fll_param); + mdelay(2); + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + return 0; +} + +static int nau8825_mclk_prepare(struct nau8825 *nau8825, unsigned int freq) +{ + int ret = 0; + + nau8825->mclk = devm_clk_get(nau8825->dev, "mclk"); + if (IS_ERR(nau8825->mclk)) { + dev_info(nau8825->dev, "No 'mclk' clock found, assume MCLK is managed externally"); + return 0; + } + + if (!nau8825->mclk_freq) { + ret = clk_prepare_enable(nau8825->mclk); + if (ret) { + dev_err(nau8825->dev, "Unable to prepare codec mclk\n"); + return ret; + } + } + + if (nau8825->mclk_freq != freq) { + freq = clk_round_rate(nau8825->mclk, freq); + ret = clk_set_rate(nau8825->mclk, freq); + if (ret) { + dev_err(nau8825->dev, "Unable to set mclk rate\n"); + return ret; + } + nau8825->mclk_freq = freq; + } + + return 0; +} + +static void nau8825_configure_mclk_as_sysclk(struct regmap *regmap) +{ + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_DCO_EN, 0); + /* Make DSP operate as default setting for power saving. */ + regmap_update_bits(regmap, NAU8825_REG_FLL1, + NAU8825_ICTRL_LATCH_MASK, 0); +} + +static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id, + unsigned int freq) +{ + struct regmap *regmap = nau8825->regmap; + int ret; + + switch (clk_id) { + case NAU8825_CLK_DIS: + /* Clock provided externally and disable internal VCO clock */ + nau8825_configure_mclk_as_sysclk(regmap); + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; + } + + break; + case NAU8825_CLK_MCLK: + /* Acquire the semaphore to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 3 * HZ); + nau8825_configure_mclk_as_sysclk(regmap); + /* MCLK not changed by clock tree */ + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_MCLK_SRC_MASK, 0); + /* Release the semaphore. */ + nau8825_sema_release(nau8825); + + ret = nau8825_mclk_prepare(nau8825, freq); + if (ret) + return ret; + + break; + case NAU8825_CLK_INTERNAL: + if (nau8825_is_jack_inserted(nau8825->regmap)) { + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_DCO_EN, NAU8825_DCO_EN); + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + /* Decrease the VCO frequency and make DSP operate + * as default setting for power saving. + */ + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_MCLK_SRC_MASK, 0xf); + regmap_update_bits(regmap, NAU8825_REG_FLL1, + NAU8825_ICTRL_LATCH_MASK | + NAU8825_FLL_RATIO_MASK, 0x10); + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_SDM_EN, NAU8825_SDM_EN); + } else { + /* The clock turns off intentionally for power saving + * when no headset connected. + */ + nau8825_configure_mclk_as_sysclk(regmap); + dev_warn(nau8825->dev, "Disable clock for power saving when no headset connected\n"); + } + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; + } + + break; + case NAU8825_CLK_FLL_MCLK: + /* Acquire the semaphore to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 3 * HZ); + /* Higher FLL reference input frequency can only set lower + * gain error, such as 0000 for input reference from MCLK + * 12.288Mhz. + */ + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK | NAU8825_GAIN_ERR_MASK, + NAU8825_FLL_CLK_SRC_MCLK | 0); + /* Release the semaphore. */ + nau8825_sema_release(nau8825); + + ret = nau8825_mclk_prepare(nau8825, freq); + if (ret) + return ret; + + break; + case NAU8825_CLK_FLL_BLK: + /* Acquire the semaphore to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 3 * HZ); + /* If FLL reference input is from low frequency source, + * higher error gain can apply such as 0xf which has + * the most sensitive gain error correction threshold, + * Therefore, FLL has the most accurate DCO to + * target frequency. + */ + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK | NAU8825_GAIN_ERR_MASK, + NAU8825_FLL_CLK_SRC_BLK | + (0xf << NAU8825_GAIN_ERR_SFT)); + /* Release the semaphore. */ + nau8825_sema_release(nau8825); + + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; + } + + break; + case NAU8825_CLK_FLL_FS: + /* Acquire the semaphore to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 3 * HZ); + /* If FLL reference input is from low frequency source, + * higher error gain can apply such as 0xf which has + * the most sensitive gain error correction threshold, + * Therefore, FLL has the most accurate DCO to + * target frequency. + */ + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK | NAU8825_GAIN_ERR_MASK, + NAU8825_FLL_CLK_SRC_FS | + (0xf << NAU8825_GAIN_ERR_SFT)); + /* Release the semaphore. */ + nau8825_sema_release(nau8825); + + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; + } + + break; + default: + dev_err(nau8825->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + dev_dbg(nau8825->dev, "Sysclk is %dHz and clock id is %d\n", freq, + clk_id); + return 0; +} + +static int nau8825_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + + return nau8825_configure_sysclk(nau8825, clk_id, freq); +} + +static int nau8825_resume_setup(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + /* Close clock when jack type detection at manual mode */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0); + + /* Clear all interruption status */ + nau8825_int_status_clear_all(regmap); + + /* Enable both insertion and ejection interruptions, and then + * bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN | + NAU8825_IRQ_EJECT_EN | NAU8825_IRQ_INSERT_EN, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_INSERT_DIS | NAU8825_IRQ_EJECT_DIS, 0); + + return 0; +} + +static int nau8825_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + if (nau8825->mclk_freq) { + ret = clk_prepare_enable(nau8825->mclk); + if (ret) { + dev_err(nau8825->dev, "Unable to prepare component mclk\n"); + return ret; + } + } + /* Setup codec configuration after resume */ + nau8825_resume_setup(nau8825); + } + break; + + case SND_SOC_BIAS_OFF: + /* Reset the configuration of jack type for detection */ + /* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_MIC_BIAS, + NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, 0); + /* ground HPL/HPR, MICGRND1/2 */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_HSD_CTRL, 0xf, 0xf); + /* Cancel and reset cross talk detection funciton */ + nau8825_xtalk_cancel(nau8825); + /* Turn off all interruptions before system shutdown. Keep the + * interruption quiet before resume setup completes. + */ + regmap_write(nau8825->regmap, + NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff); + /* Disable ADC needed for interruptions at audo mode */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, 0); + if (nau8825->mclk_freq) + clk_disable_unprepare(nau8825->mclk); + break; + } + return 0; +} + +static int __maybe_unused nau8825_suspend(struct snd_soc_component *component) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + + disable_irq(nau8825->irq); + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + /* Power down codec power; don't suppoet button wakeup */ + snd_soc_dapm_disable_pin(nau8825->dapm, "SAR"); + snd_soc_dapm_disable_pin(nau8825->dapm, "MICBIAS"); + snd_soc_dapm_sync(nau8825->dapm); + regcache_cache_only(nau8825->regmap, true); + regcache_mark_dirty(nau8825->regmap); + + return 0; +} + +static int __maybe_unused nau8825_resume(struct snd_soc_component *component) +{ + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + int ret; + + regcache_cache_only(nau8825->regmap, false); + regcache_sync(nau8825->regmap); + nau8825->xtalk_protect = true; + ret = nau8825_sema_acquire(nau8825, 0); + if (ret) + nau8825->xtalk_protect = false; + enable_irq(nau8825->irq); + + return 0; +} + +static const struct snd_soc_component_driver nau8825_component_driver = { + .probe = nau8825_component_probe, + .remove = nau8825_component_remove, + .set_sysclk = nau8825_set_sysclk, + .set_pll = nau8825_set_pll, + .set_bias_level = nau8825_set_bias_level, + .suspend = nau8825_suspend, + .resume = nau8825_resume, + .controls = nau8825_controls, + .num_controls = ARRAY_SIZE(nau8825_controls), + .dapm_widgets = nau8825_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8825_dapm_widgets), + .dapm_routes = nau8825_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8825_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static void nau8825_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8825_REG_RESET, 0x00); + regmap_write(regmap, NAU8825_REG_RESET, 0x00); +} + +static void nau8825_print_device_properties(struct nau8825 *nau8825) +{ + int i; + struct device *dev = nau8825->dev; + + dev_dbg(dev, "jkdet-enable: %d\n", nau8825->jkdet_enable); + dev_dbg(dev, "jkdet-pull-enable: %d\n", nau8825->jkdet_pull_enable); + dev_dbg(dev, "jkdet-pull-up: %d\n", nau8825->jkdet_pull_up); + dev_dbg(dev, "jkdet-polarity: %d\n", nau8825->jkdet_polarity); + dev_dbg(dev, "micbias-voltage: %d\n", nau8825->micbias_voltage); + dev_dbg(dev, "vref-impedance: %d\n", nau8825->vref_impedance); + + dev_dbg(dev, "sar-threshold-num: %d\n", nau8825->sar_threshold_num); + for (i = 0; i < nau8825->sar_threshold_num; i++) + dev_dbg(dev, "sar-threshold[%d]=%d\n", i, + nau8825->sar_threshold[i]); + + dev_dbg(dev, "sar-hysteresis: %d\n", nau8825->sar_hysteresis); + dev_dbg(dev, "sar-voltage: %d\n", nau8825->sar_voltage); + dev_dbg(dev, "sar-compare-time: %d\n", nau8825->sar_compare_time); + dev_dbg(dev, "sar-sampling-time: %d\n", nau8825->sar_sampling_time); + dev_dbg(dev, "short-key-debounce: %d\n", nau8825->key_debounce); + dev_dbg(dev, "jack-insert-debounce: %d\n", + nau8825->jack_insert_debounce); + dev_dbg(dev, "jack-eject-debounce: %d\n", + nau8825->jack_eject_debounce); + dev_dbg(dev, "crosstalk-enable: %d\n", + nau8825->xtalk_enable); +} + +static int nau8825_read_device_properties(struct device *dev, + struct nau8825 *nau8825) { + int ret; + + nau8825->jkdet_enable = device_property_read_bool(dev, + "nuvoton,jkdet-enable"); + nau8825->jkdet_pull_enable = device_property_read_bool(dev, + "nuvoton,jkdet-pull-enable"); + nau8825->jkdet_pull_up = device_property_read_bool(dev, + "nuvoton,jkdet-pull-up"); + ret = device_property_read_u32(dev, "nuvoton,jkdet-polarity", + &nau8825->jkdet_polarity); + if (ret) + nau8825->jkdet_polarity = 1; + ret = device_property_read_u32(dev, "nuvoton,micbias-voltage", + &nau8825->micbias_voltage); + if (ret) + nau8825->micbias_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,vref-impedance", + &nau8825->vref_impedance); + if (ret) + nau8825->vref_impedance = 2; + ret = device_property_read_u32(dev, "nuvoton,sar-threshold-num", + &nau8825->sar_threshold_num); + if (ret) + nau8825->sar_threshold_num = 4; + ret = device_property_read_u32_array(dev, "nuvoton,sar-threshold", + nau8825->sar_threshold, nau8825->sar_threshold_num); + if (ret) { + nau8825->sar_threshold[0] = 0x08; + nau8825->sar_threshold[1] = 0x12; + nau8825->sar_threshold[2] = 0x26; + nau8825->sar_threshold[3] = 0x73; + } + ret = device_property_read_u32(dev, "nuvoton,sar-hysteresis", + &nau8825->sar_hysteresis); + if (ret) + nau8825->sar_hysteresis = 0; + ret = device_property_read_u32(dev, "nuvoton,sar-voltage", + &nau8825->sar_voltage); + if (ret) + nau8825->sar_voltage = 6; + ret = device_property_read_u32(dev, "nuvoton,sar-compare-time", + &nau8825->sar_compare_time); + if (ret) + nau8825->sar_compare_time = 1; + ret = device_property_read_u32(dev, "nuvoton,sar-sampling-time", + &nau8825->sar_sampling_time); + if (ret) + nau8825->sar_sampling_time = 1; + ret = device_property_read_u32(dev, "nuvoton,short-key-debounce", + &nau8825->key_debounce); + if (ret) + nau8825->key_debounce = 3; + ret = device_property_read_u32(dev, "nuvoton,jack-insert-debounce", + &nau8825->jack_insert_debounce); + if (ret) + nau8825->jack_insert_debounce = 7; + ret = device_property_read_u32(dev, "nuvoton,jack-eject-debounce", + &nau8825->jack_eject_debounce); + if (ret) + nau8825->jack_eject_debounce = 0; + nau8825->xtalk_enable = device_property_read_bool(dev, + "nuvoton,crosstalk-enable"); + + nau8825->mclk = devm_clk_get(dev, "mclk"); + if (PTR_ERR(nau8825->mclk) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (PTR_ERR(nau8825->mclk) == -ENOENT) { + /* The MCLK is managed externally or not used at all */ + nau8825->mclk = NULL; + dev_info(dev, "No 'mclk' clock found, assume MCLK is managed externally"); + } else if (IS_ERR(nau8825->mclk)) { + return -EINVAL; + } + + return 0; +} + +static int nau8825_setup_irq(struct nau8825 *nau8825) +{ + int ret; + + ret = devm_request_threaded_irq(nau8825->dev, nau8825->irq, NULL, + nau8825_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "nau8825", nau8825); + + if (ret) { + dev_err(nau8825->dev, "Cannot request irq %d (%d)\n", + nau8825->irq, ret); + return ret; + } + + return 0; +} + +static int nau8825_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct nau8825 *nau8825 = dev_get_platdata(&i2c->dev); + int ret, value; + + if (!nau8825) { + nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL); + if (!nau8825) + return -ENOMEM; + ret = nau8825_read_device_properties(dev, nau8825); + if (ret) + return ret; + } + + i2c_set_clientdata(i2c, nau8825); + + nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config); + if (IS_ERR(nau8825->regmap)) + return PTR_ERR(nau8825->regmap); + nau8825->dev = dev; + nau8825->irq = i2c->irq; + /* Initiate parameters, semaphore and work queue which are needed in + * cross talk suppression measurment function. + */ + nau8825->xtalk_state = NAU8825_XTALK_DONE; + nau8825->xtalk_protect = false; + nau8825->xtalk_baktab_initialized = false; + sema_init(&nau8825->xtalk_sem, 1); + INIT_WORK(&nau8825->xtalk_work, nau8825_xtalk_work); + + nau8825_print_device_properties(nau8825); + + nau8825_reset_chip(nau8825->regmap); + ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value); + if (ret < 0) { + dev_err(dev, "Failed to read device id from the NAU8825: %d\n", + ret); + return ret; + } + if ((value & NAU8825_SOFTWARE_ID_MASK) != + NAU8825_SOFTWARE_ID_NAU8825) { + dev_err(dev, "Not a NAU8825 chip\n"); + return -ENODEV; + } + + nau8825_init_regs(nau8825); + + if (i2c->irq) + nau8825_setup_irq(nau8825); + + return devm_snd_soc_register_component(&i2c->dev, + &nau8825_component_driver, + &nau8825_dai, 1); +} + +static int nau8825_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id nau8825_i2c_ids[] = { + { "nau8825", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau8825_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id nau8825_of_ids[] = { + { .compatible = "nuvoton,nau8825", }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8825_of_ids); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id nau8825_acpi_match[] = { + { "10508825", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, nau8825_acpi_match); +#endif + +static struct i2c_driver nau8825_driver = { + .driver = { + .name = "nau8825", + .of_match_table = of_match_ptr(nau8825_of_ids), + .acpi_match_table = ACPI_PTR(nau8825_acpi_match), + }, + .probe = nau8825_i2c_probe, + .remove = nau8825_i2c_remove, + .id_table = nau8825_i2c_ids, +}; +module_i2c_driver(nau8825_driver); + +MODULE_DESCRIPTION("ASoC nau8825 driver"); +MODULE_AUTHOR("Anatol Pomozov "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h new file mode 100644 index 000000000..f6074c618 --- /dev/null +++ b/sound/soc/codecs/nau8825.h @@ -0,0 +1,487 @@ +/* + * NAU8825 ALSA SoC audio driver + * + * Copyright 2015 Google Inc. + * Author: Anatol Pomozov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __NAU8825_H__ +#define __NAU8825_H__ + +#define NAU8825_REG_RESET 0x00 +#define NAU8825_REG_ENA_CTRL 0x01 +#define NAU8825_REG_IIC_ADDR_SET 0x02 +#define NAU8825_REG_CLK_DIVIDER 0x03 +#define NAU8825_REG_FLL1 0x04 +#define NAU8825_REG_FLL2 0x05 +#define NAU8825_REG_FLL3 0x06 +#define NAU8825_REG_FLL4 0x07 +#define NAU8825_REG_FLL5 0x08 +#define NAU8825_REG_FLL6 0x09 +#define NAU8825_REG_FLL_VCO_RSV 0x0a +#define NAU8825_REG_HSD_CTRL 0x0c +#define NAU8825_REG_JACK_DET_CTRL 0x0d +#define NAU8825_REG_INTERRUPT_MASK 0x0f +#define NAU8825_REG_IRQ_STATUS 0x10 +#define NAU8825_REG_INT_CLR_KEY_STATUS 0x11 +#define NAU8825_REG_INTERRUPT_DIS_CTRL 0x12 +#define NAU8825_REG_SAR_CTRL 0x13 +#define NAU8825_REG_KEYDET_CTRL 0x14 +#define NAU8825_REG_VDET_THRESHOLD_1 0x15 +#define NAU8825_REG_VDET_THRESHOLD_2 0x16 +#define NAU8825_REG_VDET_THRESHOLD_3 0x17 +#define NAU8825_REG_VDET_THRESHOLD_4 0x18 +#define NAU8825_REG_GPIO34_CTRL 0x19 +#define NAU8825_REG_GPIO12_CTRL 0x1a +#define NAU8825_REG_TDM_CTRL 0x1b +#define NAU8825_REG_I2S_PCM_CTRL1 0x1c +#define NAU8825_REG_I2S_PCM_CTRL2 0x1d +#define NAU8825_REG_LEFT_TIME_SLOT 0x1e +#define NAU8825_REG_RIGHT_TIME_SLOT 0x1f +#define NAU8825_REG_BIQ_CTRL 0x20 +#define NAU8825_REG_BIQ_COF1 0x21 +#define NAU8825_REG_BIQ_COF2 0x22 +#define NAU8825_REG_BIQ_COF3 0x23 +#define NAU8825_REG_BIQ_COF4 0x24 +#define NAU8825_REG_BIQ_COF5 0x25 +#define NAU8825_REG_BIQ_COF6 0x26 +#define NAU8825_REG_BIQ_COF7 0x27 +#define NAU8825_REG_BIQ_COF8 0x28 +#define NAU8825_REG_BIQ_COF9 0x29 +#define NAU8825_REG_BIQ_COF10 0x2a +#define NAU8825_REG_ADC_RATE 0x2b +#define NAU8825_REG_DAC_CTRL1 0x2c +#define NAU8825_REG_DAC_CTRL2 0x2d +#define NAU8825_REG_DAC_DGAIN_CTRL 0x2f +#define NAU8825_REG_ADC_DGAIN_CTRL 0x30 +#define NAU8825_REG_MUTE_CTRL 0x31 +#define NAU8825_REG_HSVOL_CTRL 0x32 +#define NAU8825_REG_DACL_CTRL 0x33 +#define NAU8825_REG_DACR_CTRL 0x34 +#define NAU8825_REG_ADC_DRC_KNEE_IP12 0x38 +#define NAU8825_REG_ADC_DRC_KNEE_IP34 0x39 +#define NAU8825_REG_ADC_DRC_SLOPES 0x3a +#define NAU8825_REG_ADC_DRC_ATKDCY 0x3b +#define NAU8825_REG_DAC_DRC_KNEE_IP12 0x45 +#define NAU8825_REG_DAC_DRC_KNEE_IP34 0x46 +#define NAU8825_REG_DAC_DRC_SLOPES 0x47 +#define NAU8825_REG_DAC_DRC_ATKDCY 0x48 +#define NAU8825_REG_IMM_MODE_CTRL 0x4c +#define NAU8825_REG_IMM_RMS_L 0x4d +#define NAU8825_REG_IMM_RMS_R 0x4e +#define NAU8825_REG_CLASSG_CTRL 0x50 +#define NAU8825_REG_OPT_EFUSE_CTRL 0x51 +#define NAU8825_REG_MISC_CTRL 0x55 +#define NAU8825_REG_I2C_DEVICE_ID 0x58 +#define NAU8825_REG_SARDOUT_RAM_STATUS 0x59 +#define NAU8825_REG_BIAS_ADJ 0x66 +#define NAU8825_REG_TRIM_SETTINGS 0x68 +#define NAU8825_REG_ANALOG_CONTROL_1 0x69 +#define NAU8825_REG_ANALOG_CONTROL_2 0x6a +#define NAU8825_REG_ANALOG_ADC_1 0x71 +#define NAU8825_REG_ANALOG_ADC_2 0x72 +#define NAU8825_REG_RDAC 0x73 +#define NAU8825_REG_MIC_BIAS 0x74 +#define NAU8825_REG_BOOST 0x76 +#define NAU8825_REG_FEPGA 0x77 +#define NAU8825_REG_POWER_UP_CONTROL 0x7f +#define NAU8825_REG_CHARGE_PUMP 0x80 +#define NAU8825_REG_CHARGE_PUMP_INPUT_READ 0x81 +#define NAU8825_REG_GENERAL_STATUS 0x82 +#define NAU8825_REG_MAX NAU8825_REG_GENERAL_STATUS +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8825_REG_ADDR_LEN 16 +#define NAU8825_REG_DATA_LEN 16 + +/* ENA_CTRL (0x1) */ +#define NAU8825_ENABLE_DACR_SFT 10 +#define NAU8825_ENABLE_DACR (1 << NAU8825_ENABLE_DACR_SFT) +#define NAU8825_ENABLE_DACL_SFT 9 +#define NAU8825_ENABLE_DACL (1 << NAU8825_ENABLE_DACL_SFT) +#define NAU8825_ENABLE_ADC_SFT 8 +#define NAU8825_ENABLE_ADC (1 << NAU8825_ENABLE_ADC_SFT) +#define NAU8825_ENABLE_ADC_CLK_SFT 7 +#define NAU8825_ENABLE_ADC_CLK (1 << NAU8825_ENABLE_ADC_CLK_SFT) +#define NAU8825_ENABLE_DAC_CLK_SFT 6 +#define NAU8825_ENABLE_DAC_CLK (1 << NAU8825_ENABLE_DAC_CLK_SFT) +#define NAU8825_ENABLE_SAR_SFT 1 + +/* CLK_DIVIDER (0x3) */ +#define NAU8825_CLK_SRC_SFT 15 +#define NAU8825_CLK_SRC_MASK (1 << NAU8825_CLK_SRC_SFT) +#define NAU8825_CLK_SRC_VCO (1 << NAU8825_CLK_SRC_SFT) +#define NAU8825_CLK_SRC_MCLK (0 << NAU8825_CLK_SRC_SFT) +#define NAU8825_CLK_ADC_SRC_SFT 6 +#define NAU8825_CLK_ADC_SRC_MASK (0x3 << NAU8825_CLK_ADC_SRC_SFT) +#define NAU8825_CLK_DAC_SRC_SFT 4 +#define NAU8825_CLK_DAC_SRC_MASK (0x3 << NAU8825_CLK_DAC_SRC_SFT) +#define NAU8825_CLK_MCLK_SRC_MASK (0xf << 0) + +/* FLL1 (0x04) */ +#define NAU8825_ICTRL_LATCH_SFT 10 +#define NAU8825_ICTRL_LATCH_MASK (0x7 << NAU8825_ICTRL_LATCH_SFT) +#define NAU8825_FLL_RATIO_MASK (0x7f << 0) + +/* FLL3 (0x06) */ +#define NAU8825_GAIN_ERR_SFT 12 +#define NAU8825_GAIN_ERR_MASK (0xf << NAU8825_GAIN_ERR_SFT) +#define NAU8825_FLL_INTEGER_MASK (0x3ff << 0) +#define NAU8825_FLL_CLK_SRC_SFT 10 +#define NAU8825_FLL_CLK_SRC_MASK (0x3 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_MCLK (0 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_BLK (0x2 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_FS (0x3 << NAU8825_FLL_CLK_SRC_SFT) + +/* FLL4 (0x07) */ +#define NAU8825_FLL_REF_DIV_SFT 10 +#define NAU8825_FLL_REF_DIV_MASK (0x3 << NAU8825_FLL_REF_DIV_SFT) + +/* FLL5 (0x08) */ +#define NAU8825_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8825_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8825_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8825_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8825_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8825_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8825_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8825_FLL_FTR_SW_FILTER (0x0 << 12) + +/* FLL6 (0x9) */ +#define NAU8825_DCO_EN (0x1 << 15) +#define NAU8825_SDM_EN (0x1 << 14) +#define NAU8825_CUTOFF500 (0x1 << 13) + +/* HSD_CTRL (0xc) */ +#define NAU8825_HSD_AUTO_MODE (1 << 6) +/* 0 - open, 1 - short to GND */ +#define NAU8825_SPKR_DWN1R (1 << 1) +#define NAU8825_SPKR_DWN1L (1 << 0) + +/* JACK_DET_CTRL (0xd) */ +#define NAU8825_JACK_DET_RESTART (1 << 9) +#define NAU8825_JACK_DET_DB_BYPASS (1 << 8) +#define NAU8825_JACK_INSERT_DEBOUNCE_SFT 5 +#define NAU8825_JACK_INSERT_DEBOUNCE_MASK (0x7 << NAU8825_JACK_INSERT_DEBOUNCE_SFT) +#define NAU8825_JACK_EJECT_DEBOUNCE_SFT 2 +#define NAU8825_JACK_EJECT_DEBOUNCE_MASK (0x7 << NAU8825_JACK_EJECT_DEBOUNCE_SFT) +#define NAU8825_JACK_POLARITY (1 << 1) /* 0 - active low, 1 - active high */ + +/* INTERRUPT_MASK (0xf) */ +#define NAU8825_IRQ_OUTPUT_EN (1 << 11) +#define NAU8825_IRQ_HEADSET_COMPLETE_EN (1 << 10) +#define NAU8825_IRQ_RMS_EN (1 << 8) +#define NAU8825_IRQ_KEY_RELEASE_EN (1 << 7) +#define NAU8825_IRQ_KEY_SHORT_PRESS_EN (1 << 5) +#define NAU8825_IRQ_EJECT_EN (1 << 2) +#define NAU8825_IRQ_INSERT_EN (1 << 0) + +/* IRQ_STATUS (0x10) */ +#define NAU8825_HEADSET_COMPLETION_IRQ (1 << 10) +#define NAU8825_SHORT_CIRCUIT_IRQ (1 << 9) +#define NAU8825_IMPEDANCE_MEAS_IRQ (1 << 8) +#define NAU8825_KEY_IRQ_MASK (0x7 << 5) +#define NAU8825_KEY_RELEASE_IRQ (1 << 7) +#define NAU8825_KEY_LONG_PRESS_IRQ (1 << 6) +#define NAU8825_KEY_SHORT_PRESS_IRQ (1 << 5) +#define NAU8825_MIC_DETECTION_IRQ (1 << 4) +#define NAU8825_JACK_EJECTION_IRQ_MASK (3 << 2) +#define NAU8825_JACK_EJECTION_DETECTED (1 << 2) +#define NAU8825_JACK_INSERTION_IRQ_MASK (3 << 0) +#define NAU8825_JACK_INSERTION_DETECTED (1 << 0) + +/* INTERRUPT_DIS_CTRL (0x12) */ +#define NAU8825_IRQ_HEADSET_COMPLETE_DIS (1 << 10) +#define NAU8825_IRQ_KEY_RELEASE_DIS (1 << 7) +#define NAU8825_IRQ_KEY_SHORT_PRESS_DIS (1 << 5) +#define NAU8825_IRQ_EJECT_DIS (1 << 2) +#define NAU8825_IRQ_INSERT_DIS (1 << 0) + +/* SAR_CTRL (0x13) */ +#define NAU8825_SAR_ADC_EN_SFT 12 +#define NAU8825_SAR_ADC_EN (1 << NAU8825_SAR_ADC_EN_SFT) +#define NAU8825_SAR_INPUT_MASK (1 << 11) +#define NAU8825_SAR_INPUT_JKSLV (1 << 11) +#define NAU8825_SAR_INPUT_JKR2 (0 << 11) +#define NAU8825_SAR_TRACKING_GAIN_SFT 8 +#define NAU8825_SAR_TRACKING_GAIN_MASK (0x7 << NAU8825_SAR_TRACKING_GAIN_SFT) +#define NAU8825_SAR_COMPARE_TIME_SFT 2 +#define NAU8825_SAR_COMPARE_TIME_MASK (3 << 2) +#define NAU8825_SAR_SAMPLING_TIME_SFT 0 +#define NAU8825_SAR_SAMPLING_TIME_MASK (3 << 0) + +/* KEYDET_CTRL (0x14) */ +#define NAU8825_KEYDET_SHORTKEY_DEBOUNCE_SFT 12 +#define NAU8825_KEYDET_SHORTKEY_DEBOUNCE_MASK (0x3 << NAU8825_KEYDET_SHORTKEY_DEBOUNCE_SFT) +#define NAU8825_KEYDET_LEVELS_NR_SFT 8 +#define NAU8825_KEYDET_LEVELS_NR_MASK (0x7 << 8) +#define NAU8825_KEYDET_HYSTERESIS_SFT 0 +#define NAU8825_KEYDET_HYSTERESIS_MASK 0xf + +/* GPIO12_CTRL (0x1a) */ +#define NAU8825_JKDET_PULL_UP (1 << 11) /* 0 - pull down, 1 - pull up */ +#define NAU8825_JKDET_PULL_EN (1 << 9) /* 0 - enable pull, 1 - disable */ +#define NAU8825_JKDET_OUTPUT_EN (1 << 8) /* 0 - enable input, 1 - enable output */ + +/* I2S_PCM_CTRL1 (0x1c) */ +#define NAU8825_I2S_BP_SFT 7 +#define NAU8825_I2S_BP_MASK (1 << NAU8825_I2S_BP_SFT) +#define NAU8825_I2S_BP_INV (1 << NAU8825_I2S_BP_SFT) +#define NAU8825_I2S_PCMB_SFT 6 +#define NAU8825_I2S_PCMB_MASK (1 << NAU8825_I2S_PCMB_SFT) +#define NAU8825_I2S_PCMB_EN (1 << NAU8825_I2S_PCMB_SFT) +#define NAU8825_I2S_DL_SFT 2 +#define NAU8825_I2S_DL_MASK (0x3 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_16 (0 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_20 (1 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_24 (2 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DL_32 (3 << NAU8825_I2S_DL_SFT) +#define NAU8825_I2S_DF_SFT 0 +#define NAU8825_I2S_DF_MASK (0x3 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_RIGTH (0 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_LEFT (1 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_I2S (2 << NAU8825_I2S_DF_SFT) +#define NAU8825_I2S_DF_PCM_AB (3 << NAU8825_I2S_DF_SFT) + +/* I2S_PCM_CTRL2 (0x1d) */ +#define NAU8825_I2S_TRISTATE (1 << 15) /* 0 - normal mode, 1 - Hi-Z output */ +#define NAU8825_I2S_LRC_DIV_SFT 12 +#define NAU8825_I2S_LRC_DIV_MASK (0x3 << NAU8825_I2S_LRC_DIV_SFT) +#define NAU8825_I2S_MS_SFT 3 +#define NAU8825_I2S_MS_MASK (1 << NAU8825_I2S_MS_SFT) +#define NAU8825_I2S_MS_MASTER (1 << NAU8825_I2S_MS_SFT) +#define NAU8825_I2S_MS_SLAVE (0 << NAU8825_I2S_MS_SFT) +#define NAU8825_I2S_BLK_DIV_MASK 0x7 + +/* LEFT_TIME_SLOT (0x1e) */ +#define NAU8825_FS_ERR_CMP_SEL_SFT 14 +#define NAU8825_FS_ERR_CMP_SEL_MASK (0x3 << NAU8825_FS_ERR_CMP_SEL_SFT) +#define NAU8825_DIS_FS_SHORT_DET (1 << 13) + +/* BIQ_CTRL (0x20) */ +#define NAU8825_BIQ_WRT_SFT 4 +#define NAU8825_BIQ_WRT_EN (1 << NAU8825_BIQ_WRT_SFT) +#define NAU8825_BIQ_PATH_SFT 0 +#define NAU8825_BIQ_PATH_MASK (1 << NAU8825_BIQ_PATH_SFT) +#define NAU8825_BIQ_PATH_ADC (0 << NAU8825_BIQ_PATH_SFT) +#define NAU8825_BIQ_PATH_DAC (1 << NAU8825_BIQ_PATH_SFT) + +/* ADC_RATE (0x2b) */ +#define NAU8825_ADC_SINC4_SFT 4 +#define NAU8825_ADC_SINC4_EN (1 << NAU8825_ADC_SINC4_SFT) +#define NAU8825_ADC_SYNC_DOWN_SFT 0 +#define NAU8825_ADC_SYNC_DOWN_MASK 0x3 +#define NAU8825_ADC_SYNC_DOWN_32 0 +#define NAU8825_ADC_SYNC_DOWN_64 1 +#define NAU8825_ADC_SYNC_DOWN_128 2 +#define NAU8825_ADC_SYNC_DOWN_256 3 + +/* DAC_CTRL1 (0x2c) */ +#define NAU8825_DAC_CLIP_OFF (1 << 7) +#define NAU8825_DAC_OVERSAMPLE_SFT 0 +#define NAU8825_DAC_OVERSAMPLE_MASK 0x7 +#define NAU8825_DAC_OVERSAMPLE_64 0 +#define NAU8825_DAC_OVERSAMPLE_256 1 +#define NAU8825_DAC_OVERSAMPLE_128 2 +#define NAU8825_DAC_OVERSAMPLE_32 4 + +/* ADC_DGAIN_CTRL (0x30) */ +#define NAU8825_ADC_DIG_VOL_MASK 0xff + +/* MUTE_CTRL (0x31) */ +#define NAU8825_DAC_ZERO_CROSSING_EN (1 << 9) +#define NAU8825_DAC_SOFT_MUTE (1 << 9) + +/* HSVOL_CTRL (0x32) */ +#define NAU8825_HP_MUTE (1 << 15) +#define NAU8825_HP_MUTE_AUTO (1 << 14) +#define NAU8825_HPL_MUTE (1 << 13) +#define NAU8825_HPR_MUTE (1 << 12) +#define NAU8825_HPL_VOL_SFT 6 +#define NAU8825_HPL_VOL_MASK (0x3f << NAU8825_HPL_VOL_SFT) +#define NAU8825_HPR_VOL_SFT 0 +#define NAU8825_HPR_VOL_MASK (0x3f << NAU8825_HPR_VOL_SFT) +#define NAU8825_HP_VOL_MIN 0x36 + +/* DACL_CTRL (0x33) */ +#define NAU8825_DACL_CH_SEL_SFT 9 +#define NAU8825_DACL_CH_SEL_MASK (0x1 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_SEL_L (0x0 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_SEL_R (0x1 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_VOL_MASK 0xff + +/* DACR_CTRL (0x34) */ +#define NAU8825_DACR_CH_SEL_SFT 9 +#define NAU8825_DACR_CH_SEL_MASK (0x1 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_SEL_L (0x0 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_SEL_R (0x1 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_VOL_MASK 0xff + +/* IMM_MODE_CTRL (0x4C) */ +#define NAU8825_IMM_THD_SFT 8 +#define NAU8825_IMM_THD_MASK (0x3f << NAU8825_IMM_THD_SFT) +#define NAU8825_IMM_GEN_VOL_SFT 6 +#define NAU8825_IMM_GEN_VOL_MASK (0x3 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_2nd (0x0 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_4th (0x1 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_8th (0x2 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_16th (0x3 << NAU8825_IMM_GEN_VOL_SFT) + +#define NAU8825_IMM_CYC_SFT 4 +#define NAU8825_IMM_CYC_MASK (0x3 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_1024 (0x0 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_2048 (0x1 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_4096 (0x2 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_8192 (0x3 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_EN (1 << 3) +#define NAU8825_IMM_DAC_SRC_MASK 0x7 +#define NAU8825_IMM_DAC_SRC_BIQ 0x0 +#define NAU8825_IMM_DAC_SRC_DRC 0x1 +#define NAU8825_IMM_DAC_SRC_MIX 0x2 +#define NAU8825_IMM_DAC_SRC_SIN 0x3 + +/* CLASSG_CTRL (0x50) */ +#define NAU8825_CLASSG_TIMER_SFT 8 +#define NAU8825_CLASSG_TIMER_MASK (0x3f << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_1ms (0x1 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_2ms (0x2 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_8ms (0x4 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_16ms (0x8 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_32ms (0x10 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_64ms (0x20 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_LDAC_EN (0x1 << 2) +#define NAU8825_CLASSG_RDAC_EN (0x1 << 1) +#define NAU8825_CLASSG_EN (1 << 0) + +/* I2C_DEVICE_ID (0x58) */ +#define NAU8825_GPIO2JD1 (1 << 7) +#define NAU8825_SOFTWARE_ID_MASK 0x3 +#define NAU8825_SOFTWARE_ID_NAU8825 0x0 + +/* BIAS_ADJ (0x66) */ +#define NAU8825_BIAS_HPR_IMP (1 << 15) +#define NAU8825_BIAS_HPL_IMP (1 << 14) +#define NAU8825_BIAS_TESTDAC_SFT 8 +#define NAU8825_BIAS_TESTDAC_EN (0x3 << NAU8825_BIAS_TESTDAC_SFT) +#define NAU8825_BIAS_TESTDACR_EN (0x2 << NAU8825_BIAS_TESTDAC_SFT) +#define NAU8825_BIAS_TESTDACL_EN (0x1 << NAU8825_BIAS_TESTDAC_SFT) +#define NAU8825_BIAS_VMID (1 << 6) +#define NAU8825_BIAS_VMID_SEL_SFT 4 +#define NAU8825_BIAS_VMID_SEL_MASK (3 << NAU8825_BIAS_VMID_SEL_SFT) + +/* ANALOG_CONTROL_2 (0x6a) */ +#define NAU8825_HP_NON_CLASSG_CURRENT_2xADJ (1 << 12) +#define NAU8825_DAC_CAPACITOR_MSB (1 << 1) +#define NAU8825_DAC_CAPACITOR_LSB (1 << 0) + +/* ANALOG_ADC_2 (0x72) */ +#define NAU8825_ADC_VREFSEL_MASK (0x3 << 8) +#define NAU8825_ADC_VREFSEL_ANALOG (0 << 8) +#define NAU8825_ADC_VREFSEL_VMID (1 << 8) +#define NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB (2 << 8) +#define NAU8825_ADC_VREFSEL_VMID_PLUS_1DB (3 << 8) +#define NAU8825_POWERUP_ADCL (1 << 6) + +/* RDAC (0x73) */ +#define NAU8825_RDAC_FS_BCLK_ENB (1 << 15) +#define NAU8825_RDAC_EN_SFT 12 +#define NAU8825_RDAC_EN (0x3 << NAU8825_RDAC_EN_SFT) +#define NAU8825_RDAC_CLK_EN_SFT 8 +#define NAU8825_RDAC_CLK_EN (0x3 << NAU8825_RDAC_CLK_EN_SFT) +#define NAU8825_RDAC_CLK_DELAY_SFT 4 +#define NAU8825_RDAC_CLK_DELAY_MASK (0x7 << NAU8825_RDAC_CLK_DELAY_SFT) +#define NAU8825_RDAC_VREF_SFT 2 +#define NAU8825_RDAC_VREF_MASK (0x3 << NAU8825_RDAC_VREF_SFT) + +/* MIC_BIAS (0x74) */ +#define NAU8825_MICBIAS_JKSLV (1 << 14) +#define NAU8825_MICBIAS_JKR2 (1 << 12) +#define NAU8825_MICBIAS_POWERUP_SFT 8 +#define NAU8825_MICBIAS_VOLTAGE_SFT 0 +#define NAU8825_MICBIAS_VOLTAGE_MASK 0x7 + +/* BOOST (0x76) */ +#define NAU8825_PRECHARGE_DIS (1 << 13) +#define NAU8825_GLOBAL_BIAS_EN (1 << 12) +#define NAU8825_HP_BOOST_DIS (1 << 9) +#define NAU8825_HP_BOOST_G_DIS (1 << 8) +#define NAU8825_SHORT_SHUTDOWN_EN (1 << 6) + +/* POWER_UP_CONTROL (0x7f) */ +#define NAU8825_POWERUP_INTEGR_R (1 << 5) +#define NAU8825_POWERUP_INTEGR_L (1 << 4) +#define NAU8825_POWERUP_DRV_IN_R (1 << 3) +#define NAU8825_POWERUP_DRV_IN_L (1 << 2) +#define NAU8825_POWERUP_HP_DRV_R (1 << 1) +#define NAU8825_POWERUP_HP_DRV_L (1 << 0) + +/* CHARGE_PUMP (0x80) */ +#define NAU8825_JAMNODCLOW (1 << 10) +#define NAU8825_POWER_DOWN_DACR (1 << 9) +#define NAU8825_POWER_DOWN_DACL (1 << 8) +#define NAU8825_CHANRGE_PUMP_EN (1 << 5) + + +/* System Clock Source */ +enum { + NAU8825_CLK_DIS = 0, + NAU8825_CLK_MCLK, + NAU8825_CLK_INTERNAL, + NAU8825_CLK_FLL_MCLK, + NAU8825_CLK_FLL_BLK, + NAU8825_CLK_FLL_FS, +}; + +/* Cross talk detection state */ +enum { + NAU8825_XTALK_PREPARE = 0, + NAU8825_XTALK_HPR_R2L, + NAU8825_XTALK_HPL_R2L, + NAU8825_XTALK_IMM, + NAU8825_XTALK_DONE, +}; + +struct nau8825 { + struct device *dev; + struct regmap *regmap; + struct snd_soc_dapm_context *dapm; + struct snd_soc_jack *jack; + struct clk *mclk; + struct work_struct xtalk_work; + struct semaphore xtalk_sem; + int irq; + int mclk_freq; /* 0 - mclk is disabled */ + int button_pressed; + int micbias_voltage; + int vref_impedance; + bool jkdet_enable; + bool jkdet_pull_enable; + bool jkdet_pull_up; + int jkdet_polarity; + int sar_threshold_num; + int sar_threshold[8]; + int sar_hysteresis; + int sar_voltage; + int sar_compare_time; + int sar_sampling_time; + int key_debounce; + int jack_insert_debounce; + int jack_eject_debounce; + int high_imped; + int xtalk_state; + int xtalk_event; + int xtalk_event_mask; + bool xtalk_protect; + int imp_rms[NAU8825_XTALK_IMM]; + int xtalk_enable; + bool xtalk_baktab_initialized; /* True if initialized. */ +}; + +int nau8825_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack); + + +#endif /* __NAU8825_H__ */ diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c new file mode 100644 index 000000000..84777d3fa --- /dev/null +++ b/sound/soc/codecs/pcm1681.c @@ -0,0 +1,347 @@ +/* + * PCM1681 ASoC codec driver + * + * Copyright (c) StreamUnlimited GmbH 2013 + * Marek Belisko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PCM1681_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +#define PCM1681_PCM_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) + +#define PCM1681_SOFT_MUTE_ALL 0xff +#define PCM1681_DEEMPH_RATE_MASK 0x18 +#define PCM1681_DEEMPH_MASK 0x01 + +#define PCM1681_ATT_CONTROL(X) (X <= 6 ? X : X + 9) /* Attenuation level */ +#define PCM1681_SOFT_MUTE 0x07 /* Soft mute control register */ +#define PCM1681_DAC_CONTROL 0x08 /* DAC operation control */ +#define PCM1681_FMT_CONTROL 0x09 /* Audio interface data format */ +#define PCM1681_DEEMPH_CONTROL 0x0a /* De-emphasis control */ +#define PCM1681_ZERO_DETECT_STATUS 0x0e /* Zero detect status reg */ + +static const struct reg_default pcm1681_reg_defaults[] = { + { 0x01, 0xff }, + { 0x02, 0xff }, + { 0x03, 0xff }, + { 0x04, 0xff }, + { 0x05, 0xff }, + { 0x06, 0xff }, + { 0x07, 0x00 }, + { 0x08, 0x00 }, + { 0x09, 0x06 }, + { 0x0A, 0x00 }, + { 0x0B, 0xff }, + { 0x0C, 0x0f }, + { 0x0D, 0x00 }, + { 0x10, 0xff }, + { 0x11, 0xff }, + { 0x12, 0x00 }, + { 0x13, 0x00 }, +}; + +static bool pcm1681_accessible_reg(struct device *dev, unsigned int reg) +{ + return !((reg == 0x00) || (reg == 0x0f)); +} + +static bool pcm1681_writeable_reg(struct device *dev, unsigned int reg) +{ + return pcm1681_accessible_reg(dev, reg) && + (reg != PCM1681_ZERO_DETECT_STATUS); +} + +struct pcm1681_private { + struct regmap *regmap; + unsigned int format; + /* Current deemphasis status */ + unsigned int deemph; + /* Current rate for deemphasis control */ + unsigned int rate; +}; + +static const int pcm1681_deemph[] = { 44100, 48000, 32000 }; + +static int pcm1681_set_deemph(struct snd_soc_component *component) +{ + struct pcm1681_private *priv = snd_soc_component_get_drvdata(component); + int i = 0, val = -1, enable = 0; + + if (priv->deemph) { + for (i = 0; i < ARRAY_SIZE(pcm1681_deemph); i++) { + if (pcm1681_deemph[i] == priv->rate) { + val = i; + break; + } + } + } + + if (val != -1) { + regmap_update_bits(priv->regmap, PCM1681_DEEMPH_CONTROL, + PCM1681_DEEMPH_RATE_MASK, val << 3); + enable = 1; + } else { + enable = 0; + } + + /* enable/disable deemphasis functionality */ + return regmap_update_bits(priv->regmap, PCM1681_DEEMPH_CONTROL, + PCM1681_DEEMPH_MASK, enable); +} + +static int pcm1681_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm1681_private *priv = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = priv->deemph; + + return 0; +} + +static int pcm1681_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm1681_private *priv = snd_soc_component_get_drvdata(component); + + priv->deemph = ucontrol->value.integer.value[0]; + + return pcm1681_set_deemph(component); +} + +static int pcm1681_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct pcm1681_private *priv = snd_soc_component_get_drvdata(component); + + /* The PCM1681 can only be slave to all clocks */ + if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(component->dev, "Invalid clocking mode\n"); + return -EINVAL; + } + + priv->format = format; + + return 0; +} + +static int pcm1681_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct pcm1681_private *priv = snd_soc_component_get_drvdata(component); + int val; + + if (mute) + val = PCM1681_SOFT_MUTE_ALL; + else + val = 0; + + return regmap_write(priv->regmap, PCM1681_SOFT_MUTE, val); +} + +static int pcm1681_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm1681_private *priv = snd_soc_component_get_drvdata(component); + int val = 0, ret; + + priv->rate = params_rate(params); + + switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_width(params)) { + case 24: + val = 0; + break; + case 16: + val = 3; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + val = 0x04; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = 0x05; + break; + default: + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, PCM1681_FMT_CONTROL, 0x0f, val); + if (ret < 0) + return ret; + + return pcm1681_set_deemph(component); +} + +static const struct snd_soc_dai_ops pcm1681_dai_ops = { + .set_fmt = pcm1681_set_dai_fmt, + .hw_params = pcm1681_hw_params, + .digital_mute = pcm1681_digital_mute, +}; + +static const struct snd_soc_dapm_widget pcm1681_dapm_widgets[] = { +SND_SOC_DAPM_OUTPUT("VOUT1"), +SND_SOC_DAPM_OUTPUT("VOUT2"), +SND_SOC_DAPM_OUTPUT("VOUT3"), +SND_SOC_DAPM_OUTPUT("VOUT4"), +SND_SOC_DAPM_OUTPUT("VOUT5"), +SND_SOC_DAPM_OUTPUT("VOUT6"), +SND_SOC_DAPM_OUTPUT("VOUT7"), +SND_SOC_DAPM_OUTPUT("VOUT8"), +}; + +static const struct snd_soc_dapm_route pcm1681_dapm_routes[] = { + { "VOUT1", NULL, "Playback" }, + { "VOUT2", NULL, "Playback" }, + { "VOUT3", NULL, "Playback" }, + { "VOUT4", NULL, "Playback" }, + { "VOUT5", NULL, "Playback" }, + { "VOUT6", NULL, "Playback" }, + { "VOUT7", NULL, "Playback" }, + { "VOUT8", NULL, "Playback" }, +}; + +static const DECLARE_TLV_DB_SCALE(pcm1681_dac_tlv, -6350, 50, 1); + +static const struct snd_kcontrol_new pcm1681_controls[] = { + SOC_DOUBLE_R_TLV("Channel 1/2 Playback Volume", + PCM1681_ATT_CONTROL(1), PCM1681_ATT_CONTROL(2), 0, + 0x7f, 0, pcm1681_dac_tlv), + SOC_DOUBLE_R_TLV("Channel 3/4 Playback Volume", + PCM1681_ATT_CONTROL(3), PCM1681_ATT_CONTROL(4), 0, + 0x7f, 0, pcm1681_dac_tlv), + SOC_DOUBLE_R_TLV("Channel 5/6 Playback Volume", + PCM1681_ATT_CONTROL(5), PCM1681_ATT_CONTROL(6), 0, + 0x7f, 0, pcm1681_dac_tlv), + SOC_DOUBLE_R_TLV("Channel 7/8 Playback Volume", + PCM1681_ATT_CONTROL(7), PCM1681_ATT_CONTROL(8), 0, + 0x7f, 0, pcm1681_dac_tlv), + SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0, + pcm1681_get_deemph, pcm1681_put_deemph), +}; + +static struct snd_soc_dai_driver pcm1681_dai = { + .name = "pcm1681-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = PCM1681_PCM_RATES, + .formats = PCM1681_PCM_FORMATS, + }, + .ops = &pcm1681_dai_ops, +}; + +#ifdef CONFIG_OF +static const struct of_device_id pcm1681_dt_ids[] = { + { .compatible = "ti,pcm1681", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm1681_dt_ids); +#endif + +static const struct regmap_config pcm1681_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x13, + .reg_defaults = pcm1681_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(pcm1681_reg_defaults), + .writeable_reg = pcm1681_writeable_reg, + .readable_reg = pcm1681_accessible_reg, +}; + +static const struct snd_soc_component_driver soc_component_dev_pcm1681 = { + .controls = pcm1681_controls, + .num_controls = ARRAY_SIZE(pcm1681_controls), + .dapm_widgets = pcm1681_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm1681_dapm_widgets), + .dapm_routes = pcm1681_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm1681_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct i2c_device_id pcm1681_i2c_id[] = { + {"pcm1681", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, pcm1681_i2c_id); + +static int pcm1681_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct pcm1681_private *priv; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(client, &pcm1681_regmap); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&client->dev, "Failed to create regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(client, priv); + + return devm_snd_soc_register_component(&client->dev, + &soc_component_dev_pcm1681, + &pcm1681_dai, 1); +} + +static struct i2c_driver pcm1681_i2c_driver = { + .driver = { + .name = "pcm1681", + .of_match_table = of_match_ptr(pcm1681_dt_ids), + }, + .id_table = pcm1681_i2c_id, + .probe = pcm1681_i2c_probe, +}; + +module_i2c_driver(pcm1681_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments PCM1681 ALSA SoC Codec Driver"); +MODULE_AUTHOR("Marek Belisko "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm1789-i2c.c b/sound/soc/codecs/pcm1789-i2c.c new file mode 100644 index 000000000..327ec584f --- /dev/null +++ b/sound/soc/codecs/pcm1789-i2c.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 +// Audio driver for PCM1789 I2C +// Copyright (C) 2018 Bootlin +// Mylène Josserand + +#include +#include +#include +#include +#include +#include + +#include "pcm1789.h" + +static int pcm1789_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &pcm1789_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + return pcm1789_common_init(&client->dev, regmap); +} + +static int pcm1789_i2c_remove(struct i2c_client *client) +{ + return pcm1789_common_exit(&client->dev); +} + +static const struct of_device_id pcm1789_of_match[] = { + { .compatible = "ti,pcm1789", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm1789_of_match); + +static const struct i2c_device_id pcm1789_i2c_ids[] = { + { "pcm1789", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm1789_i2c_ids); + +static struct i2c_driver pcm1789_i2c_driver = { + .driver = { + .name = "pcm1789", + .of_match_table = of_match_ptr(pcm1789_of_match), + }, + .id_table = pcm1789_i2c_ids, + .probe = pcm1789_i2c_probe, + .remove = pcm1789_i2c_remove, +}; + +module_i2c_driver(pcm1789_i2c_driver); + +MODULE_DESCRIPTION("ASoC PCM1789 I2C driver"); +MODULE_AUTHOR("Mylène Josserand "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm1789.c b/sound/soc/codecs/pcm1789.c new file mode 100644 index 000000000..8df6447c7 --- /dev/null +++ b/sound/soc/codecs/pcm1789.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0 +// Audio driver for PCM1789 +// Copyright (C) 2018 Bootlin +// Mylène Josserand + +#include +#include +#include + +#include +#include +#include + +#include "pcm1789.h" + +#define PCM1789_MUTE_CONTROL 0x10 +#define PCM1789_FMT_CONTROL 0x11 +#define PCM1789_SOFT_MUTE 0x14 +#define PCM1789_DAC_VOL_LEFT 0x18 +#define PCM1789_DAC_VOL_RIGHT 0x19 + +#define PCM1789_FMT_MASK 0x07 +#define PCM1789_MUTE_MASK 0x03 +#define PCM1789_MUTE_SRET 0x06 + +struct pcm1789_private { + struct regmap *regmap; + unsigned int format; + unsigned int rate; + struct gpio_desc *reset; + struct work_struct work; + struct device *dev; +}; + +static const struct reg_default pcm1789_reg_defaults[] = { + { PCM1789_FMT_CONTROL, 0x00 }, + { PCM1789_SOFT_MUTE, 0x00 }, + { PCM1789_DAC_VOL_LEFT, 0xff }, + { PCM1789_DAC_VOL_RIGHT, 0xff }, +}; + +static bool pcm1789_accessible_reg(struct device *dev, unsigned int reg) +{ + return reg >= PCM1789_MUTE_CONTROL && reg <= PCM1789_DAC_VOL_RIGHT; +} + +static bool pcm1789_writeable_reg(struct device *dev, unsigned int reg) +{ + return pcm1789_accessible_reg(dev, reg); +} + +static int pcm1789_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct pcm1789_private *priv = snd_soc_component_get_drvdata(component); + + priv->format = format; + + return 0; +} + +static int pcm1789_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + struct pcm1789_private *priv = snd_soc_component_get_drvdata(component); + + return regmap_update_bits(priv->regmap, PCM1789_SOFT_MUTE, + PCM1789_MUTE_MASK, + mute ? 0 : PCM1789_MUTE_MASK); +} + +static int pcm1789_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + struct pcm1789_private *priv = snd_soc_component_get_drvdata(component); + int val = 0, ret; + + priv->rate = params_rate(params); + + switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_width(params)) { + case 24: + val = 2; + break; + case 16: + val = 3; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + switch (params_width(params)) { + case 16: + case 24: + case 32: + val = 0; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_LEFT_J: + switch (params_width(params)) { + case 16: + case 24: + case 32: + val = 1; + break; + default: + return -EINVAL; + } + break; + default: + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, PCM1789_FMT_CONTROL, + PCM1789_FMT_MASK, val); + if (ret < 0) + return ret; + + return 0; +} + +static void pcm1789_work_queue(struct work_struct *work) +{ + struct pcm1789_private *priv = container_of(work, + struct pcm1789_private, + work); + + /* Perform a software reset to remove codec from desynchronized state */ + if (regmap_update_bits(priv->regmap, PCM1789_MUTE_CONTROL, + 0x3 << PCM1789_MUTE_SRET, 0) < 0) + dev_err(priv->dev, "Error while setting SRET"); +} + +static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm1789_private *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + schedule_work(&priv->work); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct snd_soc_dai_ops pcm1789_dai_ops = { + .set_fmt = pcm1789_set_dai_fmt, + .hw_params = pcm1789_hw_params, + .digital_mute = pcm1789_digital_mute, + .trigger = pcm1789_trigger, +}; + +static const DECLARE_TLV_DB_SCALE(pcm1789_dac_tlv, -12000, 50, 1); + +static const struct snd_kcontrol_new pcm1789_controls[] = { + SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM1789_DAC_VOL_LEFT, + PCM1789_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0, + pcm1789_dac_tlv), +}; + +static const struct snd_soc_dapm_widget pcm1789_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("IOUTL+"), + SND_SOC_DAPM_OUTPUT("IOUTL-"), + SND_SOC_DAPM_OUTPUT("IOUTR+"), + SND_SOC_DAPM_OUTPUT("IOUTR-"), +}; + +static const struct snd_soc_dapm_route pcm1789_dapm_routes[] = { + { "IOUTL+", NULL, "Playback" }, + { "IOUTL-", NULL, "Playback" }, + { "IOUTR+", NULL, "Playback" }, + { "IOUTR-", NULL, "Playback" }, +}; + +static struct snd_soc_dai_driver pcm1789_dai = { + .name = "pcm1789-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 10000, + .rate_max = 200000, + .formats = PCM1789_FORMATS, + }, + .ops = &pcm1789_dai_ops, +}; + +const struct regmap_config pcm1789_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PCM1789_DAC_VOL_RIGHT, + .reg_defaults = pcm1789_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(pcm1789_reg_defaults), + .writeable_reg = pcm1789_writeable_reg, + .readable_reg = pcm1789_accessible_reg, +}; +EXPORT_SYMBOL_GPL(pcm1789_regmap_config); + +static const struct snd_soc_component_driver soc_component_dev_pcm1789 = { + .controls = pcm1789_controls, + .num_controls = ARRAY_SIZE(pcm1789_controls), + .dapm_widgets = pcm1789_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm1789_dapm_widgets), + .dapm_routes = pcm1789_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm1789_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +int pcm1789_common_init(struct device *dev, struct regmap *regmap) +{ + struct pcm1789_private *pcm1789; + + pcm1789 = devm_kzalloc(dev, sizeof(struct pcm1789_private), + GFP_KERNEL); + if (!pcm1789) + return -ENOMEM; + + pcm1789->regmap = regmap; + pcm1789->dev = dev; + dev_set_drvdata(dev, pcm1789); + + pcm1789->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(pcm1789->reset)) + return PTR_ERR(pcm1789->reset); + + gpiod_set_value_cansleep(pcm1789->reset, 0); + msleep(300); + + INIT_WORK(&pcm1789->work, pcm1789_work_queue); + + return devm_snd_soc_register_component(dev, &soc_component_dev_pcm1789, + &pcm1789_dai, 1); +} +EXPORT_SYMBOL_GPL(pcm1789_common_init); + +int pcm1789_common_exit(struct device *dev) +{ + struct pcm1789_private *priv = dev_get_drvdata(dev); + + flush_work(&priv->work); + + return 0; +} +EXPORT_SYMBOL_GPL(pcm1789_common_exit); + +MODULE_DESCRIPTION("ASoC PCM1789 driver"); +MODULE_AUTHOR("Mylène Josserand "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm1789.h b/sound/soc/codecs/pcm1789.h new file mode 100644 index 000000000..c446d789e --- /dev/null +++ b/sound/soc/codecs/pcm1789.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +// Definitions for PCM1789 audio driver +// Copyright (C) 2018 Bootlin +// Mylène Josserand + +#ifndef __PCM1789_H__ +#define __PCM1789_H__ + +#define PCM1789_FORMATS (SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S16_LE) + +extern const struct regmap_config pcm1789_regmap_config; + +int pcm1789_common_init(struct device *dev, struct regmap *regmap); +int pcm1789_common_exit(struct device *dev); + +#endif diff --git a/sound/soc/codecs/pcm179x-i2c.c b/sound/soc/codecs/pcm179x-i2c.c new file mode 100644 index 000000000..03747966c --- /dev/null +++ b/sound/soc/codecs/pcm179x-i2c.c @@ -0,0 +1,67 @@ +/* + * PCM179X ASoC I2C driver + * + * Copyright (c) Teenage Engineering AB 2016 + * + * Jacob Siverskog + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include "pcm179x.h" + +static int pcm179x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &pcm179x_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + return pcm179x_common_init(&client->dev, regmap); +} + +static const struct of_device_id pcm179x_of_match[] = { + { .compatible = "ti,pcm1792a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm179x_of_match); + +static const struct i2c_device_id pcm179x_i2c_ids[] = { + { "pcm179x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm179x_i2c_ids); + +static struct i2c_driver pcm179x_i2c_driver = { + .driver = { + .name = "pcm179x", + .of_match_table = of_match_ptr(pcm179x_of_match), + }, + .id_table = pcm179x_i2c_ids, + .probe = pcm179x_i2c_probe, +}; + +module_i2c_driver(pcm179x_i2c_driver); + +MODULE_DESCRIPTION("ASoC PCM179X I2C driver"); +MODULE_AUTHOR("Jacob Siverskog "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm179x-spi.c b/sound/soc/codecs/pcm179x-spi.c new file mode 100644 index 000000000..89ad71567 --- /dev/null +++ b/sound/soc/codecs/pcm179x-spi.c @@ -0,0 +1,66 @@ +/* + * PCM179X ASoC SPI driver + * + * Copyright (c) Amarula Solutions B.V. 2013 + * + * Michael Trimarchi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include "pcm179x.h" + +static int pcm179x_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_spi(spi, &pcm179x_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&spi->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + return pcm179x_common_init(&spi->dev, regmap); +} + +static const struct of_device_id pcm179x_of_match[] = { + { .compatible = "ti,pcm1792a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm179x_of_match); + +static const struct spi_device_id pcm179x_spi_ids[] = { + { "pcm179x", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, pcm179x_spi_ids); + +static struct spi_driver pcm179x_spi_driver = { + .driver = { + .name = "pcm179x", + .of_match_table = of_match_ptr(pcm179x_of_match), + }, + .id_table = pcm179x_spi_ids, + .probe = pcm179x_spi_probe, +}; + +module_spi_driver(pcm179x_spi_driver); + +MODULE_DESCRIPTION("ASoC PCM179X SPI driver"); +MODULE_AUTHOR("Michael Trimarchi "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c new file mode 100644 index 000000000..4b311c06f --- /dev/null +++ b/sound/soc/codecs/pcm179x.c @@ -0,0 +1,240 @@ +/* + * PCM179X ASoC codec driver + * + * Copyright (c) Amarula Solutions B.V. 2013 + * + * Michael Trimarchi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "pcm179x.h" + +#define PCM179X_DAC_VOL_LEFT 0x10 +#define PCM179X_DAC_VOL_RIGHT 0x11 +#define PCM179X_FMT_CONTROL 0x12 +#define PCM179X_MODE_CONTROL 0x13 +#define PCM179X_SOFT_MUTE PCM179X_FMT_CONTROL + +#define PCM179X_FMT_MASK 0x70 +#define PCM179X_FMT_SHIFT 4 +#define PCM179X_MUTE_MASK 0x01 +#define PCM179X_MUTE_SHIFT 0 +#define PCM179X_ATLD_ENABLE (1 << 7) + +static const struct reg_default pcm179x_reg_defaults[] = { + { 0x10, 0xff }, + { 0x11, 0xff }, + { 0x12, 0x50 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x01 }, + { 0x16, 0x00 }, + { 0x17, 0x00 }, +}; + +static bool pcm179x_accessible_reg(struct device *dev, unsigned int reg) +{ + return reg >= 0x10 && reg <= 0x17; +} + +static bool pcm179x_writeable_reg(struct device *dev, unsigned int reg) +{ + bool accessible; + + accessible = pcm179x_accessible_reg(dev, reg); + + return accessible && reg != 0x16 && reg != 0x17; +} + +struct pcm179x_private { + struct regmap *regmap; + unsigned int format; + unsigned int rate; +}; + +static int pcm179x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct pcm179x_private *priv = snd_soc_component_get_drvdata(component); + + priv->format = format; + + return 0; +} + +static int pcm179x_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct pcm179x_private *priv = snd_soc_component_get_drvdata(component); + int ret; + + ret = regmap_update_bits(priv->regmap, PCM179X_SOFT_MUTE, + PCM179X_MUTE_MASK, !!mute); + if (ret < 0) + return ret; + + return 0; +} + +static int pcm179x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm179x_private *priv = snd_soc_component_get_drvdata(component); + int val = 0, ret; + + priv->rate = params_rate(params); + + switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_width(params)) { + case 24: + case 32: + val = 2; + break; + case 16: + val = 0; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + switch (params_width(params)) { + case 24: + case 32: + val = 5; + break; + case 16: + val = 4; + break; + default: + return -EINVAL; + } + break; + default: + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + val = val << PCM179X_FMT_SHIFT | PCM179X_ATLD_ENABLE; + + ret = regmap_update_bits(priv->regmap, PCM179X_FMT_CONTROL, + PCM179X_FMT_MASK | PCM179X_ATLD_ENABLE, val); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_dai_ops pcm179x_dai_ops = { + .set_fmt = pcm179x_set_dai_fmt, + .hw_params = pcm179x_hw_params, + .digital_mute = pcm179x_digital_mute, +}; + +static const DECLARE_TLV_DB_SCALE(pcm179x_dac_tlv, -12000, 50, 1); + +static const struct snd_kcontrol_new pcm179x_controls[] = { + SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM179X_DAC_VOL_LEFT, + PCM179X_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0, + pcm179x_dac_tlv), + SOC_SINGLE("DAC Invert Output Switch", PCM179X_MODE_CONTROL, 7, 1, 0), + SOC_SINGLE("DAC Rolloff Filter Switch", PCM179X_MODE_CONTROL, 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget pcm179x_dapm_widgets[] = { +SND_SOC_DAPM_OUTPUT("IOUTL+"), +SND_SOC_DAPM_OUTPUT("IOUTL-"), +SND_SOC_DAPM_OUTPUT("IOUTR+"), +SND_SOC_DAPM_OUTPUT("IOUTR-"), +}; + +static const struct snd_soc_dapm_route pcm179x_dapm_routes[] = { + { "IOUTL+", NULL, "Playback" }, + { "IOUTL-", NULL, "Playback" }, + { "IOUTR+", NULL, "Playback" }, + { "IOUTR-", NULL, "Playback" }, +}; + +static struct snd_soc_dai_driver pcm179x_dai = { + .name = "pcm179x-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 10000, + .rate_max = 200000, + .formats = PCM1792A_FORMATS, }, + .ops = &pcm179x_dai_ops, +}; + +const struct regmap_config pcm179x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 23, + .reg_defaults = pcm179x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(pcm179x_reg_defaults), + .writeable_reg = pcm179x_writeable_reg, + .readable_reg = pcm179x_accessible_reg, +}; +EXPORT_SYMBOL_GPL(pcm179x_regmap_config); + +static const struct snd_soc_component_driver soc_component_dev_pcm179x = { + .controls = pcm179x_controls, + .num_controls = ARRAY_SIZE(pcm179x_controls), + .dapm_widgets = pcm179x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm179x_dapm_widgets), + .dapm_routes = pcm179x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm179x_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +int pcm179x_common_init(struct device *dev, struct regmap *regmap) +{ + struct pcm179x_private *pcm179x; + + pcm179x = devm_kzalloc(dev, sizeof(struct pcm179x_private), + GFP_KERNEL); + if (!pcm179x) + return -ENOMEM; + + pcm179x->regmap = regmap; + dev_set_drvdata(dev, pcm179x); + + return devm_snd_soc_register_component(dev, + &soc_component_dev_pcm179x, &pcm179x_dai, 1); +} +EXPORT_SYMBOL_GPL(pcm179x_common_init); + +MODULE_DESCRIPTION("ASoC PCM179X driver"); +MODULE_AUTHOR("Michael Trimarchi "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm179x.h b/sound/soc/codecs/pcm179x.h new file mode 100644 index 000000000..cf8681c9a --- /dev/null +++ b/sound/soc/codecs/pcm179x.h @@ -0,0 +1,27 @@ +/* + * definitions for PCM179X + * + * Copyright 2013 Amarula Solutions + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PCM179X_H__ +#define __PCM179X_H__ + +#define PCM1792A_FORMATS (SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S16_LE) + +extern const struct regmap_config pcm179x_regmap_config; + +int pcm179x_common_init(struct device *dev, struct regmap *regmap); + +#endif diff --git a/sound/soc/codecs/pcm186x-i2c.c b/sound/soc/codecs/pcm186x-i2c.c new file mode 100644 index 000000000..0214dc6d8 --- /dev/null +++ b/sound/soc/codecs/pcm186x-i2c.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments PCM186x Universal Audio ADC - I2C + * + * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com + * Andreas Dannenberg + * Andrew F. Davis + */ + +#include +#include +#include + +#include "pcm186x.h" + +static const struct of_device_id pcm186x_of_match[] = { + { .compatible = "ti,pcm1862", .data = (void *)PCM1862 }, + { .compatible = "ti,pcm1863", .data = (void *)PCM1863 }, + { .compatible = "ti,pcm1864", .data = (void *)PCM1864 }, + { .compatible = "ti,pcm1865", .data = (void *)PCM1865 }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm186x_of_match); + +static int pcm186x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + const enum pcm186x_type type = (enum pcm186x_type)id->driver_data; + int irq = i2c->irq; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &pcm186x_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm186x_probe(&i2c->dev, type, irq, regmap); +} + +static const struct i2c_device_id pcm186x_i2c_id[] = { + { "pcm1862", PCM1862 }, + { "pcm1863", PCM1863 }, + { "pcm1864", PCM1864 }, + { "pcm1865", PCM1865 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm186x_i2c_id); + +static struct i2c_driver pcm186x_i2c_driver = { + .probe = pcm186x_i2c_probe, + .id_table = pcm186x_i2c_id, + .driver = { + .name = "pcm186x", + .of_match_table = pcm186x_of_match, + }, +}; +module_i2c_driver(pcm186x_i2c_driver); + +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("PCM186x Universal Audio ADC I2C Interface Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm186x-spi.c b/sound/soc/codecs/pcm186x-spi.c new file mode 100644 index 000000000..b56e19827 --- /dev/null +++ b/sound/soc/codecs/pcm186x-spi.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments PCM186x Universal Audio ADC - SPI + * + * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com + * Andreas Dannenberg + * Andrew F. Davis + */ + +#include +#include +#include + +#include "pcm186x.h" + +static const struct of_device_id pcm186x_of_match[] = { + { .compatible = "ti,pcm1862", .data = (void *)PCM1862 }, + { .compatible = "ti,pcm1863", .data = (void *)PCM1863 }, + { .compatible = "ti,pcm1864", .data = (void *)PCM1864 }, + { .compatible = "ti,pcm1865", .data = (void *)PCM1865 }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm186x_of_match); + +static int pcm186x_spi_probe(struct spi_device *spi) +{ + const enum pcm186x_type type = + (enum pcm186x_type)spi_get_device_id(spi)->driver_data; + int irq = spi->irq; + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &pcm186x_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm186x_probe(&spi->dev, type, irq, regmap); +} + +static const struct spi_device_id pcm186x_spi_id[] = { + { "pcm1862", PCM1862 }, + { "pcm1863", PCM1863 }, + { "pcm1864", PCM1864 }, + { "pcm1865", PCM1865 }, + { } +}; +MODULE_DEVICE_TABLE(spi, pcm186x_spi_id); + +static struct spi_driver pcm186x_spi_driver = { + .probe = pcm186x_spi_probe, + .id_table = pcm186x_spi_id, + .driver = { + .name = "pcm186x", + .of_match_table = pcm186x_of_match, + }, +}; +module_spi_driver(pcm186x_spi_driver); + +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("PCM186x Universal Audio ADC SPI Interface Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm186x.c b/sound/soc/codecs/pcm186x.c new file mode 100644 index 000000000..3be0e1478 --- /dev/null +++ b/sound/soc/codecs/pcm186x.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments PCM186x Universal Audio ADC + * + * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com + * Andreas Dannenberg + * Andrew F. Davis + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcm186x.h" + +static const char * const pcm186x_supply_names[] = { + "avdd", /* Analog power supply. Connect to 3.3-V supply. */ + "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ + "iovdd", /* I/O power supply. Connect to 3.3-V or 1.8-V. */ +}; +#define PCM186x_NUM_SUPPLIES ARRAY_SIZE(pcm186x_supply_names) + +struct pcm186x_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[PCM186x_NUM_SUPPLIES]; + unsigned int sysclk; + unsigned int tdm_offset; + bool is_tdm_mode; + bool is_master_mode; +}; + +static const DECLARE_TLV_DB_SCALE(pcm186x_pga_tlv, -1200, 50, 0); + +static const struct snd_kcontrol_new pcm1863_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("ADC Capture Volume", PCM186X_PGA_VAL_CH1_L, + PCM186X_PGA_VAL_CH1_R, 0, -24, 80, 7, 0, + pcm186x_pga_tlv), +}; + +static const struct snd_kcontrol_new pcm1865_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", PCM186X_PGA_VAL_CH1_L, + PCM186X_PGA_VAL_CH1_R, 0, -24, 80, 7, 0, + pcm186x_pga_tlv), + SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", PCM186X_PGA_VAL_CH2_L, + PCM186X_PGA_VAL_CH2_R, 0, -24, 80, 7, 0, + pcm186x_pga_tlv), +}; + +static const unsigned int pcm186x_adc_input_channel_sel_value[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x20, 0x30 +}; + +static const char * const pcm186x_adcl_input_channel_sel_text[] = { + "No Select", + "VINL1[SE]", /* Default for ADC1L */ + "VINL2[SE]", /* Default for ADC2L */ + "VINL2[SE] + VINL1[SE]", + "VINL3[SE]", + "VINL3[SE] + VINL1[SE]", + "VINL3[SE] + VINL2[SE]", + "VINL3[SE] + VINL2[SE] + VINL1[SE]", + "VINL4[SE]", + "VINL4[SE] + VINL1[SE]", + "VINL4[SE] + VINL2[SE]", + "VINL4[SE] + VINL2[SE] + VINL1[SE]", + "VINL4[SE] + VINL3[SE]", + "VINL4[SE] + VINL3[SE] + VINL1[SE]", + "VINL4[SE] + VINL3[SE] + VINL2[SE]", + "VINL4[SE] + VINL3[SE] + VINL2[SE] + VINL1[SE]", + "{VIN1P, VIN1M}[DIFF]", + "{VIN4P, VIN4M}[DIFF]", + "{VIN1P, VIN1M}[DIFF] + {VIN4P, VIN4M}[DIFF]" +}; + +static const char * const pcm186x_adcr_input_channel_sel_text[] = { + "No Select", + "VINR1[SE]", /* Default for ADC1R */ + "VINR2[SE]", /* Default for ADC2R */ + "VINR2[SE] + VINR1[SE]", + "VINR3[SE]", + "VINR3[SE] + VINR1[SE]", + "VINR3[SE] + VINR2[SE]", + "VINR3[SE] + VINR2[SE] + VINR1[SE]", + "VINR4[SE]", + "VINR4[SE] + VINR1[SE]", + "VINR4[SE] + VINR2[SE]", + "VINR4[SE] + VINR2[SE] + VINR1[SE]", + "VINR4[SE] + VINR3[SE]", + "VINR4[SE] + VINR3[SE] + VINR1[SE]", + "VINR4[SE] + VINR3[SE] + VINR2[SE]", + "VINR4[SE] + VINR3[SE] + VINR2[SE] + VINR1[SE]", + "{VIN2P, VIN2M}[DIFF]", + "{VIN3P, VIN3M}[DIFF]", + "{VIN2P, VIN2M}[DIFF] + {VIN3P, VIN3M}[DIFF]" +}; + +static const struct soc_enum pcm186x_adc_input_channel_sel[] = { + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_L, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text), + pcm186x_adcl_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_R, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text), + pcm186x_adcr_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC2_INPUT_SEL_L, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text), + pcm186x_adcl_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), + SOC_VALUE_ENUM_SINGLE(PCM186X_ADC2_INPUT_SEL_R, 0, + PCM186X_ADC_INPUT_SEL_MASK, + ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text), + pcm186x_adcr_input_channel_sel_text, + pcm186x_adc_input_channel_sel_value), +}; + +static const struct snd_kcontrol_new pcm186x_adc_mux_controls[] = { + SOC_DAPM_ENUM("ADC1 Left Input", pcm186x_adc_input_channel_sel[0]), + SOC_DAPM_ENUM("ADC1 Right Input", pcm186x_adc_input_channel_sel[1]), + SOC_DAPM_ENUM("ADC2 Left Input", pcm186x_adc_input_channel_sel[2]), + SOC_DAPM_ENUM("ADC2 Right Input", pcm186x_adc_input_channel_sel[3]), +}; + +static const struct snd_soc_dapm_widget pcm1863_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("VINL1"), + SND_SOC_DAPM_INPUT("VINR1"), + SND_SOC_DAPM_INPUT("VINL2"), + SND_SOC_DAPM_INPUT("VINR2"), + SND_SOC_DAPM_INPUT("VINL3"), + SND_SOC_DAPM_INPUT("VINR3"), + SND_SOC_DAPM_INPUT("VINL4"), + SND_SOC_DAPM_INPUT("VINR4"), + + SND_SOC_DAPM_MUX("ADC Left Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[0]), + SND_SOC_DAPM_MUX("ADC Right Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[1]), + + /* + * Put the codec into SLEEP mode when not in use, allowing the + * Energysense mechanism to operate. + */ + SND_SOC_DAPM_ADC("ADC", "HiFi Capture", PCM186X_POWER_CTRL, 1, 1), +}; + +static const struct snd_soc_dapm_widget pcm1865_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("VINL1"), + SND_SOC_DAPM_INPUT("VINR1"), + SND_SOC_DAPM_INPUT("VINL2"), + SND_SOC_DAPM_INPUT("VINR2"), + SND_SOC_DAPM_INPUT("VINL3"), + SND_SOC_DAPM_INPUT("VINR3"), + SND_SOC_DAPM_INPUT("VINL4"), + SND_SOC_DAPM_INPUT("VINR4"), + + SND_SOC_DAPM_MUX("ADC1 Left Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[0]), + SND_SOC_DAPM_MUX("ADC1 Right Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[1]), + SND_SOC_DAPM_MUX("ADC2 Left Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[2]), + SND_SOC_DAPM_MUX("ADC2 Right Capture Source", SND_SOC_NOPM, 0, 0, + &pcm186x_adc_mux_controls[3]), + + /* + * Put the codec into SLEEP mode when not in use, allowing the + * Energysense mechanism to operate. + */ + SND_SOC_DAPM_ADC("ADC1", "HiFi Capture 1", PCM186X_POWER_CTRL, 1, 1), + SND_SOC_DAPM_ADC("ADC2", "HiFi Capture 2", PCM186X_POWER_CTRL, 1, 1), +}; + +static const struct snd_soc_dapm_route pcm1863_dapm_routes[] = { + { "ADC Left Capture Source", NULL, "VINL1" }, + { "ADC Left Capture Source", NULL, "VINR1" }, + { "ADC Left Capture Source", NULL, "VINL2" }, + { "ADC Left Capture Source", NULL, "VINR2" }, + { "ADC Left Capture Source", NULL, "VINL3" }, + { "ADC Left Capture Source", NULL, "VINR3" }, + { "ADC Left Capture Source", NULL, "VINL4" }, + { "ADC Left Capture Source", NULL, "VINR4" }, + + { "ADC", NULL, "ADC Left Capture Source" }, + + { "ADC Right Capture Source", NULL, "VINL1" }, + { "ADC Right Capture Source", NULL, "VINR1" }, + { "ADC Right Capture Source", NULL, "VINL2" }, + { "ADC Right Capture Source", NULL, "VINR2" }, + { "ADC Right Capture Source", NULL, "VINL3" }, + { "ADC Right Capture Source", NULL, "VINR3" }, + { "ADC Right Capture Source", NULL, "VINL4" }, + { "ADC Right Capture Source", NULL, "VINR4" }, + + { "ADC", NULL, "ADC Right Capture Source" }, +}; + +static const struct snd_soc_dapm_route pcm1865_dapm_routes[] = { + { "ADC1 Left Capture Source", NULL, "VINL1" }, + { "ADC1 Left Capture Source", NULL, "VINR1" }, + { "ADC1 Left Capture Source", NULL, "VINL2" }, + { "ADC1 Left Capture Source", NULL, "VINR2" }, + { "ADC1 Left Capture Source", NULL, "VINL3" }, + { "ADC1 Left Capture Source", NULL, "VINR3" }, + { "ADC1 Left Capture Source", NULL, "VINL4" }, + { "ADC1 Left Capture Source", NULL, "VINR4" }, + + { "ADC1", NULL, "ADC1 Left Capture Source" }, + + { "ADC1 Right Capture Source", NULL, "VINL1" }, + { "ADC1 Right Capture Source", NULL, "VINR1" }, + { "ADC1 Right Capture Source", NULL, "VINL2" }, + { "ADC1 Right Capture Source", NULL, "VINR2" }, + { "ADC1 Right Capture Source", NULL, "VINL3" }, + { "ADC1 Right Capture Source", NULL, "VINR3" }, + { "ADC1 Right Capture Source", NULL, "VINL4" }, + { "ADC1 Right Capture Source", NULL, "VINR4" }, + + { "ADC1", NULL, "ADC1 Right Capture Source" }, + + { "ADC2 Left Capture Source", NULL, "VINL1" }, + { "ADC2 Left Capture Source", NULL, "VINR1" }, + { "ADC2 Left Capture Source", NULL, "VINL2" }, + { "ADC2 Left Capture Source", NULL, "VINR2" }, + { "ADC2 Left Capture Source", NULL, "VINL3" }, + { "ADC2 Left Capture Source", NULL, "VINR3" }, + { "ADC2 Left Capture Source", NULL, "VINL4" }, + { "ADC2 Left Capture Source", NULL, "VINR4" }, + + { "ADC2", NULL, "ADC2 Left Capture Source" }, + + { "ADC2 Right Capture Source", NULL, "VINL1" }, + { "ADC2 Right Capture Source", NULL, "VINR1" }, + { "ADC2 Right Capture Source", NULL, "VINL2" }, + { "ADC2 Right Capture Source", NULL, "VINR2" }, + { "ADC2 Right Capture Source", NULL, "VINL3" }, + { "ADC2 Right Capture Source", NULL, "VINR3" }, + { "ADC2 Right Capture Source", NULL, "VINL4" }, + { "ADC2 Right Capture Source", NULL, "VINR4" }, + + { "ADC2", NULL, "ADC2 Right Capture Source" }, +}; + +static int pcm186x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm186x_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int rate = params_rate(params); + snd_pcm_format_t format = params_format(params); + unsigned int width = params_width(params); + unsigned int channels = params_channels(params); + unsigned int div_lrck; + unsigned int div_bck; + u8 tdm_tx_sel = 0; + u8 pcm_cfg = 0; + + dev_dbg(component->dev, "%s() rate=%u format=0x%x width=%u channels=%u\n", + __func__, rate, format, width, channels); + + switch (width) { + case 16: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_16 << + PCM186X_PCM_CFG_RX_WLEN_SHIFT | + PCM186X_PCM_CFG_TX_WLEN_16 << + PCM186X_PCM_CFG_TX_WLEN_SHIFT; + break; + case 20: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_20 << + PCM186X_PCM_CFG_RX_WLEN_SHIFT | + PCM186X_PCM_CFG_TX_WLEN_20 << + PCM186X_PCM_CFG_TX_WLEN_SHIFT; + break; + case 24: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_24 << + PCM186X_PCM_CFG_RX_WLEN_SHIFT | + PCM186X_PCM_CFG_TX_WLEN_24 << + PCM186X_PCM_CFG_TX_WLEN_SHIFT; + break; + case 32: + pcm_cfg = PCM186X_PCM_CFG_RX_WLEN_32 << + PCM186X_PCM_CFG_RX_WLEN_SHIFT | + PCM186X_PCM_CFG_TX_WLEN_32 << + PCM186X_PCM_CFG_TX_WLEN_SHIFT; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, PCM186X_PCM_CFG, + PCM186X_PCM_CFG_RX_WLEN_MASK | + PCM186X_PCM_CFG_TX_WLEN_MASK, + pcm_cfg); + + div_lrck = width * channels; + + if (priv->is_tdm_mode) { + /* Select TDM transmission data */ + switch (channels) { + case 2: + tdm_tx_sel = PCM186X_TDM_TX_SEL_2CH; + break; + case 4: + tdm_tx_sel = PCM186X_TDM_TX_SEL_4CH; + break; + case 6: + tdm_tx_sel = PCM186X_TDM_TX_SEL_6CH; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, PCM186X_TDM_TX_SEL, + PCM186X_TDM_TX_SEL_MASK, tdm_tx_sel); + + /* In DSP/TDM mode, the LRCLK divider must be 256 */ + div_lrck = 256; + + /* Configure 1/256 duty cycle for LRCK */ + snd_soc_component_update_bits(component, PCM186X_PCM_CFG, + PCM186X_PCM_CFG_TDM_LRCK_MODE, + PCM186X_PCM_CFG_TDM_LRCK_MODE); + } + + /* Only configure clock dividers in master mode. */ + if (priv->is_master_mode) { + div_bck = priv->sysclk / (div_lrck * rate); + + dev_dbg(component->dev, + "%s() master_clk=%u div_bck=%u div_lrck=%u\n", + __func__, priv->sysclk, div_bck, div_lrck); + + snd_soc_component_write(component, PCM186X_BCK_DIV, div_bck - 1); + snd_soc_component_write(component, PCM186X_LRK_DIV, div_lrck - 1); + } + + return 0; +} + +static int pcm186x_set_fmt(struct snd_soc_dai *dai, unsigned int format) +{ + struct snd_soc_component *component = dai->component; + struct pcm186x_priv *priv = snd_soc_component_get_drvdata(component); + u8 clk_ctrl = 0; + u8 pcm_cfg = 0; + + dev_dbg(component->dev, "%s() format=0x%x\n", __func__, format); + + /* set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + if (!priv->sysclk) { + dev_err(component->dev, "operating in master mode requires sysclock to be configured\n"); + return -EINVAL; + } + clk_ctrl |= PCM186X_CLK_CTRL_MST_MODE; + priv->is_master_mode = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + priv->is_master_mode = false; + break; + default: + dev_err(component->dev, "Invalid DAI master/slave interface\n"); + return -EINVAL; + } + + /* set interface polarity */ + switch (format & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + dev_err(component->dev, "Inverted DAI clocks not supported\n"); + return -EINVAL; + } + + /* set interface format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + pcm_cfg = PCM186X_PCM_CFG_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + pcm_cfg = PCM186X_PCM_CFG_FMT_LEFTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + priv->tdm_offset += 1; + /* Fall through... DSP_A uses the same basic config as DSP_B + * except we need to shift the TDM output by one BCK cycle + */ + case SND_SOC_DAIFMT_DSP_B: + priv->is_tdm_mode = true; + pcm_cfg = PCM186X_PCM_CFG_FMT_TDM; + break; + default: + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, PCM186X_CLK_CTRL, + PCM186X_CLK_CTRL_MST_MODE, clk_ctrl); + + snd_soc_component_write(component, PCM186X_TDM_TX_OFFSET, priv->tdm_offset); + + snd_soc_component_update_bits(component, PCM186X_PCM_CFG, + PCM186X_PCM_CFG_FMT_MASK, pcm_cfg); + + return 0; +} + +static int pcm186x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct pcm186x_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int first_slot, last_slot, tdm_offset; + + dev_dbg(component->dev, + "%s() tx_mask=0x%x rx_mask=0x%x slots=%d slot_width=%d\n", + __func__, tx_mask, rx_mask, slots, slot_width); + + if (!tx_mask) { + dev_err(component->dev, "tdm tx mask must not be 0\n"); + return -EINVAL; + } + + first_slot = __ffs(tx_mask); + last_slot = __fls(tx_mask); + + if (last_slot - first_slot != hweight32(tx_mask) - 1) { + dev_err(component->dev, "tdm tx mask must be contiguous\n"); + return -EINVAL; + } + + tdm_offset = first_slot * slot_width; + + if (tdm_offset > 255) { + dev_err(component->dev, "tdm tx slot selection out of bounds\n"); + return -EINVAL; + } + + priv->tdm_offset = tdm_offset; + + return 0; +} + +static int pcm186x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct pcm186x_priv *priv = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s() clk_id=%d freq=%u dir=%d\n", + __func__, clk_id, freq, dir); + + priv->sysclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops pcm186x_dai_ops = { + .set_sysclk = pcm186x_set_dai_sysclk, + .set_tdm_slot = pcm186x_set_tdm_slot, + .set_fmt = pcm186x_set_fmt, + .hw_params = pcm186x_hw_params, +}; + +static struct snd_soc_dai_driver pcm1863_dai = { + .name = "pcm1863-aif", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = PCM186X_RATES, + .formats = PCM186X_FORMATS, + }, + .ops = &pcm186x_dai_ops, +}; + +static struct snd_soc_dai_driver pcm1865_dai = { + .name = "pcm1865-aif", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = PCM186X_RATES, + .formats = PCM186X_FORMATS, + }, + .ops = &pcm186x_dai_ops, +}; + +static int pcm186x_power_on(struct snd_soc_component *component) +{ + struct pcm186x_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) + return ret; + + regcache_cache_only(priv->regmap, false); + ret = regcache_sync(priv->regmap); + if (ret) { + dev_err(component->dev, "Failed to restore cache\n"); + regcache_cache_only(priv->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + return ret; + } + + snd_soc_component_update_bits(component, PCM186X_POWER_CTRL, + PCM186X_PWR_CTRL_PWRDN, 0); + + return 0; +} + +static int pcm186x_power_off(struct snd_soc_component *component) +{ + struct pcm186x_priv *priv = snd_soc_component_get_drvdata(component); + int ret; + + snd_soc_component_update_bits(component, PCM186X_POWER_CTRL, + PCM186X_PWR_CTRL_PWRDN, PCM186X_PWR_CTRL_PWRDN); + + regcache_cache_only(priv->regmap, true); + + ret = regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) + return ret; + + return 0; +} + +static int pcm186x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + dev_dbg(component->dev, "## %s: %d -> %d\n", __func__, + snd_soc_component_get_bias_level(component), level); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + pcm186x_power_on(component); + break; + case SND_SOC_BIAS_OFF: + pcm186x_power_off(component); + break; + } + + return 0; +} + +static struct snd_soc_component_driver soc_codec_dev_pcm1863 = { + .set_bias_level = pcm186x_set_bias_level, + .controls = pcm1863_snd_controls, + .num_controls = ARRAY_SIZE(pcm1863_snd_controls), + .dapm_widgets = pcm1863_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm1863_dapm_widgets), + .dapm_routes = pcm1863_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm1863_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_component_driver soc_codec_dev_pcm1865 = { + .set_bias_level = pcm186x_set_bias_level, + .controls = pcm1865_snd_controls, + .num_controls = ARRAY_SIZE(pcm1865_snd_controls), + .dapm_widgets = pcm1865_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm1865_dapm_widgets), + .dapm_routes = pcm1865_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm1865_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static bool pcm186x_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PCM186X_PAGE: + case PCM186X_DEVICE_STATUS: + case PCM186X_FSAMPLE_STATUS: + case PCM186X_DIV_STATUS: + case PCM186X_CLK_STATUS: + case PCM186X_SUPPLY_STATUS: + case PCM186X_MMAP_STAT_CTRL: + case PCM186X_MMAP_ADDRESS: + return true; + } + + return false; +} + +static const struct regmap_range_cfg pcm186x_range = { + .name = "Pages", + .range_max = PCM186X_MAX_REGISTER, + .selector_reg = PCM186X_PAGE, + .selector_mask = 0xff, + .window_len = PCM186X_PAGE_LEN, +}; + +const struct regmap_config pcm186x_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .volatile_reg = pcm186x_volatile, + + .ranges = &pcm186x_range, + .num_ranges = 1, + + .max_register = PCM186X_MAX_REGISTER, + + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(pcm186x_regmap); + +int pcm186x_probe(struct device *dev, enum pcm186x_type type, int irq, + struct regmap *regmap) +{ + struct pcm186x_priv *priv; + int i, ret; + + priv = devm_kzalloc(dev, sizeof(struct pcm186x_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + priv->regmap = regmap; + + for (i = 0; i < ARRAY_SIZE(priv->supplies); i++) + priv->supplies[i].supply = pcm186x_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) { + dev_err(dev, "failed enable supplies: %d\n", ret); + return ret; + } + + /* Reset device registers for a consistent power-on like state */ + ret = regmap_write(regmap, PCM186X_PAGE, PCM186X_RESET); + if (ret) { + dev_err(dev, "failed to write device: %d\n", ret); + return ret; + } + + ret = regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret) { + dev_err(dev, "failed disable supplies: %d\n", ret); + return ret; + } + + switch (type) { + case PCM1865: + case PCM1864: + ret = devm_snd_soc_register_component(dev, &soc_codec_dev_pcm1865, + &pcm1865_dai, 1); + break; + case PCM1863: + case PCM1862: + default: + ret = devm_snd_soc_register_component(dev, &soc_codec_dev_pcm1863, + &pcm1863_dai, 1); + } + if (ret) { + dev_err(dev, "failed to register CODEC: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(pcm186x_probe); + +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("PCM186x Universal Audio ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm186x.h b/sound/soc/codecs/pcm186x.h new file mode 100644 index 000000000..bb3f0c42a --- /dev/null +++ b/sound/soc/codecs/pcm186x.h @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments PCM186x Universal Audio ADC + * + * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com + * Andreas Dannenberg + * Andrew F. Davis + */ + +#ifndef _PCM186X_H_ +#define _PCM186X_H_ + +#include +#include + +enum pcm186x_type { + PCM1862, + PCM1863, + PCM1864, + PCM1865, +}; + +#define PCM186X_RATES SNDRV_PCM_RATE_8000_192000 +#define PCM186X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define PCM186X_PAGE_LEN 0x0100 +#define PCM186X_PAGE_BASE(n) (PCM186X_PAGE_LEN * n) + +/* The page selection register address is the same on all pages */ +#define PCM186X_PAGE 0 + +/* Register Definitions - Page 0 */ +#define PCM186X_PGA_VAL_CH1_L (PCM186X_PAGE_BASE(0) + 1) +#define PCM186X_PGA_VAL_CH1_R (PCM186X_PAGE_BASE(0) + 2) +#define PCM186X_PGA_VAL_CH2_L (PCM186X_PAGE_BASE(0) + 3) +#define PCM186X_PGA_VAL_CH2_R (PCM186X_PAGE_BASE(0) + 4) +#define PCM186X_PGA_CTRL (PCM186X_PAGE_BASE(0) + 5) +#define PCM186X_ADC1_INPUT_SEL_L (PCM186X_PAGE_BASE(0) + 6) +#define PCM186X_ADC1_INPUT_SEL_R (PCM186X_PAGE_BASE(0) + 7) +#define PCM186X_ADC2_INPUT_SEL_L (PCM186X_PAGE_BASE(0) + 8) +#define PCM186X_ADC2_INPUT_SEL_R (PCM186X_PAGE_BASE(0) + 9) +#define PCM186X_AUXADC_INPUT_SEL (PCM186X_PAGE_BASE(0) + 10) +#define PCM186X_PCM_CFG (PCM186X_PAGE_BASE(0) + 11) +#define PCM186X_TDM_TX_SEL (PCM186X_PAGE_BASE(0) + 12) +#define PCM186X_TDM_TX_OFFSET (PCM186X_PAGE_BASE(0) + 13) +#define PCM186X_TDM_RX_OFFSET (PCM186X_PAGE_BASE(0) + 14) +#define PCM186X_DPGA_VAL_CH1_L (PCM186X_PAGE_BASE(0) + 15) +#define PCM186X_GPIO1_0_CTRL (PCM186X_PAGE_BASE(0) + 16) +#define PCM186X_GPIO3_2_CTRL (PCM186X_PAGE_BASE(0) + 17) +#define PCM186X_GPIO1_0_DIR_CTRL (PCM186X_PAGE_BASE(0) + 18) +#define PCM186X_GPIO3_2_DIR_CTRL (PCM186X_PAGE_BASE(0) + 19) +#define PCM186X_GPIO_IN_OUT (PCM186X_PAGE_BASE(0) + 20) +#define PCM186X_GPIO_PULL_CTRL (PCM186X_PAGE_BASE(0) + 21) +#define PCM186X_DPGA_VAL_CH1_R (PCM186X_PAGE_BASE(0) + 22) +#define PCM186X_DPGA_VAL_CH2_L (PCM186X_PAGE_BASE(0) + 23) +#define PCM186X_DPGA_VAL_CH2_R (PCM186X_PAGE_BASE(0) + 24) +#define PCM186X_DPGA_GAIN_CTRL (PCM186X_PAGE_BASE(0) + 25) +#define PCM186X_DPGA_MIC_CTRL (PCM186X_PAGE_BASE(0) + 26) +#define PCM186X_DIN_RESAMP_CTRL (PCM186X_PAGE_BASE(0) + 27) +#define PCM186X_CLK_CTRL (PCM186X_PAGE_BASE(0) + 32) +#define PCM186X_DSP1_CLK_DIV (PCM186X_PAGE_BASE(0) + 33) +#define PCM186X_DSP2_CLK_DIV (PCM186X_PAGE_BASE(0) + 34) +#define PCM186X_ADC_CLK_DIV (PCM186X_PAGE_BASE(0) + 35) +#define PCM186X_PLL_SCK_DIV (PCM186X_PAGE_BASE(0) + 37) +#define PCM186X_BCK_DIV (PCM186X_PAGE_BASE(0) + 38) +#define PCM186X_LRK_DIV (PCM186X_PAGE_BASE(0) + 39) +#define PCM186X_PLL_CTRL (PCM186X_PAGE_BASE(0) + 40) +#define PCM186X_PLL_P_DIV (PCM186X_PAGE_BASE(0) + 41) +#define PCM186X_PLL_R_DIV (PCM186X_PAGE_BASE(0) + 42) +#define PCM186X_PLL_J_DIV (PCM186X_PAGE_BASE(0) + 43) +#define PCM186X_PLL_D_DIV_LSB (PCM186X_PAGE_BASE(0) + 44) +#define PCM186X_PLL_D_DIV_MSB (PCM186X_PAGE_BASE(0) + 45) +#define PCM186X_SIGDET_MODE (PCM186X_PAGE_BASE(0) + 48) +#define PCM186X_SIGDET_MASK (PCM186X_PAGE_BASE(0) + 49) +#define PCM186X_SIGDET_STAT (PCM186X_PAGE_BASE(0) + 50) +#define PCM186X_SIGDET_LOSS_TIME (PCM186X_PAGE_BASE(0) + 52) +#define PCM186X_SIGDET_SCAN_TIME (PCM186X_PAGE_BASE(0) + 53) +#define PCM186X_SIGDET_INT_INTVL (PCM186X_PAGE_BASE(0) + 54) +#define PCM186X_SIGDET_DC_REF_CH1_L (PCM186X_PAGE_BASE(0) + 64) +#define PCM186X_SIGDET_DC_DIFF_CH1_L (PCM186X_PAGE_BASE(0) + 65) +#define PCM186X_SIGDET_DC_LEV_CH1_L (PCM186X_PAGE_BASE(0) + 66) +#define PCM186X_SIGDET_DC_REF_CH1_R (PCM186X_PAGE_BASE(0) + 67) +#define PCM186X_SIGDET_DC_DIFF_CH1_R (PCM186X_PAGE_BASE(0) + 68) +#define PCM186X_SIGDET_DC_LEV_CH1_R (PCM186X_PAGE_BASE(0) + 69) +#define PCM186X_SIGDET_DC_REF_CH2_L (PCM186X_PAGE_BASE(0) + 70) +#define PCM186X_SIGDET_DC_DIFF_CH2_L (PCM186X_PAGE_BASE(0) + 71) +#define PCM186X_SIGDET_DC_LEV_CH2_L (PCM186X_PAGE_BASE(0) + 72) +#define PCM186X_SIGDET_DC_REF_CH2_R (PCM186X_PAGE_BASE(0) + 73) +#define PCM186X_SIGDET_DC_DIFF_CH2_R (PCM186X_PAGE_BASE(0) + 74) +#define PCM186X_SIGDET_DC_LEV_CH2_R (PCM186X_PAGE_BASE(0) + 75) +#define PCM186X_SIGDET_DC_REF_CH3_L (PCM186X_PAGE_BASE(0) + 76) +#define PCM186X_SIGDET_DC_DIFF_CH3_L (PCM186X_PAGE_BASE(0) + 77) +#define PCM186X_SIGDET_DC_LEV_CH3_L (PCM186X_PAGE_BASE(0) + 78) +#define PCM186X_SIGDET_DC_REF_CH3_R (PCM186X_PAGE_BASE(0) + 79) +#define PCM186X_SIGDET_DC_DIFF_CH3_R (PCM186X_PAGE_BASE(0) + 80) +#define PCM186X_SIGDET_DC_LEV_CH3_R (PCM186X_PAGE_BASE(0) + 81) +#define PCM186X_SIGDET_DC_REF_CH4_L (PCM186X_PAGE_BASE(0) + 82) +#define PCM186X_SIGDET_DC_DIFF_CH4_L (PCM186X_PAGE_BASE(0) + 83) +#define PCM186X_SIGDET_DC_LEV_CH4_L (PCM186X_PAGE_BASE(0) + 84) +#define PCM186X_SIGDET_DC_REF_CH4_R (PCM186X_PAGE_BASE(0) + 85) +#define PCM186X_SIGDET_DC_DIFF_CH4_R (PCM186X_PAGE_BASE(0) + 86) +#define PCM186X_SIGDET_DC_LEV_CH4_R (PCM186X_PAGE_BASE(0) + 87) +#define PCM186X_AUXADC_DATA_CTRL (PCM186X_PAGE_BASE(0) + 88) +#define PCM186X_AUXADC_DATA_LSB (PCM186X_PAGE_BASE(0) + 89) +#define PCM186X_AUXADC_DATA_MSB (PCM186X_PAGE_BASE(0) + 90) +#define PCM186X_INT_ENABLE (PCM186X_PAGE_BASE(0) + 96) +#define PCM186X_INT_FLAG (PCM186X_PAGE_BASE(0) + 97) +#define PCM186X_INT_POL_WIDTH (PCM186X_PAGE_BASE(0) + 98) +#define PCM186X_POWER_CTRL (PCM186X_PAGE_BASE(0) + 112) +#define PCM186X_FILTER_MUTE_CTRL (PCM186X_PAGE_BASE(0) + 113) +#define PCM186X_DEVICE_STATUS (PCM186X_PAGE_BASE(0) + 114) +#define PCM186X_FSAMPLE_STATUS (PCM186X_PAGE_BASE(0) + 115) +#define PCM186X_DIV_STATUS (PCM186X_PAGE_BASE(0) + 116) +#define PCM186X_CLK_STATUS (PCM186X_PAGE_BASE(0) + 117) +#define PCM186X_SUPPLY_STATUS (PCM186X_PAGE_BASE(0) + 120) + +/* Register Definitions - Page 1 */ +#define PCM186X_MMAP_STAT_CTRL (PCM186X_PAGE_BASE(1) + 1) +#define PCM186X_MMAP_ADDRESS (PCM186X_PAGE_BASE(1) + 2) +#define PCM186X_MEM_WDATA0 (PCM186X_PAGE_BASE(1) + 4) +#define PCM186X_MEM_WDATA1 (PCM186X_PAGE_BASE(1) + 5) +#define PCM186X_MEM_WDATA2 (PCM186X_PAGE_BASE(1) + 6) +#define PCM186X_MEM_WDATA3 (PCM186X_PAGE_BASE(1) + 7) +#define PCM186X_MEM_RDATA0 (PCM186X_PAGE_BASE(1) + 8) +#define PCM186X_MEM_RDATA1 (PCM186X_PAGE_BASE(1) + 9) +#define PCM186X_MEM_RDATA2 (PCM186X_PAGE_BASE(1) + 10) +#define PCM186X_MEM_RDATA3 (PCM186X_PAGE_BASE(1) + 11) + +/* Register Definitions - Page 3 */ +#define PCM186X_OSC_PWR_DOWN_CTRL (PCM186X_PAGE_BASE(3) + 18) +#define PCM186X_MIC_BIAS_CTRL (PCM186X_PAGE_BASE(3) + 21) + +/* Register Definitions - Page 253 */ +#define PCM186X_CURR_TRIM_CTRL (PCM186X_PAGE_BASE(253) + 20) + +#define PCM186X_MAX_REGISTER PCM186X_CURR_TRIM_CTRL + +/* PCM186X_PAGE */ +#define PCM186X_RESET 0xfe + +/* PCM186X_ADCX_INPUT_SEL_X */ +#define PCM186X_ADC_INPUT_SEL_POL BIT(7) +#define PCM186X_ADC_INPUT_SEL_MASK GENMASK(5, 0) + +/* PCM186X_PCM_CFG */ +#define PCM186X_PCM_CFG_RX_WLEN_MASK GENMASK(7, 6) +#define PCM186X_PCM_CFG_RX_WLEN_SHIFT 6 +#define PCM186X_PCM_CFG_RX_WLEN_32 0x00 +#define PCM186X_PCM_CFG_RX_WLEN_24 0x01 +#define PCM186X_PCM_CFG_RX_WLEN_20 0x02 +#define PCM186X_PCM_CFG_RX_WLEN_16 0x03 +#define PCM186X_PCM_CFG_TDM_LRCK_MODE BIT(4) +#define PCM186X_PCM_CFG_TX_WLEN_MASK GENMASK(3, 2) +#define PCM186X_PCM_CFG_TX_WLEN_SHIFT 2 +#define PCM186X_PCM_CFG_TX_WLEN_32 0x00 +#define PCM186X_PCM_CFG_TX_WLEN_24 0x01 +#define PCM186X_PCM_CFG_TX_WLEN_20 0x02 +#define PCM186X_PCM_CFG_TX_WLEN_16 0x03 +#define PCM186X_PCM_CFG_FMT_MASK GENMASK(1, 0) +#define PCM186X_PCM_CFG_FMT_SHIFT 0 +#define PCM186X_PCM_CFG_FMT_I2S 0x00 +#define PCM186X_PCM_CFG_FMT_LEFTJ 0x01 +#define PCM186X_PCM_CFG_FMT_RIGHTJ 0x02 +#define PCM186X_PCM_CFG_FMT_TDM 0x03 + +/* PCM186X_TDM_TX_SEL */ +#define PCM186X_TDM_TX_SEL_2CH 0x00 +#define PCM186X_TDM_TX_SEL_4CH 0x01 +#define PCM186X_TDM_TX_SEL_6CH 0x02 +#define PCM186X_TDM_TX_SEL_MASK 0x03 + +/* PCM186X_CLK_CTRL */ +#define PCM186X_CLK_CTRL_SCK_XI_SEL1 BIT(7) +#define PCM186X_CLK_CTRL_SCK_XI_SEL0 BIT(6) +#define PCM186X_CLK_CTRL_SCK_SRC_PLL BIT(5) +#define PCM186X_CLK_CTRL_MST_MODE BIT(4) +#define PCM186X_CLK_CTRL_ADC_SRC_PLL BIT(3) +#define PCM186X_CLK_CTRL_DSP2_SRC_PLL BIT(2) +#define PCM186X_CLK_CTRL_DSP1_SRC_PLL BIT(1) +#define PCM186X_CLK_CTRL_CLKDET_EN BIT(0) + +/* PCM186X_PLL_CTRL */ +#define PCM186X_PLL_CTRL_LOCK BIT(4) +#define PCM186X_PLL_CTRL_REF_SEL BIT(1) +#define PCM186X_PLL_CTRL_EN BIT(0) + +/* PCM186X_POWER_CTRL */ +#define PCM186X_PWR_CTRL_PWRDN BIT(2) +#define PCM186X_PWR_CTRL_SLEEP BIT(1) +#define PCM186X_PWR_CTRL_STBY BIT(0) + +/* PCM186X_CLK_STATUS */ +#define PCM186X_CLK_STATUS_LRCKHLT BIT(6) +#define PCM186X_CLK_STATUS_BCKHLT BIT(5) +#define PCM186X_CLK_STATUS_SCKHLT BIT(4) +#define PCM186X_CLK_STATUS_LRCKERR BIT(2) +#define PCM186X_CLK_STATUS_BCKERR BIT(1) +#define PCM186X_CLK_STATUS_SCKERR BIT(0) + +/* PCM186X_SUPPLY_STATUS */ +#define PCM186X_SUPPLY_STATUS_DVDD BIT(2) +#define PCM186X_SUPPLY_STATUS_AVDD BIT(1) +#define PCM186X_SUPPLY_STATUS_LDO BIT(0) + +/* PCM186X_MMAP_STAT_CTRL */ +#define PCM186X_MMAP_STAT_DONE BIT(4) +#define PCM186X_MMAP_STAT_BUSY BIT(2) +#define PCM186X_MMAP_STAT_R_REQ BIT(1) +#define PCM186X_MMAP_STAT_W_REQ BIT(0) + +extern const struct regmap_config pcm186x_regmap; + +int pcm186x_probe(struct device *dev, enum pcm186x_type type, int irq, + struct regmap *regmap); + +#endif /* _PCM186X_H_ */ diff --git a/sound/soc/codecs/pcm3008.c b/sound/soc/codecs/pcm3008.c new file mode 100644 index 000000000..c6ce9bd77 --- /dev/null +++ b/sound/soc/codecs/pcm3008.c @@ -0,0 +1,168 @@ +/* + * ALSA Soc PCM3008 codec support + * + * Author: Hugo Villeneuve + * Copyright (C) 2008 Lyrtech inc + * + * Based on AC97 Soc codec, original copyright follow: + * Copyright 2005 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Generic PCM3008 support. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcm3008.h" + +static int pcm3008_dac_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct pcm3008_setup_data *setup = component->dev->platform_data; + + gpio_set_value_cansleep(setup->pdda_pin, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static int pcm3008_adc_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct pcm3008_setup_data *setup = component->dev->platform_data; + + gpio_set_value_cansleep(setup->pdad_pin, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget pcm3008_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("VINL"), +SND_SOC_DAPM_INPUT("VINR"), + +SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, pcm3008_dac_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0, pcm3008_adc_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_OUTPUT("VOUTL"), +SND_SOC_DAPM_OUTPUT("VOUTR"), +}; + +static const struct snd_soc_dapm_route pcm3008_dapm_routes[] = { + { "PCM3008 Capture", NULL, "ADC" }, + { "ADC", NULL, "VINL" }, + { "ADC", NULL, "VINR" }, + + { "DAC", NULL, "PCM3008 Playback" }, + { "VOUTL", NULL, "DAC" }, + { "VOUTR", NULL, "DAC" }, +}; + +#define PCM3008_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +static struct snd_soc_dai_driver pcm3008_dai = { + .name = "pcm3008-hifi", + .playback = { + .stream_name = "PCM3008 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = PCM3008_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "PCM3008 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = PCM3008_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_pcm3008 = { + .dapm_widgets = pcm3008_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm3008_dapm_widgets), + .dapm_routes = pcm3008_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm3008_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int pcm3008_codec_probe(struct platform_device *pdev) +{ + struct pcm3008_setup_data *setup = pdev->dev.platform_data; + int ret; + + if (!setup) + return -EINVAL; + + /* DEM1 DEM0 DE-EMPHASIS_MODE + * Low Low De-emphasis 44.1 kHz ON + * Low High De-emphasis OFF + * High Low De-emphasis 48 kHz ON + * High High De-emphasis 32 kHz ON + */ + + /* Configure DEM0 GPIO (turning OFF DAC De-emphasis). */ + ret = devm_gpio_request_one(&pdev->dev, setup->dem0_pin, + GPIOF_OUT_INIT_HIGH, "codec_dem0"); + if (ret != 0) + return ret; + + /* Configure DEM1 GPIO (turning OFF DAC De-emphasis). */ + ret = devm_gpio_request_one(&pdev->dev, setup->dem1_pin, + GPIOF_OUT_INIT_LOW, "codec_dem1"); + if (ret != 0) + return ret; + + /* Configure PDAD GPIO. */ + ret = devm_gpio_request_one(&pdev->dev, setup->pdad_pin, + GPIOF_OUT_INIT_LOW, "codec_pdad"); + if (ret != 0) + return ret; + + /* Configure PDDA GPIO. */ + ret = devm_gpio_request_one(&pdev->dev, setup->pdda_pin, + GPIOF_OUT_INIT_LOW, "codec_pdda"); + if (ret != 0) + return ret; + + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_pcm3008, &pcm3008_dai, 1); +} + +MODULE_ALIAS("platform:pcm3008-codec"); + +static struct platform_driver pcm3008_codec_driver = { + .probe = pcm3008_codec_probe, + .driver = { + .name = "pcm3008-codec", + }, +}; + +module_platform_driver(pcm3008_codec_driver); + +MODULE_DESCRIPTION("Soc PCM3008 driver"); +MODULE_AUTHOR("Hugo Villeneuve"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm3008.h b/sound/soc/codecs/pcm3008.h new file mode 100644 index 000000000..7e5489ab4 --- /dev/null +++ b/sound/soc/codecs/pcm3008.h @@ -0,0 +1,22 @@ +/* + * PCM3008 ALSA SoC Layer + * + * Author: Hugo Villeneuve + * Copyright (C) 2008 Lyrtech inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_SND_SOC_PCM3008_H +#define __LINUX_SND_SOC_PCM3008_H + +struct pcm3008_setup_data { + unsigned dem0_pin; + unsigned dem1_pin; + unsigned pdad_pin; + unsigned pdda_pin; +}; + +#endif diff --git a/sound/soc/codecs/pcm3168a-i2c.c b/sound/soc/codecs/pcm3168a-i2c.c new file mode 100644 index 000000000..6feb0901d --- /dev/null +++ b/sound/soc/codecs/pcm3168a-i2c.c @@ -0,0 +1,66 @@ +/* + * PCM3168A codec i2c driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include + +#include + +#include "pcm3168a.h" + +static int pcm3168a_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &pcm3168a_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm3168a_probe(&i2c->dev, regmap); +} + +static int pcm3168a_i2c_remove(struct i2c_client *i2c) +{ + pcm3168a_remove(&i2c->dev); + + return 0; +} + +static const struct i2c_device_id pcm3168a_i2c_id[] = { + { "pcm3168a", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm3168a_i2c_id); + +static const struct of_device_id pcm3168a_of_match[] = { + { .compatible = "ti,pcm3168a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm3168a_of_match); + +static struct i2c_driver pcm3168a_i2c_driver = { + .probe = pcm3168a_i2c_probe, + .remove = pcm3168a_i2c_remove, + .id_table = pcm3168a_i2c_id, + .driver = { + .name = "pcm3168a", + .of_match_table = pcm3168a_of_match, + .pm = &pcm3168a_pm_ops, + }, +}; +module_i2c_driver(pcm3168a_i2c_driver); + +MODULE_DESCRIPTION("PCM3168A I2C codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a-spi.c b/sound/soc/codecs/pcm3168a-spi.c new file mode 100644 index 000000000..03945a27a --- /dev/null +++ b/sound/soc/codecs/pcm3168a-spi.c @@ -0,0 +1,65 @@ +/* + * PCM3168A codec spi driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include + +#include + +#include "pcm3168a.h" + +static int pcm3168a_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &pcm3168a_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm3168a_probe(&spi->dev, regmap); +} + +static int pcm3168a_spi_remove(struct spi_device *spi) +{ + pcm3168a_remove(&spi->dev); + + return 0; +} + +static const struct spi_device_id pcm3168a_spi_id[] = { + { "pcm3168a", }, + { }, +}; +MODULE_DEVICE_TABLE(spi, pcm3168a_spi_id); + +static const struct of_device_id pcm3168a_of_match[] = { + { .compatible = "ti,pcm3168a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm3168a_of_match); + +static struct spi_driver pcm3168a_spi_driver = { + .probe = pcm3168a_spi_probe, + .remove = pcm3168a_spi_remove, + .id_table = pcm3168a_spi_id, + .driver = { + .name = "pcm3168a", + .of_match_table = pcm3168a_of_match, + .pm = &pcm3168a_pm_ops, + }, +}; +module_spi_driver(pcm3168a_spi_driver); + +MODULE_DESCRIPTION("PCM3168A SPI codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c new file mode 100644 index 000000000..439e40245 --- /dev/null +++ b/sound/soc/codecs/pcm3168a.c @@ -0,0 +1,773 @@ +/* + * PCM3168A codec driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pcm3168a.h" + +#define PCM3168A_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +#define PCM3168A_FMT_I2S 0x0 +#define PCM3168A_FMT_LEFT_J 0x1 +#define PCM3168A_FMT_RIGHT_J 0x2 +#define PCM3168A_FMT_RIGHT_J_16 0x3 +#define PCM3168A_FMT_DSP_A 0x4 +#define PCM3168A_FMT_DSP_B 0x5 +#define PCM3168A_FMT_DSP_MASK 0x4 + +#define PCM3168A_NUM_SUPPLIES 6 +static const char *const pcm3168a_supply_names[PCM3168A_NUM_SUPPLIES] = { + "VDD1", + "VDD2", + "VCCAD1", + "VCCAD2", + "VCCDA1", + "VCCDA2" +}; + +struct pcm3168a_priv { + struct regulator_bulk_data supplies[PCM3168A_NUM_SUPPLIES]; + struct regmap *regmap; + struct clk *scki; + bool adc_master_mode; + bool dac_master_mode; + unsigned long sysclk; + unsigned int adc_fmt; + unsigned int dac_fmt; +}; + +static const char *const pcm3168a_roll_off[] = { "Sharp", "Slow" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_d1_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT, pcm3168a_roll_off); +static SOC_ENUM_SINGLE_DECL(pcm3168a_d2_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT + 1, pcm3168a_roll_off); +static SOC_ENUM_SINGLE_DECL(pcm3168a_d3_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT + 2, pcm3168a_roll_off); +static SOC_ENUM_SINGLE_DECL(pcm3168a_d4_roll_off, PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_FLT_SHIFT + 3, pcm3168a_roll_off); + +static const char *const pcm3168a_volume_type[] = { + "Individual", "Master + Individual" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_volume_type, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_ATMDDA_SHIFT, pcm3168a_volume_type); + +static const char *const pcm3168a_att_speed_mult[] = { "2048", "4096" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_att_mult, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_att_speed_mult); + +static const char *const pcm3168a_demp[] = { + "Disabled", "48khz", "44.1khz", "32khz" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_demp, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_DEMP_SHIFT, pcm3168a_demp); + +static const char *const pcm3168a_zf_func[] = { + "DAC 1/2/3/4 AND", "DAC 1/2/3/4 OR", "DAC 1/2/3 AND", + "DAC 1/2/3 OR", "DAC 4 AND", "DAC 4 OR" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_func, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_AZRO_SHIFT, pcm3168a_zf_func); + +static const char *const pcm3168a_pol[] = { "Active High", "Active Low" }; + +static SOC_ENUM_SINGLE_DECL(pcm3168a_dac_zf_pol, PCM3168A_DAC_ATT_DEMP_ZF, + PCM3168A_DAC_ATSPDA_SHIFT, pcm3168a_pol); + +static const char *const pcm3168a_con[] = { "Differential", "Single-Ended" }; + +static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc1_con, PCM3168A_ADC_SEAD, + 0, 1, pcm3168a_con); +static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc2_con, PCM3168A_ADC_SEAD, + 2, 3, pcm3168a_con); +static SOC_ENUM_DOUBLE_DECL(pcm3168a_adc3_con, PCM3168A_ADC_SEAD, + 4, 5, pcm3168a_con); + +static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_volume_type, PCM3168A_ADC_ATT_OVF, + PCM3168A_ADC_ATMDAD_SHIFT, pcm3168a_volume_type); + +static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_att_mult, PCM3168A_ADC_ATT_OVF, + PCM3168A_ADC_ATSPAD_SHIFT, pcm3168a_att_speed_mult); + +static SOC_ENUM_SINGLE_DECL(pcm3168a_adc_ov_pol, PCM3168A_ADC_ATT_OVF, + PCM3168A_ADC_OVFP_SHIFT, pcm3168a_pol); + +/* -100db to 0db, register values 0-54 cause mute */ +static const DECLARE_TLV_DB_SCALE(pcm3168a_dac_tlv, -10050, 50, 1); + +/* -100db to 20db, register values 0-14 cause mute */ +static const DECLARE_TLV_DB_SCALE(pcm3168a_adc_tlv, -10050, 50, 1); + +static const struct snd_kcontrol_new pcm3168a_snd_controls[] = { + SOC_SINGLE("DAC Power-Save Switch", PCM3168A_DAC_PWR_MST_FMT, + PCM3168A_DAC_PSMDA_SHIFT, 1, 1), + SOC_ENUM("DAC1 Digital Filter roll-off", pcm3168a_d1_roll_off), + SOC_ENUM("DAC2 Digital Filter roll-off", pcm3168a_d2_roll_off), + SOC_ENUM("DAC3 Digital Filter roll-off", pcm3168a_d3_roll_off), + SOC_ENUM("DAC4 Digital Filter roll-off", pcm3168a_d4_roll_off), + SOC_DOUBLE("DAC1 Invert Switch", PCM3168A_DAC_INV, 0, 1, 1, 0), + SOC_DOUBLE("DAC2 Invert Switch", PCM3168A_DAC_INV, 2, 3, 1, 0), + SOC_DOUBLE("DAC3 Invert Switch", PCM3168A_DAC_INV, 4, 5, 1, 0), + SOC_DOUBLE("DAC4 Invert Switch", PCM3168A_DAC_INV, 6, 7, 1, 0), + SOC_DOUBLE_STS("DAC1 Zero Flag", PCM3168A_DAC_ZERO, 0, 1, 1, 0), + SOC_DOUBLE_STS("DAC2 Zero Flag", PCM3168A_DAC_ZERO, 2, 3, 1, 0), + SOC_DOUBLE_STS("DAC3 Zero Flag", PCM3168A_DAC_ZERO, 4, 5, 1, 0), + SOC_DOUBLE_STS("DAC4 Zero Flag", PCM3168A_DAC_ZERO, 6, 7, 1, 0), + SOC_ENUM("DAC Volume Control Type", pcm3168a_dac_volume_type), + SOC_ENUM("DAC Volume Rate Multiplier", pcm3168a_dac_att_mult), + SOC_ENUM("DAC De-Emphasis", pcm3168a_dac_demp), + SOC_ENUM("DAC Zero Flag Function", pcm3168a_dac_zf_func), + SOC_ENUM("DAC Zero Flag Polarity", pcm3168a_dac_zf_pol), + SOC_SINGLE_RANGE_TLV("Master Playback Volume", + PCM3168A_DAC_VOL_MASTER, 0, 54, 255, 0, + pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC1 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START, + PCM3168A_DAC_VOL_CHAN_START + 1, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC2 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START + 2, + PCM3168A_DAC_VOL_CHAN_START + 3, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC3 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START + 4, + PCM3168A_DAC_VOL_CHAN_START + 5, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_DOUBLE_R_RANGE_TLV("DAC4 Playback Volume", + PCM3168A_DAC_VOL_CHAN_START + 6, + PCM3168A_DAC_VOL_CHAN_START + 7, + 0, 54, 255, 0, pcm3168a_dac_tlv), + SOC_SINGLE("ADC1 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_BYP_SHIFT, 1, 1), + SOC_SINGLE("ADC2 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_BYP_SHIFT + 1, 1, 1), + SOC_SINGLE("ADC3 High-Pass Filter Switch", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_BYP_SHIFT + 2, 1, 1), + SOC_ENUM("ADC1 Connection Type", pcm3168a_adc1_con), + SOC_ENUM("ADC2 Connection Type", pcm3168a_adc2_con), + SOC_ENUM("ADC3 Connection Type", pcm3168a_adc3_con), + SOC_DOUBLE("ADC1 Invert Switch", PCM3168A_ADC_INV, 0, 1, 1, 0), + SOC_DOUBLE("ADC2 Invert Switch", PCM3168A_ADC_INV, 2, 3, 1, 0), + SOC_DOUBLE("ADC3 Invert Switch", PCM3168A_ADC_INV, 4, 5, 1, 0), + SOC_DOUBLE("ADC1 Mute Switch", PCM3168A_ADC_MUTE, 0, 1, 1, 0), + SOC_DOUBLE("ADC2 Mute Switch", PCM3168A_ADC_MUTE, 2, 3, 1, 0), + SOC_DOUBLE("ADC3 Mute Switch", PCM3168A_ADC_MUTE, 4, 5, 1, 0), + SOC_DOUBLE_STS("ADC1 Overflow Flag", PCM3168A_ADC_OV, 0, 1, 1, 0), + SOC_DOUBLE_STS("ADC2 Overflow Flag", PCM3168A_ADC_OV, 2, 3, 1, 0), + SOC_DOUBLE_STS("ADC3 Overflow Flag", PCM3168A_ADC_OV, 4, 5, 1, 0), + SOC_ENUM("ADC Volume Control Type", pcm3168a_adc_volume_type), + SOC_ENUM("ADC Volume Rate Multiplier", pcm3168a_adc_att_mult), + SOC_ENUM("ADC Overflow Flag Polarity", pcm3168a_adc_ov_pol), + SOC_SINGLE_RANGE_TLV("Master Capture Volume", + PCM3168A_ADC_VOL_MASTER, 0, 14, 255, 0, + pcm3168a_adc_tlv), + SOC_DOUBLE_R_RANGE_TLV("ADC1 Capture Volume", + PCM3168A_ADC_VOL_CHAN_START, + PCM3168A_ADC_VOL_CHAN_START + 1, + 0, 14, 255, 0, pcm3168a_adc_tlv), + SOC_DOUBLE_R_RANGE_TLV("ADC2 Capture Volume", + PCM3168A_ADC_VOL_CHAN_START + 2, + PCM3168A_ADC_VOL_CHAN_START + 3, + 0, 14, 255, 0, pcm3168a_adc_tlv), + SOC_DOUBLE_R_RANGE_TLV("ADC3 Capture Volume", + PCM3168A_ADC_VOL_CHAN_START + 4, + PCM3168A_ADC_VOL_CHAN_START + 5, + 0, 14, 255, 0, pcm3168a_adc_tlv) +}; + +static const struct snd_soc_dapm_widget pcm3168a_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC1", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT + 1, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT + 2, 1), + SND_SOC_DAPM_DAC("DAC4", "Playback", PCM3168A_DAC_OP_FLT, + PCM3168A_DAC_OPEDA_SHIFT + 3, 1), + + SND_SOC_DAPM_OUTPUT("AOUT1L"), + SND_SOC_DAPM_OUTPUT("AOUT1R"), + SND_SOC_DAPM_OUTPUT("AOUT2L"), + SND_SOC_DAPM_OUTPUT("AOUT2R"), + SND_SOC_DAPM_OUTPUT("AOUT3L"), + SND_SOC_DAPM_OUTPUT("AOUT3R"), + SND_SOC_DAPM_OUTPUT("AOUT4L"), + SND_SOC_DAPM_OUTPUT("AOUT4R"), + + SND_SOC_DAPM_ADC("ADC1", "Capture", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_PSVAD_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2", "Capture", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_PSVAD_SHIFT + 1, 1), + SND_SOC_DAPM_ADC("ADC3", "Capture", PCM3168A_ADC_PWR_HPFB, + PCM3168A_ADC_PSVAD_SHIFT + 2, 1), + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R") +}; + +static const struct snd_soc_dapm_route pcm3168a_dapm_routes[] = { + /* Playback */ + { "AOUT1L", NULL, "DAC1" }, + { "AOUT1R", NULL, "DAC1" }, + + { "AOUT2L", NULL, "DAC2" }, + { "AOUT2R", NULL, "DAC2" }, + + { "AOUT3L", NULL, "DAC3" }, + { "AOUT3R", NULL, "DAC3" }, + + { "AOUT4L", NULL, "DAC4" }, + { "AOUT4R", NULL, "DAC4" }, + + /* Capture */ + { "ADC1", NULL, "AIN1L" }, + { "ADC1", NULL, "AIN1R" }, + + { "ADC2", NULL, "AIN2L" }, + { "ADC2", NULL, "AIN2R" }, + + { "ADC3", NULL, "AIN3L" }, + { "ADC3", NULL, "AIN3R" } +}; + +static unsigned int pcm3168a_scki_ratios[] = { + 768, + 512, + 384, + 256, + 192, + 128 +}; + +#define PCM3168A_NUM_SCKI_RATIOS_DAC ARRAY_SIZE(pcm3168a_scki_ratios) +#define PCM3168A_NUM_SCKI_RATIOS_ADC (ARRAY_SIZE(pcm3168a_scki_ratios) - 2) + +#define PCM1368A_MAX_SYSCLK 36864000 + +static int pcm3168a_reset(struct pcm3168a_priv *pcm3168a) +{ + int ret; + + ret = regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE, 0); + if (ret) + return ret; + + /* Internal reset is de-asserted after 3846 SCKI cycles */ + msleep(DIV_ROUND_UP(3846 * 1000, pcm3168a->sysclk)); + + return regmap_write(pcm3168a->regmap, PCM3168A_RST_SMODE, + PCM3168A_MRST_MASK | PCM3168A_SRST_MASK); +} + +static int pcm3168a_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component); + + regmap_write(pcm3168a->regmap, PCM3168A_DAC_MUTE, mute ? 0xff : 0); + + return 0; +} + +static int pcm3168a_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(dai->component); + int ret; + + if (freq > PCM1368A_MAX_SYSCLK) + return -EINVAL; + + ret = clk_set_rate(pcm3168a->scki, freq); + if (ret) + return ret; + + pcm3168a->sysclk = freq; + + return 0; +} + +static int pcm3168a_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int format, bool dac) +{ + struct snd_soc_component *component = dai->component; + struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component); + u32 fmt, reg, mask, shift; + bool master_mode; + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + fmt = PCM3168A_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_I2S: + fmt = PCM3168A_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + fmt = PCM3168A_FMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_DSP_A: + fmt = PCM3168A_FMT_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + fmt = PCM3168A_FMT_DSP_B; + break; + default: + dev_err(component->dev, "unsupported dai format\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + master_mode = false; + break; + case SND_SOC_DAIFMT_CBM_CFM: + master_mode = true; + break; + default: + dev_err(component->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + if (dac) { + reg = PCM3168A_DAC_PWR_MST_FMT; + mask = PCM3168A_DAC_FMT_MASK; + shift = PCM3168A_DAC_FMT_SHIFT; + pcm3168a->dac_master_mode = master_mode; + pcm3168a->dac_fmt = fmt; + } else { + reg = PCM3168A_ADC_MST_FMT; + mask = PCM3168A_ADC_FMTAD_MASK; + shift = PCM3168A_ADC_FMTAD_SHIFT; + pcm3168a->adc_master_mode = master_mode; + pcm3168a->adc_fmt = fmt; + } + + regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); + + return 0; +} + +static int pcm3168a_set_dai_fmt_dac(struct snd_soc_dai *dai, + unsigned int format) +{ + return pcm3168a_set_dai_fmt(dai, format, true); +} + +static int pcm3168a_set_dai_fmt_adc(struct snd_soc_dai *dai, + unsigned int format) +{ + return pcm3168a_set_dai_fmt(dai, format, false); +} + +static int pcm3168a_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm3168a_priv *pcm3168a = snd_soc_component_get_drvdata(component); + bool tx, master_mode; + u32 val, mask, shift, reg; + unsigned int rate, fmt, ratio, max_ratio; + int i, min_frame_size; + + rate = params_rate(params); + + ratio = pcm3168a->sysclk / rate; + + tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (tx) { + max_ratio = PCM3168A_NUM_SCKI_RATIOS_DAC; + reg = PCM3168A_DAC_PWR_MST_FMT; + mask = PCM3168A_DAC_MSDA_MASK; + shift = PCM3168A_DAC_MSDA_SHIFT; + master_mode = pcm3168a->dac_master_mode; + fmt = pcm3168a->dac_fmt; + } else { + max_ratio = PCM3168A_NUM_SCKI_RATIOS_ADC; + reg = PCM3168A_ADC_MST_FMT; + mask = PCM3168A_ADC_MSAD_MASK; + shift = PCM3168A_ADC_MSAD_SHIFT; + master_mode = pcm3168a->adc_master_mode; + fmt = pcm3168a->adc_fmt; + } + + for (i = 0; i < max_ratio; i++) { + if (pcm3168a_scki_ratios[i] == ratio) + break; + } + + if (i == max_ratio) { + dev_err(component->dev, "unsupported sysclk ratio\n"); + return -EINVAL; + } + + min_frame_size = params_width(params) * 2; + switch (min_frame_size) { + case 32: + if (master_mode || (fmt != PCM3168A_FMT_RIGHT_J)) { + dev_err(component->dev, "32-bit frames are supported only for slave mode using right justified\n"); + return -EINVAL; + } + fmt = PCM3168A_FMT_RIGHT_J_16; + break; + case 48: + if (master_mode || (fmt & PCM3168A_FMT_DSP_MASK)) { + dev_err(component->dev, "48-bit frames not supported in master mode, or slave mode using DSP\n"); + return -EINVAL; + } + break; + case 64: + break; + default: + dev_err(component->dev, "unsupported frame size: %d\n", min_frame_size); + return -EINVAL; + } + + if (master_mode) + val = ((i + 1) << shift); + else + val = 0; + + regmap_update_bits(pcm3168a->regmap, reg, mask, val); + + if (tx) { + mask = PCM3168A_DAC_FMT_MASK; + shift = PCM3168A_DAC_FMT_SHIFT; + } else { + mask = PCM3168A_ADC_FMTAD_MASK; + shift = PCM3168A_ADC_FMTAD_SHIFT; + } + + regmap_update_bits(pcm3168a->regmap, reg, mask, fmt << shift); + + return 0; +} + +static const struct snd_soc_dai_ops pcm3168a_dac_dai_ops = { + .set_fmt = pcm3168a_set_dai_fmt_dac, + .set_sysclk = pcm3168a_set_dai_sysclk, + .hw_params = pcm3168a_hw_params, + .digital_mute = pcm3168a_digital_mute +}; + +static const struct snd_soc_dai_ops pcm3168a_adc_dai_ops = { + .set_fmt = pcm3168a_set_dai_fmt_adc, + .set_sysclk = pcm3168a_set_dai_sysclk, + .hw_params = pcm3168a_hw_params +}; + +static struct snd_soc_dai_driver pcm3168a_dais[] = { + { + .name = "pcm3168a-dac", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = PCM3168A_FORMATS + }, + .ops = &pcm3168a_dac_dai_ops + }, + { + .name = "pcm3168a-adc", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = PCM3168A_FORMATS + }, + .ops = &pcm3168a_adc_dai_ops + }, +}; + +static const struct reg_default pcm3168a_reg_default[] = { + { PCM3168A_RST_SMODE, PCM3168A_MRST_MASK | PCM3168A_SRST_MASK }, + { PCM3168A_DAC_PWR_MST_FMT, 0x00 }, + { PCM3168A_DAC_OP_FLT, 0x00 }, + { PCM3168A_DAC_INV, 0x00 }, + { PCM3168A_DAC_MUTE, 0x00 }, + { PCM3168A_DAC_ZERO, 0x00 }, + { PCM3168A_DAC_ATT_DEMP_ZF, 0x00 }, + { PCM3168A_DAC_VOL_MASTER, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 1, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 2, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 3, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 4, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 5, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 6, 0xff }, + { PCM3168A_DAC_VOL_CHAN_START + 7, 0xff }, + { PCM3168A_ADC_SMODE, 0x00 }, + { PCM3168A_ADC_MST_FMT, 0x00 }, + { PCM3168A_ADC_PWR_HPFB, 0x00 }, + { PCM3168A_ADC_SEAD, 0x00 }, + { PCM3168A_ADC_INV, 0x00 }, + { PCM3168A_ADC_MUTE, 0x00 }, + { PCM3168A_ADC_OV, 0x00 }, + { PCM3168A_ADC_ATT_OVF, 0x00 }, + { PCM3168A_ADC_VOL_MASTER, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 1, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 2, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 3, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 4, 0xd3 }, + { PCM3168A_ADC_VOL_CHAN_START + 5, 0xd3 } +}; + +static bool pcm3168a_readable_register(struct device *dev, unsigned int reg) +{ + if (reg >= PCM3168A_RST_SMODE) + return true; + else + return false; +} + +static bool pcm3168a_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PCM3168A_DAC_ZERO: + case PCM3168A_ADC_OV: + return true; + default: + return false; + } +} + +static bool pcm3168a_writeable_register(struct device *dev, unsigned int reg) +{ + if (reg < PCM3168A_RST_SMODE) + return false; + + switch (reg) { + case PCM3168A_DAC_ZERO: + case PCM3168A_ADC_OV: + return false; + default: + return true; + } +} + +const struct regmap_config pcm3168a_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = PCM3168A_ADC_VOL_CHAN_START + 5, + .reg_defaults = pcm3168a_reg_default, + .num_reg_defaults = ARRAY_SIZE(pcm3168a_reg_default), + .readable_reg = pcm3168a_readable_register, + .volatile_reg = pcm3168a_volatile_register, + .writeable_reg = pcm3168a_writeable_register, + .cache_type = REGCACHE_FLAT +}; +EXPORT_SYMBOL_GPL(pcm3168a_regmap); + +static const struct snd_soc_component_driver pcm3168a_driver = { + .controls = pcm3168a_snd_controls, + .num_controls = ARRAY_SIZE(pcm3168a_snd_controls), + .dapm_widgets = pcm3168a_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm3168a_dapm_widgets), + .dapm_routes = pcm3168a_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm3168a_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +int pcm3168a_probe(struct device *dev, struct regmap *regmap) +{ + struct pcm3168a_priv *pcm3168a; + int ret, i; + + pcm3168a = devm_kzalloc(dev, sizeof(*pcm3168a), GFP_KERNEL); + if (pcm3168a == NULL) + return -ENOMEM; + + dev_set_drvdata(dev, pcm3168a); + + pcm3168a->scki = devm_clk_get(dev, "scki"); + if (IS_ERR(pcm3168a->scki)) { + ret = PTR_ERR(pcm3168a->scki); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to acquire clock 'scki': %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(pcm3168a->scki); + if (ret) { + dev_err(dev, "Failed to enable mclk: %d\n", ret); + return ret; + } + + pcm3168a->sysclk = clk_get_rate(pcm3168a->scki); + + for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++) + pcm3168a->supplies[i].supply = pcm3168a_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(pcm3168a->supplies), pcm3168a->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request supplies: %d\n", ret); + goto err_clk; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + goto err_clk; + } + + pcm3168a->regmap = regmap; + if (IS_ERR(pcm3168a->regmap)) { + ret = PTR_ERR(pcm3168a->regmap); + dev_err(dev, "failed to allocate regmap: %d\n", ret); + goto err_regulator; + } + + ret = pcm3168a_reset(pcm3168a); + if (ret) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err_regulator; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = devm_snd_soc_register_component(dev, &pcm3168a_driver, pcm3168a_dais, + ARRAY_SIZE(pcm3168a_dais)); + if (ret) { + dev_err(dev, "failed to register component: %d\n", ret); + goto err_regulator; + } + + return 0; + +err_regulator: + regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); +err_clk: + clk_disable_unprepare(pcm3168a->scki); + + return ret; +} +EXPORT_SYMBOL_GPL(pcm3168a_probe); + +static void pcm3168a_disable(struct device *dev) +{ + struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); + + regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); + clk_disable_unprepare(pcm3168a->scki); +} + +void pcm3168a_remove(struct device *dev) +{ + pm_runtime_disable(dev); +#ifndef CONFIG_PM + pcm3168a_disable(dev); +#endif +} +EXPORT_SYMBOL_GPL(pcm3168a_remove); + +#ifdef CONFIG_PM +static int pcm3168a_rt_resume(struct device *dev) +{ + struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(pcm3168a->scki); + if (ret) { + dev_err(dev, "Failed to enable mclk: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); + if (ret) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + goto err_clk; + } + + ret = pcm3168a_reset(pcm3168a); + if (ret) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err_regulator; + } + + regcache_cache_only(pcm3168a->regmap, false); + + regcache_mark_dirty(pcm3168a->regmap); + + ret = regcache_sync(pcm3168a->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap: %d\n", ret); + goto err_regulator; + } + + return 0; + +err_regulator: + regulator_bulk_disable(ARRAY_SIZE(pcm3168a->supplies), + pcm3168a->supplies); +err_clk: + clk_disable_unprepare(pcm3168a->scki); + + return ret; +} + +static int pcm3168a_rt_suspend(struct device *dev) +{ + struct pcm3168a_priv *pcm3168a = dev_get_drvdata(dev); + + regcache_cache_only(pcm3168a->regmap, true); + + pcm3168a_disable(dev); + + return 0; +} +#endif + +const struct dev_pm_ops pcm3168a_pm_ops = { + SET_RUNTIME_PM_OPS(pcm3168a_rt_suspend, pcm3168a_rt_resume, NULL) +}; +EXPORT_SYMBOL_GPL(pcm3168a_pm_ops); + +MODULE_DESCRIPTION("PCM3168A codec driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm3168a.h b/sound/soc/codecs/pcm3168a.h new file mode 100644 index 000000000..56c8332d8 --- /dev/null +++ b/sound/soc/codecs/pcm3168a.h @@ -0,0 +1,100 @@ +/* + * PCM3168A codec driver header + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#ifndef __PCM3168A_H__ +#define __PCM3168A_H__ + +extern const struct dev_pm_ops pcm3168a_pm_ops; +extern const struct regmap_config pcm3168a_regmap; + +extern int pcm3168a_probe(struct device *dev, struct regmap *regmap); +extern void pcm3168a_remove(struct device *dev); + +#define PCM3168A_RST_SMODE 0x40 +#define PCM3168A_MRST_MASK 0x80 +#define PCM3168A_SRST_MASK 0x40 +#define PCM3168A_DAC_SRDA_SHIFT 0 +#define PCM3168A_DAC_SRDA_MASK 0x3 + +#define PCM3168A_DAC_PWR_MST_FMT 0x41 +#define PCM3168A_DAC_PSMDA_SHIFT 7 +#define PCM3168A_DAC_PSMDA_MASK 0x80 +#define PCM3168A_DAC_MSDA_SHIFT 4 +#define PCM3168A_DAC_MSDA_MASK 0x70 +#define PCM3168A_DAC_FMT_SHIFT 0 +#define PCM3168A_DAC_FMT_MASK 0xf + +#define PCM3168A_DAC_OP_FLT 0x42 +#define PCM3168A_DAC_OPEDA_SHIFT 4 +#define PCM3168A_DAC_OPEDA_MASK 0xf0 +#define PCM3168A_DAC_FLT_SHIFT 0 +#define PCM3168A_DAC_FLT_MASK 0xf + +#define PCM3168A_DAC_INV 0x43 + +#define PCM3168A_DAC_MUTE 0x44 + +#define PCM3168A_DAC_ZERO 0x45 + +#define PCM3168A_DAC_ATT_DEMP_ZF 0x46 +#define PCM3168A_DAC_ATMDDA_MASK 0x80 +#define PCM3168A_DAC_ATMDDA_SHIFT 7 +#define PCM3168A_DAC_ATSPDA_MASK 0x40 +#define PCM3168A_DAC_ATSPDA_SHIFT 6 +#define PCM3168A_DAC_DEMP_SHIFT 4 +#define PCM3168A_DAC_DEMP_MASK 0x30 +#define PCM3168A_DAC_AZRO_SHIFT 1 +#define PCM3168A_DAC_AZRO_MASK 0xe +#define PCM3168A_DAC_ZREV_MASK 0x1 +#define PCM3168A_DAC_ZREV_SHIFT 0 + +#define PCM3168A_DAC_VOL_MASTER 0x47 + +#define PCM3168A_DAC_VOL_CHAN_START 0x48 + +#define PCM3168A_ADC_SMODE 0x50 +#define PCM3168A_ADC_SRAD_SHIFT 0 +#define PCM3168A_ADC_SRAD_MASK 0x3 + +#define PCM3168A_ADC_MST_FMT 0x51 +#define PCM3168A_ADC_MSAD_SHIFT 4 +#define PCM3168A_ADC_MSAD_MASK 0x70 +#define PCM3168A_ADC_FMTAD_SHIFT 0 +#define PCM3168A_ADC_FMTAD_MASK 0x7 + +#define PCM3168A_ADC_PWR_HPFB 0x52 +#define PCM3168A_ADC_PSVAD_SHIFT 4 +#define PCM3168A_ADC_PSVAD_MASK 0x70 +#define PCM3168A_ADC_BYP_SHIFT 0 +#define PCM3168A_ADC_BYP_MASK 0x7 + +#define PCM3168A_ADC_SEAD 0x53 + +#define PCM3168A_ADC_INV 0x54 + +#define PCM3168A_ADC_MUTE 0x55 + +#define PCM3168A_ADC_OV 0x56 + +#define PCM3168A_ADC_ATT_OVF 0x57 +#define PCM3168A_ADC_ATMDAD_MASK 0x80 +#define PCM3168A_ADC_ATMDAD_SHIFT 7 +#define PCM3168A_ADC_ATSPAD_MASK 0x40 +#define PCM3168A_ADC_ATSPAD_SHIFT 6 +#define PCM3168A_ADC_OVFP_MASK 0x1 +#define PCM3168A_ADC_OVFP_SHIFT 0 + +#define PCM3168A_ADC_VOL_MASTER 0x58 + +#define PCM3168A_ADC_VOL_CHAN_START 0x59 + +#endif diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c new file mode 100644 index 000000000..39ac2857a --- /dev/null +++ b/sound/soc/codecs/pcm5102a.c @@ -0,0 +1,66 @@ +/* + * Driver for the PCM5102A codec + * + * Author: Florian Meier + * Copyright 2013 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include + +static struct snd_soc_dai_driver pcm5102a_dai = { + .name = "pcm5102a-hifi", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE + }, +}; + +static struct snd_soc_component_driver soc_component_dev_pcm5102a = { + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int pcm5102a_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm5102a, + &pcm5102a_dai, 1); +} + +static const struct of_device_id pcm5102a_of_match[] = { + { .compatible = "ti,pcm5102a", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm5102a_of_match); + +static struct platform_driver pcm5102a_codec_driver = { + .probe = pcm5102a_probe, + .driver = { + .name = "pcm5102a-codec", + .of_match_table = pcm5102a_of_match, + }, +}; + +module_platform_driver(pcm5102a_codec_driver); + +MODULE_DESCRIPTION("ASoC PCM5102A codec driver"); +MODULE_AUTHOR("Florian Meier "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm512x-i2c.c b/sound/soc/codecs/pcm512x-i2c.c new file mode 100644 index 000000000..0fe5ced84 --- /dev/null +++ b/sound/soc/codecs/pcm512x-i2c.c @@ -0,0 +1,94 @@ +/* + * Driver for the PCM512x CODECs + * + * Author: Mark Brown + * Copyright 2014 Linaro Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include + +#include "pcm512x.h" + +static int pcm512x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + struct regmap_config config = pcm512x_regmap; + + /* msb needs to be set to enable auto-increment of addresses */ + config.read_flag_mask = 0x80; + config.write_flag_mask = 0x80; + + regmap = devm_regmap_init_i2c(i2c, &config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return pcm512x_probe(&i2c->dev, regmap); +} + +static int pcm512x_i2c_remove(struct i2c_client *i2c) +{ + pcm512x_remove(&i2c->dev); + return 0; +} + +static const struct i2c_device_id pcm512x_i2c_id[] = { + { "pcm5121", }, + { "pcm5122", }, + { "pcm5141", }, + { "pcm5142", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcm512x_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id pcm512x_of_match[] = { + { .compatible = "ti,pcm5121", }, + { .compatible = "ti,pcm5122", }, + { .compatible = "ti,pcm5141", }, + { .compatible = "ti,pcm5142", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm512x_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id pcm512x_acpi_match[] = { + { "104C5121", 0 }, + { "104C5122", 0 }, + { "104C5141", 0 }, + { "104C5142", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, pcm512x_acpi_match); +#endif + +static struct i2c_driver pcm512x_i2c_driver = { + .probe = pcm512x_i2c_probe, + .remove = pcm512x_i2c_remove, + .id_table = pcm512x_i2c_id, + .driver = { + .name = "pcm512x", + .of_match_table = of_match_ptr(pcm512x_of_match), + .acpi_match_table = ACPI_PTR(pcm512x_acpi_match), + .pm = &pcm512x_pm_ops, + }, +}; + +module_i2c_driver(pcm512x_i2c_driver); + +MODULE_DESCRIPTION("ASoC PCM512x codec driver - I2C"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm512x-spi.c b/sound/soc/codecs/pcm512x-spi.c new file mode 100644 index 000000000..7cdd2dc4f --- /dev/null +++ b/sound/soc/codecs/pcm512x-spi.c @@ -0,0 +1,76 @@ +/* + * Driver for the PCM512x CODECs + * + * Author: Mark Brown + * Copyright 2014 Linaro Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +#include "pcm512x.h" + +static int pcm512x_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_spi(spi, &pcm512x_regmap); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + return ret; + } + + return pcm512x_probe(&spi->dev, regmap); +} + +static int pcm512x_spi_remove(struct spi_device *spi) +{ + pcm512x_remove(&spi->dev); + return 0; +} + +static const struct spi_device_id pcm512x_spi_id[] = { + { "pcm5121", }, + { "pcm5122", }, + { "pcm5141", }, + { "pcm5142", }, + { }, +}; +MODULE_DEVICE_TABLE(spi, pcm512x_spi_id); + +static const struct of_device_id pcm512x_of_match[] = { + { .compatible = "ti,pcm5121", }, + { .compatible = "ti,pcm5122", }, + { .compatible = "ti,pcm5141", }, + { .compatible = "ti,pcm5142", }, + { } +}; +MODULE_DEVICE_TABLE(of, pcm512x_of_match); + +static struct spi_driver pcm512x_spi_driver = { + .probe = pcm512x_spi_probe, + .remove = pcm512x_spi_remove, + .id_table = pcm512x_spi_id, + .driver = { + .name = "pcm512x", + .of_match_table = pcm512x_of_match, + .pm = &pcm512x_pm_ops, + }, +}; + +module_spi_driver(pcm512x_spi_driver); + +MODULE_DESCRIPTION("ASoC PCM512x codec driver - SPI"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c new file mode 100644 index 000000000..5272c8164 --- /dev/null +++ b/sound/soc/codecs/pcm512x.c @@ -0,0 +1,1606 @@ +/* + * Driver for the PCM512x CODECs + * + * Author: Mark Brown + * Copyright 2014 Linaro Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcm512x.h" + +#define PCM512x_NUM_SUPPLIES 3 +static const char * const pcm512x_supply_names[PCM512x_NUM_SUPPLIES] = { + "AVDD", + "DVDD", + "CPVDD", +}; + +struct pcm512x_priv { + struct regmap *regmap; + struct clk *sclk; + struct regulator_bulk_data supplies[PCM512x_NUM_SUPPLIES]; + struct notifier_block supply_nb[PCM512x_NUM_SUPPLIES]; + int fmt; + int pll_in; + int pll_out; + int pll_r; + int pll_j; + int pll_d; + int pll_p; + unsigned long real_pll; + unsigned long overclock_pll; + unsigned long overclock_dac; + unsigned long overclock_dsp; +}; + +/* + * We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define PCM512x_REGULATOR_EVENT(n) \ +static int pcm512x_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct pcm512x_priv *pcm512x = container_of(nb, struct pcm512x_priv, \ + supply_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + regcache_mark_dirty(pcm512x->regmap); \ + regcache_cache_only(pcm512x->regmap, true); \ + } \ + return 0; \ +} + +PCM512x_REGULATOR_EVENT(0) +PCM512x_REGULATOR_EVENT(1) +PCM512x_REGULATOR_EVENT(2) + +static const struct reg_default pcm512x_reg_defaults[] = { + { PCM512x_RESET, 0x00 }, + { PCM512x_POWER, 0x00 }, + { PCM512x_MUTE, 0x00 }, + { PCM512x_DSP, 0x00 }, + { PCM512x_PLL_REF, 0x00 }, + { PCM512x_DAC_REF, 0x00 }, + { PCM512x_DAC_ROUTING, 0x11 }, + { PCM512x_DSP_PROGRAM, 0x01 }, + { PCM512x_CLKDET, 0x00 }, + { PCM512x_AUTO_MUTE, 0x00 }, + { PCM512x_ERROR_DETECT, 0x00 }, + { PCM512x_DIGITAL_VOLUME_1, 0x00 }, + { PCM512x_DIGITAL_VOLUME_2, 0x30 }, + { PCM512x_DIGITAL_VOLUME_3, 0x30 }, + { PCM512x_DIGITAL_MUTE_1, 0x22 }, + { PCM512x_DIGITAL_MUTE_2, 0x00 }, + { PCM512x_DIGITAL_MUTE_3, 0x07 }, + { PCM512x_OUTPUT_AMPLITUDE, 0x00 }, + { PCM512x_ANALOG_GAIN_CTRL, 0x00 }, + { PCM512x_UNDERVOLTAGE_PROT, 0x00 }, + { PCM512x_ANALOG_MUTE_CTRL, 0x00 }, + { PCM512x_ANALOG_GAIN_BOOST, 0x00 }, + { PCM512x_VCOM_CTRL_1, 0x00 }, + { PCM512x_VCOM_CTRL_2, 0x01 }, + { PCM512x_BCLK_LRCLK_CFG, 0x00 }, + { PCM512x_MASTER_MODE, 0x7c }, + { PCM512x_GPIO_DACIN, 0x00 }, + { PCM512x_GPIO_PLLIN, 0x00 }, + { PCM512x_SYNCHRONIZE, 0x10 }, + { PCM512x_PLL_COEFF_0, 0x00 }, + { PCM512x_PLL_COEFF_1, 0x00 }, + { PCM512x_PLL_COEFF_2, 0x00 }, + { PCM512x_PLL_COEFF_3, 0x00 }, + { PCM512x_PLL_COEFF_4, 0x00 }, + { PCM512x_DSP_CLKDIV, 0x00 }, + { PCM512x_DAC_CLKDIV, 0x00 }, + { PCM512x_NCP_CLKDIV, 0x00 }, + { PCM512x_OSR_CLKDIV, 0x00 }, + { PCM512x_MASTER_CLKDIV_1, 0x00 }, + { PCM512x_MASTER_CLKDIV_2, 0x00 }, + { PCM512x_FS_SPEED_MODE, 0x00 }, + { PCM512x_IDAC_1, 0x01 }, + { PCM512x_IDAC_2, 0x00 }, +}; + +static bool pcm512x_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PCM512x_RESET: + case PCM512x_POWER: + case PCM512x_MUTE: + case PCM512x_PLL_EN: + case PCM512x_SPI_MISO_FUNCTION: + case PCM512x_DSP: + case PCM512x_GPIO_EN: + case PCM512x_BCLK_LRCLK_CFG: + case PCM512x_DSP_GPIO_INPUT: + case PCM512x_MASTER_MODE: + case PCM512x_PLL_REF: + case PCM512x_DAC_REF: + case PCM512x_GPIO_DACIN: + case PCM512x_GPIO_PLLIN: + case PCM512x_SYNCHRONIZE: + case PCM512x_PLL_COEFF_0: + case PCM512x_PLL_COEFF_1: + case PCM512x_PLL_COEFF_2: + case PCM512x_PLL_COEFF_3: + case PCM512x_PLL_COEFF_4: + case PCM512x_DSP_CLKDIV: + case PCM512x_DAC_CLKDIV: + case PCM512x_NCP_CLKDIV: + case PCM512x_OSR_CLKDIV: + case PCM512x_MASTER_CLKDIV_1: + case PCM512x_MASTER_CLKDIV_2: + case PCM512x_FS_SPEED_MODE: + case PCM512x_IDAC_1: + case PCM512x_IDAC_2: + case PCM512x_ERROR_DETECT: + case PCM512x_I2S_1: + case PCM512x_I2S_2: + case PCM512x_DAC_ROUTING: + case PCM512x_DSP_PROGRAM: + case PCM512x_CLKDET: + case PCM512x_AUTO_MUTE: + case PCM512x_DIGITAL_VOLUME_1: + case PCM512x_DIGITAL_VOLUME_2: + case PCM512x_DIGITAL_VOLUME_3: + case PCM512x_DIGITAL_MUTE_1: + case PCM512x_DIGITAL_MUTE_2: + case PCM512x_DIGITAL_MUTE_3: + case PCM512x_GPIO_OUTPUT_1: + case PCM512x_GPIO_OUTPUT_2: + case PCM512x_GPIO_OUTPUT_3: + case PCM512x_GPIO_OUTPUT_4: + case PCM512x_GPIO_OUTPUT_5: + case PCM512x_GPIO_OUTPUT_6: + case PCM512x_GPIO_CONTROL_1: + case PCM512x_GPIO_CONTROL_2: + case PCM512x_OVERFLOW: + case PCM512x_RATE_DET_1: + case PCM512x_RATE_DET_2: + case PCM512x_RATE_DET_3: + case PCM512x_RATE_DET_4: + case PCM512x_CLOCK_STATUS: + case PCM512x_ANALOG_MUTE_DET: + case PCM512x_GPIN: + case PCM512x_DIGITAL_MUTE_DET: + case PCM512x_OUTPUT_AMPLITUDE: + case PCM512x_ANALOG_GAIN_CTRL: + case PCM512x_UNDERVOLTAGE_PROT: + case PCM512x_ANALOG_MUTE_CTRL: + case PCM512x_ANALOG_GAIN_BOOST: + case PCM512x_VCOM_CTRL_1: + case PCM512x_VCOM_CTRL_2: + case PCM512x_CRAM_CTRL: + case PCM512x_FLEX_A: + case PCM512x_FLEX_B: + return true; + default: + /* There are 256 raw register addresses */ + return reg < 0xff; + } +} + +static bool pcm512x_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PCM512x_PLL_EN: + case PCM512x_OVERFLOW: + case PCM512x_RATE_DET_1: + case PCM512x_RATE_DET_2: + case PCM512x_RATE_DET_3: + case PCM512x_RATE_DET_4: + case PCM512x_CLOCK_STATUS: + case PCM512x_ANALOG_MUTE_DET: + case PCM512x_GPIN: + case PCM512x_DIGITAL_MUTE_DET: + case PCM512x_CRAM_CTRL: + return true; + default: + /* There are 256 raw register addresses */ + return reg < 0xff; + } +} + +static int pcm512x_overclock_pll_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = pcm512x->overclock_pll; + return 0; +} + +static int pcm512x_overclock_pll_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_OFF: + case SND_SOC_BIAS_STANDBY: + break; + default: + return -EBUSY; + } + + pcm512x->overclock_pll = ucontrol->value.integer.value[0]; + return 0; +} + +static int pcm512x_overclock_dsp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = pcm512x->overclock_dsp; + return 0; +} + +static int pcm512x_overclock_dsp_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_OFF: + case SND_SOC_BIAS_STANDBY: + break; + default: + return -EBUSY; + } + + pcm512x->overclock_dsp = ucontrol->value.integer.value[0]; + return 0; +} + +static int pcm512x_overclock_dac_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = pcm512x->overclock_dac; + return 0; +} + +static int pcm512x_overclock_dac_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_OFF: + case SND_SOC_BIAS_STANDBY: + break; + default: + return -EBUSY; + } + + pcm512x->overclock_dac = ucontrol->value.integer.value[0]; + return 0; +} + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -10350, 50, 1); +static const DECLARE_TLV_DB_SCALE(analog_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(boost_tlv, 0, 80, 0); + +static const char * const pcm512x_dsp_program_texts[] = { + "FIR interpolation with de-emphasis", + "Low latency IIR with de-emphasis", + "High attenuation with de-emphasis", + "Fixed process flow", + "Ringing-less low latency FIR", +}; + +static const unsigned int pcm512x_dsp_program_values[] = { + 1, + 2, + 3, + 5, + 7, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(pcm512x_dsp_program, + PCM512x_DSP_PROGRAM, 0, 0x1f, + pcm512x_dsp_program_texts, + pcm512x_dsp_program_values); + +static const char * const pcm512x_clk_missing_text[] = { + "1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s" +}; + +static const struct soc_enum pcm512x_clk_missing = + SOC_ENUM_SINGLE(PCM512x_CLKDET, 0, 8, pcm512x_clk_missing_text); + +static const char * const pcm512x_autom_text[] = { + "21ms", "106ms", "213ms", "533ms", "1.07s", "2.13s", "5.33s", "10.66s" +}; + +static const struct soc_enum pcm512x_autom_l = + SOC_ENUM_SINGLE(PCM512x_AUTO_MUTE, PCM512x_ATML_SHIFT, 8, + pcm512x_autom_text); + +static const struct soc_enum pcm512x_autom_r = + SOC_ENUM_SINGLE(PCM512x_AUTO_MUTE, PCM512x_ATMR_SHIFT, 8, + pcm512x_autom_text); + +static const char * const pcm512x_ramp_rate_text[] = { + "1 sample/update", "2 samples/update", "4 samples/update", + "Immediate" +}; + +static const struct soc_enum pcm512x_vndf = + SOC_ENUM_SINGLE(PCM512x_DIGITAL_MUTE_1, PCM512x_VNDF_SHIFT, 4, + pcm512x_ramp_rate_text); + +static const struct soc_enum pcm512x_vnuf = + SOC_ENUM_SINGLE(PCM512x_DIGITAL_MUTE_1, PCM512x_VNUF_SHIFT, 4, + pcm512x_ramp_rate_text); + +static const struct soc_enum pcm512x_vedf = + SOC_ENUM_SINGLE(PCM512x_DIGITAL_MUTE_2, PCM512x_VEDF_SHIFT, 4, + pcm512x_ramp_rate_text); + +static const char * const pcm512x_ramp_step_text[] = { + "4dB/step", "2dB/step", "1dB/step", "0.5dB/step" +}; + +static const struct soc_enum pcm512x_vnds = + SOC_ENUM_SINGLE(PCM512x_DIGITAL_MUTE_1, PCM512x_VNDS_SHIFT, 4, + pcm512x_ramp_step_text); + +static const struct soc_enum pcm512x_vnus = + SOC_ENUM_SINGLE(PCM512x_DIGITAL_MUTE_1, PCM512x_VNUS_SHIFT, 4, + pcm512x_ramp_step_text); + +static const struct soc_enum pcm512x_veds = + SOC_ENUM_SINGLE(PCM512x_DIGITAL_MUTE_2, PCM512x_VEDS_SHIFT, 4, + pcm512x_ramp_step_text); + +static const struct snd_kcontrol_new pcm512x_controls[] = { +SOC_DOUBLE_R_TLV("Digital Playback Volume", PCM512x_DIGITAL_VOLUME_2, + PCM512x_DIGITAL_VOLUME_3, 0, 255, 1, digital_tlv), +SOC_DOUBLE_TLV("Analogue Playback Volume", PCM512x_ANALOG_GAIN_CTRL, + PCM512x_LAGN_SHIFT, PCM512x_RAGN_SHIFT, 1, 1, analog_tlv), +SOC_DOUBLE_TLV("Analogue Playback Boost Volume", PCM512x_ANALOG_GAIN_BOOST, + PCM512x_AGBL_SHIFT, PCM512x_AGBR_SHIFT, 1, 0, boost_tlv), +SOC_DOUBLE("Digital Playback Switch", PCM512x_MUTE, PCM512x_RQML_SHIFT, + PCM512x_RQMR_SHIFT, 1, 1), + +SOC_SINGLE("Deemphasis Switch", PCM512x_DSP, PCM512x_DEMP_SHIFT, 1, 1), +SOC_ENUM("DSP Program", pcm512x_dsp_program), + +SOC_ENUM("Clock Missing Period", pcm512x_clk_missing), +SOC_ENUM("Auto Mute Time Left", pcm512x_autom_l), +SOC_ENUM("Auto Mute Time Right", pcm512x_autom_r), +SOC_SINGLE("Auto Mute Mono Switch", PCM512x_DIGITAL_MUTE_3, + PCM512x_ACTL_SHIFT, 1, 0), +SOC_DOUBLE("Auto Mute Switch", PCM512x_DIGITAL_MUTE_3, PCM512x_AMLE_SHIFT, + PCM512x_AMRE_SHIFT, 1, 0), + +SOC_ENUM("Volume Ramp Down Rate", pcm512x_vndf), +SOC_ENUM("Volume Ramp Down Step", pcm512x_vnds), +SOC_ENUM("Volume Ramp Up Rate", pcm512x_vnuf), +SOC_ENUM("Volume Ramp Up Step", pcm512x_vnus), +SOC_ENUM("Volume Ramp Down Emergency Rate", pcm512x_vedf), +SOC_ENUM("Volume Ramp Down Emergency Step", pcm512x_veds), + +SOC_SINGLE_EXT("Max Overclock PLL", SND_SOC_NOPM, 0, 20, 0, + pcm512x_overclock_pll_get, pcm512x_overclock_pll_put), +SOC_SINGLE_EXT("Max Overclock DSP", SND_SOC_NOPM, 0, 40, 0, + pcm512x_overclock_dsp_get, pcm512x_overclock_dsp_put), +SOC_SINGLE_EXT("Max Overclock DAC", SND_SOC_NOPM, 0, 40, 0, + pcm512x_overclock_dac_get, pcm512x_overclock_dac_put), +}; + +static const struct snd_soc_dapm_widget pcm512x_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DACL", NULL, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_DAC("DACR", NULL, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_OUTPUT("OUTL"), +SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route pcm512x_dapm_routes[] = { + { "DACL", NULL, "Playback" }, + { "DACR", NULL, "Playback" }, + + { "OUTL", NULL, "DACL" }, + { "OUTR", NULL, "DACR" }, +}; + +static unsigned long pcm512x_pll_max(struct pcm512x_priv *pcm512x) +{ + return 25000000 + 25000000 * pcm512x->overclock_pll / 100; +} + +static unsigned long pcm512x_dsp_max(struct pcm512x_priv *pcm512x) +{ + return 50000000 + 50000000 * pcm512x->overclock_dsp / 100; +} + +static unsigned long pcm512x_dac_max(struct pcm512x_priv *pcm512x, + unsigned long rate) +{ + return rate + rate * pcm512x->overclock_dac / 100; +} + +static unsigned long pcm512x_sck_max(struct pcm512x_priv *pcm512x) +{ + if (!pcm512x->pll_out) + return 25000000; + return pcm512x_pll_max(pcm512x); +} + +static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x, + unsigned long dac_rate) +{ + /* + * If the DAC is not actually overclocked, use the good old + * NCP target rate... + */ + if (dac_rate <= 6144000) + return 1536000; + /* + * ...but if the DAC is in fact overclocked, bump the NCP target + * rate to get the recommended dividers even when overclocking. + */ + return pcm512x_dac_max(pcm512x, 1536000); +} + +static const u32 pcm512x_dai_rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, + 88200, 96000, 176400, 192000, 384000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_slave = { + .count = ARRAY_SIZE(pcm512x_dai_rates), + .list = pcm512x_dai_rates, +}; + +static int pcm512x_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct pcm512x_priv *pcm512x = rule->private; + struct snd_interval ranges[2]; + int frame_size; + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) + return frame_size; + + switch (frame_size) { + case 32: + /* No hole when the frame size is 32. */ + return 0; + case 48: + case 64: + /* There is only one hole in the range of supported + * rates, but it moves with the frame size. + */ + memset(ranges, 0, sizeof(ranges)); + ranges[0].min = 8000; + ranges[0].max = pcm512x_sck_max(pcm512x) / frame_size / 2; + ranges[1].min = DIV_ROUND_UP(16000000, frame_size); + ranges[1].max = 384000; + break; + default: + return -EINVAL; + } + + return snd_interval_ranges(hw_param_interval(params, rule->var), + ARRAY_SIZE(ranges), ranges, 0); +} + +static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct device *dev = dai->dev; + struct snd_pcm_hw_constraint_ratnums *constraints_no_pll; + struct snd_ratnum *rats_no_pll; + + if (IS_ERR(pcm512x->sclk)) { + dev_err(dev, "Need SCLK for master mode: %ld\n", + PTR_ERR(pcm512x->sclk)); + return PTR_ERR(pcm512x->sclk); + } + + if (pcm512x->pll_out) + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + pcm512x_hw_rule_rate, + pcm512x, + SNDRV_PCM_HW_PARAM_FRAME_BITS, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + + constraints_no_pll = devm_kzalloc(dev, sizeof(*constraints_no_pll), + GFP_KERNEL); + if (!constraints_no_pll) + return -ENOMEM; + constraints_no_pll->nrats = 1; + rats_no_pll = devm_kzalloc(dev, sizeof(*rats_no_pll), GFP_KERNEL); + if (!rats_no_pll) + return -ENOMEM; + constraints_no_pll->rats = rats_no_pll; + rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64; + rats_no_pll->den_min = 1; + rats_no_pll->den_max = 128; + rats_no_pll->den_step = 1; + + return snd_pcm_hw_constraint_ratnums(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + constraints_no_pll); +} + +static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + struct device *dev = dai->dev; + struct regmap *regmap = pcm512x->regmap; + + if (IS_ERR(pcm512x->sclk)) { + dev_info(dev, "No SCLK, using BCLK: %ld\n", + PTR_ERR(pcm512x->sclk)); + + /* Disable reporting of missing SCLK as an error */ + regmap_update_bits(regmap, PCM512x_ERROR_DETECT, + PCM512x_IDCH, PCM512x_IDCH); + + /* Switch PLL input to BCLK */ + regmap_update_bits(regmap, PCM512x_PLL_REF, + PCM512x_SREF, PCM512x_SREF_BCK); + } + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_slave); +} + +static int pcm512x_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + switch (pcm512x->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + return pcm512x_dai_startup_master(substream, dai); + + case SND_SOC_DAIFMT_CBS_CFS: + return pcm512x_dai_startup_slave(substream, dai); + + default: + return -EINVAL; + } +} + +static int pcm512x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct pcm512x_priv *pcm512x = dev_get_drvdata(component->dev); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, + PCM512x_RQST, 0); + if (ret != 0) { + dev_err(component->dev, "Failed to remove standby: %d\n", + ret); + return ret; + } + break; + + case SND_SOC_BIAS_OFF: + ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, + PCM512x_RQST, PCM512x_RQST); + if (ret != 0) { + dev_err(component->dev, "Failed to request standby: %d\n", + ret); + return ret; + } + break; + } + + return 0; +} + +static unsigned long pcm512x_find_sck(struct snd_soc_dai *dai, + unsigned long bclk_rate) +{ + struct device *dev = dai->dev; + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + unsigned long sck_rate; + int pow2; + + /* 64 MHz <= pll_rate <= 100 MHz, VREF mode */ + /* 16 MHz <= sck_rate <= 25 MHz, VREF mode */ + + /* select sck_rate as a multiple of bclk_rate but still with + * as many factors of 2 as possible, as that makes it easier + * to find a fast DAC rate + */ + pow2 = 1 << fls((pcm512x_pll_max(pcm512x) - 16000000) / bclk_rate); + for (; pow2; pow2 >>= 1) { + sck_rate = rounddown(pcm512x_pll_max(pcm512x), + bclk_rate * pow2); + if (sck_rate >= 16000000) + break; + } + if (!pow2) { + dev_err(dev, "Impossible to generate a suitable SCK\n"); + return 0; + } + + dev_dbg(dev, "sck_rate %lu\n", sck_rate); + return sck_rate; +} + +/* pll_rate = pllin_rate * R * J.D / P + * 1 <= R <= 16 + * 1 <= J <= 63 + * 0 <= D <= 9999 + * 1 <= P <= 15 + * 64 MHz <= pll_rate <= 100 MHz + * if D == 0 + * 1 MHz <= pllin_rate / P <= 20 MHz + * else if D > 0 + * 6.667 MHz <= pllin_rate / P <= 20 MHz + * 4 <= J <= 11 + * R = 1 + */ +static int pcm512x_find_pll_coeff(struct snd_soc_dai *dai, + unsigned long pllin_rate, + unsigned long pll_rate) +{ + struct device *dev = dai->dev; + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + unsigned long common; + int R, J, D, P; + unsigned long K; /* 10000 * J.D */ + unsigned long num; + unsigned long den; + + common = gcd(pll_rate, pllin_rate); + dev_dbg(dev, "pll %lu pllin %lu common %lu\n", + pll_rate, pllin_rate, common); + num = pll_rate / common; + den = pllin_rate / common; + + /* pllin_rate / P (or here, den) cannot be greater than 20 MHz */ + if (pllin_rate / den > 20000000 && num < 8) { + num *= DIV_ROUND_UP(pllin_rate / den, 20000000); + den *= DIV_ROUND_UP(pllin_rate / den, 20000000); + } + dev_dbg(dev, "num / den = %lu / %lu\n", num, den); + + P = den; + if (den <= 15 && num <= 16 * 63 + && 1000000 <= pllin_rate / P && pllin_rate / P <= 20000000) { + /* Try the case with D = 0 */ + D = 0; + /* factor 'num' into J and R, such that R <= 16 and J <= 63 */ + for (R = 16; R; R--) { + if (num % R) + continue; + J = num / R; + if (J == 0 || J > 63) + continue; + + dev_dbg(dev, "R * J / P = %d * %d / %d\n", R, J, P); + pcm512x->real_pll = pll_rate; + goto done; + } + /* no luck */ + } + + R = 1; + + if (num > 0xffffffffUL / 10000) + goto fallback; + + /* Try to find an exact pll_rate using the D > 0 case */ + common = gcd(10000 * num, den); + num = 10000 * num / common; + den /= common; + dev_dbg(dev, "num %lu den %lu common %lu\n", num, den, common); + + for (P = den; P <= 15; P++) { + if (pllin_rate / P < 6667000 || 200000000 < pllin_rate / P) + continue; + if (num * P % den) + continue; + K = num * P / den; + /* J == 12 is ok if D == 0 */ + if (K < 40000 || K > 120000) + continue; + + J = K / 10000; + D = K % 10000; + dev_dbg(dev, "J.D / P = %d.%04d / %d\n", J, D, P); + pcm512x->real_pll = pll_rate; + goto done; + } + + /* Fall back to an approximate pll_rate */ + +fallback: + /* find smallest possible P */ + P = DIV_ROUND_UP(pllin_rate, 20000000); + if (!P) + P = 1; + else if (P > 15) { + dev_err(dev, "Need a slower clock as pll-input\n"); + return -EINVAL; + } + if (pllin_rate / P < 6667000) { + dev_err(dev, "Need a faster clock as pll-input\n"); + return -EINVAL; + } + K = DIV_ROUND_CLOSEST_ULL(10000ULL * pll_rate * P, pllin_rate); + if (K < 40000) + K = 40000; + /* J == 12 is ok if D == 0 */ + if (K > 120000) + K = 120000; + J = K / 10000; + D = K % 10000; + dev_dbg(dev, "J.D / P ~ %d.%04d / %d\n", J, D, P); + pcm512x->real_pll = DIV_ROUND_DOWN_ULL((u64)K * pllin_rate, 10000 * P); + +done: + pcm512x->pll_r = R; + pcm512x->pll_j = J; + pcm512x->pll_d = D; + pcm512x->pll_p = P; + return 0; +} + +static unsigned long pcm512x_pllin_dac_rate(struct snd_soc_dai *dai, + unsigned long osr_rate, + unsigned long pllin_rate) +{ + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + unsigned long dac_rate; + + if (!pcm512x->pll_out) + return 0; /* no PLL to bypass, force SCK as DAC input */ + + if (pllin_rate % osr_rate) + return 0; /* futile, quit early */ + + /* run DAC no faster than 6144000 Hz */ + for (dac_rate = rounddown(pcm512x_dac_max(pcm512x, 6144000), osr_rate); + dac_rate; + dac_rate -= osr_rate) { + + if (pllin_rate / dac_rate > 128) + return 0; /* DAC divider would be too big */ + + if (!(pllin_rate % dac_rate)) + return dac_rate; + + dac_rate -= osr_rate; + } + + return 0; +} + +static int pcm512x_set_dividers(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct device *dev = dai->dev; + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + unsigned long pllin_rate = 0; + unsigned long pll_rate; + unsigned long sck_rate; + unsigned long mck_rate; + unsigned long bclk_rate; + unsigned long sample_rate; + unsigned long osr_rate; + unsigned long dacsrc_rate; + int bclk_div; + int lrclk_div; + int dsp_div; + int dac_div; + unsigned long dac_rate; + int ncp_div; + int osr_div; + int ret; + int idac; + int fssp; + int gpio; + + lrclk_div = snd_soc_params_to_frame_size(params); + if (lrclk_div == 0) { + dev_err(dev, "No LRCLK?\n"); + return -EINVAL; + } + + if (!pcm512x->pll_out) { + sck_rate = clk_get_rate(pcm512x->sclk); + bclk_div = params->rate_den * 64 / lrclk_div; + bclk_rate = DIV_ROUND_CLOSEST(sck_rate, bclk_div); + + mck_rate = sck_rate; + } else { + ret = snd_soc_params_to_bclk(params); + if (ret < 0) { + dev_err(dev, "Failed to find suitable BCLK: %d\n", ret); + return ret; + } + if (ret == 0) { + dev_err(dev, "No BCLK?\n"); + return -EINVAL; + } + bclk_rate = ret; + + pllin_rate = clk_get_rate(pcm512x->sclk); + + sck_rate = pcm512x_find_sck(dai, bclk_rate); + if (!sck_rate) + return -EINVAL; + pll_rate = 4 * sck_rate; + + ret = pcm512x_find_pll_coeff(dai, pllin_rate, pll_rate); + if (ret != 0) + return ret; + + ret = regmap_write(pcm512x->regmap, + PCM512x_PLL_COEFF_0, pcm512x->pll_p - 1); + if (ret != 0) { + dev_err(dev, "Failed to write PLL P: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, + PCM512x_PLL_COEFF_1, pcm512x->pll_j); + if (ret != 0) { + dev_err(dev, "Failed to write PLL J: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, + PCM512x_PLL_COEFF_2, pcm512x->pll_d >> 8); + if (ret != 0) { + dev_err(dev, "Failed to write PLL D msb: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, + PCM512x_PLL_COEFF_3, pcm512x->pll_d & 0xff); + if (ret != 0) { + dev_err(dev, "Failed to write PLL D lsb: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, + PCM512x_PLL_COEFF_4, pcm512x->pll_r - 1); + if (ret != 0) { + dev_err(dev, "Failed to write PLL R: %d\n", ret); + return ret; + } + + mck_rate = pcm512x->real_pll; + + bclk_div = DIV_ROUND_CLOSEST(sck_rate, bclk_rate); + } + + if (bclk_div > 128) { + dev_err(dev, "Failed to find BCLK divider\n"); + return -EINVAL; + } + + /* the actual rate */ + sample_rate = sck_rate / bclk_div / lrclk_div; + osr_rate = 16 * sample_rate; + + /* run DSP no faster than 50 MHz */ + dsp_div = mck_rate > pcm512x_dsp_max(pcm512x) ? 2 : 1; + + dac_rate = pcm512x_pllin_dac_rate(dai, osr_rate, pllin_rate); + if (dac_rate) { + /* the desired clock rate is "compatible" with the pll input + * clock, so use that clock as dac input instead of the pll + * output clock since the pll will introduce jitter and thus + * noise. + */ + dev_dbg(dev, "using pll input as dac input\n"); + ret = regmap_update_bits(pcm512x->regmap, PCM512x_DAC_REF, + PCM512x_SDAC, PCM512x_SDAC_GPIO); + if (ret != 0) { + dev_err(component->dev, + "Failed to set gpio as dacref: %d\n", ret); + return ret; + } + + gpio = PCM512x_GREF_GPIO1 + pcm512x->pll_in - 1; + ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_DACIN, + PCM512x_GREF, gpio); + if (ret != 0) { + dev_err(component->dev, + "Failed to set gpio %d as dacin: %d\n", + pcm512x->pll_in, ret); + return ret; + } + + dacsrc_rate = pllin_rate; + } else { + /* run DAC no faster than 6144000 Hz */ + unsigned long dac_mul = pcm512x_dac_max(pcm512x, 6144000) + / osr_rate; + unsigned long sck_mul = sck_rate / osr_rate; + + for (; dac_mul; dac_mul--) { + if (!(sck_mul % dac_mul)) + break; + } + if (!dac_mul) { + dev_err(dev, "Failed to find DAC rate\n"); + return -EINVAL; + } + + dac_rate = dac_mul * osr_rate; + dev_dbg(dev, "dac_rate %lu sample_rate %lu\n", + dac_rate, sample_rate); + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_DAC_REF, + PCM512x_SDAC, PCM512x_SDAC_SCK); + if (ret != 0) { + dev_err(component->dev, + "Failed to set sck as dacref: %d\n", ret); + return ret; + } + + dacsrc_rate = sck_rate; + } + + osr_div = DIV_ROUND_CLOSEST(dac_rate, osr_rate); + if (osr_div > 128) { + dev_err(dev, "Failed to find OSR divider\n"); + return -EINVAL; + } + + dac_div = DIV_ROUND_CLOSEST(dacsrc_rate, dac_rate); + if (dac_div > 128) { + dev_err(dev, "Failed to find DAC divider\n"); + return -EINVAL; + } + dac_rate = dacsrc_rate / dac_div; + + ncp_div = DIV_ROUND_CLOSEST(dac_rate, + pcm512x_ncp_target(pcm512x, dac_rate)); + if (ncp_div > 128 || dac_rate / ncp_div > 2048000) { + /* run NCP no faster than 2048000 Hz, but why? */ + ncp_div = DIV_ROUND_UP(dac_rate, 2048000); + if (ncp_div > 128) { + dev_err(dev, "Failed to find NCP divider\n"); + return -EINVAL; + } + } + + idac = mck_rate / (dsp_div * sample_rate); + + ret = regmap_write(pcm512x->regmap, PCM512x_DSP_CLKDIV, dsp_div - 1); + if (ret != 0) { + dev_err(dev, "Failed to write DSP divider: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, PCM512x_DAC_CLKDIV, dac_div - 1); + if (ret != 0) { + dev_err(dev, "Failed to write DAC divider: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, PCM512x_NCP_CLKDIV, ncp_div - 1); + if (ret != 0) { + dev_err(dev, "Failed to write NCP divider: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, PCM512x_OSR_CLKDIV, osr_div - 1); + if (ret != 0) { + dev_err(dev, "Failed to write OSR divider: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, + PCM512x_MASTER_CLKDIV_1, bclk_div - 1); + if (ret != 0) { + dev_err(dev, "Failed to write BCLK divider: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, + PCM512x_MASTER_CLKDIV_2, lrclk_div - 1); + if (ret != 0) { + dev_err(dev, "Failed to write LRCLK divider: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, PCM512x_IDAC_1, idac >> 8); + if (ret != 0) { + dev_err(dev, "Failed to write IDAC msb divider: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, PCM512x_IDAC_2, idac & 0xff); + if (ret != 0) { + dev_err(dev, "Failed to write IDAC lsb divider: %d\n", ret); + return ret; + } + + if (sample_rate <= pcm512x_dac_max(pcm512x, 48000)) + fssp = PCM512x_FSSP_48KHZ; + else if (sample_rate <= pcm512x_dac_max(pcm512x, 96000)) + fssp = PCM512x_FSSP_96KHZ; + else if (sample_rate <= pcm512x_dac_max(pcm512x, 192000)) + fssp = PCM512x_FSSP_192KHZ; + else + fssp = PCM512x_FSSP_384KHZ; + ret = regmap_update_bits(pcm512x->regmap, PCM512x_FS_SPEED_MODE, + PCM512x_FSSP, fssp); + if (ret != 0) { + dev_err(component->dev, "Failed to set fs speed: %d\n", ret); + return ret; + } + + dev_dbg(component->dev, "DSP divider %d\n", dsp_div); + dev_dbg(component->dev, "DAC divider %d\n", dac_div); + dev_dbg(component->dev, "NCP divider %d\n", ncp_div); + dev_dbg(component->dev, "OSR divider %d\n", osr_div); + dev_dbg(component->dev, "BCK divider %d\n", bclk_div); + dev_dbg(component->dev, "LRCK divider %d\n", lrclk_div); + dev_dbg(component->dev, "IDAC %d\n", idac); + dev_dbg(component->dev, "1<component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + int alen; + int gpio; + int clock_output; + int master_mode; + int ret; + + dev_dbg(component->dev, "hw_params %u Hz, %u channels\n", + params_rate(params), + params_channels(params)); + + switch (params_width(params)) { + case 16: + alen = PCM512x_ALEN_16; + break; + case 20: + alen = PCM512x_ALEN_20; + break; + case 24: + alen = PCM512x_ALEN_24; + break; + case 32: + alen = PCM512x_ALEN_32; + break; + default: + dev_err(component->dev, "Bad frame size: %d\n", + params_width(params)); + return -EINVAL; + } + + switch (pcm512x->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + ret = regmap_update_bits(pcm512x->regmap, + PCM512x_BCLK_LRCLK_CFG, + PCM512x_BCKP + | PCM512x_BCKO | PCM512x_LRKO, + 0); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable slave mode: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT, + PCM512x_DCAS, 0); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable clock divider autoset: %d\n", + ret); + return ret; + } + return 0; + case SND_SOC_DAIFMT_CBM_CFM: + clock_output = PCM512x_BCKO | PCM512x_LRKO; + master_mode = PCM512x_RLRK | PCM512x_RBCK; + break; + case SND_SOC_DAIFMT_CBM_CFS: + clock_output = PCM512x_BCKO; + master_mode = PCM512x_RBCK; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_I2S_1, + PCM512x_ALEN, alen); + if (ret != 0) { + dev_err(component->dev, "Failed to set frame size: %d\n", ret); + return ret; + } + + if (pcm512x->pll_out) { + ret = regmap_write(pcm512x->regmap, PCM512x_FLEX_A, 0x11); + if (ret != 0) { + dev_err(component->dev, "Failed to set FLEX_A: %d\n", ret); + return ret; + } + + ret = regmap_write(pcm512x->regmap, PCM512x_FLEX_B, 0xff); + if (ret != 0) { + dev_err(component->dev, "Failed to set FLEX_B: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT, + PCM512x_IDFS | PCM512x_IDBK + | PCM512x_IDSK | PCM512x_IDCH + | PCM512x_IDCM | PCM512x_DCAS + | PCM512x_IPLK, + PCM512x_IDFS | PCM512x_IDBK + | PCM512x_IDSK | PCM512x_IDCH + | PCM512x_DCAS); + if (ret != 0) { + dev_err(component->dev, + "Failed to ignore auto-clock failures: %d\n", + ret); + return ret; + } + } else { + ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT, + PCM512x_IDFS | PCM512x_IDBK + | PCM512x_IDSK | PCM512x_IDCH + | PCM512x_IDCM | PCM512x_DCAS + | PCM512x_IPLK, + PCM512x_IDFS | PCM512x_IDBK + | PCM512x_IDSK | PCM512x_IDCH + | PCM512x_DCAS | PCM512x_IPLK); + if (ret != 0) { + dev_err(component->dev, + "Failed to ignore auto-clock failures: %d\n", + ret); + return ret; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_EN, + PCM512x_PLLE, 0); + if (ret != 0) { + dev_err(component->dev, "Failed to disable pll: %d\n", ret); + return ret; + } + } + + ret = pcm512x_set_dividers(dai, params); + if (ret != 0) + return ret; + + if (pcm512x->pll_out) { + ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_REF, + PCM512x_SREF, PCM512x_SREF_GPIO); + if (ret != 0) { + dev_err(component->dev, + "Failed to set gpio as pllref: %d\n", ret); + return ret; + } + + gpio = PCM512x_GREF_GPIO1 + pcm512x->pll_in - 1; + ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_PLLIN, + PCM512x_GREF, gpio); + if (ret != 0) { + dev_err(component->dev, + "Failed to set gpio %d as pllin: %d\n", + pcm512x->pll_in, ret); + return ret; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_EN, + PCM512x_PLLE, PCM512x_PLLE); + if (ret != 0) { + dev_err(component->dev, "Failed to enable pll: %d\n", ret); + return ret; + } + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_BCLK_LRCLK_CFG, + PCM512x_BCKP | PCM512x_BCKO | PCM512x_LRKO, + clock_output); + if (ret != 0) { + dev_err(component->dev, "Failed to enable clock output: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_MASTER_MODE, + PCM512x_RLRK | PCM512x_RBCK, + master_mode); + if (ret != 0) { + dev_err(component->dev, "Failed to enable master mode: %d\n", ret); + return ret; + } + + if (pcm512x->pll_out) { + gpio = PCM512x_G1OE << (pcm512x->pll_out - 1); + ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_EN, + gpio, gpio); + if (ret != 0) { + dev_err(component->dev, "Failed to enable gpio %d: %d\n", + pcm512x->pll_out, ret); + return ret; + } + + gpio = PCM512x_GPIO_OUTPUT_1 + pcm512x->pll_out - 1; + ret = regmap_update_bits(pcm512x->regmap, gpio, + PCM512x_GxSL, PCM512x_GxSL_PLLCK); + if (ret != 0) { + dev_err(component->dev, "Failed to output pll on %d: %d\n", + ret, pcm512x->pll_out); + return ret; + } + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_SYNCHRONIZE, + PCM512x_RQSY, PCM512x_RQSY_HALT); + if (ret != 0) { + dev_err(component->dev, "Failed to halt clocks: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_SYNCHRONIZE, + PCM512x_RQSY, PCM512x_RQSY_RESUME); + if (ret != 0) { + dev_err(component->dev, "Failed to resume clocks: %d\n", ret); + return ret; + } + + return 0; +} + +static int pcm512x_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + + pcm512x->fmt = fmt; + + return 0; +} + +static const struct snd_soc_dai_ops pcm512x_dai_ops = { + .startup = pcm512x_dai_startup, + .hw_params = pcm512x_hw_params, + .set_fmt = pcm512x_set_fmt, +}; + +static struct snd_soc_dai_driver pcm512x_dai = { + .name = "pcm512x-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &pcm512x_dai_ops, +}; + +static const struct snd_soc_component_driver pcm512x_component_driver = { + .set_bias_level = pcm512x_set_bias_level, + .controls = pcm512x_controls, + .num_controls = ARRAY_SIZE(pcm512x_controls), + .dapm_widgets = pcm512x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(pcm512x_dapm_widgets), + .dapm_routes = pcm512x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(pcm512x_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_range_cfg pcm512x_range = { + .name = "Pages", .range_min = PCM512x_VIRT_BASE, + .range_max = PCM512x_MAX_REGISTER, + .selector_reg = PCM512x_PAGE, + .selector_mask = 0xff, + .window_start = 0, .window_len = 0x100, +}; + +const struct regmap_config pcm512x_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = pcm512x_readable, + .volatile_reg = pcm512x_volatile, + + .ranges = &pcm512x_range, + .num_ranges = 1, + + .max_register = PCM512x_MAX_REGISTER, + .reg_defaults = pcm512x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(pcm512x_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(pcm512x_regmap); + +int pcm512x_probe(struct device *dev, struct regmap *regmap) +{ + struct pcm512x_priv *pcm512x; + int i, ret; + + pcm512x = devm_kzalloc(dev, sizeof(struct pcm512x_priv), GFP_KERNEL); + if (!pcm512x) + return -ENOMEM; + + dev_set_drvdata(dev, pcm512x); + pcm512x->regmap = regmap; + + for (i = 0; i < ARRAY_SIZE(pcm512x->supplies); i++) + pcm512x->supplies[i].supply = pcm512x_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(pcm512x->supplies), + pcm512x->supplies); + if (ret != 0) { + dev_err(dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + pcm512x->supply_nb[0].notifier_call = pcm512x_regulator_event_0; + pcm512x->supply_nb[1].notifier_call = pcm512x_regulator_event_1; + pcm512x->supply_nb[2].notifier_call = pcm512x_regulator_event_2; + + for (i = 0; i < ARRAY_SIZE(pcm512x->supplies); i++) { + ret = regulator_register_notifier(pcm512x->supplies[i].consumer, + &pcm512x->supply_nb[i]); + if (ret != 0) { + dev_err(dev, + "Failed to register regulator notifier: %d\n", + ret); + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm512x->supplies), + pcm512x->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + /* Reset the device, verifying I/O in the process for I2C */ + ret = regmap_write(regmap, PCM512x_RESET, + PCM512x_RSTM | PCM512x_RSTR); + if (ret != 0) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err; + } + + ret = regmap_write(regmap, PCM512x_RESET, 0); + if (ret != 0) { + dev_err(dev, "Failed to reset device: %d\n", ret); + goto err; + } + + pcm512x->sclk = devm_clk_get(dev, NULL); + if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err; + } + if (!IS_ERR(pcm512x->sclk)) { + ret = clk_prepare_enable(pcm512x->sclk); + if (ret != 0) { + dev_err(dev, "Failed to enable SCLK: %d\n", ret); + goto err; + } + } + + /* Default to standby mode */ + ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, + PCM512x_RQST, PCM512x_RQST); + if (ret != 0) { + dev_err(dev, "Failed to request standby: %d\n", + ret); + goto err_clk; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + +#ifdef CONFIG_OF + if (dev->of_node) { + const struct device_node *np = dev->of_node; + u32 val; + + if (of_property_read_u32(np, "pll-in", &val) >= 0) { + if (val > 6) { + dev_err(dev, "Invalid pll-in\n"); + ret = -EINVAL; + goto err_clk; + } + pcm512x->pll_in = val; + } + + if (of_property_read_u32(np, "pll-out", &val) >= 0) { + if (val > 6) { + dev_err(dev, "Invalid pll-out\n"); + ret = -EINVAL; + goto err_clk; + } + pcm512x->pll_out = val; + } + + if (!pcm512x->pll_in != !pcm512x->pll_out) { + dev_err(dev, + "Error: both pll-in and pll-out, or none\n"); + ret = -EINVAL; + goto err_clk; + } + if (pcm512x->pll_in && pcm512x->pll_in == pcm512x->pll_out) { + dev_err(dev, "Error: pll-in == pll-out\n"); + ret = -EINVAL; + goto err_clk; + } + } +#endif + + ret = devm_snd_soc_register_component(dev, &pcm512x_component_driver, + &pcm512x_dai, 1); + if (ret != 0) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + goto err_pm; + } + + return 0; + +err_pm: + pm_runtime_disable(dev); +err_clk: + if (!IS_ERR(pcm512x->sclk)) + clk_disable_unprepare(pcm512x->sclk); +err: + regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies), + pcm512x->supplies); + return ret; +} +EXPORT_SYMBOL_GPL(pcm512x_probe); + +void pcm512x_remove(struct device *dev) +{ + struct pcm512x_priv *pcm512x = dev_get_drvdata(dev); + + pm_runtime_disable(dev); + if (!IS_ERR(pcm512x->sclk)) + clk_disable_unprepare(pcm512x->sclk); + regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies), + pcm512x->supplies); +} +EXPORT_SYMBOL_GPL(pcm512x_remove); + +#ifdef CONFIG_PM +static int pcm512x_suspend(struct device *dev) +{ + struct pcm512x_priv *pcm512x = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, + PCM512x_RQPD, PCM512x_RQPD); + if (ret != 0) { + dev_err(dev, "Failed to request power down: %d\n", ret); + return ret; + } + + ret = regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies), + pcm512x->supplies); + if (ret != 0) { + dev_err(dev, "Failed to disable supplies: %d\n", ret); + return ret; + } + + if (!IS_ERR(pcm512x->sclk)) + clk_disable_unprepare(pcm512x->sclk); + + return 0; +} + +static int pcm512x_resume(struct device *dev) +{ + struct pcm512x_priv *pcm512x = dev_get_drvdata(dev); + int ret; + + if (!IS_ERR(pcm512x->sclk)) { + ret = clk_prepare_enable(pcm512x->sclk); + if (ret != 0) { + dev_err(dev, "Failed to enable SCLK: %d\n", ret); + return ret; + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(pcm512x->supplies), + pcm512x->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(pcm512x->regmap, false); + ret = regcache_sync(pcm512x->regmap); + if (ret != 0) { + dev_err(dev, "Failed to sync cache: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, + PCM512x_RQPD, 0); + if (ret != 0) { + dev_err(dev, "Failed to remove power down: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +const struct dev_pm_ops pcm512x_pm_ops = { + SET_RUNTIME_PM_OPS(pcm512x_suspend, pcm512x_resume, NULL) +}; +EXPORT_SYMBOL_GPL(pcm512x_pm_ops); + +MODULE_DESCRIPTION("ASoC PCM512x codec driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/pcm512x.h b/sound/soc/codecs/pcm512x.h new file mode 100644 index 000000000..d70d9c0c2 --- /dev/null +++ b/sound/soc/codecs/pcm512x.h @@ -0,0 +1,270 @@ +/* + * Driver for the PCM512x CODECs + * + * Author: Mark Brown + * Copyright 2014 Linaro Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef _SND_SOC_PCM512X +#define _SND_SOC_PCM512X + +#include +#include + +#define PCM512x_VIRT_BASE 0x100 +#define PCM512x_PAGE_LEN 0x100 +#define PCM512x_PAGE_BASE(n) (PCM512x_VIRT_BASE + (PCM512x_PAGE_LEN * n)) + +#define PCM512x_PAGE 0 + +#define PCM512x_RESET (PCM512x_PAGE_BASE(0) + 1) +#define PCM512x_POWER (PCM512x_PAGE_BASE(0) + 2) +#define PCM512x_MUTE (PCM512x_PAGE_BASE(0) + 3) +#define PCM512x_PLL_EN (PCM512x_PAGE_BASE(0) + 4) +#define PCM512x_SPI_MISO_FUNCTION (PCM512x_PAGE_BASE(0) + 6) +#define PCM512x_DSP (PCM512x_PAGE_BASE(0) + 7) +#define PCM512x_GPIO_EN (PCM512x_PAGE_BASE(0) + 8) +#define PCM512x_BCLK_LRCLK_CFG (PCM512x_PAGE_BASE(0) + 9) +#define PCM512x_DSP_GPIO_INPUT (PCM512x_PAGE_BASE(0) + 10) +#define PCM512x_MASTER_MODE (PCM512x_PAGE_BASE(0) + 12) +#define PCM512x_PLL_REF (PCM512x_PAGE_BASE(0) + 13) +#define PCM512x_DAC_REF (PCM512x_PAGE_BASE(0) + 14) +#define PCM512x_GPIO_DACIN (PCM512x_PAGE_BASE(0) + 16) +#define PCM512x_GPIO_PLLIN (PCM512x_PAGE_BASE(0) + 18) +#define PCM512x_SYNCHRONIZE (PCM512x_PAGE_BASE(0) + 19) +#define PCM512x_PLL_COEFF_0 (PCM512x_PAGE_BASE(0) + 20) +#define PCM512x_PLL_COEFF_1 (PCM512x_PAGE_BASE(0) + 21) +#define PCM512x_PLL_COEFF_2 (PCM512x_PAGE_BASE(0) + 22) +#define PCM512x_PLL_COEFF_3 (PCM512x_PAGE_BASE(0) + 23) +#define PCM512x_PLL_COEFF_4 (PCM512x_PAGE_BASE(0) + 24) +#define PCM512x_DSP_CLKDIV (PCM512x_PAGE_BASE(0) + 27) +#define PCM512x_DAC_CLKDIV (PCM512x_PAGE_BASE(0) + 28) +#define PCM512x_NCP_CLKDIV (PCM512x_PAGE_BASE(0) + 29) +#define PCM512x_OSR_CLKDIV (PCM512x_PAGE_BASE(0) + 30) +#define PCM512x_MASTER_CLKDIV_1 (PCM512x_PAGE_BASE(0) + 32) +#define PCM512x_MASTER_CLKDIV_2 (PCM512x_PAGE_BASE(0) + 33) +#define PCM512x_FS_SPEED_MODE (PCM512x_PAGE_BASE(0) + 34) +#define PCM512x_IDAC_1 (PCM512x_PAGE_BASE(0) + 35) +#define PCM512x_IDAC_2 (PCM512x_PAGE_BASE(0) + 36) +#define PCM512x_ERROR_DETECT (PCM512x_PAGE_BASE(0) + 37) +#define PCM512x_I2S_1 (PCM512x_PAGE_BASE(0) + 40) +#define PCM512x_I2S_2 (PCM512x_PAGE_BASE(0) + 41) +#define PCM512x_DAC_ROUTING (PCM512x_PAGE_BASE(0) + 42) +#define PCM512x_DSP_PROGRAM (PCM512x_PAGE_BASE(0) + 43) +#define PCM512x_CLKDET (PCM512x_PAGE_BASE(0) + 44) +#define PCM512x_AUTO_MUTE (PCM512x_PAGE_BASE(0) + 59) +#define PCM512x_DIGITAL_VOLUME_1 (PCM512x_PAGE_BASE(0) + 60) +#define PCM512x_DIGITAL_VOLUME_2 (PCM512x_PAGE_BASE(0) + 61) +#define PCM512x_DIGITAL_VOLUME_3 (PCM512x_PAGE_BASE(0) + 62) +#define PCM512x_DIGITAL_MUTE_1 (PCM512x_PAGE_BASE(0) + 63) +#define PCM512x_DIGITAL_MUTE_2 (PCM512x_PAGE_BASE(0) + 64) +#define PCM512x_DIGITAL_MUTE_3 (PCM512x_PAGE_BASE(0) + 65) +#define PCM512x_GPIO_OUTPUT_1 (PCM512x_PAGE_BASE(0) + 80) +#define PCM512x_GPIO_OUTPUT_2 (PCM512x_PAGE_BASE(0) + 81) +#define PCM512x_GPIO_OUTPUT_3 (PCM512x_PAGE_BASE(0) + 82) +#define PCM512x_GPIO_OUTPUT_4 (PCM512x_PAGE_BASE(0) + 83) +#define PCM512x_GPIO_OUTPUT_5 (PCM512x_PAGE_BASE(0) + 84) +#define PCM512x_GPIO_OUTPUT_6 (PCM512x_PAGE_BASE(0) + 85) +#define PCM512x_GPIO_CONTROL_1 (PCM512x_PAGE_BASE(0) + 86) +#define PCM512x_GPIO_CONTROL_2 (PCM512x_PAGE_BASE(0) + 87) +#define PCM512x_OVERFLOW (PCM512x_PAGE_BASE(0) + 90) +#define PCM512x_RATE_DET_1 (PCM512x_PAGE_BASE(0) + 91) +#define PCM512x_RATE_DET_2 (PCM512x_PAGE_BASE(0) + 92) +#define PCM512x_RATE_DET_3 (PCM512x_PAGE_BASE(0) + 93) +#define PCM512x_RATE_DET_4 (PCM512x_PAGE_BASE(0) + 94) +#define PCM512x_CLOCK_STATUS (PCM512x_PAGE_BASE(0) + 95) +#define PCM512x_ANALOG_MUTE_DET (PCM512x_PAGE_BASE(0) + 108) +#define PCM512x_GPIN (PCM512x_PAGE_BASE(0) + 119) +#define PCM512x_DIGITAL_MUTE_DET (PCM512x_PAGE_BASE(0) + 120) + +#define PCM512x_OUTPUT_AMPLITUDE (PCM512x_PAGE_BASE(1) + 1) +#define PCM512x_ANALOG_GAIN_CTRL (PCM512x_PAGE_BASE(1) + 2) +#define PCM512x_UNDERVOLTAGE_PROT (PCM512x_PAGE_BASE(1) + 5) +#define PCM512x_ANALOG_MUTE_CTRL (PCM512x_PAGE_BASE(1) + 6) +#define PCM512x_ANALOG_GAIN_BOOST (PCM512x_PAGE_BASE(1) + 7) +#define PCM512x_VCOM_CTRL_1 (PCM512x_PAGE_BASE(1) + 8) +#define PCM512x_VCOM_CTRL_2 (PCM512x_PAGE_BASE(1) + 9) + +#define PCM512x_CRAM_CTRL (PCM512x_PAGE_BASE(44) + 1) + +#define PCM512x_FLEX_A (PCM512x_PAGE_BASE(253) + 63) +#define PCM512x_FLEX_B (PCM512x_PAGE_BASE(253) + 64) + +#define PCM512x_MAX_REGISTER (PCM512x_PAGE_BASE(253) + 64) + +/* Page 0, Register 1 - reset */ +#define PCM512x_RSTR (1 << 0) +#define PCM512x_RSTM (1 << 4) + +/* Page 0, Register 2 - power */ +#define PCM512x_RQPD (1 << 0) +#define PCM512x_RQPD_SHIFT 0 +#define PCM512x_RQST (1 << 4) +#define PCM512x_RQST_SHIFT 4 + +/* Page 0, Register 3 - mute */ +#define PCM512x_RQMR_SHIFT 0 +#define PCM512x_RQML_SHIFT 4 + +/* Page 0, Register 4 - PLL */ +#define PCM512x_PLLE (1 << 0) +#define PCM512x_PLLE_SHIFT 0 +#define PCM512x_PLCK (1 << 4) +#define PCM512x_PLCK_SHIFT 4 + +/* Page 0, Register 7 - DSP */ +#define PCM512x_SDSL (1 << 0) +#define PCM512x_SDSL_SHIFT 0 +#define PCM512x_DEMP (1 << 4) +#define PCM512x_DEMP_SHIFT 4 + +/* Page 0, Register 8 - GPIO output enable */ +#define PCM512x_G1OE (1 << 0) +#define PCM512x_G2OE (1 << 1) +#define PCM512x_G3OE (1 << 2) +#define PCM512x_G4OE (1 << 3) +#define PCM512x_G5OE (1 << 4) +#define PCM512x_G6OE (1 << 5) + +/* Page 0, Register 9 - BCK, LRCLK configuration */ +#define PCM512x_LRKO (1 << 0) +#define PCM512x_LRKO_SHIFT 0 +#define PCM512x_BCKO (1 << 4) +#define PCM512x_BCKO_SHIFT 4 +#define PCM512x_BCKP (1 << 5) +#define PCM512x_BCKP_SHIFT 5 + +/* Page 0, Register 12 - Master mode BCK, LRCLK reset */ +#define PCM512x_RLRK (1 << 0) +#define PCM512x_RLRK_SHIFT 0 +#define PCM512x_RBCK (1 << 1) +#define PCM512x_RBCK_SHIFT 1 + +/* Page 0, Register 13 - PLL reference */ +#define PCM512x_SREF (7 << 4) +#define PCM512x_SREF_SHIFT 4 +#define PCM512x_SREF_SCK (0 << 4) +#define PCM512x_SREF_BCK (1 << 4) +#define PCM512x_SREF_GPIO (3 << 4) + +/* Page 0, Register 14 - DAC reference */ +#define PCM512x_SDAC (7 << 4) +#define PCM512x_SDAC_SHIFT 4 +#define PCM512x_SDAC_MCK (0 << 4) +#define PCM512x_SDAC_PLL (1 << 4) +#define PCM512x_SDAC_SCK (3 << 4) +#define PCM512x_SDAC_BCK (4 << 4) +#define PCM512x_SDAC_GPIO (5 << 4) + +/* Page 0, Register 16, 18 - GPIO source for DAC, PLL */ +#define PCM512x_GREF (7 << 0) +#define PCM512x_GREF_SHIFT 0 +#define PCM512x_GREF_GPIO1 (0 << 0) +#define PCM512x_GREF_GPIO2 (1 << 0) +#define PCM512x_GREF_GPIO3 (2 << 0) +#define PCM512x_GREF_GPIO4 (3 << 0) +#define PCM512x_GREF_GPIO5 (4 << 0) +#define PCM512x_GREF_GPIO6 (5 << 0) + +/* Page 0, Register 19 - synchronize */ +#define PCM512x_RQSY (1 << 0) +#define PCM512x_RQSY_RESUME (0 << 0) +#define PCM512x_RQSY_HALT (1 << 0) + +/* Page 0, Register 34 - fs speed mode */ +#define PCM512x_FSSP (3 << 0) +#define PCM512x_FSSP_SHIFT 0 +#define PCM512x_FSSP_48KHZ (0 << 0) +#define PCM512x_FSSP_96KHZ (1 << 0) +#define PCM512x_FSSP_192KHZ (2 << 0) +#define PCM512x_FSSP_384KHZ (3 << 0) + +/* Page 0, Register 37 - Error detection */ +#define PCM512x_IPLK (1 << 0) +#define PCM512x_DCAS (1 << 1) +#define PCM512x_IDCM (1 << 2) +#define PCM512x_IDCH (1 << 3) +#define PCM512x_IDSK (1 << 4) +#define PCM512x_IDBK (1 << 5) +#define PCM512x_IDFS (1 << 6) + +/* Page 0, Register 40 - I2S configuration */ +#define PCM512x_ALEN (3 << 0) +#define PCM512x_ALEN_SHIFT 0 +#define PCM512x_ALEN_16 (0 << 0) +#define PCM512x_ALEN_20 (1 << 0) +#define PCM512x_ALEN_24 (2 << 0) +#define PCM512x_ALEN_32 (3 << 0) +#define PCM512x_AFMT (3 << 4) +#define PCM512x_AFMT_SHIFT 4 +#define PCM512x_AFMT_I2S (0 << 4) +#define PCM512x_AFMT_DSP (1 << 4) +#define PCM512x_AFMT_RTJ (2 << 4) +#define PCM512x_AFMT_LTJ (3 << 4) + +/* Page 0, Register 42 - DAC routing */ +#define PCM512x_AUPR_SHIFT 0 +#define PCM512x_AUPL_SHIFT 4 + +/* Page 0, Register 59 - auto mute */ +#define PCM512x_ATMR_SHIFT 0 +#define PCM512x_ATML_SHIFT 4 + +/* Page 0, Register 63 - ramp rates */ +#define PCM512x_VNDF_SHIFT 6 +#define PCM512x_VNDS_SHIFT 4 +#define PCM512x_VNUF_SHIFT 2 +#define PCM512x_VNUS_SHIFT 0 + +/* Page 0, Register 64 - emergency ramp rates */ +#define PCM512x_VEDF_SHIFT 6 +#define PCM512x_VEDS_SHIFT 4 + +/* Page 0, Register 65 - Digital mute enables */ +#define PCM512x_ACTL_SHIFT 2 +#define PCM512x_AMLE_SHIFT 1 +#define PCM512x_AMRE_SHIFT 0 + +/* Page 0, Register 80-85, GPIO output selection */ +#define PCM512x_GxSL (31 << 0) +#define PCM512x_GxSL_SHIFT 0 +#define PCM512x_GxSL_OFF (0 << 0) +#define PCM512x_GxSL_DSP (1 << 0) +#define PCM512x_GxSL_REG (2 << 0) +#define PCM512x_GxSL_AMUTB (3 << 0) +#define PCM512x_GxSL_AMUTL (4 << 0) +#define PCM512x_GxSL_AMUTR (5 << 0) +#define PCM512x_GxSL_CLKI (6 << 0) +#define PCM512x_GxSL_SDOUT (7 << 0) +#define PCM512x_GxSL_ANMUL (8 << 0) +#define PCM512x_GxSL_ANMUR (9 << 0) +#define PCM512x_GxSL_PLLLK (10 << 0) +#define PCM512x_GxSL_CPCLK (11 << 0) +#define PCM512x_GxSL_UV0_7 (14 << 0) +#define PCM512x_GxSL_UV0_3 (15 << 0) +#define PCM512x_GxSL_PLLCK (16 << 0) + +/* Page 1, Register 2 - analog volume control */ +#define PCM512x_RAGN_SHIFT 0 +#define PCM512x_LAGN_SHIFT 4 + +/* Page 1, Register 7 - analog boost control */ +#define PCM512x_AGBR_SHIFT 0 +#define PCM512x_AGBL_SHIFT 4 + +extern const struct dev_pm_ops pcm512x_pm_ops; +extern const struct regmap_config pcm512x_regmap; + +int pcm512x_probe(struct device *dev, struct regmap *regmap); +void pcm512x_remove(struct device *dev); + +#endif diff --git a/sound/soc/codecs/rl6231.c b/sound/soc/codecs/rl6231.c new file mode 100644 index 000000000..7ef3b5476 --- /dev/null +++ b/sound/soc/codecs/rl6231.c @@ -0,0 +1,244 @@ +/* + * rl6231.c - RL6231 class device shared support + * + * Copyright 2014 Realtek Semiconductor Corp. + * + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include +#include "rl6231.h" + +/** + * rl6231_get_pre_div - Return the value of pre divider. + * + * @map: map for setting. + * @reg: register. + * @sft: shift. + * + * Return the value of pre divider from given register value. + * Return negative error code for unexpected register value. + */ +int rl6231_get_pre_div(struct regmap *map, unsigned int reg, int sft) +{ + int pd, val; + + regmap_read(map, reg, &val); + + val = (val >> sft) & 0x7; + + switch (val) { + case 0: + case 1: + case 2: + case 3: + pd = val + 1; + break; + case 4: + pd = 6; + break; + case 5: + pd = 8; + break; + case 6: + pd = 12; + break; + case 7: + pd = 16; + break; + default: + pd = -EINVAL; + break; + } + + return pd; +} +EXPORT_SYMBOL_GPL(rl6231_get_pre_div); + +/** + * rl6231_calc_dmic_clk - Calculate the frequency divider parameter of dmic. + * + * @rate: base clock rate. + * + * Choose divider parameter that gives the highest possible DMIC frequency in + * 1MHz - 3MHz range. + */ +int rl6231_calc_dmic_clk(int rate) +{ + static const int div[] = {2, 3, 4, 6, 8, 12}; + int i; + + if (rate < 1000000 * div[0]) { + pr_warn("Base clock rate %d is too low\n", rate); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(div); i++) { + if ((div[i] % 3) == 0) + continue; + /* find divider that gives DMIC frequency below 3.072MHz */ + if (3072000 * div[i] >= rate) + return i; + } + + pr_warn("Base clock rate %d is too high\n", rate); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(rl6231_calc_dmic_clk); + +struct pll_calc_map { + unsigned int pll_in; + unsigned int pll_out; + int k; + int n; + int m; + bool m_bp; +}; + +static const struct pll_calc_map pll_preset_table[] = { + {19200000, 4096000, 23, 14, 1, false}, + {19200000, 24576000, 3, 30, 3, false}, +}; + +static unsigned int find_best_div(unsigned int in, + unsigned int max, unsigned int div) +{ + unsigned int d; + + if (in <= max) + return 1; + + d = in / max; + if (in % max) + d++; + + while (div % d != 0) + d++; + + + return d; +} + +/** + * rl6231_pll_calc - Calcualte PLL M/N/K code. + * @freq_in: external clock provided to codec. + * @freq_out: target clock which codec works on. + * @pll_code: Pointer to structure with M, N, K and bypass flag. + * + * Calcualte M/N/K code to configure PLL for codec. + * + * Returns 0 for success or negative error code. + */ +int rl6231_pll_calc(const unsigned int freq_in, + const unsigned int freq_out, struct rl6231_pll_code *pll_code) +{ + int max_n = RL6231_PLL_N_MAX, max_m = RL6231_PLL_M_MAX; + int i, k, n_t; + int k_t, min_k, max_k, n = 0, m = 0, m_t = 0; + unsigned int red, pll_out, in_t, out_t, div, div_t; + unsigned int red_t = abs(freq_out - freq_in); + unsigned int f_in, f_out, f_max; + bool bypass = false; + + if (RL6231_PLL_INP_MAX < freq_in || RL6231_PLL_INP_MIN > freq_in) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(pll_preset_table); i++) { + if (freq_in == pll_preset_table[i].pll_in && + freq_out == pll_preset_table[i].pll_out) { + k = pll_preset_table[i].k; + m = pll_preset_table[i].m; + n = pll_preset_table[i].n; + bypass = pll_preset_table[i].m_bp; + pr_debug("Use preset PLL parameter table\n"); + goto code_find; + } + } + + min_k = 80000000 / freq_out - 2; + max_k = 150000000 / freq_out - 2; + if (max_k > RL6231_PLL_K_MAX) + max_k = RL6231_PLL_K_MAX; + if (min_k > RL6231_PLL_K_MAX) + min_k = max_k = RL6231_PLL_K_MAX; + div_t = gcd(freq_in, freq_out); + f_max = 0xffffffff / RL6231_PLL_N_MAX; + div = find_best_div(freq_in, f_max, div_t); + f_in = freq_in / div; + f_out = freq_out / div; + k = min_k; + for (k_t = min_k; k_t <= max_k; k_t++) { + for (n_t = 0; n_t <= max_n; n_t++) { + in_t = f_in * (n_t + 2); + pll_out = f_out * (k_t + 2); + if (in_t == pll_out) { + bypass = true; + n = n_t; + k = k_t; + goto code_find; + } + out_t = in_t / (k_t + 2); + red = abs(f_out - out_t); + if (red < red_t) { + bypass = true; + n = n_t; + m = 0; + k = k_t; + if (red == 0) + goto code_find; + red_t = red; + } + for (m_t = 0; m_t <= max_m; m_t++) { + out_t = in_t / ((m_t + 2) * (k_t + 2)); + red = abs(f_out - out_t); + if (red < red_t) { + bypass = false; + n = n_t; + m = m_t; + k = k_t; + if (red == 0) + goto code_find; + red_t = red; + } + } + } + } + pr_debug("Only get approximation about PLL\n"); + +code_find: + + pll_code->m_bp = bypass; + pll_code->m_code = m; + pll_code->n_code = n; + pll_code->k_code = k; + return 0; +} +EXPORT_SYMBOL_GPL(rl6231_pll_calc); + +int rl6231_get_clk_info(int sclk, int rate) +{ + int i; + static const int pd[] = {1, 2, 3, 4, 6, 8, 12, 16}; + + if (sclk <= 0 || rate <= 0) + return -EINVAL; + + rate = rate << 8; + for (i = 0; i < ARRAY_SIZE(pd); i++) + if (sclk == rate * pd[i]) + return i; + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(rl6231_get_clk_info); + +MODULE_DESCRIPTION("RL6231 class device shared support"); +MODULE_AUTHOR("Oder Chiou "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rl6231.h b/sound/soc/codecs/rl6231.h new file mode 100644 index 000000000..4c77b441f --- /dev/null +++ b/sound/soc/codecs/rl6231.h @@ -0,0 +1,35 @@ +/* + * rl6231.h - RL6231 class device shared support + * + * Copyright 2014 Realtek Semiconductor Corp. + * + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RL6231_H__ +#define __RL6231_H__ + +#define RL6231_PLL_INP_MAX 40000000 +#define RL6231_PLL_INP_MIN 256000 +#define RL6231_PLL_N_MAX 0x1ff +#define RL6231_PLL_K_MAX 0x1f +#define RL6231_PLL_M_MAX 0xf + +struct rl6231_pll_code { + bool m_bp; /* Indicates bypass m code or not. */ + int m_code; + int n_code; + int k_code; +}; + +int rl6231_calc_dmic_clk(int rate); +int rl6231_pll_calc(const unsigned int freq_in, + const unsigned int freq_out, struct rl6231_pll_code *pll_code); +int rl6231_get_clk_info(int sclk, int rate); +int rl6231_get_pre_div(struct regmap *map, unsigned int reg, int sft); + +#endif /* __RL6231_H__ */ diff --git a/sound/soc/codecs/rl6347a.c b/sound/soc/codecs/rl6347a.c new file mode 100644 index 000000000..8f571cf8e --- /dev/null +++ b/sound/soc/codecs/rl6347a.c @@ -0,0 +1,111 @@ +/* + * rl6347a.c - RL6347A class device shared support + * + * Copyright 2015 Realtek Semiconductor Corp. + * + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "rl6347a.h" + +int rl6347a_hw_write(void *context, unsigned int reg, unsigned int value) +{ + struct i2c_client *client = context; + struct rl6347a_priv *rl6347a = i2c_get_clientdata(client); + u8 data[4]; + int ret, i; + + /* handle index registers */ + if (reg <= 0xff) { + rl6347a_hw_write(client, RL6347A_COEF_INDEX, reg); + for (i = 0; i < rl6347a->index_cache_size; i++) { + if (reg == rl6347a->index_cache[i].reg) { + rl6347a->index_cache[i].def = value; + break; + } + + } + reg = RL6347A_PROC_COEF; + } + + data[0] = (reg >> 24) & 0xff; + data[1] = (reg >> 16) & 0xff; + /* + * 4 bit VID: reg should be 0 + * 12 bit VID: value should be 0 + * So we use an OR operator to handle it rather than use if condition. + */ + data[2] = ((reg >> 8) & 0xff) | ((value >> 8) & 0xff); + data[3] = value & 0xff; + + ret = i2c_master_send(client, data, 4); + + if (ret == 4) + return 0; + else + dev_err(&client->dev, "I2C error %d\n", ret); + if (ret < 0) + return ret; + else + return -EIO; +} +EXPORT_SYMBOL_GPL(rl6347a_hw_write); + +int rl6347a_hw_read(void *context, unsigned int reg, unsigned int *value) +{ + struct i2c_client *client = context; + struct i2c_msg xfer[2]; + int ret; + __be32 be_reg; + unsigned int index, vid, buf = 0x0; + + /* handle index registers */ + if (reg <= 0xff) { + rl6347a_hw_write(client, RL6347A_COEF_INDEX, reg); + reg = RL6347A_PROC_COEF; + } + + reg = reg | 0x80000; + vid = (reg >> 8) & 0xfff; + + if (AC_VERB_GET_AMP_GAIN_MUTE == (vid & 0xf00)) { + index = (reg >> 8) & 0xf; + reg = (reg & ~0xf0f) | index; + } + be_reg = cpu_to_be32(reg); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 4; + xfer[0].buf = (u8 *)&be_reg; + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 4; + xfer[1].buf = (u8 *)&buf; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + + *value = be32_to_cpu(buf); + + return 0; +} +EXPORT_SYMBOL_GPL(rl6347a_hw_read); + +MODULE_DESCRIPTION("RL6347A class device shared support"); +MODULE_AUTHOR("Oder Chiou "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rl6347a.h b/sound/soc/codecs/rl6347a.h new file mode 100644 index 000000000..e127919cb --- /dev/null +++ b/sound/soc/codecs/rl6347a.h @@ -0,0 +1,34 @@ +/* + * rl6347a.h - RL6347A class device shared support + * + * Copyright 2015 Realtek Semiconductor Corp. + * + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __RL6347A_H__ +#define __RL6347A_H__ + +#include + +#define VERB_CMD(V, N, D) ((N << 20) | (V << 8) | D) + +#define RL6347A_VENDOR_REGISTERS 0x20 + +#define RL6347A_COEF_INDEX\ + VERB_CMD(AC_VERB_SET_COEF_INDEX, RL6347A_VENDOR_REGISTERS, 0) +#define RL6347A_PROC_COEF\ + VERB_CMD(AC_VERB_SET_PROC_COEF, RL6347A_VENDOR_REGISTERS, 0) + +struct rl6347a_priv { + struct reg_default *index_cache; + int index_cache_size; +}; + +int rl6347a_hw_write(void *context, unsigned int reg, unsigned int value); +int rl6347a_hw_read(void *context, unsigned int reg, unsigned int *value); + +#endif /* __RL6347A_H__ */ diff --git a/sound/soc/codecs/rt1305.c b/sound/soc/codecs/rt1305.c new file mode 100644 index 000000000..c4452efc7 --- /dev/null +++ b/sound/soc/codecs/rt1305.c @@ -0,0 +1,1184 @@ +/* + * rt1305.c -- RT1305 ALSA SoC amplifier component driver + * + * Copyright 2018 Realtek Semiconductor Corp. + * Author: Shuming Fan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt1305.h" + + +#define RT1305_PR_RANGE_BASE (0xff + 1) +#define RT1305_PR_SPACING 0x100 + +#define RT1305_PR_BASE (RT1305_PR_RANGE_BASE + (0 * RT1305_PR_SPACING)) + + +static const struct regmap_range_cfg rt1305_ranges[] = { + { + .name = "PR", + .range_min = RT1305_PR_BASE, + .range_max = RT1305_PR_BASE + 0xff, + .selector_reg = RT1305_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT1305_PRIV_DATA, + .window_len = 0x1, + }, +}; + + +static const struct reg_sequence init_list[] = { + + { RT1305_PR_BASE + 0xcf, 0x5548 }, + { RT1305_PR_BASE + 0x5d, 0x0442 }, + { RT1305_PR_BASE + 0xc1, 0x0320 }, + + { RT1305_POWER_STATUS, 0x0000 }, + + { RT1305_SPK_TEMP_PROTECTION_1, 0xd6de }, + { RT1305_SPK_TEMP_PROTECTION_2, 0x0707 }, + { RT1305_SPK_TEMP_PROTECTION_3, 0x4090 }, + + { RT1305_DAC_SET_1, 0xdfdf }, /* 4 ohm 2W */ + { RT1305_ADC_SET_3, 0x0219 }, + { RT1305_ADC_SET_1, 0x170f }, /* 0.2 ohm RSense*/ + +}; +#define RT1305_INIT_REG_LEN ARRAY_SIZE(init_list) + +struct rt1305_priv { + struct snd_soc_component *component; + struct regmap *regmap; + + int sysclk; + int sysclk_src; + int lrck; + int bclk; + int master; + + int pll_src; + int pll_in; + int pll_out; +}; + +static const struct reg_default rt1305_reg[] = { + + { 0x04, 0x0400 }, + { 0x05, 0x0880 }, + { 0x06, 0x0000 }, + { 0x07, 0x3100 }, + { 0x08, 0x8000 }, + { 0x09, 0x0000 }, + { 0x0a, 0x087e }, + { 0x0b, 0x0020 }, + { 0x0c, 0x0802 }, + { 0x0d, 0x0020 }, + { 0x10, 0x1d1d }, + { 0x11, 0x1d1d }, + { 0x12, 0xffff }, + { 0x14, 0x000c }, + { 0x16, 0x1717 }, + { 0x17, 0x4000 }, + { 0x18, 0x0019 }, + { 0x20, 0x0000 }, + { 0x22, 0x0000 }, + { 0x24, 0x0000 }, + { 0x26, 0x0000 }, + { 0x28, 0x0000 }, + { 0x2a, 0x4000 }, + { 0x2b, 0x3000 }, + { 0x2d, 0x6000 }, + { 0x2e, 0x0000 }, + { 0x2f, 0x8000 }, + { 0x32, 0x0000 }, + { 0x39, 0x0001 }, + { 0x3a, 0x0000 }, + { 0x3b, 0x1020 }, + { 0x3c, 0x0000 }, + { 0x3d, 0x0000 }, + { 0x3e, 0x4c00 }, + { 0x3f, 0x3000 }, + { 0x40, 0x000c }, + { 0x42, 0x0400 }, + { 0x46, 0xc22c }, + { 0x47, 0x0000 }, + { 0x4b, 0x0000 }, + { 0x4c, 0x0300 }, + { 0x4f, 0xf000 }, + { 0x50, 0xc200 }, + { 0x51, 0x1f1f }, + { 0x52, 0x01f0 }, + { 0x53, 0x407f }, + { 0x54, 0xffff }, + { 0x58, 0x4005 }, + { 0x5e, 0x0000 }, + { 0x5f, 0x0000 }, + { 0x60, 0xee13 }, + { 0x62, 0x0000 }, + { 0x63, 0x5f5f }, + { 0x64, 0x0040 }, + { 0x65, 0x4000 }, + { 0x66, 0x4004 }, + { 0x67, 0x0306 }, + { 0x68, 0x8c04 }, + { 0x69, 0xe021 }, + { 0x6a, 0x0000 }, + { 0x6c, 0xaaaa }, + { 0x70, 0x0333 }, + { 0x71, 0x3330 }, + { 0x72, 0x3333 }, + { 0x73, 0x3300 }, + { 0x74, 0x0000 }, + { 0x75, 0x0000 }, + { 0x76, 0x0000 }, + { 0x7a, 0x0003 }, + { 0x7c, 0x10ec }, + { 0x7e, 0x6251 }, + { 0x80, 0x0800 }, + { 0x81, 0x4000 }, + { 0x82, 0x0000 }, + { 0x90, 0x7a01 }, + { 0x91, 0x8431 }, + { 0x92, 0x0180 }, + { 0x93, 0x0000 }, + { 0x94, 0x0000 }, + { 0x95, 0x0000 }, + { 0x96, 0x0000 }, + { 0x97, 0x0000 }, + { 0x98, 0x0000 }, + { 0x99, 0x0000 }, + { 0x9a, 0x0000 }, + { 0x9b, 0x0000 }, + { 0x9c, 0x0000 }, + { 0x9d, 0x0000 }, + { 0x9e, 0x0000 }, + { 0x9f, 0x0000 }, + { 0xa0, 0x0000 }, + { 0xb0, 0x8200 }, + { 0xb1, 0x00ff }, + { 0xb2, 0x0008 }, + { 0xc0, 0x0200 }, + { 0xc1, 0x0000 }, + { 0xc2, 0x0000 }, + { 0xc3, 0x0000 }, + { 0xc4, 0x0000 }, + { 0xc5, 0x0000 }, + { 0xc6, 0x0000 }, + { 0xc7, 0x0000 }, + { 0xc8, 0x0000 }, + { 0xc9, 0x0000 }, + { 0xca, 0x0200 }, + { 0xcb, 0x0000 }, + { 0xcc, 0x0000 }, + { 0xcd, 0x0000 }, + { 0xce, 0x0000 }, + { 0xcf, 0x0000 }, + { 0xd0, 0x0000 }, + { 0xd1, 0x0000 }, + { 0xd2, 0x0000 }, + { 0xd3, 0x0000 }, + { 0xd4, 0x0200 }, + { 0xd5, 0x0000 }, + { 0xd6, 0x0000 }, + { 0xd7, 0x0000 }, + { 0xd8, 0x0000 }, + { 0xd9, 0x0000 }, + { 0xda, 0x0000 }, + { 0xdb, 0x0000 }, + { 0xdc, 0x0000 }, + { 0xdd, 0x0000 }, + { 0xde, 0x0200 }, + { 0xdf, 0x0000 }, + { 0xe0, 0x0000 }, + { 0xe1, 0x0000 }, + { 0xe2, 0x0000 }, + { 0xe3, 0x0000 }, + { 0xe4, 0x0000 }, + { 0xe5, 0x0000 }, + { 0xe6, 0x0000 }, + { 0xe7, 0x0000 }, + { 0xe8, 0x0200 }, + { 0xe9, 0x0000 }, + { 0xea, 0x0000 }, + { 0xeb, 0x0000 }, + { 0xec, 0x0000 }, + { 0xed, 0x0000 }, + { 0xee, 0x0000 }, + { 0xef, 0x0000 }, + { 0xf0, 0x0000 }, + { 0xf1, 0x0000 }, + { 0xf2, 0x0200 }, + { 0xf3, 0x0000 }, + { 0xf4, 0x0000 }, + { 0xf5, 0x0000 }, + { 0xf6, 0x0000 }, + { 0xf7, 0x0000 }, + { 0xf8, 0x0000 }, + { 0xf9, 0x0000 }, + { 0xfa, 0x0000 }, + { 0xfb, 0x0000 }, +}; + +static int rt1305_reg_init(struct snd_soc_component *component) +{ + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + + regmap_multi_reg_write(rt1305->regmap, init_list, RT1305_INIT_REG_LEN); + return 0; +} + +static bool rt1305_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt1305_ranges); i++) { + if (reg >= rt1305_ranges[i].range_min && + reg <= rt1305_ranges[i].range_max) { + return true; + } + } + + switch (reg) { + case RT1305_RESET: + case RT1305_SPDIF_IN_SET_1: + case RT1305_SPDIF_IN_SET_2: + case RT1305_SPDIF_IN_SET_3: + case RT1305_POWER_CTRL_2: + case RT1305_CLOCK_DETECT: + case RT1305_BIQUAD_SET_1: + case RT1305_BIQUAD_SET_2: + case RT1305_EQ_SET_2: + case RT1305_SPK_TEMP_PROTECTION_0: + case RT1305_SPK_TEMP_PROTECTION_2: + case RT1305_SPK_DC_DETECT_1: + case RT1305_SILENCE_DETECT: + case RT1305_VERSION_ID: + case RT1305_VENDOR_ID: + case RT1305_DEVICE_ID: + case RT1305_EFUSE_1: + case RT1305_EFUSE_3: + case RT1305_DC_CALIB_1: + case RT1305_DC_CALIB_3: + case RT1305_DAC_OFFSET_1: + case RT1305_DAC_OFFSET_2: + case RT1305_DAC_OFFSET_3: + case RT1305_DAC_OFFSET_4: + case RT1305_DAC_OFFSET_5: + case RT1305_DAC_OFFSET_6: + case RT1305_DAC_OFFSET_7: + case RT1305_DAC_OFFSET_8: + case RT1305_DAC_OFFSET_9: + case RT1305_DAC_OFFSET_10: + case RT1305_DAC_OFFSET_11: + case RT1305_TRIM_1: + case RT1305_TRIM_2: + return true; + + default: + return false; + } +} + +static bool rt1305_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt1305_ranges); i++) { + if (reg >= rt1305_ranges[i].range_min && + reg <= rt1305_ranges[i].range_max) { + return true; + } + } + + switch (reg) { + case RT1305_RESET: + case RT1305_CLK_1 ... RT1305_CAL_EFUSE_CLOCK: + case RT1305_PLL0_1 ... RT1305_PLL1_2: + case RT1305_MIXER_CTRL_1: + case RT1305_MIXER_CTRL_2: + case RT1305_DAC_SET_1: + case RT1305_DAC_SET_2: + case RT1305_ADC_SET_1: + case RT1305_ADC_SET_2: + case RT1305_ADC_SET_3: + case RT1305_PATH_SET: + case RT1305_SPDIF_IN_SET_1: + case RT1305_SPDIF_IN_SET_2: + case RT1305_SPDIF_IN_SET_3: + case RT1305_SPDIF_OUT_SET_1: + case RT1305_SPDIF_OUT_SET_2: + case RT1305_SPDIF_OUT_SET_3: + case RT1305_I2S_SET_1: + case RT1305_I2S_SET_2: + case RT1305_PBTL_MONO_MODE_SRC: + case RT1305_MANUALLY_I2C_DEVICE: + case RT1305_POWER_STATUS: + case RT1305_POWER_CTRL_1: + case RT1305_POWER_CTRL_2: + case RT1305_POWER_CTRL_3: + case RT1305_POWER_CTRL_4: + case RT1305_POWER_CTRL_5: + case RT1305_CLOCK_DETECT: + case RT1305_BIQUAD_SET_1: + case RT1305_BIQUAD_SET_2: + case RT1305_ADJUSTED_HPF_1: + case RT1305_ADJUSTED_HPF_2: + case RT1305_EQ_SET_1: + case RT1305_EQ_SET_2: + case RT1305_SPK_TEMP_PROTECTION_0: + case RT1305_SPK_TEMP_PROTECTION_1: + case RT1305_SPK_TEMP_PROTECTION_2: + case RT1305_SPK_TEMP_PROTECTION_3: + case RT1305_SPK_DC_DETECT_1: + case RT1305_SPK_DC_DETECT_2: + case RT1305_LOUDNESS: + case RT1305_THERMAL_FOLD_BACK_1: + case RT1305_THERMAL_FOLD_BACK_2: + case RT1305_SILENCE_DETECT ... RT1305_SPK_EXCURSION_LIMITER_7: + case RT1305_VERSION_ID: + case RT1305_VENDOR_ID: + case RT1305_DEVICE_ID: + case RT1305_EFUSE_1: + case RT1305_EFUSE_2: + case RT1305_EFUSE_3: + case RT1305_DC_CALIB_1: + case RT1305_DC_CALIB_2: + case RT1305_DC_CALIB_3: + case RT1305_DAC_OFFSET_1 ... RT1305_DAC_OFFSET_14: + case RT1305_TRIM_1: + case RT1305_TRIM_2: + case RT1305_TUNE_INTERNAL_OSC: + case RT1305_BIQUAD1_H0_L_28_16 ... RT1305_BIQUAD3_A2_R_15_0: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9435, 37, 0); + +static const char * const rt1305_rx_data_ch_select[] = { + "LR", + "RL", + "Copy L", + "Copy R", +}; + +static SOC_ENUM_SINGLE_DECL(rt1305_rx_data_ch_enum, RT1305_I2S_SET_2, 2, + rt1305_rx_data_ch_select); + +static void rt1305_reset(struct regmap *regmap) +{ + regmap_write(regmap, RT1305_RESET, 0); +} + +static const struct snd_kcontrol_new rt1305_snd_controls[] = { + SOC_DOUBLE_TLV("DAC Playback Volume", RT1305_DAC_SET_1, + 8, 0, 0xff, 0, dac_vol_tlv), + + /* I2S Data Channel Selection */ + SOC_ENUM("RX Channel Select", rt1305_rx_data_ch_enum), +}; + +static int rt1305_is_rc_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(source->dapm); + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + unsigned int val; + + snd_soc_component_read(component, RT1305_CLK_1, &val); + + if (rt1305->sysclk_src == RT1305_FS_SYS_PRE_S_PLL1 && + (val & RT1305_SEL_PLL_SRC_2_RCCLK)) + return 1; + else + return 0; +} + +static int rt1305_is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(source->dapm); + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + + if (rt1305->sysclk_src == RT1305_FS_SYS_PRE_S_PLL1) + return 1; + else + return 0; +} + +static int rt1305_classd_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT1305_POWER_CTRL_1, + RT1305_POW_PDB_JD_MASK, RT1305_POW_PDB_JD); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT1305_POWER_CTRL_1, + RT1305_POW_PDB_JD_MASK, 0); + usleep_range(150000, 200000); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_kcontrol_new rt1305_sto_dac_l = + SOC_DAPM_SINGLE("Switch", RT1305_DAC_SET_2, + RT1305_DVOL_MUTE_L_EN_SFT, 1, 1); + +static const struct snd_kcontrol_new rt1305_sto_dac_r = + SOC_DAPM_SINGLE("Switch", RT1305_DAC_SET_2, + RT1305_DVOL_MUTE_R_EN_SFT, 1, 1); + +static const struct snd_soc_dapm_widget rt1305_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PLL0", RT1305_POWER_CTRL_1, + RT1305_POW_PLL0_EN_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", RT1305_POWER_CTRL_1, + RT1305_POW_PLL1_EN_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MBIAS", RT1305_POWER_CTRL_1, + RT1305_POW_MBIAS_LV_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BG MBIAS", RT1305_POWER_CTRL_1, + RT1305_POW_BG_MBIAS_LV_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO2", RT1305_POWER_CTRL_1, + RT1305_POW_LDO2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BG2", RT1305_POWER_CTRL_1, + RT1305_POW_BG2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO2 IB2", RT1305_POWER_CTRL_1, + RT1305_POW_LDO2_IB2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF", RT1305_POWER_CTRL_1, + RT1305_POW_VREF_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF1", RT1305_POWER_CTRL_1, + RT1305_POW_VREF1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF2", RT1305_POWER_CTRL_1, + RT1305_POW_VREF2_BIT, 0, NULL, 0), + + + SND_SOC_DAPM_SUPPLY("DISC VREF", RT1305_POWER_CTRL_2, + RT1305_POW_DISC_VREF_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("FASTB VREF", RT1305_POWER_CTRL_2, + RT1305_POW_FASTB_VREF_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ULTRA FAST VREF", RT1305_POWER_CTRL_2, + RT1305_POW_ULTRA_FAST_VREF_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CHOP DAC", RT1305_POWER_CTRL_2, + RT1305_POW_CKXEN_DAC_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CKGEN DAC", RT1305_POWER_CTRL_2, + RT1305_POW_EN_CKGEN_DAC_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLAMP", RT1305_POWER_CTRL_2, + RT1305_POW_CLAMP_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BUFL", RT1305_POWER_CTRL_2, + RT1305_POW_BUFL_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BUFR", RT1305_POWER_CTRL_2, + RT1305_POW_BUFR_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CKGEN ADC", RT1305_POWER_CTRL_2, + RT1305_POW_EN_CKGEN_ADC_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC3 L", RT1305_POWER_CTRL_2, + RT1305_POW_ADC3_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC3 R", RT1305_POWER_CTRL_2, + RT1305_POW_ADC3_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("TRIOSC", RT1305_POWER_CTRL_2, + RT1305_POW_TRIOSC_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AVDD1", RT1305_POWER_CTRL_2, + RT1305_POR_AVDD1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AVDD2", RT1305_POWER_CTRL_2, + RT1305_POR_AVDD2_BIT, 0, NULL, 0), + + + SND_SOC_DAPM_SUPPLY("VSENSE R", RT1305_POWER_CTRL_3, + RT1305_POW_VSENSE_RCH_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("VSENSE L", RT1305_POWER_CTRL_3, + RT1305_POW_VSENSE_LCH_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ISENSE R", RT1305_POWER_CTRL_3, + RT1305_POW_ISENSE_RCH_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ISENSE L", RT1305_POWER_CTRL_3, + RT1305_POW_ISENSE_LCH_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("POR AVDD1", RT1305_POWER_CTRL_3, + RT1305_POW_POR_AVDD1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("POR AVDD2", RT1305_POWER_CTRL_3, + RT1305_POW_POR_AVDD2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("VCM 6172", RT1305_POWER_CTRL_3, + RT1305_EN_VCM_6172_BIT, 0, NULL, 0), + + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("DAC L Power", RT1305_POWER_CTRL_2, + RT1305_POW_DAC1_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC R Power", RT1305_POWER_CTRL_2, + RT1305_POW_DAC1_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SWITCH("DAC L", SND_SOC_NOPM, 0, 0, &rt1305_sto_dac_l), + SND_SOC_DAPM_SWITCH("DAC R", SND_SOC_NOPM, 0, 0, &rt1305_sto_dac_r), + + /* Output Lines */ + SND_SOC_DAPM_PGA_E("CLASS D", SND_SOC_NOPM, 0, 0, NULL, 0, + rt1305_classd_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), +}; + +static const struct snd_soc_dapm_route rt1305_dapm_routes[] = { + + { "DAC", NULL, "AIF1RX" }, + + { "DAC", NULL, "PLL0", rt1305_is_rc_clk_from_pll }, + { "DAC", NULL, "PLL1", rt1305_is_sys_clk_from_pll }, + + { "DAC", NULL, "MBIAS" }, + { "DAC", NULL, "BG MBIAS" }, + { "DAC", NULL, "LDO2" }, + { "DAC", NULL, "BG2" }, + { "DAC", NULL, "LDO2 IB2" }, + { "DAC", NULL, "VREF" }, + { "DAC", NULL, "VREF1" }, + { "DAC", NULL, "VREF2" }, + + { "DAC", NULL, "DISC VREF" }, + { "DAC", NULL, "FASTB VREF" }, + { "DAC", NULL, "ULTRA FAST VREF" }, + { "DAC", NULL, "CHOP DAC" }, + { "DAC", NULL, "CKGEN DAC" }, + { "DAC", NULL, "CLAMP" }, + { "DAC", NULL, "CKGEN ADC" }, + { "DAC", NULL, "TRIOSC" }, + { "DAC", NULL, "AVDD1" }, + { "DAC", NULL, "AVDD2" }, + + { "DAC", NULL, "POR AVDD1" }, + { "DAC", NULL, "POR AVDD2" }, + { "DAC", NULL, "VCM 6172" }, + + { "DAC L", "Switch", "DAC" }, + { "DAC R", "Switch", "DAC" }, + + { "DAC R", NULL, "VSENSE R" }, + { "DAC L", NULL, "VSENSE L" }, + { "DAC R", NULL, "ISENSE R" }, + { "DAC L", NULL, "ISENSE L" }, + { "DAC L", NULL, "ADC3 L" }, + { "DAC R", NULL, "ADC3 R" }, + { "DAC L", NULL, "BUFL" }, + { "DAC R", NULL, "BUFR" }, + { "DAC L", NULL, "DAC L Power" }, + { "DAC R", NULL, "DAC R Power" }, + + { "CLASS D", NULL, "DAC L" }, + { "CLASS D", NULL, "DAC R" }, + + { "SPOL", NULL, "CLASS D" }, + { "SPOR", NULL, "CLASS D" }, +}; + +static int rt1305_get_clk_info(int sclk, int rate) +{ + int i, pd[] = {1, 2, 3, 4, 6, 8, 12, 16}; + + if (sclk <= 0 || rate <= 0) + return -EINVAL; + + rate = rate << 8; + for (i = 0; i < ARRAY_SIZE(pd); i++) + if (sclk == rate * pd[i]) + return i; + + return -EINVAL; +} + +static int rt1305_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int pre_div, bclk_ms, frame_size; + + rt1305->lrck = params_rate(params); + pre_div = rt1305_get_clk_info(rt1305->sysclk, rt1305->lrck); + if (pre_div < 0) { + dev_warn(component->dev, "Force using PLL "); + snd_soc_dai_set_pll(dai, 0, RT1305_PLL1_S_BCLK, + rt1305->lrck * 64, rt1305->lrck * 256); + snd_soc_dai_set_sysclk(dai, RT1305_FS_SYS_PRE_S_PLL1, + rt1305->lrck * 256, SND_SOC_CLOCK_IN); + pre_div = 0; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", + frame_size); + return -EINVAL; + } + + bclk_ms = frame_size > 32; + rt1305->bclk = rt1305->lrck * (32 << bclk_ms); + + dev_dbg(component->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + dev_dbg(component->dev, "lrck is %dHz and pre_div is %d for iis %d\n", + rt1305->lrck, pre_div, dai->id); + + switch (params_width(params)) { + case 16: + val_len |= RT1305_I2S_DL_SEL_16B; + break; + case 20: + val_len |= RT1305_I2S_DL_SEL_20B; + break; + case 24: + val_len |= RT1305_I2S_DL_SEL_24B; + break; + case 8: + val_len |= RT1305_I2S_DL_SEL_8B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT1305_AIF1: + mask_clk = RT1305_DIV_FS_SYS_MASK; + val_clk = pre_div << RT1305_DIV_FS_SYS_SFT; + snd_soc_component_update_bits(component, RT1305_I2S_SET_2, + RT1305_I2S_DL_SEL_MASK, + val_len); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT1305_CLK_2, + mask_clk, val_clk); + + return 0; +} + +static int rt1305_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, reg1_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + reg_val |= RT1305_SEL_I2S_OUT_MODE_M; + rt1305->master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT1305_SEL_I2S_OUT_MODE_S; + rt1305->master = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg1_val |= RT1305_I2S_BCLK_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg1_val |= RT1305_I2S_DF_SEL_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg1_val |= RT1305_I2S_DF_SEL_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg1_val |= RT1305_I2S_DF_SEL_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT1305_AIF1: + snd_soc_component_update_bits(component, RT1305_I2S_SET_1, + RT1305_SEL_I2S_OUT_MODE_MASK, reg_val); + snd_soc_component_update_bits(component, RT1305_I2S_SET_2, + RT1305_I2S_DF_SEL_MASK | RT1305_I2S_BCLK_MASK, + reg1_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt1305_set_component_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + if (freq == rt1305->sysclk && clk_id == rt1305->sysclk_src) + return 0; + + switch (clk_id) { + case RT1305_FS_SYS_PRE_S_MCLK: + reg_val |= RT1305_SEL_FS_SYS_PRE_MCLK; + snd_soc_component_update_bits(component, + RT1305_CLOCK_DETECT, RT1305_SEL_CLK_DET_SRC_MASK, + RT1305_SEL_CLK_DET_SRC_MCLK); + break; + case RT1305_FS_SYS_PRE_S_PLL1: + reg_val |= RT1305_SEL_FS_SYS_PRE_PLL; + break; + case RT1305_FS_SYS_PRE_S_RCCLK: + reg_val |= RT1305_SEL_FS_SYS_PRE_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT1305_CLK_1, + RT1305_SEL_FS_SYS_PRE_MASK, reg_val); + rt1305->sysclk = freq; + rt1305->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; +} + +static int rt1305_set_component_pll(struct snd_soc_component *component, + int pll_id, int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt1305->pll_src && freq_in == rt1305->pll_in && + freq_out == rt1305->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt1305->pll_in = 0; + rt1305->pll_out = 0; + snd_soc_component_update_bits(component, RT1305_CLK_1, + RT1305_SEL_FS_SYS_PRE_MASK | RT1305_SEL_PLL_SRC_1_MASK, + RT1305_SEL_FS_SYS_PRE_PLL | RT1305_SEL_PLL_SRC_1_BCLK); + return 0; + } + + switch (source) { + case RT1305_PLL2_S_MCLK: + snd_soc_component_update_bits(component, RT1305_CLK_1, + RT1305_SEL_PLL_SRC_2_MASK | RT1305_SEL_PLL_SRC_1_MASK | + RT1305_DIV_PLL_SRC_2_MASK, + RT1305_SEL_PLL_SRC_2_MCLK | RT1305_SEL_PLL_SRC_1_PLL2); + snd_soc_component_update_bits(component, + RT1305_CLOCK_DETECT, RT1305_SEL_CLK_DET_SRC_MASK, + RT1305_SEL_CLK_DET_SRC_MCLK); + break; + case RT1305_PLL1_S_BCLK: + snd_soc_component_update_bits(component, + RT1305_CLK_1, RT1305_SEL_PLL_SRC_1_MASK, + RT1305_SEL_PLL_SRC_1_BCLK); + break; + case RT1305_PLL2_S_RCCLK: + snd_soc_component_update_bits(component, RT1305_CLK_1, + RT1305_SEL_PLL_SRC_2_MASK | RT1305_SEL_PLL_SRC_1_MASK | + RT1305_DIV_PLL_SRC_2_MASK, + RT1305_SEL_PLL_SRC_2_RCCLK | RT1305_SEL_PLL_SRC_1_PLL2); + freq_in = 98304000; + break; + default: + dev_err(component->dev, "Unknown PLL Source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT1305_PLL1_1, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT1305_PLL_1_M_SFT | + pll_code.m_bp << RT1305_PLL_1_M_BYPASS_SFT | + pll_code.n_code); + snd_soc_component_write(component, RT1305_PLL1_2, + pll_code.k_code); + + rt1305->pll_in = freq_in; + rt1305->pll_out = freq_out; + rt1305->pll_src = source; + + return 0; +} + +static int rt1305_probe(struct snd_soc_component *component) +{ + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + + rt1305->component = component; + + /* initial settings */ + rt1305_reg_init(component); + + return 0; +} + +static void rt1305_remove(struct snd_soc_component *component) +{ + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + + rt1305_reset(rt1305->regmap); +} + +#ifdef CONFIG_PM +static int rt1305_suspend(struct snd_soc_component *component) +{ + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt1305->regmap, true); + regcache_mark_dirty(rt1305->regmap); + + return 0; +} + +static int rt1305_resume(struct snd_soc_component *component) +{ + struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt1305->regmap, false); + regcache_sync(rt1305->regmap); + + return 0; +} +#else +#define rt1305_suspend NULL +#define rt1305_resume NULL +#endif + +#define RT1305_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT1305_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops rt1305_aif_dai_ops = { + .hw_params = rt1305_hw_params, + .set_fmt = rt1305_set_dai_fmt, +}; + +static struct snd_soc_dai_driver rt1305_dai[] = { + { + .name = "rt1305-aif", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT1305_STEREO_RATES, + .formats = RT1305_FORMATS, + }, + .ops = &rt1305_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt1305 = { + .probe = rt1305_probe, + .remove = rt1305_remove, + .suspend = rt1305_suspend, + .resume = rt1305_resume, + .controls = rt1305_snd_controls, + .num_controls = ARRAY_SIZE(rt1305_snd_controls), + .dapm_widgets = rt1305_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt1305_dapm_widgets), + .dapm_routes = rt1305_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt1305_dapm_routes), + .set_sysclk = rt1305_set_component_sysclk, + .set_pll = rt1305_set_component_pll, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt1305_regmap = { + .reg_bits = 8, + .val_bits = 16, + .max_register = RT1305_MAX_REG + 1 + (ARRAY_SIZE(rt1305_ranges) * + RT1305_PR_SPACING), + .volatile_reg = rt1305_volatile_register, + .readable_reg = rt1305_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt1305_reg, + .num_reg_defaults = ARRAY_SIZE(rt1305_reg), + .ranges = rt1305_ranges, + .num_ranges = ARRAY_SIZE(rt1305_ranges), + .use_single_rw = true, +}; + +#if defined(CONFIG_OF) +static const struct of_device_id rt1305_of_match[] = { + { .compatible = "realtek,rt1305", }, + { .compatible = "realtek,rt1306", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt1305_of_match); +#endif + +#ifdef CONFIG_ACPI +static struct acpi_device_id rt1305_acpi_match[] = { + {"10EC1305", 0,}, + {"10EC1306", 0,}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt1305_acpi_match); +#endif + +static const struct i2c_device_id rt1305_i2c_id[] = { + { "rt1305", 0 }, + { "rt1306", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt1305_i2c_id); + +static void rt1305_calibrate(struct rt1305_priv *rt1305) +{ + unsigned int valmsb, vallsb, offsetl, offsetr; + unsigned int rh, rl, rhl, r0ohm; + u64 r0l, r0r; + + regcache_cache_bypass(rt1305->regmap, true); + + rt1305_reset(rt1305->regmap); + regmap_write(rt1305->regmap, RT1305_ADC_SET_3, 0x0219); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xcf, 0x5548); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xc1, 0x0320); + regmap_write(rt1305->regmap, RT1305_CLOCK_DETECT, 0x1000); + regmap_write(rt1305->regmap, RT1305_CLK_1, 0x0600); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xffd0); + regmap_write(rt1305->regmap, RT1305_EFUSE_1, 0x0080); + regmap_write(rt1305->regmap, RT1305_EFUSE_1, 0x0880); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_1, 0x0dfe); + + /* Sin Gen */ + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x5d, 0x0442); + + regmap_write(rt1305->regmap, RT1305_CAL_EFUSE_CLOCK, 0xb000); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xc3, 0xd4a0); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xcc, 0x00cc); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xc1, 0x0320); + regmap_write(rt1305->regmap, RT1305_POWER_STATUS, 0x0000); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_2, 0xffff); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfc20); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x06, 0x00c0); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfca0); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfce0); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfcf0); + + /* EFUSE read */ + regmap_write(rt1305->regmap, RT1305_EFUSE_1, 0x0080); + regmap_write(rt1305->regmap, RT1305_EFUSE_1, 0x0880); + regmap_write(rt1305->regmap, RT1305_EFUSE_1, 0x0880); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfce0); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfca0); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfc20); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x06, 0x0000); + regmap_write(rt1305->regmap, RT1305_EFUSE_1, 0x0000); + + regmap_read(rt1305->regmap, RT1305_DAC_OFFSET_5, &valmsb); + regmap_read(rt1305->regmap, RT1305_DAC_OFFSET_6, &vallsb); + offsetl = valmsb << 16 | vallsb; + regmap_read(rt1305->regmap, RT1305_DAC_OFFSET_7, &valmsb); + regmap_read(rt1305->regmap, RT1305_DAC_OFFSET_8, &vallsb); + offsetr = valmsb << 16 | vallsb; + pr_info("DC offsetl=0x%x, offsetr=0x%x\n", offsetl, offsetr); + + /* R0 calibration */ + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x5d, 0x9542); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0xfcf0); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_2, 0xffff); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_1, 0x1dfe); + regmap_write(rt1305->regmap, RT1305_SILENCE_DETECT, 0x0e13); + regmap_write(rt1305->regmap, RT1305_CLK_1, 0x0650); + + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x50, 0x0064); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x51, 0x0770); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x52, 0xc30c); + regmap_write(rt1305->regmap, RT1305_SPK_TEMP_PROTECTION_1, 0x8200); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xd4, 0xfb00); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xd4, 0xff80); + msleep(2000); + regmap_read(rt1305->regmap, RT1305_PR_BASE + 0x55, &rh); + regmap_read(rt1305->regmap, RT1305_PR_BASE + 0x56, &rl); + rhl = (rh << 16) | rl; + r0ohm = (rhl*10) / 33554432; + + pr_debug("Left_rhl = 0x%x rh=0x%x rl=0x%x\n", rhl, rh, rl); + pr_info("Left channel %d.%dohm\n", (r0ohm/10), (r0ohm%10)); + + r0l = 562949953421312ULL; + if (rhl != 0) + do_div(r0l, rhl); + pr_debug("Left_r0 = 0x%llx\n", r0l); + + regmap_write(rt1305->regmap, RT1305_SPK_TEMP_PROTECTION_1, 0x9200); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xd4, 0xfb00); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xd4, 0xff80); + msleep(2000); + regmap_read(rt1305->regmap, RT1305_PR_BASE + 0x55, &rh); + regmap_read(rt1305->regmap, RT1305_PR_BASE + 0x56, &rl); + rhl = (rh << 16) | rl; + r0ohm = (rhl*10) / 33554432; + + pr_debug("Right_rhl = 0x%x rh=0x%x rl=0x%x\n", rhl, rh, rl); + pr_info("Right channel %d.%dohm\n", (r0ohm/10), (r0ohm%10)); + + r0r = 562949953421312ULL; + if (rhl != 0) + do_div(r0r, rhl); + pr_debug("Right_r0 = 0x%llx\n", r0r); + + regmap_write(rt1305->regmap, RT1305_SPK_TEMP_PROTECTION_1, 0xc2ec); + + if ((r0l > R0_UPPER) && (r0l < R0_LOWER) && + (r0r > R0_UPPER) && (r0r < R0_LOWER)) { + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x4e, + (r0l >> 16) & 0xffff); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x4f, + r0l & 0xffff); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xfe, + ((r0r >> 16) & 0xffff) | 0xf800); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0xfd, + r0r & 0xffff); + } else { + pr_err("R0 calibration failed\n"); + } + + /* restore some registers */ + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_1, 0x0dfe); + usleep_range(200000, 400000); + regmap_write(rt1305->regmap, RT1305_PR_BASE + 0x5d, 0x0442); + regmap_write(rt1305->regmap, RT1305_CLOCK_DETECT, 0x3000); + regmap_write(rt1305->regmap, RT1305_CLK_1, 0x0400); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_1, 0x0000); + regmap_write(rt1305->regmap, RT1305_CAL_EFUSE_CLOCK, 0x8000); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_2, 0x1020); + regmap_write(rt1305->regmap, RT1305_POWER_CTRL_3, 0x0000); + + regcache_cache_bypass(rt1305->regmap, false); +} + +static int rt1305_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt1305_priv *rt1305; + int ret; + unsigned int val; + + rt1305 = devm_kzalloc(&i2c->dev, sizeof(struct rt1305_priv), + GFP_KERNEL); + if (rt1305 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt1305); + + rt1305->regmap = devm_regmap_init_i2c(i2c, &rt1305_regmap); + if (IS_ERR(rt1305->regmap)) { + ret = PTR_ERR(rt1305->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt1305->regmap, RT1305_DEVICE_ID, &val); + if (val != RT1305_DEVICE_ID_NUM) { + dev_err(&i2c->dev, + "Device with ID register %x is not rt1305\n", val); + return -ENODEV; + } + + rt1305_reset(rt1305->regmap); + rt1305_calibrate(rt1305); + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt1305, + rt1305_dai, ARRAY_SIZE(rt1305_dai)); +} + +static void rt1305_i2c_shutdown(struct i2c_client *client) +{ + struct rt1305_priv *rt1305 = i2c_get_clientdata(client); + + rt1305_reset(rt1305->regmap); +} + + +static struct i2c_driver rt1305_i2c_driver = { + .driver = { + .name = "rt1305", +#if defined(CONFIG_OF) + .of_match_table = rt1305_of_match, +#endif +#if defined(CONFIG_ACPI) + .acpi_match_table = ACPI_PTR(rt1305_acpi_match) +#endif + }, + .probe = rt1305_i2c_probe, + .shutdown = rt1305_i2c_shutdown, + .id_table = rt1305_i2c_id, +}; +module_i2c_driver(rt1305_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT1305 amplifier driver"); +MODULE_AUTHOR("Shuming Fan "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt1305.h b/sound/soc/codecs/rt1305.h new file mode 100644 index 000000000..bde86f977 --- /dev/null +++ b/sound/soc/codecs/rt1305.h @@ -0,0 +1,276 @@ +/* + * RT1305.h -- RT1305 ALSA SoC amplifier component driver + * + * Copyright 2018 Realtek Semiconductor Corp. + * Author: Shuming Fan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _RT1305_H_ +#define _RT1305_H_ + +#define RT1305_DEVICE_ID_NUM 0x6251 + +#define RT1305_RESET 0x00 +#define RT1305_CLK_1 0x04 +#define RT1305_CLK_2 0x05 +#define RT1305_CLK_3 0x06 +#define RT1305_DFLL_REG 0x07 +#define RT1305_CAL_EFUSE_CLOCK 0x08 +#define RT1305_PLL0_1 0x0a +#define RT1305_PLL0_2 0x0b +#define RT1305_PLL1_1 0x0c +#define RT1305_PLL1_2 0x0d +#define RT1305_MIXER_CTRL_1 0x10 +#define RT1305_MIXER_CTRL_2 0x11 +#define RT1305_DAC_SET_1 0x12 +#define RT1305_DAC_SET_2 0x14 +#define RT1305_ADC_SET_1 0x16 +#define RT1305_ADC_SET_2 0x17 +#define RT1305_ADC_SET_3 0x18 +#define RT1305_PATH_SET 0x20 +#define RT1305_SPDIF_IN_SET_1 0x22 +#define RT1305_SPDIF_IN_SET_2 0x24 +#define RT1305_SPDIF_IN_SET_3 0x26 +#define RT1305_SPDIF_OUT_SET_1 0x28 +#define RT1305_SPDIF_OUT_SET_2 0x2a +#define RT1305_SPDIF_OUT_SET_3 0x2b +#define RT1305_I2S_SET_1 0x2d +#define RT1305_I2S_SET_2 0x2e +#define RT1305_PBTL_MONO_MODE_SRC 0x2f +#define RT1305_MANUALLY_I2C_DEVICE 0x32 +#define RT1305_POWER_STATUS 0x39 +#define RT1305_POWER_CTRL_1 0x3a +#define RT1305_POWER_CTRL_2 0x3b +#define RT1305_POWER_CTRL_3 0x3c +#define RT1305_POWER_CTRL_4 0x3d +#define RT1305_POWER_CTRL_5 0x3e +#define RT1305_CLOCK_DETECT 0x3f +#define RT1305_BIQUAD_SET_1 0x40 +#define RT1305_BIQUAD_SET_2 0x42 +#define RT1305_ADJUSTED_HPF_1 0x46 +#define RT1305_ADJUSTED_HPF_2 0x47 +#define RT1305_EQ_SET_1 0x4b +#define RT1305_EQ_SET_2 0x4c +#define RT1305_SPK_TEMP_PROTECTION_0 0x4f +#define RT1305_SPK_TEMP_PROTECTION_1 0x50 +#define RT1305_SPK_TEMP_PROTECTION_2 0x51 +#define RT1305_SPK_TEMP_PROTECTION_3 0x52 +#define RT1305_SPK_DC_DETECT_1 0x53 +#define RT1305_SPK_DC_DETECT_2 0x54 +#define RT1305_LOUDNESS 0x58 +#define RT1305_THERMAL_FOLD_BACK_1 0x5e +#define RT1305_THERMAL_FOLD_BACK_2 0x5f +#define RT1305_SILENCE_DETECT 0x60 +#define RT1305_ALC_DRC_1 0x62 +#define RT1305_ALC_DRC_2 0x63 +#define RT1305_ALC_DRC_3 0x64 +#define RT1305_ALC_DRC_4 0x65 +#define RT1305_PRIV_INDEX 0x6a +#define RT1305_PRIV_DATA 0x6c +#define RT1305_SPK_EXCURSION_LIMITER_7 0x76 +#define RT1305_VERSION_ID 0x7a +#define RT1305_VENDOR_ID 0x7c +#define RT1305_DEVICE_ID 0x7e +#define RT1305_EFUSE_1 0x80 +#define RT1305_EFUSE_2 0x81 +#define RT1305_EFUSE_3 0x82 +#define RT1305_DC_CALIB_1 0x90 +#define RT1305_DC_CALIB_2 0x91 +#define RT1305_DC_CALIB_3 0x92 +#define RT1305_DAC_OFFSET_1 0x93 +#define RT1305_DAC_OFFSET_2 0x94 +#define RT1305_DAC_OFFSET_3 0x95 +#define RT1305_DAC_OFFSET_4 0x96 +#define RT1305_DAC_OFFSET_5 0x97 +#define RT1305_DAC_OFFSET_6 0x98 +#define RT1305_DAC_OFFSET_7 0x99 +#define RT1305_DAC_OFFSET_8 0x9a +#define RT1305_DAC_OFFSET_9 0x9b +#define RT1305_DAC_OFFSET_10 0x9c +#define RT1305_DAC_OFFSET_11 0x9d +#define RT1305_DAC_OFFSET_12 0x9e +#define RT1305_DAC_OFFSET_13 0x9f +#define RT1305_DAC_OFFSET_14 0xa0 +#define RT1305_TRIM_1 0xb0 +#define RT1305_TRIM_2 0xb1 +#define RT1305_TUNE_INTERNAL_OSC 0xb2 +#define RT1305_BIQUAD1_H0_L_28_16 0xc0 +#define RT1305_BIQUAD3_A2_R_15_0 0xfb +#define RT1305_MAX_REG 0xff + +/* CLOCK-1 (0x04) */ +#define RT1305_SEL_PLL_SRC_2_MASK (0x1 << 15) +#define RT1305_SEL_PLL_SRC_2_SFT 15 +#define RT1305_SEL_PLL_SRC_2_MCLK (0x0 << 15) +#define RT1305_SEL_PLL_SRC_2_RCCLK (0x1 << 15) +#define RT1305_DIV_PLL_SRC_2_MASK (0x3 << 13) +#define RT1305_DIV_PLL_SRC_2_SFT 13 +#define RT1305_SEL_PLL_SRC_1_MASK (0x3 << 10) +#define RT1305_SEL_PLL_SRC_1_SFT 10 +#define RT1305_SEL_PLL_SRC_1_PLL2 (0x0 << 10) +#define RT1305_SEL_PLL_SRC_1_BCLK (0x1 << 10) +#define RT1305_SEL_PLL_SRC_1_DFLL (0x2 << 10) +#define RT1305_SEL_FS_SYS_PRE_MASK (0x3 << 8) +#define RT1305_SEL_FS_SYS_PRE_SFT 8 +#define RT1305_SEL_FS_SYS_PRE_MCLK (0x0 << 8) +#define RT1305_SEL_FS_SYS_PRE_PLL (0x1 << 8) +#define RT1305_SEL_FS_SYS_PRE_RCCLK (0x2 << 8) +#define RT1305_DIV_FS_SYS_MASK (0x7 << 4) +#define RT1305_DIV_FS_SYS_SFT 4 + +/* PLL1M/N/K Code-1 (0x0c) */ +#define RT1305_PLL_1_M_SFT 12 +#define RT1305_PLL_1_M_BYPASS_MASK (0x1 << 11) +#define RT1305_PLL_1_M_BYPASS_SFT 11 +#define RT1305_PLL_1_M_BYPASS (0x1 << 11) +#define RT1305_PLL_1_N_MASK (0x1ff << 0) + +/* DAC Setting (0x14) */ +#define RT1305_DVOL_MUTE_L_EN_SFT 15 +#define RT1305_DVOL_MUTE_R_EN_SFT 14 + +/* I2S Setting-1 (0x2d) */ +#define RT1305_SEL_I2S_OUT_MODE_MASK (0x1 << 15) +#define RT1305_SEL_I2S_OUT_MODE_SFT 15 +#define RT1305_SEL_I2S_OUT_MODE_S (0x0 << 15) +#define RT1305_SEL_I2S_OUT_MODE_M (0x1 << 15) + +/* I2S Setting-2 (0x2e) */ +#define RT1305_I2S_DF_SEL_MASK (0x3 << 12) +#define RT1305_I2S_DF_SEL_SFT 12 +#define RT1305_I2S_DF_SEL_I2S (0x0 << 12) +#define RT1305_I2S_DF_SEL_LEFT (0x1 << 12) +#define RT1305_I2S_DF_SEL_PCM_A (0x2 << 12) +#define RT1305_I2S_DF_SEL_PCM_B (0x3 << 12) +#define RT1305_I2S_DL_SEL_MASK (0x3 << 10) +#define RT1305_I2S_DL_SEL_SFT 10 +#define RT1305_I2S_DL_SEL_16B (0x0 << 10) +#define RT1305_I2S_DL_SEL_20B (0x1 << 10) +#define RT1305_I2S_DL_SEL_24B (0x2 << 10) +#define RT1305_I2S_DL_SEL_8B (0x3 << 10) +#define RT1305_I2S_BCLK_MASK (0x1 << 9) +#define RT1305_I2S_BCLK_SFT 9 +#define RT1305_I2S_BCLK_NORMAL (0x0 << 9) +#define RT1305_I2S_BCLK_INV (0x1 << 9) + +/* Power Control-1 (0x3a) */ +#define RT1305_POW_PDB_JD_MASK (0x1 << 12) +#define RT1305_POW_PDB_JD (0x1 << 12) +#define RT1305_POW_PDB_JD_BIT 12 +#define RT1305_POW_PLL0_EN (0x1 << 11) +#define RT1305_POW_PLL0_EN_BIT 11 +#define RT1305_POW_PLL1_EN (0x1 << 10) +#define RT1305_POW_PLL1_EN_BIT 10 +#define RT1305_POW_PDB_JD_POLARITY (0x1 << 9) +#define RT1305_POW_PDB_JD_POLARITY_BIT 9 +#define RT1305_POW_MBIAS_LV (0x1 << 8) +#define RT1305_POW_MBIAS_LV_BIT 8 +#define RT1305_POW_BG_MBIAS_LV (0x1 << 7) +#define RT1305_POW_BG_MBIAS_LV_BIT 7 +#define RT1305_POW_LDO2 (0x1 << 6) +#define RT1305_POW_LDO2_BIT 6 +#define RT1305_POW_BG2 (0x1 << 5) +#define RT1305_POW_BG2_BIT 5 +#define RT1305_POW_LDO2_IB2 (0x1 << 4) +#define RT1305_POW_LDO2_IB2_BIT 4 +#define RT1305_POW_VREF (0x1 << 3) +#define RT1305_POW_VREF_BIT 3 +#define RT1305_POW_VREF1 (0x1 << 2) +#define RT1305_POW_VREF1_BIT 2 +#define RT1305_POW_VREF2 (0x1 << 1) +#define RT1305_POW_VREF2_BIT 1 + +/* Power Control-2 (0x3b) */ +#define RT1305_POW_DISC_VREF (1 << 15) +#define RT1305_POW_DISC_VREF_BIT 15 +#define RT1305_POW_FASTB_VREF (1 << 14) +#define RT1305_POW_FASTB_VREF_BIT 14 +#define RT1305_POW_ULTRA_FAST_VREF (1 << 13) +#define RT1305_POW_ULTRA_FAST_VREF_BIT 13 +#define RT1305_POW_CKXEN_DAC (1 << 12) +#define RT1305_POW_CKXEN_DAC_BIT 12 +#define RT1305_POW_EN_CKGEN_DAC (1 << 11) +#define RT1305_POW_EN_CKGEN_DAC_BIT 11 +#define RT1305_POW_DAC1_L (1 << 10) +#define RT1305_POW_DAC1_L_BIT 10 +#define RT1305_POW_DAC1_R (1 << 9) +#define RT1305_POW_DAC1_R_BIT 9 +#define RT1305_POW_CLAMP (1 << 8) +#define RT1305_POW_CLAMP_BIT 8 +#define RT1305_POW_BUFL (1 << 7) +#define RT1305_POW_BUFL_BIT 7 +#define RT1305_POW_BUFR (1 << 6) +#define RT1305_POW_BUFR_BIT 6 +#define RT1305_POW_EN_CKGEN_ADC (1 << 5) +#define RT1305_POW_EN_CKGEN_ADC_BIT 5 +#define RT1305_POW_ADC3_L (1 << 4) +#define RT1305_POW_ADC3_L_BIT 4 +#define RT1305_POW_ADC3_R (1 << 3) +#define RT1305_POW_ADC3_R_BIT 3 +#define RT1305_POW_TRIOSC (1 << 2) +#define RT1305_POW_TRIOSC_BIT 2 +#define RT1305_POR_AVDD1 (1 << 1) +#define RT1305_POR_AVDD1_BIT 1 +#define RT1305_POR_AVDD2 (1 << 0) +#define RT1305_POR_AVDD2_BIT 0 + +/* Power Control-3 (0x3c) */ +#define RT1305_POW_VSENSE_RCH (1 << 15) +#define RT1305_POW_VSENSE_RCH_BIT 15 +#define RT1305_POW_VSENSE_LCH (1 << 14) +#define RT1305_POW_VSENSE_LCH_BIT 14 +#define RT1305_POW_ISENSE_RCH (1 << 13) +#define RT1305_POW_ISENSE_RCH_BIT 13 +#define RT1305_POW_ISENSE_LCH (1 << 12) +#define RT1305_POW_ISENSE_LCH_BIT 12 +#define RT1305_POW_POR_AVDD1 (1 << 11) +#define RT1305_POW_POR_AVDD1_BIT 11 +#define RT1305_POW_POR_AVDD2 (1 << 10) +#define RT1305_POW_POR_AVDD2_BIT 10 +#define RT1305_EN_K_HV (1 << 9) +#define RT1305_EN_K_HV_BIT 9 +#define RT1305_EN_PRE_K_HV (1 << 8) +#define RT1305_EN_PRE_K_HV_BIT 8 +#define RT1305_EN_EFUSE_1P8V (1 << 7) +#define RT1305_EN_EFUSE_1P8V_BIT 7 +#define RT1305_EN_EFUSE_5V (1 << 6) +#define RT1305_EN_EFUSE_5V_BIT 6 +#define RT1305_EN_VCM_6172 (1 << 5) +#define RT1305_EN_VCM_6172_BIT 5 +#define RT1305_POR_EFUSE (1 << 4) +#define RT1305_POR_EFUSE_BIT 4 + +/* Clock Detect (0x3f) */ +#define RT1305_SEL_CLK_DET_SRC_MASK (0x1 << 12) +#define RT1305_SEL_CLK_DET_SRC_SFT 12 +#define RT1305_SEL_CLK_DET_SRC_MCLK (0x0 << 12) +#define RT1305_SEL_CLK_DET_SRC_BCLK (0x1 << 12) + + +/* System Clock Source */ +enum { + RT1305_FS_SYS_PRE_S_MCLK, + RT1305_FS_SYS_PRE_S_PLL1, + RT1305_FS_SYS_PRE_S_RCCLK, /* 98.304M Hz */ +}; + +/* PLL Source 1/2 */ +enum { + RT1305_PLL1_S_BCLK, + RT1305_PLL2_S_MCLK, + RT1305_PLL2_S_RCCLK, /* 98.304M Hz */ +}; + +enum { + RT1305_AIF1, + RT1305_AIFS +}; + +#define R0_UPPER 0x2E8BA2 //5.5 ohm +#define R0_LOWER 0x666666 //2.5 ohm + +#endif /* end of _RT1305_H_ */ diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c new file mode 100644 index 000000000..f09f2d87a --- /dev/null +++ b/sound/soc/codecs/rt274.c @@ -0,0 +1,1231 @@ +/* + * rt274.c -- RT274 ALSA SoC audio codec driver + * + * Copyright 2017 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6347a.h" +#include "rt274.h" + +#define RT274_VENDOR_ID 0x10ec0274 + +struct rt274_priv { + struct reg_default *index_cache; + int index_cache_size; + struct regmap *regmap; + struct snd_soc_component *component; + struct i2c_client *i2c; + struct snd_soc_jack *jack; + struct delayed_work jack_detect_work; + int sys_clk; + int clk_id; + int fs; + bool master; +}; + +static const struct reg_default rt274_index_def[] = { + { 0x00, 0x1004 }, + { 0x01, 0xaaaa }, + { 0x02, 0x88aa }, + { 0x03, 0x0002 }, + { 0x04, 0xaa09 }, + { 0x05, 0x0700 }, + { 0x06, 0x6110 }, + { 0x07, 0x0200 }, + { 0x08, 0xa807 }, + { 0x09, 0x0021 }, + { 0x0a, 0x7770 }, + { 0x0b, 0x7770 }, + { 0x0c, 0x002b }, + { 0x0d, 0x2420 }, + { 0x0e, 0x65c0 }, + { 0x0f, 0x7770 }, + { 0x10, 0x0420 }, + { 0x11, 0x7418 }, + { 0x12, 0x6bd0 }, + { 0x13, 0x645f }, + { 0x14, 0x0400 }, + { 0x15, 0x8ccc }, + { 0x16, 0x4c50 }, + { 0x17, 0xff00 }, + { 0x18, 0x0003 }, + { 0x19, 0x2c11 }, + { 0x1a, 0x830b }, + { 0x1b, 0x4e4b }, + { 0x1c, 0x0000 }, + { 0x1d, 0x0000 }, + { 0x1e, 0x0000 }, + { 0x1f, 0x0000 }, + { 0x20, 0x51ff }, + { 0x21, 0x8000 }, + { 0x22, 0x8f00 }, + { 0x23, 0x88f4 }, + { 0x24, 0x0000 }, + { 0x25, 0x0000 }, + { 0x26, 0x0000 }, + { 0x27, 0x0000 }, + { 0x28, 0x0000 }, + { 0x29, 0x3000 }, + { 0x2a, 0x0000 }, + { 0x2b, 0x0000 }, + { 0x2c, 0x0f00 }, + { 0x2d, 0x100f }, + { 0x2e, 0x2902 }, + { 0x2f, 0xe280 }, + { 0x30, 0x1000 }, + { 0x31, 0x8400 }, + { 0x32, 0x5aaa }, + { 0x33, 0x8420 }, + { 0x34, 0xa20c }, + { 0x35, 0x096a }, + { 0x36, 0x5757 }, + { 0x37, 0xfe05 }, + { 0x38, 0x4901 }, + { 0x39, 0x110a }, + { 0x3a, 0x0010 }, + { 0x3b, 0x60d9 }, + { 0x3c, 0xf214 }, + { 0x3d, 0xc2ba }, + { 0x3e, 0xa928 }, + { 0x3f, 0x0000 }, + { 0x40, 0x9800 }, + { 0x41, 0x0000 }, + { 0x42, 0x2000 }, + { 0x43, 0x3d90 }, + { 0x44, 0x4900 }, + { 0x45, 0x5289 }, + { 0x46, 0x0004 }, + { 0x47, 0xa47a }, + { 0x48, 0xd049 }, + { 0x49, 0x0049 }, + { 0x4a, 0xa83b }, + { 0x4b, 0x0777 }, + { 0x4c, 0x065c }, + { 0x4d, 0x7fff }, + { 0x4e, 0x7fff }, + { 0x4f, 0x0000 }, + { 0x50, 0x0000 }, + { 0x51, 0x0000 }, + { 0x52, 0xbf5f }, + { 0x53, 0x3320 }, + { 0x54, 0xcc00 }, + { 0x55, 0x0000 }, + { 0x56, 0x3f00 }, + { 0x57, 0x0000 }, + { 0x58, 0x0000 }, + { 0x59, 0x0000 }, + { 0x5a, 0x1300 }, + { 0x5b, 0x005f }, + { 0x5c, 0x0000 }, + { 0x5d, 0x1001 }, + { 0x5e, 0x1000 }, + { 0x5f, 0x0000 }, + { 0x60, 0x5554 }, + { 0x61, 0xffc0 }, + { 0x62, 0xa000 }, + { 0x63, 0xd010 }, + { 0x64, 0x0000 }, + { 0x65, 0x3fb1 }, + { 0x66, 0x1881 }, + { 0x67, 0xc810 }, + { 0x68, 0x2000 }, + { 0x69, 0xfff0 }, + { 0x6a, 0x0300 }, + { 0x6b, 0x5060 }, + { 0x6c, 0x0000 }, + { 0x6d, 0x0000 }, + { 0x6e, 0x0c25 }, + { 0x6f, 0x0c0b }, + { 0x70, 0x8000 }, + { 0x71, 0x4008 }, + { 0x72, 0x0000 }, + { 0x73, 0x0800 }, + { 0x74, 0xa28f }, + { 0x75, 0xa050 }, + { 0x76, 0x7fe8 }, + { 0x77, 0xdb8c }, + { 0x78, 0x0000 }, + { 0x79, 0x0000 }, + { 0x7a, 0x2a96 }, + { 0x7b, 0x800f }, + { 0x7c, 0x0200 }, + { 0x7d, 0x1600 }, + { 0x7e, 0x0000 }, + { 0x7f, 0x0000 }, +}; +#define INDEX_CACHE_SIZE ARRAY_SIZE(rt274_index_def) + +static const struct reg_default rt274_reg[] = { + { 0x00170500, 0x00000400 }, + { 0x00220000, 0x00000031 }, + { 0x00239000, 0x00000057 }, + { 0x0023a000, 0x00000057 }, + { 0x00270500, 0x00000400 }, + { 0x00370500, 0x00000400 }, + { 0x00870500, 0x00000400 }, + { 0x00920000, 0x00000031 }, + { 0x00935000, 0x00000097 }, + { 0x00936000, 0x00000097 }, + { 0x00970500, 0x00000400 }, + { 0x00b37000, 0x00000400 }, + { 0x00b37200, 0x00000400 }, + { 0x00b37300, 0x00000400 }, + { 0x00c37000, 0x00000400 }, + { 0x00c37100, 0x00000400 }, + { 0x01270500, 0x00000400 }, + { 0x01370500, 0x00000400 }, + { 0x01371f00, 0x411111f0 }, + { 0x01937000, 0x00000000 }, + { 0x01970500, 0x00000400 }, + { 0x02050000, 0x0000001b }, + { 0x02139000, 0x00000080 }, + { 0x0213a000, 0x00000080 }, + { 0x02170100, 0x00000001 }, + { 0x02170500, 0x00000400 }, + { 0x02170700, 0x00000000 }, + { 0x02270100, 0x00000000 }, + { 0x02370100, 0x00000000 }, + { 0x01970700, 0x00000020 }, + { 0x00830000, 0x00000097 }, + { 0x00930000, 0x00000097 }, + { 0x01270700, 0x00000000 }, +}; + +static bool rt274_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT274_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT274_GET_HP_SENSE: + case RT274_GET_MIC_SENSE: + case RT274_PROC_COEF: + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_DAC_OUT0, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_DAC_OUT1, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_ADC_IN1, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_ADC_IN2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DAC_OUT0, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DAC_OUT1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_ADC_IN1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_ADC_IN2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DMIC1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DMIC2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_LINE1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_LINE2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_CONNECT_SEL, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_CONNECT_SEL, RT274_MIXER_IN1, 0): + case VERB_CMD(AC_VERB_GET_CONNECT_SEL, RT274_MIXER_IN2, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_DMIC1, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_DMIC2, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_LINE1, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_LINE2, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_UNSOLICITED_RESPONSE, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_UNSOLICITED_RESPONSE, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_UNSOLICITED_RESPONSE, RT274_INLINE_CMD, 0): + return true; + default: + return false; + } + + +} + +static bool rt274_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT274_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT274_GET_HP_SENSE: + case RT274_GET_MIC_SENSE: + case RT274_SET_AUDIO_POWER: + case RT274_SET_HPO_POWER: + case RT274_SET_DMIC1_POWER: + case RT274_LOUT_MUX: + case RT274_HPO_MUX: + case RT274_ADC0_MUX: + case RT274_ADC1_MUX: + case RT274_SET_MIC: + case RT274_SET_PIN_HPO: + case RT274_SET_PIN_LOUT3: + case RT274_SET_PIN_DMIC1: + case RT274_SET_AMP_GAIN_HPO: + case RT274_SET_DMIC2_DEFAULT: + case RT274_DAC0L_GAIN: + case RT274_DAC0R_GAIN: + case RT274_DAC1L_GAIN: + case RT274_DAC1R_GAIN: + case RT274_ADCL_GAIN: + case RT274_ADCR_GAIN: + case RT274_MIC_GAIN: + case RT274_HPOL_GAIN: + case RT274_HPOR_GAIN: + case RT274_LOUTL_GAIN: + case RT274_LOUTR_GAIN: + case RT274_DAC_FORMAT: + case RT274_ADC_FORMAT: + case RT274_COEF_INDEX: + case RT274_PROC_COEF: + case RT274_SET_AMP_GAIN_ADC_IN1: + case RT274_SET_AMP_GAIN_ADC_IN2: + case RT274_SET_POWER(RT274_DAC_OUT0): + case RT274_SET_POWER(RT274_DAC_OUT1): + case RT274_SET_POWER(RT274_ADC_IN1): + case RT274_SET_POWER(RT274_ADC_IN2): + case RT274_SET_POWER(RT274_DMIC2): + case RT274_SET_POWER(RT274_MIC): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_DAC_OUT0, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_DAC_OUT1, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_ADC_IN1, 0): + case VERB_CMD(AC_VERB_GET_STREAM_FORMAT, RT274_ADC_IN2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DAC_OUT0, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DAC_OUT1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_ADC_IN1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_ADC_IN2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DMIC1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_DMIC2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_LINE1, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_LINE2, 0): + case VERB_CMD(AC_VERB_GET_AMP_GAIN_MUTE, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_CONNECT_SEL, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_CONNECT_SEL, RT274_MIXER_IN1, 0): + case VERB_CMD(AC_VERB_GET_CONNECT_SEL, RT274_MIXER_IN2, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_DMIC1, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_DMIC2, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_LINE1, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_LINE2, 0): + case VERB_CMD(AC_VERB_GET_PIN_WIDGET_CONTROL, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_UNSOLICITED_RESPONSE, RT274_HP_OUT, 0): + case VERB_CMD(AC_VERB_GET_UNSOLICITED_RESPONSE, RT274_MIC, 0): + case VERB_CMD(AC_VERB_GET_UNSOLICITED_RESPONSE, RT274_INLINE_CMD, 0): + return true; + default: + return false; + } +} + +#ifdef CONFIG_PM +static void rt274_index_sync(struct snd_soc_component *component) +{ + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < INDEX_CACHE_SIZE; i++) { + snd_soc_component_write(component, rt274->index_cache[i].reg, + rt274->index_cache[i].def); + } +} +#endif + +static int rt274_jack_detect(struct rt274_priv *rt274, bool *hp, bool *mic) +{ + unsigned int buf; + + *hp = false; + *mic = false; + + if (!rt274->component) + return -EINVAL; + + regmap_read(rt274->regmap, RT274_GET_HP_SENSE, &buf); + *hp = buf & 0x80000000; + regmap_read(rt274->regmap, RT274_GET_MIC_SENSE, &buf); + *mic = buf & 0x80000000; + + pr_debug("*hp = %d *mic = %d\n", *hp, *mic); + + return 0; +} + +static void rt274_jack_detect_work(struct work_struct *work) +{ + struct rt274_priv *rt274 = + container_of(work, struct rt274_priv, jack_detect_work.work); + int status = 0; + bool hp = false; + bool mic = false; + + if (rt274_jack_detect(rt274, &hp, &mic) < 0) + return; + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt274->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); +} + +static irqreturn_t rt274_irq(int irq, void *data); + +static int rt274_mic_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + rt274->jack = jack; + + if (jack == NULL) { + /* Disable jack detection */ + regmap_update_bits(rt274->regmap, RT274_EAPD_GPIO_IRQ_CTRL, + RT274_IRQ_EN, RT274_IRQ_DIS); + + return 0; + } + + regmap_update_bits(rt274->regmap, RT274_EAPD_GPIO_IRQ_CTRL, + RT274_IRQ_EN, RT274_IRQ_EN); + + /* Send an initial report */ + rt274_irq(0, rt274); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6350, 50, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt274_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC0 Playback Volume", RT274_DAC0L_GAIN, + RT274_DAC0R_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", RT274_DAC1L_GAIN, + RT274_DAC1R_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_DOUBLE_R_TLV("ADC0 Capture Volume", RT274_ADCL_GAIN, + RT274_ADCR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_DOUBLE_R("ADC0 Capture Switch", RT274_ADCL_GAIN, + RT274_ADCR_GAIN, RT274_MUTE_SFT, 1, 1), + SOC_SINGLE_TLV("AMIC Volume", RT274_MIC_GAIN, + 0, 0x3, 0, mic_vol_tlv), +}; + +static const struct snd_kcontrol_new hpol_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT274_HPOL_GAIN, + RT274_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpor_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT274_HPOR_GAIN, + RT274_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new loutl_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT274_LOUTL_GAIN, + RT274_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new loutr_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT274_LOUTR_GAIN, + RT274_MUTE_SFT, 1, 1); + +/* ADC0 source */ +static const char * const rt274_adc_src[] = { + "Mic", "Line1", "Line2", "Dmic" +}; + +static SOC_ENUM_SINGLE_DECL( + rt274_adc0_enum, RT274_ADC0_MUX, RT274_ADC_SEL_SFT, + rt274_adc_src); + +static const struct snd_kcontrol_new rt274_adc0_mux = + SOC_DAPM_ENUM("ADC 0 source", rt274_adc0_enum); + +static SOC_ENUM_SINGLE_DECL( + rt274_adc1_enum, RT274_ADC1_MUX, RT274_ADC_SEL_SFT, + rt274_adc_src); + +static const struct snd_kcontrol_new rt274_adc1_mux = + SOC_DAPM_ENUM("ADC 1 source", rt274_adc1_enum); + +static const char * const rt274_dac_src[] = { + "DAC OUT0", "DAC OUT1" +}; +/* HP-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt274_hpo_enum, RT274_HPO_MUX, + 0, rt274_dac_src); + +static const struct snd_kcontrol_new rt274_hpo_mux = +SOC_DAPM_ENUM("HPO source", rt274_hpo_enum); + +/* Line out source */ +static SOC_ENUM_SINGLE_DECL(rt274_lout_enum, RT274_LOUT_MUX, + 0, rt274_dac_src); + +static const struct snd_kcontrol_new rt274_lout_mux = +SOC_DAPM_ENUM("LOUT source", rt274_lout_enum); + +static const struct snd_soc_dapm_widget rt274_dapm_widgets[] = { + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC1 Pin"), + SND_SOC_DAPM_INPUT("DMIC2 Pin"), + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("LINE2"), + + /* DMIC */ + SND_SOC_DAPM_PGA("DMIC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC2", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC 0", NULL, RT274_SET_STREAMID_ADC1, 4, 0), + SND_SOC_DAPM_ADC("ADC 1", NULL, RT274_SET_STREAMID_ADC2, 4, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("ADC 0 Mux", SND_SOC_NOPM, 0, 0, + &rt274_adc0_mux), + SND_SOC_DAPM_MUX("ADC 1 Mux", SND_SOC_NOPM, 0, 0, + &rt274_adc1_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RXL", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF1RXR", "AIF1 Playback", 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TXL", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TXR", "AIF1 Capture", 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RXL", "AIF1 Playback", 2, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RXR", "AIF1 Playback", 3, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TXL", "AIF1 Capture", 2, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TXR", "AIF1 Capture", 3, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("DAC 0", NULL, RT274_SET_STREAMID_DAC0, 4, 0), + SND_SOC_DAPM_DAC("DAC 1", NULL, RT274_SET_STREAMID_DAC1, 4, 0), + + /* Output Mux */ + SND_SOC_DAPM_MUX("HPO Mux", SND_SOC_NOPM, 0, 0, &rt274_hpo_mux), + SND_SOC_DAPM_MUX("LOUT Mux", SND_SOC_NOPM, 0, 0, &rt274_lout_mux), + + SND_SOC_DAPM_SUPPLY("HP Power", RT274_SET_PIN_HPO, + RT274_SET_PIN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LOUT Power", RT274_SET_PIN_LOUT3, + RT274_SET_PIN_SFT, 0, NULL, 0), + + /* Output Mixer */ + SND_SOC_DAPM_PGA("DAC OUT0", SND_SOC_NOPM, 0, 0, + NULL, 0), + SND_SOC_DAPM_PGA("DAC OUT1", SND_SOC_NOPM, 0, 0, + NULL, 0), + + /* Output Pga */ + SND_SOC_DAPM_SWITCH("LOUT L", SND_SOC_NOPM, 0, 0, + &loutl_enable_control), + SND_SOC_DAPM_SWITCH("LOUT R", SND_SOC_NOPM, 0, 0, + &loutr_enable_control), + SND_SOC_DAPM_SWITCH("HPO L", SND_SOC_NOPM, 0, 0, + &hpol_enable_control), + SND_SOC_DAPM_SWITCH("HPO R", SND_SOC_NOPM, 0, 0, + &hpor_enable_control), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPO Pin"), + SND_SOC_DAPM_OUTPUT("SPDIF"), + SND_SOC_DAPM_OUTPUT("LINE3"), +}; + +static const struct snd_soc_dapm_route rt274_dapm_routes[] = { + {"DMIC1", NULL, "DMIC1 Pin"}, + {"DMIC2", NULL, "DMIC2 Pin"}, + + {"ADC 0 Mux", "Mic", "MIC"}, + {"ADC 0 Mux", "Dmic", "DMIC1"}, + {"ADC 0 Mux", "Line1", "LINE1"}, + {"ADC 0 Mux", "Line2", "LINE2"}, + {"ADC 1 Mux", "Mic", "MIC"}, + {"ADC 1 Mux", "Dmic", "DMIC2"}, + {"ADC 1 Mux", "Line1", "LINE1"}, + {"ADC 1 Mux", "Line2", "LINE2"}, + + {"ADC 0", NULL, "ADC 0 Mux"}, + {"ADC 1", NULL, "ADC 1 Mux"}, + + {"AIF1TXL", NULL, "ADC 0"}, + {"AIF1TXR", NULL, "ADC 0"}, + {"AIF2TXL", NULL, "ADC 1"}, + {"AIF2TXR", NULL, "ADC 1"}, + + {"DAC 0", NULL, "AIF1RXL"}, + {"DAC 0", NULL, "AIF1RXR"}, + {"DAC 1", NULL, "AIF2RXL"}, + {"DAC 1", NULL, "AIF2RXR"}, + + {"DAC OUT0", NULL, "DAC 0"}, + + {"DAC OUT1", NULL, "DAC 1"}, + + {"LOUT Mux", "DAC OUT0", "DAC OUT0"}, + {"LOUT Mux", "DAC OUT1", "DAC OUT1"}, + + {"LOUT L", "Switch", "LOUT Mux"}, + {"LOUT R", "Switch", "LOUT Mux"}, + {"LOUT L", NULL, "LOUT Power"}, + {"LOUT R", NULL, "LOUT Power"}, + + {"LINE3", NULL, "LOUT L"}, + {"LINE3", NULL, "LOUT R"}, + + {"HPO Mux", "DAC OUT0", "DAC OUT0"}, + {"HPO Mux", "DAC OUT1", "DAC OUT1"}, + + {"HPO L", "Switch", "HPO Mux"}, + {"HPO R", "Switch", "HPO Mux"}, + {"HPO L", NULL, "HP Power"}, + {"HPO R", NULL, "HP Power"}, + + {"HPO Pin", NULL, "HPO L"}, + {"HPO Pin", NULL, "HPO R"}, +}; + +static int rt274_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + int d_len_code = 0, c_len_code = 0; + + switch (params_rate(params)) { + /* bit 14 0:48K 1:44.1K */ + case 44100: + case 48000: + break; + default: + dev_err(component->dev, "Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + switch (rt274->sys_clk) { + case 12288000: + case 24576000: + if (params_rate(params) != 48000) { + dev_err(component->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt274->sys_clk); + return -EINVAL; + } + break; + case 11289600: + case 22579200: + if (params_rate(params) != 44100) { + dev_err(component->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt274->sys_clk); + return -EINVAL; + } + break; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + switch (params_width(params)) { + /* bit 6:4 Bits per Sample */ + case 16: + d_len_code = 0; + c_len_code = 0; + val |= (0x1 << 4); + break; + case 32: + d_len_code = 2; + c_len_code = 3; + val |= (0x4 << 4); + break; + case 20: + d_len_code = 1; + c_len_code = 1; + val |= (0x2 << 4); + break; + case 24: + d_len_code = 2; + c_len_code = 2; + val |= (0x3 << 4); + break; + case 8: + d_len_code = 3; + c_len_code = 0; + break; + default: + return -EINVAL; + } + + if (rt274->master) + c_len_code = 0x3; + + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, 0xc018, d_len_code << 3 | c_len_code << 14); + dev_dbg(component->dev, "format val = 0x%x\n", val); + + snd_soc_component_update_bits(component, RT274_DAC_FORMAT, 0x407f, val); + snd_soc_component_update_bits(component, RT274_ADC_FORMAT, 0x407f, val); + + return 0; +} + +static int rt274_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, RT274_I2S_MODE_MASK, RT274_I2S_MODE_M); + rt274->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, RT274_I2S_MODE_MASK, RT274_I2S_MODE_S); + rt274->master = false; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_soc_component_update_bits(component, RT274_I2S_CTRL1, + RT274_I2S_FMT_MASK, RT274_I2S_FMT_I2S); + break; + case SND_SOC_DAIFMT_LEFT_J: + snd_soc_component_update_bits(component, RT274_I2S_CTRL1, + RT274_I2S_FMT_MASK, RT274_I2S_FMT_LJ); + break; + case SND_SOC_DAIFMT_DSP_A: + snd_soc_component_update_bits(component, RT274_I2S_CTRL1, + RT274_I2S_FMT_MASK, RT274_I2S_FMT_PCMA); + break; + case SND_SOC_DAIFMT_DSP_B: + snd_soc_component_update_bits(component, RT274_I2S_CTRL1, + RT274_I2S_FMT_MASK, RT274_I2S_FMT_PCMB); + break; + default: + return -EINVAL; + } + /* bit 15 Stream Type 0:PCM 1:Non-PCM */ + snd_soc_component_update_bits(component, RT274_DAC_FORMAT, 0x8000, 0); + snd_soc_component_update_bits(component, RT274_ADC_FORMAT, 0x8000, 0); + + return 0; +} + +static int rt274_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + switch (source) { + case RT274_PLL2_S_MCLK: + snd_soc_component_update_bits(component, RT274_PLL2_CTRL, + RT274_PLL2_SRC_MASK, RT274_PLL2_SRC_MCLK); + break; + default: + dev_warn(component->dev, "invalid pll source, use BCLK\n"); + case RT274_PLL2_S_BCLK: + snd_soc_component_update_bits(component, RT274_PLL2_CTRL, + RT274_PLL2_SRC_MASK, RT274_PLL2_SRC_BCLK); + break; + } + + if (source == RT274_PLL2_S_BCLK) { + snd_soc_component_update_bits(component, RT274_MCLK_CTRL, + (0x3 << 12), (0x3 << 12)); + switch (rt274->fs) { + case 50: + snd_soc_component_write(component, 0x7a, 0xaab6); + snd_soc_component_write(component, 0x7b, 0x0301); + snd_soc_component_write(component, 0x7c, 0x04fe); + break; + case 64: + snd_soc_component_write(component, 0x7a, 0xaa96); + snd_soc_component_write(component, 0x7b, 0x8003); + snd_soc_component_write(component, 0x7c, 0x081e); + break; + case 128: + snd_soc_component_write(component, 0x7a, 0xaa96); + snd_soc_component_write(component, 0x7b, 0x8003); + snd_soc_component_write(component, 0x7c, 0x080e); + break; + default: + dev_warn(component->dev, "invalid freq_in, assume 4.8M\n"); + case 100: + snd_soc_component_write(component, 0x7a, 0xaab6); + snd_soc_component_write(component, 0x7b, 0x0301); + snd_soc_component_write(component, 0x7c, 0x047e); + break; + } + } + + return 0; +} + +static int rt274_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + unsigned int clk_src, mclk_en; + + dev_dbg(component->dev, "%s freq=%d\n", __func__, freq); + + switch (clk_id) { + case RT274_SCLK_S_MCLK: + mclk_en = RT274_MCLK_MODE_EN; + clk_src = RT274_CLK_SRC_MCLK; + break; + case RT274_SCLK_S_PLL1: + mclk_en = RT274_MCLK_MODE_DIS; + clk_src = RT274_CLK_SRC_MCLK; + break; + case RT274_SCLK_S_PLL2: + mclk_en = RT274_MCLK_MODE_EN; + clk_src = RT274_CLK_SRC_PLL2; + break; + default: + mclk_en = RT274_MCLK_MODE_DIS; + clk_src = RT274_CLK_SRC_MCLK; + dev_warn(component->dev, "invalid sysclk source, use PLL1\n"); + break; + } + snd_soc_component_update_bits(component, RT274_MCLK_CTRL, + RT274_MCLK_MODE_MASK, mclk_en); + snd_soc_component_update_bits(component, RT274_CLK_CTRL, + RT274_CLK_SRC_MASK, clk_src); + + switch (freq) { + case 19200000: + if (clk_id == RT274_SCLK_S_MCLK) { + dev_err(component->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_component_update_bits(component, + RT274_I2S_CTRL2, 0x40, 0x40); + break; + case 24000000: + if (clk_id == RT274_SCLK_S_MCLK) { + dev_err(component->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_component_update_bits(component, + RT274_I2S_CTRL2, 0x40, 0x0); + break; + case 12288000: + case 11289600: + snd_soc_component_update_bits(component, + RT274_MCLK_CTRL, 0x1fcf, 0x0008); + break; + case 24576000: + case 22579200: + snd_soc_component_update_bits(component, + RT274_MCLK_CTRL, 0x1fcf, 0x1543); + break; + default: + dev_err(component->dev, "Unsupported system clock\n"); + return -EINVAL; + } + + rt274->sys_clk = freq; + rt274->clk_id = clk_id; + + return 0; +} + +static int rt274_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s ratio=%d\n", __func__, ratio); + rt274->fs = ratio; + if ((ratio / 50) == 0) + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, 0x1000, 0x1000); + else + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, 0x1000, 0x0); + + + return 0; +} + +static int rt274_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) + +{ + struct snd_soc_component *component = dai->component; + + if (rx_mask || tx_mask) { + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, RT274_TDM_EN, RT274_TDM_EN); + } else { + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, RT274_TDM_EN, RT274_TDM_DIS); + return 0; + } + + switch (slots) { + case 4: + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, RT274_TDM_CH_NUM, RT274_TDM_4CH); + break; + case 2: + snd_soc_component_update_bits(component, + RT274_I2S_CTRL1, RT274_TDM_CH_NUM, RT274_TDM_2CH); + break; + default: + dev_err(component->dev, + "Support 2 or 4 slots TDM only\n"); + return -EINVAL; + } + + return 0; +} + +static int rt274_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == + snd_soc_component_get_bias_level(component)) { + snd_soc_component_write(component, + RT274_SET_AUDIO_POWER, AC_PWRST_D0); + } + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_component_write(component, + RT274_SET_AUDIO_POWER, AC_PWRST_D3); + break; + + default: + break; + } + + return 0; +} + +static irqreturn_t rt274_irq(int irq, void *data) +{ + struct rt274_priv *rt274 = data; + bool hp = false; + bool mic = false; + int ret, status = 0; + + /* Clear IRQ */ + regmap_update_bits(rt274->regmap, RT274_EAPD_GPIO_IRQ_CTRL, + RT274_IRQ_CLR, RT274_IRQ_CLR); + + ret = rt274_jack_detect(rt274, &hp, &mic); + + if (ret == 0) { + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt274->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + pm_wakeup_event(&rt274->i2c->dev, 300); + } + + return IRQ_HANDLED; +} + +static int rt274_probe(struct snd_soc_component *component) +{ + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + rt274->component = component; + + if (rt274->i2c->irq) { + INIT_DELAYED_WORK(&rt274->jack_detect_work, + rt274_jack_detect_work); + schedule_delayed_work(&rt274->jack_detect_work, + msecs_to_jiffies(1250)); + } + + return 0; +} + +static void rt274_remove(struct snd_soc_component *component) +{ + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&rt274->jack_detect_work); +} + +#ifdef CONFIG_PM +static int rt274_suspend(struct snd_soc_component *component) +{ + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt274->regmap, true); + regcache_mark_dirty(rt274->regmap); + + return 0; +} + +static int rt274_resume(struct snd_soc_component *component) +{ + struct rt274_priv *rt274 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt274->regmap, false); + rt274_index_sync(component); + regcache_sync(rt274->regmap); + + return 0; +} +#else +#define rt274_suspend NULL +#define rt274_resume NULL +#endif + +#define RT274_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT274_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt274_aif_dai_ops = { + .hw_params = rt274_hw_params, + .set_fmt = rt274_set_dai_fmt, + .set_sysclk = rt274_set_dai_sysclk, + .set_pll = rt274_set_dai_pll, + .set_bclk_ratio = rt274_set_bclk_ratio, + .set_tdm_slot = rt274_set_tdm_slot, +}; + +static struct snd_soc_dai_driver rt274_dai[] = { + { + .name = "rt274-aif1", + .id = RT274_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT274_STEREO_RATES, + .formats = RT274_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT274_STEREO_RATES, + .formats = RT274_FORMATS, + }, + .ops = &rt274_aif_dai_ops, + .symmetric_rates = 1, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt274 = { + .probe = rt274_probe, + .remove = rt274_remove, + .suspend = rt274_suspend, + .resume = rt274_resume, + .set_bias_level = rt274_set_bias_level, + .set_jack = rt274_mic_detect, + .controls = rt274_snd_controls, + .num_controls = ARRAY_SIZE(rt274_snd_controls), + .dapm_widgets = rt274_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt274_dapm_widgets), + .dapm_routes = rt274_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt274_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt274_regmap = { + .reg_bits = 32, + .val_bits = 32, + .max_register = 0x05bfffff, + .volatile_reg = rt274_volatile_register, + .readable_reg = rt274_readable_register, + .reg_write = rl6347a_hw_write, + .reg_read = rl6347a_hw_read, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt274_reg, + .num_reg_defaults = ARRAY_SIZE(rt274_reg), +}; + +#ifdef CONFIG_OF +static const struct of_device_id rt274_of_match[] = { + {.compatible = "realtek,rt274"}, + {}, +}; +MODULE_DEVICE_TABLE(of, rt274_of_match); +#endif + +static const struct i2c_device_id rt274_i2c_id[] = { + {"rt274", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt274_i2c_id); + +static const struct acpi_device_id rt274_acpi_match[] = { + { "10EC0274", 0 }, + { "INT34C2", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt274_acpi_match); + +static int rt274_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt274_priv *rt274; + + int ret; + unsigned int val; + + rt274 = devm_kzalloc(&i2c->dev, sizeof(*rt274), + GFP_KERNEL); + if (rt274 == NULL) + return -ENOMEM; + + rt274->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt274_regmap); + if (IS_ERR(rt274->regmap)) { + ret = PTR_ERR(rt274->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = regmap_read(rt274->regmap, + RT274_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &val); + if (ret) + return ret; + + if (val != RT274_VENDOR_ID) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt274\n", val); + return -ENODEV; + } + + rt274->index_cache = devm_kmemdup(&i2c->dev, rt274_index_def, + sizeof(rt274_index_def), GFP_KERNEL); + if (!rt274->index_cache) + return -ENOMEM; + + rt274->index_cache_size = INDEX_CACHE_SIZE; + rt274->i2c = i2c; + i2c_set_clientdata(i2c, rt274); + + /* reset codec */ + regmap_write(rt274->regmap, RT274_RESET, 0); + regmap_update_bits(rt274->regmap, 0x1a, 0x4000, 0x4000); + + /* Set Pad PDB is floating */ + regmap_update_bits(rt274->regmap, RT274_PAD_CTRL12, 0x3, 0x0); + regmap_write(rt274->regmap, RT274_COEF5b_INDEX, 0x01); + regmap_write(rt274->regmap, RT274_COEF5b_COEF, 0x8540); + regmap_update_bits(rt274->regmap, 0x6f, 0x0100, 0x0100); + /* Combo jack auto detect */ + regmap_write(rt274->regmap, 0x4a, 0x201b); + /* Aux mode off */ + regmap_update_bits(rt274->regmap, 0x6f, 0x3000, 0x2000); + /* HP DC Calibration */ + regmap_update_bits(rt274->regmap, 0x6f, 0xf, 0x0); + /* Set NID=58h.Index 00h [15]= 1b; */ + regmap_write(rt274->regmap, RT274_COEF58_INDEX, 0x00); + regmap_write(rt274->regmap, RT274_COEF58_COEF, 0xb888); + msleep(500); + regmap_update_bits(rt274->regmap, 0x6f, 0xf, 0xb); + regmap_write(rt274->regmap, RT274_COEF58_INDEX, 0x00); + regmap_write(rt274->regmap, RT274_COEF58_COEF, 0x3888); + /* Set pin widget */ + regmap_write(rt274->regmap, RT274_SET_PIN_HPO, 0x40); + regmap_write(rt274->regmap, RT274_SET_PIN_LOUT3, 0x40); + regmap_write(rt274->regmap, RT274_SET_MIC, 0x20); + regmap_write(rt274->regmap, RT274_SET_PIN_DMIC1, 0x20); + + regmap_update_bits(rt274->regmap, RT274_I2S_CTRL2, 0xc004, 0x4004); + regmap_update_bits(rt274->regmap, RT274_EAPD_GPIO_IRQ_CTRL, + RT274_GPI2_SEL_MASK, RT274_GPI2_SEL_DMIC_CLK); + + /* jack detection */ + regmap_write(rt274->regmap, RT274_UNSOLICITED_HP_OUT, 0x81); + regmap_write(rt274->regmap, RT274_UNSOLICITED_MIC, 0x82); + + if (rt274->i2c->irq) { + ret = request_threaded_irq(rt274->i2c->irq, NULL, rt274_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "rt274", rt274); + if (ret != 0) { + dev_err(&i2c->dev, + "Failed to reguest IRQ: %d\n", ret); + return ret; + } + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt274, + rt274_dai, ARRAY_SIZE(rt274_dai)); + + return ret; +} + +static int rt274_i2c_remove(struct i2c_client *i2c) +{ + struct rt274_priv *rt274 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, rt274); + + return 0; +} + + +static struct i2c_driver rt274_i2c_driver = { + .driver = { + .name = "rt274", + .acpi_match_table = ACPI_PTR(rt274_acpi_match), +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rt274_of_match), +#endif + }, + .probe = rt274_i2c_probe, + .remove = rt274_i2c_remove, + .id_table = rt274_i2c_id, +}; + +module_i2c_driver(rt274_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT274 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt274.h b/sound/soc/codecs/rt274.h new file mode 100644 index 000000000..4fd1bcb73 --- /dev/null +++ b/sound/soc/codecs/rt274.h @@ -0,0 +1,217 @@ +/* + * rt274.h -- RT274 ALSA SoC audio driver + * + * Copyright 2016 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT274_H__ +#define __RT274_H__ + +#define VERB_CMD(V, N, D) ((N << 20) | (V << 8) | D) + +#define RT274_AUDIO_FUNCTION_GROUP 0x01 +#define RT274_DAC_OUT0 0x02 +#define RT274_DAC_OUT1 0x03 +#define RT274_ADC_IN2 0x08 +#define RT274_ADC_IN1 0x09 +#define RT274_DIG_CVT 0x0a +#define RT274_DMIC1 0x12 +#define RT274_DMIC2 0x13 +#define RT274_MIC 0x19 +#define RT274_LINE1 0x1a +#define RT274_LINE2 0x1b +#define RT274_LINE3 0x16 +#define RT274_SPDIF 0x1e +#define RT274_VENDOR_REGISTERS 0x20 +#define RT274_HP_OUT 0x21 +#define RT274_MIXER_IN1 0x22 +#define RT274_MIXER_IN2 0x23 +#define RT274_INLINE_CMD 0x55 + +#define RT274_SET_PIN_SFT 6 +#define RT274_SET_PIN_ENABLE 0x40 +#define RT274_SET_PIN_DISABLE 0 +#define RT274_SET_EAPD_HIGH 0x2 +#define RT274_SET_EAPD_LOW 0 + +#define RT274_MUTE_SFT 7 + +/* Verb commands */ +#define RT274_RESET\ + VERB_CMD(AC_VERB_SET_CODEC_RESET, RT274_AUDIO_FUNCTION_GROUP, 0) +#define RT274_GET_PARAM(NID, PARAM) VERB_CMD(AC_VERB_PARAMETERS, NID, PARAM) +#define RT274_SET_POWER(NID) VERB_CMD(AC_VERB_SET_POWER_STATE, NID, 0) +#define RT274_SET_AUDIO_POWER RT274_SET_POWER(RT274_AUDIO_FUNCTION_GROUP) +#define RT274_SET_HPO_POWER RT274_SET_POWER(RT274_HP_OUT) +#define RT274_SET_DMIC1_POWER RT274_SET_POWER(RT274_DMIC1) +#define RT274_LOUT_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT274_LINE3, 0) +#define RT274_HPO_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT274_HP_OUT, 0) +#define RT274_ADC0_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT274_MIXER_IN1, 0) +#define RT274_ADC1_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT274_MIXER_IN2, 0) +#define RT274_SET_MIC\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT274_MIC, 0) +#define RT274_SET_PIN_LOUT3\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT274_LINE3, 0) +#define RT274_SET_PIN_HPO\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT274_HP_OUT, 0) +#define RT274_SET_PIN_DMIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT274_DMIC1, 0) +#define RT274_SET_PIN_SPDIF\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT274_SPDIF, 0) +#define RT274_SET_PIN_DIG_CVT\ + VERB_CMD(AC_VERB_SET_DIGI_CONVERT_1, RT274_DIG_CVT, 0) +#define RT274_SET_AMP_GAIN_HPO\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_HP_OUT, 0) +#define RT274_SET_AMP_GAIN_ADC_IN1\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_ADC_IN1, 0) +#define RT274_SET_AMP_GAIN_ADC_IN2\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_ADC_IN2, 0) +#define RT274_GET_HP_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT274_HP_OUT, 0) +#define RT274_GET_MIC_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT274_MIC, 0) +#define RT274_SET_DMIC2_DEFAULT\ + VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT274_DMIC2, 0) +#define RT274_SET_SPDIF_DEFAULT\ + VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT274_SPDIF, 0) +#define RT274_DAC0L_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_DAC_OUT0, 0xa000) +#define RT274_DAC0R_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_DAC_OUT0, 0x9000) +#define RT274_DAC1L_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_DAC_OUT1, 0xa000) +#define RT274_DAC1R_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_DAC_OUT1, 0x9000) +#define RT274_ADCL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_ADC_IN1, 0x6000) +#define RT274_ADCR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_ADC_IN1, 0x5000) +#define RT274_MIC_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_MIC, 0x7000) +#define RT274_LOUTL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_LINE3, 0xa000) +#define RT274_LOUTR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_LINE3, 0x9000) +#define RT274_HPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_HP_OUT, 0xa000) +#define RT274_HPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT274_HP_OUT, 0x9000) +#define RT274_DAC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT274_DAC_OUT0, 0) +#define RT274_ADC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT274_ADC_IN1, 0) +#define RT274_COEF_INDEX\ + VERB_CMD(AC_VERB_SET_COEF_INDEX, RT274_VENDOR_REGISTERS, 0) +#define RT274_PROC_COEF\ + VERB_CMD(AC_VERB_SET_PROC_COEF, RT274_VENDOR_REGISTERS, 0) +#define RT274_UNSOLICITED_INLINE_CMD\ + VERB_CMD(AC_VERB_SET_UNSOLICITED_ENABLE, RT274_INLINE_CMD, 0) +#define RT274_UNSOLICITED_HP_OUT\ + VERB_CMD(AC_VERB_SET_UNSOLICITED_ENABLE, RT274_HP_OUT, 0) +#define RT274_UNSOLICITED_MIC\ + VERB_CMD(AC_VERB_SET_UNSOLICITED_ENABLE, RT274_MIC, 0) +#define RT274_COEF58_INDEX\ + VERB_CMD(AC_VERB_SET_COEF_INDEX, 0x58, 0) +#define RT274_COEF58_COEF\ + VERB_CMD(AC_VERB_SET_PROC_COEF, 0x58, 0) +#define RT274_COEF5b_INDEX\ + VERB_CMD(AC_VERB_SET_COEF_INDEX, 0x5b, 0) +#define RT274_COEF5b_COEF\ + VERB_CMD(AC_VERB_SET_PROC_COEF, 0x5b, 0) +#define RT274_SET_STREAMID_DAC0\ + VERB_CMD(AC_VERB_SET_CHANNEL_STREAMID, RT274_DAC_OUT0, 0) +#define RT274_SET_STREAMID_DAC1\ + VERB_CMD(AC_VERB_SET_CHANNEL_STREAMID, RT274_DAC_OUT1, 0) +#define RT274_SET_STREAMID_ADC1\ + VERB_CMD(AC_VERB_SET_CHANNEL_STREAMID, RT274_ADC_IN1, 0) +#define RT274_SET_STREAMID_ADC2\ + VERB_CMD(AC_VERB_SET_CHANNEL_STREAMID, RT274_ADC_IN2, 0) + +/* Index registers */ +#define RT274_EAPD_GPIO_IRQ_CTRL 0x10 +#define RT274_PAD_CTRL12 0x35 +#define RT274_I2S_CTRL1 0x63 +#define RT274_I2S_CTRL2 0x64 +#define RT274_MCLK_CTRL 0x71 +#define RT274_CLK_CTRL 0x72 +#define RT274_PLL2_CTRL 0x7b + + +/* EAPD GPIO IRQ control (Index 0x10) */ +#define RT274_IRQ_DIS (0x0 << 13) +#define RT274_IRQ_EN (0x1 << 13) +#define RT274_IRQ_CLR (0x1 << 12) +#define RT274_GPI2_SEL_MASK (0x3 << 7) +#define RT274_GPI2_SEL_GPIO2 (0x0 << 7) +#define RT274_GPI2_SEL_I2S (0x1 << 7) +#define RT274_GPI2_SEL_DMIC_CLK (0x2 << 7) +#define RT274_GPI2_SEL_CBJ (0x3 << 7) + +/* Front I2S_Interface control 1 (Index 0x63) */ +#define RT274_I2S_MODE_MASK (0x1 << 11) +#define RT274_I2S_MODE_S (0x0 << 11) +#define RT274_I2S_MODE_M (0x1 << 11) +#define RT274_TDM_DIS (0x0 << 10) +#define RT274_TDM_EN (0x1 << 10) +#define RT274_TDM_CH_NUM (0x1 << 7) +#define RT274_TDM_2CH (0x0 << 7) +#define RT274_TDM_4CH (0x1 << 7) +#define RT274_I2S_FMT_MASK (0x3 << 8) +#define RT274_I2S_FMT_I2S (0x0 << 8) +#define RT274_I2S_FMT_LJ (0x1 << 8) +#define RT274_I2S_FMT_PCMA (0x2 << 8) +#define RT274_I2S_FMT_PCMB (0x3 << 8) + +/* MCLK clock domain control (Index 0x71) */ +#define RT274_MCLK_MODE_MASK (0x1 << 14) +#define RT274_MCLK_MODE_DIS (0x0 << 14) +#define RT274_MCLK_MODE_EN (0x1 << 14) + +/* Clock control (Index 0x72) */ +#define RT274_CLK_SRC_MASK (0x7 << 3) +#define RT274_CLK_SRC_MCLK (0x0 << 3) +#define RT274_CLK_SRC_PLL2 (0x3 << 3) + +/* PLL2 control (Index 0x7b) */ +#define RT274_PLL2_SRC_MASK (0x1 << 13) +#define RT274_PLL2_SRC_MCLK (0x0 << 13) +#define RT274_PLL2_SRC_BCLK (0x1 << 13) + +/* HP-OUT (0x21) */ +#define RT274_M_HP_MUX_SFT 14 +#define RT274_HP_SEL_MASK 0x1 +#define RT274_HP_SEL_SFT 0 +#define RT274_HP_SEL_F 0 +#define RT274_HP_SEL_S 1 + +/* ADC (0x22) (0x23) */ +#define RT274_ADC_SEL_MASK 0x7 +#define RT274_ADC_SEL_SFT 0 +#define RT274_ADC_SEL_MIC 0 +#define RT274_ADC_SEL_LINE1 1 +#define RT274_ADC_SEL_LINE2 2 +#define RT274_ADC_SEL_DMIC 3 + +#define RT274_SCLK_S_MCLK 0 +#define RT274_SCLK_S_PLL1 1 +#define RT274_SCLK_S_PLL2 2 + +#define RT274_PLL2_S_MCLK 0 +#define RT274_PLL2_S_BCLK 1 + +enum { + RT274_AIF1, + RT274_AIFS, +}; + +#endif /* __RT274_H__ */ + diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c new file mode 100644 index 000000000..c29c6cf41 --- /dev/null +++ b/sound/soc/codecs/rt286.c @@ -0,0 +1,1280 @@ +/* + * rt286.c -- RT286 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6347a.h" +#include "rt286.h" + +#define RT286_VENDOR_ID 0x10ec0286 +#define RT288_VENDOR_ID 0x10ec0288 + +struct rt286_priv { + struct reg_default *index_cache; + int index_cache_size; + struct regmap *regmap; + struct snd_soc_component *component; + struct rt286_platform_data pdata; + struct i2c_client *i2c; + struct snd_soc_jack *jack; + struct delayed_work jack_detect_work; + int sys_clk; + int clk_id; +}; + +static const struct reg_default rt286_index_def[] = { + { 0x01, 0xaaaa }, + { 0x02, 0x8aaa }, + { 0x03, 0x0002 }, + { 0x04, 0xaf01 }, + { 0x08, 0x000d }, + { 0x09, 0xd810 }, + { 0x0a, 0x0120 }, + { 0x0b, 0x0000 }, + { 0x0d, 0x2800 }, + { 0x0f, 0x0000 }, + { 0x19, 0x0a17 }, + { 0x20, 0x0020 }, + { 0x33, 0x0208 }, + { 0x49, 0x0004 }, + { 0x4f, 0x50e9 }, + { 0x50, 0x2000 }, + { 0x63, 0x2902 }, + { 0x67, 0x1111 }, + { 0x68, 0x1016 }, + { 0x69, 0x273f }, +}; +#define INDEX_CACHE_SIZE ARRAY_SIZE(rt286_index_def) + +static const struct reg_default rt286_reg[] = { + { 0x00170500, 0x00000400 }, + { 0x00220000, 0x00000031 }, + { 0x00239000, 0x0000007f }, + { 0x0023a000, 0x0000007f }, + { 0x00270500, 0x00000400 }, + { 0x00370500, 0x00000400 }, + { 0x00870500, 0x00000400 }, + { 0x00920000, 0x00000031 }, + { 0x00935000, 0x000000c3 }, + { 0x00936000, 0x000000c3 }, + { 0x00970500, 0x00000400 }, + { 0x00b37000, 0x00000097 }, + { 0x00b37200, 0x00000097 }, + { 0x00b37300, 0x00000097 }, + { 0x00c37000, 0x00000000 }, + { 0x00c37100, 0x00000080 }, + { 0x01270500, 0x00000400 }, + { 0x01370500, 0x00000400 }, + { 0x01371f00, 0x411111f0 }, + { 0x01439000, 0x00000080 }, + { 0x0143a000, 0x00000080 }, + { 0x01470700, 0x00000000 }, + { 0x01470500, 0x00000400 }, + { 0x01470c00, 0x00000000 }, + { 0x01470100, 0x00000000 }, + { 0x01837000, 0x00000000 }, + { 0x01870500, 0x00000400 }, + { 0x02050000, 0x00000000 }, + { 0x02139000, 0x00000080 }, + { 0x0213a000, 0x00000080 }, + { 0x02170100, 0x00000000 }, + { 0x02170500, 0x00000400 }, + { 0x02170700, 0x00000000 }, + { 0x02270100, 0x00000000 }, + { 0x02370100, 0x00000000 }, + { 0x01870700, 0x00000020 }, + { 0x00830000, 0x000000c3 }, + { 0x00930000, 0x000000c3 }, + { 0x01270700, 0x00000000 }, +}; + +static bool rt286_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT286_GET_HP_SENSE: + case RT286_GET_MIC1_SENSE: + case RT286_PROC_COEF: + return true; + default: + return false; + } + + +} + +static bool rt286_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT286_GET_HP_SENSE: + case RT286_GET_MIC1_SENSE: + case RT286_SET_AUDIO_POWER: + case RT286_SET_HPO_POWER: + case RT286_SET_SPK_POWER: + case RT286_SET_DMIC1_POWER: + case RT286_SPK_MUX: + case RT286_HPO_MUX: + case RT286_ADC0_MUX: + case RT286_ADC1_MUX: + case RT286_SET_MIC1: + case RT286_SET_PIN_HPO: + case RT286_SET_PIN_SPK: + case RT286_SET_PIN_DMIC1: + case RT286_SPK_EAPD: + case RT286_SET_AMP_GAIN_HPO: + case RT286_SET_DMIC2_DEFAULT: + case RT286_DACL_GAIN: + case RT286_DACR_GAIN: + case RT286_ADCL_GAIN: + case RT286_ADCR_GAIN: + case RT286_MIC_GAIN: + case RT286_SPOL_GAIN: + case RT286_SPOR_GAIN: + case RT286_HPOL_GAIN: + case RT286_HPOR_GAIN: + case RT286_F_DAC_SWITCH: + case RT286_F_RECMIX_SWITCH: + case RT286_REC_MIC_SWITCH: + case RT286_REC_I2S_SWITCH: + case RT286_REC_LINE_SWITCH: + case RT286_REC_BEEP_SWITCH: + case RT286_DAC_FORMAT: + case RT286_ADC_FORMAT: + case RT286_COEF_INDEX: + case RT286_PROC_COEF: + case RT286_SET_AMP_GAIN_ADC_IN1: + case RT286_SET_AMP_GAIN_ADC_IN2: + case RT286_SET_GPIO_MASK: + case RT286_SET_GPIO_DIRECTION: + case RT286_SET_GPIO_DATA: + case RT286_SET_POWER(RT286_DAC_OUT1): + case RT286_SET_POWER(RT286_DAC_OUT2): + case RT286_SET_POWER(RT286_ADC_IN1): + case RT286_SET_POWER(RT286_ADC_IN2): + case RT286_SET_POWER(RT286_DMIC2): + case RT286_SET_POWER(RT286_MIC1): + return true; + default: + return false; + } +} + +#ifdef CONFIG_PM +static void rt286_index_sync(struct snd_soc_component *component) +{ + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < INDEX_CACHE_SIZE; i++) { + snd_soc_component_write(component, rt286->index_cache[i].reg, + rt286->index_cache[i].def); + } +} +#endif + +static int rt286_support_power_controls[] = { + RT286_DAC_OUT1, + RT286_DAC_OUT2, + RT286_ADC_IN1, + RT286_ADC_IN2, + RT286_MIC1, + RT286_DMIC1, + RT286_DMIC2, + RT286_SPK_OUT, + RT286_HP_OUT, +}; +#define RT286_POWER_REG_LEN ARRAY_SIZE(rt286_support_power_controls) + +static int rt286_jack_detect(struct rt286_priv *rt286, bool *hp, bool *mic) +{ + struct snd_soc_dapm_context *dapm; + unsigned int val, buf; + + *hp = false; + *mic = false; + + if (!rt286->component) + return -EINVAL; + + dapm = snd_soc_component_get_dapm(rt286->component); + + if (rt286->pdata.cbj_en) { + regmap_read(rt286->regmap, RT286_GET_HP_SENSE, &buf); + *hp = buf & 0x80000000; + if (*hp) { + /* power on HV,VERF */ + regmap_update_bits(rt286->regmap, + RT286_DC_GAIN, 0x200, 0x200); + + snd_soc_dapm_force_enable_pin(dapm, "HV"); + snd_soc_dapm_force_enable_pin(dapm, "VREF"); + /* power LDO1 */ + snd_soc_dapm_force_enable_pin(dapm, "LDO1"); + snd_soc_dapm_sync(dapm); + + regmap_write(rt286->regmap, RT286_SET_MIC1, 0x24); + msleep(50); + + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xfcc0, 0xd400); + msleep(300); + regmap_read(rt286->regmap, RT286_CBJ_CTRL2, &val); + + if (0x0070 == (val & 0x0070)) { + *mic = true; + } else { + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xfcc0, 0xe400); + msleep(300); + regmap_read(rt286->regmap, + RT286_CBJ_CTRL2, &val); + if (0x0070 == (val & 0x0070)) + *mic = true; + else + *mic = false; + } + regmap_update_bits(rt286->regmap, + RT286_DC_GAIN, 0x200, 0x0); + + } else { + *mic = false; + regmap_write(rt286->regmap, RT286_SET_MIC1, 0x20); + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0x0400, 0x0000); + } + } else { + regmap_read(rt286->regmap, RT286_GET_HP_SENSE, &buf); + *hp = buf & 0x80000000; + regmap_read(rt286->regmap, RT286_GET_MIC1_SENSE, &buf); + *mic = buf & 0x80000000; + } + if (!*mic) { + snd_soc_dapm_disable_pin(dapm, "HV"); + snd_soc_dapm_disable_pin(dapm, "VREF"); + } + if (!*hp) + snd_soc_dapm_disable_pin(dapm, "LDO1"); + snd_soc_dapm_sync(dapm); + + return 0; +} + +static void rt286_jack_detect_work(struct work_struct *work) +{ + struct rt286_priv *rt286 = + container_of(work, struct rt286_priv, jack_detect_work.work); + int status = 0; + bool hp = false; + bool mic = false; + + rt286_jack_detect(rt286, &hp, &mic); + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt286->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); +} + +int rt286_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + + rt286->jack = jack; + + if (jack) { + /* enable IRQ */ + if (rt286->jack->status & SND_JACK_HEADPHONE) + snd_soc_dapm_force_enable_pin(dapm, "LDO1"); + regmap_update_bits(rt286->regmap, RT286_IRQ_CTRL, 0x2, 0x2); + /* Send an initial empty report */ + snd_soc_jack_report(rt286->jack, rt286->jack->status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + } else { + /* disable IRQ */ + regmap_update_bits(rt286->regmap, RT286_IRQ_CTRL, 0x2, 0x0); + snd_soc_dapm_disable_pin(dapm, "LDO1"); + } + snd_soc_dapm_sync(dapm); + + return 0; +} +EXPORT_SYMBOL_GPL(rt286_mic_detect); + +static int is_mclk_mode(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + + if (rt286->clk_id == RT286_SCLK_S_MCLK) + return 1; + else + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6350, 50, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt286_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC0 Playback Volume", RT286_DACL_GAIN, + RT286_DACR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_DOUBLE_R("ADC0 Capture Switch", RT286_ADCL_GAIN, + RT286_ADCR_GAIN, 7, 1, 1), + SOC_DOUBLE_R_TLV("ADC0 Capture Volume", RT286_ADCL_GAIN, + RT286_ADCR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_SINGLE_TLV("AMIC Volume", RT286_MIC_GAIN, + 0, 0x3, 0, mic_vol_tlv), + SOC_DOUBLE_R("Speaker Playback Switch", RT286_SPOL_GAIN, + RT286_SPOR_GAIN, RT286_MUTE_SFT, 1, 1), +}; + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt286_front_mix[] = { + SOC_DAPM_SINGLE("DAC Switch", RT286_F_DAC_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIX Switch", RT286_F_RECMIX_SWITCH, + RT286_MUTE_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt286_rec_mix[] = { + SOC_DAPM_SINGLE("Mic1 Switch", RT286_REC_MIC_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("I2S Switch", RT286_REC_I2S_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Line1 Switch", RT286_REC_LINE_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Beep Switch", RT286_REC_BEEP_SWITCH, + RT286_MUTE_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new spo_enable_control = + SOC_DAPM_SINGLE("Switch", RT286_SET_PIN_SPK, + RT286_SET_PIN_SFT, 1, 0); + +static const struct snd_kcontrol_new hpol_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOL_GAIN, + RT286_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpor_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOR_GAIN, + RT286_MUTE_SFT, 1, 1); + +/* ADC0 source */ +static const char * const rt286_adc_src[] = { + "Mic", "RECMIX", "Dmic" +}; + +static const int rt286_adc_values[] = { + 0, 4, 5, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc0_enum, RT286_ADC0_MUX, RT286_ADC_SEL_SFT, + RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values); + +static const struct snd_kcontrol_new rt286_adc0_mux = + SOC_DAPM_ENUM("ADC 0 source", rt286_adc0_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc1_enum, RT286_ADC1_MUX, RT286_ADC_SEL_SFT, + RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values); + +static const struct snd_kcontrol_new rt286_adc1_mux = + SOC_DAPM_ENUM("ADC 1 source", rt286_adc1_enum); + +static const char * const rt286_dac_src[] = { + "Front", "Surround" +}; +/* HP-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt286_hpo_enum, RT286_HPO_MUX, + 0, rt286_dac_src); + +static const struct snd_kcontrol_new rt286_hpo_mux = +SOC_DAPM_ENUM("HPO source", rt286_hpo_enum); + +/* SPK-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt286_spo_enum, RT286_SPK_MUX, + 0, rt286_dac_src); + +static const struct snd_kcontrol_new rt286_spo_mux = +SOC_DAPM_ENUM("SPO source", rt286_spo_enum); + +static int rt286_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_write(component, + RT286_SPK_EAPD, RT286_SET_EAPD_HIGH); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_write(component, + RT286_SPK_EAPD, RT286_SET_EAPD_LOW); + break; + + default: + return 0; + } + + return 0; +} + +static int rt286_set_dmic1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_write(component, RT286_SET_PIN_DMIC1, 0x20); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_write(component, RT286_SET_PIN_DMIC1, 0); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_ldo2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT286_POWER_CTRL2, 0x38, 0x08); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT286_POWER_CTRL2, 0x38, 0x30); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_mic1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, + RT286_A_BIAS_CTRL3, 0xc000, 0x8000); + snd_soc_component_update_bits(component, + RT286_A_BIAS_CTRL2, 0xc000, 0x8000); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, + RT286_A_BIAS_CTRL3, 0xc000, 0x0000); + snd_soc_component_update_bits(component, + RT286_A_BIAS_CTRL2, 0xc000, 0x0000); + break; + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt286_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY_S("HV", 1, RT286_POWER_CTRL1, + 12, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF", RT286_POWER_CTRL1, + 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("LDO1", 1, RT286_POWER_CTRL2, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("LDO2", 2, RT286_POWER_CTRL1, + 13, 1, rt286_ldo2_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("MCLK MODE", RT286_PLL_CTRL1, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC1 Input Buffer", SND_SOC_NOPM, + 0, 0, rt286_mic1_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC1 Pin"), + SND_SOC_DAPM_INPUT("DMIC2 Pin"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("Beep"), + + /* DMIC */ + SND_SOC_DAPM_PGA_E("DMIC1", RT286_SET_POWER(RT286_DMIC1), 0, 1, + NULL, 0, rt286_set_dmic1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA("DMIC2", RT286_SET_POWER(RT286_DMIC2), 0, 1, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC Receiver", SND_SOC_NOPM, + 0, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX", SND_SOC_NOPM, 0, 0, + rt286_rec_mix, ARRAY_SIZE(rt286_rec_mix)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("ADC 0 Mux", RT286_SET_POWER(RT286_ADC_IN1), 0, 1, + &rt286_adc0_mux), + SND_SOC_DAPM_MUX("ADC 1 Mux", RT286_SET_POWER(RT286_ADC_IN2), 0, 1, + &rt286_adc1_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("DAC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* Output Mux */ + SND_SOC_DAPM_MUX("SPK Mux", SND_SOC_NOPM, 0, 0, &rt286_spo_mux), + SND_SOC_DAPM_MUX("HPO Mux", SND_SOC_NOPM, 0, 0, &rt286_hpo_mux), + + SND_SOC_DAPM_SUPPLY("HP Power", RT286_SET_PIN_HPO, + RT286_SET_PIN_SFT, 0, NULL, 0), + + /* Output Mixer */ + SND_SOC_DAPM_MIXER("Front", RT286_SET_POWER(RT286_DAC_OUT1), 0, 1, + rt286_front_mix, ARRAY_SIZE(rt286_front_mix)), + SND_SOC_DAPM_PGA("Surround", RT286_SET_POWER(RT286_DAC_OUT2), 0, 1, + NULL, 0), + + /* Output Pga */ + SND_SOC_DAPM_SWITCH_E("SPO", SND_SOC_NOPM, 0, 0, + &spo_enable_control, rt286_spk_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SWITCH("HPO L", SND_SOC_NOPM, 0, 0, + &hpol_enable_control), + SND_SOC_DAPM_SWITCH("HPO R", SND_SOC_NOPM, 0, 0, + &hpor_enable_control), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + SND_SOC_DAPM_OUTPUT("HPO Pin"), + SND_SOC_DAPM_OUTPUT("SPDIF"), +}; + +static const struct snd_soc_dapm_route rt286_dapm_routes[] = { + {"ADC 0", NULL, "MCLK MODE", is_mclk_mode}, + {"ADC 1", NULL, "MCLK MODE", is_mclk_mode}, + {"Front", NULL, "MCLK MODE", is_mclk_mode}, + {"Surround", NULL, "MCLK MODE", is_mclk_mode}, + + {"HP Power", NULL, "LDO1"}, + {"HP Power", NULL, "LDO2"}, + + {"MIC1", NULL, "LDO1"}, + {"MIC1", NULL, "LDO2"}, + {"MIC1", NULL, "HV"}, + {"MIC1", NULL, "VREF"}, + {"MIC1", NULL, "MIC1 Input Buffer"}, + + {"SPO", NULL, "LDO1"}, + {"SPO", NULL, "LDO2"}, + {"SPO", NULL, "HV"}, + {"SPO", NULL, "VREF"}, + + {"DMIC1", NULL, "DMIC1 Pin"}, + {"DMIC2", NULL, "DMIC2 Pin"}, + {"DMIC1", NULL, "DMIC Receiver"}, + {"DMIC2", NULL, "DMIC Receiver"}, + + {"RECMIX", "Beep Switch", "Beep"}, + {"RECMIX", "Line1 Switch", "LINE1"}, + {"RECMIX", "Mic1 Switch", "MIC1"}, + + {"ADC 0 Mux", "Dmic", "DMIC1"}, + {"ADC 0 Mux", "RECMIX", "RECMIX"}, + {"ADC 0 Mux", "Mic", "MIC1"}, + {"ADC 1 Mux", "Dmic", "DMIC2"}, + {"ADC 1 Mux", "RECMIX", "RECMIX"}, + {"ADC 1 Mux", "Mic", "MIC1"}, + + {"ADC 0", NULL, "ADC 0 Mux"}, + {"ADC 1", NULL, "ADC 1 Mux"}, + + {"AIF1TX", NULL, "ADC 0"}, + {"AIF2TX", NULL, "ADC 1"}, + + {"DAC 0", NULL, "AIF1RX"}, + {"DAC 1", NULL, "AIF2RX"}, + + {"Front", "DAC Switch", "DAC 0"}, + {"Front", "RECMIX Switch", "RECMIX"}, + + {"Surround", NULL, "DAC 1"}, + + {"SPK Mux", "Front", "Front"}, + {"SPK Mux", "Surround", "Surround"}, + + {"HPO Mux", "Front", "Front"}, + {"HPO Mux", "Surround", "Surround"}, + + {"SPO", "Switch", "SPK Mux"}, + {"HPO L", "Switch", "HPO Mux"}, + {"HPO R", "Switch", "HPO Mux"}, + {"HPO L", NULL, "HP Power"}, + {"HPO R", NULL, "HP Power"}, + + {"SPOL", NULL, "SPO"}, + {"SPOR", NULL, "SPO"}, + {"HPO Pin", NULL, "HPO L"}, + {"HPO Pin", NULL, "HPO R"}, +}; + +static int rt286_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + int d_len_code; + + switch (params_rate(params)) { + /* bit 14 0:48K 1:44.1K */ + case 44100: + val |= 0x4000; + break; + case 48000: + break; + default: + dev_err(component->dev, "Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + switch (rt286->sys_clk) { + case 12288000: + case 24576000: + if (params_rate(params) != 48000) { + dev_err(component->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt286->sys_clk); + return -EINVAL; + } + break; + case 11289600: + case 22579200: + if (params_rate(params) != 44100) { + dev_err(component->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt286->sys_clk); + return -EINVAL; + } + break; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + d_len_code = 0; + switch (params_width(params)) { + /* bit 6:4 Bits per Sample */ + case 16: + d_len_code = 0; + val |= (0x1 << 4); + break; + case 32: + d_len_code = 2; + val |= (0x4 << 4); + break; + case 20: + d_len_code = 1; + val |= (0x2 << 4); + break; + case 24: + d_len_code = 2; + val |= (0x3 << 4); + break; + case 8: + d_len_code = 3; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x0018, d_len_code << 3); + dev_dbg(component->dev, "format val = 0x%x\n", val); + + snd_soc_component_update_bits(component, RT286_DAC_FORMAT, 0x407f, val); + snd_soc_component_update_bits(component, RT286_ADC_FORMAT, 0x407f, val); + + return 0; +} + +static int rt286_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x800, 0x800); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x800, 0x0); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x300, 0x0); + break; + case SND_SOC_DAIFMT_LEFT_J: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x300, 0x1 << 8); + break; + case SND_SOC_DAIFMT_DSP_A: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x300, 0x2 << 8); + break; + case SND_SOC_DAIFMT_DSP_B: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x300, 0x3 << 8); + break; + default: + return -EINVAL; + } + /* bit 15 Stream Type 0:PCM 1:Non-PCM */ + snd_soc_component_update_bits(component, RT286_DAC_FORMAT, 0x8000, 0); + snd_soc_component_update_bits(component, RT286_ADC_FORMAT, 0x8000, 0); + + return 0; +} + +static int rt286_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s freq=%d\n", __func__, freq); + + if (RT286_SCLK_S_MCLK == clk_id) { + snd_soc_component_update_bits(component, + RT286_I2S_CTRL2, 0x0100, 0x0); + snd_soc_component_update_bits(component, + RT286_PLL_CTRL1, 0x20, 0x20); + } else { + snd_soc_component_update_bits(component, + RT286_I2S_CTRL2, 0x0100, 0x0100); + snd_soc_component_update_bits(component, + RT286_PLL_CTRL, 0x4, 0x4); + snd_soc_component_update_bits(component, + RT286_PLL_CTRL1, 0x20, 0x0); + } + + switch (freq) { + case 19200000: + if (RT286_SCLK_S_MCLK == clk_id) { + dev_err(component->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_component_update_bits(component, + RT286_I2S_CTRL2, 0x40, 0x40); + break; + case 24000000: + if (RT286_SCLK_S_MCLK == clk_id) { + dev_err(component->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_component_update_bits(component, + RT286_I2S_CTRL2, 0x40, 0x0); + break; + case 12288000: + case 11289600: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL2, 0x8, 0x0); + snd_soc_component_update_bits(component, + RT286_CLK_DIV, 0xfc1e, 0x0004); + break; + case 24576000: + case 22579200: + snd_soc_component_update_bits(component, + RT286_I2S_CTRL2, 0x8, 0x8); + snd_soc_component_update_bits(component, + RT286_CLK_DIV, 0xfc1e, 0x5406); + break; + default: + dev_err(component->dev, "Unsupported system clock\n"); + return -EINVAL; + } + + rt286->sys_clk = freq; + rt286->clk_id = clk_id; + + return 0; +} + +static int rt286_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + + dev_dbg(component->dev, "%s ratio=%d\n", __func__, ratio); + if (50 == ratio) + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x1000, 0x1000); + else + snd_soc_component_update_bits(component, + RT286_I2S_CTRL1, 0x1000, 0x0); + + + return 0; +} + +static int rt286_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == snd_soc_component_get_bias_level(component)) { + snd_soc_component_write(component, + RT286_SET_AUDIO_POWER, AC_PWRST_D0); + snd_soc_component_update_bits(component, + RT286_DC_GAIN, 0x200, 0x200); + } + break; + + case SND_SOC_BIAS_ON: + mdelay(10); + snd_soc_component_update_bits(component, + RT286_DC_GAIN, 0x200, 0x0); + + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_component_write(component, + RT286_SET_AUDIO_POWER, AC_PWRST_D3); + break; + + default: + break; + } + + return 0; +} + +static irqreturn_t rt286_irq(int irq, void *data) +{ + struct rt286_priv *rt286 = data; + bool hp = false; + bool mic = false; + int status = 0; + + rt286_jack_detect(rt286, &hp, &mic); + + /* Clear IRQ */ + regmap_update_bits(rt286->regmap, RT286_IRQ_CTRL, 0x1, 0x1); + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt286->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + pm_wakeup_event(&rt286->i2c->dev, 300); + + return IRQ_HANDLED; +} + +static int rt286_probe(struct snd_soc_component *component) +{ + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + + rt286->component = component; + + if (rt286->i2c->irq) { + regmap_update_bits(rt286->regmap, + RT286_IRQ_CTRL, 0x2, 0x2); + + INIT_DELAYED_WORK(&rt286->jack_detect_work, + rt286_jack_detect_work); + schedule_delayed_work(&rt286->jack_detect_work, + msecs_to_jiffies(1250)); + } + + return 0; +} + +static void rt286_remove(struct snd_soc_component *component) +{ + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&rt286->jack_detect_work); +} + +#ifdef CONFIG_PM +static int rt286_suspend(struct snd_soc_component *component) +{ + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt286->regmap, true); + regcache_mark_dirty(rt286->regmap); + + return 0; +} + +static int rt286_resume(struct snd_soc_component *component) +{ + struct rt286_priv *rt286 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt286->regmap, false); + rt286_index_sync(component); + regcache_sync(rt286->regmap); + + return 0; +} +#else +#define rt286_suspend NULL +#define rt286_resume NULL +#endif + +#define RT286_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT286_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt286_aif_dai_ops = { + .hw_params = rt286_hw_params, + .set_fmt = rt286_set_dai_fmt, + .set_sysclk = rt286_set_dai_sysclk, + .set_bclk_ratio = rt286_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt286_dai[] = { + { + .name = "rt286-aif1", + .id = RT286_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .ops = &rt286_aif_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "rt286-aif2", + .id = RT286_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .ops = &rt286_aif_dai_ops, + .symmetric_rates = 1, + }, + +}; + +static const struct snd_soc_component_driver soc_component_dev_rt286 = { + .probe = rt286_probe, + .remove = rt286_remove, + .suspend = rt286_suspend, + .resume = rt286_resume, + .set_bias_level = rt286_set_bias_level, + .controls = rt286_snd_controls, + .num_controls = ARRAY_SIZE(rt286_snd_controls), + .dapm_widgets = rt286_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt286_dapm_widgets), + .dapm_routes = rt286_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt286_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt286_regmap = { + .reg_bits = 32, + .val_bits = 32, + .max_register = 0x02370100, + .volatile_reg = rt286_volatile_register, + .readable_reg = rt286_readable_register, + .reg_write = rl6347a_hw_write, + .reg_read = rl6347a_hw_read, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt286_reg, + .num_reg_defaults = ARRAY_SIZE(rt286_reg), +}; + +static const struct i2c_device_id rt286_i2c_id[] = { + {"rt286", 0}, + {"rt288", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt286_i2c_id); + +static const struct acpi_device_id rt286_acpi_match[] = { + { "INT343A", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt286_acpi_match); + +static const struct dmi_system_id force_combo_jack_table[] = { + { + .ident = "Intel Wilson Beach", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "Wilson Beach SDS") + } + }, + { + .ident = "Intel Skylake RVP", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Skylake Client platform") + } + }, + { + .ident = "Intel Kabylake RVP", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Kabylake Client platform") + } + }, + { + .ident = "Thinkpad Helix 2nd", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Helix 2nd") + } + }, + + { } +}; + +static const struct dmi_system_id dmi_dell[] = { + { + .ident = "Dell", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + } + }, + { } +}; + +static int rt286_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt286_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt286_priv *rt286; + int i, ret, vendor_id; + + rt286 = devm_kzalloc(&i2c->dev, sizeof(*rt286), + GFP_KERNEL); + if (NULL == rt286) + return -ENOMEM; + + rt286->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt286_regmap); + if (IS_ERR(rt286->regmap)) { + ret = PTR_ERR(rt286->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = regmap_read(rt286->regmap, + RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &vendor_id); + if (ret != 0) { + dev_err(&i2c->dev, "I2C error %d\n", ret); + return ret; + } + if (vendor_id != RT286_VENDOR_ID && vendor_id != RT288_VENDOR_ID) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt286\n", + vendor_id); + return -ENODEV; + } + + rt286->index_cache = devm_kmemdup(&i2c->dev, rt286_index_def, + sizeof(rt286_index_def), GFP_KERNEL); + if (!rt286->index_cache) + return -ENOMEM; + + rt286->index_cache_size = INDEX_CACHE_SIZE; + rt286->i2c = i2c; + i2c_set_clientdata(i2c, rt286); + + /* restore codec default */ + for (i = 0; i < INDEX_CACHE_SIZE; i++) + regmap_write(rt286->regmap, rt286->index_cache[i].reg, + rt286->index_cache[i].def); + for (i = 0; i < ARRAY_SIZE(rt286_reg); i++) + regmap_write(rt286->regmap, rt286_reg[i].reg, + rt286_reg[i].def); + + if (pdata) + rt286->pdata = *pdata; + + if ((vendor_id == RT288_VENDOR_ID && dmi_check_system(dmi_dell)) || + dmi_check_system(force_combo_jack_table)) + rt286->pdata.cbj_en = true; + + regmap_write(rt286->regmap, RT286_SET_AUDIO_POWER, AC_PWRST_D3); + + for (i = 0; i < RT286_POWER_REG_LEN; i++) + regmap_write(rt286->regmap, + RT286_SET_POWER(rt286_support_power_controls[i]), + AC_PWRST_D1); + + if (!rt286->pdata.cbj_en) { + regmap_write(rt286->regmap, RT286_CBJ_CTRL2, 0x0000); + regmap_write(rt286->regmap, RT286_MIC1_DET_CTRL, 0x0816); + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xf000, 0xb000); + } else { + regmap_update_bits(rt286->regmap, + RT286_CBJ_CTRL1, 0xf000, 0x5000); + } + + mdelay(10); + + if (!rt286->pdata.gpio2_en) + regmap_write(rt286->regmap, RT286_SET_DMIC2_DEFAULT, 0x4000); + else + regmap_write(rt286->regmap, RT286_SET_DMIC2_DEFAULT, 0); + + mdelay(10); + + regmap_write(rt286->regmap, RT286_MISC_CTRL1, 0x0000); + /* Power down LDO, VREF */ + regmap_update_bits(rt286->regmap, RT286_POWER_CTRL2, 0xc, 0x0); + regmap_update_bits(rt286->regmap, RT286_POWER_CTRL1, 0x1001, 0x1001); + + /* Set depop parameter */ + regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL2, 0x403a, 0x401a); + regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL3, 0xf777, 0x4737); + regmap_update_bits(rt286->regmap, RT286_DEPOP_CTRL4, 0x00ff, 0x003f); + + if (vendor_id == RT288_VENDOR_ID && dmi_check_system(dmi_dell)) { + regmap_update_bits(rt286->regmap, + RT286_SET_GPIO_MASK, 0x40, 0x40); + regmap_update_bits(rt286->regmap, + RT286_SET_GPIO_DIRECTION, 0x40, 0x40); + regmap_update_bits(rt286->regmap, + RT286_SET_GPIO_DATA, 0x40, 0x40); + regmap_update_bits(rt286->regmap, + RT286_GPIO_CTRL, 0xc, 0x8); + } + + if (rt286->i2c->irq) { + ret = request_threaded_irq(rt286->i2c->irq, NULL, rt286_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "rt286", rt286); + if (ret != 0) { + dev_err(&i2c->dev, + "Failed to reguest IRQ: %d\n", ret); + return ret; + } + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt286, + rt286_dai, ARRAY_SIZE(rt286_dai)); + + return ret; +} + +static int rt286_i2c_remove(struct i2c_client *i2c) +{ + struct rt286_priv *rt286 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, rt286); + + return 0; +} + + +static struct i2c_driver rt286_i2c_driver = { + .driver = { + .name = "rt286", + .acpi_match_table = ACPI_PTR(rt286_acpi_match), + }, + .probe = rt286_i2c_probe, + .remove = rt286_i2c_remove, + .id_table = rt286_i2c_id, +}; + +module_i2c_driver(rt286_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT286 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt286.h b/sound/soc/codecs/rt286.h new file mode 100644 index 000000000..c63d0e79b --- /dev/null +++ b/sound/soc/codecs/rt286.h @@ -0,0 +1,205 @@ +/* + * rt286.h -- RT286 ALSA SoC audio driver + * + * Copyright 2011 Realtek Microelectronics + * Author: Johnny Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT286_H__ +#define __RT286_H__ + +#define VERB_CMD(V, N, D) ((N << 20) | (V << 8) | D) + +#define RT286_AUDIO_FUNCTION_GROUP 0x01 +#define RT286_DAC_OUT1 0x02 +#define RT286_DAC_OUT2 0x03 +#define RT286_ADC_IN1 0x09 +#define RT286_ADC_IN2 0x08 +#define RT286_MIXER_IN 0x0b +#define RT286_MIXER_OUT1 0x0c +#define RT286_MIXER_OUT2 0x0d +#define RT286_DMIC1 0x12 +#define RT286_DMIC2 0x13 +#define RT286_SPK_OUT 0x14 +#define RT286_MIC1 0x18 +#define RT286_LINE1 0x1a +#define RT286_BEEP 0x1d +#define RT286_SPDIF 0x1e +#define RT286_VENDOR_REGISTERS 0x20 +#define RT286_HP_OUT 0x21 +#define RT286_MIXER_IN1 0x22 +#define RT286_MIXER_IN2 0x23 + +#define RT286_SET_PIN_SFT 6 +#define RT286_SET_PIN_ENABLE 0x40 +#define RT286_SET_PIN_DISABLE 0 +#define RT286_SET_EAPD_HIGH 0x2 +#define RT286_SET_EAPD_LOW 0 + +#define RT286_MUTE_SFT 7 + +/* Verb commands */ +#define RT286_GET_PARAM(NID, PARAM) VERB_CMD(AC_VERB_PARAMETERS, NID, PARAM) +#define RT286_SET_POWER(NID) VERB_CMD(AC_VERB_SET_POWER_STATE, NID, 0) +#define RT286_SET_AUDIO_POWER RT286_SET_POWER(RT286_AUDIO_FUNCTION_GROUP) +#define RT286_SET_HPO_POWER RT286_SET_POWER(RT286_HP_OUT) +#define RT286_SET_SPK_POWER RT286_SET_POWER(RT286_SPK_OUT) +#define RT286_SET_DMIC1_POWER RT286_SET_POWER(RT286_DMIC1) +#define RT286_SPK_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_SPK_OUT, 0) +#define RT286_HPO_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_HP_OUT, 0) +#define RT286_ADC0_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN1, 0) +#define RT286_ADC1_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN2, 0) +#define RT286_SET_MIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_MIC1, 0) +#define RT286_SET_PIN_HPO\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_HP_OUT, 0) +#define RT286_SET_PIN_SPK\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_SPK_OUT, 0) +#define RT286_SET_PIN_DMIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_DMIC1, 0) +#define RT286_SPK_EAPD\ + VERB_CMD(AC_VERB_SET_EAPD_BTLENABLE, RT286_SPK_OUT, 0) +#define RT286_SET_AMP_GAIN_HPO\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0) +#define RT286_SET_AMP_GAIN_ADC_IN1\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0) +#define RT286_SET_AMP_GAIN_ADC_IN2\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN2, 0) +#define RT286_GET_HP_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_HP_OUT, 0) +#define RT286_GET_MIC1_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_MIC1, 0) +#define RT286_SET_DMIC2_DEFAULT\ + VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT286_DMIC2, 0) +#define RT286_DACL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0xa000) +#define RT286_DACR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0x9000) +#define RT286_ADCL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x6000) +#define RT286_ADCR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x5000) +#define RT286_MIC_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIC1, 0x7000) +#define RT286_SPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0xa000) +#define RT286_SPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0x9000) +#define RT286_HPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0xa000) +#define RT286_HPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0x9000) +#define RT286_F_DAC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7000) +#define RT286_F_RECMIX_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7100) +#define RT286_REC_MIC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7000) +#define RT286_REC_I2S_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7100) +#define RT286_REC_LINE_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7200) +#define RT286_REC_BEEP_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7300) +#define RT286_DAC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_DAC_OUT1, 0) +#define RT286_ADC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_ADC_IN1, 0) +#define RT286_COEF_INDEX\ + VERB_CMD(AC_VERB_SET_COEF_INDEX, RT286_VENDOR_REGISTERS, 0) +#define RT286_PROC_COEF\ + VERB_CMD(AC_VERB_SET_PROC_COEF, RT286_VENDOR_REGISTERS, 0) +#define RT286_SET_GPIO_MASK\ + VERB_CMD(AC_VERB_SET_GPIO_MASK, RT286_AUDIO_FUNCTION_GROUP, 0) +#define RT286_SET_GPIO_DIRECTION\ + VERB_CMD(AC_VERB_SET_GPIO_DIRECTION, RT286_AUDIO_FUNCTION_GROUP, 0) +#define RT286_SET_GPIO_DATA\ + VERB_CMD(AC_VERB_SET_GPIO_DATA, RT286_AUDIO_FUNCTION_GROUP, 0) + +/* Index registers */ +#define RT286_A_BIAS_CTRL1 0x01 +#define RT286_A_BIAS_CTRL2 0x02 +#define RT286_POWER_CTRL1 0x03 +#define RT286_A_BIAS_CTRL3 0x04 +#define RT286_POWER_CTRL2 0x08 +#define RT286_I2S_CTRL1 0x09 +#define RT286_I2S_CTRL2 0x0a +#define RT286_CLK_DIV 0x0b +#define RT286_DC_GAIN 0x0d +#define RT286_POWER_CTRL3 0x0f +#define RT286_MIC1_DET_CTRL 0x19 +#define RT286_MISC_CTRL1 0x20 +#define RT286_GPIO_CTRL 0x29 +#define RT286_IRQ_CTRL 0x33 +#define RT286_PLL_CTRL1 0x49 +#define RT286_CBJ_CTRL1 0x4f +#define RT286_CBJ_CTRL2 0x50 +#define RT286_PLL_CTRL 0x63 +#define RT286_DEPOP_CTRL1 0x66 +#define RT286_DEPOP_CTRL2 0x67 +#define RT286_DEPOP_CTRL3 0x68 +#define RT286_DEPOP_CTRL4 0x69 + +/* SPDIF (0x06) */ +#define RT286_SPDIF_SEL_SFT 0 +#define RT286_SPDIF_SEL_PCM0 0 +#define RT286_SPDIF_SEL_PCM1 1 +#define RT286_SPDIF_SEL_SPOUT 2 +#define RT286_SPDIF_SEL_PP 3 + +/* RECMIX (0x0b) */ +#define RT286_M_REC_BEEP_SFT 0 +#define RT286_M_REC_LINE1_SFT 1 +#define RT286_M_REC_MIC1_SFT 2 +#define RT286_M_REC_I2S_SFT 3 + +/* Front (0x0c) */ +#define RT286_M_FRONT_DAC_SFT 0 +#define RT286_M_FRONT_REC_SFT 1 + +/* SPK-OUT (0x14) */ +#define RT286_M_SPK_MUX_SFT 14 +#define RT286_SPK_SEL_MASK 0x1 +#define RT286_SPK_SEL_SFT 0 +#define RT286_SPK_SEL_F 0 +#define RT286_SPK_SEL_S 1 + +/* HP-OUT (0x21) */ +#define RT286_M_HP_MUX_SFT 14 +#define RT286_HP_SEL_MASK 0x1 +#define RT286_HP_SEL_SFT 0 +#define RT286_HP_SEL_F 0 +#define RT286_HP_SEL_S 1 + +/* ADC (0x22) (0x23) */ +#define RT286_ADC_SEL_MASK 0x7 +#define RT286_ADC_SEL_SFT 0 +#define RT286_ADC_SEL_SURR 0 +#define RT286_ADC_SEL_FRONT 1 +#define RT286_ADC_SEL_DMIC 2 +#define RT286_ADC_SEL_BEEP 4 +#define RT286_ADC_SEL_LINE1 5 +#define RT286_ADC_SEL_I2S 6 +#define RT286_ADC_SEL_MIC1 7 + +#define RT286_SCLK_S_MCLK 0 +#define RT286_SCLK_S_PLL 1 + +enum { + RT286_AIF1, + RT286_AIF2, + RT286_AIFS, +}; + +int rt286_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack); + +#endif /* __RT286_H__ */ + diff --git a/sound/soc/codecs/rt298.c b/sound/soc/codecs/rt298.c new file mode 100644 index 000000000..06cdba4ed --- /dev/null +++ b/sound/soc/codecs/rt298.c @@ -0,0 +1,1322 @@ +/* + * rt298.c -- RT298 ALSA SoC audio codec driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6347a.h" +#include "rt298.h" + +#define RT298_VENDOR_ID 0x10ec0298 + +struct rt298_priv { + struct reg_default *index_cache; + int index_cache_size; + struct regmap *regmap; + struct snd_soc_component *component; + struct rt298_platform_data pdata; + struct i2c_client *i2c; + struct snd_soc_jack *jack; + struct delayed_work jack_detect_work; + int sys_clk; + int clk_id; + int is_hp_in; +}; + +static const struct reg_default rt298_index_def[] = { + { 0x01, 0xa5a8 }, + { 0x02, 0x8e95 }, + { 0x03, 0x0002 }, + { 0x04, 0xaf67 }, + { 0x08, 0x200f }, + { 0x09, 0xd010 }, + { 0x0a, 0x0100 }, + { 0x0b, 0x0000 }, + { 0x0d, 0x2800 }, + { 0x0f, 0x0022 }, + { 0x19, 0x0217 }, + { 0x20, 0x0020 }, + { 0x33, 0x0208 }, + { 0x46, 0x0300 }, + { 0x49, 0x4004 }, + { 0x4f, 0x50c9 }, + { 0x50, 0x3000 }, + { 0x63, 0x1b02 }, + { 0x67, 0x1111 }, + { 0x68, 0x1016 }, + { 0x69, 0x273f }, +}; +#define INDEX_CACHE_SIZE ARRAY_SIZE(rt298_index_def) + +static const struct reg_default rt298_reg[] = { + { 0x00170500, 0x00000400 }, + { 0x00220000, 0x00000031 }, + { 0x00239000, 0x0000007f }, + { 0x0023a000, 0x0000007f }, + { 0x00270500, 0x00000400 }, + { 0x00370500, 0x00000400 }, + { 0x00870500, 0x00000400 }, + { 0x00920000, 0x00000031 }, + { 0x00935000, 0x000000c3 }, + { 0x00936000, 0x000000c3 }, + { 0x00970500, 0x00000400 }, + { 0x00b37000, 0x00000097 }, + { 0x00b37200, 0x00000097 }, + { 0x00b37300, 0x00000097 }, + { 0x00c37000, 0x00000000 }, + { 0x00c37100, 0x00000080 }, + { 0x01270500, 0x00000400 }, + { 0x01370500, 0x00000400 }, + { 0x01371f00, 0x411111f0 }, + { 0x01439000, 0x00000080 }, + { 0x0143a000, 0x00000080 }, + { 0x01470700, 0x00000000 }, + { 0x01470500, 0x00000400 }, + { 0x01470c00, 0x00000000 }, + { 0x01470100, 0x00000000 }, + { 0x01837000, 0x00000000 }, + { 0x01870500, 0x00000400 }, + { 0x02050000, 0x00000000 }, + { 0x02139000, 0x00000080 }, + { 0x0213a000, 0x00000080 }, + { 0x02170100, 0x00000000 }, + { 0x02170500, 0x00000400 }, + { 0x02170700, 0x00000000 }, + { 0x02270100, 0x00000000 }, + { 0x02370100, 0x00000000 }, + { 0x01870700, 0x00000020 }, + { 0x00830000, 0x000000c3 }, + { 0x00930000, 0x000000c3 }, + { 0x01270700, 0x00000000 }, +}; + +static bool rt298_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT298_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT298_GET_HP_SENSE: + case RT298_GET_MIC1_SENSE: + case RT298_PROC_COEF: + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_MIC1, 0): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_SPK_OUT, 0): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_HP_OUT, 0): + return true; + default: + return false; + } + + +} + +static bool rt298_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... 0xff: + case RT298_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID): + case RT298_GET_HP_SENSE: + case RT298_GET_MIC1_SENSE: + case RT298_SET_AUDIO_POWER: + case RT298_SET_HPO_POWER: + case RT298_SET_SPK_POWER: + case RT298_SET_DMIC1_POWER: + case RT298_SPK_MUX: + case RT298_HPO_MUX: + case RT298_ADC0_MUX: + case RT298_ADC1_MUX: + case RT298_SET_MIC1: + case RT298_SET_PIN_HPO: + case RT298_SET_PIN_SPK: + case RT298_SET_PIN_DMIC1: + case RT298_SPK_EAPD: + case RT298_SET_AMP_GAIN_HPO: + case RT298_SET_DMIC2_DEFAULT: + case RT298_DACL_GAIN: + case RT298_DACR_GAIN: + case RT298_ADCL_GAIN: + case RT298_ADCR_GAIN: + case RT298_MIC_GAIN: + case RT298_SPOL_GAIN: + case RT298_SPOR_GAIN: + case RT298_HPOL_GAIN: + case RT298_HPOR_GAIN: + case RT298_F_DAC_SWITCH: + case RT298_F_RECMIX_SWITCH: + case RT298_REC_MIC_SWITCH: + case RT298_REC_I2S_SWITCH: + case RT298_REC_LINE_SWITCH: + case RT298_REC_BEEP_SWITCH: + case RT298_DAC_FORMAT: + case RT298_ADC_FORMAT: + case RT298_COEF_INDEX: + case RT298_PROC_COEF: + case RT298_SET_AMP_GAIN_ADC_IN1: + case RT298_SET_AMP_GAIN_ADC_IN2: + case RT298_SET_POWER(RT298_DAC_OUT1): + case RT298_SET_POWER(RT298_DAC_OUT2): + case RT298_SET_POWER(RT298_ADC_IN1): + case RT298_SET_POWER(RT298_ADC_IN2): + case RT298_SET_POWER(RT298_DMIC2): + case RT298_SET_POWER(RT298_MIC1): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_MIC1, 0): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_SPK_OUT, 0): + case VERB_CMD(AC_VERB_GET_EAPD_BTLENABLE, RT298_HP_OUT, 0): + return true; + default: + return false; + } +} + +#ifdef CONFIG_PM +static void rt298_index_sync(struct snd_soc_component *component) +{ + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < INDEX_CACHE_SIZE; i++) { + snd_soc_component_write(component, rt298->index_cache[i].reg, + rt298->index_cache[i].def); + } +} +#endif + +static int rt298_support_power_controls[] = { + RT298_DAC_OUT1, + RT298_DAC_OUT2, + RT298_ADC_IN1, + RT298_ADC_IN2, + RT298_MIC1, + RT298_DMIC1, + RT298_DMIC2, + RT298_SPK_OUT, + RT298_HP_OUT, +}; +#define RT298_POWER_REG_LEN ARRAY_SIZE(rt298_support_power_controls) + +static int rt298_jack_detect(struct rt298_priv *rt298, bool *hp, bool *mic) +{ + struct snd_soc_dapm_context *dapm; + unsigned int val, buf; + + *hp = false; + *mic = false; + + if (!rt298->component) + return -EINVAL; + + dapm = snd_soc_component_get_dapm(rt298->component); + + if (rt298->pdata.cbj_en) { + regmap_read(rt298->regmap, RT298_GET_HP_SENSE, &buf); + *hp = buf & 0x80000000; + if (*hp == rt298->is_hp_in) + return -1; + rt298->is_hp_in = *hp; + if (*hp) { + /* power on HV,VERF */ + regmap_update_bits(rt298->regmap, + RT298_DC_GAIN, 0x200, 0x200); + + snd_soc_dapm_force_enable_pin(dapm, "HV"); + snd_soc_dapm_force_enable_pin(dapm, "VREF"); + /* power LDO1 */ + snd_soc_dapm_force_enable_pin(dapm, "LDO1"); + snd_soc_dapm_sync(dapm); + + regmap_update_bits(rt298->regmap, + RT298_POWER_CTRL1, 0x1001, 0); + regmap_update_bits(rt298->regmap, + RT298_POWER_CTRL2, 0x4, 0x4); + + regmap_write(rt298->regmap, RT298_SET_MIC1, 0x24); + msleep(50); + + regmap_update_bits(rt298->regmap, + RT298_CBJ_CTRL1, 0xfcc0, 0xd400); + msleep(300); + regmap_read(rt298->regmap, RT298_CBJ_CTRL2, &val); + + if (0x0070 == (val & 0x0070)) { + *mic = true; + } else { + regmap_update_bits(rt298->regmap, + RT298_CBJ_CTRL1, 0xfcc0, 0xe400); + msleep(300); + regmap_read(rt298->regmap, + RT298_CBJ_CTRL2, &val); + if (0x0070 == (val & 0x0070)) + *mic = true; + else + *mic = false; + } + regmap_update_bits(rt298->regmap, + RT298_DC_GAIN, 0x200, 0x0); + + } else { + *mic = false; + regmap_write(rt298->regmap, RT298_SET_MIC1, 0x20); + regmap_update_bits(rt298->regmap, + RT298_CBJ_CTRL1, 0x0400, 0x0000); + } + } else { + regmap_read(rt298->regmap, RT298_GET_HP_SENSE, &buf); + *hp = buf & 0x80000000; + regmap_read(rt298->regmap, RT298_GET_MIC1_SENSE, &buf); + *mic = buf & 0x80000000; + } + if (!*mic) { + snd_soc_dapm_disable_pin(dapm, "HV"); + snd_soc_dapm_disable_pin(dapm, "VREF"); + } + if (!*hp) + snd_soc_dapm_disable_pin(dapm, "LDO1"); + snd_soc_dapm_sync(dapm); + + pr_debug("*hp = %d *mic = %d\n", *hp, *mic); + + return 0; +} + +static void rt298_jack_detect_work(struct work_struct *work) +{ + struct rt298_priv *rt298 = + container_of(work, struct rt298_priv, jack_detect_work.work); + int status = 0; + bool hp = false; + bool mic = false; + + if (rt298_jack_detect(rt298, &hp, &mic) < 0) + return; + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt298->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); +} + +int rt298_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack) +{ + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm; + bool hp = false; + bool mic = false; + int status = 0; + + /* If jack in NULL, disable HS jack */ + if (!jack) { + regmap_update_bits(rt298->regmap, RT298_IRQ_CTRL, 0x2, 0x0); + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_disable_pin(dapm, "LDO1"); + snd_soc_dapm_sync(dapm); + return 0; + } + + rt298->jack = jack; + regmap_update_bits(rt298->regmap, RT298_IRQ_CTRL, 0x2, 0x2); + + rt298_jack_detect(rt298, &hp, &mic); + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt298->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + return 0; +} +EXPORT_SYMBOL_GPL(rt298_mic_detect); + +static int is_mclk_mode(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + + if (rt298->clk_id == RT298_SCLK_S_MCLK) + return 1; + else + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6350, 50, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt298_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC0 Playback Volume", RT298_DACL_GAIN, + RT298_DACR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_DOUBLE_R_TLV("ADC0 Capture Volume", RT298_ADCL_GAIN, + RT298_ADCR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_SINGLE_TLV("AMIC Volume", RT298_MIC_GAIN, + 0, 0x3, 0, mic_vol_tlv), + SOC_DOUBLE_R("Speaker Playback Switch", RT298_SPOL_GAIN, + RT298_SPOR_GAIN, RT298_MUTE_SFT, 1, 1), +}; + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt298_front_mix[] = { + SOC_DAPM_SINGLE("DAC Switch", RT298_F_DAC_SWITCH, + RT298_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIX Switch", RT298_F_RECMIX_SWITCH, + RT298_MUTE_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt298_rec_mix[] = { + SOC_DAPM_SINGLE("Mic1 Switch", RT298_REC_MIC_SWITCH, + RT298_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("I2S Switch", RT298_REC_I2S_SWITCH, + RT298_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Line1 Switch", RT298_REC_LINE_SWITCH, + RT298_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Beep Switch", RT298_REC_BEEP_SWITCH, + RT298_MUTE_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new spo_enable_control = + SOC_DAPM_SINGLE("Switch", RT298_SET_PIN_SPK, + RT298_SET_PIN_SFT, 1, 0); + +static const struct snd_kcontrol_new hpol_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT298_HPOL_GAIN, + RT298_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpor_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT298_HPOR_GAIN, + RT298_MUTE_SFT, 1, 1); + +/* ADC0 source */ +static const char * const rt298_adc_src[] = { + "Mic", "RECMIX", "Dmic" +}; + +static const int rt298_adc_values[] = { + 0, 4, 5, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt298_adc0_enum, RT298_ADC0_MUX, RT298_ADC_SEL_SFT, + RT298_ADC_SEL_MASK, rt298_adc_src, rt298_adc_values); + +static const struct snd_kcontrol_new rt298_adc0_mux = + SOC_DAPM_ENUM("ADC 0 source", rt298_adc0_enum); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt298_adc1_enum, RT298_ADC1_MUX, RT298_ADC_SEL_SFT, + RT298_ADC_SEL_MASK, rt298_adc_src, rt298_adc_values); + +static const struct snd_kcontrol_new rt298_adc1_mux = + SOC_DAPM_ENUM("ADC 1 source", rt298_adc1_enum); + +static const char * const rt298_dac_src[] = { + "Front", "Surround" +}; +/* HP-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt298_hpo_enum, RT298_HPO_MUX, + 0, rt298_dac_src); + +static const struct snd_kcontrol_new rt298_hpo_mux = +SOC_DAPM_ENUM("HPO source", rt298_hpo_enum); + +/* SPK-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt298_spo_enum, RT298_SPK_MUX, + 0, rt298_dac_src); + +static const struct snd_kcontrol_new rt298_spo_mux = +SOC_DAPM_ENUM("SPO source", rt298_spo_enum); + +static int rt298_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_write(component, + RT298_SPK_EAPD, RT298_SET_EAPD_HIGH); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_write(component, + RT298_SPK_EAPD, RT298_SET_EAPD_LOW); + break; + + default: + return 0; + } + + return 0; +} + +static int rt298_set_dmic1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_write(component, RT298_SET_PIN_DMIC1, 0x20); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_write(component, RT298_SET_PIN_DMIC1, 0); + break; + default: + return 0; + } + + return 0; +} + +static int rt298_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int nid; + + nid = (w->reg >> 20) & 0xff; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), + 0x7080, 0x7000); + /* If MCLK doesn't exist, reset AD filter */ + if (!(snd_soc_component_read32(component, RT298_VAD_CTRL) & 0x200)) { + pr_info("NO MCLK\n"); + switch (nid) { + case RT298_ADC_IN1: + snd_soc_component_update_bits(component, + RT298_D_FILTER_CTRL, 0x2, 0x2); + mdelay(10); + snd_soc_component_update_bits(component, + RT298_D_FILTER_CTRL, 0x2, 0x0); + break; + case RT298_ADC_IN2: + snd_soc_component_update_bits(component, + RT298_D_FILTER_CTRL, 0x4, 0x4); + mdelay(10); + snd_soc_component_update_bits(component, + RT298_D_FILTER_CTRL, 0x4, 0x0); + break; + } + } + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), + 0x7080, 0x7080); + break; + default: + return 0; + } + + return 0; +} + +static int rt298_mic1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, + RT298_A_BIAS_CTRL3, 0xc000, 0x8000); + snd_soc_component_update_bits(component, + RT298_A_BIAS_CTRL2, 0xc000, 0x8000); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, + RT298_A_BIAS_CTRL3, 0xc000, 0x0000); + snd_soc_component_update_bits(component, + RT298_A_BIAS_CTRL2, 0xc000, 0x0000); + break; + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt298_dapm_widgets[] = { + + SND_SOC_DAPM_SUPPLY_S("HV", 1, RT298_POWER_CTRL1, + 12, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF", RT298_POWER_CTRL1, + 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("BG_MBIAS", 1, RT298_POWER_CTRL2, + 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("LDO1", 1, RT298_POWER_CTRL2, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("LDO2", 1, RT298_POWER_CTRL2, + 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("VREF1", 1, RT298_POWER_CTRL2, + 4, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("LV", 2, RT298_POWER_CTRL1, + 13, 1, NULL, 0), + + + SND_SOC_DAPM_SUPPLY("MCLK MODE", RT298_PLL_CTRL1, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC1 Input Buffer", SND_SOC_NOPM, + 0, 0, rt298_mic1_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC1 Pin"), + SND_SOC_DAPM_INPUT("DMIC2 Pin"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("Beep"), + + /* DMIC */ + SND_SOC_DAPM_PGA_E("DMIC1", RT298_SET_POWER(RT298_DMIC1), 0, 1, + NULL, 0, rt298_set_dmic1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA("DMIC2", RT298_SET_POWER(RT298_DMIC2), 0, 1, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC Receiver", SND_SOC_NOPM, + 0, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX", SND_SOC_NOPM, 0, 0, + rt298_rec_mix, ARRAY_SIZE(rt298_rec_mix)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX_E("ADC 0 Mux", RT298_SET_POWER(RT298_ADC_IN1), 0, 1, + &rt298_adc0_mux, rt298_adc_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MUX_E("ADC 1 Mux", RT298_SET_POWER(RT298_ADC_IN2), 0, 1, + &rt298_adc1_mux, rt298_adc_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("DAC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* Output Mux */ + SND_SOC_DAPM_MUX("SPK Mux", SND_SOC_NOPM, 0, 0, &rt298_spo_mux), + SND_SOC_DAPM_MUX("HPO Mux", SND_SOC_NOPM, 0, 0, &rt298_hpo_mux), + + SND_SOC_DAPM_SUPPLY("HP Power", RT298_SET_PIN_HPO, + RT298_SET_PIN_SFT, 0, NULL, 0), + + /* Output Mixer */ + SND_SOC_DAPM_MIXER("Front", RT298_SET_POWER(RT298_DAC_OUT1), 0, 1, + rt298_front_mix, ARRAY_SIZE(rt298_front_mix)), + SND_SOC_DAPM_PGA("Surround", RT298_SET_POWER(RT298_DAC_OUT2), 0, 1, + NULL, 0), + + /* Output Pga */ + SND_SOC_DAPM_SWITCH_E("SPO", SND_SOC_NOPM, 0, 0, + &spo_enable_control, rt298_spk_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SWITCH("HPO L", SND_SOC_NOPM, 0, 0, + &hpol_enable_control), + SND_SOC_DAPM_SWITCH("HPO R", SND_SOC_NOPM, 0, 0, + &hpor_enable_control), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + SND_SOC_DAPM_OUTPUT("HPO Pin"), + SND_SOC_DAPM_OUTPUT("SPDIF"), +}; + +static const struct snd_soc_dapm_route rt298_dapm_routes[] = { + + {"ADC 0", NULL, "MCLK MODE", is_mclk_mode}, + {"ADC 1", NULL, "MCLK MODE", is_mclk_mode}, + {"Front", NULL, "MCLK MODE", is_mclk_mode}, + {"Surround", NULL, "MCLK MODE", is_mclk_mode}, + + {"HP Power", NULL, "LDO1"}, + {"HP Power", NULL, "LDO2"}, + {"HP Power", NULL, "LV"}, + {"HP Power", NULL, "VREF1"}, + {"HP Power", NULL, "BG_MBIAS"}, + + {"MIC1", NULL, "LDO1"}, + {"MIC1", NULL, "LDO2"}, + {"MIC1", NULL, "HV"}, + {"MIC1", NULL, "LV"}, + {"MIC1", NULL, "VREF"}, + {"MIC1", NULL, "VREF1"}, + {"MIC1", NULL, "BG_MBIAS"}, + {"MIC1", NULL, "MIC1 Input Buffer"}, + + {"SPO", NULL, "LDO1"}, + {"SPO", NULL, "LDO2"}, + {"SPO", NULL, "HV"}, + {"SPO", NULL, "LV"}, + {"SPO", NULL, "VREF"}, + {"SPO", NULL, "VREF1"}, + {"SPO", NULL, "BG_MBIAS"}, + + {"DMIC1", NULL, "DMIC1 Pin"}, + {"DMIC2", NULL, "DMIC2 Pin"}, + {"DMIC1", NULL, "DMIC Receiver"}, + {"DMIC2", NULL, "DMIC Receiver"}, + + {"RECMIX", "Beep Switch", "Beep"}, + {"RECMIX", "Line1 Switch", "LINE1"}, + {"RECMIX", "Mic1 Switch", "MIC1"}, + + {"ADC 0 Mux", "Dmic", "DMIC1"}, + {"ADC 0 Mux", "RECMIX", "RECMIX"}, + {"ADC 0 Mux", "Mic", "MIC1"}, + {"ADC 1 Mux", "Dmic", "DMIC2"}, + {"ADC 1 Mux", "RECMIX", "RECMIX"}, + {"ADC 1 Mux", "Mic", "MIC1"}, + + {"ADC 0", NULL, "ADC 0 Mux"}, + {"ADC 1", NULL, "ADC 1 Mux"}, + + {"AIF1TX", NULL, "ADC 0"}, + {"AIF2TX", NULL, "ADC 1"}, + + {"DAC 0", NULL, "AIF1RX"}, + {"DAC 1", NULL, "AIF2RX"}, + + {"Front", "DAC Switch", "DAC 0"}, + {"Front", "RECMIX Switch", "RECMIX"}, + + {"Surround", NULL, "DAC 1"}, + + {"SPK Mux", "Front", "Front"}, + {"SPK Mux", "Surround", "Surround"}, + + {"HPO Mux", "Front", "Front"}, + {"HPO Mux", "Surround", "Surround"}, + + {"SPO", "Switch", "SPK Mux"}, + {"HPO L", "Switch", "HPO Mux"}, + {"HPO R", "Switch", "HPO Mux"}, + {"HPO L", NULL, "HP Power"}, + {"HPO R", NULL, "HP Power"}, + + {"SPOL", NULL, "SPO"}, + {"SPOR", NULL, "SPO"}, + {"HPO Pin", NULL, "HPO L"}, + {"HPO Pin", NULL, "HPO R"}, +}; + +static int rt298_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + int d_len_code; + + switch (params_rate(params)) { + /* bit 14 0:48K 1:44.1K */ + case 44100: + case 48000: + break; + default: + dev_err(component->dev, "Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + switch (rt298->sys_clk) { + case 12288000: + case 24576000: + if (params_rate(params) != 48000) { + dev_err(component->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt298->sys_clk); + return -EINVAL; + } + break; + case 11289600: + case 22579200: + if (params_rate(params) != 44100) { + dev_err(component->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt298->sys_clk); + return -EINVAL; + } + break; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + d_len_code = 0; + switch (params_width(params)) { + /* bit 6:4 Bits per Sample */ + case 16: + d_len_code = 0; + val |= (0x1 << 4); + break; + case 32: + d_len_code = 2; + val |= (0x4 << 4); + break; + case 20: + d_len_code = 1; + val |= (0x2 << 4); + break; + case 24: + d_len_code = 2; + val |= (0x3 << 4); + break; + case 8: + d_len_code = 3; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x0018, d_len_code << 3); + dev_dbg(component->dev, "format val = 0x%x\n", val); + + snd_soc_component_update_bits(component, RT298_DAC_FORMAT, 0x407f, val); + snd_soc_component_update_bits(component, RT298_ADC_FORMAT, 0x407f, val); + + return 0; +} + +static int rt298_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x800, 0x800); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x800, 0x0); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x300, 0x0); + break; + case SND_SOC_DAIFMT_LEFT_J: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x300, 0x1 << 8); + break; + case SND_SOC_DAIFMT_DSP_A: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x300, 0x2 << 8); + break; + case SND_SOC_DAIFMT_DSP_B: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x300, 0x3 << 8); + break; + default: + return -EINVAL; + } + /* bit 15 Stream Type 0:PCM 1:Non-PCM */ + snd_soc_component_update_bits(component, RT298_DAC_FORMAT, 0x8000, 0); + snd_soc_component_update_bits(component, RT298_ADC_FORMAT, 0x8000, 0); + + return 0; +} + +static int rt298_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s freq=%d\n", __func__, freq); + + if (RT298_SCLK_S_MCLK == clk_id) { + snd_soc_component_update_bits(component, + RT298_I2S_CTRL2, 0x0100, 0x0); + snd_soc_component_update_bits(component, + RT298_PLL_CTRL1, 0x20, 0x20); + } else { + snd_soc_component_update_bits(component, + RT298_I2S_CTRL2, 0x0100, 0x0100); + snd_soc_component_update_bits(component, + RT298_PLL_CTRL1, 0x20, 0x0); + } + + switch (freq) { + case 19200000: + if (RT298_SCLK_S_MCLK == clk_id) { + dev_err(component->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_component_update_bits(component, + RT298_I2S_CTRL2, 0x40, 0x40); + break; + case 24000000: + if (RT298_SCLK_S_MCLK == clk_id) { + dev_err(component->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_component_update_bits(component, + RT298_I2S_CTRL2, 0x40, 0x0); + break; + case 12288000: + case 11289600: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL2, 0x8, 0x0); + snd_soc_component_update_bits(component, + RT298_CLK_DIV, 0xfc1e, 0x0004); + break; + case 24576000: + case 22579200: + snd_soc_component_update_bits(component, + RT298_I2S_CTRL2, 0x8, 0x8); + snd_soc_component_update_bits(component, + RT298_CLK_DIV, 0xfc1e, 0x5406); + break; + default: + dev_err(component->dev, "Unsupported system clock\n"); + return -EINVAL; + } + + rt298->sys_clk = freq; + rt298->clk_id = clk_id; + + return 0; +} + +static int rt298_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + + dev_dbg(component->dev, "%s ratio=%d\n", __func__, ratio); + if (50 == ratio) + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x1000, 0x1000); + else + snd_soc_component_update_bits(component, + RT298_I2S_CTRL1, 0x1000, 0x0); + + + return 0; +} + +static int rt298_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == + snd_soc_component_get_bias_level(component)) { + snd_soc_component_write(component, + RT298_SET_AUDIO_POWER, AC_PWRST_D0); + snd_soc_component_update_bits(component, 0x0d, 0x200, 0x200); + snd_soc_component_update_bits(component, 0x52, 0x80, 0x0); + mdelay(20); + snd_soc_component_update_bits(component, 0x0d, 0x200, 0x0); + snd_soc_component_update_bits(component, 0x52, 0x80, 0x80); + } + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_component_write(component, + RT298_SET_AUDIO_POWER, AC_PWRST_D3); + break; + + default: + break; + } + + return 0; +} + +static irqreturn_t rt298_irq(int irq, void *data) +{ + struct rt298_priv *rt298 = data; + bool hp = false; + bool mic = false; + int ret, status = 0; + + ret = rt298_jack_detect(rt298, &hp, &mic); + + /* Clear IRQ */ + regmap_update_bits(rt298->regmap, RT298_IRQ_CTRL, 0x1, 0x1); + + if (ret == 0) { + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt298->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + pm_wakeup_event(&rt298->i2c->dev, 300); + } + + return IRQ_HANDLED; +} + +static int rt298_probe(struct snd_soc_component *component) +{ + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + + rt298->component = component; + + if (rt298->i2c->irq) { + regmap_update_bits(rt298->regmap, + RT298_IRQ_CTRL, 0x2, 0x2); + + INIT_DELAYED_WORK(&rt298->jack_detect_work, + rt298_jack_detect_work); + schedule_delayed_work(&rt298->jack_detect_work, + msecs_to_jiffies(1250)); + } + + return 0; +} + +static void rt298_remove(struct snd_soc_component *component) +{ + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&rt298->jack_detect_work); +} + +#ifdef CONFIG_PM +static int rt298_suspend(struct snd_soc_component *component) +{ + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + + rt298->is_hp_in = -1; + regcache_cache_only(rt298->regmap, true); + regcache_mark_dirty(rt298->regmap); + + return 0; +} + +static int rt298_resume(struct snd_soc_component *component) +{ + struct rt298_priv *rt298 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt298->regmap, false); + rt298_index_sync(component); + regcache_sync(rt298->regmap); + + return 0; +} +#else +#define rt298_suspend NULL +#define rt298_resume NULL +#endif + +#define RT298_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT298_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt298_aif_dai_ops = { + .hw_params = rt298_hw_params, + .set_fmt = rt298_set_dai_fmt, + .set_sysclk = rt298_set_dai_sysclk, + .set_bclk_ratio = rt298_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt298_dai[] = { + { + .name = "rt298-aif1", + .id = RT298_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT298_STEREO_RATES, + .formats = RT298_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT298_STEREO_RATES, + .formats = RT298_FORMATS, + }, + .ops = &rt298_aif_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "rt298-aif2", + .id = RT298_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT298_STEREO_RATES, + .formats = RT298_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT298_STEREO_RATES, + .formats = RT298_FORMATS, + }, + .ops = &rt298_aif_dai_ops, + .symmetric_rates = 1, + }, + +}; + +static const struct snd_soc_component_driver soc_component_dev_rt298 = { + .probe = rt298_probe, + .remove = rt298_remove, + .suspend = rt298_suspend, + .resume = rt298_resume, + .set_bias_level = rt298_set_bias_level, + .controls = rt298_snd_controls, + .num_controls = ARRAY_SIZE(rt298_snd_controls), + .dapm_widgets = rt298_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt298_dapm_widgets), + .dapm_routes = rt298_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt298_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt298_regmap = { + .reg_bits = 32, + .val_bits = 32, + .max_register = 0x02370100, + .volatile_reg = rt298_volatile_register, + .readable_reg = rt298_readable_register, + .reg_write = rl6347a_hw_write, + .reg_read = rl6347a_hw_read, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt298_reg, + .num_reg_defaults = ARRAY_SIZE(rt298_reg), +}; + +static const struct i2c_device_id rt298_i2c_id[] = { + {"rt298", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt298_i2c_id); + +static const struct acpi_device_id rt298_acpi_match[] = { + { "INT343A", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt298_acpi_match); + +static const struct dmi_system_id force_combo_jack_table[] = { + { + .ident = "Intel Broxton P", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp"), + DMI_MATCH(DMI_PRODUCT_NAME, "Broxton P") + } + }, + { + .ident = "Intel Gemini Lake", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp"), + DMI_MATCH(DMI_PRODUCT_NAME, "Geminilake") + } + }, + { } +}; + +static int rt298_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt298_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt298_priv *rt298; + struct device *dev = &i2c->dev; + const struct acpi_device_id *acpiid; + int i, ret; + + rt298 = devm_kzalloc(&i2c->dev, sizeof(*rt298), + GFP_KERNEL); + if (NULL == rt298) + return -ENOMEM; + + rt298->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt298_regmap); + if (IS_ERR(rt298->regmap)) { + ret = PTR_ERR(rt298->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt298->regmap, + RT298_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID), &ret); + if (ret != RT298_VENDOR_ID) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt298\n", ret); + return -ENODEV; + } + + rt298->index_cache = devm_kmemdup(&i2c->dev, rt298_index_def, + sizeof(rt298_index_def), GFP_KERNEL); + if (!rt298->index_cache) + return -ENOMEM; + + rt298->index_cache_size = INDEX_CACHE_SIZE; + rt298->i2c = i2c; + i2c_set_clientdata(i2c, rt298); + + /* restore codec default */ + for (i = 0; i < INDEX_CACHE_SIZE; i++) + regmap_write(rt298->regmap, rt298->index_cache[i].reg, + rt298->index_cache[i].def); + for (i = 0; i < ARRAY_SIZE(rt298_reg); i++) + regmap_write(rt298->regmap, rt298_reg[i].reg, + rt298_reg[i].def); + + if (pdata) + rt298->pdata = *pdata; + + /* enable jack combo mode on supported devices */ + acpiid = acpi_match_device(dev->driver->acpi_match_table, dev); + if (acpiid && acpiid->driver_data) { + rt298->pdata = *(struct rt298_platform_data *) + acpiid->driver_data; + } + + if (dmi_check_system(force_combo_jack_table)) { + rt298->pdata.cbj_en = true; + rt298->pdata.gpio2_en = false; + } + + /* VREF Charging */ + regmap_update_bits(rt298->regmap, 0x04, 0x80, 0x80); + regmap_update_bits(rt298->regmap, 0x1b, 0x860, 0x860); + /* Vref2 */ + regmap_update_bits(rt298->regmap, 0x08, 0x20, 0x20); + + regmap_write(rt298->regmap, RT298_SET_AUDIO_POWER, AC_PWRST_D3); + + for (i = 0; i < RT298_POWER_REG_LEN; i++) + regmap_write(rt298->regmap, + RT298_SET_POWER(rt298_support_power_controls[i]), + AC_PWRST_D1); + + if (!rt298->pdata.cbj_en) { + regmap_write(rt298->regmap, RT298_CBJ_CTRL2, 0x0000); + regmap_write(rt298->regmap, RT298_MIC1_DET_CTRL, 0x0816); + regmap_update_bits(rt298->regmap, + RT298_CBJ_CTRL1, 0xf000, 0xb000); + } else { + regmap_update_bits(rt298->regmap, + RT298_CBJ_CTRL1, 0xf000, 0x5000); + } + + mdelay(10); + + if (!rt298->pdata.gpio2_en) + regmap_write(rt298->regmap, RT298_SET_DMIC2_DEFAULT, 0x40); + else + regmap_write(rt298->regmap, RT298_SET_DMIC2_DEFAULT, 0); + + mdelay(10); + + regmap_write(rt298->regmap, RT298_MISC_CTRL1, 0x0000); + regmap_update_bits(rt298->regmap, + RT298_WIND_FILTER_CTRL, 0x0082, 0x0082); + + regmap_write(rt298->regmap, RT298_UNSOLICITED_INLINE_CMD, 0x81); + regmap_write(rt298->regmap, RT298_UNSOLICITED_HP_OUT, 0x82); + regmap_write(rt298->regmap, RT298_UNSOLICITED_MIC1, 0x84); + regmap_update_bits(rt298->regmap, RT298_IRQ_FLAG_CTRL, 0x2, 0x2); + + rt298->is_hp_in = -1; + + if (rt298->i2c->irq) { + ret = request_threaded_irq(rt298->i2c->irq, NULL, rt298_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "rt298", rt298); + if (ret != 0) { + dev_err(&i2c->dev, + "Failed to reguest IRQ: %d\n", ret); + return ret; + } + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt298, + rt298_dai, ARRAY_SIZE(rt298_dai)); + + return ret; +} + +static int rt298_i2c_remove(struct i2c_client *i2c) +{ + struct rt298_priv *rt298 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, rt298); + + return 0; +} + + +static struct i2c_driver rt298_i2c_driver = { + .driver = { + .name = "rt298", + .acpi_match_table = ACPI_PTR(rt298_acpi_match), + }, + .probe = rt298_i2c_probe, + .remove = rt298_i2c_remove, + .id_table = rt298_i2c_id, +}; + +module_i2c_driver(rt298_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT298 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt298.h b/sound/soc/codecs/rt298.h new file mode 100644 index 000000000..b4db93535 --- /dev/null +++ b/sound/soc/codecs/rt298.h @@ -0,0 +1,216 @@ +/* + * rt298.h -- RT298 ALSA SoC audio driver + * + * Copyright 2011 Realtek Microelectronics + * Author: Johnny Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT298_H__ +#define __RT298_H__ + +#define VERB_CMD(V, N, D) ((N << 20) | (V << 8) | D) + +#define RT298_AUDIO_FUNCTION_GROUP 0x01 +#define RT298_DAC_OUT1 0x02 +#define RT298_DAC_OUT2 0x03 +#define RT298_DIG_CVT 0x06 +#define RT298_ADC_IN1 0x09 +#define RT298_ADC_IN2 0x08 +#define RT298_MIXER_IN 0x0b +#define RT298_MIXER_OUT1 0x0c +#define RT298_MIXER_OUT2 0x0d +#define RT298_DMIC1 0x12 +#define RT298_DMIC2 0x13 +#define RT298_SPK_OUT 0x14 +#define RT298_MIC1 0x18 +#define RT298_LINE1 0x1a +#define RT298_BEEP 0x1d +#define RT298_SPDIF 0x1e +#define RT298_VENDOR_REGISTERS 0x20 +#define RT298_HP_OUT 0x21 +#define RT298_MIXER_IN1 0x22 +#define RT298_MIXER_IN2 0x23 +#define RT298_INLINE_CMD 0x55 + +#define RT298_SET_PIN_SFT 6 +#define RT298_SET_PIN_ENABLE 0x40 +#define RT298_SET_PIN_DISABLE 0 +#define RT298_SET_EAPD_HIGH 0x2 +#define RT298_SET_EAPD_LOW 0 + +#define RT298_MUTE_SFT 7 + +/* Verb commands */ +#define RT298_GET_PARAM(NID, PARAM) VERB_CMD(AC_VERB_PARAMETERS, NID, PARAM) +#define RT298_SET_POWER(NID) VERB_CMD(AC_VERB_SET_POWER_STATE, NID, 0) +#define RT298_SET_AUDIO_POWER RT298_SET_POWER(RT298_AUDIO_FUNCTION_GROUP) +#define RT298_SET_HPO_POWER RT298_SET_POWER(RT298_HP_OUT) +#define RT298_SET_SPK_POWER RT298_SET_POWER(RT298_SPK_OUT) +#define RT298_SET_DMIC1_POWER RT298_SET_POWER(RT298_DMIC1) +#define RT298_SPK_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT298_SPK_OUT, 0) +#define RT298_HPO_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT298_HP_OUT, 0) +#define RT298_ADC0_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT298_MIXER_IN1, 0) +#define RT298_ADC1_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT298_MIXER_IN2, 0) +#define RT298_SET_MIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT298_MIC1, 0) +#define RT298_SET_PIN_HPO\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT298_HP_OUT, 0) +#define RT298_SET_PIN_SPK\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT298_SPK_OUT, 0) +#define RT298_SET_PIN_DMIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT298_DMIC1, 0) +#define RT298_SET_PIN_SPDIF\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT298_SPDIF, 0) +#define RT298_SET_PIN_DIG_CVT\ + VERB_CMD(AC_VERB_SET_DIGI_CONVERT_1, RT298_DIG_CVT, 0) +#define RT298_SPK_EAPD\ + VERB_CMD(AC_VERB_SET_EAPD_BTLENABLE, RT298_SPK_OUT, 0) +#define RT298_SET_AMP_GAIN_HPO\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_HP_OUT, 0) +#define RT298_SET_AMP_GAIN_ADC_IN1\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_ADC_IN1, 0) +#define RT298_SET_AMP_GAIN_ADC_IN2\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_ADC_IN2, 0) +#define RT298_GET_HP_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT298_HP_OUT, 0) +#define RT298_GET_MIC1_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT298_MIC1, 0) +#define RT298_SET_DMIC2_DEFAULT\ + VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT298_DMIC2, 0) +#define RT298_SET_SPDIF_DEFAULT\ + VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT298_SPDIF, 0) +#define RT298_DACL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_DAC_OUT1, 0xa000) +#define RT298_DACR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_DAC_OUT1, 0x9000) +#define RT298_ADCL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_ADC_IN1, 0x6000) +#define RT298_ADCR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_ADC_IN1, 0x5000) +#define RT298_MIC_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_MIC1, 0x7000) +#define RT298_SPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_SPK_OUT, 0xa000) +#define RT298_SPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_SPK_OUT, 0x9000) +#define RT298_HPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_HP_OUT, 0xa000) +#define RT298_HPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_HP_OUT, 0x9000) +#define RT298_F_DAC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_MIXER_OUT1, 0x7000) +#define RT298_F_RECMIX_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_MIXER_OUT1, 0x7100) +#define RT298_REC_MIC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_MIXER_IN, 0x7000) +#define RT298_REC_I2S_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_MIXER_IN, 0x7100) +#define RT298_REC_LINE_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_MIXER_IN, 0x7200) +#define RT298_REC_BEEP_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT298_MIXER_IN, 0x7300) +#define RT298_DAC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT298_DAC_OUT1, 0) +#define RT298_ADC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT298_ADC_IN1, 0) +#define RT298_COEF_INDEX\ + VERB_CMD(AC_VERB_SET_COEF_INDEX, RT298_VENDOR_REGISTERS, 0) +#define RT298_PROC_COEF\ + VERB_CMD(AC_VERB_SET_PROC_COEF, RT298_VENDOR_REGISTERS, 0) +#define RT298_UNSOLICITED_INLINE_CMD\ + VERB_CMD(AC_VERB_SET_UNSOLICITED_ENABLE, RT298_INLINE_CMD, 0) +#define RT298_UNSOLICITED_HP_OUT\ + VERB_CMD(AC_VERB_SET_UNSOLICITED_ENABLE, RT298_HP_OUT, 0) +#define RT298_UNSOLICITED_MIC1\ + VERB_CMD(AC_VERB_SET_UNSOLICITED_ENABLE, RT298_MIC1, 0) + +/* Index registers */ +#define RT298_A_BIAS_CTRL1 0x01 +#define RT298_A_BIAS_CTRL2 0x02 +#define RT298_POWER_CTRL1 0x03 +#define RT298_A_BIAS_CTRL3 0x04 +#define RT298_D_FILTER_CTRL 0x05 +#define RT298_POWER_CTRL2 0x08 +#define RT298_I2S_CTRL1 0x09 +#define RT298_I2S_CTRL2 0x0a +#define RT298_CLK_DIV 0x0b +#define RT298_DC_GAIN 0x0d +#define RT298_POWER_CTRL3 0x0f +#define RT298_MIC1_DET_CTRL 0x19 +#define RT298_MISC_CTRL1 0x20 +#define RT298_IRQ_CTRL 0x33 +#define RT298_WIND_FILTER_CTRL 0x46 +#define RT298_PLL_CTRL1 0x49 +#define RT298_VAD_CTRL 0x4e +#define RT298_CBJ_CTRL1 0x4f +#define RT298_CBJ_CTRL2 0x50 +#define RT298_PLL_CTRL 0x63 +#define RT298_DEPOP_CTRL1 0x66 +#define RT298_DEPOP_CTRL2 0x67 +#define RT298_DEPOP_CTRL3 0x68 +#define RT298_DEPOP_CTRL4 0x69 +#define RT298_IRQ_FLAG_CTRL 0x7c + +/* SPDIF (0x06) */ +#define RT298_SPDIF_SEL_SFT 0 +#define RT298_SPDIF_SEL_PCM0 0 +#define RT298_SPDIF_SEL_PCM1 1 +#define RT298_SPDIF_SEL_SPOUT 2 +#define RT298_SPDIF_SEL_PP 3 + +/* RECMIX (0x0b) */ +#define RT298_M_REC_BEEP_SFT 0 +#define RT298_M_REC_LINE1_SFT 1 +#define RT298_M_REC_MIC1_SFT 2 +#define RT298_M_REC_I2S_SFT 3 + +/* Front (0x0c) */ +#define RT298_M_FRONT_DAC_SFT 0 +#define RT298_M_FRONT_REC_SFT 1 + +/* SPK-OUT (0x14) */ +#define RT298_M_SPK_MUX_SFT 14 +#define RT298_SPK_SEL_MASK 0x1 +#define RT298_SPK_SEL_SFT 0 +#define RT298_SPK_SEL_F 0 +#define RT298_SPK_SEL_S 1 + +/* HP-OUT (0x21) */ +#define RT298_M_HP_MUX_SFT 14 +#define RT298_HP_SEL_MASK 0x1 +#define RT298_HP_SEL_SFT 0 +#define RT298_HP_SEL_F 0 +#define RT298_HP_SEL_S 1 + +/* ADC (0x22) (0x23) */ +#define RT298_ADC_SEL_MASK 0x7 +#define RT298_ADC_SEL_SFT 0 +#define RT298_ADC_SEL_SURR 0 +#define RT298_ADC_SEL_FRONT 1 +#define RT298_ADC_SEL_DMIC 2 +#define RT298_ADC_SEL_BEEP 4 +#define RT298_ADC_SEL_LINE1 5 +#define RT298_ADC_SEL_I2S 6 +#define RT298_ADC_SEL_MIC1 7 + +#define RT298_SCLK_S_MCLK 0 +#define RT298_SCLK_S_PLL 1 + +enum { + RT298_AIF1, + RT298_AIF2, + RT298_AIFS, +}; + +int rt298_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack); + +#endif /* __RT298_H__ */ + diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c new file mode 100644 index 000000000..cdb1f4000 --- /dev/null +++ b/sound/soc/codecs/rt5514-spi.c @@ -0,0 +1,517 @@ +/* + * rt5514-spi.c -- RT5514 SPI driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt5514-spi.h" + +#define DRV_NAME "rt5514-spi" + +static struct spi_device *rt5514_spi; + +struct rt5514_dsp { + struct device *dev; + struct delayed_work copy_work; + struct mutex dma_lock; + struct snd_pcm_substream *substream; + unsigned int buf_base, buf_limit, buf_rp; + size_t buf_size, get_size, dma_offset; +}; + +static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = 0x20000 / 8, + .periods_min = 8, + .periods_max = 8, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 0x20000, +}; + +static struct snd_soc_dai_driver rt5514_spi_dai = { + .name = "rt5514-dsp-cpu-dai", + .id = 0, + .capture = { + .stream_name = "DSP Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static void rt5514_spi_copy_work(struct work_struct *work) +{ + struct rt5514_dsp *rt5514_dsp = + container_of(work, struct rt5514_dsp, copy_work.work); + struct snd_pcm_runtime *runtime; + size_t period_bytes, truncated_bytes = 0; + unsigned int cur_wp, remain_data; + u8 buf[8]; + + mutex_lock(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); + goto done; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + + if (rt5514_dsp->get_size >= rt5514_dsp->buf_size) { + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_WP, (u8 *)&buf, + sizeof(buf)); + cur_wp = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + if (cur_wp >= rt5514_dsp->buf_rp) + remain_data = (cur_wp - rt5514_dsp->buf_rp); + else + remain_data = + (rt5514_dsp->buf_limit - rt5514_dsp->buf_rp) + + (cur_wp - rt5514_dsp->buf_base); + + if (remain_data < period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); + goto done; + } + } + + if (rt5514_dsp->buf_rp + period_bytes <= rt5514_dsp->buf_limit) { + rt5514_spi_burst_read(rt5514_dsp->buf_rp, + runtime->dma_area + rt5514_dsp->dma_offset, + period_bytes); + + if (rt5514_dsp->buf_rp + period_bytes == rt5514_dsp->buf_limit) + rt5514_dsp->buf_rp = rt5514_dsp->buf_base; + else + rt5514_dsp->buf_rp += period_bytes; + } else { + truncated_bytes = rt5514_dsp->buf_limit - rt5514_dsp->buf_rp; + rt5514_spi_burst_read(rt5514_dsp->buf_rp, + runtime->dma_area + rt5514_dsp->dma_offset, + truncated_bytes); + + rt5514_spi_burst_read(rt5514_dsp->buf_base, + runtime->dma_area + rt5514_dsp->dma_offset + + truncated_bytes, period_bytes - truncated_bytes); + + rt5514_dsp->buf_rp = rt5514_dsp->buf_base + period_bytes - + truncated_bytes; + } + + rt5514_dsp->get_size += period_bytes; + rt5514_dsp->dma_offset += period_bytes; + if (rt5514_dsp->dma_offset >= runtime->dma_bytes) + rt5514_dsp->dma_offset = 0; + + snd_pcm_period_elapsed(rt5514_dsp->substream); + + schedule_delayed_work(&rt5514_dsp->copy_work, 5); + +done: + mutex_unlock(&rt5514_dsp->dma_lock); +} + +static void rt5514_schedule_copy(struct rt5514_dsp *rt5514_dsp) +{ + size_t period_bytes; + u8 buf[8]; + + if (!rt5514_dsp->substream) + return; + + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + rt5514_dsp->get_size = 0; + + /** + * The address area x1800XXXX is the register address, and it cannot + * support spi burst read perfectly. So we use the spi burst read + * individually to make sure the data correctly. + */ + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_BASE, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_base = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_LIMIT, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_limit = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_WP, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_rp = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + if (rt5514_dsp->buf_rp % 8) + rt5514_dsp->buf_rp = (rt5514_dsp->buf_rp / 8) * 8; + + rt5514_dsp->buf_size = rt5514_dsp->buf_limit - rt5514_dsp->buf_base; + + if (rt5514_dsp->buf_size % period_bytes) + rt5514_dsp->buf_size = (rt5514_dsp->buf_size / period_bytes) * + period_bytes; + + if (rt5514_dsp->buf_base && rt5514_dsp->buf_limit && + rt5514_dsp->buf_rp && rt5514_dsp->buf_size) + schedule_delayed_work(&rt5514_dsp->copy_work, 0); +} + +static irqreturn_t rt5514_spi_irq(int irq, void *data) +{ + struct rt5514_dsp *rt5514_dsp = data; + + rt5514_schedule_copy(rt5514_dsp); + + return IRQ_HANDLED; +} + +/* PCM for streaming audio from the DSP buffer */ +static int rt5514_spi_pcm_open(struct snd_pcm_substream *substream) +{ + snd_soc_set_runtime_hwparams(substream, &rt5514_spi_pcm_hardware); + + return 0; +} + +static int rt5514_spi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + int ret; + u8 buf[8]; + + mutex_lock(&rt5514_dsp->dma_lock); + ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + rt5514_dsp->substream = substream; + rt5514_dsp->dma_offset = 0; + + /* Read IRQ status and schedule copy accordingly. */ + rt5514_spi_burst_read(RT5514_IRQ_CTRL, (u8 *)&buf, sizeof(buf)); + if (buf[0] & RT5514_IRQ_STATUS_BIT) + rt5514_schedule_copy(rt5514_dsp); + + mutex_unlock(&rt5514_dsp->dma_lock); + + return ret; +} + +static int rt5514_spi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + + mutex_lock(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = NULL; + mutex_unlock(&rt5514_dsp->dma_lock); + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static snd_pcm_uframes_t rt5514_spi_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + + return bytes_to_frames(runtime, rt5514_dsp->dma_offset); +} + +static const struct snd_pcm_ops rt5514_spi_pcm_ops = { + .open = rt5514_spi_pcm_open, + .hw_params = rt5514_spi_hw_params, + .hw_free = rt5514_spi_hw_free, + .pointer = rt5514_spi_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static int rt5514_spi_pcm_probe(struct snd_soc_component *component) +{ + struct rt5514_dsp *rt5514_dsp; + int ret; + + rt5514_dsp = devm_kzalloc(component->dev, sizeof(*rt5514_dsp), + GFP_KERNEL); + if (!rt5514_dsp) + return -ENOMEM; + + rt5514_dsp->dev = &rt5514_spi->dev; + mutex_init(&rt5514_dsp->dma_lock); + INIT_DELAYED_WORK(&rt5514_dsp->copy_work, rt5514_spi_copy_work); + snd_soc_component_set_drvdata(component, rt5514_dsp); + + if (rt5514_spi->irq) { + ret = devm_request_threaded_irq(&rt5514_spi->dev, + rt5514_spi->irq, NULL, rt5514_spi_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rt5514-spi", + rt5514_dsp); + if (ret) + dev_err(&rt5514_spi->dev, + "%s Failed to reguest IRQ: %d\n", __func__, + ret); + else + device_init_wakeup(rt5514_dsp->dev, true); + } + + return 0; +} + +static const struct snd_soc_component_driver rt5514_spi_component = { + .name = DRV_NAME, + .probe = rt5514_spi_pcm_probe, + .ops = &rt5514_spi_pcm_ops, +}; + +/** + * rt5514_spi_burst_read - Read data from SPI by rt5514 address. + * @addr: Start address. + * @rxbuf: Data Buffer for reading. + * @len: Data length, it must be a multiple of 8. + * + * + * Returns true for success. + */ +int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len) +{ + u8 spi_cmd = RT5514_SPI_CMD_BURST_READ; + int status; + u8 write_buf[8]; + unsigned int i, end, offset = 0; + + struct spi_message message; + struct spi_transfer x[3]; + + while (offset < len) { + if (offset + RT5514_SPI_BUF_LEN <= len) + end = RT5514_SPI_BUF_LEN; + else + end = len % RT5514_SPI_BUF_LEN; + + write_buf[0] = spi_cmd; + write_buf[1] = ((addr + offset) & 0xff000000) >> 24; + write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + write_buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + spi_message_init(&message); + memset(x, 0, sizeof(x)); + + x[0].len = 5; + x[0].tx_buf = write_buf; + spi_message_add_tail(&x[0], &message); + + x[1].len = 4; + x[1].tx_buf = write_buf; + spi_message_add_tail(&x[1], &message); + + x[2].len = end; + x[2].rx_buf = rxbuf + offset; + spi_message_add_tail(&x[2], &message); + + status = spi_sync(rt5514_spi, &message); + + if (status) + return false; + + offset += RT5514_SPI_BUF_LEN; + } + + for (i = 0; i < len; i += 8) { + write_buf[0] = rxbuf[i + 0]; + write_buf[1] = rxbuf[i + 1]; + write_buf[2] = rxbuf[i + 2]; + write_buf[3] = rxbuf[i + 3]; + write_buf[4] = rxbuf[i + 4]; + write_buf[5] = rxbuf[i + 5]; + write_buf[6] = rxbuf[i + 6]; + write_buf[7] = rxbuf[i + 7]; + + rxbuf[i + 0] = write_buf[7]; + rxbuf[i + 1] = write_buf[6]; + rxbuf[i + 2] = write_buf[5]; + rxbuf[i + 3] = write_buf[4]; + rxbuf[i + 4] = write_buf[3]; + rxbuf[i + 5] = write_buf[2]; + rxbuf[i + 6] = write_buf[1]; + rxbuf[i + 7] = write_buf[0]; + } + + return true; +} +EXPORT_SYMBOL_GPL(rt5514_spi_burst_read); + +/** + * rt5514_spi_burst_write - Write data to SPI by rt5514 address. + * @addr: Start address. + * @txbuf: Data Buffer for writng. + * @len: Data length, it must be a multiple of 8. + * + * + * Returns true for success. + */ +int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len) +{ + u8 spi_cmd = RT5514_SPI_CMD_BURST_WRITE; + u8 *write_buf; + unsigned int i, end, offset = 0; + + write_buf = kmalloc(RT5514_SPI_BUF_LEN + 6, GFP_KERNEL); + + if (write_buf == NULL) + return -ENOMEM; + + while (offset < len) { + if (offset + RT5514_SPI_BUF_LEN <= len) + end = RT5514_SPI_BUF_LEN; + else + end = len % RT5514_SPI_BUF_LEN; + + write_buf[0] = spi_cmd; + write_buf[1] = ((addr + offset) & 0xff000000) >> 24; + write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + write_buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + for (i = 0; i < end; i += 8) { + write_buf[i + 12] = txbuf[offset + i + 0]; + write_buf[i + 11] = txbuf[offset + i + 1]; + write_buf[i + 10] = txbuf[offset + i + 2]; + write_buf[i + 9] = txbuf[offset + i + 3]; + write_buf[i + 8] = txbuf[offset + i + 4]; + write_buf[i + 7] = txbuf[offset + i + 5]; + write_buf[i + 6] = txbuf[offset + i + 6]; + write_buf[i + 5] = txbuf[offset + i + 7]; + } + + write_buf[end + 5] = spi_cmd; + + spi_write(rt5514_spi, write_buf, end + 6); + + offset += RT5514_SPI_BUF_LEN; + } + + kfree(write_buf); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5514_spi_burst_write); + +static int rt5514_spi_probe(struct spi_device *spi) +{ + int ret; + + rt5514_spi = spi; + + ret = devm_snd_soc_register_component(&spi->dev, + &rt5514_spi_component, + &rt5514_spi_dai, 1); + if (ret < 0) { + dev_err(&spi->dev, "Failed to register component.\n"); + return ret; + } + + return 0; +} + +static int __maybe_unused rt5514_suspend(struct device *dev) +{ + int irq = to_spi_device(dev)->irq; + + if (device_may_wakeup(dev)) + enable_irq_wake(irq); + + return 0; +} + +static int __maybe_unused rt5514_resume(struct device *dev) +{ + struct snd_soc_component *component = snd_soc_lookup_component(dev, DRV_NAME); + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + int irq = to_spi_device(dev)->irq; + u8 buf[8]; + + if (device_may_wakeup(dev)) + disable_irq_wake(irq); + + if (rt5514_dsp) { + if (rt5514_dsp->substream) { + rt5514_spi_burst_read(RT5514_IRQ_CTRL, (u8 *)&buf, + sizeof(buf)); + if (buf[0] & RT5514_IRQ_STATUS_BIT) + rt5514_schedule_copy(rt5514_dsp); + } + } + + return 0; +} + +static const struct dev_pm_ops rt5514_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rt5514_suspend, rt5514_resume) +}; + +static const struct of_device_id rt5514_of_match[] = { + { .compatible = "realtek,rt5514", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5514_of_match); + +static struct spi_driver rt5514_spi_driver = { + .driver = { + .name = "rt5514", + .pm = &rt5514_pm_ops, + .of_match_table = of_match_ptr(rt5514_of_match), + }, + .probe = rt5514_spi_probe, +}; +module_spi_driver(rt5514_spi_driver); + +MODULE_DESCRIPTION("RT5514 SPI driver"); +MODULE_AUTHOR("Oder Chiou "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5514-spi.h b/sound/soc/codecs/rt5514-spi.h new file mode 100644 index 000000000..c1a36647c --- /dev/null +++ b/sound/soc/codecs/rt5514-spi.h @@ -0,0 +1,40 @@ +/* + * rt5514-spi.h -- RT5514 driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5514_SPI_H__ +#define __RT5514_SPI_H__ + +/** + * RT5514_SPI_BUF_LEN is the buffer size of SPI master controller. +*/ +#define RT5514_SPI_BUF_LEN 240 + +#define RT5514_BUFFER_VOICE_BASE 0x18000200 +#define RT5514_BUFFER_VOICE_LIMIT 0x18000204 +#define RT5514_BUFFER_VOICE_WP 0x1800020c +#define RT5514_IRQ_CTRL 0x18002094 + +#define RT5514_IRQ_STATUS_BIT (0x1 << 5) + +/* SPI Command */ +enum { + RT5514_SPI_CMD_16_READ = 0, + RT5514_SPI_CMD_16_WRITE, + RT5514_SPI_CMD_32_READ, + RT5514_SPI_CMD_32_WRITE, + RT5514_SPI_CMD_BURST_READ, + RT5514_SPI_CMD_BURST_WRITE, +}; + +int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len); +int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len); + +#endif /* __RT5514_SPI_H__ */ diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c new file mode 100644 index 000000000..0ecff5120 --- /dev/null +++ b/sound/soc/codecs/rt5514.c @@ -0,0 +1,1341 @@ +/* + * rt5514.c -- RT5514 ALSA SoC audio codec driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5514.h" +#if IS_ENABLED(CONFIG_SND_SOC_RT5514_SPI) +#include "rt5514-spi.h" +#endif + +static const struct reg_sequence rt5514_i2c_patch[] = { + {0x1800101c, 0x00000000}, + {0x18001100, 0x0000031f}, + {0x18001104, 0x00000007}, + {0x18001108, 0x00000000}, + {0x1800110c, 0x00000000}, + {0x18001110, 0x00000000}, + {0x18001114, 0x00000001}, + {0x18001118, 0x00000000}, + {0x18002f08, 0x00000006}, + {0x18002f00, 0x00055149}, + {0x18002f00, 0x0005514b}, + {0x18002f00, 0x00055149}, + {0xfafafafa, 0x00000001}, + {0x18002f10, 0x00000001}, + {0x18002f10, 0x00000000}, + {0x18002f10, 0x00000001}, + {0xfafafafa, 0x00000001}, + {0x18002000, 0x000010ec}, + {0xfafafafa, 0x00000000}, +}; + +static const struct reg_sequence rt5514_patch[] = { + {RT5514_DIG_IO_CTRL, 0x00000040}, + {RT5514_CLK_CTRL1, 0x38020041}, + {RT5514_SRC_CTRL, 0x44000eee}, + {RT5514_ANA_CTRL_LDO10, 0x00028604}, + {RT5514_ANA_CTRL_ADCFED, 0x00000800}, + {RT5514_ASRC_IN_CTRL1, 0x00000003}, + {RT5514_DOWNFILTER0_CTRL3, 0x10000342}, + {RT5514_DOWNFILTER1_CTRL3, 0x10000342}, +}; + +static const struct reg_default rt5514_reg[] = { + {RT5514_RESET, 0x00000000}, + {RT5514_PWR_ANA1, 0x00808880}, + {RT5514_PWR_ANA2, 0x00220000}, + {RT5514_I2S_CTRL1, 0x00000330}, + {RT5514_I2S_CTRL2, 0x20000000}, + {RT5514_VAD_CTRL6, 0xc00007d2}, + {RT5514_EXT_VAD_CTRL, 0x80000080}, + {RT5514_DIG_IO_CTRL, 0x00000040}, + {RT5514_PAD_CTRL1, 0x00804000}, + {RT5514_DMIC_DATA_CTRL, 0x00000005}, + {RT5514_DIG_SOURCE_CTRL, 0x00000002}, + {RT5514_SRC_CTRL, 0x44000eee}, + {RT5514_DOWNFILTER2_CTRL1, 0x0000882f}, + {RT5514_PLL_SOURCE_CTRL, 0x00000004}, + {RT5514_CLK_CTRL1, 0x38020041}, + {RT5514_CLK_CTRL2, 0x00000000}, + {RT5514_PLL3_CALIB_CTRL1, 0x00400200}, + {RT5514_PLL3_CALIB_CTRL5, 0x40220012}, + {RT5514_DELAY_BUF_CTRL1, 0x7fff006a}, + {RT5514_DELAY_BUF_CTRL3, 0x00000000}, + {RT5514_ASRC_IN_CTRL1, 0x00000003}, + {RT5514_DOWNFILTER0_CTRL1, 0x00020c2f}, + {RT5514_DOWNFILTER0_CTRL2, 0x00020c2f}, + {RT5514_DOWNFILTER0_CTRL3, 0x10000342}, + {RT5514_DOWNFILTER1_CTRL1, 0x00020c2f}, + {RT5514_DOWNFILTER1_CTRL2, 0x00020c2f}, + {RT5514_DOWNFILTER1_CTRL3, 0x10000342}, + {RT5514_ANA_CTRL_LDO10, 0x00028604}, + {RT5514_ANA_CTRL_LDO18_16, 0x02000345}, + {RT5514_ANA_CTRL_ADC12, 0x0000a2a8}, + {RT5514_ANA_CTRL_ADC21, 0x00001180}, + {RT5514_ANA_CTRL_ADC22, 0x0000aaa8}, + {RT5514_ANA_CTRL_ADC23, 0x00151427}, + {RT5514_ANA_CTRL_MICBST, 0x00002000}, + {RT5514_ANA_CTRL_ADCFED, 0x00000800}, + {RT5514_ANA_CTRL_INBUF, 0x00000143}, + {RT5514_ANA_CTRL_VREF, 0x00008d50}, + {RT5514_ANA_CTRL_PLL3, 0x0000000e}, + {RT5514_ANA_CTRL_PLL1_1, 0x00000000}, + {RT5514_ANA_CTRL_PLL1_2, 0x00030220}, + {RT5514_DMIC_LP_CTRL, 0x00000000}, + {RT5514_MISC_CTRL_DSP, 0x00000000}, + {RT5514_DSP_CTRL1, 0x00055149}, + {RT5514_DSP_CTRL3, 0x00000006}, + {RT5514_DSP_CTRL4, 0x00000001}, + {RT5514_VENDOR_ID1, 0x00000001}, + {RT5514_VENDOR_ID2, 0x10ec5514}, +}; + +static void rt5514_enable_dsp_prepare(struct rt5514_priv *rt5514) +{ + /* Reset */ + regmap_write(rt5514->i2c_regmap, 0x18002000, 0x000010ec); + /* LDO_I_limit */ + regmap_write(rt5514->i2c_regmap, 0x18002200, 0x00028604); + /* I2C bypass enable */ + regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000001); + /* mini-core reset */ + regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x0005514b); + regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x00055149); + /* I2C bypass disable */ + regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000000); + /* PIN config */ + regmap_write(rt5514->i2c_regmap, 0x18002070, 0x00000040); + /* PLL3(QN)=RCOSC*(10+2) */ + regmap_write(rt5514->i2c_regmap, 0x18002240, 0x0000000a); + /* PLL3 source=RCOSC, fsi=rt_clk */ + regmap_write(rt5514->i2c_regmap, 0x18002100, 0x0000000b); + /* Power on RCOSC, pll3 */ + regmap_write(rt5514->i2c_regmap, 0x18002004, 0x00808b81); + /* DSP clk source = pll3, ENABLE DSP clk */ + regmap_write(rt5514->i2c_regmap, 0x18002f08, 0x00000005); + /* Enable DSP clk auto switch */ + regmap_write(rt5514->i2c_regmap, 0x18001114, 0x00000001); + /* Reduce DSP power */ + regmap_write(rt5514->i2c_regmap, 0x18001118, 0x00000001); +} + +static bool rt5514_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5514_VENDOR_ID1: + case RT5514_VENDOR_ID2: + return true; + + default: + return false; + } +} + +static bool rt5514_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5514_RESET: + case RT5514_PWR_ANA1: + case RT5514_PWR_ANA2: + case RT5514_I2S_CTRL1: + case RT5514_I2S_CTRL2: + case RT5514_VAD_CTRL6: + case RT5514_EXT_VAD_CTRL: + case RT5514_DIG_IO_CTRL: + case RT5514_PAD_CTRL1: + case RT5514_DMIC_DATA_CTRL: + case RT5514_DIG_SOURCE_CTRL: + case RT5514_SRC_CTRL: + case RT5514_DOWNFILTER2_CTRL1: + case RT5514_PLL_SOURCE_CTRL: + case RT5514_CLK_CTRL1: + case RT5514_CLK_CTRL2: + case RT5514_PLL3_CALIB_CTRL1: + case RT5514_PLL3_CALIB_CTRL5: + case RT5514_DELAY_BUF_CTRL1: + case RT5514_DELAY_BUF_CTRL3: + case RT5514_ASRC_IN_CTRL1: + case RT5514_DOWNFILTER0_CTRL1: + case RT5514_DOWNFILTER0_CTRL2: + case RT5514_DOWNFILTER0_CTRL3: + case RT5514_DOWNFILTER1_CTRL1: + case RT5514_DOWNFILTER1_CTRL2: + case RT5514_DOWNFILTER1_CTRL3: + case RT5514_ANA_CTRL_LDO10: + case RT5514_ANA_CTRL_LDO18_16: + case RT5514_ANA_CTRL_ADC12: + case RT5514_ANA_CTRL_ADC21: + case RT5514_ANA_CTRL_ADC22: + case RT5514_ANA_CTRL_ADC23: + case RT5514_ANA_CTRL_MICBST: + case RT5514_ANA_CTRL_ADCFED: + case RT5514_ANA_CTRL_INBUF: + case RT5514_ANA_CTRL_VREF: + case RT5514_ANA_CTRL_PLL3: + case RT5514_ANA_CTRL_PLL1_1: + case RT5514_ANA_CTRL_PLL1_2: + case RT5514_DMIC_LP_CTRL: + case RT5514_MISC_CTRL_DSP: + case RT5514_DSP_CTRL1: + case RT5514_DSP_CTRL3: + case RT5514_DSP_CTRL4: + case RT5514_VENDOR_ID1: + case RT5514_VENDOR_ID2: + return true; + + default: + return false; + } +} + +static bool rt5514_i2c_readable_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case RT5514_DSP_MAPPING | RT5514_RESET: + case RT5514_DSP_MAPPING | RT5514_PWR_ANA1: + case RT5514_DSP_MAPPING | RT5514_PWR_ANA2: + case RT5514_DSP_MAPPING | RT5514_I2S_CTRL1: + case RT5514_DSP_MAPPING | RT5514_I2S_CTRL2: + case RT5514_DSP_MAPPING | RT5514_VAD_CTRL6: + case RT5514_DSP_MAPPING | RT5514_EXT_VAD_CTRL: + case RT5514_DSP_MAPPING | RT5514_DIG_IO_CTRL: + case RT5514_DSP_MAPPING | RT5514_PAD_CTRL1: + case RT5514_DSP_MAPPING | RT5514_DMIC_DATA_CTRL: + case RT5514_DSP_MAPPING | RT5514_DIG_SOURCE_CTRL: + case RT5514_DSP_MAPPING | RT5514_SRC_CTRL: + case RT5514_DSP_MAPPING | RT5514_DOWNFILTER2_CTRL1: + case RT5514_DSP_MAPPING | RT5514_PLL_SOURCE_CTRL: + case RT5514_DSP_MAPPING | RT5514_CLK_CTRL1: + case RT5514_DSP_MAPPING | RT5514_CLK_CTRL2: + case RT5514_DSP_MAPPING | RT5514_PLL3_CALIB_CTRL1: + case RT5514_DSP_MAPPING | RT5514_PLL3_CALIB_CTRL5: + case RT5514_DSP_MAPPING | RT5514_DELAY_BUF_CTRL1: + case RT5514_DSP_MAPPING | RT5514_DELAY_BUF_CTRL3: + case RT5514_DSP_MAPPING | RT5514_ASRC_IN_CTRL1: + case RT5514_DSP_MAPPING | RT5514_DOWNFILTER0_CTRL1: + case RT5514_DSP_MAPPING | RT5514_DOWNFILTER0_CTRL2: + case RT5514_DSP_MAPPING | RT5514_DOWNFILTER0_CTRL3: + case RT5514_DSP_MAPPING | RT5514_DOWNFILTER1_CTRL1: + case RT5514_DSP_MAPPING | RT5514_DOWNFILTER1_CTRL2: + case RT5514_DSP_MAPPING | RT5514_DOWNFILTER1_CTRL3: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_LDO10: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_LDO18_16: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_ADC12: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_ADC21: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_ADC22: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_ADC23: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_MICBST: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_ADCFED: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_INBUF: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_VREF: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_PLL3: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_PLL1_1: + case RT5514_DSP_MAPPING | RT5514_ANA_CTRL_PLL1_2: + case RT5514_DSP_MAPPING | RT5514_DMIC_LP_CTRL: + case RT5514_DSP_MAPPING | RT5514_MISC_CTRL_DSP: + case RT5514_DSP_MAPPING | RT5514_DSP_CTRL1: + case RT5514_DSP_MAPPING | RT5514_DSP_CTRL3: + case RT5514_DSP_MAPPING | RT5514_DSP_CTRL4: + case RT5514_DSP_MAPPING | RT5514_VENDOR_ID1: + case RT5514_DSP_MAPPING | RT5514_VENDOR_ID2: + return true; + + default: + return false; + } +} + +/* {-3, 0, +3, +4.5, +7.5, +9.5, +12, +14, +17} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-300, 300, 0), + 3, 3, TLV_DB_SCALE_ITEM(450, 0, 0), + 4, 4, TLV_DB_SCALE_ITEM(750, 0, 0), + 5, 5, TLV_DB_SCALE_ITEM(950, 0, 0), + 6, 6, TLV_DB_SCALE_ITEM(1200, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(1400, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(1700, 0, 0) +); + +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1725, 75, 0); + +static int rt5514_dsp_voice_wake_up_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = rt5514->dsp_enabled; + + return 0; +} + +static int rt5514_calibration(struct rt5514_priv *rt5514, bool on) +{ + if (on) { + regmap_write(rt5514->regmap, RT5514_ANA_CTRL_PLL3, 0x0000000a); + regmap_update_bits(rt5514->regmap, RT5514_PLL_SOURCE_CTRL, 0xf, + 0xa); + regmap_update_bits(rt5514->regmap, RT5514_PWR_ANA1, 0x301, + 0x301); + regmap_write(rt5514->regmap, RT5514_PLL3_CALIB_CTRL4, + 0x80000000 | rt5514->pll3_cal_value); + regmap_write(rt5514->regmap, RT5514_PLL3_CALIB_CTRL1, + 0x8bb80800); + regmap_update_bits(rt5514->regmap, RT5514_PLL3_CALIB_CTRL5, + 0xc0000000, 0x80000000); + regmap_update_bits(rt5514->regmap, RT5514_PLL3_CALIB_CTRL5, + 0xc0000000, 0xc0000000); + } else { + regmap_update_bits(rt5514->regmap, RT5514_PLL3_CALIB_CTRL5, + 0xc0000000, 0x40000000); + regmap_update_bits(rt5514->regmap, RT5514_PWR_ANA1, 0x301, 0); + regmap_update_bits(rt5514->regmap, RT5514_PLL_SOURCE_CTRL, 0xf, + 0x4); + } + + return 0; +} + +static int rt5514_dsp_voice_wake_up_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + const struct firmware *fw = NULL; + u8 buf[8]; + + if (ucontrol->value.integer.value[0] == rt5514->dsp_enabled) + return 0; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + rt5514->dsp_enabled = ucontrol->value.integer.value[0]; + + if (rt5514->dsp_enabled) { + if (rt5514->pdata.dsp_calib_clk_name && + !IS_ERR(rt5514->dsp_calib_clk)) { + if (clk_set_rate(rt5514->dsp_calib_clk, + rt5514->pdata.dsp_calib_clk_rate)) + dev_err(component->dev, + "Can't set rate for mclk"); + + if (clk_prepare_enable(rt5514->dsp_calib_clk)) + dev_err(component->dev, + "Can't enable dsp_calib_clk"); + + rt5514_calibration(rt5514, true); + + msleep(20); +#if IS_ENABLED(CONFIG_SND_SOC_RT5514_SPI) + rt5514_spi_burst_read(RT5514_PLL3_CALIB_CTRL6 | + RT5514_DSP_MAPPING, buf, sizeof(buf)); +#else + dev_err(component->dev, "There is no SPI driver for" + " loading the firmware\n"); + memset(buf, 0, sizeof(buf)); +#endif + rt5514->pll3_cal_value = buf[0] | buf[1] << 8 | + buf[2] << 16 | buf[3] << 24; + + rt5514_calibration(rt5514, false); + clk_disable_unprepare(rt5514->dsp_calib_clk); + } + + rt5514_enable_dsp_prepare(rt5514); + + request_firmware(&fw, RT5514_FIRMWARE1, component->dev); + if (fw) { +#if IS_ENABLED(CONFIG_SND_SOC_RT5514_SPI) + rt5514_spi_burst_write(0x4ff60000, fw->data, + ((fw->size/8)+1)*8); +#else + dev_err(component->dev, "There is no SPI driver for" + " loading the firmware\n"); +#endif + release_firmware(fw); + fw = NULL; + } + + request_firmware(&fw, RT5514_FIRMWARE2, component->dev); + if (fw) { +#if IS_ENABLED(CONFIG_SND_SOC_RT5514_SPI) + rt5514_spi_burst_write(0x4ffc0000, fw->data, + ((fw->size/8)+1)*8); +#else + dev_err(component->dev, "There is no SPI driver for" + " loading the firmware\n"); +#endif + release_firmware(fw); + fw = NULL; + } + + /* DSP run */ + regmap_write(rt5514->i2c_regmap, 0x18002f00, + 0x00055148); + + if (rt5514->pdata.dsp_calib_clk_name && + !IS_ERR(rt5514->dsp_calib_clk)) { + msleep(20); + + regmap_write(rt5514->i2c_regmap, 0x1800211c, + rt5514->pll3_cal_value); + regmap_write(rt5514->i2c_regmap, 0x18002124, + 0x00220012); + regmap_write(rt5514->i2c_regmap, 0x18002124, + 0x80220042); + regmap_write(rt5514->i2c_regmap, 0x18002124, + 0xe0220042); + } + } else { + regmap_multi_reg_write(rt5514->i2c_regmap, + rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch)); + regcache_mark_dirty(rt5514->regmap); + regcache_sync(rt5514->regmap); + } + } + + return 1; +} + +static const struct snd_kcontrol_new rt5514_snd_controls[] = { + SOC_DOUBLE_TLV("MIC Boost Volume", RT5514_ANA_CTRL_MICBST, + RT5514_SEL_BSTL_SFT, RT5514_SEL_BSTR_SFT, 8, 0, bst_tlv), + SOC_DOUBLE_R_TLV("ADC1 Capture Volume", RT5514_DOWNFILTER0_CTRL1, + RT5514_DOWNFILTER0_CTRL2, RT5514_AD_GAIN_SFT, 63, 0, + adc_vol_tlv), + SOC_DOUBLE_R_TLV("ADC2 Capture Volume", RT5514_DOWNFILTER1_CTRL1, + RT5514_DOWNFILTER1_CTRL2, RT5514_AD_GAIN_SFT, 63, 0, + adc_vol_tlv), + SOC_SINGLE_EXT("DSP Voice Wake Up", SND_SOC_NOPM, 0, 1, 0, + rt5514_dsp_voice_wake_up_get, rt5514_dsp_voice_wake_up_put), +}; + +/* ADC Mixer*/ +static const struct snd_kcontrol_new rt5514_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("DMIC Switch", RT5514_DOWNFILTER0_CTRL1, + RT5514_AD_DMIC_MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("ADC Switch", RT5514_DOWNFILTER0_CTRL1, + RT5514_AD_AD_MIX_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5514_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("DMIC Switch", RT5514_DOWNFILTER0_CTRL2, + RT5514_AD_DMIC_MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("ADC Switch", RT5514_DOWNFILTER0_CTRL2, + RT5514_AD_AD_MIX_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5514_sto2_adc_l_mix[] = { + SOC_DAPM_SINGLE("DMIC Switch", RT5514_DOWNFILTER1_CTRL1, + RT5514_AD_DMIC_MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("ADC Switch", RT5514_DOWNFILTER1_CTRL1, + RT5514_AD_AD_MIX_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5514_sto2_adc_r_mix[] = { + SOC_DAPM_SINGLE("DMIC Switch", RT5514_DOWNFILTER1_CTRL2, + RT5514_AD_DMIC_MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("ADC Switch", RT5514_DOWNFILTER1_CTRL2, + RT5514_AD_AD_MIX_BIT, 1, 1), +}; + +/* DMIC Source */ +static const char * const rt5514_dmic_src[] = { + "DMIC1", "DMIC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5514_stereo1_dmic_enum, RT5514_DIG_SOURCE_CTRL, + RT5514_AD0_DMIC_INPUT_SEL_SFT, rt5514_dmic_src); + +static const struct snd_kcontrol_new rt5514_sto1_dmic_mux = + SOC_DAPM_ENUM("Stereo1 DMIC Source", rt5514_stereo1_dmic_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5514_stereo2_dmic_enum, RT5514_DIG_SOURCE_CTRL, + RT5514_AD1_DMIC_INPUT_SEL_SFT, rt5514_dmic_src); + +static const struct snd_kcontrol_new rt5514_sto2_dmic_mux = + SOC_DAPM_ENUM("Stereo2 DMIC Source", rt5514_stereo2_dmic_enum); + +/** + * rt5514_calc_dmic_clk - Calculate the frequency divider parameter of dmic. + * + * @rate: base clock rate. + * + * Choose divider parameter that gives the highest possible DMIC frequency in + * 1MHz - 3MHz range. + */ +static int rt5514_calc_dmic_clk(struct snd_soc_component *component, int rate) +{ + int div[] = {2, 3, 4, 8, 12, 16, 24, 32}; + int i; + + if (rate < 1000000 * div[0]) { + pr_warn("Base clock rate %d is too low\n", rate); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(div); i++) { + /* find divider that gives DMIC frequency below 3.072MHz */ + if (3072000 * div[i] >= rate) + return i; + } + + dev_warn(component->dev, "Base clock rate %d is too high\n", rate); + return -EINVAL; +} + +static int rt5514_set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + int idx; + + idx = rt5514_calc_dmic_clk(component, rt5514->sysclk); + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else + regmap_update_bits(rt5514->regmap, RT5514_CLK_CTRL1, + RT5514_CLK_DMIC_OUT_SEL_MASK, + idx << RT5514_CLK_DMIC_OUT_SEL_SFT); + + if (rt5514->pdata.dmic_init_delay) + msleep(rt5514->pdata.dmic_init_delay); + + return idx; +} + +static int rt5514_is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + + if (rt5514->sysclk_src == RT5514_SCLK_S_PLL1) + return 1; + else + return 0; +} + +static int rt5514_i2s_use_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + + return (rt5514->sysclk > rt5514->lrck * 384); +} + +static const struct snd_soc_dapm_widget rt5514_dapm_widgets[] = { + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC1L"), + SND_SOC_DAPM_INPUT("DMIC1R"), + SND_SOC_DAPM_INPUT("DMIC2L"), + SND_SOC_DAPM_INPUT("DMIC2R"), + + SND_SOC_DAPM_INPUT("AMICL"), + SND_SOC_DAPM_INPUT("AMICR"), + + SND_SOC_DAPM_PGA("DMIC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC2", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("DMIC CLK", 1, SND_SOC_NOPM, 0, 0, + rt5514_set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_SUPPLY("ADC CLK", RT5514_CLK_CTRL1, + RT5514_CLK_AD_ANA1_EN_BIT, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("LDO18 IN", RT5514_PWR_ANA1, + RT5514_POW_LDO18_IN_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO18 ADC", RT5514_PWR_ANA1, + RT5514_POW_LDO18_ADC_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO21", RT5514_PWR_ANA1, RT5514_POW_LDO21_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("BG LDO18 IN", RT5514_PWR_ANA1, + RT5514_POW_BG_LDO18_IN_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BG LDO21", RT5514_PWR_ANA1, + RT5514_POW_BG_LDO21_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BG MBIAS", RT5514_PWR_ANA2, + RT5514_POW_BG_MBIAS_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MBIAS", RT5514_PWR_ANA2, RT5514_POW_MBIAS_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF2", RT5514_PWR_ANA2, RT5514_POW_VREF2_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("VREF1", RT5514_PWR_ANA2, RT5514_POW_VREF1_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Power", SND_SOC_NOPM, 0, 0, NULL, 0), + + + SND_SOC_DAPM_SUPPLY("LDO16L", RT5514_PWR_ANA2, RT5514_POWL_LDO16_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1L", RT5514_PWR_ANA2, RT5514_POW_ADC1_L_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("BSTL2", RT5514_PWR_ANA2, RT5514_POW2_BSTL_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("BSTL", RT5514_PWR_ANA2, RT5514_POW_BSTL_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("ADCFEDL", RT5514_PWR_ANA2, RT5514_POW_ADCFEDL_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADCL Power", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("LDO16R", RT5514_PWR_ANA2, RT5514_POWR_LDO16_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1R", RT5514_PWR_ANA2, RT5514_POW_ADC1_R_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("BSTR2", RT5514_PWR_ANA2, RT5514_POW2_BSTR_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("BSTR", RT5514_PWR_ANA2, RT5514_POW_BSTR_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("ADCFEDR", RT5514_PWR_ANA2, RT5514_POW_ADCFEDR_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADCR Power", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("PLL1 LDO ENABLE", RT5514_ANA_CTRL_PLL1_2, + RT5514_EN_LDO_PLL1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1 LDO", RT5514_PWR_ANA2, + RT5514_POW_PLL1_LDO_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", RT5514_PWR_ANA2, RT5514_POW_PLL1_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ASRC AD1", 1, RT5514_CLK_CTRL2, + RT5514_CLK_AD0_ASRC_EN_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ASRC AD2", 1, RT5514_CLK_CTRL2, + RT5514_CLK_AD1_ASRC_EN_BIT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5514_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo2 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5514_sto2_dmic_mux), + + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("adc stereo1 filter", RT5514_CLK_CTRL1, + RT5514_CLK_AD0_EN_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("adc stereo2 filter", RT5514_CLK_CTRL1, + RT5514_CLK_AD1_EN_BIT, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("Sto1 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5514_sto1_adc_l_mix, ARRAY_SIZE(rt5514_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto1 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5514_sto1_adc_r_mix, ARRAY_SIZE(rt5514_sto1_adc_r_mix)), + SND_SOC_DAPM_MIXER("Sto2 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5514_sto2_adc_l_mix, ARRAY_SIZE(rt5514_sto2_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto2 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5514_sto2_adc_r_mix, ARRAY_SIZE(rt5514_sto2_adc_r_mix)), + + SND_SOC_DAPM_ADC("Stereo1 ADC MIXL", NULL, RT5514_DOWNFILTER0_CTRL1, + RT5514_AD_AD_MUTE_BIT, 1), + SND_SOC_DAPM_ADC("Stereo1 ADC MIXR", NULL, RT5514_DOWNFILTER0_CTRL2, + RT5514_AD_AD_MUTE_BIT, 1), + SND_SOC_DAPM_ADC("Stereo2 ADC MIXL", NULL, RT5514_DOWNFILTER1_CTRL1, + RT5514_AD_AD_MUTE_BIT, 1), + SND_SOC_DAPM_ADC("Stereo2 ADC MIXR", NULL, RT5514_DOWNFILTER1_CTRL2, + RT5514_AD_AD_MUTE_BIT, 1), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route rt5514_dapm_routes[] = { + { "DMIC1", NULL, "DMIC1L" }, + { "DMIC1", NULL, "DMIC1R" }, + { "DMIC2", NULL, "DMIC2L" }, + { "DMIC2", NULL, "DMIC2R" }, + + { "DMIC1L", NULL, "DMIC CLK" }, + { "DMIC1R", NULL, "DMIC CLK" }, + { "DMIC2L", NULL, "DMIC CLK" }, + { "DMIC2R", NULL, "DMIC CLK" }, + + { "Stereo1 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo1 DMIC Mux", "DMIC2", "DMIC2" }, + + { "Sto1 ADC MIXL", "DMIC Switch", "Stereo1 DMIC Mux" }, + { "Sto1 ADC MIXL", "ADC Switch", "AMICL" }, + { "Sto1 ADC MIXR", "DMIC Switch", "Stereo1 DMIC Mux" }, + { "Sto1 ADC MIXR", "ADC Switch", "AMICR" }, + + { "ADC Power", NULL, "LDO18 IN" }, + { "ADC Power", NULL, "LDO18 ADC" }, + { "ADC Power", NULL, "LDO21" }, + { "ADC Power", NULL, "BG LDO18 IN" }, + { "ADC Power", NULL, "BG LDO21" }, + { "ADC Power", NULL, "BG MBIAS" }, + { "ADC Power", NULL, "MBIAS" }, + { "ADC Power", NULL, "VREF2" }, + { "ADC Power", NULL, "VREF1" }, + + { "ADCL Power", NULL, "LDO16L" }, + { "ADCL Power", NULL, "ADC1L" }, + { "ADCL Power", NULL, "BSTL2" }, + { "ADCL Power", NULL, "BSTL" }, + { "ADCL Power", NULL, "ADCFEDL" }, + + { "ADCR Power", NULL, "LDO16R" }, + { "ADCR Power", NULL, "ADC1R" }, + { "ADCR Power", NULL, "BSTR2" }, + { "ADCR Power", NULL, "BSTR" }, + { "ADCR Power", NULL, "ADCFEDR" }, + + { "AMICL", NULL, "ADC CLK" }, + { "AMICL", NULL, "ADC Power" }, + { "AMICL", NULL, "ADCL Power" }, + { "AMICR", NULL, "ADC CLK" }, + { "AMICR", NULL, "ADC Power" }, + { "AMICR", NULL, "ADCR Power" }, + + { "PLL1 LDO", NULL, "PLL1 LDO ENABLE" }, + { "PLL1", NULL, "PLL1 LDO" }, + + { "Stereo1 ADC MIXL", NULL, "Sto1 ADC MIXL" }, + { "Stereo1 ADC MIXR", NULL, "Sto1 ADC MIXR" }, + + { "Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXL" }, + { "Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXR" }, + { "Stereo1 ADC MIX", NULL, "adc stereo1 filter" }, + { "adc stereo1 filter", NULL, "PLL1", rt5514_is_sys_clk_from_pll }, + { "adc stereo1 filter", NULL, "ASRC AD1", rt5514_i2s_use_asrc }, + + { "Stereo2 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo2 DMIC Mux", "DMIC2", "DMIC2" }, + + { "Sto2 ADC MIXL", "DMIC Switch", "Stereo2 DMIC Mux" }, + { "Sto2 ADC MIXL", "ADC Switch", "AMICL" }, + { "Sto2 ADC MIXR", "DMIC Switch", "Stereo2 DMIC Mux" }, + { "Sto2 ADC MIXR", "ADC Switch", "AMICR" }, + + { "Stereo2 ADC MIXL", NULL, "Sto2 ADC MIXL" }, + { "Stereo2 ADC MIXR", NULL, "Sto2 ADC MIXR" }, + + { "Stereo2 ADC MIX", NULL, "Stereo2 ADC MIXL" }, + { "Stereo2 ADC MIX", NULL, "Stereo2 ADC MIXR" }, + { "Stereo2 ADC MIX", NULL, "adc stereo2 filter" }, + { "adc stereo2 filter", NULL, "PLL1", rt5514_is_sys_clk_from_pll }, + { "adc stereo2 filter", NULL, "ASRC AD2", rt5514_i2s_use_asrc }, + + { "AIF1TX", NULL, "Stereo1 ADC MIX"}, + { "AIF1TX", NULL, "Stereo2 ADC MIX"}, +}; + +static int rt5514_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + int pre_div, bclk_ms, frame_size; + unsigned int val_len = 0; + + rt5514->lrck = params_rate(params); + pre_div = rl6231_get_clk_info(rt5514->sysclk, rt5514->lrck); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting\n"); + return -EINVAL; + } + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + + bclk_ms = frame_size > 32; + rt5514->bclk = rt5514->lrck * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5514->bclk, rt5514->lrck); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val_len = RT5514_I2S_DL_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val_len = RT5514_I2S_DL_24; + break; + case SNDRV_PCM_FORMAT_S8: + val_len = RT5514_I2S_DL_8; + break; + default: + return -EINVAL; + } + + regmap_update_bits(rt5514->regmap, RT5514_I2S_CTRL1, RT5514_I2S_DL_MASK, + val_len); + regmap_update_bits(rt5514->regmap, RT5514_CLK_CTRL1, + RT5514_CLK_AD_ANA1_SEL_MASK, + (pre_div + 1) << RT5514_CLK_AD_ANA1_SEL_SFT); + regmap_update_bits(rt5514->regmap, RT5514_CLK_CTRL2, + RT5514_CLK_SYS_DIV_OUT_MASK | RT5514_SEL_ADC_OSR_MASK, + pre_div << RT5514_CLK_SYS_DIV_OUT_SFT | + pre_div << RT5514_SEL_ADC_OSR_SFT); + + return 0; +} + +static int rt5514_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_NB_IF: + reg_val |= RT5514_I2S_LR_INV; + break; + + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5514_I2S_BP_INV; + break; + + case SND_SOC_DAIFMT_IB_IF: + reg_val |= RT5514_I2S_BP_INV | RT5514_I2S_LR_INV; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5514_I2S_DF_LEFT; + break; + + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5514_I2S_DF_PCM_A; + break; + + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5514_I2S_DF_PCM_B; + break; + + default: + return -EINVAL; + } + + regmap_update_bits(rt5514->regmap, RT5514_I2S_CTRL1, + RT5514_I2S_DF_MASK | RT5514_I2S_BP_MASK | RT5514_I2S_LR_MASK, + reg_val); + + return 0; +} + +static int rt5514_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + if (freq == rt5514->sysclk && clk_id == rt5514->sysclk_src) + return 0; + + switch (clk_id) { + case RT5514_SCLK_S_MCLK: + reg_val |= RT5514_CLK_SYS_PRE_SEL_MCLK; + break; + + case RT5514_SCLK_S_PLL1: + reg_val |= RT5514_CLK_SYS_PRE_SEL_PLL; + break; + + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + regmap_update_bits(rt5514->regmap, RT5514_CLK_CTRL2, + RT5514_CLK_SYS_PRE_SEL_MASK, reg_val); + + rt5514->sysclk = freq; + rt5514->sysclk_src = clk_id; + + dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + + return 0; +} + +static int rt5514_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5514->pll_in = 0; + rt5514->pll_out = 0; + regmap_update_bits(rt5514->regmap, RT5514_CLK_CTRL2, + RT5514_CLK_SYS_PRE_SEL_MASK, + RT5514_CLK_SYS_PRE_SEL_MCLK); + + return 0; + } + + if (source == rt5514->pll_src && freq_in == rt5514->pll_in && + freq_out == rt5514->pll_out) + return 0; + + switch (source) { + case RT5514_PLL1_S_MCLK: + regmap_update_bits(rt5514->regmap, RT5514_PLL_SOURCE_CTRL, + RT5514_PLL_1_SEL_MASK, RT5514_PLL_1_SEL_MCLK); + break; + + case RT5514_PLL1_S_BCLK: + regmap_update_bits(rt5514->regmap, RT5514_PLL_SOURCE_CTRL, + RT5514_PLL_1_SEL_MASK, RT5514_PLL_1_SEL_SCLK); + break; + + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + regmap_write(rt5514->regmap, RT5514_ANA_CTRL_PLL1_1, + pll_code.k_code << RT5514_PLL_K_SFT | + pll_code.n_code << RT5514_PLL_N_SFT | + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5514_PLL_M_SFT); + regmap_update_bits(rt5514->regmap, RT5514_ANA_CTRL_PLL1_2, + RT5514_PLL_M_BP, pll_code.m_bp << RT5514_PLL_M_BP_SFT); + + rt5514->pll_in = freq_in; + rt5514->pll_out = freq_out; + rt5514->pll_src = source; + + return 0; +} + +static int rt5514_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + unsigned int val = 0, val2 = 0; + + if (rx_mask || tx_mask) + val |= RT5514_TDM_MODE; + + switch (tx_mask) { + case 0x3: + val2 |= RT5514_TDM_DOCKING_MODE | RT5514_TDM_DOCKING_VALID_CH2 | + RT5514_TDM_DOCKING_START_SLOT0; + break; + + case 0x30: + val2 |= RT5514_TDM_DOCKING_MODE | RT5514_TDM_DOCKING_VALID_CH2 | + RT5514_TDM_DOCKING_START_SLOT4; + break; + + case 0xf: + val2 |= RT5514_TDM_DOCKING_MODE | RT5514_TDM_DOCKING_VALID_CH4 | + RT5514_TDM_DOCKING_START_SLOT0; + break; + + case 0xf0: + val2 |= RT5514_TDM_DOCKING_MODE | RT5514_TDM_DOCKING_VALID_CH4 | + RT5514_TDM_DOCKING_START_SLOT4; + break; + + default: + break; + } + + + + switch (slots) { + case 4: + val |= RT5514_TDMSLOT_SEL_RX_4CH | RT5514_TDMSLOT_SEL_TX_4CH; + break; + + case 6: + val |= RT5514_TDMSLOT_SEL_RX_6CH | RT5514_TDMSLOT_SEL_TX_6CH; + break; + + case 8: + val |= RT5514_TDMSLOT_SEL_RX_8CH | RT5514_TDMSLOT_SEL_TX_8CH; + break; + + case 2: + default: + break; + } + + switch (slot_width) { + case 20: + val |= RT5514_CH_LEN_RX_20 | RT5514_CH_LEN_TX_20; + break; + + case 24: + val |= RT5514_CH_LEN_RX_24 | RT5514_CH_LEN_TX_24; + break; + + case 25: + val |= RT5514_TDM_MODE2; + break; + + case 32: + val |= RT5514_CH_LEN_RX_32 | RT5514_CH_LEN_TX_32; + break; + + case 16: + default: + break; + } + + regmap_update_bits(rt5514->regmap, RT5514_I2S_CTRL1, RT5514_TDM_MODE | + RT5514_TDMSLOT_SEL_RX_MASK | RT5514_TDMSLOT_SEL_TX_MASK | + RT5514_CH_LEN_RX_MASK | RT5514_CH_LEN_TX_MASK | + RT5514_TDM_MODE2, val); + + regmap_update_bits(rt5514->regmap, RT5514_I2S_CTRL2, + RT5514_TDM_DOCKING_MODE | RT5514_TDM_DOCKING_VALID_CH_MASK | + RT5514_TDM_DOCKING_START_MASK, val2); + + return 0; +} + +static int rt5514_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (IS_ERR(rt5514->mclk)) + break; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(rt5514->mclk); + } else { + ret = clk_prepare_enable(rt5514->mclk); + if (ret) + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* + * If the DSP is enabled in start of recording, the DSP + * should be disabled, and sync back to normal recording + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { + rt5514->dsp_enabled = 0; + regmap_multi_reg_write(rt5514->i2c_regmap, + rt5514_i2c_patch, + ARRAY_SIZE(rt5514_i2c_patch)); + regcache_mark_dirty(rt5514->regmap); + regcache_sync(rt5514->regmap); + } + } + break; + + default: + break; + } + + return 0; +} + +static int rt5514_probe(struct snd_soc_component *component) +{ + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + struct platform_device *pdev = container_of(component->dev, + struct platform_device, dev); + + rt5514->mclk = devm_clk_get(component->dev, "mclk"); + if (PTR_ERR(rt5514->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (rt5514->pdata.dsp_calib_clk_name) { + rt5514->dsp_calib_clk = devm_clk_get(&pdev->dev, + rt5514->pdata.dsp_calib_clk_name); + if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + + rt5514->component = component; + rt5514->pll3_cal_value = 0x0078b000; + + return 0; +} + +static int rt5514_i2c_read(void *context, unsigned int reg, unsigned int *val) +{ + struct i2c_client *client = context; + struct rt5514_priv *rt5514 = i2c_get_clientdata(client); + + regmap_read(rt5514->i2c_regmap, reg | RT5514_DSP_MAPPING, val); + + return 0; +} + +static int rt5514_i2c_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; + struct rt5514_priv *rt5514 = i2c_get_clientdata(client); + + regmap_write(rt5514->i2c_regmap, reg | RT5514_DSP_MAPPING, val); + + return 0; +} + +#define RT5514_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5514_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5514_aif_dai_ops = { + .hw_params = rt5514_hw_params, + .set_fmt = rt5514_set_dai_fmt, + .set_sysclk = rt5514_set_dai_sysclk, + .set_pll = rt5514_set_dai_pll, + .set_tdm_slot = rt5514_set_tdm_slot, +}; + +static struct snd_soc_dai_driver rt5514_dai[] = { + { + .name = "rt5514-aif1", + .id = 0, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 4, + .rates = RT5514_STEREO_RATES, + .formats = RT5514_FORMATS, + }, + .ops = &rt5514_aif_dai_ops, + } +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5514 = { + .probe = rt5514_probe, + .set_bias_level = rt5514_set_bias_level, + .controls = rt5514_snd_controls, + .num_controls = ARRAY_SIZE(rt5514_snd_controls), + .dapm_widgets = rt5514_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5514_dapm_widgets), + .dapm_routes = rt5514_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5514_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5514_i2c_regmap = { + .name = "i2c", + .reg_bits = 32, + .val_bits = 32, + + .readable_reg = rt5514_i2c_readable_register, + + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_config rt5514_regmap = { + .reg_bits = 16, + .val_bits = 32, + + .max_register = RT5514_VENDOR_ID2, + .volatile_reg = rt5514_volatile_register, + .readable_reg = rt5514_readable_register, + .reg_read = rt5514_i2c_read, + .reg_write = rt5514_i2c_write, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5514_reg, + .num_reg_defaults = ARRAY_SIZE(rt5514_reg), + .use_single_rw = true, +}; + +static const struct i2c_device_id rt5514_i2c_id[] = { + { "rt5514", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5514_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id rt5514_of_match[] = { + { .compatible = "realtek,rt5514", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5514_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5514_acpi_match[] = { + { "10EC5514", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt5514_acpi_match); +#endif + +static int rt5514_parse_dp(struct rt5514_priv *rt5514, struct device *dev) +{ + device_property_read_u32(dev, "realtek,dmic-init-delay-ms", + &rt5514->pdata.dmic_init_delay); + device_property_read_string(dev, "realtek,dsp-calib-clk-name", + &rt5514->pdata.dsp_calib_clk_name); + device_property_read_u32(dev, "realtek,dsp-calib-clk-rate", + &rt5514->pdata.dsp_calib_clk_rate); + + return 0; +} + +static __maybe_unused int rt5514_i2c_resume(struct device *dev) +{ + struct rt5514_priv *rt5514 = dev_get_drvdata(dev); + unsigned int val; + + /* + * Add a bogus read to avoid rt5514's confusion after s2r in case it + * saw glitches on the i2c lines and thought the other side sent a + * start bit. + */ + regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + + return 0; +} + +static int rt5514_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5514_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5514_priv *rt5514; + int ret; + unsigned int val = ~0; + + rt5514 = devm_kzalloc(&i2c->dev, sizeof(struct rt5514_priv), + GFP_KERNEL); + if (rt5514 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5514); + + if (pdata) + rt5514->pdata = *pdata; + else + rt5514_parse_dp(rt5514, &i2c->dev); + + rt5514->i2c_regmap = devm_regmap_init_i2c(i2c, &rt5514_i2c_regmap); + if (IS_ERR(rt5514->i2c_regmap)) { + ret = PTR_ERR(rt5514->i2c_regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + rt5514->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt5514_regmap); + if (IS_ERR(rt5514->regmap)) { + ret = PTR_ERR(rt5514->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + /* + * The rt5514 can get confused if the i2c lines glitch together, as + * can happen at bootup as regulators are turned off and on. If it's + * in this glitched state the first i2c read will fail, so we'll give + * it one change to retry. + */ + ret = regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + if (ret || val != RT5514_DEVICE_ID) + ret = regmap_read(rt5514->regmap, RT5514_VENDOR_ID2, &val); + if (ret || val != RT5514_DEVICE_ID) { + dev_err(&i2c->dev, + "Device with ID register %x is not rt5514\n", val); + return -ENODEV; + } + + ret = regmap_multi_reg_write(rt5514->i2c_regmap, rt5514_i2c_patch, + ARRAY_SIZE(rt5514_i2c_patch)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply i2c_regmap patch: %d\n", + ret); + + ret = regmap_register_patch(rt5514->regmap, rt5514_patch, + ARRAY_SIZE(rt5514_patch)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5514, + rt5514_dai, ARRAY_SIZE(rt5514_dai)); +} + +static const struct dev_pm_ops rt5514_i2_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(NULL, rt5514_i2c_resume) +}; + +static struct i2c_driver rt5514_i2c_driver = { + .driver = { + .name = "rt5514", + .acpi_match_table = ACPI_PTR(rt5514_acpi_match), + .of_match_table = of_match_ptr(rt5514_of_match), + .pm = &rt5514_i2_pm_ops, + }, + .probe = rt5514_i2c_probe, + .id_table = rt5514_i2c_id, +}; +module_i2c_driver(rt5514_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5514 driver"); +MODULE_AUTHOR("Oder Chiou "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5514.h b/sound/soc/codecs/rt5514.h new file mode 100644 index 000000000..d1ef0b3f5 --- /dev/null +++ b/sound/soc/codecs/rt5514.h @@ -0,0 +1,289 @@ +/* + * rt5514.h -- RT5514 ALSA SoC audio driver + * + * Copyright 2015 Realtek Microelectronics + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5514_H__ +#define __RT5514_H__ + +#include +#include + +#define RT5514_DEVICE_ID 0x10ec5514 + +#define RT5514_RESET 0x2000 +#define RT5514_PWR_ANA1 0x2004 +#define RT5514_PWR_ANA2 0x2008 +#define RT5514_I2S_CTRL1 0x2010 +#define RT5514_I2S_CTRL2 0x2014 +#define RT5514_VAD_CTRL6 0x2030 +#define RT5514_EXT_VAD_CTRL 0x206c +#define RT5514_DIG_IO_CTRL 0x2070 +#define RT5514_PAD_CTRL1 0x2080 +#define RT5514_DMIC_DATA_CTRL 0x20a0 +#define RT5514_DIG_SOURCE_CTRL 0x20a4 +#define RT5514_SRC_CTRL 0x20ac +#define RT5514_DOWNFILTER2_CTRL1 0x20d0 +#define RT5514_PLL_SOURCE_CTRL 0x2100 +#define RT5514_CLK_CTRL1 0x2104 +#define RT5514_CLK_CTRL2 0x2108 +#define RT5514_PLL3_CALIB_CTRL1 0x2110 +#define RT5514_PLL3_CALIB_CTRL4 0x2120 +#define RT5514_PLL3_CALIB_CTRL5 0x2124 +#define RT5514_PLL3_CALIB_CTRL6 0x2128 +#define RT5514_DELAY_BUF_CTRL1 0x2140 +#define RT5514_DELAY_BUF_CTRL3 0x2148 +#define RT5514_ASRC_IN_CTRL1 0x2180 +#define RT5514_DOWNFILTER0_CTRL1 0x2190 +#define RT5514_DOWNFILTER0_CTRL2 0x2194 +#define RT5514_DOWNFILTER0_CTRL3 0x2198 +#define RT5514_DOWNFILTER1_CTRL1 0x21a0 +#define RT5514_DOWNFILTER1_CTRL2 0x21a4 +#define RT5514_DOWNFILTER1_CTRL3 0x21a8 +#define RT5514_ANA_CTRL_LDO10 0x2200 +#define RT5514_ANA_CTRL_LDO18_16 0x2204 +#define RT5514_ANA_CTRL_ADC12 0x2210 +#define RT5514_ANA_CTRL_ADC21 0x2214 +#define RT5514_ANA_CTRL_ADC22 0x2218 +#define RT5514_ANA_CTRL_ADC23 0x221c +#define RT5514_ANA_CTRL_MICBST 0x2220 +#define RT5514_ANA_CTRL_ADCFED 0x2224 +#define RT5514_ANA_CTRL_INBUF 0x2228 +#define RT5514_ANA_CTRL_VREF 0x222c +#define RT5514_ANA_CTRL_PLL3 0x2240 +#define RT5514_ANA_CTRL_PLL1_1 0x2260 +#define RT5514_ANA_CTRL_PLL1_2 0x2264 +#define RT5514_DMIC_LP_CTRL 0x2e00 +#define RT5514_MISC_CTRL_DSP 0x2e04 +#define RT5514_DSP_CTRL1 0x2f00 +#define RT5514_DSP_CTRL3 0x2f08 +#define RT5514_DSP_CTRL4 0x2f10 +#define RT5514_VENDOR_ID1 0x2ff0 +#define RT5514_VENDOR_ID2 0x2ff4 + +#define RT5514_DSP_MAPPING 0x18000000 + +/* RT5514_PWR_ANA1 (0x2004) */ +#define RT5514_POW_LDO18_IN (0x1 << 5) +#define RT5514_POW_LDO18_IN_BIT 5 +#define RT5514_POW_LDO18_ADC (0x1 << 4) +#define RT5514_POW_LDO18_ADC_BIT 4 +#define RT5514_POW_LDO21 (0x1 << 3) +#define RT5514_POW_LDO21_BIT 3 +#define RT5514_POW_BG_LDO18_IN (0x1 << 2) +#define RT5514_POW_BG_LDO18_IN_BIT 2 +#define RT5514_POW_BG_LDO21 (0x1 << 1) +#define RT5514_POW_BG_LDO21_BIT 1 + +/* RT5514_PWR_ANA2 (0x2008) */ +#define RT5514_POW_PLL1 (0x1 << 18) +#define RT5514_POW_PLL1_BIT 18 +#define RT5514_POW_PLL1_LDO (0x1 << 16) +#define RT5514_POW_PLL1_LDO_BIT 16 +#define RT5514_POW_BG_MBIAS (0x1 << 15) +#define RT5514_POW_BG_MBIAS_BIT 15 +#define RT5514_POW_MBIAS (0x1 << 14) +#define RT5514_POW_MBIAS_BIT 14 +#define RT5514_POW_VREF2 (0x1 << 13) +#define RT5514_POW_VREF2_BIT 13 +#define RT5514_POW_VREF1 (0x1 << 12) +#define RT5514_POW_VREF1_BIT 12 +#define RT5514_POWR_LDO16 (0x1 << 11) +#define RT5514_POWR_LDO16_BIT 11 +#define RT5514_POWL_LDO16 (0x1 << 10) +#define RT5514_POWL_LDO16_BIT 10 +#define RT5514_POW_ADC2 (0x1 << 9) +#define RT5514_POW_ADC2_BIT 9 +#define RT5514_POW_INPUT_BUF (0x1 << 8) +#define RT5514_POW_INPUT_BUF_BIT 8 +#define RT5514_POW_ADC1_R (0x1 << 7) +#define RT5514_POW_ADC1_R_BIT 7 +#define RT5514_POW_ADC1_L (0x1 << 6) +#define RT5514_POW_ADC1_L_BIT 6 +#define RT5514_POW2_BSTR (0x1 << 5) +#define RT5514_POW2_BSTR_BIT 5 +#define RT5514_POW2_BSTL (0x1 << 4) +#define RT5514_POW2_BSTL_BIT 4 +#define RT5514_POW_BSTR (0x1 << 3) +#define RT5514_POW_BSTR_BIT 3 +#define RT5514_POW_BSTL (0x1 << 2) +#define RT5514_POW_BSTL_BIT 2 +#define RT5514_POW_ADCFEDR (0x1 << 1) +#define RT5514_POW_ADCFEDR_BIT 1 +#define RT5514_POW_ADCFEDL (0x1 << 0) +#define RT5514_POW_ADCFEDL_BIT 0 + +/* RT5514_I2S_CTRL1 (0x2010) */ +#define RT5514_TDM_MODE2 (0x1 << 30) +#define RT5514_TDM_MODE2_SFT 30 +#define RT5514_TDM_MODE (0x1 << 28) +#define RT5514_TDM_MODE_SFT 28 +#define RT5514_I2S_LR_MASK (0x1 << 26) +#define RT5514_I2S_LR_SFT 26 +#define RT5514_I2S_LR_NOR (0x0 << 26) +#define RT5514_I2S_LR_INV (0x1 << 26) +#define RT5514_I2S_BP_MASK (0x1 << 25) +#define RT5514_I2S_BP_SFT 25 +#define RT5514_I2S_BP_NOR (0x0 << 25) +#define RT5514_I2S_BP_INV (0x1 << 25) +#define RT5514_I2S_DF_MASK (0x7 << 16) +#define RT5514_I2S_DF_SFT 16 +#define RT5514_I2S_DF_I2S (0x0 << 16) +#define RT5514_I2S_DF_LEFT (0x1 << 16) +#define RT5514_I2S_DF_PCM_A (0x2 << 16) +#define RT5514_I2S_DF_PCM_B (0x3 << 16) +#define RT5514_TDMSLOT_SEL_RX_MASK (0x3 << 10) +#define RT5514_TDMSLOT_SEL_RX_SFT 10 +#define RT5514_TDMSLOT_SEL_RX_4CH (0x1 << 10) +#define RT5514_TDMSLOT_SEL_RX_6CH (0x2 << 10) +#define RT5514_TDMSLOT_SEL_RX_8CH (0x3 << 10) +#define RT5514_CH_LEN_RX_MASK (0x3 << 8) +#define RT5514_CH_LEN_RX_SFT 8 +#define RT5514_CH_LEN_RX_16 (0x0 << 8) +#define RT5514_CH_LEN_RX_20 (0x1 << 8) +#define RT5514_CH_LEN_RX_24 (0x2 << 8) +#define RT5514_CH_LEN_RX_32 (0x3 << 8) +#define RT5514_TDMSLOT_SEL_TX_MASK (0x3 << 6) +#define RT5514_TDMSLOT_SEL_TX_SFT 6 +#define RT5514_TDMSLOT_SEL_TX_4CH (0x1 << 6) +#define RT5514_TDMSLOT_SEL_TX_6CH (0x2 << 6) +#define RT5514_TDMSLOT_SEL_TX_8CH (0x3 << 6) +#define RT5514_CH_LEN_TX_MASK (0x3 << 4) +#define RT5514_CH_LEN_TX_SFT 4 +#define RT5514_CH_LEN_TX_16 (0x0 << 4) +#define RT5514_CH_LEN_TX_20 (0x1 << 4) +#define RT5514_CH_LEN_TX_24 (0x2 << 4) +#define RT5514_CH_LEN_TX_32 (0x3 << 4) +#define RT5514_I2S_DL_MASK (0x3 << 0) +#define RT5514_I2S_DL_SFT 0 +#define RT5514_I2S_DL_16 (0x0 << 0) +#define RT5514_I2S_DL_20 (0x1 << 0) +#define RT5514_I2S_DL_24 (0x2 << 0) +#define RT5514_I2S_DL_8 (0x3 << 0) + +/* RT5514_I2S_CTRL2 (0x2014) */ +#define RT5514_TDM_DOCKING_MODE (0x1 << 31) +#define RT5514_TDM_DOCKING_MODE_SFT 31 +#define RT5514_TDM_DOCKING_VALID_CH_MASK (0x1 << 29) +#define RT5514_TDM_DOCKING_VALID_CH_SFT 29 +#define RT5514_TDM_DOCKING_VALID_CH2 (0x0 << 29) +#define RT5514_TDM_DOCKING_VALID_CH4 (0x1 << 29) +#define RT5514_TDM_DOCKING_START_MASK (0x1 << 28) +#define RT5514_TDM_DOCKING_START_SFT 28 +#define RT5514_TDM_DOCKING_START_SLOT0 (0x0 << 28) +#define RT5514_TDM_DOCKING_START_SLOT4 (0x1 << 28) + +/* RT5514_DIG_SOURCE_CTRL (0x20a4) */ +#define RT5514_AD1_DMIC_INPUT_SEL (0x1 << 1) +#define RT5514_AD1_DMIC_INPUT_SEL_SFT 1 +#define RT5514_AD0_DMIC_INPUT_SEL (0x1 << 0) +#define RT5514_AD0_DMIC_INPUT_SEL_SFT 0 + +/* RT5514_PLL_SOURCE_CTRL (0x2100) */ +#define RT5514_PLL_1_SEL_MASK (0x7 << 12) +#define RT5514_PLL_1_SEL_SFT 12 +#define RT5514_PLL_1_SEL_SCLK (0x3 << 12) +#define RT5514_PLL_1_SEL_MCLK (0x4 << 12) + +/* RT5514_CLK_CTRL1 (0x2104) */ +#define RT5514_CLK_AD_ANA1_EN (0x1 << 31) +#define RT5514_CLK_AD_ANA1_EN_BIT 31 +#define RT5514_CLK_AD1_EN (0x1 << 24) +#define RT5514_CLK_AD1_EN_BIT 24 +#define RT5514_CLK_AD0_EN (0x1 << 23) +#define RT5514_CLK_AD0_EN_BIT 23 +#define RT5514_CLK_DMIC_OUT_SEL_MASK (0x7 << 8) +#define RT5514_CLK_DMIC_OUT_SEL_SFT 8 +#define RT5514_CLK_AD_ANA1_SEL_MASK (0xf << 0) +#define RT5514_CLK_AD_ANA1_SEL_SFT 0 + +/* RT5514_CLK_CTRL2 (0x2108) */ +#define RT5514_CLK_AD1_ASRC_EN (0x1 << 17) +#define RT5514_CLK_AD1_ASRC_EN_BIT 17 +#define RT5514_CLK_AD0_ASRC_EN (0x1 << 16) +#define RT5514_CLK_AD0_ASRC_EN_BIT 16 +#define RT5514_CLK_SYS_DIV_OUT_MASK (0x7 << 8) +#define RT5514_CLK_SYS_DIV_OUT_SFT 8 +#define RT5514_SEL_ADC_OSR_MASK (0x7 << 4) +#define RT5514_SEL_ADC_OSR_SFT 4 +#define RT5514_CLK_SYS_PRE_SEL_MASK (0x3 << 0) +#define RT5514_CLK_SYS_PRE_SEL_SFT 0 +#define RT5514_CLK_SYS_PRE_SEL_MCLK (0x2 << 0) +#define RT5514_CLK_SYS_PRE_SEL_PLL (0x3 << 0) + +/* RT5514_DOWNFILTER_CTRL (0x2190 0x2194 0x21a0 0x21a4) */ +#define RT5514_AD_DMIC_MIX (0x1 << 11) +#define RT5514_AD_DMIC_MIX_BIT 11 +#define RT5514_AD_AD_MIX (0x1 << 10) +#define RT5514_AD_AD_MIX_BIT 10 +#define RT5514_AD_AD_MUTE (0x1 << 7) +#define RT5514_AD_AD_MUTE_BIT 7 +#define RT5514_AD_GAIN_MASK (0x3f << 1) +#define RT5514_AD_GAIN_SFT 1 + +/* RT5514_ANA_CTRL_MICBST (0x2220) */ +#define RT5514_SEL_BSTL_MASK (0xf << 4) +#define RT5514_SEL_BSTL_SFT 4 +#define RT5514_SEL_BSTR_MASK (0xf << 0) +#define RT5514_SEL_BSTR_SFT 0 + +/* RT5514_ANA_CTRL_PLL1_1 (0x2260) */ +#define RT5514_PLL_K_MAX 0x1f +#define RT5514_PLL_K_MASK (RT5514_PLL_K_MAX << 16) +#define RT5514_PLL_K_SFT 16 +#define RT5514_PLL_N_MAX 0x1ff +#define RT5514_PLL_N_MASK (RT5514_PLL_N_MAX << 7) +#define RT5514_PLL_N_SFT 4 +#define RT5514_PLL_M_MAX 0xf +#define RT5514_PLL_M_MASK (RT5514_PLL_M_MAX << 0) +#define RT5514_PLL_M_SFT 0 + +/* RT5514_ANA_CTRL_PLL1_2 (0x2264) */ +#define RT5514_PLL_M_BP (0x1 << 2) +#define RT5514_PLL_M_BP_SFT 2 +#define RT5514_PLL_K_BP (0x1 << 1) +#define RT5514_PLL_K_BP_SFT 1 +#define RT5514_EN_LDO_PLL1 (0x1 << 0) +#define RT5514_EN_LDO_PLL1_BIT 0 + +#define RT5514_PLL_INP_MAX 40000000 +#define RT5514_PLL_INP_MIN 256000 + +#define RT5514_FIRMWARE1 "rt5514_dsp_fw1.bin" +#define RT5514_FIRMWARE2 "rt5514_dsp_fw2.bin" + +/* System Clock Source */ +enum { + RT5514_SCLK_S_MCLK, + RT5514_SCLK_S_PLL1, +}; + +/* PLL1 Source */ +enum { + RT5514_PLL1_S_MCLK, + RT5514_PLL1_S_BCLK, +}; + +struct rt5514_priv { + struct rt5514_platform_data pdata; + struct snd_soc_component *component; + struct regmap *i2c_regmap, *regmap; + struct clk *mclk, *dsp_calib_clk; + int sysclk; + int sysclk_src; + int lrck; + int bclk; + int pll_src; + int pll_in; + int pll_out; + int dsp_enabled; + unsigned int pll3_cal_value; +}; + +#endif /* __RT5514_H__ */ diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c new file mode 100644 index 000000000..3dc795f44 --- /dev/null +++ b/sound/soc/codecs/rt5616.c @@ -0,0 +1,1422 @@ +/* + * rt5616.c -- RT5616 ALSA SoC audio codec driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5616.h" + +#define RT5616_PR_RANGE_BASE (0xff + 1) +#define RT5616_PR_SPACING 0x100 + +#define RT5616_PR_BASE (RT5616_PR_RANGE_BASE + (0 * RT5616_PR_SPACING)) + +static const struct regmap_range_cfg rt5616_ranges[] = { + { + .name = "PR", + .range_min = RT5616_PR_BASE, + .range_max = RT5616_PR_BASE + 0xf8, + .selector_reg = RT5616_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT5616_PRIV_DATA, + .window_len = 0x1, + }, +}; + +static const struct reg_sequence init_list[] = { + {RT5616_PR_BASE + 0x3d, 0x3e00}, + {RT5616_PR_BASE + 0x25, 0x6110}, + {RT5616_PR_BASE + 0x20, 0x611f}, + {RT5616_PR_BASE + 0x21, 0x4040}, + {RT5616_PR_BASE + 0x23, 0x0004}, +}; + +#define RT5616_INIT_REG_LEN ARRAY_SIZE(init_list) + +static const struct reg_default rt5616_reg[] = { + { 0x00, 0x0021 }, + { 0x02, 0xc8c8 }, + { 0x03, 0xc8c8 }, + { 0x05, 0x0000 }, + { 0x0d, 0x0000 }, + { 0x0f, 0x0808 }, + { 0x19, 0xafaf }, + { 0x1c, 0x2f2f }, + { 0x1e, 0x0000 }, + { 0x27, 0x7860 }, + { 0x29, 0x8080 }, + { 0x2a, 0x5252 }, + { 0x3b, 0x0000 }, + { 0x3c, 0x006f }, + { 0x3d, 0x0000 }, + { 0x3e, 0x006f }, + { 0x45, 0x6000 }, + { 0x4d, 0x0000 }, + { 0x4e, 0x0000 }, + { 0x4f, 0x0279 }, + { 0x50, 0x0000 }, + { 0x51, 0x0000 }, + { 0x52, 0x0279 }, + { 0x53, 0xf000 }, + { 0x61, 0x0000 }, + { 0x62, 0x0000 }, + { 0x63, 0x00c0 }, + { 0x64, 0x0000 }, + { 0x65, 0x0000 }, + { 0x66, 0x0000 }, + { 0x70, 0x8000 }, + { 0x73, 0x1104 }, + { 0x74, 0x0c00 }, + { 0x80, 0x0000 }, + { 0x81, 0x0000 }, + { 0x82, 0x0000 }, + { 0x8b, 0x0600 }, + { 0x8e, 0x0004 }, + { 0x8f, 0x1100 }, + { 0x90, 0x0000 }, + { 0x91, 0x0c00 }, + { 0x92, 0x0000 }, + { 0x93, 0x2000 }, + { 0x94, 0x0200 }, + { 0x95, 0x0000 }, + { 0xb0, 0x2080 }, + { 0xb1, 0x0000 }, + { 0xb2, 0x0000 }, + { 0xb4, 0x2206 }, + { 0xb5, 0x1f00 }, + { 0xb6, 0x0000 }, + { 0xb7, 0x0000 }, + { 0xbb, 0x0000 }, + { 0xbc, 0x0000 }, + { 0xbd, 0x0000 }, + { 0xbe, 0x0000 }, + { 0xbf, 0x0000 }, + { 0xc0, 0x0100 }, + { 0xc1, 0x0000 }, + { 0xc2, 0x0000 }, + { 0xc8, 0x0000 }, + { 0xc9, 0x0000 }, + { 0xca, 0x0000 }, + { 0xcb, 0x0000 }, + { 0xcc, 0x0000 }, + { 0xcd, 0x0000 }, + { 0xce, 0x0000 }, + { 0xcf, 0x0013 }, + { 0xd0, 0x0680 }, + { 0xd1, 0x1c17 }, + { 0xd3, 0xb320 }, + { 0xd4, 0x0000 }, + { 0xd6, 0x0000 }, + { 0xd7, 0x0000 }, + { 0xd9, 0x0809 }, + { 0xda, 0x0000 }, + { 0xfa, 0x0010 }, + { 0xfb, 0x0000 }, + { 0xfc, 0x0000 }, + { 0xfe, 0x10ec }, + { 0xff, 0x6281 }, +}; + +struct rt5616_priv { + struct snd_soc_component *component; + struct delayed_work patch_work; + struct regmap *regmap; + struct clk *mclk; + + int sysclk; + int sysclk_src; + int lrck[RT5616_AIFS]; + int bclk[RT5616_AIFS]; + int master[RT5616_AIFS]; + + int pll_src; + int pll_in; + int pll_out; + +}; + +static bool rt5616_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5616_ranges); i++) { + if (reg >= rt5616_ranges[i].range_min && + reg <= rt5616_ranges[i].range_max) + return true; + } + + switch (reg) { + case RT5616_RESET: + case RT5616_PRIV_DATA: + case RT5616_EQ_CTRL1: + case RT5616_DRC_AGC_1: + case RT5616_IRQ_CTRL2: + case RT5616_INT_IRQ_ST: + case RT5616_PGM_REG_ARR1: + case RT5616_PGM_REG_ARR3: + case RT5616_VENDOR_ID: + case RT5616_DEVICE_ID: + return true; + default: + return false; + } +} + +static bool rt5616_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5616_ranges); i++) { + if (reg >= rt5616_ranges[i].range_min && + reg <= rt5616_ranges[i].range_max) + return true; + } + + switch (reg) { + case RT5616_RESET: + case RT5616_VERSION_ID: + case RT5616_VENDOR_ID: + case RT5616_DEVICE_ID: + case RT5616_HP_VOL: + case RT5616_LOUT_CTRL1: + case RT5616_LOUT_CTRL2: + case RT5616_IN1_IN2: + case RT5616_INL1_INR1_VOL: + case RT5616_DAC1_DIG_VOL: + case RT5616_ADC_DIG_VOL: + case RT5616_ADC_BST_VOL: + case RT5616_STO1_ADC_MIXER: + case RT5616_AD_DA_MIXER: + case RT5616_STO_DAC_MIXER: + case RT5616_REC_L1_MIXER: + case RT5616_REC_L2_MIXER: + case RT5616_REC_R1_MIXER: + case RT5616_REC_R2_MIXER: + case RT5616_HPO_MIXER: + case RT5616_OUT_L1_MIXER: + case RT5616_OUT_L2_MIXER: + case RT5616_OUT_L3_MIXER: + case RT5616_OUT_R1_MIXER: + case RT5616_OUT_R2_MIXER: + case RT5616_OUT_R3_MIXER: + case RT5616_LOUT_MIXER: + case RT5616_PWR_DIG1: + case RT5616_PWR_DIG2: + case RT5616_PWR_ANLG1: + case RT5616_PWR_ANLG2: + case RT5616_PWR_MIXER: + case RT5616_PWR_VOL: + case RT5616_PRIV_INDEX: + case RT5616_PRIV_DATA: + case RT5616_I2S1_SDP: + case RT5616_ADDA_CLK1: + case RT5616_ADDA_CLK2: + case RT5616_GLB_CLK: + case RT5616_PLL_CTRL1: + case RT5616_PLL_CTRL2: + case RT5616_HP_OVCD: + case RT5616_DEPOP_M1: + case RT5616_DEPOP_M2: + case RT5616_DEPOP_M3: + case RT5616_CHARGE_PUMP: + case RT5616_PV_DET_SPK_G: + case RT5616_MICBIAS: + case RT5616_A_JD_CTL1: + case RT5616_A_JD_CTL2: + case RT5616_EQ_CTRL1: + case RT5616_EQ_CTRL2: + case RT5616_WIND_FILTER: + case RT5616_DRC_AGC_1: + case RT5616_DRC_AGC_2: + case RT5616_DRC_AGC_3: + case RT5616_SVOL_ZC: + case RT5616_JD_CTRL1: + case RT5616_JD_CTRL2: + case RT5616_IRQ_CTRL1: + case RT5616_IRQ_CTRL2: + case RT5616_INT_IRQ_ST: + case RT5616_GPIO_CTRL1: + case RT5616_GPIO_CTRL2: + case RT5616_GPIO_CTRL3: + case RT5616_PGM_REG_ARR1: + case RT5616_PGM_REG_ARR2: + case RT5616_PGM_REG_ARR3: + case RT5616_PGM_REG_ARR4: + case RT5616_PGM_REG_ARR5: + case RT5616_SCB_FUNC: + case RT5616_SCB_CTRL: + case RT5616_BASE_BACK: + case RT5616_MP3_PLUS1: + case RT5616_MP3_PLUS2: + case RT5616_ADJ_HPF_CTRL1: + case RT5616_ADJ_HPF_CTRL2: + case RT5616_HP_CALIB_AMP_DET: + case RT5616_HP_CALIB2: + case RT5616_SV_ZCD1: + case RT5616_SV_ZCD2: + case RT5616_D_MISC: + case RT5616_DUMMY2: + case RT5616_DUMMY3: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0), +); + +static const struct snd_kcontrol_new rt5616_snd_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE("HP Playback Switch", RT5616_HP_VOL, + RT5616_L_MUTE_SFT, RT5616_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("HPVOL Playback Switch", RT5616_HP_VOL, + RT5616_VOL_L_SFT, RT5616_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("HP Playback Volume", RT5616_HP_VOL, + RT5616_L_VOL_SFT, RT5616_R_VOL_SFT, 39, 1, out_vol_tlv), + /* OUTPUT Control */ + SOC_DOUBLE("OUT Playback Switch", RT5616_LOUT_CTRL1, + RT5616_L_MUTE_SFT, RT5616_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("OUT Channel Switch", RT5616_LOUT_CTRL1, + RT5616_VOL_L_SFT, RT5616_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("OUT Playback Volume", RT5616_LOUT_CTRL1, + RT5616_L_VOL_SFT, RT5616_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5616_DAC1_DIG_VOL, + RT5616_L_VOL_SFT, RT5616_R_VOL_SFT, + 175, 0, dac_vol_tlv), + /* IN1/IN2 Control */ + SOC_SINGLE_TLV("IN1 Boost Volume", RT5616_IN1_IN2, + RT5616_BST_SFT1, 8, 0, bst_tlv), + SOC_SINGLE_TLV("IN2 Boost Volume", RT5616_IN1_IN2, + RT5616_BST_SFT2, 8, 0, bst_tlv), + /* INL/INR Volume Control */ + SOC_DOUBLE_TLV("IN Capture Volume", RT5616_INL1_INR1_VOL, + RT5616_INL_VOL_SFT, RT5616_INR_VOL_SFT, + 31, 1, in_vol_tlv), + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC Capture Switch", RT5616_ADC_DIG_VOL, + RT5616_L_MUTE_SFT, RT5616_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("ADC Capture Volume", RT5616_ADC_DIG_VOL, + RT5616_L_VOL_SFT, RT5616_R_VOL_SFT, + 127, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("ADC Boost Volume", RT5616_ADC_BST_VOL, + RT5616_ADC_L_BST_SFT, RT5616_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), +}; + +static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + unsigned int val; + + val = snd_soc_component_read32(snd_soc_dapm_to_component(source->dapm), RT5616_GLB_CLK); + val &= RT5616_SCLK_SRC_MASK; + if (val == RT5616_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5616_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5616_STO1_ADC_MIXER, + RT5616_M_STO1_ADC_L1_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5616_STO1_ADC_MIXER, + RT5616_M_STO1_ADC_R1_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5616_AD_DA_MIXER, + RT5616_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INF1 Switch", RT5616_AD_DA_MIXER, + RT5616_M_IF1_DAC_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5616_AD_DA_MIXER, + RT5616_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INF1 Switch", RT5616_AD_DA_MIXER, + RT5616_M_IF1_DAC_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5616_STO_DAC_MIXER, + RT5616_M_DAC_L1_MIXL_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5616_STO_DAC_MIXER, + RT5616_M_DAC_R1_MIXL_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5616_STO_DAC_MIXER, + RT5616_M_DAC_R1_MIXR_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5616_STO_DAC_MIXER, + RT5616_M_DAC_L1_MIXR_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5616_rec_l_mix[] = { + SOC_DAPM_SINGLE("INL1 Switch", RT5616_REC_L2_MIXER, + RT5616_M_IN1_L_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5616_REC_L2_MIXER, + RT5616_M_BST2_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5616_REC_L2_MIXER, + RT5616_M_BST1_RM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_rec_r_mix[] = { + SOC_DAPM_SINGLE("INR1 Switch", RT5616_REC_R2_MIXER, + RT5616_M_IN1_R_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5616_REC_R2_MIXER, + RT5616_M_BST2_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5616_REC_R2_MIXER, + RT5616_M_BST1_RM_R_SFT, 1, 1), +}; + +/* Analog Output Mixer */ + +static const struct snd_kcontrol_new rt5616_out_l_mix[] = { + SOC_DAPM_SINGLE("BST1 Switch", RT5616_OUT_L3_MIXER, + RT5616_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5616_OUT_L3_MIXER, + RT5616_M_BST2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL1 Switch", RT5616_OUT_L3_MIXER, + RT5616_M_IN1_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXL Switch", RT5616_OUT_L3_MIXER, + RT5616_M_RM_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5616_OUT_L3_MIXER, + RT5616_M_DAC_L1_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_out_r_mix[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5616_OUT_R3_MIXER, + RT5616_M_BST2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5616_OUT_R3_MIXER, + RT5616_M_BST1_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR1 Switch", RT5616_OUT_R3_MIXER, + RT5616_M_IN1_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXR Switch", RT5616_OUT_R3_MIXER, + RT5616_M_RM_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5616_OUT_R3_MIXER, + RT5616_M_DAC_R1_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_hpo_mix[] = { + SOC_DAPM_SINGLE("DAC1 Switch", RT5616_HPO_MIXER, + RT5616_M_DAC1_HM_SFT, 1, 1), + SOC_DAPM_SINGLE("HPVOL Switch", RT5616_HPO_MIXER, + RT5616_M_HPVOL_HM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5616_lout_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5616_LOUT_MIXER, + RT5616_M_DAC_L1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5616_LOUT_MIXER, + RT5616_M_DAC_R1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL L Switch", RT5616_LOUT_MIXER, + RT5616_M_OV_L_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL R Switch", RT5616_LOUT_MIXER, + RT5616_M_OV_R_LM_SFT, 1, 1), +}; + +static int rt5616_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5616_ADC_DIG_VOL, + RT5616_L_MUTE | RT5616_R_MUTE, 0); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, RT5616_ADC_DIG_VOL, + RT5616_L_MUTE | RT5616_R_MUTE, + RT5616_L_MUTE | RT5616_R_MUTE); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5616_charge_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* depop parameters */ + snd_soc_component_update_bits(component, RT5616_DEPOP_M2, + RT5616_DEPOP_MASK, RT5616_DEPOP_MAN); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_HP_CP_MASK | RT5616_HP_SG_MASK | + RT5616_HP_CB_MASK, RT5616_HP_CP_PU | + RT5616_HP_SG_DIS | RT5616_HP_CB_PU); + snd_soc_component_write(component, RT5616_PR_BASE + + RT5616_HP_DCC_INT1, 0x9f00); + /* headphone amp power on */ + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_FV1 | RT5616_PWR_FV2, 0); + snd_soc_component_update_bits(component, RT5616_PWR_VOL, + RT5616_PWR_HV_L | RT5616_PWR_HV_R, + RT5616_PWR_HV_L | RT5616_PWR_HV_R); + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_HP_L | RT5616_PWR_HP_R | + RT5616_PWR_HA, RT5616_PWR_HP_L | + RT5616_PWR_HP_R | RT5616_PWR_HA); + msleep(50); + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_FV1 | RT5616_PWR_FV2, + RT5616_PWR_FV1 | RT5616_PWR_FV2); + + snd_soc_component_update_bits(component, RT5616_CHARGE_PUMP, + RT5616_PM_HP_MASK, RT5616_PM_HP_HV); + snd_soc_component_update_bits(component, RT5616_PR_BASE + + RT5616_CHOP_DAC_ADC, 0x0200, 0x0200); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_HP_CO_MASK | RT5616_HP_SG_MASK, + RT5616_HP_CO_EN | RT5616_HP_SG_EN); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5616_PR_BASE + + RT5616_CHOP_DAC_ADC, 0x0200, 0x0); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_HP_SG_MASK | RT5616_HP_L_SMT_MASK | + RT5616_HP_R_SMT_MASK, RT5616_HP_SG_DIS | + RT5616_HP_L_SMT_DIS | RT5616_HP_R_SMT_DIS); + /* headphone amp power down */ + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_SMT_TRIG_MASK | + RT5616_HP_CD_PD_MASK | RT5616_HP_CO_MASK | + RT5616_HP_CP_MASK | RT5616_HP_SG_MASK | + RT5616_HP_CB_MASK, + RT5616_SMT_TRIG_DIS | RT5616_HP_CD_PD_EN | + RT5616_HP_CO_DIS | RT5616_HP_CP_PD | + RT5616_HP_SG_EN | RT5616_HP_CB_PD); + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_HP_L | RT5616_PWR_HP_R | + RT5616_PWR_HA, 0); + break; + default: + return 0; + } + + return 0; +} + +static int rt5616_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* headphone unmute sequence */ + snd_soc_component_update_bits(component, RT5616_DEPOP_M3, + RT5616_CP_FQ1_MASK | RT5616_CP_FQ2_MASK | + RT5616_CP_FQ3_MASK, + RT5616_CP_FQ_192_KHZ << RT5616_CP_FQ1_SFT | + RT5616_CP_FQ_12_KHZ << RT5616_CP_FQ2_SFT | + RT5616_CP_FQ_192_KHZ << RT5616_CP_FQ3_SFT); + snd_soc_component_write(component, RT5616_PR_BASE + + RT5616_MAMP_INT_REG2, 0xfc00); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_SMT_TRIG_MASK, RT5616_SMT_TRIG_EN); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_RSTN_MASK, RT5616_RSTN_EN); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_RSTN_MASK | RT5616_HP_L_SMT_MASK | + RT5616_HP_R_SMT_MASK, RT5616_RSTN_DIS | + RT5616_HP_L_SMT_EN | RT5616_HP_R_SMT_EN); + snd_soc_component_update_bits(component, RT5616_HP_VOL, + RT5616_L_MUTE | RT5616_R_MUTE, 0); + msleep(100); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_HP_SG_MASK | RT5616_HP_L_SMT_MASK | + RT5616_HP_R_SMT_MASK, RT5616_HP_SG_DIS | + RT5616_HP_L_SMT_DIS | RT5616_HP_R_SMT_DIS); + msleep(20); + snd_soc_component_update_bits(component, RT5616_HP_CALIB_AMP_DET, + RT5616_HPD_PS_MASK, RT5616_HPD_PS_EN); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* headphone mute sequence */ + snd_soc_component_update_bits(component, RT5616_DEPOP_M3, + RT5616_CP_FQ1_MASK | RT5616_CP_FQ2_MASK | + RT5616_CP_FQ3_MASK, + RT5616_CP_FQ_96_KHZ << RT5616_CP_FQ1_SFT | + RT5616_CP_FQ_12_KHZ << RT5616_CP_FQ2_SFT | + RT5616_CP_FQ_96_KHZ << RT5616_CP_FQ3_SFT); + snd_soc_component_write(component, RT5616_PR_BASE + + RT5616_MAMP_INT_REG2, 0xfc00); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_HP_SG_MASK, RT5616_HP_SG_EN); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_RSTP_MASK, RT5616_RSTP_EN); + snd_soc_component_update_bits(component, RT5616_DEPOP_M1, + RT5616_RSTP_MASK | RT5616_HP_L_SMT_MASK | + RT5616_HP_R_SMT_MASK, RT5616_RSTP_DIS | + RT5616_HP_L_SMT_EN | RT5616_HP_R_SMT_EN); + snd_soc_component_update_bits(component, RT5616_HP_CALIB_AMP_DET, + RT5616_HPD_PS_MASK, RT5616_HPD_PS_DIS); + msleep(90); + snd_soc_component_update_bits(component, RT5616_HP_VOL, + RT5616_L_MUTE | RT5616_R_MUTE, + RT5616_L_MUTE | RT5616_R_MUTE); + msleep(30); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5616_lout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_LM, RT5616_PWR_LM); + snd_soc_component_update_bits(component, RT5616_LOUT_CTRL1, + RT5616_L_MUTE | RT5616_R_MUTE, 0); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5616_LOUT_CTRL1, + RT5616_L_MUTE | RT5616_R_MUTE, + RT5616_L_MUTE | RT5616_R_MUTE); + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_LM, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5616_bst1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5616_PWR_ANLG2, + RT5616_PWR_BST1_OP2, RT5616_PWR_BST1_OP2); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5616_PWR_ANLG2, + RT5616_PWR_BST1_OP2, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5616_bst2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5616_PWR_ANLG2, + RT5616_PWR_BST2_OP2, RT5616_PWR_BST2_OP2); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5616_PWR_ANLG2, + RT5616_PWR_BST2_OP2, 0); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5616_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PLL1", RT5616_PWR_ANLG2, + RT5616_PWR_PLL_BIT, 0, NULL, 0), + /* Input Side */ + /* micbias */ + SND_SOC_DAPM_SUPPLY("LDO", RT5616_PWR_ANLG1, + RT5616_PWR_LDO_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("micbias1", RT5616_PWR_ANLG2, + RT5616_PWR_MB1_BIT, 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + + /* Boost */ + SND_SOC_DAPM_PGA_E("BST1", RT5616_PWR_ANLG2, + RT5616_PWR_BST1_BIT, 0, NULL, 0, rt5616_bst1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("BST2", RT5616_PWR_ANLG2, + RT5616_PWR_BST2_BIT, 0, NULL, 0, rt5616_bst2_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + /* Input Volume */ + SND_SOC_DAPM_PGA("INL1 VOL", RT5616_PWR_VOL, + RT5616_PWR_IN1_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR1 VOL", RT5616_PWR_VOL, + RT5616_PWR_IN1_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INL2 VOL", RT5616_PWR_VOL, + RT5616_PWR_IN2_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR2 VOL", RT5616_PWR_VOL, + RT5616_PWR_IN2_R_BIT, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIXL", RT5616_PWR_MIXER, RT5616_PWR_RM_L_BIT, 0, + rt5616_rec_l_mix, ARRAY_SIZE(rt5616_rec_l_mix)), + SND_SOC_DAPM_MIXER("RECMIXR", RT5616_PWR_MIXER, RT5616_PWR_RM_R_BIT, 0, + rt5616_rec_r_mix, ARRAY_SIZE(rt5616_rec_r_mix)), + /* ADCs */ + SND_SOC_DAPM_ADC_E("ADC L", NULL, RT5616_PWR_DIG1, + RT5616_PWR_ADC_L_BIT, 0, rt5616_adc_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC_E("ADC R", NULL, RT5616_PWR_DIG1, + RT5616_PWR_ADC_R_BIT, 0, rt5616_adc_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), + + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("stereo1 filter", RT5616_PWR_DIG2, + RT5616_PWR_ADC_STO1_F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5616_sto1_adc_l_mix, + ARRAY_SIZE(rt5616_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5616_sto1_adc_r_mix, + ARRAY_SIZE(rt5616_sto1_adc_r_mix)), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5616_PWR_DIG1, + RT5616_PWR_I2S1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Audio DSP */ + SND_SOC_DAPM_PGA("Audio DSP", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5616_dac_l_mix, ARRAY_SIZE(rt5616_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5616_dac_r_mix, ARRAY_SIZE(rt5616_dac_r_mix)), + + SND_SOC_DAPM_SUPPLY("Stero1 DAC Power", RT5616_PWR_DIG2, + RT5616_PWR_DAC_STO1_F_BIT, 0, NULL, 0), + + /* DAC Mixer */ + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5616_sto_dac_l_mix, + ARRAY_SIZE(rt5616_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5616_sto_dac_r_mix, + ARRAY_SIZE(rt5616_sto_dac_r_mix)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, RT5616_PWR_DIG1, + RT5616_PWR_DAC_L1_BIT, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, RT5616_PWR_DIG1, + RT5616_PWR_DAC_R1_BIT, 0), + /* OUT Mixer */ + SND_SOC_DAPM_MIXER("OUT MIXL", RT5616_PWR_MIXER, RT5616_PWR_OM_L_BIT, + 0, rt5616_out_l_mix, ARRAY_SIZE(rt5616_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5616_PWR_MIXER, RT5616_PWR_OM_R_BIT, + 0, rt5616_out_r_mix, ARRAY_SIZE(rt5616_out_r_mix)), + /* Output Volume */ + SND_SOC_DAPM_PGA("OUTVOL L", RT5616_PWR_VOL, + RT5616_PWR_OV_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUTVOL R", RT5616_PWR_VOL, + RT5616_PWR_OV_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPOVOL L", RT5616_PWR_VOL, + RT5616_PWR_HV_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPOVOL R", RT5616_PWR_VOL, + RT5616_PWR_HV_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC 1", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC 2", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPOVOL", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("INL1", RT5616_PWR_VOL, + RT5616_PWR_IN1_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR1", RT5616_PWR_VOL, + RT5616_PWR_IN1_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INL2", RT5616_PWR_VOL, + RT5616_PWR_IN2_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR2", RT5616_PWR_VOL, + RT5616_PWR_IN2_R_BIT, 0, NULL, 0), + /* HPO/LOUT/Mono Mixer */ + SND_SOC_DAPM_MIXER("HPO MIX", SND_SOC_NOPM, 0, 0, + rt5616_hpo_mix, ARRAY_SIZE(rt5616_hpo_mix)), + SND_SOC_DAPM_MIXER("LOUT MIX", SND_SOC_NOPM, 0, 0, + rt5616_lout_mix, ARRAY_SIZE(rt5616_lout_mix)), + + SND_SOC_DAPM_PGA_S("HP amp", 1, SND_SOC_NOPM, 0, 0, + rt5616_hp_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT amp", 1, SND_SOC_NOPM, 0, 0, + rt5616_lout_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY_S("Charge Pump", 1, SND_SOC_NOPM, 0, 0, + rt5616_charge_pump_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), +}; + +static const struct snd_soc_dapm_route rt5616_dapm_routes[] = { + {"IN1P", NULL, "LDO"}, + {"IN2P", NULL, "LDO"}, + + {"IN1P", NULL, "MIC1"}, + {"IN2P", NULL, "MIC2"}, + {"IN2N", NULL, "MIC2"}, + + {"BST1", NULL, "IN1P"}, + {"BST2", NULL, "IN2P"}, + {"BST2", NULL, "IN2N"}, + {"BST1", NULL, "micbias1"}, + {"BST2", NULL, "micbias1"}, + + {"INL1 VOL", NULL, "IN2P"}, + {"INR1 VOL", NULL, "IN2N"}, + + {"RECMIXL", "INL1 Switch", "INL1 VOL"}, + {"RECMIXL", "BST2 Switch", "BST2"}, + {"RECMIXL", "BST1 Switch", "BST1"}, + + {"RECMIXR", "INR1 Switch", "INR1 VOL"}, + {"RECMIXR", "BST2 Switch", "BST2"}, + {"RECMIXR", "BST1 Switch", "BST1"}, + + {"ADC L", NULL, "RECMIXL"}, + {"ADC R", NULL, "RECMIXR"}, + + {"Stereo1 ADC MIXL", "ADC1 Switch", "ADC L"}, + {"Stereo1 ADC MIXL", NULL, "stereo1 filter"}, + {"stereo1 filter", NULL, "PLL1", is_sys_clk_from_pll}, + + {"Stereo1 ADC MIXR", "ADC1 Switch", "ADC R"}, + {"Stereo1 ADC MIXR", NULL, "stereo1 filter"}, + {"stereo1 filter", NULL, "PLL1", is_sys_clk_from_pll}, + + {"IF1 ADC1", NULL, "Stereo1 ADC MIXL"}, + {"IF1 ADC1", NULL, "Stereo1 ADC MIXR"}, + {"IF1 ADC1", NULL, "I2S1"}, + + {"AIF1TX", NULL, "IF1 ADC1"}, + + {"IF1 DAC", NULL, "AIF1RX"}, + {"IF1 DAC", NULL, "I2S1"}, + + {"IF1 DAC1 L", NULL, "IF1 DAC"}, + {"IF1 DAC1 R", NULL, "IF1 DAC"}, + + {"DAC MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL"}, + {"DAC MIXL", "INF1 Switch", "IF1 DAC1 L"}, + {"DAC MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR"}, + {"DAC MIXR", "INF1 Switch", "IF1 DAC1 R"}, + + {"Audio DSP", NULL, "DAC MIXL"}, + {"Audio DSP", NULL, "DAC MIXR"}, + + {"Stereo DAC MIXL", "DAC L1 Switch", "Audio DSP"}, + {"Stereo DAC MIXL", "DAC R1 Switch", "DAC MIXR"}, + {"Stereo DAC MIXL", NULL, "Stero1 DAC Power"}, + {"Stereo DAC MIXR", "DAC R1 Switch", "Audio DSP"}, + {"Stereo DAC MIXR", "DAC L1 Switch", "DAC MIXL"}, + {"Stereo DAC MIXR", NULL, "Stero1 DAC Power"}, + + {"DAC L1", NULL, "Stereo DAC MIXL"}, + {"DAC L1", NULL, "PLL1", is_sys_clk_from_pll}, + {"DAC R1", NULL, "Stereo DAC MIXR"}, + {"DAC R1", NULL, "PLL1", is_sys_clk_from_pll}, + + {"OUT MIXL", "BST1 Switch", "BST1"}, + {"OUT MIXL", "BST2 Switch", "BST2"}, + {"OUT MIXL", "INL1 Switch", "INL1 VOL"}, + {"OUT MIXL", "REC MIXL Switch", "RECMIXL"}, + {"OUT MIXL", "DAC L1 Switch", "DAC L1"}, + + {"OUT MIXR", "BST2 Switch", "BST2"}, + {"OUT MIXR", "BST1 Switch", "BST1"}, + {"OUT MIXR", "INR1 Switch", "INR1 VOL"}, + {"OUT MIXR", "REC MIXR Switch", "RECMIXR"}, + {"OUT MIXR", "DAC R1 Switch", "DAC R1"}, + + {"HPOVOL L", NULL, "OUT MIXL"}, + {"HPOVOL R", NULL, "OUT MIXR"}, + {"OUTVOL L", NULL, "OUT MIXL"}, + {"OUTVOL R", NULL, "OUT MIXR"}, + + {"DAC 1", NULL, "DAC L1"}, + {"DAC 1", NULL, "DAC R1"}, + {"HPOVOL", NULL, "HPOVOL L"}, + {"HPOVOL", NULL, "HPOVOL R"}, + {"HPO MIX", "DAC1 Switch", "DAC 1"}, + {"HPO MIX", "HPVOL Switch", "HPOVOL"}, + + {"LOUT MIX", "DAC L1 Switch", "DAC L1"}, + {"LOUT MIX", "DAC R1 Switch", "DAC R1"}, + {"LOUT MIX", "OUTVOL L Switch", "OUTVOL L"}, + {"LOUT MIX", "OUTVOL R Switch", "OUTVOL R"}, + + {"HP amp", NULL, "HPO MIX"}, + {"HP amp", NULL, "Charge Pump"}, + {"HPOL", NULL, "HP amp"}, + {"HPOR", NULL, "HP amp"}, + + {"LOUT amp", NULL, "LOUT MIX"}, + {"LOUT amp", NULL, "Charge Pump"}, + {"LOUTL", NULL, "LOUT amp"}, + {"LOUTR", NULL, "LOUT amp"}, + +}; + +static int rt5616_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int pre_div, bclk_ms, frame_size; + + rt5616->lrck[dai->id] = params_rate(params); + + pre_div = rl6231_get_clk_info(rt5616->sysclk, rt5616->lrck[dai->id]); + + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting\n"); + return -EINVAL; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + bclk_ms = frame_size > 32 ? 1 : 0; + rt5616->bclk[dai->id] = rt5616->lrck[dai->id] * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5616->bclk[dai->id], rt5616->lrck[dai->id]); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val_len |= RT5616_I2S_DL_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val_len |= RT5616_I2S_DL_24; + break; + case SNDRV_PCM_FORMAT_S8: + val_len |= RT5616_I2S_DL_8; + break; + default: + return -EINVAL; + } + + mask_clk = RT5616_I2S_PD1_MASK; + val_clk = pre_div << RT5616_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5616_I2S1_SDP, + RT5616_I2S_DL_MASK, val_len); + snd_soc_component_update_bits(component, RT5616_ADDA_CLK1, mask_clk, val_clk); + + return 0; +} + +static int rt5616_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5616->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5616_I2S_MS_S; + rt5616->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5616_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5616_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5616_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5616_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5616_I2S1_SDP, + RT5616_I2S_MS_MASK | RT5616_I2S_BP_MASK | + RT5616_I2S_DF_MASK, reg_val); + + return 0; +} + +static int rt5616_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + if (freq == rt5616->sysclk && clk_id == rt5616->sysclk_src) + return 0; + + switch (clk_id) { + case RT5616_SCLK_S_MCLK: + reg_val |= RT5616_SCLK_SRC_MCLK; + break; + case RT5616_SCLK_S_PLL1: + reg_val |= RT5616_SCLK_SRC_PLL1; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5616_GLB_CLK, + RT5616_SCLK_SRC_MASK, reg_val); + rt5616->sysclk = freq; + rt5616->sysclk_src = clk_id; + + dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + + return 0; +} + +static int rt5616_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5616->pll_src && freq_in == rt5616->pll_in && + freq_out == rt5616->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5616->pll_in = 0; + rt5616->pll_out = 0; + snd_soc_component_update_bits(component, RT5616_GLB_CLK, + RT5616_SCLK_SRC_MASK, + RT5616_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5616_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5616_GLB_CLK, + RT5616_PLL1_SRC_MASK, + RT5616_PLL1_SRC_MCLK); + break; + case RT5616_PLL1_S_BCLK1: + case RT5616_PLL1_S_BCLK2: + snd_soc_component_update_bits(component, RT5616_GLB_CLK, + RT5616_PLL1_SRC_MASK, + RT5616_PLL1_SRC_BCLK1); + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5616_PLL_CTRL1, + pll_code.n_code << RT5616_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5616_PLL_CTRL2, + (pll_code.m_bp ? 0 : pll_code.m_code) << + RT5616_PLL_M_SFT | + pll_code.m_bp << RT5616_PLL_M_BP_SFT); + + rt5616->pll_in = freq_in; + rt5616->pll_out = freq_out; + rt5616->pll_src = source; + + return 0; +} + +static int rt5616_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* + * SND_SOC_BIAS_PREPARE is called while preparing for a + * transition to ON or away from ON. If current bias_level + * is SND_SOC_BIAS_ON, then it is preparing for a transition + * away from ON. Disable the clock in that case, otherwise + * enable it. + */ + if (IS_ERR(rt5616->mclk)) + break; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(rt5616->mclk); + } else { + ret = clk_prepare_enable(rt5616->mclk); + if (ret) + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_VREF1 | RT5616_PWR_MB | + RT5616_PWR_BG | RT5616_PWR_VREF2, + RT5616_PWR_VREF1 | RT5616_PWR_MB | + RT5616_PWR_BG | RT5616_PWR_VREF2); + mdelay(10); + snd_soc_component_update_bits(component, RT5616_PWR_ANLG1, + RT5616_PWR_FV1 | RT5616_PWR_FV2, + RT5616_PWR_FV1 | RT5616_PWR_FV2); + snd_soc_component_update_bits(component, RT5616_D_MISC, + RT5616_D_GATE_EN, + RT5616_D_GATE_EN); + } + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, RT5616_D_MISC, RT5616_D_GATE_EN, 0); + snd_soc_component_write(component, RT5616_PWR_DIG1, 0x0000); + snd_soc_component_write(component, RT5616_PWR_DIG2, 0x0000); + snd_soc_component_write(component, RT5616_PWR_VOL, 0x0000); + snd_soc_component_write(component, RT5616_PWR_MIXER, 0x0000); + snd_soc_component_write(component, RT5616_PWR_ANLG1, 0x0000); + snd_soc_component_write(component, RT5616_PWR_ANLG2, 0x0000); + break; + + default: + break; + } + + return 0; +} + +static int rt5616_probe(struct snd_soc_component *component) +{ + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + + /* Check if MCLK provided */ + rt5616->mclk = devm_clk_get(component->dev, "mclk"); + if (PTR_ERR(rt5616->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + rt5616->component = component; + + return 0; +} + +#ifdef CONFIG_PM +static int rt5616_suspend(struct snd_soc_component *component) +{ + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5616->regmap, true); + regcache_mark_dirty(rt5616->regmap); + + return 0; +} + +static int rt5616_resume(struct snd_soc_component *component) +{ + struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5616->regmap, false); + regcache_sync(rt5616->regmap); + return 0; +} +#else +#define rt5616_suspend NULL +#define rt5616_resume NULL +#endif + +#define RT5616_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5616_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5616_aif_dai_ops = { + .hw_params = rt5616_hw_params, + .set_fmt = rt5616_set_dai_fmt, + .set_sysclk = rt5616_set_dai_sysclk, + .set_pll = rt5616_set_dai_pll, +}; + +static struct snd_soc_dai_driver rt5616_dai[] = { + { + .name = "rt5616-aif1", + .id = RT5616_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5616_STEREO_RATES, + .formats = RT5616_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5616_STEREO_RATES, + .formats = RT5616_FORMATS, + }, + .ops = &rt5616_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5616 = { + .probe = rt5616_probe, + .suspend = rt5616_suspend, + .resume = rt5616_resume, + .set_bias_level = rt5616_set_bias_level, + .controls = rt5616_snd_controls, + .num_controls = ARRAY_SIZE(rt5616_snd_controls), + .dapm_widgets = rt5616_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5616_dapm_widgets), + .dapm_routes = rt5616_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5616_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5616_regmap = { + .reg_bits = 8, + .val_bits = 16, + .use_single_rw = true, + .max_register = RT5616_DEVICE_ID + 1 + (ARRAY_SIZE(rt5616_ranges) * + RT5616_PR_SPACING), + .volatile_reg = rt5616_volatile_register, + .readable_reg = rt5616_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5616_reg, + .num_reg_defaults = ARRAY_SIZE(rt5616_reg), + .ranges = rt5616_ranges, + .num_ranges = ARRAY_SIZE(rt5616_ranges), +}; + +static const struct i2c_device_id rt5616_i2c_id[] = { + { "rt5616", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5616_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id rt5616_of_match[] = { + { .compatible = "realtek,rt5616", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5616_of_match); +#endif + +static int rt5616_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5616_priv *rt5616; + unsigned int val; + int ret; + + rt5616 = devm_kzalloc(&i2c->dev, sizeof(struct rt5616_priv), + GFP_KERNEL); + if (!rt5616) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5616); + + rt5616->regmap = devm_regmap_init_i2c(i2c, &rt5616_regmap); + if (IS_ERR(rt5616->regmap)) { + ret = PTR_ERR(rt5616->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt5616->regmap, RT5616_DEVICE_ID, &val); + if (val != 0x6281) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5616\n", + val); + return -ENODEV; + } + regmap_write(rt5616->regmap, RT5616_RESET, 0); + regmap_update_bits(rt5616->regmap, RT5616_PWR_ANLG1, + RT5616_PWR_VREF1 | RT5616_PWR_MB | + RT5616_PWR_BG | RT5616_PWR_VREF2, + RT5616_PWR_VREF1 | RT5616_PWR_MB | + RT5616_PWR_BG | RT5616_PWR_VREF2); + mdelay(10); + regmap_update_bits(rt5616->regmap, RT5616_PWR_ANLG1, + RT5616_PWR_FV1 | RT5616_PWR_FV2, + RT5616_PWR_FV1 | RT5616_PWR_FV2); + + ret = regmap_register_patch(rt5616->regmap, init_list, + ARRAY_SIZE(init_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + regmap_update_bits(rt5616->regmap, RT5616_PWR_ANLG1, + RT5616_PWR_LDO_DVO_MASK, RT5616_PWR_LDO_DVO_1_2V); + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5616, + rt5616_dai, ARRAY_SIZE(rt5616_dai)); +} + +static int rt5616_i2c_remove(struct i2c_client *i2c) +{ + return 0; +} + +static void rt5616_i2c_shutdown(struct i2c_client *client) +{ + struct rt5616_priv *rt5616 = i2c_get_clientdata(client); + + regmap_write(rt5616->regmap, RT5616_HP_VOL, 0xc8c8); + regmap_write(rt5616->regmap, RT5616_LOUT_CTRL1, 0xc8c8); +} + +static struct i2c_driver rt5616_i2c_driver = { + .driver = { + .name = "rt5616", + .of_match_table = of_match_ptr(rt5616_of_match), + }, + .probe = rt5616_i2c_probe, + .remove = rt5616_i2c_remove, + .shutdown = rt5616_i2c_shutdown, + .id_table = rt5616_i2c_id, +}; +module_i2c_driver(rt5616_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5616 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt5616.h b/sound/soc/codecs/rt5616.h new file mode 100644 index 000000000..f88cdddbc --- /dev/null +++ b/sound/soc/codecs/rt5616.h @@ -0,0 +1,1819 @@ +/* + * rt5616.h -- RT5616 ALSA SoC audio driver + * + * Copyright 2011 Realtek Microelectronics + * Author: Johnny Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5616_H__ +#define __RT5616_H__ + +/* Info */ +#define RT5616_RESET 0x00 +#define RT5616_VERSION_ID 0xfd +#define RT5616_VENDOR_ID 0xfe +#define RT5616_DEVICE_ID 0xff +/* I/O - Output */ +#define RT5616_HP_VOL 0x02 +#define RT5616_LOUT_CTRL1 0x03 +#define RT5616_LOUT_CTRL2 0x05 +/* I/O - Input */ +#define RT5616_IN1_IN2 0x0d +#define RT5616_INL1_INR1_VOL 0x0f +/* I/O - ADC/DAC/DMIC */ +#define RT5616_DAC1_DIG_VOL 0x19 +#define RT5616_ADC_DIG_VOL 0x1c +#define RT5616_ADC_BST_VOL 0x1e +/* Mixer - D-D */ +#define RT5616_STO1_ADC_MIXER 0x27 +#define RT5616_AD_DA_MIXER 0x29 +#define RT5616_STO_DAC_MIXER 0x2a + +/* Mixer - ADC */ +#define RT5616_REC_L1_MIXER 0x3b +#define RT5616_REC_L2_MIXER 0x3c +#define RT5616_REC_R1_MIXER 0x3d +#define RT5616_REC_R2_MIXER 0x3e +/* Mixer - DAC */ +#define RT5616_HPO_MIXER 0x45 +#define RT5616_OUT_L1_MIXER 0x4d +#define RT5616_OUT_L2_MIXER 0x4e +#define RT5616_OUT_L3_MIXER 0x4f +#define RT5616_OUT_R1_MIXER 0x50 +#define RT5616_OUT_R2_MIXER 0x51 +#define RT5616_OUT_R3_MIXER 0x52 +#define RT5616_LOUT_MIXER 0x53 +/* Power */ +#define RT5616_PWR_DIG1 0x61 +#define RT5616_PWR_DIG2 0x62 +#define RT5616_PWR_ANLG1 0x63 +#define RT5616_PWR_ANLG2 0x64 +#define RT5616_PWR_MIXER 0x65 +#define RT5616_PWR_VOL 0x66 +/* Private Register Control */ +#define RT5616_PRIV_INDEX 0x6a +#define RT5616_PRIV_DATA 0x6c +/* Format - ADC/DAC */ +#define RT5616_I2S1_SDP 0x70 +#define RT5616_ADDA_CLK1 0x73 +#define RT5616_ADDA_CLK2 0x74 + +/* Function - Analog */ +#define RT5616_GLB_CLK 0x80 +#define RT5616_PLL_CTRL1 0x81 +#define RT5616_PLL_CTRL2 0x82 +#define RT5616_HP_OVCD 0x8b +#define RT5616_DEPOP_M1 0x8e +#define RT5616_DEPOP_M2 0x8f +#define RT5616_DEPOP_M3 0x90 +#define RT5616_CHARGE_PUMP 0x91 +#define RT5616_PV_DET_SPK_G 0x92 +#define RT5616_MICBIAS 0x93 +#define RT5616_A_JD_CTL1 0x94 +#define RT5616_A_JD_CTL2 0x95 +/* Function - Digital */ +#define RT5616_EQ_CTRL1 0xb0 +#define RT5616_EQ_CTRL2 0xb1 +#define RT5616_WIND_FILTER 0xb2 +#define RT5616_DRC_AGC_1 0xb4 +#define RT5616_DRC_AGC_2 0xb5 +#define RT5616_DRC_AGC_3 0xb6 +#define RT5616_SVOL_ZC 0xb7 +#define RT5616_JD_CTRL1 0xbb +#define RT5616_JD_CTRL2 0xbc +#define RT5616_IRQ_CTRL1 0xbd +#define RT5616_IRQ_CTRL2 0xbe +#define RT5616_INT_IRQ_ST 0xbf +#define RT5616_GPIO_CTRL1 0xc0 +#define RT5616_GPIO_CTRL2 0xc1 +#define RT5616_GPIO_CTRL3 0xc2 +#define RT5616_PGM_REG_ARR1 0xc8 +#define RT5616_PGM_REG_ARR2 0xc9 +#define RT5616_PGM_REG_ARR3 0xca +#define RT5616_PGM_REG_ARR4 0xcb +#define RT5616_PGM_REG_ARR5 0xcc +#define RT5616_SCB_FUNC 0xcd +#define RT5616_SCB_CTRL 0xce +#define RT5616_BASE_BACK 0xcf +#define RT5616_MP3_PLUS1 0xd0 +#define RT5616_MP3_PLUS2 0xd1 +#define RT5616_ADJ_HPF_CTRL1 0xd3 +#define RT5616_ADJ_HPF_CTRL2 0xd4 +#define RT5616_HP_CALIB_AMP_DET 0xd6 +#define RT5616_HP_CALIB2 0xd7 +#define RT5616_SV_ZCD1 0xd9 +#define RT5616_SV_ZCD2 0xda +#define RT5616_D_MISC 0xfa +/* Dummy Register */ +#define RT5616_DUMMY2 0xfb +#define RT5616_DUMMY3 0xfc + + +/* Index of Codec Private Register definition */ +#define RT5616_BIAS_CUR1 0x12 +#define RT5616_BIAS_CUR3 0x14 +#define RT5616_CLSD_INT_REG1 0x1c +#define RT5616_MAMP_INT_REG2 0x37 +#define RT5616_CHOP_DAC_ADC 0x3d +#define RT5616_3D_SPK 0x63 +#define RT5616_WND_1 0x6c +#define RT5616_WND_2 0x6d +#define RT5616_WND_3 0x6e +#define RT5616_WND_4 0x6f +#define RT5616_WND_5 0x70 +#define RT5616_WND_8 0x73 +#define RT5616_DIP_SPK_INF 0x75 +#define RT5616_HP_DCC_INT1 0x77 +#define RT5616_EQ_BW_LOP 0xa0 +#define RT5616_EQ_GN_LOP 0xa1 +#define RT5616_EQ_FC_BP1 0xa2 +#define RT5616_EQ_BW_BP1 0xa3 +#define RT5616_EQ_GN_BP1 0xa4 +#define RT5616_EQ_FC_BP2 0xa5 +#define RT5616_EQ_BW_BP2 0xa6 +#define RT5616_EQ_GN_BP2 0xa7 +#define RT5616_EQ_FC_BP3 0xa8 +#define RT5616_EQ_BW_BP3 0xa9 +#define RT5616_EQ_GN_BP3 0xaa +#define RT5616_EQ_FC_BP4 0xab +#define RT5616_EQ_BW_BP4 0xac +#define RT5616_EQ_GN_BP4 0xad +#define RT5616_EQ_FC_HIP1 0xae +#define RT5616_EQ_GN_HIP1 0xaf +#define RT5616_EQ_FC_HIP2 0xb0 +#define RT5616_EQ_BW_HIP2 0xb1 +#define RT5616_EQ_GN_HIP2 0xb2 +#define RT5616_EQ_PRE_VOL 0xb3 +#define RT5616_EQ_PST_VOL 0xb4 + + +/* global definition */ +#define RT5616_L_MUTE (0x1 << 15) +#define RT5616_L_MUTE_SFT 15 +#define RT5616_VOL_L_MUTE (0x1 << 14) +#define RT5616_VOL_L_SFT 14 +#define RT5616_R_MUTE (0x1 << 7) +#define RT5616_R_MUTE_SFT 7 +#define RT5616_VOL_R_MUTE (0x1 << 6) +#define RT5616_VOL_R_SFT 6 +#define RT5616_L_VOL_MASK (0x3f << 8) +#define RT5616_L_VOL_SFT 8 +#define RT5616_R_VOL_MASK (0x3f) +#define RT5616_R_VOL_SFT 0 + +/* LOUT Control 2(0x05) */ +#define RT5616_EN_DFO (0x1 << 15) + +/* IN1 and IN2 Control (0x0d) */ +/* IN3 and IN4 Control (0x0e) */ +#define RT5616_BST_MASK1 (0xf<<12) +#define RT5616_BST_SFT1 12 +#define RT5616_BST_MASK2 (0xf<<8) +#define RT5616_BST_SFT2 8 +#define RT5616_IN_DF1 (0x1 << 7) +#define RT5616_IN_SFT1 7 +#define RT5616_IN_DF2 (0x1 << 6) +#define RT5616_IN_SFT2 6 + +/* INL1 and INR1 Volume Control (0x0f) */ +#define RT5616_INL_VOL_MASK (0x1f << 8) +#define RT5616_INL_VOL_SFT 8 +#define RT5616_INR_SEL_MASK (0x1 << 7) +#define RT5616_INR_SEL_SFT 7 +#define RT5616_INR_SEL_IN4N (0x0 << 7) +#define RT5616_INR_SEL_MONON (0x1 << 7) +#define RT5616_INR_VOL_MASK (0x1f) +#define RT5616_INR_VOL_SFT 0 + +/* DAC1 Digital Volume (0x19) */ +#define RT5616_DAC_L1_VOL_MASK (0xff << 8) +#define RT5616_DAC_L1_VOL_SFT 8 +#define RT5616_DAC_R1_VOL_MASK (0xff) +#define RT5616_DAC_R1_VOL_SFT 0 + +/* DAC2 Digital Volume (0x1a) */ +#define RT5616_DAC_L2_VOL_MASK (0xff << 8) +#define RT5616_DAC_L2_VOL_SFT 8 +#define RT5616_DAC_R2_VOL_MASK (0xff) +#define RT5616_DAC_R2_VOL_SFT 0 + +/* ADC Digital Volume Control (0x1c) */ +#define RT5616_ADC_L_VOL_MASK (0x7f << 8) +#define RT5616_ADC_L_VOL_SFT 8 +#define RT5616_ADC_R_VOL_MASK (0x7f) +#define RT5616_ADC_R_VOL_SFT 0 + +/* Mono ADC Digital Volume Control (0x1d) */ +#define RT5616_M_MONO_ADC_L (0x1 << 15) +#define RT5616_M_MONO_ADC_L_SFT 15 +#define RT5616_MONO_ADC_L_VOL_MASK (0x7f << 8) +#define RT5616_MONO_ADC_L_VOL_SFT 8 +#define RT5616_M_MONO_ADC_R (0x1 << 7) +#define RT5616_M_MONO_ADC_R_SFT 7 +#define RT5616_MONO_ADC_R_VOL_MASK (0x7f) +#define RT5616_MONO_ADC_R_VOL_SFT 0 + +/* ADC Boost Volume Control (0x1e) */ +#define RT5616_ADC_L_BST_MASK (0x3 << 14) +#define RT5616_ADC_L_BST_SFT 14 +#define RT5616_ADC_R_BST_MASK (0x3 << 12) +#define RT5616_ADC_R_BST_SFT 12 +#define RT5616_ADC_COMP_MASK (0x3 << 10) +#define RT5616_ADC_COMP_SFT 10 + +/* Stereo ADC1 Mixer Control (0x27) */ +#define RT5616_M_STO1_ADC_L1 (0x1 << 14) +#define RT5616_M_STO1_ADC_L1_SFT 14 +#define RT5616_M_STO1_ADC_R1 (0x1 << 6) +#define RT5616_M_STO1_ADC_R1_SFT 6 + +/* ADC Mixer to DAC Mixer Control (0x29) */ +#define RT5616_M_ADCMIX_L (0x1 << 15) +#define RT5616_M_ADCMIX_L_SFT 15 +#define RT5616_M_IF1_DAC_L (0x1 << 14) +#define RT5616_M_IF1_DAC_L_SFT 14 +#define RT5616_M_ADCMIX_R (0x1 << 7) +#define RT5616_M_ADCMIX_R_SFT 7 +#define RT5616_M_IF1_DAC_R (0x1 << 6) +#define RT5616_M_IF1_DAC_R_SFT 6 + +/* Stereo DAC Mixer Control (0x2a) */ +#define RT5616_M_DAC_L1_MIXL (0x1 << 14) +#define RT5616_M_DAC_L1_MIXL_SFT 14 +#define RT5616_DAC_L1_STO_L_VOL_MASK (0x1 << 13) +#define RT5616_DAC_L1_STO_L_VOL_SFT 13 +#define RT5616_M_DAC_R1_MIXL (0x1 << 9) +#define RT5616_M_DAC_R1_MIXL_SFT 9 +#define RT5616_DAC_R1_STO_L_VOL_MASK (0x1 << 8) +#define RT5616_DAC_R1_STO_L_VOL_SFT 8 +#define RT5616_M_DAC_R1_MIXR (0x1 << 6) +#define RT5616_M_DAC_R1_MIXR_SFT 6 +#define RT5616_DAC_R1_STO_R_VOL_MASK (0x1 << 5) +#define RT5616_DAC_R1_STO_R_VOL_SFT 5 +#define RT5616_M_DAC_L1_MIXR (0x1 << 1) +#define RT5616_M_DAC_L1_MIXR_SFT 1 +#define RT5616_DAC_L1_STO_R_VOL_MASK (0x1) +#define RT5616_DAC_L1_STO_R_VOL_SFT 0 + +/* DD Mixer Control (0x2b) */ +#define RT5616_M_STO_DD_L1 (0x1 << 14) +#define RT5616_M_STO_DD_L1_SFT 14 +#define RT5616_STO_DD_L1_VOL_MASK (0x1 << 13) +#define RT5616_DAC_DD_L1_VOL_SFT 13 +#define RT5616_M_STO_DD_L2 (0x1 << 12) +#define RT5616_M_STO_DD_L2_SFT 12 +#define RT5616_STO_DD_L2_VOL_MASK (0x1 << 11) +#define RT5616_STO_DD_L2_VOL_SFT 11 +#define RT5616_M_STO_DD_R2_L (0x1 << 10) +#define RT5616_M_STO_DD_R2_L_SFT 10 +#define RT5616_STO_DD_R2_L_VOL_MASK (0x1 << 9) +#define RT5616_STO_DD_R2_L_VOL_SFT 9 +#define RT5616_M_STO_DD_R1 (0x1 << 6) +#define RT5616_M_STO_DD_R1_SFT 6 +#define RT5616_STO_DD_R1_VOL_MASK (0x1 << 5) +#define RT5616_STO_DD_R1_VOL_SFT 5 +#define RT5616_M_STO_DD_R2 (0x1 << 4) +#define RT5616_M_STO_DD_R2_SFT 4 +#define RT5616_STO_DD_R2_VOL_MASK (0x1 << 3) +#define RT5616_STO_DD_R2_VOL_SFT 3 +#define RT5616_M_STO_DD_L2_R (0x1 << 2) +#define RT5616_M_STO_DD_L2_R_SFT 2 +#define RT5616_STO_DD_L2_R_VOL_MASK (0x1 << 1) +#define RT5616_STO_DD_L2_R_VOL_SFT 1 + +/* Digital Mixer Control (0x2c) */ +#define RT5616_M_STO_L_DAC_L (0x1 << 15) +#define RT5616_M_STO_L_DAC_L_SFT 15 +#define RT5616_STO_L_DAC_L_VOL_MASK (0x1 << 14) +#define RT5616_STO_L_DAC_L_VOL_SFT 14 +#define RT5616_M_DAC_L2_DAC_L (0x1 << 13) +#define RT5616_M_DAC_L2_DAC_L_SFT 13 +#define RT5616_DAC_L2_DAC_L_VOL_MASK (0x1 << 12) +#define RT5616_DAC_L2_DAC_L_VOL_SFT 12 +#define RT5616_M_STO_R_DAC_R (0x1 << 11) +#define RT5616_M_STO_R_DAC_R_SFT 11 +#define RT5616_STO_R_DAC_R_VOL_MASK (0x1 << 10) +#define RT5616_STO_R_DAC_R_VOL_SFT 10 +#define RT5616_M_DAC_R2_DAC_R (0x1 << 9) +#define RT5616_M_DAC_R2_DAC_R_SFT 9 +#define RT5616_DAC_R2_DAC_R_VOL_MASK (0x1 << 8) +#define RT5616_DAC_R2_DAC_R_VOL_SFT 8 + +/* DSP Path Control 1 (0x2d) */ +#define RT5616_RXDP_SRC_MASK (0x1 << 15) +#define RT5616_RXDP_SRC_SFT 15 +#define RT5616_RXDP_SRC_NOR (0x0 << 15) +#define RT5616_RXDP_SRC_DIV3 (0x1 << 15) +#define RT5616_TXDP_SRC_MASK (0x1 << 14) +#define RT5616_TXDP_SRC_SFT 14 +#define RT5616_TXDP_SRC_NOR (0x0 << 14) +#define RT5616_TXDP_SRC_DIV3 (0x1 << 14) + +/* DSP Path Control 2 (0x2e) */ +#define RT5616_DAC_L2_SEL_MASK (0x3 << 14) +#define RT5616_DAC_L2_SEL_SFT 14 +#define RT5616_DAC_L2_SEL_IF2 (0x0 << 14) +#define RT5616_DAC_L2_SEL_IF3 (0x1 << 14) +#define RT5616_DAC_L2_SEL_TXDC (0x2 << 14) +#define RT5616_DAC_L2_SEL_BASS (0x3 << 14) +#define RT5616_DAC_R2_SEL_MASK (0x3 << 12) +#define RT5616_DAC_R2_SEL_SFT 12 +#define RT5616_DAC_R2_SEL_IF2 (0x0 << 12) +#define RT5616_DAC_R2_SEL_IF3 (0x1 << 12) +#define RT5616_DAC_R2_SEL_TXDC (0x2 << 12) +#define RT5616_IF2_ADC_L_SEL_MASK (0x1 << 11) +#define RT5616_IF2_ADC_L_SEL_SFT 11 +#define RT5616_IF2_ADC_L_SEL_TXDP (0x0 << 11) +#define RT5616_IF2_ADC_L_SEL_PASS (0x1 << 11) +#define RT5616_IF2_ADC_R_SEL_MASK (0x1 << 10) +#define RT5616_IF2_ADC_R_SEL_SFT 10 +#define RT5616_IF2_ADC_R_SEL_TXDP (0x0 << 10) +#define RT5616_IF2_ADC_R_SEL_PASS (0x1 << 10) +#define RT5616_RXDC_SEL_MASK (0x3 << 8) +#define RT5616_RXDC_SEL_SFT 8 +#define RT5616_RXDC_SEL_NOR (0x0 << 8) +#define RT5616_RXDC_SEL_L2R (0x1 << 8) +#define RT5616_RXDC_SEL_R2L (0x2 << 8) +#define RT5616_RXDC_SEL_SWAP (0x3 << 8) +#define RT5616_RXDP_SEL_MASK (0x3 << 6) +#define RT5616_RXDP_SEL_SFT 6 +#define RT5616_RXDP_SEL_NOR (0x0 << 6) +#define RT5616_RXDP_SEL_L2R (0x1 << 6) +#define RT5616_RXDP_SEL_R2L (0x2 << 6) +#define RT5616_RXDP_SEL_SWAP (0x3 << 6) +#define RT5616_TXDC_SEL_MASK (0x3 << 4) +#define RT5616_TXDC_SEL_SFT 4 +#define RT5616_TXDC_SEL_NOR (0x0 << 4) +#define RT5616_TXDC_SEL_L2R (0x1 << 4) +#define RT5616_TXDC_SEL_R2L (0x2 << 4) +#define RT5616_TXDC_SEL_SWAP (0x3 << 4) +#define RT5616_TXDP_SEL_MASK (0x3 << 2) +#define RT5616_TXDP_SEL_SFT 2 +#define RT5616_TXDP_SEL_NOR (0x0 << 2) +#define RT5616_TXDP_SEL_L2R (0x1 << 2) +#define RT5616_TXDP_SEL_R2L (0x2 << 2) +#define RT5616_TRXDP_SEL_SWAP (0x3 << 2) + +/* REC Left Mixer Control 1 (0x3b) */ +#define RT5616_G_LN_L2_RM_L_MASK (0x7 << 13) +#define RT5616_G_IN_L2_RM_L_SFT 13 +#define RT5616_G_LN_L1_RM_L_MASK (0x7 << 10) +#define RT5616_G_IN_L1_RM_L_SFT 10 +#define RT5616_G_BST3_RM_L_MASK (0x7 << 4) +#define RT5616_G_BST3_RM_L_SFT 4 +#define RT5616_G_BST2_RM_L_MASK (0x7 << 1) +#define RT5616_G_BST2_RM_L_SFT 1 + +/* REC Left Mixer Control 2 (0x3c) */ +#define RT5616_G_BST1_RM_L_MASK (0x7 << 13) +#define RT5616_G_BST1_RM_L_SFT 13 +#define RT5616_G_OM_L_RM_L_MASK (0x7 << 10) +#define RT5616_G_OM_L_RM_L_SFT 10 +#define RT5616_M_IN2_L_RM_L (0x1 << 6) +#define RT5616_M_IN2_L_RM_L_SFT 6 +#define RT5616_M_IN1_L_RM_L (0x1 << 5) +#define RT5616_M_IN1_L_RM_L_SFT 5 +#define RT5616_M_BST3_RM_L (0x1 << 3) +#define RT5616_M_BST3_RM_L_SFT 3 +#define RT5616_M_BST2_RM_L (0x1 << 2) +#define RT5616_M_BST2_RM_L_SFT 2 +#define RT5616_M_BST1_RM_L (0x1 << 1) +#define RT5616_M_BST1_RM_L_SFT 1 +#define RT5616_M_OM_L_RM_L (0x1) +#define RT5616_M_OM_L_RM_L_SFT 0 + +/* REC Right Mixer Control 1 (0x3d) */ +#define RT5616_G_IN2_R_RM_R_MASK (0x7 << 13) +#define RT5616_G_IN2_R_RM_R_SFT 13 +#define RT5616_G_IN1_R_RM_R_MASK (0x7 << 10) +#define RT5616_G_IN1_R_RM_R_SFT 10 +#define RT5616_G_BST3_RM_R_MASK (0x7 << 4) +#define RT5616_G_BST3_RM_R_SFT 4 +#define RT5616_G_BST2_RM_R_MASK (0x7 << 1) +#define RT5616_G_BST2_RM_R_SFT 1 + +/* REC Right Mixer Control 2 (0x3e) */ +#define RT5616_G_BST1_RM_R_MASK (0x7 << 13) +#define RT5616_G_BST1_RM_R_SFT 13 +#define RT5616_G_OM_R_RM_R_MASK (0x7 << 10) +#define RT5616_G_OM_R_RM_R_SFT 10 +#define RT5616_M_IN2_R_RM_R (0x1 << 6) +#define RT5616_M_IN2_R_RM_R_SFT 6 +#define RT5616_M_IN1_R_RM_R (0x1 << 5) +#define RT5616_M_IN1_R_RM_R_SFT 5 +#define RT5616_M_BST3_RM_R (0x1 << 3) +#define RT5616_M_BST3_RM_R_SFT 3 +#define RT5616_M_BST2_RM_R (0x1 << 2) +#define RT5616_M_BST2_RM_R_SFT 2 +#define RT5616_M_BST1_RM_R (0x1 << 1) +#define RT5616_M_BST1_RM_R_SFT 1 +#define RT5616_M_OM_R_RM_R (0x1) +#define RT5616_M_OM_R_RM_R_SFT 0 + +/* HPMIX Control (0x45) */ +#define RT5616_M_DAC1_HM (0x1 << 14) +#define RT5616_M_DAC1_HM_SFT 14 +#define RT5616_M_HPVOL_HM (0x1 << 13) +#define RT5616_M_HPVOL_HM_SFT 13 +#define RT5616_G_HPOMIX_MASK (0x1 << 12) +#define RT5616_G_HPOMIX_SFT 12 + +/* SPK Left Mixer Control (0x46) */ +#define RT5616_G_RM_L_SM_L_MASK (0x3 << 14) +#define RT5616_G_RM_L_SM_L_SFT 14 +#define RT5616_G_IN_L_SM_L_MASK (0x3 << 12) +#define RT5616_G_IN_L_SM_L_SFT 12 +#define RT5616_G_DAC_L1_SM_L_MASK (0x3 << 10) +#define RT5616_G_DAC_L1_SM_L_SFT 10 +#define RT5616_G_DAC_L2_SM_L_MASK (0x3 << 8) +#define RT5616_G_DAC_L2_SM_L_SFT 8 +#define RT5616_G_OM_L_SM_L_MASK (0x3 << 6) +#define RT5616_G_OM_L_SM_L_SFT 6 +#define RT5616_M_RM_L_SM_L (0x1 << 5) +#define RT5616_M_RM_L_SM_L_SFT 5 +#define RT5616_M_IN_L_SM_L (0x1 << 4) +#define RT5616_M_IN_L_SM_L_SFT 4 +#define RT5616_M_DAC_L1_SM_L (0x1 << 3) +#define RT5616_M_DAC_L1_SM_L_SFT 3 +#define RT5616_M_DAC_L2_SM_L (0x1 << 2) +#define RT5616_M_DAC_L2_SM_L_SFT 2 +#define RT5616_M_OM_L_SM_L (0x1 << 1) +#define RT5616_M_OM_L_SM_L_SFT 1 + +/* SPK Right Mixer Control (0x47) */ +#define RT5616_G_RM_R_SM_R_MASK (0x3 << 14) +#define RT5616_G_RM_R_SM_R_SFT 14 +#define RT5616_G_IN_R_SM_R_MASK (0x3 << 12) +#define RT5616_G_IN_R_SM_R_SFT 12 +#define RT5616_G_DAC_R1_SM_R_MASK (0x3 << 10) +#define RT5616_G_DAC_R1_SM_R_SFT 10 +#define RT5616_G_DAC_R2_SM_R_MASK (0x3 << 8) +#define RT5616_G_DAC_R2_SM_R_SFT 8 +#define RT5616_G_OM_R_SM_R_MASK (0x3 << 6) +#define RT5616_G_OM_R_SM_R_SFT 6 +#define RT5616_M_RM_R_SM_R (0x1 << 5) +#define RT5616_M_RM_R_SM_R_SFT 5 +#define RT5616_M_IN_R_SM_R (0x1 << 4) +#define RT5616_M_IN_R_SM_R_SFT 4 +#define RT5616_M_DAC_R1_SM_R (0x1 << 3) +#define RT5616_M_DAC_R1_SM_R_SFT 3 +#define RT5616_M_DAC_R2_SM_R (0x1 << 2) +#define RT5616_M_DAC_R2_SM_R_SFT 2 +#define RT5616_M_OM_R_SM_R (0x1 << 1) +#define RT5616_M_OM_R_SM_R_SFT 1 + +/* SPOLMIX Control (0x48) */ +#define RT5616_M_DAC_R1_SPM_L (0x1 << 15) +#define RT5616_M_DAC_R1_SPM_L_SFT 15 +#define RT5616_M_DAC_L1_SPM_L (0x1 << 14) +#define RT5616_M_DAC_L1_SPM_L_SFT 14 +#define RT5616_M_SV_R_SPM_L (0x1 << 13) +#define RT5616_M_SV_R_SPM_L_SFT 13 +#define RT5616_M_SV_L_SPM_L (0x1 << 12) +#define RT5616_M_SV_L_SPM_L_SFT 12 +#define RT5616_M_BST1_SPM_L (0x1 << 11) +#define RT5616_M_BST1_SPM_L_SFT 11 + +/* SPORMIX Control (0x49) */ +#define RT5616_M_DAC_R1_SPM_R (0x1 << 13) +#define RT5616_M_DAC_R1_SPM_R_SFT 13 +#define RT5616_M_SV_R_SPM_R (0x1 << 12) +#define RT5616_M_SV_R_SPM_R_SFT 12 +#define RT5616_M_BST1_SPM_R (0x1 << 11) +#define RT5616_M_BST1_SPM_R_SFT 11 + +/* SPOLMIX / SPORMIX Ratio Control (0x4a) */ +#define RT5616_SPO_CLSD_RATIO_MASK (0x7) +#define RT5616_SPO_CLSD_RATIO_SFT 0 + +/* Mono Output Mixer Control (0x4c) */ +#define RT5616_M_DAC_R2_MM (0x1 << 15) +#define RT5616_M_DAC_R2_MM_SFT 15 +#define RT5616_M_DAC_L2_MM (0x1 << 14) +#define RT5616_M_DAC_L2_MM_SFT 14 +#define RT5616_M_OV_R_MM (0x1 << 13) +#define RT5616_M_OV_R_MM_SFT 13 +#define RT5616_M_OV_L_MM (0x1 << 12) +#define RT5616_M_OV_L_MM_SFT 12 +#define RT5616_M_BST1_MM (0x1 << 11) +#define RT5616_M_BST1_MM_SFT 11 +#define RT5616_G_MONOMIX_MASK (0x1 << 10) +#define RT5616_G_MONOMIX_SFT 10 + +/* Output Left Mixer Control 1 (0x4d) */ +#define RT5616_G_BST2_OM_L_MASK (0x7 << 10) +#define RT5616_G_BST2_OM_L_SFT 10 +#define RT5616_G_BST1_OM_L_MASK (0x7 << 7) +#define RT5616_G_BST1_OM_L_SFT 7 +#define RT5616_G_IN1_L_OM_L_MASK (0x7 << 4) +#define RT5616_G_IN1_L_OM_L_SFT 4 +#define RT5616_G_RM_L_OM_L_MASK (0x7 << 1) +#define RT5616_G_RM_L_OM_L_SFT 1 + +/* Output Left Mixer Control 2 (0x4e) */ +#define RT5616_G_DAC_L1_OM_L_MASK (0x7 << 7) +#define RT5616_G_DAC_L1_OM_L_SFT 7 +#define RT5616_G_IN2_L_OM_L_MASK (0x7 << 4) +#define RT5616_G_IN2_L_OM_L_SFT 4 + +/* Output Left Mixer Control 3 (0x4f) */ +#define RT5616_M_IN2_L_OM_L (0x1 << 9) +#define RT5616_M_IN2_L_OM_L_SFT 9 +#define RT5616_M_BST2_OM_L (0x1 << 6) +#define RT5616_M_BST2_OM_L_SFT 6 +#define RT5616_M_BST1_OM_L (0x1 << 5) +#define RT5616_M_BST1_OM_L_SFT 5 +#define RT5616_M_IN1_L_OM_L (0x1 << 4) +#define RT5616_M_IN1_L_OM_L_SFT 4 +#define RT5616_M_RM_L_OM_L (0x1 << 3) +#define RT5616_M_RM_L_OM_L_SFT 3 +#define RT5616_M_DAC_L1_OM_L (0x1) +#define RT5616_M_DAC_L1_OM_L_SFT 0 + +/* Output Right Mixer Control 1 (0x50) */ +#define RT5616_G_BST2_OM_R_MASK (0x7 << 10) +#define RT5616_G_BST2_OM_R_SFT 10 +#define RT5616_G_BST1_OM_R_MASK (0x7 << 7) +#define RT5616_G_BST1_OM_R_SFT 7 +#define RT5616_G_IN1_R_OM_R_MASK (0x7 << 4) +#define RT5616_G_IN1_R_OM_R_SFT 4 +#define RT5616_G_RM_R_OM_R_MASK (0x7 << 1) +#define RT5616_G_RM_R_OM_R_SFT 1 + +/* Output Right Mixer Control 2 (0x51) */ +#define RT5616_G_DAC_R1_OM_R_MASK (0x7 << 7) +#define RT5616_G_DAC_R1_OM_R_SFT 7 +#define RT5616_G_IN2_R_OM_R_MASK (0x7 << 4) +#define RT5616_G_IN2_R_OM_R_SFT 4 + +/* Output Right Mixer Control 3 (0x52) */ +#define RT5616_M_IN2_R_OM_R (0x1 << 9) +#define RT5616_M_IN2_R_OM_R_SFT 9 +#define RT5616_M_BST2_OM_R (0x1 << 6) +#define RT5616_M_BST2_OM_R_SFT 6 +#define RT5616_M_BST1_OM_R (0x1 << 5) +#define RT5616_M_BST1_OM_R_SFT 5 +#define RT5616_M_IN1_R_OM_R (0x1 << 4) +#define RT5616_M_IN1_R_OM_R_SFT 4 +#define RT5616_M_RM_R_OM_R (0x1 << 3) +#define RT5616_M_RM_R_OM_R_SFT 3 +#define RT5616_M_DAC_R1_OM_R (0x1) +#define RT5616_M_DAC_R1_OM_R_SFT 0 + +/* LOUT Mixer Control (0x53) */ +#define RT5616_M_DAC_L1_LM (0x1 << 15) +#define RT5616_M_DAC_L1_LM_SFT 15 +#define RT5616_M_DAC_R1_LM (0x1 << 14) +#define RT5616_M_DAC_R1_LM_SFT 14 +#define RT5616_M_OV_L_LM (0x1 << 13) +#define RT5616_M_OV_L_LM_SFT 13 +#define RT5616_M_OV_R_LM (0x1 << 12) +#define RT5616_M_OV_R_LM_SFT 12 +#define RT5616_G_LOUTMIX_MASK (0x1 << 11) +#define RT5616_G_LOUTMIX_SFT 11 + +/* Power Management for Digital 1 (0x61) */ +#define RT5616_PWR_I2S1 (0x1 << 15) +#define RT5616_PWR_I2S1_BIT 15 +#define RT5616_PWR_I2S2 (0x1 << 14) +#define RT5616_PWR_I2S2_BIT 14 +#define RT5616_PWR_DAC_L1 (0x1 << 12) +#define RT5616_PWR_DAC_L1_BIT 12 +#define RT5616_PWR_DAC_R1 (0x1 << 11) +#define RT5616_PWR_DAC_R1_BIT 11 +#define RT5616_PWR_ADC_L (0x1 << 2) +#define RT5616_PWR_ADC_L_BIT 2 +#define RT5616_PWR_ADC_R (0x1 << 1) +#define RT5616_PWR_ADC_R_BIT 1 + +/* Power Management for Digital 2 (0x62) */ +#define RT5616_PWR_ADC_STO1_F (0x1 << 15) +#define RT5616_PWR_ADC_STO1_F_BIT 15 +#define RT5616_PWR_DAC_STO1_F (0x1 << 11) +#define RT5616_PWR_DAC_STO1_F_BIT 11 + +/* Power Management for Analog 1 (0x63) */ +#define RT5616_PWR_VREF1 (0x1 << 15) +#define RT5616_PWR_VREF1_BIT 15 +#define RT5616_PWR_FV1 (0x1 << 14) +#define RT5616_PWR_FV1_BIT 14 +#define RT5616_PWR_MB (0x1 << 13) +#define RT5616_PWR_MB_BIT 13 +#define RT5616_PWR_LM (0x1 << 12) +#define RT5616_PWR_LM_BIT 12 +#define RT5616_PWR_BG (0x1 << 11) +#define RT5616_PWR_BG_BIT 11 +#define RT5616_PWR_HP_L (0x1 << 7) +#define RT5616_PWR_HP_L_BIT 7 +#define RT5616_PWR_HP_R (0x1 << 6) +#define RT5616_PWR_HP_R_BIT 6 +#define RT5616_PWR_HA (0x1 << 5) +#define RT5616_PWR_HA_BIT 5 +#define RT5616_PWR_VREF2 (0x1 << 4) +#define RT5616_PWR_VREF2_BIT 4 +#define RT5616_PWR_FV2 (0x1 << 3) +#define RT5616_PWR_FV2_BIT 3 +#define RT5616_PWR_LDO (0x1 << 2) +#define RT5616_PWR_LDO_BIT 2 +#define RT5616_PWR_LDO_DVO_MASK (0x3) +#define RT5616_PWR_LDO_DVO_1_0V 0 +#define RT5616_PWR_LDO_DVO_1_1V 1 +#define RT5616_PWR_LDO_DVO_1_2V 2 +#define RT5616_PWR_LDO_DVO_1_3V 3 + +/* Power Management for Analog 2 (0x64) */ +#define RT5616_PWR_BST1 (0x1 << 15) +#define RT5616_PWR_BST1_BIT 15 +#define RT5616_PWR_BST2 (0x1 << 14) +#define RT5616_PWR_BST2_BIT 14 +#define RT5616_PWR_MB1 (0x1 << 11) +#define RT5616_PWR_MB1_BIT 11 +#define RT5616_PWR_PLL (0x1 << 9) +#define RT5616_PWR_PLL_BIT 9 +#define RT5616_PWR_BST1_OP2 (0x1 << 5) +#define RT5616_PWR_BST1_OP2_BIT 5 +#define RT5616_PWR_BST2_OP2 (0x1 << 4) +#define RT5616_PWR_BST2_OP2_BIT 4 +#define RT5616_PWR_BST3_OP2 (0x1 << 3) +#define RT5616_PWR_BST3_OP2_BIT 3 +#define RT5616_PWR_JD_M (0x1 << 2) +#define RT5616_PWM_JD_M_BIT 2 +#define RT5616_PWR_JD2 (0x1 << 1) +#define RT5616_PWM_JD2_BIT 1 +#define RT5616_PWR_JD3 (0x1) +#define RT5616_PWM_JD3_BIT 0 + +/* Power Management for Mixer (0x65) */ +#define RT5616_PWR_OM_L (0x1 << 15) +#define RT5616_PWR_OM_L_BIT 15 +#define RT5616_PWR_OM_R (0x1 << 14) +#define RT5616_PWR_OM_R_BIT 14 +#define RT5616_PWR_RM_L (0x1 << 11) +#define RT5616_PWR_RM_L_BIT 11 +#define RT5616_PWR_RM_R (0x1 << 10) +#define RT5616_PWR_RM_R_BIT 10 + +/* Power Management for Volume (0x66) */ +#define RT5616_PWR_OV_L (0x1 << 13) +#define RT5616_PWR_OV_L_BIT 13 +#define RT5616_PWR_OV_R (0x1 << 12) +#define RT5616_PWR_OV_R_BIT 12 +#define RT5616_PWR_HV_L (0x1 << 11) +#define RT5616_PWR_HV_L_BIT 11 +#define RT5616_PWR_HV_R (0x1 << 10) +#define RT5616_PWR_HV_R_BIT 10 +#define RT5616_PWR_IN1_L (0x1 << 9) +#define RT5616_PWR_IN1_L_BIT 9 +#define RT5616_PWR_IN1_R (0x1 << 8) +#define RT5616_PWR_IN1_R_BIT 8 +#define RT5616_PWR_IN2_L (0x1 << 7) +#define RT5616_PWR_IN2_L_BIT 7 +#define RT5616_PWR_IN2_R (0x1 << 6) +#define RT5616_PWR_IN2_R_BIT 6 + +/* I2S1/2/3 Audio Serial Data Port Control (0x70 0x71) */ +#define RT5616_I2S_MS_MASK (0x1 << 15) +#define RT5616_I2S_MS_SFT 15 +#define RT5616_I2S_MS_M (0x0 << 15) +#define RT5616_I2S_MS_S (0x1 << 15) +#define RT5616_I2S_O_CP_MASK (0x3 << 10) +#define RT5616_I2S_O_CP_SFT 10 +#define RT5616_I2S_O_CP_OFF (0x0 << 10) +#define RT5616_I2S_O_CP_U_LAW (0x1 << 10) +#define RT5616_I2S_O_CP_A_LAW (0x2 << 10) +#define RT5616_I2S_I_CP_MASK (0x3 << 8) +#define RT5616_I2S_I_CP_SFT 8 +#define RT5616_I2S_I_CP_OFF (0x0 << 8) +#define RT5616_I2S_I_CP_U_LAW (0x1 << 8) +#define RT5616_I2S_I_CP_A_LAW (0x2 << 8) +#define RT5616_I2S_BP_MASK (0x1 << 7) +#define RT5616_I2S_BP_SFT 7 +#define RT5616_I2S_BP_NOR (0x0 << 7) +#define RT5616_I2S_BP_INV (0x1 << 7) +#define RT5616_I2S_DL_MASK (0x3 << 2) +#define RT5616_I2S_DL_SFT 2 +#define RT5616_I2S_DL_16 (0x0 << 2) +#define RT5616_I2S_DL_20 (0x1 << 2) +#define RT5616_I2S_DL_24 (0x2 << 2) +#define RT5616_I2S_DL_8 (0x3 << 2) +#define RT5616_I2S_DF_MASK (0x3) +#define RT5616_I2S_DF_SFT 0 +#define RT5616_I2S_DF_I2S (0x0) +#define RT5616_I2S_DF_LEFT (0x1) +#define RT5616_I2S_DF_PCM_A (0x2) +#define RT5616_I2S_DF_PCM_B (0x3) + +/* ADC/DAC Clock Control 1 (0x73) */ +#define RT5616_I2S_PD1_MASK (0x7 << 12) +#define RT5616_I2S_PD1_SFT 12 +#define RT5616_I2S_PD1_1 (0x0 << 12) +#define RT5616_I2S_PD1_2 (0x1 << 12) +#define RT5616_I2S_PD1_3 (0x2 << 12) +#define RT5616_I2S_PD1_4 (0x3 << 12) +#define RT5616_I2S_PD1_6 (0x4 << 12) +#define RT5616_I2S_PD1_8 (0x5 << 12) +#define RT5616_I2S_PD1_12 (0x6 << 12) +#define RT5616_I2S_PD1_16 (0x7 << 12) +#define RT5616_I2S_BCLK_MS2_MASK (0x1 << 11) +#define RT5616_DAC_OSR_MASK (0x3 << 2) +#define RT5616_DAC_OSR_SFT 2 +#define RT5616_DAC_OSR_128 (0x0 << 2) +#define RT5616_DAC_OSR_64 (0x1 << 2) +#define RT5616_DAC_OSR_32 (0x2 << 2) +#define RT5616_DAC_OSR_128_3 (0x3 << 2) +#define RT5616_ADC_OSR_MASK (0x3) +#define RT5616_ADC_OSR_SFT 0 +#define RT5616_ADC_OSR_128 (0x0) +#define RT5616_ADC_OSR_64 (0x1) +#define RT5616_ADC_OSR_32 (0x2) +#define RT5616_ADC_OSR_128_3 (0x3) + +/* ADC/DAC Clock Control 2 (0x74) */ +#define RT5616_DAHPF_EN (0x1 << 11) +#define RT5616_DAHPF_EN_SFT 11 +#define RT5616_ADHPF_EN (0x1 << 10) +#define RT5616_ADHPF_EN_SFT 10 + +/* TDM Control 1 (0x77) */ +#define RT5616_TDM_INTEL_SEL_MASK (0x1 << 15) +#define RT5616_TDM_INTEL_SEL_SFT 15 +#define RT5616_TDM_INTEL_SEL_64 (0x0 << 15) +#define RT5616_TDM_INTEL_SEL_50 (0x1 << 15) +#define RT5616_TDM_MODE_SEL_MASK (0x1 << 14) +#define RT5616_TDM_MODE_SEL_SFT 14 +#define RT5616_TDM_MODE_SEL_NOR (0x0 << 14) +#define RT5616_TDM_MODE_SEL_TDM (0x1 << 14) +#define RT5616_TDM_CH_NUM_SEL_MASK (0x3 << 12) +#define RT5616_TDM_CH_NUM_SEL_SFT 12 +#define RT5616_TDM_CH_NUM_SEL_2 (0x0 << 12) +#define RT5616_TDM_CH_NUM_SEL_4 (0x1 << 12) +#define RT5616_TDM_CH_NUM_SEL_6 (0x2 << 12) +#define RT5616_TDM_CH_NUM_SEL_8 (0x3 << 12) +#define RT5616_TDM_CH_LEN_SEL_MASK (0x3 << 10) +#define RT5616_TDM_CH_LEN_SEL_SFT 10 +#define RT5616_TDM_CH_LEN_SEL_16 (0x0 << 10) +#define RT5616_TDM_CH_LEN_SEL_20 (0x1 << 10) +#define RT5616_TDM_CH_LEN_SEL_24 (0x2 << 10) +#define RT5616_TDM_CH_LEN_SEL_32 (0x3 << 10) +#define RT5616_TDM_ADC_SEL_MASK (0x1 << 9) +#define RT5616_TDM_ADC_SEL_SFT 9 +#define RT5616_TDM_ADC_SEL_NOR (0x0 << 9) +#define RT5616_TDM_ADC_SEL_SWAP (0x1 << 9) +#define RT5616_TDM_ADC_START_SEL_MASK (0x1 << 8) +#define RT5616_TDM_ADC_START_SEL_SFT 8 +#define RT5616_TDM_ADC_START_SEL_SL0 (0x0 << 8) +#define RT5616_TDM_ADC_START_SEL_SL4 (0x1 << 8) +#define RT5616_TDM_I2S_CH2_SEL_MASK (0x3 << 6) +#define RT5616_TDM_I2S_CH2_SEL_SFT 6 +#define RT5616_TDM_I2S_CH2_SEL_LR (0x0 << 6) +#define RT5616_TDM_I2S_CH2_SEL_RL (0x1 << 6) +#define RT5616_TDM_I2S_CH2_SEL_LL (0x2 << 6) +#define RT5616_TDM_I2S_CH2_SEL_RR (0x3 << 6) +#define RT5616_TDM_I2S_CH4_SEL_MASK (0x3 << 4) +#define RT5616_TDM_I2S_CH4_SEL_SFT 4 +#define RT5616_TDM_I2S_CH4_SEL_LR (0x0 << 4) +#define RT5616_TDM_I2S_CH4_SEL_RL (0x1 << 4) +#define RT5616_TDM_I2S_CH4_SEL_LL (0x2 << 4) +#define RT5616_TDM_I2S_CH4_SEL_RR (0x3 << 4) +#define RT5616_TDM_I2S_CH6_SEL_MASK (0x3 << 2) +#define RT5616_TDM_I2S_CH6_SEL_SFT 2 +#define RT5616_TDM_I2S_CH6_SEL_LR (0x0 << 2) +#define RT5616_TDM_I2S_CH6_SEL_RL (0x1 << 2) +#define RT5616_TDM_I2S_CH6_SEL_LL (0x2 << 2) +#define RT5616_TDM_I2S_CH6_SEL_RR (0x3 << 2) +#define RT5616_TDM_I2S_CH8_SEL_MASK (0x3) +#define RT5616_TDM_I2S_CH8_SEL_SFT 0 +#define RT5616_TDM_I2S_CH8_SEL_LR (0x0) +#define RT5616_TDM_I2S_CH8_SEL_RL (0x1) +#define RT5616_TDM_I2S_CH8_SEL_LL (0x2) +#define RT5616_TDM_I2S_CH8_SEL_RR (0x3) + +/* TDM Control 2 (0x78) */ +#define RT5616_TDM_LRCK_POL_SEL_MASK (0x1 << 15) +#define RT5616_TDM_LRCK_POL_SEL_SFT 15 +#define RT5616_TDM_LRCK_POL_SEL_NOR (0x0 << 15) +#define RT5616_TDM_LRCK_POL_SEL_INV (0x1 << 15) +#define RT5616_TDM_CH_VAL_SEL_MASK (0x1 << 14) +#define RT5616_TDM_CH_VAL_SEL_SFT 14 +#define RT5616_TDM_CH_VAL_SEL_CH01 (0x0 << 14) +#define RT5616_TDM_CH_VAL_SEL_CH0123 (0x1 << 14) +#define RT5616_TDM_CH_VAL_EN (0x1 << 13) +#define RT5616_TDM_CH_VAL_SFT 13 +#define RT5616_TDM_LPBK_EN (0x1 << 12) +#define RT5616_TDM_LPBK_SFT 12 +#define RT5616_TDM_LRCK_PULSE_SEL_MASK (0x1 << 11) +#define RT5616_TDM_LRCK_PULSE_SEL_SFT 11 +#define RT5616_TDM_LRCK_PULSE_SEL_BCLK (0x0 << 11) +#define RT5616_TDM_LRCK_PULSE_SEL_CH (0x1 << 11) +#define RT5616_TDM_END_EDGE_SEL_MASK (0x1 << 10) +#define RT5616_TDM_END_EDGE_SEL_SFT 10 +#define RT5616_TDM_END_EDGE_SEL_POS (0x0 << 10) +#define RT5616_TDM_END_EDGE_SEL_NEG (0x1 << 10) +#define RT5616_TDM_END_EDGE_EN (0x1 << 9) +#define RT5616_TDM_END_EDGE_EN_SFT 9 +#define RT5616_TDM_TRAN_EDGE_SEL_MASK (0x1 << 8) +#define RT5616_TDM_TRAN_EDGE_SEL_SFT 8 +#define RT5616_TDM_TRAN_EDGE_SEL_POS (0x0 << 8) +#define RT5616_TDM_TRAN_EDGE_SEL_NEG (0x1 << 8) +#define RT5616_M_TDM2_L (0x1 << 7) +#define RT5616_M_TDM2_L_SFT 7 +#define RT5616_M_TDM2_R (0x1 << 6) +#define RT5616_M_TDM2_R_SFT 6 +#define RT5616_M_TDM4_L (0x1 << 5) +#define RT5616_M_TDM4_L_SFT 5 +#define RT5616_M_TDM4_R (0x1 << 4) +#define RT5616_M_TDM4_R_SFT 4 + +/* Global Clock Control (0x80) */ +#define RT5616_SCLK_SRC_MASK (0x3 << 14) +#define RT5616_SCLK_SRC_SFT 14 +#define RT5616_SCLK_SRC_MCLK (0x0 << 14) +#define RT5616_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5616_PLL1_SRC_MASK (0x3 << 12) +#define RT5616_PLL1_SRC_SFT 12 +#define RT5616_PLL1_SRC_MCLK (0x0 << 12) +#define RT5616_PLL1_SRC_BCLK1 (0x1 << 12) +#define RT5616_PLL1_SRC_BCLK2 (0x2 << 12) +#define RT5616_PLL1_PD_MASK (0x1 << 3) +#define RT5616_PLL1_PD_SFT 3 +#define RT5616_PLL1_PD_1 (0x0 << 3) +#define RT5616_PLL1_PD_2 (0x1 << 3) + +#define RT5616_PLL_INP_MAX 40000000 +#define RT5616_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x81) */ +#define RT5616_PLL_N_MAX 0x1ff +#define RT5616_PLL_N_MASK (RT5616_PLL_N_MAX << 7) +#define RT5616_PLL_N_SFT 7 +#define RT5616_PLL_K_MAX 0x1f +#define RT5616_PLL_K_MASK (RT5616_PLL_K_MAX) +#define RT5616_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x82) */ +#define RT5616_PLL_M_MAX 0xf +#define RT5616_PLL_M_MASK (RT5616_PLL_M_MAX << 12) +#define RT5616_PLL_M_SFT 12 +#define RT5616_PLL_M_BP (0x1 << 11) +#define RT5616_PLL_M_BP_SFT 11 + +/* PLL tracking mode 1 (0x83) */ +#define RT5616_STO1_T_MASK (0x1 << 15) +#define RT5616_STO1_T_SFT 15 +#define RT5616_STO1_T_SCLK (0x0 << 15) +#define RT5616_STO1_T_LRCK1 (0x1 << 15) +#define RT5616_STO2_T_MASK (0x1 << 12) +#define RT5616_STO2_T_SFT 12 +#define RT5616_STO2_T_I2S2 (0x0 << 12) +#define RT5616_STO2_T_LRCK2 (0x1 << 12) +#define RT5616_ASRC2_REF_MASK (0x1 << 11) +#define RT5616_ASRC2_REF_SFT 11 +#define RT5616_ASRC2_REF_LRCK2 (0x0 << 11) +#define RT5616_ASRC2_REF_LRCK1 (0x1 << 11) +#define RT5616_DMIC_1_M_MASK (0x1 << 9) +#define RT5616_DMIC_1_M_SFT 9 +#define RT5616_DMIC_1_M_NOR (0x0 << 9) +#define RT5616_DMIC_1_M_ASYN (0x1 << 9) + +/* PLL tracking mode 2 (0x84) */ +#define RT5616_STO1_ASRC_EN (0x1 << 15) +#define RT5616_STO1_ASRC_EN_SFT 15 +#define RT5616_STO2_ASRC_EN (0x1 << 14) +#define RT5616_STO2_ASRC_EN_SFT 14 +#define RT5616_STO1_DAC_M_MASK (0x1 << 13) +#define RT5616_STO1_DAC_M_SFT 13 +#define RT5616_STO1_DAC_M_NOR (0x0 << 13) +#define RT5616_STO1_DAC_M_ASRC (0x1 << 13) +#define RT5616_STO2_DAC_M_MASK (0x1 << 12) +#define RT5616_STO2_DAC_M_SFT 12 +#define RT5616_STO2_DAC_M_NOR (0x0 << 12) +#define RT5616_STO2_DAC_M_ASRC (0x1 << 12) +#define RT5616_ADC_M_MASK (0x1 << 11) +#define RT5616_ADC_M_SFT 11 +#define RT5616_ADC_M_NOR (0x0 << 11) +#define RT5616_ADC_M_ASRC (0x1 << 11) +#define RT5616_I2S1_R_D_MASK (0x1 << 4) +#define RT5616_I2S1_R_D_SFT 4 +#define RT5616_I2S1_R_D_DIS (0x0 << 4) +#define RT5616_I2S1_R_D_EN (0x1 << 4) +#define RT5616_I2S2_R_D_MASK (0x1 << 3) +#define RT5616_I2S2_R_D_SFT 3 +#define RT5616_I2S2_R_D_DIS (0x0 << 3) +#define RT5616_I2S2_R_D_EN (0x1 << 3) +#define RT5616_PRE_SCLK_MASK (0x3) +#define RT5616_PRE_SCLK_SFT 0 +#define RT5616_PRE_SCLK_512 (0x0) +#define RT5616_PRE_SCLK_1024 (0x1) +#define RT5616_PRE_SCLK_2048 (0x2) + +/* PLL tracking mode 3 (0x85) */ +#define RT5616_I2S1_RATE_MASK (0xf << 12) +#define RT5616_I2S1_RATE_SFT 12 +#define RT5616_I2S2_RATE_MASK (0xf << 8) +#define RT5616_I2S2_RATE_SFT 8 +#define RT5616_G_ASRC_LP_MASK (0x1 << 3) +#define RT5616_G_ASRC_LP_SFT 3 +#define RT5616_ASRC_LP_F_M (0x1 << 2) +#define RT5616_ASRC_LP_F_SFT 2 +#define RT5616_ASRC_LP_F_NOR (0x0 << 2) +#define RT5616_ASRC_LP_F_SB (0x1 << 2) +#define RT5616_FTK_PH_DET_MASK (0x3) +#define RT5616_FTK_PH_DET_SFT 0 +#define RT5616_FTK_PH_DET_DIV1 (0x0) +#define RT5616_FTK_PH_DET_DIV2 (0x1) +#define RT5616_FTK_PH_DET_DIV4 (0x2) +#define RT5616_FTK_PH_DET_DIV8 (0x3) + +/*PLL tracking mode 6 (0x89) */ +#define RT5616_I2S1_PD_MASK (0x7 << 12) +#define RT5616_I2S1_PD_SFT 12 +#define RT5616_I2S2_PD_MASK (0x7 << 8) +#define RT5616_I2S2_PD_SFT 8 + +/*PLL tracking mode 7 (0x8a) */ +#define RT5616_FSI1_RATE_MASK (0xf << 12) +#define RT5616_FSI1_RATE_SFT 12 +#define RT5616_FSI2_RATE_MASK (0xf << 8) +#define RT5616_FSI2_RATE_SFT 8 + +/* HPOUT Over Current Detection (0x8b) */ +#define RT5616_HP_OVCD_MASK (0x1 << 10) +#define RT5616_HP_OVCD_SFT 10 +#define RT5616_HP_OVCD_DIS (0x0 << 10) +#define RT5616_HP_OVCD_EN (0x1 << 10) +#define RT5616_HP_OC_TH_MASK (0x3 << 8) +#define RT5616_HP_OC_TH_SFT 8 +#define RT5616_HP_OC_TH_90 (0x0 << 8) +#define RT5616_HP_OC_TH_105 (0x1 << 8) +#define RT5616_HP_OC_TH_120 (0x2 << 8) +#define RT5616_HP_OC_TH_135 (0x3 << 8) + +/* Depop Mode Control 1 (0x8e) */ +#define RT5616_SMT_TRIG_MASK (0x1 << 15) +#define RT5616_SMT_TRIG_SFT 15 +#define RT5616_SMT_TRIG_DIS (0x0 << 15) +#define RT5616_SMT_TRIG_EN (0x1 << 15) +#define RT5616_HP_L_SMT_MASK (0x1 << 9) +#define RT5616_HP_L_SMT_SFT 9 +#define RT5616_HP_L_SMT_DIS (0x0 << 9) +#define RT5616_HP_L_SMT_EN (0x1 << 9) +#define RT5616_HP_R_SMT_MASK (0x1 << 8) +#define RT5616_HP_R_SMT_SFT 8 +#define RT5616_HP_R_SMT_DIS (0x0 << 8) +#define RT5616_HP_R_SMT_EN (0x1 << 8) +#define RT5616_HP_CD_PD_MASK (0x1 << 7) +#define RT5616_HP_CD_PD_SFT 7 +#define RT5616_HP_CD_PD_DIS (0x0 << 7) +#define RT5616_HP_CD_PD_EN (0x1 << 7) +#define RT5616_RSTN_MASK (0x1 << 6) +#define RT5616_RSTN_SFT 6 +#define RT5616_RSTN_DIS (0x0 << 6) +#define RT5616_RSTN_EN (0x1 << 6) +#define RT5616_RSTP_MASK (0x1 << 5) +#define RT5616_RSTP_SFT 5 +#define RT5616_RSTP_DIS (0x0 << 5) +#define RT5616_RSTP_EN (0x1 << 5) +#define RT5616_HP_CO_MASK (0x1 << 4) +#define RT5616_HP_CO_SFT 4 +#define RT5616_HP_CO_DIS (0x0 << 4) +#define RT5616_HP_CO_EN (0x1 << 4) +#define RT5616_HP_CP_MASK (0x1 << 3) +#define RT5616_HP_CP_SFT 3 +#define RT5616_HP_CP_PD (0x0 << 3) +#define RT5616_HP_CP_PU (0x1 << 3) +#define RT5616_HP_SG_MASK (0x1 << 2) +#define RT5616_HP_SG_SFT 2 +#define RT5616_HP_SG_DIS (0x0 << 2) +#define RT5616_HP_SG_EN (0x1 << 2) +#define RT5616_HP_DP_MASK (0x1 << 1) +#define RT5616_HP_DP_SFT 1 +#define RT5616_HP_DP_PD (0x0 << 1) +#define RT5616_HP_DP_PU (0x1 << 1) +#define RT5616_HP_CB_MASK (0x1) +#define RT5616_HP_CB_SFT 0 +#define RT5616_HP_CB_PD (0x0) +#define RT5616_HP_CB_PU (0x1) + +/* Depop Mode Control 2 (0x8f) */ +#define RT5616_DEPOP_MASK (0x1 << 13) +#define RT5616_DEPOP_SFT 13 +#define RT5616_DEPOP_AUTO (0x0 << 13) +#define RT5616_DEPOP_MAN (0x1 << 13) +#define RT5616_RAMP_MASK (0x1 << 12) +#define RT5616_RAMP_SFT 12 +#define RT5616_RAMP_DIS (0x0 << 12) +#define RT5616_RAMP_EN (0x1 << 12) +#define RT5616_BPS_MASK (0x1 << 11) +#define RT5616_BPS_SFT 11 +#define RT5616_BPS_DIS (0x0 << 11) +#define RT5616_BPS_EN (0x1 << 11) +#define RT5616_FAST_UPDN_MASK (0x1 << 10) +#define RT5616_FAST_UPDN_SFT 10 +#define RT5616_FAST_UPDN_DIS (0x0 << 10) +#define RT5616_FAST_UPDN_EN (0x1 << 10) +#define RT5616_MRES_MASK (0x3 << 8) +#define RT5616_MRES_SFT 8 +#define RT5616_MRES_15MO (0x0 << 8) +#define RT5616_MRES_25MO (0x1 << 8) +#define RT5616_MRES_35MO (0x2 << 8) +#define RT5616_MRES_45MO (0x3 << 8) +#define RT5616_VLO_MASK (0x1 << 7) +#define RT5616_VLO_SFT 7 +#define RT5616_VLO_3V (0x0 << 7) +#define RT5616_VLO_32V (0x1 << 7) +#define RT5616_DIG_DP_MASK (0x1 << 6) +#define RT5616_DIG_DP_SFT 6 +#define RT5616_DIG_DP_DIS (0x0 << 6) +#define RT5616_DIG_DP_EN (0x1 << 6) +#define RT5616_DP_TH_MASK (0x3 << 4) +#define RT5616_DP_TH_SFT 4 + +/* Depop Mode Control 3 (0x90) */ +#define RT5616_CP_SYS_MASK (0x7 << 12) +#define RT5616_CP_SYS_SFT 12 +#define RT5616_CP_FQ1_MASK (0x7 << 8) +#define RT5616_CP_FQ1_SFT 8 +#define RT5616_CP_FQ2_MASK (0x7 << 4) +#define RT5616_CP_FQ2_SFT 4 +#define RT5616_CP_FQ3_MASK (0x7) +#define RT5616_CP_FQ3_SFT 0 +#define RT5616_CP_FQ_1_5_KHZ 0 +#define RT5616_CP_FQ_3_KHZ 1 +#define RT5616_CP_FQ_6_KHZ 2 +#define RT5616_CP_FQ_12_KHZ 3 +#define RT5616_CP_FQ_24_KHZ 4 +#define RT5616_CP_FQ_48_KHZ 5 +#define RT5616_CP_FQ_96_KHZ 6 +#define RT5616_CP_FQ_192_KHZ 7 + +/* HPOUT charge pump (0x91) */ +#define RT5616_OSW_L_MASK (0x1 << 11) +#define RT5616_OSW_L_SFT 11 +#define RT5616_OSW_L_DIS (0x0 << 11) +#define RT5616_OSW_L_EN (0x1 << 11) +#define RT5616_OSW_R_MASK (0x1 << 10) +#define RT5616_OSW_R_SFT 10 +#define RT5616_OSW_R_DIS (0x0 << 10) +#define RT5616_OSW_R_EN (0x1 << 10) +#define RT5616_PM_HP_MASK (0x3 << 8) +#define RT5616_PM_HP_SFT 8 +#define RT5616_PM_HP_LV (0x0 << 8) +#define RT5616_PM_HP_MV (0x1 << 8) +#define RT5616_PM_HP_HV (0x2 << 8) +#define RT5616_IB_HP_MASK (0x3 << 6) +#define RT5616_IB_HP_SFT 6 +#define RT5616_IB_HP_125IL (0x0 << 6) +#define RT5616_IB_HP_25IL (0x1 << 6) +#define RT5616_IB_HP_5IL (0x2 << 6) +#define RT5616_IB_HP_1IL (0x3 << 6) + +/* Micbias Control (0x93) */ +#define RT5616_MIC1_BS_MASK (0x1 << 15) +#define RT5616_MIC1_BS_SFT 15 +#define RT5616_MIC1_BS_9AV (0x0 << 15) +#define RT5616_MIC1_BS_75AV (0x1 << 15) +#define RT5616_MIC1_CLK_MASK (0x1 << 13) +#define RT5616_MIC1_CLK_SFT 13 +#define RT5616_MIC1_CLK_DIS (0x0 << 13) +#define RT5616_MIC1_CLK_EN (0x1 << 13) +#define RT5616_MIC1_OVCD_MASK (0x1 << 11) +#define RT5616_MIC1_OVCD_SFT 11 +#define RT5616_MIC1_OVCD_DIS (0x0 << 11) +#define RT5616_MIC1_OVCD_EN (0x1 << 11) +#define RT5616_MIC1_OVTH_MASK (0x3 << 9) +#define RT5616_MIC1_OVTH_SFT 9 +#define RT5616_MIC1_OVTH_600UA (0x0 << 9) +#define RT5616_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5616_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5616_PWR_MB_MASK (0x1 << 5) +#define RT5616_PWR_MB_SFT 5 +#define RT5616_PWR_MB_PD (0x0 << 5) +#define RT5616_PWR_MB_PU (0x1 << 5) +#define RT5616_PWR_CLK12M_MASK (0x1 << 4) +#define RT5616_PWR_CLK12M_SFT 4 +#define RT5616_PWR_CLK12M_PD (0x0 << 4) +#define RT5616_PWR_CLK12M_PU (0x1 << 4) + +/* Analog JD Control 1 (0x94) */ +#define RT5616_JD2_CMP_MASK (0x7 << 12) +#define RT5616_JD2_CMP_SFT 12 +#define RT5616_JD_PU (0x1 << 11) +#define RT5616_JD_PU_SFT 11 +#define RT5616_JD_PD (0x1 << 10) +#define RT5616_JD_PD_SFT 10 +#define RT5616_JD_MODE_SEL_MASK (0x3 << 8) +#define RT5616_JD_MODE_SEL_SFT 8 +#define RT5616_JD_MODE_SEL_M0 (0x0 << 8) +#define RT5616_JD_MODE_SEL_M1 (0x1 << 8) +#define RT5616_JD_MODE_SEL_M2 (0x2 << 8) +#define RT5616_JD_M_CMP (0x7 << 4) +#define RT5616_JD_M_CMP_SFT 4 +#define RT5616_JD_M_PU (0x1 << 3) +#define RT5616_JD_M_PU_SFT 3 +#define RT5616_JD_M_PD (0x1 << 2) +#define RT5616_JD_M_PD_SFT 2 +#define RT5616_JD_M_MODE_SEL_MASK (0x3) +#define RT5616_JD_M_MODE_SEL_SFT 0 +#define RT5616_JD_M_MODE_SEL_M0 (0x0) +#define RT5616_JD_M_MODE_SEL_M1 (0x1) +#define RT5616_JD_M_MODE_SEL_M2 (0x2) + +/* Analog JD Control 2 (0x95) */ +#define RT5616_JD3_CMP_MASK (0x7 << 12) +#define RT5616_JD3_CMP_SFT 12 + +/* EQ Control 1 (0xb0) */ +#define RT5616_EQ_SRC_MASK (0x1 << 15) +#define RT5616_EQ_SRC_SFT 15 +#define RT5616_EQ_SRC_DAC (0x0 << 15) +#define RT5616_EQ_SRC_ADC (0x1 << 15) +#define RT5616_EQ_UPD (0x1 << 14) +#define RT5616_EQ_UPD_BIT 14 +#define RT5616_EQ_CD_MASK (0x1 << 13) +#define RT5616_EQ_CD_SFT 13 +#define RT5616_EQ_CD_DIS (0x0 << 13) +#define RT5616_EQ_CD_EN (0x1 << 13) +#define RT5616_EQ_DITH_MASK (0x3 << 8) +#define RT5616_EQ_DITH_SFT 8 +#define RT5616_EQ_DITH_NOR (0x0 << 8) +#define RT5616_EQ_DITH_LSB (0x1 << 8) +#define RT5616_EQ_DITH_LSB_1 (0x2 << 8) +#define RT5616_EQ_DITH_LSB_2 (0x3 << 8) +#define RT5616_EQ_CD_F (0x1 << 7) +#define RT5616_EQ_CD_F_BIT 7 +#define RT5616_EQ_STA_HP2 (0x1 << 6) +#define RT5616_EQ_STA_HP2_BIT 6 +#define RT5616_EQ_STA_HP1 (0x1 << 5) +#define RT5616_EQ_STA_HP1_BIT 5 +#define RT5616_EQ_STA_BP4 (0x1 << 4) +#define RT5616_EQ_STA_BP4_BIT 4 +#define RT5616_EQ_STA_BP3 (0x1 << 3) +#define RT5616_EQ_STA_BP3_BIT 3 +#define RT5616_EQ_STA_BP2 (0x1 << 2) +#define RT5616_EQ_STA_BP2_BIT 2 +#define RT5616_EQ_STA_BP1 (0x1 << 1) +#define RT5616_EQ_STA_BP1_BIT 1 +#define RT5616_EQ_STA_LP (0x1) +#define RT5616_EQ_STA_LP_BIT 0 + +/* EQ Control 2 (0xb1) */ +#define RT5616_EQ_HPF1_M_MASK (0x1 << 8) +#define RT5616_EQ_HPF1_M_SFT 8 +#define RT5616_EQ_HPF1_M_HI (0x0 << 8) +#define RT5616_EQ_HPF1_M_1ST (0x1 << 8) +#define RT5616_EQ_LPF1_M_MASK (0x1 << 7) +#define RT5616_EQ_LPF1_M_SFT 7 +#define RT5616_EQ_LPF1_M_LO (0x0 << 7) +#define RT5616_EQ_LPF1_M_1ST (0x1 << 7) +#define RT5616_EQ_HPF2_MASK (0x1 << 6) +#define RT5616_EQ_HPF2_SFT 6 +#define RT5616_EQ_HPF2_DIS (0x0 << 6) +#define RT5616_EQ_HPF2_EN (0x1 << 6) +#define RT5616_EQ_HPF1_MASK (0x1 << 5) +#define RT5616_EQ_HPF1_SFT 5 +#define RT5616_EQ_HPF1_DIS (0x0 << 5) +#define RT5616_EQ_HPF1_EN (0x1 << 5) +#define RT5616_EQ_BPF4_MASK (0x1 << 4) +#define RT5616_EQ_BPF4_SFT 4 +#define RT5616_EQ_BPF4_DIS (0x0 << 4) +#define RT5616_EQ_BPF4_EN (0x1 << 4) +#define RT5616_EQ_BPF3_MASK (0x1 << 3) +#define RT5616_EQ_BPF3_SFT 3 +#define RT5616_EQ_BPF3_DIS (0x0 << 3) +#define RT5616_EQ_BPF3_EN (0x1 << 3) +#define RT5616_EQ_BPF2_MASK (0x1 << 2) +#define RT5616_EQ_BPF2_SFT 2 +#define RT5616_EQ_BPF2_DIS (0x0 << 2) +#define RT5616_EQ_BPF2_EN (0x1 << 2) +#define RT5616_EQ_BPF1_MASK (0x1 << 1) +#define RT5616_EQ_BPF1_SFT 1 +#define RT5616_EQ_BPF1_DIS (0x0 << 1) +#define RT5616_EQ_BPF1_EN (0x1 << 1) +#define RT5616_EQ_LPF_MASK (0x1) +#define RT5616_EQ_LPF_SFT 0 +#define RT5616_EQ_LPF_DIS (0x0) +#define RT5616_EQ_LPF_EN (0x1) +#define RT5616_EQ_CTRL_MASK (0x7f) + +/* Memory Test (0xb2) */ +#define RT5616_MT_MASK (0x1 << 15) +#define RT5616_MT_SFT 15 +#define RT5616_MT_DIS (0x0 << 15) +#define RT5616_MT_EN (0x1 << 15) + +/* DRC/AGC Control 1 (0xb4) */ +#define RT5616_DRC_AGC_P_MASK (0x1 << 15) +#define RT5616_DRC_AGC_P_SFT 15 +#define RT5616_DRC_AGC_P_DAC (0x0 << 15) +#define RT5616_DRC_AGC_P_ADC (0x1 << 15) +#define RT5616_DRC_AGC_MASK (0x1 << 14) +#define RT5616_DRC_AGC_SFT 14 +#define RT5616_DRC_AGC_DIS (0x0 << 14) +#define RT5616_DRC_AGC_EN (0x1 << 14) +#define RT5616_DRC_AGC_UPD (0x1 << 13) +#define RT5616_DRC_AGC_UPD_BIT 13 +#define RT5616_DRC_AGC_AR_MASK (0x1f << 8) +#define RT5616_DRC_AGC_AR_SFT 8 +#define RT5616_DRC_AGC_R_MASK (0x7 << 5) +#define RT5616_DRC_AGC_R_SFT 5 +#define RT5616_DRC_AGC_R_48K (0x1 << 5) +#define RT5616_DRC_AGC_R_96K (0x2 << 5) +#define RT5616_DRC_AGC_R_192K (0x3 << 5) +#define RT5616_DRC_AGC_R_441K (0x5 << 5) +#define RT5616_DRC_AGC_R_882K (0x6 << 5) +#define RT5616_DRC_AGC_R_1764K (0x7 << 5) +#define RT5616_DRC_AGC_RC_MASK (0x1f) +#define RT5616_DRC_AGC_RC_SFT 0 + +/* DRC/AGC Control 2 (0xb5) */ +#define RT5616_DRC_AGC_POB_MASK (0x3f << 8) +#define RT5616_DRC_AGC_POB_SFT 8 +#define RT5616_DRC_AGC_CP_MASK (0x1 << 7) +#define RT5616_DRC_AGC_CP_SFT 7 +#define RT5616_DRC_AGC_CP_DIS (0x0 << 7) +#define RT5616_DRC_AGC_CP_EN (0x1 << 7) +#define RT5616_DRC_AGC_CPR_MASK (0x3 << 5) +#define RT5616_DRC_AGC_CPR_SFT 5 +#define RT5616_DRC_AGC_CPR_1_1 (0x0 << 5) +#define RT5616_DRC_AGC_CPR_1_2 (0x1 << 5) +#define RT5616_DRC_AGC_CPR_1_3 (0x2 << 5) +#define RT5616_DRC_AGC_CPR_1_4 (0x3 << 5) +#define RT5616_DRC_AGC_PRB_MASK (0x1f) +#define RT5616_DRC_AGC_PRB_SFT 0 + +/* DRC/AGC Control 3 (0xb6) */ +#define RT5616_DRC_AGC_NGB_MASK (0xf << 12) +#define RT5616_DRC_AGC_NGB_SFT 12 +#define RT5616_DRC_AGC_TAR_MASK (0x1f << 7) +#define RT5616_DRC_AGC_TAR_SFT 7 +#define RT5616_DRC_AGC_NG_MASK (0x1 << 6) +#define RT5616_DRC_AGC_NG_SFT 6 +#define RT5616_DRC_AGC_NG_DIS (0x0 << 6) +#define RT5616_DRC_AGC_NG_EN (0x1 << 6) +#define RT5616_DRC_AGC_NGH_MASK (0x1 << 5) +#define RT5616_DRC_AGC_NGH_SFT 5 +#define RT5616_DRC_AGC_NGH_DIS (0x0 << 5) +#define RT5616_DRC_AGC_NGH_EN (0x1 << 5) +#define RT5616_DRC_AGC_NGT_MASK (0x1f) +#define RT5616_DRC_AGC_NGT_SFT 0 + +/* Jack Detect Control 1 (0xbb) */ +#define RT5616_JD_MASK (0x7 << 13) +#define RT5616_JD_SFT 13 +#define RT5616_JD_DIS (0x0 << 13) +#define RT5616_JD_GPIO1 (0x1 << 13) +#define RT5616_JD_GPIO2 (0x2 << 13) +#define RT5616_JD_GPIO3 (0x3 << 13) +#define RT5616_JD_GPIO4 (0x4 << 13) +#define RT5616_JD_GPIO5 (0x5 << 13) +#define RT5616_JD_GPIO6 (0x6 << 13) +#define RT5616_JD_HP_MASK (0x1 << 11) +#define RT5616_JD_HP_SFT 11 +#define RT5616_JD_HP_DIS (0x0 << 11) +#define RT5616_JD_HP_EN (0x1 << 11) +#define RT5616_JD_HP_TRG_MASK (0x1 << 10) +#define RT5616_JD_HP_TRG_SFT 10 +#define RT5616_JD_HP_TRG_LO (0x0 << 10) +#define RT5616_JD_HP_TRG_HI (0x1 << 10) +#define RT5616_JD_SPL_MASK (0x1 << 9) +#define RT5616_JD_SPL_SFT 9 +#define RT5616_JD_SPL_DIS (0x0 << 9) +#define RT5616_JD_SPL_EN (0x1 << 9) +#define RT5616_JD_SPL_TRG_MASK (0x1 << 8) +#define RT5616_JD_SPL_TRG_SFT 8 +#define RT5616_JD_SPL_TRG_LO (0x0 << 8) +#define RT5616_JD_SPL_TRG_HI (0x1 << 8) +#define RT5616_JD_SPR_MASK (0x1 << 7) +#define RT5616_JD_SPR_SFT 7 +#define RT5616_JD_SPR_DIS (0x0 << 7) +#define RT5616_JD_SPR_EN (0x1 << 7) +#define RT5616_JD_SPR_TRG_MASK (0x1 << 6) +#define RT5616_JD_SPR_TRG_SFT 6 +#define RT5616_JD_SPR_TRG_LO (0x0 << 6) +#define RT5616_JD_SPR_TRG_HI (0x1 << 6) +#define RT5616_JD_LO_MASK (0x1 << 3) +#define RT5616_JD_LO_SFT 3 +#define RT5616_JD_LO_DIS (0x0 << 3) +#define RT5616_JD_LO_EN (0x1 << 3) +#define RT5616_JD_LO_TRG_MASK (0x1 << 2) +#define RT5616_JD_LO_TRG_SFT 2 +#define RT5616_JD_LO_TRG_LO (0x0 << 2) +#define RT5616_JD_LO_TRG_HI (0x1 << 2) + +/* Jack Detect Control 2 (0xbc) */ +#define RT5616_JD_TRG_SEL_MASK (0x7 << 9) +#define RT5616_JD_TRG_SEL_SFT 9 +#define RT5616_JD_TRG_SEL_GPIO (0x0 << 9) +#define RT5616_JD_TRG_SEL_JD1_1 (0x1 << 9) +#define RT5616_JD_TRG_SEL_JD1_2 (0x2 << 9) +#define RT5616_JD_TRG_SEL_JD2 (0x3 << 9) +#define RT5616_JD_TRG_SEL_JD3 (0x4 << 9) +#define RT5616_JD3_IRQ_EN (0x1 << 8) +#define RT5616_JD3_IRQ_EN_SFT 8 +#define RT5616_JD3_EN_STKY (0x1 << 7) +#define RT5616_JD3_EN_STKY_SFT 7 +#define RT5616_JD3_INV (0x1 << 6) +#define RT5616_JD3_INV_SFT 6 + +/* IRQ Control 1 (0xbd) */ +#define RT5616_IRQ_JD_MASK (0x1 << 15) +#define RT5616_IRQ_JD_SFT 15 +#define RT5616_IRQ_JD_BP (0x0 << 15) +#define RT5616_IRQ_JD_NOR (0x1 << 15) +#define RT5616_JD_STKY_MASK (0x1 << 13) +#define RT5616_JD_STKY_SFT 13 +#define RT5616_JD_STKY_DIS (0x0 << 13) +#define RT5616_JD_STKY_EN (0x1 << 13) +#define RT5616_JD_P_MASK (0x1 << 11) +#define RT5616_JD_P_SFT 11 +#define RT5616_JD_P_NOR (0x0 << 11) +#define RT5616_JD_P_INV (0x1 << 11) +#define RT5616_JD1_1_IRQ_EN (0x1 << 9) +#define RT5616_JD1_1_IRQ_EN_SFT 9 +#define RT5616_JD1_1_EN_STKY (0x1 << 8) +#define RT5616_JD1_1_EN_STKY_SFT 8 +#define RT5616_JD1_1_INV (0x1 << 7) +#define RT5616_JD1_1_INV_SFT 7 +#define RT5616_JD1_2_IRQ_EN (0x1 << 6) +#define RT5616_JD1_2_IRQ_EN_SFT 6 +#define RT5616_JD1_2_EN_STKY (0x1 << 5) +#define RT5616_JD1_2_EN_STKY_SFT 5 +#define RT5616_JD1_2_INV (0x1 << 4) +#define RT5616_JD1_2_INV_SFT 4 +#define RT5616_JD2_IRQ_EN (0x1 << 3) +#define RT5616_JD2_IRQ_EN_SFT 3 +#define RT5616_JD2_EN_STKY (0x1 << 2) +#define RT5616_JD2_EN_STKY_SFT 2 +#define RT5616_JD2_INV (0x1 << 1) +#define RT5616_JD2_INV_SFT 1 + +/* IRQ Control 2 (0xbe) */ +#define RT5616_IRQ_MB1_OC_MASK (0x1 << 15) +#define RT5616_IRQ_MB1_OC_SFT 15 +#define RT5616_IRQ_MB1_OC_BP (0x0 << 15) +#define RT5616_IRQ_MB1_OC_NOR (0x1 << 15) +#define RT5616_MB1_OC_STKY_MASK (0x1 << 11) +#define RT5616_MB1_OC_STKY_SFT 11 +#define RT5616_MB1_OC_STKY_DIS (0x0 << 11) +#define RT5616_MB1_OC_STKY_EN (0x1 << 11) +#define RT5616_MB1_OC_P_MASK (0x1 << 7) +#define RT5616_MB1_OC_P_SFT 7 +#define RT5616_MB1_OC_P_NOR (0x0 << 7) +#define RT5616_MB1_OC_P_INV (0x1 << 7) +#define RT5616_MB2_OC_P_MASK (0x1 << 6) +#define RT5616_MB1_OC_CLR (0x1 << 3) +#define RT5616_MB1_OC_CLR_SFT 3 +#define RT5616_STA_GPIO8 (0x1) +#define RT5616_STA_GPIO8_BIT 0 + +/* Internal Status and GPIO status (0xbf) */ +#define RT5616_STA_JD3 (0x1 << 15) +#define RT5616_STA_JD3_BIT 15 +#define RT5616_STA_JD2 (0x1 << 14) +#define RT5616_STA_JD2_BIT 14 +#define RT5616_STA_JD1_2 (0x1 << 13) +#define RT5616_STA_JD1_2_BIT 13 +#define RT5616_STA_JD1_1 (0x1 << 12) +#define RT5616_STA_JD1_1_BIT 12 +#define RT5616_STA_GP7 (0x1 << 11) +#define RT5616_STA_GP7_BIT 11 +#define RT5616_STA_GP6 (0x1 << 10) +#define RT5616_STA_GP6_BIT 10 +#define RT5616_STA_GP5 (0x1 << 9) +#define RT5616_STA_GP5_BIT 9 +#define RT5616_STA_GP1 (0x1 << 8) +#define RT5616_STA_GP1_BIT 8 +#define RT5616_STA_GP2 (0x1 << 7) +#define RT5616_STA_GP2_BIT 7 +#define RT5616_STA_GP3 (0x1 << 6) +#define RT5616_STA_GP3_BIT 6 +#define RT5616_STA_GP4 (0x1 << 5) +#define RT5616_STA_GP4_BIT 5 +#define RT5616_STA_GP_JD (0x1 << 4) +#define RT5616_STA_GP_JD_BIT 4 + +/* GPIO Control 1 (0xc0) */ +#define RT5616_GP1_PIN_MASK (0x1 << 15) +#define RT5616_GP1_PIN_SFT 15 +#define RT5616_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5616_GP1_PIN_IRQ (0x1 << 15) +#define RT5616_GP2_PIN_MASK (0x1 << 14) +#define RT5616_GP2_PIN_SFT 14 +#define RT5616_GP2_PIN_GPIO2 (0x0 << 14) +#define RT5616_GP2_PIN_DMIC1_SCL (0x1 << 14) +#define RT5616_GPIO_M_MASK (0x1 << 9) +#define RT5616_GPIO_M_SFT 9 +#define RT5616_GPIO_M_FLT (0x0 << 9) +#define RT5616_GPIO_M_PH (0x1 << 9) +#define RT5616_I2S2_SEL_MASK (0x1 << 8) +#define RT5616_I2S2_SEL_SFT 8 +#define RT5616_I2S2_SEL_I2S (0x0 << 8) +#define RT5616_I2S2_SEL_GPIO (0x1 << 8) +#define RT5616_GP5_PIN_MASK (0x1 << 7) +#define RT5616_GP5_PIN_SFT 7 +#define RT5616_GP5_PIN_GPIO5 (0x0 << 7) +#define RT5616_GP5_PIN_IRQ (0x1 << 7) +#define RT5616_GP6_PIN_MASK (0x1 << 6) +#define RT5616_GP6_PIN_SFT 6 +#define RT5616_GP6_PIN_GPIO6 (0x0 << 6) +#define RT5616_GP6_PIN_DMIC_SDA (0x1 << 6) +#define RT5616_GP7_PIN_MASK (0x1 << 5) +#define RT5616_GP7_PIN_SFT 5 +#define RT5616_GP7_PIN_GPIO7 (0x0 << 5) +#define RT5616_GP7_PIN_IRQ (0x1 << 5) +#define RT5616_GP8_PIN_MASK (0x1 << 4) +#define RT5616_GP8_PIN_SFT 4 +#define RT5616_GP8_PIN_GPIO8 (0x0 << 4) +#define RT5616_GP8_PIN_DMIC_SDA (0x1 << 4) +#define RT5616_GPIO_PDM_SEL_MASK (0x1 << 3) +#define RT5616_GPIO_PDM_SEL_SFT 3 +#define RT5616_GPIO_PDM_SEL_GPIO (0x0 << 3) +#define RT5616_GPIO_PDM_SEL_PDM (0x1 << 3) + +/* GPIO Control 2 (0xc1) */ +#define RT5616_GP5_DR_MASK (0x1 << 14) +#define RT5616_GP5_DR_SFT 14 +#define RT5616_GP5_DR_IN (0x0 << 14) +#define RT5616_GP5_DR_OUT (0x1 << 14) +#define RT5616_GP5_OUT_MASK (0x1 << 13) +#define RT5616_GP5_OUT_SFT 13 +#define RT5616_GP5_OUT_LO (0x0 << 13) +#define RT5616_GP5_OUT_HI (0x1 << 13) +#define RT5616_GP5_P_MASK (0x1 << 12) +#define RT5616_GP5_P_SFT 12 +#define RT5616_GP5_P_NOR (0x0 << 12) +#define RT5616_GP5_P_INV (0x1 << 12) +#define RT5616_GP4_DR_MASK (0x1 << 11) +#define RT5616_GP4_DR_SFT 11 +#define RT5616_GP4_DR_IN (0x0 << 11) +#define RT5616_GP4_DR_OUT (0x1 << 11) +#define RT5616_GP4_OUT_MASK (0x1 << 10) +#define RT5616_GP4_OUT_SFT 10 +#define RT5616_GP4_OUT_LO (0x0 << 10) +#define RT5616_GP4_OUT_HI (0x1 << 10) +#define RT5616_GP4_P_MASK (0x1 << 9) +#define RT5616_GP4_P_SFT 9 +#define RT5616_GP4_P_NOR (0x0 << 9) +#define RT5616_GP4_P_INV (0x1 << 9) +#define RT5616_GP3_DR_MASK (0x1 << 8) +#define RT5616_GP3_DR_SFT 8 +#define RT5616_GP3_DR_IN (0x0 << 8) +#define RT5616_GP3_DR_OUT (0x1 << 8) +#define RT5616_GP3_OUT_MASK (0x1 << 7) +#define RT5616_GP3_OUT_SFT 7 +#define RT5616_GP3_OUT_LO (0x0 << 7) +#define RT5616_GP3_OUT_HI (0x1 << 7) +#define RT5616_GP3_P_MASK (0x1 << 6) +#define RT5616_GP3_P_SFT 6 +#define RT5616_GP3_P_NOR (0x0 << 6) +#define RT5616_GP3_P_INV (0x1 << 6) +#define RT5616_GP2_DR_MASK (0x1 << 5) +#define RT5616_GP2_DR_SFT 5 +#define RT5616_GP2_DR_IN (0x0 << 5) +#define RT5616_GP2_DR_OUT (0x1 << 5) +#define RT5616_GP2_OUT_MASK (0x1 << 4) +#define RT5616_GP2_OUT_SFT 4 +#define RT5616_GP2_OUT_LO (0x0 << 4) +#define RT5616_GP2_OUT_HI (0x1 << 4) +#define RT5616_GP2_P_MASK (0x1 << 3) +#define RT5616_GP2_P_SFT 3 +#define RT5616_GP2_P_NOR (0x0 << 3) +#define RT5616_GP2_P_INV (0x1 << 3) +#define RT5616_GP1_DR_MASK (0x1 << 2) +#define RT5616_GP1_DR_SFT 2 +#define RT5616_GP1_DR_IN (0x0 << 2) +#define RT5616_GP1_DR_OUT (0x1 << 2) +#define RT5616_GP1_OUT_MASK (0x1 << 1) +#define RT5616_GP1_OUT_SFT 1 +#define RT5616_GP1_OUT_LO (0x0 << 1) +#define RT5616_GP1_OUT_HI (0x1 << 1) +#define RT5616_GP1_P_MASK (0x1) +#define RT5616_GP1_P_SFT 0 +#define RT5616_GP1_P_NOR (0x0) +#define RT5616_GP1_P_INV (0x1) + +/* GPIO Control 3 (0xc2) */ +#define RT5616_GP8_DR_MASK (0x1 << 8) +#define RT5616_GP8_DR_SFT 8 +#define RT5616_GP8_DR_IN (0x0 << 8) +#define RT5616_GP8_DR_OUT (0x1 << 8) +#define RT5616_GP8_OUT_MASK (0x1 << 7) +#define RT5616_GP8_OUT_SFT 7 +#define RT5616_GP8_OUT_LO (0x0 << 7) +#define RT5616_GP8_OUT_HI (0x1 << 7) +#define RT5616_GP8_P_MASK (0x1 << 6) +#define RT5616_GP8_P_SFT 6 +#define RT5616_GP8_P_NOR (0x0 << 6) +#define RT5616_GP8_P_INV (0x1 << 6) +#define RT5616_GP7_DR_MASK (0x1 << 5) +#define RT5616_GP7_DR_SFT 5 +#define RT5616_GP7_DR_IN (0x0 << 5) +#define RT5616_GP7_DR_OUT (0x1 << 5) +#define RT5616_GP7_OUT_MASK (0x1 << 4) +#define RT5616_GP7_OUT_SFT 4 +#define RT5616_GP7_OUT_LO (0x0 << 4) +#define RT5616_GP7_OUT_HI (0x1 << 4) +#define RT5616_GP7_P_MASK (0x1 << 3) +#define RT5616_GP7_P_SFT 3 +#define RT5616_GP7_P_NOR (0x0 << 3) +#define RT5616_GP7_P_INV (0x1 << 3) +#define RT5616_GP6_DR_MASK (0x1 << 2) +#define RT5616_GP6_DR_SFT 2 +#define RT5616_GP6_DR_IN (0x0 << 2) +#define RT5616_GP6_DR_OUT (0x1 << 2) +#define RT5616_GP6_OUT_MASK (0x1 << 1) +#define RT5616_GP6_OUT_SFT 1 +#define RT5616_GP6_OUT_LO (0x0 << 1) +#define RT5616_GP6_OUT_HI (0x1 << 1) +#define RT5616_GP6_P_MASK (0x1) +#define RT5616_GP6_P_SFT 0 +#define RT5616_GP6_P_NOR (0x0) +#define RT5616_GP6_P_INV (0x1) + +/* Scramble Control (0xce) */ +#define RT5616_SCB_SWAP_MASK (0x1 << 15) +#define RT5616_SCB_SWAP_SFT 15 +#define RT5616_SCB_SWAP_DIS (0x0 << 15) +#define RT5616_SCB_SWAP_EN (0x1 << 15) +#define RT5616_SCB_MASK (0x1 << 14) +#define RT5616_SCB_SFT 14 +#define RT5616_SCB_DIS (0x0 << 14) +#define RT5616_SCB_EN (0x1 << 14) + +/* Baseback Control (0xcf) */ +#define RT5616_BB_MASK (0x1 << 15) +#define RT5616_BB_SFT 15 +#define RT5616_BB_DIS (0x0 << 15) +#define RT5616_BB_EN (0x1 << 15) +#define RT5616_BB_CT_MASK (0x7 << 12) +#define RT5616_BB_CT_SFT 12 +#define RT5616_BB_CT_A (0x0 << 12) +#define RT5616_BB_CT_B (0x1 << 12) +#define RT5616_BB_CT_C (0x2 << 12) +#define RT5616_BB_CT_D (0x3 << 12) +#define RT5616_M_BB_L_MASK (0x1 << 9) +#define RT5616_M_BB_L_SFT 9 +#define RT5616_M_BB_R_MASK (0x1 << 8) +#define RT5616_M_BB_R_SFT 8 +#define RT5616_M_BB_HPF_L_MASK (0x1 << 7) +#define RT5616_M_BB_HPF_L_SFT 7 +#define RT5616_M_BB_HPF_R_MASK (0x1 << 6) +#define RT5616_M_BB_HPF_R_SFT 6 +#define RT5616_G_BB_BST_MASK (0x3f) +#define RT5616_G_BB_BST_SFT 0 + +/* MP3 Plus Control 1 (0xd0) */ +#define RT5616_M_MP3_L_MASK (0x1 << 15) +#define RT5616_M_MP3_L_SFT 15 +#define RT5616_M_MP3_R_MASK (0x1 << 14) +#define RT5616_M_MP3_R_SFT 14 +#define RT5616_M_MP3_MASK (0x1 << 13) +#define RT5616_M_MP3_SFT 13 +#define RT5616_M_MP3_DIS (0x0 << 13) +#define RT5616_M_MP3_EN (0x1 << 13) +#define RT5616_EG_MP3_MASK (0x1f << 8) +#define RT5616_EG_MP3_SFT 8 +#define RT5616_MP3_HLP_MASK (0x1 << 7) +#define RT5616_MP3_HLP_SFT 7 +#define RT5616_MP3_HLP_DIS (0x0 << 7) +#define RT5616_MP3_HLP_EN (0x1 << 7) +#define RT5616_M_MP3_ORG_L_MASK (0x1 << 6) +#define RT5616_M_MP3_ORG_L_SFT 6 +#define RT5616_M_MP3_ORG_R_MASK (0x1 << 5) +#define RT5616_M_MP3_ORG_R_SFT 5 + +/* MP3 Plus Control 2 (0xd1) */ +#define RT5616_MP3_WT_MASK (0x1 << 13) +#define RT5616_MP3_WT_SFT 13 +#define RT5616_MP3_WT_1_4 (0x0 << 13) +#define RT5616_MP3_WT_1_2 (0x1 << 13) +#define RT5616_OG_MP3_MASK (0x1f << 8) +#define RT5616_OG_MP3_SFT 8 +#define RT5616_HG_MP3_MASK (0x3f) +#define RT5616_HG_MP3_SFT 0 + +/* 3D HP Control 1 (0xd2) */ +#define RT5616_3D_CF_MASK (0x1 << 15) +#define RT5616_3D_CF_SFT 15 +#define RT5616_3D_CF_DIS (0x0 << 15) +#define RT5616_3D_CF_EN (0x1 << 15) +#define RT5616_3D_HP_MASK (0x1 << 14) +#define RT5616_3D_HP_SFT 14 +#define RT5616_3D_HP_DIS (0x0 << 14) +#define RT5616_3D_HP_EN (0x1 << 14) +#define RT5616_3D_BT_MASK (0x1 << 13) +#define RT5616_3D_BT_SFT 13 +#define RT5616_3D_BT_DIS (0x0 << 13) +#define RT5616_3D_BT_EN (0x1 << 13) +#define RT5616_3D_1F_MIX_MASK (0x3 << 11) +#define RT5616_3D_1F_MIX_SFT 11 +#define RT5616_3D_HP_M_MASK (0x1 << 10) +#define RT5616_3D_HP_M_SFT 10 +#define RT5616_3D_HP_M_SUR (0x0 << 10) +#define RT5616_3D_HP_M_FRO (0x1 << 10) +#define RT5616_M_3D_HRTF_MASK (0x1 << 9) +#define RT5616_M_3D_HRTF_SFT 9 +#define RT5616_M_3D_D2H_MASK (0x1 << 8) +#define RT5616_M_3D_D2H_SFT 8 +#define RT5616_M_3D_D2R_MASK (0x1 << 7) +#define RT5616_M_3D_D2R_SFT 7 +#define RT5616_M_3D_REVB_MASK (0x1 << 6) +#define RT5616_M_3D_REVB_SFT 6 + +/* Adjustable high pass filter control 1 (0xd3) */ +#define RT5616_2ND_HPF_MASK (0x1 << 15) +#define RT5616_2ND_HPF_SFT 15 +#define RT5616_2ND_HPF_DIS (0x0 << 15) +#define RT5616_2ND_HPF_EN (0x1 << 15) +#define RT5616_HPF_CF_L_MASK (0x7 << 12) +#define RT5616_HPF_CF_L_SFT 12 +#define RT5616_HPF_CF_R_MASK (0x7 << 8) +#define RT5616_HPF_CF_R_SFT 8 +#define RT5616_ZD_T_MASK (0x3 << 6) +#define RT5616_ZD_T_SFT 6 +#define RT5616_ZD_F_MASK (0x3 << 4) +#define RT5616_ZD_F_SFT 4 +#define RT5616_ZD_F_IM (0x0 << 4) +#define RT5616_ZD_F_ZC_IM (0x1 << 4) +#define RT5616_ZD_F_ZC_IOD (0x2 << 4) +#define RT5616_ZD_F_UN (0x3 << 4) + +/* Adjustable high pass filter control 2 (0xd4) */ +#define RT5616_HPF_CF_L_NUM_MASK (0x3f << 8) +#define RT5616_HPF_CF_L_NUM_SFT 8 +#define RT5616_HPF_CF_R_NUM_MASK (0x3f) +#define RT5616_HPF_CF_R_NUM_SFT 0 + +/* HP calibration control and Amp detection (0xd6) */ +#define RT5616_SI_DAC_MASK (0x1 << 11) +#define RT5616_SI_DAC_SFT 11 +#define RT5616_SI_DAC_AUTO (0x0 << 11) +#define RT5616_SI_DAC_TEST (0x1 << 11) +#define RT5616_DC_CAL_M_MASK (0x1 << 10) +#define RT5616_DC_CAL_M_SFT 10 +#define RT5616_DC_CAL_M_NOR (0x0 << 10) +#define RT5616_DC_CAL_M_CAL (0x1 << 10) +#define RT5616_DC_CAL_MASK (0x1 << 9) +#define RT5616_DC_CAL_SFT 9 +#define RT5616_DC_CAL_DIS (0x0 << 9) +#define RT5616_DC_CAL_EN (0x1 << 9) +#define RT5616_HPD_RCV_MASK (0x7 << 6) +#define RT5616_HPD_RCV_SFT 6 +#define RT5616_HPD_PS_MASK (0x1 << 5) +#define RT5616_HPD_PS_SFT 5 +#define RT5616_HPD_PS_DIS (0x0 << 5) +#define RT5616_HPD_PS_EN (0x1 << 5) +#define RT5616_CAL_M_MASK (0x1 << 4) +#define RT5616_CAL_M_SFT 4 +#define RT5616_CAL_M_DEP (0x0 << 4) +#define RT5616_CAL_M_CAL (0x1 << 4) +#define RT5616_CAL_MASK (0x1 << 3) +#define RT5616_CAL_SFT 3 +#define RT5616_CAL_DIS (0x0 << 3) +#define RT5616_CAL_EN (0x1 << 3) +#define RT5616_CAL_TEST_MASK (0x1 << 2) +#define RT5616_CAL_TEST_SFT 2 +#define RT5616_CAL_TEST_DIS (0x0 << 2) +#define RT5616_CAL_TEST_EN (0x1 << 2) +#define RT5616_CAL_P_MASK (0x3) +#define RT5616_CAL_P_SFT 0 +#define RT5616_CAL_P_NONE (0x0) +#define RT5616_CAL_P_CAL (0x1) +#define RT5616_CAL_P_DAC_CAL (0x2) + +/* Soft volume and zero cross control 1 (0xd9) */ +#define RT5616_SV_MASK (0x1 << 15) +#define RT5616_SV_SFT 15 +#define RT5616_SV_DIS (0x0 << 15) +#define RT5616_SV_EN (0x1 << 15) +#define RT5616_OUT_SV_MASK (0x1 << 13) +#define RT5616_OUT_SV_SFT 13 +#define RT5616_OUT_SV_DIS (0x0 << 13) +#define RT5616_OUT_SV_EN (0x1 << 13) +#define RT5616_HP_SV_MASK (0x1 << 12) +#define RT5616_HP_SV_SFT 12 +#define RT5616_HP_SV_DIS (0x0 << 12) +#define RT5616_HP_SV_EN (0x1 << 12) +#define RT5616_ZCD_DIG_MASK (0x1 << 11) +#define RT5616_ZCD_DIG_SFT 11 +#define RT5616_ZCD_DIG_DIS (0x0 << 11) +#define RT5616_ZCD_DIG_EN (0x1 << 11) +#define RT5616_ZCD_MASK (0x1 << 10) +#define RT5616_ZCD_SFT 10 +#define RT5616_ZCD_PD (0x0 << 10) +#define RT5616_ZCD_PU (0x1 << 10) +#define RT5616_M_ZCD_MASK (0x3f << 4) +#define RT5616_M_ZCD_SFT 4 +#define RT5616_M_ZCD_OM_L (0x1 << 7) +#define RT5616_M_ZCD_OM_R (0x1 << 6) +#define RT5616_M_ZCD_RM_L (0x1 << 5) +#define RT5616_M_ZCD_RM_R (0x1 << 4) +#define RT5616_SV_DLY_MASK (0xf) +#define RT5616_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0xda) */ +#define RT5616_ZCD_HP_MASK (0x1 << 15) +#define RT5616_ZCD_HP_SFT 15 +#define RT5616_ZCD_HP_DIS (0x0 << 15) +#define RT5616_ZCD_HP_EN (0x1 << 15) + +/* Digital Misc Control (0xfa) */ +#define RT5616_I2S2_MS_SP_MASK (0x1 << 8) +#define RT5616_I2S2_MS_SP_SEL 8 +#define RT5616_I2S2_MS_SP_64 (0x0 << 8) +#define RT5616_I2S2_MS_SP_50 (0x1 << 8) +#define RT5616_CLK_DET_EN (0x1 << 3) +#define RT5616_CLK_DET_EN_SFT 3 +#define RT5616_AMP_DET_EN (0x1 << 1) +#define RT5616_AMP_DET_EN_SFT 1 +#define RT5616_D_GATE_EN (0x1) +#define RT5616_D_GATE_EN_SFT 0 + +/* Codec Private Register definition */ +/* 3D Speaker Control (0x63) */ +#define RT5616_3D_SPK_MASK (0x1 << 15) +#define RT5616_3D_SPK_SFT 15 +#define RT5616_3D_SPK_DIS (0x0 << 15) +#define RT5616_3D_SPK_EN (0x1 << 15) +#define RT5616_3D_SPK_M_MASK (0x3 << 13) +#define RT5616_3D_SPK_M_SFT 13 +#define RT5616_3D_SPK_CG_MASK (0x1f << 8) +#define RT5616_3D_SPK_CG_SFT 8 +#define RT5616_3D_SPK_SG_MASK (0x1f) +#define RT5616_3D_SPK_SG_SFT 0 + +/* Wind Noise Detection Control 1 (0x6c) */ +#define RT5616_WND_MASK (0x1 << 15) +#define RT5616_WND_SFT 15 +#define RT5616_WND_DIS (0x0 << 15) +#define RT5616_WND_EN (0x1 << 15) + +/* Wind Noise Detection Control 2 (0x6d) */ +#define RT5616_WND_FC_NW_MASK (0x3f << 10) +#define RT5616_WND_FC_NW_SFT 10 +#define RT5616_WND_FC_WK_MASK (0x3f << 4) +#define RT5616_WND_FC_WK_SFT 4 + +/* Wind Noise Detection Control 3 (0x6e) */ +#define RT5616_HPF_FC_MASK (0x3f << 6) +#define RT5616_HPF_FC_SFT 6 +#define RT5616_WND_FC_ST_MASK (0x3f) +#define RT5616_WND_FC_ST_SFT 0 + +/* Wind Noise Detection Control 4 (0x6f) */ +#define RT5616_WND_TH_LO_MASK (0x3ff) +#define RT5616_WND_TH_LO_SFT 0 + +/* Wind Noise Detection Control 5 (0x70) */ +#define RT5616_WND_TH_HI_MASK (0x3ff) +#define RT5616_WND_TH_HI_SFT 0 + +/* Wind Noise Detection Control 8 (0x73) */ +#define RT5616_WND_WIND_MASK (0x1 << 13) /* Read-Only */ +#define RT5616_WND_WIND_SFT 13 +#define RT5616_WND_STRONG_MASK (0x1 << 12) /* Read-Only */ +#define RT5616_WND_STRONG_SFT 12 +enum { + RT5616_NO_WIND, + RT5616_BREEZE, + RT5616_STORM, +}; + +/* Dipole Speaker Interface (0x75) */ +#define RT5616_DP_ATT_MASK (0x3 << 14) +#define RT5616_DP_ATT_SFT 14 +#define RT5616_DP_SPK_MASK (0x1 << 10) +#define RT5616_DP_SPK_SFT 10 +#define RT5616_DP_SPK_DIS (0x0 << 10) +#define RT5616_DP_SPK_EN (0x1 << 10) + +/* EQ Pre Volume Control (0xb3) */ +#define RT5616_EQ_PRE_VOL_MASK (0xffff) +#define RT5616_EQ_PRE_VOL_SFT 0 + +/* EQ Post Volume Control (0xb4) */ +#define RT5616_EQ_PST_VOL_MASK (0xffff) +#define RT5616_EQ_PST_VOL_SFT 0 + +/* System Clock Source */ +enum { + RT5616_SCLK_S_MCLK, + RT5616_SCLK_S_PLL1, +}; + +/* PLL1 Source */ +enum { + RT5616_PLL1_S_MCLK, + RT5616_PLL1_S_BCLK1, + RT5616_PLL1_S_BCLK2, +}; + +enum { + RT5616_AIF1, + RT5616_AIFS, +}; + +#endif /* __RT5616_H__ */ diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c new file mode 100644 index 000000000..865f49ac3 --- /dev/null +++ b/sound/soc/codecs/rt5631.c @@ -0,0 +1,1742 @@ +/* + * rt5631.c -- RT5631 ALSA Soc Audio driver + * + * Copyright 2011 Realtek Microelectronics + * + * Author: flove + * + * Based on WM8753.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt5631.h" + +struct rt5631_priv { + struct regmap *regmap; + int codec_version; + int master; + int sysclk; + int rx_rate; + int bclk_rate; + int dmic_used_flag; +}; + +static const struct reg_default rt5631_reg[] = { + { RT5631_SPK_OUT_VOL, 0x8888 }, + { RT5631_HP_OUT_VOL, 0x8080 }, + { RT5631_MONO_AXO_1_2_VOL, 0xa080 }, + { RT5631_AUX_IN_VOL, 0x0808 }, + { RT5631_ADC_REC_MIXER, 0xf0f0 }, + { RT5631_VDAC_DIG_VOL, 0x0010 }, + { RT5631_OUTMIXER_L_CTRL, 0xffc0 }, + { RT5631_OUTMIXER_R_CTRL, 0xffc0 }, + { RT5631_AXO1MIXER_CTRL, 0x88c0 }, + { RT5631_AXO2MIXER_CTRL, 0x88c0 }, + { RT5631_DIG_MIC_CTRL, 0x3000 }, + { RT5631_MONO_INPUT_VOL, 0x8808 }, + { RT5631_SPK_MIXER_CTRL, 0xf8f8 }, + { RT5631_SPK_MONO_OUT_CTRL, 0xfc00 }, + { RT5631_SPK_MONO_HP_OUT_CTRL, 0x4440 }, + { RT5631_SDP_CTRL, 0x8000 }, + { RT5631_MONO_SDP_CTRL, 0x8000 }, + { RT5631_STEREO_AD_DA_CLK_CTRL, 0x2010 }, + { RT5631_GEN_PUR_CTRL_REG, 0x0e00 }, + { RT5631_INT_ST_IRQ_CTRL_2, 0x071a }, + { RT5631_MISC_CTRL, 0x2040 }, + { RT5631_DEPOP_FUN_CTRL_2, 0x8000 }, + { RT5631_SOFT_VOL_CTRL, 0x07e0 }, + { RT5631_ALC_CTRL_1, 0x0206 }, + { RT5631_ALC_CTRL_3, 0x2000 }, + { RT5631_PSEUDO_SPATL_CTRL, 0x0553 }, +}; + +/** + * rt5631_write_index - write index register of 2nd layer + */ +static void rt5631_write_index(struct snd_soc_component *component, + unsigned int reg, unsigned int value) +{ + snd_soc_component_write(component, RT5631_INDEX_ADD, reg); + snd_soc_component_write(component, RT5631_INDEX_DATA, value); +} + +/** + * rt5631_read_index - read index register of 2nd layer + */ +static unsigned int rt5631_read_index(struct snd_soc_component *component, + unsigned int reg) +{ + unsigned int value; + + snd_soc_component_write(component, RT5631_INDEX_ADD, reg); + value = snd_soc_component_read32(component, RT5631_INDEX_DATA); + + return value; +} + +static int rt5631_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, RT5631_RESET, 0); +} + +static bool rt5631_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5631_RESET: + case RT5631_INT_ST_IRQ_CTRL_2: + case RT5631_INDEX_ADD: + case RT5631_INDEX_DATA: + case RT5631_EQ_CTRL: + return true; + default: + return false; + } +} + +static bool rt5631_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5631_RESET: + case RT5631_SPK_OUT_VOL: + case RT5631_HP_OUT_VOL: + case RT5631_MONO_AXO_1_2_VOL: + case RT5631_AUX_IN_VOL: + case RT5631_STEREO_DAC_VOL_1: + case RT5631_MIC_CTRL_1: + case RT5631_STEREO_DAC_VOL_2: + case RT5631_ADC_CTRL_1: + case RT5631_ADC_REC_MIXER: + case RT5631_ADC_CTRL_2: + case RT5631_VDAC_DIG_VOL: + case RT5631_OUTMIXER_L_CTRL: + case RT5631_OUTMIXER_R_CTRL: + case RT5631_AXO1MIXER_CTRL: + case RT5631_AXO2MIXER_CTRL: + case RT5631_MIC_CTRL_2: + case RT5631_DIG_MIC_CTRL: + case RT5631_MONO_INPUT_VOL: + case RT5631_SPK_MIXER_CTRL: + case RT5631_SPK_MONO_OUT_CTRL: + case RT5631_SPK_MONO_HP_OUT_CTRL: + case RT5631_SDP_CTRL: + case RT5631_MONO_SDP_CTRL: + case RT5631_STEREO_AD_DA_CLK_CTRL: + case RT5631_PWR_MANAG_ADD1: + case RT5631_PWR_MANAG_ADD2: + case RT5631_PWR_MANAG_ADD3: + case RT5631_PWR_MANAG_ADD4: + case RT5631_GEN_PUR_CTRL_REG: + case RT5631_GLOBAL_CLK_CTRL: + case RT5631_PLL_CTRL: + case RT5631_INT_ST_IRQ_CTRL_1: + case RT5631_INT_ST_IRQ_CTRL_2: + case RT5631_GPIO_CTRL: + case RT5631_MISC_CTRL: + case RT5631_DEPOP_FUN_CTRL_1: + case RT5631_DEPOP_FUN_CTRL_2: + case RT5631_JACK_DET_CTRL: + case RT5631_SOFT_VOL_CTRL: + case RT5631_ALC_CTRL_1: + case RT5631_ALC_CTRL_2: + case RT5631_ALC_CTRL_3: + case RT5631_PSEUDO_SPATL_CTRL: + case RT5631_INDEX_ADD: + case RT5631_INDEX_DATA: + case RT5631_EQ_CTRL: + case RT5631_VENDOR_ID: + case RT5631_VENDOR_ID1: + case RT5631_VENDOR_ID2: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -95625, 375, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +/* {0, +20, +24, +30, +35, +40, +44, +50, +52}dB */ +static const DECLARE_TLV_DB_RANGE(mic_bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +static int rt5631_dmic_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = rt5631->dmic_used_flag; + + return 0; +} + +static int rt5631_dmic_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + + rt5631->dmic_used_flag = ucontrol->value.integer.value[0]; + return 0; +} + +/* MIC Input Type */ +static const char *rt5631_input_mode[] = { + "Single ended", "Differential"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_mic1_mode_enum, RT5631_MIC_CTRL_1, + RT5631_MIC1_DIFF_INPUT_SHIFT, rt5631_input_mode); + +static SOC_ENUM_SINGLE_DECL(rt5631_mic2_mode_enum, RT5631_MIC_CTRL_1, + RT5631_MIC2_DIFF_INPUT_SHIFT, rt5631_input_mode); + +/* MONO Input Type */ +static SOC_ENUM_SINGLE_DECL(rt5631_monoin_mode_enum, RT5631_MONO_INPUT_VOL, + RT5631_MONO_DIFF_INPUT_SHIFT, rt5631_input_mode); + +/* SPK Ratio Gain Control */ +static const char *rt5631_spk_ratio[] = {"1.00x", "1.09x", "1.27x", "1.44x", + "1.56x", "1.68x", "1.99x", "2.34x"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_spk_ratio_enum, RT5631_GEN_PUR_CTRL_REG, + RT5631_SPK_AMP_RATIO_CTRL_SHIFT, rt5631_spk_ratio); + +static const struct snd_kcontrol_new rt5631_snd_controls[] = { + /* MIC */ + SOC_ENUM("MIC1 Mode Control", rt5631_mic1_mode_enum), + SOC_SINGLE_TLV("MIC1 Boost Volume", RT5631_MIC_CTRL_2, + RT5631_MIC1_BOOST_SHIFT, 8, 0, mic_bst_tlv), + SOC_ENUM("MIC2 Mode Control", rt5631_mic2_mode_enum), + SOC_SINGLE_TLV("MIC2 Boost Volume", RT5631_MIC_CTRL_2, + RT5631_MIC2_BOOST_SHIFT, 8, 0, mic_bst_tlv), + /* MONO IN */ + SOC_ENUM("MONOIN Mode Control", rt5631_monoin_mode_enum), + SOC_DOUBLE_TLV("MONOIN_RX Capture Volume", RT5631_MONO_INPUT_VOL, + RT5631_L_VOL_SHIFT, RT5631_R_VOL_SHIFT, + RT5631_VOL_MASK, 1, in_vol_tlv), + /* AXI */ + SOC_DOUBLE_TLV("AXI Capture Volume", RT5631_AUX_IN_VOL, + RT5631_L_VOL_SHIFT, RT5631_R_VOL_SHIFT, + RT5631_VOL_MASK, 1, in_vol_tlv), + /* DAC */ + SOC_DOUBLE_TLV("PCM Playback Volume", RT5631_STEREO_DAC_VOL_2, + RT5631_L_VOL_SHIFT, RT5631_R_VOL_SHIFT, + RT5631_DAC_VOL_MASK, 1, dac_vol_tlv), + SOC_DOUBLE("PCM Playback Switch", RT5631_STEREO_DAC_VOL_1, + RT5631_L_MUTE_SHIFT, RT5631_R_MUTE_SHIFT, 1, 1), + /* AXO */ + SOC_SINGLE("AXO1 Playback Switch", RT5631_MONO_AXO_1_2_VOL, + RT5631_L_MUTE_SHIFT, 1, 1), + SOC_SINGLE("AXO2 Playback Switch", RT5631_MONO_AXO_1_2_VOL, + RT5631_R_VOL_SHIFT, 1, 1), + /* OUTVOL */ + SOC_DOUBLE("OUTVOL Channel Switch", RT5631_SPK_OUT_VOL, + RT5631_L_EN_SHIFT, RT5631_R_EN_SHIFT, 1, 0), + + /* SPK */ + SOC_DOUBLE("Speaker Playback Switch", RT5631_SPK_OUT_VOL, + RT5631_L_MUTE_SHIFT, RT5631_R_MUTE_SHIFT, 1, 1), + SOC_DOUBLE_TLV("Speaker Playback Volume", RT5631_SPK_OUT_VOL, + RT5631_L_VOL_SHIFT, RT5631_R_VOL_SHIFT, 39, 1, out_vol_tlv), + /* MONO OUT */ + SOC_SINGLE("MONO Playback Switch", RT5631_MONO_AXO_1_2_VOL, + RT5631_MUTE_MONO_SHIFT, 1, 1), + /* HP */ + SOC_DOUBLE("HP Playback Switch", RT5631_HP_OUT_VOL, + RT5631_L_MUTE_SHIFT, RT5631_R_MUTE_SHIFT, 1, 1), + SOC_DOUBLE_TLV("HP Playback Volume", RT5631_HP_OUT_VOL, + RT5631_L_VOL_SHIFT, RT5631_R_VOL_SHIFT, + RT5631_VOL_MASK, 1, out_vol_tlv), + /* DMIC */ + SOC_SINGLE_EXT("DMIC Switch", 0, 0, 1, 0, + rt5631_dmic_get, rt5631_dmic_put), + SOC_DOUBLE("DMIC Capture Switch", RT5631_DIG_MIC_CTRL, + RT5631_DMIC_L_CH_MUTE_SHIFT, + RT5631_DMIC_R_CH_MUTE_SHIFT, 1, 1), + + /* SPK Ratio Gain Control */ + SOC_ENUM("SPK Ratio Control", rt5631_spk_ratio_enum), +}; + +static int check_sysclk1_source(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, RT5631_GLOBAL_CLK_CTRL); + return reg & RT5631_SYSCLK_SOUR_SEL_PLL; +} + +static int check_dmic_used(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + return rt5631->dmic_used_flag; +} + +static int check_dacl_to_outmixl(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, RT5631_OUTMIXER_L_CTRL); + return !(reg & RT5631_M_DAC_L_TO_OUTMIXER_L); +} + +static int check_dacr_to_outmixr(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, RT5631_OUTMIXER_R_CTRL); + return !(reg & RT5631_M_DAC_R_TO_OUTMIXER_R); +} + +static int check_dacl_to_spkmixl(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, RT5631_SPK_MIXER_CTRL); + return !(reg & RT5631_M_DAC_L_TO_SPKMIXER_L); +} + +static int check_dacr_to_spkmixr(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, RT5631_SPK_MIXER_CTRL); + return !(reg & RT5631_M_DAC_R_TO_SPKMIXER_R); +} + +static int check_adcl_select(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, RT5631_ADC_REC_MIXER); + return !(reg & RT5631_M_MIC1_TO_RECMIXER_L); +} + +static int check_adcr_select(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, RT5631_ADC_REC_MIXER); + return !(reg & RT5631_M_MIC2_TO_RECMIXER_R); +} + +/** + * onebit_depop_power_stage - auto depop in power stage. + * @enable: power on/off + * + * When power on/off headphone, the depop sequence is done by hardware. + */ +static void onebit_depop_power_stage(struct snd_soc_component *component, int enable) +{ + unsigned int soft_vol, hp_zc; + + /* enable one-bit depop function */ + snd_soc_component_update_bits(component, RT5631_DEPOP_FUN_CTRL_2, + RT5631_EN_ONE_BIT_DEPOP, 0); + + /* keep soft volume and zero crossing setting */ + soft_vol = snd_soc_component_read32(component, RT5631_SOFT_VOL_CTRL); + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, 0); + hp_zc = snd_soc_component_read32(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { + /* config one-bit depop parameter */ + rt5631_write_index(component, RT5631_TEST_MODE_CTRL, 0x84c0); + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x309f); + rt5631_write_index(component, RT5631_CP_INTL_REG2, 0x6530); + /* power on capless block */ + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_2, + RT5631_EN_CAP_FREE_DEPOP); + } else { + /* power off capless block */ + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_2, 0); + msleep(100); + } + + /* recover soft volume and zero crossing setting */ + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, soft_vol); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc); +} + +/** + * onebit_depop_mute_stage - auto depop in mute stage. + * @enable: mute/unmute + * + * When mute/unmute headphone, the depop sequence is done by hardware. + */ +static void onebit_depop_mute_stage(struct snd_soc_component *component, int enable) +{ + unsigned int soft_vol, hp_zc; + + /* enable one-bit depop function */ + snd_soc_component_update_bits(component, RT5631_DEPOP_FUN_CTRL_2, + RT5631_EN_ONE_BIT_DEPOP, 0); + + /* keep soft volume and zero crossing setting */ + soft_vol = snd_soc_component_read32(component, RT5631_SOFT_VOL_CTRL); + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, 0); + hp_zc = snd_soc_component_read32(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { + schedule_timeout_uninterruptible(msecs_to_jiffies(10)); + /* config one-bit depop parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x307f); + snd_soc_component_update_bits(component, RT5631_HP_OUT_VOL, + RT5631_L_MUTE | RT5631_R_MUTE, 0); + msleep(300); + } else { + snd_soc_component_update_bits(component, RT5631_HP_OUT_VOL, + RT5631_L_MUTE | RT5631_R_MUTE, + RT5631_L_MUTE | RT5631_R_MUTE); + msleep(100); + } + + /* recover soft volume and zero crossing setting */ + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, soft_vol); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc); +} + +/** + * onebit_depop_power_stage - step by step depop sequence in power stage. + * @enable: power on/off + * + * When power on/off headphone, the depop sequence is done in step by step. + */ +static void depop_seq_power_stage(struct snd_soc_component *component, int enable) +{ + unsigned int soft_vol, hp_zc; + + /* depop control by register */ + snd_soc_component_update_bits(component, RT5631_DEPOP_FUN_CTRL_2, + RT5631_EN_ONE_BIT_DEPOP, RT5631_EN_ONE_BIT_DEPOP); + + /* keep soft volume and zero crossing setting */ + soft_vol = snd_soc_component_read32(component, RT5631_SOFT_VOL_CTRL); + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, 0); + hp_zc = snd_soc_component_read32(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { + /* config depop sequence parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x303e); + + /* power on headphone and charge pump */ + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_CHARGE_PUMP | RT5631_PWR_HP_L_AMP | + RT5631_PWR_HP_R_AMP, + RT5631_PWR_CHARGE_PUMP | RT5631_PWR_HP_L_AMP | + RT5631_PWR_HP_R_AMP); + + /* power on soft generator and depop mode2 */ + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_1, + RT5631_POW_ON_SOFT_GEN | RT5631_EN_DEPOP2_FOR_HP); + msleep(100); + + /* stop depop mode */ + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_HP_DEPOP_DIS, RT5631_PWR_HP_DEPOP_DIS); + } else { + /* config depop sequence parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x303F); + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_1, + RT5631_POW_ON_SOFT_GEN | RT5631_EN_MUTE_UNMUTE_DEPOP | + RT5631_PD_HPAMP_L_ST_UP | RT5631_PD_HPAMP_R_ST_UP); + msleep(75); + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_1, + RT5631_POW_ON_SOFT_GEN | RT5631_PD_HPAMP_L_ST_UP | + RT5631_PD_HPAMP_R_ST_UP); + + /* start depop mode */ + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_HP_DEPOP_DIS, 0); + + /* config depop sequence parameter */ + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_1, + RT5631_POW_ON_SOFT_GEN | RT5631_EN_DEPOP2_FOR_HP | + RT5631_PD_HPAMP_L_ST_UP | RT5631_PD_HPAMP_R_ST_UP); + msleep(80); + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_1, + RT5631_POW_ON_SOFT_GEN); + + /* power down headphone and charge pump */ + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_CHARGE_PUMP | RT5631_PWR_HP_L_AMP | + RT5631_PWR_HP_R_AMP, 0); + } + + /* recover soft volume and zero crossing setting */ + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, soft_vol); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc); +} + +/** + * depop_seq_mute_stage - step by step depop sequence in mute stage. + * @enable: mute/unmute + * + * When mute/unmute headphone, the depop sequence is done in step by step. + */ +static void depop_seq_mute_stage(struct snd_soc_component *component, int enable) +{ + unsigned int soft_vol, hp_zc; + + /* depop control by register */ + snd_soc_component_update_bits(component, RT5631_DEPOP_FUN_CTRL_2, + RT5631_EN_ONE_BIT_DEPOP, RT5631_EN_ONE_BIT_DEPOP); + + /* keep soft volume and zero crossing setting */ + soft_vol = snd_soc_component_read32(component, RT5631_SOFT_VOL_CTRL); + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, 0); + hp_zc = snd_soc_component_read32(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { + schedule_timeout_uninterruptible(msecs_to_jiffies(10)); + + /* config depop sequence parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x302f); + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_1, + RT5631_POW_ON_SOFT_GEN | RT5631_EN_MUTE_UNMUTE_DEPOP | + RT5631_EN_HP_R_M_UN_MUTE_DEPOP | + RT5631_EN_HP_L_M_UN_MUTE_DEPOP); + + snd_soc_component_update_bits(component, RT5631_HP_OUT_VOL, + RT5631_L_MUTE | RT5631_R_MUTE, 0); + msleep(160); + } else { + /* config depop sequence parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x302f); + snd_soc_component_write(component, RT5631_DEPOP_FUN_CTRL_1, + RT5631_POW_ON_SOFT_GEN | RT5631_EN_MUTE_UNMUTE_DEPOP | + RT5631_EN_HP_R_M_UN_MUTE_DEPOP | + RT5631_EN_HP_L_M_UN_MUTE_DEPOP); + + snd_soc_component_update_bits(component, RT5631_HP_OUT_VOL, + RT5631_L_MUTE | RT5631_R_MUTE, + RT5631_L_MUTE | RT5631_R_MUTE); + msleep(150); + } + + /* recover soft volume and zero crossing setting */ + snd_soc_component_write(component, RT5631_SOFT_VOL_CTRL, soft_vol); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc); +} + +static int hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + if (rt5631->codec_version) { + onebit_depop_mute_stage(component, 0); + onebit_depop_power_stage(component, 0); + } else { + depop_seq_mute_stage(component, 0); + depop_seq_power_stage(component, 0); + } + break; + + case SND_SOC_DAPM_POST_PMU: + if (rt5631->codec_version) { + onebit_depop_power_stage(component, 1); + onebit_depop_mute_stage(component, 1); + } else { + depop_seq_power_stage(component, 1); + depop_seq_mute_stage(component, 1); + } + break; + + default: + break; + } + + return 0; +} + +static int set_dmic_params(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + + switch (rt5631->rx_rate) { + case 44100: + case 48000: + snd_soc_component_update_bits(component, RT5631_DIG_MIC_CTRL, + RT5631_DMIC_CLK_CTRL_MASK, + RT5631_DMIC_CLK_CTRL_TO_32FS); + break; + + case 32000: + case 22050: + snd_soc_component_update_bits(component, RT5631_DIG_MIC_CTRL, + RT5631_DMIC_CLK_CTRL_MASK, + RT5631_DMIC_CLK_CTRL_TO_64FS); + break; + + case 16000: + case 11025: + case 8000: + snd_soc_component_update_bits(component, RT5631_DIG_MIC_CTRL, + RT5631_DMIC_CLK_CTRL_MASK, + RT5631_DMIC_CLK_CTRL_TO_128FS); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_kcontrol_new rt5631_recmixl_mixer_controls[] = { + SOC_DAPM_SINGLE("OUTMIXL Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_OUTMIXL_RECMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC1_BST1 Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_MIC1_RECMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("AXILVOL Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_AXIL_RECMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("MONOIN_RX Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_MONO_IN_RECMIXL_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_recmixr_mixer_controls[] = { + SOC_DAPM_SINGLE("MONOIN_RX Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_MONO_IN_RECMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("AXIRVOL Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_AXIR_RECMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC2_BST2 Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_MIC2_RECMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("OUTMIXR Capture Switch", RT5631_ADC_REC_MIXER, + RT5631_M_OUTMIXR_RECMIXR_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_spkmixl_mixer_controls[] = { + SOC_DAPM_SINGLE("RECMIXL Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_RECMIXL_SPKMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC1_P Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_MIC1P_SPKMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("DACL Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_DACL_SPKMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("OUTMIXL Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_OUTMIXL_SPKMIXL_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_spkmixr_mixer_controls[] = { + SOC_DAPM_SINGLE("OUTMIXR Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_OUTMIXR_SPKMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("DACR Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_DACR_SPKMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC2_P Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_MIC2P_SPKMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("RECMIXR Playback Switch", RT5631_SPK_MIXER_CTRL, + RT5631_M_RECMIXR_SPKMIXR_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_outmixl_mixer_controls[] = { + SOC_DAPM_SINGLE("RECMIXL Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_RECMIXL_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("RECMIXR Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_RECMIXR_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("DACL Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_DACL_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC1_BST1 Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_MIC1_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC2_BST2 Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_MIC2_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("MONOIN_RXP Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_MONO_INP_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("AXILVOL Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_AXIL_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("AXIRVOL Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_AXIR_OUTMIXL_BIT, 1, 1), + SOC_DAPM_SINGLE("VDAC Playback Switch", RT5631_OUTMIXER_L_CTRL, + RT5631_M_VDAC_OUTMIXL_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_outmixr_mixer_controls[] = { + SOC_DAPM_SINGLE("VDAC Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_VDAC_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("AXIRVOL Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_AXIR_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("AXILVOL Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_AXIL_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("MONOIN_RXN Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_MONO_INN_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC2_BST2 Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_MIC2_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC1_BST1 Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_MIC1_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("DACR Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_DACR_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("RECMIXR Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_RECMIXR_OUTMIXR_BIT, 1, 1), + SOC_DAPM_SINGLE("RECMIXL Playback Switch", RT5631_OUTMIXER_R_CTRL, + RT5631_M_RECMIXL_OUTMIXR_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_AXO1MIX_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1_BST1 Playback Switch", RT5631_AXO1MIXER_CTRL, + RT5631_M_MIC1_AXO1MIX_BIT , 1, 1), + SOC_DAPM_SINGLE("MIC2_BST2 Playback Switch", RT5631_AXO1MIXER_CTRL, + RT5631_M_MIC2_AXO1MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("OUTVOLL Playback Switch", RT5631_AXO1MIXER_CTRL, + RT5631_M_OUTMIXL_AXO1MIX_BIT , 1 , 1), + SOC_DAPM_SINGLE("OUTVOLR Playback Switch", RT5631_AXO1MIXER_CTRL, + RT5631_M_OUTMIXR_AXO1MIX_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_AXO2MIX_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1_BST1 Playback Switch", RT5631_AXO2MIXER_CTRL, + RT5631_M_MIC1_AXO2MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("MIC2_BST2 Playback Switch", RT5631_AXO2MIXER_CTRL, + RT5631_M_MIC2_AXO2MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("OUTVOLL Playback Switch", RT5631_AXO2MIXER_CTRL, + RT5631_M_OUTMIXL_AXO2MIX_BIT, 1, 1), + SOC_DAPM_SINGLE("OUTVOLR Playback Switch", RT5631_AXO2MIXER_CTRL, + RT5631_M_OUTMIXR_AXO2MIX_BIT, 1 , 1), +}; + +static const struct snd_kcontrol_new rt5631_spolmix_mixer_controls[] = { + SOC_DAPM_SINGLE("SPKVOLL Playback Switch", RT5631_SPK_MONO_OUT_CTRL, + RT5631_M_SPKVOLL_SPOLMIX_BIT, 1, 1), + SOC_DAPM_SINGLE("SPKVOLR Playback Switch", RT5631_SPK_MONO_OUT_CTRL, + RT5631_M_SPKVOLR_SPOLMIX_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_spormix_mixer_controls[] = { + SOC_DAPM_SINGLE("SPKVOLL Playback Switch", RT5631_SPK_MONO_OUT_CTRL, + RT5631_M_SPKVOLL_SPORMIX_BIT, 1, 1), + SOC_DAPM_SINGLE("SPKVOLR Playback Switch", RT5631_SPK_MONO_OUT_CTRL, + RT5631_M_SPKVOLR_SPORMIX_BIT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5631_monomix_mixer_controls[] = { + SOC_DAPM_SINGLE("OUTVOLL Playback Switch", RT5631_SPK_MONO_OUT_CTRL, + RT5631_M_OUTVOLL_MONOMIX_BIT, 1, 1), + SOC_DAPM_SINGLE("OUTVOLR Playback Switch", RT5631_SPK_MONO_OUT_CTRL, + RT5631_M_OUTVOLR_MONOMIX_BIT, 1, 1), +}; + +/* Left SPK Volume Input */ +static const char *rt5631_spkvoll_sel[] = {"Vmid", "SPKMIXL"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_spkvoll_enum, RT5631_SPK_OUT_VOL, + RT5631_L_EN_SHIFT, rt5631_spkvoll_sel); + +static const struct snd_kcontrol_new rt5631_spkvoll_mux_control = + SOC_DAPM_ENUM("Left SPKVOL SRC", rt5631_spkvoll_enum); + +/* Left HP Volume Input */ +static const char *rt5631_hpvoll_sel[] = {"Vmid", "OUTMIXL"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_hpvoll_enum, RT5631_HP_OUT_VOL, + RT5631_L_EN_SHIFT, rt5631_hpvoll_sel); + +static const struct snd_kcontrol_new rt5631_hpvoll_mux_control = + SOC_DAPM_ENUM("Left HPVOL SRC", rt5631_hpvoll_enum); + +/* Left Out Volume Input */ +static const char *rt5631_outvoll_sel[] = {"Vmid", "OUTMIXL"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_outvoll_enum, RT5631_MONO_AXO_1_2_VOL, + RT5631_L_EN_SHIFT, rt5631_outvoll_sel); + +static const struct snd_kcontrol_new rt5631_outvoll_mux_control = + SOC_DAPM_ENUM("Left OUTVOL SRC", rt5631_outvoll_enum); + +/* Right Out Volume Input */ +static const char *rt5631_outvolr_sel[] = {"Vmid", "OUTMIXR"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_outvolr_enum, RT5631_MONO_AXO_1_2_VOL, + RT5631_R_EN_SHIFT, rt5631_outvolr_sel); + +static const struct snd_kcontrol_new rt5631_outvolr_mux_control = + SOC_DAPM_ENUM("Right OUTVOL SRC", rt5631_outvolr_enum); + +/* Right HP Volume Input */ +static const char *rt5631_hpvolr_sel[] = {"Vmid", "OUTMIXR"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_hpvolr_enum, RT5631_HP_OUT_VOL, + RT5631_R_EN_SHIFT, rt5631_hpvolr_sel); + +static const struct snd_kcontrol_new rt5631_hpvolr_mux_control = + SOC_DAPM_ENUM("Right HPVOL SRC", rt5631_hpvolr_enum); + +/* Right SPK Volume Input */ +static const char *rt5631_spkvolr_sel[] = {"Vmid", "SPKMIXR"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_spkvolr_enum, RT5631_SPK_OUT_VOL, + RT5631_R_EN_SHIFT, rt5631_spkvolr_sel); + +static const struct snd_kcontrol_new rt5631_spkvolr_mux_control = + SOC_DAPM_ENUM("Right SPKVOL SRC", rt5631_spkvolr_enum); + +/* SPO Left Channel Input */ +static const char *rt5631_spol_src_sel[] = { + "SPOLMIX", "MONOIN_RX", "VDAC", "DACL"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_spol_src_enum, RT5631_SPK_MONO_HP_OUT_CTRL, + RT5631_SPK_L_MUX_SEL_SHIFT, rt5631_spol_src_sel); + +static const struct snd_kcontrol_new rt5631_spol_mux_control = + SOC_DAPM_ENUM("SPOL SRC", rt5631_spol_src_enum); + +/* SPO Right Channel Input */ +static const char *rt5631_spor_src_sel[] = { + "SPORMIX", "MONOIN_RX", "VDAC", "DACR"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_spor_src_enum, RT5631_SPK_MONO_HP_OUT_CTRL, + RT5631_SPK_R_MUX_SEL_SHIFT, rt5631_spor_src_sel); + +static const struct snd_kcontrol_new rt5631_spor_mux_control = + SOC_DAPM_ENUM("SPOR SRC", rt5631_spor_src_enum); + +/* MONO Input */ +static const char *rt5631_mono_src_sel[] = {"MONOMIX", "MONOIN_RX", "VDAC"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_mono_src_enum, RT5631_SPK_MONO_HP_OUT_CTRL, + RT5631_MONO_MUX_SEL_SHIFT, rt5631_mono_src_sel); + +static const struct snd_kcontrol_new rt5631_mono_mux_control = + SOC_DAPM_ENUM("MONO SRC", rt5631_mono_src_enum); + +/* Left HPO Input */ +static const char *rt5631_hpl_src_sel[] = {"Left HPVOL", "Left DAC"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_hpl_src_enum, RT5631_SPK_MONO_HP_OUT_CTRL, + RT5631_HP_L_MUX_SEL_SHIFT, rt5631_hpl_src_sel); + +static const struct snd_kcontrol_new rt5631_hpl_mux_control = + SOC_DAPM_ENUM("HPL SRC", rt5631_hpl_src_enum); + +/* Right HPO Input */ +static const char *rt5631_hpr_src_sel[] = {"Right HPVOL", "Right DAC"}; + +static SOC_ENUM_SINGLE_DECL(rt5631_hpr_src_enum, RT5631_SPK_MONO_HP_OUT_CTRL, + RT5631_HP_R_MUX_SEL_SHIFT, rt5631_hpr_src_sel); + +static const struct snd_kcontrol_new rt5631_hpr_mux_control = + SOC_DAPM_ENUM("HPR SRC", rt5631_hpr_src_enum); + +static const struct snd_soc_dapm_widget rt5631_dapm_widgets[] = { + /* Vmid */ + SND_SOC_DAPM_VMID("Vmid"), + /* PLL1 */ + SND_SOC_DAPM_SUPPLY("PLL1", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_PLL1_BIT, 0, NULL, 0), + + /* Input Side */ + /* Input Lines */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("AXIL"), + SND_SOC_DAPM_INPUT("AXIR"), + SND_SOC_DAPM_INPUT("MONOIN_RXN"), + SND_SOC_DAPM_INPUT("MONOIN_RXP"), + SND_SOC_DAPM_INPUT("DMIC"), + + /* MICBIAS */ + SND_SOC_DAPM_MICBIAS("MIC Bias1", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_MICBIAS1_VOL_BIT, 0), + SND_SOC_DAPM_MICBIAS("MIC Bias2", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_MICBIAS2_VOL_BIT, 0), + + /* Boost */ + SND_SOC_DAPM_PGA("MIC1 Boost", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_MIC1_BOOT_GAIN_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIC2 Boost", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_MIC2_BOOT_GAIN_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("MONOIN_RXP Boost", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_MONO_IN_P_VOL_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("MONOIN_RXN Boost", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_MONO_IN_N_VOL_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("AXIL Boost", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_AXIL_IN_VOL_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("AXIR Boost", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_AXIR_IN_VOL_BIT, 0, NULL, 0), + + /* MONO In */ + SND_SOC_DAPM_MIXER("MONO_IN", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIXL Mixer", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_RECMIXER_L_BIT, 0, + &rt5631_recmixl_mixer_controls[0], + ARRAY_SIZE(rt5631_recmixl_mixer_controls)), + SND_SOC_DAPM_MIXER("RECMIXR Mixer", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_RECMIXER_R_BIT, 0, + &rt5631_recmixr_mixer_controls[0], + ARRAY_SIZE(rt5631_recmixr_mixer_controls)), + /* Because of record duplication for L/R channel, + * L/R ADCs need power up at the same time */ + SND_SOC_DAPM_MIXER("ADC Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DMIC */ + SND_SOC_DAPM_SUPPLY("DMIC Supply", RT5631_DIG_MIC_CTRL, + RT5631_DMIC_ENA_SHIFT, 0, + set_dmic_params, SND_SOC_DAPM_PRE_PMU), + /* ADC Data Srouce */ + SND_SOC_DAPM_SUPPLY("Left ADC Select", RT5631_INT_ST_IRQ_CTRL_2, + RT5631_ADC_DATA_SEL_MIC1_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right ADC Select", RT5631_INT_ST_IRQ_CTRL_2, + RT5631_ADC_DATA_SEL_MIC2_SHIFT, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("Left ADC", "HIFI Capture", + RT5631_PWR_MANAG_ADD1, RT5631_PWR_ADC_L_CLK_BIT, 0), + SND_SOC_DAPM_ADC("Right ADC", "HIFI Capture", + RT5631_PWR_MANAG_ADD1, RT5631_PWR_ADC_R_CLK_BIT, 0), + + /* DAC and ADC supply power */ + SND_SOC_DAPM_SUPPLY("I2S", RT5631_PWR_MANAG_ADD1, + RT5631_PWR_MAIN_I2S_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC REF", RT5631_PWR_MANAG_ADD1, + RT5631_PWR_DAC_REF_BIT, 0, NULL, 0), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("Left DAC", "HIFI Playback", + RT5631_PWR_MANAG_ADD1, RT5631_PWR_DAC_L_CLK_BIT, 0), + SND_SOC_DAPM_DAC("Right DAC", "HIFI Playback", + RT5631_PWR_MANAG_ADD1, RT5631_PWR_DAC_R_CLK_BIT, 0), + SND_SOC_DAPM_DAC("Voice DAC", "Voice DAC Mono Playback", + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("Voice DAC Boost", SND_SOC_NOPM, 0, 0, NULL, 0), + /* DAC supply power */ + SND_SOC_DAPM_SUPPLY("Left DAC To Mixer", RT5631_PWR_MANAG_ADD1, + RT5631_PWR_DAC_L_TO_MIXER_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right DAC To Mixer", RT5631_PWR_MANAG_ADD1, + RT5631_PWR_DAC_R_TO_MIXER_BIT, 0, NULL, 0), + + /* Left SPK Mixer */ + SND_SOC_DAPM_MIXER("SPKMIXL Mixer", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_SPKMIXER_L_BIT, 0, + &rt5631_spkmixl_mixer_controls[0], + ARRAY_SIZE(rt5631_spkmixl_mixer_controls)), + /* Left Out Mixer */ + SND_SOC_DAPM_MIXER("OUTMIXL Mixer", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_OUTMIXER_L_BIT, 0, + &rt5631_outmixl_mixer_controls[0], + ARRAY_SIZE(rt5631_outmixl_mixer_controls)), + /* Right Out Mixer */ + SND_SOC_DAPM_MIXER("OUTMIXR Mixer", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_OUTMIXER_R_BIT, 0, + &rt5631_outmixr_mixer_controls[0], + ARRAY_SIZE(rt5631_outmixr_mixer_controls)), + /* Right SPK Mixer */ + SND_SOC_DAPM_MIXER("SPKMIXR Mixer", RT5631_PWR_MANAG_ADD2, + RT5631_PWR_SPKMIXER_R_BIT, 0, + &rt5631_spkmixr_mixer_controls[0], + ARRAY_SIZE(rt5631_spkmixr_mixer_controls)), + + /* Volume Mux */ + SND_SOC_DAPM_MUX("Left SPKVOL Mux", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_SPK_L_VOL_BIT, 0, + &rt5631_spkvoll_mux_control), + SND_SOC_DAPM_MUX("Left HPVOL Mux", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_HP_L_OUT_VOL_BIT, 0, + &rt5631_hpvoll_mux_control), + SND_SOC_DAPM_MUX("Left OUTVOL Mux", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_LOUT_VOL_BIT, 0, + &rt5631_outvoll_mux_control), + SND_SOC_DAPM_MUX("Right OUTVOL Mux", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_ROUT_VOL_BIT, 0, + &rt5631_outvolr_mux_control), + SND_SOC_DAPM_MUX("Right HPVOL Mux", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_HP_R_OUT_VOL_BIT, 0, + &rt5631_hpvolr_mux_control), + SND_SOC_DAPM_MUX("Right SPKVOL Mux", RT5631_PWR_MANAG_ADD4, + RT5631_PWR_SPK_R_VOL_BIT, 0, + &rt5631_spkvolr_mux_control), + + /* DAC To HP */ + SND_SOC_DAPM_PGA_S("Left DAC_HP", 0, SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("Right DAC_HP", 0, SND_SOC_NOPM, 0, 0, NULL, 0), + + /* HP Depop */ + SND_SOC_DAPM_PGA_S("HP Depop", 1, SND_SOC_NOPM, 0, 0, + hp_event, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + + /* AXO1 Mixer */ + SND_SOC_DAPM_MIXER("AXO1MIX Mixer", RT5631_PWR_MANAG_ADD3, + RT5631_PWR_AXO1MIXER_BIT, 0, + &rt5631_AXO1MIX_mixer_controls[0], + ARRAY_SIZE(rt5631_AXO1MIX_mixer_controls)), + /* SPOL Mixer */ + SND_SOC_DAPM_MIXER("SPOLMIX Mixer", SND_SOC_NOPM, 0, 0, + &rt5631_spolmix_mixer_controls[0], + ARRAY_SIZE(rt5631_spolmix_mixer_controls)), + /* MONO Mixer */ + SND_SOC_DAPM_MIXER("MONOMIX Mixer", RT5631_PWR_MANAG_ADD3, + RT5631_PWR_MONOMIXER_BIT, 0, + &rt5631_monomix_mixer_controls[0], + ARRAY_SIZE(rt5631_monomix_mixer_controls)), + /* SPOR Mixer */ + SND_SOC_DAPM_MIXER("SPORMIX Mixer", SND_SOC_NOPM, 0, 0, + &rt5631_spormix_mixer_controls[0], + ARRAY_SIZE(rt5631_spormix_mixer_controls)), + /* AXO2 Mixer */ + SND_SOC_DAPM_MIXER("AXO2MIX Mixer", RT5631_PWR_MANAG_ADD3, + RT5631_PWR_AXO2MIXER_BIT, 0, + &rt5631_AXO2MIX_mixer_controls[0], + ARRAY_SIZE(rt5631_AXO2MIX_mixer_controls)), + + /* Mux */ + SND_SOC_DAPM_MUX("SPOL Mux", SND_SOC_NOPM, 0, 0, + &rt5631_spol_mux_control), + SND_SOC_DAPM_MUX("SPOR Mux", SND_SOC_NOPM, 0, 0, + &rt5631_spor_mux_control), + SND_SOC_DAPM_MUX("MONO Mux", SND_SOC_NOPM, 0, 0, + &rt5631_mono_mux_control), + SND_SOC_DAPM_MUX("HPL Mux", SND_SOC_NOPM, 0, 0, + &rt5631_hpl_mux_control), + SND_SOC_DAPM_MUX("HPR Mux", SND_SOC_NOPM, 0, 0, + &rt5631_hpr_mux_control), + + /* AMP supply */ + SND_SOC_DAPM_SUPPLY("MONO Depop", RT5631_PWR_MANAG_ADD3, + RT5631_PWR_MONO_DEPOP_DIS_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Class D", RT5631_PWR_MANAG_ADD1, + RT5631_PWR_CLASS_D_BIT, 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("AUXO1"), + SND_SOC_DAPM_OUTPUT("AUXO2"), + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("MONO"), +}; + +static const struct snd_soc_dapm_route rt5631_dapm_routes[] = { + {"MIC1 Boost", NULL, "MIC1"}, + {"MIC2 Boost", NULL, "MIC2"}, + {"MONOIN_RXP Boost", NULL, "MONOIN_RXP"}, + {"MONOIN_RXN Boost", NULL, "MONOIN_RXN"}, + {"AXIL Boost", NULL, "AXIL"}, + {"AXIR Boost", NULL, "AXIR"}, + + {"MONO_IN", NULL, "MONOIN_RXP Boost"}, + {"MONO_IN", NULL, "MONOIN_RXN Boost"}, + + {"RECMIXL Mixer", "OUTMIXL Capture Switch", "OUTMIXL Mixer"}, + {"RECMIXL Mixer", "MIC1_BST1 Capture Switch", "MIC1 Boost"}, + {"RECMIXL Mixer", "AXILVOL Capture Switch", "AXIL Boost"}, + {"RECMIXL Mixer", "MONOIN_RX Capture Switch", "MONO_IN"}, + + {"RECMIXR Mixer", "OUTMIXR Capture Switch", "OUTMIXR Mixer"}, + {"RECMIXR Mixer", "MIC2_BST2 Capture Switch", "MIC2 Boost"}, + {"RECMIXR Mixer", "AXIRVOL Capture Switch", "AXIR Boost"}, + {"RECMIXR Mixer", "MONOIN_RX Capture Switch", "MONO_IN"}, + + {"ADC Mixer", NULL, "RECMIXL Mixer"}, + {"ADC Mixer", NULL, "RECMIXR Mixer"}, + + {"Left ADC", NULL, "ADC Mixer"}, + {"Left ADC", NULL, "Left ADC Select", check_adcl_select}, + {"Left ADC", NULL, "PLL1", check_sysclk1_source}, + {"Left ADC", NULL, "I2S"}, + {"Left ADC", NULL, "DAC REF"}, + + {"Right ADC", NULL, "ADC Mixer"}, + {"Right ADC", NULL, "Right ADC Select", check_adcr_select}, + {"Right ADC", NULL, "PLL1", check_sysclk1_source}, + {"Right ADC", NULL, "I2S"}, + {"Right ADC", NULL, "DAC REF"}, + + {"DMIC", NULL, "DMIC Supply", check_dmic_used}, + {"Left ADC", NULL, "DMIC"}, + {"Right ADC", NULL, "DMIC"}, + + {"Left DAC", NULL, "PLL1", check_sysclk1_source}, + {"Left DAC", NULL, "I2S"}, + {"Left DAC", NULL, "DAC REF"}, + {"Right DAC", NULL, "PLL1", check_sysclk1_source}, + {"Right DAC", NULL, "I2S"}, + {"Right DAC", NULL, "DAC REF"}, + + {"Voice DAC Boost", NULL, "Voice DAC"}, + + {"SPKMIXL Mixer", NULL, "Left DAC To Mixer", check_dacl_to_spkmixl}, + {"SPKMIXL Mixer", "RECMIXL Playback Switch", "RECMIXL Mixer"}, + {"SPKMIXL Mixer", "MIC1_P Playback Switch", "MIC1"}, + {"SPKMIXL Mixer", "DACL Playback Switch", "Left DAC"}, + {"SPKMIXL Mixer", "OUTMIXL Playback Switch", "OUTMIXL Mixer"}, + + {"SPKMIXR Mixer", NULL, "Right DAC To Mixer", check_dacr_to_spkmixr}, + {"SPKMIXR Mixer", "OUTMIXR Playback Switch", "OUTMIXR Mixer"}, + {"SPKMIXR Mixer", "DACR Playback Switch", "Right DAC"}, + {"SPKMIXR Mixer", "MIC2_P Playback Switch", "MIC2"}, + {"SPKMIXR Mixer", "RECMIXR Playback Switch", "RECMIXR Mixer"}, + + {"OUTMIXL Mixer", NULL, "Left DAC To Mixer", check_dacl_to_outmixl}, + {"OUTMIXL Mixer", "RECMIXL Playback Switch", "RECMIXL Mixer"}, + {"OUTMIXL Mixer", "RECMIXR Playback Switch", "RECMIXR Mixer"}, + {"OUTMIXL Mixer", "DACL Playback Switch", "Left DAC"}, + {"OUTMIXL Mixer", "MIC1_BST1 Playback Switch", "MIC1 Boost"}, + {"OUTMIXL Mixer", "MIC2_BST2 Playback Switch", "MIC2 Boost"}, + {"OUTMIXL Mixer", "MONOIN_RXP Playback Switch", "MONOIN_RXP Boost"}, + {"OUTMIXL Mixer", "AXILVOL Playback Switch", "AXIL Boost"}, + {"OUTMIXL Mixer", "AXIRVOL Playback Switch", "AXIR Boost"}, + {"OUTMIXL Mixer", "VDAC Playback Switch", "Voice DAC Boost"}, + + {"OUTMIXR Mixer", NULL, "Right DAC To Mixer", check_dacr_to_outmixr}, + {"OUTMIXR Mixer", "RECMIXL Playback Switch", "RECMIXL Mixer"}, + {"OUTMIXR Mixer", "RECMIXR Playback Switch", "RECMIXR Mixer"}, + {"OUTMIXR Mixer", "DACR Playback Switch", "Right DAC"}, + {"OUTMIXR Mixer", "MIC1_BST1 Playback Switch", "MIC1 Boost"}, + {"OUTMIXR Mixer", "MIC2_BST2 Playback Switch", "MIC2 Boost"}, + {"OUTMIXR Mixer", "MONOIN_RXN Playback Switch", "MONOIN_RXN Boost"}, + {"OUTMIXR Mixer", "AXILVOL Playback Switch", "AXIL Boost"}, + {"OUTMIXR Mixer", "AXIRVOL Playback Switch", "AXIR Boost"}, + {"OUTMIXR Mixer", "VDAC Playback Switch", "Voice DAC Boost"}, + + {"Left SPKVOL Mux", "SPKMIXL", "SPKMIXL Mixer"}, + {"Left SPKVOL Mux", "Vmid", "Vmid"}, + {"Left HPVOL Mux", "OUTMIXL", "OUTMIXL Mixer"}, + {"Left HPVOL Mux", "Vmid", "Vmid"}, + {"Left OUTVOL Mux", "OUTMIXL", "OUTMIXL Mixer"}, + {"Left OUTVOL Mux", "Vmid", "Vmid"}, + {"Right OUTVOL Mux", "OUTMIXR", "OUTMIXR Mixer"}, + {"Right OUTVOL Mux", "Vmid", "Vmid"}, + {"Right HPVOL Mux", "OUTMIXR", "OUTMIXR Mixer"}, + {"Right HPVOL Mux", "Vmid", "Vmid"}, + {"Right SPKVOL Mux", "SPKMIXR", "SPKMIXR Mixer"}, + {"Right SPKVOL Mux", "Vmid", "Vmid"}, + + {"AXO1MIX Mixer", "MIC1_BST1 Playback Switch", "MIC1 Boost"}, + {"AXO1MIX Mixer", "OUTVOLL Playback Switch", "Left OUTVOL Mux"}, + {"AXO1MIX Mixer", "OUTVOLR Playback Switch", "Right OUTVOL Mux"}, + {"AXO1MIX Mixer", "MIC2_BST2 Playback Switch", "MIC2 Boost"}, + + {"AXO2MIX Mixer", "MIC1_BST1 Playback Switch", "MIC1 Boost"}, + {"AXO2MIX Mixer", "OUTVOLL Playback Switch", "Left OUTVOL Mux"}, + {"AXO2MIX Mixer", "OUTVOLR Playback Switch", "Right OUTVOL Mux"}, + {"AXO2MIX Mixer", "MIC2_BST2 Playback Switch", "MIC2 Boost"}, + + {"SPOLMIX Mixer", "SPKVOLL Playback Switch", "Left SPKVOL Mux"}, + {"SPOLMIX Mixer", "SPKVOLR Playback Switch", "Right SPKVOL Mux"}, + + {"SPORMIX Mixer", "SPKVOLL Playback Switch", "Left SPKVOL Mux"}, + {"SPORMIX Mixer", "SPKVOLR Playback Switch", "Right SPKVOL Mux"}, + + {"MONOMIX Mixer", "OUTVOLL Playback Switch", "Left OUTVOL Mux"}, + {"MONOMIX Mixer", "OUTVOLR Playback Switch", "Right OUTVOL Mux"}, + + {"SPOL Mux", "SPOLMIX", "SPOLMIX Mixer"}, + {"SPOL Mux", "MONOIN_RX", "MONO_IN"}, + {"SPOL Mux", "VDAC", "Voice DAC Boost"}, + {"SPOL Mux", "DACL", "Left DAC"}, + + {"SPOR Mux", "SPORMIX", "SPORMIX Mixer"}, + {"SPOR Mux", "MONOIN_RX", "MONO_IN"}, + {"SPOR Mux", "VDAC", "Voice DAC Boost"}, + {"SPOR Mux", "DACR", "Right DAC"}, + + {"MONO Mux", "MONOMIX", "MONOMIX Mixer"}, + {"MONO Mux", "MONOIN_RX", "MONO_IN"}, + {"MONO Mux", "VDAC", "Voice DAC Boost"}, + + {"Right DAC_HP", NULL, "Right DAC"}, + {"Left DAC_HP", NULL, "Left DAC"}, + + {"HPL Mux", "Left HPVOL", "Left HPVOL Mux"}, + {"HPL Mux", "Left DAC", "Left DAC_HP"}, + {"HPR Mux", "Right HPVOL", "Right HPVOL Mux"}, + {"HPR Mux", "Right DAC", "Right DAC_HP"}, + + {"HP Depop", NULL, "HPL Mux"}, + {"HP Depop", NULL, "HPR Mux"}, + + {"AUXO1", NULL, "AXO1MIX Mixer"}, + {"AUXO2", NULL, "AXO2MIX Mixer"}, + + {"SPOL", NULL, "Class D"}, + {"SPOL", NULL, "SPOL Mux"}, + {"SPOR", NULL, "Class D"}, + {"SPOR", NULL, "SPOR Mux"}, + + {"HPOL", NULL, "HP Depop"}, + {"HPOR", NULL, "HP Depop"}, + + {"MONO", NULL, "MONO Depop"}, + {"MONO", NULL, "MONO Mux"}, +}; + +struct coeff_clk_div { + u32 mclk; + u32 bclk; + u32 rate; + u16 reg_val; +}; + +/* PLL divisors */ +struct pll_div { + u32 pll_in; + u32 pll_out; + u16 reg_val; +}; + +static const struct pll_div codec_master_pll_div[] = { + {2048000, 8192000, 0x0ea0}, + {3686400, 8192000, 0x4e27}, + {12000000, 8192000, 0x456b}, + {13000000, 8192000, 0x495f}, + {13100000, 8192000, 0x0320}, + {2048000, 11289600, 0xf637}, + {3686400, 11289600, 0x2f22}, + {12000000, 11289600, 0x3e2f}, + {13000000, 11289600, 0x4d5b}, + {13100000, 11289600, 0x363b}, + {2048000, 16384000, 0x1ea0}, + {3686400, 16384000, 0x9e27}, + {12000000, 16384000, 0x452b}, + {13000000, 16384000, 0x542f}, + {13100000, 16384000, 0x03a0}, + {2048000, 16934400, 0xe625}, + {3686400, 16934400, 0x9126}, + {12000000, 16934400, 0x4d2c}, + {13000000, 16934400, 0x742f}, + {13100000, 16934400, 0x3c27}, + {2048000, 22579200, 0x2aa0}, + {3686400, 22579200, 0x2f20}, + {12000000, 22579200, 0x7e2f}, + {13000000, 22579200, 0x742f}, + {13100000, 22579200, 0x3c27}, + {2048000, 24576000, 0x2ea0}, + {3686400, 24576000, 0xee27}, + {12000000, 24576000, 0x2915}, + {13000000, 24576000, 0x772e}, + {13100000, 24576000, 0x0d20}, + {26000000, 24576000, 0x2027}, + {26000000, 22579200, 0x392f}, + {24576000, 22579200, 0x0921}, + {24576000, 24576000, 0x02a0}, +}; + +static const struct pll_div codec_slave_pll_div[] = { + {256000, 2048000, 0x46f0}, + {256000, 4096000, 0x3ea0}, + {352800, 5644800, 0x3ea0}, + {512000, 8192000, 0x3ea0}, + {1024000, 8192000, 0x46f0}, + {705600, 11289600, 0x3ea0}, + {1024000, 16384000, 0x3ea0}, + {1411200, 22579200, 0x3ea0}, + {1536000, 24576000, 0x3ea0}, + {2048000, 16384000, 0x1ea0}, + {2822400, 22579200, 0x1ea0}, + {2822400, 45158400, 0x5ec0}, + {5644800, 45158400, 0x46f0}, + {3072000, 24576000, 0x1ea0}, + {3072000, 49152000, 0x5ec0}, + {6144000, 49152000, 0x46f0}, + {705600, 11289600, 0x3ea0}, + {705600, 8467200, 0x3ab0}, + {24576000, 24576000, 0x02a0}, + {1411200, 11289600, 0x1690}, + {2822400, 11289600, 0x0a90}, + {1536000, 12288000, 0x1690}, + {3072000, 12288000, 0x0a90}, +}; + +static struct coeff_clk_div coeff_div[] = { + /* sysclk is 256fs */ + {2048000, 8000 * 32, 8000, 0x1000}, + {2048000, 8000 * 64, 8000, 0x0000}, + {2822400, 11025 * 32, 11025, 0x1000}, + {2822400, 11025 * 64, 11025, 0x0000}, + {4096000, 16000 * 32, 16000, 0x1000}, + {4096000, 16000 * 64, 16000, 0x0000}, + {5644800, 22050 * 32, 22050, 0x1000}, + {5644800, 22050 * 64, 22050, 0x0000}, + {8192000, 32000 * 32, 32000, 0x1000}, + {8192000, 32000 * 64, 32000, 0x0000}, + {11289600, 44100 * 32, 44100, 0x1000}, + {11289600, 44100 * 64, 44100, 0x0000}, + {12288000, 48000 * 32, 48000, 0x1000}, + {12288000, 48000 * 64, 48000, 0x0000}, + {22579200, 88200 * 32, 88200, 0x1000}, + {22579200, 88200 * 64, 88200, 0x0000}, + {24576000, 96000 * 32, 96000, 0x1000}, + {24576000, 96000 * 64, 96000, 0x0000}, + /* sysclk is 512fs */ + {4096000, 8000 * 32, 8000, 0x3000}, + {4096000, 8000 * 64, 8000, 0x2000}, + {5644800, 11025 * 32, 11025, 0x3000}, + {5644800, 11025 * 64, 11025, 0x2000}, + {8192000, 16000 * 32, 16000, 0x3000}, + {8192000, 16000 * 64, 16000, 0x2000}, + {11289600, 22050 * 32, 22050, 0x3000}, + {11289600, 22050 * 64, 22050, 0x2000}, + {16384000, 32000 * 32, 32000, 0x3000}, + {16384000, 32000 * 64, 32000, 0x2000}, + {22579200, 44100 * 32, 44100, 0x3000}, + {22579200, 44100 * 64, 44100, 0x2000}, + {24576000, 48000 * 32, 48000, 0x3000}, + {24576000, 48000 * 64, 48000, 0x2000}, + {45158400, 88200 * 32, 88200, 0x3000}, + {45158400, 88200 * 64, 88200, 0x2000}, + {49152000, 96000 * 32, 96000, 0x3000}, + {49152000, 96000 * 64, 96000, 0x2000}, + /* sysclk is 24.576Mhz or 22.5792Mhz */ + {24576000, 8000 * 32, 8000, 0x7080}, + {24576000, 8000 * 64, 8000, 0x6080}, + {24576000, 16000 * 32, 16000, 0x5080}, + {24576000, 16000 * 64, 16000, 0x4080}, + {24576000, 24000 * 32, 24000, 0x5000}, + {24576000, 24000 * 64, 24000, 0x4000}, + {24576000, 32000 * 32, 32000, 0x3080}, + {24576000, 32000 * 64, 32000, 0x2080}, + {22579200, 11025 * 32, 11025, 0x7000}, + {22579200, 11025 * 64, 11025, 0x6000}, + {22579200, 22050 * 32, 22050, 0x5000}, + {22579200, 22050 * 64, 22050, 0x4000}, +}; + +static int get_coeff(int mclk, int rate, int timesofbclk) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].mclk == mclk && coeff_div[i].rate == rate && + (coeff_div[i].bclk / coeff_div[i].rate) == timesofbclk) + return i; + } + return -EINVAL; +} + +static int rt5631_hifi_pcm_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + int timesofbclk = 32, coeff; + unsigned int iface = 0; + + dev_dbg(component->dev, "enter %s\n", __func__); + + rt5631->bclk_rate = snd_soc_params_to_bclk(params); + if (rt5631->bclk_rate < 0) { + dev_err(component->dev, "Fail to get BCLK rate\n"); + return rt5631->bclk_rate; + } + rt5631->rx_rate = params_rate(params); + + if (rt5631->master) + coeff = get_coeff(rt5631->sysclk, rt5631->rx_rate, + rt5631->bclk_rate / rt5631->rx_rate); + else + coeff = get_coeff(rt5631->sysclk, rt5631->rx_rate, + timesofbclk); + if (coeff < 0) { + dev_err(component->dev, "Fail to get coeff\n"); + return coeff; + } + + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= RT5631_SDP_I2S_DL_20; + break; + case 24: + iface |= RT5631_SDP_I2S_DL_24; + break; + case 8: + iface |= RT5631_SDP_I2S_DL_8; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5631_SDP_CTRL, + RT5631_SDP_I2S_DL_MASK, iface); + snd_soc_component_write(component, RT5631_STEREO_AD_DA_CLK_CTRL, + coeff_div[coeff].reg_val); + + return 0; +} + +static int rt5631_hifi_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + unsigned int iface = 0; + + dev_dbg(component->dev, "enter %s\n", __func__); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5631->master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface |= RT5631_SDP_MODE_SEL_SLAVE; + rt5631->master = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= RT5631_SDP_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= RT5631_SDP_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= RT5631_SDP_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= RT5631_SDP_I2S_BCLK_POL_CTRL; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, RT5631_SDP_CTRL, iface); + + return 0; +} + +static int rt5631_hifi_codec_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "enter %s, syclk=%d\n", __func__, freq); + + if ((freq >= (256 * 8000)) && (freq <= (512 * 96000))) { + rt5631->sysclk = freq; + return 0; + } + + return -EINVAL; +} + +static int rt5631_codec_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + int i, ret = -EINVAL; + + dev_dbg(component->dev, "enter %s\n", __func__); + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + snd_soc_component_update_bits(component, RT5631_GLOBAL_CLK_CTRL, + RT5631_SYSCLK_SOUR_SEL_MASK, + RT5631_SYSCLK_SOUR_SEL_MCLK); + + return 0; + } + + if (rt5631->master) { + for (i = 0; i < ARRAY_SIZE(codec_master_pll_div); i++) + if (freq_in == codec_master_pll_div[i].pll_in && + freq_out == codec_master_pll_div[i].pll_out) { + dev_info(component->dev, + "change PLL in master mode\n"); + snd_soc_component_write(component, RT5631_PLL_CTRL, + codec_master_pll_div[i].reg_val); + schedule_timeout_uninterruptible( + msecs_to_jiffies(20)); + snd_soc_component_update_bits(component, + RT5631_GLOBAL_CLK_CTRL, + RT5631_SYSCLK_SOUR_SEL_MASK | + RT5631_PLLCLK_SOUR_SEL_MASK, + RT5631_SYSCLK_SOUR_SEL_PLL | + RT5631_PLLCLK_SOUR_SEL_MCLK); + ret = 0; + break; + } + } else { + for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) + if (freq_in == codec_slave_pll_div[i].pll_in && + freq_out == codec_slave_pll_div[i].pll_out) { + dev_info(component->dev, + "change PLL in slave mode\n"); + snd_soc_component_write(component, RT5631_PLL_CTRL, + codec_slave_pll_div[i].reg_val); + schedule_timeout_uninterruptible( + msecs_to_jiffies(20)); + snd_soc_component_update_bits(component, + RT5631_GLOBAL_CLK_CTRL, + RT5631_SYSCLK_SOUR_SEL_MASK | + RT5631_PLLCLK_SOUR_SEL_MASK, + RT5631_SYSCLK_SOUR_SEL_PLL | + RT5631_PLLCLK_SOUR_SEL_BCLK); + ret = 0; + break; + } + } + + return ret; +} + +static int rt5631_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD2, + RT5631_PWR_MICBIAS1_VOL | RT5631_PWR_MICBIAS2_VOL, + RT5631_PWR_MICBIAS1_VOL | RT5631_PWR_MICBIAS2_VOL); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_VREF | RT5631_PWR_MAIN_BIAS, + RT5631_PWR_VREF | RT5631_PWR_MAIN_BIAS); + msleep(80); + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_FAST_VREF_CTRL, + RT5631_PWR_FAST_VREF_CTRL); + regcache_cache_only(rt5631->regmap, false); + regcache_sync(rt5631->regmap); + } + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, RT5631_PWR_MANAG_ADD1, 0x0000); + snd_soc_component_write(component, RT5631_PWR_MANAG_ADD2, 0x0000); + snd_soc_component_write(component, RT5631_PWR_MANAG_ADD3, 0x0000); + snd_soc_component_write(component, RT5631_PWR_MANAG_ADD4, 0x0000); + break; + + default: + break; + } + + return 0; +} + +static int rt5631_probe(struct snd_soc_component *component) +{ + struct rt5631_priv *rt5631 = snd_soc_component_get_drvdata(component); + unsigned int val; + + val = rt5631_read_index(component, RT5631_ADDA_MIXER_INTL_REG3); + if (val & 0x0002) + rt5631->codec_version = 1; + else + rt5631->codec_version = 0; + + rt5631_reset(component); + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_VREF | RT5631_PWR_MAIN_BIAS, + RT5631_PWR_VREF | RT5631_PWR_MAIN_BIAS); + msleep(80); + snd_soc_component_update_bits(component, RT5631_PWR_MANAG_ADD3, + RT5631_PWR_FAST_VREF_CTRL, RT5631_PWR_FAST_VREF_CTRL); + /* enable HP zero cross */ + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, 0x0f18); + /* power off ClassD auto Recovery */ + if (rt5631->codec_version) + snd_soc_component_update_bits(component, RT5631_INT_ST_IRQ_CTRL_2, + 0x2000, 0x2000); + else + snd_soc_component_update_bits(component, RT5631_INT_ST_IRQ_CTRL_2, + 0x2000, 0); + /* DMIC */ + if (rt5631->dmic_used_flag) { + snd_soc_component_update_bits(component, RT5631_GPIO_CTRL, + RT5631_GPIO_PIN_FUN_SEL_MASK | + RT5631_GPIO_DMIC_FUN_SEL_MASK, + RT5631_GPIO_PIN_FUN_SEL_GPIO_DIMC | + RT5631_GPIO_DMIC_FUN_SEL_DIMC); + snd_soc_component_update_bits(component, RT5631_DIG_MIC_CTRL, + RT5631_DMIC_L_CH_LATCH_MASK | + RT5631_DMIC_R_CH_LATCH_MASK, + RT5631_DMIC_L_CH_LATCH_FALLING | + RT5631_DMIC_R_CH_LATCH_RISING); + } + + snd_soc_component_init_bias_level(component, SND_SOC_BIAS_STANDBY); + + return 0; +} + +#define RT5631_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define RT5631_FORMAT (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5631_ops = { + .hw_params = rt5631_hifi_pcm_params, + .set_fmt = rt5631_hifi_codec_set_dai_fmt, + .set_sysclk = rt5631_hifi_codec_set_dai_sysclk, + .set_pll = rt5631_codec_set_dai_pll, +}; + +static struct snd_soc_dai_driver rt5631_dai[] = { + { + .name = "rt5631-hifi", + .id = 1, + .playback = { + .stream_name = "HIFI Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5631_STEREO_RATES, + .formats = RT5631_FORMAT, + }, + .capture = { + .stream_name = "HIFI Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5631_STEREO_RATES, + .formats = RT5631_FORMAT, + }, + .ops = &rt5631_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5631 = { + .probe = rt5631_probe, + .set_bias_level = rt5631_set_bias_level, + .controls = rt5631_snd_controls, + .num_controls = ARRAY_SIZE(rt5631_snd_controls), + .dapm_widgets = rt5631_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5631_dapm_widgets), + .dapm_routes = rt5631_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5631_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct i2c_device_id rt5631_i2c_id[] = { + { "rt5631", 0 }, + { "alc5631", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5631_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id rt5631_i2c_dt_ids[] = { + { .compatible = "realtek,rt5631"}, + { .compatible = "realtek,alc5631"}, + { } +}; +MODULE_DEVICE_TABLE(of, rt5631_i2c_dt_ids); +#endif + +static const struct regmap_config rt5631_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + + .readable_reg = rt5631_readable_register, + .volatile_reg = rt5631_volatile_register, + .max_register = RT5631_VENDOR_ID2, + .reg_defaults = rt5631_reg, + .num_reg_defaults = ARRAY_SIZE(rt5631_reg), + .cache_type = REGCACHE_RBTREE, +}; + +static int rt5631_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5631_priv *rt5631; + int ret; + + rt5631 = devm_kzalloc(&i2c->dev, sizeof(struct rt5631_priv), + GFP_KERNEL); + if (NULL == rt5631) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5631); + + rt5631->regmap = devm_regmap_init_i2c(i2c, &rt5631_regmap_config); + if (IS_ERR(rt5631->regmap)) + return PTR_ERR(rt5631->regmap); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5631, + rt5631_dai, ARRAY_SIZE(rt5631_dai)); + return ret; +} + +static int rt5631_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static struct i2c_driver rt5631_i2c_driver = { + .driver = { + .name = "rt5631", + .of_match_table = of_match_ptr(rt5631_i2c_dt_ids), + }, + .probe = rt5631_i2c_probe, + .remove = rt5631_i2c_remove, + .id_table = rt5631_i2c_id, +}; + +module_i2c_driver(rt5631_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5631 driver"); +MODULE_AUTHOR("flove "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt5631.h b/sound/soc/codecs/rt5631.h new file mode 100644 index 000000000..8a6b99a48 --- /dev/null +++ b/sound/soc/codecs/rt5631.h @@ -0,0 +1,702 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __RTCODEC5631_H__ +#define __RTCODEC5631_H__ + + +#define RT5631_RESET 0x00 +#define RT5631_SPK_OUT_VOL 0x02 +#define RT5631_HP_OUT_VOL 0x04 +#define RT5631_MONO_AXO_1_2_VOL 0x06 +#define RT5631_AUX_IN_VOL 0x0A +#define RT5631_STEREO_DAC_VOL_1 0x0C +#define RT5631_MIC_CTRL_1 0x0E +#define RT5631_STEREO_DAC_VOL_2 0x10 +#define RT5631_ADC_CTRL_1 0x12 +#define RT5631_ADC_REC_MIXER 0x14 +#define RT5631_ADC_CTRL_2 0x16 +#define RT5631_VDAC_DIG_VOL 0x18 +#define RT5631_OUTMIXER_L_CTRL 0x1A +#define RT5631_OUTMIXER_R_CTRL 0x1C +#define RT5631_AXO1MIXER_CTRL 0x1E +#define RT5631_AXO2MIXER_CTRL 0x20 +#define RT5631_MIC_CTRL_2 0x22 +#define RT5631_DIG_MIC_CTRL 0x24 +#define RT5631_MONO_INPUT_VOL 0x26 +#define RT5631_SPK_MIXER_CTRL 0x28 +#define RT5631_SPK_MONO_OUT_CTRL 0x2A +#define RT5631_SPK_MONO_HP_OUT_CTRL 0x2C +#define RT5631_SDP_CTRL 0x34 +#define RT5631_MONO_SDP_CTRL 0x36 +#define RT5631_STEREO_AD_DA_CLK_CTRL 0x38 +#define RT5631_PWR_MANAG_ADD1 0x3A +#define RT5631_PWR_MANAG_ADD2 0x3B +#define RT5631_PWR_MANAG_ADD3 0x3C +#define RT5631_PWR_MANAG_ADD4 0x3E +#define RT5631_GEN_PUR_CTRL_REG 0x40 +#define RT5631_GLOBAL_CLK_CTRL 0x42 +#define RT5631_PLL_CTRL 0x44 +#define RT5631_INT_ST_IRQ_CTRL_1 0x48 +#define RT5631_INT_ST_IRQ_CTRL_2 0x4A +#define RT5631_GPIO_CTRL 0x4C +#define RT5631_MISC_CTRL 0x52 +#define RT5631_DEPOP_FUN_CTRL_1 0x54 +#define RT5631_DEPOP_FUN_CTRL_2 0x56 +#define RT5631_JACK_DET_CTRL 0x5A +#define RT5631_SOFT_VOL_CTRL 0x5C +#define RT5631_ALC_CTRL_1 0x64 +#define RT5631_ALC_CTRL_2 0x65 +#define RT5631_ALC_CTRL_3 0x66 +#define RT5631_PSEUDO_SPATL_CTRL 0x68 +#define RT5631_INDEX_ADD 0x6A +#define RT5631_INDEX_DATA 0x6C +#define RT5631_EQ_CTRL 0x6E +#define RT5631_VENDOR_ID 0x7A +#define RT5631_VENDOR_ID1 0x7C +#define RT5631_VENDOR_ID2 0x7E + +/* Index of Codec Private Register definition */ +#define RT5631_EQ_BW_LOP 0x00 +#define RT5631_EQ_GAIN_LOP 0x01 +#define RT5631_EQ_FC_BP1 0x02 +#define RT5631_EQ_BW_BP1 0x03 +#define RT5631_EQ_GAIN_BP1 0x04 +#define RT5631_EQ_FC_BP2 0x05 +#define RT5631_EQ_BW_BP2 0x06 +#define RT5631_EQ_GAIN_BP2 0x07 +#define RT5631_EQ_FC_BP3 0x08 +#define RT5631_EQ_BW_BP3 0x09 +#define RT5631_EQ_GAIN_BP3 0x0a +#define RT5631_EQ_BW_HIP 0x0b +#define RT5631_EQ_GAIN_HIP 0x0c +#define RT5631_EQ_HPF_A1 0x0d +#define RT5631_EQ_HPF_A2 0x0e +#define RT5631_EQ_HPF_GAIN 0x0f +#define RT5631_EQ_PRE_VOL_CTRL 0x11 +#define RT5631_EQ_POST_VOL_CTRL 0x12 +#define RT5631_TEST_MODE_CTRL 0x39 +#define RT5631_CP_INTL_REG2 0x45 +#define RT5631_ADDA_MIXER_INTL_REG3 0x52 +#define RT5631_SPK_INTL_CTRL 0x56 + + +/* global definition */ +#define RT5631_L_MUTE (0x1 << 15) +#define RT5631_L_MUTE_SHIFT 15 +#define RT5631_L_EN (0x1 << 14) +#define RT5631_L_EN_SHIFT 14 +#define RT5631_R_MUTE (0x1 << 7) +#define RT5631_R_MUTE_SHIFT 7 +#define RT5631_R_EN (0x1 << 6) +#define RT5631_R_EN_SHIFT 6 +#define RT5631_VOL_MASK 0x1f +#define RT5631_L_VOL_SHIFT 8 +#define RT5631_R_VOL_SHIFT 0 + +/* Speaker Output Control(0x02) */ +#define RT5631_SPK_L_VOL_SEL_MASK (0x1 << 14) +#define RT5631_SPK_L_VOL_SEL_VMID (0x0 << 14) +#define RT5631_SPK_L_VOL_SEL_SPKMIX_L (0x1 << 14) +#define RT5631_SPK_R_VOL_SEL_MASK (0x1 << 6) +#define RT5631_SPK_R_VOL_SEL_VMID (0x0 << 6) +#define RT5631_SPK_R_VOL_SEL_SPKMIX_R (0x1 << 6) + +/* Headphone Output Control(0x04) */ +#define RT5631_HP_L_VOL_SEL_MASK (0x1 << 14) +#define RT5631_HP_L_VOL_SEL_VMID (0x0 << 14) +#define RT5631_HP_L_VOL_SEL_OUTMIX_L (0x1 << 14) +#define RT5631_HP_R_VOL_SEL_MASK (0x1 << 6) +#define RT5631_HP_R_VOL_SEL_VMID (0x0 << 6) +#define RT5631_HP_R_VOL_SEL_OUTMIX_R (0x1 << 6) + +/* Output Control for AUXOUT/MONO(0x06) */ +#define RT5631_AUXOUT_1_VOL_SEL_MASK (0x1 << 14) +#define RT5631_AUXOUT_1_VOL_SEL_VMID (0x0 << 14) +#define RT5631_AUXOUT_1_VOL_SEL_OUTMIX_L (0x1 << 14) +#define RT5631_MUTE_MONO (0x1 << 13) +#define RT5631_MUTE_MONO_SHIFT 13 +#define RT5631_AUXOUT_2_VOL_SEL_MASK (0x1 << 6) +#define RT5631_AUXOUT_2_VOL_SEL_VMID (0x0 << 6) +#define RT5631_AUXOUT_2_VOL_SEL_OUTMIX_R (0x1 << 6) + +/* Microphone Input Control 1(0x0E) */ +#define RT5631_MIC1_DIFF_INPUT_CTRL (0x1 << 15) +#define RT5631_MIC1_DIFF_INPUT_SHIFT 15 +#define RT5631_MIC2_DIFF_INPUT_CTRL (0x1 << 7) +#define RT5631_MIC2_DIFF_INPUT_SHIFT 7 + +/* Stereo DAC Digital Volume2(0x10) */ +#define RT5631_DAC_VOL_MASK 0xff + +/* ADC Recording Mixer Control(0x14) */ +#define RT5631_M_OUTMIXER_L_TO_RECMIXER_L (0x1 << 15) +#define RT5631_M_OUTMIXL_RECMIXL_BIT 15 +#define RT5631_M_MIC1_TO_RECMIXER_L (0x1 << 14) +#define RT5631_M_MIC1_RECMIXL_BIT 14 +#define RT5631_M_AXIL_TO_RECMIXER_L (0x1 << 13) +#define RT5631_M_AXIL_RECMIXL_BIT 13 +#define RT5631_M_MONO_IN_TO_RECMIXER_L (0x1 << 12) +#define RT5631_M_MONO_IN_RECMIXL_BIT 12 +#define RT5631_M_OUTMIXER_R_TO_RECMIXER_R (0x1 << 7) +#define RT5631_M_OUTMIXR_RECMIXR_BIT 7 +#define RT5631_M_MIC2_TO_RECMIXER_R (0x1 << 6) +#define RT5631_M_MIC2_RECMIXR_BIT 6 +#define RT5631_M_AXIR_TO_RECMIXER_R (0x1 << 5) +#define RT5631_M_AXIR_RECMIXR_BIT 5 +#define RT5631_M_MONO_IN_TO_RECMIXER_R (0x1 << 4) +#define RT5631_M_MONO_IN_RECMIXR_BIT 4 + +/* Left Output Mixer Control(0x1A) */ +#define RT5631_M_RECMIXER_L_TO_OUTMIXER_L (0x1 << 15) +#define RT5631_M_RECMIXL_OUTMIXL_BIT 15 +#define RT5631_M_RECMIXER_R_TO_OUTMIXER_L (0x1 << 14) +#define RT5631_M_RECMIXR_OUTMIXL_BIT 14 +#define RT5631_M_DAC_L_TO_OUTMIXER_L (0x1 << 13) +#define RT5631_M_DACL_OUTMIXL_BIT 13 +#define RT5631_M_MIC1_TO_OUTMIXER_L (0x1 << 12) +#define RT5631_M_MIC1_OUTMIXL_BIT 12 +#define RT5631_M_MIC2_TO_OUTMIXER_L (0x1 << 11) +#define RT5631_M_MIC2_OUTMIXL_BIT 11 +#define RT5631_M_MONO_IN_P_TO_OUTMIXER_L (0x1 << 10) +#define RT5631_M_MONO_INP_OUTMIXL_BIT 10 +#define RT5631_M_AXIL_TO_OUTMIXER_L (0x1 << 9) +#define RT5631_M_AXIL_OUTMIXL_BIT 9 +#define RT5631_M_AXIR_TO_OUTMIXER_L (0x1 << 8) +#define RT5631_M_AXIR_OUTMIXL_BIT 8 +#define RT5631_M_VDAC_TO_OUTMIXER_L (0x1 << 7) +#define RT5631_M_VDAC_OUTMIXL_BIT 7 + +/* Right Output Mixer Control(0x1C) */ +#define RT5631_M_RECMIXER_L_TO_OUTMIXER_R (0x1 << 15) +#define RT5631_M_RECMIXL_OUTMIXR_BIT 15 +#define RT5631_M_RECMIXER_R_TO_OUTMIXER_R (0x1 << 14) +#define RT5631_M_RECMIXR_OUTMIXR_BIT 14 +#define RT5631_M_DAC_R_TO_OUTMIXER_R (0x1 << 13) +#define RT5631_M_DACR_OUTMIXR_BIT 13 +#define RT5631_M_MIC1_TO_OUTMIXER_R (0x1 << 12) +#define RT5631_M_MIC1_OUTMIXR_BIT 12 +#define RT5631_M_MIC2_TO_OUTMIXER_R (0x1 << 11) +#define RT5631_M_MIC2_OUTMIXR_BIT 11 +#define RT5631_M_MONO_IN_N_TO_OUTMIXER_R (0x1 << 10) +#define RT5631_M_MONO_INN_OUTMIXR_BIT 10 +#define RT5631_M_AXIL_TO_OUTMIXER_R (0x1 << 9) +#define RT5631_M_AXIL_OUTMIXR_BIT 9 +#define RT5631_M_AXIR_TO_OUTMIXER_R (0x1 << 8) +#define RT5631_M_AXIR_OUTMIXR_BIT 8 +#define RT5631_M_VDAC_TO_OUTMIXER_R (0x1 << 7) +#define RT5631_M_VDAC_OUTMIXR_BIT 7 + +/* Lout Mixer Control(0x1E) */ +#define RT5631_M_MIC1_TO_AXO1MIXER (0x1 << 15) +#define RT5631_M_MIC1_AXO1MIX_BIT 15 +#define RT5631_M_MIC2_TO_AXO1MIXER (0x1 << 11) +#define RT5631_M_MIC2_AXO1MIX_BIT 11 +#define RT5631_M_OUTMIXER_L_TO_AXO1MIXER (0x1 << 7) +#define RT5631_M_OUTMIXL_AXO1MIX_BIT 7 +#define RT5631_M_OUTMIXER_R_TO_AXO1MIXER (0x1 << 6) +#define RT5631_M_OUTMIXR_AXO1MIX_BIT 6 + +/* Rout Mixer Control(0x20) */ +#define RT5631_M_MIC1_TO_AXO2MIXER (0x1 << 15) +#define RT5631_M_MIC1_AXO2MIX_BIT 15 +#define RT5631_M_MIC2_TO_AXO2MIXER (0x1 << 11) +#define RT5631_M_MIC2_AXO2MIX_BIT 11 +#define RT5631_M_OUTMIXER_L_TO_AXO2MIXER (0x1 << 7) +#define RT5631_M_OUTMIXL_AXO2MIX_BIT 7 +#define RT5631_M_OUTMIXER_R_TO_AXO2MIXER (0x1 << 6) +#define RT5631_M_OUTMIXR_AXO2MIX_BIT 6 + +/* Micphone Input Control 2(0x22) */ +#define RT5631_MIC_BIAS_90_PRECNET_AVDD 1 +#define RT5631_MIC_BIAS_75_PRECNET_AVDD 2 + +#define RT5631_MIC1_BOOST_CTRL_MASK (0xf << 12) +#define RT5631_MIC1_BOOST_CTRL_BYPASS (0x0 << 12) +#define RT5631_MIC1_BOOST_CTRL_20DB (0x1 << 12) +#define RT5631_MIC1_BOOST_CTRL_24DB (0x2 << 12) +#define RT5631_MIC1_BOOST_CTRL_30DB (0x3 << 12) +#define RT5631_MIC1_BOOST_CTRL_35DB (0x4 << 12) +#define RT5631_MIC1_BOOST_CTRL_40DB (0x5 << 12) +#define RT5631_MIC1_BOOST_CTRL_34DB (0x6 << 12) +#define RT5631_MIC1_BOOST_CTRL_50DB (0x7 << 12) +#define RT5631_MIC1_BOOST_CTRL_52DB (0x8 << 12) +#define RT5631_MIC1_BOOST_SHIFT 12 + +#define RT5631_MIC2_BOOST_CTRL_MASK (0xf << 8) +#define RT5631_MIC2_BOOST_CTRL_BYPASS (0x0 << 8) +#define RT5631_MIC2_BOOST_CTRL_20DB (0x1 << 8) +#define RT5631_MIC2_BOOST_CTRL_24DB (0x2 << 8) +#define RT5631_MIC2_BOOST_CTRL_30DB (0x3 << 8) +#define RT5631_MIC2_BOOST_CTRL_35DB (0x4 << 8) +#define RT5631_MIC2_BOOST_CTRL_40DB (0x5 << 8) +#define RT5631_MIC2_BOOST_CTRL_34DB (0x6 << 8) +#define RT5631_MIC2_BOOST_CTRL_50DB (0x7 << 8) +#define RT5631_MIC2_BOOST_CTRL_52DB (0x8 << 8) +#define RT5631_MIC2_BOOST_SHIFT 8 + +#define RT5631_MICBIAS1_VOLT_CTRL_MASK (0x1 << 7) +#define RT5631_MICBIAS1_VOLT_CTRL_90P (0x0 << 7) +#define RT5631_MICBIAS1_VOLT_CTRL_75P (0x1 << 7) + +#define RT5631_MICBIAS1_S_C_DET_MASK (0x1 << 6) +#define RT5631_MICBIAS1_S_C_DET_DIS (0x0 << 6) +#define RT5631_MICBIAS1_S_C_DET_ENA (0x1 << 6) + +#define RT5631_MICBIAS1_SHORT_CURR_DET_MASK (0x3 << 4) +#define RT5631_MICBIAS1_SHORT_CURR_DET_600UA (0x0 << 4) +#define RT5631_MICBIAS1_SHORT_CURR_DET_1500UA (0x1 << 4) +#define RT5631_MICBIAS1_SHORT_CURR_DET_2000UA (0x2 << 4) + +#define RT5631_MICBIAS2_VOLT_CTRL_MASK (0x1 << 3) +#define RT5631_MICBIAS2_VOLT_CTRL_90P (0x0 << 3) +#define RT5631_MICBIAS2_VOLT_CTRL_75P (0x1 << 3) + +#define RT5631_MICBIAS2_S_C_DET_MASK (0x1 << 2) +#define RT5631_MICBIAS2_S_C_DET_DIS (0x0 << 2) +#define RT5631_MICBIAS2_S_C_DET_ENA (0x1 << 2) + +#define RT5631_MICBIAS2_SHORT_CURR_DET_MASK (0x3) +#define RT5631_MICBIAS2_SHORT_CURR_DET_600UA (0x0) +#define RT5631_MICBIAS2_SHORT_CURR_DET_1500UA (0x1) +#define RT5631_MICBIAS2_SHORT_CURR_DET_2000UA (0x2) + + +/* Digital Microphone Control(0x24) */ +#define RT5631_DMIC_ENA_MASK (0x1 << 15) +#define RT5631_DMIC_ENA_SHIFT 15 +/* DMIC_ENA: DMIC to ADC Digital filter */ +#define RT5631_DMIC_ENA (0x1 << 15) +/* DMIC_DIS: ADC mixer to ADC Digital filter */ +#define RT5631_DMIC_DIS (0x0 << 15) +#define RT5631_DMIC_L_CH_MUTE (0x1 << 13) +#define RT5631_DMIC_L_CH_MUTE_SHIFT 13 +#define RT5631_DMIC_R_CH_MUTE (0x1 << 12) +#define RT5631_DMIC_R_CH_MUTE_SHIFT 12 +#define RT5631_DMIC_L_CH_LATCH_MASK (0x1 << 9) +#define RT5631_DMIC_L_CH_LATCH_RISING (0x1 << 9) +#define RT5631_DMIC_L_CH_LATCH_FALLING (0x0 << 9) +#define RT5631_DMIC_R_CH_LATCH_MASK (0x1 << 8) +#define RT5631_DMIC_R_CH_LATCH_RISING (0x1 << 8) +#define RT5631_DMIC_R_CH_LATCH_FALLING (0x0 << 8) +#define RT5631_DMIC_CLK_CTRL_MASK (0x3 << 4) +#define RT5631_DMIC_CLK_CTRL_TO_128FS (0x0 << 4) +#define RT5631_DMIC_CLK_CTRL_TO_64FS (0x1 << 4) +#define RT5631_DMIC_CLK_CTRL_TO_32FS (0x2 << 4) + +/* Microphone Input Volume(0x26) */ +#define RT5631_MONO_DIFF_INPUT_SHIFT 15 + +/* Speaker Mixer Control(0x28) */ +#define RT5631_M_RECMIXER_L_TO_SPKMIXER_L (0x1 << 15) +#define RT5631_M_RECMIXL_SPKMIXL_BIT 15 +#define RT5631_M_MIC1_P_TO_SPKMIXER_L (0x1 << 14) +#define RT5631_M_MIC1P_SPKMIXL_BIT 14 +#define RT5631_M_DAC_L_TO_SPKMIXER_L (0x1 << 13) +#define RT5631_M_DACL_SPKMIXL_BIT 13 +#define RT5631_M_OUTMIXER_L_TO_SPKMIXER_L (0x1 << 12) +#define RT5631_M_OUTMIXL_SPKMIXL_BIT 12 + +#define RT5631_M_RECMIXER_R_TO_SPKMIXER_R (0x1 << 7) +#define RT5631_M_RECMIXR_SPKMIXR_BIT 7 +#define RT5631_M_MIC2_P_TO_SPKMIXER_R (0x1 << 6) +#define RT5631_M_MIC2P_SPKMIXR_BIT 6 +#define RT5631_M_DAC_R_TO_SPKMIXER_R (0x1 << 5) +#define RT5631_M_DACR_SPKMIXR_BIT 5 +#define RT5631_M_OUTMIXER_R_TO_SPKMIXER_R (0x1 << 4) +#define RT5631_M_OUTMIXR_SPKMIXR_BIT 4 + +/* Speaker/Mono Output Control(0x2A) */ +#define RT5631_M_SPKVOL_L_TO_SPOL_MIXER (0x1 << 15) +#define RT5631_M_SPKVOLL_SPOLMIX_BIT 15 +#define RT5631_M_SPKVOL_R_TO_SPOL_MIXER (0x1 << 14) +#define RT5631_M_SPKVOLR_SPOLMIX_BIT 14 +#define RT5631_M_SPKVOL_L_TO_SPOR_MIXER (0x1 << 13) +#define RT5631_M_SPKVOLL_SPORMIX_BIT 13 +#define RT5631_M_SPKVOL_R_TO_SPOR_MIXER (0x1 << 12) +#define RT5631_M_SPKVOLR_SPORMIX_BIT 12 +#define RT5631_M_OUTVOL_L_TO_MONOMIXER (0x1 << 11) +#define RT5631_M_OUTVOLL_MONOMIX_BIT 11 +#define RT5631_M_OUTVOL_R_TO_MONOMIXER (0x1 << 10) +#define RT5631_M_OUTVOLR_MONOMIX_BIT 10 + +/* Speaker/Mono/HP Output Control(0x2C) */ +#define RT5631_SPK_L_MUX_SEL_MASK (0x3 << 14) +#define RT5631_SPK_L_MUX_SEL_SPKMIXER_L (0x0 << 14) +#define RT5631_SPK_L_MUX_SEL_MONO_IN (0x1 << 14) +#define RT5631_SPK_L_MUX_SEL_DAC_L (0x3 << 14) +#define RT5631_SPK_L_MUX_SEL_SHIFT 14 + +#define RT5631_SPK_R_MUX_SEL_MASK (0x3 << 10) +#define RT5631_SPK_R_MUX_SEL_SPKMIXER_R (0x0 << 10) +#define RT5631_SPK_R_MUX_SEL_MONO_IN (0x1 << 10) +#define RT5631_SPK_R_MUX_SEL_DAC_R (0x3 << 10) +#define RT5631_SPK_R_MUX_SEL_SHIFT 10 + +#define RT5631_MONO_MUX_SEL_MASK (0x3 << 6) +#define RT5631_MONO_MUX_SEL_MONOMIXER (0x0 << 6) +#define RT5631_MONO_MUX_SEL_MONO_IN (0x1 << 6) +#define RT5631_MONO_MUX_SEL_SHIFT 6 + +#define RT5631_HP_L_MUX_SEL_MASK (0x1 << 3) +#define RT5631_HP_L_MUX_SEL_HPVOL_L (0x0 << 3) +#define RT5631_HP_L_MUX_SEL_DAC_L (0x1 << 3) +#define RT5631_HP_L_MUX_SEL_SHIFT 3 + +#define RT5631_HP_R_MUX_SEL_MASK (0x1 << 2) +#define RT5631_HP_R_MUX_SEL_HPVOL_R (0x0 << 2) +#define RT5631_HP_R_MUX_SEL_DAC_R (0x1 << 2) +#define RT5631_HP_R_MUX_SEL_SHIFT 2 + +/* Stereo I2S Serial Data Port Control(0x34) */ +#define RT5631_SDP_MODE_SEL_MASK (0x1 << 15) +#define RT5631_SDP_MODE_SEL_MASTER (0x0 << 15) +#define RT5631_SDP_MODE_SEL_SLAVE (0x1 << 15) + +#define RT5631_SDP_ADC_CPS_SEL_MASK (0x3 << 10) +#define RT5631_SDP_ADC_CPS_SEL_OFF (0x0 << 10) +#define RT5631_SDP_ADC_CPS_SEL_U_LAW (0x1 << 10) +#define RT5631_SDP_ADC_CPS_SEL_A_LAW (0x2 << 10) + +#define RT5631_SDP_DAC_CPS_SEL_MASK (0x3 << 8) +#define RT5631_SDP_DAC_CPS_SEL_OFF (0x0 << 8) +#define RT5631_SDP_DAC_CPS_SEL_U_LAW (0x1 << 8) +#define RT5631_SDP_DAC_CPS_SEL_A_LAW (0x2 << 8) +/* 0:Normal 1:Invert */ +#define RT5631_SDP_I2S_BCLK_POL_CTRL (0x1 << 7) +/* 0:Normal 1:Invert */ +#define RT5631_SDP_DAC_R_INV (0x1 << 6) +/* 0:ADC data appear at left phase of LRCK + * 1:ADC data appear at right phase of LRCK + */ +#define RT5631_SDP_ADC_DATA_L_R_SWAP (0x1 << 5) +/* 0:DAC data appear at left phase of LRCK + * 1:DAC data appear at right phase of LRCK + */ +#define RT5631_SDP_DAC_DATA_L_R_SWAP (0x1 << 4) + +/* Data Length Slection */ +#define RT5631_SDP_I2S_DL_MASK (0x3 << 2) +#define RT5631_SDP_I2S_DL_16 (0x0 << 2) +#define RT5631_SDP_I2S_DL_20 (0x1 << 2) +#define RT5631_SDP_I2S_DL_24 (0x2 << 2) +#define RT5631_SDP_I2S_DL_8 (0x3 << 2) + +/* PCM Data Format Selection */ +#define RT5631_SDP_I2S_DF_MASK (0x3) +#define RT5631_SDP_I2S_DF_I2S (0x0) +#define RT5631_SDP_I2S_DF_LEFT (0x1) +#define RT5631_SDP_I2S_DF_PCM_A (0x2) +#define RT5631_SDP_I2S_DF_PCM_B (0x3) + +/* Stereo AD/DA Clock Control(0x38h) */ +#define RT5631_I2S_PRE_DIV_MASK (0x7 << 13) +#define RT5631_I2S_PRE_DIV_1 (0x0 << 13) +#define RT5631_I2S_PRE_DIV_2 (0x1 << 13) +#define RT5631_I2S_PRE_DIV_4 (0x2 << 13) +#define RT5631_I2S_PRE_DIV_8 (0x3 << 13) +#define RT5631_I2S_PRE_DIV_16 (0x4 << 13) +#define RT5631_I2S_PRE_DIV_32 (0x5 << 13) +/* CLOCK RELATIVE OF BCLK AND LCRK */ +#define RT5631_I2S_LRCK_SEL_N_BCLK_MASK (0x1 << 12) +#define RT5631_I2S_LRCK_SEL_64_BCLK (0x0 << 12) /* 64FS */ +#define RT5631_I2S_LRCK_SEL_32_BCLK (0x1 << 12) /* 32FS */ + +#define RT5631_DAC_OSR_SEL_MASK (0x3 << 10) +#define RT5631_DAC_OSR_SEL_128FS (0x3 << 10) +#define RT5631_DAC_OSR_SEL_64FS (0x3 << 10) +#define RT5631_DAC_OSR_SEL_32FS (0x3 << 10) +#define RT5631_DAC_OSR_SEL_16FS (0x3 << 10) + +#define RT5631_ADC_OSR_SEL_MASK (0x3 << 8) +#define RT5631_ADC_OSR_SEL_128FS (0x3 << 8) +#define RT5631_ADC_OSR_SEL_64FS (0x3 << 8) +#define RT5631_ADC_OSR_SEL_32FS (0x3 << 8) +#define RT5631_ADC_OSR_SEL_16FS (0x3 << 8) + +#define RT5631_ADDA_FILTER_CLK_SEL_256FS (0 << 7) /* 256FS */ +#define RT5631_ADDA_FILTER_CLK_SEL_384FS (1 << 7) /* 384FS */ + +/* Power managment addition 1 (0x3A) */ +#define RT5631_PWR_MAIN_I2S_EN (0x1 << 15) +#define RT5631_PWR_MAIN_I2S_BIT 15 +#define RT5631_PWR_CLASS_D (0x1 << 12) +#define RT5631_PWR_CLASS_D_BIT 12 +#define RT5631_PWR_ADC_L_CLK (0x1 << 11) +#define RT5631_PWR_ADC_L_CLK_BIT 11 +#define RT5631_PWR_ADC_R_CLK (0x1 << 10) +#define RT5631_PWR_ADC_R_CLK_BIT 10 +#define RT5631_PWR_DAC_L_CLK (0x1 << 9) +#define RT5631_PWR_DAC_L_CLK_BIT 9 +#define RT5631_PWR_DAC_R_CLK (0x1 << 8) +#define RT5631_PWR_DAC_R_CLK_BIT 8 +#define RT5631_PWR_DAC_REF (0x1 << 7) +#define RT5631_PWR_DAC_REF_BIT 7 +#define RT5631_PWR_DAC_L_TO_MIXER (0x1 << 6) +#define RT5631_PWR_DAC_L_TO_MIXER_BIT 6 +#define RT5631_PWR_DAC_R_TO_MIXER (0x1 << 5) +#define RT5631_PWR_DAC_R_TO_MIXER_BIT 5 + +/* Power managment addition 2 (0x3B) */ +#define RT5631_PWR_OUTMIXER_L (0x1 << 15) +#define RT5631_PWR_OUTMIXER_L_BIT 15 +#define RT5631_PWR_OUTMIXER_R (0x1 << 14) +#define RT5631_PWR_OUTMIXER_R_BIT 14 +#define RT5631_PWR_SPKMIXER_L (0x1 << 13) +#define RT5631_PWR_SPKMIXER_L_BIT 13 +#define RT5631_PWR_SPKMIXER_R (0x1 << 12) +#define RT5631_PWR_SPKMIXER_R_BIT 12 +#define RT5631_PWR_RECMIXER_L (0x1 << 11) +#define RT5631_PWR_RECMIXER_L_BIT 11 +#define RT5631_PWR_RECMIXER_R (0x1 << 10) +#define RT5631_PWR_RECMIXER_R_BIT 10 +#define RT5631_PWR_MIC1_BOOT_GAIN (0x1 << 5) +#define RT5631_PWR_MIC1_BOOT_GAIN_BIT 5 +#define RT5631_PWR_MIC2_BOOT_GAIN (0x1 << 4) +#define RT5631_PWR_MIC2_BOOT_GAIN_BIT 4 +#define RT5631_PWR_MICBIAS1_VOL (0x1 << 3) +#define RT5631_PWR_MICBIAS1_VOL_BIT 3 +#define RT5631_PWR_MICBIAS2_VOL (0x1 << 2) +#define RT5631_PWR_MICBIAS2_VOL_BIT 2 +#define RT5631_PWR_PLL1 (0x1 << 1) +#define RT5631_PWR_PLL1_BIT 1 +#define RT5631_PWR_PLL2 (0x1 << 0) +#define RT5631_PWR_PLL2_BIT 0 + +/* Power managment addition 3(0x3C) */ +#define RT5631_PWR_VREF (0x1 << 15) +#define RT5631_PWR_VREF_BIT 15 +#define RT5631_PWR_FAST_VREF_CTRL (0x1 << 14) +#define RT5631_PWR_FAST_VREF_CTRL_BIT 14 +#define RT5631_PWR_MAIN_BIAS (0x1 << 13) +#define RT5631_PWR_MAIN_BIAS_BIT 13 +#define RT5631_PWR_AXO1MIXER (0x1 << 11) +#define RT5631_PWR_AXO1MIXER_BIT 11 +#define RT5631_PWR_AXO2MIXER (0x1 << 10) +#define RT5631_PWR_AXO2MIXER_BIT 10 +#define RT5631_PWR_MONOMIXER (0x1 << 9) +#define RT5631_PWR_MONOMIXER_BIT 9 +#define RT5631_PWR_MONO_DEPOP_DIS (0x1 << 8) +#define RT5631_PWR_MONO_DEPOP_DIS_BIT 8 +#define RT5631_PWR_MONO_AMP_EN (0x1 << 7) +#define RT5631_PWR_MONO_AMP_EN_BIT 7 +#define RT5631_PWR_CHARGE_PUMP (0x1 << 4) +#define RT5631_PWR_CHARGE_PUMP_BIT 4 +#define RT5631_PWR_HP_L_AMP (0x1 << 3) +#define RT5631_PWR_HP_L_AMP_BIT 3 +#define RT5631_PWR_HP_R_AMP (0x1 << 2) +#define RT5631_PWR_HP_R_AMP_BIT 2 +#define RT5631_PWR_HP_DEPOP_DIS (0x1 << 1) +#define RT5631_PWR_HP_DEPOP_DIS_BIT 1 +#define RT5631_PWR_HP_AMP_DRIVING (0x1 << 0) +#define RT5631_PWR_HP_AMP_DRIVING_BIT 0 + +/* Power managment addition 4(0x3E) */ +#define RT5631_PWR_SPK_L_VOL (0x1 << 15) +#define RT5631_PWR_SPK_L_VOL_BIT 15 +#define RT5631_PWR_SPK_R_VOL (0x1 << 14) +#define RT5631_PWR_SPK_R_VOL_BIT 14 +#define RT5631_PWR_LOUT_VOL (0x1 << 13) +#define RT5631_PWR_LOUT_VOL_BIT 13 +#define RT5631_PWR_ROUT_VOL (0x1 << 12) +#define RT5631_PWR_ROUT_VOL_BIT 12 +#define RT5631_PWR_HP_L_OUT_VOL (0x1 << 11) +#define RT5631_PWR_HP_L_OUT_VOL_BIT 11 +#define RT5631_PWR_HP_R_OUT_VOL (0x1 << 10) +#define RT5631_PWR_HP_R_OUT_VOL_BIT 10 +#define RT5631_PWR_AXIL_IN_VOL (0x1 << 9) +#define RT5631_PWR_AXIL_IN_VOL_BIT 9 +#define RT5631_PWR_AXIR_IN_VOL (0x1 << 8) +#define RT5631_PWR_AXIR_IN_VOL_BIT 8 +#define RT5631_PWR_MONO_IN_P_VOL (0x1 << 7) +#define RT5631_PWR_MONO_IN_P_VOL_BIT 7 +#define RT5631_PWR_MONO_IN_N_VOL (0x1 << 6) +#define RT5631_PWR_MONO_IN_N_VOL_BIT 6 + +/* General Purpose Control Register(0x40) */ +#define RT5631_SPK_AMP_AUTO_RATIO_EN (0x1 << 15) + +#define RT5631_SPK_AMP_RATIO_CTRL_MASK (0x7 << 12) +#define RT5631_SPK_AMP_RATIO_CTRL_2_34 (0x0 << 12) /* 7.40DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_1_99 (0x1 << 12) /* 5.99DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_1_68 (0x2 << 12) /* 4.50DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_1_56 (0x3 << 12) /* 3.86DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_1_44 (0x4 << 12) /* 3.16DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_1_27 (0x5 << 12) /* 2.10DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_1_09 (0x6 << 12) /* 0.80DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_1_00 (0x7 << 12) /* 0.00DB */ +#define RT5631_SPK_AMP_RATIO_CTRL_SHIFT 12 + +#define RT5631_STEREO_DAC_HI_PASS_FILT_EN (0x1 << 11) +#define RT5631_STEREO_ADC_HI_PASS_FILT_EN (0x1 << 10) +/* Select ADC Wind Filter Clock type */ +#define RT5631_ADC_WIND_FILT_MASK (0x3 << 4) +#define RT5631_ADC_WIND_FILT_8_16_32K (0x0 << 4) /*8/16/32k*/ +#define RT5631_ADC_WIND_FILT_11_22_44K (0x1 << 4) /*11/22/44k*/ +#define RT5631_ADC_WIND_FILT_12_24_48K (0x2 << 4) /*12/24/48k*/ +#define RT5631_ADC_WIND_FILT_EN (0x1 << 3) +/* SelectADC Wind Filter Corner Frequency */ +#define RT5631_ADC_WIND_CNR_FREQ_MASK (0x7 << 0) +#define RT5631_ADC_WIND_CNR_FREQ_82_113_122 (0x0 << 0) /* 82/113/122 Hz */ +#define RT5631_ADC_WIND_CNR_FREQ_102_141_153 (0x1 << 0) /* 102/141/153 Hz */ +#define RT5631_ADC_WIND_CNR_FREQ_131_180_156 (0x2 << 0) /* 131/180/156 Hz */ +#define RT5631_ADC_WIND_CNR_FREQ_163_225_245 (0x3 << 0) /* 163/225/245 Hz */ +#define RT5631_ADC_WIND_CNR_FREQ_204_281_306 (0x4 << 0) /* 204/281/306 Hz */ +#define RT5631_ADC_WIND_CNR_FREQ_261_360_392 (0x5 << 0) /* 261/360/392 Hz */ +#define RT5631_ADC_WIND_CNR_FREQ_327_450_490 (0x6 << 0) /* 327/450/490 Hz */ +#define RT5631_ADC_WIND_CNR_FREQ_408_563_612 (0x7 << 0) /* 408/563/612 Hz */ + +/* Global Clock Control Register(0x42) */ +#define RT5631_SYSCLK_SOUR_SEL_MASK (0x3 << 14) +#define RT5631_SYSCLK_SOUR_SEL_MCLK (0x0 << 14) +#define RT5631_SYSCLK_SOUR_SEL_PLL (0x1 << 14) +#define RT5631_SYSCLK_SOUR_SEL_PLL_TCK (0x2 << 14) + +#define RT5631_PLLCLK_SOUR_SEL_MASK (0x3 << 12) +#define RT5631_PLLCLK_SOUR_SEL_MCLK (0x0 << 12) +#define RT5631_PLLCLK_SOUR_SEL_BCLK (0x1 << 12) +#define RT5631_PLLCLK_SOUR_SEL_VBCLK (0x2 << 12) + +#define RT5631_PLLCLK_PRE_DIV1 (0x0 << 11) +#define RT5631_PLLCLK_PRE_DIV2 (0x1 << 11) + +/* PLL Control(0x44) */ +#define RT5631_PLL_CTRL_M_VAL(m) ((m)&0xf) +#define RT5631_PLL_CTRL_K_VAL(k) (((k)&0x7) << 4) +#define RT5631_PLL_CTRL_N_VAL(n) (((n)&0xff) << 8) + +/* Internal Status and IRQ Control2(0x4A) */ +#define RT5631_ADC_DATA_SEL_MASK (0x3 << 14) +#define RT5631_ADC_DATA_SEL_Disable (0x0 << 14) +#define RT5631_ADC_DATA_SEL_MIC1 (0x1 << 14) +#define RT5631_ADC_DATA_SEL_MIC1_SHIFT 14 +#define RT5631_ADC_DATA_SEL_MIC2 (0x2 << 14) +#define RT5631_ADC_DATA_SEL_MIC2_SHIFT 15 +#define RT5631_ADC_DATA_SEL_STO (0x3 << 14) +#define RT5631_ADC_DATA_SEL_SHIFT 14 + +/* GPIO Pin Configuration(0x4C) */ +#define RT5631_GPIO_PIN_FUN_SEL_MASK (0x1 << 15) +#define RT5631_GPIO_PIN_FUN_SEL_IRQ (0x1 << 15) +#define RT5631_GPIO_PIN_FUN_SEL_GPIO_DIMC (0x0 << 15) + +#define RT5631_GPIO_DMIC_FUN_SEL_MASK (0x1 << 3) +#define RT5631_GPIO_DMIC_FUN_SEL_DIMC (0x1 << 3) +#define RT5631_GPIO_DMIC_FUN_SEL_GPIO (0x0 << 3) + +#define RT5631_GPIO_PIN_CON_MASK (0x1 << 2) +#define RT5631_GPIO_PIN_SET_INPUT (0x0 << 2) +#define RT5631_GPIO_PIN_SET_OUTPUT (0x1 << 2) + +/* De-POP function Control 1(0x54) */ +#define RT5631_POW_ON_SOFT_GEN (0x1 << 15) +#define RT5631_EN_MUTE_UNMUTE_DEPOP (0x1 << 14) +#define RT5631_EN_DEPOP2_FOR_HP (0x1 << 7) +/* Power Down HPAMP_L Starts Up Signal */ +#define RT5631_PD_HPAMP_L_ST_UP (0x1 << 5) +/* Power Down HPAMP_R Starts Up Signal */ +#define RT5631_PD_HPAMP_R_ST_UP (0x1 << 4) +/* Enable left HP mute/unmute depop */ +#define RT5631_EN_HP_L_M_UN_MUTE_DEPOP (0x1 << 1) +/* Enable right HP mute/unmute depop */ +#define RT5631_EN_HP_R_M_UN_MUTE_DEPOP (0x1 << 0) + +/* De-POP Fnction Control(0x56) */ +#define RT5631_EN_ONE_BIT_DEPOP (0x1 << 15) +#define RT5631_EN_CAP_FREE_DEPOP (0x1 << 14) + +/* Jack Detect Control Register(0x5A) */ +#define RT5631_JD_USE_MASK (0x3 << 14) +#define RT5631_JD_USE_JD2 (0x3 << 14) +#define RT5631_JD_USE_JD1 (0x2 << 14) +#define RT5631_JD_USE_GPIO (0x1 << 14) +#define RT5631_JD_OFF (0x0 << 14) +/* JD trigger enable for HP */ +#define RT5631_JD_HP_EN (0x1 << 11) +#define RT5631_JD_HP_TRI_MASK (0x1 << 10) +#define RT5631_JD_HP_TRI_HI (0x1 << 10) +#define RT5631_JD_HP_TRI_LO (0x1 << 10) +/* JD trigger enable for speaker LP/LN */ +#define RT5631_JD_SPK_L_EN (0x1 << 9) +#define RT5631_JD_SPK_L_TRI_MASK (0x1 << 8) +#define RT5631_JD_SPK_L_TRI_HI (0x1 << 8) +#define RT5631_JD_SPK_L_TRI_LO (0x0 << 8) +/* JD trigger enable for speaker RP/RN */ +#define RT5631_JD_SPK_R_EN (0x1 << 7) +#define RT5631_JD_SPK_R_TRI_MASK (0x1 << 6) +#define RT5631_JD_SPK_R_TRI_HI (0x1 << 6) +#define RT5631_JD_SPK_R_TRI_LO (0x0 << 6) +/* JD trigger enable for monoout */ +#define RT5631_JD_MONO_EN (0x1 << 5) +#define RT5631_JD_MONO_TRI_MASK (0x1 << 4) +#define RT5631_JD_MONO_TRI_HI (0x1 << 4) +#define RT5631_JD_MONO_TRI_LO (0x0 << 4) +/* JD trigger enable for Lout */ +#define RT5631_JD_AUX_1_EN (0x1 << 3) +#define RT5631_JD_AUX_1_MASK (0x1 << 2) +#define RT5631_JD_AUX_1_TRI_HI (0x1 << 2) +#define RT5631_JD_AUX_1_TRI_LO (0x0 << 2) +/* JD trigger enable for Rout */ +#define RT5631_JD_AUX_2_EN (0x1 << 1) +#define RT5631_JD_AUX_2_MASK (0x1 << 0) +#define RT5631_JD_AUX_2_TRI_HI (0x1 << 0) +#define RT5631_JD_AUX_2_TRI_LO (0x0 << 0) + +/* ALC CONTROL 1(0x64) */ +#define RT5631_ALC_ATTACK_RATE_MASK (0x1F << 8) +#define RT5631_ALC_RECOVERY_RATE_MASK (0x1F << 0) + +/* ALC CONTROL 2(0x65) */ +/* select Compensation gain for Noise gate function */ +#define RT5631_ALC_COM_NOISE_GATE_MASK (0xF << 0) + +/* ALC CONTROL 3(0x66) */ +#define RT5631_ALC_FUN_MASK (0x3 << 14) +#define RT5631_ALC_FUN_DIS (0x0 << 14) +#define RT5631_ALC_ENA_DAC_PATH (0x1 << 14) +#define RT5631_ALC_ENA_ADC_PATH (0x3 << 14) +#define RT5631_ALC_PARA_UPDATE (0x1 << 13) +#define RT5631_ALC_LIMIT_LEVEL_MASK (0x1F << 8) +#define RT5631_ALC_NOISE_GATE_FUN_MASK (0x1 << 7) +#define RT5631_ALC_NOISE_GATE_FUN_DIS (0x0 << 7) +#define RT5631_ALC_NOISE_GATE_FUN_ENA (0x1 << 7) +/* ALC noise gate hold data function */ +#define RT5631_ALC_NOISE_GATE_H_D_MASK (0x1 << 6) +#define RT5631_ALC_NOISE_GATE_H_D_DIS (0x0 << 6) +#define RT5631_ALC_NOISE_GATE_H_D_ENA (0x1 << 6) + +/* Psedueo Stereo & Spatial Effect Block Control(0x68) */ +#define RT5631_SPATIAL_CTRL_EN (0x1 << 15) +#define RT5631_ALL_PASS_FILTER_EN (0x1 << 14) +#define RT5631_PSEUDO_STEREO_EN (0x1 << 13) +#define RT5631_STEREO_EXPENSION_EN (0x1 << 12) +/* 3D gain parameter */ +#define RT5631_GAIN_3D_PARA_MASK (0x3 << 6) +#define RT5631_GAIN_3D_PARA_1_00 (0x0 << 6) /* 3D gain 1.0 */ +#define RT5631_GAIN_3D_PARA_1_50 (0x1 << 6) /* 3D gain 1.5 */ +#define RT5631_GAIN_3D_PARA_2_00 (0x2 << 6) /* 3D gain 2.0 */ +/* 3D ratio parameter */ +#define RT5631_RATIO_3D_MASK (0x3 << 4) +#define RT5631_RATIO_3D_0_0 (0x0 << 4) /* 3D ratio 0.0 */ +#define RT5631_RATIO_3D_0_66 (0x1 << 4) /* 3D ratio 0.66 */ +#define RT5631_RATIO_3D_1_0 (0x2 << 4) /* 3D ratio 1.0 */ +/* select samplerate for all pass filter */ +#define RT5631_APF_FUN_SLE_MASK (0x3 << 0) +#define RT5631_APF_FUN_SEL_48K (0x3 << 0) +#define RT5631_APF_FUN_SEL_44_1K (0x2 << 0) +#define RT5631_APF_FUN_SEL_32K (0x1 << 0) +#define RT5631_APF_FUN_DIS (0x0 << 0) + +/* EQ CONTROL 1(0x6E) */ +#define RT5631_HW_EQ_PATH_SEL_MASK (0x1 << 15) +#define RT5631_HW_EQ_PATH_SEL_DAC (0x0 << 15) +#define RT5631_HW_EQ_PATH_SEL_ADC (0x1 << 15) +#define RT5631_HW_EQ_UPDATE_CTRL (0x1 << 14) + +#define RT5631_EN_HW_EQ_HPF2 (0x1 << 5) +#define RT5631_EN_HW_EQ_HPF1 (0x1 << 4) +#define RT5631_EN_HW_EQ_BP3 (0x1 << 3) +#define RT5631_EN_HW_EQ_BP2 (0x1 << 2) +#define RT5631_EN_HW_EQ_BP1 (0x1 << 1) +#define RT5631_EN_HW_EQ_LPF (0x1 << 0) + + +#endif /* __RTCODEC5631_H__ */ diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c new file mode 100644 index 000000000..63e19a6a9 --- /dev/null +++ b/sound/soc/codecs/rt5640.c @@ -0,0 +1,2871 @@ +/* + * rt5640.c -- RT5640/RT5639 ALSA SoC audio codec driver + * + * Copyright 2011 Realtek Semiconductor Corp. + * Author: Johnny Hsu + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5640.h" + +#define RT5640_DEVICE_ID 0x6231 + +#define RT5640_PR_RANGE_BASE (0xff + 1) +#define RT5640_PR_SPACING 0x100 + +#define RT5640_PR_BASE (RT5640_PR_RANGE_BASE + (0 * RT5640_PR_SPACING)) + +static const struct regmap_range_cfg rt5640_ranges[] = { + { .name = "PR", .range_min = RT5640_PR_BASE, + .range_max = RT5640_PR_BASE + 0xb4, + .selector_reg = RT5640_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT5640_PRIV_DATA, + .window_len = 0x1, }, +}; + +static const struct reg_sequence init_list[] = { + {RT5640_PR_BASE + 0x3d, 0x3600}, + {RT5640_PR_BASE + 0x12, 0x0aa8}, + {RT5640_PR_BASE + 0x14, 0x0aaa}, + {RT5640_PR_BASE + 0x20, 0x6110}, + {RT5640_PR_BASE + 0x21, 0xe0e0}, + {RT5640_PR_BASE + 0x23, 0x1804}, +}; + +static const struct reg_default rt5640_reg[] = { + { 0x00, 0x000e }, + { 0x01, 0xc8c8 }, + { 0x02, 0xc8c8 }, + { 0x03, 0xc8c8 }, + { 0x04, 0x8000 }, + { 0x0d, 0x0000 }, + { 0x0e, 0x0000 }, + { 0x0f, 0x0808 }, + { 0x19, 0xafaf }, + { 0x1a, 0xafaf }, + { 0x1b, 0x0000 }, + { 0x1c, 0x2f2f }, + { 0x1d, 0x2f2f }, + { 0x1e, 0x0000 }, + { 0x27, 0x7060 }, + { 0x28, 0x7070 }, + { 0x29, 0x8080 }, + { 0x2a, 0x5454 }, + { 0x2b, 0x5454 }, + { 0x2c, 0xaa00 }, + { 0x2d, 0x0000 }, + { 0x2e, 0xa000 }, + { 0x2f, 0x0000 }, + { 0x3b, 0x0000 }, + { 0x3c, 0x007f }, + { 0x3d, 0x0000 }, + { 0x3e, 0x007f }, + { 0x45, 0xe000 }, + { 0x46, 0x003e }, + { 0x47, 0x003e }, + { 0x48, 0xf800 }, + { 0x49, 0x3800 }, + { 0x4a, 0x0004 }, + { 0x4c, 0xfc00 }, + { 0x4d, 0x0000 }, + { 0x4f, 0x01ff }, + { 0x50, 0x0000 }, + { 0x51, 0x0000 }, + { 0x52, 0x01ff }, + { 0x53, 0xf000 }, + { 0x61, 0x0000 }, + { 0x62, 0x0000 }, + { 0x63, 0x00c0 }, + { 0x64, 0x0000 }, + { 0x65, 0x0000 }, + { 0x66, 0x0000 }, + { 0x6a, 0x0000 }, + { 0x6c, 0x0000 }, + { 0x70, 0x8000 }, + { 0x71, 0x8000 }, + { 0x72, 0x8000 }, + { 0x73, 0x1114 }, + { 0x74, 0x0c00 }, + { 0x75, 0x1d00 }, + { 0x80, 0x0000 }, + { 0x81, 0x0000 }, + { 0x82, 0x0000 }, + { 0x83, 0x0000 }, + { 0x84, 0x0000 }, + { 0x85, 0x0008 }, + { 0x89, 0x0000 }, + { 0x8a, 0x0000 }, + { 0x8b, 0x0600 }, + { 0x8c, 0x0228 }, + { 0x8d, 0xa000 }, + { 0x8e, 0x0004 }, + { 0x8f, 0x1100 }, + { 0x90, 0x0646 }, + { 0x91, 0x0c00 }, + { 0x92, 0x0000 }, + { 0x93, 0x3000 }, + { 0xb0, 0x2080 }, + { 0xb1, 0x0000 }, + { 0xb4, 0x2206 }, + { 0xb5, 0x1f00 }, + { 0xb6, 0x0000 }, + { 0xb8, 0x034b }, + { 0xb9, 0x0066 }, + { 0xba, 0x000b }, + { 0xbb, 0x0000 }, + { 0xbc, 0x0000 }, + { 0xbd, 0x0000 }, + { 0xbe, 0x0000 }, + { 0xbf, 0x0000 }, + { 0xc0, 0x0400 }, + { 0xc2, 0x0000 }, + { 0xc4, 0x0000 }, + { 0xc5, 0x0000 }, + { 0xc6, 0x2000 }, + { 0xc8, 0x0000 }, + { 0xc9, 0x0000 }, + { 0xca, 0x0000 }, + { 0xcb, 0x0000 }, + { 0xcc, 0x0000 }, + { 0xcf, 0x0013 }, + { 0xd0, 0x0680 }, + { 0xd1, 0x1c17 }, + { 0xd2, 0x8c00 }, + { 0xd3, 0xaa20 }, + { 0xd6, 0x0400 }, + { 0xd9, 0x0809 }, + { 0xfe, 0x10ec }, + { 0xff, 0x6231 }, +}; + +static int rt5640_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, RT5640_RESET, 0); +} + +static bool rt5640_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5640_ranges); i++) + if ((reg >= rt5640_ranges[i].window_start && + reg <= rt5640_ranges[i].window_start + + rt5640_ranges[i].window_len) || + (reg >= rt5640_ranges[i].range_min && + reg <= rt5640_ranges[i].range_max)) + return true; + + switch (reg) { + case RT5640_RESET: + case RT5640_ASRC_5: + case RT5640_EQ_CTRL1: + case RT5640_DRC_AGC_1: + case RT5640_ANC_CTRL1: + case RT5640_IRQ_CTRL2: + case RT5640_INT_IRQ_ST: + case RT5640_DSP_CTRL2: + case RT5640_DSP_CTRL3: + case RT5640_PRIV_INDEX: + case RT5640_PRIV_DATA: + case RT5640_PGM_REG_ARR1: + case RT5640_PGM_REG_ARR3: + case RT5640_VENDOR_ID: + case RT5640_VENDOR_ID1: + case RT5640_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool rt5640_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5640_ranges); i++) + if ((reg >= rt5640_ranges[i].window_start && + reg <= rt5640_ranges[i].window_start + + rt5640_ranges[i].window_len) || + (reg >= rt5640_ranges[i].range_min && + reg <= rt5640_ranges[i].range_max)) + return true; + + switch (reg) { + case RT5640_RESET: + case RT5640_SPK_VOL: + case RT5640_HP_VOL: + case RT5640_OUTPUT: + case RT5640_MONO_OUT: + case RT5640_IN1_IN2: + case RT5640_IN3_IN4: + case RT5640_INL_INR_VOL: + case RT5640_DAC1_DIG_VOL: + case RT5640_DAC2_DIG_VOL: + case RT5640_DAC2_CTRL: + case RT5640_ADC_DIG_VOL: + case RT5640_ADC_DATA: + case RT5640_ADC_BST_VOL: + case RT5640_STO_ADC_MIXER: + case RT5640_MONO_ADC_MIXER: + case RT5640_AD_DA_MIXER: + case RT5640_STO_DAC_MIXER: + case RT5640_MONO_DAC_MIXER: + case RT5640_DIG_MIXER: + case RT5640_DSP_PATH1: + case RT5640_DSP_PATH2: + case RT5640_DIG_INF_DATA: + case RT5640_REC_L1_MIXER: + case RT5640_REC_L2_MIXER: + case RT5640_REC_R1_MIXER: + case RT5640_REC_R2_MIXER: + case RT5640_HPO_MIXER: + case RT5640_SPK_L_MIXER: + case RT5640_SPK_R_MIXER: + case RT5640_SPO_L_MIXER: + case RT5640_SPO_R_MIXER: + case RT5640_SPO_CLSD_RATIO: + case RT5640_MONO_MIXER: + case RT5640_OUT_L1_MIXER: + case RT5640_OUT_L2_MIXER: + case RT5640_OUT_L3_MIXER: + case RT5640_OUT_R1_MIXER: + case RT5640_OUT_R2_MIXER: + case RT5640_OUT_R3_MIXER: + case RT5640_LOUT_MIXER: + case RT5640_PWR_DIG1: + case RT5640_PWR_DIG2: + case RT5640_PWR_ANLG1: + case RT5640_PWR_ANLG2: + case RT5640_PWR_MIXER: + case RT5640_PWR_VOL: + case RT5640_PRIV_INDEX: + case RT5640_PRIV_DATA: + case RT5640_I2S1_SDP: + case RT5640_I2S2_SDP: + case RT5640_ADDA_CLK1: + case RT5640_ADDA_CLK2: + case RT5640_DMIC: + case RT5640_GLB_CLK: + case RT5640_PLL_CTRL1: + case RT5640_PLL_CTRL2: + case RT5640_ASRC_1: + case RT5640_ASRC_2: + case RT5640_ASRC_3: + case RT5640_ASRC_4: + case RT5640_ASRC_5: + case RT5640_HP_OVCD: + case RT5640_CLS_D_OVCD: + case RT5640_CLS_D_OUT: + case RT5640_DEPOP_M1: + case RT5640_DEPOP_M2: + case RT5640_DEPOP_M3: + case RT5640_CHARGE_PUMP: + case RT5640_PV_DET_SPK_G: + case RT5640_MICBIAS: + case RT5640_EQ_CTRL1: + case RT5640_EQ_CTRL2: + case RT5640_WIND_FILTER: + case RT5640_DRC_AGC_1: + case RT5640_DRC_AGC_2: + case RT5640_DRC_AGC_3: + case RT5640_SVOL_ZC: + case RT5640_ANC_CTRL1: + case RT5640_ANC_CTRL2: + case RT5640_ANC_CTRL3: + case RT5640_JD_CTRL: + case RT5640_ANC_JD: + case RT5640_IRQ_CTRL1: + case RT5640_IRQ_CTRL2: + case RT5640_INT_IRQ_ST: + case RT5640_GPIO_CTRL1: + case RT5640_GPIO_CTRL2: + case RT5640_GPIO_CTRL3: + case RT5640_DSP_CTRL1: + case RT5640_DSP_CTRL2: + case RT5640_DSP_CTRL3: + case RT5640_DSP_CTRL4: + case RT5640_PGM_REG_ARR1: + case RT5640_PGM_REG_ARR2: + case RT5640_PGM_REG_ARR3: + case RT5640_PGM_REG_ARR4: + case RT5640_PGM_REG_ARR5: + case RT5640_SCB_FUNC: + case RT5640_SCB_CTRL: + case RT5640_BASE_BACK: + case RT5640_MP3_PLUS1: + case RT5640_MP3_PLUS2: + case RT5640_3D_HP: + case RT5640_ADJ_HPF: + case RT5640_HP_CALIB_AMP_DET: + case RT5640_HP_CALIB2: + case RT5640_SV_ZCD1: + case RT5640_SV_ZCD2: + case RT5640_DUMMY1: + case RT5640_DUMMY2: + case RT5640_DUMMY3: + case RT5640_VENDOR_ID: + case RT5640_VENDOR_ID1: + case RT5640_VENDOR_ID2: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_MINMAX(dac_vol_tlv, -6562, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_MINMAX(adc_vol_tlv, -1762, 3000); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* Interface data select */ +static const char * const rt5640_data_select[] = { + "Normal", "Swap", "left copy to right", "right copy to left"}; + +static SOC_ENUM_SINGLE_DECL(rt5640_if1_dac_enum, RT5640_DIG_INF_DATA, + RT5640_IF1_DAC_SEL_SFT, rt5640_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5640_if1_adc_enum, RT5640_DIG_INF_DATA, + RT5640_IF1_ADC_SEL_SFT, rt5640_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5640_if2_dac_enum, RT5640_DIG_INF_DATA, + RT5640_IF2_DAC_SEL_SFT, rt5640_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5640_if2_adc_enum, RT5640_DIG_INF_DATA, + RT5640_IF2_ADC_SEL_SFT, rt5640_data_select); + +/* Class D speaker gain ratio */ +static const char * const rt5640_clsd_spk_ratio[] = {"1.66x", "1.83x", "1.94x", + "2x", "2.11x", "2.22x", "2.33x", "2.44x", "2.55x", "2.66x", "2.77x"}; + +static SOC_ENUM_SINGLE_DECL(rt5640_clsd_spk_ratio_enum, RT5640_CLS_D_OUT, + RT5640_CLSD_RATIO_SFT, rt5640_clsd_spk_ratio); + +static const struct snd_kcontrol_new rt5640_snd_controls[] = { + /* Speaker Output Volume */ + SOC_DOUBLE("Speaker Channel Switch", RT5640_SPK_VOL, + RT5640_VOL_L_SFT, RT5640_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("Speaker Playback Volume", RT5640_SPK_VOL, + RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, 39, 1, out_vol_tlv), + /* Headphone Output Volume */ + SOC_DOUBLE("HP Channel Switch", RT5640_HP_VOL, + RT5640_VOL_L_SFT, RT5640_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("HP Playback Volume", RT5640_HP_VOL, + RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, 39, 1, out_vol_tlv), + /* OUTPUT Control */ + SOC_DOUBLE("OUT Playback Switch", RT5640_OUTPUT, + RT5640_L_MUTE_SFT, RT5640_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("OUT Channel Switch", RT5640_OUTPUT, + RT5640_VOL_L_SFT, RT5640_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("OUT Playback Volume", RT5640_OUTPUT, + RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE("DAC2 Playback Switch", RT5640_DAC2_CTRL, + RT5640_M_DAC_L2_VOL_SFT, RT5640_M_DAC_R2_VOL_SFT, 1, 1), + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5640_DAC1_DIG_VOL, + RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, + 175, 0, dac_vol_tlv), + /* IN1/IN2/IN3 Control */ + SOC_SINGLE_TLV("IN1 Boost", RT5640_IN1_IN2, + RT5640_BST_SFT1, 8, 0, bst_tlv), + SOC_SINGLE_TLV("IN2 Boost", RT5640_IN3_IN4, + RT5640_BST_SFT2, 8, 0, bst_tlv), + SOC_SINGLE_TLV("IN3 Boost", RT5640_IN1_IN2, + RT5640_BST_SFT2, 8, 0, bst_tlv), + + /* INL/INR Volume Control */ + SOC_DOUBLE_TLV("IN Capture Volume", RT5640_INL_INR_VOL, + RT5640_INL_VOL_SFT, RT5640_INR_VOL_SFT, + 31, 1, in_vol_tlv), + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC Capture Switch", RT5640_ADC_DIG_VOL, + RT5640_L_MUTE_SFT, RT5640_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("ADC Capture Volume", RT5640_ADC_DIG_VOL, + RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, + 127, 0, adc_vol_tlv), + SOC_DOUBLE("Mono ADC Capture Switch", RT5640_DUMMY1, + RT5640_M_MONO_ADC_L_SFT, RT5640_M_MONO_ADC_R_SFT, 1, 1), + SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5640_ADC_DATA, + RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, + 127, 0, adc_vol_tlv), + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("ADC Boost Gain", RT5640_ADC_BST_VOL, + RT5640_ADC_L_BST_SFT, RT5640_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + /* Class D speaker gain ratio */ + SOC_ENUM("Class D SPK Ratio Control", rt5640_clsd_spk_ratio_enum), + + SOC_ENUM("ADC IF1 Data Switch", rt5640_if1_adc_enum), + SOC_ENUM("DAC IF1 Data Switch", rt5640_if1_dac_enum), + SOC_ENUM("ADC IF2 Data Switch", rt5640_if2_adc_enum), + SOC_ENUM("DAC IF2 Data Switch", rt5640_if2_dac_enum), +}; + +static const struct snd_kcontrol_new rt5640_specific_snd_controls[] = { + /* MONO Output Control */ + SOC_SINGLE("Mono Playback Switch", RT5640_MONO_OUT, RT5640_L_MUTE_SFT, + 1, 1), + + SOC_DOUBLE_TLV("Mono DAC Playback Volume", RT5640_DAC2_DIG_VOL, + RT5640_L_VOL_SFT, RT5640_R_VOL_SFT, 175, 0, dac_vol_tlv), +}; + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + int idx, rate; + + rate = rt5640->sysclk / rl6231_get_pre_div(rt5640->regmap, + RT5640_ADDA_CLK1, RT5640_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rate); + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else + snd_soc_component_update_bits(component, RT5640_DMIC, RT5640_DMIC_CLK_MASK, + idx << RT5640_DMIC_CLK_SFT); + return idx; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + if (!rt5640->asrc_en) + return 0; + + return 1; +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5640_sto_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5640_STO_ADC_MIXER, + RT5640_M_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5640_STO_ADC_MIXER, + RT5640_M_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_sto_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5640_STO_ADC_MIXER, + RT5640_M_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5640_STO_ADC_MIXER, + RT5640_M_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_mono_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5640_MONO_ADC_MIXER, + RT5640_M_MONO_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5640_MONO_ADC_MIXER, + RT5640_M_MONO_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_mono_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5640_MONO_ADC_MIXER, + RT5640_M_MONO_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5640_MONO_ADC_MIXER, + RT5640_M_MONO_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5640_AD_DA_MIXER, + RT5640_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INF1 Switch", RT5640_AD_DA_MIXER, + RT5640_M_IF1_DAC_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5640_AD_DA_MIXER, + RT5640_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INF1 Switch", RT5640_AD_DA_MIXER, + RT5640_M_IF1_DAC_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_L2_SFT, 1, 1), + SOC_DAPM_SINGLE("ANC Switch", RT5640_STO_DAC_MIXER, + RT5640_M_ANC_DAC_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_R2_SFT, 1, 1), + SOC_DAPM_SINGLE("ANC Switch", RT5640_STO_DAC_MIXER, + RT5640_M_ANC_DAC_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5639_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5639_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_STO_DAC_MIXER, + RT5640_M_DAC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_mono_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_MONO_DAC_MIXER, + RT5640_M_DAC_L1_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_MONO_DAC_MIXER, + RT5640_M_DAC_L2_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_MONO_DAC_MIXER, + RT5640_M_DAC_R2_MONO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_mono_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_MONO_DAC_MIXER, + RT5640_M_DAC_R1_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_MONO_DAC_MIXER, + RT5640_M_DAC_R2_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_MONO_DAC_MIXER, + RT5640_M_DAC_L2_MONO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_dig_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_DIG_MIXER, + RT5640_M_STO_L_DAC_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_DIG_MIXER, + RT5640_M_DAC_L2_DAC_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_dig_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_DIG_MIXER, + RT5640_M_STO_R_DAC_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_DIG_MIXER, + RT5640_M_DAC_R2_DAC_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5640_rec_l_mix[] = { + SOC_DAPM_SINGLE("HPOL Switch", RT5640_REC_L2_MIXER, + RT5640_M_HP_L_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5640_REC_L2_MIXER, + RT5640_M_IN_L_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5640_REC_L2_MIXER, + RT5640_M_BST2_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5640_REC_L2_MIXER, + RT5640_M_BST4_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_REC_L2_MIXER, + RT5640_M_BST1_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXL Switch", RT5640_REC_L2_MIXER, + RT5640_M_OM_L_RM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_rec_r_mix[] = { + SOC_DAPM_SINGLE("HPOR Switch", RT5640_REC_R2_MIXER, + RT5640_M_HP_R_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5640_REC_R2_MIXER, + RT5640_M_IN_R_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5640_REC_R2_MIXER, + RT5640_M_BST2_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5640_REC_R2_MIXER, + RT5640_M_BST4_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_REC_R2_MIXER, + RT5640_M_BST1_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXR Switch", RT5640_REC_R2_MIXER, + RT5640_M_OM_R_RM_R_SFT, 1, 1), +}; + +/* Analog Output Mixer */ +static const struct snd_kcontrol_new rt5640_spk_l_mix[] = { + SOC_DAPM_SINGLE("REC MIXL Switch", RT5640_SPK_L_MIXER, + RT5640_M_RM_L_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5640_SPK_L_MIXER, + RT5640_M_IN_L_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_SPK_L_MIXER, + RT5640_M_DAC_L1_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_SPK_L_MIXER, + RT5640_M_DAC_L2_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXL Switch", RT5640_SPK_L_MIXER, + RT5640_M_OM_L_SM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_spk_r_mix[] = { + SOC_DAPM_SINGLE("REC MIXR Switch", RT5640_SPK_R_MIXER, + RT5640_M_RM_R_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5640_SPK_R_MIXER, + RT5640_M_IN_R_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_SPK_R_MIXER, + RT5640_M_DAC_R1_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_SPK_R_MIXER, + RT5640_M_DAC_R2_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXR Switch", RT5640_SPK_R_MIXER, + RT5640_M_OM_R_SM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_out_l_mix[] = { + SOC_DAPM_SINGLE("SPK MIXL Switch", RT5640_OUT_L3_MIXER, + RT5640_M_SM_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_OUT_L3_MIXER, + RT5640_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5640_OUT_L3_MIXER, + RT5640_M_IN_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXL Switch", RT5640_OUT_L3_MIXER, + RT5640_M_RM_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_OUT_L3_MIXER, + RT5640_M_DAC_R2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_OUT_L3_MIXER, + RT5640_M_DAC_L2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_OUT_L3_MIXER, + RT5640_M_DAC_L1_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_out_r_mix[] = { + SOC_DAPM_SINGLE("SPK MIXR Switch", RT5640_OUT_R3_MIXER, + RT5640_M_SM_L_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_BST4_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_BST1_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5640_OUT_R3_MIXER, + RT5640_M_IN_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXR Switch", RT5640_OUT_R3_MIXER, + RT5640_M_RM_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_DAC_L2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_DAC_R2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_DAC_R1_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5639_out_l_mix[] = { + SOC_DAPM_SINGLE("BST1 Switch", RT5640_OUT_L3_MIXER, + RT5640_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5640_OUT_L3_MIXER, + RT5640_M_IN_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXL Switch", RT5640_OUT_L3_MIXER, + RT5640_M_RM_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_OUT_L3_MIXER, + RT5640_M_DAC_L1_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5639_out_r_mix[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_BST4_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_BST1_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5640_OUT_R3_MIXER, + RT5640_M_IN_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXR Switch", RT5640_OUT_R3_MIXER, + RT5640_M_RM_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_OUT_R3_MIXER, + RT5640_M_DAC_R1_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_spo_l_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_SPO_L_MIXER, + RT5640_M_DAC_R1_SPM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_SPO_L_MIXER, + RT5640_M_DAC_L1_SPM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("SPKVOL R Switch", RT5640_SPO_L_MIXER, + RT5640_M_SV_R_SPM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("SPKVOL L Switch", RT5640_SPO_L_MIXER, + RT5640_M_SV_L_SPM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_SPO_L_MIXER, + RT5640_M_BST1_SPM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_spo_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_SPO_R_MIXER, + RT5640_M_DAC_R1_SPM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("SPKVOL R Switch", RT5640_SPO_R_MIXER, + RT5640_M_SV_R_SPM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_SPO_R_MIXER, + RT5640_M_BST1_SPM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_hpo_mix[] = { + SOC_DAPM_SINGLE("HPO MIX DAC2 Switch", RT5640_HPO_MIXER, + RT5640_M_DAC2_HM_SFT, 1, 1), + SOC_DAPM_SINGLE("HPO MIX DAC1 Switch", RT5640_HPO_MIXER, + RT5640_M_DAC1_HM_SFT, 1, 1), + SOC_DAPM_SINGLE("HPO MIX HPVOL Switch", RT5640_HPO_MIXER, + RT5640_M_HPVOL_HM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5639_hpo_mix[] = { + SOC_DAPM_SINGLE("HPO MIX DAC1 Switch", RT5640_HPO_MIXER, + RT5640_M_DAC1_HM_SFT, 1, 1), + SOC_DAPM_SINGLE("HPO MIX HPVOL Switch", RT5640_HPO_MIXER, + RT5640_M_HPVOL_HM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_lout_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5640_LOUT_MIXER, + RT5640_M_DAC_L1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5640_LOUT_MIXER, + RT5640_M_DAC_R1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL L Switch", RT5640_LOUT_MIXER, + RT5640_M_OV_L_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL R Switch", RT5640_LOUT_MIXER, + RT5640_M_OV_R_LM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5640_mono_mix[] = { + SOC_DAPM_SINGLE("DAC R2 Switch", RT5640_MONO_MIXER, + RT5640_M_DAC_R2_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5640_MONO_MIXER, + RT5640_M_DAC_L2_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL R Switch", RT5640_MONO_MIXER, + RT5640_M_OV_R_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL L Switch", RT5640_MONO_MIXER, + RT5640_M_OV_L_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5640_MONO_MIXER, + RT5640_M_BST1_MM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new spk_l_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5640_SPK_VOL, + RT5640_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new spk_r_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5640_SPK_VOL, + RT5640_R_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hp_l_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5640_HP_VOL, + RT5640_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hp_r_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5640_HP_VOL, + RT5640_R_MUTE_SFT, 1, 1); + +/* Stereo ADC source */ +static const char * const rt5640_stereo_adc1_src[] = { + "DIG MIX", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5640_stereo_adc1_enum, RT5640_STO_ADC_MIXER, + RT5640_ADC_1_SRC_SFT, rt5640_stereo_adc1_src); + +static const struct snd_kcontrol_new rt5640_sto_adc_1_mux = + SOC_DAPM_ENUM("Stereo ADC1 Mux", rt5640_stereo_adc1_enum); + +static const char * const rt5640_stereo_adc2_src[] = { + "DMIC1", "DMIC2", "DIG MIX" +}; + +static SOC_ENUM_SINGLE_DECL(rt5640_stereo_adc2_enum, RT5640_STO_ADC_MIXER, + RT5640_ADC_2_SRC_SFT, rt5640_stereo_adc2_src); + +static const struct snd_kcontrol_new rt5640_sto_adc_2_mux = + SOC_DAPM_ENUM("Stereo ADC2 Mux", rt5640_stereo_adc2_enum); + +/* Mono ADC source */ +static const char * const rt5640_mono_adc_l1_src[] = { + "Mono DAC MIXL", "ADCL" +}; + +static SOC_ENUM_SINGLE_DECL(rt5640_mono_adc_l1_enum, RT5640_MONO_ADC_MIXER, + RT5640_MONO_ADC_L1_SRC_SFT, rt5640_mono_adc_l1_src); + +static const struct snd_kcontrol_new rt5640_mono_adc_l1_mux = + SOC_DAPM_ENUM("Mono ADC1 left source", rt5640_mono_adc_l1_enum); + +static const char * const rt5640_mono_adc_l2_src[] = { + "DMIC L1", "DMIC L2", "Mono DAC MIXL" +}; + +static SOC_ENUM_SINGLE_DECL(rt5640_mono_adc_l2_enum, RT5640_MONO_ADC_MIXER, + RT5640_MONO_ADC_L2_SRC_SFT, rt5640_mono_adc_l2_src); + +static const struct snd_kcontrol_new rt5640_mono_adc_l2_mux = + SOC_DAPM_ENUM("Mono ADC2 left source", rt5640_mono_adc_l2_enum); + +static const char * const rt5640_mono_adc_r1_src[] = { + "Mono DAC MIXR", "ADCR" +}; + +static SOC_ENUM_SINGLE_DECL(rt5640_mono_adc_r1_enum, RT5640_MONO_ADC_MIXER, + RT5640_MONO_ADC_R1_SRC_SFT, rt5640_mono_adc_r1_src); + +static const struct snd_kcontrol_new rt5640_mono_adc_r1_mux = + SOC_DAPM_ENUM("Mono ADC1 right source", rt5640_mono_adc_r1_enum); + +static const char * const rt5640_mono_adc_r2_src[] = { + "DMIC R1", "DMIC R2", "Mono DAC MIXR" +}; + +static SOC_ENUM_SINGLE_DECL(rt5640_mono_adc_r2_enum, RT5640_MONO_ADC_MIXER, + RT5640_MONO_ADC_R2_SRC_SFT, rt5640_mono_adc_r2_src); + +static const struct snd_kcontrol_new rt5640_mono_adc_r2_mux = + SOC_DAPM_ENUM("Mono ADC2 right source", rt5640_mono_adc_r2_enum); + +/* DAC2 channel source */ +static const char * const rt5640_dac_l2_src[] = { + "IF2", "Base L/R" +}; + +static int rt5640_dac_l2_values[] = { + 0, + 3, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5640_dac_l2_enum, + RT5640_DSP_PATH2, RT5640_DAC_L2_SEL_SFT, + 0x3, rt5640_dac_l2_src, rt5640_dac_l2_values); + +static const struct snd_kcontrol_new rt5640_dac_l2_mux = + SOC_DAPM_ENUM("DAC2 left channel source", rt5640_dac_l2_enum); + +static const char * const rt5640_dac_r2_src[] = { + "IF2", +}; + +static int rt5640_dac_r2_values[] = { + 0, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5640_dac_r2_enum, + RT5640_DSP_PATH2, RT5640_DAC_R2_SEL_SFT, + 0x3, rt5640_dac_r2_src, rt5640_dac_r2_values); + +static const struct snd_kcontrol_new rt5640_dac_r2_mux = + SOC_DAPM_ENUM("DAC2 right channel source", rt5640_dac_r2_enum); + +/* digital interface and iis interface map */ +static const char * const rt5640_dai_iis_map[] = { + "1:1|2:2", "1:2|2:1", "1:1|2:1", "1:2|2:2" +}; + +static int rt5640_dai_iis_map_values[] = { + 0, + 5, + 6, + 7, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5640_dai_iis_map_enum, + RT5640_I2S1_SDP, RT5640_I2S_IF_SFT, + 0x7, rt5640_dai_iis_map, + rt5640_dai_iis_map_values); + +static const struct snd_kcontrol_new rt5640_dai_mux = + SOC_DAPM_ENUM("DAI select", rt5640_dai_iis_map_enum); + +/* SDI select */ +static const char * const rt5640_sdi_sel[] = { + "IF1", "IF2" +}; + +static SOC_ENUM_SINGLE_DECL(rt5640_sdi_sel_enum, RT5640_I2S2_SDP, + RT5640_I2S2_SDI_SFT, rt5640_sdi_sel); + +static const struct snd_kcontrol_new rt5640_sdi_mux = + SOC_DAPM_ENUM("SDI select", rt5640_sdi_sel_enum); + +static void hp_amp_power_on(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + /* depop parameters */ + regmap_update_bits(rt5640->regmap, RT5640_PR_BASE + + RT5640_CHPUMP_INT_REG1, 0x0700, 0x0200); + regmap_update_bits(rt5640->regmap, RT5640_DEPOP_M2, + RT5640_DEPOP_MASK, RT5640_DEPOP_MAN); + regmap_update_bits(rt5640->regmap, RT5640_DEPOP_M1, + RT5640_HP_CP_MASK | RT5640_HP_SG_MASK | RT5640_HP_CB_MASK, + RT5640_HP_CP_PU | RT5640_HP_SG_DIS | RT5640_HP_CB_PU); + regmap_write(rt5640->regmap, RT5640_PR_BASE + RT5640_HP_DCC_INT1, + 0x9f00); + /* headphone amp power on */ + regmap_update_bits(rt5640->regmap, RT5640_PWR_ANLG1, + RT5640_PWR_FV1 | RT5640_PWR_FV2, 0); + regmap_update_bits(rt5640->regmap, RT5640_PWR_ANLG1, + RT5640_PWR_HA, + RT5640_PWR_HA); + usleep_range(10000, 15000); + regmap_update_bits(rt5640->regmap, RT5640_PWR_ANLG1, + RT5640_PWR_FV1 | RT5640_PWR_FV2 , + RT5640_PWR_FV1 | RT5640_PWR_FV2); +} + +static void rt5640_pmu_depop(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + regmap_update_bits(rt5640->regmap, RT5640_DEPOP_M2, + RT5640_DEPOP_MASK | RT5640_DIG_DP_MASK, + RT5640_DEPOP_AUTO | RT5640_DIG_DP_EN); + regmap_update_bits(rt5640->regmap, RT5640_CHARGE_PUMP, + RT5640_PM_HP_MASK, RT5640_PM_HP_HV); + + regmap_update_bits(rt5640->regmap, RT5640_DEPOP_M3, + RT5640_CP_FQ1_MASK | RT5640_CP_FQ2_MASK | RT5640_CP_FQ3_MASK, + (RT5640_CP_FQ_192_KHZ << RT5640_CP_FQ1_SFT) | + (RT5640_CP_FQ_12_KHZ << RT5640_CP_FQ2_SFT) | + (RT5640_CP_FQ_192_KHZ << RT5640_CP_FQ3_SFT)); + + regmap_write(rt5640->regmap, RT5640_PR_BASE + + RT5640_MAMP_INT_REG2, 0x1c00); + regmap_update_bits(rt5640->regmap, RT5640_DEPOP_M1, + RT5640_HP_CP_MASK | RT5640_HP_SG_MASK, + RT5640_HP_CP_PD | RT5640_HP_SG_EN); + regmap_update_bits(rt5640->regmap, RT5640_PR_BASE + + RT5640_CHPUMP_INT_REG1, 0x0700, 0x0400); +} + +static int rt5640_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt5640_pmu_depop(component); + rt5640->hp_mute = 0; + break; + + case SND_SOC_DAPM_PRE_PMD: + rt5640->hp_mute = 1; + msleep(70); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5640_lout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + hp_amp_power_on(component); + snd_soc_component_update_bits(component, RT5640_PWR_ANLG1, + RT5640_PWR_LM, RT5640_PWR_LM); + snd_soc_component_update_bits(component, RT5640_OUTPUT, + RT5640_L_MUTE | RT5640_R_MUTE, 0); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5640_OUTPUT, + RT5640_L_MUTE | RT5640_R_MUTE, + RT5640_L_MUTE | RT5640_R_MUTE); + snd_soc_component_update_bits(component, RT5640_PWR_ANLG1, + RT5640_PWR_LM, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5640_hp_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + hp_amp_power_on(component); + break; + default: + return 0; + } + + return 0; +} + +static int rt5640_hp_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!rt5640->hp_mute) + msleep(80); + + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5640_dapm_widgets[] = { + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("Stereo Filter ASRC", 1, RT5640_ASRC_1, + 15, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 Filter ASRC", 1, RT5640_ASRC_1, + 12, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5640_ASRC_1, + 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC1 ASRC", 1, RT5640_ASRC_1, + 9, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC2 ASRC", 1, RT5640_ASRC_1, + 8, 0, NULL, 0), + + + /* Input Side */ + /* micbias */ + SND_SOC_DAPM_SUPPLY("LDO2", RT5640_PWR_ANLG1, + RT5640_PWR_LDO2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5640_PWR_ANLG2, + RT5640_PWR_MB1_BIT, 0, NULL, 0), + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + SND_SOC_DAPM_INPUT("IN3P"), + SND_SOC_DAPM_INPUT("IN3N"), + SND_SOC_DAPM_PGA("DMIC L1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC R1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC L2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC R2", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5640_DMIC, RT5640_DMIC_1_EN_SFT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC2 Power", RT5640_DMIC, RT5640_DMIC_2_EN_SFT, 0, + NULL, 0), + /* Boost */ + SND_SOC_DAPM_PGA("BST1", RT5640_PWR_ANLG2, + RT5640_PWR_BST1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST2", RT5640_PWR_ANLG2, + RT5640_PWR_BST4_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST3", RT5640_PWR_ANLG2, + RT5640_PWR_BST2_BIT, 0, NULL, 0), + /* Input Volume */ + SND_SOC_DAPM_PGA("INL VOL", RT5640_PWR_VOL, + RT5640_PWR_IN_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR VOL", RT5640_PWR_VOL, + RT5640_PWR_IN_R_BIT, 0, NULL, 0), + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIXL", RT5640_PWR_MIXER, RT5640_PWR_RM_L_BIT, 0, + rt5640_rec_l_mix, ARRAY_SIZE(rt5640_rec_l_mix)), + SND_SOC_DAPM_MIXER("RECMIXR", RT5640_PWR_MIXER, RT5640_PWR_RM_R_BIT, 0, + rt5640_rec_r_mix, ARRAY_SIZE(rt5640_rec_r_mix)), + /* ADCs */ + SND_SOC_DAPM_ADC("ADC L", NULL, RT5640_PWR_DIG1, + RT5640_PWR_ADC_L_BIT, 0), + SND_SOC_DAPM_ADC("ADC R", NULL, RT5640_PWR_DIG1, + RT5640_PWR_ADC_R_BIT, 0), + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_sto_adc_2_mux), + SND_SOC_DAPM_MUX("Stereo ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_sto_adc_2_mux), + SND_SOC_DAPM_MUX("Stereo ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_sto_adc_1_mux), + SND_SOC_DAPM_MUX("Stereo ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_sto_adc_1_mux), + SND_SOC_DAPM_MUX("Mono ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_mono_adc_l2_mux), + SND_SOC_DAPM_MUX("Mono ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_mono_adc_l1_mux), + SND_SOC_DAPM_MUX("Mono ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_mono_adc_r1_mux), + SND_SOC_DAPM_MUX("Mono ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5640_mono_adc_r2_mux), + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("Stereo Filter", RT5640_PWR_DIG2, + RT5640_PWR_ADC_SF_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5640_sto_adc_l_mix, ARRAY_SIZE(rt5640_sto_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5640_sto_adc_r_mix, ARRAY_SIZE(rt5640_sto_adc_r_mix)), + SND_SOC_DAPM_SUPPLY("Mono Left Filter", RT5640_PWR_DIG2, + RT5640_PWR_ADC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5640_mono_adc_l_mix, ARRAY_SIZE(rt5640_mono_adc_l_mix)), + SND_SOC_DAPM_SUPPLY("Mono Right Filter", RT5640_PWR_DIG2, + RT5640_PWR_ADC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5640_mono_adc_r_mix, ARRAY_SIZE(rt5640_mono_adc_r_mix)), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5640_PWR_DIG1, + RT5640_PWR_I2S1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2", RT5640_PWR_DIG1, + RT5640_PWR_I2S2_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("DAI1 RX Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("DAI1 TX Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("DAI1 IF1 Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("DAI1 IF2 Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("SDI1 TX Mux", SND_SOC_NOPM, 0, 0, &rt5640_sdi_mux), + SND_SOC_DAPM_MUX("DAI2 RX Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("DAI2 TX Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("DAI2 IF1 Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("DAI2 IF2 Mux", SND_SOC_NOPM, 0, 0, &rt5640_dai_mux), + SND_SOC_DAPM_MUX("SDI2 TX Mux", SND_SOC_NOPM, 0, 0, &rt5640_sdi_mux), + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5640_dac_l_mix, ARRAY_SIZE(rt5640_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5640_dac_r_mix, ARRAY_SIZE(rt5640_dac_r_mix)), + + /* DAC Mixer */ + SND_SOC_DAPM_MIXER("Mono DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5640_mono_dac_l_mix, ARRAY_SIZE(rt5640_mono_dac_l_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5640_mono_dac_r_mix, ARRAY_SIZE(rt5640_mono_dac_r_mix)), + SND_SOC_DAPM_MIXER("DIG MIXL", SND_SOC_NOPM, 0, 0, + rt5640_dig_l_mix, ARRAY_SIZE(rt5640_dig_l_mix)), + SND_SOC_DAPM_MIXER("DIG MIXR", SND_SOC_NOPM, 0, 0, + rt5640_dig_r_mix, ARRAY_SIZE(rt5640_dig_r_mix)), + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_SUPPLY("DAC L1 Power", RT5640_PWR_DIG1, + RT5640_PWR_DAC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC R1 Power", RT5640_PWR_DIG1, + RT5640_PWR_DAC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC L2 Power", RT5640_PWR_DIG1, + RT5640_PWR_DAC_L2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC R2 Power", RT5640_PWR_DIG1, + RT5640_PWR_DAC_R2_BIT, 0, NULL, 0), + /* SPK/OUT Mixer */ + SND_SOC_DAPM_MIXER("SPK MIXL", RT5640_PWR_MIXER, RT5640_PWR_SM_L_BIT, + 0, rt5640_spk_l_mix, ARRAY_SIZE(rt5640_spk_l_mix)), + SND_SOC_DAPM_MIXER("SPK MIXR", RT5640_PWR_MIXER, RT5640_PWR_SM_R_BIT, + 0, rt5640_spk_r_mix, ARRAY_SIZE(rt5640_spk_r_mix)), + /* Ouput Volume */ + SND_SOC_DAPM_PGA("SPKVOL L", RT5640_PWR_VOL, + RT5640_PWR_SV_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPKVOL R", RT5640_PWR_VOL, + RT5640_PWR_SV_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUTVOL L", RT5640_PWR_VOL, + RT5640_PWR_OV_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("OUTVOL R", RT5640_PWR_VOL, + RT5640_PWR_OV_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPOVOL L", RT5640_PWR_VOL, + RT5640_PWR_HV_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPOVOL R", RT5640_PWR_VOL, + RT5640_PWR_HV_R_BIT, 0, NULL, 0), + /* SPO/HPO/LOUT/Mono Mixer */ + SND_SOC_DAPM_MIXER("SPOL MIX", SND_SOC_NOPM, 0, + 0, rt5640_spo_l_mix, ARRAY_SIZE(rt5640_spo_l_mix)), + SND_SOC_DAPM_MIXER("SPOR MIX", SND_SOC_NOPM, 0, + 0, rt5640_spo_r_mix, ARRAY_SIZE(rt5640_spo_r_mix)), + SND_SOC_DAPM_MIXER("LOUT MIX", SND_SOC_NOPM, 0, 0, + rt5640_lout_mix, ARRAY_SIZE(rt5640_lout_mix)), + SND_SOC_DAPM_SUPPLY_S("Improve HP Amp Drv", 1, SND_SOC_NOPM, + 0, 0, rt5640_hp_power_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, + rt5640_hp_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT amp", 1, SND_SOC_NOPM, 0, 0, + rt5640_lout_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("HP L Amp", RT5640_PWR_ANLG1, + RT5640_PWR_HP_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HP R Amp", RT5640_PWR_ANLG1, + RT5640_PWR_HP_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Improve SPK Amp Drv", RT5640_PWR_DIG1, + RT5640_PWR_CLS_D_BIT, 0, NULL, 0), + + /* Output Switch */ + SND_SOC_DAPM_SWITCH("Speaker L Playback", SND_SOC_NOPM, 0, 0, + &spk_l_enable_control), + SND_SOC_DAPM_SWITCH("Speaker R Playback", SND_SOC_NOPM, 0, 0, + &spk_r_enable_control), + SND_SOC_DAPM_SWITCH("HP L Playback", SND_SOC_NOPM, 0, 0, + &hp_l_enable_control), + SND_SOC_DAPM_SWITCH("HP R Playback", SND_SOC_NOPM, 0, 0, + &hp_r_enable_control), + SND_SOC_DAPM_POST("HP Post", rt5640_hp_post_event), + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("SPOLP"), + SND_SOC_DAPM_OUTPUT("SPOLN"), + SND_SOC_DAPM_OUTPUT("SPORP"), + SND_SOC_DAPM_OUTPUT("SPORN"), + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), +}; + +static const struct snd_soc_dapm_widget rt5640_specific_dapm_widgets[] = { + /* Audio DSP */ + SND_SOC_DAPM_PGA("Audio DSP", SND_SOC_NOPM, 0, 0, NULL, 0), + /* ANC */ + SND_SOC_DAPM_PGA("ANC", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DAC2 channel Mux */ + SND_SOC_DAPM_MUX("DAC L2 Mux", SND_SOC_NOPM, 0, 0, &rt5640_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Mux", SND_SOC_NOPM, 0, 0, &rt5640_dac_r2_mux), + + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5640_sto_dac_l_mix, ARRAY_SIZE(rt5640_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5640_sto_dac_r_mix, ARRAY_SIZE(rt5640_sto_dac_r_mix)), + + SND_SOC_DAPM_DAC("DAC R2", NULL, SND_SOC_NOPM, 0, + 0), + SND_SOC_DAPM_DAC("DAC L2", NULL, SND_SOC_NOPM, 0, + 0), + + SND_SOC_DAPM_MIXER("OUT MIXL", RT5640_PWR_MIXER, RT5640_PWR_OM_L_BIT, + 0, rt5640_out_l_mix, ARRAY_SIZE(rt5640_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5640_PWR_MIXER, RT5640_PWR_OM_R_BIT, + 0, rt5640_out_r_mix, ARRAY_SIZE(rt5640_out_r_mix)), + + SND_SOC_DAPM_MIXER("HPO MIX L", SND_SOC_NOPM, 0, 0, + rt5640_hpo_mix, ARRAY_SIZE(rt5640_hpo_mix)), + SND_SOC_DAPM_MIXER("HPO MIX R", SND_SOC_NOPM, 0, 0, + rt5640_hpo_mix, ARRAY_SIZE(rt5640_hpo_mix)), + + SND_SOC_DAPM_MIXER("Mono MIX", RT5640_PWR_ANLG1, RT5640_PWR_MM_BIT, 0, + rt5640_mono_mix, ARRAY_SIZE(rt5640_mono_mix)), + SND_SOC_DAPM_SUPPLY("Improve MONO Amp Drv", RT5640_PWR_ANLG1, + RT5640_PWR_MA_BIT, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("MONOP"), + SND_SOC_DAPM_OUTPUT("MONON"), +}; + +static const struct snd_soc_dapm_widget rt5639_specific_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5639_sto_dac_l_mix, ARRAY_SIZE(rt5639_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5639_sto_dac_r_mix, ARRAY_SIZE(rt5639_sto_dac_r_mix)), + + SND_SOC_DAPM_MIXER("OUT MIXL", RT5640_PWR_MIXER, RT5640_PWR_OM_L_BIT, + 0, rt5639_out_l_mix, ARRAY_SIZE(rt5639_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5640_PWR_MIXER, RT5640_PWR_OM_R_BIT, + 0, rt5639_out_r_mix, ARRAY_SIZE(rt5639_out_r_mix)), + + SND_SOC_DAPM_MIXER("HPO MIX L", SND_SOC_NOPM, 0, 0, + rt5639_hpo_mix, ARRAY_SIZE(rt5639_hpo_mix)), + SND_SOC_DAPM_MIXER("HPO MIX R", SND_SOC_NOPM, 0, 0, + rt5639_hpo_mix, ARRAY_SIZE(rt5639_hpo_mix)), +}; + +static const struct snd_soc_dapm_route rt5640_dapm_routes[] = { + { "I2S1", NULL, "Stereo Filter ASRC", is_using_asrc }, + { "I2S2", NULL, "I2S2 ASRC", is_using_asrc }, + { "I2S2", NULL, "I2S2 Filter ASRC", is_using_asrc }, + { "DMIC1", NULL, "DMIC1 ASRC", is_using_asrc }, + { "DMIC2", NULL, "DMIC2 ASRC", is_using_asrc }, + + {"IN1P", NULL, "LDO2"}, + {"IN2P", NULL, "LDO2"}, + {"IN3P", NULL, "LDO2"}, + + {"DMIC L1", NULL, "DMIC1"}, + {"DMIC R1", NULL, "DMIC1"}, + {"DMIC L2", NULL, "DMIC2"}, + {"DMIC R2", NULL, "DMIC2"}, + + {"BST1", NULL, "IN1P"}, + {"BST1", NULL, "IN1N"}, + {"BST2", NULL, "IN2P"}, + {"BST2", NULL, "IN2N"}, + {"BST3", NULL, "IN3P"}, + {"BST3", NULL, "IN3N"}, + + {"INL VOL", NULL, "IN2P"}, + {"INR VOL", NULL, "IN2N"}, + + {"RECMIXL", "HPOL Switch", "HPOL"}, + {"RECMIXL", "INL Switch", "INL VOL"}, + {"RECMIXL", "BST3 Switch", "BST3"}, + {"RECMIXL", "BST2 Switch", "BST2"}, + {"RECMIXL", "BST1 Switch", "BST1"}, + {"RECMIXL", "OUT MIXL Switch", "OUT MIXL"}, + + {"RECMIXR", "HPOR Switch", "HPOR"}, + {"RECMIXR", "INR Switch", "INR VOL"}, + {"RECMIXR", "BST3 Switch", "BST3"}, + {"RECMIXR", "BST2 Switch", "BST2"}, + {"RECMIXR", "BST1 Switch", "BST1"}, + {"RECMIXR", "OUT MIXR Switch", "OUT MIXR"}, + + {"ADC L", NULL, "RECMIXL"}, + {"ADC R", NULL, "RECMIXR"}, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC L1", NULL, "DMIC1 Power"}, + {"DMIC R1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC1 Power"}, + {"DMIC L2", NULL, "DMIC CLK"}, + {"DMIC L2", NULL, "DMIC2 Power"}, + {"DMIC R2", NULL, "DMIC CLK"}, + {"DMIC R2", NULL, "DMIC2 Power"}, + + {"Stereo ADC L2 Mux", "DMIC1", "DMIC L1"}, + {"Stereo ADC L2 Mux", "DMIC2", "DMIC L2"}, + {"Stereo ADC L2 Mux", "DIG MIX", "DIG MIXL"}, + {"Stereo ADC L1 Mux", "ADC", "ADC L"}, + {"Stereo ADC L1 Mux", "DIG MIX", "DIG MIXL"}, + + {"Stereo ADC R1 Mux", "ADC", "ADC R"}, + {"Stereo ADC R1 Mux", "DIG MIX", "DIG MIXR"}, + {"Stereo ADC R2 Mux", "DMIC1", "DMIC R1"}, + {"Stereo ADC R2 Mux", "DMIC2", "DMIC R2"}, + {"Stereo ADC R2 Mux", "DIG MIX", "DIG MIXR"}, + + {"Mono ADC L2 Mux", "DMIC L1", "DMIC L1"}, + {"Mono ADC L2 Mux", "DMIC L2", "DMIC L2"}, + {"Mono ADC L2 Mux", "Mono DAC MIXL", "Mono DAC MIXL"}, + {"Mono ADC L1 Mux", "Mono DAC MIXL", "Mono DAC MIXL"}, + {"Mono ADC L1 Mux", "ADCL", "ADC L"}, + + {"Mono ADC R1 Mux", "Mono DAC MIXR", "Mono DAC MIXR"}, + {"Mono ADC R1 Mux", "ADCR", "ADC R"}, + {"Mono ADC R2 Mux", "DMIC R1", "DMIC R1"}, + {"Mono ADC R2 Mux", "DMIC R2", "DMIC R2"}, + {"Mono ADC R2 Mux", "Mono DAC MIXR", "Mono DAC MIXR"}, + + {"Stereo ADC MIXL", "ADC1 Switch", "Stereo ADC L1 Mux"}, + {"Stereo ADC MIXL", "ADC2 Switch", "Stereo ADC L2 Mux"}, + {"Stereo ADC MIXL", NULL, "Stereo Filter"}, + + {"Stereo ADC MIXR", "ADC1 Switch", "Stereo ADC R1 Mux"}, + {"Stereo ADC MIXR", "ADC2 Switch", "Stereo ADC R2 Mux"}, + {"Stereo ADC MIXR", NULL, "Stereo Filter"}, + + {"Mono ADC MIXL", "ADC1 Switch", "Mono ADC L1 Mux"}, + {"Mono ADC MIXL", "ADC2 Switch", "Mono ADC L2 Mux"}, + {"Mono ADC MIXL", NULL, "Mono Left Filter"}, + + {"Mono ADC MIXR", "ADC1 Switch", "Mono ADC R1 Mux"}, + {"Mono ADC MIXR", "ADC2 Switch", "Mono ADC R2 Mux"}, + {"Mono ADC MIXR", NULL, "Mono Right Filter"}, + + {"IF2 ADC L", NULL, "Mono ADC MIXL"}, + {"IF2 ADC R", NULL, "Mono ADC MIXR"}, + {"IF1 ADC L", NULL, "Stereo ADC MIXL"}, + {"IF1 ADC R", NULL, "Stereo ADC MIXR"}, + + {"IF1 ADC", NULL, "I2S1"}, + {"IF1 ADC", NULL, "IF1 ADC L"}, + {"IF1 ADC", NULL, "IF1 ADC R"}, + {"IF2 ADC", NULL, "I2S2"}, + {"IF2 ADC", NULL, "IF2 ADC L"}, + {"IF2 ADC", NULL, "IF2 ADC R"}, + + {"DAI1 TX Mux", "1:1|2:2", "IF1 ADC"}, + {"DAI1 TX Mux", "1:2|2:1", "IF2 ADC"}, + {"DAI1 IF1 Mux", "1:1|2:1", "IF1 ADC"}, + {"DAI1 IF2 Mux", "1:1|2:1", "IF2 ADC"}, + {"SDI1 TX Mux", "IF1", "DAI1 IF1 Mux"}, + {"SDI1 TX Mux", "IF2", "DAI1 IF2 Mux"}, + + {"DAI2 TX Mux", "1:2|2:1", "IF1 ADC"}, + {"DAI2 TX Mux", "1:1|2:2", "IF2 ADC"}, + {"DAI2 IF1 Mux", "1:2|2:2", "IF1 ADC"}, + {"DAI2 IF2 Mux", "1:2|2:2", "IF2 ADC"}, + {"SDI2 TX Mux", "IF1", "DAI2 IF1 Mux"}, + {"SDI2 TX Mux", "IF2", "DAI2 IF2 Mux"}, + + {"AIF1TX", NULL, "DAI1 TX Mux"}, + {"AIF1TX", NULL, "SDI1 TX Mux"}, + {"AIF2TX", NULL, "DAI2 TX Mux"}, + {"AIF2TX", NULL, "SDI2 TX Mux"}, + + {"DAI1 RX Mux", "1:1|2:2", "AIF1RX"}, + {"DAI1 RX Mux", "1:1|2:1", "AIF1RX"}, + {"DAI1 RX Mux", "1:2|2:1", "AIF2RX"}, + {"DAI1 RX Mux", "1:2|2:2", "AIF2RX"}, + + {"DAI2 RX Mux", "1:2|2:1", "AIF1RX"}, + {"DAI2 RX Mux", "1:1|2:1", "AIF1RX"}, + {"DAI2 RX Mux", "1:1|2:2", "AIF2RX"}, + {"DAI2 RX Mux", "1:2|2:2", "AIF2RX"}, + + {"IF1 DAC", NULL, "I2S1"}, + {"IF1 DAC", NULL, "DAI1 RX Mux"}, + {"IF2 DAC", NULL, "I2S2"}, + {"IF2 DAC", NULL, "DAI2 RX Mux"}, + + {"IF1 DAC L", NULL, "IF1 DAC"}, + {"IF1 DAC R", NULL, "IF1 DAC"}, + {"IF2 DAC L", NULL, "IF2 DAC"}, + {"IF2 DAC R", NULL, "IF2 DAC"}, + + {"DAC MIXL", "Stereo ADC Switch", "Stereo ADC MIXL"}, + {"DAC MIXL", "INF1 Switch", "IF1 DAC L"}, + {"DAC MIXL", NULL, "DAC L1 Power"}, + {"DAC MIXR", "Stereo ADC Switch", "Stereo ADC MIXR"}, + {"DAC MIXR", "INF1 Switch", "IF1 DAC R"}, + {"DAC MIXR", NULL, "DAC R1 Power"}, + + {"Stereo DAC MIXL", "DAC L1 Switch", "DAC MIXL"}, + {"Stereo DAC MIXR", "DAC R1 Switch", "DAC MIXR"}, + + {"Mono DAC MIXL", "DAC L1 Switch", "DAC MIXL"}, + {"Mono DAC MIXR", "DAC R1 Switch", "DAC MIXR"}, + + {"DIG MIXL", "DAC L1 Switch", "DAC MIXL"}, + {"DIG MIXR", "DAC R1 Switch", "DAC MIXR"}, + + {"DAC L1", NULL, "Stereo DAC MIXL"}, + {"DAC L1", NULL, "DAC L1 Power"}, + {"DAC R1", NULL, "Stereo DAC MIXR"}, + {"DAC R1", NULL, "DAC R1 Power"}, + + {"SPK MIXL", "REC MIXL Switch", "RECMIXL"}, + {"SPK MIXL", "INL Switch", "INL VOL"}, + {"SPK MIXL", "DAC L1 Switch", "DAC L1"}, + {"SPK MIXL", "OUT MIXL Switch", "OUT MIXL"}, + {"SPK MIXR", "REC MIXR Switch", "RECMIXR"}, + {"SPK MIXR", "INR Switch", "INR VOL"}, + {"SPK MIXR", "DAC R1 Switch", "DAC R1"}, + {"SPK MIXR", "OUT MIXR Switch", "OUT MIXR"}, + + {"OUT MIXL", "BST1 Switch", "BST1"}, + {"OUT MIXL", "INL Switch", "INL VOL"}, + {"OUT MIXL", "REC MIXL Switch", "RECMIXL"}, + {"OUT MIXL", "DAC L1 Switch", "DAC L1"}, + + {"OUT MIXR", "BST2 Switch", "BST2"}, + {"OUT MIXR", "BST1 Switch", "BST1"}, + {"OUT MIXR", "INR Switch", "INR VOL"}, + {"OUT MIXR", "REC MIXR Switch", "RECMIXR"}, + {"OUT MIXR", "DAC R1 Switch", "DAC R1"}, + + {"SPKVOL L", NULL, "SPK MIXL"}, + {"SPKVOL R", NULL, "SPK MIXR"}, + {"HPOVOL L", NULL, "OUT MIXL"}, + {"HPOVOL R", NULL, "OUT MIXR"}, + {"OUTVOL L", NULL, "OUT MIXL"}, + {"OUTVOL R", NULL, "OUT MIXR"}, + + {"SPOL MIX", "DAC R1 Switch", "DAC R1"}, + {"SPOL MIX", "DAC L1 Switch", "DAC L1"}, + {"SPOL MIX", "SPKVOL R Switch", "SPKVOL R"}, + {"SPOL MIX", "SPKVOL L Switch", "SPKVOL L"}, + {"SPOL MIX", "BST1 Switch", "BST1"}, + {"SPOR MIX", "DAC R1 Switch", "DAC R1"}, + {"SPOR MIX", "SPKVOL R Switch", "SPKVOL R"}, + {"SPOR MIX", "BST1 Switch", "BST1"}, + + {"HPO MIX L", "HPO MIX DAC1 Switch", "DAC L1"}, + {"HPO MIX L", "HPO MIX HPVOL Switch", "HPOVOL L"}, + {"HPO MIX L", NULL, "HP L Amp"}, + {"HPO MIX R", "HPO MIX DAC1 Switch", "DAC R1"}, + {"HPO MIX R", "HPO MIX HPVOL Switch", "HPOVOL R"}, + {"HPO MIX R", NULL, "HP R Amp"}, + + {"LOUT MIX", "DAC L1 Switch", "DAC L1"}, + {"LOUT MIX", "DAC R1 Switch", "DAC R1"}, + {"LOUT MIX", "OUTVOL L Switch", "OUTVOL L"}, + {"LOUT MIX", "OUTVOL R Switch", "OUTVOL R"}, + + {"HP Amp", NULL, "HPO MIX L"}, + {"HP Amp", NULL, "HPO MIX R"}, + + {"Speaker L Playback", "Switch", "SPOL MIX"}, + {"Speaker R Playback", "Switch", "SPOR MIX"}, + {"SPOLP", NULL, "Speaker L Playback"}, + {"SPOLN", NULL, "Speaker L Playback"}, + {"SPORP", NULL, "Speaker R Playback"}, + {"SPORN", NULL, "Speaker R Playback"}, + + {"SPOLP", NULL, "Improve SPK Amp Drv"}, + {"SPOLN", NULL, "Improve SPK Amp Drv"}, + {"SPORP", NULL, "Improve SPK Amp Drv"}, + {"SPORN", NULL, "Improve SPK Amp Drv"}, + + {"HPOL", NULL, "Improve HP Amp Drv"}, + {"HPOR", NULL, "Improve HP Amp Drv"}, + + {"HP L Playback", "Switch", "HP Amp"}, + {"HP R Playback", "Switch", "HP Amp"}, + {"HPOL", NULL, "HP L Playback"}, + {"HPOR", NULL, "HP R Playback"}, + + {"LOUT amp", NULL, "LOUT MIX"}, + {"LOUTL", NULL, "LOUT amp"}, + {"LOUTR", NULL, "LOUT amp"}, +}; + +static const struct snd_soc_dapm_route rt5640_specific_dapm_routes[] = { + {"ANC", NULL, "Stereo ADC MIXL"}, + {"ANC", NULL, "Stereo ADC MIXR"}, + + {"Audio DSP", NULL, "DAC MIXL"}, + {"Audio DSP", NULL, "DAC MIXR"}, + + {"DAC L2 Mux", "IF2", "IF2 DAC L"}, + {"DAC L2 Mux", "Base L/R", "Audio DSP"}, + {"DAC L2 Mux", NULL, "DAC L2 Power"}, + {"DAC R2 Mux", "IF2", "IF2 DAC R"}, + {"DAC R2 Mux", NULL, "DAC R2 Power"}, + + {"Stereo DAC MIXL", "DAC L2 Switch", "DAC L2 Mux"}, + {"Stereo DAC MIXL", "ANC Switch", "ANC"}, + {"Stereo DAC MIXR", "DAC R2 Switch", "DAC R2 Mux"}, + {"Stereo DAC MIXR", "ANC Switch", "ANC"}, + + {"Mono DAC MIXL", "DAC L2 Switch", "DAC L2 Mux"}, + {"Mono DAC MIXL", "DAC R2 Switch", "DAC R2 Mux"}, + + {"Mono DAC MIXR", "DAC R2 Switch", "DAC R2 Mux"}, + {"Mono DAC MIXR", "DAC L2 Switch", "DAC L2 Mux"}, + + {"DIG MIXR", "DAC R2 Switch", "DAC R2 Mux"}, + {"DIG MIXL", "DAC L2 Switch", "DAC L2 Mux"}, + + {"DAC L2", NULL, "Mono DAC MIXL"}, + {"DAC L2", NULL, "DAC L2 Power"}, + {"DAC R2", NULL, "Mono DAC MIXR"}, + {"DAC R2", NULL, "DAC R2 Power"}, + + {"SPK MIXL", "DAC L2 Switch", "DAC L2"}, + {"SPK MIXR", "DAC R2 Switch", "DAC R2"}, + + {"OUT MIXL", "SPK MIXL Switch", "SPK MIXL"}, + {"OUT MIXR", "SPK MIXR Switch", "SPK MIXR"}, + + {"OUT MIXL", "DAC R2 Switch", "DAC R2"}, + {"OUT MIXL", "DAC L2 Switch", "DAC L2"}, + + {"OUT MIXR", "DAC L2 Switch", "DAC L2"}, + {"OUT MIXR", "DAC R2 Switch", "DAC R2"}, + + {"HPO MIX L", "HPO MIX DAC2 Switch", "DAC L2"}, + {"HPO MIX R", "HPO MIX DAC2 Switch", "DAC R2"}, + + {"Mono MIX", "DAC R2 Switch", "DAC R2"}, + {"Mono MIX", "DAC L2 Switch", "DAC L2"}, + {"Mono MIX", "OUTVOL R Switch", "OUTVOL R"}, + {"Mono MIX", "OUTVOL L Switch", "OUTVOL L"}, + {"Mono MIX", "BST1 Switch", "BST1"}, + + {"MONOP", NULL, "Mono MIX"}, + {"MONON", NULL, "Mono MIX"}, + {"MONOP", NULL, "Improve MONO Amp Drv"}, +}; + +static const struct snd_soc_dapm_route rt5639_specific_dapm_routes[] = { + {"Stereo DAC MIXL", "DAC L2 Switch", "IF2 DAC L"}, + {"Stereo DAC MIXR", "DAC R2 Switch", "IF2 DAC R"}, + + {"Mono DAC MIXL", "DAC L2 Switch", "IF2 DAC L"}, + {"Mono DAC MIXL", "DAC R2 Switch", "IF2 DAC R"}, + + {"Mono DAC MIXR", "DAC R2 Switch", "IF2 DAC R"}, + {"Mono DAC MIXR", "DAC L2 Switch", "IF2 DAC L"}, + + {"DIG MIXL", "DAC L2 Switch", "IF2 DAC L"}, + {"DIG MIXR", "DAC R2 Switch", "IF2 DAC R"}, + + {"IF2 DAC L", NULL, "DAC L2 Power"}, + {"IF2 DAC R", NULL, "DAC R2 Power"}, +}; + +static int get_sdp_info(struct snd_soc_component *component, int dai_id) +{ + int ret = 0, val; + + if (component == NULL) + return -EINVAL; + + val = snd_soc_component_read32(component, RT5640_I2S1_SDP); + val = (val & RT5640_I2S_IF_MASK) >> RT5640_I2S_IF_SFT; + switch (dai_id) { + case RT5640_AIF1: + switch (val) { + case RT5640_IF_123: + case RT5640_IF_132: + ret |= RT5640_U_IF1; + break; + case RT5640_IF_113: + ret |= RT5640_U_IF1; + /* fall through */ + case RT5640_IF_312: + case RT5640_IF_213: + ret |= RT5640_U_IF2; + break; + } + break; + + case RT5640_AIF2: + switch (val) { + case RT5640_IF_231: + case RT5640_IF_213: + ret |= RT5640_U_IF1; + break; + case RT5640_IF_223: + ret |= RT5640_U_IF1; + /* fall through */ + case RT5640_IF_123: + case RT5640_IF_321: + ret |= RT5640_U_IF2; + break; + } + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rt5640_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int dai_sel, pre_div, bclk_ms, frame_size; + + rt5640->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5640->sysclk, rt5640->lrck[dai->id]); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting %d for DAI %d\n", + rt5640->lrck[dai->id], dai->id); + return -EINVAL; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return frame_size; + } + if (frame_size > 32) + bclk_ms = 1; + else + bclk_ms = 0; + rt5640->bclk[dai->id] = rt5640->lrck[dai->id] * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5640->bclk[dai->id], rt5640->lrck[dai->id]); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len |= RT5640_I2S_DL_20; + break; + case 24: + val_len |= RT5640_I2S_DL_24; + break; + case 8: + val_len |= RT5640_I2S_DL_8; + break; + default: + return -EINVAL; + } + + dai_sel = get_sdp_info(component, dai->id); + if (dai_sel < 0) { + dev_err(component->dev, "Failed to get sdp info: %d\n", dai_sel); + return -EINVAL; + } + if (dai_sel & RT5640_U_IF1) { + mask_clk = RT5640_I2S_BCLK_MS1_MASK | RT5640_I2S_PD1_MASK; + val_clk = bclk_ms << RT5640_I2S_BCLK_MS1_SFT | + pre_div << RT5640_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5640_I2S1_SDP, + RT5640_I2S_DL_MASK, val_len); + snd_soc_component_update_bits(component, RT5640_ADDA_CLK1, mask_clk, val_clk); + } + if (dai_sel & RT5640_U_IF2) { + mask_clk = RT5640_I2S_BCLK_MS2_MASK | RT5640_I2S_PD2_MASK; + val_clk = bclk_ms << RT5640_I2S_BCLK_MS2_SFT | + pre_div << RT5640_I2S_PD2_SFT; + snd_soc_component_update_bits(component, RT5640_I2S2_SDP, + RT5640_I2S_DL_MASK, val_len); + snd_soc_component_update_bits(component, RT5640_ADDA_CLK1, mask_clk, val_clk); + } + + return 0; +} + +static int rt5640_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + int dai_sel; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5640->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5640_I2S_MS_S; + rt5640->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5640_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5640_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5640_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5640_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + dai_sel = get_sdp_info(component, dai->id); + if (dai_sel < 0) { + dev_err(component->dev, "Failed to get sdp info: %d\n", dai_sel); + return -EINVAL; + } + if (dai_sel & RT5640_U_IF1) { + snd_soc_component_update_bits(component, RT5640_I2S1_SDP, + RT5640_I2S_MS_MASK | RT5640_I2S_BP_MASK | + RT5640_I2S_DF_MASK, reg_val); + } + if (dai_sel & RT5640_U_IF2) { + snd_soc_component_update_bits(component, RT5640_I2S2_SDP, + RT5640_I2S_MS_MASK | RT5640_I2S_BP_MASK | + RT5640_I2S_DF_MASK, reg_val); + } + + return 0; +} + +static int rt5640_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + unsigned int pll_bit = 0; + + if (freq == rt5640->sysclk && clk_id == rt5640->sysclk_src) + return 0; + + switch (clk_id) { + case RT5640_SCLK_S_MCLK: + reg_val |= RT5640_SCLK_SRC_MCLK; + break; + case RT5640_SCLK_S_PLL1: + reg_val |= RT5640_SCLK_SRC_PLL1; + pll_bit |= RT5640_PWR_PLL; + break; + case RT5640_SCLK_S_RCCLK: + reg_val |= RT5640_SCLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5640_PWR_ANLG2, + RT5640_PWR_PLL, pll_bit); + snd_soc_component_update_bits(component, RT5640_GLB_CLK, + RT5640_SCLK_SRC_MASK, reg_val); + rt5640->sysclk = freq; + rt5640->sysclk_src = clk_id; + + dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + return 0; +} + +static int rt5640_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5640->pll_src && freq_in == rt5640->pll_in && + freq_out == rt5640->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5640->pll_in = 0; + rt5640->pll_out = 0; + snd_soc_component_update_bits(component, RT5640_GLB_CLK, + RT5640_SCLK_SRC_MASK, RT5640_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5640_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5640_GLB_CLK, + RT5640_PLL1_SRC_MASK, RT5640_PLL1_SRC_MCLK); + break; + case RT5640_PLL1_S_BCLK1: + snd_soc_component_update_bits(component, RT5640_GLB_CLK, + RT5640_PLL1_SRC_MASK, RT5640_PLL1_SRC_BCLK1); + break; + case RT5640_PLL1_S_BCLK2: + snd_soc_component_update_bits(component, RT5640_GLB_CLK, + RT5640_PLL1_SRC_MASK, RT5640_PLL1_SRC_BCLK2); + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5640_PLL_CTRL1, + pll_code.n_code << RT5640_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5640_PLL_CTRL2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5640_PLL_M_SFT | + pll_code.m_bp << RT5640_PLL_M_BP_SFT); + + rt5640->pll_in = freq_in; + rt5640->pll_out = freq_out; + rt5640->pll_src = source; + + return 0; +} + +static int rt5640_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* + * SND_SOC_BIAS_PREPARE is called while preparing for a + * transition to ON or away from ON. If current bias_level + * is SND_SOC_BIAS_ON, then it is preparing for a transition + * away from ON. Disable the clock in that case, otherwise + * enable it. + */ + if (IS_ERR(rt5640->mclk)) + break; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(rt5640->mclk); + } else { + ret = clk_prepare_enable(rt5640->mclk); + if (ret) + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (SND_SOC_BIAS_OFF == snd_soc_component_get_bias_level(component)) { + snd_soc_component_update_bits(component, RT5640_PWR_ANLG1, + RT5640_PWR_VREF1 | RT5640_PWR_MB | + RT5640_PWR_BG | RT5640_PWR_VREF2, + RT5640_PWR_VREF1 | RT5640_PWR_MB | + RT5640_PWR_BG | RT5640_PWR_VREF2); + usleep_range(10000, 15000); + snd_soc_component_update_bits(component, RT5640_PWR_ANLG1, + RT5640_PWR_FV1 | RT5640_PWR_FV2, + RT5640_PWR_FV1 | RT5640_PWR_FV2); + snd_soc_component_update_bits(component, RT5640_DUMMY1, + 0x0301, 0x0301); + snd_soc_component_update_bits(component, RT5640_MICBIAS, + 0x0030, 0x0030); + } + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, RT5640_DEPOP_M1, 0x0004); + snd_soc_component_write(component, RT5640_DEPOP_M2, 0x1100); + snd_soc_component_update_bits(component, RT5640_DUMMY1, 0x1, 0); + snd_soc_component_write(component, RT5640_PWR_DIG1, 0x0000); + snd_soc_component_write(component, RT5640_PWR_DIG2, 0x0000); + snd_soc_component_write(component, RT5640_PWR_VOL, 0x0000); + snd_soc_component_write(component, RT5640_PWR_MIXER, 0x0000); + snd_soc_component_write(component, RT5640_PWR_ANLG1, 0x0000); + snd_soc_component_write(component, RT5640_PWR_ANLG2, 0x0000); + break; + + default: + break; + } + + return 0; +} + +int rt5640_dmic_enable(struct snd_soc_component *component, + bool dmic1_data_pin, bool dmic2_data_pin) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + regmap_update_bits(rt5640->regmap, RT5640_GPIO_CTRL1, + RT5640_GP2_PIN_MASK, RT5640_GP2_PIN_DMIC1_SCL); + + if (dmic1_data_pin) { + regmap_update_bits(rt5640->regmap, RT5640_DMIC, + RT5640_DMIC_1_DP_MASK, RT5640_DMIC_1_DP_GPIO3); + regmap_update_bits(rt5640->regmap, RT5640_GPIO_CTRL1, + RT5640_GP3_PIN_MASK, RT5640_GP3_PIN_DMIC1_SDA); + } + + if (dmic2_data_pin) { + regmap_update_bits(rt5640->regmap, RT5640_DMIC, + RT5640_DMIC_2_DP_MASK, RT5640_DMIC_2_DP_GPIO4); + regmap_update_bits(rt5640->regmap, RT5640_GPIO_CTRL1, + RT5640_GP4_PIN_MASK, RT5640_GP4_PIN_DMIC2_SDA); + } + + return 0; +} +EXPORT_SYMBOL_GPL(rt5640_dmic_enable); + +int rt5640_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + unsigned int asrc2_mask = 0; + unsigned int asrc2_value = 0; + + switch (clk_src) { + case RT5640_CLK_SEL_SYS: + case RT5640_CLK_SEL_ASRC: + break; + + default: + return -EINVAL; + } + + if (!filter_mask) + return -EINVAL; + + if (filter_mask & RT5640_DA_STEREO_FILTER) { + asrc2_mask |= RT5640_STO_DAC_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_STO_DAC_M_MASK) + | (clk_src << RT5640_STO_DAC_M_SFT); + } + + if (filter_mask & RT5640_DA_MONO_L_FILTER) { + asrc2_mask |= RT5640_MDA_L_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MDA_L_M_MASK) + | (clk_src << RT5640_MDA_L_M_SFT); + } + + if (filter_mask & RT5640_DA_MONO_R_FILTER) { + asrc2_mask |= RT5640_MDA_R_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MDA_R_M_MASK) + | (clk_src << RT5640_MDA_R_M_SFT); + } + + if (filter_mask & RT5640_AD_STEREO_FILTER) { + asrc2_mask |= RT5640_ADC_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_ADC_M_MASK) + | (clk_src << RT5640_ADC_M_SFT); + } + + if (filter_mask & RT5640_AD_MONO_L_FILTER) { + asrc2_mask |= RT5640_MAD_L_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MAD_L_M_MASK) + | (clk_src << RT5640_MAD_L_M_SFT); + } + + if (filter_mask & RT5640_AD_MONO_R_FILTER) { + asrc2_mask |= RT5640_MAD_R_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MAD_R_M_MASK) + | (clk_src << RT5640_MAD_R_M_SFT); + } + + snd_soc_component_update_bits(component, RT5640_ASRC_2, + asrc2_mask, asrc2_value); + + if (snd_soc_component_read32(component, RT5640_ASRC_2)) { + rt5640->asrc_en = true; + snd_soc_component_update_bits(component, RT5640_JD_CTRL, 0x3, 0x3); + } else { + rt5640->asrc_en = false; + snd_soc_component_update_bits(component, RT5640_JD_CTRL, 0x3, 0x0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(rt5640_sel_asrc_clk_src); + +static void rt5640_enable_micbias1_for_ovcd(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + snd_soc_dapm_mutex_lock(dapm); + snd_soc_dapm_force_enable_pin_unlocked(dapm, "LDO2"); + snd_soc_dapm_force_enable_pin_unlocked(dapm, "MICBIAS1"); + /* OVCD is unreliable when used with RCCLK as sysclk-source */ + snd_soc_dapm_force_enable_pin_unlocked(dapm, "Platform Clock"); + snd_soc_dapm_sync_unlocked(dapm); + snd_soc_dapm_mutex_unlock(dapm); +} + +static void rt5640_disable_micbias1_for_ovcd(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + snd_soc_dapm_mutex_lock(dapm); + snd_soc_dapm_disable_pin_unlocked(dapm, "Platform Clock"); + snd_soc_dapm_disable_pin_unlocked(dapm, "MICBIAS1"); + snd_soc_dapm_disable_pin_unlocked(dapm, "LDO2"); + snd_soc_dapm_sync_unlocked(dapm); + snd_soc_dapm_mutex_unlock(dapm); +} + +static void rt5640_enable_micbias1_ovcd_irq(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2, + RT5640_IRQ_MB1_OC_MASK, RT5640_IRQ_MB1_OC_NOR); + rt5640->ovcd_irq_enabled = true; +} + +static void rt5640_disable_micbias1_ovcd_irq(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2, + RT5640_IRQ_MB1_OC_MASK, RT5640_IRQ_MB1_OC_BP); + rt5640->ovcd_irq_enabled = false; +} + +static void rt5640_clear_micbias1_ovcd(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2, + RT5640_MB1_OC_STATUS, 0); +} + +static bool rt5640_micbias1_ovcd(struct snd_soc_component *component) +{ + int val; + + val = snd_soc_component_read32(component, RT5640_IRQ_CTRL2); + dev_dbg(component->dev, "irq ctrl2 %#04x\n", val); + + return (val & RT5640_MB1_OC_STATUS); +} + +static bool rt5640_jack_inserted(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + int val; + + val = snd_soc_component_read32(component, RT5640_INT_IRQ_ST); + dev_dbg(component->dev, "irq status %#04x\n", val); + + if (rt5640->jd_inverted) + return !(val & RT5640_JD_STATUS); + else + return (val & RT5640_JD_STATUS); +} + +/* Jack detect and button-press timings */ +#define JACK_SETTLE_TIME 100 /* milli seconds */ +#define JACK_DETECT_COUNT 5 +#define JACK_DETECT_MAXCOUNT 20 /* Aprox. 2 seconds worth of tries */ +#define JACK_UNPLUG_TIME 80 /* milli seconds */ +#define BP_POLL_TIME 10 /* milli seconds */ +#define BP_POLL_MAXCOUNT 200 /* assume something is wrong after this */ +#define BP_THRESHOLD 3 + +static void rt5640_start_button_press_work(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + rt5640->poll_count = 0; + rt5640->press_count = 0; + rt5640->release_count = 0; + rt5640->pressed = false; + rt5640->press_reported = false; + rt5640_clear_micbias1_ovcd(component); + schedule_delayed_work(&rt5640->bp_work, msecs_to_jiffies(BP_POLL_TIME)); +} + +static void rt5640_button_press_work(struct work_struct *work) +{ + struct rt5640_priv *rt5640 = + container_of(work, struct rt5640_priv, bp_work.work); + struct snd_soc_component *component = rt5640->component; + + /* Check the jack was not removed underneath us */ + if (!rt5640_jack_inserted(component)) + return; + + if (rt5640_micbias1_ovcd(component)) { + rt5640->release_count = 0; + rt5640->press_count++; + /* Remember till after JACK_UNPLUG_TIME wait */ + if (rt5640->press_count >= BP_THRESHOLD) + rt5640->pressed = true; + rt5640_clear_micbias1_ovcd(component); + } else { + rt5640->press_count = 0; + rt5640->release_count++; + } + + /* + * The pins get temporarily shorted on jack unplug, so we poll for + * at least JACK_UNPLUG_TIME milli-seconds before reporting a press. + */ + rt5640->poll_count++; + if (rt5640->poll_count < (JACK_UNPLUG_TIME / BP_POLL_TIME)) { + schedule_delayed_work(&rt5640->bp_work, + msecs_to_jiffies(BP_POLL_TIME)); + return; + } + + if (rt5640->pressed && !rt5640->press_reported) { + dev_dbg(component->dev, "headset button press\n"); + snd_soc_jack_report(rt5640->jack, SND_JACK_BTN_0, + SND_JACK_BTN_0); + rt5640->press_reported = true; + } + + if (rt5640->release_count >= BP_THRESHOLD) { + if (rt5640->press_reported) { + dev_dbg(component->dev, "headset button release\n"); + snd_soc_jack_report(rt5640->jack, 0, SND_JACK_BTN_0); + } + /* Re-enable OVCD IRQ to detect next press */ + rt5640_enable_micbias1_ovcd_irq(component); + return; /* Stop polling */ + } + + schedule_delayed_work(&rt5640->bp_work, msecs_to_jiffies(BP_POLL_TIME)); +} + +static int rt5640_detect_headset(struct snd_soc_component *component) +{ + int i, headset_count = 0, headphone_count = 0; + + /* + * We get the insertion event before the jack is fully inserted at which + * point the second ring on a TRRS connector may short the 2nd ring and + * sleeve contacts, also the overcurrent detection is not entirely + * reliable. So we try several times with a wait in between until we + * detect the same type JACK_DETECT_COUNT times in a row. + */ + for (i = 0; i < JACK_DETECT_MAXCOUNT; i++) { + /* Clear any previous over-current status flag */ + rt5640_clear_micbias1_ovcd(component); + + msleep(JACK_SETTLE_TIME); + + /* Check the jack is still connected before checking ovcd */ + if (!rt5640_jack_inserted(component)) + return 0; + + if (rt5640_micbias1_ovcd(component)) { + /* + * Over current detected, there is a short between the + * 2nd ring contact and the ground, so a TRS connector + * without a mic contact and thus plain headphones. + */ + dev_dbg(component->dev, "jack mic-gnd shorted\n"); + headset_count = 0; + headphone_count++; + if (headphone_count == JACK_DETECT_COUNT) + return SND_JACK_HEADPHONE; + } else { + dev_dbg(component->dev, "jack mic-gnd open\n"); + headphone_count = 0; + headset_count++; + if (headset_count == JACK_DETECT_COUNT) + return SND_JACK_HEADSET; + } + } + + dev_err(component->dev, "Error detecting headset vs headphones, bad contact?, assuming headphones\n"); + return SND_JACK_HEADPHONE; +} + +static void rt5640_jack_work(struct work_struct *work) +{ + struct rt5640_priv *rt5640 = + container_of(work, struct rt5640_priv, jack_work); + struct snd_soc_component *component = rt5640->component; + int status; + + if (!rt5640_jack_inserted(component)) { + /* Jack removed, or spurious IRQ? */ + if (rt5640->jack->status & SND_JACK_HEADPHONE) { + if (rt5640->jack->status & SND_JACK_MICROPHONE) { + cancel_delayed_work_sync(&rt5640->bp_work); + rt5640_disable_micbias1_ovcd_irq(component); + rt5640_disable_micbias1_for_ovcd(component); + } + snd_soc_jack_report(rt5640->jack, 0, + SND_JACK_HEADSET | SND_JACK_BTN_0); + dev_dbg(component->dev, "jack unplugged\n"); + } + } else if (!(rt5640->jack->status & SND_JACK_HEADPHONE)) { + /* Jack inserted */ + WARN_ON(rt5640->ovcd_irq_enabled); + rt5640_enable_micbias1_for_ovcd(component); + status = rt5640_detect_headset(component); + if (status == SND_JACK_HEADSET) { + /* Enable ovcd IRQ for button press detect. */ + rt5640_enable_micbias1_ovcd_irq(component); + } else { + /* No more need for overcurrent detect. */ + rt5640_disable_micbias1_for_ovcd(component); + } + dev_dbg(component->dev, "detect status %#02x\n", status); + snd_soc_jack_report(rt5640->jack, status, SND_JACK_HEADSET); + } else if (rt5640->ovcd_irq_enabled && rt5640_micbias1_ovcd(component)) { + dev_dbg(component->dev, "OVCD IRQ\n"); + + /* + * The ovcd IRQ keeps firing while the button is pressed, so + * we disable it and start polling the button until released. + * + * The disable will make the IRQ pin 0 again and since we get + * IRQs on both edges (so as to detect both jack plugin and + * unplug) this means we will immediately get another IRQ. + * The ovcd_irq_enabled check above makes the 2ND IRQ a NOP. + */ + rt5640_disable_micbias1_ovcd_irq(component); + rt5640_start_button_press_work(component); + + /* + * If the jack-detect IRQ flag goes high (unplug) after our + * above rt5640_jack_inserted() check and before we have + * disabled the OVCD IRQ, the IRQ pin will stay high and as + * we react to edges, we miss the unplug event -> recheck. + */ + queue_work(system_long_wq, &rt5640->jack_work); + } +} + +static irqreturn_t rt5640_irq(int irq, void *data) +{ + struct rt5640_priv *rt5640 = data; + + if (rt5640->jack) + queue_work(system_long_wq, &rt5640->jack_work); + + return IRQ_HANDLED; +} + +static void rt5640_cancel_work(void *data) +{ + struct rt5640_priv *rt5640 = data; + + cancel_work_sync(&rt5640->jack_work); + cancel_delayed_work_sync(&rt5640->bp_work); +} + +static void rt5640_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + /* Select JD-source */ + snd_soc_component_update_bits(component, RT5640_JD_CTRL, + RT5640_JD_MASK, rt5640->jd_src); + + /* Selecting GPIO01 as an interrupt */ + snd_soc_component_update_bits(component, RT5640_GPIO_CTRL1, + RT5640_GP1_PIN_MASK, RT5640_GP1_PIN_IRQ); + + /* Set GPIO1 output */ + snd_soc_component_update_bits(component, RT5640_GPIO_CTRL3, + RT5640_GP1_PF_MASK, RT5640_GP1_PF_OUT); + + /* Enabling jd2 in general control 1 */ + snd_soc_component_write(component, RT5640_DUMMY1, 0x3f41); + + /* Enabling jd2 in general control 2 */ + snd_soc_component_write(component, RT5640_DUMMY2, 0x4001); + + snd_soc_component_write(component, RT5640_PR_BASE + RT5640_BIAS_CUR4, + 0xa800 | rt5640->ovcd_sf); + + snd_soc_component_update_bits(component, RT5640_MICBIAS, + RT5640_MIC1_OVTH_MASK | RT5640_MIC1_OVCD_MASK, + rt5640->ovcd_th | RT5640_MIC1_OVCD_EN); + + /* + * The over-current-detect is only reliable in detecting the absence + * of over-current, when the mic-contact in the jack is short-circuited, + * the hardware periodically retries if it can apply the bias-current + * leading to the ovcd status flip-flopping 1-0-1 with it being 0 about + * 10% of the time, as we poll the ovcd status bit we might hit that + * 10%, so we enable sticky mode and when checking OVCD we clear the + * status, msleep() a bit and then check to get a reliable reading. + */ + snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2, + RT5640_MB1_OC_STKY_MASK, RT5640_MB1_OC_STKY_EN); + + /* + * All IRQs get or-ed together, so we need the jack IRQ to report 0 + * when a jack is inserted so that the OVCD IRQ then toggles the IRQ + * pin 0/1 instead of it being stuck to 1. So we invert the JD polarity + * on systems where the hardware does not already do this. + */ + if (rt5640->jd_inverted) + snd_soc_component_write(component, RT5640_IRQ_CTRL1, + RT5640_IRQ_JD_NOR); + else + snd_soc_component_write(component, RT5640_IRQ_CTRL1, + RT5640_IRQ_JD_NOR | RT5640_JD_P_INV); + + rt5640->jack = jack; + if (rt5640->jack->status & SND_JACK_MICROPHONE) { + rt5640_enable_micbias1_for_ovcd(component); + rt5640_enable_micbias1_ovcd_irq(component); + } + + enable_irq(rt5640->irq); + /* sync initial jack state */ + queue_work(system_long_wq, &rt5640->jack_work); +} + +static void rt5640_disable_jack_detect(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + /* + * soc_remove_component() force-disables jack and thus rt5640->jack + * could be NULL at the time of driver's module unloading. + */ + if (!rt5640->jack) + return; + + disable_irq(rt5640->irq); + rt5640_cancel_work(rt5640); + + if (rt5640->jack->status & SND_JACK_MICROPHONE) { + rt5640_disable_micbias1_ovcd_irq(component); + rt5640_disable_micbias1_for_ovcd(component); + snd_soc_jack_report(rt5640->jack, 0, SND_JACK_BTN_0); + } + + rt5640->jack = NULL; +} + +static int rt5640_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + if (jack) + rt5640_enable_jack_detect(component, jack); + else + rt5640_disable_jack_detect(component); + + return 0; +} + +static int rt5640_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + u32 dmic1_data_pin = 0; + u32 dmic2_data_pin = 0; + bool dmic_en = false; + u32 val; + + /* Check if MCLK provided */ + rt5640->mclk = devm_clk_get(component->dev, "mclk"); + if (PTR_ERR(rt5640->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + rt5640->component = component; + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + snd_soc_component_update_bits(component, RT5640_DUMMY1, 0x0301, 0x0301); + snd_soc_component_update_bits(component, RT5640_MICBIAS, 0x0030, 0x0030); + snd_soc_component_update_bits(component, RT5640_DSP_PATH2, 0xfc00, 0x0c00); + + switch (snd_soc_component_read32(component, RT5640_RESET) & RT5640_ID_MASK) { + case RT5640_ID_5640: + case RT5640_ID_5642: + snd_soc_add_component_controls(component, + rt5640_specific_snd_controls, + ARRAY_SIZE(rt5640_specific_snd_controls)); + snd_soc_dapm_new_controls(dapm, + rt5640_specific_dapm_widgets, + ARRAY_SIZE(rt5640_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5640_specific_dapm_routes, + ARRAY_SIZE(rt5640_specific_dapm_routes)); + break; + case RT5640_ID_5639: + snd_soc_dapm_new_controls(dapm, + rt5639_specific_dapm_widgets, + ARRAY_SIZE(rt5639_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5639_specific_dapm_routes, + ARRAY_SIZE(rt5639_specific_dapm_routes)); + break; + default: + dev_err(component->dev, + "The driver is for RT5639 RT5640 or RT5642 only\n"); + return -ENODEV; + } + + /* + * Note on some platforms the platform code may need to add device-props + * rather then relying only on properties set by the firmware. + * Therefor the property parsing MUST be done here, rather then from + * rt5640_i2c_probe(), so that the platform-code can attach extra + * properties before calling snd_soc_register_card(). + */ + if (device_property_read_bool(component->dev, "realtek,in1-differential")) + snd_soc_component_update_bits(component, RT5640_IN1_IN2, + RT5640_IN_DF1, RT5640_IN_DF1); + + if (device_property_read_bool(component->dev, "realtek,in2-differential")) + snd_soc_component_update_bits(component, RT5640_IN3_IN4, + RT5640_IN_DF2, RT5640_IN_DF2); + + if (device_property_read_bool(component->dev, "realtek,in3-differential")) + snd_soc_component_update_bits(component, RT5640_IN1_IN2, + RT5640_IN_DF2, RT5640_IN_DF2); + + if (device_property_read_u32(component->dev, "realtek,dmic1-data-pin", + &val) == 0 && val) { + dmic1_data_pin = val - 1; + dmic_en = true; + } + + if (device_property_read_u32(component->dev, "realtek,dmic2-data-pin", + &val) == 0 && val) { + dmic2_data_pin = val - 1; + dmic_en = true; + } + + if (dmic_en) + rt5640_dmic_enable(component, dmic1_data_pin, dmic2_data_pin); + + if (device_property_read_u32(component->dev, + "realtek,jack-detect-source", &val) == 0) { + if (val <= RT5640_JD_SRC_GPIO4) + rt5640->jd_src = val << RT5640_JD_SFT; + else + dev_warn(component->dev, "Warning: Invalid jack-detect-source value: %d, leaving jack-detect disabled\n", + val); + } + + if (!device_property_read_bool(component->dev, "realtek,jack-detect-not-inverted")) + rt5640->jd_inverted = true; + + /* + * Testing on various boards has shown that good defaults for the OVCD + * threshold and scale-factor are 2000µA and 0.75. For an effective + * limit of 1500µA, this seems to be more reliable then 1500µA and 1.0. + */ + rt5640->ovcd_th = RT5640_MIC1_OVTH_2000UA; + rt5640->ovcd_sf = RT5640_MIC_OVCD_SF_0P75; + + if (device_property_read_u32(component->dev, + "realtek,over-current-threshold-microamp", &val) == 0) { + switch (val) { + case 600: + rt5640->ovcd_th = RT5640_MIC1_OVTH_600UA; + break; + case 1500: + rt5640->ovcd_th = RT5640_MIC1_OVTH_1500UA; + break; + case 2000: + rt5640->ovcd_th = RT5640_MIC1_OVTH_2000UA; + break; + default: + dev_warn(component->dev, "Warning: Invalid over-current-threshold-microamp value: %d, defaulting to 2000uA\n", + val); + } + } + + if (device_property_read_u32(component->dev, + "realtek,over-current-scale-factor", &val) == 0) { + if (val <= RT5640_OVCD_SF_1P5) + rt5640->ovcd_sf = val << RT5640_MIC_OVCD_SF_SFT; + else + dev_warn(component->dev, "Warning: Invalid over-current-scale-factor value: %d, defaulting to 0.75\n", + val); + } + + return 0; +} + +static void rt5640_remove(struct snd_soc_component *component) +{ + rt5640_reset(component); +} + +#ifdef CONFIG_PM +static int rt5640_suspend(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + rt5640_reset(component); + regcache_cache_only(rt5640->regmap, true); + regcache_mark_dirty(rt5640->regmap); + if (gpio_is_valid(rt5640->ldo1_en)) + gpio_set_value_cansleep(rt5640->ldo1_en, 0); + + return 0; +} + +static int rt5640_resume(struct snd_soc_component *component) +{ + struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component); + + if (gpio_is_valid(rt5640->ldo1_en)) { + gpio_set_value_cansleep(rt5640->ldo1_en, 1); + msleep(400); + } + + regcache_cache_only(rt5640->regmap, false); + regcache_sync(rt5640->regmap); + + return 0; +} +#else +#define rt5640_suspend NULL +#define rt5640_resume NULL +#endif + +#define RT5640_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define RT5640_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5640_aif_dai_ops = { + .hw_params = rt5640_hw_params, + .set_fmt = rt5640_set_dai_fmt, + .set_sysclk = rt5640_set_dai_sysclk, + .set_pll = rt5640_set_dai_pll, +}; + +static struct snd_soc_dai_driver rt5640_dai[] = { + { + .name = "rt5640-aif1", + .id = RT5640_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5640_STEREO_RATES, + .formats = RT5640_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5640_STEREO_RATES, + .formats = RT5640_FORMATS, + }, + .ops = &rt5640_aif_dai_ops, + }, + { + .name = "rt5640-aif2", + .id = RT5640_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5640_STEREO_RATES, + .formats = RT5640_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5640_STEREO_RATES, + .formats = RT5640_FORMATS, + }, + .ops = &rt5640_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5640 = { + .probe = rt5640_probe, + .remove = rt5640_remove, + .suspend = rt5640_suspend, + .resume = rt5640_resume, + .set_bias_level = rt5640_set_bias_level, + .set_jack = rt5640_set_jack, + .controls = rt5640_snd_controls, + .num_controls = ARRAY_SIZE(rt5640_snd_controls), + .dapm_widgets = rt5640_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5640_dapm_widgets), + .dapm_routes = rt5640_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5640_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, + +}; + +static const struct regmap_config rt5640_regmap = { + .reg_bits = 8, + .val_bits = 16, + .use_single_rw = true, + + .max_register = RT5640_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5640_ranges) * + RT5640_PR_SPACING), + .volatile_reg = rt5640_volatile_register, + .readable_reg = rt5640_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5640_reg, + .num_reg_defaults = ARRAY_SIZE(rt5640_reg), + .ranges = rt5640_ranges, + .num_ranges = ARRAY_SIZE(rt5640_ranges), +}; + +static const struct i2c_device_id rt5640_i2c_id[] = { + { "rt5640", 0 }, + { "rt5639", 0 }, + { "rt5642", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5640_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id rt5640_of_match[] = { + { .compatible = "realtek,rt5639", }, + { .compatible = "realtek,rt5640", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5640_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5640_acpi_match[] = { + { "INT33CA", 0 }, + { "10EC3276", 0 }, + { "10EC5640", 0 }, + { "10EC5642", 0 }, + { "INTCCFFD", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, rt5640_acpi_match); +#endif + +static int rt5640_parse_dt(struct rt5640_priv *rt5640, struct device_node *np) +{ + rt5640->ldo1_en = of_get_named_gpio(np, "realtek,ldo1-en-gpios", 0); + /* + * LDO1_EN is optional (it may be statically tied on the board). + * -ENOENT means that the property doesn't exist, i.e. there is no + * GPIO, so is not an error. Any other error code means the property + * exists, but could not be parsed. + */ + if (!gpio_is_valid(rt5640->ldo1_en) && + (rt5640->ldo1_en != -ENOENT)) + return rt5640->ldo1_en; + + return 0; +} + +static int rt5640_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5640_priv *rt5640; + int ret; + unsigned int val; + + rt5640 = devm_kzalloc(&i2c->dev, + sizeof(struct rt5640_priv), + GFP_KERNEL); + if (NULL == rt5640) + return -ENOMEM; + i2c_set_clientdata(i2c, rt5640); + + if (i2c->dev.of_node) { + ret = rt5640_parse_dt(rt5640, i2c->dev.of_node); + if (ret) + return ret; + } else + rt5640->ldo1_en = -EINVAL; + + rt5640->regmap = devm_regmap_init_i2c(i2c, &rt5640_regmap); + if (IS_ERR(rt5640->regmap)) { + ret = PTR_ERR(rt5640->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + if (gpio_is_valid(rt5640->ldo1_en)) { + ret = devm_gpio_request_one(&i2c->dev, rt5640->ldo1_en, + GPIOF_OUT_INIT_HIGH, + "RT5640 LDO1_EN"); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request LDO1_EN %d: %d\n", + rt5640->ldo1_en, ret); + return ret; + } + msleep(400); + } + + regmap_read(rt5640->regmap, RT5640_VENDOR_ID2, &val); + if (val != RT5640_DEVICE_ID) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5640/39\n", val); + return -ENODEV; + } + + regmap_write(rt5640->regmap, RT5640_RESET, 0); + + ret = regmap_register_patch(rt5640->regmap, init_list, + ARRAY_SIZE(init_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + regmap_update_bits(rt5640->regmap, RT5640_DUMMY1, + RT5640_MCLK_DET, RT5640_MCLK_DET); + + rt5640->hp_mute = 1; + rt5640->irq = i2c->irq; + INIT_DELAYED_WORK(&rt5640->bp_work, rt5640_button_press_work); + INIT_WORK(&rt5640->jack_work, rt5640_jack_work); + + /* Make sure work is stopped on probe-error / remove */ + ret = devm_add_action_or_reset(&i2c->dev, rt5640_cancel_work, rt5640); + if (ret) + return ret; + + ret = devm_request_irq(&i2c->dev, rt5640->irq, rt5640_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5640", rt5640); + if (ret == 0) { + /* Gets re-enabled by rt5640_set_jack() */ + disable_irq(rt5640->irq); + } else { + dev_warn(&i2c->dev, "Failed to reguest IRQ %d: %d\n", + rt5640->irq, ret); + rt5640->irq = -ENXIO; + } + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5640, + rt5640_dai, ARRAY_SIZE(rt5640_dai)); +} + +static struct i2c_driver rt5640_i2c_driver = { + .driver = { + .name = "rt5640", + .acpi_match_table = ACPI_PTR(rt5640_acpi_match), + .of_match_table = of_match_ptr(rt5640_of_match), + }, + .probe = rt5640_i2c_probe, + .id_table = rt5640_i2c_id, +}; +module_i2c_driver(rt5640_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5640/RT5639 driver"); +MODULE_AUTHOR("Johnny Hsu "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5640.h b/sound/soc/codecs/rt5640.h new file mode 100644 index 000000000..e29e3e7d6 --- /dev/null +++ b/sound/soc/codecs/rt5640.h @@ -0,0 +1,2163 @@ +/* + * rt5640.h -- RT5640 ALSA SoC audio driver + * + * Copyright 2011 Realtek Microelectronics + * Author: Johnny Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _RT5640_H +#define _RT5640_H + +#include +#include +#include + +/* Info */ +#define RT5640_RESET 0x00 +#define RT5640_VENDOR_ID 0xfd +#define RT5640_VENDOR_ID1 0xfe +#define RT5640_VENDOR_ID2 0xff +/* I/O - Output */ +#define RT5640_SPK_VOL 0x01 +#define RT5640_HP_VOL 0x02 +#define RT5640_OUTPUT 0x03 +#define RT5640_MONO_OUT 0x04 +/* I/O - Input */ +#define RT5640_IN1_IN2 0x0d +#define RT5640_IN3_IN4 0x0e +#define RT5640_INL_INR_VOL 0x0f +/* I/O - ADC/DAC/DMIC */ +#define RT5640_DAC1_DIG_VOL 0x19 +#define RT5640_DAC2_DIG_VOL 0x1a +#define RT5640_DAC2_CTRL 0x1b +#define RT5640_ADC_DIG_VOL 0x1c +#define RT5640_ADC_DATA 0x1d +#define RT5640_ADC_BST_VOL 0x1e +/* Mixer - D-D */ +#define RT5640_STO_ADC_MIXER 0x27 +#define RT5640_MONO_ADC_MIXER 0x28 +#define RT5640_AD_DA_MIXER 0x29 +#define RT5640_STO_DAC_MIXER 0x2a +#define RT5640_MONO_DAC_MIXER 0x2b +#define RT5640_DIG_MIXER 0x2c +#define RT5640_DSP_PATH1 0x2d +#define RT5640_DSP_PATH2 0x2e +#define RT5640_DIG_INF_DATA 0x2f +/* Mixer - ADC */ +#define RT5640_REC_L1_MIXER 0x3b +#define RT5640_REC_L2_MIXER 0x3c +#define RT5640_REC_R1_MIXER 0x3d +#define RT5640_REC_R2_MIXER 0x3e +/* Mixer - DAC */ +#define RT5640_HPO_MIXER 0x45 +#define RT5640_SPK_L_MIXER 0x46 +#define RT5640_SPK_R_MIXER 0x47 +#define RT5640_SPO_L_MIXER 0x48 +#define RT5640_SPO_R_MIXER 0x49 +#define RT5640_SPO_CLSD_RATIO 0x4a +#define RT5640_MONO_MIXER 0x4c +#define RT5640_OUT_L1_MIXER 0x4d +#define RT5640_OUT_L2_MIXER 0x4e +#define RT5640_OUT_L3_MIXER 0x4f +#define RT5640_OUT_R1_MIXER 0x50 +#define RT5640_OUT_R2_MIXER 0x51 +#define RT5640_OUT_R3_MIXER 0x52 +#define RT5640_LOUT_MIXER 0x53 +/* Power */ +#define RT5640_PWR_DIG1 0x61 +#define RT5640_PWR_DIG2 0x62 +#define RT5640_PWR_ANLG1 0x63 +#define RT5640_PWR_ANLG2 0x64 +#define RT5640_PWR_MIXER 0x65 +#define RT5640_PWR_VOL 0x66 +/* Private Register Control */ +#define RT5640_PRIV_INDEX 0x6a +#define RT5640_PRIV_DATA 0x6c +/* Format - ADC/DAC */ +#define RT5640_I2S1_SDP 0x70 +#define RT5640_I2S2_SDP 0x71 +#define RT5640_ADDA_CLK1 0x73 +#define RT5640_ADDA_CLK2 0x74 +#define RT5640_DMIC 0x75 +/* Function - Analog */ +#define RT5640_GLB_CLK 0x80 +#define RT5640_PLL_CTRL1 0x81 +#define RT5640_PLL_CTRL2 0x82 +#define RT5640_ASRC_1 0x83 +#define RT5640_ASRC_2 0x84 +#define RT5640_ASRC_3 0x85 +#define RT5640_ASRC_4 0x89 +#define RT5640_ASRC_5 0x8a +#define RT5640_HP_OVCD 0x8b +#define RT5640_CLS_D_OVCD 0x8c +#define RT5640_CLS_D_OUT 0x8d +#define RT5640_DEPOP_M1 0x8e +#define RT5640_DEPOP_M2 0x8f +#define RT5640_DEPOP_M3 0x90 +#define RT5640_CHARGE_PUMP 0x91 +#define RT5640_PV_DET_SPK_G 0x92 +#define RT5640_MICBIAS 0x93 +/* Function - Digital */ +#define RT5640_EQ_CTRL1 0xb0 +#define RT5640_EQ_CTRL2 0xb1 +#define RT5640_WIND_FILTER 0xb2 +#define RT5640_DRC_AGC_1 0xb4 +#define RT5640_DRC_AGC_2 0xb5 +#define RT5640_DRC_AGC_3 0xb6 +#define RT5640_SVOL_ZC 0xb7 +#define RT5640_ANC_CTRL1 0xb8 +#define RT5640_ANC_CTRL2 0xb9 +#define RT5640_ANC_CTRL3 0xba +#define RT5640_JD_CTRL 0xbb +#define RT5640_ANC_JD 0xbc +#define RT5640_IRQ_CTRL1 0xbd +#define RT5640_IRQ_CTRL2 0xbe +#define RT5640_INT_IRQ_ST 0xbf +#define RT5640_GPIO_CTRL1 0xc0 +#define RT5640_GPIO_CTRL2 0xc1 +#define RT5640_GPIO_CTRL3 0xc2 +#define RT5640_DSP_CTRL1 0xc4 +#define RT5640_DSP_CTRL2 0xc5 +#define RT5640_DSP_CTRL3 0xc6 +#define RT5640_DSP_CTRL4 0xc7 +#define RT5640_PGM_REG_ARR1 0xc8 +#define RT5640_PGM_REG_ARR2 0xc9 +#define RT5640_PGM_REG_ARR3 0xca +#define RT5640_PGM_REG_ARR4 0xcb +#define RT5640_PGM_REG_ARR5 0xcc +#define RT5640_SCB_FUNC 0xcd +#define RT5640_SCB_CTRL 0xce +#define RT5640_BASE_BACK 0xcf +#define RT5640_MP3_PLUS1 0xd0 +#define RT5640_MP3_PLUS2 0xd1 +#define RT5640_3D_HP 0xd2 +#define RT5640_ADJ_HPF 0xd3 +#define RT5640_HP_CALIB_AMP_DET 0xd6 +#define RT5640_HP_CALIB2 0xd7 +#define RT5640_SV_ZCD1 0xd9 +#define RT5640_SV_ZCD2 0xda +/* Dummy Register */ +#define RT5640_DUMMY1 0xfa +#define RT5640_DUMMY2 0xfb +#define RT5640_DUMMY3 0xfc + + +/* Index of Codec Private Register definition */ +#define RT5640_BIAS_CUR4 0x15 +#define RT5640_CHPUMP_INT_REG1 0x24 +#define RT5640_MAMP_INT_REG2 0x37 +#define RT5640_3D_SPK 0x63 +#define RT5640_WND_1 0x6c +#define RT5640_WND_2 0x6d +#define RT5640_WND_3 0x6e +#define RT5640_WND_4 0x6f +#define RT5640_WND_5 0x70 +#define RT5640_WND_8 0x73 +#define RT5640_DIP_SPK_INF 0x75 +#define RT5640_HP_DCC_INT1 0x77 +#define RT5640_EQ_BW_LOP 0xa0 +#define RT5640_EQ_GN_LOP 0xa1 +#define RT5640_EQ_FC_BP1 0xa2 +#define RT5640_EQ_BW_BP1 0xa3 +#define RT5640_EQ_GN_BP1 0xa4 +#define RT5640_EQ_FC_BP2 0xa5 +#define RT5640_EQ_BW_BP2 0xa6 +#define RT5640_EQ_GN_BP2 0xa7 +#define RT5640_EQ_FC_BP3 0xa8 +#define RT5640_EQ_BW_BP3 0xa9 +#define RT5640_EQ_GN_BP3 0xaa +#define RT5640_EQ_FC_BP4 0xab +#define RT5640_EQ_BW_BP4 0xac +#define RT5640_EQ_GN_BP4 0xad +#define RT5640_EQ_FC_HIP1 0xae +#define RT5640_EQ_GN_HIP1 0xaf +#define RT5640_EQ_FC_HIP2 0xb0 +#define RT5640_EQ_BW_HIP2 0xb1 +#define RT5640_EQ_GN_HIP2 0xb2 +#define RT5640_EQ_PRE_VOL 0xb3 +#define RT5640_EQ_PST_VOL 0xb4 + +/* global definition */ +#define RT5640_L_MUTE (0x1 << 15) +#define RT5640_L_MUTE_SFT 15 +#define RT5640_VOL_L_MUTE (0x1 << 14) +#define RT5640_VOL_L_SFT 14 +#define RT5640_R_MUTE (0x1 << 7) +#define RT5640_R_MUTE_SFT 7 +#define RT5640_VOL_R_MUTE (0x1 << 6) +#define RT5640_VOL_R_SFT 6 +#define RT5640_L_VOL_MASK (0x3f << 8) +#define RT5640_L_VOL_SFT 8 +#define RT5640_R_VOL_MASK (0x3f) +#define RT5640_R_VOL_SFT 0 + +/* SW Reset & Device ID (0x00) */ +#define RT5640_ID_MASK (0x3 << 1) +#define RT5640_ID_5639 (0x0 << 1) +#define RT5640_ID_5640 (0x2 << 1) +#define RT5640_ID_5642 (0x3 << 1) + + +/* IN1 and IN2 Control (0x0d) */ +/* IN3 and IN4 Control (0x0e) */ +#define RT5640_BST_SFT1 12 +#define RT5640_BST_SFT2 8 +#define RT5640_IN_DF1 (0x1 << 7) +#define RT5640_IN_SFT1 7 +#define RT5640_IN_DF2 (0x1 << 6) +#define RT5640_IN_SFT2 6 + +/* INL and INR Volume Control (0x0f) */ +#define RT5640_INL_SEL_MASK (0x1 << 15) +#define RT5640_INL_SEL_SFT 15 +#define RT5640_INL_SEL_IN4P (0x0 << 15) +#define RT5640_INL_SEL_MONOP (0x1 << 15) +#define RT5640_INL_VOL_MASK (0x1f << 8) +#define RT5640_INL_VOL_SFT 8 +#define RT5640_INR_SEL_MASK (0x1 << 7) +#define RT5640_INR_SEL_SFT 7 +#define RT5640_INR_SEL_IN4N (0x0 << 7) +#define RT5640_INR_SEL_MONON (0x1 << 7) +#define RT5640_INR_VOL_MASK (0x1f) +#define RT5640_INR_VOL_SFT 0 + +/* DAC1 Digital Volume (0x19) */ +#define RT5640_DAC_L1_VOL_MASK (0xff << 8) +#define RT5640_DAC_L1_VOL_SFT 8 +#define RT5640_DAC_R1_VOL_MASK (0xff) +#define RT5640_DAC_R1_VOL_SFT 0 + +/* DAC2 Digital Volume (0x1a) */ +#define RT5640_DAC_L2_VOL_MASK (0xff << 8) +#define RT5640_DAC_L2_VOL_SFT 8 +#define RT5640_DAC_R2_VOL_MASK (0xff) +#define RT5640_DAC_R2_VOL_SFT 0 + +/* DAC2 Control (0x1b) */ +#define RT5640_M_DAC_L2_VOL (0x1 << 13) +#define RT5640_M_DAC_L2_VOL_SFT 13 +#define RT5640_M_DAC_R2_VOL (0x1 << 12) +#define RT5640_M_DAC_R2_VOL_SFT 12 + +/* ADC Digital Volume Control (0x1c) */ +#define RT5640_ADC_L_VOL_MASK (0x7f << 8) +#define RT5640_ADC_L_VOL_SFT 8 +#define RT5640_ADC_R_VOL_MASK (0x7f) +#define RT5640_ADC_R_VOL_SFT 0 + +/* Mono ADC Digital Volume Control (0x1d) */ +#define RT5640_MONO_ADC_L_VOL_MASK (0x7f << 8) +#define RT5640_MONO_ADC_L_VOL_SFT 8 +#define RT5640_MONO_ADC_R_VOL_MASK (0x7f) +#define RT5640_MONO_ADC_R_VOL_SFT 0 + +/* ADC Boost Volume Control (0x1e) */ +#define RT5640_ADC_L_BST_MASK (0x3 << 14) +#define RT5640_ADC_L_BST_SFT 14 +#define RT5640_ADC_R_BST_MASK (0x3 << 12) +#define RT5640_ADC_R_BST_SFT 12 +#define RT5640_ADC_COMP_MASK (0x3 << 10) +#define RT5640_ADC_COMP_SFT 10 + +/* Stereo ADC Mixer Control (0x27) */ +#define RT5640_M_ADC_L1 (0x1 << 14) +#define RT5640_M_ADC_L1_SFT 14 +#define RT5640_M_ADC_L2 (0x1 << 13) +#define RT5640_M_ADC_L2_SFT 13 +#define RT5640_ADC_1_SRC_MASK (0x1 << 12) +#define RT5640_ADC_1_SRC_SFT 12 +#define RT5640_ADC_1_SRC_ADC (0x1 << 12) +#define RT5640_ADC_1_SRC_DACMIX (0x0 << 12) +#define RT5640_ADC_2_SRC_MASK (0x3 << 10) +#define RT5640_ADC_2_SRC_SFT 10 +#define RT5640_ADC_2_SRC_DMIC1 (0x0 << 10) +#define RT5640_ADC_2_SRC_DMIC2 (0x1 << 10) +#define RT5640_ADC_2_SRC_DACMIX (0x2 << 10) +#define RT5640_M_ADC_R1 (0x1 << 6) +#define RT5640_M_ADC_R1_SFT 6 +#define RT5640_M_ADC_R2 (0x1 << 5) +#define RT5640_M_ADC_R2_SFT 5 + +/* Mono ADC Mixer Control (0x28) */ +#define RT5640_M_MONO_ADC_L1 (0x1 << 14) +#define RT5640_M_MONO_ADC_L1_SFT 14 +#define RT5640_M_MONO_ADC_L2 (0x1 << 13) +#define RT5640_M_MONO_ADC_L2_SFT 13 +#define RT5640_MONO_ADC_L1_SRC_MASK (0x1 << 12) +#define RT5640_MONO_ADC_L1_SRC_SFT 12 +#define RT5640_MONO_ADC_L1_SRC_DACMIXL (0x0 << 12) +#define RT5640_MONO_ADC_L1_SRC_ADCL (0x1 << 12) +#define RT5640_MONO_ADC_L2_SRC_MASK (0x3 << 10) +#define RT5640_MONO_ADC_L2_SRC_SFT 10 +#define RT5640_MONO_ADC_L2_SRC_DMIC_L1 (0x0 << 10) +#define RT5640_MONO_ADC_L2_SRC_DMIC_L2 (0x1 << 10) +#define RT5640_MONO_ADC_L2_SRC_DACMIXL (0x2 << 10) +#define RT5640_M_MONO_ADC_R1 (0x1 << 6) +#define RT5640_M_MONO_ADC_R1_SFT 6 +#define RT5640_M_MONO_ADC_R2 (0x1 << 5) +#define RT5640_M_MONO_ADC_R2_SFT 5 +#define RT5640_MONO_ADC_R1_SRC_MASK (0x1 << 4) +#define RT5640_MONO_ADC_R1_SRC_SFT 4 +#define RT5640_MONO_ADC_R1_SRC_ADCR (0x1 << 4) +#define RT5640_MONO_ADC_R1_SRC_DACMIXR (0x0 << 4) +#define RT5640_MONO_ADC_R2_SRC_MASK (0x3 << 2) +#define RT5640_MONO_ADC_R2_SRC_SFT 2 +#define RT5640_MONO_ADC_R2_SRC_DMIC_R1 (0x0 << 2) +#define RT5640_MONO_ADC_R2_SRC_DMIC_R2 (0x1 << 2) +#define RT5640_MONO_ADC_R2_SRC_DACMIXR (0x2 << 2) + +/* ADC Mixer to DAC Mixer Control (0x29) */ +#define RT5640_M_ADCMIX_L (0x1 << 15) +#define RT5640_M_ADCMIX_L_SFT 15 +#define RT5640_M_IF1_DAC_L (0x1 << 14) +#define RT5640_M_IF1_DAC_L_SFT 14 +#define RT5640_M_ADCMIX_R (0x1 << 7) +#define RT5640_M_ADCMIX_R_SFT 7 +#define RT5640_M_IF1_DAC_R (0x1 << 6) +#define RT5640_M_IF1_DAC_R_SFT 6 + +/* Stereo DAC Mixer Control (0x2a) */ +#define RT5640_M_DAC_L1 (0x1 << 14) +#define RT5640_M_DAC_L1_SFT 14 +#define RT5640_DAC_L1_STO_L_VOL_MASK (0x1 << 13) +#define RT5640_DAC_L1_STO_L_VOL_SFT 13 +#define RT5640_M_DAC_L2 (0x1 << 12) +#define RT5640_M_DAC_L2_SFT 12 +#define RT5640_DAC_L2_STO_L_VOL_MASK (0x1 << 11) +#define RT5640_DAC_L2_STO_L_VOL_SFT 11 +#define RT5640_M_ANC_DAC_L (0x1 << 10) +#define RT5640_M_ANC_DAC_L_SFT 10 +#define RT5640_M_DAC_R1 (0x1 << 6) +#define RT5640_M_DAC_R1_SFT 6 +#define RT5640_DAC_R1_STO_R_VOL_MASK (0x1 << 5) +#define RT5640_DAC_R1_STO_R_VOL_SFT 5 +#define RT5640_M_DAC_R2 (0x1 << 4) +#define RT5640_M_DAC_R2_SFT 4 +#define RT5640_DAC_R2_STO_R_VOL_MASK (0x1 << 3) +#define RT5640_DAC_R2_STO_R_VOL_SFT 3 +#define RT5640_M_ANC_DAC_R (0x1 << 2) +#define RT5640_M_ANC_DAC_R_SFT 2 + +/* Mono DAC Mixer Control (0x2b) */ +#define RT5640_M_DAC_L1_MONO_L (0x1 << 14) +#define RT5640_M_DAC_L1_MONO_L_SFT 14 +#define RT5640_DAC_L1_MONO_L_VOL_MASK (0x1 << 13) +#define RT5640_DAC_L1_MONO_L_VOL_SFT 13 +#define RT5640_M_DAC_L2_MONO_L (0x1 << 12) +#define RT5640_M_DAC_L2_MONO_L_SFT 12 +#define RT5640_DAC_L2_MONO_L_VOL_MASK (0x1 << 11) +#define RT5640_DAC_L2_MONO_L_VOL_SFT 11 +#define RT5640_M_DAC_R2_MONO_L (0x1 << 10) +#define RT5640_M_DAC_R2_MONO_L_SFT 10 +#define RT5640_DAC_R2_MONO_L_VOL_MASK (0x1 << 9) +#define RT5640_DAC_R2_MONO_L_VOL_SFT 9 +#define RT5640_M_DAC_R1_MONO_R (0x1 << 6) +#define RT5640_M_DAC_R1_MONO_R_SFT 6 +#define RT5640_DAC_R1_MONO_R_VOL_MASK (0x1 << 5) +#define RT5640_DAC_R1_MONO_R_VOL_SFT 5 +#define RT5640_M_DAC_R2_MONO_R (0x1 << 4) +#define RT5640_M_DAC_R2_MONO_R_SFT 4 +#define RT5640_DAC_R2_MONO_R_VOL_MASK (0x1 << 3) +#define RT5640_DAC_R2_MONO_R_VOL_SFT 3 +#define RT5640_M_DAC_L2_MONO_R (0x1 << 2) +#define RT5640_M_DAC_L2_MONO_R_SFT 2 +#define RT5640_DAC_L2_MONO_R_VOL_MASK (0x1 << 1) +#define RT5640_DAC_L2_MONO_R_VOL_SFT 1 + +/* Digital Mixer Control (0x2c) */ +#define RT5640_M_STO_L_DAC_L (0x1 << 15) +#define RT5640_M_STO_L_DAC_L_SFT 15 +#define RT5640_STO_L_DAC_L_VOL_MASK (0x1 << 14) +#define RT5640_STO_L_DAC_L_VOL_SFT 14 +#define RT5640_M_DAC_L2_DAC_L (0x1 << 13) +#define RT5640_M_DAC_L2_DAC_L_SFT 13 +#define RT5640_DAC_L2_DAC_L_VOL_MASK (0x1 << 12) +#define RT5640_DAC_L2_DAC_L_VOL_SFT 12 +#define RT5640_M_STO_R_DAC_R (0x1 << 11) +#define RT5640_M_STO_R_DAC_R_SFT 11 +#define RT5640_STO_R_DAC_R_VOL_MASK (0x1 << 10) +#define RT5640_STO_R_DAC_R_VOL_SFT 10 +#define RT5640_M_DAC_R2_DAC_R (0x1 << 9) +#define RT5640_M_DAC_R2_DAC_R_SFT 9 +#define RT5640_DAC_R2_DAC_R_VOL_MASK (0x1 << 8) +#define RT5640_DAC_R2_DAC_R_VOL_SFT 8 + +/* DSP Path Control 1 (0x2d) */ +#define RT5640_RXDP_SRC_MASK (0x1 << 15) +#define RT5640_RXDP_SRC_SFT 15 +#define RT5640_RXDP_SRC_NOR (0x0 << 15) +#define RT5640_RXDP_SRC_DIV3 (0x1 << 15) +#define RT5640_TXDP_SRC_MASK (0x1 << 14) +#define RT5640_TXDP_SRC_SFT 14 +#define RT5640_TXDP_SRC_NOR (0x0 << 14) +#define RT5640_TXDP_SRC_DIV3 (0x1 << 14) + +/* DSP Path Control 2 (0x2e) */ +#define RT5640_DAC_L2_SEL_MASK (0x3 << 14) +#define RT5640_DAC_L2_SEL_SFT 14 +#define RT5640_DAC_L2_SEL_IF2 (0x0 << 14) +#define RT5640_DAC_L2_SEL_IF3 (0x1 << 14) +#define RT5640_DAC_L2_SEL_TXDC (0x2 << 14) +#define RT5640_DAC_L2_SEL_BASS (0x3 << 14) +#define RT5640_DAC_R2_SEL_MASK (0x3 << 12) +#define RT5640_DAC_R2_SEL_SFT 12 +#define RT5640_DAC_R2_SEL_IF2 (0x0 << 12) +#define RT5640_DAC_R2_SEL_IF3 (0x1 << 12) +#define RT5640_DAC_R2_SEL_TXDC (0x2 << 12) +#define RT5640_IF2_ADC_L_SEL_MASK (0x1 << 11) +#define RT5640_IF2_ADC_L_SEL_SFT 11 +#define RT5640_IF2_ADC_L_SEL_TXDP (0x0 << 11) +#define RT5640_IF2_ADC_L_SEL_PASS (0x1 << 11) +#define RT5640_IF2_ADC_R_SEL_MASK (0x1 << 10) +#define RT5640_IF2_ADC_R_SEL_SFT 10 +#define RT5640_IF2_ADC_R_SEL_TXDP (0x0 << 10) +#define RT5640_IF2_ADC_R_SEL_PASS (0x1 << 10) +#define RT5640_RXDC_SEL_MASK (0x3 << 8) +#define RT5640_RXDC_SEL_SFT 8 +#define RT5640_RXDC_SEL_NOR (0x0 << 8) +#define RT5640_RXDC_SEL_L2R (0x1 << 8) +#define RT5640_RXDC_SEL_R2L (0x2 << 8) +#define RT5640_RXDC_SEL_SWAP (0x3 << 8) +#define RT5640_RXDP_SEL_MASK (0x3 << 6) +#define RT5640_RXDP_SEL_SFT 6 +#define RT5640_RXDP_SEL_NOR (0x0 << 6) +#define RT5640_RXDP_SEL_L2R (0x1 << 6) +#define RT5640_RXDP_SEL_R2L (0x2 << 6) +#define RT5640_RXDP_SEL_SWAP (0x3 << 6) +#define RT5640_TXDC_SEL_MASK (0x3 << 4) +#define RT5640_TXDC_SEL_SFT 4 +#define RT5640_TXDC_SEL_NOR (0x0 << 4) +#define RT5640_TXDC_SEL_L2R (0x1 << 4) +#define RT5640_TXDC_SEL_R2L (0x2 << 4) +#define RT5640_TXDC_SEL_SWAP (0x3 << 4) +#define RT5640_TXDP_SEL_MASK (0x3 << 2) +#define RT5640_TXDP_SEL_SFT 2 +#define RT5640_TXDP_SEL_NOR (0x0 << 2) +#define RT5640_TXDP_SEL_L2R (0x1 << 2) +#define RT5640_TXDP_SEL_R2L (0x2 << 2) +#define RT5640_TRXDP_SEL_SWAP (0x3 << 2) + +/* Digital Interface Data Control (0x2f) */ +#define RT5640_IF1_DAC_SEL_MASK (0x3 << 14) +#define RT5640_IF1_DAC_SEL_SFT 14 +#define RT5640_IF1_DAC_SEL_NOR (0x0 << 14) +#define RT5640_IF1_DAC_SEL_SWAP (0x1 << 14) +#define RT5640_IF1_DAC_SEL_L2R (0x2 << 14) +#define RT5640_IF1_DAC_SEL_R2L (0x3 << 14) +#define RT5640_IF1_ADC_SEL_MASK (0x3 << 12) +#define RT5640_IF1_ADC_SEL_SFT 12 +#define RT5640_IF1_ADC_SEL_NOR (0x0 << 12) +#define RT5640_IF1_ADC_SEL_SWAP (0x1 << 12) +#define RT5640_IF1_ADC_SEL_L2R (0x2 << 12) +#define RT5640_IF1_ADC_SEL_R2L (0x3 << 12) +#define RT5640_IF2_DAC_SEL_MASK (0x3 << 10) +#define RT5640_IF2_DAC_SEL_SFT 10 +#define RT5640_IF2_DAC_SEL_NOR (0x0 << 10) +#define RT5640_IF2_DAC_SEL_SWAP (0x1 << 10) +#define RT5640_IF2_DAC_SEL_L2R (0x2 << 10) +#define RT5640_IF2_DAC_SEL_R2L (0x3 << 10) +#define RT5640_IF2_ADC_SEL_MASK (0x3 << 8) +#define RT5640_IF2_ADC_SEL_SFT 8 +#define RT5640_IF2_ADC_SEL_NOR (0x0 << 8) +#define RT5640_IF2_ADC_SEL_SWAP (0x1 << 8) +#define RT5640_IF2_ADC_SEL_L2R (0x2 << 8) +#define RT5640_IF2_ADC_SEL_R2L (0x3 << 8) +#define RT5640_IF3_DAC_SEL_MASK (0x3 << 6) +#define RT5640_IF3_DAC_SEL_SFT 6 +#define RT5640_IF3_DAC_SEL_NOR (0x0 << 6) +#define RT5640_IF3_DAC_SEL_SWAP (0x1 << 6) +#define RT5640_IF3_DAC_SEL_L2R (0x2 << 6) +#define RT5640_IF3_DAC_SEL_R2L (0x3 << 6) +#define RT5640_IF3_ADC_SEL_MASK (0x3 << 4) +#define RT5640_IF3_ADC_SEL_SFT 4 +#define RT5640_IF3_ADC_SEL_NOR (0x0 << 4) +#define RT5640_IF3_ADC_SEL_SWAP (0x1 << 4) +#define RT5640_IF3_ADC_SEL_L2R (0x2 << 4) +#define RT5640_IF3_ADC_SEL_R2L (0x3 << 4) + +/* REC Left Mixer Control 1 (0x3b) */ +#define RT5640_G_HP_L_RM_L_MASK (0x7 << 13) +#define RT5640_G_HP_L_RM_L_SFT 13 +#define RT5640_G_IN_L_RM_L_MASK (0x7 << 10) +#define RT5640_G_IN_L_RM_L_SFT 10 +#define RT5640_G_BST4_RM_L_MASK (0x7 << 7) +#define RT5640_G_BST4_RM_L_SFT 7 +#define RT5640_G_BST3_RM_L_MASK (0x7 << 4) +#define RT5640_G_BST3_RM_L_SFT 4 +#define RT5640_G_BST2_RM_L_MASK (0x7 << 1) +#define RT5640_G_BST2_RM_L_SFT 1 + +/* REC Left Mixer Control 2 (0x3c) */ +#define RT5640_G_BST1_RM_L_MASK (0x7 << 13) +#define RT5640_G_BST1_RM_L_SFT 13 +#define RT5640_G_OM_L_RM_L_MASK (0x7 << 10) +#define RT5640_G_OM_L_RM_L_SFT 10 +#define RT5640_M_HP_L_RM_L (0x1 << 6) +#define RT5640_M_HP_L_RM_L_SFT 6 +#define RT5640_M_IN_L_RM_L (0x1 << 5) +#define RT5640_M_IN_L_RM_L_SFT 5 +#define RT5640_M_BST4_RM_L (0x1 << 4) +#define RT5640_M_BST4_RM_L_SFT 4 +#define RT5640_M_BST3_RM_L (0x1 << 3) +#define RT5640_M_BST3_RM_L_SFT 3 +#define RT5640_M_BST2_RM_L (0x1 << 2) +#define RT5640_M_BST2_RM_L_SFT 2 +#define RT5640_M_BST1_RM_L (0x1 << 1) +#define RT5640_M_BST1_RM_L_SFT 1 +#define RT5640_M_OM_L_RM_L (0x1) +#define RT5640_M_OM_L_RM_L_SFT 0 + +/* REC Right Mixer Control 1 (0x3d) */ +#define RT5640_G_HP_R_RM_R_MASK (0x7 << 13) +#define RT5640_G_HP_R_RM_R_SFT 13 +#define RT5640_G_IN_R_RM_R_MASK (0x7 << 10) +#define RT5640_G_IN_R_RM_R_SFT 10 +#define RT5640_G_BST4_RM_R_MASK (0x7 << 7) +#define RT5640_G_BST4_RM_R_SFT 7 +#define RT5640_G_BST3_RM_R_MASK (0x7 << 4) +#define RT5640_G_BST3_RM_R_SFT 4 +#define RT5640_G_BST2_RM_R_MASK (0x7 << 1) +#define RT5640_G_BST2_RM_R_SFT 1 + +/* REC Right Mixer Control 2 (0x3e) */ +#define RT5640_G_BST1_RM_R_MASK (0x7 << 13) +#define RT5640_G_BST1_RM_R_SFT 13 +#define RT5640_G_OM_R_RM_R_MASK (0x7 << 10) +#define RT5640_G_OM_R_RM_R_SFT 10 +#define RT5640_M_HP_R_RM_R (0x1 << 6) +#define RT5640_M_HP_R_RM_R_SFT 6 +#define RT5640_M_IN_R_RM_R (0x1 << 5) +#define RT5640_M_IN_R_RM_R_SFT 5 +#define RT5640_M_BST4_RM_R (0x1 << 4) +#define RT5640_M_BST4_RM_R_SFT 4 +#define RT5640_M_BST3_RM_R (0x1 << 3) +#define RT5640_M_BST3_RM_R_SFT 3 +#define RT5640_M_BST2_RM_R (0x1 << 2) +#define RT5640_M_BST2_RM_R_SFT 2 +#define RT5640_M_BST1_RM_R (0x1 << 1) +#define RT5640_M_BST1_RM_R_SFT 1 +#define RT5640_M_OM_R_RM_R (0x1) +#define RT5640_M_OM_R_RM_R_SFT 0 + +/* HPMIX Control (0x45) */ +#define RT5640_M_DAC2_HM (0x1 << 15) +#define RT5640_M_DAC2_HM_SFT 15 +#define RT5640_M_DAC1_HM (0x1 << 14) +#define RT5640_M_DAC1_HM_SFT 14 +#define RT5640_M_HPVOL_HM (0x1 << 13) +#define RT5640_M_HPVOL_HM_SFT 13 +#define RT5640_G_HPOMIX_MASK (0x1 << 12) +#define RT5640_G_HPOMIX_SFT 12 + +/* SPK Left Mixer Control (0x46) */ +#define RT5640_G_RM_L_SM_L_MASK (0x3 << 14) +#define RT5640_G_RM_L_SM_L_SFT 14 +#define RT5640_G_IN_L_SM_L_MASK (0x3 << 12) +#define RT5640_G_IN_L_SM_L_SFT 12 +#define RT5640_G_DAC_L1_SM_L_MASK (0x3 << 10) +#define RT5640_G_DAC_L1_SM_L_SFT 10 +#define RT5640_G_DAC_L2_SM_L_MASK (0x3 << 8) +#define RT5640_G_DAC_L2_SM_L_SFT 8 +#define RT5640_G_OM_L_SM_L_MASK (0x3 << 6) +#define RT5640_G_OM_L_SM_L_SFT 6 +#define RT5640_M_RM_L_SM_L (0x1 << 5) +#define RT5640_M_RM_L_SM_L_SFT 5 +#define RT5640_M_IN_L_SM_L (0x1 << 4) +#define RT5640_M_IN_L_SM_L_SFT 4 +#define RT5640_M_DAC_L1_SM_L (0x1 << 3) +#define RT5640_M_DAC_L1_SM_L_SFT 3 +#define RT5640_M_DAC_L2_SM_L (0x1 << 2) +#define RT5640_M_DAC_L2_SM_L_SFT 2 +#define RT5640_M_OM_L_SM_L (0x1 << 1) +#define RT5640_M_OM_L_SM_L_SFT 1 + +/* SPK Right Mixer Control (0x47) */ +#define RT5640_G_RM_R_SM_R_MASK (0x3 << 14) +#define RT5640_G_RM_R_SM_R_SFT 14 +#define RT5640_G_IN_R_SM_R_MASK (0x3 << 12) +#define RT5640_G_IN_R_SM_R_SFT 12 +#define RT5640_G_DAC_R1_SM_R_MASK (0x3 << 10) +#define RT5640_G_DAC_R1_SM_R_SFT 10 +#define RT5640_G_DAC_R2_SM_R_MASK (0x3 << 8) +#define RT5640_G_DAC_R2_SM_R_SFT 8 +#define RT5640_G_OM_R_SM_R_MASK (0x3 << 6) +#define RT5640_G_OM_R_SM_R_SFT 6 +#define RT5640_M_RM_R_SM_R (0x1 << 5) +#define RT5640_M_RM_R_SM_R_SFT 5 +#define RT5640_M_IN_R_SM_R (0x1 << 4) +#define RT5640_M_IN_R_SM_R_SFT 4 +#define RT5640_M_DAC_R1_SM_R (0x1 << 3) +#define RT5640_M_DAC_R1_SM_R_SFT 3 +#define RT5640_M_DAC_R2_SM_R (0x1 << 2) +#define RT5640_M_DAC_R2_SM_R_SFT 2 +#define RT5640_M_OM_R_SM_R (0x1 << 1) +#define RT5640_M_OM_R_SM_R_SFT 1 + +/* SPOLMIX Control (0x48) */ +#define RT5640_M_DAC_R1_SPM_L (0x1 << 15) +#define RT5640_M_DAC_R1_SPM_L_SFT 15 +#define RT5640_M_DAC_L1_SPM_L (0x1 << 14) +#define RT5640_M_DAC_L1_SPM_L_SFT 14 +#define RT5640_M_SV_R_SPM_L (0x1 << 13) +#define RT5640_M_SV_R_SPM_L_SFT 13 +#define RT5640_M_SV_L_SPM_L (0x1 << 12) +#define RT5640_M_SV_L_SPM_L_SFT 12 +#define RT5640_M_BST1_SPM_L (0x1 << 11) +#define RT5640_M_BST1_SPM_L_SFT 11 + +/* SPORMIX Control (0x49) */ +#define RT5640_M_DAC_R1_SPM_R (0x1 << 13) +#define RT5640_M_DAC_R1_SPM_R_SFT 13 +#define RT5640_M_SV_R_SPM_R (0x1 << 12) +#define RT5640_M_SV_R_SPM_R_SFT 12 +#define RT5640_M_BST1_SPM_R (0x1 << 11) +#define RT5640_M_BST1_SPM_R_SFT 11 + +/* SPOLMIX / SPORMIX Ratio Control (0x4a) */ +#define RT5640_SPO_CLSD_RATIO_MASK (0x7) +#define RT5640_SPO_CLSD_RATIO_SFT 0 + +/* Mono Output Mixer Control (0x4c) */ +#define RT5640_M_DAC_R2_MM (0x1 << 15) +#define RT5640_M_DAC_R2_MM_SFT 15 +#define RT5640_M_DAC_L2_MM (0x1 << 14) +#define RT5640_M_DAC_L2_MM_SFT 14 +#define RT5640_M_OV_R_MM (0x1 << 13) +#define RT5640_M_OV_R_MM_SFT 13 +#define RT5640_M_OV_L_MM (0x1 << 12) +#define RT5640_M_OV_L_MM_SFT 12 +#define RT5640_M_BST1_MM (0x1 << 11) +#define RT5640_M_BST1_MM_SFT 11 +#define RT5640_G_MONOMIX_MASK (0x1 << 10) +#define RT5640_G_MONOMIX_SFT 10 + +/* Output Left Mixer Control 1 (0x4d) */ +#define RT5640_G_BST3_OM_L_MASK (0x7 << 13) +#define RT5640_G_BST3_OM_L_SFT 13 +#define RT5640_G_BST2_OM_L_MASK (0x7 << 10) +#define RT5640_G_BST2_OM_L_SFT 10 +#define RT5640_G_BST1_OM_L_MASK (0x7 << 7) +#define RT5640_G_BST1_OM_L_SFT 7 +#define RT5640_G_IN_L_OM_L_MASK (0x7 << 4) +#define RT5640_G_IN_L_OM_L_SFT 4 +#define RT5640_G_RM_L_OM_L_MASK (0x7 << 1) +#define RT5640_G_RM_L_OM_L_SFT 1 + +/* Output Left Mixer Control 2 (0x4e) */ +#define RT5640_G_DAC_R2_OM_L_MASK (0x7 << 13) +#define RT5640_G_DAC_R2_OM_L_SFT 13 +#define RT5640_G_DAC_L2_OM_L_MASK (0x7 << 10) +#define RT5640_G_DAC_L2_OM_L_SFT 10 +#define RT5640_G_DAC_L1_OM_L_MASK (0x7 << 7) +#define RT5640_G_DAC_L1_OM_L_SFT 7 + +/* Output Left Mixer Control 3 (0x4f) */ +#define RT5640_M_SM_L_OM_L (0x1 << 8) +#define RT5640_M_SM_L_OM_L_SFT 8 +#define RT5640_M_BST3_OM_L (0x1 << 7) +#define RT5640_M_BST3_OM_L_SFT 7 +#define RT5640_M_BST2_OM_L (0x1 << 6) +#define RT5640_M_BST2_OM_L_SFT 6 +#define RT5640_M_BST1_OM_L (0x1 << 5) +#define RT5640_M_BST1_OM_L_SFT 5 +#define RT5640_M_IN_L_OM_L (0x1 << 4) +#define RT5640_M_IN_L_OM_L_SFT 4 +#define RT5640_M_RM_L_OM_L (0x1 << 3) +#define RT5640_M_RM_L_OM_L_SFT 3 +#define RT5640_M_DAC_R2_OM_L (0x1 << 2) +#define RT5640_M_DAC_R2_OM_L_SFT 2 +#define RT5640_M_DAC_L2_OM_L (0x1 << 1) +#define RT5640_M_DAC_L2_OM_L_SFT 1 +#define RT5640_M_DAC_L1_OM_L (0x1) +#define RT5640_M_DAC_L1_OM_L_SFT 0 + +/* Output Right Mixer Control 1 (0x50) */ +#define RT5640_G_BST4_OM_R_MASK (0x7 << 13) +#define RT5640_G_BST4_OM_R_SFT 13 +#define RT5640_G_BST2_OM_R_MASK (0x7 << 10) +#define RT5640_G_BST2_OM_R_SFT 10 +#define RT5640_G_BST1_OM_R_MASK (0x7 << 7) +#define RT5640_G_BST1_OM_R_SFT 7 +#define RT5640_G_IN_R_OM_R_MASK (0x7 << 4) +#define RT5640_G_IN_R_OM_R_SFT 4 +#define RT5640_G_RM_R_OM_R_MASK (0x7 << 1) +#define RT5640_G_RM_R_OM_R_SFT 1 + +/* Output Right Mixer Control 2 (0x51) */ +#define RT5640_G_DAC_L2_OM_R_MASK (0x7 << 13) +#define RT5640_G_DAC_L2_OM_R_SFT 13 +#define RT5640_G_DAC_R2_OM_R_MASK (0x7 << 10) +#define RT5640_G_DAC_R2_OM_R_SFT 10 +#define RT5640_G_DAC_R1_OM_R_MASK (0x7 << 7) +#define RT5640_G_DAC_R1_OM_R_SFT 7 + +/* Output Right Mixer Control 3 (0x52) */ +#define RT5640_M_SM_L_OM_R (0x1 << 8) +#define RT5640_M_SM_L_OM_R_SFT 8 +#define RT5640_M_BST4_OM_R (0x1 << 7) +#define RT5640_M_BST4_OM_R_SFT 7 +#define RT5640_M_BST2_OM_R (0x1 << 6) +#define RT5640_M_BST2_OM_R_SFT 6 +#define RT5640_M_BST1_OM_R (0x1 << 5) +#define RT5640_M_BST1_OM_R_SFT 5 +#define RT5640_M_IN_R_OM_R (0x1 << 4) +#define RT5640_M_IN_R_OM_R_SFT 4 +#define RT5640_M_RM_R_OM_R (0x1 << 3) +#define RT5640_M_RM_R_OM_R_SFT 3 +#define RT5640_M_DAC_L2_OM_R (0x1 << 2) +#define RT5640_M_DAC_L2_OM_R_SFT 2 +#define RT5640_M_DAC_R2_OM_R (0x1 << 1) +#define RT5640_M_DAC_R2_OM_R_SFT 1 +#define RT5640_M_DAC_R1_OM_R (0x1) +#define RT5640_M_DAC_R1_OM_R_SFT 0 + +/* LOUT Mixer Control (0x53) */ +#define RT5640_M_DAC_L1_LM (0x1 << 15) +#define RT5640_M_DAC_L1_LM_SFT 15 +#define RT5640_M_DAC_R1_LM (0x1 << 14) +#define RT5640_M_DAC_R1_LM_SFT 14 +#define RT5640_M_OV_L_LM (0x1 << 13) +#define RT5640_M_OV_L_LM_SFT 13 +#define RT5640_M_OV_R_LM (0x1 << 12) +#define RT5640_M_OV_R_LM_SFT 12 +#define RT5640_G_LOUTMIX_MASK (0x1 << 11) +#define RT5640_G_LOUTMIX_SFT 11 + +/* Power Management for Digital 1 (0x61) */ +#define RT5640_PWR_I2S1 (0x1 << 15) +#define RT5640_PWR_I2S1_BIT 15 +#define RT5640_PWR_I2S2 (0x1 << 14) +#define RT5640_PWR_I2S2_BIT 14 +#define RT5640_PWR_DAC_L1 (0x1 << 12) +#define RT5640_PWR_DAC_L1_BIT 12 +#define RT5640_PWR_DAC_R1 (0x1 << 11) +#define RT5640_PWR_DAC_R1_BIT 11 +#define RT5640_PWR_DAC_L2 (0x1 << 7) +#define RT5640_PWR_DAC_L2_BIT 7 +#define RT5640_PWR_DAC_R2 (0x1 << 6) +#define RT5640_PWR_DAC_R2_BIT 6 +#define RT5640_PWR_ADC_L (0x1 << 2) +#define RT5640_PWR_ADC_L_BIT 2 +#define RT5640_PWR_ADC_R (0x1 << 1) +#define RT5640_PWR_ADC_R_BIT 1 +#define RT5640_PWR_CLS_D (0x1) +#define RT5640_PWR_CLS_D_BIT 0 + +/* Power Management for Digital 2 (0x62) */ +#define RT5640_PWR_ADC_SF (0x1 << 15) +#define RT5640_PWR_ADC_SF_BIT 15 +#define RT5640_PWR_ADC_MF_L (0x1 << 14) +#define RT5640_PWR_ADC_MF_L_BIT 14 +#define RT5640_PWR_ADC_MF_R (0x1 << 13) +#define RT5640_PWR_ADC_MF_R_BIT 13 +#define RT5640_PWR_I2S_DSP (0x1 << 12) +#define RT5640_PWR_I2S_DSP_BIT 12 + +/* Power Management for Analog 1 (0x63) */ +#define RT5640_PWR_VREF1 (0x1 << 15) +#define RT5640_PWR_VREF1_BIT 15 +#define RT5640_PWR_FV1 (0x1 << 14) +#define RT5640_PWR_FV1_BIT 14 +#define RT5640_PWR_MB (0x1 << 13) +#define RT5640_PWR_MB_BIT 13 +#define RT5640_PWR_LM (0x1 << 12) +#define RT5640_PWR_LM_BIT 12 +#define RT5640_PWR_BG (0x1 << 11) +#define RT5640_PWR_BG_BIT 11 +#define RT5640_PWR_MM (0x1 << 10) +#define RT5640_PWR_MM_BIT 10 +#define RT5640_PWR_MA (0x1 << 8) +#define RT5640_PWR_MA_BIT 8 +#define RT5640_PWR_HP_L (0x1 << 7) +#define RT5640_PWR_HP_L_BIT 7 +#define RT5640_PWR_HP_R (0x1 << 6) +#define RT5640_PWR_HP_R_BIT 6 +#define RT5640_PWR_HA (0x1 << 5) +#define RT5640_PWR_HA_BIT 5 +#define RT5640_PWR_VREF2 (0x1 << 4) +#define RT5640_PWR_VREF2_BIT 4 +#define RT5640_PWR_FV2 (0x1 << 3) +#define RT5640_PWR_FV2_BIT 3 +#define RT5640_PWR_LDO2 (0x1 << 2) +#define RT5640_PWR_LDO2_BIT 2 + +/* Power Management for Analog 2 (0x64) */ +#define RT5640_PWR_BST1 (0x1 << 15) +#define RT5640_PWR_BST1_BIT 15 +#define RT5640_PWR_BST2 (0x1 << 14) +#define RT5640_PWR_BST2_BIT 14 +#define RT5640_PWR_BST3 (0x1 << 13) +#define RT5640_PWR_BST3_BIT 13 +#define RT5640_PWR_BST4 (0x1 << 12) +#define RT5640_PWR_BST4_BIT 12 +#define RT5640_PWR_MB1 (0x1 << 11) +#define RT5640_PWR_MB1_BIT 11 +#define RT5640_PWR_PLL (0x1 << 9) +#define RT5640_PWR_PLL_BIT 9 + +/* Power Management for Mixer (0x65) */ +#define RT5640_PWR_OM_L (0x1 << 15) +#define RT5640_PWR_OM_L_BIT 15 +#define RT5640_PWR_OM_R (0x1 << 14) +#define RT5640_PWR_OM_R_BIT 14 +#define RT5640_PWR_SM_L (0x1 << 13) +#define RT5640_PWR_SM_L_BIT 13 +#define RT5640_PWR_SM_R (0x1 << 12) +#define RT5640_PWR_SM_R_BIT 12 +#define RT5640_PWR_RM_L (0x1 << 11) +#define RT5640_PWR_RM_L_BIT 11 +#define RT5640_PWR_RM_R (0x1 << 10) +#define RT5640_PWR_RM_R_BIT 10 + +/* Power Management for Volume (0x66) */ +#define RT5640_PWR_SV_L (0x1 << 15) +#define RT5640_PWR_SV_L_BIT 15 +#define RT5640_PWR_SV_R (0x1 << 14) +#define RT5640_PWR_SV_R_BIT 14 +#define RT5640_PWR_OV_L (0x1 << 13) +#define RT5640_PWR_OV_L_BIT 13 +#define RT5640_PWR_OV_R (0x1 << 12) +#define RT5640_PWR_OV_R_BIT 12 +#define RT5640_PWR_HV_L (0x1 << 11) +#define RT5640_PWR_HV_L_BIT 11 +#define RT5640_PWR_HV_R (0x1 << 10) +#define RT5640_PWR_HV_R_BIT 10 +#define RT5640_PWR_IN_L (0x1 << 9) +#define RT5640_PWR_IN_L_BIT 9 +#define RT5640_PWR_IN_R (0x1 << 8) +#define RT5640_PWR_IN_R_BIT 8 + +/* I2S1/2/3 Audio Serial Data Port Control (0x70 0x71 0x72) */ +#define RT5640_I2S_MS_MASK (0x1 << 15) +#define RT5640_I2S_MS_SFT 15 +#define RT5640_I2S_MS_M (0x0 << 15) +#define RT5640_I2S_MS_S (0x1 << 15) +#define RT5640_I2S_IF_MASK (0x7 << 12) +#define RT5640_I2S_IF_SFT 12 +#define RT5640_I2S_O_CP_MASK (0x3 << 10) +#define RT5640_I2S_O_CP_SFT 10 +#define RT5640_I2S_O_CP_OFF (0x0 << 10) +#define RT5640_I2S_O_CP_U_LAW (0x1 << 10) +#define RT5640_I2S_O_CP_A_LAW (0x2 << 10) +#define RT5640_I2S_I_CP_MASK (0x3 << 8) +#define RT5640_I2S_I_CP_SFT 8 +#define RT5640_I2S_I_CP_OFF (0x0 << 8) +#define RT5640_I2S_I_CP_U_LAW (0x1 << 8) +#define RT5640_I2S_I_CP_A_LAW (0x2 << 8) +#define RT5640_I2S_BP_MASK (0x1 << 7) +#define RT5640_I2S_BP_SFT 7 +#define RT5640_I2S_BP_NOR (0x0 << 7) +#define RT5640_I2S_BP_INV (0x1 << 7) +#define RT5640_I2S_DL_MASK (0x3 << 2) +#define RT5640_I2S_DL_SFT 2 +#define RT5640_I2S_DL_16 (0x0 << 2) +#define RT5640_I2S_DL_20 (0x1 << 2) +#define RT5640_I2S_DL_24 (0x2 << 2) +#define RT5640_I2S_DL_8 (0x3 << 2) +#define RT5640_I2S_DF_MASK (0x3) +#define RT5640_I2S_DF_SFT 0 +#define RT5640_I2S_DF_I2S (0x0) +#define RT5640_I2S_DF_LEFT (0x1) +#define RT5640_I2S_DF_PCM_A (0x2) +#define RT5640_I2S_DF_PCM_B (0x3) + +/* I2S2 Audio Serial Data Port Control (0x71) */ +#define RT5640_I2S2_SDI_MASK (0x1 << 6) +#define RT5640_I2S2_SDI_SFT 6 +#define RT5640_I2S2_SDI_I2S1 (0x0 << 6) +#define RT5640_I2S2_SDI_I2S2 (0x1 << 6) + +/* ADC/DAC Clock Control 1 (0x73) */ +#define RT5640_I2S_BCLK_MS1_MASK (0x1 << 15) +#define RT5640_I2S_BCLK_MS1_SFT 15 +#define RT5640_I2S_BCLK_MS1_32 (0x0 << 15) +#define RT5640_I2S_BCLK_MS1_64 (0x1 << 15) +#define RT5640_I2S_PD1_MASK (0x7 << 12) +#define RT5640_I2S_PD1_SFT 12 +#define RT5640_I2S_PD1_1 (0x0 << 12) +#define RT5640_I2S_PD1_2 (0x1 << 12) +#define RT5640_I2S_PD1_3 (0x2 << 12) +#define RT5640_I2S_PD1_4 (0x3 << 12) +#define RT5640_I2S_PD1_6 (0x4 << 12) +#define RT5640_I2S_PD1_8 (0x5 << 12) +#define RT5640_I2S_PD1_12 (0x6 << 12) +#define RT5640_I2S_PD1_16 (0x7 << 12) +#define RT5640_I2S_BCLK_MS2_MASK (0x1 << 11) +#define RT5640_I2S_BCLK_MS2_SFT 11 +#define RT5640_I2S_BCLK_MS2_32 (0x0 << 11) +#define RT5640_I2S_BCLK_MS2_64 (0x1 << 11) +#define RT5640_I2S_PD2_MASK (0x7 << 8) +#define RT5640_I2S_PD2_SFT 8 +#define RT5640_I2S_PD2_1 (0x0 << 8) +#define RT5640_I2S_PD2_2 (0x1 << 8) +#define RT5640_I2S_PD2_3 (0x2 << 8) +#define RT5640_I2S_PD2_4 (0x3 << 8) +#define RT5640_I2S_PD2_6 (0x4 << 8) +#define RT5640_I2S_PD2_8 (0x5 << 8) +#define RT5640_I2S_PD2_12 (0x6 << 8) +#define RT5640_I2S_PD2_16 (0x7 << 8) +#define RT5640_I2S_BCLK_MS3_MASK (0x1 << 7) +#define RT5640_I2S_BCLK_MS3_SFT 7 +#define RT5640_I2S_BCLK_MS3_32 (0x0 << 7) +#define RT5640_I2S_BCLK_MS3_64 (0x1 << 7) +#define RT5640_I2S_PD3_MASK (0x7 << 4) +#define RT5640_I2S_PD3_SFT 4 +#define RT5640_I2S_PD3_1 (0x0 << 4) +#define RT5640_I2S_PD3_2 (0x1 << 4) +#define RT5640_I2S_PD3_3 (0x2 << 4) +#define RT5640_I2S_PD3_4 (0x3 << 4) +#define RT5640_I2S_PD3_6 (0x4 << 4) +#define RT5640_I2S_PD3_8 (0x5 << 4) +#define RT5640_I2S_PD3_12 (0x6 << 4) +#define RT5640_I2S_PD3_16 (0x7 << 4) +#define RT5640_DAC_OSR_MASK (0x3 << 2) +#define RT5640_DAC_OSR_SFT 2 +#define RT5640_DAC_OSR_128 (0x0 << 2) +#define RT5640_DAC_OSR_64 (0x1 << 2) +#define RT5640_DAC_OSR_32 (0x2 << 2) +#define RT5640_DAC_OSR_16 (0x3 << 2) +#define RT5640_ADC_OSR_MASK (0x3) +#define RT5640_ADC_OSR_SFT 0 +#define RT5640_ADC_OSR_128 (0x0) +#define RT5640_ADC_OSR_64 (0x1) +#define RT5640_ADC_OSR_32 (0x2) +#define RT5640_ADC_OSR_16 (0x3) + +/* ADC/DAC Clock Control 2 (0x74) */ +#define RT5640_DAC_L_OSR_MASK (0x3 << 14) +#define RT5640_DAC_L_OSR_SFT 14 +#define RT5640_DAC_L_OSR_128 (0x0 << 14) +#define RT5640_DAC_L_OSR_64 (0x1 << 14) +#define RT5640_DAC_L_OSR_32 (0x2 << 14) +#define RT5640_DAC_L_OSR_16 (0x3 << 14) +#define RT5640_ADC_R_OSR_MASK (0x3 << 12) +#define RT5640_ADC_R_OSR_SFT 12 +#define RT5640_ADC_R_OSR_128 (0x0 << 12) +#define RT5640_ADC_R_OSR_64 (0x1 << 12) +#define RT5640_ADC_R_OSR_32 (0x2 << 12) +#define RT5640_ADC_R_OSR_16 (0x3 << 12) +#define RT5640_DAHPF_EN (0x1 << 11) +#define RT5640_DAHPF_EN_SFT 11 +#define RT5640_ADHPF_EN (0x1 << 10) +#define RT5640_ADHPF_EN_SFT 10 + +/* Digital Microphone Control (0x75) */ +#define RT5640_DMIC_1_EN_MASK (0x1 << 15) +#define RT5640_DMIC_1_EN_SFT 15 +#define RT5640_DMIC_1_DIS (0x0 << 15) +#define RT5640_DMIC_1_EN (0x1 << 15) +#define RT5640_DMIC_2_EN_MASK (0x1 << 14) +#define RT5640_DMIC_2_EN_SFT 14 +#define RT5640_DMIC_2_DIS (0x0 << 14) +#define RT5640_DMIC_2_EN (0x1 << 14) +#define RT5640_DMIC_1L_LH_MASK (0x1 << 13) +#define RT5640_DMIC_1L_LH_SFT 13 +#define RT5640_DMIC_1L_LH_FALLING (0x0 << 13) +#define RT5640_DMIC_1L_LH_RISING (0x1 << 13) +#define RT5640_DMIC_1R_LH_MASK (0x1 << 12) +#define RT5640_DMIC_1R_LH_SFT 12 +#define RT5640_DMIC_1R_LH_FALLING (0x0 << 12) +#define RT5640_DMIC_1R_LH_RISING (0x1 << 12) +#define RT5640_DMIC_1_DP_MASK (0x1 << 11) +#define RT5640_DMIC_1_DP_SFT 11 +#define RT5640_DMIC_1_DP_GPIO3 (0x0 << 11) +#define RT5640_DMIC_1_DP_IN1P (0x1 << 11) +#define RT5640_DMIC_2_DP_MASK (0x1 << 10) +#define RT5640_DMIC_2_DP_SFT 10 +#define RT5640_DMIC_2_DP_GPIO4 (0x0 << 10) +#define RT5640_DMIC_2_DP_IN1N (0x1 << 10) +#define RT5640_DMIC_2L_LH_MASK (0x1 << 9) +#define RT5640_DMIC_2L_LH_SFT 9 +#define RT5640_DMIC_2L_LH_FALLING (0x0 << 9) +#define RT5640_DMIC_2L_LH_RISING (0x1 << 9) +#define RT5640_DMIC_2R_LH_MASK (0x1 << 8) +#define RT5640_DMIC_2R_LH_SFT 8 +#define RT5640_DMIC_2R_LH_FALLING (0x0 << 8) +#define RT5640_DMIC_2R_LH_RISING (0x1 << 8) +#define RT5640_DMIC_CLK_MASK (0x7 << 5) +#define RT5640_DMIC_CLK_SFT 5 + +/* Global Clock Control (0x80) */ +#define RT5640_SCLK_SRC_MASK (0x3 << 14) +#define RT5640_SCLK_SRC_SFT 14 +#define RT5640_SCLK_SRC_MCLK (0x0 << 14) +#define RT5640_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5640_SCLK_SRC_RCCLK (0x2 << 14) +#define RT5640_PLL1_SRC_MASK (0x3 << 12) +#define RT5640_PLL1_SRC_SFT 12 +#define RT5640_PLL1_SRC_MCLK (0x0 << 12) +#define RT5640_PLL1_SRC_BCLK1 (0x1 << 12) +#define RT5640_PLL1_SRC_BCLK2 (0x2 << 12) +#define RT5640_PLL1_SRC_BCLK3 (0x3 << 12) +#define RT5640_PLL1_PD_MASK (0x1 << 3) +#define RT5640_PLL1_PD_SFT 3 +#define RT5640_PLL1_PD_1 (0x0 << 3) +#define RT5640_PLL1_PD_2 (0x1 << 3) + +#define RT5640_PLL_INP_MAX 40000000 +#define RT5640_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x81) */ +#define RT5640_PLL_N_MAX 0x1ff +#define RT5640_PLL_N_MASK (RT5640_PLL_N_MAX << 7) +#define RT5640_PLL_N_SFT 7 +#define RT5640_PLL_K_MAX 0x1f +#define RT5640_PLL_K_MASK (RT5640_PLL_K_MAX) +#define RT5640_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x82) */ +#define RT5640_PLL_M_MAX 0xf +#define RT5640_PLL_M_MASK (RT5640_PLL_M_MAX << 12) +#define RT5640_PLL_M_SFT 12 +#define RT5640_PLL_M_BP (0x1 << 11) +#define RT5640_PLL_M_BP_SFT 11 + +/* ASRC Control 1 (0x83) */ +#define RT5640_STO_T_MASK (0x1 << 15) +#define RT5640_STO_T_SFT 15 +#define RT5640_STO_T_SCLK (0x0 << 15) +#define RT5640_STO_T_LRCK1 (0x1 << 15) +#define RT5640_M1_T_MASK (0x1 << 14) +#define RT5640_M1_T_SFT 14 +#define RT5640_M1_T_I2S2 (0x0 << 14) +#define RT5640_M1_T_I2S2_D3 (0x1 << 14) +#define RT5640_I2S2_F_MASK (0x1 << 12) +#define RT5640_I2S2_F_SFT 12 +#define RT5640_I2S2_F_I2S2_D2 (0x0 << 12) +#define RT5640_I2S2_F_I2S1_TCLK (0x1 << 12) +#define RT5640_DMIC_1_M_MASK (0x1 << 9) +#define RT5640_DMIC_1_M_SFT 9 +#define RT5640_DMIC_1_M_NOR (0x0 << 9) +#define RT5640_DMIC_1_M_ASYN (0x1 << 9) +#define RT5640_DMIC_2_M_MASK (0x1 << 8) +#define RT5640_DMIC_2_M_SFT 8 +#define RT5640_DMIC_2_M_NOR (0x0 << 8) +#define RT5640_DMIC_2_M_ASYN (0x1 << 8) + +/* ASRC clock source selection (0x84) */ +#define RT5640_CLK_SEL_SYS (0x0) +#define RT5640_CLK_SEL_ASRC (0x1) + +/* ASRC Control 2 (0x84) */ +#define RT5640_MDA_L_M_MASK (0x1 << 15) +#define RT5640_MDA_L_M_SFT 15 +#define RT5640_MDA_L_M_NOR (0x0 << 15) +#define RT5640_MDA_L_M_ASYN (0x1 << 15) +#define RT5640_MDA_R_M_MASK (0x1 << 14) +#define RT5640_MDA_R_M_SFT 14 +#define RT5640_MDA_R_M_NOR (0x0 << 14) +#define RT5640_MDA_R_M_ASYN (0x1 << 14) +#define RT5640_MAD_L_M_MASK (0x1 << 13) +#define RT5640_MAD_L_M_SFT 13 +#define RT5640_MAD_L_M_NOR (0x0 << 13) +#define RT5640_MAD_L_M_ASYN (0x1 << 13) +#define RT5640_MAD_R_M_MASK (0x1 << 12) +#define RT5640_MAD_R_M_SFT 12 +#define RT5640_MAD_R_M_NOR (0x0 << 12) +#define RT5640_MAD_R_M_ASYN (0x1 << 12) +#define RT5640_ADC_M_MASK (0x1 << 11) +#define RT5640_ADC_M_SFT 11 +#define RT5640_ADC_M_NOR (0x0 << 11) +#define RT5640_ADC_M_ASYN (0x1 << 11) +#define RT5640_STO_DAC_M_MASK (0x1 << 5) +#define RT5640_STO_DAC_M_SFT 5 +#define RT5640_STO_DAC_M_NOR (0x0 << 5) +#define RT5640_STO_DAC_M_ASYN (0x1 << 5) +#define RT5640_I2S1_R_D_MASK (0x1 << 4) +#define RT5640_I2S1_R_D_SFT 4 +#define RT5640_I2S1_R_D_DIS (0x0 << 4) +#define RT5640_I2S1_R_D_EN (0x1 << 4) +#define RT5640_I2S2_R_D_MASK (0x1 << 3) +#define RT5640_I2S2_R_D_SFT 3 +#define RT5640_I2S2_R_D_DIS (0x0 << 3) +#define RT5640_I2S2_R_D_EN (0x1 << 3) +#define RT5640_PRE_SCLK_MASK (0x3) +#define RT5640_PRE_SCLK_SFT 0 +#define RT5640_PRE_SCLK_512 (0x0) +#define RT5640_PRE_SCLK_1024 (0x1) +#define RT5640_PRE_SCLK_2048 (0x2) + +/* ASRC Control 3 (0x85) */ +#define RT5640_I2S1_RATE_MASK (0xf << 12) +#define RT5640_I2S1_RATE_SFT 12 +#define RT5640_I2S2_RATE_MASK (0xf << 8) +#define RT5640_I2S2_RATE_SFT 8 + +/* ASRC Control 4 (0x89) */ +#define RT5640_I2S1_PD_MASK (0x7 << 12) +#define RT5640_I2S1_PD_SFT 12 +#define RT5640_I2S2_PD_MASK (0x7 << 8) +#define RT5640_I2S2_PD_SFT 8 + +/* HPOUT Over Current Detection (0x8b) */ +#define RT5640_HP_OVCD_MASK (0x1 << 10) +#define RT5640_HP_OVCD_SFT 10 +#define RT5640_HP_OVCD_DIS (0x0 << 10) +#define RT5640_HP_OVCD_EN (0x1 << 10) +#define RT5640_HP_OC_TH_MASK (0x3 << 8) +#define RT5640_HP_OC_TH_SFT 8 +#define RT5640_HP_OC_TH_90 (0x0 << 8) +#define RT5640_HP_OC_TH_105 (0x1 << 8) +#define RT5640_HP_OC_TH_120 (0x2 << 8) +#define RT5640_HP_OC_TH_135 (0x3 << 8) + +/* Class D Over Current Control (0x8c) */ +#define RT5640_CLSD_OC_MASK (0x1 << 9) +#define RT5640_CLSD_OC_SFT 9 +#define RT5640_CLSD_OC_PU (0x0 << 9) +#define RT5640_CLSD_OC_PD (0x1 << 9) +#define RT5640_AUTO_PD_MASK (0x1 << 8) +#define RT5640_AUTO_PD_SFT 8 +#define RT5640_AUTO_PD_DIS (0x0 << 8) +#define RT5640_AUTO_PD_EN (0x1 << 8) +#define RT5640_CLSD_OC_TH_MASK (0x3f) +#define RT5640_CLSD_OC_TH_SFT 0 + +/* Class D Output Control (0x8d) */ +#define RT5640_CLSD_RATIO_MASK (0xf << 12) +#define RT5640_CLSD_RATIO_SFT 12 +#define RT5640_CLSD_OM_MASK (0x1 << 11) +#define RT5640_CLSD_OM_SFT 11 +#define RT5640_CLSD_OM_MONO (0x0 << 11) +#define RT5640_CLSD_OM_STO (0x1 << 11) +#define RT5640_CLSD_SCH_MASK (0x1 << 10) +#define RT5640_CLSD_SCH_SFT 10 +#define RT5640_CLSD_SCH_L (0x0 << 10) +#define RT5640_CLSD_SCH_S (0x1 << 10) + +/* Depop Mode Control 1 (0x8e) */ +#define RT5640_SMT_TRIG_MASK (0x1 << 15) +#define RT5640_SMT_TRIG_SFT 15 +#define RT5640_SMT_TRIG_DIS (0x0 << 15) +#define RT5640_SMT_TRIG_EN (0x1 << 15) +#define RT5640_HP_L_SMT_MASK (0x1 << 9) +#define RT5640_HP_L_SMT_SFT 9 +#define RT5640_HP_L_SMT_DIS (0x0 << 9) +#define RT5640_HP_L_SMT_EN (0x1 << 9) +#define RT5640_HP_R_SMT_MASK (0x1 << 8) +#define RT5640_HP_R_SMT_SFT 8 +#define RT5640_HP_R_SMT_DIS (0x0 << 8) +#define RT5640_HP_R_SMT_EN (0x1 << 8) +#define RT5640_HP_CD_PD_MASK (0x1 << 7) +#define RT5640_HP_CD_PD_SFT 7 +#define RT5640_HP_CD_PD_DIS (0x0 << 7) +#define RT5640_HP_CD_PD_EN (0x1 << 7) +#define RT5640_RSTN_MASK (0x1 << 6) +#define RT5640_RSTN_SFT 6 +#define RT5640_RSTN_DIS (0x0 << 6) +#define RT5640_RSTN_EN (0x1 << 6) +#define RT5640_RSTP_MASK (0x1 << 5) +#define RT5640_RSTP_SFT 5 +#define RT5640_RSTP_DIS (0x0 << 5) +#define RT5640_RSTP_EN (0x1 << 5) +#define RT5640_HP_CO_MASK (0x1 << 4) +#define RT5640_HP_CO_SFT 4 +#define RT5640_HP_CO_DIS (0x0 << 4) +#define RT5640_HP_CO_EN (0x1 << 4) +#define RT5640_HP_CP_MASK (0x1 << 3) +#define RT5640_HP_CP_SFT 3 +#define RT5640_HP_CP_PD (0x0 << 3) +#define RT5640_HP_CP_PU (0x1 << 3) +#define RT5640_HP_SG_MASK (0x1 << 2) +#define RT5640_HP_SG_SFT 2 +#define RT5640_HP_SG_DIS (0x0 << 2) +#define RT5640_HP_SG_EN (0x1 << 2) +#define RT5640_HP_DP_MASK (0x1 << 1) +#define RT5640_HP_DP_SFT 1 +#define RT5640_HP_DP_PD (0x0 << 1) +#define RT5640_HP_DP_PU (0x1 << 1) +#define RT5640_HP_CB_MASK (0x1) +#define RT5640_HP_CB_SFT 0 +#define RT5640_HP_CB_PD (0x0) +#define RT5640_HP_CB_PU (0x1) + +/* Depop Mode Control 2 (0x8f) */ +#define RT5640_DEPOP_MASK (0x1 << 13) +#define RT5640_DEPOP_SFT 13 +#define RT5640_DEPOP_AUTO (0x0 << 13) +#define RT5640_DEPOP_MAN (0x1 << 13) +#define RT5640_RAMP_MASK (0x1 << 12) +#define RT5640_RAMP_SFT 12 +#define RT5640_RAMP_DIS (0x0 << 12) +#define RT5640_RAMP_EN (0x1 << 12) +#define RT5640_BPS_MASK (0x1 << 11) +#define RT5640_BPS_SFT 11 +#define RT5640_BPS_DIS (0x0 << 11) +#define RT5640_BPS_EN (0x1 << 11) +#define RT5640_FAST_UPDN_MASK (0x1 << 10) +#define RT5640_FAST_UPDN_SFT 10 +#define RT5640_FAST_UPDN_DIS (0x0 << 10) +#define RT5640_FAST_UPDN_EN (0x1 << 10) +#define RT5640_MRES_MASK (0x3 << 8) +#define RT5640_MRES_SFT 8 +#define RT5640_MRES_15MO (0x0 << 8) +#define RT5640_MRES_25MO (0x1 << 8) +#define RT5640_MRES_35MO (0x2 << 8) +#define RT5640_MRES_45MO (0x3 << 8) +#define RT5640_VLO_MASK (0x1 << 7) +#define RT5640_VLO_SFT 7 +#define RT5640_VLO_3V (0x0 << 7) +#define RT5640_VLO_32V (0x1 << 7) +#define RT5640_DIG_DP_MASK (0x1 << 6) +#define RT5640_DIG_DP_SFT 6 +#define RT5640_DIG_DP_DIS (0x0 << 6) +#define RT5640_DIG_DP_EN (0x1 << 6) +#define RT5640_DP_TH_MASK (0x3 << 4) +#define RT5640_DP_TH_SFT 4 + +/* Depop Mode Control 3 (0x90) */ +#define RT5640_CP_SYS_MASK (0x7 << 12) +#define RT5640_CP_SYS_SFT 12 +#define RT5640_CP_FQ1_MASK (0x7 << 8) +#define RT5640_CP_FQ1_SFT 8 +#define RT5640_CP_FQ2_MASK (0x7 << 4) +#define RT5640_CP_FQ2_SFT 4 +#define RT5640_CP_FQ3_MASK (0x7) +#define RT5640_CP_FQ3_SFT 0 +#define RT5640_CP_FQ_1_5_KHZ 0 +#define RT5640_CP_FQ_3_KHZ 1 +#define RT5640_CP_FQ_6_KHZ 2 +#define RT5640_CP_FQ_12_KHZ 3 +#define RT5640_CP_FQ_24_KHZ 4 +#define RT5640_CP_FQ_48_KHZ 5 +#define RT5640_CP_FQ_96_KHZ 6 +#define RT5640_CP_FQ_192_KHZ 7 + +/* HPOUT charge pump (0x91) */ +#define RT5640_OSW_L_MASK (0x1 << 11) +#define RT5640_OSW_L_SFT 11 +#define RT5640_OSW_L_DIS (0x0 << 11) +#define RT5640_OSW_L_EN (0x1 << 11) +#define RT5640_OSW_R_MASK (0x1 << 10) +#define RT5640_OSW_R_SFT 10 +#define RT5640_OSW_R_DIS (0x0 << 10) +#define RT5640_OSW_R_EN (0x1 << 10) +#define RT5640_PM_HP_MASK (0x3 << 8) +#define RT5640_PM_HP_SFT 8 +#define RT5640_PM_HP_LV (0x0 << 8) +#define RT5640_PM_HP_MV (0x1 << 8) +#define RT5640_PM_HP_HV (0x2 << 8) +#define RT5640_IB_HP_MASK (0x3 << 6) +#define RT5640_IB_HP_SFT 6 +#define RT5640_IB_HP_125IL (0x0 << 6) +#define RT5640_IB_HP_25IL (0x1 << 6) +#define RT5640_IB_HP_5IL (0x2 << 6) +#define RT5640_IB_HP_1IL (0x3 << 6) + +/* PV detection and SPK gain control (0x92) */ +#define RT5640_PVDD_DET_MASK (0x1 << 15) +#define RT5640_PVDD_DET_SFT 15 +#define RT5640_PVDD_DET_DIS (0x0 << 15) +#define RT5640_PVDD_DET_EN (0x1 << 15) +#define RT5640_SPK_AG_MASK (0x1 << 14) +#define RT5640_SPK_AG_SFT 14 +#define RT5640_SPK_AG_DIS (0x0 << 14) +#define RT5640_SPK_AG_EN (0x1 << 14) + +/* Micbias Control (0x93) */ +#define RT5640_MIC1_BS_MASK (0x1 << 15) +#define RT5640_MIC1_BS_SFT 15 +#define RT5640_MIC1_BS_9AV (0x0 << 15) +#define RT5640_MIC1_BS_75AV (0x1 << 15) +#define RT5640_MIC2_BS_MASK (0x1 << 14) +#define RT5640_MIC2_BS_SFT 14 +#define RT5640_MIC2_BS_9AV (0x0 << 14) +#define RT5640_MIC2_BS_75AV (0x1 << 14) +#define RT5640_MIC1_CLK_MASK (0x1 << 13) +#define RT5640_MIC1_CLK_SFT 13 +#define RT5640_MIC1_CLK_DIS (0x0 << 13) +#define RT5640_MIC1_CLK_EN (0x1 << 13) +#define RT5640_MIC2_CLK_MASK (0x1 << 12) +#define RT5640_MIC2_CLK_SFT 12 +#define RT5640_MIC2_CLK_DIS (0x0 << 12) +#define RT5640_MIC2_CLK_EN (0x1 << 12) +#define RT5640_MIC1_OVCD_MASK (0x1 << 11) +#define RT5640_MIC1_OVCD_SFT 11 +#define RT5640_MIC1_OVCD_DIS (0x0 << 11) +#define RT5640_MIC1_OVCD_EN (0x1 << 11) +#define RT5640_MIC1_OVTH_MASK (0x3 << 9) +#define RT5640_MIC1_OVTH_SFT 9 +#define RT5640_MIC1_OVTH_600UA (0x0 << 9) +#define RT5640_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5640_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5640_MIC2_OVCD_MASK (0x1 << 8) +#define RT5640_MIC2_OVCD_SFT 8 +#define RT5640_MIC2_OVCD_DIS (0x0 << 8) +#define RT5640_MIC2_OVCD_EN (0x1 << 8) +#define RT5640_MIC2_OVTH_MASK (0x3 << 6) +#define RT5640_MIC2_OVTH_SFT 6 +#define RT5640_MIC2_OVTH_600UA (0x0 << 6) +#define RT5640_MIC2_OVTH_1500UA (0x1 << 6) +#define RT5640_MIC2_OVTH_2000UA (0x2 << 6) +#define RT5640_PWR_MB_MASK (0x1 << 5) +#define RT5640_PWR_MB_SFT 5 +#define RT5640_PWR_MB_PD (0x0 << 5) +#define RT5640_PWR_MB_PU (0x1 << 5) +#define RT5640_PWR_CLK25M_MASK (0x1 << 4) +#define RT5640_PWR_CLK25M_SFT 4 +#define RT5640_PWR_CLK25M_PD (0x0 << 4) +#define RT5640_PWR_CLK25M_PU (0x1 << 4) + +/* EQ Control 1 (0xb0) */ +#define RT5640_EQ_SRC_MASK (0x1 << 15) +#define RT5640_EQ_SRC_SFT 15 +#define RT5640_EQ_SRC_DAC (0x0 << 15) +#define RT5640_EQ_SRC_ADC (0x1 << 15) +#define RT5640_EQ_UPD (0x1 << 14) +#define RT5640_EQ_UPD_BIT 14 +#define RT5640_EQ_CD_MASK (0x1 << 13) +#define RT5640_EQ_CD_SFT 13 +#define RT5640_EQ_CD_DIS (0x0 << 13) +#define RT5640_EQ_CD_EN (0x1 << 13) +#define RT5640_EQ_DITH_MASK (0x3 << 8) +#define RT5640_EQ_DITH_SFT 8 +#define RT5640_EQ_DITH_NOR (0x0 << 8) +#define RT5640_EQ_DITH_LSB (0x1 << 8) +#define RT5640_EQ_DITH_LSB_1 (0x2 << 8) +#define RT5640_EQ_DITH_LSB_2 (0x3 << 8) + +/* EQ Control 2 (0xb1) */ +#define RT5640_EQ_HPF1_M_MASK (0x1 << 8) +#define RT5640_EQ_HPF1_M_SFT 8 +#define RT5640_EQ_HPF1_M_HI (0x0 << 8) +#define RT5640_EQ_HPF1_M_1ST (0x1 << 8) +#define RT5640_EQ_LPF1_M_MASK (0x1 << 7) +#define RT5640_EQ_LPF1_M_SFT 7 +#define RT5640_EQ_LPF1_M_LO (0x0 << 7) +#define RT5640_EQ_LPF1_M_1ST (0x1 << 7) +#define RT5640_EQ_HPF2_MASK (0x1 << 6) +#define RT5640_EQ_HPF2_SFT 6 +#define RT5640_EQ_HPF2_DIS (0x0 << 6) +#define RT5640_EQ_HPF2_EN (0x1 << 6) +#define RT5640_EQ_HPF1_MASK (0x1 << 5) +#define RT5640_EQ_HPF1_SFT 5 +#define RT5640_EQ_HPF1_DIS (0x0 << 5) +#define RT5640_EQ_HPF1_EN (0x1 << 5) +#define RT5640_EQ_BPF4_MASK (0x1 << 4) +#define RT5640_EQ_BPF4_SFT 4 +#define RT5640_EQ_BPF4_DIS (0x0 << 4) +#define RT5640_EQ_BPF4_EN (0x1 << 4) +#define RT5640_EQ_BPF3_MASK (0x1 << 3) +#define RT5640_EQ_BPF3_SFT 3 +#define RT5640_EQ_BPF3_DIS (0x0 << 3) +#define RT5640_EQ_BPF3_EN (0x1 << 3) +#define RT5640_EQ_BPF2_MASK (0x1 << 2) +#define RT5640_EQ_BPF2_SFT 2 +#define RT5640_EQ_BPF2_DIS (0x0 << 2) +#define RT5640_EQ_BPF2_EN (0x1 << 2) +#define RT5640_EQ_BPF1_MASK (0x1 << 1) +#define RT5640_EQ_BPF1_SFT 1 +#define RT5640_EQ_BPF1_DIS (0x0 << 1) +#define RT5640_EQ_BPF1_EN (0x1 << 1) +#define RT5640_EQ_LPF_MASK (0x1) +#define RT5640_EQ_LPF_SFT 0 +#define RT5640_EQ_LPF_DIS (0x0) +#define RT5640_EQ_LPF_EN (0x1) + +/* Memory Test (0xb2) */ +#define RT5640_MT_MASK (0x1 << 15) +#define RT5640_MT_SFT 15 +#define RT5640_MT_DIS (0x0 << 15) +#define RT5640_MT_EN (0x1 << 15) + +/* DRC/AGC Control 1 (0xb4) */ +#define RT5640_DRC_AGC_P_MASK (0x1 << 15) +#define RT5640_DRC_AGC_P_SFT 15 +#define RT5640_DRC_AGC_P_DAC (0x0 << 15) +#define RT5640_DRC_AGC_P_ADC (0x1 << 15) +#define RT5640_DRC_AGC_MASK (0x1 << 14) +#define RT5640_DRC_AGC_SFT 14 +#define RT5640_DRC_AGC_DIS (0x0 << 14) +#define RT5640_DRC_AGC_EN (0x1 << 14) +#define RT5640_DRC_AGC_UPD (0x1 << 13) +#define RT5640_DRC_AGC_UPD_BIT 13 +#define RT5640_DRC_AGC_AR_MASK (0x1f << 8) +#define RT5640_DRC_AGC_AR_SFT 8 +#define RT5640_DRC_AGC_R_MASK (0x7 << 5) +#define RT5640_DRC_AGC_R_SFT 5 +#define RT5640_DRC_AGC_R_48K (0x1 << 5) +#define RT5640_DRC_AGC_R_96K (0x2 << 5) +#define RT5640_DRC_AGC_R_192K (0x3 << 5) +#define RT5640_DRC_AGC_R_441K (0x5 << 5) +#define RT5640_DRC_AGC_R_882K (0x6 << 5) +#define RT5640_DRC_AGC_R_1764K (0x7 << 5) +#define RT5640_DRC_AGC_RC_MASK (0x1f) +#define RT5640_DRC_AGC_RC_SFT 0 + +/* DRC/AGC Control 2 (0xb5) */ +#define RT5640_DRC_AGC_POB_MASK (0x3f << 8) +#define RT5640_DRC_AGC_POB_SFT 8 +#define RT5640_DRC_AGC_CP_MASK (0x1 << 7) +#define RT5640_DRC_AGC_CP_SFT 7 +#define RT5640_DRC_AGC_CP_DIS (0x0 << 7) +#define RT5640_DRC_AGC_CP_EN (0x1 << 7) +#define RT5640_DRC_AGC_CPR_MASK (0x3 << 5) +#define RT5640_DRC_AGC_CPR_SFT 5 +#define RT5640_DRC_AGC_CPR_1_1 (0x0 << 5) +#define RT5640_DRC_AGC_CPR_1_2 (0x1 << 5) +#define RT5640_DRC_AGC_CPR_1_3 (0x2 << 5) +#define RT5640_DRC_AGC_CPR_1_4 (0x3 << 5) +#define RT5640_DRC_AGC_PRB_MASK (0x1f) +#define RT5640_DRC_AGC_PRB_SFT 0 + +/* DRC/AGC Control 3 (0xb6) */ +#define RT5640_DRC_AGC_NGB_MASK (0xf << 12) +#define RT5640_DRC_AGC_NGB_SFT 12 +#define RT5640_DRC_AGC_TAR_MASK (0x1f << 7) +#define RT5640_DRC_AGC_TAR_SFT 7 +#define RT5640_DRC_AGC_NG_MASK (0x1 << 6) +#define RT5640_DRC_AGC_NG_SFT 6 +#define RT5640_DRC_AGC_NG_DIS (0x0 << 6) +#define RT5640_DRC_AGC_NG_EN (0x1 << 6) +#define RT5640_DRC_AGC_NGH_MASK (0x1 << 5) +#define RT5640_DRC_AGC_NGH_SFT 5 +#define RT5640_DRC_AGC_NGH_DIS (0x0 << 5) +#define RT5640_DRC_AGC_NGH_EN (0x1 << 5) +#define RT5640_DRC_AGC_NGT_MASK (0x1f) +#define RT5640_DRC_AGC_NGT_SFT 0 + +/* ANC Control 1 (0xb8) */ +#define RT5640_ANC_M_MASK (0x1 << 15) +#define RT5640_ANC_M_SFT 15 +#define RT5640_ANC_M_NOR (0x0 << 15) +#define RT5640_ANC_M_REV (0x1 << 15) +#define RT5640_ANC_MASK (0x1 << 14) +#define RT5640_ANC_SFT 14 +#define RT5640_ANC_DIS (0x0 << 14) +#define RT5640_ANC_EN (0x1 << 14) +#define RT5640_ANC_MD_MASK (0x3 << 12) +#define RT5640_ANC_MD_SFT 12 +#define RT5640_ANC_MD_DIS (0x0 << 12) +#define RT5640_ANC_MD_67MS (0x1 << 12) +#define RT5640_ANC_MD_267MS (0x2 << 12) +#define RT5640_ANC_MD_1067MS (0x3 << 12) +#define RT5640_ANC_SN_MASK (0x1 << 11) +#define RT5640_ANC_SN_SFT 11 +#define RT5640_ANC_SN_DIS (0x0 << 11) +#define RT5640_ANC_SN_EN (0x1 << 11) +#define RT5640_ANC_CLK_MASK (0x1 << 10) +#define RT5640_ANC_CLK_SFT 10 +#define RT5640_ANC_CLK_ANC (0x0 << 10) +#define RT5640_ANC_CLK_REG (0x1 << 10) +#define RT5640_ANC_ZCD_MASK (0x3 << 8) +#define RT5640_ANC_ZCD_SFT 8 +#define RT5640_ANC_ZCD_DIS (0x0 << 8) +#define RT5640_ANC_ZCD_T1 (0x1 << 8) +#define RT5640_ANC_ZCD_T2 (0x2 << 8) +#define RT5640_ANC_ZCD_WT (0x3 << 8) +#define RT5640_ANC_CS_MASK (0x1 << 7) +#define RT5640_ANC_CS_SFT 7 +#define RT5640_ANC_CS_DIS (0x0 << 7) +#define RT5640_ANC_CS_EN (0x1 << 7) +#define RT5640_ANC_SW_MASK (0x1 << 6) +#define RT5640_ANC_SW_SFT 6 +#define RT5640_ANC_SW_NOR (0x0 << 6) +#define RT5640_ANC_SW_AUTO (0x1 << 6) +#define RT5640_ANC_CO_L_MASK (0x3f) +#define RT5640_ANC_CO_L_SFT 0 + +/* ANC Control 2 (0xb6) */ +#define RT5640_ANC_FG_R_MASK (0xf << 12) +#define RT5640_ANC_FG_R_SFT 12 +#define RT5640_ANC_FG_L_MASK (0xf << 8) +#define RT5640_ANC_FG_L_SFT 8 +#define RT5640_ANC_CG_R_MASK (0xf << 4) +#define RT5640_ANC_CG_R_SFT 4 +#define RT5640_ANC_CG_L_MASK (0xf) +#define RT5640_ANC_CG_L_SFT 0 + +/* ANC Control 3 (0xb6) */ +#define RT5640_ANC_CD_MASK (0x1 << 6) +#define RT5640_ANC_CD_SFT 6 +#define RT5640_ANC_CD_BOTH (0x0 << 6) +#define RT5640_ANC_CD_IND (0x1 << 6) +#define RT5640_ANC_CO_R_MASK (0x3f) +#define RT5640_ANC_CO_R_SFT 0 + +/* Jack Detect Control (0xbb) */ +#define RT5640_JD_MASK (0x7 << 13) +#define RT5640_JD_SFT 13 +#define RT5640_JD_DIS (0x0 << 13) +#define RT5640_JD_GPIO1 (0x1 << 13) +#define RT5640_JD_JD1_IN4P (0x2 << 13) +#define RT5640_JD_JD2_IN4N (0x3 << 13) +#define RT5640_JD_GPIO2 (0x4 << 13) +#define RT5640_JD_GPIO3 (0x5 << 13) +#define RT5640_JD_GPIO4 (0x6 << 13) +#define RT5640_JD_HP_MASK (0x1 << 11) +#define RT5640_JD_HP_SFT 11 +#define RT5640_JD_HP_DIS (0x0 << 11) +#define RT5640_JD_HP_EN (0x1 << 11) +#define RT5640_JD_HP_TRG_MASK (0x1 << 10) +#define RT5640_JD_HP_TRG_SFT 10 +#define RT5640_JD_HP_TRG_LO (0x0 << 10) +#define RT5640_JD_HP_TRG_HI (0x1 << 10) +#define RT5640_JD_SPL_MASK (0x1 << 9) +#define RT5640_JD_SPL_SFT 9 +#define RT5640_JD_SPL_DIS (0x0 << 9) +#define RT5640_JD_SPL_EN (0x1 << 9) +#define RT5640_JD_SPL_TRG_MASK (0x1 << 8) +#define RT5640_JD_SPL_TRG_SFT 8 +#define RT5640_JD_SPL_TRG_LO (0x0 << 8) +#define RT5640_JD_SPL_TRG_HI (0x1 << 8) +#define RT5640_JD_SPR_MASK (0x1 << 7) +#define RT5640_JD_SPR_SFT 7 +#define RT5640_JD_SPR_DIS (0x0 << 7) +#define RT5640_JD_SPR_EN (0x1 << 7) +#define RT5640_JD_SPR_TRG_MASK (0x1 << 6) +#define RT5640_JD_SPR_TRG_SFT 6 +#define RT5640_JD_SPR_TRG_LO (0x0 << 6) +#define RT5640_JD_SPR_TRG_HI (0x1 << 6) +#define RT5640_JD_MO_MASK (0x1 << 5) +#define RT5640_JD_MO_SFT 5 +#define RT5640_JD_MO_DIS (0x0 << 5) +#define RT5640_JD_MO_EN (0x1 << 5) +#define RT5640_JD_MO_TRG_MASK (0x1 << 4) +#define RT5640_JD_MO_TRG_SFT 4 +#define RT5640_JD_MO_TRG_LO (0x0 << 4) +#define RT5640_JD_MO_TRG_HI (0x1 << 4) +#define RT5640_JD_LO_MASK (0x1 << 3) +#define RT5640_JD_LO_SFT 3 +#define RT5640_JD_LO_DIS (0x0 << 3) +#define RT5640_JD_LO_EN (0x1 << 3) +#define RT5640_JD_LO_TRG_MASK (0x1 << 2) +#define RT5640_JD_LO_TRG_SFT 2 +#define RT5640_JD_LO_TRG_LO (0x0 << 2) +#define RT5640_JD_LO_TRG_HI (0x1 << 2) +#define RT5640_JD1_IN4P_MASK (0x1 << 1) +#define RT5640_JD1_IN4P_SFT 1 +#define RT5640_JD1_IN4P_DIS (0x0 << 1) +#define RT5640_JD1_IN4P_EN (0x1 << 1) +#define RT5640_JD2_IN4N_MASK (0x1) +#define RT5640_JD2_IN4N_SFT 0 +#define RT5640_JD2_IN4N_DIS (0x0) +#define RT5640_JD2_IN4N_EN (0x1) + +/* Jack detect for ANC (0xbc) */ +#define RT5640_ANC_DET_MASK (0x3 << 4) +#define RT5640_ANC_DET_SFT 4 +#define RT5640_ANC_DET_DIS (0x0 << 4) +#define RT5640_ANC_DET_MB1 (0x1 << 4) +#define RT5640_ANC_DET_MB2 (0x2 << 4) +#define RT5640_ANC_DET_JD (0x3 << 4) +#define RT5640_AD_TRG_MASK (0x1 << 3) +#define RT5640_AD_TRG_SFT 3 +#define RT5640_AD_TRG_LO (0x0 << 3) +#define RT5640_AD_TRG_HI (0x1 << 3) +#define RT5640_ANCM_DET_MASK (0x3 << 4) +#define RT5640_ANCM_DET_SFT 4 +#define RT5640_ANCM_DET_DIS (0x0 << 4) +#define RT5640_ANCM_DET_MB1 (0x1 << 4) +#define RT5640_ANCM_DET_MB2 (0x2 << 4) +#define RT5640_ANCM_DET_JD (0x3 << 4) +#define RT5640_AMD_TRG_MASK (0x1 << 3) +#define RT5640_AMD_TRG_SFT 3 +#define RT5640_AMD_TRG_LO (0x0 << 3) +#define RT5640_AMD_TRG_HI (0x1 << 3) + +/* IRQ Control 1 (0xbd) */ +#define RT5640_IRQ_JD_MASK (0x1 << 15) +#define RT5640_IRQ_JD_SFT 15 +#define RT5640_IRQ_JD_BP (0x0 << 15) +#define RT5640_IRQ_JD_NOR (0x1 << 15) +#define RT5640_IRQ_OT_MASK (0x1 << 14) +#define RT5640_IRQ_OT_SFT 14 +#define RT5640_IRQ_OT_BP (0x0 << 14) +#define RT5640_IRQ_OT_NOR (0x1 << 14) +#define RT5640_JD_STKY_MASK (0x1 << 13) +#define RT5640_JD_STKY_SFT 13 +#define RT5640_JD_STKY_DIS (0x0 << 13) +#define RT5640_JD_STKY_EN (0x1 << 13) +#define RT5640_OT_STKY_MASK (0x1 << 12) +#define RT5640_OT_STKY_SFT 12 +#define RT5640_OT_STKY_DIS (0x0 << 12) +#define RT5640_OT_STKY_EN (0x1 << 12) +#define RT5640_JD_P_MASK (0x1 << 11) +#define RT5640_JD_P_SFT 11 +#define RT5640_JD_P_NOR (0x0 << 11) +#define RT5640_JD_P_INV (0x1 << 11) +#define RT5640_OT_P_MASK (0x1 << 10) +#define RT5640_OT_P_SFT 10 +#define RT5640_OT_P_NOR (0x0 << 10) +#define RT5640_OT_P_INV (0x1 << 10) + +/* IRQ Control 2 (0xbe) */ +#define RT5640_IRQ_MB1_OC_MASK (0x1 << 15) +#define RT5640_IRQ_MB1_OC_SFT 15 +#define RT5640_IRQ_MB1_OC_BP (0x0 << 15) +#define RT5640_IRQ_MB1_OC_NOR (0x1 << 15) +#define RT5640_IRQ_MB2_OC_MASK (0x1 << 14) +#define RT5640_IRQ_MB2_OC_SFT 14 +#define RT5640_IRQ_MB2_OC_BP (0x0 << 14) +#define RT5640_IRQ_MB2_OC_NOR (0x1 << 14) +#define RT5640_MB1_OC_STKY_MASK (0x1 << 11) +#define RT5640_MB1_OC_STKY_SFT 11 +#define RT5640_MB1_OC_STKY_DIS (0x0 << 11) +#define RT5640_MB1_OC_STKY_EN (0x1 << 11) +#define RT5640_MB2_OC_STKY_MASK (0x1 << 10) +#define RT5640_MB2_OC_STKY_SFT 10 +#define RT5640_MB2_OC_STKY_DIS (0x0 << 10) +#define RT5640_MB2_OC_STKY_EN (0x1 << 10) +#define RT5640_MB1_OC_P_MASK (0x1 << 7) +#define RT5640_MB1_OC_P_SFT 7 +#define RT5640_MB1_OC_P_NOR (0x0 << 7) +#define RT5640_MB1_OC_P_INV (0x1 << 7) +#define RT5640_MB2_OC_P_MASK (0x1 << 6) +#define RT5640_MB2_OC_P_SFT 6 +#define RT5640_MB2_OC_P_NOR (0x0 << 6) +#define RT5640_MB2_OC_P_INV (0x1 << 6) +#define RT5640_MB1_OC_STATUS (0x1 << 3) +#define RT5640_MB1_OC_STATUS_SFT 3 +#define RT5640_MB2_OC_STATUS (0x1 << 2) +#define RT5640_MB2_OC_STATUS_SFT 2 + +/* GPIO and Internal Status (0xbf) */ +#define RT5640_GPIO1_STATUS (0x1 << 8) +#define RT5640_GPIO2_STATUS (0x1 << 7) +#define RT5640_JD_STATUS (0x1 << 4) +#define RT5640_OVT_STATUS (0x1 << 3) +#define RT5640_CLS_D_OVCD_STATUS (0x1 << 0) + +/* GPIO Control 1 (0xc0) */ +#define RT5640_GP1_PIN_MASK (0x1 << 15) +#define RT5640_GP1_PIN_SFT 15 +#define RT5640_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5640_GP1_PIN_IRQ (0x1 << 15) +#define RT5640_GP2_PIN_MASK (0x1 << 14) +#define RT5640_GP2_PIN_SFT 14 +#define RT5640_GP2_PIN_GPIO2 (0x0 << 14) +#define RT5640_GP2_PIN_DMIC1_SCL (0x1 << 14) +#define RT5640_GP3_PIN_MASK (0x3 << 12) +#define RT5640_GP3_PIN_SFT 12 +#define RT5640_GP3_PIN_GPIO3 (0x0 << 12) +#define RT5640_GP3_PIN_DMIC1_SDA (0x1 << 12) +#define RT5640_GP3_PIN_IRQ (0x2 << 12) +#define RT5640_GP4_PIN_MASK (0x1 << 11) +#define RT5640_GP4_PIN_SFT 11 +#define RT5640_GP4_PIN_GPIO4 (0x0 << 11) +#define RT5640_GP4_PIN_DMIC2_SDA (0x1 << 11) +#define RT5640_DP_SIG_MASK (0x1 << 10) +#define RT5640_DP_SIG_SFT 10 +#define RT5640_DP_SIG_TEST (0x0 << 10) +#define RT5640_DP_SIG_AP (0x1 << 10) +#define RT5640_GPIO_M_MASK (0x1 << 9) +#define RT5640_GPIO_M_SFT 9 +#define RT5640_GPIO_M_FLT (0x0 << 9) +#define RT5640_GPIO_M_PH (0x1 << 9) + +/* GPIO Control 3 (0xc2) */ +#define RT5640_GP4_PF_MASK (0x1 << 11) +#define RT5640_GP4_PF_SFT 11 +#define RT5640_GP4_PF_IN (0x0 << 11) +#define RT5640_GP4_PF_OUT (0x1 << 11) +#define RT5640_GP4_OUT_MASK (0x1 << 10) +#define RT5640_GP4_OUT_SFT 10 +#define RT5640_GP4_OUT_LO (0x0 << 10) +#define RT5640_GP4_OUT_HI (0x1 << 10) +#define RT5640_GP4_P_MASK (0x1 << 9) +#define RT5640_GP4_P_SFT 9 +#define RT5640_GP4_P_NOR (0x0 << 9) +#define RT5640_GP4_P_INV (0x1 << 9) +#define RT5640_GP3_PF_MASK (0x1 << 8) +#define RT5640_GP3_PF_SFT 8 +#define RT5640_GP3_PF_IN (0x0 << 8) +#define RT5640_GP3_PF_OUT (0x1 << 8) +#define RT5640_GP3_OUT_MASK (0x1 << 7) +#define RT5640_GP3_OUT_SFT 7 +#define RT5640_GP3_OUT_LO (0x0 << 7) +#define RT5640_GP3_OUT_HI (0x1 << 7) +#define RT5640_GP3_P_MASK (0x1 << 6) +#define RT5640_GP3_P_SFT 6 +#define RT5640_GP3_P_NOR (0x0 << 6) +#define RT5640_GP3_P_INV (0x1 << 6) +#define RT5640_GP2_PF_MASK (0x1 << 5) +#define RT5640_GP2_PF_SFT 5 +#define RT5640_GP2_PF_IN (0x0 << 5) +#define RT5640_GP2_PF_OUT (0x1 << 5) +#define RT5640_GP2_OUT_MASK (0x1 << 4) +#define RT5640_GP2_OUT_SFT 4 +#define RT5640_GP2_OUT_LO (0x0 << 4) +#define RT5640_GP2_OUT_HI (0x1 << 4) +#define RT5640_GP2_P_MASK (0x1 << 3) +#define RT5640_GP2_P_SFT 3 +#define RT5640_GP2_P_NOR (0x0 << 3) +#define RT5640_GP2_P_INV (0x1 << 3) +#define RT5640_GP1_PF_MASK (0x1 << 2) +#define RT5640_GP1_PF_SFT 2 +#define RT5640_GP1_PF_IN (0x0 << 2) +#define RT5640_GP1_PF_OUT (0x1 << 2) +#define RT5640_GP1_OUT_MASK (0x1 << 1) +#define RT5640_GP1_OUT_SFT 1 +#define RT5640_GP1_OUT_LO (0x0 << 1) +#define RT5640_GP1_OUT_HI (0x1 << 1) +#define RT5640_GP1_P_MASK (0x1) +#define RT5640_GP1_P_SFT 0 +#define RT5640_GP1_P_NOR (0x0) +#define RT5640_GP1_P_INV (0x1) + +/* FM34-500 Register Control 1 (0xc4) */ +#define RT5640_DSP_ADD_SFT 0 + +/* FM34-500 Register Control 2 (0xc5) */ +#define RT5640_DSP_DAT_SFT 0 + +/* FM34-500 Register Control 3 (0xc6) */ +#define RT5640_DSP_BUSY_MASK (0x1 << 15) +#define RT5640_DSP_BUSY_BIT 15 +#define RT5640_DSP_DS_MASK (0x1 << 14) +#define RT5640_DSP_DS_SFT 14 +#define RT5640_DSP_DS_FM3010 (0x1 << 14) +#define RT5640_DSP_DS_TEMP (0x1 << 14) +#define RT5640_DSP_CLK_MASK (0x3 << 12) +#define RT5640_DSP_CLK_SFT 12 +#define RT5640_DSP_CLK_384K (0x0 << 12) +#define RT5640_DSP_CLK_192K (0x1 << 12) +#define RT5640_DSP_CLK_96K (0x2 << 12) +#define RT5640_DSP_CLK_64K (0x3 << 12) +#define RT5640_DSP_PD_PIN_MASK (0x1 << 11) +#define RT5640_DSP_PD_PIN_SFT 11 +#define RT5640_DSP_PD_PIN_LO (0x0 << 11) +#define RT5640_DSP_PD_PIN_HI (0x1 << 11) +#define RT5640_DSP_RST_PIN_MASK (0x1 << 10) +#define RT5640_DSP_RST_PIN_SFT 10 +#define RT5640_DSP_RST_PIN_LO (0x0 << 10) +#define RT5640_DSP_RST_PIN_HI (0x1 << 10) +#define RT5640_DSP_R_EN (0x1 << 9) +#define RT5640_DSP_R_EN_BIT 9 +#define RT5640_DSP_W_EN (0x1 << 8) +#define RT5640_DSP_W_EN_BIT 8 +#define RT5640_DSP_CMD_MASK (0xff) +#define RT5640_DSP_CMD_SFT 0 +#define RT5640_DSP_CMD_MW (0x3B) /* Memory Write */ +#define RT5640_DSP_CMD_MR (0x37) /* Memory Read */ +#define RT5640_DSP_CMD_RR (0x60) /* Register Read */ +#define RT5640_DSP_CMD_RW (0x68) /* Register Write */ + +/* Programmable Register Array Control 1 (0xc8) */ +#define RT5640_REG_SEQ_MASK (0xf << 12) +#define RT5640_REG_SEQ_SFT 12 +#define RT5640_SEQ1_ST_MASK (0x1 << 11) /*RO*/ +#define RT5640_SEQ1_ST_SFT 11 +#define RT5640_SEQ1_ST_RUN (0x0 << 11) +#define RT5640_SEQ1_ST_FIN (0x1 << 11) +#define RT5640_SEQ2_ST_MASK (0x1 << 10) /*RO*/ +#define RT5640_SEQ2_ST_SFT 10 +#define RT5640_SEQ2_ST_RUN (0x0 << 10) +#define RT5640_SEQ2_ST_FIN (0x1 << 10) +#define RT5640_REG_LV_MASK (0x1 << 9) +#define RT5640_REG_LV_SFT 9 +#define RT5640_REG_LV_MX (0x0 << 9) +#define RT5640_REG_LV_PR (0x1 << 9) +#define RT5640_SEQ_2_PT_MASK (0x1 << 8) +#define RT5640_SEQ_2_PT_BIT 8 +#define RT5640_REG_IDX_MASK (0xff) +#define RT5640_REG_IDX_SFT 0 + +/* Programmable Register Array Control 2 (0xc9) */ +#define RT5640_REG_DAT_MASK (0xffff) +#define RT5640_REG_DAT_SFT 0 + +/* Programmable Register Array Control 3 (0xca) */ +#define RT5640_SEQ_DLY_MASK (0xff << 8) +#define RT5640_SEQ_DLY_SFT 8 +#define RT5640_PROG_MASK (0x1 << 7) +#define RT5640_PROG_SFT 7 +#define RT5640_PROG_DIS (0x0 << 7) +#define RT5640_PROG_EN (0x1 << 7) +#define RT5640_SEQ1_PT_RUN (0x1 << 6) +#define RT5640_SEQ1_PT_RUN_BIT 6 +#define RT5640_SEQ2_PT_RUN (0x1 << 5) +#define RT5640_SEQ2_PT_RUN_BIT 5 + +/* Programmable Register Array Control 4 (0xcb) */ +#define RT5640_SEQ1_START_MASK (0xf << 8) +#define RT5640_SEQ1_START_SFT 8 +#define RT5640_SEQ1_END_MASK (0xf) +#define RT5640_SEQ1_END_SFT 0 + +/* Programmable Register Array Control 5 (0xcc) */ +#define RT5640_SEQ2_START_MASK (0xf << 8) +#define RT5640_SEQ2_START_SFT 8 +#define RT5640_SEQ2_END_MASK (0xf) +#define RT5640_SEQ2_END_SFT 0 + +/* Scramble Function (0xcd) */ +#define RT5640_SCB_KEY_MASK (0xff) +#define RT5640_SCB_KEY_SFT 0 + +/* Scramble Control (0xce) */ +#define RT5640_SCB_SWAP_MASK (0x1 << 15) +#define RT5640_SCB_SWAP_SFT 15 +#define RT5640_SCB_SWAP_DIS (0x0 << 15) +#define RT5640_SCB_SWAP_EN (0x1 << 15) +#define RT5640_SCB_MASK (0x1 << 14) +#define RT5640_SCB_SFT 14 +#define RT5640_SCB_DIS (0x0 << 14) +#define RT5640_SCB_EN (0x1 << 14) + +/* Baseback Control (0xcf) */ +#define RT5640_BB_MASK (0x1 << 15) +#define RT5640_BB_SFT 15 +#define RT5640_BB_DIS (0x0 << 15) +#define RT5640_BB_EN (0x1 << 15) +#define RT5640_BB_CT_MASK (0x7 << 12) +#define RT5640_BB_CT_SFT 12 +#define RT5640_BB_CT_A (0x0 << 12) +#define RT5640_BB_CT_B (0x1 << 12) +#define RT5640_BB_CT_C (0x2 << 12) +#define RT5640_BB_CT_D (0x3 << 12) +#define RT5640_M_BB_L_MASK (0x1 << 9) +#define RT5640_M_BB_L_SFT 9 +#define RT5640_M_BB_R_MASK (0x1 << 8) +#define RT5640_M_BB_R_SFT 8 +#define RT5640_M_BB_HPF_L_MASK (0x1 << 7) +#define RT5640_M_BB_HPF_L_SFT 7 +#define RT5640_M_BB_HPF_R_MASK (0x1 << 6) +#define RT5640_M_BB_HPF_R_SFT 6 +#define RT5640_G_BB_BST_MASK (0x3f) +#define RT5640_G_BB_BST_SFT 0 + +/* MP3 Plus Control 1 (0xd0) */ +#define RT5640_M_MP3_L_MASK (0x1 << 15) +#define RT5640_M_MP3_L_SFT 15 +#define RT5640_M_MP3_R_MASK (0x1 << 14) +#define RT5640_M_MP3_R_SFT 14 +#define RT5640_M_MP3_MASK (0x1 << 13) +#define RT5640_M_MP3_SFT 13 +#define RT5640_M_MP3_DIS (0x0 << 13) +#define RT5640_M_MP3_EN (0x1 << 13) +#define RT5640_EG_MP3_MASK (0x1f << 8) +#define RT5640_EG_MP3_SFT 8 +#define RT5640_MP3_HLP_MASK (0x1 << 7) +#define RT5640_MP3_HLP_SFT 7 +#define RT5640_MP3_HLP_DIS (0x0 << 7) +#define RT5640_MP3_HLP_EN (0x1 << 7) +#define RT5640_M_MP3_ORG_L_MASK (0x1 << 6) +#define RT5640_M_MP3_ORG_L_SFT 6 +#define RT5640_M_MP3_ORG_R_MASK (0x1 << 5) +#define RT5640_M_MP3_ORG_R_SFT 5 + +/* MP3 Plus Control 2 (0xd1) */ +#define RT5640_MP3_WT_MASK (0x1 << 13) +#define RT5640_MP3_WT_SFT 13 +#define RT5640_MP3_WT_1_4 (0x0 << 13) +#define RT5640_MP3_WT_1_2 (0x1 << 13) +#define RT5640_OG_MP3_MASK (0x1f << 8) +#define RT5640_OG_MP3_SFT 8 +#define RT5640_HG_MP3_MASK (0x3f) +#define RT5640_HG_MP3_SFT 0 + +/* 3D HP Control 1 (0xd2) */ +#define RT5640_3D_CF_MASK (0x1 << 15) +#define RT5640_3D_CF_SFT 15 +#define RT5640_3D_CF_DIS (0x0 << 15) +#define RT5640_3D_CF_EN (0x1 << 15) +#define RT5640_3D_HP_MASK (0x1 << 14) +#define RT5640_3D_HP_SFT 14 +#define RT5640_3D_HP_DIS (0x0 << 14) +#define RT5640_3D_HP_EN (0x1 << 14) +#define RT5640_3D_BT_MASK (0x1 << 13) +#define RT5640_3D_BT_SFT 13 +#define RT5640_3D_BT_DIS (0x0 << 13) +#define RT5640_3D_BT_EN (0x1 << 13) +#define RT5640_3D_1F_MIX_MASK (0x3 << 11) +#define RT5640_3D_1F_MIX_SFT 11 +#define RT5640_3D_HP_M_MASK (0x1 << 10) +#define RT5640_3D_HP_M_SFT 10 +#define RT5640_3D_HP_M_SUR (0x0 << 10) +#define RT5640_3D_HP_M_FRO (0x1 << 10) +#define RT5640_M_3D_HRTF_MASK (0x1 << 9) +#define RT5640_M_3D_HRTF_SFT 9 +#define RT5640_M_3D_D2H_MASK (0x1 << 8) +#define RT5640_M_3D_D2H_SFT 8 +#define RT5640_M_3D_D2R_MASK (0x1 << 7) +#define RT5640_M_3D_D2R_SFT 7 +#define RT5640_M_3D_REVB_MASK (0x1 << 6) +#define RT5640_M_3D_REVB_SFT 6 + +/* Adjustable high pass filter control 1 (0xd3) */ +#define RT5640_2ND_HPF_MASK (0x1 << 15) +#define RT5640_2ND_HPF_SFT 15 +#define RT5640_2ND_HPF_DIS (0x0 << 15) +#define RT5640_2ND_HPF_EN (0x1 << 15) +#define RT5640_HPF_CF_L_MASK (0x7 << 12) +#define RT5640_HPF_CF_L_SFT 12 +#define RT5640_1ST_HPF_MASK (0x1 << 11) +#define RT5640_1ST_HPF_SFT 11 +#define RT5640_1ST_HPF_DIS (0x0 << 11) +#define RT5640_1ST_HPF_EN (0x1 << 11) +#define RT5640_HPF_CF_R_MASK (0x7 << 8) +#define RT5640_HPF_CF_R_SFT 8 +#define RT5640_ZD_T_MASK (0x3 << 6) +#define RT5640_ZD_T_SFT 6 +#define RT5640_ZD_F_MASK (0x3 << 4) +#define RT5640_ZD_F_SFT 4 +#define RT5640_ZD_F_IM (0x0 << 4) +#define RT5640_ZD_F_ZC_IM (0x1 << 4) +#define RT5640_ZD_F_ZC_IOD (0x2 << 4) +#define RT5640_ZD_F_UN (0x3 << 4) + +/* HP calibration control and Amp detection (0xd6) */ +#define RT5640_SI_DAC_MASK (0x1 << 11) +#define RT5640_SI_DAC_SFT 11 +#define RT5640_SI_DAC_AUTO (0x0 << 11) +#define RT5640_SI_DAC_TEST (0x1 << 11) +#define RT5640_DC_CAL_M_MASK (0x1 << 10) +#define RT5640_DC_CAL_M_SFT 10 +#define RT5640_DC_CAL_M_CAL (0x0 << 10) +#define RT5640_DC_CAL_M_NOR (0x1 << 10) +#define RT5640_DC_CAL_MASK (0x1 << 9) +#define RT5640_DC_CAL_SFT 9 +#define RT5640_DC_CAL_DIS (0x0 << 9) +#define RT5640_DC_CAL_EN (0x1 << 9) +#define RT5640_HPD_RCV_MASK (0x7 << 6) +#define RT5640_HPD_RCV_SFT 6 +#define RT5640_HPD_PS_MASK (0x1 << 5) +#define RT5640_HPD_PS_SFT 5 +#define RT5640_HPD_PS_DIS (0x0 << 5) +#define RT5640_HPD_PS_EN (0x1 << 5) +#define RT5640_CAL_M_MASK (0x1 << 4) +#define RT5640_CAL_M_SFT 4 +#define RT5640_CAL_M_DEP (0x0 << 4) +#define RT5640_CAL_M_CAL (0x1 << 4) +#define RT5640_CAL_MASK (0x1 << 3) +#define RT5640_CAL_SFT 3 +#define RT5640_CAL_DIS (0x0 << 3) +#define RT5640_CAL_EN (0x1 << 3) +#define RT5640_CAL_TEST_MASK (0x1 << 2) +#define RT5640_CAL_TEST_SFT 2 +#define RT5640_CAL_TEST_DIS (0x0 << 2) +#define RT5640_CAL_TEST_EN (0x1 << 2) +#define RT5640_CAL_P_MASK (0x3) +#define RT5640_CAL_P_SFT 0 +#define RT5640_CAL_P_NONE (0x0) +#define RT5640_CAL_P_CAL (0x1) +#define RT5640_CAL_P_DAC_CAL (0x2) + +/* Soft volume and zero cross control 1 (0xd9) */ +#define RT5640_SV_MASK (0x1 << 15) +#define RT5640_SV_SFT 15 +#define RT5640_SV_DIS (0x0 << 15) +#define RT5640_SV_EN (0x1 << 15) +#define RT5640_SPO_SV_MASK (0x1 << 14) +#define RT5640_SPO_SV_SFT 14 +#define RT5640_SPO_SV_DIS (0x0 << 14) +#define RT5640_SPO_SV_EN (0x1 << 14) +#define RT5640_OUT_SV_MASK (0x1 << 13) +#define RT5640_OUT_SV_SFT 13 +#define RT5640_OUT_SV_DIS (0x0 << 13) +#define RT5640_OUT_SV_EN (0x1 << 13) +#define RT5640_HP_SV_MASK (0x1 << 12) +#define RT5640_HP_SV_SFT 12 +#define RT5640_HP_SV_DIS (0x0 << 12) +#define RT5640_HP_SV_EN (0x1 << 12) +#define RT5640_ZCD_DIG_MASK (0x1 << 11) +#define RT5640_ZCD_DIG_SFT 11 +#define RT5640_ZCD_DIG_DIS (0x0 << 11) +#define RT5640_ZCD_DIG_EN (0x1 << 11) +#define RT5640_ZCD_MASK (0x1 << 10) +#define RT5640_ZCD_SFT 10 +#define RT5640_ZCD_PD (0x0 << 10) +#define RT5640_ZCD_PU (0x1 << 10) +#define RT5640_M_ZCD_MASK (0x3f << 4) +#define RT5640_M_ZCD_SFT 4 +#define RT5640_M_ZCD_RM_L (0x1 << 9) +#define RT5640_M_ZCD_RM_R (0x1 << 8) +#define RT5640_M_ZCD_SM_L (0x1 << 7) +#define RT5640_M_ZCD_SM_R (0x1 << 6) +#define RT5640_M_ZCD_OM_L (0x1 << 5) +#define RT5640_M_ZCD_OM_R (0x1 << 4) +#define RT5640_SV_DLY_MASK (0xf) +#define RT5640_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0xda) */ +#define RT5640_ZCD_HP_MASK (0x1 << 15) +#define RT5640_ZCD_HP_SFT 15 +#define RT5640_ZCD_HP_DIS (0x0 << 15) +#define RT5640_ZCD_HP_EN (0x1 << 15) + +/* General Control 1 (0xfa) */ +#define RT5640_M_MONO_ADC_L (0x1 << 13) +#define RT5640_M_MONO_ADC_L_SFT 13 +#define RT5640_M_MONO_ADC_R (0x1 << 12) +#define RT5640_M_MONO_ADC_R_SFT 12 +#define RT5640_MCLK_DET (0x1 << 11) + +/* Codec Private Register definition */ + +/* MIC Over current threshold scale factor (0x15) */ +#define RT5640_MIC_OVCD_SF_MASK (0x3 << 8) +#define RT5640_MIC_OVCD_SF_SFT 8 +#define RT5640_MIC_OVCD_SF_0P5 (0x0 << 8) +#define RT5640_MIC_OVCD_SF_0P75 (0x1 << 8) +#define RT5640_MIC_OVCD_SF_1P0 (0x2 << 8) +#define RT5640_MIC_OVCD_SF_1P5 (0x3 << 8) + +/* 3D Speaker Control (0x63) */ +#define RT5640_3D_SPK_MASK (0x1 << 15) +#define RT5640_3D_SPK_SFT 15 +#define RT5640_3D_SPK_DIS (0x0 << 15) +#define RT5640_3D_SPK_EN (0x1 << 15) +#define RT5640_3D_SPK_M_MASK (0x3 << 13) +#define RT5640_3D_SPK_M_SFT 13 +#define RT5640_3D_SPK_CG_MASK (0x1f << 8) +#define RT5640_3D_SPK_CG_SFT 8 +#define RT5640_3D_SPK_SG_MASK (0x1f) +#define RT5640_3D_SPK_SG_SFT 0 + +/* Wind Noise Detection Control 1 (0x6c) */ +#define RT5640_WND_MASK (0x1 << 15) +#define RT5640_WND_SFT 15 +#define RT5640_WND_DIS (0x0 << 15) +#define RT5640_WND_EN (0x1 << 15) + +/* Wind Noise Detection Control 2 (0x6d) */ +#define RT5640_WND_FC_NW_MASK (0x3f << 10) +#define RT5640_WND_FC_NW_SFT 10 +#define RT5640_WND_FC_WK_MASK (0x3f << 4) +#define RT5640_WND_FC_WK_SFT 4 + +/* Wind Noise Detection Control 3 (0x6e) */ +#define RT5640_HPF_FC_MASK (0x3f << 6) +#define RT5640_HPF_FC_SFT 6 +#define RT5640_WND_FC_ST_MASK (0x3f) +#define RT5640_WND_FC_ST_SFT 0 + +/* Wind Noise Detection Control 4 (0x6f) */ +#define RT5640_WND_TH_LO_MASK (0x3ff) +#define RT5640_WND_TH_LO_SFT 0 + +/* Wind Noise Detection Control 5 (0x70) */ +#define RT5640_WND_TH_HI_MASK (0x3ff) +#define RT5640_WND_TH_HI_SFT 0 + +/* Wind Noise Detection Control 8 (0x73) */ +#define RT5640_WND_WIND_MASK (0x1 << 13) /* Read-Only */ +#define RT5640_WND_WIND_SFT 13 +#define RT5640_WND_STRONG_MASK (0x1 << 12) /* Read-Only */ +#define RT5640_WND_STRONG_SFT 12 +enum { + RT5640_NO_WIND, + RT5640_BREEZE, + RT5640_STORM, +}; + +/* Dipole Speaker Interface (0x75) */ +#define RT5640_DP_ATT_MASK (0x3 << 14) +#define RT5640_DP_ATT_SFT 14 +#define RT5640_DP_SPK_MASK (0x1 << 10) +#define RT5640_DP_SPK_SFT 10 +#define RT5640_DP_SPK_DIS (0x0 << 10) +#define RT5640_DP_SPK_EN (0x1 << 10) + +/* EQ Pre Volume Control (0xb3) */ +#define RT5640_EQ_PRE_VOL_MASK (0xffff) +#define RT5640_EQ_PRE_VOL_SFT 0 + +/* EQ Post Volume Control (0xb4) */ +#define RT5640_EQ_PST_VOL_MASK (0xffff) +#define RT5640_EQ_PST_VOL_SFT 0 + +#define RT5640_NO_JACK BIT(0) +#define RT5640_HEADSET_DET BIT(1) +#define RT5640_HEADPHO_DET BIT(2) + +/* System Clock Source */ +#define RT5640_SCLK_S_MCLK 0 +#define RT5640_SCLK_S_PLL1 1 +#define RT5640_SCLK_S_PLL1_TK 2 +#define RT5640_SCLK_S_RCCLK 3 + +/* PLL1 Source */ +#define RT5640_PLL1_S_MCLK 0 +#define RT5640_PLL1_S_BCLK1 1 +#define RT5640_PLL1_S_BCLK2 2 +#define RT5640_PLL1_S_BCLK3 3 + + +enum { + RT5640_AIF1, + RT5640_AIF2, + RT5640_AIF3, + RT5640_AIFS, +}; + +enum { + RT5640_U_IF1 = 0x1, + RT5640_U_IF2 = 0x2, + RT5640_U_IF3 = 0x4, +}; + +enum { + RT5640_IF_123, + RT5640_IF_132, + RT5640_IF_312, + RT5640_IF_321, + RT5640_IF_231, + RT5640_IF_213, + RT5640_IF_113, + RT5640_IF_223, + RT5640_IF_ALL, +}; + +enum { + RT5640_DMIC_DIS, + RT5640_DMIC1, + RT5640_DMIC2, +}; + +/* filter mask */ +enum { + RT5640_DA_STEREO_FILTER = 0x1, + RT5640_DA_MONO_L_FILTER = (0x1 << 1), + RT5640_DA_MONO_R_FILTER = (0x1 << 2), + RT5640_AD_STEREO_FILTER = (0x1 << 3), + RT5640_AD_MONO_L_FILTER = (0x1 << 4), + RT5640_AD_MONO_R_FILTER = (0x1 << 5), +}; + +struct rt5640_priv { + struct snd_soc_component *component; + struct regmap *regmap; + struct clk *mclk; + + int ldo1_en; /* GPIO for LDO1_EN */ + int irq; + int sysclk; + int sysclk_src; + int lrck[RT5640_AIFS]; + int bclk[RT5640_AIFS]; + int master[RT5640_AIFS]; + + int pll_src; + int pll_in; + int pll_out; + + bool hp_mute; + bool asrc_en; + + /* Jack and button detect data */ + bool ovcd_irq_enabled; + bool pressed; + bool press_reported; + int press_count; + int release_count; + int poll_count; + struct delayed_work bp_work; + struct work_struct jack_work; + struct snd_soc_jack *jack; + unsigned int jd_src; + bool jd_inverted; + unsigned int ovcd_th; + unsigned int ovcd_sf; +}; + +int rt5640_dmic_enable(struct snd_soc_component *component, + bool dmic1_data_pin, bool dmic2_data_pin); +int rt5640_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +#endif diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c new file mode 100644 index 000000000..d34000182 --- /dev/null +++ b/sound/soc/codecs/rt5645.c @@ -0,0 +1,4151 @@ +/* + * rt5645.c -- RT5645 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5645.h" + +#define QUIRK_INV_JD1_1(q) ((q) & 1) +#define QUIRK_LEVEL_IRQ(q) (((q) >> 1) & 1) +#define QUIRK_IN2_DIFF(q) (((q) >> 2) & 1) +#define QUIRK_JD_MODE(q) (((q) >> 4) & 7) +#define QUIRK_DMIC1_DATA_PIN(q) (((q) >> 8) & 3) +#define QUIRK_DMIC2_DATA_PIN(q) (((q) >> 12) & 3) + +static unsigned int quirk = -1; +module_param(quirk, uint, 0444); +MODULE_PARM_DESC(quirk, "RT5645 pdata quirk override"); + +#define RT5645_DEVICE_ID 0x6308 +#define RT5650_DEVICE_ID 0x6419 + +#define RT5645_PR_RANGE_BASE (0xff + 1) +#define RT5645_PR_SPACING 0x100 + +#define RT5645_PR_BASE (RT5645_PR_RANGE_BASE + (0 * RT5645_PR_SPACING)) + +#define RT5645_HWEQ_NUM 57 + +#define TIME_TO_POWER_MS 400 + +static const struct regmap_range_cfg rt5645_ranges[] = { + { + .name = "PR", + .range_min = RT5645_PR_BASE, + .range_max = RT5645_PR_BASE + 0xf8, + .selector_reg = RT5645_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT5645_PRIV_DATA, + .window_len = 0x1, + }, +}; + +static const struct reg_sequence init_list[] = { + {RT5645_PR_BASE + 0x3d, 0x3600}, + {RT5645_PR_BASE + 0x1c, 0xfd70}, + {RT5645_PR_BASE + 0x20, 0x611f}, + {RT5645_PR_BASE + 0x21, 0x4040}, + {RT5645_PR_BASE + 0x23, 0x0004}, + {RT5645_ASRC_4, 0x0120}, +}; + +static const struct reg_sequence rt5650_init_list[] = { + {0xf6, 0x0100}, +}; + +static const struct reg_default rt5645_reg[] = { + { 0x00, 0x0000 }, + { 0x01, 0xc8c8 }, + { 0x02, 0xc8c8 }, + { 0x03, 0xc8c8 }, + { 0x0a, 0x0002 }, + { 0x0b, 0x2827 }, + { 0x0c, 0xe000 }, + { 0x0d, 0x0000 }, + { 0x0e, 0x0000 }, + { 0x0f, 0x0808 }, + { 0x14, 0x3333 }, + { 0x16, 0x4b00 }, + { 0x18, 0x018b }, + { 0x19, 0xafaf }, + { 0x1a, 0xafaf }, + { 0x1b, 0x0001 }, + { 0x1c, 0x2f2f }, + { 0x1d, 0x2f2f }, + { 0x1e, 0x0000 }, + { 0x20, 0x0000 }, + { 0x27, 0x7060 }, + { 0x28, 0x7070 }, + { 0x29, 0x8080 }, + { 0x2a, 0x5656 }, + { 0x2b, 0x5454 }, + { 0x2c, 0xaaa0 }, + { 0x2d, 0x0000 }, + { 0x2f, 0x1002 }, + { 0x31, 0x5000 }, + { 0x32, 0x0000 }, + { 0x33, 0x0000 }, + { 0x34, 0x0000 }, + { 0x35, 0x0000 }, + { 0x3b, 0x0000 }, + { 0x3c, 0x007f }, + { 0x3d, 0x0000 }, + { 0x3e, 0x007f }, + { 0x3f, 0x0000 }, + { 0x40, 0x001f }, + { 0x41, 0x0000 }, + { 0x42, 0x001f }, + { 0x45, 0x6000 }, + { 0x46, 0x003e }, + { 0x47, 0x003e }, + { 0x48, 0xf807 }, + { 0x4a, 0x0004 }, + { 0x4d, 0x0000 }, + { 0x4e, 0x0000 }, + { 0x4f, 0x01ff }, + { 0x50, 0x0000 }, + { 0x51, 0x0000 }, + { 0x52, 0x01ff }, + { 0x53, 0xf000 }, + { 0x56, 0x0111 }, + { 0x57, 0x0064 }, + { 0x58, 0xef0e }, + { 0x59, 0xf0f0 }, + { 0x5a, 0xef0e }, + { 0x5b, 0xf0f0 }, + { 0x5c, 0xef0e }, + { 0x5d, 0xf0f0 }, + { 0x5e, 0xf000 }, + { 0x5f, 0x0000 }, + { 0x61, 0x0300 }, + { 0x62, 0x0000 }, + { 0x63, 0x00c2 }, + { 0x64, 0x0000 }, + { 0x65, 0x0000 }, + { 0x66, 0x0000 }, + { 0x6a, 0x0000 }, + { 0x6c, 0x0aaa }, + { 0x70, 0x8000 }, + { 0x71, 0x8000 }, + { 0x72, 0x8000 }, + { 0x73, 0x7770 }, + { 0x74, 0x3e00 }, + { 0x75, 0x2409 }, + { 0x76, 0x000a }, + { 0x77, 0x0c00 }, + { 0x78, 0x0000 }, + { 0x79, 0x0123 }, + { 0x80, 0x0000 }, + { 0x81, 0x0000 }, + { 0x82, 0x0000 }, + { 0x83, 0x0000 }, + { 0x84, 0x0000 }, + { 0x85, 0x0000 }, + { 0x8a, 0x0120 }, + { 0x8e, 0x0004 }, + { 0x8f, 0x1100 }, + { 0x90, 0x0646 }, + { 0x91, 0x0c06 }, + { 0x93, 0x0000 }, + { 0x94, 0x0200 }, + { 0x95, 0x0000 }, + { 0x9a, 0x2184 }, + { 0x9b, 0x010a }, + { 0x9c, 0x0aea }, + { 0x9d, 0x000c }, + { 0x9e, 0x0400 }, + { 0xa0, 0xa0a8 }, + { 0xa1, 0x0059 }, + { 0xa2, 0x0001 }, + { 0xae, 0x6000 }, + { 0xaf, 0x0000 }, + { 0xb0, 0x6000 }, + { 0xb1, 0x0000 }, + { 0xb2, 0x0000 }, + { 0xb3, 0x001f }, + { 0xb4, 0x020c }, + { 0xb5, 0x1f00 }, + { 0xb6, 0x0000 }, + { 0xbb, 0x0000 }, + { 0xbc, 0x0000 }, + { 0xbd, 0x0000 }, + { 0xbe, 0x0000 }, + { 0xbf, 0x3100 }, + { 0xc0, 0x0000 }, + { 0xc1, 0x0000 }, + { 0xc2, 0x0000 }, + { 0xc3, 0x2000 }, + { 0xcd, 0x0000 }, + { 0xce, 0x0000 }, + { 0xcf, 0x1813 }, + { 0xd0, 0x0690 }, + { 0xd1, 0x1c17 }, + { 0xd3, 0xb320 }, + { 0xd4, 0x0000 }, + { 0xd6, 0x0400 }, + { 0xd9, 0x0809 }, + { 0xda, 0x0000 }, + { 0xdb, 0x0003 }, + { 0xdc, 0x0049 }, + { 0xdd, 0x001b }, + { 0xdf, 0x0008 }, + { 0xe0, 0x4000 }, + { 0xe6, 0x8000 }, + { 0xe7, 0x0200 }, + { 0xec, 0xb300 }, + { 0xed, 0x0000 }, + { 0xf0, 0x001f }, + { 0xf1, 0x020c }, + { 0xf2, 0x1f00 }, + { 0xf3, 0x0000 }, + { 0xf4, 0x4000 }, + { 0xf8, 0x0000 }, + { 0xf9, 0x0000 }, + { 0xfa, 0x2060 }, + { 0xfb, 0x4040 }, + { 0xfc, 0x0000 }, + { 0xfd, 0x0002 }, + { 0xfe, 0x10ec }, + { 0xff, 0x6308 }, +}; + +static const struct reg_default rt5650_reg[] = { + { 0x00, 0x0000 }, + { 0x01, 0xc8c8 }, + { 0x02, 0xc8c8 }, + { 0x03, 0xc8c8 }, + { 0x0a, 0x0002 }, + { 0x0b, 0x2827 }, + { 0x0c, 0xe000 }, + { 0x0d, 0x0000 }, + { 0x0e, 0x0000 }, + { 0x0f, 0x0808 }, + { 0x14, 0x3333 }, + { 0x16, 0x4b00 }, + { 0x18, 0x018b }, + { 0x19, 0xafaf }, + { 0x1a, 0xafaf }, + { 0x1b, 0x0001 }, + { 0x1c, 0x2f2f }, + { 0x1d, 0x2f2f }, + { 0x1e, 0x0000 }, + { 0x20, 0x0000 }, + { 0x27, 0x7060 }, + { 0x28, 0x7070 }, + { 0x29, 0x8080 }, + { 0x2a, 0x5656 }, + { 0x2b, 0x5454 }, + { 0x2c, 0xaaa0 }, + { 0x2d, 0x0000 }, + { 0x2f, 0x5002 }, + { 0x31, 0x5000 }, + { 0x32, 0x0000 }, + { 0x33, 0x0000 }, + { 0x34, 0x0000 }, + { 0x35, 0x0000 }, + { 0x3b, 0x0000 }, + { 0x3c, 0x007f }, + { 0x3d, 0x0000 }, + { 0x3e, 0x007f }, + { 0x3f, 0x0000 }, + { 0x40, 0x001f }, + { 0x41, 0x0000 }, + { 0x42, 0x001f }, + { 0x45, 0x6000 }, + { 0x46, 0x003e }, + { 0x47, 0x003e }, + { 0x48, 0xf807 }, + { 0x4a, 0x0004 }, + { 0x4d, 0x0000 }, + { 0x4e, 0x0000 }, + { 0x4f, 0x01ff }, + { 0x50, 0x0000 }, + { 0x51, 0x0000 }, + { 0x52, 0x01ff }, + { 0x53, 0xf000 }, + { 0x56, 0x0111 }, + { 0x57, 0x0064 }, + { 0x58, 0xef0e }, + { 0x59, 0xf0f0 }, + { 0x5a, 0xef0e }, + { 0x5b, 0xf0f0 }, + { 0x5c, 0xef0e }, + { 0x5d, 0xf0f0 }, + { 0x5e, 0xf000 }, + { 0x5f, 0x0000 }, + { 0x61, 0x0300 }, + { 0x62, 0x0000 }, + { 0x63, 0x00c2 }, + { 0x64, 0x0000 }, + { 0x65, 0x0000 }, + { 0x66, 0x0000 }, + { 0x6a, 0x0000 }, + { 0x6c, 0x0aaa }, + { 0x70, 0x8000 }, + { 0x71, 0x8000 }, + { 0x72, 0x8000 }, + { 0x73, 0x7770 }, + { 0x74, 0x3e00 }, + { 0x75, 0x2409 }, + { 0x76, 0x000a }, + { 0x77, 0x0c00 }, + { 0x78, 0x0000 }, + { 0x79, 0x0123 }, + { 0x7a, 0x0123 }, + { 0x80, 0x0000 }, + { 0x81, 0x0000 }, + { 0x82, 0x0000 }, + { 0x83, 0x0000 }, + { 0x84, 0x0000 }, + { 0x85, 0x0000 }, + { 0x8a, 0x0120 }, + { 0x8e, 0x0004 }, + { 0x8f, 0x1100 }, + { 0x90, 0x0646 }, + { 0x91, 0x0c06 }, + { 0x93, 0x0000 }, + { 0x94, 0x0200 }, + { 0x95, 0x0000 }, + { 0x9a, 0x2184 }, + { 0x9b, 0x010a }, + { 0x9c, 0x0aea }, + { 0x9d, 0x000c }, + { 0x9e, 0x0400 }, + { 0xa0, 0xa0a8 }, + { 0xa1, 0x0059 }, + { 0xa2, 0x0001 }, + { 0xae, 0x6000 }, + { 0xaf, 0x0000 }, + { 0xb0, 0x6000 }, + { 0xb1, 0x0000 }, + { 0xb2, 0x0000 }, + { 0xb3, 0x001f }, + { 0xb4, 0x020c }, + { 0xb5, 0x1f00 }, + { 0xb6, 0x0000 }, + { 0xbb, 0x0000 }, + { 0xbc, 0x0000 }, + { 0xbd, 0x0000 }, + { 0xbe, 0x0000 }, + { 0xbf, 0x3100 }, + { 0xc0, 0x0000 }, + { 0xc1, 0x0000 }, + { 0xc2, 0x0000 }, + { 0xc3, 0x2000 }, + { 0xcd, 0x0000 }, + { 0xce, 0x0000 }, + { 0xcf, 0x1813 }, + { 0xd0, 0x0690 }, + { 0xd1, 0x1c17 }, + { 0xd3, 0xb320 }, + { 0xd4, 0x0000 }, + { 0xd6, 0x0400 }, + { 0xd9, 0x0809 }, + { 0xda, 0x0000 }, + { 0xdb, 0x0003 }, + { 0xdc, 0x0049 }, + { 0xdd, 0x001b }, + { 0xdf, 0x0008 }, + { 0xe0, 0x4000 }, + { 0xe6, 0x8000 }, + { 0xe7, 0x0200 }, + { 0xec, 0xb300 }, + { 0xed, 0x0000 }, + { 0xf0, 0x001f }, + { 0xf1, 0x020c }, + { 0xf2, 0x1f00 }, + { 0xf3, 0x0000 }, + { 0xf4, 0x4000 }, + { 0xf8, 0x0000 }, + { 0xf9, 0x0000 }, + { 0xfa, 0x2060 }, + { 0xfb, 0x4040 }, + { 0xfc, 0x0000 }, + { 0xfd, 0x0002 }, + { 0xfe, 0x10ec }, + { 0xff, 0x6308 }, +}; + +struct rt5645_eq_param_s { + unsigned short reg; + unsigned short val; +}; + +static const char *const rt5645_supply_names[] = { + "avdd", + "cpvdd", +}; + +struct rt5645_priv { + struct snd_soc_component *component; + struct rt5645_platform_data pdata; + struct regmap *regmap; + struct i2c_client *i2c; + struct gpio_desc *gpiod_hp_det; + struct snd_soc_jack *hp_jack; + struct snd_soc_jack *mic_jack; + struct snd_soc_jack *btn_jack; + struct delayed_work jack_detect_work, rcclock_work; + struct regulator_bulk_data supplies[ARRAY_SIZE(rt5645_supply_names)]; + struct rt5645_eq_param_s *eq_param; + struct timer_list btn_check_timer; + + int codec_type; + int sysclk; + int sysclk_src; + int lrck[RT5645_AIFS]; + int bclk[RT5645_AIFS]; + int master[RT5645_AIFS]; + + int pll_src; + int pll_in; + int pll_out; + + int jack_type; + bool en_button_func; + bool hp_on; + int v_id; +}; + +static int rt5645_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, RT5645_RESET, 0); +} + +static bool rt5645_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5645_ranges); i++) { + if (reg >= rt5645_ranges[i].range_min && + reg <= rt5645_ranges[i].range_max) { + return true; + } + } + + switch (reg) { + case RT5645_RESET: + case RT5645_PRIV_INDEX: + case RT5645_PRIV_DATA: + case RT5645_IN1_CTRL1: + case RT5645_IN1_CTRL2: + case RT5645_IN1_CTRL3: + case RT5645_A_JD_CTRL1: + case RT5645_ADC_EQ_CTRL1: + case RT5645_EQ_CTRL1: + case RT5645_ALC_CTRL_1: + case RT5645_IRQ_CTRL2: + case RT5645_IRQ_CTRL3: + case RT5645_INT_IRQ_ST: + case RT5645_IL_CMD: + case RT5650_4BTN_IL_CMD1: + case RT5645_VENDOR_ID: + case RT5645_VENDOR_ID1: + case RT5645_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool rt5645_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5645_ranges); i++) { + if (reg >= rt5645_ranges[i].range_min && + reg <= rt5645_ranges[i].range_max) { + return true; + } + } + + switch (reg) { + case RT5645_RESET: + case RT5645_SPK_VOL: + case RT5645_HP_VOL: + case RT5645_LOUT1: + case RT5645_IN1_CTRL1: + case RT5645_IN1_CTRL2: + case RT5645_IN1_CTRL3: + case RT5645_IN2_CTRL: + case RT5645_INL1_INR1_VOL: + case RT5645_SPK_FUNC_LIM: + case RT5645_ADJ_HPF_CTRL: + case RT5645_DAC1_DIG_VOL: + case RT5645_DAC2_DIG_VOL: + case RT5645_DAC_CTRL: + case RT5645_STO1_ADC_DIG_VOL: + case RT5645_MONO_ADC_DIG_VOL: + case RT5645_ADC_BST_VOL1: + case RT5645_ADC_BST_VOL2: + case RT5645_STO1_ADC_MIXER: + case RT5645_MONO_ADC_MIXER: + case RT5645_AD_DA_MIXER: + case RT5645_STO_DAC_MIXER: + case RT5645_MONO_DAC_MIXER: + case RT5645_DIG_MIXER: + case RT5650_A_DAC_SOUR: + case RT5645_DIG_INF1_DATA: + case RT5645_PDM_OUT_CTRL: + case RT5645_REC_L1_MIXER: + case RT5645_REC_L2_MIXER: + case RT5645_REC_R1_MIXER: + case RT5645_REC_R2_MIXER: + case RT5645_HPMIXL_CTRL: + case RT5645_HPOMIXL_CTRL: + case RT5645_HPMIXR_CTRL: + case RT5645_HPOMIXR_CTRL: + case RT5645_HPO_MIXER: + case RT5645_SPK_L_MIXER: + case RT5645_SPK_R_MIXER: + case RT5645_SPO_MIXER: + case RT5645_SPO_CLSD_RATIO: + case RT5645_OUT_L1_MIXER: + case RT5645_OUT_R1_MIXER: + case RT5645_OUT_L_GAIN1: + case RT5645_OUT_L_GAIN2: + case RT5645_OUT_R_GAIN1: + case RT5645_OUT_R_GAIN2: + case RT5645_LOUT_MIXER: + case RT5645_HAPTIC_CTRL1: + case RT5645_HAPTIC_CTRL2: + case RT5645_HAPTIC_CTRL3: + case RT5645_HAPTIC_CTRL4: + case RT5645_HAPTIC_CTRL5: + case RT5645_HAPTIC_CTRL6: + case RT5645_HAPTIC_CTRL7: + case RT5645_HAPTIC_CTRL8: + case RT5645_HAPTIC_CTRL9: + case RT5645_HAPTIC_CTRL10: + case RT5645_PWR_DIG1: + case RT5645_PWR_DIG2: + case RT5645_PWR_ANLG1: + case RT5645_PWR_ANLG2: + case RT5645_PWR_MIXER: + case RT5645_PWR_VOL: + case RT5645_PRIV_INDEX: + case RT5645_PRIV_DATA: + case RT5645_I2S1_SDP: + case RT5645_I2S2_SDP: + case RT5645_ADDA_CLK1: + case RT5645_ADDA_CLK2: + case RT5645_DMIC_CTRL1: + case RT5645_DMIC_CTRL2: + case RT5645_TDM_CTRL_1: + case RT5645_TDM_CTRL_2: + case RT5645_TDM_CTRL_3: + case RT5650_TDM_CTRL_4: + case RT5645_GLB_CLK: + case RT5645_PLL_CTRL1: + case RT5645_PLL_CTRL2: + case RT5645_ASRC_1: + case RT5645_ASRC_2: + case RT5645_ASRC_3: + case RT5645_ASRC_4: + case RT5645_DEPOP_M1: + case RT5645_DEPOP_M2: + case RT5645_DEPOP_M3: + case RT5645_CHARGE_PUMP: + case RT5645_MICBIAS: + case RT5645_A_JD_CTRL1: + case RT5645_VAD_CTRL4: + case RT5645_CLSD_OUT_CTRL: + case RT5645_ADC_EQ_CTRL1: + case RT5645_ADC_EQ_CTRL2: + case RT5645_EQ_CTRL1: + case RT5645_EQ_CTRL2: + case RT5645_ALC_CTRL_1: + case RT5645_ALC_CTRL_2: + case RT5645_ALC_CTRL_3: + case RT5645_ALC_CTRL_4: + case RT5645_ALC_CTRL_5: + case RT5645_JD_CTRL: + case RT5645_IRQ_CTRL1: + case RT5645_IRQ_CTRL2: + case RT5645_IRQ_CTRL3: + case RT5645_INT_IRQ_ST: + case RT5645_GPIO_CTRL1: + case RT5645_GPIO_CTRL2: + case RT5645_GPIO_CTRL3: + case RT5645_BASS_BACK: + case RT5645_MP3_PLUS1: + case RT5645_MP3_PLUS2: + case RT5645_ADJ_HPF1: + case RT5645_ADJ_HPF2: + case RT5645_HP_CALIB_AMP_DET: + case RT5645_SV_ZCD1: + case RT5645_SV_ZCD2: + case RT5645_IL_CMD: + case RT5645_IL_CMD2: + case RT5645_IL_CMD3: + case RT5650_4BTN_IL_CMD1: + case RT5650_4BTN_IL_CMD2: + case RT5645_DRC1_HL_CTRL1: + case RT5645_DRC2_HL_CTRL1: + case RT5645_ADC_MONO_HP_CTRL1: + case RT5645_ADC_MONO_HP_CTRL2: + case RT5645_DRC2_CTRL1: + case RT5645_DRC2_CTRL2: + case RT5645_DRC2_CTRL3: + case RT5645_DRC2_CTRL4: + case RT5645_DRC2_CTRL5: + case RT5645_JD_CTRL3: + case RT5645_JD_CTRL4: + case RT5645_GEN_CTRL1: + case RT5645_GEN_CTRL2: + case RT5645_GEN_CTRL3: + case RT5645_VENDOR_ID: + case RT5645_VENDOR_ID1: + case RT5645_VENDOR_ID2: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* {-6, -4.5, -3, -1.5, 0, 0.82, 1.58, 2.28} dB */ +static const DECLARE_TLV_DB_RANGE(spk_clsd_tlv, + 0, 4, TLV_DB_SCALE_ITEM(-600, 150, 0), + 5, 5, TLV_DB_SCALE_ITEM(82, 0, 0), + 6, 6, TLV_DB_SCALE_ITEM(158, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(228, 0, 0) +); + +static int rt5645_hweq_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = RT5645_HWEQ_NUM * sizeof(struct rt5645_eq_param_s); + + return 0; +} + +static int rt5645_hweq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + struct rt5645_eq_param_s *eq_param = + (struct rt5645_eq_param_s *)ucontrol->value.bytes.data; + int i; + + for (i = 0; i < RT5645_HWEQ_NUM; i++) { + eq_param[i].reg = cpu_to_be16(rt5645->eq_param[i].reg); + eq_param[i].val = cpu_to_be16(rt5645->eq_param[i].val); + } + + return 0; +} + +static bool rt5645_validate_hweq(unsigned short reg) +{ + if ((reg >= 0x1a4 && reg <= 0x1cd) | (reg >= 0x1e5 && reg <= 0x1f8) | + (reg == RT5645_EQ_CTRL2)) + return true; + + return false; +} + +static int rt5645_hweq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + struct rt5645_eq_param_s *eq_param = + (struct rt5645_eq_param_s *)ucontrol->value.bytes.data; + int i; + + for (i = 0; i < RT5645_HWEQ_NUM; i++) { + eq_param[i].reg = be16_to_cpu(eq_param[i].reg); + eq_param[i].val = be16_to_cpu(eq_param[i].val); + } + + /* The final setting of the table should be RT5645_EQ_CTRL2 */ + for (i = RT5645_HWEQ_NUM - 1; i >= 0; i--) { + if (eq_param[i].reg == 0) + continue; + else if (eq_param[i].reg != RT5645_EQ_CTRL2) + return 0; + else + break; + } + + for (i = 0; i < RT5645_HWEQ_NUM; i++) { + if (!rt5645_validate_hweq(eq_param[i].reg) && + eq_param[i].reg != 0) + return 0; + else if (eq_param[i].reg == 0) + break; + } + + memcpy(rt5645->eq_param, eq_param, + RT5645_HWEQ_NUM * sizeof(struct rt5645_eq_param_s)); + + return 0; +} + +#define RT5645_HWEQ(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = rt5645_hweq_info, \ + .get = rt5645_hweq_get, \ + .put = rt5645_hweq_put \ +} + +static int rt5645_spk_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + int ret; + + regmap_update_bits(rt5645->regmap, RT5645_MICBIAS, + RT5645_PWR_CLK25M_MASK, RT5645_PWR_CLK25M_PU); + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + mod_delayed_work(system_power_efficient_wq, &rt5645->rcclock_work, + msecs_to_jiffies(200)); + + return ret; +} + +static const char * const rt5645_dac1_vol_ctrl_mode_text[] = { + "immediately", "zero crossing", "soft ramp" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_dac1_vol_ctrl_mode, RT5645_PR_BASE, + RT5645_DA1_ZDET_SFT, rt5645_dac1_vol_ctrl_mode_text); + +static const struct snd_kcontrol_new rt5645_snd_controls[] = { + /* Speaker Output Volume */ + SOC_DOUBLE("Speaker Channel Switch", RT5645_SPK_VOL, + RT5645_VOL_L_SFT, RT5645_VOL_R_SFT, 1, 1), + SOC_DOUBLE_EXT_TLV("Speaker Playback Volume", RT5645_SPK_VOL, + RT5645_L_VOL_SFT, RT5645_R_VOL_SFT, 39, 1, snd_soc_get_volsw, + rt5645_spk_put_volsw, out_vol_tlv), + + /* ClassD modulator Speaker Gain Ratio */ + SOC_SINGLE_TLV("Speaker ClassD Playback Volume", RT5645_SPO_CLSD_RATIO, + RT5645_SPK_G_CLSD_SFT, 7, 0, spk_clsd_tlv), + + /* Headphone Output Volume */ + SOC_DOUBLE("Headphone Channel Switch", RT5645_HP_VOL, + RT5645_VOL_L_SFT, RT5645_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("Headphone Playback Volume", RT5645_HP_VOL, + RT5645_L_VOL_SFT, RT5645_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* OUTPUT Control */ + SOC_DOUBLE("OUT Playback Switch", RT5645_LOUT1, + RT5645_L_MUTE_SFT, RT5645_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("OUT Channel Switch", RT5645_LOUT1, + RT5645_VOL_L_SFT, RT5645_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("OUT Playback Volume", RT5645_LOUT1, + RT5645_L_VOL_SFT, RT5645_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE("DAC2 Playback Switch", RT5645_DAC_CTRL, + RT5645_M_DAC_L2_VOL_SFT, RT5645_M_DAC_R2_VOL_SFT, 1, 1), + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5645_DAC1_DIG_VOL, + RT5645_L_VOL_SFT + 1, RT5645_R_VOL_SFT + 1, 87, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("Mono DAC Playback Volume", RT5645_DAC2_DIG_VOL, + RT5645_L_VOL_SFT + 1, RT5645_R_VOL_SFT + 1, 87, 0, dac_vol_tlv), + + /* IN1/IN2 Control */ + SOC_SINGLE_TLV("IN1 Boost", RT5645_IN1_CTRL1, + RT5645_BST_SFT1, 12, 0, bst_tlv), + SOC_SINGLE_TLV("IN2 Boost", RT5645_IN2_CTRL, + RT5645_BST_SFT2, 8, 0, bst_tlv), + + /* INL/INR Volume Control */ + SOC_DOUBLE_TLV("IN Capture Volume", RT5645_INL1_INR1_VOL, + RT5645_INL_VOL_SFT, RT5645_INR_VOL_SFT, 31, 1, in_vol_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC Capture Switch", RT5645_STO1_ADC_DIG_VOL, + RT5645_L_MUTE_SFT, RT5645_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("ADC Capture Volume", RT5645_STO1_ADC_DIG_VOL, + RT5645_L_VOL_SFT + 1, RT5645_R_VOL_SFT + 1, 63, 0, adc_vol_tlv), + SOC_DOUBLE("Mono ADC Capture Switch", RT5645_MONO_ADC_DIG_VOL, + RT5645_L_MUTE_SFT, RT5645_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5645_MONO_ADC_DIG_VOL, + RT5645_L_VOL_SFT + 1, RT5645_R_VOL_SFT + 1, 63, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("ADC Boost Capture Volume", RT5645_ADC_BST_VOL1, + RT5645_STO1_ADC_L_BST_SFT, RT5645_STO1_ADC_R_BST_SFT, 3, 0, + adc_bst_tlv), + SOC_DOUBLE_TLV("Mono ADC Boost Capture Volume", RT5645_ADC_BST_VOL2, + RT5645_MONO_ADC_L_BST_SFT, RT5645_MONO_ADC_R_BST_SFT, 3, 0, + adc_bst_tlv), + + /* I2S2 function select */ + SOC_SINGLE("I2S2 Func Switch", RT5645_GPIO_CTRL1, RT5645_I2S2_SEL_SFT, + 1, 1), + RT5645_HWEQ("Speaker HWEQ"), + + /* Digital Soft Volume Control */ + SOC_ENUM("DAC1 Digital Volume Control Func", rt5645_dac1_vol_ctrl_mode), +}; + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + int idx, rate; + + rate = rt5645->sysclk / rl6231_get_pre_div(rt5645->regmap, + RT5645_ADDA_CLK1, RT5645_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rate); + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else + snd_soc_component_update_bits(component, RT5645_DMIC_CTRL1, + RT5645_DMIC_CLK_MASK, idx << RT5645_DMIC_CLK_SFT); + return idx; +} + +static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int val; + + val = snd_soc_component_read32(component, RT5645_GLB_CLK); + val &= RT5645_SCLK_SRC_MASK; + if (val == RT5645_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg, shift, val; + + switch (source->shift) { + case 0: + reg = RT5645_ASRC_3; + shift = 0; + break; + case 1: + reg = RT5645_ASRC_3; + shift = 4; + break; + case 3: + reg = RT5645_ASRC_2; + shift = 0; + break; + case 8: + reg = RT5645_ASRC_2; + shift = 4; + break; + case 9: + reg = RT5645_ASRC_2; + shift = 8; + break; + case 10: + reg = RT5645_ASRC_2; + shift = 12; + break; + default: + return 0; + } + + val = (snd_soc_component_read32(component, reg) >> shift) & 0xf; + switch (val) { + case 1: + case 2: + case 3: + case 4: + return 1; + default: + return 0; + } + +} + +static int rt5645_enable_hweq(struct snd_soc_component *component) +{ + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < RT5645_HWEQ_NUM; i++) { + if (rt5645_validate_hweq(rt5645->eq_param[i].reg)) + regmap_write(rt5645->regmap, rt5645->eq_param[i].reg, + rt5645->eq_param[i].val); + else + break; + } + + return 0; +} + +/** + * rt5645_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5645 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the codec driver will turn on ASRC + * for these filters if ASRC is selected as their clock source. + */ +int rt5645_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + unsigned int asrc2_mask = 0; + unsigned int asrc2_value = 0; + unsigned int asrc3_mask = 0; + unsigned int asrc3_value = 0; + + switch (clk_src) { + case RT5645_CLK_SEL_SYS: + case RT5645_CLK_SEL_I2S1_ASRC: + case RT5645_CLK_SEL_I2S2_ASRC: + case RT5645_CLK_SEL_SYS2: + break; + + default: + return -EINVAL; + } + + if (filter_mask & RT5645_DA_STEREO_FILTER) { + asrc2_mask |= RT5645_DA_STO_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5645_DA_STO_CLK_SEL_MASK) + | (clk_src << RT5645_DA_STO_CLK_SEL_SFT); + } + + if (filter_mask & RT5645_DA_MONO_L_FILTER) { + asrc2_mask |= RT5645_DA_MONOL_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5645_DA_MONOL_CLK_SEL_MASK) + | (clk_src << RT5645_DA_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5645_DA_MONO_R_FILTER) { + asrc2_mask |= RT5645_DA_MONOR_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5645_DA_MONOR_CLK_SEL_MASK) + | (clk_src << RT5645_DA_MONOR_CLK_SEL_SFT); + } + + if (filter_mask & RT5645_AD_STEREO_FILTER) { + asrc2_mask |= RT5645_AD_STO1_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5645_AD_STO1_CLK_SEL_MASK) + | (clk_src << RT5645_AD_STO1_CLK_SEL_SFT); + } + + if (filter_mask & RT5645_AD_MONO_L_FILTER) { + asrc3_mask |= RT5645_AD_MONOL_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5645_AD_MONOL_CLK_SEL_MASK) + | (clk_src << RT5645_AD_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5645_AD_MONO_R_FILTER) { + asrc3_mask |= RT5645_AD_MONOR_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5645_AD_MONOR_CLK_SEL_MASK) + | (clk_src << RT5645_AD_MONOR_CLK_SEL_SFT); + } + + if (asrc2_mask) + snd_soc_component_update_bits(component, RT5645_ASRC_2, + asrc2_mask, asrc2_value); + + if (asrc3_mask) + snd_soc_component_update_bits(component, RT5645_ASRC_3, + asrc3_mask, asrc3_value); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5645_sel_asrc_clk_src); + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5645_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5645_STO1_ADC_MIXER, + RT5645_M_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5645_STO1_ADC_MIXER, + RT5645_M_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5645_STO1_ADC_MIXER, + RT5645_M_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5645_STO1_ADC_MIXER, + RT5645_M_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_mono_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5645_MONO_ADC_MIXER, + RT5645_M_MONO_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5645_MONO_ADC_MIXER, + RT5645_M_MONO_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_mono_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5645_MONO_ADC_MIXER, + RT5645_M_MONO_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5645_MONO_ADC_MIXER, + RT5645_M_MONO_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5645_AD_DA_MIXER, + RT5645_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 Switch", RT5645_AD_DA_MIXER, + RT5645_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5645_AD_DA_MIXER, + RT5645_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 Switch", RT5645_AD_DA_MIXER, + RT5645_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5645_STO_DAC_MIXER, + RT5645_M_DAC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5645_STO_DAC_MIXER, + RT5645_M_DAC_L2_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_STO_DAC_MIXER, + RT5645_M_DAC_R1_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_STO_DAC_MIXER, + RT5645_M_DAC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5645_STO_DAC_MIXER, + RT5645_M_DAC_R2_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5645_STO_DAC_MIXER, + RT5645_M_DAC_L1_STO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_mono_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5645_MONO_DAC_MIXER, + RT5645_M_DAC_L1_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5645_MONO_DAC_MIXER, + RT5645_M_DAC_L2_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5645_MONO_DAC_MIXER, + RT5645_M_DAC_R2_MONO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_mono_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_MONO_DAC_MIXER, + RT5645_M_DAC_R1_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5645_MONO_DAC_MIXER, + RT5645_M_DAC_R2_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5645_MONO_DAC_MIXER, + RT5645_M_DAC_L2_MONO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_dig_l_mix[] = { + SOC_DAPM_SINGLE("Sto DAC Mix L Switch", RT5645_DIG_MIXER, + RT5645_M_STO_L_DAC_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5645_DIG_MIXER, + RT5645_M_DAC_L2_DAC_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5645_DIG_MIXER, + RT5645_M_DAC_R2_DAC_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_dig_r_mix[] = { + SOC_DAPM_SINGLE("Sto DAC Mix R Switch", RT5645_DIG_MIXER, + RT5645_M_STO_R_DAC_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5645_DIG_MIXER, + RT5645_M_DAC_R2_DAC_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5645_DIG_MIXER, + RT5645_M_DAC_L2_DAC_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5645_rec_l_mix[] = { + SOC_DAPM_SINGLE("HPOL Switch", RT5645_REC_L2_MIXER, + RT5645_M_HP_L_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5645_REC_L2_MIXER, + RT5645_M_IN_L_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5645_REC_L2_MIXER, + RT5645_M_BST2_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5645_REC_L2_MIXER, + RT5645_M_BST1_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXL Switch", RT5645_REC_L2_MIXER, + RT5645_M_OM_L_RM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_rec_r_mix[] = { + SOC_DAPM_SINGLE("HPOR Switch", RT5645_REC_R2_MIXER, + RT5645_M_HP_R_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5645_REC_R2_MIXER, + RT5645_M_IN_R_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5645_REC_R2_MIXER, + RT5645_M_BST2_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5645_REC_R2_MIXER, + RT5645_M_BST1_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXR Switch", RT5645_REC_R2_MIXER, + RT5645_M_OM_R_RM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_spk_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5645_SPK_L_MIXER, + RT5645_M_DAC_L1_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5645_SPK_L_MIXER, + RT5645_M_DAC_L2_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5645_SPK_L_MIXER, + RT5645_M_IN_L_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5645_SPK_L_MIXER, + RT5645_M_BST1_L_SM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_spk_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_SPK_R_MIXER, + RT5645_M_DAC_R1_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5645_SPK_R_MIXER, + RT5645_M_DAC_R2_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5645_SPK_R_MIXER, + RT5645_M_IN_R_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5645_SPK_R_MIXER, + RT5645_M_BST2_R_SM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_out_l_mix[] = { + SOC_DAPM_SINGLE("BST1 Switch", RT5645_OUT_L1_MIXER, + RT5645_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5645_OUT_L1_MIXER, + RT5645_M_IN_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5645_OUT_L1_MIXER, + RT5645_M_DAC_L2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5645_OUT_L1_MIXER, + RT5645_M_DAC_L1_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_out_r_mix[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5645_OUT_R1_MIXER, + RT5645_M_BST2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5645_OUT_R1_MIXER, + RT5645_M_IN_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5645_OUT_R1_MIXER, + RT5645_M_DAC_R2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_OUT_R1_MIXER, + RT5645_M_DAC_R1_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_spo_l_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_SPO_MIXER, + RT5645_M_DAC_R1_SPM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5645_SPO_MIXER, + RT5645_M_DAC_L1_SPM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("SPKVOL R Switch", RT5645_SPO_MIXER, + RT5645_M_SV_R_SPM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("SPKVOL L Switch", RT5645_SPO_MIXER, + RT5645_M_SV_L_SPM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_spo_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_SPO_MIXER, + RT5645_M_DAC_R1_SPM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("SPKVOL R Switch", RT5645_SPO_MIXER, + RT5645_M_SV_R_SPM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_hpo_mix[] = { + SOC_DAPM_SINGLE("DAC1 Switch", RT5645_HPO_MIXER, + RT5645_M_DAC1_HM_SFT, 1, 1), + SOC_DAPM_SINGLE("HPVOL Switch", RT5645_HPO_MIXER, + RT5645_M_HPVOL_HM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_hpvoll_mix[] = { + SOC_DAPM_SINGLE("DAC1 Switch", RT5645_HPOMIXL_CTRL, + RT5645_M_DAC1_HV_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC2 Switch", RT5645_HPOMIXL_CTRL, + RT5645_M_DAC2_HV_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5645_HPOMIXL_CTRL, + RT5645_M_IN_HV_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5645_HPOMIXL_CTRL, + RT5645_M_BST1_HV_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_hpvolr_mix[] = { + SOC_DAPM_SINGLE("DAC1 Switch", RT5645_HPOMIXR_CTRL, + RT5645_M_DAC1_HV_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC2 Switch", RT5645_HPOMIXR_CTRL, + RT5645_M_DAC2_HV_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5645_HPOMIXR_CTRL, + RT5645_M_IN_HV_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5645_HPOMIXR_CTRL, + RT5645_M_BST2_HV_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5645_lout_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5645_LOUT_MIXER, + RT5645_M_DAC_L1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5645_LOUT_MIXER, + RT5645_M_DAC_R1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTMIX L Switch", RT5645_LOUT_MIXER, + RT5645_M_OV_L_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTMIX R Switch", RT5645_LOUT_MIXER, + RT5645_M_OV_R_LM_SFT, 1, 1), +}; + +/*DAC1 L/R source*/ /* MX-29 [9:8] [11:10] */ +static const char * const rt5645_dac1_src[] = { + "IF1 DAC", "IF2 DAC", "IF3 DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_dac1l_enum, RT5645_AD_DA_MIXER, + RT5645_DAC1_L_SEL_SFT, rt5645_dac1_src); + +static const struct snd_kcontrol_new rt5645_dac1l_mux = + SOC_DAPM_ENUM("DAC1 L source", rt5645_dac1l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5645_dac1r_enum, RT5645_AD_DA_MIXER, + RT5645_DAC1_R_SEL_SFT, rt5645_dac1_src); + +static const struct snd_kcontrol_new rt5645_dac1r_mux = + SOC_DAPM_ENUM("DAC1 R source", rt5645_dac1r_enum); + +/*DAC2 L/R source*/ /* MX-1B [6:4] [2:0] */ +static const char * const rt5645_dac12_src[] = { + "IF1 DAC", "IF2 DAC", "IF3 DAC", "Mono ADC", "VAD_ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_dac2l_enum, RT5645_DAC_CTRL, + RT5645_DAC2_L_SEL_SFT, rt5645_dac12_src); + +static const struct snd_kcontrol_new rt5645_dac_l2_mux = + SOC_DAPM_ENUM("DAC2 L source", rt5645_dac2l_enum); + +static const char * const rt5645_dacr2_src[] = { + "IF1 DAC", "IF2 DAC", "IF3 DAC", "Mono ADC", "Haptic" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_dac2r_enum, RT5645_DAC_CTRL, + RT5645_DAC2_R_SEL_SFT, rt5645_dacr2_src); + +static const struct snd_kcontrol_new rt5645_dac_r2_mux = + SOC_DAPM_ENUM("DAC2 R source", rt5645_dac2r_enum); + + +/* INL/R source */ +static const char * const rt5645_inl_src[] = { + "IN2P", "MonoP" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_inl_enum, RT5645_INL1_INR1_VOL, + RT5645_INL_SEL_SFT, rt5645_inl_src); + +static const struct snd_kcontrol_new rt5645_inl_mux = + SOC_DAPM_ENUM("INL source", rt5645_inl_enum); + +static const char * const rt5645_inr_src[] = { + "IN2N", "MonoN" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_inr_enum, RT5645_INL1_INR1_VOL, + RT5645_INR_SEL_SFT, rt5645_inr_src); + +static const struct snd_kcontrol_new rt5645_inr_mux = + SOC_DAPM_ENUM("INR source", rt5645_inr_enum); + +/* Stereo1 ADC source */ +/* MX-27 [12] */ +static const char * const rt5645_stereo_adc1_src[] = { + "DAC MIX", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_stereo1_adc1_enum, RT5645_STO1_ADC_MIXER, + RT5645_ADC_1_SRC_SFT, rt5645_stereo_adc1_src); + +static const struct snd_kcontrol_new rt5645_sto_adc1_mux = + SOC_DAPM_ENUM("Stereo1 ADC1 Mux", rt5645_stereo1_adc1_enum); + +/* MX-27 [11] */ +static const char * const rt5645_stereo_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_stereo1_adc2_enum, RT5645_STO1_ADC_MIXER, + RT5645_ADC_2_SRC_SFT, rt5645_stereo_adc2_src); + +static const struct snd_kcontrol_new rt5645_sto_adc2_mux = + SOC_DAPM_ENUM("Stereo1 ADC2 Mux", rt5645_stereo1_adc2_enum); + +/* MX-27 [8] */ +static const char * const rt5645_stereo_dmic_src[] = { + "DMIC1", "DMIC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_stereo1_dmic_enum, RT5645_STO1_ADC_MIXER, + RT5645_DMIC_SRC_SFT, rt5645_stereo_dmic_src); + +static const struct snd_kcontrol_new rt5645_sto1_dmic_mux = + SOC_DAPM_ENUM("Stereo1 DMIC source", rt5645_stereo1_dmic_enum); + +/* Mono ADC source */ +/* MX-28 [12] */ +static const char * const rt5645_mono_adc_l1_src[] = { + "Mono DAC MIXL", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_mono_adc_l1_enum, RT5645_MONO_ADC_MIXER, + RT5645_MONO_ADC_L1_SRC_SFT, rt5645_mono_adc_l1_src); + +static const struct snd_kcontrol_new rt5645_mono_adc_l1_mux = + SOC_DAPM_ENUM("Mono ADC1 left source", rt5645_mono_adc_l1_enum); +/* MX-28 [11] */ +static const char * const rt5645_mono_adc_l2_src[] = { + "Mono DAC MIXL", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_mono_adc_l2_enum, RT5645_MONO_ADC_MIXER, + RT5645_MONO_ADC_L2_SRC_SFT, rt5645_mono_adc_l2_src); + +static const struct snd_kcontrol_new rt5645_mono_adc_l2_mux = + SOC_DAPM_ENUM("Mono ADC2 left source", rt5645_mono_adc_l2_enum); + +/* MX-28 [8] */ +static const char * const rt5645_mono_dmic_src[] = { + "DMIC1", "DMIC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_mono_dmic_l_enum, RT5645_MONO_ADC_MIXER, + RT5645_MONO_DMIC_L_SRC_SFT, rt5645_mono_dmic_src); + +static const struct snd_kcontrol_new rt5645_mono_dmic_l_mux = + SOC_DAPM_ENUM("Mono DMIC left source", rt5645_mono_dmic_l_enum); +/* MX-28 [1:0] */ +static SOC_ENUM_SINGLE_DECL( + rt5645_mono_dmic_r_enum, RT5645_MONO_ADC_MIXER, + RT5645_MONO_DMIC_R_SRC_SFT, rt5645_mono_dmic_src); + +static const struct snd_kcontrol_new rt5645_mono_dmic_r_mux = + SOC_DAPM_ENUM("Mono DMIC Right source", rt5645_mono_dmic_r_enum); +/* MX-28 [4] */ +static const char * const rt5645_mono_adc_r1_src[] = { + "Mono DAC MIXR", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_mono_adc_r1_enum, RT5645_MONO_ADC_MIXER, + RT5645_MONO_ADC_R1_SRC_SFT, rt5645_mono_adc_r1_src); + +static const struct snd_kcontrol_new rt5645_mono_adc_r1_mux = + SOC_DAPM_ENUM("Mono ADC1 right source", rt5645_mono_adc_r1_enum); +/* MX-28 [3] */ +static const char * const rt5645_mono_adc_r2_src[] = { + "Mono DAC MIXR", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_mono_adc_r2_enum, RT5645_MONO_ADC_MIXER, + RT5645_MONO_ADC_R2_SRC_SFT, rt5645_mono_adc_r2_src); + +static const struct snd_kcontrol_new rt5645_mono_adc_r2_mux = + SOC_DAPM_ENUM("Mono ADC2 right source", rt5645_mono_adc_r2_enum); + +/* MX-77 [9:8] */ +static const char * const rt5645_if1_adc_in_src[] = { + "IF_ADC1/IF_ADC2/VAD_ADC", "IF_ADC2/IF_ADC1/VAD_ADC", + "VAD_ADC/IF_ADC1/IF_ADC2", "VAD_ADC/IF_ADC2/IF_ADC1" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_if1_adc_in_enum, RT5645_TDM_CTRL_1, + RT5645_IF1_ADC_IN_SFT, rt5645_if1_adc_in_src); + +static const struct snd_kcontrol_new rt5645_if1_adc_in_mux = + SOC_DAPM_ENUM("IF1 ADC IN source", rt5645_if1_adc_in_enum); + +/* MX-78 [4:0] */ +static const char * const rt5650_if1_adc_in_src[] = { + "IF_ADC1/IF_ADC2/DAC_REF/Null", + "IF_ADC1/IF_ADC2/Null/DAC_REF", + "IF_ADC1/DAC_REF/IF_ADC2/Null", + "IF_ADC1/DAC_REF/Null/IF_ADC2", + "IF_ADC1/Null/DAC_REF/IF_ADC2", + "IF_ADC1/Null/IF_ADC2/DAC_REF", + + "IF_ADC2/IF_ADC1/DAC_REF/Null", + "IF_ADC2/IF_ADC1/Null/DAC_REF", + "IF_ADC2/DAC_REF/IF_ADC1/Null", + "IF_ADC2/DAC_REF/Null/IF_ADC1", + "IF_ADC2/Null/DAC_REF/IF_ADC1", + "IF_ADC2/Null/IF_ADC1/DAC_REF", + + "DAC_REF/IF_ADC1/IF_ADC2/Null", + "DAC_REF/IF_ADC1/Null/IF_ADC2", + "DAC_REF/IF_ADC2/IF_ADC1/Null", + "DAC_REF/IF_ADC2/Null/IF_ADC1", + "DAC_REF/Null/IF_ADC1/IF_ADC2", + "DAC_REF/Null/IF_ADC2/IF_ADC1", + + "Null/IF_ADC1/IF_ADC2/DAC_REF", + "Null/IF_ADC1/DAC_REF/IF_ADC2", + "Null/IF_ADC2/IF_ADC1/DAC_REF", + "Null/IF_ADC2/DAC_REF/IF_ADC1", + "Null/DAC_REF/IF_ADC1/IF_ADC2", + "Null/DAC_REF/IF_ADC2/IF_ADC1", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5650_if1_adc_in_enum, RT5645_TDM_CTRL_2, + 0, rt5650_if1_adc_in_src); + +static const struct snd_kcontrol_new rt5650_if1_adc_in_mux = + SOC_DAPM_ENUM("IF1 ADC IN source", rt5650_if1_adc_in_enum); + +/* MX-78 [15:14][13:12][11:10] */ +static const char * const rt5645_tdm_adc_swap_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5650_tdm_adc_slot0_1_enum, + RT5645_TDM_CTRL_2, 14, rt5645_tdm_adc_swap_select); + +static const struct snd_kcontrol_new rt5650_if1_adc1_in_mux = + SOC_DAPM_ENUM("IF1 ADC1 IN source", rt5650_tdm_adc_slot0_1_enum); + +static SOC_ENUM_SINGLE_DECL(rt5650_tdm_adc_slot2_3_enum, + RT5645_TDM_CTRL_2, 12, rt5645_tdm_adc_swap_select); + +static const struct snd_kcontrol_new rt5650_if1_adc2_in_mux = + SOC_DAPM_ENUM("IF1 ADC2 IN source", rt5650_tdm_adc_slot2_3_enum); + +static SOC_ENUM_SINGLE_DECL(rt5650_tdm_adc_slot4_5_enum, + RT5645_TDM_CTRL_2, 10, rt5645_tdm_adc_swap_select); + +static const struct snd_kcontrol_new rt5650_if1_adc3_in_mux = + SOC_DAPM_ENUM("IF1 ADC3 IN source", rt5650_tdm_adc_slot4_5_enum); + +/* MX-77 [7:6][5:4][3:2] */ +static SOC_ENUM_SINGLE_DECL(rt5645_tdm_adc_slot0_1_enum, + RT5645_TDM_CTRL_1, 6, rt5645_tdm_adc_swap_select); + +static const struct snd_kcontrol_new rt5645_if1_adc1_in_mux = + SOC_DAPM_ENUM("IF1 ADC1 IN source", rt5645_tdm_adc_slot0_1_enum); + +static SOC_ENUM_SINGLE_DECL(rt5645_tdm_adc_slot2_3_enum, + RT5645_TDM_CTRL_1, 4, rt5645_tdm_adc_swap_select); + +static const struct snd_kcontrol_new rt5645_if1_adc2_in_mux = + SOC_DAPM_ENUM("IF1 ADC2 IN source", rt5645_tdm_adc_slot2_3_enum); + +static SOC_ENUM_SINGLE_DECL(rt5645_tdm_adc_slot4_5_enum, + RT5645_TDM_CTRL_1, 2, rt5645_tdm_adc_swap_select); + +static const struct snd_kcontrol_new rt5645_if1_adc3_in_mux = + SOC_DAPM_ENUM("IF1 ADC3 IN source", rt5645_tdm_adc_slot4_5_enum); + +/* MX-79 [14:12][10:8][6:4][2:0] */ +static const char * const rt5645_tdm_dac_swap_select[] = { + "Slot0", "Slot1", "Slot2", "Slot3" +}; + +static SOC_ENUM_SINGLE_DECL(rt5645_tdm_dac0_enum, + RT5645_TDM_CTRL_3, 12, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5645_if1_dac0_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC0 source", rt5645_tdm_dac0_enum); + +static SOC_ENUM_SINGLE_DECL(rt5645_tdm_dac1_enum, + RT5645_TDM_CTRL_3, 8, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5645_if1_dac1_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC1 source", rt5645_tdm_dac1_enum); + +static SOC_ENUM_SINGLE_DECL(rt5645_tdm_dac2_enum, + RT5645_TDM_CTRL_3, 4, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5645_if1_dac2_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC2 source", rt5645_tdm_dac2_enum); + +static SOC_ENUM_SINGLE_DECL(rt5645_tdm_dac3_enum, + RT5645_TDM_CTRL_3, 0, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5645_if1_dac3_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC3 source", rt5645_tdm_dac3_enum); + +/* MX-7a [14:12][10:8][6:4][2:0] */ +static SOC_ENUM_SINGLE_DECL(rt5650_tdm_dac0_enum, + RT5650_TDM_CTRL_4, 12, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5650_if1_dac0_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC0 source", rt5650_tdm_dac0_enum); + +static SOC_ENUM_SINGLE_DECL(rt5650_tdm_dac1_enum, + RT5650_TDM_CTRL_4, 8, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5650_if1_dac1_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC1 source", rt5650_tdm_dac1_enum); + +static SOC_ENUM_SINGLE_DECL(rt5650_tdm_dac2_enum, + RT5650_TDM_CTRL_4, 4, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5650_if1_dac2_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC2 source", rt5650_tdm_dac2_enum); + +static SOC_ENUM_SINGLE_DECL(rt5650_tdm_dac3_enum, + RT5650_TDM_CTRL_4, 0, rt5645_tdm_dac_swap_select); + +static const struct snd_kcontrol_new rt5650_if1_dac3_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC3 source", rt5650_tdm_dac3_enum); + +/* MX-2d [3] [2] */ +static const char * const rt5650_a_dac1_src[] = { + "DAC1", "Stereo DAC Mixer" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5650_a_dac1_l_enum, RT5650_A_DAC_SOUR, + RT5650_A_DAC1_L_IN_SFT, rt5650_a_dac1_src); + +static const struct snd_kcontrol_new rt5650_a_dac1_l_mux = + SOC_DAPM_ENUM("A DAC1 L source", rt5650_a_dac1_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5650_a_dac1_r_enum, RT5650_A_DAC_SOUR, + RT5650_A_DAC1_R_IN_SFT, rt5650_a_dac1_src); + +static const struct snd_kcontrol_new rt5650_a_dac1_r_mux = + SOC_DAPM_ENUM("A DAC1 R source", rt5650_a_dac1_r_enum); + +/* MX-2d [1] [0] */ +static const char * const rt5650_a_dac2_src[] = { + "Stereo DAC Mixer", "Mono DAC Mixer" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5650_a_dac2_l_enum, RT5650_A_DAC_SOUR, + RT5650_A_DAC2_L_IN_SFT, rt5650_a_dac2_src); + +static const struct snd_kcontrol_new rt5650_a_dac2_l_mux = + SOC_DAPM_ENUM("A DAC2 L source", rt5650_a_dac2_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5650_a_dac2_r_enum, RT5650_A_DAC_SOUR, + RT5650_A_DAC2_R_IN_SFT, rt5650_a_dac2_src); + +static const struct snd_kcontrol_new rt5650_a_dac2_r_mux = + SOC_DAPM_ENUM("A DAC2 R source", rt5650_a_dac2_r_enum); + +/* MX-2F [13:12] */ +static const char * const rt5645_if2_adc_in_src[] = { + "IF_ADC1", "IF_ADC2", "VAD_ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_if2_adc_in_enum, RT5645_DIG_INF1_DATA, + RT5645_IF2_ADC_IN_SFT, rt5645_if2_adc_in_src); + +static const struct snd_kcontrol_new rt5645_if2_adc_in_mux = + SOC_DAPM_ENUM("IF2 ADC IN source", rt5645_if2_adc_in_enum); + +/* MX-2F [1:0] */ +static const char * const rt5645_if3_adc_in_src[] = { + "IF_ADC1", "IF_ADC2", "VAD_ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_if3_adc_in_enum, RT5645_DIG_INF1_DATA, + RT5645_IF3_ADC_IN_SFT, rt5645_if3_adc_in_src); + +static const struct snd_kcontrol_new rt5645_if3_adc_in_mux = + SOC_DAPM_ENUM("IF3 ADC IN source", rt5645_if3_adc_in_enum); + +/* MX-31 [15] [13] [11] [9] */ +static const char * const rt5645_pdm_src[] = { + "Mono DAC", "Stereo DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_pdm1_l_enum, RT5645_PDM_OUT_CTRL, + RT5645_PDM1_L_SFT, rt5645_pdm_src); + +static const struct snd_kcontrol_new rt5645_pdm1_l_mux = + SOC_DAPM_ENUM("PDM1 L source", rt5645_pdm1_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5645_pdm1_r_enum, RT5645_PDM_OUT_CTRL, + RT5645_PDM1_R_SFT, rt5645_pdm_src); + +static const struct snd_kcontrol_new rt5645_pdm1_r_mux = + SOC_DAPM_ENUM("PDM1 R source", rt5645_pdm1_r_enum); + +/* MX-9D [9:8] */ +static const char * const rt5645_vad_adc_src[] = { + "Sto1 ADC L", "Mono ADC L", "Mono ADC R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_vad_adc_enum, RT5645_VAD_CTRL4, + RT5645_VAD_SEL_SFT, rt5645_vad_adc_src); + +static const struct snd_kcontrol_new rt5645_vad_adc_mux = + SOC_DAPM_ENUM("VAD ADC source", rt5645_vad_adc_enum); + +static const struct snd_kcontrol_new spk_l_vol_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5645_SPK_VOL, + RT5645_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new spk_r_vol_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5645_SPK_VOL, + RT5645_R_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hp_l_vol_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5645_HP_VOL, + RT5645_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hp_r_vol_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5645_HP_VOL, + RT5645_R_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new pdm1_l_vol_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5645_PDM_OUT_CTRL, + RT5645_M_PDM1_L, 1, 1); + +static const struct snd_kcontrol_new pdm1_r_vol_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5645_PDM_OUT_CTRL, + RT5645_M_PDM1_R, 1, 1); + +static void hp_amp_power(struct snd_soc_component *component, int on) +{ + static int hp_amp_power_count; + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + if (on) { + if (hp_amp_power_count <= 0) { + if (rt5645->codec_type == CODEC_TYPE_RT5650) { + snd_soc_component_write(component, RT5645_DEPOP_M2, 0x3100); + snd_soc_component_write(component, RT5645_CHARGE_PUMP, + 0x0e06); + snd_soc_component_write(component, RT5645_DEPOP_M1, 0x000d); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + RT5645_HP_DCC_INT1, 0x9f01); + msleep(20); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_HP_CO_MASK, RT5645_HP_CO_EN); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + 0x3e, 0x7400); + snd_soc_component_write(component, RT5645_DEPOP_M3, 0x0737); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + RT5645_MAMP_INT_REG2, 0xfc00); + snd_soc_component_write(component, RT5645_DEPOP_M2, 0x1140); + msleep(90); + rt5645->hp_on = true; + } else { + /* depop parameters */ + snd_soc_component_update_bits(component, RT5645_DEPOP_M2, + RT5645_DEPOP_MASK, RT5645_DEPOP_MAN); + snd_soc_component_write(component, RT5645_DEPOP_M1, 0x000d); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + RT5645_HP_DCC_INT1, 0x9f01); + mdelay(150); + /* headphone amp power on */ + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_FV1 | RT5645_PWR_FV2, 0); + snd_soc_component_update_bits(component, RT5645_PWR_VOL, + RT5645_PWR_HV_L | RT5645_PWR_HV_R, + RT5645_PWR_HV_L | RT5645_PWR_HV_R); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_HP_L | RT5645_PWR_HP_R | + RT5645_PWR_HA, + RT5645_PWR_HP_L | RT5645_PWR_HP_R | + RT5645_PWR_HA); + mdelay(5); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_FV1 | RT5645_PWR_FV2, + RT5645_PWR_FV1 | RT5645_PWR_FV2); + + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_HP_CO_MASK | RT5645_HP_SG_MASK, + RT5645_HP_CO_EN | RT5645_HP_SG_EN); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + 0x14, 0x1aaa); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + 0x24, 0x0430); + } + } + hp_amp_power_count++; + } else { + hp_amp_power_count--; + if (hp_amp_power_count <= 0) { + if (rt5645->codec_type == CODEC_TYPE_RT5650) { + regmap_write(rt5645->regmap, RT5645_PR_BASE + + 0x3e, 0x7400); + snd_soc_component_write(component, RT5645_DEPOP_M3, 0x0737); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + RT5645_MAMP_INT_REG2, 0xfc00); + snd_soc_component_write(component, RT5645_DEPOP_M2, 0x1140); + msleep(100); + snd_soc_component_write(component, RT5645_DEPOP_M1, 0x0001); + + } else { + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_HP_SG_MASK | + RT5645_HP_L_SMT_MASK | + RT5645_HP_R_SMT_MASK, + RT5645_HP_SG_DIS | + RT5645_HP_L_SMT_DIS | + RT5645_HP_R_SMT_DIS); + /* headphone amp power down */ + snd_soc_component_write(component, RT5645_DEPOP_M1, 0x0000); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_HP_L | RT5645_PWR_HP_R | + RT5645_PWR_HA, 0); + snd_soc_component_update_bits(component, RT5645_DEPOP_M2, + RT5645_DEPOP_MASK, 0); + } + } + } +} + +static int rt5645_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + hp_amp_power(component, 1); + /* headphone unmute sequence */ + if (rt5645->codec_type == CODEC_TYPE_RT5645) { + snd_soc_component_update_bits(component, RT5645_DEPOP_M3, + RT5645_CP_FQ1_MASK | RT5645_CP_FQ2_MASK | + RT5645_CP_FQ3_MASK, + (RT5645_CP_FQ_192_KHZ << RT5645_CP_FQ1_SFT) | + (RT5645_CP_FQ_12_KHZ << RT5645_CP_FQ2_SFT) | + (RT5645_CP_FQ_192_KHZ << RT5645_CP_FQ3_SFT)); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + RT5645_MAMP_INT_REG2, 0xfc00); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_SMT_TRIG_MASK, RT5645_SMT_TRIG_EN); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_RSTN_MASK, RT5645_RSTN_EN); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_RSTN_MASK | RT5645_HP_L_SMT_MASK | + RT5645_HP_R_SMT_MASK, RT5645_RSTN_DIS | + RT5645_HP_L_SMT_EN | RT5645_HP_R_SMT_EN); + msleep(40); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_HP_SG_MASK | RT5645_HP_L_SMT_MASK | + RT5645_HP_R_SMT_MASK, RT5645_HP_SG_DIS | + RT5645_HP_L_SMT_DIS | RT5645_HP_R_SMT_DIS); + } + break; + + case SND_SOC_DAPM_PRE_PMD: + /* headphone mute sequence */ + if (rt5645->codec_type == CODEC_TYPE_RT5645) { + snd_soc_component_update_bits(component, RT5645_DEPOP_M3, + RT5645_CP_FQ1_MASK | RT5645_CP_FQ2_MASK | + RT5645_CP_FQ3_MASK, + (RT5645_CP_FQ_96_KHZ << RT5645_CP_FQ1_SFT) | + (RT5645_CP_FQ_12_KHZ << RT5645_CP_FQ2_SFT) | + (RT5645_CP_FQ_96_KHZ << RT5645_CP_FQ3_SFT)); + regmap_write(rt5645->regmap, RT5645_PR_BASE + + RT5645_MAMP_INT_REG2, 0xfc00); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_HP_SG_MASK, RT5645_HP_SG_EN); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_RSTP_MASK, RT5645_RSTP_EN); + snd_soc_component_update_bits(component, RT5645_DEPOP_M1, + RT5645_RSTP_MASK | RT5645_HP_L_SMT_MASK | + RT5645_HP_R_SMT_MASK, RT5645_RSTP_DIS | + RT5645_HP_L_SMT_EN | RT5645_HP_R_SMT_EN); + msleep(30); + } + hp_amp_power(component, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5645_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt5645_enable_hweq(component); + snd_soc_component_update_bits(component, RT5645_PWR_DIG1, + RT5645_PWR_CLS_D | RT5645_PWR_CLS_D_R | + RT5645_PWR_CLS_D_L, + RT5645_PWR_CLS_D | RT5645_PWR_CLS_D_R | + RT5645_PWR_CLS_D_L); + snd_soc_component_update_bits(component, RT5645_GEN_CTRL3, + RT5645_DET_CLK_MASK, RT5645_DET_CLK_MODE1); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5645_GEN_CTRL3, + RT5645_DET_CLK_MASK, RT5645_DET_CLK_DIS); + snd_soc_component_write(component, RT5645_EQ_CTRL2, 0); + snd_soc_component_update_bits(component, RT5645_PWR_DIG1, + RT5645_PWR_CLS_D | RT5645_PWR_CLS_D_R | + RT5645_PWR_CLS_D_L, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5645_lout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + hp_amp_power(component, 1); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_LM, RT5645_PWR_LM); + snd_soc_component_update_bits(component, RT5645_LOUT1, + RT5645_L_MUTE | RT5645_R_MUTE, 0); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5645_LOUT1, + RT5645_L_MUTE | RT5645_R_MUTE, + RT5645_L_MUTE | RT5645_R_MUTE); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_LM, 0); + hp_amp_power(component, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5645_bst2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5645_PWR_ANLG2, + RT5645_PWR_BST2_P, RT5645_PWR_BST2_P); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5645_PWR_ANLG2, + RT5645_PWR_BST2_P, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5650_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (rt5645->hp_on) { + msleep(100); + rt5645->hp_on = false; + } + break; + + default: + return 0; + } + + return 0; +} + +static int rt5645_set_micbias1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, RT5645_GEN_CTRL2, + RT5645_MICBIAS1_POW_CTRL_SEL_MASK, + RT5645_MICBIAS1_POW_CTRL_SEL_M); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, RT5645_GEN_CTRL2, + RT5645_MICBIAS1_POW_CTRL_SEL_MASK, + RT5645_MICBIAS1_POW_CTRL_SEL_A); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5645_set_micbias2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, RT5645_GEN_CTRL2, + RT5645_MICBIAS2_POW_CTRL_SEL_MASK, + RT5645_MICBIAS2_POW_CTRL_SEL_M); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, RT5645_GEN_CTRL2, + RT5645_MICBIAS2_POW_CTRL_SEL_MASK, + RT5645_MICBIAS2_POW_CTRL_SEL_A); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5645_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO2", RT5645_PWR_MIXER, + RT5645_PWR_LDO2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", RT5645_PWR_ANLG2, + RT5645_PWR_PLL_BIT, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("JD Power", RT5645_PWR_ANLG2, + RT5645_PWR_JD1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Det Power", RT5645_PWR_VOL, + RT5645_PWR_MIC_DET_BIT, 0, NULL, 0), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("I2S1 ASRC", 1, RT5645_ASRC_1, + 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5645_ASRC_1, + 12, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC STO ASRC", 1, RT5645_ASRC_1, + 10, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO L ASRC", 1, RT5645_ASRC_1, + 9, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO R ASRC", 1, RT5645_ASRC_1, + 8, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO1 ASRC", 1, RT5645_ASRC_1, + 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO L ASRC", 1, RT5645_ASRC_1, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO R ASRC", 1, RT5645_ASRC_1, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5645_ASRC_1, + 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO L ASRC", 1, RT5645_ASRC_1, + 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO R ASRC", 1, RT5645_ASRC_1, + 0, 0, NULL, 0), + + /* Input Side */ + /* micbias */ + SND_SOC_DAPM_SUPPLY("micbias1", RT5645_PWR_ANLG2, + RT5645_PWR_MB1_BIT, 0, rt5645_set_micbias1_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("micbias2", RT5645_PWR_ANLG2, + RT5645_PWR_MB2_BIT, 0, rt5645_set_micbias2_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + SND_SOC_DAPM_INPUT("DMIC L2"), + SND_SOC_DAPM_INPUT("DMIC R2"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + + SND_SOC_DAPM_INPUT("Haptic Generator"), + + SND_SOC_DAPM_PGA("DMIC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5645_DMIC_CTRL1, + RT5645_DMIC_1_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC2 Power", RT5645_DMIC_CTRL1, + RT5645_DMIC_2_EN_SFT, 0, NULL, 0), + /* Boost */ + SND_SOC_DAPM_PGA("BST1", RT5645_PWR_ANLG2, + RT5645_PWR_BST1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA_E("BST2", RT5645_PWR_ANLG2, + RT5645_PWR_BST2_BIT, 0, NULL, 0, rt5645_bst2_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + /* Input Volume */ + SND_SOC_DAPM_PGA("INL VOL", RT5645_PWR_VOL, + RT5645_PWR_IN_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR VOL", RT5645_PWR_VOL, + RT5645_PWR_IN_R_BIT, 0, NULL, 0), + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIXL", RT5645_PWR_MIXER, RT5645_PWR_RM_L_BIT, + 0, rt5645_rec_l_mix, ARRAY_SIZE(rt5645_rec_l_mix)), + SND_SOC_DAPM_MIXER("RECMIXR", RT5645_PWR_MIXER, RT5645_PWR_RM_R_BIT, + 0, rt5645_rec_r_mix, ARRAY_SIZE(rt5645_rec_r_mix)), + /* ADCs */ + SND_SOC_DAPM_ADC("ADC L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC R", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("ADC L power", RT5645_PWR_DIG1, + RT5645_PWR_ADC_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC R power", RT5645_PWR_DIG1, + RT5645_PWR_ADC_R_BIT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5645_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_sto_adc2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_sto_adc2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_sto_adc1_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_sto_adc1_mux), + SND_SOC_DAPM_MUX("Mono DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5645_mono_dmic_l_mux), + SND_SOC_DAPM_MUX("Mono DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5645_mono_dmic_r_mux), + SND_SOC_DAPM_MUX("Mono ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_mono_adc_l2_mux), + SND_SOC_DAPM_MUX("Mono ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_mono_adc_l1_mux), + SND_SOC_DAPM_MUX("Mono ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_mono_adc_r1_mux), + SND_SOC_DAPM_MUX("Mono ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5645_mono_adc_r2_mux), + /* ADC Mixer */ + + SND_SOC_DAPM_SUPPLY_S("adc stereo1 filter", 1, RT5645_PWR_DIG2, + RT5645_PWR_ADC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER_E("Sto1 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5645_sto1_adc_l_mix, ARRAY_SIZE(rt5645_sto1_adc_l_mix), + NULL, 0), + SND_SOC_DAPM_MIXER_E("Sto1 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5645_sto1_adc_r_mix, ARRAY_SIZE(rt5645_sto1_adc_r_mix), + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("adc mono left filter", 1, RT5645_PWR_DIG2, + RT5645_PWR_ADC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER_E("Mono ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5645_mono_adc_l_mix, ARRAY_SIZE(rt5645_mono_adc_l_mix), + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("adc mono right filter", 1, RT5645_PWR_DIG2, + RT5645_PWR_ADC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER_E("Mono ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5645_mono_adc_r_mix, ARRAY_SIZE(rt5645_mono_adc_r_mix), + NULL, 0), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIXL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo1 ADC MIXR", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Sto2 ADC LR MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("VAD_ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF_ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF_ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC4", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* IF1 2 Mux */ + SND_SOC_DAPM_MUX("IF2 ADC Mux", SND_SOC_NOPM, + 0, 0, &rt5645_if2_adc_in_mux), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5645_PWR_DIG1, + RT5645_PWR_I2S1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2", RT5645_PWR_DIG1, + RT5645_PWR_I2S2_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("VAD ADC Mux", SND_SOC_NOPM, + 0, 0, &rt5645_vad_adc_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5645_dac_l_mix, ARRAY_SIZE(rt5645_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5645_dac_r_mix, ARRAY_SIZE(rt5645_dac_r_mix)), + + /* DAC2 channel Mux */ + SND_SOC_DAPM_MUX("DAC L2 Mux", SND_SOC_NOPM, 0, 0, &rt5645_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Mux", SND_SOC_NOPM, 0, 0, &rt5645_dac_r2_mux), + SND_SOC_DAPM_PGA("DAC L2 Volume", RT5645_PWR_DIG1, + RT5645_PWR_DAC_L2_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC R2 Volume", RT5645_PWR_DIG1, + RT5645_PWR_DAC_R2_BIT, 0, NULL, 0), + + SND_SOC_DAPM_MUX("DAC1 L Mux", SND_SOC_NOPM, 0, 0, &rt5645_dac1l_mux), + SND_SOC_DAPM_MUX("DAC1 R Mux", SND_SOC_NOPM, 0, 0, &rt5645_dac1r_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY_S("dac stereo1 filter", 1, RT5645_PWR_DIG2, + RT5645_PWR_DAC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("dac mono left filter", 1, RT5645_PWR_DIG2, + RT5645_PWR_DAC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("dac mono right filter", 1, RT5645_PWR_DIG2, + RT5645_PWR_DAC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5645_sto_dac_l_mix, ARRAY_SIZE(rt5645_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5645_sto_dac_r_mix, ARRAY_SIZE(rt5645_sto_dac_r_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5645_mono_dac_l_mix, ARRAY_SIZE(rt5645_mono_dac_l_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5645_mono_dac_r_mix, ARRAY_SIZE(rt5645_mono_dac_r_mix)), + SND_SOC_DAPM_MIXER("DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5645_dig_l_mix, ARRAY_SIZE(rt5645_dig_l_mix)), + SND_SOC_DAPM_MIXER("DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5645_dig_r_mix, ARRAY_SIZE(rt5645_dig_r_mix)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, RT5645_PWR_DIG1, RT5645_PWR_DAC_L1_BIT, + 0), + SND_SOC_DAPM_DAC("DAC L2", NULL, RT5645_PWR_DIG1, RT5645_PWR_DAC_L2_BIT, + 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, RT5645_PWR_DIG1, RT5645_PWR_DAC_R1_BIT, + 0), + SND_SOC_DAPM_DAC("DAC R2", NULL, RT5645_PWR_DIG1, RT5645_PWR_DAC_R2_BIT, + 0), + /* OUT Mixer */ + SND_SOC_DAPM_MIXER("SPK MIXL", RT5645_PWR_MIXER, RT5645_PWR_SM_L_BIT, + 0, rt5645_spk_l_mix, ARRAY_SIZE(rt5645_spk_l_mix)), + SND_SOC_DAPM_MIXER("SPK MIXR", RT5645_PWR_MIXER, RT5645_PWR_SM_R_BIT, + 0, rt5645_spk_r_mix, ARRAY_SIZE(rt5645_spk_r_mix)), + SND_SOC_DAPM_MIXER("OUT MIXL", RT5645_PWR_MIXER, RT5645_PWR_OM_L_BIT, + 0, rt5645_out_l_mix, ARRAY_SIZE(rt5645_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5645_PWR_MIXER, RT5645_PWR_OM_R_BIT, + 0, rt5645_out_r_mix, ARRAY_SIZE(rt5645_out_r_mix)), + /* Ouput Volume */ + SND_SOC_DAPM_SWITCH("SPKVOL L", RT5645_PWR_VOL, RT5645_PWR_SV_L_BIT, 0, + &spk_l_vol_control), + SND_SOC_DAPM_SWITCH("SPKVOL R", RT5645_PWR_VOL, RT5645_PWR_SV_R_BIT, 0, + &spk_r_vol_control), + SND_SOC_DAPM_MIXER("HPOVOL MIXL", RT5645_PWR_VOL, RT5645_PWR_HV_L_BIT, + 0, rt5645_hpvoll_mix, ARRAY_SIZE(rt5645_hpvoll_mix)), + SND_SOC_DAPM_MIXER("HPOVOL MIXR", RT5645_PWR_VOL, RT5645_PWR_HV_R_BIT, + 0, rt5645_hpvolr_mix, ARRAY_SIZE(rt5645_hpvolr_mix)), + SND_SOC_DAPM_SUPPLY("HPOVOL MIXL Power", RT5645_PWR_MIXER, + RT5645_PWR_HM_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HPOVOL MIXR Power", RT5645_PWR_MIXER, + RT5645_PWR_HM_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC 1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC 2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPOVOL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("HPOVOL L", SND_SOC_NOPM, 0, 0, &hp_l_vol_control), + SND_SOC_DAPM_SWITCH("HPOVOL R", SND_SOC_NOPM, 0, 0, &hp_r_vol_control), + + /* HPO/LOUT/Mono Mixer */ + SND_SOC_DAPM_MIXER("SPOL MIX", SND_SOC_NOPM, 0, 0, rt5645_spo_l_mix, + ARRAY_SIZE(rt5645_spo_l_mix)), + SND_SOC_DAPM_MIXER("SPOR MIX", SND_SOC_NOPM, 0, 0, rt5645_spo_r_mix, + ARRAY_SIZE(rt5645_spo_r_mix)), + SND_SOC_DAPM_MIXER("HPO MIX", SND_SOC_NOPM, 0, 0, rt5645_hpo_mix, + ARRAY_SIZE(rt5645_hpo_mix)), + SND_SOC_DAPM_MIXER("LOUT MIX", SND_SOC_NOPM, 0, 0, rt5645_lout_mix, + ARRAY_SIZE(rt5645_lout_mix)), + + SND_SOC_DAPM_PGA_S("HP amp", 1, SND_SOC_NOPM, 0, 0, rt5645_hp_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT amp", 1, SND_SOC_NOPM, 0, 0, rt5645_lout_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("SPK amp", 2, SND_SOC_NOPM, 0, 0, rt5645_spk_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + + /* PDM */ + SND_SOC_DAPM_SUPPLY("PDM1 Power", RT5645_PWR_DIG2, RT5645_PWR_PDM1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_MUX("PDM1 L Mux", SND_SOC_NOPM, 0, 0, &rt5645_pdm1_l_mux), + SND_SOC_DAPM_MUX("PDM1 R Mux", SND_SOC_NOPM, 0, 0, &rt5645_pdm1_r_mux), + + SND_SOC_DAPM_SWITCH("PDM1 L", SND_SOC_NOPM, 0, 0, &pdm1_l_vol_control), + SND_SOC_DAPM_SWITCH("PDM1 R", SND_SOC_NOPM, 0, 0, &pdm1_r_vol_control), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), + SND_SOC_DAPM_OUTPUT("PDM1L"), + SND_SOC_DAPM_OUTPUT("PDM1R"), + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + SND_SOC_DAPM_POST("DAPM_POST", rt5650_hp_event), +}; + +static const struct snd_soc_dapm_widget rt5645_specific_dapm_widgets[] = { + SND_SOC_DAPM_MUX("RT5645 IF1 DAC1 L Mux", SND_SOC_NOPM, 0, 0, + &rt5645_if1_dac0_tdm_sel_mux), + SND_SOC_DAPM_MUX("RT5645 IF1 DAC1 R Mux", SND_SOC_NOPM, 0, 0, + &rt5645_if1_dac1_tdm_sel_mux), + SND_SOC_DAPM_MUX("RT5645 IF1 DAC2 L Mux", SND_SOC_NOPM, 0, 0, + &rt5645_if1_dac2_tdm_sel_mux), + SND_SOC_DAPM_MUX("RT5645 IF1 DAC2 R Mux", SND_SOC_NOPM, 0, 0, + &rt5645_if1_dac3_tdm_sel_mux), + SND_SOC_DAPM_MUX("RT5645 IF1 ADC Mux", SND_SOC_NOPM, + 0, 0, &rt5645_if1_adc_in_mux), + SND_SOC_DAPM_MUX("RT5645 IF1 ADC1 Swap Mux", SND_SOC_NOPM, + 0, 0, &rt5645_if1_adc1_in_mux), + SND_SOC_DAPM_MUX("RT5645 IF1 ADC2 Swap Mux", SND_SOC_NOPM, + 0, 0, &rt5645_if1_adc2_in_mux), + SND_SOC_DAPM_MUX("RT5645 IF1 ADC3 Swap Mux", SND_SOC_NOPM, + 0, 0, &rt5645_if1_adc3_in_mux), +}; + +static const struct snd_soc_dapm_widget rt5650_specific_dapm_widgets[] = { + SND_SOC_DAPM_MUX("A DAC1 L Mux", SND_SOC_NOPM, + 0, 0, &rt5650_a_dac1_l_mux), + SND_SOC_DAPM_MUX("A DAC1 R Mux", SND_SOC_NOPM, + 0, 0, &rt5650_a_dac1_r_mux), + SND_SOC_DAPM_MUX("A DAC2 L Mux", SND_SOC_NOPM, + 0, 0, &rt5650_a_dac2_l_mux), + SND_SOC_DAPM_MUX("A DAC2 R Mux", SND_SOC_NOPM, + 0, 0, &rt5650_a_dac2_r_mux), + + SND_SOC_DAPM_MUX("RT5650 IF1 ADC1 Swap Mux", SND_SOC_NOPM, + 0, 0, &rt5650_if1_adc1_in_mux), + SND_SOC_DAPM_MUX("RT5650 IF1 ADC2 Swap Mux", SND_SOC_NOPM, + 0, 0, &rt5650_if1_adc2_in_mux), + SND_SOC_DAPM_MUX("RT5650 IF1 ADC3 Swap Mux", SND_SOC_NOPM, + 0, 0, &rt5650_if1_adc3_in_mux), + SND_SOC_DAPM_MUX("RT5650 IF1 ADC Mux", SND_SOC_NOPM, + 0, 0, &rt5650_if1_adc_in_mux), + + SND_SOC_DAPM_MUX("RT5650 IF1 DAC1 L Mux", SND_SOC_NOPM, 0, 0, + &rt5650_if1_dac0_tdm_sel_mux), + SND_SOC_DAPM_MUX("RT5650 IF1 DAC1 R Mux", SND_SOC_NOPM, 0, 0, + &rt5650_if1_dac1_tdm_sel_mux), + SND_SOC_DAPM_MUX("RT5650 IF1 DAC2 L Mux", SND_SOC_NOPM, 0, 0, + &rt5650_if1_dac2_tdm_sel_mux), + SND_SOC_DAPM_MUX("RT5650 IF1 DAC2 R Mux", SND_SOC_NOPM, 0, 0, + &rt5650_if1_dac3_tdm_sel_mux), +}; + +static const struct snd_soc_dapm_route rt5645_dapm_routes[] = { + { "adc stereo1 filter", NULL, "ADC STO1 ASRC", is_using_asrc }, + { "adc mono left filter", NULL, "ADC MONO L ASRC", is_using_asrc }, + { "adc mono right filter", NULL, "ADC MONO R ASRC", is_using_asrc }, + { "dac mono left filter", NULL, "DAC MONO L ASRC", is_using_asrc }, + { "dac mono right filter", NULL, "DAC MONO R ASRC", is_using_asrc }, + { "dac stereo1 filter", NULL, "DAC STO ASRC", is_using_asrc }, + + { "I2S1", NULL, "I2S1 ASRC" }, + { "I2S2", NULL, "I2S2 ASRC" }, + + { "IN1P", NULL, "LDO2" }, + { "IN2P", NULL, "LDO2" }, + + { "DMIC1", NULL, "DMIC L1" }, + { "DMIC1", NULL, "DMIC R1" }, + { "DMIC2", NULL, "DMIC L2" }, + { "DMIC2", NULL, "DMIC R2" }, + + { "BST1", NULL, "IN1P" }, + { "BST1", NULL, "IN1N" }, + { "BST1", NULL, "JD Power" }, + { "BST1", NULL, "Mic Det Power" }, + { "BST2", NULL, "IN2P" }, + { "BST2", NULL, "IN2N" }, + + { "INL VOL", NULL, "IN2P" }, + { "INR VOL", NULL, "IN2N" }, + + { "RECMIXL", "HPOL Switch", "HPOL" }, + { "RECMIXL", "INL Switch", "INL VOL" }, + { "RECMIXL", "BST2 Switch", "BST2" }, + { "RECMIXL", "BST1 Switch", "BST1" }, + { "RECMIXL", "OUT MIXL Switch", "OUT MIXL" }, + + { "RECMIXR", "HPOR Switch", "HPOR" }, + { "RECMIXR", "INR Switch", "INR VOL" }, + { "RECMIXR", "BST2 Switch", "BST2" }, + { "RECMIXR", "BST1 Switch", "BST1" }, + { "RECMIXR", "OUT MIXR Switch", "OUT MIXR" }, + + { "ADC L", NULL, "RECMIXL" }, + { "ADC L", NULL, "ADC L power" }, + { "ADC R", NULL, "RECMIXR" }, + { "ADC R", NULL, "ADC R power" }, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC L1", NULL, "DMIC1 Power"}, + {"DMIC R1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC1 Power"}, + {"DMIC L2", NULL, "DMIC CLK"}, + {"DMIC L2", NULL, "DMIC2 Power"}, + {"DMIC R2", NULL, "DMIC CLK"}, + {"DMIC R2", NULL, "DMIC2 Power"}, + + { "Stereo1 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo1 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo1 DMIC Mux", NULL, "DMIC STO1 ASRC" }, + + { "Mono DMIC L Mux", "DMIC1", "DMIC L1" }, + { "Mono DMIC L Mux", "DMIC2", "DMIC L2" }, + { "Mono DMIC L Mux", NULL, "DMIC MONO L ASRC" }, + + { "Mono DMIC R Mux", "DMIC1", "DMIC R1" }, + { "Mono DMIC R Mux", "DMIC2", "DMIC R2" }, + { "Mono DMIC R Mux", NULL, "DMIC MONO R ASRC" }, + + { "Stereo1 ADC L2 Mux", "DMIC", "Stereo1 DMIC Mux" }, + { "Stereo1 ADC L2 Mux", "DAC MIX", "DAC MIXL" }, + { "Stereo1 ADC L1 Mux", "ADC", "ADC L" }, + { "Stereo1 ADC L1 Mux", "DAC MIX", "DAC MIXL" }, + + { "Stereo1 ADC R1 Mux", "ADC", "ADC R" }, + { "Stereo1 ADC R1 Mux", "DAC MIX", "DAC MIXR" }, + { "Stereo1 ADC R2 Mux", "DMIC", "Stereo1 DMIC Mux" }, + { "Stereo1 ADC R2 Mux", "DAC MIX", "DAC MIXR" }, + + { "Mono ADC L2 Mux", "DMIC", "Mono DMIC L Mux" }, + { "Mono ADC L2 Mux", "Mono DAC MIXL", "Mono DAC MIXL" }, + { "Mono ADC L1 Mux", "Mono DAC MIXL", "Mono DAC MIXL" }, + { "Mono ADC L1 Mux", "ADC", "ADC L" }, + + { "Mono ADC R1 Mux", "Mono DAC MIXR", "Mono DAC MIXR" }, + { "Mono ADC R1 Mux", "ADC", "ADC R" }, + { "Mono ADC R2 Mux", "DMIC", "Mono DMIC R Mux" }, + { "Mono ADC R2 Mux", "Mono DAC MIXR", "Mono DAC MIXR" }, + + { "Sto1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux" }, + { "Sto1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux" }, + { "Sto1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux" }, + { "Sto1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux" }, + + { "Stereo1 ADC MIXL", NULL, "Sto1 ADC MIXL" }, + { "Stereo1 ADC MIXL", NULL, "adc stereo1 filter" }, + { "adc stereo1 filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Stereo1 ADC MIXR", NULL, "Sto1 ADC MIXR" }, + { "Stereo1 ADC MIXR", NULL, "adc stereo1 filter" }, + { "adc stereo1 filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Mono ADC MIXL", "ADC1 Switch", "Mono ADC L1 Mux" }, + { "Mono ADC MIXL", "ADC2 Switch", "Mono ADC L2 Mux" }, + { "Mono ADC MIXL", NULL, "adc mono left filter" }, + { "adc mono left filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Mono ADC MIXR", "ADC1 Switch", "Mono ADC R1 Mux" }, + { "Mono ADC MIXR", "ADC2 Switch", "Mono ADC R2 Mux" }, + { "Mono ADC MIXR", NULL, "adc mono right filter" }, + { "adc mono right filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "VAD ADC Mux", "Sto1 ADC L", "Stereo1 ADC MIXL" }, + { "VAD ADC Mux", "Mono ADC L", "Mono ADC MIXL" }, + { "VAD ADC Mux", "Mono ADC R", "Mono ADC MIXR" }, + + { "IF_ADC1", NULL, "Stereo1 ADC MIXL" }, + { "IF_ADC1", NULL, "Stereo1 ADC MIXR" }, + { "IF_ADC2", NULL, "Mono ADC MIXL" }, + { "IF_ADC2", NULL, "Mono ADC MIXR" }, + { "VAD_ADC", NULL, "VAD ADC Mux" }, + + { "IF2 ADC Mux", "IF_ADC1", "IF_ADC1" }, + { "IF2 ADC Mux", "IF_ADC2", "IF_ADC2" }, + { "IF2 ADC Mux", "VAD_ADC", "VAD_ADC" }, + + { "IF1 ADC", NULL, "I2S1" }, + { "IF2 ADC", NULL, "I2S2" }, + { "IF2 ADC", NULL, "IF2 ADC Mux" }, + + { "AIF2TX", NULL, "IF2 ADC" }, + + { "IF1 DAC0", NULL, "AIF1RX" }, + { "IF1 DAC1", NULL, "AIF1RX" }, + { "IF1 DAC2", NULL, "AIF1RX" }, + { "IF1 DAC3", NULL, "AIF1RX" }, + { "IF2 DAC", NULL, "AIF2RX" }, + + { "IF1 DAC0", NULL, "I2S1" }, + { "IF1 DAC1", NULL, "I2S1" }, + { "IF1 DAC2", NULL, "I2S1" }, + { "IF1 DAC3", NULL, "I2S1" }, + { "IF2 DAC", NULL, "I2S2" }, + + { "IF2 DAC L", NULL, "IF2 DAC" }, + { "IF2 DAC R", NULL, "IF2 DAC" }, + + { "DAC1 L Mux", "IF2 DAC", "IF2 DAC L" }, + { "DAC1 R Mux", "IF2 DAC", "IF2 DAC R" }, + + { "DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL" }, + { "DAC1 MIXL", "DAC1 Switch", "DAC1 L Mux" }, + { "DAC1 MIXL", NULL, "dac stereo1 filter" }, + { "DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR" }, + { "DAC1 MIXR", "DAC1 Switch", "DAC1 R Mux" }, + { "DAC1 MIXR", NULL, "dac stereo1 filter" }, + + { "DAC L2 Mux", "IF2 DAC", "IF2 DAC L" }, + { "DAC L2 Mux", "Mono ADC", "Mono ADC MIXL" }, + { "DAC L2 Mux", "VAD_ADC", "VAD_ADC" }, + { "DAC L2 Volume", NULL, "DAC L2 Mux" }, + { "DAC L2 Volume", NULL, "dac mono left filter" }, + + { "DAC R2 Mux", "IF2 DAC", "IF2 DAC R" }, + { "DAC R2 Mux", "Mono ADC", "Mono ADC MIXR" }, + { "DAC R2 Mux", "Haptic", "Haptic Generator" }, + { "DAC R2 Volume", NULL, "DAC R2 Mux" }, + { "DAC R2 Volume", NULL, "dac mono right filter" }, + + { "Stereo DAC MIXL", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXL", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXL", "DAC L2 Switch", "DAC L2 Volume" }, + { "Stereo DAC MIXL", NULL, "dac stereo1 filter" }, + { "Stereo DAC MIXR", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXR", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXR", "DAC R2 Switch", "DAC R2 Volume" }, + { "Stereo DAC MIXR", NULL, "dac stereo1 filter" }, + + { "Mono DAC MIXL", "DAC L1 Switch", "DAC1 MIXL" }, + { "Mono DAC MIXL", "DAC L2 Switch", "DAC L2 Volume" }, + { "Mono DAC MIXL", "DAC R2 Switch", "DAC R2 Volume" }, + { "Mono DAC MIXL", NULL, "dac mono left filter" }, + { "Mono DAC MIXR", "DAC R1 Switch", "DAC1 MIXR" }, + { "Mono DAC MIXR", "DAC R2 Switch", "DAC R2 Volume" }, + { "Mono DAC MIXR", "DAC L2 Switch", "DAC L2 Volume" }, + { "Mono DAC MIXR", NULL, "dac mono right filter" }, + + { "DAC MIXL", "Sto DAC Mix L Switch", "Stereo DAC MIXL" }, + { "DAC MIXL", "DAC L2 Switch", "DAC L2 Volume" }, + { "DAC MIXL", "DAC R2 Switch", "DAC R2 Volume" }, + { "DAC MIXR", "Sto DAC Mix R Switch", "Stereo DAC MIXR" }, + { "DAC MIXR", "DAC R2 Switch", "DAC R2 Volume" }, + { "DAC MIXR", "DAC L2 Switch", "DAC L2 Volume" }, + + { "DAC L1", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC R1", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC L2", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC R2", NULL, "PLL1", is_sys_clk_from_pll }, + + { "SPK MIXL", "BST1 Switch", "BST1" }, + { "SPK MIXL", "INL Switch", "INL VOL" }, + { "SPK MIXL", "DAC L1 Switch", "DAC L1" }, + { "SPK MIXL", "DAC L2 Switch", "DAC L2" }, + { "SPK MIXR", "BST2 Switch", "BST2" }, + { "SPK MIXR", "INR Switch", "INR VOL" }, + { "SPK MIXR", "DAC R1 Switch", "DAC R1" }, + { "SPK MIXR", "DAC R2 Switch", "DAC R2" }, + + { "OUT MIXL", "BST1 Switch", "BST1" }, + { "OUT MIXL", "INL Switch", "INL VOL" }, + { "OUT MIXL", "DAC L2 Switch", "DAC L2" }, + { "OUT MIXL", "DAC L1 Switch", "DAC L1" }, + + { "OUT MIXR", "BST2 Switch", "BST2" }, + { "OUT MIXR", "INR Switch", "INR VOL" }, + { "OUT MIXR", "DAC R2 Switch", "DAC R2" }, + { "OUT MIXR", "DAC R1 Switch", "DAC R1" }, + + { "HPOVOL MIXL", "DAC1 Switch", "DAC L1" }, + { "HPOVOL MIXL", "DAC2 Switch", "DAC L2" }, + { "HPOVOL MIXL", "INL Switch", "INL VOL" }, + { "HPOVOL MIXL", "BST1 Switch", "BST1" }, + { "HPOVOL MIXL", NULL, "HPOVOL MIXL Power" }, + { "HPOVOL MIXR", "DAC1 Switch", "DAC R1" }, + { "HPOVOL MIXR", "DAC2 Switch", "DAC R2" }, + { "HPOVOL MIXR", "INR Switch", "INR VOL" }, + { "HPOVOL MIXR", "BST2 Switch", "BST2" }, + { "HPOVOL MIXR", NULL, "HPOVOL MIXR Power" }, + + { "DAC 2", NULL, "DAC L2" }, + { "DAC 2", NULL, "DAC R2" }, + { "DAC 1", NULL, "DAC L1" }, + { "DAC 1", NULL, "DAC R1" }, + { "HPOVOL L", "Switch", "HPOVOL MIXL" }, + { "HPOVOL R", "Switch", "HPOVOL MIXR" }, + { "HPOVOL", NULL, "HPOVOL L" }, + { "HPOVOL", NULL, "HPOVOL R" }, + { "HPO MIX", "DAC1 Switch", "DAC 1" }, + { "HPO MIX", "HPVOL Switch", "HPOVOL" }, + + { "SPKVOL L", "Switch", "SPK MIXL" }, + { "SPKVOL R", "Switch", "SPK MIXR" }, + + { "SPOL MIX", "DAC L1 Switch", "DAC L1" }, + { "SPOL MIX", "SPKVOL L Switch", "SPKVOL L" }, + { "SPOR MIX", "DAC R1 Switch", "DAC R1" }, + { "SPOR MIX", "SPKVOL R Switch", "SPKVOL R" }, + + { "LOUT MIX", "DAC L1 Switch", "DAC L1" }, + { "LOUT MIX", "DAC R1 Switch", "DAC R1" }, + { "LOUT MIX", "OUTMIX L Switch", "OUT MIXL" }, + { "LOUT MIX", "OUTMIX R Switch", "OUT MIXR" }, + + { "PDM1 L Mux", "Stereo DAC", "Stereo DAC MIXL" }, + { "PDM1 L Mux", "Mono DAC", "Mono DAC MIXL" }, + { "PDM1 L Mux", NULL, "PDM1 Power" }, + { "PDM1 R Mux", "Stereo DAC", "Stereo DAC MIXR" }, + { "PDM1 R Mux", "Mono DAC", "Mono DAC MIXR" }, + { "PDM1 R Mux", NULL, "PDM1 Power" }, + + { "HP amp", NULL, "HPO MIX" }, + { "HP amp", NULL, "JD Power" }, + { "HP amp", NULL, "Mic Det Power" }, + { "HP amp", NULL, "LDO2" }, + { "HPOL", NULL, "HP amp" }, + { "HPOR", NULL, "HP amp" }, + + { "LOUT amp", NULL, "LOUT MIX" }, + { "LOUTL", NULL, "LOUT amp" }, + { "LOUTR", NULL, "LOUT amp" }, + + { "PDM1 L", "Switch", "PDM1 L Mux" }, + { "PDM1 R", "Switch", "PDM1 R Mux" }, + + { "PDM1L", NULL, "PDM1 L" }, + { "PDM1R", NULL, "PDM1 R" }, + + { "SPK amp", NULL, "SPOL MIX" }, + { "SPK amp", NULL, "SPOR MIX" }, + { "SPOL", NULL, "SPK amp" }, + { "SPOR", NULL, "SPK amp" }, +}; + +static const struct snd_soc_dapm_route rt5650_specific_dapm_routes[] = { + { "A DAC1 L Mux", "DAC1", "DAC1 MIXL"}, + { "A DAC1 L Mux", "Stereo DAC Mixer", "Stereo DAC MIXL"}, + { "A DAC1 R Mux", "DAC1", "DAC1 MIXR"}, + { "A DAC1 R Mux", "Stereo DAC Mixer", "Stereo DAC MIXR"}, + + { "A DAC2 L Mux", "Stereo DAC Mixer", "Stereo DAC MIXL"}, + { "A DAC2 L Mux", "Mono DAC Mixer", "Mono DAC MIXL"}, + { "A DAC2 R Mux", "Stereo DAC Mixer", "Stereo DAC MIXR"}, + { "A DAC2 R Mux", "Mono DAC Mixer", "Mono DAC MIXR"}, + + { "DAC L1", NULL, "A DAC1 L Mux" }, + { "DAC R1", NULL, "A DAC1 R Mux" }, + { "DAC L2", NULL, "A DAC2 L Mux" }, + { "DAC R2", NULL, "A DAC2 R Mux" }, + + { "RT5650 IF1 ADC1 Swap Mux", "L/R", "IF_ADC1" }, + { "RT5650 IF1 ADC1 Swap Mux", "R/L", "IF_ADC1" }, + { "RT5650 IF1 ADC1 Swap Mux", "L/L", "IF_ADC1" }, + { "RT5650 IF1 ADC1 Swap Mux", "R/R", "IF_ADC1" }, + + { "RT5650 IF1 ADC2 Swap Mux", "L/R", "IF_ADC2" }, + { "RT5650 IF1 ADC2 Swap Mux", "R/L", "IF_ADC2" }, + { "RT5650 IF1 ADC2 Swap Mux", "L/L", "IF_ADC2" }, + { "RT5650 IF1 ADC2 Swap Mux", "R/R", "IF_ADC2" }, + + { "RT5650 IF1 ADC3 Swap Mux", "L/R", "VAD_ADC" }, + { "RT5650 IF1 ADC3 Swap Mux", "R/L", "VAD_ADC" }, + { "RT5650 IF1 ADC3 Swap Mux", "L/L", "VAD_ADC" }, + { "RT5650 IF1 ADC3 Swap Mux", "R/R", "VAD_ADC" }, + + { "IF1 ADC", NULL, "RT5650 IF1 ADC1 Swap Mux" }, + { "IF1 ADC", NULL, "RT5650 IF1 ADC2 Swap Mux" }, + { "IF1 ADC", NULL, "RT5650 IF1 ADC3 Swap Mux" }, + + { "RT5650 IF1 ADC Mux", "IF_ADC1/IF_ADC2/DAC_REF/Null", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC1/IF_ADC2/Null/DAC_REF", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC1/DAC_REF/IF_ADC2/Null", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC1/DAC_REF/Null/IF_ADC2", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC1/Null/DAC_REF/IF_ADC2", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC1/Null/IF_ADC2/DAC_REF", "IF1 ADC" }, + + { "RT5650 IF1 ADC Mux", "IF_ADC2/IF_ADC1/DAC_REF/Null", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC2/IF_ADC1/Null/DAC_REF", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC2/DAC_REF/IF_ADC1/Null", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC2/DAC_REF/Null/IF_ADC1", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC2/Null/DAC_REF/IF_ADC1", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "IF_ADC2/Null/IF_ADC1/DAC_REF", "IF1 ADC" }, + + { "RT5650 IF1 ADC Mux", "DAC_REF/IF_ADC1/IF_ADC2/Null", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "DAC_REF/IF_ADC1/Null/IF_ADC2", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "DAC_REF/IF_ADC2/IF_ADC1/Null", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "DAC_REF/IF_ADC2/Null/IF_ADC1", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "DAC_REF/Null/IF_ADC1/IF_ADC2", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "DAC_REF/Null/IF_ADC2/IF_ADC1", "IF1 ADC" }, + + { "RT5650 IF1 ADC Mux", "Null/IF_ADC1/IF_ADC2/DAC_REF", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "Null/IF_ADC1/DAC_REF/IF_ADC2", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "Null/IF_ADC2/IF_ADC1/DAC_REF", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "Null/IF_ADC2/DAC_REF/IF_ADC1", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "Null/DAC_REF/IF_ADC1/IF_ADC2", "IF1 ADC" }, + { "RT5650 IF1 ADC Mux", "Null/DAC_REF/IF_ADC2/IF_ADC1", "IF1 ADC" }, + { "AIF1TX", NULL, "RT5650 IF1 ADC Mux" }, + + { "RT5650 IF1 DAC1 L Mux", "Slot0", "IF1 DAC0" }, + { "RT5650 IF1 DAC1 L Mux", "Slot1", "IF1 DAC1" }, + { "RT5650 IF1 DAC1 L Mux", "Slot2", "IF1 DAC2" }, + { "RT5650 IF1 DAC1 L Mux", "Slot3", "IF1 DAC3" }, + + { "RT5650 IF1 DAC1 R Mux", "Slot0", "IF1 DAC0" }, + { "RT5650 IF1 DAC1 R Mux", "Slot1", "IF1 DAC1" }, + { "RT5650 IF1 DAC1 R Mux", "Slot2", "IF1 DAC2" }, + { "RT5650 IF1 DAC1 R Mux", "Slot3", "IF1 DAC3" }, + + { "RT5650 IF1 DAC2 L Mux", "Slot0", "IF1 DAC0" }, + { "RT5650 IF1 DAC2 L Mux", "Slot1", "IF1 DAC1" }, + { "RT5650 IF1 DAC2 L Mux", "Slot2", "IF1 DAC2" }, + { "RT5650 IF1 DAC2 L Mux", "Slot3", "IF1 DAC3" }, + + { "RT5650 IF1 DAC2 R Mux", "Slot0", "IF1 DAC0" }, + { "RT5650 IF1 DAC2 R Mux", "Slot1", "IF1 DAC1" }, + { "RT5650 IF1 DAC2 R Mux", "Slot2", "IF1 DAC2" }, + { "RT5650 IF1 DAC2 R Mux", "Slot3", "IF1 DAC3" }, + + { "DAC1 L Mux", "IF1 DAC", "RT5650 IF1 DAC1 L Mux" }, + { "DAC1 R Mux", "IF1 DAC", "RT5650 IF1 DAC1 R Mux" }, + + { "DAC L2 Mux", "IF1 DAC", "RT5650 IF1 DAC2 L Mux" }, + { "DAC R2 Mux", "IF1 DAC", "RT5650 IF1 DAC2 R Mux" }, +}; + +static const struct snd_soc_dapm_route rt5645_specific_dapm_routes[] = { + { "DAC L1", NULL, "Stereo DAC MIXL" }, + { "DAC R1", NULL, "Stereo DAC MIXR" }, + { "DAC L2", NULL, "Mono DAC MIXL" }, + { "DAC R2", NULL, "Mono DAC MIXR" }, + + { "RT5645 IF1 ADC1 Swap Mux", "L/R", "IF_ADC1" }, + { "RT5645 IF1 ADC1 Swap Mux", "R/L", "IF_ADC1" }, + { "RT5645 IF1 ADC1 Swap Mux", "L/L", "IF_ADC1" }, + { "RT5645 IF1 ADC1 Swap Mux", "R/R", "IF_ADC1" }, + + { "RT5645 IF1 ADC2 Swap Mux", "L/R", "IF_ADC2" }, + { "RT5645 IF1 ADC2 Swap Mux", "R/L", "IF_ADC2" }, + { "RT5645 IF1 ADC2 Swap Mux", "L/L", "IF_ADC2" }, + { "RT5645 IF1 ADC2 Swap Mux", "R/R", "IF_ADC2" }, + + { "RT5645 IF1 ADC3 Swap Mux", "L/R", "VAD_ADC" }, + { "RT5645 IF1 ADC3 Swap Mux", "R/L", "VAD_ADC" }, + { "RT5645 IF1 ADC3 Swap Mux", "L/L", "VAD_ADC" }, + { "RT5645 IF1 ADC3 Swap Mux", "R/R", "VAD_ADC" }, + + { "IF1 ADC", NULL, "RT5645 IF1 ADC1 Swap Mux" }, + { "IF1 ADC", NULL, "RT5645 IF1 ADC2 Swap Mux" }, + { "IF1 ADC", NULL, "RT5645 IF1 ADC3 Swap Mux" }, + + { "RT5645 IF1 ADC Mux", "IF_ADC1/IF_ADC2/VAD_ADC", "IF1 ADC" }, + { "RT5645 IF1 ADC Mux", "IF_ADC2/IF_ADC1/VAD_ADC", "IF1 ADC" }, + { "RT5645 IF1 ADC Mux", "VAD_ADC/IF_ADC1/IF_ADC2", "IF1 ADC" }, + { "RT5645 IF1 ADC Mux", "VAD_ADC/IF_ADC2/IF_ADC1", "IF1 ADC" }, + { "AIF1TX", NULL, "RT5645 IF1 ADC Mux" }, + + { "RT5645 IF1 DAC1 L Mux", "Slot0", "IF1 DAC0" }, + { "RT5645 IF1 DAC1 L Mux", "Slot1", "IF1 DAC1" }, + { "RT5645 IF1 DAC1 L Mux", "Slot2", "IF1 DAC2" }, + { "RT5645 IF1 DAC1 L Mux", "Slot3", "IF1 DAC3" }, + + { "RT5645 IF1 DAC1 R Mux", "Slot0", "IF1 DAC0" }, + { "RT5645 IF1 DAC1 R Mux", "Slot1", "IF1 DAC1" }, + { "RT5645 IF1 DAC1 R Mux", "Slot2", "IF1 DAC2" }, + { "RT5645 IF1 DAC1 R Mux", "Slot3", "IF1 DAC3" }, + + { "RT5645 IF1 DAC2 L Mux", "Slot0", "IF1 DAC0" }, + { "RT5645 IF1 DAC2 L Mux", "Slot1", "IF1 DAC1" }, + { "RT5645 IF1 DAC2 L Mux", "Slot2", "IF1 DAC2" }, + { "RT5645 IF1 DAC2 L Mux", "Slot3", "IF1 DAC3" }, + + { "RT5645 IF1 DAC2 R Mux", "Slot0", "IF1 DAC0" }, + { "RT5645 IF1 DAC2 R Mux", "Slot1", "IF1 DAC1" }, + { "RT5645 IF1 DAC2 R Mux", "Slot2", "IF1 DAC2" }, + { "RT5645 IF1 DAC2 R Mux", "Slot3", "IF1 DAC3" }, + + { "DAC1 L Mux", "IF1 DAC", "RT5645 IF1 DAC1 L Mux" }, + { "DAC1 R Mux", "IF1 DAC", "RT5645 IF1 DAC1 R Mux" }, + + { "DAC L2 Mux", "IF1 DAC", "RT5645 IF1 DAC2 L Mux" }, + { "DAC R2 Mux", "IF1 DAC", "RT5645 IF1 DAC2 R Mux" }, +}; + +static const struct snd_soc_dapm_route rt5645_old_dapm_routes[] = { + { "SPOL MIX", "DAC R1 Switch", "DAC R1" }, + { "SPOL MIX", "SPKVOL R Switch", "SPKVOL R" }, +}; + +static int rt5645_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk, dl_sft; + int pre_div, bclk_ms, frame_size; + + rt5645->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5645->sysclk, rt5645->lrck[dai->id]); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting\n"); + return -EINVAL; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + + switch (rt5645->codec_type) { + case CODEC_TYPE_RT5650: + dl_sft = 4; + break; + default: + dl_sft = 2; + break; + } + + bclk_ms = frame_size > 32; + rt5645->bclk[dai->id] = rt5645->lrck[dai->id] * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5645->bclk[dai->id], rt5645->lrck[dai->id]); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len = 0x1; + break; + case 24: + val_len = 0x2; + break; + case 8: + val_len = 0x3; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5645_AIF1: + mask_clk = RT5645_I2S_PD1_MASK; + val_clk = pre_div << RT5645_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5645_I2S1_SDP, + (0x3 << dl_sft), (val_len << dl_sft)); + snd_soc_component_update_bits(component, RT5645_ADDA_CLK1, mask_clk, val_clk); + break; + case RT5645_AIF2: + mask_clk = RT5645_I2S_BCLK_MS2_MASK | RT5645_I2S_PD2_MASK; + val_clk = bclk_ms << RT5645_I2S_BCLK_MS2_SFT | + pre_div << RT5645_I2S_PD2_SFT; + snd_soc_component_update_bits(component, RT5645_I2S2_SDP, + (0x3 << dl_sft), (val_len << dl_sft)); + snd_soc_component_update_bits(component, RT5645_ADDA_CLK1, mask_clk, val_clk); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5645_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, pol_sft; + + switch (rt5645->codec_type) { + case CODEC_TYPE_RT5650: + pol_sft = 8; + break; + default: + pol_sft = 7; + break; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5645->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5645_I2S_MS_S; + rt5645->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= (1 << pol_sft); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5645_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5645_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5645_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + switch (dai->id) { + case RT5645_AIF1: + snd_soc_component_update_bits(component, RT5645_I2S1_SDP, + RT5645_I2S_MS_MASK | (1 << pol_sft) | + RT5645_I2S_DF_MASK, reg_val); + break; + case RT5645_AIF2: + snd_soc_component_update_bits(component, RT5645_I2S2_SDP, + RT5645_I2S_MS_MASK | (1 << pol_sft) | + RT5645_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5645_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + if (freq == rt5645->sysclk && clk_id == rt5645->sysclk_src) + return 0; + + switch (clk_id) { + case RT5645_SCLK_S_MCLK: + reg_val |= RT5645_SCLK_SRC_MCLK; + break; + case RT5645_SCLK_S_PLL1: + reg_val |= RT5645_SCLK_SRC_PLL1; + break; + case RT5645_SCLK_S_RCCLK: + reg_val |= RT5645_SCLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5645_GLB_CLK, + RT5645_SCLK_SRC_MASK, reg_val); + rt5645->sysclk = freq; + rt5645->sysclk_src = clk_id; + + dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + + return 0; +} + +static int rt5645_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5645->pll_src && freq_in == rt5645->pll_in && + freq_out == rt5645->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5645->pll_in = 0; + rt5645->pll_out = 0; + snd_soc_component_update_bits(component, RT5645_GLB_CLK, + RT5645_SCLK_SRC_MASK, RT5645_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5645_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5645_GLB_CLK, + RT5645_PLL1_SRC_MASK, RT5645_PLL1_SRC_MCLK); + break; + case RT5645_PLL1_S_BCLK1: + case RT5645_PLL1_S_BCLK2: + switch (dai->id) { + case RT5645_AIF1: + snd_soc_component_update_bits(component, RT5645_GLB_CLK, + RT5645_PLL1_SRC_MASK, RT5645_PLL1_SRC_BCLK1); + break; + case RT5645_AIF2: + snd_soc_component_update_bits(component, RT5645_GLB_CLK, + RT5645_PLL1_SRC_MASK, RT5645_PLL1_SRC_BCLK2); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5645_PLL_CTRL1, + pll_code.n_code << RT5645_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5645_PLL_CTRL2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5645_PLL_M_SFT | + pll_code.m_bp << RT5645_PLL_M_BP_SFT); + + rt5645->pll_in = freq_in; + rt5645->pll_out = freq_out; + rt5645->pll_src = source; + + return 0; +} + +static int rt5645_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + unsigned int i_slot_sft, o_slot_sft, i_width_sht, o_width_sht, en_sft; + unsigned int mask, val = 0; + + switch (rt5645->codec_type) { + case CODEC_TYPE_RT5650: + en_sft = 15; + i_slot_sft = 10; + o_slot_sft = 8; + i_width_sht = 6; + o_width_sht = 4; + mask = 0x8ff0; + break; + default: + en_sft = 14; + i_slot_sft = o_slot_sft = 12; + i_width_sht = o_width_sht = 10; + mask = 0x7c00; + break; + } + if (rx_mask || tx_mask) { + val |= (1 << en_sft); + if (rt5645->codec_type == CODEC_TYPE_RT5645) + snd_soc_component_update_bits(component, RT5645_BASS_BACK, + RT5645_G_BB_BST_MASK, RT5645_G_BB_BST_25DB); + } + + switch (slots) { + case 4: + val |= (1 << i_slot_sft) | (1 << o_slot_sft); + break; + case 6: + val |= (2 << i_slot_sft) | (2 << o_slot_sft); + break; + case 8: + val |= (3 << i_slot_sft) | (3 << o_slot_sft); + break; + case 2: + default: + break; + } + + switch (slot_width) { + case 20: + val |= (1 << i_width_sht) | (1 << o_width_sht); + break; + case 24: + val |= (2 << i_width_sht) | (2 << o_width_sht); + break; + case 32: + val |= (3 << i_width_sht) | (3 << o_width_sht); + break; + case 16: + default: + break; + } + + snd_soc_component_update_bits(component, RT5645_TDM_CTRL_1, mask, val); + + return 0; +} + +static int rt5645_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == snd_soc_component_get_bias_level(component)) { + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2); + mdelay(10); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_FV1 | RT5645_PWR_FV2, + RT5645_PWR_FV1 | RT5645_PWR_FV2); + snd_soc_component_update_bits(component, RT5645_GEN_CTRL1, + RT5645_DIG_GATE_CTRL, RT5645_DIG_GATE_CTRL); + } + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2); + mdelay(10); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_FV1 | RT5645_PWR_FV2, + RT5645_PWR_FV1 | RT5645_PWR_FV2); + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_write(component, RT5645_DEPOP_M2, 0x1140); + msleep(40); + if (rt5645->en_button_func) + queue_delayed_work(system_power_efficient_wq, + &rt5645->jack_detect_work, + msecs_to_jiffies(0)); + } + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, RT5645_DEPOP_M2, 0x1100); + if (!rt5645->en_button_func) + snd_soc_component_update_bits(component, RT5645_GEN_CTRL1, + RT5645_DIG_GATE_CTRL, 0); + snd_soc_component_update_bits(component, RT5645_PWR_ANLG1, + RT5645_PWR_VREF1 | RT5645_PWR_MB | + RT5645_PWR_BG | RT5645_PWR_VREF2 | + RT5645_PWR_FV1 | RT5645_PWR_FV2, 0x0); + break; + + default: + break; + } + + return 0; +} + +static void rt5645_enable_push_button_irq(struct snd_soc_component *component, + bool enable) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (enable) { + snd_soc_dapm_force_enable_pin(dapm, "ADC L power"); + snd_soc_dapm_force_enable_pin(dapm, "ADC R power"); + snd_soc_dapm_sync(dapm); + + snd_soc_component_update_bits(component, RT5650_4BTN_IL_CMD1, 0x3, 0x3); + snd_soc_component_update_bits(component, + RT5645_INT_IRQ_ST, 0x8, 0x8); + snd_soc_component_update_bits(component, + RT5650_4BTN_IL_CMD2, 0x8000, 0x8000); + snd_soc_component_read32(component, RT5650_4BTN_IL_CMD1); + pr_debug("%s read %x = %x\n", __func__, RT5650_4BTN_IL_CMD1, + snd_soc_component_read32(component, RT5650_4BTN_IL_CMD1)); + } else { + snd_soc_component_update_bits(component, RT5650_4BTN_IL_CMD2, 0x8000, 0x0); + snd_soc_component_update_bits(component, RT5645_INT_IRQ_ST, 0x8, 0x0); + + snd_soc_dapm_disable_pin(dapm, "ADC L power"); + snd_soc_dapm_disable_pin(dapm, "ADC R power"); + snd_soc_dapm_sync(dapm); + } +} + +static int rt5645_jack_detect(struct snd_soc_component *component, int jack_insert) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (jack_insert) { + regmap_write(rt5645->regmap, RT5645_CHARGE_PUMP, 0x0e06); + + /* for jack type detect */ + snd_soc_dapm_force_enable_pin(dapm, "LDO2"); + snd_soc_dapm_force_enable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + if (!dapm->card->instantiated) { + /* Power up necessary bits for JD if dapm is + not ready yet */ + regmap_update_bits(rt5645->regmap, RT5645_PWR_ANLG1, + RT5645_PWR_MB | RT5645_PWR_VREF2, + RT5645_PWR_MB | RT5645_PWR_VREF2); + regmap_update_bits(rt5645->regmap, RT5645_PWR_MIXER, + RT5645_PWR_LDO2, RT5645_PWR_LDO2); + regmap_update_bits(rt5645->regmap, RT5645_PWR_VOL, + RT5645_PWR_MIC_DET, RT5645_PWR_MIC_DET); + } + + regmap_write(rt5645->regmap, RT5645_JD_CTRL3, 0x00f0); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2, + RT5645_CBJ_MN_JD, RT5645_CBJ_MN_JD); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL1, + RT5645_CBJ_BST1_EN, RT5645_CBJ_BST1_EN); + msleep(100); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2, + RT5645_CBJ_MN_JD, 0); + + msleep(600); + regmap_read(rt5645->regmap, RT5645_IN1_CTRL3, &val); + val &= 0x7; + dev_dbg(component->dev, "val = %d\n", val); + + if (val == 1 || val == 2) { + rt5645->jack_type = SND_JACK_HEADSET; + if (rt5645->en_button_func) { + rt5645_enable_push_button_irq(component, true); + } + } else { + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + rt5645->jack_type = SND_JACK_HEADPHONE; + } + if (rt5645->pdata.level_trigger_irq) + regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2, + RT5645_JD_1_1_MASK, RT5645_JD_1_1_NOR); + } else { /* jack out */ + rt5645->jack_type = 0; + + regmap_update_bits(rt5645->regmap, RT5645_HP_VOL, + RT5645_L_MUTE | RT5645_R_MUTE, + RT5645_L_MUTE | RT5645_R_MUTE); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2, + RT5645_CBJ_MN_JD, RT5645_CBJ_MN_JD); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL1, + RT5645_CBJ_BST1_EN, 0); + + if (rt5645->en_button_func) + rt5645_enable_push_button_irq(component, false); + + if (rt5645->pdata.jd_mode == 0) + snd_soc_dapm_disable_pin(dapm, "LDO2"); + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + if (rt5645->pdata.level_trigger_irq) + regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2, + RT5645_JD_1_1_MASK, RT5645_JD_1_1_INV); + } + + return rt5645->jack_type; +} + +static int rt5645_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read32(component, RT5650_4BTN_IL_CMD1); + pr_debug("val=0x%x\n", val); + btn_type = val & 0xfff0; + snd_soc_component_write(component, RT5650_4BTN_IL_CMD1, val); + + return btn_type; +} + +static irqreturn_t rt5645_irq(int irq, void *data); + +int rt5645_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack, + struct snd_soc_jack *btn_jack) +{ + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + rt5645->hp_jack = hp_jack; + rt5645->mic_jack = mic_jack; + rt5645->btn_jack = btn_jack; + if (rt5645->btn_jack && rt5645->codec_type == CODEC_TYPE_RT5650) { + rt5645->en_button_func = true; + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP1_PIN_IRQ, RT5645_GP1_PIN_IRQ); + regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL1, + RT5645_DIG_GATE_CTRL, RT5645_DIG_GATE_CTRL); + } + rt5645_irq(0, rt5645); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5645_set_jack_detect); + +static void rt5645_jack_detect_work(struct work_struct *work) +{ + struct rt5645_priv *rt5645 = + container_of(work, struct rt5645_priv, jack_detect_work.work); + int val, btn_type, gpio_state = 0, report = 0; + + if (!rt5645->component) + return; + + switch (rt5645->pdata.jd_mode) { + case 0: /* Not using rt5645 JD */ + if (rt5645->gpiod_hp_det) { + gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); + dev_dbg(rt5645->component->dev, "gpio_state = %d\n", + gpio_state); + report = rt5645_jack_detect(rt5645->component, gpio_state); + } + snd_soc_jack_report(rt5645->hp_jack, + report, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, + report, SND_JACK_MICROPHONE); + return; + case 4: + val = snd_soc_component_read32(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; + break; + default: /* read rt5645 jd1_1 status */ + val = snd_soc_component_read32(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; + break; + + } + + if (!val && (rt5645->jack_type == 0)) { /* jack in */ + report = rt5645_jack_detect(rt5645->component, 1); + } else if (!val && rt5645->jack_type != 0) { + /* for push button and jack out */ + btn_type = 0; + if (snd_soc_component_read32(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { + /* button pressed */ + report = SND_JACK_HEADSET; + btn_type = rt5645_button_detect(rt5645->component); + /* rt5650 can report three kinds of button behavior, + one click, double click and hold. However, + currently we will report button pressed/released + event. So all the three button behaviors are + treated as button pressed. */ + switch (btn_type) { + case 0x8000: + case 0x4000: + case 0x2000: + report |= SND_JACK_BTN_0; + break; + case 0x1000: + case 0x0800: + case 0x0400: + report |= SND_JACK_BTN_1; + break; + case 0x0200: + case 0x0100: + case 0x0080: + report |= SND_JACK_BTN_2; + break; + case 0x0040: + case 0x0020: + case 0x0010: + report |= SND_JACK_BTN_3; + break; + case 0x0000: /* unpressed */ + break; + default: + dev_err(rt5645->component->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + } + if (btn_type == 0)/* button release */ + report = rt5645->jack_type; + else { + mod_timer(&rt5645->btn_check_timer, + msecs_to_jiffies(100)); + } + } else { + /* jack out */ + report = 0; + snd_soc_component_update_bits(rt5645->component, + RT5645_INT_IRQ_ST, 0x1, 0x0); + rt5645_jack_detect(rt5645->component, 0); + } + + snd_soc_jack_report(rt5645->hp_jack, report, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, report, SND_JACK_MICROPHONE); + if (rt5645->en_button_func) + snd_soc_jack_report(rt5645->btn_jack, + report, SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); +} + +static void rt5645_rcclock_work(struct work_struct *work) +{ + struct rt5645_priv *rt5645 = + container_of(work, struct rt5645_priv, rcclock_work.work); + + regmap_update_bits(rt5645->regmap, RT5645_MICBIAS, + RT5645_PWR_CLK25M_MASK, RT5645_PWR_CLK25M_PD); +} + +static irqreturn_t rt5645_irq(int irq, void *data) +{ + struct rt5645_priv *rt5645 = data; + + queue_delayed_work(system_power_efficient_wq, + &rt5645->jack_detect_work, msecs_to_jiffies(250)); + + return IRQ_HANDLED; +} + +static void rt5645_btn_check_callback(struct timer_list *t) +{ + struct rt5645_priv *rt5645 = from_timer(rt5645, t, btn_check_timer); + + queue_delayed_work(system_power_efficient_wq, + &rt5645->jack_detect_work, msecs_to_jiffies(5)); +} + +static int rt5645_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + rt5645->component = component; + + switch (rt5645->codec_type) { + case CODEC_TYPE_RT5645: + snd_soc_dapm_new_controls(dapm, + rt5645_specific_dapm_widgets, + ARRAY_SIZE(rt5645_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5645_specific_dapm_routes, + ARRAY_SIZE(rt5645_specific_dapm_routes)); + if (rt5645->v_id < 3) { + snd_soc_dapm_add_routes(dapm, + rt5645_old_dapm_routes, + ARRAY_SIZE(rt5645_old_dapm_routes)); + } + break; + case CODEC_TYPE_RT5650: + snd_soc_dapm_new_controls(dapm, + rt5650_specific_dapm_widgets, + ARRAY_SIZE(rt5650_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5650_specific_dapm_routes, + ARRAY_SIZE(rt5650_specific_dapm_routes)); + break; + } + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + /* for JD function */ + if (rt5645->pdata.jd_mode) { + snd_soc_dapm_force_enable_pin(dapm, "JD Power"); + snd_soc_dapm_force_enable_pin(dapm, "LDO2"); + snd_soc_dapm_sync(dapm); + } + + if (rt5645->pdata.long_name) + component->card->long_name = rt5645->pdata.long_name; + + rt5645->eq_param = devm_kcalloc(component->dev, + RT5645_HWEQ_NUM, sizeof(struct rt5645_eq_param_s), + GFP_KERNEL); + + return 0; +} + +static void rt5645_remove(struct snd_soc_component *component) +{ + rt5645_reset(component); +} + +#ifdef CONFIG_PM +static int rt5645_suspend(struct snd_soc_component *component) +{ + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5645->regmap, true); + regcache_mark_dirty(rt5645->regmap); + + return 0; +} + +static int rt5645_resume(struct snd_soc_component *component) +{ + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5645->regmap, false); + regcache_sync(rt5645->regmap); + + return 0; +} +#else +#define rt5645_suspend NULL +#define rt5645_resume NULL +#endif + +#define RT5645_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define RT5645_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5645_aif_dai_ops = { + .hw_params = rt5645_hw_params, + .set_fmt = rt5645_set_dai_fmt, + .set_sysclk = rt5645_set_dai_sysclk, + .set_tdm_slot = rt5645_set_tdm_slot, + .set_pll = rt5645_set_dai_pll, +}; + +static struct snd_soc_dai_driver rt5645_dai[] = { + { + .name = "rt5645-aif1", + .id = RT5645_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5645_STEREO_RATES, + .formats = RT5645_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 4, + .rates = RT5645_STEREO_RATES, + .formats = RT5645_FORMATS, + }, + .ops = &rt5645_aif_dai_ops, + }, + { + .name = "rt5645-aif2", + .id = RT5645_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5645_STEREO_RATES, + .formats = RT5645_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5645_STEREO_RATES, + .formats = RT5645_FORMATS, + }, + .ops = &rt5645_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5645 = { + .probe = rt5645_probe, + .remove = rt5645_remove, + .suspend = rt5645_suspend, + .resume = rt5645_resume, + .set_bias_level = rt5645_set_bias_level, + .controls = rt5645_snd_controls, + .num_controls = ARRAY_SIZE(rt5645_snd_controls), + .dapm_widgets = rt5645_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5645_dapm_widgets), + .dapm_routes = rt5645_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5645_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5645_regmap = { + .reg_bits = 8, + .val_bits = 16, + .use_single_rw = true, + .max_register = RT5645_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5645_ranges) * + RT5645_PR_SPACING), + .volatile_reg = rt5645_volatile_register, + .readable_reg = rt5645_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5645_reg, + .num_reg_defaults = ARRAY_SIZE(rt5645_reg), + .ranges = rt5645_ranges, + .num_ranges = ARRAY_SIZE(rt5645_ranges), +}; + +static const struct regmap_config rt5650_regmap = { + .reg_bits = 8, + .val_bits = 16, + .use_single_rw = true, + .max_register = RT5645_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5645_ranges) * + RT5645_PR_SPACING), + .volatile_reg = rt5645_volatile_register, + .readable_reg = rt5645_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5650_reg, + .num_reg_defaults = ARRAY_SIZE(rt5650_reg), + .ranges = rt5645_ranges, + .num_ranges = ARRAY_SIZE(rt5645_ranges), +}; + +static const struct regmap_config temp_regmap = { + .name="nocache", + .reg_bits = 8, + .val_bits = 16, + .use_single_rw = true, + .max_register = RT5645_VENDOR_ID2 + 1, + .cache_type = REGCACHE_NONE, +}; + +static const struct i2c_device_id rt5645_i2c_id[] = { + { "rt5645", 0 }, + { "rt5650", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id rt5645_of_match[] = { + { .compatible = "realtek,rt5645", }, + { .compatible = "realtek,rt5650", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5645_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5645_acpi_match[] = { + { "10EC5645", 0 }, + { "10EC5648", 0 }, + { "10EC5650", 0 }, + { "10EC5640", 0 }, + { "10EC3270", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt5645_acpi_match); +#endif + +static const struct rt5645_platform_data intel_braswell_platform_data = { + .dmic1_data_pin = RT5645_DMIC1_DISABLE, + .dmic2_data_pin = RT5645_DMIC_DATA_IN2P, + .jd_mode = 3, +}; + +static const struct rt5645_platform_data buddy_platform_data = { + .dmic1_data_pin = RT5645_DMIC_DATA_GPIO5, + .dmic2_data_pin = RT5645_DMIC_DATA_IN2P, + .jd_mode = 4, + .level_trigger_irq = true, +}; + +static const struct rt5645_platform_data gpd_win_platform_data = { + .jd_mode = 3, + .inv_jd1_1 = true, + .long_name = "gpd-win-pocket-rt5645", + /* The GPD pocket has a diff. mic, for the win this does not matter. */ + .in2_diff = true, +}; + +static const struct rt5645_platform_data asus_t100ha_platform_data = { + .dmic1_data_pin = RT5645_DMIC_DATA_IN2N, + .dmic2_data_pin = RT5645_DMIC2_DISABLE, + .jd_mode = 3, + .inv_jd1_1 = true, +}; + +static const struct rt5645_platform_data asus_t101ha_platform_data = { + .dmic1_data_pin = RT5645_DMIC_DATA_IN2N, + .dmic2_data_pin = RT5645_DMIC2_DISABLE, + .jd_mode = 3, +}; + +static const struct rt5645_platform_data lenovo_ideapad_miix_310_pdata = { + .jd_mode = 3, + .in2_diff = true, +}; + +static const struct rt5645_platform_data jd_mode3_platform_data = { + .jd_mode = 3, +}; + +static const struct rt5645_platform_data lattepanda_board_platform_data = { + .jd_mode = 2, + .inv_jd1_1 = true +}; + +static const struct dmi_system_id dmi_platform_data[] = { + { + .ident = "Chrome Buddy", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Buddy"), + }, + .driver_data = (void *)&buddy_platform_data, + }, + { + .ident = "Intel Strago", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Strago"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, + { + .ident = "Google Chrome", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, + { + .ident = "Google Setzer", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Setzer"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, + { + .ident = "Microsoft Surface 3", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat + * generic dmi strings, which is why we test for 4 strings. + * Comparing against 23 other byt/cht boards, board_vendor + * and board_name are unique to the GPDwin, where as only one + * other board has the same board_serial and 3 others have + * the same default product_name. Also the GPDwin is the + * only device to have both board_ and product_name not set. + */ + .ident = "GPD Win / Pocket", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_MATCH(DMI_BOARD_SERIAL, "Default string"), + DMI_MATCH(DMI_PRODUCT_NAME, "Default string"), + }, + .driver_data = (void *)&gpd_win_platform_data, + }, + { + .ident = "ASUS T100HAN", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "T100HAN"), + }, + .driver_data = (void *)&asus_t100ha_platform_data, + }, + { + .ident = "ASUS T101HA", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "T101HA"), + }, + .driver_data = (void *)&asus_t101ha_platform_data, + }, + { + .ident = "MINIX Z83-4", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MINIX"), + DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"), + }, + .driver_data = (void *)&jd_mode3_platform_data, + }, + { + .ident = "Teclast X80 Pro", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_MATCH(DMI_PRODUCT_NAME, "X80 Pro"), + }, + .driver_data = (void *)&jd_mode3_platform_data, + }, + { + .ident = "Lenovo Ideapad Miix 310", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80SG"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "MIIX 310-10ICR"), + }, + .driver_data = (void *)&lenovo_ideapad_miix_310_pdata, + }, + { + .ident = "Lenovo Ideapad Miix 320", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "80XF"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, + { + .ident = "LattePanda board", + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + DMI_EXACT_MATCH(DMI_BOARD_VERSION, "Default string"), + }, + .driver_data = (void *)&lattepanda_board_platform_data, + }, + { } +}; + +static bool rt5645_check_dp(struct device *dev) +{ + if (device_property_present(dev, "realtek,in2-differential") || + device_property_present(dev, "realtek,dmic1-data-pin") || + device_property_present(dev, "realtek,dmic2-data-pin") || + device_property_present(dev, "realtek,jd-mode")) + return true; + + return false; +} + +static int rt5645_parse_dt(struct rt5645_priv *rt5645, struct device *dev) +{ + rt5645->pdata.in2_diff = device_property_read_bool(dev, + "realtek,in2-differential"); + device_property_read_u32(dev, + "realtek,dmic1-data-pin", &rt5645->pdata.dmic1_data_pin); + device_property_read_u32(dev, + "realtek,dmic2-data-pin", &rt5645->pdata.dmic2_data_pin); + device_property_read_u32(dev, + "realtek,jd-mode", &rt5645->pdata.jd_mode); + + return 0; +} + +static int rt5645_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5645_platform_data *pdata = dev_get_platdata(&i2c->dev); + const struct dmi_system_id *dmi_data; + struct rt5645_priv *rt5645; + int ret, i; + unsigned int val; + struct regmap *regmap; + + rt5645 = devm_kzalloc(&i2c->dev, sizeof(struct rt5645_priv), + GFP_KERNEL); + if (rt5645 == NULL) + return -ENOMEM; + + rt5645->i2c = i2c; + i2c_set_clientdata(i2c, rt5645); + + dmi_data = dmi_first_match(dmi_platform_data); + if (dmi_data) { + dev_info(&i2c->dev, "Detected %s platform\n", dmi_data->ident); + pdata = dmi_data->driver_data; + } + + if (pdata) + rt5645->pdata = *pdata; + else if (rt5645_check_dp(&i2c->dev)) + rt5645_parse_dt(rt5645, &i2c->dev); + else + rt5645->pdata = jd_mode3_platform_data; + + if (quirk != -1) { + rt5645->pdata.in2_diff = QUIRK_IN2_DIFF(quirk); + rt5645->pdata.level_trigger_irq = QUIRK_LEVEL_IRQ(quirk); + rt5645->pdata.inv_jd1_1 = QUIRK_INV_JD1_1(quirk); + rt5645->pdata.jd_mode = QUIRK_JD_MODE(quirk); + rt5645->pdata.dmic1_data_pin = QUIRK_DMIC1_DATA_PIN(quirk); + rt5645->pdata.dmic2_data_pin = QUIRK_DMIC2_DATA_PIN(quirk); + } + + rt5645->gpiod_hp_det = devm_gpiod_get_optional(&i2c->dev, "hp-detect", + GPIOD_IN); + + if (IS_ERR(rt5645->gpiod_hp_det)) { + dev_info(&i2c->dev, "failed to initialize gpiod\n"); + ret = PTR_ERR(rt5645->gpiod_hp_det); + /* + * Continue if optional gpiod is missing, bail for all other + * errors, including -EPROBE_DEFER + */ + if (ret != -ENOENT) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(rt5645->supplies); i++) + rt5645->supplies[i].supply = rt5645_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, + ARRAY_SIZE(rt5645->supplies), + rt5645->supplies); + if (ret) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(rt5645->supplies), + rt5645->supplies); + if (ret) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + regmap = devm_regmap_init_i2c(i2c, &temp_regmap); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&i2c->dev, "Failed to allocate temp register map: %d\n", + ret); + return ret; + } + + /* + * Read after 400msec, as it is the interval required between + * read and power On. + */ + msleep(TIME_TO_POWER_MS); + regmap_read(regmap, RT5645_VENDOR_ID2, &val); + + switch (val) { + case RT5645_DEVICE_ID: + rt5645->regmap = devm_regmap_init_i2c(i2c, &rt5645_regmap); + rt5645->codec_type = CODEC_TYPE_RT5645; + break; + case RT5650_DEVICE_ID: + rt5645->regmap = devm_regmap_init_i2c(i2c, &rt5650_regmap); + rt5645->codec_type = CODEC_TYPE_RT5650; + break; + default: + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5645 or rt5650\n", + val); + ret = -ENODEV; + goto err_enable; + } + + if (IS_ERR(rt5645->regmap)) { + ret = PTR_ERR(rt5645->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_write(rt5645->regmap, RT5645_RESET, 0); + + regmap_read(regmap, RT5645_VENDOR_ID, &val); + rt5645->v_id = val & 0xff; + + regmap_write(rt5645->regmap, RT5645_AD_DA_MIXER, 0x8080); + + ret = regmap_register_patch(rt5645->regmap, init_list, + ARRAY_SIZE(init_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + if (rt5645->codec_type == CODEC_TYPE_RT5650) { + ret = regmap_register_patch(rt5645->regmap, rt5650_init_list, + ARRAY_SIZE(rt5650_init_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Apply rt5650 patch failed: %d\n", + ret); + } + + regmap_update_bits(rt5645->regmap, RT5645_CLSD_OUT_CTRL, 0xc0, 0xc0); + + if (rt5645->pdata.in2_diff) + regmap_update_bits(rt5645->regmap, RT5645_IN2_CTRL, + RT5645_IN_DF2, RT5645_IN_DF2); + + if (rt5645->pdata.dmic1_data_pin || rt5645->pdata.dmic2_data_pin) { + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP2_PIN_MASK, RT5645_GP2_PIN_DMIC1_SCL); + } + switch (rt5645->pdata.dmic1_data_pin) { + case RT5645_DMIC_DATA_IN2N: + regmap_update_bits(rt5645->regmap, RT5645_DMIC_CTRL1, + RT5645_DMIC_1_DP_MASK, RT5645_DMIC_1_DP_IN2N); + break; + + case RT5645_DMIC_DATA_GPIO5: + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_I2S2_DAC_PIN_MASK, RT5645_I2S2_DAC_PIN_GPIO); + regmap_update_bits(rt5645->regmap, RT5645_DMIC_CTRL1, + RT5645_DMIC_1_DP_MASK, RT5645_DMIC_1_DP_GPIO5); + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP5_PIN_MASK, RT5645_GP5_PIN_DMIC1_SDA); + break; + + case RT5645_DMIC_DATA_GPIO11: + regmap_update_bits(rt5645->regmap, RT5645_DMIC_CTRL1, + RT5645_DMIC_1_DP_MASK, RT5645_DMIC_1_DP_GPIO11); + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP11_PIN_MASK, + RT5645_GP11_PIN_DMIC1_SDA); + break; + + default: + break; + } + + switch (rt5645->pdata.dmic2_data_pin) { + case RT5645_DMIC_DATA_IN2P: + regmap_update_bits(rt5645->regmap, RT5645_DMIC_CTRL1, + RT5645_DMIC_2_DP_MASK, RT5645_DMIC_2_DP_IN2P); + break; + + case RT5645_DMIC_DATA_GPIO6: + regmap_update_bits(rt5645->regmap, RT5645_DMIC_CTRL1, + RT5645_DMIC_2_DP_MASK, RT5645_DMIC_2_DP_GPIO6); + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP6_PIN_MASK, RT5645_GP6_PIN_DMIC2_SDA); + break; + + case RT5645_DMIC_DATA_GPIO10: + regmap_update_bits(rt5645->regmap, RT5645_DMIC_CTRL1, + RT5645_DMIC_2_DP_MASK, RT5645_DMIC_2_DP_GPIO10); + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP10_PIN_MASK, + RT5645_GP10_PIN_DMIC2_SDA); + break; + + case RT5645_DMIC_DATA_GPIO12: + regmap_update_bits(rt5645->regmap, RT5645_DMIC_CTRL1, + RT5645_DMIC_2_DP_MASK, RT5645_DMIC_2_DP_GPIO12); + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP12_PIN_MASK, + RT5645_GP12_PIN_DMIC2_SDA); + break; + + default: + break; + } + + if (rt5645->pdata.jd_mode) { + regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL3, + RT5645_IRQ_CLK_GATE_CTRL, + RT5645_IRQ_CLK_GATE_CTRL); + regmap_update_bits(rt5645->regmap, RT5645_MICBIAS, + RT5645_IRQ_CLK_INT, RT5645_IRQ_CLK_INT); + regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2, + RT5645_IRQ_JD_1_1_EN, RT5645_IRQ_JD_1_1_EN); + regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL3, + RT5645_JD_PSV_MODE, RT5645_JD_PSV_MODE); + regmap_update_bits(rt5645->regmap, RT5645_HPO_MIXER, + RT5645_IRQ_PSV_MODE, RT5645_IRQ_PSV_MODE); + regmap_update_bits(rt5645->regmap, RT5645_MICBIAS, + RT5645_MIC2_OVCD_EN, RT5645_MIC2_OVCD_EN); + regmap_update_bits(rt5645->regmap, RT5645_GPIO_CTRL1, + RT5645_GP1_PIN_IRQ, RT5645_GP1_PIN_IRQ); + switch (rt5645->pdata.jd_mode) { + case 1: + regmap_update_bits(rt5645->regmap, RT5645_A_JD_CTRL1, + RT5645_JD1_MODE_MASK, + RT5645_JD1_MODE_0); + break; + case 2: + regmap_update_bits(rt5645->regmap, RT5645_A_JD_CTRL1, + RT5645_JD1_MODE_MASK, + RT5645_JD1_MODE_1); + break; + case 3: + case 4: + regmap_update_bits(rt5645->regmap, RT5645_A_JD_CTRL1, + RT5645_JD1_MODE_MASK, + RT5645_JD1_MODE_2); + break; + default: + break; + } + if (rt5645->pdata.inv_jd1_1) { + regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2, + RT5645_JD_1_1_MASK, RT5645_JD_1_1_INV); + } + } + + regmap_update_bits(rt5645->regmap, RT5645_ADDA_CLK1, + RT5645_I2S_PD1_MASK, RT5645_I2S_PD1_2); + + if (rt5645->pdata.level_trigger_irq) { + regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2, + RT5645_JD_1_1_MASK, RT5645_JD_1_1_INV); + } + timer_setup(&rt5645->btn_check_timer, rt5645_btn_check_callback, 0); + + INIT_DELAYED_WORK(&rt5645->jack_detect_work, rt5645_jack_detect_work); + INIT_DELAYED_WORK(&rt5645->rcclock_work, rt5645_rcclock_work); + + if (rt5645->i2c->irq) { + ret = request_threaded_irq(rt5645->i2c->irq, NULL, rt5645_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5645", rt5645); + if (ret) { + dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); + goto err_enable; + } + } + + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_rt5645, + rt5645_dai, ARRAY_SIZE(rt5645_dai)); + if (ret) + goto err_irq; + + return 0; + +err_irq: + if (rt5645->i2c->irq) + free_irq(rt5645->i2c->irq, rt5645); +err_enable: + regulator_bulk_disable(ARRAY_SIZE(rt5645->supplies), rt5645->supplies); + return ret; +} + +static int rt5645_i2c_remove(struct i2c_client *i2c) +{ + struct rt5645_priv *rt5645 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, rt5645); + + /* + * Since the rt5645_btn_check_callback() can queue jack_detect_work, + * the timer need to be delted first + */ + del_timer_sync(&rt5645->btn_check_timer); + + cancel_delayed_work_sync(&rt5645->jack_detect_work); + cancel_delayed_work_sync(&rt5645->rcclock_work); + + regulator_bulk_disable(ARRAY_SIZE(rt5645->supplies), rt5645->supplies); + + return 0; +} + +static void rt5645_i2c_shutdown(struct i2c_client *i2c) +{ + struct rt5645_priv *rt5645 = i2c_get_clientdata(i2c); + + regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL3, + RT5645_RING2_SLEEVE_GND, RT5645_RING2_SLEEVE_GND); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2, RT5645_CBJ_MN_JD, + RT5645_CBJ_MN_JD); + regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL1, RT5645_CBJ_BST1_EN, + 0); + msleep(20); + regmap_write(rt5645->regmap, RT5645_RESET, 0); +} + +static struct i2c_driver rt5645_i2c_driver = { + .driver = { + .name = "rt5645", + .of_match_table = of_match_ptr(rt5645_of_match), + .acpi_match_table = ACPI_PTR(rt5645_acpi_match), + }, + .probe = rt5645_i2c_probe, + .remove = rt5645_i2c_remove, + .shutdown = rt5645_i2c_shutdown, + .id_table = rt5645_i2c_id, +}; +module_i2c_driver(rt5645_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5645 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h new file mode 100644 index 000000000..cc2455768 --- /dev/null +++ b/sound/soc/codecs/rt5645.h @@ -0,0 +1,2209 @@ +/* + * rt5645.h -- RT5645 ALSA SoC audio driver + * + * Copyright 2013 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5645_H__ +#define __RT5645_H__ + +#include + +/* Info */ +#define RT5645_RESET 0x00 +#define RT5645_VENDOR_ID 0xfd +#define RT5645_VENDOR_ID1 0xfe +#define RT5645_VENDOR_ID2 0xff +/* I/O - Output */ +#define RT5645_SPK_VOL 0x01 +#define RT5645_HP_VOL 0x02 +#define RT5645_LOUT1 0x03 +#define RT5645_LOUT_CTRL 0x05 +/* I/O - Input */ +#define RT5645_IN1_CTRL1 0x0a +#define RT5645_IN1_CTRL2 0x0b +#define RT5645_IN1_CTRL3 0x0c +#define RT5645_IN2_CTRL 0x0d +#define RT5645_INL1_INR1_VOL 0x0f +#define RT5645_SPK_FUNC_LIM 0x14 +#define RT5645_ADJ_HPF_CTRL 0x16 +/* I/O - ADC/DAC/DMIC */ +#define RT5645_DAC1_DIG_VOL 0x19 +#define RT5645_DAC2_DIG_VOL 0x1a +#define RT5645_DAC_CTRL 0x1b +#define RT5645_STO1_ADC_DIG_VOL 0x1c +#define RT5645_MONO_ADC_DIG_VOL 0x1d +#define RT5645_ADC_BST_VOL1 0x1e +#define RT5645_ADC_BST_VOL2 0x20 +/* Mixer - D-D */ +#define RT5645_STO1_ADC_MIXER 0x27 +#define RT5645_MONO_ADC_MIXER 0x28 +#define RT5645_AD_DA_MIXER 0x29 +#define RT5645_STO_DAC_MIXER 0x2a +#define RT5645_MONO_DAC_MIXER 0x2b +#define RT5645_DIG_MIXER 0x2c +#define RT5650_A_DAC_SOUR 0x2d +#define RT5645_DIG_INF1_DATA 0x2f +/* Mixer - PDM */ +#define RT5645_PDM_OUT_CTRL 0x31 +/* Mixer - ADC */ +#define RT5645_REC_L1_MIXER 0x3b +#define RT5645_REC_L2_MIXER 0x3c +#define RT5645_REC_R1_MIXER 0x3d +#define RT5645_REC_R2_MIXER 0x3e +/* Mixer - DAC */ +#define RT5645_HPMIXL_CTRL 0x3f +#define RT5645_HPOMIXL_CTRL 0x40 +#define RT5645_HPMIXR_CTRL 0x41 +#define RT5645_HPOMIXR_CTRL 0x42 +#define RT5645_HPO_MIXER 0x45 +#define RT5645_SPK_L_MIXER 0x46 +#define RT5645_SPK_R_MIXER 0x47 +#define RT5645_SPO_MIXER 0x48 +#define RT5645_SPO_CLSD_RATIO 0x4a +#define RT5645_OUT_L_GAIN1 0x4d +#define RT5645_OUT_L_GAIN2 0x4e +#define RT5645_OUT_L1_MIXER 0x4f +#define RT5645_OUT_R_GAIN1 0x50 +#define RT5645_OUT_R_GAIN2 0x51 +#define RT5645_OUT_R1_MIXER 0x52 +#define RT5645_LOUT_MIXER 0x53 +/* Haptic */ +#define RT5645_HAPTIC_CTRL1 0x56 +#define RT5645_HAPTIC_CTRL2 0x57 +#define RT5645_HAPTIC_CTRL3 0x58 +#define RT5645_HAPTIC_CTRL4 0x59 +#define RT5645_HAPTIC_CTRL5 0x5a +#define RT5645_HAPTIC_CTRL6 0x5b +#define RT5645_HAPTIC_CTRL7 0x5c +#define RT5645_HAPTIC_CTRL8 0x5d +#define RT5645_HAPTIC_CTRL9 0x5e +#define RT5645_HAPTIC_CTRL10 0x5f +/* Power */ +#define RT5645_PWR_DIG1 0x61 +#define RT5645_PWR_DIG2 0x62 +#define RT5645_PWR_ANLG1 0x63 +#define RT5645_PWR_ANLG2 0x64 +#define RT5645_PWR_MIXER 0x65 +#define RT5645_PWR_VOL 0x66 +/* Private Register Control */ +#define RT5645_PRIV_INDEX 0x6a +#define RT5645_PRIV_DATA 0x6c +/* Format - ADC/DAC */ +#define RT5645_I2S1_SDP 0x70 +#define RT5645_I2S2_SDP 0x71 +#define RT5645_ADDA_CLK1 0x73 +#define RT5645_ADDA_CLK2 0x74 +#define RT5645_DMIC_CTRL1 0x75 +#define RT5645_DMIC_CTRL2 0x76 +/* Format - TDM Control */ +#define RT5645_TDM_CTRL_1 0x77 +#define RT5645_TDM_CTRL_2 0x78 +#define RT5645_TDM_CTRL_3 0x79 +#define RT5650_TDM_CTRL_4 0x7a + +/* Function - Analog */ +#define RT5645_GLB_CLK 0x80 +#define RT5645_PLL_CTRL1 0x81 +#define RT5645_PLL_CTRL2 0x82 +#define RT5645_ASRC_1 0x83 +#define RT5645_ASRC_2 0x84 +#define RT5645_ASRC_3 0x85 +#define RT5645_ASRC_4 0x8a +#define RT5645_DEPOP_M1 0x8e +#define RT5645_DEPOP_M2 0x8f +#define RT5645_DEPOP_M3 0x90 +#define RT5645_CHARGE_PUMP 0x91 +#define RT5645_MICBIAS 0x93 +#define RT5645_A_JD_CTRL1 0x94 +#define RT5645_VAD_CTRL4 0x9d +#define RT5645_CLSD_OUT_CTRL 0xa0 +/* Function - Digital */ +#define RT5645_ADC_EQ_CTRL1 0xae +#define RT5645_ADC_EQ_CTRL2 0xaf +#define RT5645_EQ_CTRL1 0xb0 +#define RT5645_EQ_CTRL2 0xb1 +#define RT5645_ALC_CTRL_1 0xb3 +#define RT5645_ALC_CTRL_2 0xb4 +#define RT5645_ALC_CTRL_3 0xb5 +#define RT5645_ALC_CTRL_4 0xb6 +#define RT5645_ALC_CTRL_5 0xb7 +#define RT5645_JD_CTRL 0xbb +#define RT5645_IRQ_CTRL1 0xbc +#define RT5645_IRQ_CTRL2 0xbd +#define RT5645_IRQ_CTRL3 0xbe +#define RT5645_INT_IRQ_ST 0xbf +#define RT5645_GPIO_CTRL1 0xc0 +#define RT5645_GPIO_CTRL2 0xc1 +#define RT5645_GPIO_CTRL3 0xc2 +#define RT5645_BASS_BACK 0xcf +#define RT5645_MP3_PLUS1 0xd0 +#define RT5645_MP3_PLUS2 0xd1 +#define RT5645_ADJ_HPF1 0xd3 +#define RT5645_ADJ_HPF2 0xd4 +#define RT5645_HP_CALIB_AMP_DET 0xd6 +#define RT5645_SV_ZCD1 0xd9 +#define RT5645_SV_ZCD2 0xda +#define RT5645_IL_CMD 0xdb +#define RT5645_IL_CMD2 0xdc +#define RT5645_IL_CMD3 0xdd +#define RT5650_4BTN_IL_CMD1 0xdf +#define RT5650_4BTN_IL_CMD2 0xe0 +#define RT5645_DRC1_HL_CTRL1 0xe7 +#define RT5645_DRC2_HL_CTRL1 0xe9 +#define RT5645_MUTI_DRC_CTRL1 0xea +#define RT5645_ADC_MONO_HP_CTRL1 0xec +#define RT5645_ADC_MONO_HP_CTRL2 0xed +#define RT5645_DRC2_CTRL1 0xf0 +#define RT5645_DRC2_CTRL2 0xf1 +#define RT5645_DRC2_CTRL3 0xf2 +#define RT5645_DRC2_CTRL4 0xf3 +#define RT5645_DRC2_CTRL5 0xf4 +#define RT5645_JD_CTRL3 0xf8 +#define RT5645_JD_CTRL4 0xf9 +/* General Control */ +#define RT5645_GEN_CTRL1 0xfa +#define RT5645_GEN_CTRL2 0xfb +#define RT5645_GEN_CTRL3 0xfc + + +/* Index of Codec Private Register definition */ +#define RT5645_DIG_VOL 0x00 +#define RT5645_PR_ALC_CTRL_1 0x01 +#define RT5645_PR_ALC_CTRL_2 0x02 +#define RT5645_PR_ALC_CTRL_3 0x03 +#define RT5645_PR_ALC_CTRL_4 0x04 +#define RT5645_PR_ALC_CTRL_5 0x05 +#define RT5645_PR_ALC_CTRL_6 0x06 +#define RT5645_BIAS_CUR1 0x12 +#define RT5645_BIAS_CUR3 0x14 +#define RT5645_CLSD_INT_REG1 0x1c +#define RT5645_MAMP_INT_REG2 0x37 +#define RT5645_CHOP_DAC_ADC 0x3d +#define RT5645_MIXER_INT_REG 0x3f +#define RT5645_3D_SPK 0x63 +#define RT5645_WND_1 0x6c +#define RT5645_WND_2 0x6d +#define RT5645_WND_3 0x6e +#define RT5645_WND_4 0x6f +#define RT5645_WND_5 0x70 +#define RT5645_WND_8 0x73 +#define RT5645_DIP_SPK_INF 0x75 +#define RT5645_HP_DCC_INT1 0x77 +#define RT5645_EQ_BW_LOP 0xa0 +#define RT5645_EQ_GN_LOP 0xa1 +#define RT5645_EQ_FC_BP1 0xa2 +#define RT5645_EQ_BW_BP1 0xa3 +#define RT5645_EQ_GN_BP1 0xa4 +#define RT5645_EQ_FC_BP2 0xa5 +#define RT5645_EQ_BW_BP2 0xa6 +#define RT5645_EQ_GN_BP2 0xa7 +#define RT5645_EQ_FC_BP3 0xa8 +#define RT5645_EQ_BW_BP3 0xa9 +#define RT5645_EQ_GN_BP3 0xaa +#define RT5645_EQ_FC_BP4 0xab +#define RT5645_EQ_BW_BP4 0xac +#define RT5645_EQ_GN_BP4 0xad +#define RT5645_EQ_FC_HIP1 0xae +#define RT5645_EQ_GN_HIP1 0xaf +#define RT5645_EQ_FC_HIP2 0xb0 +#define RT5645_EQ_BW_HIP2 0xb1 +#define RT5645_EQ_GN_HIP2 0xb2 +#define RT5645_EQ_PRE_VOL 0xb3 +#define RT5645_EQ_PST_VOL 0xb4 + + +/* global definition */ +#define RT5645_L_MUTE (0x1 << 15) +#define RT5645_L_MUTE_SFT 15 +#define RT5645_VOL_L_MUTE (0x1 << 14) +#define RT5645_VOL_L_SFT 14 +#define RT5645_R_MUTE (0x1 << 7) +#define RT5645_R_MUTE_SFT 7 +#define RT5645_VOL_R_MUTE (0x1 << 6) +#define RT5645_VOL_R_SFT 6 +#define RT5645_L_VOL_MASK (0x3f << 8) +#define RT5645_L_VOL_SFT 8 +#define RT5645_R_VOL_MASK (0x3f) +#define RT5645_R_VOL_SFT 0 + +/* IN1 Control 1 (0x0a) */ +#define RT5645_CBJ_BST1_MASK (0xf << 12) +#define RT5645_CBJ_BST1_SFT (12) +#define RT5645_CBJ_JD_HP_EN (0x1 << 9) +#define RT5645_CBJ_JD_MIC_EN (0x1 << 8) +#define RT5645_CBJ_JD_MIC_SW_EN (0x1 << 7) +#define RT5645_CBJ_MIC_SEL_R (0x1 << 6) +#define RT5645_CBJ_MIC_SEL_L (0x1 << 5) +#define RT5645_CBJ_MIC_SW (0x1 << 4) +#define RT5645_CBJ_BST1_EN (0x1 << 2) + +/* IN1 Control 2 (0x0b) */ +#define RT5645_CBJ_MN_JD (0x1 << 12) +#define RT5645_CAPLESS_EN (0x1 << 11) +#define RT5645_CBJ_DET_MODE (0x1 << 7) + +/* IN1 Control 3 (0x0c) */ +#define RT5645_CBJ_TIE_G_L (0x1 << 15) +#define RT5645_CBJ_TIE_G_R (0x1 << 14) + +/* IN2 Control (0x0d) */ +#define RT5645_BST_MASK1 (0xf<<12) +#define RT5645_BST_SFT1 12 +#define RT5645_BST_MASK2 (0xf<<8) +#define RT5645_BST_SFT2 8 +#define RT5645_IN_DF2 (0x1 << 6) +#define RT5645_IN_SFT2 6 + +/* INL and INR Volume Control (0x0f) */ +#define RT5645_INL_SEL_MASK (0x1 << 15) +#define RT5645_INL_SEL_SFT 15 +#define RT5645_INL_SEL_IN4P (0x0 << 15) +#define RT5645_INL_SEL_MONOP (0x1 << 15) +#define RT5645_INL_VOL_MASK (0x1f << 8) +#define RT5645_INL_VOL_SFT 8 +#define RT5645_INR_SEL_MASK (0x1 << 7) +#define RT5645_INR_SEL_SFT 7 +#define RT5645_INR_SEL_IN4N (0x0 << 7) +#define RT5645_INR_SEL_MONON (0x1 << 7) +#define RT5645_INR_VOL_MASK (0x1f) +#define RT5645_INR_VOL_SFT 0 + +/* DAC1 Digital Volume (0x19) */ +#define RT5645_DAC_L1_VOL_MASK (0xff << 8) +#define RT5645_DAC_L1_VOL_SFT 8 +#define RT5645_DAC_R1_VOL_MASK (0xff) +#define RT5645_DAC_R1_VOL_SFT 0 + +/* DAC2 Digital Volume (0x1a) */ +#define RT5645_DAC_L2_VOL_MASK (0xff << 8) +#define RT5645_DAC_L2_VOL_SFT 8 +#define RT5645_DAC_R2_VOL_MASK (0xff) +#define RT5645_DAC_R2_VOL_SFT 0 + +/* DAC2 Control (0x1b) */ +#define RT5645_M_DAC_L2_VOL (0x1 << 13) +#define RT5645_M_DAC_L2_VOL_SFT 13 +#define RT5645_M_DAC_R2_VOL (0x1 << 12) +#define RT5645_M_DAC_R2_VOL_SFT 12 +#define RT5645_DAC2_L_SEL_MASK (0x7 << 4) +#define RT5645_DAC2_L_SEL_SFT 4 +#define RT5645_DAC2_R_SEL_MASK (0x7 << 0) +#define RT5645_DAC2_R_SEL_SFT 0 + +/* ADC Digital Volume Control (0x1c) */ +#define RT5645_ADC_L_VOL_MASK (0x7f << 8) +#define RT5645_ADC_L_VOL_SFT 8 +#define RT5645_ADC_R_VOL_MASK (0x7f) +#define RT5645_ADC_R_VOL_SFT 0 + +/* Mono ADC Digital Volume Control (0x1d) */ +#define RT5645_MONO_ADC_L_VOL_MASK (0x7f << 8) +#define RT5645_MONO_ADC_L_VOL_SFT 8 +#define RT5645_MONO_ADC_R_VOL_MASK (0x7f) +#define RT5645_MONO_ADC_R_VOL_SFT 0 + +/* ADC Boost Volume Control (0x1e) */ +#define RT5645_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5645_STO1_ADC_L_BST_SFT 14 +#define RT5645_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5645_STO1_ADC_R_BST_SFT 12 +#define RT5645_STO1_ADC_COMP_MASK (0x3 << 10) +#define RT5645_STO1_ADC_COMP_SFT 10 + +/* ADC Boost Volume Control (0x20) */ +#define RT5645_MONO_ADC_L_BST_MASK (0x3 << 14) +#define RT5645_MONO_ADC_L_BST_SFT 14 +#define RT5645_MONO_ADC_R_BST_MASK (0x3 << 12) +#define RT5645_MONO_ADC_R_BST_SFT 12 +#define RT5645_MONO_ADC_COMP_MASK (0x3 << 10) +#define RT5645_MONO_ADC_COMP_SFT 10 + +/* Stereo2 ADC Mixer Control (0x26) */ +#define RT5645_STO2_ADC_SRC_MASK (0x1 << 15) +#define RT5645_STO2_ADC_SRC_SFT 15 + +/* Stereo ADC Mixer Control (0x27) */ +#define RT5645_M_ADC_L1 (0x1 << 14) +#define RT5645_M_ADC_L1_SFT 14 +#define RT5645_M_ADC_L2 (0x1 << 13) +#define RT5645_M_ADC_L2_SFT 13 +#define RT5645_ADC_1_SRC_MASK (0x1 << 12) +#define RT5645_ADC_1_SRC_SFT 12 +#define RT5645_ADC_1_SRC_ADC (0x1 << 12) +#define RT5645_ADC_1_SRC_DACMIX (0x0 << 12) +#define RT5645_ADC_2_SRC_MASK (0x1 << 11) +#define RT5645_ADC_2_SRC_SFT 11 +#define RT5645_DMIC_SRC_MASK (0x1 << 8) +#define RT5645_DMIC_SRC_SFT 8 +#define RT5645_M_ADC_R1 (0x1 << 6) +#define RT5645_M_ADC_R1_SFT 6 +#define RT5645_M_ADC_R2 (0x1 << 5) +#define RT5645_M_ADC_R2_SFT 5 +#define RT5645_DMIC3_SRC_MASK (0x1 << 1) +#define RT5645_DMIC3_SRC_SFT 0 + +/* Mono ADC Mixer Control (0x28) */ +#define RT5645_M_MONO_ADC_L1 (0x1 << 14) +#define RT5645_M_MONO_ADC_L1_SFT 14 +#define RT5645_M_MONO_ADC_L2 (0x1 << 13) +#define RT5645_M_MONO_ADC_L2_SFT 13 +#define RT5645_MONO_ADC_L1_SRC_MASK (0x1 << 12) +#define RT5645_MONO_ADC_L1_SRC_SFT 12 +#define RT5645_MONO_ADC_L1_SRC_DACMIXL (0x0 << 12) +#define RT5645_MONO_ADC_L1_SRC_ADCL (0x1 << 12) +#define RT5645_MONO_ADC_L2_SRC_MASK (0x1 << 11) +#define RT5645_MONO_ADC_L2_SRC_SFT 11 +#define RT5645_MONO_DMIC_L_SRC_MASK (0x1 << 8) +#define RT5645_MONO_DMIC_L_SRC_SFT 8 +#define RT5645_M_MONO_ADC_R1 (0x1 << 6) +#define RT5645_M_MONO_ADC_R1_SFT 6 +#define RT5645_M_MONO_ADC_R2 (0x1 << 5) +#define RT5645_M_MONO_ADC_R2_SFT 5 +#define RT5645_MONO_ADC_R1_SRC_MASK (0x1 << 4) +#define RT5645_MONO_ADC_R1_SRC_SFT 4 +#define RT5645_MONO_ADC_R1_SRC_ADCR (0x1 << 4) +#define RT5645_MONO_ADC_R1_SRC_DACMIXR (0x0 << 4) +#define RT5645_MONO_ADC_R2_SRC_MASK (0x1 << 3) +#define RT5645_MONO_ADC_R2_SRC_SFT 3 +#define RT5645_MONO_DMIC_R_SRC_MASK (0x3) +#define RT5645_MONO_DMIC_R_SRC_SFT 0 + +/* ADC Mixer to DAC Mixer Control (0x29) */ +#define RT5645_M_ADCMIX_L (0x1 << 15) +#define RT5645_M_ADCMIX_L_SFT 15 +#define RT5645_M_DAC1_L (0x1 << 14) +#define RT5645_M_DAC1_L_SFT 14 +#define RT5645_DAC1_R_SEL_MASK (0x3 << 10) +#define RT5645_DAC1_R_SEL_SFT 10 +#define RT5645_DAC1_R_SEL_IF1 (0x0 << 10) +#define RT5645_DAC1_R_SEL_IF2 (0x1 << 10) +#define RT5645_DAC1_R_SEL_IF3 (0x2 << 10) +#define RT5645_DAC1_R_SEL_IF4 (0x3 << 10) +#define RT5645_DAC1_L_SEL_MASK (0x3 << 8) +#define RT5645_DAC1_L_SEL_SFT 8 +#define RT5645_DAC1_L_SEL_IF1 (0x0 << 8) +#define RT5645_DAC1_L_SEL_IF2 (0x1 << 8) +#define RT5645_DAC1_L_SEL_IF3 (0x2 << 8) +#define RT5645_DAC1_L_SEL_IF4 (0x3 << 8) +#define RT5645_M_ADCMIX_R (0x1 << 7) +#define RT5645_M_ADCMIX_R_SFT 7 +#define RT5645_M_DAC1_R (0x1 << 6) +#define RT5645_M_DAC1_R_SFT 6 + +/* Stereo DAC Mixer Control (0x2a) */ +#define RT5645_M_DAC_L1 (0x1 << 14) +#define RT5645_M_DAC_L1_SFT 14 +#define RT5645_DAC_L1_STO_L_VOL_MASK (0x1 << 13) +#define RT5645_DAC_L1_STO_L_VOL_SFT 13 +#define RT5645_M_DAC_L2 (0x1 << 12) +#define RT5645_M_DAC_L2_SFT 12 +#define RT5645_DAC_L2_STO_L_VOL_MASK (0x1 << 11) +#define RT5645_DAC_L2_STO_L_VOL_SFT 11 +#define RT5645_M_ANC_DAC_L (0x1 << 10) +#define RT5645_M_ANC_DAC_L_SFT 10 +#define RT5645_M_DAC_R1_STO_L (0x1 << 9) +#define RT5645_M_DAC_R1_STO_L_SFT 9 +#define RT5645_DAC_R1_STO_L_VOL_MASK (0x1 << 8) +#define RT5645_DAC_R1_STO_L_VOL_SFT 8 +#define RT5645_M_DAC_R1 (0x1 << 6) +#define RT5645_M_DAC_R1_SFT 6 +#define RT5645_DAC_R1_STO_R_VOL_MASK (0x1 << 5) +#define RT5645_DAC_R1_STO_R_VOL_SFT 5 +#define RT5645_M_DAC_R2 (0x1 << 4) +#define RT5645_M_DAC_R2_SFT 4 +#define RT5645_DAC_R2_STO_R_VOL_MASK (0x1 << 3) +#define RT5645_DAC_R2_STO_R_VOL_SFT 3 +#define RT5645_M_ANC_DAC_R (0x1 << 2) +#define RT5645_M_ANC_DAC_R_SFT 2 +#define RT5645_M_DAC_L1_STO_R (0x1 << 1) +#define RT5645_M_DAC_L1_STO_R_SFT 1 +#define RT5645_DAC_L1_STO_R_VOL_MASK (0x1) +#define RT5645_DAC_L1_STO_R_VOL_SFT 0 + +/* Mono DAC Mixer Control (0x2b) */ +#define RT5645_M_DAC_L1_MONO_L (0x1 << 14) +#define RT5645_M_DAC_L1_MONO_L_SFT 14 +#define RT5645_DAC_L1_MONO_L_VOL_MASK (0x1 << 13) +#define RT5645_DAC_L1_MONO_L_VOL_SFT 13 +#define RT5645_M_DAC_L2_MONO_L (0x1 << 12) +#define RT5645_M_DAC_L2_MONO_L_SFT 12 +#define RT5645_DAC_L2_MONO_L_VOL_MASK (0x1 << 11) +#define RT5645_DAC_L2_MONO_L_VOL_SFT 11 +#define RT5645_M_DAC_R2_MONO_L (0x1 << 10) +#define RT5645_M_DAC_R2_MONO_L_SFT 10 +#define RT5645_DAC_R2_MONO_L_VOL_MASK (0x1 << 9) +#define RT5645_DAC_R2_MONO_L_VOL_SFT 9 +#define RT5645_M_DAC_R1_MONO_R (0x1 << 6) +#define RT5645_M_DAC_R1_MONO_R_SFT 6 +#define RT5645_DAC_R1_MONO_R_VOL_MASK (0x1 << 5) +#define RT5645_DAC_R1_MONO_R_VOL_SFT 5 +#define RT5645_M_DAC_R2_MONO_R (0x1 << 4) +#define RT5645_M_DAC_R2_MONO_R_SFT 4 +#define RT5645_DAC_R2_MONO_R_VOL_MASK (0x1 << 3) +#define RT5645_DAC_R2_MONO_R_VOL_SFT 3 +#define RT5645_M_DAC_L2_MONO_R (0x1 << 2) +#define RT5645_M_DAC_L2_MONO_R_SFT 2 +#define RT5645_DAC_L2_MONO_R_VOL_MASK (0x1 << 1) +#define RT5645_DAC_L2_MONO_R_VOL_SFT 1 + +/* Digital Mixer Control (0x2c) */ +#define RT5645_M_STO_L_DAC_L (0x1 << 15) +#define RT5645_M_STO_L_DAC_L_SFT 15 +#define RT5645_STO_L_DAC_L_VOL_MASK (0x1 << 14) +#define RT5645_STO_L_DAC_L_VOL_SFT 14 +#define RT5645_M_DAC_L2_DAC_L (0x1 << 13) +#define RT5645_M_DAC_L2_DAC_L_SFT 13 +#define RT5645_DAC_L2_DAC_L_VOL_MASK (0x1 << 12) +#define RT5645_DAC_L2_DAC_L_VOL_SFT 12 +#define RT5645_M_STO_R_DAC_R (0x1 << 11) +#define RT5645_M_STO_R_DAC_R_SFT 11 +#define RT5645_STO_R_DAC_R_VOL_MASK (0x1 << 10) +#define RT5645_STO_R_DAC_R_VOL_SFT 10 +#define RT5645_M_DAC_R2_DAC_R (0x1 << 9) +#define RT5645_M_DAC_R2_DAC_R_SFT 9 +#define RT5645_DAC_R2_DAC_R_VOL_MASK (0x1 << 8) +#define RT5645_DAC_R2_DAC_R_VOL_SFT 8 +#define RT5645_M_DAC_R2_DAC_L (0x1 << 7) +#define RT5645_M_DAC_R2_DAC_L_SFT 7 +#define RT5645_DAC_R2_DAC_L_VOL_MASK (0x1 << 6) +#define RT5645_DAC_R2_DAC_L_VOL_SFT 6 +#define RT5645_M_DAC_L2_DAC_R (0x1 << 5) +#define RT5645_M_DAC_L2_DAC_R_SFT 5 +#define RT5645_DAC_L2_DAC_R_VOL_MASK (0x1 << 4) +#define RT5645_DAC_L2_DAC_R_VOL_SFT 4 + +/* Analog DAC1/2 Input Source Control (0x2d) */ +#define RT5650_A_DAC1_L_IN_SFT 3 +#define RT5650_A_DAC1_R_IN_SFT 2 +#define RT5650_A_DAC2_L_IN_SFT 1 +#define RT5650_A_DAC2_R_IN_SFT 0 + +/* Digital Interface Data Control (0x2f) */ +#define RT5645_IF1_ADC2_IN_SEL (0x1 << 15) +#define RT5645_IF1_ADC2_IN_SFT 15 +#define RT5645_IF2_ADC_IN_MASK (0x7 << 12) +#define RT5645_IF2_ADC_IN_SFT 12 +#define RT5645_IF2_DAC_SEL_MASK (0x3 << 10) +#define RT5645_IF2_DAC_SEL_SFT 10 +#define RT5645_IF2_ADC_SEL_MASK (0x3 << 8) +#define RT5645_IF2_ADC_SEL_SFT 8 +#define RT5645_IF3_DAC_SEL_MASK (0x3 << 6) +#define RT5645_IF3_DAC_SEL_SFT 6 +#define RT5645_IF3_ADC_SEL_MASK (0x3 << 4) +#define RT5645_IF3_ADC_SEL_SFT 4 +#define RT5645_IF3_ADC_IN_MASK (0x7) +#define RT5645_IF3_ADC_IN_SFT 0 + +/* PDM Output Control (0x31) */ +#define RT5645_PDM1_L_MASK (0x1 << 15) +#define RT5645_PDM1_L_SFT 15 +#define RT5645_M_PDM1_L (0x1 << 14) +#define RT5645_M_PDM1_L_SFT 14 +#define RT5645_PDM1_R_MASK (0x1 << 13) +#define RT5645_PDM1_R_SFT 13 +#define RT5645_M_PDM1_R (0x1 << 12) +#define RT5645_M_PDM1_R_SFT 12 +#define RT5645_PDM2_L_MASK (0x1 << 11) +#define RT5645_PDM2_L_SFT 11 +#define RT5645_M_PDM2_L (0x1 << 10) +#define RT5645_M_PDM2_L_SFT 10 +#define RT5645_PDM2_R_MASK (0x1 << 9) +#define RT5645_PDM2_R_SFT 9 +#define RT5645_M_PDM2_R (0x1 << 8) +#define RT5645_M_PDM2_R_SFT 8 +#define RT5645_PDM2_BUSY (0x1 << 7) +#define RT5645_PDM1_BUSY (0x1 << 6) +#define RT5645_PDM_PATTERN (0x1 << 5) +#define RT5645_PDM_GAIN (0x1 << 4) +#define RT5645_PDM_DIV_MASK (0x3) + +/* REC Left Mixer Control 1 (0x3b) */ +#define RT5645_G_HP_L_RM_L_MASK (0x7 << 13) +#define RT5645_G_HP_L_RM_L_SFT 13 +#define RT5645_G_IN_L_RM_L_MASK (0x7 << 10) +#define RT5645_G_IN_L_RM_L_SFT 10 +#define RT5645_G_BST4_RM_L_MASK (0x7 << 7) +#define RT5645_G_BST4_RM_L_SFT 7 +#define RT5645_G_BST3_RM_L_MASK (0x7 << 4) +#define RT5645_G_BST3_RM_L_SFT 4 +#define RT5645_G_BST2_RM_L_MASK (0x7 << 1) +#define RT5645_G_BST2_RM_L_SFT 1 + +/* REC Left Mixer Control 2 (0x3c) */ +#define RT5645_G_BST1_RM_L_MASK (0x7 << 13) +#define RT5645_G_BST1_RM_L_SFT 13 +#define RT5645_G_OM_L_RM_L_MASK (0x7 << 10) +#define RT5645_G_OM_L_RM_L_SFT 10 +#define RT5645_M_MM_L_RM_L (0x1 << 6) +#define RT5645_M_MM_L_RM_L_SFT 6 +#define RT5645_M_IN_L_RM_L (0x1 << 5) +#define RT5645_M_IN_L_RM_L_SFT 5 +#define RT5645_M_HP_L_RM_L (0x1 << 4) +#define RT5645_M_HP_L_RM_L_SFT 4 +#define RT5645_M_BST3_RM_L (0x1 << 3) +#define RT5645_M_BST3_RM_L_SFT 3 +#define RT5645_M_BST2_RM_L (0x1 << 2) +#define RT5645_M_BST2_RM_L_SFT 2 +#define RT5645_M_BST1_RM_L (0x1 << 1) +#define RT5645_M_BST1_RM_L_SFT 1 +#define RT5645_M_OM_L_RM_L (0x1) +#define RT5645_M_OM_L_RM_L_SFT 0 + +/* REC Right Mixer Control 1 (0x3d) */ +#define RT5645_G_HP_R_RM_R_MASK (0x7 << 13) +#define RT5645_G_HP_R_RM_R_SFT 13 +#define RT5645_G_IN_R_RM_R_MASK (0x7 << 10) +#define RT5645_G_IN_R_RM_R_SFT 10 +#define RT5645_G_BST4_RM_R_MASK (0x7 << 7) +#define RT5645_G_BST4_RM_R_SFT 7 +#define RT5645_G_BST3_RM_R_MASK (0x7 << 4) +#define RT5645_G_BST3_RM_R_SFT 4 +#define RT5645_G_BST2_RM_R_MASK (0x7 << 1) +#define RT5645_G_BST2_RM_R_SFT 1 + +/* REC Right Mixer Control 2 (0x3e) */ +#define RT5645_G_BST1_RM_R_MASK (0x7 << 13) +#define RT5645_G_BST1_RM_R_SFT 13 +#define RT5645_G_OM_R_RM_R_MASK (0x7 << 10) +#define RT5645_G_OM_R_RM_R_SFT 10 +#define RT5645_M_MM_R_RM_R (0x1 << 6) +#define RT5645_M_MM_R_RM_R_SFT 6 +#define RT5645_M_IN_R_RM_R (0x1 << 5) +#define RT5645_M_IN_R_RM_R_SFT 5 +#define RT5645_M_HP_R_RM_R (0x1 << 4) +#define RT5645_M_HP_R_RM_R_SFT 4 +#define RT5645_M_BST3_RM_R (0x1 << 3) +#define RT5645_M_BST3_RM_R_SFT 3 +#define RT5645_M_BST2_RM_R (0x1 << 2) +#define RT5645_M_BST2_RM_R_SFT 2 +#define RT5645_M_BST1_RM_R (0x1 << 1) +#define RT5645_M_BST1_RM_R_SFT 1 +#define RT5645_M_OM_R_RM_R (0x1) +#define RT5645_M_OM_R_RM_R_SFT 0 + +/* HPOMIX Control (0x40) (0x42) */ +#define RT5645_M_BST1_HV (0x1 << 4) +#define RT5645_M_BST1_HV_SFT 4 +#define RT5645_M_BST2_HV (0x1 << 4) +#define RT5645_M_BST2_HV_SFT 4 +#define RT5645_M_BST3_HV (0x1 << 3) +#define RT5645_M_BST3_HV_SFT 3 +#define RT5645_M_IN_HV (0x1 << 2) +#define RT5645_M_IN_HV_SFT 2 +#define RT5645_M_DAC2_HV (0x1 << 1) +#define RT5645_M_DAC2_HV_SFT 1 +#define RT5645_M_DAC1_HV (0x1 << 0) +#define RT5645_M_DAC1_HV_SFT 0 + +/* HPMIX Control (0x45) */ +#define RT5645_M_DAC1_HM (0x1 << 14) +#define RT5645_M_DAC1_HM_SFT 14 +#define RT5645_M_HPVOL_HM (0x1 << 13) +#define RT5645_M_HPVOL_HM_SFT 13 +#define RT5645_IRQ_PSV_MODE (0x1 << 12) + +/* SPK Left Mixer Control (0x46) */ +#define RT5645_G_RM_L_SM_L_MASK (0x3 << 14) +#define RT5645_G_RM_L_SM_L_SFT 14 +#define RT5645_G_IN_L_SM_L_MASK (0x3 << 12) +#define RT5645_G_IN_L_SM_L_SFT 12 +#define RT5645_G_DAC_L1_SM_L_MASK (0x3 << 10) +#define RT5645_G_DAC_L1_SM_L_SFT 10 +#define RT5645_G_DAC_L2_SM_L_MASK (0x3 << 8) +#define RT5645_G_DAC_L2_SM_L_SFT 8 +#define RT5645_G_OM_L_SM_L_MASK (0x3 << 6) +#define RT5645_G_OM_L_SM_L_SFT 6 +#define RT5645_M_BST1_L_SM_L (0x1 << 5) +#define RT5645_M_BST1_L_SM_L_SFT 5 +#define RT5645_M_BST3_L_SM_L (0x1 << 4) +#define RT5645_M_BST3_L_SM_L_SFT 4 +#define RT5645_M_IN_L_SM_L (0x1 << 3) +#define RT5645_M_IN_L_SM_L_SFT 3 +#define RT5645_M_DAC_L2_SM_L (0x1 << 2) +#define RT5645_M_DAC_L2_SM_L_SFT 2 +#define RT5645_M_DAC_L1_SM_L (0x1 << 1) +#define RT5645_M_DAC_L1_SM_L_SFT 1 + +/* SPK Right Mixer Control (0x47) */ +#define RT5645_G_RM_R_SM_R_MASK (0x3 << 14) +#define RT5645_G_RM_R_SM_R_SFT 14 +#define RT5645_G_IN_R_SM_R_MASK (0x3 << 12) +#define RT5645_G_IN_R_SM_R_SFT 12 +#define RT5645_G_DAC_R1_SM_R_MASK (0x3 << 10) +#define RT5645_G_DAC_R1_SM_R_SFT 10 +#define RT5645_G_DAC_R2_SM_R_MASK (0x3 << 8) +#define RT5645_G_DAC_R2_SM_R_SFT 8 +#define RT5645_G_OM_R_SM_R_MASK (0x3 << 6) +#define RT5645_G_OM_R_SM_R_SFT 6 +#define RT5645_M_BST2_R_SM_R (0x1 << 5) +#define RT5645_M_BST2_R_SM_R_SFT 5 +#define RT5645_M_BST3_R_SM_R (0x1 << 4) +#define RT5645_M_BST3_R_SM_R_SFT 4 +#define RT5645_M_IN_R_SM_R (0x1 << 3) +#define RT5645_M_IN_R_SM_R_SFT 3 +#define RT5645_M_DAC_R2_SM_R (0x1 << 2) +#define RT5645_M_DAC_R2_SM_R_SFT 2 +#define RT5645_M_DAC_R1_SM_R (0x1 << 1) +#define RT5645_M_DAC_R1_SM_R_SFT 1 + +/* SPOLMIX Control (0x48) */ +#define RT5645_M_DAC_L1_SPM_L (0x1 << 15) +#define RT5645_M_DAC_L1_SPM_L_SFT 15 +#define RT5645_M_DAC_R1_SPM_L (0x1 << 14) +#define RT5645_M_DAC_R1_SPM_L_SFT 14 +#define RT5645_M_SV_L_SPM_L (0x1 << 13) +#define RT5645_M_SV_L_SPM_L_SFT 13 +#define RT5645_M_SV_R_SPM_L (0x1 << 12) +#define RT5645_M_SV_R_SPM_L_SFT 12 +#define RT5645_M_BST3_SPM_L (0x1 << 11) +#define RT5645_M_BST3_SPM_L_SFT 11 +#define RT5645_M_DAC_R1_SPM_R (0x1 << 2) +#define RT5645_M_DAC_R1_SPM_R_SFT 2 +#define RT5645_M_BST3_SPM_R (0x1 << 1) +#define RT5645_M_BST3_SPM_R_SFT 1 +#define RT5645_M_SV_R_SPM_R (0x1 << 0) +#define RT5645_M_SV_R_SPM_R_SFT 0 + +/* SPOMIX Ratio Control (0x4a) */ +#define RT5645_SPK_G_CLSD_MASK (0x7 << 0) +#define RT5645_SPK_G_CLSD_SFT 0 + +/* Mono Output Mixer Control (0x4c) */ +#define RT5645_G_MONOMIX_MASK (0x1 << 10) +#define RT5645_G_MONOMIX_SFT 10 +#define RT5645_M_OV_L_MM (0x1 << 9) +#define RT5645_M_OV_L_MM_SFT 9 +#define RT5645_M_DAC_L2_MA (0x1 << 8) +#define RT5645_M_DAC_L2_MA_SFT 8 +#define RT5645_M_BST2_MM (0x1 << 4) +#define RT5645_M_BST2_MM_SFT 4 +#define RT5645_M_DAC_R1_MM (0x1 << 3) +#define RT5645_M_DAC_R1_MM_SFT 3 +#define RT5645_M_DAC_R2_MM (0x1 << 2) +#define RT5645_M_DAC_R2_MM_SFT 2 +#define RT5645_M_DAC_L2_MM (0x1 << 1) +#define RT5645_M_DAC_L2_MM_SFT 1 +#define RT5645_M_BST3_MM (0x1 << 0) +#define RT5645_M_BST3_MM_SFT 0 + +/* Output Left Mixer Control 1 (0x4d) */ +#define RT5645_G_BST3_OM_L_MASK (0x7 << 13) +#define RT5645_G_BST3_OM_L_SFT 13 +#define RT5645_G_BST2_OM_L_MASK (0x7 << 10) +#define RT5645_G_BST2_OM_L_SFT 10 +#define RT5645_G_BST1_OM_L_MASK (0x7 << 7) +#define RT5645_G_BST1_OM_L_SFT 7 +#define RT5645_G_IN_L_OM_L_MASK (0x7 << 4) +#define RT5645_G_IN_L_OM_L_SFT 4 +#define RT5645_G_RM_L_OM_L_MASK (0x7 << 1) +#define RT5645_G_RM_L_OM_L_SFT 1 + +/* Output Left Mixer Control 2 (0x4e) */ +#define RT5645_G_DAC_R2_OM_L_MASK (0x7 << 13) +#define RT5645_G_DAC_R2_OM_L_SFT 13 +#define RT5645_G_DAC_L2_OM_L_MASK (0x7 << 10) +#define RT5645_G_DAC_L2_OM_L_SFT 10 +#define RT5645_G_DAC_L1_OM_L_MASK (0x7 << 7) +#define RT5645_G_DAC_L1_OM_L_SFT 7 + +/* Output Left Mixer Control 3 (0x4f) */ +#define RT5645_M_BST3_OM_L (0x1 << 4) +#define RT5645_M_BST3_OM_L_SFT 4 +#define RT5645_M_BST1_OM_L (0x1 << 3) +#define RT5645_M_BST1_OM_L_SFT 3 +#define RT5645_M_IN_L_OM_L (0x1 << 2) +#define RT5645_M_IN_L_OM_L_SFT 2 +#define RT5645_M_DAC_L2_OM_L (0x1 << 1) +#define RT5645_M_DAC_L2_OM_L_SFT 1 +#define RT5645_M_DAC_L1_OM_L (0x1) +#define RT5645_M_DAC_L1_OM_L_SFT 0 + +/* Output Right Mixer Control 1 (0x50) */ +#define RT5645_G_BST4_OM_R_MASK (0x7 << 13) +#define RT5645_G_BST4_OM_R_SFT 13 +#define RT5645_G_BST2_OM_R_MASK (0x7 << 10) +#define RT5645_G_BST2_OM_R_SFT 10 +#define RT5645_G_BST1_OM_R_MASK (0x7 << 7) +#define RT5645_G_BST1_OM_R_SFT 7 +#define RT5645_G_IN_R_OM_R_MASK (0x7 << 4) +#define RT5645_G_IN_R_OM_R_SFT 4 +#define RT5645_G_RM_R_OM_R_MASK (0x7 << 1) +#define RT5645_G_RM_R_OM_R_SFT 1 + +/* Output Right Mixer Control 2 (0x51) */ +#define RT5645_G_DAC_L2_OM_R_MASK (0x7 << 13) +#define RT5645_G_DAC_L2_OM_R_SFT 13 +#define RT5645_G_DAC_R2_OM_R_MASK (0x7 << 10) +#define RT5645_G_DAC_R2_OM_R_SFT 10 +#define RT5645_G_DAC_R1_OM_R_MASK (0x7 << 7) +#define RT5645_G_DAC_R1_OM_R_SFT 7 + +/* Output Right Mixer Control 3 (0x52) */ +#define RT5645_M_BST3_OM_R (0x1 << 4) +#define RT5645_M_BST3_OM_R_SFT 4 +#define RT5645_M_BST2_OM_R (0x1 << 3) +#define RT5645_M_BST2_OM_R_SFT 3 +#define RT5645_M_IN_R_OM_R (0x1 << 2) +#define RT5645_M_IN_R_OM_R_SFT 2 +#define RT5645_M_DAC_R2_OM_R (0x1 << 1) +#define RT5645_M_DAC_R2_OM_R_SFT 1 +#define RT5645_M_DAC_R1_OM_R (0x1) +#define RT5645_M_DAC_R1_OM_R_SFT 0 + +/* LOUT Mixer Control (0x53) */ +#define RT5645_M_DAC_L1_LM (0x1 << 15) +#define RT5645_M_DAC_L1_LM_SFT 15 +#define RT5645_M_DAC_R1_LM (0x1 << 14) +#define RT5645_M_DAC_R1_LM_SFT 14 +#define RT5645_M_OV_L_LM (0x1 << 13) +#define RT5645_M_OV_L_LM_SFT 13 +#define RT5645_M_OV_R_LM (0x1 << 12) +#define RT5645_M_OV_R_LM_SFT 12 +#define RT5645_G_LOUTMIX_MASK (0x1 << 11) +#define RT5645_G_LOUTMIX_SFT 11 + +/* Power Management for Digital 1 (0x61) */ +#define RT5645_PWR_I2S1 (0x1 << 15) +#define RT5645_PWR_I2S1_BIT 15 +#define RT5645_PWR_I2S2 (0x1 << 14) +#define RT5645_PWR_I2S2_BIT 14 +#define RT5645_PWR_I2S3 (0x1 << 13) +#define RT5645_PWR_I2S3_BIT 13 +#define RT5645_PWR_DAC_L1 (0x1 << 12) +#define RT5645_PWR_DAC_L1_BIT 12 +#define RT5645_PWR_DAC_R1 (0x1 << 11) +#define RT5645_PWR_DAC_R1_BIT 11 +#define RT5645_PWR_CLS_D_R (0x1 << 9) +#define RT5645_PWR_CLS_D_R_BIT 9 +#define RT5645_PWR_CLS_D_L (0x1 << 8) +#define RT5645_PWR_CLS_D_L_BIT 8 +#define RT5645_PWR_DAC_L2 (0x1 << 7) +#define RT5645_PWR_DAC_L2_BIT 7 +#define RT5645_PWR_DAC_R2 (0x1 << 6) +#define RT5645_PWR_DAC_R2_BIT 6 +#define RT5645_PWR_ADC_L (0x1 << 2) +#define RT5645_PWR_ADC_L_BIT 2 +#define RT5645_PWR_ADC_R (0x1 << 1) +#define RT5645_PWR_ADC_R_BIT 1 +#define RT5645_PWR_CLS_D (0x1) +#define RT5645_PWR_CLS_D_BIT 0 + +/* Power Management for Digital 2 (0x62) */ +#define RT5645_PWR_ADC_S1F (0x1 << 15) +#define RT5645_PWR_ADC_S1F_BIT 15 +#define RT5645_PWR_ADC_MF_L (0x1 << 14) +#define RT5645_PWR_ADC_MF_L_BIT 14 +#define RT5645_PWR_ADC_MF_R (0x1 << 13) +#define RT5645_PWR_ADC_MF_R_BIT 13 +#define RT5645_PWR_I2S_DSP (0x1 << 12) +#define RT5645_PWR_I2S_DSP_BIT 12 +#define RT5645_PWR_DAC_S1F (0x1 << 11) +#define RT5645_PWR_DAC_S1F_BIT 11 +#define RT5645_PWR_DAC_MF_L (0x1 << 10) +#define RT5645_PWR_DAC_MF_L_BIT 10 +#define RT5645_PWR_DAC_MF_R (0x1 << 9) +#define RT5645_PWR_DAC_MF_R_BIT 9 +#define RT5645_PWR_PDM1 (0x1 << 7) +#define RT5645_PWR_PDM1_BIT 7 +#define RT5645_PWR_PDM2 (0x1 << 6) +#define RT5645_PWR_PDM2_BIT 6 +#define RT5645_PWR_IPTV (0x1 << 1) +#define RT5645_PWR_IPTV_BIT 1 +#define RT5645_PWR_PAD (0x1) +#define RT5645_PWR_PAD_BIT 0 + +/* Power Management for Analog 1 (0x63) */ +#define RT5645_PWR_VREF1 (0x1 << 15) +#define RT5645_PWR_VREF1_BIT 15 +#define RT5645_PWR_FV1 (0x1 << 14) +#define RT5645_PWR_FV1_BIT 14 +#define RT5645_PWR_MB (0x1 << 13) +#define RT5645_PWR_MB_BIT 13 +#define RT5645_PWR_LM (0x1 << 12) +#define RT5645_PWR_LM_BIT 12 +#define RT5645_PWR_BG (0x1 << 11) +#define RT5645_PWR_BG_BIT 11 +#define RT5645_PWR_MA (0x1 << 10) +#define RT5645_PWR_MA_BIT 10 +#define RT5645_PWR_HP_L (0x1 << 7) +#define RT5645_PWR_HP_L_BIT 7 +#define RT5645_PWR_HP_R (0x1 << 6) +#define RT5645_PWR_HP_R_BIT 6 +#define RT5645_PWR_HA (0x1 << 5) +#define RT5645_PWR_HA_BIT 5 +#define RT5645_PWR_VREF2 (0x1 << 4) +#define RT5645_PWR_VREF2_BIT 4 +#define RT5645_PWR_FV2 (0x1 << 3) +#define RT5645_PWR_FV2_BIT 3 +#define RT5645_LDO_SEL_MASK (0x3) +#define RT5645_LDO_SEL_SFT 0 + +/* Power Management for Analog 2 (0x64) */ +#define RT5645_PWR_BST1 (0x1 << 15) +#define RT5645_PWR_BST1_BIT 15 +#define RT5645_PWR_BST2 (0x1 << 14) +#define RT5645_PWR_BST2_BIT 14 +#define RT5645_PWR_BST3 (0x1 << 13) +#define RT5645_PWR_BST3_BIT 13 +#define RT5645_PWR_BST4 (0x1 << 12) +#define RT5645_PWR_BST4_BIT 12 +#define RT5645_PWR_MB1 (0x1 << 11) +#define RT5645_PWR_MB1_BIT 11 +#define RT5645_PWR_MB2 (0x1 << 10) +#define RT5645_PWR_MB2_BIT 10 +#define RT5645_PWR_PLL (0x1 << 9) +#define RT5645_PWR_PLL_BIT 9 +#define RT5645_PWR_BST2_P (0x1 << 5) +#define RT5645_PWR_BST2_P_BIT 5 +#define RT5645_PWR_BST3_P (0x1 << 4) +#define RT5645_PWR_BST3_P_BIT 4 +#define RT5645_PWR_BST4_P (0x1 << 3) +#define RT5645_PWR_BST4_P_BIT 3 +#define RT5645_PWR_JD1 (0x1 << 2) +#define RT5645_PWR_JD1_BIT 2 +#define RT5645_PWR_JD (0x1 << 1) +#define RT5645_PWR_JD_BIT 1 + +/* Power Management for Mixer (0x65) */ +#define RT5645_PWR_OM_L (0x1 << 15) +#define RT5645_PWR_OM_L_BIT 15 +#define RT5645_PWR_OM_R (0x1 << 14) +#define RT5645_PWR_OM_R_BIT 14 +#define RT5645_PWR_SM_L (0x1 << 13) +#define RT5645_PWR_SM_L_BIT 13 +#define RT5645_PWR_SM_R (0x1 << 12) +#define RT5645_PWR_SM_R_BIT 12 +#define RT5645_PWR_RM_L (0x1 << 11) +#define RT5645_PWR_RM_L_BIT 11 +#define RT5645_PWR_RM_R (0x1 << 10) +#define RT5645_PWR_RM_R_BIT 10 +#define RT5645_PWR_MM (0x1 << 8) +#define RT5645_PWR_MM_BIT 8 +#define RT5645_PWR_HM_L (0x1 << 7) +#define RT5645_PWR_HM_L_BIT 7 +#define RT5645_PWR_HM_R (0x1 << 6) +#define RT5645_PWR_HM_R_BIT 6 +#define RT5645_PWR_LDO2 (0x1 << 1) +#define RT5645_PWR_LDO2_BIT 1 + +/* Power Management for Volume (0x66) */ +#define RT5645_PWR_SV_L (0x1 << 15) +#define RT5645_PWR_SV_L_BIT 15 +#define RT5645_PWR_SV_R (0x1 << 14) +#define RT5645_PWR_SV_R_BIT 14 +#define RT5645_PWR_HV_L (0x1 << 11) +#define RT5645_PWR_HV_L_BIT 11 +#define RT5645_PWR_HV_R (0x1 << 10) +#define RT5645_PWR_HV_R_BIT 10 +#define RT5645_PWR_IN_L (0x1 << 9) +#define RT5645_PWR_IN_L_BIT 9 +#define RT5645_PWR_IN_R (0x1 << 8) +#define RT5645_PWR_IN_R_BIT 8 +#define RT5645_PWR_MIC_DET (0x1 << 5) +#define RT5645_PWR_MIC_DET_BIT 5 + +/* I2S1/2 Audio Serial Data Port Control (0x70 0x71) */ +#define RT5645_I2S_MS_MASK (0x1 << 15) +#define RT5645_I2S_MS_SFT 15 +#define RT5645_I2S_MS_M (0x0 << 15) +#define RT5645_I2S_MS_S (0x1 << 15) +#define RT5645_I2S_O_CP_MASK (0x3 << 10) +#define RT5645_I2S_O_CP_SFT 10 +#define RT5645_I2S_O_CP_OFF (0x0 << 10) +#define RT5645_I2S_O_CP_U_LAW (0x1 << 10) +#define RT5645_I2S_O_CP_A_LAW (0x2 << 10) +#define RT5645_I2S_I_CP_MASK (0x3 << 8) +#define RT5645_I2S_I_CP_SFT 8 +#define RT5645_I2S_I_CP_OFF (0x0 << 8) +#define RT5645_I2S_I_CP_U_LAW (0x1 << 8) +#define RT5645_I2S_I_CP_A_LAW (0x2 << 8) +#define RT5645_I2S_BP_MASK (0x1 << 7) +#define RT5645_I2S_BP_SFT 7 +#define RT5645_I2S_BP_NOR (0x0 << 7) +#define RT5645_I2S_BP_INV (0x1 << 7) +#define RT5645_I2S_DL_MASK (0x3 << 2) +#define RT5645_I2S_DL_SFT 2 +#define RT5645_I2S_DL_16 (0x0 << 2) +#define RT5645_I2S_DL_20 (0x1 << 2) +#define RT5645_I2S_DL_24 (0x2 << 2) +#define RT5645_I2S_DL_8 (0x3 << 2) +#define RT5645_I2S_DF_MASK (0x3) +#define RT5645_I2S_DF_SFT 0 +#define RT5645_I2S_DF_I2S (0x0) +#define RT5645_I2S_DF_LEFT (0x1) +#define RT5645_I2S_DF_PCM_A (0x2) +#define RT5645_I2S_DF_PCM_B (0x3) + +/* I2S2 Audio Serial Data Port Control (0x71) */ +#define RT5645_I2S2_SDI_MASK (0x1 << 6) +#define RT5645_I2S2_SDI_SFT 6 +#define RT5645_I2S2_SDI_I2S1 (0x0 << 6) +#define RT5645_I2S2_SDI_I2S2 (0x1 << 6) + +/* ADC/DAC Clock Control 1 (0x73) */ +#define RT5645_I2S_PD1_MASK (0x7 << 12) +#define RT5645_I2S_PD1_SFT 12 +#define RT5645_I2S_PD1_1 (0x0 << 12) +#define RT5645_I2S_PD1_2 (0x1 << 12) +#define RT5645_I2S_PD1_3 (0x2 << 12) +#define RT5645_I2S_PD1_4 (0x3 << 12) +#define RT5645_I2S_PD1_6 (0x4 << 12) +#define RT5645_I2S_PD1_8 (0x5 << 12) +#define RT5645_I2S_PD1_12 (0x6 << 12) +#define RT5645_I2S_PD1_16 (0x7 << 12) +#define RT5645_I2S_BCLK_MS2_MASK (0x1 << 11) +#define RT5645_I2S_BCLK_MS2_SFT 11 +#define RT5645_I2S_BCLK_MS2_32 (0x0 << 11) +#define RT5645_I2S_BCLK_MS2_64 (0x1 << 11) +#define RT5645_I2S_PD2_MASK (0x7 << 8) +#define RT5645_I2S_PD2_SFT 8 +#define RT5645_I2S_PD2_1 (0x0 << 8) +#define RT5645_I2S_PD2_2 (0x1 << 8) +#define RT5645_I2S_PD2_3 (0x2 << 8) +#define RT5645_I2S_PD2_4 (0x3 << 8) +#define RT5645_I2S_PD2_6 (0x4 << 8) +#define RT5645_I2S_PD2_8 (0x5 << 8) +#define RT5645_I2S_PD2_12 (0x6 << 8) +#define RT5645_I2S_PD2_16 (0x7 << 8) +#define RT5645_I2S_BCLK_MS3_MASK (0x1 << 7) +#define RT5645_I2S_BCLK_MS3_SFT 7 +#define RT5645_I2S_BCLK_MS3_32 (0x0 << 7) +#define RT5645_I2S_BCLK_MS3_64 (0x1 << 7) +#define RT5645_I2S_PD3_MASK (0x7 << 4) +#define RT5645_I2S_PD3_SFT 4 +#define RT5645_I2S_PD3_1 (0x0 << 4) +#define RT5645_I2S_PD3_2 (0x1 << 4) +#define RT5645_I2S_PD3_3 (0x2 << 4) +#define RT5645_I2S_PD3_4 (0x3 << 4) +#define RT5645_I2S_PD3_6 (0x4 << 4) +#define RT5645_I2S_PD3_8 (0x5 << 4) +#define RT5645_I2S_PD3_12 (0x6 << 4) +#define RT5645_I2S_PD3_16 (0x7 << 4) +#define RT5645_DAC_OSR_MASK (0x3 << 2) +#define RT5645_DAC_OSR_SFT 2 +#define RT5645_DAC_OSR_128 (0x0 << 2) +#define RT5645_DAC_OSR_64 (0x1 << 2) +#define RT5645_DAC_OSR_32 (0x2 << 2) +#define RT5645_DAC_OSR_16 (0x3 << 2) +#define RT5645_ADC_OSR_MASK (0x3) +#define RT5645_ADC_OSR_SFT 0 +#define RT5645_ADC_OSR_128 (0x0) +#define RT5645_ADC_OSR_64 (0x1) +#define RT5645_ADC_OSR_32 (0x2) +#define RT5645_ADC_OSR_16 (0x3) + +/* ADC/DAC Clock Control 2 (0x74) */ +#define RT5645_DAC_L_OSR_MASK (0x3 << 14) +#define RT5645_DAC_L_OSR_SFT 14 +#define RT5645_DAC_L_OSR_128 (0x0 << 14) +#define RT5645_DAC_L_OSR_64 (0x1 << 14) +#define RT5645_DAC_L_OSR_32 (0x2 << 14) +#define RT5645_DAC_L_OSR_16 (0x3 << 14) +#define RT5645_ADC_R_OSR_MASK (0x3 << 12) +#define RT5645_ADC_R_OSR_SFT 12 +#define RT5645_ADC_R_OSR_128 (0x0 << 12) +#define RT5645_ADC_R_OSR_64 (0x1 << 12) +#define RT5645_ADC_R_OSR_32 (0x2 << 12) +#define RT5645_ADC_R_OSR_16 (0x3 << 12) +#define RT5645_DAHPF_EN (0x1 << 11) +#define RT5645_DAHPF_EN_SFT 11 +#define RT5645_ADHPF_EN (0x1 << 10) +#define RT5645_ADHPF_EN_SFT 10 + +/* Digital Microphone Control (0x75) */ +#define RT5645_DMIC_1_EN_MASK (0x1 << 15) +#define RT5645_DMIC_1_EN_SFT 15 +#define RT5645_DMIC_1_DIS (0x0 << 15) +#define RT5645_DMIC_1_EN (0x1 << 15) +#define RT5645_DMIC_2_EN_MASK (0x1 << 14) +#define RT5645_DMIC_2_EN_SFT 14 +#define RT5645_DMIC_2_DIS (0x0 << 14) +#define RT5645_DMIC_2_EN (0x1 << 14) +#define RT5645_DMIC_1L_LH_MASK (0x1 << 13) +#define RT5645_DMIC_1L_LH_SFT 13 +#define RT5645_DMIC_1L_LH_FALLING (0x0 << 13) +#define RT5645_DMIC_1L_LH_RISING (0x1 << 13) +#define RT5645_DMIC_1R_LH_MASK (0x1 << 12) +#define RT5645_DMIC_1R_LH_SFT 12 +#define RT5645_DMIC_1R_LH_FALLING (0x0 << 12) +#define RT5645_DMIC_1R_LH_RISING (0x1 << 12) +#define RT5645_DMIC_2_DP_MASK (0x3 << 10) +#define RT5645_DMIC_2_DP_SFT 10 +#define RT5645_DMIC_2_DP_GPIO6 (0x0 << 10) +#define RT5645_DMIC_2_DP_GPIO10 (0x1 << 10) +#define RT5645_DMIC_2_DP_GPIO12 (0x2 << 10) +#define RT5645_DMIC_2_DP_IN2P (0x3 << 10) +#define RT5645_DMIC_2L_LH_MASK (0x1 << 9) +#define RT5645_DMIC_2L_LH_SFT 9 +#define RT5645_DMIC_2L_LH_FALLING (0x0 << 9) +#define RT5645_DMIC_2L_LH_RISING (0x1 << 9) +#define RT5645_DMIC_2R_LH_MASK (0x1 << 8) +#define RT5645_DMIC_2R_LH_SFT 8 +#define RT5645_DMIC_2R_LH_FALLING (0x0 << 8) +#define RT5645_DMIC_2R_LH_RISING (0x1 << 8) +#define RT5645_DMIC_CLK_MASK (0x7 << 5) +#define RT5645_DMIC_CLK_SFT 5 +#define RT5645_DMIC_3_EN_MASK (0x1 << 4) +#define RT5645_DMIC_3_EN_SFT 4 +#define RT5645_DMIC_3_DIS (0x0 << 4) +#define RT5645_DMIC_3_EN (0x1 << 4) +#define RT5645_DMIC_1_DP_MASK (0x3 << 0) +#define RT5645_DMIC_1_DP_SFT 0 +#define RT5645_DMIC_1_DP_GPIO5 (0x0 << 0) +#define RT5645_DMIC_1_DP_IN2N (0x1 << 0) +#define RT5645_DMIC_1_DP_GPIO11 (0x2 << 0) + +/* TDM Control 1 (0x77) */ +#define RT5645_IF1_ADC_IN_MASK (0x3 << 8) +#define RT5645_IF1_ADC_IN_SFT 8 + +/* Global Clock Control (0x80) */ +#define RT5645_SCLK_SRC_MASK (0x3 << 14) +#define RT5645_SCLK_SRC_SFT 14 +#define RT5645_SCLK_SRC_MCLK (0x0 << 14) +#define RT5645_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5645_SCLK_SRC_RCCLK (0x2 << 14) +#define RT5645_PLL1_SRC_MASK (0x7 << 11) +#define RT5645_PLL1_SRC_SFT 11 +#define RT5645_PLL1_SRC_MCLK (0x0 << 11) +#define RT5645_PLL1_SRC_BCLK1 (0x1 << 11) +#define RT5645_PLL1_SRC_BCLK2 (0x2 << 11) +#define RT5645_PLL1_SRC_BCLK3 (0x3 << 11) +#define RT5645_PLL1_SRC_RCCLK (0x4 << 11) +#define RT5645_PLL1_PD_MASK (0x1 << 3) +#define RT5645_PLL1_PD_SFT 3 +#define RT5645_PLL1_PD_1 (0x0 << 3) +#define RT5645_PLL1_PD_2 (0x1 << 3) + +#define RT5645_PLL_INP_MAX 40000000 +#define RT5645_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x81) */ +#define RT5645_PLL_N_MAX 0x1ff +#define RT5645_PLL_N_MASK (RT5645_PLL_N_MAX << 7) +#define RT5645_PLL_N_SFT 7 +#define RT5645_PLL_K_MAX 0x1f +#define RT5645_PLL_K_MASK (RT5645_PLL_K_MAX) +#define RT5645_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x82) */ +#define RT5645_PLL_M_MAX 0xf +#define RT5645_PLL_M_MASK (RT5645_PLL_M_MAX << 12) +#define RT5645_PLL_M_SFT 12 +#define RT5645_PLL_M_BP (0x1 << 11) +#define RT5645_PLL_M_BP_SFT 11 + +/* ASRC Control 1 (0x83) */ +#define RT5645_STO_T_MASK (0x1 << 15) +#define RT5645_STO_T_SFT 15 +#define RT5645_STO_T_SCLK (0x0 << 15) +#define RT5645_STO_T_LRCK1 (0x1 << 15) +#define RT5645_M1_T_MASK (0x1 << 14) +#define RT5645_M1_T_SFT 14 +#define RT5645_M1_T_I2S2 (0x0 << 14) +#define RT5645_M1_T_I2S2_D3 (0x1 << 14) +#define RT5645_I2S2_F_MASK (0x1 << 12) +#define RT5645_I2S2_F_SFT 12 +#define RT5645_I2S2_F_I2S2_D2 (0x0 << 12) +#define RT5645_I2S2_F_I2S1_TCLK (0x1 << 12) +#define RT5645_DMIC_1_M_MASK (0x1 << 9) +#define RT5645_DMIC_1_M_SFT 9 +#define RT5645_DMIC_1_M_NOR (0x0 << 9) +#define RT5645_DMIC_1_M_ASYN (0x1 << 9) +#define RT5645_DMIC_2_M_MASK (0x1 << 8) +#define RT5645_DMIC_2_M_SFT 8 +#define RT5645_DMIC_2_M_NOR (0x0 << 8) +#define RT5645_DMIC_2_M_ASYN (0x1 << 8) + +/* ASRC clock source selection (0x84, 0x85) */ +#define RT5645_CLK_SEL_SYS (0x0) +#define RT5645_CLK_SEL_I2S1_ASRC (0x1) +#define RT5645_CLK_SEL_I2S2_ASRC (0x2) +#define RT5645_CLK_SEL_SYS2 (0x5) + +/* ASRC Control 2 (0x84) */ +#define RT5645_DA_STO_CLK_SEL_MASK (0xf << 12) +#define RT5645_DA_STO_CLK_SEL_SFT 12 +#define RT5645_DA_MONOL_CLK_SEL_MASK (0xf << 8) +#define RT5645_DA_MONOL_CLK_SEL_SFT 8 +#define RT5645_DA_MONOR_CLK_SEL_MASK (0xf << 4) +#define RT5645_DA_MONOR_CLK_SEL_SFT 4 +#define RT5645_AD_STO1_CLK_SEL_MASK (0xf << 0) +#define RT5645_AD_STO1_CLK_SEL_SFT 0 + +/* ASRC Control 3 (0x85) */ +#define RT5645_AD_MONOL_CLK_SEL_MASK (0xf << 4) +#define RT5645_AD_MONOL_CLK_SEL_SFT 4 +#define RT5645_AD_MONOR_CLK_SEL_MASK (0xf << 0) +#define RT5645_AD_MONOR_CLK_SEL_SFT 0 + +/* ASRC Control 4 (0x89) */ +#define RT5645_I2S1_PD_MASK (0x7 << 12) +#define RT5645_I2S1_PD_SFT 12 +#define RT5645_I2S2_PD_MASK (0x7 << 8) +#define RT5645_I2S2_PD_SFT 8 + +/* HPOUT Over Current Detection (0x8b) */ +#define RT5645_HP_OVCD_MASK (0x1 << 10) +#define RT5645_HP_OVCD_SFT 10 +#define RT5645_HP_OVCD_DIS (0x0 << 10) +#define RT5645_HP_OVCD_EN (0x1 << 10) +#define RT5645_HP_OC_TH_MASK (0x3 << 8) +#define RT5645_HP_OC_TH_SFT 8 +#define RT5645_HP_OC_TH_90 (0x0 << 8) +#define RT5645_HP_OC_TH_105 (0x1 << 8) +#define RT5645_HP_OC_TH_120 (0x2 << 8) +#define RT5645_HP_OC_TH_135 (0x3 << 8) + +/* Class D Over Current Control (0x8c) */ +#define RT5645_CLSD_OC_MASK (0x1 << 9) +#define RT5645_CLSD_OC_SFT 9 +#define RT5645_CLSD_OC_PU (0x0 << 9) +#define RT5645_CLSD_OC_PD (0x1 << 9) +#define RT5645_AUTO_PD_MASK (0x1 << 8) +#define RT5645_AUTO_PD_SFT 8 +#define RT5645_AUTO_PD_DIS (0x0 << 8) +#define RT5645_AUTO_PD_EN (0x1 << 8) +#define RT5645_CLSD_OC_TH_MASK (0x3f) +#define RT5645_CLSD_OC_TH_SFT 0 + +/* Class D Output Control (0x8d) */ +#define RT5645_CLSD_RATIO_MASK (0xf << 12) +#define RT5645_CLSD_RATIO_SFT 12 +#define RT5645_CLSD_OM_MASK (0x1 << 11) +#define RT5645_CLSD_OM_SFT 11 +#define RT5645_CLSD_OM_MONO (0x0 << 11) +#define RT5645_CLSD_OM_STO (0x1 << 11) +#define RT5645_CLSD_SCH_MASK (0x1 << 10) +#define RT5645_CLSD_SCH_SFT 10 +#define RT5645_CLSD_SCH_L (0x0 << 10) +#define RT5645_CLSD_SCH_S (0x1 << 10) + +/* Depop Mode Control 1 (0x8e) */ +#define RT5645_SMT_TRIG_MASK (0x1 << 15) +#define RT5645_SMT_TRIG_SFT 15 +#define RT5645_SMT_TRIG_DIS (0x0 << 15) +#define RT5645_SMT_TRIG_EN (0x1 << 15) +#define RT5645_HP_L_SMT_MASK (0x1 << 9) +#define RT5645_HP_L_SMT_SFT 9 +#define RT5645_HP_L_SMT_DIS (0x0 << 9) +#define RT5645_HP_L_SMT_EN (0x1 << 9) +#define RT5645_HP_R_SMT_MASK (0x1 << 8) +#define RT5645_HP_R_SMT_SFT 8 +#define RT5645_HP_R_SMT_DIS (0x0 << 8) +#define RT5645_HP_R_SMT_EN (0x1 << 8) +#define RT5645_HP_CD_PD_MASK (0x1 << 7) +#define RT5645_HP_CD_PD_SFT 7 +#define RT5645_HP_CD_PD_DIS (0x0 << 7) +#define RT5645_HP_CD_PD_EN (0x1 << 7) +#define RT5645_RSTN_MASK (0x1 << 6) +#define RT5645_RSTN_SFT 6 +#define RT5645_RSTN_DIS (0x0 << 6) +#define RT5645_RSTN_EN (0x1 << 6) +#define RT5645_RSTP_MASK (0x1 << 5) +#define RT5645_RSTP_SFT 5 +#define RT5645_RSTP_DIS (0x0 << 5) +#define RT5645_RSTP_EN (0x1 << 5) +#define RT5645_HP_CO_MASK (0x1 << 4) +#define RT5645_HP_CO_SFT 4 +#define RT5645_HP_CO_DIS (0x0 << 4) +#define RT5645_HP_CO_EN (0x1 << 4) +#define RT5645_HP_CP_MASK (0x1 << 3) +#define RT5645_HP_CP_SFT 3 +#define RT5645_HP_CP_PD (0x0 << 3) +#define RT5645_HP_CP_PU (0x1 << 3) +#define RT5645_HP_SG_MASK (0x1 << 2) +#define RT5645_HP_SG_SFT 2 +#define RT5645_HP_SG_DIS (0x0 << 2) +#define RT5645_HP_SG_EN (0x1 << 2) +#define RT5645_HP_DP_MASK (0x1 << 1) +#define RT5645_HP_DP_SFT 1 +#define RT5645_HP_DP_PD (0x0 << 1) +#define RT5645_HP_DP_PU (0x1 << 1) +#define RT5645_HP_CB_MASK (0x1) +#define RT5645_HP_CB_SFT 0 +#define RT5645_HP_CB_PD (0x0) +#define RT5645_HP_CB_PU (0x1) + +/* Depop Mode Control 2 (0x8f) */ +#define RT5645_DEPOP_MASK (0x1 << 13) +#define RT5645_DEPOP_SFT 13 +#define RT5645_DEPOP_AUTO (0x0 << 13) +#define RT5645_DEPOP_MAN (0x1 << 13) +#define RT5645_RAMP_MASK (0x1 << 12) +#define RT5645_RAMP_SFT 12 +#define RT5645_RAMP_DIS (0x0 << 12) +#define RT5645_RAMP_EN (0x1 << 12) +#define RT5645_BPS_MASK (0x1 << 11) +#define RT5645_BPS_SFT 11 +#define RT5645_BPS_DIS (0x0 << 11) +#define RT5645_BPS_EN (0x1 << 11) +#define RT5645_FAST_UPDN_MASK (0x1 << 10) +#define RT5645_FAST_UPDN_SFT 10 +#define RT5645_FAST_UPDN_DIS (0x0 << 10) +#define RT5645_FAST_UPDN_EN (0x1 << 10) +#define RT5645_MRES_MASK (0x3 << 8) +#define RT5645_MRES_SFT 8 +#define RT5645_MRES_15MO (0x0 << 8) +#define RT5645_MRES_25MO (0x1 << 8) +#define RT5645_MRES_35MO (0x2 << 8) +#define RT5645_MRES_45MO (0x3 << 8) +#define RT5645_VLO_MASK (0x1 << 7) +#define RT5645_VLO_SFT 7 +#define RT5645_VLO_3V (0x0 << 7) +#define RT5645_VLO_32V (0x1 << 7) +#define RT5645_DIG_DP_MASK (0x1 << 6) +#define RT5645_DIG_DP_SFT 6 +#define RT5645_DIG_DP_DIS (0x0 << 6) +#define RT5645_DIG_DP_EN (0x1 << 6) +#define RT5645_DP_TH_MASK (0x3 << 4) +#define RT5645_DP_TH_SFT 4 + +/* Depop Mode Control 3 (0x90) */ +#define RT5645_CP_SYS_MASK (0x7 << 12) +#define RT5645_CP_SYS_SFT 12 +#define RT5645_CP_FQ1_MASK (0x7 << 8) +#define RT5645_CP_FQ1_SFT 8 +#define RT5645_CP_FQ2_MASK (0x7 << 4) +#define RT5645_CP_FQ2_SFT 4 +#define RT5645_CP_FQ3_MASK (0x7) +#define RT5645_CP_FQ3_SFT 0 +#define RT5645_CP_FQ_1_5_KHZ 0 +#define RT5645_CP_FQ_3_KHZ 1 +#define RT5645_CP_FQ_6_KHZ 2 +#define RT5645_CP_FQ_12_KHZ 3 +#define RT5645_CP_FQ_24_KHZ 4 +#define RT5645_CP_FQ_48_KHZ 5 +#define RT5645_CP_FQ_96_KHZ 6 +#define RT5645_CP_FQ_192_KHZ 7 + +/* PV detection and SPK gain control (0x92) */ +#define RT5645_PVDD_DET_MASK (0x1 << 15) +#define RT5645_PVDD_DET_SFT 15 +#define RT5645_PVDD_DET_DIS (0x0 << 15) +#define RT5645_PVDD_DET_EN (0x1 << 15) +#define RT5645_SPK_AG_MASK (0x1 << 14) +#define RT5645_SPK_AG_SFT 14 +#define RT5645_SPK_AG_DIS (0x0 << 14) +#define RT5645_SPK_AG_EN (0x1 << 14) + +/* Micbias Control (0x93) */ +#define RT5645_MIC1_BS_MASK (0x1 << 15) +#define RT5645_MIC1_BS_SFT 15 +#define RT5645_MIC1_BS_9AV (0x0 << 15) +#define RT5645_MIC1_BS_75AV (0x1 << 15) +#define RT5645_MIC2_BS_MASK (0x1 << 14) +#define RT5645_MIC2_BS_SFT 14 +#define RT5645_MIC2_BS_9AV (0x0 << 14) +#define RT5645_MIC2_BS_75AV (0x1 << 14) +#define RT5645_MIC1_CLK_MASK (0x1 << 13) +#define RT5645_MIC1_CLK_SFT 13 +#define RT5645_MIC1_CLK_DIS (0x0 << 13) +#define RT5645_MIC1_CLK_EN (0x1 << 13) +#define RT5645_MIC2_CLK_MASK (0x1 << 12) +#define RT5645_MIC2_CLK_SFT 12 +#define RT5645_MIC2_CLK_DIS (0x0 << 12) +#define RT5645_MIC2_CLK_EN (0x1 << 12) +#define RT5645_MIC1_OVCD_MASK (0x1 << 11) +#define RT5645_MIC1_OVCD_SFT 11 +#define RT5645_MIC1_OVCD_DIS (0x0 << 11) +#define RT5645_MIC1_OVCD_EN (0x1 << 11) +#define RT5645_MIC1_OVTH_MASK (0x3 << 9) +#define RT5645_MIC1_OVTH_SFT 9 +#define RT5645_MIC1_OVTH_600UA (0x0 << 9) +#define RT5645_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5645_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5645_MIC2_OVCD_MASK (0x1 << 8) +#define RT5645_MIC2_OVCD_SFT 8 +#define RT5645_MIC2_OVCD_DIS (0x0 << 8) +#define RT5645_MIC2_OVCD_EN (0x1 << 8) +#define RT5645_MIC2_OVTH_MASK (0x3 << 6) +#define RT5645_MIC2_OVTH_SFT 6 +#define RT5645_MIC2_OVTH_600UA (0x0 << 6) +#define RT5645_MIC2_OVTH_1500UA (0x1 << 6) +#define RT5645_MIC2_OVTH_2000UA (0x2 << 6) +#define RT5645_PWR_MB_MASK (0x1 << 5) +#define RT5645_PWR_MB_SFT 5 +#define RT5645_PWR_MB_PD (0x0 << 5) +#define RT5645_PWR_MB_PU (0x1 << 5) +#define RT5645_PWR_CLK25M_MASK (0x1 << 4) +#define RT5645_PWR_CLK25M_SFT 4 +#define RT5645_PWR_CLK25M_PD (0x0 << 4) +#define RT5645_PWR_CLK25M_PU (0x1 << 4) +#define RT5645_IRQ_CLK_MCLK (0x0 << 3) +#define RT5645_IRQ_CLK_INT (0x1 << 3) +#define RT5645_JD1_MODE_MASK (0x3 << 0) +#define RT5645_JD1_MODE_0 (0x0 << 0) +#define RT5645_JD1_MODE_1 (0x1 << 0) +#define RT5645_JD1_MODE_2 (0x2 << 0) + +/* VAD Control 4 (0x9d) */ +#define RT5645_VAD_SEL_MASK (0x3 << 8) +#define RT5645_VAD_SEL_SFT 8 + +/* EQ Control 1 (0xb0) */ +#define RT5645_EQ_SRC_MASK (0x1 << 15) +#define RT5645_EQ_SRC_SFT 15 +#define RT5645_EQ_SRC_DAC (0x0 << 15) +#define RT5645_EQ_SRC_ADC (0x1 << 15) +#define RT5645_EQ_UPD (0x1 << 14) +#define RT5645_EQ_UPD_BIT 14 +#define RT5645_EQ_CD_MASK (0x1 << 13) +#define RT5645_EQ_CD_SFT 13 +#define RT5645_EQ_CD_DIS (0x0 << 13) +#define RT5645_EQ_CD_EN (0x1 << 13) +#define RT5645_EQ_DITH_MASK (0x3 << 8) +#define RT5645_EQ_DITH_SFT 8 +#define RT5645_EQ_DITH_NOR (0x0 << 8) +#define RT5645_EQ_DITH_LSB (0x1 << 8) +#define RT5645_EQ_DITH_LSB_1 (0x2 << 8) +#define RT5645_EQ_DITH_LSB_2 (0x3 << 8) + +/* EQ Control 2 (0xb1) */ +#define RT5645_EQ_HPF1_M_MASK (0x1 << 8) +#define RT5645_EQ_HPF1_M_SFT 8 +#define RT5645_EQ_HPF1_M_HI (0x0 << 8) +#define RT5645_EQ_HPF1_M_1ST (0x1 << 8) +#define RT5645_EQ_LPF1_M_MASK (0x1 << 7) +#define RT5645_EQ_LPF1_M_SFT 7 +#define RT5645_EQ_LPF1_M_LO (0x0 << 7) +#define RT5645_EQ_LPF1_M_1ST (0x1 << 7) +#define RT5645_EQ_HPF2_MASK (0x1 << 6) +#define RT5645_EQ_HPF2_SFT 6 +#define RT5645_EQ_HPF2_DIS (0x0 << 6) +#define RT5645_EQ_HPF2_EN (0x1 << 6) +#define RT5645_EQ_HPF1_MASK (0x1 << 5) +#define RT5645_EQ_HPF1_SFT 5 +#define RT5645_EQ_HPF1_DIS (0x0 << 5) +#define RT5645_EQ_HPF1_EN (0x1 << 5) +#define RT5645_EQ_BPF4_MASK (0x1 << 4) +#define RT5645_EQ_BPF4_SFT 4 +#define RT5645_EQ_BPF4_DIS (0x0 << 4) +#define RT5645_EQ_BPF4_EN (0x1 << 4) +#define RT5645_EQ_BPF3_MASK (0x1 << 3) +#define RT5645_EQ_BPF3_SFT 3 +#define RT5645_EQ_BPF3_DIS (0x0 << 3) +#define RT5645_EQ_BPF3_EN (0x1 << 3) +#define RT5645_EQ_BPF2_MASK (0x1 << 2) +#define RT5645_EQ_BPF2_SFT 2 +#define RT5645_EQ_BPF2_DIS (0x0 << 2) +#define RT5645_EQ_BPF2_EN (0x1 << 2) +#define RT5645_EQ_BPF1_MASK (0x1 << 1) +#define RT5645_EQ_BPF1_SFT 1 +#define RT5645_EQ_BPF1_DIS (0x0 << 1) +#define RT5645_EQ_BPF1_EN (0x1 << 1) +#define RT5645_EQ_LPF_MASK (0x1) +#define RT5645_EQ_LPF_SFT 0 +#define RT5645_EQ_LPF_DIS (0x0) +#define RT5645_EQ_LPF_EN (0x1) +#define RT5645_EQ_CTRL_MASK (0x7f) + +/* Memory Test (0xb2) */ +#define RT5645_MT_MASK (0x1 << 15) +#define RT5645_MT_SFT 15 +#define RT5645_MT_DIS (0x0 << 15) +#define RT5645_MT_EN (0x1 << 15) + +/* DRC/AGC Control 1 (0xb4) */ +#define RT5645_DRC_AGC_P_MASK (0x1 << 15) +#define RT5645_DRC_AGC_P_SFT 15 +#define RT5645_DRC_AGC_P_DAC (0x0 << 15) +#define RT5645_DRC_AGC_P_ADC (0x1 << 15) +#define RT5645_DRC_AGC_MASK (0x1 << 14) +#define RT5645_DRC_AGC_SFT 14 +#define RT5645_DRC_AGC_DIS (0x0 << 14) +#define RT5645_DRC_AGC_EN (0x1 << 14) +#define RT5645_DRC_AGC_UPD (0x1 << 13) +#define RT5645_DRC_AGC_UPD_BIT 13 +#define RT5645_DRC_AGC_AR_MASK (0x1f << 8) +#define RT5645_DRC_AGC_AR_SFT 8 +#define RT5645_DRC_AGC_R_MASK (0x7 << 5) +#define RT5645_DRC_AGC_R_SFT 5 +#define RT5645_DRC_AGC_R_48K (0x1 << 5) +#define RT5645_DRC_AGC_R_96K (0x2 << 5) +#define RT5645_DRC_AGC_R_192K (0x3 << 5) +#define RT5645_DRC_AGC_R_441K (0x5 << 5) +#define RT5645_DRC_AGC_R_882K (0x6 << 5) +#define RT5645_DRC_AGC_R_1764K (0x7 << 5) +#define RT5645_DRC_AGC_RC_MASK (0x1f) +#define RT5645_DRC_AGC_RC_SFT 0 + +/* DRC/AGC Control 2 (0xb5) */ +#define RT5645_DRC_AGC_POB_MASK (0x3f << 8) +#define RT5645_DRC_AGC_POB_SFT 8 +#define RT5645_DRC_AGC_CP_MASK (0x1 << 7) +#define RT5645_DRC_AGC_CP_SFT 7 +#define RT5645_DRC_AGC_CP_DIS (0x0 << 7) +#define RT5645_DRC_AGC_CP_EN (0x1 << 7) +#define RT5645_DRC_AGC_CPR_MASK (0x3 << 5) +#define RT5645_DRC_AGC_CPR_SFT 5 +#define RT5645_DRC_AGC_CPR_1_1 (0x0 << 5) +#define RT5645_DRC_AGC_CPR_1_2 (0x1 << 5) +#define RT5645_DRC_AGC_CPR_1_3 (0x2 << 5) +#define RT5645_DRC_AGC_CPR_1_4 (0x3 << 5) +#define RT5645_DRC_AGC_PRB_MASK (0x1f) +#define RT5645_DRC_AGC_PRB_SFT 0 + +/* DRC/AGC Control 3 (0xb6) */ +#define RT5645_DRC_AGC_NGB_MASK (0xf << 12) +#define RT5645_DRC_AGC_NGB_SFT 12 +#define RT5645_DRC_AGC_TAR_MASK (0x1f << 7) +#define RT5645_DRC_AGC_TAR_SFT 7 +#define RT5645_DRC_AGC_NG_MASK (0x1 << 6) +#define RT5645_DRC_AGC_NG_SFT 6 +#define RT5645_DRC_AGC_NG_DIS (0x0 << 6) +#define RT5645_DRC_AGC_NG_EN (0x1 << 6) +#define RT5645_DRC_AGC_NGH_MASK (0x1 << 5) +#define RT5645_DRC_AGC_NGH_SFT 5 +#define RT5645_DRC_AGC_NGH_DIS (0x0 << 5) +#define RT5645_DRC_AGC_NGH_EN (0x1 << 5) +#define RT5645_DRC_AGC_NGT_MASK (0x1f) +#define RT5645_DRC_AGC_NGT_SFT 0 + +/* ANC Control 1 (0xb8) */ +#define RT5645_ANC_M_MASK (0x1 << 15) +#define RT5645_ANC_M_SFT 15 +#define RT5645_ANC_M_NOR (0x0 << 15) +#define RT5645_ANC_M_REV (0x1 << 15) +#define RT5645_ANC_MASK (0x1 << 14) +#define RT5645_ANC_SFT 14 +#define RT5645_ANC_DIS (0x0 << 14) +#define RT5645_ANC_EN (0x1 << 14) +#define RT5645_ANC_MD_MASK (0x3 << 12) +#define RT5645_ANC_MD_SFT 12 +#define RT5645_ANC_MD_DIS (0x0 << 12) +#define RT5645_ANC_MD_67MS (0x1 << 12) +#define RT5645_ANC_MD_267MS (0x2 << 12) +#define RT5645_ANC_MD_1067MS (0x3 << 12) +#define RT5645_ANC_SN_MASK (0x1 << 11) +#define RT5645_ANC_SN_SFT 11 +#define RT5645_ANC_SN_DIS (0x0 << 11) +#define RT5645_ANC_SN_EN (0x1 << 11) +#define RT5645_ANC_CLK_MASK (0x1 << 10) +#define RT5645_ANC_CLK_SFT 10 +#define RT5645_ANC_CLK_ANC (0x0 << 10) +#define RT5645_ANC_CLK_REG (0x1 << 10) +#define RT5645_ANC_ZCD_MASK (0x3 << 8) +#define RT5645_ANC_ZCD_SFT 8 +#define RT5645_ANC_ZCD_DIS (0x0 << 8) +#define RT5645_ANC_ZCD_T1 (0x1 << 8) +#define RT5645_ANC_ZCD_T2 (0x2 << 8) +#define RT5645_ANC_ZCD_WT (0x3 << 8) +#define RT5645_ANC_CS_MASK (0x1 << 7) +#define RT5645_ANC_CS_SFT 7 +#define RT5645_ANC_CS_DIS (0x0 << 7) +#define RT5645_ANC_CS_EN (0x1 << 7) +#define RT5645_ANC_SW_MASK (0x1 << 6) +#define RT5645_ANC_SW_SFT 6 +#define RT5645_ANC_SW_NOR (0x0 << 6) +#define RT5645_ANC_SW_AUTO (0x1 << 6) +#define RT5645_ANC_CO_L_MASK (0x3f) +#define RT5645_ANC_CO_L_SFT 0 + +/* ANC Control 2 (0xb6) */ +#define RT5645_ANC_FG_R_MASK (0xf << 12) +#define RT5645_ANC_FG_R_SFT 12 +#define RT5645_ANC_FG_L_MASK (0xf << 8) +#define RT5645_ANC_FG_L_SFT 8 +#define RT5645_ANC_CG_R_MASK (0xf << 4) +#define RT5645_ANC_CG_R_SFT 4 +#define RT5645_ANC_CG_L_MASK (0xf) +#define RT5645_ANC_CG_L_SFT 0 + +/* ANC Control 3 (0xb6) */ +#define RT5645_ANC_CD_MASK (0x1 << 6) +#define RT5645_ANC_CD_SFT 6 +#define RT5645_ANC_CD_BOTH (0x0 << 6) +#define RT5645_ANC_CD_IND (0x1 << 6) +#define RT5645_ANC_CO_R_MASK (0x3f) +#define RT5645_ANC_CO_R_SFT 0 + +/* Jack Detect Control (0xbb) */ +#define RT5645_JD_MASK (0x7 << 13) +#define RT5645_JD_SFT 13 +#define RT5645_JD_DIS (0x0 << 13) +#define RT5645_JD_GPIO1 (0x1 << 13) +#define RT5645_JD_JD1_IN4P (0x2 << 13) +#define RT5645_JD_JD2_IN4N (0x3 << 13) +#define RT5645_JD_GPIO2 (0x4 << 13) +#define RT5645_JD_GPIO3 (0x5 << 13) +#define RT5645_JD_GPIO4 (0x6 << 13) +#define RT5645_JD_HP_MASK (0x1 << 11) +#define RT5645_JD_HP_SFT 11 +#define RT5645_JD_HP_DIS (0x0 << 11) +#define RT5645_JD_HP_EN (0x1 << 11) +#define RT5645_JD_HP_TRG_MASK (0x1 << 10) +#define RT5645_JD_HP_TRG_SFT 10 +#define RT5645_JD_HP_TRG_LO (0x0 << 10) +#define RT5645_JD_HP_TRG_HI (0x1 << 10) +#define RT5645_JD_SPL_MASK (0x1 << 9) +#define RT5645_JD_SPL_SFT 9 +#define RT5645_JD_SPL_DIS (0x0 << 9) +#define RT5645_JD_SPL_EN (0x1 << 9) +#define RT5645_JD_SPL_TRG_MASK (0x1 << 8) +#define RT5645_JD_SPL_TRG_SFT 8 +#define RT5645_JD_SPL_TRG_LO (0x0 << 8) +#define RT5645_JD_SPL_TRG_HI (0x1 << 8) +#define RT5645_JD_SPR_MASK (0x1 << 7) +#define RT5645_JD_SPR_SFT 7 +#define RT5645_JD_SPR_DIS (0x0 << 7) +#define RT5645_JD_SPR_EN (0x1 << 7) +#define RT5645_JD_SPR_TRG_MASK (0x1 << 6) +#define RT5645_JD_SPR_TRG_SFT 6 +#define RT5645_JD_SPR_TRG_LO (0x0 << 6) +#define RT5645_JD_SPR_TRG_HI (0x1 << 6) +#define RT5645_JD_MO_MASK (0x1 << 5) +#define RT5645_JD_MO_SFT 5 +#define RT5645_JD_MO_DIS (0x0 << 5) +#define RT5645_JD_MO_EN (0x1 << 5) +#define RT5645_JD_MO_TRG_MASK (0x1 << 4) +#define RT5645_JD_MO_TRG_SFT 4 +#define RT5645_JD_MO_TRG_LO (0x0 << 4) +#define RT5645_JD_MO_TRG_HI (0x1 << 4) +#define RT5645_JD_LO_MASK (0x1 << 3) +#define RT5645_JD_LO_SFT 3 +#define RT5645_JD_LO_DIS (0x0 << 3) +#define RT5645_JD_LO_EN (0x1 << 3) +#define RT5645_JD_LO_TRG_MASK (0x1 << 2) +#define RT5645_JD_LO_TRG_SFT 2 +#define RT5645_JD_LO_TRG_LO (0x0 << 2) +#define RT5645_JD_LO_TRG_HI (0x1 << 2) +#define RT5645_JD1_IN4P_MASK (0x1 << 1) +#define RT5645_JD1_IN4P_SFT 1 +#define RT5645_JD1_IN4P_DIS (0x0 << 1) +#define RT5645_JD1_IN4P_EN (0x1 << 1) +#define RT5645_JD2_IN4N_MASK (0x1) +#define RT5645_JD2_IN4N_SFT 0 +#define RT5645_JD2_IN4N_DIS (0x0) +#define RT5645_JD2_IN4N_EN (0x1) + +/* Jack detect for ANC (0xbc) */ +#define RT5645_ANC_DET_MASK (0x3 << 4) +#define RT5645_ANC_DET_SFT 4 +#define RT5645_ANC_DET_DIS (0x0 << 4) +#define RT5645_ANC_DET_MB1 (0x1 << 4) +#define RT5645_ANC_DET_MB2 (0x2 << 4) +#define RT5645_ANC_DET_JD (0x3 << 4) +#define RT5645_AD_TRG_MASK (0x1 << 3) +#define RT5645_AD_TRG_SFT 3 +#define RT5645_AD_TRG_LO (0x0 << 3) +#define RT5645_AD_TRG_HI (0x1 << 3) +#define RT5645_ANCM_DET_MASK (0x3 << 4) +#define RT5645_ANCM_DET_SFT 4 +#define RT5645_ANCM_DET_DIS (0x0 << 4) +#define RT5645_ANCM_DET_MB1 (0x1 << 4) +#define RT5645_ANCM_DET_MB2 (0x2 << 4) +#define RT5645_ANCM_DET_JD (0x3 << 4) +#define RT5645_AMD_TRG_MASK (0x1 << 3) +#define RT5645_AMD_TRG_SFT 3 +#define RT5645_AMD_TRG_LO (0x0 << 3) +#define RT5645_AMD_TRG_HI (0x1 << 3) + +/* IRQ Control 1 (0xbd) */ +#define RT5645_IRQ_JD_MASK (0x1 << 15) +#define RT5645_IRQ_JD_SFT 15 +#define RT5645_IRQ_JD_BP (0x0 << 15) +#define RT5645_IRQ_JD_NOR (0x1 << 15) +#define RT5645_IRQ_OT_MASK (0x1 << 14) +#define RT5645_IRQ_OT_SFT 14 +#define RT5645_IRQ_OT_BP (0x0 << 14) +#define RT5645_IRQ_OT_NOR (0x1 << 14) +#define RT5645_JD_STKY_MASK (0x1 << 13) +#define RT5645_JD_STKY_SFT 13 +#define RT5645_JD_STKY_DIS (0x0 << 13) +#define RT5645_JD_STKY_EN (0x1 << 13) +#define RT5645_OT_STKY_MASK (0x1 << 12) +#define RT5645_OT_STKY_SFT 12 +#define RT5645_OT_STKY_DIS (0x0 << 12) +#define RT5645_OT_STKY_EN (0x1 << 12) +#define RT5645_JD_P_MASK (0x1 << 11) +#define RT5645_JD_P_SFT 11 +#define RT5645_JD_P_NOR (0x0 << 11) +#define RT5645_JD_P_INV (0x1 << 11) +#define RT5645_OT_P_MASK (0x1 << 10) +#define RT5645_OT_P_SFT 10 +#define RT5645_OT_P_NOR (0x0 << 10) +#define RT5645_OT_P_INV (0x1 << 10) +#define RT5645_IRQ_JD_1_1_EN (0x1 << 9) +#define RT5645_JD_1_1_MASK (0x1 << 7) +#define RT5645_JD_1_1_SFT 7 +#define RT5645_JD_1_1_NOR (0x0 << 7) +#define RT5645_JD_1_1_INV (0x1 << 7) + +/* IRQ Control 2 (0xbe) */ +#define RT5645_IRQ_MB1_OC_MASK (0x1 << 15) +#define RT5645_IRQ_MB1_OC_SFT 15 +#define RT5645_IRQ_MB1_OC_BP (0x0 << 15) +#define RT5645_IRQ_MB1_OC_NOR (0x1 << 15) +#define RT5645_IRQ_MB2_OC_MASK (0x1 << 14) +#define RT5645_IRQ_MB2_OC_SFT 14 +#define RT5645_IRQ_MB2_OC_BP (0x0 << 14) +#define RT5645_IRQ_MB2_OC_NOR (0x1 << 14) +#define RT5645_MB1_OC_STKY_MASK (0x1 << 13) +#define RT5645_MB1_OC_STKY_SFT 13 +#define RT5645_MB1_OC_STKY_DIS (0x0 << 13) +#define RT5645_MB1_OC_STKY_EN (0x1 << 13) +#define RT5645_MB2_OC_STKY_MASK (0x1 << 12) +#define RT5645_MB2_OC_STKY_SFT 12 +#define RT5645_MB2_OC_STKY_DIS (0x0 << 12) +#define RT5645_MB2_OC_STKY_EN (0x1 << 12) +#define RT5645_MB1_OC_P_MASK (0x1 << 7) +#define RT5645_MB1_OC_P_SFT 7 +#define RT5645_MB1_OC_P_NOR (0x0 << 7) +#define RT5645_MB1_OC_P_INV (0x1 << 7) +#define RT5645_MB2_OC_P_MASK (0x1 << 6) +#define RT5645_MB2_OC_P_SFT 6 +#define RT5645_MB2_OC_P_NOR (0x0 << 6) +#define RT5645_MB2_OC_P_INV (0x1 << 6) +#define RT5645_MB1_OC_CLR (0x1 << 3) +#define RT5645_MB1_OC_CLR_SFT 3 +#define RT5645_MB2_OC_CLR (0x1 << 2) +#define RT5645_MB2_OC_CLR_SFT 2 + +/* GPIO Control 1 (0xc0) */ +#define RT5645_GP1_PIN_MASK (0x1 << 15) +#define RT5645_GP1_PIN_SFT 15 +#define RT5645_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5645_GP1_PIN_IRQ (0x1 << 15) +#define RT5645_GP2_PIN_MASK (0x1 << 14) +#define RT5645_GP2_PIN_SFT 14 +#define RT5645_GP2_PIN_GPIO2 (0x0 << 14) +#define RT5645_GP2_PIN_DMIC1_SCL (0x1 << 14) +#define RT5645_GP3_PIN_MASK (0x3 << 12) +#define RT5645_GP3_PIN_SFT 12 +#define RT5645_GP3_PIN_GPIO3 (0x0 << 12) +#define RT5645_GP3_PIN_DMIC1_SDA (0x1 << 12) +#define RT5645_GP3_PIN_IRQ (0x2 << 12) +#define RT5645_GP4_PIN_MASK (0x1 << 11) +#define RT5645_GP4_PIN_SFT 11 +#define RT5645_GP4_PIN_GPIO4 (0x0 << 11) +#define RT5645_GP4_PIN_DMIC2_SDA (0x1 << 11) +#define RT5645_DP_SIG_MASK (0x1 << 10) +#define RT5645_DP_SIG_SFT 10 +#define RT5645_DP_SIG_TEST (0x0 << 10) +#define RT5645_DP_SIG_AP (0x1 << 10) +#define RT5645_GPIO_M_MASK (0x1 << 9) +#define RT5645_GPIO_M_SFT 9 +#define RT5645_GPIO_M_FLT (0x0 << 9) +#define RT5645_GPIO_M_PH (0x1 << 9) +#define RT5645_I2S2_SEL (0x1 << 8) +#define RT5645_I2S2_SEL_SFT 8 +#define RT5645_GP5_PIN_MASK (0x1 << 7) +#define RT5645_GP5_PIN_SFT 7 +#define RT5645_GP5_PIN_GPIO5 (0x0 << 7) +#define RT5645_GP5_PIN_DMIC1_SDA (0x1 << 7) +#define RT5645_GP6_PIN_MASK (0x1 << 6) +#define RT5645_GP6_PIN_SFT 6 +#define RT5645_GP6_PIN_GPIO6 (0x0 << 6) +#define RT5645_GP6_PIN_DMIC2_SDA (0x1 << 6) +#define RT5645_I2S2_DAC_PIN_MASK (0x1 << 4) +#define RT5645_I2S2_DAC_PIN_SFT 4 +#define RT5645_I2S2_DAC_PIN_I2S (0x0 << 4) +#define RT5645_I2S2_DAC_PIN_GPIO (0x1 << 4) +#define RT5645_GP8_PIN_MASK (0x1 << 3) +#define RT5645_GP8_PIN_SFT 3 +#define RT5645_GP8_PIN_GPIO8 (0x0 << 3) +#define RT5645_GP8_PIN_DMIC2_SDA (0x1 << 3) +#define RT5645_GP12_PIN_MASK (0x1 << 2) +#define RT5645_GP12_PIN_SFT 2 +#define RT5645_GP12_PIN_GPIO12 (0x0 << 2) +#define RT5645_GP12_PIN_DMIC2_SDA (0x1 << 2) +#define RT5645_GP11_PIN_MASK (0x1 << 1) +#define RT5645_GP11_PIN_SFT 1 +#define RT5645_GP11_PIN_GPIO11 (0x0 << 1) +#define RT5645_GP11_PIN_DMIC1_SDA (0x1 << 1) +#define RT5645_GP10_PIN_MASK (0x1) +#define RT5645_GP10_PIN_SFT 0 +#define RT5645_GP10_PIN_GPIO10 (0x0) +#define RT5645_GP10_PIN_DMIC2_SDA (0x1) + +/* GPIO Control 3 (0xc2) */ +#define RT5645_GP4_PF_MASK (0x1 << 11) +#define RT5645_GP4_PF_SFT 11 +#define RT5645_GP4_PF_IN (0x0 << 11) +#define RT5645_GP4_PF_OUT (0x1 << 11) +#define RT5645_GP4_OUT_MASK (0x1 << 10) +#define RT5645_GP4_OUT_SFT 10 +#define RT5645_GP4_OUT_LO (0x0 << 10) +#define RT5645_GP4_OUT_HI (0x1 << 10) +#define RT5645_GP4_P_MASK (0x1 << 9) +#define RT5645_GP4_P_SFT 9 +#define RT5645_GP4_P_NOR (0x0 << 9) +#define RT5645_GP4_P_INV (0x1 << 9) +#define RT5645_GP3_PF_MASK (0x1 << 8) +#define RT5645_GP3_PF_SFT 8 +#define RT5645_GP3_PF_IN (0x0 << 8) +#define RT5645_GP3_PF_OUT (0x1 << 8) +#define RT5645_GP3_OUT_MASK (0x1 << 7) +#define RT5645_GP3_OUT_SFT 7 +#define RT5645_GP3_OUT_LO (0x0 << 7) +#define RT5645_GP3_OUT_HI (0x1 << 7) +#define RT5645_GP3_P_MASK (0x1 << 6) +#define RT5645_GP3_P_SFT 6 +#define RT5645_GP3_P_NOR (0x0 << 6) +#define RT5645_GP3_P_INV (0x1 << 6) +#define RT5645_GP2_PF_MASK (0x1 << 5) +#define RT5645_GP2_PF_SFT 5 +#define RT5645_GP2_PF_IN (0x0 << 5) +#define RT5645_GP2_PF_OUT (0x1 << 5) +#define RT5645_GP2_OUT_MASK (0x1 << 4) +#define RT5645_GP2_OUT_SFT 4 +#define RT5645_GP2_OUT_LO (0x0 << 4) +#define RT5645_GP2_OUT_HI (0x1 << 4) +#define RT5645_GP2_P_MASK (0x1 << 3) +#define RT5645_GP2_P_SFT 3 +#define RT5645_GP2_P_NOR (0x0 << 3) +#define RT5645_GP2_P_INV (0x1 << 3) +#define RT5645_GP1_PF_MASK (0x1 << 2) +#define RT5645_GP1_PF_SFT 2 +#define RT5645_GP1_PF_IN (0x0 << 2) +#define RT5645_GP1_PF_OUT (0x1 << 2) +#define RT5645_GP1_OUT_MASK (0x1 << 1) +#define RT5645_GP1_OUT_SFT 1 +#define RT5645_GP1_OUT_LO (0x0 << 1) +#define RT5645_GP1_OUT_HI (0x1 << 1) +#define RT5645_GP1_P_MASK (0x1) +#define RT5645_GP1_P_SFT 0 +#define RT5645_GP1_P_NOR (0x0) +#define RT5645_GP1_P_INV (0x1) + +/* Programmable Register Array Control 1 (0xc8) */ +#define RT5645_REG_SEQ_MASK (0xf << 12) +#define RT5645_REG_SEQ_SFT 12 +#define RT5645_SEQ1_ST_MASK (0x1 << 11) /*RO*/ +#define RT5645_SEQ1_ST_SFT 11 +#define RT5645_SEQ1_ST_RUN (0x0 << 11) +#define RT5645_SEQ1_ST_FIN (0x1 << 11) +#define RT5645_SEQ2_ST_MASK (0x1 << 10) /*RO*/ +#define RT5645_SEQ2_ST_SFT 10 +#define RT5645_SEQ2_ST_RUN (0x0 << 10) +#define RT5645_SEQ2_ST_FIN (0x1 << 10) +#define RT5645_REG_LV_MASK (0x1 << 9) +#define RT5645_REG_LV_SFT 9 +#define RT5645_REG_LV_MX (0x0 << 9) +#define RT5645_REG_LV_PR (0x1 << 9) +#define RT5645_SEQ_2_PT_MASK (0x1 << 8) +#define RT5645_SEQ_2_PT_BIT 8 +#define RT5645_REG_IDX_MASK (0xff) +#define RT5645_REG_IDX_SFT 0 + +/* Programmable Register Array Control 2 (0xc9) */ +#define RT5645_REG_DAT_MASK (0xffff) +#define RT5645_REG_DAT_SFT 0 + +/* Programmable Register Array Control 3 (0xca) */ +#define RT5645_SEQ_DLY_MASK (0xff << 8) +#define RT5645_SEQ_DLY_SFT 8 +#define RT5645_PROG_MASK (0x1 << 7) +#define RT5645_PROG_SFT 7 +#define RT5645_PROG_DIS (0x0 << 7) +#define RT5645_PROG_EN (0x1 << 7) +#define RT5645_SEQ1_PT_RUN (0x1 << 6) +#define RT5645_SEQ1_PT_RUN_BIT 6 +#define RT5645_SEQ2_PT_RUN (0x1 << 5) +#define RT5645_SEQ2_PT_RUN_BIT 5 + +/* Programmable Register Array Control 4 (0xcb) */ +#define RT5645_SEQ1_START_MASK (0xf << 8) +#define RT5645_SEQ1_START_SFT 8 +#define RT5645_SEQ1_END_MASK (0xf) +#define RT5645_SEQ1_END_SFT 0 + +/* Programmable Register Array Control 5 (0xcc) */ +#define RT5645_SEQ2_START_MASK (0xf << 8) +#define RT5645_SEQ2_START_SFT 8 +#define RT5645_SEQ2_END_MASK (0xf) +#define RT5645_SEQ2_END_SFT 0 + +/* Scramble Function (0xcd) */ +#define RT5645_SCB_KEY_MASK (0xff) +#define RT5645_SCB_KEY_SFT 0 + +/* Scramble Control (0xce) */ +#define RT5645_SCB_SWAP_MASK (0x1 << 15) +#define RT5645_SCB_SWAP_SFT 15 +#define RT5645_SCB_SWAP_DIS (0x0 << 15) +#define RT5645_SCB_SWAP_EN (0x1 << 15) +#define RT5645_SCB_MASK (0x1 << 14) +#define RT5645_SCB_SFT 14 +#define RT5645_SCB_DIS (0x0 << 14) +#define RT5645_SCB_EN (0x1 << 14) + +/* Baseback Control (0xcf) */ +#define RT5645_BB_MASK (0x1 << 15) +#define RT5645_BB_SFT 15 +#define RT5645_BB_DIS (0x0 << 15) +#define RT5645_BB_EN (0x1 << 15) +#define RT5645_BB_CT_MASK (0x7 << 12) +#define RT5645_BB_CT_SFT 12 +#define RT5645_BB_CT_A (0x0 << 12) +#define RT5645_BB_CT_B (0x1 << 12) +#define RT5645_BB_CT_C (0x2 << 12) +#define RT5645_BB_CT_D (0x3 << 12) +#define RT5645_M_BB_L_MASK (0x1 << 9) +#define RT5645_M_BB_L_SFT 9 +#define RT5645_M_BB_R_MASK (0x1 << 8) +#define RT5645_M_BB_R_SFT 8 +#define RT5645_M_BB_HPF_L_MASK (0x1 << 7) +#define RT5645_M_BB_HPF_L_SFT 7 +#define RT5645_M_BB_HPF_R_MASK (0x1 << 6) +#define RT5645_M_BB_HPF_R_SFT 6 +#define RT5645_G_BB_BST_MASK (0x3f) +#define RT5645_G_BB_BST_SFT 0 +#define RT5645_G_BB_BST_25DB 0x14 + +/* MP3 Plus Control 1 (0xd0) */ +#define RT5645_M_MP3_L_MASK (0x1 << 15) +#define RT5645_M_MP3_L_SFT 15 +#define RT5645_M_MP3_R_MASK (0x1 << 14) +#define RT5645_M_MP3_R_SFT 14 +#define RT5645_M_MP3_MASK (0x1 << 13) +#define RT5645_M_MP3_SFT 13 +#define RT5645_M_MP3_DIS (0x0 << 13) +#define RT5645_M_MP3_EN (0x1 << 13) +#define RT5645_EG_MP3_MASK (0x1f << 8) +#define RT5645_EG_MP3_SFT 8 +#define RT5645_MP3_HLP_MASK (0x1 << 7) +#define RT5645_MP3_HLP_SFT 7 +#define RT5645_MP3_HLP_DIS (0x0 << 7) +#define RT5645_MP3_HLP_EN (0x1 << 7) +#define RT5645_M_MP3_ORG_L_MASK (0x1 << 6) +#define RT5645_M_MP3_ORG_L_SFT 6 +#define RT5645_M_MP3_ORG_R_MASK (0x1 << 5) +#define RT5645_M_MP3_ORG_R_SFT 5 + +/* MP3 Plus Control 2 (0xd1) */ +#define RT5645_MP3_WT_MASK (0x1 << 13) +#define RT5645_MP3_WT_SFT 13 +#define RT5645_MP3_WT_1_4 (0x0 << 13) +#define RT5645_MP3_WT_1_2 (0x1 << 13) +#define RT5645_OG_MP3_MASK (0x1f << 8) +#define RT5645_OG_MP3_SFT 8 +#define RT5645_HG_MP3_MASK (0x3f) +#define RT5645_HG_MP3_SFT 0 + +/* 3D HP Control 1 (0xd2) */ +#define RT5645_3D_CF_MASK (0x1 << 15) +#define RT5645_3D_CF_SFT 15 +#define RT5645_3D_CF_DIS (0x0 << 15) +#define RT5645_3D_CF_EN (0x1 << 15) +#define RT5645_3D_HP_MASK (0x1 << 14) +#define RT5645_3D_HP_SFT 14 +#define RT5645_3D_HP_DIS (0x0 << 14) +#define RT5645_3D_HP_EN (0x1 << 14) +#define RT5645_3D_BT_MASK (0x1 << 13) +#define RT5645_3D_BT_SFT 13 +#define RT5645_3D_BT_DIS (0x0 << 13) +#define RT5645_3D_BT_EN (0x1 << 13) +#define RT5645_3D_1F_MIX_MASK (0x3 << 11) +#define RT5645_3D_1F_MIX_SFT 11 +#define RT5645_3D_HP_M_MASK (0x1 << 10) +#define RT5645_3D_HP_M_SFT 10 +#define RT5645_3D_HP_M_SUR (0x0 << 10) +#define RT5645_3D_HP_M_FRO (0x1 << 10) +#define RT5645_M_3D_HRTF_MASK (0x1 << 9) +#define RT5645_M_3D_HRTF_SFT 9 +#define RT5645_M_3D_D2H_MASK (0x1 << 8) +#define RT5645_M_3D_D2H_SFT 8 +#define RT5645_M_3D_D2R_MASK (0x1 << 7) +#define RT5645_M_3D_D2R_SFT 7 +#define RT5645_M_3D_REVB_MASK (0x1 << 6) +#define RT5645_M_3D_REVB_SFT 6 + +/* Adjustable high pass filter control 1 (0xd3) */ +#define RT5645_2ND_HPF_MASK (0x1 << 15) +#define RT5645_2ND_HPF_SFT 15 +#define RT5645_2ND_HPF_DIS (0x0 << 15) +#define RT5645_2ND_HPF_EN (0x1 << 15) +#define RT5645_HPF_CF_L_MASK (0x7 << 12) +#define RT5645_HPF_CF_L_SFT 12 +#define RT5645_1ST_HPF_MASK (0x1 << 11) +#define RT5645_1ST_HPF_SFT 11 +#define RT5645_1ST_HPF_DIS (0x0 << 11) +#define RT5645_1ST_HPF_EN (0x1 << 11) +#define RT5645_HPF_CF_R_MASK (0x7 << 8) +#define RT5645_HPF_CF_R_SFT 8 +#define RT5645_ZD_T_MASK (0x3 << 6) +#define RT5645_ZD_T_SFT 6 +#define RT5645_ZD_F_MASK (0x3 << 4) +#define RT5645_ZD_F_SFT 4 +#define RT5645_ZD_F_IM (0x0 << 4) +#define RT5645_ZD_F_ZC_IM (0x1 << 4) +#define RT5645_ZD_F_ZC_IOD (0x2 << 4) +#define RT5645_ZD_F_UN (0x3 << 4) + +/* HP calibration control and Amp detection (0xd6) */ +#define RT5645_SI_DAC_MASK (0x1 << 11) +#define RT5645_SI_DAC_SFT 11 +#define RT5645_SI_DAC_AUTO (0x0 << 11) +#define RT5645_SI_DAC_TEST (0x1 << 11) +#define RT5645_DC_CAL_M_MASK (0x1 << 10) +#define RT5645_DC_CAL_M_SFT 10 +#define RT5645_DC_CAL_M_CAL (0x0 << 10) +#define RT5645_DC_CAL_M_NOR (0x1 << 10) +#define RT5645_DC_CAL_MASK (0x1 << 9) +#define RT5645_DC_CAL_SFT 9 +#define RT5645_DC_CAL_DIS (0x0 << 9) +#define RT5645_DC_CAL_EN (0x1 << 9) +#define RT5645_HPD_RCV_MASK (0x7 << 6) +#define RT5645_HPD_RCV_SFT 6 +#define RT5645_HPD_PS_MASK (0x1 << 5) +#define RT5645_HPD_PS_SFT 5 +#define RT5645_HPD_PS_DIS (0x0 << 5) +#define RT5645_HPD_PS_EN (0x1 << 5) +#define RT5645_CAL_M_MASK (0x1 << 4) +#define RT5645_CAL_M_SFT 4 +#define RT5645_CAL_M_DEP (0x0 << 4) +#define RT5645_CAL_M_CAL (0x1 << 4) +#define RT5645_CAL_MASK (0x1 << 3) +#define RT5645_CAL_SFT 3 +#define RT5645_CAL_DIS (0x0 << 3) +#define RT5645_CAL_EN (0x1 << 3) +#define RT5645_CAL_TEST_MASK (0x1 << 2) +#define RT5645_CAL_TEST_SFT 2 +#define RT5645_CAL_TEST_DIS (0x0 << 2) +#define RT5645_CAL_TEST_EN (0x1 << 2) +#define RT5645_CAL_P_MASK (0x3) +#define RT5645_CAL_P_SFT 0 +#define RT5645_CAL_P_NONE (0x0) +#define RT5645_CAL_P_CAL (0x1) +#define RT5645_CAL_P_DAC_CAL (0x2) + +/* Soft volume and zero cross control 1 (0xd9) */ +#define RT5645_SV_MASK (0x1 << 15) +#define RT5645_SV_SFT 15 +#define RT5645_SV_DIS (0x0 << 15) +#define RT5645_SV_EN (0x1 << 15) +#define RT5645_SPO_SV_MASK (0x1 << 14) +#define RT5645_SPO_SV_SFT 14 +#define RT5645_SPO_SV_DIS (0x0 << 14) +#define RT5645_SPO_SV_EN (0x1 << 14) +#define RT5645_OUT_SV_MASK (0x1 << 13) +#define RT5645_OUT_SV_SFT 13 +#define RT5645_OUT_SV_DIS (0x0 << 13) +#define RT5645_OUT_SV_EN (0x1 << 13) +#define RT5645_HP_SV_MASK (0x1 << 12) +#define RT5645_HP_SV_SFT 12 +#define RT5645_HP_SV_DIS (0x0 << 12) +#define RT5645_HP_SV_EN (0x1 << 12) +#define RT5645_ZCD_DIG_MASK (0x1 << 11) +#define RT5645_ZCD_DIG_SFT 11 +#define RT5645_ZCD_DIG_DIS (0x0 << 11) +#define RT5645_ZCD_DIG_EN (0x1 << 11) +#define RT5645_ZCD_MASK (0x1 << 10) +#define RT5645_ZCD_SFT 10 +#define RT5645_ZCD_PD (0x0 << 10) +#define RT5645_ZCD_PU (0x1 << 10) +#define RT5645_M_ZCD_MASK (0x3f << 4) +#define RT5645_M_ZCD_SFT 4 +#define RT5645_M_ZCD_RM_L (0x1 << 9) +#define RT5645_M_ZCD_RM_R (0x1 << 8) +#define RT5645_M_ZCD_SM_L (0x1 << 7) +#define RT5645_M_ZCD_SM_R (0x1 << 6) +#define RT5645_M_ZCD_OM_L (0x1 << 5) +#define RT5645_M_ZCD_OM_R (0x1 << 4) +#define RT5645_SV_DLY_MASK (0xf) +#define RT5645_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0xda) */ +#define RT5645_ZCD_HP_MASK (0x1 << 15) +#define RT5645_ZCD_HP_SFT 15 +#define RT5645_ZCD_HP_DIS (0x0 << 15) +#define RT5645_ZCD_HP_EN (0x1 << 15) + + +/* Codec Private Register definition */ +/* DAC ADC Digital Volume (0x00) */ +#define RT5645_DA1_ZDET_SFT 6 + +/* 3D Speaker Control (0x63) */ +#define RT5645_3D_SPK_MASK (0x1 << 15) +#define RT5645_3D_SPK_SFT 15 +#define RT5645_3D_SPK_DIS (0x0 << 15) +#define RT5645_3D_SPK_EN (0x1 << 15) +#define RT5645_3D_SPK_M_MASK (0x3 << 13) +#define RT5645_3D_SPK_M_SFT 13 +#define RT5645_3D_SPK_CG_MASK (0x1f << 8) +#define RT5645_3D_SPK_CG_SFT 8 +#define RT5645_3D_SPK_SG_MASK (0x1f) +#define RT5645_3D_SPK_SG_SFT 0 + +/* Wind Noise Detection Control 1 (0x6c) */ +#define RT5645_WND_MASK (0x1 << 15) +#define RT5645_WND_SFT 15 +#define RT5645_WND_DIS (0x0 << 15) +#define RT5645_WND_EN (0x1 << 15) + +/* Wind Noise Detection Control 2 (0x6d) */ +#define RT5645_WND_FC_NW_MASK (0x3f << 10) +#define RT5645_WND_FC_NW_SFT 10 +#define RT5645_WND_FC_WK_MASK (0x3f << 4) +#define RT5645_WND_FC_WK_SFT 4 + +/* Wind Noise Detection Control 3 (0x6e) */ +#define RT5645_HPF_FC_MASK (0x3f << 6) +#define RT5645_HPF_FC_SFT 6 +#define RT5645_WND_FC_ST_MASK (0x3f) +#define RT5645_WND_FC_ST_SFT 0 + +/* Wind Noise Detection Control 4 (0x6f) */ +#define RT5645_WND_TH_LO_MASK (0x3ff) +#define RT5645_WND_TH_LO_SFT 0 + +/* Wind Noise Detection Control 5 (0x70) */ +#define RT5645_WND_TH_HI_MASK (0x3ff) +#define RT5645_WND_TH_HI_SFT 0 + +/* Wind Noise Detection Control 8 (0x73) */ +#define RT5645_WND_WIND_MASK (0x1 << 13) /* Read-Only */ +#define RT5645_WND_WIND_SFT 13 +#define RT5645_WND_STRONG_MASK (0x1 << 12) /* Read-Only */ +#define RT5645_WND_STRONG_SFT 12 +enum { + RT5645_NO_WIND, + RT5645_BREEZE, + RT5645_STORM, +}; + +/* Dipole Speaker Interface (0x75) */ +#define RT5645_DP_ATT_MASK (0x3 << 14) +#define RT5645_DP_ATT_SFT 14 +#define RT5645_DP_SPK_MASK (0x1 << 10) +#define RT5645_DP_SPK_SFT 10 +#define RT5645_DP_SPK_DIS (0x0 << 10) +#define RT5645_DP_SPK_EN (0x1 << 10) + +/* EQ Pre Volume Control (0xb3) */ +#define RT5645_EQ_PRE_VOL_MASK (0xffff) +#define RT5645_EQ_PRE_VOL_SFT 0 + +/* EQ Post Volume Control (0xb4) */ +#define RT5645_EQ_PST_VOL_MASK (0xffff) +#define RT5645_EQ_PST_VOL_SFT 0 + +/* Jack Detect Control 3 (0xf8) */ +#define RT5645_CMP_MIC_IN_DET_MASK (0x7 << 12) +#define RT5645_JD_CBJ_EN (0x1 << 7) +#define RT5645_JD_CBJ_POL (0x1 << 6) +#define RT5645_JD_TRI_CBJ_SEL_MASK (0x7 << 3) +#define RT5645_JD_TRI_CBJ_SEL_SFT (3) +#define RT5645_JD_TRI_HPO_SEL_MASK (0x7) +#define RT5645_JD_TRI_HPO_SEL_SFT (0) +#define RT5645_JD_F_GPIO_JD1 (0x0) +#define RT5645_JD_F_JD1_1 (0x1) +#define RT5645_JD_F_JD1_2 (0x2) +#define RT5645_JD_F_JD2 (0x3) +#define RT5645_JD_F_JD3 (0x4) +#define RT5645_JD_F_GPIO_JD2 (0x5) +#define RT5645_JD_F_MX0B_12 (0x6) + +/* Digital Misc Control (0xfa) */ +#define RT5645_RST_DSP (0x1 << 13) +#define RT5645_IF1_ADC1_IN1_SEL (0x1 << 12) +#define RT5645_IF1_ADC1_IN1_SFT 12 +#define RT5645_IF1_ADC1_IN2_SEL (0x1 << 11) +#define RT5645_IF1_ADC1_IN2_SFT 11 +#define RT5645_IF1_ADC2_IN1_SEL (0x1 << 10) +#define RT5645_IF1_ADC2_IN1_SFT 10 +#define RT5645_DIG_GATE_CTRL 0x1 + +/* General Control2 (0xfb) */ +#define RT5645_RXDC_SRC_MASK (0x1 << 7) +#define RT5645_RXDC_SRC_STO (0x0 << 7) +#define RT5645_RXDC_SRC_MONO (0x1 << 7) +#define RT5645_RXDC_SRC_SFT (7) +#define RT5645_MICBIAS1_POW_CTRL_SEL_MASK (0x1 << 5) +#define RT5645_MICBIAS1_POW_CTRL_SEL_A (0x0 << 5) +#define RT5645_MICBIAS1_POW_CTRL_SEL_M (0x1 << 5) +#define RT5645_MICBIAS2_POW_CTRL_SEL_MASK (0x1 << 4) +#define RT5645_MICBIAS2_POW_CTRL_SEL_A (0x0 << 4) +#define RT5645_MICBIAS2_POW_CTRL_SEL_M (0x1 << 4) +#define RT5645_RXDP2_SEL_MASK (0x1 << 3) +#define RT5645_RXDP2_SEL_IF2 (0x0 << 3) +#define RT5645_RXDP2_SEL_ADC (0x1 << 3) +#define RT5645_RXDP2_SEL_SFT (3) + +/* General Control3 (0xfc) */ +#define RT5645_JD_PSV_MODE (0x1 << 12) +#define RT5645_IRQ_CLK_GATE_CTRL (0x1 << 11) +#define RT5645_DET_CLK_MASK (0x3 << 9) +#define RT5645_DET_CLK_DIS (0x0 << 9) +#define RT5645_DET_CLK_MODE1 (0x1 << 9) +#define RT5645_DET_CLK_MODE2 (0x2 << 9) +#define RT5645_MICINDET_MANU (0x1 << 7) +#define RT5645_RING2_SLEEVE_GND (0x1 << 5) + +/* Vendor ID (0xfd) */ +#define RT5645_VER_C 0x2 +#define RT5645_VER_D 0x3 + + +/* Volume Rescale */ +#define RT5645_VOL_RSCL_MAX 0x27 +#define RT5645_VOL_RSCL_RANGE 0x1F +/* Debug String Length */ +#define RT5645_REG_DISP_LEN 23 + + +/* System Clock Source */ +enum { + RT5645_SCLK_S_MCLK, + RT5645_SCLK_S_PLL1, + RT5645_SCLK_S_RCCLK, +}; + +/* PLL1 Source */ +enum { + RT5645_PLL1_S_MCLK, + RT5645_PLL1_S_BCLK1, + RT5645_PLL1_S_BCLK2, +}; + +enum { + RT5645_AIF1, + RT5645_AIF2, + RT5645_AIFS, +}; + +enum { + RT5645_DMIC1_DISABLE, + RT5645_DMIC_DATA_IN2P, + RT5645_DMIC_DATA_GPIO6, + RT5645_DMIC_DATA_GPIO10, + RT5645_DMIC_DATA_GPIO12, +}; + +enum { + RT5645_DMIC2_DISABLE, + RT5645_DMIC_DATA_IN2N, + RT5645_DMIC_DATA_GPIO5, + RT5645_DMIC_DATA_GPIO11, +}; + +enum { + CODEC_TYPE_RT5645, + CODEC_TYPE_RT5650, +}; + +/* filter mask */ +enum { + RT5645_DA_STEREO_FILTER = 0x1, + RT5645_DA_MONO_L_FILTER = (0x1 << 1), + RT5645_DA_MONO_R_FILTER = (0x1 << 2), + RT5645_AD_STEREO_FILTER = (0x1 << 3), + RT5645_AD_MONO_L_FILTER = (0x1 << 4), + RT5645_AD_MONO_R_FILTER = (0x1 << 5), +}; + +int rt5645_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +int rt5645_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hp_jack, struct snd_soc_jack *mic_jack, + struct snd_soc_jack *btn_jack); +#endif /* __RT5645_H__ */ diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c new file mode 100644 index 000000000..318a4c9b3 --- /dev/null +++ b/sound/soc/codecs/rt5651.c @@ -0,0 +1,2234 @@ +/* + * rt5651.c -- RT5651 ALSA SoC audio codec driver + * + * Copyright 2014 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5651.h" + +#define RT5651_DEVICE_ID_VALUE 0x6281 + +#define RT5651_PR_RANGE_BASE (0xff + 1) +#define RT5651_PR_SPACING 0x100 + +#define RT5651_PR_BASE (RT5651_PR_RANGE_BASE + (0 * RT5651_PR_SPACING)) + +static const struct regmap_range_cfg rt5651_ranges[] = { + { .name = "PR", .range_min = RT5651_PR_BASE, + .range_max = RT5651_PR_BASE + 0xb4, + .selector_reg = RT5651_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT5651_PRIV_DATA, + .window_len = 0x1, }, +}; + +static const struct reg_sequence init_list[] = { + {RT5651_PR_BASE + 0x3d, 0x3e00}, +}; + +static const struct reg_default rt5651_reg[] = { + { 0x00, 0x0000 }, + { 0x02, 0xc8c8 }, + { 0x03, 0xc8c8 }, + { 0x05, 0x0000 }, + { 0x0d, 0x0000 }, + { 0x0e, 0x0000 }, + { 0x0f, 0x0808 }, + { 0x10, 0x0808 }, + { 0x19, 0xafaf }, + { 0x1a, 0xafaf }, + { 0x1b, 0x0c00 }, + { 0x1c, 0x2f2f }, + { 0x1d, 0x2f2f }, + { 0x1e, 0x0000 }, + { 0x27, 0x7860 }, + { 0x28, 0x7070 }, + { 0x29, 0x8080 }, + { 0x2a, 0x5252 }, + { 0x2b, 0x5454 }, + { 0x2f, 0x0000 }, + { 0x30, 0x5000 }, + { 0x3b, 0x0000 }, + { 0x3c, 0x006f }, + { 0x3d, 0x0000 }, + { 0x3e, 0x006f }, + { 0x45, 0x6000 }, + { 0x4d, 0x0000 }, + { 0x4e, 0x0000 }, + { 0x4f, 0x0279 }, + { 0x50, 0x0000 }, + { 0x51, 0x0000 }, + { 0x52, 0x0279 }, + { 0x53, 0xf000 }, + { 0x61, 0x0000 }, + { 0x62, 0x0000 }, + { 0x63, 0x00c0 }, + { 0x64, 0x0000 }, + { 0x65, 0x0000 }, + { 0x66, 0x0000 }, + { 0x70, 0x8000 }, + { 0x71, 0x8000 }, + { 0x73, 0x1104 }, + { 0x74, 0x0c00 }, + { 0x75, 0x1400 }, + { 0x77, 0x0c00 }, + { 0x78, 0x4000 }, + { 0x79, 0x0123 }, + { 0x80, 0x0000 }, + { 0x81, 0x0000 }, + { 0x82, 0x0000 }, + { 0x83, 0x0800 }, + { 0x84, 0x0000 }, + { 0x85, 0x0008 }, + { 0x89, 0x0000 }, + { 0x8e, 0x0004 }, + { 0x8f, 0x1100 }, + { 0x90, 0x0000 }, + { 0x93, 0x2000 }, + { 0x94, 0x0200 }, + { 0xb0, 0x2080 }, + { 0xb1, 0x0000 }, + { 0xb4, 0x2206 }, + { 0xb5, 0x1f00 }, + { 0xb6, 0x0000 }, + { 0xbb, 0x0000 }, + { 0xbc, 0x0000 }, + { 0xbd, 0x0000 }, + { 0xbe, 0x0000 }, + { 0xbf, 0x0000 }, + { 0xc0, 0x0400 }, + { 0xc1, 0x0000 }, + { 0xc2, 0x0000 }, + { 0xcf, 0x0013 }, + { 0xd0, 0x0680 }, + { 0xd1, 0x1c17 }, + { 0xd3, 0xb320 }, + { 0xd9, 0x0809 }, + { 0xfa, 0x0010 }, + { 0xfe, 0x10ec }, + { 0xff, 0x6281 }, +}; + +static bool rt5651_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5651_ranges); i++) { + if ((reg >= rt5651_ranges[i].window_start && + reg <= rt5651_ranges[i].window_start + + rt5651_ranges[i].window_len) || + (reg >= rt5651_ranges[i].range_min && + reg <= rt5651_ranges[i].range_max)) { + return true; + } + } + + switch (reg) { + case RT5651_RESET: + case RT5651_PRIV_DATA: + case RT5651_EQ_CTRL1: + case RT5651_ALC_1: + case RT5651_IRQ_CTRL2: + case RT5651_INT_IRQ_ST: + case RT5651_PGM_REG_ARR1: + case RT5651_PGM_REG_ARR3: + case RT5651_VENDOR_ID: + case RT5651_DEVICE_ID: + return true; + default: + return false; + } +} + +static bool rt5651_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5651_ranges); i++) { + if ((reg >= rt5651_ranges[i].window_start && + reg <= rt5651_ranges[i].window_start + + rt5651_ranges[i].window_len) || + (reg >= rt5651_ranges[i].range_min && + reg <= rt5651_ranges[i].range_max)) { + return true; + } + } + + switch (reg) { + case RT5651_RESET: + case RT5651_VERSION_ID: + case RT5651_VENDOR_ID: + case RT5651_DEVICE_ID: + case RT5651_HP_VOL: + case RT5651_LOUT_CTRL1: + case RT5651_LOUT_CTRL2: + case RT5651_IN1_IN2: + case RT5651_IN3: + case RT5651_INL1_INR1_VOL: + case RT5651_INL2_INR2_VOL: + case RT5651_DAC1_DIG_VOL: + case RT5651_DAC2_DIG_VOL: + case RT5651_DAC2_CTRL: + case RT5651_ADC_DIG_VOL: + case RT5651_ADC_DATA: + case RT5651_ADC_BST_VOL: + case RT5651_STO1_ADC_MIXER: + case RT5651_STO2_ADC_MIXER: + case RT5651_AD_DA_MIXER: + case RT5651_STO_DAC_MIXER: + case RT5651_DD_MIXER: + case RT5651_DIG_INF_DATA: + case RT5651_PDM_CTL: + case RT5651_REC_L1_MIXER: + case RT5651_REC_L2_MIXER: + case RT5651_REC_R1_MIXER: + case RT5651_REC_R2_MIXER: + case RT5651_HPO_MIXER: + case RT5651_OUT_L1_MIXER: + case RT5651_OUT_L2_MIXER: + case RT5651_OUT_L3_MIXER: + case RT5651_OUT_R1_MIXER: + case RT5651_OUT_R2_MIXER: + case RT5651_OUT_R3_MIXER: + case RT5651_LOUT_MIXER: + case RT5651_PWR_DIG1: + case RT5651_PWR_DIG2: + case RT5651_PWR_ANLG1: + case RT5651_PWR_ANLG2: + case RT5651_PWR_MIXER: + case RT5651_PWR_VOL: + case RT5651_PRIV_INDEX: + case RT5651_PRIV_DATA: + case RT5651_I2S1_SDP: + case RT5651_I2S2_SDP: + case RT5651_ADDA_CLK1: + case RT5651_ADDA_CLK2: + case RT5651_DMIC: + case RT5651_TDM_CTL_1: + case RT5651_TDM_CTL_2: + case RT5651_TDM_CTL_3: + case RT5651_GLB_CLK: + case RT5651_PLL_CTRL1: + case RT5651_PLL_CTRL2: + case RT5651_PLL_MODE_1: + case RT5651_PLL_MODE_2: + case RT5651_PLL_MODE_3: + case RT5651_PLL_MODE_4: + case RT5651_PLL_MODE_5: + case RT5651_PLL_MODE_6: + case RT5651_PLL_MODE_7: + case RT5651_DEPOP_M1: + case RT5651_DEPOP_M2: + case RT5651_DEPOP_M3: + case RT5651_CHARGE_PUMP: + case RT5651_MICBIAS: + case RT5651_A_JD_CTL1: + case RT5651_EQ_CTRL1: + case RT5651_EQ_CTRL2: + case RT5651_ALC_1: + case RT5651_ALC_2: + case RT5651_ALC_3: + case RT5651_JD_CTRL1: + case RT5651_JD_CTRL2: + case RT5651_IRQ_CTRL1: + case RT5651_IRQ_CTRL2: + case RT5651_INT_IRQ_ST: + case RT5651_GPIO_CTRL1: + case RT5651_GPIO_CTRL2: + case RT5651_GPIO_CTRL3: + case RT5651_PGM_REG_ARR1: + case RT5651_PGM_REG_ARR2: + case RT5651_PGM_REG_ARR3: + case RT5651_PGM_REG_ARR4: + case RT5651_PGM_REG_ARR5: + case RT5651_SCB_FUNC: + case RT5651_SCB_CTRL: + case RT5651_BASE_BACK: + case RT5651_MP3_PLUS1: + case RT5651_MP3_PLUS2: + case RT5651_ADJ_HPF_CTRL1: + case RT5651_ADJ_HPF_CTRL2: + case RT5651_HP_CALIB_AMP_DET: + case RT5651_HP_CALIB2: + case RT5651_SV_ZCD1: + case RT5651_SV_ZCD2: + case RT5651_D_MISC: + case RT5651_DUMMY2: + case RT5651_DUMMY3: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_MINMAX(dac_vol_tlv, -6562, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_MINMAX(adc_vol_tlv, -1762, 3000); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* Interface data select */ +static const char * const rt5651_data_select[] = { + "Normal", "Swap", "left copy to right", "right copy to left"}; + +static SOC_ENUM_SINGLE_DECL(rt5651_if2_dac_enum, RT5651_DIG_INF_DATA, + RT5651_IF2_DAC_SEL_SFT, rt5651_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5651_if2_adc_enum, RT5651_DIG_INF_DATA, + RT5651_IF2_ADC_SEL_SFT, rt5651_data_select); + +static const struct snd_kcontrol_new rt5651_snd_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE_TLV("HP Playback Volume", RT5651_HP_VOL, + RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, 39, 1, out_vol_tlv), + /* OUTPUT Control */ + SOC_DOUBLE_TLV("OUT Playback Volume", RT5651_LOUT_CTRL1, + RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE("DAC2 Playback Switch", RT5651_DAC2_CTRL, + RT5651_M_DAC_L2_VOL_SFT, RT5651_M_DAC_R2_VOL_SFT, 1, 1), + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5651_DAC1_DIG_VOL, + RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, + 175, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("Mono DAC Playback Volume", RT5651_DAC2_DIG_VOL, + RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, + 175, 0, dac_vol_tlv), + /* IN1/IN2/IN3 Control */ + SOC_SINGLE_TLV("IN1 Boost", RT5651_IN1_IN2, + RT5651_BST_SFT1, 8, 0, bst_tlv), + SOC_SINGLE_TLV("IN2 Boost", RT5651_IN1_IN2, + RT5651_BST_SFT2, 8, 0, bst_tlv), + SOC_SINGLE_TLV("IN3 Boost", RT5651_IN3, + RT5651_BST_SFT1, 8, 0, bst_tlv), + /* INL/INR Volume Control */ + SOC_DOUBLE_TLV("IN Capture Volume", RT5651_INL1_INR1_VOL, + RT5651_INL_VOL_SFT, RT5651_INR_VOL_SFT, + 31, 1, in_vol_tlv), + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC Capture Switch", RT5651_ADC_DIG_VOL, + RT5651_L_MUTE_SFT, RT5651_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("ADC Capture Volume", RT5651_ADC_DIG_VOL, + RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, + 127, 0, adc_vol_tlv), + SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5651_ADC_DATA, + RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, + 127, 0, adc_vol_tlv), + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("ADC Boost Gain", RT5651_ADC_BST_VOL, + RT5651_ADC_L_BST_SFT, RT5651_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + /* ASRC */ + SOC_SINGLE("IF1 ASRC Switch", RT5651_PLL_MODE_1, + RT5651_STO1_T_SFT, 1, 0), + SOC_SINGLE("IF2 ASRC Switch", RT5651_PLL_MODE_1, + RT5651_STO2_T_SFT, 1, 0), + SOC_SINGLE("DMIC ASRC Switch", RT5651_PLL_MODE_1, + RT5651_DMIC_1_M_SFT, 1, 0), + + SOC_ENUM("ADC IF2 Data Switch", rt5651_if2_adc_enum), + SOC_ENUM("DAC IF2 Data Switch", rt5651_if2_dac_enum), +}; + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + int idx, rate; + + rate = rt5651->sysclk / rl6231_get_pre_div(rt5651->regmap, + RT5651_ADDA_CLK1, RT5651_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rate); + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else + snd_soc_component_update_bits(component, RT5651_DMIC, RT5651_DMIC_CLK_MASK, + idx << RT5651_DMIC_CLK_SFT); + + return idx; +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5651_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5651_STO1_ADC_MIXER, + RT5651_M_STO1_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5651_STO1_ADC_MIXER, + RT5651_M_STO1_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5651_STO1_ADC_MIXER, + RT5651_M_STO1_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5651_STO1_ADC_MIXER, + RT5651_M_STO1_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_sto2_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5651_STO2_ADC_MIXER, + RT5651_M_STO2_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5651_STO2_ADC_MIXER, + RT5651_M_STO2_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_sto2_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5651_STO2_ADC_MIXER, + RT5651_M_STO2_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5651_STO2_ADC_MIXER, + RT5651_M_STO2_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5651_AD_DA_MIXER, + RT5651_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INF1 Switch", RT5651_AD_DA_MIXER, + RT5651_M_IF1_DAC_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5651_AD_DA_MIXER, + RT5651_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INF1 Switch", RT5651_AD_DA_MIXER, + RT5651_M_IF1_DAC_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5651_STO_DAC_MIXER, + RT5651_M_DAC_L1_MIXL_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5651_STO_DAC_MIXER, + RT5651_M_DAC_L2_MIXL_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5651_STO_DAC_MIXER, + RT5651_M_DAC_R1_MIXL_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5651_STO_DAC_MIXER, + RT5651_M_DAC_R1_MIXR_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5651_STO_DAC_MIXER, + RT5651_M_DAC_R2_MIXR_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5651_STO_DAC_MIXER, + RT5651_M_DAC_L1_MIXR_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_dd_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5651_DD_MIXER, + RT5651_M_STO_DD_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5651_DD_MIXER, + RT5651_M_STO_DD_L2_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5651_DD_MIXER, + RT5651_M_STO_DD_R2_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_dd_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5651_DD_MIXER, + RT5651_M_STO_DD_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5651_DD_MIXER, + RT5651_M_STO_DD_R2_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5651_DD_MIXER, + RT5651_M_STO_DD_L2_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5651_rec_l_mix[] = { + SOC_DAPM_SINGLE("INL1 Switch", RT5651_REC_L2_MIXER, + RT5651_M_IN1_L_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5651_REC_L2_MIXER, + RT5651_M_BST3_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5651_REC_L2_MIXER, + RT5651_M_BST2_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5651_REC_L2_MIXER, + RT5651_M_BST1_RM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_rec_r_mix[] = { + SOC_DAPM_SINGLE("INR1 Switch", RT5651_REC_R2_MIXER, + RT5651_M_IN1_R_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5651_REC_R2_MIXER, + RT5651_M_BST3_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5651_REC_R2_MIXER, + RT5651_M_BST2_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5651_REC_R2_MIXER, + RT5651_M_BST1_RM_R_SFT, 1, 1), +}; + +/* Analog Output Mixer */ + +static const struct snd_kcontrol_new rt5651_out_l_mix[] = { + SOC_DAPM_SINGLE("BST1 Switch", RT5651_OUT_L3_MIXER, + RT5651_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5651_OUT_L3_MIXER, + RT5651_M_BST2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL1 Switch", RT5651_OUT_L3_MIXER, + RT5651_M_IN1_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXL Switch", RT5651_OUT_L3_MIXER, + RT5651_M_RM_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5651_OUT_L3_MIXER, + RT5651_M_DAC_L1_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_out_r_mix[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5651_OUT_R3_MIXER, + RT5651_M_BST2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5651_OUT_R3_MIXER, + RT5651_M_BST1_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR1 Switch", RT5651_OUT_R3_MIXER, + RT5651_M_IN1_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("REC MIXR Switch", RT5651_OUT_R3_MIXER, + RT5651_M_RM_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5651_OUT_R3_MIXER, + RT5651_M_DAC_R1_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_hpo_mix[] = { + SOC_DAPM_SINGLE("HPO MIX DAC1 Switch", RT5651_HPO_MIXER, + RT5651_M_DAC1_HM_SFT, 1, 1), + SOC_DAPM_SINGLE("HPO MIX HPVOL Switch", RT5651_HPO_MIXER, + RT5651_M_HPVOL_HM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5651_lout_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5651_LOUT_MIXER, + RT5651_M_DAC_L1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5651_LOUT_MIXER, + RT5651_M_DAC_R1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL L Switch", RT5651_LOUT_MIXER, + RT5651_M_OV_L_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL R Switch", RT5651_LOUT_MIXER, + RT5651_M_OV_R_LM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new outvol_l_control = + SOC_DAPM_SINGLE("Switch", RT5651_LOUT_CTRL1, + RT5651_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new outvol_r_control = + SOC_DAPM_SINGLE("Switch", RT5651_LOUT_CTRL1, + RT5651_VOL_R_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_l_mute_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5651_LOUT_CTRL1, + RT5651_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_r_mute_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5651_LOUT_CTRL1, + RT5651_R_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpovol_l_control = + SOC_DAPM_SINGLE("Switch", RT5651_HP_VOL, + RT5651_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new hpovol_r_control = + SOC_DAPM_SINGLE("Switch", RT5651_HP_VOL, + RT5651_VOL_R_SFT, 1, 1); + +static const struct snd_kcontrol_new hpo_l_mute_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5651_HP_VOL, + RT5651_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpo_r_mute_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5651_HP_VOL, + RT5651_R_MUTE_SFT, 1, 1); + +/* Stereo ADC source */ +static const char * const rt5651_stereo1_adc1_src[] = {"DD MIX", "ADC"}; + +static SOC_ENUM_SINGLE_DECL( + rt5651_stereo1_adc1_enum, RT5651_STO1_ADC_MIXER, + RT5651_STO1_ADC_1_SRC_SFT, rt5651_stereo1_adc1_src); + +static const struct snd_kcontrol_new rt5651_sto1_adc_l1_mux = + SOC_DAPM_ENUM("Stereo1 ADC L1 source", rt5651_stereo1_adc1_enum); + +static const struct snd_kcontrol_new rt5651_sto1_adc_r1_mux = + SOC_DAPM_ENUM("Stereo1 ADC R1 source", rt5651_stereo1_adc1_enum); + +static const char * const rt5651_stereo1_adc2_src[] = {"DMIC", "DD MIX"}; + +static SOC_ENUM_SINGLE_DECL( + rt5651_stereo1_adc2_enum, RT5651_STO1_ADC_MIXER, + RT5651_STO1_ADC_2_SRC_SFT, rt5651_stereo1_adc2_src); + +static const struct snd_kcontrol_new rt5651_sto1_adc_l2_mux = + SOC_DAPM_ENUM("Stereo1 ADC L2 source", rt5651_stereo1_adc2_enum); + +static const struct snd_kcontrol_new rt5651_sto1_adc_r2_mux = + SOC_DAPM_ENUM("Stereo1 ADC R2 source", rt5651_stereo1_adc2_enum); + +/* Mono ADC source */ +static const char * const rt5651_sto2_adc_l1_src[] = {"DD MIXL", "ADCL"}; + +static SOC_ENUM_SINGLE_DECL( + rt5651_sto2_adc_l1_enum, RT5651_STO1_ADC_MIXER, + RT5651_STO2_ADC_L1_SRC_SFT, rt5651_sto2_adc_l1_src); + +static const struct snd_kcontrol_new rt5651_sto2_adc_l1_mux = + SOC_DAPM_ENUM("Stereo2 ADC1 left source", rt5651_sto2_adc_l1_enum); + +static const char * const rt5651_sto2_adc_l2_src[] = {"DMIC L", "DD MIXL"}; + +static SOC_ENUM_SINGLE_DECL( + rt5651_sto2_adc_l2_enum, RT5651_STO1_ADC_MIXER, + RT5651_STO2_ADC_L2_SRC_SFT, rt5651_sto2_adc_l2_src); + +static const struct snd_kcontrol_new rt5651_sto2_adc_l2_mux = + SOC_DAPM_ENUM("Stereo2 ADC2 left source", rt5651_sto2_adc_l2_enum); + +static const char * const rt5651_sto2_adc_r1_src[] = {"DD MIXR", "ADCR"}; + +static SOC_ENUM_SINGLE_DECL( + rt5651_sto2_adc_r1_enum, RT5651_STO1_ADC_MIXER, + RT5651_STO2_ADC_R1_SRC_SFT, rt5651_sto2_adc_r1_src); + +static const struct snd_kcontrol_new rt5651_sto2_adc_r1_mux = + SOC_DAPM_ENUM("Stereo2 ADC1 right source", rt5651_sto2_adc_r1_enum); + +static const char * const rt5651_sto2_adc_r2_src[] = {"DMIC R", "DD MIXR"}; + +static SOC_ENUM_SINGLE_DECL( + rt5651_sto2_adc_r2_enum, RT5651_STO1_ADC_MIXER, + RT5651_STO2_ADC_R2_SRC_SFT, rt5651_sto2_adc_r2_src); + +static const struct snd_kcontrol_new rt5651_sto2_adc_r2_mux = + SOC_DAPM_ENUM("Stereo2 ADC2 right source", rt5651_sto2_adc_r2_enum); + +/* DAC2 channel source */ + +static const char * const rt5651_dac_src[] = {"IF1", "IF2"}; + +static SOC_ENUM_SINGLE_DECL(rt5651_dac_l2_enum, RT5651_DAC2_CTRL, + RT5651_SEL_DAC_L2_SFT, rt5651_dac_src); + +static const struct snd_kcontrol_new rt5651_dac_l2_mux = + SOC_DAPM_ENUM("DAC2 left channel source", rt5651_dac_l2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5651_dac_r2_enum, RT5651_DAC2_CTRL, + RT5651_SEL_DAC_R2_SFT, rt5651_dac_src); + +static const struct snd_kcontrol_new rt5651_dac_r2_mux = + SOC_DAPM_ENUM("DAC2 right channel source", rt5651_dac_r2_enum); + +/* IF2_ADC channel source */ + +static const char * const rt5651_adc_src[] = {"IF1 ADC1", "IF1 ADC2"}; + +static SOC_ENUM_SINGLE_DECL(rt5651_if2_adc_src_enum, RT5651_DIG_INF_DATA, + RT5651_IF2_ADC_SRC_SFT, rt5651_adc_src); + +static const struct snd_kcontrol_new rt5651_if2_adc_src_mux = + SOC_DAPM_ENUM("IF2 ADC channel source", rt5651_if2_adc_src_enum); + +/* PDM select */ +static const char * const rt5651_pdm_sel[] = {"DD MIX", "Stereo DAC MIX"}; + +static SOC_ENUM_SINGLE_DECL( + rt5651_pdm_l_sel_enum, RT5651_PDM_CTL, + RT5651_PDM_L_SEL_SFT, rt5651_pdm_sel); + +static SOC_ENUM_SINGLE_DECL( + rt5651_pdm_r_sel_enum, RT5651_PDM_CTL, + RT5651_PDM_R_SEL_SFT, rt5651_pdm_sel); + +static const struct snd_kcontrol_new rt5651_pdm_l_mux = + SOC_DAPM_ENUM("PDM L select", rt5651_pdm_l_sel_enum); + +static const struct snd_kcontrol_new rt5651_pdm_r_mux = + SOC_DAPM_ENUM("PDM R select", rt5651_pdm_r_sel_enum); + +static int rt5651_amp_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* depop parameters */ + regmap_update_bits(rt5651->regmap, RT5651_PR_BASE + + RT5651_CHPUMP_INT_REG1, 0x0700, 0x0200); + regmap_update_bits(rt5651->regmap, RT5651_DEPOP_M2, + RT5651_DEPOP_MASK, RT5651_DEPOP_MAN); + regmap_update_bits(rt5651->regmap, RT5651_DEPOP_M1, + RT5651_HP_CP_MASK | RT5651_HP_SG_MASK | + RT5651_HP_CB_MASK, RT5651_HP_CP_PU | + RT5651_HP_SG_DIS | RT5651_HP_CB_PU); + regmap_write(rt5651->regmap, RT5651_PR_BASE + + RT5651_HP_DCC_INT1, 0x9f00); + /* headphone amp power on */ + regmap_update_bits(rt5651->regmap, RT5651_PWR_ANLG1, + RT5651_PWR_FV1 | RT5651_PWR_FV2, 0); + regmap_update_bits(rt5651->regmap, RT5651_PWR_ANLG1, + RT5651_PWR_HA, + RT5651_PWR_HA); + usleep_range(10000, 15000); + regmap_update_bits(rt5651->regmap, RT5651_PWR_ANLG1, + RT5651_PWR_FV1 | RT5651_PWR_FV2 , + RT5651_PWR_FV1 | RT5651_PWR_FV2); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5651_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* headphone unmute sequence */ + regmap_update_bits(rt5651->regmap, RT5651_DEPOP_M2, + RT5651_DEPOP_MASK | RT5651_DIG_DP_MASK, + RT5651_DEPOP_AUTO | RT5651_DIG_DP_EN); + regmap_update_bits(rt5651->regmap, RT5651_CHARGE_PUMP, + RT5651_PM_HP_MASK, RT5651_PM_HP_HV); + + regmap_update_bits(rt5651->regmap, RT5651_DEPOP_M3, + RT5651_CP_FQ1_MASK | RT5651_CP_FQ2_MASK | + RT5651_CP_FQ3_MASK, + (RT5651_CP_FQ_192_KHZ << RT5651_CP_FQ1_SFT) | + (RT5651_CP_FQ_12_KHZ << RT5651_CP_FQ2_SFT) | + (RT5651_CP_FQ_192_KHZ << RT5651_CP_FQ3_SFT)); + + regmap_write(rt5651->regmap, RT5651_PR_BASE + + RT5651_MAMP_INT_REG2, 0x1c00); + regmap_update_bits(rt5651->regmap, RT5651_DEPOP_M1, + RT5651_HP_CP_MASK | RT5651_HP_SG_MASK, + RT5651_HP_CP_PD | RT5651_HP_SG_EN); + regmap_update_bits(rt5651->regmap, RT5651_PR_BASE + + RT5651_CHPUMP_INT_REG1, 0x0700, 0x0400); + rt5651->hp_mute = 0; + break; + + case SND_SOC_DAPM_PRE_PMD: + rt5651->hp_mute = 1; + usleep_range(70000, 75000); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5651_hp_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!rt5651->hp_mute) + usleep_range(80000, 85000); + + break; + + default: + return 0; + } + + return 0; +} + +static int rt5651_bst1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_BST1_OP2, RT5651_PWR_BST1_OP2); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_BST1_OP2, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5651_bst2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_BST2_OP2, RT5651_PWR_BST2_OP2); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_BST2_OP2, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5651_bst3_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_BST3_OP2, RT5651_PWR_BST3_OP2); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_BST3_OP2, 0); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5651_dapm_widgets[] = { + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("I2S1 ASRC", 1, RT5651_PLL_MODE_2, + 15, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5651_PLL_MODE_2, + 14, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("STO1 DAC ASRC", 1, RT5651_PLL_MODE_2, + 13, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("STO2 DAC ASRC", 1, RT5651_PLL_MODE_2, + 12, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC ASRC", 1, RT5651_PLL_MODE_2, + 11, 0, NULL, 0), + + /* micbias */ + SND_SOC_DAPM_SUPPLY("LDO", RT5651_PWR_ANLG1, + RT5651_PWR_LDO_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("micbias1", RT5651_PWR_ANLG2, + RT5651_PWR_MB1_BIT, 0, NULL, 0), + /* Input Lines */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("MIC3"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + SND_SOC_DAPM_INPUT("IN3P"), + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + SND_SOC_DAPM_SUPPLY("DMIC CLK", RT5651_DMIC, RT5651_DMIC_1_EN_SFT, + 0, set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + /* Boost */ + SND_SOC_DAPM_PGA_E("BST1", RT5651_PWR_ANLG2, + RT5651_PWR_BST1_BIT, 0, NULL, 0, rt5651_bst1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("BST2", RT5651_PWR_ANLG2, + RT5651_PWR_BST2_BIT, 0, NULL, 0, rt5651_bst2_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("BST3", RT5651_PWR_ANLG2, + RT5651_PWR_BST3_BIT, 0, NULL, 0, rt5651_bst3_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + /* Input Volume */ + SND_SOC_DAPM_PGA("INL1 VOL", RT5651_PWR_VOL, + RT5651_PWR_IN1_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR1 VOL", RT5651_PWR_VOL, + RT5651_PWR_IN1_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INL2 VOL", RT5651_PWR_VOL, + RT5651_PWR_IN2_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR2 VOL", RT5651_PWR_VOL, + RT5651_PWR_IN2_R_BIT, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIXL", RT5651_PWR_MIXER, RT5651_PWR_RM_L_BIT, 0, + rt5651_rec_l_mix, ARRAY_SIZE(rt5651_rec_l_mix)), + SND_SOC_DAPM_MIXER("RECMIXR", RT5651_PWR_MIXER, RT5651_PWR_RM_R_BIT, 0, + rt5651_rec_r_mix, ARRAY_SIZE(rt5651_rec_r_mix)), + /* ADCs */ + SND_SOC_DAPM_ADC("ADC L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC R", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC L Power", RT5651_PWR_DIG1, + RT5651_PWR_ADC_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC R Power", RT5651_PWR_DIG1, + RT5651_PWR_ADC_R_BIT, 0, NULL, 0), + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto1_adc_l2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto1_adc_r2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto1_adc_l1_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto1_adc_r1_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto2_adc_l2_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto2_adc_l1_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto2_adc_r1_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5651_sto2_adc_r2_mux), + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("Stereo1 Filter", RT5651_PWR_DIG2, + RT5651_PWR_ADC_STO1_F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Stereo2 Filter", RT5651_PWR_DIG2, + RT5651_PWR_ADC_STO2_F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5651_sto1_adc_l_mix, + ARRAY_SIZE(rt5651_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5651_sto1_adc_r_mix, + ARRAY_SIZE(rt5651_sto1_adc_r_mix)), + SND_SOC_DAPM_MIXER("Stereo2 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5651_sto2_adc_l_mix, + ARRAY_SIZE(rt5651_sto2_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo2 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5651_sto2_adc_r_mix, + ARRAY_SIZE(rt5651_sto2_adc_r_mix)), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5651_PWR_DIG1, + RT5651_PWR_I2S1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2", RT5651_PWR_DIG1, + RT5651_PWR_I2S2_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("IF2 ADC", SND_SOC_NOPM, 0, 0, + &rt5651_if2_adc_src_mux), + + /* Digital Interface Select */ + + SND_SOC_DAPM_MUX("PDM L Mux", RT5651_PDM_CTL, + RT5651_M_PDM_L_SFT, 1, &rt5651_pdm_l_mux), + SND_SOC_DAPM_MUX("PDM R Mux", RT5651_PDM_CTL, + RT5651_M_PDM_R_SFT, 1, &rt5651_pdm_r_mux), + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Audio DSP */ + SND_SOC_DAPM_PGA("Audio DSP", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5651_dac_l_mix, ARRAY_SIZE(rt5651_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5651_dac_r_mix, ARRAY_SIZE(rt5651_dac_r_mix)), + + /* DAC2 channel Mux */ + SND_SOC_DAPM_MUX("DAC L2 Mux", SND_SOC_NOPM, 0, 0, &rt5651_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Mux", SND_SOC_NOPM, 0, 0, &rt5651_dac_r2_mux), + SND_SOC_DAPM_PGA("DAC L2 Volume", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC R2 Volume", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Stero1 DAC Power", RT5651_PWR_DIG2, + RT5651_PWR_DAC_STO1_F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Stero2 DAC Power", RT5651_PWR_DIG2, + RT5651_PWR_DAC_STO2_F_BIT, 0, NULL, 0), + /* DAC Mixer */ + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5651_sto_dac_l_mix, + ARRAY_SIZE(rt5651_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5651_sto_dac_r_mix, + ARRAY_SIZE(rt5651_sto_dac_r_mix)), + SND_SOC_DAPM_MIXER("DD MIXL", SND_SOC_NOPM, 0, 0, + rt5651_dd_dac_l_mix, + ARRAY_SIZE(rt5651_dd_dac_l_mix)), + SND_SOC_DAPM_MIXER("DD MIXR", SND_SOC_NOPM, 0, 0, + rt5651_dd_dac_r_mix, + ARRAY_SIZE(rt5651_dd_dac_r_mix)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("DAC L1 Power", RT5651_PWR_DIG1, + RT5651_PWR_DAC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC R1 Power", RT5651_PWR_DIG1, + RT5651_PWR_DAC_R1_BIT, 0, NULL, 0), + /* OUT Mixer */ + SND_SOC_DAPM_MIXER("OUT MIXL", RT5651_PWR_MIXER, RT5651_PWR_OM_L_BIT, + 0, rt5651_out_l_mix, ARRAY_SIZE(rt5651_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5651_PWR_MIXER, RT5651_PWR_OM_R_BIT, + 0, rt5651_out_r_mix, ARRAY_SIZE(rt5651_out_r_mix)), + /* Ouput Volume */ + SND_SOC_DAPM_SWITCH("OUTVOL L", RT5651_PWR_VOL, + RT5651_PWR_OV_L_BIT, 0, &outvol_l_control), + SND_SOC_DAPM_SWITCH("OUTVOL R", RT5651_PWR_VOL, + RT5651_PWR_OV_R_BIT, 0, &outvol_r_control), + SND_SOC_DAPM_SWITCH("HPOVOL L", RT5651_PWR_VOL, + RT5651_PWR_HV_L_BIT, 0, &hpovol_l_control), + SND_SOC_DAPM_SWITCH("HPOVOL R", RT5651_PWR_VOL, + RT5651_PWR_HV_R_BIT, 0, &hpovol_r_control), + SND_SOC_DAPM_PGA("INL1", RT5651_PWR_VOL, + RT5651_PWR_IN1_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR1", RT5651_PWR_VOL, + RT5651_PWR_IN1_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INL2", RT5651_PWR_VOL, + RT5651_PWR_IN2_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR2", RT5651_PWR_VOL, + RT5651_PWR_IN2_R_BIT, 0, NULL, 0), + /* HPO/LOUT/Mono Mixer */ + SND_SOC_DAPM_MIXER("HPOL MIX", SND_SOC_NOPM, 0, 0, + rt5651_hpo_mix, ARRAY_SIZE(rt5651_hpo_mix)), + SND_SOC_DAPM_MIXER("HPOR MIX", SND_SOC_NOPM, 0, 0, + rt5651_hpo_mix, ARRAY_SIZE(rt5651_hpo_mix)), + SND_SOC_DAPM_SUPPLY("HP L Amp", RT5651_PWR_ANLG1, + RT5651_PWR_HP_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HP R Amp", RT5651_PWR_ANLG1, + RT5651_PWR_HP_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LOUT MIX", RT5651_PWR_ANLG1, RT5651_PWR_LM_BIT, 0, + rt5651_lout_mix, ARRAY_SIZE(rt5651_lout_mix)), + + SND_SOC_DAPM_SUPPLY("Amp Power", RT5651_PWR_ANLG1, + RT5651_PWR_HA_BIT, 0, rt5651_amp_power_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, rt5651_hp_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SWITCH("HPO L Playback", SND_SOC_NOPM, 0, 0, + &hpo_l_mute_control), + SND_SOC_DAPM_SWITCH("HPO R Playback", SND_SOC_NOPM, 0, 0, + &hpo_r_mute_control), + SND_SOC_DAPM_SWITCH("LOUT L Playback", SND_SOC_NOPM, 0, 0, + &lout_l_mute_control), + SND_SOC_DAPM_SWITCH("LOUT R Playback", SND_SOC_NOPM, 0, 0, + &lout_r_mute_control), + SND_SOC_DAPM_POST("HP Post", rt5651_hp_post_event), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), + SND_SOC_DAPM_OUTPUT("PDML"), + SND_SOC_DAPM_OUTPUT("PDMR"), +}; + +static const struct snd_soc_dapm_route rt5651_dapm_routes[] = { + {"Stero1 DAC Power", NULL, "STO1 DAC ASRC"}, + {"Stero2 DAC Power", NULL, "STO2 DAC ASRC"}, + {"I2S1", NULL, "I2S1 ASRC"}, + {"I2S2", NULL, "I2S2 ASRC"}, + + {"IN1P", NULL, "LDO"}, + {"IN2P", NULL, "LDO"}, + {"IN3P", NULL, "LDO"}, + + {"IN1P", NULL, "MIC1"}, + {"IN2P", NULL, "MIC2"}, + {"IN2N", NULL, "MIC2"}, + {"IN3P", NULL, "MIC3"}, + + {"BST1", NULL, "IN1P"}, + {"BST2", NULL, "IN2P"}, + {"BST2", NULL, "IN2N"}, + {"BST3", NULL, "IN3P"}, + + {"INL1 VOL", NULL, "IN2P"}, + {"INR1 VOL", NULL, "IN2N"}, + + {"RECMIXL", "INL1 Switch", "INL1 VOL"}, + {"RECMIXL", "BST3 Switch", "BST3"}, + {"RECMIXL", "BST2 Switch", "BST2"}, + {"RECMIXL", "BST1 Switch", "BST1"}, + + {"RECMIXR", "INR1 Switch", "INR1 VOL"}, + {"RECMIXR", "BST3 Switch", "BST3"}, + {"RECMIXR", "BST2 Switch", "BST2"}, + {"RECMIXR", "BST1 Switch", "BST1"}, + + {"ADC L", NULL, "RECMIXL"}, + {"ADC L", NULL, "ADC L Power"}, + {"ADC R", NULL, "RECMIXR"}, + {"ADC R", NULL, "ADC R Power"}, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC CLK"}, + + {"Stereo1 ADC L2 Mux", "DMIC", "DMIC L1"}, + {"Stereo1 ADC L2 Mux", "DD MIX", "DD MIXL"}, + {"Stereo1 ADC L1 Mux", "ADC", "ADC L"}, + {"Stereo1 ADC L1 Mux", "DD MIX", "DD MIXL"}, + + {"Stereo1 ADC R1 Mux", "ADC", "ADC R"}, + {"Stereo1 ADC R1 Mux", "DD MIX", "DD MIXR"}, + {"Stereo1 ADC R2 Mux", "DMIC", "DMIC R1"}, + {"Stereo1 ADC R2 Mux", "DD MIX", "DD MIXR"}, + + {"Stereo2 ADC L2 Mux", "DMIC L", "DMIC L1"}, + {"Stereo2 ADC L2 Mux", "DD MIXL", "DD MIXL"}, + {"Stereo2 ADC L1 Mux", "DD MIXL", "DD MIXL"}, + {"Stereo2 ADC L1 Mux", "ADCL", "ADC L"}, + + {"Stereo2 ADC R1 Mux", "DD MIXR", "DD MIXR"}, + {"Stereo2 ADC R1 Mux", "ADCR", "ADC R"}, + {"Stereo2 ADC R2 Mux", "DMIC R", "DMIC R1"}, + {"Stereo2 ADC R2 Mux", "DD MIXR", "DD MIXR"}, + + {"Stereo1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux"}, + {"Stereo1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux"}, + {"Stereo1 ADC MIXL", NULL, "Stereo1 Filter"}, + {"Stereo1 Filter", NULL, "ADC ASRC"}, + + {"Stereo1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux"}, + {"Stereo1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux"}, + {"Stereo1 ADC MIXR", NULL, "Stereo1 Filter"}, + + {"Stereo2 ADC MIXL", "ADC1 Switch", "Stereo2 ADC L1 Mux"}, + {"Stereo2 ADC MIXL", "ADC2 Switch", "Stereo2 ADC L2 Mux"}, + {"Stereo2 ADC MIXL", NULL, "Stereo2 Filter"}, + {"Stereo2 Filter", NULL, "ADC ASRC"}, + + {"Stereo2 ADC MIXR", "ADC1 Switch", "Stereo2 ADC R1 Mux"}, + {"Stereo2 ADC MIXR", "ADC2 Switch", "Stereo2 ADC R2 Mux"}, + {"Stereo2 ADC MIXR", NULL, "Stereo2 Filter"}, + + {"IF1 ADC2", NULL, "Stereo2 ADC MIXL"}, + {"IF1 ADC2", NULL, "Stereo2 ADC MIXR"}, + {"IF1 ADC1", NULL, "Stereo1 ADC MIXL"}, + {"IF1 ADC1", NULL, "Stereo1 ADC MIXR"}, + + {"IF1 ADC1", NULL, "I2S1"}, + + {"IF2 ADC", "IF1 ADC1", "IF1 ADC1"}, + {"IF2 ADC", "IF1 ADC2", "IF1 ADC2"}, + {"IF2 ADC", NULL, "I2S2"}, + + {"AIF1TX", NULL, "IF1 ADC1"}, + {"AIF1TX", NULL, "IF1 ADC2"}, + {"AIF2TX", NULL, "IF2 ADC"}, + + {"IF1 DAC", NULL, "AIF1RX"}, + {"IF1 DAC", NULL, "I2S1"}, + {"IF2 DAC", NULL, "AIF2RX"}, + {"IF2 DAC", NULL, "I2S2"}, + + {"IF1 DAC1 L", NULL, "IF1 DAC"}, + {"IF1 DAC1 R", NULL, "IF1 DAC"}, + {"IF1 DAC2 L", NULL, "IF1 DAC"}, + {"IF1 DAC2 R", NULL, "IF1 DAC"}, + {"IF2 DAC L", NULL, "IF2 DAC"}, + {"IF2 DAC R", NULL, "IF2 DAC"}, + + {"DAC MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL"}, + {"DAC MIXL", "INF1 Switch", "IF1 DAC1 L"}, + {"DAC MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR"}, + {"DAC MIXR", "INF1 Switch", "IF1 DAC1 R"}, + + {"Audio DSP", NULL, "DAC MIXL"}, + {"Audio DSP", NULL, "DAC MIXR"}, + + {"DAC L2 Mux", "IF1", "IF1 DAC2 L"}, + {"DAC L2 Mux", "IF2", "IF2 DAC L"}, + {"DAC L2 Volume", NULL, "DAC L2 Mux"}, + + {"DAC R2 Mux", "IF1", "IF1 DAC2 R"}, + {"DAC R2 Mux", "IF2", "IF2 DAC R"}, + {"DAC R2 Volume", NULL, "DAC R2 Mux"}, + + {"Stereo DAC MIXL", "DAC L1 Switch", "Audio DSP"}, + {"Stereo DAC MIXL", "DAC L2 Switch", "DAC L2 Volume"}, + {"Stereo DAC MIXL", "DAC R1 Switch", "DAC MIXR"}, + {"Stereo DAC MIXL", NULL, "Stero1 DAC Power"}, + {"Stereo DAC MIXL", NULL, "Stero2 DAC Power"}, + {"Stereo DAC MIXR", "DAC R1 Switch", "Audio DSP"}, + {"Stereo DAC MIXR", "DAC R2 Switch", "DAC R2 Volume"}, + {"Stereo DAC MIXR", "DAC L1 Switch", "DAC MIXL"}, + {"Stereo DAC MIXR", NULL, "Stero1 DAC Power"}, + {"Stereo DAC MIXR", NULL, "Stero2 DAC Power"}, + + {"PDM L Mux", "Stereo DAC MIX", "Stereo DAC MIXL"}, + {"PDM L Mux", "DD MIX", "DAC MIXL"}, + {"PDM R Mux", "Stereo DAC MIX", "Stereo DAC MIXR"}, + {"PDM R Mux", "DD MIX", "DAC MIXR"}, + + {"DAC L1", NULL, "Stereo DAC MIXL"}, + {"DAC L1", NULL, "DAC L1 Power"}, + {"DAC R1", NULL, "Stereo DAC MIXR"}, + {"DAC R1", NULL, "DAC R1 Power"}, + + {"DD MIXL", "DAC L1 Switch", "DAC MIXL"}, + {"DD MIXL", "DAC L2 Switch", "DAC L2 Volume"}, + {"DD MIXL", "DAC R2 Switch", "DAC R2 Volume"}, + {"DD MIXL", NULL, "Stero2 DAC Power"}, + + {"DD MIXR", "DAC R1 Switch", "DAC MIXR"}, + {"DD MIXR", "DAC R2 Switch", "DAC R2 Volume"}, + {"DD MIXR", "DAC L2 Switch", "DAC L2 Volume"}, + {"DD MIXR", NULL, "Stero2 DAC Power"}, + + {"OUT MIXL", "BST1 Switch", "BST1"}, + {"OUT MIXL", "BST2 Switch", "BST2"}, + {"OUT MIXL", "INL1 Switch", "INL1 VOL"}, + {"OUT MIXL", "REC MIXL Switch", "RECMIXL"}, + {"OUT MIXL", "DAC L1 Switch", "DAC L1"}, + + {"OUT MIXR", "BST2 Switch", "BST2"}, + {"OUT MIXR", "BST1 Switch", "BST1"}, + {"OUT MIXR", "INR1 Switch", "INR1 VOL"}, + {"OUT MIXR", "REC MIXR Switch", "RECMIXR"}, + {"OUT MIXR", "DAC R1 Switch", "DAC R1"}, + + {"HPOVOL L", "Switch", "OUT MIXL"}, + {"HPOVOL R", "Switch", "OUT MIXR"}, + {"OUTVOL L", "Switch", "OUT MIXL"}, + {"OUTVOL R", "Switch", "OUT MIXR"}, + + {"HPOL MIX", "HPO MIX DAC1 Switch", "DAC L1"}, + {"HPOL MIX", "HPO MIX HPVOL Switch", "HPOVOL L"}, + {"HPOL MIX", NULL, "HP L Amp"}, + {"HPOR MIX", "HPO MIX DAC1 Switch", "DAC R1"}, + {"HPOR MIX", "HPO MIX HPVOL Switch", "HPOVOL R"}, + {"HPOR MIX", NULL, "HP R Amp"}, + + {"LOUT MIX", "DAC L1 Switch", "DAC L1"}, + {"LOUT MIX", "DAC R1 Switch", "DAC R1"}, + {"LOUT MIX", "OUTVOL L Switch", "OUTVOL L"}, + {"LOUT MIX", "OUTVOL R Switch", "OUTVOL R"}, + + {"HP Amp", NULL, "HPOL MIX"}, + {"HP Amp", NULL, "HPOR MIX"}, + {"HP Amp", NULL, "Amp Power"}, + {"HPO L Playback", "Switch", "HP Amp"}, + {"HPO R Playback", "Switch", "HP Amp"}, + {"HPOL", NULL, "HPO L Playback"}, + {"HPOR", NULL, "HPO R Playback"}, + + {"LOUT L Playback", "Switch", "LOUT MIX"}, + {"LOUT R Playback", "Switch", "LOUT MIX"}, + {"LOUTL", NULL, "LOUT L Playback"}, + {"LOUTL", NULL, "Amp Power"}, + {"LOUTR", NULL, "LOUT R Playback"}, + {"LOUTR", NULL, "Amp Power"}, + + {"PDML", NULL, "PDM L Mux"}, + {"PDMR", NULL, "PDM R Mux"}, +}; + +static int rt5651_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int pre_div, bclk_ms, frame_size; + + rt5651->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5651->sysclk, rt5651->lrck[dai->id]); + + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting\n"); + return -EINVAL; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + bclk_ms = frame_size > 32 ? 1 : 0; + rt5651->bclk[dai->id] = rt5651->lrck[dai->id] * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5651->bclk[dai->id], rt5651->lrck[dai->id]); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len |= RT5651_I2S_DL_20; + break; + case 24: + val_len |= RT5651_I2S_DL_24; + break; + case 8: + val_len |= RT5651_I2S_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5651_AIF1: + mask_clk = RT5651_I2S_PD1_MASK; + val_clk = pre_div << RT5651_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5651_I2S1_SDP, + RT5651_I2S_DL_MASK, val_len); + snd_soc_component_update_bits(component, RT5651_ADDA_CLK1, mask_clk, val_clk); + break; + case RT5651_AIF2: + mask_clk = RT5651_I2S_BCLK_MS2_MASK | RT5651_I2S_PD2_MASK; + val_clk = pre_div << RT5651_I2S_PD2_SFT; + snd_soc_component_update_bits(component, RT5651_I2S2_SDP, + RT5651_I2S_DL_MASK, val_len); + snd_soc_component_update_bits(component, RT5651_ADDA_CLK1, mask_clk, val_clk); + break; + default: + dev_err(component->dev, "Wrong dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5651_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5651->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5651_I2S_MS_S; + rt5651->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5651_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5651_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5651_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5651_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5651_AIF1: + snd_soc_component_update_bits(component, RT5651_I2S1_SDP, + RT5651_I2S_MS_MASK | RT5651_I2S_BP_MASK | + RT5651_I2S_DF_MASK, reg_val); + break; + case RT5651_AIF2: + snd_soc_component_update_bits(component, RT5651_I2S2_SDP, + RT5651_I2S_MS_MASK | RT5651_I2S_BP_MASK | + RT5651_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Wrong dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5651_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + unsigned int pll_bit = 0; + + if (freq == rt5651->sysclk && clk_id == rt5651->sysclk_src) + return 0; + + switch (clk_id) { + case RT5651_SCLK_S_MCLK: + reg_val |= RT5651_SCLK_SRC_MCLK; + break; + case RT5651_SCLK_S_PLL1: + reg_val |= RT5651_SCLK_SRC_PLL1; + pll_bit |= RT5651_PWR_PLL; + break; + case RT5651_SCLK_S_RCCLK: + reg_val |= RT5651_SCLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_PLL, pll_bit); + snd_soc_component_update_bits(component, RT5651_GLB_CLK, + RT5651_SCLK_SRC_MASK, reg_val); + rt5651->sysclk = freq; + rt5651->sysclk_src = clk_id; + + dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + + return 0; +} + +static int rt5651_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5651->pll_src && freq_in == rt5651->pll_in && + freq_out == rt5651->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5651->pll_in = 0; + rt5651->pll_out = 0; + snd_soc_component_update_bits(component, RT5651_GLB_CLK, + RT5651_SCLK_SRC_MASK, RT5651_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5651_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5651_GLB_CLK, + RT5651_PLL1_SRC_MASK, RT5651_PLL1_SRC_MCLK); + break; + case RT5651_PLL1_S_BCLK1: + snd_soc_component_update_bits(component, RT5651_GLB_CLK, + RT5651_PLL1_SRC_MASK, RT5651_PLL1_SRC_BCLK1); + break; + case RT5651_PLL1_S_BCLK2: + snd_soc_component_update_bits(component, RT5651_GLB_CLK, + RT5651_PLL1_SRC_MASK, RT5651_PLL1_SRC_BCLK2); + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5651_PLL_CTRL1, + pll_code.n_code << RT5651_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5651_PLL_CTRL2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5651_PLL_M_SFT | + pll_code.m_bp << RT5651_PLL_M_BP_SFT); + + rt5651->pll_in = freq_in; + rt5651->pll_out = freq_out; + rt5651->pll_src = source; + + return 0; +} + +static int rt5651_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == snd_soc_component_get_bias_level(component)) { + if (snd_soc_component_read32(component, RT5651_PLL_MODE_1) & 0x9200) + snd_soc_component_update_bits(component, RT5651_D_MISC, + 0xc00, 0xc00); + } + break; + case SND_SOC_BIAS_STANDBY: + if (SND_SOC_BIAS_OFF == snd_soc_component_get_bias_level(component)) { + snd_soc_component_update_bits(component, RT5651_PWR_ANLG1, + RT5651_PWR_VREF1 | RT5651_PWR_MB | + RT5651_PWR_BG | RT5651_PWR_VREF2, + RT5651_PWR_VREF1 | RT5651_PWR_MB | + RT5651_PWR_BG | RT5651_PWR_VREF2); + usleep_range(10000, 15000); + snd_soc_component_update_bits(component, RT5651_PWR_ANLG1, + RT5651_PWR_FV1 | RT5651_PWR_FV2, + RT5651_PWR_FV1 | RT5651_PWR_FV2); + snd_soc_component_update_bits(component, RT5651_D_MISC, 0x1, 0x1); + } + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, RT5651_D_MISC, 0x0010); + snd_soc_component_write(component, RT5651_PWR_DIG1, 0x0000); + snd_soc_component_write(component, RT5651_PWR_DIG2, 0x0000); + snd_soc_component_write(component, RT5651_PWR_VOL, 0x0000); + snd_soc_component_write(component, RT5651_PWR_MIXER, 0x0000); + /* Do not touch the LDO voltage select bits on bias-off */ + snd_soc_component_update_bits(component, RT5651_PWR_ANLG1, + ~RT5651_PWR_LDO_DVO_MASK, 0); + /* Leave PLL1 and jack-detect power as is, all others off */ + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + ~(RT5651_PWR_PLL | RT5651_PWR_JD_M), 0); + break; + + default: + break; + } + + return 0; +} + +static void rt5651_enable_micbias1_for_ovcd(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + snd_soc_dapm_mutex_lock(dapm); + snd_soc_dapm_force_enable_pin_unlocked(dapm, "LDO"); + snd_soc_dapm_force_enable_pin_unlocked(dapm, "micbias1"); + /* OVCD is unreliable when used with RCCLK as sysclk-source */ + snd_soc_dapm_force_enable_pin_unlocked(dapm, "Platform Clock"); + snd_soc_dapm_sync_unlocked(dapm); + snd_soc_dapm_mutex_unlock(dapm); +} + +static void rt5651_disable_micbias1_for_ovcd(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + snd_soc_dapm_mutex_lock(dapm); + snd_soc_dapm_disable_pin_unlocked(dapm, "Platform Clock"); + snd_soc_dapm_disable_pin_unlocked(dapm, "micbias1"); + snd_soc_dapm_disable_pin_unlocked(dapm, "LDO"); + snd_soc_dapm_sync_unlocked(dapm); + snd_soc_dapm_mutex_unlock(dapm); +} + +static void rt5651_enable_micbias1_ovcd_irq(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + snd_soc_component_update_bits(component, RT5651_IRQ_CTRL2, + RT5651_IRQ_MB1_OC_MASK, RT5651_IRQ_MB1_OC_NOR); + rt5651->ovcd_irq_enabled = true; +} + +static void rt5651_disable_micbias1_ovcd_irq(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + snd_soc_component_update_bits(component, RT5651_IRQ_CTRL2, + RT5651_IRQ_MB1_OC_MASK, RT5651_IRQ_MB1_OC_BP); + rt5651->ovcd_irq_enabled = false; +} + +static void rt5651_clear_micbias1_ovcd(struct snd_soc_component *component) +{ + snd_soc_component_update_bits(component, RT5651_IRQ_CTRL2, + RT5651_MB1_OC_CLR, 0); +} + +static bool rt5651_micbias1_ovcd(struct snd_soc_component *component) +{ + int val; + + val = snd_soc_component_read32(component, RT5651_IRQ_CTRL2); + dev_dbg(component->dev, "irq ctrl2 %#04x\n", val); + + return (val & RT5651_MB1_OC_CLR); +} + +static bool rt5651_jack_inserted(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + int val; + + val = snd_soc_component_read32(component, RT5651_INT_IRQ_ST); + dev_dbg(component->dev, "irq status %#04x\n", val); + + switch (rt5651->jd_src) { + case RT5651_JD1_1: + val &= 0x1000; + break; + case RT5651_JD1_2: + val &= 0x2000; + break; + case RT5651_JD2: + val &= 0x4000; + break; + default: + break; + } + + return val == 0; +} + +/* Jack detect and button-press timings */ +#define JACK_SETTLE_TIME 100 /* milli seconds */ +#define JACK_DETECT_COUNT 5 +#define JACK_DETECT_MAXCOUNT 20 /* Aprox. 2 seconds worth of tries */ +#define JACK_UNPLUG_TIME 80 /* milli seconds */ +#define BP_POLL_TIME 10 /* milli seconds */ +#define BP_POLL_MAXCOUNT 200 /* assume something is wrong after this */ +#define BP_THRESHOLD 3 + +static void rt5651_start_button_press_work(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + rt5651->poll_count = 0; + rt5651->press_count = 0; + rt5651->release_count = 0; + rt5651->pressed = false; + rt5651->press_reported = false; + rt5651_clear_micbias1_ovcd(component); + schedule_delayed_work(&rt5651->bp_work, msecs_to_jiffies(BP_POLL_TIME)); +} + +static void rt5651_button_press_work(struct work_struct *work) +{ + struct rt5651_priv *rt5651 = + container_of(work, struct rt5651_priv, bp_work.work); + struct snd_soc_component *component = rt5651->component; + + /* Check the jack was not removed underneath us */ + if (!rt5651_jack_inserted(component)) + return; + + if (rt5651_micbias1_ovcd(component)) { + rt5651->release_count = 0; + rt5651->press_count++; + /* Remember till after JACK_UNPLUG_TIME wait */ + if (rt5651->press_count >= BP_THRESHOLD) + rt5651->pressed = true; + rt5651_clear_micbias1_ovcd(component); + } else { + rt5651->press_count = 0; + rt5651->release_count++; + } + + /* + * The pins get temporarily shorted on jack unplug, so we poll for + * at least JACK_UNPLUG_TIME milli-seconds before reporting a press. + */ + rt5651->poll_count++; + if (rt5651->poll_count < (JACK_UNPLUG_TIME / BP_POLL_TIME)) { + schedule_delayed_work(&rt5651->bp_work, + msecs_to_jiffies(BP_POLL_TIME)); + return; + } + + if (rt5651->pressed && !rt5651->press_reported) { + dev_dbg(component->dev, "headset button press\n"); + snd_soc_jack_report(rt5651->hp_jack, SND_JACK_BTN_0, + SND_JACK_BTN_0); + rt5651->press_reported = true; + } + + if (rt5651->release_count >= BP_THRESHOLD) { + if (rt5651->press_reported) { + dev_dbg(component->dev, "headset button release\n"); + snd_soc_jack_report(rt5651->hp_jack, 0, SND_JACK_BTN_0); + } + /* Re-enable OVCD IRQ to detect next press */ + rt5651_enable_micbias1_ovcd_irq(component); + return; /* Stop polling */ + } + + schedule_delayed_work(&rt5651->bp_work, msecs_to_jiffies(BP_POLL_TIME)); +} + +static int rt5651_detect_headset(struct snd_soc_component *component) +{ + int i, headset_count = 0, headphone_count = 0; + + /* + * We get the insertion event before the jack is fully inserted at which + * point the second ring on a TRRS connector may short the 2nd ring and + * sleeve contacts, also the overcurrent detection is not entirely + * reliable. So we try several times with a wait in between until we + * detect the same type JACK_DETECT_COUNT times in a row. + */ + for (i = 0; i < JACK_DETECT_MAXCOUNT; i++) { + /* Clear any previous over-current status flag */ + rt5651_clear_micbias1_ovcd(component); + + msleep(JACK_SETTLE_TIME); + + /* Check the jack is still connected before checking ovcd */ + if (!rt5651_jack_inserted(component)) + return 0; + + if (rt5651_micbias1_ovcd(component)) { + /* + * Over current detected, there is a short between the + * 2nd ring contact and the ground, so a TRS connector + * without a mic contact and thus plain headphones. + */ + dev_dbg(component->dev, "mic-gnd shorted\n"); + headset_count = 0; + headphone_count++; + if (headphone_count == JACK_DETECT_COUNT) + return SND_JACK_HEADPHONE; + } else { + dev_dbg(component->dev, "mic-gnd open\n"); + headphone_count = 0; + headset_count++; + if (headset_count == JACK_DETECT_COUNT) + return SND_JACK_HEADSET; + } + } + + dev_err(component->dev, "Error detecting headset vs headphones, bad contact?, assuming headphones\n"); + return SND_JACK_HEADPHONE; +} + +static void rt5651_jack_detect_work(struct work_struct *work) +{ + struct rt5651_priv *rt5651 = + container_of(work, struct rt5651_priv, jack_detect_work); + struct snd_soc_component *component = rt5651->component; + int report = 0; + + if (!rt5651_jack_inserted(component)) { + /* Jack removed, or spurious IRQ? */ + if (rt5651->hp_jack->status & SND_JACK_HEADPHONE) { + if (rt5651->hp_jack->status & SND_JACK_MICROPHONE) { + cancel_delayed_work_sync(&rt5651->bp_work); + rt5651_disable_micbias1_ovcd_irq(component); + rt5651_disable_micbias1_for_ovcd(component); + } + snd_soc_jack_report(rt5651->hp_jack, 0, + SND_JACK_HEADSET | SND_JACK_BTN_0); + dev_dbg(component->dev, "jack unplugged\n"); + } + } else if (!(rt5651->hp_jack->status & SND_JACK_HEADPHONE)) { + /* Jack inserted */ + WARN_ON(rt5651->ovcd_irq_enabled); + rt5651_enable_micbias1_for_ovcd(component); + report = rt5651_detect_headset(component); + if (report == SND_JACK_HEADSET) { + /* Enable ovcd IRQ for button press detect. */ + rt5651_enable_micbias1_ovcd_irq(component); + } else { + /* No more need for overcurrent detect. */ + rt5651_disable_micbias1_for_ovcd(component); + } + dev_dbg(component->dev, "detect report %#02x\n", report); + snd_soc_jack_report(rt5651->hp_jack, report, SND_JACK_HEADSET); + } else if (rt5651->ovcd_irq_enabled && rt5651_micbias1_ovcd(component)) { + dev_dbg(component->dev, "OVCD IRQ\n"); + + /* + * The ovcd IRQ keeps firing while the button is pressed, so + * we disable it and start polling the button until released. + * + * The disable will make the IRQ pin 0 again and since we get + * IRQs on both edges (so as to detect both jack plugin and + * unplug) this means we will immediately get another IRQ. + * The ovcd_irq_enabled check above makes the 2ND IRQ a NOP. + */ + rt5651_disable_micbias1_ovcd_irq(component); + rt5651_start_button_press_work(component); + + /* + * If the jack-detect IRQ flag goes high (unplug) after our + * above rt5651_jack_inserted() check and before we have + * disabled the OVCD IRQ, the IRQ pin will stay high and as + * we react to edges, we miss the unplug event -> recheck. + */ + queue_work(system_long_wq, &rt5651->jack_detect_work); + } +} + +static irqreturn_t rt5651_irq(int irq, void *data) +{ + struct rt5651_priv *rt5651 = data; + + queue_work(system_power_efficient_wq, &rt5651->jack_detect_work); + + return IRQ_HANDLED; +} + +static void rt5651_cancel_work(void *data) +{ + struct rt5651_priv *rt5651 = data; + + cancel_work_sync(&rt5651->jack_detect_work); + cancel_delayed_work_sync(&rt5651->bp_work); +} + +static void rt5651_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hp_jack) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + /* IRQ output on GPIO1 */ + snd_soc_component_update_bits(component, RT5651_GPIO_CTRL1, + RT5651_GP1_PIN_MASK, RT5651_GP1_PIN_IRQ); + + /* Select jack detect source */ + switch (rt5651->jd_src) { + case RT5651_JD1_1: + snd_soc_component_update_bits(component, RT5651_JD_CTRL2, + RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD1_1); + snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, + RT5651_JD1_1_IRQ_EN, RT5651_JD1_1_IRQ_EN); + break; + case RT5651_JD1_2: + snd_soc_component_update_bits(component, RT5651_JD_CTRL2, + RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD1_2); + snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, + RT5651_JD1_2_IRQ_EN, RT5651_JD1_2_IRQ_EN); + break; + case RT5651_JD2: + snd_soc_component_update_bits(component, RT5651_JD_CTRL2, + RT5651_JD_TRG_SEL_MASK, RT5651_JD_TRG_SEL_JD2); + snd_soc_component_update_bits(component, RT5651_IRQ_CTRL1, + RT5651_JD2_IRQ_EN, RT5651_JD2_IRQ_EN); + break; + case RT5651_JD_NULL: + return; + default: + dev_err(component->dev, "Currently only JD1_1 / JD1_2 / JD2 are supported\n"); + return; + } + + /* Enable jack detect power */ + snd_soc_component_update_bits(component, RT5651_PWR_ANLG2, + RT5651_PWR_JD_M, RT5651_PWR_JD_M); + + /* Set OVCD threshold current and scale-factor */ + snd_soc_component_write(component, RT5651_PR_BASE + RT5651_BIAS_CUR4, + 0xa800 | rt5651->ovcd_sf); + + snd_soc_component_update_bits(component, RT5651_MICBIAS, + RT5651_MIC1_OVCD_MASK | + RT5651_MIC1_OVTH_MASK | + RT5651_PWR_CLK12M_MASK | + RT5651_PWR_MB_MASK, + RT5651_MIC1_OVCD_EN | + rt5651->ovcd_th | + RT5651_PWR_MB_PU | + RT5651_PWR_CLK12M_PU); + + /* + * The over-current-detect is only reliable in detecting the absence + * of over-current, when the mic-contact in the jack is short-circuited, + * the hardware periodically retries if it can apply the bias-current + * leading to the ovcd status flip-flopping 1-0-1 with it being 0 about + * 10% of the time, as we poll the ovcd status bit we might hit that + * 10%, so we enable sticky mode and when checking OVCD we clear the + * status, msleep() a bit and then check to get a reliable reading. + */ + snd_soc_component_update_bits(component, RT5651_IRQ_CTRL2, + RT5651_MB1_OC_STKY_MASK, RT5651_MB1_OC_STKY_EN); + + rt5651->hp_jack = hp_jack; + if (rt5651->hp_jack->status & SND_JACK_MICROPHONE) { + rt5651_enable_micbias1_for_ovcd(component); + rt5651_enable_micbias1_ovcd_irq(component); + } + + enable_irq(rt5651->irq); + /* sync initial jack state */ + queue_work(system_power_efficient_wq, &rt5651->jack_detect_work); +} + +static void rt5651_disable_jack_detect(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + disable_irq(rt5651->irq); + rt5651_cancel_work(rt5651); + + if (rt5651->hp_jack->status & SND_JACK_MICROPHONE) { + rt5651_disable_micbias1_ovcd_irq(component); + rt5651_disable_micbias1_for_ovcd(component); + snd_soc_jack_report(rt5651->hp_jack, 0, SND_JACK_BTN_0); + } + + rt5651->hp_jack = NULL; +} + +static int rt5651_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + if (jack) + rt5651_enable_jack_detect(component, jack); + else + rt5651_disable_jack_detect(component); + + return 0; +} + +/* + * Note on some platforms the platform code may need to add device-properties, + * rather then relying only on properties set by the firmware. Therefor the + * property parsing MUST be done from the component driver's probe function, + * rather then from the i2c driver's probe function, so that the platform-code + * can attach extra properties before calling snd_soc_register_card(). + */ +static void rt5651_apply_properties(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + u32 val; + + if (device_property_read_bool(component->dev, "realtek,in2-differential")) + snd_soc_component_update_bits(component, RT5651_IN1_IN2, + RT5651_IN_DF2, RT5651_IN_DF2); + + if (device_property_read_bool(component->dev, "realtek,dmic-en")) + snd_soc_component_update_bits(component, RT5651_GPIO_CTRL1, + RT5651_GP2_PIN_MASK, RT5651_GP2_PIN_DMIC1_SCL); + + if (device_property_read_u32(component->dev, + "realtek,jack-detect-source", &val) == 0) + rt5651->jd_src = val; + + /* + * Testing on various boards has shown that good defaults for the OVCD + * threshold and scale-factor are 2000µA and 0.75. For an effective + * limit of 1500µA, this seems to be more reliable then 1500µA and 1.0. + */ + rt5651->ovcd_th = RT5651_MIC1_OVTH_2000UA; + rt5651->ovcd_sf = RT5651_MIC_OVCD_SF_0P75; + + if (device_property_read_u32(component->dev, + "realtek,over-current-threshold-microamp", &val) == 0) { + switch (val) { + case 600: + rt5651->ovcd_th = RT5651_MIC1_OVTH_600UA; + break; + case 1500: + rt5651->ovcd_th = RT5651_MIC1_OVTH_1500UA; + break; + case 2000: + rt5651->ovcd_th = RT5651_MIC1_OVTH_2000UA; + break; + default: + dev_warn(component->dev, "Warning: Invalid over-current-threshold-microamp value: %d, defaulting to 2000uA\n", + val); + } + } + + if (device_property_read_u32(component->dev, + "realtek,over-current-scale-factor", &val) == 0) { + if (val <= RT5651_OVCD_SF_1P5) + rt5651->ovcd_sf = val << RT5651_MIC_OVCD_SF_SFT; + else + dev_warn(component->dev, "Warning: Invalid over-current-scale-factor value: %d, defaulting to 0.75\n", + val); + } +} + +static int rt5651_probe(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + rt5651->component = component; + + snd_soc_component_update_bits(component, RT5651_PWR_ANLG1, + RT5651_PWR_LDO_DVO_MASK, RT5651_PWR_LDO_DVO_1_2V); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + rt5651_apply_properties(component); + + return 0; +} + +#ifdef CONFIG_PM +static int rt5651_suspend(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5651->regmap, true); + regcache_mark_dirty(rt5651->regmap); + return 0; +} + +static int rt5651_resume(struct snd_soc_component *component) +{ + struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5651->regmap, false); + snd_soc_component_cache_sync(component); + + return 0; +} +#else +#define rt5651_suspend NULL +#define rt5651_resume NULL +#endif + +#define RT5651_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define RT5651_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5651_aif_dai_ops = { + .hw_params = rt5651_hw_params, + .set_fmt = rt5651_set_dai_fmt, + .set_sysclk = rt5651_set_dai_sysclk, + .set_pll = rt5651_set_dai_pll, +}; + +static struct snd_soc_dai_driver rt5651_dai[] = { + { + .name = "rt5651-aif1", + .id = RT5651_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5651_STEREO_RATES, + .formats = RT5651_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5651_STEREO_RATES, + .formats = RT5651_FORMATS, + }, + .ops = &rt5651_aif_dai_ops, + }, + { + .name = "rt5651-aif2", + .id = RT5651_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5651_STEREO_RATES, + .formats = RT5651_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5651_STEREO_RATES, + .formats = RT5651_FORMATS, + }, + .ops = &rt5651_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5651 = { + .probe = rt5651_probe, + .suspend = rt5651_suspend, + .resume = rt5651_resume, + .set_bias_level = rt5651_set_bias_level, + .set_jack = rt5651_set_jack, + .controls = rt5651_snd_controls, + .num_controls = ARRAY_SIZE(rt5651_snd_controls), + .dapm_widgets = rt5651_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5651_dapm_widgets), + .dapm_routes = rt5651_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5651_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5651_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = RT5651_DEVICE_ID + 1 + (ARRAY_SIZE(rt5651_ranges) * + RT5651_PR_SPACING), + .volatile_reg = rt5651_volatile_register, + .readable_reg = rt5651_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5651_reg, + .num_reg_defaults = ARRAY_SIZE(rt5651_reg), + .ranges = rt5651_ranges, + .num_ranges = ARRAY_SIZE(rt5651_ranges), + .use_single_rw = true, +}; + +#if defined(CONFIG_OF) +static const struct of_device_id rt5651_of_match[] = { + { .compatible = "realtek,rt5651", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5651_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5651_acpi_match[] = { + { "10EC5651", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, rt5651_acpi_match); +#endif + +static const struct i2c_device_id rt5651_i2c_id[] = { + { "rt5651", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5651_i2c_id); + +/* + * Note this function MUST not look at device-properties, see the comment + * above rt5651_apply_properties(). + */ +static int rt5651_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5651_priv *rt5651; + int ret; + + rt5651 = devm_kzalloc(&i2c->dev, sizeof(*rt5651), + GFP_KERNEL); + if (NULL == rt5651) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5651); + + rt5651->regmap = devm_regmap_init_i2c(i2c, &rt5651_regmap); + if (IS_ERR(rt5651->regmap)) { + ret = PTR_ERR(rt5651->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt5651->regmap, RT5651_DEVICE_ID, &ret); + if (ret != RT5651_DEVICE_ID_VALUE) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5651\n", ret); + return -ENODEV; + } + + regmap_write(rt5651->regmap, RT5651_RESET, 0); + + ret = regmap_register_patch(rt5651->regmap, init_list, + ARRAY_SIZE(init_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + rt5651->irq = i2c->irq; + rt5651->hp_mute = 1; + + INIT_DELAYED_WORK(&rt5651->bp_work, rt5651_button_press_work); + INIT_WORK(&rt5651->jack_detect_work, rt5651_jack_detect_work); + + /* Make sure work is stopped on probe-error / remove */ + ret = devm_add_action_or_reset(&i2c->dev, rt5651_cancel_work, rt5651); + if (ret) + return ret; + + ret = devm_request_irq(&i2c->dev, rt5651->irq, rt5651_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5651", rt5651); + if (ret == 0) { + /* Gets re-enabled by rt5651_set_jack() */ + disable_irq(rt5651->irq); + } else { + dev_warn(&i2c->dev, "Failed to reguest IRQ %d: %d\n", + rt5651->irq, ret); + rt5651->irq = -ENXIO; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5651, + rt5651_dai, ARRAY_SIZE(rt5651_dai)); + + return ret; +} + +static struct i2c_driver rt5651_i2c_driver = { + .driver = { + .name = "rt5651", + .acpi_match_table = ACPI_PTR(rt5651_acpi_match), + .of_match_table = of_match_ptr(rt5651_of_match), + }, + .probe = rt5651_i2c_probe, + .id_table = rt5651_i2c_id, +}; +module_i2c_driver(rt5651_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5651 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5651.h b/sound/soc/codecs/rt5651.h new file mode 100644 index 000000000..ac6de6fb5 --- /dev/null +++ b/sound/soc/codecs/rt5651.h @@ -0,0 +1,2103 @@ +/* + * rt5651.h -- RT5651 ALSA SoC audio driver + * + * Copyright 2011 Realtek Microelectronics + * Author: Johnny Hsu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5651_H__ +#define __RT5651_H__ + +#include + +/* Info */ +#define RT5651_RESET 0x00 +#define RT5651_VERSION_ID 0xfd +#define RT5651_VENDOR_ID 0xfe +#define RT5651_DEVICE_ID 0xff +/* I/O - Output */ +#define RT5651_HP_VOL 0x02 +#define RT5651_LOUT_CTRL1 0x03 +#define RT5651_LOUT_CTRL2 0x05 +/* I/O - Input */ +#define RT5651_IN1_IN2 0x0d +#define RT5651_IN3 0x0e +#define RT5651_INL1_INR1_VOL 0x0f +#define RT5651_INL2_INR2_VOL 0x10 +/* I/O - ADC/DAC/DMIC */ +#define RT5651_DAC1_DIG_VOL 0x19 +#define RT5651_DAC2_DIG_VOL 0x1a +#define RT5651_DAC2_CTRL 0x1b +#define RT5651_ADC_DIG_VOL 0x1c +#define RT5651_ADC_DATA 0x1d +#define RT5651_ADC_BST_VOL 0x1e +/* Mixer - D-D */ +#define RT5651_STO1_ADC_MIXER 0x27 +#define RT5651_STO2_ADC_MIXER 0x28 +#define RT5651_AD_DA_MIXER 0x29 +#define RT5651_STO_DAC_MIXER 0x2a +#define RT5651_DD_MIXER 0x2b +#define RT5651_DIG_INF_DATA 0x2f +/* PDM */ +#define RT5651_PDM_CTL 0x30 +#define RT5651_PDM_I2C_CTL1 0x31 +#define RT5651_PDM_I2C_CTL2 0x32 +#define RT5651_PDM_I2C_DATA_W 0x33 +#define RT5651_PDM_I2C_DATA_R 0x34 +/* Mixer - ADC */ +#define RT5651_REC_L1_MIXER 0x3b +#define RT5651_REC_L2_MIXER 0x3c +#define RT5651_REC_R1_MIXER 0x3d +#define RT5651_REC_R2_MIXER 0x3e +/* Mixer - DAC */ +#define RT5651_HPO_MIXER 0x45 +#define RT5651_OUT_L1_MIXER 0x4d +#define RT5651_OUT_L2_MIXER 0x4e +#define RT5651_OUT_L3_MIXER 0x4f +#define RT5651_OUT_R1_MIXER 0x50 +#define RT5651_OUT_R2_MIXER 0x51 +#define RT5651_OUT_R3_MIXER 0x52 +#define RT5651_LOUT_MIXER 0x53 +/* Power */ +#define RT5651_PWR_DIG1 0x61 +#define RT5651_PWR_DIG2 0x62 +#define RT5651_PWR_ANLG1 0x63 +#define RT5651_PWR_ANLG2 0x64 +#define RT5651_PWR_MIXER 0x65 +#define RT5651_PWR_VOL 0x66 +/* Private Register Control */ +#define RT5651_PRIV_INDEX 0x6a +#define RT5651_PRIV_DATA 0x6c +/* Format - ADC/DAC */ +#define RT5651_I2S1_SDP 0x70 +#define RT5651_I2S2_SDP 0x71 +#define RT5651_ADDA_CLK1 0x73 +#define RT5651_ADDA_CLK2 0x74 +#define RT5651_DMIC 0x75 +/* TDM Control */ +#define RT5651_TDM_CTL_1 0x77 +#define RT5651_TDM_CTL_2 0x78 +#define RT5651_TDM_CTL_3 0x79 +/* Function - Analog */ +#define RT5651_GLB_CLK 0x80 +#define RT5651_PLL_CTRL1 0x81 +#define RT5651_PLL_CTRL2 0x82 +#define RT5651_PLL_MODE_1 0x83 +#define RT5651_PLL_MODE_2 0x84 +#define RT5651_PLL_MODE_3 0x85 +#define RT5651_PLL_MODE_4 0x86 +#define RT5651_PLL_MODE_5 0x87 +#define RT5651_PLL_MODE_6 0x89 +#define RT5651_PLL_MODE_7 0x8a +#define RT5651_DEPOP_M1 0x8e +#define RT5651_DEPOP_M2 0x8f +#define RT5651_DEPOP_M3 0x90 +#define RT5651_CHARGE_PUMP 0x91 +#define RT5651_MICBIAS 0x93 +#define RT5651_A_JD_CTL1 0x94 +/* Function - Digital */ +#define RT5651_EQ_CTRL1 0xb0 +#define RT5651_EQ_CTRL2 0xb1 +#define RT5651_ALC_1 0xb4 +#define RT5651_ALC_2 0xb5 +#define RT5651_ALC_3 0xb6 +#define RT5651_JD_CTRL1 0xbb +#define RT5651_JD_CTRL2 0xbc +#define RT5651_IRQ_CTRL1 0xbd +#define RT5651_IRQ_CTRL2 0xbe +#define RT5651_INT_IRQ_ST 0xbf +#define RT5651_GPIO_CTRL1 0xc0 +#define RT5651_GPIO_CTRL2 0xc1 +#define RT5651_GPIO_CTRL3 0xc2 +#define RT5651_PGM_REG_ARR1 0xc8 +#define RT5651_PGM_REG_ARR2 0xc9 +#define RT5651_PGM_REG_ARR3 0xca +#define RT5651_PGM_REG_ARR4 0xcb +#define RT5651_PGM_REG_ARR5 0xcc +#define RT5651_SCB_FUNC 0xcd +#define RT5651_SCB_CTRL 0xce +#define RT5651_BASE_BACK 0xcf +#define RT5651_MP3_PLUS1 0xd0 +#define RT5651_MP3_PLUS2 0xd1 +#define RT5651_ADJ_HPF_CTRL1 0xd3 +#define RT5651_ADJ_HPF_CTRL2 0xd4 +#define RT5651_HP_CALIB_AMP_DET 0xd6 +#define RT5651_HP_CALIB2 0xd7 +#define RT5651_SV_ZCD1 0xd9 +#define RT5651_SV_ZCD2 0xda +#define RT5651_D_MISC 0xfa +/* Dummy Register */ +#define RT5651_DUMMY2 0xfb +#define RT5651_DUMMY3 0xfc + + +/* Index of Codec Private Register definition */ +#define RT5651_BIAS_CUR1 0x12 +#define RT5651_BIAS_CUR3 0x14 +#define RT5651_BIAS_CUR4 0x15 +#define RT5651_CLSD_INT_REG1 0x1c +#define RT5651_CHPUMP_INT_REG1 0x24 +#define RT5651_MAMP_INT_REG2 0x37 +#define RT5651_CHOP_DAC_ADC 0x3d +#define RT5651_3D_SPK 0x63 +#define RT5651_WND_1 0x6c +#define RT5651_WND_2 0x6d +#define RT5651_WND_3 0x6e +#define RT5651_WND_4 0x6f +#define RT5651_WND_5 0x70 +#define RT5651_WND_8 0x73 +#define RT5651_DIP_SPK_INF 0x75 +#define RT5651_HP_DCC_INT1 0x77 +#define RT5651_EQ_BW_LOP 0xa0 +#define RT5651_EQ_GN_LOP 0xa1 +#define RT5651_EQ_FC_BP1 0xa2 +#define RT5651_EQ_BW_BP1 0xa3 +#define RT5651_EQ_GN_BP1 0xa4 +#define RT5651_EQ_FC_BP2 0xa5 +#define RT5651_EQ_BW_BP2 0xa6 +#define RT5651_EQ_GN_BP2 0xa7 +#define RT5651_EQ_FC_BP3 0xa8 +#define RT5651_EQ_BW_BP3 0xa9 +#define RT5651_EQ_GN_BP3 0xaa +#define RT5651_EQ_FC_BP4 0xab +#define RT5651_EQ_BW_BP4 0xac +#define RT5651_EQ_GN_BP4 0xad +#define RT5651_EQ_FC_HIP1 0xae +#define RT5651_EQ_GN_HIP1 0xaf +#define RT5651_EQ_FC_HIP2 0xb0 +#define RT5651_EQ_BW_HIP2 0xb1 +#define RT5651_EQ_GN_HIP2 0xb2 +#define RT5651_EQ_PRE_VOL 0xb3 +#define RT5651_EQ_PST_VOL 0xb4 + + +/* global definition */ +#define RT5651_L_MUTE (0x1 << 15) +#define RT5651_L_MUTE_SFT 15 +#define RT5651_VOL_L_MUTE (0x1 << 14) +#define RT5651_VOL_L_SFT 14 +#define RT5651_R_MUTE (0x1 << 7) +#define RT5651_R_MUTE_SFT 7 +#define RT5651_VOL_R_MUTE (0x1 << 6) +#define RT5651_VOL_R_SFT 6 +#define RT5651_L_VOL_MASK (0x3f << 8) +#define RT5651_L_VOL_SFT 8 +#define RT5651_R_VOL_MASK (0x3f) +#define RT5651_R_VOL_SFT 0 + +/* LOUT Control 2(0x05) */ +#define RT5651_EN_DFO (0x1 << 15) + +/* IN1 and IN2 Control (0x0d) */ +/* IN3 and IN4 Control (0x0e) */ +#define RT5651_BST_MASK1 (0xf<<12) +#define RT5651_BST_SFT1 12 +#define RT5651_BST_MASK2 (0xf<<8) +#define RT5651_BST_SFT2 8 +#define RT5651_IN_DF1 (0x1 << 7) +#define RT5651_IN_SFT1 7 +#define RT5651_IN_DF2 (0x1 << 6) +#define RT5651_IN_SFT2 6 + +/* INL1 and INR1 Volume Control (0x0f) */ +/* INL2 and INR2 Volume Control (0x10) */ +#define RT5651_INL_SEL_MASK (0x1 << 15) +#define RT5651_INL_SEL_SFT 15 +#define RT5651_INL_SEL_IN4P (0x0 << 15) +#define RT5651_INL_SEL_MONOP (0x1 << 15) +#define RT5651_INL_VOL_MASK (0x1f << 8) +#define RT5651_INL_VOL_SFT 8 +#define RT5651_INR_SEL_MASK (0x1 << 7) +#define RT5651_INR_SEL_SFT 7 +#define RT5651_INR_SEL_IN4N (0x0 << 7) +#define RT5651_INR_SEL_MONON (0x1 << 7) +#define RT5651_INR_VOL_MASK (0x1f) +#define RT5651_INR_VOL_SFT 0 + +/* DAC1 Digital Volume (0x19) */ +#define RT5651_DAC_L1_VOL_MASK (0xff << 8) +#define RT5651_DAC_L1_VOL_SFT 8 +#define RT5651_DAC_R1_VOL_MASK (0xff) +#define RT5651_DAC_R1_VOL_SFT 0 + +/* DAC2 Digital Volume (0x1a) */ +#define RT5651_DAC_L2_VOL_MASK (0xff << 8) +#define RT5651_DAC_L2_VOL_SFT 8 +#define RT5651_DAC_R2_VOL_MASK (0xff) +#define RT5651_DAC_R2_VOL_SFT 0 + +/* DAC2 Control (0x1b) */ +#define RT5651_M_DAC_L2_VOL (0x1 << 13) +#define RT5651_M_DAC_L2_VOL_SFT 13 +#define RT5651_M_DAC_R2_VOL (0x1 << 12) +#define RT5651_M_DAC_R2_VOL_SFT 12 +#define RT5651_SEL_DAC_L2 (0x1 << 11) +#define RT5651_IF2_DAC_L2 (0x1 << 11) +#define RT5651_IF1_DAC_L2 (0x0 << 11) +#define RT5651_SEL_DAC_L2_SFT 11 +#define RT5651_SEL_DAC_R2 (0x1 << 10) +#define RT5651_IF2_DAC_R2 (0x1 << 11) +#define RT5651_IF1_DAC_R2 (0x0 << 11) +#define RT5651_SEL_DAC_R2_SFT 10 + +/* ADC Digital Volume Control (0x1c) */ +#define RT5651_ADC_L_VOL_MASK (0x7f << 8) +#define RT5651_ADC_L_VOL_SFT 8 +#define RT5651_ADC_R_VOL_MASK (0x7f) +#define RT5651_ADC_R_VOL_SFT 0 + +/* Mono ADC Digital Volume Control (0x1d) */ +#define RT5651_M_MONO_ADC_L (0x1 << 15) +#define RT5651_M_MONO_ADC_L_SFT 15 +#define RT5651_MONO_ADC_L_VOL_MASK (0x7f << 8) +#define RT5651_MONO_ADC_L_VOL_SFT 8 +#define RT5651_M_MONO_ADC_R (0x1 << 7) +#define RT5651_M_MONO_ADC_R_SFT 7 +#define RT5651_MONO_ADC_R_VOL_MASK (0x7f) +#define RT5651_MONO_ADC_R_VOL_SFT 0 + +/* ADC Boost Volume Control (0x1e) */ +#define RT5651_ADC_L_BST_MASK (0x3 << 14) +#define RT5651_ADC_L_BST_SFT 14 +#define RT5651_ADC_R_BST_MASK (0x3 << 12) +#define RT5651_ADC_R_BST_SFT 12 +#define RT5651_ADC_COMP_MASK (0x3 << 10) +#define RT5651_ADC_COMP_SFT 10 + +/* Stereo ADC1 Mixer Control (0x27) */ +#define RT5651_M_STO1_ADC_L1 (0x1 << 14) +#define RT5651_M_STO1_ADC_L1_SFT 14 +#define RT5651_M_STO1_ADC_L2 (0x1 << 13) +#define RT5651_M_STO1_ADC_L2_SFT 13 +#define RT5651_STO1_ADC_1_SRC_MASK (0x1 << 12) +#define RT5651_STO1_ADC_1_SRC_SFT 12 +#define RT5651_STO1_ADC_1_SRC_ADC (0x1 << 12) +#define RT5651_STO1_ADC_1_SRC_DACMIX (0x0 << 12) +#define RT5651_STO1_ADC_2_SRC_MASK (0x1 << 11) +#define RT5651_STO1_ADC_2_SRC_SFT 11 +#define RT5651_STO1_ADC_2_SRC_DMIC (0x0 << 11) +#define RT5651_STO1_ADC_2_SRC_DACMIXR (0x1 << 11) +#define RT5651_M_STO1_ADC_R1 (0x1 << 6) +#define RT5651_M_STO1_ADC_R1_SFT 6 +#define RT5651_M_STO1_ADC_R2 (0x1 << 5) +#define RT5651_M_STO1_ADC_R2_SFT 5 + +/* Stereo ADC2 Mixer Control (0x28) */ +#define RT5651_M_STO2_ADC_L1 (0x1 << 14) +#define RT5651_M_STO2_ADC_L1_SFT 14 +#define RT5651_M_STO2_ADC_L2 (0x1 << 13) +#define RT5651_M_STO2_ADC_L2_SFT 13 +#define RT5651_STO2_ADC_L1_SRC_MASK (0x1 << 12) +#define RT5651_STO2_ADC_L1_SRC_SFT 12 +#define RT5651_STO2_ADC_L1_SRC_DACMIXL (0x0 << 12) +#define RT5651_STO2_ADC_L1_SRC_ADCL (0x1 << 12) +#define RT5651_STO2_ADC_L2_SRC_MASK (0x1 << 11) +#define RT5651_STO2_ADC_L2_SRC_SFT 11 +#define RT5651_STO2_ADC_L2_SRC_DMIC (0x0 << 11) +#define RT5651_STO2_ADC_L2_SRC_DACMIXR (0x1 << 11) +#define RT5651_M_STO2_ADC_R1 (0x1 << 6) +#define RT5651_M_STO2_ADC_R1_SFT 6 +#define RT5651_M_STO2_ADC_R2 (0x1 << 5) +#define RT5651_M_STO2_ADC_R2_SFT 5 +#define RT5651_STO2_ADC_R1_SRC_MASK (0x1 << 4) +#define RT5651_STO2_ADC_R1_SRC_SFT 4 +#define RT5651_STO2_ADC_R1_SRC_ADCR (0x1 << 4) +#define RT5651_STO2_ADC_R1_SRC_DACMIXR (0x0 << 4) +#define RT5651_STO2_ADC_R2_SRC_MASK (0x1 << 3) +#define RT5651_STO2_ADC_R2_SRC_SFT 3 +#define RT5651_STO2_ADC_R2_SRC_DMIC (0x0 << 3) +#define RT5651_STO2_ADC_R2_SRC_DACMIXR (0x1 << 3) + +/* ADC Mixer to DAC Mixer Control (0x29) */ +#define RT5651_M_ADCMIX_L (0x1 << 15) +#define RT5651_M_ADCMIX_L_SFT 15 +#define RT5651_M_IF1_DAC_L (0x1 << 14) +#define RT5651_M_IF1_DAC_L_SFT 14 +#define RT5651_M_ADCMIX_R (0x1 << 7) +#define RT5651_M_ADCMIX_R_SFT 7 +#define RT5651_M_IF1_DAC_R (0x1 << 6) +#define RT5651_M_IF1_DAC_R_SFT 6 + +/* Stereo DAC Mixer Control (0x2a) */ +#define RT5651_M_DAC_L1_MIXL (0x1 << 14) +#define RT5651_M_DAC_L1_MIXL_SFT 14 +#define RT5651_DAC_L1_STO_L_VOL_MASK (0x1 << 13) +#define RT5651_DAC_L1_STO_L_VOL_SFT 13 +#define RT5651_M_DAC_L2_MIXL (0x1 << 12) +#define RT5651_M_DAC_L2_MIXL_SFT 12 +#define RT5651_DAC_L2_STO_L_VOL_MASK (0x1 << 11) +#define RT5651_DAC_L2_STO_L_VOL_SFT 11 +#define RT5651_M_DAC_R1_MIXL (0x1 << 9) +#define RT5651_M_DAC_R1_MIXL_SFT 9 +#define RT5651_DAC_R1_STO_L_VOL_MASK (0x1 << 8) +#define RT5651_DAC_R1_STO_L_VOL_SFT 8 +#define RT5651_M_DAC_R1_MIXR (0x1 << 6) +#define RT5651_M_DAC_R1_MIXR_SFT 6 +#define RT5651_DAC_R1_STO_R_VOL_MASK (0x1 << 5) +#define RT5651_DAC_R1_STO_R_VOL_SFT 5 +#define RT5651_M_DAC_R2_MIXR (0x1 << 4) +#define RT5651_M_DAC_R2_MIXR_SFT 4 +#define RT5651_DAC_R2_STO_R_VOL_MASK (0x1 << 3) +#define RT5651_DAC_R2_STO_R_VOL_SFT 3 +#define RT5651_M_DAC_L1_MIXR (0x1 << 1) +#define RT5651_M_DAC_L1_MIXR_SFT 1 +#define RT5651_DAC_L1_STO_R_VOL_MASK (0x1) +#define RT5651_DAC_L1_STO_R_VOL_SFT 0 + +/* DD Mixer Control (0x2b) */ +#define RT5651_M_STO_DD_L1 (0x1 << 14) +#define RT5651_M_STO_DD_L1_SFT 14 +#define RT5651_STO_DD_L1_VOL_MASK (0x1 << 13) +#define RT5651_DAC_DD_L1_VOL_SFT 13 +#define RT5651_M_STO_DD_L2 (0x1 << 12) +#define RT5651_M_STO_DD_L2_SFT 12 +#define RT5651_STO_DD_L2_VOL_MASK (0x1 << 11) +#define RT5651_STO_DD_L2_VOL_SFT 11 +#define RT5651_M_STO_DD_R2_L (0x1 << 10) +#define RT5651_M_STO_DD_R2_L_SFT 10 +#define RT5651_STO_DD_R2_L_VOL_MASK (0x1 << 9) +#define RT5651_STO_DD_R2_L_VOL_SFT 9 +#define RT5651_M_STO_DD_R1 (0x1 << 6) +#define RT5651_M_STO_DD_R1_SFT 6 +#define RT5651_STO_DD_R1_VOL_MASK (0x1 << 5) +#define RT5651_STO_DD_R1_VOL_SFT 5 +#define RT5651_M_STO_DD_R2 (0x1 << 4) +#define RT5651_M_STO_DD_R2_SFT 4 +#define RT5651_STO_DD_R2_VOL_MASK (0x1 << 3) +#define RT5651_STO_DD_R2_VOL_SFT 3 +#define RT5651_M_STO_DD_L2_R (0x1 << 2) +#define RT5651_M_STO_DD_L2_R_SFT 2 +#define RT5651_STO_DD_L2_R_VOL_MASK (0x1 << 1) +#define RT5651_STO_DD_L2_R_VOL_SFT 1 + +/* Digital Mixer Control (0x2c) */ +#define RT5651_M_STO_L_DAC_L (0x1 << 15) +#define RT5651_M_STO_L_DAC_L_SFT 15 +#define RT5651_STO_L_DAC_L_VOL_MASK (0x1 << 14) +#define RT5651_STO_L_DAC_L_VOL_SFT 14 +#define RT5651_M_DAC_L2_DAC_L (0x1 << 13) +#define RT5651_M_DAC_L2_DAC_L_SFT 13 +#define RT5651_DAC_L2_DAC_L_VOL_MASK (0x1 << 12) +#define RT5651_DAC_L2_DAC_L_VOL_SFT 12 +#define RT5651_M_STO_R_DAC_R (0x1 << 11) +#define RT5651_M_STO_R_DAC_R_SFT 11 +#define RT5651_STO_R_DAC_R_VOL_MASK (0x1 << 10) +#define RT5651_STO_R_DAC_R_VOL_SFT 10 +#define RT5651_M_DAC_R2_DAC_R (0x1 << 9) +#define RT5651_M_DAC_R2_DAC_R_SFT 9 +#define RT5651_DAC_R2_DAC_R_VOL_MASK (0x1 << 8) +#define RT5651_DAC_R2_DAC_R_VOL_SFT 8 + +/* DSP Path Control 1 (0x2d) */ +#define RT5651_RXDP_SRC_MASK (0x1 << 15) +#define RT5651_RXDP_SRC_SFT 15 +#define RT5651_RXDP_SRC_NOR (0x0 << 15) +#define RT5651_RXDP_SRC_DIV3 (0x1 << 15) +#define RT5651_TXDP_SRC_MASK (0x1 << 14) +#define RT5651_TXDP_SRC_SFT 14 +#define RT5651_TXDP_SRC_NOR (0x0 << 14) +#define RT5651_TXDP_SRC_DIV3 (0x1 << 14) + +/* DSP Path Control 2 (0x2e) */ +#define RT5651_DAC_L2_SEL_MASK (0x3 << 14) +#define RT5651_DAC_L2_SEL_SFT 14 +#define RT5651_DAC_L2_SEL_IF2 (0x0 << 14) +#define RT5651_DAC_L2_SEL_IF3 (0x1 << 14) +#define RT5651_DAC_L2_SEL_TXDC (0x2 << 14) +#define RT5651_DAC_L2_SEL_BASS (0x3 << 14) +#define RT5651_DAC_R2_SEL_MASK (0x3 << 12) +#define RT5651_DAC_R2_SEL_SFT 12 +#define RT5651_DAC_R2_SEL_IF2 (0x0 << 12) +#define RT5651_DAC_R2_SEL_IF3 (0x1 << 12) +#define RT5651_DAC_R2_SEL_TXDC (0x2 << 12) +#define RT5651_IF2_ADC_L_SEL_MASK (0x1 << 11) +#define RT5651_IF2_ADC_L_SEL_SFT 11 +#define RT5651_IF2_ADC_L_SEL_TXDP (0x0 << 11) +#define RT5651_IF2_ADC_L_SEL_PASS (0x1 << 11) +#define RT5651_IF2_ADC_R_SEL_MASK (0x1 << 10) +#define RT5651_IF2_ADC_R_SEL_SFT 10 +#define RT5651_IF2_ADC_R_SEL_TXDP (0x0 << 10) +#define RT5651_IF2_ADC_R_SEL_PASS (0x1 << 10) +#define RT5651_RXDC_SEL_MASK (0x3 << 8) +#define RT5651_RXDC_SEL_SFT 8 +#define RT5651_RXDC_SEL_NOR (0x0 << 8) +#define RT5651_RXDC_SEL_L2R (0x1 << 8) +#define RT5651_RXDC_SEL_R2L (0x2 << 8) +#define RT5651_RXDC_SEL_SWAP (0x3 << 8) +#define RT5651_RXDP_SEL_MASK (0x3 << 6) +#define RT5651_RXDP_SEL_SFT 6 +#define RT5651_RXDP_SEL_NOR (0x0 << 6) +#define RT5651_RXDP_SEL_L2R (0x1 << 6) +#define RT5651_RXDP_SEL_R2L (0x2 << 6) +#define RT5651_RXDP_SEL_SWAP (0x3 << 6) +#define RT5651_TXDC_SEL_MASK (0x3 << 4) +#define RT5651_TXDC_SEL_SFT 4 +#define RT5651_TXDC_SEL_NOR (0x0 << 4) +#define RT5651_TXDC_SEL_L2R (0x1 << 4) +#define RT5651_TXDC_SEL_R2L (0x2 << 4) +#define RT5651_TXDC_SEL_SWAP (0x3 << 4) +#define RT5651_TXDP_SEL_MASK (0x3 << 2) +#define RT5651_TXDP_SEL_SFT 2 +#define RT5651_TXDP_SEL_NOR (0x0 << 2) +#define RT5651_TXDP_SEL_L2R (0x1 << 2) +#define RT5651_TXDP_SEL_R2L (0x2 << 2) +#define RT5651_TRXDP_SEL_SWAP (0x3 << 2) + +/* Digital Interface Data Control (0x2f) */ +#define RT5651_IF2_DAC_SEL_MASK (0x3 << 10) +#define RT5651_IF2_DAC_SEL_SFT 10 +#define RT5651_IF2_DAC_SEL_NOR (0x0 << 10) +#define RT5651_IF2_DAC_SEL_SWAP (0x1 << 10) +#define RT5651_IF2_DAC_SEL_L2R (0x2 << 10) +#define RT5651_IF2_DAC_SEL_R2L (0x3 << 10) +#define RT5651_IF2_ADC_SEL_MASK (0x3 << 8) +#define RT5651_IF2_ADC_SEL_SFT 8 +#define RT5651_IF2_ADC_SEL_NOR (0x0 << 8) +#define RT5651_IF2_ADC_SEL_SWAP (0x1 << 8) +#define RT5651_IF2_ADC_SEL_L2R (0x2 << 8) +#define RT5651_IF2_ADC_SEL_R2L (0x3 << 8) +#define RT5651_IF2_ADC_SRC_MASK (0x1 << 7) +#define RT5651_IF2_ADC_SRC_SFT 7 +#define RT5651_IF1_ADC1 (0x0 << 7) +#define RT5651_IF1_ADC2 (0x1 << 7) + +/* PDM Output Control (0x30) */ +#define RT5651_PDM_L_SEL_MASK (0x1 << 15) +#define RT5651_PDM_L_SEL_SFT 15 +#define RT5651_PDM_L_SEL_DD_L (0x0 << 15) +#define RT5651_PDM_L_SEL_STO_L (0x1 << 15) +#define RT5651_M_PDM_L (0x1 << 14) +#define RT5651_M_PDM_L_SFT 14 +#define RT5651_PDM_R_SEL_MASK (0x1 << 13) +#define RT5651_PDM_R_SEL_SFT 13 +#define RT5651_PDM_R_SEL_DD_L (0x0 << 13) +#define RT5651_PDM_R_SEL_STO_L (0x1 << 13) +#define RT5651_M_PDM_R (0x1 << 12) +#define RT5651_M_PDM_R_SFT 12 +#define RT5651_PDM_BUSY (0x1 << 6) +#define RT5651_PDM_BUSY_SFT 6 +#define RT5651_PDM_PATTERN_SEL_MASK (0x1 << 5) +#define RT5651_PDM_PATTERN_SEL_64 (0x0 << 5) +#define RT5651_PDM_PATTERN_SEL_128 (0x1 << 5) +#define RT5651_PDM_VOL_MASK (0x1 << 4) +#define RT5651_PDM_VOL_SFT 4 +#define RT5651_PDM_DIV_MASK (0x3) +#define RT5651_PDM_DIV_SFT 0 +#define RT5651_PDM_DIV_1 0 +#define RT5651_PDM_DIV_2 1 +#define RT5651_PDM_DIV_3 2 +#define RT5651_PDM_DIV_4 3 + +/* PDM I2C/Data Control 1 (0x31) */ +#define RT5651_PDM_I2C_ID_MASK (0xf << 12) +#define PT5631_PDM_CMD_EXE (0x1 << 11) +#define RT5651_PDM_I2C_CMD_MASK (0x1 << 10) +#define RT5651_PDM_I2C_CMD_R (0x0 << 10) +#define RT5651_PDM_I2C_CMD_W (0x1 << 10) +#define RT5651_PDM_I2C_CMD_EXE (0x1 << 9) +#define RT5651_PDM_I2C_NORMAL (0x0 << 8) +#define RT5651_PDM_I2C_BUSY (0x1 << 8) + +/* PDM I2C/Data Control 2 (0x32) */ +#define RT5651_PDM_I2C_ADDR (0xff << 8) +#define RT5651_PDM_I2C_CMD_PATTERN (0xff) + + +/* REC Left Mixer Control 1 (0x3b) */ +#define RT5651_G_LN_L2_RM_L_MASK (0x7 << 13) +#define RT5651_G_IN_L2_RM_L_SFT 13 +#define RT5651_G_LN_L1_RM_L_MASK (0x7 << 10) +#define RT5651_G_IN_L1_RM_L_SFT 10 +#define RT5651_G_BST3_RM_L_MASK (0x7 << 4) +#define RT5651_G_BST3_RM_L_SFT 4 +#define RT5651_G_BST2_RM_L_MASK (0x7 << 1) +#define RT5651_G_BST2_RM_L_SFT 1 + +/* REC Left Mixer Control 2 (0x3c) */ +#define RT5651_G_BST1_RM_L_MASK (0x7 << 13) +#define RT5651_G_BST1_RM_L_SFT 13 +#define RT5651_G_OM_L_RM_L_MASK (0x7 << 10) +#define RT5651_G_OM_L_RM_L_SFT 10 +#define RT5651_M_IN2_L_RM_L (0x1 << 6) +#define RT5651_M_IN2_L_RM_L_SFT 6 +#define RT5651_M_IN1_L_RM_L (0x1 << 5) +#define RT5651_M_IN1_L_RM_L_SFT 5 +#define RT5651_M_BST3_RM_L (0x1 << 3) +#define RT5651_M_BST3_RM_L_SFT 3 +#define RT5651_M_BST2_RM_L (0x1 << 2) +#define RT5651_M_BST2_RM_L_SFT 2 +#define RT5651_M_BST1_RM_L (0x1 << 1) +#define RT5651_M_BST1_RM_L_SFT 1 +#define RT5651_M_OM_L_RM_L (0x1) +#define RT5651_M_OM_L_RM_L_SFT 0 + +/* REC Right Mixer Control 1 (0x3d) */ +#define RT5651_G_IN2_R_RM_R_MASK (0x7 << 13) +#define RT5651_G_IN2_R_RM_R_SFT 13 +#define RT5651_G_IN1_R_RM_R_MASK (0x7 << 10) +#define RT5651_G_IN1_R_RM_R_SFT 10 +#define RT5651_G_BST3_RM_R_MASK (0x7 << 4) +#define RT5651_G_BST3_RM_R_SFT 4 +#define RT5651_G_BST2_RM_R_MASK (0x7 << 1) +#define RT5651_G_BST2_RM_R_SFT 1 + +/* REC Right Mixer Control 2 (0x3e) */ +#define RT5651_G_BST1_RM_R_MASK (0x7 << 13) +#define RT5651_G_BST1_RM_R_SFT 13 +#define RT5651_G_OM_R_RM_R_MASK (0x7 << 10) +#define RT5651_G_OM_R_RM_R_SFT 10 +#define RT5651_M_IN2_R_RM_R (0x1 << 6) +#define RT5651_M_IN2_R_RM_R_SFT 6 +#define RT5651_M_IN1_R_RM_R (0x1 << 5) +#define RT5651_M_IN1_R_RM_R_SFT 5 +#define RT5651_M_BST3_RM_R (0x1 << 3) +#define RT5651_M_BST3_RM_R_SFT 3 +#define RT5651_M_BST2_RM_R (0x1 << 2) +#define RT5651_M_BST2_RM_R_SFT 2 +#define RT5651_M_BST1_RM_R (0x1 << 1) +#define RT5651_M_BST1_RM_R_SFT 1 +#define RT5651_M_OM_R_RM_R (0x1) +#define RT5651_M_OM_R_RM_R_SFT 0 + +/* HPMIX Control (0x45) */ +#define RT5651_M_DAC1_HM (0x1 << 14) +#define RT5651_M_DAC1_HM_SFT 14 +#define RT5651_M_HPVOL_HM (0x1 << 13) +#define RT5651_M_HPVOL_HM_SFT 13 +#define RT5651_G_HPOMIX_MASK (0x1 << 12) +#define RT5651_G_HPOMIX_SFT 12 + +/* SPK Left Mixer Control (0x46) */ +#define RT5651_G_RM_L_SM_L_MASK (0x3 << 14) +#define RT5651_G_RM_L_SM_L_SFT 14 +#define RT5651_G_IN_L_SM_L_MASK (0x3 << 12) +#define RT5651_G_IN_L_SM_L_SFT 12 +#define RT5651_G_DAC_L1_SM_L_MASK (0x3 << 10) +#define RT5651_G_DAC_L1_SM_L_SFT 10 +#define RT5651_G_DAC_L2_SM_L_MASK (0x3 << 8) +#define RT5651_G_DAC_L2_SM_L_SFT 8 +#define RT5651_G_OM_L_SM_L_MASK (0x3 << 6) +#define RT5651_G_OM_L_SM_L_SFT 6 +#define RT5651_M_RM_L_SM_L (0x1 << 5) +#define RT5651_M_RM_L_SM_L_SFT 5 +#define RT5651_M_IN_L_SM_L (0x1 << 4) +#define RT5651_M_IN_L_SM_L_SFT 4 +#define RT5651_M_DAC_L1_SM_L (0x1 << 3) +#define RT5651_M_DAC_L1_SM_L_SFT 3 +#define RT5651_M_DAC_L2_SM_L (0x1 << 2) +#define RT5651_M_DAC_L2_SM_L_SFT 2 +#define RT5651_M_OM_L_SM_L (0x1 << 1) +#define RT5651_M_OM_L_SM_L_SFT 1 + +/* SPK Right Mixer Control (0x47) */ +#define RT5651_G_RM_R_SM_R_MASK (0x3 << 14) +#define RT5651_G_RM_R_SM_R_SFT 14 +#define RT5651_G_IN_R_SM_R_MASK (0x3 << 12) +#define RT5651_G_IN_R_SM_R_SFT 12 +#define RT5651_G_DAC_R1_SM_R_MASK (0x3 << 10) +#define RT5651_G_DAC_R1_SM_R_SFT 10 +#define RT5651_G_DAC_R2_SM_R_MASK (0x3 << 8) +#define RT5651_G_DAC_R2_SM_R_SFT 8 +#define RT5651_G_OM_R_SM_R_MASK (0x3 << 6) +#define RT5651_G_OM_R_SM_R_SFT 6 +#define RT5651_M_RM_R_SM_R (0x1 << 5) +#define RT5651_M_RM_R_SM_R_SFT 5 +#define RT5651_M_IN_R_SM_R (0x1 << 4) +#define RT5651_M_IN_R_SM_R_SFT 4 +#define RT5651_M_DAC_R1_SM_R (0x1 << 3) +#define RT5651_M_DAC_R1_SM_R_SFT 3 +#define RT5651_M_DAC_R2_SM_R (0x1 << 2) +#define RT5651_M_DAC_R2_SM_R_SFT 2 +#define RT5651_M_OM_R_SM_R (0x1 << 1) +#define RT5651_M_OM_R_SM_R_SFT 1 + +/* SPOLMIX Control (0x48) */ +#define RT5651_M_DAC_R1_SPM_L (0x1 << 15) +#define RT5651_M_DAC_R1_SPM_L_SFT 15 +#define RT5651_M_DAC_L1_SPM_L (0x1 << 14) +#define RT5651_M_DAC_L1_SPM_L_SFT 14 +#define RT5651_M_SV_R_SPM_L (0x1 << 13) +#define RT5651_M_SV_R_SPM_L_SFT 13 +#define RT5651_M_SV_L_SPM_L (0x1 << 12) +#define RT5651_M_SV_L_SPM_L_SFT 12 +#define RT5651_M_BST1_SPM_L (0x1 << 11) +#define RT5651_M_BST1_SPM_L_SFT 11 + +/* SPORMIX Control (0x49) */ +#define RT5651_M_DAC_R1_SPM_R (0x1 << 13) +#define RT5651_M_DAC_R1_SPM_R_SFT 13 +#define RT5651_M_SV_R_SPM_R (0x1 << 12) +#define RT5651_M_SV_R_SPM_R_SFT 12 +#define RT5651_M_BST1_SPM_R (0x1 << 11) +#define RT5651_M_BST1_SPM_R_SFT 11 + +/* SPOLMIX / SPORMIX Ratio Control (0x4a) */ +#define RT5651_SPO_CLSD_RATIO_MASK (0x7) +#define RT5651_SPO_CLSD_RATIO_SFT 0 + +/* Mono Output Mixer Control (0x4c) */ +#define RT5651_M_DAC_R2_MM (0x1 << 15) +#define RT5651_M_DAC_R2_MM_SFT 15 +#define RT5651_M_DAC_L2_MM (0x1 << 14) +#define RT5651_M_DAC_L2_MM_SFT 14 +#define RT5651_M_OV_R_MM (0x1 << 13) +#define RT5651_M_OV_R_MM_SFT 13 +#define RT5651_M_OV_L_MM (0x1 << 12) +#define RT5651_M_OV_L_MM_SFT 12 +#define RT5651_M_BST1_MM (0x1 << 11) +#define RT5651_M_BST1_MM_SFT 11 +#define RT5651_G_MONOMIX_MASK (0x1 << 10) +#define RT5651_G_MONOMIX_SFT 10 + +/* Output Left Mixer Control 1 (0x4d) */ +#define RT5651_G_BST2_OM_L_MASK (0x7 << 10) +#define RT5651_G_BST2_OM_L_SFT 10 +#define RT5651_G_BST1_OM_L_MASK (0x7 << 7) +#define RT5651_G_BST1_OM_L_SFT 7 +#define RT5651_G_IN1_L_OM_L_MASK (0x7 << 4) +#define RT5651_G_IN1_L_OM_L_SFT 4 +#define RT5651_G_RM_L_OM_L_MASK (0x7 << 1) +#define RT5651_G_RM_L_OM_L_SFT 1 + +/* Output Left Mixer Control 2 (0x4e) */ +#define RT5651_G_DAC_L1_OM_L_MASK (0x7 << 7) +#define RT5651_G_DAC_L1_OM_L_SFT 7 +#define RT5651_G_IN2_L_OM_L_MASK (0x7 << 4) +#define RT5651_G_IN2_L_OM_L_SFT 4 + +/* Output Left Mixer Control 3 (0x4f) */ +#define RT5651_M_IN2_L_OM_L (0x1 << 9) +#define RT5651_M_IN2_L_OM_L_SFT 9 +#define RT5651_M_BST2_OM_L (0x1 << 6) +#define RT5651_M_BST2_OM_L_SFT 6 +#define RT5651_M_BST1_OM_L (0x1 << 5) +#define RT5651_M_BST1_OM_L_SFT 5 +#define RT5651_M_IN1_L_OM_L (0x1 << 4) +#define RT5651_M_IN1_L_OM_L_SFT 4 +#define RT5651_M_RM_L_OM_L (0x1 << 3) +#define RT5651_M_RM_L_OM_L_SFT 3 +#define RT5651_M_DAC_L1_OM_L (0x1) +#define RT5651_M_DAC_L1_OM_L_SFT 0 + +/* Output Right Mixer Control 1 (0x50) */ +#define RT5651_G_BST2_OM_R_MASK (0x7 << 10) +#define RT5651_G_BST2_OM_R_SFT 10 +#define RT5651_G_BST1_OM_R_MASK (0x7 << 7) +#define RT5651_G_BST1_OM_R_SFT 7 +#define RT5651_G_IN1_R_OM_R_MASK (0x7 << 4) +#define RT5651_G_IN1_R_OM_R_SFT 4 +#define RT5651_G_RM_R_OM_R_MASK (0x7 << 1) +#define RT5651_G_RM_R_OM_R_SFT 1 + +/* Output Right Mixer Control 2 (0x51) */ +#define RT5651_G_DAC_R1_OM_R_MASK (0x7 << 7) +#define RT5651_G_DAC_R1_OM_R_SFT 7 +#define RT5651_G_IN2_R_OM_R_MASK (0x7 << 4) +#define RT5651_G_IN2_R_OM_R_SFT 4 + +/* Output Right Mixer Control 3 (0x52) */ +#define RT5651_M_IN2_R_OM_R (0x1 << 9) +#define RT5651_M_IN2_R_OM_R_SFT 9 +#define RT5651_M_BST2_OM_R (0x1 << 6) +#define RT5651_M_BST2_OM_R_SFT 6 +#define RT5651_M_BST1_OM_R (0x1 << 5) +#define RT5651_M_BST1_OM_R_SFT 5 +#define RT5651_M_IN1_R_OM_R (0x1 << 4) +#define RT5651_M_IN1_R_OM_R_SFT 4 +#define RT5651_M_RM_R_OM_R (0x1 << 3) +#define RT5651_M_RM_R_OM_R_SFT 3 +#define RT5651_M_DAC_R1_OM_R (0x1) +#define RT5651_M_DAC_R1_OM_R_SFT 0 + +/* LOUT Mixer Control (0x53) */ +#define RT5651_M_DAC_L1_LM (0x1 << 15) +#define RT5651_M_DAC_L1_LM_SFT 15 +#define RT5651_M_DAC_R1_LM (0x1 << 14) +#define RT5651_M_DAC_R1_LM_SFT 14 +#define RT5651_M_OV_L_LM (0x1 << 13) +#define RT5651_M_OV_L_LM_SFT 13 +#define RT5651_M_OV_R_LM (0x1 << 12) +#define RT5651_M_OV_R_LM_SFT 12 +#define RT5651_G_LOUTMIX_MASK (0x1 << 11) +#define RT5651_G_LOUTMIX_SFT 11 + +/* Power Management for Digital 1 (0x61) */ +#define RT5651_PWR_I2S1 (0x1 << 15) +#define RT5651_PWR_I2S1_BIT 15 +#define RT5651_PWR_I2S2 (0x1 << 14) +#define RT5651_PWR_I2S2_BIT 14 +#define RT5651_PWR_DAC_L1 (0x1 << 12) +#define RT5651_PWR_DAC_L1_BIT 12 +#define RT5651_PWR_DAC_R1 (0x1 << 11) +#define RT5651_PWR_DAC_R1_BIT 11 +#define RT5651_PWR_ADC_L (0x1 << 2) +#define RT5651_PWR_ADC_L_BIT 2 +#define RT5651_PWR_ADC_R (0x1 << 1) +#define RT5651_PWR_ADC_R_BIT 1 + +/* Power Management for Digital 2 (0x62) */ +#define RT5651_PWR_ADC_STO1_F (0x1 << 15) +#define RT5651_PWR_ADC_STO1_F_BIT 15 +#define RT5651_PWR_ADC_STO2_F (0x1 << 14) +#define RT5651_PWR_ADC_STO2_F_BIT 14 +#define RT5651_PWR_DAC_STO1_F (0x1 << 11) +#define RT5651_PWR_DAC_STO1_F_BIT 11 +#define RT5651_PWR_DAC_STO2_F (0x1 << 10) +#define RT5651_PWR_DAC_STO2_F_BIT 10 +#define RT5651_PWR_PDM (0x1 << 9) +#define RT5651_PWR_PDM_BIT 9 + +/* Power Management for Analog 1 (0x63) */ +#define RT5651_PWR_VREF1 (0x1 << 15) +#define RT5651_PWR_VREF1_BIT 15 +#define RT5651_PWR_FV1 (0x1 << 14) +#define RT5651_PWR_FV1_BIT 14 +#define RT5651_PWR_MB (0x1 << 13) +#define RT5651_PWR_MB_BIT 13 +#define RT5651_PWR_LM (0x1 << 12) +#define RT5651_PWR_LM_BIT 12 +#define RT5651_PWR_BG (0x1 << 11) +#define RT5651_PWR_BG_BIT 11 +#define RT5651_PWR_HP_L (0x1 << 7) +#define RT5651_PWR_HP_L_BIT 7 +#define RT5651_PWR_HP_R (0x1 << 6) +#define RT5651_PWR_HP_R_BIT 6 +#define RT5651_PWR_HA (0x1 << 5) +#define RT5651_PWR_HA_BIT 5 +#define RT5651_PWR_VREF2 (0x1 << 4) +#define RT5651_PWR_VREF2_BIT 4 +#define RT5651_PWR_FV2 (0x1 << 3) +#define RT5651_PWR_FV2_BIT 3 +#define RT5651_PWR_LDO (0x1 << 2) +#define RT5651_PWR_LDO_BIT 2 +#define RT5651_PWR_LDO_DVO_MASK (0x3) +#define RT5651_PWR_LDO_DVO_1_0V 0 +#define RT5651_PWR_LDO_DVO_1_1V 1 +#define RT5651_PWR_LDO_DVO_1_2V 2 +#define RT5651_PWR_LDO_DVO_1_3V 3 + +/* Power Management for Analog 2 (0x64) */ +#define RT5651_PWR_BST1 (0x1 << 15) +#define RT5651_PWR_BST1_BIT 15 +#define RT5651_PWR_BST2 (0x1 << 14) +#define RT5651_PWR_BST2_BIT 14 +#define RT5651_PWR_BST3 (0x1 << 13) +#define RT5651_PWR_BST3_BIT 13 +#define RT5651_PWR_MB1 (0x1 << 11) +#define RT5651_PWR_MB1_BIT 11 +#define RT5651_PWR_PLL (0x1 << 9) +#define RT5651_PWR_PLL_BIT 9 +#define RT5651_PWR_BST1_OP2 (0x1 << 5) +#define RT5651_PWR_BST1_OP2_BIT 5 +#define RT5651_PWR_BST2_OP2 (0x1 << 4) +#define RT5651_PWR_BST2_OP2_BIT 4 +#define RT5651_PWR_BST3_OP2 (0x1 << 3) +#define RT5651_PWR_BST3_OP2_BIT 3 +#define RT5651_PWR_JD_M (0x1 << 2) +#define RT5651_PWM_JD_M_BIT 2 +#define RT5651_PWR_JD2 (0x1 << 1) +#define RT5651_PWM_JD2_BIT 1 +#define RT5651_PWR_JD3 (0x1) +#define RT5651_PWM_JD3_BIT 0 + +/* Power Management for Mixer (0x65) */ +#define RT5651_PWR_OM_L (0x1 << 15) +#define RT5651_PWR_OM_L_BIT 15 +#define RT5651_PWR_OM_R (0x1 << 14) +#define RT5651_PWR_OM_R_BIT 14 +#define RT5651_PWR_RM_L (0x1 << 11) +#define RT5651_PWR_RM_L_BIT 11 +#define RT5651_PWR_RM_R (0x1 << 10) +#define RT5651_PWR_RM_R_BIT 10 + +/* Power Management for Volume (0x66) */ +#define RT5651_PWR_OV_L (0x1 << 13) +#define RT5651_PWR_OV_L_BIT 13 +#define RT5651_PWR_OV_R (0x1 << 12) +#define RT5651_PWR_OV_R_BIT 12 +#define RT5651_PWR_HV_L (0x1 << 11) +#define RT5651_PWR_HV_L_BIT 11 +#define RT5651_PWR_HV_R (0x1 << 10) +#define RT5651_PWR_HV_R_BIT 10 +#define RT5651_PWR_IN1_L (0x1 << 9) +#define RT5651_PWR_IN1_L_BIT 9 +#define RT5651_PWR_IN1_R (0x1 << 8) +#define RT5651_PWR_IN1_R_BIT 8 +#define RT5651_PWR_IN2_L (0x1 << 7) +#define RT5651_PWR_IN2_L_BIT 7 +#define RT5651_PWR_IN2_R (0x1 << 6) +#define RT5651_PWR_IN2_R_BIT 6 + +/* I2S1/2/3 Audio Serial Data Port Control (0x70 0x71) */ +#define RT5651_I2S_MS_MASK (0x1 << 15) +#define RT5651_I2S_MS_SFT 15 +#define RT5651_I2S_MS_M (0x0 << 15) +#define RT5651_I2S_MS_S (0x1 << 15) +#define RT5651_I2S_O_CP_MASK (0x3 << 10) +#define RT5651_I2S_O_CP_SFT 10 +#define RT5651_I2S_O_CP_OFF (0x0 << 10) +#define RT5651_I2S_O_CP_U_LAW (0x1 << 10) +#define RT5651_I2S_O_CP_A_LAW (0x2 << 10) +#define RT5651_I2S_I_CP_MASK (0x3 << 8) +#define RT5651_I2S_I_CP_SFT 8 +#define RT5651_I2S_I_CP_OFF (0x0 << 8) +#define RT5651_I2S_I_CP_U_LAW (0x1 << 8) +#define RT5651_I2S_I_CP_A_LAW (0x2 << 8) +#define RT5651_I2S_BP_MASK (0x1 << 7) +#define RT5651_I2S_BP_SFT 7 +#define RT5651_I2S_BP_NOR (0x0 << 7) +#define RT5651_I2S_BP_INV (0x1 << 7) +#define RT5651_I2S_DL_MASK (0x3 << 2) +#define RT5651_I2S_DL_SFT 2 +#define RT5651_I2S_DL_16 (0x0 << 2) +#define RT5651_I2S_DL_20 (0x1 << 2) +#define RT5651_I2S_DL_24 (0x2 << 2) +#define RT5651_I2S_DL_8 (0x3 << 2) +#define RT5651_I2S_DF_MASK (0x3) +#define RT5651_I2S_DF_SFT 0 +#define RT5651_I2S_DF_I2S (0x0) +#define RT5651_I2S_DF_LEFT (0x1) +#define RT5651_I2S_DF_PCM_A (0x2) +#define RT5651_I2S_DF_PCM_B (0x3) + +/* ADC/DAC Clock Control 1 (0x73) */ +#define RT5651_I2S_PD1_MASK (0x7 << 12) +#define RT5651_I2S_PD1_SFT 12 +#define RT5651_I2S_PD1_1 (0x0 << 12) +#define RT5651_I2S_PD1_2 (0x1 << 12) +#define RT5651_I2S_PD1_3 (0x2 << 12) +#define RT5651_I2S_PD1_4 (0x3 << 12) +#define RT5651_I2S_PD1_6 (0x4 << 12) +#define RT5651_I2S_PD1_8 (0x5 << 12) +#define RT5651_I2S_PD1_12 (0x6 << 12) +#define RT5651_I2S_PD1_16 (0x7 << 12) +#define RT5651_I2S_BCLK_MS2_MASK (0x1 << 11) +#define RT5651_I2S_BCLK_MS2_SFT 11 +#define RT5651_I2S_BCLK_MS2_32 (0x0 << 11) +#define RT5651_I2S_BCLK_MS2_64 (0x1 << 11) +#define RT5651_I2S_PD2_MASK (0x7 << 8) +#define RT5651_I2S_PD2_SFT 8 +#define RT5651_I2S_PD2_1 (0x0 << 8) +#define RT5651_I2S_PD2_2 (0x1 << 8) +#define RT5651_I2S_PD2_3 (0x2 << 8) +#define RT5651_I2S_PD2_4 (0x3 << 8) +#define RT5651_I2S_PD2_6 (0x4 << 8) +#define RT5651_I2S_PD2_8 (0x5 << 8) +#define RT5651_I2S_PD2_12 (0x6 << 8) +#define RT5651_I2S_PD2_16 (0x7 << 8) +#define RT5651_DAC_OSR_MASK (0x3 << 2) +#define RT5651_DAC_OSR_SFT 2 +#define RT5651_DAC_OSR_128 (0x0 << 2) +#define RT5651_DAC_OSR_64 (0x1 << 2) +#define RT5651_DAC_OSR_32 (0x2 << 2) +#define RT5651_DAC_OSR_128_3 (0x3 << 2) +#define RT5651_ADC_OSR_MASK (0x3) +#define RT5651_ADC_OSR_SFT 0 +#define RT5651_ADC_OSR_128 (0x0) +#define RT5651_ADC_OSR_64 (0x1) +#define RT5651_ADC_OSR_32 (0x2) +#define RT5651_ADC_OSR_128_3 (0x3) + +/* ADC/DAC Clock Control 2 (0x74) */ +#define RT5651_DAHPF_EN (0x1 << 11) +#define RT5651_DAHPF_EN_SFT 11 +#define RT5651_ADHPF_EN (0x1 << 10) +#define RT5651_ADHPF_EN_SFT 10 + +/* Digital Microphone Control (0x75) */ +#define RT5651_DMIC_1_EN_MASK (0x1 << 15) +#define RT5651_DMIC_1_EN_SFT 15 +#define RT5651_DMIC_1_DIS (0x0 << 15) +#define RT5651_DMIC_1_EN (0x1 << 15) +#define RT5651_DMIC_1L_LH_MASK (0x1 << 13) +#define RT5651_DMIC_1L_LH_SFT 13 +#define RT5651_DMIC_1L_LH_FALLING (0x0 << 13) +#define RT5651_DMIC_1L_LH_RISING (0x1 << 13) +#define RT5651_DMIC_1R_LH_MASK (0x1 << 12) +#define RT5651_DMIC_1R_LH_SFT 12 +#define RT5651_DMIC_1R_LH_FALLING (0x0 << 12) +#define RT5651_DMIC_1R_LH_RISING (0x1 << 12) +#define RT5651_DMIC_1_DP_MASK (0x3 << 10) +#define RT5651_DMIC_1_DP_SFT 10 +#define RT5651_DMIC_1_DP_GPIO6 (0x0 << 10) +#define RT5651_DMIC_1_DP_IN1P (0x1 << 10) +#define RT5651_DMIC_2_DP_GPIO8 (0x2 << 10) +#define RT5651_DMIC_CLK_MASK (0x7 << 5) +#define RT5651_DMIC_CLK_SFT 5 + +/* TDM Control 1 (0x77) */ +#define RT5651_TDM_INTEL_SEL_MASK (0x1 << 15) +#define RT5651_TDM_INTEL_SEL_SFT 15 +#define RT5651_TDM_INTEL_SEL_64 (0x0 << 15) +#define RT5651_TDM_INTEL_SEL_50 (0x1 << 15) +#define RT5651_TDM_MODE_SEL_MASK (0x1 << 14) +#define RT5651_TDM_MODE_SEL_SFT 14 +#define RT5651_TDM_MODE_SEL_NOR (0x0 << 14) +#define RT5651_TDM_MODE_SEL_TDM (0x1 << 14) +#define RT5651_TDM_CH_NUM_SEL_MASK (0x3 << 12) +#define RT5651_TDM_CH_NUM_SEL_SFT 12 +#define RT5651_TDM_CH_NUM_SEL_2 (0x0 << 12) +#define RT5651_TDM_CH_NUM_SEL_4 (0x1 << 12) +#define RT5651_TDM_CH_NUM_SEL_6 (0x2 << 12) +#define RT5651_TDM_CH_NUM_SEL_8 (0x3 << 12) +#define RT5651_TDM_CH_LEN_SEL_MASK (0x3 << 10) +#define RT5651_TDM_CH_LEN_SEL_SFT 10 +#define RT5651_TDM_CH_LEN_SEL_16 (0x0 << 10) +#define RT5651_TDM_CH_LEN_SEL_20 (0x1 << 10) +#define RT5651_TDM_CH_LEN_SEL_24 (0x2 << 10) +#define RT5651_TDM_CH_LEN_SEL_32 (0x3 << 10) +#define RT5651_TDM_ADC_SEL_MASK (0x1 << 9) +#define RT5651_TDM_ADC_SEL_SFT 9 +#define RT5651_TDM_ADC_SEL_NOR (0x0 << 9) +#define RT5651_TDM_ADC_SEL_SWAP (0x1 << 9) +#define RT5651_TDM_ADC_START_SEL_MASK (0x1 << 8) +#define RT5651_TDM_ADC_START_SEL_SFT 8 +#define RT5651_TDM_ADC_START_SEL_SL0 (0x0 << 8) +#define RT5651_TDM_ADC_START_SEL_SL4 (0x1 << 8) +#define RT5651_TDM_I2S_CH2_SEL_MASK (0x3 << 6) +#define RT5651_TDM_I2S_CH2_SEL_SFT 6 +#define RT5651_TDM_I2S_CH2_SEL_LR (0x0 << 6) +#define RT5651_TDM_I2S_CH2_SEL_RL (0x1 << 6) +#define RT5651_TDM_I2S_CH2_SEL_LL (0x2 << 6) +#define RT5651_TDM_I2S_CH2_SEL_RR (0x3 << 6) +#define RT5651_TDM_I2S_CH4_SEL_MASK (0x3 << 4) +#define RT5651_TDM_I2S_CH4_SEL_SFT 4 +#define RT5651_TDM_I2S_CH4_SEL_LR (0x0 << 4) +#define RT5651_TDM_I2S_CH4_SEL_RL (0x1 << 4) +#define RT5651_TDM_I2S_CH4_SEL_LL (0x2 << 4) +#define RT5651_TDM_I2S_CH4_SEL_RR (0x3 << 4) +#define RT5651_TDM_I2S_CH6_SEL_MASK (0x3 << 2) +#define RT5651_TDM_I2S_CH6_SEL_SFT 2 +#define RT5651_TDM_I2S_CH6_SEL_LR (0x0 << 2) +#define RT5651_TDM_I2S_CH6_SEL_RL (0x1 << 2) +#define RT5651_TDM_I2S_CH6_SEL_LL (0x2 << 2) +#define RT5651_TDM_I2S_CH6_SEL_RR (0x3 << 2) +#define RT5651_TDM_I2S_CH8_SEL_MASK (0x3) +#define RT5651_TDM_I2S_CH8_SEL_SFT 0 +#define RT5651_TDM_I2S_CH8_SEL_LR (0x0) +#define RT5651_TDM_I2S_CH8_SEL_RL (0x1) +#define RT5651_TDM_I2S_CH8_SEL_LL (0x2) +#define RT5651_TDM_I2S_CH8_SEL_RR (0x3) + +/* TDM Control 2 (0x78) */ +#define RT5651_TDM_LRCK_POL_SEL_MASK (0x1 << 15) +#define RT5651_TDM_LRCK_POL_SEL_SFT 15 +#define RT5651_TDM_LRCK_POL_SEL_NOR (0x0 << 15) +#define RT5651_TDM_LRCK_POL_SEL_INV (0x1 << 15) +#define RT5651_TDM_CH_VAL_SEL_MASK (0x1 << 14) +#define RT5651_TDM_CH_VAL_SEL_SFT 14 +#define RT5651_TDM_CH_VAL_SEL_CH01 (0x0 << 14) +#define RT5651_TDM_CH_VAL_SEL_CH0123 (0x1 << 14) +#define RT5651_TDM_CH_VAL_EN (0x1 << 13) +#define RT5651_TDM_CH_VAL_SFT 13 +#define RT5651_TDM_LPBK_EN (0x1 << 12) +#define RT5651_TDM_LPBK_SFT 12 +#define RT5651_TDM_LRCK_PULSE_SEL_MASK (0x1 << 11) +#define RT5651_TDM_LRCK_PULSE_SEL_SFT 11 +#define RT5651_TDM_LRCK_PULSE_SEL_BCLK (0x0 << 11) +#define RT5651_TDM_LRCK_PULSE_SEL_CH (0x1 << 11) +#define RT5651_TDM_END_EDGE_SEL_MASK (0x1 << 10) +#define RT5651_TDM_END_EDGE_SEL_SFT 10 +#define RT5651_TDM_END_EDGE_SEL_POS (0x0 << 10) +#define RT5651_TDM_END_EDGE_SEL_NEG (0x1 << 10) +#define RT5651_TDM_END_EDGE_EN (0x1 << 9) +#define RT5651_TDM_END_EDGE_EN_SFT 9 +#define RT5651_TDM_TRAN_EDGE_SEL_MASK (0x1 << 8) +#define RT5651_TDM_TRAN_EDGE_SEL_SFT 8 +#define RT5651_TDM_TRAN_EDGE_SEL_POS (0x0 << 8) +#define RT5651_TDM_TRAN_EDGE_SEL_NEG (0x1 << 8) +#define RT5651_M_TDM2_L (0x1 << 7) +#define RT5651_M_TDM2_L_SFT 7 +#define RT5651_M_TDM2_R (0x1 << 6) +#define RT5651_M_TDM2_R_SFT 6 +#define RT5651_M_TDM4_L (0x1 << 5) +#define RT5651_M_TDM4_L_SFT 5 +#define RT5651_M_TDM4_R (0x1 << 4) +#define RT5651_M_TDM4_R_SFT 4 + +/* TDM Control 3 (0x79) */ +#define RT5651_CH2_L_SEL_MASK (0x7 << 12) +#define RT5651_CH2_L_SEL_SFT 12 +#define RT5651_CH2_L_SEL_SL0 (0x0 << 12) +#define RT5651_CH2_L_SEL_SL1 (0x1 << 12) +#define RT5651_CH2_L_SEL_SL2 (0x2 << 12) +#define RT5651_CH2_L_SEL_SL3 (0x3 << 12) +#define RT5651_CH2_L_SEL_SL4 (0x4 << 12) +#define RT5651_CH2_L_SEL_SL5 (0x5 << 12) +#define RT5651_CH2_L_SEL_SL6 (0x6 << 12) +#define RT5651_CH2_L_SEL_SL7 (0x7 << 12) +#define RT5651_CH2_R_SEL_MASK (0x7 << 8) +#define RT5651_CH2_R_SEL_SFT 8 +#define RT5651_CH2_R_SEL_SL0 (0x0 << 8) +#define RT5651_CH2_R_SEL_SL1 (0x1 << 8) +#define RT5651_CH2_R_SEL_SL2 (0x2 << 8) +#define RT5651_CH2_R_SEL_SL3 (0x3 << 8) +#define RT5651_CH2_R_SEL_SL4 (0x4 << 8) +#define RT5651_CH2_R_SEL_SL5 (0x5 << 8) +#define RT5651_CH2_R_SEL_SL6 (0x6 << 8) +#define RT5651_CH2_R_SEL_SL7 (0x7 << 8) +#define RT5651_CH4_L_SEL_MASK (0x7 << 4) +#define RT5651_CH4_L_SEL_SFT 4 +#define RT5651_CH4_L_SEL_SL0 (0x0 << 4) +#define RT5651_CH4_L_SEL_SL1 (0x1 << 4) +#define RT5651_CH4_L_SEL_SL2 (0x2 << 4) +#define RT5651_CH4_L_SEL_SL3 (0x3 << 4) +#define RT5651_CH4_L_SEL_SL4 (0x4 << 4) +#define RT5651_CH4_L_SEL_SL5 (0x5 << 4) +#define RT5651_CH4_L_SEL_SL6 (0x6 << 4) +#define RT5651_CH4_L_SEL_SL7 (0x7 << 4) +#define RT5651_CH4_R_SEL_MASK (0x7) +#define RT5651_CH4_R_SEL_SFT 0 +#define RT5651_CH4_R_SEL_SL0 (0x0) +#define RT5651_CH4_R_SEL_SL1 (0x1) +#define RT5651_CH4_R_SEL_SL2 (0x2) +#define RT5651_CH4_R_SEL_SL3 (0x3) +#define RT5651_CH4_R_SEL_SL4 (0x4) +#define RT5651_CH4_R_SEL_SL5 (0x5) +#define RT5651_CH4_R_SEL_SL6 (0x6) +#define RT5651_CH4_R_SEL_SL7 (0x7) + +/* Global Clock Control (0x80) */ +#define RT5651_SCLK_SRC_MASK (0x3 << 14) +#define RT5651_SCLK_SRC_SFT 14 +#define RT5651_SCLK_SRC_MCLK (0x0 << 14) +#define RT5651_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5651_SCLK_SRC_RCCLK (0x2 << 14) +#define RT5651_PLL1_SRC_MASK (0x3 << 12) +#define RT5651_PLL1_SRC_SFT 12 +#define RT5651_PLL1_SRC_MCLK (0x0 << 12) +#define RT5651_PLL1_SRC_BCLK1 (0x1 << 12) +#define RT5651_PLL1_SRC_BCLK2 (0x2 << 12) +#define RT5651_PLL1_PD_MASK (0x1 << 3) +#define RT5651_PLL1_PD_SFT 3 +#define RT5651_PLL1_PD_1 (0x0 << 3) +#define RT5651_PLL1_PD_2 (0x1 << 3) + +#define RT5651_PLL_INP_MAX 40000000 +#define RT5651_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x81) */ +#define RT5651_PLL_N_MAX 0x1ff +#define RT5651_PLL_N_MASK (RT5651_PLL_N_MAX << 7) +#define RT5651_PLL_N_SFT 7 +#define RT5651_PLL_K_MAX 0x1f +#define RT5651_PLL_K_MASK (RT5651_PLL_K_MAX) +#define RT5651_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x82) */ +#define RT5651_PLL_M_MAX 0xf +#define RT5651_PLL_M_MASK (RT5651_PLL_M_MAX << 12) +#define RT5651_PLL_M_SFT 12 +#define RT5651_PLL_M_BP (0x1 << 11) +#define RT5651_PLL_M_BP_SFT 11 + +/* PLL tracking mode 1 (0x83) */ +#define RT5651_STO1_T_MASK (0x1 << 15) +#define RT5651_STO1_T_SFT 15 +#define RT5651_STO1_T_SCLK (0x0 << 15) +#define RT5651_STO1_T_LRCK1 (0x1 << 15) +#define RT5651_STO2_T_MASK (0x1 << 12) +#define RT5651_STO2_T_SFT 12 +#define RT5651_STO2_T_I2S2 (0x0 << 12) +#define RT5651_STO2_T_LRCK2 (0x1 << 12) +#define RT5651_ASRC2_REF_MASK (0x1 << 11) +#define RT5651_ASRC2_REF_SFT 11 +#define RT5651_ASRC2_REF_LRCK2 (0x0 << 11) +#define RT5651_ASRC2_REF_LRCK1 (0x1 << 11) +#define RT5651_DMIC_1_M_MASK (0x1 << 9) +#define RT5651_DMIC_1_M_SFT 9 +#define RT5651_DMIC_1_M_NOR (0x0 << 9) +#define RT5651_DMIC_1_M_ASYN (0x1 << 9) + +/* PLL tracking mode 2 (0x84) */ +#define RT5651_STO1_ASRC_EN (0x1 << 15) +#define RT5651_STO1_ASRC_EN_SFT 15 +#define RT5651_STO2_ASRC_EN (0x1 << 14) +#define RT5651_STO2_ASRC_EN_SFT 14 +#define RT5651_STO1_DAC_M_MASK (0x1 << 13) +#define RT5651_STO1_DAC_M_SFT 13 +#define RT5651_STO1_DAC_M_NOR (0x0 << 13) +#define RT5651_STO1_DAC_M_ASRC (0x1 << 13) +#define RT5651_STO2_DAC_M_MASK (0x1 << 12) +#define RT5651_STO2_DAC_M_SFT 12 +#define RT5651_STO2_DAC_M_NOR (0x0 << 12) +#define RT5651_STO2_DAC_M_ASRC (0x1 << 12) +#define RT5651_ADC_M_MASK (0x1 << 11) +#define RT5651_ADC_M_SFT 11 +#define RT5651_ADC_M_NOR (0x0 << 11) +#define RT5651_ADC_M_ASRC (0x1 << 11) +#define RT5651_I2S1_R_D_MASK (0x1 << 4) +#define RT5651_I2S1_R_D_SFT 4 +#define RT5651_I2S1_R_D_DIS (0x0 << 4) +#define RT5651_I2S1_R_D_EN (0x1 << 4) +#define RT5651_I2S2_R_D_MASK (0x1 << 3) +#define RT5651_I2S2_R_D_SFT 3 +#define RT5651_I2S2_R_D_DIS (0x0 << 3) +#define RT5651_I2S2_R_D_EN (0x1 << 3) +#define RT5651_PRE_SCLK_MASK (0x3) +#define RT5651_PRE_SCLK_SFT 0 +#define RT5651_PRE_SCLK_512 (0x0) +#define RT5651_PRE_SCLK_1024 (0x1) +#define RT5651_PRE_SCLK_2048 (0x2) + +/* PLL tracking mode 3 (0x85) */ +#define RT5651_I2S1_RATE_MASK (0xf << 12) +#define RT5651_I2S1_RATE_SFT 12 +#define RT5651_I2S2_RATE_MASK (0xf << 8) +#define RT5651_I2S2_RATE_SFT 8 +#define RT5651_G_ASRC_LP_MASK (0x1 << 3) +#define RT5651_G_ASRC_LP_SFT 3 +#define RT5651_ASRC_LP_F_M (0x1 << 2) +#define RT5651_ASRC_LP_F_SFT 2 +#define RT5651_ASRC_LP_F_NOR (0x0 << 2) +#define RT5651_ASRC_LP_F_SB (0x1 << 2) +#define RT5651_FTK_PH_DET_MASK (0x3) +#define RT5651_FTK_PH_DET_SFT 0 +#define RT5651_FTK_PH_DET_DIV1 (0x0) +#define RT5651_FTK_PH_DET_DIV2 (0x1) +#define RT5651_FTK_PH_DET_DIV4 (0x2) +#define RT5651_FTK_PH_DET_DIV8 (0x3) + +/*PLL tracking mode 6 (0x89) */ +#define RT5651_I2S1_PD_MASK (0x7 << 12) +#define RT5651_I2S1_PD_SFT 12 +#define RT5651_I2S2_PD_MASK (0x7 << 8) +#define RT5651_I2S2_PD_SFT 8 + +/*PLL tracking mode 7 (0x8a) */ +#define RT5651_FSI1_RATE_MASK (0xf << 12) +#define RT5651_FSI1_RATE_SFT 12 +#define RT5651_FSI2_RATE_MASK (0xf << 8) +#define RT5651_FSI2_RATE_SFT 8 + +/* HPOUT Over Current Detection (0x8b) */ +#define RT5651_HP_OVCD_MASK (0x1 << 10) +#define RT5651_HP_OVCD_SFT 10 +#define RT5651_HP_OVCD_DIS (0x0 << 10) +#define RT5651_HP_OVCD_EN (0x1 << 10) +#define RT5651_HP_OC_TH_MASK (0x3 << 8) +#define RT5651_HP_OC_TH_SFT 8 +#define RT5651_HP_OC_TH_90 (0x0 << 8) +#define RT5651_HP_OC_TH_105 (0x1 << 8) +#define RT5651_HP_OC_TH_120 (0x2 << 8) +#define RT5651_HP_OC_TH_135 (0x3 << 8) + +/* Depop Mode Control 1 (0x8e) */ +#define RT5651_SMT_TRIG_MASK (0x1 << 15) +#define RT5651_SMT_TRIG_SFT 15 +#define RT5651_SMT_TRIG_DIS (0x0 << 15) +#define RT5651_SMT_TRIG_EN (0x1 << 15) +#define RT5651_HP_L_SMT_MASK (0x1 << 9) +#define RT5651_HP_L_SMT_SFT 9 +#define RT5651_HP_L_SMT_DIS (0x0 << 9) +#define RT5651_HP_L_SMT_EN (0x1 << 9) +#define RT5651_HP_R_SMT_MASK (0x1 << 8) +#define RT5651_HP_R_SMT_SFT 8 +#define RT5651_HP_R_SMT_DIS (0x0 << 8) +#define RT5651_HP_R_SMT_EN (0x1 << 8) +#define RT5651_HP_CD_PD_MASK (0x1 << 7) +#define RT5651_HP_CD_PD_SFT 7 +#define RT5651_HP_CD_PD_DIS (0x0 << 7) +#define RT5651_HP_CD_PD_EN (0x1 << 7) +#define RT5651_RSTN_MASK (0x1 << 6) +#define RT5651_RSTN_SFT 6 +#define RT5651_RSTN_DIS (0x0 << 6) +#define RT5651_RSTN_EN (0x1 << 6) +#define RT5651_RSTP_MASK (0x1 << 5) +#define RT5651_RSTP_SFT 5 +#define RT5651_RSTP_DIS (0x0 << 5) +#define RT5651_RSTP_EN (0x1 << 5) +#define RT5651_HP_CO_MASK (0x1 << 4) +#define RT5651_HP_CO_SFT 4 +#define RT5651_HP_CO_DIS (0x0 << 4) +#define RT5651_HP_CO_EN (0x1 << 4) +#define RT5651_HP_CP_MASK (0x1 << 3) +#define RT5651_HP_CP_SFT 3 +#define RT5651_HP_CP_PD (0x0 << 3) +#define RT5651_HP_CP_PU (0x1 << 3) +#define RT5651_HP_SG_MASK (0x1 << 2) +#define RT5651_HP_SG_SFT 2 +#define RT5651_HP_SG_DIS (0x0 << 2) +#define RT5651_HP_SG_EN (0x1 << 2) +#define RT5651_HP_DP_MASK (0x1 << 1) +#define RT5651_HP_DP_SFT 1 +#define RT5651_HP_DP_PD (0x0 << 1) +#define RT5651_HP_DP_PU (0x1 << 1) +#define RT5651_HP_CB_MASK (0x1) +#define RT5651_HP_CB_SFT 0 +#define RT5651_HP_CB_PD (0x0) +#define RT5651_HP_CB_PU (0x1) + +/* Depop Mode Control 2 (0x8f) */ +#define RT5651_DEPOP_MASK (0x1 << 13) +#define RT5651_DEPOP_SFT 13 +#define RT5651_DEPOP_AUTO (0x0 << 13) +#define RT5651_DEPOP_MAN (0x1 << 13) +#define RT5651_RAMP_MASK (0x1 << 12) +#define RT5651_RAMP_SFT 12 +#define RT5651_RAMP_DIS (0x0 << 12) +#define RT5651_RAMP_EN (0x1 << 12) +#define RT5651_BPS_MASK (0x1 << 11) +#define RT5651_BPS_SFT 11 +#define RT5651_BPS_DIS (0x0 << 11) +#define RT5651_BPS_EN (0x1 << 11) +#define RT5651_FAST_UPDN_MASK (0x1 << 10) +#define RT5651_FAST_UPDN_SFT 10 +#define RT5651_FAST_UPDN_DIS (0x0 << 10) +#define RT5651_FAST_UPDN_EN (0x1 << 10) +#define RT5651_MRES_MASK (0x3 << 8) +#define RT5651_MRES_SFT 8 +#define RT5651_MRES_15MO (0x0 << 8) +#define RT5651_MRES_25MO (0x1 << 8) +#define RT5651_MRES_35MO (0x2 << 8) +#define RT5651_MRES_45MO (0x3 << 8) +#define RT5651_VLO_MASK (0x1 << 7) +#define RT5651_VLO_SFT 7 +#define RT5651_VLO_3V (0x0 << 7) +#define RT5651_VLO_32V (0x1 << 7) +#define RT5651_DIG_DP_MASK (0x1 << 6) +#define RT5651_DIG_DP_SFT 6 +#define RT5651_DIG_DP_DIS (0x0 << 6) +#define RT5651_DIG_DP_EN (0x1 << 6) +#define RT5651_DP_TH_MASK (0x3 << 4) +#define RT5651_DP_TH_SFT 4 + +/* Depop Mode Control 3 (0x90) */ +#define RT5651_CP_SYS_MASK (0x7 << 12) +#define RT5651_CP_SYS_SFT 12 +#define RT5651_CP_FQ1_MASK (0x7 << 8) +#define RT5651_CP_FQ1_SFT 8 +#define RT5651_CP_FQ2_MASK (0x7 << 4) +#define RT5651_CP_FQ2_SFT 4 +#define RT5651_CP_FQ3_MASK (0x7) +#define RT5651_CP_FQ3_SFT 0 +#define RT5651_CP_FQ_1_5_KHZ 0 +#define RT5651_CP_FQ_3_KHZ 1 +#define RT5651_CP_FQ_6_KHZ 2 +#define RT5651_CP_FQ_12_KHZ 3 +#define RT5651_CP_FQ_24_KHZ 4 +#define RT5651_CP_FQ_48_KHZ 5 +#define RT5651_CP_FQ_96_KHZ 6 +#define RT5651_CP_FQ_192_KHZ 7 + +/* HPOUT charge pump (0x91) */ +#define RT5651_OSW_L_MASK (0x1 << 11) +#define RT5651_OSW_L_SFT 11 +#define RT5651_OSW_L_DIS (0x0 << 11) +#define RT5651_OSW_L_EN (0x1 << 11) +#define RT5651_OSW_R_MASK (0x1 << 10) +#define RT5651_OSW_R_SFT 10 +#define RT5651_OSW_R_DIS (0x0 << 10) +#define RT5651_OSW_R_EN (0x1 << 10) +#define RT5651_PM_HP_MASK (0x3 << 8) +#define RT5651_PM_HP_SFT 8 +#define RT5651_PM_HP_LV (0x0 << 8) +#define RT5651_PM_HP_MV (0x1 << 8) +#define RT5651_PM_HP_HV (0x2 << 8) +#define RT5651_IB_HP_MASK (0x3 << 6) +#define RT5651_IB_HP_SFT 6 +#define RT5651_IB_HP_125IL (0x0 << 6) +#define RT5651_IB_HP_25IL (0x1 << 6) +#define RT5651_IB_HP_5IL (0x2 << 6) +#define RT5651_IB_HP_1IL (0x3 << 6) + +/* Micbias Control (0x93) */ +#define RT5651_MIC1_BS_MASK (0x1 << 15) +#define RT5651_MIC1_BS_SFT 15 +#define RT5651_MIC1_BS_9AV (0x0 << 15) +#define RT5651_MIC1_BS_75AV (0x1 << 15) +#define RT5651_MIC1_CLK_MASK (0x1 << 13) +#define RT5651_MIC1_CLK_SFT 13 +#define RT5651_MIC1_CLK_DIS (0x0 << 13) +#define RT5651_MIC1_CLK_EN (0x1 << 13) +#define RT5651_MIC1_OVCD_MASK (0x1 << 11) +#define RT5651_MIC1_OVCD_SFT 11 +#define RT5651_MIC1_OVCD_DIS (0x0 << 11) +#define RT5651_MIC1_OVCD_EN (0x1 << 11) +#define RT5651_MIC1_OVTH_MASK (0x3 << 9) +#define RT5651_MIC1_OVTH_SFT 9 +#define RT5651_MIC1_OVTH_600UA (0x0 << 9) +#define RT5651_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5651_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5651_PWR_MB_MASK (0x1 << 5) +#define RT5651_PWR_MB_SFT 5 +#define RT5651_PWR_MB_PD (0x0 << 5) +#define RT5651_PWR_MB_PU (0x1 << 5) +#define RT5651_PWR_CLK12M_MASK (0x1 << 4) +#define RT5651_PWR_CLK12M_SFT 4 +#define RT5651_PWR_CLK12M_PD (0x0 << 4) +#define RT5651_PWR_CLK12M_PU (0x1 << 4) + +/* Analog JD Control 1 (0x94) */ +#define RT5651_JD2_CMP_MASK (0x7 << 12) +#define RT5651_JD2_CMP_SFT 12 +#define RT5651_JD_PU (0x1 << 11) +#define RT5651_JD_PU_SFT 11 +#define RT5651_JD_PD (0x1 << 10) +#define RT5651_JD_PD_SFT 10 +#define RT5651_JD_MODE_SEL_MASK (0x3 << 8) +#define RT5651_JD_MODE_SEL_SFT 8 +#define RT5651_JD_MODE_SEL_M0 (0x0 << 8) +#define RT5651_JD_MODE_SEL_M1 (0x1 << 8) +#define RT5651_JD_MODE_SEL_M2 (0x2 << 8) +#define RT5651_JD_M_CMP (0x7 << 4) +#define RT5651_JD_M_CMP_SFT 4 +#define RT5651_JD_M_PU (0x1 << 3) +#define RT5651_JD_M_PU_SFT 3 +#define RT5651_JD_M_PD (0x1 << 2) +#define RT5651_JD_M_PD_SFT 2 +#define RT5651_JD_M_MODE_SEL_MASK (0x3) +#define RT5651_JD_M_MODE_SEL_SFT 0 +#define RT5651_JD_M_MODE_SEL_M0 (0x0) +#define RT5651_JD_M_MODE_SEL_M1 (0x1) +#define RT5651_JD_M_MODE_SEL_M2 (0x2) + +/* Analog JD Control 2 (0x95) */ +#define RT5651_JD3_CMP_MASK (0x7 << 12) +#define RT5651_JD3_CMP_SFT 12 + +/* EQ Control 1 (0xb0) */ +#define RT5651_EQ_SRC_MASK (0x1 << 15) +#define RT5651_EQ_SRC_SFT 15 +#define RT5651_EQ_SRC_DAC (0x0 << 15) +#define RT5651_EQ_SRC_ADC (0x1 << 15) +#define RT5651_EQ_UPD (0x1 << 14) +#define RT5651_EQ_UPD_BIT 14 +#define RT5651_EQ_CD_MASK (0x1 << 13) +#define RT5651_EQ_CD_SFT 13 +#define RT5651_EQ_CD_DIS (0x0 << 13) +#define RT5651_EQ_CD_EN (0x1 << 13) +#define RT5651_EQ_DITH_MASK (0x3 << 8) +#define RT5651_EQ_DITH_SFT 8 +#define RT5651_EQ_DITH_NOR (0x0 << 8) +#define RT5651_EQ_DITH_LSB (0x1 << 8) +#define RT5651_EQ_DITH_LSB_1 (0x2 << 8) +#define RT5651_EQ_DITH_LSB_2 (0x3 << 8) +#define RT5651_EQ_CD_F (0x1 << 7) +#define RT5651_EQ_CD_F_BIT 7 +#define RT5651_EQ_STA_HP2 (0x1 << 6) +#define RT5651_EQ_STA_HP2_BIT 6 +#define RT5651_EQ_STA_HP1 (0x1 << 5) +#define RT5651_EQ_STA_HP1_BIT 5 +#define RT5651_EQ_STA_BP4 (0x1 << 4) +#define RT5651_EQ_STA_BP4_BIT 4 +#define RT5651_EQ_STA_BP3 (0x1 << 3) +#define RT5651_EQ_STA_BP3_BIT 3 +#define RT5651_EQ_STA_BP2 (0x1 << 2) +#define RT5651_EQ_STA_BP2_BIT 2 +#define RT5651_EQ_STA_BP1 (0x1 << 1) +#define RT5651_EQ_STA_BP1_BIT 1 +#define RT5651_EQ_STA_LP (0x1) +#define RT5651_EQ_STA_LP_BIT 0 + +/* EQ Control 2 (0xb1) */ +#define RT5651_EQ_HPF1_M_MASK (0x1 << 8) +#define RT5651_EQ_HPF1_M_SFT 8 +#define RT5651_EQ_HPF1_M_HI (0x0 << 8) +#define RT5651_EQ_HPF1_M_1ST (0x1 << 8) +#define RT5651_EQ_LPF1_M_MASK (0x1 << 7) +#define RT5651_EQ_LPF1_M_SFT 7 +#define RT5651_EQ_LPF1_M_LO (0x0 << 7) +#define RT5651_EQ_LPF1_M_1ST (0x1 << 7) +#define RT5651_EQ_HPF2_MASK (0x1 << 6) +#define RT5651_EQ_HPF2_SFT 6 +#define RT5651_EQ_HPF2_DIS (0x0 << 6) +#define RT5651_EQ_HPF2_EN (0x1 << 6) +#define RT5651_EQ_HPF1_MASK (0x1 << 5) +#define RT5651_EQ_HPF1_SFT 5 +#define RT5651_EQ_HPF1_DIS (0x0 << 5) +#define RT5651_EQ_HPF1_EN (0x1 << 5) +#define RT5651_EQ_BPF4_MASK (0x1 << 4) +#define RT5651_EQ_BPF4_SFT 4 +#define RT5651_EQ_BPF4_DIS (0x0 << 4) +#define RT5651_EQ_BPF4_EN (0x1 << 4) +#define RT5651_EQ_BPF3_MASK (0x1 << 3) +#define RT5651_EQ_BPF3_SFT 3 +#define RT5651_EQ_BPF3_DIS (0x0 << 3) +#define RT5651_EQ_BPF3_EN (0x1 << 3) +#define RT5651_EQ_BPF2_MASK (0x1 << 2) +#define RT5651_EQ_BPF2_SFT 2 +#define RT5651_EQ_BPF2_DIS (0x0 << 2) +#define RT5651_EQ_BPF2_EN (0x1 << 2) +#define RT5651_EQ_BPF1_MASK (0x1 << 1) +#define RT5651_EQ_BPF1_SFT 1 +#define RT5651_EQ_BPF1_DIS (0x0 << 1) +#define RT5651_EQ_BPF1_EN (0x1 << 1) +#define RT5651_EQ_LPF_MASK (0x1) +#define RT5651_EQ_LPF_SFT 0 +#define RT5651_EQ_LPF_DIS (0x0) +#define RT5651_EQ_LPF_EN (0x1) +#define RT5651_EQ_CTRL_MASK (0x7f) + +/* Memory Test (0xb2) */ +#define RT5651_MT_MASK (0x1 << 15) +#define RT5651_MT_SFT 15 +#define RT5651_MT_DIS (0x0 << 15) +#define RT5651_MT_EN (0x1 << 15) + +/* ALC Control 1 (0xb4) */ +#define RT5651_ALC_P_MASK (0x1 << 15) +#define RT5651_ALC_P_SFT 15 +#define RT5651_ALC_P_DAC (0x0 << 15) +#define RT5651_ALC_P_ADC (0x1 << 15) +#define RT5651_ALC_MASK (0x1 << 14) +#define RT5651_ALC_SFT 14 +#define RT5651_ALC_DIS (0x0 << 14) +#define RT5651_ALC_EN (0x1 << 14) +#define RT5651_ALC_UPD (0x1 << 13) +#define RT5651_ALC_UPD_BIT 13 +#define RT5651_ALC_AR_MASK (0x1f << 8) +#define RT5651_ALC_AR_SFT 8 +#define RT5651_ALC_R_MASK (0x7 << 5) +#define RT5651_ALC_R_SFT 5 +#define RT5651_ALC_R_48K (0x1 << 5) +#define RT5651_ALC_R_96K (0x2 << 5) +#define RT5651_ALC_R_192K (0x3 << 5) +#define RT5651_ALC_R_441K (0x5 << 5) +#define RT5651_ALC_R_882K (0x6 << 5) +#define RT5651_ALC_R_1764K (0x7 << 5) +#define RT5651_ALC_RC_MASK (0x1f) +#define RT5651_ALC_RC_SFT 0 + +/* ALC Control 2 (0xb5) */ +#define RT5651_ALC_POB_MASK (0x3f << 8) +#define RT5651_ALC_POB_SFT 8 +#define RT5651_ALC_DRC_MASK (0x1 << 7) +#define RT5651_ALC_DRC_SFT 7 +#define RT5651_ALC_DRC_DIS (0x0 << 7) +#define RT5651_ALC_DRC_EN (0x1 << 7) +#define RT5651_ALC_CPR_MASK (0x3 << 5) +#define RT5651_ALC_CPR_SFT 5 +#define RT5651_ALC_CPR_1_1 (0x0 << 5) +#define RT5651_ALC_CPR_1_2 (0x1 << 5) +#define RT5651_ALC_CPR_1_4 (0x2 << 5) +#define RT5651_ALC_CPR_1_8 (0x3 << 5) +#define RT5651_ALC_PRB_MASK (0x1f) +#define RT5651_ALC_PRB_SFT 0 + +/* ALC Control 3 (0xb6) */ +#define RT5651_ALC_NGB_MASK (0xf << 12) +#define RT5651_ALC_NGB_SFT 12 +#define RT5651_ALC_TAR_MASK (0x1f << 7) +#define RT5651_ALC_TAR_SFT 7 +#define RT5651_ALC_NG_MASK (0x1 << 6) +#define RT5651_ALC_NG_SFT 6 +#define RT5651_ALC_NG_DIS (0x0 << 6) +#define RT5651_ALC_NG_EN (0x1 << 6) +#define RT5651_ALC_NGH_MASK (0x1 << 5) +#define RT5651_ALC_NGH_SFT 5 +#define RT5651_ALC_NGH_DIS (0x0 << 5) +#define RT5651_ALC_NGH_EN (0x1 << 5) +#define RT5651_ALC_NGT_MASK (0x1f) +#define RT5651_ALC_NGT_SFT 0 + +/* Jack Detect Control 1 (0xbb) */ +#define RT5651_JD_MASK (0x7 << 13) +#define RT5651_JD_SFT 13 +#define RT5651_JD_DIS (0x0 << 13) +#define RT5651_JD_GPIO1 (0x1 << 13) +#define RT5651_JD_GPIO2 (0x2 << 13) +#define RT5651_JD_GPIO3 (0x3 << 13) +#define RT5651_JD_GPIO4 (0x4 << 13) +#define RT5651_JD_GPIO5 (0x5 << 13) +#define RT5651_JD_GPIO6 (0x6 << 13) +#define RT5651_JD_HP_MASK (0x1 << 11) +#define RT5651_JD_HP_SFT 11 +#define RT5651_JD_HP_DIS (0x0 << 11) +#define RT5651_JD_HP_EN (0x1 << 11) +#define RT5651_JD_HP_TRG_MASK (0x1 << 10) +#define RT5651_JD_HP_TRG_SFT 10 +#define RT5651_JD_HP_TRG_LO (0x0 << 10) +#define RT5651_JD_HP_TRG_HI (0x1 << 10) +#define RT5651_JD_SPL_MASK (0x1 << 9) +#define RT5651_JD_SPL_SFT 9 +#define RT5651_JD_SPL_DIS (0x0 << 9) +#define RT5651_JD_SPL_EN (0x1 << 9) +#define RT5651_JD_SPL_TRG_MASK (0x1 << 8) +#define RT5651_JD_SPL_TRG_SFT 8 +#define RT5651_JD_SPL_TRG_LO (0x0 << 8) +#define RT5651_JD_SPL_TRG_HI (0x1 << 8) +#define RT5651_JD_SPR_MASK (0x1 << 7) +#define RT5651_JD_SPR_SFT 7 +#define RT5651_JD_SPR_DIS (0x0 << 7) +#define RT5651_JD_SPR_EN (0x1 << 7) +#define RT5651_JD_SPR_TRG_MASK (0x1 << 6) +#define RT5651_JD_SPR_TRG_SFT 6 +#define RT5651_JD_SPR_TRG_LO (0x0 << 6) +#define RT5651_JD_SPR_TRG_HI (0x1 << 6) +#define RT5651_JD_LO_MASK (0x1 << 3) +#define RT5651_JD_LO_SFT 3 +#define RT5651_JD_LO_DIS (0x0 << 3) +#define RT5651_JD_LO_EN (0x1 << 3) +#define RT5651_JD_LO_TRG_MASK (0x1 << 2) +#define RT5651_JD_LO_TRG_SFT 2 +#define RT5651_JD_LO_TRG_LO (0x0 << 2) +#define RT5651_JD_LO_TRG_HI (0x1 << 2) + +/* Jack Detect Control 2 (0xbc) */ +#define RT5651_JD_TRG_SEL_MASK (0x7 << 9) +#define RT5651_JD_TRG_SEL_SFT 9 +#define RT5651_JD_TRG_SEL_GPIO (0x0 << 9) +#define RT5651_JD_TRG_SEL_JD1_1 (0x1 << 9) +#define RT5651_JD_TRG_SEL_JD1_2 (0x2 << 9) +#define RT5651_JD_TRG_SEL_JD2 (0x3 << 9) +#define RT5651_JD_TRG_SEL_JD3 (0x4 << 9) +#define RT5651_JD3_IRQ_EN (0x1 << 8) +#define RT5651_JD3_IRQ_EN_SFT 8 +#define RT5651_JD3_EN_STKY (0x1 << 7) +#define RT5651_JD3_EN_STKY_SFT 7 +#define RT5651_JD3_INV (0x1 << 6) +#define RT5651_JD3_INV_SFT 6 + +/* IRQ Control 1 (0xbd) */ +#define RT5651_IRQ_JD_MASK (0x1 << 15) +#define RT5651_IRQ_JD_SFT 15 +#define RT5651_IRQ_JD_BP (0x0 << 15) +#define RT5651_IRQ_JD_NOR (0x1 << 15) +#define RT5651_JD_STKY_MASK (0x1 << 13) +#define RT5651_JD_STKY_SFT 13 +#define RT5651_JD_STKY_DIS (0x0 << 13) +#define RT5651_JD_STKY_EN (0x1 << 13) +#define RT5651_JD_P_MASK (0x1 << 11) +#define RT5651_JD_P_SFT 11 +#define RT5651_JD_P_NOR (0x0 << 11) +#define RT5651_JD_P_INV (0x1 << 11) +#define RT5651_JD1_1_IRQ_EN (0x1 << 9) +#define RT5651_JD1_1_IRQ_EN_SFT 9 +#define RT5651_JD1_1_EN_STKY (0x1 << 8) +#define RT5651_JD1_1_EN_STKY_SFT 8 +#define RT5651_JD1_1_INV (0x1 << 7) +#define RT5651_JD1_1_INV_SFT 7 +#define RT5651_JD1_2_IRQ_EN (0x1 << 6) +#define RT5651_JD1_2_IRQ_EN_SFT 6 +#define RT5651_JD1_2_EN_STKY (0x1 << 5) +#define RT5651_JD1_2_EN_STKY_SFT 5 +#define RT5651_JD1_2_INV (0x1 << 4) +#define RT5651_JD1_2_INV_SFT 4 +#define RT5651_JD2_IRQ_EN (0x1 << 3) +#define RT5651_JD2_IRQ_EN_SFT 3 +#define RT5651_JD2_EN_STKY (0x1 << 2) +#define RT5651_JD2_EN_STKY_SFT 2 +#define RT5651_JD2_INV (0x1 << 1) +#define RT5651_JD2_INV_SFT 1 + +/* IRQ Control 2 (0xbe) */ +#define RT5651_IRQ_MB1_OC_MASK (0x1 << 15) +#define RT5651_IRQ_MB1_OC_SFT 15 +#define RT5651_IRQ_MB1_OC_BP (0x0 << 15) +#define RT5651_IRQ_MB1_OC_NOR (0x1 << 15) +#define RT5651_MB1_OC_STKY_MASK (0x1 << 11) +#define RT5651_MB1_OC_STKY_SFT 11 +#define RT5651_MB1_OC_STKY_DIS (0x0 << 11) +#define RT5651_MB1_OC_STKY_EN (0x1 << 11) +#define RT5651_MB1_OC_P_MASK (0x1 << 7) +#define RT5651_MB1_OC_P_SFT 7 +#define RT5651_MB1_OC_P_NOR (0x0 << 7) +#define RT5651_MB1_OC_P_INV (0x1 << 7) +#define RT5651_MB2_OC_P_MASK (0x1 << 6) +#define RT5651_MB1_OC_CLR (0x1 << 3) +#define RT5651_MB1_OC_CLR_SFT 3 +#define RT5651_STA_GPIO8 (0x1) +#define RT5651_STA_GPIO8_BIT 0 + +/* Internal Status and GPIO status (0xbf) */ +#define RT5651_STA_JD3 (0x1 << 15) +#define RT5651_STA_JD3_BIT 15 +#define RT5651_STA_JD2 (0x1 << 14) +#define RT5651_STA_JD2_BIT 14 +#define RT5651_STA_JD1_2 (0x1 << 13) +#define RT5651_STA_JD1_2_BIT 13 +#define RT5651_STA_JD1_1 (0x1 << 12) +#define RT5651_STA_JD1_1_BIT 12 +#define RT5651_STA_GP7 (0x1 << 11) +#define RT5651_STA_GP7_BIT 11 +#define RT5651_STA_GP6 (0x1 << 10) +#define RT5651_STA_GP6_BIT 10 +#define RT5651_STA_GP5 (0x1 << 9) +#define RT5651_STA_GP5_BIT 9 +#define RT5651_STA_GP1 (0x1 << 8) +#define RT5651_STA_GP1_BIT 8 +#define RT5651_STA_GP2 (0x1 << 7) +#define RT5651_STA_GP2_BIT 7 +#define RT5651_STA_GP3 (0x1 << 6) +#define RT5651_STA_GP3_BIT 6 +#define RT5651_STA_GP4 (0x1 << 5) +#define RT5651_STA_GP4_BIT 5 +#define RT5651_STA_GP_JD (0x1 << 4) +#define RT5651_STA_GP_JD_BIT 4 + +/* GPIO Control 1 (0xc0) */ +#define RT5651_GP1_PIN_MASK (0x1 << 15) +#define RT5651_GP1_PIN_SFT 15 +#define RT5651_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5651_GP1_PIN_IRQ (0x1 << 15) +#define RT5651_GP2_PIN_MASK (0x1 << 14) +#define RT5651_GP2_PIN_SFT 14 +#define RT5651_GP2_PIN_GPIO2 (0x0 << 14) +#define RT5651_GP2_PIN_DMIC1_SCL (0x1 << 14) +#define RT5651_GPIO_M_MASK (0x1 << 9) +#define RT5651_GPIO_M_SFT 9 +#define RT5651_GPIO_M_FLT (0x0 << 9) +#define RT5651_GPIO_M_PH (0x1 << 9) +#define RT5651_I2S2_SEL_MASK (0x1 << 8) +#define RT5651_I2S2_SEL_SFT 8 +#define RT5651_I2S2_SEL_I2S (0x0 << 8) +#define RT5651_I2S2_SEL_GPIO (0x1 << 8) +#define RT5651_GP5_PIN_MASK (0x1 << 7) +#define RT5651_GP5_PIN_SFT 7 +#define RT5651_GP5_PIN_GPIO5 (0x0 << 7) +#define RT5651_GP5_PIN_IRQ (0x1 << 7) +#define RT5651_GP6_PIN_MASK (0x1 << 6) +#define RT5651_GP6_PIN_SFT 6 +#define RT5651_GP6_PIN_GPIO6 (0x0 << 6) +#define RT5651_GP6_PIN_DMIC_SDA (0x1 << 6) +#define RT5651_GP7_PIN_MASK (0x1 << 5) +#define RT5651_GP7_PIN_SFT 5 +#define RT5651_GP7_PIN_GPIO7 (0x0 << 5) +#define RT5651_GP7_PIN_IRQ (0x1 << 5) +#define RT5651_GP8_PIN_MASK (0x1 << 4) +#define RT5651_GP8_PIN_SFT 4 +#define RT5651_GP8_PIN_GPIO8 (0x0 << 4) +#define RT5651_GP8_PIN_DMIC_SDA (0x1 << 4) +#define RT5651_GPIO_PDM_SEL_MASK (0x1 << 3) +#define RT5651_GPIO_PDM_SEL_SFT 3 +#define RT5651_GPIO_PDM_SEL_GPIO (0x0 << 3) +#define RT5651_GPIO_PDM_SEL_PDM (0x1 << 3) + +/* GPIO Control 2 (0xc1) */ +#define RT5651_GP5_DR_MASK (0x1 << 14) +#define RT5651_GP5_DR_SFT 14 +#define RT5651_GP5_DR_IN (0x0 << 14) +#define RT5651_GP5_DR_OUT (0x1 << 14) +#define RT5651_GP5_OUT_MASK (0x1 << 13) +#define RT5651_GP5_OUT_SFT 13 +#define RT5651_GP5_OUT_LO (0x0 << 13) +#define RT5651_GP5_OUT_HI (0x1 << 13) +#define RT5651_GP5_P_MASK (0x1 << 12) +#define RT5651_GP5_P_SFT 12 +#define RT5651_GP5_P_NOR (0x0 << 12) +#define RT5651_GP5_P_INV (0x1 << 12) +#define RT5651_GP4_DR_MASK (0x1 << 11) +#define RT5651_GP4_DR_SFT 11 +#define RT5651_GP4_DR_IN (0x0 << 11) +#define RT5651_GP4_DR_OUT (0x1 << 11) +#define RT5651_GP4_OUT_MASK (0x1 << 10) +#define RT5651_GP4_OUT_SFT 10 +#define RT5651_GP4_OUT_LO (0x0 << 10) +#define RT5651_GP4_OUT_HI (0x1 << 10) +#define RT5651_GP4_P_MASK (0x1 << 9) +#define RT5651_GP4_P_SFT 9 +#define RT5651_GP4_P_NOR (0x0 << 9) +#define RT5651_GP4_P_INV (0x1 << 9) +#define RT5651_GP3_DR_MASK (0x1 << 8) +#define RT5651_GP3_DR_SFT 8 +#define RT5651_GP3_DR_IN (0x0 << 8) +#define RT5651_GP3_DR_OUT (0x1 << 8) +#define RT5651_GP3_OUT_MASK (0x1 << 7) +#define RT5651_GP3_OUT_SFT 7 +#define RT5651_GP3_OUT_LO (0x0 << 7) +#define RT5651_GP3_OUT_HI (0x1 << 7) +#define RT5651_GP3_P_MASK (0x1 << 6) +#define RT5651_GP3_P_SFT 6 +#define RT5651_GP3_P_NOR (0x0 << 6) +#define RT5651_GP3_P_INV (0x1 << 6) +#define RT5651_GP2_DR_MASK (0x1 << 5) +#define RT5651_GP2_DR_SFT 5 +#define RT5651_GP2_DR_IN (0x0 << 5) +#define RT5651_GP2_DR_OUT (0x1 << 5) +#define RT5651_GP2_OUT_MASK (0x1 << 4) +#define RT5651_GP2_OUT_SFT 4 +#define RT5651_GP2_OUT_LO (0x0 << 4) +#define RT5651_GP2_OUT_HI (0x1 << 4) +#define RT5651_GP2_P_MASK (0x1 << 3) +#define RT5651_GP2_P_SFT 3 +#define RT5651_GP2_P_NOR (0x0 << 3) +#define RT5651_GP2_P_INV (0x1 << 3) +#define RT5651_GP1_DR_MASK (0x1 << 2) +#define RT5651_GP1_DR_SFT 2 +#define RT5651_GP1_DR_IN (0x0 << 2) +#define RT5651_GP1_DR_OUT (0x1 << 2) +#define RT5651_GP1_OUT_MASK (0x1 << 1) +#define RT5651_GP1_OUT_SFT 1 +#define RT5651_GP1_OUT_LO (0x0 << 1) +#define RT5651_GP1_OUT_HI (0x1 << 1) +#define RT5651_GP1_P_MASK (0x1) +#define RT5651_GP1_P_SFT 0 +#define RT5651_GP1_P_NOR (0x0) +#define RT5651_GP1_P_INV (0x1) + +/* GPIO Control 3 (0xc2) */ +#define RT5651_GP8_DR_MASK (0x1 << 8) +#define RT5651_GP8_DR_SFT 8 +#define RT5651_GP8_DR_IN (0x0 << 8) +#define RT5651_GP8_DR_OUT (0x1 << 8) +#define RT5651_GP8_OUT_MASK (0x1 << 7) +#define RT5651_GP8_OUT_SFT 7 +#define RT5651_GP8_OUT_LO (0x0 << 7) +#define RT5651_GP8_OUT_HI (0x1 << 7) +#define RT5651_GP8_P_MASK (0x1 << 6) +#define RT5651_GP8_P_SFT 6 +#define RT5651_GP8_P_NOR (0x0 << 6) +#define RT5651_GP8_P_INV (0x1 << 6) +#define RT5651_GP7_DR_MASK (0x1 << 5) +#define RT5651_GP7_DR_SFT 5 +#define RT5651_GP7_DR_IN (0x0 << 5) +#define RT5651_GP7_DR_OUT (0x1 << 5) +#define RT5651_GP7_OUT_MASK (0x1 << 4) +#define RT5651_GP7_OUT_SFT 4 +#define RT5651_GP7_OUT_LO (0x0 << 4) +#define RT5651_GP7_OUT_HI (0x1 << 4) +#define RT5651_GP7_P_MASK (0x1 << 3) +#define RT5651_GP7_P_SFT 3 +#define RT5651_GP7_P_NOR (0x0 << 3) +#define RT5651_GP7_P_INV (0x1 << 3) +#define RT5651_GP6_DR_MASK (0x1 << 2) +#define RT5651_GP6_DR_SFT 2 +#define RT5651_GP6_DR_IN (0x0 << 2) +#define RT5651_GP6_DR_OUT (0x1 << 2) +#define RT5651_GP6_OUT_MASK (0x1 << 1) +#define RT5651_GP6_OUT_SFT 1 +#define RT5651_GP6_OUT_LO (0x0 << 1) +#define RT5651_GP6_OUT_HI (0x1 << 1) +#define RT5651_GP6_P_MASK (0x1) +#define RT5651_GP6_P_SFT 0 +#define RT5651_GP6_P_NOR (0x0) +#define RT5651_GP6_P_INV (0x1) + +/* Scramble Control (0xce) */ +#define RT5651_SCB_SWAP_MASK (0x1 << 15) +#define RT5651_SCB_SWAP_SFT 15 +#define RT5651_SCB_SWAP_DIS (0x0 << 15) +#define RT5651_SCB_SWAP_EN (0x1 << 15) +#define RT5651_SCB_MASK (0x1 << 14) +#define RT5651_SCB_SFT 14 +#define RT5651_SCB_DIS (0x0 << 14) +#define RT5651_SCB_EN (0x1 << 14) + +/* Baseback Control (0xcf) */ +#define RT5651_BB_MASK (0x1 << 15) +#define RT5651_BB_SFT 15 +#define RT5651_BB_DIS (0x0 << 15) +#define RT5651_BB_EN (0x1 << 15) +#define RT5651_BB_CT_MASK (0x7 << 12) +#define RT5651_BB_CT_SFT 12 +#define RT5651_BB_CT_A (0x0 << 12) +#define RT5651_BB_CT_B (0x1 << 12) +#define RT5651_BB_CT_C (0x2 << 12) +#define RT5651_BB_CT_D (0x3 << 12) +#define RT5651_M_BB_L_MASK (0x1 << 9) +#define RT5651_M_BB_L_SFT 9 +#define RT5651_M_BB_R_MASK (0x1 << 8) +#define RT5651_M_BB_R_SFT 8 +#define RT5651_M_BB_HPF_L_MASK (0x1 << 7) +#define RT5651_M_BB_HPF_L_SFT 7 +#define RT5651_M_BB_HPF_R_MASK (0x1 << 6) +#define RT5651_M_BB_HPF_R_SFT 6 +#define RT5651_G_BB_BST_MASK (0x3f) +#define RT5651_G_BB_BST_SFT 0 + +/* MP3 Plus Control 1 (0xd0) */ +#define RT5651_M_MP3_L_MASK (0x1 << 15) +#define RT5651_M_MP3_L_SFT 15 +#define RT5651_M_MP3_R_MASK (0x1 << 14) +#define RT5651_M_MP3_R_SFT 14 +#define RT5651_M_MP3_MASK (0x1 << 13) +#define RT5651_M_MP3_SFT 13 +#define RT5651_M_MP3_DIS (0x0 << 13) +#define RT5651_M_MP3_EN (0x1 << 13) +#define RT5651_EG_MP3_MASK (0x1f << 8) +#define RT5651_EG_MP3_SFT 8 +#define RT5651_MP3_HLP_MASK (0x1 << 7) +#define RT5651_MP3_HLP_SFT 7 +#define RT5651_MP3_HLP_DIS (0x0 << 7) +#define RT5651_MP3_HLP_EN (0x1 << 7) +#define RT5651_M_MP3_ORG_L_MASK (0x1 << 6) +#define RT5651_M_MP3_ORG_L_SFT 6 +#define RT5651_M_MP3_ORG_R_MASK (0x1 << 5) +#define RT5651_M_MP3_ORG_R_SFT 5 + +/* MP3 Plus Control 2 (0xd1) */ +#define RT5651_MP3_WT_MASK (0x1 << 13) +#define RT5651_MP3_WT_SFT 13 +#define RT5651_MP3_WT_1_4 (0x0 << 13) +#define RT5651_MP3_WT_1_2 (0x1 << 13) +#define RT5651_OG_MP3_MASK (0x1f << 8) +#define RT5651_OG_MP3_SFT 8 +#define RT5651_HG_MP3_MASK (0x3f) +#define RT5651_HG_MP3_SFT 0 + +/* 3D HP Control 1 (0xd2) */ +#define RT5651_3D_CF_MASK (0x1 << 15) +#define RT5651_3D_CF_SFT 15 +#define RT5651_3D_CF_DIS (0x0 << 15) +#define RT5651_3D_CF_EN (0x1 << 15) +#define RT5651_3D_HP_MASK (0x1 << 14) +#define RT5651_3D_HP_SFT 14 +#define RT5651_3D_HP_DIS (0x0 << 14) +#define RT5651_3D_HP_EN (0x1 << 14) +#define RT5651_3D_BT_MASK (0x1 << 13) +#define RT5651_3D_BT_SFT 13 +#define RT5651_3D_BT_DIS (0x0 << 13) +#define RT5651_3D_BT_EN (0x1 << 13) +#define RT5651_3D_1F_MIX_MASK (0x3 << 11) +#define RT5651_3D_1F_MIX_SFT 11 +#define RT5651_3D_HP_M_MASK (0x1 << 10) +#define RT5651_3D_HP_M_SFT 10 +#define RT5651_3D_HP_M_SUR (0x0 << 10) +#define RT5651_3D_HP_M_FRO (0x1 << 10) +#define RT5651_M_3D_HRTF_MASK (0x1 << 9) +#define RT5651_M_3D_HRTF_SFT 9 +#define RT5651_M_3D_D2H_MASK (0x1 << 8) +#define RT5651_M_3D_D2H_SFT 8 +#define RT5651_M_3D_D2R_MASK (0x1 << 7) +#define RT5651_M_3D_D2R_SFT 7 +#define RT5651_M_3D_REVB_MASK (0x1 << 6) +#define RT5651_M_3D_REVB_SFT 6 + +/* Adjustable high pass filter control 1 (0xd3) */ +#define RT5651_2ND_HPF_MASK (0x1 << 15) +#define RT5651_2ND_HPF_SFT 15 +#define RT5651_2ND_HPF_DIS (0x0 << 15) +#define RT5651_2ND_HPF_EN (0x1 << 15) +#define RT5651_HPF_CF_L_MASK (0x7 << 12) +#define RT5651_HPF_CF_L_SFT 12 +#define RT5651_HPF_CF_R_MASK (0x7 << 8) +#define RT5651_HPF_CF_R_SFT 8 +#define RT5651_ZD_T_MASK (0x3 << 6) +#define RT5651_ZD_T_SFT 6 +#define RT5651_ZD_F_MASK (0x3 << 4) +#define RT5651_ZD_F_SFT 4 +#define RT5651_ZD_F_IM (0x0 << 4) +#define RT5651_ZD_F_ZC_IM (0x1 << 4) +#define RT5651_ZD_F_ZC_IOD (0x2 << 4) +#define RT5651_ZD_F_UN (0x3 << 4) + +/* Adjustable high pass filter control 2 (0xd4) */ +#define RT5651_HPF_CF_L_NUM_MASK (0x3f << 8) +#define RT5651_HPF_CF_L_NUM_SFT 8 +#define RT5651_HPF_CF_R_NUM_MASK (0x3f) +#define RT5651_HPF_CF_R_NUM_SFT 0 + +/* HP calibration control and Amp detection (0xd6) */ +#define RT5651_SI_DAC_MASK (0x1 << 11) +#define RT5651_SI_DAC_SFT 11 +#define RT5651_SI_DAC_AUTO (0x0 << 11) +#define RT5651_SI_DAC_TEST (0x1 << 11) +#define RT5651_DC_CAL_M_MASK (0x1 << 10) +#define RT5651_DC_CAL_M_SFT 10 +#define RT5651_DC_CAL_M_NOR (0x0 << 10) +#define RT5651_DC_CAL_M_CAL (0x1 << 10) +#define RT5651_DC_CAL_MASK (0x1 << 9) +#define RT5651_DC_CAL_SFT 9 +#define RT5651_DC_CAL_DIS (0x0 << 9) +#define RT5651_DC_CAL_EN (0x1 << 9) +#define RT5651_HPD_RCV_MASK (0x7 << 6) +#define RT5651_HPD_RCV_SFT 6 +#define RT5651_HPD_PS_MASK (0x1 << 5) +#define RT5651_HPD_PS_SFT 5 +#define RT5651_HPD_PS_DIS (0x0 << 5) +#define RT5651_HPD_PS_EN (0x1 << 5) +#define RT5651_CAL_M_MASK (0x1 << 4) +#define RT5651_CAL_M_SFT 4 +#define RT5651_CAL_M_DEP (0x0 << 4) +#define RT5651_CAL_M_CAL (0x1 << 4) +#define RT5651_CAL_MASK (0x1 << 3) +#define RT5651_CAL_SFT 3 +#define RT5651_CAL_DIS (0x0 << 3) +#define RT5651_CAL_EN (0x1 << 3) +#define RT5651_CAL_TEST_MASK (0x1 << 2) +#define RT5651_CAL_TEST_SFT 2 +#define RT5651_CAL_TEST_DIS (0x0 << 2) +#define RT5651_CAL_TEST_EN (0x1 << 2) +#define RT5651_CAL_P_MASK (0x3) +#define RT5651_CAL_P_SFT 0 +#define RT5651_CAL_P_NONE (0x0) +#define RT5651_CAL_P_CAL (0x1) +#define RT5651_CAL_P_DAC_CAL (0x2) + +/* Soft volume and zero cross control 1 (0xd9) */ +#define RT5651_SV_MASK (0x1 << 15) +#define RT5651_SV_SFT 15 +#define RT5651_SV_DIS (0x0 << 15) +#define RT5651_SV_EN (0x1 << 15) +#define RT5651_OUT_SV_MASK (0x1 << 13) +#define RT5651_OUT_SV_SFT 13 +#define RT5651_OUT_SV_DIS (0x0 << 13) +#define RT5651_OUT_SV_EN (0x1 << 13) +#define RT5651_HP_SV_MASK (0x1 << 12) +#define RT5651_HP_SV_SFT 12 +#define RT5651_HP_SV_DIS (0x0 << 12) +#define RT5651_HP_SV_EN (0x1 << 12) +#define RT5651_ZCD_DIG_MASK (0x1 << 11) +#define RT5651_ZCD_DIG_SFT 11 +#define RT5651_ZCD_DIG_DIS (0x0 << 11) +#define RT5651_ZCD_DIG_EN (0x1 << 11) +#define RT5651_ZCD_MASK (0x1 << 10) +#define RT5651_ZCD_SFT 10 +#define RT5651_ZCD_PD (0x0 << 10) +#define RT5651_ZCD_PU (0x1 << 10) +#define RT5651_M_ZCD_MASK (0x3f << 4) +#define RT5651_M_ZCD_SFT 4 +#define RT5651_M_ZCD_OM_L (0x1 << 7) +#define RT5651_M_ZCD_OM_R (0x1 << 6) +#define RT5651_M_ZCD_RM_L (0x1 << 5) +#define RT5651_M_ZCD_RM_R (0x1 << 4) +#define RT5651_SV_DLY_MASK (0xf) +#define RT5651_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0xda) */ +#define RT5651_ZCD_HP_MASK (0x1 << 15) +#define RT5651_ZCD_HP_SFT 15 +#define RT5651_ZCD_HP_DIS (0x0 << 15) +#define RT5651_ZCD_HP_EN (0x1 << 15) + +/* Digital Misc Control (0xfa) */ +#define RT5651_I2S2_MS_SP_MASK (0x1 << 8) +#define RT5651_I2S2_MS_SP_SEL 8 +#define RT5651_I2S2_MS_SP_64 (0x0 << 8) +#define RT5651_I2S2_MS_SP_50 (0x1 << 8) +#define RT5651_CLK_DET_EN (0x1 << 3) +#define RT5651_CLK_DET_EN_SFT 3 +#define RT5651_AMP_DET_EN (0x1 << 1) +#define RT5651_AMP_DET_EN_SFT 1 +#define RT5651_D_GATE_EN (0x1) +#define RT5651_D_GATE_EN_SFT 0 + +/* Codec Private Register definition */ + +/* MIC Over current threshold scale factor (0x15) */ +#define RT5651_MIC_OVCD_SF_MASK (0x3 << 8) +#define RT5651_MIC_OVCD_SF_SFT 8 +#define RT5651_MIC_OVCD_SF_0P5 (0x0 << 8) +#define RT5651_MIC_OVCD_SF_0P75 (0x1 << 8) +#define RT5651_MIC_OVCD_SF_1P0 (0x2 << 8) +#define RT5651_MIC_OVCD_SF_1P5 (0x3 << 8) + +/* 3D Speaker Control (0x63) */ +#define RT5651_3D_SPK_MASK (0x1 << 15) +#define RT5651_3D_SPK_SFT 15 +#define RT5651_3D_SPK_DIS (0x0 << 15) +#define RT5651_3D_SPK_EN (0x1 << 15) +#define RT5651_3D_SPK_M_MASK (0x3 << 13) +#define RT5651_3D_SPK_M_SFT 13 +#define RT5651_3D_SPK_CG_MASK (0x1f << 8) +#define RT5651_3D_SPK_CG_SFT 8 +#define RT5651_3D_SPK_SG_MASK (0x1f) +#define RT5651_3D_SPK_SG_SFT 0 + +/* Wind Noise Detection Control 1 (0x6c) */ +#define RT5651_WND_MASK (0x1 << 15) +#define RT5651_WND_SFT 15 +#define RT5651_WND_DIS (0x0 << 15) +#define RT5651_WND_EN (0x1 << 15) + +/* Wind Noise Detection Control 2 (0x6d) */ +#define RT5651_WND_FC_NW_MASK (0x3f << 10) +#define RT5651_WND_FC_NW_SFT 10 +#define RT5651_WND_FC_WK_MASK (0x3f << 4) +#define RT5651_WND_FC_WK_SFT 4 + +/* Wind Noise Detection Control 3 (0x6e) */ +#define RT5651_HPF_FC_MASK (0x3f << 6) +#define RT5651_HPF_FC_SFT 6 +#define RT5651_WND_FC_ST_MASK (0x3f) +#define RT5651_WND_FC_ST_SFT 0 + +/* Wind Noise Detection Control 4 (0x6f) */ +#define RT5651_WND_TH_LO_MASK (0x3ff) +#define RT5651_WND_TH_LO_SFT 0 + +/* Wind Noise Detection Control 5 (0x70) */ +#define RT5651_WND_TH_HI_MASK (0x3ff) +#define RT5651_WND_TH_HI_SFT 0 + +/* Wind Noise Detection Control 8 (0x73) */ +#define RT5651_WND_WIND_MASK (0x1 << 13) /* Read-Only */ +#define RT5651_WND_WIND_SFT 13 +#define RT5651_WND_STRONG_MASK (0x1 << 12) /* Read-Only */ +#define RT5651_WND_STRONG_SFT 12 +enum { + RT5651_NO_WIND, + RT5651_BREEZE, + RT5651_STORM, +}; + +/* Dipole Speaker Interface (0x75) */ +#define RT5651_DP_ATT_MASK (0x3 << 14) +#define RT5651_DP_ATT_SFT 14 +#define RT5651_DP_SPK_MASK (0x1 << 10) +#define RT5651_DP_SPK_SFT 10 +#define RT5651_DP_SPK_DIS (0x0 << 10) +#define RT5651_DP_SPK_EN (0x1 << 10) + +/* EQ Pre Volume Control (0xb3) */ +#define RT5651_EQ_PRE_VOL_MASK (0xffff) +#define RT5651_EQ_PRE_VOL_SFT 0 + +/* EQ Post Volume Control (0xb4) */ +#define RT5651_EQ_PST_VOL_MASK (0xffff) +#define RT5651_EQ_PST_VOL_SFT 0 + +/* System Clock Source */ +enum { + RT5651_SCLK_S_MCLK, + RT5651_SCLK_S_PLL1, + RT5651_SCLK_S_RCCLK, +}; + +/* PLL1 Source */ +enum { + RT5651_PLL1_S_MCLK, + RT5651_PLL1_S_BCLK1, + RT5651_PLL1_S_BCLK2, +}; + +enum { + RT5651_AIF1, + RT5651_AIF2, + RT5651_AIFS, +}; + +struct rt5651_pll_code { + bool m_bp; /* Indicates bypass m code or not. */ + int m_code; + int n_code; + int k_code; +}; + +struct rt5651_priv { + struct snd_soc_component *component; + struct regmap *regmap; + /* Jack and button detect data */ + struct snd_soc_jack *hp_jack; + struct work_struct jack_detect_work; + struct delayed_work bp_work; + bool ovcd_irq_enabled; + bool pressed; + bool press_reported; + int press_count; + int release_count; + int poll_count; + unsigned int jd_src; + unsigned int ovcd_th; + unsigned int ovcd_sf; + + int irq; + int sysclk; + int sysclk_src; + int lrck[RT5651_AIFS]; + int bclk[RT5651_AIFS]; + int master[RT5651_AIFS]; + + int pll_src; + int pll_in; + int pll_out; + + int dmic_en; + bool hp_mute; +}; + +#endif /* __RT5651_H__ */ diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c new file mode 100644 index 000000000..ab73f84b5 --- /dev/null +++ b/sound/soc/codecs/rt5659.c @@ -0,0 +1,4393 @@ +/* + * rt5659.c -- RT5659/RT5658 ALSA SoC audio codec driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5659.h" + +static const struct reg_default rt5659_reg[] = { + { 0x0000, 0x0000 }, + { 0x0001, 0x4848 }, + { 0x0002, 0x8080 }, + { 0x0003, 0xc8c8 }, + { 0x0004, 0xc80a }, + { 0x0005, 0x0000 }, + { 0x0006, 0x0000 }, + { 0x0007, 0x0103 }, + { 0x0008, 0x0080 }, + { 0x0009, 0x0000 }, + { 0x000a, 0x0000 }, + { 0x000c, 0x0000 }, + { 0x000d, 0x0000 }, + { 0x000f, 0x0808 }, + { 0x0010, 0x3080 }, + { 0x0011, 0x4a00 }, + { 0x0012, 0x4e00 }, + { 0x0015, 0x42c1 }, + { 0x0016, 0x0000 }, + { 0x0018, 0x000b }, + { 0x0019, 0xafaf }, + { 0x001a, 0xafaf }, + { 0x001b, 0x0011 }, + { 0x001c, 0x2f2f }, + { 0x001d, 0x2f2f }, + { 0x001e, 0x2f2f }, + { 0x001f, 0x0000 }, + { 0x0020, 0x0000 }, + { 0x0021, 0x0000 }, + { 0x0022, 0x5757 }, + { 0x0023, 0x0039 }, + { 0x0026, 0xc060 }, + { 0x0027, 0xd8d8 }, + { 0x0029, 0x8080 }, + { 0x002a, 0xaaaa }, + { 0x002b, 0xaaaa }, + { 0x002c, 0x00af }, + { 0x002d, 0x0000 }, + { 0x002f, 0x1002 }, + { 0x0031, 0x5000 }, + { 0x0032, 0x0000 }, + { 0x0033, 0x0000 }, + { 0x0034, 0x0000 }, + { 0x0035, 0x0000 }, + { 0x0036, 0x0000 }, + { 0x003a, 0x0000 }, + { 0x003b, 0x0000 }, + { 0x003c, 0x007f }, + { 0x003d, 0x0000 }, + { 0x003e, 0x007f }, + { 0x0040, 0x0808 }, + { 0x0046, 0x001f }, + { 0x0047, 0x001f }, + { 0x0048, 0x0003 }, + { 0x0049, 0xe061 }, + { 0x004a, 0x0000 }, + { 0x004b, 0x031f }, + { 0x004d, 0x0000 }, + { 0x004e, 0x001f }, + { 0x004f, 0x0000 }, + { 0x0050, 0x001f }, + { 0x0052, 0xf000 }, + { 0x0053, 0x0111 }, + { 0x0054, 0x0064 }, + { 0x0055, 0x0080 }, + { 0x0056, 0xef0e }, + { 0x0057, 0xf0f0 }, + { 0x0058, 0xef0e }, + { 0x0059, 0xf0f0 }, + { 0x005a, 0xef0e }, + { 0x005b, 0xf0f0 }, + { 0x005c, 0xf000 }, + { 0x005d, 0x0000 }, + { 0x005e, 0x1f2c }, + { 0x005f, 0x1f2c }, + { 0x0060, 0x2717 }, + { 0x0061, 0x0000 }, + { 0x0062, 0x0000 }, + { 0x0063, 0x003e }, + { 0x0064, 0x0000 }, + { 0x0065, 0x0000 }, + { 0x0066, 0x0000 }, + { 0x0067, 0x0000 }, + { 0x006a, 0x0000 }, + { 0x006b, 0x0000 }, + { 0x006c, 0x0000 }, + { 0x006e, 0x0000 }, + { 0x006f, 0x0000 }, + { 0x0070, 0x8000 }, + { 0x0071, 0x8000 }, + { 0x0072, 0x8000 }, + { 0x0073, 0x1110 }, + { 0x0074, 0xfe00 }, + { 0x0075, 0x2409 }, + { 0x0076, 0x000a }, + { 0x0077, 0x00f0 }, + { 0x0078, 0x0000 }, + { 0x0079, 0x0000 }, + { 0x007a, 0x0123 }, + { 0x007b, 0x8003 }, + { 0x0080, 0x0000 }, + { 0x0081, 0x0000 }, + { 0x0082, 0x0000 }, + { 0x0083, 0x0000 }, + { 0x0084, 0x0000 }, + { 0x0085, 0x0000 }, + { 0x0086, 0x0008 }, + { 0x0087, 0x0000 }, + { 0x0088, 0x0000 }, + { 0x0089, 0x0000 }, + { 0x008a, 0x0000 }, + { 0x008b, 0x0000 }, + { 0x008c, 0x0003 }, + { 0x008e, 0x0000 }, + { 0x008f, 0x1000 }, + { 0x0090, 0x0646 }, + { 0x0091, 0x0c16 }, + { 0x0092, 0x0073 }, + { 0x0093, 0x0000 }, + { 0x0094, 0x0080 }, + { 0x0097, 0x0000 }, + { 0x0098, 0x0000 }, + { 0x0099, 0x0000 }, + { 0x009a, 0x0000 }, + { 0x009b, 0x0000 }, + { 0x009c, 0x007f }, + { 0x009d, 0x0000 }, + { 0x009e, 0x007f }, + { 0x009f, 0x0000 }, + { 0x00a0, 0x0060 }, + { 0x00a1, 0x90a1 }, + { 0x00ae, 0x2000 }, + { 0x00af, 0x0000 }, + { 0x00b0, 0x2000 }, + { 0x00b1, 0x0000 }, + { 0x00b2, 0x0000 }, + { 0x00b6, 0x0000 }, + { 0x00b7, 0x0000 }, + { 0x00b8, 0x0000 }, + { 0x00b9, 0x0000 }, + { 0x00ba, 0x0000 }, + { 0x00bb, 0x0000 }, + { 0x00be, 0x0000 }, + { 0x00bf, 0x0000 }, + { 0x00c0, 0x0000 }, + { 0x00c1, 0x0000 }, + { 0x00c2, 0x0000 }, + { 0x00c3, 0x0000 }, + { 0x00c4, 0x0003 }, + { 0x00c5, 0x0000 }, + { 0x00cb, 0xa02f }, + { 0x00cc, 0x0000 }, + { 0x00cd, 0x0e02 }, + { 0x00d6, 0x0000 }, + { 0x00d7, 0x2244 }, + { 0x00d9, 0x0809 }, + { 0x00da, 0x0000 }, + { 0x00db, 0x0008 }, + { 0x00dc, 0x00c0 }, + { 0x00dd, 0x6724 }, + { 0x00de, 0x3131 }, + { 0x00df, 0x0008 }, + { 0x00e0, 0x4000 }, + { 0x00e1, 0x3131 }, + { 0x00e4, 0x400c }, + { 0x00e5, 0x8031 }, + { 0x00ea, 0xb320 }, + { 0x00eb, 0x0000 }, + { 0x00ec, 0xb300 }, + { 0x00ed, 0x0000 }, + { 0x00f0, 0x0000 }, + { 0x00f1, 0x0202 }, + { 0x00f2, 0x0ddd }, + { 0x00f3, 0x0ddd }, + { 0x00f4, 0x0ddd }, + { 0x00f6, 0x0000 }, + { 0x00f7, 0x0000 }, + { 0x00f8, 0x0000 }, + { 0x00f9, 0x0000 }, + { 0x00fa, 0x8000 }, + { 0x00fb, 0x0000 }, + { 0x00fc, 0x0000 }, + { 0x00fd, 0x0001 }, + { 0x00fe, 0x10ec }, + { 0x00ff, 0x6311 }, + { 0x0100, 0xaaaa }, + { 0x010a, 0xaaaa }, + { 0x010b, 0x00a0 }, + { 0x010c, 0xaeae }, + { 0x010d, 0xaaaa }, + { 0x010e, 0xaaa8 }, + { 0x010f, 0xa0aa }, + { 0x0110, 0xe02a }, + { 0x0111, 0xa702 }, + { 0x0112, 0xaaaa }, + { 0x0113, 0x2800 }, + { 0x0116, 0x0000 }, + { 0x0117, 0x0f00 }, + { 0x011a, 0x0020 }, + { 0x011b, 0x0011 }, + { 0x011c, 0x0150 }, + { 0x011d, 0x0000 }, + { 0x011e, 0x0000 }, + { 0x011f, 0x0000 }, + { 0x0120, 0x0000 }, + { 0x0121, 0x009b }, + { 0x0122, 0x5014 }, + { 0x0123, 0x0421 }, + { 0x0124, 0x7cea }, + { 0x0125, 0x0420 }, + { 0x0126, 0x5550 }, + { 0x0132, 0x0000 }, + { 0x0133, 0x0000 }, + { 0x0137, 0x5055 }, + { 0x0138, 0x3700 }, + { 0x0139, 0x79a1 }, + { 0x013a, 0x2020 }, + { 0x013b, 0x2020 }, + { 0x013c, 0x2005 }, + { 0x013e, 0x1f00 }, + { 0x013f, 0x0000 }, + { 0x0145, 0x0002 }, + { 0x0146, 0x0000 }, + { 0x0147, 0x0000 }, + { 0x0148, 0x0000 }, + { 0x0150, 0x1813 }, + { 0x0151, 0x0690 }, + { 0x0152, 0x1c17 }, + { 0x0153, 0x6883 }, + { 0x0154, 0xd3ce }, + { 0x0155, 0x352d }, + { 0x0156, 0x00eb }, + { 0x0157, 0x3717 }, + { 0x0158, 0x4c6a }, + { 0x0159, 0xe41b }, + { 0x015a, 0x2a13 }, + { 0x015b, 0xb600 }, + { 0x015c, 0xc730 }, + { 0x015d, 0x35d4 }, + { 0x015e, 0x00bf }, + { 0x0160, 0x0ec0 }, + { 0x0161, 0x0020 }, + { 0x0162, 0x0080 }, + { 0x0163, 0x0800 }, + { 0x0164, 0x0000 }, + { 0x0165, 0x0000 }, + { 0x0166, 0x0000 }, + { 0x0167, 0x001f }, + { 0x0170, 0x4e80 }, + { 0x0171, 0x0020 }, + { 0x0172, 0x0080 }, + { 0x0173, 0x0800 }, + { 0x0174, 0x000c }, + { 0x0175, 0x0000 }, + { 0x0190, 0x3300 }, + { 0x0191, 0x2200 }, + { 0x0192, 0x0000 }, + { 0x01b0, 0x4b38 }, + { 0x01b1, 0x0000 }, + { 0x01b2, 0x0000 }, + { 0x01b3, 0x0000 }, + { 0x01c0, 0x0045 }, + { 0x01c1, 0x0540 }, + { 0x01c2, 0x0000 }, + { 0x01c3, 0x0030 }, + { 0x01c7, 0x0000 }, + { 0x01c8, 0x5757 }, + { 0x01c9, 0x5757 }, + { 0x01ca, 0x5757 }, + { 0x01cb, 0x5757 }, + { 0x01cc, 0x5757 }, + { 0x01cd, 0x5757 }, + { 0x01ce, 0x006f }, + { 0x01da, 0x0000 }, + { 0x01db, 0x0000 }, + { 0x01de, 0x7d00 }, + { 0x01df, 0x10c0 }, + { 0x01e0, 0x06a1 }, + { 0x01e1, 0x0000 }, + { 0x01e2, 0x0000 }, + { 0x01e3, 0x0000 }, + { 0x01e4, 0x0001 }, + { 0x01e6, 0x0000 }, + { 0x01e7, 0x0000 }, + { 0x01e8, 0x0000 }, + { 0x01ea, 0x0000 }, + { 0x01eb, 0x0000 }, + { 0x01ec, 0x0000 }, + { 0x01ed, 0x0000 }, + { 0x01ee, 0x0000 }, + { 0x01ef, 0x0000 }, + { 0x01f0, 0x0000 }, + { 0x01f1, 0x0000 }, + { 0x01f2, 0x0000 }, + { 0x01f6, 0x1e04 }, + { 0x01f7, 0x01a1 }, + { 0x01f8, 0x0000 }, + { 0x01f9, 0x0000 }, + { 0x01fa, 0x0002 }, + { 0x01fb, 0x0000 }, + { 0x01fc, 0x0000 }, + { 0x01fd, 0x0000 }, + { 0x01fe, 0x0000 }, + { 0x0200, 0x066c }, + { 0x0201, 0x7fff }, + { 0x0202, 0x7fff }, + { 0x0203, 0x0000 }, + { 0x0204, 0x0000 }, + { 0x0205, 0x0000 }, + { 0x0206, 0x0000 }, + { 0x0207, 0x0000 }, + { 0x0208, 0x0000 }, + { 0x0256, 0x0000 }, + { 0x0257, 0x0000 }, + { 0x0258, 0x0000 }, + { 0x0259, 0x0000 }, + { 0x025a, 0x0000 }, + { 0x025b, 0x3333 }, + { 0x025c, 0x3333 }, + { 0x025d, 0x3333 }, + { 0x025e, 0x0000 }, + { 0x025f, 0x0000 }, + { 0x0260, 0x0000 }, + { 0x0261, 0x0022 }, + { 0x0262, 0x0300 }, + { 0x0265, 0x1e80 }, + { 0x0266, 0x0131 }, + { 0x0267, 0x0003 }, + { 0x0268, 0x0000 }, + { 0x0269, 0x0000 }, + { 0x026a, 0x0000 }, + { 0x026b, 0x0000 }, + { 0x026c, 0x0000 }, + { 0x026d, 0x0000 }, + { 0x026e, 0x0000 }, + { 0x026f, 0x0000 }, + { 0x0270, 0x0000 }, + { 0x0271, 0x0000 }, + { 0x0272, 0x0000 }, + { 0x0273, 0x0000 }, + { 0x0280, 0x0000 }, + { 0x0281, 0x0000 }, + { 0x0282, 0x0418 }, + { 0x0283, 0x7fff }, + { 0x0284, 0x7000 }, + { 0x0290, 0x01d0 }, + { 0x0291, 0x0100 }, + { 0x02fa, 0x0000 }, + { 0x02fb, 0x0000 }, + { 0x02fc, 0x0000 }, + { 0x0300, 0x001f }, + { 0x0301, 0x032c }, + { 0x0302, 0x5f21 }, + { 0x0303, 0x4000 }, + { 0x0304, 0x4000 }, + { 0x0305, 0x0600 }, + { 0x0306, 0x8000 }, + { 0x0307, 0x0700 }, + { 0x0308, 0x001f }, + { 0x0309, 0x032c }, + { 0x030a, 0x5f21 }, + { 0x030b, 0x4000 }, + { 0x030c, 0x4000 }, + { 0x030d, 0x0600 }, + { 0x030e, 0x8000 }, + { 0x030f, 0x0700 }, + { 0x0310, 0x4560 }, + { 0x0311, 0xa4a8 }, + { 0x0312, 0x7418 }, + { 0x0313, 0x0000 }, + { 0x0314, 0x0006 }, + { 0x0315, 0x00ff }, + { 0x0316, 0xc400 }, + { 0x0317, 0x4560 }, + { 0x0318, 0xa4a8 }, + { 0x0319, 0x7418 }, + { 0x031a, 0x0000 }, + { 0x031b, 0x0006 }, + { 0x031c, 0x00ff }, + { 0x031d, 0xc400 }, + { 0x0320, 0x0f20 }, + { 0x0321, 0x8700 }, + { 0x0322, 0x7dc2 }, + { 0x0323, 0xa178 }, + { 0x0324, 0x5383 }, + { 0x0325, 0x7dc2 }, + { 0x0326, 0xa178 }, + { 0x0327, 0x5383 }, + { 0x0328, 0x003e }, + { 0x0329, 0x02c1 }, + { 0x032a, 0xd37d }, + { 0x0330, 0x00a6 }, + { 0x0331, 0x04c3 }, + { 0x0332, 0x27c8 }, + { 0x0333, 0xbf50 }, + { 0x0334, 0x0045 }, + { 0x0335, 0x2007 }, + { 0x0336, 0x7418 }, + { 0x0337, 0x0501 }, + { 0x0338, 0x0000 }, + { 0x0339, 0x0010 }, + { 0x033a, 0x1010 }, + { 0x0340, 0x0800 }, + { 0x0341, 0x0800 }, + { 0x0342, 0x0800 }, + { 0x0343, 0x0800 }, + { 0x0344, 0x0000 }, + { 0x0345, 0x0000 }, + { 0x0346, 0x0000 }, + { 0x0347, 0x0000 }, + { 0x0348, 0x0000 }, + { 0x0349, 0x0000 }, + { 0x034a, 0x0000 }, + { 0x034b, 0x0000 }, + { 0x034c, 0x0000 }, + { 0x034d, 0x0000 }, + { 0x034e, 0x0000 }, + { 0x034f, 0x0000 }, + { 0x0350, 0x0000 }, + { 0x0351, 0x0000 }, + { 0x0352, 0x0000 }, + { 0x0353, 0x0000 }, + { 0x0354, 0x0000 }, + { 0x0355, 0x0000 }, + { 0x0356, 0x0000 }, + { 0x0357, 0x0000 }, + { 0x0358, 0x0000 }, + { 0x0359, 0x0000 }, + { 0x035a, 0x0000 }, + { 0x035b, 0x0000 }, + { 0x035c, 0x0000 }, + { 0x035d, 0x0000 }, + { 0x035e, 0x2000 }, + { 0x035f, 0x0000 }, + { 0x0360, 0x2000 }, + { 0x0361, 0x2000 }, + { 0x0362, 0x0000 }, + { 0x0363, 0x2000 }, + { 0x0364, 0x0200 }, + { 0x0365, 0x0000 }, + { 0x0366, 0x0000 }, + { 0x0367, 0x0000 }, + { 0x0368, 0x0000 }, + { 0x0369, 0x0000 }, + { 0x036a, 0x0000 }, + { 0x036b, 0x0000 }, + { 0x036c, 0x0000 }, + { 0x036d, 0x0000 }, + { 0x036e, 0x0200 }, + { 0x036f, 0x0000 }, + { 0x0370, 0x0000 }, + { 0x0371, 0x0000 }, + { 0x0372, 0x0000 }, + { 0x0373, 0x0000 }, + { 0x0374, 0x0000 }, + { 0x0375, 0x0000 }, + { 0x0376, 0x0000 }, + { 0x0377, 0x0000 }, + { 0x03d0, 0x0000 }, + { 0x03d1, 0x0000 }, + { 0x03d2, 0x0000 }, + { 0x03d3, 0x0000 }, + { 0x03d4, 0x2000 }, + { 0x03d5, 0x2000 }, + { 0x03d6, 0x0000 }, + { 0x03d7, 0x0000 }, + { 0x03d8, 0x2000 }, + { 0x03d9, 0x2000 }, + { 0x03da, 0x2000 }, + { 0x03db, 0x2000 }, + { 0x03dc, 0x0000 }, + { 0x03dd, 0x0000 }, + { 0x03de, 0x0000 }, + { 0x03df, 0x2000 }, + { 0x03e0, 0x0000 }, + { 0x03e1, 0x0000 }, + { 0x03e2, 0x0000 }, + { 0x03e3, 0x0000 }, + { 0x03e4, 0x0000 }, + { 0x03e5, 0x0000 }, + { 0x03e6, 0x0000 }, + { 0x03e7, 0x0000 }, + { 0x03e8, 0x0000 }, + { 0x03e9, 0x0000 }, + { 0x03ea, 0x0000 }, + { 0x03eb, 0x0000 }, + { 0x03ec, 0x0000 }, + { 0x03ed, 0x0000 }, + { 0x03ee, 0x0000 }, + { 0x03ef, 0x0000 }, + { 0x03f0, 0x0800 }, + { 0x03f1, 0x0800 }, + { 0x03f2, 0x0800 }, + { 0x03f3, 0x0800 }, +}; + +static bool rt5659_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5659_RESET: + case RT5659_EJD_CTRL_2: + case RT5659_SILENCE_CTRL: + case RT5659_DAC2_DIG_VOL: + case RT5659_HP_IMP_GAIN_2: + case RT5659_PDM_OUT_CTRL: + case RT5659_PDM_DATA_CTRL_1: + case RT5659_PDM_DATA_CTRL_4: + case RT5659_HAPTIC_GEN_CTRL_1: + case RT5659_HAPTIC_GEN_CTRL_3: + case RT5659_HAPTIC_LPF_CTRL_3: + case RT5659_CLK_DET: + case RT5659_MICBIAS_1: + case RT5659_ASRC_11: + case RT5659_ADC_EQ_CTRL_1: + case RT5659_DAC_EQ_CTRL_1: + case RT5659_INT_ST_1: + case RT5659_INT_ST_2: + case RT5659_GPIO_STA: + case RT5659_SINE_GEN_CTRL_1: + case RT5659_IL_CMD_1: + case RT5659_4BTN_IL_CMD_1: + case RT5659_PSV_IL_CMD_1: + case RT5659_AJD1_CTRL: + case RT5659_AJD2_AJD3_CTRL: + case RT5659_JD_CTRL_3: + case RT5659_VENDOR_ID: + case RT5659_VENDOR_ID_1: + case RT5659_DEVICE_ID: + case RT5659_MEMORY_TEST: + case RT5659_SOFT_RAMP_DEPOP_DAC_CLK_CTRL: + case RT5659_VOL_TEST: + case RT5659_STO_NG2_CTRL_1: + case RT5659_STO_NG2_CTRL_5: + case RT5659_STO_NG2_CTRL_6: + case RT5659_STO_NG2_CTRL_7: + case RT5659_MONO_NG2_CTRL_1: + case RT5659_MONO_NG2_CTRL_5: + case RT5659_MONO_NG2_CTRL_6: + case RT5659_HP_IMP_SENS_CTRL_1: + case RT5659_HP_IMP_SENS_CTRL_3: + case RT5659_HP_IMP_SENS_CTRL_4: + case RT5659_HP_CALIB_CTRL_1: + case RT5659_HP_CALIB_CTRL_9: + case RT5659_HP_CALIB_STA_1: + case RT5659_HP_CALIB_STA_2: + case RT5659_HP_CALIB_STA_3: + case RT5659_HP_CALIB_STA_4: + case RT5659_HP_CALIB_STA_5: + case RT5659_HP_CALIB_STA_6: + case RT5659_HP_CALIB_STA_7: + case RT5659_HP_CALIB_STA_8: + case RT5659_HP_CALIB_STA_9: + case RT5659_MONO_AMP_CALIB_CTRL_1: + case RT5659_MONO_AMP_CALIB_CTRL_3: + case RT5659_MONO_AMP_CALIB_STA_1: + case RT5659_MONO_AMP_CALIB_STA_2: + case RT5659_MONO_AMP_CALIB_STA_3: + case RT5659_MONO_AMP_CALIB_STA_4: + case RT5659_SPK_PWR_LMT_STA_1: + case RT5659_SPK_PWR_LMT_STA_2: + case RT5659_SPK_PWR_LMT_STA_3: + case RT5659_SPK_PWR_LMT_STA_4: + case RT5659_SPK_PWR_LMT_STA_5: + case RT5659_SPK_PWR_LMT_STA_6: + case RT5659_SPK_DC_CAILB_CTRL_1: + case RT5659_SPK_DC_CAILB_STA_1: + case RT5659_SPK_DC_CAILB_STA_2: + case RT5659_SPK_DC_CAILB_STA_3: + case RT5659_SPK_DC_CAILB_STA_4: + case RT5659_SPK_DC_CAILB_STA_5: + case RT5659_SPK_DC_CAILB_STA_6: + case RT5659_SPK_DC_CAILB_STA_7: + case RT5659_SPK_DC_CAILB_STA_8: + case RT5659_SPK_DC_CAILB_STA_9: + case RT5659_SPK_DC_CAILB_STA_10: + case RT5659_SPK_VDD_STA_1: + case RT5659_SPK_VDD_STA_2: + case RT5659_SPK_DC_DET_CTRL_1: + case RT5659_PURE_DC_DET_CTRL_1: + case RT5659_PURE_DC_DET_CTRL_2: + case RT5659_DRC1_PRIV_1: + case RT5659_DRC1_PRIV_4: + case RT5659_DRC1_PRIV_5: + case RT5659_DRC1_PRIV_6: + case RT5659_DRC1_PRIV_7: + case RT5659_DRC2_PRIV_1: + case RT5659_DRC2_PRIV_4: + case RT5659_DRC2_PRIV_5: + case RT5659_DRC2_PRIV_6: + case RT5659_DRC2_PRIV_7: + case RT5659_ALC_PGA_STA_1: + case RT5659_ALC_PGA_STA_2: + case RT5659_ALC_PGA_STA_3: + return true; + default: + return false; + } +} + +static bool rt5659_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5659_RESET: + case RT5659_SPO_VOL: + case RT5659_HP_VOL: + case RT5659_LOUT: + case RT5659_MONO_OUT: + case RT5659_HPL_GAIN: + case RT5659_HPR_GAIN: + case RT5659_MONO_GAIN: + case RT5659_SPDIF_CTRL_1: + case RT5659_SPDIF_CTRL_2: + case RT5659_CAL_BST_CTRL: + case RT5659_IN1_IN2: + case RT5659_IN3_IN4: + case RT5659_INL1_INR1_VOL: + case RT5659_EJD_CTRL_1: + case RT5659_EJD_CTRL_2: + case RT5659_EJD_CTRL_3: + case RT5659_SILENCE_CTRL: + case RT5659_PSV_CTRL: + case RT5659_SIDETONE_CTRL: + case RT5659_DAC1_DIG_VOL: + case RT5659_DAC2_DIG_VOL: + case RT5659_DAC_CTRL: + case RT5659_STO1_ADC_DIG_VOL: + case RT5659_MONO_ADC_DIG_VOL: + case RT5659_STO2_ADC_DIG_VOL: + case RT5659_STO1_BOOST: + case RT5659_MONO_BOOST: + case RT5659_STO2_BOOST: + case RT5659_HP_IMP_GAIN_1: + case RT5659_HP_IMP_GAIN_2: + case RT5659_STO1_ADC_MIXER: + case RT5659_MONO_ADC_MIXER: + case RT5659_AD_DA_MIXER: + case RT5659_STO_DAC_MIXER: + case RT5659_MONO_DAC_MIXER: + case RT5659_DIG_MIXER: + case RT5659_A_DAC_MUX: + case RT5659_DIG_INF23_DATA: + case RT5659_PDM_OUT_CTRL: + case RT5659_PDM_DATA_CTRL_1: + case RT5659_PDM_DATA_CTRL_2: + case RT5659_PDM_DATA_CTRL_3: + case RT5659_PDM_DATA_CTRL_4: + case RT5659_SPDIF_CTRL: + case RT5659_REC1_GAIN: + case RT5659_REC1_L1_MIXER: + case RT5659_REC1_L2_MIXER: + case RT5659_REC1_R1_MIXER: + case RT5659_REC1_R2_MIXER: + case RT5659_CAL_REC: + case RT5659_REC2_L1_MIXER: + case RT5659_REC2_L2_MIXER: + case RT5659_REC2_R1_MIXER: + case RT5659_REC2_R2_MIXER: + case RT5659_SPK_L_MIXER: + case RT5659_SPK_R_MIXER: + case RT5659_SPO_AMP_GAIN: + case RT5659_ALC_BACK_GAIN: + case RT5659_MONOMIX_GAIN: + case RT5659_MONOMIX_IN_GAIN: + case RT5659_OUT_L_GAIN: + case RT5659_OUT_L_MIXER: + case RT5659_OUT_R_GAIN: + case RT5659_OUT_R_MIXER: + case RT5659_LOUT_MIXER: + case RT5659_HAPTIC_GEN_CTRL_1: + case RT5659_HAPTIC_GEN_CTRL_2: + case RT5659_HAPTIC_GEN_CTRL_3: + case RT5659_HAPTIC_GEN_CTRL_4: + case RT5659_HAPTIC_GEN_CTRL_5: + case RT5659_HAPTIC_GEN_CTRL_6: + case RT5659_HAPTIC_GEN_CTRL_7: + case RT5659_HAPTIC_GEN_CTRL_8: + case RT5659_HAPTIC_GEN_CTRL_9: + case RT5659_HAPTIC_GEN_CTRL_10: + case RT5659_HAPTIC_GEN_CTRL_11: + case RT5659_HAPTIC_LPF_CTRL_1: + case RT5659_HAPTIC_LPF_CTRL_2: + case RT5659_HAPTIC_LPF_CTRL_3: + case RT5659_PWR_DIG_1: + case RT5659_PWR_DIG_2: + case RT5659_PWR_ANLG_1: + case RT5659_PWR_ANLG_2: + case RT5659_PWR_ANLG_3: + case RT5659_PWR_MIXER: + case RT5659_PWR_VOL: + case RT5659_PRIV_INDEX: + case RT5659_CLK_DET: + case RT5659_PRIV_DATA: + case RT5659_PRE_DIV_1: + case RT5659_PRE_DIV_2: + case RT5659_I2S1_SDP: + case RT5659_I2S2_SDP: + case RT5659_I2S3_SDP: + case RT5659_ADDA_CLK_1: + case RT5659_ADDA_CLK_2: + case RT5659_DMIC_CTRL_1: + case RT5659_DMIC_CTRL_2: + case RT5659_TDM_CTRL_1: + case RT5659_TDM_CTRL_2: + case RT5659_TDM_CTRL_3: + case RT5659_TDM_CTRL_4: + case RT5659_TDM_CTRL_5: + case RT5659_GLB_CLK: + case RT5659_PLL_CTRL_1: + case RT5659_PLL_CTRL_2: + case RT5659_ASRC_1: + case RT5659_ASRC_2: + case RT5659_ASRC_3: + case RT5659_ASRC_4: + case RT5659_ASRC_5: + case RT5659_ASRC_6: + case RT5659_ASRC_7: + case RT5659_ASRC_8: + case RT5659_ASRC_9: + case RT5659_ASRC_10: + case RT5659_DEPOP_1: + case RT5659_DEPOP_2: + case RT5659_DEPOP_3: + case RT5659_HP_CHARGE_PUMP_1: + case RT5659_HP_CHARGE_PUMP_2: + case RT5659_MICBIAS_1: + case RT5659_MICBIAS_2: + case RT5659_ASRC_11: + case RT5659_ASRC_12: + case RT5659_ASRC_13: + case RT5659_REC_M1_M2_GAIN_CTRL: + case RT5659_RC_CLK_CTRL: + case RT5659_CLASSD_CTRL_1: + case RT5659_CLASSD_CTRL_2: + case RT5659_ADC_EQ_CTRL_1: + case RT5659_ADC_EQ_CTRL_2: + case RT5659_DAC_EQ_CTRL_1: + case RT5659_DAC_EQ_CTRL_2: + case RT5659_DAC_EQ_CTRL_3: + case RT5659_IRQ_CTRL_1: + case RT5659_IRQ_CTRL_2: + case RT5659_IRQ_CTRL_3: + case RT5659_IRQ_CTRL_4: + case RT5659_IRQ_CTRL_5: + case RT5659_IRQ_CTRL_6: + case RT5659_INT_ST_1: + case RT5659_INT_ST_2: + case RT5659_GPIO_CTRL_1: + case RT5659_GPIO_CTRL_2: + case RT5659_GPIO_CTRL_3: + case RT5659_GPIO_CTRL_4: + case RT5659_GPIO_CTRL_5: + case RT5659_GPIO_STA: + case RT5659_SINE_GEN_CTRL_1: + case RT5659_SINE_GEN_CTRL_2: + case RT5659_SINE_GEN_CTRL_3: + case RT5659_HP_AMP_DET_CTRL_1: + case RT5659_HP_AMP_DET_CTRL_2: + case RT5659_SV_ZCD_1: + case RT5659_SV_ZCD_2: + case RT5659_IL_CMD_1: + case RT5659_IL_CMD_2: + case RT5659_IL_CMD_3: + case RT5659_IL_CMD_4: + case RT5659_4BTN_IL_CMD_1: + case RT5659_4BTN_IL_CMD_2: + case RT5659_4BTN_IL_CMD_3: + case RT5659_PSV_IL_CMD_1: + case RT5659_PSV_IL_CMD_2: + case RT5659_ADC_STO1_HP_CTRL_1: + case RT5659_ADC_STO1_HP_CTRL_2: + case RT5659_ADC_MONO_HP_CTRL_1: + case RT5659_ADC_MONO_HP_CTRL_2: + case RT5659_AJD1_CTRL: + case RT5659_AJD2_AJD3_CTRL: + case RT5659_JD1_THD: + case RT5659_JD2_THD: + case RT5659_JD3_THD: + case RT5659_JD_CTRL_1: + case RT5659_JD_CTRL_2: + case RT5659_JD_CTRL_3: + case RT5659_JD_CTRL_4: + case RT5659_DIG_MISC: + case RT5659_DUMMY_2: + case RT5659_DUMMY_3: + case RT5659_VENDOR_ID: + case RT5659_VENDOR_ID_1: + case RT5659_DEVICE_ID: + case RT5659_DAC_ADC_DIG_VOL: + case RT5659_BIAS_CUR_CTRL_1: + case RT5659_BIAS_CUR_CTRL_2: + case RT5659_BIAS_CUR_CTRL_3: + case RT5659_BIAS_CUR_CTRL_4: + case RT5659_BIAS_CUR_CTRL_5: + case RT5659_BIAS_CUR_CTRL_6: + case RT5659_BIAS_CUR_CTRL_7: + case RT5659_BIAS_CUR_CTRL_8: + case RT5659_BIAS_CUR_CTRL_9: + case RT5659_BIAS_CUR_CTRL_10: + case RT5659_MEMORY_TEST: + case RT5659_VREF_REC_OP_FB_CAP_CTRL: + case RT5659_CLASSD_0: + case RT5659_CLASSD_1: + case RT5659_CLASSD_2: + case RT5659_CLASSD_3: + case RT5659_CLASSD_4: + case RT5659_CLASSD_5: + case RT5659_CLASSD_6: + case RT5659_CLASSD_7: + case RT5659_CLASSD_8: + case RT5659_CLASSD_9: + case RT5659_CLASSD_10: + case RT5659_CHARGE_PUMP_1: + case RT5659_CHARGE_PUMP_2: + case RT5659_DIG_IN_CTRL_1: + case RT5659_DIG_IN_CTRL_2: + case RT5659_PAD_DRIVING_CTRL: + case RT5659_SOFT_RAMP_DEPOP: + case RT5659_PLL: + case RT5659_CHOP_DAC: + case RT5659_CHOP_ADC: + case RT5659_CALIB_ADC_CTRL: + case RT5659_SOFT_RAMP_DEPOP_DAC_CLK_CTRL: + case RT5659_VOL_TEST: + case RT5659_TEST_MODE_CTRL_1: + case RT5659_TEST_MODE_CTRL_2: + case RT5659_TEST_MODE_CTRL_3: + case RT5659_TEST_MODE_CTRL_4: + case RT5659_BASSBACK_CTRL: + case RT5659_MP3_PLUS_CTRL_1: + case RT5659_MP3_PLUS_CTRL_2: + case RT5659_MP3_HPF_A1: + case RT5659_MP3_HPF_A2: + case RT5659_MP3_HPF_H0: + case RT5659_MP3_LPF_H0: + case RT5659_3D_SPK_CTRL: + case RT5659_3D_SPK_COEF_1: + case RT5659_3D_SPK_COEF_2: + case RT5659_3D_SPK_COEF_3: + case RT5659_3D_SPK_COEF_4: + case RT5659_3D_SPK_COEF_5: + case RT5659_3D_SPK_COEF_6: + case RT5659_3D_SPK_COEF_7: + case RT5659_STO_NG2_CTRL_1: + case RT5659_STO_NG2_CTRL_2: + case RT5659_STO_NG2_CTRL_3: + case RT5659_STO_NG2_CTRL_4: + case RT5659_STO_NG2_CTRL_5: + case RT5659_STO_NG2_CTRL_6: + case RT5659_STO_NG2_CTRL_7: + case RT5659_STO_NG2_CTRL_8: + case RT5659_MONO_NG2_CTRL_1: + case RT5659_MONO_NG2_CTRL_2: + case RT5659_MONO_NG2_CTRL_3: + case RT5659_MONO_NG2_CTRL_4: + case RT5659_MONO_NG2_CTRL_5: + case RT5659_MONO_NG2_CTRL_6: + case RT5659_MID_HP_AMP_DET: + case RT5659_LOW_HP_AMP_DET: + case RT5659_LDO_CTRL: + case RT5659_HP_DECROSS_CTRL_1: + case RT5659_HP_DECROSS_CTRL_2: + case RT5659_HP_DECROSS_CTRL_3: + case RT5659_HP_DECROSS_CTRL_4: + case RT5659_HP_IMP_SENS_CTRL_1: + case RT5659_HP_IMP_SENS_CTRL_2: + case RT5659_HP_IMP_SENS_CTRL_3: + case RT5659_HP_IMP_SENS_CTRL_4: + case RT5659_HP_IMP_SENS_MAP_1: + case RT5659_HP_IMP_SENS_MAP_2: + case RT5659_HP_IMP_SENS_MAP_3: + case RT5659_HP_IMP_SENS_MAP_4: + case RT5659_HP_IMP_SENS_MAP_5: + case RT5659_HP_IMP_SENS_MAP_6: + case RT5659_HP_IMP_SENS_MAP_7: + case RT5659_HP_IMP_SENS_MAP_8: + case RT5659_HP_LOGIC_CTRL_1: + case RT5659_HP_LOGIC_CTRL_2: + case RT5659_HP_CALIB_CTRL_1: + case RT5659_HP_CALIB_CTRL_2: + case RT5659_HP_CALIB_CTRL_3: + case RT5659_HP_CALIB_CTRL_4: + case RT5659_HP_CALIB_CTRL_5: + case RT5659_HP_CALIB_CTRL_6: + case RT5659_HP_CALIB_CTRL_7: + case RT5659_HP_CALIB_CTRL_9: + case RT5659_HP_CALIB_CTRL_10: + case RT5659_HP_CALIB_CTRL_11: + case RT5659_HP_CALIB_STA_1: + case RT5659_HP_CALIB_STA_2: + case RT5659_HP_CALIB_STA_3: + case RT5659_HP_CALIB_STA_4: + case RT5659_HP_CALIB_STA_5: + case RT5659_HP_CALIB_STA_6: + case RT5659_HP_CALIB_STA_7: + case RT5659_HP_CALIB_STA_8: + case RT5659_HP_CALIB_STA_9: + case RT5659_MONO_AMP_CALIB_CTRL_1: + case RT5659_MONO_AMP_CALIB_CTRL_2: + case RT5659_MONO_AMP_CALIB_CTRL_3: + case RT5659_MONO_AMP_CALIB_CTRL_4: + case RT5659_MONO_AMP_CALIB_CTRL_5: + case RT5659_MONO_AMP_CALIB_STA_1: + case RT5659_MONO_AMP_CALIB_STA_2: + case RT5659_MONO_AMP_CALIB_STA_3: + case RT5659_MONO_AMP_CALIB_STA_4: + case RT5659_SPK_PWR_LMT_CTRL_1: + case RT5659_SPK_PWR_LMT_CTRL_2: + case RT5659_SPK_PWR_LMT_CTRL_3: + case RT5659_SPK_PWR_LMT_STA_1: + case RT5659_SPK_PWR_LMT_STA_2: + case RT5659_SPK_PWR_LMT_STA_3: + case RT5659_SPK_PWR_LMT_STA_4: + case RT5659_SPK_PWR_LMT_STA_5: + case RT5659_SPK_PWR_LMT_STA_6: + case RT5659_FLEX_SPK_BST_CTRL_1: + case RT5659_FLEX_SPK_BST_CTRL_2: + case RT5659_FLEX_SPK_BST_CTRL_3: + case RT5659_FLEX_SPK_BST_CTRL_4: + case RT5659_SPK_EX_LMT_CTRL_1: + case RT5659_SPK_EX_LMT_CTRL_2: + case RT5659_SPK_EX_LMT_CTRL_3: + case RT5659_SPK_EX_LMT_CTRL_4: + case RT5659_SPK_EX_LMT_CTRL_5: + case RT5659_SPK_EX_LMT_CTRL_6: + case RT5659_SPK_EX_LMT_CTRL_7: + case RT5659_ADJ_HPF_CTRL_1: + case RT5659_ADJ_HPF_CTRL_2: + case RT5659_SPK_DC_CAILB_CTRL_1: + case RT5659_SPK_DC_CAILB_CTRL_2: + case RT5659_SPK_DC_CAILB_CTRL_3: + case RT5659_SPK_DC_CAILB_CTRL_4: + case RT5659_SPK_DC_CAILB_CTRL_5: + case RT5659_SPK_DC_CAILB_STA_1: + case RT5659_SPK_DC_CAILB_STA_2: + case RT5659_SPK_DC_CAILB_STA_3: + case RT5659_SPK_DC_CAILB_STA_4: + case RT5659_SPK_DC_CAILB_STA_5: + case RT5659_SPK_DC_CAILB_STA_6: + case RT5659_SPK_DC_CAILB_STA_7: + case RT5659_SPK_DC_CAILB_STA_8: + case RT5659_SPK_DC_CAILB_STA_9: + case RT5659_SPK_DC_CAILB_STA_10: + case RT5659_SPK_VDD_STA_1: + case RT5659_SPK_VDD_STA_2: + case RT5659_SPK_DC_DET_CTRL_1: + case RT5659_SPK_DC_DET_CTRL_2: + case RT5659_SPK_DC_DET_CTRL_3: + case RT5659_PURE_DC_DET_CTRL_1: + case RT5659_PURE_DC_DET_CTRL_2: + case RT5659_DUMMY_4: + case RT5659_DUMMY_5: + case RT5659_DUMMY_6: + case RT5659_DRC1_CTRL_1: + case RT5659_DRC1_CTRL_2: + case RT5659_DRC1_CTRL_3: + case RT5659_DRC1_CTRL_4: + case RT5659_DRC1_CTRL_5: + case RT5659_DRC1_CTRL_6: + case RT5659_DRC1_HARD_LMT_CTRL_1: + case RT5659_DRC1_HARD_LMT_CTRL_2: + case RT5659_DRC2_CTRL_1: + case RT5659_DRC2_CTRL_2: + case RT5659_DRC2_CTRL_3: + case RT5659_DRC2_CTRL_4: + case RT5659_DRC2_CTRL_5: + case RT5659_DRC2_CTRL_6: + case RT5659_DRC2_HARD_LMT_CTRL_1: + case RT5659_DRC2_HARD_LMT_CTRL_2: + case RT5659_DRC1_PRIV_1: + case RT5659_DRC1_PRIV_2: + case RT5659_DRC1_PRIV_3: + case RT5659_DRC1_PRIV_4: + case RT5659_DRC1_PRIV_5: + case RT5659_DRC1_PRIV_6: + case RT5659_DRC1_PRIV_7: + case RT5659_DRC2_PRIV_1: + case RT5659_DRC2_PRIV_2: + case RT5659_DRC2_PRIV_3: + case RT5659_DRC2_PRIV_4: + case RT5659_DRC2_PRIV_5: + case RT5659_DRC2_PRIV_6: + case RT5659_DRC2_PRIV_7: + case RT5659_MULTI_DRC_CTRL: + case RT5659_CROSS_OVER_1: + case RT5659_CROSS_OVER_2: + case RT5659_CROSS_OVER_3: + case RT5659_CROSS_OVER_4: + case RT5659_CROSS_OVER_5: + case RT5659_CROSS_OVER_6: + case RT5659_CROSS_OVER_7: + case RT5659_CROSS_OVER_8: + case RT5659_CROSS_OVER_9: + case RT5659_CROSS_OVER_10: + case RT5659_ALC_PGA_CTRL_1: + case RT5659_ALC_PGA_CTRL_2: + case RT5659_ALC_PGA_CTRL_3: + case RT5659_ALC_PGA_CTRL_4: + case RT5659_ALC_PGA_CTRL_5: + case RT5659_ALC_PGA_CTRL_6: + case RT5659_ALC_PGA_CTRL_7: + case RT5659_ALC_PGA_CTRL_8: + case RT5659_ALC_PGA_STA_1: + case RT5659_ALC_PGA_STA_2: + case RT5659_ALC_PGA_STA_3: + case RT5659_DAC_L_EQ_PRE_VOL: + case RT5659_DAC_R_EQ_PRE_VOL: + case RT5659_DAC_L_EQ_POST_VOL: + case RT5659_DAC_R_EQ_POST_VOL: + case RT5659_DAC_L_EQ_LPF1_A1: + case RT5659_DAC_L_EQ_LPF1_H0: + case RT5659_DAC_R_EQ_LPF1_A1: + case RT5659_DAC_R_EQ_LPF1_H0: + case RT5659_DAC_L_EQ_BPF2_A1: + case RT5659_DAC_L_EQ_BPF2_A2: + case RT5659_DAC_L_EQ_BPF2_H0: + case RT5659_DAC_R_EQ_BPF2_A1: + case RT5659_DAC_R_EQ_BPF2_A2: + case RT5659_DAC_R_EQ_BPF2_H0: + case RT5659_DAC_L_EQ_BPF3_A1: + case RT5659_DAC_L_EQ_BPF3_A2: + case RT5659_DAC_L_EQ_BPF3_H0: + case RT5659_DAC_R_EQ_BPF3_A1: + case RT5659_DAC_R_EQ_BPF3_A2: + case RT5659_DAC_R_EQ_BPF3_H0: + case RT5659_DAC_L_EQ_BPF4_A1: + case RT5659_DAC_L_EQ_BPF4_A2: + case RT5659_DAC_L_EQ_BPF4_H0: + case RT5659_DAC_R_EQ_BPF4_A1: + case RT5659_DAC_R_EQ_BPF4_A2: + case RT5659_DAC_R_EQ_BPF4_H0: + case RT5659_DAC_L_EQ_HPF1_A1: + case RT5659_DAC_L_EQ_HPF1_H0: + case RT5659_DAC_R_EQ_HPF1_A1: + case RT5659_DAC_R_EQ_HPF1_H0: + case RT5659_DAC_L_EQ_HPF2_A1: + case RT5659_DAC_L_EQ_HPF2_A2: + case RT5659_DAC_L_EQ_HPF2_H0: + case RT5659_DAC_R_EQ_HPF2_A1: + case RT5659_DAC_R_EQ_HPF2_A2: + case RT5659_DAC_R_EQ_HPF2_H0: + case RT5659_DAC_L_BI_EQ_BPF1_H0_1: + case RT5659_DAC_L_BI_EQ_BPF1_H0_2: + case RT5659_DAC_L_BI_EQ_BPF1_B1_1: + case RT5659_DAC_L_BI_EQ_BPF1_B1_2: + case RT5659_DAC_L_BI_EQ_BPF1_B2_1: + case RT5659_DAC_L_BI_EQ_BPF1_B2_2: + case RT5659_DAC_L_BI_EQ_BPF1_A1_1: + case RT5659_DAC_L_BI_EQ_BPF1_A1_2: + case RT5659_DAC_L_BI_EQ_BPF1_A2_1: + case RT5659_DAC_L_BI_EQ_BPF1_A2_2: + case RT5659_DAC_R_BI_EQ_BPF1_H0_1: + case RT5659_DAC_R_BI_EQ_BPF1_H0_2: + case RT5659_DAC_R_BI_EQ_BPF1_B1_1: + case RT5659_DAC_R_BI_EQ_BPF1_B1_2: + case RT5659_DAC_R_BI_EQ_BPF1_B2_1: + case RT5659_DAC_R_BI_EQ_BPF1_B2_2: + case RT5659_DAC_R_BI_EQ_BPF1_A1_1: + case RT5659_DAC_R_BI_EQ_BPF1_A1_2: + case RT5659_DAC_R_BI_EQ_BPF1_A2_1: + case RT5659_DAC_R_BI_EQ_BPF1_A2_2: + case RT5659_ADC_L_EQ_LPF1_A1: + case RT5659_ADC_R_EQ_LPF1_A1: + case RT5659_ADC_L_EQ_LPF1_H0: + case RT5659_ADC_R_EQ_LPF1_H0: + case RT5659_ADC_L_EQ_BPF1_A1: + case RT5659_ADC_R_EQ_BPF1_A1: + case RT5659_ADC_L_EQ_BPF1_A2: + case RT5659_ADC_R_EQ_BPF1_A2: + case RT5659_ADC_L_EQ_BPF1_H0: + case RT5659_ADC_R_EQ_BPF1_H0: + case RT5659_ADC_L_EQ_BPF2_A1: + case RT5659_ADC_R_EQ_BPF2_A1: + case RT5659_ADC_L_EQ_BPF2_A2: + case RT5659_ADC_R_EQ_BPF2_A2: + case RT5659_ADC_L_EQ_BPF2_H0: + case RT5659_ADC_R_EQ_BPF2_H0: + case RT5659_ADC_L_EQ_BPF3_A1: + case RT5659_ADC_R_EQ_BPF3_A1: + case RT5659_ADC_L_EQ_BPF3_A2: + case RT5659_ADC_R_EQ_BPF3_A2: + case RT5659_ADC_L_EQ_BPF3_H0: + case RT5659_ADC_R_EQ_BPF3_H0: + case RT5659_ADC_L_EQ_BPF4_A1: + case RT5659_ADC_R_EQ_BPF4_A1: + case RT5659_ADC_L_EQ_BPF4_A2: + case RT5659_ADC_R_EQ_BPF4_A2: + case RT5659_ADC_L_EQ_BPF4_H0: + case RT5659_ADC_R_EQ_BPF4_H0: + case RT5659_ADC_L_EQ_HPF1_A1: + case RT5659_ADC_R_EQ_HPF1_A1: + case RT5659_ADC_L_EQ_HPF1_H0: + case RT5659_ADC_R_EQ_HPF1_H0: + case RT5659_ADC_L_EQ_PRE_VOL: + case RT5659_ADC_R_EQ_PRE_VOL: + case RT5659_ADC_L_EQ_POST_VOL: + case RT5659_ADC_R_EQ_POST_VOL: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -2325, 75, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); +static const DECLARE_TLV_DB_SCALE(in_bst_tlv, -1200, 75, 0); + +/* Interface data select */ +static const char * const rt5659_data_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5659_if1_01_adc_enum, + RT5659_TDM_CTRL_2, RT5659_DS_ADC_SLOT01_SFT, rt5659_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5659_if1_23_adc_enum, + RT5659_TDM_CTRL_2, RT5659_DS_ADC_SLOT23_SFT, rt5659_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5659_if1_45_adc_enum, + RT5659_TDM_CTRL_2, RT5659_DS_ADC_SLOT45_SFT, rt5659_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5659_if1_67_adc_enum, + RT5659_TDM_CTRL_2, RT5659_DS_ADC_SLOT67_SFT, rt5659_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5659_if2_dac_enum, + RT5659_DIG_INF23_DATA, RT5659_IF2_DAC_SEL_SFT, rt5659_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5659_if2_adc_enum, + RT5659_DIG_INF23_DATA, RT5659_IF2_ADC_SEL_SFT, rt5659_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5659_if3_dac_enum, + RT5659_DIG_INF23_DATA, RT5659_IF3_DAC_SEL_SFT, rt5659_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5659_if3_adc_enum, + RT5659_DIG_INF23_DATA, RT5659_IF3_ADC_SEL_SFT, rt5659_data_select); + +static const struct snd_kcontrol_new rt5659_if1_01_adc_swap_mux = + SOC_DAPM_ENUM("IF1 01 ADC Swap Source", rt5659_if1_01_adc_enum); + +static const struct snd_kcontrol_new rt5659_if1_23_adc_swap_mux = + SOC_DAPM_ENUM("IF1 23 ADC1 Swap Source", rt5659_if1_23_adc_enum); + +static const struct snd_kcontrol_new rt5659_if1_45_adc_swap_mux = + SOC_DAPM_ENUM("IF1 45 ADC1 Swap Source", rt5659_if1_45_adc_enum); + +static const struct snd_kcontrol_new rt5659_if1_67_adc_swap_mux = + SOC_DAPM_ENUM("IF1 67 ADC1 Swap Source", rt5659_if1_67_adc_enum); + +static const struct snd_kcontrol_new rt5659_if2_dac_swap_mux = + SOC_DAPM_ENUM("IF2 DAC Swap Source", rt5659_if2_dac_enum); + +static const struct snd_kcontrol_new rt5659_if2_adc_swap_mux = + SOC_DAPM_ENUM("IF2 ADC Swap Source", rt5659_if2_adc_enum); + +static const struct snd_kcontrol_new rt5659_if3_dac_swap_mux = + SOC_DAPM_ENUM("IF3 DAC Swap Source", rt5659_if3_dac_enum); + +static const struct snd_kcontrol_new rt5659_if3_adc_swap_mux = + SOC_DAPM_ENUM("IF3 ADC Swap Source", rt5659_if3_adc_enum); + +static const char * const rt5659_asrc_clk_src[] = { + "clk_sysy_div_out", "clk_i2s1_track", "clk_i2s2_track", + "clk_i2s3_track", "clk_sys2", "clk_sys3" +}; + +static unsigned int rt5659_asrc_clk_map_values[] = { + 0, 1, 2, 3, 5, 6, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt5659_da_sto_asrc_enum, RT5659_ASRC_2, RT5659_DA_STO_T_SFT, 0x7, + rt5659_asrc_clk_src, rt5659_asrc_clk_map_values); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt5659_da_monol_asrc_enum, RT5659_ASRC_2, RT5659_DA_MONO_L_T_SFT, 0x7, + rt5659_asrc_clk_src, rt5659_asrc_clk_map_values); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt5659_da_monor_asrc_enum, RT5659_ASRC_2, RT5659_DA_MONO_R_T_SFT, 0x7, + rt5659_asrc_clk_src, rt5659_asrc_clk_map_values); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt5659_ad_sto1_asrc_enum, RT5659_ASRC_2, RT5659_AD_STO1_T_SFT, 0x7, + rt5659_asrc_clk_src, rt5659_asrc_clk_map_values); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt5659_ad_sto2_asrc_enum, RT5659_ASRC_3, RT5659_AD_STO2_T_SFT, 0x7, + rt5659_asrc_clk_src, rt5659_asrc_clk_map_values); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt5659_ad_monol_asrc_enum, RT5659_ASRC_3, RT5659_AD_MONO_L_T_SFT, 0x7, + rt5659_asrc_clk_src, rt5659_asrc_clk_map_values); + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt5659_ad_monor_asrc_enum, RT5659_ASRC_3, RT5659_AD_MONO_R_T_SFT, 0x7, + rt5659_asrc_clk_src, rt5659_asrc_clk_map_values); + +static int rt5659_hp_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int ret = snd_soc_put_volsw(kcontrol, ucontrol); + + if (snd_soc_component_read32(component, RT5659_STO_NG2_CTRL_1) & RT5659_NG2_EN) { + snd_soc_component_update_bits(component, RT5659_STO_NG2_CTRL_1, + RT5659_NG2_EN_MASK, RT5659_NG2_DIS); + snd_soc_component_update_bits(component, RT5659_STO_NG2_CTRL_1, + RT5659_NG2_EN_MASK, RT5659_NG2_EN); + } + + return ret; +} + +static void rt5659_enable_push_button_irq(struct snd_soc_component *component, + bool enable) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (enable) { + snd_soc_component_write(component, RT5659_4BTN_IL_CMD_1, 0x000b); + + /* MICBIAS1 and Mic Det Power for button detect*/ + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_force_enable_pin(dapm, + "Mic Det Power"); + snd_soc_dapm_sync(dapm); + + snd_soc_component_update_bits(component, RT5659_PWR_ANLG_2, + RT5659_PWR_MB1, RT5659_PWR_MB1); + snd_soc_component_update_bits(component, RT5659_PWR_VOL, + RT5659_PWR_MIC_DET, RT5659_PWR_MIC_DET); + + snd_soc_component_update_bits(component, RT5659_IRQ_CTRL_2, + RT5659_IL_IRQ_MASK, RT5659_IL_IRQ_EN); + snd_soc_component_update_bits(component, RT5659_4BTN_IL_CMD_2, + RT5659_4BTN_IL_MASK, RT5659_4BTN_IL_EN); + } else { + snd_soc_component_update_bits(component, RT5659_4BTN_IL_CMD_2, + RT5659_4BTN_IL_MASK, RT5659_4BTN_IL_DIS); + snd_soc_component_update_bits(component, RT5659_IRQ_CTRL_2, + RT5659_IL_IRQ_MASK, RT5659_IL_IRQ_DIS); + /* MICBIAS1 and Mic Det Power for button detect*/ + snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + } +} + +/** + * rt5659_headset_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ + +static int rt5659_headset_detect(struct snd_soc_component *component, int jack_insert) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int val, i = 0, sleep_time[5] = {300, 150, 100, 50, 30}; + int reg_63; + + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + + if (jack_insert) { + snd_soc_dapm_force_enable_pin(dapm, + "Mic Det Power"); + snd_soc_dapm_sync(dapm); + reg_63 = snd_soc_component_read32(component, RT5659_PWR_ANLG_1); + + snd_soc_component_update_bits(component, RT5659_PWR_ANLG_1, + RT5659_PWR_VREF2 | RT5659_PWR_MB, + RT5659_PWR_VREF2 | RT5659_PWR_MB); + msleep(20); + snd_soc_component_update_bits(component, RT5659_PWR_ANLG_1, + RT5659_PWR_FV2, RT5659_PWR_FV2); + + snd_soc_component_write(component, RT5659_EJD_CTRL_2, 0x4160); + snd_soc_component_update_bits(component, RT5659_EJD_CTRL_1, + 0x20, 0x0); + msleep(20); + snd_soc_component_update_bits(component, RT5659_EJD_CTRL_1, + 0x20, 0x20); + + while (i < 5) { + msleep(sleep_time[i]); + val = snd_soc_component_read32(component, RT5659_EJD_CTRL_2) & 0x0003; + i++; + if (val == 0x1 || val == 0x2 || val == 0x3) + break; + } + + switch (val) { + case 1: + rt5659->jack_type = SND_JACK_HEADSET; + rt5659_enable_push_button_irq(component, true); + break; + default: + snd_soc_component_write(component, RT5659_PWR_ANLG_1, reg_63); + rt5659->jack_type = SND_JACK_HEADPHONE; + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + break; + } + } else { + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + if (rt5659->jack_type == SND_JACK_HEADSET) + rt5659_enable_push_button_irq(component, false); + rt5659->jack_type = 0; + } + + dev_dbg(component->dev, "jack_type = %d\n", rt5659->jack_type); + return rt5659->jack_type; +} + +static int rt5659_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read32(component, RT5659_4BTN_IL_CMD_1); + btn_type = val & 0xfff0; + snd_soc_component_write(component, RT5659_4BTN_IL_CMD_1, val); + + return btn_type; +} + +static irqreturn_t rt5659_irq(int irq, void *data) +{ + struct rt5659_priv *rt5659 = data; + + queue_delayed_work(system_power_efficient_wq, + &rt5659->jack_detect_work, msecs_to_jiffies(250)); + + return IRQ_HANDLED; +} + +int rt5659_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack) +{ + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + + rt5659->hs_jack = hs_jack; + + rt5659_irq(0, rt5659); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5659_set_jack_detect); + +static void rt5659_jack_detect_work(struct work_struct *work) +{ + struct rt5659_priv *rt5659 = + container_of(work, struct rt5659_priv, jack_detect_work.work); + int val, btn_type, report = 0; + + if (!rt5659->component) + return; + + val = snd_soc_component_read32(rt5659->component, RT5659_INT_ST_1) & 0x0080; + if (!val) { + /* jack in */ + if (rt5659->jack_type == 0) { + /* jack was out, report jack type */ + report = rt5659_headset_detect(rt5659->component, 1); + } else { + /* jack is already in, report button event */ + report = SND_JACK_HEADSET; + btn_type = rt5659_button_detect(rt5659->component); + /** + * rt5659 can report three kinds of button behavior, + * one click, double click and hold. However, + * currently we will report button pressed/released + * event. So all the three button behaviors are + * treated as button pressed. + */ + switch (btn_type) { + case 0x8000: + case 0x4000: + case 0x2000: + report |= SND_JACK_BTN_0; + break; + case 0x1000: + case 0x0800: + case 0x0400: + report |= SND_JACK_BTN_1; + break; + case 0x0200: + case 0x0100: + case 0x0080: + report |= SND_JACK_BTN_2; + break; + case 0x0040: + case 0x0020: + case 0x0010: + report |= SND_JACK_BTN_3; + break; + case 0x0000: /* unpressed */ + break; + default: + btn_type = 0; + dev_err(rt5659->component->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + + /* button release or spurious interrput*/ + if (btn_type == 0) + report = rt5659->jack_type; + } + } else { + /* jack out */ + report = rt5659_headset_detect(rt5659->component, 0); + } + + snd_soc_jack_report(rt5659->hs_jack, report, SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); +} + +static void rt5659_jack_detect_intel_hd_header(struct work_struct *work) +{ + struct rt5659_priv *rt5659 = + container_of(work, struct rt5659_priv, jack_detect_work.work); + unsigned int value; + bool hp_flag, mic_flag; + + if (!rt5659->hs_jack) + return; + + /* headphone jack */ + regmap_read(rt5659->regmap, RT5659_GPIO_STA, &value); + hp_flag = (!(value & 0x8)) ? true : false; + + if (hp_flag != rt5659->hda_hp_plugged) { + rt5659->hda_hp_plugged = hp_flag; + + if (hp_flag) { + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_1, + 0x10, 0x0); + rt5659->jack_type |= SND_JACK_HEADPHONE; + } else { + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_1, + 0x10, 0x10); + rt5659->jack_type = rt5659->jack_type & + (~SND_JACK_HEADPHONE); + } + + snd_soc_jack_report(rt5659->hs_jack, rt5659->jack_type, + SND_JACK_HEADPHONE); + } + + /* mic jack */ + regmap_read(rt5659->regmap, RT5659_4BTN_IL_CMD_1, &value); + regmap_write(rt5659->regmap, RT5659_4BTN_IL_CMD_1, value); + mic_flag = (value & 0x2000) ? true : false; + + if (mic_flag != rt5659->hda_mic_plugged) { + rt5659->hda_mic_plugged = mic_flag; + if (mic_flag) { + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_2, + 0x2, 0x2); + rt5659->jack_type |= SND_JACK_MICROPHONE; + } else { + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_2, + 0x2, 0x0); + rt5659->jack_type = rt5659->jack_type + & (~SND_JACK_MICROPHONE); + } + + snd_soc_jack_report(rt5659->hs_jack, rt5659->jack_type, + SND_JACK_MICROPHONE); + } +} + +static const struct snd_kcontrol_new rt5659_snd_controls[] = { + /* Speaker Output Volume */ + SOC_DOUBLE_TLV("Speaker Playback Volume", RT5659_SPO_VOL, + RT5659_L_VOL_SFT, RT5659_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* Headphone Output Volume */ + SOC_DOUBLE_R_EXT_TLV("Headphone Playback Volume", RT5659_HPL_GAIN, + RT5659_HPR_GAIN, RT5659_G_HP_SFT, 31, 1, snd_soc_get_volsw, + rt5659_hp_vol_put, hp_vol_tlv), + + /* Mono Output Volume */ + SOC_SINGLE_TLV("Mono Playback Volume", RT5659_MONO_OUT, + RT5659_L_VOL_SFT, 39, 1, out_vol_tlv), + + /* Output Volume */ + SOC_DOUBLE_TLV("OUT Playback Volume", RT5659_LOUT, + RT5659_L_VOL_SFT, RT5659_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5659_DAC1_DIG_VOL, + RT5659_L_VOL_SFT, RT5659_R_VOL_SFT, 175, 0, dac_vol_tlv), + SOC_DOUBLE("DAC1 Playback Switch", RT5659_AD_DA_MIXER, + RT5659_M_DAC1_L_SFT, RT5659_M_DAC1_R_SFT, 1, 1), + + SOC_DOUBLE_TLV("DAC2 Playback Volume", RT5659_DAC2_DIG_VOL, + RT5659_L_VOL_SFT, RT5659_R_VOL_SFT, 175, 0, dac_vol_tlv), + SOC_DOUBLE("DAC2 Playback Switch", RT5659_DAC_CTRL, + RT5659_M_DAC2_L_VOL_SFT, RT5659_M_DAC2_R_VOL_SFT, 1, 1), + + /* IN1/IN2/IN3/IN4 Volume */ + SOC_SINGLE_TLV("IN1 Boost Volume", RT5659_IN1_IN2, + RT5659_BST1_SFT, 69, 0, in_bst_tlv), + SOC_SINGLE_TLV("IN2 Boost Volume", RT5659_IN1_IN2, + RT5659_BST2_SFT, 69, 0, in_bst_tlv), + SOC_SINGLE_TLV("IN3 Boost Volume", RT5659_IN3_IN4, + RT5659_BST3_SFT, 69, 0, in_bst_tlv), + SOC_SINGLE_TLV("IN4 Boost Volume", RT5659_IN3_IN4, + RT5659_BST4_SFT, 69, 0, in_bst_tlv), + + /* INL/INR Volume Control */ + SOC_DOUBLE_TLV("IN Capture Volume", RT5659_INL1_INR1_VOL, + RT5659_INL_VOL_SFT, RT5659_INR_VOL_SFT, 31, 1, in_vol_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("STO1 ADC Capture Switch", RT5659_STO1_ADC_DIG_VOL, + RT5659_L_MUTE_SFT, RT5659_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("STO1 ADC Capture Volume", RT5659_STO1_ADC_DIG_VOL, + RT5659_L_VOL_SFT, RT5659_R_VOL_SFT, 127, 0, adc_vol_tlv), + SOC_DOUBLE("Mono ADC Capture Switch", RT5659_MONO_ADC_DIG_VOL, + RT5659_L_MUTE_SFT, RT5659_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5659_MONO_ADC_DIG_VOL, + RT5659_L_VOL_SFT, RT5659_R_VOL_SFT, 127, 0, adc_vol_tlv), + SOC_DOUBLE("STO2 ADC Capture Switch", RT5659_STO2_ADC_DIG_VOL, + RT5659_L_MUTE_SFT, RT5659_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("STO2 ADC Capture Volume", RT5659_STO2_ADC_DIG_VOL, + RT5659_L_VOL_SFT, RT5659_R_VOL_SFT, 127, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Gain Volume", RT5659_STO1_BOOST, + RT5659_STO1_ADC_L_BST_SFT, RT5659_STO1_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + SOC_DOUBLE_TLV("Mono ADC Boost Gain Volume", RT5659_MONO_BOOST, + RT5659_MONO_ADC_L_BST_SFT, RT5659_MONO_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + SOC_DOUBLE_TLV("STO2 ADC Boost Gain Volume", RT5659_STO2_BOOST, + RT5659_STO2_ADC_L_BST_SFT, RT5659_STO2_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + SOC_SINGLE("DAC IF1 DAC1 L Data Switch", RT5659_TDM_CTRL_4, 12, 7, 0), + SOC_SINGLE("DAC IF1 DAC1 R Data Switch", RT5659_TDM_CTRL_4, 8, 7, 0), + SOC_SINGLE("DAC IF1 DAC2 L Data Switch", RT5659_TDM_CTRL_4, 4, 7, 0), + SOC_SINGLE("DAC IF1 DAC2 R Data Switch", RT5659_TDM_CTRL_4, 0, 7, 0), +}; + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + * Choose dmic clock between 1MHz and 3MHz. + * It is better for clock to approximate 3MHz. + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + int pd, idx = -EINVAL; + + pd = rl6231_get_pre_div(rt5659->regmap, + RT5659_ADDA_CLK_1, RT5659_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rt5659->sysclk / pd); + + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else { + snd_soc_component_update_bits(component, RT5659_DMIC_CTRL_1, + RT5659_DMIC_CLK_MASK, idx << RT5659_DMIC_CLK_SFT); + } + return idx; +} + +static int set_adc1_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5659_CHOP_ADC, + RT5659_CKXEN_ADC1_MASK | RT5659_CKGEN_ADC1_MASK, + RT5659_CKXEN_ADC1_MASK | RT5659_CKGEN_ADC1_MASK); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5659_CHOP_ADC, + RT5659_CKXEN_ADC1_MASK | RT5659_CKGEN_ADC1_MASK, 0); + break; + + default: + return 0; + } + + return 0; + +} + +static int set_adc2_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5659_CHOP_ADC, + RT5659_CKXEN_ADC2_MASK | RT5659_CKGEN_ADC2_MASK, + RT5659_CKXEN_ADC2_MASK | RT5659_CKGEN_ADC2_MASK); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5659_CHOP_ADC, + RT5659_CKXEN_ADC2_MASK | RT5659_CKGEN_ADC2_MASK, 0); + break; + + default: + return 0; + } + + return 0; + +} + +static int rt5659_charge_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Depop */ + snd_soc_component_write(component, RT5659_DEPOP_1, 0x0009); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_write(component, RT5659_HP_CHARGE_PUMP_1, 0x0c16); + break; + default: + return 0; + } + + return 0; +} + +static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int val; + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + val = snd_soc_component_read32(component, RT5659_GLB_CLK); + val &= RT5659_SCLK_SRC_MASK; + if (val == RT5659_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int reg, shift, val; + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (w->shift) { + case RT5659_ADC_MONO_R_ASRC_SFT: + reg = RT5659_ASRC_3; + shift = RT5659_AD_MONO_R_T_SFT; + break; + case RT5659_ADC_MONO_L_ASRC_SFT: + reg = RT5659_ASRC_3; + shift = RT5659_AD_MONO_L_T_SFT; + break; + case RT5659_ADC_STO1_ASRC_SFT: + reg = RT5659_ASRC_2; + shift = RT5659_AD_STO1_T_SFT; + break; + case RT5659_DAC_MONO_R_ASRC_SFT: + reg = RT5659_ASRC_2; + shift = RT5659_DA_MONO_R_T_SFT; + break; + case RT5659_DAC_MONO_L_ASRC_SFT: + reg = RT5659_ASRC_2; + shift = RT5659_DA_MONO_L_T_SFT; + break; + case RT5659_DAC_STO_ASRC_SFT: + reg = RT5659_ASRC_2; + shift = RT5659_DA_STO_T_SFT; + break; + default: + return 0; + } + + val = (snd_soc_component_read32(component, reg) >> shift) & 0xf; + switch (val) { + case 1: + case 2: + case 3: + /* I2S_Pre_Div1 should be 1 in asrc mode */ + snd_soc_component_update_bits(component, RT5659_ADDA_CLK_1, + RT5659_I2S_PD1_MASK, RT5659_I2S_PD1_2); + return 1; + default: + return 0; + } + +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5659_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5659_STO1_ADC_MIXER, + RT5659_M_STO1_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5659_STO1_ADC_MIXER, + RT5659_M_STO1_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5659_STO1_ADC_MIXER, + RT5659_M_STO1_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5659_STO1_ADC_MIXER, + RT5659_M_STO1_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_mono_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5659_MONO_ADC_MIXER, + RT5659_M_MONO_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5659_MONO_ADC_MIXER, + RT5659_M_MONO_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_mono_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5659_MONO_ADC_MIXER, + RT5659_M_MONO_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5659_MONO_ADC_MIXER, + RT5659_M_MONO_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5659_AD_DA_MIXER, + RT5659_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5659_AD_DA_MIXER, + RT5659_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5659_AD_DA_MIXER, + RT5659_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5659_AD_DA_MIXER, + RT5659_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_L1_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_R1_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_L2_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_R2_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_L1_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_R1_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_L2_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_STO_DAC_MIXER, + RT5659_M_DAC_R2_STO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_mono_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_L1_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_R1_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_L2_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_R2_MONO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_mono_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_L1_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_R1_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_L2_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_MONO_DAC_MIXER, + RT5659_M_DAC_R2_MONO_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5659_rec1_l_mix[] = { + SOC_DAPM_SINGLE("SPKVOLL Switch", RT5659_REC1_L2_MIXER, + RT5659_M_SPKVOLL_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5659_REC1_L2_MIXER, + RT5659_M_INL_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5659_REC1_L2_MIXER, + RT5659_M_BST4_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_REC1_L2_MIXER, + RT5659_M_BST3_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5659_REC1_L2_MIXER, + RT5659_M_BST2_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5659_REC1_L2_MIXER, + RT5659_M_BST1_RM1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_rec1_r_mix[] = { + SOC_DAPM_SINGLE("HPOVOLR Switch", RT5659_REC1_L2_MIXER, + RT5659_M_HPOVOLR_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5659_REC1_R2_MIXER, + RT5659_M_INR_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5659_REC1_R2_MIXER, + RT5659_M_BST4_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_REC1_R2_MIXER, + RT5659_M_BST3_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5659_REC1_R2_MIXER, + RT5659_M_BST2_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5659_REC1_R2_MIXER, + RT5659_M_BST1_RM1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_rec2_l_mix[] = { + SOC_DAPM_SINGLE("SPKVOLL Switch", RT5659_REC2_L2_MIXER, + RT5659_M_SPKVOL_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOLL Switch", RT5659_REC2_L2_MIXER, + RT5659_M_OUTVOLL_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5659_REC2_L2_MIXER, + RT5659_M_BST4_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_REC2_L2_MIXER, + RT5659_M_BST3_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5659_REC2_L2_MIXER, + RT5659_M_BST2_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5659_REC2_L2_MIXER, + RT5659_M_BST1_RM2_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_rec2_r_mix[] = { + SOC_DAPM_SINGLE("MONOVOL Switch", RT5659_REC2_R2_MIXER, + RT5659_M_MONOVOL_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOLR Switch", RT5659_REC2_R2_MIXER, + RT5659_M_OUTVOLR_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5659_REC2_R2_MIXER, + RT5659_M_BST4_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_REC2_R2_MIXER, + RT5659_M_BST3_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5659_REC2_R2_MIXER, + RT5659_M_BST2_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5659_REC2_R2_MIXER, + RT5659_M_BST1_RM2_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_spk_l_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_SPK_L_MIXER, + RT5659_M_DAC_L2_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5659_SPK_L_MIXER, + RT5659_M_BST1_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5659_SPK_L_MIXER, + RT5659_M_IN_L_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5659_SPK_L_MIXER, + RT5659_M_IN_R_SM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_SPK_L_MIXER, + RT5659_M_BST3_SM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_spk_r_mix[] = { + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_SPK_R_MIXER, + RT5659_M_DAC_R2_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5659_SPK_R_MIXER, + RT5659_M_BST4_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5659_SPK_R_MIXER, + RT5659_M_IN_L_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5659_SPK_R_MIXER, + RT5659_M_IN_R_SM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_SPK_R_MIXER, + RT5659_M_BST3_SM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_monovol_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_MONOMIX_IN_GAIN, + RT5659_M_DAC_L2_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_MONOMIX_IN_GAIN, + RT5659_M_DAC_R2_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5659_MONOMIX_IN_GAIN, + RT5659_M_BST1_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5659_MONOMIX_IN_GAIN, + RT5659_M_BST2_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_MONOMIX_IN_GAIN, + RT5659_M_BST3_MM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_out_l_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_OUT_L_MIXER, + RT5659_M_DAC_L2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5659_OUT_L_MIXER, + RT5659_M_IN_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5659_OUT_L_MIXER, + RT5659_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5659_OUT_L_MIXER, + RT5659_M_BST2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_OUT_L_MIXER, + RT5659_M_BST3_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_out_r_mix[] = { + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_OUT_R_MIXER, + RT5659_M_DAC_R2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5659_OUT_R_MIXER, + RT5659_M_IN_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5659_OUT_R_MIXER, + RT5659_M_BST2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5659_OUT_R_MIXER, + RT5659_M_BST3_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5659_OUT_R_MIXER, + RT5659_M_BST4_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_spo_l_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_SPO_AMP_GAIN, + RT5659_M_DAC_L2_SPKOMIX_SFT, 1, 0), + SOC_DAPM_SINGLE("SPKVOL L Switch", RT5659_SPO_AMP_GAIN, + RT5659_M_SPKVOLL_SPKOMIX_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new rt5659_spo_r_mix[] = { + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_SPO_AMP_GAIN, + RT5659_M_DAC_R2_SPKOMIX_SFT, 1, 0), + SOC_DAPM_SINGLE("SPKVOL R Switch", RT5659_SPO_AMP_GAIN, + RT5659_M_SPKVOLR_SPKOMIX_SFT, 1, 0), +}; + +static const struct snd_kcontrol_new rt5659_mono_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_MONOMIX_IN_GAIN, + RT5659_M_DAC_L2_MA_SFT, 1, 1), + SOC_DAPM_SINGLE("MONOVOL Switch", RT5659_MONOMIX_IN_GAIN, + RT5659_M_MONOVOL_MA_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_lout_l_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5659_LOUT_MIXER, + RT5659_M_DAC_L2_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL L Switch", RT5659_LOUT_MIXER, + RT5659_M_OV_L_LM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5659_lout_r_mix[] = { + SOC_DAPM_SINGLE("DAC R2 Switch", RT5659_LOUT_MIXER, + RT5659_M_DAC_R2_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL R Switch", RT5659_LOUT_MIXER, + RT5659_M_OV_R_LM_SFT, 1, 1), +}; + +/*DAC L2, DAC R2*/ +/*MX-1B [6:4], MX-1B [2:0]*/ +static const char * const rt5659_dac2_src[] = { + "IF1 DAC2", "IF2 DAC", "IF3 DAC", "Mono ADC MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_dac_l2_enum, RT5659_DAC_CTRL, + RT5659_DAC_L2_SEL_SFT, rt5659_dac2_src); + +static const struct snd_kcontrol_new rt5659_dac_l2_mux = + SOC_DAPM_ENUM("DAC L2 Source", rt5659_dac_l2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5659_dac_r2_enum, RT5659_DAC_CTRL, + RT5659_DAC_R2_SEL_SFT, rt5659_dac2_src); + +static const struct snd_kcontrol_new rt5659_dac_r2_mux = + SOC_DAPM_ENUM("DAC R2 Source", rt5659_dac_r2_enum); + + +/* STO1 ADC1 Source */ +/* MX-26 [13] */ +static const char * const rt5659_sto1_adc1_src[] = { + "DAC MIX", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_sto1_adc1_enum, RT5659_STO1_ADC_MIXER, + RT5659_STO1_ADC1_SRC_SFT, rt5659_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5659_sto1_adc1_mux = + SOC_DAPM_ENUM("Stereo1 ADC1 Source", rt5659_sto1_adc1_enum); + +/* STO1 ADC Source */ +/* MX-26 [12] */ +static const char * const rt5659_sto1_adc_src[] = { + "ADC1", "ADC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_sto1_adc_enum, RT5659_STO1_ADC_MIXER, + RT5659_STO1_ADC_SRC_SFT, rt5659_sto1_adc_src); + +static const struct snd_kcontrol_new rt5659_sto1_adc_mux = + SOC_DAPM_ENUM("Stereo1 ADC Source", rt5659_sto1_adc_enum); + +/* STO1 ADC2 Source */ +/* MX-26 [11] */ +static const char * const rt5659_sto1_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_sto1_adc2_enum, RT5659_STO1_ADC_MIXER, + RT5659_STO1_ADC2_SRC_SFT, rt5659_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5659_sto1_adc2_mux = + SOC_DAPM_ENUM("Stereo1 ADC2 Source", rt5659_sto1_adc2_enum); + +/* STO1 DMIC Source */ +/* MX-26 [8] */ +static const char * const rt5659_sto1_dmic_src[] = { + "DMIC1", "DMIC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_sto1_dmic_enum, RT5659_STO1_ADC_MIXER, + RT5659_STO1_DMIC_SRC_SFT, rt5659_sto1_dmic_src); + +static const struct snd_kcontrol_new rt5659_sto1_dmic_mux = + SOC_DAPM_ENUM("Stereo1 DMIC Source", rt5659_sto1_dmic_enum); + + +/* MONO ADC L2 Source */ +/* MX-27 [12] */ +static const char * const rt5659_mono_adc_l2_src[] = { + "Mono DAC MIXL", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_adc_l2_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_ADC_L2_SRC_SFT, rt5659_mono_adc_l2_src); + +static const struct snd_kcontrol_new rt5659_mono_adc_l2_mux = + SOC_DAPM_ENUM("Mono ADC L2 Source", rt5659_mono_adc_l2_enum); + + +/* MONO ADC L1 Source */ +/* MX-27 [11] */ +static const char * const rt5659_mono_adc_l1_src[] = { + "Mono DAC MIXL", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_adc_l1_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_ADC_L1_SRC_SFT, rt5659_mono_adc_l1_src); + +static const struct snd_kcontrol_new rt5659_mono_adc_l1_mux = + SOC_DAPM_ENUM("Mono ADC L1 Source", rt5659_mono_adc_l1_enum); + +/* MONO ADC L Source, MONO ADC R Source*/ +/* MX-27 [10:9], MX-27 [2:1] */ +static const char * const rt5659_mono_adc_src[] = { + "ADC1 L", "ADC1 R", "ADC2 L", "ADC2 R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_adc_l_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_ADC_L_SRC_SFT, rt5659_mono_adc_src); + +static const struct snd_kcontrol_new rt5659_mono_adc_l_mux = + SOC_DAPM_ENUM("Mono ADC L Source", rt5659_mono_adc_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_adcr_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_ADC_R_SRC_SFT, rt5659_mono_adc_src); + +static const struct snd_kcontrol_new rt5659_mono_adc_r_mux = + SOC_DAPM_ENUM("Mono ADC R Source", rt5659_mono_adcr_enum); + +/* MONO DMIC L Source */ +/* MX-27 [8] */ +static const char * const rt5659_mono_dmic_l_src[] = { + "DMIC1 L", "DMIC2 L" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_dmic_l_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_DMIC_L_SRC_SFT, rt5659_mono_dmic_l_src); + +static const struct snd_kcontrol_new rt5659_mono_dmic_l_mux = + SOC_DAPM_ENUM("Mono DMIC L Source", rt5659_mono_dmic_l_enum); + +/* MONO ADC R2 Source */ +/* MX-27 [4] */ +static const char * const rt5659_mono_adc_r2_src[] = { + "Mono DAC MIXR", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_adc_r2_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_ADC_R2_SRC_SFT, rt5659_mono_adc_r2_src); + +static const struct snd_kcontrol_new rt5659_mono_adc_r2_mux = + SOC_DAPM_ENUM("Mono ADC R2 Source", rt5659_mono_adc_r2_enum); + +/* MONO ADC R1 Source */ +/* MX-27 [3] */ +static const char * const rt5659_mono_adc_r1_src[] = { + "Mono DAC MIXR", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_adc_r1_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_ADC_R1_SRC_SFT, rt5659_mono_adc_r1_src); + +static const struct snd_kcontrol_new rt5659_mono_adc_r1_mux = + SOC_DAPM_ENUM("Mono ADC R1 Source", rt5659_mono_adc_r1_enum); + +/* MONO DMIC R Source */ +/* MX-27 [0] */ +static const char * const rt5659_mono_dmic_r_src[] = { + "DMIC1 R", "DMIC2 R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_mono_dmic_r_enum, RT5659_MONO_ADC_MIXER, + RT5659_MONO_DMIC_R_SRC_SFT, rt5659_mono_dmic_r_src); + +static const struct snd_kcontrol_new rt5659_mono_dmic_r_mux = + SOC_DAPM_ENUM("Mono DMIC R Source", rt5659_mono_dmic_r_enum); + + +/* DAC R1 Source, DAC L1 Source*/ +/* MX-29 [11:10], MX-29 [9:8]*/ +static const char * const rt5659_dac1_src[] = { + "IF1 DAC1", "IF2 DAC", "IF3 DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_dac_r1_enum, RT5659_AD_DA_MIXER, + RT5659_DAC1_R_SEL_SFT, rt5659_dac1_src); + +static const struct snd_kcontrol_new rt5659_dac_r1_mux = + SOC_DAPM_ENUM("DAC R1 Source", rt5659_dac_r1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5659_dac_l1_enum, RT5659_AD_DA_MIXER, + RT5659_DAC1_L_SEL_SFT, rt5659_dac1_src); + +static const struct snd_kcontrol_new rt5659_dac_l1_mux = + SOC_DAPM_ENUM("DAC L1 Source", rt5659_dac_l1_enum); + +/* DAC Digital Mixer L Source, DAC Digital Mixer R Source*/ +/* MX-2C [6], MX-2C [4]*/ +static const char * const rt5659_dig_dac_mix_src[] = { + "Stereo DAC Mixer", "Mono DAC Mixer" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_dig_dac_mixl_enum, RT5659_DIG_MIXER, + RT5659_DAC_MIX_L_SFT, rt5659_dig_dac_mix_src); + +static const struct snd_kcontrol_new rt5659_dig_dac_mixl_mux = + SOC_DAPM_ENUM("DAC Digital Mixer L Source", rt5659_dig_dac_mixl_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5659_dig_dac_mixr_enum, RT5659_DIG_MIXER, + RT5659_DAC_MIX_R_SFT, rt5659_dig_dac_mix_src); + +static const struct snd_kcontrol_new rt5659_dig_dac_mixr_mux = + SOC_DAPM_ENUM("DAC Digital Mixer R Source", rt5659_dig_dac_mixr_enum); + +/* Analog DAC L1 Source, Analog DAC R1 Source*/ +/* MX-2D [3], MX-2D [2]*/ +static const char * const rt5659_alg_dac1_src[] = { + "DAC", "Stereo DAC Mixer" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_alg_dac_l1_enum, RT5659_A_DAC_MUX, + RT5659_A_DACL1_SFT, rt5659_alg_dac1_src); + +static const struct snd_kcontrol_new rt5659_alg_dac_l1_mux = + SOC_DAPM_ENUM("Analog DACL1 Source", rt5659_alg_dac_l1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5659_alg_dac_r1_enum, RT5659_A_DAC_MUX, + RT5659_A_DACR1_SFT, rt5659_alg_dac1_src); + +static const struct snd_kcontrol_new rt5659_alg_dac_r1_mux = + SOC_DAPM_ENUM("Analog DACR1 Source", rt5659_alg_dac_r1_enum); + +/* Analog DAC LR Source, Analog DAC R2 Source*/ +/* MX-2D [1], MX-2D [0]*/ +static const char * const rt5659_alg_dac2_src[] = { + "Stereo DAC Mixer", "Mono DAC Mixer" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_alg_dac_l2_enum, RT5659_A_DAC_MUX, + RT5659_A_DACL2_SFT, rt5659_alg_dac2_src); + +static const struct snd_kcontrol_new rt5659_alg_dac_l2_mux = + SOC_DAPM_ENUM("Analog DAC L2 Source", rt5659_alg_dac_l2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5659_alg_dac_r2_enum, RT5659_A_DAC_MUX, + RT5659_A_DACR2_SFT, rt5659_alg_dac2_src); + +static const struct snd_kcontrol_new rt5659_alg_dac_r2_mux = + SOC_DAPM_ENUM("Analog DAC R2 Source", rt5659_alg_dac_r2_enum); + +/* Interface2 ADC Data Input*/ +/* MX-2F [13:12] */ +static const char * const rt5659_if2_adc_in_src[] = { + "IF_ADC1", "IF_ADC2", "DAC_REF", "IF_ADC3" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_if2_adc_in_enum, RT5659_DIG_INF23_DATA, + RT5659_IF2_ADC_IN_SFT, rt5659_if2_adc_in_src); + +static const struct snd_kcontrol_new rt5659_if2_adc_in_mux = + SOC_DAPM_ENUM("IF2 ADC IN Source", rt5659_if2_adc_in_enum); + +/* Interface3 ADC Data Input*/ +/* MX-2F [1:0] */ +static const char * const rt5659_if3_adc_in_src[] = { + "IF_ADC1", "IF_ADC2", "DAC_REF", "Stereo2_ADC_L/R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_if3_adc_in_enum, RT5659_DIG_INF23_DATA, + RT5659_IF3_ADC_IN_SFT, rt5659_if3_adc_in_src); + +static const struct snd_kcontrol_new rt5659_if3_adc_in_mux = + SOC_DAPM_ENUM("IF3 ADC IN Source", rt5659_if3_adc_in_enum); + +/* PDM 1 L/R*/ +/* MX-31 [15] [13] */ +static const char * const rt5659_pdm_src[] = { + "Mono DAC", "Stereo DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_pdm_l_enum, RT5659_PDM_OUT_CTRL, + RT5659_PDM1_L_SFT, rt5659_pdm_src); + +static const struct snd_kcontrol_new rt5659_pdm_l_mux = + SOC_DAPM_ENUM("PDM L Source", rt5659_pdm_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5659_pdm_r_enum, RT5659_PDM_OUT_CTRL, + RT5659_PDM1_R_SFT, rt5659_pdm_src); + +static const struct snd_kcontrol_new rt5659_pdm_r_mux = + SOC_DAPM_ENUM("PDM R Source", rt5659_pdm_r_enum); + +/* SPDIF Output source*/ +/* MX-36 [1:0] */ +static const char * const rt5659_spdif_src[] = { + "IF1_DAC1", "IF1_DAC2", "IF2_DAC", "IF3_DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_spdif_enum, RT5659_SPDIF_CTRL, + RT5659_SPDIF_SEL_SFT, rt5659_spdif_src); + +static const struct snd_kcontrol_new rt5659_spdif_mux = + SOC_DAPM_ENUM("SPDIF Source", rt5659_spdif_enum); + +/* I2S1 TDM ADCDAT Source */ +/* MX-78[4:0] */ +static const char * const rt5659_rx_adc_data_src[] = { + "AD1:AD2:DAC:NUL", "AD1:AD2:NUL:DAC", "AD1:DAC:AD2:NUL", + "AD1:DAC:NUL:AD2", "AD1:NUL:DAC:AD2", "AD1:NUL:AD2:DAC", + "AD2:AD1:DAC:NUL", "AD2:AD1:NUL:DAC", "AD2:DAC:AD1:NUL", + "AD2:DAC:NUL:AD1", "AD2:NUL:DAC:AD1", "AD1:NUL:AD1:DAC", + "DAC:AD1:AD2:NUL", "DAC:AD1:NUL:AD2", "DAC:AD2:AD1:NUL", + "DAC:AD2:NUL:AD1", "DAC:NUL:DAC:AD2", "DAC:NUL:AD2:DAC", + "NUL:AD1:AD2:DAC", "NUL:AD1:DAC:AD2", "NUL:AD2:AD1:DAC", + "NUL:AD2:DAC:AD1", "NUL:DAC:DAC:AD2", "NUL:DAC:AD2:DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5659_rx_adc_data_enum, RT5659_TDM_CTRL_2, + RT5659_ADCDAT_SRC_SFT, rt5659_rx_adc_data_src); + +static const struct snd_kcontrol_new rt5659_rx_adc_dac_mux = + SOC_DAPM_ENUM("TDM ADCDAT Source", rt5659_rx_adc_data_enum); + +/* Out Volume Switch */ +static const struct snd_kcontrol_new spkvol_l_switch = + SOC_DAPM_SINGLE("Switch", RT5659_SPO_VOL, RT5659_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new spkvol_r_switch = + SOC_DAPM_SINGLE("Switch", RT5659_SPO_VOL, RT5659_VOL_R_SFT, 1, 1); + +static const struct snd_kcontrol_new monovol_switch = + SOC_DAPM_SINGLE("Switch", RT5659_MONO_OUT, RT5659_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new outvol_l_switch = + SOC_DAPM_SINGLE("Switch", RT5659_LOUT, RT5659_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new outvol_r_switch = + SOC_DAPM_SINGLE("Switch", RT5659_LOUT, RT5659_VOL_R_SFT, 1, 1); + +/* Out Switch */ +static const struct snd_kcontrol_new spo_switch = + SOC_DAPM_SINGLE("Switch", RT5659_CLASSD_2, RT5659_M_RF_DIG_SFT, 1, 1); + +static const struct snd_kcontrol_new mono_switch = + SOC_DAPM_SINGLE("Switch", RT5659_MONO_OUT, RT5659_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpo_l_switch = + SOC_DAPM_SINGLE("Switch", RT5659_HP_VOL, RT5659_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpo_r_switch = + SOC_DAPM_SINGLE("Switch", RT5659_HP_VOL, RT5659_R_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_l_switch = + SOC_DAPM_SINGLE("Switch", RT5659_LOUT, RT5659_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_r_switch = + SOC_DAPM_SINGLE("Switch", RT5659_LOUT, RT5659_R_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new pdm_l_switch = + SOC_DAPM_SINGLE("Switch", RT5659_PDM_OUT_CTRL, RT5659_M_PDM1_L_SFT, 1, + 1); + +static const struct snd_kcontrol_new pdm_r_switch = + SOC_DAPM_SINGLE("Switch", RT5659_PDM_OUT_CTRL, RT5659_M_PDM1_R_SFT, 1, + 1); + +static int rt5659_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, RT5659_CLASSD_CTRL_1, + RT5659_POW_CLSD_DB_MASK, RT5659_POW_CLSD_DB_EN); + snd_soc_component_update_bits(component, RT5659_CLASSD_2, + RT5659_M_RI_DIG, RT5659_M_RI_DIG); + snd_soc_component_write(component, RT5659_CLASSD_1, 0x0803); + snd_soc_component_write(component, RT5659_SPK_DC_CAILB_CTRL_3, 0x0000); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_write(component, RT5659_CLASSD_1, 0x0011); + snd_soc_component_update_bits(component, RT5659_CLASSD_2, + RT5659_M_RI_DIG, 0x0); + snd_soc_component_write(component, RT5659_SPK_DC_CAILB_CTRL_3, 0x0003); + snd_soc_component_update_bits(component, RT5659_CLASSD_CTRL_1, + RT5659_POW_CLSD_DB_MASK, RT5659_POW_CLSD_DB_DIS); + break; + + default: + return 0; + } + + return 0; + +} + +static int rt5659_mono_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_write(component, RT5659_MONO_AMP_CALIB_CTRL_1, 0x1e00); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_write(component, RT5659_MONO_AMP_CALIB_CTRL_1, 0x1e04); + break; + + default: + return 0; + } + + return 0; + +} + +static int rt5659_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_write(component, RT5659_HP_CHARGE_PUMP_1, 0x0e1e); + snd_soc_component_update_bits(component, RT5659_DEPOP_1, 0x0010, 0x0010); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_write(component, RT5659_DEPOP_1, 0x0000); + break; + + default: + return 0; + } + + return 0; +} + +static int set_dmic_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /*Add delay to avoid pop noise*/ + msleep(450); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5659_particular_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO2", RT5659_PWR_ANLG_3, RT5659_PWR_LDO2_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5659_PWR_ANLG_2, RT5659_PWR_MB1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Det Power", RT5659_PWR_VOL, + RT5659_PWR_MIC_DET_BIT, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_widget rt5659_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PLL", RT5659_PWR_ANLG_3, RT5659_PWR_PLL_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("Mono Vref", RT5659_PWR_ANLG_1, + RT5659_PWR_VREF3_BIT, 0, NULL, 0), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("I2S1 ASRC", 1, RT5659_ASRC_1, + RT5659_I2S1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5659_ASRC_1, + RT5659_I2S2_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S3 ASRC", 1, RT5659_ASRC_1, + RT5659_I2S3_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC STO ASRC", 1, RT5659_ASRC_1, + RT5659_DAC_STO_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC Mono L ASRC", 1, RT5659_ASRC_1, + RT5659_DAC_MONO_L_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC Mono R ASRC", 1, RT5659_ASRC_1, + RT5659_DAC_MONO_R_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5659_ASRC_1, + RT5659_ADC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC Mono L ASRC", 1, RT5659_ASRC_1, + RT5659_ADC_MONO_L_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC Mono R ASRC", 1, RT5659_ASRC_1, + RT5659_ADC_MONO_R_ASRC_SFT, 0, NULL, 0), + + /* Input Side */ + SND_SOC_DAPM_SUPPLY("MICBIAS2", RT5659_PWR_ANLG_2, RT5659_PWR_MB2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS3", RT5659_PWR_ANLG_2, RT5659_PWR_MB3_BIT, + 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + SND_SOC_DAPM_INPUT("DMIC L2"), + SND_SOC_DAPM_INPUT("DMIC R2"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + SND_SOC_DAPM_INPUT("IN3P"), + SND_SOC_DAPM_INPUT("IN3N"), + SND_SOC_DAPM_INPUT("IN4P"), + SND_SOC_DAPM_INPUT("IN4N"), + + SND_SOC_DAPM_PGA("DMIC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC2", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5659_DMIC_CTRL_1, + RT5659_DMIC_1_EN_SFT, 0, set_dmic_power, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("DMIC2 Power", RT5659_DMIC_CTRL_1, + RT5659_DMIC_2_EN_SFT, 0, set_dmic_power, SND_SOC_DAPM_POST_PMU), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1", RT5659_PWR_ANLG_2, + RT5659_PWR_BST1_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST2", RT5659_PWR_ANLG_2, + RT5659_PWR_BST2_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST3", RT5659_PWR_ANLG_2, + RT5659_PWR_BST3_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST4", RT5659_PWR_ANLG_2, + RT5659_PWR_BST4_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST1 Power", RT5659_PWR_ANLG_2, + RT5659_PWR_BST1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST2 Power", RT5659_PWR_ANLG_2, + RT5659_PWR_BST2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST3 Power", RT5659_PWR_ANLG_2, + RT5659_PWR_BST3_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST4 Power", RT5659_PWR_ANLG_2, + RT5659_PWR_BST4_BIT, 0, NULL, 0), + + + /* Input Volume */ + SND_SOC_DAPM_PGA("INL VOL", RT5659_PWR_VOL, RT5659_PWR_IN_L_BIT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("INR VOL", RT5659_PWR_VOL, RT5659_PWR_IN_R_BIT, + 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX1L", RT5659_PWR_MIXER, RT5659_PWR_RM1_L_BIT, + 0, rt5659_rec1_l_mix, ARRAY_SIZE(rt5659_rec1_l_mix)), + SND_SOC_DAPM_MIXER("RECMIX1R", RT5659_PWR_MIXER, RT5659_PWR_RM1_R_BIT, + 0, rt5659_rec1_r_mix, ARRAY_SIZE(rt5659_rec1_r_mix)), + SND_SOC_DAPM_MIXER("RECMIX2L", RT5659_PWR_MIXER, RT5659_PWR_RM2_L_BIT, + 0, rt5659_rec2_l_mix, ARRAY_SIZE(rt5659_rec2_l_mix)), + SND_SOC_DAPM_MIXER("RECMIX2R", RT5659_PWR_MIXER, RT5659_PWR_RM2_R_BIT, + 0, rt5659_rec2_r_mix, ARRAY_SIZE(rt5659_rec2_r_mix)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC1 L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC1 R", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC2 L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC2 R", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("ADC1 L Power", RT5659_PWR_DIG_1, + RT5659_PWR_ADC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 R Power", RT5659_PWR_DIG_1, + RT5659_PWR_ADC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 L Power", RT5659_PWR_DIG_1, + RT5659_PWR_ADC_L2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 R Power", RT5659_PWR_DIG_1, + RT5659_PWR_ADC_R2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 clock", SND_SOC_NOPM, 0, 0, set_adc1_clk, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("ADC2 clock", SND_SOC_NOPM, 0, 0, set_adc2_clk, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo1 DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_adc1_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_adc1_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_adc2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_adc2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_adc_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5659_sto1_adc_mux), + SND_SOC_DAPM_MUX("Mono ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_adc_l2_mux), + SND_SOC_DAPM_MUX("Mono ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_adc_r2_mux), + SND_SOC_DAPM_MUX("Mono ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_adc_l1_mux), + SND_SOC_DAPM_MUX("Mono ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_adc_r1_mux), + SND_SOC_DAPM_MUX("Mono DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_dmic_l_mux), + SND_SOC_DAPM_MUX("Mono DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_dmic_r_mux), + SND_SOC_DAPM_MUX("Mono ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_adc_l_mux), + SND_SOC_DAPM_MUX("Mono ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5659_mono_adc_r_mux), + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("ADC Stereo1 Filter", RT5659_PWR_DIG_2, + RT5659_PWR_ADC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Stereo2 Filter", RT5659_PWR_DIG_2, + RT5659_PWR_ADC_S2F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", SND_SOC_NOPM, + 0, 0, rt5659_sto1_adc_l_mix, + ARRAY_SIZE(rt5659_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXR", SND_SOC_NOPM, + 0, 0, rt5659_sto1_adc_r_mix, + ARRAY_SIZE(rt5659_sto1_adc_r_mix)), + SND_SOC_DAPM_SUPPLY("ADC Mono Left Filter", RT5659_PWR_DIG_2, + RT5659_PWR_ADC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXL", RT5659_MONO_ADC_DIG_VOL, + RT5659_L_MUTE_SFT, 1, rt5659_mono_adc_l_mix, + ARRAY_SIZE(rt5659_mono_adc_l_mix)), + SND_SOC_DAPM_SUPPLY("ADC Mono Right Filter", RT5659_PWR_DIG_2, + RT5659_PWR_ADC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXR", RT5659_MONO_ADC_DIG_VOL, + RT5659_R_MUTE_SFT, 1, rt5659_mono_adc_r_mix, + ARRAY_SIZE(rt5659_mono_adc_r_mix)), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("IF_ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF_ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF_ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC LR", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Stereo1 ADC Volume L", RT5659_STO1_ADC_DIG_VOL, + RT5659_L_MUTE_SFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Stereo1 ADC Volume R", RT5659_STO1_ADC_DIG_VOL, + RT5659_R_MUTE_SFT, 1, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5659_PWR_DIG_1, RT5659_PWR_I2S1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2", RT5659_PWR_DIG_1, RT5659_PWR_I2S2_BIT, 0, + NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S3", RT5659_PWR_DIG_1, RT5659_PWR_I2S3_BIT, 0, + NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_PGA("TDM AD1:AD2:DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("TDM AD2:DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("TDM Data Mux", SND_SOC_NOPM, 0, 0, + &rt5659_rx_adc_dac_mux), + SND_SOC_DAPM_MUX("IF2 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if2_adc_in_mux), + SND_SOC_DAPM_MUX("IF3 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if3_adc_in_mux), + SND_SOC_DAPM_MUX("IF1 01 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if1_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 23 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if1_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 45 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if1_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 67 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if1_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF2 DAC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if2_dac_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if2_adc_swap_mux), + SND_SOC_DAPM_MUX("IF3 DAC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if3_dac_swap_mux), + SND_SOC_DAPM_MUX("IF3 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5659_if3_adc_swap_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF3RX", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF3TX", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5659_dac_l_mix, ARRAY_SIZE(rt5659_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5659_dac_r_mix, ARRAY_SIZE(rt5659_dac_r_mix)), + + /* DAC channel Mux */ + SND_SOC_DAPM_MUX("DAC L1 Mux", SND_SOC_NOPM, 0, 0, &rt5659_dac_l1_mux), + SND_SOC_DAPM_MUX("DAC R1 Mux", SND_SOC_NOPM, 0, 0, &rt5659_dac_r1_mux), + SND_SOC_DAPM_MUX("DAC L2 Mux", SND_SOC_NOPM, 0, 0, &rt5659_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Mux", SND_SOC_NOPM, 0, 0, &rt5659_dac_r2_mux), + + SND_SOC_DAPM_MUX("DAC L1 Source", SND_SOC_NOPM, 0, 0, + &rt5659_alg_dac_l1_mux), + SND_SOC_DAPM_MUX("DAC R1 Source", SND_SOC_NOPM, 0, 0, + &rt5659_alg_dac_r1_mux), + SND_SOC_DAPM_MUX("DAC L2 Source", SND_SOC_NOPM, 0, 0, + &rt5659_alg_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Source", SND_SOC_NOPM, 0, 0, + &rt5659_alg_dac_r2_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("DAC Stereo1 Filter", RT5659_PWR_DIG_2, + RT5659_PWR_DAC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Mono Left Filter", RT5659_PWR_DIG_2, + RT5659_PWR_DAC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Mono Right Filter", RT5659_PWR_DIG_2, + RT5659_PWR_DAC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5659_sto_dac_l_mix, ARRAY_SIZE(rt5659_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5659_sto_dac_r_mix, ARRAY_SIZE(rt5659_sto_dac_r_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5659_mono_dac_l_mix, ARRAY_SIZE(rt5659_mono_dac_l_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5659_mono_dac_r_mix, ARRAY_SIZE(rt5659_mono_dac_r_mix)), + SND_SOC_DAPM_MUX("DAC MIXL", SND_SOC_NOPM, 0, 0, + &rt5659_dig_dac_mixl_mux), + SND_SOC_DAPM_MUX("DAC MIXR", SND_SOC_NOPM, 0, 0, + &rt5659_dig_dac_mixr_mux), + + /* DACs */ + SND_SOC_DAPM_SUPPLY_S("DAC L1 Power", 1, RT5659_PWR_DIG_1, + RT5659_PWR_DAC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC R1 Power", 1, RT5659_PWR_DIG_1, + RT5659_PWR_DAC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DAC L1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("DAC L2 Power", RT5659_PWR_DIG_1, + RT5659_PWR_DAC_L2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC R2 Power", RT5659_PWR_DIG_1, + RT5659_PWR_DAC_R2_BIT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DAC L2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC R2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("DAC_REF", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* OUT Mixer */ + SND_SOC_DAPM_MIXER("SPK MIXL", RT5659_PWR_MIXER, RT5659_PWR_SM_L_BIT, + 0, rt5659_spk_l_mix, ARRAY_SIZE(rt5659_spk_l_mix)), + SND_SOC_DAPM_MIXER("SPK MIXR", RT5659_PWR_MIXER, RT5659_PWR_SM_R_BIT, + 0, rt5659_spk_r_mix, ARRAY_SIZE(rt5659_spk_r_mix)), + SND_SOC_DAPM_MIXER("MONOVOL MIX", RT5659_PWR_MIXER, RT5659_PWR_MM_BIT, + 0, rt5659_monovol_mix, ARRAY_SIZE(rt5659_monovol_mix)), + SND_SOC_DAPM_MIXER("OUT MIXL", RT5659_PWR_MIXER, RT5659_PWR_OM_L_BIT, + 0, rt5659_out_l_mix, ARRAY_SIZE(rt5659_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5659_PWR_MIXER, RT5659_PWR_OM_R_BIT, + 0, rt5659_out_r_mix, ARRAY_SIZE(rt5659_out_r_mix)), + + /* Output Volume */ + SND_SOC_DAPM_SWITCH("SPKVOL L", RT5659_PWR_VOL, RT5659_PWR_SV_L_BIT, 0, + &spkvol_l_switch), + SND_SOC_DAPM_SWITCH("SPKVOL R", RT5659_PWR_VOL, RT5659_PWR_SV_R_BIT, 0, + &spkvol_r_switch), + SND_SOC_DAPM_SWITCH("MONOVOL", RT5659_PWR_VOL, RT5659_PWR_MV_BIT, 0, + &monovol_switch), + SND_SOC_DAPM_SWITCH("OUTVOL L", RT5659_PWR_VOL, RT5659_PWR_OV_L_BIT, 0, + &outvol_l_switch), + SND_SOC_DAPM_SWITCH("OUTVOL R", RT5659_PWR_VOL, RT5659_PWR_OV_R_BIT, 0, + &outvol_r_switch), + + /* SPO/MONO/HPO/LOUT */ + SND_SOC_DAPM_MIXER("SPO L MIX", SND_SOC_NOPM, 0, 0, rt5659_spo_l_mix, + ARRAY_SIZE(rt5659_spo_l_mix)), + SND_SOC_DAPM_MIXER("SPO R MIX", SND_SOC_NOPM, 0, 0, rt5659_spo_r_mix, + ARRAY_SIZE(rt5659_spo_r_mix)), + SND_SOC_DAPM_MIXER("Mono MIX", SND_SOC_NOPM, 0, 0, rt5659_mono_mix, + ARRAY_SIZE(rt5659_mono_mix)), + SND_SOC_DAPM_MIXER("LOUT L MIX", SND_SOC_NOPM, 0, 0, rt5659_lout_l_mix, + ARRAY_SIZE(rt5659_lout_l_mix)), + SND_SOC_DAPM_MIXER("LOUT R MIX", SND_SOC_NOPM, 0, 0, rt5659_lout_r_mix, + ARRAY_SIZE(rt5659_lout_r_mix)), + + SND_SOC_DAPM_PGA_S("SPK Amp", 1, RT5659_PWR_DIG_1, RT5659_PWR_CLS_D_BIT, + 0, rt5659_spk_event, SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_PGA_S("Mono Amp", 1, RT5659_PWR_ANLG_1, RT5659_PWR_MA_BIT, + 0, rt5659_mono_event, SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, rt5659_hp_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT Amp", 1, RT5659_PWR_ANLG_1, RT5659_PWR_LM_BIT, + 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Charge Pump", SND_SOC_NOPM, 0, 0, + rt5659_charge_pump_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SWITCH("SPO Playback", SND_SOC_NOPM, 0, 0, &spo_switch), + SND_SOC_DAPM_SWITCH("Mono Playback", SND_SOC_NOPM, 0, 0, + &mono_switch), + SND_SOC_DAPM_SWITCH("HPO L Playback", SND_SOC_NOPM, 0, 0, + &hpo_l_switch), + SND_SOC_DAPM_SWITCH("HPO R Playback", SND_SOC_NOPM, 0, 0, + &hpo_r_switch), + SND_SOC_DAPM_SWITCH("LOUT L Playback", SND_SOC_NOPM, 0, 0, + &lout_l_switch), + SND_SOC_DAPM_SWITCH("LOUT R Playback", SND_SOC_NOPM, 0, 0, + &lout_r_switch), + SND_SOC_DAPM_SWITCH("PDM L Playback", SND_SOC_NOPM, 0, 0, + &pdm_l_switch), + SND_SOC_DAPM_SWITCH("PDM R Playback", SND_SOC_NOPM, 0, 0, + &pdm_r_switch), + + /* PDM */ + SND_SOC_DAPM_SUPPLY("PDM Power", RT5659_PWR_DIG_2, + RT5659_PWR_PDM1_BIT, 0, NULL, 0), + SND_SOC_DAPM_MUX("PDM L Mux", RT5659_PDM_OUT_CTRL, + RT5659_M_PDM1_L_SFT, 1, &rt5659_pdm_l_mux), + SND_SOC_DAPM_MUX("PDM R Mux", RT5659_PDM_OUT_CTRL, + RT5659_M_PDM1_R_SFT, 1, &rt5659_pdm_r_mux), + + /* SPDIF */ + SND_SOC_DAPM_MUX("SPDIF Mux", SND_SOC_NOPM, 0, 0, &rt5659_spdif_mux), + + SND_SOC_DAPM_SUPPLY("SYS CLK DET", RT5659_CLK_DET, 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET", RT5659_CLK_DET, 0, 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_OUTPUT("PDML"), + SND_SOC_DAPM_OUTPUT("PDMR"), + SND_SOC_DAPM_OUTPUT("SPDIF"), +}; + +static const struct snd_soc_dapm_route rt5659_dapm_routes[] = { + /*PLL*/ + { "ADC Stereo1 Filter", NULL, "PLL", is_sys_clk_from_pll }, + { "ADC Stereo2 Filter", NULL, "PLL", is_sys_clk_from_pll }, + { "ADC Mono Left Filter", NULL, "PLL", is_sys_clk_from_pll }, + { "ADC Mono Right Filter", NULL, "PLL", is_sys_clk_from_pll }, + { "DAC Stereo1 Filter", NULL, "PLL", is_sys_clk_from_pll }, + { "DAC Mono Left Filter", NULL, "PLL", is_sys_clk_from_pll }, + { "DAC Mono Right Filter", NULL, "PLL", is_sys_clk_from_pll }, + + /*ASRC*/ + { "ADC Stereo1 Filter", NULL, "ADC STO1 ASRC", is_using_asrc }, + { "ADC Mono Left Filter", NULL, "ADC Mono L ASRC", is_using_asrc }, + { "ADC Mono Right Filter", NULL, "ADC Mono R ASRC", is_using_asrc }, + { "DAC Mono Left Filter", NULL, "DAC Mono L ASRC", is_using_asrc }, + { "DAC Mono Right Filter", NULL, "DAC Mono R ASRC", is_using_asrc }, + { "DAC Stereo1 Filter", NULL, "DAC STO ASRC", is_using_asrc }, + + { "SYS CLK DET", NULL, "CLKDET" }, + + { "I2S1", NULL, "I2S1 ASRC" }, + { "I2S2", NULL, "I2S2 ASRC" }, + { "I2S3", NULL, "I2S3 ASRC" }, + + { "DMIC1", NULL, "DMIC L1" }, + { "DMIC1", NULL, "DMIC R1" }, + { "DMIC2", NULL, "DMIC L2" }, + { "DMIC2", NULL, "DMIC R2" }, + + { "BST1", NULL, "IN1P" }, + { "BST1", NULL, "IN1N" }, + { "BST1", NULL, "BST1 Power" }, + { "BST2", NULL, "IN2P" }, + { "BST2", NULL, "IN2N" }, + { "BST2", NULL, "BST2 Power" }, + { "BST3", NULL, "IN3P" }, + { "BST3", NULL, "IN3N" }, + { "BST3", NULL, "BST3 Power" }, + { "BST4", NULL, "IN4P" }, + { "BST4", NULL, "IN4N" }, + { "BST4", NULL, "BST4 Power" }, + + { "INL VOL", NULL, "IN2P" }, + { "INR VOL", NULL, "IN2N" }, + + { "RECMIX1L", "SPKVOLL Switch", "SPKVOL L" }, + { "RECMIX1L", "INL Switch", "INL VOL" }, + { "RECMIX1L", "BST4 Switch", "BST4" }, + { "RECMIX1L", "BST3 Switch", "BST3" }, + { "RECMIX1L", "BST2 Switch", "BST2" }, + { "RECMIX1L", "BST1 Switch", "BST1" }, + + { "RECMIX1R", "HPOVOLR Switch", "HPO R Playback" }, + { "RECMIX1R", "INR Switch", "INR VOL" }, + { "RECMIX1R", "BST4 Switch", "BST4" }, + { "RECMIX1R", "BST3 Switch", "BST3" }, + { "RECMIX1R", "BST2 Switch", "BST2" }, + { "RECMIX1R", "BST1 Switch", "BST1" }, + + { "RECMIX2L", "SPKVOLL Switch", "SPKVOL L" }, + { "RECMIX2L", "OUTVOLL Switch", "OUTVOL L" }, + { "RECMIX2L", "BST4 Switch", "BST4" }, + { "RECMIX2L", "BST3 Switch", "BST3" }, + { "RECMIX2L", "BST2 Switch", "BST2" }, + { "RECMIX2L", "BST1 Switch", "BST1" }, + + { "RECMIX2R", "MONOVOL Switch", "MONOVOL" }, + { "RECMIX2R", "OUTVOLR Switch", "OUTVOL R" }, + { "RECMIX2R", "BST4 Switch", "BST4" }, + { "RECMIX2R", "BST3 Switch", "BST3" }, + { "RECMIX2R", "BST2 Switch", "BST2" }, + { "RECMIX2R", "BST1 Switch", "BST1" }, + + { "ADC1 L", NULL, "RECMIX1L" }, + { "ADC1 L", NULL, "ADC1 L Power" }, + { "ADC1 L", NULL, "ADC1 clock" }, + { "ADC1 R", NULL, "RECMIX1R" }, + { "ADC1 R", NULL, "ADC1 R Power" }, + { "ADC1 R", NULL, "ADC1 clock" }, + + { "ADC2 L", NULL, "RECMIX2L" }, + { "ADC2 L", NULL, "ADC2 L Power" }, + { "ADC2 L", NULL, "ADC2 clock" }, + { "ADC2 R", NULL, "RECMIX2R" }, + { "ADC2 R", NULL, "ADC2 R Power" }, + { "ADC2 R", NULL, "ADC2 clock" }, + + { "DMIC L1", NULL, "DMIC CLK" }, + { "DMIC L1", NULL, "DMIC1 Power" }, + { "DMIC R1", NULL, "DMIC CLK" }, + { "DMIC R1", NULL, "DMIC1 Power" }, + { "DMIC L2", NULL, "DMIC CLK" }, + { "DMIC L2", NULL, "DMIC2 Power" }, + { "DMIC R2", NULL, "DMIC CLK" }, + { "DMIC R2", NULL, "DMIC2 Power" }, + + { "Stereo1 DMIC L Mux", "DMIC1", "DMIC L1" }, + { "Stereo1 DMIC L Mux", "DMIC2", "DMIC L2" }, + + { "Stereo1 DMIC R Mux", "DMIC1", "DMIC R1" }, + { "Stereo1 DMIC R Mux", "DMIC2", "DMIC R2" }, + + { "Mono DMIC L Mux", "DMIC1 L", "DMIC L1" }, + { "Mono DMIC L Mux", "DMIC2 L", "DMIC L2" }, + + { "Mono DMIC R Mux", "DMIC1 R", "DMIC R1" }, + { "Mono DMIC R Mux", "DMIC2 R", "DMIC R2" }, + + { "Stereo1 ADC L Mux", "ADC1", "ADC1 L" }, + { "Stereo1 ADC L Mux", "ADC2", "ADC2 L" }, + { "Stereo1 ADC R Mux", "ADC1", "ADC1 R" }, + { "Stereo1 ADC R Mux", "ADC2", "ADC2 R" }, + + { "Stereo1 ADC L1 Mux", "ADC", "Stereo1 ADC L Mux" }, + { "Stereo1 ADC L1 Mux", "DAC MIX", "DAC MIXL" }, + { "Stereo1 ADC L2 Mux", "DMIC", "Stereo1 DMIC L Mux" }, + { "Stereo1 ADC L2 Mux", "DAC MIX", "DAC MIXL" }, + + { "Stereo1 ADC R1 Mux", "ADC", "Stereo1 ADC R Mux" }, + { "Stereo1 ADC R1 Mux", "DAC MIX", "DAC MIXR" }, + { "Stereo1 ADC R2 Mux", "DMIC", "Stereo1 DMIC R Mux" }, + { "Stereo1 ADC R2 Mux", "DAC MIX", "DAC MIXR" }, + + { "Mono ADC L Mux", "ADC1 L", "ADC1 L" }, + { "Mono ADC L Mux", "ADC1 R", "ADC1 R" }, + { "Mono ADC L Mux", "ADC2 L", "ADC2 L" }, + { "Mono ADC L Mux", "ADC2 R", "ADC2 R" }, + + { "Mono ADC R Mux", "ADC1 L", "ADC1 L" }, + { "Mono ADC R Mux", "ADC1 R", "ADC1 R" }, + { "Mono ADC R Mux", "ADC2 L", "ADC2 L" }, + { "Mono ADC R Mux", "ADC2 R", "ADC2 R" }, + + { "Mono ADC L2 Mux", "DMIC", "Mono DMIC L Mux" }, + { "Mono ADC L2 Mux", "Mono DAC MIXL", "Mono DAC MIXL" }, + { "Mono ADC L1 Mux", "Mono DAC MIXL", "Mono DAC MIXL" }, + { "Mono ADC L1 Mux", "ADC", "Mono ADC L Mux" }, + + { "Mono ADC R1 Mux", "Mono DAC MIXR", "Mono DAC MIXR" }, + { "Mono ADC R1 Mux", "ADC", "Mono ADC R Mux" }, + { "Mono ADC R2 Mux", "DMIC", "Mono DMIC R Mux" }, + { "Mono ADC R2 Mux", "Mono DAC MIXR", "Mono DAC MIXR" }, + + { "Stereo1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux" }, + { "Stereo1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux" }, + { "Stereo1 ADC MIXL", NULL, "ADC Stereo1 Filter" }, + + { "Stereo1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux" }, + { "Stereo1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux" }, + { "Stereo1 ADC MIXR", NULL, "ADC Stereo1 Filter" }, + + { "Mono ADC MIXL", "ADC1 Switch", "Mono ADC L1 Mux" }, + { "Mono ADC MIXL", "ADC2 Switch", "Mono ADC L2 Mux" }, + { "Mono ADC MIXL", NULL, "ADC Mono Left Filter" }, + + { "Mono ADC MIXR", "ADC1 Switch", "Mono ADC R1 Mux" }, + { "Mono ADC MIXR", "ADC2 Switch", "Mono ADC R2 Mux" }, + { "Mono ADC MIXR", NULL, "ADC Mono Right Filter" }, + + { "Stereo1 ADC Volume L", NULL, "Stereo1 ADC MIXL" }, + { "Stereo1 ADC Volume R", NULL, "Stereo1 ADC MIXR" }, + + { "IF_ADC1", NULL, "Stereo1 ADC Volume L" }, + { "IF_ADC1", NULL, "Stereo1 ADC Volume R" }, + { "IF_ADC2", NULL, "Mono ADC MIXL" }, + { "IF_ADC2", NULL, "Mono ADC MIXR" }, + + { "TDM AD1:AD2:DAC", NULL, "IF_ADC1" }, + { "TDM AD1:AD2:DAC", NULL, "IF_ADC2" }, + { "TDM AD1:AD2:DAC", NULL, "DAC_REF" }, + { "TDM AD2:DAC", NULL, "IF_ADC2" }, + { "TDM AD2:DAC", NULL, "DAC_REF" }, + { "TDM Data Mux", "AD1:AD2:DAC:NUL", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD1:AD2:NUL:DAC", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD1:DAC:AD2:NUL", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD1:DAC:NUL:AD2", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD1:NUL:DAC:AD2", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD1:NUL:AD2:DAC", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD2:AD1:DAC:NUL", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD2:AD1:NUL:DAC", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD2:DAC:AD1:NUL", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD2:DAC:NUL:AD1", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD2:NUL:DAC:AD1", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "AD1:NUL:AD1:DAC", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "DAC:AD1:AD2:NUL", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "DAC:AD1:NUL:AD2", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "DAC:AD2:AD1:NUL", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "DAC:AD2:NUL:AD1", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "DAC:NUL:DAC:AD2", "TDM AD2:DAC" }, + { "TDM Data Mux", "DAC:NUL:AD2:DAC", "TDM AD2:DAC" }, + { "TDM Data Mux", "NUL:AD1:AD2:DAC", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "NUL:AD1:DAC:AD2", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "NUL:AD2:AD1:DAC", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "NUL:AD2:DAC:AD1", "TDM AD1:AD2:DAC" }, + { "TDM Data Mux", "NUL:DAC:DAC:AD2", "TDM AD2:DAC" }, + { "TDM Data Mux", "NUL:DAC:AD2:DAC", "TDM AD2:DAC" }, + { "IF1 01 ADC Swap Mux", "L/R", "TDM Data Mux" }, + { "IF1 01 ADC Swap Mux", "R/L", "TDM Data Mux" }, + { "IF1 01 ADC Swap Mux", "L/L", "TDM Data Mux" }, + { "IF1 01 ADC Swap Mux", "R/R", "TDM Data Mux" }, + { "IF1 23 ADC Swap Mux", "L/R", "TDM Data Mux" }, + { "IF1 23 ADC Swap Mux", "R/L", "TDM Data Mux" }, + { "IF1 23 ADC Swap Mux", "L/L", "TDM Data Mux" }, + { "IF1 23 ADC Swap Mux", "R/R", "TDM Data Mux" }, + { "IF1 45 ADC Swap Mux", "L/R", "TDM Data Mux" }, + { "IF1 45 ADC Swap Mux", "R/L", "TDM Data Mux" }, + { "IF1 45 ADC Swap Mux", "L/L", "TDM Data Mux" }, + { "IF1 45 ADC Swap Mux", "R/R", "TDM Data Mux" }, + { "IF1 67 ADC Swap Mux", "L/R", "TDM Data Mux" }, + { "IF1 67 ADC Swap Mux", "R/L", "TDM Data Mux" }, + { "IF1 67 ADC Swap Mux", "L/L", "TDM Data Mux" }, + { "IF1 67 ADC Swap Mux", "R/R", "TDM Data Mux" }, + { "IF1 ADC", NULL, "IF1 01 ADC Swap Mux" }, + { "IF1 ADC", NULL, "IF1 23 ADC Swap Mux" }, + { "IF1 ADC", NULL, "IF1 45 ADC Swap Mux" }, + { "IF1 ADC", NULL, "IF1 67 ADC Swap Mux" }, + { "IF1 ADC", NULL, "I2S1" }, + + { "IF2 ADC Mux", "IF_ADC1", "IF_ADC1" }, + { "IF2 ADC Mux", "IF_ADC2", "IF_ADC2" }, + { "IF2 ADC Mux", "IF_ADC3", "IF_ADC3" }, + { "IF2 ADC Mux", "DAC_REF", "DAC_REF" }, + { "IF2 ADC", NULL, "IF2 ADC Mux"}, + { "IF2 ADC", NULL, "I2S2" }, + + { "IF3 ADC Mux", "IF_ADC1", "IF_ADC1" }, + { "IF3 ADC Mux", "IF_ADC2", "IF_ADC2" }, + { "IF3 ADC Mux", "Stereo2_ADC_L/R", "Stereo2 ADC LR" }, + { "IF3 ADC Mux", "DAC_REF", "DAC_REF" }, + { "IF3 ADC", NULL, "IF3 ADC Mux"}, + { "IF3 ADC", NULL, "I2S3" }, + + { "AIF1TX", NULL, "IF1 ADC" }, + { "IF2 ADC Swap Mux", "L/R", "IF2 ADC" }, + { "IF2 ADC Swap Mux", "R/L", "IF2 ADC" }, + { "IF2 ADC Swap Mux", "L/L", "IF2 ADC" }, + { "IF2 ADC Swap Mux", "R/R", "IF2 ADC" }, + { "AIF2TX", NULL, "IF2 ADC Swap Mux" }, + { "IF3 ADC Swap Mux", "L/R", "IF3 ADC" }, + { "IF3 ADC Swap Mux", "R/L", "IF3 ADC" }, + { "IF3 ADC Swap Mux", "L/L", "IF3 ADC" }, + { "IF3 ADC Swap Mux", "R/R", "IF3 ADC" }, + { "AIF3TX", NULL, "IF3 ADC Swap Mux" }, + + { "IF1 DAC1", NULL, "AIF1RX" }, + { "IF1 DAC2", NULL, "AIF1RX" }, + { "IF2 DAC Swap Mux", "L/R", "AIF2RX" }, + { "IF2 DAC Swap Mux", "R/L", "AIF2RX" }, + { "IF2 DAC Swap Mux", "L/L", "AIF2RX" }, + { "IF2 DAC Swap Mux", "R/R", "AIF2RX" }, + { "IF2 DAC", NULL, "IF2 DAC Swap Mux" }, + { "IF3 DAC Swap Mux", "L/R", "AIF3RX" }, + { "IF3 DAC Swap Mux", "R/L", "AIF3RX" }, + { "IF3 DAC Swap Mux", "L/L", "AIF3RX" }, + { "IF3 DAC Swap Mux", "R/R", "AIF3RX" }, + { "IF3 DAC", NULL, "IF3 DAC Swap Mux" }, + + { "IF1 DAC1", NULL, "I2S1" }, + { "IF1 DAC2", NULL, "I2S1" }, + { "IF2 DAC", NULL, "I2S2" }, + { "IF3 DAC", NULL, "I2S3" }, + + { "IF1 DAC2 L", NULL, "IF1 DAC2" }, + { "IF1 DAC2 R", NULL, "IF1 DAC2" }, + { "IF1 DAC1 L", NULL, "IF1 DAC1" }, + { "IF1 DAC1 R", NULL, "IF1 DAC1" }, + { "IF2 DAC L", NULL, "IF2 DAC" }, + { "IF2 DAC R", NULL, "IF2 DAC" }, + { "IF3 DAC L", NULL, "IF3 DAC" }, + { "IF3 DAC R", NULL, "IF3 DAC" }, + + { "DAC L1 Mux", "IF1 DAC1", "IF1 DAC1 L" }, + { "DAC L1 Mux", "IF2 DAC", "IF2 DAC L" }, + { "DAC L1 Mux", "IF3 DAC", "IF3 DAC L" }, + { "DAC L1 Mux", NULL, "DAC Stereo1 Filter" }, + + { "DAC R1 Mux", "IF1 DAC1", "IF1 DAC1 R" }, + { "DAC R1 Mux", "IF2 DAC", "IF2 DAC R" }, + { "DAC R1 Mux", "IF3 DAC", "IF3 DAC R" }, + { "DAC R1 Mux", NULL, "DAC Stereo1 Filter" }, + + { "DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC Volume L" }, + { "DAC1 MIXL", "DAC1 Switch", "DAC L1 Mux" }, + { "DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC Volume R" }, + { "DAC1 MIXR", "DAC1 Switch", "DAC R1 Mux" }, + + { "DAC_REF", NULL, "DAC1 MIXL" }, + { "DAC_REF", NULL, "DAC1 MIXR" }, + + { "DAC L2 Mux", "IF1 DAC2", "IF1 DAC2 L" }, + { "DAC L2 Mux", "IF2 DAC", "IF2 DAC L" }, + { "DAC L2 Mux", "IF3 DAC", "IF3 DAC L" }, + { "DAC L2 Mux", "Mono ADC MIX", "Mono ADC MIXL" }, + { "DAC L2 Mux", NULL, "DAC Mono Left Filter" }, + + { "DAC R2 Mux", "IF1 DAC2", "IF1 DAC2 R" }, + { "DAC R2 Mux", "IF2 DAC", "IF2 DAC R" }, + { "DAC R2 Mux", "IF3 DAC", "IF3 DAC R" }, + { "DAC R2 Mux", "Mono ADC MIX", "Mono ADC MIXR" }, + { "DAC R2 Mux", NULL, "DAC Mono Right Filter" }, + + { "Stereo DAC MIXL", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXL", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXL", "DAC L2 Switch", "DAC L2 Mux" }, + { "Stereo DAC MIXL", "DAC R2 Switch", "DAC R2 Mux" }, + + { "Stereo DAC MIXR", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXR", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXR", "DAC L2 Switch", "DAC L2 Mux" }, + { "Stereo DAC MIXR", "DAC R2 Switch", "DAC R2 Mux" }, + + { "Mono DAC MIXL", "DAC L1 Switch", "DAC1 MIXL" }, + { "Mono DAC MIXL", "DAC R1 Switch", "DAC1 MIXR" }, + { "Mono DAC MIXL", "DAC L2 Switch", "DAC L2 Mux" }, + { "Mono DAC MIXL", "DAC R2 Switch", "DAC R2 Mux" }, + { "Mono DAC MIXR", "DAC L1 Switch", "DAC1 MIXL" }, + { "Mono DAC MIXR", "DAC R1 Switch", "DAC1 MIXR" }, + { "Mono DAC MIXR", "DAC R2 Switch", "DAC R2 Mux" }, + { "Mono DAC MIXR", "DAC L2 Switch", "DAC L2 Mux" }, + + { "DAC MIXL", "Stereo DAC Mixer", "Stereo DAC MIXL" }, + { "DAC MIXL", "Mono DAC Mixer", "Mono DAC MIXL" }, + { "DAC MIXR", "Stereo DAC Mixer", "Stereo DAC MIXR" }, + { "DAC MIXR", "Mono DAC Mixer", "Mono DAC MIXR" }, + + { "DAC L1 Source", NULL, "DAC L1 Power" }, + { "DAC L1 Source", "DAC", "DAC1 MIXL" }, + { "DAC L1 Source", "Stereo DAC Mixer", "Stereo DAC MIXL" }, + { "DAC R1 Source", NULL, "DAC R1 Power" }, + { "DAC R1 Source", "DAC", "DAC1 MIXR" }, + { "DAC R1 Source", "Stereo DAC Mixer", "Stereo DAC MIXR" }, + { "DAC L2 Source", "Stereo DAC Mixer", "Stereo DAC MIXL" }, + { "DAC L2 Source", "Mono DAC Mixer", "Mono DAC MIXL" }, + { "DAC L2 Source", NULL, "DAC L2 Power" }, + { "DAC R2 Source", "Stereo DAC Mixer", "Stereo DAC MIXR" }, + { "DAC R2 Source", "Mono DAC Mixer", "Mono DAC MIXR" }, + { "DAC R2 Source", NULL, "DAC R2 Power" }, + + { "DAC L1", NULL, "DAC L1 Source" }, + { "DAC R1", NULL, "DAC R1 Source" }, + { "DAC L2", NULL, "DAC L2 Source" }, + { "DAC R2", NULL, "DAC R2 Source" }, + + { "SPK MIXL", "DAC L2 Switch", "DAC L2" }, + { "SPK MIXL", "BST1 Switch", "BST1" }, + { "SPK MIXL", "INL Switch", "INL VOL" }, + { "SPK MIXL", "INR Switch", "INR VOL" }, + { "SPK MIXL", "BST3 Switch", "BST3" }, + { "SPK MIXR", "DAC R2 Switch", "DAC R2" }, + { "SPK MIXR", "BST4 Switch", "BST4" }, + { "SPK MIXR", "INL Switch", "INL VOL" }, + { "SPK MIXR", "INR Switch", "INR VOL" }, + { "SPK MIXR", "BST3 Switch", "BST3" }, + + { "MONOVOL MIX", "DAC L2 Switch", "DAC L2" }, + { "MONOVOL MIX", "DAC R2 Switch", "DAC R2" }, + { "MONOVOL MIX", "BST1 Switch", "BST1" }, + { "MONOVOL MIX", "BST2 Switch", "BST2" }, + { "MONOVOL MIX", "BST3 Switch", "BST3" }, + + { "OUT MIXL", "DAC L2 Switch", "DAC L2" }, + { "OUT MIXL", "INL Switch", "INL VOL" }, + { "OUT MIXL", "BST1 Switch", "BST1" }, + { "OUT MIXL", "BST2 Switch", "BST2" }, + { "OUT MIXL", "BST3 Switch", "BST3" }, + { "OUT MIXR", "DAC R2 Switch", "DAC R2" }, + { "OUT MIXR", "INR Switch", "INR VOL" }, + { "OUT MIXR", "BST2 Switch", "BST2" }, + { "OUT MIXR", "BST3 Switch", "BST3" }, + { "OUT MIXR", "BST4 Switch", "BST4" }, + + { "SPKVOL L", "Switch", "SPK MIXL" }, + { "SPKVOL R", "Switch", "SPK MIXR" }, + { "SPO L MIX", "DAC L2 Switch", "DAC L2" }, + { "SPO L MIX", "SPKVOL L Switch", "SPKVOL L" }, + { "SPO R MIX", "DAC R2 Switch", "DAC R2" }, + { "SPO R MIX", "SPKVOL R Switch", "SPKVOL R" }, + { "SPK Amp", NULL, "SPO L MIX" }, + { "SPK Amp", NULL, "SPO R MIX" }, + { "SPK Amp", NULL, "SYS CLK DET" }, + { "SPO Playback", "Switch", "SPK Amp" }, + { "SPOL", NULL, "SPO Playback" }, + { "SPOR", NULL, "SPO Playback" }, + + { "MONOVOL", "Switch", "MONOVOL MIX" }, + { "Mono MIX", "DAC L2 Switch", "DAC L2" }, + { "Mono MIX", "MONOVOL Switch", "MONOVOL" }, + { "Mono Amp", NULL, "Mono MIX" }, + { "Mono Amp", NULL, "Mono Vref" }, + { "Mono Amp", NULL, "SYS CLK DET" }, + { "Mono Playback", "Switch", "Mono Amp" }, + { "MONOOUT", NULL, "Mono Playback" }, + + { "HP Amp", NULL, "DAC L1" }, + { "HP Amp", NULL, "DAC R1" }, + { "HP Amp", NULL, "Charge Pump" }, + { "HP Amp", NULL, "SYS CLK DET" }, + { "HPO L Playback", "Switch", "HP Amp"}, + { "HPO R Playback", "Switch", "HP Amp"}, + { "HPOL", NULL, "HPO L Playback" }, + { "HPOR", NULL, "HPO R Playback" }, + + { "OUTVOL L", "Switch", "OUT MIXL" }, + { "OUTVOL R", "Switch", "OUT MIXR" }, + { "LOUT L MIX", "DAC L2 Switch", "DAC L2" }, + { "LOUT L MIX", "OUTVOL L Switch", "OUTVOL L" }, + { "LOUT R MIX", "DAC R2 Switch", "DAC R2" }, + { "LOUT R MIX", "OUTVOL R Switch", "OUTVOL R" }, + { "LOUT Amp", NULL, "LOUT L MIX" }, + { "LOUT Amp", NULL, "LOUT R MIX" }, + { "LOUT Amp", NULL, "Charge Pump" }, + { "LOUT Amp", NULL, "SYS CLK DET" }, + { "LOUT L Playback", "Switch", "LOUT Amp" }, + { "LOUT R Playback", "Switch", "LOUT Amp" }, + { "LOUTL", NULL, "LOUT L Playback" }, + { "LOUTR", NULL, "LOUT R Playback" }, + + { "PDM L Mux", "Mono DAC", "Mono DAC MIXL" }, + { "PDM L Mux", "Stereo DAC", "Stereo DAC MIXL" }, + { "PDM L Mux", NULL, "PDM Power" }, + { "PDM R Mux", "Mono DAC", "Mono DAC MIXR" }, + { "PDM R Mux", "Stereo DAC", "Stereo DAC MIXR" }, + { "PDM R Mux", NULL, "PDM Power" }, + { "PDM L Playback", "Switch", "PDM L Mux" }, + { "PDM R Playback", "Switch", "PDM R Mux" }, + { "PDML", NULL, "PDM L Playback" }, + { "PDMR", NULL, "PDM R Playback" }, + + { "SPDIF Mux", "IF3_DAC", "IF3 DAC" }, + { "SPDIF Mux", "IF2_DAC", "IF2 DAC" }, + { "SPDIF Mux", "IF1_DAC2", "IF1 DAC2" }, + { "SPDIF Mux", "IF1_DAC1", "IF1 DAC1" }, + { "SPDIF", NULL, "SPDIF Mux" }, +}; + +static int rt5659_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int pre_div, frame_size; + + rt5659->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5659->sysclk, rt5659->lrck[dai->id]); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting %d for DAI %d\n", + rt5659->lrck[dai->id], dai->id); + return -EINVAL; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + + dev_dbg(dai->dev, "lrck is %dHz and pre_div is %d for iis %d\n", + rt5659->lrck[dai->id], pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len |= RT5659_I2S_DL_20; + break; + case 24: + val_len |= RT5659_I2S_DL_24; + break; + case 8: + val_len |= RT5659_I2S_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5659_AIF1: + mask_clk = RT5659_I2S_PD1_MASK; + val_clk = pre_div << RT5659_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5659_I2S1_SDP, + RT5659_I2S_DL_MASK, val_len); + break; + case RT5659_AIF2: + mask_clk = RT5659_I2S_PD2_MASK; + val_clk = pre_div << RT5659_I2S_PD2_SFT; + snd_soc_component_update_bits(component, RT5659_I2S2_SDP, + RT5659_I2S_DL_MASK, val_len); + break; + case RT5659_AIF3: + mask_clk = RT5659_I2S_PD3_MASK; + val_clk = pre_div << RT5659_I2S_PD3_SFT; + snd_soc_component_update_bits(component, RT5659_I2S3_SDP, + RT5659_I2S_DL_MASK, val_len); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5659_ADDA_CLK_1, mask_clk, val_clk); + + switch (rt5659->lrck[dai->id]) { + case 192000: + snd_soc_component_update_bits(component, RT5659_ADDA_CLK_1, + RT5659_DAC_OSR_MASK, RT5659_DAC_OSR_32); + break; + case 96000: + snd_soc_component_update_bits(component, RT5659_ADDA_CLK_1, + RT5659_DAC_OSR_MASK, RT5659_DAC_OSR_64); + break; + default: + snd_soc_component_update_bits(component, RT5659_ADDA_CLK_1, + RT5659_DAC_OSR_MASK, RT5659_DAC_OSR_128); + break; + } + + return 0; +} + +static int rt5659_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5659->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5659_I2S_MS_S; + rt5659->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5659_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5659_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5659_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5659_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5659_AIF1: + snd_soc_component_update_bits(component, RT5659_I2S1_SDP, + RT5659_I2S_MS_MASK | RT5659_I2S_BP_MASK | + RT5659_I2S_DF_MASK, reg_val); + break; + case RT5659_AIF2: + snd_soc_component_update_bits(component, RT5659_I2S2_SDP, + RT5659_I2S_MS_MASK | RT5659_I2S_BP_MASK | + RT5659_I2S_DF_MASK, reg_val); + break; + case RT5659_AIF3: + snd_soc_component_update_bits(component, RT5659_I2S3_SDP, + RT5659_I2S_MS_MASK | RT5659_I2S_BP_MASK | + RT5659_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5659_set_component_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + int ret; + + if (freq == rt5659->sysclk && clk_id == rt5659->sysclk_src) + return 0; + + switch (clk_id) { + case RT5659_SCLK_S_MCLK: + ret = clk_set_rate(rt5659->mclk, freq); + if (ret) + return ret; + + reg_val |= RT5659_SCLK_SRC_MCLK; + break; + case RT5659_SCLK_S_PLL1: + reg_val |= RT5659_SCLK_SRC_PLL1; + break; + case RT5659_SCLK_S_RCCLK: + reg_val |= RT5659_SCLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5659_GLB_CLK, + RT5659_SCLK_SRC_MASK, reg_val); + rt5659->sysclk = freq; + rt5659->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; +} + +static int rt5659_set_component_pll(struct snd_soc_component *component, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5659->pll_src && freq_in == rt5659->pll_in && + freq_out == rt5659->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5659->pll_in = 0; + rt5659->pll_out = 0; + snd_soc_component_update_bits(component, RT5659_GLB_CLK, + RT5659_SCLK_SRC_MASK, RT5659_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5659_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5659_GLB_CLK, + RT5659_PLL1_SRC_MASK, RT5659_PLL1_SRC_MCLK); + break; + case RT5659_PLL1_S_BCLK1: + snd_soc_component_update_bits(component, RT5659_GLB_CLK, + RT5659_PLL1_SRC_MASK, RT5659_PLL1_SRC_BCLK1); + break; + case RT5659_PLL1_S_BCLK2: + snd_soc_component_update_bits(component, RT5659_GLB_CLK, + RT5659_PLL1_SRC_MASK, RT5659_PLL1_SRC_BCLK2); + break; + case RT5659_PLL1_S_BCLK3: + snd_soc_component_update_bits(component, RT5659_GLB_CLK, + RT5659_PLL1_SRC_MASK, RT5659_PLL1_SRC_BCLK3); + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5659_PLL_CTRL_1, + pll_code.n_code << RT5659_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5659_PLL_CTRL_2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5659_PLL_M_SFT | + pll_code.m_bp << RT5659_PLL_M_BP_SFT); + + rt5659->pll_in = freq_in; + rt5659->pll_out = freq_out; + rt5659->pll_src = source; + + return 0; +} + +static int rt5659_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val = 0; + + if (rx_mask || tx_mask) + val |= (1 << 15); + + switch (slots) { + case 4: + val |= (1 << 10); + val |= (1 << 8); + break; + case 6: + val |= (2 << 10); + val |= (2 << 8); + break; + case 8: + val |= (3 << 10); + val |= (3 << 8); + break; + case 2: + break; + default: + return -EINVAL; + } + + switch (slot_width) { + case 20: + val |= (1 << 6); + val |= (1 << 4); + break; + case 24: + val |= (2 << 6); + val |= (2 << 4); + break; + case 32: + val |= (3 << 6); + val |= (3 << 4); + break; + case 16: + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5659_TDM_CTRL_1, 0x8ff0, val); + + return 0; +} + +static int rt5659_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s ratio=%d\n", __func__, ratio); + + rt5659->bclk[dai->id] = ratio; + + if (ratio == 64) { + switch (dai->id) { + case RT5659_AIF2: + snd_soc_component_update_bits(component, RT5659_ADDA_CLK_1, + RT5659_I2S_BCLK_MS2_MASK, + RT5659_I2S_BCLK_MS2_64); + break; + case RT5659_AIF3: + snd_soc_component_update_bits(component, RT5659_ADDA_CLK_1, + RT5659_I2S_BCLK_MS3_MASK, + RT5659_I2S_BCLK_MS3_64); + break; + } + } + + return 0; +} + +static int rt5659_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(rt5659->regmap, RT5659_DIG_MISC, + RT5659_DIG_GATE_CTRL, RT5659_DIG_GATE_CTRL); + regmap_update_bits(rt5659->regmap, RT5659_PWR_DIG_1, + RT5659_PWR_LDO, RT5659_PWR_LDO); + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_1, + RT5659_PWR_MB | RT5659_PWR_VREF1 | RT5659_PWR_VREF2, + RT5659_PWR_MB | RT5659_PWR_VREF1 | RT5659_PWR_VREF2); + msleep(20); + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_1, + RT5659_PWR_FV1 | RT5659_PWR_FV2, + RT5659_PWR_FV1 | RT5659_PWR_FV2); + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_OFF) { + ret = clk_prepare_enable(rt5659->mclk); + if (ret) { + dev_err(component->dev, + "failed to enable MCLK: %d\n", ret); + return ret; + } + } + break; + + case SND_SOC_BIAS_OFF: + regmap_update_bits(rt5659->regmap, RT5659_PWR_DIG_1, + RT5659_PWR_LDO, 0); + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_1, + RT5659_PWR_MB | RT5659_PWR_VREF1 | RT5659_PWR_VREF2 + | RT5659_PWR_FV1 | RT5659_PWR_FV2, + RT5659_PWR_MB | RT5659_PWR_VREF2); + regmap_update_bits(rt5659->regmap, RT5659_DIG_MISC, + RT5659_DIG_GATE_CTRL, 0); + clk_disable_unprepare(rt5659->mclk); + break; + + default: + break; + } + + return 0; +} + +static int rt5659_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + + rt5659->component = component; + + switch (rt5659->pdata.jd_src) { + case RT5659_JD_HDA_HEADER: + break; + + default: + snd_soc_dapm_new_controls(dapm, + rt5659_particular_dapm_widgets, + ARRAY_SIZE(rt5659_particular_dapm_widgets)); + break; + } + + return 0; +} + +static void rt5659_remove(struct snd_soc_component *component) +{ + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + + regmap_write(rt5659->regmap, RT5659_RESET, 0); +} + +#ifdef CONFIG_PM +static int rt5659_suspend(struct snd_soc_component *component) +{ + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5659->regmap, true); + regcache_mark_dirty(rt5659->regmap); + return 0; +} + +static int rt5659_resume(struct snd_soc_component *component) +{ + struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5659->regmap, false); + regcache_sync(rt5659->regmap); + + return 0; +} +#else +#define rt5659_suspend NULL +#define rt5659_resume NULL +#endif + +#define RT5659_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5659_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5659_aif_dai_ops = { + .hw_params = rt5659_hw_params, + .set_fmt = rt5659_set_dai_fmt, + .set_tdm_slot = rt5659_set_tdm_slot, + .set_bclk_ratio = rt5659_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt5659_dai[] = { + { + .name = "rt5659-aif1", + .id = RT5659_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5659_STEREO_RATES, + .formats = RT5659_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5659_STEREO_RATES, + .formats = RT5659_FORMATS, + }, + .ops = &rt5659_aif_dai_ops, + }, + { + .name = "rt5659-aif2", + .id = RT5659_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5659_STEREO_RATES, + .formats = RT5659_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5659_STEREO_RATES, + .formats = RT5659_FORMATS, + }, + .ops = &rt5659_aif_dai_ops, + }, + { + .name = "rt5659-aif3", + .id = RT5659_AIF3, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5659_STEREO_RATES, + .formats = RT5659_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5659_STEREO_RATES, + .formats = RT5659_FORMATS, + }, + .ops = &rt5659_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5659 = { + .probe = rt5659_probe, + .remove = rt5659_remove, + .suspend = rt5659_suspend, + .resume = rt5659_resume, + .set_bias_level = rt5659_set_bias_level, + .controls = rt5659_snd_controls, + .num_controls = ARRAY_SIZE(rt5659_snd_controls), + .dapm_widgets = rt5659_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5659_dapm_widgets), + .dapm_routes = rt5659_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5659_dapm_routes), + .set_sysclk = rt5659_set_component_sysclk, + .set_pll = rt5659_set_component_pll, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + + +static const struct regmap_config rt5659_regmap = { + .reg_bits = 16, + .val_bits = 16, + .max_register = 0x0400, + .volatile_reg = rt5659_volatile_register, + .readable_reg = rt5659_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5659_reg, + .num_reg_defaults = ARRAY_SIZE(rt5659_reg), +}; + +static const struct i2c_device_id rt5659_i2c_id[] = { + { "rt5658", 0 }, + { "rt5659", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5659_i2c_id); + +static int rt5659_parse_dt(struct rt5659_priv *rt5659, struct device *dev) +{ + rt5659->pdata.in1_diff = device_property_read_bool(dev, + "realtek,in1-differential"); + rt5659->pdata.in3_diff = device_property_read_bool(dev, + "realtek,in3-differential"); + rt5659->pdata.in4_diff = device_property_read_bool(dev, + "realtek,in4-differential"); + + + device_property_read_u32(dev, "realtek,dmic1-data-pin", + &rt5659->pdata.dmic1_data_pin); + device_property_read_u32(dev, "realtek,dmic2-data-pin", + &rt5659->pdata.dmic2_data_pin); + device_property_read_u32(dev, "realtek,jd-src", + &rt5659->pdata.jd_src); + + return 0; +} + +static void rt5659_calibrate(struct rt5659_priv *rt5659) +{ + int value, count; + + /* Calibrate HPO Start */ + /* Fine tune HP Performance */ + regmap_write(rt5659->regmap, RT5659_BIAS_CUR_CTRL_8, 0xa502); + regmap_write(rt5659->regmap, RT5659_CHOP_DAC, 0x3030); + + regmap_write(rt5659->regmap, RT5659_PRE_DIV_1, 0xef00); + regmap_write(rt5659->regmap, RT5659_PRE_DIV_2, 0xeffc); + regmap_write(rt5659->regmap, RT5659_MICBIAS_2, 0x0280); + regmap_write(rt5659->regmap, RT5659_DIG_MISC, 0x0001); + regmap_write(rt5659->regmap, RT5659_GLB_CLK, 0x8000); + + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_1, 0xaa7e); + msleep(60); + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_1, 0xfe7e); + msleep(50); + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_3, 0x0004); + regmap_write(rt5659->regmap, RT5659_PWR_DIG_2, 0x0400); + msleep(50); + regmap_write(rt5659->regmap, RT5659_PWR_DIG_1, 0x0080); + usleep_range(10000, 10005); + regmap_write(rt5659->regmap, RT5659_DEPOP_1, 0x0009); + msleep(50); + regmap_write(rt5659->regmap, RT5659_PWR_DIG_1, 0x0f80); + msleep(50); + regmap_write(rt5659->regmap, RT5659_HP_CHARGE_PUMP_1, 0x0e16); + msleep(50); + + /* Enalbe K ADC Power And Clock */ + regmap_write(rt5659->regmap, RT5659_CAL_REC, 0x0505); + msleep(50); + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_3, 0x0184); + regmap_write(rt5659->regmap, RT5659_CALIB_ADC_CTRL, 0x3c05); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_2, 0x20c1); + + /* K Headphone */ + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_2, 0x2cc1); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, 0x5100); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_7, 0x0014); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, 0xd100); + msleep(60); + + /* Manual K ADC Offset */ + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_2, 0x2cc1); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, 0x4900); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_7, 0x0016); + regmap_update_bits(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, + 0x8000, 0x8000); + + count = 0; + while (true) { + regmap_read(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 30) { + dev_err(rt5659->component->dev, + "HP Calibration 1 Failure\n"); + return; + } + + count++; + } + + /* Manual K Internal Path Offset */ + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_2, 0x2cc1); + regmap_write(rt5659->regmap, RT5659_HP_VOL, 0x0000); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, 0x4500); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_7, 0x001f); + regmap_update_bits(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, + 0x8000, 0x8000); + + count = 0; + while (true) { + regmap_read(rt5659->regmap, RT5659_HP_CALIB_CTRL_1, &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 85) { + dev_err(rt5659->component->dev, + "HP Calibration 2 Failure\n"); + return; + } + + count++; + } + + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_7, 0x0000); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_2, 0x20c0); + /* Calibrate HPO End */ + + /* Calibrate SPO Start */ + regmap_write(rt5659->regmap, RT5659_CLASSD_0, 0x2021); + regmap_write(rt5659->regmap, RT5659_CLASSD_CTRL_1, 0x0260); + regmap_write(rt5659->regmap, RT5659_PWR_MIXER, 0x3000); + regmap_write(rt5659->regmap, RT5659_PWR_VOL, 0xc000); + regmap_write(rt5659->regmap, RT5659_A_DAC_MUX, 0x000c); + regmap_write(rt5659->regmap, RT5659_DIG_MISC, 0x8000); + regmap_write(rt5659->regmap, RT5659_SPO_VOL, 0x0808); + regmap_write(rt5659->regmap, RT5659_SPK_L_MIXER, 0x001e); + regmap_write(rt5659->regmap, RT5659_SPK_R_MIXER, 0x001e); + regmap_write(rt5659->regmap, RT5659_CLASSD_1, 0x0803); + regmap_write(rt5659->regmap, RT5659_CLASSD_2, 0x0554); + regmap_write(rt5659->regmap, RT5659_SPO_AMP_GAIN, 0x1103); + + /* Enalbe K ADC Power And Clock */ + regmap_write(rt5659->regmap, RT5659_CAL_REC, 0x0909); + regmap_update_bits(rt5659->regmap, RT5659_HP_CALIB_CTRL_2, 0x0001, + 0x0001); + + /* Start Calibration */ + regmap_write(rt5659->regmap, RT5659_SPK_DC_CAILB_CTRL_3, 0x0000); + regmap_write(rt5659->regmap, RT5659_CLASSD_0, 0x0021); + regmap_write(rt5659->regmap, RT5659_SPK_DC_CAILB_CTRL_1, 0x3e80); + regmap_update_bits(rt5659->regmap, RT5659_SPK_DC_CAILB_CTRL_1, + 0x8000, 0x8000); + + count = 0; + while (true) { + regmap_read(rt5659->regmap, + RT5659_SPK_DC_CAILB_CTRL_1, &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 10) { + dev_err(rt5659->component->dev, + "SPK Calibration Failure\n"); + return; + } + + count++; + } + /* Calibrate SPO End */ + + /* Calibrate MONO Start */ + regmap_write(rt5659->regmap, RT5659_DIG_MISC, 0x0000); + regmap_write(rt5659->regmap, RT5659_MONOMIX_IN_GAIN, 0x021f); + regmap_write(rt5659->regmap, RT5659_MONO_OUT, 0x480a); + /* MONO NG2 GAIN 5dB */ + regmap_write(rt5659->regmap, RT5659_MONO_GAIN, 0x0003); + regmap_write(rt5659->regmap, RT5659_MONO_NG2_CTRL_5, 0x0009); + + /* Start Calibration */ + regmap_write(rt5659->regmap, RT5659_SPK_DC_CAILB_CTRL_3, 0x000f); + regmap_write(rt5659->regmap, RT5659_MONO_AMP_CALIB_CTRL_1, 0x1e00); + regmap_update_bits(rt5659->regmap, RT5659_MONO_AMP_CALIB_CTRL_1, + 0x8000, 0x8000); + + count = 0; + while (true) { + regmap_read(rt5659->regmap, RT5659_MONO_AMP_CALIB_CTRL_1, + &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 35) { + dev_err(rt5659->component->dev, + "Mono Calibration Failure\n"); + return; + } + + count++; + } + + regmap_write(rt5659->regmap, RT5659_SPK_DC_CAILB_CTRL_3, 0x0003); + /* Calibrate MONO End */ + + /* Power Off */ + regmap_write(rt5659->regmap, RT5659_CAL_REC, 0x0808); + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_3, 0x0000); + regmap_write(rt5659->regmap, RT5659_CALIB_ADC_CTRL, 0x2005); + regmap_write(rt5659->regmap, RT5659_HP_CALIB_CTRL_2, 0x20c0); + regmap_write(rt5659->regmap, RT5659_DEPOP_1, 0x0000); + regmap_write(rt5659->regmap, RT5659_CLASSD_1, 0x0011); + regmap_write(rt5659->regmap, RT5659_CLASSD_2, 0x0150); + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_1, 0xfe3e); + regmap_write(rt5659->regmap, RT5659_MONO_OUT, 0xc80a); + regmap_write(rt5659->regmap, RT5659_MONO_AMP_CALIB_CTRL_1, 0x1e04); + regmap_write(rt5659->regmap, RT5659_PWR_MIXER, 0x0000); + regmap_write(rt5659->regmap, RT5659_PWR_VOL, 0x0000); + regmap_write(rt5659->regmap, RT5659_PWR_DIG_1, 0x0000); + regmap_write(rt5659->regmap, RT5659_PWR_DIG_2, 0x0000); + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_1, 0x003e); + regmap_write(rt5659->regmap, RT5659_CLASSD_CTRL_1, 0x0060); + regmap_write(rt5659->regmap, RT5659_CLASSD_0, 0x2021); + regmap_write(rt5659->regmap, RT5659_GLB_CLK, 0x0000); + regmap_write(rt5659->regmap, RT5659_MICBIAS_2, 0x0080); + regmap_write(rt5659->regmap, RT5659_HP_VOL, 0x8080); + regmap_write(rt5659->regmap, RT5659_HP_CHARGE_PUMP_1, 0x0c16); +} + +static void rt5659_intel_hd_header_probe_setup(struct rt5659_priv *rt5659) +{ + int value; + + regmap_read(rt5659->regmap, RT5659_GPIO_STA, &value); + if (!(value & 0x8)) { + rt5659->hda_hp_plugged = true; + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_1, + 0x10, 0x0); + } else { + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_1, + 0x10, 0x10); + } + + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_1, + RT5659_PWR_VREF2 | RT5659_PWR_MB, + RT5659_PWR_VREF2 | RT5659_PWR_MB); + msleep(20); + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_1, + RT5659_PWR_FV2, RT5659_PWR_FV2); + + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_3, RT5659_PWR_LDO2, + RT5659_PWR_LDO2); + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_2, RT5659_PWR_MB1, + RT5659_PWR_MB1); + regmap_update_bits(rt5659->regmap, RT5659_PWR_VOL, RT5659_PWR_MIC_DET, + RT5659_PWR_MIC_DET); + msleep(20); + + regmap_update_bits(rt5659->regmap, RT5659_4BTN_IL_CMD_2, + RT5659_4BTN_IL_MASK, RT5659_4BTN_IL_EN); + regmap_read(rt5659->regmap, RT5659_4BTN_IL_CMD_1, &value); + regmap_write(rt5659->regmap, RT5659_4BTN_IL_CMD_1, value); + regmap_read(rt5659->regmap, RT5659_4BTN_IL_CMD_1, &value); + + if (value & 0x2000) { + rt5659->hda_mic_plugged = true; + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_2, + 0x2, 0x2); + } else { + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_2, + 0x2, 0x0); + } + + regmap_update_bits(rt5659->regmap, RT5659_IRQ_CTRL_2, + RT5659_IL_IRQ_MASK, RT5659_IL_IRQ_EN); +} + +static int rt5659_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5659_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5659_priv *rt5659; + int ret; + unsigned int val; + + rt5659 = devm_kzalloc(&i2c->dev, sizeof(struct rt5659_priv), + GFP_KERNEL); + + if (rt5659 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5659); + + if (pdata) + rt5659->pdata = *pdata; + else + rt5659_parse_dt(rt5659, &i2c->dev); + + rt5659->gpiod_ldo1_en = devm_gpiod_get_optional(&i2c->dev, "ldo1-en", + GPIOD_OUT_HIGH); + if (IS_ERR(rt5659->gpiod_ldo1_en)) + dev_warn(&i2c->dev, "Request ldo1-en GPIO failed\n"); + + rt5659->gpiod_reset = devm_gpiod_get_optional(&i2c->dev, "reset", + GPIOD_OUT_HIGH); + + /* Sleep for 300 ms miniumum */ + msleep(300); + + rt5659->regmap = devm_regmap_init_i2c(i2c, &rt5659_regmap); + if (IS_ERR(rt5659->regmap)) { + ret = PTR_ERR(rt5659->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt5659->regmap, RT5659_DEVICE_ID, &val); + if (val != DEVICE_ID) { + dev_err(&i2c->dev, + "Device with ID register %x is not rt5659\n", val); + return -ENODEV; + } + + regmap_write(rt5659->regmap, RT5659_RESET, 0); + + /* Check if MCLK provided */ + rt5659->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(rt5659->mclk)) { + if (PTR_ERR(rt5659->mclk) != -ENOENT) + return PTR_ERR(rt5659->mclk); + /* Otherwise mark the mclk pointer to NULL */ + rt5659->mclk = NULL; + } + + rt5659_calibrate(rt5659); + + /* line in diff mode*/ + if (rt5659->pdata.in1_diff) + regmap_update_bits(rt5659->regmap, RT5659_IN1_IN2, + RT5659_IN1_DF_MASK, RT5659_IN1_DF_MASK); + if (rt5659->pdata.in3_diff) + regmap_update_bits(rt5659->regmap, RT5659_IN3_IN4, + RT5659_IN3_DF_MASK, RT5659_IN3_DF_MASK); + if (rt5659->pdata.in4_diff) + regmap_update_bits(rt5659->regmap, RT5659_IN3_IN4, + RT5659_IN4_DF_MASK, RT5659_IN4_DF_MASK); + + /* DMIC pin*/ + if (rt5659->pdata.dmic1_data_pin != RT5659_DMIC1_NULL || + rt5659->pdata.dmic2_data_pin != RT5659_DMIC2_NULL) { + regmap_update_bits(rt5659->regmap, RT5659_GPIO_CTRL_1, + RT5659_GP2_PIN_MASK, RT5659_GP2_PIN_DMIC1_SCL); + + switch (rt5659->pdata.dmic1_data_pin) { + case RT5659_DMIC1_DATA_IN2N: + regmap_update_bits(rt5659->regmap, RT5659_DMIC_CTRL_1, + RT5659_DMIC_1_DP_MASK, RT5659_DMIC_1_DP_IN2N); + break; + + case RT5659_DMIC1_DATA_GPIO5: + regmap_update_bits(rt5659->regmap, + RT5659_GPIO_CTRL_3, + RT5659_I2S2_PIN_MASK, + RT5659_I2S2_PIN_GPIO); + regmap_update_bits(rt5659->regmap, RT5659_DMIC_CTRL_1, + RT5659_DMIC_1_DP_MASK, RT5659_DMIC_1_DP_GPIO5); + regmap_update_bits(rt5659->regmap, RT5659_GPIO_CTRL_1, + RT5659_GP5_PIN_MASK, RT5659_GP5_PIN_DMIC1_SDA); + break; + + case RT5659_DMIC1_DATA_GPIO9: + regmap_update_bits(rt5659->regmap, RT5659_DMIC_CTRL_1, + RT5659_DMIC_1_DP_MASK, RT5659_DMIC_1_DP_GPIO9); + regmap_update_bits(rt5659->regmap, RT5659_GPIO_CTRL_1, + RT5659_GP9_PIN_MASK, RT5659_GP9_PIN_DMIC1_SDA); + break; + + case RT5659_DMIC1_DATA_GPIO11: + regmap_update_bits(rt5659->regmap, RT5659_DMIC_CTRL_1, + RT5659_DMIC_1_DP_MASK, RT5659_DMIC_1_DP_GPIO11); + regmap_update_bits(rt5659->regmap, RT5659_GPIO_CTRL_1, + RT5659_GP11_PIN_MASK, + RT5659_GP11_PIN_DMIC1_SDA); + break; + + default: + dev_dbg(&i2c->dev, "no DMIC1\n"); + break; + } + + switch (rt5659->pdata.dmic2_data_pin) { + case RT5659_DMIC2_DATA_IN2P: + regmap_update_bits(rt5659->regmap, + RT5659_DMIC_CTRL_1, + RT5659_DMIC_2_DP_MASK, + RT5659_DMIC_2_DP_IN2P); + break; + + case RT5659_DMIC2_DATA_GPIO6: + regmap_update_bits(rt5659->regmap, + RT5659_DMIC_CTRL_1, + RT5659_DMIC_2_DP_MASK, + RT5659_DMIC_2_DP_GPIO6); + regmap_update_bits(rt5659->regmap, + RT5659_GPIO_CTRL_1, + RT5659_GP6_PIN_MASK, + RT5659_GP6_PIN_DMIC2_SDA); + break; + + case RT5659_DMIC2_DATA_GPIO10: + regmap_update_bits(rt5659->regmap, + RT5659_DMIC_CTRL_1, + RT5659_DMIC_2_DP_MASK, + RT5659_DMIC_2_DP_GPIO10); + regmap_update_bits(rt5659->regmap, + RT5659_GPIO_CTRL_1, + RT5659_GP10_PIN_MASK, + RT5659_GP10_PIN_DMIC2_SDA); + break; + + case RT5659_DMIC2_DATA_GPIO12: + regmap_update_bits(rt5659->regmap, + RT5659_DMIC_CTRL_1, + RT5659_DMIC_2_DP_MASK, + RT5659_DMIC_2_DP_GPIO12); + regmap_update_bits(rt5659->regmap, + RT5659_GPIO_CTRL_1, + RT5659_GP12_PIN_MASK, + RT5659_GP12_PIN_DMIC2_SDA); + break; + + default: + dev_dbg(&i2c->dev, "no DMIC2\n"); + break; + + } + } else { + regmap_update_bits(rt5659->regmap, RT5659_GPIO_CTRL_1, + RT5659_GP2_PIN_MASK | RT5659_GP5_PIN_MASK | + RT5659_GP9_PIN_MASK | RT5659_GP11_PIN_MASK | + RT5659_GP6_PIN_MASK | RT5659_GP10_PIN_MASK | + RT5659_GP12_PIN_MASK, + RT5659_GP2_PIN_GPIO2 | RT5659_GP5_PIN_GPIO5 | + RT5659_GP9_PIN_GPIO9 | RT5659_GP11_PIN_GPIO11 | + RT5659_GP6_PIN_GPIO6 | RT5659_GP10_PIN_GPIO10 | + RT5659_GP12_PIN_GPIO12); + regmap_update_bits(rt5659->regmap, RT5659_DMIC_CTRL_1, + RT5659_DMIC_1_DP_MASK | RT5659_DMIC_2_DP_MASK, + RT5659_DMIC_1_DP_IN2N | RT5659_DMIC_2_DP_IN2P); + } + + switch (rt5659->pdata.jd_src) { + case RT5659_JD3: + regmap_write(rt5659->regmap, RT5659_EJD_CTRL_1, 0xa880); + regmap_write(rt5659->regmap, RT5659_RC_CLK_CTRL, 0x9000); + regmap_write(rt5659->regmap, RT5659_GPIO_CTRL_1, 0xc800); + regmap_update_bits(rt5659->regmap, RT5659_PWR_ANLG_1, + RT5659_PWR_MB, RT5659_PWR_MB); + regmap_write(rt5659->regmap, RT5659_PWR_ANLG_2, 0x0001); + regmap_write(rt5659->regmap, RT5659_IRQ_CTRL_2, 0x0040); + INIT_DELAYED_WORK(&rt5659->jack_detect_work, + rt5659_jack_detect_work); + break; + case RT5659_JD_HDA_HEADER: + regmap_write(rt5659->regmap, RT5659_GPIO_CTRL_3, 0x8000); + regmap_write(rt5659->regmap, RT5659_RC_CLK_CTRL, 0x0900); + regmap_write(rt5659->regmap, RT5659_EJD_CTRL_1, 0x70c0); + regmap_write(rt5659->regmap, RT5659_JD_CTRL_1, 0x2000); + regmap_write(rt5659->regmap, RT5659_IRQ_CTRL_1, 0x0040); + INIT_DELAYED_WORK(&rt5659->jack_detect_work, + rt5659_jack_detect_intel_hd_header); + rt5659_intel_hd_header_probe_setup(rt5659); + break; + default: + break; + } + + if (i2c->irq) { + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + rt5659_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5659", rt5659); + if (ret) + dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); + + /* Enable IRQ output for GPIO1 pin any way */ + regmap_update_bits(rt5659->regmap, RT5659_GPIO_CTRL_1, + RT5659_GP1_PIN_MASK, RT5659_GP1_PIN_IRQ); + } + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5659, + rt5659_dai, ARRAY_SIZE(rt5659_dai)); +} + +static void rt5659_i2c_shutdown(struct i2c_client *client) +{ + struct rt5659_priv *rt5659 = i2c_get_clientdata(client); + + regmap_write(rt5659->regmap, RT5659_RESET, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id rt5659_of_match[] = { + { .compatible = "realtek,rt5658", }, + { .compatible = "realtek,rt5659", }, + { }, +}; +MODULE_DEVICE_TABLE(of, rt5659_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5659_acpi_match[] = { + { "10EC5658", 0, }, + { "10EC5659", 0, }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, rt5659_acpi_match); +#endif + +static struct i2c_driver rt5659_i2c_driver = { + .driver = { + .name = "rt5659", + .of_match_table = of_match_ptr(rt5659_of_match), + .acpi_match_table = ACPI_PTR(rt5659_acpi_match), + }, + .probe = rt5659_i2c_probe, + .shutdown = rt5659_i2c_shutdown, + .id_table = rt5659_i2c_id, +}; +module_i2c_driver(rt5659_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5659 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5659.h b/sound/soc/codecs/rt5659.h new file mode 100644 index 000000000..8b576d768 --- /dev/null +++ b/sound/soc/codecs/rt5659.h @@ -0,0 +1,1824 @@ +/* + * rt5659.h -- RT5659/RT5658 ALSA SoC audio driver + * + * Copyright 2015 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5659_H__ +#define __RT5659_H__ + +#include + +#define DEVICE_ID 0x6311 + +/* Info */ +#define RT5659_RESET 0x0000 +#define RT5659_VENDOR_ID 0x00fd +#define RT5659_VENDOR_ID_1 0x00fe +#define RT5659_DEVICE_ID 0x00ff +/* I/O - Output */ +#define RT5659_SPO_VOL 0x0001 +#define RT5659_HP_VOL 0x0002 +#define RT5659_LOUT 0x0003 +#define RT5659_MONO_OUT 0x0004 +#define RT5659_HPL_GAIN 0x0005 +#define RT5659_HPR_GAIN 0x0006 +#define RT5659_MONO_GAIN 0x0007 +#define RT5659_SPDIF_CTRL_1 0x0008 +#define RT5659_SPDIF_CTRL_2 0x0009 +/* I/O - Input */ +#define RT5659_CAL_BST_CTRL 0x000a +#define RT5659_IN1_IN2 0x000c +#define RT5659_IN3_IN4 0x000d +#define RT5659_INL1_INR1_VOL 0x000f +/* I/O - Speaker */ +#define RT5659_EJD_CTRL_1 0x0010 +#define RT5659_EJD_CTRL_2 0x0011 +#define RT5659_EJD_CTRL_3 0x0012 +#define RT5659_SILENCE_CTRL 0x0015 +#define RT5659_PSV_CTRL 0x0016 +/* I/O - Sidetone */ +#define RT5659_SIDETONE_CTRL 0x0018 +/* I/O - ADC/DAC/DMIC */ +#define RT5659_DAC1_DIG_VOL 0x0019 +#define RT5659_DAC2_DIG_VOL 0x001a +#define RT5659_DAC_CTRL 0x001b +#define RT5659_STO1_ADC_DIG_VOL 0x001c +#define RT5659_MONO_ADC_DIG_VOL 0x001d +#define RT5659_STO2_ADC_DIG_VOL 0x001e +#define RT5659_STO1_BOOST 0x001f +#define RT5659_MONO_BOOST 0x0020 +#define RT5659_STO2_BOOST 0x0021 +#define RT5659_HP_IMP_GAIN_1 0x0022 +#define RT5659_HP_IMP_GAIN_2 0x0023 +/* Mixer - D-D */ +#define RT5659_STO1_ADC_MIXER 0x0026 +#define RT5659_MONO_ADC_MIXER 0x0027 +#define RT5659_AD_DA_MIXER 0x0029 +#define RT5659_STO_DAC_MIXER 0x002a +#define RT5659_MONO_DAC_MIXER 0x002b +#define RT5659_DIG_MIXER 0x002c +#define RT5659_A_DAC_MUX 0x002d +#define RT5659_DIG_INF23_DATA 0x002f +/* Mixer - PDM */ +#define RT5659_PDM_OUT_CTRL 0x0031 +#define RT5659_PDM_DATA_CTRL_1 0x0032 +#define RT5659_PDM_DATA_CTRL_2 0x0033 +#define RT5659_PDM_DATA_CTRL_3 0x0034 +#define RT5659_PDM_DATA_CTRL_4 0x0035 +#define RT5659_SPDIF_CTRL 0x0036 + +/* Mixer - ADC */ +#define RT5659_REC1_GAIN 0x003a +#define RT5659_REC1_L1_MIXER 0x003b +#define RT5659_REC1_L2_MIXER 0x003c +#define RT5659_REC1_R1_MIXER 0x003d +#define RT5659_REC1_R2_MIXER 0x003e +#define RT5659_CAL_REC 0x0040 +#define RT5659_REC2_L1_MIXER 0x009b +#define RT5659_REC2_L2_MIXER 0x009c +#define RT5659_REC2_R1_MIXER 0x009d +#define RT5659_REC2_R2_MIXER 0x009e +#define RT5659_RC_CLK_CTRL 0x009f +/* Mixer - DAC */ +#define RT5659_SPK_L_MIXER 0x0046 +#define RT5659_SPK_R_MIXER 0x0047 +#define RT5659_SPO_AMP_GAIN 0x0048 +#define RT5659_ALC_BACK_GAIN 0x0049 +#define RT5659_MONOMIX_GAIN 0x004a +#define RT5659_MONOMIX_IN_GAIN 0x004b +#define RT5659_OUT_L_GAIN 0x004d +#define RT5659_OUT_L_MIXER 0x004e +#define RT5659_OUT_R_GAIN 0x004f +#define RT5659_OUT_R_MIXER 0x0050 +#define RT5659_LOUT_MIXER 0x0052 + +#define RT5659_HAPTIC_GEN_CTRL_1 0x0053 +#define RT5659_HAPTIC_GEN_CTRL_2 0x0054 +#define RT5659_HAPTIC_GEN_CTRL_3 0x0055 +#define RT5659_HAPTIC_GEN_CTRL_4 0x0056 +#define RT5659_HAPTIC_GEN_CTRL_5 0x0057 +#define RT5659_HAPTIC_GEN_CTRL_6 0x0058 +#define RT5659_HAPTIC_GEN_CTRL_7 0x0059 +#define RT5659_HAPTIC_GEN_CTRL_8 0x005a +#define RT5659_HAPTIC_GEN_CTRL_9 0x005b +#define RT5659_HAPTIC_GEN_CTRL_10 0x005c +#define RT5659_HAPTIC_GEN_CTRL_11 0x005d +#define RT5659_HAPTIC_LPF_CTRL_1 0x005e +#define RT5659_HAPTIC_LPF_CTRL_2 0x005f +#define RT5659_HAPTIC_LPF_CTRL_3 0x0060 +/* Power */ +#define RT5659_PWR_DIG_1 0x0061 +#define RT5659_PWR_DIG_2 0x0062 +#define RT5659_PWR_ANLG_1 0x0063 +#define RT5659_PWR_ANLG_2 0x0064 +#define RT5659_PWR_ANLG_3 0x0065 +#define RT5659_PWR_MIXER 0x0066 +#define RT5659_PWR_VOL 0x0067 +/* Private Register Control */ +#define RT5659_PRIV_INDEX 0x006a +#define RT5659_CLK_DET 0x006b +#define RT5659_PRIV_DATA 0x006c +/* System Clock Pre Divider Gating Control */ +#define RT5659_PRE_DIV_1 0x006e +#define RT5659_PRE_DIV_2 0x006f +/* Format - ADC/DAC */ +#define RT5659_I2S1_SDP 0x0070 +#define RT5659_I2S2_SDP 0x0071 +#define RT5659_I2S3_SDP 0x0072 +#define RT5659_ADDA_CLK_1 0x0073 +#define RT5659_ADDA_CLK_2 0x0074 +#define RT5659_DMIC_CTRL_1 0x0075 +#define RT5659_DMIC_CTRL_2 0x0076 +/* Format - TDM Control */ +#define RT5659_TDM_CTRL_1 0x0077 +#define RT5659_TDM_CTRL_2 0x0078 +#define RT5659_TDM_CTRL_3 0x0079 +#define RT5659_TDM_CTRL_4 0x007a +#define RT5659_TDM_CTRL_5 0x007b + +/* Function - Analog */ +#define RT5659_GLB_CLK 0x0080 +#define RT5659_PLL_CTRL_1 0x0081 +#define RT5659_PLL_CTRL_2 0x0082 +#define RT5659_ASRC_1 0x0083 +#define RT5659_ASRC_2 0x0084 +#define RT5659_ASRC_3 0x0085 +#define RT5659_ASRC_4 0x0086 +#define RT5659_ASRC_5 0x0087 +#define RT5659_ASRC_6 0x0088 +#define RT5659_ASRC_7 0x0089 +#define RT5659_ASRC_8 0x008a +#define RT5659_ASRC_9 0x008b +#define RT5659_ASRC_10 0x008c +#define RT5659_DEPOP_1 0x008e +#define RT5659_DEPOP_2 0x008f +#define RT5659_DEPOP_3 0x0090 +#define RT5659_HP_CHARGE_PUMP_1 0x0091 +#define RT5659_HP_CHARGE_PUMP_2 0x0092 +#define RT5659_MICBIAS_1 0x0093 +#define RT5659_MICBIAS_2 0x0094 +#define RT5659_ASRC_11 0x0097 +#define RT5659_ASRC_12 0x0098 +#define RT5659_ASRC_13 0x0099 +#define RT5659_REC_M1_M2_GAIN_CTRL 0x009a +#define RT5659_CLASSD_CTRL_1 0x00a0 +#define RT5659_CLASSD_CTRL_2 0x00a1 + +/* Function - Digital */ +#define RT5659_ADC_EQ_CTRL_1 0x00ae +#define RT5659_ADC_EQ_CTRL_2 0x00af +#define RT5659_DAC_EQ_CTRL_1 0x00b0 +#define RT5659_DAC_EQ_CTRL_2 0x00b1 +#define RT5659_DAC_EQ_CTRL_3 0x00b2 + +#define RT5659_IRQ_CTRL_1 0x00b6 +#define RT5659_IRQ_CTRL_2 0x00b7 +#define RT5659_IRQ_CTRL_3 0x00b8 +#define RT5659_IRQ_CTRL_4 0x00ba +#define RT5659_IRQ_CTRL_5 0x00bb +#define RT5659_IRQ_CTRL_6 0x00bc +#define RT5659_INT_ST_1 0x00be +#define RT5659_INT_ST_2 0x00bf +#define RT5659_GPIO_CTRL_1 0x00c0 +#define RT5659_GPIO_CTRL_2 0x00c1 +#define RT5659_GPIO_CTRL_3 0x00c2 +#define RT5659_GPIO_CTRL_4 0x00c3 +#define RT5659_GPIO_CTRL_5 0x00c4 +#define RT5659_GPIO_STA 0x00c5 +#define RT5659_SINE_GEN_CTRL_1 0x00cb +#define RT5659_SINE_GEN_CTRL_2 0x00cc +#define RT5659_SINE_GEN_CTRL_3 0x00cd +#define RT5659_HP_AMP_DET_CTRL_1 0x00d6 +#define RT5659_HP_AMP_DET_CTRL_2 0x00d7 +#define RT5659_SV_ZCD_1 0x00d9 +#define RT5659_SV_ZCD_2 0x00da +#define RT5659_IL_CMD_1 0x00db +#define RT5659_IL_CMD_2 0x00dc +#define RT5659_IL_CMD_3 0x00dd +#define RT5659_IL_CMD_4 0x00de +#define RT5659_4BTN_IL_CMD_1 0x00df +#define RT5659_4BTN_IL_CMD_2 0x00e0 +#define RT5659_4BTN_IL_CMD_3 0x00e1 +#define RT5659_PSV_IL_CMD_1 0x00e4 +#define RT5659_PSV_IL_CMD_2 0x00e5 + +#define RT5659_ADC_STO1_HP_CTRL_1 0x00ea +#define RT5659_ADC_STO1_HP_CTRL_2 0x00eb +#define RT5659_ADC_MONO_HP_CTRL_1 0x00ec +#define RT5659_ADC_MONO_HP_CTRL_2 0x00ed +#define RT5659_AJD1_CTRL 0x00f0 +#define RT5659_AJD2_AJD3_CTRL 0x00f1 +#define RT5659_JD1_THD 0x00f2 +#define RT5659_JD2_THD 0x00f3 +#define RT5659_JD3_THD 0x00f4 +#define RT5659_JD_CTRL_1 0x00f6 +#define RT5659_JD_CTRL_2 0x00f7 +#define RT5659_JD_CTRL_3 0x00f8 +#define RT5659_JD_CTRL_4 0x00f9 +/* General Control */ +#define RT5659_DIG_MISC 0x00fa +#define RT5659_DUMMY_2 0x00fb +#define RT5659_DUMMY_3 0x00fc + +#define RT5659_DAC_ADC_DIG_VOL 0x0100 +#define RT5659_BIAS_CUR_CTRL_1 0x010a +#define RT5659_BIAS_CUR_CTRL_2 0x010b +#define RT5659_BIAS_CUR_CTRL_3 0x010c +#define RT5659_BIAS_CUR_CTRL_4 0x010d +#define RT5659_BIAS_CUR_CTRL_5 0x010e +#define RT5659_BIAS_CUR_CTRL_6 0x010f +#define RT5659_BIAS_CUR_CTRL_7 0x0110 +#define RT5659_BIAS_CUR_CTRL_8 0x0111 +#define RT5659_BIAS_CUR_CTRL_9 0x0112 +#define RT5659_BIAS_CUR_CTRL_10 0x0113 +#define RT5659_MEMORY_TEST 0x0116 +#define RT5659_VREF_REC_OP_FB_CAP_CTRL 0x0117 +#define RT5659_CLASSD_0 0x011a +#define RT5659_CLASSD_1 0x011b +#define RT5659_CLASSD_2 0x011c +#define RT5659_CLASSD_3 0x011d +#define RT5659_CLASSD_4 0x011e +#define RT5659_CLASSD_5 0x011f +#define RT5659_CLASSD_6 0x0120 +#define RT5659_CLASSD_7 0x0121 +#define RT5659_CLASSD_8 0x0122 +#define RT5659_CLASSD_9 0x0123 +#define RT5659_CLASSD_10 0x0124 +#define RT5659_CHARGE_PUMP_1 0x0125 +#define RT5659_CHARGE_PUMP_2 0x0126 +#define RT5659_DIG_IN_CTRL_1 0x0132 +#define RT5659_DIG_IN_CTRL_2 0x0133 +#define RT5659_PAD_DRIVING_CTRL 0x0137 +#define RT5659_SOFT_RAMP_DEPOP 0x0138 +#define RT5659_PLL 0x0139 +#define RT5659_CHOP_DAC 0x013a +#define RT5659_CHOP_ADC 0x013b +#define RT5659_CALIB_ADC_CTRL 0x013c +#define RT5659_SOFT_RAMP_DEPOP_DAC_CLK_CTRL 0x013e +#define RT5659_VOL_TEST 0x013f +#define RT5659_TEST_MODE_CTRL_1 0x0145 +#define RT5659_TEST_MODE_CTRL_2 0x0146 +#define RT5659_TEST_MODE_CTRL_3 0x0147 +#define RT5659_TEST_MODE_CTRL_4 0x0148 +#define RT5659_BASSBACK_CTRL 0x0150 +#define RT5659_MP3_PLUS_CTRL_1 0x0151 +#define RT5659_MP3_PLUS_CTRL_2 0x0152 +#define RT5659_MP3_HPF_A1 0x0153 +#define RT5659_MP3_HPF_A2 0x0154 +#define RT5659_MP3_HPF_H0 0x0155 +#define RT5659_MP3_LPF_H0 0x0156 +#define RT5659_3D_SPK_CTRL 0x0157 +#define RT5659_3D_SPK_COEF_1 0x0158 +#define RT5659_3D_SPK_COEF_2 0x0159 +#define RT5659_3D_SPK_COEF_3 0x015a +#define RT5659_3D_SPK_COEF_4 0x015b +#define RT5659_3D_SPK_COEF_5 0x015c +#define RT5659_3D_SPK_COEF_6 0x015d +#define RT5659_3D_SPK_COEF_7 0x015e +#define RT5659_STO_NG2_CTRL_1 0x0160 +#define RT5659_STO_NG2_CTRL_2 0x0161 +#define RT5659_STO_NG2_CTRL_3 0x0162 +#define RT5659_STO_NG2_CTRL_4 0x0163 +#define RT5659_STO_NG2_CTRL_5 0x0164 +#define RT5659_STO_NG2_CTRL_6 0x0165 +#define RT5659_STO_NG2_CTRL_7 0x0166 +#define RT5659_STO_NG2_CTRL_8 0x0167 +#define RT5659_MONO_NG2_CTRL_1 0x0170 +#define RT5659_MONO_NG2_CTRL_2 0x0171 +#define RT5659_MONO_NG2_CTRL_3 0x0172 +#define RT5659_MONO_NG2_CTRL_4 0x0173 +#define RT5659_MONO_NG2_CTRL_5 0x0174 +#define RT5659_MONO_NG2_CTRL_6 0x0175 +#define RT5659_MID_HP_AMP_DET 0x0190 +#define RT5659_LOW_HP_AMP_DET 0x0191 +#define RT5659_LDO_CTRL 0x0192 +#define RT5659_HP_DECROSS_CTRL_1 0x01b0 +#define RT5659_HP_DECROSS_CTRL_2 0x01b1 +#define RT5659_HP_DECROSS_CTRL_3 0x01b2 +#define RT5659_HP_DECROSS_CTRL_4 0x01b3 +#define RT5659_HP_IMP_SENS_CTRL_1 0x01c0 +#define RT5659_HP_IMP_SENS_CTRL_2 0x01c1 +#define RT5659_HP_IMP_SENS_CTRL_3 0x01c2 +#define RT5659_HP_IMP_SENS_CTRL_4 0x01c3 +#define RT5659_HP_IMP_SENS_MAP_1 0x01c7 +#define RT5659_HP_IMP_SENS_MAP_2 0x01c8 +#define RT5659_HP_IMP_SENS_MAP_3 0x01c9 +#define RT5659_HP_IMP_SENS_MAP_4 0x01ca +#define RT5659_HP_IMP_SENS_MAP_5 0x01cb +#define RT5659_HP_IMP_SENS_MAP_6 0x01cc +#define RT5659_HP_IMP_SENS_MAP_7 0x01cd +#define RT5659_HP_IMP_SENS_MAP_8 0x01ce +#define RT5659_HP_LOGIC_CTRL_1 0x01da +#define RT5659_HP_LOGIC_CTRL_2 0x01db +#define RT5659_HP_CALIB_CTRL_1 0x01de +#define RT5659_HP_CALIB_CTRL_2 0x01df +#define RT5659_HP_CALIB_CTRL_3 0x01e0 +#define RT5659_HP_CALIB_CTRL_4 0x01e1 +#define RT5659_HP_CALIB_CTRL_5 0x01e2 +#define RT5659_HP_CALIB_CTRL_6 0x01e3 +#define RT5659_HP_CALIB_CTRL_7 0x01e4 +#define RT5659_HP_CALIB_CTRL_9 0x01e6 +#define RT5659_HP_CALIB_CTRL_10 0x01e7 +#define RT5659_HP_CALIB_CTRL_11 0x01e8 +#define RT5659_HP_CALIB_STA_1 0x01ea +#define RT5659_HP_CALIB_STA_2 0x01eb +#define RT5659_HP_CALIB_STA_3 0x01ec +#define RT5659_HP_CALIB_STA_4 0x01ed +#define RT5659_HP_CALIB_STA_5 0x01ee +#define RT5659_HP_CALIB_STA_6 0x01ef +#define RT5659_HP_CALIB_STA_7 0x01f0 +#define RT5659_HP_CALIB_STA_8 0x01f1 +#define RT5659_HP_CALIB_STA_9 0x01f2 +#define RT5659_MONO_AMP_CALIB_CTRL_1 0x01f6 +#define RT5659_MONO_AMP_CALIB_CTRL_2 0x01f7 +#define RT5659_MONO_AMP_CALIB_CTRL_3 0x01f8 +#define RT5659_MONO_AMP_CALIB_CTRL_4 0x01f9 +#define RT5659_MONO_AMP_CALIB_CTRL_5 0x01fa +#define RT5659_MONO_AMP_CALIB_STA_1 0x01fb +#define RT5659_MONO_AMP_CALIB_STA_2 0x01fc +#define RT5659_MONO_AMP_CALIB_STA_3 0x01fd +#define RT5659_MONO_AMP_CALIB_STA_4 0x01fe +#define RT5659_SPK_PWR_LMT_CTRL_1 0x0200 +#define RT5659_SPK_PWR_LMT_CTRL_2 0x0201 +#define RT5659_SPK_PWR_LMT_CTRL_3 0x0202 +#define RT5659_SPK_PWR_LMT_STA_1 0x0203 +#define RT5659_SPK_PWR_LMT_STA_2 0x0204 +#define RT5659_SPK_PWR_LMT_STA_3 0x0205 +#define RT5659_SPK_PWR_LMT_STA_4 0x0206 +#define RT5659_SPK_PWR_LMT_STA_5 0x0207 +#define RT5659_SPK_PWR_LMT_STA_6 0x0208 +#define RT5659_FLEX_SPK_BST_CTRL_1 0x0256 +#define RT5659_FLEX_SPK_BST_CTRL_2 0x0257 +#define RT5659_FLEX_SPK_BST_CTRL_3 0x0258 +#define RT5659_FLEX_SPK_BST_CTRL_4 0x0259 +#define RT5659_SPK_EX_LMT_CTRL_1 0x025a +#define RT5659_SPK_EX_LMT_CTRL_2 0x025b +#define RT5659_SPK_EX_LMT_CTRL_3 0x025c +#define RT5659_SPK_EX_LMT_CTRL_4 0x025d +#define RT5659_SPK_EX_LMT_CTRL_5 0x025e +#define RT5659_SPK_EX_LMT_CTRL_6 0x025f +#define RT5659_SPK_EX_LMT_CTRL_7 0x0260 +#define RT5659_ADJ_HPF_CTRL_1 0x0261 +#define RT5659_ADJ_HPF_CTRL_2 0x0262 +#define RT5659_SPK_DC_CAILB_CTRL_1 0x0265 +#define RT5659_SPK_DC_CAILB_CTRL_2 0x0266 +#define RT5659_SPK_DC_CAILB_CTRL_3 0x0267 +#define RT5659_SPK_DC_CAILB_CTRL_4 0x0268 +#define RT5659_SPK_DC_CAILB_CTRL_5 0x0269 +#define RT5659_SPK_DC_CAILB_STA_1 0x026a +#define RT5659_SPK_DC_CAILB_STA_2 0x026b +#define RT5659_SPK_DC_CAILB_STA_3 0x026c +#define RT5659_SPK_DC_CAILB_STA_4 0x026d +#define RT5659_SPK_DC_CAILB_STA_5 0x026e +#define RT5659_SPK_DC_CAILB_STA_6 0x026f +#define RT5659_SPK_DC_CAILB_STA_7 0x0270 +#define RT5659_SPK_DC_CAILB_STA_8 0x0271 +#define RT5659_SPK_DC_CAILB_STA_9 0x0272 +#define RT5659_SPK_DC_CAILB_STA_10 0x0273 +#define RT5659_SPK_VDD_STA_1 0x0280 +#define RT5659_SPK_VDD_STA_2 0x0281 +#define RT5659_SPK_DC_DET_CTRL_1 0x0282 +#define RT5659_SPK_DC_DET_CTRL_2 0x0283 +#define RT5659_SPK_DC_DET_CTRL_3 0x0284 +#define RT5659_PURE_DC_DET_CTRL_1 0x0290 +#define RT5659_PURE_DC_DET_CTRL_2 0x0291 +#define RT5659_DUMMY_4 0x02fa +#define RT5659_DUMMY_5 0x02fb +#define RT5659_DUMMY_6 0x02fc +#define RT5659_DRC1_CTRL_1 0x0300 +#define RT5659_DRC1_CTRL_2 0x0301 +#define RT5659_DRC1_CTRL_3 0x0302 +#define RT5659_DRC1_CTRL_4 0x0303 +#define RT5659_DRC1_CTRL_5 0x0304 +#define RT5659_DRC1_CTRL_6 0x0305 +#define RT5659_DRC1_HARD_LMT_CTRL_1 0x0306 +#define RT5659_DRC1_HARD_LMT_CTRL_2 0x0307 +#define RT5659_DRC2_CTRL_1 0x0308 +#define RT5659_DRC2_CTRL_2 0x0309 +#define RT5659_DRC2_CTRL_3 0x030a +#define RT5659_DRC2_CTRL_4 0x030b +#define RT5659_DRC2_CTRL_5 0x030c +#define RT5659_DRC2_CTRL_6 0x030d +#define RT5659_DRC2_HARD_LMT_CTRL_1 0x030e +#define RT5659_DRC2_HARD_LMT_CTRL_2 0x030f +#define RT5659_DRC1_PRIV_1 0x0310 +#define RT5659_DRC1_PRIV_2 0x0311 +#define RT5659_DRC1_PRIV_3 0x0312 +#define RT5659_DRC1_PRIV_4 0x0313 +#define RT5659_DRC1_PRIV_5 0x0314 +#define RT5659_DRC1_PRIV_6 0x0315 +#define RT5659_DRC1_PRIV_7 0x0316 +#define RT5659_DRC2_PRIV_1 0x0317 +#define RT5659_DRC2_PRIV_2 0x0318 +#define RT5659_DRC2_PRIV_3 0x0319 +#define RT5659_DRC2_PRIV_4 0x031a +#define RT5659_DRC2_PRIV_5 0x031b +#define RT5659_DRC2_PRIV_6 0x031c +#define RT5659_DRC2_PRIV_7 0x031d +#define RT5659_MULTI_DRC_CTRL 0x0320 +#define RT5659_CROSS_OVER_1 0x0321 +#define RT5659_CROSS_OVER_2 0x0322 +#define RT5659_CROSS_OVER_3 0x0323 +#define RT5659_CROSS_OVER_4 0x0324 +#define RT5659_CROSS_OVER_5 0x0325 +#define RT5659_CROSS_OVER_6 0x0326 +#define RT5659_CROSS_OVER_7 0x0327 +#define RT5659_CROSS_OVER_8 0x0328 +#define RT5659_CROSS_OVER_9 0x0329 +#define RT5659_CROSS_OVER_10 0x032a +#define RT5659_ALC_PGA_CTRL_1 0x0330 +#define RT5659_ALC_PGA_CTRL_2 0x0331 +#define RT5659_ALC_PGA_CTRL_3 0x0332 +#define RT5659_ALC_PGA_CTRL_4 0x0333 +#define RT5659_ALC_PGA_CTRL_5 0x0334 +#define RT5659_ALC_PGA_CTRL_6 0x0335 +#define RT5659_ALC_PGA_CTRL_7 0x0336 +#define RT5659_ALC_PGA_CTRL_8 0x0337 +#define RT5659_ALC_PGA_STA_1 0x0338 +#define RT5659_ALC_PGA_STA_2 0x0339 +#define RT5659_ALC_PGA_STA_3 0x033a +#define RT5659_DAC_L_EQ_PRE_VOL 0x0340 +#define RT5659_DAC_R_EQ_PRE_VOL 0x0341 +#define RT5659_DAC_L_EQ_POST_VOL 0x0342 +#define RT5659_DAC_R_EQ_POST_VOL 0x0343 +#define RT5659_DAC_L_EQ_LPF1_A1 0x0344 +#define RT5659_DAC_L_EQ_LPF1_H0 0x0345 +#define RT5659_DAC_R_EQ_LPF1_A1 0x0346 +#define RT5659_DAC_R_EQ_LPF1_H0 0x0347 +#define RT5659_DAC_L_EQ_BPF2_A1 0x0348 +#define RT5659_DAC_L_EQ_BPF2_A2 0x0349 +#define RT5659_DAC_L_EQ_BPF2_H0 0x034a +#define RT5659_DAC_R_EQ_BPF2_A1 0x034b +#define RT5659_DAC_R_EQ_BPF2_A2 0x034c +#define RT5659_DAC_R_EQ_BPF2_H0 0x034d +#define RT5659_DAC_L_EQ_BPF3_A1 0x034e +#define RT5659_DAC_L_EQ_BPF3_A2 0x034f +#define RT5659_DAC_L_EQ_BPF3_H0 0x0350 +#define RT5659_DAC_R_EQ_BPF3_A1 0x0351 +#define RT5659_DAC_R_EQ_BPF3_A2 0x0352 +#define RT5659_DAC_R_EQ_BPF3_H0 0x0353 +#define RT5659_DAC_L_EQ_BPF4_A1 0x0354 +#define RT5659_DAC_L_EQ_BPF4_A2 0x0355 +#define RT5659_DAC_L_EQ_BPF4_H0 0x0356 +#define RT5659_DAC_R_EQ_BPF4_A1 0x0357 +#define RT5659_DAC_R_EQ_BPF4_A2 0x0358 +#define RT5659_DAC_R_EQ_BPF4_H0 0x0359 +#define RT5659_DAC_L_EQ_HPF1_A1 0x035a +#define RT5659_DAC_L_EQ_HPF1_H0 0x035b +#define RT5659_DAC_R_EQ_HPF1_A1 0x035c +#define RT5659_DAC_R_EQ_HPF1_H0 0x035d +#define RT5659_DAC_L_EQ_HPF2_A1 0x035e +#define RT5659_DAC_L_EQ_HPF2_A2 0x035f +#define RT5659_DAC_L_EQ_HPF2_H0 0x0360 +#define RT5659_DAC_R_EQ_HPF2_A1 0x0361 +#define RT5659_DAC_R_EQ_HPF2_A2 0x0362 +#define RT5659_DAC_R_EQ_HPF2_H0 0x0363 +#define RT5659_DAC_L_BI_EQ_BPF1_H0_1 0x0364 +#define RT5659_DAC_L_BI_EQ_BPF1_H0_2 0x0365 +#define RT5659_DAC_L_BI_EQ_BPF1_B1_1 0x0366 +#define RT5659_DAC_L_BI_EQ_BPF1_B1_2 0x0367 +#define RT5659_DAC_L_BI_EQ_BPF1_B2_1 0x0368 +#define RT5659_DAC_L_BI_EQ_BPF1_B2_2 0x0369 +#define RT5659_DAC_L_BI_EQ_BPF1_A1_1 0x036a +#define RT5659_DAC_L_BI_EQ_BPF1_A1_2 0x036b +#define RT5659_DAC_L_BI_EQ_BPF1_A2_1 0x036c +#define RT5659_DAC_L_BI_EQ_BPF1_A2_2 0x036d +#define RT5659_DAC_R_BI_EQ_BPF1_H0_1 0x036e +#define RT5659_DAC_R_BI_EQ_BPF1_H0_2 0x036f +#define RT5659_DAC_R_BI_EQ_BPF1_B1_1 0x0370 +#define RT5659_DAC_R_BI_EQ_BPF1_B1_2 0x0371 +#define RT5659_DAC_R_BI_EQ_BPF1_B2_1 0x0372 +#define RT5659_DAC_R_BI_EQ_BPF1_B2_2 0x0373 +#define RT5659_DAC_R_BI_EQ_BPF1_A1_1 0x0374 +#define RT5659_DAC_R_BI_EQ_BPF1_A1_2 0x0375 +#define RT5659_DAC_R_BI_EQ_BPF1_A2_1 0x0376 +#define RT5659_DAC_R_BI_EQ_BPF1_A2_2 0x0377 +#define RT5659_ADC_L_EQ_LPF1_A1 0x03d0 +#define RT5659_ADC_R_EQ_LPF1_A1 0x03d1 +#define RT5659_ADC_L_EQ_LPF1_H0 0x03d2 +#define RT5659_ADC_R_EQ_LPF1_H0 0x03d3 +#define RT5659_ADC_L_EQ_BPF1_A1 0x03d4 +#define RT5659_ADC_R_EQ_BPF1_A1 0x03d5 +#define RT5659_ADC_L_EQ_BPF1_A2 0x03d6 +#define RT5659_ADC_R_EQ_BPF1_A2 0x03d7 +#define RT5659_ADC_L_EQ_BPF1_H0 0x03d8 +#define RT5659_ADC_R_EQ_BPF1_H0 0x03d9 +#define RT5659_ADC_L_EQ_BPF2_A1 0x03da +#define RT5659_ADC_R_EQ_BPF2_A1 0x03db +#define RT5659_ADC_L_EQ_BPF2_A2 0x03dc +#define RT5659_ADC_R_EQ_BPF2_A2 0x03dd +#define RT5659_ADC_L_EQ_BPF2_H0 0x03de +#define RT5659_ADC_R_EQ_BPF2_H0 0x03df +#define RT5659_ADC_L_EQ_BPF3_A1 0x03e0 +#define RT5659_ADC_R_EQ_BPF3_A1 0x03e1 +#define RT5659_ADC_L_EQ_BPF3_A2 0x03e2 +#define RT5659_ADC_R_EQ_BPF3_A2 0x03e3 +#define RT5659_ADC_L_EQ_BPF3_H0 0x03e4 +#define RT5659_ADC_R_EQ_BPF3_H0 0x03e5 +#define RT5659_ADC_L_EQ_BPF4_A1 0x03e6 +#define RT5659_ADC_R_EQ_BPF4_A1 0x03e7 +#define RT5659_ADC_L_EQ_BPF4_A2 0x03e8 +#define RT5659_ADC_R_EQ_BPF4_A2 0x03e9 +#define RT5659_ADC_L_EQ_BPF4_H0 0x03ea +#define RT5659_ADC_R_EQ_BPF4_H0 0x03eb +#define RT5659_ADC_L_EQ_HPF1_A1 0x03ec +#define RT5659_ADC_R_EQ_HPF1_A1 0x03ed +#define RT5659_ADC_L_EQ_HPF1_H0 0x03ee +#define RT5659_ADC_R_EQ_HPF1_H0 0x03ef +#define RT5659_ADC_L_EQ_PRE_VOL 0x03f0 +#define RT5659_ADC_R_EQ_PRE_VOL 0x03f1 +#define RT5659_ADC_L_EQ_POST_VOL 0x03f2 +#define RT5659_ADC_R_EQ_POST_VOL 0x03f3 + + + +/* global definition */ +#define RT5659_L_MUTE (0x1 << 15) +#define RT5659_L_MUTE_SFT 15 +#define RT5659_VOL_L_MUTE (0x1 << 14) +#define RT5659_VOL_L_SFT 14 +#define RT5659_R_MUTE (0x1 << 7) +#define RT5659_R_MUTE_SFT 7 +#define RT5659_VOL_R_MUTE (0x1 << 6) +#define RT5659_VOL_R_SFT 6 +#define RT5659_L_VOL_MASK (0x3f << 8) +#define RT5659_L_VOL_SFT 8 +#define RT5659_R_VOL_MASK (0x3f) +#define RT5659_R_VOL_SFT 0 + +/*Headphone Amp L/R Analog Gain and Digital NG2 Gain Control (0x0005 0x0006)*/ +#define RT5659_G_HP (0x1f << 8) +#define RT5659_G_HP_SFT 8 +#define RT5659_G_STO_DA_DMIX (0x1f) +#define RT5659_G_STO_DA_SFT 0 + +/* IN1/IN2 Control (0x000c) */ +#define RT5659_IN1_DF_MASK (0x1 << 15) +#define RT5659_IN1_DF 15 +#define RT5659_BST1_MASK (0x7f << 8) +#define RT5659_BST1_SFT 8 +#define RT5659_BST2_MASK (0x7f) +#define RT5659_BST2_SFT 0 + +/* IN3/IN4 Control (0x000d) */ +#define RT5659_IN3_DF_MASK (0x1 << 15) +#define RT5659_IN3_DF 15 +#define RT5659_BST3_MASK (0x7f << 8) +#define RT5659_BST3_SFT 8 +#define RT5659_IN4_DF_MASK (0x1 << 7) +#define RT5659_IN4_DF 7 +#define RT5659_BST4_MASK (0x7f) +#define RT5659_BST4_SFT 0 + +/* INL and INR Volume Control (0x000f) */ +#define RT5659_INL_VOL_MASK (0x1f << 8) +#define RT5659_INL_VOL_SFT 8 +#define RT5659_INR_VOL_MASK (0x1f) +#define RT5659_INR_VOL_SFT 0 + +/* Embeeded Jack and Type Detection Control 1 (0x0010) */ +#define RT5659_EMB_JD_EN (0x1 << 15) +#define RT5659_EMB_JD_EN_SFT 15 +#define RT5659_JD_MODE (0x1 << 13) +#define RT5659_JD_MODE_SFT 13 +#define RT5659_EXT_JD_EN (0x1 << 11) +#define RT5659_EXT_JD_EN_SFT 11 +#define RT5659_EXT_JD_DIG (0x1 << 9) + +/* Embeeded Jack and Type Detection Control 2 (0x0011) */ +#define RT5659_EXT_JD_SRC (0x7 << 4) +#define RT5659_EXT_JD_SRC_SFT 4 +#define RT5659_EXT_JD_SRC_GPIO_JD1 (0x0 << 4) +#define RT5659_EXT_JD_SRC_GPIO_JD2 (0x1 << 4) +#define RT5659_EXT_JD_SRC_JD1_1 (0x2 << 4) +#define RT5659_EXT_JD_SRC_JD1_2 (0x3 << 4) +#define RT5659_EXT_JD_SRC_JD2 (0x4 << 4) +#define RT5659_EXT_JD_SRC_JD3 (0x5 << 4) +#define RT5659_EXT_JD_SRC_MANUAL (0x6 << 4) + +/* Slience Detection Control (0x0015) */ +#define RT5659_SIL_DET_MASK (0x1 << 15) +#define RT5659_SIL_DET_DIS (0x0 << 15) +#define RT5659_SIL_DET_EN (0x1 << 15) + +/* Sidetone Control (0x0018) */ +#define RT5659_ST_SEL_MASK (0x7 << 9) +#define RT5659_ST_SEL_SFT 9 +#define RT5659_ST_EN (0x1 << 6) +#define RT5659_ST_EN_SFT 6 + +/* DAC1 Digital Volume (0x0019) */ +#define RT5659_DAC_L1_VOL_MASK (0xff << 8) +#define RT5659_DAC_L1_VOL_SFT 8 +#define RT5659_DAC_R1_VOL_MASK (0xff) +#define RT5659_DAC_R1_VOL_SFT 0 + +/* DAC2 Digital Volume (0x001a) */ +#define RT5659_DAC_L2_VOL_MASK (0xff << 8) +#define RT5659_DAC_L2_VOL_SFT 8 +#define RT5659_DAC_R2_VOL_MASK (0xff) +#define RT5659_DAC_R2_VOL_SFT 0 + +/* DAC2 Control (0x001b) */ +#define RT5659_M_DAC2_L_VOL (0x1 << 13) +#define RT5659_M_DAC2_L_VOL_SFT 13 +#define RT5659_M_DAC2_R_VOL (0x1 << 12) +#define RT5659_M_DAC2_R_VOL_SFT 12 +#define RT5659_DAC_L2_SEL_MASK (0x7 << 4) +#define RT5659_DAC_L2_SEL_SFT 4 +#define RT5659_DAC_R2_SEL_MASK (0x7 << 0) +#define RT5659_DAC_R2_SEL_SFT 0 + +/* ADC Digital Volume Control (0x001c) */ +#define RT5659_ADC_L_VOL_MASK (0x7f << 8) +#define RT5659_ADC_L_VOL_SFT 8 +#define RT5659_ADC_R_VOL_MASK (0x7f) +#define RT5659_ADC_R_VOL_SFT 0 + +/* Mono ADC Digital Volume Control (0x001d) */ +#define RT5659_MONO_ADC_L_VOL_MASK (0x7f << 8) +#define RT5659_MONO_ADC_L_VOL_SFT 8 +#define RT5659_MONO_ADC_R_VOL_MASK (0x7f) +#define RT5659_MONO_ADC_R_VOL_SFT 0 + +/* Stereo1 ADC Boost Gain Control (0x001f) */ +#define RT5659_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5659_STO1_ADC_L_BST_SFT 14 +#define RT5659_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5659_STO1_ADC_R_BST_SFT 12 + +/* Mono ADC Boost Gain Control (0x0020) */ +#define RT5659_MONO_ADC_L_BST_MASK (0x3 << 14) +#define RT5659_MONO_ADC_L_BST_SFT 14 +#define RT5659_MONO_ADC_R_BST_MASK (0x3 << 12) +#define RT5659_MONO_ADC_R_BST_SFT 12 + +/* Stereo1 ADC Boost Gain Control (0x001f) */ +#define RT5659_STO2_ADC_L_BST_MASK (0x3 << 14) +#define RT5659_STO2_ADC_L_BST_SFT 14 +#define RT5659_STO2_ADC_R_BST_MASK (0x3 << 12) +#define RT5659_STO2_ADC_R_BST_SFT 12 + +/* Stereo ADC Mixer Control (0x0026) */ +#define RT5659_M_STO1_ADC_L1 (0x1 << 15) +#define RT5659_M_STO1_ADC_L1_SFT 15 +#define RT5659_M_STO1_ADC_L2 (0x1 << 14) +#define RT5659_M_STO1_ADC_L2_SFT 14 +#define RT5659_STO1_ADC1_SRC_MASK (0x1 << 13) +#define RT5659_STO1_ADC1_SRC_SFT 13 +#define RT5659_STO1_ADC1_SRC_ADC (0x1 << 13) +#define RT5659_STO1_ADC1_SRC_DACMIX (0x0 << 13) +#define RT5659_STO1_ADC_SRC_MASK (0x1 << 12) +#define RT5659_STO1_ADC_SRC_SFT 12 +#define RT5659_STO1_ADC_SRC_ADC1 (0x1 << 12) +#define RT5659_STO1_ADC_SRC_ADC2 (0x0 << 12) +#define RT5659_STO1_ADC2_SRC_MASK (0x1 << 11) +#define RT5659_STO1_ADC2_SRC_SFT 11 +#define RT5659_STO1_DMIC_SRC_MASK (0x1 << 8) +#define RT5659_STO1_DMIC_SRC_SFT 8 +#define RT5659_STO1_DMIC_SRC_DMIC2 (0x1 << 8) +#define RT5659_STO1_DMIC_SRC_DMIC1 (0x0 << 8) +#define RT5659_M_STO1_ADC_R1 (0x1 << 6) +#define RT5659_M_STO1_ADC_R1_SFT 6 +#define RT5659_M_STO1_ADC_R2 (0x1 << 5) +#define RT5659_M_STO1_ADC_R2_SFT 5 + +/* Mono1 ADC Mixer control (0x0027) */ +#define RT5659_M_MONO_ADC_L1 (0x1 << 15) +#define RT5659_M_MONO_ADC_L1_SFT 15 +#define RT5659_M_MONO_ADC_L2 (0x1 << 14) +#define RT5659_M_MONO_ADC_L2_SFT 14 +#define RT5659_MONO_ADC_L2_SRC_MASK (0x1 << 12) +#define RT5659_MONO_ADC_L2_SRC_SFT 12 +#define RT5659_MONO_ADC_L1_SRC_MASK (0x1 << 11) +#define RT5659_MONO_ADC_L1_SRC_SFT 11 +#define RT5659_MONO_ADC_L_SRC_MASK (0x3 << 9) +#define RT5659_MONO_ADC_L_SRC_SFT 9 +#define RT5659_MONO_DMIC_L_SRC_MASK (0x1 << 8) +#define RT5659_MONO_DMIC_L_SRC_SFT 8 +#define RT5659_M_MONO_ADC_R1 (0x1 << 7) +#define RT5659_M_MONO_ADC_R1_SFT 7 +#define RT5659_M_MONO_ADC_R2 (0x1 << 6) +#define RT5659_M_MONO_ADC_R2_SFT 6 +#define RT5659_STO2_ADC_SRC_MASK (0x1 << 5) +#define RT5659_STO2_ADC_SRC_SFT 5 +#define RT5659_MONO_ADC_R2_SRC_MASK (0x1 << 4) +#define RT5659_MONO_ADC_R2_SRC_SFT 4 +#define RT5659_MONO_ADC_R1_SRC_MASK (0x1 << 3) +#define RT5659_MONO_ADC_R1_SRC_SFT 3 +#define RT5659_MONO_ADC_R_SRC_MASK (0x3 << 1) +#define RT5659_MONO_ADC_R_SRC_SFT 1 +#define RT5659_MONO_DMIC_R_SRC_MASK 0x1 +#define RT5659_MONO_DMIC_R_SRC_SFT 0 + +/* ADC Mixer to DAC Mixer Control (0x0029) */ +#define RT5659_M_ADCMIX_L (0x1 << 15) +#define RT5659_M_ADCMIX_L_SFT 15 +#define RT5659_M_DAC1_L (0x1 << 14) +#define RT5659_M_DAC1_L_SFT 14 +#define RT5659_DAC1_R_SEL_MASK (0x3 << 10) +#define RT5659_DAC1_R_SEL_SFT 10 +#define RT5659_DAC1_R_SEL_IF1 (0x0 << 10) +#define RT5659_DAC1_R_SEL_IF2 (0x1 << 10) +#define RT5659_DAC1_R_SEL_IF3 (0x2 << 10) +#define RT5659_DAC1_L_SEL_MASK (0x3 << 8) +#define RT5659_DAC1_L_SEL_SFT 8 +#define RT5659_DAC1_L_SEL_IF1 (0x0 << 8) +#define RT5659_DAC1_L_SEL_IF2 (0x1 << 8) +#define RT5659_DAC1_L_SEL_IF3 (0x2 << 8) +#define RT5659_M_ADCMIX_R (0x1 << 7) +#define RT5659_M_ADCMIX_R_SFT 7 +#define RT5659_M_DAC1_R (0x1 << 6) +#define RT5659_M_DAC1_R_SFT 6 + +/* Stereo DAC Mixer Control (0x002a) */ +#define RT5659_M_DAC_L1_STO_L (0x1 << 15) +#define RT5659_M_DAC_L1_STO_L_SFT 15 +#define RT5659_G_DAC_L1_STO_L_MASK (0x1 << 14) +#define RT5659_G_DAC_L1_STO_L_SFT 14 +#define RT5659_M_DAC_R1_STO_L (0x1 << 13) +#define RT5659_M_DAC_R1_STO_L_SFT 13 +#define RT5659_G_DAC_R1_STO_L_MASK (0x1 << 12) +#define RT5659_G_DAC_R1_STO_L_SFT 12 +#define RT5659_M_DAC_L2_STO_L (0x1 << 11) +#define RT5659_M_DAC_L2_STO_L_SFT 11 +#define RT5659_G_DAC_L2_STO_L_MASK (0x1 << 10) +#define RT5659_G_DAC_L2_STO_L_SFT 10 +#define RT5659_M_DAC_R2_STO_L (0x1 << 9) +#define RT5659_M_DAC_R2_STO_L_SFT 9 +#define RT5659_G_DAC_R2_STO_L_MASK (0x1 << 8) +#define RT5659_G_DAC_R2_STO_L_SFT 8 +#define RT5659_M_DAC_L1_STO_R (0x1 << 7) +#define RT5659_M_DAC_L1_STO_R_SFT 7 +#define RT5659_G_DAC_L1_STO_R_MASK (0x1 << 6) +#define RT5659_G_DAC_L1_STO_R_SFT 6 +#define RT5659_M_DAC_R1_STO_R (0x1 << 5) +#define RT5659_M_DAC_R1_STO_R_SFT 5 +#define RT5659_G_DAC_R1_STO_R_MASK (0x1 << 4) +#define RT5659_G_DAC_R1_STO_R_SFT 4 +#define RT5659_M_DAC_L2_STO_R (0x1 << 3) +#define RT5659_M_DAC_L2_STO_R_SFT 3 +#define RT5659_G_DAC_L2_STO_R_MASK (0x1 << 2) +#define RT5659_G_DAC_L2_STO_R_SFT 2 +#define RT5659_M_DAC_R2_STO_R (0x1 << 1) +#define RT5659_M_DAC_R2_STO_R_SFT 1 +#define RT5659_G_DAC_R2_STO_R_MASK (0x1) +#define RT5659_G_DAC_R2_STO_R_SFT 0 + +/* Mono DAC Mixer Control (0x002b) */ +#define RT5659_M_DAC_L1_MONO_L (0x1 << 15) +#define RT5659_M_DAC_L1_MONO_L_SFT 15 +#define RT5659_G_DAC_L1_MONO_L_MASK (0x1 << 14) +#define RT5659_G_DAC_L1_MONO_L_SFT 14 +#define RT5659_M_DAC_R1_MONO_L (0x1 << 13) +#define RT5659_M_DAC_R1_MONO_L_SFT 13 +#define RT5659_G_DAC_R1_MONO_L_MASK (0x1 << 12) +#define RT5659_G_DAC_R1_MONO_L_SFT 12 +#define RT5659_M_DAC_L2_MONO_L (0x1 << 11) +#define RT5659_M_DAC_L2_MONO_L_SFT 11 +#define RT5659_G_DAC_L2_MONO_L_MASK (0x1 << 10) +#define RT5659_G_DAC_L2_MONO_L_SFT 10 +#define RT5659_M_DAC_R2_MONO_L (0x1 << 9) +#define RT5659_M_DAC_R2_MONO_L_SFT 9 +#define RT5659_G_DAC_R2_MONO_L_MASK (0x1 << 8) +#define RT5659_G_DAC_R2_MONO_L_SFT 8 +#define RT5659_M_DAC_L1_MONO_R (0x1 << 7) +#define RT5659_M_DAC_L1_MONO_R_SFT 7 +#define RT5659_G_DAC_L1_MONO_R_MASK (0x1 << 6) +#define RT5659_G_DAC_L1_MONO_R_SFT 6 +#define RT5659_M_DAC_R1_MONO_R (0x1 << 5) +#define RT5659_M_DAC_R1_MONO_R_SFT 5 +#define RT5659_G_DAC_R1_MONO_R_MASK (0x1 << 4) +#define RT5659_G_DAC_R1_MONO_R_SFT 4 +#define RT5659_M_DAC_L2_MONO_R (0x1 << 3) +#define RT5659_M_DAC_L2_MONO_R_SFT 3 +#define RT5659_G_DAC_L2_MONO_R_MASK (0x1 << 2) +#define RT5659_G_DAC_L2_MONO_R_SFT 2 +#define RT5659_M_DAC_R2_MONO_R (0x1 << 1) +#define RT5659_M_DAC_R2_MONO_R_SFT 1 +#define RT5659_G_DAC_R2_MONO_R_MASK (0x1) +#define RT5659_G_DAC_R2_MONO_R_SFT 0 + +/* Digital Mixer Control (0x002c) */ +#define RT5659_M_DAC_MIX_L (0x1 << 7) +#define RT5659_M_DAC_MIX_L_SFT 7 +#define RT5659_DAC_MIX_L_MASK (0x1 << 6) +#define RT5659_DAC_MIX_L_SFT 6 +#define RT5659_M_DAC_MIX_R (0x1 << 5) +#define RT5659_M_DAC_MIX_R_SFT 5 +#define RT5659_DAC_MIX_R_MASK (0x1 << 4) +#define RT5659_DAC_MIX_R_SFT 4 + +/* Analog DAC Input Source Control (0x002d) */ +#define RT5659_A_DACL1_SEL (0x1 << 3) +#define RT5659_A_DACL1_SFT 3 +#define RT5659_A_DACR1_SEL (0x1 << 2) +#define RT5659_A_DACR1_SFT 2 +#define RT5659_A_DACL2_SEL (0x1 << 1) +#define RT5659_A_DACL2_SFT 1 +#define RT5659_A_DACR2_SEL (0x1 << 0) +#define RT5659_A_DACR2_SFT 0 + +/* Digital Interface Data Control (0x002f) */ +#define RT5659_IF2_ADC3_IN_MASK (0x3 << 14) +#define RT5659_IF2_ADC3_IN_SFT 14 +#define RT5659_IF2_ADC_IN_MASK (0x3 << 12) +#define RT5659_IF2_ADC_IN_SFT 12 +#define RT5659_IF2_DAC_SEL_MASK (0x3 << 10) +#define RT5659_IF2_DAC_SEL_SFT 10 +#define RT5659_IF2_ADC_SEL_MASK (0x3 << 8) +#define RT5659_IF2_ADC_SEL_SFT 8 +#define RT5659_IF3_DAC_SEL_MASK (0x3 << 6) +#define RT5659_IF3_DAC_SEL_SFT 6 +#define RT5659_IF3_ADC_SEL_MASK (0x3 << 4) +#define RT5659_IF3_ADC_SEL_SFT 4 +#define RT5659_IF3_ADC_IN_MASK (0x3 << 0) +#define RT5659_IF3_ADC_IN_SFT 0 + +/* PDM Output Control (0x0031) */ +#define RT5659_PDM1_L_MASK (0x1 << 15) +#define RT5659_PDM1_L_SFT 15 +#define RT5659_M_PDM1_L (0x1 << 14) +#define RT5659_M_PDM1_L_SFT 14 +#define RT5659_PDM1_R_MASK (0x1 << 13) +#define RT5659_PDM1_R_SFT 13 +#define RT5659_M_PDM1_R (0x1 << 12) +#define RT5659_M_PDM1_R_SFT 12 +#define RT5659_PDM2_BUSY (0x1 << 7) +#define RT5659_PDM1_BUSY (0x1 << 6) +#define RT5659_PDM_PATTERN (0x1 << 5) +#define RT5659_PDM_GAIN (0x1 << 4) +#define RT5659_PDM_DIV_MASK (0x3) + +/*S/PDIF Output Control (0x0036) */ +#define RT5659_SPDIF_SEL_MASK (0x3 << 0) +#define RT5659_SPDIF_SEL_SFT 0 + +/* REC Left Mixer Control 2 (0x003c) */ +#define RT5659_M_BST1_RM1_L (0x1 << 5) +#define RT5659_M_BST1_RM1_L_SFT 5 +#define RT5659_M_BST2_RM1_L (0x1 << 4) +#define RT5659_M_BST2_RM1_L_SFT 4 +#define RT5659_M_BST3_RM1_L (0x1 << 3) +#define RT5659_M_BST3_RM1_L_SFT 3 +#define RT5659_M_BST4_RM1_L (0x1 << 2) +#define RT5659_M_BST4_RM1_L_SFT 2 +#define RT5659_M_INL_RM1_L (0x1 << 1) +#define RT5659_M_INL_RM1_L_SFT 1 +#define RT5659_M_SPKVOLL_RM1_L (0x1) +#define RT5659_M_SPKVOLL_RM1_L_SFT 0 + +/* REC Right Mixer Control 2 (0x003e) */ +#define RT5659_M_BST1_RM1_R (0x1 << 5) +#define RT5659_M_BST1_RM1_R_SFT 5 +#define RT5659_M_BST2_RM1_R (0x1 << 4) +#define RT5659_M_BST2_RM1_R_SFT 4 +#define RT5659_M_BST3_RM1_R (0x1 << 3) +#define RT5659_M_BST3_RM1_R_SFT 3 +#define RT5659_M_BST4_RM1_R (0x1 << 2) +#define RT5659_M_BST4_RM1_R_SFT 2 +#define RT5659_M_INR_RM1_R (0x1 << 1) +#define RT5659_M_INR_RM1_R_SFT 1 +#define RT5659_M_HPOVOLR_RM1_R (0x1) +#define RT5659_M_HPOVOLR_RM1_R_SFT 0 + +/* SPK Left Mixer Control (0x0046) */ +#define RT5659_M_BST3_SM_L (0x1 << 4) +#define RT5659_M_BST3_SM_L_SFT 4 +#define RT5659_M_IN_R_SM_L (0x1 << 3) +#define RT5659_M_IN_R_SM_L_SFT 3 +#define RT5659_M_IN_L_SM_L (0x1 << 2) +#define RT5659_M_IN_L_SM_L_SFT 2 +#define RT5659_M_BST1_SM_L (0x1 << 1) +#define RT5659_M_BST1_SM_L_SFT 1 +#define RT5659_M_DAC_L2_SM_L (0x1) +#define RT5659_M_DAC_L2_SM_L_SFT 0 + +/* SPK Right Mixer Control (0x0047) */ +#define RT5659_M_BST3_SM_R (0x1 << 4) +#define RT5659_M_BST3_SM_R_SFT 4 +#define RT5659_M_IN_R_SM_R (0x1 << 3) +#define RT5659_M_IN_R_SM_R_SFT 3 +#define RT5659_M_IN_L_SM_R (0x1 << 2) +#define RT5659_M_IN_L_SM_R_SFT 2 +#define RT5659_M_BST4_SM_R (0x1 << 1) +#define RT5659_M_BST4_SM_R_SFT 1 +#define RT5659_M_DAC_R2_SM_R (0x1) +#define RT5659_M_DAC_R2_SM_R_SFT 0 + +/* SPO Amp Input and Gain Control (0x0048) */ +#define RT5659_M_DAC_L2_SPKOMIX (0x1 << 13) +#define RT5659_M_DAC_L2_SPKOMIX_SFT 13 +#define RT5659_M_SPKVOLL_SPKOMIX (0x1 << 12) +#define RT5659_M_SPKVOLL_SPKOMIX_SFT 12 +#define RT5659_M_DAC_R2_SPKOMIX (0x1 << 9) +#define RT5659_M_DAC_R2_SPKOMIX_SFT 9 +#define RT5659_M_SPKVOLR_SPKOMIX (0x1 << 8) +#define RT5659_M_SPKVOLR_SPKOMIX_SFT 8 + +/* MONOMIX Input and Gain Control (0x004b) */ +#define RT5659_M_MONOVOL_MA (0x1 << 9) +#define RT5659_M_MONOVOL_MA_SFT 9 +#define RT5659_M_DAC_L2_MA (0x1 << 8) +#define RT5659_M_DAC_L2_MA_SFT 8 +#define RT5659_M_BST3_MM (0x1 << 4) +#define RT5659_M_BST3_MM_SFT 4 +#define RT5659_M_BST2_MM (0x1 << 3) +#define RT5659_M_BST2_MM_SFT 3 +#define RT5659_M_BST1_MM (0x1 << 2) +#define RT5659_M_BST1_MM_SFT 2 +#define RT5659_M_DAC_R2_MM (0x1 << 1) +#define RT5659_M_DAC_R2_MM_SFT 1 +#define RT5659_M_DAC_L2_MM (0x1) +#define RT5659_M_DAC_L2_MM_SFT 0 + +/* Output Left Mixer Control 1 (0x004d) */ +#define RT5659_G_BST3_OM_L_MASK (0x7 << 12) +#define RT5659_G_BST3_OM_L_SFT 12 +#define RT5659_G_BST2_OM_L_MASK (0x7 << 9) +#define RT5659_G_BST2_OM_L_SFT 9 +#define RT5659_G_BST1_OM_L_MASK (0x7 << 6) +#define RT5659_G_BST1_OM_L_SFT 6 +#define RT5659_G_IN_L_OM_L_MASK (0x7 << 3) +#define RT5659_G_IN_L_OM_L_SFT 3 +#define RT5659_G_DAC_L2_OM_L_MASK (0x7 << 0) +#define RT5659_G_DAC_L2_OM_L_SFT 0 + +/* Output Left Mixer Input Control (0x004e) */ +#define RT5659_M_BST3_OM_L (0x1 << 4) +#define RT5659_M_BST3_OM_L_SFT 4 +#define RT5659_M_BST2_OM_L (0x1 << 3) +#define RT5659_M_BST2_OM_L_SFT 3 +#define RT5659_M_BST1_OM_L (0x1 << 2) +#define RT5659_M_BST1_OM_L_SFT 2 +#define RT5659_M_IN_L_OM_L (0x1 << 1) +#define RT5659_M_IN_L_OM_L_SFT 1 +#define RT5659_M_DAC_L2_OM_L (0x1) +#define RT5659_M_DAC_L2_OM_L_SFT 0 + +/* Output Right Mixer Input Control (0x0050) */ +#define RT5659_M_BST4_OM_R (0x1 << 4) +#define RT5659_M_BST4_OM_R_SFT 4 +#define RT5659_M_BST3_OM_R (0x1 << 3) +#define RT5659_M_BST3_OM_R_SFT 3 +#define RT5659_M_BST2_OM_R (0x1 << 2) +#define RT5659_M_BST2_OM_R_SFT 2 +#define RT5659_M_IN_R_OM_R (0x1 << 1) +#define RT5659_M_IN_R_OM_R_SFT 1 +#define RT5659_M_DAC_R2_OM_R (0x1) +#define RT5659_M_DAC_R2_OM_R_SFT 0 + +/* LOUT Mixer Control (0x0052) */ +#define RT5659_M_DAC_L2_LM (0x1 << 15) +#define RT5659_M_DAC_L2_LM_SFT 15 +#define RT5659_M_DAC_R2_LM (0x1 << 14) +#define RT5659_M_DAC_R2_LM_SFT 14 +#define RT5659_M_OV_L_LM (0x1 << 13) +#define RT5659_M_OV_L_LM_SFT 13 +#define RT5659_M_OV_R_LM (0x1 << 12) +#define RT5659_M_OV_R_LM_SFT 12 + +/* Power Management for Digital 1 (0x0061) */ +#define RT5659_PWR_I2S1 (0x1 << 15) +#define RT5659_PWR_I2S1_BIT 15 +#define RT5659_PWR_I2S2 (0x1 << 14) +#define RT5659_PWR_I2S2_BIT 14 +#define RT5659_PWR_I2S3 (0x1 << 13) +#define RT5659_PWR_I2S3_BIT 13 +#define RT5659_PWR_SPDIF (0x1 << 12) +#define RT5659_PWR_SPDIF_BIT 12 +#define RT5659_PWR_DAC_L1 (0x1 << 11) +#define RT5659_PWR_DAC_L1_BIT 11 +#define RT5659_PWR_DAC_R1 (0x1 << 10) +#define RT5659_PWR_DAC_R1_BIT 10 +#define RT5659_PWR_DAC_L2 (0x1 << 9) +#define RT5659_PWR_DAC_L2_BIT 9 +#define RT5659_PWR_DAC_R2 (0x1 << 8) +#define RT5659_PWR_DAC_R2_BIT 8 +#define RT5659_PWR_LDO (0x1 << 7) +#define RT5659_PWR_LDO_BIT 7 +#define RT5659_PWR_ADC_L1 (0x1 << 4) +#define RT5659_PWR_ADC_L1_BIT 4 +#define RT5659_PWR_ADC_R1 (0x1 << 3) +#define RT5659_PWR_ADC_R1_BIT 3 +#define RT5659_PWR_ADC_L2 (0x1 << 2) +#define RT5659_PWR_ADC_L2_BIT 2 +#define RT5659_PWR_ADC_R2 (0x1 << 1) +#define RT5659_PWR_ADC_R2_BIT 1 +#define RT5659_PWR_CLS_D (0x1) +#define RT5659_PWR_CLS_D_BIT 0 + +/* Power Management for Digital 2 (0x0062) */ +#define RT5659_PWR_ADC_S1F (0x1 << 15) +#define RT5659_PWR_ADC_S1F_BIT 15 +#define RT5659_PWR_ADC_S2F (0x1 << 14) +#define RT5659_PWR_ADC_S2F_BIT 14 +#define RT5659_PWR_ADC_MF_L (0x1 << 13) +#define RT5659_PWR_ADC_MF_L_BIT 13 +#define RT5659_PWR_ADC_MF_R (0x1 << 12) +#define RT5659_PWR_ADC_MF_R_BIT 12 +#define RT5659_PWR_DAC_S1F (0x1 << 10) +#define RT5659_PWR_DAC_S1F_BIT 10 +#define RT5659_PWR_DAC_MF_L (0x1 << 9) +#define RT5659_PWR_DAC_MF_L_BIT 9 +#define RT5659_PWR_DAC_MF_R (0x1 << 8) +#define RT5659_PWR_DAC_MF_R_BIT 8 +#define RT5659_PWR_PDM1 (0x1 << 7) +#define RT5659_PWR_PDM1_BIT 7 + +/* Power Management for Analog 1 (0x0063) */ +#define RT5659_PWR_VREF1 (0x1 << 15) +#define RT5659_PWR_VREF1_BIT 15 +#define RT5659_PWR_FV1 (0x1 << 14) +#define RT5659_PWR_FV1_BIT 14 +#define RT5659_PWR_VREF2 (0x1 << 13) +#define RT5659_PWR_VREF2_BIT 13 +#define RT5659_PWR_FV2 (0x1 << 12) +#define RT5659_PWR_FV2_BIT 12 +#define RT5659_PWR_VREF3 (0x1 << 11) +#define RT5659_PWR_VREF3_BIT 11 +#define RT5659_PWR_FV3 (0x1 << 10) +#define RT5659_PWR_FV3_BIT 10 +#define RT5659_PWR_MB (0x1 << 9) +#define RT5659_PWR_MB_BIT 9 +#define RT5659_PWR_LM (0x1 << 8) +#define RT5659_PWR_LM_BIT 8 +#define RT5659_PWR_BG (0x1 << 7) +#define RT5659_PWR_BG_BIT 7 +#define RT5659_PWR_MA (0x1 << 6) +#define RT5659_PWR_MA_BIT 6 +#define RT5659_PWR_HA_L (0x1 << 5) +#define RT5659_PWR_HA_L_BIT 5 +#define RT5659_PWR_HA_R (0x1 << 4) +#define RT5659_PWR_HA_R_BIT 4 + +/* Power Management for Analog 2 (0x0064) */ +#define RT5659_PWR_BST1 (0x1 << 15) +#define RT5659_PWR_BST1_BIT 15 +#define RT5659_PWR_BST2 (0x1 << 14) +#define RT5659_PWR_BST2_BIT 14 +#define RT5659_PWR_BST3 (0x1 << 13) +#define RT5659_PWR_BST3_BIT 13 +#define RT5659_PWR_BST4 (0x1 << 12) +#define RT5659_PWR_BST4_BIT 12 +#define RT5659_PWR_MB1 (0x1 << 11) +#define RT5659_PWR_MB1_BIT 11 +#define RT5659_PWR_MB2 (0x1 << 10) +#define RT5659_PWR_MB2_BIT 10 +#define RT5659_PWR_MB3 (0x1 << 9) +#define RT5659_PWR_MB3_BIT 9 +#define RT5659_PWR_BST1_P (0x1 << 6) +#define RT5659_PWR_BST1_P_BIT 6 +#define RT5659_PWR_BST2_P (0x1 << 5) +#define RT5659_PWR_BST2_P_BIT 5 +#define RT5659_PWR_BST3_P (0x1 << 4) +#define RT5659_PWR_BST3_P_BIT 4 +#define RT5659_PWR_BST4_P (0x1 << 3) +#define RT5659_PWR_BST4_P_BIT 3 +#define RT5659_PWR_JD1 (0x1 << 2) +#define RT5659_PWR_JD1_BIT 2 +#define RT5659_PWR_JD2 (0x1 << 1) +#define RT5659_PWR_JD2_BIT 1 +#define RT5659_PWR_JD3 (0x1) +#define RT5659_PWR_JD3_BIT 0 + +/* Power Management for Analog 3 (0x0065) */ +#define RT5659_PWR_BST_L (0x1 << 8) +#define RT5659_PWR_BST_L_BIT 8 +#define RT5659_PWR_BST_R (0x1 << 7) +#define RT5659_PWR_BST_R_BIT 7 +#define RT5659_PWR_PLL (0x1 << 6) +#define RT5659_PWR_PLL_BIT 6 +#define RT5659_PWR_LDO5 (0x1 << 5) +#define RT5659_PWR_LDO5_BIT 5 +#define RT5659_PWR_LDO4 (0x1 << 4) +#define RT5659_PWR_LDO4_BIT 4 +#define RT5659_PWR_LDO3 (0x1 << 3) +#define RT5659_PWR_LDO3_BIT 3 +#define RT5659_PWR_LDO2 (0x1 << 2) +#define RT5659_PWR_LDO2_BIT 2 +#define RT5659_PWR_SVD (0x1 << 1) +#define RT5659_PWR_SVD_BIT 1 + +/* Power Management for Mixer (0x0066) */ +#define RT5659_PWR_OM_L (0x1 << 15) +#define RT5659_PWR_OM_L_BIT 15 +#define RT5659_PWR_OM_R (0x1 << 14) +#define RT5659_PWR_OM_R_BIT 14 +#define RT5659_PWR_SM_L (0x1 << 13) +#define RT5659_PWR_SM_L_BIT 13 +#define RT5659_PWR_SM_R (0x1 << 12) +#define RT5659_PWR_SM_R_BIT 12 +#define RT5659_PWR_RM1_L (0x1 << 11) +#define RT5659_PWR_RM1_L_BIT 11 +#define RT5659_PWR_RM1_R (0x1 << 10) +#define RT5659_PWR_RM1_R_BIT 10 +#define RT5659_PWR_MM (0x1 << 8) +#define RT5659_PWR_MM_BIT 8 +#define RT5659_PWR_RM2_L (0x1 << 3) +#define RT5659_PWR_RM2_L_BIT 3 +#define RT5659_PWR_RM2_R (0x1 << 2) +#define RT5659_PWR_RM2_R_BIT 2 + +/* Power Management for Volume (0x0067) */ +#define RT5659_PWR_SV_L (0x1 << 15) +#define RT5659_PWR_SV_L_BIT 15 +#define RT5659_PWR_SV_R (0x1 << 14) +#define RT5659_PWR_SV_R_BIT 14 +#define RT5659_PWR_OV_L (0x1 << 13) +#define RT5659_PWR_OV_L_BIT 13 +#define RT5659_PWR_OV_R (0x1 << 12) +#define RT5659_PWR_OV_R_BIT 12 +#define RT5659_PWR_IN_L (0x1 << 9) +#define RT5659_PWR_IN_L_BIT 9 +#define RT5659_PWR_IN_R (0x1 << 8) +#define RT5659_PWR_IN_R_BIT 8 +#define RT5659_PWR_MV (0x1 << 7) +#define RT5659_PWR_MV_BIT 7 +#define RT5659_PWR_MIC_DET (0x1 << 5) +#define RT5659_PWR_MIC_DET_BIT 5 + +/* I2S1/2/3 Audio Serial Data Port Control (0x0070 0x0071 0x0072) */ +#define RT5659_I2S_MS_MASK (0x1 << 15) +#define RT5659_I2S_MS_SFT 15 +#define RT5659_I2S_MS_M (0x0 << 15) +#define RT5659_I2S_MS_S (0x1 << 15) +#define RT5659_I2S_O_CP_MASK (0x3 << 12) +#define RT5659_I2S_O_CP_SFT 12 +#define RT5659_I2S_O_CP_OFF (0x0 << 12) +#define RT5659_I2S_O_CP_U_LAW (0x1 << 12) +#define RT5659_I2S_O_CP_A_LAW (0x2 << 12) +#define RT5659_I2S_I_CP_MASK (0x3 << 10) +#define RT5659_I2S_I_CP_SFT 10 +#define RT5659_I2S_I_CP_OFF (0x0 << 10) +#define RT5659_I2S_I_CP_U_LAW (0x1 << 10) +#define RT5659_I2S_I_CP_A_LAW (0x2 << 10) +#define RT5659_I2S_BP_MASK (0x1 << 8) +#define RT5659_I2S_BP_SFT 8 +#define RT5659_I2S_BP_NOR (0x0 << 8) +#define RT5659_I2S_BP_INV (0x1 << 8) +#define RT5659_I2S_DL_MASK (0x3 << 4) +#define RT5659_I2S_DL_SFT 4 +#define RT5659_I2S_DL_16 (0x0 << 4) +#define RT5659_I2S_DL_20 (0x1 << 4) +#define RT5659_I2S_DL_24 (0x2 << 4) +#define RT5659_I2S_DL_8 (0x3 << 4) +#define RT5659_I2S_DF_MASK (0x7) +#define RT5659_I2S_DF_SFT 0 +#define RT5659_I2S_DF_I2S (0x0) +#define RT5659_I2S_DF_LEFT (0x1) +#define RT5659_I2S_DF_PCM_A (0x2) +#define RT5659_I2S_DF_PCM_B (0x3) +#define RT5659_I2S_DF_PCM_A_N (0x6) +#define RT5659_I2S_DF_PCM_B_N (0x7) + +/* ADC/DAC Clock Control 1 (0x0073) */ +#define RT5659_I2S_PD1_MASK (0x7 << 12) +#define RT5659_I2S_PD1_SFT 12 +#define RT5659_I2S_PD1_1 (0x0 << 12) +#define RT5659_I2S_PD1_2 (0x1 << 12) +#define RT5659_I2S_PD1_3 (0x2 << 12) +#define RT5659_I2S_PD1_4 (0x3 << 12) +#define RT5659_I2S_PD1_6 (0x4 << 12) +#define RT5659_I2S_PD1_8 (0x5 << 12) +#define RT5659_I2S_PD1_12 (0x6 << 12) +#define RT5659_I2S_PD1_16 (0x7 << 12) +#define RT5659_I2S_BCLK_MS2_MASK (0x1 << 11) +#define RT5659_I2S_BCLK_MS2_SFT 11 +#define RT5659_I2S_BCLK_MS2_32 (0x0 << 11) +#define RT5659_I2S_BCLK_MS2_64 (0x1 << 11) +#define RT5659_I2S_PD2_MASK (0x7 << 8) +#define RT5659_I2S_PD2_SFT 8 +#define RT5659_I2S_PD2_1 (0x0 << 8) +#define RT5659_I2S_PD2_2 (0x1 << 8) +#define RT5659_I2S_PD2_3 (0x2 << 8) +#define RT5659_I2S_PD2_4 (0x3 << 8) +#define RT5659_I2S_PD2_6 (0x4 << 8) +#define RT5659_I2S_PD2_8 (0x5 << 8) +#define RT5659_I2S_PD2_12 (0x6 << 8) +#define RT5659_I2S_PD2_16 (0x7 << 8) +#define RT5659_I2S_BCLK_MS3_MASK (0x1 << 7) +#define RT5659_I2S_BCLK_MS3_SFT 7 +#define RT5659_I2S_BCLK_MS3_32 (0x0 << 7) +#define RT5659_I2S_BCLK_MS3_64 (0x1 << 7) +#define RT5659_I2S_PD3_MASK (0x7 << 4) +#define RT5659_I2S_PD3_SFT 4 +#define RT5659_I2S_PD3_1 (0x0 << 4) +#define RT5659_I2S_PD3_2 (0x1 << 4) +#define RT5659_I2S_PD3_3 (0x2 << 4) +#define RT5659_I2S_PD3_4 (0x3 << 4) +#define RT5659_I2S_PD3_6 (0x4 << 4) +#define RT5659_I2S_PD3_8 (0x5 << 4) +#define RT5659_I2S_PD3_12 (0x6 << 4) +#define RT5659_I2S_PD3_16 (0x7 << 4) +#define RT5659_DAC_OSR_MASK (0x3 << 2) +#define RT5659_DAC_OSR_SFT 2 +#define RT5659_DAC_OSR_128 (0x0 << 2) +#define RT5659_DAC_OSR_64 (0x1 << 2) +#define RT5659_DAC_OSR_32 (0x2 << 2) +#define RT5659_DAC_OSR_16 (0x3 << 2) +#define RT5659_ADC_OSR_MASK (0x3) +#define RT5659_ADC_OSR_SFT 0 +#define RT5659_ADC_OSR_128 (0x0) +#define RT5659_ADC_OSR_64 (0x1) +#define RT5659_ADC_OSR_32 (0x2) +#define RT5659_ADC_OSR_16 (0x3) + +/* Digital Microphone Control (0x0075) */ +#define RT5659_DMIC_1_EN_MASK (0x1 << 15) +#define RT5659_DMIC_1_EN_SFT 15 +#define RT5659_DMIC_1_DIS (0x0 << 15) +#define RT5659_DMIC_1_EN (0x1 << 15) +#define RT5659_DMIC_2_EN_MASK (0x1 << 14) +#define RT5659_DMIC_2_EN_SFT 14 +#define RT5659_DMIC_2_DIS (0x0 << 14) +#define RT5659_DMIC_2_EN (0x1 << 14) +#define RT5659_DMIC_1L_LH_MASK (0x1 << 13) +#define RT5659_DMIC_1L_LH_SFT 13 +#define RT5659_DMIC_1L_LH_RISING (0x0 << 13) +#define RT5659_DMIC_1L_LH_FALLING (0x1 << 13) +#define RT5659_DMIC_1R_LH_MASK (0x1 << 12) +#define RT5659_DMIC_1R_LH_SFT 12 +#define RT5659_DMIC_1R_LH_RISING (0x0 << 12) +#define RT5659_DMIC_1R_LH_FALLING (0x1 << 12) +#define RT5659_DMIC_2_DP_MASK (0x3 << 10) +#define RT5659_DMIC_2_DP_SFT 10 +#define RT5659_DMIC_2_DP_GPIO6 (0x0 << 10) +#define RT5659_DMIC_2_DP_GPIO10 (0x1 << 10) +#define RT5659_DMIC_2_DP_GPIO12 (0x2 << 10) +#define RT5659_DMIC_2_DP_IN2P (0x3 << 10) +#define RT5659_DMIC_CLK_MASK (0x7 << 5) +#define RT5659_DMIC_CLK_SFT 5 +#define RT5659_DMIC_1_DP_MASK (0x3 << 0) +#define RT5659_DMIC_1_DP_SFT 0 +#define RT5659_DMIC_1_DP_GPIO5 (0x0 << 0) +#define RT5659_DMIC_1_DP_GPIO9 (0x1 << 0) +#define RT5659_DMIC_1_DP_GPIO11 (0x2 << 0) +#define RT5659_DMIC_1_DP_IN2N (0x3 << 0) + +/* TDM control 1 (0x0078)*/ +#define RT5659_DS_ADC_SLOT01_SFT 14 +#define RT5659_DS_ADC_SLOT23_SFT 12 +#define RT5659_DS_ADC_SLOT45_SFT 10 +#define RT5659_DS_ADC_SLOT67_SFT 8 +#define RT5659_ADCDAT_SRC_MASK 0x1f +#define RT5659_ADCDAT_SRC_SFT 0 + +/* Global Clock Control (0x0080) */ +#define RT5659_SCLK_SRC_MASK (0x3 << 14) +#define RT5659_SCLK_SRC_SFT 14 +#define RT5659_SCLK_SRC_MCLK (0x0 << 14) +#define RT5659_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5659_SCLK_SRC_RCCLK (0x2 << 14) +#define RT5659_PLL1_SRC_MASK (0x7 << 11) +#define RT5659_PLL1_SRC_SFT 11 +#define RT5659_PLL1_SRC_MCLK (0x0 << 11) +#define RT5659_PLL1_SRC_BCLK1 (0x1 << 11) +#define RT5659_PLL1_SRC_BCLK2 (0x2 << 11) +#define RT5659_PLL1_SRC_BCLK3 (0x3 << 11) +#define RT5659_PLL1_PD_MASK (0x1 << 3) +#define RT5659_PLL1_PD_SFT 3 +#define RT5659_PLL1_PD_1 (0x0 << 3) +#define RT5659_PLL1_PD_2 (0x1 << 3) + +#define RT5659_PLL_INP_MAX 40000000 +#define RT5659_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x0081) */ +#define RT5659_PLL_N_MAX 0x001ff +#define RT5659_PLL_N_MASK (RT5659_PLL_N_MAX << 7) +#define RT5659_PLL_N_SFT 7 +#define RT5659_PLL_K_MAX 0x001f +#define RT5659_PLL_K_MASK (RT5659_PLL_K_MAX) +#define RT5659_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x0082) */ +#define RT5659_PLL_M_MAX 0x00f +#define RT5659_PLL_M_MASK (RT5659_PLL_M_MAX << 12) +#define RT5659_PLL_M_SFT 12 +#define RT5659_PLL_M_BP (0x1 << 11) +#define RT5659_PLL_M_BP_SFT 11 + +/* PLL tracking mode 1 (0x0083) */ +#define RT5659_I2S3_ASRC_MASK (0x1 << 13) +#define RT5659_I2S3_ASRC_SFT 13 +#define RT5659_I2S2_ASRC_MASK (0x1 << 12) +#define RT5659_I2S2_ASRC_SFT 12 +#define RT5659_I2S1_ASRC_MASK (0x1 << 11) +#define RT5659_I2S1_ASRC_SFT 11 +#define RT5659_DAC_STO_ASRC_MASK (0x1 << 10) +#define RT5659_DAC_STO_ASRC_SFT 10 +#define RT5659_DAC_MONO_L_ASRC_MASK (0x1 << 9) +#define RT5659_DAC_MONO_L_ASRC_SFT 9 +#define RT5659_DAC_MONO_R_ASRC_MASK (0x1 << 8) +#define RT5659_DAC_MONO_R_ASRC_SFT 8 +#define RT5659_DMIC_STO1_ASRC_MASK (0x1 << 7) +#define RT5659_DMIC_STO1_ASRC_SFT 7 +#define RT5659_DMIC_MONO_L_ASRC_MASK (0x1 << 5) +#define RT5659_DMIC_MONO_L_ASRC_SFT 5 +#define RT5659_DMIC_MONO_R_ASRC_MASK (0x1 << 4) +#define RT5659_DMIC_MONO_R_ASRC_SFT 4 +#define RT5659_ADC_STO1_ASRC_MASK (0x1 << 3) +#define RT5659_ADC_STO1_ASRC_SFT 3 +#define RT5659_ADC_MONO_L_ASRC_MASK (0x1 << 1) +#define RT5659_ADC_MONO_L_ASRC_SFT 1 +#define RT5659_ADC_MONO_R_ASRC_MASK (0x1) +#define RT5659_ADC_MONO_R_ASRC_SFT 0 + +/* PLL tracking mode 2 (0x0084)*/ +#define RT5659_DA_STO_T_MASK (0x7 << 12) +#define RT5659_DA_STO_T_SFT 12 +#define RT5659_DA_MONO_L_T_MASK (0x7 << 8) +#define RT5659_DA_MONO_L_T_SFT 8 +#define RT5659_DA_MONO_R_T_MASK (0x7 << 4) +#define RT5659_DA_MONO_R_T_SFT 4 +#define RT5659_AD_STO1_T_MASK (0x7) +#define RT5659_AD_STO1_T_SFT 0 + +/* PLL tracking mode 3 (0x0085)*/ +#define RT5659_AD_STO2_T_MASK (0x7 << 8) +#define RT5659_AD_STO2_T_SFT 8 +#define RT5659_AD_MONO_L_T_MASK (0x7 << 4) +#define RT5659_AD_MONO_L_T_SFT 4 +#define RT5659_AD_MONO_R_T_MASK (0x7) +#define RT5659_AD_MONO_R_T_SFT 0 + +/* ASRC Control 4 (0x0086) */ +#define RT5659_I2S1_RATE_MASK (0xf << 12) +#define RT5659_I2S1_RATE_SFT 12 +#define RT5659_I2S2_RATE_MASK (0xf << 8) +#define RT5659_I2S2_RATE_SFT 8 +#define RT5659_I2S3_RATE_MASK (0xf << 4) +#define RT5659_I2S3_RATE_SFT 4 + +/* Depop Mode Control 1 (0x8e) */ +#define RT5659_SMT_TRIG_MASK (0x1 << 15) +#define RT5659_SMT_TRIG_SFT 15 +#define RT5659_SMT_TRIG_DIS (0x0 << 15) +#define RT5659_SMT_TRIG_EN (0x1 << 15) +#define RT5659_HP_L_SMT_MASK (0x1 << 9) +#define RT5659_HP_L_SMT_SFT 9 +#define RT5659_HP_L_SMT_DIS (0x0 << 9) +#define RT5659_HP_L_SMT_EN (0x1 << 9) +#define RT5659_HP_R_SMT_MASK (0x1 << 8) +#define RT5659_HP_R_SMT_SFT 8 +#define RT5659_HP_R_SMT_DIS (0x0 << 8) +#define RT5659_HP_R_SMT_EN (0x1 << 8) +#define RT5659_HP_CD_PD_MASK (0x1 << 7) +#define RT5659_HP_CD_PD_SFT 7 +#define RT5659_HP_CD_PD_DIS (0x0 << 7) +#define RT5659_HP_CD_PD_EN (0x1 << 7) +#define RT5659_RSTN_MASK (0x1 << 6) +#define RT5659_RSTN_SFT 6 +#define RT5659_RSTN_DIS (0x0 << 6) +#define RT5659_RSTN_EN (0x1 << 6) +#define RT5659_RSTP_MASK (0x1 << 5) +#define RT5659_RSTP_SFT 5 +#define RT5659_RSTP_DIS (0x0 << 5) +#define RT5659_RSTP_EN (0x1 << 5) +#define RT5659_HP_CO_MASK (0x1 << 4) +#define RT5659_HP_CO_SFT 4 +#define RT5659_HP_CO_DIS (0x0 << 4) +#define RT5659_HP_CO_EN (0x1 << 4) +#define RT5659_HP_CP_MASK (0x1 << 3) +#define RT5659_HP_CP_SFT 3 +#define RT5659_HP_CP_PD (0x0 << 3) +#define RT5659_HP_CP_PU (0x1 << 3) +#define RT5659_HP_SG_MASK (0x1 << 2) +#define RT5659_HP_SG_SFT 2 +#define RT5659_HP_SG_DIS (0x0 << 2) +#define RT5659_HP_SG_EN (0x1 << 2) +#define RT5659_HP_DP_MASK (0x1 << 1) +#define RT5659_HP_DP_SFT 1 +#define RT5659_HP_DP_PD (0x0 << 1) +#define RT5659_HP_DP_PU (0x1 << 1) +#define RT5659_HP_CB_MASK (0x1) +#define RT5659_HP_CB_SFT 0 +#define RT5659_HP_CB_PD (0x0) +#define RT5659_HP_CB_PU (0x1) + +/* Depop Mode Control 2 (0x8f) */ +#define RT5659_DEPOP_MASK (0x1 << 13) +#define RT5659_DEPOP_SFT 13 +#define RT5659_DEPOP_AUTO (0x0 << 13) +#define RT5659_DEPOP_MAN (0x1 << 13) +#define RT5659_RAMP_MASK (0x1 << 12) +#define RT5659_RAMP_SFT 12 +#define RT5659_RAMP_DIS (0x0 << 12) +#define RT5659_RAMP_EN (0x1 << 12) +#define RT5659_BPS_MASK (0x1 << 11) +#define RT5659_BPS_SFT 11 +#define RT5659_BPS_DIS (0x0 << 11) +#define RT5659_BPS_EN (0x1 << 11) +#define RT5659_FAST_UPDN_MASK (0x1 << 10) +#define RT5659_FAST_UPDN_SFT 10 +#define RT5659_FAST_UPDN_DIS (0x0 << 10) +#define RT5659_FAST_UPDN_EN (0x1 << 10) +#define RT5659_MRES_MASK (0x3 << 8) +#define RT5659_MRES_SFT 8 +#define RT5659_MRES_15MO (0x0 << 8) +#define RT5659_MRES_25MO (0x1 << 8) +#define RT5659_MRES_35MO (0x2 << 8) +#define RT5659_MRES_45MO (0x3 << 8) +#define RT5659_VLO_MASK (0x1 << 7) +#define RT5659_VLO_SFT 7 +#define RT5659_VLO_3V (0x0 << 7) +#define RT5659_VLO_32V (0x1 << 7) +#define RT5659_DIG_DP_MASK (0x1 << 6) +#define RT5659_DIG_DP_SFT 6 +#define RT5659_DIG_DP_DIS (0x0 << 6) +#define RT5659_DIG_DP_EN (0x1 << 6) +#define RT5659_DP_TH_MASK (0x3 << 4) +#define RT5659_DP_TH_SFT 4 + +/* Depop Mode Control 3 (0x90) */ +#define RT5659_CP_SYS_MASK (0x7 << 12) +#define RT5659_CP_SYS_SFT 12 +#define RT5659_CP_FQ1_MASK (0x7 << 8) +#define RT5659_CP_FQ1_SFT 8 +#define RT5659_CP_FQ2_MASK (0x7 << 4) +#define RT5659_CP_FQ2_SFT 4 +#define RT5659_CP_FQ3_MASK (0x7) +#define RT5659_CP_FQ3_SFT 0 +#define RT5659_CP_FQ_1_5_KHZ 0 +#define RT5659_CP_FQ_3_KHZ 1 +#define RT5659_CP_FQ_6_KHZ 2 +#define RT5659_CP_FQ_12_KHZ 3 +#define RT5659_CP_FQ_24_KHZ 4 +#define RT5659_CP_FQ_48_KHZ 5 +#define RT5659_CP_FQ_96_KHZ 6 +#define RT5659_CP_FQ_192_KHZ 7 + +/* HPOUT charge pump 1 (0x0091) */ +#define RT5659_OSW_L_MASK (0x1 << 11) +#define RT5659_OSW_L_SFT 11 +#define RT5659_OSW_L_DIS (0x0 << 11) +#define RT5659_OSW_L_EN (0x1 << 11) +#define RT5659_OSW_R_MASK (0x1 << 10) +#define RT5659_OSW_R_SFT 10 +#define RT5659_OSW_R_DIS (0x0 << 10) +#define RT5659_OSW_R_EN (0x1 << 10) +#define RT5659_PM_HP_MASK (0x3 << 8) +#define RT5659_PM_HP_SFT 8 +#define RT5659_PM_HP_LV (0x0 << 8) +#define RT5659_PM_HP_MV (0x1 << 8) +#define RT5659_PM_HP_HV (0x2 << 8) +#define RT5659_IB_HP_MASK (0x3 << 6) +#define RT5659_IB_HP_SFT 6 +#define RT5659_IB_HP_125IL (0x0 << 6) +#define RT5659_IB_HP_25IL (0x1 << 6) +#define RT5659_IB_HP_5IL (0x2 << 6) +#define RT5659_IB_HP_1IL (0x3 << 6) + +/* PV detection and SPK gain control (0x92) */ +#define RT5659_PVDD_DET_MASK (0x1 << 15) +#define RT5659_PVDD_DET_SFT 15 +#define RT5659_PVDD_DET_DIS (0x0 << 15) +#define RT5659_PVDD_DET_EN (0x1 << 15) +#define RT5659_SPK_AG_MASK (0x1 << 14) +#define RT5659_SPK_AG_SFT 14 +#define RT5659_SPK_AG_DIS (0x0 << 14) +#define RT5659_SPK_AG_EN (0x1 << 14) + +/* Micbias Control (0x93) */ +#define RT5659_MIC1_BS_MASK (0x1 << 15) +#define RT5659_MIC1_BS_SFT 15 +#define RT5659_MIC1_BS_9AV (0x0 << 15) +#define RT5659_MIC1_BS_75AV (0x1 << 15) +#define RT5659_MIC2_BS_MASK (0x1 << 14) +#define RT5659_MIC2_BS_SFT 14 +#define RT5659_MIC2_BS_9AV (0x0 << 14) +#define RT5659_MIC2_BS_75AV (0x1 << 14) +#define RT5659_MIC1_CLK_MASK (0x1 << 13) +#define RT5659_MIC1_CLK_SFT 13 +#define RT5659_MIC1_CLK_DIS (0x0 << 13) +#define RT5659_MIC1_CLK_EN (0x1 << 13) +#define RT5659_MIC2_CLK_MASK (0x1 << 12) +#define RT5659_MIC2_CLK_SFT 12 +#define RT5659_MIC2_CLK_DIS (0x0 << 12) +#define RT5659_MIC2_CLK_EN (0x1 << 12) +#define RT5659_MIC1_OVCD_MASK (0x1 << 11) +#define RT5659_MIC1_OVCD_SFT 11 +#define RT5659_MIC1_OVCD_DIS (0x0 << 11) +#define RT5659_MIC1_OVCD_EN (0x1 << 11) +#define RT5659_MIC1_OVTH_MASK (0x3 << 9) +#define RT5659_MIC1_OVTH_SFT 9 +#define RT5659_MIC1_OVTH_600UA (0x0 << 9) +#define RT5659_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5659_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5659_MIC2_OVCD_MASK (0x1 << 8) +#define RT5659_MIC2_OVCD_SFT 8 +#define RT5659_MIC2_OVCD_DIS (0x0 << 8) +#define RT5659_MIC2_OVCD_EN (0x1 << 8) +#define RT5659_MIC2_OVTH_MASK (0x3 << 6) +#define RT5659_MIC2_OVTH_SFT 6 +#define RT5659_MIC2_OVTH_600UA (0x0 << 6) +#define RT5659_MIC2_OVTH_1500UA (0x1 << 6) +#define RT5659_MIC2_OVTH_2000UA (0x2 << 6) +#define RT5659_PWR_MB_MASK (0x1 << 5) +#define RT5659_PWR_MB_SFT 5 +#define RT5659_PWR_MB_PD (0x0 << 5) +#define RT5659_PWR_MB_PU (0x1 << 5) +#define RT5659_PWR_CLK25M_MASK (0x1 << 4) +#define RT5659_PWR_CLK25M_SFT 4 +#define RT5659_PWR_CLK25M_PD (0x0 << 4) +#define RT5659_PWR_CLK25M_PU (0x1 << 4) + +/* REC Mixer 2 Left Control 2 (0x009c) */ +#define RT5659_M_BST1_RM2_L (0x1 << 5) +#define RT5659_M_BST1_RM2_L_SFT 5 +#define RT5659_M_BST2_RM2_L (0x1 << 4) +#define RT5659_M_BST2_RM2_L_SFT 4 +#define RT5659_M_BST3_RM2_L (0x1 << 3) +#define RT5659_M_BST3_RM2_L_SFT 3 +#define RT5659_M_BST4_RM2_L (0x1 << 2) +#define RT5659_M_BST4_RM2_L_SFT 2 +#define RT5659_M_OUTVOLL_RM2_L (0x1 << 1) +#define RT5659_M_OUTVOLL_RM2_L_SFT 1 +#define RT5659_M_SPKVOL_RM2_L (0x1) +#define RT5659_M_SPKVOL_RM2_L_SFT 0 + +/* REC Mixer 2 Right Control 2 (0x009e) */ +#define RT5659_M_BST1_RM2_R (0x1 << 5) +#define RT5659_M_BST1_RM2_R_SFT 5 +#define RT5659_M_BST2_RM2_R (0x1 << 4) +#define RT5659_M_BST2_RM2_R_SFT 4 +#define RT5659_M_BST3_RM2_R (0x1 << 3) +#define RT5659_M_BST3_RM2_R_SFT 3 +#define RT5659_M_BST4_RM2_R (0x1 << 2) +#define RT5659_M_BST4_RM2_R_SFT 2 +#define RT5659_M_OUTVOLR_RM2_R (0x1 << 1) +#define RT5659_M_OUTVOLR_RM2_R_SFT 1 +#define RT5659_M_MONOVOL_RM2_R (0x1) +#define RT5659_M_MONOVOL_RM2_R_SFT 0 + +/* Class D Output Control (0x00a0) */ +#define RT5659_POW_CLSD_DB_MASK (0x1 << 9) +#define RT5659_POW_CLSD_DB_EN (0x1 << 9) +#define RT5659_POW_CLSD_DB_DIS (0x0 << 9) + +/* EQ Control 1 (0x00b0) */ +#define RT5659_EQ_SRC_DAC (0x0 << 15) +#define RT5659_EQ_SRC_ADC (0x1 << 15) +#define RT5659_EQ_UPD (0x1 << 14) +#define RT5659_EQ_UPD_BIT 14 +#define RT5659_EQ_CD_MASK (0x1 << 13) +#define RT5659_EQ_CD_SFT 13 +#define RT5659_EQ_CD_DIS (0x0 << 13) +#define RT5659_EQ_CD_EN (0x1 << 13) +#define RT5659_EQ_DITH_MASK (0x3 << 8) +#define RT5659_EQ_DITH_SFT 8 +#define RT5659_EQ_DITH_NOR (0x0 << 8) +#define RT5659_EQ_DITH_LSB (0x1 << 8) +#define RT5659_EQ_DITH_LSB_1 (0x2 << 8) +#define RT5659_EQ_DITH_LSB_2 (0x3 << 8) + +/* IRQ Control 1 (0x00b7) */ +#define RT5659_JD1_1_EN_MASK (0x1 << 15) +#define RT5659_JD1_1_EN_SFT 15 +#define RT5659_JD1_1_DIS (0x0 << 15) +#define RT5659_JD1_1_EN (0x1 << 15) +#define RT5659_JD1_2_EN_MASK (0x1 << 12) +#define RT5659_JD1_2_EN_SFT 12 +#define RT5659_JD1_2_DIS (0x0 << 12) +#define RT5659_JD1_2_EN (0x1 << 12) +#define RT5659_IL_IRQ_MASK (0x1 << 3) +#define RT5659_IL_IRQ_DIS (0x0 << 3) +#define RT5659_IL_IRQ_EN (0x1 << 3) + +/* IRQ Control 5 (0x00ba) */ +#define RT5659_IRQ_JD_EN (0x1 << 3) +#define RT5659_IRQ_JD_EN_SFT 3 + +/* GPIO Control 1 (0x00c0) */ +#define RT5659_GP1_PIN_MASK (0x1 << 15) +#define RT5659_GP1_PIN_SFT 15 +#define RT5659_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5659_GP1_PIN_IRQ (0x1 << 15) +#define RT5659_GP2_PIN_MASK (0x1 << 14) +#define RT5659_GP2_PIN_SFT 14 +#define RT5659_GP2_PIN_GPIO2 (0x0 << 14) +#define RT5659_GP2_PIN_DMIC1_SCL (0x1 << 14) +#define RT5659_GP3_PIN_MASK (0x1 << 13) +#define RT5659_GP3_PIN_SFT 13 +#define RT5659_GP3_PIN_GPIO3 (0x0 << 13) +#define RT5659_GP3_PIN_PDM_SCL (0x1 << 13) +#define RT5659_GP4_PIN_MASK (0x1 << 12) +#define RT5659_GP4_PIN_SFT 12 +#define RT5659_GP4_PIN_GPIO4 (0x0 << 12) +#define RT5659_GP4_PIN_PDM_SDA (0x1 << 12) +#define RT5659_GP5_PIN_MASK (0x1 << 11) +#define RT5659_GP5_PIN_SFT 11 +#define RT5659_GP5_PIN_GPIO5 (0x0 << 11) +#define RT5659_GP5_PIN_DMIC1_SDA (0x1 << 11) +#define RT5659_GP6_PIN_MASK (0x1 << 10) +#define RT5659_GP6_PIN_SFT 10 +#define RT5659_GP6_PIN_GPIO6 (0x0 << 10) +#define RT5659_GP6_PIN_DMIC2_SDA (0x1 << 10) +#define RT5659_GP7_PIN_MASK (0x1 << 9) +#define RT5659_GP7_PIN_SFT 9 +#define RT5659_GP7_PIN_GPIO7 (0x0 << 9) +#define RT5659_GP7_PIN_PDM_SCL (0x1 << 9) +#define RT5659_GP8_PIN_MASK (0x1 << 8) +#define RT5659_GP8_PIN_SFT 8 +#define RT5659_GP8_PIN_GPIO8 (0x0 << 8) +#define RT5659_GP8_PIN_PDM_SDA (0x1 << 8) +#define RT5659_GP9_PIN_MASK (0x1 << 7) +#define RT5659_GP9_PIN_SFT 7 +#define RT5659_GP9_PIN_GPIO9 (0x0 << 7) +#define RT5659_GP9_PIN_DMIC1_SDA (0x1 << 7) +#define RT5659_GP10_PIN_MASK (0x1 << 6) +#define RT5659_GP10_PIN_SFT 6 +#define RT5659_GP10_PIN_GPIO10 (0x0 << 6) +#define RT5659_GP10_PIN_DMIC2_SDA (0x1 << 6) +#define RT5659_GP11_PIN_MASK (0x1 << 5) +#define RT5659_GP11_PIN_SFT 5 +#define RT5659_GP11_PIN_GPIO11 (0x0 << 5) +#define RT5659_GP11_PIN_DMIC1_SDA (0x1 << 5) +#define RT5659_GP12_PIN_MASK (0x1 << 4) +#define RT5659_GP12_PIN_SFT 4 +#define RT5659_GP12_PIN_GPIO12 (0x0 << 4) +#define RT5659_GP12_PIN_DMIC2_SDA (0x1 << 4) +#define RT5659_GP13_PIN_MASK (0x3 << 2) +#define RT5659_GP13_PIN_SFT 2 +#define RT5659_GP13_PIN_GPIO13 (0x0 << 2) +#define RT5659_GP13_PIN_SPDIF_SDA (0x1 << 2) +#define RT5659_GP13_PIN_DMIC2_SCL (0x2 << 2) +#define RT5659_GP13_PIN_PDM_SCL (0x3 << 2) +#define RT5659_GP15_PIN_MASK (0x3) +#define RT5659_GP15_PIN_SFT 0 +#define RT5659_GP15_PIN_GPIO15 (0x0) +#define RT5659_GP15_PIN_DMIC3_SCL (0x1) +#define RT5659_GP15_PIN_PDM_SDA (0x2) + +/* GPIO Control 2 (0x00c1)*/ +#define RT5659_GP1_PF_IN (0x0 << 2) +#define RT5659_GP1_PF_OUT (0x1 << 2) +#define RT5659_GP1_PF_MASK (0x1 << 2) +#define RT5659_GP1_PF_SFT 2 + +/* GPIO Control 3 (0x00c2) */ +#define RT5659_I2S2_PIN_MASK (0x1 << 15) +#define RT5659_I2S2_PIN_SFT 15 +#define RT5659_I2S2_PIN_I2S (0x0 << 15) +#define RT5659_I2S2_PIN_GPIO (0x1 << 15) + +/* Soft volume and zero cross control 1 (0x00d9) */ +#define RT5659_SV_MASK (0x1 << 15) +#define RT5659_SV_SFT 15 +#define RT5659_SV_DIS (0x0 << 15) +#define RT5659_SV_EN (0x1 << 15) +#define RT5659_OUT_SV_MASK (0x1 << 13) +#define RT5659_OUT_SV_SFT 13 +#define RT5659_OUT_SV_DIS (0x0 << 13) +#define RT5659_OUT_SV_EN (0x1 << 13) +#define RT5659_HP_SV_MASK (0x1 << 12) +#define RT5659_HP_SV_SFT 12 +#define RT5659_HP_SV_DIS (0x0 << 12) +#define RT5659_HP_SV_EN (0x1 << 12) +#define RT5659_ZCD_DIG_MASK (0x1 << 11) +#define RT5659_ZCD_DIG_SFT 11 +#define RT5659_ZCD_DIG_DIS (0x0 << 11) +#define RT5659_ZCD_DIG_EN (0x1 << 11) +#define RT5659_ZCD_MASK (0x1 << 10) +#define RT5659_ZCD_SFT 10 +#define RT5659_ZCD_PD (0x0 << 10) +#define RT5659_ZCD_PU (0x1 << 10) +#define RT5659_SV_DLY_MASK (0xf) +#define RT5659_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0x00da) */ +#define RT5659_ZCD_HP_MASK (0x1 << 15) +#define RT5659_ZCD_HP_SFT 15 +#define RT5659_ZCD_HP_DIS (0x0 << 15) +#define RT5659_ZCD_HP_EN (0x1 << 15) + +/* 4 Button Inline Command Control 2 (0x00e0) */ +#define RT5659_4BTN_IL_MASK (0x1 << 15) +#define RT5659_4BTN_IL_EN (0x1 << 15) +#define RT5659_4BTN_IL_DIS (0x0 << 15) + +/* Analog JD Control 1 (0x00f0) */ +#define RT5659_JD1_MODE_MASK (0x3 << 0) +#define RT5659_JD1_MODE_0 (0x0 << 0) +#define RT5659_JD1_MODE_1 (0x1 << 0) +#define RT5659_JD1_MODE_2 (0x2 << 0) + +/* Jack Detect Control 3 (0x00f8) */ +#define RT5659_JD_TRI_HPO_SEL_MASK (0x7) +#define RT5659_JD_TRI_HPO_SEL_SFT (0) +#define RT5659_JD_HPO_GPIO_JD1 (0x0) +#define RT5659_JD_HPO_JD1_1 (0x1) +#define RT5659_JD_HPO_JD1_2 (0x2) +#define RT5659_JD_HPO_JD2 (0x3) +#define RT5659_JD_HPO_GPIO_JD2 (0x4) +#define RT5659_JD_HPO_JD3 (0x5) +#define RT5659_JD_HPO_JD_D (0x6) + +/* Digital Misc Control (0x00fa) */ +#define RT5659_AM_MASK (0x1 << 7) +#define RT5659_AM_EN (0x1 << 7) +#define RT5659_AM_DIS (0x1 << 7) +#define RT5659_DIG_GATE_CTRL 0x1 +#define RT5659_DIG_GATE_CTRL_SFT (0) + +/* Chopper and Clock control for ADC (0x011c)*/ +#define RT5659_M_RF_DIG_MASK (0x1 << 12) +#define RT5659_M_RF_DIG_SFT 12 +#define RT5659_M_RI_DIG (0x1 << 11) + +/* Chopper and Clock control for DAC (0x013a)*/ +#define RT5659_CKXEN_DAC1_MASK (0x1 << 13) +#define RT5659_CKXEN_DAC1_SFT 13 +#define RT5659_CKGEN_DAC1_MASK (0x1 << 12) +#define RT5659_CKGEN_DAC1_SFT 12 +#define RT5659_CKXEN_DAC2_MASK (0x1 << 5) +#define RT5659_CKXEN_DAC2_SFT 5 +#define RT5659_CKGEN_DAC2_MASK (0x1 << 4) +#define RT5659_CKGEN_DAC2_SFT 4 + +/* Chopper and Clock control for ADC (0x013b)*/ +#define RT5659_CKXEN_ADC1_MASK (0x1 << 13) +#define RT5659_CKXEN_ADC1_SFT 13 +#define RT5659_CKGEN_ADC1_MASK (0x1 << 12) +#define RT5659_CKGEN_ADC1_SFT 12 +#define RT5659_CKXEN_ADC2_MASK (0x1 << 5) +#define RT5659_CKXEN_ADC2_SFT 5 +#define RT5659_CKGEN_ADC2_MASK (0x1 << 4) +#define RT5659_CKGEN_ADC2_SFT 4 + +/* Test Mode Control 1 (0x0145) */ +#define RT5659_AD2DA_LB_MASK (0x1 << 9) +#define RT5659_AD2DA_LB_SFT 9 + +/* Stereo Noise Gate Control 1 (0x0160) */ +#define RT5659_NG2_EN_MASK (0x1 << 15) +#define RT5659_NG2_EN (0x1 << 15) +#define RT5659_NG2_DIS (0x0 << 15) + +/* System Clock Source */ +enum { + RT5659_SCLK_S_MCLK, + RT5659_SCLK_S_PLL1, + RT5659_SCLK_S_RCCLK, +}; + +/* PLL1 Source */ +enum { + RT5659_PLL1_S_MCLK, + RT5659_PLL1_S_BCLK1, + RT5659_PLL1_S_BCLK2, + RT5659_PLL1_S_BCLK3, + RT5659_PLL1_S_BCLK4, +}; + +enum { + RT5659_AIF1, + RT5659_AIF2, + RT5659_AIF3, + RT5659_AIF4, + RT5659_AIFS, +}; + +struct rt5659_pll_code { + bool m_bp; + int m_code; + int n_code; + int k_code; +}; + +struct rt5659_priv { + struct snd_soc_component *component; + struct rt5659_platform_data pdata; + struct regmap *regmap; + struct gpio_desc *gpiod_ldo1_en; + struct gpio_desc *gpiod_reset; + struct snd_soc_jack *hs_jack; + struct delayed_work jack_detect_work; + struct clk *mclk; + + int sysclk; + int sysclk_src; + int lrck[RT5659_AIFS]; + int bclk[RT5659_AIFS]; + int master[RT5659_AIFS]; + int v_id; + + int pll_src; + int pll_in; + int pll_out; + + int jack_type; + bool hda_hp_plugged; + bool hda_mic_plugged; +}; + +int rt5659_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack); + +#endif /* __RT5659_H__ */ diff --git a/sound/soc/codecs/rt5660.c b/sound/soc/codecs/rt5660.c new file mode 100644 index 000000000..20a755137 --- /dev/null +++ b/sound/soc/codecs/rt5660.c @@ -0,0 +1,1350 @@ +/* + * rt5660.c -- RT5660 ALSA SoC audio codec driver + * + * Copyright 2016 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5660.h" + +#define RT5660_DEVICE_ID 0x6338 + +#define RT5660_PR_RANGE_BASE (0xff + 1) +#define RT5660_PR_SPACING 0x100 + +#define RT5660_PR_BASE (RT5660_PR_RANGE_BASE + (0 * RT5660_PR_SPACING)) + +static const struct regmap_range_cfg rt5660_ranges[] = { + { .name = "PR", .range_min = RT5660_PR_BASE, + .range_max = RT5660_PR_BASE + 0xf3, + .selector_reg = RT5660_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT5660_PRIV_DATA, + .window_len = 0x1, }, +}; + +static const struct reg_sequence rt5660_patch[] = { + { RT5660_ALC_PGA_CTRL2, 0x44c3 }, + { RT5660_PR_BASE + 0x3d, 0x2600 }, +}; + +static const struct reg_default rt5660_reg[] = { + { 0x00, 0x0000 }, + { 0x01, 0xc800 }, + { 0x02, 0xc8c8 }, + { 0x0d, 0x1010 }, + { 0x0e, 0x1010 }, + { 0x19, 0xafaf }, + { 0x1c, 0x2f2f }, + { 0x1e, 0x0000 }, + { 0x27, 0x6060 }, + { 0x29, 0x8080 }, + { 0x2a, 0x4242 }, + { 0x2f, 0x0000 }, + { 0x3b, 0x0000 }, + { 0x3c, 0x007f }, + { 0x3d, 0x0000 }, + { 0x3e, 0x007f }, + { 0x45, 0xe000 }, + { 0x46, 0x003e }, + { 0x48, 0xf800 }, + { 0x4a, 0x0004 }, + { 0x4d, 0x0000 }, + { 0x4e, 0x0000 }, + { 0x4f, 0x01ff }, + { 0x50, 0x0000 }, + { 0x51, 0x0000 }, + { 0x52, 0x01ff }, + { 0x61, 0x0000 }, + { 0x62, 0x0000 }, + { 0x63, 0x00c0 }, + { 0x64, 0x0000 }, + { 0x65, 0x0000 }, + { 0x66, 0x0000 }, + { 0x70, 0x8000 }, + { 0x73, 0x7000 }, + { 0x74, 0x3c00 }, + { 0x75, 0x2800 }, + { 0x80, 0x0000 }, + { 0x81, 0x0000 }, + { 0x82, 0x0000 }, + { 0x8c, 0x0228 }, + { 0x8d, 0xa000 }, + { 0x8e, 0x0000 }, + { 0x92, 0x0000 }, + { 0x93, 0x3000 }, + { 0xa1, 0x0059 }, + { 0xa2, 0x0001 }, + { 0xa3, 0x5c80 }, + { 0xa4, 0x0146 }, + { 0xa5, 0x1f1f }, + { 0xa6, 0x78c6 }, + { 0xa7, 0xe5ec }, + { 0xa8, 0xba61 }, + { 0xa9, 0x3c78 }, + { 0xaa, 0x8ae2 }, + { 0xab, 0xe5ec }, + { 0xac, 0xc600 }, + { 0xad, 0xba61 }, + { 0xae, 0x17ed }, + { 0xb0, 0x2080 }, + { 0xb1, 0x0000 }, + { 0xb3, 0x001f }, + { 0xb4, 0x020c }, + { 0xb5, 0x1f00 }, + { 0xb6, 0x0000 }, + { 0xb7, 0x4000 }, + { 0xbb, 0x0000 }, + { 0xbd, 0x0000 }, + { 0xbe, 0x0000 }, + { 0xbf, 0x0100 }, + { 0xc0, 0x0000 }, + { 0xc2, 0x0000 }, + { 0xd3, 0xa220 }, + { 0xd9, 0x0809 }, + { 0xda, 0x0000 }, + { 0xe0, 0x8000 }, + { 0xe1, 0x0200 }, + { 0xe2, 0x8000 }, + { 0xe3, 0x0200 }, + { 0xe4, 0x0f20 }, + { 0xe5, 0x001f }, + { 0xe6, 0x020c }, + { 0xe7, 0x1f00 }, + { 0xe8, 0x0000 }, + { 0xe9, 0x4000 }, + { 0xea, 0x00a6 }, + { 0xeb, 0x04c3 }, + { 0xec, 0x27c8 }, + { 0xed, 0x7418 }, + { 0xee, 0xbf50 }, + { 0xef, 0x0045 }, + { 0xf0, 0x0007 }, + { 0xfa, 0x0000 }, + { 0xfd, 0x0000 }, + { 0xfe, 0x10ec }, + { 0xff, 0x6338 }, +}; + +static bool rt5660_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5660_ranges); i++) + if ((reg >= rt5660_ranges[i].window_start && + reg <= rt5660_ranges[i].window_start + + rt5660_ranges[i].window_len) || + (reg >= rt5660_ranges[i].range_min && + reg <= rt5660_ranges[i].range_max)) + return true; + + switch (reg) { + case RT5660_RESET: + case RT5660_PRIV_DATA: + case RT5660_EQ_CTRL1: + case RT5660_IRQ_CTRL2: + case RT5660_INT_IRQ_ST: + case RT5660_VENDOR_ID: + case RT5660_VENDOR_ID1: + case RT5660_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool rt5660_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5660_ranges); i++) + if ((reg >= rt5660_ranges[i].window_start && + reg <= rt5660_ranges[i].window_start + + rt5660_ranges[i].window_len) || + (reg >= rt5660_ranges[i].range_min && + reg <= rt5660_ranges[i].range_max)) + return true; + + switch (reg) { + case RT5660_RESET: + case RT5660_SPK_VOL: + case RT5660_LOUT_VOL: + case RT5660_IN1_IN2: + case RT5660_IN3_IN4: + case RT5660_DAC1_DIG_VOL: + case RT5660_STO1_ADC_DIG_VOL: + case RT5660_ADC_BST_VOL1: + case RT5660_STO1_ADC_MIXER: + case RT5660_AD_DA_MIXER: + case RT5660_STO_DAC_MIXER: + case RT5660_DIG_INF1_DATA: + case RT5660_REC_L1_MIXER: + case RT5660_REC_L2_MIXER: + case RT5660_REC_R1_MIXER: + case RT5660_REC_R2_MIXER: + case RT5660_LOUT_MIXER: + case RT5660_SPK_MIXER: + case RT5660_SPO_MIXER: + case RT5660_SPO_CLSD_RATIO: + case RT5660_OUT_L_GAIN1: + case RT5660_OUT_L_GAIN2: + case RT5660_OUT_L1_MIXER: + case RT5660_OUT_R_GAIN1: + case RT5660_OUT_R_GAIN2: + case RT5660_OUT_R1_MIXER: + case RT5660_PWR_DIG1: + case RT5660_PWR_DIG2: + case RT5660_PWR_ANLG1: + case RT5660_PWR_ANLG2: + case RT5660_PWR_MIXER: + case RT5660_PWR_VOL: + case RT5660_PRIV_INDEX: + case RT5660_PRIV_DATA: + case RT5660_I2S1_SDP: + case RT5660_ADDA_CLK1: + case RT5660_ADDA_CLK2: + case RT5660_DMIC_CTRL1: + case RT5660_GLB_CLK: + case RT5660_PLL_CTRL1: + case RT5660_PLL_CTRL2: + case RT5660_CLSD_AMP_OC_CTRL: + case RT5660_CLSD_AMP_CTRL: + case RT5660_LOUT_AMP_CTRL: + case RT5660_SPK_AMP_SPKVDD: + case RT5660_MICBIAS: + case RT5660_CLSD_OUT_CTRL1: + case RT5660_CLSD_OUT_CTRL2: + case RT5660_DIPOLE_MIC_CTRL1: + case RT5660_DIPOLE_MIC_CTRL2: + case RT5660_DIPOLE_MIC_CTRL3: + case RT5660_DIPOLE_MIC_CTRL4: + case RT5660_DIPOLE_MIC_CTRL5: + case RT5660_DIPOLE_MIC_CTRL6: + case RT5660_DIPOLE_MIC_CTRL7: + case RT5660_DIPOLE_MIC_CTRL8: + case RT5660_DIPOLE_MIC_CTRL9: + case RT5660_DIPOLE_MIC_CTRL10: + case RT5660_DIPOLE_MIC_CTRL11: + case RT5660_DIPOLE_MIC_CTRL12: + case RT5660_EQ_CTRL1: + case RT5660_EQ_CTRL2: + case RT5660_DRC_AGC_CTRL1: + case RT5660_DRC_AGC_CTRL2: + case RT5660_DRC_AGC_CTRL3: + case RT5660_DRC_AGC_CTRL4: + case RT5660_DRC_AGC_CTRL5: + case RT5660_JD_CTRL: + case RT5660_IRQ_CTRL1: + case RT5660_IRQ_CTRL2: + case RT5660_INT_IRQ_ST: + case RT5660_GPIO_CTRL1: + case RT5660_GPIO_CTRL2: + case RT5660_WIND_FILTER_CTRL1: + case RT5660_SV_ZCD1: + case RT5660_SV_ZCD2: + case RT5660_DRC1_LM_CTRL1: + case RT5660_DRC1_LM_CTRL2: + case RT5660_DRC2_LM_CTRL1: + case RT5660_DRC2_LM_CTRL2: + case RT5660_MULTI_DRC_CTRL: + case RT5660_DRC2_CTRL1: + case RT5660_DRC2_CTRL2: + case RT5660_DRC2_CTRL3: + case RT5660_DRC2_CTRL4: + case RT5660_DRC2_CTRL5: + case RT5660_ALC_PGA_CTRL1: + case RT5660_ALC_PGA_CTRL2: + case RT5660_ALC_PGA_CTRL3: + case RT5660_ALC_PGA_CTRL4: + case RT5660_ALC_PGA_CTRL5: + case RT5660_ALC_PGA_CTRL6: + case RT5660_ALC_PGA_CTRL7: + case RT5660_GEN_CTRL1: + case RT5660_GEN_CTRL2: + case RT5660_GEN_CTRL3: + case RT5660_VENDOR_ID: + case RT5660_VENDOR_ID1: + case RT5660_VENDOR_ID2: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(rt5660_out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(rt5660_dac_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(rt5660_adc_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(rt5660_adc_bst_tlv, 0, 1200, 0); +static const DECLARE_TLV_DB_SCALE(rt5660_bst_tlv, -1200, 75, 0); + +static const struct snd_kcontrol_new rt5660_snd_controls[] = { + /* Speaker Output Volume */ + SOC_SINGLE("Speaker Playback Switch", RT5660_SPK_VOL, RT5660_L_MUTE_SFT, + 1, 1), + SOC_SINGLE_TLV("Speaker Playback Volume", RT5660_SPK_VOL, + RT5660_L_VOL_SFT, 39, 1, rt5660_out_vol_tlv), + + /* OUTPUT Control */ + SOC_DOUBLE("OUT Playback Switch", RT5660_LOUT_VOL, RT5660_L_MUTE_SFT, + RT5660_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("OUT Playback Volume", RT5660_LOUT_VOL, RT5660_L_VOL_SFT, + RT5660_R_VOL_SFT, 39, 1, rt5660_out_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5660_DAC1_DIG_VOL, + RT5660_DAC_L1_VOL_SFT, RT5660_DAC_R1_VOL_SFT, 87, 0, + rt5660_dac_vol_tlv), + + /* IN1/IN2/IN3 Control */ + SOC_SINGLE_TLV("IN1 Boost Volume", RT5660_IN1_IN2, RT5660_BST_SFT1, 69, + 0, rt5660_bst_tlv), + SOC_SINGLE_TLV("IN2 Boost Volume", RT5660_IN1_IN2, RT5660_BST_SFT2, 69, + 0, rt5660_bst_tlv), + SOC_SINGLE_TLV("IN3 Boost Volume", RT5660_IN3_IN4, RT5660_BST_SFT3, 69, + 0, rt5660_bst_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC Capture Switch", RT5660_STO1_ADC_DIG_VOL, + RT5660_L_MUTE_SFT, RT5660_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("ADC Capture Volume", RT5660_STO1_ADC_DIG_VOL, + RT5660_ADC_L_VOL_SFT, RT5660_ADC_R_VOL_SFT, 63, 0, + rt5660_adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Gain Volume", RT5660_ADC_BST_VOL1, + RT5660_STO1_ADC_L_BST_SFT, RT5660_STO1_ADC_R_BST_SFT, 3, 0, + rt5660_adc_bst_tlv), +}; + +/** + * rt5660_set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + */ +static int rt5660_set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + int idx, rate; + + rate = rt5660->sysclk / rl6231_get_pre_div(rt5660->regmap, + RT5660_ADDA_CLK1, RT5660_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rate); + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else + snd_soc_component_update_bits(component, RT5660_DMIC_CTRL1, + RT5660_DMIC_CLK_MASK, idx << RT5660_DMIC_CLK_SFT); + + return idx; +} + +static int rt5660_is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int val; + + val = snd_soc_component_read32(component, RT5660_GLB_CLK); + val &= RT5660_SCLK_SRC_MASK; + if (val == RT5660_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5660_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5660_STO1_ADC_MIXER, + RT5660_M_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5660_STO1_ADC_MIXER, + RT5660_M_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5660_STO1_ADC_MIXER, + RT5660_M_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5660_STO1_ADC_MIXER, + RT5660_M_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5660_AD_DA_MIXER, + RT5660_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5660_AD_DA_MIXER, + RT5660_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5660_AD_DA_MIXER, + RT5660_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5660_AD_DA_MIXER, + RT5660_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5660_STO_DAC_MIXER, + RT5660_M_DAC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5660_STO_DAC_MIXER, + RT5660_M_DAC_R1_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5660_STO_DAC_MIXER, + RT5660_M_DAC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5660_STO_DAC_MIXER, + RT5660_M_DAC_L1_STO_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5660_rec_l_mix[] = { + SOC_DAPM_SINGLE("BST3 Switch", RT5660_REC_L2_MIXER, + RT5660_M_BST3_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5660_REC_L2_MIXER, + RT5660_M_BST2_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5660_REC_L2_MIXER, + RT5660_M_BST1_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXL Switch", RT5660_REC_L2_MIXER, + RT5660_M_OM_L_RM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_rec_r_mix[] = { + SOC_DAPM_SINGLE("BST3 Switch", RT5660_REC_R2_MIXER, + RT5660_M_BST3_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5660_REC_R2_MIXER, + RT5660_M_BST2_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5660_REC_R2_MIXER, + RT5660_M_BST1_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("OUT MIXR Switch", RT5660_REC_R2_MIXER, + RT5660_M_OM_R_RM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_spk_mix[] = { + SOC_DAPM_SINGLE("BST3 Switch", RT5660_SPK_MIXER, + RT5660_M_BST3_SM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5660_SPK_MIXER, + RT5660_M_BST1_SM_SFT, 1, 1), + SOC_DAPM_SINGLE("DACL Switch", RT5660_SPK_MIXER, + RT5660_M_DACL_SM_SFT, 1, 1), + SOC_DAPM_SINGLE("DACR Switch", RT5660_SPK_MIXER, + RT5660_M_DACR_SM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTMIXL Switch", RT5660_SPK_MIXER, + RT5660_M_OM_L_SM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_out_l_mix[] = { + SOC_DAPM_SINGLE("BST3 Switch", RT5660_OUT_L1_MIXER, + RT5660_M_BST3_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5660_OUT_L1_MIXER, + RT5660_M_BST2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5660_OUT_L1_MIXER, + RT5660_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIXL Switch", RT5660_OUT_L1_MIXER, + RT5660_M_RM_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DACR Switch", RT5660_OUT_L1_MIXER, + RT5660_M_DAC_R_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DACL Switch", RT5660_OUT_L1_MIXER, + RT5660_M_DAC_L_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_out_r_mix[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5660_OUT_R1_MIXER, + RT5660_M_BST2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5660_OUT_R1_MIXER, + RT5660_M_BST1_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIXR Switch", RT5660_OUT_R1_MIXER, + RT5660_M_RM_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DACR Switch", RT5660_OUT_R1_MIXER, + RT5660_M_DAC_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DACL Switch", RT5660_OUT_R1_MIXER, + RT5660_M_DAC_L_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_spo_mix[] = { + SOC_DAPM_SINGLE("DACR Switch", RT5660_SPO_MIXER, + RT5660_M_DAC_R_SPM_SFT, 1, 1), + SOC_DAPM_SINGLE("DACL Switch", RT5660_SPO_MIXER, + RT5660_M_DAC_L_SPM_SFT, 1, 1), + SOC_DAPM_SINGLE("SPKVOL Switch", RT5660_SPO_MIXER, + RT5660_M_SV_SPM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5660_SPO_MIXER, + RT5660_M_BST1_SPM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5660_lout_mix[] = { + SOC_DAPM_SINGLE("DAC Switch", RT5660_LOUT_MIXER, + RT5660_M_DAC1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTMIX Switch", RT5660_LOUT_MIXER, + RT5660_M_LOVOL_LM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new spk_vol_control = + SOC_DAPM_SINGLE("Switch", RT5660_SPK_VOL, + RT5660_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_l_vol_control = + SOC_DAPM_SINGLE("Switch", RT5660_LOUT_VOL, + RT5660_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_r_vol_control = + SOC_DAPM_SINGLE("Switch", RT5660_LOUT_VOL, + RT5660_VOL_R_SFT, 1, 1); + +/* Interface data select */ +static const char * const rt5660_data_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5660_if1_dac_enum, + RT5660_DIG_INF1_DATA, RT5660_IF1_DAC_IN_SFT, rt5660_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5660_if1_adc_enum, + RT5660_DIG_INF1_DATA, RT5660_IF1_ADC_IN_SFT, rt5660_data_select); + +static const struct snd_kcontrol_new rt5660_if1_dac_swap_mux = + SOC_DAPM_ENUM("IF1 DAC Swap Source", rt5660_if1_dac_enum); + +static const struct snd_kcontrol_new rt5660_if1_adc_swap_mux = + SOC_DAPM_ENUM("IF1 ADC Swap Source", rt5660_if1_adc_enum); + +static int rt5660_lout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5660_LOUT_AMP_CTRL, + RT5660_LOUT_CO_MASK | RT5660_LOUT_CB_MASK, + RT5660_LOUT_CO_EN | RT5660_LOUT_CB_PU); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5660_LOUT_AMP_CTRL, + RT5660_LOUT_CO_MASK | RT5660_LOUT_CB_MASK, + RT5660_LOUT_CO_DIS | RT5660_LOUT_CB_PD); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5660_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO2", RT5660_PWR_ANLG1, + RT5660_PWR_LDO2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", RT5660_PWR_ANLG2, + RT5660_PWR_PLL_BIT, 0, NULL, 0), + + /* MICBIAS */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5660_PWR_ANLG2, + RT5660_PWR_MB1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", RT5660_PWR_ANLG2, + RT5660_PWR_MB2_BIT, 0, NULL, 0), + + /* Input Side */ + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN3P"), + SND_SOC_DAPM_INPUT("IN3N"), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + rt5660_set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC Power", RT5660_DMIC_CTRL1, + RT5660_DMIC_1_EN_SFT, 0, NULL, 0), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1", RT5660_PWR_ANLG2, RT5660_PWR_BST1_BIT, 0, + NULL, 0), + SND_SOC_DAPM_PGA("BST2", RT5660_PWR_ANLG2, RT5660_PWR_BST2_BIT, 0, + NULL, 0), + SND_SOC_DAPM_PGA("BST3", RT5660_PWR_ANLG2, RT5660_PWR_BST3_BIT, 0, + NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIXL", RT5660_PWR_MIXER, RT5660_PWR_RM_L_BIT, + 0, rt5660_rec_l_mix, ARRAY_SIZE(rt5660_rec_l_mix)), + SND_SOC_DAPM_MIXER("RECMIXR", RT5660_PWR_MIXER, RT5660_PWR_RM_R_BIT, + 0, rt5660_rec_r_mix, ARRAY_SIZE(rt5660_rec_r_mix)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC R", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("ADC L power", RT5660_PWR_DIG1, + RT5660_PWR_ADC_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC R power", RT5660_PWR_DIG1, + RT5660_PWR_ADC_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC clock", RT5660_PR_BASE + RT5660_CHOP_DAC_ADC, + 12, 0, NULL, 0), + + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("adc stereo1 filter", RT5660_PWR_DIG2, + RT5660_PWR_ADC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Sto1 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5660_sto1_adc_l_mix, ARRAY_SIZE(rt5660_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto1 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5660_sto1_adc_r_mix, ARRAY_SIZE(rt5660_sto1_adc_r_mix)), + + /* ADC */ + SND_SOC_DAPM_ADC("Stereo1 ADC MIXL", NULL, RT5660_STO1_ADC_DIG_VOL, + RT5660_L_MUTE_SFT, 1), + SND_SOC_DAPM_ADC("Stereo1 ADC MIXR", NULL, RT5660_STO1_ADC_DIG_VOL, + RT5660_R_MUTE_SFT, 1), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5660_PWR_DIG1, RT5660_PWR_I2S1_BIT, 0, + NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("IF1 DAC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5660_if1_dac_swap_mux), + SND_SOC_DAPM_PGA("IF1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("IF1 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5660_if1_adc_swap_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, rt5660_dac_l_mix, + ARRAY_SIZE(rt5660_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, rt5660_dac_r_mix, + ARRAY_SIZE(rt5660_dac_r_mix)), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("dac stereo1 filter", RT5660_PWR_DIG2, + RT5660_PWR_DAC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5660_sto_dac_l_mix, ARRAY_SIZE(rt5660_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5660_sto_dac_r_mix, ARRAY_SIZE(rt5660_sto_dac_r_mix)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, RT5660_PWR_DIG1, + RT5660_PWR_DAC_L1_BIT, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, RT5660_PWR_DIG1, + RT5660_PWR_DAC_R1_BIT, 0), + + /* OUT Mixer */ + SND_SOC_DAPM_MIXER("SPK MIX", RT5660_PWR_MIXER, RT5660_PWR_SM_BIT, + 0, rt5660_spk_mix, ARRAY_SIZE(rt5660_spk_mix)), + SND_SOC_DAPM_MIXER("OUT MIXL", RT5660_PWR_MIXER, RT5660_PWR_OM_L_BIT, + 0, rt5660_out_l_mix, ARRAY_SIZE(rt5660_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5660_PWR_MIXER, RT5660_PWR_OM_R_BIT, + 0, rt5660_out_r_mix, ARRAY_SIZE(rt5660_out_r_mix)), + + /* Output Volume */ + SND_SOC_DAPM_SWITCH("SPKVOL", RT5660_PWR_VOL, + RT5660_PWR_SV_BIT, 0, &spk_vol_control), + SND_SOC_DAPM_PGA("DAC 1", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("LOUTVOL", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("LOUTVOL L", SND_SOC_NOPM, + RT5660_PWR_LV_L_BIT, 0, &lout_l_vol_control), + SND_SOC_DAPM_SWITCH("LOUTVOL R", SND_SOC_NOPM, + RT5660_PWR_LV_R_BIT, 0, &lout_r_vol_control), + + /* HPO/LOUT/Mono Mixer */ + SND_SOC_DAPM_MIXER("SPO MIX", SND_SOC_NOPM, 0, + 0, rt5660_spo_mix, ARRAY_SIZE(rt5660_spo_mix)), + SND_SOC_DAPM_MIXER("LOUT MIX", SND_SOC_NOPM, 0, 0, + rt5660_lout_mix, ARRAY_SIZE(rt5660_lout_mix)), + SND_SOC_DAPM_SUPPLY("VREF HP", RT5660_GEN_CTRL1, + RT5660_PWR_VREF_HP_SFT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("LOUT amp", 1, RT5660_PWR_ANLG1, + RT5660_PWR_HA_BIT, 0, rt5660_lout_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("SPK amp", 1, RT5660_PWR_DIG1, + RT5660_PWR_CLS_D_BIT, 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), + SND_SOC_DAPM_OUTPUT("SPO"), +}; + +static const struct snd_soc_dapm_route rt5660_dapm_routes[] = { + { "MICBIAS1", NULL, "LDO2" }, + { "MICBIAS2", NULL, "LDO2" }, + + { "BST1", NULL, "IN1P" }, + { "BST1", NULL, "IN1N" }, + { "BST2", NULL, "IN2P" }, + { "BST3", NULL, "IN3P" }, + { "BST3", NULL, "IN3N" }, + + { "RECMIXL", "BST3 Switch", "BST3" }, + { "RECMIXL", "BST2 Switch", "BST2" }, + { "RECMIXL", "BST1 Switch", "BST1" }, + { "RECMIXL", "OUT MIXL Switch", "OUT MIXL" }, + + { "RECMIXR", "BST3 Switch", "BST3" }, + { "RECMIXR", "BST2 Switch", "BST2" }, + { "RECMIXR", "BST1 Switch", "BST1" }, + { "RECMIXR", "OUT MIXR Switch", "OUT MIXR" }, + + { "ADC L", NULL, "RECMIXL" }, + { "ADC L", NULL, "ADC L power" }, + { "ADC L", NULL, "ADC clock" }, + { "ADC R", NULL, "RECMIXR" }, + { "ADC R", NULL, "ADC R power" }, + { "ADC R", NULL, "ADC clock" }, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC L1", NULL, "DMIC Power"}, + {"DMIC R1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC Power"}, + + { "Sto1 ADC MIXL", "ADC1 Switch", "ADC L" }, + { "Sto1 ADC MIXL", "ADC2 Switch", "DMIC L1" }, + { "Sto1 ADC MIXR", "ADC1 Switch", "ADC R" }, + { "Sto1 ADC MIXR", "ADC2 Switch", "DMIC R1" }, + + { "Stereo1 ADC MIXL", NULL, "Sto1 ADC MIXL" }, + { "Stereo1 ADC MIXL", NULL, "adc stereo1 filter" }, + { "adc stereo1 filter", NULL, "PLL1", rt5660_is_sys_clk_from_pll }, + + { "Stereo1 ADC MIXR", NULL, "Sto1 ADC MIXR" }, + { "Stereo1 ADC MIXR", NULL, "adc stereo1 filter" }, + { "adc stereo1 filter", NULL, "PLL1", rt5660_is_sys_clk_from_pll }, + + { "IF1 ADC", NULL, "Stereo1 ADC MIXL" }, + { "IF1 ADC", NULL, "Stereo1 ADC MIXR" }, + { "IF1 ADC", NULL, "I2S1" }, + + { "IF1 ADC Swap Mux", "L/R", "IF1 ADC" }, + { "IF1 ADC Swap Mux", "R/L", "IF1 ADC" }, + { "IF1 ADC Swap Mux", "L/L", "IF1 ADC" }, + { "IF1 ADC Swap Mux", "R/R", "IF1 ADC" }, + { "AIF1TX", NULL, "IF1 ADC Swap Mux" }, + + { "IF1 DAC", NULL, "AIF1RX" }, + { "IF1 DAC", NULL, "I2S1" }, + + { "IF1 DAC Swap Mux", "L/R", "IF1 DAC" }, + { "IF1 DAC Swap Mux", "R/L", "IF1 DAC" }, + { "IF1 DAC Swap Mux", "L/L", "IF1 DAC" }, + { "IF1 DAC Swap Mux", "R/R", "IF1 DAC" }, + + { "IF1 DAC L", NULL, "IF1 DAC Swap Mux" }, + { "IF1 DAC R", NULL, "IF1 DAC Swap Mux" }, + + { "DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL" }, + { "DAC1 MIXL", "DAC1 Switch", "IF1 DAC L" }, + { "DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR" }, + { "DAC1 MIXR", "DAC1 Switch", "IF1 DAC R" }, + + { "Stereo DAC MIXL", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXL", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXL", NULL, "dac stereo1 filter" }, + { "dac stereo1 filter", NULL, "PLL1", rt5660_is_sys_clk_from_pll }, + { "Stereo DAC MIXR", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXR", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXR", NULL, "dac stereo1 filter" }, + { "dac stereo1 filter", NULL, "PLL1", rt5660_is_sys_clk_from_pll }, + + { "DAC L1", NULL, "Stereo DAC MIXL" }, + { "DAC R1", NULL, "Stereo DAC MIXR" }, + + { "SPK MIX", "BST3 Switch", "BST3" }, + { "SPK MIX", "BST1 Switch", "BST1" }, + { "SPK MIX", "DACL Switch", "DAC L1" }, + { "SPK MIX", "DACR Switch", "DAC R1" }, + { "SPK MIX", "OUTMIXL Switch", "OUT MIXL" }, + + { "OUT MIXL", "BST3 Switch", "BST3" }, + { "OUT MIXL", "BST2 Switch", "BST2" }, + { "OUT MIXL", "BST1 Switch", "BST1" }, + { "OUT MIXL", "RECMIXL Switch", "RECMIXL" }, + { "OUT MIXL", "DACR Switch", "DAC R1" }, + { "OUT MIXL", "DACL Switch", "DAC L1" }, + + { "OUT MIXR", "BST2 Switch", "BST2" }, + { "OUT MIXR", "BST1 Switch", "BST1" }, + { "OUT MIXR", "RECMIXR Switch", "RECMIXR" }, + { "OUT MIXR", "DACR Switch", "DAC R1" }, + { "OUT MIXR", "DACL Switch", "DAC L1" }, + + { "SPO MIX", "DACR Switch", "DAC R1" }, + { "SPO MIX", "DACL Switch", "DAC L1" }, + { "SPO MIX", "SPKVOL Switch", "SPKVOL" }, + { "SPO MIX", "BST1 Switch", "BST1" }, + + { "SPKVOL", "Switch", "SPK MIX" }, + { "LOUTVOL L", "Switch", "OUT MIXL" }, + { "LOUTVOL R", "Switch", "OUT MIXR" }, + + { "LOUTVOL", NULL, "LOUTVOL L" }, + { "LOUTVOL", NULL, "LOUTVOL R" }, + + { "DAC 1", NULL, "DAC L1" }, + { "DAC 1", NULL, "DAC R1" }, + + { "LOUT MIX", "DAC Switch", "DAC 1" }, + { "LOUT MIX", "OUTMIX Switch", "LOUTVOL" }, + + { "LOUT amp", NULL, "LOUT MIX" }, + { "LOUT amp", NULL, "VREF HP" }, + { "LOUTL", NULL, "LOUT amp" }, + { "LOUTR", NULL, "LOUT amp" }, + + { "SPK amp", NULL, "SPO MIX" }, + { "SPO", NULL, "SPK amp" }, +}; + +static int rt5660_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int pre_div, bclk_ms, frame_size; + + rt5660->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5660->sysclk, rt5660->lrck[dai->id]); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting %d for DAI %d\n", + rt5660->lrck[dai->id], dai->id); + return -EINVAL; + } + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return frame_size; + } + + if (frame_size > 32) + bclk_ms = 1; + else + bclk_ms = 0; + + rt5660->bclk[dai->id] = rt5660->lrck[dai->id] * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5660->bclk[dai->id], rt5660->lrck[dai->id]); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len |= RT5660_I2S_DL_20; + break; + case 24: + val_len |= RT5660_I2S_DL_24; + break; + case 8: + val_len |= RT5660_I2S_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5660_AIF1: + mask_clk = RT5660_I2S_BCLK_MS1_MASK | RT5660_I2S_PD1_MASK; + val_clk = bclk_ms << RT5660_I2S_BCLK_MS1_SFT | + pre_div << RT5660_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5660_I2S1_SDP, RT5660_I2S_DL_MASK, + val_len); + snd_soc_component_update_bits(component, RT5660_ADDA_CLK1, mask_clk, val_clk); + break; + + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5660_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5660->master[dai->id] = 1; + break; + + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5660_I2S_MS_S; + rt5660->master[dai->id] = 0; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5660_I2S_BP_INV; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5660_I2S_DF_LEFT; + break; + + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5660_I2S_DF_PCM_A; + break; + + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5660_I2S_DF_PCM_B; + break; + + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5660_AIF1: + snd_soc_component_update_bits(component, RT5660_I2S1_SDP, + RT5660_I2S_MS_MASK | RT5660_I2S_BP_MASK | + RT5660_I2S_DF_MASK, reg_val); + break; + + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5660_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + if (freq == rt5660->sysclk && clk_id == rt5660->sysclk_src) + return 0; + + switch (clk_id) { + case RT5660_SCLK_S_MCLK: + reg_val |= RT5660_SCLK_SRC_MCLK; + break; + + case RT5660_SCLK_S_PLL1: + reg_val |= RT5660_SCLK_SRC_PLL1; + break; + + case RT5660_SCLK_S_RCCLK: + reg_val |= RT5660_SCLK_SRC_RCCLK; + break; + + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5660_GLB_CLK, RT5660_SCLK_SRC_MASK, + reg_val); + + rt5660->sysclk = freq; + rt5660->sysclk_src = clk_id; + + dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + + return 0; +} + +static int rt5660_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5660->pll_src && freq_in == rt5660->pll_in && + freq_out == rt5660->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5660->pll_in = 0; + rt5660->pll_out = 0; + snd_soc_component_update_bits(component, RT5660_GLB_CLK, + RT5660_SCLK_SRC_MASK, RT5660_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5660_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5660_GLB_CLK, + RT5660_PLL1_SRC_MASK, RT5660_PLL1_SRC_MCLK); + break; + + case RT5660_PLL1_S_BCLK: + snd_soc_component_update_bits(component, RT5660_GLB_CLK, + RT5660_PLL1_SRC_MASK, RT5660_PLL1_SRC_BCLK1); + break; + + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5660_PLL_CTRL1, + pll_code.n_code << RT5660_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5660_PLL_CTRL2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5660_PLL_M_SFT | + pll_code.m_bp << RT5660_PLL_M_BP_SFT); + + rt5660->pll_in = freq_in; + rt5660->pll_out = freq_out; + rt5660->pll_src = source; + + return 0; +} + +static int rt5660_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, RT5660_GEN_CTRL1, + RT5660_DIG_GATE_CTRL, RT5660_DIG_GATE_CTRL); + + if (IS_ERR(rt5660->mclk)) + break; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(rt5660->mclk); + } else { + ret = clk_prepare_enable(rt5660->mclk); + if (ret) + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_update_bits(component, RT5660_PWR_ANLG1, + RT5660_PWR_VREF1 | RT5660_PWR_MB | + RT5660_PWR_BG | RT5660_PWR_VREF2, + RT5660_PWR_VREF1 | RT5660_PWR_MB | + RT5660_PWR_BG | RT5660_PWR_VREF2); + usleep_range(10000, 15000); + snd_soc_component_update_bits(component, RT5660_PWR_ANLG1, + RT5660_PWR_FV1 | RT5660_PWR_FV2, + RT5660_PWR_FV1 | RT5660_PWR_FV2); + } + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, RT5660_GEN_CTRL1, + RT5660_DIG_GATE_CTRL, 0); + break; + + default: + break; + } + + return 0; +} + +static int rt5660_probe(struct snd_soc_component *component) +{ + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + + rt5660->component = component; + + return 0; +} + +static void rt5660_remove(struct snd_soc_component *component) +{ + snd_soc_component_write(component, RT5660_RESET, 0); +} + +#ifdef CONFIG_PM +static int rt5660_suspend(struct snd_soc_component *component) +{ + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5660->regmap, true); + regcache_mark_dirty(rt5660->regmap); + + return 0; +} + +static int rt5660_resume(struct snd_soc_component *component) +{ + struct rt5660_priv *rt5660 = snd_soc_component_get_drvdata(component); + + if (rt5660->pdata.poweroff_codec_in_suspend) + msleep(350); + + regcache_cache_only(rt5660->regmap, false); + regcache_sync(rt5660->regmap); + + return 0; +} +#else +#define rt5660_suspend NULL +#define rt5660_resume NULL +#endif + +#define RT5660_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5660_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5660_aif_dai_ops = { + .hw_params = rt5660_hw_params, + .set_fmt = rt5660_set_dai_fmt, + .set_sysclk = rt5660_set_dai_sysclk, + .set_pll = rt5660_set_dai_pll, +}; + +static struct snd_soc_dai_driver rt5660_dai[] = { + { + .name = "rt5660-aif1", + .id = RT5660_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5660_STEREO_RATES, + .formats = RT5660_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5660_STEREO_RATES, + .formats = RT5660_FORMATS, + }, + .ops = &rt5660_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5660 = { + .probe = rt5660_probe, + .remove = rt5660_remove, + .suspend = rt5660_suspend, + .resume = rt5660_resume, + .set_bias_level = rt5660_set_bias_level, + .controls = rt5660_snd_controls, + .num_controls = ARRAY_SIZE(rt5660_snd_controls), + .dapm_widgets = rt5660_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5660_dapm_widgets), + .dapm_routes = rt5660_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5660_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5660_regmap = { + .reg_bits = 8, + .val_bits = 16, + .use_single_rw = true, + + .max_register = RT5660_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5660_ranges) * + RT5660_PR_SPACING), + .volatile_reg = rt5660_volatile_register, + .readable_reg = rt5660_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5660_reg, + .num_reg_defaults = ARRAY_SIZE(rt5660_reg), + .ranges = rt5660_ranges, + .num_ranges = ARRAY_SIZE(rt5660_ranges), +}; + +static const struct i2c_device_id rt5660_i2c_id[] = { + { "rt5660", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5660_i2c_id); + +static const struct of_device_id rt5660_of_match[] = { + { .compatible = "realtek,rt5660", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5660_of_match); + +static const struct acpi_device_id rt5660_acpi_match[] = { + { "10EC5660", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, rt5660_acpi_match); + +static int rt5660_parse_dt(struct rt5660_priv *rt5660, struct device *dev) +{ + rt5660->pdata.in1_diff = device_property_read_bool(dev, + "realtek,in1-differential"); + rt5660->pdata.in3_diff = device_property_read_bool(dev, + "realtek,in3-differential"); + rt5660->pdata.poweroff_codec_in_suspend = device_property_read_bool(dev, + "realtek,poweroff-in-suspend"); + device_property_read_u32(dev, "realtek,dmic1-data-pin", + &rt5660->pdata.dmic1_data_pin); + + return 0; +} + +static int rt5660_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5660_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5660_priv *rt5660; + int ret; + unsigned int val; + + rt5660 = devm_kzalloc(&i2c->dev, sizeof(struct rt5660_priv), + GFP_KERNEL); + + if (rt5660 == NULL) + return -ENOMEM; + + /* Check if MCLK provided */ + rt5660->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (PTR_ERR(rt5660->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + i2c_set_clientdata(i2c, rt5660); + + if (pdata) + rt5660->pdata = *pdata; + else if (i2c->dev.of_node) + rt5660_parse_dt(rt5660, &i2c->dev); + + rt5660->regmap = devm_regmap_init_i2c(i2c, &rt5660_regmap); + if (IS_ERR(rt5660->regmap)) { + ret = PTR_ERR(rt5660->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt5660->regmap, RT5660_VENDOR_ID2, &val); + if (val != RT5660_DEVICE_ID) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5660\n", val); + return -ENODEV; + } + + regmap_write(rt5660->regmap, RT5660_RESET, 0); + + ret = regmap_register_patch(rt5660->regmap, rt5660_patch, + ARRAY_SIZE(rt5660_patch)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + regmap_update_bits(rt5660->regmap, RT5660_GEN_CTRL1, + RT5660_AUTO_DIS_AMP | RT5660_MCLK_DET | RT5660_POW_CLKDET, + RT5660_AUTO_DIS_AMP | RT5660_MCLK_DET | RT5660_POW_CLKDET); + + if (rt5660->pdata.dmic1_data_pin) { + regmap_update_bits(rt5660->regmap, RT5660_GPIO_CTRL1, + RT5660_GP1_PIN_MASK, RT5660_GP1_PIN_DMIC1_SCL); + + if (rt5660->pdata.dmic1_data_pin == RT5660_DMIC1_DATA_GPIO2) + regmap_update_bits(rt5660->regmap, RT5660_DMIC_CTRL1, + RT5660_SEL_DMIC_DATA_MASK, + RT5660_SEL_DMIC_DATA_GPIO2); + else if (rt5660->pdata.dmic1_data_pin == RT5660_DMIC1_DATA_IN1P) + regmap_update_bits(rt5660->regmap, RT5660_DMIC_CTRL1, + RT5660_SEL_DMIC_DATA_MASK, + RT5660_SEL_DMIC_DATA_IN1P); + } + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5660, + rt5660_dai, ARRAY_SIZE(rt5660_dai)); +} + +static struct i2c_driver rt5660_i2c_driver = { + .driver = { + .name = "rt5660", + .acpi_match_table = ACPI_PTR(rt5660_acpi_match), + .of_match_table = of_match_ptr(rt5660_of_match), + }, + .probe = rt5660_i2c_probe, + .id_table = rt5660_i2c_id, +}; +module_i2c_driver(rt5660_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5660 driver"); +MODULE_AUTHOR("Oder Chiou "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5660.h b/sound/soc/codecs/rt5660.h new file mode 100644 index 000000000..c65de0a20 --- /dev/null +++ b/sound/soc/codecs/rt5660.h @@ -0,0 +1,850 @@ +/* + * rt5660.h -- RT5660 ALSA SoC audio driver + * + * Copyright 2016 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _RT5660_H +#define _RT5660_H + +#include +#include + +/* Info */ +#define RT5660_RESET 0x00 +#define RT5660_VENDOR_ID 0xfd +#define RT5660_VENDOR_ID1 0xfe +#define RT5660_VENDOR_ID2 0xff +/* I/O - Output */ +#define RT5660_SPK_VOL 0x01 +#define RT5660_LOUT_VOL 0x02 +/* I/O - Input */ +#define RT5660_IN1_IN2 0x0d +#define RT5660_IN3_IN4 0x0e +/* I/O - ADC/DAC/DMIC */ +#define RT5660_DAC1_DIG_VOL 0x19 +#define RT5660_STO1_ADC_DIG_VOL 0x1c +#define RT5660_ADC_BST_VOL1 0x1e +/* Mixer - D-D */ +#define RT5660_STO1_ADC_MIXER 0x27 +#define RT5660_AD_DA_MIXER 0x29 +#define RT5660_STO_DAC_MIXER 0x2a +#define RT5660_DIG_INF1_DATA 0x2f +/* Mixer - ADC */ +#define RT5660_REC_L1_MIXER 0x3b +#define RT5660_REC_L2_MIXER 0x3c +#define RT5660_REC_R1_MIXER 0x3d +#define RT5660_REC_R2_MIXER 0x3e +/* Mixer - DAC */ +#define RT5660_LOUT_MIXER 0x45 +#define RT5660_SPK_MIXER 0x46 +#define RT5660_SPO_MIXER 0x48 +#define RT5660_SPO_CLSD_RATIO 0x4a +#define RT5660_OUT_L_GAIN1 0x4d +#define RT5660_OUT_L_GAIN2 0x4e +#define RT5660_OUT_L1_MIXER 0x4f +#define RT5660_OUT_R_GAIN1 0x50 +#define RT5660_OUT_R_GAIN2 0x51 +#define RT5660_OUT_R1_MIXER 0x52 +/* Power */ +#define RT5660_PWR_DIG1 0x61 +#define RT5660_PWR_DIG2 0x62 +#define RT5660_PWR_ANLG1 0x63 +#define RT5660_PWR_ANLG2 0x64 +#define RT5660_PWR_MIXER 0x65 +#define RT5660_PWR_VOL 0x66 +/* Private Register Control */ +#define RT5660_PRIV_INDEX 0x6a +#define RT5660_PRIV_DATA 0x6c +/* Format - ADC/DAC */ +#define RT5660_I2S1_SDP 0x70 +#define RT5660_ADDA_CLK1 0x73 +#define RT5660_ADDA_CLK2 0x74 +#define RT5660_DMIC_CTRL1 0x75 +/* Function - Analog */ +#define RT5660_GLB_CLK 0x80 +#define RT5660_PLL_CTRL1 0x81 +#define RT5660_PLL_CTRL2 0x82 +#define RT5660_CLSD_AMP_OC_CTRL 0x8c +#define RT5660_CLSD_AMP_CTRL 0x8d +#define RT5660_LOUT_AMP_CTRL 0x8e +#define RT5660_SPK_AMP_SPKVDD 0x92 +#define RT5660_MICBIAS 0x93 +#define RT5660_CLSD_OUT_CTRL1 0xa1 +#define RT5660_CLSD_OUT_CTRL2 0xa2 +#define RT5660_DIPOLE_MIC_CTRL1 0xa3 +#define RT5660_DIPOLE_MIC_CTRL2 0xa4 +#define RT5660_DIPOLE_MIC_CTRL3 0xa5 +#define RT5660_DIPOLE_MIC_CTRL4 0xa6 +#define RT5660_DIPOLE_MIC_CTRL5 0xa7 +#define RT5660_DIPOLE_MIC_CTRL6 0xa8 +#define RT5660_DIPOLE_MIC_CTRL7 0xa9 +#define RT5660_DIPOLE_MIC_CTRL8 0xaa +#define RT5660_DIPOLE_MIC_CTRL9 0xab +#define RT5660_DIPOLE_MIC_CTRL10 0xac +#define RT5660_DIPOLE_MIC_CTRL11 0xad +#define RT5660_DIPOLE_MIC_CTRL12 0xae +/* Function - Digital */ +#define RT5660_EQ_CTRL1 0xb0 +#define RT5660_EQ_CTRL2 0xb1 +#define RT5660_DRC_AGC_CTRL1 0xb3 +#define RT5660_DRC_AGC_CTRL2 0xb4 +#define RT5660_DRC_AGC_CTRL3 0xb5 +#define RT5660_DRC_AGC_CTRL4 0xb6 +#define RT5660_DRC_AGC_CTRL5 0xb7 +#define RT5660_JD_CTRL 0xbb +#define RT5660_IRQ_CTRL1 0xbd +#define RT5660_IRQ_CTRL2 0xbe +#define RT5660_INT_IRQ_ST 0xbf +#define RT5660_GPIO_CTRL1 0xc0 +#define RT5660_GPIO_CTRL2 0xc2 +#define RT5660_WIND_FILTER_CTRL1 0xd3 +#define RT5660_SV_ZCD1 0xd9 +#define RT5660_SV_ZCD2 0xda +#define RT5660_DRC1_LM_CTRL1 0xe0 +#define RT5660_DRC1_LM_CTRL2 0xe1 +#define RT5660_DRC2_LM_CTRL1 0xe2 +#define RT5660_DRC2_LM_CTRL2 0xe3 +#define RT5660_MULTI_DRC_CTRL 0xe4 +#define RT5660_DRC2_CTRL1 0xe5 +#define RT5660_DRC2_CTRL2 0xe6 +#define RT5660_DRC2_CTRL3 0xe7 +#define RT5660_DRC2_CTRL4 0xe8 +#define RT5660_DRC2_CTRL5 0xe9 +#define RT5660_ALC_PGA_CTRL1 0xea +#define RT5660_ALC_PGA_CTRL2 0xeb +#define RT5660_ALC_PGA_CTRL3 0xec +#define RT5660_ALC_PGA_CTRL4 0xed +#define RT5660_ALC_PGA_CTRL5 0xee +#define RT5660_ALC_PGA_CTRL6 0xef +#define RT5660_ALC_PGA_CTRL7 0xf0 + +/* General Control */ +#define RT5660_GEN_CTRL1 0xfa +#define RT5660_GEN_CTRL2 0xfb +#define RT5660_GEN_CTRL3 0xfc + +/* Index of Codec Private Register definition */ +#define RT5660_CHOP_DAC_ADC 0x3d + +/* Global Definition */ +#define RT5660_L_MUTE (0x1 << 15) +#define RT5660_L_MUTE_SFT 15 +#define RT5660_VOL_L_MUTE (0x1 << 14) +#define RT5660_VOL_L_SFT 14 +#define RT5660_R_MUTE (0x1 << 7) +#define RT5660_R_MUTE_SFT 7 +#define RT5660_VOL_R_MUTE (0x1 << 6) +#define RT5660_VOL_R_SFT 6 +#define RT5660_L_VOL_MASK (0x3f << 8) +#define RT5660_L_VOL_SFT 8 +#define RT5660_R_VOL_MASK (0x3f) +#define RT5660_R_VOL_SFT 0 + +/* IN1 and IN2 Control (0x0d) */ +#define RT5660_IN_DF1 (0x1 << 15) +#define RT5660_IN_SFT1 15 +#define RT5660_BST_MASK1 (0x7f << 8) +#define RT5660_BST_SFT1 8 +#define RT5660_IN_DF2 (0x1 << 7) +#define RT5660_IN_SFT2 7 +#define RT5660_BST_MASK2 (0x7f << 0) +#define RT5660_BST_SFT2 0 + +/* IN3 and IN4 Control (0x0e) */ +#define RT5660_IN_DF3 (0x1 << 15) +#define RT5660_IN_SFT3 15 +#define RT5660_BST_MASK3 (0x7f << 8) +#define RT5660_BST_SFT3 8 +#define RT5660_IN_DF4 (0x1 << 7) +#define RT5660_IN_SFT4 7 +#define RT5660_BST_MASK4 (0x7f << 0) +#define RT5660_BST_SFT4 0 + +/* DAC1 Digital Volume (0x19) */ +#define RT5660_DAC_L1_VOL_MASK (0x7f << 9) +#define RT5660_DAC_L1_VOL_SFT 9 +#define RT5660_DAC_R1_VOL_MASK (0x7f << 1) +#define RT5660_DAC_R1_VOL_SFT 1 + +/* ADC Digital Volume Control (0x1c) */ +#define RT5660_ADC_L_VOL_MASK (0x3f << 9) +#define RT5660_ADC_L_VOL_SFT 9 +#define RT5660_ADC_R_VOL_MASK (0x3f << 1) +#define RT5660_ADC_R_VOL_SFT 1 + +/* ADC Boost Volume Control (0x1e) */ +#define RT5660_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5660_STO1_ADC_L_BST_SFT 14 +#define RT5660_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5660_STO1_ADC_R_BST_SFT 12 + +/* Stereo ADC Mixer Control (0x27) */ +#define RT5660_M_ADC_L1 (0x1 << 14) +#define RT5660_M_ADC_L1_SFT 14 +#define RT5660_M_ADC_L2 (0x1 << 13) +#define RT5660_M_ADC_L2_SFT 13 +#define RT5660_M_ADC_R1 (0x1 << 6) +#define RT5660_M_ADC_R1_SFT 6 +#define RT5660_M_ADC_R2 (0x1 << 5) +#define RT5660_M_ADC_R2_SFT 5 + +/* ADC Mixer to DAC Mixer Control (0x29) */ +#define RT5660_M_ADCMIX_L (0x1 << 15) +#define RT5660_M_ADCMIX_L_SFT 15 +#define RT5660_M_DAC1_L (0x1 << 14) +#define RT5660_M_DAC1_L_SFT 14 +#define RT5660_M_ADCMIX_R (0x1 << 7) +#define RT5660_M_ADCMIX_R_SFT 7 +#define RT5660_M_DAC1_R (0x1 << 6) +#define RT5660_M_DAC1_R_SFT 6 + +/* Stereo DAC Mixer Control (0x2a) */ +#define RT5660_M_DAC_L1 (0x1 << 14) +#define RT5660_M_DAC_L1_SFT 14 +#define RT5660_DAC_L1_STO_L_VOL_MASK (0x1 << 13) +#define RT5660_DAC_L1_STO_L_VOL_SFT 13 +#define RT5660_M_DAC_R1_STO_L (0x1 << 9) +#define RT5660_M_DAC_R1_STO_L_SFT 9 +#define RT5660_DAC_R1_STO_L_VOL_MASK (0x1 << 8) +#define RT5660_DAC_R1_STO_L_VOL_SFT 8 +#define RT5660_M_DAC_R1 (0x1 << 6) +#define RT5660_M_DAC_R1_SFT 6 +#define RT5660_DAC_R1_STO_R_VOL_MASK (0x1 << 5) +#define RT5660_DAC_R1_STO_R_VOL_SFT 5 +#define RT5660_M_DAC_L1_STO_R (0x1 << 1) +#define RT5660_M_DAC_L1_STO_R_SFT 1 +#define RT5660_DAC_L1_STO_R_VOL_MASK (0x1) +#define RT5660_DAC_L1_STO_R_VOL_SFT 0 + +/* Digital Interface Data Control (0x2f) */ +#define RT5660_IF1_DAC_IN_SEL (0x3 << 14) +#define RT5660_IF1_DAC_IN_SFT 14 +#define RT5660_IF1_ADC_IN_SEL (0x3 << 12) +#define RT5660_IF1_ADC_IN_SFT 12 + +/* REC Left Mixer Control 1 (0x3b) */ +#define RT5660_G_BST3_RM_L_MASK (0x7 << 4) +#define RT5660_G_BST3_RM_L_SFT 4 +#define RT5660_G_BST2_RM_L_MASK (0x7 << 1) +#define RT5660_G_BST2_RM_L_SFT 1 + +/* REC Left Mixer Control 2 (0x3c) */ +#define RT5660_G_BST1_RM_L_MASK (0x7 << 13) +#define RT5660_G_BST1_RM_L_SFT 13 +#define RT5660_G_OM_L_RM_L_MASK (0x7 << 10) +#define RT5660_G_OM_L_RM_L_SFT 10 +#define RT5660_M_BST3_RM_L (0x1 << 3) +#define RT5660_M_BST3_RM_L_SFT 3 +#define RT5660_M_BST2_RM_L (0x1 << 2) +#define RT5660_M_BST2_RM_L_SFT 2 +#define RT5660_M_BST1_RM_L (0x1 << 1) +#define RT5660_M_BST1_RM_L_SFT 1 +#define RT5660_M_OM_L_RM_L (0x1) +#define RT5660_M_OM_L_RM_L_SFT 0 + +/* REC Right Mixer Control 1 (0x3d) */ +#define RT5660_G_BST3_RM_R_MASK (0x7 << 4) +#define RT5660_G_BST3_RM_R_SFT 4 +#define RT5660_G_BST2_RM_R_MASK (0x7 << 1) +#define RT5660_G_BST2_RM_R_SFT 1 + +/* REC Right Mixer Control 2 (0x3e) */ +#define RT5660_G_BST1_RM_R_MASK (0x7 << 13) +#define RT5660_G_BST1_RM_R_SFT 13 +#define RT5660_G_OM_R_RM_R_MASK (0x7 << 10) +#define RT5660_G_OM_R_RM_R_SFT 10 +#define RT5660_M_BST3_RM_R (0x1 << 3) +#define RT5660_M_BST3_RM_R_SFT 3 +#define RT5660_M_BST2_RM_R (0x1 << 2) +#define RT5660_M_BST2_RM_R_SFT 2 +#define RT5660_M_BST1_RM_R (0x1 << 1) +#define RT5660_M_BST1_RM_R_SFT 1 +#define RT5660_M_OM_R_RM_R (0x1) +#define RT5660_M_OM_R_RM_R_SFT 0 + +/* LOUTMIX Control (0x45) */ +#define RT5660_M_DAC1_LM (0x1 << 14) +#define RT5660_M_DAC1_LM_SFT 14 +#define RT5660_M_LOVOL_M (0x1 << 13) +#define RT5660_M_LOVOL_LM_SFT 13 + +/* SPK Mixer Control (0x46) */ +#define RT5660_G_BST3_SM_MASK (0x3 << 14) +#define RT5660_G_BST3_SM_SFT 14 +#define RT5660_G_BST1_SM_MASK (0x3 << 12) +#define RT5660_G_BST1_SM_SFT 12 +#define RT5660_G_DACl_SM_MASK (0x3 << 10) +#define RT5660_G_DACl_SM_SFT 10 +#define RT5660_G_DACR_SM_MASK (0x3 << 8) +#define RT5660_G_DACR_SM_SFT 8 +#define RT5660_G_OM_L_SM_MASK (0x3 << 6) +#define RT5660_G_OM_L_SM_SFT 6 +#define RT5660_M_DACR_SM (0x1 << 5) +#define RT5660_M_DACR_SM_SFT 5 +#define RT5660_M_BST1_SM (0x1 << 4) +#define RT5660_M_BST1_SM_SFT 4 +#define RT5660_M_BST3_SM (0x1 << 3) +#define RT5660_M_BST3_SM_SFT 3 +#define RT5660_M_DACL_SM (0x1 << 2) +#define RT5660_M_DACL_SM_SFT 2 +#define RT5660_M_OM_L_SM (0x1 << 1) +#define RT5660_M_OM_L_SM_SFT 1 + +/* SPOMIX Control (0x48) */ +#define RT5660_M_DAC_R_SPM (0x1 << 14) +#define RT5660_M_DAC_R_SPM_SFT 14 +#define RT5660_M_DAC_L_SPM (0x1 << 13) +#define RT5660_M_DAC_L_SPM_SFT 13 +#define RT5660_M_SV_SPM (0x1 << 12) +#define RT5660_M_SV_SPM_SFT 12 +#define RT5660_M_BST1_SPM (0x1 << 11) +#define RT5660_M_BST1_SPM_SFT 11 + +/* Output Left Mixer Control 1 (0x4d) */ +#define RT5660_G_BST3_OM_L_MASK (0x7 << 13) +#define RT5660_G_BST3_OM_L_SFT 13 +#define RT5660_G_BST2_OM_L_MASK (0x7 << 10) +#define RT5660_G_BST2_OM_L_SFT 10 +#define RT5660_G_BST1_OM_L_MASK (0x7 << 7) +#define RT5660_G_BST1_OM_L_SFT 7 +#define RT5660_G_RM_L_OM_L_MASK (0x7 << 1) +#define RT5660_G_RM_L_OM_L_SFT 1 + +/* Output Left Mixer Control 2 (0x4e) */ +#define RT5660_G_DAC_R1_OM_L_MASK (0x7 << 10) +#define RT5660_G_DAC_R1_OM_L_SFT 10 +#define RT5660_G_DAC_L1_OM_L_MASK (0x7 << 7) +#define RT5660_G_DAC_L1_OM_L_SFT 7 + +/* Output Left Mixer Control 3 (0x4f) */ +#define RT5660_M_BST3_OM_L (0x1 << 5) +#define RT5660_M_BST3_OM_L_SFT 5 +#define RT5660_M_BST2_OM_L (0x1 << 4) +#define RT5660_M_BST2_OM_L_SFT 4 +#define RT5660_M_BST1_OM_L (0x1 << 3) +#define RT5660_M_BST1_OM_L_SFT 3 +#define RT5660_M_RM_L_OM_L (0x1 << 2) +#define RT5660_M_RM_L_OM_L_SFT 2 +#define RT5660_M_DAC_R_OM_L (0x1 << 1) +#define RT5660_M_DAC_R_OM_L_SFT 1 +#define RT5660_M_DAC_L_OM_L (0x1) +#define RT5660_M_DAC_L_OM_L_SFT 0 + +/* Output Right Mixer Control 1 (0x50) */ +#define RT5660_G_BST2_OM_R_MASK (0x7 << 10) +#define RT5660_G_BST2_OM_R_SFT 10 +#define RT5660_G_BST1_OM_R_MASK (0x7 << 7) +#define RT5660_G_BST1_OM_R_SFT 7 +#define RT5660_G_RM_R_OM_R_MASK (0x7 << 1) +#define RT5660_G_RM_R_OM_R_SFT 1 + +/* Output Right Mixer Control 2 (0x51) */ +#define RT5660_G_DAC_L_OM_R_MASK (0x7 << 10) +#define RT5660_G_DAC_L_OM_R_SFT 10 +#define RT5660_G_DAC_R_OM_R_MASK (0x7 << 7) +#define RT5660_G_DAC_R_OM_R_SFT 7 + +/* Output Right Mixer Control 3 (0x52) */ +#define RT5660_M_BST2_OM_R (0x1 << 4) +#define RT5660_M_BST2_OM_R_SFT 4 +#define RT5660_M_BST1_OM_R (0x1 << 3) +#define RT5660_M_BST1_OM_R_SFT 3 +#define RT5660_M_RM_R_OM_R (0x1 << 2) +#define RT5660_M_RM_R_OM_R_SFT 2 +#define RT5660_M_DAC_L_OM_R (0x1 << 1) +#define RT5660_M_DAC_L_OM_R_SFT 1 +#define RT5660_M_DAC_R_OM_R (0x1) +#define RT5660_M_DAC_R_OM_R_SFT 0 + +/* Power Management for Digital 1 (0x61) */ +#define RT5660_PWR_I2S1 (0x1 << 15) +#define RT5660_PWR_I2S1_BIT 15 +#define RT5660_PWR_DAC_L1 (0x1 << 12) +#define RT5660_PWR_DAC_L1_BIT 12 +#define RT5660_PWR_DAC_R1 (0x1 << 11) +#define RT5660_PWR_DAC_R1_BIT 11 +#define RT5660_PWR_ADC_L (0x1 << 2) +#define RT5660_PWR_ADC_L_BIT 2 +#define RT5660_PWR_ADC_R (0x1 << 1) +#define RT5660_PWR_ADC_R_BIT 1 +#define RT5660_PWR_CLS_D (0x1) +#define RT5660_PWR_CLS_D_BIT 0 + +/* Power Management for Digital 2 (0x62) */ +#define RT5660_PWR_ADC_S1F (0x1 << 15) +#define RT5660_PWR_ADC_S1F_BIT 15 +#define RT5660_PWR_DAC_S1F (0x1 << 11) +#define RT5660_PWR_DAC_S1F_BIT 11 + +/* Power Management for Analog 1 (0x63) */ +#define RT5660_PWR_VREF1 (0x1 << 15) +#define RT5660_PWR_VREF1_BIT 15 +#define RT5660_PWR_FV1 (0x1 << 14) +#define RT5660_PWR_FV1_BIT 14 +#define RT5660_PWR_MB (0x1 << 13) +#define RT5660_PWR_MB_BIT 13 +#define RT5660_PWR_BG (0x1 << 11) +#define RT5660_PWR_BG_BIT 11 +#define RT5660_PWR_HP_L (0x1 << 7) +#define RT5660_PWR_HP_L_BIT 7 +#define RT5660_PWR_HP_R (0x1 << 6) +#define RT5660_PWR_HP_R_BIT 6 +#define RT5660_PWR_HA (0x1 << 5) +#define RT5660_PWR_HA_BIT 5 +#define RT5660_PWR_VREF2 (0x1 << 4) +#define RT5660_PWR_VREF2_BIT 4 +#define RT5660_PWR_FV2 (0x1 << 3) +#define RT5660_PWR_FV2_BIT 3 +#define RT5660_PWR_LDO2 (0x1 << 2) +#define RT5660_PWR_LDO2_BIT 2 + +/* Power Management for Analog 2 (0x64) */ +#define RT5660_PWR_BST1 (0x1 << 15) +#define RT5660_PWR_BST1_BIT 15 +#define RT5660_PWR_BST2 (0x1 << 14) +#define RT5660_PWR_BST2_BIT 14 +#define RT5660_PWR_BST3 (0x1 << 13) +#define RT5660_PWR_BST3_BIT 13 +#define RT5660_PWR_MB1 (0x1 << 11) +#define RT5660_PWR_MB1_BIT 11 +#define RT5660_PWR_MB2 (0x1 << 10) +#define RT5660_PWR_MB2_BIT 10 +#define RT5660_PWR_PLL (0x1 << 9) +#define RT5660_PWR_PLL_BIT 9 + +/* Power Management for Mixer (0x65) */ +#define RT5660_PWR_OM_L (0x1 << 15) +#define RT5660_PWR_OM_L_BIT 15 +#define RT5660_PWR_OM_R (0x1 << 14) +#define RT5660_PWR_OM_R_BIT 14 +#define RT5660_PWR_SM (0x1 << 13) +#define RT5660_PWR_SM_BIT 13 +#define RT5660_PWR_RM_L (0x1 << 11) +#define RT5660_PWR_RM_L_BIT 11 +#define RT5660_PWR_RM_R (0x1 << 10) +#define RT5660_PWR_RM_R_BIT 10 + +/* Power Management for Volume (0x66) */ +#define RT5660_PWR_SV (0x1 << 15) +#define RT5660_PWR_SV_BIT 15 +#define RT5660_PWR_LV_L (0x1 << 11) +#define RT5660_PWR_LV_L_BIT 11 +#define RT5660_PWR_LV_R (0x1 << 10) +#define RT5660_PWR_LV_R_BIT 10 + +/* I2S1 Audio Serial Data Port Control (0x70) */ +#define RT5660_I2S_MS_MASK (0x1 << 15) +#define RT5660_I2S_MS_SFT 15 +#define RT5660_I2S_MS_M (0x0 << 15) +#define RT5660_I2S_MS_S (0x1 << 15) +#define RT5660_I2S_O_CP_MASK (0x3 << 10) +#define RT5660_I2S_O_CP_SFT 10 +#define RT5660_I2S_O_CP_OFF (0x0 << 10) +#define RT5660_I2S_O_CP_U_LAW (0x1 << 10) +#define RT5660_I2S_O_CP_A_LAW (0x2 << 10) +#define RT5660_I2S_I_CP_MASK (0x3 << 8) +#define RT5660_I2S_I_CP_SFT 8 +#define RT5660_I2S_I_CP_OFF (0x0 << 8) +#define RT5660_I2S_I_CP_U_LAW (0x1 << 8) +#define RT5660_I2S_I_CP_A_LAW (0x2 << 8) +#define RT5660_I2S_BP_MASK (0x1 << 7) +#define RT5660_I2S_BP_SFT 7 +#define RT5660_I2S_BP_NOR (0x0 << 7) +#define RT5660_I2S_BP_INV (0x1 << 7) +#define RT5660_I2S_DL_MASK (0x3 << 2) +#define RT5660_I2S_DL_SFT 2 +#define RT5660_I2S_DL_16 (0x0 << 2) +#define RT5660_I2S_DL_20 (0x1 << 2) +#define RT5660_I2S_DL_24 (0x2 << 2) +#define RT5660_I2S_DL_8 (0x3 << 2) +#define RT5660_I2S_DF_MASK (0x3) +#define RT5660_I2S_DF_SFT 0 +#define RT5660_I2S_DF_I2S (0x0) +#define RT5660_I2S_DF_LEFT (0x1) +#define RT5660_I2S_DF_PCM_A (0x2) +#define RT5660_I2S_DF_PCM_B (0x3) + +/* ADC/DAC Clock Control 1 (0x73) */ +#define RT5660_I2S_BCLK_MS1_MASK (0x1 << 15) +#define RT5660_I2S_BCLK_MS1_SFT 15 +#define RT5660_I2S_BCLK_MS1_32 (0x0 << 15) +#define RT5660_I2S_BCLK_MS1_64 (0x1 << 15) +#define RT5660_I2S_PD1_MASK (0x7 << 12) +#define RT5660_I2S_PD1_SFT 12 +#define RT5660_I2S_PD1_1 (0x0 << 12) +#define RT5660_I2S_PD1_2 (0x1 << 12) +#define RT5660_I2S_PD1_3 (0x2 << 12) +#define RT5660_I2S_PD1_4 (0x3 << 12) +#define RT5660_I2S_PD1_6 (0x4 << 12) +#define RT5660_I2S_PD1_8 (0x5 << 12) +#define RT5660_I2S_PD1_12 (0x6 << 12) +#define RT5660_I2S_PD1_16 (0x7 << 12) +#define RT5660_DAC_OSR_MASK (0x3 << 2) +#define RT5660_DAC_OSR_SFT 2 +#define RT5660_DAC_OSR_128 (0x0 << 2) +#define RT5660_DAC_OSR_64 (0x1 << 2) +#define RT5660_DAC_OSR_32 (0x2 << 2) +#define RT5660_DAC_OSR_16 (0x3 << 2) +#define RT5660_ADC_OSR_MASK (0x3) +#define RT5660_ADC_OSR_SFT 0 +#define RT5660_ADC_OSR_128 (0x0) +#define RT5660_ADC_OSR_64 (0x1) +#define RT5660_ADC_OSR_32 (0x2) +#define RT5660_ADC_OSR_16 (0x3) + +/* ADC/DAC Clock Control 2 (0x74) */ +#define RT5660_RESET_ADF (0x1 << 13) +#define RT5660_RESET_ADF_SFT 13 +#define RT5660_RESET_DAF (0x1 << 12) +#define RT5660_RESET_DAF_SFT 12 +#define RT5660_DAHPF_EN (0x1 << 11) +#define RT5660_DAHPF_EN_SFT 11 +#define RT5660_ADHPF_EN (0x1 << 10) +#define RT5660_ADHPF_EN_SFT 10 + +/* Digital Microphone Control (0x75) */ +#define RT5660_DMIC_1_EN_MASK (0x1 << 15) +#define RT5660_DMIC_1_EN_SFT 15 +#define RT5660_DMIC_1_DIS (0x0 << 15) +#define RT5660_DMIC_1_EN (0x1 << 15) +#define RT5660_DMIC_1L_LH_MASK (0x1 << 13) +#define RT5660_DMIC_1L_LH_SFT 13 +#define RT5660_DMIC_1L_LH_RISING (0x0 << 13) +#define RT5660_DMIC_1L_LH_FALLING (0x1 << 13) +#define RT5660_DMIC_1R_LH_MASK (0x1 << 12) +#define RT5660_DMIC_1R_LH_SFT 12 +#define RT5660_DMIC_1R_LH_RISING (0x0 << 12) +#define RT5660_DMIC_1R_LH_FALLING (0x1 << 12) +#define RT5660_SEL_DMIC_DATA_MASK (0x1 << 11) +#define RT5660_SEL_DMIC_DATA_SFT 11 +#define RT5660_SEL_DMIC_DATA_GPIO2 (0x0 << 11) +#define RT5660_SEL_DMIC_DATA_IN1P (0x1 << 11) +#define RT5660_DMIC_CLK_MASK (0x7 << 5) +#define RT5660_DMIC_CLK_SFT 5 + +/* Global Clock Control (0x80) */ +#define RT5660_SCLK_SRC_MASK (0x3 << 14) +#define RT5660_SCLK_SRC_SFT 14 +#define RT5660_SCLK_SRC_MCLK (0x0 << 14) +#define RT5660_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5660_SCLK_SRC_RCCLK (0x2 << 14) +#define RT5660_PLL1_SRC_MASK (0x3 << 12) +#define RT5660_PLL1_SRC_SFT 12 +#define RT5660_PLL1_SRC_MCLK (0x0 << 12) +#define RT5660_PLL1_SRC_BCLK1 (0x1 << 12) +#define RT5660_PLL1_SRC_RCCLK (0x2 << 12) +#define RT5660_PLL1_PD_MASK (0x1 << 3) +#define RT5660_PLL1_PD_SFT 3 +#define RT5660_PLL1_PD_1 (0x0 << 3) +#define RT5660_PLL1_PD_2 (0x1 << 3) + +#define RT5660_PLL_INP_MAX 40000000 +#define RT5660_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x81) */ +#define RT5660_PLL_N_MAX 0x1ff +#define RT5660_PLL_N_MASK (RT5660_PLL_N_MAX << 7) +#define RT5660_PLL_N_SFT 7 +#define RT5660_PLL_K_MAX 0x1f +#define RT5660_PLL_K_MASK (RT5660_PLL_K_MAX) +#define RT5660_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x82) */ +#define RT5660_PLL_M_MAX 0xf +#define RT5660_PLL_M_MASK (RT5660_PLL_M_MAX << 12) +#define RT5660_PLL_M_SFT 12 +#define RT5660_PLL_M_BP (0x1 << 11) +#define RT5660_PLL_M_BP_SFT 11 + +/* Class D Over Current Control (0x8c) */ +#define RT5660_CLSD_OC_MASK (0x1 << 9) +#define RT5660_CLSD_OC_SFT 9 +#define RT5660_CLSD_OC_PU (0x0 << 9) +#define RT5660_CLSD_OC_PD (0x1 << 9) +#define RT5660_AUTO_PD_MASK (0x1 << 8) +#define RT5660_AUTO_PD_SFT 8 +#define RT5660_AUTO_PD_DIS (0x0 << 8) +#define RT5660_AUTO_PD_EN (0x1 << 8) +#define RT5660_CLSD_OC_TH_MASK (0x3f) +#define RT5660_CLSD_OC_TH_SFT 0 + +/* Class D Output Control (0x8d) */ +#define RT5660_CLSD_RATIO_MASK (0xf << 12) +#define RT5660_CLSD_RATIO_SFT 12 + +/* Lout Amp Control 1 (0x8e) */ +#define RT5660_LOUT_CO_MASK (0x1 << 4) +#define RT5660_LOUT_CO_SFT 4 +#define RT5660_LOUT_CO_DIS (0x0 << 4) +#define RT5660_LOUT_CO_EN (0x1 << 4) +#define RT5660_LOUT_CB_MASK (0x1) +#define RT5660_LOUT_CB_SFT 0 +#define RT5660_LOUT_CB_PD (0x0) +#define RT5660_LOUT_CB_PU (0x1) + +/* SPKVDD detection control (0x92) */ +#define RT5660_SPKVDD_DET_MASK (0x1 << 15) +#define RT5660_SPKVDD_DET_SFT 15 +#define RT5660_SPKVDD_DET_DIS (0x0 << 15) +#define RT5660_SPKVDD_DET_EN (0x1 << 15) +#define RT5660_SPK_AG_MASK (0x1 << 14) +#define RT5660_SPK_AG_SFT 14 +#define RT5660_SPK_AG_DIS (0x0 << 14) +#define RT5660_SPK_AG_EN (0x1 << 14) + +/* Micbias Control (0x93) */ +#define RT5660_MIC1_BS_MASK (0x1 << 15) +#define RT5660_MIC1_BS_SFT 15 +#define RT5660_MIC1_BS_9AV (0x0 << 15) +#define RT5660_MIC1_BS_75AV (0x1 << 15) +#define RT5660_MIC2_BS_MASK (0x1 << 14) +#define RT5660_MIC2_BS_SFT 14 +#define RT5660_MIC2_BS_9AV (0x0 << 14) +#define RT5660_MIC2_BS_75AV (0x1 << 14) +#define RT5660_MIC1_OVCD_MASK (0x1 << 11) +#define RT5660_MIC1_OVCD_SFT 11 +#define RT5660_MIC1_OVCD_DIS (0x0 << 11) +#define RT5660_MIC1_OVCD_EN (0x1 << 11) +#define RT5660_MIC1_OVTH_MASK (0x3 << 9) +#define RT5660_MIC1_OVTH_SFT 9 +#define RT5660_MIC1_OVTH_600UA (0x0 << 9) +#define RT5660_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5660_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5660_MIC2_OVCD_MASK (0x1 << 8) +#define RT5660_MIC2_OVCD_SFT 8 +#define RT5660_MIC2_OVCD_DIS (0x0 << 8) +#define RT5660_MIC2_OVCD_EN (0x1 << 8) +#define RT5660_MIC2_OVTH_MASK (0x3 << 6) +#define RT5660_MIC2_OVTH_SFT 6 +#define RT5660_MIC2_OVTH_600UA (0x0 << 6) +#define RT5660_MIC2_OVTH_1500UA (0x1 << 6) +#define RT5660_MIC2_OVTH_2000UA (0x2 << 6) +#define RT5660_PWR_CLK25M_MASK (0x1 << 4) +#define RT5660_PWR_CLK25M_SFT 4 +#define RT5660_PWR_CLK25M_PD (0x0 << 4) +#define RT5660_PWR_CLK25M_PU (0x1 << 4) + +/* EQ Control 1 (0xb0) */ +#define RT5660_EQ_SRC_MASK (0x1 << 15) +#define RT5660_EQ_SRC_SFT 15 +#define RT5660_EQ_SRC_DAC (0x0 << 15) +#define RT5660_EQ_SRC_ADC (0x1 << 15) +#define RT5660_EQ_UPD (0x1 << 14) +#define RT5660_EQ_UPD_BIT 14 + +/* Jack Detect Control (0xbb) */ +#define RT5660_JD_MASK (0x3 << 14) +#define RT5660_JD_SFT 14 +#define RT5660_JD_DIS (0x0 << 14) +#define RT5660_JD_GPIO1 (0x1 << 14) +#define RT5660_JD_GPIO2 (0x2 << 14) +#define RT5660_JD_LOUT_MASK (0x1 << 11) +#define RT5660_JD_LOUT_SFT 11 +#define RT5660_JD_LOUT_DIS (0x0 << 11) +#define RT5660_JD_LOUT_EN (0x1 << 11) +#define RT5660_JD_LOUT_TRG_MASK (0x1 << 10) +#define RT5660_JD_LOUT_TRG_SFT 10 +#define RT5660_JD_LOUT_TRG_LO (0x0 << 10) +#define RT5660_JD_LOUT_TRG_HI (0x1 << 10) +#define RT5660_JD_SPO_MASK (0x1 << 9) +#define RT5660_JD_SPO_SFT 9 +#define RT5660_JD_SPO_DIS (0x0 << 9) +#define RT5660_JD_SPO_EN (0x1 << 9) +#define RT5660_JD_SPO_TRG_MASK (0x1 << 8) +#define RT5660_JD_SPO_TRG_SFT 8 +#define RT5660_JD_SPO_TRG_LO (0x0 << 8) +#define RT5660_JD_SPO_TRG_HI (0x1 << 8) + +/* IRQ Control 1 (0xbd) */ +#define RT5660_IRQ_JD_MASK (0x1 << 15) +#define RT5660_IRQ_JD_SFT 15 +#define RT5660_IRQ_JD_BP (0x0 << 15) +#define RT5660_IRQ_JD_NOR (0x1 << 15) +#define RT5660_IRQ_OT_MASK (0x1 << 14) +#define RT5660_IRQ_OT_SFT 14 +#define RT5660_IRQ_OT_BP (0x0 << 14) +#define RT5660_IRQ_OT_NOR (0x1 << 14) +#define RT5660_JD_STKY_MASK (0x1 << 13) +#define RT5660_JD_STKY_SFT 13 +#define RT5660_JD_STKY_DIS (0x0 << 13) +#define RT5660_JD_STKY_EN (0x1 << 13) +#define RT5660_OT_STKY_MASK (0x1 << 12) +#define RT5660_OT_STKY_SFT 12 +#define RT5660_OT_STKY_DIS (0x0 << 12) +#define RT5660_OT_STKY_EN (0x1 << 12) +#define RT5660_JD_P_MASK (0x1 << 11) +#define RT5660_JD_P_SFT 11 +#define RT5660_JD_P_NOR (0x0 << 11) +#define RT5660_JD_P_INV (0x1 << 11) +#define RT5660_OT_P_MASK (0x1 << 10) +#define RT5660_OT_P_SFT 10 +#define RT5660_OT_P_NOR (0x0 << 10) +#define RT5660_OT_P_INV (0x1 << 10) + +/* IRQ Control 2 (0xbe) */ +#define RT5660_IRQ_MB1_OC_MASK (0x1 << 15) +#define RT5660_IRQ_MB1_OC_SFT 15 +#define RT5660_IRQ_MB1_OC_BP (0x0 << 15) +#define RT5660_IRQ_MB1_OC_NOR (0x1 << 15) +#define RT5660_IRQ_MB2_OC_MASK (0x1 << 14) +#define RT5660_IRQ_MB2_OC_SFT 14 +#define RT5660_IRQ_MB2_OC_BP (0x0 << 14) +#define RT5660_IRQ_MB2_OC_NOR (0x1 << 14) +#define RT5660_MB1_OC_STKY_MASK (0x1 << 11) +#define RT5660_MB1_OC_STKY_SFT 11 +#define RT5660_MB1_OC_STKY_DIS (0x0 << 11) +#define RT5660_MB1_OC_STKY_EN (0x1 << 11) +#define RT5660_MB2_OC_STKY_MASK (0x1 << 10) +#define RT5660_MB2_OC_STKY_SFT 10 +#define RT5660_MB2_OC_STKY_DIS (0x0 << 10) +#define RT5660_MB2_OC_STKY_EN (0x1 << 10) +#define RT5660_MB1_OC_P_MASK (0x1 << 7) +#define RT5660_MB1_OC_P_SFT 7 +#define RT5660_MB1_OC_P_NOR (0x0 << 7) +#define RT5660_MB1_OC_P_INV (0x1 << 7) +#define RT5660_MB2_OC_P_MASK (0x1 << 6) +#define RT5660_MB2_OC_P_SFT 6 +#define RT5660_MB2_OC_P_NOR (0x0 << 6) +#define RT5660_MB2_OC_P_INV (0x1 << 6) +#define RT5660_MB1_OC_CLR (0x1 << 3) +#define RT5660_MB1_OC_CLR_SFT 3 +#define RT5660_MB2_OC_CLR (0x1 << 2) +#define RT5660_MB2_OC_CLR_SFT 2 + +/* GPIO Control 1 (0xc0) */ +#define RT5660_GP2_PIN_MASK (0x1 << 14) +#define RT5660_GP2_PIN_SFT 14 +#define RT5660_GP2_PIN_GPIO2 (0x0 << 14) +#define RT5660_GP2_PIN_DMIC1_SDA (0x1 << 14) +#define RT5660_GP1_PIN_MASK (0x3 << 12) +#define RT5660_GP1_PIN_SFT 12 +#define RT5660_GP1_PIN_GPIO1 (0x0 << 12) +#define RT5660_GP1_PIN_DMIC1_SCL (0x1 << 12) +#define RT5660_GP1_PIN_IRQ (0x2 << 12) +#define RT5660_GPIO_M_MASK (0x1 << 9) +#define RT5660_GPIO_M_SFT 9 +#define RT5660_GPIO_M_FLT (0x0 << 9) +#define RT5660_GPIO_M_PH (0x1 << 9) + +/* GPIO Control 3 (0xc2) */ +#define RT5660_GP2_PF_MASK (0x1 << 5) +#define RT5660_GP2_PF_SFT 5 +#define RT5660_GP2_PF_IN (0x0 << 5) +#define RT5660_GP2_PF_OUT (0x1 << 5) +#define RT5660_GP2_OUT_MASK (0x1 << 4) +#define RT5660_GP2_OUT_SFT 4 +#define RT5660_GP2_OUT_LO (0x0 << 4) +#define RT5660_GP2_OUT_HI (0x1 << 4) +#define RT5660_GP2_P_MASK (0x1 << 3) +#define RT5660_GP2_P_SFT 3 +#define RT5660_GP2_P_NOR (0x0 << 3) +#define RT5660_GP2_P_INV (0x1 << 3) +#define RT5660_GP1_PF_MASK (0x1 << 2) +#define RT5660_GP1_PF_SFT 2 +#define RT5660_GP1_PF_IN (0x0 << 2) +#define RT5660_GP1_PF_OUT (0x1 << 2) +#define RT5660_GP1_OUT_MASK (0x1 << 1) +#define RT5660_GP1_OUT_SFT 1 +#define RT5660_GP1_OUT_LO (0x0 << 1) +#define RT5660_GP1_OUT_HI (0x1 << 1) +#define RT5660_GP1_P_MASK (0x1) +#define RT5660_GP1_P_SFT 0 +#define RT5660_GP1_P_NOR (0x0) +#define RT5660_GP1_P_INV (0x1) + +/* Soft volume and zero cross control 1 (0xd9) */ +#define RT5660_SV_MASK (0x1 << 15) +#define RT5660_SV_SFT 15 +#define RT5660_SV_DIS (0x0 << 15) +#define RT5660_SV_EN (0x1 << 15) +#define RT5660_SPO_SV_MASK (0x1 << 14) +#define RT5660_SPO_SV_SFT 14 +#define RT5660_SPO_SV_DIS (0x0 << 14) +#define RT5660_SPO_SV_EN (0x1 << 14) +#define RT5660_OUT_SV_MASK (0x1 << 12) +#define RT5660_OUT_SV_SFT 12 +#define RT5660_OUT_SV_DIS (0x0 << 12) +#define RT5660_OUT_SV_EN (0x1 << 12) +#define RT5660_ZCD_DIG_MASK (0x1 << 11) +#define RT5660_ZCD_DIG_SFT 11 +#define RT5660_ZCD_DIG_DIS (0x0 << 11) +#define RT5660_ZCD_DIG_EN (0x1 << 11) +#define RT5660_ZCD_MASK (0x1 << 10) +#define RT5660_ZCD_SFT 10 +#define RT5660_ZCD_PD (0x0 << 10) +#define RT5660_ZCD_PU (0x1 << 10) +#define RT5660_SV_DLY_MASK (0xf) +#define RT5660_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0xda) */ +#define RT5660_ZCD_SPO_MASK (0x1 << 15) +#define RT5660_ZCD_SPO_SFT 15 +#define RT5660_ZCD_SPO_DIS (0x0 << 15) +#define RT5660_ZCD_SPO_EN (0x1 << 15) +#define RT5660_ZCD_OMR_MASK (0x1 << 8) +#define RT5660_ZCD_OMR_SFT 8 +#define RT5660_ZCD_OMR_DIS (0x0 << 8) +#define RT5660_ZCD_OMR_EN (0x1 << 8) +#define RT5660_ZCD_OML_MASK (0x1 << 7) +#define RT5660_ZCD_OML_SFT 7 +#define RT5660_ZCD_OML_DIS (0x0 << 7) +#define RT5660_ZCD_OML_EN (0x1 << 7) +#define RT5660_ZCD_SPM_MASK (0x1 << 6) +#define RT5660_ZCD_SPM_SFT 6 +#define RT5660_ZCD_SPM_DIS (0x0 << 6) +#define RT5660_ZCD_SPM_EN (0x1 << 6) +#define RT5660_ZCD_RMR_MASK (0x1 << 5) +#define RT5660_ZCD_RMR_SFT 5 +#define RT5660_ZCD_RMR_DIS (0x0 << 5) +#define RT5660_ZCD_RMR_EN (0x1 << 5) +#define RT5660_ZCD_RML_MASK (0x1 << 4) +#define RT5660_ZCD_RML_SFT 4 +#define RT5660_ZCD_RML_DIS (0x0 << 4) +#define RT5660_ZCD_RML_EN (0x1 << 4) + +/* General Control 1 (0xfa) */ +#define RT5660_PWR_VREF_HP (0x1 << 11) +#define RT5660_PWR_VREF_HP_SFT 11 +#define RT5660_AUTO_DIS_AMP (0x1 << 6) +#define RT5660_MCLK_DET (0x1 << 5) +#define RT5660_POW_CLKDET (0x1 << 1) +#define RT5660_DIG_GATE_CTRL (0x1) +#define RT5660_DIG_GATE_CTRL_SFT 0 + +/* System Clock Source */ +#define RT5660_SCLK_S_MCLK 0 +#define RT5660_SCLK_S_PLL1 1 +#define RT5660_SCLK_S_RCCLK 2 + +/* PLL1 Source */ +#define RT5660_PLL1_S_MCLK 0 +#define RT5660_PLL1_S_BCLK 1 + +enum { + RT5660_AIF1, + RT5660_AIFS, +}; + +struct rt5660_priv { + struct snd_soc_component *component; + struct rt5660_platform_data pdata; + struct regmap *regmap; + struct clk *mclk; + + int sysclk; + int sysclk_src; + int lrck[RT5660_AIFS]; + int bclk[RT5660_AIFS]; + int master[RT5660_AIFS]; + + int pll_src; + int pll_in; + int pll_out; +}; + +#endif diff --git a/sound/soc/codecs/rt5663.c b/sound/soc/codecs/rt5663.c new file mode 100644 index 000000000..b92e1b6ed --- /dev/null +++ b/sound/soc/codecs/rt5663.c @@ -0,0 +1,3687 @@ +/* + * rt5663.c -- RT5663 ALSA SoC audio codec driver + * + * Copyright 2016 Realtek Semiconductor Corp. + * Author: Jack Yu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt5663.h" +#include "rl6231.h" + +#define RT5663_DEVICE_ID_2 0x6451 +#define RT5663_DEVICE_ID_1 0x6406 + +enum { + CODEC_VER_1, + CODEC_VER_0, +}; + +struct impedance_mapping_table { + unsigned int imp_min; + unsigned int imp_max; + unsigned int vol; + unsigned int dc_offset_l_manual; + unsigned int dc_offset_r_manual; + unsigned int dc_offset_l_manual_mic; + unsigned int dc_offset_r_manual_mic; +}; + +struct rt5663_priv { + struct snd_soc_component *component; + struct rt5663_platform_data pdata; + struct regmap *regmap; + struct delayed_work jack_detect_work, jd_unplug_work; + struct snd_soc_jack *hs_jack; + struct timer_list btn_check_timer; + struct impedance_mapping_table *imp_table; + + int codec_ver; + int sysclk; + int sysclk_src; + int lrck; + + int pll_src; + int pll_in; + int pll_out; + + int jack_type; +}; + +static const struct reg_sequence rt5663_patch_list[] = { + { 0x002a, 0x8020 }, + { 0x0086, 0x0028 }, + { 0x0117, 0x0f28 }, + { 0x02fb, 0x8089 }, +}; + +static const struct reg_default rt5663_v2_reg[] = { + { 0x0000, 0x0000 }, + { 0x0001, 0xc8c8 }, + { 0x0002, 0x8080 }, + { 0x0003, 0x8000 }, + { 0x0004, 0xc80a }, + { 0x0005, 0x0000 }, + { 0x0006, 0x0000 }, + { 0x0007, 0x0000 }, + { 0x000a, 0x0000 }, + { 0x000b, 0x0000 }, + { 0x000c, 0x0000 }, + { 0x000d, 0x0000 }, + { 0x000f, 0x0808 }, + { 0x0010, 0x4000 }, + { 0x0011, 0x0000 }, + { 0x0012, 0x1404 }, + { 0x0013, 0x1000 }, + { 0x0014, 0xa00a }, + { 0x0015, 0x0404 }, + { 0x0016, 0x0404 }, + { 0x0017, 0x0011 }, + { 0x0018, 0xafaf }, + { 0x0019, 0xafaf }, + { 0x001a, 0xafaf }, + { 0x001b, 0x0011 }, + { 0x001c, 0x2f2f }, + { 0x001d, 0x2f2f }, + { 0x001e, 0x2f2f }, + { 0x001f, 0x0000 }, + { 0x0020, 0x0000 }, + { 0x0021, 0x0000 }, + { 0x0022, 0x5757 }, + { 0x0023, 0x0039 }, + { 0x0024, 0x000b }, + { 0x0026, 0xc0c0 }, + { 0x0027, 0xc0c0 }, + { 0x0028, 0xc0c0 }, + { 0x0029, 0x8080 }, + { 0x002a, 0xaaaa }, + { 0x002b, 0xaaaa }, + { 0x002c, 0xaba8 }, + { 0x002d, 0x0000 }, + { 0x002e, 0x0000 }, + { 0x002f, 0x0000 }, + { 0x0030, 0x0000 }, + { 0x0031, 0x5000 }, + { 0x0032, 0x0000 }, + { 0x0033, 0x0000 }, + { 0x0034, 0x0000 }, + { 0x0035, 0x0000 }, + { 0x003a, 0x0000 }, + { 0x003b, 0x0000 }, + { 0x003c, 0x00ff }, + { 0x003d, 0x0000 }, + { 0x003e, 0x00ff }, + { 0x003f, 0x0000 }, + { 0x0040, 0x0000 }, + { 0x0041, 0x00ff }, + { 0x0042, 0x0000 }, + { 0x0043, 0x00ff }, + { 0x0044, 0x0c0c }, + { 0x0049, 0xc00b }, + { 0x004a, 0x0000 }, + { 0x004b, 0x031f }, + { 0x004d, 0x0000 }, + { 0x004e, 0x001f }, + { 0x004f, 0x0000 }, + { 0x0050, 0x001f }, + { 0x0052, 0xf000 }, + { 0x0061, 0x0000 }, + { 0x0062, 0x0000 }, + { 0x0063, 0x003e }, + { 0x0064, 0x0000 }, + { 0x0065, 0x0000 }, + { 0x0066, 0x003f }, + { 0x0067, 0x0000 }, + { 0x006b, 0x0000 }, + { 0x006d, 0xff00 }, + { 0x006e, 0x2808 }, + { 0x006f, 0x000a }, + { 0x0070, 0x8000 }, + { 0x0071, 0x8000 }, + { 0x0072, 0x8000 }, + { 0x0073, 0x7000 }, + { 0x0074, 0x7770 }, + { 0x0075, 0x0002 }, + { 0x0076, 0x0001 }, + { 0x0078, 0x00f0 }, + { 0x0079, 0x0000 }, + { 0x007a, 0x0000 }, + { 0x007b, 0x0000 }, + { 0x007c, 0x0000 }, + { 0x007d, 0x0123 }, + { 0x007e, 0x4500 }, + { 0x007f, 0x8003 }, + { 0x0080, 0x0000 }, + { 0x0081, 0x0000 }, + { 0x0082, 0x0000 }, + { 0x0083, 0x0000 }, + { 0x0084, 0x0000 }, + { 0x0085, 0x0000 }, + { 0x0086, 0x0008 }, + { 0x0087, 0x0000 }, + { 0x0088, 0x0000 }, + { 0x0089, 0x0000 }, + { 0x008a, 0x0000 }, + { 0x008b, 0x0000 }, + { 0x008c, 0x0003 }, + { 0x008e, 0x0060 }, + { 0x008f, 0x1000 }, + { 0x0091, 0x0c26 }, + { 0x0092, 0x0073 }, + { 0x0093, 0x0000 }, + { 0x0094, 0x0080 }, + { 0x0098, 0x0000 }, + { 0x0099, 0x0000 }, + { 0x009a, 0x0007 }, + { 0x009f, 0x0000 }, + { 0x00a0, 0x0000 }, + { 0x00a1, 0x0002 }, + { 0x00a2, 0x0001 }, + { 0x00a3, 0x0002 }, + { 0x00a4, 0x0001 }, + { 0x00ae, 0x2040 }, + { 0x00af, 0x0000 }, + { 0x00b6, 0x0000 }, + { 0x00b7, 0x0000 }, + { 0x00b8, 0x0000 }, + { 0x00b9, 0x0000 }, + { 0x00ba, 0x0002 }, + { 0x00bb, 0x0000 }, + { 0x00be, 0x0000 }, + { 0x00c0, 0x0000 }, + { 0x00c1, 0x0aaa }, + { 0x00c2, 0xaa80 }, + { 0x00c3, 0x0003 }, + { 0x00c4, 0x0000 }, + { 0x00d0, 0x0000 }, + { 0x00d1, 0x2244 }, + { 0x00d2, 0x0000 }, + { 0x00d3, 0x3300 }, + { 0x00d4, 0x2200 }, + { 0x00d9, 0x0809 }, + { 0x00da, 0x0000 }, + { 0x00db, 0x0008 }, + { 0x00dc, 0x00c0 }, + { 0x00dd, 0x6724 }, + { 0x00de, 0x3131 }, + { 0x00df, 0x0008 }, + { 0x00e0, 0x4000 }, + { 0x00e1, 0x3131 }, + { 0x00e2, 0x600c }, + { 0x00ea, 0xb320 }, + { 0x00eb, 0x0000 }, + { 0x00ec, 0xb300 }, + { 0x00ed, 0x0000 }, + { 0x00ee, 0xb320 }, + { 0x00ef, 0x0000 }, + { 0x00f0, 0x0201 }, + { 0x00f1, 0x0ddd }, + { 0x00f2, 0x0ddd }, + { 0x00f6, 0x0000 }, + { 0x00f7, 0x0000 }, + { 0x00f8, 0x0000 }, + { 0x00fa, 0x0000 }, + { 0x00fb, 0x0000 }, + { 0x00fc, 0x0000 }, + { 0x00fd, 0x0000 }, + { 0x00fe, 0x10ec }, + { 0x00ff, 0x6451 }, + { 0x0100, 0xaaaa }, + { 0x0101, 0x000a }, + { 0x010a, 0xaaaa }, + { 0x010b, 0xa0a0 }, + { 0x010c, 0xaeae }, + { 0x010d, 0xaaaa }, + { 0x010e, 0xaaaa }, + { 0x010f, 0xaaaa }, + { 0x0110, 0xe002 }, + { 0x0111, 0xa602 }, + { 0x0112, 0xaaaa }, + { 0x0113, 0x2000 }, + { 0x0117, 0x0f00 }, + { 0x0125, 0x0420 }, + { 0x0132, 0x0000 }, + { 0x0133, 0x0000 }, + { 0x0136, 0x5555 }, + { 0x0137, 0x5540 }, + { 0x0138, 0x3700 }, + { 0x0139, 0x79a1 }, + { 0x013a, 0x2020 }, + { 0x013b, 0x2020 }, + { 0x013c, 0x2005 }, + { 0x013f, 0x0000 }, + { 0x0145, 0x0002 }, + { 0x0146, 0x0000 }, + { 0x0147, 0x0000 }, + { 0x0148, 0x0000 }, + { 0x0160, 0x4ec0 }, + { 0x0161, 0x0080 }, + { 0x0162, 0x0200 }, + { 0x0163, 0x0800 }, + { 0x0164, 0x0000 }, + { 0x0165, 0x0000 }, + { 0x0166, 0x0000 }, + { 0x0167, 0x000f }, + { 0x0168, 0x000f }, + { 0x0170, 0x4e80 }, + { 0x0171, 0x0080 }, + { 0x0172, 0x0200 }, + { 0x0173, 0x0800 }, + { 0x0174, 0x00ff }, + { 0x0175, 0x0000 }, + { 0x0190, 0x4131 }, + { 0x0191, 0x4131 }, + { 0x0192, 0x4131 }, + { 0x0193, 0x4131 }, + { 0x0194, 0x0000 }, + { 0x0195, 0x0000 }, + { 0x0196, 0x0000 }, + { 0x0197, 0x0000 }, + { 0x0198, 0x0000 }, + { 0x0199, 0x0000 }, + { 0x01a0, 0x1e64 }, + { 0x01a1, 0x06a3 }, + { 0x01a2, 0x0000 }, + { 0x01a3, 0x0000 }, + { 0x01a4, 0x0000 }, + { 0x01a5, 0x0000 }, + { 0x01a6, 0x0000 }, + { 0x01a7, 0x0000 }, + { 0x01a8, 0x0000 }, + { 0x01a9, 0x0000 }, + { 0x01aa, 0x0000 }, + { 0x01ab, 0x0000 }, + { 0x01b5, 0x0000 }, + { 0x01b6, 0x01c3 }, + { 0x01b7, 0x02a0 }, + { 0x01b8, 0x03e9 }, + { 0x01b9, 0x1389 }, + { 0x01ba, 0xc351 }, + { 0x01bb, 0x0009 }, + { 0x01bc, 0x0018 }, + { 0x01bd, 0x002a }, + { 0x01be, 0x004c }, + { 0x01bf, 0x0097 }, + { 0x01c0, 0x433d }, + { 0x01c1, 0x0000 }, + { 0x01c2, 0x0000 }, + { 0x01c3, 0x0000 }, + { 0x01c4, 0x0000 }, + { 0x01c5, 0x0000 }, + { 0x01c6, 0x0000 }, + { 0x01c7, 0x0000 }, + { 0x01c8, 0x40af }, + { 0x01c9, 0x0702 }, + { 0x01ca, 0x0000 }, + { 0x01cb, 0x0000 }, + { 0x01cc, 0x5757 }, + { 0x01cd, 0x5757 }, + { 0x01ce, 0x5757 }, + { 0x01cf, 0x5757 }, + { 0x01d0, 0x5757 }, + { 0x01d1, 0x5757 }, + { 0x01d2, 0x5757 }, + { 0x01d3, 0x5757 }, + { 0x01d4, 0x5757 }, + { 0x01d5, 0x5757 }, + { 0x01d6, 0x003c }, + { 0x01da, 0x0000 }, + { 0x01db, 0x0000 }, + { 0x01dc, 0x0000 }, + { 0x01de, 0x7c00 }, + { 0x01df, 0x0320 }, + { 0x01e0, 0x06a1 }, + { 0x01e1, 0x0000 }, + { 0x01e2, 0x0000 }, + { 0x01e3, 0x0000 }, + { 0x01e4, 0x0000 }, + { 0x01e5, 0x0000 }, + { 0x01e6, 0x0001 }, + { 0x01e7, 0x0000 }, + { 0x01e8, 0x0000 }, + { 0x01ea, 0x0000 }, + { 0x01eb, 0x0000 }, + { 0x01ec, 0x0000 }, + { 0x01ed, 0x0000 }, + { 0x01ee, 0x0000 }, + { 0x01ef, 0x0000 }, + { 0x01f0, 0x0000 }, + { 0x01f1, 0x0000 }, + { 0x01f2, 0x0000 }, + { 0x01f3, 0x0000 }, + { 0x01f4, 0x0000 }, + { 0x0200, 0x0000 }, + { 0x0201, 0x0000 }, + { 0x0202, 0x0000 }, + { 0x0203, 0x0000 }, + { 0x0204, 0x0000 }, + { 0x0205, 0x0000 }, + { 0x0206, 0x0000 }, + { 0x0207, 0x0000 }, + { 0x0208, 0x0000 }, + { 0x0210, 0x60b1 }, + { 0x0211, 0xa000 }, + { 0x0212, 0x024c }, + { 0x0213, 0xf7ff }, + { 0x0214, 0x024c }, + { 0x0215, 0x0102 }, + { 0x0216, 0x00a3 }, + { 0x0217, 0x0048 }, + { 0x0218, 0x92c0 }, + { 0x0219, 0x0000 }, + { 0x021a, 0x00c8 }, + { 0x021b, 0x0020 }, + { 0x02fa, 0x0000 }, + { 0x02fb, 0x0000 }, + { 0x02fc, 0x0000 }, + { 0x02ff, 0x0110 }, + { 0x0300, 0x001f }, + { 0x0301, 0x032c }, + { 0x0302, 0x5f21 }, + { 0x0303, 0x4000 }, + { 0x0304, 0x4000 }, + { 0x0305, 0x06d5 }, + { 0x0306, 0x8000 }, + { 0x0307, 0x0700 }, + { 0x0310, 0x4560 }, + { 0x0311, 0xa4a8 }, + { 0x0312, 0x7418 }, + { 0x0313, 0x0000 }, + { 0x0314, 0x0006 }, + { 0x0315, 0xffff }, + { 0x0316, 0xc400 }, + { 0x0317, 0x0000 }, + { 0x0330, 0x00a6 }, + { 0x0331, 0x04c3 }, + { 0x0332, 0x27c8 }, + { 0x0333, 0xbf50 }, + { 0x0334, 0x0045 }, + { 0x0335, 0x0007 }, + { 0x0336, 0x7418 }, + { 0x0337, 0x0501 }, + { 0x0338, 0x0000 }, + { 0x0339, 0x0010 }, + { 0x033a, 0x1010 }, + { 0x03c0, 0x7e00 }, + { 0x03c1, 0x8000 }, + { 0x03c2, 0x8000 }, + { 0x03c3, 0x8000 }, + { 0x03c4, 0x8000 }, + { 0x03c5, 0x8000 }, + { 0x03c6, 0x8000 }, + { 0x03c7, 0x8000 }, + { 0x03c8, 0x8000 }, + { 0x03c9, 0x8000 }, + { 0x03ca, 0x8000 }, + { 0x03cb, 0x8000 }, + { 0x03cc, 0x8000 }, + { 0x03d0, 0x0000 }, + { 0x03d1, 0x0000 }, + { 0x03d2, 0x0000 }, + { 0x03d3, 0x0000 }, + { 0x03d4, 0x2000 }, + { 0x03d5, 0x2000 }, + { 0x03d6, 0x0000 }, + { 0x03d7, 0x0000 }, + { 0x03d8, 0x2000 }, + { 0x03d9, 0x2000 }, + { 0x03da, 0x2000 }, + { 0x03db, 0x2000 }, + { 0x03dc, 0x0000 }, + { 0x03dd, 0x0000 }, + { 0x03de, 0x0000 }, + { 0x03df, 0x2000 }, + { 0x03e0, 0x0000 }, + { 0x03e1, 0x0000 }, + { 0x03e2, 0x0000 }, + { 0x03e3, 0x0000 }, + { 0x03e4, 0x0000 }, + { 0x03e5, 0x0000 }, + { 0x03e6, 0x0000 }, + { 0x03e7, 0x0000 }, + { 0x03e8, 0x0000 }, + { 0x03e9, 0x0000 }, + { 0x03ea, 0x0000 }, + { 0x03eb, 0x0000 }, + { 0x03ec, 0x0000 }, + { 0x03ed, 0x0000 }, + { 0x03ee, 0x0000 }, + { 0x03ef, 0x0000 }, + { 0x03f0, 0x0800 }, + { 0x03f1, 0x0800 }, + { 0x03f2, 0x0800 }, + { 0x03f3, 0x0800 }, + { 0x03fe, 0x0000 }, + { 0x03ff, 0x0000 }, + { 0x07f0, 0x0000 }, + { 0x07fa, 0x0000 }, +}; + +static const struct reg_default rt5663_reg[] = { + { 0x0000, 0x0000 }, + { 0x0002, 0x0008 }, + { 0x0005, 0x1000 }, + { 0x0006, 0x1000 }, + { 0x000a, 0x0000 }, + { 0x0010, 0x000f }, + { 0x0015, 0x42f1 }, + { 0x0016, 0x0000 }, + { 0x0018, 0x000b }, + { 0x0019, 0xafaf }, + { 0x001c, 0x2f2f }, + { 0x001f, 0x0000 }, + { 0x0022, 0x5757 }, + { 0x0023, 0x0039 }, + { 0x0026, 0xc0c0 }, + { 0x0029, 0x8080 }, + { 0x002a, 0x8020 }, + { 0x002c, 0x000c }, + { 0x002d, 0x0000 }, + { 0x0040, 0x0808 }, + { 0x0061, 0x0000 }, + { 0x0062, 0x0000 }, + { 0x0063, 0x003e }, + { 0x0064, 0x0000 }, + { 0x0065, 0x0000 }, + { 0x0066, 0x0000 }, + { 0x006b, 0x0000 }, + { 0x006e, 0x0000 }, + { 0x006f, 0x0000 }, + { 0x0070, 0x8020 }, + { 0x0073, 0x1000 }, + { 0x0074, 0xe400 }, + { 0x0075, 0x0002 }, + { 0x0076, 0x0001 }, + { 0x0077, 0x00f0 }, + { 0x0078, 0x0000 }, + { 0x0079, 0x0000 }, + { 0x007a, 0x0123 }, + { 0x007b, 0x8003 }, + { 0x0080, 0x0000 }, + { 0x0081, 0x0000 }, + { 0x0082, 0x0000 }, + { 0x0083, 0x0000 }, + { 0x0084, 0x0000 }, + { 0x0086, 0x0028 }, + { 0x0087, 0x0000 }, + { 0x008a, 0x0000 }, + { 0x008b, 0x0000 }, + { 0x008c, 0x0003 }, + { 0x008e, 0x0008 }, + { 0x008f, 0x1000 }, + { 0x0090, 0x0646 }, + { 0x0091, 0x0e3e }, + { 0x0092, 0x1071 }, + { 0x0093, 0x0000 }, + { 0x0094, 0x0080 }, + { 0x0097, 0x0000 }, + { 0x0098, 0x0000 }, + { 0x009a, 0x0000 }, + { 0x009f, 0x0000 }, + { 0x00ae, 0x6000 }, + { 0x00af, 0x0000 }, + { 0x00b6, 0x0000 }, + { 0x00b7, 0x0000 }, + { 0x00b8, 0x0000 }, + { 0x00ba, 0x0000 }, + { 0x00bb, 0x0000 }, + { 0x00be, 0x0000 }, + { 0x00bf, 0x0000 }, + { 0x00c0, 0x0000 }, + { 0x00c1, 0x0000 }, + { 0x00c5, 0x0000 }, + { 0x00cb, 0xa02f }, + { 0x00cc, 0x0000 }, + { 0x00cd, 0x0e02 }, + { 0x00d9, 0x08f9 }, + { 0x00db, 0x0008 }, + { 0x00dc, 0x00c0 }, + { 0x00dd, 0x6729 }, + { 0x00de, 0x3131 }, + { 0x00df, 0x0008 }, + { 0x00e0, 0x4000 }, + { 0x00e1, 0x3131 }, + { 0x00e2, 0x0043 }, + { 0x00e4, 0x400b }, + { 0x00e5, 0x8031 }, + { 0x00e6, 0x3080 }, + { 0x00e7, 0x4100 }, + { 0x00e8, 0x1400 }, + { 0x00e9, 0xe00a }, + { 0x00ea, 0x0404 }, + { 0x00eb, 0x0404 }, + { 0x00ec, 0xb320 }, + { 0x00ed, 0x0000 }, + { 0x00f4, 0x0000 }, + { 0x00f6, 0x0000 }, + { 0x00f8, 0x0000 }, + { 0x00fa, 0x8000 }, + { 0x00fd, 0x0001 }, + { 0x00fe, 0x10ec }, + { 0x00ff, 0x6406 }, + { 0x0100, 0xa0a0 }, + { 0x0108, 0x4444 }, + { 0x0109, 0x4444 }, + { 0x010a, 0xaaaa }, + { 0x010b, 0x00a0 }, + { 0x010c, 0x8aaa }, + { 0x010d, 0xaaaa }, + { 0x010e, 0x2aaa }, + { 0x010f, 0x002a }, + { 0x0110, 0xa0a4 }, + { 0x0111, 0x4602 }, + { 0x0112, 0x0101 }, + { 0x0113, 0x2000 }, + { 0x0114, 0x0000 }, + { 0x0116, 0x0000 }, + { 0x0117, 0x0f28 }, + { 0x0118, 0x0006 }, + { 0x0125, 0x2424 }, + { 0x0126, 0x5550 }, + { 0x0127, 0x0400 }, + { 0x0128, 0x7711 }, + { 0x0132, 0x0004 }, + { 0x0137, 0x5441 }, + { 0x0139, 0x79a1 }, + { 0x013a, 0x30c0 }, + { 0x013b, 0x2000 }, + { 0x013c, 0x2005 }, + { 0x013d, 0x30c0 }, + { 0x013e, 0x0000 }, + { 0x0140, 0x3700 }, + { 0x0141, 0x1f00 }, + { 0x0144, 0x0000 }, + { 0x0145, 0x0002 }, + { 0x0146, 0x0000 }, + { 0x0160, 0x0e80 }, + { 0x0161, 0x0080 }, + { 0x0162, 0x0200 }, + { 0x0163, 0x0800 }, + { 0x0164, 0x0000 }, + { 0x0165, 0x0000 }, + { 0x0166, 0x0000 }, + { 0x0167, 0x1417 }, + { 0x0168, 0x0017 }, + { 0x0169, 0x0017 }, + { 0x0180, 0x2000 }, + { 0x0181, 0x0000 }, + { 0x0182, 0x0000 }, + { 0x0183, 0x2000 }, + { 0x0184, 0x0000 }, + { 0x0185, 0x0000 }, + { 0x01b0, 0x4b30 }, + { 0x01b1, 0x0000 }, + { 0x01b2, 0xd870 }, + { 0x01b3, 0x0000 }, + { 0x01b4, 0x0030 }, + { 0x01b5, 0x5757 }, + { 0x01b6, 0x5757 }, + { 0x01b7, 0x5757 }, + { 0x01b8, 0x5757 }, + { 0x01c0, 0x433d }, + { 0x01c1, 0x0540 }, + { 0x01c2, 0x0000 }, + { 0x01c3, 0x0000 }, + { 0x01c4, 0x0000 }, + { 0x01c5, 0x0009 }, + { 0x01c6, 0x0018 }, + { 0x01c7, 0x002a }, + { 0x01c8, 0x004c }, + { 0x01c9, 0x0097 }, + { 0x01ca, 0x01c3 }, + { 0x01cb, 0x03e9 }, + { 0x01cc, 0x1389 }, + { 0x01cd, 0xc351 }, + { 0x01ce, 0x0000 }, + { 0x01cf, 0x0000 }, + { 0x01d0, 0x0000 }, + { 0x01d1, 0x0000 }, + { 0x01d2, 0x0000 }, + { 0x01d3, 0x003c }, + { 0x01d4, 0x5757 }, + { 0x01d5, 0x5757 }, + { 0x01d6, 0x5757 }, + { 0x01d7, 0x5757 }, + { 0x01d8, 0x5757 }, + { 0x01d9, 0x5757 }, + { 0x01da, 0x0000 }, + { 0x01db, 0x0000 }, + { 0x01dd, 0x0009 }, + { 0x01de, 0x7f00 }, + { 0x01df, 0x00c8 }, + { 0x01e0, 0x0691 }, + { 0x01e1, 0x0000 }, + { 0x01e2, 0x0000 }, + { 0x01e3, 0x0000 }, + { 0x01e4, 0x0000 }, + { 0x01e5, 0x0040 }, + { 0x01e6, 0x0000 }, + { 0x01e7, 0x0000 }, + { 0x01e8, 0x0000 }, + { 0x01ea, 0x0000 }, + { 0x01eb, 0x0000 }, + { 0x01ec, 0x0000 }, + { 0x01ed, 0x0000 }, + { 0x01ee, 0x0000 }, + { 0x01ef, 0x0000 }, + { 0x01f0, 0x0000 }, + { 0x01f1, 0x0000 }, + { 0x01f2, 0x0000 }, + { 0x0200, 0x0000 }, + { 0x0201, 0x2244 }, + { 0x0202, 0xaaaa }, + { 0x0250, 0x8010 }, + { 0x0251, 0x0000 }, + { 0x0252, 0x028a }, + { 0x02fa, 0x0000 }, + { 0x02fb, 0x8089 }, + { 0x02fc, 0x0300 }, + { 0x0300, 0x0000 }, + { 0x03d0, 0x0000 }, + { 0x03d1, 0x0000 }, + { 0x03d2, 0x0000 }, + { 0x03d3, 0x0000 }, + { 0x03d4, 0x2000 }, + { 0x03d5, 0x2000 }, + { 0x03d6, 0x0000 }, + { 0x03d7, 0x0000 }, + { 0x03d8, 0x2000 }, + { 0x03d9, 0x2000 }, + { 0x03da, 0x2000 }, + { 0x03db, 0x2000 }, + { 0x03dc, 0x0000 }, + { 0x03dd, 0x0000 }, + { 0x03de, 0x0000 }, + { 0x03df, 0x2000 }, + { 0x03e0, 0x0000 }, + { 0x03e1, 0x0000 }, + { 0x03e2, 0x0000 }, + { 0x03e3, 0x0000 }, + { 0x03e4, 0x0000 }, + { 0x03e5, 0x0000 }, + { 0x03e6, 0x0000 }, + { 0x03e7, 0x0000 }, + { 0x03e8, 0x0000 }, + { 0x03e9, 0x0000 }, + { 0x03ea, 0x0000 }, + { 0x03eb, 0x0000 }, + { 0x03ec, 0x0000 }, + { 0x03ed, 0x0000 }, + { 0x03ee, 0x0000 }, + { 0x03ef, 0x0000 }, + { 0x03f0, 0x0800 }, + { 0x03f1, 0x0800 }, + { 0x03f2, 0x0800 }, + { 0x03f3, 0x0800 }, +}; + +static bool rt5663_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5663_RESET: + case RT5663_SIL_DET_CTL: + case RT5663_HP_IMP_GAIN_2: + case RT5663_AD_DA_MIXER: + case RT5663_FRAC_DIV_2: + case RT5663_MICBIAS_1: + case RT5663_ASRC_11_2: + case RT5663_ADC_EQ_1: + case RT5663_INT_ST_1: + case RT5663_INT_ST_2: + case RT5663_GPIO_STA1: + case RT5663_SIN_GEN_1: + case RT5663_IL_CMD_1: + case RT5663_IL_CMD_5: + case RT5663_IL_CMD_PWRSAV1: + case RT5663_EM_JACK_TYPE_1: + case RT5663_EM_JACK_TYPE_2: + case RT5663_EM_JACK_TYPE_3: + case RT5663_JD_CTRL2: + case RT5663_VENDOR_ID: + case RT5663_VENDOR_ID_1: + case RT5663_VENDOR_ID_2: + case RT5663_PLL_INT_REG: + case RT5663_SOFT_RAMP: + case RT5663_STO_DRE_1: + case RT5663_STO_DRE_5: + case RT5663_STO_DRE_6: + case RT5663_STO_DRE_7: + case RT5663_MIC_DECRO_1: + case RT5663_MIC_DECRO_4: + case RT5663_HP_IMP_SEN_1: + case RT5663_HP_IMP_SEN_3: + case RT5663_HP_IMP_SEN_4: + case RT5663_HP_IMP_SEN_5: + case RT5663_HP_CALIB_1_1: + case RT5663_HP_CALIB_9: + case RT5663_HP_CALIB_ST1: + case RT5663_HP_CALIB_ST2: + case RT5663_HP_CALIB_ST3: + case RT5663_HP_CALIB_ST4: + case RT5663_HP_CALIB_ST5: + case RT5663_HP_CALIB_ST6: + case RT5663_HP_CALIB_ST7: + case RT5663_HP_CALIB_ST8: + case RT5663_HP_CALIB_ST9: + case RT5663_ANA_JD: + return true; + default: + return false; + } +} + +static bool rt5663_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5663_RESET: + case RT5663_HP_OUT_EN: + case RT5663_HP_LCH_DRE: + case RT5663_HP_RCH_DRE: + case RT5663_CALIB_BST: + case RT5663_RECMIX: + case RT5663_SIL_DET_CTL: + case RT5663_PWR_SAV_SILDET: + case RT5663_SIDETONE_CTL: + case RT5663_STO1_DAC_DIG_VOL: + case RT5663_STO1_ADC_DIG_VOL: + case RT5663_STO1_BOOST: + case RT5663_HP_IMP_GAIN_1: + case RT5663_HP_IMP_GAIN_2: + case RT5663_STO1_ADC_MIXER: + case RT5663_AD_DA_MIXER: + case RT5663_STO_DAC_MIXER: + case RT5663_DIG_SIDE_MIXER: + case RT5663_BYPASS_STO_DAC: + case RT5663_CALIB_REC_MIX: + case RT5663_PWR_DIG_1: + case RT5663_PWR_DIG_2: + case RT5663_PWR_ANLG_1: + case RT5663_PWR_ANLG_2: + case RT5663_PWR_ANLG_3: + case RT5663_PWR_MIXER: + case RT5663_SIG_CLK_DET: + case RT5663_PRE_DIV_GATING_1: + case RT5663_PRE_DIV_GATING_2: + case RT5663_I2S1_SDP: + case RT5663_ADDA_CLK_1: + case RT5663_ADDA_RST: + case RT5663_FRAC_DIV_1: + case RT5663_FRAC_DIV_2: + case RT5663_TDM_1: + case RT5663_TDM_2: + case RT5663_TDM_3: + case RT5663_TDM_4: + case RT5663_TDM_5: + case RT5663_GLB_CLK: + case RT5663_PLL_1: + case RT5663_PLL_2: + case RT5663_ASRC_1: + case RT5663_ASRC_2: + case RT5663_ASRC_4: + case RT5663_DUMMY_REG: + case RT5663_ASRC_8: + case RT5663_ASRC_9: + case RT5663_ASRC_11: + case RT5663_DEPOP_1: + case RT5663_DEPOP_2: + case RT5663_DEPOP_3: + case RT5663_HP_CHARGE_PUMP_1: + case RT5663_HP_CHARGE_PUMP_2: + case RT5663_MICBIAS_1: + case RT5663_RC_CLK: + case RT5663_ASRC_11_2: + case RT5663_DUMMY_REG_2: + case RT5663_REC_PATH_GAIN: + case RT5663_AUTO_1MRC_CLK: + case RT5663_ADC_EQ_1: + case RT5663_ADC_EQ_2: + case RT5663_IRQ_1: + case RT5663_IRQ_2: + case RT5663_IRQ_3: + case RT5663_IRQ_4: + case RT5663_IRQ_5: + case RT5663_INT_ST_1: + case RT5663_INT_ST_2: + case RT5663_GPIO_1: + case RT5663_GPIO_2: + case RT5663_GPIO_STA1: + case RT5663_SIN_GEN_1: + case RT5663_SIN_GEN_2: + case RT5663_SIN_GEN_3: + case RT5663_SOF_VOL_ZC1: + case RT5663_IL_CMD_1: + case RT5663_IL_CMD_2: + case RT5663_IL_CMD_3: + case RT5663_IL_CMD_4: + case RT5663_IL_CMD_5: + case RT5663_IL_CMD_6: + case RT5663_IL_CMD_7: + case RT5663_IL_CMD_8: + case RT5663_IL_CMD_PWRSAV1: + case RT5663_IL_CMD_PWRSAV2: + case RT5663_EM_JACK_TYPE_1: + case RT5663_EM_JACK_TYPE_2: + case RT5663_EM_JACK_TYPE_3: + case RT5663_EM_JACK_TYPE_4: + case RT5663_EM_JACK_TYPE_5: + case RT5663_EM_JACK_TYPE_6: + case RT5663_STO1_HPF_ADJ1: + case RT5663_STO1_HPF_ADJ2: + case RT5663_FAST_OFF_MICBIAS: + case RT5663_JD_CTRL1: + case RT5663_JD_CTRL2: + case RT5663_DIG_MISC: + case RT5663_VENDOR_ID: + case RT5663_VENDOR_ID_1: + case RT5663_VENDOR_ID_2: + case RT5663_DIG_VOL_ZCD: + case RT5663_ANA_BIAS_CUR_1: + case RT5663_ANA_BIAS_CUR_2: + case RT5663_ANA_BIAS_CUR_3: + case RT5663_ANA_BIAS_CUR_4: + case RT5663_ANA_BIAS_CUR_5: + case RT5663_ANA_BIAS_CUR_6: + case RT5663_BIAS_CUR_5: + case RT5663_BIAS_CUR_6: + case RT5663_BIAS_CUR_7: + case RT5663_BIAS_CUR_8: + case RT5663_DACREF_LDO: + case RT5663_DUMMY_REG_3: + case RT5663_BIAS_CUR_9: + case RT5663_DUMMY_REG_4: + case RT5663_VREFADJ_OP: + case RT5663_VREF_RECMIX: + case RT5663_CHARGE_PUMP_1: + case RT5663_CHARGE_PUMP_1_2: + case RT5663_CHARGE_PUMP_1_3: + case RT5663_CHARGE_PUMP_2: + case RT5663_DIG_IN_PIN1: + case RT5663_PAD_DRV_CTL: + case RT5663_PLL_INT_REG: + case RT5663_CHOP_DAC_L: + case RT5663_CHOP_ADC: + case RT5663_CALIB_ADC: + case RT5663_CHOP_DAC_R: + case RT5663_DUMMY_CTL_DACLR: + case RT5663_DUMMY_REG_5: + case RT5663_SOFT_RAMP: + case RT5663_TEST_MODE_1: + case RT5663_TEST_MODE_2: + case RT5663_TEST_MODE_3: + case RT5663_STO_DRE_1: + case RT5663_STO_DRE_2: + case RT5663_STO_DRE_3: + case RT5663_STO_DRE_4: + case RT5663_STO_DRE_5: + case RT5663_STO_DRE_6: + case RT5663_STO_DRE_7: + case RT5663_STO_DRE_8: + case RT5663_STO_DRE_9: + case RT5663_STO_DRE_10: + case RT5663_MIC_DECRO_1: + case RT5663_MIC_DECRO_2: + case RT5663_MIC_DECRO_3: + case RT5663_MIC_DECRO_4: + case RT5663_MIC_DECRO_5: + case RT5663_MIC_DECRO_6: + case RT5663_HP_DECRO_1: + case RT5663_HP_DECRO_2: + case RT5663_HP_DECRO_3: + case RT5663_HP_DECRO_4: + case RT5663_HP_DECOUP: + case RT5663_HP_IMP_SEN_MAP8: + case RT5663_HP_IMP_SEN_MAP9: + case RT5663_HP_IMP_SEN_MAP10: + case RT5663_HP_IMP_SEN_MAP11: + case RT5663_HP_IMP_SEN_1: + case RT5663_HP_IMP_SEN_2: + case RT5663_HP_IMP_SEN_3: + case RT5663_HP_IMP_SEN_4: + case RT5663_HP_IMP_SEN_5: + case RT5663_HP_IMP_SEN_6: + case RT5663_HP_IMP_SEN_7: + case RT5663_HP_IMP_SEN_8: + case RT5663_HP_IMP_SEN_9: + case RT5663_HP_IMP_SEN_10: + case RT5663_HP_IMP_SEN_11: + case RT5663_HP_IMP_SEN_12: + case RT5663_HP_IMP_SEN_13: + case RT5663_HP_IMP_SEN_14: + case RT5663_HP_IMP_SEN_15: + case RT5663_HP_IMP_SEN_16: + case RT5663_HP_IMP_SEN_17: + case RT5663_HP_IMP_SEN_18: + case RT5663_HP_IMP_SEN_19: + case RT5663_HP_IMPSEN_DIG5: + case RT5663_HP_IMPSEN_MAP1: + case RT5663_HP_IMPSEN_MAP2: + case RT5663_HP_IMPSEN_MAP3: + case RT5663_HP_IMPSEN_MAP4: + case RT5663_HP_IMPSEN_MAP5: + case RT5663_HP_IMPSEN_MAP7: + case RT5663_HP_LOGIC_1: + case RT5663_HP_LOGIC_2: + case RT5663_HP_CALIB_1: + case RT5663_HP_CALIB_1_1: + case RT5663_HP_CALIB_2: + case RT5663_HP_CALIB_3: + case RT5663_HP_CALIB_4: + case RT5663_HP_CALIB_5: + case RT5663_HP_CALIB_5_1: + case RT5663_HP_CALIB_6: + case RT5663_HP_CALIB_7: + case RT5663_HP_CALIB_9: + case RT5663_HP_CALIB_10: + case RT5663_HP_CALIB_11: + case RT5663_HP_CALIB_ST1: + case RT5663_HP_CALIB_ST2: + case RT5663_HP_CALIB_ST3: + case RT5663_HP_CALIB_ST4: + case RT5663_HP_CALIB_ST5: + case RT5663_HP_CALIB_ST6: + case RT5663_HP_CALIB_ST7: + case RT5663_HP_CALIB_ST8: + case RT5663_HP_CALIB_ST9: + case RT5663_HP_AMP_DET: + case RT5663_DUMMY_REG_6: + case RT5663_HP_BIAS: + case RT5663_CBJ_1: + case RT5663_CBJ_2: + case RT5663_CBJ_3: + case RT5663_DUMMY_1: + case RT5663_DUMMY_2: + case RT5663_DUMMY_3: + case RT5663_ANA_JD: + case RT5663_ADC_LCH_LPF1_A1: + case RT5663_ADC_RCH_LPF1_A1: + case RT5663_ADC_LCH_LPF1_H0: + case RT5663_ADC_RCH_LPF1_H0: + case RT5663_ADC_LCH_BPF1_A1: + case RT5663_ADC_RCH_BPF1_A1: + case RT5663_ADC_LCH_BPF1_A2: + case RT5663_ADC_RCH_BPF1_A2: + case RT5663_ADC_LCH_BPF1_H0: + case RT5663_ADC_RCH_BPF1_H0: + case RT5663_ADC_LCH_BPF2_A1: + case RT5663_ADC_RCH_BPF2_A1: + case RT5663_ADC_LCH_BPF2_A2: + case RT5663_ADC_RCH_BPF2_A2: + case RT5663_ADC_LCH_BPF2_H0: + case RT5663_ADC_RCH_BPF2_H0: + case RT5663_ADC_LCH_BPF3_A1: + case RT5663_ADC_RCH_BPF3_A1: + case RT5663_ADC_LCH_BPF3_A2: + case RT5663_ADC_RCH_BPF3_A2: + case RT5663_ADC_LCH_BPF3_H0: + case RT5663_ADC_RCH_BPF3_H0: + case RT5663_ADC_LCH_BPF4_A1: + case RT5663_ADC_RCH_BPF4_A1: + case RT5663_ADC_LCH_BPF4_A2: + case RT5663_ADC_RCH_BPF4_A2: + case RT5663_ADC_LCH_BPF4_H0: + case RT5663_ADC_RCH_BPF4_H0: + case RT5663_ADC_LCH_HPF1_A1: + case RT5663_ADC_RCH_HPF1_A1: + case RT5663_ADC_LCH_HPF1_H0: + case RT5663_ADC_RCH_HPF1_H0: + case RT5663_ADC_EQ_PRE_VOL_L: + case RT5663_ADC_EQ_PRE_VOL_R: + case RT5663_ADC_EQ_POST_VOL_L: + case RT5663_ADC_EQ_POST_VOL_R: + return true; + default: + return false; + } +} + +static bool rt5663_v2_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5663_RESET: + case RT5663_CBJ_TYPE_2: + case RT5663_PDM_OUT_CTL: + case RT5663_PDM_I2C_DATA_CTL1: + case RT5663_PDM_I2C_DATA_CTL4: + case RT5663_ALC_BK_GAIN: + case RT5663_PLL_2: + case RT5663_MICBIAS_1: + case RT5663_ADC_EQ_1: + case RT5663_INT_ST_1: + case RT5663_GPIO_STA2: + case RT5663_IL_CMD_1: + case RT5663_IL_CMD_5: + case RT5663_A_JD_CTRL: + case RT5663_JD_CTRL2: + case RT5663_VENDOR_ID: + case RT5663_VENDOR_ID_1: + case RT5663_VENDOR_ID_2: + case RT5663_STO_DRE_1: + case RT5663_STO_DRE_5: + case RT5663_STO_DRE_6: + case RT5663_STO_DRE_7: + case RT5663_MONO_DYNA_6: + case RT5663_STO1_SIL_DET: + case RT5663_MONOL_SIL_DET: + case RT5663_MONOR_SIL_DET: + case RT5663_STO2_DAC_SIL: + case RT5663_MONO_AMP_CAL_ST1: + case RT5663_MONO_AMP_CAL_ST2: + case RT5663_MONO_AMP_CAL_ST3: + case RT5663_MONO_AMP_CAL_ST4: + case RT5663_HP_IMP_SEN_2: + case RT5663_HP_IMP_SEN_3: + case RT5663_HP_IMP_SEN_4: + case RT5663_HP_IMP_SEN_10: + case RT5663_HP_CALIB_1: + case RT5663_HP_CALIB_10: + case RT5663_HP_CALIB_ST1: + case RT5663_HP_CALIB_ST4: + case RT5663_HP_CALIB_ST5: + case RT5663_HP_CALIB_ST6: + case RT5663_HP_CALIB_ST7: + case RT5663_HP_CALIB_ST8: + case RT5663_HP_CALIB_ST9: + case RT5663_HP_CALIB_ST10: + case RT5663_HP_CALIB_ST11: + return true; + default: + return false; + } +} + +static bool rt5663_v2_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5663_LOUT_CTRL: + case RT5663_HP_AMP_2: + case RT5663_MONO_OUT: + case RT5663_MONO_GAIN: + case RT5663_AEC_BST: + case RT5663_IN1_IN2: + case RT5663_IN3_IN4: + case RT5663_INL1_INR1: + case RT5663_CBJ_TYPE_2: + case RT5663_CBJ_TYPE_3: + case RT5663_CBJ_TYPE_4: + case RT5663_CBJ_TYPE_5: + case RT5663_CBJ_TYPE_8: + case RT5663_DAC3_DIG_VOL: + case RT5663_DAC3_CTRL: + case RT5663_MONO_ADC_DIG_VOL: + case RT5663_STO2_ADC_DIG_VOL: + case RT5663_MONO_ADC_BST_GAIN: + case RT5663_STO2_ADC_BST_GAIN: + case RT5663_SIDETONE_CTRL: + case RT5663_MONO1_ADC_MIXER: + case RT5663_STO2_ADC_MIXER: + case RT5663_MONO_DAC_MIXER: + case RT5663_DAC2_SRC_CTRL: + case RT5663_IF_3_4_DATA_CTL: + case RT5663_IF_5_DATA_CTL: + case RT5663_PDM_OUT_CTL: + case RT5663_PDM_I2C_DATA_CTL1: + case RT5663_PDM_I2C_DATA_CTL2: + case RT5663_PDM_I2C_DATA_CTL3: + case RT5663_PDM_I2C_DATA_CTL4: + case RT5663_RECMIX1_NEW: + case RT5663_RECMIX1L_0: + case RT5663_RECMIX1L: + case RT5663_RECMIX1R_0: + case RT5663_RECMIX1R: + case RT5663_RECMIX2_NEW: + case RT5663_RECMIX2_L_2: + case RT5663_RECMIX2_R: + case RT5663_RECMIX2_R_2: + case RT5663_CALIB_REC_LR: + case RT5663_ALC_BK_GAIN: + case RT5663_MONOMIX_GAIN: + case RT5663_MONOMIX_IN_GAIN: + case RT5663_OUT_MIXL_GAIN: + case RT5663_OUT_LMIX_IN_GAIN: + case RT5663_OUT_RMIX_IN_GAIN: + case RT5663_OUT_RMIX_IN_GAIN1: + case RT5663_LOUT_MIXER_CTRL: + case RT5663_PWR_VOL: + case RT5663_ADCDAC_RST: + case RT5663_I2S34_SDP: + case RT5663_I2S5_SDP: + case RT5663_TDM_6: + case RT5663_TDM_7: + case RT5663_TDM_8: + case RT5663_TDM_9: + case RT5663_ASRC_3: + case RT5663_ASRC_6: + case RT5663_ASRC_7: + case RT5663_PLL_TRK_13: + case RT5663_I2S_M_CLK_CTL: + case RT5663_FDIV_I2S34_M_CLK: + case RT5663_FDIV_I2S34_M_CLK2: + case RT5663_FDIV_I2S5_M_CLK: + case RT5663_FDIV_I2S5_M_CLK2: + case RT5663_V2_IRQ_4: + case RT5663_GPIO_3: + case RT5663_GPIO_4: + case RT5663_GPIO_STA2: + case RT5663_HP_AMP_DET1: + case RT5663_HP_AMP_DET2: + case RT5663_HP_AMP_DET3: + case RT5663_MID_BD_HP_AMP: + case RT5663_LOW_BD_HP_AMP: + case RT5663_SOF_VOL_ZC2: + case RT5663_ADC_STO2_ADJ1: + case RT5663_ADC_STO2_ADJ2: + case RT5663_A_JD_CTRL: + case RT5663_JD1_TRES_CTRL: + case RT5663_JD2_TRES_CTRL: + case RT5663_V2_JD_CTRL2: + case RT5663_DUM_REG_2: + case RT5663_DUM_REG_3: + case RT5663_VENDOR_ID: + case RT5663_VENDOR_ID_1: + case RT5663_VENDOR_ID_2: + case RT5663_DACADC_DIG_VOL2: + case RT5663_DIG_IN_PIN2: + case RT5663_PAD_DRV_CTL1: + case RT5663_SOF_RAM_DEPOP: + case RT5663_VOL_TEST: + case RT5663_TEST_MODE_4: + case RT5663_TEST_MODE_5: + case RT5663_STO_DRE_9: + case RT5663_MONO_DYNA_1: + case RT5663_MONO_DYNA_2: + case RT5663_MONO_DYNA_3: + case RT5663_MONO_DYNA_4: + case RT5663_MONO_DYNA_5: + case RT5663_MONO_DYNA_6: + case RT5663_STO1_SIL_DET: + case RT5663_MONOL_SIL_DET: + case RT5663_MONOR_SIL_DET: + case RT5663_STO2_DAC_SIL: + case RT5663_PWR_SAV_CTL1: + case RT5663_PWR_SAV_CTL2: + case RT5663_PWR_SAV_CTL3: + case RT5663_PWR_SAV_CTL4: + case RT5663_PWR_SAV_CTL5: + case RT5663_PWR_SAV_CTL6: + case RT5663_MONO_AMP_CAL1: + case RT5663_MONO_AMP_CAL2: + case RT5663_MONO_AMP_CAL3: + case RT5663_MONO_AMP_CAL4: + case RT5663_MONO_AMP_CAL5: + case RT5663_MONO_AMP_CAL6: + case RT5663_MONO_AMP_CAL7: + case RT5663_MONO_AMP_CAL_ST1: + case RT5663_MONO_AMP_CAL_ST2: + case RT5663_MONO_AMP_CAL_ST3: + case RT5663_MONO_AMP_CAL_ST4: + case RT5663_MONO_AMP_CAL_ST5: + case RT5663_V2_HP_IMP_SEN_13: + case RT5663_V2_HP_IMP_SEN_14: + case RT5663_V2_HP_IMP_SEN_6: + case RT5663_V2_HP_IMP_SEN_7: + case RT5663_V2_HP_IMP_SEN_8: + case RT5663_V2_HP_IMP_SEN_9: + case RT5663_V2_HP_IMP_SEN_10: + case RT5663_HP_LOGIC_3: + case RT5663_HP_CALIB_ST10: + case RT5663_HP_CALIB_ST11: + case RT5663_PRO_REG_TBL_4: + case RT5663_PRO_REG_TBL_5: + case RT5663_PRO_REG_TBL_6: + case RT5663_PRO_REG_TBL_7: + case RT5663_PRO_REG_TBL_8: + case RT5663_PRO_REG_TBL_9: + case RT5663_SAR_ADC_INL_1: + case RT5663_SAR_ADC_INL_2: + case RT5663_SAR_ADC_INL_3: + case RT5663_SAR_ADC_INL_4: + case RT5663_SAR_ADC_INL_5: + case RT5663_SAR_ADC_INL_6: + case RT5663_SAR_ADC_INL_7: + case RT5663_SAR_ADC_INL_8: + case RT5663_SAR_ADC_INL_9: + case RT5663_SAR_ADC_INL_10: + case RT5663_SAR_ADC_INL_11: + case RT5663_SAR_ADC_INL_12: + case RT5663_DRC_CTRL_1: + case RT5663_DRC1_CTRL_2: + case RT5663_DRC1_CTRL_3: + case RT5663_DRC1_CTRL_4: + case RT5663_DRC1_CTRL_5: + case RT5663_DRC1_CTRL_6: + case RT5663_DRC1_HD_CTRL_1: + case RT5663_DRC1_HD_CTRL_2: + case RT5663_DRC1_PRI_REG_1: + case RT5663_DRC1_PRI_REG_2: + case RT5663_DRC1_PRI_REG_3: + case RT5663_DRC1_PRI_REG_4: + case RT5663_DRC1_PRI_REG_5: + case RT5663_DRC1_PRI_REG_6: + case RT5663_DRC1_PRI_REG_7: + case RT5663_DRC1_PRI_REG_8: + case RT5663_ALC_PGA_CTL_1: + case RT5663_ALC_PGA_CTL_2: + case RT5663_ALC_PGA_CTL_3: + case RT5663_ALC_PGA_CTL_4: + case RT5663_ALC_PGA_CTL_5: + case RT5663_ALC_PGA_CTL_6: + case RT5663_ALC_PGA_CTL_7: + case RT5663_ALC_PGA_CTL_8: + case RT5663_ALC_PGA_REG_1: + case RT5663_ALC_PGA_REG_2: + case RT5663_ALC_PGA_REG_3: + case RT5663_ADC_EQ_RECOV_1: + case RT5663_ADC_EQ_RECOV_2: + case RT5663_ADC_EQ_RECOV_3: + case RT5663_ADC_EQ_RECOV_4: + case RT5663_ADC_EQ_RECOV_5: + case RT5663_ADC_EQ_RECOV_6: + case RT5663_ADC_EQ_RECOV_7: + case RT5663_ADC_EQ_RECOV_8: + case RT5663_ADC_EQ_RECOV_9: + case RT5663_ADC_EQ_RECOV_10: + case RT5663_ADC_EQ_RECOV_11: + case RT5663_ADC_EQ_RECOV_12: + case RT5663_ADC_EQ_RECOV_13: + case RT5663_VID_HIDDEN: + case RT5663_VID_CUSTOMER: + case RT5663_SCAN_MODE: + case RT5663_I2C_BYPA: + return true; + case RT5663_TDM_1: + case RT5663_DEPOP_3: + case RT5663_ASRC_11_2: + case RT5663_INT_ST_2: + case RT5663_GPIO_STA1: + case RT5663_SIN_GEN_1: + case RT5663_SIN_GEN_2: + case RT5663_SIN_GEN_3: + case RT5663_IL_CMD_PWRSAV1: + case RT5663_IL_CMD_PWRSAV2: + case RT5663_EM_JACK_TYPE_1: + case RT5663_EM_JACK_TYPE_2: + case RT5663_EM_JACK_TYPE_3: + case RT5663_EM_JACK_TYPE_4: + case RT5663_FAST_OFF_MICBIAS: + case RT5663_ANA_BIAS_CUR_1: + case RT5663_ANA_BIAS_CUR_2: + case RT5663_BIAS_CUR_9: + case RT5663_DUMMY_REG_4: + case RT5663_VREF_RECMIX: + case RT5663_CHARGE_PUMP_1_2: + case RT5663_CHARGE_PUMP_1_3: + case RT5663_CHARGE_PUMP_2: + case RT5663_CHOP_DAC_R: + case RT5663_DUMMY_CTL_DACLR: + case RT5663_DUMMY_REG_5: + case RT5663_SOFT_RAMP: + case RT5663_TEST_MODE_1: + case RT5663_STO_DRE_10: + case RT5663_MIC_DECRO_1: + case RT5663_MIC_DECRO_2: + case RT5663_MIC_DECRO_3: + case RT5663_MIC_DECRO_4: + case RT5663_MIC_DECRO_5: + case RT5663_MIC_DECRO_6: + case RT5663_HP_DECRO_1: + case RT5663_HP_DECRO_2: + case RT5663_HP_DECRO_3: + case RT5663_HP_DECRO_4: + case RT5663_HP_DECOUP: + case RT5663_HP_IMPSEN_MAP4: + case RT5663_HP_IMPSEN_MAP5: + case RT5663_HP_IMPSEN_MAP7: + case RT5663_HP_CALIB_1: + case RT5663_CBJ_1: + case RT5663_CBJ_2: + case RT5663_CBJ_3: + return false; + default: + return rt5663_readable_register(dev, reg); + } +} + +static const DECLARE_TLV_DB_SCALE(rt5663_hp_vol_tlv, -2400, 150, 0); +static const DECLARE_TLV_DB_SCALE(rt5663_v2_hp_vol_tlv, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1725, 75, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(in_bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* Interface data select */ +static const char * const rt5663_if1_adc_data_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5663_if1_adc_enum, RT5663_TDM_2, + RT5663_DATA_SWAP_ADCDAT1_SHIFT, rt5663_if1_adc_data_select); + +static void rt5663_enable_push_button_irq(struct snd_soc_component *component, + bool enable) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + if (enable) { + snd_soc_component_update_bits(component, RT5663_IL_CMD_6, + RT5663_EN_4BTN_INL_MASK, RT5663_EN_4BTN_INL_EN); + /* reset in-line command */ + snd_soc_component_update_bits(component, RT5663_IL_CMD_6, + RT5663_RESET_4BTN_INL_MASK, + RT5663_RESET_4BTN_INL_RESET); + snd_soc_component_update_bits(component, RT5663_IL_CMD_6, + RT5663_RESET_4BTN_INL_MASK, + RT5663_RESET_4BTN_INL_NOR); + switch (rt5663->codec_ver) { + case CODEC_VER_1: + snd_soc_component_update_bits(component, RT5663_IRQ_3, + RT5663_V2_EN_IRQ_INLINE_MASK, + RT5663_V2_EN_IRQ_INLINE_NOR); + break; + case CODEC_VER_0: + snd_soc_component_update_bits(component, RT5663_IRQ_2, + RT5663_EN_IRQ_INLINE_MASK, + RT5663_EN_IRQ_INLINE_NOR); + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + } + } else { + switch (rt5663->codec_ver) { + case CODEC_VER_1: + snd_soc_component_update_bits(component, RT5663_IRQ_3, + RT5663_V2_EN_IRQ_INLINE_MASK, + RT5663_V2_EN_IRQ_INLINE_BYP); + break; + case CODEC_VER_0: + snd_soc_component_update_bits(component, RT5663_IRQ_2, + RT5663_EN_IRQ_INLINE_MASK, + RT5663_EN_IRQ_INLINE_BYP); + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + } + snd_soc_component_update_bits(component, RT5663_IL_CMD_6, + RT5663_EN_4BTN_INL_MASK, RT5663_EN_4BTN_INL_DIS); + /* reset in-line command */ + snd_soc_component_update_bits(component, RT5663_IL_CMD_6, + RT5663_RESET_4BTN_INL_MASK, + RT5663_RESET_4BTN_INL_RESET); + snd_soc_component_update_bits(component, RT5663_IL_CMD_6, + RT5663_RESET_4BTN_INL_MASK, + RT5663_RESET_4BTN_INL_NOR); + } +} + +/** + * rt5663_v2_jack_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ + +static int rt5663_v2_jack_detect(struct snd_soc_component *component, int jack_insert) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + int val, i = 0, sleep_time[5] = {300, 150, 100, 50, 30}; + + dev_dbg(component->dev, "%s jack_insert:%d\n", __func__, jack_insert); + if (jack_insert) { + snd_soc_component_write(component, RT5663_CBJ_TYPE_2, 0x8040); + snd_soc_component_write(component, RT5663_CBJ_TYPE_3, 0x1484); + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS2"); + snd_soc_dapm_force_enable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_force_enable_pin(dapm, "CBJ Power"); + snd_soc_dapm_sync(dapm); + snd_soc_component_update_bits(component, RT5663_RC_CLK, + RT5663_DIG_1M_CLK_MASK, RT5663_DIG_1M_CLK_EN); + snd_soc_component_update_bits(component, RT5663_RECMIX, 0x8, 0x8); + + while (i < 5) { + msleep(sleep_time[i]); + val = snd_soc_component_read32(component, RT5663_CBJ_TYPE_2) & 0x0003; + if (val == 0x1 || val == 0x2 || val == 0x3) + break; + dev_dbg(component->dev, "%s: MX-0011 val=%x sleep %d\n", + __func__, val, sleep_time[i]); + i++; + } + dev_dbg(component->dev, "%s val = %d\n", __func__, val); + switch (val) { + case 1: + case 2: + rt5663->jack_type = SND_JACK_HEADSET; + rt5663_enable_push_button_irq(component, true); + break; + default: + snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS2"); + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_disable_pin(dapm, "CBJ Power"); + snd_soc_dapm_sync(dapm); + rt5663->jack_type = SND_JACK_HEADPHONE; + break; + } + } else { + snd_soc_component_update_bits(component, RT5663_RECMIX, 0x8, 0x0); + + if (rt5663->jack_type == SND_JACK_HEADSET) { + rt5663_enable_push_button_irq(component, false); + snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_disable_pin(dapm, "MICBIAS2"); + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_disable_pin(dapm, "CBJ Power"); + snd_soc_dapm_sync(dapm); + } + rt5663->jack_type = 0; + } + + dev_dbg(component->dev, "jack_type = %d\n", rt5663->jack_type); + return rt5663->jack_type; +} + +/** + * rt5663_jack_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ +static int rt5663_jack_detect(struct snd_soc_component *component, int jack_insert) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + int val, i = 0; + + dev_dbg(component->dev, "%s jack_insert:%d\n", __func__, jack_insert); + + if (jack_insert) { + snd_soc_component_update_bits(component, RT5663_DIG_MISC, + RT5663_DIG_GATE_CTRL_MASK, RT5663_DIG_GATE_CTRL_EN); + snd_soc_component_update_bits(component, RT5663_HP_CHARGE_PUMP_1, + RT5663_SI_HP_MASK | RT5663_OSW_HP_L_MASK | + RT5663_OSW_HP_R_MASK, RT5663_SI_HP_EN | + RT5663_OSW_HP_L_DIS | RT5663_OSW_HP_R_DIS); + snd_soc_component_update_bits(component, RT5663_DUMMY_1, + RT5663_EMB_CLK_MASK | RT5663_HPA_CPL_BIAS_MASK | + RT5663_HPA_CPR_BIAS_MASK, RT5663_EMB_CLK_EN | + RT5663_HPA_CPL_BIAS_1 | RT5663_HPA_CPR_BIAS_1); + snd_soc_component_update_bits(component, RT5663_CBJ_1, + RT5663_INBUF_CBJ_BST1_MASK | RT5663_CBJ_SENSE_BST1_MASK, + RT5663_INBUF_CBJ_BST1_ON | RT5663_CBJ_SENSE_BST1_L); + snd_soc_component_update_bits(component, RT5663_IL_CMD_2, + RT5663_PWR_MIC_DET_MASK, RT5663_PWR_MIC_DET_ON); + /* BST1 power on for JD */ + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_2, + RT5663_PWR_BST1_MASK, RT5663_PWR_BST1_ON); + snd_soc_component_update_bits(component, RT5663_EM_JACK_TYPE_1, + RT5663_CBJ_DET_MASK | RT5663_EXT_JD_MASK | + RT5663_POL_EXT_JD_MASK, RT5663_CBJ_DET_EN | + RT5663_EXT_JD_EN | RT5663_POL_EXT_JD_EN); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_MB_MASK | RT5663_LDO1_DVO_MASK | + RT5663_AMP_HP_MASK, RT5663_PWR_MB | + RT5663_LDO1_DVO_0_9V | RT5663_AMP_HP_3X); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_VREF1_MASK | RT5663_PWR_VREF2_MASK | + RT5663_PWR_FV1_MASK | RT5663_PWR_FV2_MASK, + RT5663_PWR_VREF1 | RT5663_PWR_VREF2); + msleep(20); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_FV1_MASK | RT5663_PWR_FV2_MASK, + RT5663_PWR_FV1 | RT5663_PWR_FV2); + snd_soc_component_update_bits(component, RT5663_AUTO_1MRC_CLK, + RT5663_IRQ_POW_SAV_MASK, RT5663_IRQ_POW_SAV_EN); + snd_soc_component_update_bits(component, RT5663_IRQ_1, + RT5663_EN_IRQ_JD1_MASK, RT5663_EN_IRQ_JD1_EN); + snd_soc_component_update_bits(component, RT5663_EM_JACK_TYPE_1, + RT5663_EM_JD_MASK, RT5663_EM_JD_RST); + snd_soc_component_update_bits(component, RT5663_EM_JACK_TYPE_1, + RT5663_EM_JD_MASK, RT5663_EM_JD_NOR); + + while (true) { + regmap_read(rt5663->regmap, RT5663_INT_ST_2, &val); + if (!(val & 0x80)) + usleep_range(10000, 10005); + else + break; + + if (i > 200) + break; + i++; + } + + val = snd_soc_component_read32(component, RT5663_EM_JACK_TYPE_2) & 0x0003; + dev_dbg(component->dev, "%s val = %d\n", __func__, val); + + snd_soc_component_update_bits(component, RT5663_HP_CHARGE_PUMP_1, + RT5663_OSW_HP_L_MASK | RT5663_OSW_HP_R_MASK, + RT5663_OSW_HP_L_EN | RT5663_OSW_HP_R_EN); + + switch (val) { + case 1: + case 2: + rt5663->jack_type = SND_JACK_HEADSET; + rt5663_enable_push_button_irq(component, true); + + if (rt5663->pdata.impedance_sensing_num) + break; + + if (rt5663->pdata.dc_offset_l_manual_mic) { + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_2, + rt5663->pdata.dc_offset_l_manual_mic >> + 16); + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_3, + rt5663->pdata.dc_offset_l_manual_mic & + 0xffff); + } + + if (rt5663->pdata.dc_offset_r_manual_mic) { + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_5, + rt5663->pdata.dc_offset_r_manual_mic >> + 16); + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_6, + rt5663->pdata.dc_offset_r_manual_mic & + 0xffff); + } + break; + default: + rt5663->jack_type = SND_JACK_HEADPHONE; + snd_soc_component_update_bits(component, + RT5663_PWR_ANLG_1, + RT5663_PWR_MB_MASK | RT5663_PWR_VREF1_MASK | + RT5663_PWR_VREF2_MASK, 0); + if (rt5663->pdata.impedance_sensing_num) + break; + + if (rt5663->pdata.dc_offset_l_manual) { + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_2, + rt5663->pdata.dc_offset_l_manual >> 16); + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_3, + rt5663->pdata.dc_offset_l_manual & + 0xffff); + } + + if (rt5663->pdata.dc_offset_r_manual) { + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_5, + rt5663->pdata.dc_offset_r_manual >> 16); + regmap_write(rt5663->regmap, RT5663_MIC_DECRO_6, + rt5663->pdata.dc_offset_r_manual & + 0xffff); + } + break; + } + } else { + if (rt5663->jack_type == SND_JACK_HEADSET) + rt5663_enable_push_button_irq(component, false); + rt5663->jack_type = 0; + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_MB_MASK | RT5663_PWR_VREF1_MASK | + RT5663_PWR_VREF2_MASK, 0); + } + + dev_dbg(component->dev, "jack_type = %d\n", rt5663->jack_type); + return rt5663->jack_type; +} + +static int rt5663_impedance_sensing(struct snd_soc_component *component) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + unsigned int value, i, reg84, reg26, reg2fa, reg91, reg10, reg80; + + for (i = 0; i < rt5663->pdata.impedance_sensing_num; i++) { + if (rt5663->imp_table[i].vol == 7) + break; + } + + if (rt5663->jack_type == SND_JACK_HEADSET) { + snd_soc_component_write(component, RT5663_MIC_DECRO_2, + rt5663->imp_table[i].dc_offset_l_manual_mic >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_3, + rt5663->imp_table[i].dc_offset_l_manual_mic & 0xffff); + snd_soc_component_write(component, RT5663_MIC_DECRO_5, + rt5663->imp_table[i].dc_offset_r_manual_mic >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_6, + rt5663->imp_table[i].dc_offset_r_manual_mic & 0xffff); + } else { + snd_soc_component_write(component, RT5663_MIC_DECRO_2, + rt5663->imp_table[i].dc_offset_l_manual >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_3, + rt5663->imp_table[i].dc_offset_l_manual & 0xffff); + snd_soc_component_write(component, RT5663_MIC_DECRO_5, + rt5663->imp_table[i].dc_offset_r_manual >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_6, + rt5663->imp_table[i].dc_offset_r_manual & 0xffff); + } + + reg84 = snd_soc_component_read32(component, RT5663_ASRC_2); + reg26 = snd_soc_component_read32(component, RT5663_STO1_ADC_MIXER); + reg2fa = snd_soc_component_read32(component, RT5663_DUMMY_1); + reg91 = snd_soc_component_read32(component, RT5663_HP_CHARGE_PUMP_1); + reg10 = snd_soc_component_read32(component, RT5663_RECMIX); + reg80 = snd_soc_component_read32(component, RT5663_GLB_CLK); + + snd_soc_component_update_bits(component, RT5663_STO_DRE_1, 0x8000, 0); + snd_soc_component_write(component, RT5663_ASRC_2, 0); + snd_soc_component_write(component, RT5663_STO1_ADC_MIXER, 0x4040); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_VREF1_MASK | RT5663_PWR_VREF2_MASK | + RT5663_PWR_FV1_MASK | RT5663_PWR_FV2_MASK, + RT5663_PWR_VREF1 | RT5663_PWR_VREF2); + usleep_range(10000, 10005); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_FV1_MASK | RT5663_PWR_FV2_MASK, + RT5663_PWR_FV1 | RT5663_PWR_FV2); + snd_soc_component_update_bits(component, RT5663_GLB_CLK, RT5663_SCLK_SRC_MASK, + RT5663_SCLK_SRC_RCCLK); + snd_soc_component_update_bits(component, RT5663_RC_CLK, RT5663_DIG_25M_CLK_MASK, + RT5663_DIG_25M_CLK_EN); + snd_soc_component_update_bits(component, RT5663_ADDA_CLK_1, RT5663_I2S_PD1_MASK, 0); + snd_soc_component_write(component, RT5663_PRE_DIV_GATING_1, 0xff00); + snd_soc_component_write(component, RT5663_PRE_DIV_GATING_2, 0xfffc); + snd_soc_component_write(component, RT5663_HP_CHARGE_PUMP_1, 0x1232); + snd_soc_component_write(component, RT5663_HP_LOGIC_2, 0x0005); + snd_soc_component_write(component, RT5663_DEPOP_2, 0x3003); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0030, 0x0030); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0003, 0x0003); + snd_soc_component_update_bits(component, RT5663_PWR_DIG_2, + RT5663_PWR_ADC_S1F | RT5663_PWR_DAC_S1F, + RT5663_PWR_ADC_S1F | RT5663_PWR_DAC_S1F); + snd_soc_component_update_bits(component, RT5663_PWR_DIG_1, + RT5663_PWR_DAC_L1 | RT5663_PWR_DAC_R1 | + RT5663_PWR_LDO_DACREF_MASK | RT5663_PWR_ADC_L1 | + RT5663_PWR_ADC_R1, + RT5663_PWR_DAC_L1 | RT5663_PWR_DAC_R1 | + RT5663_PWR_LDO_DACREF_ON | RT5663_PWR_ADC_L1 | + RT5663_PWR_ADC_R1); + msleep(40); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_2, + RT5663_PWR_RECMIX1 | RT5663_PWR_RECMIX2, + RT5663_PWR_RECMIX1 | RT5663_PWR_RECMIX2); + msleep(30); + snd_soc_component_write(component, RT5663_HP_CHARGE_PUMP_2, 0x1371); + snd_soc_component_write(component, RT5663_STO_DAC_MIXER, 0); + snd_soc_component_write(component, RT5663_BYPASS_STO_DAC, 0x000c); + snd_soc_component_write(component, RT5663_HP_BIAS, 0xafaa); + snd_soc_component_write(component, RT5663_CHARGE_PUMP_1, 0x2224); + snd_soc_component_write(component, RT5663_HP_OUT_EN, 0x8088); + snd_soc_component_write(component, RT5663_CHOP_ADC, 0x3000); + snd_soc_component_write(component, RT5663_ADDA_RST, 0xc000); + snd_soc_component_write(component, RT5663_STO1_HPF_ADJ1, 0x3320); + snd_soc_component_write(component, RT5663_HP_CALIB_2, 0x00c9); + snd_soc_component_write(component, RT5663_DUMMY_1, 0x004c); + snd_soc_component_write(component, RT5663_ANA_BIAS_CUR_1, 0x7733); + snd_soc_component_write(component, RT5663_CHARGE_PUMP_2, 0x7777); + snd_soc_component_write(component, RT5663_STO_DRE_9, 0x0007); + snd_soc_component_write(component, RT5663_STO_DRE_10, 0x0007); + snd_soc_component_write(component, RT5663_DUMMY_2, 0x02a4); + snd_soc_component_write(component, RT5663_RECMIX, 0x0005); + snd_soc_component_write(component, RT5663_HP_IMP_SEN_1, 0x4334); + snd_soc_component_update_bits(component, RT5663_IRQ_3, 0x0004, 0x0004); + snd_soc_component_write(component, RT5663_HP_LOGIC_1, 0x2200); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x3000, 0x3000); + snd_soc_component_write(component, RT5663_HP_LOGIC_1, 0x6200); + + for (i = 0; i < 100; i++) { + msleep(20); + if (snd_soc_component_read32(component, RT5663_INT_ST_1) & 0x2) + break; + } + + value = snd_soc_component_read32(component, RT5663_HP_IMP_SEN_4); + + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x3000, 0); + snd_soc_component_write(component, RT5663_INT_ST_1, 0); + snd_soc_component_write(component, RT5663_HP_LOGIC_1, 0); + snd_soc_component_update_bits(component, RT5663_RC_CLK, RT5663_DIG_25M_CLK_MASK, + RT5663_DIG_25M_CLK_DIS); + snd_soc_component_write(component, RT5663_GLB_CLK, reg80); + snd_soc_component_write(component, RT5663_RECMIX, reg10); + snd_soc_component_write(component, RT5663_DUMMY_2, 0x00a4); + snd_soc_component_write(component, RT5663_DUMMY_1, reg2fa); + snd_soc_component_write(component, RT5663_HP_CALIB_2, 0x00c8); + snd_soc_component_write(component, RT5663_STO1_HPF_ADJ1, 0xb320); + snd_soc_component_write(component, RT5663_ADDA_RST, 0xe400); + snd_soc_component_write(component, RT5663_CHOP_ADC, 0x2000); + snd_soc_component_write(component, RT5663_HP_OUT_EN, 0x0008); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_2, + RT5663_PWR_RECMIX1 | RT5663_PWR_RECMIX2, 0); + snd_soc_component_update_bits(component, RT5663_PWR_DIG_1, + RT5663_PWR_DAC_L1 | RT5663_PWR_DAC_R1 | + RT5663_PWR_LDO_DACREF_MASK | RT5663_PWR_ADC_L1 | + RT5663_PWR_ADC_R1, 0); + snd_soc_component_update_bits(component, RT5663_PWR_DIG_2, + RT5663_PWR_ADC_S1F | RT5663_PWR_DAC_S1F, 0); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0003, 0); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0030, 0); + snd_soc_component_write(component, RT5663_HP_LOGIC_2, 0); + snd_soc_component_write(component, RT5663_HP_CHARGE_PUMP_1, reg91); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_VREF1_MASK | RT5663_PWR_VREF2_MASK, 0); + snd_soc_component_write(component, RT5663_STO1_ADC_MIXER, reg26); + snd_soc_component_write(component, RT5663_ASRC_2, reg84); + + for (i = 0; i < rt5663->pdata.impedance_sensing_num; i++) { + if (value >= rt5663->imp_table[i].imp_min && + value <= rt5663->imp_table[i].imp_max) + break; + } + + snd_soc_component_update_bits(component, RT5663_STO_DRE_9, RT5663_DRE_GAIN_HP_MASK, + rt5663->imp_table[i].vol); + snd_soc_component_update_bits(component, RT5663_STO_DRE_10, RT5663_DRE_GAIN_HP_MASK, + rt5663->imp_table[i].vol); + + if (rt5663->jack_type == SND_JACK_HEADSET) { + snd_soc_component_write(component, RT5663_MIC_DECRO_2, + rt5663->imp_table[i].dc_offset_l_manual_mic >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_3, + rt5663->imp_table[i].dc_offset_l_manual_mic & 0xffff); + snd_soc_component_write(component, RT5663_MIC_DECRO_5, + rt5663->imp_table[i].dc_offset_r_manual_mic >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_6, + rt5663->imp_table[i].dc_offset_r_manual_mic & 0xffff); + } else { + snd_soc_component_write(component, RT5663_MIC_DECRO_2, + rt5663->imp_table[i].dc_offset_l_manual >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_3, + rt5663->imp_table[i].dc_offset_l_manual & 0xffff); + snd_soc_component_write(component, RT5663_MIC_DECRO_5, + rt5663->imp_table[i].dc_offset_r_manual >> 16); + snd_soc_component_write(component, RT5663_MIC_DECRO_6, + rt5663->imp_table[i].dc_offset_r_manual & 0xffff); + } + + return 0; +} + +static int rt5663_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read32(component, RT5663_IL_CMD_5); + dev_dbg(component->dev, "%s: val=0x%x\n", __func__, val); + btn_type = val & 0xfff0; + snd_soc_component_write(component, RT5663_IL_CMD_5, val); + + return btn_type; +} + +static irqreturn_t rt5663_irq(int irq, void *data) +{ + struct rt5663_priv *rt5663 = data; + + dev_dbg(regmap_get_device(rt5663->regmap), "%s IRQ queue work\n", + __func__); + + queue_delayed_work(system_wq, &rt5663->jack_detect_work, + msecs_to_jiffies(250)); + + return IRQ_HANDLED; +} + +static int rt5663_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + rt5663->hs_jack = hs_jack; + + rt5663_irq(0, rt5663); + + return 0; +} + +static bool rt5663_check_jd_status(struct snd_soc_component *component) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + int val = snd_soc_component_read32(component, RT5663_INT_ST_1); + + dev_dbg(component->dev, "%s val=%x\n", __func__, val); + + /* JD1 */ + switch (rt5663->codec_ver) { + case CODEC_VER_1: + return !(val & 0x2000); + case CODEC_VER_0: + return !(val & 0x1000); + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + } + + return false; +} + +static void rt5663_jack_detect_work(struct work_struct *work) +{ + struct rt5663_priv *rt5663 = + container_of(work, struct rt5663_priv, jack_detect_work.work); + struct snd_soc_component *component = rt5663->component; + int btn_type, report = 0; + + if (!component) + return; + + if (rt5663_check_jd_status(component)) { + /* jack in */ + if (rt5663->jack_type == 0) { + /* jack was out, report jack type */ + switch (rt5663->codec_ver) { + case CODEC_VER_1: + report = rt5663_v2_jack_detect( + rt5663->component, 1); + break; + case CODEC_VER_0: + report = rt5663_jack_detect(rt5663->component, 1); + if (rt5663->pdata.impedance_sensing_num) + rt5663_impedance_sensing(rt5663->component); + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + } + + /* Delay the jack insert report to avoid pop noise */ + msleep(30); + } else { + /* jack is already in, report button event */ + report = SND_JACK_HEADSET; + btn_type = rt5663_button_detect(rt5663->component); + /** + * rt5663 can report three kinds of button behavior, + * one click, double click and hold. However, + * currently we will report button pressed/released + * event. So all the three button behaviors are + * treated as button pressed. + */ + switch (btn_type) { + case 0x8000: + case 0x4000: + case 0x2000: + report |= SND_JACK_BTN_0; + break; + case 0x1000: + case 0x0800: + case 0x0400: + report |= SND_JACK_BTN_1; + break; + case 0x0200: + case 0x0100: + case 0x0080: + report |= SND_JACK_BTN_2; + break; + case 0x0040: + case 0x0020: + case 0x0010: + report |= SND_JACK_BTN_3; + break; + case 0x0000: /* unpressed */ + break; + default: + btn_type = 0; + dev_err(rt5663->component->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + /* button release or spurious interrput*/ + if (btn_type == 0) { + report = rt5663->jack_type; + cancel_delayed_work_sync( + &rt5663->jd_unplug_work); + } else { + queue_delayed_work(system_wq, + &rt5663->jd_unplug_work, + msecs_to_jiffies(500)); + } + } + } else { + /* jack out */ + switch (rt5663->codec_ver) { + case CODEC_VER_1: + report = rt5663_v2_jack_detect(rt5663->component, 0); + break; + case CODEC_VER_0: + report = rt5663_jack_detect(rt5663->component, 0); + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + } + } + dev_dbg(component->dev, "%s jack report: 0x%04x\n", __func__, report); + snd_soc_jack_report(rt5663->hs_jack, report, SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); +} + +static void rt5663_jd_unplug_work(struct work_struct *work) +{ + struct rt5663_priv *rt5663 = + container_of(work, struct rt5663_priv, jd_unplug_work.work); + struct snd_soc_component *component = rt5663->component; + + if (!component) + return; + + if (!rt5663_check_jd_status(component)) { + /* jack out */ + switch (rt5663->codec_ver) { + case CODEC_VER_1: + rt5663_v2_jack_detect(rt5663->component, 0); + break; + case CODEC_VER_0: + rt5663_jack_detect(rt5663->component, 0); + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + } + + snd_soc_jack_report(rt5663->hs_jack, 0, SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + } else { + queue_delayed_work(system_wq, &rt5663->jd_unplug_work, + msecs_to_jiffies(500)); + } +} + +static const struct snd_kcontrol_new rt5663_snd_controls[] = { + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC Playback Volume", RT5663_STO1_DAC_DIG_VOL, + RT5663_DAC_L1_VOL_SHIFT + 1, RT5663_DAC_R1_VOL_SHIFT + 1, + 87, 0, dac_vol_tlv), + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC Capture Switch", RT5663_STO1_ADC_DIG_VOL, + RT5663_ADC_L_MUTE_SHIFT, RT5663_ADC_R_MUTE_SHIFT, 1, 1), + SOC_DOUBLE_TLV("ADC Capture Volume", RT5663_STO1_ADC_DIG_VOL, + RT5663_ADC_L_VOL_SHIFT + 1, RT5663_ADC_R_VOL_SHIFT + 1, + 63, 0, adc_vol_tlv), +}; + +static const struct snd_kcontrol_new rt5663_v2_specific_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE_R_TLV("Headphone Playback Volume", RT5663_HP_LCH_DRE, + RT5663_HP_RCH_DRE, RT5663_GAIN_HP_SHIFT, 15, 1, + rt5663_v2_hp_vol_tlv), + /* Mic Boost Volume */ + SOC_SINGLE_TLV("IN1 Capture Volume", RT5663_AEC_BST, + RT5663_GAIN_CBJ_SHIFT, 8, 0, in_bst_tlv), +}; + +static const struct snd_kcontrol_new rt5663_specific_controls[] = { + /* Mic Boost Volume*/ + SOC_SINGLE_TLV("IN1 Capture Volume", RT5663_CBJ_2, + RT5663_GAIN_BST1_SHIFT, 8, 0, in_bst_tlv), + /* Data Swap for Slot0/1 in ADCDAT1 */ + SOC_ENUM("IF1 ADC Data Swap", rt5663_if1_adc_enum), +}; + +static const struct snd_kcontrol_new rt5663_hpvol_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE_R_TLV("Headphone Playback Volume", RT5663_STO_DRE_9, + RT5663_STO_DRE_10, RT5663_DRE_GAIN_HP_SHIFT, 23, 1, + rt5663_hp_vol_tlv), +}; + +static int rt5663_is_sys_clk_from_pll(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int val; + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + val = snd_soc_component_read32(component, RT5663_GLB_CLK); + val &= RT5663_SCLK_SRC_MASK; + if (val == RT5663_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +static int rt5663_is_using_asrc(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int reg, shift, val; + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + if (rt5663->codec_ver == CODEC_VER_1) { + switch (w->shift) { + case RT5663_ADC_STO1_ASRC_SHIFT: + reg = RT5663_ASRC_3; + shift = RT5663_V2_AD_STO1_TRACK_SHIFT; + break; + case RT5663_DAC_STO1_ASRC_SHIFT: + reg = RT5663_ASRC_2; + shift = RT5663_DA_STO1_TRACK_SHIFT; + break; + default: + return 0; + } + } else { + switch (w->shift) { + case RT5663_ADC_STO1_ASRC_SHIFT: + reg = RT5663_ASRC_2; + shift = RT5663_AD_STO1_TRACK_SHIFT; + break; + case RT5663_DAC_STO1_ASRC_SHIFT: + reg = RT5663_ASRC_2; + shift = RT5663_DA_STO1_TRACK_SHIFT; + break; + default: + return 0; + } + } + + val = (snd_soc_component_read32(component, reg) >> shift) & 0x7; + + if (val) + return 1; + + return 0; +} + +static int rt5663_i2s_use_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + int da_asrc_en, ad_asrc_en; + + da_asrc_en = (snd_soc_component_read32(component, RT5663_ASRC_2) & + RT5663_DA_STO1_TRACK_MASK) ? 1 : 0; + switch (rt5663->codec_ver) { + case CODEC_VER_1: + ad_asrc_en = (snd_soc_component_read32(component, RT5663_ASRC_3) & + RT5663_V2_AD_STO1_TRACK_MASK) ? 1 : 0; + break; + case CODEC_VER_0: + ad_asrc_en = (snd_soc_component_read32(component, RT5663_ASRC_2) & + RT5663_AD_STO1_TRACK_MASK) ? 1 : 0; + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + return 1; + } + + if (da_asrc_en || ad_asrc_en) + if (rt5663->sysclk > rt5663->lrck * 384) + return 1; + + dev_err(component->dev, "sysclk < 384 x fs, disable i2s asrc\n"); + + return 0; +} + +/** + * rt5663_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5663 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the codec driver will turn on ASRC + * for these filters if ASRC is selected as their clock source. + */ +int rt5663_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + unsigned int asrc2_mask = 0; + unsigned int asrc2_value = 0; + unsigned int asrc3_mask = 0; + unsigned int asrc3_value = 0; + + switch (clk_src) { + case RT5663_CLK_SEL_SYS: + case RT5663_CLK_SEL_I2S1_ASRC: + break; + + default: + return -EINVAL; + } + + if (filter_mask & RT5663_DA_STEREO_FILTER) { + asrc2_mask |= RT5663_DA_STO1_TRACK_MASK; + asrc2_value |= clk_src << RT5663_DA_STO1_TRACK_SHIFT; + } + + if (filter_mask & RT5663_AD_STEREO_FILTER) { + switch (rt5663->codec_ver) { + case CODEC_VER_1: + asrc3_mask |= RT5663_V2_AD_STO1_TRACK_MASK; + asrc3_value |= clk_src << RT5663_V2_AD_STO1_TRACK_SHIFT; + break; + case CODEC_VER_0: + asrc2_mask |= RT5663_AD_STO1_TRACK_MASK; + asrc2_value |= clk_src << RT5663_AD_STO1_TRACK_SHIFT; + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + } + } + + if (asrc2_mask) + snd_soc_component_update_bits(component, RT5663_ASRC_2, asrc2_mask, + asrc2_value); + + if (asrc3_mask) + snd_soc_component_update_bits(component, RT5663_ASRC_3, asrc3_mask, + asrc3_value); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5663_sel_asrc_clk_src); + +/* Analog Mixer */ +static const struct snd_kcontrol_new rt5663_recmix1l[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5663_RECMIX1L, + RT5663_RECMIX1L_BST2_SHIFT, 1, 1), + SOC_DAPM_SINGLE("BST1 CBJ Switch", RT5663_RECMIX1L, + RT5663_RECMIX1L_BST1_CBJ_SHIFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5663_recmix1r[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5663_RECMIX1R, + RT5663_RECMIX1R_BST2_SHIFT, 1, 1), +}; + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5663_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5663_STO1_ADC_MIXER, + RT5663_M_STO1_ADC_L1_SHIFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5663_STO1_ADC_MIXER, + RT5663_M_STO1_ADC_L2_SHIFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5663_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5663_STO1_ADC_MIXER, + RT5663_M_STO1_ADC_R1_SHIFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5663_STO1_ADC_MIXER, + RT5663_M_STO1_ADC_R2_SHIFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5663_adda_l_mix[] = { + SOC_DAPM_SINGLE("ADC L Switch", RT5663_AD_DA_MIXER, + RT5663_M_ADCMIX_L_SHIFT, 1, 1), + SOC_DAPM_SINGLE("DAC L Switch", RT5663_AD_DA_MIXER, + RT5663_M_DAC1_L_SHIFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5663_adda_r_mix[] = { + SOC_DAPM_SINGLE("ADC R Switch", RT5663_AD_DA_MIXER, + RT5663_M_ADCMIX_R_SHIFT, 1, 1), + SOC_DAPM_SINGLE("DAC R Switch", RT5663_AD_DA_MIXER, + RT5663_M_DAC1_R_SHIFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5663_sto1_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L Switch", RT5663_STO_DAC_MIXER, + RT5663_M_DAC_L1_STO_L_SHIFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5663_sto1_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R Switch", RT5663_STO_DAC_MIXER, + RT5663_M_DAC_R1_STO_R_SHIFT, 1, 1), +}; + +/* Out Switch */ +static const struct snd_kcontrol_new rt5663_hpo_switch = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5663_HP_AMP_2, + RT5663_EN_DAC_HPO_SHIFT, 1, 0); + +/* Stereo ADC source */ +static const char * const rt5663_sto1_adc_src[] = { + "ADC L", "ADC R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5663_sto1_adcl_enum, RT5663_STO1_ADC_MIXER, + RT5663_STO1_ADC_L_SRC_SHIFT, rt5663_sto1_adc_src); + +static const struct snd_kcontrol_new rt5663_sto1_adcl_mux = + SOC_DAPM_ENUM("STO1 ADC L Mux", rt5663_sto1_adcl_enum); + +static SOC_ENUM_SINGLE_DECL(rt5663_sto1_adcr_enum, RT5663_STO1_ADC_MIXER, + RT5663_STO1_ADC_R_SRC_SHIFT, rt5663_sto1_adc_src); + +static const struct snd_kcontrol_new rt5663_sto1_adcr_mux = + SOC_DAPM_ENUM("STO1 ADC R Mux", rt5663_sto1_adcr_enum); + +/* RT5663: Analog DACL1 input source */ +static const char * const rt5663_alg_dacl_src[] = { + "DAC L", "STO DAC MIXL" +}; + +static SOC_ENUM_SINGLE_DECL(rt5663_alg_dacl_enum, RT5663_BYPASS_STO_DAC, + RT5663_DACL1_SRC_SHIFT, rt5663_alg_dacl_src); + +static const struct snd_kcontrol_new rt5663_alg_dacl_mux = + SOC_DAPM_ENUM("DAC L Mux", rt5663_alg_dacl_enum); + +/* RT5663: Analog DACR1 input source */ +static const char * const rt5663_alg_dacr_src[] = { + "DAC R", "STO DAC MIXR" +}; + +static SOC_ENUM_SINGLE_DECL(rt5663_alg_dacr_enum, RT5663_BYPASS_STO_DAC, + RT5663_DACR1_SRC_SHIFT, rt5663_alg_dacr_src); + +static const struct snd_kcontrol_new rt5663_alg_dacr_mux = + SOC_DAPM_ENUM("DAC R Mux", rt5663_alg_dacr_enum); + +static int rt5663_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (rt5663->codec_ver == CODEC_VER_1) { + snd_soc_component_update_bits(component, RT5663_HP_CHARGE_PUMP_1, + RT5663_SEL_PM_HP_SHIFT, RT5663_SEL_PM_HP_HIGH); + snd_soc_component_update_bits(component, RT5663_HP_LOGIC_2, + RT5663_HP_SIG_SRC1_MASK, + RT5663_HP_SIG_SRC1_SILENCE); + } else { + snd_soc_component_update_bits(component, + RT5663_DACREF_LDO, 0x3e0e, 0x3a0a); + snd_soc_component_write(component, RT5663_DEPOP_2, 0x3003); + snd_soc_component_update_bits(component, RT5663_HP_CHARGE_PUMP_1, + RT5663_OVCD_HP_MASK, RT5663_OVCD_HP_DIS); + snd_soc_component_write(component, RT5663_HP_CHARGE_PUMP_2, 0x1371); + snd_soc_component_write(component, RT5663_HP_BIAS, 0xabba); + snd_soc_component_write(component, RT5663_CHARGE_PUMP_1, 0x2224); + snd_soc_component_write(component, RT5663_ANA_BIAS_CUR_1, 0x7766); + snd_soc_component_write(component, RT5663_HP_BIAS, 0xafaa); + snd_soc_component_write(component, RT5663_CHARGE_PUMP_2, 0x7777); + snd_soc_component_update_bits(component, RT5663_STO_DRE_1, 0x8000, + 0x8000); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x3000, + 0x3000); + } + break; + + case SND_SOC_DAPM_PRE_PMD: + if (rt5663->codec_ver == CODEC_VER_1) { + snd_soc_component_update_bits(component, RT5663_HP_LOGIC_2, + RT5663_HP_SIG_SRC1_MASK, + RT5663_HP_SIG_SRC1_REG); + } else { + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x3000, 0x0); + snd_soc_component_update_bits(component, RT5663_HP_CHARGE_PUMP_1, + RT5663_OVCD_HP_MASK, RT5663_OVCD_HP_EN); + snd_soc_component_update_bits(component, + RT5663_DACREF_LDO, 0x3e0e, 0); + } + break; + + default: + return 0; + } + + return 0; +} + +static int rt5663_charge_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (rt5663->codec_ver == CODEC_VER_0) { + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0030, + 0x0030); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0003, + 0x0003); + } + break; + + case SND_SOC_DAPM_POST_PMD: + if (rt5663->codec_ver == CODEC_VER_0) { + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0003, 0); + snd_soc_component_update_bits(component, RT5663_DEPOP_1, 0x0030, 0); + } + break; + + default: + return 0; + } + + return 0; +} + +static int rt5663_bst2_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_2, + RT5663_PWR_BST2_MASK | RT5663_PWR_BST2_OP_MASK, + RT5663_PWR_BST2 | RT5663_PWR_BST2_OP); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_2, + RT5663_PWR_BST2_MASK | RT5663_PWR_BST2_OP_MASK, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5663_pre_div_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_write(component, RT5663_PRE_DIV_GATING_1, 0xff00); + snd_soc_component_write(component, RT5663_PRE_DIV_GATING_2, 0xfffc); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_write(component, RT5663_PRE_DIV_GATING_1, 0x0000); + snd_soc_component_write(component, RT5663_PRE_DIV_GATING_2, 0x0000); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5663_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PLL", RT5663_PWR_ANLG_3, RT5663_PWR_PLL_SHIFT, 0, + NULL, 0), + + /* micbias */ + SND_SOC_DAPM_MICBIAS("MICBIAS1", RT5663_PWR_ANLG_2, + RT5663_PWR_MB1_SHIFT, 0), + SND_SOC_DAPM_MICBIAS("MICBIAS2", RT5663_PWR_ANLG_2, + RT5663_PWR_MB2_SHIFT, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + + /* REC Mixer Power */ + SND_SOC_DAPM_SUPPLY("RECMIX1L Power", RT5663_PWR_ANLG_2, + RT5663_PWR_RECMIX1_SHIFT, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC L Power", RT5663_PWR_DIG_1, + RT5663_PWR_ADC_L1_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Clock", RT5663_CHOP_ADC, + RT5663_CKGEN_ADCC_SHIFT, 0, NULL, 0), + + /* ADC Mixer */ + SND_SOC_DAPM_MIXER("STO1 ADC MIXL", SND_SOC_NOPM, + 0, 0, rt5663_sto1_adc_l_mix, + ARRAY_SIZE(rt5663_sto1_adc_l_mix)), + + /* ADC Filter Power */ + SND_SOC_DAPM_SUPPLY("STO1 ADC Filter", RT5663_PWR_DIG_2, + RT5663_PWR_ADC_S1F_SHIFT, 0, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S", RT5663_PWR_DIG_1, RT5663_PWR_I2S1_SHIFT, 0, + NULL, 0), + SND_SOC_DAPM_PGA("IF DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIFRX", "AIF Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIFTX", "AIF Capture", 0, SND_SOC_NOPM, 0, 0), + + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("ADDA MIXL", SND_SOC_NOPM, 0, 0, rt5663_adda_l_mix, + ARRAY_SIZE(rt5663_adda_l_mix)), + SND_SOC_DAPM_MIXER("ADDA MIXR", SND_SOC_NOPM, 0, 0, rt5663_adda_r_mix, + ARRAY_SIZE(rt5663_adda_r_mix)), + SND_SOC_DAPM_PGA("DAC L1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC R1", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("STO1 DAC Filter", RT5663_PWR_DIG_2, + RT5663_PWR_DAC_S1F_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("STO1 DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5663_sto1_dac_l_mix, ARRAY_SIZE(rt5663_sto1_dac_l_mix)), + SND_SOC_DAPM_MIXER("STO1 DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5663_sto1_dac_r_mix, ARRAY_SIZE(rt5663_sto1_dac_r_mix)), + + /* DACs */ + SND_SOC_DAPM_SUPPLY("STO1 DAC L Power", RT5663_PWR_DIG_1, + RT5663_PWR_DAC_L1_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("STO1 DAC R Power", RT5663_PWR_DIG_1, + RT5663_PWR_DAC_R1_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DAC L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC R", NULL, SND_SOC_NOPM, 0, 0), + + /* Headphone*/ + SND_SOC_DAPM_SUPPLY("HP Charge Pump", SND_SOC_NOPM, 0, 0, + rt5663_charge_pump_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, rt5663_hp_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), +}; + +static const struct snd_soc_dapm_widget rt5663_v2_specific_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO2", RT5663_PWR_ANLG_3, + RT5663_PWR_LDO2_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Det Power", RT5663_PWR_VOL, + RT5663_V2_PWR_MIC_DET_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("LDO DAC", RT5663_PWR_DIG_1, + RT5663_PWR_LDO_DACREF_SHIFT, 0, NULL, 0), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY("I2S ASRC", RT5663_ASRC_1, + RT5663_I2S1_ASRC_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC ASRC", RT5663_ASRC_1, + RT5663_DAC_STO1_ASRC_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC ASRC", RT5663_ASRC_1, + RT5663_ADC_STO1_ASRC_SHIFT, 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1 CBJ", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CBJ Power", RT5663_PWR_ANLG_3, + RT5663_PWR_CBJ_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST2 Power", SND_SOC_NOPM, 0, 0, + rt5663_bst2_power, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX1L", SND_SOC_NOPM, 0, 0, rt5663_recmix1l, + ARRAY_SIZE(rt5663_recmix1l)), + SND_SOC_DAPM_MIXER("RECMIX1R", SND_SOC_NOPM, 0, 0, rt5663_recmix1r, + ARRAY_SIZE(rt5663_recmix1r)), + SND_SOC_DAPM_SUPPLY("RECMIX1R Power", RT5663_PWR_ANLG_2, + RT5663_PWR_RECMIX2_SHIFT, 0, NULL, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC R", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC R Power", RT5663_PWR_DIG_1, + RT5663_PWR_ADC_R1_SHIFT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_PGA("STO1 ADC L1", RT5663_STO1_ADC_MIXER, + RT5663_STO1_ADC_L1_SRC_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("STO1 ADC R1", RT5663_STO1_ADC_MIXER, + RT5663_STO1_ADC_R1_SRC_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("STO1 ADC L2", RT5663_STO1_ADC_MIXER, + RT5663_STO1_ADC_L2_SRC_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("STO1 ADC R2", RT5663_STO1_ADC_MIXER, + RT5663_STO1_ADC_R2_SRC_SHIFT, 1, NULL, 0), + + SND_SOC_DAPM_MUX("STO1 ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5663_sto1_adcl_mux), + SND_SOC_DAPM_MUX("STO1 ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5663_sto1_adcr_mux), + + /* ADC Mix */ + SND_SOC_DAPM_MIXER("STO1 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5663_sto1_adc_r_mix, ARRAY_SIZE(rt5663_sto1_adc_r_mix)), + + /* Analog DAC Clock */ + SND_SOC_DAPM_SUPPLY("DAC Clock", RT5663_CHOP_DAC_L, + RT5663_CKGEN_DAC1_SHIFT, 0, NULL, 0), + + /* Headphone out */ + SND_SOC_DAPM_SWITCH("HPO Playback", SND_SOC_NOPM, 0, 0, + &rt5663_hpo_switch), +}; + +static const struct snd_soc_dapm_widget rt5663_specific_dapm_widgets[] = { + /* System Clock Pre Divider Gating */ + SND_SOC_DAPM_SUPPLY("Pre Div Power", SND_SOC_NOPM, 0, 0, + rt5663_pre_div_power, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + + /* LDO */ + SND_SOC_DAPM_SUPPLY("LDO ADC", RT5663_PWR_DIG_1, + RT5663_PWR_LDO_DACREF_SHIFT, 0, NULL, 0), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY("I2S ASRC", RT5663_ASRC_1, + RT5663_I2S1_ASRC_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC ASRC", RT5663_ASRC_1, + RT5663_DAC_STO1_ASRC_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC ASRC", RT5663_ASRC_1, + RT5663_ADC_STO1_ASRC_SHIFT, 0, NULL, 0), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* STO ADC */ + SND_SOC_DAPM_PGA("STO1 ADC L1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("STO1 ADC L2", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Analog DAC source */ + SND_SOC_DAPM_MUX("DAC L Mux", SND_SOC_NOPM, 0, 0, &rt5663_alg_dacl_mux), + SND_SOC_DAPM_MUX("DAC R Mux", SND_SOC_NOPM, 0, 0, &rt5663_alg_dacr_mux), +}; + +static const struct snd_soc_dapm_route rt5663_dapm_routes[] = { + /* PLL */ + { "I2S", NULL, "PLL", rt5663_is_sys_clk_from_pll }, + + /* ASRC */ + { "STO1 ADC Filter", NULL, "ADC ASRC", rt5663_is_using_asrc }, + { "STO1 DAC Filter", NULL, "DAC ASRC", rt5663_is_using_asrc }, + { "I2S", NULL, "I2S ASRC", rt5663_i2s_use_asrc }, + + { "ADC L", NULL, "ADC L Power" }, + { "ADC L", NULL, "ADC Clock" }, + + { "STO1 ADC L2", NULL, "STO1 DAC MIXL" }, + + { "STO1 ADC MIXL", "ADC1 Switch", "STO1 ADC L1" }, + { "STO1 ADC MIXL", "ADC2 Switch", "STO1 ADC L2" }, + { "STO1 ADC MIXL", NULL, "STO1 ADC Filter" }, + + { "IF1 ADC1", NULL, "STO1 ADC MIXL" }, + { "IF ADC", NULL, "IF1 ADC1" }, + { "AIFTX", NULL, "IF ADC" }, + { "AIFTX", NULL, "I2S" }, + + { "AIFRX", NULL, "I2S" }, + { "IF DAC", NULL, "AIFRX" }, + { "IF1 DAC1 L", NULL, "IF DAC" }, + { "IF1 DAC1 R", NULL, "IF DAC" }, + + { "ADDA MIXL", "ADC L Switch", "STO1 ADC MIXL" }, + { "ADDA MIXL", "DAC L Switch", "IF1 DAC1 L" }, + { "ADDA MIXL", NULL, "STO1 DAC Filter" }, + { "ADDA MIXL", NULL, "STO1 DAC L Power" }, + { "ADDA MIXR", "DAC R Switch", "IF1 DAC1 R" }, + { "ADDA MIXR", NULL, "STO1 DAC Filter" }, + { "ADDA MIXR", NULL, "STO1 DAC R Power" }, + + { "DAC L1", NULL, "ADDA MIXL" }, + { "DAC R1", NULL, "ADDA MIXR" }, + + { "STO1 DAC MIXL", "DAC L Switch", "DAC L1" }, + { "STO1 DAC MIXL", NULL, "STO1 DAC L Power" }, + { "STO1 DAC MIXL", NULL, "STO1 DAC Filter" }, + { "STO1 DAC MIXR", "DAC R Switch", "DAC R1" }, + { "STO1 DAC MIXR", NULL, "STO1 DAC R Power" }, + { "STO1 DAC MIXR", NULL, "STO1 DAC Filter" }, + + { "HP Amp", NULL, "HP Charge Pump" }, + { "HP Amp", NULL, "DAC L" }, + { "HP Amp", NULL, "DAC R" }, +}; + +static const struct snd_soc_dapm_route rt5663_v2_specific_dapm_routes[] = { + { "MICBIAS1", NULL, "LDO2" }, + { "MICBIAS2", NULL, "LDO2" }, + + { "BST1 CBJ", NULL, "IN1P" }, + { "BST1 CBJ", NULL, "IN1N" }, + { "BST1 CBJ", NULL, "CBJ Power" }, + + { "BST2", NULL, "IN2P" }, + { "BST2", NULL, "IN2N" }, + { "BST2", NULL, "BST2 Power" }, + + { "RECMIX1L", "BST2 Switch", "BST2" }, + { "RECMIX1L", "BST1 CBJ Switch", "BST1 CBJ" }, + { "RECMIX1L", NULL, "RECMIX1L Power" }, + { "RECMIX1R", "BST2 Switch", "BST2" }, + { "RECMIX1R", NULL, "RECMIX1R Power" }, + + { "ADC L", NULL, "RECMIX1L" }, + { "ADC R", NULL, "RECMIX1R" }, + { "ADC R", NULL, "ADC R Power" }, + { "ADC R", NULL, "ADC Clock" }, + + { "STO1 ADC L Mux", "ADC L", "ADC L" }, + { "STO1 ADC L Mux", "ADC R", "ADC R" }, + { "STO1 ADC L1", NULL, "STO1 ADC L Mux" }, + + { "STO1 ADC R Mux", "ADC L", "ADC L" }, + { "STO1 ADC R Mux", "ADC R", "ADC R" }, + { "STO1 ADC R1", NULL, "STO1 ADC R Mux" }, + { "STO1 ADC R2", NULL, "STO1 DAC MIXR" }, + + { "STO1 ADC MIXR", "ADC1 Switch", "STO1 ADC R1" }, + { "STO1 ADC MIXR", "ADC2 Switch", "STO1 ADC R2" }, + { "STO1 ADC MIXR", NULL, "STO1 ADC Filter" }, + + { "IF1 ADC1", NULL, "STO1 ADC MIXR" }, + + { "ADDA MIXR", "ADC R Switch", "STO1 ADC MIXR" }, + + { "DAC L", NULL, "STO1 DAC MIXL" }, + { "DAC L", NULL, "LDO DAC" }, + { "DAC L", NULL, "DAC Clock" }, + { "DAC R", NULL, "STO1 DAC MIXR" }, + { "DAC R", NULL, "LDO DAC" }, + { "DAC R", NULL, "DAC Clock" }, + + { "HPO Playback", "Switch", "HP Amp" }, + { "HPOL", NULL, "HPO Playback" }, + { "HPOR", NULL, "HPO Playback" }, +}; + +static const struct snd_soc_dapm_route rt5663_specific_dapm_routes[] = { + { "I2S", NULL, "Pre Div Power" }, + + { "BST1", NULL, "IN1P" }, + { "BST1", NULL, "IN1N" }, + { "BST1", NULL, "RECMIX1L Power" }, + + { "ADC L", NULL, "BST1" }, + + { "STO1 ADC L1", NULL, "ADC L" }, + + { "DAC L Mux", "DAC L", "DAC L1" }, + { "DAC L Mux", "STO DAC MIXL", "STO1 DAC MIXL" }, + { "DAC R Mux", "DAC R", "DAC R1"}, + { "DAC R Mux", "STO DAC MIXR", "STO1 DAC MIXR" }, + + { "DAC L", NULL, "DAC L Mux" }, + { "DAC R", NULL, "DAC R Mux" }, + + { "HPOL", NULL, "HP Amp" }, + { "HPOR", NULL, "HP Amp" }, +}; + +static int rt5663_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0; + int pre_div; + + rt5663->lrck = params_rate(params); + + dev_dbg(dai->dev, "bclk is %dHz and sysclk is %dHz\n", + rt5663->lrck, rt5663->sysclk); + + pre_div = rl6231_get_clk_info(rt5663->sysclk, rt5663->lrck); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting %d for DAI %d\n", + rt5663->lrck, dai->id); + return -EINVAL; + } + + dev_dbg(dai->dev, "pre_div is %d for iis %d\n", pre_div, dai->id); + + switch (params_width(params)) { + case 8: + val_len = RT5663_I2S_DL_8; + break; + case 16: + val_len = RT5663_I2S_DL_16; + break; + case 20: + val_len = RT5663_I2S_DL_20; + break; + case 24: + val_len = RT5663_I2S_DL_24; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5663_I2S1_SDP, + RT5663_I2S_DL_MASK, val_len); + + snd_soc_component_update_bits(component, RT5663_ADDA_CLK_1, + RT5663_I2S_PD1_MASK, pre_div << RT5663_I2S_PD1_SHIFT); + + return 0; +} + +static int rt5663_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5663_I2S_MS_S; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5663_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5663_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5663_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5663_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5663_I2S1_SDP, RT5663_I2S_MS_MASK | + RT5663_I2S_BP_MASK | RT5663_I2S_DF_MASK, reg_val); + + return 0; +} + +static int rt5663_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + if (freq == rt5663->sysclk && clk_id == rt5663->sysclk_src) + return 0; + + switch (clk_id) { + case RT5663_SCLK_S_MCLK: + reg_val |= RT5663_SCLK_SRC_MCLK; + break; + case RT5663_SCLK_S_PLL1: + reg_val |= RT5663_SCLK_SRC_PLL1; + break; + case RT5663_SCLK_S_RCCLK: + reg_val |= RT5663_SCLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5663_GLB_CLK, RT5663_SCLK_SRC_MASK, + reg_val); + rt5663->sysclk = freq; + rt5663->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; +} + +static int rt5663_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + int mask, shift, val; + + if (source == rt5663->pll_src && freq_in == rt5663->pll_in && + freq_out == rt5663->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5663->pll_in = 0; + rt5663->pll_out = 0; + snd_soc_component_update_bits(component, RT5663_GLB_CLK, + RT5663_SCLK_SRC_MASK, RT5663_SCLK_SRC_MCLK); + return 0; + } + + switch (rt5663->codec_ver) { + case CODEC_VER_1: + mask = RT5663_V2_PLL1_SRC_MASK; + shift = RT5663_V2_PLL1_SRC_SHIFT; + break; + case CODEC_VER_0: + mask = RT5663_PLL1_SRC_MASK; + shift = RT5663_PLL1_SRC_SHIFT; + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + return -EINVAL; + } + + switch (source) { + case RT5663_PLL1_S_MCLK: + val = 0x0; + break; + case RT5663_PLL1_S_BCLK1: + val = 0x1; + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5663_GLB_CLK, mask, (val << shift)); + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", pll_code.m_bp, + (pll_code.m_bp ? 0 : pll_code.m_code), pll_code.n_code, + pll_code.k_code); + + snd_soc_component_write(component, RT5663_PLL_1, + pll_code.n_code << RT5663_PLL_N_SHIFT | pll_code.k_code); + snd_soc_component_write(component, RT5663_PLL_2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5663_PLL_M_SHIFT | + pll_code.m_bp << RT5663_PLL_M_BP_SHIFT); + + rt5663->pll_in = freq_in; + rt5663->pll_out = freq_out; + rt5663->pll_src = source; + + return 0; +} + +static int rt5663_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + unsigned int val = 0, reg; + + if (rx_mask || tx_mask) + val |= RT5663_TDM_MODE_TDM; + + switch (slots) { + case 4: + val |= RT5663_TDM_IN_CH_4; + val |= RT5663_TDM_OUT_CH_4; + break; + case 6: + val |= RT5663_TDM_IN_CH_6; + val |= RT5663_TDM_OUT_CH_6; + break; + case 8: + val |= RT5663_TDM_IN_CH_8; + val |= RT5663_TDM_OUT_CH_8; + break; + case 2: + break; + default: + return -EINVAL; + } + + switch (slot_width) { + case 20: + val |= RT5663_TDM_IN_LEN_20; + val |= RT5663_TDM_OUT_LEN_20; + break; + case 24: + val |= RT5663_TDM_IN_LEN_24; + val |= RT5663_TDM_OUT_LEN_24; + break; + case 32: + val |= RT5663_TDM_IN_LEN_32; + val |= RT5663_TDM_OUT_LEN_32; + break; + case 16: + break; + default: + return -EINVAL; + } + + switch (rt5663->codec_ver) { + case CODEC_VER_1: + reg = RT5663_TDM_2; + break; + case CODEC_VER_0: + reg = RT5663_TDM_1; + break; + default: + dev_err(component->dev, "Unknown CODEC Version\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, reg, RT5663_TDM_MODE_MASK | + RT5663_TDM_IN_CH_MASK | RT5663_TDM_OUT_CH_MASK | + RT5663_TDM_IN_LEN_MASK | RT5663_TDM_OUT_LEN_MASK, val); + + return 0; +} + +static int rt5663_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + unsigned int reg; + + dev_dbg(component->dev, "%s ratio = %d\n", __func__, ratio); + + if (rt5663->codec_ver == CODEC_VER_1) + reg = RT5663_TDM_9; + else + reg = RT5663_TDM_5; + + switch (ratio) { + case 32: + snd_soc_component_update_bits(component, reg, + RT5663_TDM_LENGTN_MASK, + RT5663_TDM_LENGTN_16); + break; + case 40: + snd_soc_component_update_bits(component, reg, + RT5663_TDM_LENGTN_MASK, + RT5663_TDM_LENGTN_20); + break; + case 48: + snd_soc_component_update_bits(component, reg, + RT5663_TDM_LENGTN_MASK, + RT5663_TDM_LENGTN_24); + break; + case 64: + snd_soc_component_update_bits(component, reg, + RT5663_TDM_LENGTN_MASK, + RT5663_TDM_LENGTN_32); + break; + default: + dev_err(component->dev, "Invalid ratio!\n"); + return -EINVAL; + } + + return 0; +} + +static int rt5663_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_FV1_MASK | RT5663_PWR_FV2_MASK, + RT5663_PWR_FV1 | RT5663_PWR_FV2); + break; + + case SND_SOC_BIAS_PREPARE: + if (rt5663->codec_ver == CODEC_VER_1) { + snd_soc_component_update_bits(component, RT5663_DIG_MISC, + RT5663_DIG_GATE_CTRL_MASK, + RT5663_DIG_GATE_CTRL_EN); + snd_soc_component_update_bits(component, RT5663_SIG_CLK_DET, + RT5663_EN_ANA_CLK_DET_MASK | + RT5663_PWR_CLK_DET_MASK, + RT5663_EN_ANA_CLK_DET_AUTO | + RT5663_PWR_CLK_DET_EN); + } + break; + + case SND_SOC_BIAS_STANDBY: + if (rt5663->codec_ver == CODEC_VER_1) + snd_soc_component_update_bits(component, RT5663_DIG_MISC, + RT5663_DIG_GATE_CTRL_MASK, + RT5663_DIG_GATE_CTRL_DIS); + snd_soc_component_update_bits(component, RT5663_PWR_ANLG_1, + RT5663_PWR_VREF1_MASK | RT5663_PWR_VREF2_MASK | + RT5663_PWR_FV1_MASK | RT5663_PWR_FV2_MASK | + RT5663_PWR_MB_MASK, RT5663_PWR_VREF1 | + RT5663_PWR_VREF2 | RT5663_PWR_MB); + usleep_range(10000, 10005); + if (rt5663->codec_ver == CODEC_VER_1) { + snd_soc_component_update_bits(component, RT5663_SIG_CLK_DET, + RT5663_EN_ANA_CLK_DET_MASK | + RT5663_PWR_CLK_DET_MASK, + RT5663_EN_ANA_CLK_DET_DIS | + RT5663_PWR_CLK_DET_DIS); + } + break; + + case SND_SOC_BIAS_OFF: + if (rt5663->jack_type != SND_JACK_HEADSET) + snd_soc_component_update_bits(component, + RT5663_PWR_ANLG_1, + RT5663_PWR_VREF1_MASK | RT5663_PWR_VREF2_MASK | + RT5663_PWR_FV1 | RT5663_PWR_FV2 | + RT5663_PWR_MB_MASK, 0); + else + snd_soc_component_update_bits(component, + RT5663_PWR_ANLG_1, + RT5663_PWR_FV1_MASK | RT5663_PWR_FV2_MASK, + RT5663_PWR_FV1 | RT5663_PWR_FV2); + break; + + default: + break; + } + + return 0; +} + +static int rt5663_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + rt5663->component = component; + + switch (rt5663->codec_ver) { + case CODEC_VER_1: + snd_soc_dapm_new_controls(dapm, + rt5663_v2_specific_dapm_widgets, + ARRAY_SIZE(rt5663_v2_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5663_v2_specific_dapm_routes, + ARRAY_SIZE(rt5663_v2_specific_dapm_routes)); + snd_soc_add_component_controls(component, rt5663_v2_specific_controls, + ARRAY_SIZE(rt5663_v2_specific_controls)); + break; + case CODEC_VER_0: + snd_soc_dapm_new_controls(dapm, + rt5663_specific_dapm_widgets, + ARRAY_SIZE(rt5663_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5663_specific_dapm_routes, + ARRAY_SIZE(rt5663_specific_dapm_routes)); + snd_soc_add_component_controls(component, rt5663_specific_controls, + ARRAY_SIZE(rt5663_specific_controls)); + + if (!rt5663->imp_table) + snd_soc_add_component_controls(component, rt5663_hpvol_controls, + ARRAY_SIZE(rt5663_hpvol_controls)); + break; + } + + return 0; +} + +static void rt5663_remove(struct snd_soc_component *component) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + regmap_write(rt5663->regmap, RT5663_RESET, 0); +} + +#ifdef CONFIG_PM +static int rt5663_suspend(struct snd_soc_component *component) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5663->regmap, true); + regcache_mark_dirty(rt5663->regmap); + + return 0; +} + +static int rt5663_resume(struct snd_soc_component *component) +{ + struct rt5663_priv *rt5663 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5663->regmap, false); + regcache_sync(rt5663->regmap); + + rt5663_irq(0, rt5663); + + return 0; +} +#else +#define rt5663_suspend NULL +#define rt5663_resume NULL +#endif + +#define RT5663_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5663_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5663_aif_dai_ops = { + .hw_params = rt5663_hw_params, + .set_fmt = rt5663_set_dai_fmt, + .set_sysclk = rt5663_set_dai_sysclk, + .set_pll = rt5663_set_dai_pll, + .set_tdm_slot = rt5663_set_tdm_slot, + .set_bclk_ratio = rt5663_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt5663_dai[] = { + { + .name = "rt5663-aif", + .id = RT5663_AIF, + .playback = { + .stream_name = "AIF Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5663_STEREO_RATES, + .formats = RT5663_FORMATS, + }, + .capture = { + .stream_name = "AIF Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5663_STEREO_RATES, + .formats = RT5663_FORMATS, + }, + .ops = &rt5663_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5663 = { + .probe = rt5663_probe, + .remove = rt5663_remove, + .suspend = rt5663_suspend, + .resume = rt5663_resume, + .set_bias_level = rt5663_set_bias_level, + .controls = rt5663_snd_controls, + .num_controls = ARRAY_SIZE(rt5663_snd_controls), + .dapm_widgets = rt5663_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5663_dapm_widgets), + .dapm_routes = rt5663_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5663_dapm_routes), + .set_jack = rt5663_set_jack_detect, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5663_v2_regmap = { + .reg_bits = 16, + .val_bits = 16, + .use_single_rw = true, + .max_register = 0x07fa, + .volatile_reg = rt5663_v2_volatile_register, + .readable_reg = rt5663_v2_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5663_v2_reg, + .num_reg_defaults = ARRAY_SIZE(rt5663_v2_reg), +}; + +static const struct regmap_config rt5663_regmap = { + .reg_bits = 16, + .val_bits = 16, + .use_single_rw = true, + .max_register = 0x03f3, + .volatile_reg = rt5663_volatile_register, + .readable_reg = rt5663_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5663_reg, + .num_reg_defaults = ARRAY_SIZE(rt5663_reg), +}; + +static const struct regmap_config temp_regmap = { + .name = "nocache", + .reg_bits = 16, + .val_bits = 16, + .use_single_rw = true, + .max_register = 0x03f3, + .cache_type = REGCACHE_NONE, +}; + +static const struct i2c_device_id rt5663_i2c_id[] = { + { "rt5663", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt5663_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id rt5663_of_match[] = { + { .compatible = "realtek,rt5663", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5663_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5663_acpi_match[] = { + { "10EC5663", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt5663_acpi_match); +#endif + +static void rt5663_v2_calibrate(struct rt5663_priv *rt5663) +{ + regmap_write(rt5663->regmap, RT5663_BIAS_CUR_8, 0xa402); + regmap_write(rt5663->regmap, RT5663_PWR_DIG_1, 0x0100); + regmap_write(rt5663->regmap, RT5663_RECMIX, 0x4040); + regmap_write(rt5663->regmap, RT5663_DIG_MISC, 0x0001); + regmap_write(rt5663->regmap, RT5663_RC_CLK, 0x0380); + regmap_write(rt5663->regmap, RT5663_GLB_CLK, 0x8000); + regmap_write(rt5663->regmap, RT5663_ADDA_CLK_1, 0x1000); + regmap_write(rt5663->regmap, RT5663_CHOP_DAC_L, 0x3030); + regmap_write(rt5663->regmap, RT5663_CALIB_ADC, 0x3c05); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_1, 0xa23e); + msleep(40); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_1, 0xf23e); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_2, 0x0321); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_1, 0xfc00); + msleep(500); +} + +static void rt5663_calibrate(struct rt5663_priv *rt5663) +{ + int value, count; + + regmap_write(rt5663->regmap, RT5663_RESET, 0x0000); + msleep(20); + regmap_write(rt5663->regmap, RT5663_ANA_BIAS_CUR_4, 0x00a1); + regmap_write(rt5663->regmap, RT5663_RC_CLK, 0x0380); + regmap_write(rt5663->regmap, RT5663_GLB_CLK, 0x8000); + regmap_write(rt5663->regmap, RT5663_ADDA_CLK_1, 0x1000); + regmap_write(rt5663->regmap, RT5663_VREF_RECMIX, 0x0032); + regmap_write(rt5663->regmap, RT5663_HP_IMP_SEN_19, 0x000c); + regmap_write(rt5663->regmap, RT5663_DUMMY_1, 0x0324); + regmap_write(rt5663->regmap, RT5663_DIG_MISC, 0x8001); + regmap_write(rt5663->regmap, RT5663_VREFADJ_OP, 0x0f28); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_1, 0xa23b); + msleep(30); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_1, 0xf23b); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_2, 0x8000); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_3, 0x0008); + regmap_write(rt5663->regmap, RT5663_PRE_DIV_GATING_1, 0xffff); + regmap_write(rt5663->regmap, RT5663_PRE_DIV_GATING_2, 0xffff); + regmap_write(rt5663->regmap, RT5663_CBJ_1, 0x8c10); + regmap_write(rt5663->regmap, RT5663_IL_CMD_2, 0x00c1); + regmap_write(rt5663->regmap, RT5663_EM_JACK_TYPE_1, 0xb880); + regmap_write(rt5663->regmap, RT5663_EM_JACK_TYPE_2, 0x4110); + regmap_write(rt5663->regmap, RT5663_EM_JACK_TYPE_2, 0x4118); + + count = 0; + while (true) { + regmap_read(rt5663->regmap, RT5663_INT_ST_2, &value); + if (!(value & 0x80)) + usleep_range(10000, 10005); + else + break; + + if (++count > 200) + break; + } + + regmap_write(rt5663->regmap, RT5663_HP_IMP_SEN_19, 0x0000); + regmap_write(rt5663->regmap, RT5663_DEPOP_2, 0x3003); + regmap_write(rt5663->regmap, RT5663_DEPOP_1, 0x0038); + regmap_write(rt5663->regmap, RT5663_DEPOP_1, 0x003b); + regmap_write(rt5663->regmap, RT5663_PWR_DIG_2, 0x8400); + regmap_write(rt5663->regmap, RT5663_PWR_DIG_1, 0x8df8); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_2, 0x8003); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_3, 0x018c); + regmap_write(rt5663->regmap, RT5663_HP_CHARGE_PUMP_1, 0x1e32); + regmap_write(rt5663->regmap, RT5663_DUMMY_2, 0x8089); + regmap_write(rt5663->regmap, RT5663_DACREF_LDO, 0x3b0b); + msleep(40); + regmap_write(rt5663->regmap, RT5663_STO_DAC_MIXER, 0x0000); + regmap_write(rt5663->regmap, RT5663_BYPASS_STO_DAC, 0x000c); + regmap_write(rt5663->regmap, RT5663_HP_BIAS, 0xafaa); + regmap_write(rt5663->regmap, RT5663_CHARGE_PUMP_1, 0x2224); + regmap_write(rt5663->regmap, RT5663_HP_OUT_EN, 0x8088); + regmap_write(rt5663->regmap, RT5663_STO_DRE_9, 0x0017); + regmap_write(rt5663->regmap, RT5663_STO_DRE_10, 0x0017); + regmap_write(rt5663->regmap, RT5663_STO1_ADC_MIXER, 0x4040); + regmap_write(rt5663->regmap, RT5663_CHOP_ADC, 0x3000); + regmap_write(rt5663->regmap, RT5663_RECMIX, 0x0005); + regmap_write(rt5663->regmap, RT5663_ADDA_RST, 0xc000); + regmap_write(rt5663->regmap, RT5663_STO1_HPF_ADJ1, 0x3320); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_2, 0x00c9); + regmap_write(rt5663->regmap, RT5663_DUMMY_1, 0x004c); + regmap_write(rt5663->regmap, RT5663_ANA_BIAS_CUR_1, 0x1111); + regmap_write(rt5663->regmap, RT5663_BIAS_CUR_8, 0x4402); + regmap_write(rt5663->regmap, RT5663_CHARGE_PUMP_2, 0x3311); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_1, 0x0069); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_3, 0x06ce); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_1_1, 0x6800); + regmap_write(rt5663->regmap, RT5663_CHARGE_PUMP_2, 0x1100); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_7, 0x0057); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_1_1, 0xe800); + + count = 0; + while (true) { + regmap_read(rt5663->regmap, RT5663_HP_CALIB_1_1, &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 200) + return; + count++; + } + + regmap_write(rt5663->regmap, RT5663_HP_CALIB_1_1, 0x6200); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_7, 0x0059); + regmap_write(rt5663->regmap, RT5663_HP_CALIB_1_1, 0xe200); + + count = 0; + while (true) { + regmap_read(rt5663->regmap, RT5663_HP_CALIB_1_1, &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 200) + return; + count++; + } + + regmap_write(rt5663->regmap, RT5663_EM_JACK_TYPE_1, 0xb8e0); + usleep_range(10000, 10005); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_1, 0x003b); + usleep_range(10000, 10005); + regmap_write(rt5663->regmap, RT5663_PWR_DIG_1, 0x0000); + usleep_range(10000, 10005); + regmap_write(rt5663->regmap, RT5663_DEPOP_1, 0x000b); + usleep_range(10000, 10005); + regmap_write(rt5663->regmap, RT5663_DEPOP_1, 0x0008); + usleep_range(10000, 10005); + regmap_write(rt5663->regmap, RT5663_PWR_ANLG_2, 0x0000); + usleep_range(10000, 10005); +} + +static int rt5663_parse_dp(struct rt5663_priv *rt5663, struct device *dev) +{ + int table_size; + int ret; + + device_property_read_u32(dev, "realtek,dc_offset_l_manual", + &rt5663->pdata.dc_offset_l_manual); + device_property_read_u32(dev, "realtek,dc_offset_r_manual", + &rt5663->pdata.dc_offset_r_manual); + device_property_read_u32(dev, "realtek,dc_offset_l_manual_mic", + &rt5663->pdata.dc_offset_l_manual_mic); + device_property_read_u32(dev, "realtek,dc_offset_r_manual_mic", + &rt5663->pdata.dc_offset_r_manual_mic); + device_property_read_u32(dev, "realtek,impedance_sensing_num", + &rt5663->pdata.impedance_sensing_num); + + if (rt5663->pdata.impedance_sensing_num) { + table_size = sizeof(struct impedance_mapping_table) * + rt5663->pdata.impedance_sensing_num; + rt5663->imp_table = devm_kzalloc(dev, table_size, GFP_KERNEL); + if (!rt5663->imp_table) + return -ENOMEM; + ret = device_property_read_u32_array(dev, + "realtek,impedance_sensing_table", + (u32 *)rt5663->imp_table, table_size); + if (ret) + return ret; + } + + return 0; +} + +static int rt5663_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5663_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5663_priv *rt5663; + int ret; + unsigned int val; + struct regmap *regmap; + + rt5663 = devm_kzalloc(&i2c->dev, sizeof(struct rt5663_priv), + GFP_KERNEL); + + if (rt5663 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5663); + + if (pdata) + rt5663->pdata = *pdata; + else { + ret = rt5663_parse_dp(rt5663, &i2c->dev); + if (ret) + return ret; + } + + regmap = devm_regmap_init_i2c(i2c, &temp_regmap); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&i2c->dev, "Failed to allocate temp register map: %d\n", + ret); + return ret; + } + + ret = regmap_read(regmap, RT5663_VENDOR_ID_2, &val); + if (ret || (val != RT5663_DEVICE_ID_2 && val != RT5663_DEVICE_ID_1)) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5663, retry one time.\n", + val); + msleep(100); + regmap_read(regmap, RT5663_VENDOR_ID_2, &val); + } + + switch (val) { + case RT5663_DEVICE_ID_2: + rt5663->regmap = devm_regmap_init_i2c(i2c, &rt5663_v2_regmap); + rt5663->codec_ver = CODEC_VER_1; + break; + case RT5663_DEVICE_ID_1: + rt5663->regmap = devm_regmap_init_i2c(i2c, &rt5663_regmap); + rt5663->codec_ver = CODEC_VER_0; + break; + default: + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5663\n", + val); + return -ENODEV; + } + + if (IS_ERR(rt5663->regmap)) { + ret = PTR_ERR(rt5663->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + /* reset and calibrate */ + regmap_write(rt5663->regmap, RT5663_RESET, 0); + regcache_cache_bypass(rt5663->regmap, true); + switch (rt5663->codec_ver) { + case CODEC_VER_1: + rt5663_v2_calibrate(rt5663); + break; + case CODEC_VER_0: + rt5663_calibrate(rt5663); + break; + default: + dev_err(&i2c->dev, "%s:Unknown codec type\n", __func__); + } + regcache_cache_bypass(rt5663->regmap, false); + regmap_write(rt5663->regmap, RT5663_RESET, 0); + dev_dbg(&i2c->dev, "calibrate done\n"); + + switch (rt5663->codec_ver) { + case CODEC_VER_1: + break; + case CODEC_VER_0: + ret = regmap_register_patch(rt5663->regmap, rt5663_patch_list, + ARRAY_SIZE(rt5663_patch_list)); + if (ret != 0) + dev_warn(&i2c->dev, + "Failed to apply regmap patch: %d\n", ret); + break; + default: + dev_err(&i2c->dev, "%s:Unknown codec type\n", __func__); + } + + /* GPIO1 as IRQ */ + regmap_update_bits(rt5663->regmap, RT5663_GPIO_1, RT5663_GP1_PIN_MASK, + RT5663_GP1_PIN_IRQ); + /* 4btn inline command debounce */ + regmap_update_bits(rt5663->regmap, RT5663_IL_CMD_5, + RT5663_4BTN_CLK_DEB_MASK, RT5663_4BTN_CLK_DEB_65MS); + + switch (rt5663->codec_ver) { + case CODEC_VER_1: + regmap_write(rt5663->regmap, RT5663_BIAS_CUR_8, 0xa402); + /* JD1 */ + regmap_update_bits(rt5663->regmap, RT5663_AUTO_1MRC_CLK, + RT5663_IRQ_POW_SAV_MASK | RT5663_IRQ_POW_SAV_JD1_MASK, + RT5663_IRQ_POW_SAV_EN | RT5663_IRQ_POW_SAV_JD1_EN); + regmap_update_bits(rt5663->regmap, RT5663_PWR_ANLG_2, + RT5663_PWR_JD1_MASK, RT5663_PWR_JD1); + regmap_update_bits(rt5663->regmap, RT5663_IRQ_1, + RT5663_EN_CB_JD_MASK, RT5663_EN_CB_JD_EN); + + regmap_update_bits(rt5663->regmap, RT5663_HP_LOGIC_2, + RT5663_HP_SIG_SRC1_MASK, RT5663_HP_SIG_SRC1_REG); + regmap_update_bits(rt5663->regmap, RT5663_RECMIX, + RT5663_VREF_BIAS_MASK | RT5663_CBJ_DET_MASK | + RT5663_DET_TYPE_MASK, RT5663_VREF_BIAS_REG | + RT5663_CBJ_DET_EN | RT5663_DET_TYPE_QFN); + /* Set GPIO4 and GPIO8 as input for combo jack */ + regmap_update_bits(rt5663->regmap, RT5663_GPIO_2, + RT5663_GP4_PIN_CONF_MASK, RT5663_GP4_PIN_CONF_INPUT); + regmap_update_bits(rt5663->regmap, RT5663_GPIO_3, + RT5663_GP8_PIN_CONF_MASK, RT5663_GP8_PIN_CONF_INPUT); + regmap_update_bits(rt5663->regmap, RT5663_PWR_ANLG_1, + RT5663_LDO1_DVO_MASK | RT5663_AMP_HP_MASK, + RT5663_LDO1_DVO_0_9V | RT5663_AMP_HP_3X); + break; + case CODEC_VER_0: + regmap_update_bits(rt5663->regmap, RT5663_DIG_MISC, + RT5663_DIG_GATE_CTRL_MASK, RT5663_DIG_GATE_CTRL_EN); + regmap_update_bits(rt5663->regmap, RT5663_AUTO_1MRC_CLK, + RT5663_IRQ_MANUAL_MASK, RT5663_IRQ_MANUAL_EN); + regmap_update_bits(rt5663->regmap, RT5663_IRQ_1, + RT5663_EN_IRQ_JD1_MASK, RT5663_EN_IRQ_JD1_EN); + regmap_update_bits(rt5663->regmap, RT5663_GPIO_1, + RT5663_GPIO1_TYPE_MASK, RT5663_GPIO1_TYPE_EN); + regmap_write(rt5663->regmap, RT5663_VREF_RECMIX, 0x0032); + regmap_update_bits(rt5663->regmap, RT5663_GPIO_2, + RT5663_GP1_PIN_CONF_MASK | RT5663_SEL_GPIO1_MASK, + RT5663_GP1_PIN_CONF_OUTPUT | RT5663_SEL_GPIO1_EN); + regmap_update_bits(rt5663->regmap, RT5663_RECMIX, + RT5663_RECMIX1_BST1_MASK, RT5663_RECMIX1_BST1_ON); + regmap_update_bits(rt5663->regmap, RT5663_TDM_2, + RT5663_DATA_SWAP_ADCDAT1_MASK, + RT5663_DATA_SWAP_ADCDAT1_LL); + break; + default: + dev_err(&i2c->dev, "%s:Unknown codec type\n", __func__); + } + + INIT_DELAYED_WORK(&rt5663->jack_detect_work, rt5663_jack_detect_work); + INIT_DELAYED_WORK(&rt5663->jd_unplug_work, rt5663_jd_unplug_work); + + if (i2c->irq) { + ret = request_irq(i2c->irq, rt5663_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5663", rt5663); + if (ret) + dev_err(&i2c->dev, "%s Failed to reguest IRQ: %d\n", + __func__, ret); + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5663, + rt5663_dai, ARRAY_SIZE(rt5663_dai)); + + if (ret) { + if (i2c->irq) + free_irq(i2c->irq, rt5663); + } + + return ret; +} + +static int rt5663_i2c_remove(struct i2c_client *i2c) +{ + struct rt5663_priv *rt5663 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, rt5663); + + return 0; +} + +static void rt5663_i2c_shutdown(struct i2c_client *client) +{ + struct rt5663_priv *rt5663 = i2c_get_clientdata(client); + + regmap_write(rt5663->regmap, RT5663_RESET, 0); +} + +static struct i2c_driver rt5663_i2c_driver = { + .driver = { + .name = "rt5663", + .acpi_match_table = ACPI_PTR(rt5663_acpi_match), + .of_match_table = of_match_ptr(rt5663_of_match), + }, + .probe = rt5663_i2c_probe, + .remove = rt5663_i2c_remove, + .shutdown = rt5663_i2c_shutdown, + .id_table = rt5663_i2c_id, +}; +module_i2c_driver(rt5663_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5663 driver"); +MODULE_AUTHOR("Jack Yu "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5663.h b/sound/soc/codecs/rt5663.h new file mode 100644 index 000000000..794cf3fad --- /dev/null +++ b/sound/soc/codecs/rt5663.h @@ -0,0 +1,1131 @@ +/* + * rt5663.h -- RT5663 ALSA SoC audio driver + * + * Copyright 2016 Realtek Microelectronics + * Author: Jack Yu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5663_H__ +#define __RT5663_H__ + +#include + +/* Info */ +#define RT5663_RESET 0x0000 +#define RT5663_VENDOR_ID 0x00fd +#define RT5663_VENDOR_ID_1 0x00fe +#define RT5663_VENDOR_ID_2 0x00ff + +#define RT5663_LOUT_CTRL 0x0001 +#define RT5663_HP_AMP_2 0x0003 +#define RT5663_MONO_OUT 0x0004 +#define RT5663_MONO_GAIN 0x0007 + +#define RT5663_AEC_BST 0x000b +#define RT5663_IN1_IN2 0x000c +#define RT5663_IN3_IN4 0x000d +#define RT5663_INL1_INR1 0x000f +#define RT5663_CBJ_TYPE_2 0x0011 +#define RT5663_CBJ_TYPE_3 0x0012 +#define RT5663_CBJ_TYPE_4 0x0013 +#define RT5663_CBJ_TYPE_5 0x0014 +#define RT5663_CBJ_TYPE_8 0x0017 + +/* I/O - ADC/DAC/DMIC */ +#define RT5663_DAC3_DIG_VOL 0x001a +#define RT5663_DAC3_CTRL 0x001b +#define RT5663_MONO_ADC_DIG_VOL 0x001d +#define RT5663_STO2_ADC_DIG_VOL 0x001e +#define RT5663_MONO_ADC_BST_GAIN 0x0020 +#define RT5663_STO2_ADC_BST_GAIN 0x0021 +#define RT5663_SIDETONE_CTRL 0x0024 +/* Mixer - D-D */ +#define RT5663_MONO1_ADC_MIXER 0x0027 +#define RT5663_STO2_ADC_MIXER 0x0028 +#define RT5663_MONO_DAC_MIXER 0x002b +#define RT5663_DAC2_SRC_CTRL 0x002e +#define RT5663_IF_3_4_DATA_CTL 0x002f +#define RT5663_IF_5_DATA_CTL 0x0030 +#define RT5663_PDM_OUT_CTL 0x0031 +#define RT5663_PDM_I2C_DATA_CTL1 0x0032 +#define RT5663_PDM_I2C_DATA_CTL2 0x0033 +#define RT5663_PDM_I2C_DATA_CTL3 0x0034 +#define RT5663_PDM_I2C_DATA_CTL4 0x0035 + +/*Mixer - Analog*/ +#define RT5663_RECMIX1_NEW 0x003a +#define RT5663_RECMIX1L_0 0x003b +#define RT5663_RECMIX1L 0x003c +#define RT5663_RECMIX1R_0 0x003d +#define RT5663_RECMIX1R 0x003e +#define RT5663_RECMIX2_NEW 0x003f +#define RT5663_RECMIX2_L_2 0x0041 +#define RT5663_RECMIX2_R 0x0042 +#define RT5663_RECMIX2_R_2 0x0043 +#define RT5663_CALIB_REC_LR 0x0044 +#define RT5663_ALC_BK_GAIN 0x0049 +#define RT5663_MONOMIX_GAIN 0x004a +#define RT5663_MONOMIX_IN_GAIN 0x004b +#define RT5663_OUT_MIXL_GAIN 0x004d +#define RT5663_OUT_LMIX_IN_GAIN 0x004e +#define RT5663_OUT_RMIX_IN_GAIN 0x004f +#define RT5663_OUT_RMIX_IN_GAIN1 0x0050 +#define RT5663_LOUT_MIXER_CTRL 0x0052 +/* Power */ +#define RT5663_PWR_VOL 0x0067 + +#define RT5663_ADCDAC_RST 0x006d +/* Format - ADC/DAC */ +#define RT5663_I2S34_SDP 0x0071 +#define RT5663_I2S5_SDP 0x0072 + +/* Function - Analog */ +#define RT5663_ASRC_3 0x0085 +#define RT5663_ASRC_6 0x0088 +#define RT5663_ASRC_7 0x0089 +#define RT5663_PLL_TRK_13 0x0099 +#define RT5663_I2S_M_CLK_CTL 0x00a0 +#define RT5663_FDIV_I2S34_M_CLK 0x00a1 +#define RT5663_FDIV_I2S34_M_CLK2 0x00a2 +#define RT5663_FDIV_I2S5_M_CLK 0x00a3 +#define RT5663_FDIV_I2S5_M_CLK2 0x00a4 + +/* Function - Digital */ +#define RT5663_V2_IRQ_4 0x00b9 +#define RT5663_GPIO_3 0x00c2 +#define RT5663_GPIO_4 0x00c3 +#define RT5663_GPIO_STA2 0x00c4 +#define RT5663_HP_AMP_DET1 0x00d0 +#define RT5663_HP_AMP_DET2 0x00d1 +#define RT5663_HP_AMP_DET3 0x00d2 +#define RT5663_MID_BD_HP_AMP 0x00d3 +#define RT5663_LOW_BD_HP_AMP 0x00d4 +#define RT5663_SOF_VOL_ZC2 0x00da +#define RT5663_ADC_STO2_ADJ1 0x00ee +#define RT5663_ADC_STO2_ADJ2 0x00ef +/* General Control */ +#define RT5663_A_JD_CTRL 0x00f0 +#define RT5663_JD1_TRES_CTRL 0x00f1 +#define RT5663_JD2_TRES_CTRL 0x00f2 +#define RT5663_V2_JD_CTRL2 0x00f7 +#define RT5663_DUM_REG_2 0x00fb +#define RT5663_DUM_REG_3 0x00fc + + +#define RT5663_DACADC_DIG_VOL2 0x0101 +#define RT5663_DIG_IN_PIN2 0x0133 +#define RT5663_PAD_DRV_CTL1 0x0136 +#define RT5663_SOF_RAM_DEPOP 0x0138 +#define RT5663_VOL_TEST 0x013f +#define RT5663_MONO_DYNA_1 0x0170 +#define RT5663_MONO_DYNA_2 0x0171 +#define RT5663_MONO_DYNA_3 0x0172 +#define RT5663_MONO_DYNA_4 0x0173 +#define RT5663_MONO_DYNA_5 0x0174 +#define RT5663_MONO_DYNA_6 0x0175 +#define RT5663_STO1_SIL_DET 0x0190 +#define RT5663_MONOL_SIL_DET 0x0191 +#define RT5663_MONOR_SIL_DET 0x0192 +#define RT5663_STO2_DAC_SIL 0x0193 +#define RT5663_PWR_SAV_CTL1 0x0194 +#define RT5663_PWR_SAV_CTL2 0x0195 +#define RT5663_PWR_SAV_CTL3 0x0196 +#define RT5663_PWR_SAV_CTL4 0x0197 +#define RT5663_PWR_SAV_CTL5 0x0198 +#define RT5663_PWR_SAV_CTL6 0x0199 +#define RT5663_MONO_AMP_CAL1 0x01a0 +#define RT5663_MONO_AMP_CAL2 0x01a1 +#define RT5663_MONO_AMP_CAL3 0x01a2 +#define RT5663_MONO_AMP_CAL4 0x01a3 +#define RT5663_MONO_AMP_CAL5 0x01a4 +#define RT5663_MONO_AMP_CAL6 0x01a5 +#define RT5663_MONO_AMP_CAL7 0x01a6 +#define RT5663_MONO_AMP_CAL_ST1 0x01a7 +#define RT5663_MONO_AMP_CAL_ST2 0x01a8 +#define RT5663_MONO_AMP_CAL_ST3 0x01a9 +#define RT5663_MONO_AMP_CAL_ST4 0x01aa +#define RT5663_MONO_AMP_CAL_ST5 0x01ab +#define RT5663_V2_HP_IMP_SEN_13 0x01b9 +#define RT5663_V2_HP_IMP_SEN_14 0x01ba +#define RT5663_V2_HP_IMP_SEN_6 0x01bb +#define RT5663_V2_HP_IMP_SEN_7 0x01bc +#define RT5663_V2_HP_IMP_SEN_8 0x01bd +#define RT5663_V2_HP_IMP_SEN_9 0x01be +#define RT5663_V2_HP_IMP_SEN_10 0x01bf +#define RT5663_HP_LOGIC_3 0x01dc +#define RT5663_HP_CALIB_ST10 0x01f3 +#define RT5663_HP_CALIB_ST11 0x01f4 +#define RT5663_PRO_REG_TBL_4 0x0203 +#define RT5663_PRO_REG_TBL_5 0x0204 +#define RT5663_PRO_REG_TBL_6 0x0205 +#define RT5663_PRO_REG_TBL_7 0x0206 +#define RT5663_PRO_REG_TBL_8 0x0207 +#define RT5663_PRO_REG_TBL_9 0x0208 +#define RT5663_SAR_ADC_INL_1 0x0210 +#define RT5663_SAR_ADC_INL_2 0x0211 +#define RT5663_SAR_ADC_INL_3 0x0212 +#define RT5663_SAR_ADC_INL_4 0x0213 +#define RT5663_SAR_ADC_INL_5 0x0214 +#define RT5663_SAR_ADC_INL_6 0x0215 +#define RT5663_SAR_ADC_INL_7 0x0216 +#define RT5663_SAR_ADC_INL_8 0x0217 +#define RT5663_SAR_ADC_INL_9 0x0218 +#define RT5663_SAR_ADC_INL_10 0x0219 +#define RT5663_SAR_ADC_INL_11 0x021a +#define RT5663_SAR_ADC_INL_12 0x021b +#define RT5663_DRC_CTRL_1 0x02ff +#define RT5663_DRC1_CTRL_2 0x0301 +#define RT5663_DRC1_CTRL_3 0x0302 +#define RT5663_DRC1_CTRL_4 0x0303 +#define RT5663_DRC1_CTRL_5 0x0304 +#define RT5663_DRC1_CTRL_6 0x0305 +#define RT5663_DRC1_HD_CTRL_1 0x0306 +#define RT5663_DRC1_HD_CTRL_2 0x0307 +#define RT5663_DRC1_PRI_REG_1 0x0310 +#define RT5663_DRC1_PRI_REG_2 0x0311 +#define RT5663_DRC1_PRI_REG_3 0x0312 +#define RT5663_DRC1_PRI_REG_4 0x0313 +#define RT5663_DRC1_PRI_REG_5 0x0314 +#define RT5663_DRC1_PRI_REG_6 0x0315 +#define RT5663_DRC1_PRI_REG_7 0x0316 +#define RT5663_DRC1_PRI_REG_8 0x0317 +#define RT5663_ALC_PGA_CTL_1 0x0330 +#define RT5663_ALC_PGA_CTL_2 0x0331 +#define RT5663_ALC_PGA_CTL_3 0x0332 +#define RT5663_ALC_PGA_CTL_4 0x0333 +#define RT5663_ALC_PGA_CTL_5 0x0334 +#define RT5663_ALC_PGA_CTL_6 0x0335 +#define RT5663_ALC_PGA_CTL_7 0x0336 +#define RT5663_ALC_PGA_CTL_8 0x0337 +#define RT5663_ALC_PGA_REG_1 0x0338 +#define RT5663_ALC_PGA_REG_2 0x0339 +#define RT5663_ALC_PGA_REG_3 0x033a +#define RT5663_ADC_EQ_RECOV_1 0x03c0 +#define RT5663_ADC_EQ_RECOV_2 0x03c1 +#define RT5663_ADC_EQ_RECOV_3 0x03c2 +#define RT5663_ADC_EQ_RECOV_4 0x03c3 +#define RT5663_ADC_EQ_RECOV_5 0x03c4 +#define RT5663_ADC_EQ_RECOV_6 0x03c5 +#define RT5663_ADC_EQ_RECOV_7 0x03c6 +#define RT5663_ADC_EQ_RECOV_8 0x03c7 +#define RT5663_ADC_EQ_RECOV_9 0x03c8 +#define RT5663_ADC_EQ_RECOV_10 0x03c9 +#define RT5663_ADC_EQ_RECOV_11 0x03ca +#define RT5663_ADC_EQ_RECOV_12 0x03cb +#define RT5663_ADC_EQ_RECOV_13 0x03cc +#define RT5663_VID_HIDDEN 0x03fe +#define RT5663_VID_CUSTOMER 0x03ff +#define RT5663_SCAN_MODE 0x07f0 +#define RT5663_I2C_BYPA 0x07fa + +/* Headphone Amp Control 2 (0x0003) */ +#define RT5663_EN_DAC_HPO_MASK (0x1 << 14) +#define RT5663_EN_DAC_HPO_SHIFT 14 +#define RT5663_EN_DAC_HPO_DIS (0x0 << 14) +#define RT5663_EN_DAC_HPO_EN (0x1 << 14) + +/*Headphone Amp L/R Analog Gain and Digital NG2 Gain Control (0x0005 0x0006)*/ +#define RT5663_GAIN_HP (0x1f << 8) +#define RT5663_GAIN_HP_SHIFT 8 + +/* AEC BST Control (0x000b) */ +#define RT5663_GAIN_CBJ_MASK (0xf << 8) +#define RT5663_GAIN_CBJ_SHIFT 8 + +/* IN1 Control / MIC GND REF (0x000c) */ +#define RT5663_IN1_DF_MASK (0x1 << 15) +#define RT5663_IN1_DF_SHIFT 15 + +/* Combo Jack and Type Detection Control 1 (0x0010) */ +#define RT5663_CBJ_DET_MASK (0x1 << 15) +#define RT5663_CBJ_DET_SHIFT 15 +#define RT5663_CBJ_DET_DIS (0x0 << 15) +#define RT5663_CBJ_DET_EN (0x1 << 15) +#define RT5663_DET_TYPE_MASK (0x1 << 12) +#define RT5663_DET_TYPE_SHIFT 12 +#define RT5663_DET_TYPE_WLCSP (0x0 << 12) +#define RT5663_DET_TYPE_QFN (0x1 << 12) +#define RT5663_VREF_BIAS_MASK (0x1 << 6) +#define RT5663_VREF_BIAS_SHIFT 6 +#define RT5663_VREF_BIAS_FSM (0x0 << 6) +#define RT5663_VREF_BIAS_REG (0x1 << 6) + +/* REC Left Mixer Control 2 (0x003c) */ +#define RT5663_RECMIX1L_BST1_CBJ (0x1 << 7) +#define RT5663_RECMIX1L_BST1_CBJ_SHIFT 7 +#define RT5663_RECMIX1L_BST2 (0x1 << 4) +#define RT5663_RECMIX1L_BST2_SHIFT 4 + +/* REC Right Mixer Control 2 (0x003e) */ +#define RT5663_RECMIX1R_BST2 (0x1 << 4) +#define RT5663_RECMIX1R_BST2_SHIFT 4 + +/* DAC1 Digital Volume (0x0019) */ +#define RT5663_DAC_L1_VOL_MASK (0xff << 8) +#define RT5663_DAC_L1_VOL_SHIFT 8 +#define RT5663_DAC_R1_VOL_MASK (0xff) +#define RT5663_DAC_R1_VOL_SHIFT 0 + +/* ADC Digital Volume Control (0x001c) */ +#define RT5663_ADC_L_MUTE_MASK (0x1 << 15) +#define RT5663_ADC_L_MUTE_SHIFT 15 +#define RT5663_ADC_L_VOL_MASK (0x7f << 8) +#define RT5663_ADC_L_VOL_SHIFT 8 +#define RT5663_ADC_R_MUTE_MASK (0x1 << 7) +#define RT5663_ADC_R_MUTE_SHIFT 7 +#define RT5663_ADC_R_VOL_MASK (0x7f) +#define RT5663_ADC_R_VOL_SHIFT 0 + +/* Stereo ADC Mixer Control (0x0026) */ +#define RT5663_M_STO1_ADC_L1 (0x1 << 15) +#define RT5663_M_STO1_ADC_L1_SHIFT 15 +#define RT5663_M_STO1_ADC_L2 (0x1 << 14) +#define RT5663_M_STO1_ADC_L2_SHIFT 14 +#define RT5663_STO1_ADC_L1_SRC (0x1 << 13) +#define RT5663_STO1_ADC_L1_SRC_SHIFT 13 +#define RT5663_STO1_ADC_L2_SRC (0x1 << 12) +#define RT5663_STO1_ADC_L2_SRC_SHIFT 12 +#define RT5663_STO1_ADC_L_SRC (0x3 << 10) +#define RT5663_STO1_ADC_L_SRC_SHIFT 10 +#define RT5663_M_STO1_ADC_R1 (0x1 << 7) +#define RT5663_M_STO1_ADC_R1_SHIFT 7 +#define RT5663_M_STO1_ADC_R2 (0x1 << 6) +#define RT5663_M_STO1_ADC_R2_SHIFT 6 +#define RT5663_STO1_ADC_R1_SRC (0x1 << 5) +#define RT5663_STO1_ADC_R1_SRC_SHIFT 5 +#define RT5663_STO1_ADC_R2_SRC (0x1 << 4) +#define RT5663_STO1_ADC_R2_SRC_SHIFT 4 +#define RT5663_STO1_ADC_R_SRC (0x3 << 2) +#define RT5663_STO1_ADC_R_SRC_SHIFT 2 + +/* ADC Mixer to DAC Mixer Control (0x0029) */ +#define RT5663_M_ADCMIX_L (0x1 << 15) +#define RT5663_M_ADCMIX_L_SHIFT 15 +#define RT5663_M_DAC1_L (0x1 << 14) +#define RT5663_M_DAC1_L_SHIFT 14 +#define RT5663_M_ADCMIX_R (0x1 << 7) +#define RT5663_M_ADCMIX_R_SHIFT 7 +#define RT5663_M_DAC1_R (0x1 << 6) +#define RT5663_M_DAC1_R_SHIFT 6 + +/* Stereo DAC Mixer Control (0x002a) */ +#define RT5663_M_DAC_L1_STO_L (0x1 << 15) +#define RT5663_M_DAC_L1_STO_L_SHIFT 15 +#define RT5663_M_DAC_R1_STO_L (0x1 << 13) +#define RT5663_M_DAC_R1_STO_L_SHIFT 13 +#define RT5663_M_DAC_L1_STO_R (0x1 << 7) +#define RT5663_M_DAC_L1_STO_R_SHIFT 7 +#define RT5663_M_DAC_R1_STO_R (0x1 << 5) +#define RT5663_M_DAC_R1_STO_R_SHIFT 5 + +/* Power Management for Digital 1 (0x0061) */ +#define RT5663_PWR_I2S1 (0x1 << 15) +#define RT5663_PWR_I2S1_SHIFT 15 +#define RT5663_PWR_DAC_L1 (0x1 << 11) +#define RT5663_PWR_DAC_L1_SHIFT 11 +#define RT5663_PWR_DAC_R1 (0x1 << 10) +#define RT5663_PWR_DAC_R1_SHIFT 10 +#define RT5663_PWR_LDO_DACREF_MASK (0x1 << 8) +#define RT5663_PWR_LDO_DACREF_SHIFT 8 +#define RT5663_PWR_LDO_DACREF_ON (0x1 << 8) +#define RT5663_PWR_LDO_DACREF_DOWN (0x0 << 8) +#define RT5663_PWR_LDO_SHIFT 8 +#define RT5663_PWR_ADC_L1 (0x1 << 4) +#define RT5663_PWR_ADC_L1_SHIFT 4 +#define RT5663_PWR_ADC_R1 (0x1 << 3) +#define RT5663_PWR_ADC_R1_SHIFT 3 + +/* Power Management for Digital 2 (0x0062) */ +#define RT5663_PWR_ADC_S1F (0x1 << 15) +#define RT5663_PWR_ADC_S1F_SHIFT 15 +#define RT5663_PWR_DAC_S1F (0x1 << 10) +#define RT5663_PWR_DAC_S1F_SHIFT 10 + +/* Power Management for Analog 1 (0x0063) */ +#define RT5663_PWR_VREF1 (0x1 << 15) +#define RT5663_PWR_VREF1_MASK (0x1 << 15) +#define RT5663_PWR_VREF1_SHIFT 15 +#define RT5663_PWR_FV1 (0x1 << 14) +#define RT5663_PWR_FV1_MASK (0x1 << 14) +#define RT5663_PWR_FV1_SHIFT 14 +#define RT5663_PWR_VREF2 (0x1 << 13) +#define RT5663_PWR_VREF2_MASK (0x1 << 13) +#define RT5663_PWR_VREF2_SHIFT 13 +#define RT5663_PWR_FV2 (0x1 << 12) +#define RT5663_PWR_FV2_MASK (0x1 << 12) +#define RT5663_PWR_FV2_SHIFT 12 +#define RT5663_PWR_MB (0x1 << 9) +#define RT5663_PWR_MB_MASK (0x1 << 9) +#define RT5663_PWR_MB_SHIFT 9 +#define RT5663_AMP_HP_MASK (0x3 << 2) +#define RT5663_AMP_HP_SHIFT 2 +#define RT5663_AMP_HP_1X (0x0 << 2) +#define RT5663_AMP_HP_3X (0x1 << 2) +#define RT5663_AMP_HP_5X (0x3 << 2) +#define RT5663_LDO1_DVO_MASK (0x3) +#define RT5663_LDO1_DVO_SHIFT 0 +#define RT5663_LDO1_DVO_0_9V (0x0) +#define RT5663_LDO1_DVO_1_0V (0x1) +#define RT5663_LDO1_DVO_1_2V (0x2) +#define RT5663_LDO1_DVO_1_4V (0x3) + +/* Power Management for Analog 2 (0x0064) */ +#define RT5663_PWR_BST1 (0x1 << 15) +#define RT5663_PWR_BST1_MASK (0x1 << 15) +#define RT5663_PWR_BST1_SHIFT 15 +#define RT5663_PWR_BST1_OFF (0x0 << 15) +#define RT5663_PWR_BST1_ON (0x1 << 15) +#define RT5663_PWR_BST2 (0x1 << 14) +#define RT5663_PWR_BST2_MASK (0x1 << 14) +#define RT5663_PWR_BST2_SHIFT 14 +#define RT5663_PWR_MB1 (0x1 << 11) +#define RT5663_PWR_MB1_SHIFT 11 +#define RT5663_PWR_MB2 (0x1 << 10) +#define RT5663_PWR_MB2_SHIFT 10 +#define RT5663_PWR_BST2_OP (0x1 << 6) +#define RT5663_PWR_BST2_OP_MASK (0x1 << 6) +#define RT5663_PWR_BST2_OP_SHIFT 6 +#define RT5663_PWR_JD1 (0x1 << 3) +#define RT5663_PWR_JD1_MASK (0x1 << 3) +#define RT5663_PWR_JD1_SHIFT 3 +#define RT5663_PWR_JD2 (0x1 << 2) +#define RT5663_PWR_JD2_MASK (0x1 << 2) +#define RT5663_PWR_JD2_SHIFT 2 +#define RT5663_PWR_RECMIX1 (0x1 << 1) +#define RT5663_PWR_RECMIX1_SHIFT 1 +#define RT5663_PWR_RECMIX2 (0x1) +#define RT5663_PWR_RECMIX2_SHIFT 0 + +/* Power Management for Analog 3 (0x0065) */ +#define RT5663_PWR_CBJ_MASK (0x1 << 9) +#define RT5663_PWR_CBJ_SHIFT 9 +#define RT5663_PWR_CBJ_OFF (0x0 << 9) +#define RT5663_PWR_CBJ_ON (0x1 << 9) +#define RT5663_PWR_PLL (0x1 << 6) +#define RT5663_PWR_PLL_SHIFT 6 +#define RT5663_PWR_LDO2 (0x1 << 2) +#define RT5663_PWR_LDO2_SHIFT 2 + +/* Power Management for Volume (0x0067) */ +#define RT5663_V2_PWR_MIC_DET (0x1 << 5) +#define RT5663_V2_PWR_MIC_DET_SHIFT 5 + +/* MCLK and System Clock Detection Control (0x006b) */ +#define RT5663_EN_ANA_CLK_DET_MASK (0x1 << 15) +#define RT5663_EN_ANA_CLK_DET_SHIFT 15 +#define RT5663_EN_ANA_CLK_DET_DIS (0x0 << 15) +#define RT5663_EN_ANA_CLK_DET_AUTO (0x1 << 15) +#define RT5663_PWR_CLK_DET_MASK (0x1) +#define RT5663_PWR_CLK_DET_SHIFT 0 +#define RT5663_PWR_CLK_DET_DIS (0x0) +#define RT5663_PWR_CLK_DET_EN (0x1) + +/* I2S1 Audio Serial Data Port Control (0x0070) */ +#define RT5663_I2S_MS_MASK (0x1 << 15) +#define RT5663_I2S_MS_SHIFT 15 +#define RT5663_I2S_MS_M (0x0 << 15) +#define RT5663_I2S_MS_S (0x1 << 15) +#define RT5663_I2S_BP_MASK (0x1 << 8) +#define RT5663_I2S_BP_SHIFT 8 +#define RT5663_I2S_BP_NOR (0x0 << 8) +#define RT5663_I2S_BP_INV (0x1 << 8) +#define RT5663_I2S_DL_MASK (0x3 << 4) +#define RT5663_I2S_DL_SHIFT 4 +#define RT5663_I2S_DL_16 (0x0 << 4) +#define RT5663_I2S_DL_20 (0x1 << 4) +#define RT5663_I2S_DL_24 (0x2 << 4) +#define RT5663_I2S_DL_8 (0x3 << 4) +#define RT5663_I2S_DF_MASK (0x7) +#define RT5663_I2S_DF_SHIFT 0 +#define RT5663_I2S_DF_I2S (0x0) +#define RT5663_I2S_DF_LEFT (0x1) +#define RT5663_I2S_DF_PCM_A (0x2) +#define RT5663_I2S_DF_PCM_B (0x3) +#define RT5663_I2S_DF_PCM_A_N (0x6) +#define RT5663_I2S_DF_PCM_B_N (0x7) + +/* ADC/DAC Clock Control 1 (0x0073) */ +#define RT5663_I2S_PD1_MASK (0x7 << 12) +#define RT5663_I2S_PD1_SHIFT 12 +#define RT5663_M_I2S_DIV_MASK (0x7 << 8) +#define RT5663_M_I2S_DIV_SHIFT 8 +#define RT5663_CLK_SRC_MASK (0x3 << 4) +#define RT5663_CLK_SRC_MCLK (0x0 << 4) +#define RT5663_CLK_SRC_PLL_OUT (0x1 << 4) +#define RT5663_CLK_SRC_DIV (0x2 << 4) +#define RT5663_CLK_SRC_RC (0x3 << 4) +#define RT5663_DAC_OSR_MASK (0x3 << 2) +#define RT5663_DAC_OSR_SHIFT 2 +#define RT5663_DAC_OSR_128 (0x0 << 2) +#define RT5663_DAC_OSR_64 (0x1 << 2) +#define RT5663_DAC_OSR_32 (0x2 << 2) +#define RT5663_ADC_OSR_MASK (0x3) +#define RT5663_ADC_OSR_SHIFT 0 +#define RT5663_ADC_OSR_128 (0x0) +#define RT5663_ADC_OSR_64 (0x1) +#define RT5663_ADC_OSR_32 (0x2) + +/* TDM1 control 1 (0x0078) */ +#define RT5663_TDM_MODE_MASK (0x1 << 15) +#define RT5663_TDM_MODE_SHIFT 15 +#define RT5663_TDM_MODE_I2S (0x0 << 15) +#define RT5663_TDM_MODE_TDM (0x1 << 15) +#define RT5663_TDM_IN_CH_MASK (0x3 << 10) +#define RT5663_TDM_IN_CH_SHIFT 10 +#define RT5663_TDM_IN_CH_2 (0x0 << 10) +#define RT5663_TDM_IN_CH_4 (0x1 << 10) +#define RT5663_TDM_IN_CH_6 (0x2 << 10) +#define RT5663_TDM_IN_CH_8 (0x3 << 10) +#define RT5663_TDM_OUT_CH_MASK (0x3 << 8) +#define RT5663_TDM_OUT_CH_SHIFT 8 +#define RT5663_TDM_OUT_CH_2 (0x0 << 8) +#define RT5663_TDM_OUT_CH_4 (0x1 << 8) +#define RT5663_TDM_OUT_CH_6 (0x2 << 8) +#define RT5663_TDM_OUT_CH_8 (0x3 << 8) +#define RT5663_TDM_IN_LEN_MASK (0x3 << 6) +#define RT5663_TDM_IN_LEN_SHIFT 6 +#define RT5663_TDM_IN_LEN_16 (0x0 << 6) +#define RT5663_TDM_IN_LEN_20 (0x1 << 6) +#define RT5663_TDM_IN_LEN_24 (0x2 << 6) +#define RT5663_TDM_IN_LEN_32 (0x3 << 6) +#define RT5663_TDM_OUT_LEN_MASK (0x3 << 4) +#define RT5663_TDM_OUT_LEN_SHIFT 4 +#define RT5663_TDM_OUT_LEN_16 (0x0 << 4) +#define RT5663_TDM_OUT_LEN_20 (0x1 << 4) +#define RT5663_TDM_OUT_LEN_24 (0x2 << 4) +#define RT5663_TDM_OUT_LEN_32 (0x3 << 4) + +/* Global Clock Control (0x0080) */ +#define RT5663_SCLK_SRC_MASK (0x3 << 14) +#define RT5663_SCLK_SRC_SHIFT 14 +#define RT5663_SCLK_SRC_MCLK (0x0 << 14) +#define RT5663_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5663_SCLK_SRC_RCCLK (0x2 << 14) +#define RT5663_PLL1_SRC_MASK (0x7 << 11) +#define RT5663_PLL1_SRC_SHIFT 11 +#define RT5663_PLL1_SRC_MCLK (0x0 << 11) +#define RT5663_PLL1_SRC_BCLK1 (0x1 << 11) +#define RT5663_V2_PLL1_SRC_MASK (0x7 << 8) +#define RT5663_V2_PLL1_SRC_SHIFT 8 +#define RT5663_V2_PLL1_SRC_MCLK (0x0 << 8) +#define RT5663_V2_PLL1_SRC_BCLK1 (0x1 << 8) +#define RT5663_PLL1_PD_MASK (0x1 << 4) +#define RT5663_PLL1_PD_SHIFT 4 + +#define RT5663_PLL_INP_MAX 40000000 +#define RT5663_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x0081) */ +#define RT5663_PLL_N_MAX 0x001ff +#define RT5663_PLL_N_MASK (RT5663_PLL_N_MAX << 7) +#define RT5663_PLL_N_SHIFT 7 +#define RT5663_PLL_K_MAX 0x001f +#define RT5663_PLL_K_MASK (RT5663_PLL_K_MAX) +#define RT5663_PLL_K_SHIFT 0 + +/* PLL M/N/K Code Control 2 (0x0082) */ +#define RT5663_PLL_M_MAX 0x00f +#define RT5663_PLL_M_MASK (RT5663_PLL_M_MAX << 12) +#define RT5663_PLL_M_SHIFT 12 +#define RT5663_PLL_M_BP (0x1 << 11) +#define RT5663_PLL_M_BP_SHIFT 11 + +/* PLL tracking mode 1 (0x0083) */ +#define RT5663_V2_I2S1_ASRC_MASK (0x1 << 13) +#define RT5663_V2_I2S1_ASRC_SHIFT 13 +#define RT5663_V2_DAC_STO1_ASRC_MASK (0x1 << 12) +#define RT5663_V2_DAC_STO1_ASRC_SHIFT 12 +#define RT5663_V2_ADC_STO1_ASRC_MASK (0x1 << 4) +#define RT5663_V2_ADC_STO1_ASRC_SHIFT 4 + +/* PLL tracking mode 2 (0x0084)*/ +#define RT5663_DA_STO1_TRACK_MASK (0x7 << 12) +#define RT5663_DA_STO1_TRACK_SHIFT 12 +#define RT5663_DA_STO1_TRACK_SYSCLK (0x0 << 12) +#define RT5663_DA_STO1_TRACK_I2S1 (0x1 << 12) + +/* PLL tracking mode 3 (0x0085)*/ +#define RT5663_V2_AD_STO1_TRACK_MASK (0x7 << 12) +#define RT5663_V2_AD_STO1_TRACK_SHIFT 12 +#define RT5663_V2_AD_STO1_TRACK_SYSCLK (0x0 << 12) +#define RT5663_V2_AD_STO1_TRACK_I2S1 (0x1 << 12) + +/* HPOUT Charge pump control 1 (0x0091) */ +#define RT5663_OSW_HP_L_MASK (0x1 << 11) +#define RT5663_OSW_HP_L_SHIFT 11 +#define RT5663_OSW_HP_L_EN (0x1 << 11) +#define RT5663_OSW_HP_L_DIS (0x0 << 11) +#define RT5663_OSW_HP_R_MASK (0x1 << 10) +#define RT5663_OSW_HP_R_SHIFT 10 +#define RT5663_OSW_HP_R_EN (0x1 << 10) +#define RT5663_OSW_HP_R_DIS (0x0 << 10) +#define RT5663_SEL_PM_HP_MASK (0x3 << 8) +#define RT5663_SEL_PM_HP_SHIFT 8 +#define RT5663_SEL_PM_HP_0_6 (0x0 << 8) +#define RT5663_SEL_PM_HP_0_9 (0x1 << 8) +#define RT5663_SEL_PM_HP_1_8 (0x2 << 8) +#define RT5663_SEL_PM_HP_HIGH (0x3 << 8) +#define RT5663_OVCD_HP_MASK (0x1 << 2) +#define RT5663_OVCD_HP_SHIFT 2 +#define RT5663_OVCD_HP_EN (0x1 << 2) +#define RT5663_OVCD_HP_DIS (0x0 << 2) + +/* RC Clock Control (0x0094) */ +#define RT5663_DIG_25M_CLK_MASK (0x1 << 9) +#define RT5663_DIG_25M_CLK_SHIFT 9 +#define RT5663_DIG_25M_CLK_DIS (0x0 << 9) +#define RT5663_DIG_25M_CLK_EN (0x1 << 9) +#define RT5663_DIG_1M_CLK_MASK (0x1 << 8) +#define RT5663_DIG_1M_CLK_SHIFT 8 +#define RT5663_DIG_1M_CLK_DIS (0x0 << 8) +#define RT5663_DIG_1M_CLK_EN (0x1 << 8) + +/* Auto Turn On 1M RC CLK (0x009f) */ +#define RT5663_IRQ_POW_SAV_MASK (0x1 << 15) +#define RT5663_IRQ_POW_SAV_SHIFT 15 +#define RT5663_IRQ_POW_SAV_DIS (0x0 << 15) +#define RT5663_IRQ_POW_SAV_EN (0x1 << 15) +#define RT5663_IRQ_POW_SAV_JD1_MASK (0x1 << 14) +#define RT5663_IRQ_POW_SAV_JD1_SHIFT 14 +#define RT5663_IRQ_POW_SAV_JD1_DIS (0x0 << 14) +#define RT5663_IRQ_POW_SAV_JD1_EN (0x1 << 14) +#define RT5663_IRQ_MANUAL_MASK (0x1 << 8) +#define RT5663_IRQ_MANUAL_SHIFT 8 +#define RT5663_IRQ_MANUAL_DIS (0x0 << 8) +#define RT5663_IRQ_MANUAL_EN (0x1 << 8) + +/* IRQ Control 1 (0x00b6) */ +#define RT5663_EN_CB_JD_MASK (0x1 << 3) +#define RT5663_EN_CB_JD_SHIFT 3 +#define RT5663_EN_CB_JD_EN (0x1 << 3) +#define RT5663_EN_CB_JD_DIS (0x0 << 3) + +/* IRQ Control 3 (0x00b8) */ +#define RT5663_V2_EN_IRQ_INLINE_MASK (0x1 << 6) +#define RT5663_V2_EN_IRQ_INLINE_SHIFT 6 +#define RT5663_V2_EN_IRQ_INLINE_BYP (0x0 << 6) +#define RT5663_V2_EN_IRQ_INLINE_NOR (0x1 << 6) + +/* GPIO Control 1 (0x00c0) */ +#define RT5663_GP1_PIN_MASK (0x1 << 15) +#define RT5663_GP1_PIN_SHIFT 15 +#define RT5663_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5663_GP1_PIN_IRQ (0x1 << 15) + +/* GPIO Control 2 (0x00c1) */ +#define RT5663_GP4_PIN_CONF_MASK (0x1 << 5) +#define RT5663_GP4_PIN_CONF_SHIFT 5 +#define RT5663_GP4_PIN_CONF_INPUT (0x0 << 5) +#define RT5663_GP4_PIN_CONF_OUTPUT (0x1 << 5) + +/* GPIO Control 2 (0x00c2) */ +#define RT5663_GP8_PIN_CONF_MASK (0x1 << 13) +#define RT5663_GP8_PIN_CONF_SHIFT 13 +#define RT5663_GP8_PIN_CONF_INPUT (0x0 << 13) +#define RT5663_GP8_PIN_CONF_OUTPUT (0x1 << 13) + +/* 4 Buttons Inline Command Function 1 (0x00df) */ +#define RT5663_4BTN_CLK_DEB_MASK (0x3 << 2) +#define RT5663_4BTN_CLK_DEB_SHIFT 2 +#define RT5663_4BTN_CLK_DEB_8MS (0x0 << 2) +#define RT5663_4BTN_CLK_DEB_16MS (0x1 << 2) +#define RT5663_4BTN_CLK_DEB_32MS (0x2 << 2) +#define RT5663_4BTN_CLK_DEB_65MS (0x3 << 2) + +/* Inline Command Function 6 (0x00e0) */ +#define RT5663_EN_4BTN_INL_MASK (0x1 << 15) +#define RT5663_EN_4BTN_INL_SHIFT 15 +#define RT5663_EN_4BTN_INL_DIS (0x0 << 15) +#define RT5663_EN_4BTN_INL_EN (0x1 << 15) +#define RT5663_RESET_4BTN_INL_MASK (0x1 << 14) +#define RT5663_RESET_4BTN_INL_SHIFT 14 +#define RT5663_RESET_4BTN_INL_RESET (0x0 << 14) +#define RT5663_RESET_4BTN_INL_NOR (0x1 << 14) + +/* Digital Misc Control (0x00fa) */ +#define RT5663_DIG_GATE_CTRL_MASK 0x1 +#define RT5663_DIG_GATE_CTRL_SHIFT (0) +#define RT5663_DIG_GATE_CTRL_DIS 0x0 +#define RT5663_DIG_GATE_CTRL_EN 0x1 + +/* Chopper and Clock control for DAC L (0x013a)*/ +#define RT5663_CKXEN_DAC1_MASK (0x1 << 13) +#define RT5663_CKXEN_DAC1_SHIFT 13 +#define RT5663_CKGEN_DAC1_MASK (0x1 << 12) +#define RT5663_CKGEN_DAC1_SHIFT 12 + +/* Chopper and Clock control for ADC (0x013b)*/ +#define RT5663_CKXEN_ADCC_MASK (0x1 << 13) +#define RT5663_CKXEN_ADCC_SHIFT 13 +#define RT5663_CKGEN_ADCC_MASK (0x1 << 12) +#define RT5663_CKGEN_ADCC_SHIFT 12 + +/* HP Behavior Logic Control 2 (0x01db) */ +#define RT5663_HP_SIG_SRC1_MASK (0x3) +#define RT5663_HP_SIG_SRC1_SHIFT 0 +#define RT5663_HP_SIG_SRC1_HP_DC (0x0) +#define RT5663_HP_SIG_SRC1_HP_CALIB (0x1) +#define RT5663_HP_SIG_SRC1_REG (0x2) +#define RT5663_HP_SIG_SRC1_SILENCE (0x3) + +/* RT5663 specific register */ +#define RT5663_HP_OUT_EN 0x0002 +#define RT5663_HP_LCH_DRE 0x0005 +#define RT5663_HP_RCH_DRE 0x0006 +#define RT5663_CALIB_BST 0x000a +#define RT5663_RECMIX 0x0010 +#define RT5663_SIL_DET_CTL 0x0015 +#define RT5663_PWR_SAV_SILDET 0x0016 +#define RT5663_SIDETONE_CTL 0x0018 +#define RT5663_STO1_DAC_DIG_VOL 0x0019 +#define RT5663_STO1_ADC_DIG_VOL 0x001c +#define RT5663_STO1_BOOST 0x001f +#define RT5663_HP_IMP_GAIN_1 0x0022 +#define RT5663_HP_IMP_GAIN_2 0x0023 +#define RT5663_STO1_ADC_MIXER 0x0026 +#define RT5663_AD_DA_MIXER 0x0029 +#define RT5663_STO_DAC_MIXER 0x002a +#define RT5663_DIG_SIDE_MIXER 0x002c +#define RT5663_BYPASS_STO_DAC 0x002d +#define RT5663_CALIB_REC_MIX 0x0040 +#define RT5663_PWR_DIG_1 0x0061 +#define RT5663_PWR_DIG_2 0x0062 +#define RT5663_PWR_ANLG_1 0x0063 +#define RT5663_PWR_ANLG_2 0x0064 +#define RT5663_PWR_ANLG_3 0x0065 +#define RT5663_PWR_MIXER 0x0066 +#define RT5663_SIG_CLK_DET 0x006b +#define RT5663_PRE_DIV_GATING_1 0x006e +#define RT5663_PRE_DIV_GATING_2 0x006f +#define RT5663_I2S1_SDP 0x0070 +#define RT5663_ADDA_CLK_1 0x0073 +#define RT5663_ADDA_RST 0x0074 +#define RT5663_FRAC_DIV_1 0x0075 +#define RT5663_FRAC_DIV_2 0x0076 +#define RT5663_TDM_1 0x0077 +#define RT5663_TDM_2 0x0078 +#define RT5663_TDM_3 0x0079 +#define RT5663_TDM_4 0x007a +#define RT5663_TDM_5 0x007b +#define RT5663_TDM_6 0x007c +#define RT5663_TDM_7 0x007d +#define RT5663_TDM_8 0x007e +#define RT5663_TDM_9 0x007f +#define RT5663_GLB_CLK 0x0080 +#define RT5663_PLL_1 0x0081 +#define RT5663_PLL_2 0x0082 +#define RT5663_ASRC_1 0x0083 +#define RT5663_ASRC_2 0x0084 +#define RT5663_ASRC_4 0x0086 +#define RT5663_DUMMY_REG 0x0087 +#define RT5663_ASRC_8 0x008a +#define RT5663_ASRC_9 0x008b +#define RT5663_ASRC_11 0x008c +#define RT5663_DEPOP_1 0x008e +#define RT5663_DEPOP_2 0x008f +#define RT5663_DEPOP_3 0x0090 +#define RT5663_HP_CHARGE_PUMP_1 0x0091 +#define RT5663_HP_CHARGE_PUMP_2 0x0092 +#define RT5663_MICBIAS_1 0x0093 +#define RT5663_RC_CLK 0x0094 +#define RT5663_ASRC_11_2 0x0097 +#define RT5663_DUMMY_REG_2 0x0098 +#define RT5663_REC_PATH_GAIN 0x009a +#define RT5663_AUTO_1MRC_CLK 0x009f +#define RT5663_ADC_EQ_1 0x00ae +#define RT5663_ADC_EQ_2 0x00af +#define RT5663_IRQ_1 0x00b6 +#define RT5663_IRQ_2 0x00b7 +#define RT5663_IRQ_3 0x00b8 +#define RT5663_IRQ_4 0x00ba +#define RT5663_IRQ_5 0x00bb +#define RT5663_INT_ST_1 0x00be +#define RT5663_INT_ST_2 0x00bf +#define RT5663_GPIO_1 0x00c0 +#define RT5663_GPIO_2 0x00c1 +#define RT5663_GPIO_STA1 0x00c5 +#define RT5663_SIN_GEN_1 0x00cb +#define RT5663_SIN_GEN_2 0x00cc +#define RT5663_SIN_GEN_3 0x00cd +#define RT5663_SOF_VOL_ZC1 0x00d9 +#define RT5663_IL_CMD_1 0x00db +#define RT5663_IL_CMD_2 0x00dc +#define RT5663_IL_CMD_3 0x00dd +#define RT5663_IL_CMD_4 0x00de +#define RT5663_IL_CMD_5 0x00df +#define RT5663_IL_CMD_6 0x00e0 +#define RT5663_IL_CMD_7 0x00e1 +#define RT5663_IL_CMD_8 0x00e2 +#define RT5663_IL_CMD_PWRSAV1 0x00e4 +#define RT5663_IL_CMD_PWRSAV2 0x00e5 +#define RT5663_EM_JACK_TYPE_1 0x00e6 +#define RT5663_EM_JACK_TYPE_2 0x00e7 +#define RT5663_EM_JACK_TYPE_3 0x00e8 +#define RT5663_EM_JACK_TYPE_4 0x00e9 +#define RT5663_EM_JACK_TYPE_5 0x00ea +#define RT5663_EM_JACK_TYPE_6 0x00eb +#define RT5663_STO1_HPF_ADJ1 0x00ec +#define RT5663_STO1_HPF_ADJ2 0x00ed +#define RT5663_FAST_OFF_MICBIAS 0x00f4 +#define RT5663_JD_CTRL1 0x00f6 +#define RT5663_JD_CTRL2 0x00f8 +#define RT5663_DIG_MISC 0x00fa +#define RT5663_DIG_VOL_ZCD 0x0100 +#define RT5663_ANA_BIAS_CUR_1 0x0108 +#define RT5663_ANA_BIAS_CUR_2 0x0109 +#define RT5663_ANA_BIAS_CUR_3 0x010a +#define RT5663_ANA_BIAS_CUR_4 0x010b +#define RT5663_ANA_BIAS_CUR_5 0x010c +#define RT5663_ANA_BIAS_CUR_6 0x010d +#define RT5663_BIAS_CUR_5 0x010e +#define RT5663_BIAS_CUR_6 0x010f +#define RT5663_BIAS_CUR_7 0x0110 +#define RT5663_BIAS_CUR_8 0x0111 +#define RT5663_DACREF_LDO 0x0112 +#define RT5663_DUMMY_REG_3 0x0113 +#define RT5663_BIAS_CUR_9 0x0114 +#define RT5663_DUMMY_REG_4 0x0116 +#define RT5663_VREFADJ_OP 0x0117 +#define RT5663_VREF_RECMIX 0x0118 +#define RT5663_CHARGE_PUMP_1 0x0125 +#define RT5663_CHARGE_PUMP_1_2 0x0126 +#define RT5663_CHARGE_PUMP_1_3 0x0127 +#define RT5663_CHARGE_PUMP_2 0x0128 +#define RT5663_DIG_IN_PIN1 0x0132 +#define RT5663_PAD_DRV_CTL 0x0137 +#define RT5663_PLL_INT_REG 0x0139 +#define RT5663_CHOP_DAC_L 0x013a +#define RT5663_CHOP_ADC 0x013b +#define RT5663_CALIB_ADC 0x013c +#define RT5663_CHOP_DAC_R 0x013d +#define RT5663_DUMMY_CTL_DACLR 0x013e +#define RT5663_DUMMY_REG_5 0x0140 +#define RT5663_SOFT_RAMP 0x0141 +#define RT5663_TEST_MODE_1 0x0144 +#define RT5663_TEST_MODE_2 0x0145 +#define RT5663_TEST_MODE_3 0x0146 +#define RT5663_TEST_MODE_4 0x0147 +#define RT5663_TEST_MODE_5 0x0148 +#define RT5663_STO_DRE_1 0x0160 +#define RT5663_STO_DRE_2 0x0161 +#define RT5663_STO_DRE_3 0x0162 +#define RT5663_STO_DRE_4 0x0163 +#define RT5663_STO_DRE_5 0x0164 +#define RT5663_STO_DRE_6 0x0165 +#define RT5663_STO_DRE_7 0x0166 +#define RT5663_STO_DRE_8 0x0167 +#define RT5663_STO_DRE_9 0x0168 +#define RT5663_STO_DRE_10 0x0169 +#define RT5663_MIC_DECRO_1 0x0180 +#define RT5663_MIC_DECRO_2 0x0181 +#define RT5663_MIC_DECRO_3 0x0182 +#define RT5663_MIC_DECRO_4 0x0183 +#define RT5663_MIC_DECRO_5 0x0184 +#define RT5663_MIC_DECRO_6 0x0185 +#define RT5663_HP_DECRO_1 0x01b0 +#define RT5663_HP_DECRO_2 0x01b1 +#define RT5663_HP_DECRO_3 0x01b2 +#define RT5663_HP_DECRO_4 0x01b3 +#define RT5663_HP_DECOUP 0x01b4 +#define RT5663_HP_IMP_SEN_MAP8 0x01b5 +#define RT5663_HP_IMP_SEN_MAP9 0x01b6 +#define RT5663_HP_IMP_SEN_MAP10 0x01b7 +#define RT5663_HP_IMP_SEN_MAP11 0x01b8 +#define RT5663_HP_IMP_SEN_1 0x01c0 +#define RT5663_HP_IMP_SEN_2 0x01c1 +#define RT5663_HP_IMP_SEN_3 0x01c2 +#define RT5663_HP_IMP_SEN_4 0x01c3 +#define RT5663_HP_IMP_SEN_5 0x01c4 +#define RT5663_HP_IMP_SEN_6 0x01c5 +#define RT5663_HP_IMP_SEN_7 0x01c6 +#define RT5663_HP_IMP_SEN_8 0x01c7 +#define RT5663_HP_IMP_SEN_9 0x01c8 +#define RT5663_HP_IMP_SEN_10 0x01c9 +#define RT5663_HP_IMP_SEN_11 0x01ca +#define RT5663_HP_IMP_SEN_12 0x01cb +#define RT5663_HP_IMP_SEN_13 0x01cc +#define RT5663_HP_IMP_SEN_14 0x01cd +#define RT5663_HP_IMP_SEN_15 0x01ce +#define RT5663_HP_IMP_SEN_16 0x01cf +#define RT5663_HP_IMP_SEN_17 0x01d0 +#define RT5663_HP_IMP_SEN_18 0x01d1 +#define RT5663_HP_IMP_SEN_19 0x01d2 +#define RT5663_HP_IMPSEN_DIG5 0x01d3 +#define RT5663_HP_IMPSEN_MAP1 0x01d4 +#define RT5663_HP_IMPSEN_MAP2 0x01d5 +#define RT5663_HP_IMPSEN_MAP3 0x01d6 +#define RT5663_HP_IMPSEN_MAP4 0x01d7 +#define RT5663_HP_IMPSEN_MAP5 0x01d8 +#define RT5663_HP_IMPSEN_MAP7 0x01d9 +#define RT5663_HP_LOGIC_1 0x01da +#define RT5663_HP_LOGIC_2 0x01db +#define RT5663_HP_CALIB_1 0x01dd +#define RT5663_HP_CALIB_1_1 0x01de +#define RT5663_HP_CALIB_2 0x01df +#define RT5663_HP_CALIB_3 0x01e0 +#define RT5663_HP_CALIB_4 0x01e1 +#define RT5663_HP_CALIB_5 0x01e2 +#define RT5663_HP_CALIB_5_1 0x01e3 +#define RT5663_HP_CALIB_6 0x01e4 +#define RT5663_HP_CALIB_7 0x01e5 +#define RT5663_HP_CALIB_9 0x01e6 +#define RT5663_HP_CALIB_10 0x01e7 +#define RT5663_HP_CALIB_11 0x01e8 +#define RT5663_HP_CALIB_ST1 0x01ea +#define RT5663_HP_CALIB_ST2 0x01eb +#define RT5663_HP_CALIB_ST3 0x01ec +#define RT5663_HP_CALIB_ST4 0x01ed +#define RT5663_HP_CALIB_ST5 0x01ee +#define RT5663_HP_CALIB_ST6 0x01ef +#define RT5663_HP_CALIB_ST7 0x01f0 +#define RT5663_HP_CALIB_ST8 0x01f1 +#define RT5663_HP_CALIB_ST9 0x01f2 +#define RT5663_HP_AMP_DET 0x0200 +#define RT5663_DUMMY_REG_6 0x0201 +#define RT5663_HP_BIAS 0x0202 +#define RT5663_CBJ_1 0x0250 +#define RT5663_CBJ_2 0x0251 +#define RT5663_CBJ_3 0x0252 +#define RT5663_DUMMY_1 0x02fa +#define RT5663_DUMMY_2 0x02fb +#define RT5663_DUMMY_3 0x02fc +#define RT5663_ANA_JD 0x0300 +#define RT5663_ADC_LCH_LPF1_A1 0x03d0 +#define RT5663_ADC_RCH_LPF1_A1 0x03d1 +#define RT5663_ADC_LCH_LPF1_H0 0x03d2 +#define RT5663_ADC_RCH_LPF1_H0 0x03d3 +#define RT5663_ADC_LCH_BPF1_A1 0x03d4 +#define RT5663_ADC_RCH_BPF1_A1 0x03d5 +#define RT5663_ADC_LCH_BPF1_A2 0x03d6 +#define RT5663_ADC_RCH_BPF1_A2 0x03d7 +#define RT5663_ADC_LCH_BPF1_H0 0x03d8 +#define RT5663_ADC_RCH_BPF1_H0 0x03d9 +#define RT5663_ADC_LCH_BPF2_A1 0x03da +#define RT5663_ADC_RCH_BPF2_A1 0x03db +#define RT5663_ADC_LCH_BPF2_A2 0x03dc +#define RT5663_ADC_RCH_BPF2_A2 0x03dd +#define RT5663_ADC_LCH_BPF2_H0 0x03de +#define RT5663_ADC_RCH_BPF2_H0 0x03df +#define RT5663_ADC_LCH_BPF3_A1 0x03e0 +#define RT5663_ADC_RCH_BPF3_A1 0x03e1 +#define RT5663_ADC_LCH_BPF3_A2 0x03e2 +#define RT5663_ADC_RCH_BPF3_A2 0x03e3 +#define RT5663_ADC_LCH_BPF3_H0 0x03e4 +#define RT5663_ADC_RCH_BPF3_H0 0x03e5 +#define RT5663_ADC_LCH_BPF4_A1 0x03e6 +#define RT5663_ADC_RCH_BPF4_A1 0x03e7 +#define RT5663_ADC_LCH_BPF4_A2 0x03e8 +#define RT5663_ADC_RCH_BPF4_A2 0x03e9 +#define RT5663_ADC_LCH_BPF4_H0 0x03ea +#define RT5663_ADC_RCH_BPF4_H0 0x03eb +#define RT5663_ADC_LCH_HPF1_A1 0x03ec +#define RT5663_ADC_RCH_HPF1_A1 0x03ed +#define RT5663_ADC_LCH_HPF1_H0 0x03ee +#define RT5663_ADC_RCH_HPF1_H0 0x03ef +#define RT5663_ADC_EQ_PRE_VOL_L 0x03f0 +#define RT5663_ADC_EQ_PRE_VOL_R 0x03f1 +#define RT5663_ADC_EQ_POST_VOL_L 0x03f2 +#define RT5663_ADC_EQ_POST_VOL_R 0x03f3 + +/* RECMIX Control (0x0010) */ +#define RT5663_RECMIX1_BST1_MASK (0x1) +#define RT5663_RECMIX1_BST1_SHIFT 0 +#define RT5663_RECMIX1_BST1_ON (0x0) +#define RT5663_RECMIX1_BST1_OFF (0x1) + +/* Bypass Stereo1 DAC Mixer Control (0x002d) */ +#define RT5663_DACL1_SRC_MASK (0x1 << 3) +#define RT5663_DACL1_SRC_SHIFT 3 +#define RT5663_DACR1_SRC_MASK (0x1 << 2) +#define RT5663_DACR1_SRC_SHIFT 2 + +/* TDM control 2 (0x0078) */ +#define RT5663_DATA_SWAP_ADCDAT1_MASK (0x3 << 14) +#define RT5663_DATA_SWAP_ADCDAT1_SHIFT 14 +#define RT5663_DATA_SWAP_ADCDAT1_LR (0x0 << 14) +#define RT5663_DATA_SWAP_ADCDAT1_RL (0x1 << 14) +#define RT5663_DATA_SWAP_ADCDAT1_LL (0x2 << 14) +#define RT5663_DATA_SWAP_ADCDAT1_RR (0x3 << 14) + +/* TDM control 5 (0x007b) */ +#define RT5663_TDM_LENGTN_MASK (0x3) +#define RT5663_TDM_LENGTN_SHIFT 0 +#define RT5663_TDM_LENGTN_16 (0x0) +#define RT5663_TDM_LENGTN_20 (0x1) +#define RT5663_TDM_LENGTN_24 (0x2) +#define RT5663_TDM_LENGTN_32 (0x3) + +/* PLL tracking mode 1 (0x0083) */ +#define RT5663_I2S1_ASRC_MASK (0x1 << 11) +#define RT5663_I2S1_ASRC_SHIFT 11 +#define RT5663_DAC_STO1_ASRC_MASK (0x1 << 10) +#define RT5663_DAC_STO1_ASRC_SHIFT 10 +#define RT5663_ADC_STO1_ASRC_MASK (0x1 << 3) +#define RT5663_ADC_STO1_ASRC_SHIFT 3 + +/* PLL tracking mode 2 (0x0084)*/ +#define RT5663_DA_STO1_TRACK_MASK (0x7 << 12) +#define RT5663_DA_STO1_TRACK_SHIFT 12 +#define RT5663_DA_STO1_TRACK_SYSCLK (0x0 << 12) +#define RT5663_DA_STO1_TRACK_I2S1 (0x1 << 12) +#define RT5663_AD_STO1_TRACK_MASK (0x7) +#define RT5663_AD_STO1_TRACK_SHIFT 0 +#define RT5663_AD_STO1_TRACK_SYSCLK (0x0) +#define RT5663_AD_STO1_TRACK_I2S1 (0x1) + +/* HPOUT Charge pump control 1 (0x0091) */ +#define RT5663_SI_HP_MASK (0x1 << 12) +#define RT5663_SI_HP_SHIFT 12 +#define RT5663_SI_HP_EN (0x1 << 12) +#define RT5663_SI_HP_DIS (0x0 << 12) + +/* GPIO Control 2 (0x00b6) */ +#define RT5663_GP1_PIN_CONF_MASK (0x1 << 2) +#define RT5663_GP1_PIN_CONF_SHIFT 2 +#define RT5663_GP1_PIN_CONF_OUTPUT (0x1 << 2) +#define RT5663_GP1_PIN_CONF_INPUT (0x0 << 2) + +/* GPIO Control 2 (0x00b7) */ +#define RT5663_EN_IRQ_INLINE_MASK (0x1 << 3) +#define RT5663_EN_IRQ_INLINE_SHIFT 3 +#define RT5663_EN_IRQ_INLINE_NOR (0x1 << 3) +#define RT5663_EN_IRQ_INLINE_BYP (0x0 << 3) + +/* GPIO Control 1 (0x00c0) */ +#define RT5663_GPIO1_TYPE_MASK (0x1 << 15) +#define RT5663_GPIO1_TYPE_SHIFT 15 +#define RT5663_GPIO1_TYPE_EN (0x1 << 15) +#define RT5663_GPIO1_TYPE_DIS (0x0 << 15) + +/* IRQ Control 1 (0x00c1) */ +#define RT5663_EN_IRQ_JD1_MASK (0x1 << 6) +#define RT5663_EN_IRQ_JD1_SHIFT 6 +#define RT5663_EN_IRQ_JD1_EN (0x1 << 6) +#define RT5663_EN_IRQ_JD1_DIS (0x0 << 6) +#define RT5663_SEL_GPIO1_MASK (0x1 << 2) +#define RT5663_SEL_GPIO1_SHIFT 6 +#define RT5663_SEL_GPIO1_EN (0x1 << 2) +#define RT5663_SEL_GPIO1_DIS (0x0 << 2) + +/* Inline Command Function 2 (0x00dc) */ +#define RT5663_PWR_MIC_DET_MASK (0x1) +#define RT5663_PWR_MIC_DET_SHIFT 0 +#define RT5663_PWR_MIC_DET_ON (0x1) +#define RT5663_PWR_MIC_DET_OFF (0x0) + +/* Embeeded Jack and Type Detection Control 1 (0x00e6)*/ +#define RT5663_CBJ_DET_MASK (0x1 << 15) +#define RT5663_CBJ_DET_SHIFT 15 +#define RT5663_CBJ_DET_DIS (0x0 << 15) +#define RT5663_CBJ_DET_EN (0x1 << 15) +#define RT5663_EXT_JD_MASK (0x1 << 11) +#define RT5663_EXT_JD_SHIFT 11 +#define RT5663_EXT_JD_EN (0x1 << 11) +#define RT5663_EXT_JD_DIS (0x0 << 11) +#define RT5663_POL_EXT_JD_MASK (0x1 << 10) +#define RT5663_POL_EXT_JD_SHIFT 10 +#define RT5663_POL_EXT_JD_EN (0x1 << 10) +#define RT5663_POL_EXT_JD_DIS (0x0 << 10) +#define RT5663_EM_JD_MASK (0x1 << 7) +#define RT5663_EM_JD_SHIFT 7 +#define RT5663_EM_JD_NOR (0x1 << 7) +#define RT5663_EM_JD_RST (0x0 << 7) + +/* DACREF LDO Control (0x0112)*/ +#define RT5663_PWR_LDO_DACREFL_MASK (0x1 << 9) +#define RT5663_PWR_LDO_DACREFL_SHIFT 9 +#define RT5663_PWR_LDO_DACREFR_MASK (0x1 << 1) +#define RT5663_PWR_LDO_DACREFR_SHIFT 1 + +/* Stereo Dynamic Range Enhancement Control 9 (0x0168, 0x0169)*/ +#define RT5663_DRE_GAIN_HP_MASK (0x1f) +#define RT5663_DRE_GAIN_HP_SHIFT 0 + +/* Combo Jack Control (0x0250) */ +#define RT5663_INBUF_CBJ_BST1_MASK (0x1 << 11) +#define RT5663_INBUF_CBJ_BST1_SHIFT 11 +#define RT5663_INBUF_CBJ_BST1_ON (0x1 << 11) +#define RT5663_INBUF_CBJ_BST1_OFF (0x0 << 11) +#define RT5663_CBJ_SENSE_BST1_MASK (0x1 << 10) +#define RT5663_CBJ_SENSE_BST1_SHIFT 10 +#define RT5663_CBJ_SENSE_BST1_L (0x1 << 10) +#define RT5663_CBJ_SENSE_BST1_R (0x0 << 10) + +/* Combo Jack Control (0x0251) */ +#define RT5663_GAIN_BST1_MASK (0xf) +#define RT5663_GAIN_BST1_SHIFT 0 + +/* Dummy register 1 (0x02fa) */ +#define RT5663_EMB_CLK_MASK (0x1 << 9) +#define RT5663_EMB_CLK_SHIFT 9 +#define RT5663_EMB_CLK_EN (0x1 << 9) +#define RT5663_EMB_CLK_DIS (0x0 << 9) +#define RT5663_HPA_CPL_BIAS_MASK (0x7 << 6) +#define RT5663_HPA_CPL_BIAS_SHIFT 6 +#define RT5663_HPA_CPL_BIAS_0_5 (0x0 << 6) +#define RT5663_HPA_CPL_BIAS_1 (0x1 << 6) +#define RT5663_HPA_CPL_BIAS_2 (0x2 << 6) +#define RT5663_HPA_CPL_BIAS_3 (0x3 << 6) +#define RT5663_HPA_CPL_BIAS_4_1 (0x4 << 6) +#define RT5663_HPA_CPL_BIAS_4_2 (0x5 << 6) +#define RT5663_HPA_CPL_BIAS_6 (0x6 << 6) +#define RT5663_HPA_CPL_BIAS_8 (0x7 << 6) +#define RT5663_HPA_CPR_BIAS_MASK (0x7 << 3) +#define RT5663_HPA_CPR_BIAS_SHIFT 3 +#define RT5663_HPA_CPR_BIAS_0_5 (0x0 << 3) +#define RT5663_HPA_CPR_BIAS_1 (0x1 << 3) +#define RT5663_HPA_CPR_BIAS_2 (0x2 << 3) +#define RT5663_HPA_CPR_BIAS_3 (0x3 << 3) +#define RT5663_HPA_CPR_BIAS_4_1 (0x4 << 3) +#define RT5663_HPA_CPR_BIAS_4_2 (0x5 << 3) +#define RT5663_HPA_CPR_BIAS_6 (0x6 << 3) +#define RT5663_HPA_CPR_BIAS_8 (0x7 << 3) +#define RT5663_DUMMY_BIAS_MASK (0x7) +#define RT5663_DUMMY_BIAS_SHIFT 0 +#define RT5663_DUMMY_BIAS_0_5 (0x0) +#define RT5663_DUMMY_BIAS_1 (0x1) +#define RT5663_DUMMY_BIAS_2 (0x2) +#define RT5663_DUMMY_BIAS_3 (0x3) +#define RT5663_DUMMY_BIAS_4_1 (0x4) +#define RT5663_DUMMY_BIAS_4_2 (0x5) +#define RT5663_DUMMY_BIAS_6 (0x6) +#define RT5663_DUMMY_BIAS_8 (0x7) + + +/* System Clock Source */ +enum { + RT5663_SCLK_S_MCLK, + RT5663_SCLK_S_PLL1, + RT5663_SCLK_S_RCCLK, +}; + +/* PLL1 Source */ +enum { + RT5663_PLL1_S_MCLK, + RT5663_PLL1_S_BCLK1, +}; + +enum { + RT5663_AIF, + RT5663_AIFS, +}; + +/* asrc clock source */ +enum { + RT5663_CLK_SEL_SYS = 0x0, + RT5663_CLK_SEL_I2S1_ASRC = 0x1, +}; + +/* filter mask */ +enum { + RT5663_DA_STEREO_FILTER = 0x1, + RT5663_AD_STEREO_FILTER = 0x2, +}; + +int rt5663_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +#endif /* __RT5663_H__ */ diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c new file mode 100644 index 000000000..6ba99f5ed --- /dev/null +++ b/sound/soc/codecs/rt5665.c @@ -0,0 +1,4983 @@ +/* + * rt5665.c -- RT5665/RT5658 ALSA SoC audio codec driver + * + * Copyright 2016 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5665.h" + +#define RT5665_NUM_SUPPLIES 3 + +static const char *rt5665_supply_names[RT5665_NUM_SUPPLIES] = { + "AVDD", + "MICVDD", + "VBAT", +}; + +struct rt5665_priv { + struct snd_soc_component *component; + struct rt5665_platform_data pdata; + struct regmap *regmap; + struct gpio_desc *gpiod_ldo1_en; + struct gpio_desc *gpiod_reset; + struct snd_soc_jack *hs_jack; + struct regulator_bulk_data supplies[RT5665_NUM_SUPPLIES]; + struct delayed_work jack_detect_work; + struct delayed_work calibrate_work; + struct delayed_work jd_check_work; + struct mutex calibrate_mutex; + + int sysclk; + int sysclk_src; + int lrck[RT5665_AIFS]; + int bclk[RT5665_AIFS]; + int master[RT5665_AIFS]; + int id; + + int pll_src; + int pll_in; + int pll_out; + + int jack_type; + int irq_work_delay_time; + unsigned int sar_adc_value; + bool calibration_done; +}; + +static const struct reg_default rt5665_reg[] = { + {0x0000, 0x0000}, + {0x0001, 0xc8c8}, + {0x0002, 0x8080}, + {0x0003, 0x8000}, + {0x0004, 0xc80a}, + {0x0005, 0x0000}, + {0x0006, 0x0000}, + {0x0007, 0x0000}, + {0x000a, 0x0000}, + {0x000b, 0x0000}, + {0x000c, 0x0000}, + {0x000d, 0x0000}, + {0x000f, 0x0808}, + {0x0010, 0x4040}, + {0x0011, 0x0000}, + {0x0012, 0x1404}, + {0x0013, 0x1000}, + {0x0014, 0xa00a}, + {0x0015, 0x0404}, + {0x0016, 0x0404}, + {0x0017, 0x0011}, + {0x0018, 0xafaf}, + {0x0019, 0xafaf}, + {0x001a, 0xafaf}, + {0x001b, 0x0011}, + {0x001c, 0x2f2f}, + {0x001d, 0x2f2f}, + {0x001e, 0x2f2f}, + {0x001f, 0x0000}, + {0x0020, 0x0000}, + {0x0021, 0x0000}, + {0x0022, 0x5757}, + {0x0023, 0x0039}, + {0x0026, 0xc0c0}, + {0x0027, 0xc0c0}, + {0x0028, 0xc0c0}, + {0x0029, 0x8080}, + {0x002a, 0xaaaa}, + {0x002b, 0xaaaa}, + {0x002c, 0xaba8}, + {0x002d, 0x0000}, + {0x002e, 0x0000}, + {0x002f, 0x0000}, + {0x0030, 0x0000}, + {0x0031, 0x5000}, + {0x0032, 0x0000}, + {0x0033, 0x0000}, + {0x0034, 0x0000}, + {0x0035, 0x0000}, + {0x003a, 0x0000}, + {0x003b, 0x0000}, + {0x003c, 0x00ff}, + {0x003d, 0x0000}, + {0x003e, 0x00ff}, + {0x003f, 0x0000}, + {0x0040, 0x0000}, + {0x0041, 0x00ff}, + {0x0042, 0x0000}, + {0x0043, 0x00ff}, + {0x0044, 0x0c0c}, + {0x0049, 0xc00b}, + {0x004a, 0x0000}, + {0x004b, 0x031f}, + {0x004d, 0x0000}, + {0x004e, 0x001f}, + {0x004f, 0x0000}, + {0x0050, 0x001f}, + {0x0052, 0xf000}, + {0x0061, 0x0000}, + {0x0062, 0x0000}, + {0x0063, 0x003e}, + {0x0064, 0x0000}, + {0x0065, 0x0000}, + {0x0066, 0x003f}, + {0x0067, 0x0000}, + {0x006b, 0x0000}, + {0x006d, 0xff00}, + {0x006e, 0x2808}, + {0x006f, 0x000a}, + {0x0070, 0x8000}, + {0x0071, 0x8000}, + {0x0072, 0x8000}, + {0x0073, 0x7000}, + {0x0074, 0x7770}, + {0x0075, 0x0002}, + {0x0076, 0x0001}, + {0x0078, 0x00f0}, + {0x0079, 0x0000}, + {0x007a, 0x0000}, + {0x007b, 0x0000}, + {0x007c, 0x0000}, + {0x007d, 0x0123}, + {0x007e, 0x4500}, + {0x007f, 0x8003}, + {0x0080, 0x0000}, + {0x0081, 0x0000}, + {0x0082, 0x0000}, + {0x0083, 0x0000}, + {0x0084, 0x0000}, + {0x0085, 0x0000}, + {0x0086, 0x0008}, + {0x0087, 0x0000}, + {0x0088, 0x0000}, + {0x0089, 0x0000}, + {0x008a, 0x0000}, + {0x008b, 0x0000}, + {0x008c, 0x0003}, + {0x008e, 0x0060}, + {0x008f, 0x1000}, + {0x0091, 0x0c26}, + {0x0092, 0x0073}, + {0x0093, 0x0000}, + {0x0094, 0x0080}, + {0x0098, 0x0000}, + {0x0099, 0x0000}, + {0x009a, 0x0007}, + {0x009f, 0x0000}, + {0x00a0, 0x0000}, + {0x00a1, 0x0002}, + {0x00a2, 0x0001}, + {0x00a3, 0x0002}, + {0x00a4, 0x0001}, + {0x00ae, 0x2040}, + {0x00af, 0x0000}, + {0x00b6, 0x0000}, + {0x00b7, 0x0000}, + {0x00b8, 0x0000}, + {0x00b9, 0x0000}, + {0x00ba, 0x0002}, + {0x00bb, 0x0000}, + {0x00be, 0x0000}, + {0x00c0, 0x0000}, + {0x00c1, 0x0aaa}, + {0x00c2, 0xaa80}, + {0x00c3, 0x0003}, + {0x00c4, 0x0000}, + {0x00d0, 0x0000}, + {0x00d1, 0x2244}, + {0x00d3, 0x3300}, + {0x00d4, 0x2200}, + {0x00d9, 0x0809}, + {0x00da, 0x0000}, + {0x00db, 0x0008}, + {0x00dc, 0x00c0}, + {0x00dd, 0x6724}, + {0x00de, 0x3131}, + {0x00df, 0x0008}, + {0x00e0, 0x4000}, + {0x00e1, 0x3131}, + {0x00e2, 0x600c}, + {0x00ea, 0xb320}, + {0x00eb, 0x0000}, + {0x00ec, 0xb300}, + {0x00ed, 0x0000}, + {0x00ee, 0xb320}, + {0x00ef, 0x0000}, + {0x00f0, 0x0201}, + {0x00f1, 0x0ddd}, + {0x00f2, 0x0ddd}, + {0x00f6, 0x0000}, + {0x00f7, 0x0000}, + {0x00f8, 0x0000}, + {0x00fa, 0x0000}, + {0x00fb, 0x0000}, + {0x00fc, 0x0000}, + {0x00fd, 0x0000}, + {0x00fe, 0x10ec}, + {0x00ff, 0x6451}, + {0x0100, 0xaaaa}, + {0x0101, 0x000a}, + {0x010a, 0xaaaa}, + {0x010b, 0xa0a0}, + {0x010c, 0xaeae}, + {0x010d, 0xaaaa}, + {0x010e, 0xaaaa}, + {0x010f, 0xaaaa}, + {0x0110, 0xe002}, + {0x0111, 0xa402}, + {0x0112, 0xaaaa}, + {0x0113, 0x2000}, + {0x0117, 0x0f00}, + {0x0125, 0x0410}, + {0x0132, 0x0000}, + {0x0133, 0x0000}, + {0x0137, 0x5540}, + {0x0138, 0x3700}, + {0x0139, 0x79a1}, + {0x013a, 0x2020}, + {0x013b, 0x2020}, + {0x013c, 0x2005}, + {0x013f, 0x0000}, + {0x0145, 0x0002}, + {0x0146, 0x0000}, + {0x0147, 0x0000}, + {0x0148, 0x0000}, + {0x0150, 0x0000}, + {0x0160, 0x4eff}, + {0x0161, 0x0080}, + {0x0162, 0x0200}, + {0x0163, 0x0800}, + {0x0164, 0x0000}, + {0x0165, 0x0000}, + {0x0166, 0x0000}, + {0x0167, 0x000f}, + {0x0170, 0x4e87}, + {0x0171, 0x0080}, + {0x0172, 0x0200}, + {0x0173, 0x0800}, + {0x0174, 0x00ff}, + {0x0175, 0x0000}, + {0x0190, 0x413d}, + {0x0191, 0x4139}, + {0x0192, 0x4135}, + {0x0193, 0x413d}, + {0x0194, 0x0000}, + {0x0195, 0x0000}, + {0x0196, 0x0000}, + {0x0197, 0x0000}, + {0x0198, 0x0000}, + {0x0199, 0x0000}, + {0x01a0, 0x1e64}, + {0x01a1, 0x06a3}, + {0x01a2, 0x0000}, + {0x01a3, 0x0000}, + {0x01a4, 0x0000}, + {0x01a5, 0x0000}, + {0x01a6, 0x0000}, + {0x01a7, 0x8000}, + {0x01a8, 0x0000}, + {0x01a9, 0x0000}, + {0x01aa, 0x0000}, + {0x01ab, 0x0000}, + {0x01b5, 0x0000}, + {0x01b6, 0x01c3}, + {0x01b7, 0x02a0}, + {0x01b8, 0x03e9}, + {0x01b9, 0x1389}, + {0x01ba, 0xc351}, + {0x01bb, 0x0009}, + {0x01bc, 0x0018}, + {0x01bd, 0x002a}, + {0x01be, 0x004c}, + {0x01bf, 0x0097}, + {0x01c0, 0x433d}, + {0x01c1, 0x0000}, + {0x01c2, 0x0000}, + {0x01c3, 0x0000}, + {0x01c4, 0x0000}, + {0x01c5, 0x0000}, + {0x01c6, 0x0000}, + {0x01c7, 0x0000}, + {0x01c8, 0x40af}, + {0x01c9, 0x0702}, + {0x01ca, 0x0000}, + {0x01cb, 0x0000}, + {0x01cc, 0x5757}, + {0x01cd, 0x5757}, + {0x01ce, 0x5757}, + {0x01cf, 0x5757}, + {0x01d0, 0x5757}, + {0x01d1, 0x5757}, + {0x01d2, 0x5757}, + {0x01d3, 0x5757}, + {0x01d4, 0x5757}, + {0x01d5, 0x5757}, + {0x01d6, 0x003c}, + {0x01da, 0x0000}, + {0x01db, 0x0000}, + {0x01dc, 0x0000}, + {0x01de, 0x7c00}, + {0x01df, 0x0320}, + {0x01e0, 0x06a1}, + {0x01e1, 0x0000}, + {0x01e2, 0x0000}, + {0x01e3, 0x0000}, + {0x01e4, 0x0000}, + {0x01e6, 0x0001}, + {0x01e7, 0x0000}, + {0x01e8, 0x0000}, + {0x01ea, 0xbf3f}, + {0x01eb, 0x0000}, + {0x01ec, 0x0000}, + {0x01ed, 0x0000}, + {0x01ee, 0x0000}, + {0x01ef, 0x0000}, + {0x01f0, 0x0000}, + {0x01f1, 0x0000}, + {0x01f2, 0x0000}, + {0x01f3, 0x0000}, + {0x01f4, 0x0000}, + {0x0200, 0x0000}, + {0x0201, 0x0000}, + {0x0202, 0x0000}, + {0x0203, 0x0000}, + {0x0204, 0x0000}, + {0x0205, 0x0000}, + {0x0206, 0x0000}, + {0x0207, 0x0000}, + {0x0208, 0x0000}, + {0x0210, 0x60b1}, + {0x0211, 0xa005}, + {0x0212, 0x024c}, + {0x0213, 0xf7ff}, + {0x0214, 0x024c}, + {0x0215, 0x0102}, + {0x0216, 0x00a3}, + {0x0217, 0x0048}, + {0x0218, 0xa2c0}, + {0x0219, 0x0400}, + {0x021a, 0x00c8}, + {0x021b, 0x00c0}, + {0x02ff, 0x0110}, + {0x0300, 0x001f}, + {0x0301, 0x032c}, + {0x0302, 0x5f21}, + {0x0303, 0x4000}, + {0x0304, 0x4000}, + {0x0305, 0x06d5}, + {0x0306, 0x8000}, + {0x0307, 0x0700}, + {0x0310, 0x4560}, + {0x0311, 0xa4a8}, + {0x0312, 0x7418}, + {0x0313, 0x0000}, + {0x0314, 0x0006}, + {0x0315, 0xffff}, + {0x0316, 0xc400}, + {0x0317, 0x0000}, + {0x0330, 0x00a6}, + {0x0331, 0x04c3}, + {0x0332, 0x27c8}, + {0x0333, 0xbf50}, + {0x0334, 0x0045}, + {0x0335, 0x0007}, + {0x0336, 0x7418}, + {0x0337, 0x0501}, + {0x0338, 0x0000}, + {0x0339, 0x0010}, + {0x033a, 0x1010}, + {0x03c0, 0x7e00}, + {0x03c1, 0x8000}, + {0x03c2, 0x8000}, + {0x03c3, 0x8000}, + {0x03c4, 0x8000}, + {0x03c5, 0x8000}, + {0x03c6, 0x8000}, + {0x03c7, 0x8000}, + {0x03c8, 0x8000}, + {0x03c9, 0x8000}, + {0x03ca, 0x8000}, + {0x03cb, 0x8000}, + {0x03cc, 0x8000}, + {0x03d0, 0x0000}, + {0x03d1, 0x0000}, + {0x03d2, 0x0000}, + {0x03d3, 0x0000}, + {0x03d4, 0x2000}, + {0x03d5, 0x2000}, + {0x03d6, 0x0000}, + {0x03d7, 0x0000}, + {0x03d8, 0x2000}, + {0x03d9, 0x2000}, + {0x03da, 0x2000}, + {0x03db, 0x2000}, + {0x03dc, 0x0000}, + {0x03dd, 0x0000}, + {0x03de, 0x0000}, + {0x03df, 0x2000}, + {0x03e0, 0x0000}, + {0x03e1, 0x0000}, + {0x03e2, 0x0000}, + {0x03e3, 0x0000}, + {0x03e4, 0x0000}, + {0x03e5, 0x0000}, + {0x03e6, 0x0000}, + {0x03e7, 0x0000}, + {0x03e8, 0x0000}, + {0x03e9, 0x0000}, + {0x03ea, 0x0000}, + {0x03eb, 0x0000}, + {0x03ec, 0x0000}, + {0x03ed, 0x0000}, + {0x03ee, 0x0000}, + {0x03ef, 0x0000}, + {0x03f0, 0x0800}, + {0x03f1, 0x0800}, + {0x03f2, 0x0800}, + {0x03f3, 0x0800}, +}; + +static bool rt5665_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5665_RESET: + case RT5665_EJD_CTRL_2: + case RT5665_GPIO_STA: + case RT5665_INT_ST_1: + case RT5665_IL_CMD_1: + case RT5665_4BTN_IL_CMD_1: + case RT5665_PSV_IL_CMD_1: + case RT5665_AJD1_CTRL: + case RT5665_JD_CTRL_3: + case RT5665_STO_NG2_CTRL_1: + case RT5665_SAR_IL_CMD_4: + case RT5665_DEVICE_ID: + case RT5665_STO1_DAC_SIL_DET ... RT5665_STO2_DAC_SIL_DET: + case RT5665_MONO_AMP_CALIB_STA1 ... RT5665_MONO_AMP_CALIB_STA6: + case RT5665_HP_IMP_SENS_CTRL_12 ... RT5665_HP_IMP_SENS_CTRL_15: + case RT5665_HP_CALIB_STA_1 ... RT5665_HP_CALIB_STA_11: + return true; + default: + return false; + } +} + +static bool rt5665_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5665_RESET: + case RT5665_VENDOR_ID: + case RT5665_VENDOR_ID_1: + case RT5665_DEVICE_ID: + case RT5665_LOUT: + case RT5665_HP_CTRL_1: + case RT5665_HP_CTRL_2: + case RT5665_MONO_OUT: + case RT5665_HPL_GAIN: + case RT5665_HPR_GAIN: + case RT5665_MONO_GAIN: + case RT5665_CAL_BST_CTRL: + case RT5665_CBJ_BST_CTRL: + case RT5665_IN1_IN2: + case RT5665_IN3_IN4: + case RT5665_INL1_INR1_VOL: + case RT5665_EJD_CTRL_1: + case RT5665_EJD_CTRL_2: + case RT5665_EJD_CTRL_3: + case RT5665_EJD_CTRL_4: + case RT5665_EJD_CTRL_5: + case RT5665_EJD_CTRL_6: + case RT5665_EJD_CTRL_7: + case RT5665_DAC2_CTRL: + case RT5665_DAC2_DIG_VOL: + case RT5665_DAC1_DIG_VOL: + case RT5665_DAC3_DIG_VOL: + case RT5665_DAC3_CTRL: + case RT5665_STO1_ADC_DIG_VOL: + case RT5665_MONO_ADC_DIG_VOL: + case RT5665_STO2_ADC_DIG_VOL: + case RT5665_STO1_ADC_BOOST: + case RT5665_MONO_ADC_BOOST: + case RT5665_STO2_ADC_BOOST: + case RT5665_HP_IMP_GAIN_1: + case RT5665_HP_IMP_GAIN_2: + case RT5665_STO1_ADC_MIXER: + case RT5665_MONO_ADC_MIXER: + case RT5665_STO2_ADC_MIXER: + case RT5665_AD_DA_MIXER: + case RT5665_STO1_DAC_MIXER: + case RT5665_MONO_DAC_MIXER: + case RT5665_STO2_DAC_MIXER: + case RT5665_A_DAC1_MUX: + case RT5665_A_DAC2_MUX: + case RT5665_DIG_INF2_DATA: + case RT5665_DIG_INF3_DATA: + case RT5665_PDM_OUT_CTRL: + case RT5665_PDM_DATA_CTRL_1: + case RT5665_PDM_DATA_CTRL_2: + case RT5665_PDM_DATA_CTRL_3: + case RT5665_PDM_DATA_CTRL_4: + case RT5665_REC1_GAIN: + case RT5665_REC1_L1_MIXER: + case RT5665_REC1_L2_MIXER: + case RT5665_REC1_R1_MIXER: + case RT5665_REC1_R2_MIXER: + case RT5665_REC2_GAIN: + case RT5665_REC2_L1_MIXER: + case RT5665_REC2_L2_MIXER: + case RT5665_REC2_R1_MIXER: + case RT5665_REC2_R2_MIXER: + case RT5665_CAL_REC: + case RT5665_ALC_BACK_GAIN: + case RT5665_MONOMIX_GAIN: + case RT5665_MONOMIX_IN_GAIN: + case RT5665_OUT_L_GAIN: + case RT5665_OUT_L_MIXER: + case RT5665_OUT_R_GAIN: + case RT5665_OUT_R_MIXER: + case RT5665_LOUT_MIXER: + case RT5665_PWR_DIG_1: + case RT5665_PWR_DIG_2: + case RT5665_PWR_ANLG_1: + case RT5665_PWR_ANLG_2: + case RT5665_PWR_ANLG_3: + case RT5665_PWR_MIXER: + case RT5665_PWR_VOL: + case RT5665_CLK_DET: + case RT5665_HPF_CTRL1: + case RT5665_DMIC_CTRL_1: + case RT5665_DMIC_CTRL_2: + case RT5665_I2S1_SDP: + case RT5665_I2S2_SDP: + case RT5665_I2S3_SDP: + case RT5665_ADDA_CLK_1: + case RT5665_ADDA_CLK_2: + case RT5665_I2S1_F_DIV_CTRL_1: + case RT5665_I2S1_F_DIV_CTRL_2: + case RT5665_TDM_CTRL_1: + case RT5665_TDM_CTRL_2: + case RT5665_TDM_CTRL_3: + case RT5665_TDM_CTRL_4: + case RT5665_TDM_CTRL_5: + case RT5665_TDM_CTRL_6: + case RT5665_TDM_CTRL_7: + case RT5665_TDM_CTRL_8: + case RT5665_GLB_CLK: + case RT5665_PLL_CTRL_1: + case RT5665_PLL_CTRL_2: + case RT5665_ASRC_1: + case RT5665_ASRC_2: + case RT5665_ASRC_3: + case RT5665_ASRC_4: + case RT5665_ASRC_5: + case RT5665_ASRC_6: + case RT5665_ASRC_7: + case RT5665_ASRC_8: + case RT5665_ASRC_9: + case RT5665_ASRC_10: + case RT5665_DEPOP_1: + case RT5665_DEPOP_2: + case RT5665_HP_CHARGE_PUMP_1: + case RT5665_HP_CHARGE_PUMP_2: + case RT5665_MICBIAS_1: + case RT5665_MICBIAS_2: + case RT5665_ASRC_12: + case RT5665_ASRC_13: + case RT5665_ASRC_14: + case RT5665_RC_CLK_CTRL: + case RT5665_I2S_M_CLK_CTRL_1: + case RT5665_I2S2_F_DIV_CTRL_1: + case RT5665_I2S2_F_DIV_CTRL_2: + case RT5665_I2S3_F_DIV_CTRL_1: + case RT5665_I2S3_F_DIV_CTRL_2: + case RT5665_EQ_CTRL_1: + case RT5665_EQ_CTRL_2: + case RT5665_IRQ_CTRL_1: + case RT5665_IRQ_CTRL_2: + case RT5665_IRQ_CTRL_3: + case RT5665_IRQ_CTRL_4: + case RT5665_IRQ_CTRL_5: + case RT5665_IRQ_CTRL_6: + case RT5665_INT_ST_1: + case RT5665_GPIO_CTRL_1: + case RT5665_GPIO_CTRL_2: + case RT5665_GPIO_CTRL_3: + case RT5665_GPIO_CTRL_4: + case RT5665_GPIO_STA: + case RT5665_HP_AMP_DET_CTRL_1: + case RT5665_HP_AMP_DET_CTRL_2: + case RT5665_MID_HP_AMP_DET: + case RT5665_LOW_HP_AMP_DET: + case RT5665_SV_ZCD_1: + case RT5665_SV_ZCD_2: + case RT5665_IL_CMD_1: + case RT5665_IL_CMD_2: + case RT5665_IL_CMD_3: + case RT5665_IL_CMD_4: + case RT5665_4BTN_IL_CMD_1: + case RT5665_4BTN_IL_CMD_2: + case RT5665_4BTN_IL_CMD_3: + case RT5665_PSV_IL_CMD_1: + case RT5665_ADC_STO1_HP_CTRL_1: + case RT5665_ADC_STO1_HP_CTRL_2: + case RT5665_ADC_MONO_HP_CTRL_1: + case RT5665_ADC_MONO_HP_CTRL_2: + case RT5665_ADC_STO2_HP_CTRL_1: + case RT5665_ADC_STO2_HP_CTRL_2: + case RT5665_AJD1_CTRL: + case RT5665_JD1_THD: + case RT5665_JD2_THD: + case RT5665_JD_CTRL_1: + case RT5665_JD_CTRL_2: + case RT5665_JD_CTRL_3: + case RT5665_DIG_MISC: + case RT5665_DUMMY_2: + case RT5665_DUMMY_3: + case RT5665_DAC_ADC_DIG_VOL1: + case RT5665_DAC_ADC_DIG_VOL2: + case RT5665_BIAS_CUR_CTRL_1: + case RT5665_BIAS_CUR_CTRL_2: + case RT5665_BIAS_CUR_CTRL_3: + case RT5665_BIAS_CUR_CTRL_4: + case RT5665_BIAS_CUR_CTRL_5: + case RT5665_BIAS_CUR_CTRL_6: + case RT5665_BIAS_CUR_CTRL_7: + case RT5665_BIAS_CUR_CTRL_8: + case RT5665_BIAS_CUR_CTRL_9: + case RT5665_BIAS_CUR_CTRL_10: + case RT5665_VREF_REC_OP_FB_CAP_CTRL: + case RT5665_CHARGE_PUMP_1: + case RT5665_DIG_IN_CTRL_1: + case RT5665_DIG_IN_CTRL_2: + case RT5665_PAD_DRIVING_CTRL: + case RT5665_SOFT_RAMP_DEPOP: + case RT5665_PLL: + case RT5665_CHOP_DAC: + case RT5665_CHOP_ADC: + case RT5665_CALIB_ADC_CTRL: + case RT5665_VOL_TEST: + case RT5665_TEST_MODE_CTRL_1: + case RT5665_TEST_MODE_CTRL_2: + case RT5665_TEST_MODE_CTRL_3: + case RT5665_TEST_MODE_CTRL_4: + case RT5665_BASSBACK_CTRL: + case RT5665_STO_NG2_CTRL_1: + case RT5665_STO_NG2_CTRL_2: + case RT5665_STO_NG2_CTRL_3: + case RT5665_STO_NG2_CTRL_4: + case RT5665_STO_NG2_CTRL_5: + case RT5665_STO_NG2_CTRL_6: + case RT5665_STO_NG2_CTRL_7: + case RT5665_STO_NG2_CTRL_8: + case RT5665_MONO_NG2_CTRL_1: + case RT5665_MONO_NG2_CTRL_2: + case RT5665_MONO_NG2_CTRL_3: + case RT5665_MONO_NG2_CTRL_4: + case RT5665_MONO_NG2_CTRL_5: + case RT5665_MONO_NG2_CTRL_6: + case RT5665_STO1_DAC_SIL_DET: + case RT5665_MONOL_DAC_SIL_DET: + case RT5665_MONOR_DAC_SIL_DET: + case RT5665_STO2_DAC_SIL_DET: + case RT5665_SIL_PSV_CTRL1: + case RT5665_SIL_PSV_CTRL2: + case RT5665_SIL_PSV_CTRL3: + case RT5665_SIL_PSV_CTRL4: + case RT5665_SIL_PSV_CTRL5: + case RT5665_SIL_PSV_CTRL6: + case RT5665_MONO_AMP_CALIB_CTRL_1: + case RT5665_MONO_AMP_CALIB_CTRL_2: + case RT5665_MONO_AMP_CALIB_CTRL_3: + case RT5665_MONO_AMP_CALIB_CTRL_4: + case RT5665_MONO_AMP_CALIB_CTRL_5: + case RT5665_MONO_AMP_CALIB_CTRL_6: + case RT5665_MONO_AMP_CALIB_CTRL_7: + case RT5665_MONO_AMP_CALIB_STA1: + case RT5665_MONO_AMP_CALIB_STA2: + case RT5665_MONO_AMP_CALIB_STA3: + case RT5665_MONO_AMP_CALIB_STA4: + case RT5665_MONO_AMP_CALIB_STA6: + case RT5665_HP_IMP_SENS_CTRL_01: + case RT5665_HP_IMP_SENS_CTRL_02: + case RT5665_HP_IMP_SENS_CTRL_03: + case RT5665_HP_IMP_SENS_CTRL_04: + case RT5665_HP_IMP_SENS_CTRL_05: + case RT5665_HP_IMP_SENS_CTRL_06: + case RT5665_HP_IMP_SENS_CTRL_07: + case RT5665_HP_IMP_SENS_CTRL_08: + case RT5665_HP_IMP_SENS_CTRL_09: + case RT5665_HP_IMP_SENS_CTRL_10: + case RT5665_HP_IMP_SENS_CTRL_11: + case RT5665_HP_IMP_SENS_CTRL_12: + case RT5665_HP_IMP_SENS_CTRL_13: + case RT5665_HP_IMP_SENS_CTRL_14: + case RT5665_HP_IMP_SENS_CTRL_15: + case RT5665_HP_IMP_SENS_CTRL_16: + case RT5665_HP_IMP_SENS_CTRL_17: + case RT5665_HP_IMP_SENS_CTRL_18: + case RT5665_HP_IMP_SENS_CTRL_19: + case RT5665_HP_IMP_SENS_CTRL_20: + case RT5665_HP_IMP_SENS_CTRL_21: + case RT5665_HP_IMP_SENS_CTRL_22: + case RT5665_HP_IMP_SENS_CTRL_23: + case RT5665_HP_IMP_SENS_CTRL_24: + case RT5665_HP_IMP_SENS_CTRL_25: + case RT5665_HP_IMP_SENS_CTRL_26: + case RT5665_HP_IMP_SENS_CTRL_27: + case RT5665_HP_IMP_SENS_CTRL_28: + case RT5665_HP_IMP_SENS_CTRL_29: + case RT5665_HP_IMP_SENS_CTRL_30: + case RT5665_HP_IMP_SENS_CTRL_31: + case RT5665_HP_IMP_SENS_CTRL_32: + case RT5665_HP_IMP_SENS_CTRL_33: + case RT5665_HP_IMP_SENS_CTRL_34: + case RT5665_HP_LOGIC_CTRL_1: + case RT5665_HP_LOGIC_CTRL_2: + case RT5665_HP_LOGIC_CTRL_3: + case RT5665_HP_CALIB_CTRL_1: + case RT5665_HP_CALIB_CTRL_2: + case RT5665_HP_CALIB_CTRL_3: + case RT5665_HP_CALIB_CTRL_4: + case RT5665_HP_CALIB_CTRL_5: + case RT5665_HP_CALIB_CTRL_6: + case RT5665_HP_CALIB_CTRL_7: + case RT5665_HP_CALIB_CTRL_9: + case RT5665_HP_CALIB_CTRL_10: + case RT5665_HP_CALIB_CTRL_11: + case RT5665_HP_CALIB_STA_1: + case RT5665_HP_CALIB_STA_2: + case RT5665_HP_CALIB_STA_3: + case RT5665_HP_CALIB_STA_4: + case RT5665_HP_CALIB_STA_5: + case RT5665_HP_CALIB_STA_6: + case RT5665_HP_CALIB_STA_7: + case RT5665_HP_CALIB_STA_8: + case RT5665_HP_CALIB_STA_9: + case RT5665_HP_CALIB_STA_10: + case RT5665_HP_CALIB_STA_11: + case RT5665_PGM_TAB_CTRL1: + case RT5665_PGM_TAB_CTRL2: + case RT5665_PGM_TAB_CTRL3: + case RT5665_PGM_TAB_CTRL4: + case RT5665_PGM_TAB_CTRL5: + case RT5665_PGM_TAB_CTRL6: + case RT5665_PGM_TAB_CTRL7: + case RT5665_PGM_TAB_CTRL8: + case RT5665_PGM_TAB_CTRL9: + case RT5665_SAR_IL_CMD_1: + case RT5665_SAR_IL_CMD_2: + case RT5665_SAR_IL_CMD_3: + case RT5665_SAR_IL_CMD_4: + case RT5665_SAR_IL_CMD_5: + case RT5665_SAR_IL_CMD_6: + case RT5665_SAR_IL_CMD_7: + case RT5665_SAR_IL_CMD_8: + case RT5665_SAR_IL_CMD_9: + case RT5665_SAR_IL_CMD_10: + case RT5665_SAR_IL_CMD_11: + case RT5665_SAR_IL_CMD_12: + case RT5665_DRC1_CTRL_0: + case RT5665_DRC1_CTRL_1: + case RT5665_DRC1_CTRL_2: + case RT5665_DRC1_CTRL_3: + case RT5665_DRC1_CTRL_4: + case RT5665_DRC1_CTRL_5: + case RT5665_DRC1_CTRL_6: + case RT5665_DRC1_HARD_LMT_CTRL_1: + case RT5665_DRC1_HARD_LMT_CTRL_2: + case RT5665_DRC1_PRIV_1: + case RT5665_DRC1_PRIV_2: + case RT5665_DRC1_PRIV_3: + case RT5665_DRC1_PRIV_4: + case RT5665_DRC1_PRIV_5: + case RT5665_DRC1_PRIV_6: + case RT5665_DRC1_PRIV_7: + case RT5665_DRC1_PRIV_8: + case RT5665_ALC_PGA_CTRL_1: + case RT5665_ALC_PGA_CTRL_2: + case RT5665_ALC_PGA_CTRL_3: + case RT5665_ALC_PGA_CTRL_4: + case RT5665_ALC_PGA_CTRL_5: + case RT5665_ALC_PGA_CTRL_6: + case RT5665_ALC_PGA_CTRL_7: + case RT5665_ALC_PGA_CTRL_8: + case RT5665_ALC_PGA_STA_1: + case RT5665_ALC_PGA_STA_2: + case RT5665_ALC_PGA_STA_3: + case RT5665_EQ_AUTO_RCV_CTRL1: + case RT5665_EQ_AUTO_RCV_CTRL2: + case RT5665_EQ_AUTO_RCV_CTRL3: + case RT5665_EQ_AUTO_RCV_CTRL4: + case RT5665_EQ_AUTO_RCV_CTRL5: + case RT5665_EQ_AUTO_RCV_CTRL6: + case RT5665_EQ_AUTO_RCV_CTRL7: + case RT5665_EQ_AUTO_RCV_CTRL8: + case RT5665_EQ_AUTO_RCV_CTRL9: + case RT5665_EQ_AUTO_RCV_CTRL10: + case RT5665_EQ_AUTO_RCV_CTRL11: + case RT5665_EQ_AUTO_RCV_CTRL12: + case RT5665_EQ_AUTO_RCV_CTRL13: + case RT5665_ADC_L_EQ_LPF1_A1: + case RT5665_R_EQ_LPF1_A1: + case RT5665_L_EQ_LPF1_H0: + case RT5665_R_EQ_LPF1_H0: + case RT5665_L_EQ_BPF1_A1: + case RT5665_R_EQ_BPF1_A1: + case RT5665_L_EQ_BPF1_A2: + case RT5665_R_EQ_BPF1_A2: + case RT5665_L_EQ_BPF1_H0: + case RT5665_R_EQ_BPF1_H0: + case RT5665_L_EQ_BPF2_A1: + case RT5665_R_EQ_BPF2_A1: + case RT5665_L_EQ_BPF2_A2: + case RT5665_R_EQ_BPF2_A2: + case RT5665_L_EQ_BPF2_H0: + case RT5665_R_EQ_BPF2_H0: + case RT5665_L_EQ_BPF3_A1: + case RT5665_R_EQ_BPF3_A1: + case RT5665_L_EQ_BPF3_A2: + case RT5665_R_EQ_BPF3_A2: + case RT5665_L_EQ_BPF3_H0: + case RT5665_R_EQ_BPF3_H0: + case RT5665_L_EQ_BPF4_A1: + case RT5665_R_EQ_BPF4_A1: + case RT5665_L_EQ_BPF4_A2: + case RT5665_R_EQ_BPF4_A2: + case RT5665_L_EQ_BPF4_H0: + case RT5665_R_EQ_BPF4_H0: + case RT5665_L_EQ_HPF1_A1: + case RT5665_R_EQ_HPF1_A1: + case RT5665_L_EQ_HPF1_H0: + case RT5665_R_EQ_HPF1_H0: + case RT5665_L_EQ_PRE_VOL: + case RT5665_R_EQ_PRE_VOL: + case RT5665_L_EQ_POST_VOL: + case RT5665_R_EQ_POST_VOL: + case RT5665_SCAN_MODE_CTRL: + case RT5665_I2C_MODE: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(mono_vol_tlv, -1400, 150, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); +static const DECLARE_TLV_DB_SCALE(in_bst_tlv, -1200, 75, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* Interface data select */ +static const char * const rt5665_data_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_1_01_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_1_DS_ADC_SLOT01_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_1_23_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_1_DS_ADC_SLOT23_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_1_45_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_1_DS_ADC_SLOT45_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_1_67_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_1_DS_ADC_SLOT67_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_2_01_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_2_DS_ADC_SLOT01_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_2_23_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_2_DS_ADC_SLOT23_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_2_45_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_2_DS_ADC_SLOT45_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if1_2_67_adc_enum, + RT5665_TDM_CTRL_2, RT5665_I2S1_2_DS_ADC_SLOT67_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if2_1_dac_enum, + RT5665_DIG_INF2_DATA, RT5665_IF2_1_DAC_SEL_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if2_1_adc_enum, + RT5665_DIG_INF2_DATA, RT5665_IF2_1_ADC_SEL_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if2_2_dac_enum, + RT5665_DIG_INF2_DATA, RT5665_IF2_2_DAC_SEL_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if2_2_adc_enum, + RT5665_DIG_INF2_DATA, RT5665_IF2_2_ADC_SEL_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if3_dac_enum, + RT5665_DIG_INF3_DATA, RT5665_IF3_DAC_SEL_SFT, rt5665_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5665_if3_adc_enum, + RT5665_DIG_INF3_DATA, RT5665_IF3_ADC_SEL_SFT, rt5665_data_select); + +static const struct snd_kcontrol_new rt5665_if1_1_01_adc_swap_mux = + SOC_DAPM_ENUM("IF1_1 01 ADC Swap Mux", rt5665_if1_1_01_adc_enum); + +static const struct snd_kcontrol_new rt5665_if1_1_23_adc_swap_mux = + SOC_DAPM_ENUM("IF1_1 23 ADC Swap Mux", rt5665_if1_1_23_adc_enum); + +static const struct snd_kcontrol_new rt5665_if1_1_45_adc_swap_mux = + SOC_DAPM_ENUM("IF1_1 45 ADC Swap Mux", rt5665_if1_1_45_adc_enum); + +static const struct snd_kcontrol_new rt5665_if1_1_67_adc_swap_mux = + SOC_DAPM_ENUM("IF1_1 67 ADC Swap Mux", rt5665_if1_1_67_adc_enum); + +static const struct snd_kcontrol_new rt5665_if1_2_01_adc_swap_mux = + SOC_DAPM_ENUM("IF1_2 01 ADC Swap Mux", rt5665_if1_2_01_adc_enum); + +static const struct snd_kcontrol_new rt5665_if1_2_23_adc_swap_mux = + SOC_DAPM_ENUM("IF1_2 23 ADC1 Swap Mux", rt5665_if1_2_23_adc_enum); + +static const struct snd_kcontrol_new rt5665_if1_2_45_adc_swap_mux = + SOC_DAPM_ENUM("IF1_2 45 ADC1 Swap Mux", rt5665_if1_2_45_adc_enum); + +static const struct snd_kcontrol_new rt5665_if1_2_67_adc_swap_mux = + SOC_DAPM_ENUM("IF1_2 67 ADC1 Swap Mux", rt5665_if1_2_67_adc_enum); + +static const struct snd_kcontrol_new rt5665_if2_1_dac_swap_mux = + SOC_DAPM_ENUM("IF2_1 DAC Swap Source", rt5665_if2_1_dac_enum); + +static const struct snd_kcontrol_new rt5665_if2_1_adc_swap_mux = + SOC_DAPM_ENUM("IF2_1 ADC Swap Source", rt5665_if2_1_adc_enum); + +static const struct snd_kcontrol_new rt5665_if2_2_dac_swap_mux = + SOC_DAPM_ENUM("IF2_2 DAC Swap Source", rt5665_if2_2_dac_enum); + +static const struct snd_kcontrol_new rt5665_if2_2_adc_swap_mux = + SOC_DAPM_ENUM("IF2_2 ADC Swap Source", rt5665_if2_2_adc_enum); + +static const struct snd_kcontrol_new rt5665_if3_dac_swap_mux = + SOC_DAPM_ENUM("IF3 DAC Swap Source", rt5665_if3_dac_enum); + +static const struct snd_kcontrol_new rt5665_if3_adc_swap_mux = + SOC_DAPM_ENUM("IF3 ADC Swap Source", rt5665_if3_adc_enum); + +static int rt5665_hp_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int ret = snd_soc_put_volsw(kcontrol, ucontrol); + + if (snd_soc_component_read32(component, RT5665_STO_NG2_CTRL_1) & RT5665_NG2_EN) { + snd_soc_component_update_bits(component, RT5665_STO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_DIS); + snd_soc_component_update_bits(component, RT5665_STO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_EN); + } + + return ret; +} + +static int rt5665_mono_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int ret = snd_soc_put_volsw(kcontrol, ucontrol); + + if (snd_soc_component_read32(component, RT5665_MONO_NG2_CTRL_1) & RT5665_NG2_EN) { + snd_soc_component_update_bits(component, RT5665_MONO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_DIS); + snd_soc_component_update_bits(component, RT5665_MONO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_EN); + } + + return ret; +} + +/** + * rt5665_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5665 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the codec driver will turn on ASRC + * for these filters if ASRC is selected as their clock source. + */ +int rt5665_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + unsigned int asrc2_mask = 0; + unsigned int asrc2_value = 0; + unsigned int asrc3_mask = 0; + unsigned int asrc3_value = 0; + + switch (clk_src) { + case RT5665_CLK_SEL_SYS: + case RT5665_CLK_SEL_I2S1_ASRC: + case RT5665_CLK_SEL_I2S2_ASRC: + case RT5665_CLK_SEL_I2S3_ASRC: + case RT5665_CLK_SEL_SYS2: + case RT5665_CLK_SEL_SYS3: + case RT5665_CLK_SEL_SYS4: + break; + + default: + return -EINVAL; + } + + if (filter_mask & RT5665_DA_STEREO1_FILTER) { + asrc2_mask |= RT5665_DA_STO1_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5665_DA_STO1_CLK_SEL_MASK) + | (clk_src << RT5665_DA_STO1_CLK_SEL_SFT); + } + + if (filter_mask & RT5665_DA_STEREO2_FILTER) { + asrc2_mask |= RT5665_DA_STO2_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5665_DA_STO2_CLK_SEL_MASK) + | (clk_src << RT5665_DA_STO2_CLK_SEL_SFT); + } + + if (filter_mask & RT5665_DA_MONO_L_FILTER) { + asrc2_mask |= RT5665_DA_MONOL_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5665_DA_MONOL_CLK_SEL_MASK) + | (clk_src << RT5665_DA_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5665_DA_MONO_R_FILTER) { + asrc2_mask |= RT5665_DA_MONOR_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5665_DA_MONOR_CLK_SEL_MASK) + | (clk_src << RT5665_DA_MONOR_CLK_SEL_SFT); + } + + if (filter_mask & RT5665_AD_STEREO1_FILTER) { + asrc3_mask |= RT5665_AD_STO1_CLK_SEL_MASK; + asrc3_value = (asrc2_value & ~RT5665_AD_STO1_CLK_SEL_MASK) + | (clk_src << RT5665_AD_STO1_CLK_SEL_SFT); + } + + if (filter_mask & RT5665_AD_STEREO2_FILTER) { + asrc3_mask |= RT5665_AD_STO2_CLK_SEL_MASK; + asrc3_value = (asrc2_value & ~RT5665_AD_STO2_CLK_SEL_MASK) + | (clk_src << RT5665_AD_STO2_CLK_SEL_SFT); + } + + if (filter_mask & RT5665_AD_MONO_L_FILTER) { + asrc3_mask |= RT5665_AD_MONOL_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5665_AD_MONOL_CLK_SEL_MASK) + | (clk_src << RT5665_AD_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5665_AD_MONO_R_FILTER) { + asrc3_mask |= RT5665_AD_MONOR_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5665_AD_MONOR_CLK_SEL_MASK) + | (clk_src << RT5665_AD_MONOR_CLK_SEL_SFT); + } + + if (asrc2_mask) + snd_soc_component_update_bits(component, RT5665_ASRC_2, + asrc2_mask, asrc2_value); + + if (asrc3_mask) + snd_soc_component_update_bits(component, RT5665_ASRC_3, + asrc3_mask, asrc3_value); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5665_sel_asrc_clk_src); + +static int rt5665_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read32(component, RT5665_4BTN_IL_CMD_1); + btn_type = val & 0xfff0; + snd_soc_component_write(component, RT5665_4BTN_IL_CMD_1, val); + + return btn_type; +} + +static void rt5665_enable_push_button_irq(struct snd_soc_component *component, + bool enable) +{ + if (enable) { + snd_soc_component_write(component, RT5665_4BTN_IL_CMD_1, 0x0003); + snd_soc_component_update_bits(component, RT5665_SAR_IL_CMD_9, 0x1, 0x1); + snd_soc_component_write(component, RT5665_IL_CMD_1, 0x0048); + snd_soc_component_update_bits(component, RT5665_4BTN_IL_CMD_2, + RT5665_4BTN_IL_MASK | RT5665_4BTN_IL_RST_MASK, + RT5665_4BTN_IL_EN | RT5665_4BTN_IL_NOR); + snd_soc_component_update_bits(component, RT5665_IRQ_CTRL_3, + RT5665_IL_IRQ_MASK, RT5665_IL_IRQ_EN); + } else { + snd_soc_component_update_bits(component, RT5665_IRQ_CTRL_3, + RT5665_IL_IRQ_MASK, RT5665_IL_IRQ_DIS); + snd_soc_component_update_bits(component, RT5665_4BTN_IL_CMD_2, + RT5665_4BTN_IL_MASK, RT5665_4BTN_IL_DIS); + snd_soc_component_update_bits(component, RT5665_4BTN_IL_CMD_2, + RT5665_4BTN_IL_RST_MASK, RT5665_4BTN_IL_RST); + } +} + +/** + * rt5665_headset_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ +static int rt5665_headset_detect(struct snd_soc_component *component, int jack_insert) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + unsigned int sar_hs_type, val; + + if (jack_insert) { + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_sync(dapm); + + regmap_update_bits(rt5665->regmap, RT5665_MICBIAS_2, 0x100, + 0x100); + + regmap_read(rt5665->regmap, RT5665_GPIO_STA, &val); + if (val & 0x4) { + regmap_update_bits(rt5665->regmap, RT5665_EJD_CTRL_1, + 0x100, 0); + + regmap_read(rt5665->regmap, RT5665_GPIO_STA, &val); + while (val & 0x4) { + usleep_range(10000, 15000); + regmap_read(rt5665->regmap, RT5665_GPIO_STA, + &val); + } + } + + regmap_update_bits(rt5665->regmap, RT5665_EJD_CTRL_1, + 0x1a0, 0x120); + regmap_write(rt5665->regmap, RT5665_EJD_CTRL_3, 0x3424); + regmap_write(rt5665->regmap, RT5665_IL_CMD_1, 0x0048); + regmap_write(rt5665->regmap, RT5665_SAR_IL_CMD_1, 0xa291); + + usleep_range(10000, 15000); + + rt5665->sar_adc_value = snd_soc_component_read32(rt5665->component, + RT5665_SAR_IL_CMD_4) & 0x7ff; + + sar_hs_type = rt5665->pdata.sar_hs_type ? + rt5665->pdata.sar_hs_type : 729; + + if (rt5665->sar_adc_value > sar_hs_type) { + rt5665->jack_type = SND_JACK_HEADSET; + rt5665_enable_push_button_irq(component, true); + } else { + rt5665->jack_type = SND_JACK_HEADPHONE; + regmap_write(rt5665->regmap, RT5665_SAR_IL_CMD_1, + 0x2291); + regmap_update_bits(rt5665->regmap, RT5665_MICBIAS_2, + 0x100, 0); + snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_sync(dapm); + } + } else { + regmap_write(rt5665->regmap, RT5665_SAR_IL_CMD_1, 0x2291); + regmap_update_bits(rt5665->regmap, RT5665_MICBIAS_2, 0x100, 0); + snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + snd_soc_dapm_sync(dapm); + if (rt5665->jack_type == SND_JACK_HEADSET) + rt5665_enable_push_button_irq(component, false); + rt5665->jack_type = 0; + } + + dev_dbg(component->dev, "jack_type = %d\n", rt5665->jack_type); + return rt5665->jack_type; +} + +static irqreturn_t rt5665_irq(int irq, void *data) +{ + struct rt5665_priv *rt5665 = data; + + mod_delayed_work(system_power_efficient_wq, + &rt5665->jack_detect_work, msecs_to_jiffies(250)); + + return IRQ_HANDLED; +} + +static void rt5665_jd_check_handler(struct work_struct *work) +{ + struct rt5665_priv *rt5665 = container_of(work, struct rt5665_priv, + jd_check_work.work); + + if (snd_soc_component_read32(rt5665->component, RT5665_AJD1_CTRL) & 0x0010) { + /* jack out */ + rt5665->jack_type = rt5665_headset_detect(rt5665->component, 0); + + snd_soc_jack_report(rt5665->hs_jack, rt5665->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + } else { + schedule_delayed_work(&rt5665->jd_check_work, 500); + } +} + +static int rt5665_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + + switch (rt5665->pdata.jd_src) { + case RT5665_JD1: + regmap_update_bits(rt5665->regmap, RT5665_GPIO_CTRL_1, + RT5665_GP1_PIN_MASK, RT5665_GP1_PIN_IRQ); + regmap_update_bits(rt5665->regmap, RT5665_RC_CLK_CTRL, + 0xc000, 0xc000); + regmap_update_bits(rt5665->regmap, RT5665_PWR_ANLG_2, + RT5665_PWR_JD1, RT5665_PWR_JD1); + regmap_update_bits(rt5665->regmap, RT5665_IRQ_CTRL_1, 0x8, 0x8); + break; + + case RT5665_JD_NULL: + break; + + default: + dev_warn(component->dev, "Wrong JD source\n"); + break; + } + + rt5665->hs_jack = hs_jack; + + return 0; +} + +static void rt5665_jack_detect_handler(struct work_struct *work) +{ + struct rt5665_priv *rt5665 = + container_of(work, struct rt5665_priv, jack_detect_work.work); + int val, btn_type; + + while (!rt5665->component) { + pr_debug("%s codec = null\n", __func__); + usleep_range(10000, 15000); + } + + while (!rt5665->component->card->instantiated) { + pr_debug("%s\n", __func__); + usleep_range(10000, 15000); + } + + while (!rt5665->calibration_done) { + pr_debug("%s calibration not ready\n", __func__); + usleep_range(10000, 15000); + } + + mutex_lock(&rt5665->calibrate_mutex); + + val = snd_soc_component_read32(rt5665->component, RT5665_AJD1_CTRL) & 0x0010; + if (!val) { + /* jack in */ + if (rt5665->jack_type == 0) { + /* jack was out, report jack type */ + rt5665->jack_type = + rt5665_headset_detect(rt5665->component, 1); + } else { + /* jack is already in, report button event */ + rt5665->jack_type = SND_JACK_HEADSET; + btn_type = rt5665_button_detect(rt5665->component); + /** + * rt5665 can report three kinds of button behavior, + * one click, double click and hold. However, + * currently we will report button pressed/released + * event. So all the three button behaviors are + * treated as button pressed. + */ + switch (btn_type) { + case 0x8000: + case 0x4000: + case 0x2000: + rt5665->jack_type |= SND_JACK_BTN_0; + break; + case 0x1000: + case 0x0800: + case 0x0400: + rt5665->jack_type |= SND_JACK_BTN_1; + break; + case 0x0200: + case 0x0100: + case 0x0080: + rt5665->jack_type |= SND_JACK_BTN_2; + break; + case 0x0040: + case 0x0020: + case 0x0010: + rt5665->jack_type |= SND_JACK_BTN_3; + break; + case 0x0000: /* unpressed */ + break; + default: + btn_type = 0; + dev_err(rt5665->component->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + } + } else { + /* jack out */ + rt5665->jack_type = rt5665_headset_detect(rt5665->component, 0); + } + + snd_soc_jack_report(rt5665->hs_jack, rt5665->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (rt5665->jack_type & (SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3)) + schedule_delayed_work(&rt5665->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5665->jd_check_work); + + mutex_unlock(&rt5665->calibrate_mutex); +} + +static const char * const rt5665_clk_sync[] = { + "I2S1_1", "I2S1_2", "I2S2", "I2S3", "IF2 Slave", "IF3 Slave" +}; + +static const struct soc_enum rt5665_enum[] = { + SOC_ENUM_SINGLE(RT5665_I2S1_SDP, 11, 5, rt5665_clk_sync), + SOC_ENUM_SINGLE(RT5665_I2S2_SDP, 11, 5, rt5665_clk_sync), + SOC_ENUM_SINGLE(RT5665_I2S3_SDP, 11, 5, rt5665_clk_sync), +}; + +static const struct snd_kcontrol_new rt5665_snd_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE_R_EXT_TLV("Headphone Playback Volume", RT5665_HPL_GAIN, + RT5665_HPR_GAIN, RT5665_G_HP_SFT, 15, 1, snd_soc_get_volsw, + rt5665_hp_vol_put, hp_vol_tlv), + + /* Mono Output Volume */ + SOC_SINGLE_EXT_TLV("Mono Playback Volume", RT5665_MONO_GAIN, + RT5665_L_VOL_SFT, 15, 1, snd_soc_get_volsw, + rt5665_mono_vol_put, mono_vol_tlv), + + SOC_SINGLE_TLV("MONOVOL Playback Volume", RT5665_MONO_OUT, + RT5665_L_VOL_SFT, 39, 1, out_vol_tlv), + + /* Output Volume */ + SOC_DOUBLE_TLV("OUT Playback Volume", RT5665_LOUT, RT5665_L_VOL_SFT, + RT5665_R_VOL_SFT, 39, 1, out_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5665_DAC1_DIG_VOL, + RT5665_L_VOL_SFT, RT5665_R_VOL_SFT, 175, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("DAC2 Playback Volume", RT5665_DAC2_DIG_VOL, + RT5665_L_VOL_SFT, RT5665_R_VOL_SFT, 175, 0, dac_vol_tlv), + SOC_DOUBLE("DAC2 Playback Switch", RT5665_DAC2_CTRL, + RT5665_M_DAC2_L_VOL_SFT, RT5665_M_DAC2_R_VOL_SFT, 1, 1), + + /* IN1/IN2/IN3/IN4 Volume */ + SOC_SINGLE_TLV("IN1 Boost Volume", RT5665_IN1_IN2, + RT5665_BST1_SFT, 69, 0, in_bst_tlv), + SOC_SINGLE_TLV("IN2 Boost Volume", RT5665_IN1_IN2, + RT5665_BST2_SFT, 69, 0, in_bst_tlv), + SOC_SINGLE_TLV("IN3 Boost Volume", RT5665_IN3_IN4, + RT5665_BST3_SFT, 69, 0, in_bst_tlv), + SOC_SINGLE_TLV("IN4 Boost Volume", RT5665_IN3_IN4, + RT5665_BST4_SFT, 69, 0, in_bst_tlv), + SOC_SINGLE_TLV("CBJ Boost Volume", RT5665_CBJ_BST_CTRL, + RT5665_BST_CBJ_SFT, 8, 0, bst_tlv), + + /* INL/INR Volume Control */ + SOC_DOUBLE_TLV("IN Capture Volume", RT5665_INL1_INR1_VOL, + RT5665_INL_VOL_SFT, RT5665_INR_VOL_SFT, 31, 1, in_vol_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("STO1 ADC Capture Switch", RT5665_STO1_ADC_DIG_VOL, + RT5665_L_MUTE_SFT, RT5665_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("STO1 ADC Capture Volume", RT5665_STO1_ADC_DIG_VOL, + RT5665_L_VOL_SFT, RT5665_R_VOL_SFT, 127, 0, adc_vol_tlv), + SOC_DOUBLE("Mono ADC Capture Switch", RT5665_MONO_ADC_DIG_VOL, + RT5665_L_MUTE_SFT, RT5665_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5665_MONO_ADC_DIG_VOL, + RT5665_L_VOL_SFT, RT5665_R_VOL_SFT, 127, 0, adc_vol_tlv), + SOC_DOUBLE("STO2 ADC Capture Switch", RT5665_STO2_ADC_DIG_VOL, + RT5665_L_MUTE_SFT, RT5665_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("STO2 ADC Capture Volume", RT5665_STO2_ADC_DIG_VOL, + RT5665_L_VOL_SFT, RT5665_R_VOL_SFT, 127, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Gain Volume", RT5665_STO1_ADC_BOOST, + RT5665_STO1_ADC_L_BST_SFT, RT5665_STO1_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + SOC_DOUBLE_TLV("Mono ADC Boost Gain Volume", RT5665_MONO_ADC_BOOST, + RT5665_MONO_ADC_L_BST_SFT, RT5665_MONO_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + SOC_DOUBLE_TLV("STO2 ADC Boost Gain Volume", RT5665_STO2_ADC_BOOST, + RT5665_STO2_ADC_L_BST_SFT, RT5665_STO2_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + /* I2S3 CLK Source */ + SOC_ENUM("I2S1 Master Clk Sel", rt5665_enum[0]), + SOC_ENUM("I2S2 Master Clk Sel", rt5665_enum[1]), + SOC_ENUM("I2S3 Master Clk Sel", rt5665_enum[2]), +}; + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + * Choose dmic clock between 1MHz and 3MHz. + * It is better for clock to approximate 3MHz. + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + int pd, idx = -EINVAL; + + pd = rl6231_get_pre_div(rt5665->regmap, + RT5665_ADDA_CLK_1, RT5665_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rt5665->sysclk / pd); + + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else { + snd_soc_component_update_bits(component, RT5665_DMIC_CTRL_1, + RT5665_DMIC_CLK_MASK, idx << RT5665_DMIC_CLK_SFT); + } + return idx; +} + +static int rt5665_charge_pump_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, RT5665_HP_CHARGE_PUMP_1, + RT5665_PM_HP_MASK | RT5665_OSW_L_MASK, + RT5665_PM_HP_HV | RT5665_OSW_L_EN); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, RT5665_HP_CHARGE_PUMP_1, + RT5665_PM_HP_MASK | RT5665_OSW_L_MASK, + RT5665_PM_HP_LV | RT5665_OSW_L_DIS); + break; + default: + return 0; + } + + return 0; +} + +static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int val; + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + val = snd_soc_component_read32(component, RT5665_GLB_CLK); + val &= RT5665_SCLK_SRC_MASK; + if (val == RT5665_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int reg, shift, val; + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (w->shift) { + case RT5665_ADC_MONO_R_ASRC_SFT: + reg = RT5665_ASRC_3; + shift = RT5665_AD_MONOR_CLK_SEL_SFT; + break; + case RT5665_ADC_MONO_L_ASRC_SFT: + reg = RT5665_ASRC_3; + shift = RT5665_AD_MONOL_CLK_SEL_SFT; + break; + case RT5665_ADC_STO1_ASRC_SFT: + reg = RT5665_ASRC_3; + shift = RT5665_AD_STO1_CLK_SEL_SFT; + break; + case RT5665_ADC_STO2_ASRC_SFT: + reg = RT5665_ASRC_3; + shift = RT5665_AD_STO2_CLK_SEL_SFT; + break; + case RT5665_DAC_MONO_R_ASRC_SFT: + reg = RT5665_ASRC_2; + shift = RT5665_DA_MONOR_CLK_SEL_SFT; + break; + case RT5665_DAC_MONO_L_ASRC_SFT: + reg = RT5665_ASRC_2; + shift = RT5665_DA_MONOL_CLK_SEL_SFT; + break; + case RT5665_DAC_STO1_ASRC_SFT: + reg = RT5665_ASRC_2; + shift = RT5665_DA_STO1_CLK_SEL_SFT; + break; + case RT5665_DAC_STO2_ASRC_SFT: + reg = RT5665_ASRC_2; + shift = RT5665_DA_STO2_CLK_SEL_SFT; + break; + default: + return 0; + } + + val = (snd_soc_component_read32(component, reg) >> shift) & 0xf; + switch (val) { + case RT5665_CLK_SEL_I2S1_ASRC: + case RT5665_CLK_SEL_I2S2_ASRC: + case RT5665_CLK_SEL_I2S3_ASRC: + /* I2S_Pre_Div1 should be 1 in asrc mode */ + snd_soc_component_update_bits(component, RT5665_ADDA_CLK_1, + RT5665_I2S_PD1_MASK, RT5665_I2S_PD1_2); + return 1; + default: + return 0; + } + +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5665_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5665_STO1_ADC_MIXER, + RT5665_M_STO1_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5665_STO1_ADC_MIXER, + RT5665_M_STO1_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5665_STO1_ADC_MIXER, + RT5665_M_STO1_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5665_STO1_ADC_MIXER, + RT5665_M_STO1_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_sto2_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5665_STO2_ADC_MIXER, + RT5665_M_STO2_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5665_STO2_ADC_MIXER, + RT5665_M_STO2_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_sto2_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5665_STO2_ADC_MIXER, + RT5665_M_STO2_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5665_STO2_ADC_MIXER, + RT5665_M_STO2_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_mono_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5665_MONO_ADC_MIXER, + RT5665_M_MONO_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5665_MONO_ADC_MIXER, + RT5665_M_MONO_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_mono_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5665_MONO_ADC_MIXER, + RT5665_M_MONO_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5665_MONO_ADC_MIXER, + RT5665_M_MONO_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5665_AD_DA_MIXER, + RT5665_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5665_AD_DA_MIXER, + RT5665_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5665_AD_DA_MIXER, + RT5665_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5665_AD_DA_MIXER, + RT5665_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_sto1_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_L1_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_R1_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_L2_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_R2_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_sto1_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_L1_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_R1_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_L2_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5665_STO1_DAC_MIXER, + RT5665_M_DAC_R2_STO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_sto2_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5665_STO2_DAC_MIXER, + RT5665_M_DAC_L1_STO2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_STO2_DAC_MIXER, + RT5665_M_DAC_L2_STO2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L3 Switch", RT5665_STO2_DAC_MIXER, + RT5665_M_DAC_L3_STO2_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_sto2_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5665_STO2_DAC_MIXER, + RT5665_M_DAC_R1_STO2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5665_STO2_DAC_MIXER, + RT5665_M_DAC_R2_STO2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R3 Switch", RT5665_STO2_DAC_MIXER, + RT5665_M_DAC_R3_STO2_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_mono_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_L1_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_R1_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_L2_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_R2_MONO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_mono_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_L1_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_R1_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_L2_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5665_MONO_DAC_MIXER, + RT5665_M_DAC_R2_MONO_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5665_rec1_l_mix[] = { + SOC_DAPM_SINGLE("CBJ Switch", RT5665_REC1_L2_MIXER, + RT5665_M_CBJ_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5665_REC1_L2_MIXER, + RT5665_M_INL_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5665_REC1_L2_MIXER, + RT5665_M_INR_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5665_REC1_L2_MIXER, + RT5665_M_BST4_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5665_REC1_L2_MIXER, + RT5665_M_BST3_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5665_REC1_L2_MIXER, + RT5665_M_BST2_RM1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5665_REC1_L2_MIXER, + RT5665_M_BST1_RM1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_rec1_r_mix[] = { + SOC_DAPM_SINGLE("MONOVOL Switch", RT5665_REC1_R2_MIXER, + RT5665_M_AEC_REF_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5665_REC1_R2_MIXER, + RT5665_M_INR_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5665_REC1_R2_MIXER, + RT5665_M_BST4_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5665_REC1_R2_MIXER, + RT5665_M_BST3_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5665_REC1_R2_MIXER, + RT5665_M_BST2_RM1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5665_REC1_R2_MIXER, + RT5665_M_BST1_RM1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_rec2_l_mix[] = { + SOC_DAPM_SINGLE("INL Switch", RT5665_REC2_L2_MIXER, + RT5665_M_INL_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5665_REC2_L2_MIXER, + RT5665_M_INR_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("CBJ Switch", RT5665_REC2_L2_MIXER, + RT5665_M_CBJ_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5665_REC2_L2_MIXER, + RT5665_M_BST4_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5665_REC2_L2_MIXER, + RT5665_M_BST3_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5665_REC2_L2_MIXER, + RT5665_M_BST2_RM2_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5665_REC2_L2_MIXER, + RT5665_M_BST1_RM2_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_rec2_r_mix[] = { + SOC_DAPM_SINGLE("MONOVOL Switch", RT5665_REC2_R2_MIXER, + RT5665_M_MONOVOL_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5665_REC2_R2_MIXER, + RT5665_M_INL_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5665_REC2_R2_MIXER, + RT5665_M_INR_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5665_REC2_R2_MIXER, + RT5665_M_BST4_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5665_REC2_R2_MIXER, + RT5665_M_BST3_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5665_REC2_R2_MIXER, + RT5665_M_BST2_RM2_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5665_REC2_R2_MIXER, + RT5665_M_BST1_RM2_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_monovol_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_MONOMIX_IN_GAIN, + RT5665_M_DAC_L2_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIX2L Switch", RT5665_MONOMIX_IN_GAIN, + RT5665_M_RECMIC2L_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5665_MONOMIX_IN_GAIN, + RT5665_M_BST1_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5665_MONOMIX_IN_GAIN, + RT5665_M_BST2_MM_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5665_MONOMIX_IN_GAIN, + RT5665_M_BST3_MM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_out_l_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_OUT_L_MIXER, + RT5665_M_DAC_L2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5665_OUT_L_MIXER, + RT5665_M_IN_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5665_OUT_L_MIXER, + RT5665_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5665_OUT_L_MIXER, + RT5665_M_BST2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5665_OUT_L_MIXER, + RT5665_M_BST3_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_out_r_mix[] = { + SOC_DAPM_SINGLE("DAC R2 Switch", RT5665_OUT_R_MIXER, + RT5665_M_DAC_R2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5665_OUT_R_MIXER, + RT5665_M_IN_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5665_OUT_R_MIXER, + RT5665_M_BST2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST3 Switch", RT5665_OUT_R_MIXER, + RT5665_M_BST3_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST4 Switch", RT5665_OUT_R_MIXER, + RT5665_M_BST4_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_mono_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_MONOMIX_IN_GAIN, + RT5665_M_DAC_L2_MA_SFT, 1, 1), + SOC_DAPM_SINGLE("MONOVOL Switch", RT5665_MONOMIX_IN_GAIN, + RT5665_M_MONOVOL_MA_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_lout_l_mix[] = { + SOC_DAPM_SINGLE("DAC L2 Switch", RT5665_LOUT_MIXER, + RT5665_M_DAC_L2_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL L Switch", RT5665_LOUT_MIXER, + RT5665_M_OV_L_LM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5665_lout_r_mix[] = { + SOC_DAPM_SINGLE("DAC R2 Switch", RT5665_LOUT_MIXER, + RT5665_M_DAC_R2_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTVOL R Switch", RT5665_LOUT_MIXER, + RT5665_M_OV_R_LM_SFT, 1, 1), +}; + +/*DAC L2, DAC R2*/ +/*MX-17 [6:4], MX-17 [2:0]*/ +static const char * const rt5665_dac2_src[] = { + "IF1 DAC2", "IF2_1 DAC", "IF2_2 DAC", "IF3 DAC", "Mono ADC MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_dac_l2_enum, RT5665_DAC2_CTRL, + RT5665_DAC_L2_SEL_SFT, rt5665_dac2_src); + +static const struct snd_kcontrol_new rt5665_dac_l2_mux = + SOC_DAPM_ENUM("Digital DAC L2 Source", rt5665_dac_l2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_dac_r2_enum, RT5665_DAC2_CTRL, + RT5665_DAC_R2_SEL_SFT, rt5665_dac2_src); + +static const struct snd_kcontrol_new rt5665_dac_r2_mux = + SOC_DAPM_ENUM("Digital DAC R2 Source", rt5665_dac_r2_enum); + +/*DAC L3, DAC R3*/ +/*MX-1B [6:4], MX-1B [2:0]*/ +static const char * const rt5665_dac3_src[] = { + "IF1 DAC2", "IF2_1 DAC", "IF2_2 DAC", "IF3 DAC", "STO2 ADC MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_dac_l3_enum, RT5665_DAC3_CTRL, + RT5665_DAC_L3_SEL_SFT, rt5665_dac3_src); + +static const struct snd_kcontrol_new rt5665_dac_l3_mux = + SOC_DAPM_ENUM("Digital DAC L3 Source", rt5665_dac_l3_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_dac_r3_enum, RT5665_DAC3_CTRL, + RT5665_DAC_R3_SEL_SFT, rt5665_dac3_src); + +static const struct snd_kcontrol_new rt5665_dac_r3_mux = + SOC_DAPM_ENUM("Digital DAC R3 Source", rt5665_dac_r3_enum); + +/* STO1 ADC1 Source */ +/* MX-26 [13] [5] */ +static const char * const rt5665_sto1_adc1_src[] = { + "DD Mux", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_adc1l_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_ADC1L_SRC_SFT, rt5665_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5665_sto1_adc1l_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5665_sto1_adc1l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_adc1r_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_ADC1R_SRC_SFT, rt5665_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5665_sto1_adc1r_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5665_sto1_adc1r_enum); + +/* STO1 ADC Source */ +/* MX-26 [11:10] [3:2] */ +static const char * const rt5665_sto1_adc_src[] = { + "ADC1 L", "ADC1 R", "ADC2 L", "ADC2 R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_adcl_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_ADCL_SRC_SFT, rt5665_sto1_adc_src); + +static const struct snd_kcontrol_new rt5665_sto1_adcl_mux = + SOC_DAPM_ENUM("Stereo1 ADCL Source", rt5665_sto1_adcl_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_adcr_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_ADCR_SRC_SFT, rt5665_sto1_adc_src); + +static const struct snd_kcontrol_new rt5665_sto1_adcr_mux = + SOC_DAPM_ENUM("Stereo1 ADCR Source", rt5665_sto1_adcr_enum); + +/* STO1 ADC2 Source */ +/* MX-26 [12] [4] */ +static const char * const rt5665_sto1_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_adc2l_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_ADC2L_SRC_SFT, rt5665_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5665_sto1_adc2l_mux = + SOC_DAPM_ENUM("Stereo1 ADC2L Source", rt5665_sto1_adc2l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_adc2r_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_ADC2R_SRC_SFT, rt5665_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5665_sto1_adc2r_mux = + SOC_DAPM_ENUM("Stereo1 ADC2R Source", rt5665_sto1_adc2r_enum); + +/* STO1 DMIC Source */ +/* MX-26 [8] */ +static const char * const rt5665_sto1_dmic_src[] = { + "DMIC1", "DMIC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_dmic_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_DMIC_SRC_SFT, rt5665_sto1_dmic_src); + +static const struct snd_kcontrol_new rt5665_sto1_dmic_mux = + SOC_DAPM_ENUM("Stereo1 DMIC Mux", rt5665_sto1_dmic_enum); + +/* MX-26 [9] */ +static const char * const rt5665_sto1_dd_l_src[] = { + "STO2 DAC", "MONO DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_dd_l_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_DD_L_SRC_SFT, rt5665_sto1_dd_l_src); + +static const struct snd_kcontrol_new rt5665_sto1_dd_l_mux = + SOC_DAPM_ENUM("Stereo1 DD L Source", rt5665_sto1_dd_l_enum); + +/* MX-26 [1:0] */ +static const char * const rt5665_sto1_dd_r_src[] = { + "STO2 DAC", "MONO DAC", "AEC REF" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto1_dd_r_enum, RT5665_STO1_ADC_MIXER, + RT5665_STO1_DD_R_SRC_SFT, rt5665_sto1_dd_r_src); + +static const struct snd_kcontrol_new rt5665_sto1_dd_r_mux = + SOC_DAPM_ENUM("Stereo1 DD R Source", rt5665_sto1_dd_r_enum); + +/* MONO ADC L2 Source */ +/* MX-27 [12] */ +static const char * const rt5665_mono_adc_l2_src[] = { + "DAC MIXL", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_adc_l2_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_ADC_L2_SRC_SFT, rt5665_mono_adc_l2_src); + +static const struct snd_kcontrol_new rt5665_mono_adc_l2_mux = + SOC_DAPM_ENUM("Mono ADC L2 Source", rt5665_mono_adc_l2_enum); + + +/* MONO ADC L1 Source */ +/* MX-27 [13] */ +static const char * const rt5665_mono_adc_l1_src[] = { + "DD Mux", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_adc_l1_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_ADC_L1_SRC_SFT, rt5665_mono_adc_l1_src); + +static const struct snd_kcontrol_new rt5665_mono_adc_l1_mux = + SOC_DAPM_ENUM("Mono ADC L1 Source", rt5665_mono_adc_l1_enum); + +/* MX-27 [9][1]*/ +static const char * const rt5665_mono_dd_src[] = { + "STO2 DAC", "MONO DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_dd_l_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_DD_L_SRC_SFT, rt5665_mono_dd_src); + +static const struct snd_kcontrol_new rt5665_mono_dd_l_mux = + SOC_DAPM_ENUM("Mono DD L Source", rt5665_mono_dd_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_dd_r_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_DD_R_SRC_SFT, rt5665_mono_dd_src); + +static const struct snd_kcontrol_new rt5665_mono_dd_r_mux = + SOC_DAPM_ENUM("Mono DD R Source", rt5665_mono_dd_r_enum); + +/* MONO ADC L Source, MONO ADC R Source*/ +/* MX-27 [11:10], MX-27 [3:2] */ +static const char * const rt5665_mono_adc_src[] = { + "ADC1 L", "ADC1 R", "ADC2 L", "ADC2 R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_adc_l_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_ADC_L_SRC_SFT, rt5665_mono_adc_src); + +static const struct snd_kcontrol_new rt5665_mono_adc_l_mux = + SOC_DAPM_ENUM("Mono ADC L Source", rt5665_mono_adc_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_adcr_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_ADC_R_SRC_SFT, rt5665_mono_adc_src); + +static const struct snd_kcontrol_new rt5665_mono_adc_r_mux = + SOC_DAPM_ENUM("Mono ADC R Source", rt5665_mono_adcr_enum); + +/* MONO DMIC L Source */ +/* MX-27 [8] */ +static const char * const rt5665_mono_dmic_l_src[] = { + "DMIC1 L", "DMIC2 L" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_dmic_l_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_DMIC_L_SRC_SFT, rt5665_mono_dmic_l_src); + +static const struct snd_kcontrol_new rt5665_mono_dmic_l_mux = + SOC_DAPM_ENUM("Mono DMIC L Source", rt5665_mono_dmic_l_enum); + +/* MONO ADC R2 Source */ +/* MX-27 [4] */ +static const char * const rt5665_mono_adc_r2_src[] = { + "DAC MIXR", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_adc_r2_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_ADC_R2_SRC_SFT, rt5665_mono_adc_r2_src); + +static const struct snd_kcontrol_new rt5665_mono_adc_r2_mux = + SOC_DAPM_ENUM("Mono ADC R2 Source", rt5665_mono_adc_r2_enum); + +/* MONO ADC R1 Source */ +/* MX-27 [5] */ +static const char * const rt5665_mono_adc_r1_src[] = { + "DD Mux", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_adc_r1_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_ADC_R1_SRC_SFT, rt5665_mono_adc_r1_src); + +static const struct snd_kcontrol_new rt5665_mono_adc_r1_mux = + SOC_DAPM_ENUM("Mono ADC R1 Source", rt5665_mono_adc_r1_enum); + +/* MONO DMIC R Source */ +/* MX-27 [0] */ +static const char * const rt5665_mono_dmic_r_src[] = { + "DMIC1 R", "DMIC2 R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_mono_dmic_r_enum, RT5665_MONO_ADC_MIXER, + RT5665_MONO_DMIC_R_SRC_SFT, rt5665_mono_dmic_r_src); + +static const struct snd_kcontrol_new rt5665_mono_dmic_r_mux = + SOC_DAPM_ENUM("Mono DMIC R Source", rt5665_mono_dmic_r_enum); + + +/* STO2 ADC1 Source */ +/* MX-28 [13] [5] */ +static const char * const rt5665_sto2_adc1_src[] = { + "DD Mux", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_adc1l_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_ADC1L_SRC_SFT, rt5665_sto2_adc1_src); + +static const struct snd_kcontrol_new rt5665_sto2_adc1l_mux = + SOC_DAPM_ENUM("Stereo2 ADC1L Source", rt5665_sto2_adc1l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_adc1r_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_ADC1R_SRC_SFT, rt5665_sto2_adc1_src); + +static const struct snd_kcontrol_new rt5665_sto2_adc1r_mux = + SOC_DAPM_ENUM("Stereo2 ADC1L Source", rt5665_sto2_adc1r_enum); + +/* STO2 ADC Source */ +/* MX-28 [11:10] [3:2] */ +static const char * const rt5665_sto2_adc_src[] = { + "ADC1 L", "ADC1 R", "ADC2 L" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_adcl_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_ADCL_SRC_SFT, rt5665_sto2_adc_src); + +static const struct snd_kcontrol_new rt5665_sto2_adcl_mux = + SOC_DAPM_ENUM("Stereo2 ADCL Source", rt5665_sto2_adcl_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_adcr_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_ADCR_SRC_SFT, rt5665_sto2_adc_src); + +static const struct snd_kcontrol_new rt5665_sto2_adcr_mux = + SOC_DAPM_ENUM("Stereo2 ADCR Source", rt5665_sto2_adcr_enum); + +/* STO2 ADC2 Source */ +/* MX-28 [12] [4] */ +static const char * const rt5665_sto2_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_adc2l_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_ADC2L_SRC_SFT, rt5665_sto2_adc2_src); + +static const struct snd_kcontrol_new rt5665_sto2_adc2l_mux = + SOC_DAPM_ENUM("Stereo2 ADC2L Source", rt5665_sto2_adc2l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_adc2r_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_ADC2R_SRC_SFT, rt5665_sto2_adc2_src); + +static const struct snd_kcontrol_new rt5665_sto2_adc2r_mux = + SOC_DAPM_ENUM("Stereo2 ADC2R Source", rt5665_sto2_adc2r_enum); + +/* STO2 DMIC Source */ +/* MX-28 [8] */ +static const char * const rt5665_sto2_dmic_src[] = { + "DMIC1", "DMIC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_dmic_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_DMIC_SRC_SFT, rt5665_sto2_dmic_src); + +static const struct snd_kcontrol_new rt5665_sto2_dmic_mux = + SOC_DAPM_ENUM("Stereo2 DMIC Source", rt5665_sto2_dmic_enum); + +/* MX-28 [9] */ +static const char * const rt5665_sto2_dd_l_src[] = { + "STO2 DAC", "MONO DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_dd_l_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_DD_L_SRC_SFT, rt5665_sto2_dd_l_src); + +static const struct snd_kcontrol_new rt5665_sto2_dd_l_mux = + SOC_DAPM_ENUM("Stereo2 DD L Source", rt5665_sto2_dd_l_enum); + +/* MX-28 [1] */ +static const char * const rt5665_sto2_dd_r_src[] = { + "STO2 DAC", "MONO DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_sto2_dd_r_enum, RT5665_STO2_ADC_MIXER, + RT5665_STO2_DD_R_SRC_SFT, rt5665_sto2_dd_r_src); + +static const struct snd_kcontrol_new rt5665_sto2_dd_r_mux = + SOC_DAPM_ENUM("Stereo2 DD R Source", rt5665_sto2_dd_r_enum); + +/* DAC R1 Source, DAC L1 Source*/ +/* MX-29 [11:10], MX-29 [9:8]*/ +static const char * const rt5665_dac1_src[] = { + "IF1 DAC1", "IF2_1 DAC", "IF2_2 DAC", "IF3 DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_dac_r1_enum, RT5665_AD_DA_MIXER, + RT5665_DAC1_R_SEL_SFT, rt5665_dac1_src); + +static const struct snd_kcontrol_new rt5665_dac_r1_mux = + SOC_DAPM_ENUM("DAC R1 Source", rt5665_dac_r1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_dac_l1_enum, RT5665_AD_DA_MIXER, + RT5665_DAC1_L_SEL_SFT, rt5665_dac1_src); + +static const struct snd_kcontrol_new rt5665_dac_l1_mux = + SOC_DAPM_ENUM("DAC L1 Source", rt5665_dac_l1_enum); + +/* DAC Digital Mixer L Source, DAC Digital Mixer R Source*/ +/* MX-2D [13:12], MX-2D [9:8]*/ +static const char * const rt5665_dig_dac_mix_src[] = { + "Stereo1 DAC Mixer", "Stereo2 DAC Mixer", "Mono DAC Mixer" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_dig_dac_mixl_enum, RT5665_A_DAC1_MUX, + RT5665_DAC_MIX_L_SFT, rt5665_dig_dac_mix_src); + +static const struct snd_kcontrol_new rt5665_dig_dac_mixl_mux = + SOC_DAPM_ENUM("DAC Digital Mixer L Source", rt5665_dig_dac_mixl_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_dig_dac_mixr_enum, RT5665_A_DAC1_MUX, + RT5665_DAC_MIX_R_SFT, rt5665_dig_dac_mix_src); + +static const struct snd_kcontrol_new rt5665_dig_dac_mixr_mux = + SOC_DAPM_ENUM("DAC Digital Mixer R Source", rt5665_dig_dac_mixr_enum); + +/* Analog DAC L1 Source, Analog DAC R1 Source*/ +/* MX-2D [5:4], MX-2D [1:0]*/ +static const char * const rt5665_alg_dac1_src[] = { + "Stereo1 DAC Mixer", "DAC1", "DMIC1" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_alg_dac_l1_enum, RT5665_A_DAC1_MUX, + RT5665_A_DACL1_SFT, rt5665_alg_dac1_src); + +static const struct snd_kcontrol_new rt5665_alg_dac_l1_mux = + SOC_DAPM_ENUM("Analog DAC L1 Source", rt5665_alg_dac_l1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_alg_dac_r1_enum, RT5665_A_DAC1_MUX, + RT5665_A_DACR1_SFT, rt5665_alg_dac1_src); + +static const struct snd_kcontrol_new rt5665_alg_dac_r1_mux = + SOC_DAPM_ENUM("Analog DAC R1 Source", rt5665_alg_dac_r1_enum); + +/* Analog DAC LR Source, Analog DAC R2 Source*/ +/* MX-2E [5:4], MX-2E [0]*/ +static const char * const rt5665_alg_dac2_src[] = { + "Mono DAC Mixer", "DAC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_alg_dac_l2_enum, RT5665_A_DAC2_MUX, + RT5665_A_DACL2_SFT, rt5665_alg_dac2_src); + +static const struct snd_kcontrol_new rt5665_alg_dac_l2_mux = + SOC_DAPM_ENUM("Analog DAC L2 Source", rt5665_alg_dac_l2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_alg_dac_r2_enum, RT5665_A_DAC2_MUX, + RT5665_A_DACR2_SFT, rt5665_alg_dac2_src); + +static const struct snd_kcontrol_new rt5665_alg_dac_r2_mux = + SOC_DAPM_ENUM("Analog DAC R2 Source", rt5665_alg_dac_r2_enum); + +/* Interface2 ADC Data Input*/ +/* MX-2F [14:12] */ +static const char * const rt5665_if2_1_adc_in_src[] = { + "STO1 ADC", "STO2 ADC", "MONO ADC", "IF1 DAC1", + "IF1 DAC2", "IF2_2 DAC", "IF3 DAC", "DAC1 MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if2_1_adc_in_enum, RT5665_DIG_INF2_DATA, + RT5665_IF2_1_ADC_IN_SFT, rt5665_if2_1_adc_in_src); + +static const struct snd_kcontrol_new rt5665_if2_1_adc_in_mux = + SOC_DAPM_ENUM("IF2_1 ADC IN Source", rt5665_if2_1_adc_in_enum); + +/* MX-2F [6:4] */ +static const char * const rt5665_if2_2_adc_in_src[] = { + "STO1 ADC", "STO2 ADC", "MONO ADC", "IF1 DAC1", + "IF1 DAC2", "IF2_1 DAC", "IF3 DAC", "DAC1 MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if2_2_adc_in_enum, RT5665_DIG_INF2_DATA, + RT5665_IF2_2_ADC_IN_SFT, rt5665_if2_2_adc_in_src); + +static const struct snd_kcontrol_new rt5665_if2_2_adc_in_mux = + SOC_DAPM_ENUM("IF2_1 ADC IN Source", rt5665_if2_2_adc_in_enum); + +/* Interface3 ADC Data Input*/ +/* MX-30 [6:4] */ +static const char * const rt5665_if3_adc_in_src[] = { + "STO1 ADC", "STO2 ADC", "MONO ADC", "IF1 DAC1", + "IF1 DAC2", "IF2_1 DAC", "IF2_2 DAC", "DAC1 MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if3_adc_in_enum, RT5665_DIG_INF3_DATA, + RT5665_IF3_ADC_IN_SFT, rt5665_if3_adc_in_src); + +static const struct snd_kcontrol_new rt5665_if3_adc_in_mux = + SOC_DAPM_ENUM("IF3 ADC IN Source", rt5665_if3_adc_in_enum); + +/* PDM 1 L/R*/ +/* MX-31 [11:10] [9:8] */ +static const char * const rt5665_pdm_src[] = { + "Stereo1 DAC", "Stereo2 DAC", "Mono DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_pdm_l_enum, RT5665_PDM_OUT_CTRL, + RT5665_PDM1_L_SFT, rt5665_pdm_src); + +static const struct snd_kcontrol_new rt5665_pdm_l_mux = + SOC_DAPM_ENUM("PDM L Source", rt5665_pdm_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_pdm_r_enum, RT5665_PDM_OUT_CTRL, + RT5665_PDM1_R_SFT, rt5665_pdm_src); + +static const struct snd_kcontrol_new rt5665_pdm_r_mux = + SOC_DAPM_ENUM("PDM R Source", rt5665_pdm_r_enum); + + +/* I2S1 TDM ADCDAT Source */ +/* MX-7a[10] */ +static const char * const rt5665_if1_1_adc1_data_src[] = { + "STO1 ADC", "IF2_1 DAC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if1_1_adc1_data_enum, RT5665_TDM_CTRL_3, + RT5665_IF1_ADC1_SEL_SFT, rt5665_if1_1_adc1_data_src); + +static const struct snd_kcontrol_new rt5665_if1_1_adc1_mux = + SOC_DAPM_ENUM("IF1_1 ADC1 Source", rt5665_if1_1_adc1_data_enum); + +/* MX-7a[9] */ +static const char * const rt5665_if1_1_adc2_data_src[] = { + "STO2 ADC", "IF2_2 DAC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if1_1_adc2_data_enum, RT5665_TDM_CTRL_3, + RT5665_IF1_ADC2_SEL_SFT, rt5665_if1_1_adc2_data_src); + +static const struct snd_kcontrol_new rt5665_if1_1_adc2_mux = + SOC_DAPM_ENUM("IF1_1 ADC2 Source", rt5665_if1_1_adc2_data_enum); + +/* MX-7a[8] */ +static const char * const rt5665_if1_1_adc3_data_src[] = { + "MONO ADC", "IF3 DAC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if1_1_adc3_data_enum, RT5665_TDM_CTRL_3, + RT5665_IF1_ADC3_SEL_SFT, rt5665_if1_1_adc3_data_src); + +static const struct snd_kcontrol_new rt5665_if1_1_adc3_mux = + SOC_DAPM_ENUM("IF1_1 ADC3 Source", rt5665_if1_1_adc3_data_enum); + +/* MX-7b[10] */ +static const char * const rt5665_if1_2_adc1_data_src[] = { + "STO1 ADC", "IF1 DAC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if1_2_adc1_data_enum, RT5665_TDM_CTRL_4, + RT5665_IF1_ADC1_SEL_SFT, rt5665_if1_2_adc1_data_src); + +static const struct snd_kcontrol_new rt5665_if1_2_adc1_mux = + SOC_DAPM_ENUM("IF1_2 ADC1 Source", rt5665_if1_2_adc1_data_enum); + +/* MX-7b[9] */ +static const char * const rt5665_if1_2_adc2_data_src[] = { + "STO2 ADC", "IF2_1 DAC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if1_2_adc2_data_enum, RT5665_TDM_CTRL_4, + RT5665_IF1_ADC2_SEL_SFT, rt5665_if1_2_adc2_data_src); + +static const struct snd_kcontrol_new rt5665_if1_2_adc2_mux = + SOC_DAPM_ENUM("IF1_2 ADC2 Source", rt5665_if1_2_adc2_data_enum); + +/* MX-7b[8] */ +static const char * const rt5665_if1_2_adc3_data_src[] = { + "MONO ADC", "IF2_2 DAC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if1_2_adc3_data_enum, RT5665_TDM_CTRL_4, + RT5665_IF1_ADC3_SEL_SFT, rt5665_if1_2_adc3_data_src); + +static const struct snd_kcontrol_new rt5665_if1_2_adc3_mux = + SOC_DAPM_ENUM("IF1_2 ADC3 Source", rt5665_if1_2_adc3_data_enum); + +/* MX-7b[7] */ +static const char * const rt5665_if1_2_adc4_data_src[] = { + "DAC1", "IF3 DAC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_if1_2_adc4_data_enum, RT5665_TDM_CTRL_4, + RT5665_IF1_ADC4_SEL_SFT, rt5665_if1_2_adc4_data_src); + +static const struct snd_kcontrol_new rt5665_if1_2_adc4_mux = + SOC_DAPM_ENUM("IF1_2 ADC4 Source", rt5665_if1_2_adc4_data_enum); + +/* MX-7a[4:0] MX-7b[4:0] */ +static const char * const rt5665_tdm_adc_data_src[] = { + "1234", "1243", "1324", "1342", "1432", "1423", + "2134", "2143", "2314", "2341", "2431", "2413", + "3124", "3142", "3214", "3241", "3412", "3421", + "4123", "4132", "4213", "4231", "4312", "4321" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5665_tdm1_adc_data_enum, RT5665_TDM_CTRL_3, + RT5665_TDM_ADC_SEL_SFT, rt5665_tdm_adc_data_src); + +static const struct snd_kcontrol_new rt5665_tdm1_adc_mux = + SOC_DAPM_ENUM("TDM1 ADC Mux", rt5665_tdm1_adc_data_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5665_tdm2_adc_data_enum, RT5665_TDM_CTRL_4, + RT5665_TDM_ADC_SEL_SFT, rt5665_tdm_adc_data_src); + +static const struct snd_kcontrol_new rt5665_tdm2_adc_mux = + SOC_DAPM_ENUM("TDM2 ADCDAT Source", rt5665_tdm2_adc_data_enum); + +/* Out Volume Switch */ +static const struct snd_kcontrol_new monovol_switch = + SOC_DAPM_SINGLE("Switch", RT5665_MONO_OUT, RT5665_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new outvol_l_switch = + SOC_DAPM_SINGLE("Switch", RT5665_LOUT, RT5665_VOL_L_SFT, 1, 1); + +static const struct snd_kcontrol_new outvol_r_switch = + SOC_DAPM_SINGLE("Switch", RT5665_LOUT, RT5665_VOL_R_SFT, 1, 1); + +/* Out Switch */ +static const struct snd_kcontrol_new mono_switch = + SOC_DAPM_SINGLE("Switch", RT5665_MONO_OUT, RT5665_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpo_switch = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5665_HP_CTRL_2, + RT5665_VOL_L_SFT, 1, 0); + +static const struct snd_kcontrol_new lout_l_switch = + SOC_DAPM_SINGLE("Switch", RT5665_LOUT, RT5665_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_r_switch = + SOC_DAPM_SINGLE("Switch", RT5665_LOUT, RT5665_R_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new pdm_l_switch = + SOC_DAPM_SINGLE("Switch", RT5665_PDM_OUT_CTRL, + RT5665_M_PDM1_L_SFT, 1, 1); + +static const struct snd_kcontrol_new pdm_r_switch = + SOC_DAPM_SINGLE("Switch", RT5665_PDM_OUT_CTRL, + RT5665_M_PDM1_R_SFT, 1, 1); + +static int rt5665_mono_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, RT5665_MONO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_EN); + snd_soc_component_update_bits(component, RT5665_MONO_AMP_CALIB_CTRL_1, 0x40, + 0x0); + snd_soc_component_update_bits(component, RT5665_MONO_OUT, 0x10, 0x10); + snd_soc_component_update_bits(component, RT5665_MONO_OUT, 0x20, 0x20); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, RT5665_MONO_OUT, 0x20, 0); + snd_soc_component_update_bits(component, RT5665_MONO_OUT, 0x10, 0); + snd_soc_component_update_bits(component, RT5665_MONO_AMP_CALIB_CTRL_1, 0x40, + 0x40); + snd_soc_component_update_bits(component, RT5665_MONO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_DIS); + break; + + default: + return 0; + } + + return 0; + +} + +static int rt5665_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, RT5665_STO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_EN); + snd_soc_component_write(component, RT5665_HP_LOGIC_CTRL_2, 0x0003); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_write(component, RT5665_HP_LOGIC_CTRL_2, 0x0002); + snd_soc_component_update_bits(component, RT5665_STO_NG2_CTRL_1, + RT5665_NG2_EN_MASK, RT5665_NG2_DIS); + break; + + default: + return 0; + } + + return 0; + +} + +static int rt5665_lout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5665_DEPOP_1, + RT5665_PUMP_EN, RT5665_PUMP_EN); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5665_DEPOP_1, + RT5665_PUMP_EN, 0); + break; + + default: + return 0; + } + + return 0; + +} + +static int set_dmic_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /*Add delay to avoid pop noise*/ + msleep(150); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5655_set_verf(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (w->shift) { + case RT5665_PWR_VREF1_BIT: + snd_soc_component_update_bits(component, RT5665_PWR_ANLG_1, + RT5665_PWR_FV1, 0); + break; + + case RT5665_PWR_VREF2_BIT: + snd_soc_component_update_bits(component, RT5665_PWR_ANLG_1, + RT5665_PWR_FV2, 0); + break; + + case RT5665_PWR_VREF3_BIT: + snd_soc_component_update_bits(component, RT5665_PWR_ANLG_1, + RT5665_PWR_FV3, 0); + break; + + default: + break; + } + break; + + case SND_SOC_DAPM_POST_PMU: + usleep_range(15000, 20000); + switch (w->shift) { + case RT5665_PWR_VREF1_BIT: + snd_soc_component_update_bits(component, RT5665_PWR_ANLG_1, + RT5665_PWR_FV1, RT5665_PWR_FV1); + break; + + case RT5665_PWR_VREF2_BIT: + snd_soc_component_update_bits(component, RT5665_PWR_ANLG_1, + RT5665_PWR_FV2, RT5665_PWR_FV2); + break; + + case RT5665_PWR_VREF3_BIT: + snd_soc_component_update_bits(component, RT5665_PWR_ANLG_1, + RT5665_PWR_FV3, RT5665_PWR_FV3); + break; + + default: + break; + } + break; + + default: + return 0; + } + + return 0; +} + +static int rt5665_i2s_pin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int val1, val2, mask1 = 0, mask2 = 0; + + switch (w->shift) { + case RT5665_PWR_I2S2_1_BIT: + mask1 = RT5665_GP2_PIN_MASK | RT5665_GP3_PIN_MASK | + RT5665_GP4_PIN_MASK | RT5665_GP5_PIN_MASK; + val1 = RT5665_GP2_PIN_BCLK2 | RT5665_GP3_PIN_LRCK2 | + RT5665_GP4_PIN_DACDAT2_1 | RT5665_GP5_PIN_ADCDAT2_1; + break; + case RT5665_PWR_I2S2_2_BIT: + mask1 = RT5665_GP2_PIN_MASK | RT5665_GP3_PIN_MASK | + RT5665_GP8_PIN_MASK; + val1 = RT5665_GP2_PIN_BCLK2 | RT5665_GP3_PIN_LRCK2 | + RT5665_GP8_PIN_DACDAT2_2; + mask2 = RT5665_GP9_PIN_MASK; + val2 = RT5665_GP9_PIN_ADCDAT2_2; + break; + case RT5665_PWR_I2S3_BIT: + mask1 = RT5665_GP6_PIN_MASK | RT5665_GP7_PIN_MASK | + RT5665_GP8_PIN_MASK; + val1 = RT5665_GP6_PIN_BCLK3 | RT5665_GP7_PIN_LRCK3 | + RT5665_GP8_PIN_DACDAT3; + mask2 = RT5665_GP9_PIN_MASK; + val2 = RT5665_GP9_PIN_ADCDAT3; + break; + } + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (mask1) + snd_soc_component_update_bits(component, RT5665_GPIO_CTRL_1, + mask1, val1); + if (mask2) + snd_soc_component_update_bits(component, RT5665_GPIO_CTRL_2, + mask2, val2); + break; + case SND_SOC_DAPM_POST_PMD: + if (mask1) + snd_soc_component_update_bits(component, RT5665_GPIO_CTRL_1, + mask1, 0); + if (mask2) + snd_soc_component_update_bits(component, RT5665_GPIO_CTRL_2, + mask2, 0); + break; + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5665_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO2", RT5665_PWR_ANLG_3, RT5665_PWR_LDO2_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL", RT5665_PWR_ANLG_3, RT5665_PWR_PLL_BIT, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Det Power", RT5665_PWR_VOL, + RT5665_PWR_MIC_DET_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Vref1", RT5665_PWR_ANLG_1, RT5665_PWR_VREF1_BIT, 0, + rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("Vref2", RT5665_PWR_ANLG_1, RT5665_PWR_VREF2_BIT, 0, + rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("Vref3", RT5665_PWR_ANLG_1, RT5665_PWR_VREF3_BIT, 0, + rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("I2S1 ASRC", 1, RT5665_ASRC_1, + RT5665_I2S1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5665_ASRC_1, + RT5665_I2S2_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S3 ASRC", 1, RT5665_ASRC_1, + RT5665_I2S3_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC STO1 ASRC", 1, RT5665_ASRC_1, + RT5665_DAC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC STO2 ASRC", 1, RT5665_ASRC_1, + RT5665_DAC_STO2_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC Mono L ASRC", 1, RT5665_ASRC_1, + RT5665_DAC_MONO_L_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC Mono R ASRC", 1, RT5665_ASRC_1, + RT5665_DAC_MONO_R_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5665_ASRC_1, + RT5665_ADC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO2 ASRC", 1, RT5665_ASRC_1, + RT5665_ADC_STO2_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC Mono L ASRC", 1, RT5665_ASRC_1, + RT5665_ADC_MONO_L_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC Mono R ASRC", 1, RT5665_ASRC_1, + RT5665_ADC_MONO_R_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO1 ASRC", 1, RT5665_ASRC_1, + RT5665_DMIC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO2 ASRC", 1, RT5665_ASRC_1, + RT5665_DMIC_STO2_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO L ASRC", 1, RT5665_ASRC_1, + RT5665_DMIC_MONO_L_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO R ASRC", 1, RT5665_ASRC_1, + RT5665_DMIC_MONO_R_ASRC_SFT, 0, NULL, 0), + + /* Input Side */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5665_PWR_ANLG_2, RT5665_PWR_MB1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", RT5665_PWR_ANLG_2, RT5665_PWR_MB2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS3", RT5665_PWR_ANLG_2, RT5665_PWR_MB3_BIT, + 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + SND_SOC_DAPM_INPUT("DMIC L2"), + SND_SOC_DAPM_INPUT("DMIC R2"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + SND_SOC_DAPM_INPUT("IN3P"), + SND_SOC_DAPM_INPUT("IN3N"), + SND_SOC_DAPM_INPUT("IN4P"), + SND_SOC_DAPM_INPUT("IN4N"), + + SND_SOC_DAPM_PGA("DMIC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC2", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5665_DMIC_CTRL_1, + RT5665_DMIC_1_EN_SFT, 0, set_dmic_power, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("DMIC2 Power", RT5665_DMIC_CTRL_1, + RT5665_DMIC_2_EN_SFT, 0, set_dmic_power, SND_SOC_DAPM_POST_PMU), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST2", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST3", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST4", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("BST1 CBJ", SND_SOC_NOPM, + 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST1 Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST2 Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST3 Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST3_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST4 Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST4_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST1P Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST1_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST2P Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST2_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST3P Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST3_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("BST4P Power", RT5665_PWR_ANLG_2, + RT5665_PWR_BST4_P_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CBJ Power", RT5665_PWR_ANLG_3, + RT5665_PWR_CBJ_BIT, 0, NULL, 0), + + + /* Input Volume */ + SND_SOC_DAPM_PGA("INL VOL", RT5665_PWR_VOL, RT5665_PWR_IN_L_BIT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("INR VOL", RT5665_PWR_VOL, RT5665_PWR_IN_R_BIT, + 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX1L", SND_SOC_NOPM, 0, 0, rt5665_rec1_l_mix, + ARRAY_SIZE(rt5665_rec1_l_mix)), + SND_SOC_DAPM_MIXER("RECMIX1R", SND_SOC_NOPM, 0, 0, rt5665_rec1_r_mix, + ARRAY_SIZE(rt5665_rec1_r_mix)), + SND_SOC_DAPM_MIXER("RECMIX2L", SND_SOC_NOPM, 0, 0, rt5665_rec2_l_mix, + ARRAY_SIZE(rt5665_rec2_l_mix)), + SND_SOC_DAPM_MIXER("RECMIX2R", SND_SOC_NOPM, 0, 0, rt5665_rec2_r_mix, + ARRAY_SIZE(rt5665_rec2_r_mix)), + SND_SOC_DAPM_SUPPLY("RECMIX1L Power", RT5665_PWR_ANLG_2, + RT5665_PWR_RM1_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RECMIX1R Power", RT5665_PWR_ANLG_2, + RT5665_PWR_RM1_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RECMIX2L Power", RT5665_PWR_MIXER, + RT5665_PWR_RM2_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RECMIX2R Power", RT5665_PWR_MIXER, + RT5665_PWR_RM2_R_BIT, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC1 L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC1 R", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC2 L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC2 R", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("ADC1 L Power", RT5665_PWR_DIG_1, + RT5665_PWR_ADC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 R Power", RT5665_PWR_DIG_1, + RT5665_PWR_ADC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 L Power", RT5665_PWR_DIG_1, + RT5665_PWR_ADC_L2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 R Power", RT5665_PWR_DIG_1, + RT5665_PWR_ADC_R2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 clock", RT5665_CHOP_ADC, + RT5665_CKGEN_ADC1_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 clock", RT5665_CHOP_ADC, + RT5665_CKGEN_ADC2_SFT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo1 DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_adc1l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_adc1r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_adc2l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_adc2r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_adcl_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_adcr_mux), + SND_SOC_DAPM_MUX("Stereo1 DD L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_dd_l_mux), + SND_SOC_DAPM_MUX("Stereo1 DD R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto1_dd_r_mux), + SND_SOC_DAPM_MUX("Mono ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_adc_l2_mux), + SND_SOC_DAPM_MUX("Mono ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_adc_r2_mux), + SND_SOC_DAPM_MUX("Mono ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_adc_l1_mux), + SND_SOC_DAPM_MUX("Mono ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_adc_r1_mux), + SND_SOC_DAPM_MUX("Mono DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_dmic_l_mux), + SND_SOC_DAPM_MUX("Mono DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_dmic_r_mux), + SND_SOC_DAPM_MUX("Mono ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_adc_l_mux), + SND_SOC_DAPM_MUX("Mono ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_adc_r_mux), + SND_SOC_DAPM_MUX("Mono DD L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_dd_l_mux), + SND_SOC_DAPM_MUX("Mono DD R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_mono_dd_r_mux), + SND_SOC_DAPM_MUX("Stereo2 DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_dmic_mux), + SND_SOC_DAPM_MUX("Stereo2 DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_dmic_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_adc1l_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_adc1r_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_adc2l_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_adc2r_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_adcl_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_adcr_mux), + SND_SOC_DAPM_MUX("Stereo2 DD L Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_dd_l_mux), + SND_SOC_DAPM_MUX("Stereo2 DD R Mux", SND_SOC_NOPM, 0, 0, + &rt5665_sto2_dd_r_mux), + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("ADC Stereo1 Filter", RT5665_PWR_DIG_2, + RT5665_PWR_ADC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Stereo2 Filter", RT5665_PWR_DIG_2, + RT5665_PWR_ADC_S2F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", RT5665_STO1_ADC_DIG_VOL, + RT5665_L_MUTE_SFT, 1, rt5665_sto1_adc_l_mix, + ARRAY_SIZE(rt5665_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXR", RT5665_STO1_ADC_DIG_VOL, + RT5665_R_MUTE_SFT, 1, rt5665_sto1_adc_r_mix, + ARRAY_SIZE(rt5665_sto1_adc_r_mix)), + SND_SOC_DAPM_MIXER("Stereo2 ADC MIXL", RT5665_STO2_ADC_DIG_VOL, + RT5665_L_MUTE_SFT, 1, rt5665_sto2_adc_l_mix, + ARRAY_SIZE(rt5665_sto2_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo2 ADC MIXR", RT5665_STO2_ADC_DIG_VOL, + RT5665_R_MUTE_SFT, 1, rt5665_sto2_adc_r_mix, + ARRAY_SIZE(rt5665_sto2_adc_r_mix)), + SND_SOC_DAPM_SUPPLY("ADC Mono Left Filter", RT5665_PWR_DIG_2, + RT5665_PWR_ADC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXL", RT5665_MONO_ADC_DIG_VOL, + RT5665_L_MUTE_SFT, 1, rt5665_mono_adc_l_mix, + ARRAY_SIZE(rt5665_mono_adc_l_mix)), + SND_SOC_DAPM_SUPPLY("ADC Mono Right Filter", RT5665_PWR_DIG_2, + RT5665_PWR_ADC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXR", RT5665_MONO_ADC_DIG_VOL, + RT5665_R_MUTE_SFT, 1, rt5665_mono_adc_r_mix, + ARRAY_SIZE(rt5665_mono_adc_r_mix)), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1_1", RT5665_PWR_DIG_1, RT5665_PWR_I2S1_1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S1_2", RT5665_PWR_DIG_1, RT5665_PWR_I2S1_2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2_1", RT5665_PWR_DIG_1, RT5665_PWR_I2S2_1_BIT, + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("I2S2_2", RT5665_PWR_DIG_1, RT5665_PWR_I2S2_2_BIT, + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("I2S3", RT5665_PWR_DIG_1, RT5665_PWR_I2S3_BIT, + 0, rt5665_i2s_pin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC3 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC3 R", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("IF2_1 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2_2 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2_1 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2_1 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2_2 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2_2 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2_1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2_2 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("IF3 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("IF1_1_ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_adc1_mux), + SND_SOC_DAPM_MUX("IF1_1_ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_adc2_mux), + SND_SOC_DAPM_MUX("IF1_1_ADC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_adc3_mux), + SND_SOC_DAPM_PGA("IF1_1_ADC4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MUX("IF1_2_ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_adc1_mux), + SND_SOC_DAPM_MUX("IF1_2_ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_adc2_mux), + SND_SOC_DAPM_MUX("IF1_2_ADC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_adc3_mux), + SND_SOC_DAPM_MUX("IF1_2_ADC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_adc4_mux), + SND_SOC_DAPM_MUX("TDM1 slot 01 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm1_adc_mux), + SND_SOC_DAPM_MUX("TDM1 slot 23 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm1_adc_mux), + SND_SOC_DAPM_MUX("TDM1 slot 45 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm1_adc_mux), + SND_SOC_DAPM_MUX("TDM1 slot 67 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm1_adc_mux), + SND_SOC_DAPM_MUX("TDM2 slot 01 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm2_adc_mux), + SND_SOC_DAPM_MUX("TDM2 slot 23 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm2_adc_mux), + SND_SOC_DAPM_MUX("TDM2 slot 45 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm2_adc_mux), + SND_SOC_DAPM_MUX("TDM2 slot 67 Data Mux", SND_SOC_NOPM, 0, 0, + &rt5665_tdm2_adc_mux), + SND_SOC_DAPM_MUX("IF2_1 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if2_1_adc_in_mux), + SND_SOC_DAPM_MUX("IF2_2 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if2_2_adc_in_mux), + SND_SOC_DAPM_MUX("IF3 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if3_adc_in_mux), + SND_SOC_DAPM_MUX("IF1_1 0 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_1 1 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_1 2 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_1 3 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_1 4 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_1 5 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_1 6 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_1 7 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_1_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 0 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 1 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 2 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 3 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 4 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 5 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 6 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1_2 7 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if1_2_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF2_1 DAC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if2_1_dac_swap_mux), + SND_SOC_DAPM_MUX("IF2_1 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if2_1_adc_swap_mux), + SND_SOC_DAPM_MUX("IF2_2 DAC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if2_2_dac_swap_mux), + SND_SOC_DAPM_MUX("IF2_2 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if2_2_adc_swap_mux), + SND_SOC_DAPM_MUX("IF3 DAC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if3_dac_swap_mux), + SND_SOC_DAPM_MUX("IF3 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5665_if3_adc_swap_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 0", "AIF1_1 Capture", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 1", "AIF1_1 Capture", + 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 2", "AIF1_1 Capture", + 2, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 3", "AIF1_1 Capture", + 3, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 4", "AIF1_1 Capture", + 4, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 5", "AIF1_1 Capture", + 5, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 6", "AIF1_1 Capture", + 6, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_1TX slot 7", "AIF1_1 Capture", + 7, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 0", "AIF1_2 Capture", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 1", "AIF1_2 Capture", + 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 2", "AIF1_2 Capture", + 2, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 3", "AIF1_2 Capture", + 3, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 4", "AIF1_2 Capture", + 4, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 5", "AIF1_2 Capture", + 5, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 6", "AIF1_2 Capture", + 6, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1_2TX slot 7", "AIF1_2 Capture", + 7, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2_1TX", "AIF2_1 Capture", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2_2TX", "AIF2_2 Capture", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF3TX", "AIF3 Capture", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2_1RX", "AIF2_1 Playback", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2_2RX", "AIF2_2 Playback", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF3RX", "AIF3 Playback", + 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5665_dac_l_mix, ARRAY_SIZE(rt5665_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5665_dac_r_mix, ARRAY_SIZE(rt5665_dac_r_mix)), + + /* DAC channel Mux */ + SND_SOC_DAPM_MUX("DAC L1 Mux", SND_SOC_NOPM, 0, 0, &rt5665_dac_l1_mux), + SND_SOC_DAPM_MUX("DAC R1 Mux", SND_SOC_NOPM, 0, 0, &rt5665_dac_r1_mux), + SND_SOC_DAPM_MUX("DAC L2 Mux", SND_SOC_NOPM, 0, 0, &rt5665_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Mux", SND_SOC_NOPM, 0, 0, &rt5665_dac_r2_mux), + SND_SOC_DAPM_MUX("DAC L3 Mux", SND_SOC_NOPM, 0, 0, &rt5665_dac_l3_mux), + SND_SOC_DAPM_MUX("DAC R3 Mux", SND_SOC_NOPM, 0, 0, &rt5665_dac_r3_mux), + + SND_SOC_DAPM_MUX("DAC L1 Source", SND_SOC_NOPM, 0, 0, + &rt5665_alg_dac_l1_mux), + SND_SOC_DAPM_MUX("DAC R1 Source", SND_SOC_NOPM, 0, 0, + &rt5665_alg_dac_r1_mux), + SND_SOC_DAPM_MUX("DAC L2 Source", SND_SOC_NOPM, 0, 0, + &rt5665_alg_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Source", SND_SOC_NOPM, 0, 0, + &rt5665_alg_dac_r2_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("DAC Stereo1 Filter", RT5665_PWR_DIG_2, + RT5665_PWR_DAC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Stereo2 Filter", RT5665_PWR_DIG_2, + RT5665_PWR_DAC_S2F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Mono Left Filter", RT5665_PWR_DIG_2, + RT5665_PWR_DAC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Mono Right Filter", RT5665_PWR_DIG_2, + RT5665_PWR_DAC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5665_sto1_dac_l_mix, ARRAY_SIZE(rt5665_sto1_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5665_sto1_dac_r_mix, ARRAY_SIZE(rt5665_sto1_dac_r_mix)), + SND_SOC_DAPM_MIXER("Stereo2 DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5665_sto2_dac_l_mix, ARRAY_SIZE(rt5665_sto2_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo2 DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5665_sto2_dac_r_mix, ARRAY_SIZE(rt5665_sto2_dac_r_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5665_mono_dac_l_mix, ARRAY_SIZE(rt5665_mono_dac_l_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5665_mono_dac_r_mix, ARRAY_SIZE(rt5665_mono_dac_r_mix)), + SND_SOC_DAPM_MUX("DAC MIXL", SND_SOC_NOPM, 0, 0, + &rt5665_dig_dac_mixl_mux), + SND_SOC_DAPM_MUX("DAC MIXR", SND_SOC_NOPM, 0, 0, + &rt5665_dig_dac_mixr_mux), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("DAC L2 Power", RT5665_PWR_DIG_1, + RT5665_PWR_DAC_L2_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC R2 Power", RT5665_PWR_DIG_1, + RT5665_PWR_DAC_R2_BIT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DAC L2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC R2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("DAC1 MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("DAC 1 Clock", 1, RT5665_CHOP_DAC, + RT5665_CKGEN_DAC1_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC 2 Clock", 1, RT5665_CHOP_DAC, + RT5665_CKGEN_DAC2_SFT, 0, NULL, 0), + + /* OUT Mixer */ + SND_SOC_DAPM_MIXER("MONOVOL MIX", RT5665_PWR_MIXER, RT5665_PWR_MM_BIT, + 0, rt5665_monovol_mix, ARRAY_SIZE(rt5665_monovol_mix)), + SND_SOC_DAPM_MIXER("OUT MIXL", RT5665_PWR_MIXER, RT5665_PWR_OM_L_BIT, + 0, rt5665_out_l_mix, ARRAY_SIZE(rt5665_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5665_PWR_MIXER, RT5665_PWR_OM_R_BIT, + 0, rt5665_out_r_mix, ARRAY_SIZE(rt5665_out_r_mix)), + + /* Output Volume */ + SND_SOC_DAPM_SWITCH("MONOVOL", RT5665_PWR_VOL, RT5665_PWR_MV_BIT, 0, + &monovol_switch), + SND_SOC_DAPM_SWITCH("OUTVOL L", RT5665_PWR_VOL, RT5665_PWR_OV_L_BIT, 0, + &outvol_l_switch), + SND_SOC_DAPM_SWITCH("OUTVOL R", RT5665_PWR_VOL, RT5665_PWR_OV_R_BIT, 0, + &outvol_r_switch), + + /* MONO/HPO/LOUT */ + SND_SOC_DAPM_MIXER("Mono MIX", SND_SOC_NOPM, 0, 0, rt5665_mono_mix, + ARRAY_SIZE(rt5665_mono_mix)), + SND_SOC_DAPM_MIXER("LOUT L MIX", SND_SOC_NOPM, 0, 0, rt5665_lout_l_mix, + ARRAY_SIZE(rt5665_lout_l_mix)), + SND_SOC_DAPM_MIXER("LOUT R MIX", SND_SOC_NOPM, 0, 0, rt5665_lout_r_mix, + ARRAY_SIZE(rt5665_lout_r_mix)), + SND_SOC_DAPM_PGA_S("Mono Amp", 1, RT5665_PWR_ANLG_1, RT5665_PWR_MA_BIT, + 0, rt5665_mono_event, SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, rt5665_hp_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_PGA_S("LOUT Amp", 1, RT5665_PWR_ANLG_1, + RT5665_PWR_LM_BIT, 0, rt5665_lout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_SUPPLY("Charge Pump", SND_SOC_NOPM, 0, 0, + rt5665_charge_pump_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SWITCH("Mono Playback", SND_SOC_NOPM, 0, 0, + &mono_switch), + SND_SOC_DAPM_SWITCH("HPO Playback", SND_SOC_NOPM, 0, 0, + &hpo_switch), + SND_SOC_DAPM_SWITCH("LOUT L Playback", SND_SOC_NOPM, 0, 0, + &lout_l_switch), + SND_SOC_DAPM_SWITCH("LOUT R Playback", SND_SOC_NOPM, 0, 0, + &lout_r_switch), + SND_SOC_DAPM_SWITCH("PDM L Playback", SND_SOC_NOPM, 0, 0, + &pdm_l_switch), + SND_SOC_DAPM_SWITCH("PDM R Playback", SND_SOC_NOPM, 0, 0, + &pdm_r_switch), + + /* PDM */ + SND_SOC_DAPM_SUPPLY("PDM Power", RT5665_PWR_DIG_2, + RT5665_PWR_PDM1_BIT, 0, NULL, 0), + SND_SOC_DAPM_MUX("PDM L Mux", SND_SOC_NOPM, + 0, 1, &rt5665_pdm_l_mux), + SND_SOC_DAPM_MUX("PDM R Mux", SND_SOC_NOPM, + 0, 1, &rt5665_pdm_r_mux), + + /* CLK DET */ + SND_SOC_DAPM_SUPPLY("CLKDET SYS", RT5665_CLK_DET, RT5665_SYS_CLK_DET, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET HP", RT5665_CLK_DET, RT5665_HP_CLK_DET, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET MONO", RT5665_CLK_DET, RT5665_MONO_CLK_DET, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET LOUT", RT5665_CLK_DET, RT5665_LOUT_CLK_DET, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET", RT5665_CLK_DET, RT5665_POW_CLK_DET, + 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_OUTPUT("PDML"), + SND_SOC_DAPM_OUTPUT("PDMR"), +}; + +static const struct snd_soc_dapm_route rt5665_dapm_routes[] = { + /*PLL*/ + {"ADC Stereo1 Filter", NULL, "PLL", is_sys_clk_from_pll}, + {"ADC Stereo2 Filter", NULL, "PLL", is_sys_clk_from_pll}, + {"ADC Mono Left Filter", NULL, "PLL", is_sys_clk_from_pll}, + {"ADC Mono Right Filter", NULL, "PLL", is_sys_clk_from_pll}, + {"DAC Stereo1 Filter", NULL, "PLL", is_sys_clk_from_pll}, + {"DAC Stereo2 Filter", NULL, "PLL", is_sys_clk_from_pll}, + {"DAC Mono Left Filter", NULL, "PLL", is_sys_clk_from_pll}, + {"DAC Mono Right Filter", NULL, "PLL", is_sys_clk_from_pll}, + + /*ASRC*/ + {"ADC Stereo1 Filter", NULL, "ADC STO1 ASRC", is_using_asrc}, + {"ADC Stereo2 Filter", NULL, "ADC STO2 ASRC", is_using_asrc}, + {"ADC Mono Left Filter", NULL, "ADC Mono L ASRC", is_using_asrc}, + {"ADC Mono Right Filter", NULL, "ADC Mono R ASRC", is_using_asrc}, + {"DAC Mono Left Filter", NULL, "DAC Mono L ASRC", is_using_asrc}, + {"DAC Mono Right Filter", NULL, "DAC Mono R ASRC", is_using_asrc}, + {"DAC Stereo1 Filter", NULL, "DAC STO1 ASRC", is_using_asrc}, + {"DAC Stereo2 Filter", NULL, "DAC STO2 ASRC", is_using_asrc}, + {"I2S1 ASRC", NULL, "CLKDET"}, + {"I2S2 ASRC", NULL, "CLKDET"}, + {"I2S3 ASRC", NULL, "CLKDET"}, + + /*Vref*/ + {"Mic Det Power", NULL, "Vref2"}, + {"MICBIAS1", NULL, "Vref1"}, + {"MICBIAS1", NULL, "Vref2"}, + {"MICBIAS2", NULL, "Vref1"}, + {"MICBIAS2", NULL, "Vref2"}, + {"MICBIAS3", NULL, "Vref1"}, + {"MICBIAS3", NULL, "Vref2"}, + + {"Stereo1 DMIC L Mux", NULL, "DMIC STO1 ASRC"}, + {"Stereo1 DMIC R Mux", NULL, "DMIC STO1 ASRC"}, + {"Stereo2 DMIC L Mux", NULL, "DMIC STO2 ASRC"}, + {"Stereo2 DMIC R Mux", NULL, "DMIC STO2 ASRC"}, + {"Mono DMIC L Mux", NULL, "DMIC MONO L ASRC"}, + {"Mono DMIC R Mux", NULL, "DMIC MONO R ASRC"}, + + {"I2S1_1", NULL, "I2S1 ASRC"}, + {"I2S1_2", NULL, "I2S1 ASRC"}, + {"I2S2_1", NULL, "I2S2 ASRC"}, + {"I2S2_2", NULL, "I2S2 ASRC"}, + {"I2S3", NULL, "I2S3 ASRC"}, + + {"CLKDET SYS", NULL, "CLKDET"}, + {"CLKDET HP", NULL, "CLKDET"}, + {"CLKDET MONO", NULL, "CLKDET"}, + {"CLKDET LOUT", NULL, "CLKDET"}, + + {"IN1P", NULL, "LDO2"}, + {"IN2P", NULL, "LDO2"}, + {"IN3P", NULL, "LDO2"}, + {"IN4P", NULL, "LDO2"}, + + {"DMIC1", NULL, "DMIC L1"}, + {"DMIC1", NULL, "DMIC R1"}, + {"DMIC2", NULL, "DMIC L2"}, + {"DMIC2", NULL, "DMIC R2"}, + + {"BST1", NULL, "IN1P"}, + {"BST1", NULL, "IN1N"}, + {"BST1", NULL, "BST1 Power"}, + {"BST1", NULL, "BST1P Power"}, + {"BST2", NULL, "IN2P"}, + {"BST2", NULL, "IN2N"}, + {"BST2", NULL, "BST2 Power"}, + {"BST2", NULL, "BST2P Power"}, + {"BST3", NULL, "IN3P"}, + {"BST3", NULL, "IN3N"}, + {"BST3", NULL, "BST3 Power"}, + {"BST3", NULL, "BST3P Power"}, + {"BST4", NULL, "IN4P"}, + {"BST4", NULL, "IN4N"}, + {"BST4", NULL, "BST4 Power"}, + {"BST4", NULL, "BST4P Power"}, + {"BST1 CBJ", NULL, "IN1P"}, + {"BST1 CBJ", NULL, "IN1N"}, + {"BST1 CBJ", NULL, "CBJ Power"}, + {"CBJ Power", NULL, "Vref2"}, + + {"INL VOL", NULL, "IN3P"}, + {"INR VOL", NULL, "IN3N"}, + + {"RECMIX1L", "CBJ Switch", "BST1 CBJ"}, + {"RECMIX1L", "INL Switch", "INL VOL"}, + {"RECMIX1L", "INR Switch", "INR VOL"}, + {"RECMIX1L", "BST4 Switch", "BST4"}, + {"RECMIX1L", "BST3 Switch", "BST3"}, + {"RECMIX1L", "BST2 Switch", "BST2"}, + {"RECMIX1L", "BST1 Switch", "BST1"}, + {"RECMIX1L", NULL, "RECMIX1L Power"}, + + {"RECMIX1R", "MONOVOL Switch", "MONOVOL"}, + {"RECMIX1R", "INR Switch", "INR VOL"}, + {"RECMIX1R", "BST4 Switch", "BST4"}, + {"RECMIX1R", "BST3 Switch", "BST3"}, + {"RECMIX1R", "BST2 Switch", "BST2"}, + {"RECMIX1R", "BST1 Switch", "BST1"}, + {"RECMIX1R", NULL, "RECMIX1R Power"}, + + {"RECMIX2L", "CBJ Switch", "BST1 CBJ"}, + {"RECMIX2L", "INL Switch", "INL VOL"}, + {"RECMIX2L", "INR Switch", "INR VOL"}, + {"RECMIX2L", "BST4 Switch", "BST4"}, + {"RECMIX2L", "BST3 Switch", "BST3"}, + {"RECMIX2L", "BST2 Switch", "BST2"}, + {"RECMIX2L", "BST1 Switch", "BST1"}, + {"RECMIX2L", NULL, "RECMIX2L Power"}, + + {"RECMIX2R", "MONOVOL Switch", "MONOVOL"}, + {"RECMIX2R", "INL Switch", "INL VOL"}, + {"RECMIX2R", "INR Switch", "INR VOL"}, + {"RECMIX2R", "BST4 Switch", "BST4"}, + {"RECMIX2R", "BST3 Switch", "BST3"}, + {"RECMIX2R", "BST2 Switch", "BST2"}, + {"RECMIX2R", "BST1 Switch", "BST1"}, + {"RECMIX2R", NULL, "RECMIX2R Power"}, + + {"ADC1 L", NULL, "RECMIX1L"}, + {"ADC1 L", NULL, "ADC1 L Power"}, + {"ADC1 L", NULL, "ADC1 clock"}, + {"ADC1 R", NULL, "RECMIX1R"}, + {"ADC1 R", NULL, "ADC1 R Power"}, + {"ADC1 R", NULL, "ADC1 clock"}, + + {"ADC2 L", NULL, "RECMIX2L"}, + {"ADC2 L", NULL, "ADC2 L Power"}, + {"ADC2 L", NULL, "ADC2 clock"}, + {"ADC2 R", NULL, "RECMIX2R"}, + {"ADC2 R", NULL, "ADC2 R Power"}, + {"ADC2 R", NULL, "ADC2 clock"}, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC L1", NULL, "DMIC1 Power"}, + {"DMIC R1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC1 Power"}, + {"DMIC L2", NULL, "DMIC CLK"}, + {"DMIC L2", NULL, "DMIC2 Power"}, + {"DMIC R2", NULL, "DMIC CLK"}, + {"DMIC R2", NULL, "DMIC2 Power"}, + + {"Stereo1 DMIC L Mux", "DMIC1", "DMIC L1"}, + {"Stereo1 DMIC L Mux", "DMIC2", "DMIC L2"}, + + {"Stereo1 DMIC R Mux", "DMIC1", "DMIC R1"}, + {"Stereo1 DMIC R Mux", "DMIC2", "DMIC R2"}, + + {"Mono DMIC L Mux", "DMIC1 L", "DMIC L1"}, + {"Mono DMIC L Mux", "DMIC2 L", "DMIC L2"}, + + {"Mono DMIC R Mux", "DMIC1 R", "DMIC R1"}, + {"Mono DMIC R Mux", "DMIC2 R", "DMIC R2"}, + + {"Stereo2 DMIC L Mux", "DMIC1", "DMIC L1"}, + {"Stereo2 DMIC L Mux", "DMIC2", "DMIC L2"}, + + {"Stereo2 DMIC R Mux", "DMIC1", "DMIC R1"}, + {"Stereo2 DMIC R Mux", "DMIC2", "DMIC R2"}, + + {"Stereo1 ADC L Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC L Mux", "ADC1 R", "ADC1 R"}, + {"Stereo1 ADC L Mux", "ADC2 L", "ADC2 L"}, + {"Stereo1 ADC L Mux", "ADC2 R", "ADC2 R"}, + {"Stereo1 ADC R Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC R Mux", "ADC1 R", "ADC1 R"}, + {"Stereo1 ADC R Mux", "ADC2 L", "ADC2 L"}, + {"Stereo1 ADC R Mux", "ADC2 R", "ADC2 R"}, + + {"Stereo1 DD L Mux", "STO2 DAC", "Stereo2 DAC MIXL"}, + {"Stereo1 DD L Mux", "MONO DAC", "Mono DAC MIXL"}, + + {"Stereo1 DD R Mux", "STO2 DAC", "Stereo2 DAC MIXR"}, + {"Stereo1 DD R Mux", "MONO DAC", "Mono DAC MIXR"}, + + {"Stereo1 ADC L1 Mux", "ADC", "Stereo1 ADC L Mux"}, + {"Stereo1 ADC L1 Mux", "DD Mux", "Stereo1 DD L Mux"}, + {"Stereo1 ADC L2 Mux", "DMIC", "Stereo1 DMIC L Mux"}, + {"Stereo1 ADC L2 Mux", "DAC MIX", "DAC MIXL"}, + + {"Stereo1 ADC R1 Mux", "ADC", "Stereo1 ADC R Mux"}, + {"Stereo1 ADC R1 Mux", "DD Mux", "Stereo1 DD R Mux"}, + {"Stereo1 ADC R2 Mux", "DMIC", "Stereo1 DMIC R Mux"}, + {"Stereo1 ADC R2 Mux", "DAC MIX", "DAC MIXR"}, + + {"Mono ADC L Mux", "ADC1 L", "ADC1 L"}, + {"Mono ADC L Mux", "ADC1 R", "ADC1 R"}, + {"Mono ADC L Mux", "ADC2 L", "ADC2 L"}, + {"Mono ADC L Mux", "ADC2 R", "ADC2 R"}, + + {"Mono ADC R Mux", "ADC1 L", "ADC1 L"}, + {"Mono ADC R Mux", "ADC1 R", "ADC1 R"}, + {"Mono ADC R Mux", "ADC2 L", "ADC2 L"}, + {"Mono ADC R Mux", "ADC2 R", "ADC2 R"}, + + {"Mono DD L Mux", "STO2 DAC", "Stereo2 DAC MIXL"}, + {"Mono DD L Mux", "MONO DAC", "Mono DAC MIXL"}, + + {"Mono DD R Mux", "STO2 DAC", "Stereo2 DAC MIXR"}, + {"Mono DD R Mux", "MONO DAC", "Mono DAC MIXR"}, + + {"Mono ADC L2 Mux", "DMIC", "Mono DMIC L Mux"}, + {"Mono ADC L2 Mux", "DAC MIXL", "DAC MIXL"}, + {"Mono ADC L1 Mux", "DD Mux", "Mono DD L Mux"}, + {"Mono ADC L1 Mux", "ADC", "Mono ADC L Mux"}, + + {"Mono ADC R1 Mux", "DD Mux", "Mono DD R Mux"}, + {"Mono ADC R1 Mux", "ADC", "Mono ADC R Mux"}, + {"Mono ADC R2 Mux", "DMIC", "Mono DMIC R Mux"}, + {"Mono ADC R2 Mux", "DAC MIXR", "DAC MIXR"}, + + {"Stereo2 ADC L Mux", "ADC1 L", "ADC1 L"}, + {"Stereo2 ADC L Mux", "ADC2 L", "ADC2 L"}, + {"Stereo2 ADC L Mux", "ADC1 R", "ADC1 R"}, + {"Stereo2 ADC R Mux", "ADC1 L", "ADC1 L"}, + {"Stereo2 ADC R Mux", "ADC2 L", "ADC2 L"}, + {"Stereo2 ADC R Mux", "ADC1 R", "ADC1 R"}, + + {"Stereo2 DD L Mux", "STO2 DAC", "Stereo2 DAC MIXL"}, + {"Stereo2 DD L Mux", "MONO DAC", "Mono DAC MIXL"}, + + {"Stereo2 DD R Mux", "STO2 DAC", "Stereo2 DAC MIXR"}, + {"Stereo2 DD R Mux", "MONO DAC", "Mono DAC MIXR"}, + + {"Stereo2 ADC L1 Mux", "ADC", "Stereo2 ADC L Mux"}, + {"Stereo2 ADC L1 Mux", "DD Mux", "Stereo2 DD L Mux"}, + {"Stereo2 ADC L2 Mux", "DMIC", "Stereo2 DMIC L Mux"}, + {"Stereo2 ADC L2 Mux", "DAC MIX", "DAC MIXL"}, + + {"Stereo2 ADC R1 Mux", "ADC", "Stereo2 ADC R Mux"}, + {"Stereo2 ADC R1 Mux", "DD Mux", "Stereo2 DD R Mux"}, + {"Stereo2 ADC R2 Mux", "DMIC", "Stereo2 DMIC R Mux"}, + {"Stereo2 ADC R2 Mux", "DAC MIX", "DAC MIXR"}, + + {"Stereo1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux"}, + {"Stereo1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux"}, + {"Stereo1 ADC MIXL", NULL, "ADC Stereo1 Filter"}, + + {"Stereo1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux"}, + {"Stereo1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux"}, + {"Stereo1 ADC MIXR", NULL, "ADC Stereo1 Filter"}, + + {"Mono ADC MIXL", "ADC1 Switch", "Mono ADC L1 Mux"}, + {"Mono ADC MIXL", "ADC2 Switch", "Mono ADC L2 Mux"}, + {"Mono ADC MIXL", NULL, "ADC Mono Left Filter"}, + + {"Mono ADC MIXR", "ADC1 Switch", "Mono ADC R1 Mux"}, + {"Mono ADC MIXR", "ADC2 Switch", "Mono ADC R2 Mux"}, + {"Mono ADC MIXR", NULL, "ADC Mono Right Filter"}, + + {"Stereo2 ADC MIXL", "ADC1 Switch", "Stereo2 ADC L1 Mux"}, + {"Stereo2 ADC MIXL", "ADC2 Switch", "Stereo2 ADC L2 Mux"}, + {"Stereo2 ADC MIXL", NULL, "ADC Stereo2 Filter"}, + + {"Stereo2 ADC MIXR", "ADC1 Switch", "Stereo2 ADC R1 Mux"}, + {"Stereo2 ADC MIXR", "ADC2 Switch", "Stereo2 ADC R2 Mux"}, + {"Stereo2 ADC MIXR", NULL, "ADC Stereo2 Filter"}, + + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXL"}, + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXR"}, + {"Stereo2 ADC MIX", NULL, "Stereo2 ADC MIXL"}, + {"Stereo2 ADC MIX", NULL, "Stereo2 ADC MIXR"}, + {"Mono ADC MIX", NULL, "Mono ADC MIXL"}, + {"Mono ADC MIX", NULL, "Mono ADC MIXR"}, + + {"IF1_1_ADC1 Mux", "STO1 ADC", "Stereo1 ADC MIX"}, + {"IF1_1_ADC1 Mux", "IF2_1 DAC", "IF2_1 DAC"}, + {"IF1_1_ADC2 Mux", "STO2 ADC", "Stereo2 ADC MIX"}, + {"IF1_1_ADC2 Mux", "IF2_2 DAC", "IF2_2 DAC"}, + {"IF1_1_ADC3 Mux", "MONO ADC", "Mono ADC MIX"}, + {"IF1_1_ADC3 Mux", "IF3 DAC", "IF3 DAC"}, + {"IF1_1_ADC4", NULL, "DAC1 MIX"}, + + {"IF1_2_ADC1 Mux", "STO1 ADC", "Stereo1 ADC MIX"}, + {"IF1_2_ADC1 Mux", "IF1 DAC", "IF1 DAC1"}, + {"IF1_2_ADC2 Mux", "STO2 ADC", "Stereo2 ADC MIX"}, + {"IF1_2_ADC2 Mux", "IF2_1 DAC", "IF2_1 DAC"}, + {"IF1_2_ADC3 Mux", "MONO ADC", "Mono ADC MIX"}, + {"IF1_2_ADC3 Mux", "IF2_2 DAC", "IF2_2 DAC"}, + {"IF1_2_ADC4 Mux", "DAC1", "DAC1 MIX"}, + {"IF1_2_ADC4 Mux", "IF3 DAC", "IF3 DAC"}, + + {"TDM1 slot 01 Data Mux", "1234", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 01 Data Mux", "1243", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 01 Data Mux", "1324", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 01 Data Mux", "1342", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 01 Data Mux", "1432", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 01 Data Mux", "1423", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 01 Data Mux", "2134", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 01 Data Mux", "2143", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 01 Data Mux", "2314", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 01 Data Mux", "2341", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 01 Data Mux", "2431", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 01 Data Mux", "2413", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 01 Data Mux", "3124", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 01 Data Mux", "3142", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 01 Data Mux", "3214", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 01 Data Mux", "3241", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 01 Data Mux", "3412", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 01 Data Mux", "3421", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 01 Data Mux", "4123", "IF1_1_ADC4"}, + {"TDM1 slot 01 Data Mux", "4132", "IF1_1_ADC4"}, + {"TDM1 slot 01 Data Mux", "4213", "IF1_1_ADC4"}, + {"TDM1 slot 01 Data Mux", "4231", "IF1_1_ADC4"}, + {"TDM1 slot 01 Data Mux", "4312", "IF1_1_ADC4"}, + {"TDM1 slot 01 Data Mux", "4321", "IF1_1_ADC4"}, + {"TDM1 slot 01 Data Mux", NULL, "I2S1_1"}, + + {"TDM1 slot 23 Data Mux", "1234", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 23 Data Mux", "1243", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 23 Data Mux", "1324", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 23 Data Mux", "1342", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 23 Data Mux", "1432", "IF1_1_ADC4"}, + {"TDM1 slot 23 Data Mux", "1423", "IF1_1_ADC4"}, + {"TDM1 slot 23 Data Mux", "2134", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 23 Data Mux", "2143", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 23 Data Mux", "2314", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 23 Data Mux", "2341", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 23 Data Mux", "2431", "IF1_1_ADC4"}, + {"TDM1 slot 23 Data Mux", "2413", "IF1_1_ADC4"}, + {"TDM1 slot 23 Data Mux", "3124", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 23 Data Mux", "3142", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 23 Data Mux", "3214", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 23 Data Mux", "3241", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 23 Data Mux", "3412", "IF1_1_ADC4"}, + {"TDM1 slot 23 Data Mux", "3421", "IF1_1_ADC4"}, + {"TDM1 slot 23 Data Mux", "4123", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 23 Data Mux", "4132", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 23 Data Mux", "4213", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 23 Data Mux", "4231", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 23 Data Mux", "4312", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 23 Data Mux", "4321", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 23 Data Mux", NULL, "I2S1_1"}, + + {"TDM1 slot 45 Data Mux", "1234", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 45 Data Mux", "1243", "IF1_1_ADC4"}, + {"TDM1 slot 45 Data Mux", "1324", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 45 Data Mux", "1342", "IF1_1_ADC4"}, + {"TDM1 slot 45 Data Mux", "1432", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 45 Data Mux", "1423", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 45 Data Mux", "2134", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 45 Data Mux", "2143", "IF1_1_ADC4"}, + {"TDM1 slot 45 Data Mux", "2314", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 45 Data Mux", "2341", "IF1_1_ADC4"}, + {"TDM1 slot 45 Data Mux", "2431", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 45 Data Mux", "2413", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 45 Data Mux", "3124", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 45 Data Mux", "3142", "IF1_1_ADC4"}, + {"TDM1 slot 45 Data Mux", "3214", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 45 Data Mux", "3241", "IF1_1_ADC4"}, + {"TDM1 slot 45 Data Mux", "3412", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 45 Data Mux", "3421", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 45 Data Mux", "4123", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 45 Data Mux", "4132", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 45 Data Mux", "4213", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 45 Data Mux", "4231", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 45 Data Mux", "4312", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 45 Data Mux", "4321", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 45 Data Mux", NULL, "I2S1_1"}, + + {"TDM1 slot 67 Data Mux", "1234", "IF1_1_ADC4"}, + {"TDM1 slot 67 Data Mux", "1243", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 67 Data Mux", "1324", "IF1_1_ADC4"}, + {"TDM1 slot 67 Data Mux", "1342", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 67 Data Mux", "1432", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 67 Data Mux", "1423", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 67 Data Mux", "2134", "IF1_1_ADC4"}, + {"TDM1 slot 67 Data Mux", "2143", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 67 Data Mux", "2314", "IF1_1_ADC4"}, + {"TDM1 slot 67 Data Mux", "2341", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 67 Data Mux", "2431", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 67 Data Mux", "2413", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 67 Data Mux", "3124", "IF1_1_ADC4"}, + {"TDM1 slot 67 Data Mux", "3142", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 67 Data Mux", "3214", "IF1_1_ADC4"}, + {"TDM1 slot 67 Data Mux", "3241", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 67 Data Mux", "3412", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 67 Data Mux", "3421", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 67 Data Mux", "4123", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 67 Data Mux", "4132", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 67 Data Mux", "4213", "IF1_1_ADC3 Mux"}, + {"TDM1 slot 67 Data Mux", "4231", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 67 Data Mux", "4312", "IF1_1_ADC2 Mux"}, + {"TDM1 slot 67 Data Mux", "4321", "IF1_1_ADC1 Mux"}, + {"TDM1 slot 67 Data Mux", NULL, "I2S1_1"}, + + + {"TDM2 slot 01 Data Mux", "1234", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 01 Data Mux", "1243", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 01 Data Mux", "1324", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 01 Data Mux", "1342", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 01 Data Mux", "1432", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 01 Data Mux", "1423", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 01 Data Mux", "2134", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 01 Data Mux", "2143", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 01 Data Mux", "2314", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 01 Data Mux", "2341", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 01 Data Mux", "2431", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 01 Data Mux", "2413", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 01 Data Mux", "3124", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 01 Data Mux", "3142", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 01 Data Mux", "3214", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 01 Data Mux", "3241", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 01 Data Mux", "3412", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 01 Data Mux", "3421", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 01 Data Mux", "4123", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 01 Data Mux", "4132", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 01 Data Mux", "4213", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 01 Data Mux", "4231", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 01 Data Mux", "4312", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 01 Data Mux", "4321", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 01 Data Mux", NULL, "I2S1_2"}, + + {"TDM2 slot 23 Data Mux", "1234", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 23 Data Mux", "1243", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 23 Data Mux", "1324", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 23 Data Mux", "1342", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 23 Data Mux", "1432", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 23 Data Mux", "1423", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 23 Data Mux", "2134", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 23 Data Mux", "2143", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 23 Data Mux", "2314", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 23 Data Mux", "2341", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 23 Data Mux", "2431", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 23 Data Mux", "2413", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 23 Data Mux", "3124", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 23 Data Mux", "3142", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 23 Data Mux", "3214", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 23 Data Mux", "3241", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 23 Data Mux", "3412", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 23 Data Mux", "3421", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 23 Data Mux", "4123", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 23 Data Mux", "4132", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 23 Data Mux", "4213", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 23 Data Mux", "4231", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 23 Data Mux", "4312", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 23 Data Mux", "4321", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 23 Data Mux", NULL, "I2S1_2"}, + + {"TDM2 slot 45 Data Mux", "1234", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 45 Data Mux", "1243", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 45 Data Mux", "1324", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 45 Data Mux", "1342", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 45 Data Mux", "1432", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 45 Data Mux", "1423", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 45 Data Mux", "2134", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 45 Data Mux", "2143", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 45 Data Mux", "2314", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 45 Data Mux", "2341", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 45 Data Mux", "2431", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 45 Data Mux", "2413", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 45 Data Mux", "3124", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 45 Data Mux", "3142", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 45 Data Mux", "3214", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 45 Data Mux", "3241", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 45 Data Mux", "3412", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 45 Data Mux", "3421", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 45 Data Mux", "4123", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 45 Data Mux", "4132", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 45 Data Mux", "4213", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 45 Data Mux", "4231", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 45 Data Mux", "4312", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 45 Data Mux", "4321", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 45 Data Mux", NULL, "I2S1_2"}, + + {"TDM2 slot 67 Data Mux", "1234", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 67 Data Mux", "1243", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 67 Data Mux", "1324", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 67 Data Mux", "1342", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 67 Data Mux", "1432", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 67 Data Mux", "1423", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 67 Data Mux", "2134", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 67 Data Mux", "2143", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 67 Data Mux", "2314", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 67 Data Mux", "2341", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 67 Data Mux", "2431", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 67 Data Mux", "2413", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 67 Data Mux", "3124", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 67 Data Mux", "3142", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 67 Data Mux", "3214", "IF1_2_ADC4 Mux"}, + {"TDM2 slot 67 Data Mux", "3241", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 67 Data Mux", "3412", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 67 Data Mux", "3421", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 67 Data Mux", "4123", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 67 Data Mux", "4132", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 67 Data Mux", "4213", "IF1_2_ADC3 Mux"}, + {"TDM2 slot 67 Data Mux", "4231", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 67 Data Mux", "4312", "IF1_2_ADC2 Mux"}, + {"TDM2 slot 67 Data Mux", "4321", "IF1_2_ADC1 Mux"}, + {"TDM2 slot 67 Data Mux", NULL, "I2S1_2"}, + + {"IF1_1 0 ADC Swap Mux", "L/R", "TDM1 slot 01 Data Mux"}, + {"IF1_1 0 ADC Swap Mux", "L/L", "TDM1 slot 01 Data Mux"}, + {"IF1_1 1 ADC Swap Mux", "R/L", "TDM1 slot 01 Data Mux"}, + {"IF1_1 1 ADC Swap Mux", "R/R", "TDM1 slot 01 Data Mux"}, + {"IF1_1 2 ADC Swap Mux", "L/R", "TDM1 slot 23 Data Mux"}, + {"IF1_1 2 ADC Swap Mux", "R/L", "TDM1 slot 23 Data Mux"}, + {"IF1_1 3 ADC Swap Mux", "L/L", "TDM1 slot 23 Data Mux"}, + {"IF1_1 3 ADC Swap Mux", "R/R", "TDM1 slot 23 Data Mux"}, + {"IF1_1 4 ADC Swap Mux", "L/R", "TDM1 slot 45 Data Mux"}, + {"IF1_1 4 ADC Swap Mux", "R/L", "TDM1 slot 45 Data Mux"}, + {"IF1_1 5 ADC Swap Mux", "L/L", "TDM1 slot 45 Data Mux"}, + {"IF1_1 5 ADC Swap Mux", "R/R", "TDM1 slot 45 Data Mux"}, + {"IF1_1 6 ADC Swap Mux", "L/R", "TDM1 slot 67 Data Mux"}, + {"IF1_1 6 ADC Swap Mux", "R/L", "TDM1 slot 67 Data Mux"}, + {"IF1_1 7 ADC Swap Mux", "L/L", "TDM1 slot 67 Data Mux"}, + {"IF1_1 7 ADC Swap Mux", "R/R", "TDM1 slot 67 Data Mux"}, + {"IF1_2 0 ADC Swap Mux", "L/R", "TDM2 slot 01 Data Mux"}, + {"IF1_2 0 ADC Swap Mux", "R/L", "TDM2 slot 01 Data Mux"}, + {"IF1_2 1 ADC Swap Mux", "L/L", "TDM2 slot 01 Data Mux"}, + {"IF1_2 1 ADC Swap Mux", "R/R", "TDM2 slot 01 Data Mux"}, + {"IF1_2 2 ADC Swap Mux", "L/R", "TDM2 slot 23 Data Mux"}, + {"IF1_2 2 ADC Swap Mux", "R/L", "TDM2 slot 23 Data Mux"}, + {"IF1_2 3 ADC Swap Mux", "L/L", "TDM2 slot 23 Data Mux"}, + {"IF1_2 3 ADC Swap Mux", "R/R", "TDM2 slot 23 Data Mux"}, + {"IF1_2 4 ADC Swap Mux", "L/R", "TDM2 slot 45 Data Mux"}, + {"IF1_2 4 ADC Swap Mux", "R/L", "TDM2 slot 45 Data Mux"}, + {"IF1_2 5 ADC Swap Mux", "L/L", "TDM2 slot 45 Data Mux"}, + {"IF1_2 5 ADC Swap Mux", "R/R", "TDM2 slot 45 Data Mux"}, + {"IF1_2 6 ADC Swap Mux", "L/R", "TDM2 slot 67 Data Mux"}, + {"IF1_2 6 ADC Swap Mux", "R/L", "TDM2 slot 67 Data Mux"}, + {"IF1_2 7 ADC Swap Mux", "L/L", "TDM2 slot 67 Data Mux"}, + {"IF1_2 7 ADC Swap Mux", "R/R", "TDM2 slot 67 Data Mux"}, + + {"IF2_1 ADC Mux", "STO1 ADC", "Stereo1 ADC MIX"}, + {"IF2_1 ADC Mux", "STO2 ADC", "Stereo2 ADC MIX"}, + {"IF2_1 ADC Mux", "MONO ADC", "Mono ADC MIX"}, + {"IF2_1 ADC Mux", "IF1 DAC1", "IF1 DAC1"}, + {"IF2_1 ADC Mux", "IF1 DAC2", "IF1 DAC2"}, + {"IF2_1 ADC Mux", "IF2_2 DAC", "IF2_2 DAC"}, + {"IF2_1 ADC Mux", "IF3 DAC", "IF3 DAC"}, + {"IF2_1 ADC Mux", "DAC1 MIX", "DAC1 MIX"}, + {"IF2_1 ADC", NULL, "IF2_1 ADC Mux"}, + {"IF2_1 ADC", NULL, "I2S2_1"}, + + {"IF2_2 ADC Mux", "STO1 ADC", "Stereo1 ADC MIX"}, + {"IF2_2 ADC Mux", "STO2 ADC", "Stereo2 ADC MIX"}, + {"IF2_2 ADC Mux", "MONO ADC", "Mono ADC MIX"}, + {"IF2_2 ADC Mux", "IF1 DAC1", "IF1 DAC1"}, + {"IF2_2 ADC Mux", "IF1 DAC2", "IF1 DAC2"}, + {"IF2_2 ADC Mux", "IF2_1 DAC", "IF2_1 DAC"}, + {"IF2_2 ADC Mux", "IF3 DAC", "IF3 DAC"}, + {"IF2_2 ADC Mux", "DAC1 MIX", "DAC1 MIX"}, + {"IF2_2 ADC", NULL, "IF2_2 ADC Mux"}, + {"IF2_2 ADC", NULL, "I2S2_2"}, + + {"IF3 ADC Mux", "STO1 ADC", "Stereo1 ADC MIX"}, + {"IF3 ADC Mux", "STO2 ADC", "Stereo2 ADC MIX"}, + {"IF3 ADC Mux", "MONO ADC", "Mono ADC MIX"}, + {"IF3 ADC Mux", "IF1 DAC1", "IF1 DAC1"}, + {"IF3 ADC Mux", "IF1 DAC2", "IF1 DAC2"}, + {"IF3 ADC Mux", "IF2_1 DAC", "IF2_1 DAC"}, + {"IF3 ADC Mux", "IF2_2 DAC", "IF2_2 DAC"}, + {"IF3 ADC Mux", "DAC1 MIX", "DAC1 MIX"}, + {"IF3 ADC", NULL, "IF3 ADC Mux"}, + {"IF3 ADC", NULL, "I2S3"}, + + {"AIF1_1TX slot 0", NULL, "IF1_1 0 ADC Swap Mux"}, + {"AIF1_1TX slot 1", NULL, "IF1_1 1 ADC Swap Mux"}, + {"AIF1_1TX slot 2", NULL, "IF1_1 2 ADC Swap Mux"}, + {"AIF1_1TX slot 3", NULL, "IF1_1 3 ADC Swap Mux"}, + {"AIF1_1TX slot 4", NULL, "IF1_1 4 ADC Swap Mux"}, + {"AIF1_1TX slot 5", NULL, "IF1_1 5 ADC Swap Mux"}, + {"AIF1_1TX slot 6", NULL, "IF1_1 6 ADC Swap Mux"}, + {"AIF1_1TX slot 7", NULL, "IF1_1 7 ADC Swap Mux"}, + {"AIF1_2TX slot 0", NULL, "IF1_2 0 ADC Swap Mux"}, + {"AIF1_2TX slot 1", NULL, "IF1_2 1 ADC Swap Mux"}, + {"AIF1_2TX slot 2", NULL, "IF1_2 2 ADC Swap Mux"}, + {"AIF1_2TX slot 3", NULL, "IF1_2 3 ADC Swap Mux"}, + {"AIF1_2TX slot 4", NULL, "IF1_2 4 ADC Swap Mux"}, + {"AIF1_2TX slot 5", NULL, "IF1_2 5 ADC Swap Mux"}, + {"AIF1_2TX slot 6", NULL, "IF1_2 6 ADC Swap Mux"}, + {"AIF1_2TX slot 7", NULL, "IF1_2 7 ADC Swap Mux"}, + {"IF2_1 ADC Swap Mux", "L/R", "IF2_1 ADC"}, + {"IF2_1 ADC Swap Mux", "R/L", "IF2_1 ADC"}, + {"IF2_1 ADC Swap Mux", "L/L", "IF2_1 ADC"}, + {"IF2_1 ADC Swap Mux", "R/R", "IF2_1 ADC"}, + {"AIF2_1TX", NULL, "IF2_1 ADC Swap Mux"}, + {"IF2_2 ADC Swap Mux", "L/R", "IF2_2 ADC"}, + {"IF2_2 ADC Swap Mux", "R/L", "IF2_2 ADC"}, + {"IF2_2 ADC Swap Mux", "L/L", "IF2_2 ADC"}, + {"IF2_2 ADC Swap Mux", "R/R", "IF2_2 ADC"}, + {"AIF2_2TX", NULL, "IF2_2 ADC Swap Mux"}, + {"IF3 ADC Swap Mux", "L/R", "IF3 ADC"}, + {"IF3 ADC Swap Mux", "R/L", "IF3 ADC"}, + {"IF3 ADC Swap Mux", "L/L", "IF3 ADC"}, + {"IF3 ADC Swap Mux", "R/R", "IF3 ADC"}, + {"AIF3TX", NULL, "IF3 ADC Swap Mux"}, + + {"IF1 DAC1", NULL, "AIF1RX"}, + {"IF1 DAC2", NULL, "AIF1RX"}, + {"IF1 DAC3", NULL, "AIF1RX"}, + {"IF2_1 DAC Swap Mux", "L/R", "AIF2_1RX"}, + {"IF2_1 DAC Swap Mux", "R/L", "AIF2_1RX"}, + {"IF2_1 DAC Swap Mux", "L/L", "AIF2_1RX"}, + {"IF2_1 DAC Swap Mux", "R/R", "AIF2_1RX"}, + {"IF2_2 DAC Swap Mux", "L/R", "AIF2_2RX"}, + {"IF2_2 DAC Swap Mux", "R/L", "AIF2_2RX"}, + {"IF2_2 DAC Swap Mux", "L/L", "AIF2_2RX"}, + {"IF2_2 DAC Swap Mux", "R/R", "AIF2_2RX"}, + {"IF2_1 DAC", NULL, "IF2_1 DAC Swap Mux"}, + {"IF2_2 DAC", NULL, "IF2_2 DAC Swap Mux"}, + {"IF3 DAC Swap Mux", "L/R", "AIF3RX"}, + {"IF3 DAC Swap Mux", "R/L", "AIF3RX"}, + {"IF3 DAC Swap Mux", "L/L", "AIF3RX"}, + {"IF3 DAC Swap Mux", "R/R", "AIF3RX"}, + {"IF3 DAC", NULL, "IF3 DAC Swap Mux"}, + + {"IF1 DAC1", NULL, "I2S1_1"}, + {"IF1 DAC2", NULL, "I2S1_1"}, + {"IF1 DAC3", NULL, "I2S1_1"}, + {"IF2_1 DAC", NULL, "I2S2_1"}, + {"IF2_2 DAC", NULL, "I2S2_2"}, + {"IF3 DAC", NULL, "I2S3"}, + + {"IF1 DAC1 L", NULL, "IF1 DAC1"}, + {"IF1 DAC1 R", NULL, "IF1 DAC1"}, + {"IF1 DAC2 L", NULL, "IF1 DAC2"}, + {"IF1 DAC2 R", NULL, "IF1 DAC2"}, + {"IF1 DAC3 L", NULL, "IF1 DAC3"}, + {"IF1 DAC3 R", NULL, "IF1 DAC3"}, + {"IF2_1 DAC L", NULL, "IF2_1 DAC"}, + {"IF2_1 DAC R", NULL, "IF2_1 DAC"}, + {"IF2_2 DAC L", NULL, "IF2_2 DAC"}, + {"IF2_2 DAC R", NULL, "IF2_2 DAC"}, + {"IF3 DAC L", NULL, "IF3 DAC"}, + {"IF3 DAC R", NULL, "IF3 DAC"}, + + {"DAC L1 Mux", "IF1 DAC1", "IF1 DAC1 L"}, + {"DAC L1 Mux", "IF2_1 DAC", "IF2_1 DAC L"}, + {"DAC L1 Mux", "IF2_2 DAC", "IF2_2 DAC L"}, + {"DAC L1 Mux", "IF3 DAC", "IF3 DAC L"}, + {"DAC L1 Mux", NULL, "DAC Stereo1 Filter"}, + + {"DAC R1 Mux", "IF1 DAC1", "IF1 DAC1 R"}, + {"DAC R1 Mux", "IF2_1 DAC", "IF2_1 DAC R"}, + {"DAC R1 Mux", "IF2_2 DAC", "IF2_2 DAC R"}, + {"DAC R1 Mux", "IF3 DAC", "IF3 DAC R"}, + {"DAC R1 Mux", NULL, "DAC Stereo1 Filter"}, + + {"DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL"}, + {"DAC1 MIXL", "DAC1 Switch", "DAC L1 Mux"}, + {"DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR"}, + {"DAC1 MIXR", "DAC1 Switch", "DAC R1 Mux"}, + + {"DAC1 MIX", NULL, "DAC1 MIXL"}, + {"DAC1 MIX", NULL, "DAC1 MIXR"}, + + {"DAC L2 Mux", "IF1 DAC2", "IF1 DAC2 L"}, + {"DAC L2 Mux", "IF2_1 DAC", "IF2_1 DAC L"}, + {"DAC L2 Mux", "IF2_2 DAC", "IF2_2 DAC L"}, + {"DAC L2 Mux", "IF3 DAC", "IF3 DAC L"}, + {"DAC L2 Mux", "Mono ADC MIX", "Mono ADC MIXL"}, + {"DAC L2 Mux", NULL, "DAC Mono Left Filter"}, + + {"DAC R2 Mux", "IF1 DAC2", "IF1 DAC2 R"}, + {"DAC R2 Mux", "IF2_1 DAC", "IF2_1 DAC R"}, + {"DAC R2 Mux", "IF2_2 DAC", "IF2_2 DAC R"}, + {"DAC R2 Mux", "IF3 DAC", "IF3 DAC R"}, + {"DAC R2 Mux", "Mono ADC MIX", "Mono ADC MIXR"}, + {"DAC R2 Mux", NULL, "DAC Mono Right Filter"}, + + {"DAC L3 Mux", "IF1 DAC2", "IF1 DAC2 L"}, + {"DAC L3 Mux", "IF2_1 DAC", "IF2_1 DAC L"}, + {"DAC L3 Mux", "IF2_2 DAC", "IF2_2 DAC L"}, + {"DAC L3 Mux", "IF3 DAC", "IF3 DAC L"}, + {"DAC L3 Mux", "STO2 ADC MIX", "Stereo2 ADC MIXL"}, + {"DAC L3 Mux", NULL, "DAC Stereo2 Filter"}, + + {"DAC R3 Mux", "IF1 DAC2", "IF1 DAC2 R"}, + {"DAC R3 Mux", "IF2_1 DAC", "IF2_1 DAC R"}, + {"DAC R3 Mux", "IF2_2 DAC", "IF2_2 DAC R"}, + {"DAC R3 Mux", "IF3 DAC", "IF3 DAC R"}, + {"DAC R3 Mux", "STO2 ADC MIX", "Stereo2 ADC MIXR"}, + {"DAC R3 Mux", NULL, "DAC Stereo2 Filter"}, + + {"Stereo1 DAC MIXL", "DAC L1 Switch", "DAC1 MIXL"}, + {"Stereo1 DAC MIXL", "DAC R1 Switch", "DAC1 MIXR"}, + {"Stereo1 DAC MIXL", "DAC L2 Switch", "DAC L2 Mux"}, + {"Stereo1 DAC MIXL", "DAC R2 Switch", "DAC R2 Mux"}, + + {"Stereo1 DAC MIXR", "DAC R1 Switch", "DAC1 MIXR"}, + {"Stereo1 DAC MIXR", "DAC L1 Switch", "DAC1 MIXL"}, + {"Stereo1 DAC MIXR", "DAC L2 Switch", "DAC L2 Mux"}, + {"Stereo1 DAC MIXR", "DAC R2 Switch", "DAC R2 Mux"}, + + {"Stereo2 DAC MIXL", "DAC L1 Switch", "DAC1 MIXL"}, + {"Stereo2 DAC MIXL", "DAC L2 Switch", "DAC L2 Mux"}, + {"Stereo2 DAC MIXL", "DAC L3 Switch", "DAC L3 Mux"}, + + {"Stereo2 DAC MIXR", "DAC R1 Switch", "DAC1 MIXR"}, + {"Stereo2 DAC MIXR", "DAC R2 Switch", "DAC R2 Mux"}, + {"Stereo2 DAC MIXR", "DAC R3 Switch", "DAC R3 Mux"}, + + {"Mono DAC MIXL", "DAC L1 Switch", "DAC1 MIXL"}, + {"Mono DAC MIXL", "DAC R1 Switch", "DAC1 MIXR"}, + {"Mono DAC MIXL", "DAC L2 Switch", "DAC L2 Mux"}, + {"Mono DAC MIXL", "DAC R2 Switch", "DAC R2 Mux"}, + {"Mono DAC MIXR", "DAC L1 Switch", "DAC1 MIXL"}, + {"Mono DAC MIXR", "DAC R1 Switch", "DAC1 MIXR"}, + {"Mono DAC MIXR", "DAC L2 Switch", "DAC L2 Mux"}, + {"Mono DAC MIXR", "DAC R2 Switch", "DAC R2 Mux"}, + + {"DAC MIXL", "Stereo1 DAC Mixer", "Stereo1 DAC MIXL"}, + {"DAC MIXL", "Stereo2 DAC Mixer", "Stereo2 DAC MIXL"}, + {"DAC MIXL", "Mono DAC Mixer", "Mono DAC MIXL"}, + {"DAC MIXR", "Stereo1 DAC Mixer", "Stereo1 DAC MIXR"}, + {"DAC MIXR", "Stereo2 DAC Mixer", "Stereo2 DAC MIXR"}, + {"DAC MIXR", "Mono DAC Mixer", "Mono DAC MIXR"}, + + {"DAC L1 Source", "DAC1", "DAC1 MIXL"}, + {"DAC L1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXL"}, + {"DAC L1 Source", "DMIC1", "DMIC L1"}, + {"DAC R1 Source", "DAC1", "DAC1 MIXR"}, + {"DAC R1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXR"}, + {"DAC R1 Source", "DMIC1", "DMIC R1"}, + + {"DAC L2 Source", "DAC2", "DAC L2 Mux"}, + {"DAC L2 Source", "Mono DAC Mixer", "Mono DAC MIXL"}, + {"DAC L2 Source", NULL, "DAC L2 Power"}, + {"DAC R2 Source", "DAC2", "DAC R2 Mux"}, + {"DAC R2 Source", "Mono DAC Mixer", "Mono DAC MIXR"}, + {"DAC R2 Source", NULL, "DAC R2 Power"}, + + {"DAC L1", NULL, "DAC L1 Source"}, + {"DAC R1", NULL, "DAC R1 Source"}, + {"DAC L2", NULL, "DAC L2 Source"}, + {"DAC R2", NULL, "DAC R2 Source"}, + + {"DAC L1", NULL, "DAC 1 Clock"}, + {"DAC R1", NULL, "DAC 1 Clock"}, + {"DAC L2", NULL, "DAC 2 Clock"}, + {"DAC R2", NULL, "DAC 2 Clock"}, + + {"MONOVOL MIX", "DAC L2 Switch", "DAC L2"}, + {"MONOVOL MIX", "RECMIX2L Switch", "RECMIX2L"}, + {"MONOVOL MIX", "BST1 Switch", "BST1"}, + {"MONOVOL MIX", "BST2 Switch", "BST2"}, + {"MONOVOL MIX", "BST3 Switch", "BST3"}, + + {"OUT MIXL", "DAC L2 Switch", "DAC L2"}, + {"OUT MIXL", "INL Switch", "INL VOL"}, + {"OUT MIXL", "BST1 Switch", "BST1"}, + {"OUT MIXL", "BST2 Switch", "BST2"}, + {"OUT MIXL", "BST3 Switch", "BST3"}, + {"OUT MIXR", "DAC R2 Switch", "DAC R2"}, + {"OUT MIXR", "INR Switch", "INR VOL"}, + {"OUT MIXR", "BST2 Switch", "BST2"}, + {"OUT MIXR", "BST3 Switch", "BST3"}, + {"OUT MIXR", "BST4 Switch", "BST4"}, + + {"MONOVOL", "Switch", "MONOVOL MIX"}, + {"Mono MIX", "DAC L2 Switch", "DAC L2"}, + {"Mono MIX", "MONOVOL Switch", "MONOVOL"}, + {"Mono Amp", NULL, "Mono MIX"}, + {"Mono Amp", NULL, "Vref2"}, + {"Mono Amp", NULL, "Vref3"}, + {"Mono Amp", NULL, "CLKDET SYS"}, + {"Mono Amp", NULL, "CLKDET MONO"}, + {"Mono Playback", "Switch", "Mono Amp"}, + {"MONOOUT", NULL, "Mono Playback"}, + + {"HP Amp", NULL, "DAC L1"}, + {"HP Amp", NULL, "DAC R1"}, + {"HP Amp", NULL, "Charge Pump"}, + {"HP Amp", NULL, "CLKDET SYS"}, + {"HP Amp", NULL, "CLKDET HP"}, + {"HP Amp", NULL, "CBJ Power"}, + {"HP Amp", NULL, "Vref2"}, + {"HPO Playback", "Switch", "HP Amp"}, + {"HPOL", NULL, "HPO Playback"}, + {"HPOR", NULL, "HPO Playback"}, + + {"OUTVOL L", "Switch", "OUT MIXL"}, + {"OUTVOL R", "Switch", "OUT MIXR"}, + {"LOUT L MIX", "DAC L2 Switch", "DAC L2"}, + {"LOUT L MIX", "OUTVOL L Switch", "OUTVOL L"}, + {"LOUT R MIX", "DAC R2 Switch", "DAC R2"}, + {"LOUT R MIX", "OUTVOL R Switch", "OUTVOL R"}, + {"LOUT Amp", NULL, "LOUT L MIX"}, + {"LOUT Amp", NULL, "LOUT R MIX"}, + {"LOUT Amp", NULL, "Vref1"}, + {"LOUT Amp", NULL, "Vref2"}, + {"LOUT Amp", NULL, "CLKDET SYS"}, + {"LOUT Amp", NULL, "CLKDET LOUT"}, + {"LOUT L Playback", "Switch", "LOUT Amp"}, + {"LOUT R Playback", "Switch", "LOUT Amp"}, + {"LOUTL", NULL, "LOUT L Playback"}, + {"LOUTR", NULL, "LOUT R Playback"}, + + {"PDM L Mux", "Mono DAC", "Mono DAC MIXL"}, + {"PDM L Mux", "Stereo1 DAC", "Stereo1 DAC MIXL"}, + {"PDM L Mux", "Stereo2 DAC", "Stereo2 DAC MIXL"}, + {"PDM L Mux", NULL, "PDM Power"}, + {"PDM R Mux", "Mono DAC", "Mono DAC MIXR"}, + {"PDM R Mux", "Stereo1 DAC", "Stereo1 DAC MIXR"}, + {"PDM R Mux", "Stereo2 DAC", "Stereo2 DAC MIXR"}, + {"PDM R Mux", NULL, "PDM Power"}, + {"PDM L Playback", "Switch", "PDM L Mux"}, + {"PDM R Playback", "Switch", "PDM R Mux"}, + {"PDML", NULL, "PDM L Playback"}, + {"PDMR", NULL, "PDM R Playback"}, +}; + +static int rt5665_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val = 0; + + if (rx_mask || tx_mask) + val |= RT5665_I2S1_MODE_TDM; + + switch (slots) { + case 4: + val |= RT5665_TDM_IN_CH_4; + val |= RT5665_TDM_OUT_CH_4; + break; + case 6: + val |= RT5665_TDM_IN_CH_6; + val |= RT5665_TDM_OUT_CH_6; + break; + case 8: + val |= RT5665_TDM_IN_CH_8; + val |= RT5665_TDM_OUT_CH_8; + break; + case 2: + break; + default: + return -EINVAL; + } + + switch (slot_width) { + case 20: + val |= RT5665_TDM_IN_LEN_20; + val |= RT5665_TDM_OUT_LEN_20; + break; + case 24: + val |= RT5665_TDM_IN_LEN_24; + val |= RT5665_TDM_OUT_LEN_24; + break; + case 32: + val |= RT5665_TDM_IN_LEN_32; + val |= RT5665_TDM_OUT_LEN_32; + break; + case 16: + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5665_TDM_CTRL_1, + RT5665_I2S1_MODE_MASK | RT5665_TDM_IN_CH_MASK | + RT5665_TDM_OUT_CH_MASK | RT5665_TDM_IN_LEN_MASK | + RT5665_TDM_OUT_LEN_MASK, val); + + return 0; +} + + +static int rt5665_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, reg_clk, mask_clk, val_bits = 0x0100; + int pre_div, frame_size; + + rt5665->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5665->sysclk, rt5665->lrck[dai->id]); + if (pre_div < 0) { + dev_warn(component->dev, "Force using PLL"); + snd_soc_component_set_pll(component, 0, RT5665_PLL1_S_MCLK, + rt5665->sysclk, rt5665->lrck[dai->id] * 512); + snd_soc_component_set_sysclk(component, RT5665_SCLK_S_PLL1, 0, + rt5665->lrck[dai->id] * 512, 0); + pre_div = 1; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + + dev_dbg(dai->dev, "lrck is %dHz and pre_div is %d for iis %d\n", + rt5665->lrck[dai->id], pre_div, dai->id); + + switch (params_width(params)) { + case 16: + val_bits = 0x0100; + break; + case 20: + val_len |= RT5665_I2S_DL_20; + val_bits = 0x1300; + break; + case 24: + val_len |= RT5665_I2S_DL_24; + val_bits = 0x2500; + break; + case 8: + val_len |= RT5665_I2S_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5665_AIF1_1: + case RT5665_AIF1_2: + if (params_channels(params) > 2) + rt5665_set_tdm_slot(dai, 0xf, 0xf, + params_channels(params), params_width(params)); + reg_clk = RT5665_ADDA_CLK_1; + mask_clk = RT5665_I2S_PD1_MASK; + val_clk = pre_div << RT5665_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5665_I2S1_SDP, + RT5665_I2S_DL_MASK, val_len); + break; + case RT5665_AIF2_1: + case RT5665_AIF2_2: + reg_clk = RT5665_ADDA_CLK_2; + mask_clk = RT5665_I2S_PD2_MASK; + val_clk = pre_div << RT5665_I2S_PD2_SFT; + snd_soc_component_update_bits(component, RT5665_I2S2_SDP, + RT5665_I2S_DL_MASK, val_len); + break; + case RT5665_AIF3: + reg_clk = RT5665_ADDA_CLK_2; + mask_clk = RT5665_I2S_PD3_MASK; + val_clk = pre_div << RT5665_I2S_PD3_SFT; + snd_soc_component_update_bits(component, RT5665_I2S3_SDP, + RT5665_I2S_DL_MASK, val_len); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + snd_soc_component_update_bits(component, reg_clk, mask_clk, val_clk); + snd_soc_component_update_bits(component, RT5665_STO1_DAC_SIL_DET, 0x3700, val_bits); + + switch (rt5665->lrck[dai->id]) { + case 192000: + snd_soc_component_update_bits(component, RT5665_ADDA_CLK_1, + RT5665_DAC_OSR_MASK | RT5665_ADC_OSR_MASK, + RT5665_DAC_OSR_32 | RT5665_ADC_OSR_32); + break; + case 96000: + snd_soc_component_update_bits(component, RT5665_ADDA_CLK_1, + RT5665_DAC_OSR_MASK | RT5665_ADC_OSR_MASK, + RT5665_DAC_OSR_64 | RT5665_ADC_OSR_64); + break; + default: + snd_soc_component_update_bits(component, RT5665_ADDA_CLK_1, + RT5665_DAC_OSR_MASK | RT5665_ADC_OSR_MASK, + RT5665_DAC_OSR_128 | RT5665_ADC_OSR_128); + break; + } + + if (rt5665->master[RT5665_AIF2_1] || rt5665->master[RT5665_AIF2_2]) { + snd_soc_component_update_bits(component, RT5665_I2S_M_CLK_CTRL_1, + RT5665_I2S2_M_PD_MASK, pre_div << RT5665_I2S2_M_PD_SFT); + } + if (rt5665->master[RT5665_AIF3]) { + snd_soc_component_update_bits(component, RT5665_I2S_M_CLK_CTRL_1, + RT5665_I2S3_M_PD_MASK, pre_div << RT5665_I2S3_M_PD_SFT); + } + + return 0; +} + +static int rt5665_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5665->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5665_I2S_MS_S; + rt5665->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5665_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5665_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5665_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5665_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5665_AIF1_1: + case RT5665_AIF1_2: + snd_soc_component_update_bits(component, RT5665_I2S1_SDP, + RT5665_I2S_MS_MASK | RT5665_I2S_BP_MASK | + RT5665_I2S_DF_MASK, reg_val); + break; + case RT5665_AIF2_1: + case RT5665_AIF2_2: + snd_soc_component_update_bits(component, RT5665_I2S2_SDP, + RT5665_I2S_MS_MASK | RT5665_I2S_BP_MASK | + RT5665_I2S_DF_MASK, reg_val); + break; + case RT5665_AIF3: + snd_soc_component_update_bits(component, RT5665_I2S3_SDP, + RT5665_I2S_MS_MASK | RT5665_I2S_BP_MASK | + RT5665_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5665_set_component_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, src = 0; + + if (freq == rt5665->sysclk && clk_id == rt5665->sysclk_src) + return 0; + + switch (clk_id) { + case RT5665_SCLK_S_MCLK: + reg_val |= RT5665_SCLK_SRC_MCLK; + src = RT5665_CLK_SRC_MCLK; + break; + case RT5665_SCLK_S_PLL1: + reg_val |= RT5665_SCLK_SRC_PLL1; + src = RT5665_CLK_SRC_PLL1; + break; + case RT5665_SCLK_S_RCCLK: + reg_val |= RT5665_SCLK_SRC_RCCLK; + src = RT5665_CLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5665_GLB_CLK, + RT5665_SCLK_SRC_MASK, reg_val); + + if (rt5665->master[RT5665_AIF2_1] || rt5665->master[RT5665_AIF2_2]) { + snd_soc_component_update_bits(component, RT5665_I2S_M_CLK_CTRL_1, + RT5665_I2S2_SRC_MASK, src << RT5665_I2S2_SRC_SFT); + } + if (rt5665->master[RT5665_AIF3]) { + snd_soc_component_update_bits(component, RT5665_I2S_M_CLK_CTRL_1, + RT5665_I2S3_SRC_MASK, src << RT5665_I2S3_SRC_SFT); + } + + rt5665->sysclk = freq; + rt5665->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + + return 0; +} + +static int rt5665_set_component_pll(struct snd_soc_component *component, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5665->pll_src && freq_in == rt5665->pll_in && + freq_out == rt5665->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5665->pll_in = 0; + rt5665->pll_out = 0; + snd_soc_component_update_bits(component, RT5665_GLB_CLK, + RT5665_SCLK_SRC_MASK, RT5665_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5665_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5665_GLB_CLK, + RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_MCLK); + break; + case RT5665_PLL1_S_BCLK1: + snd_soc_component_update_bits(component, RT5665_GLB_CLK, + RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_BCLK1); + break; + case RT5665_PLL1_S_BCLK2: + snd_soc_component_update_bits(component, RT5665_GLB_CLK, + RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_BCLK2); + break; + case RT5665_PLL1_S_BCLK3: + snd_soc_component_update_bits(component, RT5665_GLB_CLK, + RT5665_PLL1_SRC_MASK, RT5665_PLL1_SRC_BCLK3); + break; + default: + dev_err(component->dev, "Unknown PLL Source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5665_PLL_CTRL_1, + pll_code.n_code << RT5665_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5665_PLL_CTRL_2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5665_PLL_M_SFT | + pll_code.m_bp << RT5665_PLL_M_BP_SFT); + + rt5665->pll_in = freq_in; + rt5665->pll_out = freq_out; + rt5665->pll_src = source; + + return 0; +} + +static int rt5665_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s ratio=%d\n", __func__, ratio); + + rt5665->bclk[dai->id] = ratio; + + if (ratio == 64) { + switch (dai->id) { + case RT5665_AIF2_1: + case RT5665_AIF2_2: + snd_soc_component_update_bits(component, RT5665_ADDA_CLK_2, + RT5665_I2S_BCLK_MS2_MASK, + RT5665_I2S_BCLK_MS2_64); + break; + case RT5665_AIF3: + snd_soc_component_update_bits(component, RT5665_ADDA_CLK_2, + RT5665_I2S_BCLK_MS3_MASK, + RT5665_I2S_BCLK_MS3_64); + break; + } + } + + return 0; +} + +static int rt5665_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(rt5665->regmap, RT5665_DIG_MISC, + RT5665_DIG_GATE_CTRL, RT5665_DIG_GATE_CTRL); + break; + + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(rt5665->regmap, RT5665_PWR_DIG_1, + RT5665_PWR_LDO, RT5665_PWR_LDO); + regmap_update_bits(rt5665->regmap, RT5665_PWR_ANLG_1, + RT5665_PWR_MB, RT5665_PWR_MB); + regmap_update_bits(rt5665->regmap, RT5665_DIG_MISC, + RT5665_DIG_GATE_CTRL, 0); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(rt5665->regmap, RT5665_PWR_DIG_1, + RT5665_PWR_LDO, 0); + regmap_update_bits(rt5665->regmap, RT5665_PWR_ANLG_1, + RT5665_PWR_MB, 0); + break; + + default: + break; + } + + return 0; +} + +static int rt5665_probe(struct snd_soc_component *component) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + + rt5665->component = component; + + schedule_delayed_work(&rt5665->calibrate_work, msecs_to_jiffies(100)); + + return 0; +} + +static void rt5665_remove(struct snd_soc_component *component) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + + regmap_write(rt5665->regmap, RT5665_RESET, 0); +} + +#ifdef CONFIG_PM +static int rt5665_suspend(struct snd_soc_component *component) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5665->regmap, true); + regcache_mark_dirty(rt5665->regmap); + return 0; +} + +static int rt5665_resume(struct snd_soc_component *component) +{ + struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5665->regmap, false); + regcache_sync(rt5665->regmap); + + return 0; +} +#else +#define rt5665_suspend NULL +#define rt5665_resume NULL +#endif + +#define RT5665_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5665_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5665_aif_dai_ops = { + .hw_params = rt5665_hw_params, + .set_fmt = rt5665_set_dai_fmt, + .set_tdm_slot = rt5665_set_tdm_slot, + .set_bclk_ratio = rt5665_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt5665_dai[] = { + { + .name = "rt5665-aif1_1", + .id = RT5665_AIF1_1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .capture = { + .stream_name = "AIF1_1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .ops = &rt5665_aif_dai_ops, + }, + { + .name = "rt5665-aif1_2", + .id = RT5665_AIF1_2, + .capture = { + .stream_name = "AIF1_2 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .ops = &rt5665_aif_dai_ops, + }, + { + .name = "rt5665-aif2_1", + .id = RT5665_AIF2_1, + .playback = { + .stream_name = "AIF2_1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .capture = { + .stream_name = "AIF2_1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .ops = &rt5665_aif_dai_ops, + }, + { + .name = "rt5665-aif2_2", + .id = RT5665_AIF2_2, + .playback = { + .stream_name = "AIF2_2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .capture = { + .stream_name = "AIF2_2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .ops = &rt5665_aif_dai_ops, + }, + { + .name = "rt5665-aif3", + .id = RT5665_AIF3, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5665_STEREO_RATES, + .formats = RT5665_FORMATS, + }, + .ops = &rt5665_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5665 = { + .probe = rt5665_probe, + .remove = rt5665_remove, + .suspend = rt5665_suspend, + .resume = rt5665_resume, + .set_bias_level = rt5665_set_bias_level, + .controls = rt5665_snd_controls, + .num_controls = ARRAY_SIZE(rt5665_snd_controls), + .dapm_widgets = rt5665_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5665_dapm_widgets), + .dapm_routes = rt5665_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5665_dapm_routes), + .set_sysclk = rt5665_set_component_sysclk, + .set_pll = rt5665_set_component_pll, + .set_jack = rt5665_set_jack_detect, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + + +static const struct regmap_config rt5665_regmap = { + .reg_bits = 16, + .val_bits = 16, + .max_register = 0x0400, + .volatile_reg = rt5665_volatile_register, + .readable_reg = rt5665_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5665_reg, + .num_reg_defaults = ARRAY_SIZE(rt5665_reg), + .use_single_rw = true, +}; + +static const struct i2c_device_id rt5665_i2c_id[] = { + {"rt5665", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt5665_i2c_id); + +static int rt5665_parse_dt(struct rt5665_priv *rt5665, struct device *dev) +{ + rt5665->pdata.in1_diff = of_property_read_bool(dev->of_node, + "realtek,in1-differential"); + rt5665->pdata.in2_diff = of_property_read_bool(dev->of_node, + "realtek,in2-differential"); + rt5665->pdata.in3_diff = of_property_read_bool(dev->of_node, + "realtek,in3-differential"); + rt5665->pdata.in4_diff = of_property_read_bool(dev->of_node, + "realtek,in4-differential"); + + of_property_read_u32(dev->of_node, "realtek,dmic1-data-pin", + &rt5665->pdata.dmic1_data_pin); + of_property_read_u32(dev->of_node, "realtek,dmic2-data-pin", + &rt5665->pdata.dmic2_data_pin); + of_property_read_u32(dev->of_node, "realtek,jd-src", + &rt5665->pdata.jd_src); + + rt5665->pdata.ldo1_en = of_get_named_gpio(dev->of_node, + "realtek,ldo1-en-gpios", 0); + + return 0; +} + +static void rt5665_calibrate(struct rt5665_priv *rt5665) +{ + int value, count; + + mutex_lock(&rt5665->calibrate_mutex); + + regcache_cache_bypass(rt5665->regmap, true); + + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regmap_write(rt5665->regmap, RT5665_BIAS_CUR_CTRL_8, 0xa602); + regmap_write(rt5665->regmap, RT5665_HP_CHARGE_PUMP_1, 0x0c26); + regmap_write(rt5665->regmap, RT5665_MONOMIX_IN_GAIN, 0x021f); + regmap_write(rt5665->regmap, RT5665_MONO_OUT, 0x480a); + regmap_write(rt5665->regmap, RT5665_PWR_MIXER, 0x083f); + regmap_write(rt5665->regmap, RT5665_PWR_DIG_1, 0x0180); + regmap_write(rt5665->regmap, RT5665_EJD_CTRL_1, 0x4040); + regmap_write(rt5665->regmap, RT5665_HP_LOGIC_CTRL_2, 0x0000); + regmap_write(rt5665->regmap, RT5665_DIG_MISC, 0x0001); + regmap_write(rt5665->regmap, RT5665_MICBIAS_2, 0x0380); + regmap_write(rt5665->regmap, RT5665_GLB_CLK, 0x8000); + regmap_write(rt5665->regmap, RT5665_ADDA_CLK_1, 0x1000); + regmap_write(rt5665->regmap, RT5665_CHOP_DAC, 0x3030); + regmap_write(rt5665->regmap, RT5665_CALIB_ADC_CTRL, 0x3c05); + regmap_write(rt5665->regmap, RT5665_PWR_ANLG_1, 0xaa3e); + usleep_range(15000, 20000); + regmap_write(rt5665->regmap, RT5665_PWR_ANLG_1, 0xfe7e); + regmap_write(rt5665->regmap, RT5665_HP_CALIB_CTRL_2, 0x0321); + + regmap_write(rt5665->regmap, RT5665_HP_CALIB_CTRL_1, 0xfc00); + count = 0; + while (true) { + regmap_read(rt5665->regmap, RT5665_HP_CALIB_STA_1, &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 60) { + pr_err("HP Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); + goto out_unlock; + } + + count++; + } + + regmap_write(rt5665->regmap, RT5665_MONO_AMP_CALIB_CTRL_1, 0x9e24); + count = 0; + while (true) { + regmap_read(rt5665->regmap, RT5665_MONO_AMP_CALIB_STA1, &value); + if (value & 0x8000) + usleep_range(10000, 10005); + else + break; + + if (count > 60) { + pr_err("MONO Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); + goto out_unlock; + } + + count++; + } + + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); + + regcache_mark_dirty(rt5665->regmap); + regcache_sync(rt5665->regmap); + + regmap_write(rt5665->regmap, RT5665_BIAS_CUR_CTRL_8, 0xa602); + regmap_write(rt5665->regmap, RT5665_ASRC_8, 0x0120); + +out_unlock: + rt5665->calibration_done = true; + mutex_unlock(&rt5665->calibrate_mutex); +} + +static void rt5665_calibrate_handler(struct work_struct *work) +{ + struct rt5665_priv *rt5665 = container_of(work, struct rt5665_priv, + calibrate_work.work); + + while (!rt5665->component->card->instantiated) { + pr_debug("%s\n", __func__); + usleep_range(10000, 15000); + } + + rt5665_calibrate(rt5665); +} + +static int rt5665_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5665_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5665_priv *rt5665; + int i, ret; + unsigned int val; + + rt5665 = devm_kzalloc(&i2c->dev, sizeof(struct rt5665_priv), + GFP_KERNEL); + + if (rt5665 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5665); + + if (pdata) + rt5665->pdata = *pdata; + else + rt5665_parse_dt(rt5665, &i2c->dev); + + for (i = 0; i < ARRAY_SIZE(rt5665->supplies); i++) + rt5665->supplies[i].supply = rt5665_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(rt5665->supplies), + rt5665->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(rt5665->supplies), + rt5665->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + if (gpio_is_valid(rt5665->pdata.ldo1_en)) { + if (devm_gpio_request_one(&i2c->dev, rt5665->pdata.ldo1_en, + GPIOF_OUT_INIT_HIGH, "rt5665")) + dev_err(&i2c->dev, "Fail gpio_request gpio_ldo\n"); + } + + /* Sleep for 300 ms miniumum */ + usleep_range(300000, 350000); + + rt5665->regmap = devm_regmap_init_i2c(i2c, &rt5665_regmap); + if (IS_ERR(rt5665->regmap)) { + ret = PTR_ERR(rt5665->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt5665->regmap, RT5665_DEVICE_ID, &val); + if (val != DEVICE_ID) { + dev_err(&i2c->dev, + "Device with ID register %x is not rt5665\n", val); + return -ENODEV; + } + + regmap_read(rt5665->regmap, RT5665_RESET, &val); + switch (val) { + case 0x0: + rt5665->id = CODEC_5666; + break; + case 0x3: + default: + rt5665->id = CODEC_5665; + break; + } + + regmap_write(rt5665->regmap, RT5665_RESET, 0); + + /* line in diff mode*/ + if (rt5665->pdata.in1_diff) + regmap_update_bits(rt5665->regmap, RT5665_IN1_IN2, + RT5665_IN1_DF_MASK, RT5665_IN1_DF_MASK); + if (rt5665->pdata.in2_diff) + regmap_update_bits(rt5665->regmap, RT5665_IN1_IN2, + RT5665_IN2_DF_MASK, RT5665_IN2_DF_MASK); + if (rt5665->pdata.in3_diff) + regmap_update_bits(rt5665->regmap, RT5665_IN3_IN4, + RT5665_IN3_DF_MASK, RT5665_IN3_DF_MASK); + if (rt5665->pdata.in4_diff) + regmap_update_bits(rt5665->regmap, RT5665_IN3_IN4, + RT5665_IN4_DF_MASK, RT5665_IN4_DF_MASK); + + /* DMIC pin*/ + if (rt5665->pdata.dmic1_data_pin != RT5665_DMIC1_NULL || + rt5665->pdata.dmic2_data_pin != RT5665_DMIC2_NULL) { + regmap_update_bits(rt5665->regmap, RT5665_GPIO_CTRL_2, + RT5665_GP9_PIN_MASK, RT5665_GP9_PIN_DMIC1_SCL); + regmap_update_bits(rt5665->regmap, RT5665_GPIO_CTRL_1, + RT5665_GP8_PIN_MASK, RT5665_GP8_PIN_DMIC2_SCL); + switch (rt5665->pdata.dmic1_data_pin) { + case RT5665_DMIC1_DATA_IN2N: + regmap_update_bits(rt5665->regmap, RT5665_DMIC_CTRL_1, + RT5665_DMIC_1_DP_MASK, RT5665_DMIC_1_DP_IN2N); + break; + + case RT5665_DMIC1_DATA_GPIO4: + regmap_update_bits(rt5665->regmap, RT5665_DMIC_CTRL_1, + RT5665_DMIC_1_DP_MASK, RT5665_DMIC_1_DP_GPIO4); + regmap_update_bits(rt5665->regmap, RT5665_GPIO_CTRL_1, + RT5665_GP4_PIN_MASK, RT5665_GP4_PIN_DMIC1_SDA); + break; + + default: + dev_dbg(&i2c->dev, "no DMIC1\n"); + break; + } + + switch (rt5665->pdata.dmic2_data_pin) { + case RT5665_DMIC2_DATA_IN2P: + regmap_update_bits(rt5665->regmap, RT5665_DMIC_CTRL_1, + RT5665_DMIC_2_DP_MASK, RT5665_DMIC_2_DP_IN2P); + break; + + case RT5665_DMIC2_DATA_GPIO5: + regmap_update_bits(rt5665->regmap, + RT5665_DMIC_CTRL_1, + RT5665_DMIC_2_DP_MASK, + RT5665_DMIC_2_DP_GPIO5); + regmap_update_bits(rt5665->regmap, RT5665_GPIO_CTRL_1, + RT5665_GP5_PIN_MASK, RT5665_GP5_PIN_DMIC2_SDA); + break; + + default: + dev_dbg(&i2c->dev, "no DMIC2\n"); + break; + + } + } + + regmap_write(rt5665->regmap, RT5665_HP_LOGIC_CTRL_2, 0x0002); + regmap_update_bits(rt5665->regmap, RT5665_EJD_CTRL_1, + 0xf000 | RT5665_VREF_POW_MASK, 0xe000 | RT5665_VREF_POW_REG); + /* Work around for pow_pump */ + regmap_update_bits(rt5665->regmap, RT5665_STO1_DAC_SIL_DET, + RT5665_DEB_STO_DAC_MASK, RT5665_DEB_80_MS); + + regmap_update_bits(rt5665->regmap, RT5665_HP_CHARGE_PUMP_1, + RT5665_PM_HP_MASK, RT5665_PM_HP_HV); + + /* Set GPIO4,8 as input for combo jack */ + if (rt5665->id == CODEC_5666) { + regmap_update_bits(rt5665->regmap, RT5665_GPIO_CTRL_2, + RT5665_GP4_PF_MASK, RT5665_GP4_PF_IN); + regmap_update_bits(rt5665->regmap, RT5665_GPIO_CTRL_3, + RT5665_GP8_PF_MASK, RT5665_GP8_PF_IN); + } + + /* Enhance performance*/ + regmap_update_bits(rt5665->regmap, RT5665_PWR_ANLG_1, + RT5665_HP_DRIVER_MASK | RT5665_LDO1_DVO_MASK, + RT5665_HP_DRIVER_5X | RT5665_LDO1_DVO_12); + + INIT_DELAYED_WORK(&rt5665->jack_detect_work, + rt5665_jack_detect_handler); + INIT_DELAYED_WORK(&rt5665->calibrate_work, + rt5665_calibrate_handler); + INIT_DELAYED_WORK(&rt5665->jd_check_work, + rt5665_jd_check_handler); + + mutex_init(&rt5665->calibrate_mutex); + + if (i2c->irq) { + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + rt5665_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5665", rt5665); + if (ret) + dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); + + } + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5665, + rt5665_dai, ARRAY_SIZE(rt5665_dai)); +} + +static void rt5665_i2c_shutdown(struct i2c_client *client) +{ + struct rt5665_priv *rt5665 = i2c_get_clientdata(client); + + regmap_write(rt5665->regmap, RT5665_RESET, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id rt5665_of_match[] = { + {.compatible = "realtek,rt5665"}, + {.compatible = "realtek,rt5666"}, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5665_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5665_acpi_match[] = { + {"10EC5665", 0,}, + {"10EC5666", 0,}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt5665_acpi_match); +#endif + +static struct i2c_driver rt5665_i2c_driver = { + .driver = { + .name = "rt5665", + .of_match_table = of_match_ptr(rt5665_of_match), + .acpi_match_table = ACPI_PTR(rt5665_acpi_match), + }, + .probe = rt5665_i2c_probe, + .shutdown = rt5665_i2c_shutdown, + .id_table = rt5665_i2c_id, +}; +module_i2c_driver(rt5665_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5665 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5665.h b/sound/soc/codecs/rt5665.h new file mode 100644 index 000000000..b0a98ca39 --- /dev/null +++ b/sound/soc/codecs/rt5665.h @@ -0,0 +1,2008 @@ +/* + * rt5665.h -- RT5665/RT5658 ALSA SoC audio driver + * + * Copyright 2016 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5665_H__ +#define __RT5665_H__ + +#include + +#define DEVICE_ID 0x6451 + +/* Info */ +#define RT5665_RESET 0x0000 +#define RT5665_VENDOR_ID 0x00fd +#define RT5665_VENDOR_ID_1 0x00fe +#define RT5665_DEVICE_ID 0x00ff +/* I/O - Output */ +#define RT5665_LOUT 0x0001 +#define RT5665_HP_CTRL_1 0x0002 +#define RT5665_HP_CTRL_2 0x0003 +#define RT5665_MONO_OUT 0x0004 +#define RT5665_HPL_GAIN 0x0005 +#define RT5665_HPR_GAIN 0x0006 +#define RT5665_MONO_GAIN 0x0007 + +/* I/O - Input */ +#define RT5665_CAL_BST_CTRL 0x000a +#define RT5665_CBJ_BST_CTRL 0x000b +#define RT5665_IN1_IN2 0x000c +#define RT5665_IN3_IN4 0x000d +#define RT5665_INL1_INR1_VOL 0x000f +/* I/O - Speaker */ +#define RT5665_EJD_CTRL_1 0x0010 +#define RT5665_EJD_CTRL_2 0x0011 +#define RT5665_EJD_CTRL_3 0x0012 +#define RT5665_EJD_CTRL_4 0x0013 +#define RT5665_EJD_CTRL_5 0x0014 +#define RT5665_EJD_CTRL_6 0x0015 +#define RT5665_EJD_CTRL_7 0x0016 +/* I/O - ADC/DAC/DMIC */ +#define RT5665_DAC2_CTRL 0x0017 +#define RT5665_DAC2_DIG_VOL 0x0018 +#define RT5665_DAC1_DIG_VOL 0x0019 +#define RT5665_DAC3_DIG_VOL 0x001a +#define RT5665_DAC3_CTRL 0x001b +#define RT5665_STO1_ADC_DIG_VOL 0x001c +#define RT5665_MONO_ADC_DIG_VOL 0x001d +#define RT5665_STO2_ADC_DIG_VOL 0x001e +#define RT5665_STO1_ADC_BOOST 0x001f +#define RT5665_MONO_ADC_BOOST 0x0020 +#define RT5665_STO2_ADC_BOOST 0x0021 +#define RT5665_HP_IMP_GAIN_1 0x0022 +#define RT5665_HP_IMP_GAIN_2 0x0023 +/* Mixer - D-D */ +#define RT5665_STO1_ADC_MIXER 0x0026 +#define RT5665_MONO_ADC_MIXER 0x0027 +#define RT5665_STO2_ADC_MIXER 0x0028 +#define RT5665_AD_DA_MIXER 0x0029 +#define RT5665_STO1_DAC_MIXER 0x002a +#define RT5665_MONO_DAC_MIXER 0x002b +#define RT5665_STO2_DAC_MIXER 0x002c +#define RT5665_A_DAC1_MUX 0x002d +#define RT5665_A_DAC2_MUX 0x002e +#define RT5665_DIG_INF2_DATA 0x002f +#define RT5665_DIG_INF3_DATA 0x0030 +/* Mixer - PDM */ +#define RT5665_PDM_OUT_CTRL 0x0031 +#define RT5665_PDM_DATA_CTRL_1 0x0032 +#define RT5665_PDM_DATA_CTRL_2 0x0033 +#define RT5665_PDM_DATA_CTRL_3 0x0034 +#define RT5665_PDM_DATA_CTRL_4 0x0035 +/* Mixer - ADC */ +#define RT5665_REC1_GAIN 0x003a +#define RT5665_REC1_L1_MIXER 0x003b +#define RT5665_REC1_L2_MIXER 0x003c +#define RT5665_REC1_R1_MIXER 0x003d +#define RT5665_REC1_R2_MIXER 0x003e +#define RT5665_REC2_GAIN 0x003f +#define RT5665_REC2_L1_MIXER 0x0040 +#define RT5665_REC2_L2_MIXER 0x0041 +#define RT5665_REC2_R1_MIXER 0x0042 +#define RT5665_REC2_R2_MIXER 0x0043 +#define RT5665_CAL_REC 0x0044 +/* Mixer - DAC */ +#define RT5665_ALC_BACK_GAIN 0x0049 +#define RT5665_MONOMIX_GAIN 0x004a +#define RT5665_MONOMIX_IN_GAIN 0x004b +#define RT5665_OUT_L_GAIN 0x004d +#define RT5665_OUT_L_MIXER 0x004e +#define RT5665_OUT_R_GAIN 0x004f +#define RT5665_OUT_R_MIXER 0x0050 +#define RT5665_LOUT_MIXER 0x0052 +/* Power */ +#define RT5665_PWR_DIG_1 0x0061 +#define RT5665_PWR_DIG_2 0x0062 +#define RT5665_PWR_ANLG_1 0x0063 +#define RT5665_PWR_ANLG_2 0x0064 +#define RT5665_PWR_ANLG_3 0x0065 +#define RT5665_PWR_MIXER 0x0066 +#define RT5665_PWR_VOL 0x0067 +/* Clock Detect */ +#define RT5665_CLK_DET 0x006b +/* Filter */ +#define RT5665_HPF_CTRL1 0x006d +/* DMIC */ +#define RT5665_DMIC_CTRL_1 0x006e +#define RT5665_DMIC_CTRL_2 0x006f +/* Format - ADC/DAC */ +#define RT5665_I2S1_SDP 0x0070 +#define RT5665_I2S2_SDP 0x0071 +#define RT5665_I2S3_SDP 0x0072 +#define RT5665_ADDA_CLK_1 0x0073 +#define RT5665_ADDA_CLK_2 0x0074 +#define RT5665_I2S1_F_DIV_CTRL_1 0x0075 +#define RT5665_I2S1_F_DIV_CTRL_2 0x0076 +/* Format - TDM Control */ +#define RT5665_TDM_CTRL_1 0x0078 +#define RT5665_TDM_CTRL_2 0x0079 +#define RT5665_TDM_CTRL_3 0x007a +#define RT5665_TDM_CTRL_4 0x007b +#define RT5665_TDM_CTRL_5 0x007c +#define RT5665_TDM_CTRL_6 0x007d +#define RT5665_TDM_CTRL_7 0x007e +#define RT5665_TDM_CTRL_8 0x007f +/* Function - Analog */ +#define RT5665_GLB_CLK 0x0080 +#define RT5665_PLL_CTRL_1 0x0081 +#define RT5665_PLL_CTRL_2 0x0082 +#define RT5665_ASRC_1 0x0083 +#define RT5665_ASRC_2 0x0084 +#define RT5665_ASRC_3 0x0085 +#define RT5665_ASRC_4 0x0086 +#define RT5665_ASRC_5 0x0087 +#define RT5665_ASRC_6 0x0088 +#define RT5665_ASRC_7 0x0089 +#define RT5665_ASRC_8 0x008a +#define RT5665_ASRC_9 0x008b +#define RT5665_ASRC_10 0x008c +#define RT5665_DEPOP_1 0x008e +#define RT5665_DEPOP_2 0x008f +#define RT5665_HP_CHARGE_PUMP_1 0x0091 +#define RT5665_HP_CHARGE_PUMP_2 0x0092 +#define RT5665_MICBIAS_1 0x0093 +#define RT5665_MICBIAS_2 0x0094 +#define RT5665_ASRC_12 0x0098 +#define RT5665_ASRC_13 0x0099 +#define RT5665_ASRC_14 0x009a +#define RT5665_RC_CLK_CTRL 0x009f +#define RT5665_I2S_M_CLK_CTRL_1 0x00a0 +#define RT5665_I2S2_F_DIV_CTRL_1 0x00a1 +#define RT5665_I2S2_F_DIV_CTRL_2 0x00a2 +#define RT5665_I2S3_F_DIV_CTRL_1 0x00a3 +#define RT5665_I2S3_F_DIV_CTRL_2 0x00a4 +/* Function - Digital */ +#define RT5665_EQ_CTRL_1 0x00ae +#define RT5665_EQ_CTRL_2 0x00af +#define RT5665_IRQ_CTRL_1 0x00b6 +#define RT5665_IRQ_CTRL_2 0x00b7 +#define RT5665_IRQ_CTRL_3 0x00b8 +#define RT5665_IRQ_CTRL_4 0x00b9 +#define RT5665_IRQ_CTRL_5 0x00ba +#define RT5665_IRQ_CTRL_6 0x00bb +#define RT5665_INT_ST_1 0x00be +#define RT5665_GPIO_CTRL_1 0x00c0 +#define RT5665_GPIO_CTRL_2 0x00c1 +#define RT5665_GPIO_CTRL_3 0x00c2 +#define RT5665_GPIO_CTRL_4 0x00c3 +#define RT5665_GPIO_STA 0x00c4 +#define RT5665_HP_AMP_DET_CTRL_1 0x00d0 +#define RT5665_HP_AMP_DET_CTRL_2 0x00d1 +#define RT5665_MID_HP_AMP_DET 0x00d3 +#define RT5665_LOW_HP_AMP_DET 0x00d4 +#define RT5665_SV_ZCD_1 0x00d9 +#define RT5665_SV_ZCD_2 0x00da +#define RT5665_IL_CMD_1 0x00db +#define RT5665_IL_CMD_2 0x00dc +#define RT5665_IL_CMD_3 0x00dd +#define RT5665_IL_CMD_4 0x00de +#define RT5665_4BTN_IL_CMD_1 0x00df +#define RT5665_4BTN_IL_CMD_2 0x00e0 +#define RT5665_4BTN_IL_CMD_3 0x00e1 +#define RT5665_PSV_IL_CMD_1 0x00e2 + +#define RT5665_ADC_STO1_HP_CTRL_1 0x00ea +#define RT5665_ADC_STO1_HP_CTRL_2 0x00eb +#define RT5665_ADC_MONO_HP_CTRL_1 0x00ec +#define RT5665_ADC_MONO_HP_CTRL_2 0x00ed +#define RT5665_ADC_STO2_HP_CTRL_1 0x00ee +#define RT5665_ADC_STO2_HP_CTRL_2 0x00ef +#define RT5665_AJD1_CTRL 0x00f0 +#define RT5665_JD1_THD 0x00f1 +#define RT5665_JD2_THD 0x00f2 +#define RT5665_JD_CTRL_1 0x00f6 +#define RT5665_JD_CTRL_2 0x00f7 +#define RT5665_JD_CTRL_3 0x00f8 +/* General Control */ +#define RT5665_DIG_MISC 0x00fa +#define RT5665_DUMMY_2 0x00fb +#define RT5665_DUMMY_3 0x00fc + +#define RT5665_DAC_ADC_DIG_VOL1 0x0100 +#define RT5665_DAC_ADC_DIG_VOL2 0x0101 +#define RT5665_BIAS_CUR_CTRL_1 0x010a +#define RT5665_BIAS_CUR_CTRL_2 0x010b +#define RT5665_BIAS_CUR_CTRL_3 0x010c +#define RT5665_BIAS_CUR_CTRL_4 0x010d +#define RT5665_BIAS_CUR_CTRL_5 0x010e +#define RT5665_BIAS_CUR_CTRL_6 0x010f +#define RT5665_BIAS_CUR_CTRL_7 0x0110 +#define RT5665_BIAS_CUR_CTRL_8 0x0111 +#define RT5665_BIAS_CUR_CTRL_9 0x0112 +#define RT5665_BIAS_CUR_CTRL_10 0x0113 +#define RT5665_VREF_REC_OP_FB_CAP_CTRL 0x0117 +#define RT5665_CHARGE_PUMP_1 0x0125 +#define RT5665_DIG_IN_CTRL_1 0x0132 +#define RT5665_DIG_IN_CTRL_2 0x0133 +#define RT5665_PAD_DRIVING_CTRL 0x0137 +#define RT5665_SOFT_RAMP_DEPOP 0x0138 +#define RT5665_PLL 0x0139 +#define RT5665_CHOP_DAC 0x013a +#define RT5665_CHOP_ADC 0x013b +#define RT5665_CALIB_ADC_CTRL 0x013c +#define RT5665_VOL_TEST 0x013f +#define RT5665_TEST_MODE_CTRL_1 0x0145 +#define RT5665_TEST_MODE_CTRL_2 0x0146 +#define RT5665_TEST_MODE_CTRL_3 0x0147 +#define RT5665_TEST_MODE_CTRL_4 0x0148 +#define RT5665_BASSBACK_CTRL 0x0150 +#define RT5665_STO_NG2_CTRL_1 0x0160 +#define RT5665_STO_NG2_CTRL_2 0x0161 +#define RT5665_STO_NG2_CTRL_3 0x0162 +#define RT5665_STO_NG2_CTRL_4 0x0163 +#define RT5665_STO_NG2_CTRL_5 0x0164 +#define RT5665_STO_NG2_CTRL_6 0x0165 +#define RT5665_STO_NG2_CTRL_7 0x0166 +#define RT5665_STO_NG2_CTRL_8 0x0167 +#define RT5665_MONO_NG2_CTRL_1 0x0170 +#define RT5665_MONO_NG2_CTRL_2 0x0171 +#define RT5665_MONO_NG2_CTRL_3 0x0172 +#define RT5665_MONO_NG2_CTRL_4 0x0173 +#define RT5665_MONO_NG2_CTRL_5 0x0174 +#define RT5665_MONO_NG2_CTRL_6 0x0175 +#define RT5665_STO1_DAC_SIL_DET 0x0190 +#define RT5665_MONOL_DAC_SIL_DET 0x0191 +#define RT5665_MONOR_DAC_SIL_DET 0x0192 +#define RT5665_STO2_DAC_SIL_DET 0x0193 +#define RT5665_SIL_PSV_CTRL1 0x0194 +#define RT5665_SIL_PSV_CTRL2 0x0195 +#define RT5665_SIL_PSV_CTRL3 0x0196 +#define RT5665_SIL_PSV_CTRL4 0x0197 +#define RT5665_SIL_PSV_CTRL5 0x0198 +#define RT5665_SIL_PSV_CTRL6 0x0199 +#define RT5665_MONO_AMP_CALIB_CTRL_1 0x01a0 +#define RT5665_MONO_AMP_CALIB_CTRL_2 0x01a1 +#define RT5665_MONO_AMP_CALIB_CTRL_3 0x01a2 +#define RT5665_MONO_AMP_CALIB_CTRL_4 0x01a3 +#define RT5665_MONO_AMP_CALIB_CTRL_5 0x01a4 +#define RT5665_MONO_AMP_CALIB_CTRL_6 0x01a5 +#define RT5665_MONO_AMP_CALIB_CTRL_7 0x01a6 +#define RT5665_MONO_AMP_CALIB_STA1 0x01a7 +#define RT5665_MONO_AMP_CALIB_STA2 0x01a8 +#define RT5665_MONO_AMP_CALIB_STA3 0x01a9 +#define RT5665_MONO_AMP_CALIB_STA4 0x01aa +#define RT5665_MONO_AMP_CALIB_STA6 0x01ab +#define RT5665_HP_IMP_SENS_CTRL_01 0x01b5 +#define RT5665_HP_IMP_SENS_CTRL_02 0x01b6 +#define RT5665_HP_IMP_SENS_CTRL_03 0x01b7 +#define RT5665_HP_IMP_SENS_CTRL_04 0x01b8 +#define RT5665_HP_IMP_SENS_CTRL_05 0x01b9 +#define RT5665_HP_IMP_SENS_CTRL_06 0x01ba +#define RT5665_HP_IMP_SENS_CTRL_07 0x01bb +#define RT5665_HP_IMP_SENS_CTRL_08 0x01bc +#define RT5665_HP_IMP_SENS_CTRL_09 0x01bd +#define RT5665_HP_IMP_SENS_CTRL_10 0x01be +#define RT5665_HP_IMP_SENS_CTRL_11 0x01bf +#define RT5665_HP_IMP_SENS_CTRL_12 0x01c0 +#define RT5665_HP_IMP_SENS_CTRL_13 0x01c1 +#define RT5665_HP_IMP_SENS_CTRL_14 0x01c2 +#define RT5665_HP_IMP_SENS_CTRL_15 0x01c3 +#define RT5665_HP_IMP_SENS_CTRL_16 0x01c4 +#define RT5665_HP_IMP_SENS_CTRL_17 0x01c5 +#define RT5665_HP_IMP_SENS_CTRL_18 0x01c6 +#define RT5665_HP_IMP_SENS_CTRL_19 0x01c7 +#define RT5665_HP_IMP_SENS_CTRL_20 0x01c8 +#define RT5665_HP_IMP_SENS_CTRL_21 0x01c9 +#define RT5665_HP_IMP_SENS_CTRL_22 0x01ca +#define RT5665_HP_IMP_SENS_CTRL_23 0x01cb +#define RT5665_HP_IMP_SENS_CTRL_24 0x01cc +#define RT5665_HP_IMP_SENS_CTRL_25 0x01cd +#define RT5665_HP_IMP_SENS_CTRL_26 0x01ce +#define RT5665_HP_IMP_SENS_CTRL_27 0x01cf +#define RT5665_HP_IMP_SENS_CTRL_28 0x01d0 +#define RT5665_HP_IMP_SENS_CTRL_29 0x01d1 +#define RT5665_HP_IMP_SENS_CTRL_30 0x01d2 +#define RT5665_HP_IMP_SENS_CTRL_31 0x01d3 +#define RT5665_HP_IMP_SENS_CTRL_32 0x01d4 +#define RT5665_HP_IMP_SENS_CTRL_33 0x01d5 +#define RT5665_HP_IMP_SENS_CTRL_34 0x01d6 +#define RT5665_HP_LOGIC_CTRL_1 0x01da +#define RT5665_HP_LOGIC_CTRL_2 0x01db +#define RT5665_HP_LOGIC_CTRL_3 0x01dc +#define RT5665_HP_CALIB_CTRL_1 0x01de +#define RT5665_HP_CALIB_CTRL_2 0x01df +#define RT5665_HP_CALIB_CTRL_3 0x01e0 +#define RT5665_HP_CALIB_CTRL_4 0x01e1 +#define RT5665_HP_CALIB_CTRL_5 0x01e2 +#define RT5665_HP_CALIB_CTRL_6 0x01e3 +#define RT5665_HP_CALIB_CTRL_7 0x01e4 +#define RT5665_HP_CALIB_CTRL_9 0x01e6 +#define RT5665_HP_CALIB_CTRL_10 0x01e7 +#define RT5665_HP_CALIB_CTRL_11 0x01e8 +#define RT5665_HP_CALIB_STA_1 0x01ea +#define RT5665_HP_CALIB_STA_2 0x01eb +#define RT5665_HP_CALIB_STA_3 0x01ec +#define RT5665_HP_CALIB_STA_4 0x01ed +#define RT5665_HP_CALIB_STA_5 0x01ee +#define RT5665_HP_CALIB_STA_6 0x01ef +#define RT5665_HP_CALIB_STA_7 0x01f0 +#define RT5665_HP_CALIB_STA_8 0x01f1 +#define RT5665_HP_CALIB_STA_9 0x01f2 +#define RT5665_HP_CALIB_STA_10 0x01f3 +#define RT5665_HP_CALIB_STA_11 0x01f4 +#define RT5665_PGM_TAB_CTRL1 0x0200 +#define RT5665_PGM_TAB_CTRL2 0x0201 +#define RT5665_PGM_TAB_CTRL3 0x0202 +#define RT5665_PGM_TAB_CTRL4 0x0203 +#define RT5665_PGM_TAB_CTRL5 0x0204 +#define RT5665_PGM_TAB_CTRL6 0x0205 +#define RT5665_PGM_TAB_CTRL7 0x0206 +#define RT5665_PGM_TAB_CTRL8 0x0207 +#define RT5665_PGM_TAB_CTRL9 0x0208 +#define RT5665_SAR_IL_CMD_1 0x0210 +#define RT5665_SAR_IL_CMD_2 0x0211 +#define RT5665_SAR_IL_CMD_3 0x0212 +#define RT5665_SAR_IL_CMD_4 0x0213 +#define RT5665_SAR_IL_CMD_5 0x0214 +#define RT5665_SAR_IL_CMD_6 0x0215 +#define RT5665_SAR_IL_CMD_7 0x0216 +#define RT5665_SAR_IL_CMD_8 0x0217 +#define RT5665_SAR_IL_CMD_9 0x0218 +#define RT5665_SAR_IL_CMD_10 0x0219 +#define RT5665_SAR_IL_CMD_11 0x021a +#define RT5665_SAR_IL_CMD_12 0x021b +#define RT5665_DRC1_CTRL_0 0x02ff +#define RT5665_DRC1_CTRL_1 0x0300 +#define RT5665_DRC1_CTRL_2 0x0301 +#define RT5665_DRC1_CTRL_3 0x0302 +#define RT5665_DRC1_CTRL_4 0x0303 +#define RT5665_DRC1_CTRL_5 0x0304 +#define RT5665_DRC1_CTRL_6 0x0305 +#define RT5665_DRC1_HARD_LMT_CTRL_1 0x0306 +#define RT5665_DRC1_HARD_LMT_CTRL_2 0x0307 +#define RT5665_DRC1_PRIV_1 0x0310 +#define RT5665_DRC1_PRIV_2 0x0311 +#define RT5665_DRC1_PRIV_3 0x0312 +#define RT5665_DRC1_PRIV_4 0x0313 +#define RT5665_DRC1_PRIV_5 0x0314 +#define RT5665_DRC1_PRIV_6 0x0315 +#define RT5665_DRC1_PRIV_7 0x0316 +#define RT5665_DRC1_PRIV_8 0x0317 +#define RT5665_ALC_PGA_CTRL_1 0x0330 +#define RT5665_ALC_PGA_CTRL_2 0x0331 +#define RT5665_ALC_PGA_CTRL_3 0x0332 +#define RT5665_ALC_PGA_CTRL_4 0x0333 +#define RT5665_ALC_PGA_CTRL_5 0x0334 +#define RT5665_ALC_PGA_CTRL_6 0x0335 +#define RT5665_ALC_PGA_CTRL_7 0x0336 +#define RT5665_ALC_PGA_CTRL_8 0x0337 +#define RT5665_ALC_PGA_STA_1 0x0338 +#define RT5665_ALC_PGA_STA_2 0x0339 +#define RT5665_ALC_PGA_STA_3 0x033a +#define RT5665_EQ_AUTO_RCV_CTRL1 0x03c0 +#define RT5665_EQ_AUTO_RCV_CTRL2 0x03c1 +#define RT5665_EQ_AUTO_RCV_CTRL3 0x03c2 +#define RT5665_EQ_AUTO_RCV_CTRL4 0x03c3 +#define RT5665_EQ_AUTO_RCV_CTRL5 0x03c4 +#define RT5665_EQ_AUTO_RCV_CTRL6 0x03c5 +#define RT5665_EQ_AUTO_RCV_CTRL7 0x03c6 +#define RT5665_EQ_AUTO_RCV_CTRL8 0x03c7 +#define RT5665_EQ_AUTO_RCV_CTRL9 0x03c8 +#define RT5665_EQ_AUTO_RCV_CTRL10 0x03c9 +#define RT5665_EQ_AUTO_RCV_CTRL11 0x03ca +#define RT5665_EQ_AUTO_RCV_CTRL12 0x03cb +#define RT5665_EQ_AUTO_RCV_CTRL13 0x03cc +#define RT5665_ADC_L_EQ_LPF1_A1 0x03d0 +#define RT5665_R_EQ_LPF1_A1 0x03d1 +#define RT5665_L_EQ_LPF1_H0 0x03d2 +#define RT5665_R_EQ_LPF1_H0 0x03d3 +#define RT5665_L_EQ_BPF1_A1 0x03d4 +#define RT5665_R_EQ_BPF1_A1 0x03d5 +#define RT5665_L_EQ_BPF1_A2 0x03d6 +#define RT5665_R_EQ_BPF1_A2 0x03d7 +#define RT5665_L_EQ_BPF1_H0 0x03d8 +#define RT5665_R_EQ_BPF1_H0 0x03d9 +#define RT5665_L_EQ_BPF2_A1 0x03da +#define RT5665_R_EQ_BPF2_A1 0x03db +#define RT5665_L_EQ_BPF2_A2 0x03dc +#define RT5665_R_EQ_BPF2_A2 0x03dd +#define RT5665_L_EQ_BPF2_H0 0x03de +#define RT5665_R_EQ_BPF2_H0 0x03df +#define RT5665_L_EQ_BPF3_A1 0x03e0 +#define RT5665_R_EQ_BPF3_A1 0x03e1 +#define RT5665_L_EQ_BPF3_A2 0x03e2 +#define RT5665_R_EQ_BPF3_A2 0x03e3 +#define RT5665_L_EQ_BPF3_H0 0x03e4 +#define RT5665_R_EQ_BPF3_H0 0x03e5 +#define RT5665_L_EQ_BPF4_A1 0x03e6 +#define RT5665_R_EQ_BPF4_A1 0x03e7 +#define RT5665_L_EQ_BPF4_A2 0x03e8 +#define RT5665_R_EQ_BPF4_A2 0x03e9 +#define RT5665_L_EQ_BPF4_H0 0x03ea +#define RT5665_R_EQ_BPF4_H0 0x03eb +#define RT5665_L_EQ_HPF1_A1 0x03ec +#define RT5665_R_EQ_HPF1_A1 0x03ed +#define RT5665_L_EQ_HPF1_H0 0x03ee +#define RT5665_R_EQ_HPF1_H0 0x03ef +#define RT5665_L_EQ_PRE_VOL 0x03f0 +#define RT5665_R_EQ_PRE_VOL 0x03f1 +#define RT5665_L_EQ_POST_VOL 0x03f2 +#define RT5665_R_EQ_POST_VOL 0x03f3 +#define RT5665_SCAN_MODE_CTRL 0x07f0 +#define RT5665_I2C_MODE 0x07fa + + + +/* global definition */ +#define RT5665_L_MUTE (0x1 << 15) +#define RT5665_L_MUTE_SFT 15 +#define RT5665_VOL_L_MUTE (0x1 << 14) +#define RT5665_VOL_L_SFT 14 +#define RT5665_R_MUTE (0x1 << 7) +#define RT5665_R_MUTE_SFT 7 +#define RT5665_VOL_R_MUTE (0x1 << 6) +#define RT5665_VOL_R_SFT 6 +#define RT5665_L_VOL_MASK (0x3f << 8) +#define RT5665_L_VOL_SFT 8 +#define RT5665_R_VOL_MASK (0x3f) +#define RT5665_R_VOL_SFT 0 + +/*Headphone Amp L/R Analog Gain and Digital NG2 Gain Control (0x0005 0x0006)*/ +#define RT5665_G_HP (0xf << 8) +#define RT5665_G_HP_SFT 8 +#define RT5665_G_STO_DA_DMIX (0xf) +#define RT5665_G_STO_DA_SFT 0 + +/* CBJ Control (0x000b) */ +#define RT5665_BST_CBJ_MASK (0xf << 8) +#define RT5665_BST_CBJ_SFT 8 + +/* IN1/IN2 Control (0x000c) */ +#define RT5665_IN1_DF_MASK (0x1 << 15) +#define RT5665_IN1_DF 15 +#define RT5665_BST1_MASK (0x7f << 8) +#define RT5665_BST1_SFT 8 +#define RT5665_IN2_DF_MASK (0x1 << 7) +#define RT5665_IN2_DF 7 +#define RT5665_BST2_MASK (0x7f) +#define RT5665_BST2_SFT 0 + +/* IN3/IN4 Control (0x000d) */ +#define RT5665_IN3_DF_MASK (0x1 << 15) +#define RT5665_IN3_DF 15 +#define RT5665_BST3_MASK (0x7f << 8) +#define RT5665_BST3_SFT 8 +#define RT5665_IN4_DF_MASK (0x1 << 7) +#define RT5665_IN4_DF 7 +#define RT5665_BST4_MASK (0x7f) +#define RT5665_BST4_SFT 0 + +/* INL and INR Volume Control (0x000f) */ +#define RT5665_INL_VOL_MASK (0x1f << 8) +#define RT5665_INL_VOL_SFT 8 +#define RT5665_INR_VOL_MASK (0x1f) +#define RT5665_INR_VOL_SFT 0 + +/* Embeeded Jack and Type Detection Control 1 (0x0010) */ +#define RT5665_EMB_JD_EN (0x1 << 15) +#define RT5665_EMB_JD_EN_SFT 15 +#define RT5665_JD_MODE (0x1 << 13) +#define RT5665_JD_MODE_SFT 13 +#define RT5665_POLA_EXT_JD_MASK (0x1 << 11) +#define RT5665_POLA_EXT_JD_LOW (0x1 << 11) +#define RT5665_POLA_EXT_JD_HIGH (0x0 << 11) +#define RT5665_EXT_JD_DIG (0x1 << 9) +#define RT5665_POL_FAST_OFF_MASK (0x1 << 8) +#define RT5665_POL_FAST_OFF_HIGH (0x1 << 8) +#define RT5665_POL_FAST_OFF_LOW (0x0 << 8) +#define RT5665_VREF_POW_MASK (0x1 << 6) +#define RT5665_VREF_POW_FSM (0x0 << 6) +#define RT5665_VREF_POW_REG (0x1 << 6) +#define RT5665_MB1_PATH_MASK (0x1 << 5) +#define RT5665_CTRL_MB1_REG (0x1 << 5) +#define RT5665_CTRL_MB1_FSM (0x0 << 5) +#define RT5665_MB2_PATH_MASK (0x1 << 4) +#define RT5665_CTRL_MB2_REG (0x1 << 4) +#define RT5665_CTRL_MB2_FSM (0x0 << 4) +#define RT5665_TRIG_JD_MASK (0x1 << 3) +#define RT5665_TRIG_JD_HIGH (0x1 << 3) +#define RT5665_TRIG_JD_LOW (0x0 << 3) + +/* Embeeded Jack and Type Detection Control 2 (0x0011) */ +#define RT5665_EXT_JD_SRC (0x7 << 4) +#define RT5665_EXT_JD_SRC_SFT 4 +#define RT5665_EXT_JD_SRC_GPIO_JD1 (0x0 << 4) +#define RT5665_EXT_JD_SRC_GPIO_JD2 (0x1 << 4) +#define RT5665_EXT_JD_SRC_JD1_1 (0x2 << 4) +#define RT5665_EXT_JD_SRC_JD1_2 (0x3 << 4) +#define RT5665_EXT_JD_SRC_JD2 (0x4 << 4) +#define RT5665_EXT_JD_SRC_JD3 (0x5 << 4) +#define RT5665_EXT_JD_SRC_MANUAL (0x6 << 4) + +/* Combo Jack and Type Detection Control 4 (0x0013) */ +#define RT5665_SEL_SHT_MID_TON_MASK (0x3 << 12) +#define RT5665_SEL_SHT_MID_TON_2 (0x0 << 12) +#define RT5665_SEL_SHT_MID_TON_3 (0x1 << 12) +#define RT5665_CBJ_JD_TEST_MASK (0x1 << 6) +#define RT5665_CBJ_JD_TEST_NORM (0x0 << 6) +#define RT5665_CBJ_JD_TEST_MODE (0x1 << 6) + +/* Slience Detection Control (0x0015) */ +#define RT5665_SIL_DET_MASK (0x1 << 15) +#define RT5665_SIL_DET_DIS (0x0 << 15) +#define RT5665_SIL_DET_EN (0x1 << 15) + +/* DAC2 Control (0x0017) */ +#define RT5665_M_DAC2_L_VOL (0x1 << 13) +#define RT5665_M_DAC2_L_VOL_SFT 13 +#define RT5665_M_DAC2_R_VOL (0x1 << 12) +#define RT5665_M_DAC2_R_VOL_SFT 12 +#define RT5665_DAC_L2_SEL_MASK (0x7 << 4) +#define RT5665_DAC_L2_SEL_SFT 4 +#define RT5665_DAC_R2_SEL_MASK (0x7 << 0) +#define RT5665_DAC_R2_SEL_SFT 0 + +/* Sidetone Control (0x0018) */ +#define RT5665_ST_SEL_MASK (0x7 << 9) +#define RT5665_ST_SEL_SFT 9 +#define RT5665_ST_EN (0x1 << 6) +#define RT5665_ST_EN_SFT 6 + +/* DAC1 Digital Volume (0x0019) */ +#define RT5665_DAC_L1_VOL_MASK (0xff << 8) +#define RT5665_DAC_L1_VOL_SFT 8 +#define RT5665_DAC_R1_VOL_MASK (0xff) +#define RT5665_DAC_R1_VOL_SFT 0 + +/* DAC2 Digital Volume (0x001a) */ +#define RT5665_DAC_L2_VOL_MASK (0xff << 8) +#define RT5665_DAC_L2_VOL_SFT 8 +#define RT5665_DAC_R2_VOL_MASK (0xff) +#define RT5665_DAC_R2_VOL_SFT 0 + +/* DAC3 Control (0x001b) */ +#define RT5665_M_DAC3_L_VOL (0x1 << 13) +#define RT5665_M_DAC3_L_VOL_SFT 13 +#define RT5665_M_DAC3_R_VOL (0x1 << 12) +#define RT5665_M_DAC3_R_VOL_SFT 12 +#define RT5665_DAC_L3_SEL_MASK (0x7 << 4) +#define RT5665_DAC_L3_SEL_SFT 4 +#define RT5665_DAC_R3_SEL_MASK (0x7 << 0) +#define RT5665_DAC_R3_SEL_SFT 0 + +/* ADC Digital Volume Control (0x001c) */ +#define RT5665_ADC_L_VOL_MASK (0x7f << 8) +#define RT5665_ADC_L_VOL_SFT 8 +#define RT5665_ADC_R_VOL_MASK (0x7f) +#define RT5665_ADC_R_VOL_SFT 0 + +/* Mono ADC Digital Volume Control (0x001d) */ +#define RT5665_MONO_ADC_L_VOL_MASK (0x7f << 8) +#define RT5665_MONO_ADC_L_VOL_SFT 8 +#define RT5665_MONO_ADC_R_VOL_MASK (0x7f) +#define RT5665_MONO_ADC_R_VOL_SFT 0 + +/* Stereo1 ADC Boost Gain Control (0x001f) */ +#define RT5665_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5665_STO1_ADC_L_BST_SFT 14 +#define RT5665_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5665_STO1_ADC_R_BST_SFT 12 + +/* Mono ADC Boost Gain Control (0x0020) */ +#define RT5665_MONO_ADC_L_BST_MASK (0x3 << 14) +#define RT5665_MONO_ADC_L_BST_SFT 14 +#define RT5665_MONO_ADC_R_BST_MASK (0x3 << 12) +#define RT5665_MONO_ADC_R_BST_SFT 12 + +/* Stereo1 ADC Boost Gain Control (0x001f) */ +#define RT5665_STO2_ADC_L_BST_MASK (0x3 << 14) +#define RT5665_STO2_ADC_L_BST_SFT 14 +#define RT5665_STO2_ADC_R_BST_MASK (0x3 << 12) +#define RT5665_STO2_ADC_R_BST_SFT 12 + +/* Stereo1 ADC Mixer Control (0x0026) */ +#define RT5665_M_STO1_ADC_L1 (0x1 << 15) +#define RT5665_M_STO1_ADC_L1_SFT 15 +#define RT5665_M_STO1_ADC_L2 (0x1 << 14) +#define RT5665_M_STO1_ADC_L2_SFT 14 +#define RT5665_STO1_ADC1L_SRC_MASK (0x1 << 13) +#define RT5665_STO1_ADC1L_SRC_SFT 13 +#define RT5665_STO1_ADC1_SRC_ADC (0x1 << 13) +#define RT5665_STO1_ADC1_SRC_DACMIX (0x0 << 13) +#define RT5665_STO1_ADC2L_SRC_MASK (0x1 << 12) +#define RT5665_STO1_ADC2L_SRC_SFT 12 +#define RT5665_STO1_ADCL_SRC_MASK (0x3 << 10) +#define RT5665_STO1_ADCL_SRC_SFT 10 +#define RT5665_STO1_DD_L_SRC_MASK (0x1 << 9) +#define RT5665_STO1_DD_L_SRC_SFT 9 +#define RT5665_STO1_DMIC_SRC_MASK (0x1 << 8) +#define RT5665_STO1_DMIC_SRC_SFT 8 +#define RT5665_STO1_DMIC_SRC_DMIC2 (0x1 << 8) +#define RT5665_STO1_DMIC_SRC_DMIC1 (0x0 << 8) +#define RT5665_M_STO1_ADC_R1 (0x1 << 7) +#define RT5665_M_STO1_ADC_R1_SFT 7 +#define RT5665_M_STO1_ADC_R2 (0x1 << 6) +#define RT5665_M_STO1_ADC_R2_SFT 6 +#define RT5665_STO1_ADC1R_SRC_MASK (0x1 << 5) +#define RT5665_STO1_ADC1R_SRC_SFT 5 +#define RT5665_STO1_ADC2R_SRC_MASK (0x1 << 4) +#define RT5665_STO1_ADC2R_SRC_SFT 4 +#define RT5665_STO1_ADCR_SRC_MASK (0x3 << 2) +#define RT5665_STO1_ADCR_SRC_SFT 2 +#define RT5665_STO1_DD_R_SRC_MASK (0x3) +#define RT5665_STO1_DD_R_SRC_SFT 0 + + +/* Mono1 ADC Mixer control (0x0027) */ +#define RT5665_M_MONO_ADC_L1 (0x1 << 15) +#define RT5665_M_MONO_ADC_L1_SFT 15 +#define RT5665_M_MONO_ADC_L2 (0x1 << 14) +#define RT5665_M_MONO_ADC_L2_SFT 14 +#define RT5665_MONO_ADC_L1_SRC_MASK (0x1 << 13) +#define RT5665_MONO_ADC_L1_SRC_SFT 13 +#define RT5665_MONO_ADC_L2_SRC_MASK (0x1 << 12) +#define RT5665_MONO_ADC_L2_SRC_SFT 12 +#define RT5665_MONO_ADC_L_SRC_MASK (0x3 << 10) +#define RT5665_MONO_ADC_L_SRC_SFT 10 +#define RT5665_MONO_DD_L_SRC_MASK (0x1 << 9) +#define RT5665_MONO_DD_L_SRC_SFT 9 +#define RT5665_MONO_DMIC_L_SRC_MASK (0x1 << 8) +#define RT5665_MONO_DMIC_L_SRC_SFT 8 +#define RT5665_M_MONO_ADC_R1 (0x1 << 7) +#define RT5665_M_MONO_ADC_R1_SFT 7 +#define RT5665_M_MONO_ADC_R2 (0x1 << 6) +#define RT5665_M_MONO_ADC_R2_SFT 6 +#define RT5665_MONO_ADC_R1_SRC_MASK (0x1 << 5) +#define RT5665_MONO_ADC_R1_SRC_SFT 5 +#define RT5665_MONO_ADC_R2_SRC_MASK (0x1 << 4) +#define RT5665_MONO_ADC_R2_SRC_SFT 4 +#define RT5665_MONO_ADC_R_SRC_MASK (0x3 << 2) +#define RT5665_MONO_ADC_R_SRC_SFT 2 +#define RT5665_MONO_DD_R_SRC_MASK (0x1 << 1) +#define RT5665_MONO_DD_R_SRC_SFT 1 +#define RT5665_MONO_DMIC_R_SRC_MASK 0x1 +#define RT5665_MONO_DMIC_R_SRC_SFT 0 + +/* Stereo2 ADC Mixer Control (0x0028) */ +#define RT5665_M_STO2_ADC_L1 (0x1 << 15) +#define RT5665_M_STO2_ADC_L1_UN (0x0 << 15) +#define RT5665_M_STO2_ADC_L1_SFT 15 +#define RT5665_M_STO2_ADC_L2 (0x1 << 14) +#define RT5665_M_STO2_ADC_L2_SFT 14 +#define RT5665_STO2_ADC1L_SRC_MASK (0x1 << 13) +#define RT5665_STO2_ADC1L_SRC_SFT 13 +#define RT5665_STO2_ADC1_SRC_ADC (0x1 << 13) +#define RT5665_STO2_ADC1_SRC_DACMIX (0x0 << 13) +#define RT5665_STO2_ADC2L_SRC_MASK (0x1 << 12) +#define RT5665_STO2_ADC2L_SRC_SFT 12 +#define RT5665_STO2_ADCL_SRC_MASK (0x3 << 10) +#define RT5665_STO2_ADCL_SRC_SFT 10 +#define RT5665_STO2_DD_L_SRC_MASK (0x1 << 9) +#define RT5665_STO2_DD_L_SRC_SFT 9 +#define RT5665_STO2_DMIC_SRC_MASK (0x1 << 8) +#define RT5665_STO2_DMIC_SRC_SFT 8 +#define RT5665_STO2_DMIC_SRC_DMIC2 (0x1 << 8) +#define RT5665_STO2_DMIC_SRC_DMIC1 (0x0 << 8) +#define RT5665_M_STO2_ADC_R1 (0x1 << 7) +#define RT5665_M_STO2_ADC_R1_UN (0x0 << 7) +#define RT5665_M_STO2_ADC_R1_SFT 7 +#define RT5665_M_STO2_ADC_R2 (0x1 << 6) +#define RT5665_M_STO2_ADC_R2_SFT 6 +#define RT5665_STO2_ADC1R_SRC_MASK (0x1 << 5) +#define RT5665_STO2_ADC1R_SRC_SFT 5 +#define RT5665_STO2_ADC2R_SRC_MASK (0x1 << 4) +#define RT5665_STO2_ADC2R_SRC_SFT 4 +#define RT5665_STO2_ADCR_SRC_MASK (0x3 << 2) +#define RT5665_STO2_ADCR_SRC_SFT 2 +#define RT5665_STO2_DD_R_SRC_MASK (0x1 << 1) +#define RT5665_STO2_DD_R_SRC_SFT 1 + +/* ADC Mixer to DAC Mixer Control (0x0029) */ +#define RT5665_M_ADCMIX_L (0x1 << 15) +#define RT5665_M_ADCMIX_L_SFT 15 +#define RT5665_M_DAC1_L (0x1 << 14) +#define RT5665_M_DAC1_L_SFT 14 +#define RT5665_DAC1_R_SEL_MASK (0x3 << 10) +#define RT5665_DAC1_R_SEL_SFT 10 +#define RT5665_DAC1_L_SEL_MASK (0x3 << 8) +#define RT5665_DAC1_L_SEL_SFT 8 +#define RT5665_M_ADCMIX_R (0x1 << 7) +#define RT5665_M_ADCMIX_R_SFT 7 +#define RT5665_M_DAC1_R (0x1 << 6) +#define RT5665_M_DAC1_R_SFT 6 + +/* Stereo1 DAC Mixer Control (0x002a) */ +#define RT5665_M_DAC_L1_STO_L (0x1 << 15) +#define RT5665_M_DAC_L1_STO_L_SFT 15 +#define RT5665_G_DAC_L1_STO_L_MASK (0x1 << 14) +#define RT5665_G_DAC_L1_STO_L_SFT 14 +#define RT5665_M_DAC_R1_STO_L (0x1 << 13) +#define RT5665_M_DAC_R1_STO_L_SFT 13 +#define RT5665_G_DAC_R1_STO_L_MASK (0x1 << 12) +#define RT5665_G_DAC_R1_STO_L_SFT 12 +#define RT5665_M_DAC_L2_STO_L (0x1 << 11) +#define RT5665_M_DAC_L2_STO_L_SFT 11 +#define RT5665_G_DAC_L2_STO_L_MASK (0x1 << 10) +#define RT5665_G_DAC_L2_STO_L_SFT 10 +#define RT5665_M_DAC_R2_STO_L (0x1 << 9) +#define RT5665_M_DAC_R2_STO_L_SFT 9 +#define RT5665_G_DAC_R2_STO_L_MASK (0x1 << 8) +#define RT5665_G_DAC_R2_STO_L_SFT 8 +#define RT5665_M_DAC_L1_STO_R (0x1 << 7) +#define RT5665_M_DAC_L1_STO_R_SFT 7 +#define RT5665_G_DAC_L1_STO_R_MASK (0x1 << 6) +#define RT5665_G_DAC_L1_STO_R_SFT 6 +#define RT5665_M_DAC_R1_STO_R (0x1 << 5) +#define RT5665_M_DAC_R1_STO_R_SFT 5 +#define RT5665_G_DAC_R1_STO_R_MASK (0x1 << 4) +#define RT5665_G_DAC_R1_STO_R_SFT 4 +#define RT5665_M_DAC_L2_STO_R (0x1 << 3) +#define RT5665_M_DAC_L2_STO_R_SFT 3 +#define RT5665_G_DAC_L2_STO_R_MASK (0x1 << 2) +#define RT5665_G_DAC_L2_STO_R_SFT 2 +#define RT5665_M_DAC_R2_STO_R (0x1 << 1) +#define RT5665_M_DAC_R2_STO_R_SFT 1 +#define RT5665_G_DAC_R2_STO_R_MASK (0x1) +#define RT5665_G_DAC_R2_STO_R_SFT 0 + +/* Mono DAC Mixer Control (0x002b) */ +#define RT5665_M_DAC_L1_MONO_L (0x1 << 15) +#define RT5665_M_DAC_L1_MONO_L_SFT 15 +#define RT5665_G_DAC_L1_MONO_L_MASK (0x1 << 14) +#define RT5665_G_DAC_L1_MONO_L_SFT 14 +#define RT5665_M_DAC_R1_MONO_L (0x1 << 13) +#define RT5665_M_DAC_R1_MONO_L_SFT 13 +#define RT5665_G_DAC_R1_MONO_L_MASK (0x1 << 12) +#define RT5665_G_DAC_R1_MONO_L_SFT 12 +#define RT5665_M_DAC_L2_MONO_L (0x1 << 11) +#define RT5665_M_DAC_L2_MONO_L_SFT 11 +#define RT5665_G_DAC_L2_MONO_L_MASK (0x1 << 10) +#define RT5665_G_DAC_L2_MONO_L_SFT 10 +#define RT5665_M_DAC_R2_MONO_L (0x1 << 9) +#define RT5665_M_DAC_R2_MONO_L_SFT 9 +#define RT5665_G_DAC_R2_MONO_L_MASK (0x1 << 8) +#define RT5665_G_DAC_R2_MONO_L_SFT 8 +#define RT5665_M_DAC_L1_MONO_R (0x1 << 7) +#define RT5665_M_DAC_L1_MONO_R_SFT 7 +#define RT5665_G_DAC_L1_MONO_R_MASK (0x1 << 6) +#define RT5665_G_DAC_L1_MONO_R_SFT 6 +#define RT5665_M_DAC_R1_MONO_R (0x1 << 5) +#define RT5665_M_DAC_R1_MONO_R_SFT 5 +#define RT5665_G_DAC_R1_MONO_R_MASK (0x1 << 4) +#define RT5665_G_DAC_R1_MONO_R_SFT 4 +#define RT5665_M_DAC_L2_MONO_R (0x1 << 3) +#define RT5665_M_DAC_L2_MONO_R_SFT 3 +#define RT5665_G_DAC_L2_MONO_R_MASK (0x1 << 2) +#define RT5665_G_DAC_L2_MONO_R_SFT 2 +#define RT5665_M_DAC_R2_MONO_R (0x1 << 1) +#define RT5665_M_DAC_R2_MONO_R_SFT 1 +#define RT5665_G_DAC_R2_MONO_R_MASK (0x1) +#define RT5665_G_DAC_R2_MONO_R_SFT 0 + +/* Stereo2 DAC Mixer Control (0x002c) */ +#define RT5665_M_DAC_L1_STO2_L (0x1 << 15) +#define RT5665_M_DAC_L1_STO2_L_SFT 15 +#define RT5665_G_DAC_L1_STO2_L_MASK (0x1 << 14) +#define RT5665_G_DAC_L1_STO2_L_SFT 14 +#define RT5665_M_DAC_L2_STO2_L (0x1 << 13) +#define RT5665_M_DAC_L2_STO2_L_SFT 13 +#define RT5665_G_DAC_L2_STO2_L_MASK (0x1 << 12) +#define RT5665_G_DAC_L2_STO2_L_SFT 12 +#define RT5665_M_DAC_L3_STO2_L (0x1 << 11) +#define RT5665_M_DAC_L3_STO2_L_SFT 11 +#define RT5665_G_DAC_L3_STO2_L_MASK (0x1 << 10) +#define RT5665_G_DAC_L3_STO2_L_SFT 10 +#define RT5665_M_ST_DAC_L1 (0x1 << 9) +#define RT5665_M_ST_DAC_L1_SFT 9 +#define RT5665_M_ST_DAC_R1 (0x1 << 8) +#define RT5665_M_ST_DAC_R1_SFT 8 +#define RT5665_M_DAC_R1_STO2_R (0x1 << 7) +#define RT5665_M_DAC_R1_STO2_R_SFT 7 +#define RT5665_G_DAC_R1_STO2_R_MASK (0x1 << 6) +#define RT5665_G_DAC_R1_STO2_R_SFT 6 +#define RT5665_M_DAC_R2_STO2_R (0x1 << 5) +#define RT5665_M_DAC_R2_STO2_R_SFT 5 +#define RT5665_G_DAC_R2_STO2_R_MASK (0x1 << 4) +#define RT5665_G_DAC_R2_STO2_R_SFT 4 +#define RT5665_M_DAC_R3_STO2_R (0x1 << 3) +#define RT5665_M_DAC_R3_STO2_R_SFT 3 +#define RT5665_G_DAC_R3_STO2_R_MASK (0x1 << 2) +#define RT5665_G_DAC_R3_STO2_R_SFT 2 + +/* Analog DAC1 Input Source Control (0x002d) */ +#define RT5665_DAC_MIX_L_MASK (0x3 << 12) +#define RT5665_DAC_MIX_L_SFT 12 +#define RT5665_DAC_MIX_R_MASK (0x3 << 8) +#define RT5665_DAC_MIX_R_SFT 8 +#define RT5665_DAC_L1_SRC_MASK (0x3 << 4) +#define RT5665_A_DACL1_SFT 4 +#define RT5665_DAC_R1_SRC_MASK (0x3) +#define RT5665_A_DACR1_SFT 0 + +/* Analog DAC Input Source Control (0x002e) */ +#define RT5665_A_DACL2_SEL (0x1 << 4) +#define RT5665_A_DACL2_SFT 4 +#define RT5665_A_DACR2_SEL (0x1 << 0) +#define RT5665_A_DACR2_SFT 0 + +/* Digital Interface Data Control (0x002f) */ +#define RT5665_IF2_1_ADC_IN_MASK (0x7 << 12) +#define RT5665_IF2_1_ADC_IN_SFT 12 +#define RT5665_IF2_1_DAC_SEL_MASK (0x3 << 10) +#define RT5665_IF2_1_DAC_SEL_SFT 10 +#define RT5665_IF2_1_ADC_SEL_MASK (0x3 << 8) +#define RT5665_IF2_1_ADC_SEL_SFT 8 +#define RT5665_IF2_2_ADC_IN_MASK (0x7 << 4) +#define RT5665_IF2_2_ADC_IN_SFT 4 +#define RT5665_IF2_2_DAC_SEL_MASK (0x3 << 2) +#define RT5665_IF2_2_DAC_SEL_SFT 2 +#define RT5665_IF2_2_ADC_SEL_MASK (0x3 << 0) +#define RT5665_IF2_2_ADC_SEL_SFT 0 + +/* Digital Interface Data Control (0x0030) */ +#define RT5665_IF3_ADC_IN_MASK (0x7 << 4) +#define RT5665_IF3_ADC_IN_SFT 4 +#define RT5665_IF3_DAC_SEL_MASK (0x3 << 2) +#define RT5665_IF3_DAC_SEL_SFT 2 +#define RT5665_IF3_ADC_SEL_MASK (0x3 << 0) +#define RT5665_IF3_ADC_SEL_SFT 0 + +/* PDM Output Control (0x0031) */ +#define RT5665_M_PDM1_L (0x1 << 14) +#define RT5665_M_PDM1_L_SFT 14 +#define RT5665_M_PDM1_R (0x1 << 12) +#define RT5665_M_PDM1_R_SFT 12 +#define RT5665_PDM1_L_MASK (0x3 << 10) +#define RT5665_PDM1_L_SFT 10 +#define RT5665_PDM1_R_MASK (0x3 << 8) +#define RT5665_PDM1_R_SFT 8 +#define RT5665_PDM1_BUSY (0x1 << 6) +#define RT5665_PDM_PATTERN (0x1 << 5) +#define RT5665_PDM_GAIN (0x1 << 4) +#define RT5665_LRCK_PDM_PI2C (0x1 << 3) +#define RT5665_PDM_DIV_MASK (0x3) + +/*S/PDIF Output Control (0x0036) */ +#define RT5665_SPDIF_SEL_MASK (0x3 << 0) +#define RT5665_SPDIF_SEL_SFT 0 + +/* REC Left Mixer Control 2 (0x003c) */ +#define RT5665_M_CBJ_RM1_L (0x1 << 7) +#define RT5665_M_CBJ_RM1_L_SFT 7 +#define RT5665_M_BST1_RM1_L (0x1 << 5) +#define RT5665_M_BST1_RM1_L_SFT 5 +#define RT5665_M_BST2_RM1_L (0x1 << 4) +#define RT5665_M_BST2_RM1_L_SFT 4 +#define RT5665_M_BST3_RM1_L (0x1 << 3) +#define RT5665_M_BST3_RM1_L_SFT 3 +#define RT5665_M_BST4_RM1_L (0x1 << 2) +#define RT5665_M_BST4_RM1_L_SFT 2 +#define RT5665_M_INL_RM1_L (0x1 << 1) +#define RT5665_M_INL_RM1_L_SFT 1 +#define RT5665_M_INR_RM1_L (0x1) +#define RT5665_M_INR_RM1_L_SFT 0 + +/* REC Right Mixer Control 2 (0x003e) */ +#define RT5665_M_AEC_REF_RM1_R (0x1 << 7) +#define RT5665_M_AEC_REF_RM1_R_SFT 7 +#define RT5665_M_BST1_RM1_R (0x1 << 5) +#define RT5665_M_BST1_RM1_R_SFT 5 +#define RT5665_M_BST2_RM1_R (0x1 << 4) +#define RT5665_M_BST2_RM1_R_SFT 4 +#define RT5665_M_BST3_RM1_R (0x1 << 3) +#define RT5665_M_BST3_RM1_R_SFT 3 +#define RT5665_M_BST4_RM1_R (0x1 << 2) +#define RT5665_M_BST4_RM1_R_SFT 2 +#define RT5665_M_INR_RM1_R (0x1 << 1) +#define RT5665_M_INR_RM1_R_SFT 1 +#define RT5665_M_MONOVOL_RM1_R (0x1) +#define RT5665_M_MONOVOL_RM1_R_SFT 0 + +/* REC Mixer 2 Left Control 2 (0x0041) */ +#define RT5665_M_CBJ_RM2_L (0x1 << 7) +#define RT5665_M_CBJ_RM2_L_SFT 7 +#define RT5665_M_BST1_RM2_L (0x1 << 5) +#define RT5665_M_BST1_RM2_L_SFT 5 +#define RT5665_M_BST2_RM2_L (0x1 << 4) +#define RT5665_M_BST2_RM2_L_SFT 4 +#define RT5665_M_BST3_RM2_L (0x1 << 3) +#define RT5665_M_BST3_RM2_L_SFT 3 +#define RT5665_M_BST4_RM2_L (0x1 << 2) +#define RT5665_M_BST4_RM2_L_SFT 2 +#define RT5665_M_INL_RM2_L (0x1 << 1) +#define RT5665_M_INL_RM2_L_SFT 1 +#define RT5665_M_INR_RM2_L (0x1) +#define RT5665_M_INR_RM2_L_SFT 0 + +/* REC Mixer 2 Right Control 2 (0x0043) */ +#define RT5665_M_MONOVOL_RM2_R (0x1 << 7) +#define RT5665_M_MONOVOL_RM2_R_SFT 7 +#define RT5665_M_BST1_RM2_R (0x1 << 5) +#define RT5665_M_BST1_RM2_R_SFT 5 +#define RT5665_M_BST2_RM2_R (0x1 << 4) +#define RT5665_M_BST2_RM2_R_SFT 4 +#define RT5665_M_BST3_RM2_R (0x1 << 3) +#define RT5665_M_BST3_RM2_R_SFT 3 +#define RT5665_M_BST4_RM2_R (0x1 << 2) +#define RT5665_M_BST4_RM2_R_SFT 2 +#define RT5665_M_INL_RM2_R (0x1 << 1) +#define RT5665_M_INL_RM2_R_SFT 1 +#define RT5665_M_INR_RM2_R (0x1) +#define RT5665_M_INR_RM2_R_SFT 0 + +/* SPK Left Mixer Control (0x0046) */ +#define RT5665_M_BST3_SM_L (0x1 << 4) +#define RT5665_M_BST3_SM_L_SFT 4 +#define RT5665_M_IN_R_SM_L (0x1 << 3) +#define RT5665_M_IN_R_SM_L_SFT 3 +#define RT5665_M_IN_L_SM_L (0x1 << 2) +#define RT5665_M_IN_L_SM_L_SFT 2 +#define RT5665_M_BST1_SM_L (0x1 << 1) +#define RT5665_M_BST1_SM_L_SFT 1 +#define RT5665_M_DAC_L2_SM_L (0x1) +#define RT5665_M_DAC_L2_SM_L_SFT 0 + +/* SPK Right Mixer Control (0x0047) */ +#define RT5665_M_BST3_SM_R (0x1 << 4) +#define RT5665_M_BST3_SM_R_SFT 4 +#define RT5665_M_IN_R_SM_R (0x1 << 3) +#define RT5665_M_IN_R_SM_R_SFT 3 +#define RT5665_M_IN_L_SM_R (0x1 << 2) +#define RT5665_M_IN_L_SM_R_SFT 2 +#define RT5665_M_BST4_SM_R (0x1 << 1) +#define RT5665_M_BST4_SM_R_SFT 1 +#define RT5665_M_DAC_R2_SM_R (0x1) +#define RT5665_M_DAC_R2_SM_R_SFT 0 + +/* SPO Amp Input and Gain Control (0x0048) */ +#define RT5665_M_DAC_L2_SPKOMIX (0x1 << 13) +#define RT5665_M_DAC_L2_SPKOMIX_SFT 13 +#define RT5665_M_SPKVOLL_SPKOMIX (0x1 << 12) +#define RT5665_M_SPKVOLL_SPKOMIX_SFT 12 +#define RT5665_M_DAC_R2_SPKOMIX (0x1 << 9) +#define RT5665_M_DAC_R2_SPKOMIX_SFT 9 +#define RT5665_M_SPKVOLR_SPKOMIX (0x1 << 8) +#define RT5665_M_SPKVOLR_SPKOMIX_SFT 8 + +/* MONOMIX Input and Gain Control (0x004b) */ +#define RT5665_G_MONOVOL_MA (0x1 << 10) +#define RT5665_G_MONOVOL_MA_SFT 10 +#define RT5665_M_MONOVOL_MA (0x1 << 9) +#define RT5665_M_MONOVOL_MA_SFT 9 +#define RT5665_M_DAC_L2_MA (0x1 << 8) +#define RT5665_M_DAC_L2_MA_SFT 8 +#define RT5665_M_BST3_MM (0x1 << 4) +#define RT5665_M_BST3_MM_SFT 4 +#define RT5665_M_BST2_MM (0x1 << 3) +#define RT5665_M_BST2_MM_SFT 3 +#define RT5665_M_BST1_MM (0x1 << 2) +#define RT5665_M_BST1_MM_SFT 2 +#define RT5665_M_RECMIC2L_MM (0x1 << 1) +#define RT5665_M_RECMIC2L_MM_SFT 1 +#define RT5665_M_DAC_L2_MM (0x1) +#define RT5665_M_DAC_L2_MM_SFT 0 + +/* Output Left Mixer Control 1 (0x004d) */ +#define RT5665_G_BST3_OM_L_MASK (0x7 << 12) +#define RT5665_G_BST3_OM_L_SFT 12 +#define RT5665_G_BST2_OM_L_MASK (0x7 << 9) +#define RT5665_G_BST2_OM_L_SFT 9 +#define RT5665_G_BST1_OM_L_MASK (0x7 << 6) +#define RT5665_G_BST1_OM_L_SFT 6 +#define RT5665_G_IN_L_OM_L_MASK (0x7 << 3) +#define RT5665_G_IN_L_OM_L_SFT 3 +#define RT5665_G_DAC_L2_OM_L_MASK (0x7 << 0) +#define RT5665_G_DAC_L2_OM_L_SFT 0 + +/* Output Left Mixer Input Control (0x004e) */ +#define RT5665_M_BST3_OM_L (0x1 << 4) +#define RT5665_M_BST3_OM_L_SFT 4 +#define RT5665_M_BST2_OM_L (0x1 << 3) +#define RT5665_M_BST2_OM_L_SFT 3 +#define RT5665_M_BST1_OM_L (0x1 << 2) +#define RT5665_M_BST1_OM_L_SFT 2 +#define RT5665_M_IN_L_OM_L (0x1 << 1) +#define RT5665_M_IN_L_OM_L_SFT 1 +#define RT5665_M_DAC_L2_OM_L (0x1) +#define RT5665_M_DAC_L2_OM_L_SFT 0 + +/* Output Right Mixer Input Control (0x0050) */ +#define RT5665_M_BST4_OM_R (0x1 << 4) +#define RT5665_M_BST4_OM_R_SFT 4 +#define RT5665_M_BST3_OM_R (0x1 << 3) +#define RT5665_M_BST3_OM_R_SFT 3 +#define RT5665_M_BST2_OM_R (0x1 << 2) +#define RT5665_M_BST2_OM_R_SFT 2 +#define RT5665_M_IN_R_OM_R (0x1 << 1) +#define RT5665_M_IN_R_OM_R_SFT 1 +#define RT5665_M_DAC_R2_OM_R (0x1) +#define RT5665_M_DAC_R2_OM_R_SFT 0 + +/* LOUT Mixer Control (0x0052) */ +#define RT5665_M_DAC_L2_LM (0x1 << 15) +#define RT5665_M_DAC_L2_LM_SFT 15 +#define RT5665_M_DAC_R2_LM (0x1 << 14) +#define RT5665_M_DAC_R2_LM_SFT 14 +#define RT5665_M_OV_L_LM (0x1 << 13) +#define RT5665_M_OV_L_LM_SFT 13 +#define RT5665_M_OV_R_LM (0x1 << 12) +#define RT5665_M_OV_R_LM_SFT 12 +#define RT5665_LOUT_BST_SFT 11 +#define RT5665_LOUT_DF (0x1 << 11) +#define RT5665_LOUT_DF_SFT 11 + +/* Power Management for Digital 1 (0x0061) */ +#define RT5665_PWR_I2S1_1 (0x1 << 15) +#define RT5665_PWR_I2S1_1_BIT 15 +#define RT5665_PWR_I2S1_2 (0x1 << 14) +#define RT5665_PWR_I2S1_2_BIT 14 +#define RT5665_PWR_I2S2_1 (0x1 << 13) +#define RT5665_PWR_I2S2_1_BIT 13 +#define RT5665_PWR_I2S2_2 (0x1 << 12) +#define RT5665_PWR_I2S2_2_BIT 12 +#define RT5665_PWR_DAC_L1 (0x1 << 11) +#define RT5665_PWR_DAC_L1_BIT 11 +#define RT5665_PWR_DAC_R1 (0x1 << 10) +#define RT5665_PWR_DAC_R1_BIT 10 +#define RT5665_PWR_I2S3 (0x1 << 9) +#define RT5665_PWR_I2S3_BIT 9 +#define RT5665_PWR_LDO (0x1 << 8) +#define RT5665_PWR_LDO_BIT 8 +#define RT5665_PWR_DAC_L2 (0x1 << 7) +#define RT5665_PWR_DAC_L2_BIT 7 +#define RT5665_PWR_DAC_R2 (0x1 << 6) +#define RT5665_PWR_DAC_R2_BIT 6 +#define RT5665_PWR_ADC_L1 (0x1 << 4) +#define RT5665_PWR_ADC_L1_BIT 4 +#define RT5665_PWR_ADC_R1 (0x1 << 3) +#define RT5665_PWR_ADC_R1_BIT 3 +#define RT5665_PWR_ADC_L2 (0x1 << 2) +#define RT5665_PWR_ADC_L2_BIT 2 +#define RT5665_PWR_ADC_R2 (0x1 << 1) +#define RT5665_PWR_ADC_R2_BIT 1 + +/* Power Management for Digital 2 (0x0062) */ +#define RT5665_PWR_ADC_S1F (0x1 << 15) +#define RT5665_PWR_ADC_S1F_BIT 15 +#define RT5665_PWR_ADC_S2F (0x1 << 14) +#define RT5665_PWR_ADC_S2F_BIT 14 +#define RT5665_PWR_ADC_MF_L (0x1 << 13) +#define RT5665_PWR_ADC_MF_L_BIT 13 +#define RT5665_PWR_ADC_MF_R (0x1 << 12) +#define RT5665_PWR_ADC_MF_R_BIT 12 +#define RT5665_PWR_DAC_S2F (0x1 << 11) +#define RT5665_PWR_DAC_S2F_BIT 11 +#define RT5665_PWR_DAC_S1F (0x1 << 10) +#define RT5665_PWR_DAC_S1F_BIT 10 +#define RT5665_PWR_DAC_MF_L (0x1 << 9) +#define RT5665_PWR_DAC_MF_L_BIT 9 +#define RT5665_PWR_DAC_MF_R (0x1 << 8) +#define RT5665_PWR_DAC_MF_R_BIT 8 +#define RT5665_PWR_PDM1 (0x1 << 7) +#define RT5665_PWR_PDM1_BIT 7 + +/* Power Management for Analog 1 (0x0063) */ +#define RT5665_PWR_VREF1 (0x1 << 15) +#define RT5665_PWR_VREF1_BIT 15 +#define RT5665_PWR_FV1 (0x1 << 14) +#define RT5665_PWR_FV1_BIT 14 +#define RT5665_PWR_VREF2 (0x1 << 13) +#define RT5665_PWR_VREF2_BIT 13 +#define RT5665_PWR_FV2 (0x1 << 12) +#define RT5665_PWR_FV2_BIT 12 +#define RT5665_PWR_VREF3 (0x1 << 11) +#define RT5665_PWR_VREF3_BIT 11 +#define RT5665_PWR_FV3 (0x1 << 10) +#define RT5665_PWR_FV3_BIT 10 +#define RT5665_PWR_MB (0x1 << 9) +#define RT5665_PWR_MB_BIT 9 +#define RT5665_PWR_LM (0x1 << 8) +#define RT5665_PWR_LM_BIT 8 +#define RT5665_PWR_BG (0x1 << 7) +#define RT5665_PWR_BG_BIT 7 +#define RT5665_PWR_MA (0x1 << 6) +#define RT5665_PWR_MA_BIT 6 +#define RT5665_PWR_HA_L (0x1 << 5) +#define RT5665_PWR_HA_L_BIT 5 +#define RT5665_PWR_HA_R (0x1 << 4) +#define RT5665_PWR_HA_R_BIT 4 +#define RT5665_HP_DRIVER_MASK (0x3 << 2) +#define RT5665_HP_DRIVER_1X (0x0 << 2) +#define RT5665_HP_DRIVER_3X (0x1 << 2) +#define RT5665_HP_DRIVER_5X (0x3 << 2) +#define RT5665_LDO1_DVO_MASK (0x3) +#define RT5665_LDO1_DVO_09 (0x0) +#define RT5665_LDO1_DVO_10 (0x1) +#define RT5665_LDO1_DVO_12 (0x2) +#define RT5665_LDO1_DVO_14 (0x3) + +/* Power Management for Analog 2 (0x0064) */ +#define RT5665_PWR_BST1 (0x1 << 15) +#define RT5665_PWR_BST1_BIT 15 +#define RT5665_PWR_BST2 (0x1 << 14) +#define RT5665_PWR_BST2_BIT 14 +#define RT5665_PWR_BST3 (0x1 << 13) +#define RT5665_PWR_BST3_BIT 13 +#define RT5665_PWR_BST4 (0x1 << 12) +#define RT5665_PWR_BST4_BIT 12 +#define RT5665_PWR_MB1 (0x1 << 11) +#define RT5665_PWR_MB1_PWR_DOWN (0x0 << 11) +#define RT5665_PWR_MB1_BIT 11 +#define RT5665_PWR_MB2 (0x1 << 10) +#define RT5665_PWR_MB2_PWR_DOWN (0x0 << 10) +#define RT5665_PWR_MB2_BIT 10 +#define RT5665_PWR_MB3 (0x1 << 9) +#define RT5665_PWR_MB3_BIT 9 +#define RT5665_PWR_BST1_P (0x1 << 7) +#define RT5665_PWR_BST1_P_BIT 7 +#define RT5665_PWR_BST2_P (0x1 << 6) +#define RT5665_PWR_BST2_P_BIT 6 +#define RT5665_PWR_BST3_P (0x1 << 5) +#define RT5665_PWR_BST3_P_BIT 5 +#define RT5665_PWR_BST4_P (0x1 << 4) +#define RT5665_PWR_BST4_P_BIT 4 +#define RT5665_PWR_JD1 (0x1 << 3) +#define RT5665_PWR_JD1_BIT 3 +#define RT5665_PWR_JD2 (0x1 << 2) +#define RT5665_PWR_JD2_BIT 2 +#define RT5665_PWR_RM1_L (0x1 << 1) +#define RT5665_PWR_RM1_L_BIT 1 +#define RT5665_PWR_RM1_R (0x1) +#define RT5665_PWR_RM1_R_BIT 0 + +/* Power Management for Analog 3 (0x0065) */ +#define RT5665_PWR_CBJ (0x1 << 9) +#define RT5665_PWR_CBJ_BIT 9 +#define RT5665_PWR_BST_L (0x1 << 8) +#define RT5665_PWR_BST_L_BIT 8 +#define RT5665_PWR_BST_R (0x1 << 7) +#define RT5665_PWR_BST_R_BIT 7 +#define RT5665_PWR_PLL (0x1 << 6) +#define RT5665_PWR_PLL_BIT 6 +#define RT5665_PWR_LDO2 (0x1 << 2) +#define RT5665_PWR_LDO2_BIT 2 +#define RT5665_PWR_SVD (0x1 << 1) +#define RT5665_PWR_SVD_BIT 1 + +/* Power Management for Mixer (0x0066) */ +#define RT5665_PWR_RM2_L (0x1 << 15) +#define RT5665_PWR_RM2_L_BIT 15 +#define RT5665_PWR_RM2_R (0x1 << 14) +#define RT5665_PWR_RM2_R_BIT 14 +#define RT5665_PWR_OM_L (0x1 << 13) +#define RT5665_PWR_OM_L_BIT 13 +#define RT5665_PWR_OM_R (0x1 << 12) +#define RT5665_PWR_OM_R_BIT 12 +#define RT5665_PWR_MM (0x1 << 11) +#define RT5665_PWR_MM_BIT 11 +#define RT5665_PWR_AEC_REF (0x1 << 6) +#define RT5665_PWR_AEC_REF_BIT 6 +#define RT5665_PWR_STO1_DAC_L (0x1 << 5) +#define RT5665_PWR_STO1_DAC_L_BIT 5 +#define RT5665_PWR_STO1_DAC_R (0x1 << 4) +#define RT5665_PWR_STO1_DAC_R_BIT 4 +#define RT5665_PWR_MONO_DAC_L (0x1 << 3) +#define RT5665_PWR_MONO_DAC_L_BIT 3 +#define RT5665_PWR_MONO_DAC_R (0x1 << 2) +#define RT5665_PWR_MONO_DAC_R_BIT 2 +#define RT5665_PWR_STO2_DAC_L (0x1 << 1) +#define RT5665_PWR_STO2_DAC_L_BIT 1 +#define RT5665_PWR_STO2_DAC_R (0x1) +#define RT5665_PWR_STO2_DAC_R_BIT 0 + +/* Power Management for Volume (0x0067) */ +#define RT5665_PWR_OV_L (0x1 << 13) +#define RT5665_PWR_OV_L_BIT 13 +#define RT5665_PWR_OV_R (0x1 << 12) +#define RT5665_PWR_OV_R_BIT 12 +#define RT5665_PWR_IN_L (0x1 << 9) +#define RT5665_PWR_IN_L_BIT 9 +#define RT5665_PWR_IN_R (0x1 << 8) +#define RT5665_PWR_IN_R_BIT 8 +#define RT5665_PWR_MV (0x1 << 7) +#define RT5665_PWR_MV_BIT 7 +#define RT5665_PWR_MIC_DET (0x1 << 5) +#define RT5665_PWR_MIC_DET_BIT 5 + +/* (0x006b) */ +#define RT5665_SYS_CLK_DET 15 +#define RT5665_HP_CLK_DET 14 +#define RT5665_MONO_CLK_DET 13 +#define RT5665_LOUT_CLK_DET 12 +#define RT5665_POW_CLK_DET 0 + +/* Digital Microphone Control 1 (0x006e) */ +#define RT5665_DMIC_1_EN_MASK (0x1 << 15) +#define RT5665_DMIC_1_EN_SFT 15 +#define RT5665_DMIC_1_DIS (0x0 << 15) +#define RT5665_DMIC_1_EN (0x1 << 15) +#define RT5665_DMIC_2_EN_MASK (0x1 << 14) +#define RT5665_DMIC_2_EN_SFT 14 +#define RT5665_DMIC_2_DIS (0x0 << 14) +#define RT5665_DMIC_2_EN (0x1 << 14) +#define RT5665_DMIC_2_DP_MASK (0x1 << 9) +#define RT5665_DMIC_2_DP_SFT 9 +#define RT5665_DMIC_2_DP_GPIO5 (0x0 << 9) +#define RT5665_DMIC_2_DP_IN2P (0x1 << 9) +#define RT5665_DMIC_CLK_MASK (0x7 << 5) +#define RT5665_DMIC_CLK_SFT 5 +#define RT5665_DMIC_1_DP_MASK (0x1 << 1) +#define RT5665_DMIC_1_DP_SFT 1 +#define RT5665_DMIC_1_DP_GPIO4 (0x0 << 1) +#define RT5665_DMIC_1_DP_IN2N (0x1 << 1) + + +/* Digital Microphone Control 1 (0x006f) */ +#define RT5665_DMIC_2L_LH_MASK (0x1 << 3) +#define RT5665_DMIC_2L_LH_SFT 3 +#define RT5665_DMIC_2L_LH_RISING (0x0 << 3) +#define RT5665_DMIC_2L_LH_FALLING (0x1 << 3) +#define RT5665_DMIC_2R_LH_MASK (0x1 << 2) +#define RT5665_DMIC_2R_LH_SFT 2 +#define RT5665_DMIC_2R_LH_RISING (0x0 << 2) +#define RT5665_DMIC_2R_LH_FALLING (0x1 << 2) +#define RT5665_DMIC_1L_LH_MASK (0x1 << 1) +#define RT5665_DMIC_1L_LH_SFT 1 +#define RT5665_DMIC_1L_LH_RISING (0x0 << 1) +#define RT5665_DMIC_1L_LH_FALLING (0x1 << 1) +#define RT5665_DMIC_1R_LH_MASK (0x1 << 0) +#define RT5665_DMIC_1R_LH_SFT 0 +#define RT5665_DMIC_1R_LH_RISING (0x0) +#define RT5665_DMIC_1R_LH_FALLING (0x1) + +/* I2S1/2/3 Audio Serial Data Port Control (0x0070 0x0071 0x0072) */ +#define RT5665_I2S_MS_MASK (0x1 << 15) +#define RT5665_I2S_MS_SFT 15 +#define RT5665_I2S_MS_M (0x0 << 15) +#define RT5665_I2S_MS_S (0x1 << 15) +#define RT5665_I2S_PIN_CFG_MASK (0x1 << 14) +#define RT5665_I2S_PIN_CFG_SFT 14 +#define RT5665_I2S_CLK_SEL_MASK (0x1 << 11) +#define RT5665_I2S_CLK_SEL_SFT 11 +#define RT5665_I2S_BP_MASK (0x1 << 8) +#define RT5665_I2S_BP_SFT 8 +#define RT5665_I2S_BP_NOR (0x0 << 8) +#define RT5665_I2S_BP_INV (0x1 << 8) +#define RT5665_I2S_DL_MASK (0x3 << 4) +#define RT5665_I2S_DL_SFT 4 +#define RT5665_I2S_DL_16 (0x0 << 4) +#define RT5665_I2S_DL_20 (0x1 << 4) +#define RT5665_I2S_DL_24 (0x2 << 4) +#define RT5665_I2S_DL_8 (0x3 << 4) +#define RT5665_I2S_DF_MASK (0x7) +#define RT5665_I2S_DF_SFT 0 +#define RT5665_I2S_DF_I2S (0x0) +#define RT5665_I2S_DF_LEFT (0x1) +#define RT5665_I2S_DF_PCM_A (0x2) +#define RT5665_I2S_DF_PCM_B (0x3) +#define RT5665_I2S_DF_PCM_A_N (0x6) +#define RT5665_I2S_DF_PCM_B_N (0x7) + +/* ADC/DAC Clock Control 1 (0x0073) */ +#define RT5665_I2S_PD1_MASK (0x7 << 12) +#define RT5665_I2S_PD1_SFT 12 +#define RT5665_I2S_PD1_1 (0x0 << 12) +#define RT5665_I2S_PD1_2 (0x1 << 12) +#define RT5665_I2S_PD1_3 (0x2 << 12) +#define RT5665_I2S_PD1_4 (0x3 << 12) +#define RT5665_I2S_PD1_6 (0x4 << 12) +#define RT5665_I2S_PD1_8 (0x5 << 12) +#define RT5665_I2S_PD1_12 (0x6 << 12) +#define RT5665_I2S_PD1_16 (0x7 << 12) +#define RT5665_I2S_M_PD2_MASK (0x7 << 8) +#define RT5665_I2S_M_PD2_SFT 8 +#define RT5665_I2S_M_PD2_1 (0x0 << 8) +#define RT5665_I2S_M_PD2_2 (0x1 << 8) +#define RT5665_I2S_M_PD2_3 (0x2 << 8) +#define RT5665_I2S_M_PD2_4 (0x3 << 8) +#define RT5665_I2S_M_PD2_6 (0x4 << 8) +#define RT5665_I2S_M_PD2_8 (0x5 << 8) +#define RT5665_I2S_M_PD2_12 (0x6 << 8) +#define RT5665_I2S_M_PD2_16 (0x7 << 8) +#define RT5665_I2S_CLK_SRC_MASK (0x3 << 4) +#define RT5665_I2S_CLK_SRC_SFT 4 +#define RT5665_I2S_CLK_SRC_MCLK (0x0 << 4) +#define RT5665_I2S_CLK_SRC_PLL1 (0x1 << 4) +#define RT5665_I2S_CLK_SRC_RCCLK (0x2 << 4) +#define RT5665_DAC_OSR_MASK (0x3 << 2) +#define RT5665_DAC_OSR_SFT 2 +#define RT5665_DAC_OSR_128 (0x0 << 2) +#define RT5665_DAC_OSR_64 (0x1 << 2) +#define RT5665_DAC_OSR_32 (0x2 << 2) +#define RT5665_ADC_OSR_MASK (0x3) +#define RT5665_ADC_OSR_SFT 0 +#define RT5665_ADC_OSR_128 (0x0) +#define RT5665_ADC_OSR_64 (0x1) +#define RT5665_ADC_OSR_32 (0x2) + +/* ADC/DAC Clock Control 2 (0x0074) */ +#define RT5665_I2S_BCLK_MS2_MASK (0x1 << 15) +#define RT5665_I2S_BCLK_MS2_SFT 15 +#define RT5665_I2S_BCLK_MS2_32 (0x0 << 15) +#define RT5665_I2S_BCLK_MS2_64 (0x1 << 15) +#define RT5665_I2S_PD2_MASK (0x7 << 12) +#define RT5665_I2S_PD2_SFT 12 +#define RT5665_I2S_PD2_1 (0x0 << 12) +#define RT5665_I2S_PD2_2 (0x1 << 12) +#define RT5665_I2S_PD2_3 (0x2 << 12) +#define RT5665_I2S_PD2_4 (0x3 << 12) +#define RT5665_I2S_PD2_6 (0x4 << 12) +#define RT5665_I2S_PD2_8 (0x5 << 12) +#define RT5665_I2S_PD2_12 (0x6 << 12) +#define RT5665_I2S_PD2_16 (0x7 << 12) +#define RT5665_I2S_BCLK_MS3_MASK (0x1 << 11) +#define RT5665_I2S_BCLK_MS3_SFT 11 +#define RT5665_I2S_BCLK_MS3_32 (0x0 << 11) +#define RT5665_I2S_BCLK_MS3_64 (0x1 << 11) +#define RT5665_I2S_PD3_MASK (0x7 << 8) +#define RT5665_I2S_PD3_SFT 8 +#define RT5665_I2S_PD3_1 (0x0 << 8) +#define RT5665_I2S_PD3_2 (0x1 << 8) +#define RT5665_I2S_PD3_3 (0x2 << 8) +#define RT5665_I2S_PD3_4 (0x3 << 8) +#define RT5665_I2S_PD3_6 (0x4 << 8) +#define RT5665_I2S_PD3_8 (0x5 << 8) +#define RT5665_I2S_PD3_12 (0x6 << 8) +#define RT5665_I2S_PD3_16 (0x7 << 8) +#define RT5665_I2S_PD4_MASK (0x7 << 4) +#define RT5665_I2S_PD4_SFT 4 +#define RT5665_I2S_PD4_1 (0x0 << 4) +#define RT5665_I2S_PD4_2 (0x1 << 4) +#define RT5665_I2S_PD4_3 (0x2 << 4) +#define RT5665_I2S_PD4_4 (0x3 << 4) +#define RT5665_I2S_PD4_6 (0x4 << 4) +#define RT5665_I2S_PD4_8 (0x5 << 4) +#define RT5665_I2S_PD4_12 (0x6 << 4) +#define RT5665_I2S_PD4_16 (0x7 << 4) + +/* TDM control 1 (0x0078) */ +#define RT5665_I2S1_MODE_MASK (0x1 << 15) +#define RT5665_I2S1_MODE_I2S (0x0 << 15) +#define RT5665_I2S1_MODE_TDM (0x1 << 15) +#define RT5665_TDM_IN_CH_MASK (0x3 << 10) +#define RT5665_TDM_IN_CH_2 (0x0 << 10) +#define RT5665_TDM_IN_CH_4 (0x1 << 10) +#define RT5665_TDM_IN_CH_6 (0x2 << 10) +#define RT5665_TDM_IN_CH_8 (0x3 << 10) +#define RT5665_TDM_OUT_CH_MASK (0x3 << 8) +#define RT5665_TDM_OUT_CH_2 (0x0 << 8) +#define RT5665_TDM_OUT_CH_4 (0x1 << 8) +#define RT5665_TDM_OUT_CH_6 (0x2 << 8) +#define RT5665_TDM_OUT_CH_8 (0x3 << 8) +#define RT5665_TDM_IN_LEN_MASK (0x3 << 6) +#define RT5665_TDM_IN_LEN_16 (0x0 << 6) +#define RT5665_TDM_IN_LEN_20 (0x1 << 6) +#define RT5665_TDM_IN_LEN_24 (0x2 << 6) +#define RT5665_TDM_IN_LEN_32 (0x3 << 6) +#define RT5665_TDM_OUT_LEN_MASK (0x3 << 4) +#define RT5665_TDM_OUT_LEN_16 (0x0 << 4) +#define RT5665_TDM_OUT_LEN_20 (0x1 << 4) +#define RT5665_TDM_OUT_LEN_24 (0x2 << 4) +#define RT5665_TDM_OUT_LEN_32 (0x3 << 4) + + +/* TDM control 2 (0x0079) */ +#define RT5665_I2S1_1_DS_ADC_SLOT01_SFT 14 +#define RT5665_I2S1_1_DS_ADC_SLOT23_SFT 12 +#define RT5665_I2S1_1_DS_ADC_SLOT45_SFT 10 +#define RT5665_I2S1_1_DS_ADC_SLOT67_SFT 8 +#define RT5665_I2S1_2_DS_ADC_SLOT01_SFT 6 +#define RT5665_I2S1_2_DS_ADC_SLOT23_SFT 4 +#define RT5665_I2S1_2_DS_ADC_SLOT45_SFT 2 +#define RT5665_I2S1_2_DS_ADC_SLOT67_SFT 0 + +/* TDM control 3/4 (0x007a) (0x007b) */ +#define RT5665_IF1_ADC1_SEL_SFT 10 +#define RT5665_IF1_ADC2_SEL_SFT 9 +#define RT5665_IF1_ADC3_SEL_SFT 8 +#define RT5665_IF1_ADC4_SEL_SFT 7 +#define RT5665_TDM_ADC_SEL_SFT 0 +#define RT5665_TDM_ADC_CTRL_MASK (0x1f << 0) +#define RT5665_TDM_ADC_DATA_06 (0x6 << 0) + +/* Global Clock Control (0x0080) */ +#define RT5665_SCLK_SRC_MASK (0x3 << 14) +#define RT5665_SCLK_SRC_SFT 14 +#define RT5665_SCLK_SRC_MCLK (0x0 << 14) +#define RT5665_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5665_SCLK_SRC_RCCLK (0x2 << 14) +#define RT5665_PLL1_SRC_MASK (0x7 << 8) +#define RT5665_PLL1_SRC_SFT 8 +#define RT5665_PLL1_SRC_MCLK (0x0 << 8) +#define RT5665_PLL1_SRC_BCLK1 (0x1 << 8) +#define RT5665_PLL1_SRC_BCLK2 (0x2 << 8) +#define RT5665_PLL1_SRC_BCLK3 (0x3 << 8) +#define RT5665_PLL1_PD_MASK (0x7 << 4) +#define RT5665_PLL1_PD_SFT 4 + + +#define RT5665_PLL_INP_MAX 40000000 +#define RT5665_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x0081) */ +#define RT5665_PLL_N_MAX 0x001ff +#define RT5665_PLL_N_MASK (RT5665_PLL_N_MAX << 7) +#define RT5665_PLL_N_SFT 7 +#define RT5665_PLL_K_MAX 0x001f +#define RT5665_PLL_K_MASK (RT5665_PLL_K_MAX) +#define RT5665_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x0082) */ +#define RT5665_PLL_M_MAX 0x00f +#define RT5665_PLL_M_MASK (RT5665_PLL_M_MAX << 12) +#define RT5665_PLL_M_SFT 12 +#define RT5665_PLL_M_BP (0x1 << 11) +#define RT5665_PLL_M_BP_SFT 11 +#define RT5665_PLL_K_BP (0x1 << 10) +#define RT5665_PLL_K_BP_SFT 10 + +/* PLL tracking mode 1 (0x0083) */ +#define RT5665_I2S3_ASRC_MASK (0x1 << 15) +#define RT5665_I2S3_ASRC_SFT 15 +#define RT5665_I2S2_ASRC_MASK (0x1 << 14) +#define RT5665_I2S2_ASRC_SFT 14 +#define RT5665_I2S1_ASRC_MASK (0x1 << 13) +#define RT5665_I2S1_ASRC_SFT 13 +#define RT5665_DAC_STO1_ASRC_MASK (0x1 << 12) +#define RT5665_DAC_STO1_ASRC_SFT 12 +#define RT5665_DAC_STO2_ASRC_MASK (0x1 << 11) +#define RT5665_DAC_STO2_ASRC_SFT 11 +#define RT5665_DAC_MONO_L_ASRC_MASK (0x1 << 10) +#define RT5665_DAC_MONO_L_ASRC_SFT 10 +#define RT5665_DAC_MONO_R_ASRC_MASK (0x1 << 9) +#define RT5665_DAC_MONO_R_ASRC_SFT 9 +#define RT5665_DMIC_STO1_ASRC_MASK (0x1 << 8) +#define RT5665_DMIC_STO1_ASRC_SFT 8 +#define RT5665_DMIC_STO2_ASRC_MASK (0x1 << 7) +#define RT5665_DMIC_STO2_ASRC_SFT 7 +#define RT5665_DMIC_MONO_L_ASRC_MASK (0x1 << 6) +#define RT5665_DMIC_MONO_L_ASRC_SFT 6 +#define RT5665_DMIC_MONO_R_ASRC_MASK (0x1 << 5) +#define RT5665_DMIC_MONO_R_ASRC_SFT 5 +#define RT5665_ADC_STO1_ASRC_MASK (0x1 << 4) +#define RT5665_ADC_STO1_ASRC_SFT 4 +#define RT5665_ADC_STO2_ASRC_MASK (0x1 << 3) +#define RT5665_ADC_STO2_ASRC_SFT 3 +#define RT5665_ADC_MONO_L_ASRC_MASK (0x1 << 2) +#define RT5665_ADC_MONO_L_ASRC_SFT 2 +#define RT5665_ADC_MONO_R_ASRC_MASK (0x1 << 1) +#define RT5665_ADC_MONO_R_ASRC_SFT 1 + +/* PLL tracking mode 2 (0x0084)*/ +#define RT5665_DA_STO1_CLK_SEL_MASK (0x7 << 12) +#define RT5665_DA_STO1_CLK_SEL_SFT 12 +#define RT5665_DA_STO2_CLK_SEL_MASK (0x7 << 8) +#define RT5665_DA_STO2_CLK_SEL_SFT 8 +#define RT5665_DA_MONOL_CLK_SEL_MASK (0x7 << 4) +#define RT5665_DA_MONOL_CLK_SEL_SFT 4 +#define RT5665_DA_MONOR_CLK_SEL_MASK (0x7) +#define RT5665_DA_MONOR_CLK_SEL_SFT 0 + +/* PLL tracking mode 3 (0x0085)*/ +#define RT5665_AD_STO1_CLK_SEL_MASK (0x7 << 12) +#define RT5665_AD_STO1_CLK_SEL_SFT 12 +#define RT5665_AD_STO2_CLK_SEL_MASK (0x7 << 8) +#define RT5665_AD_STO2_CLK_SEL_SFT 8 +#define RT5665_AD_MONOL_CLK_SEL_MASK (0x7 << 4) +#define RT5665_AD_MONOL_CLK_SEL_SFT 4 +#define RT5665_AD_MONOR_CLK_SEL_MASK (0x7) +#define RT5665_AD_MONOR_CLK_SEL_SFT 0 + +/* ASRC Control 4 (0x0086) */ +#define RT5665_I2S1_RATE_MASK (0xf << 12) +#define RT5665_I2S1_RATE_SFT 12 +#define RT5665_I2S2_RATE_MASK (0xf << 8) +#define RT5665_I2S2_RATE_SFT 8 +#define RT5665_I2S3_RATE_MASK (0xf << 4) +#define RT5665_I2S3_RATE_SFT 4 + +/* Depop Mode Control 1 (0x008e) */ +#define RT5665_PUMP_EN (0x1 << 3) + +/* Depop Mode Control 2 (0x8f) */ +#define RT5665_DEPOP_MASK (0x1 << 13) +#define RT5665_DEPOP_SFT 13 +#define RT5665_DEPOP_AUTO (0x0 << 13) +#define RT5665_DEPOP_MAN (0x1 << 13) +#define RT5665_RAMP_MASK (0x1 << 12) +#define RT5665_RAMP_SFT 12 +#define RT5665_RAMP_DIS (0x0 << 12) +#define RT5665_RAMP_EN (0x1 << 12) +#define RT5665_BPS_MASK (0x1 << 11) +#define RT5665_BPS_SFT 11 +#define RT5665_BPS_DIS (0x0 << 11) +#define RT5665_BPS_EN (0x1 << 11) +#define RT5665_FAST_UPDN_MASK (0x1 << 10) +#define RT5665_FAST_UPDN_SFT 10 +#define RT5665_FAST_UPDN_DIS (0x0 << 10) +#define RT5665_FAST_UPDN_EN (0x1 << 10) +#define RT5665_MRES_MASK (0x3 << 8) +#define RT5665_MRES_SFT 8 +#define RT5665_MRES_15MO (0x0 << 8) +#define RT5665_MRES_25MO (0x1 << 8) +#define RT5665_MRES_35MO (0x2 << 8) +#define RT5665_MRES_45MO (0x3 << 8) +#define RT5665_VLO_MASK (0x1 << 7) +#define RT5665_VLO_SFT 7 +#define RT5665_VLO_3V (0x0 << 7) +#define RT5665_VLO_32V (0x1 << 7) +#define RT5665_DIG_DP_MASK (0x1 << 6) +#define RT5665_DIG_DP_SFT 6 +#define RT5665_DIG_DP_DIS (0x0 << 6) +#define RT5665_DIG_DP_EN (0x1 << 6) +#define RT5665_DP_TH_MASK (0x3 << 4) +#define RT5665_DP_TH_SFT 4 + +/* Depop Mode Control 3 (0x90) */ +#define RT5665_CP_SYS_MASK (0x7 << 12) +#define RT5665_CP_SYS_SFT 12 +#define RT5665_CP_FQ1_MASK (0x7 << 8) +#define RT5665_CP_FQ1_SFT 8 +#define RT5665_CP_FQ2_MASK (0x7 << 4) +#define RT5665_CP_FQ2_SFT 4 +#define RT5665_CP_FQ3_MASK (0x7) +#define RT5665_CP_FQ3_SFT 0 +#define RT5665_CP_FQ_1_5_KHZ 0 +#define RT5665_CP_FQ_3_KHZ 1 +#define RT5665_CP_FQ_6_KHZ 2 +#define RT5665_CP_FQ_12_KHZ 3 +#define RT5665_CP_FQ_24_KHZ 4 +#define RT5665_CP_FQ_48_KHZ 5 +#define RT5665_CP_FQ_96_KHZ 6 +#define RT5665_CP_FQ_192_KHZ 7 + +/* HPOUT charge pump 1 (0x0091) */ +#define RT5665_OSW_L_MASK (0x1 << 11) +#define RT5665_OSW_L_SFT 11 +#define RT5665_OSW_L_DIS (0x0 << 11) +#define RT5665_OSW_L_EN (0x1 << 11) +#define RT5665_OSW_R_MASK (0x1 << 10) +#define RT5665_OSW_R_SFT 10 +#define RT5665_OSW_R_DIS (0x0 << 10) +#define RT5665_OSW_R_EN (0x1 << 10) +#define RT5665_PM_HP_MASK (0x3 << 8) +#define RT5665_PM_HP_SFT 8 +#define RT5665_PM_HP_LV (0x0 << 8) +#define RT5665_PM_HP_MV (0x1 << 8) +#define RT5665_PM_HP_HV (0x2 << 8) +#define RT5665_IB_HP_MASK (0x3 << 6) +#define RT5665_IB_HP_SFT 6 +#define RT5665_IB_HP_125IL (0x0 << 6) +#define RT5665_IB_HP_25IL (0x1 << 6) +#define RT5665_IB_HP_5IL (0x2 << 6) +#define RT5665_IB_HP_1IL (0x3 << 6) + +/* PV detection and SPK gain control (0x92) */ +#define RT5665_PVDD_DET_MASK (0x1 << 15) +#define RT5665_PVDD_DET_SFT 15 +#define RT5665_PVDD_DET_DIS (0x0 << 15) +#define RT5665_PVDD_DET_EN (0x1 << 15) +#define RT5665_SPK_AG_MASK (0x1 << 14) +#define RT5665_SPK_AG_SFT 14 +#define RT5665_SPK_AG_DIS (0x0 << 14) +#define RT5665_SPK_AG_EN (0x1 << 14) + +/* Micbias Control1 (0x93) */ +#define RT5665_MIC1_BS_MASK (0x1 << 15) +#define RT5665_MIC1_BS_SFT 15 +#define RT5665_MIC1_BS_9AV (0x0 << 15) +#define RT5665_MIC1_BS_75AV (0x1 << 15) +#define RT5665_MIC2_BS_MASK (0x1 << 14) +#define RT5665_MIC2_BS_SFT 14 +#define RT5665_MIC2_BS_9AV (0x0 << 14) +#define RT5665_MIC2_BS_75AV (0x1 << 14) +#define RT5665_MIC1_CLK_MASK (0x1 << 13) +#define RT5665_MIC1_CLK_SFT 13 +#define RT5665_MIC1_CLK_DIS (0x0 << 13) +#define RT5665_MIC1_CLK_EN (0x1 << 13) +#define RT5665_MIC2_CLK_MASK (0x1 << 12) +#define RT5665_MIC2_CLK_SFT 12 +#define RT5665_MIC2_CLK_DIS (0x0 << 12) +#define RT5665_MIC2_CLK_EN (0x1 << 12) +#define RT5665_MIC1_OVCD_MASK (0x1 << 11) +#define RT5665_MIC1_OVCD_SFT 11 +#define RT5665_MIC1_OVCD_DIS (0x0 << 11) +#define RT5665_MIC1_OVCD_EN (0x1 << 11) +#define RT5665_MIC1_OVTH_MASK (0x3 << 9) +#define RT5665_MIC1_OVTH_SFT 9 +#define RT5665_MIC1_OVTH_600UA (0x0 << 9) +#define RT5665_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5665_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5665_MIC2_OVCD_MASK (0x1 << 8) +#define RT5665_MIC2_OVCD_SFT 8 +#define RT5665_MIC2_OVCD_DIS (0x0 << 8) +#define RT5665_MIC2_OVCD_EN (0x1 << 8) +#define RT5665_MIC2_OVTH_MASK (0x3 << 6) +#define RT5665_MIC2_OVTH_SFT 6 +#define RT5665_MIC2_OVTH_600UA (0x0 << 6) +#define RT5665_MIC2_OVTH_1500UA (0x1 << 6) +#define RT5665_MIC2_OVTH_2000UA (0x2 << 6) +#define RT5665_PWR_MB_MASK (0x1 << 5) +#define RT5665_PWR_MB_SFT 5 +#define RT5665_PWR_MB_PD (0x0 << 5) +#define RT5665_PWR_MB_PU (0x1 << 5) + +/* Micbias Control2 (0x94) */ +#define RT5665_PWR_CLK25M_MASK (0x1 << 9) +#define RT5665_PWR_CLK25M_SFT 9 +#define RT5665_PWR_CLK25M_PD (0x0 << 9) +#define RT5665_PWR_CLK25M_PU (0x1 << 9) +#define RT5665_PWR_CLK1M_MASK (0x1 << 8) +#define RT5665_PWR_CLK1M_SFT 8 +#define RT5665_PWR_CLK1M_PD (0x0 << 8) +#define RT5665_PWR_CLK1M_PU (0x1 << 8) + +/* I2S Master Mode Clock Control 1 (0x00a0) */ +#define RT5665_CLK_SRC_MCLK (0x0) +#define RT5665_CLK_SRC_PLL1 (0x1) +#define RT5665_CLK_SRC_RCCLK (0x2) +#define RT5665_I2S_PD_1 (0x0) +#define RT5665_I2S_PD_2 (0x1) +#define RT5665_I2S_PD_3 (0x2) +#define RT5665_I2S_PD_4 (0x3) +#define RT5665_I2S_PD_6 (0x4) +#define RT5665_I2S_PD_8 (0x5) +#define RT5665_I2S_PD_12 (0x6) +#define RT5665_I2S_PD_16 (0x7) +#define RT5665_I2S2_SRC_MASK (0x3 << 12) +#define RT5665_I2S2_SRC_SFT 12 +#define RT5665_I2S2_M_PD_MASK (0x7 << 8) +#define RT5665_I2S2_M_PD_SFT 8 +#define RT5665_I2S3_SRC_MASK (0x3 << 4) +#define RT5665_I2S3_SRC_SFT 4 +#define RT5665_I2S3_M_PD_MASK (0x7 << 0) +#define RT5665_I2S3_M_PD_SFT 0 + + +/* EQ Control 1 (0x00b0) */ +#define RT5665_EQ_SRC_DAC (0x0 << 15) +#define RT5665_EQ_SRC_ADC (0x1 << 15) +#define RT5665_EQ_UPD (0x1 << 14) +#define RT5665_EQ_UPD_BIT 14 +#define RT5665_EQ_CD_MASK (0x1 << 13) +#define RT5665_EQ_CD_SFT 13 +#define RT5665_EQ_CD_DIS (0x0 << 13) +#define RT5665_EQ_CD_EN (0x1 << 13) +#define RT5665_EQ_DITH_MASK (0x3 << 8) +#define RT5665_EQ_DITH_SFT 8 +#define RT5665_EQ_DITH_NOR (0x0 << 8) +#define RT5665_EQ_DITH_LSB (0x1 << 8) +#define RT5665_EQ_DITH_LSB_1 (0x2 << 8) +#define RT5665_EQ_DITH_LSB_2 (0x3 << 8) + +/* IRQ Control 1 (0x00b7) */ +#define RT5665_JD1_1_EN_MASK (0x1 << 15) +#define RT5665_JD1_1_EN_SFT 15 +#define RT5665_JD1_1_DIS (0x0 << 15) +#define RT5665_JD1_1_EN (0x1 << 15) +#define RT5665_JD1_2_EN_MASK (0x1 << 12) +#define RT5665_JD1_2_EN_SFT 12 +#define RT5665_JD1_2_DIS (0x0 << 12) +#define RT5665_JD1_2_EN (0x1 << 12) + +/* IRQ Control 2 (0x00b8) */ +#define RT5665_IL_IRQ_MASK (0x1 << 6) +#define RT5665_IL_IRQ_DIS (0x0 << 6) +#define RT5665_IL_IRQ_EN (0x1 << 6) + +/* IRQ Control 5 (0x00ba) */ +#define RT5665_IRQ_JD_EN (0x1 << 3) +#define RT5665_IRQ_JD_EN_SFT 3 + +/* GPIO Control 1 (0x00c0) */ +#define RT5665_GP1_PIN_MASK (0x1 << 15) +#define RT5665_GP1_PIN_SFT 15 +#define RT5665_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5665_GP1_PIN_IRQ (0x1 << 15) +#define RT5665_GP2_PIN_MASK (0x3 << 13) +#define RT5665_GP2_PIN_SFT 13 +#define RT5665_GP2_PIN_GPIO2 (0x0 << 13) +#define RT5665_GP2_PIN_BCLK2 (0x1 << 13) +#define RT5665_GP2_PIN_PDM_SCL (0x2 << 13) +#define RT5665_GP3_PIN_MASK (0x3 << 11) +#define RT5665_GP3_PIN_SFT 11 +#define RT5665_GP3_PIN_GPIO3 (0x0 << 11) +#define RT5665_GP3_PIN_LRCK2 (0x1 << 11) +#define RT5665_GP3_PIN_PDM_SDA (0x2 << 11) +#define RT5665_GP4_PIN_MASK (0x3 << 9) +#define RT5665_GP4_PIN_SFT 9 +#define RT5665_GP4_PIN_GPIO4 (0x0 << 9) +#define RT5665_GP4_PIN_DACDAT2_1 (0x1 << 9) +#define RT5665_GP4_PIN_DMIC1_SDA (0x2 << 9) +#define RT5665_GP5_PIN_MASK (0x3 << 7) +#define RT5665_GP5_PIN_SFT 7 +#define RT5665_GP5_PIN_GPIO5 (0x0 << 7) +#define RT5665_GP5_PIN_ADCDAT2_1 (0x1 << 7) +#define RT5665_GP5_PIN_DMIC2_SDA (0x2 << 7) +#define RT5665_GP6_PIN_MASK (0x3 << 5) +#define RT5665_GP6_PIN_SFT 5 +#define RT5665_GP6_PIN_GPIO6 (0x0 << 5) +#define RT5665_GP6_PIN_BCLK3 (0x1 << 5) +#define RT5665_GP6_PIN_PDM_SCL (0x2 << 5) +#define RT5665_GP7_PIN_MASK (0x3 << 3) +#define RT5665_GP7_PIN_SFT 3 +#define RT5665_GP7_PIN_GPIO7 (0x0 << 3) +#define RT5665_GP7_PIN_LRCK3 (0x1 << 3) +#define RT5665_GP7_PIN_PDM_SDA (0x2 << 3) +#define RT5665_GP8_PIN_MASK (0x3 << 1) +#define RT5665_GP8_PIN_SFT 1 +#define RT5665_GP8_PIN_GPIO8 (0x0 << 1) +#define RT5665_GP8_PIN_DACDAT3 (0x1 << 1) +#define RT5665_GP8_PIN_DMIC2_SCL (0x2 << 1) +#define RT5665_GP8_PIN_DACDAT2_2 (0x3 << 1) + + +/* GPIO Control 2 (0x00c1)*/ +#define RT5665_GP9_PIN_MASK (0x3 << 14) +#define RT5665_GP9_PIN_SFT 14 +#define RT5665_GP9_PIN_GPIO9 (0x0 << 14) +#define RT5665_GP9_PIN_ADCDAT3 (0x1 << 14) +#define RT5665_GP9_PIN_DMIC1_SCL (0x2 << 14) +#define RT5665_GP9_PIN_ADCDAT2_2 (0x3 << 14) +#define RT5665_GP10_PIN_MASK (0x3 << 12) +#define RT5665_GP10_PIN_SFT 12 +#define RT5665_GP10_PIN_GPIO10 (0x0 << 12) +#define RT5665_GP10_PIN_ADCDAT1_2 (0x1 << 12) +#define RT5665_GP10_PIN_LPD (0x2 << 12) +#define RT5665_GP1_PF_MASK (0x1 << 11) +#define RT5665_GP1_PF_IN (0x0 << 11) +#define RT5665_GP1_PF_OUT (0x1 << 11) +#define RT5665_GP1_OUT_MASK (0x1 << 10) +#define RT5665_GP1_OUT_H (0x0 << 10) +#define RT5665_GP1_OUT_L (0x1 << 10) +#define RT5665_GP2_PF_MASK (0x1 << 9) +#define RT5665_GP2_PF_IN (0x0 << 9) +#define RT5665_GP2_PF_OUT (0x1 << 9) +#define RT5665_GP2_OUT_MASK (0x1 << 8) +#define RT5665_GP2_OUT_H (0x0 << 8) +#define RT5665_GP2_OUT_L (0x1 << 8) +#define RT5665_GP3_PF_MASK (0x1 << 7) +#define RT5665_GP3_PF_IN (0x0 << 7) +#define RT5665_GP3_PF_OUT (0x1 << 7) +#define RT5665_GP3_OUT_MASK (0x1 << 6) +#define RT5665_GP3_OUT_H (0x0 << 6) +#define RT5665_GP3_OUT_L (0x1 << 6) +#define RT5665_GP4_PF_MASK (0x1 << 5) +#define RT5665_GP4_PF_IN (0x0 << 5) +#define RT5665_GP4_PF_OUT (0x1 << 5) +#define RT5665_GP4_OUT_MASK (0x1 << 4) +#define RT5665_GP4_OUT_H (0x0 << 4) +#define RT5665_GP4_OUT_L (0x1 << 4) +#define RT5665_GP5_PF_MASK (0x1 << 3) +#define RT5665_GP5_PF_IN (0x0 << 3) +#define RT5665_GP5_PF_OUT (0x1 << 3) +#define RT5665_GP5_OUT_MASK (0x1 << 2) +#define RT5665_GP5_OUT_H (0x0 << 2) +#define RT5665_GP5_OUT_L (0x1 << 2) +#define RT5665_GP6_PF_MASK (0x1 << 1) +#define RT5665_GP6_PF_IN (0x0 << 1) +#define RT5665_GP6_PF_OUT (0x1 << 1) +#define RT5665_GP6_OUT_MASK (0x1) +#define RT5665_GP6_OUT_H (0x0) +#define RT5665_GP6_OUT_L (0x1) + + +/* GPIO Control 3 (0x00c2) */ +#define RT5665_GP7_PF_MASK (0x1 << 15) +#define RT5665_GP7_PF_IN (0x0 << 15) +#define RT5665_GP7_PF_OUT (0x1 << 15) +#define RT5665_GP7_OUT_MASK (0x1 << 14) +#define RT5665_GP7_OUT_H (0x0 << 14) +#define RT5665_GP7_OUT_L (0x1 << 14) +#define RT5665_GP8_PF_MASK (0x1 << 13) +#define RT5665_GP8_PF_IN (0x0 << 13) +#define RT5665_GP8_PF_OUT (0x1 << 13) +#define RT5665_GP8_OUT_MASK (0x1 << 12) +#define RT5665_GP8_OUT_H (0x0 << 12) +#define RT5665_GP8_OUT_L (0x1 << 12) +#define RT5665_GP9_PF_MASK (0x1 << 11) +#define RT5665_GP9_PF_IN (0x0 << 11) +#define RT5665_GP9_PF_OUT (0x1 << 11) +#define RT5665_GP9_OUT_MASK (0x1 << 10) +#define RT5665_GP9_OUT_H (0x0 << 10) +#define RT5665_GP9_OUT_L (0x1 << 10) +#define RT5665_GP10_PF_MASK (0x1 << 9) +#define RT5665_GP10_PF_IN (0x0 << 9) +#define RT5665_GP10_PF_OUT (0x1 << 9) +#define RT5665_GP10_OUT_MASK (0x1 << 8) +#define RT5665_GP10_OUT_H (0x0 << 8) +#define RT5665_GP10_OUT_L (0x1 << 8) +#define RT5665_GP11_PF_MASK (0x1 << 7) +#define RT5665_GP11_PF_IN (0x0 << 7) +#define RT5665_GP11_PF_OUT (0x1 << 7) +#define RT5665_GP11_OUT_MASK (0x1 << 6) +#define RT5665_GP11_OUT_H (0x0 << 6) +#define RT5665_GP11_OUT_L (0x1 << 6) + +/* Soft volume and zero cross control 1 (0x00d9) */ +#define RT5665_SV_MASK (0x1 << 15) +#define RT5665_SV_SFT 15 +#define RT5665_SV_DIS (0x0 << 15) +#define RT5665_SV_EN (0x1 << 15) +#define RT5665_OUT_SV_MASK (0x1 << 13) +#define RT5665_OUT_SV_SFT 13 +#define RT5665_OUT_SV_DIS (0x0 << 13) +#define RT5665_OUT_SV_EN (0x1 << 13) +#define RT5665_HP_SV_MASK (0x1 << 12) +#define RT5665_HP_SV_SFT 12 +#define RT5665_HP_SV_DIS (0x0 << 12) +#define RT5665_HP_SV_EN (0x1 << 12) +#define RT5665_ZCD_DIG_MASK (0x1 << 11) +#define RT5665_ZCD_DIG_SFT 11 +#define RT5665_ZCD_DIG_DIS (0x0 << 11) +#define RT5665_ZCD_DIG_EN (0x1 << 11) +#define RT5665_ZCD_MASK (0x1 << 10) +#define RT5665_ZCD_SFT 10 +#define RT5665_ZCD_PD (0x0 << 10) +#define RT5665_ZCD_PU (0x1 << 10) +#define RT5665_SV_DLY_MASK (0xf) +#define RT5665_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0x00da) */ +#define RT5665_ZCD_HP_MASK (0x1 << 15) +#define RT5665_ZCD_HP_SFT 15 +#define RT5665_ZCD_HP_DIS (0x0 << 15) +#define RT5665_ZCD_HP_EN (0x1 << 15) + +/* 4 Button Inline Command Control 2 (0x00e0) */ +#define RT5665_4BTN_IL_MASK (0x1 << 15) +#define RT5665_4BTN_IL_EN (0x1 << 15) +#define RT5665_4BTN_IL_DIS (0x0 << 15) +#define RT5665_4BTN_IL_RST_MASK (0x1 << 14) +#define RT5665_4BTN_IL_NOR (0x1 << 14) +#define RT5665_4BTN_IL_RST (0x0 << 14) + +/* Analog JD Control 1 (0x00f0) */ +#define RT5665_JD1_MODE_MASK (0x3 << 0) +#define RT5665_JD1_MODE_0 (0x0 << 0) +#define RT5665_JD1_MODE_1 (0x1 << 0) +#define RT5665_JD1_MODE_2 (0x2 << 0) + +/* Jack Detect Control 3 (0x00f8) */ +#define RT5665_JD_TRI_HPO_SEL_MASK (0x7) +#define RT5665_JD_TRI_HPO_SEL_SFT (0) +#define RT5665_JD_HPO_GPIO_JD1 (0x0) +#define RT5665_JD_HPO_JD1_1 (0x1) +#define RT5665_JD_HPO_JD1_2 (0x2) +#define RT5665_JD_HPO_JD2 (0x3) +#define RT5665_JD_HPO_GPIO_JD2 (0x4) +#define RT5665_JD_HPO_JD3 (0x5) +#define RT5665_JD_HPO_JD_D (0x6) + +/* Digital Misc Control (0x00fa) */ +#define RT5665_AM_MASK (0x1 << 7) +#define RT5665_AM_EN (0x1 << 7) +#define RT5665_AM_DIS (0x1 << 7) +#define RT5665_DIG_GATE_CTRL 0x1 +#define RT5665_DIG_GATE_CTRL_SFT (0) + +/* Chopper and Clock control for ADC (0x011c)*/ +#define RT5665_M_RF_DIG_MASK (0x1 << 12) +#define RT5665_M_RF_DIG_SFT 12 +#define RT5665_M_RI_DIG (0x1 << 11) + +/* Chopper and Clock control for DAC (0x013a)*/ +#define RT5665_CKXEN_DAC1_MASK (0x1 << 13) +#define RT5665_CKXEN_DAC1_SFT 13 +#define RT5665_CKGEN_DAC1_MASK (0x1 << 12) +#define RT5665_CKGEN_DAC1_SFT 12 +#define RT5665_CKXEN_DAC2_MASK (0x1 << 5) +#define RT5665_CKXEN_DAC2_SFT 5 +#define RT5665_CKGEN_DAC2_MASK (0x1 << 4) +#define RT5665_CKGEN_DAC2_SFT 4 + +/* Chopper and Clock control for ADC (0x013b)*/ +#define RT5665_CKXEN_ADC1_MASK (0x1 << 13) +#define RT5665_CKXEN_ADC1_SFT 13 +#define RT5665_CKGEN_ADC1_MASK (0x1 << 12) +#define RT5665_CKGEN_ADC1_SFT 12 +#define RT5665_CKXEN_ADC2_MASK (0x1 << 5) +#define RT5665_CKXEN_ADC2_SFT 5 +#define RT5665_CKGEN_ADC2_MASK (0x1 << 4) +#define RT5665_CKGEN_ADC2_SFT 4 + +/* Volume test (0x013f)*/ +#define RT5665_SEL_CLK_VOL_MASK (0x1 << 15) +#define RT5665_SEL_CLK_VOL_EN (0x1 << 15) +#define RT5665_SEL_CLK_VOL_DIS (0x0 << 15) + +/* Test Mode Control 1 (0x0145) */ +#define RT5665_AD2DA_LB_MASK (0x1 << 9) +#define RT5665_AD2DA_LB_SFT 9 + +/* Stereo Noise Gate Control 1 (0x0160) */ +#define RT5665_NG2_EN_MASK (0x1 << 15) +#define RT5665_NG2_EN (0x1 << 15) +#define RT5665_NG2_DIS (0x0 << 15) + +/* Stereo1 DAC Silence Detection Control (0x0190) */ +#define RT5665_DEB_STO_DAC_MASK (0x7 << 4) +#define RT5665_DEB_80_MS (0x0 << 4) + +/* SAR ADC Inline Command Control 1 (0x0210) */ +#define RT5665_SAR_BUTT_DET_MASK (0x1 << 15) +#define RT5665_SAR_BUTT_DET_EN (0x1 << 15) +#define RT5665_SAR_BUTT_DET_DIS (0x0 << 15) +#define RT5665_SAR_BUTDET_MODE_MASK (0x1 << 14) +#define RT5665_SAR_BUTDET_POW_SAV (0x1 << 14) +#define RT5665_SAR_BUTDET_POW_NORM (0x0 << 14) +#define RT5665_SAR_BUTDET_RST_MASK (0x1 << 13) +#define RT5665_SAR_BUTDET_RST_NORMAL (0x1 << 13) +#define RT5665_SAR_BUTDET_RST (0x0 << 13) +#define RT5665_SAR_POW_MASK (0x1 << 12) +#define RT5665_SAR_POW_EN (0x1 << 12) +#define RT5665_SAR_POW_DIS (0x0 << 12) +#define RT5665_SAR_RST_MASK (0x1 << 11) +#define RT5665_SAR_RST_NORMAL (0x1 << 11) +#define RT5665_SAR_RST (0x0 << 11) +#define RT5665_SAR_BYPASS_MASK (0x1 << 10) +#define RT5665_SAR_BYPASS_EN (0x1 << 10) +#define RT5665_SAR_BYPASS_DIS (0x0 << 10) +#define RT5665_SAR_SEL_MB1_MASK (0x1 << 9) +#define RT5665_SAR_SEL_MB1_SEL (0x1 << 9) +#define RT5665_SAR_SEL_MB1_NOSEL (0x0 << 9) +#define RT5665_SAR_SEL_MB2_MASK (0x1 << 8) +#define RT5665_SAR_SEL_MB2_SEL (0x1 << 8) +#define RT5665_SAR_SEL_MB2_NOSEL (0x0 << 8) +#define RT5665_SAR_SEL_MODE_MASK (0x1 << 7) +#define RT5665_SAR_SEL_MODE_CMP (0x1 << 7) +#define RT5665_SAR_SEL_MODE_ADC (0x0 << 7) +#define RT5665_SAR_SEL_MB1_MB2_MASK (0x1 << 5) +#define RT5665_SAR_SEL_MB1_MB2_AUTO (0x1 << 5) +#define RT5665_SAR_SEL_MB1_MB2_MANU (0x0 << 5) +#define RT5665_SAR_SEL_SIGNAL_MASK (0x1 << 4) +#define RT5665_SAR_SEL_SIGNAL_AUTO (0x1 << 4) +#define RT5665_SAR_SEL_SIGNAL_MANU (0x0 << 4) + +/* System Clock Source */ +enum { + RT5665_SCLK_S_MCLK, + RT5665_SCLK_S_PLL1, + RT5665_SCLK_S_RCCLK, +}; + +/* PLL1 Source */ +enum { + RT5665_PLL1_S_MCLK, + RT5665_PLL1_S_BCLK1, + RT5665_PLL1_S_BCLK2, + RT5665_PLL1_S_BCLK3, + RT5665_PLL1_S_BCLK4, +}; + +enum { + RT5665_AIF1_1, + RT5665_AIF1_2, + RT5665_AIF2_1, + RT5665_AIF2_2, + RT5665_AIF3, + RT5665_AIFS +}; + +enum { + CODEC_5665, + CODEC_5666, +}; + +/* filter mask */ +enum { + RT5665_DA_STEREO1_FILTER = 0x1, + RT5665_DA_STEREO2_FILTER = (0x1 << 1), + RT5665_DA_MONO_L_FILTER = (0x1 << 2), + RT5665_DA_MONO_R_FILTER = (0x1 << 3), + RT5665_AD_STEREO1_FILTER = (0x1 << 4), + RT5665_AD_STEREO2_FILTER = (0x1 << 5), + RT5665_AD_MONO_L_FILTER = (0x1 << 6), + RT5665_AD_MONO_R_FILTER = (0x1 << 7), +}; + +enum { + RT5665_CLK_SEL_SYS, + RT5665_CLK_SEL_I2S1_ASRC, + RT5665_CLK_SEL_I2S2_ASRC, + RT5665_CLK_SEL_I2S3_ASRC, + RT5665_CLK_SEL_SYS2, + RT5665_CLK_SEL_SYS3, + RT5665_CLK_SEL_SYS4, +}; + +int rt5665_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +#endif /* __RT5665_H__ */ diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c new file mode 100644 index 000000000..a78503f24 --- /dev/null +++ b/sound/soc/codecs/rt5668.c @@ -0,0 +1,2641 @@ +/* + * rt5668.c -- RT5668B ALSA SoC audio component driver + * + * Copyright 2018 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5668.h" + +#define RT5668_NUM_SUPPLIES 3 + +static const char *rt5668_supply_names[RT5668_NUM_SUPPLIES] = { + "AVDD", + "MICVDD", + "VBAT", +}; + +struct rt5668_priv { + struct snd_soc_component *component; + struct rt5668_platform_data pdata; + struct regmap *regmap; + struct snd_soc_jack *hs_jack; + struct regulator_bulk_data supplies[RT5668_NUM_SUPPLIES]; + struct delayed_work jack_detect_work; + struct delayed_work jd_check_work; + struct mutex calibrate_mutex; + + int sysclk; + int sysclk_src; + int lrck[RT5668_AIFS]; + int bclk[RT5668_AIFS]; + int master[RT5668_AIFS]; + + int pll_src; + int pll_in; + int pll_out; + + int jack_type; +}; + +static const struct reg_default rt5668_reg[] = { + {0x0002, 0x8080}, + {0x0003, 0x8000}, + {0x0005, 0x0000}, + {0x0006, 0x0000}, + {0x0008, 0x800f}, + {0x000b, 0x0000}, + {0x0010, 0x4040}, + {0x0011, 0x0000}, + {0x0012, 0x1404}, + {0x0013, 0x1000}, + {0x0014, 0xa00a}, + {0x0015, 0x0404}, + {0x0016, 0x0404}, + {0x0019, 0xafaf}, + {0x001c, 0x2f2f}, + {0x001f, 0x0000}, + {0x0022, 0x5757}, + {0x0023, 0x0039}, + {0x0024, 0x000b}, + {0x0026, 0xc0c4}, + {0x0029, 0x8080}, + {0x002a, 0xa0a0}, + {0x002b, 0x0300}, + {0x0030, 0x0000}, + {0x003c, 0x0080}, + {0x0044, 0x0c0c}, + {0x0049, 0x0000}, + {0x0061, 0x0000}, + {0x0062, 0x0000}, + {0x0063, 0x003f}, + {0x0064, 0x0000}, + {0x0065, 0x0000}, + {0x0066, 0x0030}, + {0x0067, 0x0000}, + {0x006b, 0x0000}, + {0x006c, 0x0000}, + {0x006d, 0x2200}, + {0x006e, 0x0a10}, + {0x0070, 0x8000}, + {0x0071, 0x8000}, + {0x0073, 0x0000}, + {0x0074, 0x0000}, + {0x0075, 0x0002}, + {0x0076, 0x0001}, + {0x0079, 0x0000}, + {0x007a, 0x0000}, + {0x007b, 0x0000}, + {0x007c, 0x0100}, + {0x007e, 0x0000}, + {0x0080, 0x0000}, + {0x0081, 0x0000}, + {0x0082, 0x0000}, + {0x0083, 0x0000}, + {0x0084, 0x0000}, + {0x0085, 0x0000}, + {0x0086, 0x0005}, + {0x0087, 0x0000}, + {0x0088, 0x0000}, + {0x008c, 0x0003}, + {0x008d, 0x0000}, + {0x008e, 0x0060}, + {0x008f, 0x1000}, + {0x0091, 0x0c26}, + {0x0092, 0x0073}, + {0x0093, 0x0000}, + {0x0094, 0x0080}, + {0x0098, 0x0000}, + {0x009a, 0x0000}, + {0x009b, 0x0000}, + {0x009c, 0x0000}, + {0x009d, 0x0000}, + {0x009e, 0x100c}, + {0x009f, 0x0000}, + {0x00a0, 0x0000}, + {0x00a3, 0x0002}, + {0x00a4, 0x0001}, + {0x00ae, 0x2040}, + {0x00af, 0x0000}, + {0x00b6, 0x0000}, + {0x00b7, 0x0000}, + {0x00b8, 0x0000}, + {0x00b9, 0x0002}, + {0x00be, 0x0000}, + {0x00c0, 0x0160}, + {0x00c1, 0x82a0}, + {0x00c2, 0x0000}, + {0x00d0, 0x0000}, + {0x00d1, 0x2244}, + {0x00d2, 0x3300}, + {0x00d3, 0x2200}, + {0x00d4, 0x0000}, + {0x00d9, 0x0009}, + {0x00da, 0x0000}, + {0x00db, 0x0000}, + {0x00dc, 0x00c0}, + {0x00dd, 0x2220}, + {0x00de, 0x3131}, + {0x00df, 0x3131}, + {0x00e0, 0x3131}, + {0x00e2, 0x0000}, + {0x00e3, 0x4000}, + {0x00e4, 0x0aa0}, + {0x00e5, 0x3131}, + {0x00e6, 0x3131}, + {0x00e7, 0x3131}, + {0x00e8, 0x3131}, + {0x00ea, 0xb320}, + {0x00eb, 0x0000}, + {0x00f0, 0x0000}, + {0x00f1, 0x00d0}, + {0x00f2, 0x00d0}, + {0x00f6, 0x0000}, + {0x00fa, 0x0000}, + {0x00fb, 0x0000}, + {0x00fc, 0x0000}, + {0x00fd, 0x0000}, + {0x00fe, 0x10ec}, + {0x00ff, 0x6530}, + {0x0100, 0xa0a0}, + {0x010b, 0x0000}, + {0x010c, 0xae00}, + {0x010d, 0xaaa0}, + {0x010e, 0x8aa2}, + {0x010f, 0x02a2}, + {0x0110, 0xc000}, + {0x0111, 0x04a2}, + {0x0112, 0x2800}, + {0x0113, 0x0000}, + {0x0117, 0x0100}, + {0x0125, 0x0410}, + {0x0132, 0x6026}, + {0x0136, 0x5555}, + {0x0138, 0x3700}, + {0x013a, 0x2000}, + {0x013b, 0x2000}, + {0x013c, 0x2005}, + {0x013f, 0x0000}, + {0x0142, 0x0000}, + {0x0145, 0x0002}, + {0x0146, 0x0000}, + {0x0147, 0x0000}, + {0x0148, 0x0000}, + {0x0149, 0x0000}, + {0x0150, 0x79a1}, + {0x0151, 0x0000}, + {0x0160, 0x4ec0}, + {0x0161, 0x0080}, + {0x0162, 0x0200}, + {0x0163, 0x0800}, + {0x0164, 0x0000}, + {0x0165, 0x0000}, + {0x0166, 0x0000}, + {0x0167, 0x000f}, + {0x0168, 0x000f}, + {0x0169, 0x0021}, + {0x0190, 0x413d}, + {0x0194, 0x0000}, + {0x0195, 0x0000}, + {0x0197, 0x0022}, + {0x0198, 0x0000}, + {0x0199, 0x0000}, + {0x01af, 0x0000}, + {0x01b0, 0x0400}, + {0x01b1, 0x0000}, + {0x01b2, 0x0000}, + {0x01b3, 0x0000}, + {0x01b4, 0x0000}, + {0x01b5, 0x0000}, + {0x01b6, 0x01c3}, + {0x01b7, 0x02a0}, + {0x01b8, 0x03e9}, + {0x01b9, 0x1389}, + {0x01ba, 0xc351}, + {0x01bb, 0x0009}, + {0x01bc, 0x0018}, + {0x01bd, 0x002a}, + {0x01be, 0x004c}, + {0x01bf, 0x0097}, + {0x01c0, 0x433d}, + {0x01c1, 0x2800}, + {0x01c2, 0x0000}, + {0x01c3, 0x0000}, + {0x01c4, 0x0000}, + {0x01c5, 0x0000}, + {0x01c6, 0x0000}, + {0x01c7, 0x0000}, + {0x01c8, 0x40af}, + {0x01c9, 0x0702}, + {0x01ca, 0x0000}, + {0x01cb, 0x0000}, + {0x01cc, 0x5757}, + {0x01cd, 0x5757}, + {0x01ce, 0x5757}, + {0x01cf, 0x5757}, + {0x01d0, 0x5757}, + {0x01d1, 0x5757}, + {0x01d2, 0x5757}, + {0x01d3, 0x5757}, + {0x01d4, 0x5757}, + {0x01d5, 0x5757}, + {0x01d6, 0x0000}, + {0x01d7, 0x0008}, + {0x01d8, 0x0029}, + {0x01d9, 0x3333}, + {0x01da, 0x0000}, + {0x01db, 0x0004}, + {0x01dc, 0x0000}, + {0x01de, 0x7c00}, + {0x01df, 0x0320}, + {0x01e0, 0x06a1}, + {0x01e1, 0x0000}, + {0x01e2, 0x0000}, + {0x01e3, 0x0000}, + {0x01e4, 0x0000}, + {0x01e6, 0x0001}, + {0x01e7, 0x0000}, + {0x01e8, 0x0000}, + {0x01ea, 0x0000}, + {0x01eb, 0x0000}, + {0x01ec, 0x0000}, + {0x01ed, 0x0000}, + {0x01ee, 0x0000}, + {0x01ef, 0x0000}, + {0x01f0, 0x0000}, + {0x01f1, 0x0000}, + {0x01f2, 0x0000}, + {0x01f3, 0x0000}, + {0x01f4, 0x0000}, + {0x0210, 0x6297}, + {0x0211, 0xa005}, + {0x0212, 0x824c}, + {0x0213, 0xf7ff}, + {0x0214, 0xf24c}, + {0x0215, 0x0102}, + {0x0216, 0x00a3}, + {0x0217, 0x0048}, + {0x0218, 0xa2c0}, + {0x0219, 0x0400}, + {0x021a, 0x00c8}, + {0x021b, 0x00c0}, + {0x021c, 0x0000}, + {0x0250, 0x4500}, + {0x0251, 0x40b3}, + {0x0252, 0x0000}, + {0x0253, 0x0000}, + {0x0254, 0x0000}, + {0x0255, 0x0000}, + {0x0256, 0x0000}, + {0x0257, 0x0000}, + {0x0258, 0x0000}, + {0x0259, 0x0000}, + {0x025a, 0x0005}, + {0x0270, 0x0000}, + {0x02ff, 0x0110}, + {0x0300, 0x001f}, + {0x0301, 0x032c}, + {0x0302, 0x5f21}, + {0x0303, 0x4000}, + {0x0304, 0x4000}, + {0x0305, 0x06d5}, + {0x0306, 0x8000}, + {0x0307, 0x0700}, + {0x0310, 0x4560}, + {0x0311, 0xa4a8}, + {0x0312, 0x7418}, + {0x0313, 0x0000}, + {0x0314, 0x0006}, + {0x0315, 0xffff}, + {0x0316, 0xc400}, + {0x0317, 0x0000}, + {0x03c0, 0x7e00}, + {0x03c1, 0x8000}, + {0x03c2, 0x8000}, + {0x03c3, 0x8000}, + {0x03c4, 0x8000}, + {0x03c5, 0x8000}, + {0x03c6, 0x8000}, + {0x03c7, 0x8000}, + {0x03c8, 0x8000}, + {0x03c9, 0x8000}, + {0x03ca, 0x8000}, + {0x03cb, 0x8000}, + {0x03cc, 0x8000}, + {0x03d0, 0x0000}, + {0x03d1, 0x0000}, + {0x03d2, 0x0000}, + {0x03d3, 0x0000}, + {0x03d4, 0x2000}, + {0x03d5, 0x2000}, + {0x03d6, 0x0000}, + {0x03d7, 0x0000}, + {0x03d8, 0x2000}, + {0x03d9, 0x2000}, + {0x03da, 0x2000}, + {0x03db, 0x2000}, + {0x03dc, 0x0000}, + {0x03dd, 0x0000}, + {0x03de, 0x0000}, + {0x03df, 0x2000}, + {0x03e0, 0x0000}, + {0x03e1, 0x0000}, + {0x03e2, 0x0000}, + {0x03e3, 0x0000}, + {0x03e4, 0x0000}, + {0x03e5, 0x0000}, + {0x03e6, 0x0000}, + {0x03e7, 0x0000}, + {0x03e8, 0x0000}, + {0x03e9, 0x0000}, + {0x03ea, 0x0000}, + {0x03eb, 0x0000}, + {0x03ec, 0x0000}, + {0x03ed, 0x0000}, + {0x03ee, 0x0000}, + {0x03ef, 0x0000}, + {0x03f0, 0x0800}, + {0x03f1, 0x0800}, + {0x03f2, 0x0800}, + {0x03f3, 0x0800}, +}; + +static bool rt5668_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5668_RESET: + case RT5668_CBJ_CTRL_2: + case RT5668_INT_ST_1: + case RT5668_4BTN_IL_CMD_1: + case RT5668_AJD1_CTRL: + case RT5668_HP_CALIB_CTRL_1: + case RT5668_DEVICE_ID: + case RT5668_I2C_MODE: + case RT5668_HP_CALIB_CTRL_10: + case RT5668_EFUSE_CTRL_2: + case RT5668_JD_TOP_VC_VTRL: + case RT5668_HP_IMP_SENS_CTRL_19: + case RT5668_IL_CMD_1: + case RT5668_SAR_IL_CMD_2: + case RT5668_SAR_IL_CMD_4: + case RT5668_SAR_IL_CMD_10: + case RT5668_SAR_IL_CMD_11: + case RT5668_EFUSE_CTRL_6...RT5668_EFUSE_CTRL_11: + case RT5668_HP_CALIB_STA_1...RT5668_HP_CALIB_STA_11: + return true; + default: + return false; + } +} + +static bool rt5668_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5668_RESET: + case RT5668_VERSION_ID: + case RT5668_VENDOR_ID: + case RT5668_DEVICE_ID: + case RT5668_HP_CTRL_1: + case RT5668_HP_CTRL_2: + case RT5668_HPL_GAIN: + case RT5668_HPR_GAIN: + case RT5668_I2C_CTRL: + case RT5668_CBJ_BST_CTRL: + case RT5668_CBJ_CTRL_1: + case RT5668_CBJ_CTRL_2: + case RT5668_CBJ_CTRL_3: + case RT5668_CBJ_CTRL_4: + case RT5668_CBJ_CTRL_5: + case RT5668_CBJ_CTRL_6: + case RT5668_CBJ_CTRL_7: + case RT5668_DAC1_DIG_VOL: + case RT5668_STO1_ADC_DIG_VOL: + case RT5668_STO1_ADC_BOOST: + case RT5668_HP_IMP_GAIN_1: + case RT5668_HP_IMP_GAIN_2: + case RT5668_SIDETONE_CTRL: + case RT5668_STO1_ADC_MIXER: + case RT5668_AD_DA_MIXER: + case RT5668_STO1_DAC_MIXER: + case RT5668_A_DAC1_MUX: + case RT5668_DIG_INF2_DATA: + case RT5668_REC_MIXER: + case RT5668_CAL_REC: + case RT5668_ALC_BACK_GAIN: + case RT5668_PWR_DIG_1: + case RT5668_PWR_DIG_2: + case RT5668_PWR_ANLG_1: + case RT5668_PWR_ANLG_2: + case RT5668_PWR_ANLG_3: + case RT5668_PWR_MIXER: + case RT5668_PWR_VOL: + case RT5668_CLK_DET: + case RT5668_RESET_LPF_CTRL: + case RT5668_RESET_HPF_CTRL: + case RT5668_DMIC_CTRL_1: + case RT5668_I2S1_SDP: + case RT5668_I2S2_SDP: + case RT5668_ADDA_CLK_1: + case RT5668_ADDA_CLK_2: + case RT5668_I2S1_F_DIV_CTRL_1: + case RT5668_I2S1_F_DIV_CTRL_2: + case RT5668_TDM_CTRL: + case RT5668_TDM_ADDA_CTRL_1: + case RT5668_TDM_ADDA_CTRL_2: + case RT5668_DATA_SEL_CTRL_1: + case RT5668_TDM_TCON_CTRL: + case RT5668_GLB_CLK: + case RT5668_PLL_CTRL_1: + case RT5668_PLL_CTRL_2: + case RT5668_PLL_TRACK_1: + case RT5668_PLL_TRACK_2: + case RT5668_PLL_TRACK_3: + case RT5668_PLL_TRACK_4: + case RT5668_PLL_TRACK_5: + case RT5668_PLL_TRACK_6: + case RT5668_PLL_TRACK_11: + case RT5668_SDW_REF_CLK: + case RT5668_DEPOP_1: + case RT5668_DEPOP_2: + case RT5668_HP_CHARGE_PUMP_1: + case RT5668_HP_CHARGE_PUMP_2: + case RT5668_MICBIAS_1: + case RT5668_MICBIAS_2: + case RT5668_PLL_TRACK_12: + case RT5668_PLL_TRACK_14: + case RT5668_PLL2_CTRL_1: + case RT5668_PLL2_CTRL_2: + case RT5668_PLL2_CTRL_3: + case RT5668_PLL2_CTRL_4: + case RT5668_RC_CLK_CTRL: + case RT5668_I2S_M_CLK_CTRL_1: + case RT5668_I2S2_F_DIV_CTRL_1: + case RT5668_I2S2_F_DIV_CTRL_2: + case RT5668_EQ_CTRL_1: + case RT5668_EQ_CTRL_2: + case RT5668_IRQ_CTRL_1: + case RT5668_IRQ_CTRL_2: + case RT5668_IRQ_CTRL_3: + case RT5668_IRQ_CTRL_4: + case RT5668_INT_ST_1: + case RT5668_GPIO_CTRL_1: + case RT5668_GPIO_CTRL_2: + case RT5668_GPIO_CTRL_3: + case RT5668_HP_AMP_DET_CTRL_1: + case RT5668_HP_AMP_DET_CTRL_2: + case RT5668_MID_HP_AMP_DET: + case RT5668_LOW_HP_AMP_DET: + case RT5668_DELAY_BUF_CTRL: + case RT5668_SV_ZCD_1: + case RT5668_SV_ZCD_2: + case RT5668_IL_CMD_1: + case RT5668_IL_CMD_2: + case RT5668_IL_CMD_3: + case RT5668_IL_CMD_4: + case RT5668_IL_CMD_5: + case RT5668_IL_CMD_6: + case RT5668_4BTN_IL_CMD_1: + case RT5668_4BTN_IL_CMD_2: + case RT5668_4BTN_IL_CMD_3: + case RT5668_4BTN_IL_CMD_4: + case RT5668_4BTN_IL_CMD_5: + case RT5668_4BTN_IL_CMD_6: + case RT5668_4BTN_IL_CMD_7: + case RT5668_ADC_STO1_HP_CTRL_1: + case RT5668_ADC_STO1_HP_CTRL_2: + case RT5668_AJD1_CTRL: + case RT5668_JD1_THD: + case RT5668_JD2_THD: + case RT5668_JD_CTRL_1: + case RT5668_DUMMY_1: + case RT5668_DUMMY_2: + case RT5668_DUMMY_3: + case RT5668_DAC_ADC_DIG_VOL1: + case RT5668_BIAS_CUR_CTRL_2: + case RT5668_BIAS_CUR_CTRL_3: + case RT5668_BIAS_CUR_CTRL_4: + case RT5668_BIAS_CUR_CTRL_5: + case RT5668_BIAS_CUR_CTRL_6: + case RT5668_BIAS_CUR_CTRL_7: + case RT5668_BIAS_CUR_CTRL_8: + case RT5668_BIAS_CUR_CTRL_9: + case RT5668_BIAS_CUR_CTRL_10: + case RT5668_VREF_REC_OP_FB_CAP_CTRL: + case RT5668_CHARGE_PUMP_1: + case RT5668_DIG_IN_CTRL_1: + case RT5668_PAD_DRIVING_CTRL: + case RT5668_SOFT_RAMP_DEPOP: + case RT5668_CHOP_DAC: + case RT5668_CHOP_ADC: + case RT5668_CALIB_ADC_CTRL: + case RT5668_VOL_TEST: + case RT5668_SPKVDD_DET_STA: + case RT5668_TEST_MODE_CTRL_1: + case RT5668_TEST_MODE_CTRL_2: + case RT5668_TEST_MODE_CTRL_3: + case RT5668_TEST_MODE_CTRL_4: + case RT5668_TEST_MODE_CTRL_5: + case RT5668_PLL1_INTERNAL: + case RT5668_PLL2_INTERNAL: + case RT5668_STO_NG2_CTRL_1: + case RT5668_STO_NG2_CTRL_2: + case RT5668_STO_NG2_CTRL_3: + case RT5668_STO_NG2_CTRL_4: + case RT5668_STO_NG2_CTRL_5: + case RT5668_STO_NG2_CTRL_6: + case RT5668_STO_NG2_CTRL_7: + case RT5668_STO_NG2_CTRL_8: + case RT5668_STO_NG2_CTRL_9: + case RT5668_STO_NG2_CTRL_10: + case RT5668_STO1_DAC_SIL_DET: + case RT5668_SIL_PSV_CTRL1: + case RT5668_SIL_PSV_CTRL2: + case RT5668_SIL_PSV_CTRL3: + case RT5668_SIL_PSV_CTRL4: + case RT5668_SIL_PSV_CTRL5: + case RT5668_HP_IMP_SENS_CTRL_01: + case RT5668_HP_IMP_SENS_CTRL_02: + case RT5668_HP_IMP_SENS_CTRL_03: + case RT5668_HP_IMP_SENS_CTRL_04: + case RT5668_HP_IMP_SENS_CTRL_05: + case RT5668_HP_IMP_SENS_CTRL_06: + case RT5668_HP_IMP_SENS_CTRL_07: + case RT5668_HP_IMP_SENS_CTRL_08: + case RT5668_HP_IMP_SENS_CTRL_09: + case RT5668_HP_IMP_SENS_CTRL_10: + case RT5668_HP_IMP_SENS_CTRL_11: + case RT5668_HP_IMP_SENS_CTRL_12: + case RT5668_HP_IMP_SENS_CTRL_13: + case RT5668_HP_IMP_SENS_CTRL_14: + case RT5668_HP_IMP_SENS_CTRL_15: + case RT5668_HP_IMP_SENS_CTRL_16: + case RT5668_HP_IMP_SENS_CTRL_17: + case RT5668_HP_IMP_SENS_CTRL_18: + case RT5668_HP_IMP_SENS_CTRL_19: + case RT5668_HP_IMP_SENS_CTRL_20: + case RT5668_HP_IMP_SENS_CTRL_21: + case RT5668_HP_IMP_SENS_CTRL_22: + case RT5668_HP_IMP_SENS_CTRL_23: + case RT5668_HP_IMP_SENS_CTRL_24: + case RT5668_HP_IMP_SENS_CTRL_25: + case RT5668_HP_IMP_SENS_CTRL_26: + case RT5668_HP_IMP_SENS_CTRL_27: + case RT5668_HP_IMP_SENS_CTRL_28: + case RT5668_HP_IMP_SENS_CTRL_29: + case RT5668_HP_IMP_SENS_CTRL_30: + case RT5668_HP_IMP_SENS_CTRL_31: + case RT5668_HP_IMP_SENS_CTRL_32: + case RT5668_HP_IMP_SENS_CTRL_33: + case RT5668_HP_IMP_SENS_CTRL_34: + case RT5668_HP_IMP_SENS_CTRL_35: + case RT5668_HP_IMP_SENS_CTRL_36: + case RT5668_HP_IMP_SENS_CTRL_37: + case RT5668_HP_IMP_SENS_CTRL_38: + case RT5668_HP_IMP_SENS_CTRL_39: + case RT5668_HP_IMP_SENS_CTRL_40: + case RT5668_HP_IMP_SENS_CTRL_41: + case RT5668_HP_IMP_SENS_CTRL_42: + case RT5668_HP_IMP_SENS_CTRL_43: + case RT5668_HP_LOGIC_CTRL_1: + case RT5668_HP_LOGIC_CTRL_2: + case RT5668_HP_LOGIC_CTRL_3: + case RT5668_HP_CALIB_CTRL_1: + case RT5668_HP_CALIB_CTRL_2: + case RT5668_HP_CALIB_CTRL_3: + case RT5668_HP_CALIB_CTRL_4: + case RT5668_HP_CALIB_CTRL_5: + case RT5668_HP_CALIB_CTRL_6: + case RT5668_HP_CALIB_CTRL_7: + case RT5668_HP_CALIB_CTRL_9: + case RT5668_HP_CALIB_CTRL_10: + case RT5668_HP_CALIB_CTRL_11: + case RT5668_HP_CALIB_STA_1: + case RT5668_HP_CALIB_STA_2: + case RT5668_HP_CALIB_STA_3: + case RT5668_HP_CALIB_STA_4: + case RT5668_HP_CALIB_STA_5: + case RT5668_HP_CALIB_STA_6: + case RT5668_HP_CALIB_STA_7: + case RT5668_HP_CALIB_STA_8: + case RT5668_HP_CALIB_STA_9: + case RT5668_HP_CALIB_STA_10: + case RT5668_HP_CALIB_STA_11: + case RT5668_SAR_IL_CMD_1: + case RT5668_SAR_IL_CMD_2: + case RT5668_SAR_IL_CMD_3: + case RT5668_SAR_IL_CMD_4: + case RT5668_SAR_IL_CMD_5: + case RT5668_SAR_IL_CMD_6: + case RT5668_SAR_IL_CMD_7: + case RT5668_SAR_IL_CMD_8: + case RT5668_SAR_IL_CMD_9: + case RT5668_SAR_IL_CMD_10: + case RT5668_SAR_IL_CMD_11: + case RT5668_SAR_IL_CMD_12: + case RT5668_SAR_IL_CMD_13: + case RT5668_EFUSE_CTRL_1: + case RT5668_EFUSE_CTRL_2: + case RT5668_EFUSE_CTRL_3: + case RT5668_EFUSE_CTRL_4: + case RT5668_EFUSE_CTRL_5: + case RT5668_EFUSE_CTRL_6: + case RT5668_EFUSE_CTRL_7: + case RT5668_EFUSE_CTRL_8: + case RT5668_EFUSE_CTRL_9: + case RT5668_EFUSE_CTRL_10: + case RT5668_EFUSE_CTRL_11: + case RT5668_JD_TOP_VC_VTRL: + case RT5668_DRC1_CTRL_0: + case RT5668_DRC1_CTRL_1: + case RT5668_DRC1_CTRL_2: + case RT5668_DRC1_CTRL_3: + case RT5668_DRC1_CTRL_4: + case RT5668_DRC1_CTRL_5: + case RT5668_DRC1_CTRL_6: + case RT5668_DRC1_HARD_LMT_CTRL_1: + case RT5668_DRC1_HARD_LMT_CTRL_2: + case RT5668_DRC1_PRIV_1: + case RT5668_DRC1_PRIV_2: + case RT5668_DRC1_PRIV_3: + case RT5668_DRC1_PRIV_4: + case RT5668_DRC1_PRIV_5: + case RT5668_DRC1_PRIV_6: + case RT5668_DRC1_PRIV_7: + case RT5668_DRC1_PRIV_8: + case RT5668_EQ_AUTO_RCV_CTRL1: + case RT5668_EQ_AUTO_RCV_CTRL2: + case RT5668_EQ_AUTO_RCV_CTRL3: + case RT5668_EQ_AUTO_RCV_CTRL4: + case RT5668_EQ_AUTO_RCV_CTRL5: + case RT5668_EQ_AUTO_RCV_CTRL6: + case RT5668_EQ_AUTO_RCV_CTRL7: + case RT5668_EQ_AUTO_RCV_CTRL8: + case RT5668_EQ_AUTO_RCV_CTRL9: + case RT5668_EQ_AUTO_RCV_CTRL10: + case RT5668_EQ_AUTO_RCV_CTRL11: + case RT5668_EQ_AUTO_RCV_CTRL12: + case RT5668_EQ_AUTO_RCV_CTRL13: + case RT5668_ADC_L_EQ_LPF1_A1: + case RT5668_R_EQ_LPF1_A1: + case RT5668_L_EQ_LPF1_H0: + case RT5668_R_EQ_LPF1_H0: + case RT5668_L_EQ_BPF1_A1: + case RT5668_R_EQ_BPF1_A1: + case RT5668_L_EQ_BPF1_A2: + case RT5668_R_EQ_BPF1_A2: + case RT5668_L_EQ_BPF1_H0: + case RT5668_R_EQ_BPF1_H0: + case RT5668_L_EQ_BPF2_A1: + case RT5668_R_EQ_BPF2_A1: + case RT5668_L_EQ_BPF2_A2: + case RT5668_R_EQ_BPF2_A2: + case RT5668_L_EQ_BPF2_H0: + case RT5668_R_EQ_BPF2_H0: + case RT5668_L_EQ_BPF3_A1: + case RT5668_R_EQ_BPF3_A1: + case RT5668_L_EQ_BPF3_A2: + case RT5668_R_EQ_BPF3_A2: + case RT5668_L_EQ_BPF3_H0: + case RT5668_R_EQ_BPF3_H0: + case RT5668_L_EQ_BPF4_A1: + case RT5668_R_EQ_BPF4_A1: + case RT5668_L_EQ_BPF4_A2: + case RT5668_R_EQ_BPF4_A2: + case RT5668_L_EQ_BPF4_H0: + case RT5668_R_EQ_BPF4_H0: + case RT5668_L_EQ_HPF1_A1: + case RT5668_R_EQ_HPF1_A1: + case RT5668_L_EQ_HPF1_H0: + case RT5668_R_EQ_HPF1_H0: + case RT5668_L_EQ_PRE_VOL: + case RT5668_R_EQ_PRE_VOL: + case RT5668_L_EQ_POST_VOL: + case RT5668_R_EQ_POST_VOL: + case RT5668_I2C_MODE: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* Interface data select */ +static const char * const rt5668_data_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5668_if2_adc_enum, + RT5668_DIG_INF2_DATA, RT5668_IF2_ADC_SEL_SFT, rt5668_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5668_if1_01_adc_enum, + RT5668_TDM_ADDA_CTRL_1, RT5668_IF1_ADC1_SEL_SFT, rt5668_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5668_if1_23_adc_enum, + RT5668_TDM_ADDA_CTRL_1, RT5668_IF1_ADC2_SEL_SFT, rt5668_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5668_if1_45_adc_enum, + RT5668_TDM_ADDA_CTRL_1, RT5668_IF1_ADC3_SEL_SFT, rt5668_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5668_if1_67_adc_enum, + RT5668_TDM_ADDA_CTRL_1, RT5668_IF1_ADC4_SEL_SFT, rt5668_data_select); + +static const struct snd_kcontrol_new rt5668_if2_adc_swap_mux = + SOC_DAPM_ENUM("IF2 ADC Swap Mux", rt5668_if2_adc_enum); + +static const struct snd_kcontrol_new rt5668_if1_01_adc_swap_mux = + SOC_DAPM_ENUM("IF1 01 ADC Swap Mux", rt5668_if1_01_adc_enum); + +static const struct snd_kcontrol_new rt5668_if1_23_adc_swap_mux = + SOC_DAPM_ENUM("IF1 23 ADC Swap Mux", rt5668_if1_23_adc_enum); + +static const struct snd_kcontrol_new rt5668_if1_45_adc_swap_mux = + SOC_DAPM_ENUM("IF1 45 ADC Swap Mux", rt5668_if1_45_adc_enum); + +static const struct snd_kcontrol_new rt5668_if1_67_adc_swap_mux = + SOC_DAPM_ENUM("IF1 67 ADC Swap Mux", rt5668_if1_67_adc_enum); + +static void rt5668_reset(struct regmap *regmap) +{ + regmap_write(regmap, RT5668_RESET, 0); + regmap_write(regmap, RT5668_I2C_MODE, 1); +} +/** + * rt5668_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5668 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the component driver will turn on + * ASRC for these filters if ASRC is selected as their clock source. + */ +int rt5668_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + + switch (clk_src) { + case RT5668_CLK_SEL_SYS: + case RT5668_CLK_SEL_I2S1_ASRC: + case RT5668_CLK_SEL_I2S2_ASRC: + break; + + default: + return -EINVAL; + } + + if (filter_mask & RT5668_DA_STEREO1_FILTER) { + snd_soc_component_update_bits(component, RT5668_PLL_TRACK_2, + RT5668_FILTER_CLK_SEL_MASK, + clk_src << RT5668_FILTER_CLK_SEL_SFT); + } + + if (filter_mask & RT5668_AD_STEREO1_FILTER) { + snd_soc_component_update_bits(component, RT5668_PLL_TRACK_3, + RT5668_FILTER_CLK_SEL_MASK, + clk_src << RT5668_FILTER_CLK_SEL_SFT); + } + + return 0; +} +EXPORT_SYMBOL_GPL(rt5668_sel_asrc_clk_src); + +static int rt5668_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read32(component, RT5668_4BTN_IL_CMD_1); + btn_type = val & 0xfff0; + snd_soc_component_write(component, RT5668_4BTN_IL_CMD_1, val); + pr_debug("%s btn_type=%x\n", __func__, btn_type); + + return btn_type; +} + +static void rt5668_enable_push_button_irq(struct snd_soc_component *component, + bool enable) +{ + if (enable) { + snd_soc_component_update_bits(component, RT5668_SAR_IL_CMD_1, + RT5668_SAR_BUTT_DET_MASK, RT5668_SAR_BUTT_DET_EN); + snd_soc_component_update_bits(component, RT5668_SAR_IL_CMD_13, + RT5668_SAR_SOUR_MASK, RT5668_SAR_SOUR_BTN); + snd_soc_component_write(component, RT5668_IL_CMD_1, 0x0040); + snd_soc_component_update_bits(component, RT5668_4BTN_IL_CMD_2, + RT5668_4BTN_IL_MASK | RT5668_4BTN_IL_RST_MASK, + RT5668_4BTN_IL_EN | RT5668_4BTN_IL_NOR); + snd_soc_component_update_bits(component, RT5668_IRQ_CTRL_3, + RT5668_IL_IRQ_MASK, RT5668_IL_IRQ_EN); + } else { + snd_soc_component_update_bits(component, RT5668_IRQ_CTRL_3, + RT5668_IL_IRQ_MASK, RT5668_IL_IRQ_DIS); + snd_soc_component_update_bits(component, RT5668_SAR_IL_CMD_1, + RT5668_SAR_BUTT_DET_MASK, RT5668_SAR_BUTT_DET_DIS); + snd_soc_component_update_bits(component, RT5668_4BTN_IL_CMD_2, + RT5668_4BTN_IL_MASK, RT5668_4BTN_IL_DIS); + snd_soc_component_update_bits(component, RT5668_4BTN_IL_CMD_2, + RT5668_4BTN_IL_RST_MASK, RT5668_4BTN_IL_RST); + snd_soc_component_update_bits(component, RT5668_SAR_IL_CMD_13, + RT5668_SAR_SOUR_MASK, RT5668_SAR_SOUR_TYPE); + } +} + +/** + * rt5668_headset_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ +static int rt5668_headset_detect(struct snd_soc_component *component, + int jack_insert) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + unsigned int val, count; + + if (jack_insert) { + snd_soc_dapm_force_enable_pin(dapm, "CBJ Power"); + snd_soc_dapm_sync(dapm); + snd_soc_component_update_bits(component, RT5668_CBJ_CTRL_1, + RT5668_TRIG_JD_MASK, RT5668_TRIG_JD_HIGH); + + count = 0; + val = snd_soc_component_read32(component, RT5668_CBJ_CTRL_2) + & RT5668_JACK_TYPE_MASK; + while (val == 0 && count < 50) { + usleep_range(10000, 15000); + val = snd_soc_component_read32(component, + RT5668_CBJ_CTRL_2) & RT5668_JACK_TYPE_MASK; + count++; + } + + switch (val) { + case 0x1: + case 0x2: + rt5668->jack_type = SND_JACK_HEADSET; + rt5668_enable_push_button_irq(component, true); + break; + default: + rt5668->jack_type = SND_JACK_HEADPHONE; + } + + } else { + rt5668_enable_push_button_irq(component, false); + snd_soc_component_update_bits(component, RT5668_CBJ_CTRL_1, + RT5668_TRIG_JD_MASK, RT5668_TRIG_JD_LOW); + snd_soc_dapm_disable_pin(dapm, "CBJ Power"); + snd_soc_dapm_sync(dapm); + + rt5668->jack_type = 0; + } + + dev_dbg(component->dev, "jack_type = %d\n", rt5668->jack_type); + return rt5668->jack_type; +} + +static irqreturn_t rt5668_irq(int irq, void *data) +{ + struct rt5668_priv *rt5668 = data; + + mod_delayed_work(system_power_efficient_wq, + &rt5668->jack_detect_work, msecs_to_jiffies(250)); + + return IRQ_HANDLED; +} + +static void rt5668_jd_check_handler(struct work_struct *work) +{ + struct rt5668_priv *rt5668 = container_of(work, struct rt5668_priv, + jd_check_work.work); + + if (snd_soc_component_read32(rt5668->component, RT5668_AJD1_CTRL) + & RT5668_JDH_RS_MASK) { + /* jack out */ + rt5668->jack_type = rt5668_headset_detect(rt5668->component, 0); + + snd_soc_jack_report(rt5668->hs_jack, rt5668->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + } else { + schedule_delayed_work(&rt5668->jd_check_work, 500); + } +} + +static int rt5668_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + + switch (rt5668->pdata.jd_src) { + case RT5668_JD1: + snd_soc_component_update_bits(component, RT5668_CBJ_CTRL_2, + RT5668_EXT_JD_SRC, RT5668_EXT_JD_SRC_MANUAL); + snd_soc_component_write(component, RT5668_CBJ_CTRL_1, 0xd002); + snd_soc_component_update_bits(component, RT5668_CBJ_CTRL_3, + RT5668_CBJ_IN_BUF_EN, RT5668_CBJ_IN_BUF_EN); + snd_soc_component_update_bits(component, RT5668_SAR_IL_CMD_1, + RT5668_SAR_POW_MASK, RT5668_SAR_POW_EN); + regmap_update_bits(rt5668->regmap, RT5668_GPIO_CTRL_1, + RT5668_GP1_PIN_MASK, RT5668_GP1_PIN_IRQ); + regmap_update_bits(rt5668->regmap, RT5668_RC_CLK_CTRL, + RT5668_POW_IRQ | RT5668_POW_JDH | + RT5668_POW_ANA, RT5668_POW_IRQ | + RT5668_POW_JDH | RT5668_POW_ANA); + regmap_update_bits(rt5668->regmap, RT5668_PWR_ANLG_2, + RT5668_PWR_JDH | RT5668_PWR_JDL, + RT5668_PWR_JDH | RT5668_PWR_JDL); + regmap_update_bits(rt5668->regmap, RT5668_IRQ_CTRL_2, + RT5668_JD1_EN_MASK | RT5668_JD1_POL_MASK, + RT5668_JD1_EN | RT5668_JD1_POL_NOR); + mod_delayed_work(system_power_efficient_wq, + &rt5668->jack_detect_work, msecs_to_jiffies(250)); + break; + + case RT5668_JD_NULL: + regmap_update_bits(rt5668->regmap, RT5668_IRQ_CTRL_2, + RT5668_JD1_EN_MASK, RT5668_JD1_DIS); + regmap_update_bits(rt5668->regmap, RT5668_RC_CLK_CTRL, + RT5668_POW_JDH | RT5668_POW_JDL, 0); + break; + + default: + dev_warn(component->dev, "Wrong JD source\n"); + break; + } + + rt5668->hs_jack = hs_jack; + + return 0; +} + +static void rt5668_jack_detect_handler(struct work_struct *work) +{ + struct rt5668_priv *rt5668 = + container_of(work, struct rt5668_priv, jack_detect_work.work); + int val, btn_type; + + if (!rt5668->component || !rt5668->component->card || + !rt5668->component->card->instantiated) { + /* card not yet ready, try later */ + mod_delayed_work(system_power_efficient_wq, + &rt5668->jack_detect_work, msecs_to_jiffies(15)); + return; + } + + mutex_lock(&rt5668->calibrate_mutex); + + val = snd_soc_component_read32(rt5668->component, RT5668_AJD1_CTRL) + & RT5668_JDH_RS_MASK; + if (!val) { + /* jack in */ + if (rt5668->jack_type == 0) { + /* jack was out, report jack type */ + rt5668->jack_type = + rt5668_headset_detect(rt5668->component, 1); + } else { + /* jack is already in, report button event */ + rt5668->jack_type = SND_JACK_HEADSET; + btn_type = rt5668_button_detect(rt5668->component); + /** + * rt5668 can report three kinds of button behavior, + * one click, double click and hold. However, + * currently we will report button pressed/released + * event. So all the three button behaviors are + * treated as button pressed. + */ + switch (btn_type) { + case 0x8000: + case 0x4000: + case 0x2000: + rt5668->jack_type |= SND_JACK_BTN_0; + break; + case 0x1000: + case 0x0800: + case 0x0400: + rt5668->jack_type |= SND_JACK_BTN_1; + break; + case 0x0200: + case 0x0100: + case 0x0080: + rt5668->jack_type |= SND_JACK_BTN_2; + break; + case 0x0040: + case 0x0020: + case 0x0010: + rt5668->jack_type |= SND_JACK_BTN_3; + break; + case 0x0000: /* unpressed */ + break; + default: + btn_type = 0; + dev_err(rt5668->component->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + } + } else { + /* jack out */ + rt5668->jack_type = rt5668_headset_detect(rt5668->component, 0); + } + + snd_soc_jack_report(rt5668->hs_jack, rt5668->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (rt5668->jack_type & (SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3)) + schedule_delayed_work(&rt5668->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5668->jd_check_work); + + mutex_unlock(&rt5668->calibrate_mutex); +} + +static const struct snd_kcontrol_new rt5668_snd_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE_R_TLV("Headphone Playback Volume", RT5668_HPL_GAIN, + RT5668_HPR_GAIN, RT5668_G_HP_SFT, 15, 1, hp_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5668_DAC1_DIG_VOL, + RT5668_L_VOL_SFT, RT5668_R_VOL_SFT, 175, 0, dac_vol_tlv), + + /* IN Boost Volume */ + SOC_SINGLE_TLV("CBJ Boost Volume", RT5668_CBJ_BST_CTRL, + RT5668_BST_CBJ_SFT, 8, 0, bst_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("STO1 ADC Capture Switch", RT5668_STO1_ADC_DIG_VOL, + RT5668_L_MUTE_SFT, RT5668_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("STO1 ADC Capture Volume", RT5668_STO1_ADC_DIG_VOL, + RT5668_L_VOL_SFT, RT5668_R_VOL_SFT, 127, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Gain Volume", RT5668_STO1_ADC_BOOST, + RT5668_STO1_ADC_L_BST_SFT, RT5668_STO1_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), +}; + + +static int rt5668_div_sel(struct rt5668_priv *rt5668, + int target, const int div[], int size) +{ + int i; + + if (rt5668->sysclk < target) { + pr_err("sysclk rate %d is too low\n", + rt5668->sysclk); + return 0; + } + + for (i = 0; i < size - 1; i++) { + pr_info("div[%d]=%d\n", i, div[i]); + if (target * div[i] == rt5668->sysclk) + return i; + if (target * div[i + 1] > rt5668->sysclk) { + pr_err("can't find div for sysclk %d\n", + rt5668->sysclk); + return i; + } + } + + if (target * div[i] < rt5668->sysclk) + pr_err("sysclk rate %d is too high\n", + rt5668->sysclk); + + return size - 1; + +} + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + * Choose dmic clock between 1MHz and 3MHz. + * It is better for clock to approximate 3MHz. + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + int idx = -EINVAL; + static const int div[] = {2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128}; + + idx = rt5668_div_sel(rt5668, 1500000, div, ARRAY_SIZE(div)); + + snd_soc_component_update_bits(component, RT5668_DMIC_CTRL_1, + RT5668_DMIC_CLK_MASK, idx << RT5668_DMIC_CLK_SFT); + + return 0; +} + +static int set_filter_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + int ref, val, reg, idx = -EINVAL; + static const int div[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48}; + + val = snd_soc_component_read32(component, RT5668_GPIO_CTRL_1) & + RT5668_GP4_PIN_MASK; + if (w->shift == RT5668_PWR_ADC_S1F_BIT && + val == RT5668_GP4_PIN_ADCDAT2) + ref = 256 * rt5668->lrck[RT5668_AIF2]; + else + ref = 256 * rt5668->lrck[RT5668_AIF1]; + + idx = rt5668_div_sel(rt5668, ref, div, ARRAY_SIZE(div)); + + if (w->shift == RT5668_PWR_ADC_S1F_BIT) + reg = RT5668_PLL_TRACK_3; + else + reg = RT5668_PLL_TRACK_2; + + snd_soc_component_update_bits(component, reg, + RT5668_FILTER_CLK_SEL_MASK, idx << RT5668_FILTER_CLK_SEL_SFT); + + return 0; +} + +static int is_sys_clk_from_pll1(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int val; + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + val = snd_soc_component_read32(component, RT5668_GLB_CLK); + val &= RT5668_SCLK_SRC_MASK; + if (val == RT5668_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int reg, shift, val; + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (w->shift) { + case RT5668_ADC_STO1_ASRC_SFT: + reg = RT5668_PLL_TRACK_3; + shift = RT5668_FILTER_CLK_SEL_SFT; + break; + case RT5668_DAC_STO1_ASRC_SFT: + reg = RT5668_PLL_TRACK_2; + shift = RT5668_FILTER_CLK_SEL_SFT; + break; + default: + return 0; + } + + val = (snd_soc_component_read32(component, reg) >> shift) & 0xf; + switch (val) { + case RT5668_CLK_SEL_I2S1_ASRC: + case RT5668_CLK_SEL_I2S2_ASRC: + return 1; + default: + return 0; + } + +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5668_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5668_STO1_ADC_MIXER, + RT5668_M_STO1_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5668_STO1_ADC_MIXER, + RT5668_M_STO1_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5668_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5668_STO1_ADC_MIXER, + RT5668_M_STO1_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5668_STO1_ADC_MIXER, + RT5668_M_STO1_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5668_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5668_AD_DA_MIXER, + RT5668_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5668_AD_DA_MIXER, + RT5668_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5668_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5668_AD_DA_MIXER, + RT5668_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5668_AD_DA_MIXER, + RT5668_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5668_sto1_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5668_STO1_DAC_MIXER, + RT5668_M_DAC_L1_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5668_STO1_DAC_MIXER, + RT5668_M_DAC_R1_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5668_sto1_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5668_STO1_DAC_MIXER, + RT5668_M_DAC_L1_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5668_STO1_DAC_MIXER, + RT5668_M_DAC_R1_STO_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5668_rec1_l_mix[] = { + SOC_DAPM_SINGLE("CBJ Switch", RT5668_REC_MIXER, + RT5668_M_CBJ_RM1_L_SFT, 1, 1), +}; + +/* STO1 ADC1 Source */ +/* MX-26 [13] [5] */ +static const char * const rt5668_sto1_adc1_src[] = { + "DAC MIX", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5668_sto1_adc1l_enum, RT5668_STO1_ADC_MIXER, + RT5668_STO1_ADC1L_SRC_SFT, rt5668_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5668_sto1_adc1l_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5668_sto1_adc1l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5668_sto1_adc1r_enum, RT5668_STO1_ADC_MIXER, + RT5668_STO1_ADC1R_SRC_SFT, rt5668_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5668_sto1_adc1r_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5668_sto1_adc1r_enum); + +/* STO1 ADC Source */ +/* MX-26 [11:10] [3:2] */ +static const char * const rt5668_sto1_adc_src[] = { + "ADC1 L", "ADC1 R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5668_sto1_adcl_enum, RT5668_STO1_ADC_MIXER, + RT5668_STO1_ADCL_SRC_SFT, rt5668_sto1_adc_src); + +static const struct snd_kcontrol_new rt5668_sto1_adcl_mux = + SOC_DAPM_ENUM("Stereo1 ADCL Source", rt5668_sto1_adcl_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5668_sto1_adcr_enum, RT5668_STO1_ADC_MIXER, + RT5668_STO1_ADCR_SRC_SFT, rt5668_sto1_adc_src); + +static const struct snd_kcontrol_new rt5668_sto1_adcr_mux = + SOC_DAPM_ENUM("Stereo1 ADCR Source", rt5668_sto1_adcr_enum); + +/* STO1 ADC2 Source */ +/* MX-26 [12] [4] */ +static const char * const rt5668_sto1_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5668_sto1_adc2l_enum, RT5668_STO1_ADC_MIXER, + RT5668_STO1_ADC2L_SRC_SFT, rt5668_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5668_sto1_adc2l_mux = + SOC_DAPM_ENUM("Stereo1 ADC2L Source", rt5668_sto1_adc2l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5668_sto1_adc2r_enum, RT5668_STO1_ADC_MIXER, + RT5668_STO1_ADC2R_SRC_SFT, rt5668_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5668_sto1_adc2r_mux = + SOC_DAPM_ENUM("Stereo1 ADC2R Source", rt5668_sto1_adc2r_enum); + +/* MX-79 [6:4] I2S1 ADC data location */ +static const unsigned int rt5668_if1_adc_slot_values[] = { + 0, + 2, + 4, + 6, +}; + +static const char * const rt5668_if1_adc_slot_src[] = { + "Slot 0", "Slot 2", "Slot 4", "Slot 6" +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5668_if1_adc_slot_enum, + RT5668_TDM_CTRL, RT5668_TDM_ADC_LCA_SFT, RT5668_TDM_ADC_LCA_MASK, + rt5668_if1_adc_slot_src, rt5668_if1_adc_slot_values); + +static const struct snd_kcontrol_new rt5668_if1_adc_slot_mux = + SOC_DAPM_ENUM("IF1 ADC Slot location", rt5668_if1_adc_slot_enum); + +/* Analog DAC L1 Source, Analog DAC R1 Source*/ +/* MX-2B [4], MX-2B [0]*/ +static const char * const rt5668_alg_dac1_src[] = { + "Stereo1 DAC Mixer", "DAC1" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5668_alg_dac_l1_enum, RT5668_A_DAC1_MUX, + RT5668_A_DACL1_SFT, rt5668_alg_dac1_src); + +static const struct snd_kcontrol_new rt5668_alg_dac_l1_mux = + SOC_DAPM_ENUM("Analog DAC L1 Source", rt5668_alg_dac_l1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5668_alg_dac_r1_enum, RT5668_A_DAC1_MUX, + RT5668_A_DACR1_SFT, rt5668_alg_dac1_src); + +static const struct snd_kcontrol_new rt5668_alg_dac_r1_mux = + SOC_DAPM_ENUM("Analog DAC R1 Source", rt5668_alg_dac_r1_enum); + +/* Out Switch */ +static const struct snd_kcontrol_new hpol_switch = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5668_HP_CTRL_1, + RT5668_L_MUTE_SFT, 1, 1); +static const struct snd_kcontrol_new hpor_switch = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5668_HP_CTRL_1, + RT5668_R_MUTE_SFT, 1, 1); + +static int rt5668_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_write(component, + RT5668_HP_LOGIC_CTRL_2, 0x0012); + snd_soc_component_write(component, + RT5668_HP_CTRL_2, 0x6000); + snd_soc_component_update_bits(component, RT5668_STO_NG2_CTRL_1, + RT5668_NG2_EN_MASK, RT5668_NG2_EN); + snd_soc_component_update_bits(component, + RT5668_DEPOP_1, 0x60, 0x60); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, + RT5668_DEPOP_1, 0x60, 0x0); + snd_soc_component_write(component, + RT5668_HP_CTRL_2, 0x0000); + break; + + default: + return 0; + } + + return 0; + +} + +static int set_dmic_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /*Add delay to avoid pop noise*/ + msleep(150); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5655_set_verf(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (w->shift) { + case RT5668_PWR_VREF1_BIT: + snd_soc_component_update_bits(component, + RT5668_PWR_ANLG_1, RT5668_PWR_FV1, 0); + break; + + case RT5668_PWR_VREF2_BIT: + snd_soc_component_update_bits(component, + RT5668_PWR_ANLG_1, RT5668_PWR_FV2, 0); + break; + + default: + break; + } + break; + + case SND_SOC_DAPM_POST_PMU: + usleep_range(15000, 20000); + switch (w->shift) { + case RT5668_PWR_VREF1_BIT: + snd_soc_component_update_bits(component, + RT5668_PWR_ANLG_1, RT5668_PWR_FV1, + RT5668_PWR_FV1); + break; + + case RT5668_PWR_VREF2_BIT: + snd_soc_component_update_bits(component, + RT5668_PWR_ANLG_1, RT5668_PWR_FV2, + RT5668_PWR_FV2); + break; + + default: + break; + } + break; + + default: + return 0; + } + + return 0; +} + +static const unsigned int rt5668_adcdat_pin_values[] = { + 1, + 3, +}; + +static const char * const rt5668_adcdat_pin_select[] = { + "ADCDAT1", + "ADCDAT2", +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5668_adcdat_pin_enum, + RT5668_GPIO_CTRL_1, RT5668_GP4_PIN_SFT, RT5668_GP4_PIN_MASK, + rt5668_adcdat_pin_select, rt5668_adcdat_pin_values); + +static const struct snd_kcontrol_new rt5668_adcdat_pin_ctrl = + SOC_DAPM_ENUM("ADCDAT", rt5668_adcdat_pin_enum); + +static const struct snd_soc_dapm_widget rt5668_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO2", RT5668_PWR_ANLG_3, RT5668_PWR_LDO2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", RT5668_PWR_ANLG_3, RT5668_PWR_PLL_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2B", RT5668_PWR_ANLG_3, RT5668_PWR_PLL2B_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2F", RT5668_PWR_ANLG_3, RT5668_PWR_PLL2F_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Vref1", RT5668_PWR_ANLG_1, RT5668_PWR_VREF1_BIT, 0, + rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("Vref2", RT5668_PWR_ANLG_1, RT5668_PWR_VREF2_BIT, 0, + rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("DAC STO1 ASRC", 1, RT5668_PLL_TRACK_1, + RT5668_DAC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5668_PLL_TRACK_1, + RT5668_ADC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("AD ASRC", 1, RT5668_PLL_TRACK_1, + RT5668_AD_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DA ASRC", 1, RT5668_PLL_TRACK_1, + RT5668_DA_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC ASRC", 1, RT5668_PLL_TRACK_1, + RT5668_DMIC_ASRC_SFT, 0, NULL, 0), + + /* Input Side */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5668_PWR_ANLG_2, RT5668_PWR_MB1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", RT5668_PWR_ANLG_2, RT5668_PWR_MB2_BIT, + 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + + SND_SOC_DAPM_INPUT("IN1P"), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5668_DMIC_CTRL_1, + RT5668_DMIC_1_EN_SFT, 0, set_dmic_power, SND_SOC_DAPM_POST_PMU), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1 CBJ", SND_SOC_NOPM, + 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("CBJ Power", RT5668_PWR_ANLG_3, + RT5668_PWR_CBJ_BIT, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX1L", SND_SOC_NOPM, 0, 0, rt5668_rec1_l_mix, + ARRAY_SIZE(rt5668_rec1_l_mix)), + SND_SOC_DAPM_SUPPLY("RECMIX1L Power", RT5668_PWR_ANLG_2, + RT5668_PWR_RM1_L_BIT, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC1 L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC1 R", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("ADC1 L Power", RT5668_PWR_DIG_1, + RT5668_PWR_ADC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 R Power", RT5668_PWR_DIG_1, + RT5668_PWR_ADC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 clock", RT5668_CHOP_ADC, + RT5668_CKGEN_ADC1_SFT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5668_sto1_adc1l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5668_sto1_adc1r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5668_sto1_adc2l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5668_sto1_adc2r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5668_sto1_adcl_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5668_sto1_adcr_mux), + SND_SOC_DAPM_MUX("IF1_ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5668_if1_adc_slot_mux), + + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("ADC Stereo1 Filter", RT5668_PWR_DIG_2, + RT5668_PWR_ADC_S1F_BIT, 0, set_filter_clk, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", RT5668_STO1_ADC_DIG_VOL, + RT5668_L_MUTE_SFT, 1, rt5668_sto1_adc_l_mix, + ARRAY_SIZE(rt5668_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXR", RT5668_STO1_ADC_DIG_VOL, + RT5668_R_MUTE_SFT, 1, rt5668_sto1_adc_r_mix, + ARRAY_SIZE(rt5668_sto1_adc_r_mix)), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5668_PWR_DIG_1, RT5668_PWR_I2S1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2", RT5668_PWR_DIG_1, RT5668_PWR_I2S2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("IF1 01 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5668_if1_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 23 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5668_if1_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 45 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5668_if1_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 67 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5668_if1_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5668_if2_adc_swap_mux), + + SND_SOC_DAPM_MUX("ADCDAT Mux", SND_SOC_NOPM, 0, 0, + &rt5668_adcdat_pin_ctrl), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, + RT5668_I2S1_SDP, RT5668_SEL_ADCDAT_SFT, 1), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, + RT5668_I2S2_SDP, RT5668_I2S2_PIN_CFG_SFT, 1), + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5668_dac_l_mix, ARRAY_SIZE(rt5668_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5668_dac_r_mix, ARRAY_SIZE(rt5668_dac_r_mix)), + + /* DAC channel Mux */ + SND_SOC_DAPM_MUX("DAC L1 Source", SND_SOC_NOPM, 0, 0, + &rt5668_alg_dac_l1_mux), + SND_SOC_DAPM_MUX("DAC R1 Source", SND_SOC_NOPM, 0, 0, + &rt5668_alg_dac_r1_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("DAC Stereo1 Filter", RT5668_PWR_DIG_2, + RT5668_PWR_DAC_S1F_BIT, 0, set_filter_clk, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5668_sto1_dac_l_mix, ARRAY_SIZE(rt5668_sto1_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5668_sto1_dac_r_mix, ARRAY_SIZE(rt5668_sto1_dac_r_mix)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, RT5668_PWR_DIG_1, + RT5668_PWR_DAC_L1_BIT, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, RT5668_PWR_DIG_1, + RT5668_PWR_DAC_R1_BIT, 0), + SND_SOC_DAPM_SUPPLY_S("DAC 1 Clock", 3, RT5668_CHOP_DAC, + RT5668_CKGEN_DAC1_SFT, 0, NULL, 0), + + /* HPO */ + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, rt5668_hp_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_SUPPLY("HP Amp L", RT5668_PWR_ANLG_1, + RT5668_PWR_HA_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HP Amp R", RT5668_PWR_ANLG_1, + RT5668_PWR_HA_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("Charge Pump", 1, RT5668_DEPOP_1, + RT5668_PUMP_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("Capless", 2, RT5668_DEPOP_1, + RT5668_CAPLESS_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("HPOL Playback", SND_SOC_NOPM, 0, 0, + &hpol_switch), + SND_SOC_DAPM_SWITCH("HPOR Playback", SND_SOC_NOPM, 0, 0, + &hpor_switch), + + /* CLK DET */ + SND_SOC_DAPM_SUPPLY("CLKDET SYS", RT5668_CLK_DET, + RT5668_SYS_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET PLL1", RT5668_CLK_DET, + RT5668_PLL1_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET PLL2", RT5668_CLK_DET, + RT5668_PLL2_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET", RT5668_CLK_DET, + RT5668_POW_CLK_DET_SFT, 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + +}; + +static const struct snd_soc_dapm_route rt5668_dapm_routes[] = { + /*PLL*/ + {"ADC Stereo1 Filter", NULL, "PLL1", is_sys_clk_from_pll1}, + {"DAC Stereo1 Filter", NULL, "PLL1", is_sys_clk_from_pll1}, + + /*ASRC*/ + {"ADC Stereo1 Filter", NULL, "ADC STO1 ASRC", is_using_asrc}, + {"DAC Stereo1 Filter", NULL, "DAC STO1 ASRC", is_using_asrc}, + {"ADC STO1 ASRC", NULL, "AD ASRC"}, + {"DAC STO1 ASRC", NULL, "DA ASRC"}, + + /*Vref*/ + {"MICBIAS1", NULL, "Vref1"}, + {"MICBIAS1", NULL, "Vref2"}, + {"MICBIAS2", NULL, "Vref1"}, + {"MICBIAS2", NULL, "Vref2"}, + + {"CLKDET SYS", NULL, "CLKDET"}, + + {"IN1P", NULL, "LDO2"}, + + {"BST1 CBJ", NULL, "IN1P"}, + {"BST1 CBJ", NULL, "CBJ Power"}, + {"CBJ Power", NULL, "Vref2"}, + + {"RECMIX1L", "CBJ Switch", "BST1 CBJ"}, + {"RECMIX1L", NULL, "RECMIX1L Power"}, + + {"ADC1 L", NULL, "RECMIX1L"}, + {"ADC1 L", NULL, "ADC1 L Power"}, + {"ADC1 L", NULL, "ADC1 clock"}, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC L1", NULL, "DMIC1 Power"}, + {"DMIC R1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC1 Power"}, + {"DMIC CLK", NULL, "DMIC ASRC"}, + + {"Stereo1 ADC L Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC L Mux", "ADC1 R", "ADC1 R"}, + {"Stereo1 ADC R Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC R Mux", "ADC1 R", "ADC1 R"}, + + {"Stereo1 ADC L1 Mux", "ADC", "Stereo1 ADC L Mux"}, + {"Stereo1 ADC L1 Mux", "DAC MIX", "Stereo1 DAC MIXL"}, + {"Stereo1 ADC L2 Mux", "DMIC", "DMIC L1"}, + {"Stereo1 ADC L2 Mux", "DAC MIX", "Stereo1 DAC MIXL"}, + + {"Stereo1 ADC R1 Mux", "ADC", "Stereo1 ADC R Mux"}, + {"Stereo1 ADC R1 Mux", "DAC MIX", "Stereo1 DAC MIXR"}, + {"Stereo1 ADC R2 Mux", "DMIC", "DMIC R1"}, + {"Stereo1 ADC R2 Mux", "DAC MIX", "Stereo1 DAC MIXR"}, + + {"Stereo1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux"}, + {"Stereo1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux"}, + {"Stereo1 ADC MIXL", NULL, "ADC Stereo1 Filter"}, + + {"Stereo1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux"}, + {"Stereo1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux"}, + {"Stereo1 ADC MIXR", NULL, "ADC Stereo1 Filter"}, + + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXL"}, + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXR"}, + + {"IF1 01 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + + {"IF1_ADC Mux", "Slot 0", "IF1 01 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 2", "IF1 23 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 4", "IF1 45 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 6", "IF1 67 ADC Swap Mux"}, + {"IF1_ADC Mux", NULL, "I2S1"}, + {"ADCDAT Mux", "ADCDAT1", "IF1_ADC Mux"}, + {"AIF1TX", NULL, "ADCDAT Mux"}, + {"IF2 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"ADCDAT Mux", "ADCDAT2", "IF2 ADC Swap Mux"}, + {"AIF2TX", NULL, "ADCDAT Mux"}, + + {"IF1 DAC1 L", NULL, "AIF1RX"}, + {"IF1 DAC1 L", NULL, "I2S1"}, + {"IF1 DAC1 L", NULL, "DAC Stereo1 Filter"}, + {"IF1 DAC1 R", NULL, "AIF1RX"}, + {"IF1 DAC1 R", NULL, "I2S1"}, + {"IF1 DAC1 R", NULL, "DAC Stereo1 Filter"}, + + {"DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL"}, + {"DAC1 MIXL", "DAC1 Switch", "IF1 DAC1 L"}, + {"DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR"}, + {"DAC1 MIXR", "DAC1 Switch", "IF1 DAC1 R"}, + + {"Stereo1 DAC MIXL", "DAC L1 Switch", "DAC1 MIXL"}, + {"Stereo1 DAC MIXL", "DAC R1 Switch", "DAC1 MIXR"}, + + {"Stereo1 DAC MIXR", "DAC R1 Switch", "DAC1 MIXR"}, + {"Stereo1 DAC MIXR", "DAC L1 Switch", "DAC1 MIXL"}, + + {"DAC L1 Source", "DAC1", "DAC1 MIXL"}, + {"DAC L1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXL"}, + {"DAC R1 Source", "DAC1", "DAC1 MIXR"}, + {"DAC R1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXR"}, + + {"DAC L1", NULL, "DAC L1 Source"}, + {"DAC R1", NULL, "DAC R1 Source"}, + + {"DAC L1", NULL, "DAC 1 Clock"}, + {"DAC R1", NULL, "DAC 1 Clock"}, + + {"HP Amp", NULL, "DAC L1"}, + {"HP Amp", NULL, "DAC R1"}, + {"HP Amp", NULL, "HP Amp L"}, + {"HP Amp", NULL, "HP Amp R"}, + {"HP Amp", NULL, "Capless"}, + {"HP Amp", NULL, "Charge Pump"}, + {"HP Amp", NULL, "CLKDET SYS"}, + {"HP Amp", NULL, "CBJ Power"}, + {"HP Amp", NULL, "Vref2"}, + {"HPOL Playback", "Switch", "HP Amp"}, + {"HPOR Playback", "Switch", "HP Amp"}, + {"HPOL", NULL, "HPOL Playback"}, + {"HPOR", NULL, "HPOR Playback"}, +}; + +static int rt5668_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val = 0; + + switch (slots) { + case 4: + val |= RT5668_TDM_TX_CH_4; + val |= RT5668_TDM_RX_CH_4; + break; + case 6: + val |= RT5668_TDM_TX_CH_6; + val |= RT5668_TDM_RX_CH_6; + break; + case 8: + val |= RT5668_TDM_TX_CH_8; + val |= RT5668_TDM_RX_CH_8; + break; + case 2: + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5668_TDM_CTRL, + RT5668_TDM_TX_CH_MASK | RT5668_TDM_RX_CH_MASK, val); + + switch (slot_width) { + case 16: + val = RT5668_TDM_CL_16; + break; + case 20: + val = RT5668_TDM_CL_20; + break; + case 24: + val = RT5668_TDM_CL_24; + break; + case 32: + val = RT5668_TDM_CL_32; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5668_TDM_TCON_CTRL, + RT5668_TDM_CL_MASK, val); + + return 0; +} + + +static int rt5668_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + unsigned int len_1 = 0, len_2 = 0; + int pre_div, frame_size; + + rt5668->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5668->sysclk, rt5668->lrck[dai->id]); + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", + frame_size); + return -EINVAL; + } + + dev_dbg(dai->dev, "lrck is %dHz and pre_div is %d for iis %d\n", + rt5668->lrck[dai->id], pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + len_1 |= RT5668_I2S1_DL_20; + len_2 |= RT5668_I2S2_DL_20; + break; + case 24: + len_1 |= RT5668_I2S1_DL_24; + len_2 |= RT5668_I2S2_DL_24; + break; + case 32: + len_1 |= RT5668_I2S1_DL_32; + len_2 |= RT5668_I2S2_DL_24; + break; + case 8: + len_1 |= RT5668_I2S2_DL_8; + len_2 |= RT5668_I2S2_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5668_AIF1: + snd_soc_component_update_bits(component, RT5668_I2S1_SDP, + RT5668_I2S1_DL_MASK, len_1); + if (rt5668->master[RT5668_AIF1]) { + snd_soc_component_update_bits(component, + RT5668_ADDA_CLK_1, RT5668_I2S_M_DIV_MASK, + pre_div << RT5668_I2S_M_DIV_SFT); + } + if (params_channels(params) == 1) /* mono mode */ + snd_soc_component_update_bits(component, + RT5668_I2S1_SDP, RT5668_I2S1_MONO_MASK, + RT5668_I2S1_MONO_EN); + else + snd_soc_component_update_bits(component, + RT5668_I2S1_SDP, RT5668_I2S1_MONO_MASK, + RT5668_I2S1_MONO_DIS); + break; + case RT5668_AIF2: + snd_soc_component_update_bits(component, RT5668_I2S2_SDP, + RT5668_I2S2_DL_MASK, len_2); + if (rt5668->master[RT5668_AIF2]) { + snd_soc_component_update_bits(component, + RT5668_I2S_M_CLK_CTRL_1, RT5668_I2S2_M_PD_MASK, + pre_div << RT5668_I2S2_M_PD_SFT); + } + if (params_channels(params) == 1) /* mono mode */ + snd_soc_component_update_bits(component, + RT5668_I2S2_SDP, RT5668_I2S2_MONO_MASK, + RT5668_I2S2_MONO_EN); + else + snd_soc_component_update_bits(component, + RT5668_I2S2_SDP, RT5668_I2S2_MONO_MASK, + RT5668_I2S2_MONO_DIS); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5668_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, tdm_ctrl = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5668->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + rt5668->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5668_I2S_BP_INV; + tdm_ctrl |= RT5668_TDM_S_BP_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + if (dai->id == RT5668_AIF1) + tdm_ctrl |= RT5668_TDM_S_LP_INV | RT5668_TDM_M_BP_INV; + else + return -EINVAL; + break; + case SND_SOC_DAIFMT_IB_IF: + if (dai->id == RT5668_AIF1) + tdm_ctrl |= RT5668_TDM_S_BP_INV | RT5668_TDM_S_LP_INV | + RT5668_TDM_M_BP_INV | RT5668_TDM_M_LP_INV; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5668_I2S_DF_LEFT; + tdm_ctrl |= RT5668_TDM_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5668_I2S_DF_PCM_A; + tdm_ctrl |= RT5668_TDM_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5668_I2S_DF_PCM_B; + tdm_ctrl |= RT5668_TDM_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5668_AIF1: + snd_soc_component_update_bits(component, RT5668_I2S1_SDP, + RT5668_I2S_DF_MASK, reg_val); + snd_soc_component_update_bits(component, RT5668_TDM_TCON_CTRL, + RT5668_TDM_MS_MASK | RT5668_TDM_S_BP_MASK | + RT5668_TDM_DF_MASK | RT5668_TDM_M_BP_MASK | + RT5668_TDM_M_LP_MASK | RT5668_TDM_S_LP_MASK, + tdm_ctrl | rt5668->master[dai->id]); + break; + case RT5668_AIF2: + if (rt5668->master[dai->id] == 0) + reg_val |= RT5668_I2S2_MS_S; + snd_soc_component_update_bits(component, RT5668_I2S2_SDP, + RT5668_I2S2_MS_MASK | RT5668_I2S_BP_MASK | + RT5668_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5668_set_component_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, src = 0; + + if (freq == rt5668->sysclk && clk_id == rt5668->sysclk_src) + return 0; + + switch (clk_id) { + case RT5668_SCLK_S_MCLK: + reg_val |= RT5668_SCLK_SRC_MCLK; + src = RT5668_CLK_SRC_MCLK; + break; + case RT5668_SCLK_S_PLL1: + reg_val |= RT5668_SCLK_SRC_PLL1; + src = RT5668_CLK_SRC_PLL1; + break; + case RT5668_SCLK_S_PLL2: + reg_val |= RT5668_SCLK_SRC_PLL2; + src = RT5668_CLK_SRC_PLL2; + break; + case RT5668_SCLK_S_RCCLK: + reg_val |= RT5668_SCLK_SRC_RCCLK; + src = RT5668_CLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5668_GLB_CLK, + RT5668_SCLK_SRC_MASK, reg_val); + + if (rt5668->master[RT5668_AIF2]) { + snd_soc_component_update_bits(component, + RT5668_I2S_M_CLK_CTRL_1, RT5668_I2S2_SRC_MASK, + src << RT5668_I2S2_SRC_SFT); + } + + rt5668->sysclk = freq; + rt5668->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; +} + +static int rt5668_set_component_pll(struct snd_soc_component *component, + int pll_id, int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5668->pll_src && freq_in == rt5668->pll_in && + freq_out == rt5668->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5668->pll_in = 0; + rt5668->pll_out = 0; + snd_soc_component_update_bits(component, RT5668_GLB_CLK, + RT5668_SCLK_SRC_MASK, RT5668_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5668_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5668_GLB_CLK, + RT5668_PLL1_SRC_MASK, RT5668_PLL1_SRC_MCLK); + break; + case RT5668_PLL1_S_BCLK1: + snd_soc_component_update_bits(component, RT5668_GLB_CLK, + RT5668_PLL1_SRC_MASK, RT5668_PLL1_SRC_BCLK1); + break; + default: + dev_err(component->dev, "Unknown PLL Source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5668_PLL_CTRL_1, + pll_code.n_code << RT5668_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5668_PLL_CTRL_2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5668_PLL_M_SFT | + pll_code.m_bp << RT5668_PLL_M_BP_SFT); + + rt5668->pll_in = freq_in; + rt5668->pll_out = freq_out; + rt5668->pll_src = source; + + return 0; +} + +static int rt5668_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + + rt5668->bclk[dai->id] = ratio; + + switch (ratio) { + case 64: + snd_soc_component_update_bits(component, RT5668_ADDA_CLK_2, + RT5668_I2S2_BCLK_MS2_MASK, + RT5668_I2S2_BCLK_MS2_64); + break; + case 32: + snd_soc_component_update_bits(component, RT5668_ADDA_CLK_2, + RT5668_I2S2_BCLK_MS2_MASK, + RT5668_I2S2_BCLK_MS2_32); + break; + default: + dev_err(dai->dev, "Invalid bclk ratio %d\n", ratio); + return -EINVAL; + } + + return 0; +} + +static int rt5668_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(rt5668->regmap, RT5668_PWR_ANLG_1, + RT5668_PWR_MB | RT5668_PWR_BG, + RT5668_PWR_MB | RT5668_PWR_BG); + regmap_update_bits(rt5668->regmap, RT5668_PWR_DIG_1, + RT5668_DIG_GATE_CTRL | RT5668_PWR_LDO, + RT5668_DIG_GATE_CTRL | RT5668_PWR_LDO); + break; + + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(rt5668->regmap, RT5668_PWR_ANLG_1, + RT5668_PWR_MB, RT5668_PWR_MB); + regmap_update_bits(rt5668->regmap, RT5668_PWR_DIG_1, + RT5668_DIG_GATE_CTRL, RT5668_DIG_GATE_CTRL); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(rt5668->regmap, RT5668_PWR_DIG_1, + RT5668_DIG_GATE_CTRL | RT5668_PWR_LDO, 0); + regmap_update_bits(rt5668->regmap, RT5668_PWR_ANLG_1, + RT5668_PWR_MB | RT5668_PWR_BG, 0); + break; + + default: + break; + } + + return 0; +} + +static int rt5668_probe(struct snd_soc_component *component) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + + rt5668->component = component; + + return 0; +} + +static void rt5668_remove(struct snd_soc_component *component) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + + rt5668_reset(rt5668->regmap); +} + +#ifdef CONFIG_PM +static int rt5668_suspend(struct snd_soc_component *component) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5668->regmap, true); + regcache_mark_dirty(rt5668->regmap); + return 0; +} + +static int rt5668_resume(struct snd_soc_component *component) +{ + struct rt5668_priv *rt5668 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5668->regmap, false); + regcache_sync(rt5668->regmap); + + return 0; +} +#else +#define rt5668_suspend NULL +#define rt5668_resume NULL +#endif + +#define RT5668_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5668_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5668_aif1_dai_ops = { + .hw_params = rt5668_hw_params, + .set_fmt = rt5668_set_dai_fmt, + .set_tdm_slot = rt5668_set_tdm_slot, +}; + +static const struct snd_soc_dai_ops rt5668_aif2_dai_ops = { + .hw_params = rt5668_hw_params, + .set_fmt = rt5668_set_dai_fmt, + .set_bclk_ratio = rt5668_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt5668_dai[] = { + { + .name = "rt5668-aif1", + .id = RT5668_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5668_STEREO_RATES, + .formats = RT5668_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5668_STEREO_RATES, + .formats = RT5668_FORMATS, + }, + .ops = &rt5668_aif1_dai_ops, + }, + { + .name = "rt5668-aif2", + .id = RT5668_AIF2, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5668_STEREO_RATES, + .formats = RT5668_FORMATS, + }, + .ops = &rt5668_aif2_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5668 = { + .probe = rt5668_probe, + .remove = rt5668_remove, + .suspend = rt5668_suspend, + .resume = rt5668_resume, + .set_bias_level = rt5668_set_bias_level, + .controls = rt5668_snd_controls, + .num_controls = ARRAY_SIZE(rt5668_snd_controls), + .dapm_widgets = rt5668_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5668_dapm_widgets), + .dapm_routes = rt5668_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5668_dapm_routes), + .set_sysclk = rt5668_set_component_sysclk, + .set_pll = rt5668_set_component_pll, + .set_jack = rt5668_set_jack_detect, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5668_regmap = { + .reg_bits = 16, + .val_bits = 16, + .max_register = RT5668_I2C_MODE, + .volatile_reg = rt5668_volatile_register, + .readable_reg = rt5668_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5668_reg, + .num_reg_defaults = ARRAY_SIZE(rt5668_reg), + .use_single_rw = true, +}; + +static const struct i2c_device_id rt5668_i2c_id[] = { + {"rt5668b", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt5668_i2c_id); + +static int rt5668_parse_dt(struct rt5668_priv *rt5668, struct device *dev) +{ + + of_property_read_u32(dev->of_node, "realtek,dmic1-data-pin", + &rt5668->pdata.dmic1_data_pin); + of_property_read_u32(dev->of_node, "realtek,dmic1-clk-pin", + &rt5668->pdata.dmic1_clk_pin); + of_property_read_u32(dev->of_node, "realtek,jd-src", + &rt5668->pdata.jd_src); + + rt5668->pdata.ldo1_en = of_get_named_gpio(dev->of_node, + "realtek,ldo1-en-gpios", 0); + + return 0; +} + +static void rt5668_calibrate(struct rt5668_priv *rt5668) +{ + int value, count; + + mutex_lock(&rt5668->calibrate_mutex); + + rt5668_reset(rt5668->regmap); + regmap_write(rt5668->regmap, RT5668_PWR_ANLG_1, 0xa2bf); + usleep_range(15000, 20000); + regmap_write(rt5668->regmap, RT5668_PWR_ANLG_1, 0xf2bf); + regmap_write(rt5668->regmap, RT5668_MICBIAS_2, 0x0380); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x8001); + regmap_write(rt5668->regmap, RT5668_TEST_MODE_CTRL_1, 0x0000); + regmap_write(rt5668->regmap, RT5668_STO1_DAC_MIXER, 0x2080); + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0x4040); + regmap_write(rt5668->regmap, RT5668_DEPOP_1, 0x0069); + regmap_write(rt5668->regmap, RT5668_CHOP_DAC, 0x3000); + regmap_write(rt5668->regmap, RT5668_HP_CTRL_2, 0x6000); + regmap_write(rt5668->regmap, RT5668_HP_CHARGE_PUMP_1, 0x0f26); + regmap_write(rt5668->regmap, RT5668_CALIB_ADC_CTRL, 0x7f05); + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0x686c); + regmap_write(rt5668->regmap, RT5668_CAL_REC, 0x0d0d); + regmap_write(rt5668->regmap, RT5668_HP_CALIB_CTRL_9, 0x000f); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x8d01); + regmap_write(rt5668->regmap, RT5668_HP_CALIB_CTRL_2, 0x0321); + regmap_write(rt5668->regmap, RT5668_HP_LOGIC_CTRL_2, 0x0004); + regmap_write(rt5668->regmap, RT5668_HP_CALIB_CTRL_1, 0x7c00); + regmap_write(rt5668->regmap, RT5668_HP_CALIB_CTRL_3, 0x06a1); + regmap_write(rt5668->regmap, RT5668_A_DAC1_MUX, 0x0311); + regmap_write(rt5668->regmap, RT5668_RESET_HPF_CTRL, 0x0000); + regmap_write(rt5668->regmap, RT5668_ADC_STO1_HP_CTRL_1, 0x3320); + + regmap_write(rt5668->regmap, RT5668_HP_CALIB_CTRL_1, 0xfc00); + + for (count = 0; count < 60; count++) { + regmap_read(rt5668->regmap, RT5668_HP_CALIB_STA_1, &value); + if (!(value & 0x8000)) + break; + + usleep_range(10000, 10005); + } + + if (count >= 60) + pr_err("HP Calibration Failure\n"); + + /* restore settings */ + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x0000); + + mutex_unlock(&rt5668->calibrate_mutex); + +} + +static int rt5668_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5668_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5668_priv *rt5668; + int i, ret; + unsigned int val; + + rt5668 = devm_kzalloc(&i2c->dev, sizeof(struct rt5668_priv), + GFP_KERNEL); + + if (rt5668 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5668); + + if (pdata) + rt5668->pdata = *pdata; + else + rt5668_parse_dt(rt5668, &i2c->dev); + + rt5668->regmap = devm_regmap_init_i2c(i2c, &rt5668_regmap); + if (IS_ERR(rt5668->regmap)) { + ret = PTR_ERR(rt5668->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(rt5668->supplies); i++) + rt5668->supplies[i].supply = rt5668_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(rt5668->supplies), + rt5668->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(rt5668->supplies), + rt5668->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + if (gpio_is_valid(rt5668->pdata.ldo1_en)) { + if (devm_gpio_request_one(&i2c->dev, rt5668->pdata.ldo1_en, + GPIOF_OUT_INIT_HIGH, "rt5668")) + dev_err(&i2c->dev, "Fail gpio_request gpio_ldo\n"); + } + + /* Sleep for 300 ms miniumum */ + usleep_range(300000, 350000); + + regmap_write(rt5668->regmap, RT5668_I2C_MODE, 0x1); + usleep_range(10000, 15000); + + regmap_read(rt5668->regmap, RT5668_DEVICE_ID, &val); + if (val != DEVICE_ID) { + pr_err("Device with ID register %x is not rt5668\n", val); + return -ENODEV; + } + + rt5668_reset(rt5668->regmap); + + rt5668_calibrate(rt5668); + + regmap_write(rt5668->regmap, RT5668_DEPOP_1, 0x0000); + + /* DMIC pin*/ + if (rt5668->pdata.dmic1_data_pin != RT5668_DMIC1_NULL) { + switch (rt5668->pdata.dmic1_data_pin) { + case RT5668_DMIC1_DATA_GPIO2: /* share with LRCK2 */ + regmap_update_bits(rt5668->regmap, RT5668_DMIC_CTRL_1, + RT5668_DMIC_1_DP_MASK, RT5668_DMIC_1_DP_GPIO2); + regmap_update_bits(rt5668->regmap, RT5668_GPIO_CTRL_1, + RT5668_GP2_PIN_MASK, RT5668_GP2_PIN_DMIC_SDA); + break; + + case RT5668_DMIC1_DATA_GPIO5: /* share with DACDAT1 */ + regmap_update_bits(rt5668->regmap, RT5668_DMIC_CTRL_1, + RT5668_DMIC_1_DP_MASK, RT5668_DMIC_1_DP_GPIO5); + regmap_update_bits(rt5668->regmap, RT5668_GPIO_CTRL_1, + RT5668_GP5_PIN_MASK, RT5668_GP5_PIN_DMIC_SDA); + break; + + default: + dev_dbg(&i2c->dev, "invalid DMIC_DAT pin\n"); + break; + } + + switch (rt5668->pdata.dmic1_clk_pin) { + case RT5668_DMIC1_CLK_GPIO1: /* share with IRQ */ + regmap_update_bits(rt5668->regmap, RT5668_GPIO_CTRL_1, + RT5668_GP1_PIN_MASK, RT5668_GP1_PIN_DMIC_CLK); + break; + + case RT5668_DMIC1_CLK_GPIO3: /* share with BCLK2 */ + regmap_update_bits(rt5668->regmap, RT5668_GPIO_CTRL_1, + RT5668_GP3_PIN_MASK, RT5668_GP3_PIN_DMIC_CLK); + break; + + default: + dev_dbg(&i2c->dev, "invalid DMIC_CLK pin\n"); + break; + } + } + + regmap_update_bits(rt5668->regmap, RT5668_PWR_ANLG_1, + RT5668_LDO1_DVO_MASK | RT5668_HP_DRIVER_MASK, + RT5668_LDO1_DVO_14 | RT5668_HP_DRIVER_5X); + regmap_write(rt5668->regmap, RT5668_MICBIAS_2, 0x0380); + regmap_update_bits(rt5668->regmap, RT5668_GPIO_CTRL_1, + RT5668_GP4_PIN_MASK | RT5668_GP5_PIN_MASK, + RT5668_GP4_PIN_ADCDAT1 | RT5668_GP5_PIN_DACDAT1); + regmap_write(rt5668->regmap, RT5668_TEST_MODE_CTRL_1, 0x0000); + + INIT_DELAYED_WORK(&rt5668->jack_detect_work, + rt5668_jack_detect_handler); + INIT_DELAYED_WORK(&rt5668->jd_check_work, + rt5668_jd_check_handler); + + mutex_init(&rt5668->calibrate_mutex); + + if (i2c->irq) { + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + rt5668_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5668", rt5668); + if (ret) + dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); + + } + + return snd_soc_register_component(&i2c->dev, &soc_component_dev_rt5668, + rt5668_dai, ARRAY_SIZE(rt5668_dai)); +} + +static int rt5668_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_component(&i2c->dev); + + return 0; +} + +static void rt5668_i2c_shutdown(struct i2c_client *client) +{ + struct rt5668_priv *rt5668 = i2c_get_clientdata(client); + + rt5668_reset(rt5668->regmap); +} + +#ifdef CONFIG_OF +static const struct of_device_id rt5668_of_match[] = { + {.compatible = "realtek,rt5668b"}, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5668_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5668_acpi_match[] = { + {"10EC5668", 0,}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt5668_acpi_match); +#endif + +static struct i2c_driver rt5668_i2c_driver = { + .driver = { + .name = "rt5668b", + .of_match_table = of_match_ptr(rt5668_of_match), + .acpi_match_table = ACPI_PTR(rt5668_acpi_match), + }, + .probe = rt5668_i2c_probe, + .remove = rt5668_i2c_remove, + .shutdown = rt5668_i2c_shutdown, + .id_table = rt5668_i2c_id, +}; +module_i2c_driver(rt5668_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5668B driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5668.h b/sound/soc/codecs/rt5668.h new file mode 100644 index 000000000..3e7bcfd56 --- /dev/null +++ b/sound/soc/codecs/rt5668.h @@ -0,0 +1,1318 @@ +/* + * rt5668.h -- RT5668/RT5658 ALSA SoC audio driver + * + * Copyright 2018 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5668_H__ +#define __RT5668_H__ + +#include + +#define DEVICE_ID 0x6530 + +/* Info */ +#define RT5668_RESET 0x0000 +#define RT5668_VERSION_ID 0x00fd +#define RT5668_VENDOR_ID 0x00fe +#define RT5668_DEVICE_ID 0x00ff +/* I/O - Output */ +#define RT5668_HP_CTRL_1 0x0002 +#define RT5668_HP_CTRL_2 0x0003 +#define RT5668_HPL_GAIN 0x0005 +#define RT5668_HPR_GAIN 0x0006 + +#define RT5668_I2C_CTRL 0x0008 + +/* I/O - Input */ +#define RT5668_CBJ_BST_CTRL 0x000b +#define RT5668_CBJ_CTRL_1 0x0010 +#define RT5668_CBJ_CTRL_2 0x0011 +#define RT5668_CBJ_CTRL_3 0x0012 +#define RT5668_CBJ_CTRL_4 0x0013 +#define RT5668_CBJ_CTRL_5 0x0014 +#define RT5668_CBJ_CTRL_6 0x0015 +#define RT5668_CBJ_CTRL_7 0x0016 +/* I/O - ADC/DAC/DMIC */ +#define RT5668_DAC1_DIG_VOL 0x0019 +#define RT5668_STO1_ADC_DIG_VOL 0x001c +#define RT5668_STO1_ADC_BOOST 0x001f +#define RT5668_HP_IMP_GAIN_1 0x0022 +#define RT5668_HP_IMP_GAIN_2 0x0023 +/* Mixer - D-D */ +#define RT5668_SIDETONE_CTRL 0x0024 +#define RT5668_STO1_ADC_MIXER 0x0026 +#define RT5668_AD_DA_MIXER 0x0029 +#define RT5668_STO1_DAC_MIXER 0x002a +#define RT5668_A_DAC1_MUX 0x002b +#define RT5668_DIG_INF2_DATA 0x0030 +/* Mixer - ADC */ +#define RT5668_REC_MIXER 0x003c +#define RT5668_CAL_REC 0x0044 +#define RT5668_ALC_BACK_GAIN 0x0049 +/* Power */ +#define RT5668_PWR_DIG_1 0x0061 +#define RT5668_PWR_DIG_2 0x0062 +#define RT5668_PWR_ANLG_1 0x0063 +#define RT5668_PWR_ANLG_2 0x0064 +#define RT5668_PWR_ANLG_3 0x0065 +#define RT5668_PWR_MIXER 0x0066 +#define RT5668_PWR_VOL 0x0067 +/* Clock Detect */ +#define RT5668_CLK_DET 0x006b +/* Filter Auto Reset */ +#define RT5668_RESET_LPF_CTRL 0x006c +#define RT5668_RESET_HPF_CTRL 0x006d +/* DMIC */ +#define RT5668_DMIC_CTRL_1 0x006e +/* Format - ADC/DAC */ +#define RT5668_I2S1_SDP 0x0070 +#define RT5668_I2S2_SDP 0x0071 +#define RT5668_ADDA_CLK_1 0x0073 +#define RT5668_ADDA_CLK_2 0x0074 +#define RT5668_I2S1_F_DIV_CTRL_1 0x0075 +#define RT5668_I2S1_F_DIV_CTRL_2 0x0076 +/* Format - TDM Control */ +#define RT5668_TDM_CTRL 0x0079 +#define RT5668_TDM_ADDA_CTRL_1 0x007a +#define RT5668_TDM_ADDA_CTRL_2 0x007b +#define RT5668_DATA_SEL_CTRL_1 0x007c +#define RT5668_TDM_TCON_CTRL 0x007e +/* Function - Analog */ +#define RT5668_GLB_CLK 0x0080 +#define RT5668_PLL_CTRL_1 0x0081 +#define RT5668_PLL_CTRL_2 0x0082 +#define RT5668_PLL_TRACK_1 0x0083 +#define RT5668_PLL_TRACK_2 0x0084 +#define RT5668_PLL_TRACK_3 0x0085 +#define RT5668_PLL_TRACK_4 0x0086 +#define RT5668_PLL_TRACK_5 0x0087 +#define RT5668_PLL_TRACK_6 0x0088 +#define RT5668_PLL_TRACK_11 0x008c +#define RT5668_SDW_REF_CLK 0x008d +#define RT5668_DEPOP_1 0x008e +#define RT5668_DEPOP_2 0x008f +#define RT5668_HP_CHARGE_PUMP_1 0x0091 +#define RT5668_HP_CHARGE_PUMP_2 0x0092 +#define RT5668_MICBIAS_1 0x0093 +#define RT5668_MICBIAS_2 0x0094 +#define RT5668_PLL_TRACK_12 0x0098 +#define RT5668_PLL_TRACK_14 0x009a +#define RT5668_PLL2_CTRL_1 0x009b +#define RT5668_PLL2_CTRL_2 0x009c +#define RT5668_PLL2_CTRL_3 0x009d +#define RT5668_PLL2_CTRL_4 0x009e +#define RT5668_RC_CLK_CTRL 0x009f +#define RT5668_I2S_M_CLK_CTRL_1 0x00a0 +#define RT5668_I2S2_F_DIV_CTRL_1 0x00a3 +#define RT5668_I2S2_F_DIV_CTRL_2 0x00a4 +/* Function - Digital */ +#define RT5668_EQ_CTRL_1 0x00ae +#define RT5668_EQ_CTRL_2 0x00af +#define RT5668_IRQ_CTRL_1 0x00b6 +#define RT5668_IRQ_CTRL_2 0x00b7 +#define RT5668_IRQ_CTRL_3 0x00b8 +#define RT5668_IRQ_CTRL_4 0x00b9 +#define RT5668_INT_ST_1 0x00be +#define RT5668_GPIO_CTRL_1 0x00c0 +#define RT5668_GPIO_CTRL_2 0x00c1 +#define RT5668_GPIO_CTRL_3 0x00c2 +#define RT5668_HP_AMP_DET_CTRL_1 0x00d0 +#define RT5668_HP_AMP_DET_CTRL_2 0x00d1 +#define RT5668_MID_HP_AMP_DET 0x00d2 +#define RT5668_LOW_HP_AMP_DET 0x00d3 +#define RT5668_DELAY_BUF_CTRL 0x00d4 +#define RT5668_SV_ZCD_1 0x00d9 +#define RT5668_SV_ZCD_2 0x00da +#define RT5668_IL_CMD_1 0x00db +#define RT5668_IL_CMD_2 0x00dc +#define RT5668_IL_CMD_3 0x00dd +#define RT5668_IL_CMD_4 0x00de +#define RT5668_IL_CMD_5 0x00df +#define RT5668_IL_CMD_6 0x00e0 +#define RT5668_4BTN_IL_CMD_1 0x00e2 +#define RT5668_4BTN_IL_CMD_2 0x00e3 +#define RT5668_4BTN_IL_CMD_3 0x00e4 +#define RT5668_4BTN_IL_CMD_4 0x00e5 +#define RT5668_4BTN_IL_CMD_5 0x00e6 +#define RT5668_4BTN_IL_CMD_6 0x00e7 +#define RT5668_4BTN_IL_CMD_7 0x00e8 + +#define RT5668_ADC_STO1_HP_CTRL_1 0x00ea +#define RT5668_ADC_STO1_HP_CTRL_2 0x00eb +#define RT5668_AJD1_CTRL 0x00f0 +#define RT5668_JD1_THD 0x00f1 +#define RT5668_JD2_THD 0x00f2 +#define RT5668_JD_CTRL_1 0x00f6 +/* General Control */ +#define RT5668_DUMMY_1 0x00fa +#define RT5668_DUMMY_2 0x00fb +#define RT5668_DUMMY_3 0x00fc + +#define RT5668_DAC_ADC_DIG_VOL1 0x0100 +#define RT5668_BIAS_CUR_CTRL_2 0x010b +#define RT5668_BIAS_CUR_CTRL_3 0x010c +#define RT5668_BIAS_CUR_CTRL_4 0x010d +#define RT5668_BIAS_CUR_CTRL_5 0x010e +#define RT5668_BIAS_CUR_CTRL_6 0x010f +#define RT5668_BIAS_CUR_CTRL_7 0x0110 +#define RT5668_BIAS_CUR_CTRL_8 0x0111 +#define RT5668_BIAS_CUR_CTRL_9 0x0112 +#define RT5668_BIAS_CUR_CTRL_10 0x0113 +#define RT5668_VREF_REC_OP_FB_CAP_CTRL 0x0117 +#define RT5668_CHARGE_PUMP_1 0x0125 +#define RT5668_DIG_IN_CTRL_1 0x0132 +#define RT5668_PAD_DRIVING_CTRL 0x0136 +#define RT5668_SOFT_RAMP_DEPOP 0x0138 +#define RT5668_CHOP_DAC 0x013a +#define RT5668_CHOP_ADC 0x013b +#define RT5668_CALIB_ADC_CTRL 0x013c +#define RT5668_VOL_TEST 0x013f +#define RT5668_SPKVDD_DET_STA 0x0142 +#define RT5668_TEST_MODE_CTRL_1 0x0145 +#define RT5668_TEST_MODE_CTRL_2 0x0146 +#define RT5668_TEST_MODE_CTRL_3 0x0147 +#define RT5668_TEST_MODE_CTRL_4 0x0148 +#define RT5668_TEST_MODE_CTRL_5 0x0149 +#define RT5668_PLL1_INTERNAL 0x0150 +#define RT5668_PLL2_INTERNAL 0x0151 +#define RT5668_STO_NG2_CTRL_1 0x0160 +#define RT5668_STO_NG2_CTRL_2 0x0161 +#define RT5668_STO_NG2_CTRL_3 0x0162 +#define RT5668_STO_NG2_CTRL_4 0x0163 +#define RT5668_STO_NG2_CTRL_5 0x0164 +#define RT5668_STO_NG2_CTRL_6 0x0165 +#define RT5668_STO_NG2_CTRL_7 0x0166 +#define RT5668_STO_NG2_CTRL_8 0x0167 +#define RT5668_STO_NG2_CTRL_9 0x0168 +#define RT5668_STO_NG2_CTRL_10 0x0169 +#define RT5668_STO1_DAC_SIL_DET 0x0190 +#define RT5668_SIL_PSV_CTRL1 0x0194 +#define RT5668_SIL_PSV_CTRL2 0x0195 +#define RT5668_SIL_PSV_CTRL3 0x0197 +#define RT5668_SIL_PSV_CTRL4 0x0198 +#define RT5668_SIL_PSV_CTRL5 0x0199 +#define RT5668_HP_IMP_SENS_CTRL_01 0x01af +#define RT5668_HP_IMP_SENS_CTRL_02 0x01b0 +#define RT5668_HP_IMP_SENS_CTRL_03 0x01b1 +#define RT5668_HP_IMP_SENS_CTRL_04 0x01b2 +#define RT5668_HP_IMP_SENS_CTRL_05 0x01b3 +#define RT5668_HP_IMP_SENS_CTRL_06 0x01b4 +#define RT5668_HP_IMP_SENS_CTRL_07 0x01b5 +#define RT5668_HP_IMP_SENS_CTRL_08 0x01b6 +#define RT5668_HP_IMP_SENS_CTRL_09 0x01b7 +#define RT5668_HP_IMP_SENS_CTRL_10 0x01b8 +#define RT5668_HP_IMP_SENS_CTRL_11 0x01b9 +#define RT5668_HP_IMP_SENS_CTRL_12 0x01ba +#define RT5668_HP_IMP_SENS_CTRL_13 0x01bb +#define RT5668_HP_IMP_SENS_CTRL_14 0x01bc +#define RT5668_HP_IMP_SENS_CTRL_15 0x01bd +#define RT5668_HP_IMP_SENS_CTRL_16 0x01be +#define RT5668_HP_IMP_SENS_CTRL_17 0x01bf +#define RT5668_HP_IMP_SENS_CTRL_18 0x01c0 +#define RT5668_HP_IMP_SENS_CTRL_19 0x01c1 +#define RT5668_HP_IMP_SENS_CTRL_20 0x01c2 +#define RT5668_HP_IMP_SENS_CTRL_21 0x01c3 +#define RT5668_HP_IMP_SENS_CTRL_22 0x01c4 +#define RT5668_HP_IMP_SENS_CTRL_23 0x01c5 +#define RT5668_HP_IMP_SENS_CTRL_24 0x01c6 +#define RT5668_HP_IMP_SENS_CTRL_25 0x01c7 +#define RT5668_HP_IMP_SENS_CTRL_26 0x01c8 +#define RT5668_HP_IMP_SENS_CTRL_27 0x01c9 +#define RT5668_HP_IMP_SENS_CTRL_28 0x01ca +#define RT5668_HP_IMP_SENS_CTRL_29 0x01cb +#define RT5668_HP_IMP_SENS_CTRL_30 0x01cc +#define RT5668_HP_IMP_SENS_CTRL_31 0x01cd +#define RT5668_HP_IMP_SENS_CTRL_32 0x01ce +#define RT5668_HP_IMP_SENS_CTRL_33 0x01cf +#define RT5668_HP_IMP_SENS_CTRL_34 0x01d0 +#define RT5668_HP_IMP_SENS_CTRL_35 0x01d1 +#define RT5668_HP_IMP_SENS_CTRL_36 0x01d2 +#define RT5668_HP_IMP_SENS_CTRL_37 0x01d3 +#define RT5668_HP_IMP_SENS_CTRL_38 0x01d4 +#define RT5668_HP_IMP_SENS_CTRL_39 0x01d5 +#define RT5668_HP_IMP_SENS_CTRL_40 0x01d6 +#define RT5668_HP_IMP_SENS_CTRL_41 0x01d7 +#define RT5668_HP_IMP_SENS_CTRL_42 0x01d8 +#define RT5668_HP_IMP_SENS_CTRL_43 0x01d9 +#define RT5668_HP_LOGIC_CTRL_1 0x01da +#define RT5668_HP_LOGIC_CTRL_2 0x01db +#define RT5668_HP_LOGIC_CTRL_3 0x01dc +#define RT5668_HP_CALIB_CTRL_1 0x01de +#define RT5668_HP_CALIB_CTRL_2 0x01df +#define RT5668_HP_CALIB_CTRL_3 0x01e0 +#define RT5668_HP_CALIB_CTRL_4 0x01e1 +#define RT5668_HP_CALIB_CTRL_5 0x01e2 +#define RT5668_HP_CALIB_CTRL_6 0x01e3 +#define RT5668_HP_CALIB_CTRL_7 0x01e4 +#define RT5668_HP_CALIB_CTRL_9 0x01e6 +#define RT5668_HP_CALIB_CTRL_10 0x01e7 +#define RT5668_HP_CALIB_CTRL_11 0x01e8 +#define RT5668_HP_CALIB_STA_1 0x01ea +#define RT5668_HP_CALIB_STA_2 0x01eb +#define RT5668_HP_CALIB_STA_3 0x01ec +#define RT5668_HP_CALIB_STA_4 0x01ed +#define RT5668_HP_CALIB_STA_5 0x01ee +#define RT5668_HP_CALIB_STA_6 0x01ef +#define RT5668_HP_CALIB_STA_7 0x01f0 +#define RT5668_HP_CALIB_STA_8 0x01f1 +#define RT5668_HP_CALIB_STA_9 0x01f2 +#define RT5668_HP_CALIB_STA_10 0x01f3 +#define RT5668_HP_CALIB_STA_11 0x01f4 +#define RT5668_SAR_IL_CMD_1 0x0210 +#define RT5668_SAR_IL_CMD_2 0x0211 +#define RT5668_SAR_IL_CMD_3 0x0212 +#define RT5668_SAR_IL_CMD_4 0x0213 +#define RT5668_SAR_IL_CMD_5 0x0214 +#define RT5668_SAR_IL_CMD_6 0x0215 +#define RT5668_SAR_IL_CMD_7 0x0216 +#define RT5668_SAR_IL_CMD_8 0x0217 +#define RT5668_SAR_IL_CMD_9 0x0218 +#define RT5668_SAR_IL_CMD_10 0x0219 +#define RT5668_SAR_IL_CMD_11 0x021a +#define RT5668_SAR_IL_CMD_12 0x021b +#define RT5668_SAR_IL_CMD_13 0x021c +#define RT5668_EFUSE_CTRL_1 0x0250 +#define RT5668_EFUSE_CTRL_2 0x0251 +#define RT5668_EFUSE_CTRL_3 0x0252 +#define RT5668_EFUSE_CTRL_4 0x0253 +#define RT5668_EFUSE_CTRL_5 0x0254 +#define RT5668_EFUSE_CTRL_6 0x0255 +#define RT5668_EFUSE_CTRL_7 0x0256 +#define RT5668_EFUSE_CTRL_8 0x0257 +#define RT5668_EFUSE_CTRL_9 0x0258 +#define RT5668_EFUSE_CTRL_10 0x0259 +#define RT5668_EFUSE_CTRL_11 0x025a +#define RT5668_JD_TOP_VC_VTRL 0x0270 +#define RT5668_DRC1_CTRL_0 0x02ff +#define RT5668_DRC1_CTRL_1 0x0300 +#define RT5668_DRC1_CTRL_2 0x0301 +#define RT5668_DRC1_CTRL_3 0x0302 +#define RT5668_DRC1_CTRL_4 0x0303 +#define RT5668_DRC1_CTRL_5 0x0304 +#define RT5668_DRC1_CTRL_6 0x0305 +#define RT5668_DRC1_HARD_LMT_CTRL_1 0x0306 +#define RT5668_DRC1_HARD_LMT_CTRL_2 0x0307 +#define RT5668_DRC1_PRIV_1 0x0310 +#define RT5668_DRC1_PRIV_2 0x0311 +#define RT5668_DRC1_PRIV_3 0x0312 +#define RT5668_DRC1_PRIV_4 0x0313 +#define RT5668_DRC1_PRIV_5 0x0314 +#define RT5668_DRC1_PRIV_6 0x0315 +#define RT5668_DRC1_PRIV_7 0x0316 +#define RT5668_DRC1_PRIV_8 0x0317 +#define RT5668_EQ_AUTO_RCV_CTRL1 0x03c0 +#define RT5668_EQ_AUTO_RCV_CTRL2 0x03c1 +#define RT5668_EQ_AUTO_RCV_CTRL3 0x03c2 +#define RT5668_EQ_AUTO_RCV_CTRL4 0x03c3 +#define RT5668_EQ_AUTO_RCV_CTRL5 0x03c4 +#define RT5668_EQ_AUTO_RCV_CTRL6 0x03c5 +#define RT5668_EQ_AUTO_RCV_CTRL7 0x03c6 +#define RT5668_EQ_AUTO_RCV_CTRL8 0x03c7 +#define RT5668_EQ_AUTO_RCV_CTRL9 0x03c8 +#define RT5668_EQ_AUTO_RCV_CTRL10 0x03c9 +#define RT5668_EQ_AUTO_RCV_CTRL11 0x03ca +#define RT5668_EQ_AUTO_RCV_CTRL12 0x03cb +#define RT5668_EQ_AUTO_RCV_CTRL13 0x03cc +#define RT5668_ADC_L_EQ_LPF1_A1 0x03d0 +#define RT5668_R_EQ_LPF1_A1 0x03d1 +#define RT5668_L_EQ_LPF1_H0 0x03d2 +#define RT5668_R_EQ_LPF1_H0 0x03d3 +#define RT5668_L_EQ_BPF1_A1 0x03d4 +#define RT5668_R_EQ_BPF1_A1 0x03d5 +#define RT5668_L_EQ_BPF1_A2 0x03d6 +#define RT5668_R_EQ_BPF1_A2 0x03d7 +#define RT5668_L_EQ_BPF1_H0 0x03d8 +#define RT5668_R_EQ_BPF1_H0 0x03d9 +#define RT5668_L_EQ_BPF2_A1 0x03da +#define RT5668_R_EQ_BPF2_A1 0x03db +#define RT5668_L_EQ_BPF2_A2 0x03dc +#define RT5668_R_EQ_BPF2_A2 0x03dd +#define RT5668_L_EQ_BPF2_H0 0x03de +#define RT5668_R_EQ_BPF2_H0 0x03df +#define RT5668_L_EQ_BPF3_A1 0x03e0 +#define RT5668_R_EQ_BPF3_A1 0x03e1 +#define RT5668_L_EQ_BPF3_A2 0x03e2 +#define RT5668_R_EQ_BPF3_A2 0x03e3 +#define RT5668_L_EQ_BPF3_H0 0x03e4 +#define RT5668_R_EQ_BPF3_H0 0x03e5 +#define RT5668_L_EQ_BPF4_A1 0x03e6 +#define RT5668_R_EQ_BPF4_A1 0x03e7 +#define RT5668_L_EQ_BPF4_A2 0x03e8 +#define RT5668_R_EQ_BPF4_A2 0x03e9 +#define RT5668_L_EQ_BPF4_H0 0x03ea +#define RT5668_R_EQ_BPF4_H0 0x03eb +#define RT5668_L_EQ_HPF1_A1 0x03ec +#define RT5668_R_EQ_HPF1_A1 0x03ed +#define RT5668_L_EQ_HPF1_H0 0x03ee +#define RT5668_R_EQ_HPF1_H0 0x03ef +#define RT5668_L_EQ_PRE_VOL 0x03f0 +#define RT5668_R_EQ_PRE_VOL 0x03f1 +#define RT5668_L_EQ_POST_VOL 0x03f2 +#define RT5668_R_EQ_POST_VOL 0x03f3 +#define RT5668_I2C_MODE 0xffff + + +/* global definition */ +#define RT5668_L_MUTE (0x1 << 15) +#define RT5668_L_MUTE_SFT 15 +#define RT5668_VOL_L_MUTE (0x1 << 14) +#define RT5668_VOL_L_SFT 14 +#define RT5668_R_MUTE (0x1 << 7) +#define RT5668_R_MUTE_SFT 7 +#define RT5668_VOL_R_MUTE (0x1 << 6) +#define RT5668_VOL_R_SFT 6 +#define RT5668_L_VOL_MASK (0x3f << 8) +#define RT5668_L_VOL_SFT 8 +#define RT5668_R_VOL_MASK (0x3f) +#define RT5668_R_VOL_SFT 0 + +/*Headphone Amp L/R Analog Gain and Digital NG2 Gain Control (0x0005 0x0006)*/ +#define RT5668_G_HP (0xf << 8) +#define RT5668_G_HP_SFT 8 +#define RT5668_G_STO_DA_DMIX (0xf) +#define RT5668_G_STO_DA_SFT 0 + +/* CBJ Control (0x000b) */ +#define RT5668_BST_CBJ_MASK (0xf << 8) +#define RT5668_BST_CBJ_SFT 8 + +/* Embeeded Jack and Type Detection Control 1 (0x0010) */ +#define RT5668_EMB_JD_EN (0x1 << 15) +#define RT5668_EMB_JD_EN_SFT 15 +#define RT5668_EMB_JD_RST (0x1 << 14) +#define RT5668_JD_MODE (0x1 << 13) +#define RT5668_JD_MODE_SFT 13 +#define RT5668_DET_TYPE (0x1 << 12) +#define RT5668_DET_TYPE_SFT 12 +#define RT5668_POLA_EXT_JD_MASK (0x1 << 11) +#define RT5668_POLA_EXT_JD_LOW (0x1 << 11) +#define RT5668_POLA_EXT_JD_HIGH (0x0 << 11) +#define RT5668_EXT_JD_DIG (0x1 << 9) +#define RT5668_POL_FAST_OFF_MASK (0x1 << 8) +#define RT5668_POL_FAST_OFF_HIGH (0x1 << 8) +#define RT5668_POL_FAST_OFF_LOW (0x0 << 8) +#define RT5668_FAST_OFF_MASK (0x1 << 7) +#define RT5668_FAST_OFF_EN (0x1 << 7) +#define RT5668_FAST_OFF_DIS (0x0 << 7) +#define RT5668_VREF_POW_MASK (0x1 << 6) +#define RT5668_VREF_POW_FSM (0x0 << 6) +#define RT5668_VREF_POW_REG (0x1 << 6) +#define RT5668_MB1_PATH_MASK (0x1 << 5) +#define RT5668_CTRL_MB1_REG (0x1 << 5) +#define RT5668_CTRL_MB1_FSM (0x0 << 5) +#define RT5668_MB2_PATH_MASK (0x1 << 4) +#define RT5668_CTRL_MB2_REG (0x1 << 4) +#define RT5668_CTRL_MB2_FSM (0x0 << 4) +#define RT5668_TRIG_JD_MASK (0x1 << 3) +#define RT5668_TRIG_JD_HIGH (0x1 << 3) +#define RT5668_TRIG_JD_LOW (0x0 << 3) +#define RT5668_MIC_CAP_MASK (0x1 << 1) +#define RT5668_MIC_CAP_HS (0x1 << 1) +#define RT5668_MIC_CAP_HP (0x0 << 1) +#define RT5668_MIC_CAP_SRC_MASK (0x1) +#define RT5668_MIC_CAP_SRC_REG (0x1) +#define RT5668_MIC_CAP_SRC_ANA (0x0) + +/* Embeeded Jack and Type Detection Control 2 (0x0011) */ +#define RT5668_EXT_JD_SRC (0x7 << 4) +#define RT5668_EXT_JD_SRC_SFT 4 +#define RT5668_EXT_JD_SRC_GPIO_JD1 (0x0 << 4) +#define RT5668_EXT_JD_SRC_GPIO_JD2 (0x1 << 4) +#define RT5668_EXT_JD_SRC_JDH (0x2 << 4) +#define RT5668_EXT_JD_SRC_JDL (0x3 << 4) +#define RT5668_EXT_JD_SRC_MANUAL (0x4 << 4) +#define RT5668_JACK_TYPE_MASK (0x3) + +/* Combo Jack and Type Detection Control 3 (0x0012) */ +#define RT5668_CBJ_IN_BUF_EN (0x1 << 7) + +/* Combo Jack and Type Detection Control 4 (0x0013) */ +#define RT5668_SEL_SHT_MID_TON_MASK (0x3 << 12) +#define RT5668_SEL_SHT_MID_TON_2 (0x0 << 12) +#define RT5668_SEL_SHT_MID_TON_3 (0x1 << 12) +#define RT5668_CBJ_JD_TEST_MASK (0x1 << 6) +#define RT5668_CBJ_JD_TEST_NORM (0x0 << 6) +#define RT5668_CBJ_JD_TEST_MODE (0x1 << 6) + +/* DAC1 Digital Volume (0x0019) */ +#define RT5668_DAC_L1_VOL_MASK (0xff << 8) +#define RT5668_DAC_L1_VOL_SFT 8 +#define RT5668_DAC_R1_VOL_MASK (0xff) +#define RT5668_DAC_R1_VOL_SFT 0 + +/* ADC Digital Volume Control (0x001c) */ +#define RT5668_ADC_L_VOL_MASK (0x7f << 8) +#define RT5668_ADC_L_VOL_SFT 8 +#define RT5668_ADC_R_VOL_MASK (0x7f) +#define RT5668_ADC_R_VOL_SFT 0 + +/* Stereo1 ADC Boost Gain Control (0x001f) */ +#define RT5668_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5668_STO1_ADC_L_BST_SFT 14 +#define RT5668_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5668_STO1_ADC_R_BST_SFT 12 + +/* Sidetone Control (0x0024) */ +#define RT5668_ST_SRC_SEL (0x1 << 8) +#define RT5668_ST_SRC_SFT 8 +#define RT5668_ST_EN_MASK (0x1 << 6) +#define RT5668_ST_DIS (0x0 << 6) +#define RT5668_ST_EN (0x1 << 6) +#define RT5668_ST_EN_SFT 6 + +/* Stereo1 ADC Mixer Control (0x0026) */ +#define RT5668_M_STO1_ADC_L1 (0x1 << 15) +#define RT5668_M_STO1_ADC_L1_SFT 15 +#define RT5668_M_STO1_ADC_L2 (0x1 << 14) +#define RT5668_M_STO1_ADC_L2_SFT 14 +#define RT5668_STO1_ADC1L_SRC_MASK (0x1 << 13) +#define RT5668_STO1_ADC1L_SRC_SFT 13 +#define RT5668_STO1_ADC1_SRC_ADC (0x1 << 13) +#define RT5668_STO1_ADC1_SRC_DACMIX (0x0 << 13) +#define RT5668_STO1_ADC2L_SRC_MASK (0x1 << 12) +#define RT5668_STO1_ADC2L_SRC_SFT 12 +#define RT5668_STO1_ADCL_SRC_MASK (0x3 << 10) +#define RT5668_STO1_ADCL_SRC_SFT 10 +#define RT5668_STO1_DD_L_SRC_MASK (0x1 << 9) +#define RT5668_STO1_DD_L_SRC_SFT 9 +#define RT5668_STO1_DMIC_SRC_MASK (0x1 << 8) +#define RT5668_STO1_DMIC_SRC_SFT 8 +#define RT5668_STO1_DMIC_SRC_DMIC2 (0x1 << 8) +#define RT5668_STO1_DMIC_SRC_DMIC1 (0x0 << 8) +#define RT5668_M_STO1_ADC_R1 (0x1 << 7) +#define RT5668_M_STO1_ADC_R1_SFT 7 +#define RT5668_M_STO1_ADC_R2 (0x1 << 6) +#define RT5668_M_STO1_ADC_R2_SFT 6 +#define RT5668_STO1_ADC1R_SRC_MASK (0x1 << 5) +#define RT5668_STO1_ADC1R_SRC_SFT 5 +#define RT5668_STO1_ADC2R_SRC_MASK (0x1 << 4) +#define RT5668_STO1_ADC2R_SRC_SFT 4 +#define RT5668_STO1_ADCR_SRC_MASK (0x3 << 2) +#define RT5668_STO1_ADCR_SRC_SFT 2 + +/* ADC Mixer to DAC Mixer Control (0x0029) */ +#define RT5668_M_ADCMIX_L (0x1 << 15) +#define RT5668_M_ADCMIX_L_SFT 15 +#define RT5668_M_DAC1_L (0x1 << 14) +#define RT5668_M_DAC1_L_SFT 14 +#define RT5668_DAC1_R_SEL_MASK (0x1 << 10) +#define RT5668_DAC1_R_SEL_SFT 10 +#define RT5668_DAC1_L_SEL_MASK (0x1 << 8) +#define RT5668_DAC1_L_SEL_SFT 8 +#define RT5668_M_ADCMIX_R (0x1 << 7) +#define RT5668_M_ADCMIX_R_SFT 7 +#define RT5668_M_DAC1_R (0x1 << 6) +#define RT5668_M_DAC1_R_SFT 6 + +/* Stereo1 DAC Mixer Control (0x002a) */ +#define RT5668_M_DAC_L1_STO_L (0x1 << 15) +#define RT5668_M_DAC_L1_STO_L_SFT 15 +#define RT5668_G_DAC_L1_STO_L_MASK (0x1 << 14) +#define RT5668_G_DAC_L1_STO_L_SFT 14 +#define RT5668_M_DAC_R1_STO_L (0x1 << 13) +#define RT5668_M_DAC_R1_STO_L_SFT 13 +#define RT5668_G_DAC_R1_STO_L_MASK (0x1 << 12) +#define RT5668_G_DAC_R1_STO_L_SFT 12 +#define RT5668_M_DAC_L1_STO_R (0x1 << 7) +#define RT5668_M_DAC_L1_STO_R_SFT 7 +#define RT5668_G_DAC_L1_STO_R_MASK (0x1 << 6) +#define RT5668_G_DAC_L1_STO_R_SFT 6 +#define RT5668_M_DAC_R1_STO_R (0x1 << 5) +#define RT5668_M_DAC_R1_STO_R_SFT 5 +#define RT5668_G_DAC_R1_STO_R_MASK (0x1 << 4) +#define RT5668_G_DAC_R1_STO_R_SFT 4 + +/* Analog DAC1 Input Source Control (0x002b) */ +#define RT5668_M_ST_STO_L (0x1 << 9) +#define RT5668_M_ST_STO_L_SFT 9 +#define RT5668_M_ST_STO_R (0x1 << 8) +#define RT5668_M_ST_STO_R_SFT 8 +#define RT5668_DAC_L1_SRC_MASK (0x3 << 4) +#define RT5668_A_DACL1_SFT 4 +#define RT5668_DAC_R1_SRC_MASK (0x3) +#define RT5668_A_DACR1_SFT 0 + +/* Digital Interface Data Control (0x0030) */ +#define RT5668_IF2_ADC_SEL_MASK (0x3 << 0) +#define RT5668_IF2_ADC_SEL_SFT 0 + +/* REC Left Mixer Control 2 (0x003c) */ +#define RT5668_G_CBJ_RM1_L (0x7 << 10) +#define RT5668_G_CBJ_RM1_L_SFT 10 +#define RT5668_M_CBJ_RM1_L (0x1 << 7) +#define RT5668_M_CBJ_RM1_L_SFT 7 + +/* Power Management for Digital 1 (0x0061) */ +#define RT5668_PWR_I2S1 (0x1 << 15) +#define RT5668_PWR_I2S1_BIT 15 +#define RT5668_PWR_I2S2 (0x1 << 14) +#define RT5668_PWR_I2S2_BIT 14 +#define RT5668_PWR_DAC_L1 (0x1 << 11) +#define RT5668_PWR_DAC_L1_BIT 11 +#define RT5668_PWR_DAC_R1 (0x1 << 10) +#define RT5668_PWR_DAC_R1_BIT 10 +#define RT5668_PWR_LDO (0x1 << 8) +#define RT5668_PWR_LDO_BIT 8 +#define RT5668_PWR_ADC_L1 (0x1 << 4) +#define RT5668_PWR_ADC_L1_BIT 4 +#define RT5668_PWR_ADC_R1 (0x1 << 3) +#define RT5668_PWR_ADC_R1_BIT 3 +#define RT5668_DIG_GATE_CTRL (0x1 << 0) +#define RT5668_DIG_GATE_CTRL_SFT 0 + + +/* Power Management for Digital 2 (0x0062) */ +#define RT5668_PWR_ADC_S1F (0x1 << 15) +#define RT5668_PWR_ADC_S1F_BIT 15 +#define RT5668_PWR_DAC_S1F (0x1 << 10) +#define RT5668_PWR_DAC_S1F_BIT 10 + +/* Power Management for Analog 1 (0x0063) */ +#define RT5668_PWR_VREF1 (0x1 << 15) +#define RT5668_PWR_VREF1_BIT 15 +#define RT5668_PWR_FV1 (0x1 << 14) +#define RT5668_PWR_FV1_BIT 14 +#define RT5668_PWR_VREF2 (0x1 << 13) +#define RT5668_PWR_VREF2_BIT 13 +#define RT5668_PWR_FV2 (0x1 << 12) +#define RT5668_PWR_FV2_BIT 12 +#define RT5668_LDO1_DBG_MASK (0x3 << 10) +#define RT5668_PWR_MB (0x1 << 9) +#define RT5668_PWR_MB_BIT 9 +#define RT5668_PWR_BG (0x1 << 7) +#define RT5668_PWR_BG_BIT 7 +#define RT5668_LDO1_BYPASS_MASK (0x1 << 6) +#define RT5668_LDO1_BYPASS (0x1 << 6) +#define RT5668_LDO1_NOT_BYPASS (0x0 << 6) +#define RT5668_PWR_MA_BIT 6 +#define RT5668_LDO1_DVO_MASK (0x3 << 4) +#define RT5668_LDO1_DVO_09 (0x0 << 4) +#define RT5668_LDO1_DVO_10 (0x1 << 4) +#define RT5668_LDO1_DVO_12 (0x2 << 4) +#define RT5668_LDO1_DVO_14 (0x3 << 4) +#define RT5668_HP_DRIVER_MASK (0x3 << 2) +#define RT5668_HP_DRIVER_1X (0x0 << 2) +#define RT5668_HP_DRIVER_3X (0x1 << 2) +#define RT5668_HP_DRIVER_5X (0x3 << 2) +#define RT5668_PWR_HA_L (0x1 << 1) +#define RT5668_PWR_HA_L_BIT 1 +#define RT5668_PWR_HA_R (0x1 << 0) +#define RT5668_PWR_HA_R_BIT 0 + +/* Power Management for Analog 2 (0x0064) */ +#define RT5668_PWR_MB1 (0x1 << 11) +#define RT5668_PWR_MB1_PWR_DOWN (0x0 << 11) +#define RT5668_PWR_MB1_BIT 11 +#define RT5668_PWR_MB2 (0x1 << 10) +#define RT5668_PWR_MB2_PWR_DOWN (0x0 << 10) +#define RT5668_PWR_MB2_BIT 10 +#define RT5668_PWR_JDH (0x1 << 3) +#define RT5668_PWR_JDH_BIT 3 +#define RT5668_PWR_JDL (0x1 << 2) +#define RT5668_PWR_JDL_BIT 2 +#define RT5668_PWR_RM1_L (0x1 << 1) +#define RT5668_PWR_RM1_L_BIT 1 + +/* Power Management for Analog 3 (0x0065) */ +#define RT5668_PWR_CBJ (0x1 << 9) +#define RT5668_PWR_CBJ_BIT 9 +#define RT5668_PWR_PLL (0x1 << 6) +#define RT5668_PWR_PLL_BIT 6 +#define RT5668_PWR_PLL2B (0x1 << 5) +#define RT5668_PWR_PLL2B_BIT 5 +#define RT5668_PWR_PLL2F (0x1 << 4) +#define RT5668_PWR_PLL2F_BIT 4 +#define RT5668_PWR_LDO2 (0x1 << 2) +#define RT5668_PWR_LDO2_BIT 2 +#define RT5668_PWR_DET_SPKVDD (0x1 << 1) +#define RT5668_PWR_DET_SPKVDD_BIT 1 + +/* Power Management for Mixer (0x0066) */ +#define RT5668_PWR_STO1_DAC_L (0x1 << 5) +#define RT5668_PWR_STO1_DAC_L_BIT 5 +#define RT5668_PWR_STO1_DAC_R (0x1 << 4) +#define RT5668_PWR_STO1_DAC_R_BIT 4 + +/* MCLK and System Clock Detection Control (0x006b) */ +#define RT5668_SYS_CLK_DET (0x1 << 15) +#define RT5668_SYS_CLK_DET_SFT 15 +#define RT5668_PLL1_CLK_DET (0x1 << 14) +#define RT5668_PLL1_CLK_DET_SFT 14 +#define RT5668_PLL2_CLK_DET (0x1 << 13) +#define RT5668_PLL2_CLK_DET_SFT 13 +#define RT5668_POW_CLK_DET2_SFT 8 +#define RT5668_POW_CLK_DET_SFT 0 + +/* Digital Microphone Control 1 (0x006e) */ +#define RT5668_DMIC_1_EN_MASK (0x1 << 15) +#define RT5668_DMIC_1_EN_SFT 15 +#define RT5668_DMIC_1_DIS (0x0 << 15) +#define RT5668_DMIC_1_EN (0x1 << 15) +#define RT5668_DMIC_1_DP_MASK (0x3 << 4) +#define RT5668_DMIC_1_DP_SFT 4 +#define RT5668_DMIC_1_DP_GPIO2 (0x0 << 4) +#define RT5668_DMIC_1_DP_GPIO5 (0x1 << 4) +#define RT5668_DMIC_CLK_MASK (0xf << 0) +#define RT5668_DMIC_CLK_SFT 0 + +/* I2S1 Audio Serial Data Port Control (0x0070) */ +#define RT5668_SEL_ADCDAT_MASK (0x1 << 15) +#define RT5668_SEL_ADCDAT_OUT (0x0 << 15) +#define RT5668_SEL_ADCDAT_IN (0x1 << 15) +#define RT5668_SEL_ADCDAT_SFT 15 +#define RT5668_I2S1_TX_CHL_MASK (0x7 << 12) +#define RT5668_I2S1_TX_CHL_SFT 12 +#define RT5668_I2S1_TX_CHL_16 (0x0 << 12) +#define RT5668_I2S1_TX_CHL_20 (0x1 << 12) +#define RT5668_I2S1_TX_CHL_24 (0x2 << 12) +#define RT5668_I2S1_TX_CHL_32 (0x3 << 12) +#define RT5668_I2S1_TX_CHL_8 (0x4 << 12) +#define RT5668_I2S1_RX_CHL_MASK (0x7 << 8) +#define RT5668_I2S1_RX_CHL_SFT 8 +#define RT5668_I2S1_RX_CHL_16 (0x0 << 8) +#define RT5668_I2S1_RX_CHL_20 (0x1 << 8) +#define RT5668_I2S1_RX_CHL_24 (0x2 << 8) +#define RT5668_I2S1_RX_CHL_32 (0x3 << 8) +#define RT5668_I2S1_RX_CHL_8 (0x4 << 8) +#define RT5668_I2S1_MONO_MASK (0x1 << 7) +#define RT5668_I2S1_MONO_EN (0x1 << 7) +#define RT5668_I2S1_MONO_DIS (0x0 << 7) +#define RT5668_I2S2_MONO_MASK (0x1 << 6) +#define RT5668_I2S2_MONO_EN (0x1 << 6) +#define RT5668_I2S2_MONO_DIS (0x0 << 6) +#define RT5668_I2S1_DL_MASK (0x7 << 4) +#define RT5668_I2S1_DL_SFT 4 +#define RT5668_I2S1_DL_16 (0x0 << 4) +#define RT5668_I2S1_DL_20 (0x1 << 4) +#define RT5668_I2S1_DL_24 (0x2 << 4) +#define RT5668_I2S1_DL_32 (0x3 << 4) +#define RT5668_I2S1_DL_8 (0x4 << 4) + +/* I2S1/2 Audio Serial Data Port Control (0x0070)(0x0071) */ +#define RT5668_I2S2_MS_MASK (0x1 << 15) +#define RT5668_I2S2_MS_SFT 15 +#define RT5668_I2S2_MS_M (0x0 << 15) +#define RT5668_I2S2_MS_S (0x1 << 15) +#define RT5668_I2S2_PIN_CFG_MASK (0x1 << 14) +#define RT5668_I2S2_PIN_CFG_SFT 14 +#define RT5668_I2S2_CLK_SEL_MASK (0x1 << 11) +#define RT5668_I2S2_CLK_SEL_SFT 11 +#define RT5668_I2S2_OUT_MASK (0x1 << 9) +#define RT5668_I2S2_OUT_SFT 9 +#define RT5668_I2S2_OUT_UM (0x0 << 9) +#define RT5668_I2S2_OUT_M (0x1 << 9) +#define RT5668_I2S_BP_MASK (0x1 << 8) +#define RT5668_I2S_BP_SFT 8 +#define RT5668_I2S_BP_NOR (0x0 << 8) +#define RT5668_I2S_BP_INV (0x1 << 8) +#define RT5668_I2S2_MONO_EN (0x1 << 6) +#define RT5668_I2S2_MONO_DIS (0x0 << 6) +#define RT5668_I2S2_DL_MASK (0x3 << 4) +#define RT5668_I2S2_DL_SFT 4 +#define RT5668_I2S2_DL_16 (0x0 << 4) +#define RT5668_I2S2_DL_20 (0x1 << 4) +#define RT5668_I2S2_DL_24 (0x2 << 4) +#define RT5668_I2S2_DL_8 (0x3 << 4) +#define RT5668_I2S_DF_MASK (0x7) +#define RT5668_I2S_DF_SFT 0 +#define RT5668_I2S_DF_I2S (0x0) +#define RT5668_I2S_DF_LEFT (0x1) +#define RT5668_I2S_DF_PCM_A (0x2) +#define RT5668_I2S_DF_PCM_B (0x3) +#define RT5668_I2S_DF_PCM_A_N (0x6) +#define RT5668_I2S_DF_PCM_B_N (0x7) + +/* ADC/DAC Clock Control 1 (0x0073) */ +#define RT5668_ADC_OSR_MASK (0xf << 12) +#define RT5668_ADC_OSR_SFT 12 +#define RT5668_ADC_OSR_D_1 (0x0 << 12) +#define RT5668_ADC_OSR_D_2 (0x1 << 12) +#define RT5668_ADC_OSR_D_4 (0x2 << 12) +#define RT5668_ADC_OSR_D_6 (0x3 << 12) +#define RT5668_ADC_OSR_D_8 (0x4 << 12) +#define RT5668_ADC_OSR_D_12 (0x5 << 12) +#define RT5668_ADC_OSR_D_16 (0x6 << 12) +#define RT5668_ADC_OSR_D_24 (0x7 << 12) +#define RT5668_ADC_OSR_D_32 (0x8 << 12) +#define RT5668_ADC_OSR_D_48 (0x9 << 12) +#define RT5668_I2S_M_DIV_MASK (0xf << 12) +#define RT5668_I2S_M_DIV_SFT 8 +#define RT5668_I2S_M_D_1 (0x0 << 8) +#define RT5668_I2S_M_D_2 (0x1 << 8) +#define RT5668_I2S_M_D_3 (0x2 << 8) +#define RT5668_I2S_M_D_4 (0x3 << 8) +#define RT5668_I2S_M_D_6 (0x4 << 8) +#define RT5668_I2S_M_D_8 (0x5 << 8) +#define RT5668_I2S_M_D_12 (0x6 << 8) +#define RT5668_I2S_M_D_16 (0x7 << 8) +#define RT5668_I2S_M_D_24 (0x8 << 8) +#define RT5668_I2S_M_D_32 (0x9 << 8) +#define RT5668_I2S_M_D_48 (0x10 << 8) +#define RT5668_I2S_CLK_SRC_MASK (0x7 << 4) +#define RT5668_I2S_CLK_SRC_SFT 4 +#define RT5668_I2S_CLK_SRC_MCLK (0x0 << 4) +#define RT5668_I2S_CLK_SRC_PLL1 (0x1 << 4) +#define RT5668_I2S_CLK_SRC_PLL2 (0x2 << 4) +#define RT5668_I2S_CLK_SRC_SDW (0x3 << 4) +#define RT5668_I2S_CLK_SRC_RCCLK (0x4 << 4) /* 25M */ +#define RT5668_DAC_OSR_MASK (0xf << 0) +#define RT5668_DAC_OSR_SFT 0 +#define RT5668_DAC_OSR_D_1 (0x0 << 0) +#define RT5668_DAC_OSR_D_2 (0x1 << 0) +#define RT5668_DAC_OSR_D_4 (0x2 << 0) +#define RT5668_DAC_OSR_D_6 (0x3 << 0) +#define RT5668_DAC_OSR_D_8 (0x4 << 0) +#define RT5668_DAC_OSR_D_12 (0x5 << 0) +#define RT5668_DAC_OSR_D_16 (0x6 << 0) +#define RT5668_DAC_OSR_D_24 (0x7 << 0) +#define RT5668_DAC_OSR_D_32 (0x8 << 0) +#define RT5668_DAC_OSR_D_48 (0x9 << 0) + +/* ADC/DAC Clock Control 2 (0x0074) */ +#define RT5668_I2S2_BCLK_MS2_MASK (0x1 << 11) +#define RT5668_I2S2_BCLK_MS2_SFT 11 +#define RT5668_I2S2_BCLK_MS2_32 (0x0 << 11) +#define RT5668_I2S2_BCLK_MS2_64 (0x1 << 11) + + +/* TDM control 1 (0x0079) */ +#define RT5668_TDM_TX_CH_MASK (0x3 << 12) +#define RT5668_TDM_TX_CH_2 (0x0 << 12) +#define RT5668_TDM_TX_CH_4 (0x1 << 12) +#define RT5668_TDM_TX_CH_6 (0x2 << 12) +#define RT5668_TDM_TX_CH_8 (0x3 << 12) +#define RT5668_TDM_RX_CH_MASK (0x3 << 8) +#define RT5668_TDM_RX_CH_2 (0x0 << 8) +#define RT5668_TDM_RX_CH_4 (0x1 << 8) +#define RT5668_TDM_RX_CH_6 (0x2 << 8) +#define RT5668_TDM_RX_CH_8 (0x3 << 8) +#define RT5668_TDM_ADC_LCA_MASK (0xf << 4) +#define RT5668_TDM_ADC_LCA_SFT 4 +#define RT5668_TDM_ADC_DL_SFT 0 + +/* TDM control 3 (0x007a) */ +#define RT5668_IF1_ADC1_SEL_SFT 14 +#define RT5668_IF1_ADC2_SEL_SFT 12 +#define RT5668_IF1_ADC3_SEL_SFT 10 +#define RT5668_IF1_ADC4_SEL_SFT 8 +#define RT5668_TDM_ADC_SEL_SFT 4 + +/* TDM/I2S control (0x007e) */ +#define RT5668_TDM_S_BP_MASK (0x1 << 15) +#define RT5668_TDM_S_BP_SFT 15 +#define RT5668_TDM_S_BP_NOR (0x0 << 15) +#define RT5668_TDM_S_BP_INV (0x1 << 15) +#define RT5668_TDM_S_LP_MASK (0x1 << 14) +#define RT5668_TDM_S_LP_SFT 14 +#define RT5668_TDM_S_LP_NOR (0x0 << 14) +#define RT5668_TDM_S_LP_INV (0x1 << 14) +#define RT5668_TDM_DF_MASK (0x7 << 11) +#define RT5668_TDM_DF_SFT 11 +#define RT5668_TDM_DF_I2S (0x0 << 11) +#define RT5668_TDM_DF_LEFT (0x1 << 11) +#define RT5668_TDM_DF_PCM_A (0x2 << 11) +#define RT5668_TDM_DF_PCM_B (0x3 << 11) +#define RT5668_TDM_DF_PCM_A_N (0x6 << 11) +#define RT5668_TDM_DF_PCM_B_N (0x7 << 11) +#define RT5668_TDM_CL_MASK (0x3 << 4) +#define RT5668_TDM_CL_16 (0x0 << 4) +#define RT5668_TDM_CL_20 (0x1 << 4) +#define RT5668_TDM_CL_24 (0x2 << 4) +#define RT5668_TDM_CL_32 (0x3 << 4) +#define RT5668_TDM_M_BP_MASK (0x1 << 2) +#define RT5668_TDM_M_BP_SFT 2 +#define RT5668_TDM_M_BP_NOR (0x0 << 2) +#define RT5668_TDM_M_BP_INV (0x1 << 2) +#define RT5668_TDM_M_LP_MASK (0x1 << 1) +#define RT5668_TDM_M_LP_SFT 1 +#define RT5668_TDM_M_LP_NOR (0x0 << 1) +#define RT5668_TDM_M_LP_INV (0x1 << 1) +#define RT5668_TDM_MS_MASK (0x1 << 0) +#define RT5668_TDM_MS_SFT 0 +#define RT5668_TDM_MS_M (0x0 << 0) +#define RT5668_TDM_MS_S (0x1 << 0) + +/* Global Clock Control (0x0080) */ +#define RT5668_SCLK_SRC_MASK (0x7 << 13) +#define RT5668_SCLK_SRC_SFT 13 +#define RT5668_SCLK_SRC_MCLK (0x0 << 13) +#define RT5668_SCLK_SRC_PLL1 (0x1 << 13) +#define RT5668_SCLK_SRC_PLL2 (0x2 << 13) +#define RT5668_SCLK_SRC_SDW (0x3 << 13) +#define RT5668_SCLK_SRC_RCCLK (0x4 << 13) +#define RT5668_PLL1_SRC_MASK (0x3 << 10) +#define RT5668_PLL1_SRC_SFT 10 +#define RT5668_PLL1_SRC_MCLK (0x0 << 10) +#define RT5668_PLL1_SRC_BCLK1 (0x1 << 10) +#define RT5668_PLL1_SRC_SDW (0x2 << 10) +#define RT5668_PLL1_SRC_RC (0x3 << 10) +#define RT5668_PLL2_SRC_MASK (0x3 << 8) +#define RT5668_PLL2_SRC_SFT 8 +#define RT5668_PLL2_SRC_MCLK (0x0 << 8) +#define RT5668_PLL2_SRC_BCLK1 (0x1 << 8) +#define RT5668_PLL2_SRC_SDW (0x2 << 8) +#define RT5668_PLL2_SRC_RC (0x3 << 8) + + + +#define RT5668_PLL_INP_MAX 40000000 +#define RT5668_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x0081) */ +#define RT5668_PLL_N_MAX 0x001ff +#define RT5668_PLL_N_MASK (RT5668_PLL_N_MAX << 7) +#define RT5668_PLL_N_SFT 7 +#define RT5668_PLL_K_MAX 0x001f +#define RT5668_PLL_K_MASK (RT5668_PLL_K_MAX) +#define RT5668_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x0082) */ +#define RT5668_PLL_M_MAX 0x00f +#define RT5668_PLL_M_MASK (RT5668_PLL_M_MAX << 12) +#define RT5668_PLL_M_SFT 12 +#define RT5668_PLL_M_BP (0x1 << 11) +#define RT5668_PLL_M_BP_SFT 11 +#define RT5668_PLL_K_BP (0x1 << 10) +#define RT5668_PLL_K_BP_SFT 10 + +/* PLL tracking mode 1 (0x0083) */ +#define RT5668_DA_ASRC_MASK (0x1 << 13) +#define RT5668_DA_ASRC_SFT 13 +#define RT5668_DAC_STO1_ASRC_MASK (0x1 << 12) +#define RT5668_DAC_STO1_ASRC_SFT 12 +#define RT5668_AD_ASRC_MASK (0x1 << 8) +#define RT5668_AD_ASRC_SFT 8 +#define RT5668_AD_ASRC_SEL_MASK (0x1 << 4) +#define RT5668_AD_ASRC_SEL_SFT 4 +#define RT5668_DMIC_ASRC_MASK (0x1 << 3) +#define RT5668_DMIC_ASRC_SFT 3 +#define RT5668_ADC_STO1_ASRC_MASK (0x1 << 2) +#define RT5668_ADC_STO1_ASRC_SFT 2 +#define RT5668_DA_ASRC_SEL_MASK (0x1 << 0) +#define RT5668_DA_ASRC_SEL_SFT 0 + +/* PLL tracking mode 2 3 (0x0084)(0x0085)*/ +#define RT5668_FILTER_CLK_SEL_MASK (0x7 << 12) +#define RT5668_FILTER_CLK_SEL_SFT 12 + +/* ASRC Control 4 (0x0086) */ +#define RT5668_ASRCIN_FTK_N1_MASK (0x3 << 14) +#define RT5668_ASRCIN_FTK_N1_SFT 14 +#define RT5668_ASRCIN_FTK_N2_MASK (0x3 << 12) +#define RT5668_ASRCIN_FTK_N2_SFT 12 +#define RT5668_ASRCIN_FTK_M1_MASK (0x7 << 8) +#define RT5668_ASRCIN_FTK_M1_SFT 8 +#define RT5668_ASRCIN_FTK_M2_MASK (0x7 << 4) +#define RT5668_ASRCIN_FTK_M2_SFT 4 + +/* SoundWire reference clk (0x008d) */ +#define RT5668_PLL2_OUT_MASK (0x1 << 8) +#define RT5668_PLL2_OUT_98M (0x0 << 8) +#define RT5668_PLL2_OUT_49M (0x1 << 8) +#define RT5668_SDW_REF_2_MASK (0xf << 4) +#define RT5668_SDW_REF_2_SFT 4 +#define RT5668_SDW_REF_2_48K (0x0 << 4) +#define RT5668_SDW_REF_2_96K (0x1 << 4) +#define RT5668_SDW_REF_2_192K (0x2 << 4) +#define RT5668_SDW_REF_2_32K (0x3 << 4) +#define RT5668_SDW_REF_2_24K (0x4 << 4) +#define RT5668_SDW_REF_2_16K (0x5 << 4) +#define RT5668_SDW_REF_2_12K (0x6 << 4) +#define RT5668_SDW_REF_2_8K (0x7 << 4) +#define RT5668_SDW_REF_2_44K (0x8 << 4) +#define RT5668_SDW_REF_2_88K (0x9 << 4) +#define RT5668_SDW_REF_2_176K (0xa << 4) +#define RT5668_SDW_REF_2_353K (0xb << 4) +#define RT5668_SDW_REF_2_22K (0xc << 4) +#define RT5668_SDW_REF_2_384K (0xd << 4) +#define RT5668_SDW_REF_2_11K (0xe << 4) +#define RT5668_SDW_REF_1_MASK (0xf << 0) +#define RT5668_SDW_REF_1_SFT 0 +#define RT5668_SDW_REF_1_48K (0x0 << 0) +#define RT5668_SDW_REF_1_96K (0x1 << 0) +#define RT5668_SDW_REF_1_192K (0x2 << 0) +#define RT5668_SDW_REF_1_32K (0x3 << 0) +#define RT5668_SDW_REF_1_24K (0x4 << 0) +#define RT5668_SDW_REF_1_16K (0x5 << 0) +#define RT5668_SDW_REF_1_12K (0x6 << 0) +#define RT5668_SDW_REF_1_8K (0x7 << 0) +#define RT5668_SDW_REF_1_44K (0x8 << 0) +#define RT5668_SDW_REF_1_88K (0x9 << 0) +#define RT5668_SDW_REF_1_176K (0xa << 0) +#define RT5668_SDW_REF_1_353K (0xb << 0) +#define RT5668_SDW_REF_1_22K (0xc << 0) +#define RT5668_SDW_REF_1_384K (0xd << 0) +#define RT5668_SDW_REF_1_11K (0xe << 0) + +/* Depop Mode Control 1 (0x008e) */ +#define RT5668_PUMP_EN (0x1 << 3) +#define RT5668_PUMP_EN_SFT 3 +#define RT5668_CAPLESS_EN (0x1 << 0) +#define RT5668_CAPLESS_EN_SFT 0 + +/* Depop Mode Control 2 (0x8f) */ +#define RT5668_RAMP_MASK (0x1 << 12) +#define RT5668_RAMP_SFT 12 +#define RT5668_RAMP_DIS (0x0 << 12) +#define RT5668_RAMP_EN (0x1 << 12) +#define RT5668_BPS_MASK (0x1 << 11) +#define RT5668_BPS_SFT 11 +#define RT5668_BPS_DIS (0x0 << 11) +#define RT5668_BPS_EN (0x1 << 11) +#define RT5668_FAST_UPDN_MASK (0x1 << 10) +#define RT5668_FAST_UPDN_SFT 10 +#define RT5668_FAST_UPDN_DIS (0x0 << 10) +#define RT5668_FAST_UPDN_EN (0x1 << 10) +#define RT5668_VLO_MASK (0x1 << 7) +#define RT5668_VLO_SFT 7 +#define RT5668_VLO_3V (0x0 << 7) +#define RT5668_VLO_33V (0x1 << 7) + +/* HPOUT charge pump 1 (0x0091) */ +#define RT5668_OSW_L_MASK (0x1 << 11) +#define RT5668_OSW_L_SFT 11 +#define RT5668_OSW_L_DIS (0x0 << 11) +#define RT5668_OSW_L_EN (0x1 << 11) +#define RT5668_OSW_R_MASK (0x1 << 10) +#define RT5668_OSW_R_SFT 10 +#define RT5668_OSW_R_DIS (0x0 << 10) +#define RT5668_OSW_R_EN (0x1 << 10) +#define RT5668_PM_HP_MASK (0x3 << 8) +#define RT5668_PM_HP_SFT 8 +#define RT5668_PM_HP_LV (0x0 << 8) +#define RT5668_PM_HP_MV (0x1 << 8) +#define RT5668_PM_HP_HV (0x2 << 8) +#define RT5668_IB_HP_MASK (0x3 << 6) +#define RT5668_IB_HP_SFT 6 +#define RT5668_IB_HP_125IL (0x0 << 6) +#define RT5668_IB_HP_25IL (0x1 << 6) +#define RT5668_IB_HP_5IL (0x2 << 6) +#define RT5668_IB_HP_1IL (0x3 << 6) + +/* Micbias Control1 (0x93) */ +#define RT5668_MIC1_OV_MASK (0x3 << 14) +#define RT5668_MIC1_OV_SFT 14 +#define RT5668_MIC1_OV_2V7 (0x0 << 14) +#define RT5668_MIC1_OV_2V4 (0x1 << 14) +#define RT5668_MIC1_OV_2V25 (0x3 << 14) +#define RT5668_MIC1_OV_1V8 (0x4 << 14) +#define RT5668_MIC1_CLK_MASK (0x1 << 13) +#define RT5668_MIC1_CLK_SFT 13 +#define RT5668_MIC1_CLK_DIS (0x0 << 13) +#define RT5668_MIC1_CLK_EN (0x1 << 13) +#define RT5668_MIC1_OVCD_MASK (0x1 << 12) +#define RT5668_MIC1_OVCD_SFT 12 +#define RT5668_MIC1_OVCD_DIS (0x0 << 12) +#define RT5668_MIC1_OVCD_EN (0x1 << 12) +#define RT5668_MIC1_OVTH_MASK (0x3 << 10) +#define RT5668_MIC1_OVTH_SFT 10 +#define RT5668_MIC1_OVTH_768UA (0x0 << 10) +#define RT5668_MIC1_OVTH_960UA (0x1 << 10) +#define RT5668_MIC1_OVTH_1152UA (0x2 << 10) +#define RT5668_MIC1_OVTH_1960UA (0x3 << 10) +#define RT5668_MIC2_OV_MASK (0x3 << 8) +#define RT5668_MIC2_OV_SFT 8 +#define RT5668_MIC2_OV_2V7 (0x0 << 8) +#define RT5668_MIC2_OV_2V4 (0x1 << 8) +#define RT5668_MIC2_OV_2V25 (0x3 << 8) +#define RT5668_MIC2_OV_1V8 (0x4 << 8) +#define RT5668_MIC2_CLK_MASK (0x1 << 7) +#define RT5668_MIC2_CLK_SFT 7 +#define RT5668_MIC2_CLK_DIS (0x0 << 7) +#define RT5668_MIC2_CLK_EN (0x1 << 7) +#define RT5668_MIC2_OVTH_MASK (0x3 << 4) +#define RT5668_MIC2_OVTH_SFT 4 +#define RT5668_MIC2_OVTH_768UA (0x0 << 4) +#define RT5668_MIC2_OVTH_960UA (0x1 << 4) +#define RT5668_MIC2_OVTH_1152UA (0x2 << 4) +#define RT5668_MIC2_OVTH_1960UA (0x3 << 4) +#define RT5668_PWR_MB_MASK (0x1 << 3) +#define RT5668_PWR_MB_SFT 3 +#define RT5668_PWR_MB_PD (0x0 << 3) +#define RT5668_PWR_MB_PU (0x1 << 3) + +/* Micbias Control2 (0x0094) */ +#define RT5668_PWR_CLK25M_MASK (0x1 << 9) +#define RT5668_PWR_CLK25M_SFT 9 +#define RT5668_PWR_CLK25M_PD (0x0 << 9) +#define RT5668_PWR_CLK25M_PU (0x1 << 9) +#define RT5668_PWR_CLK1M_MASK (0x1 << 8) +#define RT5668_PWR_CLK1M_SFT 8 +#define RT5668_PWR_CLK1M_PD (0x0 << 8) +#define RT5668_PWR_CLK1M_PU (0x1 << 8) + +/* RC Clock Control (0x009f) */ +#define RT5668_POW_IRQ (0x1 << 15) +#define RT5668_POW_JDH (0x1 << 14) +#define RT5668_POW_JDL (0x1 << 13) +#define RT5668_POW_ANA (0x1 << 12) + +/* I2S Master Mode Clock Control 1 (0x00a0) */ +#define RT5668_CLK_SRC_MCLK (0x0) +#define RT5668_CLK_SRC_PLL1 (0x1) +#define RT5668_CLK_SRC_PLL2 (0x2) +#define RT5668_CLK_SRC_SDW (0x3) +#define RT5668_CLK_SRC_RCCLK (0x4) +#define RT5668_I2S_PD_1 (0x0) +#define RT5668_I2S_PD_2 (0x1) +#define RT5668_I2S_PD_3 (0x2) +#define RT5668_I2S_PD_4 (0x3) +#define RT5668_I2S_PD_6 (0x4) +#define RT5668_I2S_PD_8 (0x5) +#define RT5668_I2S_PD_12 (0x6) +#define RT5668_I2S_PD_16 (0x7) +#define RT5668_I2S_PD_24 (0x8) +#define RT5668_I2S_PD_32 (0x9) +#define RT5668_I2S_PD_48 (0xa) +#define RT5668_I2S2_SRC_MASK (0x3 << 4) +#define RT5668_I2S2_SRC_SFT 4 +#define RT5668_I2S2_M_PD_MASK (0xf << 0) +#define RT5668_I2S2_M_PD_SFT 0 + +/* IRQ Control 1 (0x00b6) */ +#define RT5668_JD1_PULSE_EN_MASK (0x1 << 10) +#define RT5668_JD1_PULSE_EN_SFT 10 +#define RT5668_JD1_PULSE_DIS (0x0 << 10) +#define RT5668_JD1_PULSE_EN (0x1 << 10) + +/* IRQ Control 2 (0x00b7) */ +#define RT5668_JD1_EN_MASK (0x1 << 15) +#define RT5668_JD1_EN_SFT 15 +#define RT5668_JD1_DIS (0x0 << 15) +#define RT5668_JD1_EN (0x1 << 15) +#define RT5668_JD1_POL_MASK (0x1 << 13) +#define RT5668_JD1_POL_NOR (0x0 << 13) +#define RT5668_JD1_POL_INV (0x1 << 13) + +/* IRQ Control 3 (0x00b8) */ +#define RT5668_IL_IRQ_MASK (0x1 << 7) +#define RT5668_IL_IRQ_DIS (0x0 << 7) +#define RT5668_IL_IRQ_EN (0x1 << 7) + +/* GPIO Control 1 (0x00c0) */ +#define RT5668_GP1_PIN_MASK (0x3 << 14) +#define RT5668_GP1_PIN_SFT 14 +#define RT5668_GP1_PIN_GPIO1 (0x0 << 14) +#define RT5668_GP1_PIN_IRQ (0x1 << 14) +#define RT5668_GP1_PIN_DMIC_CLK (0x2 << 14) +#define RT5668_GP2_PIN_MASK (0x3 << 12) +#define RT5668_GP2_PIN_SFT 12 +#define RT5668_GP2_PIN_GPIO2 (0x0 << 12) +#define RT5668_GP2_PIN_LRCK2 (0x1 << 12) +#define RT5668_GP2_PIN_DMIC_SDA (0x2 << 12) +#define RT5668_GP3_PIN_MASK (0x3 << 10) +#define RT5668_GP3_PIN_SFT 10 +#define RT5668_GP3_PIN_GPIO3 (0x0 << 10) +#define RT5668_GP3_PIN_BCLK2 (0x1 << 10) +#define RT5668_GP3_PIN_DMIC_CLK (0x2 << 10) +#define RT5668_GP4_PIN_MASK (0x3 << 8) +#define RT5668_GP4_PIN_SFT 8 +#define RT5668_GP4_PIN_GPIO4 (0x0 << 8) +#define RT5668_GP4_PIN_ADCDAT1 (0x1 << 8) +#define RT5668_GP4_PIN_DMIC_CLK (0x2 << 8) +#define RT5668_GP4_PIN_ADCDAT2 (0x3 << 8) +#define RT5668_GP5_PIN_MASK (0x3 << 6) +#define RT5668_GP5_PIN_SFT 6 +#define RT5668_GP5_PIN_GPIO5 (0x0 << 6) +#define RT5668_GP5_PIN_DACDAT1 (0x1 << 6) +#define RT5668_GP5_PIN_DMIC_SDA (0x2 << 6) +#define RT5668_GP6_PIN_MASK (0x1 << 5) +#define RT5668_GP6_PIN_SFT 5 +#define RT5668_GP6_PIN_GPIO6 (0x0 << 5) +#define RT5668_GP6_PIN_LRCK1 (0x1 << 5) + +/* GPIO Control 2 (0x00c1)*/ +#define RT5668_GP1_PF_MASK (0x1 << 15) +#define RT5668_GP1_PF_IN (0x0 << 15) +#define RT5668_GP1_PF_OUT (0x1 << 15) +#define RT5668_GP1_OUT_MASK (0x1 << 14) +#define RT5668_GP1_OUT_L (0x0 << 14) +#define RT5668_GP1_OUT_H (0x1 << 14) +#define RT5668_GP2_PF_MASK (0x1 << 13) +#define RT5668_GP2_PF_IN (0x0 << 13) +#define RT5668_GP2_PF_OUT (0x1 << 13) +#define RT5668_GP2_OUT_MASK (0x1 << 12) +#define RT5668_GP2_OUT_L (0x0 << 12) +#define RT5668_GP2_OUT_H (0x1 << 12) +#define RT5668_GP3_PF_MASK (0x1 << 11) +#define RT5668_GP3_PF_IN (0x0 << 11) +#define RT5668_GP3_PF_OUT (0x1 << 11) +#define RT5668_GP3_OUT_MASK (0x1 << 10) +#define RT5668_GP3_OUT_L (0x0 << 10) +#define RT5668_GP3_OUT_H (0x1 << 10) +#define RT5668_GP4_PF_MASK (0x1 << 9) +#define RT5668_GP4_PF_IN (0x0 << 9) +#define RT5668_GP4_PF_OUT (0x1 << 9) +#define RT5668_GP4_OUT_MASK (0x1 << 8) +#define RT5668_GP4_OUT_L (0x0 << 8) +#define RT5668_GP4_OUT_H (0x1 << 8) +#define RT5668_GP5_PF_MASK (0x1 << 7) +#define RT5668_GP5_PF_IN (0x0 << 7) +#define RT5668_GP5_PF_OUT (0x1 << 7) +#define RT5668_GP5_OUT_MASK (0x1 << 6) +#define RT5668_GP5_OUT_L (0x0 << 6) +#define RT5668_GP5_OUT_H (0x1 << 6) +#define RT5668_GP6_PF_MASK (0x1 << 5) +#define RT5668_GP6_PF_IN (0x0 << 5) +#define RT5668_GP6_PF_OUT (0x1 << 5) +#define RT5668_GP6_OUT_MASK (0x1 << 4) +#define RT5668_GP6_OUT_L (0x0 << 4) +#define RT5668_GP6_OUT_H (0x1 << 4) + + +/* GPIO Status (0x00c2) */ +#define RT5668_GP6_STA (0x1 << 6) +#define RT5668_GP5_STA (0x1 << 5) +#define RT5668_GP4_STA (0x1 << 4) +#define RT5668_GP3_STA (0x1 << 3) +#define RT5668_GP2_STA (0x1 << 2) +#define RT5668_GP1_STA (0x1 << 1) + +/* Soft volume and zero cross control 1 (0x00d9) */ +#define RT5668_SV_MASK (0x1 << 15) +#define RT5668_SV_SFT 15 +#define RT5668_SV_DIS (0x0 << 15) +#define RT5668_SV_EN (0x1 << 15) +#define RT5668_ZCD_MASK (0x1 << 10) +#define RT5668_ZCD_SFT 10 +#define RT5668_ZCD_PD (0x0 << 10) +#define RT5668_ZCD_PU (0x1 << 10) +#define RT5668_SV_DLY_MASK (0xf) +#define RT5668_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0x00da) */ +#define RT5668_ZCD_BST1_CBJ_MASK (0x1 << 7) +#define RT5668_ZCD_BST1_CBJ_SFT 7 +#define RT5668_ZCD_BST1_CBJ_DIS (0x0 << 7) +#define RT5668_ZCD_BST1_CBJ_EN (0x1 << 7) +#define RT5668_ZCD_RECMIX_MASK (0x1) +#define RT5668_ZCD_RECMIX_SFT 0 +#define RT5668_ZCD_RECMIX_DIS (0x0) +#define RT5668_ZCD_RECMIX_EN (0x1) + +/* 4 Button Inline Command Control 2 (0x00e3) */ +#define RT5668_4BTN_IL_MASK (0x1 << 15) +#define RT5668_4BTN_IL_EN (0x1 << 15) +#define RT5668_4BTN_IL_DIS (0x0 << 15) +#define RT5668_4BTN_IL_RST_MASK (0x1 << 14) +#define RT5668_4BTN_IL_NOR (0x1 << 14) +#define RT5668_4BTN_IL_RST (0x0 << 14) + +/* Analog JD Control (0x00f0) */ +#define RT5668_JDH_RS_MASK (0x1 << 4) +#define RT5668_JDH_NO_PLUG (0x1 << 4) +#define RT5668_JDH_PLUG (0x0 << 4) + +/* Chopper and Clock control for DAC (0x013a)*/ +#define RT5668_CKXEN_DAC1_MASK (0x1 << 13) +#define RT5668_CKXEN_DAC1_SFT 13 +#define RT5668_CKGEN_DAC1_MASK (0x1 << 12) +#define RT5668_CKGEN_DAC1_SFT 12 + +/* Chopper and Clock control for ADC (0x013b)*/ +#define RT5668_CKXEN_ADC1_MASK (0x1 << 13) +#define RT5668_CKXEN_ADC1_SFT 13 +#define RT5668_CKGEN_ADC1_MASK (0x1 << 12) +#define RT5668_CKGEN_ADC1_SFT 12 + +/* Volume test (0x013f)*/ +#define RT5668_SEL_CLK_VOL_MASK (0x1 << 15) +#define RT5668_SEL_CLK_VOL_EN (0x1 << 15) +#define RT5668_SEL_CLK_VOL_DIS (0x0 << 15) + +/* Test Mode Control 1 (0x0145) */ +#define RT5668_AD2DA_LB_MASK (0x1 << 10) +#define RT5668_AD2DA_LB_SFT 10 + +/* Stereo Noise Gate Control 1 (0x0160) */ +#define RT5668_NG2_EN_MASK (0x1 << 15) +#define RT5668_NG2_EN (0x1 << 15) +#define RT5668_NG2_DIS (0x0 << 15) + +/* Stereo1 DAC Silence Detection Control (0x0190) */ +#define RT5668_DEB_STO_DAC_MASK (0x7 << 4) +#define RT5668_DEB_80_MS (0x0 << 4) + +/* SAR ADC Inline Command Control 1 (0x0210) */ +#define RT5668_SAR_BUTT_DET_MASK (0x1 << 15) +#define RT5668_SAR_BUTT_DET_EN (0x1 << 15) +#define RT5668_SAR_BUTT_DET_DIS (0x0 << 15) +#define RT5668_SAR_BUTDET_MODE_MASK (0x1 << 14) +#define RT5668_SAR_BUTDET_POW_SAV (0x1 << 14) +#define RT5668_SAR_BUTDET_POW_NORM (0x0 << 14) +#define RT5668_SAR_BUTDET_RST_MASK (0x1 << 13) +#define RT5668_SAR_BUTDET_RST_NORMAL (0x1 << 13) +#define RT5668_SAR_BUTDET_RST (0x0 << 13) +#define RT5668_SAR_POW_MASK (0x1 << 12) +#define RT5668_SAR_POW_EN (0x1 << 12) +#define RT5668_SAR_POW_DIS (0x0 << 12) +#define RT5668_SAR_RST_MASK (0x1 << 11) +#define RT5668_SAR_RST_NORMAL (0x1 << 11) +#define RT5668_SAR_RST (0x0 << 11) +#define RT5668_SAR_BYPASS_MASK (0x1 << 10) +#define RT5668_SAR_BYPASS_EN (0x1 << 10) +#define RT5668_SAR_BYPASS_DIS (0x0 << 10) +#define RT5668_SAR_SEL_MB1_MASK (0x1 << 9) +#define RT5668_SAR_SEL_MB1_SEL (0x1 << 9) +#define RT5668_SAR_SEL_MB1_NOSEL (0x0 << 9) +#define RT5668_SAR_SEL_MB2_MASK (0x1 << 8) +#define RT5668_SAR_SEL_MB2_SEL (0x1 << 8) +#define RT5668_SAR_SEL_MB2_NOSEL (0x0 << 8) +#define RT5668_SAR_SEL_MODE_MASK (0x1 << 7) +#define RT5668_SAR_SEL_MODE_CMP (0x1 << 7) +#define RT5668_SAR_SEL_MODE_ADC (0x0 << 7) +#define RT5668_SAR_SEL_MB1_MB2_MASK (0x1 << 5) +#define RT5668_SAR_SEL_MB1_MB2_AUTO (0x1 << 5) +#define RT5668_SAR_SEL_MB1_MB2_MANU (0x0 << 5) +#define RT5668_SAR_SEL_SIGNAL_MASK (0x1 << 4) +#define RT5668_SAR_SEL_SIGNAL_AUTO (0x1 << 4) +#define RT5668_SAR_SEL_SIGNAL_MANU (0x0 << 4) + +/* SAR ADC Inline Command Control 13 (0x021c) */ +#define RT5668_SAR_SOUR_MASK (0x3f) +#define RT5668_SAR_SOUR_BTN (0x3f) +#define RT5668_SAR_SOUR_TYPE (0x0) + + +/* System Clock Source */ +enum { + RT5668_SCLK_S_MCLK, + RT5668_SCLK_S_PLL1, + RT5668_SCLK_S_PLL2, + RT5668_SCLK_S_RCCLK, +}; + +/* PLL Source */ +enum { + RT5668_PLL1_S_MCLK, + RT5668_PLL1_S_BCLK1, + RT5668_PLL1_S_RCCLK, +}; + +enum { + RT5668_AIF1, + RT5668_AIF2, + RT5668_AIFS +}; + +/* filter mask */ +enum { + RT5668_DA_STEREO1_FILTER = 0x1, + RT5668_AD_STEREO1_FILTER = (0x1 << 1), +}; + +enum { + RT5668_CLK_SEL_SYS, + RT5668_CLK_SEL_I2S1_ASRC, + RT5668_CLK_SEL_I2S2_ASRC, +}; + +int rt5668_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +#endif /* __RT5668_H__ */ diff --git a/sound/soc/codecs/rt5670-dsp.h b/sound/soc/codecs/rt5670-dsp.h new file mode 100644 index 000000000..a34d0cdb8 --- /dev/null +++ b/sound/soc/codecs/rt5670-dsp.h @@ -0,0 +1,54 @@ +/* + * rt5670-dsp.h -- RT5670 ALSA SoC DSP driver + * + * Copyright 2014 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5670_DSP_H__ +#define __RT5670_DSP_H__ + +#define RT5670_DSP_CTRL1 0xe0 +#define RT5670_DSP_CTRL2 0xe1 +#define RT5670_DSP_CTRL3 0xe2 +#define RT5670_DSP_CTRL4 0xe3 +#define RT5670_DSP_CTRL5 0xe4 + +/* DSP Control 1 (0xe0) */ +#define RT5670_DSP_CMD_MASK (0xff << 8) +#define RT5670_DSP_CMD_PE (0x0d << 8) /* Patch Entry */ +#define RT5670_DSP_CMD_MW (0x3b << 8) /* Memory Write */ +#define RT5670_DSP_CMD_MR (0x37 << 8) /* Memory Read */ +#define RT5670_DSP_CMD_RR (0x60 << 8) /* Register Read */ +#define RT5670_DSP_CMD_RW (0x68 << 8) /* Register Write */ +#define RT5670_DSP_REG_DATHI (0x26 << 8) /* High Data Addr */ +#define RT5670_DSP_REG_DATLO (0x25 << 8) /* Low Data Addr */ +#define RT5670_DSP_CLK_MASK (0x3 << 6) +#define RT5670_DSP_CLK_SFT 6 +#define RT5670_DSP_CLK_768K (0x0 << 6) +#define RT5670_DSP_CLK_384K (0x1 << 6) +#define RT5670_DSP_CLK_192K (0x2 << 6) +#define RT5670_DSP_CLK_96K (0x3 << 6) +#define RT5670_DSP_BUSY_MASK (0x1 << 5) +#define RT5670_DSP_RW_MASK (0x1 << 4) +#define RT5670_DSP_DL_MASK (0x3 << 2) +#define RT5670_DSP_DL_0 (0x0 << 2) +#define RT5670_DSP_DL_1 (0x1 << 2) +#define RT5670_DSP_DL_2 (0x2 << 2) +#define RT5670_DSP_DL_3 (0x3 << 2) +#define RT5670_DSP_I2C_AL_16 (0x1 << 1) +#define RT5670_DSP_CMD_EN (0x1) + +struct rt5670_dsp_param { + u16 cmd_fmt; + u16 addr; + u16 data; + u8 cmd; +}; + +#endif /* __RT5670_DSP_H__ */ + diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c new file mode 100644 index 000000000..6a2a58e10 --- /dev/null +++ b/sound/soc/codecs/rt5670.c @@ -0,0 +1,3250 @@ +/* + * rt5670.c -- RT5670 ALSA SoC audio codec driver + * + * Copyright 2014 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5670.h" +#include "rt5670-dsp.h" + +#define RT5670_DEV_GPIO BIT(0) +#define RT5670_IN2_DIFF BIT(1) +#define RT5670_DMIC_EN BIT(2) +#define RT5670_DMIC1_IN2P BIT(3) +#define RT5670_DMIC1_GPIO6 BIT(4) +#define RT5670_DMIC1_GPIO7 BIT(5) +#define RT5670_DMIC2_INR BIT(6) +#define RT5670_DMIC2_GPIO8 BIT(7) +#define RT5670_DMIC3_GPIO5 BIT(8) +#define RT5670_JD_MODE1 BIT(9) +#define RT5670_JD_MODE2 BIT(10) +#define RT5670_JD_MODE3 BIT(11) +#define RT5670_GPIO1_IS_EXT_SPK_EN BIT(12) + +static unsigned long rt5670_quirk; +static unsigned int quirk_override; +module_param_named(quirk, quirk_override, uint, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); + +#define RT5670_DEVICE_ID 0x6271 + +#define RT5670_PR_RANGE_BASE (0xff + 1) +#define RT5670_PR_SPACING 0x100 + +#define RT5670_PR_BASE (RT5670_PR_RANGE_BASE + (0 * RT5670_PR_SPACING)) + +static const struct regmap_range_cfg rt5670_ranges[] = { + { .name = "PR", .range_min = RT5670_PR_BASE, + .range_max = RT5670_PR_BASE + 0xf8, + .selector_reg = RT5670_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT5670_PRIV_DATA, + .window_len = 0x1, }, +}; + +static const struct reg_sequence init_list[] = { + { RT5670_PR_BASE + 0x14, 0x9a8a }, + { RT5670_PR_BASE + 0x38, 0x1fe1 }, + { RT5670_PR_BASE + 0x3d, 0x3640 }, + { 0x8a, 0x0123 }, +}; + +static const struct reg_default rt5670_reg[] = { + { 0x00, 0x0000 }, + { 0x02, 0x8888 }, + { 0x03, 0x8888 }, + { 0x0a, 0x0001 }, + { 0x0b, 0x0827 }, + { 0x0c, 0x0000 }, + { 0x0d, 0x0008 }, + { 0x0e, 0x0000 }, + { 0x0f, 0x0808 }, + { 0x19, 0xafaf }, + { 0x1a, 0xafaf }, + { 0x1b, 0x0011 }, + { 0x1c, 0x2f2f }, + { 0x1d, 0x2f2f }, + { 0x1e, 0x0000 }, + { 0x1f, 0x2f2f }, + { 0x20, 0x0000 }, + { 0x26, 0x7860 }, + { 0x27, 0x7860 }, + { 0x28, 0x7871 }, + { 0x29, 0x8080 }, + { 0x2a, 0x5656 }, + { 0x2b, 0x5454 }, + { 0x2c, 0xaaa0 }, + { 0x2d, 0x0000 }, + { 0x2e, 0x2f2f }, + { 0x2f, 0x1002 }, + { 0x30, 0x0000 }, + { 0x31, 0x5f00 }, + { 0x32, 0x0000 }, + { 0x33, 0x0000 }, + { 0x34, 0x0000 }, + { 0x35, 0x0000 }, + { 0x36, 0x0000 }, + { 0x37, 0x0000 }, + { 0x38, 0x0000 }, + { 0x3b, 0x0000 }, + { 0x3c, 0x007f }, + { 0x3d, 0x0000 }, + { 0x3e, 0x007f }, + { 0x45, 0xe00f }, + { 0x4c, 0x5380 }, + { 0x4f, 0x0073 }, + { 0x52, 0x00d3 }, + { 0x53, 0xf000 }, + { 0x61, 0x0000 }, + { 0x62, 0x0001 }, + { 0x63, 0x00c3 }, + { 0x64, 0x0000 }, + { 0x65, 0x0001 }, + { 0x66, 0x0000 }, + { 0x6f, 0x8000 }, + { 0x70, 0x8000 }, + { 0x71, 0x8000 }, + { 0x72, 0x8000 }, + { 0x73, 0x7770 }, + { 0x74, 0x0e00 }, + { 0x75, 0x1505 }, + { 0x76, 0x0015 }, + { 0x77, 0x0c00 }, + { 0x78, 0x4000 }, + { 0x79, 0x0123 }, + { 0x7f, 0x1100 }, + { 0x80, 0x0000 }, + { 0x81, 0x0000 }, + { 0x82, 0x0000 }, + { 0x83, 0x0000 }, + { 0x84, 0x0000 }, + { 0x85, 0x0000 }, + { 0x86, 0x0004 }, + { 0x87, 0x0000 }, + { 0x88, 0x0000 }, + { 0x89, 0x0000 }, + { 0x8a, 0x0123 }, + { 0x8b, 0x0000 }, + { 0x8c, 0x0003 }, + { 0x8d, 0x0000 }, + { 0x8e, 0x0004 }, + { 0x8f, 0x1100 }, + { 0x90, 0x0646 }, + { 0x91, 0x0c06 }, + { 0x93, 0x0000 }, + { 0x94, 0x1270 }, + { 0x95, 0x1000 }, + { 0x97, 0x0000 }, + { 0x98, 0x0000 }, + { 0x99, 0x0000 }, + { 0x9a, 0x2184 }, + { 0x9b, 0x010a }, + { 0x9c, 0x0aea }, + { 0x9d, 0x000c }, + { 0x9e, 0x0400 }, + { 0xae, 0x7000 }, + { 0xaf, 0x0000 }, + { 0xb0, 0x7000 }, + { 0xb1, 0x0000 }, + { 0xb2, 0x0000 }, + { 0xb3, 0x001f }, + { 0xb4, 0x220c }, + { 0xb5, 0x1f00 }, + { 0xb6, 0x0000 }, + { 0xb7, 0x0000 }, + { 0xbb, 0x0000 }, + { 0xbc, 0x0000 }, + { 0xbd, 0x0000 }, + { 0xbe, 0x0000 }, + { 0xbf, 0x0000 }, + { 0xc0, 0x0000 }, + { 0xc1, 0x0000 }, + { 0xc2, 0x0000 }, + { 0xcd, 0x0000 }, + { 0xce, 0x0000 }, + { 0xcf, 0x1813 }, + { 0xd0, 0x0690 }, + { 0xd1, 0x1c17 }, + { 0xd3, 0xa220 }, + { 0xd4, 0x0000 }, + { 0xd6, 0x0400 }, + { 0xd9, 0x0809 }, + { 0xda, 0x0000 }, + { 0xdb, 0x0001 }, + { 0xdc, 0x0049 }, + { 0xdd, 0x0024 }, + { 0xe6, 0x8000 }, + { 0xe7, 0x0000 }, + { 0xec, 0xa200 }, + { 0xed, 0x0000 }, + { 0xee, 0xa200 }, + { 0xef, 0x0000 }, + { 0xf8, 0x0000 }, + { 0xf9, 0x0000 }, + { 0xfa, 0x8010 }, + { 0xfb, 0x0033 }, + { 0xfc, 0x0100 }, +}; + +static bool rt5670_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5670_ranges); i++) { + if ((reg >= rt5670_ranges[i].window_start && + reg <= rt5670_ranges[i].window_start + + rt5670_ranges[i].window_len) || + (reg >= rt5670_ranges[i].range_min && + reg <= rt5670_ranges[i].range_max)) { + return true; + } + } + + switch (reg) { + case RT5670_RESET: + case RT5670_PDM_DATA_CTRL1: + case RT5670_PDM1_DATA_CTRL4: + case RT5670_PDM2_DATA_CTRL4: + case RT5670_PRIV_DATA: + case RT5670_ASRC_5: + case RT5670_CJ_CTRL1: + case RT5670_CJ_CTRL2: + case RT5670_CJ_CTRL3: + case RT5670_A_JD_CTRL1: + case RT5670_A_JD_CTRL2: + case RT5670_VAD_CTRL5: + case RT5670_ADC_EQ_CTRL1: + case RT5670_EQ_CTRL1: + case RT5670_ALC_CTRL_1: + case RT5670_IRQ_CTRL2: + case RT5670_INT_IRQ_ST: + case RT5670_IL_CMD: + case RT5670_DSP_CTRL1: + case RT5670_DSP_CTRL2: + case RT5670_DSP_CTRL3: + case RT5670_DSP_CTRL4: + case RT5670_DSP_CTRL5: + case RT5670_VENDOR_ID: + case RT5670_VENDOR_ID1: + case RT5670_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool rt5670_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5670_ranges); i++) { + if ((reg >= rt5670_ranges[i].window_start && + reg <= rt5670_ranges[i].window_start + + rt5670_ranges[i].window_len) || + (reg >= rt5670_ranges[i].range_min && + reg <= rt5670_ranges[i].range_max)) { + return true; + } + } + + switch (reg) { + case RT5670_RESET: + case RT5670_HP_VOL: + case RT5670_LOUT1: + case RT5670_CJ_CTRL1: + case RT5670_CJ_CTRL2: + case RT5670_CJ_CTRL3: + case RT5670_IN2: + case RT5670_INL1_INR1_VOL: + case RT5670_DAC1_DIG_VOL: + case RT5670_DAC2_DIG_VOL: + case RT5670_DAC_CTRL: + case RT5670_STO1_ADC_DIG_VOL: + case RT5670_MONO_ADC_DIG_VOL: + case RT5670_STO2_ADC_DIG_VOL: + case RT5670_ADC_BST_VOL1: + case RT5670_ADC_BST_VOL2: + case RT5670_STO2_ADC_MIXER: + case RT5670_STO1_ADC_MIXER: + case RT5670_MONO_ADC_MIXER: + case RT5670_AD_DA_MIXER: + case RT5670_STO_DAC_MIXER: + case RT5670_DD_MIXER: + case RT5670_DIG_MIXER: + case RT5670_DSP_PATH1: + case RT5670_DSP_PATH2: + case RT5670_DIG_INF1_DATA: + case RT5670_DIG_INF2_DATA: + case RT5670_PDM_OUT_CTRL: + case RT5670_PDM_DATA_CTRL1: + case RT5670_PDM1_DATA_CTRL2: + case RT5670_PDM1_DATA_CTRL3: + case RT5670_PDM1_DATA_CTRL4: + case RT5670_PDM2_DATA_CTRL2: + case RT5670_PDM2_DATA_CTRL3: + case RT5670_PDM2_DATA_CTRL4: + case RT5670_REC_L1_MIXER: + case RT5670_REC_L2_MIXER: + case RT5670_REC_R1_MIXER: + case RT5670_REC_R2_MIXER: + case RT5670_HPO_MIXER: + case RT5670_MONO_MIXER: + case RT5670_OUT_L1_MIXER: + case RT5670_OUT_R1_MIXER: + case RT5670_LOUT_MIXER: + case RT5670_PWR_DIG1: + case RT5670_PWR_DIG2: + case RT5670_PWR_ANLG1: + case RT5670_PWR_ANLG2: + case RT5670_PWR_MIXER: + case RT5670_PWR_VOL: + case RT5670_PRIV_INDEX: + case RT5670_PRIV_DATA: + case RT5670_I2S4_SDP: + case RT5670_I2S1_SDP: + case RT5670_I2S2_SDP: + case RT5670_I2S3_SDP: + case RT5670_ADDA_CLK1: + case RT5670_ADDA_CLK2: + case RT5670_DMIC_CTRL1: + case RT5670_DMIC_CTRL2: + case RT5670_TDM_CTRL_1: + case RT5670_TDM_CTRL_2: + case RT5670_TDM_CTRL_3: + case RT5670_DSP_CLK: + case RT5670_GLB_CLK: + case RT5670_PLL_CTRL1: + case RT5670_PLL_CTRL2: + case RT5670_ASRC_1: + case RT5670_ASRC_2: + case RT5670_ASRC_3: + case RT5670_ASRC_4: + case RT5670_ASRC_5: + case RT5670_ASRC_7: + case RT5670_ASRC_8: + case RT5670_ASRC_9: + case RT5670_ASRC_10: + case RT5670_ASRC_11: + case RT5670_ASRC_12: + case RT5670_ASRC_13: + case RT5670_ASRC_14: + case RT5670_DEPOP_M1: + case RT5670_DEPOP_M2: + case RT5670_DEPOP_M3: + case RT5670_CHARGE_PUMP: + case RT5670_MICBIAS: + case RT5670_A_JD_CTRL1: + case RT5670_A_JD_CTRL2: + case RT5670_VAD_CTRL1: + case RT5670_VAD_CTRL2: + case RT5670_VAD_CTRL3: + case RT5670_VAD_CTRL4: + case RT5670_VAD_CTRL5: + case RT5670_ADC_EQ_CTRL1: + case RT5670_ADC_EQ_CTRL2: + case RT5670_EQ_CTRL1: + case RT5670_EQ_CTRL2: + case RT5670_ALC_DRC_CTRL1: + case RT5670_ALC_DRC_CTRL2: + case RT5670_ALC_CTRL_1: + case RT5670_ALC_CTRL_2: + case RT5670_ALC_CTRL_3: + case RT5670_JD_CTRL: + case RT5670_IRQ_CTRL1: + case RT5670_IRQ_CTRL2: + case RT5670_INT_IRQ_ST: + case RT5670_GPIO_CTRL1: + case RT5670_GPIO_CTRL2: + case RT5670_GPIO_CTRL3: + case RT5670_SCRABBLE_FUN: + case RT5670_SCRABBLE_CTRL: + case RT5670_BASE_BACK: + case RT5670_MP3_PLUS1: + case RT5670_MP3_PLUS2: + case RT5670_ADJ_HPF1: + case RT5670_ADJ_HPF2: + case RT5670_HP_CALIB_AMP_DET: + case RT5670_SV_ZCD1: + case RT5670_SV_ZCD2: + case RT5670_IL_CMD: + case RT5670_IL_CMD2: + case RT5670_IL_CMD3: + case RT5670_DRC_HL_CTRL1: + case RT5670_DRC_HL_CTRL2: + case RT5670_ADC_MONO_HP_CTRL1: + case RT5670_ADC_MONO_HP_CTRL2: + case RT5670_ADC_STO2_HP_CTRL1: + case RT5670_ADC_STO2_HP_CTRL2: + case RT5670_JD_CTRL3: + case RT5670_JD_CTRL4: + case RT5670_DIG_MISC: + case RT5670_DSP_CTRL1: + case RT5670_DSP_CTRL2: + case RT5670_DSP_CTRL3: + case RT5670_DSP_CTRL4: + case RT5670_DSP_CTRL5: + case RT5670_GEN_CTRL2: + case RT5670_GEN_CTRL3: + case RT5670_VENDOR_ID: + case RT5670_VENDOR_ID1: + case RT5670_VENDOR_ID2: + return true; + default: + return false; + } +} + +/** + * rt5670_headset_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ + +static int rt5670_headset_detect(struct snd_soc_component *component, int jack_insert) +{ + int val; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + if (jack_insert) { + snd_soc_dapm_force_enable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + snd_soc_component_update_bits(component, RT5670_GEN_CTRL3, 0x4, 0x0); + snd_soc_component_update_bits(component, RT5670_CJ_CTRL2, + RT5670_CBJ_DET_MODE | RT5670_CBJ_MN_JD, + RT5670_CBJ_MN_JD); + snd_soc_component_write(component, RT5670_GPIO_CTRL2, 0x0004); + snd_soc_component_update_bits(component, RT5670_GPIO_CTRL1, + RT5670_GP1_PIN_MASK, RT5670_GP1_PIN_IRQ); + snd_soc_component_update_bits(component, RT5670_CJ_CTRL1, + RT5670_CBJ_BST1_EN, RT5670_CBJ_BST1_EN); + snd_soc_component_write(component, RT5670_JD_CTRL3, 0x00f0); + snd_soc_component_update_bits(component, RT5670_CJ_CTRL2, + RT5670_CBJ_MN_JD, RT5670_CBJ_MN_JD); + snd_soc_component_update_bits(component, RT5670_CJ_CTRL2, + RT5670_CBJ_MN_JD, 0); + msleep(300); + val = snd_soc_component_read32(component, RT5670_CJ_CTRL3) & 0x7; + if (val == 0x1 || val == 0x2) { + rt5670->jack_type = SND_JACK_HEADSET; + /* for push button */ + snd_soc_component_update_bits(component, RT5670_INT_IRQ_ST, 0x8, 0x8); + snd_soc_component_update_bits(component, RT5670_IL_CMD, 0x40, 0x40); + snd_soc_component_read32(component, RT5670_IL_CMD); + } else { + snd_soc_component_update_bits(component, RT5670_GEN_CTRL3, 0x4, 0x4); + rt5670->jack_type = SND_JACK_HEADPHONE; + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + } + } else { + snd_soc_component_update_bits(component, RT5670_INT_IRQ_ST, 0x8, 0x0); + snd_soc_component_update_bits(component, RT5670_GEN_CTRL3, 0x4, 0x4); + rt5670->jack_type = 0; + snd_soc_dapm_disable_pin(dapm, "Mic Det Power"); + snd_soc_dapm_sync(dapm); + } + + return rt5670->jack_type; +} + +void rt5670_jack_suspend(struct snd_soc_component *component) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + rt5670->jack_type_saved = rt5670->jack_type; + rt5670_headset_detect(component, 0); +} +EXPORT_SYMBOL_GPL(rt5670_jack_suspend); + +void rt5670_jack_resume(struct snd_soc_component *component) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + if (rt5670->jack_type_saved) + rt5670_headset_detect(component, 1); +} +EXPORT_SYMBOL_GPL(rt5670_jack_resume); + +static int rt5670_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read32(component, RT5670_IL_CMD); + btn_type = val & 0xff80; + snd_soc_component_write(component, RT5670_IL_CMD, val); + if (btn_type != 0) { + msleep(20); + val = snd_soc_component_read32(component, RT5670_IL_CMD); + snd_soc_component_write(component, RT5670_IL_CMD, val); + } + + return btn_type; +} + +static int rt5670_irq_detection(void *data) +{ + struct rt5670_priv *rt5670 = (struct rt5670_priv *)data; + struct snd_soc_jack_gpio *gpio = &rt5670->hp_gpio; + struct snd_soc_jack *jack = rt5670->jack; + int val, btn_type, report = jack->status; + + if (rt5670->pdata.jd_mode == 1) /* 2 port */ + val = snd_soc_component_read32(rt5670->component, RT5670_A_JD_CTRL1) & 0x0070; + else + val = snd_soc_component_read32(rt5670->component, RT5670_A_JD_CTRL1) & 0x0020; + + switch (val) { + /* jack in */ + case 0x30: /* 2 port */ + case 0x0: /* 1 port or 2 port */ + if (rt5670->jack_type == 0) { + report = rt5670_headset_detect(rt5670->component, 1); + /* for push button and jack out */ + gpio->debounce_time = 25; + break; + } + btn_type = 0; + if (snd_soc_component_read32(rt5670->component, RT5670_INT_IRQ_ST) & 0x4) { + /* button pressed */ + report = SND_JACK_HEADSET; + btn_type = rt5670_button_detect(rt5670->component); + switch (btn_type) { + case 0x2000: /* up */ + report |= SND_JACK_BTN_1; + break; + case 0x0400: /* center */ + report |= SND_JACK_BTN_0; + break; + case 0x0080: /* down */ + report |= SND_JACK_BTN_2; + break; + default: + dev_err(rt5670->component->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + } + if (btn_type == 0)/* button release */ + report = rt5670->jack_type; + + break; + /* jack out */ + case 0x70: /* 2 port */ + case 0x10: /* 2 port */ + case 0x20: /* 1 port */ + report = 0; + snd_soc_component_update_bits(rt5670->component, RT5670_INT_IRQ_ST, 0x1, 0x0); + rt5670_headset_detect(rt5670->component, 0); + gpio->debounce_time = 150; /* for jack in */ + break; + default: + break; + } + + return report; +} + +int rt5670_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + int ret; + + rt5670->jack = jack; + rt5670->hp_gpio.gpiod_dev = component->dev; + rt5670->hp_gpio.name = "headset"; + rt5670->hp_gpio.report = SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2; + rt5670->hp_gpio.debounce_time = 150; + rt5670->hp_gpio.wake = true; + rt5670->hp_gpio.data = (struct rt5670_priv *)rt5670; + rt5670->hp_gpio.jack_status_check = rt5670_irq_detection; + + ret = snd_soc_jack_add_gpios(rt5670->jack, 1, + &rt5670->hp_gpio); + if (ret) { + dev_err(component->dev, "Adding jack GPIO failed\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rt5670_set_jack_detect); + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* Interface data select */ +static const char * const rt5670_data_select[] = { + "Normal", "Swap", "left copy to right", "right copy to left" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_if2_dac_enum, RT5670_DIG_INF1_DATA, + RT5670_IF2_DAC_SEL_SFT, rt5670_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5670_if2_adc_enum, RT5670_DIG_INF1_DATA, + RT5670_IF2_ADC_SEL_SFT, rt5670_data_select); + +static const struct snd_kcontrol_new rt5670_snd_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE("HP Playback Switch", RT5670_HP_VOL, + RT5670_L_MUTE_SFT, RT5670_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("HP Playback Volume", RT5670_HP_VOL, + RT5670_L_VOL_SFT, RT5670_R_VOL_SFT, + 39, 1, out_vol_tlv), + /* OUTPUT Control */ + SOC_DOUBLE("OUT Channel Switch", RT5670_LOUT1, + RT5670_VOL_L_SFT, RT5670_VOL_R_SFT, 1, 1), + SOC_DOUBLE_TLV("OUT Playback Volume", RT5670_LOUT1, + RT5670_L_VOL_SFT, RT5670_R_VOL_SFT, 39, 1, out_vol_tlv), + /* DAC Digital Volume */ + SOC_DOUBLE("DAC2 Playback Switch", RT5670_DAC_CTRL, + RT5670_M_DAC_L2_VOL_SFT, RT5670_M_DAC_R2_VOL_SFT, 1, 1), + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5670_DAC1_DIG_VOL, + RT5670_L_VOL_SFT, RT5670_R_VOL_SFT, + 175, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("Mono DAC Playback Volume", RT5670_DAC2_DIG_VOL, + RT5670_L_VOL_SFT, RT5670_R_VOL_SFT, + 175, 0, dac_vol_tlv), + /* IN1/IN2 Control */ + SOC_SINGLE_TLV("IN1 Boost Volume", RT5670_CJ_CTRL1, + RT5670_BST_SFT1, 8, 0, bst_tlv), + SOC_SINGLE_TLV("IN2 Boost Volume", RT5670_IN2, + RT5670_BST_SFT1, 8, 0, bst_tlv), + /* INL/INR Volume Control */ + SOC_DOUBLE_TLV("IN Capture Volume", RT5670_INL1_INR1_VOL, + RT5670_INL_VOL_SFT, RT5670_INR_VOL_SFT, + 31, 1, in_vol_tlv), + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC Capture Switch", RT5670_STO1_ADC_DIG_VOL, + RT5670_L_MUTE_SFT, RT5670_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("ADC Capture Volume", RT5670_STO1_ADC_DIG_VOL, + RT5670_L_VOL_SFT, RT5670_R_VOL_SFT, + 127, 0, adc_vol_tlv), + + SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5670_MONO_ADC_DIG_VOL, + RT5670_L_VOL_SFT, RT5670_R_VOL_SFT, + 127, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Gain Volume", RT5670_ADC_BST_VOL1, + RT5670_STO1_ADC_L_BST_SFT, RT5670_STO1_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + SOC_DOUBLE_TLV("STO2 ADC Boost Gain Volume", RT5670_ADC_BST_VOL1, + RT5670_STO2_ADC_L_BST_SFT, RT5670_STO2_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), + + SOC_ENUM("ADC IF2 Data Switch", rt5670_if2_adc_enum), + SOC_ENUM("DAC IF2 Data Switch", rt5670_if2_dac_enum), +}; + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + * Choose dmic clock between 1MHz and 3MHz. + * It is better for clock to approximate 3MHz. + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + int idx, rate; + + rate = rt5670->sysclk / rl6231_get_pre_div(rt5670->regmap, + RT5670_ADDA_CLK1, RT5670_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rate); + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else + snd_soc_component_update_bits(component, RT5670_DMIC_CTRL1, + RT5670_DMIC_CLK_MASK, idx << RT5670_DMIC_CLK_SFT); + return idx; +} + +static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + if (rt5670->sysclk_src == RT5670_SCLK_S_PLL1) + return 1; + else + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg, shift, val; + + switch (source->shift) { + case 0: + reg = RT5670_ASRC_3; + shift = 0; + break; + case 1: + reg = RT5670_ASRC_3; + shift = 4; + break; + case 2: + reg = RT5670_ASRC_5; + shift = 12; + break; + case 3: + reg = RT5670_ASRC_2; + shift = 0; + break; + case 8: + reg = RT5670_ASRC_2; + shift = 4; + break; + case 9: + reg = RT5670_ASRC_2; + shift = 8; + break; + case 10: + reg = RT5670_ASRC_2; + shift = 12; + break; + default: + return 0; + } + + val = (snd_soc_component_read32(component, reg) >> shift) & 0xf; + switch (val) { + case 1: + case 2: + case 3: + case 4: + return 1; + default: + return 0; + } + +} + +static int can_use_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + if (rt5670->sysclk > rt5670->lrck[RT5670_AIF1] * 384) + return 1; + + return 0; +} + + +/** + * rt5670_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5670 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the codec driver will turn on ASRC + * for these filters if ASRC is selected as their clock source. + */ +int rt5670_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + unsigned int asrc2_mask = 0, asrc2_value = 0; + unsigned int asrc3_mask = 0, asrc3_value = 0; + + if (clk_src > RT5670_CLK_SEL_SYS3) + return -EINVAL; + + if (filter_mask & RT5670_DA_STEREO_FILTER) { + asrc2_mask |= RT5670_DA_STO_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5670_DA_STO_CLK_SEL_MASK) + | (clk_src << RT5670_DA_STO_CLK_SEL_SFT); + } + + if (filter_mask & RT5670_DA_MONO_L_FILTER) { + asrc2_mask |= RT5670_DA_MONOL_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5670_DA_MONOL_CLK_SEL_MASK) + | (clk_src << RT5670_DA_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5670_DA_MONO_R_FILTER) { + asrc2_mask |= RT5670_DA_MONOR_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5670_DA_MONOR_CLK_SEL_MASK) + | (clk_src << RT5670_DA_MONOR_CLK_SEL_SFT); + } + + if (filter_mask & RT5670_AD_STEREO_FILTER) { + asrc2_mask |= RT5670_AD_STO1_CLK_SEL_MASK; + asrc2_value = (asrc2_value & ~RT5670_AD_STO1_CLK_SEL_MASK) + | (clk_src << RT5670_AD_STO1_CLK_SEL_SFT); + } + + if (filter_mask & RT5670_AD_MONO_L_FILTER) { + asrc3_mask |= RT5670_AD_MONOL_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5670_AD_MONOL_CLK_SEL_MASK) + | (clk_src << RT5670_AD_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5670_AD_MONO_R_FILTER) { + asrc3_mask |= RT5670_AD_MONOR_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5670_AD_MONOR_CLK_SEL_MASK) + | (clk_src << RT5670_AD_MONOR_CLK_SEL_SFT); + } + + if (filter_mask & RT5670_UP_RATE_FILTER) { + asrc3_mask |= RT5670_UP_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5670_UP_CLK_SEL_MASK) + | (clk_src << RT5670_UP_CLK_SEL_SFT); + } + + if (filter_mask & RT5670_DOWN_RATE_FILTER) { + asrc3_mask |= RT5670_DOWN_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5670_DOWN_CLK_SEL_MASK) + | (clk_src << RT5670_DOWN_CLK_SEL_SFT); + } + + if (asrc2_mask) + snd_soc_component_update_bits(component, RT5670_ASRC_2, + asrc2_mask, asrc2_value); + + if (asrc3_mask) + snd_soc_component_update_bits(component, RT5670_ASRC_3, + asrc3_mask, asrc3_value); + return 0; +} +EXPORT_SYMBOL_GPL(rt5670_sel_asrc_clk_src); + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5670_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5670_STO1_ADC_MIXER, + RT5670_M_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5670_STO1_ADC_MIXER, + RT5670_M_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5670_STO1_ADC_MIXER, + RT5670_M_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5670_STO1_ADC_MIXER, + RT5670_M_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_sto2_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5670_STO2_ADC_MIXER, + RT5670_M_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5670_STO2_ADC_MIXER, + RT5670_M_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_sto2_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5670_STO2_ADC_MIXER, + RT5670_M_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5670_STO2_ADC_MIXER, + RT5670_M_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_mono_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5670_MONO_ADC_MIXER, + RT5670_M_MONO_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5670_MONO_ADC_MIXER, + RT5670_M_MONO_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_mono_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5670_MONO_ADC_MIXER, + RT5670_M_MONO_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5670_MONO_ADC_MIXER, + RT5670_M_MONO_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5670_AD_DA_MIXER, + RT5670_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5670_AD_DA_MIXER, + RT5670_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5670_AD_DA_MIXER, + RT5670_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5670_AD_DA_MIXER, + RT5670_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_sto_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5670_STO_DAC_MIXER, + RT5670_M_DAC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5670_STO_DAC_MIXER, + RT5670_M_DAC_L2_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5670_STO_DAC_MIXER, + RT5670_M_DAC_R1_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_sto_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5670_STO_DAC_MIXER, + RT5670_M_DAC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5670_STO_DAC_MIXER, + RT5670_M_DAC_R2_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5670_STO_DAC_MIXER, + RT5670_M_DAC_L1_STO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_mono_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5670_DD_MIXER, + RT5670_M_DAC_L1_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5670_DD_MIXER, + RT5670_M_DAC_L2_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5670_DD_MIXER, + RT5670_M_DAC_R2_MONO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_mono_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5670_DD_MIXER, + RT5670_M_DAC_R1_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5670_DD_MIXER, + RT5670_M_DAC_R2_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5670_DD_MIXER, + RT5670_M_DAC_L2_MONO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_dig_l_mix[] = { + SOC_DAPM_SINGLE("Sto DAC Mix L Switch", RT5670_DIG_MIXER, + RT5670_M_STO_L_DAC_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5670_DIG_MIXER, + RT5670_M_DAC_L2_DAC_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5670_DIG_MIXER, + RT5670_M_DAC_R2_DAC_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_dig_r_mix[] = { + SOC_DAPM_SINGLE("Sto DAC Mix R Switch", RT5670_DIG_MIXER, + RT5670_M_STO_R_DAC_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5670_DIG_MIXER, + RT5670_M_DAC_R2_DAC_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5670_DIG_MIXER, + RT5670_M_DAC_L2_DAC_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5670_rec_l_mix[] = { + SOC_DAPM_SINGLE("INL Switch", RT5670_REC_L2_MIXER, + RT5670_M_IN_L_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5670_REC_L2_MIXER, + RT5670_M_BST2_RM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5670_REC_L2_MIXER, + RT5670_M_BST1_RM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_rec_r_mix[] = { + SOC_DAPM_SINGLE("INR Switch", RT5670_REC_R2_MIXER, + RT5670_M_IN_R_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST2 Switch", RT5670_REC_R2_MIXER, + RT5670_M_BST2_RM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("BST1 Switch", RT5670_REC_R2_MIXER, + RT5670_M_BST1_RM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_out_l_mix[] = { + SOC_DAPM_SINGLE("BST1 Switch", RT5670_OUT_L1_MIXER, + RT5670_M_BST1_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5670_OUT_L1_MIXER, + RT5670_M_IN_L_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L2 Switch", RT5670_OUT_L1_MIXER, + RT5670_M_DAC_L2_OM_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC L1 Switch", RT5670_OUT_L1_MIXER, + RT5670_M_DAC_L1_OM_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_out_r_mix[] = { + SOC_DAPM_SINGLE("BST2 Switch", RT5670_OUT_R1_MIXER, + RT5670_M_BST2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5670_OUT_R1_MIXER, + RT5670_M_IN_R_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R2 Switch", RT5670_OUT_R1_MIXER, + RT5670_M_DAC_R2_OM_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5670_OUT_R1_MIXER, + RT5670_M_DAC_R1_OM_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_hpo_mix[] = { + SOC_DAPM_SINGLE("DAC1 Switch", RT5670_HPO_MIXER, + RT5670_M_DAC1_HM_SFT, 1, 1), + SOC_DAPM_SINGLE("HPVOL Switch", RT5670_HPO_MIXER, + RT5670_M_HPVOL_HM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_hpvoll_mix[] = { + SOC_DAPM_SINGLE("DAC1 Switch", RT5670_HPO_MIXER, + RT5670_M_DACL1_HML_SFT, 1, 1), + SOC_DAPM_SINGLE("INL Switch", RT5670_HPO_MIXER, + RT5670_M_INL1_HML_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_hpvolr_mix[] = { + SOC_DAPM_SINGLE("DAC1 Switch", RT5670_HPO_MIXER, + RT5670_M_DACR1_HMR_SFT, 1, 1), + SOC_DAPM_SINGLE("INR Switch", RT5670_HPO_MIXER, + RT5670_M_INR1_HMR_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_lout_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5670_LOUT_MIXER, + RT5670_M_DAC_L1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5670_LOUT_MIXER, + RT5670_M_DAC_R1_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTMIX L Switch", RT5670_LOUT_MIXER, + RT5670_M_OV_L_LM_SFT, 1, 1), + SOC_DAPM_SINGLE("OUTMIX R Switch", RT5670_LOUT_MIXER, + RT5670_M_OV_R_LM_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_hpl_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5670_HPO_MIXER, + RT5670_M_DACL1_HML_SFT, 1, 1), + SOC_DAPM_SINGLE("INL1 Switch", RT5670_HPO_MIXER, + RT5670_M_INL1_HML_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5670_hpr_mix[] = { + SOC_DAPM_SINGLE("DAC R1 Switch", RT5670_HPO_MIXER, + RT5670_M_DACR1_HMR_SFT, 1, 1), + SOC_DAPM_SINGLE("INR1 Switch", RT5670_HPO_MIXER, + RT5670_M_INR1_HMR_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new lout_l_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5670_LOUT1, + RT5670_L_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new lout_r_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5670_LOUT1, + RT5670_R_MUTE_SFT, 1, 1); + +/* DAC1 L/R source */ /* MX-29 [9:8] [11:10] */ +static const char * const rt5670_dac1_src[] = { + "IF1 DAC", "IF2 DAC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_dac1l_enum, RT5670_AD_DA_MIXER, + RT5670_DAC1_L_SEL_SFT, rt5670_dac1_src); + +static const struct snd_kcontrol_new rt5670_dac1l_mux = + SOC_DAPM_ENUM("DAC1 L source", rt5670_dac1l_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_dac1r_enum, RT5670_AD_DA_MIXER, + RT5670_DAC1_R_SEL_SFT, rt5670_dac1_src); + +static const struct snd_kcontrol_new rt5670_dac1r_mux = + SOC_DAPM_ENUM("DAC1 R source", rt5670_dac1r_enum); + +/*DAC2 L/R source*/ /* MX-1B [6:4] [2:0] */ +/* TODO Use SOC_VALUE_ENUM_SINGLE_DECL */ +static const char * const rt5670_dac12_src[] = { + "IF1 DAC", "IF2 DAC", "IF3 DAC", "TxDC DAC", + "Bass", "VAD_ADC", "IF4 DAC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_dac2l_enum, RT5670_DAC_CTRL, + RT5670_DAC2_L_SEL_SFT, rt5670_dac12_src); + +static const struct snd_kcontrol_new rt5670_dac_l2_mux = + SOC_DAPM_ENUM("DAC2 L source", rt5670_dac2l_enum); + +static const char * const rt5670_dacr2_src[] = { + "IF1 DAC", "IF2 DAC", "IF3 DAC", "TxDC DAC", "TxDP ADC", "IF4 DAC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_dac2r_enum, RT5670_DAC_CTRL, + RT5670_DAC2_R_SEL_SFT, rt5670_dacr2_src); + +static const struct snd_kcontrol_new rt5670_dac_r2_mux = + SOC_DAPM_ENUM("DAC2 R source", rt5670_dac2r_enum); + +/*RxDP source*/ /* MX-2D [15:13] */ +static const char * const rt5670_rxdp_src[] = { + "IF2 DAC", "IF1 DAC", "STO1 ADC Mixer", "STO2 ADC Mixer", + "Mono ADC Mixer L", "Mono ADC Mixer R", "DAC1" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_rxdp_enum, RT5670_DSP_PATH1, + RT5670_RXDP_SEL_SFT, rt5670_rxdp_src); + +static const struct snd_kcontrol_new rt5670_rxdp_mux = + SOC_DAPM_ENUM("DAC2 L source", rt5670_rxdp_enum); + +/* MX-2D [1] [0] */ +static const char * const rt5670_dsp_bypass_src[] = { + "DSP", "Bypass" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_dsp_ul_enum, RT5670_DSP_PATH1, + RT5670_DSP_UL_SFT, rt5670_dsp_bypass_src); + +static const struct snd_kcontrol_new rt5670_dsp_ul_mux = + SOC_DAPM_ENUM("DSP UL source", rt5670_dsp_ul_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_dsp_dl_enum, RT5670_DSP_PATH1, + RT5670_DSP_DL_SFT, rt5670_dsp_bypass_src); + +static const struct snd_kcontrol_new rt5670_dsp_dl_mux = + SOC_DAPM_ENUM("DSP DL source", rt5670_dsp_dl_enum); + +/* Stereo2 ADC source */ +/* MX-26 [15] */ +static const char * const rt5670_stereo2_adc_lr_src[] = { + "L", "LR" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo2_adc_lr_enum, RT5670_STO2_ADC_MIXER, + RT5670_STO2_ADC_SRC_SFT, rt5670_stereo2_adc_lr_src); + +static const struct snd_kcontrol_new rt5670_sto2_adc_lr_mux = + SOC_DAPM_ENUM("Stereo2 ADC LR source", rt5670_stereo2_adc_lr_enum); + +/* Stereo1 ADC source */ +/* MX-27 MX-26 [12] */ +static const char * const rt5670_stereo_adc1_src[] = { + "DAC MIX", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo1_adc1_enum, RT5670_STO1_ADC_MIXER, + RT5670_ADC_1_SRC_SFT, rt5670_stereo_adc1_src); + +static const struct snd_kcontrol_new rt5670_sto_adc_1_mux = + SOC_DAPM_ENUM("Stereo1 ADC 1 Mux", rt5670_stereo1_adc1_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo2_adc1_enum, RT5670_STO2_ADC_MIXER, + RT5670_ADC_1_SRC_SFT, rt5670_stereo_adc1_src); + +static const struct snd_kcontrol_new rt5670_sto2_adc_1_mux = + SOC_DAPM_ENUM("Stereo2 ADC 1 Mux", rt5670_stereo2_adc1_enum); + + +/* MX-27 MX-26 [11] */ +static const char * const rt5670_stereo_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo1_adc2_enum, RT5670_STO1_ADC_MIXER, + RT5670_ADC_2_SRC_SFT, rt5670_stereo_adc2_src); + +static const struct snd_kcontrol_new rt5670_sto_adc_2_mux = + SOC_DAPM_ENUM("Stereo1 ADC 2 Mux", rt5670_stereo1_adc2_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo2_adc2_enum, RT5670_STO2_ADC_MIXER, + RT5670_ADC_2_SRC_SFT, rt5670_stereo_adc2_src); + +static const struct snd_kcontrol_new rt5670_sto2_adc_2_mux = + SOC_DAPM_ENUM("Stereo2 ADC 2 Mux", rt5670_stereo2_adc2_enum); + + +/* MX-27 MX26 [10] */ +static const char * const rt5670_stereo_adc_src[] = { + "ADC1L ADC2R", "ADC3" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo1_adc_enum, RT5670_STO1_ADC_MIXER, + RT5670_ADC_SRC_SFT, rt5670_stereo_adc_src); + +static const struct snd_kcontrol_new rt5670_sto_adc_mux = + SOC_DAPM_ENUM("Stereo1 ADC source", rt5670_stereo1_adc_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo2_adc_enum, RT5670_STO2_ADC_MIXER, + RT5670_ADC_SRC_SFT, rt5670_stereo_adc_src); + +static const struct snd_kcontrol_new rt5670_sto2_adc_mux = + SOC_DAPM_ENUM("Stereo2 ADC source", rt5670_stereo2_adc_enum); + +/* MX-27 MX-26 [9:8] */ +static const char * const rt5670_stereo_dmic_src[] = { + "DMIC1", "DMIC2", "DMIC3" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo1_dmic_enum, RT5670_STO1_ADC_MIXER, + RT5670_DMIC_SRC_SFT, rt5670_stereo_dmic_src); + +static const struct snd_kcontrol_new rt5670_sto1_dmic_mux = + SOC_DAPM_ENUM("Stereo1 DMIC source", rt5670_stereo1_dmic_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo2_dmic_enum, RT5670_STO2_ADC_MIXER, + RT5670_DMIC_SRC_SFT, rt5670_stereo_dmic_src); + +static const struct snd_kcontrol_new rt5670_sto2_dmic_mux = + SOC_DAPM_ENUM("Stereo2 DMIC source", rt5670_stereo2_dmic_enum); + +/* MX-27 [0] */ +static const char * const rt5670_stereo_dmic3_src[] = { + "DMIC3", "PDM ADC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_stereo_dmic3_enum, RT5670_STO1_ADC_MIXER, + RT5670_DMIC3_SRC_SFT, rt5670_stereo_dmic3_src); + +static const struct snd_kcontrol_new rt5670_sto_dmic3_mux = + SOC_DAPM_ENUM("Stereo DMIC3 source", rt5670_stereo_dmic3_enum); + +/* Mono ADC source */ +/* MX-28 [12] */ +static const char * const rt5670_mono_adc_l1_src[] = { + "Mono DAC MIXL", "ADC1" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_mono_adc_l1_enum, RT5670_MONO_ADC_MIXER, + RT5670_MONO_ADC_L1_SRC_SFT, rt5670_mono_adc_l1_src); + +static const struct snd_kcontrol_new rt5670_mono_adc_l1_mux = + SOC_DAPM_ENUM("Mono ADC1 left source", rt5670_mono_adc_l1_enum); +/* MX-28 [11] */ +static const char * const rt5670_mono_adc_l2_src[] = { + "Mono DAC MIXL", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_mono_adc_l2_enum, RT5670_MONO_ADC_MIXER, + RT5670_MONO_ADC_L2_SRC_SFT, rt5670_mono_adc_l2_src); + +static const struct snd_kcontrol_new rt5670_mono_adc_l2_mux = + SOC_DAPM_ENUM("Mono ADC2 left source", rt5670_mono_adc_l2_enum); + +/* MX-28 [9:8] */ +static const char * const rt5670_mono_dmic_src[] = { + "DMIC1", "DMIC2", "DMIC3" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_mono_dmic_l_enum, RT5670_MONO_ADC_MIXER, + RT5670_MONO_DMIC_L_SRC_SFT, rt5670_mono_dmic_src); + +static const struct snd_kcontrol_new rt5670_mono_dmic_l_mux = + SOC_DAPM_ENUM("Mono DMIC left source", rt5670_mono_dmic_l_enum); +/* MX-28 [1:0] */ +static SOC_ENUM_SINGLE_DECL(rt5670_mono_dmic_r_enum, RT5670_MONO_ADC_MIXER, + RT5670_MONO_DMIC_R_SRC_SFT, rt5670_mono_dmic_src); + +static const struct snd_kcontrol_new rt5670_mono_dmic_r_mux = + SOC_DAPM_ENUM("Mono DMIC Right source", rt5670_mono_dmic_r_enum); +/* MX-28 [4] */ +static const char * const rt5670_mono_adc_r1_src[] = { + "Mono DAC MIXR", "ADC2" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_mono_adc_r1_enum, RT5670_MONO_ADC_MIXER, + RT5670_MONO_ADC_R1_SRC_SFT, rt5670_mono_adc_r1_src); + +static const struct snd_kcontrol_new rt5670_mono_adc_r1_mux = + SOC_DAPM_ENUM("Mono ADC1 right source", rt5670_mono_adc_r1_enum); +/* MX-28 [3] */ +static const char * const rt5670_mono_adc_r2_src[] = { + "Mono DAC MIXR", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_mono_adc_r2_enum, RT5670_MONO_ADC_MIXER, + RT5670_MONO_ADC_R2_SRC_SFT, rt5670_mono_adc_r2_src); + +static const struct snd_kcontrol_new rt5670_mono_adc_r2_mux = + SOC_DAPM_ENUM("Mono ADC2 right source", rt5670_mono_adc_r2_enum); + +/* MX-2D [3:2] */ +static const char * const rt5670_txdp_slot_src[] = { + "Slot 0-1", "Slot 2-3", "Slot 4-5", "Slot 6-7" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_txdp_slot_enum, RT5670_DSP_PATH1, + RT5670_TXDP_SLOT_SEL_SFT, rt5670_txdp_slot_src); + +static const struct snd_kcontrol_new rt5670_txdp_slot_mux = + SOC_DAPM_ENUM("TxDP Slot source", rt5670_txdp_slot_enum); + +/* MX-2F [15] */ +static const char * const rt5670_if1_adc2_in_src[] = { + "IF_ADC2", "VAD_ADC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_if1_adc2_in_enum, RT5670_DIG_INF1_DATA, + RT5670_IF1_ADC2_IN_SFT, rt5670_if1_adc2_in_src); + +static const struct snd_kcontrol_new rt5670_if1_adc2_in_mux = + SOC_DAPM_ENUM("IF1 ADC2 IN source", rt5670_if1_adc2_in_enum); + +/* MX-2F [14:12] */ +static const char * const rt5670_if2_adc_in_src[] = { + "IF_ADC1", "IF_ADC2", "IF_ADC3", "TxDC_DAC", "TxDP_ADC", "VAD_ADC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_if2_adc_in_enum, RT5670_DIG_INF1_DATA, + RT5670_IF2_ADC_IN_SFT, rt5670_if2_adc_in_src); + +static const struct snd_kcontrol_new rt5670_if2_adc_in_mux = + SOC_DAPM_ENUM("IF2 ADC IN source", rt5670_if2_adc_in_enum); + +/* MX-30 [5:4] */ +static const char * const rt5670_if4_adc_in_src[] = { + "IF_ADC1", "IF_ADC2", "IF_ADC3" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_if4_adc_in_enum, RT5670_DIG_INF2_DATA, + RT5670_IF4_ADC_IN_SFT, rt5670_if4_adc_in_src); + +static const struct snd_kcontrol_new rt5670_if4_adc_in_mux = + SOC_DAPM_ENUM("IF4 ADC IN source", rt5670_if4_adc_in_enum); + +/* MX-31 [15] [13] [11] [9] */ +static const char * const rt5670_pdm_src[] = { + "Mono DAC", "Stereo DAC" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_pdm1_l_enum, RT5670_PDM_OUT_CTRL, + RT5670_PDM1_L_SFT, rt5670_pdm_src); + +static const struct snd_kcontrol_new rt5670_pdm1_l_mux = + SOC_DAPM_ENUM("PDM1 L source", rt5670_pdm1_l_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_pdm1_r_enum, RT5670_PDM_OUT_CTRL, + RT5670_PDM1_R_SFT, rt5670_pdm_src); + +static const struct snd_kcontrol_new rt5670_pdm1_r_mux = + SOC_DAPM_ENUM("PDM1 R source", rt5670_pdm1_r_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_pdm2_l_enum, RT5670_PDM_OUT_CTRL, + RT5670_PDM2_L_SFT, rt5670_pdm_src); + +static const struct snd_kcontrol_new rt5670_pdm2_l_mux = + SOC_DAPM_ENUM("PDM2 L source", rt5670_pdm2_l_enum); + +static SOC_ENUM_SINGLE_DECL(rt5670_pdm2_r_enum, RT5670_PDM_OUT_CTRL, + RT5670_PDM2_R_SFT, rt5670_pdm_src); + +static const struct snd_kcontrol_new rt5670_pdm2_r_mux = + SOC_DAPM_ENUM("PDM2 R source", rt5670_pdm2_r_enum); + +/* MX-FA [12] */ +static const char * const rt5670_if1_adc1_in1_src[] = { + "IF_ADC1", "IF1_ADC3" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_if1_adc1_in1_enum, RT5670_DIG_MISC, + RT5670_IF1_ADC1_IN1_SFT, rt5670_if1_adc1_in1_src); + +static const struct snd_kcontrol_new rt5670_if1_adc1_in1_mux = + SOC_DAPM_ENUM("IF1 ADC1 IN1 source", rt5670_if1_adc1_in1_enum); + +/* MX-FA [11] */ +static const char * const rt5670_if1_adc1_in2_src[] = { + "IF1_ADC1_IN1", "IF1_ADC4" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_if1_adc1_in2_enum, RT5670_DIG_MISC, + RT5670_IF1_ADC1_IN2_SFT, rt5670_if1_adc1_in2_src); + +static const struct snd_kcontrol_new rt5670_if1_adc1_in2_mux = + SOC_DAPM_ENUM("IF1 ADC1 IN2 source", rt5670_if1_adc1_in2_enum); + +/* MX-FA [10] */ +static const char * const rt5670_if1_adc2_in1_src[] = { + "IF1_ADC2_IN", "IF1_ADC4" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_if1_adc2_in1_enum, RT5670_DIG_MISC, + RT5670_IF1_ADC2_IN1_SFT, rt5670_if1_adc2_in1_src); + +static const struct snd_kcontrol_new rt5670_if1_adc2_in1_mux = + SOC_DAPM_ENUM("IF1 ADC2 IN1 source", rt5670_if1_adc2_in1_enum); + +/* MX-9D [9:8] */ +static const char * const rt5670_vad_adc_src[] = { + "Sto1 ADC L", "Mono ADC L", "Mono ADC R", "Sto2 ADC L" +}; + +static SOC_ENUM_SINGLE_DECL(rt5670_vad_adc_enum, RT5670_VAD_CTRL4, + RT5670_VAD_SEL_SFT, rt5670_vad_adc_src); + +static const struct snd_kcontrol_new rt5670_vad_adc_mux = + SOC_DAPM_ENUM("VAD ADC source", rt5670_vad_adc_enum); + +static int rt5670_hp_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rt5670->regmap, RT5670_CHARGE_PUMP, + RT5670_PM_HP_MASK, RT5670_PM_HP_HV); + regmap_update_bits(rt5670->regmap, RT5670_GEN_CTRL2, + 0x0400, 0x0400); + /* headphone amp power on */ + regmap_update_bits(rt5670->regmap, RT5670_PWR_ANLG1, + RT5670_PWR_HA | RT5670_PWR_FV1 | + RT5670_PWR_FV2, RT5670_PWR_HA | + RT5670_PWR_FV1 | RT5670_PWR_FV2); + /* depop parameters */ + regmap_write(rt5670->regmap, RT5670_DEPOP_M2, 0x3100); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x8009); + regmap_write(rt5670->regmap, RT5670_PR_BASE + + RT5670_HP_DCC_INT1, 0x9f00); + mdelay(20); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x8019); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x0004); + msleep(30); + break; + default: + return 0; + } + + return 0; +} + +static int rt5670_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* headphone unmute sequence */ + regmap_write(rt5670->regmap, RT5670_PR_BASE + + RT5670_MAMP_INT_REG2, 0xb400); + regmap_write(rt5670->regmap, RT5670_DEPOP_M3, 0x0772); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x805d); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x831d); + regmap_update_bits(rt5670->regmap, RT5670_GEN_CTRL2, + 0x0300, 0x0300); + regmap_update_bits(rt5670->regmap, RT5670_HP_VOL, + RT5670_L_MUTE | RT5670_R_MUTE, 0); + msleep(80); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x8019); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* headphone mute sequence */ + regmap_write(rt5670->regmap, RT5670_PR_BASE + + RT5670_MAMP_INT_REG2, 0xb400); + regmap_write(rt5670->regmap, RT5670_DEPOP_M3, 0x0772); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x803d); + mdelay(10); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x831d); + mdelay(10); + regmap_update_bits(rt5670->regmap, RT5670_HP_VOL, + RT5670_L_MUTE | RT5670_R_MUTE, + RT5670_L_MUTE | RT5670_R_MUTE); + msleep(20); + regmap_update_bits(rt5670->regmap, + RT5670_GEN_CTRL2, 0x0300, 0x0); + regmap_write(rt5670->regmap, RT5670_DEPOP_M1, 0x8019); + regmap_write(rt5670->regmap, RT5670_DEPOP_M3, 0x0707); + regmap_write(rt5670->regmap, RT5670_PR_BASE + + RT5670_MAMP_INT_REG2, 0xfc00); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5670_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + if (!rt5670->pdata.gpio1_is_ext_spk_en) + return 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL2, + RT5670_GP1_OUT_MASK, RT5670_GP1_OUT_HI); + break; + + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL2, + RT5670_GP1_OUT_MASK, RT5670_GP1_OUT_LO); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5670_bst1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5670_PWR_ANLG2, + RT5670_PWR_BST1_P, RT5670_PWR_BST1_P); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5670_PWR_ANLG2, + RT5670_PWR_BST1_P, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5670_bst2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, RT5670_PWR_ANLG2, + RT5670_PWR_BST2_P, RT5670_PWR_BST2_P); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, RT5670_PWR_ANLG2, + RT5670_PWR_BST2_P, 0); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5670_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PLL1", RT5670_PWR_ANLG2, + RT5670_PWR_PLL_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S DSP", RT5670_PWR_DIG2, + RT5670_PWR_I2S_DSP_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Det Power", RT5670_PWR_VOL, + RT5670_PWR_MIC_DET_BIT, 0, NULL, 0), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("I2S1 ASRC", 1, RT5670_ASRC_1, + 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5670_ASRC_1, + 12, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC STO ASRC", 1, RT5670_ASRC_1, + 10, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO L ASRC", 1, RT5670_ASRC_1, + 9, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO R ASRC", 1, RT5670_ASRC_1, + 8, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO1 ASRC", 1, RT5670_ASRC_1, + 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO2 ASRC", 1, RT5670_ASRC_1, + 6, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO L ASRC", 1, RT5670_ASRC_1, + 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO R ASRC", 1, RT5670_ASRC_1, + 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5670_ASRC_1, + 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO2 ASRC", 1, RT5670_ASRC_1, + 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO L ASRC", 1, RT5670_ASRC_1, + 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO R ASRC", 1, RT5670_ASRC_1, + 0, 0, NULL, 0), + + /* Input Side */ + /* micbias */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5670_PWR_ANLG2, + RT5670_PWR_MB1_BIT, 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + SND_SOC_DAPM_INPUT("DMIC L2"), + SND_SOC_DAPM_INPUT("DMIC R2"), + SND_SOC_DAPM_INPUT("DMIC L3"), + SND_SOC_DAPM_INPUT("DMIC R3"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + + SND_SOC_DAPM_PGA("DMIC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC3", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5670_DMIC_CTRL1, + RT5670_DMIC_1_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC2 Power", RT5670_DMIC_CTRL1, + RT5670_DMIC_2_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC3 Power", RT5670_DMIC_CTRL1, + RT5670_DMIC_3_EN_SFT, 0, NULL, 0), + /* Boost */ + SND_SOC_DAPM_PGA_E("BST1", RT5670_PWR_ANLG2, RT5670_PWR_BST1_BIT, + 0, NULL, 0, rt5670_bst1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("BST2", RT5670_PWR_ANLG2, RT5670_PWR_BST2_BIT, + 0, NULL, 0, rt5670_bst2_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + /* Input Volume */ + SND_SOC_DAPM_PGA("INL VOL", RT5670_PWR_VOL, + RT5670_PWR_IN_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("INR VOL", RT5670_PWR_VOL, + RT5670_PWR_IN_R_BIT, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIXL", RT5670_PWR_MIXER, RT5670_PWR_RM_L_BIT, 0, + rt5670_rec_l_mix, ARRAY_SIZE(rt5670_rec_l_mix)), + SND_SOC_DAPM_MIXER("RECMIXR", RT5670_PWR_MIXER, RT5670_PWR_RM_R_BIT, 0, + rt5670_rec_r_mix, ARRAY_SIZE(rt5670_rec_r_mix)), + /* ADCs */ + SND_SOC_DAPM_ADC("ADC 1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC 2", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_PGA("ADC 1_2", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("ADC 1 power", RT5670_PWR_DIG1, + RT5670_PWR_ADC_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC 2 power", RT5670_PWR_DIG1, + RT5670_PWR_ADC_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC clock", RT5670_PR_BASE + + RT5670_CHOP_DAC_ADC, 12, 0, NULL, 0), + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto_adc_2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto_adc_2_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto_adc_1_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto_adc_1_mux), + SND_SOC_DAPM_MUX("Stereo2 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto2_dmic_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto2_adc_2_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto2_adc_2_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto2_adc_1_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto2_adc_1_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC LR Mux", SND_SOC_NOPM, 0, 0, + &rt5670_sto2_adc_lr_mux), + SND_SOC_DAPM_MUX("Mono DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5670_mono_dmic_l_mux), + SND_SOC_DAPM_MUX("Mono DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5670_mono_dmic_r_mux), + SND_SOC_DAPM_MUX("Mono ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_mono_adc_l2_mux), + SND_SOC_DAPM_MUX("Mono ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_mono_adc_l1_mux), + SND_SOC_DAPM_MUX("Mono ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_mono_adc_r1_mux), + SND_SOC_DAPM_MUX("Mono ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_mono_adc_r2_mux), + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("ADC Stereo1 Filter", RT5670_PWR_DIG2, + RT5670_PWR_ADC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC Stereo2 Filter", RT5670_PWR_DIG2, + RT5670_PWR_ADC_S2F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Sto1 ADC MIXL", RT5670_STO1_ADC_DIG_VOL, + RT5670_L_MUTE_SFT, 1, rt5670_sto1_adc_l_mix, + ARRAY_SIZE(rt5670_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto1 ADC MIXR", RT5670_STO1_ADC_DIG_VOL, + RT5670_R_MUTE_SFT, 1, rt5670_sto1_adc_r_mix, + ARRAY_SIZE(rt5670_sto1_adc_r_mix)), + SND_SOC_DAPM_MIXER("Sto2 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5670_sto2_adc_l_mix, + ARRAY_SIZE(rt5670_sto2_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto2 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5670_sto2_adc_r_mix, + ARRAY_SIZE(rt5670_sto2_adc_r_mix)), + SND_SOC_DAPM_SUPPLY("ADC Mono Left Filter", RT5670_PWR_DIG2, + RT5670_PWR_ADC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXL", RT5670_MONO_ADC_DIG_VOL, + RT5670_L_MUTE_SFT, 1, rt5670_mono_adc_l_mix, + ARRAY_SIZE(rt5670_mono_adc_l_mix)), + SND_SOC_DAPM_SUPPLY("ADC Mono Right Filter", RT5670_PWR_DIG2, + RT5670_PWR_ADC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXR", RT5670_MONO_ADC_DIG_VOL, + RT5670_R_MUTE_SFT, 1, rt5670_mono_adc_r_mix, + ARRAY_SIZE(rt5670_mono_adc_r_mix)), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIXL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo1 ADC MIXR", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIXL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIXR", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Sto2 ADC LR MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo1 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("VAD_ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF_ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF_ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF_ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1_ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DSP */ + SND_SOC_DAPM_PGA("TxDP_ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("TxDP_ADC_L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("TxDP_ADC_R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("TxDC_DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("TDM Data Mux", SND_SOC_NOPM, 0, 0, + &rt5670_txdp_slot_mux), + + SND_SOC_DAPM_MUX("DSP UL Mux", SND_SOC_NOPM, 0, 0, + &rt5670_dsp_ul_mux), + SND_SOC_DAPM_MUX("DSP DL Mux", SND_SOC_NOPM, 0, 0, + &rt5670_dsp_dl_mux), + + SND_SOC_DAPM_MUX("RxDP Mux", SND_SOC_NOPM, 0, 0, + &rt5670_rxdp_mux), + + /* IF2 Mux */ + SND_SOC_DAPM_MUX("IF2 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5670_if2_adc_in_mux), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5670_PWR_DIG1, + RT5670_PWR_I2S1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2 R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2", RT5670_PWR_DIG1, + RT5670_PWR_I2S2_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("IF1 ADC1 IN1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_if1_adc1_in1_mux), + SND_SOC_DAPM_MUX("IF1 ADC1 IN2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_if1_adc1_in2_mux), + SND_SOC_DAPM_MUX("IF1 ADC2 IN Mux", SND_SOC_NOPM, 0, 0, + &rt5670_if1_adc2_in_mux), + SND_SOC_DAPM_MUX("IF1 ADC2 IN1 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_if1_adc2_in1_mux), + SND_SOC_DAPM_MUX("VAD ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5670_vad_adc_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, + RT5670_GPIO_CTRL1, RT5670_I2S2_PIN_SFT, 1), + + /* Audio DSP */ + SND_SOC_DAPM_PGA("Audio DSP", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5670_dac_l_mix, ARRAY_SIZE(rt5670_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5670_dac_r_mix, ARRAY_SIZE(rt5670_dac_r_mix)), + SND_SOC_DAPM_PGA("DAC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DAC2 channel Mux */ + SND_SOC_DAPM_MUX("DAC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_dac_l2_mux), + SND_SOC_DAPM_MUX("DAC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5670_dac_r2_mux), + SND_SOC_DAPM_PGA("DAC L2 Volume", RT5670_PWR_DIG1, + RT5670_PWR_DAC_L2_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC R2 Volume", RT5670_PWR_DIG1, + RT5670_PWR_DAC_R2_BIT, 0, NULL, 0), + + SND_SOC_DAPM_MUX("DAC1 L Mux", SND_SOC_NOPM, 0, 0, &rt5670_dac1l_mux), + SND_SOC_DAPM_MUX("DAC1 R Mux", SND_SOC_NOPM, 0, 0, &rt5670_dac1r_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("DAC Stereo1 Filter", RT5670_PWR_DIG2, + RT5670_PWR_DAC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Mono Left Filter", RT5670_PWR_DIG2, + RT5670_PWR_DAC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC Mono Right Filter", RT5670_PWR_DIG2, + RT5670_PWR_DAC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5670_sto_dac_l_mix, + ARRAY_SIZE(rt5670_sto_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5670_sto_dac_r_mix, + ARRAY_SIZE(rt5670_sto_dac_r_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5670_mono_dac_l_mix, + ARRAY_SIZE(rt5670_mono_dac_l_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5670_mono_dac_r_mix, + ARRAY_SIZE(rt5670_mono_dac_r_mix)), + SND_SOC_DAPM_MIXER("DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5670_dig_l_mix, + ARRAY_SIZE(rt5670_dig_l_mix)), + SND_SOC_DAPM_MIXER("DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5670_dig_r_mix, + ARRAY_SIZE(rt5670_dig_r_mix)), + + /* DACs */ + SND_SOC_DAPM_SUPPLY("DAC L1 Power", RT5670_PWR_DIG1, + RT5670_PWR_DAC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAC R1 Power", RT5670_PWR_DIG1, + RT5670_PWR_DAC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_DAC("DAC L1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC L2", NULL, RT5670_PWR_DIG1, + RT5670_PWR_DAC_L2_BIT, 0), + + SND_SOC_DAPM_DAC("DAC R2", NULL, RT5670_PWR_DIG1, + RT5670_PWR_DAC_R2_BIT, 0), + /* OUT Mixer */ + + SND_SOC_DAPM_MIXER("OUT MIXL", RT5670_PWR_MIXER, RT5670_PWR_OM_L_BIT, + 0, rt5670_out_l_mix, ARRAY_SIZE(rt5670_out_l_mix)), + SND_SOC_DAPM_MIXER("OUT MIXR", RT5670_PWR_MIXER, RT5670_PWR_OM_R_BIT, + 0, rt5670_out_r_mix, ARRAY_SIZE(rt5670_out_r_mix)), + /* Ouput Volume */ + SND_SOC_DAPM_MIXER("HPOVOL MIXL", RT5670_PWR_VOL, + RT5670_PWR_HV_L_BIT, 0, + rt5670_hpvoll_mix, ARRAY_SIZE(rt5670_hpvoll_mix)), + SND_SOC_DAPM_MIXER("HPOVOL MIXR", RT5670_PWR_VOL, + RT5670_PWR_HV_R_BIT, 0, + rt5670_hpvolr_mix, ARRAY_SIZE(rt5670_hpvolr_mix)), + SND_SOC_DAPM_PGA("DAC 1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DAC 2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPOVOL", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* HPO/LOUT/Mono Mixer */ + SND_SOC_DAPM_MIXER("HPO MIX", SND_SOC_NOPM, 0, 0, + rt5670_hpo_mix, ARRAY_SIZE(rt5670_hpo_mix)), + SND_SOC_DAPM_MIXER("LOUT MIX", RT5670_PWR_ANLG1, RT5670_PWR_LM_BIT, + 0, rt5670_lout_mix, ARRAY_SIZE(rt5670_lout_mix)), + SND_SOC_DAPM_SUPPLY_S("Improve HP Amp Drv", 1, SND_SOC_NOPM, 0, 0, + rt5670_hp_power_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("HP L Amp", RT5670_PWR_ANLG1, + RT5670_PWR_HP_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HP R Amp", RT5670_PWR_ANLG1, + RT5670_PWR_HP_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, + rt5670_hp_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SWITCH("LOUT L Playback", SND_SOC_NOPM, 0, 0, + &lout_l_enable_control), + SND_SOC_DAPM_SWITCH("LOUT R Playback", SND_SOC_NOPM, 0, 0, + &lout_r_enable_control), + SND_SOC_DAPM_PGA("LOUT Amp", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* PDM */ + SND_SOC_DAPM_SUPPLY("PDM1 Power", RT5670_PWR_DIG2, + RT5670_PWR_PDM1_BIT, 0, NULL, 0), + + SND_SOC_DAPM_MUX("PDM1 L Mux", RT5670_PDM_OUT_CTRL, + RT5670_M_PDM1_L_SFT, 1, &rt5670_pdm1_l_mux), + SND_SOC_DAPM_MUX("PDM1 R Mux", RT5670_PDM_OUT_CTRL, + RT5670_M_PDM1_R_SFT, 1, &rt5670_pdm1_r_mux), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + SND_SOC_DAPM_OUTPUT("LOUTL"), + SND_SOC_DAPM_OUTPUT("LOUTR"), +}; + +static const struct snd_soc_dapm_widget rt5670_specific_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PDM2 Power", RT5670_PWR_DIG2, + RT5670_PWR_PDM2_BIT, 0, NULL, 0), + SND_SOC_DAPM_MUX("PDM2 L Mux", RT5670_PDM_OUT_CTRL, + RT5670_M_PDM2_L_SFT, 1, &rt5670_pdm2_l_mux), + SND_SOC_DAPM_MUX("PDM2 R Mux", RT5670_PDM_OUT_CTRL, + RT5670_M_PDM2_R_SFT, 1, &rt5670_pdm2_r_mux), + SND_SOC_DAPM_OUTPUT("PDM1L"), + SND_SOC_DAPM_OUTPUT("PDM1R"), + SND_SOC_DAPM_OUTPUT("PDM2L"), + SND_SOC_DAPM_OUTPUT("PDM2R"), +}; + +static const struct snd_soc_dapm_widget rt5672_specific_dapm_widgets[] = { + SND_SOC_DAPM_PGA_E("SPO Amp", SND_SOC_NOPM, 0, 0, NULL, 0, + rt5670_spk_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_OUTPUT("SPOLP"), + SND_SOC_DAPM_OUTPUT("SPOLN"), + SND_SOC_DAPM_OUTPUT("SPORP"), + SND_SOC_DAPM_OUTPUT("SPORN"), +}; + +static const struct snd_soc_dapm_route rt5670_dapm_routes[] = { + { "ADC Stereo1 Filter", NULL, "ADC STO1 ASRC", is_using_asrc }, + { "ADC Stereo2 Filter", NULL, "ADC STO2 ASRC", is_using_asrc }, + { "ADC Mono Left Filter", NULL, "ADC MONO L ASRC", is_using_asrc }, + { "ADC Mono Right Filter", NULL, "ADC MONO R ASRC", is_using_asrc }, + { "DAC Mono Left Filter", NULL, "DAC MONO L ASRC", is_using_asrc }, + { "DAC Mono Right Filter", NULL, "DAC MONO R ASRC", is_using_asrc }, + { "DAC Stereo1 Filter", NULL, "DAC STO ASRC", is_using_asrc }, + { "Stereo1 DMIC Mux", NULL, "DMIC STO1 ASRC", can_use_asrc }, + { "Stereo2 DMIC Mux", NULL, "DMIC STO2 ASRC", can_use_asrc }, + { "Mono DMIC L Mux", NULL, "DMIC MONO L ASRC", can_use_asrc }, + { "Mono DMIC R Mux", NULL, "DMIC MONO R ASRC", can_use_asrc }, + + { "I2S1", NULL, "I2S1 ASRC", can_use_asrc}, + { "I2S2", NULL, "I2S2 ASRC", can_use_asrc}, + + { "DMIC1", NULL, "DMIC L1" }, + { "DMIC1", NULL, "DMIC R1" }, + { "DMIC2", NULL, "DMIC L2" }, + { "DMIC2", NULL, "DMIC R2" }, + { "DMIC3", NULL, "DMIC L3" }, + { "DMIC3", NULL, "DMIC R3" }, + + { "BST1", NULL, "IN1P" }, + { "BST1", NULL, "IN1N" }, + { "BST1", NULL, "Mic Det Power" }, + { "BST2", NULL, "IN2P" }, + { "BST2", NULL, "IN2N" }, + + { "INL VOL", NULL, "IN2P" }, + { "INR VOL", NULL, "IN2N" }, + + { "RECMIXL", "INL Switch", "INL VOL" }, + { "RECMIXL", "BST2 Switch", "BST2" }, + { "RECMIXL", "BST1 Switch", "BST1" }, + + { "RECMIXR", "INR Switch", "INR VOL" }, + { "RECMIXR", "BST2 Switch", "BST2" }, + { "RECMIXR", "BST1 Switch", "BST1" }, + + { "ADC 1", NULL, "RECMIXL" }, + { "ADC 1", NULL, "ADC 1 power" }, + { "ADC 1", NULL, "ADC clock" }, + { "ADC 2", NULL, "RECMIXR" }, + { "ADC 2", NULL, "ADC 2 power" }, + { "ADC 2", NULL, "ADC clock" }, + + { "DMIC L1", NULL, "DMIC CLK" }, + { "DMIC L1", NULL, "DMIC1 Power" }, + { "DMIC R1", NULL, "DMIC CLK" }, + { "DMIC R1", NULL, "DMIC1 Power" }, + { "DMIC L2", NULL, "DMIC CLK" }, + { "DMIC L2", NULL, "DMIC2 Power" }, + { "DMIC R2", NULL, "DMIC CLK" }, + { "DMIC R2", NULL, "DMIC2 Power" }, + { "DMIC L3", NULL, "DMIC CLK" }, + { "DMIC L3", NULL, "DMIC3 Power" }, + { "DMIC R3", NULL, "DMIC CLK" }, + { "DMIC R3", NULL, "DMIC3 Power" }, + + { "Stereo1 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo1 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo1 DMIC Mux", "DMIC3", "DMIC3" }, + + { "Stereo2 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo2 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo2 DMIC Mux", "DMIC3", "DMIC3" }, + + { "Mono DMIC L Mux", "DMIC1", "DMIC L1" }, + { "Mono DMIC L Mux", "DMIC2", "DMIC L2" }, + { "Mono DMIC L Mux", "DMIC3", "DMIC L3" }, + + { "Mono DMIC R Mux", "DMIC1", "DMIC R1" }, + { "Mono DMIC R Mux", "DMIC2", "DMIC R2" }, + { "Mono DMIC R Mux", "DMIC3", "DMIC R3" }, + + { "ADC 1_2", NULL, "ADC 1" }, + { "ADC 1_2", NULL, "ADC 2" }, + + { "Stereo1 ADC L2 Mux", "DMIC", "Stereo1 DMIC Mux" }, + { "Stereo1 ADC L2 Mux", "DAC MIX", "DAC MIXL" }, + { "Stereo1 ADC L1 Mux", "ADC", "ADC 1_2" }, + { "Stereo1 ADC L1 Mux", "DAC MIX", "DAC MIXL" }, + + { "Stereo1 ADC R1 Mux", "ADC", "ADC 1_2" }, + { "Stereo1 ADC R1 Mux", "DAC MIX", "DAC MIXR" }, + { "Stereo1 ADC R2 Mux", "DMIC", "Stereo1 DMIC Mux" }, + { "Stereo1 ADC R2 Mux", "DAC MIX", "DAC MIXR" }, + + { "Mono ADC L2 Mux", "DMIC", "Mono DMIC L Mux" }, + { "Mono ADC L2 Mux", "Mono DAC MIXL", "Mono DAC MIXL" }, + { "Mono ADC L1 Mux", "Mono DAC MIXL", "Mono DAC MIXL" }, + { "Mono ADC L1 Mux", "ADC1", "ADC 1" }, + + { "Mono ADC R1 Mux", "Mono DAC MIXR", "Mono DAC MIXR" }, + { "Mono ADC R1 Mux", "ADC2", "ADC 2" }, + { "Mono ADC R2 Mux", "DMIC", "Mono DMIC R Mux" }, + { "Mono ADC R2 Mux", "Mono DAC MIXR", "Mono DAC MIXR" }, + + { "Sto1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux" }, + { "Sto1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux" }, + { "Sto1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux" }, + { "Sto1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux" }, + + { "Stereo1 ADC MIXL", NULL, "Sto1 ADC MIXL" }, + { "Stereo1 ADC MIXL", NULL, "ADC Stereo1 Filter" }, + + { "Stereo1 ADC MIXR", NULL, "Sto1 ADC MIXR" }, + { "Stereo1 ADC MIXR", NULL, "ADC Stereo1 Filter" }, + { "ADC Stereo1 Filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Mono ADC MIXL", "ADC1 Switch", "Mono ADC L1 Mux" }, + { "Mono ADC MIXL", "ADC2 Switch", "Mono ADC L2 Mux" }, + { "Mono ADC MIXL", NULL, "ADC Mono Left Filter" }, + { "ADC Mono Left Filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Mono ADC MIXR", "ADC1 Switch", "Mono ADC R1 Mux" }, + { "Mono ADC MIXR", "ADC2 Switch", "Mono ADC R2 Mux" }, + { "Mono ADC MIXR", NULL, "ADC Mono Right Filter" }, + { "ADC Mono Right Filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Stereo2 ADC L2 Mux", "DMIC", "Stereo2 DMIC Mux" }, + { "Stereo2 ADC L2 Mux", "DAC MIX", "DAC MIXL" }, + { "Stereo2 ADC L1 Mux", "ADC", "ADC 1_2" }, + { "Stereo2 ADC L1 Mux", "DAC MIX", "DAC MIXL" }, + + { "Stereo2 ADC R1 Mux", "ADC", "ADC 1_2" }, + { "Stereo2 ADC R1 Mux", "DAC MIX", "DAC MIXR" }, + { "Stereo2 ADC R2 Mux", "DMIC", "Stereo2 DMIC Mux" }, + { "Stereo2 ADC R2 Mux", "DAC MIX", "DAC MIXR" }, + + { "Sto2 ADC MIXL", "ADC1 Switch", "Stereo2 ADC L1 Mux" }, + { "Sto2 ADC MIXL", "ADC2 Switch", "Stereo2 ADC L2 Mux" }, + { "Sto2 ADC MIXR", "ADC1 Switch", "Stereo2 ADC R1 Mux" }, + { "Sto2 ADC MIXR", "ADC2 Switch", "Stereo2 ADC R2 Mux" }, + + { "Sto2 ADC LR MIX", NULL, "Sto2 ADC MIXL" }, + { "Sto2 ADC LR MIX", NULL, "Sto2 ADC MIXR" }, + + { "Stereo2 ADC LR Mux", "L", "Sto2 ADC MIXL" }, + { "Stereo2 ADC LR Mux", "LR", "Sto2 ADC LR MIX" }, + + { "Stereo2 ADC MIXL", NULL, "Stereo2 ADC LR Mux" }, + { "Stereo2 ADC MIXL", NULL, "ADC Stereo2 Filter" }, + + { "Stereo2 ADC MIXR", NULL, "Sto2 ADC MIXR" }, + { "Stereo2 ADC MIXR", NULL, "ADC Stereo2 Filter" }, + { "ADC Stereo2 Filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "VAD ADC Mux", "Sto1 ADC L", "Stereo1 ADC MIXL" }, + { "VAD ADC Mux", "Mono ADC L", "Mono ADC MIXL" }, + { "VAD ADC Mux", "Mono ADC R", "Mono ADC MIXR" }, + { "VAD ADC Mux", "Sto2 ADC L", "Sto2 ADC MIXL" }, + + { "VAD_ADC", NULL, "VAD ADC Mux" }, + + { "IF_ADC1", NULL, "Stereo1 ADC MIXL" }, + { "IF_ADC1", NULL, "Stereo1 ADC MIXR" }, + { "IF_ADC2", NULL, "Mono ADC MIXL" }, + { "IF_ADC2", NULL, "Mono ADC MIXR" }, + { "IF_ADC3", NULL, "Stereo2 ADC MIXL" }, + { "IF_ADC3", NULL, "Stereo2 ADC MIXR" }, + + { "IF1 ADC1 IN1 Mux", "IF_ADC1", "IF_ADC1" }, + { "IF1 ADC1 IN1 Mux", "IF1_ADC3", "IF1_ADC3" }, + + { "IF1 ADC1 IN2 Mux", "IF1_ADC1_IN1", "IF1 ADC1 IN1 Mux" }, + { "IF1 ADC1 IN2 Mux", "IF1_ADC4", "TxDP_ADC" }, + + { "IF1 ADC2 IN Mux", "IF_ADC2", "IF_ADC2" }, + { "IF1 ADC2 IN Mux", "VAD_ADC", "VAD_ADC" }, + + { "IF1 ADC2 IN1 Mux", "IF1_ADC2_IN", "IF1 ADC2 IN Mux" }, + { "IF1 ADC2 IN1 Mux", "IF1_ADC4", "TxDP_ADC" }, + + { "IF1_ADC1" , NULL, "IF1 ADC1 IN2 Mux" }, + { "IF1_ADC2" , NULL, "IF1 ADC2 IN1 Mux" }, + + { "Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXL" }, + { "Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXR" }, + { "Stereo2 ADC MIX", NULL, "Sto2 ADC MIXL" }, + { "Stereo2 ADC MIX", NULL, "Sto2 ADC MIXR" }, + { "Mono ADC MIX", NULL, "Mono ADC MIXL" }, + { "Mono ADC MIX", NULL, "Mono ADC MIXR" }, + + { "RxDP Mux", "IF2 DAC", "IF2 DAC" }, + { "RxDP Mux", "IF1 DAC", "IF1 DAC2" }, + { "RxDP Mux", "STO1 ADC Mixer", "Stereo1 ADC MIX" }, + { "RxDP Mux", "STO2 ADC Mixer", "Stereo2 ADC MIX" }, + { "RxDP Mux", "Mono ADC Mixer L", "Mono ADC MIXL" }, + { "RxDP Mux", "Mono ADC Mixer R", "Mono ADC MIXR" }, + { "RxDP Mux", "DAC1", "DAC MIX" }, + + { "TDM Data Mux", "Slot 0-1", "Stereo1 ADC MIX" }, + { "TDM Data Mux", "Slot 2-3", "Mono ADC MIX" }, + { "TDM Data Mux", "Slot 4-5", "Stereo2 ADC MIX" }, + { "TDM Data Mux", "Slot 6-7", "IF2 DAC" }, + + { "DSP UL Mux", "Bypass", "TDM Data Mux" }, + { "DSP UL Mux", NULL, "I2S DSP" }, + { "DSP DL Mux", "Bypass", "RxDP Mux" }, + { "DSP DL Mux", NULL, "I2S DSP" }, + + { "TxDP_ADC_L", NULL, "DSP UL Mux" }, + { "TxDP_ADC_R", NULL, "DSP UL Mux" }, + { "TxDC_DAC", NULL, "DSP DL Mux" }, + + { "TxDP_ADC", NULL, "TxDP_ADC_L" }, + { "TxDP_ADC", NULL, "TxDP_ADC_R" }, + + { "IF1 ADC", NULL, "I2S1" }, + { "IF1 ADC", NULL, "IF1_ADC1" }, + { "IF1 ADC", NULL, "IF1_ADC2" }, + { "IF1 ADC", NULL, "IF_ADC3" }, + { "IF1 ADC", NULL, "TxDP_ADC" }, + + { "IF2 ADC Mux", "IF_ADC1", "IF_ADC1" }, + { "IF2 ADC Mux", "IF_ADC2", "IF_ADC2" }, + { "IF2 ADC Mux", "IF_ADC3", "IF_ADC3" }, + { "IF2 ADC Mux", "TxDC_DAC", "TxDC_DAC" }, + { "IF2 ADC Mux", "TxDP_ADC", "TxDP_ADC" }, + { "IF2 ADC Mux", "VAD_ADC", "VAD_ADC" }, + + { "IF2 ADC L", NULL, "IF2 ADC Mux" }, + { "IF2 ADC R", NULL, "IF2 ADC Mux" }, + + { "IF2 ADC", NULL, "I2S2" }, + { "IF2 ADC", NULL, "IF2 ADC L" }, + { "IF2 ADC", NULL, "IF2 ADC R" }, + + { "AIF1TX", NULL, "IF1 ADC" }, + { "AIF2TX", NULL, "IF2 ADC" }, + + { "IF1 DAC1", NULL, "AIF1RX" }, + { "IF1 DAC2", NULL, "AIF1RX" }, + { "IF2 DAC", NULL, "AIF2RX" }, + + { "IF1 DAC1", NULL, "I2S1" }, + { "IF1 DAC2", NULL, "I2S1" }, + { "IF2 DAC", NULL, "I2S2" }, + + { "IF1 DAC2 L", NULL, "IF1 DAC2" }, + { "IF1 DAC2 R", NULL, "IF1 DAC2" }, + { "IF1 DAC1 L", NULL, "IF1 DAC1" }, + { "IF1 DAC1 R", NULL, "IF1 DAC1" }, + { "IF2 DAC L", NULL, "IF2 DAC" }, + { "IF2 DAC R", NULL, "IF2 DAC" }, + + { "DAC1 L Mux", "IF1 DAC", "IF1 DAC1 L" }, + { "DAC1 L Mux", "IF2 DAC", "IF2 DAC L" }, + + { "DAC1 R Mux", "IF1 DAC", "IF1 DAC1 R" }, + { "DAC1 R Mux", "IF2 DAC", "IF2 DAC R" }, + + { "DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL" }, + { "DAC1 MIXL", "DAC1 Switch", "DAC1 L Mux" }, + { "DAC1 MIXL", NULL, "DAC Stereo1 Filter" }, + { "DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR" }, + { "DAC1 MIXR", "DAC1 Switch", "DAC1 R Mux" }, + { "DAC1 MIXR", NULL, "DAC Stereo1 Filter" }, + + { "DAC Stereo1 Filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC Mono Left Filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "DAC Mono Right Filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "DAC MIX", NULL, "DAC1 MIXL" }, + { "DAC MIX", NULL, "DAC1 MIXR" }, + + { "Audio DSP", NULL, "DAC1 MIXL" }, + { "Audio DSP", NULL, "DAC1 MIXR" }, + + { "DAC L2 Mux", "IF1 DAC", "IF1 DAC2 L" }, + { "DAC L2 Mux", "IF2 DAC", "IF2 DAC L" }, + { "DAC L2 Mux", "TxDC DAC", "TxDC_DAC" }, + { "DAC L2 Mux", "VAD_ADC", "VAD_ADC" }, + { "DAC L2 Volume", NULL, "DAC L2 Mux" }, + { "DAC L2 Volume", NULL, "DAC Mono Left Filter" }, + + { "DAC R2 Mux", "IF1 DAC", "IF1 DAC2 R" }, + { "DAC R2 Mux", "IF2 DAC", "IF2 DAC R" }, + { "DAC R2 Mux", "TxDC DAC", "TxDC_DAC" }, + { "DAC R2 Mux", "TxDP ADC", "TxDP_ADC" }, + { "DAC R2 Volume", NULL, "DAC R2 Mux" }, + { "DAC R2 Volume", NULL, "DAC Mono Right Filter" }, + + { "Stereo DAC MIXL", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXL", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXL", "DAC L2 Switch", "DAC L2 Volume" }, + { "Stereo DAC MIXL", NULL, "DAC Stereo1 Filter" }, + { "Stereo DAC MIXL", NULL, "DAC L1 Power" }, + { "Stereo DAC MIXR", "DAC R1 Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXR", "DAC L1 Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXR", "DAC R2 Switch", "DAC R2 Volume" }, + { "Stereo DAC MIXR", NULL, "DAC Stereo1 Filter" }, + { "Stereo DAC MIXR", NULL, "DAC R1 Power" }, + + { "Mono DAC MIXL", "DAC L1 Switch", "DAC1 MIXL" }, + { "Mono DAC MIXL", "DAC L2 Switch", "DAC L2 Volume" }, + { "Mono DAC MIXL", "DAC R2 Switch", "DAC R2 Volume" }, + { "Mono DAC MIXL", NULL, "DAC Mono Left Filter" }, + { "Mono DAC MIXR", "DAC R1 Switch", "DAC1 MIXR" }, + { "Mono DAC MIXR", "DAC R2 Switch", "DAC R2 Volume" }, + { "Mono DAC MIXR", "DAC L2 Switch", "DAC L2 Volume" }, + { "Mono DAC MIXR", NULL, "DAC Mono Right Filter" }, + + { "DAC MIXL", "Sto DAC Mix L Switch", "Stereo DAC MIXL" }, + { "DAC MIXL", "DAC L2 Switch", "DAC L2 Volume" }, + { "DAC MIXL", "DAC R2 Switch", "DAC R2 Volume" }, + { "DAC MIXR", "Sto DAC Mix R Switch", "Stereo DAC MIXR" }, + { "DAC MIXR", "DAC R2 Switch", "DAC R2 Volume" }, + { "DAC MIXR", "DAC L2 Switch", "DAC L2 Volume" }, + + { "DAC L1", NULL, "DAC L1 Power" }, + { "DAC L1", NULL, "Stereo DAC MIXL" }, + { "DAC R1", NULL, "DAC R1 Power" }, + { "DAC R1", NULL, "Stereo DAC MIXR" }, + { "DAC L2", NULL, "Mono DAC MIXL" }, + { "DAC R2", NULL, "Mono DAC MIXR" }, + + { "OUT MIXL", "BST1 Switch", "BST1" }, + { "OUT MIXL", "INL Switch", "INL VOL" }, + { "OUT MIXL", "DAC L2 Switch", "DAC L2" }, + { "OUT MIXL", "DAC L1 Switch", "DAC L1" }, + + { "OUT MIXR", "BST2 Switch", "BST2" }, + { "OUT MIXR", "INR Switch", "INR VOL" }, + { "OUT MIXR", "DAC R2 Switch", "DAC R2" }, + { "OUT MIXR", "DAC R1 Switch", "DAC R1" }, + + { "HPOVOL MIXL", "DAC1 Switch", "DAC L1" }, + { "HPOVOL MIXL", "INL Switch", "INL VOL" }, + { "HPOVOL MIXR", "DAC1 Switch", "DAC R1" }, + { "HPOVOL MIXR", "INR Switch", "INR VOL" }, + + { "DAC 2", NULL, "DAC L2" }, + { "DAC 2", NULL, "DAC R2" }, + { "DAC 1", NULL, "DAC L1" }, + { "DAC 1", NULL, "DAC R1" }, + { "HPOVOL", NULL, "HPOVOL MIXL" }, + { "HPOVOL", NULL, "HPOVOL MIXR" }, + { "HPO MIX", "DAC1 Switch", "DAC 1" }, + { "HPO MIX", "HPVOL Switch", "HPOVOL" }, + + { "LOUT MIX", "DAC L1 Switch", "DAC L1" }, + { "LOUT MIX", "DAC R1 Switch", "DAC R1" }, + { "LOUT MIX", "OUTMIX L Switch", "OUT MIXL" }, + { "LOUT MIX", "OUTMIX R Switch", "OUT MIXR" }, + + { "PDM1 L Mux", "Stereo DAC", "Stereo DAC MIXL" }, + { "PDM1 L Mux", "Mono DAC", "Mono DAC MIXL" }, + { "PDM1 L Mux", NULL, "PDM1 Power" }, + { "PDM1 R Mux", "Stereo DAC", "Stereo DAC MIXR" }, + { "PDM1 R Mux", "Mono DAC", "Mono DAC MIXR" }, + { "PDM1 R Mux", NULL, "PDM1 Power" }, + + { "HP Amp", NULL, "HPO MIX" }, + { "HP Amp", NULL, "Mic Det Power" }, + { "HPOL", NULL, "HP Amp" }, + { "HPOL", NULL, "HP L Amp" }, + { "HPOL", NULL, "Improve HP Amp Drv" }, + { "HPOR", NULL, "HP Amp" }, + { "HPOR", NULL, "HP R Amp" }, + { "HPOR", NULL, "Improve HP Amp Drv" }, + + { "LOUT Amp", NULL, "LOUT MIX" }, + { "LOUT L Playback", "Switch", "LOUT Amp" }, + { "LOUT R Playback", "Switch", "LOUT Amp" }, + { "LOUTL", NULL, "LOUT L Playback" }, + { "LOUTR", NULL, "LOUT R Playback" }, + { "LOUTL", NULL, "Improve HP Amp Drv" }, + { "LOUTR", NULL, "Improve HP Amp Drv" }, +}; + +static const struct snd_soc_dapm_route rt5670_specific_dapm_routes[] = { + { "PDM2 L Mux", "Stereo DAC", "Stereo DAC MIXL" }, + { "PDM2 L Mux", "Mono DAC", "Mono DAC MIXL" }, + { "PDM2 L Mux", NULL, "PDM2 Power" }, + { "PDM2 R Mux", "Stereo DAC", "Stereo DAC MIXR" }, + { "PDM2 R Mux", "Mono DAC", "Mono DAC MIXR" }, + { "PDM2 R Mux", NULL, "PDM2 Power" }, + { "PDM1L", NULL, "PDM1 L Mux" }, + { "PDM1R", NULL, "PDM1 R Mux" }, + { "PDM2L", NULL, "PDM2 L Mux" }, + { "PDM2R", NULL, "PDM2 R Mux" }, +}; + +static const struct snd_soc_dapm_route rt5672_specific_dapm_routes[] = { + { "SPO Amp", NULL, "PDM1 L Mux" }, + { "SPO Amp", NULL, "PDM1 R Mux" }, + { "SPOLP", NULL, "SPO Amp" }, + { "SPOLN", NULL, "SPO Amp" }, + { "SPORP", NULL, "SPO Amp" }, + { "SPORN", NULL, "SPO Amp" }, +}; + +static int rt5670_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int pre_div, bclk_ms, frame_size; + + rt5670->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5670->sysclk, rt5670->lrck[dai->id]); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting %d for DAI %d\n", + rt5670->lrck[dai->id], dai->id); + return -EINVAL; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + bclk_ms = frame_size > 32; + rt5670->bclk[dai->id] = rt5670->lrck[dai->id] * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5670->bclk[dai->id], rt5670->lrck[dai->id]); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len |= RT5670_I2S_DL_20; + break; + case 24: + val_len |= RT5670_I2S_DL_24; + break; + case 8: + val_len |= RT5670_I2S_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5670_AIF1: + mask_clk = RT5670_I2S_BCLK_MS1_MASK | RT5670_I2S_PD1_MASK; + val_clk = bclk_ms << RT5670_I2S_BCLK_MS1_SFT | + pre_div << RT5670_I2S_PD1_SFT; + snd_soc_component_update_bits(component, RT5670_I2S1_SDP, + RT5670_I2S_DL_MASK, val_len); + snd_soc_component_update_bits(component, RT5670_ADDA_CLK1, mask_clk, val_clk); + break; + case RT5670_AIF2: + mask_clk = RT5670_I2S_BCLK_MS2_MASK | RT5670_I2S_PD2_MASK; + val_clk = bclk_ms << RT5670_I2S_BCLK_MS2_SFT | + pre_div << RT5670_I2S_PD2_SFT; + snd_soc_component_update_bits(component, RT5670_I2S2_SDP, + RT5670_I2S_DL_MASK, val_len); + snd_soc_component_update_bits(component, RT5670_ADDA_CLK1, mask_clk, val_clk); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5670_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5670->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5670_I2S_MS_S; + rt5670->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5670_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5670_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5670_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5670_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5670_AIF1: + snd_soc_component_update_bits(component, RT5670_I2S1_SDP, + RT5670_I2S_MS_MASK | RT5670_I2S_BP_MASK | + RT5670_I2S_DF_MASK, reg_val); + break; + case RT5670_AIF2: + snd_soc_component_update_bits(component, RT5670_I2S2_SDP, + RT5670_I2S_MS_MASK | RT5670_I2S_BP_MASK | + RT5670_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5670_set_codec_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (clk_id) { + case RT5670_SCLK_S_MCLK: + reg_val |= RT5670_SCLK_SRC_MCLK; + break; + case RT5670_SCLK_S_PLL1: + reg_val |= RT5670_SCLK_SRC_PLL1; + break; + case RT5670_SCLK_S_RCCLK: + reg_val |= RT5670_SCLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5670_GLB_CLK, + RT5670_SCLK_SRC_MASK, reg_val); + rt5670->sysclk = freq; + if (clk_id != RT5670_SCLK_S_RCCLK) + rt5670->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk : %dHz clock id : %d\n", freq, clk_id); + + return 0; +} + +static int rt5670_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5670->pll_src && freq_in == rt5670->pll_in && + freq_out == rt5670->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5670->pll_in = 0; + rt5670->pll_out = 0; + snd_soc_component_update_bits(component, RT5670_GLB_CLK, + RT5670_SCLK_SRC_MASK, RT5670_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5670_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5670_GLB_CLK, + RT5670_PLL1_SRC_MASK, RT5670_PLL1_SRC_MCLK); + break; + case RT5670_PLL1_S_BCLK1: + case RT5670_PLL1_S_BCLK2: + case RT5670_PLL1_S_BCLK3: + case RT5670_PLL1_S_BCLK4: + switch (dai->id) { + case RT5670_AIF1: + snd_soc_component_update_bits(component, RT5670_GLB_CLK, + RT5670_PLL1_SRC_MASK, RT5670_PLL1_SRC_BCLK1); + break; + case RT5670_AIF2: + snd_soc_component_update_bits(component, RT5670_GLB_CLK, + RT5670_PLL1_SRC_MASK, RT5670_PLL1_SRC_BCLK2); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5670_PLL_CTRL1, + pll_code.n_code << RT5670_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5670_PLL_CTRL2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5670_PLL_M_SFT | + pll_code.m_bp << RT5670_PLL_M_BP_SFT); + + rt5670->pll_in = freq_in; + rt5670->pll_out = freq_out; + rt5670->pll_src = source; + + return 0; +} + +static int rt5670_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val = 0; + + if (rx_mask || tx_mask) + val |= (1 << 14); + + switch (slots) { + case 4: + val |= (1 << 12); + break; + case 6: + val |= (2 << 12); + break; + case 8: + val |= (3 << 12); + break; + case 2: + break; + default: + return -EINVAL; + } + + switch (slot_width) { + case 20: + val |= (1 << 10); + break; + case 24: + val |= (2 << 10); + break; + case 32: + val |= (3 << 10); + break; + case 16: + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5670_TDM_CTRL_1, 0x7c00, val); + + return 0; +} + +static int rt5670_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + + dev_dbg(component->dev, "%s ratio=%d\n", __func__, ratio); + if (dai->id != RT5670_AIF1) + return 0; + + if ((ratio % 50) == 0) + snd_soc_component_update_bits(component, RT5670_GEN_CTRL3, + RT5670_TDM_DATA_MODE_SEL, RT5670_TDM_DATA_MODE_50FS); + else + snd_soc_component_update_bits(component, RT5670_GEN_CTRL3, + RT5670_TDM_DATA_MODE_SEL, RT5670_TDM_DATA_MODE_NOR); + + return 0; +} + +static int rt5670_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == snd_soc_component_get_bias_level(component)) { + snd_soc_component_update_bits(component, RT5670_PWR_ANLG1, + RT5670_PWR_VREF1 | RT5670_PWR_MB | + RT5670_PWR_BG | RT5670_PWR_VREF2, + RT5670_PWR_VREF1 | RT5670_PWR_MB | + RT5670_PWR_BG | RT5670_PWR_VREF2); + mdelay(10); + snd_soc_component_update_bits(component, RT5670_PWR_ANLG1, + RT5670_PWR_FV1 | RT5670_PWR_FV2, + RT5670_PWR_FV1 | RT5670_PWR_FV2); + snd_soc_component_update_bits(component, RT5670_CHARGE_PUMP, + RT5670_OSW_L_MASK | RT5670_OSW_R_MASK, + RT5670_OSW_L_DIS | RT5670_OSW_R_DIS); + snd_soc_component_update_bits(component, RT5670_DIG_MISC, 0x1, 0x1); + snd_soc_component_update_bits(component, RT5670_PWR_ANLG1, + RT5670_LDO_SEL_MASK, 0x5); + } + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_component_update_bits(component, RT5670_PWR_ANLG1, + RT5670_PWR_VREF1 | RT5670_PWR_VREF2 | + RT5670_PWR_FV1 | RT5670_PWR_FV2, 0); + snd_soc_component_update_bits(component, RT5670_PWR_ANLG1, + RT5670_LDO_SEL_MASK, 0x3); + break; + case SND_SOC_BIAS_OFF: + if (rt5670->pdata.jd_mode) + snd_soc_component_update_bits(component, RT5670_PWR_ANLG1, + RT5670_PWR_VREF1 | RT5670_PWR_MB | + RT5670_PWR_BG | RT5670_PWR_VREF2 | + RT5670_PWR_FV1 | RT5670_PWR_FV2, + RT5670_PWR_MB | RT5670_PWR_BG); + else + snd_soc_component_update_bits(component, RT5670_PWR_ANLG1, + RT5670_PWR_VREF1 | RT5670_PWR_MB | + RT5670_PWR_BG | RT5670_PWR_VREF2 | + RT5670_PWR_FV1 | RT5670_PWR_FV2, 0); + + snd_soc_component_update_bits(component, RT5670_DIG_MISC, 0x1, 0x0); + break; + + default: + break; + } + + return 0; +} + +static int rt5670_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + switch (snd_soc_component_read32(component, RT5670_RESET) & RT5670_ID_MASK) { + case RT5670_ID_5670: + case RT5670_ID_5671: + snd_soc_dapm_new_controls(dapm, + rt5670_specific_dapm_widgets, + ARRAY_SIZE(rt5670_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5670_specific_dapm_routes, + ARRAY_SIZE(rt5670_specific_dapm_routes)); + break; + case RT5670_ID_5672: + snd_soc_dapm_new_controls(dapm, + rt5672_specific_dapm_widgets, + ARRAY_SIZE(rt5672_specific_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, + rt5672_specific_dapm_routes, + ARRAY_SIZE(rt5672_specific_dapm_routes)); + break; + default: + dev_err(component->dev, + "The driver is for RT5670 RT5671 or RT5672 only\n"); + return -ENODEV; + } + rt5670->component = component; + + return 0; +} + +static void rt5670_remove(struct snd_soc_component *component) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + regmap_write(rt5670->regmap, RT5670_RESET, 0); + snd_soc_jack_free_gpios(rt5670->jack, 1, &rt5670->hp_gpio); +} + +#ifdef CONFIG_PM +static int rt5670_suspend(struct snd_soc_component *component) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5670->regmap, true); + regcache_mark_dirty(rt5670->regmap); + return 0; +} + +static int rt5670_resume(struct snd_soc_component *component) +{ + struct rt5670_priv *rt5670 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5670->regmap, false); + regcache_sync(rt5670->regmap); + + return 0; +} +#else +#define rt5670_suspend NULL +#define rt5670_resume NULL +#endif + +#define RT5670_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define RT5670_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5670_aif_dai_ops = { + .hw_params = rt5670_hw_params, + .set_fmt = rt5670_set_dai_fmt, + .set_tdm_slot = rt5670_set_tdm_slot, + .set_pll = rt5670_set_dai_pll, + .set_bclk_ratio = rt5670_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt5670_dai[] = { + { + .name = "rt5670-aif1", + .id = RT5670_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5670_STEREO_RATES, + .formats = RT5670_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5670_STEREO_RATES, + .formats = RT5670_FORMATS, + }, + .ops = &rt5670_aif_dai_ops, + .symmetric_rates = 1, + }, + { + .name = "rt5670-aif2", + .id = RT5670_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5670_STEREO_RATES, + .formats = RT5670_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5670_STEREO_RATES, + .formats = RT5670_FORMATS, + }, + .ops = &rt5670_aif_dai_ops, + .symmetric_rates = 1, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5670 = { + .probe = rt5670_probe, + .remove = rt5670_remove, + .suspend = rt5670_suspend, + .resume = rt5670_resume, + .set_bias_level = rt5670_set_bias_level, + .set_sysclk = rt5670_set_codec_sysclk, + .controls = rt5670_snd_controls, + .num_controls = ARRAY_SIZE(rt5670_snd_controls), + .dapm_widgets = rt5670_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5670_dapm_widgets), + .dapm_routes = rt5670_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5670_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5670_regmap = { + .reg_bits = 8, + .val_bits = 16, + .use_single_rw = true, + .max_register = RT5670_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5670_ranges) * + RT5670_PR_SPACING), + .volatile_reg = rt5670_volatile_register, + .readable_reg = rt5670_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5670_reg, + .num_reg_defaults = ARRAY_SIZE(rt5670_reg), + .ranges = rt5670_ranges, + .num_ranges = ARRAY_SIZE(rt5670_ranges), +}; + +static const struct i2c_device_id rt5670_i2c_id[] = { + { "rt5670", 0 }, + { "rt5671", 0 }, + { "rt5672", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt5670_i2c_id); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5670_acpi_match[] = { + { "10EC5670", 0}, + { "10EC5672", 0}, + { "10EC5640", 0}, /* quirk */ + { }, +}; +MODULE_DEVICE_TABLE(acpi, rt5670_acpi_match); +#endif + +static int rt5670_quirk_cb(const struct dmi_system_id *id) +{ + rt5670_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id dmi_platform_intel_quirks[] = { + { + .callback = rt5670_quirk_cb, + .ident = "Intel Braswell", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Braswell CRB"), + }, + .driver_data = (unsigned long *)(RT5670_DMIC_EN | + RT5670_DMIC1_IN2P | + RT5670_DEV_GPIO | + RT5670_JD_MODE1), + }, + { + .callback = rt5670_quirk_cb, + .ident = "Dell Wyse 3040", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Wyse 3040"), + }, + .driver_data = (unsigned long *)(RT5670_DMIC_EN | + RT5670_DMIC1_IN2P | + RT5670_DEV_GPIO | + RT5670_JD_MODE1), + }, + { + .callback = rt5670_quirk_cb, + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"), + }, + .driver_data = (unsigned long *)(RT5670_DMIC_EN | + RT5670_DMIC1_IN2P | + RT5670_DEV_GPIO | + RT5670_JD_MODE1), + }, + { + .callback = rt5670_quirk_cb, + .ident = "Lenovo Thinkpad Tablet 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"), + }, + .driver_data = (unsigned long *)(RT5670_DMIC_EN | + RT5670_DMIC1_IN2P | + RT5670_DEV_GPIO | + RT5670_JD_MODE1), + }, + { + .callback = rt5670_quirk_cb, + .ident = "Lenovo Miix 2 10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"), + }, + .driver_data = (unsigned long *)(RT5670_DMIC_EN | + RT5670_DMIC1_IN2P | + RT5670_GPIO1_IS_EXT_SPK_EN | + RT5670_JD_MODE2), + }, + { + .callback = rt5670_quirk_cb, + .ident = "Dell Venue 8 Pro 5855", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5855"), + }, + .driver_data = (unsigned long *)(RT5670_DMIC_EN | + RT5670_DMIC2_INR | + RT5670_DEV_GPIO | + RT5670_JD_MODE3), + }, + {} +}; + +static int rt5670_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5670_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5670_priv *rt5670; + int ret; + unsigned int val; + + rt5670 = devm_kzalloc(&i2c->dev, + sizeof(struct rt5670_priv), + GFP_KERNEL); + if (NULL == rt5670) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5670); + + if (pdata) + rt5670->pdata = *pdata; + + dmi_check_system(dmi_platform_intel_quirks); + if (quirk_override) { + dev_info(&i2c->dev, "Overriding quirk 0x%x => 0x%x\n", + (unsigned int)rt5670_quirk, quirk_override); + rt5670_quirk = quirk_override; + } + + if (rt5670_quirk & RT5670_DEV_GPIO) { + rt5670->pdata.dev_gpio = true; + dev_info(&i2c->dev, "quirk dev_gpio\n"); + } + if (rt5670_quirk & RT5670_GPIO1_IS_EXT_SPK_EN) { + rt5670->pdata.gpio1_is_ext_spk_en = true; + dev_info(&i2c->dev, "quirk GPIO1 is external speaker enable\n"); + } + if (rt5670_quirk & RT5670_IN2_DIFF) { + rt5670->pdata.in2_diff = true; + dev_info(&i2c->dev, "quirk IN2_DIFF\n"); + } + if (rt5670_quirk & RT5670_DMIC_EN) { + rt5670->pdata.dmic_en = true; + dev_info(&i2c->dev, "quirk DMIC enabled\n"); + } + if (rt5670_quirk & RT5670_DMIC1_IN2P) { + rt5670->pdata.dmic1_data_pin = RT5670_DMIC_DATA_IN2P; + dev_info(&i2c->dev, "quirk DMIC1 on IN2P pin\n"); + } + if (rt5670_quirk & RT5670_DMIC1_GPIO6) { + rt5670->pdata.dmic1_data_pin = RT5670_DMIC_DATA_GPIO6; + dev_info(&i2c->dev, "quirk DMIC1 on GPIO6 pin\n"); + } + if (rt5670_quirk & RT5670_DMIC1_GPIO7) { + rt5670->pdata.dmic1_data_pin = RT5670_DMIC_DATA_GPIO7; + dev_info(&i2c->dev, "quirk DMIC1 on GPIO7 pin\n"); + } + if (rt5670_quirk & RT5670_DMIC2_INR) { + rt5670->pdata.dmic2_data_pin = RT5670_DMIC_DATA_IN3N; + dev_info(&i2c->dev, "quirk DMIC2 on INR pin\n"); + } + if (rt5670_quirk & RT5670_DMIC2_GPIO8) { + rt5670->pdata.dmic2_data_pin = RT5670_DMIC_DATA_GPIO8; + dev_info(&i2c->dev, "quirk DMIC2 on GPIO8 pin\n"); + } + if (rt5670_quirk & RT5670_DMIC3_GPIO5) { + rt5670->pdata.dmic3_data_pin = RT5670_DMIC_DATA_GPIO5; + dev_info(&i2c->dev, "quirk DMIC3 on GPIO5 pin\n"); + } + + if (rt5670_quirk & RT5670_JD_MODE1) { + rt5670->pdata.jd_mode = 1; + dev_info(&i2c->dev, "quirk JD mode 1\n"); + } + if (rt5670_quirk & RT5670_JD_MODE2) { + rt5670->pdata.jd_mode = 2; + dev_info(&i2c->dev, "quirk JD mode 2\n"); + } + if (rt5670_quirk & RT5670_JD_MODE3) { + rt5670->pdata.jd_mode = 3; + dev_info(&i2c->dev, "quirk JD mode 3\n"); + } + + rt5670->regmap = devm_regmap_init_i2c(i2c, &rt5670_regmap); + if (IS_ERR(rt5670->regmap)) { + ret = PTR_ERR(rt5670->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt5670->regmap, RT5670_VENDOR_ID2, &val); + if (val != RT5670_DEVICE_ID) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5670/72\n", val); + return -ENODEV; + } + + regmap_write(rt5670->regmap, RT5670_RESET, 0); + regmap_update_bits(rt5670->regmap, RT5670_PWR_ANLG1, + RT5670_PWR_HP_L | RT5670_PWR_HP_R | + RT5670_PWR_VREF2, RT5670_PWR_VREF2); + msleep(100); + + regmap_write(rt5670->regmap, RT5670_RESET, 0); + + regmap_read(rt5670->regmap, RT5670_VENDOR_ID, &val); + if (val >= 4) + regmap_write(rt5670->regmap, RT5670_GPIO_CTRL3, 0x0980); + else + regmap_write(rt5670->regmap, RT5670_GPIO_CTRL3, 0x0d00); + + ret = regmap_register_patch(rt5670->regmap, init_list, + ARRAY_SIZE(init_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + regmap_update_bits(rt5670->regmap, RT5670_DIG_MISC, + RT5670_MCLK_DET, RT5670_MCLK_DET); + + if (rt5670->pdata.in2_diff) + regmap_update_bits(rt5670->regmap, RT5670_IN2, + RT5670_IN_DF2, RT5670_IN_DF2); + + if (rt5670->pdata.dev_gpio) { + /* for push button */ + regmap_write(rt5670->regmap, RT5670_IL_CMD, 0x0000); + regmap_write(rt5670->regmap, RT5670_IL_CMD2, 0x0010); + regmap_write(rt5670->regmap, RT5670_IL_CMD3, 0x0014); + /* for irq */ + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, + RT5670_GP1_PIN_MASK, RT5670_GP1_PIN_IRQ); + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL2, + RT5670_GP1_PF_MASK, RT5670_GP1_PF_OUT); + } + + if (rt5670->pdata.gpio1_is_ext_spk_en) { + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, + RT5670_GP1_PIN_MASK, RT5670_GP1_PIN_GPIO1); + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL2, + RT5670_GP1_PF_MASK, RT5670_GP1_PF_OUT); + } + + if (rt5670->pdata.jd_mode) { + regmap_update_bits(rt5670->regmap, RT5670_GLB_CLK, + RT5670_SCLK_SRC_MASK, RT5670_SCLK_SRC_RCCLK); + rt5670->sysclk = 0; + rt5670->sysclk_src = RT5670_SCLK_S_RCCLK; + regmap_update_bits(rt5670->regmap, RT5670_PWR_ANLG1, + RT5670_PWR_MB, RT5670_PWR_MB); + regmap_update_bits(rt5670->regmap, RT5670_PWR_ANLG2, + RT5670_PWR_JD1, RT5670_PWR_JD1); + regmap_update_bits(rt5670->regmap, RT5670_IRQ_CTRL1, + RT5670_JD1_1_EN_MASK, RT5670_JD1_1_EN); + regmap_update_bits(rt5670->regmap, RT5670_JD_CTRL3, + RT5670_JD_TRI_CBJ_SEL_MASK | + RT5670_JD_TRI_HPO_SEL_MASK, + RT5670_JD_CBJ_JD1_1 | RT5670_JD_HPO_JD1_1); + switch (rt5670->pdata.jd_mode) { + case 1: + regmap_update_bits(rt5670->regmap, RT5670_A_JD_CTRL1, + RT5670_JD1_MODE_MASK, + RT5670_JD1_MODE_0); + break; + case 2: + regmap_update_bits(rt5670->regmap, RT5670_A_JD_CTRL1, + RT5670_JD1_MODE_MASK, + RT5670_JD1_MODE_1); + break; + case 3: + regmap_update_bits(rt5670->regmap, RT5670_A_JD_CTRL1, + RT5670_JD1_MODE_MASK, + RT5670_JD1_MODE_2); + break; + default: + break; + } + } + + if (rt5670->pdata.dmic_en) { + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, + RT5670_GP2_PIN_MASK, + RT5670_GP2_PIN_DMIC1_SCL); + + switch (rt5670->pdata.dmic1_data_pin) { + case RT5670_DMIC_DATA_IN2P: + regmap_update_bits(rt5670->regmap, RT5670_DMIC_CTRL1, + RT5670_DMIC_1_DP_MASK, + RT5670_DMIC_1_DP_IN2P); + break; + + case RT5670_DMIC_DATA_GPIO6: + regmap_update_bits(rt5670->regmap, RT5670_DMIC_CTRL1, + RT5670_DMIC_1_DP_MASK, + RT5670_DMIC_1_DP_GPIO6); + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, + RT5670_GP6_PIN_MASK, + RT5670_GP6_PIN_DMIC1_SDA); + break; + + case RT5670_DMIC_DATA_GPIO7: + regmap_update_bits(rt5670->regmap, RT5670_DMIC_CTRL1, + RT5670_DMIC_1_DP_MASK, + RT5670_DMIC_1_DP_GPIO7); + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, + RT5670_GP7_PIN_MASK, + RT5670_GP7_PIN_DMIC1_SDA); + break; + + default: + break; + } + + switch (rt5670->pdata.dmic2_data_pin) { + case RT5670_DMIC_DATA_IN3N: + regmap_update_bits(rt5670->regmap, RT5670_DMIC_CTRL1, + RT5670_DMIC_2_DP_MASK, + RT5670_DMIC_2_DP_IN3N); + break; + + case RT5670_DMIC_DATA_GPIO8: + regmap_update_bits(rt5670->regmap, RT5670_DMIC_CTRL1, + RT5670_DMIC_2_DP_MASK, + RT5670_DMIC_2_DP_GPIO8); + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, + RT5670_GP8_PIN_MASK, + RT5670_GP8_PIN_DMIC2_SDA); + break; + + default: + break; + } + + switch (rt5670->pdata.dmic3_data_pin) { + case RT5670_DMIC_DATA_GPIO5: + regmap_update_bits(rt5670->regmap, RT5670_DMIC_CTRL2, + RT5670_DMIC_3_DP_MASK, + RT5670_DMIC_3_DP_GPIO5); + regmap_update_bits(rt5670->regmap, RT5670_GPIO_CTRL1, + RT5670_GP5_PIN_MASK, + RT5670_GP5_PIN_DMIC3_SDA); + break; + + case RT5670_DMIC_DATA_GPIO9: + case RT5670_DMIC_DATA_GPIO10: + dev_err(&i2c->dev, + "Always use GPIO5 as DMIC3 data pin\n"); + break; + + default: + break; + } + + } + + pm_runtime_enable(&i2c->dev); + pm_request_idle(&i2c->dev); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5670, + rt5670_dai, ARRAY_SIZE(rt5670_dai)); + if (ret < 0) + goto err; + + pm_runtime_put(&i2c->dev); + + return 0; +err: + pm_runtime_disable(&i2c->dev); + + return ret; +} + +static int rt5670_i2c_remove(struct i2c_client *i2c) +{ + pm_runtime_disable(&i2c->dev); + + return 0; +} + +static struct i2c_driver rt5670_i2c_driver = { + .driver = { + .name = "rt5670", + .acpi_match_table = ACPI_PTR(rt5670_acpi_match), + }, + .probe = rt5670_i2c_probe, + .remove = rt5670_i2c_remove, + .id_table = rt5670_i2c_id, +}; + +module_i2c_driver(rt5670_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5670 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5670.h b/sound/soc/codecs/rt5670.h new file mode 100644 index 000000000..563edd60f --- /dev/null +++ b/sound/soc/codecs/rt5670.h @@ -0,0 +1,2019 @@ +/* + * rt5670.h -- RT5670 ALSA SoC audio driver + * + * Copyright 2014 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5670_H__ +#define __RT5670_H__ + +#include + +/* Info */ +#define RT5670_RESET 0x00 +#define RT5670_VENDOR_ID 0xfd +#define RT5670_VENDOR_ID1 0xfe +#define RT5670_VENDOR_ID2 0xff +/* I/O - Output */ +#define RT5670_HP_VOL 0x02 +#define RT5670_LOUT1 0x03 +/* I/O - Input */ +#define RT5670_CJ_CTRL1 0x0a +#define RT5670_CJ_CTRL2 0x0b +#define RT5670_CJ_CTRL3 0x0c +#define RT5670_IN2 0x0e +#define RT5670_INL1_INR1_VOL 0x0f +/* I/O - ADC/DAC/DMIC */ +#define RT5670_DAC1_DIG_VOL 0x19 +#define RT5670_DAC2_DIG_VOL 0x1a +#define RT5670_DAC_CTRL 0x1b +#define RT5670_STO1_ADC_DIG_VOL 0x1c +#define RT5670_MONO_ADC_DIG_VOL 0x1d +#define RT5670_ADC_BST_VOL1 0x1e +#define RT5670_STO2_ADC_DIG_VOL 0x1f +/* Mixer - D-D */ +#define RT5670_ADC_BST_VOL2 0x20 +#define RT5670_STO2_ADC_MIXER 0x26 +#define RT5670_STO1_ADC_MIXER 0x27 +#define RT5670_MONO_ADC_MIXER 0x28 +#define RT5670_AD_DA_MIXER 0x29 +#define RT5670_STO_DAC_MIXER 0x2a +#define RT5670_DD_MIXER 0x2b +#define RT5670_DIG_MIXER 0x2c +#define RT5670_DSP_PATH1 0x2d +#define RT5670_DSP_PATH2 0x2e +#define RT5670_DIG_INF1_DATA 0x2f +#define RT5670_DIG_INF2_DATA 0x30 +/* Mixer - PDM */ +#define RT5670_PDM_OUT_CTRL 0x31 +#define RT5670_PDM_DATA_CTRL1 0x32 +#define RT5670_PDM1_DATA_CTRL2 0x33 +#define RT5670_PDM1_DATA_CTRL3 0x34 +#define RT5670_PDM1_DATA_CTRL4 0x35 +#define RT5670_PDM2_DATA_CTRL2 0x36 +#define RT5670_PDM2_DATA_CTRL3 0x37 +#define RT5670_PDM2_DATA_CTRL4 0x38 +/* Mixer - ADC */ +#define RT5670_REC_L1_MIXER 0x3b +#define RT5670_REC_L2_MIXER 0x3c +#define RT5670_REC_R1_MIXER 0x3d +#define RT5670_REC_R2_MIXER 0x3e +/* Mixer - DAC */ +#define RT5670_HPO_MIXER 0x45 +#define RT5670_MONO_MIXER 0x4c +#define RT5670_OUT_L1_MIXER 0x4f +#define RT5670_OUT_R1_MIXER 0x52 +#define RT5670_LOUT_MIXER 0x53 +/* Power */ +#define RT5670_PWR_DIG1 0x61 +#define RT5670_PWR_DIG2 0x62 +#define RT5670_PWR_ANLG1 0x63 +#define RT5670_PWR_ANLG2 0x64 +#define RT5670_PWR_MIXER 0x65 +#define RT5670_PWR_VOL 0x66 +/* Private Register Control */ +#define RT5670_PRIV_INDEX 0x6a +#define RT5670_PRIV_DATA 0x6c +/* Format - ADC/DAC */ +#define RT5670_I2S4_SDP 0x6f +#define RT5670_I2S1_SDP 0x70 +#define RT5670_I2S2_SDP 0x71 +#define RT5670_I2S3_SDP 0x72 +#define RT5670_ADDA_CLK1 0x73 +#define RT5670_ADDA_CLK2 0x74 +#define RT5670_DMIC_CTRL1 0x75 +#define RT5670_DMIC_CTRL2 0x76 +/* Format - TDM Control */ +#define RT5670_TDM_CTRL_1 0x77 +#define RT5670_TDM_CTRL_2 0x78 +#define RT5670_TDM_CTRL_3 0x79 + +/* Function - Analog */ +#define RT5670_DSP_CLK 0x7f +#define RT5670_GLB_CLK 0x80 +#define RT5670_PLL_CTRL1 0x81 +#define RT5670_PLL_CTRL2 0x82 +#define RT5670_ASRC_1 0x83 +#define RT5670_ASRC_2 0x84 +#define RT5670_ASRC_3 0x85 +#define RT5670_ASRC_4 0x86 +#define RT5670_ASRC_5 0x87 +#define RT5670_ASRC_7 0x89 +#define RT5670_ASRC_8 0x8a +#define RT5670_ASRC_9 0x8b +#define RT5670_ASRC_10 0x8c +#define RT5670_ASRC_11 0x8d +#define RT5670_DEPOP_M1 0x8e +#define RT5670_DEPOP_M2 0x8f +#define RT5670_DEPOP_M3 0x90 +#define RT5670_CHARGE_PUMP 0x91 +#define RT5670_MICBIAS 0x93 +#define RT5670_A_JD_CTRL1 0x94 +#define RT5670_A_JD_CTRL2 0x95 +#define RT5670_ASRC_12 0x97 +#define RT5670_ASRC_13 0x98 +#define RT5670_ASRC_14 0x99 +#define RT5670_VAD_CTRL1 0x9a +#define RT5670_VAD_CTRL2 0x9b +#define RT5670_VAD_CTRL3 0x9c +#define RT5670_VAD_CTRL4 0x9d +#define RT5670_VAD_CTRL5 0x9e +/* Function - Digital */ +#define RT5670_ADC_EQ_CTRL1 0xae +#define RT5670_ADC_EQ_CTRL2 0xaf +#define RT5670_EQ_CTRL1 0xb0 +#define RT5670_EQ_CTRL2 0xb1 +#define RT5670_ALC_DRC_CTRL1 0xb2 +#define RT5670_ALC_DRC_CTRL2 0xb3 +#define RT5670_ALC_CTRL_1 0xb4 +#define RT5670_ALC_CTRL_2 0xb5 +#define RT5670_ALC_CTRL_3 0xb6 +#define RT5670_ALC_CTRL_4 0xb7 +#define RT5670_JD_CTRL 0xbb +#define RT5670_IRQ_CTRL1 0xbd +#define RT5670_IRQ_CTRL2 0xbe +#define RT5670_INT_IRQ_ST 0xbf +#define RT5670_GPIO_CTRL1 0xc0 +#define RT5670_GPIO_CTRL2 0xc1 +#define RT5670_GPIO_CTRL3 0xc2 +#define RT5670_SCRABBLE_FUN 0xcd +#define RT5670_SCRABBLE_CTRL 0xce +#define RT5670_BASE_BACK 0xcf +#define RT5670_MP3_PLUS1 0xd0 +#define RT5670_MP3_PLUS2 0xd1 +#define RT5670_ADJ_HPF1 0xd3 +#define RT5670_ADJ_HPF2 0xd4 +#define RT5670_HP_CALIB_AMP_DET 0xd6 +#define RT5670_SV_ZCD1 0xd9 +#define RT5670_SV_ZCD2 0xda +#define RT5670_IL_CMD 0xdb +#define RT5670_IL_CMD2 0xdc +#define RT5670_IL_CMD3 0xdd +#define RT5670_DRC_HL_CTRL1 0xe6 +#define RT5670_DRC_HL_CTRL2 0xe7 +#define RT5670_ADC_MONO_HP_CTRL1 0xec +#define RT5670_ADC_MONO_HP_CTRL2 0xed +#define RT5670_ADC_STO2_HP_CTRL1 0xee +#define RT5670_ADC_STO2_HP_CTRL2 0xef +#define RT5670_JD_CTRL3 0xf8 +#define RT5670_JD_CTRL4 0xf9 +/* General Control */ +#define RT5670_DIG_MISC 0xfa +#define RT5670_GEN_CTRL2 0xfb +#define RT5670_GEN_CTRL3 0xfc + + +/* Index of Codec Private Register definition */ +#define RT5670_DIG_VOL 0x00 +#define RT5670_PR_ALC_CTRL_1 0x01 +#define RT5670_PR_ALC_CTRL_2 0x02 +#define RT5670_PR_ALC_CTRL_3 0x03 +#define RT5670_PR_ALC_CTRL_4 0x04 +#define RT5670_PR_ALC_CTRL_5 0x05 +#define RT5670_PR_ALC_CTRL_6 0x06 +#define RT5670_BIAS_CUR1 0x12 +#define RT5670_BIAS_CUR3 0x14 +#define RT5670_CLSD_INT_REG1 0x1c +#define RT5670_MAMP_INT_REG2 0x37 +#define RT5670_CHOP_DAC_ADC 0x3d +#define RT5670_MIXER_INT_REG 0x3f +#define RT5670_3D_SPK 0x63 +#define RT5670_WND_1 0x6c +#define RT5670_WND_2 0x6d +#define RT5670_WND_3 0x6e +#define RT5670_WND_4 0x6f +#define RT5670_WND_5 0x70 +#define RT5670_WND_8 0x73 +#define RT5670_DIP_SPK_INF 0x75 +#define RT5670_HP_DCC_INT1 0x77 +#define RT5670_EQ_BW_LOP 0xa0 +#define RT5670_EQ_GN_LOP 0xa1 +#define RT5670_EQ_FC_BP1 0xa2 +#define RT5670_EQ_BW_BP1 0xa3 +#define RT5670_EQ_GN_BP1 0xa4 +#define RT5670_EQ_FC_BP2 0xa5 +#define RT5670_EQ_BW_BP2 0xa6 +#define RT5670_EQ_GN_BP2 0xa7 +#define RT5670_EQ_FC_BP3 0xa8 +#define RT5670_EQ_BW_BP3 0xa9 +#define RT5670_EQ_GN_BP3 0xaa +#define RT5670_EQ_FC_BP4 0xab +#define RT5670_EQ_BW_BP4 0xac +#define RT5670_EQ_GN_BP4 0xad +#define RT5670_EQ_FC_HIP1 0xae +#define RT5670_EQ_GN_HIP1 0xaf +#define RT5670_EQ_FC_HIP2 0xb0 +#define RT5670_EQ_BW_HIP2 0xb1 +#define RT5670_EQ_GN_HIP2 0xb2 +#define RT5670_EQ_PRE_VOL 0xb3 +#define RT5670_EQ_PST_VOL 0xb4 + + +/* global definition */ +#define RT5670_L_MUTE (0x1 << 15) +#define RT5670_L_MUTE_SFT 15 +#define RT5670_VOL_L_MUTE (0x1 << 14) +#define RT5670_VOL_L_SFT 14 +#define RT5670_R_MUTE (0x1 << 7) +#define RT5670_R_MUTE_SFT 7 +#define RT5670_VOL_R_MUTE (0x1 << 6) +#define RT5670_VOL_R_SFT 6 +#define RT5670_L_VOL_MASK (0x3f << 8) +#define RT5670_L_VOL_SFT 8 +#define RT5670_R_VOL_MASK (0x3f) +#define RT5670_R_VOL_SFT 0 + +/* SW Reset & Device ID (0x00) */ +#define RT5670_ID_MASK (0x3 << 1) +#define RT5670_ID_5670 (0x0 << 1) +#define RT5670_ID_5672 (0x1 << 1) +#define RT5670_ID_5671 (0x2 << 1) + +/* Combo Jack Control 1 (0x0a) */ +#define RT5670_CBJ_BST1_MASK (0xf << 12) +#define RT5670_CBJ_BST1_SFT (12) +#define RT5670_CBJ_JD_HP_EN (0x1 << 9) +#define RT5670_CBJ_JD_MIC_EN (0x1 << 8) +#define RT5670_CBJ_BST1_EN (0x1 << 2) + +/* Combo Jack Control 1 (0x0b) */ +#define RT5670_CBJ_MN_JD (0x1 << 12) +#define RT5670_CAPLESS_EN (0x1 << 11) +#define RT5670_CBJ_DET_MODE (0x1 << 7) + +/* IN2 Control (0x0e) */ +#define RT5670_BST_MASK1 (0xf<<12) +#define RT5670_BST_SFT1 12 +#define RT5670_BST_MASK2 (0xf<<8) +#define RT5670_BST_SFT2 8 +#define RT5670_IN_DF1 (0x1 << 7) +#define RT5670_IN_SFT1 7 +#define RT5670_IN_DF2 (0x1 << 6) +#define RT5670_IN_SFT2 6 + +/* INL and INR Volume Control (0x0f) */ +#define RT5670_INL_SEL_MASK (0x1 << 15) +#define RT5670_INL_SEL_SFT 15 +#define RT5670_INL_SEL_IN4P (0x0 << 15) +#define RT5670_INL_SEL_MONOP (0x1 << 15) +#define RT5670_INL_VOL_MASK (0x1f << 8) +#define RT5670_INL_VOL_SFT 8 +#define RT5670_INR_SEL_MASK (0x1 << 7) +#define RT5670_INR_SEL_SFT 7 +#define RT5670_INR_SEL_IN4N (0x0 << 7) +#define RT5670_INR_SEL_MONON (0x1 << 7) +#define RT5670_INR_VOL_MASK (0x1f) +#define RT5670_INR_VOL_SFT 0 + +/* Sidetone Control (0x18) */ +#define RT5670_ST_SEL_MASK (0x7 << 9) +#define RT5670_ST_SEL_SFT 9 +#define RT5670_M_ST_DACR2 (0x1 << 8) +#define RT5670_M_ST_DACR2_SFT 8 +#define RT5670_M_ST_DACL2 (0x1 << 7) +#define RT5670_M_ST_DACL2_SFT 7 +#define RT5670_ST_EN (0x1 << 6) +#define RT5670_ST_EN_SFT 6 + +/* DAC1 Digital Volume (0x19) */ +#define RT5670_DAC_L1_VOL_MASK (0xff << 8) +#define RT5670_DAC_L1_VOL_SFT 8 +#define RT5670_DAC_R1_VOL_MASK (0xff) +#define RT5670_DAC_R1_VOL_SFT 0 + +/* DAC2 Digital Volume (0x1a) */ +#define RT5670_DAC_L2_VOL_MASK (0xff << 8) +#define RT5670_DAC_L2_VOL_SFT 8 +#define RT5670_DAC_R2_VOL_MASK (0xff) +#define RT5670_DAC_R2_VOL_SFT 0 + +/* DAC2 Control (0x1b) */ +#define RT5670_M_DAC_L2_VOL (0x1 << 13) +#define RT5670_M_DAC_L2_VOL_SFT 13 +#define RT5670_M_DAC_R2_VOL (0x1 << 12) +#define RT5670_M_DAC_R2_VOL_SFT 12 +#define RT5670_DAC2_L_SEL_MASK (0x7 << 4) +#define RT5670_DAC2_L_SEL_SFT 4 +#define RT5670_DAC2_R_SEL_MASK (0x7 << 0) +#define RT5670_DAC2_R_SEL_SFT 0 + +/* ADC Digital Volume Control (0x1c) */ +#define RT5670_ADC_L_VOL_MASK (0x7f << 8) +#define RT5670_ADC_L_VOL_SFT 8 +#define RT5670_ADC_R_VOL_MASK (0x7f) +#define RT5670_ADC_R_VOL_SFT 0 + +/* Mono ADC Digital Volume Control (0x1d) */ +#define RT5670_MONO_ADC_L_VOL_MASK (0x7f << 8) +#define RT5670_MONO_ADC_L_VOL_SFT 8 +#define RT5670_MONO_ADC_R_VOL_MASK (0x7f) +#define RT5670_MONO_ADC_R_VOL_SFT 0 + +/* ADC Boost Volume Control (0x1e) */ +#define RT5670_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5670_STO1_ADC_L_BST_SFT 14 +#define RT5670_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5670_STO1_ADC_R_BST_SFT 12 +#define RT5670_STO1_ADC_COMP_MASK (0x3 << 10) +#define RT5670_STO1_ADC_COMP_SFT 10 +#define RT5670_STO2_ADC_L_BST_MASK (0x3 << 8) +#define RT5670_STO2_ADC_L_BST_SFT 8 +#define RT5670_STO2_ADC_R_BST_MASK (0x3 << 6) +#define RT5670_STO2_ADC_R_BST_SFT 6 +#define RT5670_STO2_ADC_COMP_MASK (0x3 << 4) +#define RT5670_STO2_ADC_COMP_SFT 4 + +/* Stereo2 ADC Mixer Control (0x26) */ +#define RT5670_STO2_ADC_SRC_MASK (0x1 << 15) +#define RT5670_STO2_ADC_SRC_SFT 15 + +/* Stereo ADC Mixer Control (0x26 0x27) */ +#define RT5670_M_ADC_L1 (0x1 << 14) +#define RT5670_M_ADC_L1_SFT 14 +#define RT5670_M_ADC_L2 (0x1 << 13) +#define RT5670_M_ADC_L2_SFT 13 +#define RT5670_ADC_1_SRC_MASK (0x1 << 12) +#define RT5670_ADC_1_SRC_SFT 12 +#define RT5670_ADC_1_SRC_ADC (0x1 << 12) +#define RT5670_ADC_1_SRC_DACMIX (0x0 << 12) +#define RT5670_ADC_2_SRC_MASK (0x1 << 11) +#define RT5670_ADC_2_SRC_SFT 11 +#define RT5670_ADC_SRC_MASK (0x1 << 10) +#define RT5670_ADC_SRC_SFT 10 +#define RT5670_DMIC_SRC_MASK (0x3 << 8) +#define RT5670_DMIC_SRC_SFT 8 +#define RT5670_M_ADC_R1 (0x1 << 6) +#define RT5670_M_ADC_R1_SFT 6 +#define RT5670_M_ADC_R2 (0x1 << 5) +#define RT5670_M_ADC_R2_SFT 5 +#define RT5670_DMIC3_SRC_MASK (0x1 << 1) +#define RT5670_DMIC3_SRC_SFT 0 + +/* Mono ADC Mixer Control (0x28) */ +#define RT5670_M_MONO_ADC_L1 (0x1 << 14) +#define RT5670_M_MONO_ADC_L1_SFT 14 +#define RT5670_M_MONO_ADC_L2 (0x1 << 13) +#define RT5670_M_MONO_ADC_L2_SFT 13 +#define RT5670_MONO_ADC_L1_SRC_MASK (0x1 << 12) +#define RT5670_MONO_ADC_L1_SRC_SFT 12 +#define RT5670_MONO_ADC_L1_SRC_DACMIXL (0x0 << 12) +#define RT5670_MONO_ADC_L1_SRC_ADCL (0x1 << 12) +#define RT5670_MONO_ADC_L2_SRC_MASK (0x1 << 11) +#define RT5670_MONO_ADC_L2_SRC_SFT 11 +#define RT5670_MONO_ADC_L_SRC_MASK (0x1 << 10) +#define RT5670_MONO_ADC_L_SRC_SFT 10 +#define RT5670_MONO_DMIC_L_SRC_MASK (0x3 << 8) +#define RT5670_MONO_DMIC_L_SRC_SFT 8 +#define RT5670_M_MONO_ADC_R1 (0x1 << 6) +#define RT5670_M_MONO_ADC_R1_SFT 6 +#define RT5670_M_MONO_ADC_R2 (0x1 << 5) +#define RT5670_M_MONO_ADC_R2_SFT 5 +#define RT5670_MONO_ADC_R1_SRC_MASK (0x1 << 4) +#define RT5670_MONO_ADC_R1_SRC_SFT 4 +#define RT5670_MONO_ADC_R1_SRC_ADCR (0x1 << 4) +#define RT5670_MONO_ADC_R1_SRC_DACMIXR (0x0 << 4) +#define RT5670_MONO_ADC_R2_SRC_MASK (0x1 << 3) +#define RT5670_MONO_ADC_R2_SRC_SFT 3 +#define RT5670_MONO_DMIC_R_SRC_MASK (0x3) +#define RT5670_MONO_DMIC_R_SRC_SFT 0 + +/* ADC Mixer to DAC Mixer Control (0x29) */ +#define RT5670_M_ADCMIX_L (0x1 << 15) +#define RT5670_M_ADCMIX_L_SFT 15 +#define RT5670_M_DAC1_L (0x1 << 14) +#define RT5670_M_DAC1_L_SFT 14 +#define RT5670_DAC1_R_SEL_MASK (0x3 << 10) +#define RT5670_DAC1_R_SEL_SFT 10 +#define RT5670_DAC1_R_SEL_IF1 (0x0 << 10) +#define RT5670_DAC1_R_SEL_IF2 (0x1 << 10) +#define RT5670_DAC1_R_SEL_IF3 (0x2 << 10) +#define RT5670_DAC1_R_SEL_IF4 (0x3 << 10) +#define RT5670_DAC1_L_SEL_MASK (0x3 << 8) +#define RT5670_DAC1_L_SEL_SFT 8 +#define RT5670_DAC1_L_SEL_IF1 (0x0 << 8) +#define RT5670_DAC1_L_SEL_IF2 (0x1 << 8) +#define RT5670_DAC1_L_SEL_IF3 (0x2 << 8) +#define RT5670_DAC1_L_SEL_IF4 (0x3 << 8) +#define RT5670_M_ADCMIX_R (0x1 << 7) +#define RT5670_M_ADCMIX_R_SFT 7 +#define RT5670_M_DAC1_R (0x1 << 6) +#define RT5670_M_DAC1_R_SFT 6 + +/* Stereo DAC Mixer Control (0x2a) */ +#define RT5670_M_DAC_L1 (0x1 << 14) +#define RT5670_M_DAC_L1_SFT 14 +#define RT5670_DAC_L1_STO_L_VOL_MASK (0x1 << 13) +#define RT5670_DAC_L1_STO_L_VOL_SFT 13 +#define RT5670_M_DAC_L2 (0x1 << 12) +#define RT5670_M_DAC_L2_SFT 12 +#define RT5670_DAC_L2_STO_L_VOL_MASK (0x1 << 11) +#define RT5670_DAC_L2_STO_L_VOL_SFT 11 +#define RT5670_M_DAC_R1_STO_L (0x1 << 9) +#define RT5670_M_DAC_R1_STO_L_SFT 9 +#define RT5670_DAC_R1_STO_L_VOL_MASK (0x1 << 8) +#define RT5670_DAC_R1_STO_L_VOL_SFT 8 +#define RT5670_M_DAC_R1 (0x1 << 6) +#define RT5670_M_DAC_R1_SFT 6 +#define RT5670_DAC_R1_STO_R_VOL_MASK (0x1 << 5) +#define RT5670_DAC_R1_STO_R_VOL_SFT 5 +#define RT5670_M_DAC_R2 (0x1 << 4) +#define RT5670_M_DAC_R2_SFT 4 +#define RT5670_DAC_R2_STO_R_VOL_MASK (0x1 << 3) +#define RT5670_DAC_R2_STO_R_VOL_SFT 3 +#define RT5670_M_DAC_L1_STO_R (0x1 << 1) +#define RT5670_M_DAC_L1_STO_R_SFT 1 +#define RT5670_DAC_L1_STO_R_VOL_MASK (0x1) +#define RT5670_DAC_L1_STO_R_VOL_SFT 0 + +/* Mono DAC Mixer Control (0x2b) */ +#define RT5670_M_DAC_L1_MONO_L (0x1 << 14) +#define RT5670_M_DAC_L1_MONO_L_SFT 14 +#define RT5670_DAC_L1_MONO_L_VOL_MASK (0x1 << 13) +#define RT5670_DAC_L1_MONO_L_VOL_SFT 13 +#define RT5670_M_DAC_L2_MONO_L (0x1 << 12) +#define RT5670_M_DAC_L2_MONO_L_SFT 12 +#define RT5670_DAC_L2_MONO_L_VOL_MASK (0x1 << 11) +#define RT5670_DAC_L2_MONO_L_VOL_SFT 11 +#define RT5670_M_DAC_R2_MONO_L (0x1 << 10) +#define RT5670_M_DAC_R2_MONO_L_SFT 10 +#define RT5670_DAC_R2_MONO_L_VOL_MASK (0x1 << 9) +#define RT5670_DAC_R2_MONO_L_VOL_SFT 9 +#define RT5670_M_DAC_R1_MONO_R (0x1 << 6) +#define RT5670_M_DAC_R1_MONO_R_SFT 6 +#define RT5670_DAC_R1_MONO_R_VOL_MASK (0x1 << 5) +#define RT5670_DAC_R1_MONO_R_VOL_SFT 5 +#define RT5670_M_DAC_R2_MONO_R (0x1 << 4) +#define RT5670_M_DAC_R2_MONO_R_SFT 4 +#define RT5670_DAC_R2_MONO_R_VOL_MASK (0x1 << 3) +#define RT5670_DAC_R2_MONO_R_VOL_SFT 3 +#define RT5670_M_DAC_L2_MONO_R (0x1 << 2) +#define RT5670_M_DAC_L2_MONO_R_SFT 2 +#define RT5670_DAC_L2_MONO_R_VOL_MASK (0x1 << 1) +#define RT5670_DAC_L2_MONO_R_VOL_SFT 1 + +/* Digital Mixer Control (0x2c) */ +#define RT5670_M_STO_L_DAC_L (0x1 << 15) +#define RT5670_M_STO_L_DAC_L_SFT 15 +#define RT5670_STO_L_DAC_L_VOL_MASK (0x1 << 14) +#define RT5670_STO_L_DAC_L_VOL_SFT 14 +#define RT5670_M_DAC_L2_DAC_L (0x1 << 13) +#define RT5670_M_DAC_L2_DAC_L_SFT 13 +#define RT5670_DAC_L2_DAC_L_VOL_MASK (0x1 << 12) +#define RT5670_DAC_L2_DAC_L_VOL_SFT 12 +#define RT5670_M_STO_R_DAC_R (0x1 << 11) +#define RT5670_M_STO_R_DAC_R_SFT 11 +#define RT5670_STO_R_DAC_R_VOL_MASK (0x1 << 10) +#define RT5670_STO_R_DAC_R_VOL_SFT 10 +#define RT5670_M_DAC_R2_DAC_R (0x1 << 9) +#define RT5670_M_DAC_R2_DAC_R_SFT 9 +#define RT5670_DAC_R2_DAC_R_VOL_MASK (0x1 << 8) +#define RT5670_DAC_R2_DAC_R_VOL_SFT 8 +#define RT5670_M_DAC_R2_DAC_L (0x1 << 7) +#define RT5670_M_DAC_R2_DAC_L_SFT 7 +#define RT5670_DAC_R2_DAC_L_VOL_MASK (0x1 << 6) +#define RT5670_DAC_R2_DAC_L_VOL_SFT 6 +#define RT5670_M_DAC_L2_DAC_R (0x1 << 5) +#define RT5670_M_DAC_L2_DAC_R_SFT 5 +#define RT5670_DAC_L2_DAC_R_VOL_MASK (0x1 << 4) +#define RT5670_DAC_L2_DAC_R_VOL_SFT 4 + +/* DSP Path Control 1 (0x2d) */ +#define RT5670_RXDP_SEL_MASK (0x7 << 13) +#define RT5670_RXDP_SEL_SFT 13 +#define RT5670_RXDP_SRC_MASK (0x3 << 11) +#define RT5670_RXDP_SRC_SFT 11 +#define RT5670_RXDP_SRC_NOR (0x0 << 11) +#define RT5670_RXDP_SRC_DIV2 (0x1 << 11) +#define RT5670_RXDP_SRC_DIV3 (0x2 << 11) +#define RT5670_TXDP_SRC_MASK (0x3 << 4) +#define RT5670_TXDP_SRC_SFT 4 +#define RT5670_TXDP_SRC_NOR (0x0 << 4) +#define RT5670_TXDP_SRC_DIV2 (0x1 << 4) +#define RT5670_TXDP_SRC_DIV3 (0x2 << 4) +#define RT5670_TXDP_SLOT_SEL_MASK (0x3 << 2) +#define RT5670_TXDP_SLOT_SEL_SFT 2 +#define RT5670_DSP_UL_SEL (0x1 << 1) +#define RT5670_DSP_UL_SFT 1 +#define RT5670_DSP_DL_SEL 0x1 +#define RT5670_DSP_DL_SFT 0 + +/* DSP Path Control 2 (0x2e) */ +#define RT5670_TXDP_L_VOL_MASK (0x7f << 8) +#define RT5670_TXDP_L_VOL_SFT 8 +#define RT5670_TXDP_R_VOL_MASK (0x7f) +#define RT5670_TXDP_R_VOL_SFT 0 + +/* Digital Interface Data Control (0x2f) */ +#define RT5670_IF1_ADC2_IN_SEL (0x1 << 15) +#define RT5670_IF1_ADC2_IN_SFT 15 +#define RT5670_IF2_ADC_IN_MASK (0x7 << 12) +#define RT5670_IF2_ADC_IN_SFT 12 +#define RT5670_IF2_DAC_SEL_MASK (0x3 << 10) +#define RT5670_IF2_DAC_SEL_SFT 10 +#define RT5670_IF2_ADC_SEL_MASK (0x3 << 8) +#define RT5670_IF2_ADC_SEL_SFT 8 + +/* Digital Interface Data Control (0x30) */ +#define RT5670_IF4_ADC_IN_MASK (0x3 << 4) +#define RT5670_IF4_ADC_IN_SFT 4 + +/* PDM Output Control (0x31) */ +#define RT5670_PDM1_L_MASK (0x1 << 15) +#define RT5670_PDM1_L_SFT 15 +#define RT5670_M_PDM1_L (0x1 << 14) +#define RT5670_M_PDM1_L_SFT 14 +#define RT5670_PDM1_R_MASK (0x1 << 13) +#define RT5670_PDM1_R_SFT 13 +#define RT5670_M_PDM1_R (0x1 << 12) +#define RT5670_M_PDM1_R_SFT 12 +#define RT5670_PDM2_L_MASK (0x1 << 11) +#define RT5670_PDM2_L_SFT 11 +#define RT5670_M_PDM2_L (0x1 << 10) +#define RT5670_M_PDM2_L_SFT 10 +#define RT5670_PDM2_R_MASK (0x1 << 9) +#define RT5670_PDM2_R_SFT 9 +#define RT5670_M_PDM2_R (0x1 << 8) +#define RT5670_M_PDM2_R_SFT 8 +#define RT5670_PDM2_BUSY (0x1 << 7) +#define RT5670_PDM1_BUSY (0x1 << 6) +#define RT5670_PDM_PATTERN (0x1 << 5) +#define RT5670_PDM_GAIN (0x1 << 4) +#define RT5670_PDM_DIV_MASK (0x3) + +/* REC Left Mixer Control 1 (0x3b) */ +#define RT5670_G_HP_L_RM_L_MASK (0x7 << 13) +#define RT5670_G_HP_L_RM_L_SFT 13 +#define RT5670_G_IN_L_RM_L_MASK (0x7 << 10) +#define RT5670_G_IN_L_RM_L_SFT 10 +#define RT5670_G_BST4_RM_L_MASK (0x7 << 7) +#define RT5670_G_BST4_RM_L_SFT 7 +#define RT5670_G_BST3_RM_L_MASK (0x7 << 4) +#define RT5670_G_BST3_RM_L_SFT 4 +#define RT5670_G_BST2_RM_L_MASK (0x7 << 1) +#define RT5670_G_BST2_RM_L_SFT 1 + +/* REC Left Mixer Control 2 (0x3c) */ +#define RT5670_G_BST1_RM_L_MASK (0x7 << 13) +#define RT5670_G_BST1_RM_L_SFT 13 +#define RT5670_M_IN_L_RM_L (0x1 << 5) +#define RT5670_M_IN_L_RM_L_SFT 5 +#define RT5670_M_BST2_RM_L (0x1 << 3) +#define RT5670_M_BST2_RM_L_SFT 3 +#define RT5670_M_BST1_RM_L (0x1 << 1) +#define RT5670_M_BST1_RM_L_SFT 1 + +/* REC Right Mixer Control 1 (0x3d) */ +#define RT5670_G_HP_R_RM_R_MASK (0x7 << 13) +#define RT5670_G_HP_R_RM_R_SFT 13 +#define RT5670_G_IN_R_RM_R_MASK (0x7 << 10) +#define RT5670_G_IN_R_RM_R_SFT 10 +#define RT5670_G_BST4_RM_R_MASK (0x7 << 7) +#define RT5670_G_BST4_RM_R_SFT 7 +#define RT5670_G_BST3_RM_R_MASK (0x7 << 4) +#define RT5670_G_BST3_RM_R_SFT 4 +#define RT5670_G_BST2_RM_R_MASK (0x7 << 1) +#define RT5670_G_BST2_RM_R_SFT 1 + +/* REC Right Mixer Control 2 (0x3e) */ +#define RT5670_G_BST1_RM_R_MASK (0x7 << 13) +#define RT5670_G_BST1_RM_R_SFT 13 +#define RT5670_M_IN_R_RM_R (0x1 << 5) +#define RT5670_M_IN_R_RM_R_SFT 5 +#define RT5670_M_BST2_RM_R (0x1 << 3) +#define RT5670_M_BST2_RM_R_SFT 3 +#define RT5670_M_BST1_RM_R (0x1 << 1) +#define RT5670_M_BST1_RM_R_SFT 1 + +/* HPMIX Control (0x45) */ +#define RT5670_M_DAC2_HM (0x1 << 15) +#define RT5670_M_DAC2_HM_SFT 15 +#define RT5670_M_HPVOL_HM (0x1 << 14) +#define RT5670_M_HPVOL_HM_SFT 14 +#define RT5670_M_DAC1_HM (0x1 << 13) +#define RT5670_M_DAC1_HM_SFT 13 +#define RT5670_G_HPOMIX_MASK (0x1 << 12) +#define RT5670_G_HPOMIX_SFT 12 +#define RT5670_M_INR1_HMR (0x1 << 3) +#define RT5670_M_INR1_HMR_SFT 3 +#define RT5670_M_DACR1_HMR (0x1 << 2) +#define RT5670_M_DACR1_HMR_SFT 2 +#define RT5670_M_INL1_HML (0x1 << 1) +#define RT5670_M_INL1_HML_SFT 1 +#define RT5670_M_DACL1_HML (0x1) +#define RT5670_M_DACL1_HML_SFT 0 + +/* Mono Output Mixer Control (0x4c) */ +#define RT5670_M_DAC_R2_MA (0x1 << 15) +#define RT5670_M_DAC_R2_MA_SFT 15 +#define RT5670_M_DAC_L2_MA (0x1 << 14) +#define RT5670_M_DAC_L2_MA_SFT 14 +#define RT5670_M_OV_R_MM (0x1 << 13) +#define RT5670_M_OV_R_MM_SFT 13 +#define RT5670_M_OV_L_MM (0x1 << 12) +#define RT5670_M_OV_L_MM_SFT 12 +#define RT5670_G_MONOMIX_MASK (0x1 << 10) +#define RT5670_G_MONOMIX_SFT 10 +#define RT5670_M_DAC_R2_MM (0x1 << 9) +#define RT5670_M_DAC_R2_MM_SFT 9 +#define RT5670_M_DAC_L2_MM (0x1 << 8) +#define RT5670_M_DAC_L2_MM_SFT 8 +#define RT5670_M_BST4_MM (0x1 << 7) +#define RT5670_M_BST4_MM_SFT 7 + +/* Output Left Mixer Control 1 (0x4d) */ +#define RT5670_G_BST3_OM_L_MASK (0x7 << 13) +#define RT5670_G_BST3_OM_L_SFT 13 +#define RT5670_G_BST2_OM_L_MASK (0x7 << 10) +#define RT5670_G_BST2_OM_L_SFT 10 +#define RT5670_G_BST1_OM_L_MASK (0x7 << 7) +#define RT5670_G_BST1_OM_L_SFT 7 +#define RT5670_G_IN_L_OM_L_MASK (0x7 << 4) +#define RT5670_G_IN_L_OM_L_SFT 4 +#define RT5670_G_RM_L_OM_L_MASK (0x7 << 1) +#define RT5670_G_RM_L_OM_L_SFT 1 + +/* Output Left Mixer Control 2 (0x4e) */ +#define RT5670_G_DAC_R2_OM_L_MASK (0x7 << 13) +#define RT5670_G_DAC_R2_OM_L_SFT 13 +#define RT5670_G_DAC_L2_OM_L_MASK (0x7 << 10) +#define RT5670_G_DAC_L2_OM_L_SFT 10 +#define RT5670_G_DAC_L1_OM_L_MASK (0x7 << 7) +#define RT5670_G_DAC_L1_OM_L_SFT 7 + +/* Output Left Mixer Control 3 (0x4f) */ +#define RT5670_M_BST1_OM_L (0x1 << 5) +#define RT5670_M_BST1_OM_L_SFT 5 +#define RT5670_M_IN_L_OM_L (0x1 << 4) +#define RT5670_M_IN_L_OM_L_SFT 4 +#define RT5670_M_DAC_L2_OM_L (0x1 << 1) +#define RT5670_M_DAC_L2_OM_L_SFT 1 +#define RT5670_M_DAC_L1_OM_L (0x1) +#define RT5670_M_DAC_L1_OM_L_SFT 0 + +/* Output Right Mixer Control 1 (0x50) */ +#define RT5670_G_BST4_OM_R_MASK (0x7 << 13) +#define RT5670_G_BST4_OM_R_SFT 13 +#define RT5670_G_BST2_OM_R_MASK (0x7 << 10) +#define RT5670_G_BST2_OM_R_SFT 10 +#define RT5670_G_BST1_OM_R_MASK (0x7 << 7) +#define RT5670_G_BST1_OM_R_SFT 7 +#define RT5670_G_IN_R_OM_R_MASK (0x7 << 4) +#define RT5670_G_IN_R_OM_R_SFT 4 +#define RT5670_G_RM_R_OM_R_MASK (0x7 << 1) +#define RT5670_G_RM_R_OM_R_SFT 1 + +/* Output Right Mixer Control 2 (0x51) */ +#define RT5670_G_DAC_L2_OM_R_MASK (0x7 << 13) +#define RT5670_G_DAC_L2_OM_R_SFT 13 +#define RT5670_G_DAC_R2_OM_R_MASK (0x7 << 10) +#define RT5670_G_DAC_R2_OM_R_SFT 10 +#define RT5670_G_DAC_R1_OM_R_MASK (0x7 << 7) +#define RT5670_G_DAC_R1_OM_R_SFT 7 + +/* Output Right Mixer Control 3 (0x52) */ +#define RT5670_M_BST2_OM_R (0x1 << 6) +#define RT5670_M_BST2_OM_R_SFT 6 +#define RT5670_M_IN_R_OM_R (0x1 << 4) +#define RT5670_M_IN_R_OM_R_SFT 4 +#define RT5670_M_DAC_R2_OM_R (0x1 << 1) +#define RT5670_M_DAC_R2_OM_R_SFT 1 +#define RT5670_M_DAC_R1_OM_R (0x1) +#define RT5670_M_DAC_R1_OM_R_SFT 0 + +/* LOUT Mixer Control (0x53) */ +#define RT5670_M_DAC_L1_LM (0x1 << 15) +#define RT5670_M_DAC_L1_LM_SFT 15 +#define RT5670_M_DAC_R1_LM (0x1 << 14) +#define RT5670_M_DAC_R1_LM_SFT 14 +#define RT5670_M_OV_L_LM (0x1 << 13) +#define RT5670_M_OV_L_LM_SFT 13 +#define RT5670_M_OV_R_LM (0x1 << 12) +#define RT5670_M_OV_R_LM_SFT 12 +#define RT5670_G_LOUTMIX_MASK (0x1 << 11) +#define RT5670_G_LOUTMIX_SFT 11 + +/* Power Management for Digital 1 (0x61) */ +#define RT5670_PWR_I2S1 (0x1 << 15) +#define RT5670_PWR_I2S1_BIT 15 +#define RT5670_PWR_I2S2 (0x1 << 14) +#define RT5670_PWR_I2S2_BIT 14 +#define RT5670_PWR_DAC_L1 (0x1 << 12) +#define RT5670_PWR_DAC_L1_BIT 12 +#define RT5670_PWR_DAC_R1 (0x1 << 11) +#define RT5670_PWR_DAC_R1_BIT 11 +#define RT5670_PWR_DAC_L2 (0x1 << 7) +#define RT5670_PWR_DAC_L2_BIT 7 +#define RT5670_PWR_DAC_R2 (0x1 << 6) +#define RT5670_PWR_DAC_R2_BIT 6 +#define RT5670_PWR_ADC_L (0x1 << 2) +#define RT5670_PWR_ADC_L_BIT 2 +#define RT5670_PWR_ADC_R (0x1 << 1) +#define RT5670_PWR_ADC_R_BIT 1 +#define RT5670_PWR_CLS_D (0x1) +#define RT5670_PWR_CLS_D_BIT 0 + +/* Power Management for Digital 2 (0x62) */ +#define RT5670_PWR_ADC_S1F (0x1 << 15) +#define RT5670_PWR_ADC_S1F_BIT 15 +#define RT5670_PWR_ADC_MF_L (0x1 << 14) +#define RT5670_PWR_ADC_MF_L_BIT 14 +#define RT5670_PWR_ADC_MF_R (0x1 << 13) +#define RT5670_PWR_ADC_MF_R_BIT 13 +#define RT5670_PWR_I2S_DSP (0x1 << 12) +#define RT5670_PWR_I2S_DSP_BIT 12 +#define RT5670_PWR_DAC_S1F (0x1 << 11) +#define RT5670_PWR_DAC_S1F_BIT 11 +#define RT5670_PWR_DAC_MF_L (0x1 << 10) +#define RT5670_PWR_DAC_MF_L_BIT 10 +#define RT5670_PWR_DAC_MF_R (0x1 << 9) +#define RT5670_PWR_DAC_MF_R_BIT 9 +#define RT5670_PWR_ADC_S2F (0x1 << 8) +#define RT5670_PWR_ADC_S2F_BIT 8 +#define RT5670_PWR_PDM1 (0x1 << 7) +#define RT5670_PWR_PDM1_BIT 7 +#define RT5670_PWR_PDM2 (0x1 << 6) +#define RT5670_PWR_PDM2_BIT 6 + +/* Power Management for Analog 1 (0x63) */ +#define RT5670_PWR_VREF1 (0x1 << 15) +#define RT5670_PWR_VREF1_BIT 15 +#define RT5670_PWR_FV1 (0x1 << 14) +#define RT5670_PWR_FV1_BIT 14 +#define RT5670_PWR_MB (0x1 << 13) +#define RT5670_PWR_MB_BIT 13 +#define RT5670_PWR_LM (0x1 << 12) +#define RT5670_PWR_LM_BIT 12 +#define RT5670_PWR_BG (0x1 << 11) +#define RT5670_PWR_BG_BIT 11 +#define RT5670_PWR_HP_L (0x1 << 7) +#define RT5670_PWR_HP_L_BIT 7 +#define RT5670_PWR_HP_R (0x1 << 6) +#define RT5670_PWR_HP_R_BIT 6 +#define RT5670_PWR_HA (0x1 << 5) +#define RT5670_PWR_HA_BIT 5 +#define RT5670_PWR_VREF2 (0x1 << 4) +#define RT5670_PWR_VREF2_BIT 4 +#define RT5670_PWR_FV2 (0x1 << 3) +#define RT5670_PWR_FV2_BIT 3 +#define RT5670_LDO_SEL_MASK (0x7) +#define RT5670_LDO_SEL_SFT 0 + +/* Power Management for Analog 2 (0x64) */ +#define RT5670_PWR_BST1 (0x1 << 15) +#define RT5670_PWR_BST1_BIT 15 +#define RT5670_PWR_BST2 (0x1 << 13) +#define RT5670_PWR_BST2_BIT 13 +#define RT5670_PWR_MB1 (0x1 << 11) +#define RT5670_PWR_MB1_BIT 11 +#define RT5670_PWR_MB2 (0x1 << 10) +#define RT5670_PWR_MB2_BIT 10 +#define RT5670_PWR_PLL (0x1 << 9) +#define RT5670_PWR_PLL_BIT 9 +#define RT5670_PWR_BST1_P (0x1 << 6) +#define RT5670_PWR_BST1_P_BIT 6 +#define RT5670_PWR_BST2_P (0x1 << 4) +#define RT5670_PWR_BST2_P_BIT 4 +#define RT5670_PWR_JD1 (0x1 << 2) +#define RT5670_PWR_JD1_BIT 2 +#define RT5670_PWR_JD (0x1 << 1) +#define RT5670_PWR_JD_BIT 1 + +/* Power Management for Mixer (0x65) */ +#define RT5670_PWR_OM_L (0x1 << 15) +#define RT5670_PWR_OM_L_BIT 15 +#define RT5670_PWR_OM_R (0x1 << 14) +#define RT5670_PWR_OM_R_BIT 14 +#define RT5670_PWR_RM_L (0x1 << 11) +#define RT5670_PWR_RM_L_BIT 11 +#define RT5670_PWR_RM_R (0x1 << 10) +#define RT5670_PWR_RM_R_BIT 10 + +/* Power Management for Volume (0x66) */ +#define RT5670_PWR_HV_L (0x1 << 11) +#define RT5670_PWR_HV_L_BIT 11 +#define RT5670_PWR_HV_R (0x1 << 10) +#define RT5670_PWR_HV_R_BIT 10 +#define RT5670_PWR_IN_L (0x1 << 9) +#define RT5670_PWR_IN_L_BIT 9 +#define RT5670_PWR_IN_R (0x1 << 8) +#define RT5670_PWR_IN_R_BIT 8 +#define RT5670_PWR_MIC_DET (0x1 << 5) +#define RT5670_PWR_MIC_DET_BIT 5 + +/* I2S1/2/3 Audio Serial Data Port Control (0x70 0x71 0x72) */ +#define RT5670_I2S_MS_MASK (0x1 << 15) +#define RT5670_I2S_MS_SFT 15 +#define RT5670_I2S_MS_M (0x0 << 15) +#define RT5670_I2S_MS_S (0x1 << 15) +#define RT5670_I2S_IF_MASK (0x7 << 12) +#define RT5670_I2S_IF_SFT 12 +#define RT5670_I2S_O_CP_MASK (0x3 << 10) +#define RT5670_I2S_O_CP_SFT 10 +#define RT5670_I2S_O_CP_OFF (0x0 << 10) +#define RT5670_I2S_O_CP_U_LAW (0x1 << 10) +#define RT5670_I2S_O_CP_A_LAW (0x2 << 10) +#define RT5670_I2S_I_CP_MASK (0x3 << 8) +#define RT5670_I2S_I_CP_SFT 8 +#define RT5670_I2S_I_CP_OFF (0x0 << 8) +#define RT5670_I2S_I_CP_U_LAW (0x1 << 8) +#define RT5670_I2S_I_CP_A_LAW (0x2 << 8) +#define RT5670_I2S_BP_MASK (0x1 << 7) +#define RT5670_I2S_BP_SFT 7 +#define RT5670_I2S_BP_NOR (0x0 << 7) +#define RT5670_I2S_BP_INV (0x1 << 7) +#define RT5670_I2S_DL_MASK (0x3 << 2) +#define RT5670_I2S_DL_SFT 2 +#define RT5670_I2S_DL_16 (0x0 << 2) +#define RT5670_I2S_DL_20 (0x1 << 2) +#define RT5670_I2S_DL_24 (0x2 << 2) +#define RT5670_I2S_DL_8 (0x3 << 2) +#define RT5670_I2S_DF_MASK (0x3) +#define RT5670_I2S_DF_SFT 0 +#define RT5670_I2S_DF_I2S (0x0) +#define RT5670_I2S_DF_LEFT (0x1) +#define RT5670_I2S_DF_PCM_A (0x2) +#define RT5670_I2S_DF_PCM_B (0x3) + +/* I2S2 Audio Serial Data Port Control (0x71) */ +#define RT5670_I2S2_SDI_MASK (0x1 << 6) +#define RT5670_I2S2_SDI_SFT 6 +#define RT5670_I2S2_SDI_I2S1 (0x0 << 6) +#define RT5670_I2S2_SDI_I2S2 (0x1 << 6) + +/* ADC/DAC Clock Control 1 (0x73) */ +#define RT5670_I2S_BCLK_MS1_MASK (0x1 << 15) +#define RT5670_I2S_BCLK_MS1_SFT 15 +#define RT5670_I2S_BCLK_MS1_32 (0x0 << 15) +#define RT5670_I2S_BCLK_MS1_64 (0x1 << 15) +#define RT5670_I2S_PD1_MASK (0x7 << 12) +#define RT5670_I2S_PD1_SFT 12 +#define RT5670_I2S_PD1_1 (0x0 << 12) +#define RT5670_I2S_PD1_2 (0x1 << 12) +#define RT5670_I2S_PD1_3 (0x2 << 12) +#define RT5670_I2S_PD1_4 (0x3 << 12) +#define RT5670_I2S_PD1_6 (0x4 << 12) +#define RT5670_I2S_PD1_8 (0x5 << 12) +#define RT5670_I2S_PD1_12 (0x6 << 12) +#define RT5670_I2S_PD1_16 (0x7 << 12) +#define RT5670_I2S_BCLK_MS2_MASK (0x1 << 11) +#define RT5670_I2S_BCLK_MS2_SFT 11 +#define RT5670_I2S_BCLK_MS2_32 (0x0 << 11) +#define RT5670_I2S_BCLK_MS2_64 (0x1 << 11) +#define RT5670_I2S_PD2_MASK (0x7 << 8) +#define RT5670_I2S_PD2_SFT 8 +#define RT5670_I2S_PD2_1 (0x0 << 8) +#define RT5670_I2S_PD2_2 (0x1 << 8) +#define RT5670_I2S_PD2_3 (0x2 << 8) +#define RT5670_I2S_PD2_4 (0x3 << 8) +#define RT5670_I2S_PD2_6 (0x4 << 8) +#define RT5670_I2S_PD2_8 (0x5 << 8) +#define RT5670_I2S_PD2_12 (0x6 << 8) +#define RT5670_I2S_PD2_16 (0x7 << 8) +#define RT5670_I2S_BCLK_MS3_MASK (0x1 << 7) +#define RT5670_I2S_BCLK_MS3_SFT 7 +#define RT5670_I2S_BCLK_MS3_32 (0x0 << 7) +#define RT5670_I2S_BCLK_MS3_64 (0x1 << 7) +#define RT5670_I2S_PD3_MASK (0x7 << 4) +#define RT5670_I2S_PD3_SFT 4 +#define RT5670_I2S_PD3_1 (0x0 << 4) +#define RT5670_I2S_PD3_2 (0x1 << 4) +#define RT5670_I2S_PD3_3 (0x2 << 4) +#define RT5670_I2S_PD3_4 (0x3 << 4) +#define RT5670_I2S_PD3_6 (0x4 << 4) +#define RT5670_I2S_PD3_8 (0x5 << 4) +#define RT5670_I2S_PD3_12 (0x6 << 4) +#define RT5670_I2S_PD3_16 (0x7 << 4) +#define RT5670_DAC_OSR_MASK (0x3 << 2) +#define RT5670_DAC_OSR_SFT 2 +#define RT5670_DAC_OSR_128 (0x0 << 2) +#define RT5670_DAC_OSR_64 (0x1 << 2) +#define RT5670_DAC_OSR_32 (0x2 << 2) +#define RT5670_DAC_OSR_16 (0x3 << 2) +#define RT5670_ADC_OSR_MASK (0x3) +#define RT5670_ADC_OSR_SFT 0 +#define RT5670_ADC_OSR_128 (0x0) +#define RT5670_ADC_OSR_64 (0x1) +#define RT5670_ADC_OSR_32 (0x2) +#define RT5670_ADC_OSR_16 (0x3) + +/* ADC/DAC Clock Control 2 (0x74) */ +#define RT5670_DAC_L_OSR_MASK (0x3 << 14) +#define RT5670_DAC_L_OSR_SFT 14 +#define RT5670_DAC_L_OSR_128 (0x0 << 14) +#define RT5670_DAC_L_OSR_64 (0x1 << 14) +#define RT5670_DAC_L_OSR_32 (0x2 << 14) +#define RT5670_DAC_L_OSR_16 (0x3 << 14) +#define RT5670_ADC_R_OSR_MASK (0x3 << 12) +#define RT5670_ADC_R_OSR_SFT 12 +#define RT5670_ADC_R_OSR_128 (0x0 << 12) +#define RT5670_ADC_R_OSR_64 (0x1 << 12) +#define RT5670_ADC_R_OSR_32 (0x2 << 12) +#define RT5670_ADC_R_OSR_16 (0x3 << 12) +#define RT5670_DAHPF_EN (0x1 << 11) +#define RT5670_DAHPF_EN_SFT 11 +#define RT5670_ADHPF_EN (0x1 << 10) +#define RT5670_ADHPF_EN_SFT 10 + +/* Digital Microphone Control (0x75) */ +#define RT5670_DMIC_1_EN_MASK (0x1 << 15) +#define RT5670_DMIC_1_EN_SFT 15 +#define RT5670_DMIC_1_DIS (0x0 << 15) +#define RT5670_DMIC_1_EN (0x1 << 15) +#define RT5670_DMIC_2_EN_MASK (0x1 << 14) +#define RT5670_DMIC_2_EN_SFT 14 +#define RT5670_DMIC_2_DIS (0x0 << 14) +#define RT5670_DMIC_2_EN (0x1 << 14) +#define RT5670_DMIC_1L_LH_MASK (0x1 << 13) +#define RT5670_DMIC_1L_LH_SFT 13 +#define RT5670_DMIC_1L_LH_FALLING (0x0 << 13) +#define RT5670_DMIC_1L_LH_RISING (0x1 << 13) +#define RT5670_DMIC_1R_LH_MASK (0x1 << 12) +#define RT5670_DMIC_1R_LH_SFT 12 +#define RT5670_DMIC_1R_LH_FALLING (0x0 << 12) +#define RT5670_DMIC_1R_LH_RISING (0x1 << 12) +#define RT5670_DMIC_2_DP_MASK (0x1 << 10) +#define RT5670_DMIC_2_DP_SFT 10 +#define RT5670_DMIC_2_DP_GPIO8 (0x0 << 10) +#define RT5670_DMIC_2_DP_IN3N (0x1 << 10) +#define RT5670_DMIC_2L_LH_MASK (0x1 << 9) +#define RT5670_DMIC_2L_LH_SFT 9 +#define RT5670_DMIC_2L_LH_FALLING (0x0 << 9) +#define RT5670_DMIC_2L_LH_RISING (0x1 << 9) +#define RT5670_DMIC_2R_LH_MASK (0x1 << 8) +#define RT5670_DMIC_2R_LH_SFT 8 +#define RT5670_DMIC_2R_LH_FALLING (0x0 << 8) +#define RT5670_DMIC_2R_LH_RISING (0x1 << 8) +#define RT5670_DMIC_CLK_MASK (0x7 << 5) +#define RT5670_DMIC_CLK_SFT 5 +#define RT5670_DMIC_3_EN_MASK (0x1 << 4) +#define RT5670_DMIC_3_EN_SFT 4 +#define RT5670_DMIC_3_DIS (0x0 << 4) +#define RT5670_DMIC_3_EN (0x1 << 4) +#define RT5670_DMIC_1_DP_MASK (0x3 << 0) +#define RT5670_DMIC_1_DP_SFT 0 +#define RT5670_DMIC_1_DP_GPIO6 (0x0 << 0) +#define RT5670_DMIC_1_DP_IN2P (0x1 << 0) +#define RT5670_DMIC_1_DP_GPIO7 (0x2 << 0) + +/* Digital Microphone Control2 (0x76) */ +#define RT5670_DMIC_3_DP_MASK (0x3 << 6) +#define RT5670_DMIC_3_DP_SFT 6 +#define RT5670_DMIC_3_DP_GPIO9 (0x0 << 6) +#define RT5670_DMIC_3_DP_GPIO10 (0x1 << 6) +#define RT5670_DMIC_3_DP_GPIO5 (0x2 << 6) + +/* Global Clock Control (0x80) */ +#define RT5670_SCLK_SRC_MASK (0x3 << 14) +#define RT5670_SCLK_SRC_SFT 14 +#define RT5670_SCLK_SRC_MCLK (0x0 << 14) +#define RT5670_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5670_SCLK_SRC_RCCLK (0x2 << 14) /* 15MHz */ +#define RT5670_PLL1_SRC_MASK (0x7 << 11) +#define RT5670_PLL1_SRC_SFT 11 +#define RT5670_PLL1_SRC_MCLK (0x0 << 11) +#define RT5670_PLL1_SRC_BCLK1 (0x1 << 11) +#define RT5670_PLL1_SRC_BCLK2 (0x2 << 11) +#define RT5670_PLL1_SRC_BCLK3 (0x3 << 11) +#define RT5670_PLL1_PD_MASK (0x1 << 3) +#define RT5670_PLL1_PD_SFT 3 +#define RT5670_PLL1_PD_1 (0x0 << 3) +#define RT5670_PLL1_PD_2 (0x1 << 3) + +#define RT5670_PLL_INP_MAX 40000000 +#define RT5670_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x81) */ +#define RT5670_PLL_N_MAX 0x1ff +#define RT5670_PLL_N_MASK (RT5670_PLL_N_MAX << 7) +#define RT5670_PLL_N_SFT 7 +#define RT5670_PLL_K_MAX 0x1f +#define RT5670_PLL_K_MASK (RT5670_PLL_K_MAX) +#define RT5670_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x82) */ +#define RT5670_PLL_M_MAX 0xf +#define RT5670_PLL_M_MASK (RT5670_PLL_M_MAX << 12) +#define RT5670_PLL_M_SFT 12 +#define RT5670_PLL_M_BP (0x1 << 11) +#define RT5670_PLL_M_BP_SFT 11 + +/* ASRC Control 1 (0x83) */ +#define RT5670_STO_T_MASK (0x1 << 15) +#define RT5670_STO_T_SFT 15 +#define RT5670_STO_T_SCLK (0x0 << 15) +#define RT5670_STO_T_LRCK1 (0x1 << 15) +#define RT5670_M1_T_MASK (0x1 << 14) +#define RT5670_M1_T_SFT 14 +#define RT5670_M1_T_I2S2 (0x0 << 14) +#define RT5670_M1_T_I2S2_D3 (0x1 << 14) +#define RT5670_I2S2_F_MASK (0x1 << 12) +#define RT5670_I2S2_F_SFT 12 +#define RT5670_I2S2_F_I2S2_D2 (0x0 << 12) +#define RT5670_I2S2_F_I2S1_TCLK (0x1 << 12) +#define RT5670_DMIC_1_M_MASK (0x1 << 9) +#define RT5670_DMIC_1_M_SFT 9 +#define RT5670_DMIC_1_M_NOR (0x0 << 9) +#define RT5670_DMIC_1_M_ASYN (0x1 << 9) +#define RT5670_DMIC_2_M_MASK (0x1 << 8) +#define RT5670_DMIC_2_M_SFT 8 +#define RT5670_DMIC_2_M_NOR (0x0 << 8) +#define RT5670_DMIC_2_M_ASYN (0x1 << 8) + +/* ASRC clock source selection (0x84, 0x85) */ +#define RT5670_CLK_SEL_SYS (0x0) +#define RT5670_CLK_SEL_I2S1_ASRC (0x1) +#define RT5670_CLK_SEL_I2S2_ASRC (0x2) +#define RT5670_CLK_SEL_I2S3_ASRC (0x3) +#define RT5670_CLK_SEL_SYS2 (0x5) +#define RT5670_CLK_SEL_SYS3 (0x6) + +/* ASRC Control 2 (0x84) */ +#define RT5670_DA_STO_CLK_SEL_MASK (0xf << 12) +#define RT5670_DA_STO_CLK_SEL_SFT 12 +#define RT5670_DA_MONOL_CLK_SEL_MASK (0xf << 8) +#define RT5670_DA_MONOL_CLK_SEL_SFT 8 +#define RT5670_DA_MONOR_CLK_SEL_MASK (0xf << 4) +#define RT5670_DA_MONOR_CLK_SEL_SFT 4 +#define RT5670_AD_STO1_CLK_SEL_MASK (0xf << 0) +#define RT5670_AD_STO1_CLK_SEL_SFT 0 + +/* ASRC Control 3 (0x85) */ +#define RT5670_UP_CLK_SEL_MASK (0xf << 12) +#define RT5670_UP_CLK_SEL_SFT 12 +#define RT5670_DOWN_CLK_SEL_MASK (0xf << 8) +#define RT5670_DOWN_CLK_SEL_SFT 8 +#define RT5670_AD_MONOL_CLK_SEL_MASK (0xf << 4) +#define RT5670_AD_MONOL_CLK_SEL_SFT 4 +#define RT5670_AD_MONOR_CLK_SEL_MASK (0xf << 0) +#define RT5670_AD_MONOR_CLK_SEL_SFT 0 + +/* ASRC Control 4 (0x89) */ +#define RT5670_I2S1_PD_MASK (0x7 << 12) +#define RT5670_I2S1_PD_SFT 12 +#define RT5670_I2S2_PD_MASK (0x7 << 8) +#define RT5670_I2S2_PD_SFT 8 + +/* HPOUT Over Current Detection (0x8b) */ +#define RT5670_HP_OVCD_MASK (0x1 << 10) +#define RT5670_HP_OVCD_SFT 10 +#define RT5670_HP_OVCD_DIS (0x0 << 10) +#define RT5670_HP_OVCD_EN (0x1 << 10) +#define RT5670_HP_OC_TH_MASK (0x3 << 8) +#define RT5670_HP_OC_TH_SFT 8 +#define RT5670_HP_OC_TH_90 (0x0 << 8) +#define RT5670_HP_OC_TH_105 (0x1 << 8) +#define RT5670_HP_OC_TH_120 (0x2 << 8) +#define RT5670_HP_OC_TH_135 (0x3 << 8) + +/* Class D Over Current Control (0x8c) */ +#define RT5670_CLSD_OC_MASK (0x1 << 9) +#define RT5670_CLSD_OC_SFT 9 +#define RT5670_CLSD_OC_PU (0x0 << 9) +#define RT5670_CLSD_OC_PD (0x1 << 9) +#define RT5670_AUTO_PD_MASK (0x1 << 8) +#define RT5670_AUTO_PD_SFT 8 +#define RT5670_AUTO_PD_DIS (0x0 << 8) +#define RT5670_AUTO_PD_EN (0x1 << 8) +#define RT5670_CLSD_OC_TH_MASK (0x3f) +#define RT5670_CLSD_OC_TH_SFT 0 + +/* Class D Output Control (0x8d) */ +#define RT5670_CLSD_RATIO_MASK (0xf << 12) +#define RT5670_CLSD_RATIO_SFT 12 +#define RT5670_CLSD_OM_MASK (0x1 << 11) +#define RT5670_CLSD_OM_SFT 11 +#define RT5670_CLSD_OM_MONO (0x0 << 11) +#define RT5670_CLSD_OM_STO (0x1 << 11) +#define RT5670_CLSD_SCH_MASK (0x1 << 10) +#define RT5670_CLSD_SCH_SFT 10 +#define RT5670_CLSD_SCH_L (0x0 << 10) +#define RT5670_CLSD_SCH_S (0x1 << 10) + +/* Depop Mode Control 1 (0x8e) */ +#define RT5670_SMT_TRIG_MASK (0x1 << 15) +#define RT5670_SMT_TRIG_SFT 15 +#define RT5670_SMT_TRIG_DIS (0x0 << 15) +#define RT5670_SMT_TRIG_EN (0x1 << 15) +#define RT5670_HP_L_SMT_MASK (0x1 << 9) +#define RT5670_HP_L_SMT_SFT 9 +#define RT5670_HP_L_SMT_DIS (0x0 << 9) +#define RT5670_HP_L_SMT_EN (0x1 << 9) +#define RT5670_HP_R_SMT_MASK (0x1 << 8) +#define RT5670_HP_R_SMT_SFT 8 +#define RT5670_HP_R_SMT_DIS (0x0 << 8) +#define RT5670_HP_R_SMT_EN (0x1 << 8) +#define RT5670_HP_CD_PD_MASK (0x1 << 7) +#define RT5670_HP_CD_PD_SFT 7 +#define RT5670_HP_CD_PD_DIS (0x0 << 7) +#define RT5670_HP_CD_PD_EN (0x1 << 7) +#define RT5670_RSTN_MASK (0x1 << 6) +#define RT5670_RSTN_SFT 6 +#define RT5670_RSTN_DIS (0x0 << 6) +#define RT5670_RSTN_EN (0x1 << 6) +#define RT5670_RSTP_MASK (0x1 << 5) +#define RT5670_RSTP_SFT 5 +#define RT5670_RSTP_DIS (0x0 << 5) +#define RT5670_RSTP_EN (0x1 << 5) +#define RT5670_HP_CO_MASK (0x1 << 4) +#define RT5670_HP_CO_SFT 4 +#define RT5670_HP_CO_DIS (0x0 << 4) +#define RT5670_HP_CO_EN (0x1 << 4) +#define RT5670_HP_CP_MASK (0x1 << 3) +#define RT5670_HP_CP_SFT 3 +#define RT5670_HP_CP_PD (0x0 << 3) +#define RT5670_HP_CP_PU (0x1 << 3) +#define RT5670_HP_SG_MASK (0x1 << 2) +#define RT5670_HP_SG_SFT 2 +#define RT5670_HP_SG_DIS (0x0 << 2) +#define RT5670_HP_SG_EN (0x1 << 2) +#define RT5670_HP_DP_MASK (0x1 << 1) +#define RT5670_HP_DP_SFT 1 +#define RT5670_HP_DP_PD (0x0 << 1) +#define RT5670_HP_DP_PU (0x1 << 1) +#define RT5670_HP_CB_MASK (0x1) +#define RT5670_HP_CB_SFT 0 +#define RT5670_HP_CB_PD (0x0) +#define RT5670_HP_CB_PU (0x1) + +/* Depop Mode Control 2 (0x8f) */ +#define RT5670_DEPOP_MASK (0x1 << 13) +#define RT5670_DEPOP_SFT 13 +#define RT5670_DEPOP_AUTO (0x0 << 13) +#define RT5670_DEPOP_MAN (0x1 << 13) +#define RT5670_RAMP_MASK (0x1 << 12) +#define RT5670_RAMP_SFT 12 +#define RT5670_RAMP_DIS (0x0 << 12) +#define RT5670_RAMP_EN (0x1 << 12) +#define RT5670_BPS_MASK (0x1 << 11) +#define RT5670_BPS_SFT 11 +#define RT5670_BPS_DIS (0x0 << 11) +#define RT5670_BPS_EN (0x1 << 11) +#define RT5670_FAST_UPDN_MASK (0x1 << 10) +#define RT5670_FAST_UPDN_SFT 10 +#define RT5670_FAST_UPDN_DIS (0x0 << 10) +#define RT5670_FAST_UPDN_EN (0x1 << 10) +#define RT5670_MRES_MASK (0x3 << 8) +#define RT5670_MRES_SFT 8 +#define RT5670_MRES_15MO (0x0 << 8) +#define RT5670_MRES_25MO (0x1 << 8) +#define RT5670_MRES_35MO (0x2 << 8) +#define RT5670_MRES_45MO (0x3 << 8) +#define RT5670_VLO_MASK (0x1 << 7) +#define RT5670_VLO_SFT 7 +#define RT5670_VLO_3V (0x0 << 7) +#define RT5670_VLO_32V (0x1 << 7) +#define RT5670_DIG_DP_MASK (0x1 << 6) +#define RT5670_DIG_DP_SFT 6 +#define RT5670_DIG_DP_DIS (0x0 << 6) +#define RT5670_DIG_DP_EN (0x1 << 6) +#define RT5670_DP_TH_MASK (0x3 << 4) +#define RT5670_DP_TH_SFT 4 + +/* Depop Mode Control 3 (0x90) */ +#define RT5670_CP_SYS_MASK (0x7 << 12) +#define RT5670_CP_SYS_SFT 12 +#define RT5670_CP_FQ1_MASK (0x7 << 8) +#define RT5670_CP_FQ1_SFT 8 +#define RT5670_CP_FQ2_MASK (0x7 << 4) +#define RT5670_CP_FQ2_SFT 4 +#define RT5670_CP_FQ3_MASK (0x7) +#define RT5670_CP_FQ3_SFT 0 +#define RT5670_CP_FQ_1_5_KHZ 0 +#define RT5670_CP_FQ_3_KHZ 1 +#define RT5670_CP_FQ_6_KHZ 2 +#define RT5670_CP_FQ_12_KHZ 3 +#define RT5670_CP_FQ_24_KHZ 4 +#define RT5670_CP_FQ_48_KHZ 5 +#define RT5670_CP_FQ_96_KHZ 6 +#define RT5670_CP_FQ_192_KHZ 7 + +/* HPOUT charge pump (0x91) */ +#define RT5670_OSW_L_MASK (0x1 << 11) +#define RT5670_OSW_L_SFT 11 +#define RT5670_OSW_L_DIS (0x0 << 11) +#define RT5670_OSW_L_EN (0x1 << 11) +#define RT5670_OSW_R_MASK (0x1 << 10) +#define RT5670_OSW_R_SFT 10 +#define RT5670_OSW_R_DIS (0x0 << 10) +#define RT5670_OSW_R_EN (0x1 << 10) +#define RT5670_PM_HP_MASK (0x3 << 8) +#define RT5670_PM_HP_SFT 8 +#define RT5670_PM_HP_LV (0x0 << 8) +#define RT5670_PM_HP_MV (0x1 << 8) +#define RT5670_PM_HP_HV (0x2 << 8) +#define RT5670_IB_HP_MASK (0x3 << 6) +#define RT5670_IB_HP_SFT 6 +#define RT5670_IB_HP_125IL (0x0 << 6) +#define RT5670_IB_HP_25IL (0x1 << 6) +#define RT5670_IB_HP_5IL (0x2 << 6) +#define RT5670_IB_HP_1IL (0x3 << 6) + +/* PV detection and SPK gain control (0x92) */ +#define RT5670_PVDD_DET_MASK (0x1 << 15) +#define RT5670_PVDD_DET_SFT 15 +#define RT5670_PVDD_DET_DIS (0x0 << 15) +#define RT5670_PVDD_DET_EN (0x1 << 15) +#define RT5670_SPK_AG_MASK (0x1 << 14) +#define RT5670_SPK_AG_SFT 14 +#define RT5670_SPK_AG_DIS (0x0 << 14) +#define RT5670_SPK_AG_EN (0x1 << 14) + +/* Micbias Control (0x93) */ +#define RT5670_MIC1_BS_MASK (0x1 << 15) +#define RT5670_MIC1_BS_SFT 15 +#define RT5670_MIC1_BS_9AV (0x0 << 15) +#define RT5670_MIC1_BS_75AV (0x1 << 15) +#define RT5670_MIC2_BS_MASK (0x1 << 14) +#define RT5670_MIC2_BS_SFT 14 +#define RT5670_MIC2_BS_9AV (0x0 << 14) +#define RT5670_MIC2_BS_75AV (0x1 << 14) +#define RT5670_MIC1_CLK_MASK (0x1 << 13) +#define RT5670_MIC1_CLK_SFT 13 +#define RT5670_MIC1_CLK_DIS (0x0 << 13) +#define RT5670_MIC1_CLK_EN (0x1 << 13) +#define RT5670_MIC2_CLK_MASK (0x1 << 12) +#define RT5670_MIC2_CLK_SFT 12 +#define RT5670_MIC2_CLK_DIS (0x0 << 12) +#define RT5670_MIC2_CLK_EN (0x1 << 12) +#define RT5670_MIC1_OVCD_MASK (0x1 << 11) +#define RT5670_MIC1_OVCD_SFT 11 +#define RT5670_MIC1_OVCD_DIS (0x0 << 11) +#define RT5670_MIC1_OVCD_EN (0x1 << 11) +#define RT5670_MIC1_OVTH_MASK (0x3 << 9) +#define RT5670_MIC1_OVTH_SFT 9 +#define RT5670_MIC1_OVTH_600UA (0x0 << 9) +#define RT5670_MIC1_OVTH_1500UA (0x1 << 9) +#define RT5670_MIC1_OVTH_2000UA (0x2 << 9) +#define RT5670_MIC2_OVCD_MASK (0x1 << 8) +#define RT5670_MIC2_OVCD_SFT 8 +#define RT5670_MIC2_OVCD_DIS (0x0 << 8) +#define RT5670_MIC2_OVCD_EN (0x1 << 8) +#define RT5670_MIC2_OVTH_MASK (0x3 << 6) +#define RT5670_MIC2_OVTH_SFT 6 +#define RT5670_MIC2_OVTH_600UA (0x0 << 6) +#define RT5670_MIC2_OVTH_1500UA (0x1 << 6) +#define RT5670_MIC2_OVTH_2000UA (0x2 << 6) +#define RT5670_PWR_MB_MASK (0x1 << 5) +#define RT5670_PWR_MB_SFT 5 +#define RT5670_PWR_MB_PD (0x0 << 5) +#define RT5670_PWR_MB_PU (0x1 << 5) +#define RT5670_PWR_CLK25M_MASK (0x1 << 4) +#define RT5670_PWR_CLK25M_SFT 4 +#define RT5670_PWR_CLK25M_PD (0x0 << 4) +#define RT5670_PWR_CLK25M_PU (0x1 << 4) + +/* Analog JD Control 1 (0x94) */ +#define RT5670_JD1_MODE_MASK (0x3 << 0) +#define RT5670_JD1_MODE_0 (0x0 << 0) +#define RT5670_JD1_MODE_1 (0x1 << 0) +#define RT5670_JD1_MODE_2 (0x2 << 0) + +/* VAD Control 4 (0x9d) */ +#define RT5670_VAD_SEL_MASK (0x3 << 8) +#define RT5670_VAD_SEL_SFT 8 + +/* EQ Control 1 (0xb0) */ +#define RT5670_EQ_SRC_MASK (0x1 << 15) +#define RT5670_EQ_SRC_SFT 15 +#define RT5670_EQ_SRC_DAC (0x0 << 15) +#define RT5670_EQ_SRC_ADC (0x1 << 15) +#define RT5670_EQ_UPD (0x1 << 14) +#define RT5670_EQ_UPD_BIT 14 +#define RT5670_EQ_CD_MASK (0x1 << 13) +#define RT5670_EQ_CD_SFT 13 +#define RT5670_EQ_CD_DIS (0x0 << 13) +#define RT5670_EQ_CD_EN (0x1 << 13) +#define RT5670_EQ_DITH_MASK (0x3 << 8) +#define RT5670_EQ_DITH_SFT 8 +#define RT5670_EQ_DITH_NOR (0x0 << 8) +#define RT5670_EQ_DITH_LSB (0x1 << 8) +#define RT5670_EQ_DITH_LSB_1 (0x2 << 8) +#define RT5670_EQ_DITH_LSB_2 (0x3 << 8) + +/* EQ Control 2 (0xb1) */ +#define RT5670_EQ_HPF1_M_MASK (0x1 << 8) +#define RT5670_EQ_HPF1_M_SFT 8 +#define RT5670_EQ_HPF1_M_HI (0x0 << 8) +#define RT5670_EQ_HPF1_M_1ST (0x1 << 8) +#define RT5670_EQ_LPF1_M_MASK (0x1 << 7) +#define RT5670_EQ_LPF1_M_SFT 7 +#define RT5670_EQ_LPF1_M_LO (0x0 << 7) +#define RT5670_EQ_LPF1_M_1ST (0x1 << 7) +#define RT5670_EQ_HPF2_MASK (0x1 << 6) +#define RT5670_EQ_HPF2_SFT 6 +#define RT5670_EQ_HPF2_DIS (0x0 << 6) +#define RT5670_EQ_HPF2_EN (0x1 << 6) +#define RT5670_EQ_HPF1_MASK (0x1 << 5) +#define RT5670_EQ_HPF1_SFT 5 +#define RT5670_EQ_HPF1_DIS (0x0 << 5) +#define RT5670_EQ_HPF1_EN (0x1 << 5) +#define RT5670_EQ_BPF4_MASK (0x1 << 4) +#define RT5670_EQ_BPF4_SFT 4 +#define RT5670_EQ_BPF4_DIS (0x0 << 4) +#define RT5670_EQ_BPF4_EN (0x1 << 4) +#define RT5670_EQ_BPF3_MASK (0x1 << 3) +#define RT5670_EQ_BPF3_SFT 3 +#define RT5670_EQ_BPF3_DIS (0x0 << 3) +#define RT5670_EQ_BPF3_EN (0x1 << 3) +#define RT5670_EQ_BPF2_MASK (0x1 << 2) +#define RT5670_EQ_BPF2_SFT 2 +#define RT5670_EQ_BPF2_DIS (0x0 << 2) +#define RT5670_EQ_BPF2_EN (0x1 << 2) +#define RT5670_EQ_BPF1_MASK (0x1 << 1) +#define RT5670_EQ_BPF1_SFT 1 +#define RT5670_EQ_BPF1_DIS (0x0 << 1) +#define RT5670_EQ_BPF1_EN (0x1 << 1) +#define RT5670_EQ_LPF_MASK (0x1) +#define RT5670_EQ_LPF_SFT 0 +#define RT5670_EQ_LPF_DIS (0x0) +#define RT5670_EQ_LPF_EN (0x1) +#define RT5670_EQ_CTRL_MASK (0x7f) + +/* Memory Test (0xb2) */ +#define RT5670_MT_MASK (0x1 << 15) +#define RT5670_MT_SFT 15 +#define RT5670_MT_DIS (0x0 << 15) +#define RT5670_MT_EN (0x1 << 15) + +/* DRC/AGC Control 1 (0xb4) */ +#define RT5670_DRC_AGC_P_MASK (0x1 << 15) +#define RT5670_DRC_AGC_P_SFT 15 +#define RT5670_DRC_AGC_P_DAC (0x0 << 15) +#define RT5670_DRC_AGC_P_ADC (0x1 << 15) +#define RT5670_DRC_AGC_MASK (0x1 << 14) +#define RT5670_DRC_AGC_SFT 14 +#define RT5670_DRC_AGC_DIS (0x0 << 14) +#define RT5670_DRC_AGC_EN (0x1 << 14) +#define RT5670_DRC_AGC_UPD (0x1 << 13) +#define RT5670_DRC_AGC_UPD_BIT 13 +#define RT5670_DRC_AGC_AR_MASK (0x1f << 8) +#define RT5670_DRC_AGC_AR_SFT 8 +#define RT5670_DRC_AGC_R_MASK (0x7 << 5) +#define RT5670_DRC_AGC_R_SFT 5 +#define RT5670_DRC_AGC_R_48K (0x1 << 5) +#define RT5670_DRC_AGC_R_96K (0x2 << 5) +#define RT5670_DRC_AGC_R_192K (0x3 << 5) +#define RT5670_DRC_AGC_R_441K (0x5 << 5) +#define RT5670_DRC_AGC_R_882K (0x6 << 5) +#define RT5670_DRC_AGC_R_1764K (0x7 << 5) +#define RT5670_DRC_AGC_RC_MASK (0x1f) +#define RT5670_DRC_AGC_RC_SFT 0 + +/* DRC/AGC Control 2 (0xb5) */ +#define RT5670_DRC_AGC_POB_MASK (0x3f << 8) +#define RT5670_DRC_AGC_POB_SFT 8 +#define RT5670_DRC_AGC_CP_MASK (0x1 << 7) +#define RT5670_DRC_AGC_CP_SFT 7 +#define RT5670_DRC_AGC_CP_DIS (0x0 << 7) +#define RT5670_DRC_AGC_CP_EN (0x1 << 7) +#define RT5670_DRC_AGC_CPR_MASK (0x3 << 5) +#define RT5670_DRC_AGC_CPR_SFT 5 +#define RT5670_DRC_AGC_CPR_1_1 (0x0 << 5) +#define RT5670_DRC_AGC_CPR_1_2 (0x1 << 5) +#define RT5670_DRC_AGC_CPR_1_3 (0x2 << 5) +#define RT5670_DRC_AGC_CPR_1_4 (0x3 << 5) +#define RT5670_DRC_AGC_PRB_MASK (0x1f) +#define RT5670_DRC_AGC_PRB_SFT 0 + +/* DRC/AGC Control 3 (0xb6) */ +#define RT5670_DRC_AGC_NGB_MASK (0xf << 12) +#define RT5670_DRC_AGC_NGB_SFT 12 +#define RT5670_DRC_AGC_TAR_MASK (0x1f << 7) +#define RT5670_DRC_AGC_TAR_SFT 7 +#define RT5670_DRC_AGC_NG_MASK (0x1 << 6) +#define RT5670_DRC_AGC_NG_SFT 6 +#define RT5670_DRC_AGC_NG_DIS (0x0 << 6) +#define RT5670_DRC_AGC_NG_EN (0x1 << 6) +#define RT5670_DRC_AGC_NGH_MASK (0x1 << 5) +#define RT5670_DRC_AGC_NGH_SFT 5 +#define RT5670_DRC_AGC_NGH_DIS (0x0 << 5) +#define RT5670_DRC_AGC_NGH_EN (0x1 << 5) +#define RT5670_DRC_AGC_NGT_MASK (0x1f) +#define RT5670_DRC_AGC_NGT_SFT 0 + +/* Jack Detect Control (0xbb) */ +#define RT5670_JD_MASK (0x7 << 13) +#define RT5670_JD_SFT 13 +#define RT5670_JD_DIS (0x0 << 13) +#define RT5670_JD_GPIO1 (0x1 << 13) +#define RT5670_JD_JD1_IN4P (0x2 << 13) +#define RT5670_JD_JD2_IN4N (0x3 << 13) +#define RT5670_JD_GPIO2 (0x4 << 13) +#define RT5670_JD_GPIO3 (0x5 << 13) +#define RT5670_JD_GPIO4 (0x6 << 13) +#define RT5670_JD_HP_MASK (0x1 << 11) +#define RT5670_JD_HP_SFT 11 +#define RT5670_JD_HP_DIS (0x0 << 11) +#define RT5670_JD_HP_EN (0x1 << 11) +#define RT5670_JD_HP_TRG_MASK (0x1 << 10) +#define RT5670_JD_HP_TRG_SFT 10 +#define RT5670_JD_HP_TRG_LO (0x0 << 10) +#define RT5670_JD_HP_TRG_HI (0x1 << 10) +#define RT5670_JD_SPL_MASK (0x1 << 9) +#define RT5670_JD_SPL_SFT 9 +#define RT5670_JD_SPL_DIS (0x0 << 9) +#define RT5670_JD_SPL_EN (0x1 << 9) +#define RT5670_JD_SPL_TRG_MASK (0x1 << 8) +#define RT5670_JD_SPL_TRG_SFT 8 +#define RT5670_JD_SPL_TRG_LO (0x0 << 8) +#define RT5670_JD_SPL_TRG_HI (0x1 << 8) +#define RT5670_JD_SPR_MASK (0x1 << 7) +#define RT5670_JD_SPR_SFT 7 +#define RT5670_JD_SPR_DIS (0x0 << 7) +#define RT5670_JD_SPR_EN (0x1 << 7) +#define RT5670_JD_SPR_TRG_MASK (0x1 << 6) +#define RT5670_JD_SPR_TRG_SFT 6 +#define RT5670_JD_SPR_TRG_LO (0x0 << 6) +#define RT5670_JD_SPR_TRG_HI (0x1 << 6) +#define RT5670_JD_MO_MASK (0x1 << 5) +#define RT5670_JD_MO_SFT 5 +#define RT5670_JD_MO_DIS (0x0 << 5) +#define RT5670_JD_MO_EN (0x1 << 5) +#define RT5670_JD_MO_TRG_MASK (0x1 << 4) +#define RT5670_JD_MO_TRG_SFT 4 +#define RT5670_JD_MO_TRG_LO (0x0 << 4) +#define RT5670_JD_MO_TRG_HI (0x1 << 4) +#define RT5670_JD_LO_MASK (0x1 << 3) +#define RT5670_JD_LO_SFT 3 +#define RT5670_JD_LO_DIS (0x0 << 3) +#define RT5670_JD_LO_EN (0x1 << 3) +#define RT5670_JD_LO_TRG_MASK (0x1 << 2) +#define RT5670_JD_LO_TRG_SFT 2 +#define RT5670_JD_LO_TRG_LO (0x0 << 2) +#define RT5670_JD_LO_TRG_HI (0x1 << 2) +#define RT5670_JD1_IN4P_MASK (0x1 << 1) +#define RT5670_JD1_IN4P_SFT 1 +#define RT5670_JD1_IN4P_DIS (0x0 << 1) +#define RT5670_JD1_IN4P_EN (0x1 << 1) +#define RT5670_JD2_IN4N_MASK (0x1) +#define RT5670_JD2_IN4N_SFT 0 +#define RT5670_JD2_IN4N_DIS (0x0) +#define RT5670_JD2_IN4N_EN (0x1) + +/* IRQ Control 1 (0xbd) */ +#define RT5670_IRQ_JD_MASK (0x1 << 15) +#define RT5670_IRQ_JD_SFT 15 +#define RT5670_IRQ_JD_BP (0x0 << 15) +#define RT5670_IRQ_JD_NOR (0x1 << 15) +#define RT5670_IRQ_OT_MASK (0x1 << 14) +#define RT5670_IRQ_OT_SFT 14 +#define RT5670_IRQ_OT_BP (0x0 << 14) +#define RT5670_IRQ_OT_NOR (0x1 << 14) +#define RT5670_JD_STKY_MASK (0x1 << 13) +#define RT5670_JD_STKY_SFT 13 +#define RT5670_JD_STKY_DIS (0x0 << 13) +#define RT5670_JD_STKY_EN (0x1 << 13) +#define RT5670_OT_STKY_MASK (0x1 << 12) +#define RT5670_OT_STKY_SFT 12 +#define RT5670_OT_STKY_DIS (0x0 << 12) +#define RT5670_OT_STKY_EN (0x1 << 12) +#define RT5670_JD_P_MASK (0x1 << 11) +#define RT5670_JD_P_SFT 11 +#define RT5670_JD_P_NOR (0x0 << 11) +#define RT5670_JD_P_INV (0x1 << 11) +#define RT5670_OT_P_MASK (0x1 << 10) +#define RT5670_OT_P_SFT 10 +#define RT5670_OT_P_NOR (0x0 << 10) +#define RT5670_OT_P_INV (0x1 << 10) +#define RT5670_JD1_1_EN_MASK (0x1 << 9) +#define RT5670_JD1_1_EN_SFT 9 +#define RT5670_JD1_1_DIS (0x0 << 9) +#define RT5670_JD1_1_EN (0x1 << 9) + +/* IRQ Control 2 (0xbe) */ +#define RT5670_IRQ_MB1_OC_MASK (0x1 << 15) +#define RT5670_IRQ_MB1_OC_SFT 15 +#define RT5670_IRQ_MB1_OC_BP (0x0 << 15) +#define RT5670_IRQ_MB1_OC_NOR (0x1 << 15) +#define RT5670_IRQ_MB2_OC_MASK (0x1 << 14) +#define RT5670_IRQ_MB2_OC_SFT 14 +#define RT5670_IRQ_MB2_OC_BP (0x0 << 14) +#define RT5670_IRQ_MB2_OC_NOR (0x1 << 14) +#define RT5670_MB1_OC_STKY_MASK (0x1 << 11) +#define RT5670_MB1_OC_STKY_SFT 11 +#define RT5670_MB1_OC_STKY_DIS (0x0 << 11) +#define RT5670_MB1_OC_STKY_EN (0x1 << 11) +#define RT5670_MB2_OC_STKY_MASK (0x1 << 10) +#define RT5670_MB2_OC_STKY_SFT 10 +#define RT5670_MB2_OC_STKY_DIS (0x0 << 10) +#define RT5670_MB2_OC_STKY_EN (0x1 << 10) +#define RT5670_MB1_OC_P_MASK (0x1 << 7) +#define RT5670_MB1_OC_P_SFT 7 +#define RT5670_MB1_OC_P_NOR (0x0 << 7) +#define RT5670_MB1_OC_P_INV (0x1 << 7) +#define RT5670_MB2_OC_P_MASK (0x1 << 6) +#define RT5670_MB2_OC_P_SFT 6 +#define RT5670_MB2_OC_P_NOR (0x0 << 6) +#define RT5670_MB2_OC_P_INV (0x1 << 6) +#define RT5670_MB1_OC_CLR (0x1 << 3) +#define RT5670_MB1_OC_CLR_SFT 3 +#define RT5670_MB2_OC_CLR (0x1 << 2) +#define RT5670_MB2_OC_CLR_SFT 2 + +/* GPIO Control 1 (0xc0) */ +#define RT5670_GP1_PIN_MASK (0x1 << 15) +#define RT5670_GP1_PIN_SFT 15 +#define RT5670_GP1_PIN_GPIO1 (0x0 << 15) +#define RT5670_GP1_PIN_IRQ (0x1 << 15) +#define RT5670_GP2_PIN_MASK (0x1 << 14) +#define RT5670_GP2_PIN_SFT 14 +#define RT5670_GP2_PIN_GPIO2 (0x0 << 14) +#define RT5670_GP2_PIN_DMIC1_SCL (0x1 << 14) +#define RT5670_GP3_PIN_MASK (0x3 << 12) +#define RT5670_GP3_PIN_SFT 12 +#define RT5670_GP3_PIN_GPIO3 (0x0 << 12) +#define RT5670_GP3_PIN_DMIC1_SDA (0x1 << 12) +#define RT5670_GP3_PIN_IRQ (0x2 << 12) +#define RT5670_GP4_PIN_MASK (0x1 << 11) +#define RT5670_GP4_PIN_SFT 11 +#define RT5670_GP4_PIN_GPIO4 (0x0 << 11) +#define RT5670_GP4_PIN_DMIC2_SDA (0x1 << 11) +#define RT5670_DP_SIG_MASK (0x1 << 10) +#define RT5670_DP_SIG_SFT 10 +#define RT5670_DP_SIG_TEST (0x0 << 10) +#define RT5670_DP_SIG_AP (0x1 << 10) +#define RT5670_GPIO_M_MASK (0x1 << 9) +#define RT5670_GPIO_M_SFT 9 +#define RT5670_GPIO_M_FLT (0x0 << 9) +#define RT5670_GPIO_M_PH (0x1 << 9) +#define RT5670_I2S2_PIN_MASK (0x1 << 8) +#define RT5670_I2S2_PIN_SFT 8 +#define RT5670_I2S2_PIN_I2S (0x0 << 8) +#define RT5670_I2S2_PIN_GPIO (0x1 << 8) +#define RT5670_GP5_PIN_MASK (0x1 << 7) +#define RT5670_GP5_PIN_SFT 7 +#define RT5670_GP5_PIN_GPIO5 (0x0 << 7) +#define RT5670_GP5_PIN_DMIC3_SDA (0x1 << 7) +#define RT5670_GP6_PIN_MASK (0x1 << 6) +#define RT5670_GP6_PIN_SFT 6 +#define RT5670_GP6_PIN_GPIO6 (0x0 << 6) +#define RT5670_GP6_PIN_DMIC1_SDA (0x1 << 6) +#define RT5670_GP7_PIN_MASK (0x3 << 4) +#define RT5670_GP7_PIN_SFT 4 +#define RT5670_GP7_PIN_GPIO7 (0x0 << 4) +#define RT5670_GP7_PIN_DMIC1_SDA (0x1 << 4) +#define RT5670_GP7_PIN_PDM_SCL2 (0x2 << 4) +#define RT5670_GP8_PIN_MASK (0x1 << 3) +#define RT5670_GP8_PIN_SFT 3 +#define RT5670_GP8_PIN_GPIO8 (0x0 << 3) +#define RT5670_GP8_PIN_DMIC2_SDA (0x1 << 3) +#define RT5670_GP9_PIN_MASK (0x1 << 2) +#define RT5670_GP9_PIN_SFT 2 +#define RT5670_GP9_PIN_GPIO9 (0x0 << 2) +#define RT5670_GP9_PIN_DMIC3_SDA (0x1 << 2) +#define RT5670_GP10_PIN_MASK (0x3) +#define RT5670_GP10_PIN_SFT 0 +#define RT5670_GP10_PIN_GPIO9 (0x0) +#define RT5670_GP10_PIN_DMIC3_SDA (0x1) +#define RT5670_GP10_PIN_PDM_ADT2 (0x2) + +/* GPIO Control 2 (0xc1) */ +#define RT5670_GP4_PF_MASK (0x1 << 11) +#define RT5670_GP4_PF_SFT 11 +#define RT5670_GP4_PF_IN (0x0 << 11) +#define RT5670_GP4_PF_OUT (0x1 << 11) +#define RT5670_GP4_OUT_MASK (0x1 << 10) +#define RT5670_GP4_OUT_SFT 10 +#define RT5670_GP4_OUT_LO (0x0 << 10) +#define RT5670_GP4_OUT_HI (0x1 << 10) +#define RT5670_GP4_P_MASK (0x1 << 9) +#define RT5670_GP4_P_SFT 9 +#define RT5670_GP4_P_NOR (0x0 << 9) +#define RT5670_GP4_P_INV (0x1 << 9) +#define RT5670_GP3_PF_MASK (0x1 << 8) +#define RT5670_GP3_PF_SFT 8 +#define RT5670_GP3_PF_IN (0x0 << 8) +#define RT5670_GP3_PF_OUT (0x1 << 8) +#define RT5670_GP3_OUT_MASK (0x1 << 7) +#define RT5670_GP3_OUT_SFT 7 +#define RT5670_GP3_OUT_LO (0x0 << 7) +#define RT5670_GP3_OUT_HI (0x1 << 7) +#define RT5670_GP3_P_MASK (0x1 << 6) +#define RT5670_GP3_P_SFT 6 +#define RT5670_GP3_P_NOR (0x0 << 6) +#define RT5670_GP3_P_INV (0x1 << 6) +#define RT5670_GP2_PF_MASK (0x1 << 5) +#define RT5670_GP2_PF_SFT 5 +#define RT5670_GP2_PF_IN (0x0 << 5) +#define RT5670_GP2_PF_OUT (0x1 << 5) +#define RT5670_GP2_OUT_MASK (0x1 << 4) +#define RT5670_GP2_OUT_SFT 4 +#define RT5670_GP2_OUT_LO (0x0 << 4) +#define RT5670_GP2_OUT_HI (0x1 << 4) +#define RT5670_GP2_P_MASK (0x1 << 3) +#define RT5670_GP2_P_SFT 3 +#define RT5670_GP2_P_NOR (0x0 << 3) +#define RT5670_GP2_P_INV (0x1 << 3) +#define RT5670_GP1_PF_MASK (0x1 << 2) +#define RT5670_GP1_PF_SFT 2 +#define RT5670_GP1_PF_IN (0x0 << 2) +#define RT5670_GP1_PF_OUT (0x1 << 2) +#define RT5670_GP1_OUT_MASK (0x1 << 1) +#define RT5670_GP1_OUT_SFT 1 +#define RT5670_GP1_OUT_LO (0x0 << 1) +#define RT5670_GP1_OUT_HI (0x1 << 1) +#define RT5670_GP1_P_MASK (0x1) +#define RT5670_GP1_P_SFT 0 +#define RT5670_GP1_P_NOR (0x0) +#define RT5670_GP1_P_INV (0x1) + +/* Scramble Function (0xcd) */ +#define RT5670_SCB_KEY_MASK (0xff) +#define RT5670_SCB_KEY_SFT 0 + +/* Scramble Control (0xce) */ +#define RT5670_SCB_SWAP_MASK (0x1 << 15) +#define RT5670_SCB_SWAP_SFT 15 +#define RT5670_SCB_SWAP_DIS (0x0 << 15) +#define RT5670_SCB_SWAP_EN (0x1 << 15) +#define RT5670_SCB_MASK (0x1 << 14) +#define RT5670_SCB_SFT 14 +#define RT5670_SCB_DIS (0x0 << 14) +#define RT5670_SCB_EN (0x1 << 14) + +/* Baseback Control (0xcf) */ +#define RT5670_BB_MASK (0x1 << 15) +#define RT5670_BB_SFT 15 +#define RT5670_BB_DIS (0x0 << 15) +#define RT5670_BB_EN (0x1 << 15) +#define RT5670_BB_CT_MASK (0x7 << 12) +#define RT5670_BB_CT_SFT 12 +#define RT5670_BB_CT_A (0x0 << 12) +#define RT5670_BB_CT_B (0x1 << 12) +#define RT5670_BB_CT_C (0x2 << 12) +#define RT5670_BB_CT_D (0x3 << 12) +#define RT5670_M_BB_L_MASK (0x1 << 9) +#define RT5670_M_BB_L_SFT 9 +#define RT5670_M_BB_R_MASK (0x1 << 8) +#define RT5670_M_BB_R_SFT 8 +#define RT5670_M_BB_HPF_L_MASK (0x1 << 7) +#define RT5670_M_BB_HPF_L_SFT 7 +#define RT5670_M_BB_HPF_R_MASK (0x1 << 6) +#define RT5670_M_BB_HPF_R_SFT 6 +#define RT5670_G_BB_BST_MASK (0x3f) +#define RT5670_G_BB_BST_SFT 0 + +/* MP3 Plus Control 1 (0xd0) */ +#define RT5670_M_MP3_L_MASK (0x1 << 15) +#define RT5670_M_MP3_L_SFT 15 +#define RT5670_M_MP3_R_MASK (0x1 << 14) +#define RT5670_M_MP3_R_SFT 14 +#define RT5670_M_MP3_MASK (0x1 << 13) +#define RT5670_M_MP3_SFT 13 +#define RT5670_M_MP3_DIS (0x0 << 13) +#define RT5670_M_MP3_EN (0x1 << 13) +#define RT5670_EG_MP3_MASK (0x1f << 8) +#define RT5670_EG_MP3_SFT 8 +#define RT5670_MP3_HLP_MASK (0x1 << 7) +#define RT5670_MP3_HLP_SFT 7 +#define RT5670_MP3_HLP_DIS (0x0 << 7) +#define RT5670_MP3_HLP_EN (0x1 << 7) +#define RT5670_M_MP3_ORG_L_MASK (0x1 << 6) +#define RT5670_M_MP3_ORG_L_SFT 6 +#define RT5670_M_MP3_ORG_R_MASK (0x1 << 5) +#define RT5670_M_MP3_ORG_R_SFT 5 + +/* MP3 Plus Control 2 (0xd1) */ +#define RT5670_MP3_WT_MASK (0x1 << 13) +#define RT5670_MP3_WT_SFT 13 +#define RT5670_MP3_WT_1_4 (0x0 << 13) +#define RT5670_MP3_WT_1_2 (0x1 << 13) +#define RT5670_OG_MP3_MASK (0x1f << 8) +#define RT5670_OG_MP3_SFT 8 +#define RT5670_HG_MP3_MASK (0x3f) +#define RT5670_HG_MP3_SFT 0 + +/* 3D HP Control 1 (0xd2) */ +#define RT5670_3D_CF_MASK (0x1 << 15) +#define RT5670_3D_CF_SFT 15 +#define RT5670_3D_CF_DIS (0x0 << 15) +#define RT5670_3D_CF_EN (0x1 << 15) +#define RT5670_3D_HP_MASK (0x1 << 14) +#define RT5670_3D_HP_SFT 14 +#define RT5670_3D_HP_DIS (0x0 << 14) +#define RT5670_3D_HP_EN (0x1 << 14) +#define RT5670_3D_BT_MASK (0x1 << 13) +#define RT5670_3D_BT_SFT 13 +#define RT5670_3D_BT_DIS (0x0 << 13) +#define RT5670_3D_BT_EN (0x1 << 13) +#define RT5670_3D_1F_MIX_MASK (0x3 << 11) +#define RT5670_3D_1F_MIX_SFT 11 +#define RT5670_3D_HP_M_MASK (0x1 << 10) +#define RT5670_3D_HP_M_SFT 10 +#define RT5670_3D_HP_M_SUR (0x0 << 10) +#define RT5670_3D_HP_M_FRO (0x1 << 10) +#define RT5670_M_3D_HRTF_MASK (0x1 << 9) +#define RT5670_M_3D_HRTF_SFT 9 +#define RT5670_M_3D_D2H_MASK (0x1 << 8) +#define RT5670_M_3D_D2H_SFT 8 +#define RT5670_M_3D_D2R_MASK (0x1 << 7) +#define RT5670_M_3D_D2R_SFT 7 +#define RT5670_M_3D_REVB_MASK (0x1 << 6) +#define RT5670_M_3D_REVB_SFT 6 + +/* Adjustable high pass filter control 1 (0xd3) */ +#define RT5670_2ND_HPF_MASK (0x1 << 15) +#define RT5670_2ND_HPF_SFT 15 +#define RT5670_2ND_HPF_DIS (0x0 << 15) +#define RT5670_2ND_HPF_EN (0x1 << 15) +#define RT5670_HPF_CF_L_MASK (0x7 << 12) +#define RT5670_HPF_CF_L_SFT 12 +#define RT5670_1ST_HPF_MASK (0x1 << 11) +#define RT5670_1ST_HPF_SFT 11 +#define RT5670_1ST_HPF_DIS (0x0 << 11) +#define RT5670_1ST_HPF_EN (0x1 << 11) +#define RT5670_HPF_CF_R_MASK (0x7 << 8) +#define RT5670_HPF_CF_R_SFT 8 +#define RT5670_ZD_T_MASK (0x3 << 6) +#define RT5670_ZD_T_SFT 6 +#define RT5670_ZD_F_MASK (0x3 << 4) +#define RT5670_ZD_F_SFT 4 +#define RT5670_ZD_F_IM (0x0 << 4) +#define RT5670_ZD_F_ZC_IM (0x1 << 4) +#define RT5670_ZD_F_ZC_IOD (0x2 << 4) +#define RT5670_ZD_F_UN (0x3 << 4) + +/* HP calibration control and Amp detection (0xd6) */ +#define RT5670_SI_DAC_MASK (0x1 << 11) +#define RT5670_SI_DAC_SFT 11 +#define RT5670_SI_DAC_AUTO (0x0 << 11) +#define RT5670_SI_DAC_TEST (0x1 << 11) +#define RT5670_DC_CAL_M_MASK (0x1 << 10) +#define RT5670_DC_CAL_M_SFT 10 +#define RT5670_DC_CAL_M_CAL (0x0 << 10) +#define RT5670_DC_CAL_M_NOR (0x1 << 10) +#define RT5670_DC_CAL_MASK (0x1 << 9) +#define RT5670_DC_CAL_SFT 9 +#define RT5670_DC_CAL_DIS (0x0 << 9) +#define RT5670_DC_CAL_EN (0x1 << 9) +#define RT5670_HPD_RCV_MASK (0x7 << 6) +#define RT5670_HPD_RCV_SFT 6 +#define RT5670_HPD_PS_MASK (0x1 << 5) +#define RT5670_HPD_PS_SFT 5 +#define RT5670_HPD_PS_DIS (0x0 << 5) +#define RT5670_HPD_PS_EN (0x1 << 5) +#define RT5670_CAL_M_MASK (0x1 << 4) +#define RT5670_CAL_M_SFT 4 +#define RT5670_CAL_M_DEP (0x0 << 4) +#define RT5670_CAL_M_CAL (0x1 << 4) +#define RT5670_CAL_MASK (0x1 << 3) +#define RT5670_CAL_SFT 3 +#define RT5670_CAL_DIS (0x0 << 3) +#define RT5670_CAL_EN (0x1 << 3) +#define RT5670_CAL_TEST_MASK (0x1 << 2) +#define RT5670_CAL_TEST_SFT 2 +#define RT5670_CAL_TEST_DIS (0x0 << 2) +#define RT5670_CAL_TEST_EN (0x1 << 2) +#define RT5670_CAL_P_MASK (0x3) +#define RT5670_CAL_P_SFT 0 +#define RT5670_CAL_P_NONE (0x0) +#define RT5670_CAL_P_CAL (0x1) +#define RT5670_CAL_P_DAC_CAL (0x2) + +/* Soft volume and zero cross control 1 (0xd9) */ +#define RT5670_SV_MASK (0x1 << 15) +#define RT5670_SV_SFT 15 +#define RT5670_SV_DIS (0x0 << 15) +#define RT5670_SV_EN (0x1 << 15) +#define RT5670_SPO_SV_MASK (0x1 << 14) +#define RT5670_SPO_SV_SFT 14 +#define RT5670_SPO_SV_DIS (0x0 << 14) +#define RT5670_SPO_SV_EN (0x1 << 14) +#define RT5670_OUT_SV_MASK (0x1 << 13) +#define RT5670_OUT_SV_SFT 13 +#define RT5670_OUT_SV_DIS (0x0 << 13) +#define RT5670_OUT_SV_EN (0x1 << 13) +#define RT5670_HP_SV_MASK (0x1 << 12) +#define RT5670_HP_SV_SFT 12 +#define RT5670_HP_SV_DIS (0x0 << 12) +#define RT5670_HP_SV_EN (0x1 << 12) +#define RT5670_ZCD_DIG_MASK (0x1 << 11) +#define RT5670_ZCD_DIG_SFT 11 +#define RT5670_ZCD_DIG_DIS (0x0 << 11) +#define RT5670_ZCD_DIG_EN (0x1 << 11) +#define RT5670_ZCD_MASK (0x1 << 10) +#define RT5670_ZCD_SFT 10 +#define RT5670_ZCD_PD (0x0 << 10) +#define RT5670_ZCD_PU (0x1 << 10) +#define RT5670_M_ZCD_MASK (0x3f << 4) +#define RT5670_M_ZCD_SFT 4 +#define RT5670_M_ZCD_RM_L (0x1 << 9) +#define RT5670_M_ZCD_RM_R (0x1 << 8) +#define RT5670_M_ZCD_SM_L (0x1 << 7) +#define RT5670_M_ZCD_SM_R (0x1 << 6) +#define RT5670_M_ZCD_OM_L (0x1 << 5) +#define RT5670_M_ZCD_OM_R (0x1 << 4) +#define RT5670_SV_DLY_MASK (0xf) +#define RT5670_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0xda) */ +#define RT5670_ZCD_HP_MASK (0x1 << 15) +#define RT5670_ZCD_HP_SFT 15 +#define RT5670_ZCD_HP_DIS (0x0 << 15) +#define RT5670_ZCD_HP_EN (0x1 << 15) + +/* General Control 3 (0xfc) */ +#define RT5670_TDM_DATA_MODE_SEL (0x1 << 11) +#define RT5670_TDM_DATA_MODE_NOR (0x0 << 11) +#define RT5670_TDM_DATA_MODE_50FS (0x1 << 11) + +/* Codec Private Register definition */ +/* 3D Speaker Control (0x63) */ +#define RT5670_3D_SPK_MASK (0x1 << 15) +#define RT5670_3D_SPK_SFT 15 +#define RT5670_3D_SPK_DIS (0x0 << 15) +#define RT5670_3D_SPK_EN (0x1 << 15) +#define RT5670_3D_SPK_M_MASK (0x3 << 13) +#define RT5670_3D_SPK_M_SFT 13 +#define RT5670_3D_SPK_CG_MASK (0x1f << 8) +#define RT5670_3D_SPK_CG_SFT 8 +#define RT5670_3D_SPK_SG_MASK (0x1f) +#define RT5670_3D_SPK_SG_SFT 0 + +/* Wind Noise Detection Control 1 (0x6c) */ +#define RT5670_WND_MASK (0x1 << 15) +#define RT5670_WND_SFT 15 +#define RT5670_WND_DIS (0x0 << 15) +#define RT5670_WND_EN (0x1 << 15) + +/* Wind Noise Detection Control 2 (0x6d) */ +#define RT5670_WND_FC_NW_MASK (0x3f << 10) +#define RT5670_WND_FC_NW_SFT 10 +#define RT5670_WND_FC_WK_MASK (0x3f << 4) +#define RT5670_WND_FC_WK_SFT 4 + +/* Wind Noise Detection Control 3 (0x6e) */ +#define RT5670_HPF_FC_MASK (0x3f << 6) +#define RT5670_HPF_FC_SFT 6 +#define RT5670_WND_FC_ST_MASK (0x3f) +#define RT5670_WND_FC_ST_SFT 0 + +/* Wind Noise Detection Control 4 (0x6f) */ +#define RT5670_WND_TH_LO_MASK (0x3ff) +#define RT5670_WND_TH_LO_SFT 0 + +/* Wind Noise Detection Control 5 (0x70) */ +#define RT5670_WND_TH_HI_MASK (0x3ff) +#define RT5670_WND_TH_HI_SFT 0 + +/* Wind Noise Detection Control 8 (0x73) */ +#define RT5670_WND_WIND_MASK (0x1 << 13) /* Read-Only */ +#define RT5670_WND_WIND_SFT 13 +#define RT5670_WND_STRONG_MASK (0x1 << 12) /* Read-Only */ +#define RT5670_WND_STRONG_SFT 12 +enum { + RT5670_NO_WIND, + RT5670_BREEZE, + RT5670_STORM, +}; + +/* Dipole Speaker Interface (0x75) */ +#define RT5670_DP_ATT_MASK (0x3 << 14) +#define RT5670_DP_ATT_SFT 14 +#define RT5670_DP_SPK_MASK (0x1 << 10) +#define RT5670_DP_SPK_SFT 10 +#define RT5670_DP_SPK_DIS (0x0 << 10) +#define RT5670_DP_SPK_EN (0x1 << 10) + +/* EQ Pre Volume Control (0xb3) */ +#define RT5670_EQ_PRE_VOL_MASK (0xffff) +#define RT5670_EQ_PRE_VOL_SFT 0 + +/* EQ Post Volume Control (0xb4) */ +#define RT5670_EQ_PST_VOL_MASK (0xffff) +#define RT5670_EQ_PST_VOL_SFT 0 + +/* Jack Detect Control 3 (0xf8) */ +#define RT5670_CMP_MIC_IN_DET_MASK (0x7 << 12) +#define RT5670_JD_CBJ_EN (0x1 << 7) +#define RT5670_JD_CBJ_POL (0x1 << 6) +#define RT5670_JD_TRI_CBJ_SEL_MASK (0x7 << 3) +#define RT5670_JD_TRI_CBJ_SEL_SFT (3) +#define RT5670_JD_CBJ_GPIO_JD1 (0x0 << 3) +#define RT5670_JD_CBJ_JD1_1 (0x1 << 3) +#define RT5670_JD_CBJ_JD1_2 (0x2 << 3) +#define RT5670_JD_CBJ_JD2 (0x3 << 3) +#define RT5670_JD_CBJ_JD3 (0x4 << 3) +#define RT5670_JD_CBJ_GPIO_JD2 (0x5 << 3) +#define RT5670_JD_CBJ_MX0B_12 (0x6 << 3) +#define RT5670_JD_TRI_HPO_SEL_MASK (0x7 << 3) +#define RT5670_JD_TRI_HPO_SEL_SFT (0) +#define RT5670_JD_HPO_GPIO_JD1 (0x0) +#define RT5670_JD_HPO_JD1_1 (0x1) +#define RT5670_JD_HPO_JD1_2 (0x2) +#define RT5670_JD_HPO_JD2 (0x3) +#define RT5670_JD_HPO_JD3 (0x4) +#define RT5670_JD_HPO_GPIO_JD2 (0x5) +#define RT5670_JD_HPO_MX0B_12 (0x6) + +/* Digital Misc Control (0xfa) */ +#define RT5670_RST_DSP (0x1 << 13) +#define RT5670_IF1_ADC1_IN1_SEL (0x1 << 12) +#define RT5670_IF1_ADC1_IN1_SFT 12 +#define RT5670_IF1_ADC1_IN2_SEL (0x1 << 11) +#define RT5670_IF1_ADC1_IN2_SFT 11 +#define RT5670_IF1_ADC2_IN1_SEL (0x1 << 10) +#define RT5670_IF1_ADC2_IN1_SFT 10 +#define RT5670_MCLK_DET (0x1 << 3) + +/* General Control2 (0xfb) */ +#define RT5670_RXDC_SRC_MASK (0x1 << 7) +#define RT5670_RXDC_SRC_STO (0x0 << 7) +#define RT5670_RXDC_SRC_MONO (0x1 << 7) +#define RT5670_RXDC_SRC_SFT (7) +#define RT5670_RXDP2_SEL_MASK (0x1 << 3) +#define RT5670_RXDP2_SEL_IF2 (0x0 << 3) +#define RT5670_RXDP2_SEL_ADC (0x1 << 3) +#define RT5670_RXDP2_SEL_SFT (3) + +/* System Clock Source */ +enum { + RT5670_SCLK_S_MCLK, + RT5670_SCLK_S_PLL1, + RT5670_SCLK_S_RCCLK, +}; + +/* PLL1 Source */ +enum { + RT5670_PLL1_S_MCLK, + RT5670_PLL1_S_BCLK1, + RT5670_PLL1_S_BCLK2, + RT5670_PLL1_S_BCLK3, + RT5670_PLL1_S_BCLK4, +}; + +enum { + RT5670_AIF1, + RT5670_AIF2, + RT5670_AIF3, + RT5670_AIF4, + RT5670_AIFS, +}; + +enum { + RT5670_DMIC1_DISABLED, + RT5670_DMIC_DATA_GPIO6, + RT5670_DMIC_DATA_IN2P, + RT5670_DMIC_DATA_GPIO7, +}; + +enum { + RT5670_DMIC2_DISABLED, + RT5670_DMIC_DATA_GPIO8, + RT5670_DMIC_DATA_IN3N, +}; + +enum { + RT5670_DMIC3_DISABLED, + RT5670_DMIC_DATA_GPIO9, + RT5670_DMIC_DATA_GPIO10, + RT5670_DMIC_DATA_GPIO5, +}; + +/* filter mask */ +enum { + RT5670_DA_STEREO_FILTER = 0x1, + RT5670_DA_MONO_L_FILTER = (0x1 << 1), + RT5670_DA_MONO_R_FILTER = (0x1 << 2), + RT5670_AD_STEREO_FILTER = (0x1 << 3), + RT5670_AD_MONO_L_FILTER = (0x1 << 4), + RT5670_AD_MONO_R_FILTER = (0x1 << 5), + RT5670_UP_RATE_FILTER = (0x1 << 6), + RT5670_DOWN_RATE_FILTER = (0x1 << 7), +}; + +int rt5670_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +struct rt5670_priv { + struct snd_soc_component *component; + struct rt5670_platform_data pdata; + struct regmap *regmap; + struct snd_soc_jack *jack; + struct snd_soc_jack_gpio hp_gpio; + + int sysclk; + int sysclk_src; + int lrck[RT5670_AIFS]; + int bclk[RT5670_AIFS]; + int master[RT5670_AIFS]; + + int pll_src; + int pll_in; + int pll_out; + + int dsp_sw; /* expected parameter setting */ + int dsp_rate; + int jack_type; + int jack_type_saved; +}; + +void rt5670_jack_suspend(struct snd_soc_component *component); +void rt5670_jack_resume(struct snd_soc_component *component); +int rt5670_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack); +#endif /* __RT5670_H__ */ diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c new file mode 100644 index 000000000..06abcd017 --- /dev/null +++ b/sound/soc/codecs/rt5677-spi.c @@ -0,0 +1,237 @@ +/* + * rt5677-spi.c -- RT5677 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt5677-spi.h" + +#define RT5677_SPI_BURST_LEN 240 +#define RT5677_SPI_HEADER 5 +#define RT5677_SPI_FREQ 6000000 + +/* The AddressPhase and DataPhase of SPI commands are MSB first on the wire. + * DataPhase word size of 16-bit commands is 2 bytes. + * DataPhase word size of 32-bit commands is 4 bytes. + * DataPhase word size of burst commands is 8 bytes. + * The DSP CPU is little-endian. + */ +#define RT5677_SPI_WRITE_BURST 0x5 +#define RT5677_SPI_READ_BURST 0x4 +#define RT5677_SPI_WRITE_32 0x3 +#define RT5677_SPI_READ_32 0x2 +#define RT5677_SPI_WRITE_16 0x1 +#define RT5677_SPI_READ_16 0x0 + +static struct spi_device *g_spi; +static DEFINE_MUTEX(spi_mutex); + +/* Select a suitable transfer command for the next transfer to ensure + * the transfer address is always naturally aligned while minimizing + * the total number of transfers required. + * + * 3 transfer commands are available: + * RT5677_SPI_READ/WRITE_16: Transfer 2 bytes + * RT5677_SPI_READ/WRITE_32: Transfer 4 bytes + * RT5677_SPI_READ/WRITE_BURST: Transfer any multiples of 8 bytes + * + * Note: + * 16 Bit writes and reads are restricted to the address range + * 0x18020000 ~ 0x18021000 + * + * For example, reading 256 bytes at 0x60030004 uses the following commands: + * 0x60030004 RT5677_SPI_READ_32 4 bytes + * 0x60030008 RT5677_SPI_READ_BURST 240 bytes + * 0x600300F8 RT5677_SPI_READ_BURST 8 bytes + * 0x60030100 RT5677_SPI_READ_32 4 bytes + * + * Input: + * @read: true for read commands; false for write commands + * @align: alignment of the next transfer address + * @remain: number of bytes remaining to transfer + * + * Output: + * @len: number of bytes to transfer with the selected command + * Returns the selected command + */ +static u8 rt5677_spi_select_cmd(bool read, u32 align, u32 remain, u32 *len) +{ + u8 cmd; + + if (align == 4 || remain <= 4) { + cmd = RT5677_SPI_READ_32; + *len = 4; + } else { + cmd = RT5677_SPI_READ_BURST; + *len = (((remain - 1) >> 3) + 1) << 3; + *len = min_t(u32, *len, RT5677_SPI_BURST_LEN); + } + return read ? cmd : cmd + 1; +} + +/* Copy dstlen bytes from src to dst, while reversing byte order for each word. + * If srclen < dstlen, zeros are padded. + */ +static void rt5677_spi_reverse(u8 *dst, u32 dstlen, const u8 *src, u32 srclen) +{ + u32 w, i, si; + u32 word_size = min_t(u32, dstlen, 8); + + for (w = 0; w < dstlen; w += word_size) { + for (i = 0; i < word_size; i++) { + si = w + word_size - i - 1; + dst[w + i] = si < srclen ? src[si] : 0; + } + } +} + +/* Read DSP address space using SPI. addr and len have to be 4-byte aligned. */ +int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) +{ + u32 offset; + int status = 0; + struct spi_transfer t[2]; + struct spi_message m; + /* +4 bytes is for the DummyPhase following the AddressPhase */ + u8 header[RT5677_SPI_HEADER + 4]; + u8 body[RT5677_SPI_BURST_LEN]; + u8 spi_cmd; + u8 *cb = rxbuf; + + if (!g_spi) + return -ENODEV; + + if ((addr & 3) || (len & 3)) { + dev_err(&g_spi->dev, "Bad read align 0x%x(%zu)\n", addr, len); + return -EACCES; + } + + memset(t, 0, sizeof(t)); + t[0].tx_buf = header; + t[0].len = sizeof(header); + t[0].speed_hz = RT5677_SPI_FREQ; + t[1].rx_buf = body; + t[1].speed_hz = RT5677_SPI_FREQ; + spi_message_init_with_transfers(&m, t, ARRAY_SIZE(t)); + + for (offset = 0; offset < len; offset += t[1].len) { + spi_cmd = rt5677_spi_select_cmd(true, (addr + offset) & 7, + len - offset, &t[1].len); + + /* Construct SPI message header */ + header[0] = spi_cmd; + header[1] = ((addr + offset) & 0xff000000) >> 24; + header[2] = ((addr + offset) & 0x00ff0000) >> 16; + header[3] = ((addr + offset) & 0x0000ff00) >> 8; + header[4] = ((addr + offset) & 0x000000ff) >> 0; + + mutex_lock(&spi_mutex); + status |= spi_sync(g_spi, &m); + mutex_unlock(&spi_mutex); + + /* Copy data back to caller buffer */ + rt5677_spi_reverse(cb + offset, t[1].len, body, t[1].len); + } + return status; +} +EXPORT_SYMBOL_GPL(rt5677_spi_read); + +/* Write DSP address space using SPI. addr has to be 4-byte aligned. + * If len is not 4-byte aligned, then extra zeros are written at the end + * as padding. + */ +int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) +{ + u32 offset; + int status = 0; + struct spi_transfer t; + struct spi_message m; + /* +1 byte is for the DummyPhase following the DataPhase */ + u8 buf[RT5677_SPI_HEADER + RT5677_SPI_BURST_LEN + 1]; + u8 *body = buf + RT5677_SPI_HEADER; + u8 spi_cmd; + const u8 *cb = txbuf; + + if (!g_spi) + return -ENODEV; + + if (addr & 3) { + dev_err(&g_spi->dev, "Bad write align 0x%x(%zu)\n", addr, len); + return -EACCES; + } + + memset(&t, 0, sizeof(t)); + t.tx_buf = buf; + t.speed_hz = RT5677_SPI_FREQ; + spi_message_init_with_transfers(&m, &t, 1); + + for (offset = 0; offset < len;) { + spi_cmd = rt5677_spi_select_cmd(false, (addr + offset) & 7, + len - offset, &t.len); + + /* Construct SPI message header */ + buf[0] = spi_cmd; + buf[1] = ((addr + offset) & 0xff000000) >> 24; + buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + /* Fetch data from caller buffer */ + rt5677_spi_reverse(body, t.len, cb + offset, len - offset); + offset += t.len; + t.len += RT5677_SPI_HEADER + 1; + + mutex_lock(&spi_mutex); + status |= spi_sync(g_spi, &m); + mutex_unlock(&spi_mutex); + } + return status; +} +EXPORT_SYMBOL_GPL(rt5677_spi_write); + +int rt5677_spi_write_firmware(u32 addr, const struct firmware *fw) +{ + return rt5677_spi_write(addr, fw->data, fw->size); +} +EXPORT_SYMBOL_GPL(rt5677_spi_write_firmware); + +static int rt5677_spi_probe(struct spi_device *spi) +{ + g_spi = spi; + return 0; +} + +static struct spi_driver rt5677_spi_driver = { + .driver = { + .name = "rt5677", + }, + .probe = rt5677_spi_probe, +}; +module_spi_driver(rt5677_spi_driver); + +MODULE_DESCRIPTION("ASoC RT5677 SPI driver"); +MODULE_AUTHOR("Oder Chiou "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5677-spi.h b/sound/soc/codecs/rt5677-spi.h new file mode 100644 index 000000000..662db16cf --- /dev/null +++ b/sound/soc/codecs/rt5677-spi.h @@ -0,0 +1,19 @@ +/* + * rt5677-spi.h -- RT5677 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5677_SPI_H__ +#define __RT5677_SPI_H__ + +int rt5677_spi_read(u32 addr, void *rxbuf, size_t len); +int rt5677_spi_write(u32 addr, const void *txbuf, size_t len); +int rt5677_spi_write_firmware(u32 addr, const struct firmware *fw); + +#endif /* __RT5677_SPI_H__ */ diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c new file mode 100644 index 000000000..71b7b881d --- /dev/null +++ b/sound/soc/codecs/rt5677.c @@ -0,0 +1,5282 @@ +/* + * rt5677.c -- RT5677 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5677.h" +#include "rt5677-spi.h" + +#define RT5677_DEVICE_ID 0x6327 + +#define RT5677_PR_RANGE_BASE (0xff + 1) +#define RT5677_PR_SPACING 0x100 + +#define RT5677_PR_BASE (RT5677_PR_RANGE_BASE + (0 * RT5677_PR_SPACING)) + +static const struct regmap_range_cfg rt5677_ranges[] = { + { + .name = "PR", + .range_min = RT5677_PR_BASE, + .range_max = RT5677_PR_BASE + 0xfd, + .selector_reg = RT5677_PRIV_INDEX, + .selector_mask = 0xff, + .selector_shift = 0x0, + .window_start = RT5677_PRIV_DATA, + .window_len = 0x1, + }, +}; + +static const struct reg_sequence init_list[] = { + {RT5677_ASRC_12, 0x0018}, + {RT5677_PR_BASE + 0x3d, 0x364d}, + {RT5677_PR_BASE + 0x17, 0x4fc0}, + {RT5677_PR_BASE + 0x13, 0x0312}, + {RT5677_PR_BASE + 0x1e, 0x0000}, + {RT5677_PR_BASE + 0x12, 0x0eaa}, + {RT5677_PR_BASE + 0x14, 0x018a}, + {RT5677_PR_BASE + 0x15, 0x0490}, + {RT5677_PR_BASE + 0x38, 0x0f71}, + {RT5677_PR_BASE + 0x39, 0x0f71}, +}; +#define RT5677_INIT_REG_LEN ARRAY_SIZE(init_list) + +static const struct reg_default rt5677_reg[] = { + {RT5677_RESET , 0x0000}, + {RT5677_LOUT1 , 0xa800}, + {RT5677_IN1 , 0x0000}, + {RT5677_MICBIAS , 0x0000}, + {RT5677_SLIMBUS_PARAM , 0x0000}, + {RT5677_SLIMBUS_RX , 0x0000}, + {RT5677_SLIMBUS_CTRL , 0x0000}, + {RT5677_SIDETONE_CTRL , 0x000b}, + {RT5677_ANA_DAC1_2_3_SRC , 0x0000}, + {RT5677_IF_DSP_DAC3_4_MIXER , 0x1111}, + {RT5677_DAC4_DIG_VOL , 0xafaf}, + {RT5677_DAC3_DIG_VOL , 0xafaf}, + {RT5677_DAC1_DIG_VOL , 0xafaf}, + {RT5677_DAC2_DIG_VOL , 0xafaf}, + {RT5677_IF_DSP_DAC2_MIXER , 0x0011}, + {RT5677_STO1_ADC_DIG_VOL , 0x2f2f}, + {RT5677_MONO_ADC_DIG_VOL , 0x2f2f}, + {RT5677_STO1_2_ADC_BST , 0x0000}, + {RT5677_STO2_ADC_DIG_VOL , 0x2f2f}, + {RT5677_ADC_BST_CTRL2 , 0x0000}, + {RT5677_STO3_4_ADC_BST , 0x0000}, + {RT5677_STO3_ADC_DIG_VOL , 0x2f2f}, + {RT5677_STO4_ADC_DIG_VOL , 0x2f2f}, + {RT5677_STO4_ADC_MIXER , 0xd4c0}, + {RT5677_STO3_ADC_MIXER , 0xd4c0}, + {RT5677_STO2_ADC_MIXER , 0xd4c0}, + {RT5677_STO1_ADC_MIXER , 0xd4c0}, + {RT5677_MONO_ADC_MIXER , 0xd4d1}, + {RT5677_ADC_IF_DSP_DAC1_MIXER , 0x8080}, + {RT5677_STO1_DAC_MIXER , 0xaaaa}, + {RT5677_MONO_DAC_MIXER , 0xaaaa}, + {RT5677_DD1_MIXER , 0xaaaa}, + {RT5677_DD2_MIXER , 0xaaaa}, + {RT5677_IF3_DATA , 0x0000}, + {RT5677_IF4_DATA , 0x0000}, + {RT5677_PDM_OUT_CTRL , 0x8888}, + {RT5677_PDM_DATA_CTRL1 , 0x0000}, + {RT5677_PDM_DATA_CTRL2 , 0x0000}, + {RT5677_PDM1_DATA_CTRL2 , 0x0000}, + {RT5677_PDM1_DATA_CTRL3 , 0x0000}, + {RT5677_PDM1_DATA_CTRL4 , 0x0000}, + {RT5677_PDM2_DATA_CTRL2 , 0x0000}, + {RT5677_PDM2_DATA_CTRL3 , 0x0000}, + {RT5677_PDM2_DATA_CTRL4 , 0x0000}, + {RT5677_TDM1_CTRL1 , 0x0300}, + {RT5677_TDM1_CTRL2 , 0x0000}, + {RT5677_TDM1_CTRL3 , 0x4000}, + {RT5677_TDM1_CTRL4 , 0x0123}, + {RT5677_TDM1_CTRL5 , 0x4567}, + {RT5677_TDM2_CTRL1 , 0x0300}, + {RT5677_TDM2_CTRL2 , 0x0000}, + {RT5677_TDM2_CTRL3 , 0x4000}, + {RT5677_TDM2_CTRL4 , 0x0123}, + {RT5677_TDM2_CTRL5 , 0x4567}, + {RT5677_I2C_MASTER_CTRL1 , 0x0001}, + {RT5677_I2C_MASTER_CTRL2 , 0x0000}, + {RT5677_I2C_MASTER_CTRL3 , 0x0000}, + {RT5677_I2C_MASTER_CTRL4 , 0x0000}, + {RT5677_I2C_MASTER_CTRL5 , 0x0000}, + {RT5677_I2C_MASTER_CTRL6 , 0x0000}, + {RT5677_I2C_MASTER_CTRL7 , 0x0000}, + {RT5677_I2C_MASTER_CTRL8 , 0x0000}, + {RT5677_DMIC_CTRL1 , 0x1505}, + {RT5677_DMIC_CTRL2 , 0x0055}, + {RT5677_HAP_GENE_CTRL1 , 0x0111}, + {RT5677_HAP_GENE_CTRL2 , 0x0064}, + {RT5677_HAP_GENE_CTRL3 , 0xef0e}, + {RT5677_HAP_GENE_CTRL4 , 0xf0f0}, + {RT5677_HAP_GENE_CTRL5 , 0xef0e}, + {RT5677_HAP_GENE_CTRL6 , 0xf0f0}, + {RT5677_HAP_GENE_CTRL7 , 0xef0e}, + {RT5677_HAP_GENE_CTRL8 , 0xf0f0}, + {RT5677_HAP_GENE_CTRL9 , 0xf000}, + {RT5677_HAP_GENE_CTRL10 , 0x0000}, + {RT5677_PWR_DIG1 , 0x0000}, + {RT5677_PWR_DIG2 , 0x0000}, + {RT5677_PWR_ANLG1 , 0x0055}, + {RT5677_PWR_ANLG2 , 0x0000}, + {RT5677_PWR_DSP1 , 0x0001}, + {RT5677_PWR_DSP_ST , 0x0000}, + {RT5677_PWR_DSP2 , 0x0000}, + {RT5677_ADC_DAC_HPF_CTRL1 , 0x0e00}, + {RT5677_PRIV_INDEX , 0x0000}, + {RT5677_PRIV_DATA , 0x0000}, + {RT5677_I2S4_SDP , 0x8000}, + {RT5677_I2S1_SDP , 0x8000}, + {RT5677_I2S2_SDP , 0x8000}, + {RT5677_I2S3_SDP , 0x8000}, + {RT5677_CLK_TREE_CTRL1 , 0x1111}, + {RT5677_CLK_TREE_CTRL2 , 0x1111}, + {RT5677_CLK_TREE_CTRL3 , 0x0000}, + {RT5677_PLL1_CTRL1 , 0x0000}, + {RT5677_PLL1_CTRL2 , 0x0000}, + {RT5677_PLL2_CTRL1 , 0x0c60}, + {RT5677_PLL2_CTRL2 , 0x2000}, + {RT5677_GLB_CLK1 , 0x0000}, + {RT5677_GLB_CLK2 , 0x0000}, + {RT5677_ASRC_1 , 0x0000}, + {RT5677_ASRC_2 , 0x0000}, + {RT5677_ASRC_3 , 0x0000}, + {RT5677_ASRC_4 , 0x0000}, + {RT5677_ASRC_5 , 0x0000}, + {RT5677_ASRC_6 , 0x0000}, + {RT5677_ASRC_7 , 0x0000}, + {RT5677_ASRC_8 , 0x0000}, + {RT5677_ASRC_9 , 0x0000}, + {RT5677_ASRC_10 , 0x0000}, + {RT5677_ASRC_11 , 0x0000}, + {RT5677_ASRC_12 , 0x0018}, + {RT5677_ASRC_13 , 0x0000}, + {RT5677_ASRC_14 , 0x0000}, + {RT5677_ASRC_15 , 0x0000}, + {RT5677_ASRC_16 , 0x0000}, + {RT5677_ASRC_17 , 0x0000}, + {RT5677_ASRC_18 , 0x0000}, + {RT5677_ASRC_19 , 0x0000}, + {RT5677_ASRC_20 , 0x0000}, + {RT5677_ASRC_21 , 0x000c}, + {RT5677_ASRC_22 , 0x0000}, + {RT5677_ASRC_23 , 0x0000}, + {RT5677_VAD_CTRL1 , 0x2184}, + {RT5677_VAD_CTRL2 , 0x010a}, + {RT5677_VAD_CTRL3 , 0x0aea}, + {RT5677_VAD_CTRL4 , 0x000c}, + {RT5677_VAD_CTRL5 , 0x0000}, + {RT5677_DSP_INB_CTRL1 , 0x0000}, + {RT5677_DSP_INB_CTRL2 , 0x0000}, + {RT5677_DSP_IN_OUTB_CTRL , 0x0000}, + {RT5677_DSP_OUTB0_1_DIG_VOL , 0x2f2f}, + {RT5677_DSP_OUTB2_3_DIG_VOL , 0x2f2f}, + {RT5677_DSP_OUTB4_5_DIG_VOL , 0x2f2f}, + {RT5677_DSP_OUTB6_7_DIG_VOL , 0x2f2f}, + {RT5677_ADC_EQ_CTRL1 , 0x6000}, + {RT5677_ADC_EQ_CTRL2 , 0x0000}, + {RT5677_EQ_CTRL1 , 0xc000}, + {RT5677_EQ_CTRL2 , 0x0000}, + {RT5677_EQ_CTRL3 , 0x0000}, + {RT5677_SOFT_VOL_ZERO_CROSS1 , 0x0009}, + {RT5677_JD_CTRL1 , 0x0000}, + {RT5677_JD_CTRL2 , 0x0000}, + {RT5677_JD_CTRL3 , 0x0000}, + {RT5677_IRQ_CTRL1 , 0x0000}, + {RT5677_IRQ_CTRL2 , 0x0000}, + {RT5677_GPIO_ST , 0x0000}, + {RT5677_GPIO_CTRL1 , 0x0000}, + {RT5677_GPIO_CTRL2 , 0x0000}, + {RT5677_GPIO_CTRL3 , 0x0000}, + {RT5677_STO1_ADC_HI_FILTER1 , 0xb320}, + {RT5677_STO1_ADC_HI_FILTER2 , 0x0000}, + {RT5677_MONO_ADC_HI_FILTER1 , 0xb300}, + {RT5677_MONO_ADC_HI_FILTER2 , 0x0000}, + {RT5677_STO2_ADC_HI_FILTER1 , 0xb300}, + {RT5677_STO2_ADC_HI_FILTER2 , 0x0000}, + {RT5677_STO3_ADC_HI_FILTER1 , 0xb300}, + {RT5677_STO3_ADC_HI_FILTER2 , 0x0000}, + {RT5677_STO4_ADC_HI_FILTER1 , 0xb300}, + {RT5677_STO4_ADC_HI_FILTER2 , 0x0000}, + {RT5677_MB_DRC_CTRL1 , 0x0f20}, + {RT5677_DRC1_CTRL1 , 0x001f}, + {RT5677_DRC1_CTRL2 , 0x020c}, + {RT5677_DRC1_CTRL3 , 0x1f00}, + {RT5677_DRC1_CTRL4 , 0x0000}, + {RT5677_DRC1_CTRL5 , 0x0000}, + {RT5677_DRC1_CTRL6 , 0x0029}, + {RT5677_DRC2_CTRL1 , 0x001f}, + {RT5677_DRC2_CTRL2 , 0x020c}, + {RT5677_DRC2_CTRL3 , 0x1f00}, + {RT5677_DRC2_CTRL4 , 0x0000}, + {RT5677_DRC2_CTRL5 , 0x0000}, + {RT5677_DRC2_CTRL6 , 0x0029}, + {RT5677_DRC1_HL_CTRL1 , 0x8000}, + {RT5677_DRC1_HL_CTRL2 , 0x0200}, + {RT5677_DRC2_HL_CTRL1 , 0x8000}, + {RT5677_DRC2_HL_CTRL2 , 0x0200}, + {RT5677_DSP_INB1_SRC_CTRL1 , 0x5800}, + {RT5677_DSP_INB1_SRC_CTRL2 , 0x0000}, + {RT5677_DSP_INB1_SRC_CTRL3 , 0x0000}, + {RT5677_DSP_INB1_SRC_CTRL4 , 0x0800}, + {RT5677_DSP_INB2_SRC_CTRL1 , 0x5800}, + {RT5677_DSP_INB2_SRC_CTRL2 , 0x0000}, + {RT5677_DSP_INB2_SRC_CTRL3 , 0x0000}, + {RT5677_DSP_INB2_SRC_CTRL4 , 0x0800}, + {RT5677_DSP_INB3_SRC_CTRL1 , 0x5800}, + {RT5677_DSP_INB3_SRC_CTRL2 , 0x0000}, + {RT5677_DSP_INB3_SRC_CTRL3 , 0x0000}, + {RT5677_DSP_INB3_SRC_CTRL4 , 0x0800}, + {RT5677_DSP_OUTB1_SRC_CTRL1 , 0x5800}, + {RT5677_DSP_OUTB1_SRC_CTRL2 , 0x0000}, + {RT5677_DSP_OUTB1_SRC_CTRL3 , 0x0000}, + {RT5677_DSP_OUTB1_SRC_CTRL4 , 0x0800}, + {RT5677_DSP_OUTB2_SRC_CTRL1 , 0x5800}, + {RT5677_DSP_OUTB2_SRC_CTRL2 , 0x0000}, + {RT5677_DSP_OUTB2_SRC_CTRL3 , 0x0000}, + {RT5677_DSP_OUTB2_SRC_CTRL4 , 0x0800}, + {RT5677_DSP_OUTB_0123_MIXER_CTRL, 0xfefe}, + {RT5677_DSP_OUTB_45_MIXER_CTRL , 0xfefe}, + {RT5677_DSP_OUTB_67_MIXER_CTRL , 0xfefe}, + {RT5677_DIG_MISC , 0x0000}, + {RT5677_GEN_CTRL1 , 0x0000}, + {RT5677_GEN_CTRL2 , 0x0000}, + {RT5677_VENDOR_ID , 0x0000}, + {RT5677_VENDOR_ID1 , 0x10ec}, + {RT5677_VENDOR_ID2 , 0x6327}, +}; + +static bool rt5677_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5677_ranges); i++) { + if (reg >= rt5677_ranges[i].range_min && + reg <= rt5677_ranges[i].range_max) { + return true; + } + } + + switch (reg) { + case RT5677_RESET: + case RT5677_SLIMBUS_PARAM: + case RT5677_PDM_DATA_CTRL1: + case RT5677_PDM_DATA_CTRL2: + case RT5677_PDM1_DATA_CTRL4: + case RT5677_PDM2_DATA_CTRL4: + case RT5677_I2C_MASTER_CTRL1: + case RT5677_I2C_MASTER_CTRL7: + case RT5677_I2C_MASTER_CTRL8: + case RT5677_HAP_GENE_CTRL2: + case RT5677_PWR_ANLG2: /* Modified by DSP firmware */ + case RT5677_PWR_DSP_ST: + case RT5677_PRIV_DATA: + case RT5677_ASRC_22: + case RT5677_ASRC_23: + case RT5677_VAD_CTRL5: + case RT5677_ADC_EQ_CTRL1: + case RT5677_EQ_CTRL1: + case RT5677_IRQ_CTRL1: + case RT5677_IRQ_CTRL2: + case RT5677_GPIO_ST: + case RT5677_DSP_INB1_SRC_CTRL4: + case RT5677_DSP_INB2_SRC_CTRL4: + case RT5677_DSP_INB3_SRC_CTRL4: + case RT5677_DSP_OUTB1_SRC_CTRL4: + case RT5677_DSP_OUTB2_SRC_CTRL4: + case RT5677_VENDOR_ID: + case RT5677_VENDOR_ID1: + case RT5677_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool rt5677_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rt5677_ranges); i++) { + if (reg >= rt5677_ranges[i].range_min && + reg <= rt5677_ranges[i].range_max) { + return true; + } + } + + switch (reg) { + case RT5677_RESET: + case RT5677_LOUT1: + case RT5677_IN1: + case RT5677_MICBIAS: + case RT5677_SLIMBUS_PARAM: + case RT5677_SLIMBUS_RX: + case RT5677_SLIMBUS_CTRL: + case RT5677_SIDETONE_CTRL: + case RT5677_ANA_DAC1_2_3_SRC: + case RT5677_IF_DSP_DAC3_4_MIXER: + case RT5677_DAC4_DIG_VOL: + case RT5677_DAC3_DIG_VOL: + case RT5677_DAC1_DIG_VOL: + case RT5677_DAC2_DIG_VOL: + case RT5677_IF_DSP_DAC2_MIXER: + case RT5677_STO1_ADC_DIG_VOL: + case RT5677_MONO_ADC_DIG_VOL: + case RT5677_STO1_2_ADC_BST: + case RT5677_STO2_ADC_DIG_VOL: + case RT5677_ADC_BST_CTRL2: + case RT5677_STO3_4_ADC_BST: + case RT5677_STO3_ADC_DIG_VOL: + case RT5677_STO4_ADC_DIG_VOL: + case RT5677_STO4_ADC_MIXER: + case RT5677_STO3_ADC_MIXER: + case RT5677_STO2_ADC_MIXER: + case RT5677_STO1_ADC_MIXER: + case RT5677_MONO_ADC_MIXER: + case RT5677_ADC_IF_DSP_DAC1_MIXER: + case RT5677_STO1_DAC_MIXER: + case RT5677_MONO_DAC_MIXER: + case RT5677_DD1_MIXER: + case RT5677_DD2_MIXER: + case RT5677_IF3_DATA: + case RT5677_IF4_DATA: + case RT5677_PDM_OUT_CTRL: + case RT5677_PDM_DATA_CTRL1: + case RT5677_PDM_DATA_CTRL2: + case RT5677_PDM1_DATA_CTRL2: + case RT5677_PDM1_DATA_CTRL3: + case RT5677_PDM1_DATA_CTRL4: + case RT5677_PDM2_DATA_CTRL2: + case RT5677_PDM2_DATA_CTRL3: + case RT5677_PDM2_DATA_CTRL4: + case RT5677_TDM1_CTRL1: + case RT5677_TDM1_CTRL2: + case RT5677_TDM1_CTRL3: + case RT5677_TDM1_CTRL4: + case RT5677_TDM1_CTRL5: + case RT5677_TDM2_CTRL1: + case RT5677_TDM2_CTRL2: + case RT5677_TDM2_CTRL3: + case RT5677_TDM2_CTRL4: + case RT5677_TDM2_CTRL5: + case RT5677_I2C_MASTER_CTRL1: + case RT5677_I2C_MASTER_CTRL2: + case RT5677_I2C_MASTER_CTRL3: + case RT5677_I2C_MASTER_CTRL4: + case RT5677_I2C_MASTER_CTRL5: + case RT5677_I2C_MASTER_CTRL6: + case RT5677_I2C_MASTER_CTRL7: + case RT5677_I2C_MASTER_CTRL8: + case RT5677_DMIC_CTRL1: + case RT5677_DMIC_CTRL2: + case RT5677_HAP_GENE_CTRL1: + case RT5677_HAP_GENE_CTRL2: + case RT5677_HAP_GENE_CTRL3: + case RT5677_HAP_GENE_CTRL4: + case RT5677_HAP_GENE_CTRL5: + case RT5677_HAP_GENE_CTRL6: + case RT5677_HAP_GENE_CTRL7: + case RT5677_HAP_GENE_CTRL8: + case RT5677_HAP_GENE_CTRL9: + case RT5677_HAP_GENE_CTRL10: + case RT5677_PWR_DIG1: + case RT5677_PWR_DIG2: + case RT5677_PWR_ANLG1: + case RT5677_PWR_ANLG2: + case RT5677_PWR_DSP1: + case RT5677_PWR_DSP_ST: + case RT5677_PWR_DSP2: + case RT5677_ADC_DAC_HPF_CTRL1: + case RT5677_PRIV_INDEX: + case RT5677_PRIV_DATA: + case RT5677_I2S4_SDP: + case RT5677_I2S1_SDP: + case RT5677_I2S2_SDP: + case RT5677_I2S3_SDP: + case RT5677_CLK_TREE_CTRL1: + case RT5677_CLK_TREE_CTRL2: + case RT5677_CLK_TREE_CTRL3: + case RT5677_PLL1_CTRL1: + case RT5677_PLL1_CTRL2: + case RT5677_PLL2_CTRL1: + case RT5677_PLL2_CTRL2: + case RT5677_GLB_CLK1: + case RT5677_GLB_CLK2: + case RT5677_ASRC_1: + case RT5677_ASRC_2: + case RT5677_ASRC_3: + case RT5677_ASRC_4: + case RT5677_ASRC_5: + case RT5677_ASRC_6: + case RT5677_ASRC_7: + case RT5677_ASRC_8: + case RT5677_ASRC_9: + case RT5677_ASRC_10: + case RT5677_ASRC_11: + case RT5677_ASRC_12: + case RT5677_ASRC_13: + case RT5677_ASRC_14: + case RT5677_ASRC_15: + case RT5677_ASRC_16: + case RT5677_ASRC_17: + case RT5677_ASRC_18: + case RT5677_ASRC_19: + case RT5677_ASRC_20: + case RT5677_ASRC_21: + case RT5677_ASRC_22: + case RT5677_ASRC_23: + case RT5677_VAD_CTRL1: + case RT5677_VAD_CTRL2: + case RT5677_VAD_CTRL3: + case RT5677_VAD_CTRL4: + case RT5677_VAD_CTRL5: + case RT5677_DSP_INB_CTRL1: + case RT5677_DSP_INB_CTRL2: + case RT5677_DSP_IN_OUTB_CTRL: + case RT5677_DSP_OUTB0_1_DIG_VOL: + case RT5677_DSP_OUTB2_3_DIG_VOL: + case RT5677_DSP_OUTB4_5_DIG_VOL: + case RT5677_DSP_OUTB6_7_DIG_VOL: + case RT5677_ADC_EQ_CTRL1: + case RT5677_ADC_EQ_CTRL2: + case RT5677_EQ_CTRL1: + case RT5677_EQ_CTRL2: + case RT5677_EQ_CTRL3: + case RT5677_SOFT_VOL_ZERO_CROSS1: + case RT5677_JD_CTRL1: + case RT5677_JD_CTRL2: + case RT5677_JD_CTRL3: + case RT5677_IRQ_CTRL1: + case RT5677_IRQ_CTRL2: + case RT5677_GPIO_ST: + case RT5677_GPIO_CTRL1: + case RT5677_GPIO_CTRL2: + case RT5677_GPIO_CTRL3: + case RT5677_STO1_ADC_HI_FILTER1: + case RT5677_STO1_ADC_HI_FILTER2: + case RT5677_MONO_ADC_HI_FILTER1: + case RT5677_MONO_ADC_HI_FILTER2: + case RT5677_STO2_ADC_HI_FILTER1: + case RT5677_STO2_ADC_HI_FILTER2: + case RT5677_STO3_ADC_HI_FILTER1: + case RT5677_STO3_ADC_HI_FILTER2: + case RT5677_STO4_ADC_HI_FILTER1: + case RT5677_STO4_ADC_HI_FILTER2: + case RT5677_MB_DRC_CTRL1: + case RT5677_DRC1_CTRL1: + case RT5677_DRC1_CTRL2: + case RT5677_DRC1_CTRL3: + case RT5677_DRC1_CTRL4: + case RT5677_DRC1_CTRL5: + case RT5677_DRC1_CTRL6: + case RT5677_DRC2_CTRL1: + case RT5677_DRC2_CTRL2: + case RT5677_DRC2_CTRL3: + case RT5677_DRC2_CTRL4: + case RT5677_DRC2_CTRL5: + case RT5677_DRC2_CTRL6: + case RT5677_DRC1_HL_CTRL1: + case RT5677_DRC1_HL_CTRL2: + case RT5677_DRC2_HL_CTRL1: + case RT5677_DRC2_HL_CTRL2: + case RT5677_DSP_INB1_SRC_CTRL1: + case RT5677_DSP_INB1_SRC_CTRL2: + case RT5677_DSP_INB1_SRC_CTRL3: + case RT5677_DSP_INB1_SRC_CTRL4: + case RT5677_DSP_INB2_SRC_CTRL1: + case RT5677_DSP_INB2_SRC_CTRL2: + case RT5677_DSP_INB2_SRC_CTRL3: + case RT5677_DSP_INB2_SRC_CTRL4: + case RT5677_DSP_INB3_SRC_CTRL1: + case RT5677_DSP_INB3_SRC_CTRL2: + case RT5677_DSP_INB3_SRC_CTRL3: + case RT5677_DSP_INB3_SRC_CTRL4: + case RT5677_DSP_OUTB1_SRC_CTRL1: + case RT5677_DSP_OUTB1_SRC_CTRL2: + case RT5677_DSP_OUTB1_SRC_CTRL3: + case RT5677_DSP_OUTB1_SRC_CTRL4: + case RT5677_DSP_OUTB2_SRC_CTRL1: + case RT5677_DSP_OUTB2_SRC_CTRL2: + case RT5677_DSP_OUTB2_SRC_CTRL3: + case RT5677_DSP_OUTB2_SRC_CTRL4: + case RT5677_DSP_OUTB_0123_MIXER_CTRL: + case RT5677_DSP_OUTB_45_MIXER_CTRL: + case RT5677_DSP_OUTB_67_MIXER_CTRL: + case RT5677_DIG_MISC: + case RT5677_GEN_CTRL1: + case RT5677_GEN_CTRL2: + case RT5677_VENDOR_ID: + case RT5677_VENDOR_ID1: + case RT5677_VENDOR_ID2: + return true; + default: + return false; + } +} + +/** + * rt5677_dsp_mode_i2c_write_addr - Write value to address on DSP mode. + * @rt5677: Private Data. + * @addr: Address index. + * @value: Address data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_write_addr(struct rt5677_priv *rt5677, + unsigned int addr, unsigned int value, unsigned int opcode) +{ + struct snd_soc_component *component = rt5677->component; + int ret; + + mutex_lock(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, + value >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set data msb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, + value & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set data lsb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + opcode); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); + goto err; + } + +err: + mutex_unlock(&rt5677->dsp_cmd_lock); + + return ret; +} + +/** + * rt5677_dsp_mode_i2c_read_addr - Read value from address on DSP mode. + * rt5677: Private Data. + * @addr: Address index. + * @value: Address data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_read_addr( + struct rt5677_priv *rt5677, unsigned int addr, unsigned int *value) +{ + struct snd_soc_component *component = rt5677->component; + int ret; + unsigned int msb, lsb; + + mutex_lock(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); + goto err; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + 0x0002); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); + goto err; + } + + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, &msb); + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, &lsb); + *value = (msb << 16) | lsb; + +err: + mutex_unlock(&rt5677->dsp_cmd_lock); + + return ret; +} + +/** + * rt5677_dsp_mode_i2c_write - Write register on DSP mode. + * rt5677: Private Data. + * @reg: Register index. + * @value: Register data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_write(struct rt5677_priv *rt5677, + unsigned int reg, unsigned int value) +{ + return rt5677_dsp_mode_i2c_write_addr(rt5677, 0x18020000 + reg * 2, + value, 0x0001); +} + +/** + * rt5677_dsp_mode_i2c_read - Read register on DSP mode. + * @codec: SoC audio codec device. + * @reg: Register index. + * @value: Register data. + * + * + * Returns 0 for success or negative error code. + */ +static int rt5677_dsp_mode_i2c_read( + struct rt5677_priv *rt5677, unsigned int reg, unsigned int *value) +{ + int ret = rt5677_dsp_mode_i2c_read_addr(rt5677, 0x18020000 + reg * 2, + value); + + *value &= 0xffff; + + return ret; +} + +static void rt5677_set_dsp_mode(struct snd_soc_component *component, bool on) +{ + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + if (on) { + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x2, 0x2); + rt5677->is_dsp_mode = true; + } else { + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x2, 0x0); + rt5677->is_dsp_mode = false; + } +} + +static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on) +{ + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + static bool activity; + int ret; + + if (!IS_ENABLED(CONFIG_SND_SOC_RT5677_SPI)) + return -ENXIO; + + if (on && !activity) { + activity = true; + + regcache_cache_only(rt5677->regmap, false); + regcache_cache_bypass(rt5677->regmap, true); + + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x1, 0x1); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_BIAS_CUR4, 0x0f00, 0x0f00); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, + RT5677_LDO1_SEL_MASK, 0x0); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_LDO1, RT5677_PWR_LDO1); + switch (rt5677->type) { + case RT5677: + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_MCLK_SRC_MASK, RT5677_MCLK2_SRC); + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK2, + RT5677_PLL2_PR_SRC_MASK | + RT5677_DSP_CLK_SRC_MASK, + RT5677_PLL2_PR_SRC_MCLK2 | + RT5677_DSP_CLK_SRC_BYPASS); + break; + case RT5676: + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK2, + RT5677_DSP_CLK_SRC_MASK, + RT5677_DSP_CLK_SRC_BYPASS); + break; + default: + break; + } + regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x07ff); + regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x07fd); + rt5677_set_dsp_mode(component, true); + + ret = request_firmware(&rt5677->fw1, RT5677_FIRMWARE1, + component->dev); + if (ret == 0) { + rt5677_spi_write_firmware(0x50000000, rt5677->fw1); + release_firmware(rt5677->fw1); + } + + ret = request_firmware(&rt5677->fw2, RT5677_FIRMWARE2, + component->dev); + if (ret == 0) { + rt5677_spi_write_firmware(0x60000000, rt5677->fw2); + release_firmware(rt5677->fw2); + } + + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x1, 0x0); + + regcache_cache_bypass(rt5677->regmap, false); + regcache_cache_only(rt5677->regmap, true); + } else if (!on && activity) { + activity = false; + + regcache_cache_only(rt5677->regmap, false); + regcache_cache_bypass(rt5677->regmap, true); + + regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, 0x1, 0x1); + rt5677_set_dsp_mode(component, false); + regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x0001); + + regmap_write(rt5677->regmap, RT5677_RESET, 0x10ec); + + regcache_cache_bypass(rt5677->regmap, false); + regcache_mark_dirty(rt5677->regmap); + regcache_sync(rt5677->regmap); + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); +static const DECLARE_TLV_DB_SCALE(st_vol_tlv, -4650, 150, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +static int rt5677_dsp_vad_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = rt5677->dsp_vad_en; + + return 0; +} + +static int rt5677_dsp_vad_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + rt5677->dsp_vad_en = !!ucontrol->value.integer.value[0]; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + rt5677_set_dsp_vad(component, rt5677->dsp_vad_en); + + return 0; +} + +static const struct snd_kcontrol_new rt5677_snd_controls[] = { + /* OUTPUT Control */ + SOC_SINGLE("OUT1 Playback Switch", RT5677_LOUT1, + RT5677_LOUT1_L_MUTE_SFT, 1, 1), + SOC_SINGLE("OUT2 Playback Switch", RT5677_LOUT1, + RT5677_LOUT2_L_MUTE_SFT, 1, 1), + SOC_SINGLE("OUT3 Playback Switch", RT5677_LOUT1, + RT5677_LOUT3_L_MUTE_SFT, 1, 1), + + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5677_DAC1_DIG_VOL, + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("DAC2 Playback Volume", RT5677_DAC2_DIG_VOL, + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("DAC3 Playback Volume", RT5677_DAC3_DIG_VOL, + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("DAC4 Playback Volume", RT5677_DAC4_DIG_VOL, + RT5677_L_VOL_SFT, RT5677_R_VOL_SFT, 87, 0, dac_vol_tlv), + + /* IN1/IN2 Control */ + SOC_SINGLE_TLV("IN1 Boost", RT5677_IN1, RT5677_BST_SFT1, 8, 0, bst_tlv), + SOC_SINGLE_TLV("IN2 Boost", RT5677_IN1, RT5677_BST_SFT2, 8, 0, bst_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("ADC1 Capture Switch", RT5677_STO1_ADC_DIG_VOL, + RT5677_L_MUTE_SFT, RT5677_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("ADC2 Capture Switch", RT5677_STO2_ADC_DIG_VOL, + RT5677_L_MUTE_SFT, RT5677_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("ADC3 Capture Switch", RT5677_STO3_ADC_DIG_VOL, + RT5677_L_MUTE_SFT, RT5677_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("ADC4 Capture Switch", RT5677_STO4_ADC_DIG_VOL, + RT5677_L_MUTE_SFT, RT5677_R_MUTE_SFT, 1, 1), + SOC_DOUBLE("Mono ADC Capture Switch", RT5677_MONO_ADC_DIG_VOL, + RT5677_L_MUTE_SFT, RT5677_R_MUTE_SFT, 1, 1), + + SOC_DOUBLE_TLV("ADC1 Capture Volume", RT5677_STO1_ADC_DIG_VOL, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, + adc_vol_tlv), + SOC_DOUBLE_TLV("ADC2 Capture Volume", RT5677_STO2_ADC_DIG_VOL, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, + adc_vol_tlv), + SOC_DOUBLE_TLV("ADC3 Capture Volume", RT5677_STO3_ADC_DIG_VOL, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, + adc_vol_tlv), + SOC_DOUBLE_TLV("ADC4 Capture Volume", RT5677_STO4_ADC_DIG_VOL, + RT5677_STO1_ADC_L_VOL_SFT, RT5677_STO1_ADC_R_VOL_SFT, 63, 0, + adc_vol_tlv), + SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5677_MONO_ADC_DIG_VOL, + RT5677_MONO_ADC_L_VOL_SFT, RT5677_MONO_ADC_R_VOL_SFT, 63, 0, + adc_vol_tlv), + + /* Sidetone Control */ + SOC_SINGLE_TLV("Sidetone Volume", RT5677_SIDETONE_CTRL, + RT5677_ST_VOL_SFT, 31, 0, st_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Volume", RT5677_STO1_2_ADC_BST, + RT5677_STO1_ADC_L_BST_SFT, RT5677_STO1_ADC_R_BST_SFT, 3, 0, + adc_bst_tlv), + SOC_DOUBLE_TLV("STO2 ADC Boost Volume", RT5677_STO1_2_ADC_BST, + RT5677_STO2_ADC_L_BST_SFT, RT5677_STO2_ADC_R_BST_SFT, 3, 0, + adc_bst_tlv), + SOC_DOUBLE_TLV("STO3 ADC Boost Volume", RT5677_STO3_4_ADC_BST, + RT5677_STO3_ADC_L_BST_SFT, RT5677_STO3_ADC_R_BST_SFT, 3, 0, + adc_bst_tlv), + SOC_DOUBLE_TLV("STO4 ADC Boost Volume", RT5677_STO3_4_ADC_BST, + RT5677_STO4_ADC_L_BST_SFT, RT5677_STO4_ADC_R_BST_SFT, 3, 0, + adc_bst_tlv), + SOC_DOUBLE_TLV("Mono ADC Boost Volume", RT5677_ADC_BST_CTRL2, + RT5677_MONO_ADC_L_BST_SFT, RT5677_MONO_ADC_R_BST_SFT, 3, 0, + adc_bst_tlv), + + SOC_SINGLE_EXT("DSP VAD Switch", SND_SOC_NOPM, 0, 1, 0, + rt5677_dsp_vad_get, rt5677_dsp_vad_put), +}; + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + * Choose dmic clock between 1MHz and 3MHz. + * It is better for clock to approximate 3MHz. + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + int idx, rate; + + rate = rt5677->sysclk / rl6231_get_pre_div(rt5677->regmap, + RT5677_CLK_TREE_CTRL1, RT5677_I2S_PD1_SFT); + idx = rl6231_calc_dmic_clk(rate); + if (idx < 0) + dev_err(component->dev, "Failed to set DMIC clock\n"); + else + regmap_update_bits(rt5677->regmap, RT5677_DMIC_CTRL1, + RT5677_DMIC_CLK_MASK, idx << RT5677_DMIC_CLK_SFT); + return idx; +} + +static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int val; + + regmap_read(rt5677->regmap, RT5677_GLB_CLK1, &val); + val &= RT5677_SCLK_SRC_MASK; + if (val == RT5677_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int reg, shift, val; + + if (source->reg == RT5677_ASRC_1) { + switch (source->shift) { + case 12: + reg = RT5677_ASRC_4; + shift = 0; + break; + case 13: + reg = RT5677_ASRC_4; + shift = 4; + break; + case 14: + reg = RT5677_ASRC_4; + shift = 8; + break; + case 15: + reg = RT5677_ASRC_4; + shift = 12; + break; + default: + return 0; + } + } else { + switch (source->shift) { + case 0: + reg = RT5677_ASRC_6; + shift = 8; + break; + case 1: + reg = RT5677_ASRC_6; + shift = 12; + break; + case 2: + reg = RT5677_ASRC_5; + shift = 0; + break; + case 3: + reg = RT5677_ASRC_5; + shift = 4; + break; + case 4: + reg = RT5677_ASRC_5; + shift = 8; + break; + case 5: + reg = RT5677_ASRC_5; + shift = 12; + break; + case 12: + reg = RT5677_ASRC_3; + shift = 0; + break; + case 13: + reg = RT5677_ASRC_3; + shift = 4; + break; + case 14: + reg = RT5677_ASRC_3; + shift = 12; + break; + default: + return 0; + } + } + + regmap_read(rt5677->regmap, reg, &val); + val = (val >> shift) & 0xf; + + switch (val) { + case 1 ... 6: + return 1; + default: + return 0; + } + +} + +static int can_use_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + if (rt5677->sysclk > rt5677->lrck[RT5677_AIF1] * 384) + return 1; + + return 0; +} + +/** + * rt5677_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5677 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the codec driver will turn on ASRC + * for these filters if ASRC is selected as their clock source. + */ +int rt5677_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int asrc3_mask = 0, asrc3_value = 0; + unsigned int asrc4_mask = 0, asrc4_value = 0; + unsigned int asrc5_mask = 0, asrc5_value = 0; + unsigned int asrc6_mask = 0, asrc6_value = 0; + unsigned int asrc7_mask = 0, asrc7_value = 0; + unsigned int asrc8_mask = 0, asrc8_value = 0; + + switch (clk_src) { + case RT5677_CLK_SEL_SYS: + case RT5677_CLK_SEL_I2S1_ASRC: + case RT5677_CLK_SEL_I2S2_ASRC: + case RT5677_CLK_SEL_I2S3_ASRC: + case RT5677_CLK_SEL_I2S4_ASRC: + case RT5677_CLK_SEL_I2S5_ASRC: + case RT5677_CLK_SEL_I2S6_ASRC: + case RT5677_CLK_SEL_SYS2: + case RT5677_CLK_SEL_SYS3: + case RT5677_CLK_SEL_SYS4: + case RT5677_CLK_SEL_SYS5: + case RT5677_CLK_SEL_SYS6: + case RT5677_CLK_SEL_SYS7: + break; + + default: + return -EINVAL; + } + + /* ASRC 3 */ + if (filter_mask & RT5677_DA_STEREO_FILTER) { + asrc3_mask |= RT5677_DA_STO_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5677_DA_STO_CLK_SEL_MASK) + | (clk_src << RT5677_DA_STO_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO2_L_FILTER) { + asrc3_mask |= RT5677_DA_MONO2L_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5677_DA_MONO2L_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO2L_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO2_R_FILTER) { + asrc3_mask |= RT5677_DA_MONO2R_CLK_SEL_MASK; + asrc3_value = (asrc3_value & ~RT5677_DA_MONO2R_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO2R_CLK_SEL_SFT); + } + + if (asrc3_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_3, asrc3_mask, + asrc3_value); + + /* ASRC 4 */ + if (filter_mask & RT5677_DA_MONO3_L_FILTER) { + asrc4_mask |= RT5677_DA_MONO3L_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO3L_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO3L_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO3_R_FILTER) { + asrc4_mask |= RT5677_DA_MONO3R_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO3R_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO3R_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO4_L_FILTER) { + asrc4_mask |= RT5677_DA_MONO4L_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO4L_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO4L_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DA_MONO4_R_FILTER) { + asrc4_mask |= RT5677_DA_MONO4R_CLK_SEL_MASK; + asrc4_value = (asrc4_value & ~RT5677_DA_MONO4R_CLK_SEL_MASK) + | (clk_src << RT5677_DA_MONO4R_CLK_SEL_SFT); + } + + if (asrc4_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_4, asrc4_mask, + asrc4_value); + + /* ASRC 5 */ + if (filter_mask & RT5677_AD_STEREO1_FILTER) { + asrc5_mask |= RT5677_AD_STO1_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO1_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO1_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_STEREO2_FILTER) { + asrc5_mask |= RT5677_AD_STO2_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO2_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO2_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_STEREO3_FILTER) { + asrc5_mask |= RT5677_AD_STO3_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO3_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO3_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_STEREO4_FILTER) { + asrc5_mask |= RT5677_AD_STO4_CLK_SEL_MASK; + asrc5_value = (asrc5_value & ~RT5677_AD_STO4_CLK_SEL_MASK) + | (clk_src << RT5677_AD_STO4_CLK_SEL_SFT); + } + + if (asrc5_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_5, asrc5_mask, + asrc5_value); + + /* ASRC 6 */ + if (filter_mask & RT5677_AD_MONO_L_FILTER) { + asrc6_mask |= RT5677_AD_MONOL_CLK_SEL_MASK; + asrc6_value = (asrc6_value & ~RT5677_AD_MONOL_CLK_SEL_MASK) + | (clk_src << RT5677_AD_MONOL_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_AD_MONO_R_FILTER) { + asrc6_mask |= RT5677_AD_MONOR_CLK_SEL_MASK; + asrc6_value = (asrc6_value & ~RT5677_AD_MONOR_CLK_SEL_MASK) + | (clk_src << RT5677_AD_MONOR_CLK_SEL_SFT); + } + + if (asrc6_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_6, asrc6_mask, + asrc6_value); + + /* ASRC 7 */ + if (filter_mask & RT5677_DSP_OB_0_3_FILTER) { + asrc7_mask |= RT5677_DSP_OB_0_3_CLK_SEL_MASK; + asrc7_value = (asrc7_value & ~RT5677_DSP_OB_0_3_CLK_SEL_MASK) + | (clk_src << RT5677_DSP_OB_0_3_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_DSP_OB_4_7_FILTER) { + asrc7_mask |= RT5677_DSP_OB_4_7_CLK_SEL_MASK; + asrc7_value = (asrc7_value & ~RT5677_DSP_OB_4_7_CLK_SEL_MASK) + | (clk_src << RT5677_DSP_OB_4_7_CLK_SEL_SFT); + } + + if (asrc7_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_7, asrc7_mask, + asrc7_value); + + /* ASRC 8 */ + if (filter_mask & RT5677_I2S1_SOURCE) { + asrc8_mask |= RT5677_I2S1_CLK_SEL_MASK; + asrc8_value = (asrc8_value & ~RT5677_I2S1_CLK_SEL_MASK) + | ((clk_src - 1) << RT5677_I2S1_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_I2S2_SOURCE) { + asrc8_mask |= RT5677_I2S2_CLK_SEL_MASK; + asrc8_value = (asrc8_value & ~RT5677_I2S2_CLK_SEL_MASK) + | ((clk_src - 1) << RT5677_I2S2_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_I2S3_SOURCE) { + asrc8_mask |= RT5677_I2S3_CLK_SEL_MASK; + asrc8_value = (asrc8_value & ~RT5677_I2S3_CLK_SEL_MASK) + | ((clk_src - 1) << RT5677_I2S3_CLK_SEL_SFT); + } + + if (filter_mask & RT5677_I2S4_SOURCE) { + asrc8_mask |= RT5677_I2S4_CLK_SEL_MASK; + asrc8_value = (asrc8_value & ~RT5677_I2S4_CLK_SEL_MASK) + | ((clk_src - 1) << RT5677_I2S4_CLK_SEL_SFT); + } + + if (asrc8_mask) + regmap_update_bits(rt5677->regmap, RT5677_ASRC_8, asrc8_mask, + asrc8_value); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5677_sel_asrc_clk_src); + +static int rt5677_dmic_use_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int asrc_setting; + + switch (source->shift) { + case 11: + regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); + asrc_setting = (asrc_setting & RT5677_AD_STO1_CLK_SEL_MASK) >> + RT5677_AD_STO1_CLK_SEL_SFT; + break; + + case 10: + regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); + asrc_setting = (asrc_setting & RT5677_AD_STO2_CLK_SEL_MASK) >> + RT5677_AD_STO2_CLK_SEL_SFT; + break; + + case 9: + regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); + asrc_setting = (asrc_setting & RT5677_AD_STO3_CLK_SEL_MASK) >> + RT5677_AD_STO3_CLK_SEL_SFT; + break; + + case 8: + regmap_read(rt5677->regmap, RT5677_ASRC_5, &asrc_setting); + asrc_setting = (asrc_setting & RT5677_AD_STO4_CLK_SEL_MASK) >> + RT5677_AD_STO4_CLK_SEL_SFT; + break; + + case 7: + regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting); + asrc_setting = (asrc_setting & RT5677_AD_MONOL_CLK_SEL_MASK) >> + RT5677_AD_MONOL_CLK_SEL_SFT; + break; + + case 6: + regmap_read(rt5677->regmap, RT5677_ASRC_6, &asrc_setting); + asrc_setting = (asrc_setting & RT5677_AD_MONOR_CLK_SEL_MASK) >> + RT5677_AD_MONOR_CLK_SEL_SFT; + break; + + default: + return 0; + } + + if (asrc_setting >= RT5677_CLK_SEL_I2S1_ASRC && + asrc_setting <= RT5677_CLK_SEL_I2S6_ASRC) + return 1; + + return 0; +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5677_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO1_ADC_MIXER, + RT5677_M_STO1_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO1_ADC_MIXER, + RT5677_M_STO1_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO1_ADC_MIXER, + RT5677_M_STO1_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO1_ADC_MIXER, + RT5677_M_STO1_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto2_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO2_ADC_MIXER, + RT5677_M_STO2_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO2_ADC_MIXER, + RT5677_M_STO2_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto2_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO2_ADC_MIXER, + RT5677_M_STO2_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO2_ADC_MIXER, + RT5677_M_STO2_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto3_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO3_ADC_MIXER, + RT5677_M_STO3_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO3_ADC_MIXER, + RT5677_M_STO3_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto3_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO3_ADC_MIXER, + RT5677_M_STO3_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO3_ADC_MIXER, + RT5677_M_STO3_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto4_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO4_ADC_MIXER, + RT5677_M_STO4_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO4_ADC_MIXER, + RT5677_M_STO4_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto4_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_STO4_ADC_MIXER, + RT5677_M_STO4_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_STO4_ADC_MIXER, + RT5677_M_STO4_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_mono_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_MONO_ADC_MIXER, + RT5677_M_MONO_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_MONO_ADC_MIXER, + RT5677_M_MONO_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_mono_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5677_MONO_ADC_MIXER, + RT5677_M_MONO_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5677_MONO_ADC_MIXER, + RT5677_M_MONO_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5677_ADC_IF_DSP_DAC1_MIXER, + RT5677_M_ADDA_MIXER1_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5677_ADC_IF_DSP_DAC1_MIXER, + RT5677_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5677_ADC_IF_DSP_DAC1_MIXER, + RT5677_M_ADDA_MIXER1_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5677_ADC_IF_DSP_DAC1_MIXER, + RT5677_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto1_dac_l_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ST L Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_ST_DAC1_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 L Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_DAC1_L_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC2 L Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_DAC2_L_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 R Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_DAC1_R_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_sto1_dac_r_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ST R Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_ST_DAC1_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 R Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_DAC1_R_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC2 R Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_DAC2_R_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 L Switch", RT5677_STO1_DAC_MIXER, + RT5677_M_DAC1_L_STO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_mono_dac_l_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ST L Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_ST_DAC2_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 L Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_DAC1_L_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC2 L Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_DAC2_L_MONO_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC2 R Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_DAC2_R_MONO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_mono_dac_r_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ST R Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_ST_DAC2_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC1 R Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_DAC1_R_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC2 R Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_DAC2_R_MONO_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC2 L Switch", RT5677_MONO_DAC_MIXER, + RT5677_M_DAC2_L_MONO_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_dd1_l_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Sto DAC Mix L Switch", RT5677_DD1_MIXER, + RT5677_M_STO_L_DD1_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("Mono DAC Mix L Switch", RT5677_DD1_MIXER, + RT5677_M_MONO_L_DD1_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC3 L Switch", RT5677_DD1_MIXER, + RT5677_M_DAC3_L_DD1_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC3 R Switch", RT5677_DD1_MIXER, + RT5677_M_DAC3_R_DD1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_dd1_r_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Sto DAC Mix R Switch", RT5677_DD1_MIXER, + RT5677_M_STO_R_DD1_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("Mono DAC Mix R Switch", RT5677_DD1_MIXER, + RT5677_M_MONO_R_DD1_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC3 R Switch", RT5677_DD1_MIXER, + RT5677_M_DAC3_R_DD1_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC3 L Switch", RT5677_DD1_MIXER, + RT5677_M_DAC3_L_DD1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_dd2_l_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Sto DAC Mix L Switch", RT5677_DD2_MIXER, + RT5677_M_STO_L_DD2_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("Mono DAC Mix L Switch", RT5677_DD2_MIXER, + RT5677_M_MONO_L_DD2_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC4 L Switch", RT5677_DD2_MIXER, + RT5677_M_DAC4_L_DD2_L_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC4 R Switch", RT5677_DD2_MIXER, + RT5677_M_DAC4_R_DD2_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_dd2_r_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Sto DAC Mix R Switch", RT5677_DD2_MIXER, + RT5677_M_STO_R_DD2_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("Mono DAC Mix R Switch", RT5677_DD2_MIXER, + RT5677_M_MONO_R_DD2_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC4 R Switch", RT5677_DD2_MIXER, + RT5677_M_DAC4_R_DD2_R_SFT, 1, 1), + SOC_DAPM_SINGLE_AUTODISABLE("DAC4 L Switch", RT5677_DD2_MIXER, + RT5677_M_DAC4_L_DD2_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_ob_01_mix[] = { + SOC_DAPM_SINGLE("IB01 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_01_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB23 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_23_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB45 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_45_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB6 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_6_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB7 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_7_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB8 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_8_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB9 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_9_H_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_ob_23_mix[] = { + SOC_DAPM_SINGLE("IB01 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_01_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB23 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_23_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB45 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_45_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB6 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_6_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB7 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_7_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB8 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_8_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB9 Switch", RT5677_DSP_OUTB_0123_MIXER_CTRL, + RT5677_DSP_IB_9_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_ob_4_mix[] = { + SOC_DAPM_SINGLE("IB01 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_01_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB23 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_23_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB45 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_45_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB6 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_6_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB7 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_7_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB8 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_8_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB9 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_9_H_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_ob_5_mix[] = { + SOC_DAPM_SINGLE("IB01 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_01_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB23 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_23_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB45 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_45_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB6 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_6_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB7 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_7_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB8 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_8_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB9 Switch", RT5677_DSP_OUTB_45_MIXER_CTRL, + RT5677_DSP_IB_9_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_ob_6_mix[] = { + SOC_DAPM_SINGLE("IB01 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_01_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB23 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_23_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB45 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_45_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB6 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_6_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB7 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_7_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB8 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_8_H_SFT, 1, 1), + SOC_DAPM_SINGLE("IB9 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_9_H_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5677_ob_7_mix[] = { + SOC_DAPM_SINGLE("IB01 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_01_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB23 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_23_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB45 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_45_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB6 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_6_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB7 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_7_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB8 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_8_L_SFT, 1, 1), + SOC_DAPM_SINGLE("IB9 Switch", RT5677_DSP_OUTB_67_MIXER_CTRL, + RT5677_DSP_IB_9_L_SFT, 1, 1), +}; + + +/* Mux */ +/* DAC1 L/R Source */ /* MX-29 [10:8] */ +static const char * const rt5677_dac1_src[] = { + "IF1 DAC 01", "IF2 DAC 01", "IF3 DAC LR", "IF4 DAC LR", "SLB DAC 01", + "OB 01" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac1_enum, RT5677_ADC_IF_DSP_DAC1_MIXER, + RT5677_DAC1_L_SEL_SFT, rt5677_dac1_src); + +static const struct snd_kcontrol_new rt5677_dac1_mux = + SOC_DAPM_ENUM("DAC1 Source", rt5677_dac1_enum); + +/* ADDA1 L/R Source */ /* MX-29 [1:0] */ +static const char * const rt5677_adda1_src[] = { + "STO1 ADC MIX", "STO2 ADC MIX", "OB 67", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_adda1_enum, RT5677_ADC_IF_DSP_DAC1_MIXER, + RT5677_ADDA1_SEL_SFT, rt5677_adda1_src); + +static const struct snd_kcontrol_new rt5677_adda1_mux = + SOC_DAPM_ENUM("ADDA1 Source", rt5677_adda1_enum); + + +/*DAC2 L/R Source*/ /* MX-1B [6:4] [2:0] */ +static const char * const rt5677_dac2l_src[] = { + "IF1 DAC 2", "IF2 DAC 2", "IF3 DAC L", "IF4 DAC L", "SLB DAC 2", + "OB 2", +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac2l_enum, RT5677_IF_DSP_DAC2_MIXER, + RT5677_SEL_DAC2_L_SRC_SFT, rt5677_dac2l_src); + +static const struct snd_kcontrol_new rt5677_dac2_l_mux = + SOC_DAPM_ENUM("DAC2 L Source", rt5677_dac2l_enum); + +static const char * const rt5677_dac2r_src[] = { + "IF1 DAC 3", "IF2 DAC 3", "IF3 DAC R", "IF4 DAC R", "SLB DAC 3", + "OB 3", "Haptic Generator", "VAD ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac2r_enum, RT5677_IF_DSP_DAC2_MIXER, + RT5677_SEL_DAC2_R_SRC_SFT, rt5677_dac2r_src); + +static const struct snd_kcontrol_new rt5677_dac2_r_mux = + SOC_DAPM_ENUM("DAC2 R Source", rt5677_dac2r_enum); + +/*DAC3 L/R Source*/ /* MX-16 [6:4] [2:0] */ +static const char * const rt5677_dac3l_src[] = { + "IF1 DAC 4", "IF2 DAC 4", "IF3 DAC L", "IF4 DAC L", + "SLB DAC 4", "OB 4" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac3l_enum, RT5677_IF_DSP_DAC3_4_MIXER, + RT5677_SEL_DAC3_L_SRC_SFT, rt5677_dac3l_src); + +static const struct snd_kcontrol_new rt5677_dac3_l_mux = + SOC_DAPM_ENUM("DAC3 L Source", rt5677_dac3l_enum); + +static const char * const rt5677_dac3r_src[] = { + "IF1 DAC 5", "IF2 DAC 5", "IF3 DAC R", "IF4 DAC R", + "SLB DAC 5", "OB 5" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac3r_enum, RT5677_IF_DSP_DAC3_4_MIXER, + RT5677_SEL_DAC3_R_SRC_SFT, rt5677_dac3r_src); + +static const struct snd_kcontrol_new rt5677_dac3_r_mux = + SOC_DAPM_ENUM("DAC3 R Source", rt5677_dac3r_enum); + +/*DAC4 L/R Source*/ /* MX-16 [14:12] [10:8] */ +static const char * const rt5677_dac4l_src[] = { + "IF1 DAC 6", "IF2 DAC 6", "IF3 DAC L", "IF4 DAC L", + "SLB DAC 6", "OB 6" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac4l_enum, RT5677_IF_DSP_DAC3_4_MIXER, + RT5677_SEL_DAC4_L_SRC_SFT, rt5677_dac4l_src); + +static const struct snd_kcontrol_new rt5677_dac4_l_mux = + SOC_DAPM_ENUM("DAC4 L Source", rt5677_dac4l_enum); + +static const char * const rt5677_dac4r_src[] = { + "IF1 DAC 7", "IF2 DAC 7", "IF3 DAC R", "IF4 DAC R", + "SLB DAC 7", "OB 7" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac4r_enum, RT5677_IF_DSP_DAC3_4_MIXER, + RT5677_SEL_DAC4_R_SRC_SFT, rt5677_dac4r_src); + +static const struct snd_kcontrol_new rt5677_dac4_r_mux = + SOC_DAPM_ENUM("DAC4 R Source", rt5677_dac4r_enum); + +/* In/OutBound Source Pass SRC */ /* MX-A5 [3] [4] [0] [1] [2] */ +static const char * const rt5677_iob_bypass_src[] = { + "Bypass", "Pass SRC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_ob01_bypass_src_enum, RT5677_DSP_IN_OUTB_CTRL, + RT5677_SEL_SRC_OB01_SFT, rt5677_iob_bypass_src); + +static const struct snd_kcontrol_new rt5677_ob01_bypass_src_mux = + SOC_DAPM_ENUM("OB01 Bypass Source", rt5677_ob01_bypass_src_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_ob23_bypass_src_enum, RT5677_DSP_IN_OUTB_CTRL, + RT5677_SEL_SRC_OB23_SFT, rt5677_iob_bypass_src); + +static const struct snd_kcontrol_new rt5677_ob23_bypass_src_mux = + SOC_DAPM_ENUM("OB23 Bypass Source", rt5677_ob23_bypass_src_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_ib01_bypass_src_enum, RT5677_DSP_IN_OUTB_CTRL, + RT5677_SEL_SRC_IB01_SFT, rt5677_iob_bypass_src); + +static const struct snd_kcontrol_new rt5677_ib01_bypass_src_mux = + SOC_DAPM_ENUM("IB01 Bypass Source", rt5677_ib01_bypass_src_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_ib23_bypass_src_enum, RT5677_DSP_IN_OUTB_CTRL, + RT5677_SEL_SRC_IB23_SFT, rt5677_iob_bypass_src); + +static const struct snd_kcontrol_new rt5677_ib23_bypass_src_mux = + SOC_DAPM_ENUM("IB23 Bypass Source", rt5677_ib23_bypass_src_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_ib45_bypass_src_enum, RT5677_DSP_IN_OUTB_CTRL, + RT5677_SEL_SRC_IB45_SFT, rt5677_iob_bypass_src); + +static const struct snd_kcontrol_new rt5677_ib45_bypass_src_mux = + SOC_DAPM_ENUM("IB45 Bypass Source", rt5677_ib45_bypass_src_enum); + +/* Stereo ADC Source 2 */ /* MX-27 MX26 MX25 [11:10] */ +static const char * const rt5677_stereo_adc2_src[] = { + "DD MIX1", "DMIC", "Stereo DAC MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo1_adc2_enum, RT5677_STO1_ADC_MIXER, + RT5677_SEL_STO1_ADC2_SFT, rt5677_stereo_adc2_src); + +static const struct snd_kcontrol_new rt5677_sto1_adc2_mux = + SOC_DAPM_ENUM("Stereo1 ADC2 Source", rt5677_stereo1_adc2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo2_adc2_enum, RT5677_STO2_ADC_MIXER, + RT5677_SEL_STO2_ADC2_SFT, rt5677_stereo_adc2_src); + +static const struct snd_kcontrol_new rt5677_sto2_adc2_mux = + SOC_DAPM_ENUM("Stereo2 ADC2 Source", rt5677_stereo2_adc2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo3_adc2_enum, RT5677_STO3_ADC_MIXER, + RT5677_SEL_STO3_ADC2_SFT, rt5677_stereo_adc2_src); + +static const struct snd_kcontrol_new rt5677_sto3_adc2_mux = + SOC_DAPM_ENUM("Stereo3 ADC2 Source", rt5677_stereo3_adc2_enum); + +/* DMIC Source */ /* MX-28 [9:8][1:0] MX-27 MX-26 MX-25 MX-24 [9:8] */ +static const char * const rt5677_dmic_src[] = { + "DMIC1", "DMIC2", "DMIC3", "DMIC4" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_mono_dmic_l_enum, RT5677_MONO_ADC_MIXER, + RT5677_SEL_MONO_DMIC_L_SFT, rt5677_dmic_src); + +static const struct snd_kcontrol_new rt5677_mono_dmic_l_mux = + SOC_DAPM_ENUM("Mono DMIC L Source", rt5677_mono_dmic_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_mono_dmic_r_enum, RT5677_MONO_ADC_MIXER, + RT5677_SEL_MONO_DMIC_R_SFT, rt5677_dmic_src); + +static const struct snd_kcontrol_new rt5677_mono_dmic_r_mux = + SOC_DAPM_ENUM("Mono DMIC R Source", rt5677_mono_dmic_r_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo1_dmic_enum, RT5677_STO1_ADC_MIXER, + RT5677_SEL_STO1_DMIC_SFT, rt5677_dmic_src); + +static const struct snd_kcontrol_new rt5677_sto1_dmic_mux = + SOC_DAPM_ENUM("Stereo1 DMIC Source", rt5677_stereo1_dmic_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo2_dmic_enum, RT5677_STO2_ADC_MIXER, + RT5677_SEL_STO2_DMIC_SFT, rt5677_dmic_src); + +static const struct snd_kcontrol_new rt5677_sto2_dmic_mux = + SOC_DAPM_ENUM("Stereo2 DMIC Source", rt5677_stereo2_dmic_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo3_dmic_enum, RT5677_STO3_ADC_MIXER, + RT5677_SEL_STO3_DMIC_SFT, rt5677_dmic_src); + +static const struct snd_kcontrol_new rt5677_sto3_dmic_mux = + SOC_DAPM_ENUM("Stereo3 DMIC Source", rt5677_stereo3_dmic_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo4_dmic_enum, RT5677_STO4_ADC_MIXER, + RT5677_SEL_STO4_DMIC_SFT, rt5677_dmic_src); + +static const struct snd_kcontrol_new rt5677_sto4_dmic_mux = + SOC_DAPM_ENUM("Stereo4 DMIC Source", rt5677_stereo4_dmic_enum); + +/* Stereo2 ADC Source */ /* MX-26 [0] */ +static const char * const rt5677_stereo2_adc_lr_src[] = { + "L", "LR" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo2_adc_lr_enum, RT5677_STO2_ADC_MIXER, + RT5677_SEL_STO2_LR_MIX_SFT, rt5677_stereo2_adc_lr_src); + +static const struct snd_kcontrol_new rt5677_sto2_adc_lr_mux = + SOC_DAPM_ENUM("Stereo2 ADC LR Source", rt5677_stereo2_adc_lr_enum); + +/* Stereo1 ADC Source 1 */ /* MX-27 MX26 MX25 [13:12] */ +static const char * const rt5677_stereo_adc1_src[] = { + "DD MIX1", "ADC1/2", "Stereo DAC MIX" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo1_adc1_enum, RT5677_STO1_ADC_MIXER, + RT5677_SEL_STO1_ADC1_SFT, rt5677_stereo_adc1_src); + +static const struct snd_kcontrol_new rt5677_sto1_adc1_mux = + SOC_DAPM_ENUM("Stereo1 ADC1 Source", rt5677_stereo1_adc1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo2_adc1_enum, RT5677_STO2_ADC_MIXER, + RT5677_SEL_STO2_ADC1_SFT, rt5677_stereo_adc1_src); + +static const struct snd_kcontrol_new rt5677_sto2_adc1_mux = + SOC_DAPM_ENUM("Stereo2 ADC1 Source", rt5677_stereo2_adc1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo3_adc1_enum, RT5677_STO3_ADC_MIXER, + RT5677_SEL_STO3_ADC1_SFT, rt5677_stereo_adc1_src); + +static const struct snd_kcontrol_new rt5677_sto3_adc1_mux = + SOC_DAPM_ENUM("Stereo3 ADC1 Source", rt5677_stereo3_adc1_enum); + +/* Mono ADC Left Source 2 */ /* MX-28 [11:10] */ +static const char * const rt5677_mono_adc2_l_src[] = { + "DD MIX1L", "DMIC", "MONO DAC MIXL" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_mono_adc2_l_enum, RT5677_MONO_ADC_MIXER, + RT5677_SEL_MONO_ADC_L2_SFT, rt5677_mono_adc2_l_src); + +static const struct snd_kcontrol_new rt5677_mono_adc2_l_mux = + SOC_DAPM_ENUM("Mono ADC2 L Source", rt5677_mono_adc2_l_enum); + +/* Mono ADC Left Source 1 */ /* MX-28 [13:12] */ +static const char * const rt5677_mono_adc1_l_src[] = { + "DD MIX1L", "ADC1", "MONO DAC MIXL" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_mono_adc1_l_enum, RT5677_MONO_ADC_MIXER, + RT5677_SEL_MONO_ADC_L1_SFT, rt5677_mono_adc1_l_src); + +static const struct snd_kcontrol_new rt5677_mono_adc1_l_mux = + SOC_DAPM_ENUM("Mono ADC1 L Source", rt5677_mono_adc1_l_enum); + +/* Mono ADC Right Source 2 */ /* MX-28 [3:2] */ +static const char * const rt5677_mono_adc2_r_src[] = { + "DD MIX1R", "DMIC", "MONO DAC MIXR" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_mono_adc2_r_enum, RT5677_MONO_ADC_MIXER, + RT5677_SEL_MONO_ADC_R2_SFT, rt5677_mono_adc2_r_src); + +static const struct snd_kcontrol_new rt5677_mono_adc2_r_mux = + SOC_DAPM_ENUM("Mono ADC2 R Source", rt5677_mono_adc2_r_enum); + +/* Mono ADC Right Source 1 */ /* MX-28 [5:4] */ +static const char * const rt5677_mono_adc1_r_src[] = { + "DD MIX1R", "ADC2", "MONO DAC MIXR" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_mono_adc1_r_enum, RT5677_MONO_ADC_MIXER, + RT5677_SEL_MONO_ADC_R1_SFT, rt5677_mono_adc1_r_src); + +static const struct snd_kcontrol_new rt5677_mono_adc1_r_mux = + SOC_DAPM_ENUM("Mono ADC1 R Source", rt5677_mono_adc1_r_enum); + +/* Stereo4 ADC Source 2 */ /* MX-24 [11:10] */ +static const char * const rt5677_stereo4_adc2_src[] = { + "DD MIX1", "DMIC", "DD MIX2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo4_adc2_enum, RT5677_STO4_ADC_MIXER, + RT5677_SEL_STO4_ADC2_SFT, rt5677_stereo4_adc2_src); + +static const struct snd_kcontrol_new rt5677_sto4_adc2_mux = + SOC_DAPM_ENUM("Stereo4 ADC2 Source", rt5677_stereo4_adc2_enum); + + +/* Stereo4 ADC Source 1 */ /* MX-24 [13:12] */ +static const char * const rt5677_stereo4_adc1_src[] = { + "DD MIX1", "ADC1/2", "DD MIX2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_stereo4_adc1_enum, RT5677_STO4_ADC_MIXER, + RT5677_SEL_STO4_ADC1_SFT, rt5677_stereo4_adc1_src); + +static const struct snd_kcontrol_new rt5677_sto4_adc1_mux = + SOC_DAPM_ENUM("Stereo4 ADC1 Source", rt5677_stereo4_adc1_enum); + +/* InBound0/1 Source */ /* MX-A3 [14:12] */ +static const char * const rt5677_inbound01_src[] = { + "IF1 DAC 01", "IF2 DAC 01", "SLB DAC 01", "STO1 ADC MIX", + "VAD ADC/DAC1 FS" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_inbound01_enum, RT5677_DSP_INB_CTRL1, + RT5677_IB01_SRC_SFT, rt5677_inbound01_src); + +static const struct snd_kcontrol_new rt5677_ib01_src_mux = + SOC_DAPM_ENUM("InBound0/1 Source", rt5677_inbound01_enum); + +/* InBound2/3 Source */ /* MX-A3 [10:8] */ +static const char * const rt5677_inbound23_src[] = { + "IF1 DAC 23", "IF2 DAC 23", "SLB DAC 23", "STO2 ADC MIX", + "DAC1 FS", "IF4 DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_inbound23_enum, RT5677_DSP_INB_CTRL1, + RT5677_IB23_SRC_SFT, rt5677_inbound23_src); + +static const struct snd_kcontrol_new rt5677_ib23_src_mux = + SOC_DAPM_ENUM("InBound2/3 Source", rt5677_inbound23_enum); + +/* InBound4/5 Source */ /* MX-A3 [6:4] */ +static const char * const rt5677_inbound45_src[] = { + "IF1 DAC 45", "IF2 DAC 45", "SLB DAC 45", "STO3 ADC MIX", + "IF3 DAC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_inbound45_enum, RT5677_DSP_INB_CTRL1, + RT5677_IB45_SRC_SFT, rt5677_inbound45_src); + +static const struct snd_kcontrol_new rt5677_ib45_src_mux = + SOC_DAPM_ENUM("InBound4/5 Source", rt5677_inbound45_enum); + +/* InBound6 Source */ /* MX-A3 [2:0] */ +static const char * const rt5677_inbound6_src[] = { + "IF1 DAC 6", "IF2 DAC 6", "SLB DAC 6", "STO4 ADC MIX L", + "IF4 DAC L", "STO1 ADC MIX L", "STO2 ADC MIX L", "STO3 ADC MIX L" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_inbound6_enum, RT5677_DSP_INB_CTRL1, + RT5677_IB6_SRC_SFT, rt5677_inbound6_src); + +static const struct snd_kcontrol_new rt5677_ib6_src_mux = + SOC_DAPM_ENUM("InBound6 Source", rt5677_inbound6_enum); + +/* InBound7 Source */ /* MX-A4 [14:12] */ +static const char * const rt5677_inbound7_src[] = { + "IF1 DAC 7", "IF2 DAC 7", "SLB DAC 7", "STO4 ADC MIX R", + "IF4 DAC R", "STO1 ADC MIX R", "STO2 ADC MIX R", "STO3 ADC MIX R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_inbound7_enum, RT5677_DSP_INB_CTRL2, + RT5677_IB7_SRC_SFT, rt5677_inbound7_src); + +static const struct snd_kcontrol_new rt5677_ib7_src_mux = + SOC_DAPM_ENUM("InBound7 Source", rt5677_inbound7_enum); + +/* InBound8 Source */ /* MX-A4 [10:8] */ +static const char * const rt5677_inbound8_src[] = { + "STO1 ADC MIX L", "STO2 ADC MIX L", "STO3 ADC MIX L", "STO4 ADC MIX L", + "MONO ADC MIX L", "DACL1 FS" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_inbound8_enum, RT5677_DSP_INB_CTRL2, + RT5677_IB8_SRC_SFT, rt5677_inbound8_src); + +static const struct snd_kcontrol_new rt5677_ib8_src_mux = + SOC_DAPM_ENUM("InBound8 Source", rt5677_inbound8_enum); + +/* InBound9 Source */ /* MX-A4 [6:4] */ +static const char * const rt5677_inbound9_src[] = { + "STO1 ADC MIX R", "STO2 ADC MIX R", "STO3 ADC MIX R", "STO4 ADC MIX R", + "MONO ADC MIX R", "DACR1 FS", "DAC1 FS" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_inbound9_enum, RT5677_DSP_INB_CTRL2, + RT5677_IB9_SRC_SFT, rt5677_inbound9_src); + +static const struct snd_kcontrol_new rt5677_ib9_src_mux = + SOC_DAPM_ENUM("InBound9 Source", rt5677_inbound9_enum); + +/* VAD Source */ /* MX-9F [6:4] */ +static const char * const rt5677_vad_src[] = { + "STO1 ADC MIX L", "MONO ADC MIX L", "MONO ADC MIX R", "STO2 ADC MIX L", + "STO3 ADC MIX L" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_vad_enum, RT5677_VAD_CTRL4, + RT5677_VAD_SRC_SFT, rt5677_vad_src); + +static const struct snd_kcontrol_new rt5677_vad_src_mux = + SOC_DAPM_ENUM("VAD Source", rt5677_vad_enum); + +/* Sidetone Source */ /* MX-13 [11:9] */ +static const char * const rt5677_sidetone_src[] = { + "DMIC1 L", "DMIC2 L", "DMIC3 L", "DMIC4 L", "ADC1", "ADC2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_sidetone_enum, RT5677_SIDETONE_CTRL, + RT5677_ST_SEL_SFT, rt5677_sidetone_src); + +static const struct snd_kcontrol_new rt5677_sidetone_mux = + SOC_DAPM_ENUM("Sidetone Source", rt5677_sidetone_enum); + +/* DAC1/2 Source */ /* MX-15 [1:0] */ +static const char * const rt5677_dac12_src[] = { + "STO1 DAC MIX", "MONO DAC MIX", "DD MIX1", "DD MIX2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac12_enum, RT5677_ANA_DAC1_2_3_SRC, + RT5677_ANA_DAC1_2_SRC_SEL_SFT, rt5677_dac12_src); + +static const struct snd_kcontrol_new rt5677_dac12_mux = + SOC_DAPM_ENUM("Analog DAC1/2 Source", rt5677_dac12_enum); + +/* DAC3 Source */ /* MX-15 [5:4] */ +static const char * const rt5677_dac3_src[] = { + "MONO DAC MIXL", "MONO DAC MIXR", "DD MIX1L", "DD MIX2L" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_dac3_enum, RT5677_ANA_DAC1_2_3_SRC, + RT5677_ANA_DAC3_SRC_SEL_SFT, rt5677_dac3_src); + +static const struct snd_kcontrol_new rt5677_dac3_mux = + SOC_DAPM_ENUM("Analog DAC3 Source", rt5677_dac3_enum); + +/* PDM channel Source */ /* MX-31 [13:12][9:8][5:4][1:0] */ +static const char * const rt5677_pdm_src[] = { + "STO1 DAC MIX", "MONO DAC MIX", "DD MIX1", "DD MIX2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_pdm1_l_enum, RT5677_PDM_OUT_CTRL, + RT5677_SEL_PDM1_L_SFT, rt5677_pdm_src); + +static const struct snd_kcontrol_new rt5677_pdm1_l_mux = + SOC_DAPM_ENUM("PDM1 Source", rt5677_pdm1_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_pdm2_l_enum, RT5677_PDM_OUT_CTRL, + RT5677_SEL_PDM2_L_SFT, rt5677_pdm_src); + +static const struct snd_kcontrol_new rt5677_pdm2_l_mux = + SOC_DAPM_ENUM("PDM2 Source", rt5677_pdm2_l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_pdm1_r_enum, RT5677_PDM_OUT_CTRL, + RT5677_SEL_PDM1_R_SFT, rt5677_pdm_src); + +static const struct snd_kcontrol_new rt5677_pdm1_r_mux = + SOC_DAPM_ENUM("PDM1 Source", rt5677_pdm1_r_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_pdm2_r_enum, RT5677_PDM_OUT_CTRL, + RT5677_SEL_PDM2_R_SFT, rt5677_pdm_src); + +static const struct snd_kcontrol_new rt5677_pdm2_r_mux = + SOC_DAPM_ENUM("PDM2 Source", rt5677_pdm2_r_enum); + +/* TDM IF1/2 SLB ADC1 Data Selection */ /* MX-3C MX-41 [5:4] MX-08 [1:0] */ +static const char * const rt5677_if12_adc1_src[] = { + "STO1 ADC MIX", "OB01", "VAD ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc1_enum, RT5677_TDM1_CTRL2, + RT5677_IF1_ADC1_SFT, rt5677_if12_adc1_src); + +static const struct snd_kcontrol_new rt5677_if1_adc1_mux = + SOC_DAPM_ENUM("IF1 ADC1 Source", rt5677_if1_adc1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc1_enum, RT5677_TDM2_CTRL2, + RT5677_IF2_ADC1_SFT, rt5677_if12_adc1_src); + +static const struct snd_kcontrol_new rt5677_if2_adc1_mux = + SOC_DAPM_ENUM("IF2 ADC1 Source", rt5677_if2_adc1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_slb_adc1_enum, RT5677_SLIMBUS_RX, + RT5677_SLB_ADC1_SFT, rt5677_if12_adc1_src); + +static const struct snd_kcontrol_new rt5677_slb_adc1_mux = + SOC_DAPM_ENUM("SLB ADC1 Source", rt5677_slb_adc1_enum); + +/* TDM IF1/2 SLB ADC2 Data Selection */ /* MX-3C MX-41 [7:6] MX-08 [3:2] */ +static const char * const rt5677_if12_adc2_src[] = { + "STO2 ADC MIX", "OB23" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc2_enum, RT5677_TDM1_CTRL2, + RT5677_IF1_ADC2_SFT, rt5677_if12_adc2_src); + +static const struct snd_kcontrol_new rt5677_if1_adc2_mux = + SOC_DAPM_ENUM("IF1 ADC2 Source", rt5677_if1_adc2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc2_enum, RT5677_TDM2_CTRL2, + RT5677_IF2_ADC2_SFT, rt5677_if12_adc2_src); + +static const struct snd_kcontrol_new rt5677_if2_adc2_mux = + SOC_DAPM_ENUM("IF2 ADC2 Source", rt5677_if2_adc2_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_slb_adc2_enum, RT5677_SLIMBUS_RX, + RT5677_SLB_ADC2_SFT, rt5677_if12_adc2_src); + +static const struct snd_kcontrol_new rt5677_slb_adc2_mux = + SOC_DAPM_ENUM("SLB ADC2 Source", rt5677_slb_adc2_enum); + +/* TDM IF1/2 SLB ADC3 Data Selection */ /* MX-3C MX-41 [9:8] MX-08 [5:4] */ +static const char * const rt5677_if12_adc3_src[] = { + "STO3 ADC MIX", "MONO ADC MIX", "OB45" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc3_enum, RT5677_TDM1_CTRL2, + RT5677_IF1_ADC3_SFT, rt5677_if12_adc3_src); + +static const struct snd_kcontrol_new rt5677_if1_adc3_mux = + SOC_DAPM_ENUM("IF1 ADC3 Source", rt5677_if1_adc3_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc3_enum, RT5677_TDM2_CTRL2, + RT5677_IF2_ADC3_SFT, rt5677_if12_adc3_src); + +static const struct snd_kcontrol_new rt5677_if2_adc3_mux = + SOC_DAPM_ENUM("IF2 ADC3 Source", rt5677_if2_adc3_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_slb_adc3_enum, RT5677_SLIMBUS_RX, + RT5677_SLB_ADC3_SFT, rt5677_if12_adc3_src); + +static const struct snd_kcontrol_new rt5677_slb_adc3_mux = + SOC_DAPM_ENUM("SLB ADC3 Source", rt5677_slb_adc3_enum); + +/* TDM IF1/2 SLB ADC4 Data Selection */ /* MX-3C MX-41 [11:10] MX-08 [7:6] */ +static const char * const rt5677_if12_adc4_src[] = { + "STO4 ADC MIX", "OB67", "OB01" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc4_enum, RT5677_TDM1_CTRL2, + RT5677_IF1_ADC4_SFT, rt5677_if12_adc4_src); + +static const struct snd_kcontrol_new rt5677_if1_adc4_mux = + SOC_DAPM_ENUM("IF1 ADC4 Source", rt5677_if1_adc4_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc4_enum, RT5677_TDM2_CTRL2, + RT5677_IF2_ADC4_SFT, rt5677_if12_adc4_src); + +static const struct snd_kcontrol_new rt5677_if2_adc4_mux = + SOC_DAPM_ENUM("IF2 ADC4 Source", rt5677_if2_adc4_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_slb_adc4_enum, RT5677_SLIMBUS_RX, + RT5677_SLB_ADC4_SFT, rt5677_if12_adc4_src); + +static const struct snd_kcontrol_new rt5677_slb_adc4_mux = + SOC_DAPM_ENUM("SLB ADC4 Source", rt5677_slb_adc4_enum); + +/* Interface3/4 ADC Data Input */ /* MX-2F [3:0] MX-30 [7:4] */ +static const char * const rt5677_if34_adc_src[] = { + "STO1 ADC MIX", "STO2 ADC MIX", "STO3 ADC MIX", "STO4 ADC MIX", + "MONO ADC MIX", "OB01", "OB23", "VAD ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if3_adc_enum, RT5677_IF3_DATA, + RT5677_IF3_ADC_IN_SFT, rt5677_if34_adc_src); + +static const struct snd_kcontrol_new rt5677_if3_adc_mux = + SOC_DAPM_ENUM("IF3 ADC Source", rt5677_if3_adc_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if4_adc_enum, RT5677_IF4_DATA, + RT5677_IF4_ADC_IN_SFT, rt5677_if34_adc_src); + +static const struct snd_kcontrol_new rt5677_if4_adc_mux = + SOC_DAPM_ENUM("IF4 ADC Source", rt5677_if4_adc_enum); + +/* TDM IF1/2 ADC Data Selection */ /* MX-3B MX-40 [7:6][5:4][3:2][1:0] */ +static const char * const rt5677_if12_adc_swap_src[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc1_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC1_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc1_swap_mux = + SOC_DAPM_ENUM("IF1 ADC1 Swap Source", rt5677_if1_adc1_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc2_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC2_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc2_swap_mux = + SOC_DAPM_ENUM("IF1 ADC2 Swap Source", rt5677_if1_adc2_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc3_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC3_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc3_swap_mux = + SOC_DAPM_ENUM("IF1 ADC3 Swap Source", rt5677_if1_adc3_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc4_swap_enum, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC4_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc4_swap_mux = + SOC_DAPM_ENUM("IF1 ADC4 Swap Source", rt5677_if1_adc4_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc1_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF1_ADC2_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc1_swap_mux = + SOC_DAPM_ENUM("IF1 ADC2 Swap Source", rt5677_if2_adc1_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc2_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC2_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc2_swap_mux = + SOC_DAPM_ENUM("IF2 ADC2 Swap Source", rt5677_if2_adc2_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc3_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC3_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc3_swap_mux = + SOC_DAPM_ENUM("IF2 ADC3 Swap Source", rt5677_if2_adc3_swap_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc4_swap_enum, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC4_SWAP_SFT, rt5677_if12_adc_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc4_swap_mux = + SOC_DAPM_ENUM("IF2 ADC4 Swap Source", rt5677_if2_adc4_swap_enum); + +/* TDM IF1 ADC Data Selection */ /* MX-3C [2:0] */ +static const char * const rt5677_if1_adc_tdm_swap_src[] = { + "1/2/3/4", "2/1/3/4", "2/3/1/4", "4/1/2/3", "1/3/2/4", "1/4/2/3", + "3/1/2/4", "3/4/1/2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_adc_tdm_swap_enum, RT5677_TDM1_CTRL2, + RT5677_IF1_ADC_CTRL_SFT, rt5677_if1_adc_tdm_swap_src); + +static const struct snd_kcontrol_new rt5677_if1_adc_tdm_swap_mux = + SOC_DAPM_ENUM("IF1 ADC TDM Swap Source", rt5677_if1_adc_tdm_swap_enum); + +/* TDM IF2 ADC Data Selection */ /* MX-41[2:0] */ +static const char * const rt5677_if2_adc_tdm_swap_src[] = { + "1/2/3/4", "2/1/3/4", "3/1/2/4", "4/1/2/3", "1/3/2/4", "1/4/2/3", + "2/3/1/4", "3/4/1/2" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_adc_tdm_swap_enum, RT5677_TDM2_CTRL2, + RT5677_IF2_ADC_CTRL_SFT, rt5677_if2_adc_tdm_swap_src); + +static const struct snd_kcontrol_new rt5677_if2_adc_tdm_swap_mux = + SOC_DAPM_ENUM("IF2 ADC TDM Swap Source", rt5677_if2_adc_tdm_swap_enum); + +/* TDM IF1/2 DAC Data Selection */ /* MX-3E[14:12][10:8][6:4][2:0] + MX-3F[14:12][10:8][6:4][2:0] + MX-43[14:12][10:8][6:4][2:0] + MX-44[14:12][10:8][6:4][2:0] */ +static const char * const rt5677_if12_dac_tdm_sel_src[] = { + "Slot0", "Slot1", "Slot2", "Slot3", "Slot4", "Slot5", "Slot6", "Slot7" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac0_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC0_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac0_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC0 TDM Source", rt5677_if1_dac0_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac1_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC1_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac1_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC1 TDM Source", rt5677_if1_dac1_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac2_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC2_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac2_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC2 TDM Source", rt5677_if1_dac2_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac3_tdm_sel_enum, RT5677_TDM1_CTRL4, + RT5677_IF1_DAC3_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac3_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC3 TDM Source", rt5677_if1_dac3_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac4_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC4_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac4_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC4 TDM Source", rt5677_if1_dac4_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac5_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC5_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac5_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC5 TDM Source", rt5677_if1_dac5_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac6_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC6_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac6_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC6 TDM Source", rt5677_if1_dac6_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if1_dac7_tdm_sel_enum, RT5677_TDM1_CTRL5, + RT5677_IF1_DAC7_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if1_dac7_tdm_sel_mux = + SOC_DAPM_ENUM("IF1 DAC7 TDM Source", rt5677_if1_dac7_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac0_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC0_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac0_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC0 TDM Source", rt5677_if2_dac0_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac1_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC1_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac1_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC1 TDM Source", rt5677_if2_dac1_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac2_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC2_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac2_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC2 TDM Source", rt5677_if2_dac2_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac3_tdm_sel_enum, RT5677_TDM2_CTRL4, + RT5677_IF2_DAC3_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac3_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC3 TDM Source", rt5677_if2_dac3_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac4_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC4_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac4_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC4 TDM Source", rt5677_if2_dac4_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac5_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC5_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac5_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC5 TDM Source", rt5677_if2_dac5_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac6_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC6_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac6_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC6 TDM Source", rt5677_if2_dac6_tdm_sel_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5677_if2_dac7_tdm_sel_enum, RT5677_TDM2_CTRL5, + RT5677_IF2_DAC7_SFT, rt5677_if12_dac_tdm_sel_src); + +static const struct snd_kcontrol_new rt5677_if2_dac7_tdm_sel_mux = + SOC_DAPM_ENUM("IF2 DAC7 TDM Source", rt5677_if2_dac7_tdm_sel_enum); + +static int rt5677_bst1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_BST1_P, RT5677_PWR_BST1_P); + break; + + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_BST1_P, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_bst2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_BST2_P, RT5677_PWR_BST2_P); + break; + + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_BST2_P, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_set_pll1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(rt5677->regmap, RT5677_PLL1_CTRL2, 0x2, 0x2); + break; + + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rt5677->regmap, RT5677_PLL1_CTRL2, 0x2, 0x0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_set_pll2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(rt5677->regmap, RT5677_PLL2_CTRL2, 0x2, 0x2); + break; + + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rt5677->regmap, RT5677_PLL2_CTRL2, 0x2, 0x0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_set_micbias1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_CLK_MB1 | RT5677_PWR_PP_MB1 | + RT5677_PWR_CLK_MB, RT5677_PWR_CLK_MB1 | + RT5677_PWR_PP_MB1 | RT5677_PWR_CLK_MB); + break; + + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_CLK_MB1 | RT5677_PWR_PP_MB1 | + RT5677_PWR_CLK_MB, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_if1_adc_tdm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int value; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_read(rt5677->regmap, RT5677_TDM1_CTRL2, &value); + if (value & RT5677_IF1_ADC_CTRL_MASK) + regmap_update_bits(rt5677->regmap, RT5677_TDM1_CTRL1, + RT5677_IF1_ADC_MODE_MASK, + RT5677_IF1_ADC_MODE_TDM); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_if2_adc_tdm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int value; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_read(rt5677->regmap, RT5677_TDM2_CTRL2, &value); + if (value & RT5677_IF2_ADC_CTRL_MASK) + regmap_update_bits(rt5677->regmap, RT5677_TDM2_CTRL1, + RT5677_IF2_ADC_MODE_MASK, + RT5677_IF2_ADC_MODE_TDM); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_vref_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_ON && + !rt5677->is_vref_slow) { + mdelay(20); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, + RT5677_PWR_FV1 | RT5677_PWR_FV2, + RT5677_PWR_FV1 | RT5677_PWR_FV2); + rt5677->is_vref_slow = true; + } + break; + + default: + return 0; + } + + return 0; +} + +static int rt5677_filter_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(50); + break; + + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt5677_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("PLL1", RT5677_PWR_ANLG2, RT5677_PWR_PLL1_BIT, + 0, rt5677_set_pll1_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("PLL2", RT5677_PWR_ANLG2, RT5677_PWR_PLL2_BIT, + 0, rt5677_set_pll2_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("I2S1 ASRC", 1, RT5677_ASRC_1, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5677_ASRC_1, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S3 ASRC", 1, RT5677_ASRC_1, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S4 ASRC", 1, RT5677_ASRC_1, 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC STO ASRC", 1, RT5677_ASRC_2, 14, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO2 L ASRC", 1, RT5677_ASRC_2, 13, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO2 R ASRC", 1, RT5677_ASRC_2, 12, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO3 L ASRC", 1, RT5677_ASRC_1, 15, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO3 R ASRC", 1, RT5677_ASRC_1, 14, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO4 L ASRC", 1, RT5677_ASRC_1, 13, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DAC MONO4 R ASRC", 1, RT5677_ASRC_1, 12, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO1 ASRC", 1, RT5677_ASRC_2, 11, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO2 ASRC", 1, RT5677_ASRC_2, 10, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO3 ASRC", 1, RT5677_ASRC_2, 9, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DMIC STO4 ASRC", 1, RT5677_ASRC_2, 8, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO L ASRC", 1, RT5677_ASRC_2, 7, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("DMIC MONO R ASRC", 1, RT5677_ASRC_2, 6, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5677_ASRC_2, 5, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO2 ASRC", 1, RT5677_ASRC_2, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO3 ASRC", 1, RT5677_ASRC_2, 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO4 ASRC", 1, RT5677_ASRC_2, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO L ASRC", 1, RT5677_ASRC_2, 1, 0, NULL, + 0), + SND_SOC_DAPM_SUPPLY_S("ADC MONO R ASRC", 1, RT5677_ASRC_2, 0, 0, NULL, + 0), + + /* Input Side */ + /* micbias */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5677_PWR_ANLG2, RT5677_PWR_MB1_BIT, + 0, rt5677_set_micbias1_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + SND_SOC_DAPM_INPUT("DMIC L2"), + SND_SOC_DAPM_INPUT("DMIC R2"), + SND_SOC_DAPM_INPUT("DMIC L3"), + SND_SOC_DAPM_INPUT("DMIC R3"), + SND_SOC_DAPM_INPUT("DMIC L4"), + SND_SOC_DAPM_INPUT("DMIC R4"), + + SND_SOC_DAPM_INPUT("IN1P"), + SND_SOC_DAPM_INPUT("IN1N"), + SND_SOC_DAPM_INPUT("IN2P"), + SND_SOC_DAPM_INPUT("IN2N"), + + SND_SOC_DAPM_INPUT("Haptic Generator"), + + SND_SOC_DAPM_PGA("DMIC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DMIC4", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DMIC1 power", RT5677_DMIC_CTRL1, + RT5677_DMIC_1_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC2 power", RT5677_DMIC_CTRL1, + RT5677_DMIC_2_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC3 power", RT5677_DMIC_CTRL1, + RT5677_DMIC_3_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DMIC4 power", RT5677_DMIC_CTRL2, + RT5677_DMIC_4_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + + /* Boost */ + SND_SOC_DAPM_PGA_E("BST1", RT5677_PWR_ANLG2, + RT5677_PWR_BST1_BIT, 0, NULL, 0, rt5677_bst1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("BST2", RT5677_PWR_ANLG2, + RT5677_PWR_BST2_BIT, 0, NULL, 0, rt5677_bst2_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC 1", NULL, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_ADC("ADC 2", NULL, SND_SOC_NOPM, + 0, 0), + SND_SOC_DAPM_PGA("ADC 1_2", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("ADC 1 power", RT5677_PWR_DIG1, + RT5677_PWR_ADC_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC 2 power", RT5677_PWR_DIG1, + RT5677_PWR_ADC_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 clock", RT5677_PWR_DIG1, + RT5677_PWR_ADCFED1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 clock", RT5677_PWR_DIG1, + RT5677_PWR_ADCFED2_BIT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto1_dmic_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto1_adc1_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto1_adc2_mux), + SND_SOC_DAPM_MUX("Stereo2 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto2_dmic_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto2_adc1_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto2_adc2_mux), + SND_SOC_DAPM_MUX("Stereo2 ADC LR Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto2_adc_lr_mux), + SND_SOC_DAPM_MUX("Stereo3 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto3_dmic_mux), + SND_SOC_DAPM_MUX("Stereo3 ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto3_adc1_mux), + SND_SOC_DAPM_MUX("Stereo3 ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto3_adc2_mux), + SND_SOC_DAPM_MUX("Stereo4 DMIC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto4_dmic_mux), + SND_SOC_DAPM_MUX("Stereo4 ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto4_adc1_mux), + SND_SOC_DAPM_MUX("Stereo4 ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sto4_adc2_mux), + SND_SOC_DAPM_MUX("Mono DMIC L Mux", SND_SOC_NOPM, 0, 0, + &rt5677_mono_dmic_l_mux), + SND_SOC_DAPM_MUX("Mono DMIC R Mux", SND_SOC_NOPM, 0, 0, + &rt5677_mono_dmic_r_mux), + SND_SOC_DAPM_MUX("Mono ADC2 L Mux", SND_SOC_NOPM, 0, 0, + &rt5677_mono_adc2_l_mux), + SND_SOC_DAPM_MUX("Mono ADC1 L Mux", SND_SOC_NOPM, 0, 0, + &rt5677_mono_adc1_l_mux), + SND_SOC_DAPM_MUX("Mono ADC1 R Mux", SND_SOC_NOPM, 0, 0, + &rt5677_mono_adc1_r_mux), + SND_SOC_DAPM_MUX("Mono ADC2 R Mux", SND_SOC_NOPM, 0, 0, + &rt5677_mono_adc2_r_mux), + + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("adc stereo1 filter", RT5677_PWR_DIG2, + RT5677_PWR_ADC_S1F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("adc stereo2 filter", RT5677_PWR_DIG2, + RT5677_PWR_ADC_S2F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("adc stereo3 filter", RT5677_PWR_DIG2, + RT5677_PWR_ADC_S3F_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("adc stereo4 filter", RT5677_PWR_DIG2, + RT5677_PWR_ADC_S4F_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Sto1 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5677_sto1_adc_l_mix, ARRAY_SIZE(rt5677_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto1 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5677_sto1_adc_r_mix, ARRAY_SIZE(rt5677_sto1_adc_r_mix)), + SND_SOC_DAPM_MIXER("Sto2 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5677_sto2_adc_l_mix, ARRAY_SIZE(rt5677_sto2_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto2 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5677_sto2_adc_r_mix, ARRAY_SIZE(rt5677_sto2_adc_r_mix)), + SND_SOC_DAPM_MIXER("Sto3 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5677_sto3_adc_l_mix, ARRAY_SIZE(rt5677_sto3_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto3 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5677_sto3_adc_r_mix, ARRAY_SIZE(rt5677_sto3_adc_r_mix)), + SND_SOC_DAPM_MIXER("Sto4 ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5677_sto4_adc_l_mix, ARRAY_SIZE(rt5677_sto4_adc_l_mix)), + SND_SOC_DAPM_MIXER("Sto4 ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5677_sto4_adc_r_mix, ARRAY_SIZE(rt5677_sto4_adc_r_mix)), + SND_SOC_DAPM_SUPPLY("adc mono left filter", RT5677_PWR_DIG2, + RT5677_PWR_ADC_MF_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXL", SND_SOC_NOPM, 0, 0, + rt5677_mono_adc_l_mix, ARRAY_SIZE(rt5677_mono_adc_l_mix)), + SND_SOC_DAPM_SUPPLY("adc mono right filter", RT5677_PWR_DIG2, + RT5677_PWR_ADC_MF_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Mono ADC MIXR", SND_SOC_NOPM, 0, 0, + rt5677_mono_adc_r_mix, ARRAY_SIZE(rt5677_mono_adc_r_mix)), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIXL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo1 ADC MIXR", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo1 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIXL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIXR", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo2 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo3 ADC MIXL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo3 ADC MIXR", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo3 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo4 ADC MIXL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo4 ADC MIXR", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Stereo4 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Sto2 ADC LR MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DSP */ + SND_SOC_DAPM_MUX("IB9 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib9_src_mux), + SND_SOC_DAPM_MUX("IB8 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib8_src_mux), + SND_SOC_DAPM_MUX("IB7 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib7_src_mux), + SND_SOC_DAPM_MUX("IB6 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib6_src_mux), + SND_SOC_DAPM_MUX("IB45 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib45_src_mux), + SND_SOC_DAPM_MUX("IB23 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib23_src_mux), + SND_SOC_DAPM_MUX("IB01 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib01_src_mux), + SND_SOC_DAPM_MUX("IB45 Bypass Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib45_bypass_src_mux), + SND_SOC_DAPM_MUX("IB23 Bypass Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib23_bypass_src_mux), + SND_SOC_DAPM_MUX("IB01 Bypass Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ib01_bypass_src_mux), + SND_SOC_DAPM_MUX("OB23 Bypass Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ob23_bypass_src_mux), + SND_SOC_DAPM_MUX("OB01 Bypass Mux", SND_SOC_NOPM, 0, 0, + &rt5677_ob01_bypass_src_mux), + + SND_SOC_DAPM_PGA("OB45", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OB67", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("OutBound2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OutBound3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OutBound4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OutBound5", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OutBound6", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("OutBound7", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5677_PWR_DIG1, + RT5677_PWR_I2S1_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC5", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC6", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC7", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC01", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC23", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC45", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC67", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 ADC4", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("I2S2", RT5677_PWR_DIG1, + RT5677_PWR_I2S2_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC5", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC6", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC7", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC01", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC23", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC45", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 DAC67", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF2 ADC4", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("I2S3", RT5677_PWR_DIG1, + RT5677_PWR_I2S3_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF3 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("I2S4", RT5677_PWR_DIG1, + RT5677_PWR_I2S4_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF4 DAC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF4 DAC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF4 DAC R", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF4 ADC", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF4 ADC L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF4 ADC R", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("SLB", RT5677_PWR_DIG1, + RT5677_PWR_SLB_BIT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC0", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC4", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC5", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC6", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC7", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC01", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC23", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC45", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB DAC67", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB ADC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB ADC2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB ADC3", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("SLB ADC4", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("IF1 ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc1_mux), + SND_SOC_DAPM_MUX("IF1 ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc2_mux), + SND_SOC_DAPM_MUX("IF1 ADC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc3_mux), + SND_SOC_DAPM_MUX("IF1 ADC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc4_mux), + SND_SOC_DAPM_MUX("IF1 ADC1 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc1_swap_mux), + SND_SOC_DAPM_MUX("IF1 ADC2 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc2_swap_mux), + SND_SOC_DAPM_MUX("IF1 ADC3 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc3_swap_mux), + SND_SOC_DAPM_MUX("IF1 ADC4 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc4_swap_mux), + SND_SOC_DAPM_MUX_E("IF1 ADC TDM Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_adc_tdm_swap_mux, rt5677_if1_adc_tdm_event, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX("IF2 ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc1_mux), + SND_SOC_DAPM_MUX("IF2 ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc2_mux), + SND_SOC_DAPM_MUX("IF2 ADC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc3_mux), + SND_SOC_DAPM_MUX("IF2 ADC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc4_mux), + SND_SOC_DAPM_MUX("IF2 ADC1 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc1_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC2 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc2_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC3 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc3_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC4 Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc4_swap_mux), + SND_SOC_DAPM_MUX_E("IF2 ADC TDM Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_adc_tdm_swap_mux, rt5677_if2_adc_tdm_event, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX("IF3 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if3_adc_mux), + SND_SOC_DAPM_MUX("IF4 ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if4_adc_mux), + SND_SOC_DAPM_MUX("SLB ADC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_slb_adc1_mux), + SND_SOC_DAPM_MUX("SLB ADC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_slb_adc2_mux), + SND_SOC_DAPM_MUX("SLB ADC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_slb_adc3_mux), + SND_SOC_DAPM_MUX("SLB ADC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_slb_adc4_mux), + + SND_SOC_DAPM_MUX("IF1 DAC0 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac0_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac1_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac2_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac3_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac4_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC5 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac5_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC6 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac6_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF1 DAC7 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if1_dac7_tdm_sel_mux), + + SND_SOC_DAPM_MUX("IF2 DAC0 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac0_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac1_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC2 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac2_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC3 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac3_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC4 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac4_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC5 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac5_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC6 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac6_tdm_sel_mux), + SND_SOC_DAPM_MUX("IF2 DAC7 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_if2_dac7_tdm_sel_mux), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF3RX", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF3TX", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF4RX", "AIF4 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF4TX", "AIF4 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLBRX", "SLIMBus Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLBTX", "SLIMBus Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Sidetone Mux */ + SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, + &rt5677_sidetone_mux), + SND_SOC_DAPM_SUPPLY("Sidetone Power", RT5677_SIDETONE_CTRL, + RT5677_ST_EN_SFT, 0, NULL, 0), + + /* VAD Mux*/ + SND_SOC_DAPM_MUX("VAD ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_vad_src_mux), + + /* Tensilica DSP */ + SND_SOC_DAPM_PGA("Tensilica DSP", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("OB01 MIX", SND_SOC_NOPM, 0, 0, + rt5677_ob_01_mix, ARRAY_SIZE(rt5677_ob_01_mix)), + SND_SOC_DAPM_MIXER("OB23 MIX", SND_SOC_NOPM, 0, 0, + rt5677_ob_23_mix, ARRAY_SIZE(rt5677_ob_23_mix)), + SND_SOC_DAPM_MIXER("OB4 MIX", SND_SOC_NOPM, 0, 0, + rt5677_ob_4_mix, ARRAY_SIZE(rt5677_ob_4_mix)), + SND_SOC_DAPM_MIXER("OB5 MIX", SND_SOC_NOPM, 0, 0, + rt5677_ob_5_mix, ARRAY_SIZE(rt5677_ob_5_mix)), + SND_SOC_DAPM_MIXER("OB6 MIX", SND_SOC_NOPM, 0, 0, + rt5677_ob_6_mix, ARRAY_SIZE(rt5677_ob_6_mix)), + SND_SOC_DAPM_MIXER("OB7 MIX", SND_SOC_NOPM, 0, 0, + rt5677_ob_7_mix, ARRAY_SIZE(rt5677_ob_7_mix)), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5677_dac_l_mix, ARRAY_SIZE(rt5677_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5677_dac_r_mix, ARRAY_SIZE(rt5677_dac_r_mix)), + SND_SOC_DAPM_PGA("DAC1 FS", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DAC Mux */ + SND_SOC_DAPM_MUX("DAC1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac1_mux), + SND_SOC_DAPM_MUX("ADDA1 Mux", SND_SOC_NOPM, 0, 0, + &rt5677_adda1_mux), + SND_SOC_DAPM_MUX("DAC12 SRC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac12_mux), + SND_SOC_DAPM_MUX("DAC3 SRC Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac3_mux), + + /* DAC2 channel Mux */ + SND_SOC_DAPM_MUX("DAC2 L Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac2_l_mux), + SND_SOC_DAPM_MUX("DAC2 R Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac2_r_mux), + + /* DAC3 channel Mux */ + SND_SOC_DAPM_MUX("DAC3 L Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac3_l_mux), + SND_SOC_DAPM_MUX("DAC3 R Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac3_r_mux), + + /* DAC4 channel Mux */ + SND_SOC_DAPM_MUX("DAC4 L Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac4_l_mux), + SND_SOC_DAPM_MUX("DAC4 R Mux", SND_SOC_NOPM, 0, 0, + &rt5677_dac4_r_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("dac stereo1 filter", RT5677_PWR_DIG2, + RT5677_PWR_DAC_S1F_BIT, 0, rt5677_filter_power_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("dac mono2 left filter", RT5677_PWR_DIG2, + RT5677_PWR_DAC_M2F_L_BIT, 0, rt5677_filter_power_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("dac mono2 right filter", RT5677_PWR_DIG2, + RT5677_PWR_DAC_M2F_R_BIT, 0, rt5677_filter_power_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("dac mono3 left filter", RT5677_PWR_DIG2, + RT5677_PWR_DAC_M3F_L_BIT, 0, rt5677_filter_power_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("dac mono3 right filter", RT5677_PWR_DIG2, + RT5677_PWR_DAC_M3F_R_BIT, 0, rt5677_filter_power_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("dac mono4 left filter", RT5677_PWR_DIG2, + RT5677_PWR_DAC_M4F_L_BIT, 0, rt5677_filter_power_event, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("dac mono4 right filter", RT5677_PWR_DIG2, + RT5677_PWR_DAC_M4F_R_BIT, 0, rt5677_filter_power_event, + SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_MIXER("Stereo DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5677_sto1_dac_l_mix, ARRAY_SIZE(rt5677_sto1_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5677_sto1_dac_r_mix, ARRAY_SIZE(rt5677_sto1_dac_r_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5677_mono_dac_l_mix, ARRAY_SIZE(rt5677_mono_dac_l_mix)), + SND_SOC_DAPM_MIXER("Mono DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5677_mono_dac_r_mix, ARRAY_SIZE(rt5677_mono_dac_r_mix)), + SND_SOC_DAPM_MIXER("DD1 MIXL", SND_SOC_NOPM, 0, 0, + rt5677_dd1_l_mix, ARRAY_SIZE(rt5677_dd1_l_mix)), + SND_SOC_DAPM_MIXER("DD1 MIXR", SND_SOC_NOPM, 0, 0, + rt5677_dd1_r_mix, ARRAY_SIZE(rt5677_dd1_r_mix)), + SND_SOC_DAPM_MIXER("DD2 MIXL", SND_SOC_NOPM, 0, 0, + rt5677_dd2_l_mix, ARRAY_SIZE(rt5677_dd2_l_mix)), + SND_SOC_DAPM_MIXER("DD2 MIXR", SND_SOC_NOPM, 0, 0, + rt5677_dd2_r_mix, ARRAY_SIZE(rt5677_dd2_r_mix)), + SND_SOC_DAPM_PGA("Stereo DAC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono DAC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DD1 MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("DD2 MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC 1", NULL, RT5677_PWR_DIG1, + RT5677_PWR_DAC1_BIT, 0), + SND_SOC_DAPM_DAC("DAC 2", NULL, RT5677_PWR_DIG1, + RT5677_PWR_DAC2_BIT, 0), + SND_SOC_DAPM_DAC("DAC 3", NULL, RT5677_PWR_DIG1, + RT5677_PWR_DAC3_BIT, 0), + + /* PDM */ + SND_SOC_DAPM_SUPPLY("PDM1 Power", RT5677_PWR_DIG2, + RT5677_PWR_PDM1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PDM2 Power", RT5677_PWR_DIG2, + RT5677_PWR_PDM2_BIT, 0, NULL, 0), + + SND_SOC_DAPM_MUX("PDM1 L Mux", RT5677_PDM_OUT_CTRL, RT5677_M_PDM1_L_SFT, + 1, &rt5677_pdm1_l_mux), + SND_SOC_DAPM_MUX("PDM1 R Mux", RT5677_PDM_OUT_CTRL, RT5677_M_PDM1_R_SFT, + 1, &rt5677_pdm1_r_mux), + SND_SOC_DAPM_MUX("PDM2 L Mux", RT5677_PDM_OUT_CTRL, RT5677_M_PDM2_L_SFT, + 1, &rt5677_pdm2_l_mux), + SND_SOC_DAPM_MUX("PDM2 R Mux", RT5677_PDM_OUT_CTRL, RT5677_M_PDM2_R_SFT, + 1, &rt5677_pdm2_r_mux), + + SND_SOC_DAPM_PGA_S("LOUT1 amp", 0, RT5677_PWR_ANLG1, RT5677_PWR_LO1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_PGA_S("LOUT2 amp", 0, RT5677_PWR_ANLG1, RT5677_PWR_LO2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_PGA_S("LOUT3 amp", 0, RT5677_PWR_ANLG1, RT5677_PWR_LO3_BIT, + 0, NULL, 0), + + SND_SOC_DAPM_PGA_S("LOUT1 vref", 1, SND_SOC_NOPM, 0, 0, + rt5677_vref_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT2 vref", 1, SND_SOC_NOPM, 0, 0, + rt5677_vref_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("LOUT3 vref", 1, SND_SOC_NOPM, 0, 0, + rt5677_vref_event, SND_SOC_DAPM_POST_PMU), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("LOUT3"), + SND_SOC_DAPM_OUTPUT("PDM1L"), + SND_SOC_DAPM_OUTPUT("PDM1R"), + SND_SOC_DAPM_OUTPUT("PDM2L"), + SND_SOC_DAPM_OUTPUT("PDM2R"), + + SND_SOC_DAPM_POST("vref", rt5677_vref_event), +}; + +static const struct snd_soc_dapm_route rt5677_dapm_routes[] = { + { "Stereo1 DMIC Mux", NULL, "DMIC STO1 ASRC", rt5677_dmic_use_asrc }, + { "Stereo2 DMIC Mux", NULL, "DMIC STO2 ASRC", rt5677_dmic_use_asrc }, + { "Stereo3 DMIC Mux", NULL, "DMIC STO3 ASRC", rt5677_dmic_use_asrc }, + { "Stereo4 DMIC Mux", NULL, "DMIC STO4 ASRC", rt5677_dmic_use_asrc }, + { "Mono DMIC L Mux", NULL, "DMIC MONO L ASRC", rt5677_dmic_use_asrc }, + { "Mono DMIC R Mux", NULL, "DMIC MONO R ASRC", rt5677_dmic_use_asrc }, + { "I2S1", NULL, "I2S1 ASRC", can_use_asrc}, + { "I2S2", NULL, "I2S2 ASRC", can_use_asrc}, + { "I2S3", NULL, "I2S3 ASRC", can_use_asrc}, + { "I2S4", NULL, "I2S4 ASRC", can_use_asrc}, + + { "dac stereo1 filter", NULL, "DAC STO ASRC", is_using_asrc }, + { "dac mono2 left filter", NULL, "DAC MONO2 L ASRC", is_using_asrc }, + { "dac mono2 right filter", NULL, "DAC MONO2 R ASRC", is_using_asrc }, + { "dac mono3 left filter", NULL, "DAC MONO3 L ASRC", is_using_asrc }, + { "dac mono3 right filter", NULL, "DAC MONO3 R ASRC", is_using_asrc }, + { "dac mono4 left filter", NULL, "DAC MONO4 L ASRC", is_using_asrc }, + { "dac mono4 right filter", NULL, "DAC MONO4 R ASRC", is_using_asrc }, + { "adc stereo1 filter", NULL, "ADC STO1 ASRC", is_using_asrc }, + { "adc stereo2 filter", NULL, "ADC STO2 ASRC", is_using_asrc }, + { "adc stereo3 filter", NULL, "ADC STO3 ASRC", is_using_asrc }, + { "adc stereo4 filter", NULL, "ADC STO4 ASRC", is_using_asrc }, + { "adc mono left filter", NULL, "ADC MONO L ASRC", is_using_asrc }, + { "adc mono right filter", NULL, "ADC MONO R ASRC", is_using_asrc }, + + { "DMIC1", NULL, "DMIC L1" }, + { "DMIC1", NULL, "DMIC R1" }, + { "DMIC2", NULL, "DMIC L2" }, + { "DMIC2", NULL, "DMIC R2" }, + { "DMIC3", NULL, "DMIC L3" }, + { "DMIC3", NULL, "DMIC R3" }, + { "DMIC4", NULL, "DMIC L4" }, + { "DMIC4", NULL, "DMIC R4" }, + + { "DMIC L1", NULL, "DMIC CLK" }, + { "DMIC R1", NULL, "DMIC CLK" }, + { "DMIC L2", NULL, "DMIC CLK" }, + { "DMIC R2", NULL, "DMIC CLK" }, + { "DMIC L3", NULL, "DMIC CLK" }, + { "DMIC R3", NULL, "DMIC CLK" }, + { "DMIC L4", NULL, "DMIC CLK" }, + { "DMIC R4", NULL, "DMIC CLK" }, + + { "DMIC L1", NULL, "DMIC1 power" }, + { "DMIC R1", NULL, "DMIC1 power" }, + { "DMIC L3", NULL, "DMIC3 power" }, + { "DMIC R3", NULL, "DMIC3 power" }, + { "DMIC L4", NULL, "DMIC4 power" }, + { "DMIC R4", NULL, "DMIC4 power" }, + + { "BST1", NULL, "IN1P" }, + { "BST1", NULL, "IN1N" }, + { "BST2", NULL, "IN2P" }, + { "BST2", NULL, "IN2N" }, + + { "IN1P", NULL, "MICBIAS1" }, + { "IN1N", NULL, "MICBIAS1" }, + { "IN2P", NULL, "MICBIAS1" }, + { "IN2N", NULL, "MICBIAS1" }, + + { "ADC 1", NULL, "BST1" }, + { "ADC 1", NULL, "ADC 1 power" }, + { "ADC 1", NULL, "ADC1 clock" }, + { "ADC 2", NULL, "BST2" }, + { "ADC 2", NULL, "ADC 2 power" }, + { "ADC 2", NULL, "ADC2 clock" }, + + { "Stereo1 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo1 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo1 DMIC Mux", "DMIC3", "DMIC3" }, + { "Stereo1 DMIC Mux", "DMIC4", "DMIC4" }, + + { "Stereo2 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo2 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo2 DMIC Mux", "DMIC3", "DMIC3" }, + { "Stereo2 DMIC Mux", "DMIC4", "DMIC4" }, + + { "Stereo3 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo3 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo3 DMIC Mux", "DMIC3", "DMIC3" }, + { "Stereo3 DMIC Mux", "DMIC4", "DMIC4" }, + + { "Stereo4 DMIC Mux", "DMIC1", "DMIC1" }, + { "Stereo4 DMIC Mux", "DMIC2", "DMIC2" }, + { "Stereo4 DMIC Mux", "DMIC3", "DMIC3" }, + { "Stereo4 DMIC Mux", "DMIC4", "DMIC4" }, + + { "Mono DMIC L Mux", "DMIC1", "DMIC1" }, + { "Mono DMIC L Mux", "DMIC2", "DMIC2" }, + { "Mono DMIC L Mux", "DMIC3", "DMIC3" }, + { "Mono DMIC L Mux", "DMIC4", "DMIC4" }, + + { "Mono DMIC R Mux", "DMIC1", "DMIC1" }, + { "Mono DMIC R Mux", "DMIC2", "DMIC2" }, + { "Mono DMIC R Mux", "DMIC3", "DMIC3" }, + { "Mono DMIC R Mux", "DMIC4", "DMIC4" }, + + { "ADC 1_2", NULL, "ADC 1" }, + { "ADC 1_2", NULL, "ADC 2" }, + + { "Stereo1 ADC1 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo1 ADC1 Mux", "ADC1/2", "ADC 1_2" }, + { "Stereo1 ADC1 Mux", "Stereo DAC MIX", "Stereo DAC MIX" }, + + { "Stereo1 ADC2 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo1 ADC2 Mux", "DMIC", "Stereo1 DMIC Mux" }, + { "Stereo1 ADC2 Mux", "Stereo DAC MIX", "Stereo DAC MIX" }, + + { "Stereo2 ADC1 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo2 ADC1 Mux", "ADC1/2", "ADC 1_2" }, + { "Stereo2 ADC1 Mux", "Stereo DAC MIX", "Stereo DAC MIX" }, + + { "Stereo2 ADC2 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo2 ADC2 Mux", "DMIC", "Stereo2 DMIC Mux" }, + { "Stereo2 ADC2 Mux", "Stereo DAC MIX", "Stereo DAC MIX" }, + + { "Stereo3 ADC1 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo3 ADC1 Mux", "ADC1/2", "ADC 1_2" }, + { "Stereo3 ADC1 Mux", "Stereo DAC MIX", "Stereo DAC MIX" }, + + { "Stereo3 ADC2 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo3 ADC2 Mux", "DMIC", "Stereo3 DMIC Mux" }, + { "Stereo3 ADC2 Mux", "Stereo DAC MIX", "Stereo DAC MIX" }, + + { "Stereo4 ADC1 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo4 ADC1 Mux", "ADC1/2", "ADC 1_2" }, + { "Stereo4 ADC1 Mux", "DD MIX2", "DD2 MIX" }, + + { "Stereo4 ADC2 Mux", "DD MIX1", "DD1 MIX" }, + { "Stereo4 ADC2 Mux", "DMIC", "Stereo3 DMIC Mux" }, + { "Stereo4 ADC2 Mux", "DD MIX2", "DD2 MIX" }, + + { "Mono ADC2 L Mux", "DD MIX1L", "DD1 MIXL" }, + { "Mono ADC2 L Mux", "DMIC", "Mono DMIC L Mux" }, + { "Mono ADC2 L Mux", "MONO DAC MIXL", "Mono DAC MIXL" }, + + { "Mono ADC1 L Mux", "DD MIX1L", "DD1 MIXL" }, + { "Mono ADC1 L Mux", "ADC1", "ADC 1" }, + { "Mono ADC1 L Mux", "MONO DAC MIXL", "Mono DAC MIXL" }, + + { "Mono ADC1 R Mux", "DD MIX1R", "DD1 MIXR" }, + { "Mono ADC1 R Mux", "ADC2", "ADC 2" }, + { "Mono ADC1 R Mux", "MONO DAC MIXR", "Mono DAC MIXR" }, + + { "Mono ADC2 R Mux", "DD MIX1R", "DD1 MIXR" }, + { "Mono ADC2 R Mux", "DMIC", "Mono DMIC R Mux" }, + { "Mono ADC2 R Mux", "MONO DAC MIXR", "Mono DAC MIXR" }, + + { "Sto1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC1 Mux" }, + { "Sto1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC2 Mux" }, + { "Sto1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC1 Mux" }, + { "Sto1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC2 Mux" }, + + { "Stereo1 ADC MIXL", NULL, "Sto1 ADC MIXL" }, + { "Stereo1 ADC MIXL", NULL, "adc stereo1 filter" }, + { "Stereo1 ADC MIXR", NULL, "Sto1 ADC MIXR" }, + { "Stereo1 ADC MIXR", NULL, "adc stereo1 filter" }, + { "adc stereo1 filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXL" }, + { "Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXR" }, + + { "Sto2 ADC MIXL", "ADC1 Switch", "Stereo2 ADC1 Mux" }, + { "Sto2 ADC MIXL", "ADC2 Switch", "Stereo2 ADC2 Mux" }, + { "Sto2 ADC MIXR", "ADC1 Switch", "Stereo2 ADC1 Mux" }, + { "Sto2 ADC MIXR", "ADC2 Switch", "Stereo2 ADC2 Mux" }, + + { "Sto2 ADC LR MIX", NULL, "Sto2 ADC MIXL" }, + { "Sto2 ADC LR MIX", NULL, "Sto2 ADC MIXR" }, + + { "Stereo2 ADC LR Mux", "L", "Sto2 ADC MIXL" }, + { "Stereo2 ADC LR Mux", "LR", "Sto2 ADC LR MIX" }, + + { "Stereo2 ADC MIXL", NULL, "Stereo2 ADC LR Mux" }, + { "Stereo2 ADC MIXL", NULL, "adc stereo2 filter" }, + { "Stereo2 ADC MIXR", NULL, "Sto2 ADC MIXR" }, + { "Stereo2 ADC MIXR", NULL, "adc stereo2 filter" }, + { "adc stereo2 filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Stereo2 ADC MIX", NULL, "Stereo2 ADC MIXL" }, + { "Stereo2 ADC MIX", NULL, "Stereo2 ADC MIXR" }, + + { "Sto3 ADC MIXL", "ADC1 Switch", "Stereo3 ADC1 Mux" }, + { "Sto3 ADC MIXL", "ADC2 Switch", "Stereo3 ADC2 Mux" }, + { "Sto3 ADC MIXR", "ADC1 Switch", "Stereo3 ADC1 Mux" }, + { "Sto3 ADC MIXR", "ADC2 Switch", "Stereo3 ADC2 Mux" }, + + { "Stereo3 ADC MIXL", NULL, "Sto3 ADC MIXL" }, + { "Stereo3 ADC MIXL", NULL, "adc stereo3 filter" }, + { "Stereo3 ADC MIXR", NULL, "Sto3 ADC MIXR" }, + { "Stereo3 ADC MIXR", NULL, "adc stereo3 filter" }, + { "adc stereo3 filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Stereo3 ADC MIX", NULL, "Stereo3 ADC MIXL" }, + { "Stereo3 ADC MIX", NULL, "Stereo3 ADC MIXR" }, + + { "Sto4 ADC MIXL", "ADC1 Switch", "Stereo4 ADC1 Mux" }, + { "Sto4 ADC MIXL", "ADC2 Switch", "Stereo4 ADC2 Mux" }, + { "Sto4 ADC MIXR", "ADC1 Switch", "Stereo4 ADC1 Mux" }, + { "Sto4 ADC MIXR", "ADC2 Switch", "Stereo4 ADC2 Mux" }, + + { "Stereo4 ADC MIXL", NULL, "Sto4 ADC MIXL" }, + { "Stereo4 ADC MIXL", NULL, "adc stereo4 filter" }, + { "Stereo4 ADC MIXR", NULL, "Sto4 ADC MIXR" }, + { "Stereo4 ADC MIXR", NULL, "adc stereo4 filter" }, + { "adc stereo4 filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Stereo4 ADC MIX", NULL, "Stereo4 ADC MIXL" }, + { "Stereo4 ADC MIX", NULL, "Stereo4 ADC MIXR" }, + + { "Mono ADC MIXL", "ADC1 Switch", "Mono ADC1 L Mux" }, + { "Mono ADC MIXL", "ADC2 Switch", "Mono ADC2 L Mux" }, + { "Mono ADC MIXL", NULL, "adc mono left filter" }, + { "adc mono left filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Mono ADC MIXR", "ADC1 Switch", "Mono ADC1 R Mux" }, + { "Mono ADC MIXR", "ADC2 Switch", "Mono ADC2 R Mux" }, + { "Mono ADC MIXR", NULL, "adc mono right filter" }, + { "adc mono right filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Mono ADC MIX", NULL, "Mono ADC MIXL" }, + { "Mono ADC MIX", NULL, "Mono ADC MIXR" }, + + { "VAD ADC Mux", "STO1 ADC MIX L", "Stereo1 ADC MIXL" }, + { "VAD ADC Mux", "MONO ADC MIX L", "Mono ADC MIXL" }, + { "VAD ADC Mux", "MONO ADC MIX R", "Mono ADC MIXR" }, + { "VAD ADC Mux", "STO2 ADC MIX L", "Stereo2 ADC MIXL" }, + { "VAD ADC Mux", "STO3 ADC MIX L", "Stereo3 ADC MIXL" }, + + { "IF1 ADC1 Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, + { "IF1 ADC1 Mux", "OB01", "OB01 Bypass Mux" }, + { "IF1 ADC1 Mux", "VAD ADC", "VAD ADC Mux" }, + + { "IF1 ADC2 Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, + { "IF1 ADC2 Mux", "OB23", "OB23 Bypass Mux" }, + + { "IF1 ADC3 Mux", "STO3 ADC MIX", "Stereo3 ADC MIX" }, + { "IF1 ADC3 Mux", "MONO ADC MIX", "Mono ADC MIX" }, + { "IF1 ADC3 Mux", "OB45", "OB45" }, + + { "IF1 ADC4 Mux", "STO4 ADC MIX", "Stereo4 ADC MIX" }, + { "IF1 ADC4 Mux", "OB67", "OB67" }, + { "IF1 ADC4 Mux", "OB01", "OB01 Bypass Mux" }, + + { "IF1 ADC1 Swap Mux", "L/R", "IF1 ADC1 Mux" }, + { "IF1 ADC1 Swap Mux", "R/L", "IF1 ADC1 Mux" }, + { "IF1 ADC1 Swap Mux", "L/L", "IF1 ADC1 Mux" }, + { "IF1 ADC1 Swap Mux", "R/R", "IF1 ADC1 Mux" }, + + { "IF1 ADC2 Swap Mux", "L/R", "IF1 ADC2 Mux" }, + { "IF1 ADC2 Swap Mux", "R/L", "IF1 ADC2 Mux" }, + { "IF1 ADC2 Swap Mux", "L/L", "IF1 ADC2 Mux" }, + { "IF1 ADC2 Swap Mux", "R/R", "IF1 ADC2 Mux" }, + + { "IF1 ADC3 Swap Mux", "L/R", "IF1 ADC3 Mux" }, + { "IF1 ADC3 Swap Mux", "R/L", "IF1 ADC3 Mux" }, + { "IF1 ADC3 Swap Mux", "L/L", "IF1 ADC3 Mux" }, + { "IF1 ADC3 Swap Mux", "R/R", "IF1 ADC3 Mux" }, + + { "IF1 ADC4 Swap Mux", "L/R", "IF1 ADC4 Mux" }, + { "IF1 ADC4 Swap Mux", "R/L", "IF1 ADC4 Mux" }, + { "IF1 ADC4 Swap Mux", "L/L", "IF1 ADC4 Mux" }, + { "IF1 ADC4 Swap Mux", "R/R", "IF1 ADC4 Mux" }, + + { "IF1 ADC", NULL, "IF1 ADC1 Swap Mux" }, + { "IF1 ADC", NULL, "IF1 ADC2 Swap Mux" }, + { "IF1 ADC", NULL, "IF1 ADC3 Swap Mux" }, + { "IF1 ADC", NULL, "IF1 ADC4 Swap Mux" }, + + { "IF1 ADC TDM Swap Mux", "1/2/3/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "2/1/3/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "2/3/1/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "4/1/2/3", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "1/3/2/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "1/4/2/3", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "3/1/2/4", "IF1 ADC" }, + { "IF1 ADC TDM Swap Mux", "3/4/1/2", "IF1 ADC" }, + + { "AIF1TX", NULL, "I2S1" }, + { "AIF1TX", NULL, "IF1 ADC TDM Swap Mux" }, + + { "IF2 ADC1 Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, + { "IF2 ADC1 Mux", "OB01", "OB01 Bypass Mux" }, + { "IF2 ADC1 Mux", "VAD ADC", "VAD ADC Mux" }, + + { "IF2 ADC2 Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, + { "IF2 ADC2 Mux", "OB23", "OB23 Bypass Mux" }, + + { "IF2 ADC3 Mux", "STO3 ADC MIX", "Stereo3 ADC MIX" }, + { "IF2 ADC3 Mux", "MONO ADC MIX", "Mono ADC MIX" }, + { "IF2 ADC3 Mux", "OB45", "OB45" }, + + { "IF2 ADC4 Mux", "STO4 ADC MIX", "Stereo4 ADC MIX" }, + { "IF2 ADC4 Mux", "OB67", "OB67" }, + { "IF2 ADC4 Mux", "OB01", "OB01 Bypass Mux" }, + + { "IF2 ADC1 Swap Mux", "L/R", "IF2 ADC1 Mux" }, + { "IF2 ADC1 Swap Mux", "R/L", "IF2 ADC1 Mux" }, + { "IF2 ADC1 Swap Mux", "L/L", "IF2 ADC1 Mux" }, + { "IF2 ADC1 Swap Mux", "R/R", "IF2 ADC1 Mux" }, + + { "IF2 ADC2 Swap Mux", "L/R", "IF2 ADC2 Mux" }, + { "IF2 ADC2 Swap Mux", "R/L", "IF2 ADC2 Mux" }, + { "IF2 ADC2 Swap Mux", "L/L", "IF2 ADC2 Mux" }, + { "IF2 ADC2 Swap Mux", "R/R", "IF2 ADC2 Mux" }, + + { "IF2 ADC3 Swap Mux", "L/R", "IF2 ADC3 Mux" }, + { "IF2 ADC3 Swap Mux", "R/L", "IF2 ADC3 Mux" }, + { "IF2 ADC3 Swap Mux", "L/L", "IF2 ADC3 Mux" }, + { "IF2 ADC3 Swap Mux", "R/R", "IF2 ADC3 Mux" }, + + { "IF2 ADC4 Swap Mux", "L/R", "IF2 ADC4 Mux" }, + { "IF2 ADC4 Swap Mux", "R/L", "IF2 ADC4 Mux" }, + { "IF2 ADC4 Swap Mux", "L/L", "IF2 ADC4 Mux" }, + { "IF2 ADC4 Swap Mux", "R/R", "IF2 ADC4 Mux" }, + + { "IF2 ADC", NULL, "IF2 ADC1 Swap Mux" }, + { "IF2 ADC", NULL, "IF2 ADC2 Swap Mux" }, + { "IF2 ADC", NULL, "IF2 ADC3 Swap Mux" }, + { "IF2 ADC", NULL, "IF2 ADC4 Swap Mux" }, + + { "IF2 ADC TDM Swap Mux", "1/2/3/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "2/1/3/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "3/1/2/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "4/1/2/3", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "1/3/2/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "1/4/2/3", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "2/3/1/4", "IF2 ADC" }, + { "IF2 ADC TDM Swap Mux", "3/4/1/2", "IF2 ADC" }, + + { "AIF2TX", NULL, "I2S2" }, + { "AIF2TX", NULL, "IF2 ADC TDM Swap Mux" }, + + { "IF3 ADC Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, + { "IF3 ADC Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, + { "IF3 ADC Mux", "STO3 ADC MIX", "Stereo3 ADC MIX" }, + { "IF3 ADC Mux", "STO4 ADC MIX", "Stereo4 ADC MIX" }, + { "IF3 ADC Mux", "MONO ADC MIX", "Mono ADC MIX" }, + { "IF3 ADC Mux", "OB01", "OB01 Bypass Mux" }, + { "IF3 ADC Mux", "OB23", "OB23 Bypass Mux" }, + { "IF3 ADC Mux", "VAD ADC", "VAD ADC Mux" }, + + { "AIF3TX", NULL, "I2S3" }, + { "AIF3TX", NULL, "IF3 ADC Mux" }, + + { "IF4 ADC Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, + { "IF4 ADC Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, + { "IF4 ADC Mux", "STO3 ADC MIX", "Stereo3 ADC MIX" }, + { "IF4 ADC Mux", "STO4 ADC MIX", "Stereo4 ADC MIX" }, + { "IF4 ADC Mux", "MONO ADC MIX", "Mono ADC MIX" }, + { "IF4 ADC Mux", "OB01", "OB01 Bypass Mux" }, + { "IF4 ADC Mux", "OB23", "OB23 Bypass Mux" }, + { "IF4 ADC Mux", "VAD ADC", "VAD ADC Mux" }, + + { "AIF4TX", NULL, "I2S4" }, + { "AIF4TX", NULL, "IF4 ADC Mux" }, + + { "SLB ADC1 Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, + { "SLB ADC1 Mux", "OB01", "OB01 Bypass Mux" }, + { "SLB ADC1 Mux", "VAD ADC", "VAD ADC Mux" }, + + { "SLB ADC2 Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, + { "SLB ADC2 Mux", "OB23", "OB23 Bypass Mux" }, + + { "SLB ADC3 Mux", "STO3 ADC MIX", "Stereo3 ADC MIX" }, + { "SLB ADC3 Mux", "MONO ADC MIX", "Mono ADC MIX" }, + { "SLB ADC3 Mux", "OB45", "OB45" }, + + { "SLB ADC4 Mux", "STO4 ADC MIX", "Stereo4 ADC MIX" }, + { "SLB ADC4 Mux", "OB67", "OB67" }, + { "SLB ADC4 Mux", "OB01", "OB01 Bypass Mux" }, + + { "SLBTX", NULL, "SLB" }, + { "SLBTX", NULL, "SLB ADC1 Mux" }, + { "SLBTX", NULL, "SLB ADC2 Mux" }, + { "SLBTX", NULL, "SLB ADC3 Mux" }, + { "SLBTX", NULL, "SLB ADC4 Mux" }, + + { "IB01 Mux", "IF1 DAC 01", "IF1 DAC01" }, + { "IB01 Mux", "IF2 DAC 01", "IF2 DAC01" }, + { "IB01 Mux", "SLB DAC 01", "SLB DAC01" }, + { "IB01 Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, + { "IB01 Mux", "VAD ADC/DAC1 FS", "DAC1 FS" }, + + { "IB01 Bypass Mux", "Bypass", "IB01 Mux" }, + { "IB01 Bypass Mux", "Pass SRC", "IB01 Mux" }, + + { "IB23 Mux", "IF1 DAC 23", "IF1 DAC23" }, + { "IB23 Mux", "IF2 DAC 23", "IF2 DAC23" }, + { "IB23 Mux", "SLB DAC 23", "SLB DAC23" }, + { "IB23 Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, + { "IB23 Mux", "DAC1 FS", "DAC1 FS" }, + { "IB23 Mux", "IF4 DAC", "IF4 DAC" }, + + { "IB23 Bypass Mux", "Bypass", "IB23 Mux" }, + { "IB23 Bypass Mux", "Pass SRC", "IB23 Mux" }, + + { "IB45 Mux", "IF1 DAC 45", "IF1 DAC45" }, + { "IB45 Mux", "IF2 DAC 45", "IF2 DAC45" }, + { "IB45 Mux", "SLB DAC 45", "SLB DAC45" }, + { "IB45 Mux", "STO3 ADC MIX", "Stereo3 ADC MIX" }, + { "IB45 Mux", "IF3 DAC", "IF3 DAC" }, + + { "IB45 Bypass Mux", "Bypass", "IB45 Mux" }, + { "IB45 Bypass Mux", "Pass SRC", "IB45 Mux" }, + + { "IB6 Mux", "IF1 DAC 6", "IF1 DAC6 Mux" }, + { "IB6 Mux", "IF2 DAC 6", "IF2 DAC6 Mux" }, + { "IB6 Mux", "SLB DAC 6", "SLB DAC6" }, + { "IB6 Mux", "STO4 ADC MIX L", "Stereo4 ADC MIXL" }, + { "IB6 Mux", "IF4 DAC L", "IF4 DAC L" }, + { "IB6 Mux", "STO1 ADC MIX L", "Stereo1 ADC MIXL" }, + { "IB6 Mux", "STO2 ADC MIX L", "Stereo2 ADC MIXL" }, + { "IB6 Mux", "STO3 ADC MIX L", "Stereo3 ADC MIXL" }, + + { "IB7 Mux", "IF1 DAC 7", "IF1 DAC7 Mux" }, + { "IB7 Mux", "IF2 DAC 7", "IF2 DAC7 Mux" }, + { "IB7 Mux", "SLB DAC 7", "SLB DAC7" }, + { "IB7 Mux", "STO4 ADC MIX R", "Stereo4 ADC MIXR" }, + { "IB7 Mux", "IF4 DAC R", "IF4 DAC R" }, + { "IB7 Mux", "STO1 ADC MIX R", "Stereo1 ADC MIXR" }, + { "IB7 Mux", "STO2 ADC MIX R", "Stereo2 ADC MIXR" }, + { "IB7 Mux", "STO3 ADC MIX R", "Stereo3 ADC MIXR" }, + + { "IB8 Mux", "STO1 ADC MIX L", "Stereo1 ADC MIXL" }, + { "IB8 Mux", "STO2 ADC MIX L", "Stereo2 ADC MIXL" }, + { "IB8 Mux", "STO3 ADC MIX L", "Stereo3 ADC MIXL" }, + { "IB8 Mux", "STO4 ADC MIX L", "Stereo4 ADC MIXL" }, + { "IB8 Mux", "MONO ADC MIX L", "Mono ADC MIXL" }, + { "IB8 Mux", "DACL1 FS", "DAC1 MIXL" }, + + { "IB9 Mux", "STO1 ADC MIX R", "Stereo1 ADC MIXR" }, + { "IB9 Mux", "STO2 ADC MIX R", "Stereo2 ADC MIXR" }, + { "IB9 Mux", "STO3 ADC MIX R", "Stereo3 ADC MIXR" }, + { "IB9 Mux", "STO4 ADC MIX R", "Stereo4 ADC MIXR" }, + { "IB9 Mux", "MONO ADC MIX R", "Mono ADC MIXR" }, + { "IB9 Mux", "DACR1 FS", "DAC1 MIXR" }, + { "IB9 Mux", "DAC1 FS", "DAC1 FS" }, + + { "OB01 MIX", "IB01 Switch", "IB01 Bypass Mux" }, + { "OB01 MIX", "IB23 Switch", "IB23 Bypass Mux" }, + { "OB01 MIX", "IB45 Switch", "IB45 Bypass Mux" }, + { "OB01 MIX", "IB6 Switch", "IB6 Mux" }, + { "OB01 MIX", "IB7 Switch", "IB7 Mux" }, + { "OB01 MIX", "IB8 Switch", "IB8 Mux" }, + { "OB01 MIX", "IB9 Switch", "IB9 Mux" }, + + { "OB23 MIX", "IB01 Switch", "IB01 Bypass Mux" }, + { "OB23 MIX", "IB23 Switch", "IB23 Bypass Mux" }, + { "OB23 MIX", "IB45 Switch", "IB45 Bypass Mux" }, + { "OB23 MIX", "IB6 Switch", "IB6 Mux" }, + { "OB23 MIX", "IB7 Switch", "IB7 Mux" }, + { "OB23 MIX", "IB8 Switch", "IB8 Mux" }, + { "OB23 MIX", "IB9 Switch", "IB9 Mux" }, + + { "OB4 MIX", "IB01 Switch", "IB01 Bypass Mux" }, + { "OB4 MIX", "IB23 Switch", "IB23 Bypass Mux" }, + { "OB4 MIX", "IB45 Switch", "IB45 Bypass Mux" }, + { "OB4 MIX", "IB6 Switch", "IB6 Mux" }, + { "OB4 MIX", "IB7 Switch", "IB7 Mux" }, + { "OB4 MIX", "IB8 Switch", "IB8 Mux" }, + { "OB4 MIX", "IB9 Switch", "IB9 Mux" }, + + { "OB5 MIX", "IB01 Switch", "IB01 Bypass Mux" }, + { "OB5 MIX", "IB23 Switch", "IB23 Bypass Mux" }, + { "OB5 MIX", "IB45 Switch", "IB45 Bypass Mux" }, + { "OB5 MIX", "IB6 Switch", "IB6 Mux" }, + { "OB5 MIX", "IB7 Switch", "IB7 Mux" }, + { "OB5 MIX", "IB8 Switch", "IB8 Mux" }, + { "OB5 MIX", "IB9 Switch", "IB9 Mux" }, + + { "OB6 MIX", "IB01 Switch", "IB01 Bypass Mux" }, + { "OB6 MIX", "IB23 Switch", "IB23 Bypass Mux" }, + { "OB6 MIX", "IB45 Switch", "IB45 Bypass Mux" }, + { "OB6 MIX", "IB6 Switch", "IB6 Mux" }, + { "OB6 MIX", "IB7 Switch", "IB7 Mux" }, + { "OB6 MIX", "IB8 Switch", "IB8 Mux" }, + { "OB6 MIX", "IB9 Switch", "IB9 Mux" }, + + { "OB7 MIX", "IB01 Switch", "IB01 Bypass Mux" }, + { "OB7 MIX", "IB23 Switch", "IB23 Bypass Mux" }, + { "OB7 MIX", "IB45 Switch", "IB45 Bypass Mux" }, + { "OB7 MIX", "IB6 Switch", "IB6 Mux" }, + { "OB7 MIX", "IB7 Switch", "IB7 Mux" }, + { "OB7 MIX", "IB8 Switch", "IB8 Mux" }, + { "OB7 MIX", "IB9 Switch", "IB9 Mux" }, + + { "OB01 Bypass Mux", "Bypass", "OB01 MIX" }, + { "OB01 Bypass Mux", "Pass SRC", "OB01 MIX" }, + { "OB23 Bypass Mux", "Bypass", "OB23 MIX" }, + { "OB23 Bypass Mux", "Pass SRC", "OB23 MIX" }, + + { "OutBound2", NULL, "OB23 Bypass Mux" }, + { "OutBound3", NULL, "OB23 Bypass Mux" }, + { "OutBound4", NULL, "OB4 MIX" }, + { "OutBound5", NULL, "OB5 MIX" }, + { "OutBound6", NULL, "OB6 MIX" }, + { "OutBound7", NULL, "OB7 MIX" }, + + { "OB45", NULL, "OutBound4" }, + { "OB45", NULL, "OutBound5" }, + { "OB67", NULL, "OutBound6" }, + { "OB67", NULL, "OutBound7" }, + + { "IF1 DAC0", NULL, "AIF1RX" }, + { "IF1 DAC1", NULL, "AIF1RX" }, + { "IF1 DAC2", NULL, "AIF1RX" }, + { "IF1 DAC3", NULL, "AIF1RX" }, + { "IF1 DAC4", NULL, "AIF1RX" }, + { "IF1 DAC5", NULL, "AIF1RX" }, + { "IF1 DAC6", NULL, "AIF1RX" }, + { "IF1 DAC7", NULL, "AIF1RX" }, + { "IF1 DAC0", NULL, "I2S1" }, + { "IF1 DAC1", NULL, "I2S1" }, + { "IF1 DAC2", NULL, "I2S1" }, + { "IF1 DAC3", NULL, "I2S1" }, + { "IF1 DAC4", NULL, "I2S1" }, + { "IF1 DAC5", NULL, "I2S1" }, + { "IF1 DAC6", NULL, "I2S1" }, + { "IF1 DAC7", NULL, "I2S1" }, + + { "IF1 DAC0 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC0 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC0 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC0 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC0 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC0 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC0 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC0 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC1 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC1 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC1 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC1 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC1 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC1 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC1 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC1 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC2 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC2 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC2 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC2 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC2 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC2 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC2 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC2 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC3 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC3 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC3 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC3 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC3 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC3 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC3 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC3 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC4 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC4 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC4 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC4 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC4 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC4 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC4 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC4 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC5 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC5 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC5 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC5 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC5 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC5 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC5 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC5 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC6 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC6 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC6 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC6 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC6 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC6 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC6 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC6 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC7 Mux", "Slot0", "IF1 DAC0" }, + { "IF1 DAC7 Mux", "Slot1", "IF1 DAC1" }, + { "IF1 DAC7 Mux", "Slot2", "IF1 DAC2" }, + { "IF1 DAC7 Mux", "Slot3", "IF1 DAC3" }, + { "IF1 DAC7 Mux", "Slot4", "IF1 DAC4" }, + { "IF1 DAC7 Mux", "Slot5", "IF1 DAC5" }, + { "IF1 DAC7 Mux", "Slot6", "IF1 DAC6" }, + { "IF1 DAC7 Mux", "Slot7", "IF1 DAC7" }, + + { "IF1 DAC01", NULL, "IF1 DAC0 Mux" }, + { "IF1 DAC01", NULL, "IF1 DAC1 Mux" }, + { "IF1 DAC23", NULL, "IF1 DAC2 Mux" }, + { "IF1 DAC23", NULL, "IF1 DAC3 Mux" }, + { "IF1 DAC45", NULL, "IF1 DAC4 Mux" }, + { "IF1 DAC45", NULL, "IF1 DAC5 Mux" }, + { "IF1 DAC67", NULL, "IF1 DAC6 Mux" }, + { "IF1 DAC67", NULL, "IF1 DAC7 Mux" }, + + { "IF2 DAC0", NULL, "AIF2RX" }, + { "IF2 DAC1", NULL, "AIF2RX" }, + { "IF2 DAC2", NULL, "AIF2RX" }, + { "IF2 DAC3", NULL, "AIF2RX" }, + { "IF2 DAC4", NULL, "AIF2RX" }, + { "IF2 DAC5", NULL, "AIF2RX" }, + { "IF2 DAC6", NULL, "AIF2RX" }, + { "IF2 DAC7", NULL, "AIF2RX" }, + { "IF2 DAC0", NULL, "I2S2" }, + { "IF2 DAC1", NULL, "I2S2" }, + { "IF2 DAC2", NULL, "I2S2" }, + { "IF2 DAC3", NULL, "I2S2" }, + { "IF2 DAC4", NULL, "I2S2" }, + { "IF2 DAC5", NULL, "I2S2" }, + { "IF2 DAC6", NULL, "I2S2" }, + { "IF2 DAC7", NULL, "I2S2" }, + + { "IF2 DAC0 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC0 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC0 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC0 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC0 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC0 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC0 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC0 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC1 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC1 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC1 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC1 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC1 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC1 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC1 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC1 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC2 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC2 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC2 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC2 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC2 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC2 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC2 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC2 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC3 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC3 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC3 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC3 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC3 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC3 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC3 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC3 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC4 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC4 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC4 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC4 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC4 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC4 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC4 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC4 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC5 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC5 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC5 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC5 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC5 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC5 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC5 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC5 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC6 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC6 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC6 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC6 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC6 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC6 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC6 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC6 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC7 Mux", "Slot0", "IF2 DAC0" }, + { "IF2 DAC7 Mux", "Slot1", "IF2 DAC1" }, + { "IF2 DAC7 Mux", "Slot2", "IF2 DAC2" }, + { "IF2 DAC7 Mux", "Slot3", "IF2 DAC3" }, + { "IF2 DAC7 Mux", "Slot4", "IF2 DAC4" }, + { "IF2 DAC7 Mux", "Slot5", "IF2 DAC5" }, + { "IF2 DAC7 Mux", "Slot6", "IF2 DAC6" }, + { "IF2 DAC7 Mux", "Slot7", "IF2 DAC7" }, + + { "IF2 DAC01", NULL, "IF2 DAC0 Mux" }, + { "IF2 DAC01", NULL, "IF2 DAC1 Mux" }, + { "IF2 DAC23", NULL, "IF2 DAC2 Mux" }, + { "IF2 DAC23", NULL, "IF2 DAC3 Mux" }, + { "IF2 DAC45", NULL, "IF2 DAC4 Mux" }, + { "IF2 DAC45", NULL, "IF2 DAC5 Mux" }, + { "IF2 DAC67", NULL, "IF2 DAC6 Mux" }, + { "IF2 DAC67", NULL, "IF2 DAC7 Mux" }, + + { "IF3 DAC", NULL, "AIF3RX" }, + { "IF3 DAC", NULL, "I2S3" }, + + { "IF4 DAC", NULL, "AIF4RX" }, + { "IF4 DAC", NULL, "I2S4" }, + + { "IF3 DAC L", NULL, "IF3 DAC" }, + { "IF3 DAC R", NULL, "IF3 DAC" }, + + { "IF4 DAC L", NULL, "IF4 DAC" }, + { "IF4 DAC R", NULL, "IF4 DAC" }, + + { "SLB DAC0", NULL, "SLBRX" }, + { "SLB DAC1", NULL, "SLBRX" }, + { "SLB DAC2", NULL, "SLBRX" }, + { "SLB DAC3", NULL, "SLBRX" }, + { "SLB DAC4", NULL, "SLBRX" }, + { "SLB DAC5", NULL, "SLBRX" }, + { "SLB DAC6", NULL, "SLBRX" }, + { "SLB DAC7", NULL, "SLBRX" }, + { "SLB DAC0", NULL, "SLB" }, + { "SLB DAC1", NULL, "SLB" }, + { "SLB DAC2", NULL, "SLB" }, + { "SLB DAC3", NULL, "SLB" }, + { "SLB DAC4", NULL, "SLB" }, + { "SLB DAC5", NULL, "SLB" }, + { "SLB DAC6", NULL, "SLB" }, + { "SLB DAC7", NULL, "SLB" }, + + { "SLB DAC01", NULL, "SLB DAC0" }, + { "SLB DAC01", NULL, "SLB DAC1" }, + { "SLB DAC23", NULL, "SLB DAC2" }, + { "SLB DAC23", NULL, "SLB DAC3" }, + { "SLB DAC45", NULL, "SLB DAC4" }, + { "SLB DAC45", NULL, "SLB DAC5" }, + { "SLB DAC67", NULL, "SLB DAC6" }, + { "SLB DAC67", NULL, "SLB DAC7" }, + + { "ADDA1 Mux", "STO1 ADC MIX", "Stereo1 ADC MIX" }, + { "ADDA1 Mux", "STO2 ADC MIX", "Stereo2 ADC MIX" }, + { "ADDA1 Mux", "OB 67", "OB67" }, + + { "DAC1 Mux", "IF1 DAC 01", "IF1 DAC01" }, + { "DAC1 Mux", "IF2 DAC 01", "IF2 DAC01" }, + { "DAC1 Mux", "IF3 DAC LR", "IF3 DAC" }, + { "DAC1 Mux", "IF4 DAC LR", "IF4 DAC" }, + { "DAC1 Mux", "SLB DAC 01", "SLB DAC01" }, + { "DAC1 Mux", "OB 01", "OB01 Bypass Mux" }, + + { "DAC1 MIXL", "Stereo ADC Switch", "ADDA1 Mux" }, + { "DAC1 MIXL", "DAC1 Switch", "DAC1 Mux" }, + { "DAC1 MIXR", "Stereo ADC Switch", "ADDA1 Mux" }, + { "DAC1 MIXR", "DAC1 Switch", "DAC1 Mux" }, + + { "DAC1 FS", NULL, "DAC1 MIXL" }, + { "DAC1 FS", NULL, "DAC1 MIXR" }, + + { "DAC2 L Mux", "IF1 DAC 2", "IF1 DAC2 Mux" }, + { "DAC2 L Mux", "IF2 DAC 2", "IF2 DAC2 Mux" }, + { "DAC2 L Mux", "IF3 DAC L", "IF3 DAC L" }, + { "DAC2 L Mux", "IF4 DAC L", "IF4 DAC L" }, + { "DAC2 L Mux", "SLB DAC 2", "SLB DAC2" }, + { "DAC2 L Mux", "OB 2", "OutBound2" }, + + { "DAC2 R Mux", "IF1 DAC 3", "IF1 DAC3 Mux" }, + { "DAC2 R Mux", "IF2 DAC 3", "IF2 DAC3 Mux" }, + { "DAC2 R Mux", "IF3 DAC R", "IF3 DAC R" }, + { "DAC2 R Mux", "IF4 DAC R", "IF4 DAC R" }, + { "DAC2 R Mux", "SLB DAC 3", "SLB DAC3" }, + { "DAC2 R Mux", "OB 3", "OutBound3" }, + { "DAC2 R Mux", "Haptic Generator", "Haptic Generator" }, + { "DAC2 R Mux", "VAD ADC", "VAD ADC Mux" }, + + { "DAC3 L Mux", "IF1 DAC 4", "IF1 DAC4 Mux" }, + { "DAC3 L Mux", "IF2 DAC 4", "IF2 DAC4 Mux" }, + { "DAC3 L Mux", "IF3 DAC L", "IF3 DAC L" }, + { "DAC3 L Mux", "IF4 DAC L", "IF4 DAC L" }, + { "DAC3 L Mux", "SLB DAC 4", "SLB DAC4" }, + { "DAC3 L Mux", "OB 4", "OutBound4" }, + + { "DAC3 R Mux", "IF1 DAC 5", "IF1 DAC5 Mux" }, + { "DAC3 R Mux", "IF2 DAC 5", "IF2 DAC5 Mux" }, + { "DAC3 R Mux", "IF3 DAC R", "IF3 DAC R" }, + { "DAC3 R Mux", "IF4 DAC R", "IF4 DAC R" }, + { "DAC3 R Mux", "SLB DAC 5", "SLB DAC5" }, + { "DAC3 R Mux", "OB 5", "OutBound5" }, + + { "DAC4 L Mux", "IF1 DAC 6", "IF1 DAC6 Mux" }, + { "DAC4 L Mux", "IF2 DAC 6", "IF2 DAC6 Mux" }, + { "DAC4 L Mux", "IF3 DAC L", "IF3 DAC L" }, + { "DAC4 L Mux", "IF4 DAC L", "IF4 DAC L" }, + { "DAC4 L Mux", "SLB DAC 6", "SLB DAC6" }, + { "DAC4 L Mux", "OB 6", "OutBound6" }, + + { "DAC4 R Mux", "IF1 DAC 7", "IF1 DAC7 Mux" }, + { "DAC4 R Mux", "IF2 DAC 7", "IF2 DAC7 Mux" }, + { "DAC4 R Mux", "IF3 DAC R", "IF3 DAC R" }, + { "DAC4 R Mux", "IF4 DAC R", "IF4 DAC R" }, + { "DAC4 R Mux", "SLB DAC 7", "SLB DAC7" }, + { "DAC4 R Mux", "OB 7", "OutBound7" }, + + { "Sidetone Mux", "DMIC1 L", "DMIC L1" }, + { "Sidetone Mux", "DMIC2 L", "DMIC L2" }, + { "Sidetone Mux", "DMIC3 L", "DMIC L3" }, + { "Sidetone Mux", "DMIC4 L", "DMIC L4" }, + { "Sidetone Mux", "ADC1", "ADC 1" }, + { "Sidetone Mux", "ADC2", "ADC 2" }, + { "Sidetone Mux", NULL, "Sidetone Power" }, + + { "Stereo DAC MIXL", "ST L Switch", "Sidetone Mux" }, + { "Stereo DAC MIXL", "DAC1 L Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXL", "DAC2 L Switch", "DAC2 L Mux" }, + { "Stereo DAC MIXL", "DAC1 R Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXL", NULL, "dac stereo1 filter" }, + { "Stereo DAC MIXR", "ST R Switch", "Sidetone Mux" }, + { "Stereo DAC MIXR", "DAC1 R Switch", "DAC1 MIXR" }, + { "Stereo DAC MIXR", "DAC2 R Switch", "DAC2 R Mux" }, + { "Stereo DAC MIXR", "DAC1 L Switch", "DAC1 MIXL" }, + { "Stereo DAC MIXR", NULL, "dac stereo1 filter" }, + { "dac stereo1 filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Mono DAC MIXL", "ST L Switch", "Sidetone Mux" }, + { "Mono DAC MIXL", "DAC1 L Switch", "DAC1 MIXL" }, + { "Mono DAC MIXL", "DAC2 L Switch", "DAC2 L Mux" }, + { "Mono DAC MIXL", "DAC2 R Switch", "DAC2 R Mux" }, + { "Mono DAC MIXL", NULL, "dac mono2 left filter" }, + { "dac mono2 left filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "Mono DAC MIXR", "ST R Switch", "Sidetone Mux" }, + { "Mono DAC MIXR", "DAC1 R Switch", "DAC1 MIXR" }, + { "Mono DAC MIXR", "DAC2 R Switch", "DAC2 R Mux" }, + { "Mono DAC MIXR", "DAC2 L Switch", "DAC2 L Mux" }, + { "Mono DAC MIXR", NULL, "dac mono2 right filter" }, + { "dac mono2 right filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "DD1 MIXL", "Sto DAC Mix L Switch", "Stereo DAC MIXL" }, + { "DD1 MIXL", "Mono DAC Mix L Switch", "Mono DAC MIXL" }, + { "DD1 MIXL", "DAC3 L Switch", "DAC3 L Mux" }, + { "DD1 MIXL", "DAC3 R Switch", "DAC3 R Mux" }, + { "DD1 MIXL", NULL, "dac mono3 left filter" }, + { "dac mono3 left filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "DD1 MIXR", "Sto DAC Mix R Switch", "Stereo DAC MIXR" }, + { "DD1 MIXR", "Mono DAC Mix R Switch", "Mono DAC MIXR" }, + { "DD1 MIXR", "DAC3 L Switch", "DAC3 L Mux" }, + { "DD1 MIXR", "DAC3 R Switch", "DAC3 R Mux" }, + { "DD1 MIXR", NULL, "dac mono3 right filter" }, + { "dac mono3 right filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "DD2 MIXL", "Sto DAC Mix L Switch", "Stereo DAC MIXL" }, + { "DD2 MIXL", "Mono DAC Mix L Switch", "Mono DAC MIXL" }, + { "DD2 MIXL", "DAC4 L Switch", "DAC4 L Mux" }, + { "DD2 MIXL", "DAC4 R Switch", "DAC4 R Mux" }, + { "DD2 MIXL", NULL, "dac mono4 left filter" }, + { "dac mono4 left filter", NULL, "PLL1", is_sys_clk_from_pll }, + { "DD2 MIXR", "Sto DAC Mix R Switch", "Stereo DAC MIXR" }, + { "DD2 MIXR", "Mono DAC Mix R Switch", "Mono DAC MIXR" }, + { "DD2 MIXR", "DAC4 L Switch", "DAC4 L Mux" }, + { "DD2 MIXR", "DAC4 R Switch", "DAC4 R Mux" }, + { "DD2 MIXR", NULL, "dac mono4 right filter" }, + { "dac mono4 right filter", NULL, "PLL1", is_sys_clk_from_pll }, + + { "Stereo DAC MIX", NULL, "Stereo DAC MIXL" }, + { "Stereo DAC MIX", NULL, "Stereo DAC MIXR" }, + { "Mono DAC MIX", NULL, "Mono DAC MIXL" }, + { "Mono DAC MIX", NULL, "Mono DAC MIXR" }, + { "DD1 MIX", NULL, "DD1 MIXL" }, + { "DD1 MIX", NULL, "DD1 MIXR" }, + { "DD2 MIX", NULL, "DD2 MIXL" }, + { "DD2 MIX", NULL, "DD2 MIXR" }, + + { "DAC12 SRC Mux", "STO1 DAC MIX", "Stereo DAC MIX" }, + { "DAC12 SRC Mux", "MONO DAC MIX", "Mono DAC MIX" }, + { "DAC12 SRC Mux", "DD MIX1", "DD1 MIX" }, + { "DAC12 SRC Mux", "DD MIX2", "DD2 MIX" }, + + { "DAC3 SRC Mux", "MONO DAC MIXL", "Mono DAC MIXL" }, + { "DAC3 SRC Mux", "MONO DAC MIXR", "Mono DAC MIXR" }, + { "DAC3 SRC Mux", "DD MIX1L", "DD1 MIXL" }, + { "DAC3 SRC Mux", "DD MIX2L", "DD2 MIXL" }, + + { "DAC 1", NULL, "DAC12 SRC Mux" }, + { "DAC 2", NULL, "DAC12 SRC Mux" }, + { "DAC 3", NULL, "DAC3 SRC Mux" }, + + { "PDM1 L Mux", "STO1 DAC MIX", "Stereo DAC MIXL" }, + { "PDM1 L Mux", "MONO DAC MIX", "Mono DAC MIXL" }, + { "PDM1 L Mux", "DD MIX1", "DD1 MIXL" }, + { "PDM1 L Mux", "DD MIX2", "DD2 MIXL" }, + { "PDM1 L Mux", NULL, "PDM1 Power" }, + { "PDM1 R Mux", "STO1 DAC MIX", "Stereo DAC MIXR" }, + { "PDM1 R Mux", "MONO DAC MIX", "Mono DAC MIXR" }, + { "PDM1 R Mux", "DD MIX1", "DD1 MIXR" }, + { "PDM1 R Mux", "DD MIX2", "DD2 MIXR" }, + { "PDM1 R Mux", NULL, "PDM1 Power" }, + { "PDM2 L Mux", "STO1 DAC MIX", "Stereo DAC MIXL" }, + { "PDM2 L Mux", "MONO DAC MIX", "Mono DAC MIXL" }, + { "PDM2 L Mux", "DD MIX1", "DD1 MIXL" }, + { "PDM2 L Mux", "DD MIX2", "DD2 MIXL" }, + { "PDM2 L Mux", NULL, "PDM2 Power" }, + { "PDM2 R Mux", "STO1 DAC MIX", "Stereo DAC MIXR" }, + { "PDM2 R Mux", "MONO DAC MIX", "Mono DAC MIXR" }, + { "PDM2 R Mux", "DD MIX1", "DD1 MIXR" }, + { "PDM2 R Mux", "DD MIX1", "DD2 MIXR" }, + { "PDM2 R Mux", NULL, "PDM2 Power" }, + + { "LOUT1 amp", NULL, "DAC 1" }, + { "LOUT2 amp", NULL, "DAC 2" }, + { "LOUT3 amp", NULL, "DAC 3" }, + + { "LOUT1 vref", NULL, "LOUT1 amp" }, + { "LOUT2 vref", NULL, "LOUT2 amp" }, + { "LOUT3 vref", NULL, "LOUT3 amp" }, + + { "LOUT1", NULL, "LOUT1 vref" }, + { "LOUT2", NULL, "LOUT2 vref" }, + { "LOUT3", NULL, "LOUT3 vref" }, + + { "PDM1L", NULL, "PDM1 L Mux" }, + { "PDM1R", NULL, "PDM1 R Mux" }, + { "PDM2L", NULL, "PDM2 L Mux" }, + { "PDM2R", NULL, "PDM2 R Mux" }, +}; + +static const struct snd_soc_dapm_route rt5677_dmic2_clk_1[] = { + { "DMIC L2", NULL, "DMIC1 power" }, + { "DMIC R2", NULL, "DMIC1 power" }, +}; + +static const struct snd_soc_dapm_route rt5677_dmic2_clk_2[] = { + { "DMIC L2", NULL, "DMIC2 power" }, + { "DMIC R2", NULL, "DMIC2 power" }, +}; + +static int rt5677_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int val_len = 0, val_clk, mask_clk; + int pre_div, bclk_ms, frame_size; + + rt5677->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5677->sysclk, rt5677->lrck[dai->id]); + if (pre_div < 0) { + dev_err(component->dev, "Unsupported clock setting: sysclk=%dHz lrck=%dHz\n", + rt5677->sysclk, rt5677->lrck[dai->id]); + return -EINVAL; + } + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", frame_size); + return -EINVAL; + } + bclk_ms = frame_size > 32; + rt5677->bclk[dai->id] = rt5677->lrck[dai->id] * (32 << bclk_ms); + + dev_dbg(dai->dev, "bclk is %dHz and lrck is %dHz\n", + rt5677->bclk[dai->id], rt5677->lrck[dai->id]); + dev_dbg(dai->dev, "bclk_ms is %d and pre_div is %d for iis %d\n", + bclk_ms, pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + val_len |= RT5677_I2S_DL_20; + break; + case 24: + val_len |= RT5677_I2S_DL_24; + break; + case 8: + val_len |= RT5677_I2S_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5677_AIF1: + mask_clk = RT5677_I2S_PD1_MASK; + val_clk = pre_div << RT5677_I2S_PD1_SFT; + regmap_update_bits(rt5677->regmap, RT5677_I2S1_SDP, + RT5677_I2S_DL_MASK, val_len); + regmap_update_bits(rt5677->regmap, RT5677_CLK_TREE_CTRL1, + mask_clk, val_clk); + break; + case RT5677_AIF2: + mask_clk = RT5677_I2S_PD2_MASK; + val_clk = pre_div << RT5677_I2S_PD2_SFT; + regmap_update_bits(rt5677->regmap, RT5677_I2S2_SDP, + RT5677_I2S_DL_MASK, val_len); + regmap_update_bits(rt5677->regmap, RT5677_CLK_TREE_CTRL1, + mask_clk, val_clk); + break; + case RT5677_AIF3: + mask_clk = RT5677_I2S_BCLK_MS3_MASK | RT5677_I2S_PD3_MASK; + val_clk = bclk_ms << RT5677_I2S_BCLK_MS3_SFT | + pre_div << RT5677_I2S_PD3_SFT; + regmap_update_bits(rt5677->regmap, RT5677_I2S3_SDP, + RT5677_I2S_DL_MASK, val_len); + regmap_update_bits(rt5677->regmap, RT5677_CLK_TREE_CTRL1, + mask_clk, val_clk); + break; + case RT5677_AIF4: + mask_clk = RT5677_I2S_BCLK_MS4_MASK | RT5677_I2S_PD4_MASK; + val_clk = bclk_ms << RT5677_I2S_BCLK_MS4_SFT | + pre_div << RT5677_I2S_PD4_SFT; + regmap_update_bits(rt5677->regmap, RT5677_I2S4_SDP, + RT5677_I2S_DL_MASK, val_len); + regmap_update_bits(rt5677->regmap, RT5677_CLK_TREE_CTRL1, + mask_clk, val_clk); + break; + default: + break; + } + + return 0; +} + +static int rt5677_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5677->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg_val |= RT5677_I2S_MS_S; + rt5677->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5677_I2S_BP_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5677_I2S_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5677_I2S_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5677_I2S_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5677_AIF1: + regmap_update_bits(rt5677->regmap, RT5677_I2S1_SDP, + RT5677_I2S_MS_MASK | RT5677_I2S_BP_MASK | + RT5677_I2S_DF_MASK, reg_val); + break; + case RT5677_AIF2: + regmap_update_bits(rt5677->regmap, RT5677_I2S2_SDP, + RT5677_I2S_MS_MASK | RT5677_I2S_BP_MASK | + RT5677_I2S_DF_MASK, reg_val); + break; + case RT5677_AIF3: + regmap_update_bits(rt5677->regmap, RT5677_I2S3_SDP, + RT5677_I2S_MS_MASK | RT5677_I2S_BP_MASK | + RT5677_I2S_DF_MASK, reg_val); + break; + case RT5677_AIF4: + regmap_update_bits(rt5677->regmap, RT5677_I2S4_SDP, + RT5677_I2S_MS_MASK | RT5677_I2S_BP_MASK | + RT5677_I2S_DF_MASK, reg_val); + break; + default: + break; + } + + + return 0; +} + +static int rt5677_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0; + + if (freq == rt5677->sysclk && clk_id == rt5677->sysclk_src) + return 0; + + switch (clk_id) { + case RT5677_SCLK_S_MCLK: + reg_val |= RT5677_SCLK_SRC_MCLK; + break; + case RT5677_SCLK_S_PLL1: + reg_val |= RT5677_SCLK_SRC_PLL1; + break; + case RT5677_SCLK_S_RCCLK: + reg_val |= RT5677_SCLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_SCLK_SRC_MASK, reg_val); + rt5677->sysclk = freq; + rt5677->sysclk_src = clk_id; + + dev_dbg(dai->dev, "Sysclk is %dHz and clock id is %d\n", freq, clk_id); + + return 0; +} + +/** + * rt5677_pll_calc - Calcualte PLL M/N/K code. + * @freq_in: external clock provided to codec. + * @freq_out: target clock which codec works on. + * @pll_code: Pointer to structure with M, N, K, bypass K and bypass M flag. + * + * Calcualte M/N/K code and bypass K/M flag to configure PLL for codec. + * + * Returns 0 for success or negative error code. + */ +static int rt5677_pll_calc(const unsigned int freq_in, + const unsigned int freq_out, struct rl6231_pll_code *pll_code) +{ + if (RT5677_PLL_INP_MIN > freq_in) + return -EINVAL; + + return rl6231_pll_calc(freq_in, freq_out, pll_code); +} + +static int rt5677_set_dai_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5677->pll_src && freq_in == rt5677->pll_in && + freq_out == rt5677->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5677->pll_in = 0; + rt5677->pll_out = 0; + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_SCLK_SRC_MASK, RT5677_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5677_PLL1_S_MCLK: + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_PLL1_SRC_MASK, RT5677_PLL1_SRC_MCLK); + break; + case RT5677_PLL1_S_BCLK1: + case RT5677_PLL1_S_BCLK2: + case RT5677_PLL1_S_BCLK3: + case RT5677_PLL1_S_BCLK4: + switch (dai->id) { + case RT5677_AIF1: + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_PLL1_SRC_MASK, RT5677_PLL1_SRC_BCLK1); + break; + case RT5677_AIF2: + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_PLL1_SRC_MASK, RT5677_PLL1_SRC_BCLK2); + break; + case RT5677_AIF3: + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_PLL1_SRC_MASK, RT5677_PLL1_SRC_BCLK3); + break; + case RT5677_AIF4: + regmap_update_bits(rt5677->regmap, RT5677_GLB_CLK1, + RT5677_PLL1_SRC_MASK, RT5677_PLL1_SRC_BCLK4); + break; + default: + break; + } + break; + default: + dev_err(component->dev, "Unknown PLL source %d\n", source); + return -EINVAL; + } + + ret = rt5677_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "m_bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + regmap_write(rt5677->regmap, RT5677_PLL1_CTRL1, + pll_code.n_code << RT5677_PLL_N_SFT | pll_code.k_code); + regmap_write(rt5677->regmap, RT5677_PLL1_CTRL2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5677_PLL_M_SFT | + pll_code.m_bp << RT5677_PLL_M_BP_SFT); + + rt5677->pll_in = freq_in; + rt5677->pll_out = freq_out; + rt5677->pll_src = source; + + return 0; +} + +static int rt5677_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + unsigned int val = 0, slot_width_25 = 0; + + if (rx_mask || tx_mask) + val |= (1 << 12); + + switch (slots) { + case 4: + val |= (1 << 10); + break; + case 6: + val |= (2 << 10); + break; + case 8: + val |= (3 << 10); + break; + case 2: + default: + break; + } + + switch (slot_width) { + case 20: + val |= (1 << 8); + break; + case 25: + slot_width_25 = 0x8080; + /* fall through */ + case 24: + val |= (2 << 8); + break; + case 32: + val |= (3 << 8); + break; + case 16: + default: + break; + } + + switch (dai->id) { + case RT5677_AIF1: + regmap_update_bits(rt5677->regmap, RT5677_TDM1_CTRL1, 0x1f00, + val); + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x8000, + slot_width_25); + break; + case RT5677_AIF2: + regmap_update_bits(rt5677->regmap, RT5677_TDM2_CTRL1, 0x1f00, + val); + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x80, + slot_width_25); + break; + default: + break; + } + + return 0; +} + +static int rt5677_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) { + rt5677_set_dsp_vad(component, false); + + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, + RT5677_LDO1_SEL_MASK | RT5677_LDO2_SEL_MASK, + 0x0055); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_BIAS_CUR4, + 0x0f00, 0x0f00); + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1, + RT5677_PWR_FV1 | RT5677_PWR_FV2 | + RT5677_PWR_VREF1 | RT5677_PWR_MB | + RT5677_PWR_BG | RT5677_PWR_VREF2, + RT5677_PWR_VREF1 | RT5677_PWR_MB | + RT5677_PWR_BG | RT5677_PWR_VREF2); + rt5677->is_vref_slow = false; + regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2, + RT5677_PWR_CORE, RT5677_PWR_CORE); + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, + 0x1, 0x1); + } + break; + + case SND_SOC_BIAS_STANDBY: + break; + + case SND_SOC_BIAS_OFF: + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x1, 0x0); + regmap_write(rt5677->regmap, RT5677_PWR_DIG1, 0x0000); + regmap_write(rt5677->regmap, RT5677_PWR_DIG2, 0x0000); + regmap_write(rt5677->regmap, RT5677_PWR_ANLG1, 0x0022); + regmap_write(rt5677->regmap, RT5677_PWR_ANLG2, 0x0000); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_BIAS_CUR4, 0x0f00, 0x0000); + + if (rt5677->dsp_vad_en) + rt5677_set_dsp_vad(component, true); + break; + + default: + break; + } + + return 0; +} + +#ifdef CONFIG_GPIOLIB +static void rt5677_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct rt5677_priv *rt5677 = gpiochip_get_data(chip); + + switch (offset) { + case RT5677_GPIO1 ... RT5677_GPIO5: + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL2, + 0x1 << (offset * 3 + 1), !!value << (offset * 3 + 1)); + break; + + case RT5677_GPIO6: + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL3, + RT5677_GPIO6_OUT_MASK, !!value << RT5677_GPIO6_OUT_SFT); + break; + + default: + break; + } +} + +static int rt5677_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct rt5677_priv *rt5677 = gpiochip_get_data(chip); + + switch (offset) { + case RT5677_GPIO1 ... RT5677_GPIO5: + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL2, + 0x3 << (offset * 3 + 1), + (0x2 | !!value) << (offset * 3 + 1)); + break; + + case RT5677_GPIO6: + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL3, + RT5677_GPIO6_DIR_MASK | RT5677_GPIO6_OUT_MASK, + RT5677_GPIO6_DIR_OUT | !!value << RT5677_GPIO6_OUT_SFT); + break; + + default: + break; + } + + return 0; +} + +static int rt5677_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct rt5677_priv *rt5677 = gpiochip_get_data(chip); + int value, ret; + + ret = regmap_read(rt5677->regmap, RT5677_GPIO_ST, &value); + if (ret < 0) + return ret; + + return (value & (0x1 << offset)) >> offset; +} + +static int rt5677_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct rt5677_priv *rt5677 = gpiochip_get_data(chip); + + switch (offset) { + case RT5677_GPIO1 ... RT5677_GPIO5: + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL2, + 0x1 << (offset * 3 + 2), 0x0); + break; + + case RT5677_GPIO6: + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL3, + RT5677_GPIO6_DIR_MASK, RT5677_GPIO6_DIR_IN); + break; + + default: + break; + } + + return 0; +} + +/** Configures the gpio as + * 0 - floating + * 1 - pull down + * 2 - pull up + */ +static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, + int value) +{ + int shift; + + switch (offset) { + case RT5677_GPIO1 ... RT5677_GPIO2: + shift = 2 * (1 - offset); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_DIG_IN_PIN_ST_CTRL2, + 0x3 << shift, + (value & 0x3) << shift); + break; + + case RT5677_GPIO3 ... RT5677_GPIO6: + shift = 2 * (9 - offset); + regmap_update_bits(rt5677->regmap, + RT5677_PR_BASE + RT5677_DIG_IN_PIN_ST_CTRL3, + 0x3 << shift, + (value & 0x3) << shift); + break; + + default: + break; + } +} + +static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct rt5677_priv *rt5677 = gpiochip_get_data(chip); + struct regmap_irq_chip_data *data = rt5677->irq_data; + int irq; + + if ((rt5677->pdata.jd1_gpio == 1 && offset == RT5677_GPIO1) || + (rt5677->pdata.jd1_gpio == 2 && + offset == RT5677_GPIO2) || + (rt5677->pdata.jd1_gpio == 3 && + offset == RT5677_GPIO3)) { + irq = RT5677_IRQ_JD1; + } else if ((rt5677->pdata.jd2_gpio == 1 && offset == RT5677_GPIO4) || + (rt5677->pdata.jd2_gpio == 2 && + offset == RT5677_GPIO5) || + (rt5677->pdata.jd2_gpio == 3 && + offset == RT5677_GPIO6)) { + irq = RT5677_IRQ_JD2; + } else if ((rt5677->pdata.jd3_gpio == 1 && + offset == RT5677_GPIO4) || + (rt5677->pdata.jd3_gpio == 2 && + offset == RT5677_GPIO5) || + (rt5677->pdata.jd3_gpio == 3 && + offset == RT5677_GPIO6)) { + irq = RT5677_IRQ_JD3; + } else { + return -ENXIO; + } + + return regmap_irq_get_virq(data, irq); +} + +static const struct gpio_chip rt5677_template_chip = { + .label = "rt5677", + .owner = THIS_MODULE, + .direction_output = rt5677_gpio_direction_out, + .set = rt5677_gpio_set, + .direction_input = rt5677_gpio_direction_in, + .get = rt5677_gpio_get, + .to_irq = rt5677_to_irq, + .can_sleep = 1, +}; + +static void rt5677_init_gpio(struct i2c_client *i2c) +{ + struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + int ret; + + rt5677->gpio_chip = rt5677_template_chip; + rt5677->gpio_chip.ngpio = RT5677_GPIO_NUM; + rt5677->gpio_chip.parent = &i2c->dev; + rt5677->gpio_chip.base = -1; + + ret = gpiochip_add_data(&rt5677->gpio_chip, rt5677); + if (ret != 0) + dev_err(&i2c->dev, "Failed to add GPIOs: %d\n", ret); +} + +static void rt5677_free_gpio(struct i2c_client *i2c) +{ + struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + + gpiochip_remove(&rt5677->gpio_chip); +} +#else +static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, + int value) +{ +} + +static void rt5677_init_gpio(struct i2c_client *i2c) +{ +} + +static void rt5677_free_gpio(struct i2c_client *i2c) +{ +} +#endif + +static int rt5677_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + int i; + + rt5677->component = component; + + if (rt5677->pdata.dmic2_clk_pin == RT5677_DMIC_CLK2) { + snd_soc_dapm_add_routes(dapm, + rt5677_dmic2_clk_2, + ARRAY_SIZE(rt5677_dmic2_clk_2)); + } else { /*use dmic1 clock by default*/ + snd_soc_dapm_add_routes(dapm, + rt5677_dmic2_clk_1, + ARRAY_SIZE(rt5677_dmic2_clk_1)); + } + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + regmap_write(rt5677->regmap, RT5677_DIG_MISC, 0x0020); + regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x0c00); + + for (i = 0; i < RT5677_GPIO_NUM; i++) + rt5677_gpio_config(rt5677, i, rt5677->pdata.gpio_config[i]); + + if (rt5677->irq_data) { + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, 0x8000, + 0x8000); + regmap_update_bits(rt5677->regmap, RT5677_DIG_MISC, 0x0018, + 0x0008); + + if (rt5677->pdata.jd1_gpio) + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, + RT5677_SEL_GPIO_JD1_MASK, + rt5677->pdata.jd1_gpio << + RT5677_SEL_GPIO_JD1_SFT); + + if (rt5677->pdata.jd2_gpio) + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, + RT5677_SEL_GPIO_JD2_MASK, + rt5677->pdata.jd2_gpio << + RT5677_SEL_GPIO_JD2_SFT); + + if (rt5677->pdata.jd3_gpio) + regmap_update_bits(rt5677->regmap, RT5677_JD_CTRL1, + RT5677_SEL_GPIO_JD3_MASK, + rt5677->pdata.jd3_gpio << + RT5677_SEL_GPIO_JD3_SFT); + } + + mutex_init(&rt5677->dsp_cmd_lock); + mutex_init(&rt5677->dsp_pri_lock); + + return 0; +} + +static void rt5677_remove(struct snd_soc_component *component) +{ + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + regmap_write(rt5677->regmap, RT5677_RESET, 0x10ec); + gpiod_set_value_cansleep(rt5677->pow_ldo2, 0); + gpiod_set_value_cansleep(rt5677->reset_pin, 1); +} + +#ifdef CONFIG_PM +static int rt5677_suspend(struct snd_soc_component *component) +{ + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + if (!rt5677->dsp_vad_en) { + regcache_cache_only(rt5677->regmap, true); + regcache_mark_dirty(rt5677->regmap); + + gpiod_set_value_cansleep(rt5677->pow_ldo2, 0); + gpiod_set_value_cansleep(rt5677->reset_pin, 1); + } + + return 0; +} + +static int rt5677_resume(struct snd_soc_component *component) +{ + struct rt5677_priv *rt5677 = snd_soc_component_get_drvdata(component); + + if (!rt5677->dsp_vad_en) { + rt5677->pll_src = 0; + rt5677->pll_in = 0; + rt5677->pll_out = 0; + gpiod_set_value_cansleep(rt5677->pow_ldo2, 1); + gpiod_set_value_cansleep(rt5677->reset_pin, 0); + if (rt5677->pow_ldo2 || rt5677->reset_pin) + msleep(10); + + regcache_cache_only(rt5677->regmap, false); + regcache_sync(rt5677->regmap); + } + + return 0; +} +#else +#define rt5677_suspend NULL +#define rt5677_resume NULL +#endif + +static int rt5677_read(void *context, unsigned int reg, unsigned int *val) +{ + struct i2c_client *client = context; + struct rt5677_priv *rt5677 = i2c_get_clientdata(client); + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { + mutex_lock(&rt5677->dsp_pri_lock); + rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, + reg & 0xff); + rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); + mutex_unlock(&rt5677->dsp_pri_lock); + } else { + rt5677_dsp_mode_i2c_read(rt5677, reg, val); + } + } else { + regmap_read(rt5677->regmap_physical, reg, val); + } + + return 0; +} + +static int rt5677_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; + struct rt5677_priv *rt5677 = i2c_get_clientdata(client); + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { + mutex_lock(&rt5677->dsp_pri_lock); + rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, + reg & 0xff); + rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, + val); + mutex_unlock(&rt5677->dsp_pri_lock); + } else { + rt5677_dsp_mode_i2c_write(rt5677, reg, val); + } + } else { + regmap_write(rt5677->regmap_physical, reg, val); + } + + return 0; +} + +#define RT5677_STEREO_RATES SNDRV_PCM_RATE_8000_96000 +#define RT5677_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5677_aif_dai_ops = { + .hw_params = rt5677_hw_params, + .set_fmt = rt5677_set_dai_fmt, + .set_sysclk = rt5677_set_dai_sysclk, + .set_pll = rt5677_set_dai_pll, + .set_tdm_slot = rt5677_set_tdm_slot, +}; + +static struct snd_soc_dai_driver rt5677_dai[] = { + { + .name = "rt5677-aif1", + .id = RT5677_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .ops = &rt5677_aif_dai_ops, + }, + { + .name = "rt5677-aif2", + .id = RT5677_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .ops = &rt5677_aif_dai_ops, + }, + { + .name = "rt5677-aif3", + .id = RT5677_AIF3, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .ops = &rt5677_aif_dai_ops, + }, + { + .name = "rt5677-aif4", + .id = RT5677_AIF4, + .playback = { + .stream_name = "AIF4 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .capture = { + .stream_name = "AIF4 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .ops = &rt5677_aif_dai_ops, + }, + { + .name = "rt5677-slimbus", + .id = RT5677_AIF5, + .playback = { + .stream_name = "SLIMBus Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .capture = { + .stream_name = "SLIMBus Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5677_STEREO_RATES, + .formats = RT5677_FORMATS, + }, + .ops = &rt5677_aif_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5677 = { + .probe = rt5677_probe, + .remove = rt5677_remove, + .suspend = rt5677_suspend, + .resume = rt5677_resume, + .set_bias_level = rt5677_set_bias_level, + .controls = rt5677_snd_controls, + .num_controls = ARRAY_SIZE(rt5677_snd_controls), + .dapm_widgets = rt5677_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5677_dapm_widgets), + .dapm_routes = rt5677_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5677_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5677_regmap_physical = { + .name = "physical", + .reg_bits = 8, + .val_bits = 16, + + .max_register = RT5677_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5677_ranges) * + RT5677_PR_SPACING), + .readable_reg = rt5677_readable_register, + + .cache_type = REGCACHE_NONE, + .ranges = rt5677_ranges, + .num_ranges = ARRAY_SIZE(rt5677_ranges), +}; + +static const struct regmap_config rt5677_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = RT5677_VENDOR_ID2 + 1 + (ARRAY_SIZE(rt5677_ranges) * + RT5677_PR_SPACING), + + .volatile_reg = rt5677_volatile_register, + .readable_reg = rt5677_readable_register, + .reg_read = rt5677_read, + .reg_write = rt5677_write, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5677_reg, + .num_reg_defaults = ARRAY_SIZE(rt5677_reg), + .ranges = rt5677_ranges, + .num_ranges = ARRAY_SIZE(rt5677_ranges), +}; + +static const struct of_device_id rt5677_of_match[] = { + { .compatible = "realtek,rt5677", .data = (const void *)RT5677 }, + { } +}; +MODULE_DEVICE_TABLE(of, rt5677_of_match); + +static const struct acpi_device_id rt5677_acpi_match[] = { + { "RT5677CE", RT5677 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, rt5677_acpi_match); + +static void rt5677_read_acpi_properties(struct rt5677_priv *rt5677, + struct device *dev) +{ + u32 val; + + if (!device_property_read_u32(dev, "DCLK", &val)) + rt5677->pdata.dmic2_clk_pin = val; + + rt5677->pdata.in1_diff = device_property_read_bool(dev, "IN1"); + rt5677->pdata.in2_diff = device_property_read_bool(dev, "IN2"); + rt5677->pdata.lout1_diff = device_property_read_bool(dev, "OUT1"); + rt5677->pdata.lout2_diff = device_property_read_bool(dev, "OUT2"); + rt5677->pdata.lout3_diff = device_property_read_bool(dev, "OUT3"); + + device_property_read_u32(dev, "JD1", &rt5677->pdata.jd1_gpio); + device_property_read_u32(dev, "JD2", &rt5677->pdata.jd2_gpio); + device_property_read_u32(dev, "JD3", &rt5677->pdata.jd3_gpio); +} + +static void rt5677_read_device_properties(struct rt5677_priv *rt5677, + struct device *dev) +{ + rt5677->pdata.in1_diff = device_property_read_bool(dev, + "realtek,in1-differential"); + rt5677->pdata.in2_diff = device_property_read_bool(dev, + "realtek,in2-differential"); + rt5677->pdata.lout1_diff = device_property_read_bool(dev, + "realtek,lout1-differential"); + rt5677->pdata.lout2_diff = device_property_read_bool(dev, + "realtek,lout2-differential"); + rt5677->pdata.lout3_diff = device_property_read_bool(dev, + "realtek,lout3-differential"); + + device_property_read_u8_array(dev, "realtek,gpio-config", + rt5677->pdata.gpio_config, RT5677_GPIO_NUM); + + device_property_read_u32(dev, "realtek,jd1-gpio", + &rt5677->pdata.jd1_gpio); + device_property_read_u32(dev, "realtek,jd2-gpio", + &rt5677->pdata.jd2_gpio); + device_property_read_u32(dev, "realtek,jd3-gpio", + &rt5677->pdata.jd3_gpio); +} + +static struct regmap_irq rt5677_irqs[] = { + [RT5677_IRQ_JD1] = { + .reg_offset = 0, + .mask = RT5677_EN_IRQ_GPIO_JD1, + }, + [RT5677_IRQ_JD2] = { + .reg_offset = 0, + .mask = RT5677_EN_IRQ_GPIO_JD2, + }, + [RT5677_IRQ_JD3] = { + .reg_offset = 0, + .mask = RT5677_EN_IRQ_GPIO_JD3, + }, +}; + +static struct regmap_irq_chip rt5677_irq_chip = { + .name = "rt5677", + .irqs = rt5677_irqs, + .num_irqs = ARRAY_SIZE(rt5677_irqs), + + .num_regs = 1, + .status_base = RT5677_IRQ_CTRL1, + .mask_base = RT5677_IRQ_CTRL1, + .mask_invert = 1, +}; + +static int rt5677_init_irq(struct i2c_client *i2c) +{ + int ret; + struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + + if (!rt5677->pdata.jd1_gpio && + !rt5677->pdata.jd2_gpio && + !rt5677->pdata.jd3_gpio) + return 0; + + if (!i2c->irq) { + dev_err(&i2c->dev, "No interrupt specified\n"); + return -EINVAL; + } + + ret = regmap_add_irq_chip(rt5677->regmap, i2c->irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, + &rt5677_irq_chip, &rt5677->irq_data); + + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register IRQ chip: %d\n", ret); + return ret; + } + + return 0; +} + +static void rt5677_free_irq(struct i2c_client *i2c) +{ + struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); + + if (rt5677->irq_data) + regmap_del_irq_chip(i2c->irq, rt5677->irq_data); +} + +static int rt5677_i2c_probe(struct i2c_client *i2c) +{ + struct rt5677_priv *rt5677; + int ret; + unsigned int val; + + rt5677 = devm_kzalloc(&i2c->dev, sizeof(struct rt5677_priv), + GFP_KERNEL); + if (rt5677 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5677); + + if (i2c->dev.of_node) { + const struct of_device_id *match_id; + + match_id = of_match_device(rt5677_of_match, &i2c->dev); + if (match_id) + rt5677->type = (enum rt5677_type)match_id->data; + + rt5677_read_device_properties(rt5677, &i2c->dev); + } else if (ACPI_HANDLE(&i2c->dev)) { + const struct acpi_device_id *acpi_id; + + acpi_id = acpi_match_device(rt5677_acpi_match, &i2c->dev); + if (acpi_id) + rt5677->type = (enum rt5677_type)acpi_id->driver_data; + + rt5677_read_acpi_properties(rt5677, &i2c->dev); + } else { + return -EINVAL; + } + + /* pow-ldo2 and reset are optional. The codec pins may be statically + * connected on the board without gpios. If the gpio device property + * isn't specified, devm_gpiod_get_optional returns NULL. + */ + rt5677->pow_ldo2 = devm_gpiod_get_optional(&i2c->dev, + "realtek,pow-ldo2", GPIOD_OUT_HIGH); + if (IS_ERR(rt5677->pow_ldo2)) { + ret = PTR_ERR(rt5677->pow_ldo2); + dev_err(&i2c->dev, "Failed to request POW_LDO2: %d\n", ret); + return ret; + } + rt5677->reset_pin = devm_gpiod_get_optional(&i2c->dev, + "realtek,reset", GPIOD_OUT_LOW); + if (IS_ERR(rt5677->reset_pin)) { + ret = PTR_ERR(rt5677->reset_pin); + dev_err(&i2c->dev, "Failed to request RESET: %d\n", ret); + return ret; + } + + if (rt5677->pow_ldo2 || rt5677->reset_pin) { + /* Wait a while until I2C bus becomes available. The datasheet + * does not specify the exact we should wait but startup + * sequence mentiones at least a few milliseconds. + */ + msleep(10); + } + + rt5677->regmap_physical = devm_regmap_init_i2c(i2c, + &rt5677_regmap_physical); + if (IS_ERR(rt5677->regmap_physical)) { + ret = PTR_ERR(rt5677->regmap_physical); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + rt5677->regmap = devm_regmap_init(&i2c->dev, NULL, i2c, &rt5677_regmap); + if (IS_ERR(rt5677->regmap)) { + ret = PTR_ERR(rt5677->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regmap_read(rt5677->regmap, RT5677_VENDOR_ID2, &val); + if (val != RT5677_DEVICE_ID) { + dev_err(&i2c->dev, + "Device with ID register %#x is not rt5677\n", val); + return -ENODEV; + } + + regmap_write(rt5677->regmap, RT5677_RESET, 0x10ec); + + ret = regmap_register_patch(rt5677->regmap, init_list, + ARRAY_SIZE(init_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + if (rt5677->pdata.in1_diff) + regmap_update_bits(rt5677->regmap, RT5677_IN1, + RT5677_IN_DF1, RT5677_IN_DF1); + + if (rt5677->pdata.in2_diff) + regmap_update_bits(rt5677->regmap, RT5677_IN1, + RT5677_IN_DF2, RT5677_IN_DF2); + + if (rt5677->pdata.lout1_diff) + regmap_update_bits(rt5677->regmap, RT5677_LOUT1, + RT5677_LOUT1_L_DF, RT5677_LOUT1_L_DF); + + if (rt5677->pdata.lout2_diff) + regmap_update_bits(rt5677->regmap, RT5677_LOUT1, + RT5677_LOUT2_L_DF, RT5677_LOUT2_L_DF); + + if (rt5677->pdata.lout3_diff) + regmap_update_bits(rt5677->regmap, RT5677_LOUT1, + RT5677_LOUT3_L_DF, RT5677_LOUT3_L_DF); + + if (rt5677->pdata.dmic2_clk_pin == RT5677_DMIC_CLK2) { + regmap_update_bits(rt5677->regmap, RT5677_GEN_CTRL2, + RT5677_GPIO5_FUNC_MASK, + RT5677_GPIO5_FUNC_DMIC); + regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL2, + RT5677_GPIO5_DIR_MASK, + RT5677_GPIO5_DIR_OUT); + } + + if (rt5677->pdata.micbias1_vdd_3v3) + regmap_update_bits(rt5677->regmap, RT5677_MICBIAS, + RT5677_MICBIAS1_CTRL_VDD_MASK, + RT5677_MICBIAS1_CTRL_VDD_3_3V); + + rt5677_init_gpio(i2c); + rt5677_init_irq(i2c); + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5677, + rt5677_dai, ARRAY_SIZE(rt5677_dai)); +} + +static int rt5677_i2c_remove(struct i2c_client *i2c) +{ + rt5677_free_irq(i2c); + rt5677_free_gpio(i2c); + + return 0; +} + +static struct i2c_driver rt5677_i2c_driver = { + .driver = { + .name = "rt5677", + .of_match_table = rt5677_of_match, + .acpi_match_table = ACPI_PTR(rt5677_acpi_match), + }, + .probe_new = rt5677_i2c_probe, + .remove = rt5677_i2c_remove, +}; +module_i2c_driver(rt5677_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5677 driver"); +MODULE_AUTHOR("Oder Chiou "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h new file mode 100644 index 000000000..183d92b03 --- /dev/null +++ b/sound/soc/codecs/rt5677.h @@ -0,0 +1,1822 @@ +/* + * rt5677.h -- RT5677 ALSA SoC audio driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Oder Chiou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5677_H__ +#define __RT5677_H__ + +#include +#include + +/* Info */ +#define RT5677_RESET 0x00 +#define RT5677_VENDOR_ID 0xfd +#define RT5677_VENDOR_ID1 0xfe +#define RT5677_VENDOR_ID2 0xff +/* I/O - Output */ +#define RT5677_LOUT1 0x01 +/* I/O - Input */ +#define RT5677_IN1 0x03 +#define RT5677_MICBIAS 0x04 +/* I/O - SLIMBus */ +#define RT5677_SLIMBUS_PARAM 0x07 +#define RT5677_SLIMBUS_RX 0x08 +#define RT5677_SLIMBUS_CTRL 0x09 +/* I/O */ +#define RT5677_SIDETONE_CTRL 0x13 +/* I/O - ADC/DAC */ +#define RT5677_ANA_DAC1_2_3_SRC 0x15 +#define RT5677_IF_DSP_DAC3_4_MIXER 0x16 +#define RT5677_DAC4_DIG_VOL 0x17 +#define RT5677_DAC3_DIG_VOL 0x18 +#define RT5677_DAC1_DIG_VOL 0x19 +#define RT5677_DAC2_DIG_VOL 0x1a +#define RT5677_IF_DSP_DAC2_MIXER 0x1b +#define RT5677_STO1_ADC_DIG_VOL 0x1c +#define RT5677_MONO_ADC_DIG_VOL 0x1d +#define RT5677_STO1_2_ADC_BST 0x1e +#define RT5677_STO2_ADC_DIG_VOL 0x1f +/* Mixer - D-D */ +#define RT5677_ADC_BST_CTRL2 0x20 +#define RT5677_STO3_4_ADC_BST 0x21 +#define RT5677_STO3_ADC_DIG_VOL 0x22 +#define RT5677_STO4_ADC_DIG_VOL 0x23 +#define RT5677_STO4_ADC_MIXER 0x24 +#define RT5677_STO3_ADC_MIXER 0x25 +#define RT5677_STO2_ADC_MIXER 0x26 +#define RT5677_STO1_ADC_MIXER 0x27 +#define RT5677_MONO_ADC_MIXER 0x28 +#define RT5677_ADC_IF_DSP_DAC1_MIXER 0x29 +#define RT5677_STO1_DAC_MIXER 0x2a +#define RT5677_MONO_DAC_MIXER 0x2b +#define RT5677_DD1_MIXER 0x2c +#define RT5677_DD2_MIXER 0x2d +#define RT5677_IF3_DATA 0x2f +#define RT5677_IF4_DATA 0x30 +/* Mixer - PDM */ +#define RT5677_PDM_OUT_CTRL 0x31 +#define RT5677_PDM_DATA_CTRL1 0x32 +#define RT5677_PDM_DATA_CTRL2 0x33 +#define RT5677_PDM1_DATA_CTRL2 0x34 +#define RT5677_PDM1_DATA_CTRL3 0x35 +#define RT5677_PDM1_DATA_CTRL4 0x36 +#define RT5677_PDM2_DATA_CTRL2 0x37 +#define RT5677_PDM2_DATA_CTRL3 0x38 +#define RT5677_PDM2_DATA_CTRL4 0x39 +/* TDM */ +#define RT5677_TDM1_CTRL1 0x3b +#define RT5677_TDM1_CTRL2 0x3c +#define RT5677_TDM1_CTRL3 0x3d +#define RT5677_TDM1_CTRL4 0x3e +#define RT5677_TDM1_CTRL5 0x3f +#define RT5677_TDM2_CTRL1 0x40 +#define RT5677_TDM2_CTRL2 0x41 +#define RT5677_TDM2_CTRL3 0x42 +#define RT5677_TDM2_CTRL4 0x43 +#define RT5677_TDM2_CTRL5 0x44 +/* I2C_MASTER_CTRL */ +#define RT5677_I2C_MASTER_CTRL1 0x47 +#define RT5677_I2C_MASTER_CTRL2 0x48 +#define RT5677_I2C_MASTER_CTRL3 0x49 +#define RT5677_I2C_MASTER_CTRL4 0x4a +#define RT5677_I2C_MASTER_CTRL5 0x4b +#define RT5677_I2C_MASTER_CTRL6 0x4c +#define RT5677_I2C_MASTER_CTRL7 0x4d +#define RT5677_I2C_MASTER_CTRL8 0x4e +/* DMIC */ +#define RT5677_DMIC_CTRL1 0x50 +#define RT5677_DMIC_CTRL2 0x51 +/* Haptic Generator */ +#define RT5677_HAP_GENE_CTRL1 0x56 +#define RT5677_HAP_GENE_CTRL2 0x57 +#define RT5677_HAP_GENE_CTRL3 0x58 +#define RT5677_HAP_GENE_CTRL4 0x59 +#define RT5677_HAP_GENE_CTRL5 0x5a +#define RT5677_HAP_GENE_CTRL6 0x5b +#define RT5677_HAP_GENE_CTRL7 0x5c +#define RT5677_HAP_GENE_CTRL8 0x5d +#define RT5677_HAP_GENE_CTRL9 0x5e +#define RT5677_HAP_GENE_CTRL10 0x5f +/* Power */ +#define RT5677_PWR_DIG1 0x61 +#define RT5677_PWR_DIG2 0x62 +#define RT5677_PWR_ANLG1 0x63 +#define RT5677_PWR_ANLG2 0x64 +#define RT5677_PWR_DSP1 0x65 +#define RT5677_PWR_DSP_ST 0x66 +#define RT5677_PWR_DSP2 0x67 +#define RT5677_ADC_DAC_HPF_CTRL1 0x68 +/* Private Register Control */ +#define RT5677_PRIV_INDEX 0x6a +#define RT5677_PRIV_DATA 0x6c +/* Format - ADC/DAC */ +#define RT5677_I2S4_SDP 0x6f +#define RT5677_I2S1_SDP 0x70 +#define RT5677_I2S2_SDP 0x71 +#define RT5677_I2S3_SDP 0x72 +#define RT5677_CLK_TREE_CTRL1 0x73 +#define RT5677_CLK_TREE_CTRL2 0x74 +#define RT5677_CLK_TREE_CTRL3 0x75 +/* Function - Analog */ +#define RT5677_PLL1_CTRL1 0x7a +#define RT5677_PLL1_CTRL2 0x7b +#define RT5677_PLL2_CTRL1 0x7c +#define RT5677_PLL2_CTRL2 0x7d +#define RT5677_GLB_CLK1 0x80 +#define RT5677_GLB_CLK2 0x81 +#define RT5677_ASRC_1 0x83 +#define RT5677_ASRC_2 0x84 +#define RT5677_ASRC_3 0x85 +#define RT5677_ASRC_4 0x86 +#define RT5677_ASRC_5 0x87 +#define RT5677_ASRC_6 0x88 +#define RT5677_ASRC_7 0x89 +#define RT5677_ASRC_8 0x8a +#define RT5677_ASRC_9 0x8b +#define RT5677_ASRC_10 0x8c +#define RT5677_ASRC_11 0x8d +#define RT5677_ASRC_12 0x8e +#define RT5677_ASRC_13 0x8f +#define RT5677_ASRC_14 0x90 +#define RT5677_ASRC_15 0x91 +#define RT5677_ASRC_16 0x92 +#define RT5677_ASRC_17 0x93 +#define RT5677_ASRC_18 0x94 +#define RT5677_ASRC_19 0x95 +#define RT5677_ASRC_20 0x97 +#define RT5677_ASRC_21 0x98 +#define RT5677_ASRC_22 0x99 +#define RT5677_ASRC_23 0x9a +#define RT5677_VAD_CTRL1 0x9c +#define RT5677_VAD_CTRL2 0x9d +#define RT5677_VAD_CTRL3 0x9e +#define RT5677_VAD_CTRL4 0x9f +#define RT5677_VAD_CTRL5 0xa0 +/* Function - Digital */ +#define RT5677_DSP_INB_CTRL1 0xa3 +#define RT5677_DSP_INB_CTRL2 0xa4 +#define RT5677_DSP_IN_OUTB_CTRL 0xa5 +#define RT5677_DSP_OUTB0_1_DIG_VOL 0xa6 +#define RT5677_DSP_OUTB2_3_DIG_VOL 0xa7 +#define RT5677_DSP_OUTB4_5_DIG_VOL 0xa8 +#define RT5677_DSP_OUTB6_7_DIG_VOL 0xa9 +#define RT5677_ADC_EQ_CTRL1 0xae +#define RT5677_ADC_EQ_CTRL2 0xaf +#define RT5677_EQ_CTRL1 0xb0 +#define RT5677_EQ_CTRL2 0xb1 +#define RT5677_EQ_CTRL3 0xb2 +#define RT5677_SOFT_VOL_ZERO_CROSS1 0xb3 +#define RT5677_JD_CTRL1 0xb5 +#define RT5677_JD_CTRL2 0xb6 +#define RT5677_JD_CTRL3 0xb8 +#define RT5677_IRQ_CTRL1 0xbd +#define RT5677_IRQ_CTRL2 0xbe +#define RT5677_GPIO_ST 0xbf +#define RT5677_GPIO_CTRL1 0xc0 +#define RT5677_GPIO_CTRL2 0xc1 +#define RT5677_GPIO_CTRL3 0xc2 +#define RT5677_STO1_ADC_HI_FILTER1 0xc5 +#define RT5677_STO1_ADC_HI_FILTER2 0xc6 +#define RT5677_MONO_ADC_HI_FILTER1 0xc7 +#define RT5677_MONO_ADC_HI_FILTER2 0xc8 +#define RT5677_STO2_ADC_HI_FILTER1 0xc9 +#define RT5677_STO2_ADC_HI_FILTER2 0xca +#define RT5677_STO3_ADC_HI_FILTER1 0xcb +#define RT5677_STO3_ADC_HI_FILTER2 0xcc +#define RT5677_STO4_ADC_HI_FILTER1 0xcd +#define RT5677_STO4_ADC_HI_FILTER2 0xce +#define RT5677_MB_DRC_CTRL1 0xd0 +#define RT5677_DRC1_CTRL1 0xd2 +#define RT5677_DRC1_CTRL2 0xd3 +#define RT5677_DRC1_CTRL3 0xd4 +#define RT5677_DRC1_CTRL4 0xd5 +#define RT5677_DRC1_CTRL5 0xd6 +#define RT5677_DRC1_CTRL6 0xd7 +#define RT5677_DRC2_CTRL1 0xd8 +#define RT5677_DRC2_CTRL2 0xd9 +#define RT5677_DRC2_CTRL3 0xda +#define RT5677_DRC2_CTRL4 0xdb +#define RT5677_DRC2_CTRL5 0xdc +#define RT5677_DRC2_CTRL6 0xdd +#define RT5677_DRC1_HL_CTRL1 0xde +#define RT5677_DRC1_HL_CTRL2 0xdf +#define RT5677_DRC2_HL_CTRL1 0xe0 +#define RT5677_DRC2_HL_CTRL2 0xe1 +#define RT5677_DSP_INB1_SRC_CTRL1 0xe3 +#define RT5677_DSP_INB1_SRC_CTRL2 0xe4 +#define RT5677_DSP_INB1_SRC_CTRL3 0xe5 +#define RT5677_DSP_INB1_SRC_CTRL4 0xe6 +#define RT5677_DSP_INB2_SRC_CTRL1 0xe7 +#define RT5677_DSP_INB2_SRC_CTRL2 0xe8 +#define RT5677_DSP_INB2_SRC_CTRL3 0xe9 +#define RT5677_DSP_INB2_SRC_CTRL4 0xea +#define RT5677_DSP_INB3_SRC_CTRL1 0xeb +#define RT5677_DSP_INB3_SRC_CTRL2 0xec +#define RT5677_DSP_INB3_SRC_CTRL3 0xed +#define RT5677_DSP_INB3_SRC_CTRL4 0xee +#define RT5677_DSP_OUTB1_SRC_CTRL1 0xef +#define RT5677_DSP_OUTB1_SRC_CTRL2 0xf0 +#define RT5677_DSP_OUTB1_SRC_CTRL3 0xf1 +#define RT5677_DSP_OUTB1_SRC_CTRL4 0xf2 +#define RT5677_DSP_OUTB2_SRC_CTRL1 0xf3 +#define RT5677_DSP_OUTB2_SRC_CTRL2 0xf4 +#define RT5677_DSP_OUTB2_SRC_CTRL3 0xf5 +#define RT5677_DSP_OUTB2_SRC_CTRL4 0xf6 + +/* Virtual DSP Mixer Control */ +#define RT5677_DSP_OUTB_0123_MIXER_CTRL 0xf7 +#define RT5677_DSP_OUTB_45_MIXER_CTRL 0xf8 +#define RT5677_DSP_OUTB_67_MIXER_CTRL 0xf9 + +/* General Control */ +#define RT5677_DIG_MISC 0xfa +#define RT5677_GEN_CTRL1 0xfb +#define RT5677_GEN_CTRL2 0xfc + +/* DSP Mode I2C Control*/ +#define RT5677_DSP_I2C_OP_CODE 0x00 +#define RT5677_DSP_I2C_ADDR_LSB 0x01 +#define RT5677_DSP_I2C_ADDR_MSB 0x02 +#define RT5677_DSP_I2C_DATA_LSB 0x03 +#define RT5677_DSP_I2C_DATA_MSB 0x04 + +/* Index of Codec Private Register definition */ +#define RT5677_PR_DRC1_CTRL_1 0x01 +#define RT5677_PR_DRC1_CTRL_2 0x02 +#define RT5677_PR_DRC1_CTRL_3 0x03 +#define RT5677_PR_DRC1_CTRL_4 0x04 +#define RT5677_PR_DRC1_CTRL_5 0x05 +#define RT5677_PR_DRC1_CTRL_6 0x06 +#define RT5677_PR_DRC1_CTRL_7 0x07 +#define RT5677_PR_DRC2_CTRL_1 0x08 +#define RT5677_PR_DRC2_CTRL_2 0x09 +#define RT5677_PR_DRC2_CTRL_3 0x0a +#define RT5677_PR_DRC2_CTRL_4 0x0b +#define RT5677_PR_DRC2_CTRL_5 0x0c +#define RT5677_PR_DRC2_CTRL_6 0x0d +#define RT5677_PR_DRC2_CTRL_7 0x0e +#define RT5677_BIAS_CUR1 0x10 +#define RT5677_BIAS_CUR2 0x12 +#define RT5677_BIAS_CUR3 0x13 +#define RT5677_BIAS_CUR4 0x14 +#define RT5677_BIAS_CUR5 0x15 +#define RT5677_VREF_LOUT_CTRL 0x17 +#define RT5677_DIG_VOL_CTRL1 0x1a +#define RT5677_DIG_VOL_CTRL2 0x1b +#define RT5677_ANA_ADC_GAIN_CTRL 0x1e +#define RT5677_VAD_SRAM_TEST1 0x20 +#define RT5677_VAD_SRAM_TEST2 0x21 +#define RT5677_VAD_SRAM_TEST3 0x22 +#define RT5677_VAD_SRAM_TEST4 0x23 +#define RT5677_PAD_DRV_CTRL 0x26 +#define RT5677_DIG_IN_PIN_ST_CTRL1 0x29 +#define RT5677_DIG_IN_PIN_ST_CTRL2 0x2a +#define RT5677_DIG_IN_PIN_ST_CTRL3 0x2b +#define RT5677_PLL1_INT 0x38 +#define RT5677_PLL2_INT 0x39 +#define RT5677_TEST_CTRL1 0x3a +#define RT5677_TEST_CTRL2 0x3b +#define RT5677_TEST_CTRL3 0x3c +#define RT5677_CHOP_DAC_ADC 0x3d +#define RT5677_SOFT_DEPOP_DAC_CLK_CTRL 0x3e +#define RT5677_CROSS_OVER_FILTER1 0x90 +#define RT5677_CROSS_OVER_FILTER2 0x91 +#define RT5677_CROSS_OVER_FILTER3 0x92 +#define RT5677_CROSS_OVER_FILTER4 0x93 +#define RT5677_CROSS_OVER_FILTER5 0x94 +#define RT5677_CROSS_OVER_FILTER6 0x95 +#define RT5677_CROSS_OVER_FILTER7 0x96 +#define RT5677_CROSS_OVER_FILTER8 0x97 +#define RT5677_CROSS_OVER_FILTER9 0x98 +#define RT5677_CROSS_OVER_FILTER10 0x99 + +/* global definition */ +#define RT5677_L_MUTE (0x1 << 15) +#define RT5677_L_MUTE_SFT 15 +#define RT5677_VOL_L_MUTE (0x1 << 14) +#define RT5677_VOL_L_SFT 14 +#define RT5677_R_MUTE (0x1 << 7) +#define RT5677_R_MUTE_SFT 7 +#define RT5677_VOL_R_MUTE (0x1 << 6) +#define RT5677_VOL_R_SFT 6 +#define RT5677_L_VOL_MASK (0x7f << 9) +#define RT5677_L_VOL_SFT 9 +#define RT5677_R_VOL_MASK (0x7f << 1) +#define RT5677_R_VOL_SFT 1 + +/* LOUT1 Control (0x01) */ +#define RT5677_LOUT1_L_MUTE (0x1 << 15) +#define RT5677_LOUT1_L_MUTE_SFT (15) +#define RT5677_LOUT1_L_DF (0x1 << 14) +#define RT5677_LOUT1_L_DF_SFT (14) +#define RT5677_LOUT2_L_MUTE (0x1 << 13) +#define RT5677_LOUT2_L_MUTE_SFT (13) +#define RT5677_LOUT2_L_DF (0x1 << 12) +#define RT5677_LOUT2_L_DF_SFT (12) +#define RT5677_LOUT3_L_MUTE (0x1 << 11) +#define RT5677_LOUT3_L_MUTE_SFT (11) +#define RT5677_LOUT3_L_DF (0x1 << 10) +#define RT5677_LOUT3_L_DF_SFT (10) +#define RT5677_LOUT1_ENH_DRV (0x1 << 9) +#define RT5677_LOUT1_ENH_DRV_SFT (9) +#define RT5677_LOUT2_ENH_DRV (0x1 << 8) +#define RT5677_LOUT2_ENH_DRV_SFT (8) +#define RT5677_LOUT3_ENH_DRV (0x1 << 7) +#define RT5677_LOUT3_ENH_DRV_SFT (7) + +/* IN1 Control (0x03) */ +#define RT5677_BST_MASK1 (0xf << 12) +#define RT5677_BST_SFT1 12 +#define RT5677_BST_MASK2 (0xf << 8) +#define RT5677_BST_SFT2 8 +#define RT5677_IN_DF1 (0x1 << 7) +#define RT5677_IN_DF1_SFT 7 +#define RT5677_IN_DF2 (0x1 << 6) +#define RT5677_IN_DF2_SFT 6 + +/* Micbias Control (0x04) */ +#define RT5677_MICBIAS1_OUTVOLT_MASK (0x1 << 15) +#define RT5677_MICBIAS1_OUTVOLT_SFT (15) +#define RT5677_MICBIAS1_OUTVOLT_2_7V (0x0 << 15) +#define RT5677_MICBIAS1_OUTVOLT_2_25V (0x1 << 15) +#define RT5677_MICBIAS1_CTRL_VDD_MASK (0x1 << 14) +#define RT5677_MICBIAS1_CTRL_VDD_SFT (14) +#define RT5677_MICBIAS1_CTRL_VDD_1_8V (0x0 << 14) +#define RT5677_MICBIAS1_CTRL_VDD_3_3V (0x1 << 14) +#define RT5677_MICBIAS1_OVCD_MASK (0x1 << 11) +#define RT5677_MICBIAS1_OVCD_SHIFT (11) +#define RT5677_MICBIAS1_OVCD_DIS (0x0 << 11) +#define RT5677_MICBIAS1_OVCD_EN (0x1 << 11) +#define RT5677_MICBIAS1_OVTH_MASK (0x3 << 9) +#define RT5677_MICBIAS1_OVTH_SFT 9 +#define RT5677_MICBIAS1_OVTH_640UA (0x0 << 9) +#define RT5677_MICBIAS1_OVTH_1280UA (0x1 << 9) +#define RT5677_MICBIAS1_OVTH_1920UA (0x2 << 9) + +/* SLIMbus Parameter (0x07) */ + +/* SLIMbus Rx (0x08) */ +#define RT5677_SLB_ADC4_MASK (0x3 << 6) +#define RT5677_SLB_ADC4_SFT 6 +#define RT5677_SLB_ADC3_MASK (0x3 << 4) +#define RT5677_SLB_ADC3_SFT 4 +#define RT5677_SLB_ADC2_MASK (0x3 << 2) +#define RT5677_SLB_ADC2_SFT 2 +#define RT5677_SLB_ADC1_MASK (0x3 << 0) +#define RT5677_SLB_ADC1_SFT 0 + +/* SLIMBus control (0x09) */ + +/* Sidetone Control (0x13) */ +#define RT5677_ST_HPF_SEL_MASK (0x7 << 13) +#define RT5677_ST_HPF_SEL_SFT 13 +#define RT5677_ST_HPF_PATH (0x1 << 12) +#define RT5677_ST_HPF_PATH_SFT 12 +#define RT5677_ST_SEL_MASK (0x7 << 9) +#define RT5677_ST_SEL_SFT 9 +#define RT5677_ST_EN (0x1 << 6) +#define RT5677_ST_EN_SFT 6 +#define RT5677_ST_GAIN (0x1 << 5) +#define RT5677_ST_GAIN_SFT 5 +#define RT5677_ST_VOL_MASK (0x1f << 0) +#define RT5677_ST_VOL_SFT 0 + +/* Analog DAC1/2/3 Source Control (0x15) */ +#define RT5677_ANA_DAC3_SRC_SEL_MASK (0x3 << 4) +#define RT5677_ANA_DAC3_SRC_SEL_SFT 4 +#define RT5677_ANA_DAC1_2_SRC_SEL_MASK (0x3 << 0) +#define RT5677_ANA_DAC1_2_SRC_SEL_SFT 0 + +/* IF/DSP to DAC3/4 Mixer Control (0x16) */ +#define RT5677_M_DAC4_L_VOL (0x1 << 15) +#define RT5677_M_DAC4_L_VOL_SFT 15 +#define RT5677_SEL_DAC4_L_SRC_MASK (0x7 << 12) +#define RT5677_SEL_DAC4_L_SRC_SFT 12 +#define RT5677_M_DAC4_R_VOL (0x1 << 11) +#define RT5677_M_DAC4_R_VOL_SFT 11 +#define RT5677_SEL_DAC4_R_SRC_MASK (0x7 << 8) +#define RT5677_SEL_DAC4_R_SRC_SFT 8 +#define RT5677_M_DAC3_L_VOL (0x1 << 7) +#define RT5677_M_DAC3_L_VOL_SFT 7 +#define RT5677_SEL_DAC3_L_SRC_MASK (0x7 << 4) +#define RT5677_SEL_DAC3_L_SRC_SFT 4 +#define RT5677_M_DAC3_R_VOL (0x1 << 3) +#define RT5677_M_DAC3_R_VOL_SFT 3 +#define RT5677_SEL_DAC3_R_SRC_MASK (0x7 << 0) +#define RT5677_SEL_DAC3_R_SRC_SFT 0 + +/* DAC4 Digital Volume (0x17) */ +#define RT5677_DAC4_L_VOL_MASK (0xff << 8) +#define RT5677_DAC4_L_VOL_SFT 8 +#define RT5677_DAC4_R_VOL_MASK (0xff) +#define RT5677_DAC4_R_VOL_SFT 0 + +/* DAC3 Digital Volume (0x18) */ +#define RT5677_DAC3_L_VOL_MASK (0xff << 8) +#define RT5677_DAC3_L_VOL_SFT 8 +#define RT5677_DAC3_R_VOL_MASK (0xff) +#define RT5677_DAC3_R_VOL_SFT 0 + +/* DAC3 Digital Volume (0x19) */ +#define RT5677_DAC1_L_VOL_MASK (0xff << 8) +#define RT5677_DAC1_L_VOL_SFT 8 +#define RT5677_DAC1_R_VOL_MASK (0xff) +#define RT5677_DAC1_R_VOL_SFT 0 + +/* DAC2 Digital Volume (0x1a) */ +#define RT5677_DAC2_L_VOL_MASK (0xff << 8) +#define RT5677_DAC2_L_VOL_SFT 8 +#define RT5677_DAC2_R_VOL_MASK (0xff) +#define RT5677_DAC2_R_VOL_SFT 0 + +/* IF/DSP to DAC2 Mixer Control (0x1b) */ +#define RT5677_M_DAC2_L_VOL (0x1 << 7) +#define RT5677_M_DAC2_L_VOL_SFT 7 +#define RT5677_SEL_DAC2_L_SRC_MASK (0x7 << 4) +#define RT5677_SEL_DAC2_L_SRC_SFT 4 +#define RT5677_M_DAC2_R_VOL (0x1 << 3) +#define RT5677_M_DAC2_R_VOL_SFT 3 +#define RT5677_SEL_DAC2_R_SRC_MASK (0x7 << 0) +#define RT5677_SEL_DAC2_R_SRC_SFT 0 + +/* Stereo1 ADC Digital Volume Control (0x1c) */ +#define RT5677_STO1_ADC_L_VOL_MASK (0x3f << 9) +#define RT5677_STO1_ADC_L_VOL_SFT 9 +#define RT5677_STO1_ADC_R_VOL_MASK (0x3f << 1) +#define RT5677_STO1_ADC_R_VOL_SFT 1 + +/* Mono ADC Digital Volume Control (0x1d) */ +#define RT5677_MONO_ADC_L_VOL_MASK (0x3f << 9) +#define RT5677_MONO_ADC_L_VOL_SFT 9 +#define RT5677_MONO_ADC_R_VOL_MASK (0x3f << 1) +#define RT5677_MONO_ADC_R_VOL_SFT 1 + +/* Stereo 1/2 ADC Boost Gain Control (0x1e) */ +#define RT5677_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5677_STO1_ADC_L_BST_SFT 14 +#define RT5677_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5677_STO1_ADC_R_BST_SFT 12 +#define RT5677_STO1_ADC_COMP_MASK (0x3 << 10) +#define RT5677_STO1_ADC_COMP_SFT 10 +#define RT5677_STO2_ADC_L_BST_MASK (0x3 << 8) +#define RT5677_STO2_ADC_L_BST_SFT 8 +#define RT5677_STO2_ADC_R_BST_MASK (0x3 << 6) +#define RT5677_STO2_ADC_R_BST_SFT 6 +#define RT5677_STO2_ADC_COMP_MASK (0x3 << 4) +#define RT5677_STO2_ADC_COMP_SFT 4 + +/* Stereo2 ADC Digital Volume Control (0x1f) */ +#define RT5677_STO2_ADC_L_VOL_MASK (0x7f << 8) +#define RT5677_STO2_ADC_L_VOL_SFT 8 +#define RT5677_STO2_ADC_R_VOL_MASK (0x7f) +#define RT5677_STO2_ADC_R_VOL_SFT 0 + +/* ADC Boost Gain Control 2 (0x20) */ +#define RT5677_MONO_ADC_L_BST_MASK (0x3 << 14) +#define RT5677_MONO_ADC_L_BST_SFT 14 +#define RT5677_MONO_ADC_R_BST_MASK (0x3 << 12) +#define RT5677_MONO_ADC_R_BST_SFT 12 +#define RT5677_MONO_ADC_COMP_MASK (0x3 << 10) +#define RT5677_MONO_ADC_COMP_SFT 10 + +/* Stereo 3/4 ADC Boost Gain Control (0x21) */ +#define RT5677_STO3_ADC_L_BST_MASK (0x3 << 14) +#define RT5677_STO3_ADC_L_BST_SFT 14 +#define RT5677_STO3_ADC_R_BST_MASK (0x3 << 12) +#define RT5677_STO3_ADC_R_BST_SFT 12 +#define RT5677_STO3_ADC_COMP_MASK (0x3 << 10) +#define RT5677_STO3_ADC_COMP_SFT 10 +#define RT5677_STO4_ADC_L_BST_MASK (0x3 << 8) +#define RT5677_STO4_ADC_L_BST_SFT 8 +#define RT5677_STO4_ADC_R_BST_MASK (0x3 << 6) +#define RT5677_STO4_ADC_R_BST_SFT 6 +#define RT5677_STO4_ADC_COMP_MASK (0x3 << 4) +#define RT5677_STO4_ADC_COMP_SFT 4 + +/* Stereo3 ADC Digital Volume Control (0x22) */ +#define RT5677_STO3_ADC_L_VOL_MASK (0x7f << 8) +#define RT5677_STO3_ADC_L_VOL_SFT 8 +#define RT5677_STO3_ADC_R_VOL_MASK (0x7f) +#define RT5677_STO3_ADC_R_VOL_SFT 0 + +/* Stereo4 ADC Digital Volume Control (0x23) */ +#define RT5677_STO4_ADC_L_VOL_MASK (0x7f << 8) +#define RT5677_STO4_ADC_L_VOL_SFT 8 +#define RT5677_STO4_ADC_R_VOL_MASK (0x7f) +#define RT5677_STO4_ADC_R_VOL_SFT 0 + +/* Stereo4 ADC Mixer control (0x24) */ +#define RT5677_M_STO4_ADC_L2 (0x1 << 15) +#define RT5677_M_STO4_ADC_L2_SFT 15 +#define RT5677_M_STO4_ADC_L1 (0x1 << 14) +#define RT5677_M_STO4_ADC_L1_SFT 14 +#define RT5677_SEL_STO4_ADC1_MASK (0x3 << 12) +#define RT5677_SEL_STO4_ADC1_SFT 12 +#define RT5677_SEL_STO4_ADC2_MASK (0x3 << 10) +#define RT5677_SEL_STO4_ADC2_SFT 10 +#define RT5677_SEL_STO4_DMIC_MASK (0x3 << 8) +#define RT5677_SEL_STO4_DMIC_SFT 8 +#define RT5677_M_STO4_ADC_R1 (0x1 << 7) +#define RT5677_M_STO4_ADC_R1_SFT 7 +#define RT5677_M_STO4_ADC_R2 (0x1 << 6) +#define RT5677_M_STO4_ADC_R2_SFT 6 + +/* Stereo3 ADC Mixer control (0x25) */ +#define RT5677_M_STO3_ADC_L2 (0x1 << 15) +#define RT5677_M_STO3_ADC_L2_SFT 15 +#define RT5677_M_STO3_ADC_L1 (0x1 << 14) +#define RT5677_M_STO3_ADC_L1_SFT 14 +#define RT5677_SEL_STO3_ADC1_MASK (0x3 << 12) +#define RT5677_SEL_STO3_ADC1_SFT 12 +#define RT5677_SEL_STO3_ADC2_MASK (0x3 << 10) +#define RT5677_SEL_STO3_ADC2_SFT 10 +#define RT5677_SEL_STO3_DMIC_MASK (0x3 << 8) +#define RT5677_SEL_STO3_DMIC_SFT 8 +#define RT5677_M_STO3_ADC_R1 (0x1 << 7) +#define RT5677_M_STO3_ADC_R1_SFT 7 +#define RT5677_M_STO3_ADC_R2 (0x1 << 6) +#define RT5677_M_STO3_ADC_R2_SFT 6 + +/* Stereo2 ADC Mixer Control (0x26) */ +#define RT5677_M_STO2_ADC_L2 (0x1 << 15) +#define RT5677_M_STO2_ADC_L2_SFT 15 +#define RT5677_M_STO2_ADC_L1 (0x1 << 14) +#define RT5677_M_STO2_ADC_L1_SFT 14 +#define RT5677_SEL_STO2_ADC1_MASK (0x3 << 12) +#define RT5677_SEL_STO2_ADC1_SFT 12 +#define RT5677_SEL_STO2_ADC2_MASK (0x3 << 10) +#define RT5677_SEL_STO2_ADC2_SFT 10 +#define RT5677_SEL_STO2_DMIC_MASK (0x3 << 8) +#define RT5677_SEL_STO2_DMIC_SFT 8 +#define RT5677_M_STO2_ADC_R1 (0x1 << 7) +#define RT5677_M_STO2_ADC_R1_SFT 7 +#define RT5677_M_STO2_ADC_R2 (0x1 << 6) +#define RT5677_M_STO2_ADC_R2_SFT 6 +#define RT5677_SEL_STO2_LR_MIX_MASK (0x1 << 0) +#define RT5677_SEL_STO2_LR_MIX_SFT 0 +#define RT5677_SEL_STO2_LR_MIX_L (0x0 << 0) +#define RT5677_SEL_STO2_LR_MIX_LR (0x1 << 0) + +/* Stereo1 ADC Mixer control (0x27) */ +#define RT5677_M_STO1_ADC_L2 (0x1 << 15) +#define RT5677_M_STO1_ADC_L2_SFT 15 +#define RT5677_M_STO1_ADC_L1 (0x1 << 14) +#define RT5677_M_STO1_ADC_L1_SFT 14 +#define RT5677_SEL_STO1_ADC1_MASK (0x3 << 12) +#define RT5677_SEL_STO1_ADC1_SFT 12 +#define RT5677_SEL_STO1_ADC2_MASK (0x3 << 10) +#define RT5677_SEL_STO1_ADC2_SFT 10 +#define RT5677_SEL_STO1_DMIC_MASK (0x3 << 8) +#define RT5677_SEL_STO1_DMIC_SFT 8 +#define RT5677_M_STO1_ADC_R1 (0x1 << 7) +#define RT5677_M_STO1_ADC_R1_SFT 7 +#define RT5677_M_STO1_ADC_R2 (0x1 << 6) +#define RT5677_M_STO1_ADC_R2_SFT 6 + +/* Mono ADC Mixer control (0x28) */ +#define RT5677_M_MONO_ADC_L2 (0x1 << 15) +#define RT5677_M_MONO_ADC_L2_SFT 15 +#define RT5677_M_MONO_ADC_L1 (0x1 << 14) +#define RT5677_M_MONO_ADC_L1_SFT 14 +#define RT5677_SEL_MONO_ADC_L1_MASK (0x3 << 12) +#define RT5677_SEL_MONO_ADC_L1_SFT 12 +#define RT5677_SEL_MONO_ADC_L2_MASK (0x3 << 10) +#define RT5677_SEL_MONO_ADC_L2_SFT 10 +#define RT5677_SEL_MONO_DMIC_L_MASK (0x3 << 8) +#define RT5677_SEL_MONO_DMIC_L_SFT 8 +#define RT5677_M_MONO_ADC_R1 (0x1 << 7) +#define RT5677_M_MONO_ADC_R1_SFT 7 +#define RT5677_M_MONO_ADC_R2 (0x1 << 6) +#define RT5677_M_MONO_ADC_R2_SFT 6 +#define RT5677_SEL_MONO_ADC_R1_MASK (0x3 << 4) +#define RT5677_SEL_MONO_ADC_R1_SFT 4 +#define RT5677_SEL_MONO_ADC_R2_MASK (0x3 << 2) +#define RT5677_SEL_MONO_ADC_R2_SFT 2 +#define RT5677_SEL_MONO_DMIC_R_MASK (0x3 << 0) +#define RT5677_SEL_MONO_DMIC_R_SFT 0 + +/* ADC/IF/DSP to DAC1 Mixer control (0x29) */ +#define RT5677_M_ADDA_MIXER1_L (0x1 << 15) +#define RT5677_M_ADDA_MIXER1_L_SFT 15 +#define RT5677_M_DAC1_L (0x1 << 14) +#define RT5677_M_DAC1_L_SFT 14 +#define RT5677_DAC1_L_SEL_MASK (0x7 << 8) +#define RT5677_DAC1_L_SEL_SFT 8 +#define RT5677_M_ADDA_MIXER1_R (0x1 << 7) +#define RT5677_M_ADDA_MIXER1_R_SFT 7 +#define RT5677_M_DAC1_R (0x1 << 6) +#define RT5677_M_DAC1_R_SFT 6 +#define RT5677_ADDA1_SEL_MASK (0x3 << 0) +#define RT5677_ADDA1_SEL_SFT 0 + +/* Stereo1 DAC Mixer L/R Control (0x2a) */ +#define RT5677_M_ST_DAC1_L (0x1 << 15) +#define RT5677_M_ST_DAC1_L_SFT 15 +#define RT5677_M_DAC1_L_STO_L (0x1 << 13) +#define RT5677_M_DAC1_L_STO_L_SFT 13 +#define RT5677_DAC1_L_STO_L_VOL_MASK (0x1 << 12) +#define RT5677_DAC1_L_STO_L_VOL_SFT 12 +#define RT5677_M_DAC2_L_STO_L (0x1 << 11) +#define RT5677_M_DAC2_L_STO_L_SFT 11 +#define RT5677_DAC2_L_STO_L_VOL_MASK (0x1 << 10) +#define RT5677_DAC2_L_STO_L_VOL_SFT 10 +#define RT5677_M_DAC1_R_STO_L (0x1 << 9) +#define RT5677_M_DAC1_R_STO_L_SFT 9 +#define RT5677_DAC1_R_STO_L_VOL_MASK (0x1 << 8) +#define RT5677_DAC1_R_STO_L_VOL_SFT 8 +#define RT5677_M_ST_DAC1_R (0x1 << 7) +#define RT5677_M_ST_DAC1_R_SFT 7 +#define RT5677_M_DAC1_R_STO_R (0x1 << 5) +#define RT5677_M_DAC1_R_STO_R_SFT 5 +#define RT5677_DAC1_R_STO_R_VOL_MASK (0x1 << 4) +#define RT5677_DAC1_R_STO_R_VOL_SFT 4 +#define RT5677_M_DAC2_R_STO_R (0x1 << 3) +#define RT5677_M_DAC2_R_STO_R_SFT 3 +#define RT5677_DAC2_R_STO_R_VOL_MASK (0x1 << 2) +#define RT5677_DAC2_R_STO_R_VOL_SFT 2 +#define RT5677_M_DAC1_L_STO_R (0x1 << 1) +#define RT5677_M_DAC1_L_STO_R_SFT 1 +#define RT5677_DAC1_L_STO_R_VOL_MASK (0x1 << 0) +#define RT5677_DAC1_L_STO_R_VOL_SFT 0 + +/* Mono DAC Mixer L/R Control (0x2b) */ +#define RT5677_M_ST_DAC2_L (0x1 << 15) +#define RT5677_M_ST_DAC2_L_SFT 15 +#define RT5677_M_DAC2_L_MONO_L (0x1 << 13) +#define RT5677_M_DAC2_L_MONO_L_SFT 13 +#define RT5677_DAC2_L_MONO_L_VOL_MASK (0x1 << 12) +#define RT5677_DAC2_L_MONO_L_VOL_SFT 12 +#define RT5677_M_DAC2_R_MONO_L (0x1 << 11) +#define RT5677_M_DAC2_R_MONO_L_SFT 11 +#define RT5677_DAC2_R_MONO_L_VOL_MASK (0x1 << 10) +#define RT5677_DAC2_R_MONO_L_VOL_SFT 10 +#define RT5677_M_DAC1_L_MONO_L (0x1 << 9) +#define RT5677_M_DAC1_L_MONO_L_SFT 9 +#define RT5677_DAC1_L_MONO_L_VOL_MASK (0x1 << 8) +#define RT5677_DAC1_L_MONO_L_VOL_SFT 8 +#define RT5677_M_ST_DAC2_R (0x1 << 7) +#define RT5677_M_ST_DAC2_R_SFT 7 +#define RT5677_M_DAC2_R_MONO_R (0x1 << 5) +#define RT5677_M_DAC2_R_MONO_R_SFT 5 +#define RT5677_DAC2_R_MONO_R_VOL_MASK (0x1 << 4) +#define RT5677_DAC2_R_MONO_R_VOL_SFT 4 +#define RT5677_M_DAC1_R_MONO_R (0x1 << 3) +#define RT5677_M_DAC1_R_MONO_R_SFT 3 +#define RT5677_DAC1_R_MONO_R_VOL_MASK (0x1 << 2) +#define RT5677_DAC1_R_MONO_R_VOL_SFT 2 +#define RT5677_M_DAC2_L_MONO_R (0x1 << 1) +#define RT5677_M_DAC2_L_MONO_R_SFT 1 +#define RT5677_DAC2_L_MONO_R_VOL_MASK (0x1 << 0) +#define RT5677_DAC2_L_MONO_R_VOL_SFT 0 + +/* DD Mixer 1 Control (0x2c) */ +#define RT5677_M_STO_L_DD1_L (0x1 << 15) +#define RT5677_M_STO_L_DD1_L_SFT 15 +#define RT5677_STO_L_DD1_L_VOL_MASK (0x1 << 14) +#define RT5677_STO_L_DD1_L_VOL_SFT 14 +#define RT5677_M_MONO_L_DD1_L (0x1 << 13) +#define RT5677_M_MONO_L_DD1_L_SFT 13 +#define RT5677_MONO_L_DD1_L_VOL_MASK (0x1 << 12) +#define RT5677_MONO_L_DD1_L_VOL_SFT 12 +#define RT5677_M_DAC3_L_DD1_L (0x1 << 11) +#define RT5677_M_DAC3_L_DD1_L_SFT 11 +#define RT5677_DAC3_L_DD1_L_VOL_MASK (0x1 << 10) +#define RT5677_DAC3_L_DD1_L_VOL_SFT 10 +#define RT5677_M_DAC3_R_DD1_L (0x1 << 9) +#define RT5677_M_DAC3_R_DD1_L_SFT 9 +#define RT5677_DAC3_R_DD1_L_VOL_MASK (0x1 << 8) +#define RT5677_DAC3_R_DD1_L_VOL_SFT 8 +#define RT5677_M_STO_R_DD1_R (0x1 << 7) +#define RT5677_M_STO_R_DD1_R_SFT 7 +#define RT5677_STO_R_DD1_R_VOL_MASK (0x1 << 6) +#define RT5677_STO_R_DD1_R_VOL_SFT 6 +#define RT5677_M_MONO_R_DD1_R (0x1 << 5) +#define RT5677_M_MONO_R_DD1_R_SFT 5 +#define RT5677_MONO_R_DD1_R_VOL_MASK (0x1 << 4) +#define RT5677_MONO_R_DD1_R_VOL_SFT 4 +#define RT5677_M_DAC3_R_DD1_R (0x1 << 3) +#define RT5677_M_DAC3_R_DD1_R_SFT 3 +#define RT5677_DAC3_R_DD1_R_VOL_MASK (0x1 << 2) +#define RT5677_DAC3_R_DD1_R_VOL_SFT 2 +#define RT5677_M_DAC3_L_DD1_R (0x1 << 1) +#define RT5677_M_DAC3_L_DD1_R_SFT 1 +#define RT5677_DAC3_L_DD1_R_VOL_MASK (0x1 << 0) +#define RT5677_DAC3_L_DD1_R_VOL_SFT 0 + +/* DD Mixer 2 Control (0x2d) */ +#define RT5677_M_STO_L_DD2_L (0x1 << 15) +#define RT5677_M_STO_L_DD2_L_SFT 15 +#define RT5677_STO_L_DD2_L_VOL_MASK (0x1 << 14) +#define RT5677_STO_L_DD2_L_VOL_SFT 14 +#define RT5677_M_MONO_L_DD2_L (0x1 << 13) +#define RT5677_M_MONO_L_DD2_L_SFT 13 +#define RT5677_MONO_L_DD2_L_VOL_MASK (0x1 << 12) +#define RT5677_MONO_L_DD2_L_VOL_SFT 12 +#define RT5677_M_DAC4_L_DD2_L (0x1 << 11) +#define RT5677_M_DAC4_L_DD2_L_SFT 11 +#define RT5677_DAC4_L_DD2_L_VOL_MASK (0x1 << 10) +#define RT5677_DAC4_L_DD2_L_VOL_SFT 10 +#define RT5677_M_DAC4_R_DD2_L (0x1 << 9) +#define RT5677_M_DAC4_R_DD2_L_SFT 9 +#define RT5677_DAC4_R_DD2_L_VOL_MASK (0x1 << 8) +#define RT5677_DAC4_R_DD2_L_VOL_SFT 8 +#define RT5677_M_STO_R_DD2_R (0x1 << 7) +#define RT5677_M_STO_R_DD2_R_SFT 7 +#define RT5677_STO_R_DD2_R_VOL_MASK (0x1 << 6) +#define RT5677_STO_R_DD2_R_VOL_SFT 6 +#define RT5677_M_MONO_R_DD2_R (0x1 << 5) +#define RT5677_M_MONO_R_DD2_R_SFT 5 +#define RT5677_MONO_R_DD2_R_VOL_MASK (0x1 << 4) +#define RT5677_MONO_R_DD2_R_VOL_SFT 4 +#define RT5677_M_DAC4_R_DD2_R (0x1 << 3) +#define RT5677_M_DAC4_R_DD2_R_SFT 3 +#define RT5677_DAC4_R_DD2_R_VOL_MASK (0x1 << 2) +#define RT5677_DAC4_R_DD2_R_VOL_SFT 2 +#define RT5677_M_DAC4_L_DD2_R (0x1 << 1) +#define RT5677_M_DAC4_L_DD2_R_SFT 1 +#define RT5677_DAC4_L_DD2_R_VOL_MASK (0x1 << 0) +#define RT5677_DAC4_L_DD2_R_VOL_SFT 0 + +/* IF3 data control (0x2f) */ +#define RT5677_IF3_DAC_SEL_MASK (0x3 << 6) +#define RT5677_IF3_DAC_SEL_SFT 6 +#define RT5677_IF3_ADC_SEL_MASK (0x3 << 4) +#define RT5677_IF3_ADC_SEL_SFT 4 +#define RT5677_IF3_ADC_IN_MASK (0xf << 0) +#define RT5677_IF3_ADC_IN_SFT 0 + +/* IF4 data control (0x30) */ +#define RT5677_IF4_ADC_IN_MASK (0xf << 4) +#define RT5677_IF4_ADC_IN_SFT 4 +#define RT5677_IF4_DAC_SEL_MASK (0x3 << 2) +#define RT5677_IF4_DAC_SEL_SFT 2 +#define RT5677_IF4_ADC_SEL_MASK (0x3 << 0) +#define RT5677_IF4_ADC_SEL_SFT 0 + +/* PDM Output Control (0x31) */ +#define RT5677_M_PDM1_L (0x1 << 15) +#define RT5677_M_PDM1_L_SFT 15 +#define RT5677_SEL_PDM1_L_MASK (0x3 << 12) +#define RT5677_SEL_PDM1_L_SFT 12 +#define RT5677_M_PDM1_R (0x1 << 11) +#define RT5677_M_PDM1_R_SFT 11 +#define RT5677_SEL_PDM1_R_MASK (0x3 << 8) +#define RT5677_SEL_PDM1_R_SFT 8 +#define RT5677_M_PDM2_L (0x1 << 7) +#define RT5677_M_PDM2_L_SFT 7 +#define RT5677_SEL_PDM2_L_MASK (0x3 << 4) +#define RT5677_SEL_PDM2_L_SFT 4 +#define RT5677_M_PDM2_R (0x1 << 3) +#define RT5677_M_PDM2_R_SFT 3 +#define RT5677_SEL_PDM2_R_MASK (0x3 << 0) +#define RT5677_SEL_PDM2_R_SFT 0 + +/* PDM I2C / Data Control 1 (0x32) */ +#define RT5677_PDM2_PW_DOWN (0x1 << 7) +#define RT5677_PDM1_PW_DOWN (0x1 << 6) +#define RT5677_PDM2_BUSY (0x1 << 5) +#define RT5677_PDM1_BUSY (0x1 << 4) +#define RT5677_PDM_PATTERN (0x1 << 3) +#define RT5677_PDM_GAIN (0x1 << 2) +#define RT5677_PDM_DIV_MASK (0x3 << 0) + +/* PDM I2C / Data Control 2 (0x33) */ +#define RT5677_PDM1_I2C_ID (0xf << 12) +#define RT5677_PDM1_EXE (0x1 << 11) +#define RT5677_PDM1_I2C_CMD (0x1 << 10) +#define RT5677_PDM1_I2C_EXE (0x1 << 9) +#define RT5677_PDM1_I2C_BUSY (0x1 << 8) +#define RT5677_PDM2_I2C_ID (0xf << 4) +#define RT5677_PDM2_EXE (0x1 << 3) +#define RT5677_PDM2_I2C_CMD (0x1 << 2) +#define RT5677_PDM2_I2C_EXE (0x1 << 1) +#define RT5677_PDM2_I2C_BUSY (0x1 << 0) + +/* TDM1 control 1 (0x3b) */ +#define RT5677_IF1_ADC_MODE_MASK (0x1 << 12) +#define RT5677_IF1_ADC_MODE_SFT 12 +#define RT5677_IF1_ADC_MODE_I2S (0x0 << 12) +#define RT5677_IF1_ADC_MODE_TDM (0x1 << 12) +#define RT5677_IF1_ADC1_SWAP_MASK (0x3 << 6) +#define RT5677_IF1_ADC1_SWAP_SFT 6 +#define RT5677_IF1_ADC2_SWAP_MASK (0x3 << 4) +#define RT5677_IF1_ADC2_SWAP_SFT 4 +#define RT5677_IF1_ADC3_SWAP_MASK (0x3 << 2) +#define RT5677_IF1_ADC3_SWAP_SFT 2 +#define RT5677_IF1_ADC4_SWAP_MASK (0x3 << 0) +#define RT5677_IF1_ADC4_SWAP_SFT 0 + +/* TDM1 control 2 (0x3c) */ +#define RT5677_IF1_ADC4_MASK (0x3 << 10) +#define RT5677_IF1_ADC4_SFT 10 +#define RT5677_IF1_ADC3_MASK (0x3 << 8) +#define RT5677_IF1_ADC3_SFT 8 +#define RT5677_IF1_ADC2_MASK (0x3 << 6) +#define RT5677_IF1_ADC2_SFT 6 +#define RT5677_IF1_ADC1_MASK (0x3 << 4) +#define RT5677_IF1_ADC1_SFT 4 +#define RT5677_IF1_ADC_CTRL_MASK (0x7 << 0) +#define RT5677_IF1_ADC_CTRL_SFT 0 + +/* TDM1 control 4 (0x3e) */ +#define RT5677_IF1_DAC0_MASK (0x7 << 12) +#define RT5677_IF1_DAC0_SFT 12 +#define RT5677_IF1_DAC1_MASK (0x7 << 8) +#define RT5677_IF1_DAC1_SFT 8 +#define RT5677_IF1_DAC2_MASK (0x7 << 4) +#define RT5677_IF1_DAC2_SFT 4 +#define RT5677_IF1_DAC3_MASK (0x7 << 0) +#define RT5677_IF1_DAC3_SFT 0 + +/* TDM1 control 5 (0x3f) */ +#define RT5677_IF1_DAC4_MASK (0x7 << 12) +#define RT5677_IF1_DAC4_SFT 12 +#define RT5677_IF1_DAC5_MASK (0x7 << 8) +#define RT5677_IF1_DAC5_SFT 8 +#define RT5677_IF1_DAC6_MASK (0x7 << 4) +#define RT5677_IF1_DAC6_SFT 4 +#define RT5677_IF1_DAC7_MASK (0x7 << 0) +#define RT5677_IF1_DAC7_SFT 0 + +/* TDM2 control 1 (0x40) */ +#define RT5677_IF2_ADC_MODE_MASK (0x1 << 12) +#define RT5677_IF2_ADC_MODE_SFT 12 +#define RT5677_IF2_ADC_MODE_I2S (0x0 << 12) +#define RT5677_IF2_ADC_MODE_TDM (0x1 << 12) +#define RT5677_IF2_ADC1_SWAP_MASK (0x3 << 6) +#define RT5677_IF2_ADC1_SWAP_SFT 6 +#define RT5677_IF2_ADC2_SWAP_MASK (0x3 << 4) +#define RT5677_IF2_ADC2_SWAP_SFT 4 +#define RT5677_IF2_ADC3_SWAP_MASK (0x3 << 2) +#define RT5677_IF2_ADC3_SWAP_SFT 2 +#define RT5677_IF2_ADC4_SWAP_MASK (0x3 << 0) +#define RT5677_IF2_ADC4_SWAP_SFT 0 + +/* TDM2 control 2 (0x41) */ +#define RT5677_IF2_ADC4_MASK (0x3 << 10) +#define RT5677_IF2_ADC4_SFT 10 +#define RT5677_IF2_ADC3_MASK (0x3 << 8) +#define RT5677_IF2_ADC3_SFT 8 +#define RT5677_IF2_ADC2_MASK (0x3 << 6) +#define RT5677_IF2_ADC2_SFT 6 +#define RT5677_IF2_ADC1_MASK (0x3 << 4) +#define RT5677_IF2_ADC1_SFT 4 +#define RT5677_IF2_ADC_CTRL_MASK (0x7 << 0) +#define RT5677_IF2_ADC_CTRL_SFT 0 + +/* TDM2 control 4 (0x43) */ +#define RT5677_IF2_DAC0_MASK (0x7 << 12) +#define RT5677_IF2_DAC0_SFT 12 +#define RT5677_IF2_DAC1_MASK (0x7 << 8) +#define RT5677_IF2_DAC1_SFT 8 +#define RT5677_IF2_DAC2_MASK (0x7 << 4) +#define RT5677_IF2_DAC2_SFT 4 +#define RT5677_IF2_DAC3_MASK (0x7 << 0) +#define RT5677_IF2_DAC3_SFT 0 + +/* TDM2 control 5 (0x44) */ +#define RT5677_IF2_DAC4_MASK (0x7 << 12) +#define RT5677_IF2_DAC4_SFT 12 +#define RT5677_IF2_DAC5_MASK (0x7 << 8) +#define RT5677_IF2_DAC5_SFT 8 +#define RT5677_IF2_DAC6_MASK (0x7 << 4) +#define RT5677_IF2_DAC6_SFT 4 +#define RT5677_IF2_DAC7_MASK (0x7 << 0) +#define RT5677_IF2_DAC7_SFT 0 + +/* Digital Microphone Control 1 (0x50) */ +#define RT5677_DMIC_1_EN_MASK (0x1 << 15) +#define RT5677_DMIC_1_EN_SFT 15 +#define RT5677_DMIC_1_DIS (0x0 << 15) +#define RT5677_DMIC_1_EN (0x1 << 15) +#define RT5677_DMIC_2_EN_MASK (0x1 << 14) +#define RT5677_DMIC_2_EN_SFT 14 +#define RT5677_DMIC_2_DIS (0x0 << 14) +#define RT5677_DMIC_2_EN (0x1 << 14) +#define RT5677_DMIC_L_STO1_LH_MASK (0x1 << 13) +#define RT5677_DMIC_L_STO1_LH_SFT 13 +#define RT5677_DMIC_L_STO1_LH_FALLING (0x0 << 13) +#define RT5677_DMIC_L_STO1_LH_RISING (0x1 << 13) +#define RT5677_DMIC_R_STO1_LH_MASK (0x1 << 12) +#define RT5677_DMIC_R_STO1_LH_SFT 12 +#define RT5677_DMIC_R_STO1_LH_FALLING (0x0 << 12) +#define RT5677_DMIC_R_STO1_LH_RISING (0x1 << 12) +#define RT5677_DMIC_L_STO3_LH_MASK (0x1 << 11) +#define RT5677_DMIC_L_STO3_LH_SFT 11 +#define RT5677_DMIC_L_STO3_LH_FALLING (0x0 << 11) +#define RT5677_DMIC_L_STO3_LH_RISING (0x1 << 11) +#define RT5677_DMIC_R_STO3_LH_MASK (0x1 << 10) +#define RT5677_DMIC_R_STO3_LH_SFT 10 +#define RT5677_DMIC_R_STO3_LH_FALLING (0x0 << 10) +#define RT5677_DMIC_R_STO3_LH_RISING (0x1 << 10) +#define RT5677_DMIC_L_STO2_LH_MASK (0x1 << 9) +#define RT5677_DMIC_L_STO2_LH_SFT 9 +#define RT5677_DMIC_L_STO2_LH_FALLING (0x0 << 9) +#define RT5677_DMIC_L_STO2_LH_RISING (0x1 << 9) +#define RT5677_DMIC_R_STO2_LH_MASK (0x1 << 8) +#define RT5677_DMIC_R_STO2_LH_SFT 8 +#define RT5677_DMIC_R_STO2_LH_FALLING (0x0 << 8) +#define RT5677_DMIC_R_STO2_LH_RISING (0x1 << 8) +#define RT5677_DMIC_CLK_MASK (0x7 << 5) +#define RT5677_DMIC_CLK_SFT 5 +#define RT5677_DMIC_3_EN_MASK (0x1 << 4) +#define RT5677_DMIC_3_EN_SFT 4 +#define RT5677_DMIC_3_DIS (0x0 << 4) +#define RT5677_DMIC_3_EN (0x1 << 4) +#define RT5677_DMIC_R_MONO_LH_MASK (0x1 << 2) +#define RT5677_DMIC_R_MONO_LH_SFT 2 +#define RT5677_DMIC_R_MONO_LH_FALLING (0x0 << 2) +#define RT5677_DMIC_R_MONO_LH_RISING (0x1 << 2) +#define RT5677_DMIC_L_STO4_LH_MASK (0x1 << 1) +#define RT5677_DMIC_L_STO4_LH_SFT 1 +#define RT5677_DMIC_L_STO4_LH_FALLING (0x0 << 1) +#define RT5677_DMIC_L_STO4_LH_RISING (0x1 << 1) +#define RT5677_DMIC_R_STO4_LH_MASK (0x1 << 0) +#define RT5677_DMIC_R_STO4_LH_SFT 0 +#define RT5677_DMIC_R_STO4_LH_FALLING (0x0 << 0) +#define RT5677_DMIC_R_STO4_LH_RISING (0x1 << 0) + +/* Digital Microphone Control 2 (0x51) */ +#define RT5677_DMIC_4_EN_MASK (0x1 << 15) +#define RT5677_DMIC_4_EN_SFT 15 +#define RT5677_DMIC_4_DIS (0x0 << 15) +#define RT5677_DMIC_4_EN (0x1 << 15) +#define RT5677_DMIC_4L_LH_MASK (0x1 << 7) +#define RT5677_DMIC_4L_LH_SFT 7 +#define RT5677_DMIC_4L_LH_FALLING (0x0 << 7) +#define RT5677_DMIC_4L_LH_RISING (0x1 << 7) +#define RT5677_DMIC_4R_LH_MASK (0x1 << 6) +#define RT5677_DMIC_4R_LH_SFT 6 +#define RT5677_DMIC_4R_LH_FALLING (0x0 << 6) +#define RT5677_DMIC_4R_LH_RISING (0x1 << 6) +#define RT5677_DMIC_3L_LH_MASK (0x1 << 5) +#define RT5677_DMIC_3L_LH_SFT 5 +#define RT5677_DMIC_3L_LH_FALLING (0x0 << 5) +#define RT5677_DMIC_3L_LH_RISING (0x1 << 5) +#define RT5677_DMIC_3R_LH_MASK (0x1 << 4) +#define RT5677_DMIC_3R_LH_SFT 4 +#define RT5677_DMIC_3R_LH_FALLING (0x0 << 4) +#define RT5677_DMIC_3R_LH_RISING (0x1 << 4) +#define RT5677_DMIC_2L_LH_MASK (0x1 << 3) +#define RT5677_DMIC_2L_LH_SFT 3 +#define RT5677_DMIC_2L_LH_FALLING (0x0 << 3) +#define RT5677_DMIC_2L_LH_RISING (0x1 << 3) +#define RT5677_DMIC_2R_LH_MASK (0x1 << 2) +#define RT5677_DMIC_2R_LH_SFT 2 +#define RT5677_DMIC_2R_LH_FALLING (0x0 << 2) +#define RT5677_DMIC_2R_LH_RISING (0x1 << 2) +#define RT5677_DMIC_1L_LH_MASK (0x1 << 1) +#define RT5677_DMIC_1L_LH_SFT 1 +#define RT5677_DMIC_1L_LH_FALLING (0x0 << 1) +#define RT5677_DMIC_1L_LH_RISING (0x1 << 1) +#define RT5677_DMIC_1R_LH_MASK (0x1 << 0) +#define RT5677_DMIC_1R_LH_SFT 0 +#define RT5677_DMIC_1R_LH_FALLING (0x0 << 0) +#define RT5677_DMIC_1R_LH_RISING (0x1 << 0) + +/* Power Management for Digital 1 (0x61) */ +#define RT5677_PWR_I2S1 (0x1 << 15) +#define RT5677_PWR_I2S1_BIT 15 +#define RT5677_PWR_I2S2 (0x1 << 14) +#define RT5677_PWR_I2S2_BIT 14 +#define RT5677_PWR_I2S3 (0x1 << 13) +#define RT5677_PWR_I2S3_BIT 13 +#define RT5677_PWR_DAC1 (0x1 << 12) +#define RT5677_PWR_DAC1_BIT 12 +#define RT5677_PWR_DAC2 (0x1 << 11) +#define RT5677_PWR_DAC2_BIT 11 +#define RT5677_PWR_I2S4 (0x1 << 10) +#define RT5677_PWR_I2S4_BIT 10 +#define RT5677_PWR_SLB (0x1 << 9) +#define RT5677_PWR_SLB_BIT 9 +#define RT5677_PWR_DAC3 (0x1 << 7) +#define RT5677_PWR_DAC3_BIT 7 +#define RT5677_PWR_ADCFED2 (0x1 << 4) +#define RT5677_PWR_ADCFED2_BIT 4 +#define RT5677_PWR_ADCFED1 (0x1 << 3) +#define RT5677_PWR_ADCFED1_BIT 3 +#define RT5677_PWR_ADC_L (0x1 << 2) +#define RT5677_PWR_ADC_L_BIT 2 +#define RT5677_PWR_ADC_R (0x1 << 1) +#define RT5677_PWR_ADC_R_BIT 1 +#define RT5677_PWR_I2C_MASTER (0x1 << 0) +#define RT5677_PWR_I2C_MASTER_BIT 0 + +/* Power Management for Digital 2 (0x62) */ +#define RT5677_PWR_ADC_S1F (0x1 << 15) +#define RT5677_PWR_ADC_S1F_BIT 15 +#define RT5677_PWR_ADC_MF_L (0x1 << 14) +#define RT5677_PWR_ADC_MF_L_BIT 14 +#define RT5677_PWR_ADC_MF_R (0x1 << 13) +#define RT5677_PWR_ADC_MF_R_BIT 13 +#define RT5677_PWR_DAC_S1F (0x1 << 12) +#define RT5677_PWR_DAC_S1F_BIT 12 +#define RT5677_PWR_DAC_M2F_L (0x1 << 11) +#define RT5677_PWR_DAC_M2F_L_BIT 11 +#define RT5677_PWR_DAC_M2F_R (0x1 << 10) +#define RT5677_PWR_DAC_M2F_R_BIT 10 +#define RT5677_PWR_DAC_M3F_L (0x1 << 9) +#define RT5677_PWR_DAC_M3F_L_BIT 9 +#define RT5677_PWR_DAC_M3F_R (0x1 << 8) +#define RT5677_PWR_DAC_M3F_R_BIT 8 +#define RT5677_PWR_DAC_M4F_L (0x1 << 7) +#define RT5677_PWR_DAC_M4F_L_BIT 7 +#define RT5677_PWR_DAC_M4F_R (0x1 << 6) +#define RT5677_PWR_DAC_M4F_R_BIT 6 +#define RT5677_PWR_ADC_S2F (0x1 << 5) +#define RT5677_PWR_ADC_S2F_BIT 5 +#define RT5677_PWR_ADC_S3F (0x1 << 4) +#define RT5677_PWR_ADC_S3F_BIT 4 +#define RT5677_PWR_ADC_S4F (0x1 << 3) +#define RT5677_PWR_ADC_S4F_BIT 3 +#define RT5677_PWR_PDM1 (0x1 << 2) +#define RT5677_PWR_PDM1_BIT 2 +#define RT5677_PWR_PDM2 (0x1 << 1) +#define RT5677_PWR_PDM2_BIT 1 + +/* Power Management for Analog 1 (0x63) */ +#define RT5677_PWR_VREF1 (0x1 << 15) +#define RT5677_PWR_VREF1_BIT 15 +#define RT5677_PWR_FV1 (0x1 << 14) +#define RT5677_PWR_FV1_BIT 14 +#define RT5677_PWR_MB (0x1 << 13) +#define RT5677_PWR_MB_BIT 13 +#define RT5677_PWR_LO1 (0x1 << 12) +#define RT5677_PWR_LO1_BIT 12 +#define RT5677_PWR_BG (0x1 << 11) +#define RT5677_PWR_BG_BIT 11 +#define RT5677_PWR_LO2 (0x1 << 10) +#define RT5677_PWR_LO2_BIT 10 +#define RT5677_PWR_LO3 (0x1 << 9) +#define RT5677_PWR_LO3_BIT 9 +#define RT5677_PWR_VREF2 (0x1 << 8) +#define RT5677_PWR_VREF2_BIT 8 +#define RT5677_PWR_FV2 (0x1 << 7) +#define RT5677_PWR_FV2_BIT 7 +#define RT5677_LDO2_SEL_MASK (0x7 << 4) +#define RT5677_LDO2_SEL_SFT 4 +#define RT5677_LDO1_SEL_MASK (0x7 << 0) +#define RT5677_LDO1_SEL_SFT 0 + +/* Power Management for Analog 2 (0x64) */ +#define RT5677_PWR_BST1 (0x1 << 15) +#define RT5677_PWR_BST1_BIT 15 +#define RT5677_PWR_BST2 (0x1 << 14) +#define RT5677_PWR_BST2_BIT 14 +#define RT5677_PWR_CLK_MB1 (0x1 << 13) +#define RT5677_PWR_CLK_MB1_BIT 13 +#define RT5677_PWR_SLIM (0x1 << 12) +#define RT5677_PWR_SLIM_BIT 12 +#define RT5677_PWR_MB1 (0x1 << 11) +#define RT5677_PWR_MB1_BIT 11 +#define RT5677_PWR_PP_MB1 (0x1 << 10) +#define RT5677_PWR_PP_MB1_BIT 10 +#define RT5677_PWR_PLL1 (0x1 << 9) +#define RT5677_PWR_PLL1_BIT 9 +#define RT5677_PWR_PLL2 (0x1 << 8) +#define RT5677_PWR_PLL2_BIT 8 +#define RT5677_PWR_CORE (0x1 << 7) +#define RT5677_PWR_CORE_BIT 7 +#define RT5677_PWR_CLK_MB (0x1 << 6) +#define RT5677_PWR_CLK_MB_BIT 6 +#define RT5677_PWR_BST1_P (0x1 << 5) +#define RT5677_PWR_BST1_P_BIT 5 +#define RT5677_PWR_BST2_P (0x1 << 4) +#define RT5677_PWR_BST2_P_BIT 4 +#define RT5677_PWR_IPTV (0x1 << 3) +#define RT5677_PWR_IPTV_BIT 3 +#define RT5677_PWR_25M_CLK (0x1 << 1) +#define RT5677_PWR_25M_CLK_BIT 1 +#define RT5677_PWR_LDO1 (0x1 << 0) +#define RT5677_PWR_LDO1_BIT 0 + +/* Power Management for DSP (0x65) */ +#define RT5677_PWR_SR7 (0x1 << 10) +#define RT5677_PWR_SR7_BIT 10 +#define RT5677_PWR_SR6 (0x1 << 9) +#define RT5677_PWR_SR6_BIT 9 +#define RT5677_PWR_SR5 (0x1 << 8) +#define RT5677_PWR_SR5_BIT 8 +#define RT5677_PWR_SR4 (0x1 << 7) +#define RT5677_PWR_SR4_BIT 7 +#define RT5677_PWR_SR3 (0x1 << 6) +#define RT5677_PWR_SR3_BIT 6 +#define RT5677_PWR_SR2 (0x1 << 5) +#define RT5677_PWR_SR2_BIT 5 +#define RT5677_PWR_SR1 (0x1 << 4) +#define RT5677_PWR_SR1_BIT 4 +#define RT5677_PWR_SR0 (0x1 << 3) +#define RT5677_PWR_SR0_BIT 3 +#define RT5677_PWR_MLT (0x1 << 2) +#define RT5677_PWR_MLT_BIT 2 +#define RT5677_PWR_DSP (0x1 << 1) +#define RT5677_PWR_DSP_BIT 1 +#define RT5677_PWR_DSP_CPU (0x1 << 0) +#define RT5677_PWR_DSP_CPU_BIT 0 + +/* Power Status for DSP (0x66) */ +#define RT5677_PWR_SR7_RDY (0x1 << 9) +#define RT5677_PWR_SR7_RDY_BIT 9 +#define RT5677_PWR_SR6_RDY (0x1 << 8) +#define RT5677_PWR_SR6_RDY_BIT 8 +#define RT5677_PWR_SR5_RDY (0x1 << 7) +#define RT5677_PWR_SR5_RDY_BIT 7 +#define RT5677_PWR_SR4_RDY (0x1 << 6) +#define RT5677_PWR_SR4_RDY_BIT 6 +#define RT5677_PWR_SR3_RDY (0x1 << 5) +#define RT5677_PWR_SR3_RDY_BIT 5 +#define RT5677_PWR_SR2_RDY (0x1 << 4) +#define RT5677_PWR_SR2_RDY_BIT 4 +#define RT5677_PWR_SR1_RDY (0x1 << 3) +#define RT5677_PWR_SR1_RDY_BIT 3 +#define RT5677_PWR_SR0_RDY (0x1 << 2) +#define RT5677_PWR_SR0_RDY_BIT 2 +#define RT5677_PWR_MLT_RDY (0x1 << 1) +#define RT5677_PWR_MLT_RDY_BIT 1 +#define RT5677_PWR_DSP_RDY (0x1 << 0) +#define RT5677_PWR_DSP_RDY_BIT 0 + +/* Power Management for DSP (0x67) */ +#define RT5677_PWR_SLIM_ISO (0x1 << 11) +#define RT5677_PWR_SLIM_ISO_BIT 11 +#define RT5677_PWR_CORE_ISO (0x1 << 10) +#define RT5677_PWR_CORE_ISO_BIT 10 +#define RT5677_PWR_DSP_ISO (0x1 << 9) +#define RT5677_PWR_DSP_ISO_BIT 9 +#define RT5677_PWR_SR7_ISO (0x1 << 8) +#define RT5677_PWR_SR7_ISO_BIT 8 +#define RT5677_PWR_SR6_ISO (0x1 << 7) +#define RT5677_PWR_SR6_ISO_BIT 7 +#define RT5677_PWR_SR5_ISO (0x1 << 6) +#define RT5677_PWR_SR5_ISO_BIT 6 +#define RT5677_PWR_SR4_ISO (0x1 << 5) +#define RT5677_PWR_SR4_ISO_BIT 5 +#define RT5677_PWR_SR3_ISO (0x1 << 4) +#define RT5677_PWR_SR3_ISO_BIT 4 +#define RT5677_PWR_SR2_ISO (0x1 << 3) +#define RT5677_PWR_SR2_ISO_BIT 3 +#define RT5677_PWR_SR1_ISO (0x1 << 2) +#define RT5677_PWR_SR1_ISO_BIT 2 +#define RT5677_PWR_SR0_ISO (0x1 << 1) +#define RT5677_PWR_SR0_ISO_BIT 1 +#define RT5677_PWR_MLT_ISO (0x1 << 0) +#define RT5677_PWR_MLT_ISO_BIT 0 + +/* I2S1/2/3/4 Audio Serial Data Port Control (0x6f 0x70 0x71 0x72) */ +#define RT5677_I2S_MS_MASK (0x1 << 15) +#define RT5677_I2S_MS_SFT 15 +#define RT5677_I2S_MS_M (0x0 << 15) +#define RT5677_I2S_MS_S (0x1 << 15) +#define RT5677_I2S_O_CP_MASK (0x3 << 10) +#define RT5677_I2S_O_CP_SFT 10 +#define RT5677_I2S_O_CP_OFF (0x0 << 10) +#define RT5677_I2S_O_CP_U_LAW (0x1 << 10) +#define RT5677_I2S_O_CP_A_LAW (0x2 << 10) +#define RT5677_I2S_I_CP_MASK (0x3 << 8) +#define RT5677_I2S_I_CP_SFT 8 +#define RT5677_I2S_I_CP_OFF (0x0 << 8) +#define RT5677_I2S_I_CP_U_LAW (0x1 << 8) +#define RT5677_I2S_I_CP_A_LAW (0x2 << 8) +#define RT5677_I2S_BP_MASK (0x1 << 7) +#define RT5677_I2S_BP_SFT 7 +#define RT5677_I2S_BP_NOR (0x0 << 7) +#define RT5677_I2S_BP_INV (0x1 << 7) +#define RT5677_I2S_DL_MASK (0x3 << 2) +#define RT5677_I2S_DL_SFT 2 +#define RT5677_I2S_DL_16 (0x0 << 2) +#define RT5677_I2S_DL_20 (0x1 << 2) +#define RT5677_I2S_DL_24 (0x2 << 2) +#define RT5677_I2S_DL_8 (0x3 << 2) +#define RT5677_I2S_DF_MASK (0x3 << 0) +#define RT5677_I2S_DF_SFT 0 +#define RT5677_I2S_DF_I2S (0x0 << 0) +#define RT5677_I2S_DF_LEFT (0x1 << 0) +#define RT5677_I2S_DF_PCM_A (0x2 << 0) +#define RT5677_I2S_DF_PCM_B (0x3 << 0) + +/* Clock Tree Control 1 (0x73) */ +#define RT5677_I2S_PD1_MASK (0x7 << 12) +#define RT5677_I2S_PD1_SFT 12 +#define RT5677_I2S_PD1_1 (0x0 << 12) +#define RT5677_I2S_PD1_2 (0x1 << 12) +#define RT5677_I2S_PD1_3 (0x2 << 12) +#define RT5677_I2S_PD1_4 (0x3 << 12) +#define RT5677_I2S_PD1_6 (0x4 << 12) +#define RT5677_I2S_PD1_8 (0x5 << 12) +#define RT5677_I2S_PD1_12 (0x6 << 12) +#define RT5677_I2S_PD1_16 (0x7 << 12) +#define RT5677_I2S_BCLK_MS2_MASK (0x1 << 11) +#define RT5677_I2S_BCLK_MS2_SFT 11 +#define RT5677_I2S_BCLK_MS2_32 (0x0 << 11) +#define RT5677_I2S_BCLK_MS2_64 (0x1 << 11) +#define RT5677_I2S_PD2_MASK (0x7 << 8) +#define RT5677_I2S_PD2_SFT 8 +#define RT5677_I2S_PD2_1 (0x0 << 8) +#define RT5677_I2S_PD2_2 (0x1 << 8) +#define RT5677_I2S_PD2_3 (0x2 << 8) +#define RT5677_I2S_PD2_4 (0x3 << 8) +#define RT5677_I2S_PD2_6 (0x4 << 8) +#define RT5677_I2S_PD2_8 (0x5 << 8) +#define RT5677_I2S_PD2_12 (0x6 << 8) +#define RT5677_I2S_PD2_16 (0x7 << 8) +#define RT5677_I2S_BCLK_MS3_MASK (0x1 << 7) +#define RT5677_I2S_BCLK_MS3_SFT 7 +#define RT5677_I2S_BCLK_MS3_32 (0x0 << 7) +#define RT5677_I2S_BCLK_MS3_64 (0x1 << 7) +#define RT5677_I2S_PD3_MASK (0x7 << 4) +#define RT5677_I2S_PD3_SFT 4 +#define RT5677_I2S_PD3_1 (0x0 << 4) +#define RT5677_I2S_PD3_2 (0x1 << 4) +#define RT5677_I2S_PD3_3 (0x2 << 4) +#define RT5677_I2S_PD3_4 (0x3 << 4) +#define RT5677_I2S_PD3_6 (0x4 << 4) +#define RT5677_I2S_PD3_8 (0x5 << 4) +#define RT5677_I2S_PD3_12 (0x6 << 4) +#define RT5677_I2S_PD3_16 (0x7 << 4) +#define RT5677_I2S_BCLK_MS4_MASK (0x1 << 3) +#define RT5677_I2S_BCLK_MS4_SFT 3 +#define RT5677_I2S_BCLK_MS4_32 (0x0 << 3) +#define RT5677_I2S_BCLK_MS4_64 (0x1 << 3) +#define RT5677_I2S_PD4_MASK (0x7 << 0) +#define RT5677_I2S_PD4_SFT 0 +#define RT5677_I2S_PD4_1 (0x0 << 0) +#define RT5677_I2S_PD4_2 (0x1 << 0) +#define RT5677_I2S_PD4_3 (0x2 << 0) +#define RT5677_I2S_PD4_4 (0x3 << 0) +#define RT5677_I2S_PD4_6 (0x4 << 0) +#define RT5677_I2S_PD4_8 (0x5 << 0) +#define RT5677_I2S_PD4_12 (0x6 << 0) +#define RT5677_I2S_PD4_16 (0x7 << 0) + +/* Clock Tree Control 2 (0x74) */ +#define RT5677_I2S_PD5_MASK (0x7 << 12) +#define RT5677_I2S_PD5_SFT 12 +#define RT5677_I2S_PD5_1 (0x0 << 12) +#define RT5677_I2S_PD5_2 (0x1 << 12) +#define RT5677_I2S_PD5_3 (0x2 << 12) +#define RT5677_I2S_PD5_4 (0x3 << 12) +#define RT5677_I2S_PD5_6 (0x4 << 12) +#define RT5677_I2S_PD5_8 (0x5 << 12) +#define RT5677_I2S_PD5_12 (0x6 << 12) +#define RT5677_I2S_PD5_16 (0x7 << 12) +#define RT5677_I2S_PD6_MASK (0x7 << 8) +#define RT5677_I2S_PD6_SFT 8 +#define RT5677_I2S_PD6_1 (0x0 << 8) +#define RT5677_I2S_PD6_2 (0x1 << 8) +#define RT5677_I2S_PD6_3 (0x2 << 8) +#define RT5677_I2S_PD6_4 (0x3 << 8) +#define RT5677_I2S_PD6_6 (0x4 << 8) +#define RT5677_I2S_PD6_8 (0x5 << 8) +#define RT5677_I2S_PD6_12 (0x6 << 8) +#define RT5677_I2S_PD6_16 (0x7 << 8) +#define RT5677_I2S_PD7_MASK (0x7 << 4) +#define RT5677_I2S_PD7_SFT 4 +#define RT5677_I2S_PD7_1 (0x0 << 4) +#define RT5677_I2S_PD7_2 (0x1 << 4) +#define RT5677_I2S_PD7_3 (0x2 << 4) +#define RT5677_I2S_PD7_4 (0x3 << 4) +#define RT5677_I2S_PD7_6 (0x4 << 4) +#define RT5677_I2S_PD7_8 (0x5 << 4) +#define RT5677_I2S_PD7_12 (0x6 << 4) +#define RT5677_I2S_PD7_16 (0x7 << 4) +#define RT5677_I2S_PD8_MASK (0x7 << 0) +#define RT5677_I2S_PD8_SFT 0 +#define RT5677_I2S_PD8_1 (0x0 << 0) +#define RT5677_I2S_PD8_2 (0x1 << 0) +#define RT5677_I2S_PD8_3 (0x2 << 0) +#define RT5677_I2S_PD8_4 (0x3 << 0) +#define RT5677_I2S_PD8_6 (0x4 << 0) +#define RT5677_I2S_PD8_8 (0x5 << 0) +#define RT5677_I2S_PD8_12 (0x6 << 0) +#define RT5677_I2S_PD8_16 (0x7 << 0) + +/* Clock Tree Control 3 (0x75) */ +#define RT5677_DSP_ASRC_O_MASK (0x3 << 6) +#define RT5677_DSP_ASRC_O_SFT 6 +#define RT5677_DSP_ASRC_O_1_0 (0x0 << 6) +#define RT5677_DSP_ASRC_O_1_5 (0x1 << 6) +#define RT5677_DSP_ASRC_O_2_0 (0x2 << 6) +#define RT5677_DSP_ASRC_O_3_0 (0x3 << 6) +#define RT5677_DSP_ASRC_I_MASK (0x3 << 4) +#define RT5677_DSP_ASRC_I_SFT 4 +#define RT5677_DSP_ASRC_I_1_0 (0x0 << 4) +#define RT5677_DSP_ASRC_I_1_5 (0x1 << 4) +#define RT5677_DSP_ASRC_I_2_0 (0x2 << 4) +#define RT5677_DSP_ASRC_I_3_0 (0x3 << 4) +#define RT5677_DSP_BUS_PD_MASK (0x7 << 0) +#define RT5677_DSP_BUS_PD_SFT 0 +#define RT5677_DSP_BUS_PD_1 (0x0 << 0) +#define RT5677_DSP_BUS_PD_2 (0x1 << 0) +#define RT5677_DSP_BUS_PD_3 (0x2 << 0) +#define RT5677_DSP_BUS_PD_4 (0x3 << 0) +#define RT5677_DSP_BUS_PD_6 (0x4 << 0) +#define RT5677_DSP_BUS_PD_8 (0x5 << 0) +#define RT5677_DSP_BUS_PD_12 (0x6 << 0) +#define RT5677_DSP_BUS_PD_16 (0x7 << 0) + +#define RT5677_PLL_INP_MAX 40000000 +#define RT5677_PLL_INP_MIN 2048000 +/* PLL M/N/K Code Control 1 (0x7a 0x7c) */ +#define RT5677_PLL_N_MAX 0x1ff +#define RT5677_PLL_N_MASK (RT5677_PLL_N_MAX << 7) +#define RT5677_PLL_N_SFT 7 +#define RT5677_PLL_K_BP (0x1 << 5) +#define RT5677_PLL_K_BP_SFT 5 +#define RT5677_PLL_K_MAX 0x1f +#define RT5677_PLL_K_MASK (RT5677_PLL_K_MAX) +#define RT5677_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x7b 0x7d) */ +#define RT5677_PLL_M_MAX 0xf +#define RT5677_PLL_M_MASK (RT5677_PLL_M_MAX << 12) +#define RT5677_PLL_M_SFT 12 +#define RT5677_PLL_M_BP (0x1 << 11) +#define RT5677_PLL_M_BP_SFT 11 + +/* Global Clock Control 1 (0x80) */ +#define RT5677_SCLK_SRC_MASK (0x3 << 14) +#define RT5677_SCLK_SRC_SFT 14 +#define RT5677_SCLK_SRC_MCLK (0x0 << 14) +#define RT5677_SCLK_SRC_PLL1 (0x1 << 14) +#define RT5677_SCLK_SRC_RCCLK (0x2 << 14) /* 25MHz */ +#define RT5677_SCLK_SRC_SLIM (0x3 << 14) +#define RT5677_PLL1_SRC_MASK (0x7 << 11) +#define RT5677_PLL1_SRC_SFT 11 +#define RT5677_PLL1_SRC_MCLK (0x0 << 11) +#define RT5677_PLL1_SRC_BCLK1 (0x1 << 11) +#define RT5677_PLL1_SRC_BCLK2 (0x2 << 11) +#define RT5677_PLL1_SRC_BCLK3 (0x3 << 11) +#define RT5677_PLL1_SRC_BCLK4 (0x4 << 11) +#define RT5677_PLL1_SRC_RCCLK (0x5 << 11) +#define RT5677_PLL1_SRC_SLIM (0x6 << 11) +#define RT5677_MCLK_SRC_MASK (0x1 << 10) +#define RT5677_MCLK_SRC_SFT 10 +#define RT5677_MCLK1_SRC (0x0 << 10) +#define RT5677_MCLK2_SRC (0x1 << 10) +#define RT5677_PLL1_PD_MASK (0x1 << 8) +#define RT5677_PLL1_PD_SFT 8 +#define RT5677_PLL1_PD_1 (0x0 << 8) +#define RT5677_PLL1_PD_2 (0x1 << 8) +#define RT5677_DAC_OSR_MASK (0x3 << 6) +#define RT5677_DAC_OSR_SFT 6 +#define RT5677_DAC_OSR_128 (0x0 << 6) +#define RT5677_DAC_OSR_64 (0x1 << 6) +#define RT5677_DAC_OSR_32 (0x2 << 6) +#define RT5677_ADC_OSR_MASK (0x3 << 4) +#define RT5677_ADC_OSR_SFT 4 +#define RT5677_ADC_OSR_128 (0x0 << 4) +#define RT5677_ADC_OSR_64 (0x1 << 4) +#define RT5677_ADC_OSR_32 (0x2 << 4) + +/* Global Clock Control 2 (0x81) */ +#define RT5677_PLL2_PR_SRC_MASK (0x1 << 15) +#define RT5677_PLL2_PR_SRC_SFT 15 +#define RT5677_PLL2_PR_SRC_MCLK1 (0x0 << 15) +#define RT5677_PLL2_PR_SRC_MCLK2 (0x1 << 15) +#define RT5677_PLL2_SRC_MASK (0x7 << 12) +#define RT5677_PLL2_SRC_SFT 12 +#define RT5677_PLL2_SRC_MCLK (0x0 << 12) +#define RT5677_PLL2_SRC_BCLK1 (0x1 << 12) +#define RT5677_PLL2_SRC_BCLK2 (0x2 << 12) +#define RT5677_PLL2_SRC_BCLK3 (0x3 << 12) +#define RT5677_PLL2_SRC_BCLK4 (0x4 << 12) +#define RT5677_PLL2_SRC_RCCLK (0x5 << 12) +#define RT5677_PLL2_SRC_SLIM (0x6 << 12) +#define RT5677_DSP_ASRC_O_SRC (0x3 << 10) +#define RT5677_DSP_ASRC_O_SRC_SFT 10 +#define RT5677_DSP_ASRC_O_MCLK (0x0 << 10) +#define RT5677_DSP_ASRC_O_PLL1 (0x1 << 10) +#define RT5677_DSP_ASRC_O_SLIM (0x2 << 10) +#define RT5677_DSP_ASRC_O_RCCLK (0x3 << 10) +#define RT5677_DSP_ASRC_I_SRC (0x3 << 8) +#define RT5677_DSP_ASRC_I_SRC_SFT 8 +#define RT5677_DSP_ASRC_I_MCLK (0x0 << 8) +#define RT5677_DSP_ASRC_I_PLL1 (0x1 << 8) +#define RT5677_DSP_ASRC_I_SLIM (0x2 << 8) +#define RT5677_DSP_ASRC_I_RCCLK (0x3 << 8) +#define RT5677_DSP_CLK_SRC_MASK (0x1 << 7) +#define RT5677_DSP_CLK_SRC_SFT 7 +#define RT5677_DSP_CLK_SRC_PLL2 (0x0 << 7) +#define RT5677_DSP_CLK_SRC_BYPASS (0x1 << 7) + +/* ASRC Control 3 (0x85) */ +#define RT5677_DA_STO_CLK_SEL_MASK (0xf << 12) +#define RT5677_DA_STO_CLK_SEL_SFT 12 +#define RT5677_DA_MONO2L_CLK_SEL_MASK (0xf << 4) +#define RT5677_DA_MONO2L_CLK_SEL_SFT 4 +#define RT5677_DA_MONO2R_CLK_SEL_MASK (0xf << 0) +#define RT5677_DA_MONO2R_CLK_SEL_SFT 0 + +/* ASRC Control 4 (0x86) */ +#define RT5677_DA_MONO3L_CLK_SEL_MASK (0xf << 12) +#define RT5677_DA_MONO3L_CLK_SEL_SFT 12 +#define RT5677_DA_MONO3R_CLK_SEL_MASK (0xf << 8) +#define RT5677_DA_MONO3R_CLK_SEL_SFT 8 +#define RT5677_DA_MONO4L_CLK_SEL_MASK (0xf << 4) +#define RT5677_DA_MONO4L_CLK_SEL_SFT 4 +#define RT5677_DA_MONO4R_CLK_SEL_MASK (0xf << 0) +#define RT5677_DA_MONO4R_CLK_SEL_SFT 0 + +/* ASRC Control 5 (0x87) */ +#define RT5677_AD_STO1_CLK_SEL_MASK (0xf << 12) +#define RT5677_AD_STO1_CLK_SEL_SFT 12 +#define RT5677_AD_STO2_CLK_SEL_MASK (0xf << 8) +#define RT5677_AD_STO2_CLK_SEL_SFT 8 +#define RT5677_AD_STO3_CLK_SEL_MASK (0xf << 4) +#define RT5677_AD_STO3_CLK_SEL_SFT 4 +#define RT5677_AD_STO4_CLK_SEL_MASK (0xf << 0) +#define RT5677_AD_STO4_CLK_SEL_SFT 0 + +/* ASRC Control 6 (0x88) */ +#define RT5677_AD_MONOL_CLK_SEL_MASK (0xf << 12) +#define RT5677_AD_MONOL_CLK_SEL_SFT 12 +#define RT5677_AD_MONOR_CLK_SEL_MASK (0xf << 8) +#define RT5677_AD_MONOR_CLK_SEL_SFT 8 + +/* ASRC Control 7 (0x89) */ +#define RT5677_DSP_OB_0_3_CLK_SEL_MASK (0xf << 12) +#define RT5677_DSP_OB_0_3_CLK_SEL_SFT 12 +#define RT5677_DSP_OB_4_7_CLK_SEL_MASK (0xf << 8) +#define RT5677_DSP_OB_4_7_CLK_SEL_SFT 8 + +/* ASRC Control 8 (0x8a) */ +#define RT5677_I2S1_CLK_SEL_MASK (0xf << 12) +#define RT5677_I2S1_CLK_SEL_SFT 12 +#define RT5677_I2S2_CLK_SEL_MASK (0xf << 8) +#define RT5677_I2S2_CLK_SEL_SFT 8 +#define RT5677_I2S3_CLK_SEL_MASK (0xf << 4) +#define RT5677_I2S3_CLK_SEL_SFT 4 +#define RT5677_I2S4_CLK_SEL_MASK (0xf) +#define RT5677_I2S4_CLK_SEL_SFT 0 + +/* VAD Function Control 4 (0x9f) */ +#define RT5677_VAD_SRC_MASK (0x7 << 8) +#define RT5677_VAD_SRC_SFT 8 + +/* DSP InBound Control (0xa3) */ +#define RT5677_IB01_SRC_MASK (0x7 << 12) +#define RT5677_IB01_SRC_SFT 12 +#define RT5677_IB23_SRC_MASK (0x7 << 8) +#define RT5677_IB23_SRC_SFT 8 +#define RT5677_IB45_SRC_MASK (0x7 << 4) +#define RT5677_IB45_SRC_SFT 4 +#define RT5677_IB6_SRC_MASK (0x7 << 0) +#define RT5677_IB6_SRC_SFT 0 + +/* DSP InBound Control (0xa4) */ +#define RT5677_IB7_SRC_MASK (0x7 << 12) +#define RT5677_IB7_SRC_SFT 12 +#define RT5677_IB8_SRC_MASK (0x7 << 8) +#define RT5677_IB8_SRC_SFT 8 +#define RT5677_IB9_SRC_MASK (0x7 << 4) +#define RT5677_IB9_SRC_SFT 4 + +/* DSP In/OutBound Control (0xa5) */ +#define RT5677_SEL_SRC_OB23 (0x1 << 4) +#define RT5677_SEL_SRC_OB23_SFT 4 +#define RT5677_SEL_SRC_OB01 (0x1 << 3) +#define RT5677_SEL_SRC_OB01_SFT 3 +#define RT5677_SEL_SRC_IB45 (0x1 << 2) +#define RT5677_SEL_SRC_IB45_SFT 2 +#define RT5677_SEL_SRC_IB23 (0x1 << 1) +#define RT5677_SEL_SRC_IB23_SFT 1 +#define RT5677_SEL_SRC_IB01 (0x1 << 0) +#define RT5677_SEL_SRC_IB01_SFT 0 + +/* Jack Detect Control 1 (0xb5) */ +#define RT5677_SEL_GPIO_JD1_MASK (0x3 << 14) +#define RT5677_SEL_GPIO_JD1_SFT 14 +#define RT5677_SEL_GPIO_JD2_MASK (0x3 << 12) +#define RT5677_SEL_GPIO_JD2_SFT 12 +#define RT5677_SEL_GPIO_JD3_MASK (0x3 << 10) +#define RT5677_SEL_GPIO_JD3_SFT 10 + +/* IRQ Control 1 (0xbd) */ +#define RT5677_STA_GPIO_JD1 (0x1 << 15) +#define RT5677_STA_GPIO_JD1_SFT 15 +#define RT5677_EN_IRQ_GPIO_JD1 (0x1 << 14) +#define RT5677_EN_IRQ_GPIO_JD1_SFT 14 +#define RT5677_EN_GPIO_JD1_STICKY (0x1 << 13) +#define RT5677_EN_GPIO_JD1_STICKY_SFT 13 +#define RT5677_INV_GPIO_JD1 (0x1 << 12) +#define RT5677_INV_GPIO_JD1_SFT 12 +#define RT5677_STA_GPIO_JD2 (0x1 << 11) +#define RT5677_STA_GPIO_JD2_SFT 11 +#define RT5677_EN_IRQ_GPIO_JD2 (0x1 << 10) +#define RT5677_EN_IRQ_GPIO_JD2_SFT 10 +#define RT5677_EN_GPIO_JD2_STICKY (0x1 << 9) +#define RT5677_EN_GPIO_JD2_STICKY_SFT 9 +#define RT5677_INV_GPIO_JD2 (0x1 << 8) +#define RT5677_INV_GPIO_JD2_SFT 8 +#define RT5677_STA_MICBIAS1_OVCD (0x1 << 7) +#define RT5677_STA_MICBIAS1_OVCD_SFT 7 +#define RT5677_EN_IRQ_MICBIAS1_OVCD (0x1 << 6) +#define RT5677_EN_IRQ_MICBIAS1_OVCD_SFT 6 +#define RT5677_EN_MICBIAS1_OVCD_STICKY (0x1 << 5) +#define RT5677_EN_MICBIAS1_OVCD_STICKY_SFT 5 +#define RT5677_INV_MICBIAS1_OVCD (0x1 << 4) +#define RT5677_INV_MICBIAS1_OVCD_SFT 4 +#define RT5677_STA_GPIO_JD3 (0x1 << 3) +#define RT5677_STA_GPIO_JD3_SFT 3 +#define RT5677_EN_IRQ_GPIO_JD3 (0x1 << 2) +#define RT5677_EN_IRQ_GPIO_JD3_SFT 2 +#define RT5677_EN_GPIO_JD3_STICKY (0x1 << 1) +#define RT5677_EN_GPIO_JD3_STICKY_SFT 1 +#define RT5677_INV_GPIO_JD3 (0x1 << 0) +#define RT5677_INV_GPIO_JD3_SFT 0 + +/* GPIO status (0xbf) */ +#define RT5677_GPIO6_STATUS_MASK (0x1 << 5) +#define RT5677_GPIO6_STATUS_SFT 5 +#define RT5677_GPIO5_STATUS_MASK (0x1 << 4) +#define RT5677_GPIO5_STATUS_SFT 4 +#define RT5677_GPIO4_STATUS_MASK (0x1 << 3) +#define RT5677_GPIO4_STATUS_SFT 3 +#define RT5677_GPIO3_STATUS_MASK (0x1 << 2) +#define RT5677_GPIO3_STATUS_SFT 2 +#define RT5677_GPIO2_STATUS_MASK (0x1 << 1) +#define RT5677_GPIO2_STATUS_SFT 1 +#define RT5677_GPIO1_STATUS_MASK (0x1 << 0) +#define RT5677_GPIO1_STATUS_SFT 0 + +/* GPIO Control 1 (0xc0) */ +#define RT5677_GPIO1_PIN_MASK (0x1 << 15) +#define RT5677_GPIO1_PIN_SFT 15 +#define RT5677_GPIO1_PIN_GPIO1 (0x0 << 15) +#define RT5677_GPIO1_PIN_IRQ (0x1 << 15) +#define RT5677_IPTV_MODE_MASK (0x1 << 14) +#define RT5677_IPTV_MODE_SFT 14 +#define RT5677_IPTV_MODE_GPIO (0x0 << 14) +#define RT5677_IPTV_MODE_IPTV (0x1 << 14) +#define RT5677_FUNC_MODE_MASK (0x1 << 13) +#define RT5677_FUNC_MODE_SFT 13 +#define RT5677_FUNC_MODE_DMIC_GPIO (0x0 << 13) +#define RT5677_FUNC_MODE_JTAG (0x1 << 13) + +/* GPIO Control 2 (0xc1) */ +#define RT5677_GPIO5_DIR_MASK (0x1 << 14) +#define RT5677_GPIO5_DIR_SFT 14 +#define RT5677_GPIO5_DIR_IN (0x0 << 14) +#define RT5677_GPIO5_DIR_OUT (0x1 << 14) +#define RT5677_GPIO5_OUT_MASK (0x1 << 13) +#define RT5677_GPIO5_OUT_SFT 13 +#define RT5677_GPIO5_OUT_LO (0x0 << 13) +#define RT5677_GPIO5_OUT_HI (0x1 << 13) +#define RT5677_GPIO5_P_MASK (0x1 << 12) +#define RT5677_GPIO5_P_SFT 12 +#define RT5677_GPIO5_P_NOR (0x0 << 12) +#define RT5677_GPIO5_P_INV (0x1 << 12) +#define RT5677_GPIO4_DIR_MASK (0x1 << 11) +#define RT5677_GPIO4_DIR_SFT 11 +#define RT5677_GPIO4_DIR_IN (0x0 << 11) +#define RT5677_GPIO4_DIR_OUT (0x1 << 11) +#define RT5677_GPIO4_OUT_MASK (0x1 << 10) +#define RT5677_GPIO4_OUT_SFT 10 +#define RT5677_GPIO4_OUT_LO (0x0 << 10) +#define RT5677_GPIO4_OUT_HI (0x1 << 10) +#define RT5677_GPIO4_P_MASK (0x1 << 9) +#define RT5677_GPIO4_P_SFT 9 +#define RT5677_GPIO4_P_NOR (0x0 << 9) +#define RT5677_GPIO4_P_INV (0x1 << 9) +#define RT5677_GPIO3_DIR_MASK (0x1 << 8) +#define RT5677_GPIO3_DIR_SFT 8 +#define RT5677_GPIO3_DIR_IN (0x0 << 8) +#define RT5677_GPIO3_DIR_OUT (0x1 << 8) +#define RT5677_GPIO3_OUT_MASK (0x1 << 7) +#define RT5677_GPIO3_OUT_SFT 7 +#define RT5677_GPIO3_OUT_LO (0x0 << 7) +#define RT5677_GPIO3_OUT_HI (0x1 << 7) +#define RT5677_GPIO3_P_MASK (0x1 << 6) +#define RT5677_GPIO3_P_SFT 6 +#define RT5677_GPIO3_P_NOR (0x0 << 6) +#define RT5677_GPIO3_P_INV (0x1 << 6) +#define RT5677_GPIO2_DIR_MASK (0x1 << 5) +#define RT5677_GPIO2_DIR_SFT 5 +#define RT5677_GPIO2_DIR_IN (0x0 << 5) +#define RT5677_GPIO2_DIR_OUT (0x1 << 5) +#define RT5677_GPIO2_OUT_MASK (0x1 << 4) +#define RT5677_GPIO2_OUT_SFT 4 +#define RT5677_GPIO2_OUT_LO (0x0 << 4) +#define RT5677_GPIO2_OUT_HI (0x1 << 4) +#define RT5677_GPIO2_P_MASK (0x1 << 3) +#define RT5677_GPIO2_P_SFT 3 +#define RT5677_GPIO2_P_NOR (0x0 << 3) +#define RT5677_GPIO2_P_INV (0x1 << 3) +#define RT5677_GPIO1_DIR_MASK (0x1 << 2) +#define RT5677_GPIO1_DIR_SFT 2 +#define RT5677_GPIO1_DIR_IN (0x0 << 2) +#define RT5677_GPIO1_DIR_OUT (0x1 << 2) +#define RT5677_GPIO1_OUT_MASK (0x1 << 1) +#define RT5677_GPIO1_OUT_SFT 1 +#define RT5677_GPIO1_OUT_LO (0x0 << 1) +#define RT5677_GPIO1_OUT_HI (0x1 << 1) +#define RT5677_GPIO1_P_MASK (0x1 << 0) +#define RT5677_GPIO1_P_SFT 0 +#define RT5677_GPIO1_P_NOR (0x0 << 0) +#define RT5677_GPIO1_P_INV (0x1 << 0) + +/* GPIO Control 3 (0xc2) */ +#define RT5677_GPIO6_DIR_MASK (0x1 << 2) +#define RT5677_GPIO6_DIR_SFT 2 +#define RT5677_GPIO6_DIR_IN (0x0 << 2) +#define RT5677_GPIO6_DIR_OUT (0x1 << 2) +#define RT5677_GPIO6_OUT_MASK (0x1 << 1) +#define RT5677_GPIO6_OUT_SFT 1 +#define RT5677_GPIO6_OUT_LO (0x0 << 1) +#define RT5677_GPIO6_OUT_HI (0x1 << 1) +#define RT5677_GPIO6_P_MASK (0x1 << 0) +#define RT5677_GPIO6_P_SFT 0 +#define RT5677_GPIO6_P_NOR (0x0 << 0) +#define RT5677_GPIO6_P_INV (0x1 << 0) + +/* Virtual DSP Mixer Control (0xf7 0xf8 0xf9) */ +#define RT5677_DSP_IB_01_H (0x1 << 15) +#define RT5677_DSP_IB_01_H_SFT 15 +#define RT5677_DSP_IB_23_H (0x1 << 14) +#define RT5677_DSP_IB_23_H_SFT 14 +#define RT5677_DSP_IB_45_H (0x1 << 13) +#define RT5677_DSP_IB_45_H_SFT 13 +#define RT5677_DSP_IB_6_H (0x1 << 12) +#define RT5677_DSP_IB_6_H_SFT 12 +#define RT5677_DSP_IB_7_H (0x1 << 11) +#define RT5677_DSP_IB_7_H_SFT 11 +#define RT5677_DSP_IB_8_H (0x1 << 10) +#define RT5677_DSP_IB_8_H_SFT 10 +#define RT5677_DSP_IB_9_H (0x1 << 9) +#define RT5677_DSP_IB_9_H_SFT 9 +#define RT5677_DSP_IB_01_L (0x1 << 7) +#define RT5677_DSP_IB_01_L_SFT 7 +#define RT5677_DSP_IB_23_L (0x1 << 6) +#define RT5677_DSP_IB_23_L_SFT 6 +#define RT5677_DSP_IB_45_L (0x1 << 5) +#define RT5677_DSP_IB_45_L_SFT 5 +#define RT5677_DSP_IB_6_L (0x1 << 4) +#define RT5677_DSP_IB_6_L_SFT 4 +#define RT5677_DSP_IB_7_L (0x1 << 3) +#define RT5677_DSP_IB_7_L_SFT 3 +#define RT5677_DSP_IB_8_L (0x1 << 2) +#define RT5677_DSP_IB_8_L_SFT 2 +#define RT5677_DSP_IB_9_L (0x1 << 1) +#define RT5677_DSP_IB_9_L_SFT 1 + +/* General Control2 (0xfc)*/ +#define RT5677_GPIO5_FUNC_MASK (0x1 << 9) +#define RT5677_GPIO5_FUNC_GPIO (0x0 << 9) +#define RT5677_GPIO5_FUNC_DMIC (0x1 << 9) + +#define RT5677_FIRMWARE1 "rt5677_dsp_fw1.bin" +#define RT5677_FIRMWARE2 "rt5677_dsp_fw2.bin" + +/* System Clock Source */ +enum { + RT5677_SCLK_S_MCLK, + RT5677_SCLK_S_PLL1, + RT5677_SCLK_S_RCCLK, +}; + +/* PLL1 Source */ +enum { + RT5677_PLL1_S_MCLK, + RT5677_PLL1_S_BCLK1, + RT5677_PLL1_S_BCLK2, + RT5677_PLL1_S_BCLK3, + RT5677_PLL1_S_BCLK4, +}; + +enum { + RT5677_AIF1, + RT5677_AIF2, + RT5677_AIF3, + RT5677_AIF4, + RT5677_AIF5, + RT5677_AIFS, +}; + +enum { + RT5677_GPIO1, + RT5677_GPIO2, + RT5677_GPIO3, + RT5677_GPIO4, + RT5677_GPIO5, + RT5677_GPIO6, + RT5677_GPIO_NUM, +}; + +enum { + RT5677_IRQ_JD1, + RT5677_IRQ_JD2, + RT5677_IRQ_JD3, +}; + +enum rt5677_type { + RT5677, + RT5676, +}; + +/* ASRC clock source selection */ +enum { + RT5677_CLK_SEL_SYS, + RT5677_CLK_SEL_I2S1_ASRC, + RT5677_CLK_SEL_I2S2_ASRC, + RT5677_CLK_SEL_I2S3_ASRC, + RT5677_CLK_SEL_I2S4_ASRC, + RT5677_CLK_SEL_I2S5_ASRC, + RT5677_CLK_SEL_I2S6_ASRC, + RT5677_CLK_SEL_SYS2, + RT5677_CLK_SEL_SYS3, + RT5677_CLK_SEL_SYS4, + RT5677_CLK_SEL_SYS5, + RT5677_CLK_SEL_SYS6, + RT5677_CLK_SEL_SYS7, +}; + +/* filter mask */ +enum { + RT5677_DA_STEREO_FILTER = 0x1, + RT5677_DA_MONO2_L_FILTER = (0x1 << 1), + RT5677_DA_MONO2_R_FILTER = (0x1 << 2), + RT5677_DA_MONO3_L_FILTER = (0x1 << 3), + RT5677_DA_MONO3_R_FILTER = (0x1 << 4), + RT5677_DA_MONO4_L_FILTER = (0x1 << 5), + RT5677_DA_MONO4_R_FILTER = (0x1 << 6), + RT5677_AD_STEREO1_FILTER = (0x1 << 7), + RT5677_AD_STEREO2_FILTER = (0x1 << 8), + RT5677_AD_STEREO3_FILTER = (0x1 << 9), + RT5677_AD_STEREO4_FILTER = (0x1 << 10), + RT5677_AD_MONO_L_FILTER = (0x1 << 11), + RT5677_AD_MONO_R_FILTER = (0x1 << 12), + RT5677_DSP_OB_0_3_FILTER = (0x1 << 13), + RT5677_DSP_OB_4_7_FILTER = (0x1 << 14), + RT5677_I2S1_SOURCE = (0x1 << 15), + RT5677_I2S2_SOURCE = (0x1 << 16), + RT5677_I2S3_SOURCE = (0x1 << 17), + RT5677_I2S4_SOURCE = (0x1 << 18), +}; + +enum rt5677_dmic2_clk { + RT5677_DMIC_CLK1 = 0, + RT5677_DMIC_CLK2 = 1, +}; + +struct rt5677_platform_data { + /* IN1/IN2/LOUT1/LOUT2/LOUT3 can optionally be differential */ + bool in1_diff; + bool in2_diff; + bool lout1_diff; + bool lout2_diff; + bool lout3_diff; + /* DMIC2 clock source selection */ + enum rt5677_dmic2_clk dmic2_clk_pin; + + /* configures GPIO, 0 - floating, 1 - pulldown, 2 - pullup */ + u8 gpio_config[6]; + + /* jd1 can select 0 ~ 3 as OFF, GPIO1, GPIO2 and GPIO3 respectively */ + unsigned int jd1_gpio; + /* jd2 and jd3 can select 0 ~ 3 as + OFF, GPIO4, GPIO5 and GPIO6 respectively */ + unsigned int jd2_gpio; + unsigned int jd3_gpio; + + /* Set MICBIAS1 VDD 1v8 or 3v3 */ + bool micbias1_vdd_3v3; +}; + +struct rt5677_priv { + struct snd_soc_component *component; + struct rt5677_platform_data pdata; + struct regmap *regmap, *regmap_physical; + const struct firmware *fw1, *fw2; + struct mutex dsp_cmd_lock, dsp_pri_lock; + + int sysclk; + int sysclk_src; + int lrck[RT5677_AIFS]; + int bclk[RT5677_AIFS]; + int master[RT5677_AIFS]; + int pll_src; + int pll_in; + int pll_out; + struct gpio_desc *pow_ldo2; /* POW_LDO2 pin */ + struct gpio_desc *reset_pin; /* RESET pin */ + enum rt5677_type type; +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif + bool dsp_vad_en; + struct regmap_irq_chip_data *irq_data; + bool is_dsp_mode; + bool is_vref_slow; +}; + +int rt5677_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +#endif /* __RT5677_H__ */ diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c new file mode 100644 index 000000000..5979165ac --- /dev/null +++ b/sound/soc/codecs/rt5682.c @@ -0,0 +1,2694 @@ +/* + * rt5682.c -- RT5682 ALSA SoC audio component driver + * + * Copyright 2018 Realtek Semiconductor Corp. + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rl6231.h" +#include "rt5682.h" + +#define RT5682_NUM_SUPPLIES 3 + +static const char *rt5682_supply_names[RT5682_NUM_SUPPLIES] = { + "AVDD", + "MICVDD", + "VBAT", +}; + +struct rt5682_priv { + struct snd_soc_component *component; + struct rt5682_platform_data pdata; + struct regmap *regmap; + struct snd_soc_jack *hs_jack; + struct regulator_bulk_data supplies[RT5682_NUM_SUPPLIES]; + struct delayed_work jack_detect_work; + struct delayed_work jd_check_work; + struct mutex calibrate_mutex; + + int sysclk; + int sysclk_src; + int lrck[RT5682_AIFS]; + int bclk[RT5682_AIFS]; + int master[RT5682_AIFS]; + + int pll_src; + int pll_in; + int pll_out; + + int jack_type; +}; + +static const struct reg_sequence patch_list[] = { + {0x01c1, 0x1000}, + {RT5682_DAC_ADC_DIG_VOL1, 0xa020}, +}; + +static const struct reg_default rt5682_reg[] = { + {0x0002, 0x8080}, + {0x0003, 0x8000}, + {0x0005, 0x0000}, + {0x0006, 0x0000}, + {0x0008, 0x800f}, + {0x000b, 0x0000}, + {0x0010, 0x4040}, + {0x0011, 0x0000}, + {0x0012, 0x1404}, + {0x0013, 0x1000}, + {0x0014, 0xa00a}, + {0x0015, 0x0404}, + {0x0016, 0x0404}, + {0x0019, 0xafaf}, + {0x001c, 0x2f2f}, + {0x001f, 0x0000}, + {0x0022, 0x5757}, + {0x0023, 0x0039}, + {0x0024, 0x000b}, + {0x0026, 0xc0c4}, + {0x0029, 0x8080}, + {0x002a, 0xa0a0}, + {0x002b, 0x0300}, + {0x0030, 0x0000}, + {0x003c, 0x0080}, + {0x0044, 0x0c0c}, + {0x0049, 0x0000}, + {0x0061, 0x0000}, + {0x0062, 0x0000}, + {0x0063, 0x003f}, + {0x0064, 0x0000}, + {0x0065, 0x0000}, + {0x0066, 0x0030}, + {0x0067, 0x0000}, + {0x006b, 0x0000}, + {0x006c, 0x0000}, + {0x006d, 0x2200}, + {0x006e, 0x0a10}, + {0x0070, 0x8000}, + {0x0071, 0x8000}, + {0x0073, 0x0000}, + {0x0074, 0x0000}, + {0x0075, 0x0002}, + {0x0076, 0x0001}, + {0x0079, 0x0000}, + {0x007a, 0x0000}, + {0x007b, 0x0000}, + {0x007c, 0x0100}, + {0x007e, 0x0000}, + {0x0080, 0x0000}, + {0x0081, 0x0000}, + {0x0082, 0x0000}, + {0x0083, 0x0000}, + {0x0084, 0x0000}, + {0x0085, 0x0000}, + {0x0086, 0x0005}, + {0x0087, 0x0000}, + {0x0088, 0x0000}, + {0x008c, 0x0003}, + {0x008d, 0x0000}, + {0x008e, 0x0060}, + {0x008f, 0x1000}, + {0x0091, 0x0c26}, + {0x0092, 0x0073}, + {0x0093, 0x0000}, + {0x0094, 0x0080}, + {0x0098, 0x0000}, + {0x009a, 0x0000}, + {0x009b, 0x0000}, + {0x009c, 0x0000}, + {0x009d, 0x0000}, + {0x009e, 0x100c}, + {0x009f, 0x0000}, + {0x00a0, 0x0000}, + {0x00a3, 0x0002}, + {0x00a4, 0x0001}, + {0x00ae, 0x2040}, + {0x00af, 0x0000}, + {0x00b6, 0x0000}, + {0x00b7, 0x0000}, + {0x00b8, 0x0000}, + {0x00b9, 0x0002}, + {0x00be, 0x0000}, + {0x00c0, 0x0160}, + {0x00c1, 0x82a0}, + {0x00c2, 0x0000}, + {0x00d0, 0x0000}, + {0x00d1, 0x2244}, + {0x00d2, 0x3300}, + {0x00d3, 0x2200}, + {0x00d4, 0x0000}, + {0x00d9, 0x0009}, + {0x00da, 0x0000}, + {0x00db, 0x0000}, + {0x00dc, 0x00c0}, + {0x00dd, 0x2220}, + {0x00de, 0x3131}, + {0x00df, 0x3131}, + {0x00e0, 0x3131}, + {0x00e2, 0x0000}, + {0x00e3, 0x4000}, + {0x00e4, 0x0aa0}, + {0x00e5, 0x3131}, + {0x00e6, 0x3131}, + {0x00e7, 0x3131}, + {0x00e8, 0x3131}, + {0x00ea, 0xb320}, + {0x00eb, 0x0000}, + {0x00f0, 0x0000}, + {0x00f1, 0x00d0}, + {0x00f2, 0x00d0}, + {0x00f6, 0x0000}, + {0x00fa, 0x0000}, + {0x00fb, 0x0000}, + {0x00fc, 0x0000}, + {0x00fd, 0x0000}, + {0x00fe, 0x10ec}, + {0x00ff, 0x6530}, + {0x0100, 0xa0a0}, + {0x010b, 0x0000}, + {0x010c, 0xae00}, + {0x010d, 0xaaa0}, + {0x010e, 0x8aa2}, + {0x010f, 0x02a2}, + {0x0110, 0xc000}, + {0x0111, 0x04a2}, + {0x0112, 0x2800}, + {0x0113, 0x0000}, + {0x0117, 0x0100}, + {0x0125, 0x0410}, + {0x0132, 0x6026}, + {0x0136, 0x5555}, + {0x0138, 0x3700}, + {0x013a, 0x2000}, + {0x013b, 0x2000}, + {0x013c, 0x2005}, + {0x013f, 0x0000}, + {0x0142, 0x0000}, + {0x0145, 0x0002}, + {0x0146, 0x0000}, + {0x0147, 0x0000}, + {0x0148, 0x0000}, + {0x0149, 0x0000}, + {0x0150, 0x79a1}, + {0x0151, 0x0000}, + {0x0160, 0x4ec0}, + {0x0161, 0x0080}, + {0x0162, 0x0200}, + {0x0163, 0x0800}, + {0x0164, 0x0000}, + {0x0165, 0x0000}, + {0x0166, 0x0000}, + {0x0167, 0x000f}, + {0x0168, 0x000f}, + {0x0169, 0x0021}, + {0x0190, 0x413d}, + {0x0194, 0x0000}, + {0x0195, 0x0000}, + {0x0197, 0x0022}, + {0x0198, 0x0000}, + {0x0199, 0x0000}, + {0x01af, 0x0000}, + {0x01b0, 0x0400}, + {0x01b1, 0x0000}, + {0x01b2, 0x0000}, + {0x01b3, 0x0000}, + {0x01b4, 0x0000}, + {0x01b5, 0x0000}, + {0x01b6, 0x01c3}, + {0x01b7, 0x02a0}, + {0x01b8, 0x03e9}, + {0x01b9, 0x1389}, + {0x01ba, 0xc351}, + {0x01bb, 0x0009}, + {0x01bc, 0x0018}, + {0x01bd, 0x002a}, + {0x01be, 0x004c}, + {0x01bf, 0x0097}, + {0x01c0, 0x433d}, + {0x01c2, 0x0000}, + {0x01c3, 0x0000}, + {0x01c4, 0x0000}, + {0x01c5, 0x0000}, + {0x01c6, 0x0000}, + {0x01c7, 0x0000}, + {0x01c8, 0x40af}, + {0x01c9, 0x0702}, + {0x01ca, 0x0000}, + {0x01cb, 0x0000}, + {0x01cc, 0x5757}, + {0x01cd, 0x5757}, + {0x01ce, 0x5757}, + {0x01cf, 0x5757}, + {0x01d0, 0x5757}, + {0x01d1, 0x5757}, + {0x01d2, 0x5757}, + {0x01d3, 0x5757}, + {0x01d4, 0x5757}, + {0x01d5, 0x5757}, + {0x01d6, 0x0000}, + {0x01d7, 0x0008}, + {0x01d8, 0x0029}, + {0x01d9, 0x3333}, + {0x01da, 0x0000}, + {0x01db, 0x0004}, + {0x01dc, 0x0000}, + {0x01de, 0x7c00}, + {0x01df, 0x0320}, + {0x01e0, 0x06a1}, + {0x01e1, 0x0000}, + {0x01e2, 0x0000}, + {0x01e3, 0x0000}, + {0x01e4, 0x0000}, + {0x01e6, 0x0001}, + {0x01e7, 0x0000}, + {0x01e8, 0x0000}, + {0x01ea, 0x0000}, + {0x01eb, 0x0000}, + {0x01ec, 0x0000}, + {0x01ed, 0x0000}, + {0x01ee, 0x0000}, + {0x01ef, 0x0000}, + {0x01f0, 0x0000}, + {0x01f1, 0x0000}, + {0x01f2, 0x0000}, + {0x01f3, 0x0000}, + {0x01f4, 0x0000}, + {0x0210, 0x6297}, + {0x0211, 0xa005}, + {0x0212, 0x824c}, + {0x0213, 0xf7ff}, + {0x0214, 0xf24c}, + {0x0215, 0x0102}, + {0x0216, 0x00a3}, + {0x0217, 0x0048}, + {0x0218, 0xa2c0}, + {0x0219, 0x0400}, + {0x021a, 0x00c8}, + {0x021b, 0x00c0}, + {0x021c, 0x0000}, + {0x0250, 0x4500}, + {0x0251, 0x40b3}, + {0x0252, 0x0000}, + {0x0253, 0x0000}, + {0x0254, 0x0000}, + {0x0255, 0x0000}, + {0x0256, 0x0000}, + {0x0257, 0x0000}, + {0x0258, 0x0000}, + {0x0259, 0x0000}, + {0x025a, 0x0005}, + {0x0270, 0x0000}, + {0x02ff, 0x0110}, + {0x0300, 0x001f}, + {0x0301, 0x032c}, + {0x0302, 0x5f21}, + {0x0303, 0x4000}, + {0x0304, 0x4000}, + {0x0305, 0x06d5}, + {0x0306, 0x8000}, + {0x0307, 0x0700}, + {0x0310, 0x4560}, + {0x0311, 0xa4a8}, + {0x0312, 0x7418}, + {0x0313, 0x0000}, + {0x0314, 0x0006}, + {0x0315, 0xffff}, + {0x0316, 0xc400}, + {0x0317, 0x0000}, + {0x03c0, 0x7e00}, + {0x03c1, 0x8000}, + {0x03c2, 0x8000}, + {0x03c3, 0x8000}, + {0x03c4, 0x8000}, + {0x03c5, 0x8000}, + {0x03c6, 0x8000}, + {0x03c7, 0x8000}, + {0x03c8, 0x8000}, + {0x03c9, 0x8000}, + {0x03ca, 0x8000}, + {0x03cb, 0x8000}, + {0x03cc, 0x8000}, + {0x03d0, 0x0000}, + {0x03d1, 0x0000}, + {0x03d2, 0x0000}, + {0x03d3, 0x0000}, + {0x03d4, 0x2000}, + {0x03d5, 0x2000}, + {0x03d6, 0x0000}, + {0x03d7, 0x0000}, + {0x03d8, 0x2000}, + {0x03d9, 0x2000}, + {0x03da, 0x2000}, + {0x03db, 0x2000}, + {0x03dc, 0x0000}, + {0x03dd, 0x0000}, + {0x03de, 0x0000}, + {0x03df, 0x2000}, + {0x03e0, 0x0000}, + {0x03e1, 0x0000}, + {0x03e2, 0x0000}, + {0x03e3, 0x0000}, + {0x03e4, 0x0000}, + {0x03e5, 0x0000}, + {0x03e6, 0x0000}, + {0x03e7, 0x0000}, + {0x03e8, 0x0000}, + {0x03e9, 0x0000}, + {0x03ea, 0x0000}, + {0x03eb, 0x0000}, + {0x03ec, 0x0000}, + {0x03ed, 0x0000}, + {0x03ee, 0x0000}, + {0x03ef, 0x0000}, + {0x03f0, 0x0800}, + {0x03f1, 0x0800}, + {0x03f2, 0x0800}, + {0x03f3, 0x0800}, +}; + +static bool rt5682_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5682_RESET: + case RT5682_CBJ_CTRL_2: + case RT5682_INT_ST_1: + case RT5682_4BTN_IL_CMD_1: + case RT5682_AJD1_CTRL: + case RT5682_HP_CALIB_CTRL_1: + case RT5682_DEVICE_ID: + case RT5682_I2C_MODE: + case RT5682_HP_CALIB_CTRL_10: + case RT5682_EFUSE_CTRL_2: + case RT5682_JD_TOP_VC_VTRL: + case RT5682_HP_IMP_SENS_CTRL_19: + case RT5682_IL_CMD_1: + case RT5682_SAR_IL_CMD_2: + case RT5682_SAR_IL_CMD_4: + case RT5682_SAR_IL_CMD_10: + case RT5682_SAR_IL_CMD_11: + case RT5682_EFUSE_CTRL_6...RT5682_EFUSE_CTRL_11: + case RT5682_HP_CALIB_STA_1...RT5682_HP_CALIB_STA_11: + return true; + default: + return false; + } +} + +static bool rt5682_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT5682_RESET: + case RT5682_VERSION_ID: + case RT5682_VENDOR_ID: + case RT5682_DEVICE_ID: + case RT5682_HP_CTRL_1: + case RT5682_HP_CTRL_2: + case RT5682_HPL_GAIN: + case RT5682_HPR_GAIN: + case RT5682_I2C_CTRL: + case RT5682_CBJ_BST_CTRL: + case RT5682_CBJ_CTRL_1: + case RT5682_CBJ_CTRL_2: + case RT5682_CBJ_CTRL_3: + case RT5682_CBJ_CTRL_4: + case RT5682_CBJ_CTRL_5: + case RT5682_CBJ_CTRL_6: + case RT5682_CBJ_CTRL_7: + case RT5682_DAC1_DIG_VOL: + case RT5682_STO1_ADC_DIG_VOL: + case RT5682_STO1_ADC_BOOST: + case RT5682_HP_IMP_GAIN_1: + case RT5682_HP_IMP_GAIN_2: + case RT5682_SIDETONE_CTRL: + case RT5682_STO1_ADC_MIXER: + case RT5682_AD_DA_MIXER: + case RT5682_STO1_DAC_MIXER: + case RT5682_A_DAC1_MUX: + case RT5682_DIG_INF2_DATA: + case RT5682_REC_MIXER: + case RT5682_CAL_REC: + case RT5682_ALC_BACK_GAIN: + case RT5682_PWR_DIG_1: + case RT5682_PWR_DIG_2: + case RT5682_PWR_ANLG_1: + case RT5682_PWR_ANLG_2: + case RT5682_PWR_ANLG_3: + case RT5682_PWR_MIXER: + case RT5682_PWR_VOL: + case RT5682_CLK_DET: + case RT5682_RESET_LPF_CTRL: + case RT5682_RESET_HPF_CTRL: + case RT5682_DMIC_CTRL_1: + case RT5682_I2S1_SDP: + case RT5682_I2S2_SDP: + case RT5682_ADDA_CLK_1: + case RT5682_ADDA_CLK_2: + case RT5682_I2S1_F_DIV_CTRL_1: + case RT5682_I2S1_F_DIV_CTRL_2: + case RT5682_TDM_CTRL: + case RT5682_TDM_ADDA_CTRL_1: + case RT5682_TDM_ADDA_CTRL_2: + case RT5682_DATA_SEL_CTRL_1: + case RT5682_TDM_TCON_CTRL: + case RT5682_GLB_CLK: + case RT5682_PLL_CTRL_1: + case RT5682_PLL_CTRL_2: + case RT5682_PLL_TRACK_1: + case RT5682_PLL_TRACK_2: + case RT5682_PLL_TRACK_3: + case RT5682_PLL_TRACK_4: + case RT5682_PLL_TRACK_5: + case RT5682_PLL_TRACK_6: + case RT5682_PLL_TRACK_11: + case RT5682_SDW_REF_CLK: + case RT5682_DEPOP_1: + case RT5682_DEPOP_2: + case RT5682_HP_CHARGE_PUMP_1: + case RT5682_HP_CHARGE_PUMP_2: + case RT5682_MICBIAS_1: + case RT5682_MICBIAS_2: + case RT5682_PLL_TRACK_12: + case RT5682_PLL_TRACK_14: + case RT5682_PLL2_CTRL_1: + case RT5682_PLL2_CTRL_2: + case RT5682_PLL2_CTRL_3: + case RT5682_PLL2_CTRL_4: + case RT5682_RC_CLK_CTRL: + case RT5682_I2S_M_CLK_CTRL_1: + case RT5682_I2S2_F_DIV_CTRL_1: + case RT5682_I2S2_F_DIV_CTRL_2: + case RT5682_EQ_CTRL_1: + case RT5682_EQ_CTRL_2: + case RT5682_IRQ_CTRL_1: + case RT5682_IRQ_CTRL_2: + case RT5682_IRQ_CTRL_3: + case RT5682_IRQ_CTRL_4: + case RT5682_INT_ST_1: + case RT5682_GPIO_CTRL_1: + case RT5682_GPIO_CTRL_2: + case RT5682_GPIO_CTRL_3: + case RT5682_HP_AMP_DET_CTRL_1: + case RT5682_HP_AMP_DET_CTRL_2: + case RT5682_MID_HP_AMP_DET: + case RT5682_LOW_HP_AMP_DET: + case RT5682_DELAY_BUF_CTRL: + case RT5682_SV_ZCD_1: + case RT5682_SV_ZCD_2: + case RT5682_IL_CMD_1: + case RT5682_IL_CMD_2: + case RT5682_IL_CMD_3: + case RT5682_IL_CMD_4: + case RT5682_IL_CMD_5: + case RT5682_IL_CMD_6: + case RT5682_4BTN_IL_CMD_1: + case RT5682_4BTN_IL_CMD_2: + case RT5682_4BTN_IL_CMD_3: + case RT5682_4BTN_IL_CMD_4: + case RT5682_4BTN_IL_CMD_5: + case RT5682_4BTN_IL_CMD_6: + case RT5682_4BTN_IL_CMD_7: + case RT5682_ADC_STO1_HP_CTRL_1: + case RT5682_ADC_STO1_HP_CTRL_2: + case RT5682_AJD1_CTRL: + case RT5682_JD1_THD: + case RT5682_JD2_THD: + case RT5682_JD_CTRL_1: + case RT5682_DUMMY_1: + case RT5682_DUMMY_2: + case RT5682_DUMMY_3: + case RT5682_DAC_ADC_DIG_VOL1: + case RT5682_BIAS_CUR_CTRL_2: + case RT5682_BIAS_CUR_CTRL_3: + case RT5682_BIAS_CUR_CTRL_4: + case RT5682_BIAS_CUR_CTRL_5: + case RT5682_BIAS_CUR_CTRL_6: + case RT5682_BIAS_CUR_CTRL_7: + case RT5682_BIAS_CUR_CTRL_8: + case RT5682_BIAS_CUR_CTRL_9: + case RT5682_BIAS_CUR_CTRL_10: + case RT5682_VREF_REC_OP_FB_CAP_CTRL: + case RT5682_CHARGE_PUMP_1: + case RT5682_DIG_IN_CTRL_1: + case RT5682_PAD_DRIVING_CTRL: + case RT5682_SOFT_RAMP_DEPOP: + case RT5682_CHOP_DAC: + case RT5682_CHOP_ADC: + case RT5682_CALIB_ADC_CTRL: + case RT5682_VOL_TEST: + case RT5682_SPKVDD_DET_STA: + case RT5682_TEST_MODE_CTRL_1: + case RT5682_TEST_MODE_CTRL_2: + case RT5682_TEST_MODE_CTRL_3: + case RT5682_TEST_MODE_CTRL_4: + case RT5682_TEST_MODE_CTRL_5: + case RT5682_PLL1_INTERNAL: + case RT5682_PLL2_INTERNAL: + case RT5682_STO_NG2_CTRL_1: + case RT5682_STO_NG2_CTRL_2: + case RT5682_STO_NG2_CTRL_3: + case RT5682_STO_NG2_CTRL_4: + case RT5682_STO_NG2_CTRL_5: + case RT5682_STO_NG2_CTRL_6: + case RT5682_STO_NG2_CTRL_7: + case RT5682_STO_NG2_CTRL_8: + case RT5682_STO_NG2_CTRL_9: + case RT5682_STO_NG2_CTRL_10: + case RT5682_STO1_DAC_SIL_DET: + case RT5682_SIL_PSV_CTRL1: + case RT5682_SIL_PSV_CTRL2: + case RT5682_SIL_PSV_CTRL3: + case RT5682_SIL_PSV_CTRL4: + case RT5682_SIL_PSV_CTRL5: + case RT5682_HP_IMP_SENS_CTRL_01: + case RT5682_HP_IMP_SENS_CTRL_02: + case RT5682_HP_IMP_SENS_CTRL_03: + case RT5682_HP_IMP_SENS_CTRL_04: + case RT5682_HP_IMP_SENS_CTRL_05: + case RT5682_HP_IMP_SENS_CTRL_06: + case RT5682_HP_IMP_SENS_CTRL_07: + case RT5682_HP_IMP_SENS_CTRL_08: + case RT5682_HP_IMP_SENS_CTRL_09: + case RT5682_HP_IMP_SENS_CTRL_10: + case RT5682_HP_IMP_SENS_CTRL_11: + case RT5682_HP_IMP_SENS_CTRL_12: + case RT5682_HP_IMP_SENS_CTRL_13: + case RT5682_HP_IMP_SENS_CTRL_14: + case RT5682_HP_IMP_SENS_CTRL_15: + case RT5682_HP_IMP_SENS_CTRL_16: + case RT5682_HP_IMP_SENS_CTRL_17: + case RT5682_HP_IMP_SENS_CTRL_18: + case RT5682_HP_IMP_SENS_CTRL_19: + case RT5682_HP_IMP_SENS_CTRL_20: + case RT5682_HP_IMP_SENS_CTRL_21: + case RT5682_HP_IMP_SENS_CTRL_22: + case RT5682_HP_IMP_SENS_CTRL_23: + case RT5682_HP_IMP_SENS_CTRL_24: + case RT5682_HP_IMP_SENS_CTRL_25: + case RT5682_HP_IMP_SENS_CTRL_26: + case RT5682_HP_IMP_SENS_CTRL_27: + case RT5682_HP_IMP_SENS_CTRL_28: + case RT5682_HP_IMP_SENS_CTRL_29: + case RT5682_HP_IMP_SENS_CTRL_30: + case RT5682_HP_IMP_SENS_CTRL_31: + case RT5682_HP_IMP_SENS_CTRL_32: + case RT5682_HP_IMP_SENS_CTRL_33: + case RT5682_HP_IMP_SENS_CTRL_34: + case RT5682_HP_IMP_SENS_CTRL_35: + case RT5682_HP_IMP_SENS_CTRL_36: + case RT5682_HP_IMP_SENS_CTRL_37: + case RT5682_HP_IMP_SENS_CTRL_38: + case RT5682_HP_IMP_SENS_CTRL_39: + case RT5682_HP_IMP_SENS_CTRL_40: + case RT5682_HP_IMP_SENS_CTRL_41: + case RT5682_HP_IMP_SENS_CTRL_42: + case RT5682_HP_IMP_SENS_CTRL_43: + case RT5682_HP_LOGIC_CTRL_1: + case RT5682_HP_LOGIC_CTRL_2: + case RT5682_HP_LOGIC_CTRL_3: + case RT5682_HP_CALIB_CTRL_1: + case RT5682_HP_CALIB_CTRL_2: + case RT5682_HP_CALIB_CTRL_3: + case RT5682_HP_CALIB_CTRL_4: + case RT5682_HP_CALIB_CTRL_5: + case RT5682_HP_CALIB_CTRL_6: + case RT5682_HP_CALIB_CTRL_7: + case RT5682_HP_CALIB_CTRL_9: + case RT5682_HP_CALIB_CTRL_10: + case RT5682_HP_CALIB_CTRL_11: + case RT5682_HP_CALIB_STA_1: + case RT5682_HP_CALIB_STA_2: + case RT5682_HP_CALIB_STA_3: + case RT5682_HP_CALIB_STA_4: + case RT5682_HP_CALIB_STA_5: + case RT5682_HP_CALIB_STA_6: + case RT5682_HP_CALIB_STA_7: + case RT5682_HP_CALIB_STA_8: + case RT5682_HP_CALIB_STA_9: + case RT5682_HP_CALIB_STA_10: + case RT5682_HP_CALIB_STA_11: + case RT5682_SAR_IL_CMD_1: + case RT5682_SAR_IL_CMD_2: + case RT5682_SAR_IL_CMD_3: + case RT5682_SAR_IL_CMD_4: + case RT5682_SAR_IL_CMD_5: + case RT5682_SAR_IL_CMD_6: + case RT5682_SAR_IL_CMD_7: + case RT5682_SAR_IL_CMD_8: + case RT5682_SAR_IL_CMD_9: + case RT5682_SAR_IL_CMD_10: + case RT5682_SAR_IL_CMD_11: + case RT5682_SAR_IL_CMD_12: + case RT5682_SAR_IL_CMD_13: + case RT5682_EFUSE_CTRL_1: + case RT5682_EFUSE_CTRL_2: + case RT5682_EFUSE_CTRL_3: + case RT5682_EFUSE_CTRL_4: + case RT5682_EFUSE_CTRL_5: + case RT5682_EFUSE_CTRL_6: + case RT5682_EFUSE_CTRL_7: + case RT5682_EFUSE_CTRL_8: + case RT5682_EFUSE_CTRL_9: + case RT5682_EFUSE_CTRL_10: + case RT5682_EFUSE_CTRL_11: + case RT5682_JD_TOP_VC_VTRL: + case RT5682_DRC1_CTRL_0: + case RT5682_DRC1_CTRL_1: + case RT5682_DRC1_CTRL_2: + case RT5682_DRC1_CTRL_3: + case RT5682_DRC1_CTRL_4: + case RT5682_DRC1_CTRL_5: + case RT5682_DRC1_CTRL_6: + case RT5682_DRC1_HARD_LMT_CTRL_1: + case RT5682_DRC1_HARD_LMT_CTRL_2: + case RT5682_DRC1_PRIV_1: + case RT5682_DRC1_PRIV_2: + case RT5682_DRC1_PRIV_3: + case RT5682_DRC1_PRIV_4: + case RT5682_DRC1_PRIV_5: + case RT5682_DRC1_PRIV_6: + case RT5682_DRC1_PRIV_7: + case RT5682_DRC1_PRIV_8: + case RT5682_EQ_AUTO_RCV_CTRL1: + case RT5682_EQ_AUTO_RCV_CTRL2: + case RT5682_EQ_AUTO_RCV_CTRL3: + case RT5682_EQ_AUTO_RCV_CTRL4: + case RT5682_EQ_AUTO_RCV_CTRL5: + case RT5682_EQ_AUTO_RCV_CTRL6: + case RT5682_EQ_AUTO_RCV_CTRL7: + case RT5682_EQ_AUTO_RCV_CTRL8: + case RT5682_EQ_AUTO_RCV_CTRL9: + case RT5682_EQ_AUTO_RCV_CTRL10: + case RT5682_EQ_AUTO_RCV_CTRL11: + case RT5682_EQ_AUTO_RCV_CTRL12: + case RT5682_EQ_AUTO_RCV_CTRL13: + case RT5682_ADC_L_EQ_LPF1_A1: + case RT5682_R_EQ_LPF1_A1: + case RT5682_L_EQ_LPF1_H0: + case RT5682_R_EQ_LPF1_H0: + case RT5682_L_EQ_BPF1_A1: + case RT5682_R_EQ_BPF1_A1: + case RT5682_L_EQ_BPF1_A2: + case RT5682_R_EQ_BPF1_A2: + case RT5682_L_EQ_BPF1_H0: + case RT5682_R_EQ_BPF1_H0: + case RT5682_L_EQ_BPF2_A1: + case RT5682_R_EQ_BPF2_A1: + case RT5682_L_EQ_BPF2_A2: + case RT5682_R_EQ_BPF2_A2: + case RT5682_L_EQ_BPF2_H0: + case RT5682_R_EQ_BPF2_H0: + case RT5682_L_EQ_BPF3_A1: + case RT5682_R_EQ_BPF3_A1: + case RT5682_L_EQ_BPF3_A2: + case RT5682_R_EQ_BPF3_A2: + case RT5682_L_EQ_BPF3_H0: + case RT5682_R_EQ_BPF3_H0: + case RT5682_L_EQ_BPF4_A1: + case RT5682_R_EQ_BPF4_A1: + case RT5682_L_EQ_BPF4_A2: + case RT5682_R_EQ_BPF4_A2: + case RT5682_L_EQ_BPF4_H0: + case RT5682_R_EQ_BPF4_H0: + case RT5682_L_EQ_HPF1_A1: + case RT5682_R_EQ_HPF1_A1: + case RT5682_L_EQ_HPF1_H0: + case RT5682_R_EQ_HPF1_H0: + case RT5682_L_EQ_PRE_VOL: + case RT5682_R_EQ_PRE_VOL: + case RT5682_L_EQ_POST_VOL: + case RT5682_R_EQ_POST_VOL: + case RT5682_I2C_MODE: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_bst_tlv, 0, 1200, 0); + +/* {0, +20, +24, +30, +35, +40, +44, +50, +52} dB */ +static const DECLARE_TLV_DB_RANGE(bst_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 5, TLV_DB_SCALE_ITEM(3000, 500, 0), + 6, 6, TLV_DB_SCALE_ITEM(4400, 0, 0), + 7, 7, TLV_DB_SCALE_ITEM(5000, 0, 0), + 8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0) +); + +/* Interface data select */ +static const char * const rt5682_data_select[] = { + "L/R", "R/L", "L/L", "R/R" +}; + +static SOC_ENUM_SINGLE_DECL(rt5682_if2_adc_enum, + RT5682_DIG_INF2_DATA, RT5682_IF2_ADC_SEL_SFT, rt5682_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682_if1_01_adc_enum, + RT5682_TDM_ADDA_CTRL_1, RT5682_IF1_ADC1_SEL_SFT, rt5682_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682_if1_23_adc_enum, + RT5682_TDM_ADDA_CTRL_1, RT5682_IF1_ADC2_SEL_SFT, rt5682_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682_if1_45_adc_enum, + RT5682_TDM_ADDA_CTRL_1, RT5682_IF1_ADC3_SEL_SFT, rt5682_data_select); + +static SOC_ENUM_SINGLE_DECL(rt5682_if1_67_adc_enum, + RT5682_TDM_ADDA_CTRL_1, RT5682_IF1_ADC4_SEL_SFT, rt5682_data_select); + +static const struct snd_kcontrol_new rt5682_if2_adc_swap_mux = + SOC_DAPM_ENUM("IF2 ADC Swap Mux", rt5682_if2_adc_enum); + +static const struct snd_kcontrol_new rt5682_if1_01_adc_swap_mux = + SOC_DAPM_ENUM("IF1 01 ADC Swap Mux", rt5682_if1_01_adc_enum); + +static const struct snd_kcontrol_new rt5682_if1_23_adc_swap_mux = + SOC_DAPM_ENUM("IF1 23 ADC Swap Mux", rt5682_if1_23_adc_enum); + +static const struct snd_kcontrol_new rt5682_if1_45_adc_swap_mux = + SOC_DAPM_ENUM("IF1 45 ADC Swap Mux", rt5682_if1_45_adc_enum); + +static const struct snd_kcontrol_new rt5682_if1_67_adc_swap_mux = + SOC_DAPM_ENUM("IF1 67 ADC Swap Mux", rt5682_if1_67_adc_enum); + +static void rt5682_reset(struct regmap *regmap) +{ + regmap_write(regmap, RT5682_RESET, 0); + regmap_write(regmap, RT5682_I2C_MODE, 1); +} +/** + * rt5682_sel_asrc_clk_src - select ASRC clock source for a set of filters + * @component: SoC audio component device. + * @filter_mask: mask of filters. + * @clk_src: clock source + * + * The ASRC function is for asynchronous MCLK and LRCK. Also, since RT5682 can + * only support standard 32fs or 64fs i2s format, ASRC should be enabled to + * support special i2s clock format such as Intel's 100fs(100 * sampling rate). + * ASRC function will track i2s clock and generate a corresponding system clock + * for codec. This function provides an API to select the clock source for a + * set of filters specified by the mask. And the component driver will turn on + * ASRC for these filters if ASRC is selected as their clock source. + */ +int rt5682_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src) +{ + + switch (clk_src) { + case RT5682_CLK_SEL_SYS: + case RT5682_CLK_SEL_I2S1_ASRC: + case RT5682_CLK_SEL_I2S2_ASRC: + break; + + default: + return -EINVAL; + } + + if (filter_mask & RT5682_DA_STEREO1_FILTER) { + snd_soc_component_update_bits(component, RT5682_PLL_TRACK_2, + RT5682_FILTER_CLK_SEL_MASK, + clk_src << RT5682_FILTER_CLK_SEL_SFT); + } + + if (filter_mask & RT5682_AD_STEREO1_FILTER) { + snd_soc_component_update_bits(component, RT5682_PLL_TRACK_3, + RT5682_FILTER_CLK_SEL_MASK, + clk_src << RT5682_FILTER_CLK_SEL_SFT); + } + + return 0; +} +EXPORT_SYMBOL_GPL(rt5682_sel_asrc_clk_src); + +static int rt5682_button_detect(struct snd_soc_component *component) +{ + int btn_type, val; + + val = snd_soc_component_read32(component, RT5682_4BTN_IL_CMD_1); + btn_type = val & 0xfff0; + snd_soc_component_write(component, RT5682_4BTN_IL_CMD_1, val); + pr_debug("%s btn_type=%x\n", __func__, btn_type); + snd_soc_component_update_bits(component, + RT5682_SAR_IL_CMD_2, 0x10, 0x10); + + return btn_type; +} + +static void rt5682_enable_push_button_irq(struct snd_soc_component *component, + bool enable) +{ + if (enable) { + snd_soc_component_update_bits(component, RT5682_SAR_IL_CMD_1, + RT5682_SAR_BUTT_DET_MASK, RT5682_SAR_BUTT_DET_EN); + snd_soc_component_update_bits(component, RT5682_SAR_IL_CMD_13, + RT5682_SAR_SOUR_MASK, RT5682_SAR_SOUR_BTN); + snd_soc_component_write(component, RT5682_IL_CMD_1, 0x0040); + snd_soc_component_update_bits(component, RT5682_4BTN_IL_CMD_2, + RT5682_4BTN_IL_MASK | RT5682_4BTN_IL_RST_MASK, + RT5682_4BTN_IL_EN | RT5682_4BTN_IL_NOR); + snd_soc_component_update_bits(component, RT5682_IRQ_CTRL_3, + RT5682_IL_IRQ_MASK, RT5682_IL_IRQ_EN); + } else { + snd_soc_component_update_bits(component, RT5682_IRQ_CTRL_3, + RT5682_IL_IRQ_MASK, RT5682_IL_IRQ_DIS); + snd_soc_component_update_bits(component, RT5682_SAR_IL_CMD_1, + RT5682_SAR_BUTT_DET_MASK, RT5682_SAR_BUTT_DET_DIS); + snd_soc_component_update_bits(component, RT5682_4BTN_IL_CMD_2, + RT5682_4BTN_IL_MASK, RT5682_4BTN_IL_DIS); + snd_soc_component_update_bits(component, RT5682_4BTN_IL_CMD_2, + RT5682_4BTN_IL_RST_MASK, RT5682_4BTN_IL_RST); + snd_soc_component_update_bits(component, RT5682_SAR_IL_CMD_13, + RT5682_SAR_SOUR_MASK, RT5682_SAR_SOUR_TYPE); + } +} + +/** + * rt5682_headset_detect - Detect headset. + * @component: SoC audio component device. + * @jack_insert: Jack insert or not. + * + * Detect whether is headset or not when jack inserted. + * + * Returns detect status. + */ +static int rt5682_headset_detect(struct snd_soc_component *component, + int jack_insert) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + unsigned int val, count; + + if (jack_insert) { + snd_soc_dapm_force_enable_pin(dapm, "CBJ Power"); + snd_soc_dapm_sync(dapm); + snd_soc_component_update_bits(component, RT5682_CBJ_CTRL_1, + RT5682_TRIG_JD_MASK, RT5682_TRIG_JD_HIGH); + + count = 0; + val = snd_soc_component_read32(component, RT5682_CBJ_CTRL_2) + & RT5682_JACK_TYPE_MASK; + while (val == 0 && count < 50) { + usleep_range(10000, 15000); + val = snd_soc_component_read32(component, + RT5682_CBJ_CTRL_2) & RT5682_JACK_TYPE_MASK; + count++; + } + + switch (val) { + case 0x1: + case 0x2: + rt5682->jack_type = SND_JACK_HEADSET; + rt5682_enable_push_button_irq(component, true); + break; + default: + rt5682->jack_type = SND_JACK_HEADPHONE; + } + + } else { + rt5682_enable_push_button_irq(component, false); + snd_soc_component_update_bits(component, RT5682_CBJ_CTRL_1, + RT5682_TRIG_JD_MASK, RT5682_TRIG_JD_LOW); + snd_soc_dapm_disable_pin(dapm, "CBJ Power"); + snd_soc_dapm_sync(dapm); + + rt5682->jack_type = 0; + } + + dev_dbg(component->dev, "jack_type = %d\n", rt5682->jack_type); + return rt5682->jack_type; +} + +static irqreturn_t rt5682_irq(int irq, void *data) +{ + struct rt5682_priv *rt5682 = data; + + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(250)); + + return IRQ_HANDLED; +} + +static void rt5682_jd_check_handler(struct work_struct *work) +{ + struct rt5682_priv *rt5682 = container_of(work, struct rt5682_priv, + jd_check_work.work); + + if (snd_soc_component_read32(rt5682->component, RT5682_AJD1_CTRL) + & RT5682_JDH_RS_MASK) { + /* jack out */ + rt5682->jack_type = rt5682_headset_detect(rt5682->component, 0); + + snd_soc_jack_report(rt5682->hs_jack, rt5682->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + } else { + schedule_delayed_work(&rt5682->jd_check_work, 500); + } +} + +static int rt5682_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + + rt5682->hs_jack = hs_jack; + + if (!hs_jack) { + regmap_update_bits(rt5682->regmap, RT5682_IRQ_CTRL_2, + RT5682_JD1_EN_MASK, RT5682_JD1_DIS); + regmap_update_bits(rt5682->regmap, RT5682_RC_CLK_CTRL, + RT5682_POW_JDH | RT5682_POW_JDL, 0); + return 0; + } + + switch (rt5682->pdata.jd_src) { + case RT5682_JD1: + snd_soc_component_update_bits(component, RT5682_CBJ_CTRL_2, + RT5682_EXT_JD_SRC, RT5682_EXT_JD_SRC_MANUAL); + snd_soc_component_write(component, RT5682_CBJ_CTRL_1, 0xd042); + snd_soc_component_update_bits(component, RT5682_CBJ_CTRL_3, + RT5682_CBJ_IN_BUF_EN, RT5682_CBJ_IN_BUF_EN); + snd_soc_component_update_bits(component, RT5682_SAR_IL_CMD_1, + RT5682_SAR_POW_MASK, RT5682_SAR_POW_EN); + regmap_update_bits(rt5682->regmap, RT5682_GPIO_CTRL_1, + RT5682_GP1_PIN_MASK, RT5682_GP1_PIN_IRQ); + regmap_update_bits(rt5682->regmap, RT5682_RC_CLK_CTRL, + RT5682_POW_IRQ | RT5682_POW_JDH | + RT5682_POW_ANA, RT5682_POW_IRQ | + RT5682_POW_JDH | RT5682_POW_ANA); + regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_2, + RT5682_PWR_JDH | RT5682_PWR_JDL, + RT5682_PWR_JDH | RT5682_PWR_JDL); + regmap_update_bits(rt5682->regmap, RT5682_IRQ_CTRL_2, + RT5682_JD1_EN_MASK | RT5682_JD1_POL_MASK, + RT5682_JD1_EN | RT5682_JD1_POL_NOR); + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(250)); + break; + + case RT5682_JD_NULL: + regmap_update_bits(rt5682->regmap, RT5682_IRQ_CTRL_2, + RT5682_JD1_EN_MASK, RT5682_JD1_DIS); + regmap_update_bits(rt5682->regmap, RT5682_RC_CLK_CTRL, + RT5682_POW_JDH | RT5682_POW_JDL, 0); + break; + + default: + dev_warn(component->dev, "Wrong JD source\n"); + break; + } + + return 0; +} + +static void rt5682_jack_detect_handler(struct work_struct *work) +{ + struct rt5682_priv *rt5682 = + container_of(work, struct rt5682_priv, jack_detect_work.work); + int val, btn_type; + + if (!rt5682->component || !rt5682->component->card || + !rt5682->component->card->instantiated) { + /* card not yet ready, try later */ + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(15)); + return; + } + + mutex_lock(&rt5682->calibrate_mutex); + + val = snd_soc_component_read32(rt5682->component, RT5682_AJD1_CTRL) + & RT5682_JDH_RS_MASK; + if (!val) { + /* jack in */ + if (rt5682->jack_type == 0) { + /* jack was out, report jack type */ + rt5682->jack_type = + rt5682_headset_detect(rt5682->component, 1); + } else { + /* jack is already in, report button event */ + rt5682->jack_type = SND_JACK_HEADSET; + btn_type = rt5682_button_detect(rt5682->component); + /** + * rt5682 can report three kinds of button behavior, + * one click, double click and hold. However, + * currently we will report button pressed/released + * event. So all the three button behaviors are + * treated as button pressed. + */ + switch (btn_type) { + case 0x8000: + case 0x4000: + case 0x2000: + rt5682->jack_type |= SND_JACK_BTN_0; + break; + case 0x1000: + case 0x0800: + case 0x0400: + rt5682->jack_type |= SND_JACK_BTN_1; + break; + case 0x0200: + case 0x0100: + case 0x0080: + rt5682->jack_type |= SND_JACK_BTN_2; + break; + case 0x0040: + case 0x0020: + case 0x0010: + rt5682->jack_type |= SND_JACK_BTN_3; + break; + case 0x0000: /* unpressed */ + break; + default: + btn_type = 0; + dev_err(rt5682->component->dev, + "Unexpected button code 0x%04x\n", + btn_type); + break; + } + } + } else { + /* jack out */ + rt5682->jack_type = rt5682_headset_detect(rt5682->component, 0); + } + + snd_soc_jack_report(rt5682->hs_jack, rt5682->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (rt5682->jack_type & (SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3)) + schedule_delayed_work(&rt5682->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5682->jd_check_work); + + mutex_unlock(&rt5682->calibrate_mutex); +} + +static const struct snd_kcontrol_new rt5682_snd_controls[] = { + /* Headphone Output Volume */ + SOC_DOUBLE_R_TLV("Headphone Playback Volume", RT5682_HPL_GAIN, + RT5682_HPR_GAIN, RT5682_G_HP_SFT, 15, 1, hp_vol_tlv), + + /* DAC Digital Volume */ + SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5682_DAC1_DIG_VOL, + RT5682_L_VOL_SFT + 1, RT5682_R_VOL_SFT + 1, 86, 0, dac_vol_tlv), + + /* IN Boost Volume */ + SOC_SINGLE_TLV("CBJ Boost Volume", RT5682_CBJ_BST_CTRL, + RT5682_BST_CBJ_SFT, 8, 0, bst_tlv), + + /* ADC Digital Volume Control */ + SOC_DOUBLE("STO1 ADC Capture Switch", RT5682_STO1_ADC_DIG_VOL, + RT5682_L_MUTE_SFT, RT5682_R_MUTE_SFT, 1, 1), + SOC_DOUBLE_TLV("STO1 ADC Capture Volume", RT5682_STO1_ADC_DIG_VOL, + RT5682_L_VOL_SFT + 1, RT5682_R_VOL_SFT + 1, 63, 0, adc_vol_tlv), + + /* ADC Boost Volume Control */ + SOC_DOUBLE_TLV("STO1 ADC Boost Gain Volume", RT5682_STO1_ADC_BOOST, + RT5682_STO1_ADC_L_BST_SFT, RT5682_STO1_ADC_R_BST_SFT, + 3, 0, adc_bst_tlv), +}; + + +static int rt5682_div_sel(struct rt5682_priv *rt5682, + int target, const int div[], int size) +{ + int i; + + if (rt5682->sysclk < target) { + pr_err("sysclk rate %d is too low\n", + rt5682->sysclk); + return 0; + } + + for (i = 0; i < size - 1; i++) { + pr_info("div[%d]=%d\n", i, div[i]); + if (target * div[i] == rt5682->sysclk) + return i; + if (target * div[i + 1] > rt5682->sysclk) { + pr_err("can't find div for sysclk %d\n", + rt5682->sysclk); + return i; + } + } + + if (target * div[i] < rt5682->sysclk) + pr_err("sysclk rate %d is too high\n", + rt5682->sysclk); + + return size - 1; + +} + +/** + * set_dmic_clk - Set parameter of dmic. + * + * @w: DAPM widget. + * @kcontrol: The kcontrol of this widget. + * @event: Event id. + * + * Choose dmic clock between 1MHz and 3MHz. + * It is better for clock to approximate 3MHz. + */ +static int set_dmic_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + int idx = -EINVAL; + static const int div[] = {2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128}; + + idx = rt5682_div_sel(rt5682, 1500000, div, ARRAY_SIZE(div)); + + snd_soc_component_update_bits(component, RT5682_DMIC_CTRL_1, + RT5682_DMIC_CLK_MASK, idx << RT5682_DMIC_CLK_SFT); + + return 0; +} + +static int set_filter_clk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + int ref, val, reg, idx = -EINVAL; + static const int div_f[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48}; + static const int div_o[] = {1, 2, 4, 6, 8, 12, 16, 24, 32, 48}; + + val = snd_soc_component_read32(component, RT5682_GPIO_CTRL_1) & + RT5682_GP4_PIN_MASK; + if (w->shift == RT5682_PWR_ADC_S1F_BIT && + val == RT5682_GP4_PIN_ADCDAT2) + ref = 256 * rt5682->lrck[RT5682_AIF2]; + else + ref = 256 * rt5682->lrck[RT5682_AIF1]; + + idx = rt5682_div_sel(rt5682, ref, div_f, ARRAY_SIZE(div_f)); + + if (w->shift == RT5682_PWR_ADC_S1F_BIT) + reg = RT5682_PLL_TRACK_3; + else + reg = RT5682_PLL_TRACK_2; + + snd_soc_component_update_bits(component, reg, + RT5682_FILTER_CLK_DIV_MASK, idx << RT5682_FILTER_CLK_DIV_SFT); + + /* select over sample rate */ + for (idx = 0; idx < ARRAY_SIZE(div_o); idx++) { + if (rt5682->sysclk <= 12288000 * div_o[idx]) + break; + } + + snd_soc_component_update_bits(component, RT5682_ADDA_CLK_1, + RT5682_ADC_OSR_MASK | RT5682_DAC_OSR_MASK, + (idx << RT5682_ADC_OSR_SFT) | (idx << RT5682_DAC_OSR_SFT)); + + return 0; +} + +static int is_sys_clk_from_pll1(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int val; + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + val = snd_soc_component_read32(component, RT5682_GLB_CLK); + val &= RT5682_SCLK_SRC_MASK; + if (val == RT5682_SCLK_SRC_PLL1) + return 1; + else + return 0; +} + +static int is_using_asrc(struct snd_soc_dapm_widget *w, + struct snd_soc_dapm_widget *sink) +{ + unsigned int reg, shift, val; + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (w->shift) { + case RT5682_ADC_STO1_ASRC_SFT: + reg = RT5682_PLL_TRACK_3; + shift = RT5682_FILTER_CLK_SEL_SFT; + break; + case RT5682_DAC_STO1_ASRC_SFT: + reg = RT5682_PLL_TRACK_2; + shift = RT5682_FILTER_CLK_SEL_SFT; + break; + default: + return 0; + } + + val = (snd_soc_component_read32(component, reg) >> shift) & 0xf; + switch (val) { + case RT5682_CLK_SEL_I2S1_ASRC: + case RT5682_CLK_SEL_I2S2_ASRC: + return 1; + default: + return 0; + } + +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt5682_sto1_adc_l_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5682_STO1_ADC_MIXER, + RT5682_M_STO1_ADC_L1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5682_STO1_ADC_MIXER, + RT5682_M_STO1_ADC_L2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682_sto1_adc_r_mix[] = { + SOC_DAPM_SINGLE("ADC1 Switch", RT5682_STO1_ADC_MIXER, + RT5682_M_STO1_ADC_R1_SFT, 1, 1), + SOC_DAPM_SINGLE("ADC2 Switch", RT5682_STO1_ADC_MIXER, + RT5682_M_STO1_ADC_R2_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682_dac_l_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5682_AD_DA_MIXER, + RT5682_M_ADCMIX_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5682_AD_DA_MIXER, + RT5682_M_DAC1_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682_dac_r_mix[] = { + SOC_DAPM_SINGLE("Stereo ADC Switch", RT5682_AD_DA_MIXER, + RT5682_M_ADCMIX_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC1 Switch", RT5682_AD_DA_MIXER, + RT5682_M_DAC1_R_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682_sto1_dac_l_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5682_STO1_DAC_MIXER, + RT5682_M_DAC_L1_STO_L_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5682_STO1_DAC_MIXER, + RT5682_M_DAC_R1_STO_L_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new rt5682_sto1_dac_r_mix[] = { + SOC_DAPM_SINGLE("DAC L1 Switch", RT5682_STO1_DAC_MIXER, + RT5682_M_DAC_L1_STO_R_SFT, 1, 1), + SOC_DAPM_SINGLE("DAC R1 Switch", RT5682_STO1_DAC_MIXER, + RT5682_M_DAC_R1_STO_R_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt5682_rec1_l_mix[] = { + SOC_DAPM_SINGLE("CBJ Switch", RT5682_REC_MIXER, + RT5682_M_CBJ_RM1_L_SFT, 1, 1), +}; + +/* STO1 ADC1 Source */ +/* MX-26 [13] [5] */ +static const char * const rt5682_sto1_adc1_src[] = { + "DAC MIX", "ADC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5682_sto1_adc1l_enum, RT5682_STO1_ADC_MIXER, + RT5682_STO1_ADC1L_SRC_SFT, rt5682_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5682_sto1_adc1l_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5682_sto1_adc1l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5682_sto1_adc1r_enum, RT5682_STO1_ADC_MIXER, + RT5682_STO1_ADC1R_SRC_SFT, rt5682_sto1_adc1_src); + +static const struct snd_kcontrol_new rt5682_sto1_adc1r_mux = + SOC_DAPM_ENUM("Stereo1 ADC1L Source", rt5682_sto1_adc1r_enum); + +/* STO1 ADC Source */ +/* MX-26 [11:10] [3:2] */ +static const char * const rt5682_sto1_adc_src[] = { + "ADC1 L", "ADC1 R" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5682_sto1_adcl_enum, RT5682_STO1_ADC_MIXER, + RT5682_STO1_ADCL_SRC_SFT, rt5682_sto1_adc_src); + +static const struct snd_kcontrol_new rt5682_sto1_adcl_mux = + SOC_DAPM_ENUM("Stereo1 ADCL Source", rt5682_sto1_adcl_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5682_sto1_adcr_enum, RT5682_STO1_ADC_MIXER, + RT5682_STO1_ADCR_SRC_SFT, rt5682_sto1_adc_src); + +static const struct snd_kcontrol_new rt5682_sto1_adcr_mux = + SOC_DAPM_ENUM("Stereo1 ADCR Source", rt5682_sto1_adcr_enum); + +/* STO1 ADC2 Source */ +/* MX-26 [12] [4] */ +static const char * const rt5682_sto1_adc2_src[] = { + "DAC MIX", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5682_sto1_adc2l_enum, RT5682_STO1_ADC_MIXER, + RT5682_STO1_ADC2L_SRC_SFT, rt5682_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5682_sto1_adc2l_mux = + SOC_DAPM_ENUM("Stereo1 ADC2L Source", rt5682_sto1_adc2l_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5682_sto1_adc2r_enum, RT5682_STO1_ADC_MIXER, + RT5682_STO1_ADC2R_SRC_SFT, rt5682_sto1_adc2_src); + +static const struct snd_kcontrol_new rt5682_sto1_adc2r_mux = + SOC_DAPM_ENUM("Stereo1 ADC2R Source", rt5682_sto1_adc2r_enum); + +/* MX-79 [6:4] I2S1 ADC data location */ +static const unsigned int rt5682_if1_adc_slot_values[] = { + 0, + 2, + 4, + 6, +}; + +static const char * const rt5682_if1_adc_slot_src[] = { + "Slot 0", "Slot 2", "Slot 4", "Slot 6" +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5682_if1_adc_slot_enum, + RT5682_TDM_CTRL, RT5682_TDM_ADC_LCA_SFT, RT5682_TDM_ADC_LCA_MASK, + rt5682_if1_adc_slot_src, rt5682_if1_adc_slot_values); + +static const struct snd_kcontrol_new rt5682_if1_adc_slot_mux = + SOC_DAPM_ENUM("IF1 ADC Slot location", rt5682_if1_adc_slot_enum); + +/* Analog DAC L1 Source, Analog DAC R1 Source*/ +/* MX-2B [4], MX-2B [0]*/ +static const char * const rt5682_alg_dac1_src[] = { + "Stereo1 DAC Mixer", "DAC1" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5682_alg_dac_l1_enum, RT5682_A_DAC1_MUX, + RT5682_A_DACL1_SFT, rt5682_alg_dac1_src); + +static const struct snd_kcontrol_new rt5682_alg_dac_l1_mux = + SOC_DAPM_ENUM("Analog DAC L1 Source", rt5682_alg_dac_l1_enum); + +static SOC_ENUM_SINGLE_DECL( + rt5682_alg_dac_r1_enum, RT5682_A_DAC1_MUX, + RT5682_A_DACR1_SFT, rt5682_alg_dac1_src); + +static const struct snd_kcontrol_new rt5682_alg_dac_r1_mux = + SOC_DAPM_ENUM("Analog DAC R1 Source", rt5682_alg_dac_r1_enum); + +/* Out Switch */ +static const struct snd_kcontrol_new hpol_switch = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5682_HP_CTRL_1, + RT5682_L_MUTE_SFT, 1, 1); +static const struct snd_kcontrol_new hpor_switch = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT5682_HP_CTRL_1, + RT5682_R_MUTE_SFT, 1, 1); + +static int rt5682_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_write(component, + RT5682_HP_LOGIC_CTRL_2, 0x0012); + snd_soc_component_write(component, + RT5682_HP_CTRL_2, 0x6000); + snd_soc_component_update_bits(component, RT5682_STO_NG2_CTRL_1, + RT5682_NG2_EN_MASK, RT5682_NG2_EN); + snd_soc_component_update_bits(component, + RT5682_DEPOP_1, 0x60, 0x60); + snd_soc_component_update_bits(component, + RT5682_DAC_ADC_DIG_VOL1, 0x00c0, 0x0080); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, + RT5682_DEPOP_1, 0x60, 0x0); + snd_soc_component_write(component, + RT5682_HP_CTRL_2, 0x0000); + snd_soc_component_update_bits(component, + RT5682_DAC_ADC_DIG_VOL1, 0x00c0, 0x0000); + break; + + default: + return 0; + } + + return 0; + +} + +static int set_dmic_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /*Add delay to avoid pop noise*/ + msleep(150); + break; + + default: + return 0; + } + + return 0; +} + +static int rt5655_set_verf(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (w->shift) { + case RT5682_PWR_VREF1_BIT: + snd_soc_component_update_bits(component, + RT5682_PWR_ANLG_1, RT5682_PWR_FV1, 0); + break; + + case RT5682_PWR_VREF2_BIT: + snd_soc_component_update_bits(component, + RT5682_PWR_ANLG_1, RT5682_PWR_FV2, 0); + break; + + default: + break; + } + break; + + case SND_SOC_DAPM_POST_PMU: + usleep_range(15000, 20000); + switch (w->shift) { + case RT5682_PWR_VREF1_BIT: + snd_soc_component_update_bits(component, + RT5682_PWR_ANLG_1, RT5682_PWR_FV1, + RT5682_PWR_FV1); + break; + + case RT5682_PWR_VREF2_BIT: + snd_soc_component_update_bits(component, + RT5682_PWR_ANLG_1, RT5682_PWR_FV2, + RT5682_PWR_FV2); + break; + + default: + break; + } + break; + + default: + return 0; + } + + return 0; +} + +static const unsigned int rt5682_adcdat_pin_values[] = { + 1, + 3, +}; + +static const char * const rt5682_adcdat_pin_select[] = { + "ADCDAT1", + "ADCDAT2", +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(rt5682_adcdat_pin_enum, + RT5682_GPIO_CTRL_1, RT5682_GP4_PIN_SFT, RT5682_GP4_PIN_MASK, + rt5682_adcdat_pin_select, rt5682_adcdat_pin_values); + +static const struct snd_kcontrol_new rt5682_adcdat_pin_ctrl = + SOC_DAPM_ENUM("ADCDAT", rt5682_adcdat_pin_enum); + +static const struct snd_soc_dapm_widget rt5682_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("LDO2", RT5682_PWR_ANLG_3, RT5682_PWR_LDO2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", RT5682_PWR_ANLG_3, RT5682_PWR_PLL_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2B", RT5682_PWR_ANLG_3, RT5682_PWR_PLL2B_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2F", RT5682_PWR_ANLG_3, RT5682_PWR_PLL2F_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Vref1", RT5682_PWR_ANLG_1, RT5682_PWR_VREF1_BIT, 0, + rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("Vref2", RT5682_PWR_ANLG_1, RT5682_PWR_VREF2_BIT, 0, + rt5655_set_verf, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("DAC STO1 ASRC", 1, RT5682_PLL_TRACK_1, + RT5682_DAC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ADC STO1 ASRC", 1, RT5682_PLL_TRACK_1, + RT5682_ADC_STO1_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("AD ASRC", 1, RT5682_PLL_TRACK_1, + RT5682_AD_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DA ASRC", 1, RT5682_PLL_TRACK_1, + RT5682_DA_ASRC_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC ASRC", 1, RT5682_PLL_TRACK_1, + RT5682_DMIC_ASRC_SFT, 0, NULL, 0), + + /* Input Side */ + SND_SOC_DAPM_SUPPLY("MICBIAS1", RT5682_PWR_ANLG_2, RT5682_PWR_MB1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", RT5682_PWR_ANLG_2, RT5682_PWR_MB2_BIT, + 0, NULL, 0), + + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC L1"), + SND_SOC_DAPM_INPUT("DMIC R1"), + + SND_SOC_DAPM_INPUT("IN1P"), + + SND_SOC_DAPM_SUPPLY("DMIC CLK", SND_SOC_NOPM, 0, 0, + set_dmic_clk, SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_SUPPLY("DMIC1 Power", RT5682_DMIC_CTRL_1, + RT5682_DMIC_1_EN_SFT, 0, set_dmic_power, SND_SOC_DAPM_POST_PMU), + + /* Boost */ + SND_SOC_DAPM_PGA("BST1 CBJ", SND_SOC_NOPM, + 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("CBJ Power", RT5682_PWR_ANLG_3, + RT5682_PWR_CBJ_BIT, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX1L", SND_SOC_NOPM, 0, 0, rt5682_rec1_l_mix, + ARRAY_SIZE(rt5682_rec1_l_mix)), + SND_SOC_DAPM_SUPPLY("RECMIX1L Power", RT5682_PWR_ANLG_2, + RT5682_PWR_RM1_L_BIT, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC1 L", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC1 R", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("ADC1 L Power", RT5682_PWR_DIG_1, + RT5682_PWR_ADC_L1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 R Power", RT5682_PWR_DIG_1, + RT5682_PWR_ADC_R1_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC1 clock", RT5682_CHOP_ADC, + RT5682_CKGEN_ADC1_SFT, 0, NULL, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX("Stereo1 ADC L1 Mux", SND_SOC_NOPM, 0, 0, + &rt5682_sto1_adc1l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R1 Mux", SND_SOC_NOPM, 0, 0, + &rt5682_sto1_adc1r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L2 Mux", SND_SOC_NOPM, 0, 0, + &rt5682_sto1_adc2l_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R2 Mux", SND_SOC_NOPM, 0, 0, + &rt5682_sto1_adc2r_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC L Mux", SND_SOC_NOPM, 0, 0, + &rt5682_sto1_adcl_mux), + SND_SOC_DAPM_MUX("Stereo1 ADC R Mux", SND_SOC_NOPM, 0, 0, + &rt5682_sto1_adcr_mux), + SND_SOC_DAPM_MUX("IF1_ADC Mux", SND_SOC_NOPM, 0, 0, + &rt5682_if1_adc_slot_mux), + + /* ADC Mixer */ + SND_SOC_DAPM_SUPPLY("ADC Stereo1 Filter", RT5682_PWR_DIG_2, + RT5682_PWR_ADC_S1F_BIT, 0, set_filter_clk, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", RT5682_STO1_ADC_DIG_VOL, + RT5682_L_MUTE_SFT, 1, rt5682_sto1_adc_l_mix, + ARRAY_SIZE(rt5682_sto1_adc_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 ADC MIXR", RT5682_STO1_ADC_DIG_VOL, + RT5682_R_MUTE_SFT, 1, rt5682_sto1_adc_r_mix, + ARRAY_SIZE(rt5682_sto1_adc_r_mix)), + SND_SOC_DAPM_SUPPLY("BTN Detection Mode", RT5682_SAR_IL_CMD_1, + 14, 1, NULL, 0), + + /* ADC PGA */ + SND_SOC_DAPM_PGA("Stereo1 ADC MIX", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface */ + SND_SOC_DAPM_SUPPLY("I2S1", RT5682_PWR_DIG_1, RT5682_PWR_I2S1_BIT, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("I2S2", RT5682_PWR_DIG_1, RT5682_PWR_I2S2_BIT, + 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 L", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("IF1 DAC1 R", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Digital Interface Select */ + SND_SOC_DAPM_MUX("IF1 01 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682_if1_01_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 23 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682_if1_23_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 45 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682_if1_45_adc_swap_mux), + SND_SOC_DAPM_MUX("IF1 67 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682_if1_67_adc_swap_mux), + SND_SOC_DAPM_MUX("IF2 ADC Swap Mux", SND_SOC_NOPM, 0, 0, + &rt5682_if2_adc_swap_mux), + + SND_SOC_DAPM_MUX("ADCDAT Mux", SND_SOC_NOPM, 0, 0, + &rt5682_adcdat_pin_ctrl), + + /* Audio Interface */ + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, + RT5682_I2S1_SDP, RT5682_SEL_ADCDAT_SFT, 1), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, + RT5682_I2S2_SDP, RT5682_I2S2_PIN_CFG_SFT, 1), + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + + /* Output Side */ + /* DAC mixer before sound effect */ + SND_SOC_DAPM_MIXER("DAC1 MIXL", SND_SOC_NOPM, 0, 0, + rt5682_dac_l_mix, ARRAY_SIZE(rt5682_dac_l_mix)), + SND_SOC_DAPM_MIXER("DAC1 MIXR", SND_SOC_NOPM, 0, 0, + rt5682_dac_r_mix, ARRAY_SIZE(rt5682_dac_r_mix)), + + /* DAC channel Mux */ + SND_SOC_DAPM_MUX("DAC L1 Source", SND_SOC_NOPM, 0, 0, + &rt5682_alg_dac_l1_mux), + SND_SOC_DAPM_MUX("DAC R1 Source", SND_SOC_NOPM, 0, 0, + &rt5682_alg_dac_r1_mux), + + /* DAC Mixer */ + SND_SOC_DAPM_SUPPLY("DAC Stereo1 Filter", RT5682_PWR_DIG_2, + RT5682_PWR_DAC_S1F_BIT, 0, set_filter_clk, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXL", SND_SOC_NOPM, 0, 0, + rt5682_sto1_dac_l_mix, ARRAY_SIZE(rt5682_sto1_dac_l_mix)), + SND_SOC_DAPM_MIXER("Stereo1 DAC MIXR", SND_SOC_NOPM, 0, 0, + rt5682_sto1_dac_r_mix, ARRAY_SIZE(rt5682_sto1_dac_r_mix)), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC L1", NULL, RT5682_PWR_DIG_1, + RT5682_PWR_DAC_L1_BIT, 0), + SND_SOC_DAPM_DAC("DAC R1", NULL, RT5682_PWR_DIG_1, + RT5682_PWR_DAC_R1_BIT, 0), + SND_SOC_DAPM_SUPPLY_S("DAC 1 Clock", 3, RT5682_CHOP_DAC, + RT5682_CKGEN_DAC1_SFT, 0, NULL, 0), + + /* HPO */ + SND_SOC_DAPM_PGA_S("HP Amp", 1, SND_SOC_NOPM, 0, 0, rt5682_hp_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_SUPPLY("HP Amp L", RT5682_PWR_ANLG_1, + RT5682_PWR_HA_L_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HP Amp R", RT5682_PWR_ANLG_1, + RT5682_PWR_HA_R_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("Charge Pump", 1, RT5682_DEPOP_1, + RT5682_PUMP_EN_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("Capless", 2, RT5682_DEPOP_1, + RT5682_CAPLESS_EN_SFT, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("HPOL Playback", SND_SOC_NOPM, 0, 0, + &hpol_switch), + SND_SOC_DAPM_SWITCH("HPOR Playback", SND_SOC_NOPM, 0, 0, + &hpor_switch), + + /* CLK DET */ + SND_SOC_DAPM_SUPPLY("CLKDET SYS", RT5682_CLK_DET, + RT5682_SYS_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET PLL1", RT5682_CLK_DET, + RT5682_PLL1_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET PLL2", RT5682_CLK_DET, + RT5682_PLL2_CLK_DET_SFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLKDET", RT5682_CLK_DET, + RT5682_POW_CLK_DET_SFT, 0, NULL, 0), + + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("HPOL"), + SND_SOC_DAPM_OUTPUT("HPOR"), + +}; + +static const struct snd_soc_dapm_route rt5682_dapm_routes[] = { + /*PLL*/ + {"ADC Stereo1 Filter", NULL, "PLL1", is_sys_clk_from_pll1}, + {"DAC Stereo1 Filter", NULL, "PLL1", is_sys_clk_from_pll1}, + + /*ASRC*/ + {"ADC Stereo1 Filter", NULL, "ADC STO1 ASRC", is_using_asrc}, + {"DAC Stereo1 Filter", NULL, "DAC STO1 ASRC", is_using_asrc}, + {"ADC STO1 ASRC", NULL, "AD ASRC"}, + {"ADC STO1 ASRC", NULL, "DA ASRC"}, + {"ADC STO1 ASRC", NULL, "CLKDET"}, + {"DAC STO1 ASRC", NULL, "AD ASRC"}, + {"DAC STO1 ASRC", NULL, "DA ASRC"}, + {"DAC STO1 ASRC", NULL, "CLKDET"}, + + /*Vref*/ + {"MICBIAS1", NULL, "Vref1"}, + {"MICBIAS1", NULL, "Vref2"}, + {"MICBIAS2", NULL, "Vref1"}, + {"MICBIAS2", NULL, "Vref2"}, + + {"CLKDET SYS", NULL, "CLKDET"}, + + {"IN1P", NULL, "LDO2"}, + + {"BST1 CBJ", NULL, "IN1P"}, + {"BST1 CBJ", NULL, "CBJ Power"}, + {"CBJ Power", NULL, "Vref2"}, + + {"RECMIX1L", "CBJ Switch", "BST1 CBJ"}, + {"RECMIX1L", NULL, "RECMIX1L Power"}, + + {"ADC1 L", NULL, "RECMIX1L"}, + {"ADC1 L", NULL, "ADC1 L Power"}, + {"ADC1 L", NULL, "ADC1 clock"}, + + {"DMIC L1", NULL, "DMIC CLK"}, + {"DMIC L1", NULL, "DMIC1 Power"}, + {"DMIC R1", NULL, "DMIC CLK"}, + {"DMIC R1", NULL, "DMIC1 Power"}, + {"DMIC CLK", NULL, "DMIC ASRC"}, + + {"Stereo1 ADC L Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC L Mux", "ADC1 R", "ADC1 R"}, + {"Stereo1 ADC R Mux", "ADC1 L", "ADC1 L"}, + {"Stereo1 ADC R Mux", "ADC1 R", "ADC1 R"}, + + {"Stereo1 ADC L1 Mux", "ADC", "Stereo1 ADC L Mux"}, + {"Stereo1 ADC L1 Mux", "DAC MIX", "Stereo1 DAC MIXL"}, + {"Stereo1 ADC L2 Mux", "DMIC", "DMIC L1"}, + {"Stereo1 ADC L2 Mux", "DAC MIX", "Stereo1 DAC MIXL"}, + + {"Stereo1 ADC R1 Mux", "ADC", "Stereo1 ADC R Mux"}, + {"Stereo1 ADC R1 Mux", "DAC MIX", "Stereo1 DAC MIXR"}, + {"Stereo1 ADC R2 Mux", "DMIC", "DMIC R1"}, + {"Stereo1 ADC R2 Mux", "DAC MIX", "Stereo1 DAC MIXR"}, + + {"Stereo1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux"}, + {"Stereo1 ADC MIXL", "ADC2 Switch", "Stereo1 ADC L2 Mux"}, + {"Stereo1 ADC MIXL", NULL, "ADC Stereo1 Filter"}, + + {"Stereo1 ADC MIXR", "ADC1 Switch", "Stereo1 ADC R1 Mux"}, + {"Stereo1 ADC MIXR", "ADC2 Switch", "Stereo1 ADC R2 Mux"}, + {"Stereo1 ADC MIXR", NULL, "ADC Stereo1 Filter"}, + + {"ADC Stereo1 Filter", NULL, "BTN Detection Mode"}, + + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXL"}, + {"Stereo1 ADC MIX", NULL, "Stereo1 ADC MIXR"}, + + {"IF1 01 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 01 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 23 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 45 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF1 67 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + + {"IF1_ADC Mux", "Slot 0", "IF1 01 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 2", "IF1 23 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 4", "IF1 45 ADC Swap Mux"}, + {"IF1_ADC Mux", "Slot 6", "IF1 67 ADC Swap Mux"}, + {"IF1_ADC Mux", NULL, "I2S1"}, + {"ADCDAT Mux", "ADCDAT1", "IF1_ADC Mux"}, + {"AIF1TX", NULL, "ADCDAT Mux"}, + {"IF2 ADC Swap Mux", "L/R", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "R/L", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "L/L", "Stereo1 ADC MIX"}, + {"IF2 ADC Swap Mux", "R/R", "Stereo1 ADC MIX"}, + {"ADCDAT Mux", "ADCDAT2", "IF2 ADC Swap Mux"}, + {"AIF2TX", NULL, "ADCDAT Mux"}, + + {"IF1 DAC1 L", NULL, "AIF1RX"}, + {"IF1 DAC1 L", NULL, "I2S1"}, + {"IF1 DAC1 L", NULL, "DAC Stereo1 Filter"}, + {"IF1 DAC1 R", NULL, "AIF1RX"}, + {"IF1 DAC1 R", NULL, "I2S1"}, + {"IF1 DAC1 R", NULL, "DAC Stereo1 Filter"}, + + {"DAC1 MIXL", "Stereo ADC Switch", "Stereo1 ADC MIXL"}, + {"DAC1 MIXL", "DAC1 Switch", "IF1 DAC1 L"}, + {"DAC1 MIXR", "Stereo ADC Switch", "Stereo1 ADC MIXR"}, + {"DAC1 MIXR", "DAC1 Switch", "IF1 DAC1 R"}, + + {"Stereo1 DAC MIXL", "DAC L1 Switch", "DAC1 MIXL"}, + {"Stereo1 DAC MIXL", "DAC R1 Switch", "DAC1 MIXR"}, + + {"Stereo1 DAC MIXR", "DAC R1 Switch", "DAC1 MIXR"}, + {"Stereo1 DAC MIXR", "DAC L1 Switch", "DAC1 MIXL"}, + + {"DAC L1 Source", "DAC1", "DAC1 MIXL"}, + {"DAC L1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXL"}, + {"DAC R1 Source", "DAC1", "DAC1 MIXR"}, + {"DAC R1 Source", "Stereo1 DAC Mixer", "Stereo1 DAC MIXR"}, + + {"DAC L1", NULL, "DAC L1 Source"}, + {"DAC R1", NULL, "DAC R1 Source"}, + + {"DAC L1", NULL, "DAC 1 Clock"}, + {"DAC R1", NULL, "DAC 1 Clock"}, + + {"HP Amp", NULL, "DAC L1"}, + {"HP Amp", NULL, "DAC R1"}, + {"HP Amp", NULL, "HP Amp L"}, + {"HP Amp", NULL, "HP Amp R"}, + {"HP Amp", NULL, "Capless"}, + {"HP Amp", NULL, "Charge Pump"}, + {"HP Amp", NULL, "CLKDET SYS"}, + {"HP Amp", NULL, "CBJ Power"}, + {"HP Amp", NULL, "Vref2"}, + {"HPOL Playback", "Switch", "HP Amp"}, + {"HPOR Playback", "Switch", "HP Amp"}, + {"HPOL", NULL, "HPOL Playback"}, + {"HPOR", NULL, "HPOR Playback"}, +}; + +static int rt5682_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int cl, val = 0; + + if (tx_mask || rx_mask) + snd_soc_component_update_bits(component, RT5682_TDM_ADDA_CTRL_2, + RT5682_TDM_EN, RT5682_TDM_EN); + else + snd_soc_component_update_bits(component, RT5682_TDM_ADDA_CTRL_2, + RT5682_TDM_EN, 0); + + switch (slots) { + case 4: + val |= RT5682_TDM_TX_CH_4; + val |= RT5682_TDM_RX_CH_4; + break; + case 6: + val |= RT5682_TDM_TX_CH_6; + val |= RT5682_TDM_RX_CH_6; + break; + case 8: + val |= RT5682_TDM_TX_CH_8; + val |= RT5682_TDM_RX_CH_8; + break; + case 2: + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5682_TDM_CTRL, + RT5682_TDM_TX_CH_MASK | RT5682_TDM_RX_CH_MASK, val); + + switch (slot_width) { + case 8: + if (tx_mask || rx_mask) + return -EINVAL; + cl = RT5682_I2S1_TX_CHL_8 | RT5682_I2S1_RX_CHL_8; + break; + case 16: + val = RT5682_TDM_CL_16; + cl = RT5682_I2S1_TX_CHL_16 | RT5682_I2S1_RX_CHL_16; + break; + case 20: + val = RT5682_TDM_CL_20; + cl = RT5682_I2S1_TX_CHL_20 | RT5682_I2S1_RX_CHL_20; + break; + case 24: + val = RT5682_TDM_CL_24; + cl = RT5682_I2S1_TX_CHL_24 | RT5682_I2S1_RX_CHL_24; + break; + case 32: + val = RT5682_TDM_CL_32; + cl = RT5682_I2S1_TX_CHL_32 | RT5682_I2S1_RX_CHL_32; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, RT5682_TDM_TCON_CTRL, + RT5682_TDM_CL_MASK, val); + snd_soc_component_update_bits(component, RT5682_I2S1_SDP, + RT5682_I2S1_TX_CHL_MASK | RT5682_I2S1_RX_CHL_MASK, cl); + + return 0; +} + + +static int rt5682_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + unsigned int len_1 = 0, len_2 = 0; + int pre_div, frame_size; + + rt5682->lrck[dai->id] = params_rate(params); + pre_div = rl6231_get_clk_info(rt5682->sysclk, rt5682->lrck[dai->id]); + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) { + dev_err(component->dev, "Unsupported frame size: %d\n", + frame_size); + return -EINVAL; + } + + dev_dbg(dai->dev, "lrck is %dHz and pre_div is %d for iis %d\n", + rt5682->lrck[dai->id], pre_div, dai->id); + + switch (params_width(params)) { + case 16: + break; + case 20: + len_1 |= RT5682_I2S1_DL_20; + len_2 |= RT5682_I2S2_DL_20; + break; + case 24: + len_1 |= RT5682_I2S1_DL_24; + len_2 |= RT5682_I2S2_DL_24; + break; + case 32: + len_1 |= RT5682_I2S1_DL_32; + len_2 |= RT5682_I2S2_DL_24; + break; + case 8: + len_1 |= RT5682_I2S2_DL_8; + len_2 |= RT5682_I2S2_DL_8; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5682_AIF1: + snd_soc_component_update_bits(component, RT5682_I2S1_SDP, + RT5682_I2S1_DL_MASK, len_1); + if (rt5682->master[RT5682_AIF1]) { + snd_soc_component_update_bits(component, + RT5682_ADDA_CLK_1, RT5682_I2S_M_DIV_MASK, + pre_div << RT5682_I2S_M_DIV_SFT); + } + if (params_channels(params) == 1) /* mono mode */ + snd_soc_component_update_bits(component, + RT5682_I2S1_SDP, RT5682_I2S1_MONO_MASK, + RT5682_I2S1_MONO_EN); + else + snd_soc_component_update_bits(component, + RT5682_I2S1_SDP, RT5682_I2S1_MONO_MASK, + RT5682_I2S1_MONO_DIS); + break; + case RT5682_AIF2: + snd_soc_component_update_bits(component, RT5682_I2S2_SDP, + RT5682_I2S2_DL_MASK, len_2); + if (rt5682->master[RT5682_AIF2]) { + snd_soc_component_update_bits(component, + RT5682_I2S_M_CLK_CTRL_1, RT5682_I2S2_M_PD_MASK, + pre_div << RT5682_I2S2_M_PD_SFT); + } + if (params_channels(params) == 1) /* mono mode */ + snd_soc_component_update_bits(component, + RT5682_I2S2_SDP, RT5682_I2S2_MONO_MASK, + RT5682_I2S2_MONO_EN); + else + snd_soc_component_update_bits(component, + RT5682_I2S2_SDP, RT5682_I2S2_MONO_MASK, + RT5682_I2S2_MONO_DIS); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + + return 0; +} + +static int rt5682_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, tdm_ctrl = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rt5682->master[dai->id] = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + rt5682->master[dai->id] = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + reg_val |= RT5682_I2S_BP_INV; + tdm_ctrl |= RT5682_TDM_S_BP_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + if (dai->id == RT5682_AIF1) + tdm_ctrl |= RT5682_TDM_S_LP_INV | RT5682_TDM_M_BP_INV; + else + return -EINVAL; + break; + case SND_SOC_DAIFMT_IB_IF: + if (dai->id == RT5682_AIF1) + tdm_ctrl |= RT5682_TDM_S_BP_INV | RT5682_TDM_S_LP_INV | + RT5682_TDM_M_BP_INV | RT5682_TDM_M_LP_INV; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg_val |= RT5682_I2S_DF_LEFT; + tdm_ctrl |= RT5682_TDM_DF_LEFT; + break; + case SND_SOC_DAIFMT_DSP_A: + reg_val |= RT5682_I2S_DF_PCM_A; + tdm_ctrl |= RT5682_TDM_DF_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + reg_val |= RT5682_I2S_DF_PCM_B; + tdm_ctrl |= RT5682_TDM_DF_PCM_B; + break; + default: + return -EINVAL; + } + + switch (dai->id) { + case RT5682_AIF1: + snd_soc_component_update_bits(component, RT5682_I2S1_SDP, + RT5682_I2S_DF_MASK, reg_val); + snd_soc_component_update_bits(component, RT5682_TDM_TCON_CTRL, + RT5682_TDM_MS_MASK | RT5682_TDM_S_BP_MASK | + RT5682_TDM_DF_MASK | RT5682_TDM_M_BP_MASK | + RT5682_TDM_M_LP_MASK | RT5682_TDM_S_LP_MASK, + tdm_ctrl | rt5682->master[dai->id]); + break; + case RT5682_AIF2: + if (rt5682->master[dai->id] == 0) + reg_val |= RT5682_I2S2_MS_S; + snd_soc_component_update_bits(component, RT5682_I2S2_SDP, + RT5682_I2S2_MS_MASK | RT5682_I2S_BP_MASK | + RT5682_I2S_DF_MASK, reg_val); + break; + default: + dev_err(component->dev, "Invalid dai->id: %d\n", dai->id); + return -EINVAL; + } + return 0; +} + +static int rt5682_set_component_sysclk(struct snd_soc_component *component, + int clk_id, int source, unsigned int freq, int dir) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + unsigned int reg_val = 0, src = 0; + + if (freq == rt5682->sysclk && clk_id == rt5682->sysclk_src) + return 0; + + switch (clk_id) { + case RT5682_SCLK_S_MCLK: + reg_val |= RT5682_SCLK_SRC_MCLK; + src = RT5682_CLK_SRC_MCLK; + break; + case RT5682_SCLK_S_PLL1: + reg_val |= RT5682_SCLK_SRC_PLL1; + src = RT5682_CLK_SRC_PLL1; + break; + case RT5682_SCLK_S_PLL2: + reg_val |= RT5682_SCLK_SRC_PLL2; + src = RT5682_CLK_SRC_PLL2; + break; + case RT5682_SCLK_S_RCCLK: + reg_val |= RT5682_SCLK_SRC_RCCLK; + src = RT5682_CLK_SRC_RCCLK; + break; + default: + dev_err(component->dev, "Invalid clock id (%d)\n", clk_id); + return -EINVAL; + } + snd_soc_component_update_bits(component, RT5682_GLB_CLK, + RT5682_SCLK_SRC_MASK, reg_val); + + if (rt5682->master[RT5682_AIF2]) { + snd_soc_component_update_bits(component, + RT5682_I2S_M_CLK_CTRL_1, RT5682_I2S2_SRC_MASK, + src << RT5682_I2S2_SRC_SFT); + } + + rt5682->sysclk = freq; + rt5682->sysclk_src = clk_id; + + dev_dbg(component->dev, "Sysclk is %dHz and clock id is %d\n", + freq, clk_id); + + return 0; +} + +static int rt5682_set_component_pll(struct snd_soc_component *component, + int pll_id, int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + struct rl6231_pll_code pll_code; + int ret; + + if (source == rt5682->pll_src && freq_in == rt5682->pll_in && + freq_out == rt5682->pll_out) + return 0; + + if (!freq_in || !freq_out) { + dev_dbg(component->dev, "PLL disabled\n"); + + rt5682->pll_in = 0; + rt5682->pll_out = 0; + snd_soc_component_update_bits(component, RT5682_GLB_CLK, + RT5682_SCLK_SRC_MASK, RT5682_SCLK_SRC_MCLK); + return 0; + } + + switch (source) { + case RT5682_PLL1_S_MCLK: + snd_soc_component_update_bits(component, RT5682_GLB_CLK, + RT5682_PLL1_SRC_MASK, RT5682_PLL1_SRC_MCLK); + break; + case RT5682_PLL1_S_BCLK1: + snd_soc_component_update_bits(component, RT5682_GLB_CLK, + RT5682_PLL1_SRC_MASK, RT5682_PLL1_SRC_BCLK1); + break; + default: + dev_err(component->dev, "Unknown PLL Source %d\n", source); + return -EINVAL; + } + + ret = rl6231_pll_calc(freq_in, freq_out, &pll_code); + if (ret < 0) { + dev_err(component->dev, "Unsupport input clock %d\n", freq_in); + return ret; + } + + dev_dbg(component->dev, "bypass=%d m=%d n=%d k=%d\n", + pll_code.m_bp, (pll_code.m_bp ? 0 : pll_code.m_code), + pll_code.n_code, pll_code.k_code); + + snd_soc_component_write(component, RT5682_PLL_CTRL_1, + pll_code.n_code << RT5682_PLL_N_SFT | pll_code.k_code); + snd_soc_component_write(component, RT5682_PLL_CTRL_2, + (pll_code.m_bp ? 0 : pll_code.m_code) << RT5682_PLL_M_SFT | + pll_code.m_bp << RT5682_PLL_M_BP_SFT | RT5682_PLL_RST); + + rt5682->pll_in = freq_in; + rt5682->pll_out = freq_out; + rt5682->pll_src = source; + + return 0; +} + +static int rt5682_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_component *component = dai->component; + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + + rt5682->bclk[dai->id] = ratio; + + switch (ratio) { + case 64: + snd_soc_component_update_bits(component, RT5682_ADDA_CLK_2, + RT5682_I2S2_BCLK_MS2_MASK, + RT5682_I2S2_BCLK_MS2_64); + break; + case 32: + snd_soc_component_update_bits(component, RT5682_ADDA_CLK_2, + RT5682_I2S2_BCLK_MS2_MASK, + RT5682_I2S2_BCLK_MS2_32); + break; + default: + dev_err(dai->dev, "Invalid bclk ratio %d\n", ratio); + return -EINVAL; + } + + return 0; +} + +static int rt5682_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_1, + RT5682_PWR_MB | RT5682_PWR_BG, + RT5682_PWR_MB | RT5682_PWR_BG); + regmap_update_bits(rt5682->regmap, RT5682_PWR_DIG_1, + RT5682_DIG_GATE_CTRL | RT5682_PWR_LDO, + RT5682_DIG_GATE_CTRL | RT5682_PWR_LDO); + break; + + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_1, + RT5682_PWR_MB, RT5682_PWR_MB); + regmap_update_bits(rt5682->regmap, RT5682_PWR_DIG_1, + RT5682_DIG_GATE_CTRL, RT5682_DIG_GATE_CTRL); + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(rt5682->regmap, RT5682_PWR_DIG_1, + RT5682_DIG_GATE_CTRL | RT5682_PWR_LDO, 0); + regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_1, + RT5682_PWR_MB | RT5682_PWR_BG, 0); + break; + + default: + break; + } + + return 0; +} + +static int rt5682_probe(struct snd_soc_component *component) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + + rt5682->component = component; + + return 0; +} + +static void rt5682_remove(struct snd_soc_component *component) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + + rt5682_reset(rt5682->regmap); +} + +#ifdef CONFIG_PM +static int rt5682_suspend(struct snd_soc_component *component) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5682->regmap, true); + regcache_mark_dirty(rt5682->regmap); + return 0; +} + +static int rt5682_resume(struct snd_soc_component *component) +{ + struct rt5682_priv *rt5682 = snd_soc_component_get_drvdata(component); + + regcache_cache_only(rt5682->regmap, false); + regcache_sync(rt5682->regmap); + + return 0; +} +#else +#define rt5682_suspend NULL +#define rt5682_resume NULL +#endif + +#define RT5682_STEREO_RATES SNDRV_PCM_RATE_8000_192000 +#define RT5682_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static const struct snd_soc_dai_ops rt5682_aif1_dai_ops = { + .hw_params = rt5682_hw_params, + .set_fmt = rt5682_set_dai_fmt, + .set_tdm_slot = rt5682_set_tdm_slot, +}; + +static const struct snd_soc_dai_ops rt5682_aif2_dai_ops = { + .hw_params = rt5682_hw_params, + .set_fmt = rt5682_set_dai_fmt, + .set_bclk_ratio = rt5682_set_bclk_ratio, +}; + +static struct snd_soc_dai_driver rt5682_dai[] = { + { + .name = "rt5682-aif1", + .id = RT5682_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5682_STEREO_RATES, + .formats = RT5682_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5682_STEREO_RATES, + .formats = RT5682_FORMATS, + }, + .ops = &rt5682_aif1_dai_ops, + }, + { + .name = "rt5682-aif2", + .id = RT5682_AIF2, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5682_STEREO_RATES, + .formats = RT5682_FORMATS, + }, + .ops = &rt5682_aif2_dai_ops, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_rt5682 = { + .probe = rt5682_probe, + .remove = rt5682_remove, + .suspend = rt5682_suspend, + .resume = rt5682_resume, + .set_bias_level = rt5682_set_bias_level, + .controls = rt5682_snd_controls, + .num_controls = ARRAY_SIZE(rt5682_snd_controls), + .dapm_widgets = rt5682_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt5682_dapm_widgets), + .dapm_routes = rt5682_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt5682_dapm_routes), + .set_sysclk = rt5682_set_component_sysclk, + .set_pll = rt5682_set_component_pll, + .set_jack = rt5682_set_jack_detect, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config rt5682_regmap = { + .reg_bits = 16, + .val_bits = 16, + .max_register = RT5682_I2C_MODE, + .volatile_reg = rt5682_volatile_register, + .readable_reg = rt5682_readable_register, + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt5682_reg, + .num_reg_defaults = ARRAY_SIZE(rt5682_reg), + .use_single_rw = true, +}; + +static const struct i2c_device_id rt5682_i2c_id[] = { + {"rt5682", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt5682_i2c_id); + +static int rt5682_parse_dt(struct rt5682_priv *rt5682, struct device *dev) +{ + + device_property_read_u32(dev, "realtek,dmic1-data-pin", + &rt5682->pdata.dmic1_data_pin); + device_property_read_u32(dev, "realtek,dmic1-clk-pin", + &rt5682->pdata.dmic1_clk_pin); + device_property_read_u32(dev, "realtek,jd-src", + &rt5682->pdata.jd_src); + + rt5682->pdata.ldo1_en = of_get_named_gpio(dev->of_node, + "realtek,ldo1-en-gpios", 0); + + return 0; +} + +static void rt5682_calibrate(struct rt5682_priv *rt5682) +{ + int value, count; + + mutex_lock(&rt5682->calibrate_mutex); + + rt5682_reset(rt5682->regmap); + regmap_write(rt5682->regmap, RT5682_PWR_ANLG_1, 0xa2bf); + usleep_range(15000, 20000); + regmap_write(rt5682->regmap, RT5682_PWR_ANLG_1, 0xf2bf); + regmap_write(rt5682->regmap, RT5682_MICBIAS_2, 0x0380); + regmap_write(rt5682->regmap, RT5682_PWR_DIG_1, 0x8001); + regmap_write(rt5682->regmap, RT5682_TEST_MODE_CTRL_1, 0x0000); + regmap_write(rt5682->regmap, RT5682_STO1_DAC_MIXER, 0x2080); + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0x4040); + regmap_write(rt5682->regmap, RT5682_DEPOP_1, 0x0069); + regmap_write(rt5682->regmap, RT5682_CHOP_DAC, 0x3000); + regmap_write(rt5682->regmap, RT5682_HP_CTRL_2, 0x6000); + regmap_write(rt5682->regmap, RT5682_HP_CHARGE_PUMP_1, 0x0f26); + regmap_write(rt5682->regmap, RT5682_CALIB_ADC_CTRL, 0x7f05); + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0x686c); + regmap_write(rt5682->regmap, RT5682_CAL_REC, 0x0d0d); + regmap_write(rt5682->regmap, RT5682_HP_CALIB_CTRL_9, 0x000f); + regmap_write(rt5682->regmap, RT5682_PWR_DIG_1, 0x8d01); + regmap_write(rt5682->regmap, RT5682_HP_CALIB_CTRL_2, 0x0321); + regmap_write(rt5682->regmap, RT5682_HP_LOGIC_CTRL_2, 0x0004); + regmap_write(rt5682->regmap, RT5682_HP_CALIB_CTRL_1, 0x7c00); + regmap_write(rt5682->regmap, RT5682_HP_CALIB_CTRL_3, 0x06a1); + regmap_write(rt5682->regmap, RT5682_A_DAC1_MUX, 0x0311); + regmap_write(rt5682->regmap, RT5682_RESET_HPF_CTRL, 0x0000); + regmap_write(rt5682->regmap, RT5682_ADC_STO1_HP_CTRL_1, 0x3320); + + regmap_write(rt5682->regmap, RT5682_HP_CALIB_CTRL_1, 0xfc00); + + for (count = 0; count < 60; count++) { + regmap_read(rt5682->regmap, RT5682_HP_CALIB_STA_1, &value); + if (!(value & 0x8000)) + break; + + usleep_range(10000, 10005); + } + + if (count >= 60) + pr_err("HP Calibration Failure\n"); + + /* restore settings */ + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5682->regmap, RT5682_PWR_DIG_1, 0x0000); + + mutex_unlock(&rt5682->calibrate_mutex); + +} + +static int rt5682_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt5682_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt5682_priv *rt5682; + int i, ret; + unsigned int val; + + rt5682 = devm_kzalloc(&i2c->dev, sizeof(struct rt5682_priv), + GFP_KERNEL); + + if (rt5682 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, rt5682); + + if (pdata) + rt5682->pdata = *pdata; + else + rt5682_parse_dt(rt5682, &i2c->dev); + + rt5682->regmap = devm_regmap_init_i2c(i2c, &rt5682_regmap); + if (IS_ERR(rt5682->regmap)) { + ret = PTR_ERR(rt5682->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(rt5682->supplies); i++) + rt5682->supplies[i].supply = rt5682_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(rt5682->supplies), + rt5682->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(rt5682->supplies), + rt5682->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + if (gpio_is_valid(rt5682->pdata.ldo1_en)) { + if (devm_gpio_request_one(&i2c->dev, rt5682->pdata.ldo1_en, + GPIOF_OUT_INIT_HIGH, "rt5682")) + dev_err(&i2c->dev, "Fail gpio_request gpio_ldo\n"); + } + + /* Sleep for 300 ms miniumum */ + usleep_range(300000, 350000); + + regmap_write(rt5682->regmap, RT5682_I2C_MODE, 0x1); + usleep_range(10000, 15000); + + regmap_read(rt5682->regmap, RT5682_DEVICE_ID, &val); + if (val != DEVICE_ID) { + pr_err("Device with ID register %x is not rt5682\n", val); + return -ENODEV; + } + + rt5682_reset(rt5682->regmap); + + rt5682_calibrate(rt5682); + + ret = regmap_register_patch(rt5682->regmap, patch_list, + ARRAY_SIZE(patch_list)); + if (ret != 0) + dev_warn(&i2c->dev, "Failed to apply regmap patch: %d\n", ret); + + regmap_write(rt5682->regmap, RT5682_DEPOP_1, 0x0000); + + /* DMIC pin*/ + if (rt5682->pdata.dmic1_data_pin != RT5682_DMIC1_NULL) { + switch (rt5682->pdata.dmic1_data_pin) { + case RT5682_DMIC1_DATA_GPIO2: /* share with LRCK2 */ + regmap_update_bits(rt5682->regmap, RT5682_DMIC_CTRL_1, + RT5682_DMIC_1_DP_MASK, RT5682_DMIC_1_DP_GPIO2); + regmap_update_bits(rt5682->regmap, RT5682_GPIO_CTRL_1, + RT5682_GP2_PIN_MASK, RT5682_GP2_PIN_DMIC_SDA); + break; + + case RT5682_DMIC1_DATA_GPIO5: /* share with DACDAT1 */ + regmap_update_bits(rt5682->regmap, RT5682_DMIC_CTRL_1, + RT5682_DMIC_1_DP_MASK, RT5682_DMIC_1_DP_GPIO5); + regmap_update_bits(rt5682->regmap, RT5682_GPIO_CTRL_1, + RT5682_GP5_PIN_MASK, RT5682_GP5_PIN_DMIC_SDA); + break; + + default: + dev_warn(&i2c->dev, "invalid DMIC_DAT pin\n"); + break; + } + + switch (rt5682->pdata.dmic1_clk_pin) { + case RT5682_DMIC1_CLK_GPIO1: /* share with IRQ */ + regmap_update_bits(rt5682->regmap, RT5682_GPIO_CTRL_1, + RT5682_GP1_PIN_MASK, RT5682_GP1_PIN_DMIC_CLK); + break; + + case RT5682_DMIC1_CLK_GPIO3: /* share with BCLK2 */ + regmap_update_bits(rt5682->regmap, RT5682_GPIO_CTRL_1, + RT5682_GP3_PIN_MASK, RT5682_GP3_PIN_DMIC_CLK); + break; + + default: + dev_warn(&i2c->dev, "invalid DMIC_CLK pin\n"); + break; + } + } + + regmap_update_bits(rt5682->regmap, RT5682_PWR_ANLG_1, + RT5682_LDO1_DVO_MASK | RT5682_HP_DRIVER_MASK, + RT5682_LDO1_DVO_12 | RT5682_HP_DRIVER_5X); + regmap_write(rt5682->regmap, RT5682_MICBIAS_2, 0x0380); + regmap_update_bits(rt5682->regmap, RT5682_GPIO_CTRL_1, + RT5682_GP4_PIN_MASK | RT5682_GP5_PIN_MASK, + RT5682_GP4_PIN_ADCDAT1 | RT5682_GP5_PIN_DACDAT1); + regmap_write(rt5682->regmap, RT5682_TEST_MODE_CTRL_1, 0x0000); + + INIT_DELAYED_WORK(&rt5682->jack_detect_work, + rt5682_jack_detect_handler); + INIT_DELAYED_WORK(&rt5682->jd_check_work, + rt5682_jd_check_handler); + + mutex_init(&rt5682->calibrate_mutex); + + if (i2c->irq) { + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + rt5682_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, "rt5682", rt5682); + if (ret) + dev_err(&i2c->dev, "Failed to reguest IRQ: %d\n", ret); + + } + + return devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_rt5682, + rt5682_dai, ARRAY_SIZE(rt5682_dai)); +} + +static void rt5682_i2c_shutdown(struct i2c_client *client) +{ + struct rt5682_priv *rt5682 = i2c_get_clientdata(client); + + rt5682_reset(rt5682->regmap); +} + +#ifdef CONFIG_OF +static const struct of_device_id rt5682_of_match[] = { + {.compatible = "realtek,rt5682i"}, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5682_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id rt5682_acpi_match[] = { + {"10EC5682", 0,}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt5682_acpi_match); +#endif + +static struct i2c_driver rt5682_i2c_driver = { + .driver = { + .name = "rt5682", + .of_match_table = of_match_ptr(rt5682_of_match), + .acpi_match_table = ACPI_PTR(rt5682_acpi_match), + }, + .probe = rt5682_i2c_probe, + .shutdown = rt5682_i2c_shutdown, + .id_table = rt5682_i2c_id, +}; +module_i2c_driver(rt5682_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT5682 driver"); +MODULE_AUTHOR("Bard Liao "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5682.h b/sound/soc/codecs/rt5682.h new file mode 100644 index 000000000..cdd659f4d --- /dev/null +++ b/sound/soc/codecs/rt5682.h @@ -0,0 +1,1324 @@ +/* + * rt5682.h -- RT5682/RT5658 ALSA SoC audio driver + * + * Copyright 2018 Realtek Microelectronics + * Author: Bard Liao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __RT5682_H__ +#define __RT5682_H__ + +#include + +#define DEVICE_ID 0x6530 + +/* Info */ +#define RT5682_RESET 0x0000 +#define RT5682_VERSION_ID 0x00fd +#define RT5682_VENDOR_ID 0x00fe +#define RT5682_DEVICE_ID 0x00ff +/* I/O - Output */ +#define RT5682_HP_CTRL_1 0x0002 +#define RT5682_HP_CTRL_2 0x0003 +#define RT5682_HPL_GAIN 0x0005 +#define RT5682_HPR_GAIN 0x0006 + +#define RT5682_I2C_CTRL 0x0008 + +/* I/O - Input */ +#define RT5682_CBJ_BST_CTRL 0x000b +#define RT5682_CBJ_CTRL_1 0x0010 +#define RT5682_CBJ_CTRL_2 0x0011 +#define RT5682_CBJ_CTRL_3 0x0012 +#define RT5682_CBJ_CTRL_4 0x0013 +#define RT5682_CBJ_CTRL_5 0x0014 +#define RT5682_CBJ_CTRL_6 0x0015 +#define RT5682_CBJ_CTRL_7 0x0016 +/* I/O - ADC/DAC/DMIC */ +#define RT5682_DAC1_DIG_VOL 0x0019 +#define RT5682_STO1_ADC_DIG_VOL 0x001c +#define RT5682_STO1_ADC_BOOST 0x001f +#define RT5682_HP_IMP_GAIN_1 0x0022 +#define RT5682_HP_IMP_GAIN_2 0x0023 +/* Mixer - D-D */ +#define RT5682_SIDETONE_CTRL 0x0024 +#define RT5682_STO1_ADC_MIXER 0x0026 +#define RT5682_AD_DA_MIXER 0x0029 +#define RT5682_STO1_DAC_MIXER 0x002a +#define RT5682_A_DAC1_MUX 0x002b +#define RT5682_DIG_INF2_DATA 0x0030 +/* Mixer - ADC */ +#define RT5682_REC_MIXER 0x003c +#define RT5682_CAL_REC 0x0044 +#define RT5682_ALC_BACK_GAIN 0x0049 +/* Power */ +#define RT5682_PWR_DIG_1 0x0061 +#define RT5682_PWR_DIG_2 0x0062 +#define RT5682_PWR_ANLG_1 0x0063 +#define RT5682_PWR_ANLG_2 0x0064 +#define RT5682_PWR_ANLG_3 0x0065 +#define RT5682_PWR_MIXER 0x0066 +#define RT5682_PWR_VOL 0x0067 +/* Clock Detect */ +#define RT5682_CLK_DET 0x006b +/* Filter Auto Reset */ +#define RT5682_RESET_LPF_CTRL 0x006c +#define RT5682_RESET_HPF_CTRL 0x006d +/* DMIC */ +#define RT5682_DMIC_CTRL_1 0x006e +/* Format - ADC/DAC */ +#define RT5682_I2S1_SDP 0x0070 +#define RT5682_I2S2_SDP 0x0071 +#define RT5682_ADDA_CLK_1 0x0073 +#define RT5682_ADDA_CLK_2 0x0074 +#define RT5682_I2S1_F_DIV_CTRL_1 0x0075 +#define RT5682_I2S1_F_DIV_CTRL_2 0x0076 +/* Format - TDM Control */ +#define RT5682_TDM_CTRL 0x0079 +#define RT5682_TDM_ADDA_CTRL_1 0x007a +#define RT5682_TDM_ADDA_CTRL_2 0x007b +#define RT5682_DATA_SEL_CTRL_1 0x007c +#define RT5682_TDM_TCON_CTRL 0x007e +/* Function - Analog */ +#define RT5682_GLB_CLK 0x0080 +#define RT5682_PLL_CTRL_1 0x0081 +#define RT5682_PLL_CTRL_2 0x0082 +#define RT5682_PLL_TRACK_1 0x0083 +#define RT5682_PLL_TRACK_2 0x0084 +#define RT5682_PLL_TRACK_3 0x0085 +#define RT5682_PLL_TRACK_4 0x0086 +#define RT5682_PLL_TRACK_5 0x0087 +#define RT5682_PLL_TRACK_6 0x0088 +#define RT5682_PLL_TRACK_11 0x008c +#define RT5682_SDW_REF_CLK 0x008d +#define RT5682_DEPOP_1 0x008e +#define RT5682_DEPOP_2 0x008f +#define RT5682_HP_CHARGE_PUMP_1 0x0091 +#define RT5682_HP_CHARGE_PUMP_2 0x0092 +#define RT5682_MICBIAS_1 0x0093 +#define RT5682_MICBIAS_2 0x0094 +#define RT5682_PLL_TRACK_12 0x0098 +#define RT5682_PLL_TRACK_14 0x009a +#define RT5682_PLL2_CTRL_1 0x009b +#define RT5682_PLL2_CTRL_2 0x009c +#define RT5682_PLL2_CTRL_3 0x009d +#define RT5682_PLL2_CTRL_4 0x009e +#define RT5682_RC_CLK_CTRL 0x009f +#define RT5682_I2S_M_CLK_CTRL_1 0x00a0 +#define RT5682_I2S2_F_DIV_CTRL_1 0x00a3 +#define RT5682_I2S2_F_DIV_CTRL_2 0x00a4 +/* Function - Digital */ +#define RT5682_EQ_CTRL_1 0x00ae +#define RT5682_EQ_CTRL_2 0x00af +#define RT5682_IRQ_CTRL_1 0x00b6 +#define RT5682_IRQ_CTRL_2 0x00b7 +#define RT5682_IRQ_CTRL_3 0x00b8 +#define RT5682_IRQ_CTRL_4 0x00b9 +#define RT5682_INT_ST_1 0x00be +#define RT5682_GPIO_CTRL_1 0x00c0 +#define RT5682_GPIO_CTRL_2 0x00c1 +#define RT5682_GPIO_CTRL_3 0x00c2 +#define RT5682_HP_AMP_DET_CTRL_1 0x00d0 +#define RT5682_HP_AMP_DET_CTRL_2 0x00d1 +#define RT5682_MID_HP_AMP_DET 0x00d2 +#define RT5682_LOW_HP_AMP_DET 0x00d3 +#define RT5682_DELAY_BUF_CTRL 0x00d4 +#define RT5682_SV_ZCD_1 0x00d9 +#define RT5682_SV_ZCD_2 0x00da +#define RT5682_IL_CMD_1 0x00db +#define RT5682_IL_CMD_2 0x00dc +#define RT5682_IL_CMD_3 0x00dd +#define RT5682_IL_CMD_4 0x00de +#define RT5682_IL_CMD_5 0x00df +#define RT5682_IL_CMD_6 0x00e0 +#define RT5682_4BTN_IL_CMD_1 0x00e2 +#define RT5682_4BTN_IL_CMD_2 0x00e3 +#define RT5682_4BTN_IL_CMD_3 0x00e4 +#define RT5682_4BTN_IL_CMD_4 0x00e5 +#define RT5682_4BTN_IL_CMD_5 0x00e6 +#define RT5682_4BTN_IL_CMD_6 0x00e7 +#define RT5682_4BTN_IL_CMD_7 0x00e8 + +#define RT5682_ADC_STO1_HP_CTRL_1 0x00ea +#define RT5682_ADC_STO1_HP_CTRL_2 0x00eb +#define RT5682_AJD1_CTRL 0x00f0 +#define RT5682_JD1_THD 0x00f1 +#define RT5682_JD2_THD 0x00f2 +#define RT5682_JD_CTRL_1 0x00f6 +/* General Control */ +#define RT5682_DUMMY_1 0x00fa +#define RT5682_DUMMY_2 0x00fb +#define RT5682_DUMMY_3 0x00fc + +#define RT5682_DAC_ADC_DIG_VOL1 0x0100 +#define RT5682_BIAS_CUR_CTRL_2 0x010b +#define RT5682_BIAS_CUR_CTRL_3 0x010c +#define RT5682_BIAS_CUR_CTRL_4 0x010d +#define RT5682_BIAS_CUR_CTRL_5 0x010e +#define RT5682_BIAS_CUR_CTRL_6 0x010f +#define RT5682_BIAS_CUR_CTRL_7 0x0110 +#define RT5682_BIAS_CUR_CTRL_8 0x0111 +#define RT5682_BIAS_CUR_CTRL_9 0x0112 +#define RT5682_BIAS_CUR_CTRL_10 0x0113 +#define RT5682_VREF_REC_OP_FB_CAP_CTRL 0x0117 +#define RT5682_CHARGE_PUMP_1 0x0125 +#define RT5682_DIG_IN_CTRL_1 0x0132 +#define RT5682_PAD_DRIVING_CTRL 0x0136 +#define RT5682_SOFT_RAMP_DEPOP 0x0138 +#define RT5682_CHOP_DAC 0x013a +#define RT5682_CHOP_ADC 0x013b +#define RT5682_CALIB_ADC_CTRL 0x013c +#define RT5682_VOL_TEST 0x013f +#define RT5682_SPKVDD_DET_STA 0x0142 +#define RT5682_TEST_MODE_CTRL_1 0x0145 +#define RT5682_TEST_MODE_CTRL_2 0x0146 +#define RT5682_TEST_MODE_CTRL_3 0x0147 +#define RT5682_TEST_MODE_CTRL_4 0x0148 +#define RT5682_TEST_MODE_CTRL_5 0x0149 +#define RT5682_PLL1_INTERNAL 0x0150 +#define RT5682_PLL2_INTERNAL 0x0151 +#define RT5682_STO_NG2_CTRL_1 0x0160 +#define RT5682_STO_NG2_CTRL_2 0x0161 +#define RT5682_STO_NG2_CTRL_3 0x0162 +#define RT5682_STO_NG2_CTRL_4 0x0163 +#define RT5682_STO_NG2_CTRL_5 0x0164 +#define RT5682_STO_NG2_CTRL_6 0x0165 +#define RT5682_STO_NG2_CTRL_7 0x0166 +#define RT5682_STO_NG2_CTRL_8 0x0167 +#define RT5682_STO_NG2_CTRL_9 0x0168 +#define RT5682_STO_NG2_CTRL_10 0x0169 +#define RT5682_STO1_DAC_SIL_DET 0x0190 +#define RT5682_SIL_PSV_CTRL1 0x0194 +#define RT5682_SIL_PSV_CTRL2 0x0195 +#define RT5682_SIL_PSV_CTRL3 0x0197 +#define RT5682_SIL_PSV_CTRL4 0x0198 +#define RT5682_SIL_PSV_CTRL5 0x0199 +#define RT5682_HP_IMP_SENS_CTRL_01 0x01af +#define RT5682_HP_IMP_SENS_CTRL_02 0x01b0 +#define RT5682_HP_IMP_SENS_CTRL_03 0x01b1 +#define RT5682_HP_IMP_SENS_CTRL_04 0x01b2 +#define RT5682_HP_IMP_SENS_CTRL_05 0x01b3 +#define RT5682_HP_IMP_SENS_CTRL_06 0x01b4 +#define RT5682_HP_IMP_SENS_CTRL_07 0x01b5 +#define RT5682_HP_IMP_SENS_CTRL_08 0x01b6 +#define RT5682_HP_IMP_SENS_CTRL_09 0x01b7 +#define RT5682_HP_IMP_SENS_CTRL_10 0x01b8 +#define RT5682_HP_IMP_SENS_CTRL_11 0x01b9 +#define RT5682_HP_IMP_SENS_CTRL_12 0x01ba +#define RT5682_HP_IMP_SENS_CTRL_13 0x01bb +#define RT5682_HP_IMP_SENS_CTRL_14 0x01bc +#define RT5682_HP_IMP_SENS_CTRL_15 0x01bd +#define RT5682_HP_IMP_SENS_CTRL_16 0x01be +#define RT5682_HP_IMP_SENS_CTRL_17 0x01bf +#define RT5682_HP_IMP_SENS_CTRL_18 0x01c0 +#define RT5682_HP_IMP_SENS_CTRL_19 0x01c1 +#define RT5682_HP_IMP_SENS_CTRL_20 0x01c2 +#define RT5682_HP_IMP_SENS_CTRL_21 0x01c3 +#define RT5682_HP_IMP_SENS_CTRL_22 0x01c4 +#define RT5682_HP_IMP_SENS_CTRL_23 0x01c5 +#define RT5682_HP_IMP_SENS_CTRL_24 0x01c6 +#define RT5682_HP_IMP_SENS_CTRL_25 0x01c7 +#define RT5682_HP_IMP_SENS_CTRL_26 0x01c8 +#define RT5682_HP_IMP_SENS_CTRL_27 0x01c9 +#define RT5682_HP_IMP_SENS_CTRL_28 0x01ca +#define RT5682_HP_IMP_SENS_CTRL_29 0x01cb +#define RT5682_HP_IMP_SENS_CTRL_30 0x01cc +#define RT5682_HP_IMP_SENS_CTRL_31 0x01cd +#define RT5682_HP_IMP_SENS_CTRL_32 0x01ce +#define RT5682_HP_IMP_SENS_CTRL_33 0x01cf +#define RT5682_HP_IMP_SENS_CTRL_34 0x01d0 +#define RT5682_HP_IMP_SENS_CTRL_35 0x01d1 +#define RT5682_HP_IMP_SENS_CTRL_36 0x01d2 +#define RT5682_HP_IMP_SENS_CTRL_37 0x01d3 +#define RT5682_HP_IMP_SENS_CTRL_38 0x01d4 +#define RT5682_HP_IMP_SENS_CTRL_39 0x01d5 +#define RT5682_HP_IMP_SENS_CTRL_40 0x01d6 +#define RT5682_HP_IMP_SENS_CTRL_41 0x01d7 +#define RT5682_HP_IMP_SENS_CTRL_42 0x01d8 +#define RT5682_HP_IMP_SENS_CTRL_43 0x01d9 +#define RT5682_HP_LOGIC_CTRL_1 0x01da +#define RT5682_HP_LOGIC_CTRL_2 0x01db +#define RT5682_HP_LOGIC_CTRL_3 0x01dc +#define RT5682_HP_CALIB_CTRL_1 0x01de +#define RT5682_HP_CALIB_CTRL_2 0x01df +#define RT5682_HP_CALIB_CTRL_3 0x01e0 +#define RT5682_HP_CALIB_CTRL_4 0x01e1 +#define RT5682_HP_CALIB_CTRL_5 0x01e2 +#define RT5682_HP_CALIB_CTRL_6 0x01e3 +#define RT5682_HP_CALIB_CTRL_7 0x01e4 +#define RT5682_HP_CALIB_CTRL_9 0x01e6 +#define RT5682_HP_CALIB_CTRL_10 0x01e7 +#define RT5682_HP_CALIB_CTRL_11 0x01e8 +#define RT5682_HP_CALIB_STA_1 0x01ea +#define RT5682_HP_CALIB_STA_2 0x01eb +#define RT5682_HP_CALIB_STA_3 0x01ec +#define RT5682_HP_CALIB_STA_4 0x01ed +#define RT5682_HP_CALIB_STA_5 0x01ee +#define RT5682_HP_CALIB_STA_6 0x01ef +#define RT5682_HP_CALIB_STA_7 0x01f0 +#define RT5682_HP_CALIB_STA_8 0x01f1 +#define RT5682_HP_CALIB_STA_9 0x01f2 +#define RT5682_HP_CALIB_STA_10 0x01f3 +#define RT5682_HP_CALIB_STA_11 0x01f4 +#define RT5682_SAR_IL_CMD_1 0x0210 +#define RT5682_SAR_IL_CMD_2 0x0211 +#define RT5682_SAR_IL_CMD_3 0x0212 +#define RT5682_SAR_IL_CMD_4 0x0213 +#define RT5682_SAR_IL_CMD_5 0x0214 +#define RT5682_SAR_IL_CMD_6 0x0215 +#define RT5682_SAR_IL_CMD_7 0x0216 +#define RT5682_SAR_IL_CMD_8 0x0217 +#define RT5682_SAR_IL_CMD_9 0x0218 +#define RT5682_SAR_IL_CMD_10 0x0219 +#define RT5682_SAR_IL_CMD_11 0x021a +#define RT5682_SAR_IL_CMD_12 0x021b +#define RT5682_SAR_IL_CMD_13 0x021c +#define RT5682_EFUSE_CTRL_1 0x0250 +#define RT5682_EFUSE_CTRL_2 0x0251 +#define RT5682_EFUSE_CTRL_3 0x0252 +#define RT5682_EFUSE_CTRL_4 0x0253 +#define RT5682_EFUSE_CTRL_5 0x0254 +#define RT5682_EFUSE_CTRL_6 0x0255 +#define RT5682_EFUSE_CTRL_7 0x0256 +#define RT5682_EFUSE_CTRL_8 0x0257 +#define RT5682_EFUSE_CTRL_9 0x0258 +#define RT5682_EFUSE_CTRL_10 0x0259 +#define RT5682_EFUSE_CTRL_11 0x025a +#define RT5682_JD_TOP_VC_VTRL 0x0270 +#define RT5682_DRC1_CTRL_0 0x02ff +#define RT5682_DRC1_CTRL_1 0x0300 +#define RT5682_DRC1_CTRL_2 0x0301 +#define RT5682_DRC1_CTRL_3 0x0302 +#define RT5682_DRC1_CTRL_4 0x0303 +#define RT5682_DRC1_CTRL_5 0x0304 +#define RT5682_DRC1_CTRL_6 0x0305 +#define RT5682_DRC1_HARD_LMT_CTRL_1 0x0306 +#define RT5682_DRC1_HARD_LMT_CTRL_2 0x0307 +#define RT5682_DRC1_PRIV_1 0x0310 +#define RT5682_DRC1_PRIV_2 0x0311 +#define RT5682_DRC1_PRIV_3 0x0312 +#define RT5682_DRC1_PRIV_4 0x0313 +#define RT5682_DRC1_PRIV_5 0x0314 +#define RT5682_DRC1_PRIV_6 0x0315 +#define RT5682_DRC1_PRIV_7 0x0316 +#define RT5682_DRC1_PRIV_8 0x0317 +#define RT5682_EQ_AUTO_RCV_CTRL1 0x03c0 +#define RT5682_EQ_AUTO_RCV_CTRL2 0x03c1 +#define RT5682_EQ_AUTO_RCV_CTRL3 0x03c2 +#define RT5682_EQ_AUTO_RCV_CTRL4 0x03c3 +#define RT5682_EQ_AUTO_RCV_CTRL5 0x03c4 +#define RT5682_EQ_AUTO_RCV_CTRL6 0x03c5 +#define RT5682_EQ_AUTO_RCV_CTRL7 0x03c6 +#define RT5682_EQ_AUTO_RCV_CTRL8 0x03c7 +#define RT5682_EQ_AUTO_RCV_CTRL9 0x03c8 +#define RT5682_EQ_AUTO_RCV_CTRL10 0x03c9 +#define RT5682_EQ_AUTO_RCV_CTRL11 0x03ca +#define RT5682_EQ_AUTO_RCV_CTRL12 0x03cb +#define RT5682_EQ_AUTO_RCV_CTRL13 0x03cc +#define RT5682_ADC_L_EQ_LPF1_A1 0x03d0 +#define RT5682_R_EQ_LPF1_A1 0x03d1 +#define RT5682_L_EQ_LPF1_H0 0x03d2 +#define RT5682_R_EQ_LPF1_H0 0x03d3 +#define RT5682_L_EQ_BPF1_A1 0x03d4 +#define RT5682_R_EQ_BPF1_A1 0x03d5 +#define RT5682_L_EQ_BPF1_A2 0x03d6 +#define RT5682_R_EQ_BPF1_A2 0x03d7 +#define RT5682_L_EQ_BPF1_H0 0x03d8 +#define RT5682_R_EQ_BPF1_H0 0x03d9 +#define RT5682_L_EQ_BPF2_A1 0x03da +#define RT5682_R_EQ_BPF2_A1 0x03db +#define RT5682_L_EQ_BPF2_A2 0x03dc +#define RT5682_R_EQ_BPF2_A2 0x03dd +#define RT5682_L_EQ_BPF2_H0 0x03de +#define RT5682_R_EQ_BPF2_H0 0x03df +#define RT5682_L_EQ_BPF3_A1 0x03e0 +#define RT5682_R_EQ_BPF3_A1 0x03e1 +#define RT5682_L_EQ_BPF3_A2 0x03e2 +#define RT5682_R_EQ_BPF3_A2 0x03e3 +#define RT5682_L_EQ_BPF3_H0 0x03e4 +#define RT5682_R_EQ_BPF3_H0 0x03e5 +#define RT5682_L_EQ_BPF4_A1 0x03e6 +#define RT5682_R_EQ_BPF4_A1 0x03e7 +#define RT5682_L_EQ_BPF4_A2 0x03e8 +#define RT5682_R_EQ_BPF4_A2 0x03e9 +#define RT5682_L_EQ_BPF4_H0 0x03ea +#define RT5682_R_EQ_BPF4_H0 0x03eb +#define RT5682_L_EQ_HPF1_A1 0x03ec +#define RT5682_R_EQ_HPF1_A1 0x03ed +#define RT5682_L_EQ_HPF1_H0 0x03ee +#define RT5682_R_EQ_HPF1_H0 0x03ef +#define RT5682_L_EQ_PRE_VOL 0x03f0 +#define RT5682_R_EQ_PRE_VOL 0x03f1 +#define RT5682_L_EQ_POST_VOL 0x03f2 +#define RT5682_R_EQ_POST_VOL 0x03f3 +#define RT5682_I2C_MODE 0xffff + + +/* global definition */ +#define RT5682_L_MUTE (0x1 << 15) +#define RT5682_L_MUTE_SFT 15 +#define RT5682_VOL_L_MUTE (0x1 << 14) +#define RT5682_VOL_L_SFT 14 +#define RT5682_R_MUTE (0x1 << 7) +#define RT5682_R_MUTE_SFT 7 +#define RT5682_VOL_R_MUTE (0x1 << 6) +#define RT5682_VOL_R_SFT 6 +#define RT5682_L_VOL_MASK (0x3f << 8) +#define RT5682_L_VOL_SFT 8 +#define RT5682_R_VOL_MASK (0x3f) +#define RT5682_R_VOL_SFT 0 + +/*Headphone Amp L/R Analog Gain and Digital NG2 Gain Control (0x0005 0x0006)*/ +#define RT5682_G_HP (0xf << 8) +#define RT5682_G_HP_SFT 8 +#define RT5682_G_STO_DA_DMIX (0xf) +#define RT5682_G_STO_DA_SFT 0 + +/* CBJ Control (0x000b) */ +#define RT5682_BST_CBJ_MASK (0xf << 8) +#define RT5682_BST_CBJ_SFT 8 + +/* Embeeded Jack and Type Detection Control 1 (0x0010) */ +#define RT5682_EMB_JD_EN (0x1 << 15) +#define RT5682_EMB_JD_EN_SFT 15 +#define RT5682_EMB_JD_RST (0x1 << 14) +#define RT5682_JD_MODE (0x1 << 13) +#define RT5682_JD_MODE_SFT 13 +#define RT5682_DET_TYPE (0x1 << 12) +#define RT5682_DET_TYPE_SFT 12 +#define RT5682_POLA_EXT_JD_MASK (0x1 << 11) +#define RT5682_POLA_EXT_JD_LOW (0x1 << 11) +#define RT5682_POLA_EXT_JD_HIGH (0x0 << 11) +#define RT5682_EXT_JD_DIG (0x1 << 9) +#define RT5682_POL_FAST_OFF_MASK (0x1 << 8) +#define RT5682_POL_FAST_OFF_HIGH (0x1 << 8) +#define RT5682_POL_FAST_OFF_LOW (0x0 << 8) +#define RT5682_FAST_OFF_MASK (0x1 << 7) +#define RT5682_FAST_OFF_EN (0x1 << 7) +#define RT5682_FAST_OFF_DIS (0x0 << 7) +#define RT5682_VREF_POW_MASK (0x1 << 6) +#define RT5682_VREF_POW_FSM (0x0 << 6) +#define RT5682_VREF_POW_REG (0x1 << 6) +#define RT5682_MB1_PATH_MASK (0x1 << 5) +#define RT5682_CTRL_MB1_REG (0x1 << 5) +#define RT5682_CTRL_MB1_FSM (0x0 << 5) +#define RT5682_MB2_PATH_MASK (0x1 << 4) +#define RT5682_CTRL_MB2_REG (0x1 << 4) +#define RT5682_CTRL_MB2_FSM (0x0 << 4) +#define RT5682_TRIG_JD_MASK (0x1 << 3) +#define RT5682_TRIG_JD_HIGH (0x1 << 3) +#define RT5682_TRIG_JD_LOW (0x0 << 3) +#define RT5682_MIC_CAP_MASK (0x1 << 1) +#define RT5682_MIC_CAP_HS (0x1 << 1) +#define RT5682_MIC_CAP_HP (0x0 << 1) +#define RT5682_MIC_CAP_SRC_MASK (0x1) +#define RT5682_MIC_CAP_SRC_REG (0x1) +#define RT5682_MIC_CAP_SRC_ANA (0x0) + +/* Embeeded Jack and Type Detection Control 2 (0x0011) */ +#define RT5682_EXT_JD_SRC (0x7 << 4) +#define RT5682_EXT_JD_SRC_SFT 4 +#define RT5682_EXT_JD_SRC_GPIO_JD1 (0x0 << 4) +#define RT5682_EXT_JD_SRC_GPIO_JD2 (0x1 << 4) +#define RT5682_EXT_JD_SRC_JDH (0x2 << 4) +#define RT5682_EXT_JD_SRC_JDL (0x3 << 4) +#define RT5682_EXT_JD_SRC_MANUAL (0x4 << 4) +#define RT5682_JACK_TYPE_MASK (0x3) + +/* Combo Jack and Type Detection Control 3 (0x0012) */ +#define RT5682_CBJ_IN_BUF_EN (0x1 << 7) + +/* Combo Jack and Type Detection Control 4 (0x0013) */ +#define RT5682_SEL_SHT_MID_TON_MASK (0x3 << 12) +#define RT5682_SEL_SHT_MID_TON_2 (0x0 << 12) +#define RT5682_SEL_SHT_MID_TON_3 (0x1 << 12) +#define RT5682_CBJ_JD_TEST_MASK (0x1 << 6) +#define RT5682_CBJ_JD_TEST_NORM (0x0 << 6) +#define RT5682_CBJ_JD_TEST_MODE (0x1 << 6) + +/* DAC1 Digital Volume (0x0019) */ +#define RT5682_DAC_L1_VOL_MASK (0xff << 8) +#define RT5682_DAC_L1_VOL_SFT 8 +#define RT5682_DAC_R1_VOL_MASK (0xff) +#define RT5682_DAC_R1_VOL_SFT 0 + +/* ADC Digital Volume Control (0x001c) */ +#define RT5682_ADC_L_VOL_MASK (0x7f << 8) +#define RT5682_ADC_L_VOL_SFT 8 +#define RT5682_ADC_R_VOL_MASK (0x7f) +#define RT5682_ADC_R_VOL_SFT 0 + +/* Stereo1 ADC Boost Gain Control (0x001f) */ +#define RT5682_STO1_ADC_L_BST_MASK (0x3 << 14) +#define RT5682_STO1_ADC_L_BST_SFT 14 +#define RT5682_STO1_ADC_R_BST_MASK (0x3 << 12) +#define RT5682_STO1_ADC_R_BST_SFT 12 + +/* Sidetone Control (0x0024) */ +#define RT5682_ST_SRC_SEL (0x1 << 8) +#define RT5682_ST_SRC_SFT 8 +#define RT5682_ST_EN_MASK (0x1 << 6) +#define RT5682_ST_DIS (0x0 << 6) +#define RT5682_ST_EN (0x1 << 6) +#define RT5682_ST_EN_SFT 6 + +/* Stereo1 ADC Mixer Control (0x0026) */ +#define RT5682_M_STO1_ADC_L1 (0x1 << 15) +#define RT5682_M_STO1_ADC_L1_SFT 15 +#define RT5682_M_STO1_ADC_L2 (0x1 << 14) +#define RT5682_M_STO1_ADC_L2_SFT 14 +#define RT5682_STO1_ADC1L_SRC_MASK (0x1 << 13) +#define RT5682_STO1_ADC1L_SRC_SFT 13 +#define RT5682_STO1_ADC1_SRC_ADC (0x1 << 13) +#define RT5682_STO1_ADC1_SRC_DACMIX (0x0 << 13) +#define RT5682_STO1_ADC2L_SRC_MASK (0x1 << 12) +#define RT5682_STO1_ADC2L_SRC_SFT 12 +#define RT5682_STO1_ADCL_SRC_MASK (0x3 << 10) +#define RT5682_STO1_ADCL_SRC_SFT 10 +#define RT5682_STO1_DD_L_SRC_MASK (0x1 << 9) +#define RT5682_STO1_DD_L_SRC_SFT 9 +#define RT5682_STO1_DMIC_SRC_MASK (0x1 << 8) +#define RT5682_STO1_DMIC_SRC_SFT 8 +#define RT5682_STO1_DMIC_SRC_DMIC2 (0x1 << 8) +#define RT5682_STO1_DMIC_SRC_DMIC1 (0x0 << 8) +#define RT5682_M_STO1_ADC_R1 (0x1 << 7) +#define RT5682_M_STO1_ADC_R1_SFT 7 +#define RT5682_M_STO1_ADC_R2 (0x1 << 6) +#define RT5682_M_STO1_ADC_R2_SFT 6 +#define RT5682_STO1_ADC1R_SRC_MASK (0x1 << 5) +#define RT5682_STO1_ADC1R_SRC_SFT 5 +#define RT5682_STO1_ADC2R_SRC_MASK (0x1 << 4) +#define RT5682_STO1_ADC2R_SRC_SFT 4 +#define RT5682_STO1_ADCR_SRC_MASK (0x3 << 2) +#define RT5682_STO1_ADCR_SRC_SFT 2 + +/* ADC Mixer to DAC Mixer Control (0x0029) */ +#define RT5682_M_ADCMIX_L (0x1 << 15) +#define RT5682_M_ADCMIX_L_SFT 15 +#define RT5682_M_DAC1_L (0x1 << 14) +#define RT5682_M_DAC1_L_SFT 14 +#define RT5682_DAC1_R_SEL_MASK (0x1 << 10) +#define RT5682_DAC1_R_SEL_SFT 10 +#define RT5682_DAC1_L_SEL_MASK (0x1 << 8) +#define RT5682_DAC1_L_SEL_SFT 8 +#define RT5682_M_ADCMIX_R (0x1 << 7) +#define RT5682_M_ADCMIX_R_SFT 7 +#define RT5682_M_DAC1_R (0x1 << 6) +#define RT5682_M_DAC1_R_SFT 6 + +/* Stereo1 DAC Mixer Control (0x002a) */ +#define RT5682_M_DAC_L1_STO_L (0x1 << 15) +#define RT5682_M_DAC_L1_STO_L_SFT 15 +#define RT5682_G_DAC_L1_STO_L_MASK (0x1 << 14) +#define RT5682_G_DAC_L1_STO_L_SFT 14 +#define RT5682_M_DAC_R1_STO_L (0x1 << 13) +#define RT5682_M_DAC_R1_STO_L_SFT 13 +#define RT5682_G_DAC_R1_STO_L_MASK (0x1 << 12) +#define RT5682_G_DAC_R1_STO_L_SFT 12 +#define RT5682_M_DAC_L1_STO_R (0x1 << 7) +#define RT5682_M_DAC_L1_STO_R_SFT 7 +#define RT5682_G_DAC_L1_STO_R_MASK (0x1 << 6) +#define RT5682_G_DAC_L1_STO_R_SFT 6 +#define RT5682_M_DAC_R1_STO_R (0x1 << 5) +#define RT5682_M_DAC_R1_STO_R_SFT 5 +#define RT5682_G_DAC_R1_STO_R_MASK (0x1 << 4) +#define RT5682_G_DAC_R1_STO_R_SFT 4 + +/* Analog DAC1 Input Source Control (0x002b) */ +#define RT5682_M_ST_STO_L (0x1 << 9) +#define RT5682_M_ST_STO_L_SFT 9 +#define RT5682_M_ST_STO_R (0x1 << 8) +#define RT5682_M_ST_STO_R_SFT 8 +#define RT5682_DAC_L1_SRC_MASK (0x3 << 4) +#define RT5682_A_DACL1_SFT 4 +#define RT5682_DAC_R1_SRC_MASK (0x3) +#define RT5682_A_DACR1_SFT 0 + +/* Digital Interface Data Control (0x0030) */ +#define RT5682_IF2_ADC_SEL_MASK (0x3 << 0) +#define RT5682_IF2_ADC_SEL_SFT 0 + +/* REC Left Mixer Control 2 (0x003c) */ +#define RT5682_G_CBJ_RM1_L (0x7 << 10) +#define RT5682_G_CBJ_RM1_L_SFT 10 +#define RT5682_M_CBJ_RM1_L (0x1 << 7) +#define RT5682_M_CBJ_RM1_L_SFT 7 + +/* Power Management for Digital 1 (0x0061) */ +#define RT5682_PWR_I2S1 (0x1 << 15) +#define RT5682_PWR_I2S1_BIT 15 +#define RT5682_PWR_I2S2 (0x1 << 14) +#define RT5682_PWR_I2S2_BIT 14 +#define RT5682_PWR_DAC_L1 (0x1 << 11) +#define RT5682_PWR_DAC_L1_BIT 11 +#define RT5682_PWR_DAC_R1 (0x1 << 10) +#define RT5682_PWR_DAC_R1_BIT 10 +#define RT5682_PWR_LDO (0x1 << 8) +#define RT5682_PWR_LDO_BIT 8 +#define RT5682_PWR_ADC_L1 (0x1 << 4) +#define RT5682_PWR_ADC_L1_BIT 4 +#define RT5682_PWR_ADC_R1 (0x1 << 3) +#define RT5682_PWR_ADC_R1_BIT 3 +#define RT5682_DIG_GATE_CTRL (0x1 << 0) +#define RT5682_DIG_GATE_CTRL_SFT 0 + + +/* Power Management for Digital 2 (0x0062) */ +#define RT5682_PWR_ADC_S1F (0x1 << 15) +#define RT5682_PWR_ADC_S1F_BIT 15 +#define RT5682_PWR_DAC_S1F (0x1 << 10) +#define RT5682_PWR_DAC_S1F_BIT 10 + +/* Power Management for Analog 1 (0x0063) */ +#define RT5682_PWR_VREF1 (0x1 << 15) +#define RT5682_PWR_VREF1_BIT 15 +#define RT5682_PWR_FV1 (0x1 << 14) +#define RT5682_PWR_FV1_BIT 14 +#define RT5682_PWR_VREF2 (0x1 << 13) +#define RT5682_PWR_VREF2_BIT 13 +#define RT5682_PWR_FV2 (0x1 << 12) +#define RT5682_PWR_FV2_BIT 12 +#define RT5682_LDO1_DBG_MASK (0x3 << 10) +#define RT5682_PWR_MB (0x1 << 9) +#define RT5682_PWR_MB_BIT 9 +#define RT5682_PWR_BG (0x1 << 7) +#define RT5682_PWR_BG_BIT 7 +#define RT5682_LDO1_BYPASS_MASK (0x1 << 6) +#define RT5682_LDO1_BYPASS (0x1 << 6) +#define RT5682_LDO1_NOT_BYPASS (0x0 << 6) +#define RT5682_PWR_MA_BIT 6 +#define RT5682_LDO1_DVO_MASK (0x3 << 4) +#define RT5682_LDO1_DVO_09 (0x0 << 4) +#define RT5682_LDO1_DVO_10 (0x1 << 4) +#define RT5682_LDO1_DVO_12 (0x2 << 4) +#define RT5682_LDO1_DVO_14 (0x3 << 4) +#define RT5682_HP_DRIVER_MASK (0x3 << 2) +#define RT5682_HP_DRIVER_1X (0x0 << 2) +#define RT5682_HP_DRIVER_3X (0x1 << 2) +#define RT5682_HP_DRIVER_5X (0x3 << 2) +#define RT5682_PWR_HA_L (0x1 << 1) +#define RT5682_PWR_HA_L_BIT 1 +#define RT5682_PWR_HA_R (0x1 << 0) +#define RT5682_PWR_HA_R_BIT 0 + +/* Power Management for Analog 2 (0x0064) */ +#define RT5682_PWR_MB1 (0x1 << 11) +#define RT5682_PWR_MB1_PWR_DOWN (0x0 << 11) +#define RT5682_PWR_MB1_BIT 11 +#define RT5682_PWR_MB2 (0x1 << 10) +#define RT5682_PWR_MB2_PWR_DOWN (0x0 << 10) +#define RT5682_PWR_MB2_BIT 10 +#define RT5682_PWR_JDH (0x1 << 3) +#define RT5682_PWR_JDH_BIT 3 +#define RT5682_PWR_JDL (0x1 << 2) +#define RT5682_PWR_JDL_BIT 2 +#define RT5682_PWR_RM1_L (0x1 << 1) +#define RT5682_PWR_RM1_L_BIT 1 + +/* Power Management for Analog 3 (0x0065) */ +#define RT5682_PWR_CBJ (0x1 << 9) +#define RT5682_PWR_CBJ_BIT 9 +#define RT5682_PWR_PLL (0x1 << 6) +#define RT5682_PWR_PLL_BIT 6 +#define RT5682_PWR_PLL2B (0x1 << 5) +#define RT5682_PWR_PLL2B_BIT 5 +#define RT5682_PWR_PLL2F (0x1 << 4) +#define RT5682_PWR_PLL2F_BIT 4 +#define RT5682_PWR_LDO2 (0x1 << 2) +#define RT5682_PWR_LDO2_BIT 2 +#define RT5682_PWR_DET_SPKVDD (0x1 << 1) +#define RT5682_PWR_DET_SPKVDD_BIT 1 + +/* Power Management for Mixer (0x0066) */ +#define RT5682_PWR_STO1_DAC_L (0x1 << 5) +#define RT5682_PWR_STO1_DAC_L_BIT 5 +#define RT5682_PWR_STO1_DAC_R (0x1 << 4) +#define RT5682_PWR_STO1_DAC_R_BIT 4 + +/* MCLK and System Clock Detection Control (0x006b) */ +#define RT5682_SYS_CLK_DET (0x1 << 15) +#define RT5682_SYS_CLK_DET_SFT 15 +#define RT5682_PLL1_CLK_DET (0x1 << 14) +#define RT5682_PLL1_CLK_DET_SFT 14 +#define RT5682_PLL2_CLK_DET (0x1 << 13) +#define RT5682_PLL2_CLK_DET_SFT 13 +#define RT5682_POW_CLK_DET2_SFT 8 +#define RT5682_POW_CLK_DET_SFT 0 + +/* Digital Microphone Control 1 (0x006e) */ +#define RT5682_DMIC_1_EN_MASK (0x1 << 15) +#define RT5682_DMIC_1_EN_SFT 15 +#define RT5682_DMIC_1_DIS (0x0 << 15) +#define RT5682_DMIC_1_EN (0x1 << 15) +#define RT5682_DMIC_1_DP_MASK (0x3 << 4) +#define RT5682_DMIC_1_DP_SFT 4 +#define RT5682_DMIC_1_DP_GPIO2 (0x0 << 4) +#define RT5682_DMIC_1_DP_GPIO5 (0x1 << 4) +#define RT5682_DMIC_CLK_MASK (0xf << 0) +#define RT5682_DMIC_CLK_SFT 0 + +/* I2S1 Audio Serial Data Port Control (0x0070) */ +#define RT5682_SEL_ADCDAT_MASK (0x1 << 15) +#define RT5682_SEL_ADCDAT_OUT (0x0 << 15) +#define RT5682_SEL_ADCDAT_IN (0x1 << 15) +#define RT5682_SEL_ADCDAT_SFT 15 +#define RT5682_I2S1_TX_CHL_MASK (0x7 << 12) +#define RT5682_I2S1_TX_CHL_SFT 12 +#define RT5682_I2S1_TX_CHL_16 (0x0 << 12) +#define RT5682_I2S1_TX_CHL_20 (0x1 << 12) +#define RT5682_I2S1_TX_CHL_24 (0x2 << 12) +#define RT5682_I2S1_TX_CHL_32 (0x3 << 12) +#define RT5682_I2S1_TX_CHL_8 (0x4 << 12) +#define RT5682_I2S1_RX_CHL_MASK (0x7 << 8) +#define RT5682_I2S1_RX_CHL_SFT 8 +#define RT5682_I2S1_RX_CHL_16 (0x0 << 8) +#define RT5682_I2S1_RX_CHL_20 (0x1 << 8) +#define RT5682_I2S1_RX_CHL_24 (0x2 << 8) +#define RT5682_I2S1_RX_CHL_32 (0x3 << 8) +#define RT5682_I2S1_RX_CHL_8 (0x4 << 8) +#define RT5682_I2S1_MONO_MASK (0x1 << 7) +#define RT5682_I2S1_MONO_EN (0x1 << 7) +#define RT5682_I2S1_MONO_DIS (0x0 << 7) +#define RT5682_I2S2_MONO_MASK (0x1 << 6) +#define RT5682_I2S2_MONO_EN (0x1 << 6) +#define RT5682_I2S2_MONO_DIS (0x0 << 6) +#define RT5682_I2S1_DL_MASK (0x7 << 4) +#define RT5682_I2S1_DL_SFT 4 +#define RT5682_I2S1_DL_16 (0x0 << 4) +#define RT5682_I2S1_DL_20 (0x1 << 4) +#define RT5682_I2S1_DL_24 (0x2 << 4) +#define RT5682_I2S1_DL_32 (0x3 << 4) +#define RT5682_I2S1_DL_8 (0x4 << 4) + +/* I2S1/2 Audio Serial Data Port Control (0x0070)(0x0071) */ +#define RT5682_I2S2_MS_MASK (0x1 << 15) +#define RT5682_I2S2_MS_SFT 15 +#define RT5682_I2S2_MS_M (0x0 << 15) +#define RT5682_I2S2_MS_S (0x1 << 15) +#define RT5682_I2S2_PIN_CFG_MASK (0x1 << 14) +#define RT5682_I2S2_PIN_CFG_SFT 14 +#define RT5682_I2S2_CLK_SEL_MASK (0x1 << 11) +#define RT5682_I2S2_CLK_SEL_SFT 11 +#define RT5682_I2S2_OUT_MASK (0x1 << 9) +#define RT5682_I2S2_OUT_SFT 9 +#define RT5682_I2S2_OUT_UM (0x0 << 9) +#define RT5682_I2S2_OUT_M (0x1 << 9) +#define RT5682_I2S_BP_MASK (0x1 << 8) +#define RT5682_I2S_BP_SFT 8 +#define RT5682_I2S_BP_NOR (0x0 << 8) +#define RT5682_I2S_BP_INV (0x1 << 8) +#define RT5682_I2S2_MONO_EN (0x1 << 6) +#define RT5682_I2S2_MONO_DIS (0x0 << 6) +#define RT5682_I2S2_DL_MASK (0x3 << 4) +#define RT5682_I2S2_DL_SFT 4 +#define RT5682_I2S2_DL_16 (0x0 << 4) +#define RT5682_I2S2_DL_20 (0x1 << 4) +#define RT5682_I2S2_DL_24 (0x2 << 4) +#define RT5682_I2S2_DL_8 (0x3 << 4) +#define RT5682_I2S_DF_MASK (0x7) +#define RT5682_I2S_DF_SFT 0 +#define RT5682_I2S_DF_I2S (0x0) +#define RT5682_I2S_DF_LEFT (0x1) +#define RT5682_I2S_DF_PCM_A (0x2) +#define RT5682_I2S_DF_PCM_B (0x3) +#define RT5682_I2S_DF_PCM_A_N (0x6) +#define RT5682_I2S_DF_PCM_B_N (0x7) + +/* ADC/DAC Clock Control 1 (0x0073) */ +#define RT5682_ADC_OSR_MASK (0xf << 12) +#define RT5682_ADC_OSR_SFT 12 +#define RT5682_ADC_OSR_D_1 (0x0 << 12) +#define RT5682_ADC_OSR_D_2 (0x1 << 12) +#define RT5682_ADC_OSR_D_4 (0x2 << 12) +#define RT5682_ADC_OSR_D_6 (0x3 << 12) +#define RT5682_ADC_OSR_D_8 (0x4 << 12) +#define RT5682_ADC_OSR_D_12 (0x5 << 12) +#define RT5682_ADC_OSR_D_16 (0x6 << 12) +#define RT5682_ADC_OSR_D_24 (0x7 << 12) +#define RT5682_ADC_OSR_D_32 (0x8 << 12) +#define RT5682_ADC_OSR_D_48 (0x9 << 12) +#define RT5682_I2S_M_DIV_MASK (0xf << 12) +#define RT5682_I2S_M_DIV_SFT 8 +#define RT5682_I2S_M_D_1 (0x0 << 8) +#define RT5682_I2S_M_D_2 (0x1 << 8) +#define RT5682_I2S_M_D_3 (0x2 << 8) +#define RT5682_I2S_M_D_4 (0x3 << 8) +#define RT5682_I2S_M_D_6 (0x4 << 8) +#define RT5682_I2S_M_D_8 (0x5 << 8) +#define RT5682_I2S_M_D_12 (0x6 << 8) +#define RT5682_I2S_M_D_16 (0x7 << 8) +#define RT5682_I2S_M_D_24 (0x8 << 8) +#define RT5682_I2S_M_D_32 (0x9 << 8) +#define RT5682_I2S_M_D_48 (0x10 << 8) +#define RT5682_I2S_CLK_SRC_MASK (0x7 << 4) +#define RT5682_I2S_CLK_SRC_SFT 4 +#define RT5682_I2S_CLK_SRC_MCLK (0x0 << 4) +#define RT5682_I2S_CLK_SRC_PLL1 (0x1 << 4) +#define RT5682_I2S_CLK_SRC_PLL2 (0x2 << 4) +#define RT5682_I2S_CLK_SRC_SDW (0x3 << 4) +#define RT5682_I2S_CLK_SRC_RCCLK (0x4 << 4) /* 25M */ +#define RT5682_DAC_OSR_MASK (0xf << 0) +#define RT5682_DAC_OSR_SFT 0 +#define RT5682_DAC_OSR_D_1 (0x0 << 0) +#define RT5682_DAC_OSR_D_2 (0x1 << 0) +#define RT5682_DAC_OSR_D_4 (0x2 << 0) +#define RT5682_DAC_OSR_D_6 (0x3 << 0) +#define RT5682_DAC_OSR_D_8 (0x4 << 0) +#define RT5682_DAC_OSR_D_12 (0x5 << 0) +#define RT5682_DAC_OSR_D_16 (0x6 << 0) +#define RT5682_DAC_OSR_D_24 (0x7 << 0) +#define RT5682_DAC_OSR_D_32 (0x8 << 0) +#define RT5682_DAC_OSR_D_48 (0x9 << 0) + +/* ADC/DAC Clock Control 2 (0x0074) */ +#define RT5682_I2S2_BCLK_MS2_MASK (0x1 << 11) +#define RT5682_I2S2_BCLK_MS2_SFT 11 +#define RT5682_I2S2_BCLK_MS2_32 (0x0 << 11) +#define RT5682_I2S2_BCLK_MS2_64 (0x1 << 11) + + +/* TDM control 1 (0x0079) */ +#define RT5682_TDM_TX_CH_MASK (0x3 << 12) +#define RT5682_TDM_TX_CH_2 (0x0 << 12) +#define RT5682_TDM_TX_CH_4 (0x1 << 12) +#define RT5682_TDM_TX_CH_6 (0x2 << 12) +#define RT5682_TDM_TX_CH_8 (0x3 << 12) +#define RT5682_TDM_RX_CH_MASK (0x3 << 8) +#define RT5682_TDM_RX_CH_2 (0x0 << 8) +#define RT5682_TDM_RX_CH_4 (0x1 << 8) +#define RT5682_TDM_RX_CH_6 (0x2 << 8) +#define RT5682_TDM_RX_CH_8 (0x3 << 8) +#define RT5682_TDM_ADC_LCA_MASK (0xf << 4) +#define RT5682_TDM_ADC_LCA_SFT 4 +#define RT5682_TDM_ADC_DL_SFT 0 + +/* TDM control 2 (0x007a) */ +#define RT5682_IF1_ADC1_SEL_SFT 14 +#define RT5682_IF1_ADC2_SEL_SFT 12 +#define RT5682_IF1_ADC3_SEL_SFT 10 +#define RT5682_IF1_ADC4_SEL_SFT 8 +#define RT5682_TDM_ADC_SEL_SFT 4 + +/* TDM control 3 (0x007b) */ +#define RT5682_TDM_EN (0x1 << 7) + +/* TDM/I2S control (0x007e) */ +#define RT5682_TDM_S_BP_MASK (0x1 << 15) +#define RT5682_TDM_S_BP_SFT 15 +#define RT5682_TDM_S_BP_NOR (0x0 << 15) +#define RT5682_TDM_S_BP_INV (0x1 << 15) +#define RT5682_TDM_S_LP_MASK (0x1 << 14) +#define RT5682_TDM_S_LP_SFT 14 +#define RT5682_TDM_S_LP_NOR (0x0 << 14) +#define RT5682_TDM_S_LP_INV (0x1 << 14) +#define RT5682_TDM_DF_MASK (0x7 << 11) +#define RT5682_TDM_DF_SFT 11 +#define RT5682_TDM_DF_I2S (0x0 << 11) +#define RT5682_TDM_DF_LEFT (0x1 << 11) +#define RT5682_TDM_DF_PCM_A (0x2 << 11) +#define RT5682_TDM_DF_PCM_B (0x3 << 11) +#define RT5682_TDM_DF_PCM_A_N (0x6 << 11) +#define RT5682_TDM_DF_PCM_B_N (0x7 << 11) +#define RT5682_TDM_CL_MASK (0x3 << 4) +#define RT5682_TDM_CL_16 (0x0 << 4) +#define RT5682_TDM_CL_20 (0x1 << 4) +#define RT5682_TDM_CL_24 (0x2 << 4) +#define RT5682_TDM_CL_32 (0x3 << 4) +#define RT5682_TDM_M_BP_MASK (0x1 << 2) +#define RT5682_TDM_M_BP_SFT 2 +#define RT5682_TDM_M_BP_NOR (0x0 << 2) +#define RT5682_TDM_M_BP_INV (0x1 << 2) +#define RT5682_TDM_M_LP_MASK (0x1 << 1) +#define RT5682_TDM_M_LP_SFT 1 +#define RT5682_TDM_M_LP_NOR (0x0 << 1) +#define RT5682_TDM_M_LP_INV (0x1 << 1) +#define RT5682_TDM_MS_MASK (0x1 << 0) +#define RT5682_TDM_MS_SFT 0 +#define RT5682_TDM_MS_M (0x0 << 0) +#define RT5682_TDM_MS_S (0x1 << 0) + +/* Global Clock Control (0x0080) */ +#define RT5682_SCLK_SRC_MASK (0x7 << 13) +#define RT5682_SCLK_SRC_SFT 13 +#define RT5682_SCLK_SRC_MCLK (0x0 << 13) +#define RT5682_SCLK_SRC_PLL1 (0x1 << 13) +#define RT5682_SCLK_SRC_PLL2 (0x2 << 13) +#define RT5682_SCLK_SRC_SDW (0x3 << 13) +#define RT5682_SCLK_SRC_RCCLK (0x4 << 13) +#define RT5682_PLL2_SRC_MASK (0x3 << 10) +#define RT5682_PLL2_SRC_SFT 10 +#define RT5682_PLL2_SRC_MCLK (0x0 << 10) +#define RT5682_PLL2_SRC_BCLK1 (0x1 << 10) +#define RT5682_PLL2_SRC_SDW (0x2 << 10) +#define RT5682_PLL2_SRC_RC (0x3 << 10) +#define RT5682_PLL1_SRC_MASK (0x3 << 8) +#define RT5682_PLL1_SRC_SFT 8 +#define RT5682_PLL1_SRC_MCLK (0x0 << 8) +#define RT5682_PLL1_SRC_BCLK1 (0x1 << 8) +#define RT5682_PLL1_SRC_SDW (0x2 << 8) +#define RT5682_PLL1_SRC_RC (0x3 << 8) + + + +#define RT5682_PLL_INP_MAX 40000000 +#define RT5682_PLL_INP_MIN 256000 +/* PLL M/N/K Code Control 1 (0x0081) */ +#define RT5682_PLL_N_MAX 0x001ff +#define RT5682_PLL_N_MASK (RT5682_PLL_N_MAX << 7) +#define RT5682_PLL_N_SFT 7 +#define RT5682_PLL_K_MAX 0x001f +#define RT5682_PLL_K_MASK (RT5682_PLL_K_MAX) +#define RT5682_PLL_K_SFT 0 + +/* PLL M/N/K Code Control 2 (0x0082) */ +#define RT5682_PLL_M_MAX 0x00f +#define RT5682_PLL_M_MASK (RT5682_PLL_M_MAX << 12) +#define RT5682_PLL_M_SFT 12 +#define RT5682_PLL_M_BP (0x1 << 11) +#define RT5682_PLL_M_BP_SFT 11 +#define RT5682_PLL_K_BP (0x1 << 10) +#define RT5682_PLL_K_BP_SFT 10 +#define RT5682_PLL_RST (0x1 << 1) + +/* PLL tracking mode 1 (0x0083) */ +#define RT5682_DA_ASRC_MASK (0x1 << 13) +#define RT5682_DA_ASRC_SFT 13 +#define RT5682_DAC_STO1_ASRC_MASK (0x1 << 12) +#define RT5682_DAC_STO1_ASRC_SFT 12 +#define RT5682_AD_ASRC_MASK (0x1 << 8) +#define RT5682_AD_ASRC_SFT 8 +#define RT5682_AD_ASRC_SEL_MASK (0x1 << 4) +#define RT5682_AD_ASRC_SEL_SFT 4 +#define RT5682_DMIC_ASRC_MASK (0x1 << 3) +#define RT5682_DMIC_ASRC_SFT 3 +#define RT5682_ADC_STO1_ASRC_MASK (0x1 << 2) +#define RT5682_ADC_STO1_ASRC_SFT 2 +#define RT5682_DA_ASRC_SEL_MASK (0x1 << 0) +#define RT5682_DA_ASRC_SEL_SFT 0 + +/* PLL tracking mode 2 3 (0x0084)(0x0085)*/ +#define RT5682_FILTER_CLK_SEL_MASK (0x7 << 12) +#define RT5682_FILTER_CLK_SEL_SFT 12 +#define RT5682_FILTER_CLK_DIV_MASK (0xf << 8) +#define RT5682_FILTER_CLK_DIV_SFT 8 + +/* ASRC Control 4 (0x0086) */ +#define RT5682_ASRCIN_FTK_N1_MASK (0x3 << 14) +#define RT5682_ASRCIN_FTK_N1_SFT 14 +#define RT5682_ASRCIN_FTK_N2_MASK (0x3 << 12) +#define RT5682_ASRCIN_FTK_N2_SFT 12 +#define RT5682_ASRCIN_FTK_M1_MASK (0x7 << 8) +#define RT5682_ASRCIN_FTK_M1_SFT 8 +#define RT5682_ASRCIN_FTK_M2_MASK (0x7 << 4) +#define RT5682_ASRCIN_FTK_M2_SFT 4 + +/* SoundWire reference clk (0x008d) */ +#define RT5682_PLL2_OUT_MASK (0x1 << 8) +#define RT5682_PLL2_OUT_98M (0x0 << 8) +#define RT5682_PLL2_OUT_49M (0x1 << 8) +#define RT5682_SDW_REF_2_MASK (0xf << 4) +#define RT5682_SDW_REF_2_SFT 4 +#define RT5682_SDW_REF_2_48K (0x0 << 4) +#define RT5682_SDW_REF_2_96K (0x1 << 4) +#define RT5682_SDW_REF_2_192K (0x2 << 4) +#define RT5682_SDW_REF_2_32K (0x3 << 4) +#define RT5682_SDW_REF_2_24K (0x4 << 4) +#define RT5682_SDW_REF_2_16K (0x5 << 4) +#define RT5682_SDW_REF_2_12K (0x6 << 4) +#define RT5682_SDW_REF_2_8K (0x7 << 4) +#define RT5682_SDW_REF_2_44K (0x8 << 4) +#define RT5682_SDW_REF_2_88K (0x9 << 4) +#define RT5682_SDW_REF_2_176K (0xa << 4) +#define RT5682_SDW_REF_2_353K (0xb << 4) +#define RT5682_SDW_REF_2_22K (0xc << 4) +#define RT5682_SDW_REF_2_384K (0xd << 4) +#define RT5682_SDW_REF_2_11K (0xe << 4) +#define RT5682_SDW_REF_1_MASK (0xf << 0) +#define RT5682_SDW_REF_1_SFT 0 +#define RT5682_SDW_REF_1_48K (0x0 << 0) +#define RT5682_SDW_REF_1_96K (0x1 << 0) +#define RT5682_SDW_REF_1_192K (0x2 << 0) +#define RT5682_SDW_REF_1_32K (0x3 << 0) +#define RT5682_SDW_REF_1_24K (0x4 << 0) +#define RT5682_SDW_REF_1_16K (0x5 << 0) +#define RT5682_SDW_REF_1_12K (0x6 << 0) +#define RT5682_SDW_REF_1_8K (0x7 << 0) +#define RT5682_SDW_REF_1_44K (0x8 << 0) +#define RT5682_SDW_REF_1_88K (0x9 << 0) +#define RT5682_SDW_REF_1_176K (0xa << 0) +#define RT5682_SDW_REF_1_353K (0xb << 0) +#define RT5682_SDW_REF_1_22K (0xc << 0) +#define RT5682_SDW_REF_1_384K (0xd << 0) +#define RT5682_SDW_REF_1_11K (0xe << 0) + +/* Depop Mode Control 1 (0x008e) */ +#define RT5682_PUMP_EN (0x1 << 3) +#define RT5682_PUMP_EN_SFT 3 +#define RT5682_CAPLESS_EN (0x1 << 0) +#define RT5682_CAPLESS_EN_SFT 0 + +/* Depop Mode Control 2 (0x8f) */ +#define RT5682_RAMP_MASK (0x1 << 12) +#define RT5682_RAMP_SFT 12 +#define RT5682_RAMP_DIS (0x0 << 12) +#define RT5682_RAMP_EN (0x1 << 12) +#define RT5682_BPS_MASK (0x1 << 11) +#define RT5682_BPS_SFT 11 +#define RT5682_BPS_DIS (0x0 << 11) +#define RT5682_BPS_EN (0x1 << 11) +#define RT5682_FAST_UPDN_MASK (0x1 << 10) +#define RT5682_FAST_UPDN_SFT 10 +#define RT5682_FAST_UPDN_DIS (0x0 << 10) +#define RT5682_FAST_UPDN_EN (0x1 << 10) +#define RT5682_VLO_MASK (0x1 << 7) +#define RT5682_VLO_SFT 7 +#define RT5682_VLO_3V (0x0 << 7) +#define RT5682_VLO_33V (0x1 << 7) + +/* HPOUT charge pump 1 (0x0091) */ +#define RT5682_OSW_L_MASK (0x1 << 11) +#define RT5682_OSW_L_SFT 11 +#define RT5682_OSW_L_DIS (0x0 << 11) +#define RT5682_OSW_L_EN (0x1 << 11) +#define RT5682_OSW_R_MASK (0x1 << 10) +#define RT5682_OSW_R_SFT 10 +#define RT5682_OSW_R_DIS (0x0 << 10) +#define RT5682_OSW_R_EN (0x1 << 10) +#define RT5682_PM_HP_MASK (0x3 << 8) +#define RT5682_PM_HP_SFT 8 +#define RT5682_PM_HP_LV (0x0 << 8) +#define RT5682_PM_HP_MV (0x1 << 8) +#define RT5682_PM_HP_HV (0x2 << 8) +#define RT5682_IB_HP_MASK (0x3 << 6) +#define RT5682_IB_HP_SFT 6 +#define RT5682_IB_HP_125IL (0x0 << 6) +#define RT5682_IB_HP_25IL (0x1 << 6) +#define RT5682_IB_HP_5IL (0x2 << 6) +#define RT5682_IB_HP_1IL (0x3 << 6) + +/* Micbias Control1 (0x93) */ +#define RT5682_MIC1_OV_MASK (0x3 << 14) +#define RT5682_MIC1_OV_SFT 14 +#define RT5682_MIC1_OV_2V7 (0x0 << 14) +#define RT5682_MIC1_OV_2V4 (0x1 << 14) +#define RT5682_MIC1_OV_2V25 (0x3 << 14) +#define RT5682_MIC1_OV_1V8 (0x4 << 14) +#define RT5682_MIC1_CLK_MASK (0x1 << 13) +#define RT5682_MIC1_CLK_SFT 13 +#define RT5682_MIC1_CLK_DIS (0x0 << 13) +#define RT5682_MIC1_CLK_EN (0x1 << 13) +#define RT5682_MIC1_OVCD_MASK (0x1 << 12) +#define RT5682_MIC1_OVCD_SFT 12 +#define RT5682_MIC1_OVCD_DIS (0x0 << 12) +#define RT5682_MIC1_OVCD_EN (0x1 << 12) +#define RT5682_MIC1_OVTH_MASK (0x3 << 10) +#define RT5682_MIC1_OVTH_SFT 10 +#define RT5682_MIC1_OVTH_768UA (0x0 << 10) +#define RT5682_MIC1_OVTH_960UA (0x1 << 10) +#define RT5682_MIC1_OVTH_1152UA (0x2 << 10) +#define RT5682_MIC1_OVTH_1960UA (0x3 << 10) +#define RT5682_MIC2_OV_MASK (0x3 << 8) +#define RT5682_MIC2_OV_SFT 8 +#define RT5682_MIC2_OV_2V7 (0x0 << 8) +#define RT5682_MIC2_OV_2V4 (0x1 << 8) +#define RT5682_MIC2_OV_2V25 (0x3 << 8) +#define RT5682_MIC2_OV_1V8 (0x4 << 8) +#define RT5682_MIC2_CLK_MASK (0x1 << 7) +#define RT5682_MIC2_CLK_SFT 7 +#define RT5682_MIC2_CLK_DIS (0x0 << 7) +#define RT5682_MIC2_CLK_EN (0x1 << 7) +#define RT5682_MIC2_OVTH_MASK (0x3 << 4) +#define RT5682_MIC2_OVTH_SFT 4 +#define RT5682_MIC2_OVTH_768UA (0x0 << 4) +#define RT5682_MIC2_OVTH_960UA (0x1 << 4) +#define RT5682_MIC2_OVTH_1152UA (0x2 << 4) +#define RT5682_MIC2_OVTH_1960UA (0x3 << 4) +#define RT5682_PWR_MB_MASK (0x1 << 3) +#define RT5682_PWR_MB_SFT 3 +#define RT5682_PWR_MB_PD (0x0 << 3) +#define RT5682_PWR_MB_PU (0x1 << 3) + +/* Micbias Control2 (0x0094) */ +#define RT5682_PWR_CLK25M_MASK (0x1 << 9) +#define RT5682_PWR_CLK25M_SFT 9 +#define RT5682_PWR_CLK25M_PD (0x0 << 9) +#define RT5682_PWR_CLK25M_PU (0x1 << 9) +#define RT5682_PWR_CLK1M_MASK (0x1 << 8) +#define RT5682_PWR_CLK1M_SFT 8 +#define RT5682_PWR_CLK1M_PD (0x0 << 8) +#define RT5682_PWR_CLK1M_PU (0x1 << 8) + +/* RC Clock Control (0x009f) */ +#define RT5682_POW_IRQ (0x1 << 15) +#define RT5682_POW_JDH (0x1 << 14) +#define RT5682_POW_JDL (0x1 << 13) +#define RT5682_POW_ANA (0x1 << 12) + +/* I2S Master Mode Clock Control 1 (0x00a0) */ +#define RT5682_CLK_SRC_MCLK (0x0) +#define RT5682_CLK_SRC_PLL1 (0x1) +#define RT5682_CLK_SRC_PLL2 (0x2) +#define RT5682_CLK_SRC_SDW (0x3) +#define RT5682_CLK_SRC_RCCLK (0x4) +#define RT5682_I2S_PD_1 (0x0) +#define RT5682_I2S_PD_2 (0x1) +#define RT5682_I2S_PD_3 (0x2) +#define RT5682_I2S_PD_4 (0x3) +#define RT5682_I2S_PD_6 (0x4) +#define RT5682_I2S_PD_8 (0x5) +#define RT5682_I2S_PD_12 (0x6) +#define RT5682_I2S_PD_16 (0x7) +#define RT5682_I2S_PD_24 (0x8) +#define RT5682_I2S_PD_32 (0x9) +#define RT5682_I2S_PD_48 (0xa) +#define RT5682_I2S2_SRC_MASK (0x3 << 4) +#define RT5682_I2S2_SRC_SFT 4 +#define RT5682_I2S2_M_PD_MASK (0xf << 0) +#define RT5682_I2S2_M_PD_SFT 0 + +/* IRQ Control 1 (0x00b6) */ +#define RT5682_JD1_PULSE_EN_MASK (0x1 << 10) +#define RT5682_JD1_PULSE_EN_SFT 10 +#define RT5682_JD1_PULSE_DIS (0x0 << 10) +#define RT5682_JD1_PULSE_EN (0x1 << 10) + +/* IRQ Control 2 (0x00b7) */ +#define RT5682_JD1_EN_MASK (0x1 << 15) +#define RT5682_JD1_EN_SFT 15 +#define RT5682_JD1_DIS (0x0 << 15) +#define RT5682_JD1_EN (0x1 << 15) +#define RT5682_JD1_POL_MASK (0x1 << 13) +#define RT5682_JD1_POL_NOR (0x0 << 13) +#define RT5682_JD1_POL_INV (0x1 << 13) + +/* IRQ Control 3 (0x00b8) */ +#define RT5682_IL_IRQ_MASK (0x1 << 7) +#define RT5682_IL_IRQ_DIS (0x0 << 7) +#define RT5682_IL_IRQ_EN (0x1 << 7) + +/* GPIO Control 1 (0x00c0) */ +#define RT5682_GP1_PIN_MASK (0x3 << 14) +#define RT5682_GP1_PIN_SFT 14 +#define RT5682_GP1_PIN_GPIO1 (0x0 << 14) +#define RT5682_GP1_PIN_IRQ (0x1 << 14) +#define RT5682_GP1_PIN_DMIC_CLK (0x2 << 14) +#define RT5682_GP2_PIN_MASK (0x3 << 12) +#define RT5682_GP2_PIN_SFT 12 +#define RT5682_GP2_PIN_GPIO2 (0x0 << 12) +#define RT5682_GP2_PIN_LRCK2 (0x1 << 12) +#define RT5682_GP2_PIN_DMIC_SDA (0x2 << 12) +#define RT5682_GP3_PIN_MASK (0x3 << 10) +#define RT5682_GP3_PIN_SFT 10 +#define RT5682_GP3_PIN_GPIO3 (0x0 << 10) +#define RT5682_GP3_PIN_BCLK2 (0x1 << 10) +#define RT5682_GP3_PIN_DMIC_CLK (0x2 << 10) +#define RT5682_GP4_PIN_MASK (0x3 << 8) +#define RT5682_GP4_PIN_SFT 8 +#define RT5682_GP4_PIN_GPIO4 (0x0 << 8) +#define RT5682_GP4_PIN_ADCDAT1 (0x1 << 8) +#define RT5682_GP4_PIN_DMIC_CLK (0x2 << 8) +#define RT5682_GP4_PIN_ADCDAT2 (0x3 << 8) +#define RT5682_GP5_PIN_MASK (0x3 << 6) +#define RT5682_GP5_PIN_SFT 6 +#define RT5682_GP5_PIN_GPIO5 (0x0 << 6) +#define RT5682_GP5_PIN_DACDAT1 (0x1 << 6) +#define RT5682_GP5_PIN_DMIC_SDA (0x2 << 6) +#define RT5682_GP6_PIN_MASK (0x1 << 5) +#define RT5682_GP6_PIN_SFT 5 +#define RT5682_GP6_PIN_GPIO6 (0x0 << 5) +#define RT5682_GP6_PIN_LRCK1 (0x1 << 5) + +/* GPIO Control 2 (0x00c1)*/ +#define RT5682_GP1_PF_MASK (0x1 << 15) +#define RT5682_GP1_PF_IN (0x0 << 15) +#define RT5682_GP1_PF_OUT (0x1 << 15) +#define RT5682_GP1_OUT_MASK (0x1 << 14) +#define RT5682_GP1_OUT_L (0x0 << 14) +#define RT5682_GP1_OUT_H (0x1 << 14) +#define RT5682_GP2_PF_MASK (0x1 << 13) +#define RT5682_GP2_PF_IN (0x0 << 13) +#define RT5682_GP2_PF_OUT (0x1 << 13) +#define RT5682_GP2_OUT_MASK (0x1 << 12) +#define RT5682_GP2_OUT_L (0x0 << 12) +#define RT5682_GP2_OUT_H (0x1 << 12) +#define RT5682_GP3_PF_MASK (0x1 << 11) +#define RT5682_GP3_PF_IN (0x0 << 11) +#define RT5682_GP3_PF_OUT (0x1 << 11) +#define RT5682_GP3_OUT_MASK (0x1 << 10) +#define RT5682_GP3_OUT_L (0x0 << 10) +#define RT5682_GP3_OUT_H (0x1 << 10) +#define RT5682_GP4_PF_MASK (0x1 << 9) +#define RT5682_GP4_PF_IN (0x0 << 9) +#define RT5682_GP4_PF_OUT (0x1 << 9) +#define RT5682_GP4_OUT_MASK (0x1 << 8) +#define RT5682_GP4_OUT_L (0x0 << 8) +#define RT5682_GP4_OUT_H (0x1 << 8) +#define RT5682_GP5_PF_MASK (0x1 << 7) +#define RT5682_GP5_PF_IN (0x0 << 7) +#define RT5682_GP5_PF_OUT (0x1 << 7) +#define RT5682_GP5_OUT_MASK (0x1 << 6) +#define RT5682_GP5_OUT_L (0x0 << 6) +#define RT5682_GP5_OUT_H (0x1 << 6) +#define RT5682_GP6_PF_MASK (0x1 << 5) +#define RT5682_GP6_PF_IN (0x0 << 5) +#define RT5682_GP6_PF_OUT (0x1 << 5) +#define RT5682_GP6_OUT_MASK (0x1 << 4) +#define RT5682_GP6_OUT_L (0x0 << 4) +#define RT5682_GP6_OUT_H (0x1 << 4) + + +/* GPIO Status (0x00c2) */ +#define RT5682_GP6_STA (0x1 << 6) +#define RT5682_GP5_STA (0x1 << 5) +#define RT5682_GP4_STA (0x1 << 4) +#define RT5682_GP3_STA (0x1 << 3) +#define RT5682_GP2_STA (0x1 << 2) +#define RT5682_GP1_STA (0x1 << 1) + +/* Soft volume and zero cross control 1 (0x00d9) */ +#define RT5682_SV_MASK (0x1 << 15) +#define RT5682_SV_SFT 15 +#define RT5682_SV_DIS (0x0 << 15) +#define RT5682_SV_EN (0x1 << 15) +#define RT5682_ZCD_MASK (0x1 << 10) +#define RT5682_ZCD_SFT 10 +#define RT5682_ZCD_PD (0x0 << 10) +#define RT5682_ZCD_PU (0x1 << 10) +#define RT5682_SV_DLY_MASK (0xf) +#define RT5682_SV_DLY_SFT 0 + +/* Soft volume and zero cross control 2 (0x00da) */ +#define RT5682_ZCD_BST1_CBJ_MASK (0x1 << 7) +#define RT5682_ZCD_BST1_CBJ_SFT 7 +#define RT5682_ZCD_BST1_CBJ_DIS (0x0 << 7) +#define RT5682_ZCD_BST1_CBJ_EN (0x1 << 7) +#define RT5682_ZCD_RECMIX_MASK (0x1) +#define RT5682_ZCD_RECMIX_SFT 0 +#define RT5682_ZCD_RECMIX_DIS (0x0) +#define RT5682_ZCD_RECMIX_EN (0x1) + +/* 4 Button Inline Command Control 2 (0x00e3) */ +#define RT5682_4BTN_IL_MASK (0x1 << 15) +#define RT5682_4BTN_IL_EN (0x1 << 15) +#define RT5682_4BTN_IL_DIS (0x0 << 15) +#define RT5682_4BTN_IL_RST_MASK (0x1 << 14) +#define RT5682_4BTN_IL_NOR (0x1 << 14) +#define RT5682_4BTN_IL_RST (0x0 << 14) + +/* Analog JD Control (0x00f0) */ +#define RT5682_JDH_RS_MASK (0x1 << 4) +#define RT5682_JDH_NO_PLUG (0x1 << 4) +#define RT5682_JDH_PLUG (0x0 << 4) + +/* Chopper and Clock control for DAC (0x013a)*/ +#define RT5682_CKXEN_DAC1_MASK (0x1 << 13) +#define RT5682_CKXEN_DAC1_SFT 13 +#define RT5682_CKGEN_DAC1_MASK (0x1 << 12) +#define RT5682_CKGEN_DAC1_SFT 12 + +/* Chopper and Clock control for ADC (0x013b)*/ +#define RT5682_CKXEN_ADC1_MASK (0x1 << 13) +#define RT5682_CKXEN_ADC1_SFT 13 +#define RT5682_CKGEN_ADC1_MASK (0x1 << 12) +#define RT5682_CKGEN_ADC1_SFT 12 + +/* Volume test (0x013f)*/ +#define RT5682_SEL_CLK_VOL_MASK (0x1 << 15) +#define RT5682_SEL_CLK_VOL_EN (0x1 << 15) +#define RT5682_SEL_CLK_VOL_DIS (0x0 << 15) + +/* Test Mode Control 1 (0x0145) */ +#define RT5682_AD2DA_LB_MASK (0x1 << 10) +#define RT5682_AD2DA_LB_SFT 10 + +/* Stereo Noise Gate Control 1 (0x0160) */ +#define RT5682_NG2_EN_MASK (0x1 << 15) +#define RT5682_NG2_EN (0x1 << 15) +#define RT5682_NG2_DIS (0x0 << 15) + +/* Stereo1 DAC Silence Detection Control (0x0190) */ +#define RT5682_DEB_STO_DAC_MASK (0x7 << 4) +#define RT5682_DEB_80_MS (0x0 << 4) + +/* SAR ADC Inline Command Control 1 (0x0210) */ +#define RT5682_SAR_BUTT_DET_MASK (0x1 << 15) +#define RT5682_SAR_BUTT_DET_EN (0x1 << 15) +#define RT5682_SAR_BUTT_DET_DIS (0x0 << 15) +#define RT5682_SAR_BUTDET_MODE_MASK (0x1 << 14) +#define RT5682_SAR_BUTDET_POW_SAV (0x1 << 14) +#define RT5682_SAR_BUTDET_POW_NORM (0x0 << 14) +#define RT5682_SAR_BUTDET_RST_MASK (0x1 << 13) +#define RT5682_SAR_BUTDET_RST_NORMAL (0x1 << 13) +#define RT5682_SAR_BUTDET_RST (0x0 << 13) +#define RT5682_SAR_POW_MASK (0x1 << 12) +#define RT5682_SAR_POW_EN (0x1 << 12) +#define RT5682_SAR_POW_DIS (0x0 << 12) +#define RT5682_SAR_RST_MASK (0x1 << 11) +#define RT5682_SAR_RST_NORMAL (0x1 << 11) +#define RT5682_SAR_RST (0x0 << 11) +#define RT5682_SAR_BYPASS_MASK (0x1 << 10) +#define RT5682_SAR_BYPASS_EN (0x1 << 10) +#define RT5682_SAR_BYPASS_DIS (0x0 << 10) +#define RT5682_SAR_SEL_MB1_MASK (0x1 << 9) +#define RT5682_SAR_SEL_MB1_SEL (0x1 << 9) +#define RT5682_SAR_SEL_MB1_NOSEL (0x0 << 9) +#define RT5682_SAR_SEL_MB2_MASK (0x1 << 8) +#define RT5682_SAR_SEL_MB2_SEL (0x1 << 8) +#define RT5682_SAR_SEL_MB2_NOSEL (0x0 << 8) +#define RT5682_SAR_SEL_MODE_MASK (0x1 << 7) +#define RT5682_SAR_SEL_MODE_CMP (0x1 << 7) +#define RT5682_SAR_SEL_MODE_ADC (0x0 << 7) +#define RT5682_SAR_SEL_MB1_MB2_MASK (0x1 << 5) +#define RT5682_SAR_SEL_MB1_MB2_AUTO (0x1 << 5) +#define RT5682_SAR_SEL_MB1_MB2_MANU (0x0 << 5) +#define RT5682_SAR_SEL_SIGNAL_MASK (0x1 << 4) +#define RT5682_SAR_SEL_SIGNAL_AUTO (0x1 << 4) +#define RT5682_SAR_SEL_SIGNAL_MANU (0x0 << 4) + +/* SAR ADC Inline Command Control 13 (0x021c) */ +#define RT5682_SAR_SOUR_MASK (0x3f) +#define RT5682_SAR_SOUR_BTN (0x3f) +#define RT5682_SAR_SOUR_TYPE (0x0) + + +/* System Clock Source */ +enum { + RT5682_SCLK_S_MCLK, + RT5682_SCLK_S_PLL1, + RT5682_SCLK_S_PLL2, + RT5682_SCLK_S_RCCLK, +}; + +/* PLL Source */ +enum { + RT5682_PLL1_S_MCLK, + RT5682_PLL1_S_BCLK1, + RT5682_PLL1_S_RCCLK, +}; + +enum { + RT5682_AIF1, + RT5682_AIF2, + RT5682_AIFS +}; + +/* filter mask */ +enum { + RT5682_DA_STEREO1_FILTER = 0x1, + RT5682_AD_STEREO1_FILTER = (0x1 << 1), +}; + +enum { + RT5682_CLK_SEL_SYS, + RT5682_CLK_SEL_I2S1_ASRC, + RT5682_CLK_SEL_I2S2_ASRC, +}; + +int rt5682_sel_asrc_clk_src(struct snd_soc_component *component, + unsigned int filter_mask, unsigned int clk_src); + +#endif /* __RT5682_H__ */ diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 000000000..17255e968 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1806 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// sgtl5000.c -- SGTL5000 ALSA SoC Audio driver +// +// Copyright 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sgtl5000.h" + +#define SGTL5000_DAP_REG_OFFSET 0x0100 +#define SGTL5000_MAX_REG_OFFSET 0x013A + +/* Delay for the VAG ramp up */ +#define SGTL5000_VAG_POWERUP_DELAY 500 /* ms */ +/* Delay for the VAG ramp down */ +#define SGTL5000_VAG_POWERDOWN_DELAY 500 /* ms */ + +#define SGTL5000_OUTPUTS_MUTE (SGTL5000_HP_MUTE | SGTL5000_LINE_OUT_MUTE) + +/* default value of sgtl5000 registers */ +static const struct reg_default sgtl5000_reg_defaults[] = { + { SGTL5000_CHIP_DIG_POWER, 0x0000 }, + { SGTL5000_CHIP_I2S_CTRL, 0x0010 }, + { SGTL5000_CHIP_SSS_CTRL, 0x0010 }, + { SGTL5000_CHIP_ADCDAC_CTRL, 0x020c }, + { SGTL5000_CHIP_DAC_VOL, 0x3c3c }, + { SGTL5000_CHIP_PAD_STRENGTH, 0x015f }, + { SGTL5000_CHIP_ANA_ADC_CTRL, 0x0000 }, + { SGTL5000_CHIP_ANA_HP_CTRL, 0x1818 }, + { SGTL5000_CHIP_ANA_CTRL, 0x0111 }, + { SGTL5000_CHIP_REF_CTRL, 0x0000 }, + { SGTL5000_CHIP_MIC_CTRL, 0x0000 }, + { SGTL5000_CHIP_LINE_OUT_CTRL, 0x0000 }, + { SGTL5000_CHIP_LINE_OUT_VOL, 0x0404 }, + { SGTL5000_CHIP_PLL_CTRL, 0x5000 }, + { SGTL5000_CHIP_CLK_TOP_CTRL, 0x0000 }, + { SGTL5000_CHIP_ANA_STATUS, 0x0000 }, + { SGTL5000_CHIP_SHORT_CTRL, 0x0000 }, + { SGTL5000_CHIP_ANA_TEST2, 0x0000 }, + { SGTL5000_DAP_CTRL, 0x0000 }, + { SGTL5000_DAP_PEQ, 0x0000 }, + { SGTL5000_DAP_BASS_ENHANCE, 0x0040 }, + { SGTL5000_DAP_BASS_ENHANCE_CTRL, 0x051f }, + { SGTL5000_DAP_AUDIO_EQ, 0x0000 }, + { SGTL5000_DAP_SURROUND, 0x0040 }, + { SGTL5000_DAP_EQ_BASS_BAND0, 0x002f }, + { SGTL5000_DAP_EQ_BASS_BAND1, 0x002f }, + { SGTL5000_DAP_EQ_BASS_BAND2, 0x002f }, + { SGTL5000_DAP_EQ_BASS_BAND3, 0x002f }, + { SGTL5000_DAP_EQ_BASS_BAND4, 0x002f }, + { SGTL5000_DAP_MAIN_CHAN, 0x8000 }, + { SGTL5000_DAP_MIX_CHAN, 0x0000 }, + { SGTL5000_DAP_AVC_CTRL, 0x5100 }, + { SGTL5000_DAP_AVC_THRESHOLD, 0x1473 }, + { SGTL5000_DAP_AVC_ATTACK, 0x0028 }, + { SGTL5000_DAP_AVC_DECAY, 0x0050 }, +}; + +/* AVC: Threshold dB -> register: pre-calculated values */ +static const u16 avc_thr_db2reg[97] = { + 0x5168, 0x488E, 0x40AA, 0x39A1, 0x335D, 0x2DC7, 0x28CC, 0x245D, 0x2068, + 0x1CE2, 0x19BE, 0x16F1, 0x1472, 0x1239, 0x103E, 0x0E7A, 0x0CE6, 0x0B7F, + 0x0A3F, 0x0922, 0x0824, 0x0741, 0x0677, 0x05C3, 0x0522, 0x0493, 0x0414, + 0x03A2, 0x033D, 0x02E3, 0x0293, 0x024B, 0x020B, 0x01D2, 0x019F, 0x0172, + 0x014A, 0x0126, 0x0106, 0x00E9, 0x00D0, 0x00B9, 0x00A5, 0x0093, 0x0083, + 0x0075, 0x0068, 0x005D, 0x0052, 0x0049, 0x0041, 0x003A, 0x0034, 0x002E, + 0x0029, 0x0025, 0x0021, 0x001D, 0x001A, 0x0017, 0x0014, 0x0012, 0x0010, + 0x000E, 0x000D, 0x000B, 0x000A, 0x0009, 0x0008, 0x0007, 0x0006, 0x0005, + 0x0005, 0x0004, 0x0004, 0x0003, 0x0003, 0x0002, 0x0002, 0x0002, 0x0002, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}; + +/* regulator supplies for sgtl5000, VDDD is an optional external supply */ +enum sgtl5000_regulator_supplies { + VDDA, + VDDIO, + VDDD, + SGTL5000_SUPPLY_NUM +}; + +/* vddd is optional supply */ +static const char *supply_names[SGTL5000_SUPPLY_NUM] = { + "VDDA", + "VDDIO", + "VDDD" +}; + +#define LDO_VOLTAGE 1200000 +#define LINREG_VDDD ((1600 - LDO_VOLTAGE / 1000) / 50) + +enum sgtl5000_micbias_resistor { + SGTL5000_MICBIAS_OFF = 0, + SGTL5000_MICBIAS_2K = 2, + SGTL5000_MICBIAS_4K = 4, + SGTL5000_MICBIAS_8K = 8, +}; + +enum { + I2S_LRCLK_STRENGTH_DISABLE, + I2S_LRCLK_STRENGTH_LOW, + I2S_LRCLK_STRENGTH_MEDIUM, + I2S_LRCLK_STRENGTH_HIGH, +}; + +enum { + HP_POWER_EVENT, + DAC_POWER_EVENT, + ADC_POWER_EVENT, + LAST_POWER_EVENT = ADC_POWER_EVENT +}; + +/* sgtl5000 private structure in codec */ +struct sgtl5000_priv { + int sysclk; /* sysclk rate */ + int master; /* i2s master or not */ + int fmt; /* i2s data format */ + struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM]; + int num_supplies; + struct regmap *regmap; + struct clk *mclk; + int revision; + u8 micbias_resistor; + u8 micbias_voltage; + u8 lrclk_strength; + u16 mute_state[LAST_POWER_EVENT + 1]; +}; + +static inline int hp_sel_input(struct snd_soc_component *component) +{ + return (snd_soc_component_read32(component, SGTL5000_CHIP_ANA_CTRL) & + SGTL5000_HP_SEL_MASK) >> SGTL5000_HP_SEL_SHIFT; +} + +static inline u16 mute_output(struct snd_soc_component *component, + u16 mute_mask) +{ + u16 mute_reg = snd_soc_component_read32(component, + SGTL5000_CHIP_ANA_CTRL); + + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_CTRL, + mute_mask, mute_mask); + return mute_reg; +} + +static inline void restore_output(struct snd_soc_component *component, + u16 mute_mask, u16 mute_reg) +{ + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_CTRL, + mute_mask, mute_reg); +} + +static void vag_power_on(struct snd_soc_component *component, u32 source) +{ + if (snd_soc_component_read32(component, SGTL5000_CHIP_ANA_POWER) & + SGTL5000_VAG_POWERUP) + return; + + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); + + /* When VAG powering on to get local loop from Line-In, the sleep + * is required to avoid loud pop. + */ + if (hp_sel_input(component) == SGTL5000_HP_SEL_LINE_IN && + source == HP_POWER_EVENT) + msleep(SGTL5000_VAG_POWERUP_DELAY); +} + +static int vag_power_consumers(struct snd_soc_component *component, + u16 ana_pwr_reg, u32 source) +{ + int consumers = 0; + + /* count dac/adc consumers unconditional */ + if (ana_pwr_reg & SGTL5000_DAC_POWERUP) + consumers++; + if (ana_pwr_reg & SGTL5000_ADC_POWERUP) + consumers++; + + /* + * If the event comes from HP and Line-In is selected, + * current action is 'DAC to be powered down'. + * As HP_POWERUP is not set when HP muxed to line-in, + * we need to keep VAG power ON. + */ + if (source == HP_POWER_EVENT) { + if (hp_sel_input(component) == SGTL5000_HP_SEL_LINE_IN) + consumers++; + } else { + if (ana_pwr_reg & SGTL5000_HP_POWERUP) + consumers++; + } + + return consumers; +} + +static void vag_power_off(struct snd_soc_component *component, u32 source) +{ + u16 ana_pwr = snd_soc_component_read32(component, + SGTL5000_CHIP_ANA_POWER); + + if (!(ana_pwr & SGTL5000_VAG_POWERUP)) + return; + + /* + * This function calls when any of VAG power consumers is disappearing. + * Thus, if there is more than one consumer at the moment, as minimum + * one consumer will definitely stay after the end of the current + * event. + * Don't clear VAG_POWERUP if 2 or more consumers of VAG present: + * - LINE_IN (for HP events) / HP (for DAC/ADC events) + * - DAC + * - ADC + * (the current consumer is disappearing right now) + */ + if (vag_power_consumers(component, ana_pwr, source) >= 2) + return; + + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, 0); + /* In power down case, we need wait 400-1000 ms + * when VAG fully ramped down. + * As longer we wait, as smaller pop we've got. + */ + msleep(SGTL5000_VAG_POWERDOWN_DELAY); +} + +/* + * mic_bias power on/off share the same register bits with + * output impedance of mic bias, when power on mic bias, we + * need reclaim it to impedance value. + * 0x0 = Powered off + * 0x1 = 2Kohm + * 0x2 = 4Kohm + * 0x3 = 8Kohm + */ +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct sgtl5000_priv *sgtl5000 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* change mic bias resistor */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, + sgtl5000->micbias_resistor << SGTL5000_BIAS_R_SHIFT); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, 0); + break; + } + return 0; +} + +static int vag_and_mute_control(struct snd_soc_component *component, + int event, int event_source) +{ + static const u16 mute_mask[] = { + /* + * Mask for HP_POWER_EVENT. + * Muxing Headphones have to be wrapped with mute/unmute + * headphones only. + */ + SGTL5000_HP_MUTE, + /* + * Masks for DAC_POWER_EVENT/ADC_POWER_EVENT. + * Muxing DAC or ADC block have to wrapped with mute/unmute + * both headphones and line-out. + */ + SGTL5000_OUTPUTS_MUTE, + SGTL5000_OUTPUTS_MUTE + }; + + struct sgtl5000_priv *sgtl5000 = + snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + sgtl5000->mute_state[event_source] = + mute_output(component, mute_mask[event_source]); + break; + case SND_SOC_DAPM_POST_PMU: + vag_power_on(component, event_source); + restore_output(component, mute_mask[event_source], + sgtl5000->mute_state[event_source]); + break; + case SND_SOC_DAPM_PRE_PMD: + sgtl5000->mute_state[event_source] = + mute_output(component, mute_mask[event_source]); + vag_power_off(component, event_source); + break; + case SND_SOC_DAPM_POST_PMD: + restore_output(component, mute_mask[event_source], + sgtl5000->mute_state[event_source]); + break; + default: + break; + } + + return 0; +} + +/* + * Mute Headphone when power it up/down. + * Control VAG power on HP power path. + */ +static int headphone_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + return vag_and_mute_control(component, event, HP_POWER_EVENT); +} + +/* As manual describes, ADC/DAC powering up/down requires + * to mute outputs to avoid pops. + * Control VAG power on ADC/DAC power path. + */ +static int adc_updown_depop(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + return vag_and_mute_control(component, event, ADC_POWER_EVENT); +} + +static int dac_updown_depop(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + + return vag_and_mute_control(component, event, DAC_POWER_EVENT); +} + +/* input sources for ADC */ +static const char *adc_mux_text[] = { + "MIC_IN", "LINE_IN" +}; + +static SOC_ENUM_SINGLE_DECL(adc_enum, + SGTL5000_CHIP_ANA_CTRL, 2, + adc_mux_text); + +static const struct snd_kcontrol_new adc_mux = +SOC_DAPM_ENUM("Capture Mux", adc_enum); + +/* input sources for headphone */ +static const char *hp_mux_text[] = { + "DAC", "LINE_IN" +}; + +static SOC_ENUM_SINGLE_DECL(hp_enum, + SGTL5000_CHIP_ANA_CTRL, 6, + hp_mux_text); + +static const struct snd_kcontrol_new hp_mux = +SOC_DAPM_ENUM("Headphone Mux", hp_enum); + +/* input sources for DAC */ +static const char *dac_mux_text[] = { + "ADC", "I2S", "Rsvrd", "DAP" +}; + +static SOC_ENUM_SINGLE_DECL(dac_enum, + SGTL5000_CHIP_SSS_CTRL, SGTL5000_DAC_SEL_SHIFT, + dac_mux_text); + +static const struct snd_kcontrol_new dac_mux = +SOC_DAPM_ENUM("Digital Input Mux", dac_enum); + +/* input sources for DAP */ +static const char *dap_mux_text[] = { + "ADC", "I2S" +}; + +static SOC_ENUM_SINGLE_DECL(dap_enum, + SGTL5000_CHIP_SSS_CTRL, SGTL5000_DAP_SEL_SHIFT, + dap_mux_text); + +static const struct snd_kcontrol_new dap_mux = +SOC_DAPM_ENUM("DAP Mux", dap_enum); + +/* input sources for DAP mix */ +static const char *dapmix_mux_text[] = { + "ADC", "I2S" +}; + +static SOC_ENUM_SINGLE_DECL(dapmix_enum, + SGTL5000_CHIP_SSS_CTRL, SGTL5000_DAP_MIX_SEL_SHIFT, + dapmix_mux_text); + +static const struct snd_kcontrol_new dapmix_mux = +SOC_DAPM_ENUM("DAP MIX Mux", dapmix_enum); + + +static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LINE_IN"), + SND_SOC_DAPM_INPUT("MIC_IN"), + + SND_SOC_DAPM_OUTPUT("HP_OUT"), + SND_SOC_DAPM_OUTPUT("LINE_OUT"), + + SND_SOC_DAPM_SUPPLY("Mic Bias", SGTL5000_CHIP_MIC_CTRL, 8, 0, + mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA_E("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0, + headphone_pga_event, + SND_SOC_DAPM_PRE_POST_PMU | + SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), + SND_SOC_DAPM_MUX("Headphone Mux", SND_SOC_NOPM, 0, 0, &hp_mux), + SND_SOC_DAPM_MUX("Digital Input Mux", SND_SOC_NOPM, 0, 0, &dac_mux), + SND_SOC_DAPM_MUX("DAP Mux", SGTL5000_DAP_CTRL, 0, 0, &dap_mux), + SND_SOC_DAPM_MUX("DAP MIX Mux", SGTL5000_DAP_CTRL, 4, 0, &dapmix_mux), + SND_SOC_DAPM_MIXER("DAP", SGTL5000_CHIP_DIG_POWER, 4, 0, NULL, 0), + + + /* aif for i2s input */ + SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", + 0, SGTL5000_CHIP_DIG_POWER, + 0, 0), + + /* aif for i2s output */ + SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture", + 0, SGTL5000_CHIP_DIG_POWER, + 1, 0), + + SND_SOC_DAPM_ADC_E("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0, + adc_updown_depop, SND_SOC_DAPM_PRE_POST_PMU | + SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0, + dac_updown_depop, SND_SOC_DAPM_PRE_POST_PMU | + SND_SOC_DAPM_PRE_POST_PMD), +}; + +/* routes for sgtl5000 */ +static const struct snd_soc_dapm_route sgtl5000_dapm_routes[] = { + {"Capture Mux", "LINE_IN", "LINE_IN"}, /* line_in --> adc_mux */ + {"Capture Mux", "MIC_IN", "MIC_IN"}, /* mic_in --> adc_mux */ + + {"ADC", NULL, "Capture Mux"}, /* adc_mux --> adc */ + {"AIFOUT", NULL, "ADC"}, /* adc --> i2s_out */ + + {"DAP Mux", "ADC", "ADC"}, /* adc --> DAP mux */ + {"DAP Mux", NULL, "AIFIN"}, /* i2s --> DAP mux */ + {"DAP", NULL, "DAP Mux"}, /* DAP mux --> dap */ + + {"DAP MIX Mux", "ADC", "ADC"}, /* adc --> DAP MIX mux */ + {"DAP MIX Mux", NULL, "AIFIN"}, /* i2s --> DAP MIX mux */ + {"DAP", NULL, "DAP MIX Mux"}, /* DAP MIX mux --> dap */ + + {"Digital Input Mux", "ADC", "ADC"}, /* adc --> audio mux */ + {"Digital Input Mux", NULL, "AIFIN"}, /* i2s --> audio mux */ + {"Digital Input Mux", NULL, "DAP"}, /* dap --> audio mux */ + {"DAC", NULL, "Digital Input Mux"}, /* audio mux --> dac */ + + {"Headphone Mux", "DAC", "DAC"}, /* dac --> hp_mux */ + {"LO", NULL, "DAC"}, /* dac --> line_out */ + + {"Headphone Mux", "LINE_IN", "LINE_IN"},/* line_in --> hp_mux */ + {"HP", NULL, "Headphone Mux"}, /* hp_mux --> hp */ + + {"LINE_OUT", NULL, "LO"}, + {"HP_OUT", NULL, "HP"}, +}; + +/* custom function to fetch info of PCM playback volume */ +static int dac_info_volsw(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 = 0xfc - 0x3c; + return 0; +} + +/* + * custom function to get of PCM playback volume + * + * dac volume register + * 15-------------8-7--------------0 + * | R channel vol | L channel vol | + * ------------------------------- + * + * PCM volume with 0.5017 dB steps from 0 to -90 dB + * + * register values map to dB + * 0x3B and less = Reserved + * 0x3C = 0 dB + * 0x3D = -0.5 dB + * 0xF0 = -90 dB + * 0xFC and greater = Muted + * + * register value map to userspace value + * + * register value 0x3c(0dB) 0xf0(-90dB)0xfc + * ------------------------------ + * userspace value 0xc0 0 + */ +static int dac_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int reg; + int l; + int r; + + reg = snd_soc_component_read32(component, SGTL5000_CHIP_DAC_VOL); + + /* get left channel volume */ + l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) >> SGTL5000_DAC_VOL_LEFT_SHIFT; + + /* get right channel volume */ + r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) >> SGTL5000_DAC_VOL_RIGHT_SHIFT; + + /* make sure value fall in (0x3c,0xfc) */ + l = clamp(l, 0x3c, 0xfc); + r = clamp(r, 0x3c, 0xfc); + + /* invert it and map to userspace value */ + l = 0xfc - l; + r = 0xfc - r; + + ucontrol->value.integer.value[0] = l; + ucontrol->value.integer.value[1] = r; + + return 0; +} + +/* + * custom function to put of PCM playback volume + * + * dac volume register + * 15-------------8-7--------------0 + * | R channel vol | L channel vol | + * ------------------------------- + * + * PCM volume with 0.5017 dB steps from 0 to -90 dB + * + * register values map to dB + * 0x3B and less = Reserved + * 0x3C = 0 dB + * 0x3D = -0.5 dB + * 0xF0 = -90 dB + * 0xFC and greater = Muted + * + * userspace value map to register value + * + * userspace value 0xc0 0 + * ------------------------------ + * register value 0x3c(0dB) 0xf0(-90dB)0xfc + */ +static int dac_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int reg; + int l; + int r; + + l = ucontrol->value.integer.value[0]; + r = ucontrol->value.integer.value[1]; + + /* make sure userspace volume fall in (0, 0xfc-0x3c) */ + l = clamp(l, 0, 0xfc - 0x3c); + r = clamp(r, 0, 0xfc - 0x3c); + + /* invert it, get the value can be set to register */ + l = 0xfc - l; + r = 0xfc - r; + + /* shift to get the register value */ + reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT | + r << SGTL5000_DAC_VOL_RIGHT_SHIFT; + + snd_soc_component_write(component, SGTL5000_CHIP_DAC_VOL, reg); + + return 0; +} + +/* + * custom function to get AVC threshold + * + * The threshold dB is calculated by rearranging the calculation from the + * avc_put_threshold function: register_value = 10^(dB/20) * 0.636 * 2^15 ==> + * dB = ( fls(register_value) - 14.347 ) * 6.02 + * + * As this calculation is expensive and the threshold dB values may not exceed + * 0 to 96 we use pre-calculated values. + */ +static int avc_get_threshold(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int db, i; + u16 reg = snd_soc_component_read32(component, SGTL5000_DAP_AVC_THRESHOLD); + + /* register value 0 => -96dB */ + if (!reg) { + ucontrol->value.integer.value[0] = 96; + ucontrol->value.integer.value[1] = 96; + return 0; + } + + /* get dB from register value (rounded down) */ + for (i = 0; avc_thr_db2reg[i] > reg; i++) + ; + db = i; + + ucontrol->value.integer.value[0] = db; + ucontrol->value.integer.value[1] = db; + + return 0; +} + +/* + * custom function to put AVC threshold + * + * The register value is calculated by following formula: + * register_value = 10^(dB/20) * 0.636 * 2^15 + * As this calculation is expensive and the threshold dB values may not exceed + * 0 to 96 we use pre-calculated values. + */ +static int avc_put_threshold(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int db; + u16 reg; + + db = (int)ucontrol->value.integer.value[0]; + if (db < 0 || db > 96) + return -EINVAL; + reg = avc_thr_db2reg[db]; + snd_soc_component_write(component, SGTL5000_DAP_AVC_THRESHOLD, reg); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(capture_6db_attenuate, -600, 600, 0); + +/* tlv for mic gain, 0db 20db 30db 40db */ +static const DECLARE_TLV_DB_RANGE(mic_gain_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 3, TLV_DB_SCALE_ITEM(2000, 1000, 0) +); + +/* tlv for DAP channels, 0% - 100% - 200% */ +static const DECLARE_TLV_DB_SCALE(dap_volume, 0, 1, 0); + +/* tlv for bass bands, -11.75db to 12.0db, step .25db */ +static const DECLARE_TLV_DB_SCALE(bass_band, -1175, 25, 0); + +/* tlv for hp volume, -51.5db to 12.0db, step .5db */ +static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0); + +/* tlv for lineout volume, 31 steps of .5db each */ +static const DECLARE_TLV_DB_SCALE(lineout_volume, -1550, 50, 0); + +/* tlv for dap avc max gain, 0db, 6db, 12db */ +static const DECLARE_TLV_DB_SCALE(avc_max_gain, 0, 600, 0); + +/* tlv for dap avc threshold, */ +static const DECLARE_TLV_DB_MINMAX(avc_threshold, 0, 9600); + +static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { + /* SOC_DOUBLE_S8_TLV with invert */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = dac_info_volsw, + .get = dac_get_volsw, + .put = dac_put_volsw, + }, + + SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0), + SOC_SINGLE_TLV("Capture Attenuate Switch (-6dB)", + SGTL5000_CHIP_ANA_ADC_CTRL, + 8, 1, 0, capture_6db_attenuate), + SOC_SINGLE("Capture ZC Switch", SGTL5000_CHIP_ANA_CTRL, 1, 1, 0), + + SOC_DOUBLE_TLV("Headphone Playback Volume", + SGTL5000_CHIP_ANA_HP_CTRL, + 0, 8, + 0x7f, 1, + headphone_volume), + SOC_SINGLE("Headphone Playback Switch", SGTL5000_CHIP_ANA_CTRL, + 4, 1, 1), + SOC_SINGLE("Headphone Playback ZC Switch", SGTL5000_CHIP_ANA_CTRL, + 5, 1, 0), + + SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL, + 0, 3, 0, mic_gain_tlv), + + SOC_DOUBLE_TLV("Lineout Playback Volume", + SGTL5000_CHIP_LINE_OUT_VOL, + SGTL5000_LINE_OUT_VOL_LEFT_SHIFT, + SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT, + 0x1f, 1, + lineout_volume), + SOC_SINGLE("Lineout Playback Switch", SGTL5000_CHIP_ANA_CTRL, 8, 1, 1), + + SOC_SINGLE_TLV("DAP Main channel", SGTL5000_DAP_MAIN_CHAN, + 0, 0xffff, 0, dap_volume), + + SOC_SINGLE_TLV("DAP Mix channel", SGTL5000_DAP_MIX_CHAN, + 0, 0xffff, 0, dap_volume), + /* Automatic Volume Control (DAP AVC) */ + SOC_SINGLE("AVC Switch", SGTL5000_DAP_AVC_CTRL, 0, 1, 0), + SOC_SINGLE("AVC Hard Limiter Switch", SGTL5000_DAP_AVC_CTRL, 5, 1, 0), + SOC_SINGLE_TLV("AVC Max Gain Volume", SGTL5000_DAP_AVC_CTRL, 12, 2, 0, + avc_max_gain), + SOC_SINGLE("AVC Integrator Response", SGTL5000_DAP_AVC_CTRL, 8, 3, 0), + SOC_SINGLE_EXT_TLV("AVC Threshold Volume", SGTL5000_DAP_AVC_THRESHOLD, + 0, 96, 0, avc_get_threshold, avc_put_threshold, + avc_threshold), + + SOC_SINGLE_TLV("BASS 0", SGTL5000_DAP_EQ_BASS_BAND0, + 0, 0x5F, 0, bass_band), + + SOC_SINGLE_TLV("BASS 1", SGTL5000_DAP_EQ_BASS_BAND1, + 0, 0x5F, 0, bass_band), + + SOC_SINGLE_TLV("BASS 2", SGTL5000_DAP_EQ_BASS_BAND2, + 0, 0x5F, 0, bass_band), + + SOC_SINGLE_TLV("BASS 3", SGTL5000_DAP_EQ_BASS_BAND3, + 0, 0x5F, 0, bass_band), + + SOC_SINGLE_TLV("BASS 4", SGTL5000_DAP_EQ_BASS_BAND4, + 0, 0x5F, 0, bass_band), +}; + +/* mute the codec used by alsa core */ +static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + u16 i2s_pwr = SGTL5000_I2S_IN_POWERUP; + + /* + * During 'digital mute' do not mute DAC + * because LINE_IN would be muted aswell. We want to mute + * only I2S block - this can be done by powering it off + */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_DIG_POWER, + i2s_pwr, mute ? 0 : i2s_pwr); + + return 0; +} + +/* set codec format */ +static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct sgtl5000_priv *sgtl5000 = snd_soc_component_get_drvdata(component); + u16 i2sctl = 0; + + sgtl5000->master = 0; + /* + * i2s clock and frame master setting. + * ONLY support: + * - clock and frame slave, + * - clock and frame master + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + i2sctl |= SGTL5000_I2S_MASTER; + sgtl5000->master = 1; + break; + default: + return -EINVAL; + } + + /* setting i2s data format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2sctl |= SGTL5000_I2S_MODE_PCM << SGTL5000_I2S_MODE_SHIFT; + break; + case SND_SOC_DAIFMT_DSP_B: + i2sctl |= SGTL5000_I2S_MODE_PCM << SGTL5000_I2S_MODE_SHIFT; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + case SND_SOC_DAIFMT_I2S: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ << SGTL5000_I2S_MODE_SHIFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2sctl |= SGTL5000_I2S_MODE_RJ << SGTL5000_I2S_MODE_SHIFT; + i2sctl |= SGTL5000_I2S_LRPOL; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2sctl |= SGTL5000_I2S_MODE_I2S_LJ << SGTL5000_I2S_MODE_SHIFT; + i2sctl |= SGTL5000_I2S_LRALIGN; + break; + default: + return -EINVAL; + } + + sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + i2sctl |= SGTL5000_I2S_SCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, SGTL5000_CHIP_I2S_CTRL, i2sctl); + + return 0; +} + +/* set codec sysclk */ +static int sgtl5000_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct sgtl5000_priv *sgtl5000 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case SGTL5000_SYSCLK: + sgtl5000->sysclk = freq; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * set clock according to i2s frame clock, + * sgtl5000 provides 2 clock sources: + * 1. sys_mclk: sample freq can only be configured to + * 1/256, 1/384, 1/512 of sys_mclk. + * 2. pll: can derive any audio clocks. + * + * clock setting rules: + * 1. in slave mode, only sys_mclk can be used + * 2. as constraint by sys_mclk, sample freq should be set to 32 kHz, 44.1 kHz + * and above. + * 3. usage of sys_mclk is preferred over pll to save power. + */ +static int sgtl5000_set_clock(struct snd_soc_component *component, int frame_rate) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_component_get_drvdata(component); + int clk_ctl = 0; + int sys_fs; /* sample freq */ + + /* + * sample freq should be divided by frame clock, + * if frame clock is lower than 44.1 kHz, sample freq should be set to + * 32 kHz or 44.1 kHz. + */ + switch (frame_rate) { + case 8000: + case 16000: + sys_fs = 32000; + break; + case 11025: + case 22050: + sys_fs = 44100; + break; + default: + sys_fs = frame_rate; + break; + } + + /* set divided factor of frame clock */ + switch (sys_fs / frame_rate) { + case 4: + clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT; + break; + case 2: + clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT; + break; + case 1: + clk_ctl |= SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT; + break; + default: + return -EINVAL; + } + + /* set the sys_fs according to frame rate */ + switch (sys_fs) { + case 32000: + clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT; + break; + case 44100: + clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT; + break; + case 48000: + clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT; + break; + case 96000: + clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT; + break; + default: + dev_err(component->dev, "frame rate %d not supported\n", + frame_rate); + return -EINVAL; + } + + /* + * calculate the divider of mclk/sample_freq, + * factor of freq = 96 kHz can only be 256, since mclk is in the range + * of 8 MHz - 27 MHz + */ + switch (sgtl5000->sysclk / frame_rate) { + case 256: + clk_ctl |= SGTL5000_MCLK_FREQ_256FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + case 384: + clk_ctl |= SGTL5000_MCLK_FREQ_384FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + case 512: + clk_ctl |= SGTL5000_MCLK_FREQ_512FS << + SGTL5000_MCLK_FREQ_SHIFT; + break; + default: + /* if mclk does not satisfy the divider, use pll */ + if (sgtl5000->master) { + clk_ctl |= SGTL5000_MCLK_FREQ_PLL << + SGTL5000_MCLK_FREQ_SHIFT; + } else { + dev_err(component->dev, + "PLL not supported in slave mode\n"); + dev_err(component->dev, "%d ratio is not supported. " + "SYS_MCLK needs to be 256, 384 or 512 * fs\n", + sgtl5000->sysclk / frame_rate); + return -EINVAL; + } + } + + /* if using pll, please check manual 6.4.2 for detail */ + if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) { + u64 out, t; + int div2; + int pll_ctl; + unsigned int in, int_div, frac_div; + + if (sgtl5000->sysclk > 17000000) { + div2 = 1; + in = sgtl5000->sysclk / 2; + } else { + div2 = 0; + in = sgtl5000->sysclk; + } + if (sys_fs == 44100) + out = 180633600; + else + out = 196608000; + t = do_div(out, in); + int_div = out; + t *= 2048; + do_div(t, in); + frac_div = t; + pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT | + frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT; + + snd_soc_component_write(component, SGTL5000_CHIP_PLL_CTRL, pll_ctl); + if (div2) + snd_soc_component_update_bits(component, + SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INPUT_FREQ_DIV2, + SGTL5000_INPUT_FREQ_DIV2); + else + snd_soc_component_update_bits(component, + SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INPUT_FREQ_DIV2, + 0); + + /* power up pll */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP); + + /* if using pll, clk_ctrl must be set after pll power up */ + snd_soc_component_write(component, SGTL5000_CHIP_CLK_CTRL, clk_ctl); + } else { + /* otherwise, clk_ctrl must be set before pll power down */ + snd_soc_component_write(component, SGTL5000_CHIP_CLK_CTRL, clk_ctl); + + /* power down pll */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, + 0); + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + * input: params_rate, params_fmt + */ +static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct sgtl5000_priv *sgtl5000 = snd_soc_component_get_drvdata(component); + int channels = params_channels(params); + int i2s_ctl = 0; + int stereo; + int ret; + + /* sysclk should already set */ + if (!sgtl5000->sysclk) { + dev_err(component->dev, "%s: set sysclk first!\n", __func__); + return -EFAULT; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + stereo = SGTL5000_DAC_STEREO; + else + stereo = SGTL5000_ADC_STEREO; + + /* set mono to save power */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, stereo, + channels == 1 ? 0 : stereo); + + /* set codec clock base on lrclk */ + ret = sgtl5000_set_clock(component, params_rate(params)); + if (ret) + return ret; + + /* set i2s data format */ + switch (params_width(params)) { + case 16: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case 20: + i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case 24: + i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + case 32: + if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J) + return -EINVAL; + i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT; + i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS << + SGTL5000_I2S_SCLKFREQ_SHIFT; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, SGTL5000_CHIP_I2S_CTRL, + SGTL5000_I2S_DLEN_MASK | SGTL5000_I2S_SCLKFREQ_MASK, + i2s_ctl); + + return 0; +} + +/* + * set dac bias + * common state changes: + * startup: + * off --> standby --> prepare --> on + * standby --> prepare --> on + * + * stop: + * on --> prepare --> standby + */ +static int sgtl5000_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct sgtl5000_priv *sgtl = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + regcache_cache_only(sgtl->regmap, false); + ret = regcache_sync(sgtl->regmap); + if (ret) { + regcache_cache_only(sgtl->regmap, true); + return ret; + } + + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_REFTOP_POWERUP, + SGTL5000_REFTOP_POWERUP); + break; + case SND_SOC_BIAS_OFF: + regcache_cache_only(sgtl->regmap, true); + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER, + SGTL5000_REFTOP_POWERUP, 0); + break; + } + + return 0; +} + +#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops sgtl5000_ops = { + .hw_params = sgtl5000_pcm_hw_params, + .digital_mute = sgtl5000_digital_mute, + .set_fmt = sgtl5000_set_dai_fmt, + .set_sysclk = sgtl5000_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver sgtl5000_dai = { + .name = "sgtl5000", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + /* + * only support 8~48K + 96K, + * TODO modify hw_param to support more + */ + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000, + .formats = SGTL5000_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000, + .formats = SGTL5000_FORMATS, + }, + .ops = &sgtl5000_ops, + .symmetric_rates = 1, +}; + +static bool sgtl5000_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SGTL5000_CHIP_ID: + case SGTL5000_CHIP_ADCDAC_CTRL: + case SGTL5000_CHIP_ANA_STATUS: + return true; + } + + return false; +} + +static bool sgtl5000_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SGTL5000_CHIP_ID: + case SGTL5000_CHIP_DIG_POWER: + case SGTL5000_CHIP_CLK_CTRL: + case SGTL5000_CHIP_I2S_CTRL: + case SGTL5000_CHIP_SSS_CTRL: + case SGTL5000_CHIP_ADCDAC_CTRL: + case SGTL5000_CHIP_DAC_VOL: + case SGTL5000_CHIP_PAD_STRENGTH: + case SGTL5000_CHIP_ANA_ADC_CTRL: + case SGTL5000_CHIP_ANA_HP_CTRL: + case SGTL5000_CHIP_ANA_CTRL: + case SGTL5000_CHIP_LINREG_CTRL: + case SGTL5000_CHIP_REF_CTRL: + case SGTL5000_CHIP_MIC_CTRL: + case SGTL5000_CHIP_LINE_OUT_CTRL: + case SGTL5000_CHIP_LINE_OUT_VOL: + case SGTL5000_CHIP_ANA_POWER: + case SGTL5000_CHIP_PLL_CTRL: + case SGTL5000_CHIP_CLK_TOP_CTRL: + case SGTL5000_CHIP_ANA_STATUS: + case SGTL5000_CHIP_SHORT_CTRL: + case SGTL5000_CHIP_ANA_TEST2: + case SGTL5000_DAP_CTRL: + case SGTL5000_DAP_PEQ: + case SGTL5000_DAP_BASS_ENHANCE: + case SGTL5000_DAP_BASS_ENHANCE_CTRL: + case SGTL5000_DAP_AUDIO_EQ: + case SGTL5000_DAP_SURROUND: + case SGTL5000_DAP_FLT_COEF_ACCESS: + case SGTL5000_DAP_COEF_WR_B0_MSB: + case SGTL5000_DAP_COEF_WR_B0_LSB: + case SGTL5000_DAP_EQ_BASS_BAND0: + case SGTL5000_DAP_EQ_BASS_BAND1: + case SGTL5000_DAP_EQ_BASS_BAND2: + case SGTL5000_DAP_EQ_BASS_BAND3: + case SGTL5000_DAP_EQ_BASS_BAND4: + case SGTL5000_DAP_MAIN_CHAN: + case SGTL5000_DAP_MIX_CHAN: + case SGTL5000_DAP_AVC_CTRL: + case SGTL5000_DAP_AVC_THRESHOLD: + case SGTL5000_DAP_AVC_ATTACK: + case SGTL5000_DAP_AVC_DECAY: + case SGTL5000_DAP_COEF_WR_B1_MSB: + case SGTL5000_DAP_COEF_WR_B1_LSB: + case SGTL5000_DAP_COEF_WR_B2_MSB: + case SGTL5000_DAP_COEF_WR_B2_LSB: + case SGTL5000_DAP_COEF_WR_A1_MSB: + case SGTL5000_DAP_COEF_WR_A1_LSB: + case SGTL5000_DAP_COEF_WR_A2_MSB: + case SGTL5000_DAP_COEF_WR_A2_LSB: + return true; + + default: + return false; + } +} + +/* + * This precalculated table contains all (vag_val * 100 / lo_calcntrl) results + * to select an appropriate lo_vol_* in SGTL5000_CHIP_LINE_OUT_VOL + * The calculatation was done for all possible register values which + * is the array index and the following formula: 10^((idx−15)/40) * 100 + */ +static const u8 vol_quot_table[] = { + 42, 45, 47, 50, 53, 56, 60, 63, + 67, 71, 75, 79, 84, 89, 94, 100, + 106, 112, 119, 126, 133, 141, 150, 158, + 168, 178, 188, 200, 211, 224, 237, 251 +}; + +/* + * sgtl5000 has 3 internal power supplies: + * 1. VAG, normally set to vdda/2 + * 2. charge pump, set to different value + * according to voltage of vdda and vddio + * 3. line out VAG, normally set to vddio/2 + * + * and should be set according to: + * 1. vddd provided by external or not + * 2. vdda and vddio voltage value. > 3.1v or not + */ +static int sgtl5000_set_power_regs(struct snd_soc_component *component) +{ + int vddd; + int vdda; + int vddio; + u16 ana_pwr; + u16 lreg_ctrl; + int vag; + int lo_vag; + int vol_quot; + int lo_vol; + size_t i; + struct sgtl5000_priv *sgtl5000 = snd_soc_component_get_drvdata(component); + + vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer); + vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer); + vddd = (sgtl5000->num_supplies > VDDD) + ? regulator_get_voltage(sgtl5000->supplies[VDDD].consumer) + : LDO_VOLTAGE; + + vdda = vdda / 1000; + vddio = vddio / 1000; + vddd = vddd / 1000; + + if (vdda <= 0 || vddio <= 0 || vddd < 0) { + dev_err(component->dev, "regulator voltage not set correctly\n"); + + return -EINVAL; + } + + /* according to datasheet, maximum voltage of supplies */ + if (vdda > 3600 || vddio > 3600 || vddd > 1980) { + dev_err(component->dev, + "exceed max voltage vdda %dmV vddio %dmV vddd %dmV\n", + vdda, vddio, vddd); + + return -EINVAL; + } + + /* reset value */ + ana_pwr = snd_soc_component_read32(component, SGTL5000_CHIP_ANA_POWER); + ana_pwr |= SGTL5000_DAC_STEREO | + SGTL5000_ADC_STEREO | + SGTL5000_REFTOP_POWERUP; + lreg_ctrl = snd_soc_component_read32(component, SGTL5000_CHIP_LINREG_CTRL); + + if (vddio < 3100 && vdda < 3100) { + /* enable internal oscillator used for charge pump */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_CLK_TOP_CTRL, + SGTL5000_INT_OSC_EN, + SGTL5000_INT_OSC_EN); + /* Enable VDDC charge pump */ + ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP; + } else { + ana_pwr &= ~SGTL5000_VDDC_CHRGPMP_POWERUP; + /* + * if vddio == vdda the source of charge pump should be + * assigned manually to VDDIO + */ + if (vddio == vdda) { + lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD; + lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO << + SGTL5000_VDDC_MAN_ASSN_SHIFT; + } + } + + snd_soc_component_write(component, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl); + + snd_soc_component_write(component, SGTL5000_CHIP_ANA_POWER, ana_pwr); + + /* + * set ADC/DAC VAG to vdda / 2, + * should stay in range (0.8v, 1.575v) + */ + vag = vdda / 2; + if (vag <= SGTL5000_ANA_GND_BASE) + vag = 0; + else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP * + (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT)) + vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT; + else + vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP; + + snd_soc_component_update_bits(component, SGTL5000_CHIP_REF_CTRL, + SGTL5000_ANA_GND_MASK, vag << SGTL5000_ANA_GND_SHIFT); + + /* set line out VAG to vddio / 2, in range (0.8v, 1.675v) */ + lo_vag = vddio / 2; + if (lo_vag <= SGTL5000_LINE_OUT_GND_BASE) + lo_vag = 0; + else if (lo_vag >= SGTL5000_LINE_OUT_GND_BASE + + SGTL5000_LINE_OUT_GND_STP * SGTL5000_LINE_OUT_GND_MAX) + lo_vag = SGTL5000_LINE_OUT_GND_MAX; + else + lo_vag = (lo_vag - SGTL5000_LINE_OUT_GND_BASE) / + SGTL5000_LINE_OUT_GND_STP; + + snd_soc_component_update_bits(component, SGTL5000_CHIP_LINE_OUT_CTRL, + SGTL5000_LINE_OUT_CURRENT_MASK | + SGTL5000_LINE_OUT_GND_MASK, + lo_vag << SGTL5000_LINE_OUT_GND_SHIFT | + SGTL5000_LINE_OUT_CURRENT_360u << + SGTL5000_LINE_OUT_CURRENT_SHIFT); + + /* + * Set lineout output level in range (0..31) + * the same value is used for right and left channel + * + * Searching for a suitable index solving this formula: + * idx = 40 * log10(vag_val / lo_cagcntrl) + 15 + */ + vol_quot = lo_vag ? (vag * 100) / lo_vag : 0; + lo_vol = 0; + for (i = 0; i < ARRAY_SIZE(vol_quot_table); i++) { + if (vol_quot >= vol_quot_table[i]) + lo_vol = i; + else + break; + } + + snd_soc_component_update_bits(component, SGTL5000_CHIP_LINE_OUT_VOL, + SGTL5000_LINE_OUT_VOL_RIGHT_MASK | + SGTL5000_LINE_OUT_VOL_LEFT_MASK, + lo_vol << SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT | + lo_vol << SGTL5000_LINE_OUT_VOL_LEFT_SHIFT); + + return 0; +} + +static int sgtl5000_enable_regulators(struct i2c_client *client) +{ + int ret; + int i; + int external_vddd = 0; + struct regulator *vddd; + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); + + for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++) + sgtl5000->supplies[i].supply = supply_names[i]; + + vddd = regulator_get_optional(&client->dev, "VDDD"); + if (IS_ERR(vddd)) { + /* See if it's just not registered yet */ + if (PTR_ERR(vddd) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } else { + external_vddd = 1; + regulator_put(vddd); + } + + sgtl5000->num_supplies = ARRAY_SIZE(sgtl5000->supplies) + - 1 + external_vddd; + ret = regulator_bulk_get(&client->dev, sgtl5000->num_supplies, + sgtl5000->supplies); + if (ret) + return ret; + + ret = regulator_bulk_enable(sgtl5000->num_supplies, + sgtl5000->supplies); + if (!ret) + usleep_range(10, 20); + else + regulator_bulk_free(sgtl5000->num_supplies, + sgtl5000->supplies); + + return ret; +} + +static int sgtl5000_probe(struct snd_soc_component *component) +{ + int ret; + u16 reg; + struct sgtl5000_priv *sgtl5000 = snd_soc_component_get_drvdata(component); + unsigned int zcd_mask = SGTL5000_HP_ZCD_EN | SGTL5000_ADC_ZCD_EN; + + /* power up sgtl5000 */ + ret = sgtl5000_set_power_regs(component); + if (ret) + goto err; + + /* enable small pop, introduce 400ms delay in turning off */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_REF_CTRL, + SGTL5000_SMALL_POP, 1); + + /* disable short cut detector */ + snd_soc_component_write(component, SGTL5000_CHIP_SHORT_CTRL, 0); + + snd_soc_component_write(component, SGTL5000_CHIP_DIG_POWER, + SGTL5000_ADC_EN | SGTL5000_DAC_EN); + + /* enable dac volume ramp by default */ + snd_soc_component_write(component, SGTL5000_CHIP_ADCDAC_CTRL, + SGTL5000_DAC_VOL_RAMP_EN | + SGTL5000_DAC_MUTE_RIGHT | + SGTL5000_DAC_MUTE_LEFT); + + reg = ((sgtl5000->lrclk_strength) << SGTL5000_PAD_I2S_LRCLK_SHIFT | 0x5f); + snd_soc_component_write(component, SGTL5000_CHIP_PAD_STRENGTH, reg); + + snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_CTRL, + zcd_mask, zcd_mask); + + snd_soc_component_update_bits(component, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, + sgtl5000->micbias_resistor << SGTL5000_BIAS_R_SHIFT); + + snd_soc_component_update_bits(component, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_VOLT_MASK, + sgtl5000->micbias_voltage << SGTL5000_BIAS_VOLT_SHIFT); + /* + * enable DAP Graphic EQ + * TODO: + * Add control for changing between PEQ/Tone Control/GEQ + */ + snd_soc_component_write(component, SGTL5000_DAP_AUDIO_EQ, SGTL5000_DAP_SEL_GEQ); + + /* Unmute DAC after start */ + snd_soc_component_update_bits(component, SGTL5000_CHIP_ADCDAC_CTRL, + SGTL5000_DAC_MUTE_LEFT | SGTL5000_DAC_MUTE_RIGHT, 0); + + return 0; + +err: + return ret; +} + +static const struct snd_soc_component_driver sgtl5000_driver = { + .probe = sgtl5000_probe, + .set_bias_level = sgtl5000_set_bias_level, + .controls = sgtl5000_snd_controls, + .num_controls = ARRAY_SIZE(sgtl5000_snd_controls), + .dapm_widgets = sgtl5000_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sgtl5000_dapm_widgets), + .dapm_routes = sgtl5000_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sgtl5000_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config sgtl5000_regmap = { + .reg_bits = 16, + .val_bits = 16, + .reg_stride = 2, + + .max_register = SGTL5000_MAX_REG_OFFSET, + .volatile_reg = sgtl5000_volatile, + .readable_reg = sgtl5000_readable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = sgtl5000_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(sgtl5000_reg_defaults), +}; + +/* + * Write all the default values from sgtl5000_reg_defaults[] array into the + * sgtl5000 registers, to make sure we always start with the sane registers + * values as stated in the datasheet. + * + * Since sgtl5000 does not have a reset line, nor a reset command in software, + * we follow this approach to guarantee we always start from the default values + * and avoid problems like, not being able to probe after an audio playback + * followed by a system reset or a 'reboot' command in Linux + */ +static void sgtl5000_fill_defaults(struct i2c_client *client) +{ + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); + int i, ret, val, index; + + for (i = 0; i < ARRAY_SIZE(sgtl5000_reg_defaults); i++) { + val = sgtl5000_reg_defaults[i].def; + index = sgtl5000_reg_defaults[i].reg; + ret = regmap_write(sgtl5000->regmap, index, val); + if (ret) + dev_err(&client->dev, + "%s: error %d setting reg 0x%02x to 0x%04x\n", + __func__, ret, index, val); + } +} + +static int sgtl5000_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sgtl5000_priv *sgtl5000; + int ret, reg, rev; + struct device_node *np = client->dev.of_node; + u32 value; + u16 ana_pwr; + + sgtl5000 = devm_kzalloc(&client->dev, sizeof(*sgtl5000), GFP_KERNEL); + if (!sgtl5000) + return -ENOMEM; + + i2c_set_clientdata(client, sgtl5000); + + ret = sgtl5000_enable_regulators(client); + if (ret) + return ret; + + sgtl5000->regmap = devm_regmap_init_i2c(client, &sgtl5000_regmap); + if (IS_ERR(sgtl5000->regmap)) { + ret = PTR_ERR(sgtl5000->regmap); + dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret); + goto disable_regs; + } + + sgtl5000->mclk = devm_clk_get(&client->dev, NULL); + if (IS_ERR(sgtl5000->mclk)) { + ret = PTR_ERR(sgtl5000->mclk); + /* Defer the probe to see if the clk will be provided later */ + if (ret == -ENOENT) + ret = -EPROBE_DEFER; + + if (ret != -EPROBE_DEFER) + dev_err(&client->dev, "Failed to get mclock: %d\n", + ret); + goto disable_regs; + } + + ret = clk_prepare_enable(sgtl5000->mclk); + if (ret) { + dev_err(&client->dev, "Error enabling clock %d\n", ret); + goto disable_regs; + } + + /* Need 8 clocks before I2C accesses */ + udelay(1); + + /* read chip information */ + ret = regmap_read(sgtl5000->regmap, SGTL5000_CHIP_ID, ®); + if (ret) { + dev_err(&client->dev, "Error reading chip id %d\n", ret); + goto disable_clk; + } + + if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) != + SGTL5000_PARTID_PART_ID) { + dev_err(&client->dev, + "Device with ID register %x is not a sgtl5000\n", reg); + ret = -ENODEV; + goto disable_clk; + } + + rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT; + dev_info(&client->dev, "sgtl5000 revision 0x%x\n", rev); + sgtl5000->revision = rev; + + /* reconfigure the clocks in case we're using the PLL */ + ret = regmap_write(sgtl5000->regmap, + SGTL5000_CHIP_CLK_CTRL, + SGTL5000_CHIP_CLK_CTRL_DEFAULT); + if (ret) + dev_err(&client->dev, + "Error %d initializing CHIP_CLK_CTRL\n", ret); + + /* Mute everything to avoid pop from the following power-up */ + ret = regmap_write(sgtl5000->regmap, SGTL5000_CHIP_ANA_CTRL, + SGTL5000_CHIP_ANA_CTRL_DEFAULT); + if (ret) { + dev_err(&client->dev, + "Error %d muting outputs via CHIP_ANA_CTRL\n", ret); + goto disable_clk; + } + + /* + * If VAG is powered-on (e.g. from previous boot), it would be disabled + * by the write to ANA_POWER in later steps of the probe code. This + * may create a loud pop even with all outputs muted. The proper way + * to circumvent this is disabling the bit first and waiting the proper + * cool-down time. + */ + ret = regmap_read(sgtl5000->regmap, SGTL5000_CHIP_ANA_POWER, &value); + if (ret) { + dev_err(&client->dev, "Failed to read ANA_POWER: %d\n", ret); + goto disable_clk; + } + if (value & SGTL5000_VAG_POWERUP) { + ret = regmap_update_bits(sgtl5000->regmap, + SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, + 0); + if (ret) { + dev_err(&client->dev, "Error %d disabling VAG\n", ret); + goto disable_clk; + } + + msleep(SGTL5000_VAG_POWERDOWN_DELAY); + } + + /* Follow section 2.2.1.1 of AN3663 */ + ana_pwr = SGTL5000_ANA_POWER_DEFAULT; + if (sgtl5000->num_supplies <= VDDD) { + /* internal VDDD at 1.2V */ + ret = regmap_update_bits(sgtl5000->regmap, + SGTL5000_CHIP_LINREG_CTRL, + SGTL5000_LINREG_VDDD_MASK, + LINREG_VDDD); + if (ret) + dev_err(&client->dev, + "Error %d setting LINREG_VDDD\n", ret); + + ana_pwr |= SGTL5000_LINEREG_D_POWERUP; + dev_info(&client->dev, + "Using internal LDO instead of VDDD: check ER1 erratum\n"); + } else { + /* using external LDO for VDDD + * Clear startup powerup and simple powerup + * bits to save power + */ + ana_pwr &= ~(SGTL5000_STARTUP_POWERUP + | SGTL5000_LINREG_SIMPLE_POWERUP); + dev_dbg(&client->dev, "Using external VDDD\n"); + } + ret = regmap_write(sgtl5000->regmap, SGTL5000_CHIP_ANA_POWER, ana_pwr); + if (ret) + dev_err(&client->dev, + "Error %d setting CHIP_ANA_POWER to %04x\n", + ret, ana_pwr); + + if (np) { + if (!of_property_read_u32(np, + "micbias-resistor-k-ohms", &value)) { + switch (value) { + case SGTL5000_MICBIAS_OFF: + sgtl5000->micbias_resistor = 0; + break; + case SGTL5000_MICBIAS_2K: + sgtl5000->micbias_resistor = 1; + break; + case SGTL5000_MICBIAS_4K: + sgtl5000->micbias_resistor = 2; + break; + case SGTL5000_MICBIAS_8K: + sgtl5000->micbias_resistor = 3; + break; + default: + sgtl5000->micbias_resistor = 2; + dev_err(&client->dev, + "Unsuitable MicBias resistor\n"); + } + } else { + /* default is 4Kohms */ + sgtl5000->micbias_resistor = 2; + } + if (!of_property_read_u32(np, + "micbias-voltage-m-volts", &value)) { + /* 1250mV => 0 */ + /* steps of 250mV */ + if ((value >= 1250) && (value <= 3000)) + sgtl5000->micbias_voltage = (value / 250) - 5; + else { + sgtl5000->micbias_voltage = 0; + dev_err(&client->dev, + "Unsuitable MicBias voltage\n"); + } + } else { + sgtl5000->micbias_voltage = 0; + } + } + + sgtl5000->lrclk_strength = I2S_LRCLK_STRENGTH_LOW; + if (!of_property_read_u32(np, "lrclk-strength", &value)) { + if (value > I2S_LRCLK_STRENGTH_HIGH) + value = I2S_LRCLK_STRENGTH_LOW; + sgtl5000->lrclk_strength = value; + } + + /* Ensure sgtl5000 will start with sane register values */ + sgtl5000_fill_defaults(client); + + ret = devm_snd_soc_register_component(&client->dev, + &sgtl5000_driver, &sgtl5000_dai, 1); + if (ret) + goto disable_clk; + + return 0; + +disable_clk: + clk_disable_unprepare(sgtl5000->mclk); + +disable_regs: + regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies); + regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies); + + return ret; +} + +static int sgtl5000_i2c_remove(struct i2c_client *client) +{ + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); + + clk_disable_unprepare(sgtl5000->mclk); + regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies); + regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies); + + return 0; +} + +static const struct i2c_device_id sgtl5000_id[] = { + {"sgtl5000", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, sgtl5000_id); + +static const struct of_device_id sgtl5000_dt_ids[] = { + { .compatible = "fsl,sgtl5000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sgtl5000_dt_ids); + +static struct i2c_driver sgtl5000_i2c_driver = { + .driver = { + .name = "sgtl5000", + .of_match_table = sgtl5000_dt_ids, + }, + .probe = sgtl5000_i2c_probe, + .remove = sgtl5000_i2c_remove, + .id_table = sgtl5000_id, +}; + +module_i2c_driver(sgtl5000_i2c_driver); + +MODULE_DESCRIPTION("Freescale SGTL5000 ALSA SoC Codec Driver"); +MODULE_AUTHOR("Zeng Zhaoming "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h new file mode 100644 index 000000000..066517e35 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.h @@ -0,0 +1,407 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * sgtl5000.h - SGTL5000 audio codec interface + * + * Copyright 2010-2011 Freescale Semiconductor, Inc. + */ + +#ifndef _SGTL5000_H +#define _SGTL5000_H + +/* + * Registers addresses + */ +#define SGTL5000_CHIP_ID 0x0000 +#define SGTL5000_CHIP_DIG_POWER 0x0002 +#define SGTL5000_CHIP_CLK_CTRL 0x0004 +#define SGTL5000_CHIP_I2S_CTRL 0x0006 +#define SGTL5000_CHIP_SSS_CTRL 0x000a +#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e +#define SGTL5000_CHIP_DAC_VOL 0x0010 +#define SGTL5000_CHIP_PAD_STRENGTH 0x0014 +#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020 +#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022 +#define SGTL5000_CHIP_ANA_CTRL 0x0024 +#define SGTL5000_CHIP_LINREG_CTRL 0x0026 +#define SGTL5000_CHIP_REF_CTRL 0x0028 +#define SGTL5000_CHIP_MIC_CTRL 0x002a +#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c +#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e +#define SGTL5000_CHIP_ANA_POWER 0x0030 +#define SGTL5000_CHIP_PLL_CTRL 0x0032 +#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034 +#define SGTL5000_CHIP_ANA_STATUS 0x0036 +#define SGTL5000_CHIP_SHORT_CTRL 0x003c +#define SGTL5000_CHIP_ANA_TEST2 0x003a +#define SGTL5000_DAP_CTRL 0x0100 +#define SGTL5000_DAP_PEQ 0x0102 +#define SGTL5000_DAP_BASS_ENHANCE 0x0104 +#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106 +#define SGTL5000_DAP_AUDIO_EQ 0x0108 +#define SGTL5000_DAP_SURROUND 0x010a +#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c +#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e +#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110 +#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116 +#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118 +#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a +#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c +#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e +#define SGTL5000_DAP_MAIN_CHAN 0x0120 +#define SGTL5000_DAP_MIX_CHAN 0x0122 +#define SGTL5000_DAP_AVC_CTRL 0x0124 +#define SGTL5000_DAP_AVC_THRESHOLD 0x0126 +#define SGTL5000_DAP_AVC_ATTACK 0x0128 +#define SGTL5000_DAP_AVC_DECAY 0x012a +#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c +#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e +#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130 +#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132 +#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134 +#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136 +#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138 +#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a + +/* + * Field Definitions. + */ + +/* + * SGTL5000_CHIP_ID + */ +#define SGTL5000_PARTID_MASK 0xff00 +#define SGTL5000_PARTID_SHIFT 8 +#define SGTL5000_PARTID_WIDTH 8 +#define SGTL5000_PARTID_PART_ID 0xa0 +#define SGTL5000_REVID_MASK 0x00ff +#define SGTL5000_REVID_SHIFT 0 +#define SGTL5000_REVID_WIDTH 8 + +/* + * SGTL5000_CHIP_DIG_POWER + */ +#define SGTL5000_ADC_EN 0x0040 +#define SGTL5000_DAC_EN 0x0020 +#define SGTL5000_DAP_POWERUP 0x0010 +#define SGTL5000_I2S_OUT_POWERUP 0x0002 +#define SGTL5000_I2S_IN_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_CLK_CTRL + */ +#define SGTL5000_CHIP_CLK_CTRL_DEFAULT 0x0008 +#define SGTL5000_RATE_MODE_MASK 0x0030 +#define SGTL5000_RATE_MODE_SHIFT 4 +#define SGTL5000_RATE_MODE_WIDTH 2 +#define SGTL5000_RATE_MODE_DIV_1 0 +#define SGTL5000_RATE_MODE_DIV_2 1 +#define SGTL5000_RATE_MODE_DIV_4 2 +#define SGTL5000_RATE_MODE_DIV_6 3 +#define SGTL5000_SYS_FS_MASK 0x000c +#define SGTL5000_SYS_FS_SHIFT 2 +#define SGTL5000_SYS_FS_WIDTH 2 +#define SGTL5000_SYS_FS_32k 0x0 +#define SGTL5000_SYS_FS_44_1k 0x1 +#define SGTL5000_SYS_FS_48k 0x2 +#define SGTL5000_SYS_FS_96k 0x3 +#define SGTL5000_MCLK_FREQ_MASK 0x0003 +#define SGTL5000_MCLK_FREQ_SHIFT 0 +#define SGTL5000_MCLK_FREQ_WIDTH 2 +#define SGTL5000_MCLK_FREQ_256FS 0x0 +#define SGTL5000_MCLK_FREQ_384FS 0x1 +#define SGTL5000_MCLK_FREQ_512FS 0x2 +#define SGTL5000_MCLK_FREQ_PLL 0x3 + +/* + * SGTL5000_CHIP_I2S_CTRL + */ +#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100 +#define SGTL5000_I2S_SCLKFREQ_SHIFT 8 +#define SGTL5000_I2S_SCLKFREQ_WIDTH 1 +#define SGTL5000_I2S_SCLKFREQ_64FS 0x0 +#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */ +#define SGTL5000_I2S_MASTER 0x0080 +#define SGTL5000_I2S_SCLK_INV 0x0040 +#define SGTL5000_I2S_DLEN_MASK 0x0030 +#define SGTL5000_I2S_DLEN_SHIFT 4 +#define SGTL5000_I2S_DLEN_WIDTH 2 +#define SGTL5000_I2S_DLEN_32 0x0 +#define SGTL5000_I2S_DLEN_24 0x1 +#define SGTL5000_I2S_DLEN_20 0x2 +#define SGTL5000_I2S_DLEN_16 0x3 +#define SGTL5000_I2S_MODE_MASK 0x000c +#define SGTL5000_I2S_MODE_SHIFT 2 +#define SGTL5000_I2S_MODE_WIDTH 2 +#define SGTL5000_I2S_MODE_I2S_LJ 0x0 +#define SGTL5000_I2S_MODE_RJ 0x1 +#define SGTL5000_I2S_MODE_PCM 0x2 +#define SGTL5000_I2S_LRALIGN 0x0002 +#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */ + +/* + * SGTL5000_CHIP_SSS_CTRL + */ +#define SGTL5000_DAP_MIX_LRSWAP 0x4000 +#define SGTL5000_DAP_LRSWAP 0x2000 +#define SGTL5000_DAC_LRSWAP 0x1000 +#define SGTL5000_I2S_OUT_LRSWAP 0x0400 +#define SGTL5000_DAP_MIX_SEL_MASK 0x0300 +#define SGTL5000_DAP_MIX_SEL_SHIFT 8 +#define SGTL5000_DAP_MIX_SEL_WIDTH 2 +#define SGTL5000_DAP_MIX_SEL_ADC 0x0 +#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1 +#define SGTL5000_DAP_SEL_MASK 0x00c0 +#define SGTL5000_DAP_SEL_SHIFT 6 +#define SGTL5000_DAP_SEL_WIDTH 2 +#define SGTL5000_DAP_SEL_ADC 0x0 +#define SGTL5000_DAP_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_MASK 0x0030 +#define SGTL5000_DAC_SEL_SHIFT 4 +#define SGTL5000_DAC_SEL_WIDTH 2 +#define SGTL5000_DAC_SEL_ADC 0x0 +#define SGTL5000_DAC_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_DAP 0x3 +#define SGTL5000_I2S_OUT_SEL_MASK 0x0003 +#define SGTL5000_I2S_OUT_SEL_SHIFT 0 +#define SGTL5000_I2S_OUT_SEL_WIDTH 2 +#define SGTL5000_I2S_OUT_SEL_ADC 0x0 +#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1 +#define SGTL5000_I2S_OUT_SEL_DAP 0x3 + +/* + * SGTL5000_CHIP_ADCDAC_CTRL + */ +#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000 +#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000 +#define SGTL5000_DAC_VOL_RAMP_EN 0x0200 +#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100 +#define SGTL5000_DAC_MUTE_RIGHT 0x0008 +#define SGTL5000_DAC_MUTE_LEFT 0x0004 +#define SGTL5000_ADC_HPF_FREEZE 0x0002 +#define SGTL5000_ADC_HPF_BYPASS 0x0001 + +/* + * SGTL5000_CHIP_DAC_VOL + */ +#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00 +#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8 +#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8 +#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff +#define SGTL5000_DAC_VOL_LEFT_SHIFT 0 +#define SGTL5000_DAC_VOL_LEFT_WIDTH 8 + +/* + * SGTL5000_CHIP_PAD_STRENGTH + */ +#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300 +#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8 +#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0 +#define SGTL5000_PAD_I2S_SCLK_SHIFT 6 +#define SGTL5000_PAD_I2S_SCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030 +#define SGTL5000_PAD_I2S_DOUT_SHIFT 4 +#define SGTL5000_PAD_I2S_DOUT_WIDTH 2 +#define SGTL5000_PAD_I2C_SDA_MASK 0x000c +#define SGTL5000_PAD_I2C_SDA_SHIFT 2 +#define SGTL5000_PAD_I2C_SDA_WIDTH 2 +#define SGTL5000_PAD_I2C_SCL_MASK 0x0003 +#define SGTL5000_PAD_I2C_SCL_SHIFT 0 +#define SGTL5000_PAD_I2C_SCL_WIDTH 2 + +/* + * SGTL5000_CHIP_ANA_ADC_CTRL + */ +#define SGTL5000_ADC_VOL_M6DB 0x0100 +#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0 +#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4 +#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4 +#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f +#define SGTL5000_ADC_VOL_LEFT_SHIFT 0 +#define SGTL5000_ADC_VOL_LEFT_WIDTH 4 + +/* + * SGTL5000_CHIP_ANA_HP_CTRL + */ +#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00 +#define SGTL5000_HP_VOL_RIGHT_SHIFT 8 +#define SGTL5000_HP_VOL_RIGHT_WIDTH 7 +#define SGTL5000_HP_VOL_LEFT_MASK 0x007f +#define SGTL5000_HP_VOL_LEFT_SHIFT 0 +#define SGTL5000_HP_VOL_LEFT_WIDTH 7 + +/* + * SGTL5000_CHIP_ANA_CTRL + */ +#define SGTL5000_CHIP_ANA_CTRL_DEFAULT 0x0133 +#define SGTL5000_LINE_OUT_MUTE 0x0100 +#define SGTL5000_HP_SEL_MASK 0x0040 +#define SGTL5000_HP_SEL_SHIFT 6 +#define SGTL5000_HP_SEL_WIDTH 1 +#define SGTL5000_HP_SEL_DAC 0x0 +#define SGTL5000_HP_SEL_LINE_IN 0x1 +#define SGTL5000_HP_ZCD_EN 0x0020 +#define SGTL5000_HP_MUTE 0x0010 +#define SGTL5000_ADC_SEL_MASK 0x0004 +#define SGTL5000_ADC_SEL_SHIFT 2 +#define SGTL5000_ADC_SEL_WIDTH 1 +#define SGTL5000_ADC_SEL_MIC 0x0 +#define SGTL5000_ADC_SEL_LINE_IN 0x1 +#define SGTL5000_ADC_ZCD_EN 0x0002 +#define SGTL5000_ADC_MUTE 0x0001 + +/* + * SGTL5000_CHIP_LINREG_CTRL + */ +#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040 +#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6 +#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1 +#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0 +#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1 +#define SGTL5000_VDDC_ASSN_OVRD 0x0020 +#define SGTL5000_LINREG_VDDD_MASK 0x000f +#define SGTL5000_LINREG_VDDD_SHIFT 0 +#define SGTL5000_LINREG_VDDD_WIDTH 4 + +/* + * SGTL5000_CHIP_REF_CTRL + */ +#define SGTL5000_ANA_GND_MASK 0x01f0 +#define SGTL5000_ANA_GND_SHIFT 4 +#define SGTL5000_ANA_GND_WIDTH 5 +#define SGTL5000_ANA_GND_BASE 800 /* mv */ +#define SGTL5000_ANA_GND_STP 25 /*mv */ +#define SGTL5000_BIAS_CTRL_MASK 0x000e +#define SGTL5000_BIAS_CTRL_SHIFT 1 +#define SGTL5000_BIAS_CTRL_WIDTH 3 +#define SGTL5000_SMALL_POP 1 + +/* + * SGTL5000_CHIP_MIC_CTRL + */ +#define SGTL5000_BIAS_R_MASK 0x0300 +#define SGTL5000_BIAS_R_SHIFT 8 +#define SGTL5000_BIAS_R_WIDTH 2 +#define SGTL5000_BIAS_R_off 0x0 +#define SGTL5000_BIAS_R_2K 0x1 +#define SGTL5000_BIAS_R_4k 0x2 +#define SGTL5000_BIAS_R_8k 0x3 +#define SGTL5000_BIAS_VOLT_MASK 0x0070 +#define SGTL5000_BIAS_VOLT_SHIFT 4 +#define SGTL5000_BIAS_VOLT_WIDTH 3 +#define SGTL5000_MIC_GAIN_MASK 0x0003 +#define SGTL5000_MIC_GAIN_SHIFT 0 +#define SGTL5000_MIC_GAIN_WIDTH 2 + +/* + * SGTL5000_CHIP_LINE_OUT_CTRL + */ +#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00 +#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8 +#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4 +#define SGTL5000_LINE_OUT_CURRENT_180u 0x0 +#define SGTL5000_LINE_OUT_CURRENT_270u 0x1 +#define SGTL5000_LINE_OUT_CURRENT_360u 0x3 +#define SGTL5000_LINE_OUT_CURRENT_450u 0x7 +#define SGTL5000_LINE_OUT_CURRENT_540u 0xf +#define SGTL5000_LINE_OUT_GND_MASK 0x003f +#define SGTL5000_LINE_OUT_GND_SHIFT 0 +#define SGTL5000_LINE_OUT_GND_WIDTH 6 +#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */ +#define SGTL5000_LINE_OUT_GND_STP 25 +#define SGTL5000_LINE_OUT_GND_MAX 0x23 + +/* + * SGTL5000_CHIP_LINE_OUT_VOL + */ +#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00 +#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8 +#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5 +#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f +#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0 +#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5 + +/* + * SGTL5000_CHIP_ANA_POWER + */ +#define SGTL5000_ANA_POWER_DEFAULT 0x7060 +#define SGTL5000_DAC_STEREO 0x4000 +#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 +#define SGTL5000_STARTUP_POWERUP 0x1000 +#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800 +#define SGTL5000_PLL_POWERUP 0x0400 +#define SGTL5000_LINEREG_D_POWERUP 0x0200 +#define SGTL5000_VCOAMP_POWERUP 0x0100 +#define SGTL5000_VAG_POWERUP 0x0080 +#define SGTL5000_ADC_STEREO 0x0040 +#define SGTL5000_REFTOP_POWERUP 0x0020 +#define SGTL5000_HP_POWERUP 0x0010 +#define SGTL5000_DAC_POWERUP 0x0008 +#define SGTL5000_CAPLESS_HP_POWERUP 0x0004 +#define SGTL5000_ADC_POWERUP 0x0002 +#define SGTL5000_LINE_OUT_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_PLL_CTRL + */ +#define SGTL5000_PLL_INT_DIV_MASK 0xf800 +#define SGTL5000_PLL_INT_DIV_SHIFT 11 +#define SGTL5000_PLL_INT_DIV_WIDTH 5 +#define SGTL5000_PLL_FRAC_DIV_MASK 0x07ff +#define SGTL5000_PLL_FRAC_DIV_SHIFT 0 +#define SGTL5000_PLL_FRAC_DIV_WIDTH 11 + +/* + * SGTL5000_CHIP_CLK_TOP_CTRL + */ +#define SGTL5000_INT_OSC_EN 0x0800 +#define SGTL5000_INPUT_FREQ_DIV2 0x0008 + +/* + * SGTL5000_CHIP_ANA_STATUS + */ +#define SGTL5000_HP_LRSHORT 0x0200 +#define SGTL5000_CAPLESS_SHORT 0x0100 +#define SGTL5000_PLL_LOCKED 0x0010 + +/* + * SGTL5000_CHIP_SHORT_CTRL + */ +#define SGTL5000_LVLADJR_MASK 0x7000 +#define SGTL5000_LVLADJR_SHIFT 12 +#define SGTL5000_LVLADJR_WIDTH 3 +#define SGTL5000_LVLADJL_MASK 0x0700 +#define SGTL5000_LVLADJL_SHIFT 8 +#define SGTL5000_LVLADJL_WIDTH 3 +#define SGTL5000_LVLADJC_MASK 0x0070 +#define SGTL5000_LVLADJC_SHIFT 4 +#define SGTL5000_LVLADJC_WIDTH 3 +#define SGTL5000_LR_SHORT_MOD_MASK 0x000c +#define SGTL5000_LR_SHORT_MOD_SHIFT 2 +#define SGTL5000_LR_SHORT_MOD_WIDTH 2 +#define SGTL5000_CM_SHORT_MOD_MASK 0x0003 +#define SGTL5000_CM_SHORT_MOD_SHIFT 0 +#define SGTL5000_CM_SHORT_MOD_WIDTH 2 + +/* + *SGTL5000_CHIP_ANA_TEST2 + */ +#define SGTL5000_MONO_DAC 0x1000 + +/* + * SGTL5000_DAP_CTRL + */ +#define SGTL5000_DAP_MIX_EN 0x0010 +#define SGTL5000_DAP_EN 0x0001 + +#define SGTL5000_SYSCLK 0x00 +#define SGTL5000_LRCLK 0x01 + +/* + * SGTL5000_DAP_AUDIO_EQ + */ +#define SGTL5000_DAP_SEL_PEQ 1 +#define SGTL5000_DAP_SEL_TONE_CTRL 2 +#define SGTL5000_DAP_SEL_GEQ 3 + +#endif diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c new file mode 100644 index 000000000..b779c2855 --- /dev/null +++ b/sound/soc/codecs/si476x.c @@ -0,0 +1,273 @@ +/* + * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +enum si476x_audio_registers { + SI476X_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, + SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, +}; + +enum si476x_digital_io_output_format { + SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT = 11, + SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT = 8, +}; + +#define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK ((0x7 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \ + (0x7 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)) +#define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK (0x7e) + +enum si476x_daudio_formats { + SI476X_DAUDIO_MODE_I2S = (0x0 << 1), + SI476X_DAUDIO_MODE_DSP_A = (0x6 << 1), + SI476X_DAUDIO_MODE_DSP_B = (0x7 << 1), + SI476X_DAUDIO_MODE_LEFT_J = (0x8 << 1), + SI476X_DAUDIO_MODE_RIGHT_J = (0x9 << 1), + + SI476X_DAUDIO_MODE_IB = (1 << 5), + SI476X_DAUDIO_MODE_IF = (1 << 6), +}; + +enum si476x_pcm_format { + SI476X_PCM_FORMAT_S8 = 2, + SI476X_PCM_FORMAT_S16_LE = 4, + SI476X_PCM_FORMAT_S20_3LE = 5, + SI476X_PCM_FORMAT_S24_LE = 6, +}; + +static const struct snd_soc_dapm_widget si476x_dapm_widgets[] = { +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +}; + +static const struct snd_soc_dapm_route si476x_dapm_routes[] = { + { "Capture", NULL, "LOUT" }, + { "Capture", NULL, "ROUT" }, +}; + +static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct si476x_core *core = i2c_mfd_cell_to_core(codec_dai->dev); + int err; + u16 format = 0; + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + format |= SI476X_DAUDIO_MODE_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + format |= SI476X_DAUDIO_MODE_DSP_B; + break; + case SND_SOC_DAIFMT_I2S: + format |= SI476X_DAUDIO_MODE_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + format |= SI476X_DAUDIO_MODE_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + format |= SI476X_DAUDIO_MODE_LEFT_J; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + format |= SI476X_DAUDIO_MODE_IB; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + format |= SI476X_DAUDIO_MODE_IB | + SI476X_DAUDIO_MODE_IF; + break; + case SND_SOC_DAIFMT_IB_NF: + format |= SI476X_DAUDIO_MODE_IB; + break; + case SND_SOC_DAIFMT_NB_IF: + format |= SI476X_DAUDIO_MODE_IF; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + si476x_core_lock(core); + + err = snd_soc_component_update_bits(codec_dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT, + SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK, + format); + + si476x_core_unlock(core); + + if (err < 0) { + dev_err(codec_dai->component->dev, "Failed to set output format\n"); + return err; + } + + return 0; +} + +static int si476x_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); + int rate, width, err; + + rate = params_rate(params); + if (rate < 32000 || rate > 48000) { + dev_err(dai->component->dev, "Rate: %d is not supported\n", rate); + return -EINVAL; + } + + switch (params_width(params)) { + case 8: + width = SI476X_PCM_FORMAT_S8; + break; + case 16: + width = SI476X_PCM_FORMAT_S16_LE; + break; + case 20: + width = SI476X_PCM_FORMAT_S20_3LE; + break; + case 24: + width = SI476X_PCM_FORMAT_S24_LE; + break; + default: + return -EINVAL; + } + + si476x_core_lock(core); + + err = snd_soc_component_write(dai->component, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE, + rate); + if (err < 0) { + dev_err(dai->component->dev, "Failed to set sample rate\n"); + goto out; + } + + err = snd_soc_component_update_bits(dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT, + SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK, + (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | + (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)); + if (err < 0) { + dev_err(dai->component->dev, "Failed to set output width\n"); + goto out; + } + +out: + si476x_core_unlock(core); + + return err; +} + +static const struct snd_soc_dai_ops si476x_dai_ops = { + .hw_params = si476x_codec_hw_params, + .set_fmt = si476x_codec_set_dai_fmt, +}; + +static struct snd_soc_dai_driver si476x_dai = { + .name = "si476x-codec", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE + }, + .ops = &si476x_dai_ops, +}; + +static int si476x_probe(struct snd_soc_component *component) +{ + snd_soc_component_init_regmap(component, + dev_get_regmap(component->dev->parent, NULL)); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_si476x = { + .probe = si476x_probe, + .dapm_widgets = si476x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(si476x_dapm_widgets), + .dapm_routes = si476x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(si476x_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int si476x_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_si476x, + &si476x_dai, 1); +} + +MODULE_ALIAS("platform:si476x-codec"); + +static struct platform_driver si476x_platform_driver = { + .driver = { + .name = "si476x-codec", + }, + .probe = si476x_platform_probe, +}; +module_platform_driver(si476x_platform_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("ASoC Si4761/64 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp-i2c.c b/sound/soc/codecs/sigmadsp-i2c.c new file mode 100644 index 000000000..d374c18d4 --- /dev/null +++ b/sound/soc/codecs/sigmadsp-i2c.c @@ -0,0 +1,97 @@ +/* + * Load Analog Devices SigmaStudio firmware files + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "sigmadsp.h" + +static int sigmadsp_write_i2c(void *control_data, + unsigned int addr, const uint8_t data[], size_t len) +{ + uint8_t *buf; + int ret; + + buf = kzalloc(2 + len, GFP_KERNEL | GFP_DMA); + if (!buf) + return -ENOMEM; + + put_unaligned_be16(addr, buf); + memcpy(buf + 2, data, len); + + ret = i2c_master_send(control_data, buf, len + 2); + + kfree(buf); + + if (ret < 0) + return ret; + + return 0; +} + +static int sigmadsp_read_i2c(void *control_data, + unsigned int addr, uint8_t data[], size_t len) +{ + struct i2c_client *client = control_data; + struct i2c_msg msgs[2]; + uint8_t buf[2]; + int ret; + + put_unaligned_be16(addr, buf); + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(buf); + msgs[0].buf = buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = len; + msgs[1].buf = data; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + else if (ret != ARRAY_SIZE(msgs)) + return -EIO; + return 0; +} + +/** + * devm_sigmadsp_init_i2c() - Initialize SigmaDSP instance + * @client: The parent I2C device + * @ops: The sigmadsp_ops to use for this instance + * @firmware_name: Name of the firmware file to load + * + * Allocates a SigmaDSP instance and loads the specified firmware file. + * + * Returns a pointer to a struct sigmadsp on success, or a PTR_ERR() on error. + */ +struct sigmadsp *devm_sigmadsp_init_i2c(struct i2c_client *client, + const struct sigmadsp_ops *ops, const char *firmware_name) +{ + struct sigmadsp *sigmadsp; + + sigmadsp = devm_sigmadsp_init(&client->dev, ops, firmware_name); + if (IS_ERR(sigmadsp)) + return sigmadsp; + + sigmadsp->control_data = client; + sigmadsp->write = sigmadsp_write_i2c; + sigmadsp->read = sigmadsp_read_i2c; + + return sigmadsp; +} +EXPORT_SYMBOL_GPL(devm_sigmadsp_init_i2c); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("SigmaDSP I2C firmware loader"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp-regmap.c b/sound/soc/codecs/sigmadsp-regmap.c new file mode 100644 index 000000000..912861be5 --- /dev/null +++ b/sound/soc/codecs/sigmadsp-regmap.c @@ -0,0 +1,60 @@ +/* + * Load Analog Devices SigmaStudio firmware files + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include + +#include "sigmadsp.h" + +static int sigmadsp_write_regmap(void *control_data, + unsigned int addr, const uint8_t data[], size_t len) +{ + return regmap_raw_write(control_data, addr, + data, len); +} + +static int sigmadsp_read_regmap(void *control_data, + unsigned int addr, uint8_t data[], size_t len) +{ + return regmap_raw_read(control_data, addr, + data, len); +} + +/** + * devm_sigmadsp_init_i2c() - Initialize SigmaDSP instance + * @dev: The parent device + * @regmap: Regmap instance to use + * @ops: The sigmadsp_ops to use for this instance + * @firmware_name: Name of the firmware file to load + * + * Allocates a SigmaDSP instance and loads the specified firmware file. + * + * Returns a pointer to a struct sigmadsp on success, or a PTR_ERR() on error. + */ +struct sigmadsp *devm_sigmadsp_init_regmap(struct device *dev, + struct regmap *regmap, const struct sigmadsp_ops *ops, + const char *firmware_name) +{ + struct sigmadsp *sigmadsp; + + sigmadsp = devm_sigmadsp_init(dev, ops, firmware_name); + if (IS_ERR(sigmadsp)) + return sigmadsp; + + sigmadsp->control_data = regmap; + sigmadsp->write = sigmadsp_write_regmap; + sigmadsp->read = sigmadsp_read_regmap; + + return sigmadsp; +} +EXPORT_SYMBOL_GPL(devm_sigmadsp_init_regmap); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("SigmaDSP regmap firmware loader"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c new file mode 100644 index 000000000..6df158669 --- /dev/null +++ b/sound/soc/codecs/sigmadsp.c @@ -0,0 +1,813 @@ +/* + * Load Analog Devices SigmaStudio firmware files + * + * Copyright 2009-2014 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sigmadsp.h" + +#define SIGMA_MAGIC "ADISIGM" + +#define SIGMA_FW_CHUNK_TYPE_DATA 0 +#define SIGMA_FW_CHUNK_TYPE_CONTROL 1 +#define SIGMA_FW_CHUNK_TYPE_SAMPLERATES 2 + +struct sigmadsp_control { + struct list_head head; + uint32_t samplerates; + unsigned int addr; + unsigned int num_bytes; + const char *name; + struct snd_kcontrol *kcontrol; + bool cached; + uint8_t cache[]; +}; + +struct sigmadsp_data { + struct list_head head; + uint32_t samplerates; + unsigned int addr; + unsigned int length; + uint8_t data[]; +}; + +struct sigma_fw_chunk { + __le32 length; + __le32 tag; + __le32 samplerates; +} __packed; + +struct sigma_fw_chunk_data { + struct sigma_fw_chunk chunk; + __le16 addr; + uint8_t data[]; +} __packed; + +struct sigma_fw_chunk_control { + struct sigma_fw_chunk chunk; + __le16 type; + __le16 addr; + __le16 num_bytes; + const char name[]; +} __packed; + +struct sigma_fw_chunk_samplerate { + struct sigma_fw_chunk chunk; + __le32 samplerates[]; +} __packed; + +struct sigma_firmware_header { + unsigned char magic[7]; + u8 version; + __le32 crc; +} __packed; + +enum { + SIGMA_ACTION_WRITEXBYTES = 0, + SIGMA_ACTION_WRITESINGLE, + SIGMA_ACTION_WRITESAFELOAD, + SIGMA_ACTION_END, +}; + +struct sigma_action { + u8 instr; + u8 len_hi; + __le16 len; + __be16 addr; + unsigned char payload[]; +} __packed; + +static int sigmadsp_write(struct sigmadsp *sigmadsp, unsigned int addr, + const uint8_t data[], size_t len) +{ + return sigmadsp->write(sigmadsp->control_data, addr, data, len); +} + +static int sigmadsp_read(struct sigmadsp *sigmadsp, unsigned int addr, + uint8_t data[], size_t len) +{ + return sigmadsp->read(sigmadsp->control_data, addr, data, len); +} + +static int sigmadsp_ctrl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + + info->type = SNDRV_CTL_ELEM_TYPE_BYTES; + info->count = ctrl->num_bytes; + + return 0; +} + +static int sigmadsp_ctrl_write(struct sigmadsp *sigmadsp, + struct sigmadsp_control *ctrl, void *data) +{ + /* safeload loads up to 20 bytes in a atomic operation */ + if (ctrl->num_bytes <= 20 && sigmadsp->ops && sigmadsp->ops->safeload) + return sigmadsp->ops->safeload(sigmadsp, ctrl->addr, data, + ctrl->num_bytes); + else + return sigmadsp_write(sigmadsp, ctrl->addr, data, + ctrl->num_bytes); +} + +static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + uint8_t *data; + int ret = 0; + + mutex_lock(&sigmadsp->lock); + + data = ucontrol->value.bytes.data; + + if (!(kcontrol->vd[0].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) + ret = sigmadsp_ctrl_write(sigmadsp, ctrl, data); + + if (ret == 0) { + memcpy(ctrl->cache, data, ctrl->num_bytes); + ctrl->cached = true; + } + + mutex_unlock(&sigmadsp->lock); + + return ret; +} + +static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + int ret = 0; + + mutex_lock(&sigmadsp->lock); + + if (!ctrl->cached) { + ret = sigmadsp_read(sigmadsp, ctrl->addr, ctrl->cache, + ctrl->num_bytes); + } + + if (ret == 0) { + ctrl->cached = true; + memcpy(ucontrol->value.bytes.data, ctrl->cache, + ctrl->num_bytes); + } + + mutex_unlock(&sigmadsp->lock); + + return ret; +} + +static void sigmadsp_control_free(struct snd_kcontrol *kcontrol) +{ + struct sigmadsp_control *ctrl = (void *)kcontrol->private_value; + + ctrl->kcontrol = NULL; +} + +static bool sigma_fw_validate_control_name(const char *name, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) { + /* Normal ASCII characters are valid */ + if (name[i] < ' ' || name[i] > '~') + return false; + } + + return true; +} + +static int sigma_fw_load_control(struct sigmadsp *sigmadsp, + const struct sigma_fw_chunk *chunk, unsigned int length) +{ + const struct sigma_fw_chunk_control *ctrl_chunk; + struct sigmadsp_control *ctrl; + unsigned int num_bytes; + size_t name_len; + char *name; + int ret; + + if (length <= sizeof(*ctrl_chunk)) + return -EINVAL; + + ctrl_chunk = (const struct sigma_fw_chunk_control *)chunk; + + name_len = length - sizeof(*ctrl_chunk); + if (name_len >= SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + name_len = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1; + + /* Make sure there are no non-displayable characaters in the string */ + if (!sigma_fw_validate_control_name(ctrl_chunk->name, name_len)) + return -EINVAL; + + num_bytes = le16_to_cpu(ctrl_chunk->num_bytes); + ctrl = kzalloc(sizeof(*ctrl) + num_bytes, GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + name = kzalloc(name_len + 1, GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto err_free_ctrl; + } + memcpy(name, ctrl_chunk->name, name_len); + name[name_len] = '\0'; + ctrl->name = name; + + ctrl->addr = le16_to_cpu(ctrl_chunk->addr); + ctrl->num_bytes = num_bytes; + ctrl->samplerates = le32_to_cpu(chunk->samplerates); + + list_add_tail(&ctrl->head, &sigmadsp->ctrl_list); + + return 0; + +err_free_ctrl: + kfree(ctrl); + + return ret; +} + +static int sigma_fw_load_data(struct sigmadsp *sigmadsp, + const struct sigma_fw_chunk *chunk, unsigned int length) +{ + const struct sigma_fw_chunk_data *data_chunk; + struct sigmadsp_data *data; + + if (length <= sizeof(*data_chunk)) + return -EINVAL; + + data_chunk = (struct sigma_fw_chunk_data *)chunk; + + length -= sizeof(*data_chunk); + + data = kzalloc(sizeof(*data) + length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = le16_to_cpu(data_chunk->addr); + data->length = length; + data->samplerates = le32_to_cpu(chunk->samplerates); + memcpy(data->data, data_chunk->data, length); + list_add_tail(&data->head, &sigmadsp->data_list); + + return 0; +} + +static int sigma_fw_load_samplerates(struct sigmadsp *sigmadsp, + const struct sigma_fw_chunk *chunk, unsigned int length) +{ + const struct sigma_fw_chunk_samplerate *rate_chunk; + unsigned int num_rates; + unsigned int *rates; + unsigned int i; + + rate_chunk = (const struct sigma_fw_chunk_samplerate *)chunk; + + num_rates = (length - sizeof(*rate_chunk)) / sizeof(__le32); + + if (num_rates > 32 || num_rates == 0) + return -EINVAL; + + /* We only allow one samplerates block per file */ + if (sigmadsp->rate_constraints.count) + return -EINVAL; + + rates = kcalloc(num_rates, sizeof(*rates), GFP_KERNEL); + if (!rates) + return -ENOMEM; + + for (i = 0; i < num_rates; i++) + rates[i] = le32_to_cpu(rate_chunk->samplerates[i]); + + sigmadsp->rate_constraints.count = num_rates; + sigmadsp->rate_constraints.list = rates; + + return 0; +} + +static int sigmadsp_fw_load_v2(struct sigmadsp *sigmadsp, + const struct firmware *fw) +{ + struct sigma_fw_chunk *chunk; + unsigned int length, pos; + int ret; + + /* + * Make sure that there is at least one chunk to avoid integer + * underflows later on. Empty firmware is still valid though. + */ + if (fw->size < sizeof(*chunk) + sizeof(struct sigma_firmware_header)) + return 0; + + pos = sizeof(struct sigma_firmware_header); + + while (pos < fw->size - sizeof(*chunk)) { + chunk = (struct sigma_fw_chunk *)(fw->data + pos); + + length = le32_to_cpu(chunk->length); + + if (length > fw->size - pos || length < sizeof(*chunk)) + return -EINVAL; + + switch (le32_to_cpu(chunk->tag)) { + case SIGMA_FW_CHUNK_TYPE_DATA: + ret = sigma_fw_load_data(sigmadsp, chunk, length); + break; + case SIGMA_FW_CHUNK_TYPE_CONTROL: + ret = sigma_fw_load_control(sigmadsp, chunk, length); + break; + case SIGMA_FW_CHUNK_TYPE_SAMPLERATES: + ret = sigma_fw_load_samplerates(sigmadsp, chunk, length); + break; + default: + dev_warn(sigmadsp->dev, "Unknown chunk type: %d\n", + chunk->tag); + ret = 0; + break; + } + + if (ret) + return ret; + + /* + * This can not overflow since if length is larger than the + * maximum firmware size (0x4000000) we'll error out earilier. + */ + pos += ALIGN(length, sizeof(__le32)); + } + + return 0; +} + +static inline u32 sigma_action_len(struct sigma_action *sa) +{ + return (sa->len_hi << 16) | le16_to_cpu(sa->len); +} + +static size_t sigma_action_size(struct sigma_action *sa) +{ + size_t payload = 0; + + switch (sa->instr) { + case SIGMA_ACTION_WRITEXBYTES: + case SIGMA_ACTION_WRITESINGLE: + case SIGMA_ACTION_WRITESAFELOAD: + payload = sigma_action_len(sa); + break; + default: + break; + } + + payload = ALIGN(payload, 2); + + return payload + sizeof(struct sigma_action); +} + +/* + * Returns a negative error value in case of an error, 0 if processing of + * the firmware should be stopped after this action, 1 otherwise. + */ +static int process_sigma_action(struct sigmadsp *sigmadsp, + struct sigma_action *sa) +{ + size_t len = sigma_action_len(sa); + struct sigmadsp_data *data; + + pr_debug("%s: instr:%i addr:%#x len:%zu\n", __func__, + sa->instr, sa->addr, len); + + switch (sa->instr) { + case SIGMA_ACTION_WRITEXBYTES: + case SIGMA_ACTION_WRITESINGLE: + case SIGMA_ACTION_WRITESAFELOAD: + if (len < 3) + return -EINVAL; + + data = kzalloc(sizeof(*data) + len - 2, GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->addr = be16_to_cpu(sa->addr); + data->length = len - 2; + memcpy(data->data, sa->payload, data->length); + list_add_tail(&data->head, &sigmadsp->data_list); + break; + case SIGMA_ACTION_END: + return 0; + default: + return -EINVAL; + } + + return 1; +} + +static int sigmadsp_fw_load_v1(struct sigmadsp *sigmadsp, + const struct firmware *fw) +{ + struct sigma_action *sa; + size_t size, pos; + int ret; + + pos = sizeof(struct sigma_firmware_header); + + while (pos + sizeof(*sa) <= fw->size) { + sa = (struct sigma_action *)(fw->data + pos); + + size = sigma_action_size(sa); + pos += size; + if (pos > fw->size || size == 0) + break; + + ret = process_sigma_action(sigmadsp, sa); + + pr_debug("%s: action returned %i\n", __func__, ret); + + if (ret <= 0) + return ret; + } + + if (pos != fw->size) + return -EINVAL; + + return 0; +} + +static void sigmadsp_firmware_release(struct sigmadsp *sigmadsp) +{ + struct sigmadsp_control *ctrl, *_ctrl; + struct sigmadsp_data *data, *_data; + + list_for_each_entry_safe(ctrl, _ctrl, &sigmadsp->ctrl_list, head) { + kfree(ctrl->name); + kfree(ctrl); + } + + list_for_each_entry_safe(data, _data, &sigmadsp->data_list, head) + kfree(data); + + INIT_LIST_HEAD(&sigmadsp->ctrl_list); + INIT_LIST_HEAD(&sigmadsp->data_list); +} + +static void devm_sigmadsp_release(struct device *dev, void *res) +{ + sigmadsp_firmware_release((struct sigmadsp *)res); +} + +static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) +{ + const struct sigma_firmware_header *ssfw_head; + const struct firmware *fw; + int ret; + u32 crc; + + /* first load the blob */ + ret = request_firmware(&fw, name, sigmadsp->dev); + if (ret) { + pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); + goto done; + } + + /* then verify the header */ + ret = -EINVAL; + + /* + * Reject too small or unreasonable large files. The upper limit has been + * chosen a bit arbitrarily, but it should be enough for all practical + * purposes and having the limit makes it easier to avoid integer + * overflows later in the loading process. + */ + if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); + goto done; + } + + ssfw_head = (void *)fw->data; + if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); + goto done; + } + + crc = crc32(0, fw->data + sizeof(*ssfw_head), + fw->size - sizeof(*ssfw_head)); + pr_debug("%s: crc=%x\n", __func__, crc); + if (crc != le32_to_cpu(ssfw_head->crc)) { + dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", + le32_to_cpu(ssfw_head->crc), crc); + goto done; + } + + switch (ssfw_head->version) { + case 1: + ret = sigmadsp_fw_load_v1(sigmadsp, fw); + break; + case 2: + ret = sigmadsp_fw_load_v2(sigmadsp, fw); + break; + default: + dev_err(sigmadsp->dev, + "Failed to load firmware: Invalid version %d. Supported firmware versions: 1, 2\n", + ssfw_head->version); + ret = -EINVAL; + break; + } + + if (ret) + sigmadsp_firmware_release(sigmadsp); + +done: + release_firmware(fw); + + return ret; +} + +static int sigmadsp_init(struct sigmadsp *sigmadsp, struct device *dev, + const struct sigmadsp_ops *ops, const char *firmware_name) +{ + sigmadsp->ops = ops; + sigmadsp->dev = dev; + + INIT_LIST_HEAD(&sigmadsp->ctrl_list); + INIT_LIST_HEAD(&sigmadsp->data_list); + mutex_init(&sigmadsp->lock); + + return sigmadsp_firmware_load(sigmadsp, firmware_name); +} + +/** + * devm_sigmadsp_init() - Initialize SigmaDSP instance + * @dev: The parent device + * @ops: The sigmadsp_ops to use for this instance + * @firmware_name: Name of the firmware file to load + * + * Allocates a SigmaDSP instance and loads the specified firmware file. + * + * Returns a pointer to a struct sigmadsp on success, or a PTR_ERR() on error. + */ +struct sigmadsp *devm_sigmadsp_init(struct device *dev, + const struct sigmadsp_ops *ops, const char *firmware_name) +{ + struct sigmadsp *sigmadsp; + int ret; + + sigmadsp = devres_alloc(devm_sigmadsp_release, sizeof(*sigmadsp), + GFP_KERNEL); + if (!sigmadsp) + return ERR_PTR(-ENOMEM); + + ret = sigmadsp_init(sigmadsp, dev, ops, firmware_name); + if (ret) { + devres_free(sigmadsp); + return ERR_PTR(ret); + } + + devres_add(dev, sigmadsp); + + return sigmadsp; +} +EXPORT_SYMBOL_GPL(devm_sigmadsp_init); + +static int sigmadsp_rate_to_index(struct sigmadsp *sigmadsp, unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < sigmadsp->rate_constraints.count; i++) { + if (sigmadsp->rate_constraints.list[i] == rate) + return i; + } + + return -EINVAL; +} + +static unsigned int sigmadsp_get_samplerate_mask(struct sigmadsp *sigmadsp, + unsigned int samplerate) +{ + int samplerate_index; + + if (samplerate == 0) + return 0; + + if (sigmadsp->rate_constraints.count) { + samplerate_index = sigmadsp_rate_to_index(sigmadsp, samplerate); + if (samplerate_index < 0) + return 0; + + return BIT(samplerate_index); + } else { + return ~0; + } +} + +static bool sigmadsp_samplerate_valid(unsigned int supported, + unsigned int requested) +{ + /* All samplerates are supported */ + if (!supported) + return true; + + return supported & requested; +} + +static int sigmadsp_alloc_control(struct sigmadsp *sigmadsp, + struct sigmadsp_control *ctrl, unsigned int samplerate_mask) +{ + struct snd_kcontrol_new template; + struct snd_kcontrol *kcontrol; + + memset(&template, 0, sizeof(template)); + template.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + template.name = ctrl->name; + template.info = sigmadsp_ctrl_info; + template.get = sigmadsp_ctrl_get; + template.put = sigmadsp_ctrl_put; + template.private_value = (unsigned long)ctrl; + template.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (!sigmadsp_samplerate_valid(ctrl->samplerates, samplerate_mask)) + template.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + + kcontrol = snd_ctl_new1(&template, sigmadsp); + if (!kcontrol) + return -ENOMEM; + + kcontrol->private_free = sigmadsp_control_free; + ctrl->kcontrol = kcontrol; + + return snd_ctl_add(sigmadsp->component->card->snd_card, kcontrol); +} + +static void sigmadsp_activate_ctrl(struct sigmadsp *sigmadsp, + struct sigmadsp_control *ctrl, unsigned int samplerate_mask) +{ + struct snd_card *card = sigmadsp->component->card->snd_card; + struct snd_kcontrol_volatile *vd; + struct snd_ctl_elem_id id; + bool active; + bool changed = false; + + active = sigmadsp_samplerate_valid(ctrl->samplerates, samplerate_mask); + + down_write(&card->controls_rwsem); + if (!ctrl->kcontrol) { + up_write(&card->controls_rwsem); + return; + } + + id = ctrl->kcontrol->id; + vd = &ctrl->kcontrol->vd[0]; + if (active == (bool)(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) { + vd->access ^= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + changed = true; + } + up_write(&card->controls_rwsem); + + if (active && changed) { + mutex_lock(&sigmadsp->lock); + if (ctrl->cached) + sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); + mutex_unlock(&sigmadsp->lock); + } + + if (changed) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &id); +} + +/** + * sigmadsp_attach() - Attach a sigmadsp instance to a ASoC component + * @sigmadsp: The sigmadsp instance to attach + * @component: The component to attach to + * + * Typically called in the components probe callback. + * + * Note, once this function has been called the firmware must not be released + * until after the ALSA snd_card that the component belongs to has been + * disconnected, even if sigmadsp_attach() returns an error. + */ +int sigmadsp_attach(struct sigmadsp *sigmadsp, + struct snd_soc_component *component) +{ + struct sigmadsp_control *ctrl; + unsigned int samplerate_mask; + int ret; + + sigmadsp->component = component; + + samplerate_mask = sigmadsp_get_samplerate_mask(sigmadsp, + sigmadsp->current_samplerate); + + list_for_each_entry(ctrl, &sigmadsp->ctrl_list, head) { + ret = sigmadsp_alloc_control(sigmadsp, ctrl, samplerate_mask); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(sigmadsp_attach); + +/** + * sigmadsp_setup() - Setup the DSP for the specified samplerate + * @sigmadsp: The sigmadsp instance to configure + * @samplerate: The samplerate the DSP should be configured for + * + * Loads the appropriate firmware program and parameter memory (if not already + * loaded) and enables the controls for the specified samplerate. Any control + * parameter changes that have been made previously will be restored. + * + * Returns 0 on success, a negative error code otherwise. + */ +int sigmadsp_setup(struct sigmadsp *sigmadsp, unsigned int samplerate) +{ + struct sigmadsp_control *ctrl; + unsigned int samplerate_mask; + struct sigmadsp_data *data; + int ret; + + if (sigmadsp->current_samplerate == samplerate) + return 0; + + samplerate_mask = sigmadsp_get_samplerate_mask(sigmadsp, samplerate); + if (samplerate_mask == 0) + return -EINVAL; + + list_for_each_entry(data, &sigmadsp->data_list, head) { + if (!sigmadsp_samplerate_valid(data->samplerates, + samplerate_mask)) + continue; + ret = sigmadsp_write(sigmadsp, data->addr, data->data, + data->length); + if (ret) + goto err; + } + + list_for_each_entry(ctrl, &sigmadsp->ctrl_list, head) + sigmadsp_activate_ctrl(sigmadsp, ctrl, samplerate_mask); + + sigmadsp->current_samplerate = samplerate; + + return 0; +err: + sigmadsp_reset(sigmadsp); + + return ret; +} +EXPORT_SYMBOL_GPL(sigmadsp_setup); + +/** + * sigmadsp_reset() - Notify the sigmadsp instance that the DSP has been reset + * @sigmadsp: The sigmadsp instance to reset + * + * Should be called whenever the DSP has been reset and parameter and program + * memory need to be re-loaded. + */ +void sigmadsp_reset(struct sigmadsp *sigmadsp) +{ + struct sigmadsp_control *ctrl; + + list_for_each_entry(ctrl, &sigmadsp->ctrl_list, head) + sigmadsp_activate_ctrl(sigmadsp, ctrl, false); + + sigmadsp->current_samplerate = 0; +} +EXPORT_SYMBOL_GPL(sigmadsp_reset); + +/** + * sigmadsp_restrict_params() - Applies DSP firmware specific constraints + * @sigmadsp: The sigmadsp instance + * @substream: The substream to restrict + * + * Applies samplerate constraints that may be required by the firmware Should + * typically be called from the CODEC/component drivers startup callback. + * + * Returns 0 on success, a negative error code otherwise. + */ +int sigmadsp_restrict_params(struct sigmadsp *sigmadsp, + struct snd_pcm_substream *substream) +{ + if (sigmadsp->rate_constraints.count == 0) + return 0; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &sigmadsp->rate_constraints); +} +EXPORT_SYMBOL_GPL(sigmadsp_restrict_params); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sigmadsp.h b/sound/soc/codecs/sigmadsp.h new file mode 100644 index 000000000..614475cbb --- /dev/null +++ b/sound/soc/codecs/sigmadsp.h @@ -0,0 +1,66 @@ +/* + * Load firmware files from Analog Devices SigmaStudio + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __SIGMA_FIRMWARE_H__ +#define __SIGMA_FIRMWARE_H__ + +#include +#include +#include + +#include + +struct sigmadsp; +struct snd_soc_component; +struct snd_pcm_substream; + +struct sigmadsp_ops { + int (*safeload)(struct sigmadsp *sigmadsp, unsigned int addr, + const uint8_t *data, size_t len); +}; + +struct sigmadsp { + const struct sigmadsp_ops *ops; + + struct list_head ctrl_list; + struct list_head data_list; + + struct snd_pcm_hw_constraint_list rate_constraints; + + unsigned int current_samplerate; + struct snd_soc_component *component; + struct device *dev; + + struct mutex lock; + + void *control_data; + int (*write)(void *, unsigned int, const uint8_t *, size_t); + int (*read)(void *, unsigned int, uint8_t *, size_t); +}; + +struct sigmadsp *devm_sigmadsp_init(struct device *dev, + const struct sigmadsp_ops *ops, const char *firmware_name); +void sigmadsp_reset(struct sigmadsp *sigmadsp); + +int sigmadsp_restrict_params(struct sigmadsp *sigmadsp, + struct snd_pcm_substream *substream); + +struct i2c_client; + +struct sigmadsp *devm_sigmadsp_init_regmap(struct device *dev, + struct regmap *regmap, const struct sigmadsp_ops *ops, + const char *firmware_name); +struct sigmadsp *devm_sigmadsp_init_i2c(struct i2c_client *client, + const struct sigmadsp_ops *ops, const char *firmware_name); + +int sigmadsp_attach(struct sigmadsp *sigmadsp, + struct snd_soc_component *component); +int sigmadsp_setup(struct sigmadsp *sigmadsp, unsigned int rate); +void sigmadsp_reset(struct sigmadsp *sigmadsp); + +#endif diff --git a/sound/soc/codecs/simple-amplifier.c b/sound/soc/codecs/simple-amplifier.c new file mode 100644 index 000000000..85524acf3 --- /dev/null +++ b/sound/soc/codecs/simple-amplifier.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Jerome Brunet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 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, see . + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#include +#include +#include + +#define DRV_NAME "simple-amplifier" + +struct simple_amp { + struct gpio_desc *gpiod_enable; +}; + +static int drv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct simple_amp *priv = snd_soc_component_get_drvdata(c); + int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + val = 0; + break; + default: + WARN(1, "Unexpected event"); + return -EINVAL; + } + + gpiod_set_value_cansleep(priv->gpiod_enable, val); + + return 0; +} + +static const struct snd_soc_dapm_widget simple_amp_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("INL"), + SND_SOC_DAPM_INPUT("INR"), + SND_SOC_DAPM_OUT_DRV_E("DRV", SND_SOC_NOPM, 0, 0, NULL, 0, drv_event, + (SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)), + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route simple_amp_dapm_routes[] = { + { "DRV", NULL, "INL" }, + { "DRV", NULL, "INR" }, + { "OUTL", NULL, "DRV" }, + { "OUTR", NULL, "DRV" }, +}; + +static const struct snd_soc_component_driver simple_amp_component_driver = { + .dapm_widgets = simple_amp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(simple_amp_dapm_widgets), + .dapm_routes = simple_amp_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(simple_amp_dapm_routes), +}; + +static int simple_amp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct simple_amp *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->gpiod_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_enable)) { + err = PTR_ERR(priv->gpiod_enable); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'enable' gpio: %d", err); + return err; + } + + return devm_snd_soc_register_component(dev, + &simple_amp_component_driver, + NULL, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id simple_amp_ids[] = { + { .compatible = "dioo,dio2125", }, + { .compatible = "simple-audio-amplifier", }, + { } +}; +MODULE_DEVICE_TABLE(of, simple_amp_ids); +#endif + +static struct platform_driver simple_amp_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(simple_amp_ids), + }, + .probe = simple_amp_probe, +}; + +module_platform_driver(simple_amp_driver); + +MODULE_DESCRIPTION("ASoC Simple Audio Amplifier driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sirf-audio-codec.c b/sound/soc/codecs/sirf-audio-codec.c new file mode 100644 index 000000000..e424499a8 --- /dev/null +++ b/sound/soc/codecs/sirf-audio-codec.c @@ -0,0 +1,581 @@ +/* + * SiRF audio codec driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sirf-audio-codec.h" + +struct sirf_audio_codec { + struct clk *clk; + struct regmap *regmap; + u32 reg_ctrl0, reg_ctrl1; +}; + +static const char * const input_mode_mux[] = {"Single-ended", + "Differential"}; + +static const struct soc_enum input_mode_mux_enum = + SOC_ENUM_SINGLE(AUDIO_IC_CODEC_CTRL1, 4, 2, input_mode_mux); + +static const struct snd_kcontrol_new sirf_audio_codec_input_mode_control = + SOC_DAPM_ENUM("Route", input_mode_mux_enum); + +static const DECLARE_TLV_DB_SCALE(playback_vol_tlv, -12400, 100, 0); +static const DECLARE_TLV_DB_SCALE(capture_vol_tlv_prima2, 500, 100, 0); +static const DECLARE_TLV_DB_RANGE(capture_vol_tlv_atlas6, + 0, 7, TLV_DB_SCALE_ITEM(-100, 100, 0), + 0x22, 0x3F, TLV_DB_SCALE_ITEM(700, 100, 0), +); + +static struct snd_kcontrol_new volume_controls_atlas6[] = { + SOC_DOUBLE_TLV("Playback Volume", AUDIO_IC_CODEC_CTRL0, 21, 14, + 0x7F, 0, playback_vol_tlv), + SOC_DOUBLE_TLV("Capture Volume", AUDIO_IC_CODEC_CTRL1, 16, 10, + 0x3F, 0, capture_vol_tlv_atlas6), +}; + +static struct snd_kcontrol_new volume_controls_prima2[] = { + SOC_DOUBLE_TLV("Speaker Volume", AUDIO_IC_CODEC_CTRL0, 21, 14, + 0x7F, 0, playback_vol_tlv), + SOC_DOUBLE_TLV("Capture Volume", AUDIO_IC_CODEC_CTRL1, 15, 10, + 0x1F, 0, capture_vol_tlv_prima2), +}; + +static struct snd_kcontrol_new left_input_path_controls[] = { + SOC_DAPM_SINGLE("Line Left Switch", AUDIO_IC_CODEC_CTRL1, 6, 1, 0), + SOC_DAPM_SINGLE("Mic Left Switch", AUDIO_IC_CODEC_CTRL1, 3, 1, 0), +}; + +static struct snd_kcontrol_new right_input_path_controls[] = { + SOC_DAPM_SINGLE("Line Right Switch", AUDIO_IC_CODEC_CTRL1, 5, 1, 0), + SOC_DAPM_SINGLE("Mic Right Switch", AUDIO_IC_CODEC_CTRL1, 2, 1, 0), +}; + +static struct snd_kcontrol_new left_dac_to_hp_left_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 9, 1, 0); + +static struct snd_kcontrol_new left_dac_to_hp_right_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 8, 1, 0); + +static struct snd_kcontrol_new right_dac_to_hp_left_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 7, 1, 0); + +static struct snd_kcontrol_new right_dac_to_hp_right_amp_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 6, 1, 0); + +static struct snd_kcontrol_new left_dac_to_speaker_lineout_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 11, 1, 0); + +static struct snd_kcontrol_new right_dac_to_speaker_lineout_switch_control = + SOC_DAPM_SINGLE("Switch", AUDIO_IC_CODEC_CTRL0, 10, 1, 0); + +/* After enable adc, Delay 200ms to avoid pop noise */ +static int adc_enable_delay_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(200); + break; + default: + break; + } + + return 0; +} + +static void enable_and_reset_codec(struct regmap *regmap, + u32 codec_enable_bits, u32 codec_reset_bits) +{ + regmap_update_bits(regmap, AUDIO_IC_CODEC_CTRL1, + codec_enable_bits | codec_reset_bits, + codec_enable_bits); + msleep(20); + regmap_update_bits(regmap, AUDIO_IC_CODEC_CTRL1, + codec_reset_bits, codec_reset_bits); +} + +static int atlas6_codec_enable_and_reset_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ +#define ATLAS6_CODEC_ENABLE_BITS (1 << 29) +#define ATLAS6_CODEC_RESET_BITS (1 << 28) + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct sirf_audio_codec *sirf_audio_codec = snd_soc_component_get_drvdata(component); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + enable_and_reset_codec(sirf_audio_codec->regmap, + ATLAS6_CODEC_ENABLE_BITS, ATLAS6_CODEC_RESET_BITS); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_IC_CODEC_CTRL1, ATLAS6_CODEC_ENABLE_BITS, 0); + break; + default: + break; + } + + return 0; +} + +static int prima2_codec_enable_and_reset_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ +#define PRIMA2_CODEC_ENABLE_BITS (1 << 27) +#define PRIMA2_CODEC_RESET_BITS (1 << 26) + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct sirf_audio_codec *sirf_audio_codec = snd_soc_component_get_drvdata(component); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + enable_and_reset_codec(sirf_audio_codec->regmap, + PRIMA2_CODEC_ENABLE_BITS, PRIMA2_CODEC_RESET_BITS); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_IC_CODEC_CTRL1, PRIMA2_CODEC_ENABLE_BITS, 0); + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget atlas6_output_driver_dapm_widgets[] = { + SND_SOC_DAPM_OUT_DRV("HP Left Driver", AUDIO_IC_CODEC_CTRL1, + 25, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HP Right Driver", AUDIO_IC_CODEC_CTRL1, + 26, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Speaker Driver", AUDIO_IC_CODEC_CTRL1, + 27, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_widget prima2_output_driver_dapm_widgets[] = { + SND_SOC_DAPM_OUT_DRV("HP Left Driver", AUDIO_IC_CODEC_CTRL1, + 23, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HP Right Driver", AUDIO_IC_CODEC_CTRL1, + 24, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Speaker Driver", AUDIO_IC_CODEC_CTRL1, + 25, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_widget atlas6_codec_clock_dapm_widget = + SND_SOC_DAPM_SUPPLY("codecclk", SND_SOC_NOPM, 0, 0, + atlas6_codec_enable_and_reset_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); + +static const struct snd_soc_dapm_widget prima2_codec_clock_dapm_widget = + SND_SOC_DAPM_SUPPLY("codecclk", SND_SOC_NOPM, 0, 0, + prima2_codec_enable_and_reset_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); + +static const struct snd_soc_dapm_widget sirf_audio_codec_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC left", NULL, AUDIO_IC_CODEC_CTRL0, 1, 0), + SND_SOC_DAPM_DAC("DAC right", NULL, AUDIO_IC_CODEC_CTRL0, 0, 0), + SND_SOC_DAPM_SWITCH("Left dac to hp left amp", SND_SOC_NOPM, 0, 0, + &left_dac_to_hp_left_amp_switch_control), + SND_SOC_DAPM_SWITCH("Left dac to hp right amp", SND_SOC_NOPM, 0, 0, + &left_dac_to_hp_right_amp_switch_control), + SND_SOC_DAPM_SWITCH("Right dac to hp left amp", SND_SOC_NOPM, 0, 0, + &right_dac_to_hp_left_amp_switch_control), + SND_SOC_DAPM_SWITCH("Right dac to hp right amp", SND_SOC_NOPM, 0, 0, + &right_dac_to_hp_right_amp_switch_control), + SND_SOC_DAPM_OUT_DRV("HP amp left driver", AUDIO_IC_CODEC_CTRL0, 3, 0, + NULL, 0), + SND_SOC_DAPM_OUT_DRV("HP amp right driver", AUDIO_IC_CODEC_CTRL0, 3, 0, + NULL, 0), + + SND_SOC_DAPM_SWITCH("Left dac to speaker lineout", SND_SOC_NOPM, 0, 0, + &left_dac_to_speaker_lineout_switch_control), + SND_SOC_DAPM_SWITCH("Right dac to speaker lineout", SND_SOC_NOPM, 0, 0, + &right_dac_to_speaker_lineout_switch_control), + SND_SOC_DAPM_OUT_DRV("Speaker amp driver", AUDIO_IC_CODEC_CTRL0, 4, 0, + NULL, 0), + + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("SPKOUT"), + + SND_SOC_DAPM_ADC_E("ADC left", NULL, AUDIO_IC_CODEC_CTRL1, 8, 0, + adc_enable_delay_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC_E("ADC right", NULL, AUDIO_IC_CODEC_CTRL1, 7, 0, + adc_enable_delay_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MIXER("Left PGA mixer", AUDIO_IC_CODEC_CTRL1, 1, 0, + &left_input_path_controls[0], + ARRAY_SIZE(left_input_path_controls)), + SND_SOC_DAPM_MIXER("Right PGA mixer", AUDIO_IC_CODEC_CTRL1, 0, 0, + &right_input_path_controls[0], + ARRAY_SIZE(right_input_path_controls)), + + SND_SOC_DAPM_MUX("Mic input mode mux", SND_SOC_NOPM, 0, 0, + &sirf_audio_codec_input_mode_control), + SND_SOC_DAPM_MICBIAS("Mic Bias", AUDIO_IC_CODEC_PWR, 3, 0), + SND_SOC_DAPM_INPUT("MICIN1"), + SND_SOC_DAPM_INPUT("MICIN2"), + SND_SOC_DAPM_INPUT("LINEIN1"), + SND_SOC_DAPM_INPUT("LINEIN2"), + + SND_SOC_DAPM_SUPPLY("HSL Phase Opposite", AUDIO_IC_CODEC_CTRL0, + 30, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route sirf_audio_codec_map[] = { + {"SPKOUT", NULL, "Speaker Driver"}, + {"Speaker Driver", NULL, "Speaker amp driver"}, + {"Speaker amp driver", NULL, "Left dac to speaker lineout"}, + {"Speaker amp driver", NULL, "Right dac to speaker lineout"}, + {"Left dac to speaker lineout", "Switch", "DAC left"}, + {"Right dac to speaker lineout", "Switch", "DAC right"}, + {"HPOUTL", NULL, "HP Left Driver"}, + {"HPOUTR", NULL, "HP Right Driver"}, + {"HP Left Driver", NULL, "HP amp left driver"}, + {"HP Right Driver", NULL, "HP amp right driver"}, + {"HP amp left driver", NULL, "Right dac to hp left amp"}, + {"HP amp right driver", NULL , "Right dac to hp right amp"}, + {"HP amp left driver", NULL, "Left dac to hp left amp"}, + {"HP amp right driver", NULL , "Right dac to hp right amp"}, + {"Right dac to hp left amp", "Switch", "DAC left"}, + {"Right dac to hp right amp", "Switch", "DAC right"}, + {"Left dac to hp left amp", "Switch", "DAC left"}, + {"Left dac to hp right amp", "Switch", "DAC right"}, + {"DAC left", NULL, "codecclk"}, + {"DAC right", NULL, "codecclk"}, + {"DAC left", NULL, "Playback"}, + {"DAC right", NULL, "Playback"}, + {"DAC left", NULL, "HSL Phase Opposite"}, + {"DAC right", NULL, "HSL Phase Opposite"}, + + {"Capture", NULL, "ADC left"}, + {"Capture", NULL, "ADC right"}, + {"ADC left", NULL, "codecclk"}, + {"ADC right", NULL, "codecclk"}, + {"ADC left", NULL, "Left PGA mixer"}, + {"ADC right", NULL, "Right PGA mixer"}, + {"Left PGA mixer", "Line Left Switch", "LINEIN2"}, + {"Right PGA mixer", "Line Right Switch", "LINEIN1"}, + {"Left PGA mixer", "Mic Left Switch", "MICIN2"}, + {"Right PGA mixer", "Mic Right Switch", "Mic input mode mux"}, + {"Mic input mode mux", "Single-ended", "MICIN1"}, + {"Mic input mode mux", "Differential", "MICIN1"}, +}; + +static void sirf_audio_codec_tx_enable(struct sirf_audio_codec *sirf_audio_codec) +{ + regmap_update_bits(sirf_audio_codec->regmap, AUDIO_PORT_IC_TXFIFO_OP, + AUDIO_FIFO_RESET, AUDIO_FIFO_RESET); + regmap_update_bits(sirf_audio_codec->regmap, AUDIO_PORT_IC_TXFIFO_OP, + AUDIO_FIFO_RESET, ~AUDIO_FIFO_RESET); + regmap_write(sirf_audio_codec->regmap, AUDIO_PORT_IC_TXFIFO_INT_MSK, 0); + regmap_write(sirf_audio_codec->regmap, AUDIO_PORT_IC_TXFIFO_OP, 0); + regmap_update_bits(sirf_audio_codec->regmap, AUDIO_PORT_IC_TXFIFO_OP, + AUDIO_FIFO_START, AUDIO_FIFO_START); + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_PORT_IC_CODEC_TX_CTRL, IC_TX_ENABLE, IC_TX_ENABLE); +} + +static void sirf_audio_codec_tx_disable(struct sirf_audio_codec *sirf_audio_codec) +{ + regmap_write(sirf_audio_codec->regmap, AUDIO_PORT_IC_TXFIFO_OP, 0); + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_PORT_IC_CODEC_TX_CTRL, IC_TX_ENABLE, ~IC_TX_ENABLE); +} + +static void sirf_audio_codec_rx_enable(struct sirf_audio_codec *sirf_audio_codec, + int channels) +{ + regmap_update_bits(sirf_audio_codec->regmap, AUDIO_PORT_IC_RXFIFO_OP, + AUDIO_FIFO_RESET, AUDIO_FIFO_RESET); + regmap_update_bits(sirf_audio_codec->regmap, AUDIO_PORT_IC_RXFIFO_OP, + AUDIO_FIFO_RESET, ~AUDIO_FIFO_RESET); + regmap_write(sirf_audio_codec->regmap, + AUDIO_PORT_IC_RXFIFO_INT_MSK, 0); + regmap_write(sirf_audio_codec->regmap, AUDIO_PORT_IC_RXFIFO_OP, 0); + regmap_update_bits(sirf_audio_codec->regmap, AUDIO_PORT_IC_RXFIFO_OP, + AUDIO_FIFO_START, AUDIO_FIFO_START); + if (channels == 1) + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_PORT_IC_CODEC_RX_CTRL, + IC_RX_ENABLE_MONO, IC_RX_ENABLE_MONO); + else + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_PORT_IC_CODEC_RX_CTRL, + IC_RX_ENABLE_STEREO, IC_RX_ENABLE_STEREO); +} + +static void sirf_audio_codec_rx_disable(struct sirf_audio_codec *sirf_audio_codec) +{ + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_PORT_IC_CODEC_RX_CTRL, + IC_RX_ENABLE_STEREO, ~IC_RX_ENABLE_STEREO); +} + +static int sirf_audio_codec_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct sirf_audio_codec *sirf_audio_codec = snd_soc_component_get_drvdata(component); + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + /* + * This is a workaround, When stop playback, + * need disable HP amp, avoid the current noise. + */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (playback) { + snd_soc_component_update_bits(component, AUDIO_IC_CODEC_CTRL0, + IC_HSLEN | IC_HSREN, 0); + sirf_audio_codec_tx_disable(sirf_audio_codec); + } else + sirf_audio_codec_rx_disable(sirf_audio_codec); + break; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (playback) { + sirf_audio_codec_tx_enable(sirf_audio_codec); + snd_soc_component_update_bits(component, AUDIO_IC_CODEC_CTRL0, + IC_HSLEN | IC_HSREN, IC_HSLEN | IC_HSREN); + } else + sirf_audio_codec_rx_enable(sirf_audio_codec, + substream->runtime->channels); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops sirf_audio_codec_dai_ops = { + .trigger = sirf_audio_codec_trigger, +}; + +static struct snd_soc_dai_driver sirf_audio_codec_dai = { + .name = "sirf-audio-codec", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sirf_audio_codec_dai_ops, +}; + +static int sirf_audio_codec_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + pm_runtime_enable(component->dev); + + if (of_device_is_compatible(component->dev->of_node, "sirf,prima2-audio-codec")) { + snd_soc_dapm_new_controls(dapm, + prima2_output_driver_dapm_widgets, + ARRAY_SIZE(prima2_output_driver_dapm_widgets)); + snd_soc_dapm_new_controls(dapm, + &prima2_codec_clock_dapm_widget, 1); + return snd_soc_add_component_controls(component, + volume_controls_prima2, + ARRAY_SIZE(volume_controls_prima2)); + } + if (of_device_is_compatible(component->dev->of_node, "sirf,atlas6-audio-codec")) { + snd_soc_dapm_new_controls(dapm, + atlas6_output_driver_dapm_widgets, + ARRAY_SIZE(atlas6_output_driver_dapm_widgets)); + snd_soc_dapm_new_controls(dapm, + &atlas6_codec_clock_dapm_widget, 1); + return snd_soc_add_component_controls(component, + volume_controls_atlas6, + ARRAY_SIZE(volume_controls_atlas6)); + } + + return -EINVAL; +} + +static void sirf_audio_codec_remove(struct snd_soc_component *component) +{ + pm_runtime_disable(component->dev); +} + +static const struct snd_soc_component_driver soc_codec_device_sirf_audio_codec = { + .probe = sirf_audio_codec_probe, + .remove = sirf_audio_codec_remove, + .dapm_widgets = sirf_audio_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sirf_audio_codec_dapm_widgets), + .dapm_routes = sirf_audio_codec_map, + .num_dapm_routes = ARRAY_SIZE(sirf_audio_codec_map), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id sirf_audio_codec_of_match[] = { + { .compatible = "sirf,prima2-audio-codec" }, + { .compatible = "sirf,atlas6-audio-codec" }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_audio_codec_of_match); + +static const struct regmap_config sirf_audio_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AUDIO_PORT_IC_RXFIFO_INT_MSK, + .cache_type = REGCACHE_NONE, +}; + +static int sirf_audio_codec_driver_probe(struct platform_device *pdev) +{ + int ret; + struct sirf_audio_codec *sirf_audio_codec; + void __iomem *base; + struct resource *mem_res; + const struct of_device_id *match; + + match = of_match_node(sirf_audio_codec_of_match, pdev->dev.of_node); + + sirf_audio_codec = devm_kzalloc(&pdev->dev, + sizeof(struct sirf_audio_codec), GFP_KERNEL); + if (!sirf_audio_codec) + return -ENOMEM; + + platform_set_drvdata(pdev, sirf_audio_codec); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem_res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sirf_audio_codec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sirf_audio_codec_regmap_config); + if (IS_ERR(sirf_audio_codec->regmap)) + return PTR_ERR(sirf_audio_codec->regmap); + + sirf_audio_codec->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(sirf_audio_codec->clk)) { + dev_err(&pdev->dev, "Get clock failed.\n"); + return PTR_ERR(sirf_audio_codec->clk); + } + + ret = clk_prepare_enable(sirf_audio_codec->clk); + if (ret) { + dev_err(&pdev->dev, "Enable clock failed.\n"); + return ret; + } + + ret = devm_snd_soc_register_component(&(pdev->dev), + &soc_codec_device_sirf_audio_codec, + &sirf_audio_codec_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio Codec dai failed.\n"); + goto err_clk_put; + } + + /* + * Always open charge pump, if not, when the charge pump closed the + * adc will not stable + */ + regmap_update_bits(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL0, + IC_CPFREQ, IC_CPFREQ); + + if (of_device_is_compatible(pdev->dev.of_node, "sirf,atlas6-audio-codec")) + regmap_update_bits(sirf_audio_codec->regmap, + AUDIO_IC_CODEC_CTRL0, IC_CPEN, IC_CPEN); + return 0; + +err_clk_put: + clk_disable_unprepare(sirf_audio_codec->clk); + return ret; +} + +static int sirf_audio_codec_driver_remove(struct platform_device *pdev) +{ + struct sirf_audio_codec *sirf_audio_codec = platform_get_drvdata(pdev); + + clk_disable_unprepare(sirf_audio_codec->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int sirf_audio_codec_suspend(struct device *dev) +{ + struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(dev); + + regmap_read(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL0, + &sirf_audio_codec->reg_ctrl0); + regmap_read(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL1, + &sirf_audio_codec->reg_ctrl1); + clk_disable_unprepare(sirf_audio_codec->clk); + + return 0; +} + +static int sirf_audio_codec_resume(struct device *dev) +{ + struct sirf_audio_codec *sirf_audio_codec = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(sirf_audio_codec->clk); + if (ret) + return ret; + + regmap_write(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL0, + sirf_audio_codec->reg_ctrl0); + regmap_write(sirf_audio_codec->regmap, AUDIO_IC_CODEC_CTRL1, + sirf_audio_codec->reg_ctrl1); + + return 0; +} +#endif + +static const struct dev_pm_ops sirf_audio_codec_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sirf_audio_codec_suspend, sirf_audio_codec_resume) +}; + +static struct platform_driver sirf_audio_codec_driver = { + .driver = { + .name = "sirf-audio-codec", + .of_match_table = sirf_audio_codec_of_match, + .pm = &sirf_audio_codec_pm_ops, + }, + .probe = sirf_audio_codec_driver_probe, + .remove = sirf_audio_codec_driver_remove, +}; + +module_platform_driver(sirf_audio_codec_driver); + +MODULE_DESCRIPTION("SiRF audio codec driver"); +MODULE_AUTHOR("RongJun Ying "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/sirf-audio-codec.h b/sound/soc/codecs/sirf-audio-codec.h new file mode 100644 index 000000000..ba1adc038 --- /dev/null +++ b/sound/soc/codecs/sirf-audio-codec.h @@ -0,0 +1,125 @@ +/* + * SiRF inner codec controllers define + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#ifndef _SIRF_AUDIO_CODEC_H +#define _SIRF_AUDIO_CODEC_H + + +#define AUDIO_IC_CODEC_PWR (0x00E0) +#define AUDIO_IC_CODEC_CTRL0 (0x00E4) +#define AUDIO_IC_CODEC_CTRL1 (0x00E8) +#define AUDIO_IC_CODEC_CTRL2 (0x00EC) +#define AUDIO_IC_CODEC_CTRL3 (0x00F0) + +#define MICBIASEN (1 << 3) + +#define IC_RDACEN (1 << 0) +#define IC_LDACEN (1 << 1) +#define IC_HSREN (1 << 2) +#define IC_HSLEN (1 << 3) +#define IC_SPEN (1 << 4) +#define IC_CPEN (1 << 5) + +#define IC_HPRSELR (1 << 6) +#define IC_HPLSELR (1 << 7) +#define IC_HPRSELL (1 << 8) +#define IC_HPLSELL (1 << 9) +#define IC_SPSELR (1 << 10) +#define IC_SPSELL (1 << 11) + +#define IC_MONOR (1 << 12) +#define IC_MONOL (1 << 13) + +#define IC_RXOSRSEL (1 << 28) +#define IC_CPFREQ (1 << 29) +#define IC_HSINVEN (1 << 30) + +#define IC_MICINREN (1 << 0) +#define IC_MICINLEN (1 << 1) +#define IC_MICIN1SEL (1 << 2) +#define IC_MICIN2SEL (1 << 3) +#define IC_MICDIFSEL (1 << 4) +#define IC_LINEIN1SEL (1 << 5) +#define IC_LINEIN2SEL (1 << 6) +#define IC_RADCEN (1 << 7) +#define IC_LADCEN (1 << 8) +#define IC_ALM (1 << 9) + +#define IC_DIGMICEN (1 << 22) +#define IC_DIGMICFREQ (1 << 23) +#define IC_ADC14B_12 (1 << 24) +#define IC_FIRDAC_HSL_EN (1 << 25) +#define IC_FIRDAC_HSR_EN (1 << 26) +#define IC_FIRDAC_LOUT_EN (1 << 27) +#define IC_POR (1 << 28) +#define IC_CODEC_CLK_EN (1 << 29) +#define IC_HP_3DB_BOOST (1 << 30) + +#define IC_ADC_LEFT_GAIN_SHIFT 16 +#define IC_ADC_RIGHT_GAIN_SHIFT 10 +#define IC_ADC_GAIN_MASK 0x3F +#define IC_MIC_MAX_GAIN 0x39 + +#define IC_RXPGAR_MASK 0x3F +#define IC_RXPGAR_SHIFT 14 +#define IC_RXPGAL_MASK 0x3F +#define IC_RXPGAL_SHIFT 21 +#define IC_RXPGAR 0x7B +#define IC_RXPGAL 0x7B + +#define AUDIO_PORT_TX_FIFO_LEVEL_CHECK_MASK 0x3F +#define AUDIO_PORT_TX_FIFO_SC_OFFSET 0 +#define AUDIO_PORT_TX_FIFO_LC_OFFSET 10 +#define AUDIO_PORT_TX_FIFO_HC_OFFSET 20 + +#define TX_FIFO_SC(x) (((x) & AUDIO_PORT_TX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_PORT_TX_FIFO_SC_OFFSET) +#define TX_FIFO_LC(x) (((x) & AUDIO_PORT_TX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_PORT_TX_FIFO_LC_OFFSET) +#define TX_FIFO_HC(x) (((x) & AUDIO_PORT_TX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_PORT_TX_FIFO_HC_OFFSET) + +#define AUDIO_PORT_RX_FIFO_LEVEL_CHECK_MASK 0x0F +#define AUDIO_PORT_RX_FIFO_SC_OFFSET 0 +#define AUDIO_PORT_RX_FIFO_LC_OFFSET 10 +#define AUDIO_PORT_RX_FIFO_HC_OFFSET 20 + +#define RX_FIFO_SC(x) (((x) & AUDIO_PORT_RX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_PORT_RX_FIFO_SC_OFFSET) +#define RX_FIFO_LC(x) (((x) & AUDIO_PORT_RX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_PORT_RX_FIFO_LC_OFFSET) +#define RX_FIFO_HC(x) (((x) & AUDIO_PORT_RX_FIFO_LEVEL_CHECK_MASK) \ + << AUDIO_PORT_RX_FIFO_HC_OFFSET) +#define AUDIO_PORT_IC_CODEC_TX_CTRL (0x00F4) +#define AUDIO_PORT_IC_CODEC_RX_CTRL (0x00F8) + +#define AUDIO_PORT_IC_TXFIFO_OP (0x00FC) +#define AUDIO_PORT_IC_TXFIFO_LEV_CHK (0x0100) +#define AUDIO_PORT_IC_TXFIFO_STS (0x0104) +#define AUDIO_PORT_IC_TXFIFO_INT (0x0108) +#define AUDIO_PORT_IC_TXFIFO_INT_MSK (0x010C) + +#define AUDIO_PORT_IC_RXFIFO_OP (0x0110) +#define AUDIO_PORT_IC_RXFIFO_LEV_CHK (0x0114) +#define AUDIO_PORT_IC_RXFIFO_STS (0x0118) +#define AUDIO_PORT_IC_RXFIFO_INT (0x011C) +#define AUDIO_PORT_IC_RXFIFO_INT_MSK (0x0120) + +#define AUDIO_FIFO_START (1 << 0) +#define AUDIO_FIFO_RESET (1 << 1) + +#define AUDIO_FIFO_FULL (1 << 0) +#define AUDIO_FIFO_EMPTY (1 << 1) +#define AUDIO_FIFO_OFLOW (1 << 2) +#define AUDIO_FIFO_UFLOW (1 << 3) + +#define IC_TX_ENABLE (0x03) +#define IC_RX_ENABLE_MONO (0x01) +#define IC_RX_ENABLE_STEREO (0x03) + +#endif /*__SIRF_AUDIO_CODEC_H*/ diff --git a/sound/soc/codecs/spdif_receiver.c b/sound/soc/codecs/spdif_receiver.c new file mode 100644 index 000000000..ac69d495d --- /dev/null +++ b/sound/soc/codecs/spdif_receiver.c @@ -0,0 +1,90 @@ +/* + * ALSA SoC SPDIF DIR (Digital Interface Reciever) driver + * + * Based on ALSA SoC SPDIF DIT driver + * + * This driver is used by controllers which can operate in DIR (SPDI/F) where + * no codec is needed. This file provides stub codec that can be used + * in these configurations. SPEAr SPDIF IN Audio controller uses this driver. + * + * Author: Vipin Kumar, + * Copyright: (C) 2012 ST Microelectronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct snd_soc_dapm_widget dir_widgets[] = { + SND_SOC_DAPM_INPUT("spdif-in"), +}; + +static const struct snd_soc_dapm_route dir_routes[] = { + { "Capture", NULL, "spdif-in" }, +}; + +#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) + +static struct snd_soc_component_driver soc_codec_spdif_dir = { + .dapm_widgets = dir_widgets, + .num_dapm_widgets = ARRAY_SIZE(dir_widgets), + .dapm_routes = dir_routes, + .num_dapm_routes = ARRAY_SIZE(dir_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver dir_stub_dai = { + .name = "dir-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static int spdif_dir_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_codec_spdif_dir, + &dir_stub_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id spdif_dir_dt_ids[] = { + { .compatible = "linux,spdif-dir", }, + { } +}; +MODULE_DEVICE_TABLE(of, spdif_dir_dt_ids); +#endif + +static struct platform_driver spdif_dir_driver = { + .probe = spdif_dir_probe, + .driver = { + .name = "spdif-dir", + .of_match_table = of_match_ptr(spdif_dir_dt_ids), + }, +}; + +module_platform_driver(spdif_dir_driver); + +MODULE_DESCRIPTION("ASoC SPDIF DIR driver"); +MODULE_AUTHOR("Vipin Kumar "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/spdif_transmitter.c b/sound/soc/codecs/spdif_transmitter.c new file mode 100644 index 000000000..b4f7fc4ac --- /dev/null +++ b/sound/soc/codecs/spdif_transmitter.c @@ -0,0 +1,91 @@ +/* + * ALSA SoC SPDIF DIT driver + * + * This driver is used by controllers which can operate in DIT (SPDI/F) where + * no codec is needed. This file provides stub codec that can be used + * in these configurations. TI DaVinci Audio controller uses this driver. + * + * Author: Steve Chen, + * Copyright: (C) 2009 MontaVista Software, Inc., + * Copyright: (C) 2009 Texas Instruments, India + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "spdif-dit" + +#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dapm_widget dit_widgets[] = { + SND_SOC_DAPM_OUTPUT("spdif-out"), +}; + +static const struct snd_soc_dapm_route dit_routes[] = { + { "spdif-out", NULL, "Playback" }, +}; + +static struct snd_soc_component_driver soc_codec_spdif_dit = { + .dapm_widgets = dit_widgets, + .num_dapm_widgets = ARRAY_SIZE(dit_widgets), + .dapm_routes = dit_routes, + .num_dapm_routes = ARRAY_SIZE(dit_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver dit_stub_dai = { + .name = "dit-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static int spdif_dit_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_codec_spdif_dit, + &dit_stub_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id spdif_dit_dt_ids[] = { + { .compatible = "linux,spdif-dit", }, + { } +}; +MODULE_DEVICE_TABLE(of, spdif_dit_dt_ids); +#endif + +static struct platform_driver spdif_dit_driver = { + .probe = spdif_dit_probe, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(spdif_dit_dt_ids), + }, +}; + +module_platform_driver(spdif_dit_driver); + +MODULE_AUTHOR("Steve Chen "); +MODULE_DESCRIPTION("SPDIF dummy codec driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/codecs/ssm2305.c b/sound/soc/codecs/ssm2305.c new file mode 100644 index 000000000..2968959c4 --- /dev/null +++ b/sound/soc/codecs/ssm2305.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Analog Devices SSM2305 Amplifier Driver +// +// Copyright (C) 2018 Pengutronix, Marco Felsch +// + +#include +#include +#include + +#define DRV_NAME "ssm2305" + +struct ssm2305 { + /* shutdown gpio */ + struct gpio_desc *gpiod_shutdown; +}; + +static int ssm2305_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctrl, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct ssm2305 *data = snd_soc_component_get_drvdata(c); + + gpiod_set_value_cansleep(data->gpiod_shutdown, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget ssm2305_dapm_widgets[] = { + /* Stereo input/output */ + SND_SOC_DAPM_INPUT("L_IN"), + SND_SOC_DAPM_INPUT("R_IN"), + SND_SOC_DAPM_OUTPUT("L_OUT"), + SND_SOC_DAPM_OUTPUT("R_OUT"), + + SND_SOC_DAPM_SUPPLY("Power", SND_SOC_NOPM, 0, 0, ssm2305_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route ssm2305_dapm_routes[] = { + { "L_OUT", NULL, "L_IN" }, + { "R_OUT", NULL, "R_IN" }, + { "L_IN", NULL, "Power" }, + { "R_IN", NULL, "Power" }, +}; + +static const struct snd_soc_component_driver ssm2305_component_driver = { + .dapm_widgets = ssm2305_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ssm2305_dapm_widgets), + .dapm_routes = ssm2305_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ssm2305_dapm_routes), +}; + +static int ssm2305_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ssm2305 *priv; + int err; + + /* Allocate the private data */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + /* Get shutdown gpio */ + priv->gpiod_shutdown = devm_gpiod_get(dev, "shutdown", + GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_shutdown)) { + err = PTR_ERR(priv->gpiod_shutdown); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'shutdown' gpio: %d\n", + err); + return err; + } + + return devm_snd_soc_register_component(dev, &ssm2305_component_driver, + NULL, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id ssm2305_of_match[] = { + { .compatible = "adi,ssm2305", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm2305_of_match); +#endif + +static struct platform_driver ssm2305_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(ssm2305_of_match), + }, + .probe = ssm2305_probe, +}; + +module_platform_driver(ssm2305_driver); + +MODULE_DESCRIPTION("ASoC SSM2305 amplifier driver"); +MODULE_AUTHOR("Marco Felsch "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ssm2518.c b/sound/soc/codecs/ssm2518.c new file mode 100644 index 000000000..4a1d42a33 --- /dev/null +++ b/sound/soc/codecs/ssm2518.c @@ -0,0 +1,825 @@ +/* + * SSM2518 amplifier audio driver + * + * Copyright 2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ssm2518.h" + +#define SSM2518_REG_POWER1 0x00 +#define SSM2518_REG_CLOCK 0x01 +#define SSM2518_REG_SAI_CTRL1 0x02 +#define SSM2518_REG_SAI_CTRL2 0x03 +#define SSM2518_REG_CHAN_MAP 0x04 +#define SSM2518_REG_LEFT_VOL 0x05 +#define SSM2518_REG_RIGHT_VOL 0x06 +#define SSM2518_REG_MUTE_CTRL 0x07 +#define SSM2518_REG_FAULT_CTRL 0x08 +#define SSM2518_REG_POWER2 0x09 +#define SSM2518_REG_DRC_1 0x0a +#define SSM2518_REG_DRC_2 0x0b +#define SSM2518_REG_DRC_3 0x0c +#define SSM2518_REG_DRC_4 0x0d +#define SSM2518_REG_DRC_5 0x0e +#define SSM2518_REG_DRC_6 0x0f +#define SSM2518_REG_DRC_7 0x10 +#define SSM2518_REG_DRC_8 0x11 +#define SSM2518_REG_DRC_9 0x12 + +#define SSM2518_POWER1_RESET BIT(7) +#define SSM2518_POWER1_NO_BCLK BIT(5) +#define SSM2518_POWER1_MCS_MASK (0xf << 1) +#define SSM2518_POWER1_MCS_64FS (0x0 << 1) +#define SSM2518_POWER1_MCS_128FS (0x1 << 1) +#define SSM2518_POWER1_MCS_256FS (0x2 << 1) +#define SSM2518_POWER1_MCS_384FS (0x3 << 1) +#define SSM2518_POWER1_MCS_512FS (0x4 << 1) +#define SSM2518_POWER1_MCS_768FS (0x5 << 1) +#define SSM2518_POWER1_MCS_100FS (0x6 << 1) +#define SSM2518_POWER1_MCS_200FS (0x7 << 1) +#define SSM2518_POWER1_MCS_400FS (0x8 << 1) +#define SSM2518_POWER1_SPWDN BIT(0) + +#define SSM2518_CLOCK_ASR BIT(0) + +#define SSM2518_SAI_CTRL1_FMT_MASK (0x3 << 5) +#define SSM2518_SAI_CTRL1_FMT_I2S (0x0 << 5) +#define SSM2518_SAI_CTRL1_FMT_LJ (0x1 << 5) +#define SSM2518_SAI_CTRL1_FMT_RJ_24BIT (0x2 << 5) +#define SSM2518_SAI_CTRL1_FMT_RJ_16BIT (0x3 << 5) + +#define SSM2518_SAI_CTRL1_SAI_MASK (0x7 << 2) +#define SSM2518_SAI_CTRL1_SAI_I2S (0x0 << 2) +#define SSM2518_SAI_CTRL1_SAI_TDM_2 (0x1 << 2) +#define SSM2518_SAI_CTRL1_SAI_TDM_4 (0x2 << 2) +#define SSM2518_SAI_CTRL1_SAI_TDM_8 (0x3 << 2) +#define SSM2518_SAI_CTRL1_SAI_TDM_16 (0x4 << 2) +#define SSM2518_SAI_CTRL1_SAI_MONO (0x5 << 2) + +#define SSM2518_SAI_CTRL1_FS_MASK (0x3) +#define SSM2518_SAI_CTRL1_FS_8000_12000 (0x0) +#define SSM2518_SAI_CTRL1_FS_16000_24000 (0x1) +#define SSM2518_SAI_CTRL1_FS_32000_48000 (0x2) +#define SSM2518_SAI_CTRL1_FS_64000_96000 (0x3) + +#define SSM2518_SAI_CTRL2_BCLK_INTERAL BIT(7) +#define SSM2518_SAI_CTRL2_LRCLK_PULSE BIT(6) +#define SSM2518_SAI_CTRL2_LRCLK_INVERT BIT(5) +#define SSM2518_SAI_CTRL2_MSB BIT(4) +#define SSM2518_SAI_CTRL2_SLOT_WIDTH_MASK (0x3 << 2) +#define SSM2518_SAI_CTRL2_SLOT_WIDTH_32 (0x0 << 2) +#define SSM2518_SAI_CTRL2_SLOT_WIDTH_24 (0x1 << 2) +#define SSM2518_SAI_CTRL2_SLOT_WIDTH_16 (0x2 << 2) +#define SSM2518_SAI_CTRL2_BCLK_INVERT BIT(1) + +#define SSM2518_CHAN_MAP_RIGHT_SLOT_OFFSET 4 +#define SSM2518_CHAN_MAP_RIGHT_SLOT_MASK 0xf0 +#define SSM2518_CHAN_MAP_LEFT_SLOT_OFFSET 0 +#define SSM2518_CHAN_MAP_LEFT_SLOT_MASK 0x0f + +#define SSM2518_MUTE_CTRL_ANA_GAIN BIT(5) +#define SSM2518_MUTE_CTRL_MUTE_MASTER BIT(0) + +#define SSM2518_POWER2_APWDN BIT(0) + +#define SSM2518_DAC_MUTE BIT(6) +#define SSM2518_DAC_FS_MASK 0x07 +#define SSM2518_DAC_FS_8000 0x00 +#define SSM2518_DAC_FS_16000 0x01 +#define SSM2518_DAC_FS_32000 0x02 +#define SSM2518_DAC_FS_64000 0x03 +#define SSM2518_DAC_FS_128000 0x04 + +struct ssm2518 { + struct regmap *regmap; + bool right_j; + + unsigned int sysclk; + const struct snd_pcm_hw_constraint_list *constraints; + + int enable_gpio; +}; + +static const struct reg_default ssm2518_reg_defaults[] = { + { 0x00, 0x05 }, + { 0x01, 0x00 }, + { 0x02, 0x02 }, + { 0x03, 0x00 }, + { 0x04, 0x10 }, + { 0x05, 0x40 }, + { 0x06, 0x40 }, + { 0x07, 0x81 }, + { 0x08, 0x0c }, + { 0x09, 0x99 }, + { 0x0a, 0x7c }, + { 0x0b, 0x5b }, + { 0x0c, 0x57 }, + { 0x0d, 0x89 }, + { 0x0e, 0x8c }, + { 0x0f, 0x77 }, + { 0x10, 0x26 }, + { 0x11, 0x1c }, + { 0x12, 0x97 }, +}; + +static const DECLARE_TLV_DB_MINMAX_MUTE(ssm2518_vol_tlv, -7125, 2400); +static const DECLARE_TLV_DB_SCALE(ssm2518_compressor_tlv, -3400, 200, 0); +static const DECLARE_TLV_DB_SCALE(ssm2518_expander_tlv, -8100, 300, 0); +static const DECLARE_TLV_DB_SCALE(ssm2518_noise_gate_tlv, -9600, 300, 0); +static const DECLARE_TLV_DB_SCALE(ssm2518_post_drc_tlv, -2400, 300, 0); + +static const DECLARE_TLV_DB_RANGE(ssm2518_limiter_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-2200, 200, 0), + 7, 15, TLV_DB_SCALE_ITEM(-800, 100, 0), +); + +static const char * const ssm2518_drc_peak_detector_attack_time_text[] = { + "0 ms", "0.1 ms", "0.19 ms", "0.37 ms", "0.75 ms", "1.5 ms", "3 ms", + "6 ms", "12 ms", "24 ms", "48 ms", "96 ms", "192 ms", "384 ms", + "768 ms", "1536 ms", +}; + +static const char * const ssm2518_drc_peak_detector_release_time_text[] = { + "0 ms", "1.5 ms", "3 ms", "6 ms", "12 ms", "24 ms", "48 ms", "96 ms", + "192 ms", "384 ms", "768 ms", "1536 ms", "3072 ms", "6144 ms", + "12288 ms", "24576 ms" +}; + +static const char * const ssm2518_drc_hold_time_text[] = { + "0 ms", "0.67 ms", "1.33 ms", "2.67 ms", "5.33 ms", "10.66 ms", + "21.32 ms", "42.64 ms", "85.28 ms", "170.56 ms", "341.12 ms", + "682.24 ms", "1364 ms", +}; + +static SOC_ENUM_SINGLE_DECL(ssm2518_drc_peak_detector_attack_time_enum, + SSM2518_REG_DRC_2, 4, ssm2518_drc_peak_detector_attack_time_text); +static SOC_ENUM_SINGLE_DECL(ssm2518_drc_peak_detector_release_time_enum, + SSM2518_REG_DRC_2, 0, ssm2518_drc_peak_detector_release_time_text); +static SOC_ENUM_SINGLE_DECL(ssm2518_drc_attack_time_enum, + SSM2518_REG_DRC_6, 4, ssm2518_drc_peak_detector_attack_time_text); +static SOC_ENUM_SINGLE_DECL(ssm2518_drc_decay_time_enum, + SSM2518_REG_DRC_6, 0, ssm2518_drc_peak_detector_release_time_text); +static SOC_ENUM_SINGLE_DECL(ssm2518_drc_hold_time_enum, + SSM2518_REG_DRC_7, 4, ssm2518_drc_hold_time_text); +static SOC_ENUM_SINGLE_DECL(ssm2518_drc_noise_gate_hold_time_enum, + SSM2518_REG_DRC_7, 0, ssm2518_drc_hold_time_text); +static SOC_ENUM_SINGLE_DECL(ssm2518_drc_rms_averaging_time_enum, + SSM2518_REG_DRC_9, 0, ssm2518_drc_peak_detector_release_time_text); + +static const struct snd_kcontrol_new ssm2518_snd_controls[] = { + SOC_SINGLE("Playback De-emphasis Switch", SSM2518_REG_MUTE_CTRL, + 4, 1, 0), + SOC_DOUBLE_R_TLV("Master Playback Volume", SSM2518_REG_LEFT_VOL, + SSM2518_REG_RIGHT_VOL, 0, 0xff, 1, ssm2518_vol_tlv), + SOC_DOUBLE("Master Playback Switch", SSM2518_REG_MUTE_CTRL, 2, 1, 1, 1), + + SOC_SINGLE("Amp Low Power Mode Switch", SSM2518_REG_POWER2, 4, 1, 0), + SOC_SINGLE("DAC Low Power Mode Switch", SSM2518_REG_POWER2, 3, 1, 0), + + SOC_SINGLE("DRC Limiter Switch", SSM2518_REG_DRC_1, 5, 1, 0), + SOC_SINGLE("DRC Compressor Switch", SSM2518_REG_DRC_1, 4, 1, 0), + SOC_SINGLE("DRC Expander Switch", SSM2518_REG_DRC_1, 3, 1, 0), + SOC_SINGLE("DRC Noise Gate Switch", SSM2518_REG_DRC_1, 2, 1, 0), + SOC_DOUBLE("DRC Switch", SSM2518_REG_DRC_1, 0, 1, 1, 0), + + SOC_SINGLE_TLV("DRC Limiter Threshold Volume", + SSM2518_REG_DRC_3, 4, 15, 1, ssm2518_limiter_tlv), + SOC_SINGLE_TLV("DRC Compressor Lower Threshold Volume", + SSM2518_REG_DRC_3, 0, 15, 1, ssm2518_compressor_tlv), + SOC_SINGLE_TLV("DRC Expander Upper Threshold Volume", SSM2518_REG_DRC_4, + 4, 15, 1, ssm2518_expander_tlv), + SOC_SINGLE_TLV("DRC Noise Gate Threshold Volume", + SSM2518_REG_DRC_4, 0, 15, 1, ssm2518_noise_gate_tlv), + SOC_SINGLE_TLV("DRC Upper Output Threshold Volume", + SSM2518_REG_DRC_5, 4, 15, 1, ssm2518_limiter_tlv), + SOC_SINGLE_TLV("DRC Lower Output Threshold Volume", + SSM2518_REG_DRC_5, 0, 15, 1, ssm2518_noise_gate_tlv), + SOC_SINGLE_TLV("DRC Post Volume", SSM2518_REG_DRC_8, + 2, 15, 1, ssm2518_post_drc_tlv), + + SOC_ENUM("DRC Peak Detector Attack Time", + ssm2518_drc_peak_detector_attack_time_enum), + SOC_ENUM("DRC Peak Detector Release Time", + ssm2518_drc_peak_detector_release_time_enum), + SOC_ENUM("DRC Attack Time", ssm2518_drc_attack_time_enum), + SOC_ENUM("DRC Decay Time", ssm2518_drc_decay_time_enum), + SOC_ENUM("DRC Hold Time", ssm2518_drc_hold_time_enum), + SOC_ENUM("DRC Noise Gate Hold Time", + ssm2518_drc_noise_gate_hold_time_enum), + SOC_ENUM("DRC RMS Averaging Time", ssm2518_drc_rms_averaging_time_enum), +}; + +static const struct snd_soc_dapm_widget ssm2518_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DACL", "HiFi Playback", SSM2518_REG_POWER2, 1, 1), + SND_SOC_DAPM_DAC("DACR", "HiFi Playback", SSM2518_REG_POWER2, 2, 1), + + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route ssm2518_routes[] = { + { "OUTL", NULL, "DACL" }, + { "OUTR", NULL, "DACR" }, +}; + +struct ssm2518_mcs_lut { + unsigned int rate; + const unsigned int *sysclks; +}; + +static const unsigned int ssm2518_sysclks_2048000[] = { + 2048000, 4096000, 8192000, 12288000, 16384000, 24576000, + 3200000, 6400000, 12800000, 0 +}; + +static const unsigned int ssm2518_sysclks_2822000[] = { + 2822000, 5644800, 11289600, 16934400, 22579200, 33868800, + 4410000, 8820000, 17640000, 0 +}; + +static const unsigned int ssm2518_sysclks_3072000[] = { + 3072000, 6144000, 12288000, 16384000, 24576000, 38864000, + 4800000, 9600000, 19200000, 0 +}; + +static const struct ssm2518_mcs_lut ssm2518_mcs_lut[] = { + { 8000, ssm2518_sysclks_2048000, }, + { 11025, ssm2518_sysclks_2822000, }, + { 12000, ssm2518_sysclks_3072000, }, + { 16000, ssm2518_sysclks_2048000, }, + { 24000, ssm2518_sysclks_3072000, }, + { 22050, ssm2518_sysclks_2822000, }, + { 32000, ssm2518_sysclks_2048000, }, + { 44100, ssm2518_sysclks_2822000, }, + { 48000, ssm2518_sysclks_3072000, }, + { 96000, ssm2518_sysclks_3072000, }, +}; + +static const unsigned int ssm2518_rates_2048000[] = { + 8000, 16000, 32000, +}; + +static const struct snd_pcm_hw_constraint_list ssm2518_constraints_2048000 = { + .list = ssm2518_rates_2048000, + .count = ARRAY_SIZE(ssm2518_rates_2048000), +}; + +static const unsigned int ssm2518_rates_2822000[] = { + 11025, 22050, 44100, +}; + +static const struct snd_pcm_hw_constraint_list ssm2518_constraints_2822000 = { + .list = ssm2518_rates_2822000, + .count = ARRAY_SIZE(ssm2518_rates_2822000), +}; + +static const unsigned int ssm2518_rates_3072000[] = { + 12000, 24000, 48000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list ssm2518_constraints_3072000 = { + .list = ssm2518_rates_3072000, + .count = ARRAY_SIZE(ssm2518_rates_3072000), +}; + +static const unsigned int ssm2518_rates_12288000[] = { + 8000, 12000, 16000, 24000, 32000, 48000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list ssm2518_constraints_12288000 = { + .list = ssm2518_rates_12288000, + .count = ARRAY_SIZE(ssm2518_rates_12288000), +}; + +static int ssm2518_lookup_mcs(struct ssm2518 *ssm2518, + unsigned int rate) +{ + const unsigned int *sysclks = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(ssm2518_mcs_lut); i++) { + if (ssm2518_mcs_lut[i].rate == rate) { + sysclks = ssm2518_mcs_lut[i].sysclks; + break; + } + } + + if (!sysclks) + return -EINVAL; + + for (i = 0; sysclks[i]; i++) { + if (sysclks[i] == ssm2518->sysclk) + return i; + } + + return -EINVAL; +} + +static int ssm2518_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(component); + unsigned int rate = params_rate(params); + unsigned int ctrl1, ctrl1_mask; + int mcs; + int ret; + + mcs = ssm2518_lookup_mcs(ssm2518, rate); + if (mcs < 0) + return mcs; + + ctrl1_mask = SSM2518_SAI_CTRL1_FS_MASK; + + if (rate >= 8000 && rate <= 12000) + ctrl1 = SSM2518_SAI_CTRL1_FS_8000_12000; + else if (rate >= 16000 && rate <= 24000) + ctrl1 = SSM2518_SAI_CTRL1_FS_16000_24000; + else if (rate >= 32000 && rate <= 48000) + ctrl1 = SSM2518_SAI_CTRL1_FS_32000_48000; + else if (rate >= 64000 && rate <= 96000) + ctrl1 = SSM2518_SAI_CTRL1_FS_64000_96000; + else + return -EINVAL; + + if (ssm2518->right_j) { + switch (params_width(params)) { + case 16: + ctrl1 |= SSM2518_SAI_CTRL1_FMT_RJ_16BIT; + break; + case 24: + ctrl1 |= SSM2518_SAI_CTRL1_FMT_RJ_24BIT; + break; + default: + return -EINVAL; + } + ctrl1_mask |= SSM2518_SAI_CTRL1_FMT_MASK; + } + + /* Disable auto samplerate detection */ + ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_CLOCK, + SSM2518_CLOCK_ASR, SSM2518_CLOCK_ASR); + if (ret < 0) + return ret; + + ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_SAI_CTRL1, + ctrl1_mask, ctrl1); + if (ret < 0) + return ret; + + return regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1, + SSM2518_POWER1_MCS_MASK, mcs << 1); +} + +static int ssm2518_mute(struct snd_soc_dai *dai, int mute) +{ + struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(dai->component); + unsigned int val; + + if (mute) + val = SSM2518_MUTE_CTRL_MUTE_MASTER; + else + val = 0; + + return regmap_update_bits(ssm2518->regmap, SSM2518_REG_MUTE_CTRL, + SSM2518_MUTE_CTRL_MUTE_MASTER, val); +} + +static int ssm2518_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(dai->component); + unsigned int ctrl1 = 0, ctrl2 = 0; + bool invert_fclk; + int ret; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_fclk = false; + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl2 |= SSM2518_SAI_CTRL2_BCLK_INVERT; + invert_fclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + invert_fclk = true; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl2 |= SSM2518_SAI_CTRL2_BCLK_INVERT; + invert_fclk = true; + break; + default: + return -EINVAL; + } + + ssm2518->right_j = false; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl1 |= SSM2518_SAI_CTRL1_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1 |= SSM2518_SAI_CTRL1_FMT_LJ; + invert_fclk = !invert_fclk; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl1 |= SSM2518_SAI_CTRL1_FMT_RJ_24BIT; + ssm2518->right_j = true; + invert_fclk = !invert_fclk; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl2 |= SSM2518_SAI_CTRL2_LRCLK_PULSE; + ctrl1 |= SSM2518_SAI_CTRL1_FMT_I2S; + invert_fclk = false; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl2 |= SSM2518_SAI_CTRL2_LRCLK_PULSE; + ctrl1 |= SSM2518_SAI_CTRL1_FMT_LJ; + invert_fclk = false; + break; + default: + return -EINVAL; + } + + if (invert_fclk) + ctrl2 |= SSM2518_SAI_CTRL2_LRCLK_INVERT; + + ret = regmap_write(ssm2518->regmap, SSM2518_REG_SAI_CTRL1, ctrl1); + if (ret) + return ret; + + return regmap_write(ssm2518->regmap, SSM2518_REG_SAI_CTRL2, ctrl2); +} + +static int ssm2518_set_power(struct ssm2518 *ssm2518, bool enable) +{ + int ret = 0; + + if (!enable) { + ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1, + SSM2518_POWER1_SPWDN, SSM2518_POWER1_SPWDN); + regcache_mark_dirty(ssm2518->regmap); + } + + if (gpio_is_valid(ssm2518->enable_gpio)) + gpio_set_value(ssm2518->enable_gpio, enable); + + regcache_cache_only(ssm2518->regmap, !enable); + + if (enable) { + ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1, + SSM2518_POWER1_SPWDN | SSM2518_POWER1_RESET, 0x00); + regcache_sync(ssm2518->regmap); + } + + return ret; +} + +static int ssm2518_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + ret = ssm2518_set_power(ssm2518, true); + break; + case SND_SOC_BIAS_OFF: + ret = ssm2518_set_power(ssm2518, false); + break; + } + + return ret; +} + +static int ssm2518_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(dai->component); + unsigned int ctrl1, ctrl2; + int left_slot, right_slot; + int ret; + + if (slots == 0) + return regmap_update_bits(ssm2518->regmap, + SSM2518_REG_SAI_CTRL1, SSM2518_SAI_CTRL1_SAI_MASK, + SSM2518_SAI_CTRL1_SAI_I2S); + + if (tx_mask == 0 || rx_mask != 0) + return -EINVAL; + + if (slots == 1) { + if (tx_mask != 1) + return -EINVAL; + left_slot = 0; + right_slot = 0; + } else { + /* We assume the left channel < right channel */ + left_slot = __ffs(tx_mask); + tx_mask &= ~(1 << left_slot); + if (tx_mask == 0) { + right_slot = left_slot; + } else { + right_slot = __ffs(tx_mask); + tx_mask &= ~(1 << right_slot); + } + } + + if (tx_mask != 0 || left_slot >= slots || right_slot >= slots) + return -EINVAL; + + switch (width) { + case 16: + ctrl2 = SSM2518_SAI_CTRL2_SLOT_WIDTH_16; + break; + case 24: + ctrl2 = SSM2518_SAI_CTRL2_SLOT_WIDTH_24; + break; + case 32: + ctrl2 = SSM2518_SAI_CTRL2_SLOT_WIDTH_32; + break; + default: + return -EINVAL; + } + + switch (slots) { + case 1: + ctrl1 = SSM2518_SAI_CTRL1_SAI_MONO; + break; + case 2: + ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_2; + break; + case 4: + ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_4; + break; + case 8: + ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_8; + break; + case 16: + ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_16; + break; + default: + return -EINVAL; + } + + ret = regmap_write(ssm2518->regmap, SSM2518_REG_CHAN_MAP, + (left_slot << SSM2518_CHAN_MAP_LEFT_SLOT_OFFSET) | + (right_slot << SSM2518_CHAN_MAP_RIGHT_SLOT_OFFSET)); + if (ret) + return ret; + + ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_SAI_CTRL1, + SSM2518_SAI_CTRL1_SAI_MASK, ctrl1); + if (ret) + return ret; + + return regmap_update_bits(ssm2518->regmap, SSM2518_REG_SAI_CTRL2, + SSM2518_SAI_CTRL2_SLOT_WIDTH_MASK, ctrl2); +} + +static int ssm2518_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(dai->component); + + if (ssm2518->constraints) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, ssm2518->constraints); + + return 0; +} + +#define SSM2518_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32) + +static const struct snd_soc_dai_ops ssm2518_dai_ops = { + .startup = ssm2518_startup, + .hw_params = ssm2518_hw_params, + .digital_mute = ssm2518_mute, + .set_fmt = ssm2518_set_dai_fmt, + .set_tdm_slot = ssm2518_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ssm2518_dai = { + .name = "ssm2518-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SSM2518_FORMATS, + }, + .ops = &ssm2518_dai_ops, +}; + +static int ssm2518_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (clk_id != SSM2518_SYSCLK) + return -EINVAL; + + switch (source) { + case SSM2518_SYSCLK_SRC_MCLK: + val = 0; + break; + case SSM2518_SYSCLK_SRC_BCLK: + /* In this case the bitclock is used as the system clock, and + * the bitclock signal needs to be connected to the MCLK pin and + * the BCLK pin is left unconnected */ + val = SSM2518_POWER1_NO_BCLK; + break; + default: + return -EINVAL; + } + + switch (freq) { + case 0: + ssm2518->constraints = NULL; + break; + case 2048000: + case 4096000: + case 8192000: + case 3200000: + case 6400000: + case 12800000: + ssm2518->constraints = &ssm2518_constraints_2048000; + break; + case 2822000: + case 5644800: + case 11289600: + case 16934400: + case 22579200: + case 33868800: + case 4410000: + case 8820000: + case 17640000: + ssm2518->constraints = &ssm2518_constraints_2822000; + break; + case 3072000: + case 6144000: + case 38864000: + case 4800000: + case 9600000: + case 19200000: + ssm2518->constraints = &ssm2518_constraints_3072000; + break; + case 12288000: + case 16384000: + case 24576000: + ssm2518->constraints = &ssm2518_constraints_12288000; + break; + default: + return -EINVAL; + } + + ssm2518->sysclk = freq; + + return regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1, + SSM2518_POWER1_NO_BCLK, val); +} + +static const struct snd_soc_component_driver ssm2518_component_driver = { + .set_bias_level = ssm2518_set_bias_level, + .set_sysclk = ssm2518_set_sysclk, + .controls = ssm2518_snd_controls, + .num_controls = ARRAY_SIZE(ssm2518_snd_controls), + .dapm_widgets = ssm2518_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ssm2518_dapm_widgets), + .dapm_routes = ssm2518_routes, + .num_dapm_routes = ARRAY_SIZE(ssm2518_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ssm2518_regmap_config = { + .val_bits = 8, + .reg_bits = 8, + + .max_register = SSM2518_REG_DRC_9, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ssm2518_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ssm2518_reg_defaults), +}; + +static int ssm2518_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ssm2518_platform_data *pdata = i2c->dev.platform_data; + struct ssm2518 *ssm2518; + int ret; + + ssm2518 = devm_kzalloc(&i2c->dev, sizeof(*ssm2518), GFP_KERNEL); + if (ssm2518 == NULL) + return -ENOMEM; + + if (pdata) { + ssm2518->enable_gpio = pdata->enable_gpio; + } else if (i2c->dev.of_node) { + ssm2518->enable_gpio = of_get_gpio(i2c->dev.of_node, 0); + if (ssm2518->enable_gpio < 0 && ssm2518->enable_gpio != -ENOENT) + return ssm2518->enable_gpio; + } else { + ssm2518->enable_gpio = -1; + } + + if (gpio_is_valid(ssm2518->enable_gpio)) { + ret = devm_gpio_request_one(&i2c->dev, ssm2518->enable_gpio, + GPIOF_OUT_INIT_HIGH, "SSM2518 nSD"); + if (ret) + return ret; + } + + i2c_set_clientdata(i2c, ssm2518); + + ssm2518->regmap = devm_regmap_init_i2c(i2c, &ssm2518_regmap_config); + if (IS_ERR(ssm2518->regmap)) + return PTR_ERR(ssm2518->regmap); + + /* + * The reset bit is obviously volatile, but we need to be able to cache + * the other bits in the register, so we can't just mark the whole + * register as volatile. Since this is the only place where we'll ever + * touch the reset bit just bypass the cache for this operation. + */ + regcache_cache_bypass(ssm2518->regmap, true); + ret = regmap_write(ssm2518->regmap, SSM2518_REG_POWER1, + SSM2518_POWER1_RESET); + regcache_cache_bypass(ssm2518->regmap, false); + if (ret) + return ret; + + ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER2, + SSM2518_POWER2_APWDN, 0x00); + if (ret) + return ret; + + ret = ssm2518_set_power(ssm2518, false); + if (ret) + return ret; + + return devm_snd_soc_register_component(&i2c->dev, + &ssm2518_component_driver, + &ssm2518_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id ssm2518_dt_ids[] = { + { .compatible = "adi,ssm2518", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm2518_dt_ids); +#endif + +static const struct i2c_device_id ssm2518_i2c_ids[] = { + { "ssm2518", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm2518_i2c_ids); + +static struct i2c_driver ssm2518_driver = { + .driver = { + .name = "ssm2518", + .of_match_table = of_match_ptr(ssm2518_dt_ids), + }, + .probe = ssm2518_i2c_probe, + .id_table = ssm2518_i2c_ids, +}; +module_i2c_driver(ssm2518_driver); + +MODULE_DESCRIPTION("ASoC SSM2518 driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2518.h b/sound/soc/codecs/ssm2518.h new file mode 100644 index 000000000..62511d805 --- /dev/null +++ b/sound/soc/codecs/ssm2518.h @@ -0,0 +1,20 @@ +/* + * SSM2518 amplifier audio driver + * + * Copyright 2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#ifndef __SND_SOC_CODECS_SSM2518_H__ +#define __SND_SOC_CODECS_SSM2518_H__ + +#define SSM2518_SYSCLK 0 + +enum ssm2518_sysclk_src { + SSM2518_SYSCLK_SRC_MCLK = 0, + SSM2518_SYSCLK_SRC_BCLK = 1, +}; + +#endif diff --git a/sound/soc/codecs/ssm2602-i2c.c b/sound/soc/codecs/ssm2602-i2c.c new file mode 100644 index 000000000..f1c2b1a3a --- /dev/null +++ b/sound/soc/codecs/ssm2602-i2c.c @@ -0,0 +1,58 @@ +/* + * SSM2602/SSM2603/SSM2604 I2C audio driver + * + * Copyright 2014 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include + +#include + +#include "ssm2602.h" + +/* + * ssm2602 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static int ssm2602_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return ssm2602_probe(&client->dev, id->driver_data, + devm_regmap_init_i2c(client, &ssm2602_regmap_config)); +} + +static const struct i2c_device_id ssm2602_i2c_id[] = { + { "ssm2602", SSM2602 }, + { "ssm2603", SSM2602 }, + { "ssm2604", SSM2604 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id); + +static const struct of_device_id ssm2602_of_match[] = { + { .compatible = "adi,ssm2602", }, + { .compatible = "adi,ssm2603", }, + { .compatible = "adi,ssm2604", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm2602_of_match); + +static struct i2c_driver ssm2602_i2c_driver = { + .driver = { + .name = "ssm2602", + .of_match_table = ssm2602_of_match, + }, + .probe = ssm2602_i2c_probe, + .id_table = ssm2602_i2c_id, +}; +module_i2c_driver(ssm2602_i2c_driver); + +MODULE_DESCRIPTION("ASoC SSM2602/SSM2603/SSM2604 I2C driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2602-spi.c b/sound/soc/codecs/ssm2602-spi.c new file mode 100644 index 000000000..884862857 --- /dev/null +++ b/sound/soc/codecs/ssm2602-spi.c @@ -0,0 +1,40 @@ +/* + * SSM2602 SPI audio driver + * + * Copyright 2014 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include + +#include + +#include "ssm2602.h" + +static int ssm2602_spi_probe(struct spi_device *spi) +{ + return ssm2602_probe(&spi->dev, SSM2602, + devm_regmap_init_spi(spi, &ssm2602_regmap_config)); +} + +static const struct of_device_id ssm2602_of_match[] = { + { .compatible = "adi,ssm2602", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm2602_of_match); + +static struct spi_driver ssm2602_spi_driver = { + .driver = { + .name = "ssm2602", + .of_match_table = ssm2602_of_match, + }, + .probe = ssm2602_spi_probe, +}; +module_spi_driver(ssm2602_spi_driver); + +MODULE_DESCRIPTION("ASoC SSM2602 SPI driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c new file mode 100644 index 000000000..501a4e73b --- /dev/null +++ b/sound/soc/codecs/ssm2602.c @@ -0,0 +1,659 @@ +/* + * File: sound/soc/codecs/ssm2602.c + * Author: Cliff Cai + * + * Created: Tue June 06 2008 + * Description: Driver for ssm2602 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "ssm2602.h" + +/* codec private data */ +struct ssm2602_priv { + unsigned int sysclk; + const struct snd_pcm_hw_constraint_list *sysclk_constraints; + + struct regmap *regmap; + + enum ssm2602_type type; + unsigned int clk_out_pwr; +}; + +/* + * ssm2602 register cache + * We can't read the ssm2602 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const struct reg_default ssm2602_reg[SSM2602_CACHEREGNUM] = { + { .reg = 0x00, .def = 0x0097 }, + { .reg = 0x01, .def = 0x0097 }, + { .reg = 0x02, .def = 0x0079 }, + { .reg = 0x03, .def = 0x0079 }, + { .reg = 0x04, .def = 0x000a }, + { .reg = 0x05, .def = 0x0008 }, + { .reg = 0x06, .def = 0x009f }, + { .reg = 0x07, .def = 0x000a }, + { .reg = 0x08, .def = 0x0000 }, + { .reg = 0x09, .def = 0x0000 } +}; + + +/*Appending several "None"s just for OSS mixer use*/ +static const char *ssm2602_input_select[] = { + "Line", "Mic", +}; + +static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum ssm2602_enum[] = { + SOC_ENUM_SINGLE(SSM2602_APANA, 2, ARRAY_SIZE(ssm2602_input_select), + ssm2602_input_select), + SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, ARRAY_SIZE(ssm2602_deemph), + ssm2602_deemph), +}; + +static const DECLARE_TLV_DB_RANGE(ssm260x_outmix_tlv, + 0, 47, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 48, 127, TLV_DB_SCALE_ITEM(-7400, 100, 0) +); + +static const DECLARE_TLV_DB_SCALE(ssm260x_inpga_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(ssm260x_sidetone_tlv, -1500, 300, 0); + +static const struct snd_kcontrol_new ssm260x_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 45, 0, + ssm260x_inpga_tlv), +SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0), + +SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), +}; + +static const struct snd_kcontrol_new ssm2602_snd_controls[] = { +SOC_DOUBLE_R_TLV("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, + 0, 127, 0, ssm260x_outmix_tlv), +SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, + 7, 1, 0), +SOC_SINGLE_TLV("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1, + ssm260x_sidetone_tlv), + +SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 8, 1, 0), +SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), +}; + +/* Output Mixer */ +static const struct snd_kcontrol_new ssm260x_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ssm2602_input_mux_controls = +SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]); + +static const struct snd_soc_dapm_widget ssm260x_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1), +SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0), + +SND_SOC_DAPM_SUPPLY("Digital Core Power", SSM2602_ACTIVE, 0, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1, + ssm260x_output_mixer_controls, + ARRAY_SIZE(ssm260x_output_mixer_controls)), + +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls), +SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1), + +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_INPUT("MICIN"), +}; + +static const struct snd_soc_dapm_widget ssm2604_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, + ssm260x_output_mixer_controls, + ARRAY_SIZE(ssm260x_output_mixer_controls) - 1), /* Last element is the mic */ +}; + +static const struct snd_soc_dapm_route ssm260x_routes[] = { + {"DAC", NULL, "Digital Core Power"}, + {"ADC", NULL, "Digital Core Power"}, + + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + + {"ROUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, +}; + +static const struct snd_soc_dapm_route ssm2602_routes[] = { + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + {"RHPOUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + + {"Input Mux", "Line", "Line Input"}, + {"Input Mux", "Mic", "Mic Bias"}, + {"ADC", NULL, "Input Mux"}, + + {"Mic Bias", NULL, "MICIN"}, +}; + +static const struct snd_soc_dapm_route ssm2604_routes[] = { + {"ADC", NULL, "Line Input"}, +}; + +static const unsigned int ssm2602_rates_12288000[] = { + 8000, 16000, 32000, 48000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list ssm2602_constraints_12288000 = { + .list = ssm2602_rates_12288000, + .count = ARRAY_SIZE(ssm2602_rates_12288000), +}; + +static const unsigned int ssm2602_rates_11289600[] = { + 8000, 11025, 22050, 44100, 88200, +}; + +static const struct snd_pcm_hw_constraint_list ssm2602_constraints_11289600 = { + .list = ssm2602_rates_11289600, + .count = ARRAY_SIZE(ssm2602_rates_11289600), +}; + +struct ssm2602_coeff { + u32 mclk; + u32 rate; + u8 srate; +}; + +#define SSM2602_COEFF_SRATE(sr, bosr, usb) (((sr) << 2) | ((bosr) << 1) | (usb)) + +/* codec mclk clock coefficients */ +static const struct ssm2602_coeff ssm2602_coeff_table[] = { + /* 48k */ + {12288000, 48000, SSM2602_COEFF_SRATE(0x0, 0x0, 0x0)}, + {18432000, 48000, SSM2602_COEFF_SRATE(0x0, 0x1, 0x0)}, + {12000000, 48000, SSM2602_COEFF_SRATE(0x0, 0x0, 0x1)}, + + /* 32k */ + {12288000, 32000, SSM2602_COEFF_SRATE(0x6, 0x0, 0x0)}, + {18432000, 32000, SSM2602_COEFF_SRATE(0x6, 0x1, 0x0)}, + {12000000, 32000, SSM2602_COEFF_SRATE(0x6, 0x0, 0x1)}, + + /* 16k */ + {12288000, 16000, SSM2602_COEFF_SRATE(0x5, 0x0, 0x0)}, + {18432000, 16000, SSM2602_COEFF_SRATE(0x5, 0x1, 0x0)}, + {12000000, 16000, SSM2602_COEFF_SRATE(0xa, 0x0, 0x1)}, + + /* 8k */ + {12288000, 8000, SSM2602_COEFF_SRATE(0x3, 0x0, 0x0)}, + {18432000, 8000, SSM2602_COEFF_SRATE(0x3, 0x1, 0x0)}, + {11289600, 8000, SSM2602_COEFF_SRATE(0xb, 0x0, 0x0)}, + {16934400, 8000, SSM2602_COEFF_SRATE(0xb, 0x1, 0x0)}, + {12000000, 8000, SSM2602_COEFF_SRATE(0x3, 0x0, 0x1)}, + + /* 96k */ + {12288000, 96000, SSM2602_COEFF_SRATE(0x7, 0x0, 0x0)}, + {18432000, 96000, SSM2602_COEFF_SRATE(0x7, 0x1, 0x0)}, + {12000000, 96000, SSM2602_COEFF_SRATE(0x7, 0x0, 0x1)}, + + /* 11.025k */ + {11289600, 11025, SSM2602_COEFF_SRATE(0xc, 0x0, 0x0)}, + {16934400, 11025, SSM2602_COEFF_SRATE(0xc, 0x1, 0x0)}, + {12000000, 11025, SSM2602_COEFF_SRATE(0xc, 0x1, 0x1)}, + + /* 22.05k */ + {11289600, 22050, SSM2602_COEFF_SRATE(0xd, 0x0, 0x0)}, + {16934400, 22050, SSM2602_COEFF_SRATE(0xd, 0x1, 0x0)}, + {12000000, 22050, SSM2602_COEFF_SRATE(0xd, 0x1, 0x1)}, + + /* 44.1k */ + {11289600, 44100, SSM2602_COEFF_SRATE(0x8, 0x0, 0x0)}, + {16934400, 44100, SSM2602_COEFF_SRATE(0x8, 0x1, 0x0)}, + {12000000, 44100, SSM2602_COEFF_SRATE(0x8, 0x1, 0x1)}, + + /* 88.2k */ + {11289600, 88200, SSM2602_COEFF_SRATE(0xf, 0x0, 0x0)}, + {16934400, 88200, SSM2602_COEFF_SRATE(0xf, 0x1, 0x0)}, + {12000000, 88200, SSM2602_COEFF_SRATE(0xf, 0x1, 0x1)}, +}; + +static inline int ssm2602_get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ssm2602_coeff_table); i++) { + if (ssm2602_coeff_table[i].rate == rate && + ssm2602_coeff_table[i].mclk == mclk) + return ssm2602_coeff_table[i].srate; + } + return -EINVAL; +} + +static int ssm2602_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(component); + int srate = ssm2602_get_coeff(ssm2602->sysclk, params_rate(params)); + unsigned int iface; + + if (srate < 0) + return srate; + + regmap_write(ssm2602->regmap, SSM2602_SRATE, srate); + + /* bit size */ + switch (params_width(params)) { + case 16: + iface = 0x0; + break; + case 20: + iface = 0x4; + break; + case 24: + iface = 0x8; + break; + case 32: + iface = 0xc; + break; + default: + return -EINVAL; + } + regmap_update_bits(ssm2602->regmap, SSM2602_IFACE, + IFACE_AUDIO_DATA_LEN, iface); + return 0; +} + +static int ssm2602_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(component); + + if (ssm2602->sysclk_constraints) { + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + ssm2602->sysclk_constraints); + } + + return 0; +} + +static int ssm2602_mute(struct snd_soc_dai *dai, int mute) +{ + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(dai->component); + + if (mute) + regmap_update_bits(ssm2602->regmap, SSM2602_APDIGI, + APDIGI_ENABLE_DAC_MUTE, + APDIGI_ENABLE_DAC_MUTE); + else + regmap_update_bits(ssm2602->regmap, SSM2602_APDIGI, + APDIGI_ENABLE_DAC_MUTE, 0); + return 0; +} + +static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(component); + + if (dir == SND_SOC_CLOCK_IN) { + if (clk_id != SSM2602_SYSCLK) + return -EINVAL; + + switch (freq) { + case 12288000: + case 18432000: + ssm2602->sysclk_constraints = &ssm2602_constraints_12288000; + break; + case 11289600: + case 16934400: + ssm2602->sysclk_constraints = &ssm2602_constraints_11289600; + break; + case 12000000: + ssm2602->sysclk_constraints = NULL; + break; + default: + return -EINVAL; + } + ssm2602->sysclk = freq; + } else { + unsigned int mask; + + switch (clk_id) { + case SSM2602_CLK_CLKOUT: + mask = PWR_CLK_OUT_PDN; + break; + case SSM2602_CLK_XTO: + mask = PWR_OSC_PDN; + break; + default: + return -EINVAL; + } + + if (freq == 0) + ssm2602->clk_out_pwr |= mask; + else + ssm2602->clk_out_pwr &= ~mask; + + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, + PWR_CLK_OUT_PDN | PWR_OSC_PDN, ssm2602->clk_out_pwr); + } + + return 0; +} + +static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(codec_dai->component); + unsigned int iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0013; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0003; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + regmap_write(ssm2602->regmap, SSM2602_IFACE, iface); + return 0; +} + +static int ssm2602_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid on, osc and clkout on if enabled */ + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, + PWR_POWER_OFF | PWR_CLK_OUT_PDN | PWR_OSC_PDN, + ssm2602->clk_out_pwr); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, + PWR_POWER_OFF | PWR_CLK_OUT_PDN | PWR_OSC_PDN, + PWR_CLK_OUT_PDN | PWR_OSC_PDN); + break; + case SND_SOC_BIAS_OFF: + /* everything off */ + regmap_update_bits(ssm2602->regmap, SSM2602_PWR, + PWR_POWER_OFF, PWR_POWER_OFF); + break; + + } + return 0; +} + +#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +#define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops ssm2602_dai_ops = { + .startup = ssm2602_startup, + .hw_params = ssm2602_hw_params, + .digital_mute = ssm2602_mute, + .set_sysclk = ssm2602_set_dai_sysclk, + .set_fmt = ssm2602_set_dai_fmt, +}; + +static struct snd_soc_dai_driver ssm2602_dai = { + .name = "ssm2602-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SSM2602_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SSM2602_FORMATS,}, + .ops = &ssm2602_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, +}; + +static int ssm2602_resume(struct snd_soc_component *component) +{ + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(component); + + regcache_sync(ssm2602->regmap); + + return 0; +} + +static int ssm2602_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(component); + int ret; + + regmap_update_bits(ssm2602->regmap, SSM2602_LOUT1V, + LOUT1V_LRHP_BOTH, LOUT1V_LRHP_BOTH); + regmap_update_bits(ssm2602->regmap, SSM2602_ROUT1V, + ROUT1V_RLHP_BOTH, ROUT1V_RLHP_BOTH); + + ret = snd_soc_add_component_controls(component, ssm2602_snd_controls, + ARRAY_SIZE(ssm2602_snd_controls)); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, ssm2602_dapm_widgets, + ARRAY_SIZE(ssm2602_dapm_widgets)); + if (ret) + return ret; + + return snd_soc_dapm_add_routes(dapm, ssm2602_routes, + ARRAY_SIZE(ssm2602_routes)); +} + +static int ssm2604_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int ret; + + ret = snd_soc_dapm_new_controls(dapm, ssm2604_dapm_widgets, + ARRAY_SIZE(ssm2604_dapm_widgets)); + if (ret) + return ret; + + return snd_soc_dapm_add_routes(dapm, ssm2604_routes, + ARRAY_SIZE(ssm2604_routes)); +} + +static int ssm260x_component_probe(struct snd_soc_component *component) +{ + struct ssm2602_priv *ssm2602 = snd_soc_component_get_drvdata(component); + int ret; + + ret = regmap_write(ssm2602->regmap, SSM2602_RESET, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + /* set the update bits */ + regmap_update_bits(ssm2602->regmap, SSM2602_LINVOL, + LINVOL_LRIN_BOTH, LINVOL_LRIN_BOTH); + regmap_update_bits(ssm2602->regmap, SSM2602_RINVOL, + RINVOL_RLIN_BOTH, RINVOL_RLIN_BOTH); + /*select Line in as default input*/ + regmap_write(ssm2602->regmap, SSM2602_APANA, APANA_SELECT_DAC | + APANA_ENABLE_MIC_BOOST); + + switch (ssm2602->type) { + case SSM2602: + ret = ssm2602_component_probe(component); + break; + case SSM2604: + ret = ssm2604_component_probe(component); + break; + } + + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_ssm2602 = { + .probe = ssm260x_component_probe, + .resume = ssm2602_resume, + .set_bias_level = ssm2602_set_bias_level, + .controls = ssm260x_snd_controls, + .num_controls = ARRAY_SIZE(ssm260x_snd_controls), + .dapm_widgets = ssm260x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ssm260x_dapm_widgets), + .dapm_routes = ssm260x_routes, + .num_dapm_routes = ARRAY_SIZE(ssm260x_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static bool ssm2602_register_volatile(struct device *dev, unsigned int reg) +{ + return reg == SSM2602_RESET; +} + +const struct regmap_config ssm2602_regmap_config = { + .val_bits = 9, + .reg_bits = 7, + + .max_register = SSM2602_RESET, + .volatile_reg = ssm2602_register_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ssm2602_reg, + .num_reg_defaults = ARRAY_SIZE(ssm2602_reg), +}; +EXPORT_SYMBOL_GPL(ssm2602_regmap_config); + +int ssm2602_probe(struct device *dev, enum ssm2602_type type, + struct regmap *regmap) +{ + struct ssm2602_priv *ssm2602; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ssm2602 = devm_kzalloc(dev, sizeof(*ssm2602), GFP_KERNEL); + if (ssm2602 == NULL) + return -ENOMEM; + + dev_set_drvdata(dev, ssm2602); + ssm2602->type = type; + ssm2602->regmap = regmap; + + return devm_snd_soc_register_component(dev, &soc_component_dev_ssm2602, + &ssm2602_dai, 1); +} +EXPORT_SYMBOL_GPL(ssm2602_probe); + +MODULE_DESCRIPTION("ASoC SSM2602/SSM2603/SSM2604 driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h new file mode 100644 index 000000000..747538847 --- /dev/null +++ b/sound/soc/codecs/ssm2602.h @@ -0,0 +1,139 @@ +/* + * File: sound/soc/codecs/ssm2602.h + * Author: Cliff Cai + * + * Created: Tue June 06 2008 + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SSM2602_H +#define _SSM2602_H + +#include + +struct device; + +enum ssm2602_type { + SSM2602, + SSM2604, +}; + +extern const struct regmap_config ssm2602_regmap_config; + +int ssm2602_probe(struct device *dev, enum ssm2602_type type, + struct regmap *regmap); + +/* SSM2602 Codec Register definitions */ + +#define SSM2602_LINVOL 0x00 +#define SSM2602_RINVOL 0x01 +#define SSM2602_LOUT1V 0x02 +#define SSM2602_ROUT1V 0x03 +#define SSM2602_APANA 0x04 +#define SSM2602_APDIGI 0x05 +#define SSM2602_PWR 0x06 +#define SSM2602_IFACE 0x07 +#define SSM2602_SRATE 0x08 +#define SSM2602_ACTIVE 0x09 +#define SSM2602_RESET 0x0f + +/*SSM2602 Codec Register Field definitions + *(Mask value to extract the corresponding Register field) + */ + +/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/ +#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */ + +/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/ +#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */ +#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */ +#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */ + +/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/ +#define LOUT1V_LHP_VOL 0x07F /* Left Channel Headphone volume control */ +#define LOUT1V_ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */ +#define LOUT1V_LRHP_BOTH 0x100 /* Left Channel Headphone volume update */ + +/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/ +#define ROUT1V_RHP_VOL 0x07F /* Right Channel Headphone volume control */ +#define ROUT1V_ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */ +#define ROUT1V_RLHP_BOTH 0x100 /* Right Channel Headphone volume update */ + +/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/ +#define APANA_ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */ +#define APANA_ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */ +#define APANA_ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */ +#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */ +#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */ +#define APANA_ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */ +#define APANA_SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */ +#define APANA_ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */ + +/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/ +#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */ +#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */ +#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */ +#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */ + +/*Power Down Control (SSM2602_REG_POWER) + *(1=Enable PowerDown, 0=Disable PowerDown) + */ +#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */ +#define PWR_MIC_PDN 0x002 /* Microphone Input & Bias Power Down */ +#define PWR_ADC_PDN 0x004 /* ADC Power Down */ +#define PWR_DAC_PDN 0x008 /* DAC Power Down */ +#define PWR_OUT_PDN 0x010 /* Outputs Power Down */ +#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */ +#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */ +#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */ + +/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/ +#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */ +#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */ +#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */ +#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */ +#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */ +#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */ + +/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/ +#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */ +#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */ +#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */ +#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */ +#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */ + +/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/ +#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */ + +/*********************************************************************/ + +#define SSM2602_CACHEREGNUM 10 + +enum ssm2602_clk { + SSM2602_SYSCLK, + SSM2602_CLK_CLKOUT, + SSM2602_CLK_XTO +}; + +#endif diff --git a/sound/soc/codecs/ssm4567.c b/sound/soc/codecs/ssm4567.c new file mode 100644 index 000000000..90119ea00 --- /dev/null +++ b/sound/soc/codecs/ssm4567.c @@ -0,0 +1,512 @@ +/* + * SSM4567 amplifier audio driver + * + * Copyright 2014 Google Chromium project. + * Author: Anatol Pomozov + * + * Based on code copyright/by: + * Copyright 2013 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSM4567_REG_POWER_CTRL 0x00 +#define SSM4567_REG_AMP_SNS_CTRL 0x01 +#define SSM4567_REG_DAC_CTRL 0x02 +#define SSM4567_REG_DAC_VOLUME 0x03 +#define SSM4567_REG_SAI_CTRL_1 0x04 +#define SSM4567_REG_SAI_CTRL_2 0x05 +#define SSM4567_REG_SAI_PLACEMENT_1 0x06 +#define SSM4567_REG_SAI_PLACEMENT_2 0x07 +#define SSM4567_REG_SAI_PLACEMENT_3 0x08 +#define SSM4567_REG_SAI_PLACEMENT_4 0x09 +#define SSM4567_REG_SAI_PLACEMENT_5 0x0a +#define SSM4567_REG_SAI_PLACEMENT_6 0x0b +#define SSM4567_REG_BATTERY_V_OUT 0x0c +#define SSM4567_REG_LIMITER_CTRL_1 0x0d +#define SSM4567_REG_LIMITER_CTRL_2 0x0e +#define SSM4567_REG_LIMITER_CTRL_3 0x0f +#define SSM4567_REG_STATUS_1 0x10 +#define SSM4567_REG_STATUS_2 0x11 +#define SSM4567_REG_FAULT_CTRL 0x12 +#define SSM4567_REG_PDM_CTRL 0x13 +#define SSM4567_REG_MCLK_RATIO 0x14 +#define SSM4567_REG_BOOST_CTRL_1 0x15 +#define SSM4567_REG_BOOST_CTRL_2 0x16 +#define SSM4567_REG_SOFT_RESET 0xff + +/* POWER_CTRL */ +#define SSM4567_POWER_APWDN_EN BIT(7) +#define SSM4567_POWER_BSNS_PWDN BIT(6) +#define SSM4567_POWER_VSNS_PWDN BIT(5) +#define SSM4567_POWER_ISNS_PWDN BIT(4) +#define SSM4567_POWER_BOOST_PWDN BIT(3) +#define SSM4567_POWER_AMP_PWDN BIT(2) +#define SSM4567_POWER_VBAT_ONLY BIT(1) +#define SSM4567_POWER_SPWDN BIT(0) + +/* DAC_CTRL */ +#define SSM4567_DAC_HV BIT(7) +#define SSM4567_DAC_MUTE BIT(6) +#define SSM4567_DAC_HPF BIT(5) +#define SSM4567_DAC_LPM BIT(4) +#define SSM4567_DAC_FS_MASK 0x7 +#define SSM4567_DAC_FS_8000_12000 0x0 +#define SSM4567_DAC_FS_16000_24000 0x1 +#define SSM4567_DAC_FS_32000_48000 0x2 +#define SSM4567_DAC_FS_64000_96000 0x3 +#define SSM4567_DAC_FS_128000_192000 0x4 + +/* SAI_CTRL_1 */ +#define SSM4567_SAI_CTRL_1_BCLK BIT(6) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_MASK (0x3 << 4) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_32 (0x0 << 4) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_48 (0x1 << 4) +#define SSM4567_SAI_CTRL_1_TDM_BLCKS_64 (0x2 << 4) +#define SSM4567_SAI_CTRL_1_FSYNC BIT(3) +#define SSM4567_SAI_CTRL_1_LJ BIT(2) +#define SSM4567_SAI_CTRL_1_TDM BIT(1) +#define SSM4567_SAI_CTRL_1_PDM BIT(0) + +/* SAI_CTRL_2 */ +#define SSM4567_SAI_CTRL_2_AUTO_SLOT BIT(3) +#define SSM4567_SAI_CTRL_2_TDM_SLOT_MASK 0x7 +#define SSM4567_SAI_CTRL_2_TDM_SLOT(x) (x) + +struct ssm4567 { + struct regmap *regmap; +}; + +static const struct reg_default ssm4567_reg_defaults[] = { + { SSM4567_REG_POWER_CTRL, 0x81 }, + { SSM4567_REG_AMP_SNS_CTRL, 0x09 }, + { SSM4567_REG_DAC_CTRL, 0x32 }, + { SSM4567_REG_DAC_VOLUME, 0x40 }, + { SSM4567_REG_SAI_CTRL_1, 0x00 }, + { SSM4567_REG_SAI_CTRL_2, 0x08 }, + { SSM4567_REG_SAI_PLACEMENT_1, 0x01 }, + { SSM4567_REG_SAI_PLACEMENT_2, 0x20 }, + { SSM4567_REG_SAI_PLACEMENT_3, 0x32 }, + { SSM4567_REG_SAI_PLACEMENT_4, 0x07 }, + { SSM4567_REG_SAI_PLACEMENT_5, 0x07 }, + { SSM4567_REG_SAI_PLACEMENT_6, 0x07 }, + { SSM4567_REG_BATTERY_V_OUT, 0x00 }, + { SSM4567_REG_LIMITER_CTRL_1, 0xa4 }, + { SSM4567_REG_LIMITER_CTRL_2, 0x73 }, + { SSM4567_REG_LIMITER_CTRL_3, 0x00 }, + { SSM4567_REG_STATUS_1, 0x00 }, + { SSM4567_REG_STATUS_2, 0x00 }, + { SSM4567_REG_FAULT_CTRL, 0x30 }, + { SSM4567_REG_PDM_CTRL, 0x40 }, + { SSM4567_REG_MCLK_RATIO, 0x11 }, + { SSM4567_REG_BOOST_CTRL_1, 0x03 }, + { SSM4567_REG_BOOST_CTRL_2, 0x00 }, + { SSM4567_REG_SOFT_RESET, 0x00 }, +}; + + +static bool ssm4567_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM4567_REG_POWER_CTRL ... SSM4567_REG_BOOST_CTRL_2: + return true; + default: + return false; + } + +} + +static bool ssm4567_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM4567_REG_POWER_CTRL ... SSM4567_REG_SAI_PLACEMENT_6: + case SSM4567_REG_LIMITER_CTRL_1 ... SSM4567_REG_LIMITER_CTRL_3: + case SSM4567_REG_FAULT_CTRL ... SSM4567_REG_BOOST_CTRL_2: + /* The datasheet states that soft reset register is read-only, + * but logically it is write-only. */ + case SSM4567_REG_SOFT_RESET: + return true; + default: + return false; + } +} + +static bool ssm4567_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM4567_REG_BATTERY_V_OUT: + case SSM4567_REG_STATUS_1 ... SSM4567_REG_STATUS_2: + case SSM4567_REG_SOFT_RESET: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_MINMAX_MUTE(ssm4567_vol_tlv, -7125, 2400); + +static const struct snd_kcontrol_new ssm4567_snd_controls[] = { + SOC_SINGLE_TLV("Master Playback Volume", SSM4567_REG_DAC_VOLUME, 0, + 0xff, 1, ssm4567_vol_tlv), + SOC_SINGLE("DAC Low Power Mode Switch", SSM4567_REG_DAC_CTRL, 4, 1, 0), + SOC_SINGLE("DAC High Pass Filter Switch", SSM4567_REG_DAC_CTRL, + 5, 1, 0), +}; + +static const struct snd_kcontrol_new ssm4567_amplifier_boost_control = + SOC_DAPM_SINGLE("Switch", SSM4567_REG_POWER_CTRL, 1, 1, 1); + +static const struct snd_soc_dapm_widget ssm4567_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM4567_REG_POWER_CTRL, 2, 1), + SND_SOC_DAPM_SWITCH("Amplifier Boost", SSM4567_REG_POWER_CTRL, 3, 1, + &ssm4567_amplifier_boost_control), + + SND_SOC_DAPM_SIGGEN("Sense"), + + SND_SOC_DAPM_PGA("Current Sense", SSM4567_REG_POWER_CTRL, 4, 1, NULL, 0), + SND_SOC_DAPM_PGA("Voltage Sense", SSM4567_REG_POWER_CTRL, 5, 1, NULL, 0), + SND_SOC_DAPM_PGA("VBAT Sense", SSM4567_REG_POWER_CTRL, 6, 1, NULL, 0), + + SND_SOC_DAPM_OUTPUT("OUT"), +}; + +static const struct snd_soc_dapm_route ssm4567_routes[] = { + { "OUT", NULL, "Amplifier Boost" }, + { "Amplifier Boost", "Switch", "DAC" }, + { "OUT", NULL, "DAC" }, + + { "Current Sense", NULL, "Sense" }, + { "Voltage Sense", NULL, "Sense" }, + { "VBAT Sense", NULL, "Sense" }, + { "Capture Sense", NULL, "Current Sense" }, + { "Capture Sense", NULL, "Voltage Sense" }, + { "Capture Sense", NULL, "VBAT Sense" }, +}; + +static int ssm4567_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct ssm4567 *ssm4567 = snd_soc_component_get_drvdata(component); + unsigned int rate = params_rate(params); + unsigned int dacfs; + + if (rate >= 8000 && rate <= 12000) + dacfs = SSM4567_DAC_FS_8000_12000; + else if (rate >= 16000 && rate <= 24000) + dacfs = SSM4567_DAC_FS_16000_24000; + else if (rate >= 32000 && rate <= 48000) + dacfs = SSM4567_DAC_FS_32000_48000; + else if (rate >= 64000 && rate <= 96000) + dacfs = SSM4567_DAC_FS_64000_96000; + else if (rate >= 128000 && rate <= 192000) + dacfs = SSM4567_DAC_FS_128000_192000; + else + return -EINVAL; + + return regmap_update_bits(ssm4567->regmap, SSM4567_REG_DAC_CTRL, + SSM4567_DAC_FS_MASK, dacfs); +} + +static int ssm4567_mute(struct snd_soc_dai *dai, int mute) +{ + struct ssm4567 *ssm4567 = snd_soc_component_get_drvdata(dai->component); + unsigned int val; + + val = mute ? SSM4567_DAC_MUTE : 0; + return regmap_update_bits(ssm4567->regmap, SSM4567_REG_DAC_CTRL, + SSM4567_DAC_MUTE, val); +} + +static int ssm4567_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct ssm4567 *ssm4567 = snd_soc_dai_get_drvdata(dai); + unsigned int blcks; + int slot; + int ret; + + if (tx_mask == 0) + return -EINVAL; + + if (rx_mask && rx_mask != tx_mask) + return -EINVAL; + + slot = __ffs(tx_mask); + if (tx_mask != BIT(slot)) + return -EINVAL; + + switch (width) { + case 32: + blcks = SSM4567_SAI_CTRL_1_TDM_BLCKS_32; + break; + case 48: + blcks = SSM4567_SAI_CTRL_1_TDM_BLCKS_48; + break; + case 64: + blcks = SSM4567_SAI_CTRL_1_TDM_BLCKS_64; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(ssm4567->regmap, SSM4567_REG_SAI_CTRL_2, + SSM4567_SAI_CTRL_2_AUTO_SLOT | SSM4567_SAI_CTRL_2_TDM_SLOT_MASK, + SSM4567_SAI_CTRL_2_TDM_SLOT(slot)); + if (ret) + return ret; + + return regmap_update_bits(ssm4567->regmap, SSM4567_REG_SAI_CTRL_1, + SSM4567_SAI_CTRL_1_TDM_BLCKS_MASK, blcks); +} + +static int ssm4567_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct ssm4567 *ssm4567 = snd_soc_dai_get_drvdata(dai); + unsigned int ctrl1 = 0; + bool invert_fclk; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_fclk = false; + break; + case SND_SOC_DAIFMT_IB_NF: + ctrl1 |= SSM4567_SAI_CTRL_1_BCLK; + invert_fclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + ctrl1 |= SSM4567_SAI_CTRL_1_FSYNC; + invert_fclk = true; + break; + case SND_SOC_DAIFMT_IB_IF: + ctrl1 |= SSM4567_SAI_CTRL_1_BCLK; + invert_fclk = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl1 |= SSM4567_SAI_CTRL_1_LJ; + invert_fclk = !invert_fclk; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl1 |= SSM4567_SAI_CTRL_1_TDM; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl1 |= SSM4567_SAI_CTRL_1_TDM | SSM4567_SAI_CTRL_1_LJ; + break; + case SND_SOC_DAIFMT_PDM: + ctrl1 |= SSM4567_SAI_CTRL_1_PDM; + break; + default: + return -EINVAL; + } + + if (invert_fclk) + ctrl1 |= SSM4567_SAI_CTRL_1_FSYNC; + + return regmap_update_bits(ssm4567->regmap, SSM4567_REG_SAI_CTRL_1, + SSM4567_SAI_CTRL_1_BCLK | + SSM4567_SAI_CTRL_1_FSYNC | + SSM4567_SAI_CTRL_1_LJ | + SSM4567_SAI_CTRL_1_TDM | + SSM4567_SAI_CTRL_1_PDM, + ctrl1); +} + +static int ssm4567_set_power(struct ssm4567 *ssm4567, bool enable) +{ + int ret = 0; + + if (!enable) { + ret = regmap_update_bits(ssm4567->regmap, + SSM4567_REG_POWER_CTRL, + SSM4567_POWER_SPWDN, SSM4567_POWER_SPWDN); + regcache_mark_dirty(ssm4567->regmap); + } + + regcache_cache_only(ssm4567->regmap, !enable); + + if (enable) { + ret = regmap_write(ssm4567->regmap, SSM4567_REG_SOFT_RESET, + 0x00); + if (ret) + return ret; + + ret = regmap_update_bits(ssm4567->regmap, + SSM4567_REG_POWER_CTRL, + SSM4567_POWER_SPWDN, 0x00); + regcache_sync(ssm4567->regmap); + } + + return ret; +} + +static int ssm4567_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct ssm4567 *ssm4567 = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + ret = ssm4567_set_power(ssm4567, true); + break; + case SND_SOC_BIAS_OFF: + ret = ssm4567_set_power(ssm4567, false); + break; + } + + return ret; +} + +static const struct snd_soc_dai_ops ssm4567_dai_ops = { + .hw_params = ssm4567_hw_params, + .digital_mute = ssm4567_mute, + .set_fmt = ssm4567_set_dai_fmt, + .set_tdm_slot = ssm4567_set_tdm_slot, +}; + +static struct snd_soc_dai_driver ssm4567_dai = { + .name = "ssm4567-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32, + }, + .capture = { + .stream_name = "Capture Sense", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32, + }, + .ops = &ssm4567_dai_ops, +}; + +static const struct snd_soc_component_driver ssm4567_component_driver = { + .set_bias_level = ssm4567_set_bias_level, + .controls = ssm4567_snd_controls, + .num_controls = ARRAY_SIZE(ssm4567_snd_controls), + .dapm_widgets = ssm4567_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ssm4567_dapm_widgets), + .dapm_routes = ssm4567_routes, + .num_dapm_routes = ARRAY_SIZE(ssm4567_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config ssm4567_regmap_config = { + .val_bits = 8, + .reg_bits = 8, + + .max_register = SSM4567_REG_SOFT_RESET, + .readable_reg = ssm4567_readable_reg, + .writeable_reg = ssm4567_writeable_reg, + .volatile_reg = ssm4567_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ssm4567_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ssm4567_reg_defaults), +}; + +static int ssm4567_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ssm4567 *ssm4567; + int ret; + + ssm4567 = devm_kzalloc(&i2c->dev, sizeof(*ssm4567), GFP_KERNEL); + if (ssm4567 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, ssm4567); + + ssm4567->regmap = devm_regmap_init_i2c(i2c, &ssm4567_regmap_config); + if (IS_ERR(ssm4567->regmap)) + return PTR_ERR(ssm4567->regmap); + + ret = regmap_write(ssm4567->regmap, SSM4567_REG_SOFT_RESET, 0x00); + if (ret) + return ret; + + ret = ssm4567_set_power(ssm4567, false); + if (ret) + return ret; + + return devm_snd_soc_register_component(&i2c->dev, &ssm4567_component_driver, + &ssm4567_dai, 1); +} + +static const struct i2c_device_id ssm4567_i2c_ids[] = { + { "ssm4567", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm4567_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id ssm4567_of_match[] = { + { .compatible = "adi,ssm4567", }, + { } +}; +MODULE_DEVICE_TABLE(of, ssm4567_of_match); +#endif + +#ifdef CONFIG_ACPI + +static const struct acpi_device_id ssm4567_acpi_match[] = { + { "INT343B", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, ssm4567_acpi_match); + +#endif + +static struct i2c_driver ssm4567_driver = { + .driver = { + .name = "ssm4567", + .of_match_table = of_match_ptr(ssm4567_of_match), + .acpi_match_table = ACPI_PTR(ssm4567_acpi_match), + }, + .probe = ssm4567_i2c_probe, + .id_table = ssm4567_i2c_ids, +}; +module_i2c_driver(ssm4567_driver); + +MODULE_DESCRIPTION("ASoC SSM4567 driver"); +MODULE_AUTHOR("Anatol Pomozov "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c new file mode 100644 index 000000000..ce508b4cc --- /dev/null +++ b/sound/soc/codecs/sta32x.c @@ -0,0 +1,1156 @@ +/* + * Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system + * + * Copyright: 2011 Raumfeld GmbH + * Author: Johannes Stezenbach + * + * based on code from: + * Wolfson Microelectronics PLC. + * Mark Brown + * Freescale Semiconductor, Inc. + * Timur Tabi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "sta32x.h" + +#define STA32X_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 STA32X_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE) + +/* Power-up register defaults */ +static const struct reg_default sta32x_regs[] = { + { 0x0, 0x63 }, + { 0x1, 0x80 }, + { 0x2, 0xc2 }, + { 0x3, 0x40 }, + { 0x4, 0xc2 }, + { 0x5, 0x5c }, + { 0x6, 0x10 }, + { 0x7, 0xff }, + { 0x8, 0x60 }, + { 0x9, 0x60 }, + { 0xa, 0x60 }, + { 0xb, 0x80 }, + { 0xc, 0x00 }, + { 0xd, 0x00 }, + { 0xe, 0x00 }, + { 0xf, 0x40 }, + { 0x10, 0x80 }, + { 0x11, 0x77 }, + { 0x12, 0x6a }, + { 0x13, 0x69 }, + { 0x14, 0x6a }, + { 0x15, 0x69 }, + { 0x16, 0x00 }, + { 0x17, 0x00 }, + { 0x18, 0x00 }, + { 0x19, 0x00 }, + { 0x1a, 0x00 }, + { 0x1b, 0x00 }, + { 0x1c, 0x00 }, + { 0x1d, 0x00 }, + { 0x1e, 0x00 }, + { 0x1f, 0x00 }, + { 0x20, 0x00 }, + { 0x21, 0x00 }, + { 0x22, 0x00 }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x2d }, + { 0x28, 0xc0 }, + { 0x2b, 0x00 }, + { 0x2c, 0x0c }, +}; + +static const struct regmap_range sta32x_write_regs_range[] = { + regmap_reg_range(STA32X_CONFA, STA32X_FDRC2), +}; + +static const struct regmap_range sta32x_read_regs_range[] = { + regmap_reg_range(STA32X_CONFA, STA32X_FDRC2), +}; + +static const struct regmap_range sta32x_volatile_regs_range[] = { + regmap_reg_range(STA32X_CFADDR2, STA32X_CFUD), +}; + +static const struct regmap_access_table sta32x_write_regs = { + .yes_ranges = sta32x_write_regs_range, + .n_yes_ranges = ARRAY_SIZE(sta32x_write_regs_range), +}; + +static const struct regmap_access_table sta32x_read_regs = { + .yes_ranges = sta32x_read_regs_range, + .n_yes_ranges = ARRAY_SIZE(sta32x_read_regs_range), +}; + +static const struct regmap_access_table sta32x_volatile_regs = { + .yes_ranges = sta32x_volatile_regs_range, + .n_yes_ranges = ARRAY_SIZE(sta32x_volatile_regs_range), +}; + +/* regulator power supply names */ +static const char *sta32x_supply_names[] = { + "Vdda", /* analog supply, 3.3VV */ + "Vdd3", /* digital supply, 3.3V */ + "Vcc" /* power amp spply, 10V - 36V */ +}; + +/* codec private data */ +struct sta32x_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[ARRAY_SIZE(sta32x_supply_names)]; + struct snd_soc_component *component; + struct sta32x_platform_data *pdata; + + unsigned int mclk; + unsigned int format; + + u32 coef_shadow[STA32X_COEF_COUNT]; + struct delayed_work watchdog_work; + int shutdown; + struct gpio_desc *gpiod_nreset; + struct mutex coeff_lock; +}; + +static const DECLARE_TLV_DB_SCALE(mvol_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(chvol_tlv, -7950, 50, 1); +static const DECLARE_TLV_DB_SCALE(tone_tlv, -120, 200, 0); + +static const char *sta32x_drc_ac[] = { + "Anti-Clipping", "Dynamic Range Compression" }; +static const char *sta32x_auto_eq_mode[] = { + "User", "Preset", "Loudness" }; +static const char *sta32x_auto_gc_mode[] = { + "User", "AC no clipping", "AC limited clipping (10%)", + "DRC nighttime listening mode" }; +static const char *sta32x_auto_xo_mode[] = { + "User", "80Hz", "100Hz", "120Hz", "140Hz", "160Hz", "180Hz", "200Hz", + "220Hz", "240Hz", "260Hz", "280Hz", "300Hz", "320Hz", "340Hz", "360Hz" }; +static const char *sta32x_preset_eq_mode[] = { + "Flat", "Rock", "Soft Rock", "Jazz", "Classical", "Dance", "Pop", "Soft", + "Hard", "Party", "Vocal", "Hip-Hop", "Dialog", "Bass-boost #1", + "Bass-boost #2", "Bass-boost #3", "Loudness 1", "Loudness 2", + "Loudness 3", "Loudness 4", "Loudness 5", "Loudness 6", "Loudness 7", + "Loudness 8", "Loudness 9", "Loudness 10", "Loudness 11", "Loudness 12", + "Loudness 13", "Loudness 14", "Loudness 15", "Loudness 16" }; +static const char *sta32x_limiter_select[] = { + "Limiter Disabled", "Limiter #1", "Limiter #2" }; +static const char *sta32x_limiter_attack_rate[] = { + "3.1584", "2.7072", "2.2560", "1.8048", "1.3536", "0.9024", + "0.4512", "0.2256", "0.1504", "0.1123", "0.0902", "0.0752", + "0.0645", "0.0564", "0.0501", "0.0451" }; +static const char *sta32x_limiter_release_rate[] = { + "0.5116", "0.1370", "0.0744", "0.0499", "0.0360", "0.0299", + "0.0264", "0.0208", "0.0198", "0.0172", "0.0147", "0.0137", + "0.0134", "0.0117", "0.0110", "0.0104" }; +static DECLARE_TLV_DB_RANGE(sta32x_limiter_ac_attack_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-1200, 200, 0), + 8, 16, TLV_DB_SCALE_ITEM(300, 100, 0), +); + +static DECLARE_TLV_DB_RANGE(sta32x_limiter_ac_release_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(-2900, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(-2000, 0, 0), + 3, 8, TLV_DB_SCALE_ITEM(-1400, 200, 0), + 8, 16, TLV_DB_SCALE_ITEM(-700, 100, 0), +); + +static DECLARE_TLV_DB_RANGE(sta32x_limiter_drc_attack_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-3100, 200, 0), + 8, 13, TLV_DB_SCALE_ITEM(-1600, 100, 0), + 14, 16, TLV_DB_SCALE_ITEM(-1000, 300, 0), +); + +static DECLARE_TLV_DB_RANGE(sta32x_limiter_drc_release_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 1, 2, TLV_DB_SCALE_ITEM(-3800, 200, 0), + 3, 4, TLV_DB_SCALE_ITEM(-3300, 200, 0), + 5, 12, TLV_DB_SCALE_ITEM(-3000, 200, 0), + 13, 16, TLV_DB_SCALE_ITEM(-1500, 300, 0), +); + +static SOC_ENUM_SINGLE_DECL(sta32x_drc_ac_enum, + STA32X_CONFD, STA32X_CONFD_DRC_SHIFT, + sta32x_drc_ac); +static SOC_ENUM_SINGLE_DECL(sta32x_auto_eq_enum, + STA32X_AUTO1, STA32X_AUTO1_AMEQ_SHIFT, + sta32x_auto_eq_mode); +static SOC_ENUM_SINGLE_DECL(sta32x_auto_gc_enum, + STA32X_AUTO1, STA32X_AUTO1_AMGC_SHIFT, + sta32x_auto_gc_mode); +static SOC_ENUM_SINGLE_DECL(sta32x_auto_xo_enum, + STA32X_AUTO2, STA32X_AUTO2_XO_SHIFT, + sta32x_auto_xo_mode); +static SOC_ENUM_SINGLE_DECL(sta32x_preset_eq_enum, + STA32X_AUTO3, STA32X_AUTO3_PEQ_SHIFT, + sta32x_preset_eq_mode); +static SOC_ENUM_SINGLE_DECL(sta32x_limiter_ch1_enum, + STA32X_C1CFG, STA32X_CxCFG_LS_SHIFT, + sta32x_limiter_select); +static SOC_ENUM_SINGLE_DECL(sta32x_limiter_ch2_enum, + STA32X_C2CFG, STA32X_CxCFG_LS_SHIFT, + sta32x_limiter_select); +static SOC_ENUM_SINGLE_DECL(sta32x_limiter_ch3_enum, + STA32X_C3CFG, STA32X_CxCFG_LS_SHIFT, + sta32x_limiter_select); +static SOC_ENUM_SINGLE_DECL(sta32x_limiter1_attack_rate_enum, + STA32X_L1AR, STA32X_LxA_SHIFT, + sta32x_limiter_attack_rate); +static SOC_ENUM_SINGLE_DECL(sta32x_limiter2_attack_rate_enum, + STA32X_L2AR, STA32X_LxA_SHIFT, + sta32x_limiter_attack_rate); +static SOC_ENUM_SINGLE_DECL(sta32x_limiter1_release_rate_enum, + STA32X_L1AR, STA32X_LxR_SHIFT, + sta32x_limiter_release_rate); +static SOC_ENUM_SINGLE_DECL(sta32x_limiter2_release_rate_enum, + STA32X_L2AR, STA32X_LxR_SHIFT, + sta32x_limiter_release_rate); + +/* byte array controls for setting biquad, mixer, scaling coefficients; + * for biquads all five coefficients need to be set in one go, + * mixer and pre/postscale coefs can be set individually; + * each coef is 24bit, the bytes are ordered in the same way + * as given in the STA32x data sheet (big endian; b1, b2, a1, a2, b0) + */ + +static int sta32x_coefficient_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int numcoef = kcontrol->private_value >> 16; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = 3 * numcoef; + return 0; +} + +static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; + int i, ret = 0; + + mutex_lock(&sta32x->coeff_lock); + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); + cfud &= 0xf0; + /* + * chip documentation does not say if the bits are self clearing, + * so do it explicitly + */ + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + + regmap_write(sta32x->regmap, STA32X_CFADDR2, index); + if (numcoef == 1) { + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x04); + } else if (numcoef == 5) { + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x08); + } else { + ret = -EINVAL; + goto exit_unlock; + } + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta32x->regmap, STA32X_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +exit_unlock: + mutex_unlock(&sta32x->coeff_lock); + + return ret; +} + +static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud; + int i; + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); + cfud &= 0xf0; + /* + * chip documentation does not say if the bits are self clearing, + * so do it explicitly + */ + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + + regmap_write(sta32x->regmap, STA32X_CFADDR2, index); + for (i = 0; i < numcoef && (index + i < STA32X_COEF_COUNT); i++) + sta32x->coef_shadow[index + i] = + (ucontrol->value.bytes.data[3 * i] << 16) + | (ucontrol->value.bytes.data[3 * i + 1] << 8) + | (ucontrol->value.bytes.data[3 * i + 2]); + for (i = 0; i < 3 * numcoef; i++) + regmap_write(sta32x->regmap, STA32X_B1CF1 + i, + ucontrol->value.bytes.data[i]); + if (numcoef == 1) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x01); + else if (numcoef == 5) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x02); + else + return -EINVAL; + + return 0; +} + +static int sta32x_sync_coef_shadow(struct snd_soc_component *component) +{ + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + unsigned int cfud; + int i; + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); + cfud &= 0xf0; + + for (i = 0; i < STA32X_COEF_COUNT; i++) { + regmap_write(sta32x->regmap, STA32X_CFADDR2, i); + regmap_write(sta32x->regmap, STA32X_B1CF1, + (sta32x->coef_shadow[i] >> 16) & 0xff); + regmap_write(sta32x->regmap, STA32X_B1CF2, + (sta32x->coef_shadow[i] >> 8) & 0xff); + regmap_write(sta32x->regmap, STA32X_B1CF3, + (sta32x->coef_shadow[i]) & 0xff); + /* + * chip documentation does not say if the bits are + * self-clearing, so do it explicitly + */ + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x01); + } + return 0; +} + +static int sta32x_cache_sync(struct snd_soc_component *component) +{ + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + unsigned int mute; + int rc; + + /* mute during register sync */ + regmap_read(sta32x->regmap, STA32X_MMUTE, &mute); + regmap_write(sta32x->regmap, STA32X_MMUTE, mute | STA32X_MMUTE_MMUTE); + sta32x_sync_coef_shadow(component); + rc = regcache_sync(sta32x->regmap); + regmap_write(sta32x->regmap, STA32X_MMUTE, mute); + return rc; +} + +/* work around ESD issue where sta32x resets and loses all configuration */ +static void sta32x_watchdog(struct work_struct *work) +{ + struct sta32x_priv *sta32x = container_of(work, struct sta32x_priv, + watchdog_work.work); + struct snd_soc_component *component = sta32x->component; + unsigned int confa, confa_cached; + + /* check if sta32x has reset itself */ + confa_cached = snd_soc_component_read32(component, STA32X_CONFA); + regcache_cache_bypass(sta32x->regmap, true); + confa = snd_soc_component_read32(component, STA32X_CONFA); + regcache_cache_bypass(sta32x->regmap, false); + if (confa != confa_cached) { + regcache_mark_dirty(sta32x->regmap); + sta32x_cache_sync(component); + } + + if (!sta32x->shutdown) + queue_delayed_work(system_power_efficient_wq, + &sta32x->watchdog_work, + round_jiffies_relative(HZ)); +} + +static void sta32x_watchdog_start(struct sta32x_priv *sta32x) +{ + if (sta32x->pdata->needs_esd_watchdog) { + sta32x->shutdown = 0; + queue_delayed_work(system_power_efficient_wq, + &sta32x->watchdog_work, + round_jiffies_relative(HZ)); + } +} + +static void sta32x_watchdog_stop(struct sta32x_priv *sta32x) +{ + if (sta32x->pdata->needs_esd_watchdog) { + sta32x->shutdown = 1; + cancel_delayed_work_sync(&sta32x->watchdog_work); + } +} + +#define SINGLE_COEF(xname, index) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sta32x_coefficient_info, \ + .get = sta32x_coefficient_get,\ + .put = sta32x_coefficient_put, \ + .private_value = index | (1 << 16) } + +#define BIQUAD_COEFS(xname, index) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sta32x_coefficient_info, \ + .get = sta32x_coefficient_get,\ + .put = sta32x_coefficient_put, \ + .private_value = index | (5 << 16) } + +static const struct snd_kcontrol_new sta32x_snd_controls[] = { +SOC_SINGLE_TLV("Master Volume", STA32X_MVOL, 0, 0xff, 1, mvol_tlv), +SOC_SINGLE("Master Switch", STA32X_MMUTE, 0, 1, 1), +SOC_SINGLE("Ch1 Switch", STA32X_MMUTE, 1, 1, 1), +SOC_SINGLE("Ch2 Switch", STA32X_MMUTE, 2, 1, 1), +SOC_SINGLE("Ch3 Switch", STA32X_MMUTE, 3, 1, 1), +SOC_SINGLE_TLV("Ch1 Volume", STA32X_C1VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE_TLV("Ch2 Volume", STA32X_C2VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE_TLV("Ch3 Volume", STA32X_C3VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE("De-emphasis Filter Switch", STA32X_CONFD, STA32X_CONFD_DEMP_SHIFT, 1, 0), +SOC_ENUM("Compressor/Limiter Switch", sta32x_drc_ac_enum), +SOC_SINGLE("Miami Mode Switch", STA32X_CONFD, STA32X_CONFD_MME_SHIFT, 1, 0), +SOC_SINGLE("Zero Cross Switch", STA32X_CONFE, STA32X_CONFE_ZCE_SHIFT, 1, 0), +SOC_SINGLE("Soft Ramp Switch", STA32X_CONFE, STA32X_CONFE_SVE_SHIFT, 1, 0), +SOC_SINGLE("Auto-Mute Switch", STA32X_CONFF, STA32X_CONFF_IDE_SHIFT, 1, 0), +SOC_ENUM("Automode EQ", sta32x_auto_eq_enum), +SOC_ENUM("Automode GC", sta32x_auto_gc_enum), +SOC_ENUM("Automode XO", sta32x_auto_xo_enum), +SOC_ENUM("Preset EQ", sta32x_preset_eq_enum), +SOC_SINGLE("Ch1 Tone Control Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Tone Control Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0), +SOC_SINGLE("Ch1 EQ Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 EQ Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), +SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_ENUM("Ch1 Limiter Select", sta32x_limiter_ch1_enum), +SOC_ENUM("Ch2 Limiter Select", sta32x_limiter_ch2_enum), +SOC_ENUM("Ch3 Limiter Select", sta32x_limiter_ch3_enum), +SOC_SINGLE_TLV("Bass Tone Control", STA32X_TONE, STA32X_TONE_BTC_SHIFT, 15, 0, tone_tlv), +SOC_SINGLE_TLV("Treble Tone Control", STA32X_TONE, STA32X_TONE_TTC_SHIFT, 15, 0, tone_tlv), +SOC_ENUM("Limiter1 Attack Rate (dB/ms)", sta32x_limiter1_attack_rate_enum), +SOC_ENUM("Limiter2 Attack Rate (dB/ms)", sta32x_limiter2_attack_rate_enum), +SOC_ENUM("Limiter1 Release Rate (dB/ms)", sta32x_limiter1_release_rate_enum), +SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta32x_limiter2_release_rate_enum), + +/* depending on mode, the attack/release thresholds have + * two different enum definitions; provide both + */ +SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_ac_attack_tlv), +SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_ac_attack_tlv), +SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_ac_release_tlv), +SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_ac_release_tlv), +SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_drc_attack_tlv), +SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_drc_attack_tlv), +SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_drc_release_tlv), +SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_drc_release_tlv), + +BIQUAD_COEFS("Ch1 - Biquad 1", 0), +BIQUAD_COEFS("Ch1 - Biquad 2", 5), +BIQUAD_COEFS("Ch1 - Biquad 3", 10), +BIQUAD_COEFS("Ch1 - Biquad 4", 15), +BIQUAD_COEFS("Ch2 - Biquad 1", 20), +BIQUAD_COEFS("Ch2 - Biquad 2", 25), +BIQUAD_COEFS("Ch2 - Biquad 3", 30), +BIQUAD_COEFS("Ch2 - Biquad 4", 35), +BIQUAD_COEFS("High-pass", 40), +BIQUAD_COEFS("Low-pass", 45), +SINGLE_COEF("Ch1 - Prescale", 50), +SINGLE_COEF("Ch2 - Prescale", 51), +SINGLE_COEF("Ch1 - Postscale", 52), +SINGLE_COEF("Ch2 - Postscale", 53), +SINGLE_COEF("Ch3 - Postscale", 54), +SINGLE_COEF("Thermal warning - Postscale", 55), +SINGLE_COEF("Ch1 - Mix 1", 56), +SINGLE_COEF("Ch1 - Mix 2", 57), +SINGLE_COEF("Ch2 - Mix 1", 58), +SINGLE_COEF("Ch2 - Mix 2", 59), +SINGLE_COEF("Ch3 - Mix 1", 60), +SINGLE_COEF("Ch3 - Mix 2", 61), +}; + +static const struct snd_soc_dapm_widget sta32x_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("LEFT"), +SND_SOC_DAPM_OUTPUT("RIGHT"), +SND_SOC_DAPM_OUTPUT("SUB"), +}; + +static const struct snd_soc_dapm_route sta32x_dapm_routes[] = { + { "LEFT", NULL, "DAC" }, + { "RIGHT", NULL, "DAC" }, + { "SUB", NULL, "DAC" }, +}; + +/* MCLK interpolation ratio per fs */ +static struct { + int fs; + int ir; +} interpolation_ratios[] = { + { 32000, 0 }, + { 44100, 0 }, + { 48000, 0 }, + { 88200, 1 }, + { 96000, 1 }, + { 176400, 2 }, + { 192000, 2 }, +}; + +/* MCLK to fs clock ratios */ +static int mcs_ratio_table[3][7] = { + { 768, 512, 384, 256, 128, 576, 0 }, + { 384, 256, 192, 128, 64, 0 }, + { 384, 256, 192, 128, 64, 0 }, +}; + +/** + * sta32x_set_dai_sysclk - configure MCLK + * @codec_dai: the codec DAI + * @clk_id: the clock ID (ignored) + * @freq: the MCLK input frequency + * @dir: the clock direction (ignored) + * + * The value of MCLK is used to determine which sample rates are supported + * by the STA32X, based on the mclk_ratios table. + * + * This function must be called by the machine driver's 'startup' function, + * otherwise the list of supported sample rates will not be available in + * time for ALSA. + * + * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause + * theoretically possible sample rates to be enabled. Call it again with a + * proper value set one the external clock is set (most probably you would do + * that from a machine's driver 'hw_param' hook. + */ +static int sta32x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "mclk=%u\n", freq); + sta32x->mclk = freq; + + return 0; +} + +/** + * sta32x_set_dai_fmt - configure the codec for the selected audio format + * @codec_dai: the codec DAI + * @fmt: a SND_SOC_DAIFMT_x value indicating the data format + * + * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the + * codec accordingly. + */ +static int sta32x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + u8 confb = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + sta32x->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + confb |= STA32X_CONFB_C2IM; + break; + case SND_SOC_DAIFMT_NB_IF: + confb |= STA32X_CONFB_C1IM; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(sta32x->regmap, STA32X_CONFB, + STA32X_CONFB_C1IM | STA32X_CONFB_C2IM, confb); +} + +/** + * sta32x_hw_params - program the STA32X with the given hardware parameters. + * @substream: the audio stream + * @params: the hardware parameters to set + * @dai: the SOC DAI (ignored) + * + * This function programs the hardware with the values provided. + * Specifically, the sample rate and the data format. + */ +static int sta32x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + int i, mcs = -EINVAL, ir = -EINVAL; + unsigned int confa, confb; + unsigned int rate, ratio; + int ret; + + if (!sta32x->mclk) { + dev_err(component->dev, + "sta32x->mclk is unset. Unable to determine ratio\n"); + return -EIO; + } + + rate = params_rate(params); + ratio = sta32x->mclk / rate; + dev_dbg(component->dev, "rate: %u, ratio: %u\n", rate, ratio); + + for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++) { + if (interpolation_ratios[i].fs == rate) { + ir = interpolation_ratios[i].ir; + break; + } + } + + if (ir < 0) { + dev_err(component->dev, "Unsupported samplerate: %u\n", rate); + return -EINVAL; + } + + for (i = 0; i < 6; i++) { + if (mcs_ratio_table[ir][i] == ratio) { + mcs = i; + break; + } + } + + if (mcs < 0) { + dev_err(component->dev, "Unresolvable ratio: %u\n", ratio); + return -EINVAL; + } + + confa = (ir << STA32X_CONFA_IR_SHIFT) | + (mcs << STA32X_CONFA_MCS_SHIFT); + confb = 0; + + switch (params_width(params)) { + case 24: + dev_dbg(component->dev, "24bit\n"); + /* fall through */ + case 32: + dev_dbg(component->dev, "24bit or 32bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0x2; + break; + } + + break; + case 20: + dev_dbg(component->dev, "20bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x4; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x5; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0x6; + break; + } + + break; + case 18: + dev_dbg(component->dev, "18bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x8; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x9; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0xa; + break; + } + + break; + case 16: + dev_dbg(component->dev, "16bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0xd; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0xe; + break; + } + + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(sta32x->regmap, STA32X_CONFA, + STA32X_CONFA_MCS_MASK | STA32X_CONFA_IR_MASK, + confa); + if (ret < 0) + return ret; + + ret = regmap_update_bits(sta32x->regmap, STA32X_CONFB, + STA32X_CONFB_SAI_MASK | STA32X_CONFB_SAIFB, + confb); + if (ret < 0) + return ret; + + return 0; +} + +static int sta32x_startup_sequence(struct sta32x_priv *sta32x) +{ + if (sta32x->gpiod_nreset) { + gpiod_set_value(sta32x->gpiod_nreset, 0); + mdelay(1); + gpiod_set_value(sta32x->gpiod_nreset, 1); + mdelay(1); + } + + return 0; +} + +/** + * sta32x_set_bias_level - DAPM callback + * @component: the component device + * @level: DAPM power level + * + * This is called by ALSA to put the component into low power mode + * or to wake it up. If the component is powered off completely + * all registers must be restored after power on. + */ +static int sta32x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int ret; + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "level = %d\n", level); + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* Full power on */ + regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", ret); + return ret; + } + + sta32x_startup_sequence(sta32x); + sta32x_cache_sync(component); + sta32x_watchdog_start(sta32x); + } + + /* Power down */ + regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + 0); + + break; + + case SND_SOC_BIAS_OFF: + /* The chip runs through the power down sequence for us. */ + regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, 0); + msleep(300); + sta32x_watchdog_stop(sta32x); + + gpiod_set_value(sta32x->gpiod_nreset, 0); + + regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + break; + } + return 0; +} + +static const struct snd_soc_dai_ops sta32x_dai_ops = { + .hw_params = sta32x_hw_params, + .set_sysclk = sta32x_set_dai_sysclk, + .set_fmt = sta32x_set_dai_fmt, +}; + +static struct snd_soc_dai_driver sta32x_dai = { + .name = "sta32x-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STA32X_RATES, + .formats = STA32X_FORMATS, + }, + .ops = &sta32x_dai_ops, +}; + +static int sta32x_probe(struct snd_soc_component *component) +{ + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + struct sta32x_platform_data *pdata = sta32x->pdata; + int i, ret = 0, thermal = 0; + + sta32x->component = component; + + ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (ret != 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = sta32x_startup_sequence(sta32x); + if (ret < 0) { + dev_err(component->dev, "Failed to startup device\n"); + return ret; + } + + /* CONFA */ + if (!pdata->thermal_warning_recovery) + thermal |= STA32X_CONFA_TWAB; + if (!pdata->thermal_warning_adjustment) + thermal |= STA32X_CONFA_TWRB; + if (!pdata->fault_detect_recovery) + thermal |= STA32X_CONFA_FDRB; + regmap_update_bits(sta32x->regmap, STA32X_CONFA, + STA32X_CONFA_TWAB | STA32X_CONFA_TWRB | + STA32X_CONFA_FDRB, + thermal); + + /* CONFC */ + regmap_update_bits(sta32x->regmap, STA32X_CONFC, + STA32X_CONFC_CSZ_MASK, + pdata->drop_compensation_ns + << STA32X_CONFC_CSZ_SHIFT); + + /* CONFE */ + regmap_update_bits(sta32x->regmap, STA32X_CONFE, + STA32X_CONFE_MPCV, + pdata->max_power_use_mpcc ? + STA32X_CONFE_MPCV : 0); + regmap_update_bits(sta32x->regmap, STA32X_CONFE, + STA32X_CONFE_MPC, + pdata->max_power_correction ? + STA32X_CONFE_MPC : 0); + regmap_update_bits(sta32x->regmap, STA32X_CONFE, + STA32X_CONFE_AME, + pdata->am_reduction_mode ? + STA32X_CONFE_AME : 0); + regmap_update_bits(sta32x->regmap, STA32X_CONFE, + STA32X_CONFE_PWMS, + pdata->odd_pwm_speed_mode ? + STA32X_CONFE_PWMS : 0); + + /* CONFF */ + regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_IDE, + pdata->invalid_input_detect_mute ? + STA32X_CONFF_IDE : 0); + + /* select output configuration */ + regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_OCFG_MASK, + pdata->output_conf + << STA32X_CONFF_OCFG_SHIFT); + + /* channel to output mapping */ + regmap_update_bits(sta32x->regmap, STA32X_C1CFG, + STA32X_CxCFG_OM_MASK, + pdata->ch1_output_mapping + << STA32X_CxCFG_OM_SHIFT); + regmap_update_bits(sta32x->regmap, STA32X_C2CFG, + STA32X_CxCFG_OM_MASK, + pdata->ch2_output_mapping + << STA32X_CxCFG_OM_SHIFT); + regmap_update_bits(sta32x->regmap, STA32X_C3CFG, + STA32X_CxCFG_OM_MASK, + pdata->ch3_output_mapping + << STA32X_CxCFG_OM_SHIFT); + + /* initialize coefficient shadow RAM with reset values */ + for (i = 4; i <= 49; i += 5) + sta32x->coef_shadow[i] = 0x400000; + for (i = 50; i <= 54; i++) + sta32x->coef_shadow[i] = 0x7fffff; + sta32x->coef_shadow[55] = 0x5a9df7; + sta32x->coef_shadow[56] = 0x7fffff; + sta32x->coef_shadow[59] = 0x7fffff; + sta32x->coef_shadow[60] = 0x400000; + sta32x->coef_shadow[61] = 0x400000; + + if (sta32x->pdata->needs_esd_watchdog) + INIT_DELAYED_WORK(&sta32x->watchdog_work, sta32x_watchdog); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + /* Bias level configuration will have done an extra enable */ + regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); + + return 0; +} + +static void sta32x_remove(struct snd_soc_component *component) +{ + struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); + + sta32x_watchdog_stop(sta32x); + regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); +} + +static const struct snd_soc_component_driver sta32x_component = { + .probe = sta32x_probe, + .remove = sta32x_remove, + .set_bias_level = sta32x_set_bias_level, + .controls = sta32x_snd_controls, + .num_controls = ARRAY_SIZE(sta32x_snd_controls), + .dapm_widgets = sta32x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sta32x_dapm_widgets), + .dapm_routes = sta32x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sta32x_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config sta32x_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = STA32X_FDRC2, + .reg_defaults = sta32x_regs, + .num_reg_defaults = ARRAY_SIZE(sta32x_regs), + .cache_type = REGCACHE_RBTREE, + .wr_table = &sta32x_write_regs, + .rd_table = &sta32x_read_regs, + .volatile_table = &sta32x_volatile_regs, +}; + +#ifdef CONFIG_OF +static const struct of_device_id st32x_dt_ids[] = { + { .compatible = "st,sta32x", }, + { } +}; +MODULE_DEVICE_TABLE(of, st32x_dt_ids); + +static int sta32x_probe_dt(struct device *dev, struct sta32x_priv *sta32x) +{ + struct device_node *np = dev->of_node; + struct sta32x_platform_data *pdata; + u16 tmp; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + of_property_read_u8(np, "st,output-conf", + &pdata->output_conf); + of_property_read_u8(np, "st,ch1-output-mapping", + &pdata->ch1_output_mapping); + of_property_read_u8(np, "st,ch2-output-mapping", + &pdata->ch2_output_mapping); + of_property_read_u8(np, "st,ch3-output-mapping", + &pdata->ch3_output_mapping); + + if (of_get_property(np, "st,thermal-warning-recovery", NULL)) + pdata->thermal_warning_recovery = 1; + if (of_get_property(np, "st,thermal-warning-adjustment", NULL)) + pdata->thermal_warning_adjustment = 1; + if (of_get_property(np, "st,needs_esd_watchdog", NULL)) + pdata->needs_esd_watchdog = 1; + + tmp = 140; + of_property_read_u16(np, "st,drop-compensation-ns", &tmp); + pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20; + + /* CONFE */ + if (of_get_property(np, "st,max-power-use-mpcc", NULL)) + pdata->max_power_use_mpcc = 1; + + if (of_get_property(np, "st,max-power-correction", NULL)) + pdata->max_power_correction = 1; + + if (of_get_property(np, "st,am-reduction-mode", NULL)) + pdata->am_reduction_mode = 1; + + if (of_get_property(np, "st,odd-pwm-speed-mode", NULL)) + pdata->odd_pwm_speed_mode = 1; + + /* CONFF */ + if (of_get_property(np, "st,invalid-input-detect-mute", NULL)) + pdata->invalid_input_detect_mute = 1; + + sta32x->pdata = pdata; + + return 0; +} +#endif + +static int sta32x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct sta32x_priv *sta32x; + int ret, i; + + sta32x = devm_kzalloc(&i2c->dev, sizeof(struct sta32x_priv), + GFP_KERNEL); + if (!sta32x) + return -ENOMEM; + + mutex_init(&sta32x->coeff_lock); + sta32x->pdata = dev_get_platdata(dev); + +#ifdef CONFIG_OF + if (dev->of_node) { + ret = sta32x_probe_dt(dev, sta32x); + if (ret < 0) + return ret; + } +#endif + + /* GPIOs */ + sta32x->gpiod_nreset = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(sta32x->gpiod_nreset)) + return PTR_ERR(sta32x->gpiod_nreset); + + /* regulators */ + for (i = 0; i < ARRAY_SIZE(sta32x->supplies); i++) + sta32x->supplies[i].supply = sta32x_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + sta32x->regmap = devm_regmap_init_i2c(i2c, &sta32x_regmap); + if (IS_ERR(sta32x->regmap)) { + ret = PTR_ERR(sta32x->regmap); + dev_err(dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, sta32x); + + ret = devm_snd_soc_register_component(dev, &sta32x_component, + &sta32x_dai, 1); + if (ret < 0) + dev_err(dev, "Failed to register component (%d)\n", ret); + + return ret; +} + +static const struct i2c_device_id sta32x_i2c_id[] = { + { "sta326", 0 }, + { "sta328", 0 }, + { "sta329", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sta32x_i2c_id); + +static struct i2c_driver sta32x_i2c_driver = { + .driver = { + .name = "sta32x", + .of_match_table = of_match_ptr(st32x_dt_ids), + }, + .probe = sta32x_i2c_probe, + .id_table = sta32x_i2c_id, +}; + +module_i2c_driver(sta32x_i2c_driver); + +MODULE_DESCRIPTION("ASoC STA32X driver"); +MODULE_AUTHOR("Johannes Stezenbach "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sta32x.h b/sound/soc/codecs/sta32x.h new file mode 100644 index 000000000..d3191c983 --- /dev/null +++ b/sound/soc/codecs/sta32x.h @@ -0,0 +1,211 @@ +/* + * Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system + * + * Copyright: 2011 Raumfeld GmbH + * Author: Johannes Stezenbach + * + * based on code from: + * Wolfson Microelectronics PLC. + * Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#ifndef _ASOC_STA_32X_H +#define _ASOC_STA_32X_H + +/* STA326 register addresses */ + +#define STA32X_REGISTER_COUNT 0x2d +#define STA32X_COEF_COUNT 62 + +#define STA32X_CONFA 0x00 +#define STA32X_CONFB 0x01 +#define STA32X_CONFC 0x02 +#define STA32X_CONFD 0x03 +#define STA32X_CONFE 0x04 +#define STA32X_CONFF 0x05 +#define STA32X_MMUTE 0x06 +#define STA32X_MVOL 0x07 +#define STA32X_C1VOL 0x08 +#define STA32X_C2VOL 0x09 +#define STA32X_C3VOL 0x0a +#define STA32X_AUTO1 0x0b +#define STA32X_AUTO2 0x0c +#define STA32X_AUTO3 0x0d +#define STA32X_C1CFG 0x0e +#define STA32X_C2CFG 0x0f +#define STA32X_C3CFG 0x10 +#define STA32X_TONE 0x11 +#define STA32X_L1AR 0x12 +#define STA32X_L1ATRT 0x13 +#define STA32X_L2AR 0x14 +#define STA32X_L2ATRT 0x15 +#define STA32X_CFADDR2 0x16 +#define STA32X_B1CF1 0x17 +#define STA32X_B1CF2 0x18 +#define STA32X_B1CF3 0x19 +#define STA32X_B2CF1 0x1a +#define STA32X_B2CF2 0x1b +#define STA32X_B2CF3 0x1c +#define STA32X_A1CF1 0x1d +#define STA32X_A1CF2 0x1e +#define STA32X_A1CF3 0x1f +#define STA32X_A2CF1 0x20 +#define STA32X_A2CF2 0x21 +#define STA32X_A2CF3 0x22 +#define STA32X_B0CF1 0x23 +#define STA32X_B0CF2 0x24 +#define STA32X_B0CF3 0x25 +#define STA32X_CFUD 0x26 +#define STA32X_MPCC1 0x27 +#define STA32X_MPCC2 0x28 +/* Reserved 0x29 */ +/* Reserved 0x2a */ +#define STA32X_Reserved 0x2a +#define STA32X_FDRC1 0x2b +#define STA32X_FDRC2 0x2c +/* Reserved 0x2d */ + + +/* STA326 register field definitions */ + +/* 0x00 CONFA */ +#define STA32X_CONFA_MCS_MASK 0x03 +#define STA32X_CONFA_MCS_SHIFT 0 +#define STA32X_CONFA_IR_MASK 0x18 +#define STA32X_CONFA_IR_SHIFT 3 +#define STA32X_CONFA_TWRB 0x20 +#define STA32X_CONFA_TWAB 0x40 +#define STA32X_CONFA_FDRB 0x80 + +/* 0x01 CONFB */ +#define STA32X_CONFB_SAI_MASK 0x0f +#define STA32X_CONFB_SAI_SHIFT 0 +#define STA32X_CONFB_SAIFB 0x10 +#define STA32X_CONFB_DSCKE 0x20 +#define STA32X_CONFB_C1IM 0x40 +#define STA32X_CONFB_C2IM 0x80 + +/* 0x02 CONFC */ +#define STA32X_CONFC_OM_MASK 0x03 +#define STA32X_CONFC_OM_SHIFT 0 +#define STA32X_CONFC_CSZ_MASK 0x7c +#define STA32X_CONFC_CSZ_SHIFT 2 + +/* 0x03 CONFD */ +#define STA32X_CONFD_HPB 0x01 +#define STA32X_CONFD_HPB_SHIFT 0 +#define STA32X_CONFD_DEMP 0x02 +#define STA32X_CONFD_DEMP_SHIFT 1 +#define STA32X_CONFD_DSPB 0x04 +#define STA32X_CONFD_DSPB_SHIFT 2 +#define STA32X_CONFD_PSL 0x08 +#define STA32X_CONFD_PSL_SHIFT 3 +#define STA32X_CONFD_BQL 0x10 +#define STA32X_CONFD_BQL_SHIFT 4 +#define STA32X_CONFD_DRC 0x20 +#define STA32X_CONFD_DRC_SHIFT 5 +#define STA32X_CONFD_ZDE 0x40 +#define STA32X_CONFD_ZDE_SHIFT 6 +#define STA32X_CONFD_MME 0x80 +#define STA32X_CONFD_MME_SHIFT 7 + +/* 0x04 CONFE */ +#define STA32X_CONFE_MPCV 0x01 +#define STA32X_CONFE_MPCV_SHIFT 0 +#define STA32X_CONFE_MPC 0x02 +#define STA32X_CONFE_MPC_SHIFT 1 +#define STA32X_CONFE_AME 0x08 +#define STA32X_CONFE_AME_SHIFT 3 +#define STA32X_CONFE_PWMS 0x10 +#define STA32X_CONFE_PWMS_SHIFT 4 +#define STA32X_CONFE_ZCE 0x40 +#define STA32X_CONFE_ZCE_SHIFT 6 +#define STA32X_CONFE_SVE 0x80 +#define STA32X_CONFE_SVE_SHIFT 7 + +/* 0x05 CONFF */ +#define STA32X_CONFF_OCFG_MASK 0x03 +#define STA32X_CONFF_OCFG_SHIFT 0 +#define STA32X_CONFF_IDE 0x04 +#define STA32X_CONFF_IDE_SHIFT 2 +#define STA32X_CONFF_BCLE 0x08 +#define STA32X_CONFF_ECLE 0x20 +#define STA32X_CONFF_PWDN 0x40 +#define STA32X_CONFF_EAPD 0x80 + +/* 0x06 MMUTE */ +#define STA32X_MMUTE_MMUTE 0x01 + +/* 0x0b AUTO1 */ +#define STA32X_AUTO1_AMEQ_MASK 0x03 +#define STA32X_AUTO1_AMEQ_SHIFT 0 +#define STA32X_AUTO1_AMV_MASK 0xc0 +#define STA32X_AUTO1_AMV_SHIFT 2 +#define STA32X_AUTO1_AMGC_MASK 0x30 +#define STA32X_AUTO1_AMGC_SHIFT 4 +#define STA32X_AUTO1_AMPS 0x80 + +/* 0x0c AUTO2 */ +#define STA32X_AUTO2_AMAME 0x01 +#define STA32X_AUTO2_AMAM_MASK 0x0e +#define STA32X_AUTO2_AMAM_SHIFT 1 +#define STA32X_AUTO2_XO_MASK 0xf0 +#define STA32X_AUTO2_XO_SHIFT 4 + +/* 0x0d AUTO3 */ +#define STA32X_AUTO3_PEQ_MASK 0x1f +#define STA32X_AUTO3_PEQ_SHIFT 0 + +/* 0x0e 0x0f 0x10 CxCFG */ +#define STA32X_CxCFG_TCB 0x01 /* only C1 and C2 */ +#define STA32X_CxCFG_TCB_SHIFT 0 +#define STA32X_CxCFG_EQBP 0x02 /* only C1 and C2 */ +#define STA32X_CxCFG_EQBP_SHIFT 1 +#define STA32X_CxCFG_VBP 0x03 +#define STA32X_CxCFG_VBP_SHIFT 2 +#define STA32X_CxCFG_BO 0x04 +#define STA32X_CxCFG_LS_MASK 0x30 +#define STA32X_CxCFG_LS_SHIFT 4 +#define STA32X_CxCFG_OM_MASK 0xc0 +#define STA32X_CxCFG_OM_SHIFT 6 + +/* 0x11 TONE */ +#define STA32X_TONE_BTC_SHIFT 0 +#define STA32X_TONE_TTC_SHIFT 4 + +/* 0x12 0x13 0x14 0x15 limiter attack/release */ +#define STA32X_LxA_SHIFT 0 +#define STA32X_LxR_SHIFT 4 + +/* 0x26 CFUD */ +#define STA32X_CFUD_W1 0x01 +#define STA32X_CFUD_WA 0x02 +#define STA32X_CFUD_R1 0x04 +#define STA32X_CFUD_RA 0x08 + + +/* biquad filter coefficient table offsets */ +#define STA32X_C1_BQ_BASE 0 +#define STA32X_C2_BQ_BASE 20 +#define STA32X_CH_BQ_NUM 4 +#define STA32X_BQ_NUM_COEF 5 +#define STA32X_XO_HP_BQ_BASE 40 +#define STA32X_XO_LP_BQ_BASE 45 +#define STA32X_C1_PRESCALE 50 +#define STA32X_C2_PRESCALE 51 +#define STA32X_C1_POSTSCALE 52 +#define STA32X_C2_POSTSCALE 53 +#define STA32X_C3_POSTSCALE 54 +#define STA32X_TW_POSTSCALE 55 +#define STA32X_C1_MIX1 56 +#define STA32X_C1_MIX2 57 +#define STA32X_C2_MIX1 58 +#define STA32X_C2_MIX2 59 +#define STA32X_C3_MIX1 60 +#define STA32X_C3_MIX2 61 + +#endif /* _ASOC_STA_32X_H */ diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c new file mode 100644 index 000000000..0b870319b --- /dev/null +++ b/sound/soc/codecs/sta350.c @@ -0,0 +1,1279 @@ +/* + * Codec driver for ST STA350 2.1-channel high-efficiency digital audio system + * + * Copyright: 2014 Raumfeld GmbH + * Author: Sven Brandau + * + * based on code from: + * Raumfeld GmbH + * Johannes Stezenbach + * Wolfson Microelectronics PLC. + * Mark Brown + * Freescale Semiconductor, Inc. + * Timur Tabi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "sta350.h" + +#define STA350_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 STA350_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE) + +/* Power-up register defaults */ +static const struct reg_default sta350_regs[] = { + { 0x0, 0x63 }, + { 0x1, 0x80 }, + { 0x2, 0xdf }, + { 0x3, 0x40 }, + { 0x4, 0xc2 }, + { 0x5, 0x5c }, + { 0x6, 0x00 }, + { 0x7, 0xff }, + { 0x8, 0x60 }, + { 0x9, 0x60 }, + { 0xa, 0x60 }, + { 0xb, 0x00 }, + { 0xc, 0x00 }, + { 0xd, 0x00 }, + { 0xe, 0x00 }, + { 0xf, 0x40 }, + { 0x10, 0x80 }, + { 0x11, 0x77 }, + { 0x12, 0x6a }, + { 0x13, 0x69 }, + { 0x14, 0x6a }, + { 0x15, 0x69 }, + { 0x16, 0x00 }, + { 0x17, 0x00 }, + { 0x18, 0x00 }, + { 0x19, 0x00 }, + { 0x1a, 0x00 }, + { 0x1b, 0x00 }, + { 0x1c, 0x00 }, + { 0x1d, 0x00 }, + { 0x1e, 0x00 }, + { 0x1f, 0x00 }, + { 0x20, 0x00 }, + { 0x21, 0x00 }, + { 0x22, 0x00 }, + { 0x23, 0x00 }, + { 0x24, 0x00 }, + { 0x25, 0x00 }, + { 0x26, 0x00 }, + { 0x27, 0x2a }, + { 0x28, 0xc0 }, + { 0x29, 0xf3 }, + { 0x2a, 0x33 }, + { 0x2b, 0x00 }, + { 0x2c, 0x0c }, + { 0x31, 0x00 }, + { 0x36, 0x00 }, + { 0x37, 0x00 }, + { 0x38, 0x00 }, + { 0x39, 0x01 }, + { 0x3a, 0xee }, + { 0x3b, 0xff }, + { 0x3c, 0x7e }, + { 0x3d, 0xc0 }, + { 0x3e, 0x26 }, + { 0x3f, 0x00 }, + { 0x48, 0x00 }, + { 0x49, 0x00 }, + { 0x4a, 0x00 }, + { 0x4b, 0x04 }, + { 0x4c, 0x00 }, +}; + +static const struct regmap_range sta350_write_regs_range[] = { + regmap_reg_range(STA350_CONFA, STA350_AUTO2), + regmap_reg_range(STA350_C1CFG, STA350_FDRC2), + regmap_reg_range(STA350_EQCFG, STA350_EVOLRES), + regmap_reg_range(STA350_NSHAPE, STA350_MISC2), +}; + +static const struct regmap_range sta350_read_regs_range[] = { + regmap_reg_range(STA350_CONFA, STA350_AUTO2), + regmap_reg_range(STA350_C1CFG, STA350_STATUS), + regmap_reg_range(STA350_EQCFG, STA350_EVOLRES), + regmap_reg_range(STA350_NSHAPE, STA350_MISC2), +}; + +static const struct regmap_range sta350_volatile_regs_range[] = { + regmap_reg_range(STA350_CFADDR2, STA350_CFUD), + regmap_reg_range(STA350_STATUS, STA350_STATUS), +}; + +static const struct regmap_access_table sta350_write_regs = { + .yes_ranges = sta350_write_regs_range, + .n_yes_ranges = ARRAY_SIZE(sta350_write_regs_range), +}; + +static const struct regmap_access_table sta350_read_regs = { + .yes_ranges = sta350_read_regs_range, + .n_yes_ranges = ARRAY_SIZE(sta350_read_regs_range), +}; + +static const struct regmap_access_table sta350_volatile_regs = { + .yes_ranges = sta350_volatile_regs_range, + .n_yes_ranges = ARRAY_SIZE(sta350_volatile_regs_range), +}; + +/* regulator power supply names */ +static const char * const sta350_supply_names[] = { + "vdd-dig", /* digital supply, 3.3V */ + "vdd-pll", /* pll supply, 3.3V */ + "vcc" /* power amp supply, 5V - 26V */ +}; + +/* codec private data */ +struct sta350_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[ARRAY_SIZE(sta350_supply_names)]; + struct sta350_platform_data *pdata; + + unsigned int mclk; + unsigned int format; + + u32 coef_shadow[STA350_COEF_COUNT]; + int shutdown; + + struct gpio_desc *gpiod_nreset; + struct gpio_desc *gpiod_power_down; + + struct mutex coeff_lock; +}; + +static const DECLARE_TLV_DB_SCALE(mvol_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(chvol_tlv, -7950, 50, 1); +static const DECLARE_TLV_DB_SCALE(tone_tlv, -1200, 200, 0); + +static const char * const sta350_drc_ac[] = { + "Anti-Clipping", "Dynamic Range Compression" +}; +static const char * const sta350_auto_gc_mode[] = { + "User", "AC no clipping", "AC limited clipping (10%)", + "DRC nighttime listening mode" +}; +static const char * const sta350_auto_xo_mode[] = { + "User", "80Hz", "100Hz", "120Hz", "140Hz", "160Hz", "180Hz", + "200Hz", "220Hz", "240Hz", "260Hz", "280Hz", "300Hz", "320Hz", + "340Hz", "360Hz" +}; +static const char * const sta350_binary_output[] = { + "FFX 3-state output - normal operation", "Binary output" +}; +static const char * const sta350_limiter_select[] = { + "Limiter Disabled", "Limiter #1", "Limiter #2" +}; +static const char * const sta350_limiter_attack_rate[] = { + "3.1584", "2.7072", "2.2560", "1.8048", "1.3536", "0.9024", + "0.4512", "0.2256", "0.1504", "0.1123", "0.0902", "0.0752", + "0.0645", "0.0564", "0.0501", "0.0451" +}; +static const char * const sta350_limiter_release_rate[] = { + "0.5116", "0.1370", "0.0744", "0.0499", "0.0360", "0.0299", + "0.0264", "0.0208", "0.0198", "0.0172", "0.0147", "0.0137", + "0.0134", "0.0117", "0.0110", "0.0104" +}; +static const char * const sta350_noise_shaper_type[] = { + "Third order", "Fourth order" +}; + +static DECLARE_TLV_DB_RANGE(sta350_limiter_ac_attack_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-1200, 200, 0), + 8, 16, TLV_DB_SCALE_ITEM(300, 100, 0), +); + +static DECLARE_TLV_DB_RANGE(sta350_limiter_ac_release_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(-2900, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(-2000, 0, 0), + 3, 8, TLV_DB_SCALE_ITEM(-1400, 200, 0), + 8, 16, TLV_DB_SCALE_ITEM(-700, 100, 0), +); + +static DECLARE_TLV_DB_RANGE(sta350_limiter_drc_attack_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-3100, 200, 0), + 8, 13, TLV_DB_SCALE_ITEM(-1600, 100, 0), + 14, 16, TLV_DB_SCALE_ITEM(-1000, 300, 0), +); + +static DECLARE_TLV_DB_RANGE(sta350_limiter_drc_release_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 1, 2, TLV_DB_SCALE_ITEM(-3800, 200, 0), + 3, 4, TLV_DB_SCALE_ITEM(-3300, 200, 0), + 5, 12, TLV_DB_SCALE_ITEM(-3000, 200, 0), + 13, 16, TLV_DB_SCALE_ITEM(-1500, 300, 0), +); + +static SOC_ENUM_SINGLE_DECL(sta350_drc_ac_enum, + STA350_CONFD, STA350_CONFD_DRC_SHIFT, + sta350_drc_ac); +static SOC_ENUM_SINGLE_DECL(sta350_noise_shaper_enum, + STA350_CONFE, STA350_CONFE_NSBW_SHIFT, + sta350_noise_shaper_type); +static SOC_ENUM_SINGLE_DECL(sta350_auto_gc_enum, + STA350_AUTO1, STA350_AUTO1_AMGC_SHIFT, + sta350_auto_gc_mode); +static SOC_ENUM_SINGLE_DECL(sta350_auto_xo_enum, + STA350_AUTO2, STA350_AUTO2_XO_SHIFT, + sta350_auto_xo_mode); +static SOC_ENUM_SINGLE_DECL(sta350_binary_output_ch1_enum, + STA350_C1CFG, STA350_CxCFG_BO_SHIFT, + sta350_binary_output); +static SOC_ENUM_SINGLE_DECL(sta350_binary_output_ch2_enum, + STA350_C2CFG, STA350_CxCFG_BO_SHIFT, + sta350_binary_output); +static SOC_ENUM_SINGLE_DECL(sta350_binary_output_ch3_enum, + STA350_C3CFG, STA350_CxCFG_BO_SHIFT, + sta350_binary_output); +static SOC_ENUM_SINGLE_DECL(sta350_limiter_ch1_enum, + STA350_C1CFG, STA350_CxCFG_LS_SHIFT, + sta350_limiter_select); +static SOC_ENUM_SINGLE_DECL(sta350_limiter_ch2_enum, + STA350_C2CFG, STA350_CxCFG_LS_SHIFT, + sta350_limiter_select); +static SOC_ENUM_SINGLE_DECL(sta350_limiter_ch3_enum, + STA350_C3CFG, STA350_CxCFG_LS_SHIFT, + sta350_limiter_select); +static SOC_ENUM_SINGLE_DECL(sta350_limiter1_attack_rate_enum, + STA350_L1AR, STA350_LxA_SHIFT, + sta350_limiter_attack_rate); +static SOC_ENUM_SINGLE_DECL(sta350_limiter2_attack_rate_enum, + STA350_L2AR, STA350_LxA_SHIFT, + sta350_limiter_attack_rate); +static SOC_ENUM_SINGLE_DECL(sta350_limiter1_release_rate_enum, + STA350_L1AR, STA350_LxR_SHIFT, + sta350_limiter_release_rate); +static SOC_ENUM_SINGLE_DECL(sta350_limiter2_release_rate_enum, + STA350_L2AR, STA350_LxR_SHIFT, + sta350_limiter_release_rate); + +/* + * byte array controls for setting biquad, mixer, scaling coefficients; + * for biquads all five coefficients need to be set in one go, + * mixer and pre/postscale coefs can be set individually; + * each coef is 24bit, the bytes are ordered in the same way + * as given in the STA350 data sheet (big endian; b1, b2, a1, a2, b0) + */ + +static int sta350_coefficient_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int numcoef = kcontrol->private_value >> 16; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = 3 * numcoef; + return 0; +} + +static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; + int i, ret = 0; + + mutex_lock(&sta350->coeff_lock); + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); + cfud &= 0xf0; + /* + * chip documentation does not say if the bits are self clearing, + * so do it explicitly + */ + regmap_write(sta350->regmap, STA350_CFUD, cfud); + + regmap_write(sta350->regmap, STA350_CFADDR2, index); + if (numcoef == 1) { + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x04); + } else if (numcoef == 5) { + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x08); + } else { + ret = -EINVAL; + goto exit_unlock; + } + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta350->regmap, STA350_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +exit_unlock: + mutex_unlock(&sta350->coeff_lock); + + return ret; +} + +static int sta350_coefficient_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud; + int i; + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); + cfud &= 0xf0; + /* + * chip documentation does not say if the bits are self clearing, + * so do it explicitly + */ + regmap_write(sta350->regmap, STA350_CFUD, cfud); + + regmap_write(sta350->regmap, STA350_CFADDR2, index); + for (i = 0; i < numcoef && (index + i < STA350_COEF_COUNT); i++) + sta350->coef_shadow[index + i] = + (ucontrol->value.bytes.data[3 * i] << 16) + | (ucontrol->value.bytes.data[3 * i + 1] << 8) + | (ucontrol->value.bytes.data[3 * i + 2]); + for (i = 0; i < 3 * numcoef; i++) + regmap_write(sta350->regmap, STA350_B1CF1 + i, + ucontrol->value.bytes.data[i]); + if (numcoef == 1) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x01); + else if (numcoef == 5) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x02); + else + return -EINVAL; + + return 0; +} + +static int sta350_sync_coef_shadow(struct snd_soc_component *component) +{ + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + unsigned int cfud; + int i; + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); + cfud &= 0xf0; + + for (i = 0; i < STA350_COEF_COUNT; i++) { + regmap_write(sta350->regmap, STA350_CFADDR2, i); + regmap_write(sta350->regmap, STA350_B1CF1, + (sta350->coef_shadow[i] >> 16) & 0xff); + regmap_write(sta350->regmap, STA350_B1CF2, + (sta350->coef_shadow[i] >> 8) & 0xff); + regmap_write(sta350->regmap, STA350_B1CF3, + (sta350->coef_shadow[i]) & 0xff); + /* + * chip documentation does not say if the bits are + * self-clearing, so do it explicitly + */ + regmap_write(sta350->regmap, STA350_CFUD, cfud); + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x01); + } + return 0; +} + +static int sta350_cache_sync(struct snd_soc_component *component) +{ + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + unsigned int mute; + int rc; + + /* mute during register sync */ + regmap_read(sta350->regmap, STA350_CFUD, &mute); + regmap_write(sta350->regmap, STA350_MMUTE, mute | STA350_MMUTE_MMUTE); + sta350_sync_coef_shadow(component); + rc = regcache_sync(sta350->regmap); + regmap_write(sta350->regmap, STA350_MMUTE, mute); + return rc; +} + +#define SINGLE_COEF(xname, index) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sta350_coefficient_info, \ + .get = sta350_coefficient_get,\ + .put = sta350_coefficient_put, \ + .private_value = index | (1 << 16) } + +#define BIQUAD_COEFS(xname, index) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sta350_coefficient_info, \ + .get = sta350_coefficient_get,\ + .put = sta350_coefficient_put, \ + .private_value = index | (5 << 16) } + +static const struct snd_kcontrol_new sta350_snd_controls[] = { +SOC_SINGLE_TLV("Master Volume", STA350_MVOL, 0, 0xff, 1, mvol_tlv), +/* VOL */ +SOC_SINGLE_TLV("Ch1 Volume", STA350_C1VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE_TLV("Ch2 Volume", STA350_C2VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE_TLV("Ch3 Volume", STA350_C3VOL, 0, 0xff, 1, chvol_tlv), +/* CONFD */ +SOC_SINGLE("High Pass Filter Bypass Switch", + STA350_CONFD, STA350_CONFD_HPB_SHIFT, 1, 1), +SOC_SINGLE("De-emphasis Filter Switch", + STA350_CONFD, STA350_CONFD_DEMP_SHIFT, 1, 0), +SOC_SINGLE("DSP Bypass Switch", + STA350_CONFD, STA350_CONFD_DSPB_SHIFT, 1, 0), +SOC_SINGLE("Post-scale Link Switch", + STA350_CONFD, STA350_CONFD_PSL_SHIFT, 1, 0), +SOC_SINGLE("Biquad Coefficient Link Switch", + STA350_CONFD, STA350_CONFD_BQL_SHIFT, 1, 0), +SOC_ENUM("Compressor/Limiter Switch", sta350_drc_ac_enum), +SOC_ENUM("Noise Shaper Bandwidth", sta350_noise_shaper_enum), +SOC_SINGLE("Zero-detect Mute Enable Switch", + STA350_CONFD, STA350_CONFD_ZDE_SHIFT, 1, 0), +SOC_SINGLE("Submix Mode Switch", + STA350_CONFD, STA350_CONFD_SME_SHIFT, 1, 0), +/* CONFE */ +SOC_SINGLE("Zero Cross Switch", STA350_CONFE, STA350_CONFE_ZCE_SHIFT, 1, 0), +SOC_SINGLE("Soft Ramp Switch", STA350_CONFE, STA350_CONFE_SVE_SHIFT, 1, 0), +/* MUTE */ +SOC_SINGLE("Master Switch", STA350_MMUTE, STA350_MMUTE_MMUTE_SHIFT, 1, 1), +SOC_SINGLE("Ch1 Switch", STA350_MMUTE, STA350_MMUTE_C1M_SHIFT, 1, 1), +SOC_SINGLE("Ch2 Switch", STA350_MMUTE, STA350_MMUTE_C2M_SHIFT, 1, 1), +SOC_SINGLE("Ch3 Switch", STA350_MMUTE, STA350_MMUTE_C3M_SHIFT, 1, 1), +/* AUTOx */ +SOC_ENUM("Automode GC", sta350_auto_gc_enum), +SOC_ENUM("Automode XO", sta350_auto_xo_enum), +/* CxCFG */ +SOC_SINGLE("Ch1 Tone Control Bypass Switch", + STA350_C1CFG, STA350_CxCFG_TCB_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Tone Control Bypass Switch", + STA350_C2CFG, STA350_CxCFG_TCB_SHIFT, 1, 0), +SOC_SINGLE("Ch1 EQ Bypass Switch", + STA350_C1CFG, STA350_CxCFG_EQBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 EQ Bypass Switch", + STA350_C2CFG, STA350_CxCFG_EQBP_SHIFT, 1, 0), +SOC_SINGLE("Ch1 Master Volume Bypass Switch", + STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Master Volume Bypass Switch", + STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch3 Master Volume Bypass Switch", + STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), +SOC_ENUM("Ch1 Binary Output Select", sta350_binary_output_ch1_enum), +SOC_ENUM("Ch2 Binary Output Select", sta350_binary_output_ch2_enum), +SOC_ENUM("Ch3 Binary Output Select", sta350_binary_output_ch3_enum), +SOC_ENUM("Ch1 Limiter Select", sta350_limiter_ch1_enum), +SOC_ENUM("Ch2 Limiter Select", sta350_limiter_ch2_enum), +SOC_ENUM("Ch3 Limiter Select", sta350_limiter_ch3_enum), +/* TONE */ +SOC_SINGLE_RANGE_TLV("Bass Tone Control Volume", + STA350_TONE, STA350_TONE_BTC_SHIFT, 1, 13, 0, tone_tlv), +SOC_SINGLE_RANGE_TLV("Treble Tone Control Volume", + STA350_TONE, STA350_TONE_TTC_SHIFT, 1, 13, 0, tone_tlv), +SOC_ENUM("Limiter1 Attack Rate (dB/ms)", sta350_limiter1_attack_rate_enum), +SOC_ENUM("Limiter2 Attack Rate (dB/ms)", sta350_limiter2_attack_rate_enum), +SOC_ENUM("Limiter1 Release Rate (dB/ms)", sta350_limiter1_release_rate_enum), +SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta350_limiter2_release_rate_enum), + +/* + * depending on mode, the attack/release thresholds have + * two different enum definitions; provide both + */ +SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", + STA350_L1ATRT, STA350_LxA_SHIFT, + 16, 0, sta350_limiter_ac_attack_tlv), +SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", + STA350_L2ATRT, STA350_LxA_SHIFT, + 16, 0, sta350_limiter_ac_attack_tlv), +SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", + STA350_L1ATRT, STA350_LxR_SHIFT, + 16, 0, sta350_limiter_ac_release_tlv), +SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", + STA350_L2ATRT, STA350_LxR_SHIFT, + 16, 0, sta350_limiter_ac_release_tlv), +SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", + STA350_L1ATRT, STA350_LxA_SHIFT, + 16, 0, sta350_limiter_drc_attack_tlv), +SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", + STA350_L2ATRT, STA350_LxA_SHIFT, + 16, 0, sta350_limiter_drc_attack_tlv), +SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", + STA350_L1ATRT, STA350_LxR_SHIFT, + 16, 0, sta350_limiter_drc_release_tlv), +SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", + STA350_L2ATRT, STA350_LxR_SHIFT, + 16, 0, sta350_limiter_drc_release_tlv), + +BIQUAD_COEFS("Ch1 - Biquad 1", 0), +BIQUAD_COEFS("Ch1 - Biquad 2", 5), +BIQUAD_COEFS("Ch1 - Biquad 3", 10), +BIQUAD_COEFS("Ch1 - Biquad 4", 15), +BIQUAD_COEFS("Ch2 - Biquad 1", 20), +BIQUAD_COEFS("Ch2 - Biquad 2", 25), +BIQUAD_COEFS("Ch2 - Biquad 3", 30), +BIQUAD_COEFS("Ch2 - Biquad 4", 35), +BIQUAD_COEFS("High-pass", 40), +BIQUAD_COEFS("Low-pass", 45), +SINGLE_COEF("Ch1 - Prescale", 50), +SINGLE_COEF("Ch2 - Prescale", 51), +SINGLE_COEF("Ch1 - Postscale", 52), +SINGLE_COEF("Ch2 - Postscale", 53), +SINGLE_COEF("Ch3 - Postscale", 54), +SINGLE_COEF("Thermal warning - Postscale", 55), +SINGLE_COEF("Ch1 - Mix 1", 56), +SINGLE_COEF("Ch1 - Mix 2", 57), +SINGLE_COEF("Ch2 - Mix 1", 58), +SINGLE_COEF("Ch2 - Mix 2", 59), +SINGLE_COEF("Ch3 - Mix 1", 60), +SINGLE_COEF("Ch3 - Mix 2", 61), +}; + +static const struct snd_soc_dapm_widget sta350_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("LEFT"), +SND_SOC_DAPM_OUTPUT("RIGHT"), +SND_SOC_DAPM_OUTPUT("SUB"), +}; + +static const struct snd_soc_dapm_route sta350_dapm_routes[] = { + { "LEFT", NULL, "DAC" }, + { "RIGHT", NULL, "DAC" }, + { "SUB", NULL, "DAC" }, + { "DAC", NULL, "Playback" }, +}; + +/* MCLK interpolation ratio per fs */ +static struct { + int fs; + int ir; +} interpolation_ratios[] = { + { 32000, 0 }, + { 44100, 0 }, + { 48000, 0 }, + { 88200, 1 }, + { 96000, 1 }, + { 176400, 2 }, + { 192000, 2 }, +}; + +/* MCLK to fs clock ratios */ +static int mcs_ratio_table[3][6] = { + { 768, 512, 384, 256, 128, 576 }, + { 384, 256, 192, 128, 64, 0 }, + { 192, 128, 96, 64, 32, 0 }, +}; + +/** + * sta350_set_dai_sysclk - configure MCLK + * @codec_dai: the codec DAI + * @clk_id: the clock ID (ignored) + * @freq: the MCLK input frequency + * @dir: the clock direction (ignored) + * + * The value of MCLK is used to determine which sample rates are supported + * by the STA350, based on the mcs_ratio_table. + * + * This function must be called by the machine driver's 'startup' function, + * otherwise the list of supported sample rates will not be available in + * time for ALSA. + */ +static int sta350_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "mclk=%u\n", freq); + sta350->mclk = freq; + + return 0; +} + +/** + * sta350_set_dai_fmt - configure the codec for the selected audio format + * @codec_dai: the codec DAI + * @fmt: a SND_SOC_DAIFMT_x value indicating the data format + * + * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the + * codec accordingly. + */ +static int sta350_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + unsigned int confb = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + sta350->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + confb |= STA350_CONFB_C2IM; + break; + case SND_SOC_DAIFMT_NB_IF: + confb |= STA350_CONFB_C1IM; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(sta350->regmap, STA350_CONFB, + STA350_CONFB_C1IM | STA350_CONFB_C2IM, confb); +} + +/** + * sta350_hw_params - program the STA350 with the given hardware parameters. + * @substream: the audio stream + * @params: the hardware parameters to set + * @dai: the SOC DAI (ignored) + * + * This function programs the hardware with the values provided. + * Specifically, the sample rate and the data format. + */ +static int sta350_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + int i, mcs = -EINVAL, ir = -EINVAL; + unsigned int confa, confb; + unsigned int rate, ratio; + int ret; + + if (!sta350->mclk) { + dev_err(component->dev, + "sta350->mclk is unset. Unable to determine ratio\n"); + return -EIO; + } + + rate = params_rate(params); + ratio = sta350->mclk / rate; + dev_dbg(component->dev, "rate: %u, ratio: %u\n", rate, ratio); + + for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++) { + if (interpolation_ratios[i].fs == rate) { + ir = interpolation_ratios[i].ir; + break; + } + } + + if (ir < 0) { + dev_err(component->dev, "Unsupported samplerate: %u\n", rate); + return -EINVAL; + } + + for (i = 0; i < 6; i++) { + if (mcs_ratio_table[ir][i] == ratio) { + mcs = i; + break; + } + } + + if (mcs < 0) { + dev_err(component->dev, "Unresolvable ratio: %u\n", ratio); + return -EINVAL; + } + + confa = (ir << STA350_CONFA_IR_SHIFT) | + (mcs << STA350_CONFA_MCS_SHIFT); + confb = 0; + + switch (params_width(params)) { + case 24: + dev_dbg(component->dev, "24bit\n"); + /* fall through */ + case 32: + dev_dbg(component->dev, "24bit or 32bit\n"); + switch (sta350->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0x2; + break; + } + + break; + case 20: + dev_dbg(component->dev, "20bit\n"); + switch (sta350->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x4; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x5; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0x6; + break; + } + + break; + case 18: + dev_dbg(component->dev, "18bit\n"); + switch (sta350->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x8; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x9; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0xa; + break; + } + + break; + case 16: + dev_dbg(component->dev, "16bit\n"); + switch (sta350->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0xd; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0xe; + break; + } + + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(sta350->regmap, STA350_CONFA, + STA350_CONFA_MCS_MASK | STA350_CONFA_IR_MASK, + confa); + if (ret < 0) + return ret; + + ret = regmap_update_bits(sta350->regmap, STA350_CONFB, + STA350_CONFB_SAI_MASK | STA350_CONFB_SAIFB, + confb); + if (ret < 0) + return ret; + + return 0; +} + +static int sta350_startup_sequence(struct sta350_priv *sta350) +{ + if (sta350->gpiod_power_down) + gpiod_set_value(sta350->gpiod_power_down, 1); + + if (sta350->gpiod_nreset) { + gpiod_set_value(sta350->gpiod_nreset, 0); + mdelay(1); + gpiod_set_value(sta350->gpiod_nreset, 1); + mdelay(1); + } + + return 0; +} + +/** + * sta350_set_bias_level - DAPM callback + * @component: the component device + * @level: DAPM power level + * + * This is called by ALSA to put the component into low power mode + * or to wake it up. If the component is powered off completely + * all registers must be restored after power on. + */ +static int sta350_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + int ret; + + dev_dbg(component->dev, "level = %d\n", level); + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* Full power on */ + regmap_update_bits(sta350->regmap, STA350_CONFF, + STA350_CONFF_PWDN | STA350_CONFF_EAPD, + STA350_CONFF_PWDN | STA350_CONFF_EAPD); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable( + ARRAY_SIZE(sta350->supplies), + sta350->supplies); + if (ret < 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + sta350_startup_sequence(sta350); + sta350_cache_sync(component); + } + + /* Power down */ + regmap_update_bits(sta350->regmap, STA350_CONFF, + STA350_CONFF_PWDN | STA350_CONFF_EAPD, + 0); + + break; + + case SND_SOC_BIAS_OFF: + /* The chip runs through the power down sequence for us */ + regmap_update_bits(sta350->regmap, STA350_CONFF, + STA350_CONFF_PWDN | STA350_CONFF_EAPD, 0); + + /* power down: low */ + if (sta350->gpiod_power_down) + gpiod_set_value(sta350->gpiod_power_down, 0); + + if (sta350->gpiod_nreset) + gpiod_set_value(sta350->gpiod_nreset, 0); + + regulator_bulk_disable(ARRAY_SIZE(sta350->supplies), + sta350->supplies); + break; + } + return 0; +} + +static const struct snd_soc_dai_ops sta350_dai_ops = { + .hw_params = sta350_hw_params, + .set_sysclk = sta350_set_dai_sysclk, + .set_fmt = sta350_set_dai_fmt, +}; + +static struct snd_soc_dai_driver sta350_dai = { + .name = "sta350-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STA350_RATES, + .formats = STA350_FORMATS, + }, + .ops = &sta350_dai_ops, +}; + +static int sta350_probe(struct snd_soc_component *component) +{ + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + struct sta350_platform_data *pdata = sta350->pdata; + int i, ret = 0, thermal = 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(sta350->supplies), + sta350->supplies); + if (ret < 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = sta350_startup_sequence(sta350); + if (ret < 0) { + dev_err(component->dev, "Failed to startup device\n"); + return ret; + } + + /* CONFA */ + if (!pdata->thermal_warning_recovery) + thermal |= STA350_CONFA_TWAB; + if (!pdata->thermal_warning_adjustment) + thermal |= STA350_CONFA_TWRB; + if (!pdata->fault_detect_recovery) + thermal |= STA350_CONFA_FDRB; + regmap_update_bits(sta350->regmap, STA350_CONFA, + STA350_CONFA_TWAB | STA350_CONFA_TWRB | + STA350_CONFA_FDRB, + thermal); + + /* CONFC */ + regmap_update_bits(sta350->regmap, STA350_CONFC, + STA350_CONFC_OM_MASK, + pdata->ffx_power_output_mode + << STA350_CONFC_OM_SHIFT); + regmap_update_bits(sta350->regmap, STA350_CONFC, + STA350_CONFC_CSZ_MASK, + pdata->drop_compensation_ns + << STA350_CONFC_CSZ_SHIFT); + regmap_update_bits(sta350->regmap, + STA350_CONFC, + STA350_CONFC_OCRB, + pdata->oc_warning_adjustment ? + STA350_CONFC_OCRB : 0); + + /* CONFE */ + regmap_update_bits(sta350->regmap, STA350_CONFE, + STA350_CONFE_MPCV, + pdata->max_power_use_mpcc ? + STA350_CONFE_MPCV : 0); + regmap_update_bits(sta350->regmap, STA350_CONFE, + STA350_CONFE_MPC, + pdata->max_power_correction ? + STA350_CONFE_MPC : 0); + regmap_update_bits(sta350->regmap, STA350_CONFE, + STA350_CONFE_AME, + pdata->am_reduction_mode ? + STA350_CONFE_AME : 0); + regmap_update_bits(sta350->regmap, STA350_CONFE, + STA350_CONFE_PWMS, + pdata->odd_pwm_speed_mode ? + STA350_CONFE_PWMS : 0); + regmap_update_bits(sta350->regmap, STA350_CONFE, + STA350_CONFE_DCCV, + pdata->distortion_compensation ? + STA350_CONFE_DCCV : 0); + /* CONFF */ + regmap_update_bits(sta350->regmap, STA350_CONFF, + STA350_CONFF_IDE, + pdata->invalid_input_detect_mute ? + STA350_CONFF_IDE : 0); + regmap_update_bits(sta350->regmap, STA350_CONFF, + STA350_CONFF_OCFG_MASK, + pdata->output_conf + << STA350_CONFF_OCFG_SHIFT); + + /* channel to output mapping */ + regmap_update_bits(sta350->regmap, STA350_C1CFG, + STA350_CxCFG_OM_MASK, + pdata->ch1_output_mapping + << STA350_CxCFG_OM_SHIFT); + regmap_update_bits(sta350->regmap, STA350_C2CFG, + STA350_CxCFG_OM_MASK, + pdata->ch2_output_mapping + << STA350_CxCFG_OM_SHIFT); + regmap_update_bits(sta350->regmap, STA350_C3CFG, + STA350_CxCFG_OM_MASK, + pdata->ch3_output_mapping + << STA350_CxCFG_OM_SHIFT); + + /* miscellaneous registers */ + regmap_update_bits(sta350->regmap, STA350_MISC1, + STA350_MISC1_CPWMEN, + pdata->activate_mute_output ? + STA350_MISC1_CPWMEN : 0); + regmap_update_bits(sta350->regmap, STA350_MISC1, + STA350_MISC1_BRIDGOFF, + pdata->bridge_immediate_off ? + STA350_MISC1_BRIDGOFF : 0); + regmap_update_bits(sta350->regmap, STA350_MISC1, + STA350_MISC1_NSHHPEN, + pdata->noise_shape_dc_cut ? + STA350_MISC1_NSHHPEN : 0); + regmap_update_bits(sta350->regmap, STA350_MISC1, + STA350_MISC1_RPDNEN, + pdata->powerdown_master_vol ? + STA350_MISC1_RPDNEN: 0); + + regmap_update_bits(sta350->regmap, STA350_MISC2, + STA350_MISC2_PNDLSL_MASK, + pdata->powerdown_delay_divider + << STA350_MISC2_PNDLSL_SHIFT); + + /* initialize coefficient shadow RAM with reset values */ + for (i = 4; i <= 49; i += 5) + sta350->coef_shadow[i] = 0x400000; + for (i = 50; i <= 54; i++) + sta350->coef_shadow[i] = 0x7fffff; + sta350->coef_shadow[55] = 0x5a9df7; + sta350->coef_shadow[56] = 0x7fffff; + sta350->coef_shadow[59] = 0x7fffff; + sta350->coef_shadow[60] = 0x400000; + sta350->coef_shadow[61] = 0x400000; + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + /* Bias level configuration will have done an extra enable */ + regulator_bulk_disable(ARRAY_SIZE(sta350->supplies), sta350->supplies); + + return 0; +} + +static void sta350_remove(struct snd_soc_component *component) +{ + struct sta350_priv *sta350 = snd_soc_component_get_drvdata(component); + + regulator_bulk_disable(ARRAY_SIZE(sta350->supplies), sta350->supplies); +} + +static const struct snd_soc_component_driver sta350_component = { + .probe = sta350_probe, + .remove = sta350_remove, + .set_bias_level = sta350_set_bias_level, + .controls = sta350_snd_controls, + .num_controls = ARRAY_SIZE(sta350_snd_controls), + .dapm_widgets = sta350_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sta350_dapm_widgets), + .dapm_routes = sta350_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sta350_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config sta350_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = STA350_MISC2, + .reg_defaults = sta350_regs, + .num_reg_defaults = ARRAY_SIZE(sta350_regs), + .cache_type = REGCACHE_RBTREE, + .wr_table = &sta350_write_regs, + .rd_table = &sta350_read_regs, + .volatile_table = &sta350_volatile_regs, +}; + +#ifdef CONFIG_OF +static const struct of_device_id st350_dt_ids[] = { + { .compatible = "st,sta350", }, + { } +}; +MODULE_DEVICE_TABLE(of, st350_dt_ids); + +static const char * const sta350_ffx_modes[] = { + [STA350_FFX_PM_DROP_COMP] = "drop-compensation", + [STA350_FFX_PM_TAPERED_COMP] = "tapered-compensation", + [STA350_FFX_PM_FULL_POWER] = "full-power-mode", + [STA350_FFX_PM_VARIABLE_DROP_COMP] = "variable-drop-compensation", +}; + +static int sta350_probe_dt(struct device *dev, struct sta350_priv *sta350) +{ + struct device_node *np = dev->of_node; + struct sta350_platform_data *pdata; + const char *ffx_power_mode; + u16 tmp; + u8 tmp8; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + of_property_read_u8(np, "st,output-conf", + &pdata->output_conf); + of_property_read_u8(np, "st,ch1-output-mapping", + &pdata->ch1_output_mapping); + of_property_read_u8(np, "st,ch2-output-mapping", + &pdata->ch2_output_mapping); + of_property_read_u8(np, "st,ch3-output-mapping", + &pdata->ch3_output_mapping); + + if (of_get_property(np, "st,thermal-warning-recovery", NULL)) + pdata->thermal_warning_recovery = 1; + if (of_get_property(np, "st,thermal-warning-adjustment", NULL)) + pdata->thermal_warning_adjustment = 1; + if (of_get_property(np, "st,fault-detect-recovery", NULL)) + pdata->fault_detect_recovery = 1; + + pdata->ffx_power_output_mode = STA350_FFX_PM_VARIABLE_DROP_COMP; + if (!of_property_read_string(np, "st,ffx-power-output-mode", + &ffx_power_mode)) { + int i, mode = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(sta350_ffx_modes); i++) + if (!strcasecmp(ffx_power_mode, sta350_ffx_modes[i])) + mode = i; + + if (mode < 0) + dev_warn(dev, "Unsupported ffx output mode: %s\n", + ffx_power_mode); + else + pdata->ffx_power_output_mode = mode; + } + + tmp = 140; + of_property_read_u16(np, "st,drop-compensation-ns", &tmp); + pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20; + + if (of_get_property(np, "st,overcurrent-warning-adjustment", NULL)) + pdata->oc_warning_adjustment = 1; + + /* CONFE */ + if (of_get_property(np, "st,max-power-use-mpcc", NULL)) + pdata->max_power_use_mpcc = 1; + + if (of_get_property(np, "st,max-power-correction", NULL)) + pdata->max_power_correction = 1; + + if (of_get_property(np, "st,am-reduction-mode", NULL)) + pdata->am_reduction_mode = 1; + + if (of_get_property(np, "st,odd-pwm-speed-mode", NULL)) + pdata->odd_pwm_speed_mode = 1; + + if (of_get_property(np, "st,distortion-compensation", NULL)) + pdata->distortion_compensation = 1; + + /* CONFF */ + if (of_get_property(np, "st,invalid-input-detect-mute", NULL)) + pdata->invalid_input_detect_mute = 1; + + /* MISC */ + if (of_get_property(np, "st,activate-mute-output", NULL)) + pdata->activate_mute_output = 1; + + if (of_get_property(np, "st,bridge-immediate-off", NULL)) + pdata->bridge_immediate_off = 1; + + if (of_get_property(np, "st,noise-shape-dc-cut", NULL)) + pdata->noise_shape_dc_cut = 1; + + if (of_get_property(np, "st,powerdown-master-volume", NULL)) + pdata->powerdown_master_vol = 1; + + if (!of_property_read_u8(np, "st,powerdown-delay-divider", &tmp8)) { + if (is_power_of_2(tmp8) && tmp8 >= 1 && tmp8 <= 128) + pdata->powerdown_delay_divider = ilog2(tmp8); + else + dev_warn(dev, "Unsupported powerdown delay divider %d\n", + tmp8); + } + + sta350->pdata = pdata; + + return 0; +} +#endif + +static int sta350_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct sta350_priv *sta350; + int ret, i; + + sta350 = devm_kzalloc(dev, sizeof(struct sta350_priv), GFP_KERNEL); + if (!sta350) + return -ENOMEM; + + mutex_init(&sta350->coeff_lock); + sta350->pdata = dev_get_platdata(dev); + +#ifdef CONFIG_OF + if (dev->of_node) { + ret = sta350_probe_dt(dev, sta350); + if (ret < 0) + return ret; + } +#endif + + /* GPIOs */ + sta350->gpiod_nreset = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(sta350->gpiod_nreset)) + return PTR_ERR(sta350->gpiod_nreset); + + sta350->gpiod_power_down = devm_gpiod_get_optional(dev, "power-down", + GPIOD_OUT_LOW); + if (IS_ERR(sta350->gpiod_power_down)) + return PTR_ERR(sta350->gpiod_power_down); + + /* regulators */ + for (i = 0; i < ARRAY_SIZE(sta350->supplies); i++) + sta350->supplies[i].supply = sta350_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(sta350->supplies), + sta350->supplies); + if (ret < 0) { + dev_err(dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + sta350->regmap = devm_regmap_init_i2c(i2c, &sta350_regmap); + if (IS_ERR(sta350->regmap)) { + ret = PTR_ERR(sta350->regmap); + dev_err(dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, sta350); + + ret = devm_snd_soc_register_component(dev, &sta350_component, &sta350_dai, 1); + if (ret < 0) + dev_err(dev, "Failed to register component (%d)\n", ret); + + return ret; +} + +static int sta350_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id sta350_i2c_id[] = { + { "sta350", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sta350_i2c_id); + +static struct i2c_driver sta350_i2c_driver = { + .driver = { + .name = "sta350", + .of_match_table = of_match_ptr(st350_dt_ids), + }, + .probe = sta350_i2c_probe, + .remove = sta350_i2c_remove, + .id_table = sta350_i2c_id, +}; + +module_i2c_driver(sta350_i2c_driver); + +MODULE_DESCRIPTION("ASoC STA350 driver"); +MODULE_AUTHOR("Sven Brandau "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sta350.h b/sound/soc/codecs/sta350.h new file mode 100644 index 000000000..fb7285290 --- /dev/null +++ b/sound/soc/codecs/sta350.h @@ -0,0 +1,238 @@ +/* + * Codec driver for ST STA350 2.1-channel high-efficiency digital audio system + * + * Copyright: 2011 Raumfeld GmbH + * Author: Sven Brandau + * + * based on code from: + * Raumfeld GmbH + * Johannes Stezenbach + * Wolfson Microelectronics PLC. + * Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#ifndef _ASOC_STA_350_H +#define _ASOC_STA_350_H + +/* STA50 register addresses */ + +#define STA350_REGISTER_COUNT 0x4D +#define STA350_COEF_COUNT 62 + +#define STA350_CONFA 0x00 +#define STA350_CONFB 0x01 +#define STA350_CONFC 0x02 +#define STA350_CONFD 0x03 +#define STA350_CONFE 0x04 +#define STA350_CONFF 0x05 +#define STA350_MMUTE 0x06 +#define STA350_MVOL 0x07 +#define STA350_C1VOL 0x08 +#define STA350_C2VOL 0x09 +#define STA350_C3VOL 0x0a +#define STA350_AUTO1 0x0b +#define STA350_AUTO2 0x0c +#define STA350_AUTO3 0x0d +#define STA350_C1CFG 0x0e +#define STA350_C2CFG 0x0f +#define STA350_C3CFG 0x10 +#define STA350_TONE 0x11 +#define STA350_L1AR 0x12 +#define STA350_L1ATRT 0x13 +#define STA350_L2AR 0x14 +#define STA350_L2ATRT 0x15 +#define STA350_CFADDR2 0x16 +#define STA350_B1CF1 0x17 +#define STA350_B1CF2 0x18 +#define STA350_B1CF3 0x19 +#define STA350_B2CF1 0x1a +#define STA350_B2CF2 0x1b +#define STA350_B2CF3 0x1c +#define STA350_A1CF1 0x1d +#define STA350_A1CF2 0x1e +#define STA350_A1CF3 0x1f +#define STA350_A2CF1 0x20 +#define STA350_A2CF2 0x21 +#define STA350_A2CF3 0x22 +#define STA350_B0CF1 0x23 +#define STA350_B0CF2 0x24 +#define STA350_B0CF3 0x25 +#define STA350_CFUD 0x26 +#define STA350_MPCC1 0x27 +#define STA350_MPCC2 0x28 +#define STA350_DCC1 0x29 +#define STA350_DCC2 0x2a +#define STA350_FDRC1 0x2b +#define STA350_FDRC2 0x2c +#define STA350_STATUS 0x2d +/* reserved: 0x2d - 0x30 */ +#define STA350_EQCFG 0x31 +#define STA350_EATH1 0x32 +#define STA350_ERTH1 0x33 +#define STA350_EATH2 0x34 +#define STA350_ERTH2 0x35 +#define STA350_CONFX 0x36 +#define STA350_SVCA 0x37 +#define STA350_SVCB 0x38 +#define STA350_RMS0A 0x39 +#define STA350_RMS0B 0x3a +#define STA350_RMS0C 0x3b +#define STA350_RMS1A 0x3c +#define STA350_RMS1B 0x3d +#define STA350_RMS1C 0x3e +#define STA350_EVOLRES 0x3f +/* reserved: 0x40 - 0x47 */ +#define STA350_NSHAPE 0x48 +#define STA350_CTXB4B1 0x49 +#define STA350_CTXB7B5 0x4a +#define STA350_MISC1 0x4b +#define STA350_MISC2 0x4c + +/* 0x00 CONFA */ +#define STA350_CONFA_MCS_MASK 0x03 +#define STA350_CONFA_MCS_SHIFT 0 +#define STA350_CONFA_IR_MASK 0x18 +#define STA350_CONFA_IR_SHIFT 3 +#define STA350_CONFA_TWRB BIT(5) +#define STA350_CONFA_TWAB BIT(6) +#define STA350_CONFA_FDRB BIT(7) + +/* 0x01 CONFB */ +#define STA350_CONFB_SAI_MASK 0x0f +#define STA350_CONFB_SAI_SHIFT 0 +#define STA350_CONFB_SAIFB BIT(4) +#define STA350_CONFB_DSCKE BIT(5) +#define STA350_CONFB_C1IM BIT(6) +#define STA350_CONFB_C2IM BIT(7) + +/* 0x02 CONFC */ +#define STA350_CONFC_OM_MASK 0x03 +#define STA350_CONFC_OM_SHIFT 0 +#define STA350_CONFC_CSZ_MASK 0x3c +#define STA350_CONFC_CSZ_SHIFT 2 +#define STA350_CONFC_OCRB BIT(7) + +/* 0x03 CONFD */ +#define STA350_CONFD_HPB_SHIFT 0 +#define STA350_CONFD_DEMP_SHIFT 1 +#define STA350_CONFD_DSPB_SHIFT 2 +#define STA350_CONFD_PSL_SHIFT 3 +#define STA350_CONFD_BQL_SHIFT 4 +#define STA350_CONFD_DRC_SHIFT 5 +#define STA350_CONFD_ZDE_SHIFT 6 +#define STA350_CONFD_SME_SHIFT 7 + +/* 0x04 CONFE */ +#define STA350_CONFE_MPCV BIT(0) +#define STA350_CONFE_MPCV_SHIFT 0 +#define STA350_CONFE_MPC BIT(1) +#define STA350_CONFE_MPC_SHIFT 1 +#define STA350_CONFE_NSBW BIT(2) +#define STA350_CONFE_NSBW_SHIFT 2 +#define STA350_CONFE_AME BIT(3) +#define STA350_CONFE_AME_SHIFT 3 +#define STA350_CONFE_PWMS BIT(4) +#define STA350_CONFE_PWMS_SHIFT 4 +#define STA350_CONFE_DCCV BIT(5) +#define STA350_CONFE_DCCV_SHIFT 5 +#define STA350_CONFE_ZCE BIT(6) +#define STA350_CONFE_ZCE_SHIFT 6 +#define STA350_CONFE_SVE BIT(7) +#define STA350_CONFE_SVE_SHIFT 7 + +/* 0x05 CONFF */ +#define STA350_CONFF_OCFG_MASK 0x03 +#define STA350_CONFF_OCFG_SHIFT 0 +#define STA350_CONFF_IDE BIT(2) +#define STA350_CONFF_BCLE BIT(3) +#define STA350_CONFF_LDTE BIT(4) +#define STA350_CONFF_ECLE BIT(5) +#define STA350_CONFF_PWDN BIT(6) +#define STA350_CONFF_EAPD BIT(7) + +/* 0x06 MMUTE */ +#define STA350_MMUTE_MMUTE 0x01 +#define STA350_MMUTE_MMUTE_SHIFT 0 +#define STA350_MMUTE_C1M 0x02 +#define STA350_MMUTE_C1M_SHIFT 1 +#define STA350_MMUTE_C2M 0x04 +#define STA350_MMUTE_C2M_SHIFT 2 +#define STA350_MMUTE_C3M 0x08 +#define STA350_MMUTE_C3M_SHIFT 3 +#define STA350_MMUTE_LOC_MASK 0xC0 +#define STA350_MMUTE_LOC_SHIFT 6 + +/* 0x0b AUTO1 */ +#define STA350_AUTO1_AMGC_MASK 0x30 +#define STA350_AUTO1_AMGC_SHIFT 4 + +/* 0x0c AUTO2 */ +#define STA350_AUTO2_AMAME 0x01 +#define STA350_AUTO2_AMAM_MASK 0x0e +#define STA350_AUTO2_AMAM_SHIFT 1 +#define STA350_AUTO2_XO_MASK 0xf0 +#define STA350_AUTO2_XO_SHIFT 4 + +/* 0x0d AUTO3 */ +#define STA350_AUTO3_PEQ_MASK 0x1f +#define STA350_AUTO3_PEQ_SHIFT 0 + +/* 0x0e 0x0f 0x10 CxCFG */ +#define STA350_CxCFG_TCB_SHIFT 0 +#define STA350_CxCFG_EQBP_SHIFT 1 +#define STA350_CxCFG_VBP_SHIFT 2 +#define STA350_CxCFG_BO_SHIFT 3 +#define STA350_CxCFG_LS_SHIFT 4 +#define STA350_CxCFG_OM_MASK 0xc0 +#define STA350_CxCFG_OM_SHIFT 6 + +/* 0x11 TONE */ +#define STA350_TONE_BTC_SHIFT 0 +#define STA350_TONE_TTC_SHIFT 4 + +/* 0x12 0x13 0x14 0x15 limiter attack/release */ +#define STA350_LxA_SHIFT 0 +#define STA350_LxR_SHIFT 4 + +/* 0x26 CFUD */ +#define STA350_CFUD_W1 0x01 +#define STA350_CFUD_WA 0x02 +#define STA350_CFUD_R1 0x04 +#define STA350_CFUD_RA 0x08 + + +/* biquad filter coefficient table offsets */ +#define STA350_C1_BQ_BASE 0 +#define STA350_C2_BQ_BASE 20 +#define STA350_CH_BQ_NUM 4 +#define STA350_BQ_NUM_COEF 5 +#define STA350_XO_HP_BQ_BASE 40 +#define STA350_XO_LP_BQ_BASE 45 +#define STA350_C1_PRESCALE 50 +#define STA350_C2_PRESCALE 51 +#define STA350_C1_POSTSCALE 52 +#define STA350_C2_POSTSCALE 53 +#define STA350_C3_POSTSCALE 54 +#define STA350_TW_POSTSCALE 55 +#define STA350_C1_MIX1 56 +#define STA350_C1_MIX2 57 +#define STA350_C2_MIX1 58 +#define STA350_C2_MIX2 59 +#define STA350_C3_MIX1 60 +#define STA350_C3_MIX2 61 + +/* miscellaneous register 1 */ +#define STA350_MISC1_CPWMEN BIT(2) +#define STA350_MISC1_BRIDGOFF BIT(5) +#define STA350_MISC1_NSHHPEN BIT(6) +#define STA350_MISC1_RPDNEN BIT(7) + +/* miscellaneous register 2 */ +#define STA350_MISC2_PNDLSL_MASK 0x1c +#define STA350_MISC2_PNDLSL_SHIFT 2 + +#endif /* _ASOC_STA_350_H */ diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c new file mode 100644 index 000000000..2881a0f7b --- /dev/null +++ b/sound/soc/codecs/sta529.c @@ -0,0 +1,391 @@ +/* + * ASoC codec driver for spear platform + * + * sound/soc/codecs/sta529.c -- spear ALSA Soc codec driver + * + * Copyright (C) 2012 ST Microelectronics + * Rajeev Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* STA529 Register offsets */ +#define STA529_FFXCFG0 0x00 +#define STA529_FFXCFG1 0x01 +#define STA529_MVOL 0x02 +#define STA529_LVOL 0x03 +#define STA529_RVOL 0x04 +#define STA529_TTF0 0x05 +#define STA529_TTF1 0x06 +#define STA529_TTP0 0x07 +#define STA529_TTP1 0x08 +#define STA529_S2PCFG0 0x0A +#define STA529_S2PCFG1 0x0B +#define STA529_P2SCFG0 0x0C +#define STA529_P2SCFG1 0x0D +#define STA529_PLLCFG0 0x14 +#define STA529_PLLCFG1 0x15 +#define STA529_PLLCFG2 0x16 +#define STA529_PLLCFG3 0x17 +#define STA529_PLLPFE 0x18 +#define STA529_PLLST 0x19 +#define STA529_ADCCFG 0x1E /*mic_select*/ +#define STA529_CKOCFG 0x1F +#define STA529_MISC 0x20 +#define STA529_PADST0 0x21 +#define STA529_PADST1 0x22 +#define STA529_FFXST 0x23 +#define STA529_PWMIN1 0x2D +#define STA529_PWMIN2 0x2E +#define STA529_POWST 0x32 + +#define STA529_MAX_REGISTER 0x32 + +#define STA529_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define STA529_FORMAT (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) +#define S2PC_VALUE 0x98 +#define CLOCK_OUT 0x60 +#define DATA_FORMAT_MSK 0x0E +#define LEFT_J_DATA_FORMAT 0x00 +#define I2S_DATA_FORMAT 0x02 +#define RIGHT_J_DATA_FORMAT 0x04 +#define CODEC_MUTE_VAL 0x80 + +#define POWER_CNTLMSAK 0x40 +#define POWER_STDBY 0x40 +#define FFX_MASK 0x80 +#define FFX_OFF 0x80 +#define POWER_UP 0x00 +#define FFX_CLK_ENB 0x01 +#define FFX_CLK_DIS 0x00 +#define FFX_CLK_MSK 0x01 +#define PLAY_FREQ_RANGE_MSK 0x70 +#define CAP_FREQ_RANGE_MSK 0x0C +#define PDATA_LEN_MSK 0xC0 +#define BCLK_TO_FS_MSK 0x30 +#define AUDIO_MUTE_MSK 0x80 + +static const struct reg_default sta529_reg_defaults[] = { + { 0, 0x35 }, /* R0 - FFX Configuration reg 0 */ + { 1, 0xc8 }, /* R1 - FFX Configuration reg 1 */ + { 2, 0x50 }, /* R2 - Master Volume */ + { 3, 0x00 }, /* R3 - Left Volume */ + { 4, 0x00 }, /* R4 - Right Volume */ + { 10, 0xb2 }, /* R10 - S2P Config Reg 0 */ + { 11, 0x41 }, /* R11 - S2P Config Reg 1 */ + { 12, 0x92 }, /* R12 - P2S Config Reg 0 */ + { 13, 0x41 }, /* R13 - P2S Config Reg 1 */ + { 30, 0xd2 }, /* R30 - ADC Config Reg */ + { 31, 0x40 }, /* R31 - clock Out Reg */ + { 32, 0x21 }, /* R32 - Misc Register */ +}; + +struct sta529 { + struct regmap *regmap; +}; + +static bool sta529_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + + case STA529_FFXCFG0: + case STA529_FFXCFG1: + case STA529_MVOL: + case STA529_LVOL: + case STA529_RVOL: + case STA529_S2PCFG0: + case STA529_S2PCFG1: + case STA529_P2SCFG0: + case STA529_P2SCFG1: + case STA529_ADCCFG: + case STA529_CKOCFG: + case STA529_MISC: + return true; + default: + return false; + } +} + + +static const char *pwm_mode_text[] = { "Binary", "Headphone", "Ternary", + "Phase-shift"}; + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -9150, 50, 0); +static const DECLARE_TLV_DB_SCALE(master_vol_tlv, -12750, 50, 0); +static SOC_ENUM_SINGLE_DECL(pwm_src, STA529_FFXCFG1, 4, pwm_mode_text); + +static const struct snd_kcontrol_new sta529_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", STA529_LVOL, STA529_RVOL, 0, + 127, 0, out_gain_tlv), + SOC_SINGLE_TLV("Master Playback Volume", STA529_MVOL, 0, 127, 1, + master_vol_tlv), + SOC_ENUM("PWM Select", pwm_src), +}; + +static int sta529_set_bias_level(struct snd_soc_component *component, enum + snd_soc_bias_level level) +{ + struct sta529 *sta529 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, STA529_FFXCFG0, POWER_CNTLMSAK, + POWER_UP); + snd_soc_component_update_bits(component, STA529_MISC, FFX_CLK_MSK, + FFX_CLK_ENB); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + regcache_sync(sta529->regmap); + snd_soc_component_update_bits(component, STA529_FFXCFG0, + POWER_CNTLMSAK, POWER_STDBY); + /* Making FFX output to zero */ + snd_soc_component_update_bits(component, STA529_FFXCFG0, FFX_MASK, + FFX_OFF); + snd_soc_component_update_bits(component, STA529_MISC, FFX_CLK_MSK, + FFX_CLK_DIS); + break; + case SND_SOC_BIAS_OFF: + break; + } + + return 0; + +} + +static int sta529_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int pdata, play_freq_val, record_freq_val; + int bclk_to_fs_ratio; + + switch (params_width(params)) { + case 16: + pdata = 1; + bclk_to_fs_ratio = 0; + break; + case 24: + pdata = 2; + bclk_to_fs_ratio = 1; + break; + case 32: + pdata = 3; + bclk_to_fs_ratio = 2; + break; + default: + dev_err(component->dev, "Unsupported format\n"); + return -EINVAL; + } + + switch (params_rate(params)) { + case 8000: + case 11025: + play_freq_val = 0; + record_freq_val = 2; + break; + case 16000: + case 22050: + play_freq_val = 1; + record_freq_val = 0; + break; + + case 32000: + case 44100: + case 48000: + play_freq_val = 2; + record_freq_val = 0; + break; + default: + dev_err(component->dev, "Unsupported rate\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + snd_soc_component_update_bits(component, STA529_S2PCFG1, PDATA_LEN_MSK, + pdata << 6); + snd_soc_component_update_bits(component, STA529_S2PCFG1, BCLK_TO_FS_MSK, + bclk_to_fs_ratio << 4); + snd_soc_component_update_bits(component, STA529_MISC, PLAY_FREQ_RANGE_MSK, + play_freq_val << 4); + } else { + snd_soc_component_update_bits(component, STA529_P2SCFG1, PDATA_LEN_MSK, + pdata << 6); + snd_soc_component_update_bits(component, STA529_P2SCFG1, BCLK_TO_FS_MSK, + bclk_to_fs_ratio << 4); + snd_soc_component_update_bits(component, STA529_MISC, CAP_FREQ_RANGE_MSK, + record_freq_val << 2); + } + + return 0; +} + +static int sta529_mute(struct snd_soc_dai *dai, int mute) +{ + u8 val = 0; + + if (mute) + val |= CODEC_MUTE_VAL; + + snd_soc_component_update_bits(dai->component, STA529_FFXCFG0, AUDIO_MUTE_MSK, val); + + return 0; +} + +static int sta529_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 mode = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + mode = LEFT_J_DATA_FORMAT; + break; + case SND_SOC_DAIFMT_I2S: + mode = I2S_DATA_FORMAT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + mode = RIGHT_J_DATA_FORMAT; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, STA529_S2PCFG0, DATA_FORMAT_MSK, mode); + + return 0; +} + +static const struct snd_soc_dai_ops sta529_dai_ops = { + .hw_params = sta529_hw_params, + .set_fmt = sta529_set_dai_fmt, + .digital_mute = sta529_mute, +}; + +static struct snd_soc_dai_driver sta529_dai = { + .name = "sta529-audio", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STA529_RATES, + .formats = STA529_FORMAT, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = STA529_RATES, + .formats = STA529_FORMAT, + }, + .ops = &sta529_dai_ops, +}; + +static const struct snd_soc_component_driver sta529_component_driver = { + .set_bias_level = sta529_set_bias_level, + .controls = sta529_snd_controls, + .num_controls = ARRAY_SIZE(sta529_snd_controls), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config sta529_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = STA529_MAX_REGISTER, + .readable_reg = sta529_readable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = sta529_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(sta529_reg_defaults), +}; + +static int sta529_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct sta529 *sta529; + int ret; + + sta529 = devm_kzalloc(&i2c->dev, sizeof(struct sta529), GFP_KERNEL); + if (!sta529) + return -ENOMEM; + + sta529->regmap = devm_regmap_init_i2c(i2c, &sta529_regmap); + if (IS_ERR(sta529->regmap)) { + ret = PTR_ERR(sta529->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, sta529); + + ret = devm_snd_soc_register_component(&i2c->dev, + &sta529_component_driver, &sta529_dai, 1); + if (ret != 0) + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id sta529_i2c_id[] = { + { "sta529", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sta529_i2c_id); + +static const struct of_device_id sta529_of_match[] = { + { .compatible = "st,sta529", }, + { } +}; +MODULE_DEVICE_TABLE(of, sta529_of_match); + +static struct i2c_driver sta529_i2c_driver = { + .driver = { + .name = "sta529", + .of_match_table = sta529_of_match, + }, + .probe = sta529_i2c_probe, + .id_table = sta529_i2c_id, +}; + +module_i2c_driver(sta529_i2c_driver); + +MODULE_DESCRIPTION("ASoC STA529 codec driver"); +MODULE_AUTHOR("Rajeev Kumar "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c new file mode 100644 index 000000000..f62101a62 --- /dev/null +++ b/sound/soc/codecs/stac9766.c @@ -0,0 +1,342 @@ +/* + * stac9766.c -- ALSA SoC STAC9766 codec support + * + * Copyright 2009 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Features:- + * + * o Support for AC97 Codec, S/PDIF + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STAC9766_VENDOR_ID 0x83847666 +#define STAC9766_VENDOR_ID_MASK 0xffffffff + +#define AC97_STAC_DA_CONTROL 0x6A +#define AC97_STAC_ANALOG_SPECIAL 0x6E +#define AC97_STAC_STEREO_MIC 0x78 + +static const struct reg_default stac9766_reg_defaults[] = { + { 0x02, 0x8000 }, + { 0x04, 0x8000 }, + { 0x06, 0x8000 }, + { 0x0a, 0x0000 }, + { 0x0c, 0x8008 }, + { 0x0e, 0x8008 }, + { 0x10, 0x8808 }, + { 0x12, 0x8808 }, + { 0x14, 0x8808 }, + { 0x16, 0x8808 }, + { 0x18, 0x8808 }, + { 0x1a, 0x0000 }, + { 0x1c, 0x8000 }, + { 0x20, 0x0000 }, + { 0x22, 0x0000 }, + { 0x28, 0x0a05 }, + { 0x2c, 0xbb80 }, + { 0x32, 0xbb80 }, + { 0x3a, 0x2000 }, + { 0x3e, 0x0100 }, + { 0x4c, 0x0300 }, + { 0x4e, 0xffff }, + { 0x50, 0x0000 }, + { 0x52, 0x0000 }, + { 0x54, 0x0000 }, + { 0x6a, 0x0000 }, + { 0x6e, 0x1000 }, + { 0x72, 0x0000 }, + { 0x78, 0x0000 }, +}; + +static const struct regmap_config stac9766_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x78, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = regmap_ac97_default_volatile, + + .reg_defaults = stac9766_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(stac9766_reg_defaults), +}; + +static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", + "Line", "Stereo Mix", "Mono Mix", "Phone"}; +static const char *stac9766_mono_mux[] = {"Mix", "Mic"}; +static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"}; +static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"}; +static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"}; +static const char *stac9766_record_all_mux[] = {"All analog", + "Analog plus DAC"}; +static const char *stac9766_boost1[] = {"0dB", "10dB"}; +static const char *stac9766_boost2[] = {"0dB", "20dB"}; +static const char *stac9766_stereo_mic[] = {"Off", "On"}; + +static SOC_ENUM_DOUBLE_DECL(stac9766_record_enum, + AC97_REC_SEL, 8, 0, stac9766_record_mux); +static SOC_ENUM_SINGLE_DECL(stac9766_mono_enum, + AC97_GENERAL_PURPOSE, 9, stac9766_mono_mux); +static SOC_ENUM_SINGLE_DECL(stac9766_mic_enum, + AC97_GENERAL_PURPOSE, 8, stac9766_mic_mux); +static SOC_ENUM_SINGLE_DECL(stac9766_SPDIF_enum, + AC97_STAC_DA_CONTROL, 1, stac9766_SPDIF_mux); +static SOC_ENUM_SINGLE_DECL(stac9766_popbypass_enum, + AC97_GENERAL_PURPOSE, 15, stac9766_popbypass_mux); +static SOC_ENUM_SINGLE_DECL(stac9766_record_all_enum, + AC97_STAC_ANALOG_SPECIAL, 12, + stac9766_record_all_mux); +static SOC_ENUM_SINGLE_DECL(stac9766_boost1_enum, + AC97_MIC, 6, stac9766_boost1); /* 0/10dB */ +static SOC_ENUM_SINGLE_DECL(stac9766_boost2_enum, + AC97_STAC_ANALOG_SPECIAL, 2, stac9766_boost2); /* 0/20dB */ +static SOC_ENUM_SINGLE_DECL(stac9766_stereo_mic_enum, + AC97_STAC_STEREO_MIC, 2, stac9766_stereo_mic); + +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(master_tlv, -4650, 150, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(record_tlv, 0, 150, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(beep_tlv, -4500, 300, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(mix_tlv, -3450, 150, 0); + +static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { + SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1), + SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1, + master_tlv), + SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1), + SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1, + master_tlv), + SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1), + + SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv), + SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1), + + + SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv), + SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1), + SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1), + SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv), + SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), + + SOC_ENUM("Mic Boost1", stac9766_boost1_enum), + SOC_ENUM("Mic Boost2", stac9766_boost2_enum), + SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv), + SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum), + + SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), + SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1), + SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), + SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1), + + SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1), + SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0), + SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1), + SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), + + SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum), + SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum), + SOC_ENUM("Record All Mux", stac9766_record_all_enum), + SOC_ENUM("Record Mux", stac9766_record_enum), + SOC_ENUM("Mono Mux", stac9766_mono_enum), + SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum), +}; + +static int ac97_analog_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short reg; + + /* enable variable rate audio, disable SPDIF output */ + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x5, 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return snd_soc_component_write(component, reg, runtime->rate); +} + +static int ac97_digital_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short reg; + + snd_soc_component_write(component, AC97_SPDIF, 0x2002); + + /* Enable VRA and SPDIF out */ + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x5, 0x5); + + reg = AC97_PCM_FRONT_DAC_RATE; + + return snd_soc_component_write(component, reg, runtime->rate); +} + +static int stac9766_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: /* full On */ + case SND_SOC_BIAS_PREPARE: /* partial On */ + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + snd_soc_component_write(component, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* disable everything including AC link */ + snd_soc_component_write(component, AC97_POWERDOWN, 0xffff); + break; + } + return 0; +} + +static int stac9766_component_resume(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + + return snd_ac97_reset(ac97, true, STAC9766_VENDOR_ID, + STAC9766_VENDOR_ID_MASK); +} + +static const struct snd_soc_dai_ops stac9766_dai_ops_analog = { + .prepare = ac97_analog_prepare, +}; + +static const struct snd_soc_dai_ops stac9766_dai_ops_digital = { + .prepare = ac97_digital_prepare, +}; + +static struct snd_soc_dai_driver stac9766_dai[] = { +{ + .name = "stac9766-hifi-analog", + + /* stream cababilities */ + .playback = { + .stream_name = "stac9766 analog", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SND_SOC_STD_AC97_FMTS, + }, + .capture = { + .stream_name = "stac9766 analog", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SND_SOC_STD_AC97_FMTS, + }, + /* alsa ops */ + .ops = &stac9766_dai_ops_analog, +}, +{ + .name = "stac9766-hifi-IEC958", + + /* stream cababilities */ + .playback = { + .stream_name = "stac9766 IEC958", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE, + }, + /* alsa ops */ + .ops = &stac9766_dai_ops_digital, +} +}; + +static int stac9766_component_probe(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97; + struct regmap *regmap; + int ret; + + ac97 = snd_soc_new_ac97_component(component, STAC9766_VENDOR_ID, + STAC9766_VENDOR_ID_MASK); + if (IS_ERR(ac97)) + return PTR_ERR(ac97); + + regmap = regmap_init_ac97(ac97, &stac9766_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + goto err_free_ac97; + } + + snd_soc_component_init_regmap(component, regmap); + snd_soc_component_set_drvdata(component, ac97); + + return 0; +err_free_ac97: + snd_soc_free_ac97_component(ac97); + return ret; +} + +static void stac9766_component_remove(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + + snd_soc_component_exit_regmap(component); + snd_soc_free_ac97_component(ac97); +} + +static const struct snd_soc_component_driver soc_component_dev_stac9766 = { + .controls = stac9766_snd_ac97_controls, + .num_controls = ARRAY_SIZE(stac9766_snd_ac97_controls), + .set_bias_level = stac9766_set_bias_level, + .probe = stac9766_component_probe, + .remove = stac9766_component_remove, + .resume = stac9766_component_resume, + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, + +}; + +static int stac9766_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_stac9766, stac9766_dai, ARRAY_SIZE(stac9766_dai)); +} + +static struct platform_driver stac9766_codec_driver = { + .driver = { + .name = "stac9766-codec", + }, + + .probe = stac9766_probe, +}; + +module_platform_driver(stac9766_codec_driver); + +MODULE_DESCRIPTION("ASoC stac9766 driver"); +MODULE_AUTHOR("Jon Smirl "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sti-sas.c b/sound/soc/codecs/sti-sas.c new file mode 100644 index 000000000..27196126f --- /dev/null +++ b/sound/soc/codecs/sti-sas.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include +#include +#include + +#include +#include + +/* DAC definitions */ + +/* stih407 DAC registers */ +/* sysconf 5041: Audio-Gue-Control */ +#define STIH407_AUDIO_GLUE_CTRL 0x000000A4 +/* sysconf 5042: Audio-DAC-Control */ +#define STIH407_AUDIO_DAC_CTRL 0x000000A8 + +/* DAC definitions */ +#define STIH407_DAC_SOFTMUTE 0x0 +#define STIH407_DAC_STANDBY_ANA 0x1 +#define STIH407_DAC_STANDBY 0x2 + +#define STIH407_DAC_SOFTMUTE_MASK BIT(STIH407_DAC_SOFTMUTE) +#define STIH407_DAC_STANDBY_ANA_MASK BIT(STIH407_DAC_STANDBY_ANA) +#define STIH407_DAC_STANDBY_MASK BIT(STIH407_DAC_STANDBY) + +/* SPDIF definitions */ +#define SPDIF_BIPHASE_ENABLE 0x6 +#define SPDIF_BIPHASE_IDLE 0x7 + +#define SPDIF_BIPHASE_ENABLE_MASK BIT(SPDIF_BIPHASE_ENABLE) +#define SPDIF_BIPHASE_IDLE_MASK BIT(SPDIF_BIPHASE_IDLE) + +enum { + STI_SAS_DAI_SPDIF_OUT, + STI_SAS_DAI_ANALOG_OUT, +}; + +static const struct reg_default stih407_sas_reg_defaults[] = { + { STIH407_AUDIO_DAC_CTRL, 0x000000000 }, + { STIH407_AUDIO_GLUE_CTRL, 0x00000040 }, +}; + +struct sti_dac_audio { + struct regmap *regmap; + struct regmap *virt_regmap; + struct regmap_field **field; + struct reset_control *rst; + int mclk; +}; + +struct sti_spdif_audio { + struct regmap *regmap; + struct regmap_field **field; + int mclk; +}; + +/* device data structure */ +struct sti_sas_dev_data { + const struct regmap_config *regmap; + const struct snd_soc_dai_ops *dac_ops; /* DAC function callbacks */ + const struct snd_soc_dapm_widget *dapm_widgets; /* dapms declaration */ + const int num_dapm_widgets; /* dapms declaration */ + const struct snd_soc_dapm_route *dapm_routes; /* route declaration */ + const int num_dapm_routes; /* route declaration */ +}; + +/* driver data structure */ +struct sti_sas_data { + struct device *dev; + const struct sti_sas_dev_data *dev_data; + struct sti_dac_audio dac; + struct sti_spdif_audio spdif; +}; + +/* Read a register from the sysconf reg bank */ +static int sti_sas_read_reg(void *context, unsigned int reg, + unsigned int *value) +{ + struct sti_sas_data *drvdata = context; + int status; + u32 val; + + status = regmap_read(drvdata->dac.regmap, reg, &val); + *value = (unsigned int)val; + + return status; +} + +/* Read a register from the sysconf reg bank */ +static int sti_sas_write_reg(void *context, unsigned int reg, + unsigned int value) +{ + struct sti_sas_data *drvdata = context; + int status; + + status = regmap_write(drvdata->dac.regmap, reg, value); + + return status; +} + +static int sti_sas_init_sas_registers(struct snd_soc_component *component, + struct sti_sas_data *data) +{ + int ret; + /* + * DAC and SPDIF are activated by default + * put them in IDLE to save power + */ + + /* Initialise bi-phase formatter to disabled */ + ret = snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, + SPDIF_BIPHASE_ENABLE_MASK, 0); + + if (!ret) + /* Initialise bi-phase formatter idle value to 0 */ + ret = snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, + SPDIF_BIPHASE_IDLE_MASK, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to update SPDIF registers\n"); + return ret; + } + + /* Init DAC configuration */ + /* init configuration */ + ret = snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, + STIH407_DAC_STANDBY_MASK, + STIH407_DAC_STANDBY_MASK); + + if (!ret) + ret = snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, + STIH407_DAC_STANDBY_ANA_MASK, + STIH407_DAC_STANDBY_ANA_MASK); + if (!ret) + ret = snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, + STIH407_DAC_SOFTMUTE_MASK, + STIH407_DAC_SOFTMUTE_MASK); + + if (ret < 0) { + dev_err(component->dev, "Failed to update DAC registers\n"); + return ret; + } + + return ret; +} + +/* + * DAC + */ +static int sti_sas_dac_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + /* Sanity check only */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(dai->component->dev, + "%s: ERROR: Unsupporter master mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dapm_widget stih407_sas_dapm_widgets[] = { + SND_SOC_DAPM_OUT_DRV("DAC standby ana", STIH407_AUDIO_DAC_CTRL, + STIH407_DAC_STANDBY_ANA, 1, NULL, 0), + SND_SOC_DAPM_DAC("DAC standby", "dac_p", STIH407_AUDIO_DAC_CTRL, + STIH407_DAC_STANDBY, 1), + SND_SOC_DAPM_OUTPUT("DAC Output"), +}; + +static const struct snd_soc_dapm_route stih407_sas_route[] = { + {"DAC Output", NULL, "DAC standby ana"}, + {"DAC standby ana", NULL, "DAC standby"}, +}; + + +static int stih407_sas_dac_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + return snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, + STIH407_DAC_SOFTMUTE_MASK, + STIH407_DAC_SOFTMUTE_MASK); + } else { + return snd_soc_component_update_bits(component, STIH407_AUDIO_DAC_CTRL, + STIH407_DAC_SOFTMUTE_MASK, + 0); + } +} + +/* + * SPDIF + */ +static int sti_sas_spdif_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(dai->component->dev, + "%s: ERROR: Unsupporter master mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + return 0; +} + +/* + * sti_sas_spdif_trigger: + * Trigger function is used to ensure that BiPhase Formater is disabled + * before CPU dai is stopped. + * This is mandatory to avoid that BPF is stalled + */ +static int sti_sas_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + return snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, + SPDIF_BIPHASE_ENABLE_MASK, + SPDIF_BIPHASE_ENABLE_MASK); + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + return snd_soc_component_update_bits(component, STIH407_AUDIO_GLUE_CTRL, + SPDIF_BIPHASE_ENABLE_MASK, + 0); + default: + return -EINVAL; + } +} + +static bool sti_sas_volatile_register(struct device *dev, unsigned int reg) +{ + if (reg == STIH407_AUDIO_GLUE_CTRL) + return true; + + return false; +} + +/* + * CODEC DAIS + */ + +/* + * sti_sas_set_sysclk: + * get MCLK input frequency to check that MCLK-FS ratio is coherent + */ +static int sti_sas_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct sti_sas_data *drvdata = dev_get_drvdata(component->dev); + + if (dir == SND_SOC_CLOCK_OUT) + return 0; + + if (clk_id != 0) + return -EINVAL; + + switch (dai->id) { + case STI_SAS_DAI_SPDIF_OUT: + drvdata->spdif.mclk = freq; + break; + + case STI_SAS_DAI_ANALOG_OUT: + drvdata->dac.mclk = freq; + break; + } + + return 0; +} + +static int sti_sas_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct sti_sas_data *drvdata = dev_get_drvdata(component->dev); + struct snd_pcm_runtime *runtime = substream->runtime; + + switch (dai->id) { + case STI_SAS_DAI_SPDIF_OUT: + if ((drvdata->spdif.mclk / runtime->rate) != 128) { + dev_err(component->dev, "unexpected mclk-fs ratio\n"); + return -EINVAL; + } + break; + case STI_SAS_DAI_ANALOG_OUT: + if ((drvdata->dac.mclk / runtime->rate) != 256) { + dev_err(component->dev, "unexpected mclk-fs ratio\n"); + return -EINVAL; + } + break; + } + + return 0; +} + +static const struct snd_soc_dai_ops stih407_dac_ops = { + .set_fmt = sti_sas_dac_set_fmt, + .mute_stream = stih407_sas_dac_mute, + .prepare = sti_sas_prepare, + .set_sysclk = sti_sas_set_sysclk, +}; + +static const struct regmap_config stih407_sas_regmap = { + .reg_bits = 32, + .val_bits = 32, + .fast_io = true, + .max_register = STIH407_AUDIO_DAC_CTRL, + .reg_defaults = stih407_sas_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(stih407_sas_reg_defaults), + .volatile_reg = sti_sas_volatile_register, + .cache_type = REGCACHE_RBTREE, + .reg_read = sti_sas_read_reg, + .reg_write = sti_sas_write_reg, +}; + +static const struct sti_sas_dev_data stih407_data = { + .regmap = &stih407_sas_regmap, + .dac_ops = &stih407_dac_ops, + .dapm_widgets = stih407_sas_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(stih407_sas_dapm_widgets), + .dapm_routes = stih407_sas_route, + .num_dapm_routes = ARRAY_SIZE(stih407_sas_route), +}; + +static struct snd_soc_dai_driver sti_sas_dai[] = { + { + .name = "sas-dai-spdif-out", + .id = STI_SAS_DAI_SPDIF_OUT, + .playback = { + .stream_name = "spdif_p", + .channels_min = 2, + .channels_max = 2, + .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_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = (struct snd_soc_dai_ops[]) { + { + .set_fmt = sti_sas_spdif_set_fmt, + .trigger = sti_sas_spdif_trigger, + .set_sysclk = sti_sas_set_sysclk, + .prepare = sti_sas_prepare, + } + }, + }, + { + .name = "sas-dai-dac", + .id = STI_SAS_DAI_ANALOG_OUT, + .playback = { + .stream_name = "dac_p", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + }, +}; + +#ifdef CONFIG_PM_SLEEP +static int sti_sas_resume(struct snd_soc_component *component) +{ + struct sti_sas_data *drvdata = dev_get_drvdata(component->dev); + + return sti_sas_init_sas_registers(component, drvdata); +} +#else +#define sti_sas_resume NULL +#endif + +static int sti_sas_component_probe(struct snd_soc_component *component) +{ + struct sti_sas_data *drvdata = dev_get_drvdata(component->dev); + int ret; + + ret = sti_sas_init_sas_registers(component, drvdata); + + return ret; +} + +static struct snd_soc_component_driver sti_sas_driver = { + .probe = sti_sas_component_probe, + .resume = sti_sas_resume, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id sti_sas_dev_match[] = { + { + .compatible = "st,stih407-sas-codec", + .data = &stih407_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, sti_sas_dev_match); + +static int sti_sas_driver_probe(struct platform_device *pdev) +{ + struct device_node *pnode = pdev->dev.of_node; + struct sti_sas_data *drvdata; + const struct of_device_id *of_id; + + /* Allocate device structure */ + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct sti_sas_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + /* Populate data structure depending on compatibility */ + of_id = of_match_node(sti_sas_dev_match, pnode); + if (!of_id->data) { + dev_err(&pdev->dev, "data associated to device is missing\n"); + return -EINVAL; + } + + drvdata->dev_data = (struct sti_sas_dev_data *)of_id->data; + + /* Initialise device structure */ + drvdata->dev = &pdev->dev; + + /* Request the DAC & SPDIF registers memory region */ + drvdata->dac.virt_regmap = devm_regmap_init(&pdev->dev, NULL, drvdata, + drvdata->dev_data->regmap); + if (IS_ERR(drvdata->dac.virt_regmap)) { + dev_err(&pdev->dev, "audio registers not enabled\n"); + return PTR_ERR(drvdata->dac.virt_regmap); + } + + /* Request the syscon region */ + drvdata->dac.regmap = + syscon_regmap_lookup_by_phandle(pnode, "st,syscfg"); + if (IS_ERR(drvdata->dac.regmap)) { + dev_err(&pdev->dev, "syscon registers not available\n"); + return PTR_ERR(drvdata->dac.regmap); + } + drvdata->spdif.regmap = drvdata->dac.regmap; + + sti_sas_dai[STI_SAS_DAI_ANALOG_OUT].ops = drvdata->dev_data->dac_ops; + + /* Set dapms*/ + sti_sas_driver.dapm_widgets = drvdata->dev_data->dapm_widgets; + sti_sas_driver.num_dapm_widgets = drvdata->dev_data->num_dapm_widgets; + + sti_sas_driver.dapm_routes = drvdata->dev_data->dapm_routes; + sti_sas_driver.num_dapm_routes = drvdata->dev_data->num_dapm_routes; + + /* Store context */ + dev_set_drvdata(&pdev->dev, drvdata); + + return devm_snd_soc_register_component(&pdev->dev, &sti_sas_driver, + sti_sas_dai, + ARRAY_SIZE(sti_sas_dai)); +} + +static struct platform_driver sti_sas_platform_driver = { + .driver = { + .name = "sti-sas-codec", + .of_match_table = sti_sas_dev_match, + }, + .probe = sti_sas_driver_probe, +}; + +module_platform_driver(sti_sas_platform_driver); + +MODULE_DESCRIPTION("audio codec for STMicroelectronics sti platforms"); +MODULE_AUTHOR("Arnaud.pouliquen@st.com"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c new file mode 100644 index 000000000..355ecafb7 --- /dev/null +++ b/sound/soc/codecs/tas2552.c @@ -0,0 +1,780 @@ +/* + * tas2552.c - ALSA SoC Texas Instruments TAS2552 Mono Audio Amplifier + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Dan Murphy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "tas2552.h" + +static const struct reg_default tas2552_reg_defs[] = { + {TAS2552_CFG_1, 0x22}, + {TAS2552_CFG_3, 0x80}, + {TAS2552_DOUT, 0x00}, + {TAS2552_OUTPUT_DATA, 0xc0}, + {TAS2552_PDM_CFG, 0x01}, + {TAS2552_PGA_GAIN, 0x00}, + {TAS2552_BOOST_APT_CTRL, 0x0f}, + {TAS2552_RESERVED_0D, 0xbe}, + {TAS2552_LIMIT_RATE_HYS, 0x08}, + {TAS2552_CFG_2, 0xef}, + {TAS2552_SER_CTRL_1, 0x00}, + {TAS2552_SER_CTRL_2, 0x00}, + {TAS2552_PLL_CTRL_1, 0x10}, + {TAS2552_PLL_CTRL_2, 0x00}, + {TAS2552_PLL_CTRL_3, 0x00}, + {TAS2552_BTIP, 0x8f}, + {TAS2552_BTS_CTRL, 0x80}, + {TAS2552_LIMIT_RELEASE, 0x04}, + {TAS2552_LIMIT_INT_COUNT, 0x00}, + {TAS2552_EDGE_RATE_CTRL, 0x40}, + {TAS2552_VBAT_DATA, 0x00}, +}; + +#define TAS2552_NUM_SUPPLIES 3 +static const char *tas2552_supply_names[TAS2552_NUM_SUPPLIES] = { + "vbat", /* vbat voltage */ + "iovdd", /* I/O Voltage */ + "avdd", /* Analog DAC Voltage */ +}; + +struct tas2552_data { + struct snd_soc_component *component; + struct regmap *regmap; + struct i2c_client *tas2552_client; + struct regulator_bulk_data supplies[TAS2552_NUM_SUPPLIES]; + struct gpio_desc *enable_gpio; + unsigned char regs[TAS2552_VBAT_DATA]; + unsigned int pll_clkin; + int pll_clk_id; + unsigned int pdm_clk; + int pdm_clk_id; + + unsigned int dai_fmt; + unsigned int tdm_delay; +}; + +static int tas2552_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_write(component, TAS2552_RESERVED_0D, 0xc0); + snd_soc_component_update_bits(component, TAS2552_LIMIT_RATE_HYS, (1 << 5), + (1 << 5)); + snd_soc_component_update_bits(component, TAS2552_CFG_2, 1, 0); + snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_SWS, 0); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_SWS, + TAS2552_SWS); + snd_soc_component_update_bits(component, TAS2552_CFG_2, 1, 1); + snd_soc_component_update_bits(component, TAS2552_LIMIT_RATE_HYS, (1 << 5), 0); + snd_soc_component_write(component, TAS2552_RESERVED_0D, 0xbe); + break; + } + return 0; +} + +/* Input mux controls */ +static const char * const tas2552_input_texts[] = { + "Digital", "Analog" }; +static SOC_ENUM_SINGLE_DECL(tas2552_input_mux_enum, TAS2552_CFG_3, 7, + tas2552_input_texts); + +static const struct snd_kcontrol_new tas2552_input_mux_control = + SOC_DAPM_ENUM("Route", tas2552_input_mux_enum); + +static const struct snd_soc_dapm_widget tas2552_dapm_widgets[] = +{ + SND_SOC_DAPM_INPUT("IN"), + + /* MUX Controls */ + SND_SOC_DAPM_MUX("Input selection", SND_SOC_NOPM, 0, 0, + &tas2552_input_mux_control), + + SND_SOC_DAPM_AIF_IN("DAC IN", "DAC Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUT_DRV("ClassD", TAS2552_CFG_2, 7, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL", TAS2552_CFG_2, 3, 0, NULL, 0), + SND_SOC_DAPM_POST("Post Event", tas2552_post_event), + + SND_SOC_DAPM_OUTPUT("OUT") +}; + +static const struct snd_soc_dapm_route tas2552_audio_map[] = { + {"DAC", NULL, "DAC IN"}, + {"Input selection", "Digital", "DAC"}, + {"Input selection", "Analog", "IN"}, + {"ClassD", NULL, "Input selection"}, + {"OUT", NULL, "ClassD"}, + {"ClassD", NULL, "PLL"}, +}; + +#ifdef CONFIG_PM +static void tas2552_sw_shutdown(struct tas2552_data *tas2552, int sw_shutdown) +{ + u8 cfg1_reg = 0; + + if (!tas2552->component) + return; + + if (sw_shutdown) + cfg1_reg = TAS2552_SWS; + + snd_soc_component_update_bits(tas2552->component, TAS2552_CFG_1, TAS2552_SWS, + cfg1_reg); +} +#endif + +static int tas2552_setup_pll(struct snd_soc_component *component, + struct snd_pcm_hw_params *params) +{ + struct tas2552_data *tas2552 = dev_get_drvdata(component->dev); + bool bypass_pll = false; + unsigned int pll_clk = params_rate(params) * 512; + unsigned int pll_clkin = tas2552->pll_clkin; + u8 pll_enable; + + if (!pll_clkin) { + if (tas2552->pll_clk_id != TAS2552_PLL_CLKIN_BCLK) + return -EINVAL; + + pll_clkin = snd_soc_params_to_bclk(params); + pll_clkin += tas2552->tdm_delay; + } + + pll_enable = snd_soc_component_read32(component, TAS2552_CFG_2) & TAS2552_PLL_ENABLE; + snd_soc_component_update_bits(component, TAS2552_CFG_2, TAS2552_PLL_ENABLE, 0); + + if (pll_clkin == pll_clk) + bypass_pll = true; + + if (bypass_pll) { + /* By pass the PLL configuration */ + snd_soc_component_update_bits(component, TAS2552_PLL_CTRL_2, + TAS2552_PLL_BYPASS, TAS2552_PLL_BYPASS); + } else { + /* Fill in the PLL control registers for J & D + * pll_clk = (.5 * pll_clkin * J.D) / 2^p + * Need to fill in J and D here based on incoming freq + */ + unsigned int d, q, t; + u8 j; + u8 pll_sel = (tas2552->pll_clk_id << 3) & TAS2552_PLL_SRC_MASK; + u8 p = snd_soc_component_read32(component, TAS2552_PLL_CTRL_1); + + p = (p >> 7); + +recalc: + t = (pll_clk * 2) << p; + j = t / pll_clkin; + d = t % pll_clkin; + t = pll_clkin / 10000; + q = d / (t + 1); + d = q + ((9999 - pll_clkin % 10000) * (d / t - q)) / 10000; + + if (d && (pll_clkin < 512000 || pll_clkin > 9200000)) { + if (tas2552->pll_clk_id == TAS2552_PLL_CLKIN_BCLK) { + pll_clkin = 1800000; + pll_sel = (TAS2552_PLL_CLKIN_1_8_FIXED << 3) & + TAS2552_PLL_SRC_MASK; + } else { + pll_clkin = snd_soc_params_to_bclk(params); + pll_clkin += tas2552->tdm_delay; + pll_sel = (TAS2552_PLL_CLKIN_BCLK << 3) & + TAS2552_PLL_SRC_MASK; + } + goto recalc; + } + + snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_PLL_SRC_MASK, + pll_sel); + + snd_soc_component_update_bits(component, TAS2552_PLL_CTRL_1, + TAS2552_PLL_J_MASK, j); + /* Will clear the PLL_BYPASS bit */ + snd_soc_component_write(component, TAS2552_PLL_CTRL_2, + TAS2552_PLL_D_UPPER(d)); + snd_soc_component_write(component, TAS2552_PLL_CTRL_3, + TAS2552_PLL_D_LOWER(d)); + } + + /* Restore PLL status */ + snd_soc_component_update_bits(component, TAS2552_CFG_2, TAS2552_PLL_ENABLE, + pll_enable); + + return 0; +} + +static int tas2552_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tas2552_data *tas2552 = dev_get_drvdata(component->dev); + int cpf; + u8 ser_ctrl1_reg, wclk_rate; + + switch (params_width(params)) { + case 16: + ser_ctrl1_reg = TAS2552_WORDLENGTH_16BIT; + cpf = 32 + tas2552->tdm_delay; + break; + case 20: + ser_ctrl1_reg = TAS2552_WORDLENGTH_20BIT; + cpf = 64 + tas2552->tdm_delay; + break; + case 24: + ser_ctrl1_reg = TAS2552_WORDLENGTH_24BIT; + cpf = 64 + tas2552->tdm_delay; + break; + case 32: + ser_ctrl1_reg = TAS2552_WORDLENGTH_32BIT; + cpf = 64 + tas2552->tdm_delay; + break; + default: + dev_err(component->dev, "Not supported sample size: %d\n", + params_width(params)); + return -EINVAL; + } + + if (cpf <= 32) + ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_32; + else if (cpf <= 64) + ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_64; + else if (cpf <= 128) + ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_128; + else + ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_256; + + snd_soc_component_update_bits(component, TAS2552_SER_CTRL_1, + TAS2552_WORDLENGTH_MASK | TAS2552_CLKSPERFRAME_MASK, + ser_ctrl1_reg); + + switch (params_rate(params)) { + case 8000: + wclk_rate = TAS2552_WCLK_FREQ_8KHZ; + break; + case 11025: + case 12000: + wclk_rate = TAS2552_WCLK_FREQ_11_12KHZ; + break; + case 16000: + wclk_rate = TAS2552_WCLK_FREQ_16KHZ; + break; + case 22050: + case 24000: + wclk_rate = TAS2552_WCLK_FREQ_22_24KHZ; + break; + case 32000: + wclk_rate = TAS2552_WCLK_FREQ_32KHZ; + break; + case 44100: + case 48000: + wclk_rate = TAS2552_WCLK_FREQ_44_48KHZ; + break; + case 88200: + case 96000: + wclk_rate = TAS2552_WCLK_FREQ_88_96KHZ; + break; + case 176400: + case 192000: + wclk_rate = TAS2552_WCLK_FREQ_176_192KHZ; + break; + default: + dev_err(component->dev, "Not supported sample rate: %d\n", + params_rate(params)); + return -EINVAL; + } + + snd_soc_component_update_bits(component, TAS2552_CFG_3, TAS2552_WCLK_FREQ_MASK, + wclk_rate); + + return tas2552_setup_pll(component, params); +} + +#define TAS2552_DAI_FMT_MASK (TAS2552_BCLKDIR | \ + TAS2552_WCLKDIR | \ + TAS2552_DATAFORMAT_MASK) +static int tas2552_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component); + int delay = 0; + + /* TDM slot selection only valid in DSP_A/_B mode */ + if (tas2552->dai_fmt == SND_SOC_DAIFMT_DSP_A) + delay += (tas2552->tdm_delay + 1); + else if (tas2552->dai_fmt == SND_SOC_DAIFMT_DSP_B) + delay += tas2552->tdm_delay; + + /* Configure data delay */ + snd_soc_component_write(component, TAS2552_SER_CTRL_2, delay); + + return 0; +} + +static int tas2552_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct tas2552_data *tas2552 = dev_get_drvdata(component->dev); + u8 serial_format; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + serial_format = 0x00; + break; + case SND_SOC_DAIFMT_CBS_CFM: + serial_format = TAS2552_WCLKDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + serial_format = TAS2552_BCLKDIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + serial_format = (TAS2552_BCLKDIR | TAS2552_WCLKDIR); + break; + default: + dev_vdbg(component->dev, "DAI Format master is not found\n"); + return -EINVAL; + } + + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_INV_MASK)) { + case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): + break; + case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF): + case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF): + serial_format |= TAS2552_DATAFORMAT_DSP; + break; + case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF): + serial_format |= TAS2552_DATAFORMAT_RIGHT_J; + break; + case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): + serial_format |= TAS2552_DATAFORMAT_LEFT_J; + break; + default: + dev_vdbg(component->dev, "DAI Format is not found\n"); + return -EINVAL; + } + tas2552->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + snd_soc_component_update_bits(component, TAS2552_SER_CTRL_1, TAS2552_DAI_FMT_MASK, + serial_format); + return 0; +} + +static int tas2552_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct tas2552_data *tas2552 = dev_get_drvdata(component->dev); + u8 reg, mask, val; + + switch (clk_id) { + case TAS2552_PLL_CLKIN_MCLK: + case TAS2552_PLL_CLKIN_IVCLKIN: + if (freq < 512000 || freq > 24576000) { + /* out of range PLL_CLKIN, fall back to use BCLK */ + dev_warn(component->dev, "Out of range PLL_CLKIN: %u\n", + freq); + clk_id = TAS2552_PLL_CLKIN_BCLK; + freq = 0; + } + /* fall through */ + case TAS2552_PLL_CLKIN_BCLK: + case TAS2552_PLL_CLKIN_1_8_FIXED: + mask = TAS2552_PLL_SRC_MASK; + val = (clk_id << 3) & mask; /* bit 4:5 in the register */ + reg = TAS2552_CFG_1; + tas2552->pll_clk_id = clk_id; + tas2552->pll_clkin = freq; + break; + case TAS2552_PDM_CLK_PLL: + case TAS2552_PDM_CLK_IVCLKIN: + case TAS2552_PDM_CLK_BCLK: + case TAS2552_PDM_CLK_MCLK: + mask = TAS2552_PDM_CLK_SEL_MASK; + val = (clk_id >> 1) & mask; /* bit 0:1 in the register */ + reg = TAS2552_PDM_CFG; + tas2552->pdm_clk_id = clk_id; + tas2552->pdm_clk = freq; + break; + default: + dev_err(component->dev, "Invalid clk id: %d\n", clk_id); + return -EINVAL; + } + + snd_soc_component_update_bits(component, reg, mask, val); + + return 0; +} + +static int tas2552_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component); + unsigned int lsb; + + if (unlikely(!tx_mask)) { + dev_err(component->dev, "tx masks need to be non 0\n"); + return -EINVAL; + } + + /* TDM based on DSP mode requires slots to be adjacent */ + lsb = __ffs(tx_mask); + if ((lsb + 1) != __fls(tx_mask)) { + dev_err(component->dev, "Invalid mask, slots must be adjacent\n"); + return -EINVAL; + } + + tas2552->tdm_delay = lsb * slot_width; + + /* DOUT in high-impedance on inactive bit clocks */ + snd_soc_component_update_bits(component, TAS2552_DOUT, + TAS2552_SDOUT_TRISTATE, TAS2552_SDOUT_TRISTATE); + + return 0; +} + +static int tas2552_mute(struct snd_soc_dai *dai, int mute) +{ + u8 cfg1_reg = 0; + struct snd_soc_component *component = dai->component; + + if (mute) + cfg1_reg |= TAS2552_MUTE; + + snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_MUTE, cfg1_reg); + + return 0; +} + +#ifdef CONFIG_PM +static int tas2552_runtime_suspend(struct device *dev) +{ + struct tas2552_data *tas2552 = dev_get_drvdata(dev); + + tas2552_sw_shutdown(tas2552, 1); + + regcache_cache_only(tas2552->regmap, true); + regcache_mark_dirty(tas2552->regmap); + + gpiod_set_value(tas2552->enable_gpio, 0); + + return 0; +} + +static int tas2552_runtime_resume(struct device *dev) +{ + struct tas2552_data *tas2552 = dev_get_drvdata(dev); + + gpiod_set_value(tas2552->enable_gpio, 1); + + tas2552_sw_shutdown(tas2552, 0); + + regcache_cache_only(tas2552->regmap, false); + regcache_sync(tas2552->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops tas2552_pm = { + SET_RUNTIME_PM_OPS(tas2552_runtime_suspend, tas2552_runtime_resume, + NULL) +}; + +static const struct snd_soc_dai_ops tas2552_speaker_dai_ops = { + .hw_params = tas2552_hw_params, + .prepare = tas2552_prepare, + .set_sysclk = tas2552_set_dai_sysclk, + .set_fmt = tas2552_set_dai_fmt, + .set_tdm_slot = tas2552_set_dai_tdm_slot, + .digital_mute = tas2552_mute, +}; + +/* Formats supported by TAS2552 driver. */ +#define TAS2552_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* TAS2552 dai structure. */ +static struct snd_soc_dai_driver tas2552_dai[] = { + { + .name = "tas2552-amplifier", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = TAS2552_FORMATS, + }, + .ops = &tas2552_speaker_dai_ops, + }, +}; + +/* + * DAC digital volumes. From -7 to 24 dB in 1 dB steps + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -700, 100, 0); + +static const char * const tas2552_din_source_select[] = { + "Muted", + "Left", + "Right", + "Left + Right average", +}; +static SOC_ENUM_SINGLE_DECL(tas2552_din_source_enum, + TAS2552_CFG_3, 3, + tas2552_din_source_select); + +static const struct snd_kcontrol_new tas2552_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Driver Playback Volume", + TAS2552_PGA_GAIN, 0, 0x1f, 0, dac_tlv), + SOC_ENUM("DIN source", tas2552_din_source_enum), +}; + +static int tas2552_component_probe(struct snd_soc_component *component) +{ + struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component); + int ret; + + tas2552->component = component; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas2552->supplies), + tas2552->supplies); + + if (ret != 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", + ret); + return ret; + } + + gpiod_set_value(tas2552->enable_gpio, 1); + + ret = pm_runtime_get_sync(component->dev); + if (ret < 0) { + dev_err(component->dev, "Enabling device failed: %d\n", + ret); + goto probe_fail; + } + + snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_MUTE, TAS2552_MUTE); + snd_soc_component_write(component, TAS2552_CFG_3, TAS2552_I2S_OUT_SEL | + TAS2552_DIN_SRC_SEL_AVG_L_R); + snd_soc_component_write(component, TAS2552_OUTPUT_DATA, + TAS2552_PDM_DATA_SEL_V_I | + TAS2552_R_DATA_OUT(TAS2552_DATA_OUT_V_DATA)); + snd_soc_component_write(component, TAS2552_BOOST_APT_CTRL, TAS2552_APT_DELAY_200 | + TAS2552_APT_THRESH_20_17); + + snd_soc_component_write(component, TAS2552_CFG_2, TAS2552_BOOST_EN | TAS2552_APT_EN | + TAS2552_LIM_EN); + + return 0; + +probe_fail: + gpiod_set_value(tas2552->enable_gpio, 0); + + regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies), + tas2552->supplies); + return ret; +} + +static void tas2552_component_remove(struct snd_soc_component *component) +{ + struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component); + + pm_runtime_put(component->dev); + + gpiod_set_value(tas2552->enable_gpio, 0); +}; + +#ifdef CONFIG_PM +static int tas2552_suspend(struct snd_soc_component *component) +{ + struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies), + tas2552->supplies); + + if (ret != 0) + dev_err(component->dev, "Failed to disable supplies: %d\n", + ret); + return ret; +} + +static int tas2552_resume(struct snd_soc_component *component) +{ + struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas2552->supplies), + tas2552->supplies); + + if (ret != 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", + ret); + } + + return ret; +} +#else +#define tas2552_suspend NULL +#define tas2552_resume NULL +#endif + +static const struct snd_soc_component_driver soc_component_dev_tas2552 = { + .probe = tas2552_component_probe, + .remove = tas2552_component_remove, + .suspend = tas2552_suspend, + .resume = tas2552_resume, + .controls = tas2552_snd_controls, + .num_controls = ARRAY_SIZE(tas2552_snd_controls), + .dapm_widgets = tas2552_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas2552_dapm_widgets), + .dapm_routes = tas2552_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas2552_audio_map), + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config tas2552_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TAS2552_MAX_REG, + .reg_defaults = tas2552_reg_defs, + .num_reg_defaults = ARRAY_SIZE(tas2552_reg_defs), + .cache_type = REGCACHE_RBTREE, +}; + +static int tas2552_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev; + struct tas2552_data *data; + int ret; + int i; + + dev = &client->dev; + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + data->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(data->enable_gpio)) + return PTR_ERR(data->enable_gpio); + + data->tas2552_client = client; + data->regmap = devm_regmap_init_i2c(client, &tas2552_regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data->supplies); i++) + data->supplies[i].supply = tas2552_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), + data->supplies); + if (ret != 0) { + dev_err(dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + pm_runtime_set_active(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_sync_autosuspend(&client->dev); + + dev_set_drvdata(&client->dev, data); + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_dev_tas2552, + tas2552_dai, ARRAY_SIZE(tas2552_dai)); + if (ret < 0) + dev_err(&client->dev, "Failed to register component: %d\n", ret); + + return ret; +} + +static int tas2552_i2c_remove(struct i2c_client *client) +{ + pm_runtime_disable(&client->dev); + return 0; +} + +static const struct i2c_device_id tas2552_id[] = { + { "tas2552", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas2552_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tas2552_of_match[] = { + { .compatible = "ti,tas2552", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tas2552_of_match); +#endif + +static struct i2c_driver tas2552_i2c_driver = { + .driver = { + .name = "tas2552", + .of_match_table = of_match_ptr(tas2552_of_match), + .pm = &tas2552_pm, + }, + .probe = tas2552_probe, + .remove = tas2552_i2c_remove, + .id_table = tas2552_id, +}; + +module_i2c_driver(tas2552_i2c_driver); + +MODULE_AUTHOR("Dan Muprhy "); +MODULE_DESCRIPTION("TAS2552 Audio amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas2552.h b/sound/soc/codecs/tas2552.h new file mode 100644 index 000000000..e34752b8a --- /dev/null +++ b/sound/soc/codecs/tas2552.h @@ -0,0 +1,146 @@ +/* + * tas2552.h - ALSA SoC Texas Instruments TAS2552 Mono Audio Amplifier + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Dan Murphy + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __TAS2552_H__ +#define __TAS2552_H__ + +/* Register Address Map */ +#define TAS2552_DEVICE_STATUS 0x00 +#define TAS2552_CFG_1 0x01 +#define TAS2552_CFG_2 0x02 +#define TAS2552_CFG_3 0x03 +#define TAS2552_DOUT 0x04 +#define TAS2552_SER_CTRL_1 0x05 +#define TAS2552_SER_CTRL_2 0x06 +#define TAS2552_OUTPUT_DATA 0x07 +#define TAS2552_PLL_CTRL_1 0x08 +#define TAS2552_PLL_CTRL_2 0x09 +#define TAS2552_PLL_CTRL_3 0x0a +#define TAS2552_BTIP 0x0b +#define TAS2552_BTS_CTRL 0x0c +#define TAS2552_RESERVED_0D 0x0d +#define TAS2552_LIMIT_RATE_HYS 0x0e +#define TAS2552_LIMIT_RELEASE 0x0f +#define TAS2552_LIMIT_INT_COUNT 0x10 +#define TAS2552_PDM_CFG 0x11 +#define TAS2552_PGA_GAIN 0x12 +#define TAS2552_EDGE_RATE_CTRL 0x13 +#define TAS2552_BOOST_APT_CTRL 0x14 +#define TAS2552_VER_NUM 0x16 +#define TAS2552_VBAT_DATA 0x19 +#define TAS2552_MAX_REG TAS2552_VBAT_DATA + +/* CFG1 Register Masks */ +#define TAS2552_DEV_RESET (1 << 0) +#define TAS2552_SWS (1 << 1) +#define TAS2552_MUTE (1 << 2) +#define TAS2552_PLL_SRC_MCLK (0x0 << 4) +#define TAS2552_PLL_SRC_BCLK (0x1 << 4) +#define TAS2552_PLL_SRC_IVCLKIN (0x2 << 4) +#define TAS2552_PLL_SRC_1_8_FIXED (0x3 << 4) +#define TAS2552_PLL_SRC_MASK TAS2552_PLL_SRC_1_8_FIXED + +/* CFG2 Register Masks */ +#define TAS2552_CLASSD_EN (1 << 7) +#define TAS2552_BOOST_EN (1 << 6) +#define TAS2552_APT_EN (1 << 5) +#define TAS2552_PLL_ENABLE (1 << 3) +#define TAS2552_LIM_EN (1 << 2) +#define TAS2552_IVSENSE_EN (1 << 1) + +/* CFG3 Register Masks */ +#define TAS2552_WCLK_FREQ_8KHZ (0x0 << 0) +#define TAS2552_WCLK_FREQ_11_12KHZ (0x1 << 0) +#define TAS2552_WCLK_FREQ_16KHZ (0x2 << 0) +#define TAS2552_WCLK_FREQ_22_24KHZ (0x3 << 0) +#define TAS2552_WCLK_FREQ_32KHZ (0x4 << 0) +#define TAS2552_WCLK_FREQ_44_48KHZ (0x5 << 0) +#define TAS2552_WCLK_FREQ_88_96KHZ (0x6 << 0) +#define TAS2552_WCLK_FREQ_176_192KHZ (0x7 << 0) +#define TAS2552_WCLK_FREQ_MASK TAS2552_WCLK_FREQ_176_192KHZ +#define TAS2552_DIN_SRC_SEL_MUTED (0x0 << 3) +#define TAS2552_DIN_SRC_SEL_LEFT (0x1 << 3) +#define TAS2552_DIN_SRC_SEL_RIGHT (0x2 << 3) +#define TAS2552_DIN_SRC_SEL_AVG_L_R (0x3 << 3) +#define TAS2552_PDM_IN_SEL (1 << 5) +#define TAS2552_I2S_OUT_SEL (1 << 6) +#define TAS2552_ANALOG_IN_SEL (1 << 7) + +/* DOUT Register Masks */ +#define TAS2552_SDOUT_TRISTATE (1 << 2) + +/* Serial Interface Control Register Masks */ +#define TAS2552_WORDLENGTH_16BIT (0x0 << 0) +#define TAS2552_WORDLENGTH_20BIT (0x1 << 0) +#define TAS2552_WORDLENGTH_24BIT (0x2 << 0) +#define TAS2552_WORDLENGTH_32BIT (0x3 << 0) +#define TAS2552_WORDLENGTH_MASK TAS2552_WORDLENGTH_32BIT +#define TAS2552_DATAFORMAT_I2S (0x0 << 2) +#define TAS2552_DATAFORMAT_DSP (0x1 << 2) +#define TAS2552_DATAFORMAT_RIGHT_J (0x2 << 2) +#define TAS2552_DATAFORMAT_LEFT_J (0x3 << 2) +#define TAS2552_DATAFORMAT_MASK TAS2552_DATAFORMAT_LEFT_J +#define TAS2552_CLKSPERFRAME_32 (0x0 << 4) +#define TAS2552_CLKSPERFRAME_64 (0x1 << 4) +#define TAS2552_CLKSPERFRAME_128 (0x2 << 4) +#define TAS2552_CLKSPERFRAME_256 (0x3 << 4) +#define TAS2552_CLKSPERFRAME_MASK TAS2552_CLKSPERFRAME_256 +#define TAS2552_BCLKDIR (1 << 6) +#define TAS2552_WCLKDIR (1 << 7) + +/* OUTPUT_DATA register */ +#define TAS2552_DATA_OUT_I_DATA (0x0) +#define TAS2552_DATA_OUT_V_DATA (0x1) +#define TAS2552_DATA_OUT_VBAT_DATA (0x2) +#define TAS2552_DATA_OUT_VBOOST_DATA (0x3) +#define TAS2552_DATA_OUT_PGA_GAIN (0x4) +#define TAS2552_DATA_OUT_IV_DATA (0x5) +#define TAS2552_DATA_OUT_VBAT_VBOOST_GAIN (0x6) +#define TAS2552_DATA_OUT_DISABLED (0x7) +#define TAS2552_L_DATA_OUT(x) ((x) << 0) +#define TAS2552_R_DATA_OUT(x) ((x) << 3) +#define TAS2552_PDM_DATA_SEL_I (0x0 << 6) +#define TAS2552_PDM_DATA_SEL_V (0x1 << 6) +#define TAS2552_PDM_DATA_SEL_I_V (0x2 << 6) +#define TAS2552_PDM_DATA_SEL_V_I (0x3 << 6) +#define TAS2552_PDM_DATA_SEL_MASK TAS2552_PDM_DATA_SEL_V_I + +/* PDM CFG Register */ +#define TAS2552_PDM_CLK_SEL_PLL (0x0 << 0) +#define TAS2552_PDM_CLK_SEL_IVCLKIN (0x1 << 0) +#define TAS2552_PDM_CLK_SEL_BCLK (0x2 << 0) +#define TAS2552_PDM_CLK_SEL_MCLK (0x3 << 0) +#define TAS2552_PDM_CLK_SEL_MASK TAS2552_PDM_CLK_SEL_MCLK +#define TAS2552_PDM_DATA_ES (1 << 2) + +/* Boost Auto-pass through register */ +#define TAS2552_APT_DELAY_50 (0x0 << 0) +#define TAS2552_APT_DELAY_75 (0x1 << 0) +#define TAS2552_APT_DELAY_125 (0x2 << 0) +#define TAS2552_APT_DELAY_200 (0x3 << 0) +#define TAS2552_APT_THRESH_05_02 (0x0 << 2) +#define TAS2552_APT_THRESH_10_07 (0x1 << 2) +#define TAS2552_APT_THRESH_14_11 (0x2 << 2) +#define TAS2552_APT_THRESH_20_17 (0x3 << 2) + +/* PLL Control Register */ +#define TAS2552_PLL_J_MASK 0x7f +#define TAS2552_PLL_D_UPPER(x) (((x) >> 8) & 0x3f) +#define TAS2552_PLL_D_LOWER(x) ((x) & 0xff) +#define TAS2552_PLL_BYPASS (1 << 7) + +#endif diff --git a/sound/soc/codecs/tas5086.c b/sound/soc/codecs/tas5086.c new file mode 100644 index 000000000..5efc4b714 --- /dev/null +++ b/sound/soc/codecs/tas5086.c @@ -0,0 +1,1014 @@ +/* + * TAS5086 ASoC codec driver + * + * Copyright (c) 2013 Daniel Mack + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * TODO: + * - implement DAPM and input muxing + * - implement modulation limit + * - implement non-default PWM start + * + * Note that this chip has a very unusual register layout, specifically + * because the registers are of unequal size, and multi-byte registers + * require bulk writes to take effect. Regmap does not support that kind + * of devices. + * + * Currently, the driver does not touch any of the registers >= 0x20, so + * it doesn't matter because the entire map can be accessed as 8-bit + * array. In case more features will be added in the future + * that require access to higher registers, the entire regmap H/W I/O + * routines have to be open-coded. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAS5086_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE) + +#define TAS5086_PCM_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) + +/* + * TAS5086 registers + */ +#define TAS5086_CLOCK_CONTROL 0x00 /* Clock control register */ +#define TAS5086_CLOCK_RATE(val) (val << 5) +#define TAS5086_CLOCK_RATE_MASK (0x7 << 5) +#define TAS5086_CLOCK_RATIO(val) (val << 2) +#define TAS5086_CLOCK_RATIO_MASK (0x7 << 2) +#define TAS5086_CLOCK_SCLK_RATIO_48 (1 << 1) +#define TAS5086_CLOCK_VALID (1 << 0) + +#define TAS5086_DEEMPH_MASK 0x03 +#define TAS5086_SOFT_MUTE_ALL 0x3f + +#define TAS5086_DEV_ID 0x01 /* Device ID register */ +#define TAS5086_ERROR_STATUS 0x02 /* Error status register */ +#define TAS5086_SYS_CONTROL_1 0x03 /* System control register 1 */ +#define TAS5086_SERIAL_DATA_IF 0x04 /* Serial data interface register */ +#define TAS5086_SYS_CONTROL_2 0x05 /* System control register 2 */ +#define TAS5086_SOFT_MUTE 0x06 /* Soft mute register */ +#define TAS5086_MASTER_VOL 0x07 /* Master volume */ +#define TAS5086_CHANNEL_VOL(X) (0x08 + (X)) /* Channel 1-6 volume */ +#define TAS5086_VOLUME_CONTROL 0x09 /* Volume control register */ +#define TAS5086_MOD_LIMIT 0x10 /* Modulation limit register */ +#define TAS5086_PWM_START 0x18 /* PWM start register */ +#define TAS5086_SURROUND 0x19 /* Surround register */ +#define TAS5086_SPLIT_CAP_CHARGE 0x1a /* Split cap charge period register */ +#define TAS5086_OSC_TRIM 0x1b /* Oscillator trim register */ +#define TAS5086_BKNDERR 0x1c +#define TAS5086_INPUT_MUX 0x20 +#define TAS5086_PWM_OUTPUT_MUX 0x25 + +#define TAS5086_MAX_REGISTER TAS5086_PWM_OUTPUT_MUX + +#define TAS5086_PWM_START_MIDZ_FOR_START_1 (1 << 7) +#define TAS5086_PWM_START_MIDZ_FOR_START_2 (1 << 6) +#define TAS5086_PWM_START_CHANNEL_MASK (0x3f) + +/* + * Default TAS5086 power-up configuration + */ +static const struct reg_default tas5086_reg_defaults[] = { + { 0x00, 0x6c }, + { 0x01, 0x03 }, + { 0x02, 0x00 }, + { 0x03, 0xa0 }, + { 0x04, 0x05 }, + { 0x05, 0x60 }, + { 0x06, 0x00 }, + { 0x07, 0xff }, + { 0x08, 0x30 }, + { 0x09, 0x30 }, + { 0x0a, 0x30 }, + { 0x0b, 0x30 }, + { 0x0c, 0x30 }, + { 0x0d, 0x30 }, + { 0x0e, 0xb1 }, + { 0x0f, 0x00 }, + { 0x10, 0x02 }, + { 0x11, 0x00 }, + { 0x12, 0x00 }, + { 0x13, 0x00 }, + { 0x14, 0x00 }, + { 0x15, 0x00 }, + { 0x16, 0x00 }, + { 0x17, 0x00 }, + { 0x18, 0x3f }, + { 0x19, 0x00 }, + { 0x1a, 0x18 }, + { 0x1b, 0x82 }, + { 0x1c, 0x05 }, +}; + +static int tas5086_register_size(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS5086_CLOCK_CONTROL ... TAS5086_BKNDERR: + return 1; + case TAS5086_INPUT_MUX: + case TAS5086_PWM_OUTPUT_MUX: + return 4; + } + + dev_err(dev, "Unsupported register address: %d\n", reg); + return 0; +} + +static bool tas5086_accessible_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x0f: + case 0x11 ... 0x17: + case 0x1d ... 0x1f: + return false; + default: + return true; + } +} + +static bool tas5086_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS5086_DEV_ID: + case TAS5086_ERROR_STATUS: + return true; + } + + return false; +} + +static bool tas5086_writeable_reg(struct device *dev, unsigned int reg) +{ + return tas5086_accessible_reg(dev, reg) && (reg != TAS5086_DEV_ID); +} + +static int tas5086_reg_write(void *context, unsigned int reg, + unsigned int value) +{ + struct i2c_client *client = context; + unsigned int i, size; + uint8_t buf[5]; + int ret; + + size = tas5086_register_size(&client->dev, reg); + if (size == 0) + return -EINVAL; + + buf[0] = reg; + + for (i = size; i >= 1; --i) { + buf[i] = value; + value >>= 8; + } + + ret = i2c_master_send(client, buf, size + 1); + if (ret == size + 1) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static int tas5086_reg_read(void *context, unsigned int reg, + unsigned int *value) +{ + struct i2c_client *client = context; + uint8_t send_buf, recv_buf[4]; + struct i2c_msg msgs[2]; + unsigned int size; + unsigned int i; + int ret; + + size = tas5086_register_size(&client->dev, reg); + if (size == 0) + return -EINVAL; + + send_buf = reg; + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(send_buf); + msgs[0].buf = &send_buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = size; + msgs[1].buf = recv_buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + else if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *value = 0; + + for (i = 0; i < size; i++) { + *value <<= 8; + *value |= recv_buf[i]; + } + + return 0; +} + +static const char * const supply_names[] = { + "dvdd", "avdd" +}; + +struct tas5086_private { + struct regmap *regmap; + unsigned int mclk, sclk; + unsigned int format; + bool deemph; + unsigned int charge_period; + unsigned int pwm_start_mid_z; + /* Current sample rate for de-emphasis control */ + int rate; + /* GPIO driving Reset pin, if any */ + int gpio_nreset; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; +}; + +static int tas5086_deemph[] = { 0, 32000, 44100, 48000 }; + +static int tas5086_set_deemph(struct snd_soc_component *component) +{ + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + int i, val = 0; + + if (priv->deemph) { + for (i = 0; i < ARRAY_SIZE(tas5086_deemph); i++) { + if (tas5086_deemph[i] == priv->rate) { + val = i; + break; + } + } + } + + return regmap_update_bits(priv->regmap, TAS5086_SYS_CONTROL_1, + TAS5086_DEEMPH_MASK, val); +} + +static int tas5086_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = priv->deemph; + + return 0; +} + +static int tas5086_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + + priv->deemph = ucontrol->value.integer.value[0]; + + return tas5086_set_deemph(component); +} + + +static int tas5086_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case TAS5086_CLK_IDX_MCLK: + priv->mclk = freq; + break; + case TAS5086_CLK_IDX_SCLK: + priv->sclk = freq; + break; + } + + return 0; +} + +static int tas5086_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_component *component = codec_dai->component; + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + + /* The TAS5086 can only be slave to all clocks */ + if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_err(component->dev, "Invalid clocking mode\n"); + return -EINVAL; + } + + /* we need to refer to the data format from hw_params() */ + priv->format = format; + + return 0; +} + +static const int tas5086_sample_rates[] = { + 32000, 38000, 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const int tas5086_ratios[] = { + 64, 128, 192, 256, 384, 512 +}; + +static int index_in_array(const int *array, int len, int needle) +{ + int i; + + for (i = 0; i < len; i++) + if (array[i] == needle) + return i; + + return -ENOENT; +} + +static int tas5086_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + int val; + int ret; + + priv->rate = params_rate(params); + + /* Look up the sample rate and refer to the offset in the list */ + val = index_in_array(tas5086_sample_rates, + ARRAY_SIZE(tas5086_sample_rates), priv->rate); + + if (val < 0) { + dev_err(component->dev, "Invalid sample rate\n"); + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL, + TAS5086_CLOCK_RATE_MASK, + TAS5086_CLOCK_RATE(val)); + if (ret < 0) + return ret; + + /* MCLK / Fs ratio */ + val = index_in_array(tas5086_ratios, ARRAY_SIZE(tas5086_ratios), + priv->mclk / priv->rate); + if (val < 0) { + dev_err(component->dev, "Invalid MCLK / Fs ratio\n"); + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL, + TAS5086_CLOCK_RATIO_MASK, + TAS5086_CLOCK_RATIO(val)); + if (ret < 0) + return ret; + + + ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL, + TAS5086_CLOCK_SCLK_RATIO_48, + (priv->sclk == 48 * priv->rate) ? + TAS5086_CLOCK_SCLK_RATIO_48 : 0); + if (ret < 0) + return ret; + + /* + * The chip has a very unituitive register mapping and muxes information + * about data format and sample depth into the same register, but not on + * a logical bit-boundary. Hence, we have to refer to the format passed + * in the set_dai_fmt() callback and set up everything from here. + * + * First, determine the 'base' value, using the format ... + */ + switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = 0x00; + break; + case SND_SOC_DAIFMT_I2S: + val = 0x03; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = 0x06; + break; + default: + dev_err(component->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + /* ... then add the offset for the sample bit depth. */ + switch (params_width(params)) { + case 16: + val += 0; + break; + case 20: + val += 1; + break; + case 24: + val += 2; + break; + default: + dev_err(component->dev, "Invalid bit width\n"); + return -EINVAL; + } + + ret = regmap_write(priv->regmap, TAS5086_SERIAL_DATA_IF, val); + if (ret < 0) + return ret; + + /* clock is considered valid now */ + ret = regmap_update_bits(priv->regmap, TAS5086_CLOCK_CONTROL, + TAS5086_CLOCK_VALID, TAS5086_CLOCK_VALID); + if (ret < 0) + return ret; + + return tas5086_set_deemph(component); +} + +static int tas5086_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_component *component = dai->component; + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + + if (mute) + val = TAS5086_SOFT_MUTE_ALL; + + return regmap_write(priv->regmap, TAS5086_SOFT_MUTE, val); +} + +static void tas5086_reset(struct tas5086_private *priv) +{ + if (gpio_is_valid(priv->gpio_nreset)) { + /* Reset codec - minimum assertion time is 400ns */ + gpio_direction_output(priv->gpio_nreset, 0); + udelay(1); + gpio_set_value(priv->gpio_nreset, 1); + + /* Codec needs ~15ms to wake up */ + msleep(15); + } +} + +/* charge period values in microseconds */ +static const int tas5086_charge_period[] = { + 13000, 16900, 23400, 31200, 41600, 54600, 72800, 96200, + 130000, 156000, 234000, 312000, 416000, 546000, 728000, 962000, + 1300000, 169000, 2340000, 3120000, 4160000, 5460000, 7280000, 9620000, +}; + +static int tas5086_init(struct device *dev, struct tas5086_private *priv) +{ + int ret, i; + + /* + * If any of the channels is configured to start in Mid-Z mode, + * configure 'part 1' of the PWM starts to use Mid-Z, and tell + * all configured mid-z channels to start start under 'part 1'. + */ + if (priv->pwm_start_mid_z) + regmap_write(priv->regmap, TAS5086_PWM_START, + TAS5086_PWM_START_MIDZ_FOR_START_1 | + priv->pwm_start_mid_z); + + /* lookup and set split-capacitor charge period */ + if (priv->charge_period == 0) { + regmap_write(priv->regmap, TAS5086_SPLIT_CAP_CHARGE, 0); + } else { + i = index_in_array(tas5086_charge_period, + ARRAY_SIZE(tas5086_charge_period), + priv->charge_period); + if (i >= 0) + regmap_write(priv->regmap, TAS5086_SPLIT_CAP_CHARGE, + i + 0x08); + else + dev_warn(dev, + "Invalid split-cap charge period of %d ns.\n", + priv->charge_period); + } + + /* enable factory trim */ + ret = regmap_write(priv->regmap, TAS5086_OSC_TRIM, 0x00); + if (ret < 0) + return ret; + + /* start all channels */ + ret = regmap_write(priv->regmap, TAS5086_SYS_CONTROL_2, 0x20); + if (ret < 0) + return ret; + + /* mute all channels for now */ + ret = regmap_write(priv->regmap, TAS5086_SOFT_MUTE, + TAS5086_SOFT_MUTE_ALL); + if (ret < 0) + return ret; + + return 0; +} + +/* TAS5086 controls */ +static const DECLARE_TLV_DB_SCALE(tas5086_dac_tlv, -10350, 50, 1); + +static const struct snd_kcontrol_new tas5086_controls[] = { + SOC_SINGLE_TLV("Master Playback Volume", TAS5086_MASTER_VOL, + 0, 0xff, 1, tas5086_dac_tlv), + SOC_DOUBLE_R_TLV("Channel 1/2 Playback Volume", + TAS5086_CHANNEL_VOL(0), TAS5086_CHANNEL_VOL(1), + 0, 0xff, 1, tas5086_dac_tlv), + SOC_DOUBLE_R_TLV("Channel 3/4 Playback Volume", + TAS5086_CHANNEL_VOL(2), TAS5086_CHANNEL_VOL(3), + 0, 0xff, 1, tas5086_dac_tlv), + SOC_DOUBLE_R_TLV("Channel 5/6 Playback Volume", + TAS5086_CHANNEL_VOL(4), TAS5086_CHANNEL_VOL(5), + 0, 0xff, 1, tas5086_dac_tlv), + SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0, + tas5086_get_deemph, tas5086_put_deemph), +}; + +/* Input mux controls */ +static const char *tas5086_dapm_sdin_texts[] = +{ + "SDIN1-L", "SDIN1-R", "SDIN2-L", "SDIN2-R", + "SDIN3-L", "SDIN3-R", "Ground (0)", "nc" +}; + +static const struct soc_enum tas5086_dapm_input_mux_enum[] = { + SOC_ENUM_SINGLE(TAS5086_INPUT_MUX, 20, 8, tas5086_dapm_sdin_texts), + SOC_ENUM_SINGLE(TAS5086_INPUT_MUX, 16, 8, tas5086_dapm_sdin_texts), + SOC_ENUM_SINGLE(TAS5086_INPUT_MUX, 12, 8, tas5086_dapm_sdin_texts), + SOC_ENUM_SINGLE(TAS5086_INPUT_MUX, 8, 8, tas5086_dapm_sdin_texts), + SOC_ENUM_SINGLE(TAS5086_INPUT_MUX, 4, 8, tas5086_dapm_sdin_texts), + SOC_ENUM_SINGLE(TAS5086_INPUT_MUX, 0, 8, tas5086_dapm_sdin_texts), +}; + +static const struct snd_kcontrol_new tas5086_dapm_input_mux_controls[] = { + SOC_DAPM_ENUM("Channel 1 input", tas5086_dapm_input_mux_enum[0]), + SOC_DAPM_ENUM("Channel 2 input", tas5086_dapm_input_mux_enum[1]), + SOC_DAPM_ENUM("Channel 3 input", tas5086_dapm_input_mux_enum[2]), + SOC_DAPM_ENUM("Channel 4 input", tas5086_dapm_input_mux_enum[3]), + SOC_DAPM_ENUM("Channel 5 input", tas5086_dapm_input_mux_enum[4]), + SOC_DAPM_ENUM("Channel 6 input", tas5086_dapm_input_mux_enum[5]), +}; + +/* Output mux controls */ +static const char *tas5086_dapm_channel_texts[] = + { "Channel 1 Mux", "Channel 2 Mux", "Channel 3 Mux", + "Channel 4 Mux", "Channel 5 Mux", "Channel 6 Mux" }; + +static const struct soc_enum tas5086_dapm_output_mux_enum[] = { + SOC_ENUM_SINGLE(TAS5086_PWM_OUTPUT_MUX, 20, 6, tas5086_dapm_channel_texts), + SOC_ENUM_SINGLE(TAS5086_PWM_OUTPUT_MUX, 16, 6, tas5086_dapm_channel_texts), + SOC_ENUM_SINGLE(TAS5086_PWM_OUTPUT_MUX, 12, 6, tas5086_dapm_channel_texts), + SOC_ENUM_SINGLE(TAS5086_PWM_OUTPUT_MUX, 8, 6, tas5086_dapm_channel_texts), + SOC_ENUM_SINGLE(TAS5086_PWM_OUTPUT_MUX, 4, 6, tas5086_dapm_channel_texts), + SOC_ENUM_SINGLE(TAS5086_PWM_OUTPUT_MUX, 0, 6, tas5086_dapm_channel_texts), +}; + +static const struct snd_kcontrol_new tas5086_dapm_output_mux_controls[] = { + SOC_DAPM_ENUM("PWM1 Output", tas5086_dapm_output_mux_enum[0]), + SOC_DAPM_ENUM("PWM2 Output", tas5086_dapm_output_mux_enum[1]), + SOC_DAPM_ENUM("PWM3 Output", tas5086_dapm_output_mux_enum[2]), + SOC_DAPM_ENUM("PWM4 Output", tas5086_dapm_output_mux_enum[3]), + SOC_DAPM_ENUM("PWM5 Output", tas5086_dapm_output_mux_enum[4]), + SOC_DAPM_ENUM("PWM6 Output", tas5086_dapm_output_mux_enum[5]), +}; + +static const struct snd_soc_dapm_widget tas5086_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("SDIN1-L"), + SND_SOC_DAPM_INPUT("SDIN1-R"), + SND_SOC_DAPM_INPUT("SDIN2-L"), + SND_SOC_DAPM_INPUT("SDIN2-R"), + SND_SOC_DAPM_INPUT("SDIN3-L"), + SND_SOC_DAPM_INPUT("SDIN3-R"), + SND_SOC_DAPM_INPUT("SDIN4-L"), + SND_SOC_DAPM_INPUT("SDIN4-R"), + + SND_SOC_DAPM_OUTPUT("PWM1"), + SND_SOC_DAPM_OUTPUT("PWM2"), + SND_SOC_DAPM_OUTPUT("PWM3"), + SND_SOC_DAPM_OUTPUT("PWM4"), + SND_SOC_DAPM_OUTPUT("PWM5"), + SND_SOC_DAPM_OUTPUT("PWM6"), + + SND_SOC_DAPM_MUX("Channel 1 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_input_mux_controls[0]), + SND_SOC_DAPM_MUX("Channel 2 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_input_mux_controls[1]), + SND_SOC_DAPM_MUX("Channel 3 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_input_mux_controls[2]), + SND_SOC_DAPM_MUX("Channel 4 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_input_mux_controls[3]), + SND_SOC_DAPM_MUX("Channel 5 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_input_mux_controls[4]), + SND_SOC_DAPM_MUX("Channel 6 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_input_mux_controls[5]), + + SND_SOC_DAPM_MUX("PWM1 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_output_mux_controls[0]), + SND_SOC_DAPM_MUX("PWM2 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_output_mux_controls[1]), + SND_SOC_DAPM_MUX("PWM3 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_output_mux_controls[2]), + SND_SOC_DAPM_MUX("PWM4 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_output_mux_controls[3]), + SND_SOC_DAPM_MUX("PWM5 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_output_mux_controls[4]), + SND_SOC_DAPM_MUX("PWM6 Mux", SND_SOC_NOPM, 0, 0, + &tas5086_dapm_output_mux_controls[5]), +}; + +static const struct snd_soc_dapm_route tas5086_dapm_routes[] = { + /* SDIN inputs -> channel muxes */ + { "Channel 1 Mux", "SDIN1-L", "SDIN1-L" }, + { "Channel 1 Mux", "SDIN1-R", "SDIN1-R" }, + { "Channel 1 Mux", "SDIN2-L", "SDIN2-L" }, + { "Channel 1 Mux", "SDIN2-R", "SDIN2-R" }, + { "Channel 1 Mux", "SDIN3-L", "SDIN3-L" }, + { "Channel 1 Mux", "SDIN3-R", "SDIN3-R" }, + + { "Channel 2 Mux", "SDIN1-L", "SDIN1-L" }, + { "Channel 2 Mux", "SDIN1-R", "SDIN1-R" }, + { "Channel 2 Mux", "SDIN2-L", "SDIN2-L" }, + { "Channel 2 Mux", "SDIN2-R", "SDIN2-R" }, + { "Channel 2 Mux", "SDIN3-L", "SDIN3-L" }, + { "Channel 2 Mux", "SDIN3-R", "SDIN3-R" }, + + { "Channel 2 Mux", "SDIN1-L", "SDIN1-L" }, + { "Channel 2 Mux", "SDIN1-R", "SDIN1-R" }, + { "Channel 2 Mux", "SDIN2-L", "SDIN2-L" }, + { "Channel 2 Mux", "SDIN2-R", "SDIN2-R" }, + { "Channel 2 Mux", "SDIN3-L", "SDIN3-L" }, + { "Channel 2 Mux", "SDIN3-R", "SDIN3-R" }, + + { "Channel 3 Mux", "SDIN1-L", "SDIN1-L" }, + { "Channel 3 Mux", "SDIN1-R", "SDIN1-R" }, + { "Channel 3 Mux", "SDIN2-L", "SDIN2-L" }, + { "Channel 3 Mux", "SDIN2-R", "SDIN2-R" }, + { "Channel 3 Mux", "SDIN3-L", "SDIN3-L" }, + { "Channel 3 Mux", "SDIN3-R", "SDIN3-R" }, + + { "Channel 4 Mux", "SDIN1-L", "SDIN1-L" }, + { "Channel 4 Mux", "SDIN1-R", "SDIN1-R" }, + { "Channel 4 Mux", "SDIN2-L", "SDIN2-L" }, + { "Channel 4 Mux", "SDIN2-R", "SDIN2-R" }, + { "Channel 4 Mux", "SDIN3-L", "SDIN3-L" }, + { "Channel 4 Mux", "SDIN3-R", "SDIN3-R" }, + + { "Channel 5 Mux", "SDIN1-L", "SDIN1-L" }, + { "Channel 5 Mux", "SDIN1-R", "SDIN1-R" }, + { "Channel 5 Mux", "SDIN2-L", "SDIN2-L" }, + { "Channel 5 Mux", "SDIN2-R", "SDIN2-R" }, + { "Channel 5 Mux", "SDIN3-L", "SDIN3-L" }, + { "Channel 5 Mux", "SDIN3-R", "SDIN3-R" }, + + { "Channel 6 Mux", "SDIN1-L", "SDIN1-L" }, + { "Channel 6 Mux", "SDIN1-R", "SDIN1-R" }, + { "Channel 6 Mux", "SDIN2-L", "SDIN2-L" }, + { "Channel 6 Mux", "SDIN2-R", "SDIN2-R" }, + { "Channel 6 Mux", "SDIN3-L", "SDIN3-L" }, + { "Channel 6 Mux", "SDIN3-R", "SDIN3-R" }, + + /* Channel muxes -> PWM muxes */ + { "PWM1 Mux", "Channel 1 Mux", "Channel 1 Mux" }, + { "PWM2 Mux", "Channel 1 Mux", "Channel 1 Mux" }, + { "PWM3 Mux", "Channel 1 Mux", "Channel 1 Mux" }, + { "PWM4 Mux", "Channel 1 Mux", "Channel 1 Mux" }, + { "PWM5 Mux", "Channel 1 Mux", "Channel 1 Mux" }, + { "PWM6 Mux", "Channel 1 Mux", "Channel 1 Mux" }, + + { "PWM1 Mux", "Channel 2 Mux", "Channel 2 Mux" }, + { "PWM2 Mux", "Channel 2 Mux", "Channel 2 Mux" }, + { "PWM3 Mux", "Channel 2 Mux", "Channel 2 Mux" }, + { "PWM4 Mux", "Channel 2 Mux", "Channel 2 Mux" }, + { "PWM5 Mux", "Channel 2 Mux", "Channel 2 Mux" }, + { "PWM6 Mux", "Channel 2 Mux", "Channel 2 Mux" }, + + { "PWM1 Mux", "Channel 3 Mux", "Channel 3 Mux" }, + { "PWM2 Mux", "Channel 3 Mux", "Channel 3 Mux" }, + { "PWM3 Mux", "Channel 3 Mux", "Channel 3 Mux" }, + { "PWM4 Mux", "Channel 3 Mux", "Channel 3 Mux" }, + { "PWM5 Mux", "Channel 3 Mux", "Channel 3 Mux" }, + { "PWM6 Mux", "Channel 3 Mux", "Channel 3 Mux" }, + + { "PWM1 Mux", "Channel 4 Mux", "Channel 4 Mux" }, + { "PWM2 Mux", "Channel 4 Mux", "Channel 4 Mux" }, + { "PWM3 Mux", "Channel 4 Mux", "Channel 4 Mux" }, + { "PWM4 Mux", "Channel 4 Mux", "Channel 4 Mux" }, + { "PWM5 Mux", "Channel 4 Mux", "Channel 4 Mux" }, + { "PWM6 Mux", "Channel 4 Mux", "Channel 4 Mux" }, + + { "PWM1 Mux", "Channel 5 Mux", "Channel 5 Mux" }, + { "PWM2 Mux", "Channel 5 Mux", "Channel 5 Mux" }, + { "PWM3 Mux", "Channel 5 Mux", "Channel 5 Mux" }, + { "PWM4 Mux", "Channel 5 Mux", "Channel 5 Mux" }, + { "PWM5 Mux", "Channel 5 Mux", "Channel 5 Mux" }, + { "PWM6 Mux", "Channel 5 Mux", "Channel 5 Mux" }, + + { "PWM1 Mux", "Channel 6 Mux", "Channel 6 Mux" }, + { "PWM2 Mux", "Channel 6 Mux", "Channel 6 Mux" }, + { "PWM3 Mux", "Channel 6 Mux", "Channel 6 Mux" }, + { "PWM4 Mux", "Channel 6 Mux", "Channel 6 Mux" }, + { "PWM5 Mux", "Channel 6 Mux", "Channel 6 Mux" }, + { "PWM6 Mux", "Channel 6 Mux", "Channel 6 Mux" }, + + /* The PWM muxes are directly connected to the PWM outputs */ + { "PWM1", NULL, "PWM1 Mux" }, + { "PWM2", NULL, "PWM2 Mux" }, + { "PWM3", NULL, "PWM3 Mux" }, + { "PWM4", NULL, "PWM4 Mux" }, + { "PWM5", NULL, "PWM5 Mux" }, + { "PWM6", NULL, "PWM6 Mux" }, + +}; + +static const struct snd_soc_dai_ops tas5086_dai_ops = { + .hw_params = tas5086_hw_params, + .set_sysclk = tas5086_set_dai_sysclk, + .set_fmt = tas5086_set_dai_fmt, + .mute_stream = tas5086_mute_stream, +}; + +static struct snd_soc_dai_driver tas5086_dai = { + .name = "tas5086-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 6, + .rates = TAS5086_PCM_RATES, + .formats = TAS5086_PCM_FORMATS, + }, + .ops = &tas5086_dai_ops, +}; + +#ifdef CONFIG_PM +static int tas5086_soc_suspend(struct snd_soc_component *component) +{ + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + int ret; + + /* Shut down all channels */ + ret = regmap_write(priv->regmap, TAS5086_SYS_CONTROL_2, 0x60); + if (ret < 0) + return ret; + + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + + return 0; +} + +static int tas5086_soc_resume(struct snd_soc_component *component) +{ + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); + if (ret < 0) + return ret; + + tas5086_reset(priv); + regcache_mark_dirty(priv->regmap); + + ret = tas5086_init(component->dev, priv); + if (ret < 0) + return ret; + + ret = regcache_sync(priv->regmap); + if (ret < 0) + return ret; + + return 0; +} +#else +#define tas5086_soc_suspend NULL +#define tas5086_soc_resume NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_OF +static const struct of_device_id tas5086_dt_ids[] = { + { .compatible = "ti,tas5086", }, + { } +}; +MODULE_DEVICE_TABLE(of, tas5086_dt_ids); +#endif + +static int tas5086_probe(struct snd_soc_component *component) +{ + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + int i, ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); + if (ret < 0) { + dev_err(component->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + priv->pwm_start_mid_z = 0; + priv->charge_period = 1300000; /* hardware default is 1300 ms */ + + if (of_match_device(of_match_ptr(tas5086_dt_ids), component->dev)) { + struct device_node *of_node = component->dev->of_node; + + of_property_read_u32(of_node, "ti,charge-period", + &priv->charge_period); + + for (i = 0; i < 6; i++) { + char name[25]; + + snprintf(name, sizeof(name), + "ti,mid-z-channel-%d", i + 1); + + if (of_get_property(of_node, name, NULL) != NULL) + priv->pwm_start_mid_z |= 1 << i; + } + } + + tas5086_reset(priv); + ret = tas5086_init(component->dev, priv); + if (ret < 0) + goto exit_disable_regulators; + + /* set master volume to 0 dB */ + ret = regmap_write(priv->regmap, TAS5086_MASTER_VOL, 0x30); + if (ret < 0) + goto exit_disable_regulators; + + return 0; + +exit_disable_regulators: + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + + return ret; +} + +static void tas5086_remove(struct snd_soc_component *component) +{ + struct tas5086_private *priv = snd_soc_component_get_drvdata(component); + + if (gpio_is_valid(priv->gpio_nreset)) + /* Set codec to the reset state */ + gpio_set_value(priv->gpio_nreset, 0); + + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); +}; + +static const struct snd_soc_component_driver soc_component_dev_tas5086 = { + .probe = tas5086_probe, + .remove = tas5086_remove, + .suspend = tas5086_soc_suspend, + .resume = tas5086_soc_resume, + .controls = tas5086_controls, + .num_controls = ARRAY_SIZE(tas5086_controls), + .dapm_widgets = tas5086_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas5086_dapm_widgets), + .dapm_routes = tas5086_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tas5086_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct i2c_device_id tas5086_i2c_id[] = { + { "tas5086", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas5086_i2c_id); + +static const struct regmap_config tas5086_regmap = { + .reg_bits = 8, + .val_bits = 32, + .max_register = TAS5086_MAX_REGISTER, + .reg_defaults = tas5086_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5086_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tas5086_volatile_reg, + .writeable_reg = tas5086_writeable_reg, + .readable_reg = tas5086_accessible_reg, + .reg_read = tas5086_reg_read, + .reg_write = tas5086_reg_write, +}; + +static int tas5086_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tas5086_private *priv; + struct device *dev = &i2c->dev; + int gpio_nreset = -EINVAL; + int i, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + priv->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret < 0) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + priv->regmap = devm_regmap_init(dev, NULL, i2c, &tas5086_regmap); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&i2c->dev, "Failed to create regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, priv); + + if (of_match_device(of_match_ptr(tas5086_dt_ids), dev)) { + struct device_node *of_node = dev->of_node; + gpio_nreset = of_get_named_gpio(of_node, "reset-gpio", 0); + } + + if (gpio_is_valid(gpio_nreset)) + if (devm_gpio_request(dev, gpio_nreset, "TAS5086 Reset")) + gpio_nreset = -EINVAL; + + priv->gpio_nreset = gpio_nreset; + + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + tas5086_reset(priv); + + /* The TAS5086 always returns 0x03 in its TAS5086_DEV_ID register */ + ret = regmap_read(priv->regmap, TAS5086_DEV_ID, &i); + if (ret == 0 && i != 0x3) { + dev_err(dev, + "Failed to identify TAS5086 codec (got %02x)\n", i); + ret = -ENODEV; + } + + /* + * The chip has been identified, so we can turn off the power + * again until the dai link is set up. + */ + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies); + + if (ret == 0) + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_tas5086, + &tas5086_dai, 1); + + return ret; +} + +static int tas5086_i2c_remove(struct i2c_client *i2c) +{ + return 0; +} + +static struct i2c_driver tas5086_i2c_driver = { + .driver = { + .name = "tas5086", + .of_match_table = of_match_ptr(tas5086_dt_ids), + }, + .id_table = tas5086_i2c_id, + .probe = tas5086_i2c_probe, + .remove = tas5086_i2c_remove, +}; + +module_i2c_driver(tas5086_i2c_driver); + +MODULE_AUTHOR("Daniel Mack "); +MODULE_DESCRIPTION("Texas Instruments TAS5086 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c new file mode 100644 index 000000000..354f17a2f --- /dev/null +++ b/sound/soc/codecs/tas571x.c @@ -0,0 +1,928 @@ +/* + * TAS571x amplifier audio driver + * + * Copyright (C) 2015 Google, Inc. + * Copyright (c) 2013 Daniel Mack + * + * TAS5721 support: + * Copyright (C) 2016 Petr Kulhavy, Barix AG + * + * TAS5707 support: + * Copyright (C) 2018 Jerome Brunet, Baylibre SAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tas571x.h" + +#define TAS571X_MAX_SUPPLIES 6 + +struct tas571x_chip { + const char *const *supply_names; + int num_supply_names; + const struct snd_kcontrol_new *controls; + int num_controls; + const struct regmap_config *regmap_config; + int vol_reg_size; +}; + +struct tas571x_private { + const struct tas571x_chip *chip; + struct regmap *regmap; + struct regulator_bulk_data supplies[TAS571X_MAX_SUPPLIES]; + struct clk *mclk; + unsigned int format; + struct gpio_desc *reset_gpio; + struct gpio_desc *pdn_gpio; + struct snd_soc_component_driver component_driver; +}; + +static int tas571x_register_size(struct tas571x_private *priv, unsigned int reg) +{ + switch (reg) { + case TAS571X_MVOL_REG: + case TAS571X_CH1_VOL_REG: + case TAS571X_CH2_VOL_REG: + return priv->chip->vol_reg_size; + case TAS571X_INPUT_MUX_REG: + case TAS571X_CH4_SRC_SELECT_REG: + case TAS571X_PWM_MUX_REG: + case TAS5717_CH1_RIGHT_CH_MIX_REG: + case TAS5717_CH1_LEFT_CH_MIX_REG: + case TAS5717_CH2_LEFT_CH_MIX_REG: + case TAS5717_CH2_RIGHT_CH_MIX_REG: + return 4; + default: + return 1; + } +} + +static int tas571x_reg_write(void *context, unsigned int reg, + unsigned int value) +{ + struct i2c_client *client = context; + struct tas571x_private *priv = i2c_get_clientdata(client); + unsigned int i, size; + uint8_t buf[5]; + int ret; + + size = tas571x_register_size(priv, reg); + buf[0] = reg; + + for (i = size; i >= 1; --i) { + buf[i] = value; + value >>= 8; + } + + ret = i2c_master_send(client, buf, size + 1); + if (ret == size + 1) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static int tas571x_reg_read(void *context, unsigned int reg, + unsigned int *value) +{ + struct i2c_client *client = context; + struct tas571x_private *priv = i2c_get_clientdata(client); + uint8_t send_buf, recv_buf[4]; + struct i2c_msg msgs[2]; + unsigned int size; + unsigned int i; + int ret; + + size = tas571x_register_size(priv, reg); + send_buf = reg; + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(send_buf); + msgs[0].buf = &send_buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = size; + msgs[1].buf = recv_buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + else if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *value = 0; + + for (i = 0; i < size; i++) { + *value <<= 8; + *value |= recv_buf[i]; + } + + return 0; +} + +/* + * register write for 8- and 20-byte registers + */ +static int tas571x_reg_write_multiword(struct i2c_client *client, + unsigned int reg, const long values[], size_t len) +{ + size_t i; + uint8_t *buf, *p; + int ret; + size_t send_size = 1 + len * sizeof(uint32_t); + + buf = kzalloc(send_size, GFP_KERNEL | GFP_DMA); + if (!buf) + return -ENOMEM; + buf[0] = reg; + + for (i = 0, p = buf + 1; i < len; i++, p += sizeof(uint32_t)) + put_unaligned_be32(values[i], p); + + ret = i2c_master_send(client, buf, send_size); + + kfree(buf); + + if (ret == send_size) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +/* + * register read for 8- and 20-byte registers + */ +static int tas571x_reg_read_multiword(struct i2c_client *client, + unsigned int reg, long values[], size_t len) +{ + unsigned int i; + uint8_t send_buf; + uint8_t *recv_buf, *p; + struct i2c_msg msgs[2]; + unsigned int recv_size = len * sizeof(uint32_t); + int ret; + + recv_buf = kzalloc(recv_size, GFP_KERNEL | GFP_DMA); + if (!recv_buf) + return -ENOMEM; + + send_buf = reg; + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(send_buf); + msgs[0].buf = &send_buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = recv_size; + msgs[1].buf = recv_buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + goto err_ret; + else if (ret != ARRAY_SIZE(msgs)) { + ret = -EIO; + goto err_ret; + } + + for (i = 0, p = recv_buf; i < len; i++, p += sizeof(uint32_t)) + values[i] = get_unaligned_be32(p); + +err_ret: + kfree(recv_buf); + return ret; +} + +/* + * Integer array controls for setting biquad, mixer, DRC coefficients. + * According to the datasheet each coefficient is effectively 26bits, + * i.e. stored as 32bits, where bits [31:26] are ignored. + * TI's TAS57xx Graphical Development Environment tool however produces + * coefficients with more than 26 bits. For this reason we allow values + * in the full 32-bits reange. + * The coefficients are ordered as given in the TAS571x data sheet: + * b0, b1, b2, a1, a2 + */ + +static int tas571x_coefficient_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int numcoef = kcontrol->private_value >> 16; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = numcoef; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xffffffff; + return 0; +} + +static int tas571x_coefficient_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct i2c_client *i2c = to_i2c_client(component->dev); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + + return tas571x_reg_read_multiword(i2c, index, + ucontrol->value.integer.value, numcoef); +} + +static int tas571x_coefficient_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct i2c_client *i2c = to_i2c_client(component->dev); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + + return tas571x_reg_write_multiword(i2c, index, + ucontrol->value.integer.value, numcoef); +} + +static int tas571x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format) +{ + struct tas571x_private *priv = snd_soc_component_get_drvdata(dai->component); + + priv->format = format; + + return 0; +} + +static int tas571x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tas571x_private *priv = snd_soc_component_get_drvdata(dai->component); + u32 val; + + switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = 0x00; + break; + case SND_SOC_DAIFMT_I2S: + val = 0x03; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = 0x06; + break; + default: + return -EINVAL; + } + + if (params_width(params) >= 24) + val += 2; + else if (params_width(params) >= 20) + val += 1; + + return regmap_update_bits(priv->regmap, TAS571X_SDI_REG, + TAS571X_SDI_FMT_MASK, val); +} + +static int tas571x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u8 sysctl2; + int ret; + + sysctl2 = mute ? TAS571X_SYS_CTRL_2_SDN_MASK : 0; + + ret = snd_soc_component_update_bits(component, + TAS571X_SYS_CTRL_2_REG, + TAS571X_SYS_CTRL_2_SDN_MASK, + sysctl2); + usleep_range(1000, 2000); + + return ret; +} + +static int tas571x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct tas571x_private *priv = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + if (!IS_ERR(priv->mclk)) { + ret = clk_prepare_enable(priv->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable master clock: %d\n", + ret); + return ret; + } + } + } + break; + case SND_SOC_BIAS_OFF: + if (!IS_ERR(priv->mclk)) + clk_disable_unprepare(priv->mclk); + break; + } + + return 0; +} + +static const struct snd_soc_dai_ops tas571x_dai_ops = { + .set_fmt = tas571x_set_dai_fmt, + .hw_params = tas571x_hw_params, + .digital_mute = tas571x_mute, +}; + + +#define BIQUAD_COEFS(xname, reg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = tas571x_coefficient_info, \ + .get = tas571x_coefficient_get,\ + .put = tas571x_coefficient_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .private_value = reg | (5 << 16) } + +static const char *const tas5711_supply_names[] = { + "AVDD", + "DVDD", + "PVDD_A", + "PVDD_B", + "PVDD_C", + "PVDD_D", +}; + +static const DECLARE_TLV_DB_SCALE(tas5711_volume_tlv, -10350, 50, 1); + +static const struct snd_kcontrol_new tas5711_controls[] = { + SOC_SINGLE_TLV("Master Volume", + TAS571X_MVOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE_R_TLV("Speaker Volume", + TAS571X_CH1_VOL_REG, + TAS571X_CH2_VOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE("Speaker Switch", + TAS571X_SOFT_MUTE_REG, + TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, + 1, 1), +}; + +static const struct regmap_range tas571x_readonly_regs_range[] = { + regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_DEV_ID_REG), +}; + +static const struct regmap_range tas571x_volatile_regs_range[] = { + regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_ERR_STATUS_REG), + regmap_reg_range(TAS571X_OSC_TRIM_REG, TAS571X_OSC_TRIM_REG), +}; + +static const struct regmap_access_table tas571x_write_regs = { + .no_ranges = tas571x_readonly_regs_range, + .n_no_ranges = ARRAY_SIZE(tas571x_readonly_regs_range), +}; + +static const struct regmap_access_table tas571x_volatile_regs = { + .yes_ranges = tas571x_volatile_regs_range, + .n_yes_ranges = ARRAY_SIZE(tas571x_volatile_regs_range), + +}; + +static const struct reg_default tas5711_reg_defaults[] = { + { 0x04, 0x05 }, + { 0x05, 0x40 }, + { 0x06, 0x00 }, + { 0x07, 0xff }, + { 0x08, 0x30 }, + { 0x09, 0x30 }, + { 0x1b, 0x82 }, +}; + +static const struct regmap_config tas5711_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = 0xff, + .reg_read = tas571x_reg_read, + .reg_write = tas571x_reg_write, + .reg_defaults = tas5711_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5711_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, +}; + +static const struct tas571x_chip tas5711_chip = { + .supply_names = tas5711_supply_names, + .num_supply_names = ARRAY_SIZE(tas5711_supply_names), + .controls = tas5711_controls, + .num_controls = ARRAY_SIZE(tas5711_controls), + .regmap_config = &tas5711_regmap_config, + .vol_reg_size = 1, +}; + +static const struct regmap_range tas5707_volatile_regs_range[] = { + regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_ERR_STATUS_REG), + regmap_reg_range(TAS571X_OSC_TRIM_REG, TAS571X_OSC_TRIM_REG), + regmap_reg_range(TAS5707_CH1_BQ0_REG, TAS5707_CH2_BQ6_REG), +}; + +static const struct regmap_access_table tas5707_volatile_regs = { + .yes_ranges = tas5707_volatile_regs_range, + .n_yes_ranges = ARRAY_SIZE(tas5707_volatile_regs_range), + +}; + +static const DECLARE_TLV_DB_SCALE(tas5707_volume_tlv, -7900, 50, 1); + +static const char * const tas5707_volume_slew_step_txt[] = { + "256", "512", "1024", "2048", +}; + +static const unsigned int tas5707_volume_slew_step_values[] = { + 3, 0, 1, 2, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(tas5707_volume_slew_step_enum, + TAS571X_VOL_CFG_REG, 0, 0x3, + tas5707_volume_slew_step_txt, + tas5707_volume_slew_step_values); + +static const struct snd_kcontrol_new tas5707_controls[] = { + SOC_SINGLE_TLV("Master Volume", + TAS571X_MVOL_REG, + 0, 0xff, 1, tas5707_volume_tlv), + SOC_DOUBLE_R_TLV("Speaker Volume", + TAS571X_CH1_VOL_REG, + TAS571X_CH2_VOL_REG, + 0, 0xff, 1, tas5707_volume_tlv), + SOC_DOUBLE("Speaker Switch", + TAS571X_SOFT_MUTE_REG, + TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, + 1, 1), + + SOC_ENUM("Slew Rate Steps", tas5707_volume_slew_step_enum), + + BIQUAD_COEFS("CH1 - Biquad 0", TAS5707_CH1_BQ0_REG), + BIQUAD_COEFS("CH1 - Biquad 1", TAS5707_CH1_BQ1_REG), + BIQUAD_COEFS("CH1 - Biquad 2", TAS5707_CH1_BQ2_REG), + BIQUAD_COEFS("CH1 - Biquad 3", TAS5707_CH1_BQ3_REG), + BIQUAD_COEFS("CH1 - Biquad 4", TAS5707_CH1_BQ4_REG), + BIQUAD_COEFS("CH1 - Biquad 5", TAS5707_CH1_BQ5_REG), + BIQUAD_COEFS("CH1 - Biquad 6", TAS5707_CH1_BQ6_REG), + + BIQUAD_COEFS("CH2 - Biquad 0", TAS5707_CH2_BQ0_REG), + BIQUAD_COEFS("CH2 - Biquad 1", TAS5707_CH2_BQ1_REG), + BIQUAD_COEFS("CH2 - Biquad 2", TAS5707_CH2_BQ2_REG), + BIQUAD_COEFS("CH2 - Biquad 3", TAS5707_CH2_BQ3_REG), + BIQUAD_COEFS("CH2 - Biquad 4", TAS5707_CH2_BQ4_REG), + BIQUAD_COEFS("CH2 - Biquad 5", TAS5707_CH2_BQ5_REG), + BIQUAD_COEFS("CH2 - Biquad 6", TAS5707_CH2_BQ6_REG), +}; + +static const struct reg_default tas5707_reg_defaults[] = { + {TAS571X_CLK_CTRL_REG, 0x6c}, + {TAS571X_DEV_ID_REG, 0x70}, + {TAS571X_ERR_STATUS_REG, 0x00}, + {TAS571X_SYS_CTRL_1_REG, 0xa0}, + {TAS571X_SDI_REG, 0x05}, + {TAS571X_SYS_CTRL_2_REG, 0x40}, + {TAS571X_SOFT_MUTE_REG, 0x00}, + {TAS571X_MVOL_REG, 0xff}, + {TAS571X_CH1_VOL_REG, 0x30}, + {TAS571X_CH2_VOL_REG, 0x30}, + {TAS571X_VOL_CFG_REG, 0x91}, + {TAS571X_MODULATION_LIMIT_REG, 0x02}, + {TAS571X_IC_DELAY_CH1_REG, 0xac}, + {TAS571X_IC_DELAY_CH2_REG, 0x54}, + {TAS571X_IC_DELAY_CH3_REG, 0xac}, + {TAS571X_IC_DELAY_CH4_REG, 0x54}, + {TAS571X_START_STOP_PERIOD_REG, 0x0f}, + {TAS571X_OSC_TRIM_REG, 0x82}, + {TAS571X_BKND_ERR_REG, 0x02}, + {TAS571X_INPUT_MUX_REG, 0x17772}, + {TAS571X_PWM_MUX_REG, 0x1021345}, +}; + +static const struct regmap_config tas5707_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = 0xff, + .reg_read = tas571x_reg_read, + .reg_write = tas571x_reg_write, + .reg_defaults = tas5707_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5707_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas5707_volatile_regs, +}; + +static const struct tas571x_chip tas5707_chip = { + .supply_names = tas5711_supply_names, + .num_supply_names = ARRAY_SIZE(tas5711_supply_names), + .controls = tas5707_controls, + .num_controls = ARRAY_SIZE(tas5707_controls), + .regmap_config = &tas5707_regmap_config, + .vol_reg_size = 1, +}; + +static const char *const tas5717_supply_names[] = { + "AVDD", + "DVDD", + "HPVDD", + "PVDD_AB", + "PVDD_CD", +}; + +static const DECLARE_TLV_DB_SCALE(tas5717_volume_tlv, -10375, 25, 0); + +static const struct snd_kcontrol_new tas5717_controls[] = { + /* MVOL LSB is ignored - see comments in tas571x_i2c_probe() */ + SOC_SINGLE_TLV("Master Volume", + TAS571X_MVOL_REG, 1, 0x1ff, 1, + tas5717_volume_tlv), + SOC_DOUBLE_R_TLV("Speaker Volume", + TAS571X_CH1_VOL_REG, TAS571X_CH2_VOL_REG, + 1, 0x1ff, 1, tas5717_volume_tlv), + SOC_DOUBLE("Speaker Switch", + TAS571X_SOFT_MUTE_REG, + TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, + 1, 1), + + SOC_DOUBLE_R_RANGE("CH1 Mixer Volume", + TAS5717_CH1_LEFT_CH_MIX_REG, + TAS5717_CH1_RIGHT_CH_MIX_REG, + 16, 0, 0x80, 0), + + SOC_DOUBLE_R_RANGE("CH2 Mixer Volume", + TAS5717_CH2_LEFT_CH_MIX_REG, + TAS5717_CH2_RIGHT_CH_MIX_REG, + 16, 0, 0x80, 0), + + /* + * The biquads are named according to the register names. + * Please note that TI's TAS57xx Graphical Development Environment + * tool names them different. + */ + BIQUAD_COEFS("CH1 - Biquad 0", TAS5717_CH1_BQ0_REG), + BIQUAD_COEFS("CH1 - Biquad 1", TAS5717_CH1_BQ1_REG), + BIQUAD_COEFS("CH1 - Biquad 2", TAS5717_CH1_BQ2_REG), + BIQUAD_COEFS("CH1 - Biquad 3", TAS5717_CH1_BQ3_REG), + BIQUAD_COEFS("CH1 - Biquad 4", TAS5717_CH1_BQ4_REG), + BIQUAD_COEFS("CH1 - Biquad 5", TAS5717_CH1_BQ5_REG), + BIQUAD_COEFS("CH1 - Biquad 6", TAS5717_CH1_BQ6_REG), + BIQUAD_COEFS("CH1 - Biquad 7", TAS5717_CH1_BQ7_REG), + BIQUAD_COEFS("CH1 - Biquad 8", TAS5717_CH1_BQ8_REG), + BIQUAD_COEFS("CH1 - Biquad 9", TAS5717_CH1_BQ9_REG), + BIQUAD_COEFS("CH1 - Biquad 10", TAS5717_CH1_BQ10_REG), + BIQUAD_COEFS("CH1 - Biquad 11", TAS5717_CH1_BQ11_REG), + + BIQUAD_COEFS("CH2 - Biquad 0", TAS5717_CH2_BQ0_REG), + BIQUAD_COEFS("CH2 - Biquad 1", TAS5717_CH2_BQ1_REG), + BIQUAD_COEFS("CH2 - Biquad 2", TAS5717_CH2_BQ2_REG), + BIQUAD_COEFS("CH2 - Biquad 3", TAS5717_CH2_BQ3_REG), + BIQUAD_COEFS("CH2 - Biquad 4", TAS5717_CH2_BQ4_REG), + BIQUAD_COEFS("CH2 - Biquad 5", TAS5717_CH2_BQ5_REG), + BIQUAD_COEFS("CH2 - Biquad 6", TAS5717_CH2_BQ6_REG), + BIQUAD_COEFS("CH2 - Biquad 7", TAS5717_CH2_BQ7_REG), + BIQUAD_COEFS("CH2 - Biquad 8", TAS5717_CH2_BQ8_REG), + BIQUAD_COEFS("CH2 - Biquad 9", TAS5717_CH2_BQ9_REG), + BIQUAD_COEFS("CH2 - Biquad 10", TAS5717_CH2_BQ10_REG), + BIQUAD_COEFS("CH2 - Biquad 11", TAS5717_CH2_BQ11_REG), + + BIQUAD_COEFS("CH3 - Biquad 0", TAS5717_CH3_BQ0_REG), + BIQUAD_COEFS("CH3 - Biquad 1", TAS5717_CH3_BQ1_REG), + + BIQUAD_COEFS("CH4 - Biquad 0", TAS5717_CH4_BQ0_REG), + BIQUAD_COEFS("CH4 - Biquad 1", TAS5717_CH4_BQ1_REG), +}; + +static const struct reg_default tas5717_reg_defaults[] = { + { 0x04, 0x05 }, + { 0x05, 0x40 }, + { 0x06, 0x00 }, + { 0x07, 0x03ff }, + { 0x08, 0x00c0 }, + { 0x09, 0x00c0 }, + { 0x1b, 0x82 }, + { TAS5717_CH1_RIGHT_CH_MIX_REG, 0x0 }, + { TAS5717_CH1_LEFT_CH_MIX_REG, 0x800000}, + { TAS5717_CH2_LEFT_CH_MIX_REG, 0x0 }, + { TAS5717_CH2_RIGHT_CH_MIX_REG, 0x800000}, +}; + +static const struct regmap_config tas5717_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = 0xff, + .reg_read = tas571x_reg_read, + .reg_write = tas571x_reg_write, + .reg_defaults = tas5717_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5717_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, +}; + +/* This entry is reused for tas5719 as the software interface is identical. */ +static const struct tas571x_chip tas5717_chip = { + .supply_names = tas5717_supply_names, + .num_supply_names = ARRAY_SIZE(tas5717_supply_names), + .controls = tas5717_controls, + .num_controls = ARRAY_SIZE(tas5717_controls), + .regmap_config = &tas5717_regmap_config, + .vol_reg_size = 2, +}; + +static const char *const tas5721_supply_names[] = { + "AVDD", + "DVDD", + "DRVDD", + "PVDD", +}; + +static const struct snd_kcontrol_new tas5721_controls[] = { + SOC_SINGLE_TLV("Master Volume", + TAS571X_MVOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE_R_TLV("Speaker Volume", + TAS571X_CH1_VOL_REG, + TAS571X_CH2_VOL_REG, + 0, 0xff, 1, tas5711_volume_tlv), + SOC_DOUBLE("Speaker Switch", + TAS571X_SOFT_MUTE_REG, + TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, + 1, 1), +}; + +static const struct reg_default tas5721_reg_defaults[] = { + {TAS571X_CLK_CTRL_REG, 0x6c}, + {TAS571X_DEV_ID_REG, 0x00}, + {TAS571X_ERR_STATUS_REG, 0x00}, + {TAS571X_SYS_CTRL_1_REG, 0xa0}, + {TAS571X_SDI_REG, 0x05}, + {TAS571X_SYS_CTRL_2_REG, 0x40}, + {TAS571X_SOFT_MUTE_REG, 0x00}, + {TAS571X_MVOL_REG, 0xff}, + {TAS571X_CH1_VOL_REG, 0x30}, + {TAS571X_CH2_VOL_REG, 0x30}, + {TAS571X_CH3_VOL_REG, 0x30}, + {TAS571X_VOL_CFG_REG, 0x91}, + {TAS571X_MODULATION_LIMIT_REG, 0x02}, + {TAS571X_IC_DELAY_CH1_REG, 0xac}, + {TAS571X_IC_DELAY_CH2_REG, 0x54}, + {TAS571X_IC_DELAY_CH3_REG, 0xac}, + {TAS571X_IC_DELAY_CH4_REG, 0x54}, + {TAS571X_PWM_CH_SDN_GROUP_REG, 0x30}, + {TAS571X_START_STOP_PERIOD_REG, 0x0f}, + {TAS571X_OSC_TRIM_REG, 0x82}, + {TAS571X_BKND_ERR_REG, 0x02}, + {TAS571X_INPUT_MUX_REG, 0x17772}, + {TAS571X_CH4_SRC_SELECT_REG, 0x4303}, + {TAS571X_PWM_MUX_REG, 0x1021345}, +}; + +static const struct regmap_config tas5721_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = 0xff, + .reg_read = tas571x_reg_read, + .reg_write = tas571x_reg_write, + .reg_defaults = tas5721_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5721_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, +}; + + +static const struct tas571x_chip tas5721_chip = { + .supply_names = tas5721_supply_names, + .num_supply_names = ARRAY_SIZE(tas5721_supply_names), + .controls = tas5711_controls, + .num_controls = ARRAY_SIZE(tas5711_controls), + .regmap_config = &tas5721_regmap_config, + .vol_reg_size = 1, +}; + +static const struct snd_soc_dapm_widget tas571x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DACL", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DACR", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_OUTPUT("OUT_A"), + SND_SOC_DAPM_OUTPUT("OUT_B"), + SND_SOC_DAPM_OUTPUT("OUT_C"), + SND_SOC_DAPM_OUTPUT("OUT_D"), +}; + +static const struct snd_soc_dapm_route tas571x_dapm_routes[] = { + { "DACL", NULL, "Playback" }, + { "DACR", NULL, "Playback" }, + + { "OUT_A", NULL, "DACL" }, + { "OUT_B", NULL, "DACL" }, + { "OUT_C", NULL, "DACR" }, + { "OUT_D", NULL, "DACR" }, +}; + +static const struct snd_soc_component_driver tas571x_component = { + .set_bias_level = tas571x_set_bias_level, + .dapm_widgets = tas571x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas571x_dapm_widgets), + .dapm_routes = tas571x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tas571x_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver tas571x_dai = { + .name = "tas571x-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tas571x_dai_ops, +}; + +static const struct of_device_id tas571x_of_match[]; + +static int tas571x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tas571x_private *priv; + struct device *dev = &client->dev; + const struct of_device_id *of_id; + int i, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + i2c_set_clientdata(client, priv); + + of_id = of_match_device(tas571x_of_match, dev); + if (of_id) + priv->chip = of_id->data; + else + priv->chip = (void *) id->driver_data; + + priv->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(priv->mclk) && PTR_ERR(priv->mclk) != -ENOENT) { + dev_err(dev, "Failed to request mclk: %ld\n", + PTR_ERR(priv->mclk)); + return PTR_ERR(priv->mclk); + } + + if (WARN_ON(priv->chip->num_supply_names > TAS571X_MAX_SUPPLIES)) + return -EINVAL; + for (i = 0; i < priv->chip->num_supply_names; i++) + priv->supplies[i].supply = priv->chip->supply_names[i]; + + ret = devm_regulator_bulk_get(dev, priv->chip->num_supply_names, + priv->supplies); + if (ret) { + dev_err(dev, "Failed to get supplies: %d\n", ret); + return ret; + } + ret = regulator_bulk_enable(priv->chip->num_supply_names, + priv->supplies); + if (ret) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + priv->regmap = devm_regmap_init(dev, NULL, client, + priv->chip->regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + goto disable_regs; + } + + priv->pdn_gpio = devm_gpiod_get_optional(dev, "pdn", GPIOD_OUT_LOW); + if (IS_ERR(priv->pdn_gpio)) { + dev_err(dev, "error requesting pdn_gpio: %ld\n", + PTR_ERR(priv->pdn_gpio)); + return PTR_ERR(priv->pdn_gpio); + } + + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) { + dev_err(dev, "error requesting reset_gpio: %ld\n", + PTR_ERR(priv->reset_gpio)); + return PTR_ERR(priv->reset_gpio); + } else if (priv->reset_gpio) { + /* pulse the active low reset line for ~100us */ + usleep_range(100, 200); + gpiod_set_value(priv->reset_gpio, 0); + usleep_range(13500, 20000); + } + + ret = regmap_write(priv->regmap, TAS571X_OSC_TRIM_REG, 0); + if (ret) + goto disable_regs; + + usleep_range(50000, 60000); + + memcpy(&priv->component_driver, &tas571x_component, sizeof(priv->component_driver)); + priv->component_driver.controls = priv->chip->controls; + priv->component_driver.num_controls = priv->chip->num_controls; + + if (priv->chip->vol_reg_size == 2) { + /* + * The master volume defaults to 0x3ff (mute), but we ignore + * (zero) the LSB because the hardware step size is 0.125 dB + * and TLV_DB_SCALE_ITEM has a resolution of 0.01 dB. + */ + ret = regmap_update_bits(priv->regmap, TAS571X_MVOL_REG, 1, 0); + if (ret) + goto disable_regs; + } + + ret = devm_snd_soc_register_component(&client->dev, + &priv->component_driver, + &tas571x_dai, 1); + if (ret) + goto disable_regs; + + return ret; + +disable_regs: + regulator_bulk_disable(priv->chip->num_supply_names, priv->supplies); + return ret; +} + +static int tas571x_i2c_remove(struct i2c_client *client) +{ + struct tas571x_private *priv = i2c_get_clientdata(client); + + regulator_bulk_disable(priv->chip->num_supply_names, priv->supplies); + + return 0; +} + +static const struct of_device_id tas571x_of_match[] = { + { .compatible = "ti,tas5707", .data = &tas5707_chip, }, + { .compatible = "ti,tas5711", .data = &tas5711_chip, }, + { .compatible = "ti,tas5717", .data = &tas5717_chip, }, + { .compatible = "ti,tas5719", .data = &tas5717_chip, }, + { .compatible = "ti,tas5721", .data = &tas5721_chip, }, + { } +}; +MODULE_DEVICE_TABLE(of, tas571x_of_match); + +static const struct i2c_device_id tas571x_i2c_id[] = { + { "tas5707", (kernel_ulong_t) &tas5707_chip }, + { "tas5711", (kernel_ulong_t) &tas5711_chip }, + { "tas5717", (kernel_ulong_t) &tas5717_chip }, + { "tas5719", (kernel_ulong_t) &tas5717_chip }, + { "tas5721", (kernel_ulong_t) &tas5721_chip }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id); + +static struct i2c_driver tas571x_i2c_driver = { + .driver = { + .name = "tas571x", + .of_match_table = of_match_ptr(tas571x_of_match), + }, + .probe = tas571x_i2c_probe, + .remove = tas571x_i2c_remove, + .id_table = tas571x_i2c_id, +}; +module_i2c_driver(tas571x_i2c_driver); + +MODULE_DESCRIPTION("ASoC TAS571x driver"); +MODULE_AUTHOR("Kevin Cernekee "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas571x.h b/sound/soc/codecs/tas571x.h new file mode 100644 index 000000000..bd23e89cf --- /dev/null +++ b/sound/soc/codecs/tas571x.h @@ -0,0 +1,111 @@ +/* + * TAS571x amplifier audio driver + * + * Copyright (C) 2015 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _TAS571X_H +#define _TAS571X_H + +/* device registers */ +#define TAS571X_CLK_CTRL_REG 0x00 +#define TAS571X_DEV_ID_REG 0x01 +#define TAS571X_ERR_STATUS_REG 0x02 +#define TAS571X_SYS_CTRL_1_REG 0x03 +#define TAS571X_SDI_REG 0x04 +#define TAS571X_SDI_FMT_MASK 0x0f + +#define TAS571X_SYS_CTRL_2_REG 0x05 +#define TAS571X_SYS_CTRL_2_SDN_MASK 0x40 + +#define TAS571X_SOFT_MUTE_REG 0x06 +#define TAS571X_SOFT_MUTE_CH1_SHIFT 0 +#define TAS571X_SOFT_MUTE_CH2_SHIFT 1 +#define TAS571X_SOFT_MUTE_CH3_SHIFT 2 + +#define TAS571X_MVOL_REG 0x07 +#define TAS571X_CH1_VOL_REG 0x08 +#define TAS571X_CH2_VOL_REG 0x09 +#define TAS571X_CH3_VOL_REG 0x0a +#define TAS571X_VOL_CFG_REG 0x0e +#define TAS571X_MODULATION_LIMIT_REG 0x10 +#define TAS571X_IC_DELAY_CH1_REG 0x11 +#define TAS571X_IC_DELAY_CH2_REG 0x12 +#define TAS571X_IC_DELAY_CH3_REG 0x13 +#define TAS571X_IC_DELAY_CH4_REG 0x14 + +#define TAS571X_PWM_CH_SDN_GROUP_REG 0x19 /* N/A on TAS5717, TAS5719 */ +#define TAS571X_PWM_CH1_SDN_MASK (1<<0) +#define TAS571X_PWM_CH2_SDN_SHIFT (1<<1) +#define TAS571X_PWM_CH3_SDN_SHIFT (1<<2) +#define TAS571X_PWM_CH4_SDN_SHIFT (1<<3) + +#define TAS571X_START_STOP_PERIOD_REG 0x1a +#define TAS571X_OSC_TRIM_REG 0x1b +#define TAS571X_BKND_ERR_REG 0x1c +#define TAS571X_INPUT_MUX_REG 0x20 +#define TAS571X_CH4_SRC_SELECT_REG 0x21 +#define TAS571X_PWM_MUX_REG 0x25 + +/* 20-byte biquad registers */ +#define TAS5707_CH1_BQ0_REG 0x29 +#define TAS5707_CH1_BQ1_REG 0x2a +#define TAS5707_CH1_BQ2_REG 0x2b +#define TAS5707_CH1_BQ3_REG 0x2c +#define TAS5707_CH1_BQ4_REG 0x2d +#define TAS5707_CH1_BQ5_REG 0x2e +#define TAS5707_CH1_BQ6_REG 0x2f + +#define TAS5707_CH2_BQ0_REG 0x30 +#define TAS5707_CH2_BQ1_REG 0x31 +#define TAS5707_CH2_BQ2_REG 0x32 +#define TAS5707_CH2_BQ3_REG 0x33 +#define TAS5707_CH2_BQ4_REG 0x34 +#define TAS5707_CH2_BQ5_REG 0x35 +#define TAS5707_CH2_BQ6_REG 0x36 + +#define TAS5717_CH1_BQ0_REG 0x26 +#define TAS5717_CH1_BQ1_REG 0x27 +#define TAS5717_CH1_BQ2_REG 0x28 +#define TAS5717_CH1_BQ3_REG 0x29 +#define TAS5717_CH1_BQ4_REG 0x2a +#define TAS5717_CH1_BQ5_REG 0x2b +#define TAS5717_CH1_BQ6_REG 0x2c +#define TAS5717_CH1_BQ7_REG 0x2d +#define TAS5717_CH1_BQ8_REG 0x2e +#define TAS5717_CH1_BQ9_REG 0x2f + +#define TAS5717_CH2_BQ0_REG 0x30 +#define TAS5717_CH2_BQ1_REG 0x31 +#define TAS5717_CH2_BQ2_REG 0x32 +#define TAS5717_CH2_BQ3_REG 0x33 +#define TAS5717_CH2_BQ4_REG 0x34 +#define TAS5717_CH2_BQ5_REG 0x35 +#define TAS5717_CH2_BQ6_REG 0x36 +#define TAS5717_CH2_BQ7_REG 0x37 +#define TAS5717_CH2_BQ8_REG 0x38 +#define TAS5717_CH2_BQ9_REG 0x39 + +#define TAS5717_CH1_BQ10_REG 0x58 +#define TAS5717_CH1_BQ11_REG 0x59 + +#define TAS5717_CH4_BQ0_REG 0x5a +#define TAS5717_CH4_BQ1_REG 0x5b + +#define TAS5717_CH2_BQ10_REG 0x5c +#define TAS5717_CH2_BQ11_REG 0x5d + +#define TAS5717_CH3_BQ0_REG 0x5e +#define TAS5717_CH3_BQ1_REG 0x5f + +#define TAS5717_CH1_RIGHT_CH_MIX_REG 0x72 +#define TAS5717_CH1_LEFT_CH_MIX_REG 0x73 +#define TAS5717_CH2_LEFT_CH_MIX_REG 0x76 +#define TAS5717_CH2_RIGHT_CH_MIX_REG 0x77 + +#endif /* _TAS571X_H */ diff --git a/sound/soc/codecs/tas5720.c b/sound/soc/codecs/tas5720.c new file mode 100644 index 000000000..ae3d032ac --- /dev/null +++ b/sound/soc/codecs/tas5720.c @@ -0,0 +1,656 @@ +/* + * tas5720.c - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier + * + * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tas5720.h" + +/* Define how often to check (and clear) the fault status register (in ms) */ +#define TAS5720_FAULT_CHECK_INTERVAL 200 + +enum tas572x_type { + TAS5720, + TAS5722, +}; + +static const char * const tas5720_supply_names[] = { + "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ + "pvdd", /* Class-D amp and analog power supply (connected). */ +}; + +#define TAS5720_NUM_SUPPLIES ARRAY_SIZE(tas5720_supply_names) + +struct tas5720_data { + struct snd_soc_component *component; + struct regmap *regmap; + struct i2c_client *tas5720_client; + enum tas572x_type devtype; + struct regulator_bulk_data supplies[TAS5720_NUM_SUPPLIES]; + struct delayed_work fault_check_work; + unsigned int last_fault; +}; + +static int tas5720_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + unsigned int rate = params_rate(params); + bool ssz_ds; + int ret; + + switch (rate) { + case 44100: + case 48000: + ssz_ds = false; + break; + case 88200: + case 96000: + ssz_ds = true; + break; + default: + dev_err(component->dev, "unsupported sample rate: %u\n", rate); + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_SSZ_DS, ssz_ds); + if (ret < 0) { + dev_err(component->dev, "error setting sample rate: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u8 serial_format; + int ret; + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + dev_vdbg(component->dev, "DAI Format master is not found\n"); + return -EINVAL; + } + + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_INV_MASK)) { + case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): + /* 1st data bit occur one BCLK cycle after the frame sync */ + serial_format = TAS5720_SAIF_I2S; + break; + case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF): + /* + * Note that although the TAS5720 does not have a dedicated DSP + * mode it doesn't care about the LRCLK duty cycle during TDM + * operation. Therefore we can use the device's I2S mode with + * its delaying of the 1st data bit to receive DSP_A formatted + * data. See device datasheet for additional details. + */ + serial_format = TAS5720_SAIF_I2S; + break; + case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF): + /* + * Similar to DSP_A, we can use the fact that the TAS5720 does + * not care about the LRCLK duty cycle during TDM to receive + * DSP_B formatted data in LEFTJ mode (no delaying of the 1st + * data bit). + */ + serial_format = TAS5720_SAIF_LEFTJ; + break; + case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): + /* No delay after the frame sync */ + serial_format = TAS5720_SAIF_LEFTJ; + break; + default: + dev_vdbg(component->dev, "DAI Format is not found\n"); + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_SAIF_FORMAT_MASK, + serial_format); + if (ret < 0) { + dev_err(component->dev, "error setting SAIF format: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas5720_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int first_slot; + int ret; + + if (!tx_mask) { + dev_err(component->dev, "tx masks must not be 0\n"); + return -EINVAL; + } + + /* + * Determine the first slot that is being requested. We will only + * use the first slot that is found since the TAS5720 is a mono + * amplifier. + */ + first_slot = __ffs(tx_mask); + + if (first_slot > 7) { + dev_err(component->dev, "slot selection out of bounds (%u)\n", + first_slot); + return -EINVAL; + } + + /* Enable manual TDM slot selection (instead of I2C ID based) */ + ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, + TAS5720_TDM_CFG_SRC, TAS5720_TDM_CFG_SRC); + if (ret < 0) + goto error_snd_soc_component_update_bits; + + /* Configure the TDM slot to process audio from */ + ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_TDM_SLOT_SEL_MASK, first_slot); + if (ret < 0) + goto error_snd_soc_component_update_bits; + + return 0; + +error_snd_soc_component_update_bits: + dev_err(component->dev, "error configuring TDM mode: %d\n", ret); + return ret; +} + +static int tas5720_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + int ret; + + ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_MUTE, mute ? TAS5720_MUTE : 0); + if (ret < 0) { + dev_err(component->dev, "error (un-)muting device: %d\n", ret); + return ret; + } + + return 0; +} + +static void tas5720_fault_check_work(struct work_struct *work) +{ + struct tas5720_data *tas5720 = container_of(work, struct tas5720_data, + fault_check_work.work); + struct device *dev = tas5720->component->dev; + unsigned int curr_fault; + int ret; + + ret = regmap_read(tas5720->regmap, TAS5720_FAULT_REG, &curr_fault); + if (ret < 0) { + dev_err(dev, "failed to read FAULT register: %d\n", ret); + goto out; + } + + /* Check/handle all errors except SAIF clock errors */ + curr_fault &= TAS5720_OCE | TAS5720_DCE | TAS5720_OTE; + + /* + * Only flag errors once for a given occurrence. This is needed as + * the TAS5720 will take time clearing the fault condition internally + * during which we don't want to bombard the system with the same + * error message over and over. + */ + if ((curr_fault & TAS5720_OCE) && !(tas5720->last_fault & TAS5720_OCE)) + dev_crit(dev, "experienced an over current hardware fault\n"); + + if ((curr_fault & TAS5720_DCE) && !(tas5720->last_fault & TAS5720_DCE)) + dev_crit(dev, "experienced a DC detection fault\n"); + + if ((curr_fault & TAS5720_OTE) && !(tas5720->last_fault & TAS5720_OTE)) + dev_crit(dev, "experienced an over temperature fault\n"); + + /* Store current fault value so we can detect any changes next time */ + tas5720->last_fault = curr_fault; + + if (!curr_fault) + goto out; + + /* + * Periodically toggle SDZ (shutdown bit) H->L->H to clear any latching + * faults as long as a fault condition persists. Always going through + * the full sequence no matter the first return value to minimizes + * chances for the device to end up in shutdown mode. + */ + ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) + dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); + + ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, TAS5720_SDZ); + if (ret < 0) + dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); + +out: + /* Schedule the next fault check at the specified interval */ + schedule_delayed_work(&tas5720->fault_check_work, + msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); +} + +static int tas5720_codec_probe(struct snd_soc_component *component) +{ + struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); + unsigned int device_id, expected_device_id; + int ret; + + tas5720->component = component; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret != 0) { + dev_err(component->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + /* + * Take a liberal approach to checking the device ID to allow the + * driver to be used even if the device ID does not match, however + * issue a warning if there is a mismatch. + */ + ret = regmap_read(tas5720->regmap, TAS5720_DEVICE_ID_REG, &device_id); + if (ret < 0) { + dev_err(component->dev, "failed to read device ID register: %d\n", + ret); + goto probe_fail; + } + + switch (tas5720->devtype) { + case TAS5720: + expected_device_id = TAS5720_DEVICE_ID; + break; + case TAS5722: + expected_device_id = TAS5722_DEVICE_ID; + break; + default: + dev_err(component->dev, "unexpected private driver data\n"); + return -EINVAL; + } + + if (device_id != expected_device_id) + dev_warn(component->dev, "wrong device ID. expected: %u read: %u\n", + expected_device_id, device_id); + + /* Set device to mute */ + ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL2_REG, + TAS5720_MUTE, TAS5720_MUTE); + if (ret < 0) + goto error_snd_soc_component_update_bits; + + /* + * Enter shutdown mode - our default when not playing audio - to + * minimize current consumption. On the TAS5720 there is no real down + * side doing so as all device registers are preserved and the wakeup + * of the codec is rather quick which we do using a dapm widget. + */ + ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) + goto error_snd_soc_component_update_bits; + + INIT_DELAYED_WORK(&tas5720->fault_check_work, tas5720_fault_check_work); + + return 0; + +error_snd_soc_component_update_bits: + dev_err(component->dev, "error configuring device registers: %d\n", ret); + +probe_fail: + regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + return ret; +} + +static void tas5720_codec_remove(struct snd_soc_component *component) +{ + struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); + int ret; + + cancel_delayed_work_sync(&tas5720->fault_check_work); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) + dev_err(component->dev, "failed to disable supplies: %d\n", ret); +}; + +static int tas5720_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); + int ret; + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Take TAS5720 out of shutdown mode */ + ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, TAS5720_SDZ); + if (ret < 0) { + dev_err(component->dev, "error waking component: %d\n", ret); + return ret; + } + + /* + * Observe codec shutdown-to-active time. The datasheet only + * lists a nominal value however just use-it as-is without + * additional padding to minimize the delay introduced in + * starting to play audio (actually there is other setup done + * by the ASoC framework that will provide additional delays, + * so we should always be safe). + */ + msleep(25); + + /* Turn on TAS5720 periodic fault checking/handling */ + tas5720->last_fault = 0; + schedule_delayed_work(&tas5720->fault_check_work, + msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); + } else if (event & SND_SOC_DAPM_PRE_PMD) { + /* Disable TAS5720 periodic fault checking/handling */ + cancel_delayed_work_sync(&tas5720->fault_check_work); + + /* Place TAS5720 in shutdown mode to minimize current draw */ + ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, + TAS5720_SDZ, 0); + if (ret < 0) { + dev_err(component->dev, "error shutting down component: %d\n", + ret); + return ret; + } + } + + return 0; +} + +#ifdef CONFIG_PM +static int tas5720_suspend(struct snd_soc_component *component) +{ + struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); + int ret; + + regcache_cache_only(tas5720->regmap, true); + regcache_mark_dirty(tas5720->regmap); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) + dev_err(component->dev, "failed to disable supplies: %d\n", ret); + + return ret; +} + +static int tas5720_resume(struct snd_soc_component *component) +{ + struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), + tas5720->supplies); + if (ret < 0) { + dev_err(component->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(tas5720->regmap, false); + + ret = regcache_sync(tas5720->regmap); + if (ret < 0) { + dev_err(component->dev, "failed to sync regcache: %d\n", ret); + return ret; + } + + return 0; +} +#else +#define tas5720_suspend NULL +#define tas5720_resume NULL +#endif + +static bool tas5720_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS5720_DEVICE_ID_REG: + case TAS5720_FAULT_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config tas5720_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TAS5720_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tas5720_is_volatile_reg, +}; + +static const struct regmap_config tas5722_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = TAS5722_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tas5720_is_volatile_reg, +}; + +/* + * DAC analog gain. There are four discrete values to select from, ranging + * from 19.2 dB to 26.3dB. + */ +static const DECLARE_TLV_DB_RANGE(dac_analog_tlv, + 0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0), + 0x1, 0x1, TLV_DB_SCALE_ITEM(2070, 0, 0), + 0x2, 0x2, TLV_DB_SCALE_ITEM(2350, 0, 0), + 0x3, 0x3, TLV_DB_SCALE_ITEM(2630, 0, 0), +); + +/* + * DAC digital volumes. From -103.5 to 24 dB in 0.5 dB steps. Note that + * setting the gain below -100 dB (register value <0x7) is effectively a MUTE + * as per device datasheet. + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -10350, 50, 0); + +static const struct snd_kcontrol_new tas5720_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Driver Playback Volume", + TAS5720_VOLUME_CTRL_REG, 0, 0xff, 0, dac_tlv), + SOC_SINGLE_TLV("Speaker Driver Analog Gain", TAS5720_ANALOG_CTRL_REG, + TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), +}; + +static const struct snd_soc_dapm_widget tas5720_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas5720_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUTPUT("OUT") +}; + +static const struct snd_soc_dapm_route tas5720_audio_map[] = { + { "DAC", NULL, "DAC IN" }, + { "OUT", NULL, "DAC" }, +}; + +static const struct snd_soc_component_driver soc_component_dev_tas5720 = { + .probe = tas5720_codec_probe, + .remove = tas5720_codec_remove, + .suspend = tas5720_suspend, + .resume = tas5720_resume, + .controls = tas5720_snd_controls, + .num_controls = ARRAY_SIZE(tas5720_snd_controls), + .dapm_widgets = tas5720_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), + .dapm_routes = tas5720_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* PCM rates supported by the TAS5720 driver */ +#define TAS5720_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +/* Formats supported by TAS5720 driver */ +#define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops tas5720_speaker_dai_ops = { + .hw_params = tas5720_hw_params, + .set_fmt = tas5720_set_dai_fmt, + .set_tdm_slot = tas5720_set_dai_tdm_slot, + .digital_mute = tas5720_mute, +}; + +/* + * TAS5720 DAI structure + * + * Note that were are advertising .playback.channels_max = 2 despite this being + * a mono amplifier. The reason for that is that some serial ports such as TI's + * McASP module have a minimum number of channels (2) that they can output. + * Advertising more channels than we have will allow us to interface with such + * a serial port without really any negative side effects as the TAS5720 will + * simply ignore any extra channel(s) asides from the one channel that is + * configured to be played back. + */ +static struct snd_soc_dai_driver tas5720_dai[] = { + { + .name = "tas5720-amplifier", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TAS5720_RATES, + .formats = TAS5720_FORMATS, + }, + .ops = &tas5720_speaker_dai_ops, + }, +}; + +static int tas5720_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tas5720_data *data; + const struct regmap_config *regmap_config; + int ret; + int i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->tas5720_client = client; + data->devtype = id->driver_data; + + switch (id->driver_data) { + case TAS5720: + regmap_config = &tas5720_regmap_config; + break; + case TAS5722: + regmap_config = &tas5722_regmap_config; + break; + default: + dev_err(dev, "unexpected private driver data\n"); + return -EINVAL; + } + data->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(dev, "failed to allocate register map: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data->supplies); i++) + data->supplies[i].supply = tas5720_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), + data->supplies); + if (ret != 0) { + dev_err(dev, "failed to request supplies: %d\n", ret); + return ret; + } + + dev_set_drvdata(dev, data); + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_dev_tas5720, + tas5720_dai, ARRAY_SIZE(tas5720_dai)); + if (ret < 0) { + dev_err(dev, "failed to register component: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct i2c_device_id tas5720_id[] = { + { "tas5720", TAS5720 }, + { "tas5722", TAS5722 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas5720_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tas5720_of_match[] = { + { .compatible = "ti,tas5720", }, + { .compatible = "ti,tas5722", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tas5720_of_match); +#endif + +static struct i2c_driver tas5720_i2c_driver = { + .driver = { + .name = "tas5720", + .of_match_table = of_match_ptr(tas5720_of_match), + }, + .probe = tas5720_probe, + .id_table = tas5720_id, +}; + +module_i2c_driver(tas5720_i2c_driver); + +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_DESCRIPTION("TAS5720 Audio amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas5720.h b/sound/soc/codecs/tas5720.h new file mode 100644 index 000000000..1dda30959 --- /dev/null +++ b/sound/soc/codecs/tas5720.h @@ -0,0 +1,121 @@ +/* + * tas5720.h - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier + * + * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Andreas Dannenberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __TAS5720_H__ +#define __TAS5720_H__ + +/* Register Address Map */ +#define TAS5720_DEVICE_ID_REG 0x00 +#define TAS5720_POWER_CTRL_REG 0x01 +#define TAS5720_DIGITAL_CTRL1_REG 0x02 +#define TAS5720_DIGITAL_CTRL2_REG 0x03 +#define TAS5720_VOLUME_CTRL_REG 0x04 +#define TAS5720_ANALOG_CTRL_REG 0x06 +#define TAS5720_FAULT_REG 0x08 +#define TAS5720_DIGITAL_CLIP2_REG 0x10 +#define TAS5720_DIGITAL_CLIP1_REG 0x11 +#define TAS5720_MAX_REG TAS5720_DIGITAL_CLIP1_REG + +/* Additional TAS5722-specific Registers */ +#define TAS5722_DIGITAL_CTRL2_REG 0x13 +#define TAS5722_ANALOG_CTRL2_REG 0x14 +#define TAS5722_MAX_REG TAS5722_ANALOG_CTRL2_REG + +/* TAS5720_DEVICE_ID_REG */ +#define TAS5720_DEVICE_ID 0x01 +#define TAS5722_DEVICE_ID 0x12 + +/* TAS5720_POWER_CTRL_REG */ +#define TAS5720_DIG_CLIP_MASK GENMASK(7, 2) +#define TAS5720_SLEEP BIT(1) +#define TAS5720_SDZ BIT(0) + +/* TAS5720_DIGITAL_CTRL1_REG */ +#define TAS5720_HPF_BYPASS BIT(7) +#define TAS5720_TDM_CFG_SRC BIT(6) +#define TAS5720_SSZ_DS BIT(3) +#define TAS5720_SAIF_RIGHTJ_24BIT (0x0) +#define TAS5720_SAIF_RIGHTJ_20BIT (0x1) +#define TAS5720_SAIF_RIGHTJ_18BIT (0x2) +#define TAS5720_SAIF_RIGHTJ_16BIT (0x3) +#define TAS5720_SAIF_I2S (0x4) +#define TAS5720_SAIF_LEFTJ (0x5) +#define TAS5720_SAIF_FORMAT_MASK GENMASK(2, 0) + +/* TAS5720_DIGITAL_CTRL2_REG */ +#define TAS5722_VOL_RAMP_RATE BIT(6) +#define TAS5720_MUTE BIT(4) +#define TAS5720_TDM_SLOT_SEL_MASK GENMASK(2, 0) + +/* TAS5720_ANALOG_CTRL_REG */ +#define TAS5720_PWM_RATE_6_3_FSYNC (0x0 << 4) +#define TAS5720_PWM_RATE_8_4_FSYNC (0x1 << 4) +#define TAS5720_PWM_RATE_10_5_FSYNC (0x2 << 4) +#define TAS5720_PWM_RATE_12_6_FSYNC (0x3 << 4) +#define TAS5720_PWM_RATE_14_7_FSYNC (0x4 << 4) +#define TAS5720_PWM_RATE_16_8_FSYNC (0x5 << 4) +#define TAS5720_PWM_RATE_20_10_FSYNC (0x6 << 4) +#define TAS5720_PWM_RATE_24_12_FSYNC (0x7 << 4) +#define TAS5720_PWM_RATE_MASK GENMASK(6, 4) +#define TAS5720_ANALOG_GAIN_19_2DBV (0x0 << 2) +#define TAS5720_ANALOG_GAIN_20_7DBV (0x1 << 2) +#define TAS5720_ANALOG_GAIN_23_5DBV (0x2 << 2) +#define TAS5720_ANALOG_GAIN_26_3DBV (0x3 << 2) +#define TAS5720_ANALOG_GAIN_MASK GENMASK(3, 2) +#define TAS5720_ANALOG_GAIN_SHIFT (0x2) + +/* TAS5720_FAULT_REG */ +#define TAS5720_OC_THRESH_100PCT (0x0 << 4) +#define TAS5720_OC_THRESH_75PCT (0x1 << 4) +#define TAS5720_OC_THRESH_50PCT (0x2 << 4) +#define TAS5720_OC_THRESH_25PCT (0x3 << 4) +#define TAS5720_OC_THRESH_MASK GENMASK(5, 4) +#define TAS5720_CLKE BIT(3) +#define TAS5720_OCE BIT(2) +#define TAS5720_DCE BIT(1) +#define TAS5720_OTE BIT(0) +#define TAS5720_FAULT_MASK GENMASK(3, 0) + +/* TAS5720_DIGITAL_CLIP1_REG */ +#define TAS5720_CLIP1_MASK GENMASK(7, 2) +#define TAS5720_CLIP1_SHIFT (0x2) + +/* TAS5722_DIGITAL_CTRL2_REG */ +#define TAS5722_HPF_3_7HZ (0x0 << 5) +#define TAS5722_HPF_7_4HZ (0x1 << 5) +#define TAS5722_HPF_14_9HZ (0x2 << 5) +#define TAS5722_HPF_29_7HZ (0x3 << 5) +#define TAS5722_HPF_59_4HZ (0x4 << 5) +#define TAS5722_HPF_118_4HZ (0x5 << 5) +#define TAS5722_HPF_235_0HZ (0x6 << 5) +#define TAS5722_HPF_463_2HZ (0x7 << 5) +#define TAS5722_HPF_MASK GENMASK(7, 5) +#define TAS5722_AUTO_SLEEP_OFF (0x0 << 3) +#define TAS5722_AUTO_SLEEP_1024LR (0x1 << 3) +#define TAS5722_AUTO_SLEEP_65536LR (0x2 << 3) +#define TAS5722_AUTO_SLEEP_262144LR (0x3 << 3) +#define TAS5722_AUTO_SLEEP_MASK GENMASK(4, 3) +#define TAS5722_TDM_SLOT_16B BIT(2) +#define TAS5722_MCLK_PIN_CFG BIT(1) +#define TAS5722_VOL_CONTROL_LSB BIT(0) + +/* TAS5722_ANALOG_CTRL2_REG */ +#define TAS5722_FAULTZ_PU BIT(3) +#define TAS5722_VREG_LVL BIT(2) +#define TAS5722_PWR_TUNE BIT(0) + +#endif /* __TAS5720_H__ */ diff --git a/sound/soc/codecs/tas6424.c b/sound/soc/codecs/tas6424.c new file mode 100644 index 000000000..0d6145549 --- /dev/null +++ b/sound/soc/codecs/tas6424.c @@ -0,0 +1,776 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC Texas Instruments TAS6424 Quad-Channel Audio Amplifier + * + * Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Andreas Dannenberg + * Andrew F. Davis + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tas6424.h" + +/* Define how often to check (and clear) the fault status register (in ms) */ +#define TAS6424_FAULT_CHECK_INTERVAL 200 + +static const char * const tas6424_supply_names[] = { + "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ + "vbat", /* Supply used for higher voltage analog circuits. */ + "pvdd", /* Class-D amp output FETs supply. */ +}; +#define TAS6424_NUM_SUPPLIES ARRAY_SIZE(tas6424_supply_names) + +struct tas6424_data { + struct device *dev; + struct regmap *regmap; + struct regulator_bulk_data supplies[TAS6424_NUM_SUPPLIES]; + struct delayed_work fault_check_work; + unsigned int last_fault1; + unsigned int last_fault2; + unsigned int last_warn; + struct gpio_desc *standby_gpio; + struct gpio_desc *mute_gpio; +}; + +/* + * DAC digital volumes. From -103.5 to 24 dB in 0.5 dB steps. Note that + * setting the gain below -100 dB (register value <0x7) is effectively a MUTE + * as per device datasheet. + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -10350, 50, 0); + +static const struct snd_kcontrol_new tas6424_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Driver CH1 Playback Volume", + TAS6424_CH1_VOL_CTRL, 0, 0xff, 0, dac_tlv), + SOC_SINGLE_TLV("Speaker Driver CH2 Playback Volume", + TAS6424_CH2_VOL_CTRL, 0, 0xff, 0, dac_tlv), + SOC_SINGLE_TLV("Speaker Driver CH3 Playback Volume", + TAS6424_CH3_VOL_CTRL, 0, 0xff, 0, dac_tlv), + SOC_SINGLE_TLV("Speaker Driver CH4 Playback Volume", + TAS6424_CH4_VOL_CTRL, 0, 0xff, 0, dac_tlv), + SOC_SINGLE_STROBE("Auto Diagnostics Switch", TAS6424_DC_DIAG_CTRL1, + TAS6424_LDGBYPASS_SHIFT, 1), +}; + +static int tas6424_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct tas6424_data *tas6424 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s() event=0x%0x\n", __func__, event); + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Observe codec shutdown-to-active time */ + msleep(12); + + /* Turn on TAS6424 periodic fault checking/handling */ + tas6424->last_fault1 = 0; + tas6424->last_fault2 = 0; + tas6424->last_warn = 0; + schedule_delayed_work(&tas6424->fault_check_work, + msecs_to_jiffies(TAS6424_FAULT_CHECK_INTERVAL)); + } else if (event & SND_SOC_DAPM_PRE_PMD) { + /* Disable TAS6424 periodic fault checking/handling */ + cancel_delayed_work_sync(&tas6424->fault_check_work); + } + + return 0; +} + +static const struct snd_soc_dapm_widget tas6424_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas6424_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUTPUT("OUT") +}; + +static const struct snd_soc_dapm_route tas6424_audio_map[] = { + { "DAC", NULL, "DAC IN" }, + { "OUT", NULL, "DAC" }, +}; + +static int tas6424_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + unsigned int rate = params_rate(params); + unsigned int width = params_width(params); + u8 sap_ctrl = 0; + + dev_dbg(component->dev, "%s() rate=%u width=%u\n", __func__, rate, width); + + switch (rate) { + case 44100: + sap_ctrl |= TAS6424_SAP_RATE_44100; + break; + case 48000: + sap_ctrl |= TAS6424_SAP_RATE_48000; + break; + case 96000: + sap_ctrl |= TAS6424_SAP_RATE_96000; + break; + default: + dev_err(component->dev, "unsupported sample rate: %u\n", rate); + return -EINVAL; + } + + switch (width) { + case 16: + sap_ctrl |= TAS6424_SAP_TDM_SLOT_SZ_16; + break; + case 24: + break; + default: + dev_err(component->dev, "unsupported sample width: %u\n", width); + return -EINVAL; + } + + snd_soc_component_update_bits(component, TAS6424_SAP_CTRL, + TAS6424_SAP_RATE_MASK | + TAS6424_SAP_TDM_SLOT_SZ_16, + sap_ctrl); + + return 0; +} + +static int tas6424_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u8 serial_format = 0; + + dev_dbg(component->dev, "%s() fmt=0x%0x\n", __func__, fmt); + + /* clock masters */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + dev_err(component->dev, "Invalid DAI master/slave interface\n"); + return -EINVAL; + } + + /* signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + dev_err(component->dev, "Invalid DAI clock signal polarity\n"); + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + serial_format |= TAS6424_SAP_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + serial_format |= TAS6424_SAP_DSP; + break; + case SND_SOC_DAIFMT_DSP_B: + /* + * We can use the fact that the TAS6424 does not care about the + * LRCLK duty cycle during TDM to receive DSP_B formatted data + * in LEFTJ mode (no delaying of the 1st data bit). + */ + serial_format |= TAS6424_SAP_LEFTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + serial_format |= TAS6424_SAP_LEFTJ; + break; + default: + dev_err(component->dev, "Invalid DAI interface format\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, TAS6424_SAP_CTRL, + TAS6424_SAP_FMT_MASK, serial_format); + + return 0; +} + +static int tas6424_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int first_slot, last_slot; + bool sap_tdm_slot_last; + + dev_dbg(component->dev, "%s() tx_mask=%d rx_mask=%d\n", __func__, + tx_mask, rx_mask); + + if (!tx_mask || !rx_mask) + return 0; /* nothing needed to disable TDM mode */ + + /* + * Determine the first slot and last slot that is being requested so + * we'll be able to more easily enforce certain constraints as the + * TAS6424's TDM interface is not fully configurable. + */ + first_slot = __ffs(tx_mask); + last_slot = __fls(rx_mask); + + if (last_slot - first_slot != 4) { + dev_err(component->dev, "tdm mask must cover 4 contiguous slots\n"); + return -EINVAL; + } + + switch (first_slot) { + case 0: + sap_tdm_slot_last = false; + break; + case 4: + sap_tdm_slot_last = true; + break; + default: + dev_err(component->dev, "tdm mask must start at slot 0 or 4\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, TAS6424_SAP_CTRL, TAS6424_SAP_TDM_SLOT_LAST, + sap_tdm_slot_last ? TAS6424_SAP_TDM_SLOT_LAST : 0); + + return 0; +} + +static int tas6424_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct tas6424_data *tas6424 = snd_soc_component_get_drvdata(component); + unsigned int val; + + dev_dbg(component->dev, "%s() mute=%d\n", __func__, mute); + + if (tas6424->mute_gpio) { + gpiod_set_value_cansleep(tas6424->mute_gpio, mute); + return 0; + } + + if (mute) + val = TAS6424_ALL_STATE_MUTE; + else + val = TAS6424_ALL_STATE_PLAY; + + snd_soc_component_write(component, TAS6424_CH_STATE_CTRL, val); + + return 0; +} + +static int tas6424_power_off(struct snd_soc_component *component) +{ + struct tas6424_data *tas6424 = snd_soc_component_get_drvdata(component); + int ret; + + snd_soc_component_write(component, TAS6424_CH_STATE_CTRL, TAS6424_ALL_STATE_HIZ); + + regcache_cache_only(tas6424->regmap, true); + regcache_mark_dirty(tas6424->regmap); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas6424->supplies), + tas6424->supplies); + if (ret < 0) { + dev_err(component->dev, "failed to disable supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas6424_power_on(struct snd_soc_component *component) +{ + struct tas6424_data *tas6424 = snd_soc_component_get_drvdata(component); + int ret; + u8 chan_states; + int no_auto_diags = 0; + unsigned int reg_val; + + if (!regmap_read(tas6424->regmap, TAS6424_DC_DIAG_CTRL1, ®_val)) + no_auto_diags = reg_val & TAS6424_LDGBYPASS_MASK; + + ret = regulator_bulk_enable(ARRAY_SIZE(tas6424->supplies), + tas6424->supplies); + if (ret < 0) { + dev_err(component->dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(tas6424->regmap, false); + + ret = regcache_sync(tas6424->regmap); + if (ret < 0) { + dev_err(component->dev, "failed to sync regcache: %d\n", ret); + return ret; + } + + if (tas6424->mute_gpio) { + gpiod_set_value_cansleep(tas6424->mute_gpio, 0); + /* + * channels are muted via the mute pin. Don't also mute + * them via the registers so that subsequent register + * access is not necessary to un-mute the channels + */ + chan_states = TAS6424_ALL_STATE_PLAY; + } else { + chan_states = TAS6424_ALL_STATE_MUTE; + } + snd_soc_component_write(component, TAS6424_CH_STATE_CTRL, chan_states); + + /* any time we come out of HIZ, the output channels automatically run DC + * load diagnostics if autodiagnotics are enabled. wait here until this + * completes. + */ + if (!no_auto_diags) + msleep(230); + + return 0; +} + +static int tas6424_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + dev_dbg(component->dev, "%s() level=%d\n", __func__, level); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + tas6424_power_on(component); + break; + case SND_SOC_BIAS_OFF: + tas6424_power_off(component); + break; + } + + return 0; +} + +static struct snd_soc_component_driver soc_codec_dev_tas6424 = { + .set_bias_level = tas6424_set_bias_level, + .controls = tas6424_snd_controls, + .num_controls = ARRAY_SIZE(tas6424_snd_controls), + .dapm_widgets = tas6424_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas6424_dapm_widgets), + .dapm_routes = tas6424_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas6424_audio_map), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_ops tas6424_speaker_dai_ops = { + .hw_params = tas6424_hw_params, + .set_fmt = tas6424_set_dai_fmt, + .set_tdm_slot = tas6424_set_dai_tdm_slot, + .digital_mute = tas6424_mute, +}; + +static struct snd_soc_dai_driver tas6424_dai[] = { + { + .name = "tas6424-amplifier", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 4, + .rates = TAS6424_RATES, + .formats = TAS6424_FORMATS, + }, + .ops = &tas6424_speaker_dai_ops, + }, +}; + +static void tas6424_fault_check_work(struct work_struct *work) +{ + struct tas6424_data *tas6424 = container_of(work, struct tas6424_data, + fault_check_work.work); + struct device *dev = tas6424->dev; + unsigned int reg; + int ret; + + ret = regmap_read(tas6424->regmap, TAS6424_GLOB_FAULT1, ®); + if (ret < 0) { + dev_err(dev, "failed to read FAULT1 register: %d\n", ret); + goto out; + } + + /* + * Ignore any clock faults as there is no clean way to check for them. + * We would need to start checking for those faults *after* the SAIF + * stream has been setup, and stop checking *before* the stream is + * stopped to avoid any false-positives. However there are no + * appropriate hooks to monitor these events. + */ + reg &= TAS6424_FAULT_PVDD_OV | + TAS6424_FAULT_VBAT_OV | + TAS6424_FAULT_PVDD_UV | + TAS6424_FAULT_VBAT_UV; + + if (!reg) { + tas6424->last_fault1 = reg; + goto check_global_fault2_reg; + } + + /* + * Only flag errors once for a given occurrence. This is needed as + * the TAS6424 will take time clearing the fault condition internally + * during which we don't want to bombard the system with the same + * error message over and over. + */ + if ((reg & TAS6424_FAULT_PVDD_OV) && !(tas6424->last_fault1 & TAS6424_FAULT_PVDD_OV)) + dev_crit(dev, "experienced a PVDD overvoltage fault\n"); + + if ((reg & TAS6424_FAULT_VBAT_OV) && !(tas6424->last_fault1 & TAS6424_FAULT_VBAT_OV)) + dev_crit(dev, "experienced a VBAT overvoltage fault\n"); + + if ((reg & TAS6424_FAULT_PVDD_UV) && !(tas6424->last_fault1 & TAS6424_FAULT_PVDD_UV)) + dev_crit(dev, "experienced a PVDD undervoltage fault\n"); + + if ((reg & TAS6424_FAULT_VBAT_UV) && !(tas6424->last_fault1 & TAS6424_FAULT_VBAT_UV)) + dev_crit(dev, "experienced a VBAT undervoltage fault\n"); + + /* Store current fault1 value so we can detect any changes next time */ + tas6424->last_fault1 = reg; + +check_global_fault2_reg: + ret = regmap_read(tas6424->regmap, TAS6424_GLOB_FAULT2, ®); + if (ret < 0) { + dev_err(dev, "failed to read FAULT2 register: %d\n", ret); + goto out; + } + + reg &= TAS6424_FAULT_OTSD | + TAS6424_FAULT_OTSD_CH1 | + TAS6424_FAULT_OTSD_CH2 | + TAS6424_FAULT_OTSD_CH3 | + TAS6424_FAULT_OTSD_CH4; + + if (!reg) { + tas6424->last_fault2 = reg; + goto check_warn_reg; + } + + if ((reg & TAS6424_FAULT_OTSD) && !(tas6424->last_fault2 & TAS6424_FAULT_OTSD)) + dev_crit(dev, "experienced a global overtemp shutdown\n"); + + if ((reg & TAS6424_FAULT_OTSD_CH1) && !(tas6424->last_fault2 & TAS6424_FAULT_OTSD_CH1)) + dev_crit(dev, "experienced an overtemp shutdown on CH1\n"); + + if ((reg & TAS6424_FAULT_OTSD_CH2) && !(tas6424->last_fault2 & TAS6424_FAULT_OTSD_CH2)) + dev_crit(dev, "experienced an overtemp shutdown on CH2\n"); + + if ((reg & TAS6424_FAULT_OTSD_CH3) && !(tas6424->last_fault2 & TAS6424_FAULT_OTSD_CH3)) + dev_crit(dev, "experienced an overtemp shutdown on CH3\n"); + + if ((reg & TAS6424_FAULT_OTSD_CH4) && !(tas6424->last_fault2 & TAS6424_FAULT_OTSD_CH4)) + dev_crit(dev, "experienced an overtemp shutdown on CH4\n"); + + /* Store current fault2 value so we can detect any changes next time */ + tas6424->last_fault2 = reg; + +check_warn_reg: + ret = regmap_read(tas6424->regmap, TAS6424_WARN, ®); + if (ret < 0) { + dev_err(dev, "failed to read WARN register: %d\n", ret); + goto out; + } + + reg &= TAS6424_WARN_VDD_UV | + TAS6424_WARN_VDD_POR | + TAS6424_WARN_VDD_OTW | + TAS6424_WARN_VDD_OTW_CH1 | + TAS6424_WARN_VDD_OTW_CH2 | + TAS6424_WARN_VDD_OTW_CH3 | + TAS6424_WARN_VDD_OTW_CH4; + + if (!reg) { + tas6424->last_warn = reg; + goto out; + } + + if ((reg & TAS6424_WARN_VDD_UV) && !(tas6424->last_warn & TAS6424_WARN_VDD_UV)) + dev_warn(dev, "experienced a VDD under voltage condition\n"); + + if ((reg & TAS6424_WARN_VDD_POR) && !(tas6424->last_warn & TAS6424_WARN_VDD_POR)) + dev_warn(dev, "experienced a VDD POR condition\n"); + + if ((reg & TAS6424_WARN_VDD_OTW) && !(tas6424->last_warn & TAS6424_WARN_VDD_OTW)) + dev_warn(dev, "experienced a global overtemp warning\n"); + + if ((reg & TAS6424_WARN_VDD_OTW_CH1) && !(tas6424->last_warn & TAS6424_WARN_VDD_OTW_CH1)) + dev_warn(dev, "experienced an overtemp warning on CH1\n"); + + if ((reg & TAS6424_WARN_VDD_OTW_CH2) && !(tas6424->last_warn & TAS6424_WARN_VDD_OTW_CH2)) + dev_warn(dev, "experienced an overtemp warning on CH2\n"); + + if ((reg & TAS6424_WARN_VDD_OTW_CH3) && !(tas6424->last_warn & TAS6424_WARN_VDD_OTW_CH3)) + dev_warn(dev, "experienced an overtemp warning on CH3\n"); + + if ((reg & TAS6424_WARN_VDD_OTW_CH4) && !(tas6424->last_warn & TAS6424_WARN_VDD_OTW_CH4)) + dev_warn(dev, "experienced an overtemp warning on CH4\n"); + + /* Store current warn value so we can detect any changes next time */ + tas6424->last_warn = reg; + + /* Clear any faults by toggling the CLEAR_FAULT control bit */ + ret = regmap_write_bits(tas6424->regmap, TAS6424_MISC_CTRL3, + TAS6424_CLEAR_FAULT, TAS6424_CLEAR_FAULT); + if (ret < 0) + dev_err(dev, "failed to write MISC_CTRL3 register: %d\n", ret); + + ret = regmap_write_bits(tas6424->regmap, TAS6424_MISC_CTRL3, + TAS6424_CLEAR_FAULT, 0); + if (ret < 0) + dev_err(dev, "failed to write MISC_CTRL3 register: %d\n", ret); + +out: + /* Schedule the next fault check at the specified interval */ + schedule_delayed_work(&tas6424->fault_check_work, + msecs_to_jiffies(TAS6424_FAULT_CHECK_INTERVAL)); +} + +static const struct reg_default tas6424_reg_defaults[] = { + { TAS6424_MODE_CTRL, 0x00 }, + { TAS6424_MISC_CTRL1, 0x32 }, + { TAS6424_MISC_CTRL2, 0x62 }, + { TAS6424_SAP_CTRL, 0x04 }, + { TAS6424_CH_STATE_CTRL, 0x55 }, + { TAS6424_CH1_VOL_CTRL, 0xcf }, + { TAS6424_CH2_VOL_CTRL, 0xcf }, + { TAS6424_CH3_VOL_CTRL, 0xcf }, + { TAS6424_CH4_VOL_CTRL, 0xcf }, + { TAS6424_DC_DIAG_CTRL1, 0x00 }, + { TAS6424_DC_DIAG_CTRL2, 0x11 }, + { TAS6424_DC_DIAG_CTRL3, 0x11 }, + { TAS6424_PIN_CTRL, 0xff }, + { TAS6424_AC_DIAG_CTRL1, 0x00 }, + { TAS6424_MISC_CTRL3, 0x00 }, + { TAS6424_CLIP_CTRL, 0x01 }, + { TAS6424_CLIP_WINDOW, 0x14 }, + { TAS6424_CLIP_WARN, 0x00 }, + { TAS6424_CBC_STAT, 0x00 }, + { TAS6424_MISC_CTRL4, 0x40 }, +}; + +static bool tas6424_is_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS6424_MODE_CTRL: + case TAS6424_MISC_CTRL1: + case TAS6424_MISC_CTRL2: + case TAS6424_SAP_CTRL: + case TAS6424_CH_STATE_CTRL: + case TAS6424_CH1_VOL_CTRL: + case TAS6424_CH2_VOL_CTRL: + case TAS6424_CH3_VOL_CTRL: + case TAS6424_CH4_VOL_CTRL: + case TAS6424_DC_DIAG_CTRL1: + case TAS6424_DC_DIAG_CTRL2: + case TAS6424_DC_DIAG_CTRL3: + case TAS6424_PIN_CTRL: + case TAS6424_AC_DIAG_CTRL1: + case TAS6424_MISC_CTRL3: + case TAS6424_CLIP_CTRL: + case TAS6424_CLIP_WINDOW: + case TAS6424_CLIP_WARN: + case TAS6424_CBC_STAT: + case TAS6424_MISC_CTRL4: + return true; + default: + return false; + } +} + +static bool tas6424_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TAS6424_DC_LOAD_DIAG_REP12: + case TAS6424_DC_LOAD_DIAG_REP34: + case TAS6424_DC_LOAD_DIAG_REPLO: + case TAS6424_CHANNEL_STATE: + case TAS6424_CHANNEL_FAULT: + case TAS6424_GLOB_FAULT1: + case TAS6424_GLOB_FAULT2: + case TAS6424_WARN: + case TAS6424_AC_LOAD_DIAG_REP1: + case TAS6424_AC_LOAD_DIAG_REP2: + case TAS6424_AC_LOAD_DIAG_REP3: + case TAS6424_AC_LOAD_DIAG_REP4: + return true; + default: + return false; + } +} + +static const struct regmap_config tas6424_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .writeable_reg = tas6424_is_writable_reg, + .volatile_reg = tas6424_is_volatile_reg, + + .max_register = TAS6424_MAX, + .reg_defaults = tas6424_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas6424_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tas6424_of_ids[] = { + { .compatible = "ti,tas6424", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tas6424_of_ids); +#endif + +static int tas6424_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tas6424_data *tas6424; + int ret; + int i; + + tas6424 = devm_kzalloc(dev, sizeof(*tas6424), GFP_KERNEL); + if (!tas6424) + return -ENOMEM; + dev_set_drvdata(dev, tas6424); + + tas6424->dev = dev; + + tas6424->regmap = devm_regmap_init_i2c(client, &tas6424_regmap_config); + if (IS_ERR(tas6424->regmap)) { + ret = PTR_ERR(tas6424->regmap); + dev_err(dev, "unable to allocate register map: %d\n", ret); + return ret; + } + + /* + * Get control of the standby pin and set it LOW to take the codec + * out of the stand-by mode. + * Note: The actual pin polarity is taken care of in the GPIO lib + * according the polarity specified in the DTS. + */ + tas6424->standby_gpio = devm_gpiod_get_optional(dev, "standby", + GPIOD_OUT_LOW); + if (IS_ERR(tas6424->standby_gpio)) { + if (PTR_ERR(tas6424->standby_gpio) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_info(dev, "failed to get standby GPIO: %ld\n", + PTR_ERR(tas6424->standby_gpio)); + tas6424->standby_gpio = NULL; + } + + /* + * Get control of the mute pin and set it HIGH in order to start with + * all the output muted. + * Note: The actual pin polarity is taken care of in the GPIO lib + * according the polarity specified in the DTS. + */ + tas6424->mute_gpio = devm_gpiod_get_optional(dev, "mute", + GPIOD_OUT_HIGH); + if (IS_ERR(tas6424->mute_gpio)) { + if (PTR_ERR(tas6424->mute_gpio) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_info(dev, "failed to get nmute GPIO: %ld\n", + PTR_ERR(tas6424->mute_gpio)); + tas6424->mute_gpio = NULL; + } + + for (i = 0; i < ARRAY_SIZE(tas6424->supplies); i++) + tas6424->supplies[i].supply = tas6424_supply_names[i]; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(tas6424->supplies), + tas6424->supplies); + if (ret) { + dev_err(dev, "unable to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(tas6424->supplies), + tas6424->supplies); + if (ret) { + dev_err(dev, "unable to enable supplies: %d\n", ret); + return ret; + } + + /* Reset device to establish well-defined startup state */ + ret = regmap_update_bits(tas6424->regmap, TAS6424_MODE_CTRL, + TAS6424_RESET, TAS6424_RESET); + if (ret) { + dev_err(dev, "unable to reset device: %d\n", ret); + return ret; + } + + INIT_DELAYED_WORK(&tas6424->fault_check_work, tas6424_fault_check_work); + + ret = devm_snd_soc_register_component(dev, &soc_codec_dev_tas6424, + tas6424_dai, ARRAY_SIZE(tas6424_dai)); + if (ret < 0) { + dev_err(dev, "unable to register codec: %d\n", ret); + return ret; + } + + return 0; +} + +static int tas6424_i2c_remove(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tas6424_data *tas6424 = dev_get_drvdata(dev); + int ret; + + cancel_delayed_work_sync(&tas6424->fault_check_work); + + /* put the codec in stand-by */ + if (tas6424->standby_gpio) + gpiod_set_value_cansleep(tas6424->standby_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(tas6424->supplies), + tas6424->supplies); + if (ret < 0) { + dev_err(dev, "unable to disable supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct i2c_device_id tas6424_i2c_ids[] = { + { "tas6424", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas6424_i2c_ids); + +static struct i2c_driver tas6424_i2c_driver = { + .driver = { + .name = "tas6424", + .of_match_table = of_match_ptr(tas6424_of_ids), + }, + .probe = tas6424_i2c_probe, + .remove = tas6424_i2c_remove, + .id_table = tas6424_i2c_ids, +}; +module_i2c_driver(tas6424_i2c_driver); + +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("TAS6424 Audio amplifier driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas6424.h b/sound/soc/codecs/tas6424.h new file mode 100644 index 000000000..b5958c45e --- /dev/null +++ b/sound/soc/codecs/tas6424.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC Texas Instruments TAS6424 Quad-Channel Audio Amplifier + * + * Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Andreas Dannenberg + * Andrew F. Davis + */ + +#ifndef __TAS6424_H__ +#define __TAS6424_H__ + +#define TAS6424_RATES (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define TAS6424_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* Register Address Map */ +#define TAS6424_MODE_CTRL 0x00 +#define TAS6424_MISC_CTRL1 0x01 +#define TAS6424_MISC_CTRL2 0x02 +#define TAS6424_SAP_CTRL 0x03 +#define TAS6424_CH_STATE_CTRL 0x04 +#define TAS6424_CH1_VOL_CTRL 0x05 +#define TAS6424_CH2_VOL_CTRL 0x06 +#define TAS6424_CH3_VOL_CTRL 0x07 +#define TAS6424_CH4_VOL_CTRL 0x08 +#define TAS6424_DC_DIAG_CTRL1 0x09 +#define TAS6424_DC_DIAG_CTRL2 0x0a +#define TAS6424_DC_DIAG_CTRL3 0x0b +#define TAS6424_DC_LOAD_DIAG_REP12 0x0c +#define TAS6424_DC_LOAD_DIAG_REP34 0x0d +#define TAS6424_DC_LOAD_DIAG_REPLO 0x0e +#define TAS6424_CHANNEL_STATE 0x0f +#define TAS6424_CHANNEL_FAULT 0x10 +#define TAS6424_GLOB_FAULT1 0x11 +#define TAS6424_GLOB_FAULT2 0x12 +#define TAS6424_WARN 0x13 +#define TAS6424_PIN_CTRL 0x14 +#define TAS6424_AC_DIAG_CTRL1 0x15 +#define TAS6424_AC_DIAG_CTRL2 0x16 +#define TAS6424_AC_LOAD_DIAG_REP1 0x17 +#define TAS6424_AC_LOAD_DIAG_REP2 0x18 +#define TAS6424_AC_LOAD_DIAG_REP3 0x19 +#define TAS6424_AC_LOAD_DIAG_REP4 0x1a +#define TAS6424_MISC_CTRL3 0x21 +#define TAS6424_CLIP_CTRL 0x22 +#define TAS6424_CLIP_WINDOW 0x23 +#define TAS6424_CLIP_WARN 0x24 +#define TAS6424_CBC_STAT 0x25 +#define TAS6424_MISC_CTRL4 0x26 +#define TAS6424_MAX TAS6424_MISC_CTRL4 + +/* TAS6424_MODE_CTRL_REG */ +#define TAS6424_RESET BIT(7) + +/* TAS6424_SAP_CTRL_REG */ +#define TAS6424_SAP_RATE_MASK GENMASK(7, 6) +#define TAS6424_SAP_RATE_44100 (0x00 << 6) +#define TAS6424_SAP_RATE_48000 (0x01 << 6) +#define TAS6424_SAP_RATE_96000 (0x02 << 6) +#define TAS6424_SAP_TDM_SLOT_LAST BIT(5) +#define TAS6424_SAP_TDM_SLOT_SZ_16 BIT(4) +#define TAS6424_SAP_TDM_SLOT_SWAP BIT(3) +#define TAS6424_SAP_FMT_MASK GENMASK(2, 0) +#define TAS6424_SAP_RIGHTJ_24 (0x00 << 0) +#define TAS6424_SAP_RIGHTJ_20 (0x01 << 0) +#define TAS6424_SAP_RIGHTJ_18 (0x02 << 0) +#define TAS6424_SAP_RIGHTJ_16 (0x03 << 0) +#define TAS6424_SAP_I2S (0x04 << 0) +#define TAS6424_SAP_LEFTJ (0x05 << 0) +#define TAS6424_SAP_DSP (0x06 << 0) + +/* TAS6424_CH_STATE_CTRL_REG */ +#define TAS6424_CH1_STATE_MASK GENMASK(7, 6) +#define TAS6424_CH1_STATE_PLAY (0x00 << 6) +#define TAS6424_CH1_STATE_HIZ (0x01 << 6) +#define TAS6424_CH1_STATE_MUTE (0x02 << 6) +#define TAS6424_CH1_STATE_DIAG (0x03 << 6) +#define TAS6424_CH2_STATE_MASK GENMASK(5, 4) +#define TAS6424_CH2_STATE_PLAY (0x00 << 4) +#define TAS6424_CH2_STATE_HIZ (0x01 << 4) +#define TAS6424_CH2_STATE_MUTE (0x02 << 4) +#define TAS6424_CH2_STATE_DIAG (0x03 << 4) +#define TAS6424_CH3_STATE_MASK GENMASK(3, 2) +#define TAS6424_CH3_STATE_PLAY (0x00 << 2) +#define TAS6424_CH3_STATE_HIZ (0x01 << 2) +#define TAS6424_CH3_STATE_MUTE (0x02 << 2) +#define TAS6424_CH3_STATE_DIAG (0x03 << 2) +#define TAS6424_CH4_STATE_MASK GENMASK(1, 0) +#define TAS6424_CH4_STATE_PLAY (0x00 << 0) +#define TAS6424_CH4_STATE_HIZ (0x01 << 0) +#define TAS6424_CH4_STATE_MUTE (0x02 << 0) +#define TAS6424_CH4_STATE_DIAG (0x03 << 0) +#define TAS6424_ALL_STATE_PLAY (TAS6424_CH1_STATE_PLAY | \ + TAS6424_CH2_STATE_PLAY | \ + TAS6424_CH3_STATE_PLAY | \ + TAS6424_CH4_STATE_PLAY) +#define TAS6424_ALL_STATE_HIZ (TAS6424_CH1_STATE_HIZ | \ + TAS6424_CH2_STATE_HIZ | \ + TAS6424_CH3_STATE_HIZ | \ + TAS6424_CH4_STATE_HIZ) +#define TAS6424_ALL_STATE_MUTE (TAS6424_CH1_STATE_MUTE | \ + TAS6424_CH2_STATE_MUTE | \ + TAS6424_CH3_STATE_MUTE | \ + TAS6424_CH4_STATE_MUTE) +#define TAS6424_ALL_STATE_DIAG (TAS6424_CH1_STATE_DIAG | \ + TAS6424_CH2_STATE_DIAG | \ + TAS6424_CH3_STATE_DIAG | \ + TAS6424_CH4_STATE_DIAG) + +/* TAS6424_DC_DIAG_CTRL1 */ +#define TAS6424_LDGBYPASS_SHIFT 0 +#define TAS6424_LDGBYPASS_MASK BIT(TAS6424_LDGBYPASS_SHIFT) + +/* TAS6424_GLOB_FAULT1_REG */ +#define TAS6424_FAULT_CLOCK BIT(4) +#define TAS6424_FAULT_PVDD_OV BIT(3) +#define TAS6424_FAULT_VBAT_OV BIT(2) +#define TAS6424_FAULT_PVDD_UV BIT(1) +#define TAS6424_FAULT_VBAT_UV BIT(0) + +/* TAS6424_GLOB_FAULT2_REG */ +#define TAS6424_FAULT_OTSD BIT(4) +#define TAS6424_FAULT_OTSD_CH1 BIT(3) +#define TAS6424_FAULT_OTSD_CH2 BIT(2) +#define TAS6424_FAULT_OTSD_CH3 BIT(1) +#define TAS6424_FAULT_OTSD_CH4 BIT(0) + +/* TAS6424_WARN_REG */ +#define TAS6424_WARN_VDD_UV BIT(6) +#define TAS6424_WARN_VDD_POR BIT(5) +#define TAS6424_WARN_VDD_OTW BIT(4) +#define TAS6424_WARN_VDD_OTW_CH1 BIT(3) +#define TAS6424_WARN_VDD_OTW_CH2 BIT(2) +#define TAS6424_WARN_VDD_OTW_CH3 BIT(1) +#define TAS6424_WARN_VDD_OTW_CH4 BIT(0) + +/* TAS6424_MISC_CTRL3_REG */ +#define TAS6424_CLEAR_FAULT BIT(7) +#define TAS6424_PBTL_CH_SEL BIT(6) +#define TAS6424_MASK_CBC_WARN BIT(5) +#define TAS6424_MASK_VDD_UV BIT(4) +#define TAS6424_OTSD_AUTO_RECOVERY BIT(3) + +#endif /* __TAS6424_H__ */ diff --git a/sound/soc/codecs/tda7419.c b/sound/soc/codecs/tda7419.c new file mode 100644 index 000000000..7f3b79c5a --- /dev/null +++ b/sound/soc/codecs/tda7419.c @@ -0,0 +1,654 @@ +/* + * TDA7419 audio processor driver + * + * Copyright 2018 Konsulko Group + * + * Author: Matt Porter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TDA7419_MAIN_SRC_REG 0x00 +#define TDA7419_LOUDNESS_REG 0x01 +#define TDA7419_MUTE_CLK_REG 0x02 +#define TDA7419_VOLUME_REG 0x03 +#define TDA7419_TREBLE_REG 0x04 +#define TDA7419_MIDDLE_REG 0x05 +#define TDA7419_BASS_REG 0x06 +#define TDA7419_SECOND_SRC_REG 0x07 +#define TDA7419_SUB_MID_BASS_REG 0x08 +#define TDA7419_MIXING_GAIN_REG 0x09 +#define TDA7419_ATTENUATOR_LF_REG 0x0a +#define TDA7419_ATTENUATOR_RF_REG 0x0b +#define TDA7419_ATTENUATOR_LR_REG 0x0c +#define TDA7419_ATTENUATOR_RR_REG 0x0d +#define TDA7419_MIXING_LEVEL_REG 0x0e +#define TDA7419_ATTENUATOR_SUB_REG 0x0f +#define TDA7419_SA_CLK_AC_REG 0x10 +#define TDA7419_TESTING_REG 0x11 + +#define TDA7419_MAIN_SRC_SEL 0 +#define TDA7419_MAIN_SRC_GAIN 3 +#define TDA7419_MAIN_SRC_AUTOZERO 7 + +#define TDA7419_LOUDNESS_ATTEN 0 +#define TDA7419_LOUDNESS_CENTER_FREQ 4 +#define TDA7419_LOUDNESS_BOOST 6 +#define TDA7419_LOUDNESS_SOFT_STEP 7 + +#define TDA7419_VOLUME_SOFT_STEP 7 + +#define TDA7419_SOFT_MUTE 0 +#define TDA7419_MUTE_INFLUENCE 1 +#define TDA7419_SOFT_MUTE_TIME 2 +#define TDA7419_SOFT_STEP_TIME 4 +#define TDA7419_CLK_FAST_MODE 7 + +#define TDA7419_TREBLE_CENTER_FREQ 5 +#define TDA7419_REF_OUT_SELECT 7 + +#define TDA7419_MIDDLE_Q_FACTOR 5 +#define TDA7419_MIDDLE_SOFT_STEP 7 + +#define TDA7419_BASS_Q_FACTOR 5 +#define TDA7419_BASS_SOFT_STEP 7 + +#define TDA7419_SECOND_SRC_SEL 0 +#define TDA7419_SECOND_SRC_GAIN 3 +#define TDA7419_REAR_SPKR_SRC 7 + +#define TDA7419_SUB_CUT_OFF_FREQ 0 +#define TDA7419_MIDDLE_CENTER_FREQ 2 +#define TDA7419_BASS_CENTER_FREQ 4 +#define TDA7419_BASS_DC_MODE 6 +#define TDA7419_SMOOTHING_FILTER 7 + +#define TDA7419_MIX_LF 0 +#define TDA7419_MIX_RF 1 +#define TDA7419_MIX_ENABLE 2 +#define TDA7419_SUB_ENABLE 3 +#define TDA7419_HPF_GAIN 4 + +#define TDA7419_SA_Q_FACTOR 0 +#define TDA7419_RESET_MODE 1 +#define TDA7419_SA_SOURCE 2 +#define TDA7419_SA_RUN 3 +#define TDA7419_RESET 4 +#define TDA7419_CLK_SOURCE 5 +#define TDA7419_COUPLING_MODE 6 + +struct tda7419_data { + struct regmap *regmap; +}; + +static bool tda7419_readable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct reg_default tda7419_regmap_defaults[] = { + { TDA7419_MAIN_SRC_REG, 0xfe }, + { TDA7419_LOUDNESS_REG, 0xfe }, + { TDA7419_MUTE_CLK_REG, 0xfe }, + { TDA7419_VOLUME_REG, 0xfe }, + { TDA7419_TREBLE_REG, 0xfe }, + { TDA7419_MIDDLE_REG, 0xfe }, + { TDA7419_BASS_REG, 0xfe }, + { TDA7419_SECOND_SRC_REG, 0xfe }, + { TDA7419_SUB_MID_BASS_REG, 0xfe }, + { TDA7419_MIXING_GAIN_REG, 0xfe }, + { TDA7419_ATTENUATOR_LF_REG, 0xfe }, + { TDA7419_ATTENUATOR_RF_REG, 0xfe }, + { TDA7419_ATTENUATOR_LR_REG, 0xfe }, + { TDA7419_ATTENUATOR_RR_REG, 0xfe }, + { TDA7419_MIXING_LEVEL_REG, 0xfe }, + { TDA7419_ATTENUATOR_SUB_REG, 0xfe }, + { TDA7419_SA_CLK_AC_REG, 0xfe }, + { TDA7419_TESTING_REG, 0xfe }, +}; + +static const struct regmap_config tda7419_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TDA7419_TESTING_REG, + .cache_type = REGCACHE_RBTREE, + .readable_reg = tda7419_readable_reg, + .reg_defaults = tda7419_regmap_defaults, + .num_reg_defaults = ARRAY_SIZE(tda7419_regmap_defaults), +}; + +struct tda7419_vol_control { + int min, max; + unsigned int reg, rreg, mask, thresh; + unsigned int invert:1; +}; + +static inline bool tda7419_vol_is_stereo(struct tda7419_vol_control *tvc) +{ + if (tvc->reg == tvc->rreg) + return false; + + return true; +} + +static int tda7419_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct tda7419_vol_control *tvc = + (struct tda7419_vol_control *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = tda7419_vol_is_stereo(tvc) ? 2 : 1; + uinfo->value.integer.min = tvc->min; + uinfo->value.integer.max = tvc->max; + + return 0; +} + +static inline int tda7419_vol_get_value(int val, unsigned int mask, + int min, int thresh, + unsigned int invert) +{ + val &= mask; + if (val < thresh) { + if (invert) + val = 0 - val; + } else if (val > thresh) { + if (invert) + val = val - thresh; + else + val = thresh - val; + } + + if (val < min) + val = min; + + return val; +} + +static int tda7419_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct tda7419_vol_control *tvc = + (struct tda7419_vol_control *)kcontrol->private_value; + unsigned int reg = tvc->reg; + unsigned int rreg = tvc->rreg; + unsigned int mask = tvc->mask; + int min = tvc->min; + int thresh = tvc->thresh; + unsigned int invert = tvc->invert; + int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret < 0) + return ret; + ucontrol->value.integer.value[0] = + tda7419_vol_get_value(val, mask, min, thresh, invert); + + if (tda7419_vol_is_stereo(tvc)) { + ret = snd_soc_component_read(component, rreg, &val); + if (ret < 0) + return ret; + ucontrol->value.integer.value[1] = + tda7419_vol_get_value(val, mask, min, thresh, invert); + } + + return 0; +} + +static inline int tda7419_vol_put_value(int val, int thresh, + unsigned int invert) +{ + if (val < 0) { + if (invert) + val = abs(val); + else + val = thresh - val; + } else if ((val > 0) && invert) { + val += thresh; + } + + return val; +} + +static int tda7419_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_kcontrol_chip(kcontrol); + struct tda7419_vol_control *tvc = + (struct tda7419_vol_control *)kcontrol->private_value; + unsigned int reg = tvc->reg; + unsigned int rreg = tvc->rreg; + unsigned int mask = tvc->mask; + int thresh = tvc->thresh; + unsigned int invert = tvc->invert; + int val; + int ret; + + val = tda7419_vol_put_value(ucontrol->value.integer.value[0], + thresh, invert); + ret = snd_soc_component_update_bits(component, reg, + mask, val); + if (ret < 0) + return ret; + + if (tda7419_vol_is_stereo(tvc)) { + val = tda7419_vol_put_value(ucontrol->value.integer.value[1], + thresh, invert); + ret = snd_soc_component_update_bits(component, rreg, + mask, val); + } + + return ret; +} + +#define TDA7419_SINGLE_VALUE(xreg, xmask, xmin, xmax, xthresh, xinvert) \ + ((unsigned long)&(struct tda7419_vol_control) \ + {.reg = xreg, .rreg = xreg, .mask = xmask, .min = xmin, \ + .max = xmax, .thresh = xthresh, .invert = xinvert}) + +#define TDA7419_DOUBLE_R_VALUE(xregl, xregr, xmask, xmin, xmax, xthresh, \ + xinvert) \ + ((unsigned long)&(struct tda7419_vol_control) \ + {.reg = xregl, .rreg = xregr, .mask = xmask, .min = xmin, \ + .max = xmax, .thresh = xthresh, .invert = xinvert}) + +#define TDA7419_SINGLE_TLV(xname, xreg, xmask, xmin, xmax, xthresh, \ + xinvert, xtlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (xtlv_array), \ + .info = tda7419_vol_info, \ + .get = tda7419_vol_get, \ + .put = tda7419_vol_put, \ + .private_value = TDA7419_SINGLE_VALUE(xreg, xmask, xmin, \ + xmax, xthresh, xinvert), \ +} + +#define TDA7419_DOUBLE_R_TLV(xname, xregl, xregr, xmask, xmin, xmax, \ + xthresh, xinvert, xtlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (xtlv_array), \ + .info = tda7419_vol_info, \ + .get = tda7419_vol_get, \ + .put = tda7419_vol_put, \ + .private_value = TDA7419_DOUBLE_R_VALUE(xregl, xregr, xmask, \ + xmin, xmax, xthresh, \ + xinvert), \ +} + +static const char * const enum_src_sel[] = { + "QD", "SE1", "SE2", "SE3", "SE", "Mute", "Mute", "Mute"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_main_src_sel, + TDA7419_MAIN_SRC_REG, TDA7419_MAIN_SRC_SEL, enum_src_sel); +static const struct snd_kcontrol_new soc_mux_main_src_sel = + SOC_DAPM_ENUM("Main Source Select", soc_enum_main_src_sel); +static DECLARE_TLV_DB_SCALE(tlv_src_gain, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(tlv_loudness_atten, -1500, 100, 0); +static const char * const enum_loudness_center_freq[] = { + "Flat", "400 Hz", "800 Hz", "2400 Hz"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_loudness_center_freq, + TDA7419_LOUDNESS_REG, TDA7419_LOUDNESS_CENTER_FREQ, + enum_loudness_center_freq); +static const char * const enum_mute_influence[] = { + "Pin and IIC", "IIC"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_mute_influence, + TDA7419_MUTE_CLK_REG, TDA7419_MUTE_INFLUENCE, enum_mute_influence); +static const char * const enum_soft_mute_time[] = { + "0.48 ms", "0.96 ms", "123 ms", "123 ms"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_soft_mute_time, + TDA7419_MUTE_CLK_REG, TDA7419_SOFT_MUTE_TIME, enum_soft_mute_time); +static const char * const enum_soft_step_time[] = { + "0.160 ms", "0.321 ms", "0.642 ms", "1.28 ms", + "2.56 ms", "5.12 ms", "10.24 ms", "20.48 ms"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_soft_step_time, + TDA7419_MUTE_CLK_REG, TDA7419_SOFT_STEP_TIME, enum_soft_step_time); +static DECLARE_TLV_DB_SCALE(tlv_volume, -8000, 100, 1); +static const char * const enum_treble_center_freq[] = { + "10.0 kHz", "12.5 kHz", "15.0 kHz", "17.5 kHz"}; +static DECLARE_TLV_DB_SCALE(tlv_filter, -1500, 100, 0); +static SOC_ENUM_SINGLE_DECL(soc_enum_treble_center_freq, + TDA7419_TREBLE_REG, TDA7419_TREBLE_CENTER_FREQ, + enum_treble_center_freq); +static const char * const enum_ref_out_select[] = { + "External Vref (4 V)", "Internal Vref (3.3 V)"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_ref_out_select, + TDA7419_TREBLE_REG, TDA7419_REF_OUT_SELECT, enum_ref_out_select); +static const char * const enum_middle_q_factor[] = { + "0.5", "0.75", "1.0", "1.25"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_middle_q_factor, + TDA7419_MIDDLE_REG, TDA7419_MIDDLE_Q_FACTOR, enum_middle_q_factor); +static const char * const enum_bass_q_factor[] = { + "1.0", "1.25", "1.5", "2.0"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bass_q_factor, + TDA7419_BASS_REG, TDA7419_BASS_Q_FACTOR, enum_bass_q_factor); +static SOC_ENUM_SINGLE_DECL(soc_enum_second_src_sel, + TDA7419_SECOND_SRC_REG, TDA7419_SECOND_SRC_SEL, enum_src_sel); +static const struct snd_kcontrol_new soc_mux_second_src_sel = + SOC_DAPM_ENUM("Second Source Select", soc_enum_second_src_sel); +static const char * const enum_rear_spkr_src[] = { + "Main", "Second"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_rear_spkr_src, + TDA7419_SECOND_SRC_REG, TDA7419_REAR_SPKR_SRC, enum_rear_spkr_src); +static const struct snd_kcontrol_new soc_mux_rear_spkr_src = + SOC_DAPM_ENUM("Rear Speaker Source", soc_enum_rear_spkr_src); +static const char * const enum_sub_cut_off_freq[] = { + "Flat", "80 Hz", "120 Hz", "160 Hz"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_sub_cut_off_freq, + TDA7419_SUB_MID_BASS_REG, TDA7419_SUB_CUT_OFF_FREQ, + enum_sub_cut_off_freq); +static const char * const enum_middle_center_freq[] = { + "500 Hz", "1000 Hz", "1500 Hz", "2500 Hz"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_middle_center_freq, + TDA7419_SUB_MID_BASS_REG, TDA7419_MIDDLE_CENTER_FREQ, + enum_middle_center_freq); +static const char * const enum_bass_center_freq[] = { + "60 Hz", "80 Hz", "100 Hz", "200 Hz"}; +static SOC_ENUM_SINGLE_DECL(soc_enum_bass_center_freq, + TDA7419_SUB_MID_BASS_REG, TDA7419_BASS_CENTER_FREQ, + enum_bass_center_freq); +static const char * const enum_sa_q_factor[] = { + "3.5", "1.75" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_sa_q_factor, + TDA7419_SA_CLK_AC_REG, TDA7419_SA_Q_FACTOR, enum_sa_q_factor); +static const char * const enum_reset_mode[] = { + "IIC", "Auto" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_reset_mode, + TDA7419_SA_CLK_AC_REG, TDA7419_RESET_MODE, enum_reset_mode); +static const char * const enum_sa_src[] = { + "Bass", "In Gain" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_sa_src, + TDA7419_SA_CLK_AC_REG, TDA7419_SA_SOURCE, enum_sa_src); +static const char * const enum_clk_src[] = { + "Internal", "External" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_clk_src, + TDA7419_SA_CLK_AC_REG, TDA7419_CLK_SOURCE, enum_clk_src); +static const char * const enum_coupling_mode[] = { + "DC Coupling (without HPF)", "AC Coupling after In Gain", + "DC Coupling (with HPF)", "AC Coupling after Bass" }; +static SOC_ENUM_SINGLE_DECL(soc_enum_coupling_mode, + TDA7419_SA_CLK_AC_REG, TDA7419_COUPLING_MODE, enum_coupling_mode); + +/* ASoC Controls */ +static struct snd_kcontrol_new tda7419_controls[] = { +SOC_SINGLE_TLV("Main Source Capture Volume", TDA7419_MAIN_SRC_REG, + TDA7419_MAIN_SRC_GAIN, 15, 0, tlv_src_gain), +SOC_SINGLE("Main Source AutoZero Switch", TDA7419_MAIN_SRC_REG, + TDA7419_MAIN_SRC_AUTOZERO, 1, 1), +SOC_SINGLE_TLV("Loudness Playback Volume", TDA7419_LOUDNESS_REG, + TDA7419_LOUDNESS_ATTEN, 15, 1, tlv_loudness_atten), +SOC_ENUM("Loudness Center Frequency", soc_enum_loudness_center_freq), +SOC_SINGLE("Loudness High Boost Switch", TDA7419_LOUDNESS_REG, + TDA7419_LOUDNESS_BOOST, 1, 1), +SOC_SINGLE("Loudness Soft Step Switch", TDA7419_LOUDNESS_REG, + TDA7419_LOUDNESS_SOFT_STEP, 1, 1), +SOC_SINGLE("Soft Mute Switch", TDA7419_MUTE_CLK_REG, TDA7419_SOFT_MUTE, 1, 1), +SOC_ENUM("Mute Influence", soc_enum_mute_influence), +SOC_ENUM("Soft Mute Time", soc_enum_soft_mute_time), +SOC_ENUM("Soft Step Time", soc_enum_soft_step_time), +SOC_SINGLE("Clock Fast Mode Switch", TDA7419_MUTE_CLK_REG, + TDA7419_CLK_FAST_MODE, 1, 1), +TDA7419_SINGLE_TLV("Master Playback Volume", TDA7419_VOLUME_REG, + 0x7f, -80, 15, 0x10, 0, tlv_volume), +SOC_SINGLE("Volume Soft Step Switch", TDA7419_VOLUME_REG, + TDA7419_VOLUME_SOFT_STEP, 1, 1), +TDA7419_SINGLE_TLV("Treble Playback Volume", TDA7419_TREBLE_REG, + 0x1f, -15, 15, 0x10, 1, tlv_filter), +SOC_ENUM("Treble Center Frequency", soc_enum_treble_center_freq), +SOC_ENUM("Reference Output Select", soc_enum_ref_out_select), +TDA7419_SINGLE_TLV("Middle Playback Volume", TDA7419_MIDDLE_REG, + 0x1f, -15, 15, 0x10, 1, tlv_filter), +SOC_ENUM("Middle Q Factor", soc_enum_middle_q_factor), +SOC_SINGLE("Middle Soft Step Switch", TDA7419_MIDDLE_REG, + TDA7419_MIDDLE_SOFT_STEP, 1, 1), +TDA7419_SINGLE_TLV("Bass Playback Volume", TDA7419_BASS_REG, + 0x1f, -15, 15, 0x10, 1, tlv_filter), +SOC_ENUM("Bass Q Factor", soc_enum_bass_q_factor), +SOC_SINGLE("Bass Soft Step Switch", TDA7419_BASS_REG, + TDA7419_BASS_SOFT_STEP, 1, 1), +SOC_SINGLE_TLV("Second Source Capture Volume", TDA7419_SECOND_SRC_REG, + TDA7419_SECOND_SRC_GAIN, 15, 0, tlv_src_gain), +SOC_ENUM("Subwoofer Cut-off Frequency", soc_enum_sub_cut_off_freq), +SOC_ENUM("Middle Center Frequency", soc_enum_middle_center_freq), +SOC_ENUM("Bass Center Frequency", soc_enum_bass_center_freq), +SOC_SINGLE("Bass DC Mode Switch", TDA7419_SUB_MID_BASS_REG, + TDA7419_BASS_DC_MODE, 1, 1), +SOC_SINGLE("Smoothing Filter Switch", TDA7419_SUB_MID_BASS_REG, + TDA7419_SMOOTHING_FILTER, 1, 1), +TDA7419_DOUBLE_R_TLV("Front Speaker Playback Volume", TDA7419_ATTENUATOR_LF_REG, + TDA7419_ATTENUATOR_RF_REG, 0x7f, -80, 15, 0x10, 0, + tlv_volume), +SOC_SINGLE("Left Front Soft Step Switch", TDA7419_ATTENUATOR_LF_REG, + TDA7419_VOLUME_SOFT_STEP, 1, 1), +SOC_SINGLE("Right Front Soft Step Switch", TDA7419_ATTENUATOR_RF_REG, + TDA7419_VOLUME_SOFT_STEP, 1, 1), +TDA7419_DOUBLE_R_TLV("Rear Speaker Playback Volume", TDA7419_ATTENUATOR_LR_REG, + TDA7419_ATTENUATOR_RR_REG, 0x7f, -80, 15, 0x10, 0, + tlv_volume), +SOC_SINGLE("Left Rear Soft Step Switch", TDA7419_ATTENUATOR_LR_REG, + TDA7419_VOLUME_SOFT_STEP, 1, 1), +SOC_SINGLE("Right Rear Soft Step Switch", TDA7419_ATTENUATOR_RR_REG, + TDA7419_VOLUME_SOFT_STEP, 1, 1), +TDA7419_SINGLE_TLV("Mixing Capture Volume", TDA7419_MIXING_LEVEL_REG, + 0x7f, -80, 15, 0x10, 0, tlv_volume), +SOC_SINGLE("Mixing Level Soft Step Switch", TDA7419_MIXING_LEVEL_REG, + TDA7419_VOLUME_SOFT_STEP, 1, 1), +TDA7419_SINGLE_TLV("Subwoofer Playback Volume", TDA7419_ATTENUATOR_SUB_REG, + 0x7f, -80, 15, 0x10, 0, tlv_volume), +SOC_SINGLE("Subwoofer Soft Step Switch", TDA7419_ATTENUATOR_SUB_REG, + TDA7419_VOLUME_SOFT_STEP, 1, 1), +SOC_ENUM("Spectrum Analyzer Q Factor", soc_enum_sa_q_factor), +SOC_ENUM("Spectrum Analyzer Reset Mode", soc_enum_reset_mode), +SOC_ENUM("Spectrum Analyzer Source", soc_enum_sa_src), +SOC_SINGLE("Spectrum Analyzer Run Switch", TDA7419_SA_CLK_AC_REG, + TDA7419_SA_RUN, 1, 1), +SOC_SINGLE("Spectrum Analyzer Reset Switch", TDA7419_SA_CLK_AC_REG, + TDA7419_RESET, 1, 1), +SOC_ENUM("Clock Source", soc_enum_clk_src), +SOC_ENUM("Coupling Mode", soc_enum_coupling_mode), +}; + +static const struct snd_kcontrol_new soc_mixer_lf_output_controls[] = { + SOC_DAPM_SINGLE("Mix to LF Speaker Switch", + TDA7419_MIXING_GAIN_REG, + TDA7419_MIX_LF, 1, 1), +}; + +static const struct snd_kcontrol_new soc_mixer_rf_output_controls[] = { + SOC_DAPM_SINGLE("Mix to RF Speaker Switch", + TDA7419_MIXING_GAIN_REG, + TDA7419_MIX_RF, 1, 1), +}; + +static const struct snd_kcontrol_new soc_mix_enable_switch_controls[] = { + SOC_DAPM_SINGLE("Switch", TDA7419_MIXING_GAIN_REG, + TDA7419_MIX_ENABLE, 1, 1), +}; + +static const struct snd_kcontrol_new soc_sub_enable_switch_controls[] = { + SOC_DAPM_SINGLE("Switch", TDA7419_MIXING_GAIN_REG, + TDA7419_MIX_ENABLE, 1, 1), +}; + +static const struct snd_soc_dapm_widget tda7419_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("SE3L"), + SND_SOC_DAPM_INPUT("SE3R"), + SND_SOC_DAPM_INPUT("SE2L"), + SND_SOC_DAPM_INPUT("SE2R"), + SND_SOC_DAPM_INPUT("SE1L"), + SND_SOC_DAPM_INPUT("SE1R"), + SND_SOC_DAPM_INPUT("DIFFL"), + SND_SOC_DAPM_INPUT("DIFFR"), + SND_SOC_DAPM_INPUT("MIX"), + + SND_SOC_DAPM_MUX("Main Source Select", SND_SOC_NOPM, + 0, 0, &soc_mux_main_src_sel), + SND_SOC_DAPM_MUX("Second Source Select", SND_SOC_NOPM, + 0, 0, &soc_mux_second_src_sel), + SND_SOC_DAPM_MUX("Rear Speaker Source", SND_SOC_NOPM, + 0, 0, &soc_mux_rear_spkr_src), + + SND_SOC_DAPM_SWITCH("Mix Enable", SND_SOC_NOPM, + 0, 0, &soc_mix_enable_switch_controls[0]), + SND_SOC_DAPM_MIXER_NAMED_CTL("LF Output Mixer", SND_SOC_NOPM, + 0, 0, &soc_mixer_lf_output_controls[0], + ARRAY_SIZE(soc_mixer_lf_output_controls)), + SND_SOC_DAPM_MIXER_NAMED_CTL("RF Output Mixer", SND_SOC_NOPM, + 0, 0, &soc_mixer_rf_output_controls[0], + ARRAY_SIZE(soc_mixer_rf_output_controls)), + + SND_SOC_DAPM_SWITCH("Subwoofer Enable", + SND_SOC_NOPM, 0, 0, + &soc_sub_enable_switch_controls[0]), + + SND_SOC_DAPM_OUTPUT("OUTLF"), + SND_SOC_DAPM_OUTPUT("OUTRF"), + SND_SOC_DAPM_OUTPUT("OUTLR"), + SND_SOC_DAPM_OUTPUT("OUTRR"), + SND_SOC_DAPM_OUTPUT("OUTSW"), +}; + +static const struct snd_soc_dapm_route tda7419_dapm_routes[] = { + {"Main Source Select", "SE3", "SE3L"}, + {"Main Source Select", "SE3", "SE3R"}, + {"Main Source Select", "SE2", "SE2L"}, + {"Main Source Select", "SE2", "SE2R"}, + {"Main Source Select", "SE1", "SE1L"}, + {"Main Source Select", "SE1", "SE1R"}, + {"Main Source Select", "SE", "DIFFL"}, + {"Main Source Select", "SE", "DIFFR"}, + {"Main Source Select", "QD", "DIFFL"}, + {"Main Source Select", "QD", "DIFFR"}, + + {"Second Source Select", "SE3", "SE3L"}, + {"Second Source Select", "SE3", "SE3R"}, + {"Second Source Select", "SE2", "SE2L"}, + {"Second Source Select", "SE2", "SE2R"}, + {"Second Source Select", "SE1", "SE1L"}, + {"Second Source Select", "SE1", "SE1R"}, + {"Second Source Select", "SE", "DIFFL"}, + {"Second Source Select", "SE", "DIFFR"}, + {"Second Source Select", "QD", "DIFFL"}, + {"Second Source Select", "QD", "DIFFR"}, + + {"Rear Speaker Source", "Main", "Main Source Select"}, + {"Rear Speaker Source", "Second", "Second Source Select"}, + + {"Subwoofer Enable", "Switch", "Main Source Select"}, + + {"Mix Enable", "Switch", "MIX"}, + + {"LF Output Mixer", NULL, "Main Source Select"}, + {"LF Output Mixer", "Mix to LF Speaker Switch", "Mix Enable"}, + {"RF Output Mixer", NULL, "Main Source Select"}, + {"RF Output Mixer", "Mix to RF Speaker Switch", "Mix Enable"}, + + {"OUTLF", NULL, "LF Output Mixer"}, + {"OUTRF", NULL, "RF Output Mixer"}, + {"OUTLR", NULL, "Rear Speaker Source"}, + {"OUTRR", NULL, "Rear Speaker Source"}, + {"OUTSW", NULL, "Subwoofer Enable"}, +}; + +static const struct snd_soc_component_driver tda7419_component_driver = { + .name = "tda7419", + .controls = tda7419_controls, + .num_controls = ARRAY_SIZE(tda7419_controls), + .dapm_widgets = tda7419_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tda7419_dapm_widgets), + .dapm_routes = tda7419_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tda7419_dapm_routes), +}; + +static int tda7419_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tda7419_data *tda7419; + int i, ret; + + tda7419 = devm_kzalloc(&i2c->dev, + sizeof(struct tda7419_data), + GFP_KERNEL); + if (tda7419 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, tda7419); + + tda7419->regmap = devm_regmap_init_i2c(i2c, &tda7419_regmap_config); + if (IS_ERR(tda7419->regmap)) { + ret = PTR_ERR(tda7419->regmap); + dev_err(&i2c->dev, "error initializing regmap: %d\n", + ret); + return ret; + } + + /* + * Reset registers to power-on defaults. The part does not provide a + * soft-reset function and the registers are not readable. This ensures + * that the cache matches register contents even if the registers have + * been previously initialized and not power cycled before probe. + */ + for (i = 0; i < ARRAY_SIZE(tda7419_regmap_defaults); i++) + regmap_write(tda7419->regmap, + tda7419_regmap_defaults[i].reg, + tda7419_regmap_defaults[i].def); + + ret = devm_snd_soc_register_component(&i2c->dev, + &tda7419_component_driver, NULL, 0); + if (ret < 0) { + dev_err(&i2c->dev, "error registering component: %d\n", + ret); + } + + return ret; +} + +static const struct i2c_device_id tda7419_i2c_id[] = { + { "tda7419", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tda7419_i2c_id); + +static const struct of_device_id tda7419_of_match[] = { + { .compatible = "st,tda7419" }, + { }, +}; + +static struct i2c_driver tda7419_driver = { + .driver = { + .name = "tda7419", + .of_match_table = tda7419_of_match, + }, + .probe = tda7419_probe, + .id_table = tda7419_i2c_id, +}; + +module_i2c_driver(tda7419_driver); + +MODULE_AUTHOR("Matt Porter "); +MODULE_DESCRIPTION("TDA7419 audio processor driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tfa9879.c b/sound/soc/codecs/tfa9879.c new file mode 100644 index 000000000..abc114a3a --- /dev/null +++ b/sound/soc/codecs/tfa9879.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// tfa9879.c -- driver for NXP Semiconductors TFA9879 +// +// Copyright (C) 2014 Axentia Technologies AB +// Author: Peter Rosin + +#include +#include +#include +#include +#include +#include +#include + +#include "tfa9879.h" + +struct tfa9879_priv { + struct regmap *regmap; + int lsb_justified; +}; + +static int tfa9879_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tfa9879_priv *tfa9879 = snd_soc_component_get_drvdata(component); + int fs; + int i2s_set = 0; + + switch (params_rate(params)) { + case 8000: + fs = TFA9879_I2S_FS_8000; + break; + case 11025: + fs = TFA9879_I2S_FS_11025; + break; + case 12000: + fs = TFA9879_I2S_FS_12000; + break; + case 16000: + fs = TFA9879_I2S_FS_16000; + break; + case 22050: + fs = TFA9879_I2S_FS_22050; + break; + case 24000: + fs = TFA9879_I2S_FS_24000; + break; + case 32000: + fs = TFA9879_I2S_FS_32000; + break; + case 44100: + fs = TFA9879_I2S_FS_44100; + break; + case 48000: + fs = TFA9879_I2S_FS_48000; + break; + case 64000: + fs = TFA9879_I2S_FS_64000; + break; + case 88200: + fs = TFA9879_I2S_FS_88200; + break; + case 96000: + fs = TFA9879_I2S_FS_96000; + break; + default: + return -EINVAL; + } + + switch (params_width(params)) { + case 16: + i2s_set = TFA9879_I2S_SET_LSB_J_16; + break; + case 24: + i2s_set = TFA9879_I2S_SET_LSB_J_24; + break; + default: + return -EINVAL; + } + + if (tfa9879->lsb_justified) + snd_soc_component_update_bits(component, + TFA9879_SERIAL_INTERFACE_1, + TFA9879_I2S_SET_MASK, + i2s_set << TFA9879_I2S_SET_SHIFT); + + snd_soc_component_update_bits(component, TFA9879_SERIAL_INTERFACE_1, + TFA9879_I2S_FS_MASK, + fs << TFA9879_I2S_FS_SHIFT); + return 0; +} + +static int tfa9879_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + snd_soc_component_update_bits(component, TFA9879_MISC_CONTROL, + TFA9879_S_MUTE_MASK, + !!mute << TFA9879_S_MUTE_SHIFT); + + return 0; +} + +static int tfa9879_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct tfa9879_priv *tfa9879 = snd_soc_component_get_drvdata(component); + int i2s_set; + int sck_pol; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + sck_pol = TFA9879_SCK_POL_NORMAL; + break; + case SND_SOC_DAIFMT_IB_NF: + sck_pol = TFA9879_SCK_POL_INVERSE; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + tfa9879->lsb_justified = 0; + i2s_set = TFA9879_I2S_SET_I2S_24; + break; + case SND_SOC_DAIFMT_LEFT_J: + tfa9879->lsb_justified = 0; + i2s_set = TFA9879_I2S_SET_MSB_J_24; + break; + case SND_SOC_DAIFMT_RIGHT_J: + tfa9879->lsb_justified = 1; + i2s_set = TFA9879_I2S_SET_LSB_J_24; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, TFA9879_SERIAL_INTERFACE_1, + TFA9879_SCK_POL_MASK, + sck_pol << TFA9879_SCK_POL_SHIFT); + snd_soc_component_update_bits(component, TFA9879_SERIAL_INTERFACE_1, + TFA9879_I2S_SET_MASK, + i2s_set << TFA9879_I2S_SET_SHIFT); + return 0; +} + +static const struct reg_default tfa9879_regs[] = { + { TFA9879_DEVICE_CONTROL, 0x0000 }, /* 0x00 */ + { TFA9879_SERIAL_INTERFACE_1, 0x0a18 }, /* 0x01 */ + { TFA9879_PCM_IOM2_FORMAT_1, 0x0007 }, /* 0x02 */ + { TFA9879_SERIAL_INTERFACE_2, 0x0a18 }, /* 0x03 */ + { TFA9879_PCM_IOM2_FORMAT_2, 0x0007 }, /* 0x04 */ + { TFA9879_EQUALIZER_A1, 0x59dd }, /* 0x05 */ + { TFA9879_EQUALIZER_A2, 0xc63e }, /* 0x06 */ + { TFA9879_EQUALIZER_B1, 0x651a }, /* 0x07 */ + { TFA9879_EQUALIZER_B2, 0xe53e }, /* 0x08 */ + { TFA9879_EQUALIZER_C1, 0x4616 }, /* 0x09 */ + { TFA9879_EQUALIZER_C2, 0xd33e }, /* 0x0a */ + { TFA9879_EQUALIZER_D1, 0x4df3 }, /* 0x0b */ + { TFA9879_EQUALIZER_D2, 0xea3e }, /* 0x0c */ + { TFA9879_EQUALIZER_E1, 0x5ee0 }, /* 0x0d */ + { TFA9879_EQUALIZER_E2, 0xf93e }, /* 0x0e */ + { TFA9879_BYPASS_CONTROL, 0x0093 }, /* 0x0f */ + { TFA9879_DYNAMIC_RANGE_COMPR, 0x92ba }, /* 0x10 */ + { TFA9879_BASS_TREBLE, 0x12a5 }, /* 0x11 */ + { TFA9879_HIGH_PASS_FILTER, 0x0004 }, /* 0x12 */ + { TFA9879_VOLUME_CONTROL, 0x10bd }, /* 0x13 */ + { TFA9879_MISC_CONTROL, 0x0000 }, /* 0x14 */ +}; + +static bool tfa9879_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == TFA9879_MISC_STATUS; +} + +static const DECLARE_TLV_DB_SCALE(volume_tlv, -7050, 50, 1); +static const DECLARE_TLV_DB_SCALE(tb_gain_tlv, -1800, 200, 0); +static const char * const tb_freq_text[] = { + "Low", "Mid", "High" +}; +static const struct soc_enum treble_freq_enum = + SOC_ENUM_SINGLE(TFA9879_BASS_TREBLE, TFA9879_F_TRBLE_SHIFT, + ARRAY_SIZE(tb_freq_text), tb_freq_text); +static const struct soc_enum bass_freq_enum = + SOC_ENUM_SINGLE(TFA9879_BASS_TREBLE, TFA9879_F_BASS_SHIFT, + ARRAY_SIZE(tb_freq_text), tb_freq_text); + +static const struct snd_kcontrol_new tfa9879_controls[] = { + SOC_SINGLE_TLV("PCM Playback Volume", TFA9879_VOLUME_CONTROL, + TFA9879_VOL_SHIFT, 0xbd, 1, volume_tlv), + SOC_SINGLE_TLV("Treble Volume", TFA9879_BASS_TREBLE, + TFA9879_G_TRBLE_SHIFT, 18, 0, tb_gain_tlv), + SOC_SINGLE_TLV("Bass Volume", TFA9879_BASS_TREBLE, + TFA9879_G_BASS_SHIFT, 18, 0, tb_gain_tlv), + SOC_ENUM("Treble Corner Freq", treble_freq_enum), + SOC_ENUM("Bass Corner Freq", bass_freq_enum), +}; + +static const struct snd_soc_dapm_widget tfa9879_dapm_widgets[] = { +SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_DAC("DAC", NULL, TFA9879_DEVICE_CONTROL, TFA9879_OPMODE_SHIFT, 0), +SND_SOC_DAPM_OUTPUT("LINEOUT"), +SND_SOC_DAPM_SUPPLY("POWER", TFA9879_DEVICE_CONTROL, TFA9879_POWERUP_SHIFT, 0, + NULL, 0), +}; + +static const struct snd_soc_dapm_route tfa9879_dapm_routes[] = { + { "DAC", NULL, "AIFINL" }, + { "DAC", NULL, "AIFINR" }, + + { "LINEOUT", NULL, "DAC" }, + + { "DAC", NULL, "POWER" }, +}; + +static const struct snd_soc_component_driver tfa9879_component = { + .controls = tfa9879_controls, + .num_controls = ARRAY_SIZE(tfa9879_controls), + .dapm_widgets = tfa9879_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tfa9879_dapm_widgets), + .dapm_routes = tfa9879_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tfa9879_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config tfa9879_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .volatile_reg = tfa9879_volatile_reg, + .max_register = TFA9879_MISC_STATUS, + .reg_defaults = tfa9879_regs, + .num_reg_defaults = ARRAY_SIZE(tfa9879_regs), + .cache_type = REGCACHE_RBTREE, +}; + +static const struct snd_soc_dai_ops tfa9879_dai_ops = { + .hw_params = tfa9879_hw_params, + .digital_mute = tfa9879_digital_mute, + .set_fmt = tfa9879_set_fmt, +}; + +#define TFA9879_RATES SNDRV_PCM_RATE_8000_96000 + +#define TFA9879_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver tfa9879_dai = { + .name = "tfa9879-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = TFA9879_RATES, + .formats = TFA9879_FORMATS, }, + .ops = &tfa9879_dai_ops, +}; + +static int tfa9879_i2c_probe(struct i2c_client *i2c) +{ + struct tfa9879_priv *tfa9879; + int i; + + tfa9879 = devm_kzalloc(&i2c->dev, sizeof(*tfa9879), GFP_KERNEL); + if (!tfa9879) + return -ENOMEM; + + i2c_set_clientdata(i2c, tfa9879); + + tfa9879->regmap = devm_regmap_init_i2c(i2c, &tfa9879_regmap); + if (IS_ERR(tfa9879->regmap)) + return PTR_ERR(tfa9879->regmap); + + /* Ensure the device is in reset state */ + for (i = 0; i < ARRAY_SIZE(tfa9879_regs); i++) + regmap_write(tfa9879->regmap, + tfa9879_regs[i].reg, tfa9879_regs[i].def); + + return devm_snd_soc_register_component(&i2c->dev, &tfa9879_component, + &tfa9879_dai, 1); +} + +static const struct i2c_device_id tfa9879_i2c_id[] = { + { "tfa9879", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tfa9879_i2c_id); + +static const struct of_device_id tfa9879_of_match[] = { + { .compatible = "nxp,tfa9879", }, + { } +}; +MODULE_DEVICE_TABLE(of, tfa9879_of_match); + +static struct i2c_driver tfa9879_i2c_driver = { + .driver = { + .name = "tfa9879", + .of_match_table = tfa9879_of_match, + }, + .probe_new = tfa9879_i2c_probe, + .id_table = tfa9879_i2c_id, +}; + +module_i2c_driver(tfa9879_i2c_driver); + +MODULE_DESCRIPTION("ASoC NXP Semiconductors TFA9879 driver"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tfa9879.h b/sound/soc/codecs/tfa9879.h new file mode 100644 index 000000000..66c88d039 --- /dev/null +++ b/sound/soc/codecs/tfa9879.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * tfa9879.h -- driver for NXP Semiconductors TFA9879 + * + * Copyright (C) 2014 Axentia Technologies AB + * Author: Peter Rosin + */ + +#ifndef _TFA9879_H +#define _TFA9879_H + +#define TFA9879_DEVICE_CONTROL 0x00 +#define TFA9879_SERIAL_INTERFACE_1 0x01 +#define TFA9879_PCM_IOM2_FORMAT_1 0x02 +#define TFA9879_SERIAL_INTERFACE_2 0x03 +#define TFA9879_PCM_IOM2_FORMAT_2 0x04 +#define TFA9879_EQUALIZER_A1 0x05 +#define TFA9879_EQUALIZER_A2 0x06 +#define TFA9879_EQUALIZER_B1 0x07 +#define TFA9879_EQUALIZER_B2 0x08 +#define TFA9879_EQUALIZER_C1 0x09 +#define TFA9879_EQUALIZER_C2 0x0a +#define TFA9879_EQUALIZER_D1 0x0b +#define TFA9879_EQUALIZER_D2 0x0c +#define TFA9879_EQUALIZER_E1 0x0d +#define TFA9879_EQUALIZER_E2 0x0e +#define TFA9879_BYPASS_CONTROL 0x0f +#define TFA9879_DYNAMIC_RANGE_COMPR 0x10 +#define TFA9879_BASS_TREBLE 0x11 +#define TFA9879_HIGH_PASS_FILTER 0x12 +#define TFA9879_VOLUME_CONTROL 0x13 +#define TFA9879_MISC_CONTROL 0x14 +#define TFA9879_MISC_STATUS 0x15 + +/* TFA9879_DEVICE_CONTROL */ +#define TFA9879_INPUT_SEL_MASK 0x0010 +#define TFA9879_INPUT_SEL_SHIFT 4 +#define TFA9879_OPMODE_MASK 0x0008 +#define TFA9879_OPMODE_SHIFT 3 +#define TFA9879_RESET_MASK 0x0002 +#define TFA9879_RESET_SHIFT 1 +#define TFA9879_POWERUP_MASK 0x0001 +#define TFA9879_POWERUP_SHIFT 0 + +/* TFA9879_SERIAL_INTERFACE */ +#define TFA9879_MONO_SEL_MASK 0x0c00 +#define TFA9879_MONO_SEL_SHIFT 10 +#define TFA9879_MONO_SEL_LEFT 0 +#define TFA9879_MONO_SEL_RIGHT 1 +#define TFA9879_MONO_SEL_BOTH 2 +#define TFA9879_I2S_FS_MASK 0x03c0 +#define TFA9879_I2S_FS_SHIFT 6 +#define TFA9879_I2S_FS_8000 0 +#define TFA9879_I2S_FS_11025 1 +#define TFA9879_I2S_FS_12000 2 +#define TFA9879_I2S_FS_16000 3 +#define TFA9879_I2S_FS_22050 4 +#define TFA9879_I2S_FS_24000 5 +#define TFA9879_I2S_FS_32000 6 +#define TFA9879_I2S_FS_44100 7 +#define TFA9879_I2S_FS_48000 8 +#define TFA9879_I2S_FS_64000 9 +#define TFA9879_I2S_FS_88200 10 +#define TFA9879_I2S_FS_96000 11 +#define TFA9879_I2S_SET_MASK 0x0038 +#define TFA9879_I2S_SET_SHIFT 3 +#define TFA9879_I2S_SET_MSB_J_24 2 +#define TFA9879_I2S_SET_I2S_24 3 +#define TFA9879_I2S_SET_LSB_J_16 4 +#define TFA9879_I2S_SET_LSB_J_18 5 +#define TFA9879_I2S_SET_LSB_J_20 6 +#define TFA9879_I2S_SET_LSB_J_24 7 +#define TFA9879_SCK_POL_MASK 0x0004 +#define TFA9879_SCK_POL_SHIFT 2 +#define TFA9879_SCK_POL_NORMAL 0 +#define TFA9879_SCK_POL_INVERSE 1 +#define TFA9879_I_MODE_MASK 0x0003 +#define TFA9879_I_MODE_SHIFT 0 +#define TFA9879_I_MODE_I2S 0 +#define TFA9879_I_MODE_PCM_IOM2_SHORT 1 +#define TFA9879_I_MODE_PCM_IOM2_LONG 2 + +/* TFA9879_PCM_IOM2_FORMAT */ +#define TFA9879_PCM_FS_MASK 0x0800 +#define TFA9879_PCM_FS_SHIFT 11 +#define TFA9879_A_LAW_MASK 0x0400 +#define TFA9879_A_LAW_SHIFT 10 +#define TFA9879_PCM_COMP_MASK 0x0200 +#define TFA9879_PCM_COMP_SHIFT 9 +#define TFA9879_PCM_DL_MASK 0x0100 +#define TFA9879_PCM_DL_SHIFT 8 +#define TFA9879_D1_SLOT_MASK 0x00f0 +#define TFA9879_D1_SLOT_SHIFT 4 +#define TFA9879_D2_SLOT_MASK 0x000f +#define TFA9879_D2_SLOT_SHIFT 0 + +/* TFA9879_EQUALIZER_X1 */ +#define TFA9879_T1_MASK 0x8000 +#define TFA9879_T1_SHIFT 15 +#define TFA9879_K1M_MASK 0x7ff0 +#define TFA9879_K1M_SHIFT 4 +#define TFA9879_K1E_MASK 0x000f +#define TFA9879_K1E_SHIFT 0 + +/* TFA9879_EQUALIZER_X2 */ +#define TFA9879_T2_MASK 0x8000 +#define TFA9879_T2_SHIFT 15 +#define TFA9879_K2M_MASK 0x7800 +#define TFA9879_K2M_SHIFT 11 +#define TFA9879_K2E_MASK 0x0700 +#define TFA9879_K2E_SHIFT 8 +#define TFA9879_K0_MASK 0x00fe +#define TFA9879_K0_SHIFT 1 +#define TFA9879_S_MASK 0x0001 +#define TFA9879_S_SHIFT 0 + +/* TFA9879_BYPASS_CONTROL */ +#define TFA9879_L_OCP_MASK 0x00c0 +#define TFA9879_L_OCP_SHIFT 6 +#define TFA9879_L_OTP_MASK 0x0030 +#define TFA9879_L_OTP_SHIFT 4 +#define TFA9879_CLIPCTRL_MASK 0x0008 +#define TFA9879_CLIPCTRL_SHIFT 3 +#define TFA9879_HPF_BP_MASK 0x0004 +#define TFA9879_HPF_BP_SHIFT 2 +#define TFA9879_DRC_BP_MASK 0x0002 +#define TFA9879_DRC_BP_SHIFT 1 +#define TFA9879_EQ_BP_MASK 0x0001 +#define TFA9879_EQ_BP_SHIFT 0 + +/* TFA9879_DYNAMIC_RANGE_COMPR */ +#define TFA9879_AT_LVL_MASK 0xf000 +#define TFA9879_AT_LVL_SHIFT 12 +#define TFA9879_AT_RATE_MASK 0x0f00 +#define TFA9879_AT_RATE_SHIFT 8 +#define TFA9879_RL_LVL_MASK 0x00f0 +#define TFA9879_RL_LVL_SHIFT 4 +#define TFA9879_RL_RATE_MASK 0x000f +#define TFA9879_RL_RATE_SHIFT 0 + +/* TFA9879_BASS_TREBLE */ +#define TFA9879_G_TRBLE_MASK 0x3e00 +#define TFA9879_G_TRBLE_SHIFT 9 +#define TFA9879_F_TRBLE_MASK 0x0180 +#define TFA9879_F_TRBLE_SHIFT 7 +#define TFA9879_G_BASS_MASK 0x007c +#define TFA9879_G_BASS_SHIFT 2 +#define TFA9879_F_BASS_MASK 0x0003 +#define TFA9879_F_BASS_SHIFT 0 + +/* TFA9879_HIGH_PASS_FILTER */ +#define TFA9879_HP_CTRL_MASK 0x00ff +#define TFA9879_HP_CTRL_SHIFT 0 + +/* TFA9879_VOLUME_CONTROL */ +#define TFA9879_ZR_CRSS_MASK 0x1000 +#define TFA9879_ZR_CRSS_SHIFT 12 +#define TFA9879_VOL_MASK 0x00ff +#define TFA9879_VOL_SHIFT 0 + +/* TFA9879_MISC_CONTROL */ +#define TFA9879_DE_PHAS_MASK 0x0c00 +#define TFA9879_DE_PHAS_SHIFT 10 +#define TFA9879_H_MUTE_MASK 0x0200 +#define TFA9879_H_MUTE_SHIFT 9 +#define TFA9879_S_MUTE_MASK 0x0100 +#define TFA9879_S_MUTE_SHIFT 8 +#define TFA9879_P_LIM_MASK 0x00ff +#define TFA9879_P_LIM_SHIFT 0 + +/* TFA9879_MISC_STATUS */ +#define TFA9879_PS_MASK 0x4000 +#define TFA9879_PS_SHIFT 14 +#define TFA9879_PORA_MASK 0x2000 +#define TFA9879_PORA_SHIFT 13 +#define TFA9879_AMP_MASK 0x0600 +#define TFA9879_AMP_SHIFT 9 +#define TFA9879_IBP_2_MASK 0x0100 +#define TFA9879_IBP_2_SHIFT 8 +#define TFA9879_OFP_2_MASK 0x0080 +#define TFA9879_OFP_2_SHIFT 7 +#define TFA9879_UFP_2_MASK 0x0040 +#define TFA9879_UFP_2_SHIFT 6 +#define TFA9879_IBP_1_MASK 0x0020 +#define TFA9879_IBP_1_SHIFT 5 +#define TFA9879_OFP_1_MASK 0x0010 +#define TFA9879_OFP_1_SHIFT 4 +#define TFA9879_UFP_1_MASK 0x0008 +#define TFA9879_UFP_1_SHIFT 3 +#define TFA9879_OCPOKA_MASK 0x0004 +#define TFA9879_OCPOKA_SHIFT 2 +#define TFA9879_OCPOKB_MASK 0x0002 +#define TFA9879_OCPOKB_SHIFT 1 +#define TFA9879_OTPOK_MASK 0x0001 +#define TFA9879_OTPOK_SHIFT 0 + +#endif diff --git a/sound/soc/codecs/tlv320aic23-i2c.c b/sound/soc/codecs/tlv320aic23-i2c.c new file mode 100644 index 000000000..1d7c11731 --- /dev/null +++ b/sound/soc/codecs/tlv320aic23-i2c.c @@ -0,0 +1,60 @@ +/* + * ALSA SoC TLV320AIC23 codec driver I2C interface + * + * Author: Arun KS, + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd., + * + * Based on sound/soc/codecs/wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include "tlv320aic23.h" + +static int tlv320aic23_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct regmap *regmap; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + regmap = devm_regmap_init_i2c(i2c, &tlv320aic23_regmap); + return tlv320aic23_probe(&i2c->dev, regmap); +} + +static const struct i2c_device_id tlv320aic23_id[] = { + {"tlv320aic23", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tlv320aic23_id); + +static const struct of_device_id tlv320aic23_of_match[] = { + { .compatible = "ti,tlv320aic23", }, + { } +}; +MODULE_DEVICE_TABLE(of, tlv320aic23_of_match); + +static struct i2c_driver tlv320aic23_i2c_driver = { + .driver = { + .name = "tlv320aic23-codec", + .of_match_table = of_match_ptr(tlv320aic23_of_match), + }, + .probe = tlv320aic23_i2c_probe, + .id_table = tlv320aic23_id, +}; + +module_i2c_driver(tlv320aic23_i2c_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver I2C"); +MODULE_AUTHOR("Arun KS "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic23-spi.c b/sound/soc/codecs/tlv320aic23-spi.c new file mode 100644 index 000000000..d8c9ec1e9 --- /dev/null +++ b/sound/soc/codecs/tlv320aic23-spi.c @@ -0,0 +1,48 @@ +/* + * ALSA SoC TLV320AIC23 codec driver SPI interface + * + * Author: Arun KS, + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd., + * + * Based on sound/soc/codecs/wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include "tlv320aic23.h" + +static int aic23_spi_probe(struct spi_device *spi) +{ + int ret; + struct regmap *regmap; + + dev_dbg(&spi->dev, "probing tlv320aic23 spi device\n"); + + spi->mode = SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + regmap = devm_regmap_init_spi(spi, &tlv320aic23_regmap); + return tlv320aic23_probe(&spi->dev, regmap); +} + +static struct spi_driver aic23_spi = { + .driver = { + .name = "tlv320aic23", + }, + .probe = aic23_spi_probe, +}; + +module_spi_driver(aic23_spi); + +MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver SPI"); +MODULE_AUTHOR("Arun KS "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c new file mode 100644 index 000000000..47480cb4d --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.c @@ -0,0 +1,620 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd., + * + * Based on sound/soc/codecs/wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Notes: + * The AIC23 is a driver for a low power stereo audio + * codec tlv320aic23 + * + * The machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tlv320aic23.h" + +/* + * AIC23 register cache + */ +static const struct reg_default tlv320aic23_reg[] = { + { 0, 0x0097 }, + { 1, 0x0097 }, + { 2, 0x00F9 }, + { 3, 0x00F9 }, + { 4, 0x001A }, + { 5, 0x0004 }, + { 6, 0x0007 }, + { 7, 0x0001 }, + { 8, 0x0020 }, + { 9, 0x0000 }, +}; + +const struct regmap_config tlv320aic23_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = TLV320AIC23_RESET, + .reg_defaults = tlv320aic23_reg, + .num_reg_defaults = ARRAY_SIZE(tlv320aic23_reg), + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL(tlv320aic23_regmap); + +static const char *rec_src_text[] = { "Line", "Mic" }; +static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static SOC_ENUM_SINGLE_DECL(rec_src_enum, + TLV320AIC23_ANLG, 2, rec_src_text); + +static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls = +SOC_DAPM_ENUM("Input Select", rec_src_enum); + +static SOC_ENUM_SINGLE_DECL(tlv320aic23_rec_src, + TLV320AIC23_ANLG, 2, rec_src_text); +static SOC_ENUM_SINGLE_DECL(tlv320aic23_deemph, + TLV320AIC23_DIGT, 1, deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0); +static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0); + +static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + u16 val, reg; + + val = (ucontrol->value.integer.value[0] & 0x07); + + /* linear conversion to userspace + * 000 = -6db + * 001 = -9db + * 010 = -12db + * 011 = -18db (Min) + * 100 = 0db (Max) + */ + val = (val >= 4) ? 4 : (3 - val); + + reg = snd_soc_component_read32(component, TLV320AIC23_ANLG) & (~0x1C0); + snd_soc_component_write(component, TLV320AIC23_ANLG, reg | (val << 6)); + + return 0; +} + +static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + u16 val; + + val = snd_soc_component_read32(component, TLV320AIC23_ANLG) & (0x1C0); + val = val >> 6; + val = (val >= 4) ? 4 : (3 - val); + ucontrol->value.integer.value[0] = val; + return 0; + +} + +static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL, + TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv), + SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1), + SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv), + SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1), + SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0), + SOC_SINGLE_EXT_TLV("Sidetone Volume", TLV320AIC23_ANLG, 6, 4, 0, + snd_soc_tlv320aic23_get_volsw, + snd_soc_tlv320aic23_put_volsw, sidetone_vol_tlv), + SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph), +}; + +/* PGA Mixer controls for Line and Mic switch */ +static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1), + SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0, + &tlv320aic23_rec_src_mux_controls), + SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1, + &tlv320aic23_output_mixer_controls[0], + ARRAY_SIZE(tlv320aic23_output_mixer_controls)), + SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LHPOUT"), + SND_SOC_DAPM_OUTPUT("RHPOUT"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("LLINEIN"), + SND_SOC_DAPM_INPUT("RLINEIN"), + + SND_SOC_DAPM_INPUT("MICIN"), +}; + +static const struct snd_soc_dapm_route tlv320aic23_intercon[] = { + /* Output Mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Input"}, + + /* Outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + + /* Inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Input", NULL, "MICIN"}, + + /* input mux */ + {"Capture Source", "Line", "Line Input"}, + {"Capture Source", "Mic", "Mic Input"}, + {"ADC", NULL, "Capture Source"}, + +}; + +/* AIC23 driver data */ +struct aic23 { + struct regmap *regmap; + int mclk; + int requested_adc; + int requested_dac; +}; + +/* + * Common Crystals used + * 11.2896 Mhz /128 = *88.2k /192 = 58.8k + * 12.0000 Mhz /125 = *96k /136 = 88.235K + * 12.2880 Mhz /128 = *96k /192 = 64k + * 16.9344 Mhz /128 = 132.3k /192 = *88.2k + * 18.4320 Mhz /128 = 144k /192 = *96k + */ + +/* + * Normal BOSR 0-256/2 = 128, 1-384/2 = 192 + * USB BOSR 0-250/2 = 125, 1-272/2 = 136 + */ +static const int bosr_usb_divisor_table[] = { + 128, 125, 192, 136 +}; +#define LOWER_GROUP ((1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<6) | (1<<7)) +#define UPPER_GROUP ((1<<8) | (1<<9) | (1<<10) | (1<<11) | (1<<15)) +static const unsigned short sr_valid_mask[] = { + LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 0*/ + LOWER_GROUP, /* Usb, bosr - 0*/ + LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 1*/ + UPPER_GROUP, /* Usb, bosr - 1*/ +}; +/* + * Every divisor is a factor of 11*12 + */ +#define SR_MULT (11*12) +#define A(x) (SR_MULT/x) +static const unsigned char sr_adc_mult_table[] = { + A(2), A(2), A(12), A(12), 0, 0, A(3), A(1), + A(2), A(2), A(11), A(11), 0, 0, 0, A(1) +}; +static const unsigned char sr_dac_mult_table[] = { + A(2), A(12), A(2), A(12), 0, 0, A(3), A(1), + A(2), A(11), A(2), A(11), 0, 0, 0, A(1) +}; + +static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc, + int dac, int dac_l, int dac_h, int need_dac) +{ + if ((adc >= adc_l) && (adc <= adc_h) && + (dac >= dac_l) && (dac <= dac_h)) { + int diff_adc = need_adc - adc; + int diff_dac = need_dac - dac; + return abs(diff_adc) + abs(diff_dac); + } + return UINT_MAX; +} + +static int find_rate(int mclk, u32 need_adc, u32 need_dac) +{ + int i, j; + int best_i = -1; + int best_j = -1; + int best_div = 0; + unsigned best_score = UINT_MAX; + int adc_l, adc_h, dac_l, dac_h; + + need_adc *= SR_MULT; + need_dac *= SR_MULT; + /* + * rates given are +/- 1/32 + */ + adc_l = need_adc - (need_adc >> 5); + adc_h = need_adc + (need_adc >> 5); + dac_l = need_dac - (need_dac >> 5); + dac_h = need_dac + (need_dac >> 5); + for (i = 0; i < ARRAY_SIZE(bosr_usb_divisor_table); i++) { + int base = mclk / bosr_usb_divisor_table[i]; + int mask = sr_valid_mask[i]; + for (j = 0; j < ARRAY_SIZE(sr_adc_mult_table); + j++, mask >>= 1) { + int adc; + int dac; + int score; + if ((mask & 1) == 0) + continue; + adc = base * sr_adc_mult_table[j]; + dac = base * sr_dac_mult_table[j]; + score = get_score(adc, adc_l, adc_h, need_adc, + dac, dac_l, dac_h, need_dac); + if (best_score > score) { + best_score = score; + best_i = i; + best_j = j; + best_div = 0; + } + score = get_score((adc >> 1), adc_l, adc_h, need_adc, + (dac >> 1), dac_l, dac_h, need_dac); + /* prefer to have a /2 */ + if ((score != UINT_MAX) && (best_score >= score)) { + best_score = score; + best_i = i; + best_j = j; + best_div = 1; + } + } + } + return (best_j << 2) | best_i | (best_div << TLV320AIC23_CLKIN_SHIFT); +} + +#ifdef DEBUG +static void get_current_sample_rates(struct snd_soc_component *component, int mclk, + u32 *sample_rate_adc, u32 *sample_rate_dac) +{ + int src = snd_soc_component_read32(component, TLV320AIC23_SRATE); + int sr = (src >> 2) & 0x0f; + int val = (mclk / bosr_usb_divisor_table[src & 3]); + int adc = (val * sr_adc_mult_table[sr]) / SR_MULT; + int dac = (val * sr_dac_mult_table[sr]) / SR_MULT; + if (src & TLV320AIC23_CLKIN_HALF) { + adc >>= 1; + dac >>= 1; + } + *sample_rate_adc = adc; + *sample_rate_dac = dac; +} +#endif + +static int set_sample_rate_control(struct snd_soc_component *component, int mclk, + u32 sample_rate_adc, u32 sample_rate_dac) +{ + /* Search for the right sample rate */ + int data = find_rate(mclk, sample_rate_adc, sample_rate_dac); + if (data < 0) { + printk(KERN_ERR "%s:Invalid rate %u,%u requested\n", + __func__, sample_rate_adc, sample_rate_dac); + return -EINVAL; + } + snd_soc_component_write(component, TLV320AIC23_SRATE, data); +#ifdef DEBUG + { + u32 adc, dac; + get_current_sample_rates(component, mclk, &adc, &dac); + printk(KERN_DEBUG "actual samplerate = %u,%u reg=%x\n", + adc, dac, data); + } +#endif + return 0; +} + +static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 iface_reg; + int ret; + struct aic23 *aic23 = snd_soc_component_get_drvdata(component); + u32 sample_rate_adc = aic23->requested_adc; + u32 sample_rate_dac = aic23->requested_dac; + u32 sample_rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aic23->requested_dac = sample_rate_dac = sample_rate; + if (!sample_rate_adc) + sample_rate_adc = sample_rate; + } else { + aic23->requested_adc = sample_rate_adc = sample_rate; + if (!sample_rate_dac) + sample_rate_dac = sample_rate; + } + ret = set_sample_rate_control(component, aic23->mclk, sample_rate_adc, + sample_rate_dac); + if (ret < 0) + return ret; + + iface_reg = snd_soc_component_read32(component, TLV320AIC23_DIGT_FMT) & ~(0x03 << 2); + + switch (params_width(params)) { + case 16: + break; + case 20: + iface_reg |= (0x01 << 2); + break; + case 24: + iface_reg |= (0x02 << 2); + break; + case 32: + iface_reg |= (0x03 << 2); + break; + } + snd_soc_component_write(component, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + + /* set active */ + snd_soc_component_write(component, TLV320AIC23_ACTIVE, 0x0001); + + return 0; +} + +static void tlv320aic23_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aic23 *aic23 = snd_soc_component_get_drvdata(component); + + /* deactivate */ + if (!snd_soc_component_is_active(component)) { + udelay(50); + snd_soc_component_write(component, TLV320AIC23_ACTIVE, 0x0); + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aic23->requested_dac = 0; + else + aic23->requested_adc = 0; +} + +static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 reg; + + reg = snd_soc_component_read32(component, TLV320AIC23_DIGT); + if (mute) + reg |= TLV320AIC23_DACM_MUTE; + + else + reg &= ~TLV320AIC23_DACM_MUTE; + + snd_soc_component_write(component, TLV320AIC23_DIGT, reg); + + return 0; +} + +static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface_reg; + + iface_reg = snd_soc_component_read32(component, TLV320AIC23_DIGT_FMT) & (~0x03); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface_reg |= TLV320AIC23_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface_reg &= ~TLV320AIC23_MS_MASTER; + break; + default: + return -EINVAL; + + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface_reg |= TLV320AIC23_FOR_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg |= TLV320AIC23_LRP_ON; + /* fall through */ + case SND_SOC_DAIFMT_DSP_B: + iface_reg |= TLV320AIC23_FOR_DSP; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg |= TLV320AIC23_FOR_LJUST; + break; + default: + return -EINVAL; + + } + + snd_soc_component_write(component, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct aic23 *aic23 = snd_soc_dai_get_drvdata(codec_dai); + aic23->mclk = freq; + return 0; +} + +static int tlv320aic23_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + u16 reg = snd_soc_component_read32(component, TLV320AIC23_PWR) & 0x17f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + reg &= ~(TLV320AIC23_DEVICE_PWR_OFF | TLV320AIC23_OSC_OFF | \ + TLV320AIC23_DAC_OFF); + snd_soc_component_write(component, TLV320AIC23_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + snd_soc_component_write(component, TLV320AIC23_PWR, + reg | TLV320AIC23_CLK_OFF); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + snd_soc_component_write(component, TLV320AIC23_ACTIVE, 0x0); + snd_soc_component_write(component, TLV320AIC23_PWR, 0x1ff); + break; + } + return 0; +} + +#define AIC23_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops tlv320aic23_dai_ops = { + .prepare = tlv320aic23_pcm_prepare, + .hw_params = tlv320aic23_hw_params, + .shutdown = tlv320aic23_shutdown, + .digital_mute = tlv320aic23_mute, + .set_fmt = tlv320aic23_set_dai_fmt, + .set_sysclk = tlv320aic23_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver tlv320aic23_dai = { + .name = "tlv320aic23-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .ops = &tlv320aic23_dai_ops, +}; + +static int tlv320aic23_resume(struct snd_soc_component *component) +{ + struct aic23 *aic23 = snd_soc_component_get_drvdata(component); + regcache_mark_dirty(aic23->regmap); + regcache_sync(aic23->regmap); + + return 0; +} + +static int tlv320aic23_component_probe(struct snd_soc_component *component) +{ + /* Reset codec */ + snd_soc_component_write(component, TLV320AIC23_RESET, 0); + + snd_soc_component_write(component, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K); + + /* Unmute input */ + snd_soc_component_update_bits(component, TLV320AIC23_LINVOL, + TLV320AIC23_LIM_MUTED, TLV320AIC23_LRS_ENABLED); + + snd_soc_component_update_bits(component, TLV320AIC23_RINVOL, + TLV320AIC23_LIM_MUTED, TLV320AIC23_LRS_ENABLED); + + snd_soc_component_update_bits(component, TLV320AIC23_ANLG, + TLV320AIC23_BYPASS_ON | TLV320AIC23_MICM_MUTED, + 0); + + /* Default output volume */ + snd_soc_component_write(component, TLV320AIC23_LCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & TLV320AIC23_OUT_VOL_MASK); + snd_soc_component_write(component, TLV320AIC23_RCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & TLV320AIC23_OUT_VOL_MASK); + + snd_soc_component_write(component, TLV320AIC23_ACTIVE, 0x1); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_tlv320aic23 = { + .probe = tlv320aic23_component_probe, + .resume = tlv320aic23_resume, + .set_bias_level = tlv320aic23_set_bias_level, + .controls = tlv320aic23_snd_controls, + .num_controls = ARRAY_SIZE(tlv320aic23_snd_controls), + .dapm_widgets = tlv320aic23_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), + .dapm_routes = tlv320aic23_intercon, + .num_dapm_routes = ARRAY_SIZE(tlv320aic23_intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +int tlv320aic23_probe(struct device *dev, struct regmap *regmap) +{ + struct aic23 *aic23; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + aic23 = devm_kzalloc(dev, sizeof(struct aic23), GFP_KERNEL); + if (aic23 == NULL) + return -ENOMEM; + + aic23->regmap = regmap; + + dev_set_drvdata(dev, aic23); + + return devm_snd_soc_register_component(dev, + &soc_component_dev_tlv320aic23, + &tlv320aic23_dai, 1); +} +EXPORT_SYMBOL(tlv320aic23_probe); + +MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver"); +MODULE_AUTHOR("Arun KS "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic23.h b/sound/soc/codecs/tlv320aic23.h new file mode 100644 index 000000000..3a7235a04 --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.h @@ -0,0 +1,125 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _TLV320AIC23_H +#define _TLV320AIC23_H + +struct device; +struct regmap_config; + +extern const struct regmap_config tlv320aic23_regmap; +int tlv320aic23_probe(struct device *dev, struct regmap *regmap); + +/* Codec TLV320AIC23 */ +#define TLV320AIC23_LINVOL 0x00 +#define TLV320AIC23_RINVOL 0x01 +#define TLV320AIC23_LCHNVOL 0x02 +#define TLV320AIC23_RCHNVOL 0x03 +#define TLV320AIC23_ANLG 0x04 +#define TLV320AIC23_DIGT 0x05 +#define TLV320AIC23_PWR 0x06 +#define TLV320AIC23_DIGT_FMT 0x07 +#define TLV320AIC23_SRATE 0x08 +#define TLV320AIC23_ACTIVE 0x09 +#define TLV320AIC23_RESET 0x0F + +/* Left (right) line input volume control register */ +#define TLV320AIC23_LRS_ENABLED 0x0100 +#define TLV320AIC23_LIM_MUTED 0x0080 +#define TLV320AIC23_LIV_DEFAULT 0x0017 +#define TLV320AIC23_LIV_MAX 0x001f +#define TLV320AIC23_LIV_MIN 0x0000 + +/* Left (right) channel headphone volume control register */ +#define TLV320AIC23_LZC_ON 0x0080 +#define TLV320AIC23_LHV_DEFAULT 0x0079 +#define TLV320AIC23_LHV_MAX 0x007f +#define TLV320AIC23_LHV_MIN 0x0000 + +/* Analog audio path control register */ +#define TLV320AIC23_STA_REG(x) ((x)<<6) +#define TLV320AIC23_STE_ENABLED 0x0020 +#define TLV320AIC23_DAC_SELECTED 0x0010 +#define TLV320AIC23_BYPASS_ON 0x0008 +#define TLV320AIC23_INSEL_MIC 0x0004 +#define TLV320AIC23_MICM_MUTED 0x0002 +#define TLV320AIC23_MICB_20DB 0x0001 + +/* Digital audio path control register */ +#define TLV320AIC23_DACM_MUTE 0x0008 +#define TLV320AIC23_DEEMP_32K 0x0002 +#define TLV320AIC23_DEEMP_44K 0x0004 +#define TLV320AIC23_DEEMP_48K 0x0006 +#define TLV320AIC23_ADCHP_ON 0x0001 + +/* Power control down register */ +#define TLV320AIC23_DEVICE_PWR_OFF 0x0080 +#define TLV320AIC23_CLK_OFF 0x0040 +#define TLV320AIC23_OSC_OFF 0x0020 +#define TLV320AIC23_OUT_OFF 0x0010 +#define TLV320AIC23_DAC_OFF 0x0008 +#define TLV320AIC23_ADC_OFF 0x0004 +#define TLV320AIC23_MIC_OFF 0x0002 +#define TLV320AIC23_LINE_OFF 0x0001 + +/* Digital audio interface register */ +#define TLV320AIC23_MS_MASTER 0x0040 +#define TLV320AIC23_LRSWAP_ON 0x0020 +#define TLV320AIC23_LRP_ON 0x0010 +#define TLV320AIC23_IWL_16 0x0000 +#define TLV320AIC23_IWL_20 0x0004 +#define TLV320AIC23_IWL_24 0x0008 +#define TLV320AIC23_IWL_32 0x000C +#define TLV320AIC23_FOR_I2S 0x0002 +#define TLV320AIC23_FOR_DSP 0x0003 +#define TLV320AIC23_FOR_LJUST 0x0001 + +/* Sample rate control register */ +#define TLV320AIC23_CLKOUT_HALF 0x0080 +#define TLV320AIC23_CLKIN_HALF 0x0040 +#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */ +#define TLV320AIC23_USB_CLK_ON 0x0001 +#define TLV320AIC23_SR_MASK 0xf +#define TLV320AIC23_CLKOUT_SHIFT 7 +#define TLV320AIC23_CLKIN_SHIFT 6 +#define TLV320AIC23_SR_SHIFT 2 +#define TLV320AIC23_BOSR_SHIFT 1 + +/* Digital interface register */ +#define TLV320AIC23_ACT_ON 0x0001 + +/* + * AUDIO related MACROS + */ + +#define TLV320AIC23_DEFAULT_OUT_VOL 0x70 +#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10 + +#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN +#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX +#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \ + TLV320AIC23_OUT_VOL_MIN) +#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX + +#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN +#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX +#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \ + TLV320AIC23_IN_VOL_MIN) +#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX + +#define TLV320AIC23_SIDETONE_MASK 0x1c0 +#define TLV320AIC23_SIDETONE_0 0x100 +#define TLV320AIC23_SIDETONE_6 0x000 +#define TLV320AIC23_SIDETONE_9 0x040 +#define TLV320AIC23_SIDETONE_12 0x080 +#define TLV320AIC23_SIDETONE_18 0x0c0 + +#endif /* _TLV320AIC23_H */ diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c new file mode 100644 index 000000000..b91b8d5f1 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.c @@ -0,0 +1,378 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * ALSA SoC CODEC driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tlv320aic26.h" + +MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver"); +MODULE_AUTHOR("Grant Likely "); +MODULE_LICENSE("GPL"); + +/* AIC26 driver private data */ +struct aic26 { + struct spi_device *spi; + struct regmap *regmap; + struct snd_soc_component *component; + int master; + int datfm; + int mclk; + + /* Keyclick parameters */ + int keyclick_amplitude; + int keyclick_freq; + int keyclick_len; +}; + +static const struct snd_soc_dapm_widget tlv320aic26_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("MICIN"), +SND_SOC_DAPM_INPUT("AUX"), + +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_route tlv320aic26_dapm_routes[] = { + { "Capture", NULL, "MICIN" }, + { "Capture", NULL, "AUX" }, + + { "HPL", NULL, "Playback" }, + { "HPR", NULL, "Playback" }, +}; + +/* --------------------------------------------------------------------- + * Digital Audio Interface Operations + */ +static int aic26_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aic26 *aic26 = snd_soc_component_get_drvdata(component); + int fsref, divisor, wlen, pval, jval, dval, qval; + u16 reg; + + dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n", + substream, params); + dev_dbg(&aic26->spi->dev, "rate=%i width=%d\n", params_rate(params), + params_width(params)); + + switch (params_rate(params)) { + case 8000: fsref = 48000; divisor = AIC26_DIV_6; break; + case 11025: fsref = 44100; divisor = AIC26_DIV_4; break; + case 12000: fsref = 48000; divisor = AIC26_DIV_4; break; + case 16000: fsref = 48000; divisor = AIC26_DIV_3; break; + case 22050: fsref = 44100; divisor = AIC26_DIV_2; break; + case 24000: fsref = 48000; divisor = AIC26_DIV_2; break; + case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break; + case 44100: fsref = 44100; divisor = AIC26_DIV_1; break; + case 48000: fsref = 48000; divisor = AIC26_DIV_1; break; + default: + dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL; + } + + /* select data word length */ + switch (params_width(params)) { + case 8: wlen = AIC26_WLEN_16; break; + case 16: wlen = AIC26_WLEN_16; break; + case 24: wlen = AIC26_WLEN_24; break; + case 32: wlen = AIC26_WLEN_32; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + /** + * Configure PLL + * fsref = (mclk * PLLM) / 2048 + * where PLLM = J.DDDD (DDDD register ranges from 0 to 9999, decimal) + */ + pval = 1; + /* compute J portion of multiplier */ + jval = fsref / (aic26->mclk / 2048); + /* compute fractional DDDD component of multiplier */ + dval = fsref - (jval * (aic26->mclk / 2048)); + dval = (10000 * dval) / (aic26->mclk / 2048); + dev_dbg(&aic26->spi->dev, "Setting PLLM to %d.%04d\n", jval, dval); + qval = 0; + reg = 0x8000 | qval << 11 | pval << 8 | jval << 2; + snd_soc_component_write(component, AIC26_REG_PLL_PROG1, reg); + reg = dval << 2; + snd_soc_component_write(component, AIC26_REG_PLL_PROG2, reg); + + /* Audio Control 3 (master mode, fsref rate) */ + if (aic26->master) + reg = 0x0800; + if (fsref == 48000) + reg = 0x2000; + snd_soc_component_update_bits(component, AIC26_REG_AUDIO_CTRL3, 0xf800, reg); + + /* Audio Control 1 (FSref divisor) */ + reg = wlen | aic26->datfm | (divisor << 3) | divisor; + snd_soc_component_update_bits(component, AIC26_REG_AUDIO_CTRL1, 0xfff, reg); + + return 0; +} + +/** + * aic26_mute - Mute control to reduce noise when changing audio format + */ +static int aic26_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + struct aic26 *aic26 = snd_soc_component_get_drvdata(component); + u16 reg; + + dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", + dai, mute); + + if (mute) + reg = 0x8080; + else + reg = 0; + snd_soc_component_update_bits(component, AIC26_REG_DAC_GAIN, 0x8000, reg); + + return 0; +} + +static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct aic26 *aic26 = snd_soc_component_get_drvdata(component); + + dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," + " freq=%i, dir=%i)\n", + codec_dai, clk_id, freq, dir); + + /* MCLK needs to fall between 2MHz and 50 MHz */ + if ((freq < 2000000) || (freq > 50000000)) + return -EINVAL; + + aic26->mclk = freq; + return 0; +} + +static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct aic26 *aic26 = snd_soc_component_get_drvdata(component); + + dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", + codec_dai, fmt); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break; + case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break; + default: + dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break; + case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break; + case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break; + case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Definition + */ +#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) +#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +static const struct snd_soc_dai_ops aic26_dai_ops = { + .hw_params = aic26_hw_params, + .digital_mute = aic26_mute, + .set_sysclk = aic26_set_sysclk, + .set_fmt = aic26_set_fmt, +}; + +static struct snd_soc_dai_driver aic26_dai = { + .name = "tlv320aic26-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .ops = &aic26_dai_ops, +}; + +/* --------------------------------------------------------------------- + * ALSA controls + */ +static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static SOC_ENUM_SINGLE_DECL(aic26_capture_src_enum, + AIC26_REG_AUDIO_CTRL1, 12, + aic26_capture_src_text); + +static const struct snd_kcontrol_new aic26_snd_controls[] = { + /* Output */ + SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1), + SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1), + SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0), + SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1), + SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0), + SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0), + SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0), + SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0), + SOC_ENUM("Capture Source", aic26_capture_src_enum), +}; + +/* --------------------------------------------------------------------- + * SPI device portion of driver: sysfs files for debugging + */ + +static ssize_t aic26_keyclick_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val, amp, freq, len; + + val = snd_soc_component_read32(aic26->component, AIC26_REG_AUDIO_CTRL2); + amp = (val >> 12) & 0x7; + freq = (125 << ((val >> 8) & 0x7)) >> 1; + len = 2 * (1 + ((val >> 4) & 0xf)); + + return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len); +} + +/* Any write to the keyclick attribute will trigger the keyclick event */ +static ssize_t aic26_keyclick_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + + snd_soc_component_update_bits(aic26->component, AIC26_REG_AUDIO_CTRL2, + 0x8000, 0x800); + + return count; +} + +static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); + +/* --------------------------------------------------------------------- + * SoC CODEC portion of driver: probe and release routines + */ +static int aic26_probe(struct snd_soc_component *component) +{ + struct aic26 *aic26 = dev_get_drvdata(component->dev); + int ret, reg; + + aic26->component = component; + + /* Reset the codec to power on defaults */ + snd_soc_component_write(component, AIC26_REG_RESET, 0xBB00); + + /* Power up CODEC */ + snd_soc_component_write(component, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = snd_soc_component_read32(component, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + reg |= 0x0800; /* set master mode */ + snd_soc_component_write(component, AIC26_REG_AUDIO_CTRL3, reg); + + /* Register the sysfs files for debugging */ + /* Create SysFS files */ + ret = device_create_file(component->dev, &dev_attr_keyclick); + if (ret) + dev_info(component->dev, "error creating sysfs files\n"); + + return 0; +} + +static const struct snd_soc_component_driver aic26_soc_component_dev = { + .probe = aic26_probe, + .controls = aic26_snd_controls, + .num_controls = ARRAY_SIZE(aic26_snd_controls), + .dapm_widgets = tlv320aic26_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic26_dapm_widgets), + .dapm_routes = tlv320aic26_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tlv320aic26_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config aic26_regmap = { + .reg_bits = 16, + .val_bits = 16, +}; + +/* --------------------------------------------------------------------- + * SPI device portion of driver: probe and release routines and SPI + * driver registration. + */ +static int aic26_spi_probe(struct spi_device *spi) +{ + struct aic26 *aic26; + int ret; + + dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n"); + + /* Allocate driver data */ + aic26 = devm_kzalloc(&spi->dev, sizeof *aic26, GFP_KERNEL); + if (!aic26) + return -ENOMEM; + + aic26->regmap = devm_regmap_init_spi(spi, &aic26_regmap); + if (IS_ERR(aic26->regmap)) + return PTR_ERR(aic26->regmap); + + /* Initialize the driver data */ + aic26->spi = spi; + dev_set_drvdata(&spi->dev, aic26); + aic26->master = 1; + + ret = devm_snd_soc_register_component(&spi->dev, + &aic26_soc_component_dev, &aic26_dai, 1); + return ret; +} + +static struct spi_driver aic26_spi = { + .driver = { + .name = "tlv320aic26-codec", + }, + .probe = aic26_spi_probe, +}; + +module_spi_driver(aic26_spi); diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h new file mode 100644 index 000000000..1f2879b7a --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * register definitions + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#ifndef _TLV320AIC16_H_ +#define _TLV320AIC16_H_ + +/* AIC26 Registers */ +#define AIC26_PAGE_ADDR(page, offset) ((page << 11) | offset << 5) + +/* Page 0: Auxiliary data registers */ +#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05) +#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06) +#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07) +#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09) +#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A) + +/* Page 1: Auxiliary control registers */ +#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00) +#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01) +#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03) +#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04) + +/* Page 2: Audio control registers */ +#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00) +#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01) +#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02) +#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03) +#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04) +#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05) +#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06) + +#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07) +#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08) +#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09) +#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A) +#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B) +#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C) +#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D) +#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E) +#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F) +#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10) +#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11) +#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12) +#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13) +#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14) +#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15) +#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16) +#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17) +#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18) +#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19) +#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A) + +#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B) +#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C) +#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D) +#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E) + +/* fsref dividers; used in register 'Audio Control 1' */ +enum aic26_divisors { + AIC26_DIV_1 = 0, + AIC26_DIV_1_5 = 1, + AIC26_DIV_2 = 2, + AIC26_DIV_3 = 3, + AIC26_DIV_4 = 4, + AIC26_DIV_5 = 5, + AIC26_DIV_5_5 = 6, + AIC26_DIV_6 = 7, +}; + +/* Digital data format */ +enum aic26_datfm { + AIC26_DATFM_I2S = 0 << 8, + AIC26_DATFM_DSP = 1 << 8, + AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */ + AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */ +}; + +/* Sample word length in bits; used in register 'Audio Control 1' */ +enum aic26_wlen { + AIC26_WLEN_16 = 0 << 10, + AIC26_WLEN_20 = 1 << 10, + AIC26_WLEN_24 = 2 << 10, + AIC26_WLEN_32 = 3 << 10, +}; + +#endif /* _TLV320AIC16_H_ */ diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c new file mode 100644 index 000000000..3c75dcf91 --- /dev/null +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -0,0 +1,1500 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC TLV320AIC31xx CODEC Driver + * + * Copyright (C) 2014-2017 Texas Instruments Incorporated - http://www.ti.com/ + * Jyri Sarha + * + * Based on ground work by: Ajit Kulkarni + * + * The TLV320AIC31xx series of audio codecs are low-power, highly integrated + * high performance codecs which provides a stereo DAC, a mono ADC, + * and mono/stereo Class-D speaker driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tlv320aic31xx.h" + +static const struct reg_default aic31xx_reg_defaults[] = { + { AIC31XX_CLKMUX, 0x00 }, + { AIC31XX_PLLPR, 0x11 }, + { AIC31XX_PLLJ, 0x04 }, + { AIC31XX_PLLDMSB, 0x00 }, + { AIC31XX_PLLDLSB, 0x00 }, + { AIC31XX_NDAC, 0x01 }, + { AIC31XX_MDAC, 0x01 }, + { AIC31XX_DOSRMSB, 0x00 }, + { AIC31XX_DOSRLSB, 0x80 }, + { AIC31XX_NADC, 0x01 }, + { AIC31XX_MADC, 0x01 }, + { AIC31XX_AOSR, 0x80 }, + { AIC31XX_IFACE1, 0x00 }, + { AIC31XX_DATA_OFFSET, 0x00 }, + { AIC31XX_IFACE2, 0x00 }, + { AIC31XX_BCLKN, 0x01 }, + { AIC31XX_DACSETUP, 0x14 }, + { AIC31XX_DACMUTE, 0x0c }, + { AIC31XX_LDACVOL, 0x00 }, + { AIC31XX_RDACVOL, 0x00 }, + { AIC31XX_ADCSETUP, 0x00 }, + { AIC31XX_ADCFGA, 0x80 }, + { AIC31XX_ADCVOL, 0x00 }, + { AIC31XX_HPDRIVER, 0x04 }, + { AIC31XX_SPKAMP, 0x06 }, + { AIC31XX_DACMIXERROUTE, 0x00 }, + { AIC31XX_LANALOGHPL, 0x7f }, + { AIC31XX_RANALOGHPR, 0x7f }, + { AIC31XX_LANALOGSPL, 0x7f }, + { AIC31XX_RANALOGSPR, 0x7f }, + { AIC31XX_HPLGAIN, 0x02 }, + { AIC31XX_HPRGAIN, 0x02 }, + { AIC31XX_SPLGAIN, 0x00 }, + { AIC31XX_SPRGAIN, 0x00 }, + { AIC31XX_MICBIAS, 0x00 }, + { AIC31XX_MICPGA, 0x80 }, + { AIC31XX_MICPGAPI, 0x00 }, + { AIC31XX_MICPGAMI, 0x00 }, +}; + +static bool aic31xx_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AIC31XX_PAGECTL: /* regmap implementation requires this */ + case AIC31XX_RESET: /* always clears after write */ + case AIC31XX_OT_FLAG: + case AIC31XX_ADCFLAG: + case AIC31XX_DACFLAG1: + case AIC31XX_DACFLAG2: + case AIC31XX_OFFLAG: /* Sticky interrupt flags */ + case AIC31XX_INTRDACFLAG: /* Sticky interrupt flags */ + case AIC31XX_INTRADCFLAG: /* Sticky interrupt flags */ + case AIC31XX_INTRDACFLAG2: + case AIC31XX_INTRADCFLAG2: + return true; + } + return false; +} + +static bool aic31xx_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AIC31XX_OT_FLAG: + case AIC31XX_ADCFLAG: + case AIC31XX_DACFLAG1: + case AIC31XX_DACFLAG2: + case AIC31XX_OFFLAG: /* Sticky interrupt flags */ + case AIC31XX_INTRDACFLAG: /* Sticky interrupt flags */ + case AIC31XX_INTRADCFLAG: /* Sticky interrupt flags */ + case AIC31XX_INTRDACFLAG2: + case AIC31XX_INTRADCFLAG2: + return false; + } + return true; +} + +static const struct regmap_range_cfg aic31xx_ranges[] = { + { + .range_min = 0, + .range_max = 12 * 128, + .selector_reg = AIC31XX_PAGECTL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 128, + }, +}; + +static const struct regmap_config aic31xx_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = aic31xx_writeable, + .volatile_reg = aic31xx_volatile, + .reg_defaults = aic31xx_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(aic31xx_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .ranges = aic31xx_ranges, + .num_ranges = ARRAY_SIZE(aic31xx_ranges), + .max_register = 12 * 128, +}; + +static const char * const aic31xx_supply_names[] = { + "HPVDD", + "SPRVDD", + "SPLVDD", + "AVDD", + "IOVDD", + "DVDD", +}; + +#define AIC31XX_NUM_SUPPLIES ARRAY_SIZE(aic31xx_supply_names) + +struct aic31xx_disable_nb { + struct notifier_block nb; + struct aic31xx_priv *aic31xx; +}; + +struct aic31xx_priv { + struct snd_soc_component *component; + u8 i2c_regs_status; + struct device *dev; + struct regmap *regmap; + enum aic31xx_type codec_type; + struct gpio_desc *gpio_reset; + int micbias_vg; + struct aic31xx_pdata pdata; + struct regulator_bulk_data supplies[AIC31XX_NUM_SUPPLIES]; + struct aic31xx_disable_nb disable_nb[AIC31XX_NUM_SUPPLIES]; + unsigned int sysclk; + u8 p_div; + int rate_div_line; + bool master_dapm_route_applied; +}; + +struct aic31xx_rate_divs { + u32 mclk_p; + u32 rate; + u8 pll_j; + u16 pll_d; + u16 dosr; + u8 ndac; + u8 mdac; + u8 aosr; + u8 nadc; + u8 madc; +}; + +/* ADC dividers can be disabled by configuring them to 0 */ +static const struct aic31xx_rate_divs aic31xx_divs[] = { + /* mclk/p rate pll: j d dosr ndac mdac aors nadc madc */ + /* 8k rate */ + {12000000, 8000, 8, 1920, 128, 48, 2, 128, 48, 2}, + {12000000, 8000, 8, 1920, 128, 32, 3, 128, 32, 3}, + {12500000, 8000, 7, 8643, 128, 48, 2, 128, 48, 2}, + /* 11.025k rate */ + {12000000, 11025, 7, 5264, 128, 32, 2, 128, 32, 2}, + {12000000, 11025, 8, 4672, 128, 24, 3, 128, 24, 3}, + {12500000, 11025, 7, 2253, 128, 32, 2, 128, 32, 2}, + /* 16k rate */ + {12000000, 16000, 8, 1920, 128, 24, 2, 128, 24, 2}, + {12000000, 16000, 8, 1920, 128, 16, 3, 128, 16, 3}, + {12500000, 16000, 7, 8643, 128, 24, 2, 128, 24, 2}, + /* 22.05k rate */ + {12000000, 22050, 7, 5264, 128, 16, 2, 128, 16, 2}, + {12000000, 22050, 8, 4672, 128, 12, 3, 128, 12, 3}, + {12500000, 22050, 7, 2253, 128, 16, 2, 128, 16, 2}, + /* 32k rate */ + {12000000, 32000, 8, 1920, 128, 12, 2, 128, 12, 2}, + {12000000, 32000, 8, 1920, 128, 8, 3, 128, 8, 3}, + {12500000, 32000, 7, 8643, 128, 12, 2, 128, 12, 2}, + /* 44.1k rate */ + {12000000, 44100, 7, 5264, 128, 8, 2, 128, 8, 2}, + {12000000, 44100, 8, 4672, 128, 6, 3, 128, 6, 3}, + {12500000, 44100, 7, 2253, 128, 8, 2, 128, 8, 2}, + /* 48k rate */ + {12000000, 48000, 8, 1920, 128, 8, 2, 128, 8, 2}, + {12000000, 48000, 7, 6800, 96, 5, 4, 96, 5, 4}, + {12500000, 48000, 7, 8643, 128, 8, 2, 128, 8, 2}, + /* 88.2k rate */ + {12000000, 88200, 7, 5264, 64, 8, 2, 64, 8, 2}, + {12000000, 88200, 8, 4672, 64, 6, 3, 64, 6, 3}, + {12500000, 88200, 7, 2253, 64, 8, 2, 64, 8, 2}, + /* 96k rate */ + {12000000, 96000, 8, 1920, 64, 8, 2, 64, 8, 2}, + {12000000, 96000, 7, 6800, 48, 5, 4, 48, 5, 4}, + {12500000, 96000, 7, 8643, 64, 8, 2, 64, 8, 2}, + /* 176.4k rate */ + {12000000, 176400, 7, 5264, 32, 8, 2, 32, 8, 2}, + {12000000, 176400, 8, 4672, 32, 6, 3, 32, 6, 3}, + {12500000, 176400, 7, 2253, 32, 8, 2, 32, 8, 2}, + /* 192k rate */ + {12000000, 192000, 8, 1920, 32, 8, 2, 32, 8, 2}, + {12000000, 192000, 7, 6800, 24, 5, 4, 24, 5, 4}, + {12500000, 192000, 7, 8643, 32, 8, 2, 32, 8, 2}, +}; + +static const char * const ldac_in_text[] = { + "Off", "Left Data", "Right Data", "Mono" +}; + +static const char * const rdac_in_text[] = { + "Off", "Right Data", "Left Data", "Mono" +}; + +static SOC_ENUM_SINGLE_DECL(ldac_in_enum, AIC31XX_DACSETUP, 4, ldac_in_text); + +static SOC_ENUM_SINGLE_DECL(rdac_in_enum, AIC31XX_DACSETUP, 2, rdac_in_text); + +static const char * const mic_select_text[] = { + "Off", "FFR 10 Ohm", "FFR 20 Ohm", "FFR 40 Ohm" +}; + +static SOC_ENUM_SINGLE_DECL(mic1lp_p_enum, AIC31XX_MICPGAPI, 6, + mic_select_text); +static SOC_ENUM_SINGLE_DECL(mic1rp_p_enum, AIC31XX_MICPGAPI, 4, + mic_select_text); +static SOC_ENUM_SINGLE_DECL(mic1lm_p_enum, AIC31XX_MICPGAPI, 2, + mic_select_text); + +static SOC_ENUM_SINGLE_DECL(cm_m_enum, AIC31XX_MICPGAMI, 6, mic_select_text); +static SOC_ENUM_SINGLE_DECL(mic1lm_m_enum, AIC31XX_MICPGAMI, 4, + mic_select_text); + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6350, 50, 0); +static const DECLARE_TLV_DB_SCALE(adc_fgain_tlv, 0, 10, 0); +static const DECLARE_TLV_DB_SCALE(adc_cgain_tlv, -2000, 50, 0); +static const DECLARE_TLV_DB_SCALE(mic_pga_tlv, 0, 50, 0); +static const DECLARE_TLV_DB_SCALE(hp_drv_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(class_D_drv_tlv, 600, 600, 0); +static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -6350, 50, 0); +static const DECLARE_TLV_DB_SCALE(sp_vol_tlv, -6350, 50, 0); + +/* + * controls to be exported to the user space + */ +static const struct snd_kcontrol_new common31xx_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("DAC Playback Volume", AIC31XX_LDACVOL, + AIC31XX_RDACVOL, 0, -127, 48, 7, 0, dac_vol_tlv), + + SOC_DOUBLE_R("HP Driver Playback Switch", AIC31XX_HPLGAIN, + AIC31XX_HPRGAIN, 2, 1, 0), + SOC_DOUBLE_R_TLV("HP Driver Playback Volume", AIC31XX_HPLGAIN, + AIC31XX_HPRGAIN, 3, 0x09, 0, hp_drv_tlv), + + SOC_DOUBLE_R_TLV("HP Analog Playback Volume", AIC31XX_LANALOGHPL, + AIC31XX_RANALOGHPR, 0, 0x7F, 1, hp_vol_tlv), +}; + +static const struct snd_kcontrol_new aic31xx_snd_controls[] = { + SOC_SINGLE_TLV("ADC Fine Capture Volume", AIC31XX_ADCFGA, 4, 4, 1, + adc_fgain_tlv), + + SOC_SINGLE("ADC Capture Switch", AIC31XX_ADCFGA, 7, 1, 1), + SOC_DOUBLE_R_S_TLV("ADC Capture Volume", AIC31XX_ADCVOL, AIC31XX_ADCVOL, + 0, -24, 40, 6, 0, adc_cgain_tlv), + + SOC_SINGLE_TLV("Mic PGA Capture Volume", AIC31XX_MICPGA, 0, + 119, 0, mic_pga_tlv), +}; + +static const struct snd_kcontrol_new aic311x_snd_controls[] = { + SOC_DOUBLE_R("Speaker Driver Playback Switch", AIC31XX_SPLGAIN, + AIC31XX_SPRGAIN, 2, 1, 0), + SOC_DOUBLE_R_TLV("Speaker Driver Playback Volume", AIC31XX_SPLGAIN, + AIC31XX_SPRGAIN, 3, 3, 0, class_D_drv_tlv), + + SOC_DOUBLE_R_TLV("Speaker Analog Playback Volume", AIC31XX_LANALOGSPL, + AIC31XX_RANALOGSPR, 0, 0x7F, 1, sp_vol_tlv), +}; + +static const struct snd_kcontrol_new aic310x_snd_controls[] = { + SOC_SINGLE("Speaker Driver Playback Switch", AIC31XX_SPLGAIN, + 2, 1, 0), + SOC_SINGLE_TLV("Speaker Driver Playback Volume", AIC31XX_SPLGAIN, + 3, 3, 0, class_D_drv_tlv), + + SOC_SINGLE_TLV("Speaker Analog Playback Volume", AIC31XX_LANALOGSPL, + 0, 0x7F, 1, sp_vol_tlv), +}; + +static const struct snd_kcontrol_new ldac_in_control = + SOC_DAPM_ENUM("DAC Left Input", ldac_in_enum); + +static const struct snd_kcontrol_new rdac_in_control = + SOC_DAPM_ENUM("DAC Right Input", rdac_in_enum); + +static int aic31xx_wait_bits(struct aic31xx_priv *aic31xx, unsigned int reg, + unsigned int mask, unsigned int wbits, int sleep, + int count) +{ + unsigned int bits; + int counter = count; + int ret = regmap_read(aic31xx->regmap, reg, &bits); + + while ((bits & mask) != wbits && counter && !ret) { + usleep_range(sleep, sleep * 2); + ret = regmap_read(aic31xx->regmap, reg, &bits); + counter--; + } + if ((bits & mask) != wbits) { + dev_err(aic31xx->dev, + "%s: Failed! 0x%x was 0x%x expected 0x%x (%d, 0x%x, %d us)\n", + __func__, reg, bits, wbits, ret, mask, + (count - counter) * sleep); + ret = -1; + } + return ret; +} + +#define WIDGET_BIT(reg, shift) (((shift) << 8) | (reg)) + +static int aic31xx_dapm_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + unsigned int reg = AIC31XX_DACFLAG1; + unsigned int mask; + + switch (WIDGET_BIT(w->reg, w->shift)) { + case WIDGET_BIT(AIC31XX_DACSETUP, 7): + mask = AIC31XX_LDACPWRSTATUS_MASK; + break; + case WIDGET_BIT(AIC31XX_DACSETUP, 6): + mask = AIC31XX_RDACPWRSTATUS_MASK; + break; + case WIDGET_BIT(AIC31XX_HPDRIVER, 7): + mask = AIC31XX_HPLDRVPWRSTATUS_MASK; + break; + case WIDGET_BIT(AIC31XX_HPDRIVER, 6): + mask = AIC31XX_HPRDRVPWRSTATUS_MASK; + break; + case WIDGET_BIT(AIC31XX_SPKAMP, 7): + mask = AIC31XX_SPLDRVPWRSTATUS_MASK; + break; + case WIDGET_BIT(AIC31XX_SPKAMP, 6): + mask = AIC31XX_SPRDRVPWRSTATUS_MASK; + break; + case WIDGET_BIT(AIC31XX_ADCSETUP, 7): + mask = AIC31XX_ADCPWRSTATUS_MASK; + reg = AIC31XX_ADCFLAG; + break; + default: + dev_err(component->dev, "Unknown widget '%s' calling %s\n", + w->name, __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + return aic31xx_wait_bits(aic31xx, reg, mask, mask, 5000, 100); + case SND_SOC_DAPM_POST_PMD: + return aic31xx_wait_bits(aic31xx, reg, mask, 0, 5000, 100); + default: + dev_dbg(component->dev, + "Unhandled dapm widget event %d from %s\n", + event, w->name); + } + return 0; +} + +static const struct snd_kcontrol_new aic31xx_left_output_switches[] = { + SOC_DAPM_SINGLE("From Left DAC", AIC31XX_DACMIXERROUTE, 6, 1, 0), + SOC_DAPM_SINGLE("From MIC1LP", AIC31XX_DACMIXERROUTE, 5, 1, 0), + SOC_DAPM_SINGLE("From MIC1RP", AIC31XX_DACMIXERROUTE, 4, 1, 0), +}; + +static const struct snd_kcontrol_new aic31xx_right_output_switches[] = { + SOC_DAPM_SINGLE("From Right DAC", AIC31XX_DACMIXERROUTE, 2, 1, 0), + SOC_DAPM_SINGLE("From MIC1RP", AIC31XX_DACMIXERROUTE, 1, 1, 0), +}; + +static const struct snd_kcontrol_new dac31xx_left_output_switches[] = { + SOC_DAPM_SINGLE("From Left DAC", AIC31XX_DACMIXERROUTE, 6, 1, 0), + SOC_DAPM_SINGLE("From AIN1", AIC31XX_DACMIXERROUTE, 5, 1, 0), + SOC_DAPM_SINGLE("From AIN2", AIC31XX_DACMIXERROUTE, 4, 1, 0), +}; + +static const struct snd_kcontrol_new dac31xx_right_output_switches[] = { + SOC_DAPM_SINGLE("From Right DAC", AIC31XX_DACMIXERROUTE, 2, 1, 0), + SOC_DAPM_SINGLE("From AIN2", AIC31XX_DACMIXERROUTE, 1, 1, 0), +}; + +static const struct snd_kcontrol_new p_term_mic1lp = + SOC_DAPM_ENUM("MIC1LP P-Terminal", mic1lp_p_enum); + +static const struct snd_kcontrol_new p_term_mic1rp = + SOC_DAPM_ENUM("MIC1RP P-Terminal", mic1rp_p_enum); + +static const struct snd_kcontrol_new p_term_mic1lm = + SOC_DAPM_ENUM("MIC1LM P-Terminal", mic1lm_p_enum); + +static const struct snd_kcontrol_new m_term_mic1lm = + SOC_DAPM_ENUM("MIC1LM M-Terminal", mic1lm_m_enum); + +static const struct snd_kcontrol_new aic31xx_dapm_hpl_switch = + SOC_DAPM_SINGLE("Switch", AIC31XX_LANALOGHPL, 7, 1, 0); + +static const struct snd_kcontrol_new aic31xx_dapm_hpr_switch = + SOC_DAPM_SINGLE("Switch", AIC31XX_RANALOGHPR, 7, 1, 0); + +static const struct snd_kcontrol_new aic31xx_dapm_spl_switch = + SOC_DAPM_SINGLE("Switch", AIC31XX_LANALOGSPL, 7, 1, 0); + +static const struct snd_kcontrol_new aic31xx_dapm_spr_switch = + SOC_DAPM_SINGLE("Switch", AIC31XX_RANALOGSPR, 7, 1, 0); + +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* change mic bias voltage to user defined */ + snd_soc_component_update_bits(component, AIC31XX_MICBIAS, + AIC31XX_MICBIAS_MASK, + aic31xx->micbias_vg << + AIC31XX_MICBIAS_SHIFT); + dev_dbg(component->dev, "%s: turned on\n", __func__); + break; + case SND_SOC_DAPM_PRE_PMD: + /* turn mic bias off */ + snd_soc_component_update_bits(component, AIC31XX_MICBIAS, + AIC31XX_MICBIAS_MASK, 0); + dev_dbg(component->dev, "%s: turned off\n", __func__); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget common31xx_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("AIF IN", "Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("DAC Left Input", + SND_SOC_NOPM, 0, 0, &ldac_in_control), + SND_SOC_DAPM_MUX("DAC Right Input", + SND_SOC_NOPM, 0, 0, &rdac_in_control), + /* DACs */ + SND_SOC_DAPM_DAC_E("DAC Left", "Left Playback", + AIC31XX_DACSETUP, 7, 0, aic31xx_dapm_power_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_DAC_E("DAC Right", "Right Playback", + AIC31XX_DACSETUP, 6, 0, aic31xx_dapm_power_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* HP */ + SND_SOC_DAPM_SWITCH("HP Left", SND_SOC_NOPM, 0, 0, + &aic31xx_dapm_hpl_switch), + SND_SOC_DAPM_SWITCH("HP Right", SND_SOC_NOPM, 0, 0, + &aic31xx_dapm_hpr_switch), + + /* Output drivers */ + SND_SOC_DAPM_OUT_DRV_E("HPL Driver", AIC31XX_HPDRIVER, 7, 0, + NULL, 0, aic31xx_dapm_power_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_OUT_DRV_E("HPR Driver", AIC31XX_HPDRIVER, 6, 0, + NULL, 0, aic31xx_dapm_power_event, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), + + /* Mic Bias */ + SND_SOC_DAPM_SUPPLY("MICBIAS", SND_SOC_NOPM, 0, 0, mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + /* Keep BCLK/WCLK enabled even if DAC/ADC is powered down */ + SND_SOC_DAPM_SUPPLY("Activate I2S clocks", AIC31XX_IFACE2, 2, 0, + NULL, 0), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_widget dac31xx_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Output Left", SND_SOC_NOPM, 0, 0, + dac31xx_left_output_switches, + ARRAY_SIZE(dac31xx_left_output_switches)), + SND_SOC_DAPM_MIXER("Output Right", SND_SOC_NOPM, 0, 0, + dac31xx_right_output_switches, + ARRAY_SIZE(dac31xx_right_output_switches)), +}; + +static const struct snd_soc_dapm_widget aic31xx_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("MIC1LP"), + SND_SOC_DAPM_INPUT("MIC1RP"), + SND_SOC_DAPM_INPUT("MIC1LM"), + + /* Input Selection to MIC_PGA */ + SND_SOC_DAPM_MUX("MIC1LP P-Terminal", SND_SOC_NOPM, 0, 0, + &p_term_mic1lp), + SND_SOC_DAPM_MUX("MIC1RP P-Terminal", SND_SOC_NOPM, 0, 0, + &p_term_mic1rp), + SND_SOC_DAPM_MUX("MIC1LM P-Terminal", SND_SOC_NOPM, 0, 0, + &p_term_mic1lm), + + /* ADC */ + SND_SOC_DAPM_ADC_E("ADC", "Capture", AIC31XX_ADCSETUP, 7, 0, + aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("MIC1LM M-Terminal", SND_SOC_NOPM, 0, 0, + &m_term_mic1lm), + + /* Enabling & Disabling MIC Gain Ctl */ + SND_SOC_DAPM_PGA("MIC_GAIN_CTL", AIC31XX_MICPGA, + 7, 1, NULL, 0), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Output Left", SND_SOC_NOPM, 0, 0, + aic31xx_left_output_switches, + ARRAY_SIZE(aic31xx_left_output_switches)), + SND_SOC_DAPM_MIXER("Output Right", SND_SOC_NOPM, 0, 0, + aic31xx_right_output_switches, + ARRAY_SIZE(aic31xx_right_output_switches)), + + SND_SOC_DAPM_AIF_OUT("AIF OUT", "Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_widget aic311x_dapm_widgets[] = { + /* AIC3111 and AIC3110 have stereo class-D amplifier */ + SND_SOC_DAPM_OUT_DRV_E("SPL ClassD", AIC31XX_SPKAMP, 7, 0, NULL, 0, + aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("SPR ClassD", AIC31XX_SPKAMP, 6, 0, NULL, 0, + aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("Speaker Left", SND_SOC_NOPM, 0, 0, + &aic31xx_dapm_spl_switch), + SND_SOC_DAPM_SWITCH("Speaker Right", SND_SOC_NOPM, 0, 0, + &aic31xx_dapm_spr_switch), + SND_SOC_DAPM_OUTPUT("SPL"), + SND_SOC_DAPM_OUTPUT("SPR"), +}; + +/* AIC3100 and AIC3120 have only mono class-D amplifier */ +static const struct snd_soc_dapm_widget aic310x_dapm_widgets[] = { + SND_SOC_DAPM_OUT_DRV_E("SPK ClassD", AIC31XX_SPKAMP, 7, 0, NULL, 0, + aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("Speaker", SND_SOC_NOPM, 0, 0, + &aic31xx_dapm_spl_switch), + SND_SOC_DAPM_OUTPUT("SPK"), +}; + +static const struct snd_soc_dapm_route +common31xx_audio_map[] = { + /* DAC Input Routing */ + {"DAC Left Input", "Left Data", "AIF IN"}, + {"DAC Left Input", "Right Data", "AIF IN"}, + {"DAC Left Input", "Mono", "AIF IN"}, + {"DAC Right Input", "Left Data", "AIF IN"}, + {"DAC Right Input", "Right Data", "AIF IN"}, + {"DAC Right Input", "Mono", "AIF IN"}, + {"DAC Left", NULL, "DAC Left Input"}, + {"DAC Right", NULL, "DAC Right Input"}, + + /* HPL path */ + {"HP Left", "Switch", "Output Left"}, + {"HPL Driver", NULL, "HP Left"}, + {"HPL", NULL, "HPL Driver"}, + + /* HPR path */ + {"HP Right", "Switch", "Output Right"}, + {"HPR Driver", NULL, "HP Right"}, + {"HPR", NULL, "HPR Driver"}, +}; + +static const struct snd_soc_dapm_route +dac31xx_audio_map[] = { + /* Left Output */ + {"Output Left", "From Left DAC", "DAC Left"}, + {"Output Left", "From AIN1", "AIN1"}, + {"Output Left", "From AIN2", "AIN2"}, + + /* Right Output */ + {"Output Right", "From Right DAC", "DAC Right"}, + {"Output Right", "From AIN2", "AIN2"}, +}; + +static const struct snd_soc_dapm_route +aic31xx_audio_map[] = { + /* Mic input */ + {"MIC1LP P-Terminal", "FFR 10 Ohm", "MIC1LP"}, + {"MIC1LP P-Terminal", "FFR 20 Ohm", "MIC1LP"}, + {"MIC1LP P-Terminal", "FFR 40 Ohm", "MIC1LP"}, + {"MIC1RP P-Terminal", "FFR 10 Ohm", "MIC1RP"}, + {"MIC1RP P-Terminal", "FFR 20 Ohm", "MIC1RP"}, + {"MIC1RP P-Terminal", "FFR 40 Ohm", "MIC1RP"}, + {"MIC1LM P-Terminal", "FFR 10 Ohm", "MIC1LM"}, + {"MIC1LM P-Terminal", "FFR 20 Ohm", "MIC1LM"}, + {"MIC1LM P-Terminal", "FFR 40 Ohm", "MIC1LM"}, + + {"MIC1LM M-Terminal", "FFR 10 Ohm", "MIC1LM"}, + {"MIC1LM M-Terminal", "FFR 20 Ohm", "MIC1LM"}, + {"MIC1LM M-Terminal", "FFR 40 Ohm", "MIC1LM"}, + + {"MIC_GAIN_CTL", NULL, "MIC1LP P-Terminal"}, + {"MIC_GAIN_CTL", NULL, "MIC1RP P-Terminal"}, + {"MIC_GAIN_CTL", NULL, "MIC1LM P-Terminal"}, + {"MIC_GAIN_CTL", NULL, "MIC1LM M-Terminal"}, + + {"ADC", NULL, "MIC_GAIN_CTL"}, + + {"AIF OUT", NULL, "ADC"}, + + /* Left Output */ + {"Output Left", "From Left DAC", "DAC Left"}, + {"Output Left", "From MIC1LP", "MIC1LP"}, + {"Output Left", "From MIC1RP", "MIC1RP"}, + + /* Right Output */ + {"Output Right", "From Right DAC", "DAC Right"}, + {"Output Right", "From MIC1RP", "MIC1RP"}, +}; + +static const struct snd_soc_dapm_route +aic311x_audio_map[] = { + /* SP L path */ + {"Speaker Left", "Switch", "Output Left"}, + {"SPL ClassD", NULL, "Speaker Left"}, + {"SPL", NULL, "SPL ClassD"}, + + /* SP R path */ + {"Speaker Right", "Switch", "Output Right"}, + {"SPR ClassD", NULL, "Speaker Right"}, + {"SPR", NULL, "SPR ClassD"}, +}; + +static const struct snd_soc_dapm_route +aic310x_audio_map[] = { + /* SP L path */ + {"Speaker", "Switch", "Output Left"}, + {"SPK ClassD", NULL, "Speaker"}, + {"SPK", NULL, "SPK ClassD"}, +}; + +/* + * Always connected DAPM routes for codec clock master modes. + * If the codec is the master on the I2S bus, we need to power up components + * to have valid DAC_CLK. + * + * In order to have the I2S clocks on the bus either the DACs/ADC need to be + * enabled, or the P0/R29/D2 (Keep bclk/wclk in power down) need to be set. + * + * Otherwise the codec will not generate clocks on the bus. + */ +static const struct snd_soc_dapm_route +common31xx_cm_audio_map[] = { + {"HPL", NULL, "AIF IN"}, + {"HPR", NULL, "AIF IN"}, + + {"AIF IN", NULL, "Activate I2S clocks"}, +}; + +static const struct snd_soc_dapm_route +aic31xx_cm_audio_map[] = { + {"AIF OUT", NULL, "MIC1LP"}, + {"AIF OUT", NULL, "MIC1RP"}, + {"AIF OUT", NULL, "MIC1LM"}, + + {"AIF OUT", NULL, "Activate I2S clocks"}, +}; + +static int aic31xx_add_controls(struct snd_soc_component *component) +{ + int ret = 0; + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + + if (!(aic31xx->codec_type & DAC31XX_BIT)) + ret = snd_soc_add_component_controls( + component, aic31xx_snd_controls, + ARRAY_SIZE(aic31xx_snd_controls)); + if (ret) + return ret; + + if (aic31xx->codec_type & AIC31XX_STEREO_CLASS_D_BIT) + ret = snd_soc_add_component_controls( + component, aic311x_snd_controls, + ARRAY_SIZE(aic311x_snd_controls)); + else + ret = snd_soc_add_component_controls( + component, aic310x_snd_controls, + ARRAY_SIZE(aic310x_snd_controls)); + + return ret; +} + +static int aic31xx_add_widgets(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + int ret = 0; + + if (aic31xx->codec_type & DAC31XX_BIT) { + ret = snd_soc_dapm_new_controls( + dapm, dac31xx_dapm_widgets, + ARRAY_SIZE(dac31xx_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, dac31xx_audio_map, + ARRAY_SIZE(dac31xx_audio_map)); + if (ret) + return ret; + } else { + ret = snd_soc_dapm_new_controls( + dapm, aic31xx_dapm_widgets, + ARRAY_SIZE(aic31xx_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, aic31xx_audio_map, + ARRAY_SIZE(aic31xx_audio_map)); + if (ret) + return ret; + } + + if (aic31xx->codec_type & AIC31XX_STEREO_CLASS_D_BIT) { + ret = snd_soc_dapm_new_controls( + dapm, aic311x_dapm_widgets, + ARRAY_SIZE(aic311x_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, aic311x_audio_map, + ARRAY_SIZE(aic311x_audio_map)); + if (ret) + return ret; + } else { + ret = snd_soc_dapm_new_controls( + dapm, aic310x_dapm_widgets, + ARRAY_SIZE(aic310x_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, aic310x_audio_map, + ARRAY_SIZE(aic310x_audio_map)); + if (ret) + return ret; + } + + return 0; +} + +static int aic31xx_setup_pll(struct snd_soc_component *component, + struct snd_pcm_hw_params *params) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + int bclk_score = snd_soc_params_to_frame_size(params); + int mclk_p; + int bclk_n = 0; + int match = -1; + int i; + + if (!aic31xx->sysclk || !aic31xx->p_div) { + dev_err(component->dev, "Master clock not supplied\n"); + return -EINVAL; + } + mclk_p = aic31xx->sysclk / aic31xx->p_div; + + /* Use PLL as CODEC_CLKIN and DAC_CLK as BDIV_CLKIN */ + snd_soc_component_update_bits(component, AIC31XX_CLKMUX, + AIC31XX_CODEC_CLKIN_MASK, AIC31XX_CODEC_CLKIN_PLL); + snd_soc_component_update_bits(component, AIC31XX_IFACE2, + AIC31XX_BDIVCLK_MASK, AIC31XX_DAC2BCLK); + + for (i = 0; i < ARRAY_SIZE(aic31xx_divs); i++) { + if (aic31xx_divs[i].rate == params_rate(params) && + aic31xx_divs[i].mclk_p == mclk_p) { + int s = (aic31xx_divs[i].dosr * aic31xx_divs[i].mdac) % + snd_soc_params_to_frame_size(params); + int bn = (aic31xx_divs[i].dosr * aic31xx_divs[i].mdac) / + snd_soc_params_to_frame_size(params); + if (s < bclk_score && bn > 0) { + match = i; + bclk_n = bn; + bclk_score = s; + } + } + } + + if (match == -1) { + dev_err(component->dev, + "%s: Sample rate (%u) and format not supported\n", + __func__, params_rate(params)); + /* See bellow for details how fix this. */ + return -EINVAL; + } + if (bclk_score != 0) { + dev_warn(component->dev, "Can not produce exact bitclock"); + /* This is fine if using dsp format, but if using i2s + there may be trouble. To fix the issue edit the + aic31xx_divs table for your mclk and sample + rate. Details can be found from: + http://www.ti.com/lit/ds/symlink/tlv320aic3100.pdf + Section: 5.6 CLOCK Generation and PLL + */ + } + i = match; + + /* PLL configuration */ + snd_soc_component_update_bits(component, AIC31XX_PLLPR, AIC31XX_PLL_MASK, + (aic31xx->p_div << 4) | 0x01); + snd_soc_component_write(component, AIC31XX_PLLJ, aic31xx_divs[i].pll_j); + + snd_soc_component_write(component, AIC31XX_PLLDMSB, + aic31xx_divs[i].pll_d >> 8); + snd_soc_component_write(component, AIC31XX_PLLDLSB, + aic31xx_divs[i].pll_d & 0xff); + + /* DAC dividers configuration */ + snd_soc_component_update_bits(component, AIC31XX_NDAC, AIC31XX_PLL_MASK, + aic31xx_divs[i].ndac); + snd_soc_component_update_bits(component, AIC31XX_MDAC, AIC31XX_PLL_MASK, + aic31xx_divs[i].mdac); + + snd_soc_component_write(component, AIC31XX_DOSRMSB, aic31xx_divs[i].dosr >> 8); + snd_soc_component_write(component, AIC31XX_DOSRLSB, aic31xx_divs[i].dosr & 0xff); + + /* ADC dividers configuration. Write reset value 1 if not used. */ + snd_soc_component_update_bits(component, AIC31XX_NADC, AIC31XX_PLL_MASK, + aic31xx_divs[i].nadc ? aic31xx_divs[i].nadc : 1); + snd_soc_component_update_bits(component, AIC31XX_MADC, AIC31XX_PLL_MASK, + aic31xx_divs[i].madc ? aic31xx_divs[i].madc : 1); + + snd_soc_component_write(component, AIC31XX_AOSR, aic31xx_divs[i].aosr); + + /* Bit clock divider configuration. */ + snd_soc_component_update_bits(component, AIC31XX_BCLKN, + AIC31XX_PLL_MASK, bclk_n); + + aic31xx->rate_div_line = i; + + dev_dbg(component->dev, + "pll %d.%04d/%d dosr %d n %d m %d aosr %d n %d m %d bclk_n %d\n", + aic31xx_divs[i].pll_j, + aic31xx_divs[i].pll_d, + aic31xx->p_div, + aic31xx_divs[i].dosr, + aic31xx_divs[i].ndac, + aic31xx_divs[i].mdac, + aic31xx_divs[i].aosr, + aic31xx_divs[i].nadc, + aic31xx_divs[i].madc, + bclk_n + ); + + return 0; +} + +static int aic31xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u8 data = 0; + + dev_dbg(component->dev, "## %s: width %d rate %d\n", + __func__, params_width(params), + params_rate(params)); + + switch (params_width(params)) { + case 16: + break; + case 20: + data = (AIC31XX_WORD_LEN_20BITS << + AIC31XX_IFACE1_DATALEN_SHIFT); + break; + case 24: + data = (AIC31XX_WORD_LEN_24BITS << + AIC31XX_IFACE1_DATALEN_SHIFT); + break; + case 32: + data = (AIC31XX_WORD_LEN_32BITS << + AIC31XX_IFACE1_DATALEN_SHIFT); + break; + default: + dev_err(component->dev, "%s: Unsupported width %d\n", + __func__, params_width(params)); + return -EINVAL; + } + + snd_soc_component_update_bits(component, AIC31XX_IFACE1, + AIC31XX_IFACE1_DATALEN_MASK, + data); + + return aic31xx_setup_pll(component, params); +} + +static int aic31xx_dac_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + + if (mute) { + snd_soc_component_update_bits(component, AIC31XX_DACMUTE, + AIC31XX_DACMUTE_MASK, + AIC31XX_DACMUTE_MASK); + } else { + snd_soc_component_update_bits(component, AIC31XX_DACMUTE, + AIC31XX_DACMUTE_MASK, 0x0); + } + + return 0; +} + +static int aic31xx_clock_master_routes(struct snd_soc_component *component, + unsigned int fmt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + int ret; + + fmt &= SND_SOC_DAIFMT_MASTER_MASK; + if (fmt == SND_SOC_DAIFMT_CBS_CFS && + aic31xx->master_dapm_route_applied) { + /* + * Remove the DAPM route(s) for codec clock master modes, + * if applied + */ + ret = snd_soc_dapm_del_routes(dapm, common31xx_cm_audio_map, + ARRAY_SIZE(common31xx_cm_audio_map)); + if (!ret && !(aic31xx->codec_type & DAC31XX_BIT)) + ret = snd_soc_dapm_del_routes(dapm, + aic31xx_cm_audio_map, + ARRAY_SIZE(aic31xx_cm_audio_map)); + + if (ret) + return ret; + + aic31xx->master_dapm_route_applied = false; + } else if (fmt != SND_SOC_DAIFMT_CBS_CFS && + !aic31xx->master_dapm_route_applied) { + /* + * Add the needed DAPM route(s) for codec clock master modes, + * if it is not done already + */ + ret = snd_soc_dapm_add_routes(dapm, common31xx_cm_audio_map, + ARRAY_SIZE(common31xx_cm_audio_map)); + if (!ret && !(aic31xx->codec_type & DAC31XX_BIT)) + ret = snd_soc_dapm_add_routes(dapm, + aic31xx_cm_audio_map, + ARRAY_SIZE(aic31xx_cm_audio_map)); + + if (ret) + return ret; + + aic31xx->master_dapm_route_applied = true; + } + + return 0; +} + +static int aic31xx_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 iface_reg1 = 0; + u8 iface_reg2 = 0; + u8 dsp_a_val = 0; + + dev_dbg(component->dev, "## %s: fmt = 0x%x\n", __func__, fmt); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface_reg1 |= AIC31XX_BCLK_MASTER | AIC31XX_WCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: + iface_reg1 |= AIC31XX_WCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: + iface_reg1 |= AIC31XX_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + dev_err(component->dev, "Invalid DAI master/slave interface\n"); + return -EINVAL; + } + + /* signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + iface_reg2 |= AIC31XX_BCLKINV_MASK; + break; + default: + dev_err(component->dev, "Invalid DAI clock signal polarity\n"); + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_DSP_A: + dsp_a_val = 0x1; /* fall through */ + case SND_SOC_DAIFMT_DSP_B: + /* + * NOTE: This CODEC samples on the falling edge of BCLK in + * DSP mode, this is inverted compared to what most DAIs + * expect, so we invert for this mode + */ + iface_reg2 ^= AIC31XX_BCLKINV_MASK; + iface_reg1 |= (AIC31XX_DSP_MODE << + AIC31XX_IFACE1_DATATYPE_SHIFT); + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg1 |= (AIC31XX_RIGHT_JUSTIFIED_MODE << + AIC31XX_IFACE1_DATATYPE_SHIFT); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg1 |= (AIC31XX_LEFT_JUSTIFIED_MODE << + AIC31XX_IFACE1_DATATYPE_SHIFT); + break; + default: + dev_err(component->dev, "Invalid DAI interface format\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, AIC31XX_IFACE1, + AIC31XX_IFACE1_DATATYPE_MASK | + AIC31XX_IFACE1_MASTER_MASK, + iface_reg1); + snd_soc_component_update_bits(component, AIC31XX_DATA_OFFSET, + AIC31XX_DATA_OFFSET_MASK, + dsp_a_val); + snd_soc_component_update_bits(component, AIC31XX_IFACE2, + AIC31XX_BCLKINV_MASK, + iface_reg2); + + return aic31xx_clock_master_routes(component, fmt); +} + +static int aic31xx_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + int i; + + dev_dbg(component->dev, "## %s: clk_id = %d, freq = %d, dir = %d\n", + __func__, clk_id, freq, dir); + + for (i = 1; i < 8; i++) + if (freq / i <= 20000000) + break; + if (freq/i > 20000000) { + dev_err(aic31xx->dev, "%s: Too high mclk frequency %u\n", + __func__, freq); + return -EINVAL; + } + aic31xx->p_div = i; + + for (i = 0; i < ARRAY_SIZE(aic31xx_divs); i++) + if (aic31xx_divs[i].mclk_p == freq / aic31xx->p_div) + break; + if (i == ARRAY_SIZE(aic31xx_divs)) { + dev_err(aic31xx->dev, "%s: Unsupported frequency %d\n", + __func__, freq); + return -EINVAL; + } + + /* set clock on MCLK, BCLK, or GPIO1 as PLL input */ + snd_soc_component_update_bits(component, AIC31XX_CLKMUX, AIC31XX_PLL_CLKIN_MASK, + clk_id << AIC31XX_PLL_CLKIN_SHIFT); + + aic31xx->sysclk = freq; + + return 0; +} + +static int aic31xx_regulator_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct aic31xx_disable_nb *disable_nb = + container_of(nb, struct aic31xx_disable_nb, nb); + struct aic31xx_priv *aic31xx = disable_nb->aic31xx; + + if (event & REGULATOR_EVENT_DISABLE) { + /* + * Put codec to reset and as at least one of the + * supplies was disabled. + */ + if (aic31xx->gpio_reset) + gpiod_set_value(aic31xx->gpio_reset, 1); + + regcache_mark_dirty(aic31xx->regmap); + dev_dbg(aic31xx->dev, "## %s: DISABLE received\n", __func__); + } + + return 0; +} + +static int aic31xx_reset(struct aic31xx_priv *aic31xx) +{ + int ret = 0; + + if (aic31xx->gpio_reset) { + gpiod_set_value(aic31xx->gpio_reset, 1); + ndelay(10); /* At least 10ns */ + gpiod_set_value(aic31xx->gpio_reset, 0); + } else { + ret = regmap_write(aic31xx->regmap, AIC31XX_RESET, 1); + } + mdelay(1); /* At least 1ms */ + + return ret; +} + +static void aic31xx_clk_on(struct snd_soc_component *component) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + u8 mask = AIC31XX_PM_MASK; + u8 on = AIC31XX_PM_MASK; + + dev_dbg(component->dev, "codec clock -> on (rate %d)\n", + aic31xx_divs[aic31xx->rate_div_line].rate); + snd_soc_component_update_bits(component, AIC31XX_PLLPR, mask, on); + mdelay(10); + snd_soc_component_update_bits(component, AIC31XX_NDAC, mask, on); + snd_soc_component_update_bits(component, AIC31XX_MDAC, mask, on); + if (aic31xx_divs[aic31xx->rate_div_line].nadc) + snd_soc_component_update_bits(component, AIC31XX_NADC, mask, on); + if (aic31xx_divs[aic31xx->rate_div_line].madc) + snd_soc_component_update_bits(component, AIC31XX_MADC, mask, on); + snd_soc_component_update_bits(component, AIC31XX_BCLKN, mask, on); +} + +static void aic31xx_clk_off(struct snd_soc_component *component) +{ + u8 mask = AIC31XX_PM_MASK; + u8 off = 0; + + dev_dbg(component->dev, "codec clock -> off\n"); + snd_soc_component_update_bits(component, AIC31XX_BCLKN, mask, off); + snd_soc_component_update_bits(component, AIC31XX_MADC, mask, off); + snd_soc_component_update_bits(component, AIC31XX_NADC, mask, off); + snd_soc_component_update_bits(component, AIC31XX_MDAC, mask, off); + snd_soc_component_update_bits(component, AIC31XX_NDAC, mask, off); + snd_soc_component_update_bits(component, AIC31XX_PLLPR, mask, off); +} + +static int aic31xx_power_on(struct snd_soc_component *component) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(aic31xx->supplies), + aic31xx->supplies); + if (ret) + return ret; + + regcache_cache_only(aic31xx->regmap, false); + + /* Reset device registers for a consistent power-on like state */ + ret = aic31xx_reset(aic31xx); + if (ret < 0) + dev_err(aic31xx->dev, "Could not reset device: %d\n", ret); + + ret = regcache_sync(aic31xx->regmap); + if (ret) { + dev_err(component->dev, + "Failed to restore cache: %d\n", ret); + regcache_cache_only(aic31xx->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(aic31xx->supplies), + aic31xx->supplies); + return ret; + } + + return 0; +} + +static void aic31xx_power_off(struct snd_soc_component *component) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + + regcache_cache_only(aic31xx->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(aic31xx->supplies), + aic31xx->supplies); +} + +static int aic31xx_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + dev_dbg(component->dev, "## %s: %d -> %d\n", __func__, + snd_soc_component_get_bias_level(component), level); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) + aic31xx_clk_on(component); + break; + case SND_SOC_BIAS_STANDBY: + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_OFF: + aic31xx_power_on(component); + break; + case SND_SOC_BIAS_PREPARE: + aic31xx_clk_off(component); + break; + default: + BUG(); + } + break; + case SND_SOC_BIAS_OFF: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) + aic31xx_power_off(component); + break; + } + + return 0; +} + +static int aic31xx_codec_probe(struct snd_soc_component *component) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + int i, ret; + + dev_dbg(aic31xx->dev, "## %s\n", __func__); + + aic31xx->component = component; + + for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) { + aic31xx->disable_nb[i].nb.notifier_call = + aic31xx_regulator_event; + aic31xx->disable_nb[i].aic31xx = aic31xx; + ret = regulator_register_notifier(aic31xx->supplies[i].consumer, + &aic31xx->disable_nb[i].nb); + if (ret) { + dev_err(component->dev, + "Failed to request regulator notifier: %d\n", + ret); + return ret; + } + } + + regcache_cache_only(aic31xx->regmap, true); + regcache_mark_dirty(aic31xx->regmap); + + ret = aic31xx_add_controls(component); + if (ret) + return ret; + + ret = aic31xx_add_widgets(component); + if (ret) + return ret; + + return 0; +} + +static void aic31xx_codec_remove(struct snd_soc_component *component) +{ + struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) + regulator_unregister_notifier(aic31xx->supplies[i].consumer, + &aic31xx->disable_nb[i].nb); +} + +static const struct snd_soc_component_driver soc_codec_driver_aic31xx = { + .probe = aic31xx_codec_probe, + .remove = aic31xx_codec_remove, + .set_bias_level = aic31xx_set_bias_level, + .controls = common31xx_snd_controls, + .num_controls = ARRAY_SIZE(common31xx_snd_controls), + .dapm_widgets = common31xx_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(common31xx_dapm_widgets), + .dapm_routes = common31xx_audio_map, + .num_dapm_routes = ARRAY_SIZE(common31xx_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct snd_soc_dai_ops aic31xx_dai_ops = { + .hw_params = aic31xx_hw_params, + .set_sysclk = aic31xx_set_dai_sysclk, + .set_fmt = aic31xx_set_dai_fmt, + .digital_mute = aic31xx_dac_mute, +}; + +static struct snd_soc_dai_driver dac31xx_dai_driver[] = { + { + .name = "tlv320dac31xx-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC31XX_RATES, + .formats = AIC31XX_FORMATS, + }, + .ops = &aic31xx_dai_ops, + .symmetric_rates = 1, + } +}; + +static struct snd_soc_dai_driver aic31xx_dai_driver[] = { + { + .name = "tlv320aic31xx-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC31XX_RATES, + .formats = AIC31XX_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC31XX_RATES, + .formats = AIC31XX_FORMATS, + }, + .ops = &aic31xx_dai_ops, + .symmetric_rates = 1, + } +}; + +#if defined(CONFIG_OF) +static const struct of_device_id tlv320aic31xx_of_match[] = { + { .compatible = "ti,tlv320aic310x" }, + { .compatible = "ti,tlv320aic311x" }, + { .compatible = "ti,tlv320aic3100" }, + { .compatible = "ti,tlv320aic3110" }, + { .compatible = "ti,tlv320aic3120" }, + { .compatible = "ti,tlv320aic3111" }, + { .compatible = "ti,tlv320dac3100" }, + { .compatible = "ti,tlv320dac3101" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tlv320aic31xx_of_match); +#endif /* CONFIG_OF */ + +#ifdef CONFIG_ACPI +static const struct acpi_device_id aic31xx_acpi_match[] = { + { "10TI3100", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, aic31xx_acpi_match); +#endif + +static int aic31xx_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct aic31xx_priv *aic31xx; + unsigned int micbias_value = MICBIAS_2_0V; + int i, ret; + + dev_dbg(&i2c->dev, "## %s: %s codec_type = %d\n", __func__, + id->name, (int)id->driver_data); + + aic31xx = devm_kzalloc(&i2c->dev, sizeof(*aic31xx), GFP_KERNEL); + if (!aic31xx) + return -ENOMEM; + + aic31xx->regmap = devm_regmap_init_i2c(i2c, &aic31xx_i2c_regmap); + if (IS_ERR(aic31xx->regmap)) { + ret = PTR_ERR(aic31xx->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + aic31xx->dev = &i2c->dev; + + aic31xx->codec_type = id->driver_data; + + dev_set_drvdata(aic31xx->dev, aic31xx); + + fwnode_property_read_u32(aic31xx->dev->fwnode, "ai31xx-micbias-vg", + &micbias_value); + switch (micbias_value) { + case MICBIAS_2_0V: + case MICBIAS_2_5V: + case MICBIAS_AVDDV: + aic31xx->micbias_vg = micbias_value; + break; + default: + dev_err(aic31xx->dev, "Bad ai31xx-micbias-vg value %d\n", + micbias_value); + aic31xx->micbias_vg = MICBIAS_2_0V; + } + + if (dev_get_platdata(aic31xx->dev)) { + memcpy(&aic31xx->pdata, dev_get_platdata(aic31xx->dev), sizeof(aic31xx->pdata)); + aic31xx->codec_type = aic31xx->pdata.codec_type; + aic31xx->micbias_vg = aic31xx->pdata.micbias_vg; + } + + aic31xx->gpio_reset = devm_gpiod_get_optional(aic31xx->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(aic31xx->gpio_reset)) { + if (PTR_ERR(aic31xx->gpio_reset) != -EPROBE_DEFER) + dev_err(aic31xx->dev, "not able to acquire gpio\n"); + return PTR_ERR(aic31xx->gpio_reset); + } + + for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) + aic31xx->supplies[i].supply = aic31xx_supply_names[i]; + + ret = devm_regulator_bulk_get(aic31xx->dev, + ARRAY_SIZE(aic31xx->supplies), + aic31xx->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(aic31xx->dev, + "Failed to request supplies: %d\n", ret); + return ret; + } + + if (aic31xx->codec_type & DAC31XX_BIT) + return devm_snd_soc_register_component(&i2c->dev, + &soc_codec_driver_aic31xx, + dac31xx_dai_driver, + ARRAY_SIZE(dac31xx_dai_driver)); + else + return devm_snd_soc_register_component(&i2c->dev, + &soc_codec_driver_aic31xx, + aic31xx_dai_driver, + ARRAY_SIZE(aic31xx_dai_driver)); +} + +static const struct i2c_device_id aic31xx_i2c_id[] = { + { "tlv320aic310x", AIC3100 }, + { "tlv320aic311x", AIC3110 }, + { "tlv320aic3100", AIC3100 }, + { "tlv320aic3110", AIC3110 }, + { "tlv320aic3120", AIC3120 }, + { "tlv320aic3111", AIC3111 }, + { "tlv320dac3100", DAC3100 }, + { "tlv320dac3101", DAC3101 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic31xx_i2c_id); + +static struct i2c_driver aic31xx_i2c_driver = { + .driver = { + .name = "tlv320aic31xx-codec", + .of_match_table = of_match_ptr(tlv320aic31xx_of_match), + .acpi_match_table = ACPI_PTR(aic31xx_acpi_match), + }, + .probe = aic31xx_i2c_probe, + .id_table = aic31xx_i2c_id, +}; +module_i2c_driver(aic31xx_i2c_driver); + +MODULE_AUTHOR("Jyri Sarha "); +MODULE_DESCRIPTION("ASoC TLV320AIC31xx CODEC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h new file mode 100644 index 000000000..6071de5fc --- /dev/null +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC TLV320AIC31xx CODEC Driver Definitions + * + * Copyright (C) 2014-2017 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#ifndef _TLV320AIC31XX_H +#define _TLV320AIC31XX_H + +#define AIC31XX_RATES SNDRV_PCM_RATE_8000_192000 + +#define AIC31XX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define AIC31XX_STEREO_CLASS_D_BIT BIT(1) +#define AIC31XX_MINIDSP_BIT BIT(2) +#define DAC31XX_BIT BIT(3) + +enum aic31xx_type { + AIC3100 = 0, + AIC3110 = AIC31XX_STEREO_CLASS_D_BIT, + AIC3120 = AIC31XX_MINIDSP_BIT, + AIC3111 = AIC31XX_STEREO_CLASS_D_BIT | AIC31XX_MINIDSP_BIT, + DAC3100 = DAC31XX_BIT, + DAC3101 = DAC31XX_BIT | AIC31XX_STEREO_CLASS_D_BIT, +}; + +struct aic31xx_pdata { + enum aic31xx_type codec_type; + unsigned int gpio_reset; + int micbias_vg; +}; + +#define AIC31XX_REG(page, reg) ((page * 128) + reg) + +#define AIC31XX_PAGECTL AIC31XX_REG(0, 0) /* Page Control Register */ + +/* Page 0 Registers */ +#define AIC31XX_RESET AIC31XX_REG(0, 1) /* Software reset register */ +#define AIC31XX_OT_FLAG AIC31XX_REG(0, 3) /* OT FLAG register */ +#define AIC31XX_CLKMUX AIC31XX_REG(0, 4) /* Clock clock Gen muxing, Multiplexers*/ +#define AIC31XX_PLLPR AIC31XX_REG(0, 5) /* PLL P and R-VAL register */ +#define AIC31XX_PLLJ AIC31XX_REG(0, 6) /* PLL J-VAL register */ +#define AIC31XX_PLLDMSB AIC31XX_REG(0, 7) /* PLL D-VAL MSB register */ +#define AIC31XX_PLLDLSB AIC31XX_REG(0, 8) /* PLL D-VAL LSB register */ +#define AIC31XX_NDAC AIC31XX_REG(0, 11) /* DAC NDAC_VAL register*/ +#define AIC31XX_MDAC AIC31XX_REG(0, 12) /* DAC MDAC_VAL register */ +#define AIC31XX_DOSRMSB AIC31XX_REG(0, 13) /* DAC OSR setting register 1, MSB value */ +#define AIC31XX_DOSRLSB AIC31XX_REG(0, 14) /* DAC OSR setting register 2, LSB value */ +#define AIC31XX_MINI_DSP_INPOL AIC31XX_REG(0, 16) +#define AIC31XX_NADC AIC31XX_REG(0, 18) /* Clock setting register 8, PLL */ +#define AIC31XX_MADC AIC31XX_REG(0, 19) /* Clock setting register 9, PLL */ +#define AIC31XX_AOSR AIC31XX_REG(0, 20) /* ADC Oversampling (AOSR) Register */ +#define AIC31XX_CLKOUTMUX AIC31XX_REG(0, 25) /* Clock setting register 9, Multiplexers */ +#define AIC31XX_CLKOUTMVAL AIC31XX_REG(0, 26) /* Clock setting register 10, CLOCKOUT M divider value */ +#define AIC31XX_IFACE1 AIC31XX_REG(0, 27) /* Audio Interface Setting Register 1 */ +#define AIC31XX_DATA_OFFSET AIC31XX_REG(0, 28) /* Audio Data Slot Offset Programming */ +#define AIC31XX_IFACE2 AIC31XX_REG(0, 29) /* Audio Interface Setting Register 2 */ +#define AIC31XX_BCLKN AIC31XX_REG(0, 30) /* Clock setting register 11, BCLK N Divider */ +#define AIC31XX_IFACESEC1 AIC31XX_REG(0, 31) /* Audio Interface Setting Register 3, Secondary Audio Interface */ +#define AIC31XX_IFACESEC2 AIC31XX_REG(0, 32) /* Audio Interface Setting Register 4 */ +#define AIC31XX_IFACESEC3 AIC31XX_REG(0, 33) /* Audio Interface Setting Register 5 */ +#define AIC31XX_I2C AIC31XX_REG(0, 34) /* I2C Bus Condition */ +#define AIC31XX_ADCFLAG AIC31XX_REG(0, 36) /* ADC FLAG */ +#define AIC31XX_DACFLAG1 AIC31XX_REG(0, 37) /* DAC Flag Registers */ +#define AIC31XX_DACFLAG2 AIC31XX_REG(0, 38) +#define AIC31XX_OFFLAG AIC31XX_REG(0, 39) /* Sticky Interrupt flag (overflow) */ +#define AIC31XX_INTRDACFLAG AIC31XX_REG(0, 44) /* Sticy DAC Interrupt flags */ +#define AIC31XX_INTRADCFLAG AIC31XX_REG(0, 45) /* Sticy ADC Interrupt flags */ +#define AIC31XX_INTRDACFLAG2 AIC31XX_REG(0, 46) /* DAC Interrupt flags 2 */ +#define AIC31XX_INTRADCFLAG2 AIC31XX_REG(0, 47) /* ADC Interrupt flags 2 */ +#define AIC31XX_INT1CTRL AIC31XX_REG(0, 48) /* INT1 interrupt control */ +#define AIC31XX_INT2CTRL AIC31XX_REG(0, 49) /* INT2 interrupt control */ +#define AIC31XX_GPIO1 AIC31XX_REG(0, 51) /* GPIO1 control */ +#define AIC31XX_DACPRB AIC31XX_REG(0, 60) +#define AIC31XX_ADCPRB AIC31XX_REG(0, 61) /* ADC Instruction Set Register */ +#define AIC31XX_DACSETUP AIC31XX_REG(0, 63) /* DAC channel setup register */ +#define AIC31XX_DACMUTE AIC31XX_REG(0, 64) /* DAC Mute and volume control register */ +#define AIC31XX_LDACVOL AIC31XX_REG(0, 65) /* Left DAC channel digital volume control */ +#define AIC31XX_RDACVOL AIC31XX_REG(0, 66) /* Right DAC channel digital volume control */ +#define AIC31XX_HSDETECT AIC31XX_REG(0, 67) /* Headset detection */ +#define AIC31XX_ADCSETUP AIC31XX_REG(0, 81) /* ADC Digital Mic */ +#define AIC31XX_ADCFGA AIC31XX_REG(0, 82) /* ADC Digital Volume Control Fine Adjust */ +#define AIC31XX_ADCVOL AIC31XX_REG(0, 83) /* ADC Digital Volume Control Coarse Adjust */ + +/* Page 1 Registers */ +#define AIC31XX_HPDRIVER AIC31XX_REG(1, 31) /* Headphone drivers */ +#define AIC31XX_SPKAMP AIC31XX_REG(1, 32) /* Class-D Speakear Amplifier */ +#define AIC31XX_HPPOP AIC31XX_REG(1, 33) /* HP Output Drivers POP Removal Settings */ +#define AIC31XX_SPPGARAMP AIC31XX_REG(1, 34) /* Output Driver PGA Ramp-Down Period Control */ +#define AIC31XX_DACMIXERROUTE AIC31XX_REG(1, 35) /* DAC_L and DAC_R Output Mixer Routing */ +#define AIC31XX_LANALOGHPL AIC31XX_REG(1, 36) /* Left Analog Vol to HPL */ +#define AIC31XX_RANALOGHPR AIC31XX_REG(1, 37) /* Right Analog Vol to HPR */ +#define AIC31XX_LANALOGSPL AIC31XX_REG(1, 38) /* Left Analog Vol to SPL */ +#define AIC31XX_RANALOGSPR AIC31XX_REG(1, 39) /* Right Analog Vol to SPR */ +#define AIC31XX_HPLGAIN AIC31XX_REG(1, 40) /* HPL Driver */ +#define AIC31XX_HPRGAIN AIC31XX_REG(1, 41) /* HPR Driver */ +#define AIC31XX_SPLGAIN AIC31XX_REG(1, 42) /* SPL Driver */ +#define AIC31XX_SPRGAIN AIC31XX_REG(1, 43) /* SPR Driver */ +#define AIC31XX_HPCONTROL AIC31XX_REG(1, 44) /* HP Driver Control */ +#define AIC31XX_MICBIAS AIC31XX_REG(1, 46) /* MIC Bias Control */ +#define AIC31XX_MICPGA AIC31XX_REG(1, 47) /* MIC PGA*/ +#define AIC31XX_MICPGAPI AIC31XX_REG(1, 48) /* Delta-Sigma Mono ADC Channel Fine-Gain Input Selection for P-Terminal */ +#define AIC31XX_MICPGAMI AIC31XX_REG(1, 49) /* ADC Input Selection for M-Terminal */ +#define AIC31XX_MICPGACM AIC31XX_REG(1, 50) /* Input CM Settings */ + +/* Bits, masks, and shifts */ + +/* AIC31XX_CLKMUX */ +#define AIC31XX_PLL_CLKIN_MASK GENMASK(3, 2) +#define AIC31XX_PLL_CLKIN_SHIFT (2) +#define AIC31XX_PLL_CLKIN_MCLK 0x00 +#define AIC31XX_PLL_CLKIN_BCKL 0x01 +#define AIC31XX_PLL_CLKIN_GPIO1 0x02 +#define AIC31XX_PLL_CLKIN_DIN 0x03 +#define AIC31XX_CODEC_CLKIN_MASK GENMASK(1, 0) +#define AIC31XX_CODEC_CLKIN_SHIFT (0) +#define AIC31XX_CODEC_CLKIN_MCLK 0x00 +#define AIC31XX_CODEC_CLKIN_BCLK 0x01 +#define AIC31XX_CODEC_CLKIN_GPIO1 0x02 +#define AIC31XX_CODEC_CLKIN_PLL 0x03 + +/* AIC31XX_PLLPR */ +/* AIC31XX_NDAC */ +/* AIC31XX_MDAC */ +/* AIC31XX_NADC */ +/* AIC31XX_MADC */ +/* AIC31XX_BCLKN */ +#define AIC31XX_PLL_MASK GENMASK(6, 0) +#define AIC31XX_PM_MASK BIT(7) + +/* AIC31XX_IFACE1 */ +#define AIC31XX_IFACE1_DATATYPE_MASK GENMASK(7, 6) +#define AIC31XX_IFACE1_DATATYPE_SHIFT (6) +#define AIC31XX_I2S_MODE 0x00 +#define AIC31XX_DSP_MODE 0x01 +#define AIC31XX_RIGHT_JUSTIFIED_MODE 0x02 +#define AIC31XX_LEFT_JUSTIFIED_MODE 0x03 +#define AIC31XX_IFACE1_DATALEN_MASK GENMASK(5, 4) +#define AIC31XX_IFACE1_DATALEN_SHIFT (4) +#define AIC31XX_WORD_LEN_16BITS 0x00 +#define AIC31XX_WORD_LEN_20BITS 0x01 +#define AIC31XX_WORD_LEN_24BITS 0x02 +#define AIC31XX_WORD_LEN_32BITS 0x03 +#define AIC31XX_IFACE1_MASTER_MASK GENMASK(3, 2) +#define AIC31XX_BCLK_MASTER BIT(3) +#define AIC31XX_WCLK_MASTER BIT(2) + +/* AIC31XX_DATA_OFFSET */ +#define AIC31XX_DATA_OFFSET_MASK GENMASK(7, 0) + +/* AIC31XX_IFACE2 */ +#define AIC31XX_BCLKINV_MASK BIT(3) +#define AIC31XX_BDIVCLK_MASK GENMASK(1, 0) +#define AIC31XX_DAC2BCLK 0x00 +#define AIC31XX_DACMOD2BCLK 0x01 +#define AIC31XX_ADC2BCLK 0x02 +#define AIC31XX_ADCMOD2BCLK 0x03 +#define AIC31XX_KEEP_I2SCLK BIT(2) + +/* AIC31XX_ADCFLAG */ +#define AIC31XX_ADCPWRSTATUS_MASK BIT(6) + +/* AIC31XX_DACFLAG1 */ +#define AIC31XX_LDACPWRSTATUS_MASK BIT(7) +#define AIC31XX_HPLDRVPWRSTATUS_MASK BIT(5) +#define AIC31XX_SPLDRVPWRSTATUS_MASK BIT(4) +#define AIC31XX_RDACPWRSTATUS_MASK BIT(3) +#define AIC31XX_HPRDRVPWRSTATUS_MASK BIT(1) +#define AIC31XX_SPRDRVPWRSTATUS_MASK BIT(0) + +/* AIC31XX_INTRDACFLAG */ +#define AIC31XX_HPLSCDETECT BIT(7) +#define AIC31XX_HPRSCDETECT BIT(6) +#define AIC31XX_BUTTONPRESS BIT(5) +#define AIC31XX_HSPLUG BIT(4) +#define AIC31XX_LDRCTHRES BIT(3) +#define AIC31XX_RDRCTHRES BIT(2) +#define AIC31XX_DACSINT BIT(1) +#define AIC31XX_DACAINT BIT(0) + +/* AIC31XX_INT1CTRL */ +#define AIC31XX_HSPLUGDET BIT(7) +#define AIC31XX_BUTTONPRESSDET BIT(6) +#define AIC31XX_DRCTHRES BIT(5) +#define AIC31XX_AGCNOISE BIT(4) +#define AIC31XX_SC BIT(3) +#define AIC31XX_ENGINE BIT(2) + +/* AIC31XX_DACSETUP */ +#define AIC31XX_SOFTSTEP_MASK GENMASK(1, 0) + +/* AIC31XX_DACMUTE */ +#define AIC31XX_DACMUTE_MASK GENMASK(3, 2) + +/* AIC31XX_MICBIAS */ +#define AIC31XX_MICBIAS_MASK GENMASK(1, 0) +#define AIC31XX_MICBIAS_SHIFT 0 + +#endif /* _TLV320AIC31XX_H */ diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c new file mode 100644 index 000000000..385fa2e95 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-i2c.c @@ -0,0 +1,76 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4-i2c.c + * + * Copyright 2011 NW Digital Radio + * + * Author: Jeremy McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include "tlv320aic32x4.h" + +static int aic32x4_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + struct regmap_config config; + + config = aic32x4_regmap_config; + config.reg_bits = 8; + config.val_bits = 8; + + regmap = devm_regmap_init_i2c(i2c, &config); + return aic32x4_probe(&i2c->dev, regmap); +} + +static int aic32x4_i2c_remove(struct i2c_client *i2c) +{ + return aic32x4_remove(&i2c->dev); +} + +static const struct i2c_device_id aic32x4_i2c_id[] = { + { "tlv320aic32x4", 0 }, + { "tlv320aic32x6", 1 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id); + +static const struct of_device_id aic32x4_of_id[] = { + { .compatible = "ti,tlv320aic32x4", }, + { .compatible = "ti,tlv320aic32x6", }, + { /* senitel */ } +}; +MODULE_DEVICE_TABLE(of, aic32x4_of_id); + +static struct i2c_driver aic32x4_i2c_driver = { + .driver = { + .name = "tlv320aic32x4", + .of_match_table = aic32x4_of_id, + }, + .probe = aic32x4_i2c_probe, + .remove = aic32x4_i2c_remove, + .id_table = aic32x4_i2c_id, +}; + +module_i2c_driver(aic32x4_i2c_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver I2C"); +MODULE_AUTHOR("Jeremy McDermond "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c new file mode 100644 index 000000000..07d78ae51 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4-spi.c @@ -0,0 +1,78 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4-spi.c + * + * Copyright 2011 NW Digital Radio + * + * Author: Jeremy McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include "tlv320aic32x4.h" + +static int aic32x4_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + struct regmap_config config; + + config = aic32x4_regmap_config; + config.reg_bits = 7; + config.pad_bits = 1; + config.val_bits = 8; + config.read_flag_mask = 0x01; + + regmap = devm_regmap_init_spi(spi, &config); + return aic32x4_probe(&spi->dev, regmap); +} + +static int aic32x4_spi_remove(struct spi_device *spi) +{ + return aic32x4_remove(&spi->dev); +} + +static const struct spi_device_id aic32x4_spi_id[] = { + { "tlv320aic32x4", 0 }, + { "tlv320aic32x6", 1 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, aic32x4_spi_id); + +static const struct of_device_id aic32x4_of_id[] = { + { .compatible = "ti,tlv320aic32x4", }, + { .compatible = "ti,tlv320aic32x6", }, + { /* senitel */ } +}; +MODULE_DEVICE_TABLE(of, aic32x4_of_id); + +static struct spi_driver aic32x4_spi_driver = { + .driver = { + .name = "tlv320aic32x4", + .owner = THIS_MODULE, + .of_match_table = aic32x4_of_id, + }, + .probe = aic32x4_spi_probe, + .remove = aic32x4_spi_remove, + .id_table = aic32x4_spi_id, +}; + +module_spi_driver(aic32x4_spi_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC32x4 codec driver SPI"); +MODULE_AUTHOR("Jeremy McDermond "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c new file mode 100644 index 000000000..45d9f4a09 --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -0,0 +1,1215 @@ +/* + * linux/sound/soc/codecs/tlv320aic32x4.c + * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tlv320aic32x4.h" + +struct aic32x4_rate_divs { + u32 mclk; + u32 rate; + u8 p_val; + u8 pll_j; + u16 pll_d; + u16 dosr; + u8 ndac; + u8 mdac; + u8 aosr; + u8 nadc; + u8 madc; + u8 blck_N; +}; + +struct aic32x4_priv { + struct regmap *regmap; + u32 sysclk; + u32 power_cfg; + u32 micpga_routing; + bool swapdacs; + int rstn_gpio; + struct clk *mclk; + + struct regulator *supply_ldo; + struct regulator *supply_iov; + struct regulator *supply_dv; + struct regulator *supply_av; + + struct aic32x4_setup_data *setup; + struct device *dev; +}; + +static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; + + val = snd_soc_component_read32(component, AIC32X4_DINCTL); + + ucontrol->value.integer.value[0] = (val & 0x01); + + return 0; +}; + +static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; + u8 gpio_check; + + val = snd_soc_component_read32(component, AIC32X4_DOUTCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { + printk(KERN_ERR "%s: MFP2 is not configure as a GPIO output\n", + __func__); + return -EINVAL; + } + + if (ucontrol->value.integer.value[0] == (val & AIC32X4_MFP2_GPIO_OUT_HIGH)) + return 0; + + if (ucontrol->value.integer.value[0]) + val |= ucontrol->value.integer.value[0]; + else + val &= ~AIC32X4_MFP2_GPIO_OUT_HIGH; + + snd_soc_component_write(component, AIC32X4_DOUTCTL, val); + + return 0; +}; + +static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; + + val = snd_soc_component_read32(component, AIC32X4_SCLKCTL); + + ucontrol->value.integer.value[0] = (val & 0x01); + + return 0; +}; + +static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; + u8 gpio_check; + + val = snd_soc_component_read32(component, AIC32X4_MISOCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { + printk(KERN_ERR "%s: MFP4 is not configure as a GPIO output\n", + __func__); + return -EINVAL; + } + + if (ucontrol->value.integer.value[0] == (val & AIC32X4_MFP5_GPIO_OUT_HIGH)) + return 0; + + if (ucontrol->value.integer.value[0]) + val |= ucontrol->value.integer.value[0]; + else + val &= ~AIC32X4_MFP5_GPIO_OUT_HIGH; + + snd_soc_component_write(component, AIC32X4_MISOCTL, val); + + return 0; +}; + +static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; + + val = snd_soc_component_read32(component, AIC32X4_GPIOCTL); + ucontrol->value.integer.value[0] = ((val & 0x2) >> 1); + + return 0; +}; + +static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; + u8 gpio_check; + + val = snd_soc_component_read32(component, AIC32X4_GPIOCTL); + gpio_check = (val & AIC32X4_MFP5_GPIO_OUTPUT); + if (gpio_check != AIC32X4_MFP5_GPIO_OUTPUT) { + printk(KERN_ERR "%s: MFP5 is not configure as a GPIO output\n", + __func__); + return -EINVAL; + } + + if (ucontrol->value.integer.value[0] == (val & 0x1)) + return 0; + + if (ucontrol->value.integer.value[0]) + val |= ucontrol->value.integer.value[0]; + else + val &= 0xfe; + + snd_soc_component_write(component, AIC32X4_GPIOCTL, val); + + return 0; +}; + +static const struct snd_kcontrol_new aic32x4_mfp1[] = { + SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), +}; + +static const struct snd_kcontrol_new aic32x4_mfp2[] = { + SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), +}; + +static const struct snd_kcontrol_new aic32x4_mfp3[] = { + SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), +}; + +static const struct snd_kcontrol_new aic32x4_mfp4[] = { + SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), +}; + +static const struct snd_kcontrol_new aic32x4_mfp5[] = { + SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, + aic32x4_set_mfp5_gpio), +}; + +/* 0dB min, 0.5dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_step_0_5, 0, 50, 0); +/* -63.5dB min, 0.5dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_pcm, -6350, 50, 0); +/* -6dB min, 1dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_driver_gain, -600, 100, 0); +/* -12dB min, 0.5dB steps */ +static DECLARE_TLV_DB_SCALE(tlv_adc_vol, -1200, 50, 0); + +static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("PCM Playback Volume", AIC32X4_LDACVOL, + AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), + SOC_DOUBLE_R_S_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, + AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, + tlv_driver_gain), + SOC_DOUBLE_R_S_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, + AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, + tlv_driver_gain), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, + AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, + AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, + AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_S_TLV("ADC Level Volume", AIC32X4_LADCVOL, + AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, + AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), + SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, + 4, 0x07, 0), + SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, + 0, 0x03, 0), + SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, + 6, 0x03, 0), + SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, + 1, 0x1F, 0), + SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, + 0, 0x7F, 0), + SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, + 3, 0x1F, 0), + SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, + 3, 0x1F, 0), + SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, + 0, 0x1F, 0), + SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, + 0, 0x0F, 0), +}; + +static const struct aic32x4_rate_divs aic32x4_divs[] = { + /* 8k rate */ + {12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24}, + {24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24}, + {25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24}, + /* 11.025k rate */ + {12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16}, + {24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16}, + /* 16k rate */ + {12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12}, + {24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12}, + {25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12}, + /* 22.05k rate */ + {12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8}, + {24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8}, + {25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8}, + /* 32k rate */ + {12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6}, + {24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6}, + /* 44.1k rate */ + {12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4}, + {24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4}, + {25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4}, + /* 48k rate */ + {12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4}, + {24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4}, + {25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}, + + /* 96k rate */ + {25000000, 96000, 2, 7, 8643, 64, 4, 4, 64, 4, 4, 1}, +}; + +static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { + SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0), + SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new hpr_output_mixer_controls[] = { + SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_HPRROUTE, 3, 1, 0), + SOC_DAPM_SINGLE("IN1_R Switch", AIC32X4_HPRROUTE, 2, 1, 0), +}; + +static const struct snd_kcontrol_new lol_output_mixer_controls[] = { + SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_LOLROUTE, 3, 1, 0), +}; + +static const struct snd_kcontrol_new lor_output_mixer_controls[] = { + SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0), +}; + +static const char * const resistor_text[] = { + "Off", "10 kOhm", "20 kOhm", "40 kOhm", +}; + +/* Left mixer pins */ +static SOC_ENUM_SINGLE_DECL(in1l_lpga_p_enum, AIC32X4_LMICPGAPIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_lpga_p_enum, AIC32X4_LMICPGAPIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3l_lpga_p_enum, AIC32X4_LMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in1r_lpga_p_enum, AIC32X4_LMICPGAPIN, 0, resistor_text); + +static SOC_ENUM_SINGLE_DECL(cml_lpga_n_enum, AIC32X4_LMICPGANIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2r_lpga_n_enum, AIC32X4_LMICPGANIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_text); + +static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), +}; +static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), +}; +static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), +}; +static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), +}; +static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), +}; + +/* Right mixer pins */ +static SOC_ENUM_SINGLE_DECL(in1r_rpga_p_enum, AIC32X4_RMICPGAPIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2r_rpga_p_enum, AIC32X4_RMICPGAPIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3r_rpga_p_enum, AIC32X4_RMICPGAPIN, 2, resistor_text); +static SOC_ENUM_SINGLE_DECL(in2l_rpga_p_enum, AIC32X4_RMICPGAPIN, 0, resistor_text); +static SOC_ENUM_SINGLE_DECL(cmr_rpga_n_enum, AIC32X4_RMICPGANIN, 6, resistor_text); +static SOC_ENUM_SINGLE_DECL(in1l_rpga_n_enum, AIC32X4_RMICPGANIN, 4, resistor_text); +static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_text); + +static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), +}; +static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), +}; +static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), +}; +static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), +}; +static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), +}; + +static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", AIC32X4_DACSETUP, 7, 0), + SND_SOC_DAPM_MIXER("HPL Output Mixer", SND_SOC_NOPM, 0, 0, + &hpl_output_mixer_controls[0], + ARRAY_SIZE(hpl_output_mixer_controls)), + SND_SOC_DAPM_PGA("HPL Power", AIC32X4_OUTPWRCTL, 5, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("LOL Output Mixer", SND_SOC_NOPM, 0, 0, + &lol_output_mixer_controls[0], + ARRAY_SIZE(lol_output_mixer_controls)), + SND_SOC_DAPM_PGA("LOL Power", AIC32X4_OUTPWRCTL, 3, 0, NULL, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", AIC32X4_DACSETUP, 6, 0), + SND_SOC_DAPM_MIXER("HPR Output Mixer", SND_SOC_NOPM, 0, 0, + &hpr_output_mixer_controls[0], + ARRAY_SIZE(hpr_output_mixer_controls)), + SND_SOC_DAPM_PGA("HPR Power", AIC32X4_OUTPWRCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_MIXER("LOR Output Mixer", SND_SOC_NOPM, 0, 0, + &lor_output_mixer_controls[0], + ARRAY_SIZE(lor_output_mixer_controls)), + SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, + in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, + in3r_to_lmixer_controls), + + SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LOL"), + SND_SOC_DAPM_OUTPUT("LOR"), + SND_SOC_DAPM_INPUT("IN1_L"), + SND_SOC_DAPM_INPUT("IN1_R"), + SND_SOC_DAPM_INPUT("IN2_L"), + SND_SOC_DAPM_INPUT("IN2_R"), + SND_SOC_DAPM_INPUT("IN3_L"), + SND_SOC_DAPM_INPUT("IN3_R"), + SND_SOC_DAPM_INPUT("CM_L"), + SND_SOC_DAPM_INPUT("CM_R"), +}; + +static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + /* Left Output */ + {"HPL Output Mixer", "L_DAC Switch", "Left DAC"}, + {"HPL Output Mixer", "IN1_L Switch", "IN1_L"}, + + {"HPL Power", NULL, "HPL Output Mixer"}, + {"HPL", NULL, "HPL Power"}, + + {"LOL Output Mixer", "L_DAC Switch", "Left DAC"}, + + {"LOL Power", NULL, "LOL Output Mixer"}, + {"LOL", NULL, "LOL Power"}, + + /* Right Output */ + {"HPR Output Mixer", "R_DAC Switch", "Right DAC"}, + {"HPR Output Mixer", "IN1_R Switch", "IN1_R"}, + + {"HPR Power", NULL, "HPR Output Mixer"}, + {"HPR", NULL, "HPR Power"}, + + {"LOR Output Mixer", "R_DAC Switch", "Right DAC"}, + + {"LOR Power", NULL, "LOR Output Mixer"}, + {"LOR", NULL, "LOR Power"}, + + /* Right Input */ + {"Right ADC", NULL, "IN1_R to Right Mixer Positive Resistor"}, + {"IN1_R to Right Mixer Positive Resistor", "10 kOhm", "IN1_R"}, + {"IN1_R to Right Mixer Positive Resistor", "20 kOhm", "IN1_R"}, + {"IN1_R to Right Mixer Positive Resistor", "40 kOhm", "IN1_R"}, + + {"Right ADC", NULL, "IN2_R to Right Mixer Positive Resistor"}, + {"IN2_R to Right Mixer Positive Resistor", "10 kOhm", "IN2_R"}, + {"IN2_R to Right Mixer Positive Resistor", "20 kOhm", "IN2_R"}, + {"IN2_R to Right Mixer Positive Resistor", "40 kOhm", "IN2_R"}, + + {"Right ADC", NULL, "IN3_R to Right Mixer Positive Resistor"}, + {"IN3_R to Right Mixer Positive Resistor", "10 kOhm", "IN3_R"}, + {"IN3_R to Right Mixer Positive Resistor", "20 kOhm", "IN3_R"}, + {"IN3_R to Right Mixer Positive Resistor", "40 kOhm", "IN3_R"}, + + {"Right ADC", NULL, "IN2_L to Right Mixer Positive Resistor"}, + {"IN2_L to Right Mixer Positive Resistor", "10 kOhm", "IN2_L"}, + {"IN2_L to Right Mixer Positive Resistor", "20 kOhm", "IN2_L"}, + {"IN2_L to Right Mixer Positive Resistor", "40 kOhm", "IN2_L"}, + + {"Right ADC", NULL, "CM_R to Right Mixer Negative Resistor"}, + {"CM_R to Right Mixer Negative Resistor", "10 kOhm", "CM_R"}, + {"CM_R to Right Mixer Negative Resistor", "20 kOhm", "CM_R"}, + {"CM_R to Right Mixer Negative Resistor", "40 kOhm", "CM_R"}, + + {"Right ADC", NULL, "IN1_L to Right Mixer Negative Resistor"}, + {"IN1_L to Right Mixer Negative Resistor", "10 kOhm", "IN1_L"}, + {"IN1_L to Right Mixer Negative Resistor", "20 kOhm", "IN1_L"}, + {"IN1_L to Right Mixer Negative Resistor", "40 kOhm", "IN1_L"}, + + {"Right ADC", NULL, "IN3_L to Right Mixer Negative Resistor"}, + {"IN3_L to Right Mixer Negative Resistor", "10 kOhm", "IN3_L"}, + {"IN3_L to Right Mixer Negative Resistor", "20 kOhm", "IN3_L"}, + {"IN3_L to Right Mixer Negative Resistor", "40 kOhm", "IN3_L"}, + + /* Left Input */ + {"Left ADC", NULL, "IN1_L to Left Mixer Positive Resistor"}, + {"IN1_L to Left Mixer Positive Resistor", "10 kOhm", "IN1_L"}, + {"IN1_L to Left Mixer Positive Resistor", "20 kOhm", "IN1_L"}, + {"IN1_L to Left Mixer Positive Resistor", "40 kOhm", "IN1_L"}, + + {"Left ADC", NULL, "IN2_L to Left Mixer Positive Resistor"}, + {"IN2_L to Left Mixer Positive Resistor", "10 kOhm", "IN2_L"}, + {"IN2_L to Left Mixer Positive Resistor", "20 kOhm", "IN2_L"}, + {"IN2_L to Left Mixer Positive Resistor", "40 kOhm", "IN2_L"}, + + {"Left ADC", NULL, "IN3_L to Left Mixer Positive Resistor"}, + {"IN3_L to Left Mixer Positive Resistor", "10 kOhm", "IN3_L"}, + {"IN3_L to Left Mixer Positive Resistor", "20 kOhm", "IN3_L"}, + {"IN3_L to Left Mixer Positive Resistor", "40 kOhm", "IN3_L"}, + + {"Left ADC", NULL, "IN1_R to Left Mixer Positive Resistor"}, + {"IN1_R to Left Mixer Positive Resistor", "10 kOhm", "IN1_R"}, + {"IN1_R to Left Mixer Positive Resistor", "20 kOhm", "IN1_R"}, + {"IN1_R to Left Mixer Positive Resistor", "40 kOhm", "IN1_R"}, + + {"Left ADC", NULL, "CM_L to Left Mixer Negative Resistor"}, + {"CM_L to Left Mixer Negative Resistor", "10 kOhm", "CM_L"}, + {"CM_L to Left Mixer Negative Resistor", "20 kOhm", "CM_L"}, + {"CM_L to Left Mixer Negative Resistor", "40 kOhm", "CM_L"}, + + {"Left ADC", NULL, "IN2_R to Left Mixer Negative Resistor"}, + {"IN2_R to Left Mixer Negative Resistor", "10 kOhm", "IN2_R"}, + {"IN2_R to Left Mixer Negative Resistor", "20 kOhm", "IN2_R"}, + {"IN2_R to Left Mixer Negative Resistor", "40 kOhm", "IN2_R"}, + + {"Left ADC", NULL, "IN3_R to Left Mixer Negative Resistor"}, + {"IN3_R to Left Mixer Negative Resistor", "10 kOhm", "IN3_R"}, + {"IN3_R to Left Mixer Negative Resistor", "20 kOhm", "IN3_R"}, + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, +}; + +static const struct regmap_range_cfg aic32x4_regmap_pages[] = { + { + .selector_reg = 0, + .selector_mask = 0xff, + .window_start = 0, + .window_len = 128, + .range_min = 0, + .range_max = AIC32X4_RMICPGAVOL, + }, +}; + +const struct regmap_config aic32x4_regmap_config = { + .max_register = AIC32X4_RMICPGAVOL, + .ranges = aic32x4_regmap_pages, + .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +}; +EXPORT_SYMBOL(aic32x4_regmap_config); + +static inline int aic32x4_get_divs(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) { + if ((aic32x4_divs[i].rate == rate) + && (aic32x4_divs[i].mclk == mclk)) { + return i; + } + } + printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n"); + return -EINVAL; +} + +static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 12000000: + case 24000000: + case 25000000: + aic32x4->sysclk = freq; + return 0; + } + printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n"); + return -EINVAL; +} + +static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u8 iface_reg_1 = 0; + u8 iface_reg_2 = 0; + u8 iface_reg_3 = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface_reg_1 |= AIC32X4_BCLKMASTER | AIC32X4_WCLKMASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + printk(KERN_ERR "aic32x4: invalid DAI master/slave interface\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg_1 |= (AIC32X4_DSP_MODE << + AIC32X4_IFACE1_DATATYPE_SHIFT); + iface_reg_3 |= AIC32X4_BCLKINV_MASK; /* invert bit clock */ + iface_reg_2 = 0x01; /* add offset 1 */ + break; + case SND_SOC_DAIFMT_DSP_B: + iface_reg_1 |= (AIC32X4_DSP_MODE << + AIC32X4_IFACE1_DATATYPE_SHIFT); + iface_reg_3 |= AIC32X4_BCLKINV_MASK; /* invert bit clock */ + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg_1 |= (AIC32X4_RIGHT_JUSTIFIED_MODE << + AIC32X4_IFACE1_DATATYPE_SHIFT); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg_1 |= (AIC32X4_LEFT_JUSTIFIED_MODE << + AIC32X4_IFACE1_DATATYPE_SHIFT); + break; + default: + printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, AIC32X4_IFACE1, + AIC32X4_IFACE1_DATATYPE_MASK | + AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); + snd_soc_component_update_bits(component, AIC32X4_IFACE2, + AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + snd_soc_component_update_bits(component, AIC32X4_IFACE3, + AIC32X4_BCLKINV_MASK, iface_reg_3); + + return 0; +} + +static int aic32x4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + u8 iface1_reg = 0; + u8 dacsetup_reg = 0; + int i; + + i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params)); + if (i < 0) { + printk(KERN_ERR "aic32x4: sampling rate not supported\n"); + return i; + } + + /* MCLK as PLL_CLKIN */ + snd_soc_component_update_bits(component, AIC32X4_CLKMUX, AIC32X4_PLL_CLKIN_MASK, + AIC32X4_PLL_CLKIN_MCLK << AIC32X4_PLL_CLKIN_SHIFT); + /* PLL as CODEC_CLKIN */ + snd_soc_component_update_bits(component, AIC32X4_CLKMUX, AIC32X4_CODEC_CLKIN_MASK, + AIC32X4_CODEC_CLKIN_PLL << AIC32X4_CODEC_CLKIN_SHIFT); + /* DAC_MOD_CLK as BDIV_CLKIN */ + snd_soc_component_update_bits(component, AIC32X4_IFACE3, AIC32X4_BDIVCLK_MASK, + AIC32X4_DACMOD2BCLK << AIC32X4_BDIVCLK_SHIFT); + + /* We will fix R value to 1 and will make P & J=K.D as variable */ + snd_soc_component_update_bits(component, AIC32X4_PLLPR, AIC32X4_PLL_R_MASK, 0x01); + + /* PLL P value */ + snd_soc_component_update_bits(component, AIC32X4_PLLPR, AIC32X4_PLL_P_MASK, + aic32x4_divs[i].p_val << AIC32X4_PLL_P_SHIFT); + + /* PLL J value */ + snd_soc_component_write(component, AIC32X4_PLLJ, aic32x4_divs[i].pll_j); + + /* PLL D value */ + snd_soc_component_write(component, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8)); + snd_soc_component_write(component, AIC32X4_PLLDLSB, (aic32x4_divs[i].pll_d & 0xff)); + + /* NDAC divider value */ + snd_soc_component_update_bits(component, AIC32X4_NDAC, + AIC32X4_NDAC_MASK, aic32x4_divs[i].ndac); + + /* MDAC divider value */ + snd_soc_component_update_bits(component, AIC32X4_MDAC, + AIC32X4_MDAC_MASK, aic32x4_divs[i].mdac); + + /* DOSR MSB & LSB values */ + snd_soc_component_write(component, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8); + snd_soc_component_write(component, AIC32X4_DOSRLSB, (aic32x4_divs[i].dosr & 0xff)); + + /* NADC divider value */ + snd_soc_component_update_bits(component, AIC32X4_NADC, + AIC32X4_NADC_MASK, aic32x4_divs[i].nadc); + + /* MADC divider value */ + snd_soc_component_update_bits(component, AIC32X4_MADC, + AIC32X4_MADC_MASK, aic32x4_divs[i].madc); + + /* AOSR value */ + snd_soc_component_write(component, AIC32X4_AOSR, aic32x4_divs[i].aosr); + + /* BCLK N divider */ + snd_soc_component_update_bits(component, AIC32X4_BCLKN, + AIC32X4_BCLK_MASK, aic32x4_divs[i].blck_N); + + switch (params_width(params)) { + case 16: + iface1_reg |= (AIC32X4_WORD_LEN_16BITS << + AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 20: + iface1_reg |= (AIC32X4_WORD_LEN_20BITS << + AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 24: + iface1_reg |= (AIC32X4_WORD_LEN_24BITS << + AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 32: + iface1_reg |= (AIC32X4_WORD_LEN_32BITS << + AIC32X4_IFACE1_DATALEN_SHIFT); + break; + } + snd_soc_component_update_bits(component, AIC32X4_IFACE1, + AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + + if (params_channels(params) == 1) { + dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; + } else { + if (aic32x4->swapdacs) + dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2RCHN; + else + dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; + } + snd_soc_component_update_bits(component, AIC32X4_DACSETUP, + AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + + return 0; +} + +static int aic32x4_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + snd_soc_component_update_bits(component, AIC32X4_DACMUTE, + AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + + return 0; +} + +static int aic32x4_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + /* Switch on master clock */ + ret = clk_prepare_enable(aic32x4->mclk); + if (ret) { + dev_err(component->dev, "Failed to enable master clock\n"); + return ret; + } + + /* Switch on PLL */ + snd_soc_component_update_bits(component, AIC32X4_PLLPR, + AIC32X4_PLLEN, AIC32X4_PLLEN); + + /* Switch on NDAC Divider */ + snd_soc_component_update_bits(component, AIC32X4_NDAC, + AIC32X4_NDACEN, AIC32X4_NDACEN); + + /* Switch on MDAC Divider */ + snd_soc_component_update_bits(component, AIC32X4_MDAC, + AIC32X4_MDACEN, AIC32X4_MDACEN); + + /* Switch on NADC Divider */ + snd_soc_component_update_bits(component, AIC32X4_NADC, + AIC32X4_NADCEN, AIC32X4_NADCEN); + + /* Switch on MADC Divider */ + snd_soc_component_update_bits(component, AIC32X4_MADC, + AIC32X4_MADCEN, AIC32X4_MADCEN); + + /* Switch on BCLK_N Divider */ + snd_soc_component_update_bits(component, AIC32X4_BCLKN, + AIC32X4_BCLKEN, AIC32X4_BCLKEN); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* Initial cold start */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + break; + + /* Switch off BCLK_N Divider */ + snd_soc_component_update_bits(component, AIC32X4_BCLKN, + AIC32X4_BCLKEN, 0); + + /* Switch off MADC Divider */ + snd_soc_component_update_bits(component, AIC32X4_MADC, + AIC32X4_MADCEN, 0); + + /* Switch off NADC Divider */ + snd_soc_component_update_bits(component, AIC32X4_NADC, + AIC32X4_NADCEN, 0); + + /* Switch off MDAC Divider */ + snd_soc_component_update_bits(component, AIC32X4_MDAC, + AIC32X4_MDACEN, 0); + + /* Switch off NDAC Divider */ + snd_soc_component_update_bits(component, AIC32X4_NDAC, + AIC32X4_NDACEN, 0); + + /* Switch off PLL */ + snd_soc_component_update_bits(component, AIC32X4_PLLPR, + AIC32X4_PLLEN, 0); + + /* Switch off master clock */ + clk_disable_unprepare(aic32x4->mclk); + break; + case SND_SOC_BIAS_OFF: + break; + } + return 0; +} + +#define AIC32X4_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops aic32x4_ops = { + .hw_params = aic32x4_hw_params, + .digital_mute = aic32x4_mute, + .set_fmt = aic32x4_set_dai_fmt, + .set_sysclk = aic32x4_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver aic32x4_dai = { + .name = "tlv320aic32x4-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC32X4_RATES, + .formats = AIC32X4_FORMATS,}, + .ops = &aic32x4_ops, + .symmetric_rates = 1, +}; + +static void aic32x4_setup_gpios(struct snd_soc_component *component) +{ + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + + /* setup GPIO functions */ + /* MFP1 */ + if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { + snd_soc_component_write(component, AIC32X4_DINCTL, + aic32x4->setup->gpio_func[0]); + snd_soc_add_component_controls(component, aic32x4_mfp1, + ARRAY_SIZE(aic32x4_mfp1)); + } + + /* MFP2 */ + if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { + snd_soc_component_write(component, AIC32X4_DOUTCTL, + aic32x4->setup->gpio_func[1]); + snd_soc_add_component_controls(component, aic32x4_mfp2, + ARRAY_SIZE(aic32x4_mfp2)); + } + + /* MFP3 */ + if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { + snd_soc_component_write(component, AIC32X4_SCLKCTL, + aic32x4->setup->gpio_func[2]); + snd_soc_add_component_controls(component, aic32x4_mfp3, + ARRAY_SIZE(aic32x4_mfp3)); + } + + /* MFP4 */ + if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { + snd_soc_component_write(component, AIC32X4_MISOCTL, + aic32x4->setup->gpio_func[3]); + snd_soc_add_component_controls(component, aic32x4_mfp4, + ARRAY_SIZE(aic32x4_mfp4)); + } + + /* MFP5 */ + if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { + snd_soc_component_write(component, AIC32X4_GPIOCTL, + aic32x4->setup->gpio_func[4]); + snd_soc_add_component_controls(component, aic32x4_mfp5, + ARRAY_SIZE(aic32x4_mfp5)); + } +} + +static int aic32x4_component_probe(struct snd_soc_component *component) +{ + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + u32 tmp_reg; + + if (gpio_is_valid(aic32x4->rstn_gpio)) { + ndelay(10); + gpio_set_value(aic32x4->rstn_gpio, 1); + } + + snd_soc_component_write(component, AIC32X4_RESET, 0x01); + + if (aic32x4->setup) + aic32x4_setup_gpios(component); + + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_component_write(component, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN | + AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) + snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); + + tmp_reg = (aic32x4->power_cfg & AIC32X4_PWR_AIC32X4_LDO_ENABLE) ? + AIC32X4_LDOCTLEN : 0; + snd_soc_component_write(component, AIC32X4_LDOCTL, tmp_reg); + + tmp_reg = snd_soc_component_read32(component, AIC32X4_CMMODE); + if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36) + tmp_reg |= AIC32X4_LDOIN_18_36; + if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED) + tmp_reg |= AIC32X4_LDOIN2HP; + snd_soc_component_write(component, AIC32X4_CMMODE, tmp_reg); + + /* Mic PGA routing */ + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) + snd_soc_component_write(component, AIC32X4_LMICPGANIN, + AIC32X4_LMICPGANIN_IN2R_10K); + else + snd_soc_component_write(component, AIC32X4_LMICPGANIN, + AIC32X4_LMICPGANIN_CM1L_10K); + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) + snd_soc_component_write(component, AIC32X4_RMICPGANIN, + AIC32X4_RMICPGANIN_IN1L_10K); + else + snd_soc_component_write(component, AIC32X4_RMICPGANIN, + AIC32X4_RMICPGANIN_CM1R_10K); + + /* + * Workaround: for an unknown reason, the ADC needs to be powered up + * and down for the first capture to work properly. It seems related to + * a HW BUG or some kind of behavior not documented in the datasheet. + */ + tmp_reg = snd_soc_component_read32(component, AIC32X4_ADCSETUP); + snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg | + AIC32X4_LADC_EN | AIC32X4_RADC_EN); + snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_aic32x4 = { + .probe = aic32x4_component_probe, + .set_bias_level = aic32x4_set_bias_level, + .controls = aic32x4_snd_controls, + .num_controls = ARRAY_SIZE(aic32x4_snd_controls), + .dapm_widgets = aic32x4_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aic32x4_dapm_widgets), + .dapm_routes = aic32x4_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(aic32x4_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, + struct device_node *np) +{ + struct aic32x4_setup_data *aic32x4_setup; + + aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), + GFP_KERNEL); + if (!aic32x4_setup) + return -ENOMEM; + + aic32x4->swapdacs = false; + aic32x4->micpga_routing = 0; + aic32x4->rstn_gpio = of_get_named_gpio(np, "reset-gpios", 0); + + if (of_property_read_u32_array(np, "aic32x4-gpio-func", + aic32x4_setup->gpio_func, 5) >= 0) + aic32x4->setup = aic32x4_setup; + return 0; +} + +static void aic32x4_disable_regulators(struct aic32x4_priv *aic32x4) +{ + regulator_disable(aic32x4->supply_iov); + + if (!IS_ERR(aic32x4->supply_ldo)) + regulator_disable(aic32x4->supply_ldo); + + if (!IS_ERR(aic32x4->supply_dv)) + regulator_disable(aic32x4->supply_dv); + + if (!IS_ERR(aic32x4->supply_av)) + regulator_disable(aic32x4->supply_av); +} + +static int aic32x4_setup_regulators(struct device *dev, + struct aic32x4_priv *aic32x4) +{ + int ret = 0; + + aic32x4->supply_ldo = devm_regulator_get_optional(dev, "ldoin"); + aic32x4->supply_iov = devm_regulator_get(dev, "iov"); + aic32x4->supply_dv = devm_regulator_get_optional(dev, "dv"); + aic32x4->supply_av = devm_regulator_get_optional(dev, "av"); + + /* Check if the regulator requirements are fulfilled */ + + if (IS_ERR(aic32x4->supply_iov)) { + dev_err(dev, "Missing supply 'iov'\n"); + return PTR_ERR(aic32x4->supply_iov); + } + + if (IS_ERR(aic32x4->supply_ldo)) { + if (PTR_ERR(aic32x4->supply_ldo) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (IS_ERR(aic32x4->supply_dv)) { + dev_err(dev, "Missing supply 'dv' or 'ldoin'\n"); + return PTR_ERR(aic32x4->supply_dv); + } + if (IS_ERR(aic32x4->supply_av)) { + dev_err(dev, "Missing supply 'av' or 'ldoin'\n"); + return PTR_ERR(aic32x4->supply_av); + } + } else { + if (IS_ERR(aic32x4->supply_dv) && + PTR_ERR(aic32x4->supply_dv) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (IS_ERR(aic32x4->supply_av) && + PTR_ERR(aic32x4->supply_av) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + + ret = regulator_enable(aic32x4->supply_iov); + if (ret) { + dev_err(dev, "Failed to enable regulator iov\n"); + return ret; + } + + if (!IS_ERR(aic32x4->supply_ldo)) { + ret = regulator_enable(aic32x4->supply_ldo); + if (ret) { + dev_err(dev, "Failed to enable regulator ldo\n"); + goto error_ldo; + } + } + + if (!IS_ERR(aic32x4->supply_dv)) { + ret = regulator_enable(aic32x4->supply_dv); + if (ret) { + dev_err(dev, "Failed to enable regulator dv\n"); + goto error_dv; + } + } + + if (!IS_ERR(aic32x4->supply_av)) { + ret = regulator_enable(aic32x4->supply_av); + if (ret) { + dev_err(dev, "Failed to enable regulator av\n"); + goto error_av; + } + } + + if (!IS_ERR(aic32x4->supply_ldo) && IS_ERR(aic32x4->supply_av)) + aic32x4->power_cfg |= AIC32X4_PWR_AIC32X4_LDO_ENABLE; + + return 0; + +error_av: + if (!IS_ERR(aic32x4->supply_dv)) + regulator_disable(aic32x4->supply_dv); + +error_dv: + if (!IS_ERR(aic32x4->supply_ldo)) + regulator_disable(aic32x4->supply_ldo); + +error_ldo: + regulator_disable(aic32x4->supply_iov); + return ret; +} + +int aic32x4_probe(struct device *dev, struct regmap *regmap) +{ + struct aic32x4_priv *aic32x4; + struct aic32x4_pdata *pdata = dev->platform_data; + struct device_node *np = dev->of_node; + int ret; + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), + GFP_KERNEL); + if (aic32x4 == NULL) + return -ENOMEM; + + aic32x4->dev = dev; + dev_set_drvdata(dev, aic32x4); + + if (pdata) { + aic32x4->power_cfg = pdata->power_cfg; + aic32x4->swapdacs = pdata->swapdacs; + aic32x4->micpga_routing = pdata->micpga_routing; + aic32x4->rstn_gpio = pdata->rstn_gpio; + } else if (np) { + ret = aic32x4_parse_dt(aic32x4, np); + if (ret) { + dev_err(dev, "Failed to parse DT node\n"); + return ret; + } + } else { + aic32x4->power_cfg = 0; + aic32x4->swapdacs = false; + aic32x4->micpga_routing = 0; + aic32x4->rstn_gpio = -1; + } + + aic32x4->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(aic32x4->mclk)) { + dev_err(dev, "Failed getting the mclk. The current implementation does not support the usage of this codec without mclk\n"); + return PTR_ERR(aic32x4->mclk); + } + + if (gpio_is_valid(aic32x4->rstn_gpio)) { + ret = devm_gpio_request_one(dev, aic32x4->rstn_gpio, + GPIOF_OUT_INIT_LOW, "tlv320aic32x4 rstn"); + if (ret != 0) + return ret; + } + + ret = aic32x4_setup_regulators(dev, aic32x4); + if (ret) { + dev_err(dev, "Failed to setup regulators\n"); + return ret; + } + + ret = devm_snd_soc_register_component(dev, + &soc_component_dev_aic32x4, &aic32x4_dai, 1); + if (ret) { + dev_err(dev, "Failed to register component\n"); + aic32x4_disable_regulators(aic32x4); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(aic32x4_probe); + +int aic32x4_remove(struct device *dev) +{ + struct aic32x4_priv *aic32x4 = dev_get_drvdata(dev); + + aic32x4_disable_regulators(aic32x4); + + return 0; +} +EXPORT_SYMBOL(aic32x4_remove); + +MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver"); +MODULE_AUTHOR("Javier Martin "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h new file mode 100644 index 000000000..e9df49edb --- /dev/null +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -0,0 +1,207 @@ +/* + * tlv320aic32x4.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#ifndef _TLV320AIC32X4_H +#define _TLV320AIC32X4_H + +struct device; +struct regmap_config; + +extern const struct regmap_config aic32x4_regmap_config; +int aic32x4_probe(struct device *dev, struct regmap *regmap); +int aic32x4_remove(struct device *dev); + +/* tlv320aic32x4 register space (in decimal to match datasheet) */ + +#define AIC32X4_REG(page, reg) ((page * 128) + reg) + +#define AIC32X4_PSEL AIC32X4_REG(0, 0) + +#define AIC32X4_RESET AIC32X4_REG(0, 1) +#define AIC32X4_CLKMUX AIC32X4_REG(0, 4) +#define AIC32X4_PLLPR AIC32X4_REG(0, 5) +#define AIC32X4_PLLJ AIC32X4_REG(0, 6) +#define AIC32X4_PLLDMSB AIC32X4_REG(0, 7) +#define AIC32X4_PLLDLSB AIC32X4_REG(0, 8) +#define AIC32X4_NDAC AIC32X4_REG(0, 11) +#define AIC32X4_MDAC AIC32X4_REG(0, 12) +#define AIC32X4_DOSRMSB AIC32X4_REG(0, 13) +#define AIC32X4_DOSRLSB AIC32X4_REG(0, 14) +#define AIC32X4_NADC AIC32X4_REG(0, 18) +#define AIC32X4_MADC AIC32X4_REG(0, 19) +#define AIC32X4_AOSR AIC32X4_REG(0, 20) +#define AIC32X4_CLKMUX2 AIC32X4_REG(0, 25) +#define AIC32X4_CLKOUTM AIC32X4_REG(0, 26) +#define AIC32X4_IFACE1 AIC32X4_REG(0, 27) +#define AIC32X4_IFACE2 AIC32X4_REG(0, 28) +#define AIC32X4_IFACE3 AIC32X4_REG(0, 29) +#define AIC32X4_BCLKN AIC32X4_REG(0, 30) +#define AIC32X4_IFACE4 AIC32X4_REG(0, 31) +#define AIC32X4_IFACE5 AIC32X4_REG(0, 32) +#define AIC32X4_IFACE6 AIC32X4_REG(0, 33) +#define AIC32X4_GPIOCTL AIC32X4_REG(0, 52) +#define AIC32X4_DOUTCTL AIC32X4_REG(0, 53) +#define AIC32X4_DINCTL AIC32X4_REG(0, 54) +#define AIC32X4_MISOCTL AIC32X4_REG(0, 55) +#define AIC32X4_SCLKCTL AIC32X4_REG(0, 56) +#define AIC32X4_DACSPB AIC32X4_REG(0, 60) +#define AIC32X4_ADCSPB AIC32X4_REG(0, 61) +#define AIC32X4_DACSETUP AIC32X4_REG(0, 63) +#define AIC32X4_DACMUTE AIC32X4_REG(0, 64) +#define AIC32X4_LDACVOL AIC32X4_REG(0, 65) +#define AIC32X4_RDACVOL AIC32X4_REG(0, 66) +#define AIC32X4_ADCSETUP AIC32X4_REG(0, 81) +#define AIC32X4_ADCFGA AIC32X4_REG(0, 82) +#define AIC32X4_LADCVOL AIC32X4_REG(0, 83) +#define AIC32X4_RADCVOL AIC32X4_REG(0, 84) +#define AIC32X4_LAGC1 AIC32X4_REG(0, 86) +#define AIC32X4_LAGC2 AIC32X4_REG(0, 87) +#define AIC32X4_LAGC3 AIC32X4_REG(0, 88) +#define AIC32X4_LAGC4 AIC32X4_REG(0, 89) +#define AIC32X4_LAGC5 AIC32X4_REG(0, 90) +#define AIC32X4_LAGC6 AIC32X4_REG(0, 91) +#define AIC32X4_LAGC7 AIC32X4_REG(0, 92) +#define AIC32X4_RAGC1 AIC32X4_REG(0, 94) +#define AIC32X4_RAGC2 AIC32X4_REG(0, 95) +#define AIC32X4_RAGC3 AIC32X4_REG(0, 96) +#define AIC32X4_RAGC4 AIC32X4_REG(0, 97) +#define AIC32X4_RAGC5 AIC32X4_REG(0, 98) +#define AIC32X4_RAGC6 AIC32X4_REG(0, 99) +#define AIC32X4_RAGC7 AIC32X4_REG(0, 100) + +#define AIC32X4_PWRCFG AIC32X4_REG(1, 1) +#define AIC32X4_LDOCTL AIC32X4_REG(1, 2) +#define AIC32X4_OUTPWRCTL AIC32X4_REG(1, 9) +#define AIC32X4_CMMODE AIC32X4_REG(1, 10) +#define AIC32X4_HPLROUTE AIC32X4_REG(1, 12) +#define AIC32X4_HPRROUTE AIC32X4_REG(1, 13) +#define AIC32X4_LOLROUTE AIC32X4_REG(1, 14) +#define AIC32X4_LORROUTE AIC32X4_REG(1, 15) +#define AIC32X4_HPLGAIN AIC32X4_REG(1, 16) +#define AIC32X4_HPRGAIN AIC32X4_REG(1, 17) +#define AIC32X4_LOLGAIN AIC32X4_REG(1, 18) +#define AIC32X4_LORGAIN AIC32X4_REG(1, 19) +#define AIC32X4_HEADSTART AIC32X4_REG(1, 20) +#define AIC32X4_MICBIAS AIC32X4_REG(1, 51) +#define AIC32X4_LMICPGAPIN AIC32X4_REG(1, 52) +#define AIC32X4_LMICPGANIN AIC32X4_REG(1, 54) +#define AIC32X4_RMICPGAPIN AIC32X4_REG(1, 55) +#define AIC32X4_RMICPGANIN AIC32X4_REG(1, 57) +#define AIC32X4_FLOATINGINPUT AIC32X4_REG(1, 58) +#define AIC32X4_LMICPGAVOL AIC32X4_REG(1, 59) +#define AIC32X4_RMICPGAVOL AIC32X4_REG(1, 60) + +/* Bits, masks, and shifts */ + +/* AIC32X4_CLKMUX */ +#define AIC32X4_PLL_CLKIN_MASK GENMASK(3, 2) +#define AIC32X4_PLL_CLKIN_SHIFT (2) +#define AIC32X4_PLL_CLKIN_MCLK (0x00) +#define AIC32X4_PLL_CLKIN_BCKL (0x01) +#define AIC32X4_PLL_CLKIN_GPIO1 (0x02) +#define AIC32X4_PLL_CLKIN_DIN (0x03) +#define AIC32X4_CODEC_CLKIN_MASK GENMASK(1, 0) +#define AIC32X4_CODEC_CLKIN_SHIFT (0) +#define AIC32X4_CODEC_CLKIN_MCLK (0x00) +#define AIC32X4_CODEC_CLKIN_BCLK (0x01) +#define AIC32X4_CODEC_CLKIN_GPIO1 (0x02) +#define AIC32X4_CODEC_CLKIN_PLL (0x03) + +/* AIC32X4_PLLPR */ +#define AIC32X4_PLLEN BIT(7) +#define AIC32X4_PLL_P_MASK GENMASK(6, 4) +#define AIC32X4_PLL_P_SHIFT (4) +#define AIC32X4_PLL_R_MASK GENMASK(3, 0) + +/* AIC32X4_NDAC */ +#define AIC32X4_NDACEN BIT(7) +#define AIC32X4_NDAC_MASK GENMASK(6, 0) + +/* AIC32X4_MDAC */ +#define AIC32X4_MDACEN BIT(7) +#define AIC32X4_MDAC_MASK GENMASK(6, 0) + +/* AIC32X4_NADC */ +#define AIC32X4_NADCEN BIT(7) +#define AIC32X4_NADC_MASK GENMASK(6, 0) + +/* AIC32X4_MADC */ +#define AIC32X4_MADCEN BIT(7) +#define AIC32X4_MADC_MASK GENMASK(6, 0) + +/* AIC32X4_BCLKN */ +#define AIC32X4_BCLKEN BIT(7) +#define AIC32X4_BCLK_MASK GENMASK(6, 0) + +/* AIC32X4_IFACE1 */ +#define AIC32X4_IFACE1_DATATYPE_MASK GENMASK(7, 6) +#define AIC32X4_IFACE1_DATATYPE_SHIFT (6) +#define AIC32X4_I2S_MODE (0x00) +#define AIC32X4_DSP_MODE (0x01) +#define AIC32X4_RIGHT_JUSTIFIED_MODE (0x02) +#define AIC32X4_LEFT_JUSTIFIED_MODE (0x03) +#define AIC32X4_IFACE1_DATALEN_MASK GENMASK(5, 4) +#define AIC32X4_IFACE1_DATALEN_SHIFT (4) +#define AIC32X4_WORD_LEN_16BITS (0x00) +#define AIC32X4_WORD_LEN_20BITS (0x01) +#define AIC32X4_WORD_LEN_24BITS (0x02) +#define AIC32X4_WORD_LEN_32BITS (0x03) +#define AIC32X4_IFACE1_MASTER_MASK GENMASK(3, 2) +#define AIC32X4_BCLKMASTER BIT(2) +#define AIC32X4_WCLKMASTER BIT(3) + +/* AIC32X4_IFACE2 */ +#define AIC32X4_DATA_OFFSET_MASK GENMASK(7, 0) + +/* AIC32X4_IFACE3 */ +#define AIC32X4_BCLKINV_MASK BIT(3) +#define AIC32X4_BDIVCLK_MASK GENMASK(1, 0) +#define AIC32X4_BDIVCLK_SHIFT (0) +#define AIC32X4_DAC2BCLK (0x00) +#define AIC32X4_DACMOD2BCLK (0x01) +#define AIC32X4_ADC2BCLK (0x02) +#define AIC32X4_ADCMOD2BCLK (0x03) + +/* AIC32X4_DACSETUP */ +#define AIC32X4_DAC_CHAN_MASK GENMASK(5, 2) +#define AIC32X4_LDAC2RCHN BIT(5) +#define AIC32X4_LDAC2LCHN BIT(4) +#define AIC32X4_RDAC2LCHN BIT(3) +#define AIC32X4_RDAC2RCHN BIT(2) + +/* AIC32X4_DACMUTE */ +#define AIC32X4_MUTEON 0x0C + +/* AIC32X4_ADCSETUP */ +#define AIC32X4_LADC_EN BIT(7) +#define AIC32X4_RADC_EN BIT(6) + +/* AIC32X4_PWRCFG */ +#define AIC32X4_AVDDWEAKDISABLE BIT(3) + +/* AIC32X4_LDOCTL */ +#define AIC32X4_LDOCTLEN BIT(0) + +/* AIC32X4_CMMODE */ +#define AIC32X4_LDOIN_18_36 BIT(0) +#define AIC32X4_LDOIN2HP BIT(1) + +/* AIC32X4_MICBIAS */ +#define AIC32X4_MICBIAS_LDOIN BIT(3) +#define AIC32X4_MICBIAS_2075V 0x60 + +/* AIC32X4_LMICPGANIN */ +#define AIC32X4_LMICPGANIN_IN2R_10K 0x10 +#define AIC32X4_LMICPGANIN_CM1L_10K 0x40 + +/* AIC32X4_RMICPGANIN */ +#define AIC32X4_RMICPGANIN_IN1L_10K 0x10 +#define AIC32X4_RMICPGANIN_CM1R_10K 0x40 + +#endif /* _TLV320AIC32X4_H */ diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c new file mode 100644 index 000000000..6c0a3dad4 --- /dev/null +++ b/sound/soc/codecs/tlv320aic3x.c @@ -0,0 +1,1935 @@ +/* + * ALSA SoC TLV320AIC3X codec driver + * + * Author: Vladimir Barinov, + * Copyright: (C) 2007 MontaVista Software, Inc., + * + * Based on sound/soc/codecs/wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Notes: + * The AIC3X is a driver for a low power stereo audio + * codecs aic31, aic32, aic33, aic3007. + * + * It supports full aic33 codec functionality. + * The compatibility with aic32, aic31 and aic3007 is as follows: + * aic32/aic3007 | aic31 + * --------------------------------------- + * MONO_LOUT -> N/A | MONO_LOUT -> N/A + * | IN1L -> LINE1L + * | IN1R -> LINE1R + * | IN2L -> LINE2L + * | IN2R -> LINE2R + * | MIC3L/R -> N/A + * truncated internal functionality in + * accordance with documentation + * --------------------------------------- + * + * Hence the machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tlv320aic3x.h" + +#define AIC3X_NUM_SUPPLIES 4 +static const char *aic3x_supply_names[AIC3X_NUM_SUPPLIES] = { + "IOVDD", /* I/O Voltage */ + "DVDD", /* Digital Core Voltage */ + "AVDD", /* Analog DAC Voltage */ + "DRVDD", /* ADC Analog and Output Driver Voltage */ +}; + +static LIST_HEAD(reset_list); + +struct aic3x_priv; + +struct aic3x_disable_nb { + struct notifier_block nb; + struct aic3x_priv *aic3x; +}; + +/* codec private data */ +struct aic3x_priv { + struct snd_soc_component *component; + struct regmap *regmap; + struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES]; + struct aic3x_disable_nb disable_nb[AIC3X_NUM_SUPPLIES]; + struct aic3x_setup_data *setup; + unsigned int sysclk; + unsigned int dai_fmt; + unsigned int tdm_delay; + unsigned int slot_width; + struct list_head list; + int master; + int gpio_reset; + int power; +#define AIC3X_MODEL_3X 0 +#define AIC3X_MODEL_33 1 +#define AIC3X_MODEL_3007 2 +#define AIC3X_MODEL_3104 3 + u16 model; + + /* Selects the micbias voltage */ + enum aic3x_micbias_voltage micbias_vg; + /* Output Common-Mode Voltage */ + u8 ocmv; +}; + +static const struct reg_default aic3x_reg[] = { + { 0, 0x00 }, { 1, 0x00 }, { 2, 0x00 }, { 3, 0x10 }, + { 4, 0x04 }, { 5, 0x00 }, { 6, 0x00 }, { 7, 0x00 }, + { 8, 0x00 }, { 9, 0x00 }, { 10, 0x00 }, { 11, 0x01 }, + { 12, 0x00 }, { 13, 0x00 }, { 14, 0x00 }, { 15, 0x80 }, + { 16, 0x80 }, { 17, 0xff }, { 18, 0xff }, { 19, 0x78 }, + { 20, 0x78 }, { 21, 0x78 }, { 22, 0x78 }, { 23, 0x78 }, + { 24, 0x78 }, { 25, 0x00 }, { 26, 0x00 }, { 27, 0xfe }, + { 28, 0x00 }, { 29, 0x00 }, { 30, 0xfe }, { 31, 0x00 }, + { 32, 0x18 }, { 33, 0x18 }, { 34, 0x00 }, { 35, 0x00 }, + { 36, 0x00 }, { 37, 0x00 }, { 38, 0x00 }, { 39, 0x00 }, + { 40, 0x00 }, { 41, 0x00 }, { 42, 0x00 }, { 43, 0x80 }, + { 44, 0x80 }, { 45, 0x00 }, { 46, 0x00 }, { 47, 0x00 }, + { 48, 0x00 }, { 49, 0x00 }, { 50, 0x00 }, { 51, 0x04 }, + { 52, 0x00 }, { 53, 0x00 }, { 54, 0x00 }, { 55, 0x00 }, + { 56, 0x00 }, { 57, 0x00 }, { 58, 0x04 }, { 59, 0x00 }, + { 60, 0x00 }, { 61, 0x00 }, { 62, 0x00 }, { 63, 0x00 }, + { 64, 0x00 }, { 65, 0x04 }, { 66, 0x00 }, { 67, 0x00 }, + { 68, 0x00 }, { 69, 0x00 }, { 70, 0x00 }, { 71, 0x00 }, + { 72, 0x04 }, { 73, 0x00 }, { 74, 0x00 }, { 75, 0x00 }, + { 76, 0x00 }, { 77, 0x00 }, { 78, 0x00 }, { 79, 0x00 }, + { 80, 0x00 }, { 81, 0x00 }, { 82, 0x00 }, { 83, 0x00 }, + { 84, 0x00 }, { 85, 0x00 }, { 86, 0x00 }, { 87, 0x00 }, + { 88, 0x00 }, { 89, 0x00 }, { 90, 0x00 }, { 91, 0x00 }, + { 92, 0x00 }, { 93, 0x00 }, { 94, 0x00 }, { 95, 0x00 }, + { 96, 0x00 }, { 97, 0x00 }, { 98, 0x00 }, { 99, 0x00 }, + { 100, 0x00 }, { 101, 0x00 }, { 102, 0x02 }, { 103, 0x00 }, + { 104, 0x00 }, { 105, 0x00 }, { 106, 0x00 }, { 107, 0x00 }, + { 108, 0x00 }, { 109, 0x00 }, +}; + +static bool aic3x_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AIC3X_RESET: + return true; + default: + return false; + } +} + +static const struct regmap_config aic3x_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = DAC_ICC_ADJ, + .reg_defaults = aic3x_reg, + .num_reg_defaults = ARRAY_SIZE(aic3x_reg), + + .volatile_reg = aic3x_volatile_reg, + + .cache_type = REGCACHE_RBTREE, +}; + +#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \ + SOC_SINGLE_EXT(xname, reg, shift, mask, invert, \ + snd_soc_dapm_get_volsw, snd_soc_dapm_put_volsw_aic3x) + +/* + * All input lines are connected when !0xf and disconnected with 0xf bit field, + * so we have to use specific dapm_put call for input mixer + */ +static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned short val; + struct snd_soc_dapm_update update = {}; + int connect, change; + + val = (ucontrol->value.integer.value[0] & mask); + + mask = 0xf; + if (val) + val = mask; + + connect = !!val; + + if (invert) + val = mask - val; + + mask <<= shift; + val <<= shift; + + change = snd_soc_component_test_bits(component, reg, mask, val); + if (change) { + update.kcontrol = kcontrol; + update.reg = reg; + update.mask = mask; + update.val = val; + + snd_soc_dapm_mixer_update_power(dapm, kcontrol, connect, + &update); + } + + return change; +} + +/* + * mic bias power on/off share the same register bits with + * output voltage of mic bias. when power on mic bias, we + * need reclaim it to voltage value. + * 0x0 = Powered off + * 0x1 = MICBIAS output is powered to 2.0V, + * 0x2 = MICBIAS output is powered to 2.5V + * 0x3 = MICBIAS output is connected to AVDD + */ +static int mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* change mic bias voltage to user defined */ + snd_soc_component_update_bits(component, MICBIAS_CTRL, + MICBIAS_LEVEL_MASK, + aic3x->micbias_vg << MICBIAS_LEVEL_SHIFT); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, MICBIAS_CTRL, + MICBIAS_LEVEL_MASK, 0); + break; + } + return 0; +} + +static const char * const aic3x_left_dac_mux[] = { + "DAC_L1", "DAC_L3", "DAC_L2" }; +static SOC_ENUM_SINGLE_DECL(aic3x_left_dac_enum, DAC_LINE_MUX, 6, + aic3x_left_dac_mux); + +static const char * const aic3x_right_dac_mux[] = { + "DAC_R1", "DAC_R3", "DAC_R2" }; +static SOC_ENUM_SINGLE_DECL(aic3x_right_dac_enum, DAC_LINE_MUX, 4, + aic3x_right_dac_mux); + +static const char * const aic3x_left_hpcom_mux[] = { + "differential of HPLOUT", "constant VCM", "single-ended" }; +static SOC_ENUM_SINGLE_DECL(aic3x_left_hpcom_enum, HPLCOM_CFG, 4, + aic3x_left_hpcom_mux); + +static const char * const aic3x_right_hpcom_mux[] = { + "differential of HPROUT", "constant VCM", "single-ended", + "differential of HPLCOM", "external feedback" }; +static SOC_ENUM_SINGLE_DECL(aic3x_right_hpcom_enum, HPRCOM_CFG, 3, + aic3x_right_hpcom_mux); + +static const char * const aic3x_linein_mode_mux[] = { + "single-ended", "differential" }; +static SOC_ENUM_SINGLE_DECL(aic3x_line1l_2_l_enum, LINE1L_2_LADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line1l_2_r_enum, LINE1L_2_RADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line1r_2_l_enum, LINE1R_2_LADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line1r_2_r_enum, LINE1R_2_RADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line2l_2_ldac_enum, LINE2L_2_LADC_CTRL, 7, + aic3x_linein_mode_mux); +static SOC_ENUM_SINGLE_DECL(aic3x_line2r_2_rdac_enum, LINE2R_2_RADC_CTRL, 7, + aic3x_linein_mode_mux); + +static const char * const aic3x_adc_hpf[] = { + "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" }; +static SOC_ENUM_DOUBLE_DECL(aic3x_adc_hpf_enum, AIC3X_CODEC_DFILT_CTRL, 6, 4, + aic3x_adc_hpf); + +static const char * const aic3x_agc_level[] = { + "-5.5dB", "-8dB", "-10dB", "-12dB", + "-14dB", "-17dB", "-20dB", "-24dB" }; +static SOC_ENUM_SINGLE_DECL(aic3x_lagc_level_enum, LAGC_CTRL_A, 4, + aic3x_agc_level); +static SOC_ENUM_SINGLE_DECL(aic3x_ragc_level_enum, RAGC_CTRL_A, 4, + aic3x_agc_level); + +static const char * const aic3x_agc_attack[] = { + "8ms", "11ms", "16ms", "20ms" }; +static SOC_ENUM_SINGLE_DECL(aic3x_lagc_attack_enum, LAGC_CTRL_A, 2, + aic3x_agc_attack); +static SOC_ENUM_SINGLE_DECL(aic3x_ragc_attack_enum, RAGC_CTRL_A, 2, + aic3x_agc_attack); + +static const char * const aic3x_agc_decay[] = { + "100ms", "200ms", "400ms", "500ms" }; +static SOC_ENUM_SINGLE_DECL(aic3x_lagc_decay_enum, LAGC_CTRL_A, 0, + aic3x_agc_decay); +static SOC_ENUM_SINGLE_DECL(aic3x_ragc_decay_enum, RAGC_CTRL_A, 0, + aic3x_agc_decay); + +static const char * const aic3x_poweron_time[] = { + "0us", "10us", "100us", "1ms", "10ms", "50ms", + "100ms", "200ms", "400ms", "800ms", "2s", "4s" }; +static SOC_ENUM_SINGLE_DECL(aic3x_poweron_time_enum, HPOUT_POP_REDUCTION, 4, + aic3x_poweron_time); + +static const char * const aic3x_rampup_step[] = { "0ms", "1ms", "2ms", "4ms" }; +static SOC_ENUM_SINGLE_DECL(aic3x_rampup_step_enum, HPOUT_POP_REDUCTION, 2, + aic3x_rampup_step); + +/* + * DAC digital volumes. From -63.5 to 0 dB in 0.5 dB steps + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, -6350, 50, 0); +/* ADC PGA gain volumes. From 0 to 59.5 dB in 0.5 dB steps */ +static DECLARE_TLV_DB_SCALE(adc_tlv, 0, 50, 0); +/* + * Output stage volumes. From -78.3 to 0 dB. Muted below -78.3 dB. + * Step size is approximately 0.5 dB over most of the scale but increasing + * near the very low levels. + * Define dB scale so that it is mostly correct for range about -55 to 0 dB + * but having increasing dB difference below that (and where it doesn't count + * so much). This setting shows -50 dB (actual is -50.3 dB) for register + * value 100 and -58.5 dB (actual is -78.3 dB) for register value 117. + */ +static DECLARE_TLV_DB_SCALE(output_stage_tlv, -5900, 50, 1); + +static const struct snd_kcontrol_new aic3x_snd_controls[] = { + /* Output */ + SOC_DOUBLE_R_TLV("PCM Playback Volume", + LDAC_VOL, RDAC_VOL, 0, 0x7f, 1, dac_tlv), + + /* + * Output controls that map to output mixer switches. Note these are + * only for swapped L-to-R and R-to-L routes. See below stereo controls + * for direct L-to-L and R-to-R routes. + */ + SOC_SINGLE_TLV("Left Line Mixer PGAR Bypass Volume", + PGAR_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("Left Line Mixer DACR1 Playback Volume", + DACR1_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Right Line Mixer PGAL Bypass Volume", + PGAL_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("Right Line Mixer DACL1 Playback Volume", + DACL1_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Left HP Mixer PGAR Bypass Volume", + PGAR_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("Left HP Mixer DACR1 Playback Volume", + DACR1_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Right HP Mixer PGAL Bypass Volume", + PGAL_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("Right HP Mixer DACL1 Playback Volume", + DACL1_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Left HPCOM Mixer PGAR Bypass Volume", + PGAR_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("Left HPCOM Mixer DACR1 Playback Volume", + DACR1_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Right HPCOM Mixer PGAL Bypass Volume", + PGAL_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv), + SOC_SINGLE_TLV("Right HPCOM Mixer DACL1 Playback Volume", + DACL1_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv), + + /* Stereo output controls for direct L-to-L and R-to-R routes */ + SOC_DOUBLE_R_TLV("Line PGA Bypass Volume", + PGAL_2_LLOPM_VOL, PGAR_2_RLOPM_VOL, + 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("Line DAC Playback Volume", + DACL1_2_LLOPM_VOL, DACR1_2_RLOPM_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_DOUBLE_R_TLV("HP PGA Bypass Volume", + PGAL_2_HPLOUT_VOL, PGAR_2_HPROUT_VOL, + 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("HP DAC Playback Volume", + DACL1_2_HPLOUT_VOL, DACR1_2_HPROUT_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_DOUBLE_R_TLV("HPCOM PGA Bypass Volume", + PGAL_2_HPLCOM_VOL, PGAR_2_HPRCOM_VOL, + 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("HPCOM DAC Playback Volume", + DACL1_2_HPLCOM_VOL, DACR1_2_HPRCOM_VOL, + 0, 118, 1, output_stage_tlv), + + /* Output pin mute controls */ + SOC_DOUBLE_R("Line Playback Switch", LLOPM_CTRL, RLOPM_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("HP Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("HPCOM Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3, + 0x01, 0), + + /* + * Note: enable Automatic input Gain Controller with care. It can + * adjust PGA to max value when ADC is on and will never go back. + */ + SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0), + SOC_ENUM("Left AGC Target level", aic3x_lagc_level_enum), + SOC_ENUM("Right AGC Target level", aic3x_ragc_level_enum), + SOC_ENUM("Left AGC Attack time", aic3x_lagc_attack_enum), + SOC_ENUM("Right AGC Attack time", aic3x_ragc_attack_enum), + SOC_ENUM("Left AGC Decay time", aic3x_lagc_decay_enum), + SOC_ENUM("Right AGC Decay time", aic3x_ragc_decay_enum), + + /* De-emphasis */ + SOC_DOUBLE("De-emphasis Switch", AIC3X_CODEC_DFILT_CTRL, 2, 0, 0x01, 0), + + /* Input */ + SOC_DOUBLE_R_TLV("PGA Capture Volume", LADC_VOL, RADC_VOL, + 0, 119, 0, adc_tlv), + SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1), + + SOC_ENUM("ADC HPF Cut-off", aic3x_adc_hpf_enum), + + /* Pop reduction */ + SOC_ENUM("Output Driver Power-On time", aic3x_poweron_time_enum), + SOC_ENUM("Output Driver Ramp-up step", aic3x_rampup_step_enum), +}; + +/* For other than tlv320aic3104 */ +static const struct snd_kcontrol_new aic3x_extra_snd_controls[] = { + /* + * Output controls that map to output mixer switches. Note these are + * only for swapped L-to-R and R-to-L routes. See below stereo controls + * for direct L-to-L and R-to-R routes. + */ + SOC_SINGLE_TLV("Left Line Mixer Line2R Bypass Volume", + LINE2R_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Right Line Mixer Line2L Bypass Volume", + LINE2L_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Left HP Mixer Line2R Bypass Volume", + LINE2R_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Right HP Mixer Line2L Bypass Volume", + LINE2L_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Left HPCOM Mixer Line2R Bypass Volume", + LINE2R_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv), + + SOC_SINGLE_TLV("Right HPCOM Mixer Line2L Bypass Volume", + LINE2L_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv), + + /* Stereo output controls for direct L-to-L and R-to-R routes */ + SOC_DOUBLE_R_TLV("Line Line2 Bypass Volume", + LINE2L_2_LLOPM_VOL, LINE2R_2_RLOPM_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_DOUBLE_R_TLV("HP Line2 Bypass Volume", + LINE2L_2_HPLOUT_VOL, LINE2R_2_HPROUT_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_DOUBLE_R_TLV("HPCOM Line2 Bypass Volume", + LINE2L_2_HPLCOM_VOL, LINE2R_2_HPRCOM_VOL, + 0, 118, 1, output_stage_tlv), +}; + +static const struct snd_kcontrol_new aic3x_mono_controls[] = { + SOC_DOUBLE_R_TLV("Mono Line2 Bypass Volume", + LINE2L_2_MONOLOPM_VOL, LINE2R_2_MONOLOPM_VOL, + 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("Mono PGA Bypass Volume", + PGAL_2_MONOLOPM_VOL, PGAR_2_MONOLOPM_VOL, + 0, 118, 1, output_stage_tlv), + SOC_DOUBLE_R_TLV("Mono DAC Playback Volume", + DACL1_2_MONOLOPM_VOL, DACR1_2_MONOLOPM_VOL, + 0, 118, 1, output_stage_tlv), + + SOC_SINGLE("Mono Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0), +}; + +/* + * Class-D amplifier gain. From 0 to 18 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(classd_amp_tlv, 0, 600, 0); + +static const struct snd_kcontrol_new aic3x_classd_amp_gain_ctrl = + SOC_DOUBLE_TLV("Class-D Playback Volume", CLASSD_CTRL, 6, 4, 3, 0, classd_amp_tlv); + +/* Left DAC Mux */ +static const struct snd_kcontrol_new aic3x_left_dac_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_left_dac_enum); + +/* Right DAC Mux */ +static const struct snd_kcontrol_new aic3x_right_dac_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_right_dac_enum); + +/* Left HPCOM Mux */ +static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_left_hpcom_enum); + +/* Right HPCOM Mux */ +static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_right_hpcom_enum); + +/* Left Line Mixer */ +static const struct snd_kcontrol_new aic3x_left_line_mixer_controls[] = { + SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_LLOPM_VOL, 7, 1, 0), + /* Not on tlv320aic3104 */ + SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_LLOPM_VOL, 7, 1, 0), +}; + +/* Right Line Mixer */ +static const struct snd_kcontrol_new aic3x_right_line_mixer_controls[] = { + SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_RLOPM_VOL, 7, 1, 0), + /* Not on tlv320aic3104 */ + SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_RLOPM_VOL, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new aic3x_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_MONOLOPM_VOL, 7, 1, 0), +}; + +/* Left HP Mixer */ +static const struct snd_kcontrol_new aic3x_left_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPLOUT_VOL, 7, 1, 0), + /* Not on tlv320aic3104 */ + SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPLOUT_VOL, 7, 1, 0), +}; + +/* Right HP Mixer */ +static const struct snd_kcontrol_new aic3x_right_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPROUT_VOL, 7, 1, 0), + /* Not on tlv320aic3104 */ + SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPROUT_VOL, 7, 1, 0), +}; + +/* Left HPCOM Mixer */ +static const struct snd_kcontrol_new aic3x_left_hpcom_mixer_controls[] = { + SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPLCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPLCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPLCOM_VOL, 7, 1, 0), + /* Not on tlv320aic3104 */ + SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPLCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPLCOM_VOL, 7, 1, 0), +}; + +/* Right HPCOM Mixer */ +static const struct snd_kcontrol_new aic3x_right_hpcom_mixer_controls[] = { + SOC_DAPM_SINGLE("PGAL Bypass Switch", PGAL_2_HPRCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACL1 Switch", DACL1_2_HPRCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("PGAR Bypass Switch", PGAR_2_HPRCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("DACR1 Switch", DACR1_2_HPRCOM_VOL, 7, 1, 0), + /* Not on tlv320aic3104 */ + SOC_DAPM_SINGLE("Line2L Bypass Switch", LINE2L_2_HPRCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Line2R Bypass Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0), +}; + +/* Left PGA Mixer */ +static const struct snd_kcontrol_new aic3x_left_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line2L Switch", LINE2L_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_LADC_CTRL, 0, 1, 1), +}; + +/* Right PGA Mixer */ +static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_RADC_CTRL, 4, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1), +}; + +/* Left PGA Mixer for tlv320aic3104 */ +static const struct snd_kcontrol_new aic3104_left_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic2L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic2R Switch", MIC3LR_2_LADC_CTRL, 0, 1, 1), +}; + +/* Right PGA Mixer for tlv320aic3104 */ +static const struct snd_kcontrol_new aic3104_right_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic2L Switch", MIC3LR_2_RADC_CTRL, 4, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic2R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1), +}; + +/* Left Line1 Mux */ +static const struct snd_kcontrol_new aic3x_left_line1l_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_line1l_2_l_enum); +static const struct snd_kcontrol_new aic3x_right_line1l_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_line1l_2_r_enum); + +/* Right Line1 Mux */ +static const struct snd_kcontrol_new aic3x_right_line1r_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_line1r_2_r_enum); +static const struct snd_kcontrol_new aic3x_left_line1r_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_line1r_2_l_enum); + +/* Left Line2 Mux */ +static const struct snd_kcontrol_new aic3x_left_line2_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_line2l_2_ldac_enum); + +/* Right Line2 Mux */ +static const struct snd_kcontrol_new aic3x_right_line2_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_line2r_2_rdac_enum); + +static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { + /* Left DAC to Left Outputs */ + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_PWR, 7, 0), + SND_SOC_DAPM_MUX("Left DAC Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_dac_mux_controls), + SND_SOC_DAPM_MUX("Left HPCOM Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_hpcom_mux_controls), + SND_SOC_DAPM_PGA("Left Line Out", LLOPM_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left HP Out", HPLOUT_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left HP Com", HPLCOM_CTRL, 0, 0, NULL, 0), + + /* Right DAC to Right Outputs */ + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_PWR, 6, 0), + SND_SOC_DAPM_MUX("Right DAC Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_dac_mux_controls), + SND_SOC_DAPM_MUX("Right HPCOM Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_hpcom_mux_controls), + SND_SOC_DAPM_PGA("Right Line Out", RLOPM_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right HP Out", HPROUT_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right HP Com", HPRCOM_CTRL, 0, 0, NULL, 0), + + /* Inputs to Left ADC */ + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", LINE1L_2_LADC_CTRL, 2, 0), + SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_line1l_mux_controls), + SND_SOC_DAPM_MUX("Left Line1R Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_line1r_mux_controls), + + /* Inputs to Right ADC */ + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", + LINE1R_2_RADC_CTRL, 2, 0), + SND_SOC_DAPM_MUX("Right Line1L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line1l_mux_controls), + SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line1r_mux_controls), + + /* Mic Bias */ + SND_SOC_DAPM_SUPPLY("Mic Bias", MICBIAS_CTRL, 6, 0, + mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_OUTPUT("LLOUT"), + SND_SOC_DAPM_OUTPUT("RLOUT"), + SND_SOC_DAPM_OUTPUT("HPLOUT"), + SND_SOC_DAPM_OUTPUT("HPROUT"), + SND_SOC_DAPM_OUTPUT("HPLCOM"), + SND_SOC_DAPM_OUTPUT("HPRCOM"), + + SND_SOC_DAPM_INPUT("LINE1L"), + SND_SOC_DAPM_INPUT("LINE1R"), + + /* + * Virtual output pin to detection block inside codec. This can be + * used to keep codec bias on if gpio or detection features are needed. + * Force pin on or construct a path with an input jack and mic bias + * widgets. + */ + SND_SOC_DAPM_OUTPUT("Detection"), +}; + +/* For other than tlv320aic3104 */ +static const struct snd_soc_dapm_widget aic3x_extra_dapm_widgets[] = { + /* Inputs to Left ADC */ + SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_pga_mixer_controls[0], + ARRAY_SIZE(aic3x_left_pga_mixer_controls)), + SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_line2_mux_controls), + + /* Inputs to Right ADC */ + SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_pga_mixer_controls[0], + ARRAY_SIZE(aic3x_right_pga_mixer_controls)), + SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line2_mux_controls), + + /* + * Not a real mic bias widget but similar function. This is for dynamic + * control of GPIO1 digital mic modulator clock output function when + * using digital mic. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk", + AIC3X_GPIO1_REG, 4, 0xf, + AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK, + AIC3X_GPIO1_FUNC_DISABLED), + + /* + * Also similar function like mic bias. Selects digital mic with + * configurable oversampling rate instead of ADC converter. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128", + AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64", + AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32", + AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0), + + /* Output mixers */ + SND_SOC_DAPM_MIXER("Left Line Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_line_mixer_controls[0], + ARRAY_SIZE(aic3x_left_line_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Line Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_line_mixer_controls[0], + ARRAY_SIZE(aic3x_right_line_mixer_controls)), + SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_hp_mixer_controls[0], + ARRAY_SIZE(aic3x_left_hp_mixer_controls)), + SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_hp_mixer_controls[0], + ARRAY_SIZE(aic3x_right_hp_mixer_controls)), + SND_SOC_DAPM_MIXER("Left HPCOM Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_hpcom_mixer_controls[0], + ARRAY_SIZE(aic3x_left_hpcom_mixer_controls)), + SND_SOC_DAPM_MIXER("Right HPCOM Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_hpcom_mixer_controls[0], + ARRAY_SIZE(aic3x_right_hpcom_mixer_controls)), + + SND_SOC_DAPM_INPUT("MIC3L"), + SND_SOC_DAPM_INPUT("MIC3R"), + SND_SOC_DAPM_INPUT("LINE2L"), + SND_SOC_DAPM_INPUT("LINE2R"), +}; + +/* For tlv320aic3104 */ +static const struct snd_soc_dapm_widget aic3104_extra_dapm_widgets[] = { + /* Inputs to Left ADC */ + SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3104_left_pga_mixer_controls[0], + ARRAY_SIZE(aic3104_left_pga_mixer_controls)), + + /* Inputs to Right ADC */ + SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3104_right_pga_mixer_controls[0], + ARRAY_SIZE(aic3104_right_pga_mixer_controls)), + + /* Output mixers */ + SND_SOC_DAPM_MIXER("Left Line Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_line_mixer_controls[0], + ARRAY_SIZE(aic3x_left_line_mixer_controls) - 2), + SND_SOC_DAPM_MIXER("Right Line Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_line_mixer_controls[0], + ARRAY_SIZE(aic3x_right_line_mixer_controls) - 2), + SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_hp_mixer_controls[0], + ARRAY_SIZE(aic3x_left_hp_mixer_controls) - 2), + SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_hp_mixer_controls[0], + ARRAY_SIZE(aic3x_right_hp_mixer_controls) - 2), + SND_SOC_DAPM_MIXER("Left HPCOM Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_hpcom_mixer_controls[0], + ARRAY_SIZE(aic3x_left_hpcom_mixer_controls) - 2), + SND_SOC_DAPM_MIXER("Right HPCOM Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_hpcom_mixer_controls[0], + ARRAY_SIZE(aic3x_right_hpcom_mixer_controls) - 2), + + SND_SOC_DAPM_INPUT("MIC2L"), + SND_SOC_DAPM_INPUT("MIC2R"), +}; + +static const struct snd_soc_dapm_widget aic3x_dapm_mono_widgets[] = { + /* Mono Output */ + SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_mono_mixer_controls[0], + ARRAY_SIZE(aic3x_mono_mixer_controls)), + + SND_SOC_DAPM_OUTPUT("MONO_LOUT"), +}; + +static const struct snd_soc_dapm_widget aic3007_dapm_widgets[] = { + /* Class-D outputs */ + SND_SOC_DAPM_PGA("Left Class-D Out", CLASSD_CTRL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Class-D Out", CLASSD_CTRL, 2, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("SPOP"), + SND_SOC_DAPM_OUTPUT("SPOM"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Left Input */ + {"Left Line1L Mux", "single-ended", "LINE1L"}, + {"Left Line1L Mux", "differential", "LINE1L"}, + {"Left Line1R Mux", "single-ended", "LINE1R"}, + {"Left Line1R Mux", "differential", "LINE1R"}, + + {"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"}, + {"Left PGA Mixer", "Line1R Switch", "Left Line1R Mux"}, + + {"Left ADC", NULL, "Left PGA Mixer"}, + + /* Right Input */ + {"Right Line1R Mux", "single-ended", "LINE1R"}, + {"Right Line1R Mux", "differential", "LINE1R"}, + {"Right Line1L Mux", "single-ended", "LINE1L"}, + {"Right Line1L Mux", "differential", "LINE1L"}, + + {"Right PGA Mixer", "Line1L Switch", "Right Line1L Mux"}, + {"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"}, + + {"Right ADC", NULL, "Right PGA Mixer"}, + + /* Left DAC Output */ + {"Left DAC Mux", "DAC_L1", "Left DAC"}, + {"Left DAC Mux", "DAC_L2", "Left DAC"}, + {"Left DAC Mux", "DAC_L3", "Left DAC"}, + + /* Right DAC Output */ + {"Right DAC Mux", "DAC_R1", "Right DAC"}, + {"Right DAC Mux", "DAC_R2", "Right DAC"}, + {"Right DAC Mux", "DAC_R3", "Right DAC"}, + + /* Left Line Output */ + {"Left Line Mixer", "PGAL Bypass Switch", "Left PGA Mixer"}, + {"Left Line Mixer", "DACL1 Switch", "Left DAC Mux"}, + {"Left Line Mixer", "PGAR Bypass Switch", "Right PGA Mixer"}, + {"Left Line Mixer", "DACR1 Switch", "Right DAC Mux"}, + + {"Left Line Out", NULL, "Left Line Mixer"}, + {"Left Line Out", NULL, "Left DAC Mux"}, + {"LLOUT", NULL, "Left Line Out"}, + + /* Right Line Output */ + {"Right Line Mixer", "PGAL Bypass Switch", "Left PGA Mixer"}, + {"Right Line Mixer", "DACL1 Switch", "Left DAC Mux"}, + {"Right Line Mixer", "PGAR Bypass Switch", "Right PGA Mixer"}, + {"Right Line Mixer", "DACR1 Switch", "Right DAC Mux"}, + + {"Right Line Out", NULL, "Right Line Mixer"}, + {"Right Line Out", NULL, "Right DAC Mux"}, + {"RLOUT", NULL, "Right Line Out"}, + + /* Left HP Output */ + {"Left HP Mixer", "PGAL Bypass Switch", "Left PGA Mixer"}, + {"Left HP Mixer", "DACL1 Switch", "Left DAC Mux"}, + {"Left HP Mixer", "PGAR Bypass Switch", "Right PGA Mixer"}, + {"Left HP Mixer", "DACR1 Switch", "Right DAC Mux"}, + + {"Left HP Out", NULL, "Left HP Mixer"}, + {"Left HP Out", NULL, "Left DAC Mux"}, + {"HPLOUT", NULL, "Left HP Out"}, + + /* Right HP Output */ + {"Right HP Mixer", "PGAL Bypass Switch", "Left PGA Mixer"}, + {"Right HP Mixer", "DACL1 Switch", "Left DAC Mux"}, + {"Right HP Mixer", "PGAR Bypass Switch", "Right PGA Mixer"}, + {"Right HP Mixer", "DACR1 Switch", "Right DAC Mux"}, + + {"Right HP Out", NULL, "Right HP Mixer"}, + {"Right HP Out", NULL, "Right DAC Mux"}, + {"HPROUT", NULL, "Right HP Out"}, + + /* Left HPCOM Output */ + {"Left HPCOM Mixer", "PGAL Bypass Switch", "Left PGA Mixer"}, + {"Left HPCOM Mixer", "DACL1 Switch", "Left DAC Mux"}, + {"Left HPCOM Mixer", "PGAR Bypass Switch", "Right PGA Mixer"}, + {"Left HPCOM Mixer", "DACR1 Switch", "Right DAC Mux"}, + + {"Left HPCOM Mux", "differential of HPLOUT", "Left HP Mixer"}, + {"Left HPCOM Mux", "constant VCM", "Left HPCOM Mixer"}, + {"Left HPCOM Mux", "single-ended", "Left HPCOM Mixer"}, + {"Left HP Com", NULL, "Left HPCOM Mux"}, + {"HPLCOM", NULL, "Left HP Com"}, + + /* Right HPCOM Output */ + {"Right HPCOM Mixer", "PGAL Bypass Switch", "Left PGA Mixer"}, + {"Right HPCOM Mixer", "DACL1 Switch", "Left DAC Mux"}, + {"Right HPCOM Mixer", "PGAR Bypass Switch", "Right PGA Mixer"}, + {"Right HPCOM Mixer", "DACR1 Switch", "Right DAC Mux"}, + + {"Right HPCOM Mux", "differential of HPROUT", "Right HP Mixer"}, + {"Right HPCOM Mux", "constant VCM", "Right HPCOM Mixer"}, + {"Right HPCOM Mux", "single-ended", "Right HPCOM Mixer"}, + {"Right HPCOM Mux", "differential of HPLCOM", "Left HPCOM Mixer"}, + {"Right HPCOM Mux", "external feedback", "Right HPCOM Mixer"}, + {"Right HP Com", NULL, "Right HPCOM Mux"}, + {"HPRCOM", NULL, "Right HP Com"}, +}; + +/* For other than tlv320aic3104 */ +static const struct snd_soc_dapm_route intercon_extra[] = { + /* Left Input */ + {"Left Line2L Mux", "single-ended", "LINE2L"}, + {"Left Line2L Mux", "differential", "LINE2L"}, + + {"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"}, + {"Left PGA Mixer", "Mic3L Switch", "MIC3L"}, + {"Left PGA Mixer", "Mic3R Switch", "MIC3R"}, + + {"Left ADC", NULL, "GPIO1 dmic modclk"}, + + /* Right Input */ + {"Right Line2R Mux", "single-ended", "LINE2R"}, + {"Right Line2R Mux", "differential", "LINE2R"}, + + {"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"}, + {"Right PGA Mixer", "Mic3L Switch", "MIC3L"}, + {"Right PGA Mixer", "Mic3R Switch", "MIC3R"}, + + {"Right ADC", NULL, "GPIO1 dmic modclk"}, + + /* + * Logical path between digital mic enable and GPIO1 modulator clock + * output function + */ + {"GPIO1 dmic modclk", NULL, "DMic Rate 128"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 64"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 32"}, + + /* Left Line Output */ + {"Left Line Mixer", "Line2L Bypass Switch", "Left Line2L Mux"}, + {"Left Line Mixer", "Line2R Bypass Switch", "Right Line2R Mux"}, + + /* Right Line Output */ + {"Right Line Mixer", "Line2L Bypass Switch", "Left Line2L Mux"}, + {"Right Line Mixer", "Line2R Bypass Switch", "Right Line2R Mux"}, + + /* Left HP Output */ + {"Left HP Mixer", "Line2L Bypass Switch", "Left Line2L Mux"}, + {"Left HP Mixer", "Line2R Bypass Switch", "Right Line2R Mux"}, + + /* Right HP Output */ + {"Right HP Mixer", "Line2L Bypass Switch", "Left Line2L Mux"}, + {"Right HP Mixer", "Line2R Bypass Switch", "Right Line2R Mux"}, + + /* Left HPCOM Output */ + {"Left HPCOM Mixer", "Line2L Bypass Switch", "Left Line2L Mux"}, + {"Left HPCOM Mixer", "Line2R Bypass Switch", "Right Line2R Mux"}, + + /* Right HPCOM Output */ + {"Right HPCOM Mixer", "Line2L Bypass Switch", "Left Line2L Mux"}, + {"Right HPCOM Mixer", "Line2R Bypass Switch", "Right Line2R Mux"}, +}; + +/* For tlv320aic3104 */ +static const struct snd_soc_dapm_route intercon_extra_3104[] = { + /* Left Input */ + {"Left PGA Mixer", "Mic2L Switch", "MIC2L"}, + {"Left PGA Mixer", "Mic2R Switch", "MIC2R"}, + + /* Right Input */ + {"Right PGA Mixer", "Mic2L Switch", "MIC2L"}, + {"Right PGA Mixer", "Mic2R Switch", "MIC2R"}, +}; + +static const struct snd_soc_dapm_route intercon_mono[] = { + /* Mono Output */ + {"Mono Mixer", "Line2L Bypass Switch", "Left Line2L Mux"}, + {"Mono Mixer", "PGAL Bypass Switch", "Left PGA Mixer"}, + {"Mono Mixer", "DACL1 Switch", "Left DAC Mux"}, + {"Mono Mixer", "Line2R Bypass Switch", "Right Line2R Mux"}, + {"Mono Mixer", "PGAR Bypass Switch", "Right PGA Mixer"}, + {"Mono Mixer", "DACR1 Switch", "Right DAC Mux"}, + {"Mono Out", NULL, "Mono Mixer"}, + {"MONO_LOUT", NULL, "Mono Out"}, +}; + +static const struct snd_soc_dapm_route intercon_3007[] = { + /* Class-D outputs */ + {"Left Class-D Out", NULL, "Left Line Out"}, + {"Right Class-D Out", NULL, "Left Line Out"}, + {"SPOP", NULL, "Left Class-D Out"}, + {"SPOM", NULL, "Right Class-D Out"}, +}; + +static int aic3x_add_widgets(struct snd_soc_component *component) +{ + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + switch (aic3x->model) { + case AIC3X_MODEL_3X: + case AIC3X_MODEL_33: + snd_soc_dapm_new_controls(dapm, aic3x_extra_dapm_widgets, + ARRAY_SIZE(aic3x_extra_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon_extra, + ARRAY_SIZE(intercon_extra)); + snd_soc_dapm_new_controls(dapm, aic3x_dapm_mono_widgets, + ARRAY_SIZE(aic3x_dapm_mono_widgets)); + snd_soc_dapm_add_routes(dapm, intercon_mono, + ARRAY_SIZE(intercon_mono)); + break; + case AIC3X_MODEL_3007: + snd_soc_dapm_new_controls(dapm, aic3x_extra_dapm_widgets, + ARRAY_SIZE(aic3x_extra_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon_extra, + ARRAY_SIZE(intercon_extra)); + snd_soc_dapm_new_controls(dapm, aic3007_dapm_widgets, + ARRAY_SIZE(aic3007_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon_3007, + ARRAY_SIZE(intercon_3007)); + break; + case AIC3X_MODEL_3104: + snd_soc_dapm_new_controls(dapm, aic3104_extra_dapm_widgets, + ARRAY_SIZE(aic3104_extra_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon_extra_3104, + ARRAY_SIZE(intercon_extra_3104)); + break; + } + + return 0; +} + +static int aic3x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; + u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; + u16 d, pll_d = 1; + int clk; + int width = aic3x->slot_width; + + if (!width) + width = params_width(params); + + /* select data word length */ + data = snd_soc_component_read32(component, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); + switch (width) { + case 16: + break; + case 20: + data |= (0x01 << 4); + break; + case 24: + data |= (0x02 << 4); + break; + case 32: + data |= (0x03 << 4); + break; + } + snd_soc_component_write(component, AIC3X_ASD_INTF_CTRLB, data); + + /* Fsref can be 44100 or 48000 */ + fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000; + + /* Try to find a value for Q which allows us to bypass the PLL and + * generate CODEC_CLK directly. */ + for (pll_q = 2; pll_q < 18; pll_q++) + if (aic3x->sysclk / (128 * pll_q) == fsref) { + bypass_pll = 1; + break; + } + + if (bypass_pll) { + pll_q &= 0xf; + snd_soc_component_write(component, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT); + snd_soc_component_write(component, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV); + /* disable PLL if it is bypassed */ + snd_soc_component_update_bits(component, AIC3X_PLL_PROGA_REG, PLL_ENABLE, 0); + + } else { + snd_soc_component_write(component, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV); + /* enable PLL when it is used */ + snd_soc_component_update_bits(component, AIC3X_PLL_PROGA_REG, + PLL_ENABLE, PLL_ENABLE); + } + + /* Route Left DAC to left channel input and + * right DAC to right channel input */ + data = (LDAC2LCH | RDAC2RCH); + data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; + if (params_rate(params) >= 64000) + data |= DUAL_RATE_MODE; + snd_soc_component_write(component, AIC3X_CODEC_DATAPATH_REG, data); + + /* codec sample rate select */ + data = (fsref * 20) / params_rate(params); + if (params_rate(params) < 64000) + data /= 2; + data /= 5; + data -= 2; + data |= (data << 4); + snd_soc_component_write(component, AIC3X_SAMPLE_RATE_SEL_REG, data); + + if (bypass_pll) + return 0; + + /* Use PLL, compute appropriate setup for j, d, r and p, the closest + * one wins the game. Try with d==0 first, next with d!=0. + * Constraints for j are according to the datasheet. + * The sysclk is divided by 1000 to prevent integer overflows. + */ + + codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000); + + for (r = 1; r <= 16; r++) + for (p = 1; p <= 8; p++) { + for (j = 4; j <= 55; j++) { + /* This is actually 1000*((j+(d/10000))*r)/p + * The term had to be converted to get + * rid of the division by 10000; d = 0 here + */ + int tmp_clk = (1000 * j * r) / p; + + /* Check whether this values get closer than + * the best ones we had before + */ + if (abs(codec_clk - tmp_clk) < + abs(codec_clk - last_clk)) { + pll_j = j; pll_d = 0; + pll_r = r; pll_p = p; + last_clk = tmp_clk; + } + + /* Early exit for exact matches */ + if (tmp_clk == codec_clk) + goto found; + } + } + + /* try with d != 0 */ + for (p = 1; p <= 8; p++) { + j = codec_clk * p / 1000; + + if (j < 4 || j > 11) + continue; + + /* do not use codec_clk here since we'd loose precision */ + d = ((2048 * p * fsref) - j * aic3x->sysclk) + * 100 / (aic3x->sysclk/100); + + clk = (10000 * j + d) / (10 * p); + + /* check whether this values get closer than the best + * ones we had before */ + if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) { + pll_j = j; pll_d = d; pll_r = 1; pll_p = p; + last_clk = clk; + } + + /* Early exit for exact matches */ + if (clk == codec_clk) + goto found; + } + + if (last_clk == 0) { + printk(KERN_ERR "%s(): unable to setup PLL\n", __func__); + return -EINVAL; + } + +found: + snd_soc_component_update_bits(component, AIC3X_PLL_PROGA_REG, PLLP_MASK, pll_p); + snd_soc_component_write(component, AIC3X_OVRF_STATUS_AND_PLLR_REG, + pll_r << PLLR_SHIFT); + snd_soc_component_write(component, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT); + snd_soc_component_write(component, AIC3X_PLL_PROGC_REG, + (pll_d >> 6) << PLLD_MSB_SHIFT); + snd_soc_component_write(component, AIC3X_PLL_PROGD_REG, + (pll_d & 0x3F) << PLLD_LSB_SHIFT); + + return 0; +} + +static int aic3x_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + int delay = 0; + int width = aic3x->slot_width; + + if (!width) + width = substream->runtime->sample_bits; + + /* TDM slot selection only valid in DSP_A/_B mode */ + if (aic3x->dai_fmt == SND_SOC_DAIFMT_DSP_A) + delay += (aic3x->tdm_delay*width + 1); + else if (aic3x->dai_fmt == SND_SOC_DAIFMT_DSP_B) + delay += aic3x->tdm_delay*width; + + /* Configure data delay */ + snd_soc_component_write(component, AIC3X_ASD_INTF_CTRLC, delay); + + return 0; +} + +static int aic3x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u8 ldac_reg = snd_soc_component_read32(component, LDAC_VOL) & ~MUTE_ON; + u8 rdac_reg = snd_soc_component_read32(component, RDAC_VOL) & ~MUTE_ON; + + if (mute) { + snd_soc_component_write(component, LDAC_VOL, ldac_reg | MUTE_ON); + snd_soc_component_write(component, RDAC_VOL, rdac_reg | MUTE_ON); + } else { + snd_soc_component_write(component, LDAC_VOL, ldac_reg); + snd_soc_component_write(component, RDAC_VOL, rdac_reg); + } + + return 0; +} + +static int aic3x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + + /* set clock on MCLK or GPIO2 or BCLK */ + snd_soc_component_update_bits(component, AIC3X_CLKGEN_CTRL_REG, PLLCLK_IN_MASK, + clk_id << PLLCLK_IN_SHIFT); + snd_soc_component_update_bits(component, AIC3X_CLKGEN_CTRL_REG, CLKDIV_IN_MASK, + clk_id << CLKDIV_IN_SHIFT); + + aic3x->sysclk = freq; + return 0; +} + +static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + u8 iface_areg, iface_breg; + + iface_areg = snd_soc_component_read32(component, AIC3X_ASD_INTF_CTRLA) & 0x3f; + iface_breg = snd_soc_component_read32(component, AIC3X_ASD_INTF_CTRLB) & 0x3f; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aic3x->master = 1; + iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + aic3x->master = 0; + iface_areg &= ~(BIT_CLK_MASTER | WORD_CLK_MASTER); + break; + default: + return -EINVAL; + } + + /* + * match both interface format and signal polarities since they + * are fixed + */ + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_INV_MASK)) { + case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): + break; + case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF): + case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF): + iface_breg |= (0x01 << 6); + break; + case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF): + iface_breg |= (0x02 << 6); + break; + case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): + iface_breg |= (0x03 << 6); + break; + default: + return -EINVAL; + } + + aic3x->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + /* set iface */ + snd_soc_component_write(component, AIC3X_ASD_INTF_CTRLA, iface_areg); + snd_soc_component_write(component, AIC3X_ASD_INTF_CTRLB, iface_breg); + + return 0; +} + +static int aic3x_set_dai_tdm_slot(struct snd_soc_dai *codec_dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = codec_dai->component; + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + unsigned int lsb; + + if (tx_mask != rx_mask) { + dev_err(component->dev, "tx and rx masks must be symmetric\n"); + return -EINVAL; + } + + if (unlikely(!tx_mask)) { + dev_err(component->dev, "tx and rx masks need to be non 0\n"); + return -EINVAL; + } + + /* TDM based on DSP mode requires slots to be adjacent */ + lsb = __ffs(tx_mask); + if ((lsb + 1) != __fls(tx_mask)) { + dev_err(component->dev, "Invalid mask, slots must be adjacent\n"); + return -EINVAL; + } + + switch (slot_width) { + case 16: + case 20: + case 24: + case 32: + break; + default: + dev_err(component->dev, "Unsupported slot width %d\n", slot_width); + return -EINVAL; + } + + + aic3x->tdm_delay = lsb; + aic3x->slot_width = slot_width; + + /* DOUT in high-impedance on inactive bit clocks */ + snd_soc_component_update_bits(component, AIC3X_ASD_INTF_CTRLA, + DOUT_TRISTATE, DOUT_TRISTATE); + + return 0; +} + +static int aic3x_regulator_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct aic3x_disable_nb *disable_nb = + container_of(nb, struct aic3x_disable_nb, nb); + struct aic3x_priv *aic3x = disable_nb->aic3x; + + if (event & REGULATOR_EVENT_DISABLE) { + /* + * Put codec to reset and require cache sync as at least one + * of the supplies was disabled + */ + if (gpio_is_valid(aic3x->gpio_reset)) + gpio_set_value(aic3x->gpio_reset, 0); + regcache_mark_dirty(aic3x->regmap); + } + + return 0; +} + +static int aic3x_set_power(struct snd_soc_component *component, int power) +{ + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + unsigned int pll_c, pll_d; + int ret; + + if (power) { + ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret) + goto out; + aic3x->power = 1; + + if (gpio_is_valid(aic3x->gpio_reset)) { + udelay(1); + gpio_set_value(aic3x->gpio_reset, 1); + } + + /* Sync reg_cache with the hardware */ + regcache_cache_only(aic3x->regmap, false); + regcache_sync(aic3x->regmap); + + /* Rewrite paired PLL D registers in case cached sync skipped + * writing one of them and thus caused other one also not + * being written + */ + pll_c = snd_soc_component_read32(component, AIC3X_PLL_PROGC_REG); + pll_d = snd_soc_component_read32(component, AIC3X_PLL_PROGD_REG); + if (pll_c == aic3x_reg[AIC3X_PLL_PROGC_REG].def || + pll_d == aic3x_reg[AIC3X_PLL_PROGD_REG].def) { + snd_soc_component_write(component, AIC3X_PLL_PROGC_REG, pll_c); + snd_soc_component_write(component, AIC3X_PLL_PROGD_REG, pll_d); + } + + /* + * Delay is needed to reduce pop-noise after syncing back the + * registers + */ + mdelay(50); + } else { + /* + * Do soft reset to this codec instance in order to clear + * possible VDD leakage currents in case the supply regulators + * remain on + */ + snd_soc_component_write(component, AIC3X_RESET, SOFT_RESET); + regcache_mark_dirty(aic3x->regmap); + aic3x->power = 0; + /* HW writes are needless when bias is off */ + regcache_cache_only(aic3x->regmap, true); + ret = regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + } +out: + return ret; +} + +static int aic3x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY && + aic3x->master) { + /* enable pll */ + snd_soc_component_update_bits(component, AIC3X_PLL_PROGA_REG, + PLL_ENABLE, PLL_ENABLE); + } + break; + case SND_SOC_BIAS_STANDBY: + if (!aic3x->power) + aic3x_set_power(component, 1); + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_PREPARE && + aic3x->master) { + /* disable pll */ + snd_soc_component_update_bits(component, AIC3X_PLL_PROGA_REG, + PLL_ENABLE, 0); + } + break; + case SND_SOC_BIAS_OFF: + if (aic3x->power) + aic3x_set_power(component, 0); + break; + } + + return 0; +} + +#define AIC3X_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops aic3x_dai_ops = { + .hw_params = aic3x_hw_params, + .prepare = aic3x_prepare, + .digital_mute = aic3x_mute, + .set_sysclk = aic3x_set_dai_sysclk, + .set_fmt = aic3x_set_dai_fmt, + .set_tdm_slot = aic3x_set_dai_tdm_slot, +}; + +static struct snd_soc_dai_driver aic3x_dai = { + .name = "tlv320aic3x-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC3X_RATES, + .formats = AIC3X_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC3X_RATES, + .formats = AIC3X_FORMATS,}, + .ops = &aic3x_dai_ops, + .symmetric_rates = 1, +}; + +static void aic3x_mono_init(struct snd_soc_component *component) +{ + /* DAC to Mono Line Out default volume and route to Output mixer */ + snd_soc_component_write(component, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + snd_soc_component_write(component, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + /* unmute all outputs */ + snd_soc_component_update_bits(component, MONOLOPM_CTRL, UNMUTE, UNMUTE); + + /* PGA to Mono Line Out default volume, disconnect from Output Mixer */ + snd_soc_component_write(component, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL); + snd_soc_component_write(component, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL); + + /* Line2 to Mono Out default volume, disconnect from Output Mixer */ + snd_soc_component_write(component, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL); + snd_soc_component_write(component, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL); +} + +/* + * initialise the AIC3X driver + * register the mixer and dsp interfaces with the kernel + */ +static int aic3x_init(struct snd_soc_component *component) +{ + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + + snd_soc_component_write(component, AIC3X_PAGE_SELECT, PAGE0_SELECT); + snd_soc_component_write(component, AIC3X_RESET, SOFT_RESET); + + /* DAC default volume and mute */ + snd_soc_component_write(component, LDAC_VOL, DEFAULT_VOL | MUTE_ON); + snd_soc_component_write(component, RDAC_VOL, DEFAULT_VOL | MUTE_ON); + + /* DAC to HP default volume and route to Output mixer */ + snd_soc_component_write(component, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON); + snd_soc_component_write(component, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON); + snd_soc_component_write(component, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON); + snd_soc_component_write(component, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON); + /* DAC to Line Out default volume and route to Output mixer */ + snd_soc_component_write(component, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + snd_soc_component_write(component, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + /* unmute all outputs */ + snd_soc_component_update_bits(component, LLOPM_CTRL, UNMUTE, UNMUTE); + snd_soc_component_update_bits(component, RLOPM_CTRL, UNMUTE, UNMUTE); + snd_soc_component_update_bits(component, HPLOUT_CTRL, UNMUTE, UNMUTE); + snd_soc_component_update_bits(component, HPROUT_CTRL, UNMUTE, UNMUTE); + snd_soc_component_update_bits(component, HPLCOM_CTRL, UNMUTE, UNMUTE); + snd_soc_component_update_bits(component, HPRCOM_CTRL, UNMUTE, UNMUTE); + + /* ADC default volume and unmute */ + snd_soc_component_write(component, LADC_VOL, DEFAULT_GAIN); + snd_soc_component_write(component, RADC_VOL, DEFAULT_GAIN); + /* By default route Line1 to ADC PGA mixer */ + snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x0); + snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x0); + + /* PGA to HP Bypass default volume, disconnect from Output Mixer */ + snd_soc_component_write(component, PGAL_2_HPLOUT_VOL, DEFAULT_VOL); + snd_soc_component_write(component, PGAR_2_HPROUT_VOL, DEFAULT_VOL); + snd_soc_component_write(component, PGAL_2_HPLCOM_VOL, DEFAULT_VOL); + snd_soc_component_write(component, PGAR_2_HPRCOM_VOL, DEFAULT_VOL); + /* PGA to Line Out default volume, disconnect from Output Mixer */ + snd_soc_component_write(component, PGAL_2_LLOPM_VOL, DEFAULT_VOL); + snd_soc_component_write(component, PGAR_2_RLOPM_VOL, DEFAULT_VOL); + + /* On tlv320aic3104, these registers are reserved and must not be written */ + if (aic3x->model != AIC3X_MODEL_3104) { + /* Line2 to HP Bypass default volume, disconnect from Output Mixer */ + snd_soc_component_write(component, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL); + snd_soc_component_write(component, LINE2R_2_HPROUT_VOL, DEFAULT_VOL); + snd_soc_component_write(component, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL); + snd_soc_component_write(component, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL); + /* Line2 Line Out default volume, disconnect from Output Mixer */ + snd_soc_component_write(component, LINE2L_2_LLOPM_VOL, DEFAULT_VOL); + snd_soc_component_write(component, LINE2R_2_RLOPM_VOL, DEFAULT_VOL); + } + + switch (aic3x->model) { + case AIC3X_MODEL_3X: + case AIC3X_MODEL_33: + aic3x_mono_init(component); + break; + case AIC3X_MODEL_3007: + snd_soc_component_write(component, CLASSD_CTRL, 0); + break; + } + + /* Output common-mode voltage = 1.5 V */ + snd_soc_component_update_bits(component, HPOUT_SC, HPOUT_SC_OCMV_MASK, + aic3x->ocmv << HPOUT_SC_OCMV_SHIFT); + + return 0; +} + +static bool aic3x_is_shared_reset(struct aic3x_priv *aic3x) +{ + struct aic3x_priv *a; + + list_for_each_entry(a, &reset_list, list) { + if (gpio_is_valid(aic3x->gpio_reset) && + aic3x->gpio_reset == a->gpio_reset) + return true; + } + + return false; +} + +static int aic3x_probe(struct snd_soc_component *component) +{ + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + int ret, i; + + aic3x->component = component; + + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) { + aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event; + aic3x->disable_nb[i].aic3x = aic3x; + ret = regulator_register_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); + if (ret) { + dev_err(component->dev, + "Failed to request regulator notifier: %d\n", + ret); + goto err_notif; + } + } + + regcache_mark_dirty(aic3x->regmap); + aic3x_init(component); + + if (aic3x->setup) { + if (aic3x->model != AIC3X_MODEL_3104) { + /* setup GPIO functions */ + snd_soc_component_write(component, AIC3X_GPIO1_REG, + (aic3x->setup->gpio_func[0] & 0xf) << 4); + snd_soc_component_write(component, AIC3X_GPIO2_REG, + (aic3x->setup->gpio_func[1] & 0xf) << 4); + } else { + dev_warn(component->dev, "GPIO functionality is not supported on tlv320aic3104\n"); + } + } + + switch (aic3x->model) { + case AIC3X_MODEL_3X: + case AIC3X_MODEL_33: + snd_soc_add_component_controls(component, aic3x_extra_snd_controls, + ARRAY_SIZE(aic3x_extra_snd_controls)); + snd_soc_add_component_controls(component, aic3x_mono_controls, + ARRAY_SIZE(aic3x_mono_controls)); + break; + case AIC3X_MODEL_3007: + snd_soc_add_component_controls(component, aic3x_extra_snd_controls, + ARRAY_SIZE(aic3x_extra_snd_controls)); + snd_soc_add_component_controls(component, + &aic3x_classd_amp_gain_ctrl, 1); + break; + case AIC3X_MODEL_3104: + break; + } + + /* set mic bias voltage */ + switch (aic3x->micbias_vg) { + case AIC3X_MICBIAS_2_0V: + case AIC3X_MICBIAS_2_5V: + case AIC3X_MICBIAS_AVDDV: + snd_soc_component_update_bits(component, MICBIAS_CTRL, + MICBIAS_LEVEL_MASK, + (aic3x->micbias_vg) << MICBIAS_LEVEL_SHIFT); + break; + case AIC3X_MICBIAS_OFF: + /* + * noting to do. target won't enter here. This is just to avoid + * compile time warning "warning: enumeration value + * 'AIC3X_MICBIAS_OFF' not handled in switch" + */ + break; + } + + aic3x_add_widgets(component); + + return 0; + +err_notif: + while (i--) + regulator_unregister_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); + return ret; +} + +static void aic3x_remove(struct snd_soc_component *component) +{ + struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) + regulator_unregister_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); +} + +static const struct snd_soc_component_driver soc_component_dev_aic3x = { + .set_bias_level = aic3x_set_bias_level, + .probe = aic3x_probe, + .remove = aic3x_remove, + .controls = aic3x_snd_controls, + .num_controls = ARRAY_SIZE(aic3x_snd_controls), + .dapm_widgets = aic3x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aic3x_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static void aic3x_configure_ocmv(struct i2c_client *client) +{ + struct device_node *np = client->dev.of_node; + struct aic3x_priv *aic3x = i2c_get_clientdata(client); + u32 value; + int dvdd, avdd; + + if (np && !of_property_read_u32(np, "ai3x-ocmv", &value)) { + /* OCMV setting is forced by DT */ + if (value <= 3) { + aic3x->ocmv = value; + return; + } + } + + dvdd = regulator_get_voltage(aic3x->supplies[1].consumer); + avdd = regulator_get_voltage(aic3x->supplies[2].consumer); + + if (avdd > 3600000 || dvdd > 1950000) { + dev_warn(&client->dev, + "Too high supply voltage(s) AVDD: %d, DVDD: %d\n", + avdd, dvdd); + } else if (avdd == 3600000 && dvdd == 1950000) { + aic3x->ocmv = HPOUT_SC_OCMV_1_8V; + } else if (avdd > 3300000 && dvdd > 1800000) { + aic3x->ocmv = HPOUT_SC_OCMV_1_65V; + } else if (avdd > 3000000 && dvdd > 1650000) { + aic3x->ocmv = HPOUT_SC_OCMV_1_5V; + } else if (avdd >= 2700000 && dvdd >= 1525000) { + aic3x->ocmv = HPOUT_SC_OCMV_1_35V; + } else { + dev_warn(&client->dev, + "Invalid supply voltage(s) AVDD: %d, DVDD: %d\n", + avdd, dvdd); + } +} + +/* + * AIC3X 2 wire address can be up to 4 devices with device addresses + * 0x18, 0x19, 0x1A, 0x1B + */ + +static const struct i2c_device_id aic3x_i2c_id[] = { + { "tlv320aic3x", AIC3X_MODEL_3X }, + { "tlv320aic33", AIC3X_MODEL_33 }, + { "tlv320aic3007", AIC3X_MODEL_3007 }, + { "tlv320aic3106", AIC3X_MODEL_3X }, + { "tlv320aic3104", AIC3X_MODEL_3104 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id); + +static const struct reg_sequence aic3007_class_d[] = { + /* Class-D speaker driver init; datasheet p. 46 */ + { AIC3X_PAGE_SELECT, 0x0D }, + { 0xD, 0x0D }, + { 0x8, 0x5C }, + { 0x8, 0x5D }, + { 0x8, 0x5C }, + { AIC3X_PAGE_SELECT, 0x00 }, +}; + +/* + * If the i2c layer weren't so broken, we could pass this kind of data + * around + */ +static int aic3x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct aic3x_pdata *pdata = i2c->dev.platform_data; + struct aic3x_priv *aic3x; + struct aic3x_setup_data *ai3x_setup; + struct device_node *np = i2c->dev.of_node; + int ret, i; + u32 value; + + aic3x = devm_kzalloc(&i2c->dev, sizeof(struct aic3x_priv), GFP_KERNEL); + if (!aic3x) + return -ENOMEM; + + aic3x->regmap = devm_regmap_init_i2c(i2c, &aic3x_regmap); + if (IS_ERR(aic3x->regmap)) { + ret = PTR_ERR(aic3x->regmap); + return ret; + } + + regcache_cache_only(aic3x->regmap, true); + + i2c_set_clientdata(i2c, aic3x); + if (pdata) { + aic3x->gpio_reset = pdata->gpio_reset; + aic3x->setup = pdata->setup; + aic3x->micbias_vg = pdata->micbias_vg; + } else if (np) { + ai3x_setup = devm_kzalloc(&i2c->dev, sizeof(*ai3x_setup), + GFP_KERNEL); + if (!ai3x_setup) + return -ENOMEM; + + ret = of_get_named_gpio(np, "reset-gpios", 0); + if (ret >= 0) { + aic3x->gpio_reset = ret; + } else { + ret = of_get_named_gpio(np, "gpio-reset", 0); + if (ret > 0) { + dev_warn(&i2c->dev, "Using deprecated property \"gpio-reset\", please update your DT"); + aic3x->gpio_reset = ret; + } else { + aic3x->gpio_reset = -1; + } + } + + if (of_property_read_u32_array(np, "ai3x-gpio-func", + ai3x_setup->gpio_func, 2) >= 0) { + aic3x->setup = ai3x_setup; + } + + if (!of_property_read_u32(np, "ai3x-micbias-vg", &value)) { + switch (value) { + case 1 : + aic3x->micbias_vg = AIC3X_MICBIAS_2_0V; + break; + case 2 : + aic3x->micbias_vg = AIC3X_MICBIAS_2_5V; + break; + case 3 : + aic3x->micbias_vg = AIC3X_MICBIAS_AVDDV; + break; + default : + aic3x->micbias_vg = AIC3X_MICBIAS_OFF; + dev_err(&i2c->dev, "Unsuitable MicBias voltage " + "found in DT\n"); + } + } else { + aic3x->micbias_vg = AIC3X_MICBIAS_OFF; + } + + } else { + aic3x->gpio_reset = -1; + } + + aic3x->model = id->driver_data; + + if (gpio_is_valid(aic3x->gpio_reset) && + !aic3x_is_shared_reset(aic3x)) { + ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset"); + if (ret != 0) + goto err; + gpio_direction_output(aic3x->gpio_reset, 0); + } + + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) + aic3x->supplies[i].supply = aic3x_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + goto err_gpio; + } + + aic3x_configure_ocmv(i2c); + + if (aic3x->model == AIC3X_MODEL_3007) { + ret = regmap_register_patch(aic3x->regmap, aic3007_class_d, + ARRAY_SIZE(aic3007_class_d)); + if (ret != 0) + dev_err(&i2c->dev, "Failed to init class D: %d\n", + ret); + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_aic3x, &aic3x_dai, 1); + + if (ret != 0) + goto err_gpio; + + INIT_LIST_HEAD(&aic3x->list); + list_add(&aic3x->list, &reset_list); + + return 0; + +err_gpio: + if (gpio_is_valid(aic3x->gpio_reset) && + !aic3x_is_shared_reset(aic3x)) + gpio_free(aic3x->gpio_reset); +err: + return ret; +} + +static int aic3x_i2c_remove(struct i2c_client *client) +{ + struct aic3x_priv *aic3x = i2c_get_clientdata(client); + + list_del(&aic3x->list); + + if (gpio_is_valid(aic3x->gpio_reset) && + !aic3x_is_shared_reset(aic3x)) { + gpio_set_value(aic3x->gpio_reset, 0); + gpio_free(aic3x->gpio_reset); + } + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id tlv320aic3x_of_match[] = { + { .compatible = "ti,tlv320aic3x", }, + { .compatible = "ti,tlv320aic33" }, + { .compatible = "ti,tlv320aic3007" }, + { .compatible = "ti,tlv320aic3106" }, + { .compatible = "ti,tlv320aic3104" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tlv320aic3x_of_match); +#endif + +/* machine i2c codec control layer */ +static struct i2c_driver aic3x_i2c_driver = { + .driver = { + .name = "tlv320aic3x-codec", + .of_match_table = of_match_ptr(tlv320aic3x_of_match), + }, + .probe = aic3x_i2c_probe, + .remove = aic3x_i2c_remove, + .id_table = aic3x_i2c_id, +}; + +module_i2c_driver(aic3x_i2c_driver); + +MODULE_DESCRIPTION("ASoC TLV320AIC3X codec driver"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h new file mode 100644 index 000000000..34c35196a --- /dev/null +++ b/sound/soc/codecs/tlv320aic3x.h @@ -0,0 +1,291 @@ +/* + * ALSA SoC TLV320AIC3X codec driver + * + * Author: Vladimir Barinov, + * Copyright: (C) 2007 MontaVista Software, Inc., + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AIC3X_H +#define _AIC3X_H + +/* AIC3X register space */ +#define AIC3X_CACHEREGNUM 110 + +/* Page select register */ +#define AIC3X_PAGE_SELECT 0 +/* Software reset register */ +#define AIC3X_RESET 1 +/* Codec Sample rate select register */ +#define AIC3X_SAMPLE_RATE_SEL_REG 2 +/* PLL progrramming register A */ +#define AIC3X_PLL_PROGA_REG 3 +/* PLL progrramming register B */ +#define AIC3X_PLL_PROGB_REG 4 +/* PLL progrramming register C */ +#define AIC3X_PLL_PROGC_REG 5 +/* PLL progrramming register D */ +#define AIC3X_PLL_PROGD_REG 6 +/* Codec datapath setup register */ +#define AIC3X_CODEC_DATAPATH_REG 7 +/* Audio serial data interface control register A */ +#define AIC3X_ASD_INTF_CTRLA 8 +/* Audio serial data interface control register B */ +#define AIC3X_ASD_INTF_CTRLB 9 +/* Audio serial data interface control register C */ +#define AIC3X_ASD_INTF_CTRLC 10 +/* Audio overflow status and PLL R value programming register */ +#define AIC3X_OVRF_STATUS_AND_PLLR_REG 11 +/* Audio codec digital filter control register */ +#define AIC3X_CODEC_DFILT_CTRL 12 +/* Headset/button press detection register */ +#define AIC3X_HEADSET_DETECT_CTRL_A 13 +#define AIC3X_HEADSET_DETECT_CTRL_B 14 +/* ADC PGA Gain control registers */ +#define LADC_VOL 15 +#define RADC_VOL 16 +/* MIC3 control registers */ +#define MIC3LR_2_LADC_CTRL 17 +#define MIC3LR_2_RADC_CTRL 18 +/* Line1 Input control registers */ +#define LINE1L_2_LADC_CTRL 19 +#define LINE1R_2_LADC_CTRL 21 +#define LINE1R_2_RADC_CTRL 22 +#define LINE1L_2_RADC_CTRL 24 +/* Line2 Input control registers */ +#define LINE2L_2_LADC_CTRL 20 +#define LINE2R_2_RADC_CTRL 23 +/* MICBIAS Control Register */ +#define MICBIAS_CTRL 25 + +/* AGC Control Registers A, B, C */ +#define LAGC_CTRL_A 26 +#define LAGC_CTRL_B 27 +#define LAGC_CTRL_C 28 +#define RAGC_CTRL_A 29 +#define RAGC_CTRL_B 30 +#define RAGC_CTRL_C 31 + +/* DAC Power and Left High Power Output control registers */ +#define DAC_PWR 37 +#define HPLCOM_CFG 37 +/* Right High Power Output control registers */ +#define HPRCOM_CFG 38 +/* High Power Output Stage Control Register */ +#define HPOUT_SC 40 +/* DAC Output Switching control registers */ +#define DAC_LINE_MUX 41 +/* High Power Output Driver Pop Reduction registers */ +#define HPOUT_POP_REDUCTION 42 +/* DAC Digital control registers */ +#define LDAC_VOL 43 +#define RDAC_VOL 44 +/* Left High Power Output control registers */ +#define LINE2L_2_HPLOUT_VOL 45 +#define PGAL_2_HPLOUT_VOL 46 +#define DACL1_2_HPLOUT_VOL 47 +#define LINE2R_2_HPLOUT_VOL 48 +#define PGAR_2_HPLOUT_VOL 49 +#define DACR1_2_HPLOUT_VOL 50 +#define HPLOUT_CTRL 51 +/* Left High Power COM control registers */ +#define LINE2L_2_HPLCOM_VOL 52 +#define PGAL_2_HPLCOM_VOL 53 +#define DACL1_2_HPLCOM_VOL 54 +#define LINE2R_2_HPLCOM_VOL 55 +#define PGAR_2_HPLCOM_VOL 56 +#define DACR1_2_HPLCOM_VOL 57 +#define HPLCOM_CTRL 58 +/* Right High Power Output control registers */ +#define LINE2L_2_HPROUT_VOL 59 +#define PGAL_2_HPROUT_VOL 60 +#define DACL1_2_HPROUT_VOL 61 +#define LINE2R_2_HPROUT_VOL 62 +#define PGAR_2_HPROUT_VOL 63 +#define DACR1_2_HPROUT_VOL 64 +#define HPROUT_CTRL 65 +/* Right High Power COM control registers */ +#define LINE2L_2_HPRCOM_VOL 66 +#define PGAL_2_HPRCOM_VOL 67 +#define DACL1_2_HPRCOM_VOL 68 +#define LINE2R_2_HPRCOM_VOL 69 +#define PGAR_2_HPRCOM_VOL 70 +#define DACR1_2_HPRCOM_VOL 71 +#define HPRCOM_CTRL 72 +/* Mono Line Output Plus/Minus control registers */ +#define LINE2L_2_MONOLOPM_VOL 73 +#define PGAL_2_MONOLOPM_VOL 74 +#define DACL1_2_MONOLOPM_VOL 75 +#define LINE2R_2_MONOLOPM_VOL 76 +#define PGAR_2_MONOLOPM_VOL 77 +#define DACR1_2_MONOLOPM_VOL 78 +#define MONOLOPM_CTRL 79 +/* Class-D speaker driver on tlv320aic3007 */ +#define CLASSD_CTRL 73 +/* Left Line Output Plus/Minus control registers */ +#define LINE2L_2_LLOPM_VOL 80 +#define PGAL_2_LLOPM_VOL 81 +#define DACL1_2_LLOPM_VOL 82 +#define LINE2R_2_LLOPM_VOL 83 +#define PGAR_2_LLOPM_VOL 84 +#define DACR1_2_LLOPM_VOL 85 +#define LLOPM_CTRL 86 +/* Right Line Output Plus/Minus control registers */ +#define LINE2L_2_RLOPM_VOL 87 +#define PGAL_2_RLOPM_VOL 88 +#define DACL1_2_RLOPM_VOL 89 +#define LINE2R_2_RLOPM_VOL 90 +#define PGAR_2_RLOPM_VOL 91 +#define DACR1_2_RLOPM_VOL 92 +#define RLOPM_CTRL 93 +/* GPIO/IRQ registers */ +#define AIC3X_STICKY_IRQ_FLAGS_REG 96 +#define AIC3X_RT_IRQ_FLAGS_REG 97 +#define AIC3X_GPIO1_REG 98 +#define AIC3X_GPIO2_REG 99 +#define AIC3X_GPIOA_REG 100 +#define AIC3X_GPIOB_REG 101 +/* Clock generation control register */ +#define AIC3X_CLKGEN_CTRL_REG 102 +/* New AGC registers */ +#define LAGCN_ATTACK 103 +#define LAGCN_DECAY 104 +#define RAGCN_ATTACK 105 +#define RAGCN_DECAY 106 +/* New Programmable ADC Digital Path and I2C Bus Condition Register */ +#define NEW_ADC_DIGITALPATH 107 +/* Passive Analog Signal Bypass Selection During Powerdown Register */ +#define PASSIVE_BYPASS 108 +/* DAC Quiescent Current Adjustment Register */ +#define DAC_ICC_ADJ 109 + +/* Page select register bits */ +#define PAGE0_SELECT 0 +#define PAGE1_SELECT 1 + +/* Audio serial data interface control register A bits */ +#define BIT_CLK_MASTER 0x80 +#define WORD_CLK_MASTER 0x40 +#define DOUT_TRISTATE 0x20 + +/* Codec Datapath setup register 7 */ +#define FSREF_44100 (1 << 7) +#define FSREF_48000 (0 << 7) +#define DUAL_RATE_MODE ((1 << 5) | (1 << 6)) +#define LDAC2LCH (0x1 << 3) +#define RDAC2RCH (0x1 << 1) +#define LDAC2RCH (0x2 << 3) +#define RDAC2LCH (0x2 << 1) +#define LDAC2MONOMIX (0x3 << 3) +#define RDAC2MONOMIX (0x3 << 1) + +/* PLL registers bitfields */ +#define PLLP_SHIFT 0 +#define PLLP_MASK 7 +#define PLLQ_SHIFT 3 +#define PLLR_SHIFT 0 +#define PLLJ_SHIFT 2 +#define PLLD_MSB_SHIFT 0 +#define PLLD_LSB_SHIFT 2 + +/* Clock generation register bits */ +#define CODEC_CLKIN_PLLDIV 0 +#define CODEC_CLKIN_CLKDIV 1 +#define PLL_CLKIN_SHIFT 4 +#define MCLK_SOURCE 0x0 +#define PLL_CLKDIV_SHIFT 0 +#define PLLCLK_IN_MASK 0x30 +#define PLLCLK_IN_SHIFT 4 +#define CLKDIV_IN_MASK 0xc0 +#define CLKDIV_IN_SHIFT 6 +/* clock in source */ +#define CLKIN_MCLK 0 +#define CLKIN_GPIO2 1 +#define CLKIN_BCLK 2 + +/* Software reset register bits */ +#define SOFT_RESET 0x80 + +/* PLL progrramming register A bits */ +#define PLL_ENABLE 0x80 + +/* Route bits */ +#define ROUTE_ON 0x80 + +/* Mute bits */ +#define UNMUTE 0x08 +#define MUTE_ON 0x80 + +/* Power bits */ +#define LADC_PWR_ON 0x04 +#define RADC_PWR_ON 0x04 +#define LDAC_PWR_ON 0x80 +#define RDAC_PWR_ON 0x40 +#define HPLOUT_PWR_ON 0x01 +#define HPROUT_PWR_ON 0x01 +#define HPLCOM_PWR_ON 0x01 +#define HPRCOM_PWR_ON 0x01 +#define MONOLOPM_PWR_ON 0x01 +#define LLOPM_PWR_ON 0x01 +#define RLOPM_PWR_ON 0x01 + +#define INVERT_VOL(val) (0x7f - val) + +/* Default output volume (inverted) */ +#define DEFAULT_VOL INVERT_VOL(0x50) +/* Default input volume */ +#define DEFAULT_GAIN 0x20 + +/* MICBIAS Control Register */ +#define MICBIAS_LEVEL_SHIFT (6) +#define MICBIAS_LEVEL_MASK (3 << 6) + +/* HPOUT_SC */ +#define HPOUT_SC_OCMV_MASK (3 << 6) +#define HPOUT_SC_OCMV_SHIFT (6) +#define HPOUT_SC_OCMV_1_35V 0 +#define HPOUT_SC_OCMV_1_5V 1 +#define HPOUT_SC_OCMV_1_65V 2 +#define HPOUT_SC_OCMV_1_8V 3 + +/* headset detection / button API */ + +/* The AIC3x supports detection of stereo headsets (GND + left + right signal) + * and cellular headsets (GND + speaker output + microphone input). + * It is recommended to enable MIC bias for this function to work properly. + * For more information, please refer to the datasheet. */ +enum { + AIC3X_HEADSET_DETECT_OFF = 0, + AIC3X_HEADSET_DETECT_STEREO = 1, + AIC3X_HEADSET_DETECT_CELLULAR = 2, + AIC3X_HEADSET_DETECT_BOTH = 3 +}; + +enum { + AIC3X_HEADSET_DEBOUNCE_16MS = 0, + AIC3X_HEADSET_DEBOUNCE_32MS = 1, + AIC3X_HEADSET_DEBOUNCE_64MS = 2, + AIC3X_HEADSET_DEBOUNCE_128MS = 3, + AIC3X_HEADSET_DEBOUNCE_256MS = 4, + AIC3X_HEADSET_DEBOUNCE_512MS = 5 +}; + +enum { + AIC3X_BUTTON_DEBOUNCE_0MS = 0, + AIC3X_BUTTON_DEBOUNCE_8MS = 1, + AIC3X_BUTTON_DEBOUNCE_16MS = 2, + AIC3X_BUTTON_DEBOUNCE_32MS = 3 +}; + +#define AIC3X_HEADSET_DETECT_ENABLED 0x80 +#define AIC3X_HEADSET_DETECT_SHIFT 5 +#define AIC3X_HEADSET_DETECT_MASK 3 +#define AIC3X_HEADSET_DEBOUNCE_SHIFT 2 +#define AIC3X_HEADSET_DEBOUNCE_MASK 7 +#define AIC3X_BUTTON_DEBOUNCE_SHIFT 0 +#define AIC3X_BUTTON_DEBOUNCE_MASK 3 + +#endif /* _AIC3X_H */ diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c new file mode 100644 index 000000000..a957eaeb7 --- /dev/null +++ b/sound/soc/codecs/tlv320dac33.c @@ -0,0 +1,1592 @@ +/* + * ALSA SoC Texas Instruments TLV320DAC33 codec driver + * + * Author: Peter Ujfalusi + * + * Copyright: (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "tlv320dac33.h" + +/* + * The internal FIFO is 24576 bytes long + * It can be configured to hold 16bit or 24bit samples + * In 16bit configuration the FIFO can hold 6144 stereo samples + * In 24bit configuration the FIFO can hold 4096 stereo samples + */ +#define DAC33_FIFO_SIZE_16BIT 6144 +#define DAC33_FIFO_SIZE_24BIT 4096 +#define DAC33_MODE7_MARGIN 10 /* Safety margin for FIFO in Mode7 */ + +#define BURST_BASEFREQ_HZ 49152000 + +#define SAMPLES_TO_US(rate, samples) \ + (1000000000 / (((rate) * 1000) / (samples))) + +#define US_TO_SAMPLES(rate, us) \ + ((rate) / (1000000 / ((us) < 1000000 ? (us) : 1000000))) + +#define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \ + (((samples)*5000) / (((burstrate)*5000) / ((burstrate) - (playrate)))) + +static void dac33_calculate_times(struct snd_pcm_substream *substream, + struct snd_soc_component *component); +static int dac33_prepare_chip(struct snd_pcm_substream *substream, + struct snd_soc_component *component); + +enum dac33_state { + DAC33_IDLE = 0, + DAC33_PREFILL, + DAC33_PLAYBACK, + DAC33_FLUSH, +}; + +enum dac33_fifo_modes { + DAC33_FIFO_BYPASS = 0, + DAC33_FIFO_MODE1, + DAC33_FIFO_MODE7, + DAC33_FIFO_LAST_MODE, +}; + +#define DAC33_NUM_SUPPLIES 3 +static const char *dac33_supply_names[DAC33_NUM_SUPPLIES] = { + "AVDD", + "DVDD", + "IOVDD", +}; + +struct tlv320dac33_priv { + struct mutex mutex; + struct work_struct work; + struct snd_soc_component *component; + struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES]; + struct snd_pcm_substream *substream; + int power_gpio; + int chip_power; + int irq; + unsigned int refclk; + + unsigned int alarm_threshold; /* set to be half of LATENCY_TIME_MS */ + enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */ + unsigned int fifo_size; /* Size of the FIFO in samples */ + unsigned int nsample; /* burst read amount from host */ + int mode1_latency; /* latency caused by the i2c writes in + * us */ + u8 burst_bclkdiv; /* BCLK divider value in burst mode */ + u8 *reg_cache; + unsigned int burst_rate; /* Interface speed in Burst modes */ + + int keep_bclk; /* Keep the BCLK continuously running + * in FIFO modes */ + spinlock_t lock; + unsigned long long t_stamp1; /* Time stamp for FIFO modes to */ + unsigned long long t_stamp2; /* calculate the FIFO caused delay */ + + unsigned int mode1_us_burst; /* Time to burst read n number of + * samples */ + unsigned int mode7_us_to_lthr; /* Time to reach lthr from uthr */ + + unsigned int uthr; + + enum dac33_state state; + struct i2c_client *i2c; +}; + +static const u8 dac33_reg[DAC33_CACHEREGNUM] = { +0x00, 0x00, 0x00, 0x00, /* 0x00 - 0x03 */ +0x00, 0x00, 0x00, 0x00, /* 0x04 - 0x07 */ +0x00, 0x00, 0x00, 0x00, /* 0x08 - 0x0b */ +0x00, 0x00, 0x00, 0x00, /* 0x0c - 0x0f */ +0x00, 0x00, 0x00, 0x00, /* 0x10 - 0x13 */ +0x00, 0x00, 0x00, 0x00, /* 0x14 - 0x17 */ +0x00, 0x00, 0x00, 0x00, /* 0x18 - 0x1b */ +0x00, 0x00, 0x00, 0x00, /* 0x1c - 0x1f */ +0x00, 0x00, 0x00, 0x00, /* 0x20 - 0x23 */ +0x00, 0x00, 0x00, 0x00, /* 0x24 - 0x27 */ +0x00, 0x00, 0x00, 0x00, /* 0x28 - 0x2b */ +0x00, 0x00, 0x00, 0x80, /* 0x2c - 0x2f */ +0x80, 0x00, 0x00, 0x00, /* 0x30 - 0x33 */ +0x00, 0x00, 0x00, 0x00, /* 0x34 - 0x37 */ +0x00, 0x00, /* 0x38 - 0x39 */ +/* Registers 0x3a - 0x3f are reserved */ + 0x00, 0x00, /* 0x3a - 0x3b */ +0x00, 0x00, 0x00, 0x00, /* 0x3c - 0x3f */ + +0x00, 0x00, 0x00, 0x00, /* 0x40 - 0x43 */ +0x00, 0x80, /* 0x44 - 0x45 */ +/* Registers 0x46 - 0x47 are reserved */ + 0x80, 0x80, /* 0x46 - 0x47 */ + +0x80, 0x00, 0x00, /* 0x48 - 0x4a */ +/* Registers 0x4b - 0x7c are reserved */ + 0x00, /* 0x4b */ +0x00, 0x00, 0x00, 0x00, /* 0x4c - 0x4f */ +0x00, 0x00, 0x00, 0x00, /* 0x50 - 0x53 */ +0x00, 0x00, 0x00, 0x00, /* 0x54 - 0x57 */ +0x00, 0x00, 0x00, 0x00, /* 0x58 - 0x5b */ +0x00, 0x00, 0x00, 0x00, /* 0x5c - 0x5f */ +0x00, 0x00, 0x00, 0x00, /* 0x60 - 0x63 */ +0x00, 0x00, 0x00, 0x00, /* 0x64 - 0x67 */ +0x00, 0x00, 0x00, 0x00, /* 0x68 - 0x6b */ +0x00, 0x00, 0x00, 0x00, /* 0x6c - 0x6f */ +0x00, 0x00, 0x00, 0x00, /* 0x70 - 0x73 */ +0x00, 0x00, 0x00, 0x00, /* 0x74 - 0x77 */ +0x00, 0x00, 0x00, 0x00, /* 0x78 - 0x7b */ +0x00, /* 0x7c */ + + 0xda, 0x33, 0x03, /* 0x7d - 0x7f */ +}; + +/* Register read and write */ +static inline unsigned int dac33_read_reg_cache(struct snd_soc_component *component, + unsigned reg) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + u8 *cache = dac33->reg_cache; + if (reg >= DAC33_CACHEREGNUM) + return 0; + + return cache[reg]; +} + +static inline void dac33_write_reg_cache(struct snd_soc_component *component, + u8 reg, u8 value) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + u8 *cache = dac33->reg_cache; + if (reg >= DAC33_CACHEREGNUM) + return; + + cache[reg] = value; +} + +static int dac33_read(struct snd_soc_component *component, unsigned int reg, + u8 *value) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int val, ret = 0; + + *value = reg & 0xff; + + /* If powered off, return the cached value */ + if (dac33->chip_power) { + val = i2c_smbus_read_byte_data(dac33->i2c, value[0]); + if (val < 0) { + dev_err(component->dev, "Read failed (%d)\n", val); + value[0] = dac33_read_reg_cache(component, reg); + ret = val; + } else { + value[0] = val; + dac33_write_reg_cache(component, reg, val); + } + } else { + value[0] = dac33_read_reg_cache(component, reg); + } + + return ret; +} + +static int dac33_write(struct snd_soc_component *component, unsigned int reg, + unsigned int value) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + u8 data[2]; + int ret = 0; + + /* + * data is + * D15..D8 dac33 register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + dac33_write_reg_cache(component, data[0], data[1]); + if (dac33->chip_power) { + ret = i2c_master_send(dac33->i2c, data, 2); + if (ret != 2) + dev_err(component->dev, "Write failed (%d)\n", ret); + else + ret = 0; + } + + return ret; +} + +static int dac33_write_locked(struct snd_soc_component *component, unsigned int reg, + unsigned int value) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&dac33->mutex); + ret = dac33_write(component, reg, value); + mutex_unlock(&dac33->mutex); + + return ret; +} + +#define DAC33_I2C_ADDR_AUTOINC 0x80 +static int dac33_write16(struct snd_soc_component *component, unsigned int reg, + unsigned int value) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + u8 data[3]; + int ret = 0; + + /* + * data is + * D23..D16 dac33 register offset + * D15..D8 register data MSB + * D7...D0 register data LSB + */ + data[0] = reg & 0xff; + data[1] = (value >> 8) & 0xff; + data[2] = value & 0xff; + + dac33_write_reg_cache(component, data[0], data[1]); + dac33_write_reg_cache(component, data[0] + 1, data[2]); + + if (dac33->chip_power) { + /* We need to set autoincrement mode for 16 bit writes */ + data[0] |= DAC33_I2C_ADDR_AUTOINC; + ret = i2c_master_send(dac33->i2c, data, 3); + if (ret != 3) + dev_err(component->dev, "Write failed (%d)\n", ret); + else + ret = 0; + } + + return ret; +} + +static void dac33_init_chip(struct snd_soc_component *component) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + + if (unlikely(!dac33->chip_power)) + return; + + /* A : DAC sample rate Fsref/1.5 */ + dac33_write(component, DAC33_DAC_CTRL_A, DAC33_DACRATE(0)); + /* B : DAC src=normal, not muted */ + dac33_write(component, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT | + DAC33_DACSRCL_LEFT); + /* C : (defaults) */ + dac33_write(component, DAC33_DAC_CTRL_C, 0x00); + + /* 73 : volume soft stepping control, + clock source = internal osc (?) */ + dac33_write(component, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); + + /* Restore only selected registers (gains mostly) */ + dac33_write(component, DAC33_LDAC_DIG_VOL_CTRL, + dac33_read_reg_cache(component, DAC33_LDAC_DIG_VOL_CTRL)); + dac33_write(component, DAC33_RDAC_DIG_VOL_CTRL, + dac33_read_reg_cache(component, DAC33_RDAC_DIG_VOL_CTRL)); + + dac33_write(component, DAC33_LINEL_TO_LLO_VOL, + dac33_read_reg_cache(component, DAC33_LINEL_TO_LLO_VOL)); + dac33_write(component, DAC33_LINER_TO_RLO_VOL, + dac33_read_reg_cache(component, DAC33_LINER_TO_RLO_VOL)); + + dac33_write(component, DAC33_OUT_AMP_CTRL, + dac33_read_reg_cache(component, DAC33_OUT_AMP_CTRL)); + + dac33_write(component, DAC33_LDAC_PWR_CTRL, + dac33_read_reg_cache(component, DAC33_LDAC_PWR_CTRL)); + dac33_write(component, DAC33_RDAC_PWR_CTRL, + dac33_read_reg_cache(component, DAC33_RDAC_PWR_CTRL)); +} + +static inline int dac33_read_id(struct snd_soc_component *component) +{ + int i, ret = 0; + u8 reg; + + for (i = 0; i < 3; i++) { + ret = dac33_read(component, DAC33_DEVICE_ID_MSB + i, ®); + if (ret < 0) + break; + } + + return ret; +} + +static inline void dac33_soft_power(struct snd_soc_component *component, int power) +{ + u8 reg; + + reg = dac33_read_reg_cache(component, DAC33_PWR_CTRL); + if (power) + reg |= DAC33_PDNALLB; + else + reg &= ~(DAC33_PDNALLB | DAC33_OSCPDNB | + DAC33_DACRPDNB | DAC33_DACLPDNB); + dac33_write(component, DAC33_PWR_CTRL, reg); +} + +static inline void dac33_disable_digital(struct snd_soc_component *component) +{ + u8 reg; + + /* Stop the DAI clock */ + reg = dac33_read_reg_cache(component, DAC33_SER_AUDIOIF_CTRL_B); + reg &= ~DAC33_BCLKON; + dac33_write(component, DAC33_SER_AUDIOIF_CTRL_B, reg); + + /* Power down the Oscillator, and DACs */ + reg = dac33_read_reg_cache(component, DAC33_PWR_CTRL); + reg &= ~(DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB); + dac33_write(component, DAC33_PWR_CTRL, reg); +} + +static int dac33_hard_power(struct snd_soc_component *component, int power) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + + mutex_lock(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_dbg(component->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); + goto exit; + } + + if (power) { + ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies), + dac33->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", ret); + goto exit; + } + + if (dac33->power_gpio >= 0) + gpio_set_value(dac33->power_gpio, 1); + + dac33->chip_power = 1; + } else { + dac33_soft_power(component, 0); + if (dac33->power_gpio >= 0) + gpio_set_value(dac33->power_gpio, 0); + + ret = regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), + dac33->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to disable supplies: %d\n", ret); + goto exit; + } + + dac33->chip_power = 0; + } + +exit: + mutex_unlock(&dac33->mutex); + return ret; +} + +static int dac33_playback_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (likely(dac33->substream)) { + dac33_calculate_times(dac33->substream, component); + dac33_prepare_chip(dac33->substream, component); + } + break; + case SND_SOC_DAPM_POST_PMD: + dac33_disable_digital(component); + break; + } + return 0; +} + +static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dac33->fifo_mode; + + return 0; +} + +static int dac33_set_fifo_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + + if (dac33->fifo_mode == ucontrol->value.enumerated.item[0]) + return 0; + /* Do not allow changes while stream is running*/ + if (snd_soc_component_is_active(component)) + return -EPERM; + + if (ucontrol->value.enumerated.item[0] >= DAC33_FIFO_LAST_MODE) + ret = -EINVAL; + else + dac33->fifo_mode = ucontrol->value.enumerated.item[0]; + + return ret; +} + +/* Codec operation modes */ +static const char *dac33_fifo_mode_texts[] = { + "Bypass", "Mode 1", "Mode 7" +}; + +static SOC_ENUM_SINGLE_EXT_DECL(dac33_fifo_mode_enum, dac33_fifo_mode_texts); + +/* L/R Line Output Gain */ +static const char *lr_lineout_gain_texts[] = { + "Line -12dB DAC 0dB", "Line -6dB DAC 6dB", + "Line 0dB DAC 12dB", "Line 6dB DAC 18dB", +}; + +static SOC_ENUM_SINGLE_DECL(l_lineout_gain_enum, + DAC33_LDAC_PWR_CTRL, 0, + lr_lineout_gain_texts); + +static SOC_ENUM_SINGLE_DECL(r_lineout_gain_enum, + DAC33_RDAC_PWR_CTRL, 0, + lr_lineout_gain_texts); + +/* + * DACL/R digital volume control: + * from 0 dB to -63.5 in 0.5 dB steps + * Need to be inverted later on: + * 0x00 == 0 dB + * 0x7f == -63.5 dB + */ +static DECLARE_TLV_DB_SCALE(dac_digivol_tlv, -6350, 50, 0); + +static const struct snd_kcontrol_new dac33_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC Digital Playback Volume", + DAC33_LDAC_DIG_VOL_CTRL, DAC33_RDAC_DIG_VOL_CTRL, + 0, 0x7f, 1, dac_digivol_tlv), + SOC_DOUBLE_R("DAC Digital Playback Switch", + DAC33_LDAC_DIG_VOL_CTRL, DAC33_RDAC_DIG_VOL_CTRL, 7, 1, 1), + SOC_DOUBLE_R("Line to Line Out Volume", + DAC33_LINEL_TO_LLO_VOL, DAC33_LINER_TO_RLO_VOL, 0, 127, 1), + SOC_ENUM("Left Line Output Gain", l_lineout_gain_enum), + SOC_ENUM("Right Line Output Gain", r_lineout_gain_enum), +}; + +static const struct snd_kcontrol_new dac33_mode_snd_controls[] = { + SOC_ENUM_EXT("FIFO Mode", dac33_fifo_mode_enum, + dac33_get_fifo_mode, dac33_set_fifo_mode), +}; + +/* Analog bypass */ +static const struct snd_kcontrol_new dac33_dapm_abypassl_control = + SOC_DAPM_SINGLE("Switch", DAC33_LINEL_TO_LLO_VOL, 7, 1, 1); + +static const struct snd_kcontrol_new dac33_dapm_abypassr_control = + SOC_DAPM_SINGLE("Switch", DAC33_LINER_TO_RLO_VOL, 7, 1, 1); + +/* LOP L/R invert selection */ +static const char *dac33_lr_lom_texts[] = {"DAC", "LOP"}; + +static SOC_ENUM_SINGLE_DECL(dac33_left_lom_enum, + DAC33_OUT_AMP_CTRL, 3, + dac33_lr_lom_texts); + +static const struct snd_kcontrol_new dac33_dapm_left_lom_control = +SOC_DAPM_ENUM("Route", dac33_left_lom_enum); + +static SOC_ENUM_SINGLE_DECL(dac33_right_lom_enum, + DAC33_OUT_AMP_CTRL, 2, + dac33_lr_lom_texts); + +static const struct snd_kcontrol_new dac33_dapm_right_lom_control = +SOC_DAPM_ENUM("Route", dac33_right_lom_enum); + +static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("LEFT_LO"), + SND_SOC_DAPM_OUTPUT("RIGHT_LO"), + + SND_SOC_DAPM_INPUT("LINEL"), + SND_SOC_DAPM_INPUT("LINER"), + + SND_SOC_DAPM_DAC("DACL", "Left Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DACR", "Right Playback", SND_SOC_NOPM, 0, 0), + + /* Analog bypass */ + SND_SOC_DAPM_SWITCH("Analog Left Bypass", SND_SOC_NOPM, 0, 0, + &dac33_dapm_abypassl_control), + SND_SOC_DAPM_SWITCH("Analog Right Bypass", SND_SOC_NOPM, 0, 0, + &dac33_dapm_abypassr_control), + + SND_SOC_DAPM_MUX("Left LOM Inverted From", SND_SOC_NOPM, 0, 0, + &dac33_dapm_left_lom_control), + SND_SOC_DAPM_MUX("Right LOM Inverted From", SND_SOC_NOPM, 0, 0, + &dac33_dapm_right_lom_control), + /* + * For DAPM path, when only the anlog bypass path is enabled, and the + * LOP inverted from the corresponding DAC side. + * This is needed, so we can attach the DAC power supply in this case. + */ + SND_SOC_DAPM_PGA("Left Bypass PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Bypass PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Left Amplifier", + DAC33_OUT_AMP_PWR_CTRL, 6, 3, 3, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amplifier", + DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0), + + SND_SOC_DAPM_SUPPLY("Left DAC Power", + DAC33_LDAC_PWR_CTRL, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right DAC Power", + DAC33_RDAC_PWR_CTRL, 2, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Codec Power", + DAC33_PWR_CTRL, 4, 0, NULL, 0), + + SND_SOC_DAPM_PRE("Pre Playback", dac33_playback_event), + SND_SOC_DAPM_POST("Post Playback", dac33_playback_event), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Analog bypass */ + {"Analog Left Bypass", "Switch", "LINEL"}, + {"Analog Right Bypass", "Switch", "LINER"}, + + {"Output Left Amplifier", NULL, "DACL"}, + {"Output Right Amplifier", NULL, "DACR"}, + + {"Left Bypass PGA", NULL, "Analog Left Bypass"}, + {"Right Bypass PGA", NULL, "Analog Right Bypass"}, + + {"Left LOM Inverted From", "DAC", "Left Bypass PGA"}, + {"Right LOM Inverted From", "DAC", "Right Bypass PGA"}, + {"Left LOM Inverted From", "LOP", "Analog Left Bypass"}, + {"Right LOM Inverted From", "LOP", "Analog Right Bypass"}, + + {"Output Left Amplifier", NULL, "Left LOM Inverted From"}, + {"Output Right Amplifier", NULL, "Right LOM Inverted From"}, + + {"DACL", NULL, "Left DAC Power"}, + {"DACR", NULL, "Right DAC Power"}, + + {"Left Bypass PGA", NULL, "Left DAC Power"}, + {"Right Bypass PGA", NULL, "Right DAC Power"}, + + /* output */ + {"LEFT_LO", NULL, "Output Left Amplifier"}, + {"RIGHT_LO", NULL, "Output Right Amplifier"}, + + {"LEFT_LO", NULL, "Codec Power"}, + {"RIGHT_LO", NULL, "Codec Power"}, +}; + +static int dac33_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Coming from OFF, switch on the component */ + ret = dac33_hard_power(component, 1); + if (ret != 0) + return ret; + + dac33_init_chip(component); + } + break; + case SND_SOC_BIAS_OFF: + /* Do not power off, when the component is already off */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + return 0; + ret = dac33_hard_power(component, 0); + if (ret != 0) + return ret; + break; + } + + return 0; +} + +static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) +{ + struct snd_soc_component *component = dac33->component; + unsigned int delay; + unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + dac33_write16(component, DAC33_NSAMPLE_MSB, + DAC33_THRREG(dac33->nsample)); + + /* Take the timestamps */ + spin_lock_irqsave(&dac33->lock, flags); + dac33->t_stamp2 = ktime_to_us(ktime_get()); + dac33->t_stamp1 = dac33->t_stamp2; + spin_unlock_irqrestore(&dac33->lock, flags); + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(dac33->alarm_threshold)); + /* Enable Alarm Threshold IRQ with a delay */ + delay = SAMPLES_TO_US(dac33->burst_rate, + dac33->alarm_threshold) + 1000; + usleep_range(delay, delay + 500); + dac33_write(component, DAC33_FIFO_IRQ_MASK, DAC33_MAT); + break; + case DAC33_FIFO_MODE7: + /* Take the timestamp */ + spin_lock_irqsave(&dac33->lock, flags); + dac33->t_stamp1 = ktime_to_us(ktime_get()); + /* Move back the timestamp with drain time */ + dac33->t_stamp1 -= dac33->mode7_us_to_lthr; + spin_unlock_irqrestore(&dac33->lock, flags); + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(DAC33_MODE7_MARGIN)); + + /* Enable Upper Threshold IRQ */ + dac33_write(component, DAC33_FIFO_IRQ_MASK, DAC33_MUT); + break; + default: + dev_warn(component->dev, "Unhandled FIFO mode: %d\n", + dac33->fifo_mode); + break; + } +} + +static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) +{ + struct snd_soc_component *component = dac33->component; + unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* Take the timestamp */ + spin_lock_irqsave(&dac33->lock, flags); + dac33->t_stamp2 = ktime_to_us(ktime_get()); + spin_unlock_irqrestore(&dac33->lock, flags); + + dac33_write16(component, DAC33_NSAMPLE_MSB, + DAC33_THRREG(dac33->nsample)); + break; + case DAC33_FIFO_MODE7: + /* At the moment we are not using interrupts in mode7 */ + break; + default: + dev_warn(component->dev, "Unhandled FIFO mode: %d\n", + dac33->fifo_mode); + break; + } +} + +static void dac33_work(struct work_struct *work) +{ + struct snd_soc_component *component; + struct tlv320dac33_priv *dac33; + u8 reg; + + dac33 = container_of(work, struct tlv320dac33_priv, work); + component = dac33->component; + + mutex_lock(&dac33->mutex); + switch (dac33->state) { + case DAC33_PREFILL: + dac33->state = DAC33_PLAYBACK; + dac33_prefill_handler(dac33); + break; + case DAC33_PLAYBACK: + dac33_playback_handler(dac33); + break; + case DAC33_IDLE: + break; + case DAC33_FLUSH: + dac33->state = DAC33_IDLE; + /* Mask all interrupts from dac33 */ + dac33_write(component, DAC33_FIFO_IRQ_MASK, 0); + + /* flush fifo */ + reg = dac33_read_reg_cache(component, DAC33_FIFO_CTRL_A); + reg |= DAC33_FIFOFLUSH; + dac33_write(component, DAC33_FIFO_CTRL_A, reg); + break; + } + mutex_unlock(&dac33->mutex); +} + +static irqreturn_t dac33_interrupt_handler(int irq, void *dev) +{ + struct snd_soc_component *component = dev; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + unsigned long flags; + + spin_lock_irqsave(&dac33->lock, flags); + dac33->t_stamp1 = ktime_to_us(ktime_get()); + spin_unlock_irqrestore(&dac33->lock, flags); + + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) + schedule_work(&dac33->work); + + return IRQ_HANDLED; +} + +static void dac33_oscwait(struct snd_soc_component *component) +{ + int timeout = 60; + u8 reg; + + do { + usleep_range(1000, 2000); + dac33_read(component, DAC33_INT_OSC_STATUS, ®); + } while (((reg & 0x03) != DAC33_OSCSTATUS_NORMAL) && timeout--); + if ((reg & 0x03) != DAC33_OSCSTATUS_NORMAL) + dev_err(component->dev, + "internal oscillator calibration failed\n"); +} + +static int dac33_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + + /* Stream started, save the substream pointer */ + dac33->substream = substream; + + return 0; +} + +static void dac33_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + + dac33->substream = NULL; +} + +#define CALC_BURST_RATE(bclkdiv, bclk_per_sample) \ + (BURST_BASEFREQ_HZ / bclkdiv / bclk_per_sample) +static int dac33_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + + /* Check parameters for validity */ + switch (params_rate(params)) { + case 44100: + case 48000: + break; + default: + dev_err(component->dev, "unsupported rate %d\n", + params_rate(params)); + return -EINVAL; + } + + switch (params_width(params)) { + case 16: + dac33->fifo_size = DAC33_FIFO_SIZE_16BIT; + dac33->burst_rate = CALC_BURST_RATE(dac33->burst_bclkdiv, 32); + break; + case 32: + dac33->fifo_size = DAC33_FIFO_SIZE_24BIT; + dac33->burst_rate = CALC_BURST_RATE(dac33->burst_bclkdiv, 64); + break; + default: + dev_err(component->dev, "unsupported width %d\n", + params_width(params)); + return -EINVAL; + } + + return 0; +} + +#define CALC_OSCSET(rate, refclk) ( \ + ((((rate * 10000) / refclk) * 4096) + 7000) / 10000) +#define CALC_RATIOSET(rate, refclk) ( \ + ((((refclk * 100000) / rate) * 16384) + 50000) / 100000) + +/* + * tlv320dac33 is strict on the sequence of the register writes, if the register + * writes happens in different order, than dac33 might end up in unknown state. + * Use the known, working sequence of register writes to initialize the dac33. + */ +static int dac33_prepare_chip(struct snd_pcm_substream *substream, + struct snd_soc_component *component) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + unsigned int oscset, ratioset, pwr_ctrl, reg_tmp; + u8 aictrl_a, aictrl_b, fifoctrl_a; + + switch (substream->runtime->rate) { + case 44100: + case 48000: + oscset = CALC_OSCSET(substream->runtime->rate, dac33->refclk); + ratioset = CALC_RATIOSET(substream->runtime->rate, + dac33->refclk); + break; + default: + dev_err(component->dev, "unsupported rate %d\n", + substream->runtime->rate); + return -EINVAL; + } + + + aictrl_a = dac33_read_reg_cache(component, DAC33_SER_AUDIOIF_CTRL_A); + aictrl_a &= ~(DAC33_NCYCL_MASK | DAC33_WLEN_MASK); + /* Read FIFO control A, and clear FIFO flush bit */ + fifoctrl_a = dac33_read_reg_cache(component, DAC33_FIFO_CTRL_A); + fifoctrl_a &= ~DAC33_FIFOFLUSH; + + fifoctrl_a &= ~DAC33_WIDTH; + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + aictrl_a |= (DAC33_NCYCL_16 | DAC33_WLEN_16); + fifoctrl_a |= DAC33_WIDTH; + break; + case SNDRV_PCM_FORMAT_S32_LE: + aictrl_a |= (DAC33_NCYCL_32 | DAC33_WLEN_24); + break; + default: + dev_err(component->dev, "unsupported format %d\n", + substream->runtime->format); + return -EINVAL; + } + + mutex_lock(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ + mutex_unlock(&dac33->mutex); + return 0; + } + + dac33_soft_power(component, 0); + dac33_soft_power(component, 1); + + reg_tmp = dac33_read_reg_cache(component, DAC33_INT_OSC_CTRL); + dac33_write(component, DAC33_INT_OSC_CTRL, reg_tmp); + + /* Write registers 0x08 and 0x09 (MSB, LSB) */ + dac33_write16(component, DAC33_INT_OSC_FREQ_RAT_A, oscset); + + /* OSC calibration time */ + dac33_write(component, DAC33_CALIB_TIME, 96); + + /* adjustment treshold & step */ + dac33_write(component, DAC33_INT_OSC_CTRL_B, DAC33_ADJTHRSHLD(2) | + DAC33_ADJSTEP(1)); + + /* div=4 / gain=1 / div */ + dac33_write(component, DAC33_INT_OSC_CTRL_C, DAC33_REFDIV(4)); + + pwr_ctrl = dac33_read_reg_cache(component, DAC33_PWR_CTRL); + pwr_ctrl |= DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB; + dac33_write(component, DAC33_PWR_CTRL, pwr_ctrl); + + dac33_oscwait(component); + + if (dac33->fifo_mode) { + /* Generic for all FIFO modes */ + /* 50-51 : ASRC Control registers */ + dac33_write(component, DAC33_ASRC_CTRL_A, DAC33_SRCLKDIV(1)); + dac33_write(component, DAC33_ASRC_CTRL_B, 1); /* ??? */ + + /* Write registers 0x34 and 0x35 (MSB, LSB) */ + dac33_write16(component, DAC33_SRC_REF_CLK_RATIO_A, ratioset); + + /* Set interrupts to high active */ + dac33_write(component, DAC33_INTP_CTRL_A, DAC33_INTPM_AHIGH); + } else { + /* FIFO bypass mode */ + /* 50-51 : ASRC Control registers */ + dac33_write(component, DAC33_ASRC_CTRL_A, DAC33_SRCBYP); + dac33_write(component, DAC33_ASRC_CTRL_B, 0); /* ??? */ + } + + /* Interrupt behaviour configuration */ + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + dac33_write(component, DAC33_FIFO_IRQ_MODE_B, + DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL)); + break; + case DAC33_FIFO_MODE7: + dac33_write(component, DAC33_FIFO_IRQ_MODE_A, + DAC33_UTM(DAC33_FIFO_IRQ_MODE_LEVEL)); + break; + default: + /* in FIFO bypass mode, the interrupts are not used */ + break; + } + + aictrl_b = dac33_read_reg_cache(component, DAC33_SER_AUDIOIF_CTRL_B); + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* + * For mode1: + * Disable the FIFO bypass (Enable the use of FIFO) + * Select nSample mode + * BCLK is only running when data is needed by DAC33 + */ + fifoctrl_a &= ~DAC33_FBYPAS; + fifoctrl_a &= ~DAC33_FAUTO; + if (dac33->keep_bclk) + aictrl_b |= DAC33_BCLKON; + else + aictrl_b &= ~DAC33_BCLKON; + break; + case DAC33_FIFO_MODE7: + /* + * For mode1: + * Disable the FIFO bypass (Enable the use of FIFO) + * Select Threshold mode + * BCLK is only running when data is needed by DAC33 + */ + fifoctrl_a &= ~DAC33_FBYPAS; + fifoctrl_a |= DAC33_FAUTO; + if (dac33->keep_bclk) + aictrl_b |= DAC33_BCLKON; + else + aictrl_b &= ~DAC33_BCLKON; + break; + default: + /* + * For FIFO bypass mode: + * Enable the FIFO bypass (Disable the FIFO use) + * Set the BCLK as continuous + */ + fifoctrl_a |= DAC33_FBYPAS; + aictrl_b |= DAC33_BCLKON; + break; + } + + dac33_write(component, DAC33_FIFO_CTRL_A, fifoctrl_a); + dac33_write(component, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a); + dac33_write(component, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b); + + /* + * BCLK divide ratio + * 0: 1.5 + * 1: 1 + * 2: 2 + * ... + * 254: 254 + * 255: 255 + */ + if (dac33->fifo_mode) + dac33_write(component, DAC33_SER_AUDIOIF_CTRL_C, + dac33->burst_bclkdiv); + else + if (substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE) + dac33_write(component, DAC33_SER_AUDIOIF_CTRL_C, 32); + else + dac33_write(component, DAC33_SER_AUDIOIF_CTRL_C, 16); + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + dac33_write16(component, DAC33_ATHR_MSB, + DAC33_THRREG(dac33->alarm_threshold)); + break; + case DAC33_FIFO_MODE7: + /* + * Configure the threshold levels, and leave 10 sample space + * at the bottom, and also at the top of the FIFO + */ + dac33_write16(component, DAC33_UTHR_MSB, DAC33_THRREG(dac33->uthr)); + dac33_write16(component, DAC33_LTHR_MSB, + DAC33_THRREG(DAC33_MODE7_MARGIN)); + break; + default: + break; + } + + mutex_unlock(&dac33->mutex); + + return 0; +} + +static void dac33_calculate_times(struct snd_pcm_substream *substream, + struct snd_soc_component *component) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + unsigned int period_size = substream->runtime->period_size; + unsigned int rate = substream->runtime->rate; + unsigned int nsample_limit; + + /* In bypass mode we don't need to calculate */ + if (!dac33->fifo_mode) + return; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* Number of samples under i2c latency */ + dac33->alarm_threshold = US_TO_SAMPLES(rate, + dac33->mode1_latency); + nsample_limit = dac33->fifo_size - dac33->alarm_threshold; + + if (period_size <= dac33->alarm_threshold) + /* + * Configure nSamaple to number of periods, + * which covers the latency requironment. + */ + dac33->nsample = period_size * + ((dac33->alarm_threshold / period_size) + + (dac33->alarm_threshold % period_size ? + 1 : 0)); + else if (period_size > nsample_limit) + dac33->nsample = nsample_limit; + else + dac33->nsample = period_size; + + dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate, + dac33->nsample); + dac33->t_stamp1 = 0; + dac33->t_stamp2 = 0; + break; + case DAC33_FIFO_MODE7: + dac33->uthr = UTHR_FROM_PERIOD_SIZE(period_size, rate, + dac33->burst_rate) + 9; + if (dac33->uthr > (dac33->fifo_size - DAC33_MODE7_MARGIN)) + dac33->uthr = dac33->fifo_size - DAC33_MODE7_MARGIN; + if (dac33->uthr < (DAC33_MODE7_MARGIN + 10)) + dac33->uthr = (DAC33_MODE7_MARGIN + 10); + + dac33->mode7_us_to_lthr = + SAMPLES_TO_US(substream->runtime->rate, + dac33->uthr - DAC33_MODE7_MARGIN + 1); + dac33->t_stamp1 = 0; + break; + default: + break; + } + +} + +static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (dac33->fifo_mode) { + dac33->state = DAC33_PREFILL; + schedule_work(&dac33->work); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (dac33->fifo_mode) { + dac33->state = DAC33_FLUSH; + schedule_work(&dac33->work); + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_sframes_t dac33_dai_delay( + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + unsigned long long t0, t1, t_now; + unsigned int time_delta, uthr; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; + unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: + spin_lock_irqsave(&dac33->lock, flags); + t0 = dac33->t_stamp1; + t1 = dac33->t_stamp2; + spin_unlock_irqrestore(&dac33->lock, flags); + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) + goto out; + + if (t0 > t1) { + /* + * Phase 1: + * After Alarm threshold, and before nSample write + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + if (likely(dac33->alarm_threshold > samples_out)) + delay = dac33->alarm_threshold - samples_out; + else + delay = 0; + } else if ((t_now - t1) <= dac33->mode1_us_burst) { + /* + * Phase 2: + * After nSample write (during burst operation) + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + time_delta = t_now - t1; + samples_in = time_delta ? US_TO_SAMPLES( + dac33->burst_rate, + time_delta) : 0; + + samples = dac33->alarm_threshold; + samples += (samples_in - samples_out); + + if (likely(samples > 0)) + delay = samples; + else + delay = 0; + } else { + /* + * Phase 3: + * After burst operation, before next alarm threshold + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + samples_in = dac33->nsample; + samples = dac33->alarm_threshold; + samples += (samples_in - samples_out); + + if (likely(samples > 0)) + delay = samples > dac33->fifo_size ? + dac33->fifo_size : samples; + else + delay = 0; + } + break; + case DAC33_FIFO_MODE7: + spin_lock_irqsave(&dac33->lock, flags); + t0 = dac33->t_stamp1; + uthr = dac33->uthr; + spin_unlock_irqrestore(&dac33->lock, flags); + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) + goto out; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ + delay = uthr; + goto out; + } + + time_delta = t_now - t0; + if (time_delta <= dac33->mode7_us_to_lthr) { + /* + * Phase 1: + * After burst (draining phase) + */ + samples_out = US_TO_SAMPLES( + substream->runtime->rate, + time_delta); + + if (likely(uthr > samples_out)) + delay = uthr - samples_out; + else + delay = 0; + } else { + /* + * Phase 2: + * During burst operation + */ + time_delta = time_delta - dac33->mode7_us_to_lthr; + + samples_out = US_TO_SAMPLES( + substream->runtime->rate, + time_delta); + samples_in = US_TO_SAMPLES( + dac33->burst_rate, + time_delta); + delay = DAC33_MODE7_MARGIN + samples_in - samples_out; + + if (unlikely(delay > uthr)) + delay = uthr; + } + break; + default: + dev_warn(component->dev, "Unhandled FIFO mode: %d\n", + dac33->fifo_mode); + break; + } +out: + return delay; +} + +static int dac33_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + u8 ioc_reg, asrcb_reg; + + ioc_reg = dac33_read_reg_cache(component, DAC33_INT_OSC_CTRL); + asrcb_reg = dac33_read_reg_cache(component, DAC33_ASRC_CTRL_B); + switch (clk_id) { + case TLV320DAC33_MCLK: + ioc_reg |= DAC33_REFSEL; + asrcb_reg |= DAC33_SRCREFSEL; + break; + case TLV320DAC33_SLEEPCLK: + ioc_reg &= ~DAC33_REFSEL; + asrcb_reg &= ~DAC33_SRCREFSEL; + break; + default: + dev_err(component->dev, "Invalid clock ID (%d)\n", clk_id); + break; + } + dac33->refclk = freq; + + dac33_write_reg_cache(component, DAC33_INT_OSC_CTRL, ioc_reg); + dac33_write_reg_cache(component, DAC33_ASRC_CTRL_B, asrcb_reg); + + return 0; +} + +static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + u8 aictrl_a, aictrl_b; + + aictrl_a = dac33_read_reg_cache(component, DAC33_SER_AUDIOIF_CTRL_A); + aictrl_b = dac33_read_reg_cache(component, DAC33_SER_AUDIOIF_CTRL_B); + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* Codec Master */ + aictrl_a |= (DAC33_MSBCLK | DAC33_MSWCLK); + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* Codec Slave */ + if (dac33->fifo_mode) { + dev_err(component->dev, "FIFO mode requires master mode\n"); + return -EINVAL; + } else + aictrl_a &= ~(DAC33_MSBCLK | DAC33_MSWCLK); + break; + default: + return -EINVAL; + } + + aictrl_a &= ~DAC33_AFMT_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aictrl_a |= DAC33_AFMT_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + aictrl_a |= DAC33_AFMT_DSP; + aictrl_b &= ~DAC33_DATA_DELAY_MASK; + aictrl_b |= DAC33_DATA_DELAY(0); + break; + case SND_SOC_DAIFMT_RIGHT_J: + aictrl_a |= DAC33_AFMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + aictrl_a |= DAC33_AFMT_LEFT_J; + break; + default: + dev_err(component->dev, "Unsupported format (%u)\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + dac33_write_reg_cache(component, DAC33_SER_AUDIOIF_CTRL_A, aictrl_a); + dac33_write_reg_cache(component, DAC33_SER_AUDIOIF_CTRL_B, aictrl_b); + + return 0; +} + +static int dac33_soc_probe(struct snd_soc_component *component) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + + dac33->component = component; + + /* Read the tlv320dac33 ID registers */ + ret = dac33_hard_power(component, 1); + if (ret != 0) { + dev_err(component->dev, "Failed to power up component: %d\n", ret); + goto err_power; + } + ret = dac33_read_id(component); + dac33_hard_power(component, 0); + + if (ret < 0) { + dev_err(component->dev, "Failed to read chip ID: %d\n", ret); + ret = -ENODEV; + goto err_power; + } + + /* Check if the IRQ number is valid and request it */ + if (dac33->irq >= 0) { + ret = request_irq(dac33->irq, dac33_interrupt_handler, + IRQF_TRIGGER_RISING, + component->name, component); + if (ret < 0) { + dev_err(component->dev, "Could not request IRQ%d (%d)\n", + dac33->irq, ret); + dac33->irq = -1; + } + if (dac33->irq != -1) { + INIT_WORK(&dac33->work, dac33_work); + } + } + + /* Only add the FIFO controls, if we have valid IRQ number */ + if (dac33->irq >= 0) + snd_soc_add_component_controls(component, dac33_mode_snd_controls, + ARRAY_SIZE(dac33_mode_snd_controls)); + +err_power: + return ret; +} + +static void dac33_soc_remove(struct snd_soc_component *component) +{ + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + + if (dac33->irq >= 0) { + free_irq(dac33->irq, dac33->component); + flush_work(&dac33->work); + } +} + +static const struct snd_soc_component_driver soc_component_dev_tlv320dac33 = { + .read = dac33_read_reg_cache, + .write = dac33_write_locked, + .set_bias_level = dac33_set_bias_level, + .probe = dac33_soc_probe, + .remove = dac33_soc_remove, + .controls = dac33_snd_controls, + .num_controls = ARRAY_SIZE(dac33_snd_controls), + .dapm_widgets = dac33_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dac33_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +#define DAC33_RATES (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) +#define DAC33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops dac33_dai_ops = { + .startup = dac33_startup, + .shutdown = dac33_shutdown, + .hw_params = dac33_hw_params, + .trigger = dac33_pcm_trigger, + .delay = dac33_dai_delay, + .set_sysclk = dac33_set_dai_sysclk, + .set_fmt = dac33_set_dai_fmt, +}; + +static struct snd_soc_dai_driver dac33_dai = { + .name = "tlv320dac33-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = DAC33_RATES, + .formats = DAC33_FORMATS, + .sig_bits = 24, + }, + .ops = &dac33_dai_ops, +}; + +static int dac33_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tlv320dac33_platform_data *pdata; + struct tlv320dac33_priv *dac33; + int ret, i; + + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "Platform data not set\n"); + return -ENODEV; + } + pdata = client->dev.platform_data; + + dac33 = devm_kzalloc(&client->dev, sizeof(struct tlv320dac33_priv), + GFP_KERNEL); + if (dac33 == NULL) + return -ENOMEM; + + dac33->reg_cache = devm_kmemdup(&client->dev, + dac33_reg, + ARRAY_SIZE(dac33_reg) * sizeof(u8), + GFP_KERNEL); + if (!dac33->reg_cache) + return -ENOMEM; + + dac33->i2c = client; + mutex_init(&dac33->mutex); + spin_lock_init(&dac33->lock); + + i2c_set_clientdata(client, dac33); + + dac33->power_gpio = pdata->power_gpio; + dac33->burst_bclkdiv = pdata->burst_bclkdiv; + dac33->keep_bclk = pdata->keep_bclk; + dac33->mode1_latency = pdata->mode1_latency; + if (!dac33->mode1_latency) + dac33->mode1_latency = 10000; /* 10ms */ + dac33->irq = client->irq; + /* Disable FIFO use by default */ + dac33->fifo_mode = DAC33_FIFO_BYPASS; + + /* Check if the reset GPIO number is valid and request it */ + if (dac33->power_gpio >= 0) { + ret = gpio_request(dac33->power_gpio, "tlv320dac33 reset"); + if (ret < 0) { + dev_err(&client->dev, + "Failed to request reset GPIO (%d)\n", + dac33->power_gpio); + goto err_gpio; + } + gpio_direction_output(dac33->power_gpio, 0); + } + + for (i = 0; i < ARRAY_SIZE(dac33->supplies); i++) + dac33->supplies[i].supply = dac33_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, ARRAY_SIZE(dac33->supplies), + dac33->supplies); + + if (ret != 0) { + dev_err(&client->dev, "Failed to request supplies: %d\n", ret); + goto err_get; + } + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_dev_tlv320dac33, &dac33_dai, 1); + if (ret < 0) + goto err_get; + + return ret; +err_get: + if (dac33->power_gpio >= 0) + gpio_free(dac33->power_gpio); +err_gpio: + return ret; +} + +static int dac33_i2c_remove(struct i2c_client *client) +{ + struct tlv320dac33_priv *dac33 = i2c_get_clientdata(client); + + if (unlikely(dac33->chip_power)) + dac33_hard_power(dac33->component, 0); + + if (dac33->power_gpio >= 0) + gpio_free(dac33->power_gpio); + + return 0; +} + +static const struct i2c_device_id tlv320dac33_i2c_id[] = { + { + .name = "tlv320dac33", + .driver_data = 0, + }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, tlv320dac33_i2c_id); + +static struct i2c_driver tlv320dac33_i2c_driver = { + .driver = { + .name = "tlv320dac33-codec", + }, + .probe = dac33_i2c_probe, + .remove = dac33_i2c_remove, + .id_table = tlv320dac33_i2c_id, +}; + +module_i2c_driver(tlv320dac33_i2c_driver); + +MODULE_DESCRIPTION("ASoC TLV320DAC33 codec driver"); +MODULE_AUTHOR("Peter Ujfalusi "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320dac33.h b/sound/soc/codecs/tlv320dac33.h new file mode 100644 index 000000000..ed6967074 --- /dev/null +++ b/sound/soc/codecs/tlv320dac33.h @@ -0,0 +1,264 @@ +/* + * ALSA SoC Texas Instruments TLV320DAC33 codec driver + * + * Author: Peter Ujfalusi + * + * Copyright: (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TLV320DAC33_H +#define __TLV320DAC33_H + +#define DAC33_PAGE_SELECT 0x00 +#define DAC33_PWR_CTRL 0x01 +#define DAC33_PLL_CTRL_A 0x02 +#define DAC33_PLL_CTRL_B 0x03 +#define DAC33_PLL_CTRL_C 0x04 +#define DAC33_PLL_CTRL_D 0x05 +#define DAC33_PLL_CTRL_E 0x06 +#define DAC33_INT_OSC_CTRL 0x07 +#define DAC33_INT_OSC_FREQ_RAT_A 0x08 +#define DAC33_INT_OSC_FREQ_RAT_B 0x09 +#define DAC33_INT_OSC_DAC_RATIO_SET 0x0A +#define DAC33_CALIB_TIME 0x0B +#define DAC33_INT_OSC_CTRL_B 0x0C +#define DAC33_INT_OSC_CTRL_C 0x0D +#define DAC33_INT_OSC_STATUS 0x0E +#define DAC33_INT_OSC_DAC_RATIO_READ 0x0F +#define DAC33_INT_OSC_FREQ_RAT_READ_A 0x10 +#define DAC33_INT_OSC_FREQ_RAT_READ_B 0x11 +#define DAC33_SER_AUDIOIF_CTRL_A 0x12 +#define DAC33_SER_AUDIOIF_CTRL_B 0x13 +#define DAC33_SER_AUDIOIF_CTRL_C 0x14 +#define DAC33_FIFO_CTRL_A 0x15 +#define DAC33_UTHR_MSB 0x16 +#define DAC33_UTHR_LSB 0x17 +#define DAC33_ATHR_MSB 0x18 +#define DAC33_ATHR_LSB 0x19 +#define DAC33_LTHR_MSB 0x1A +#define DAC33_LTHR_LSB 0x1B +#define DAC33_PREFILL_MSB 0x1C +#define DAC33_PREFILL_LSB 0x1D +#define DAC33_NSAMPLE_MSB 0x1E +#define DAC33_NSAMPLE_LSB 0x1F +#define DAC33_FIFO_WPTR_MSB 0x20 +#define DAC33_FIFO_WPTR_LSB 0x21 +#define DAC33_FIFO_RPTR_MSB 0x22 +#define DAC33_FIFO_RPTR_LSB 0x23 +#define DAC33_FIFO_DEPTH_MSB 0x24 +#define DAC33_FIFO_DEPTH_LSB 0x25 +#define DAC33_SAMPLES_REMAINING_MSB 0x26 +#define DAC33_SAMPLES_REMAINING_LSB 0x27 +#define DAC33_FIFO_IRQ_FLAG 0x28 +#define DAC33_FIFO_IRQ_MASK 0x29 +#define DAC33_FIFO_IRQ_MODE_A 0x2A +#define DAC33_FIFO_IRQ_MODE_B 0x2B +#define DAC33_DAC_CTRL_A 0x2C +#define DAC33_DAC_CTRL_B 0x2D +#define DAC33_DAC_CTRL_C 0x2E +#define DAC33_LDAC_DIG_VOL_CTRL 0x2F +#define DAC33_RDAC_DIG_VOL_CTRL 0x30 +#define DAC33_DAC_STATUS_FLAGS 0x31 +#define DAC33_ASRC_CTRL_A 0x32 +#define DAC33_ASRC_CTRL_B 0x33 +#define DAC33_SRC_REF_CLK_RATIO_A 0x34 +#define DAC33_SRC_REF_CLK_RATIO_B 0x35 +#define DAC33_SRC_EST_REF_CLK_RATIO_A 0x36 +#define DAC33_SRC_EST_REF_CLK_RATIO_B 0x37 +#define DAC33_INTP_CTRL_A 0x38 +#define DAC33_INTP_CTRL_B 0x39 +/* Registers 0x3A - 0x3F Reserved */ +#define DAC33_LDAC_PWR_CTRL 0x40 +#define DAC33_RDAC_PWR_CTRL 0x41 +#define DAC33_OUT_AMP_CM_CTRL 0x42 +#define DAC33_OUT_AMP_PWR_CTRL 0x43 +#define DAC33_OUT_AMP_CTRL 0x44 +#define DAC33_LINEL_TO_LLO_VOL 0x45 +/* Registers 0x45 - 0x47 Reserved */ +#define DAC33_LINER_TO_RLO_VOL 0x48 +#define DAC33_ANA_VOL_SOFT_STEP_CTRL 0x49 +#define DAC33_OSC_TRIM 0x4A +/* Registers 0x4B - 0x7C Reserved */ +#define DAC33_DEVICE_ID_MSB 0x7D +#define DAC33_DEVICE_ID_LSB 0x7E +#define DAC33_DEVICE_REV_ID 0x7F + +#define DAC33_CACHEREGNUM 128 + +/* Bit definitions */ + +/* DAC33_PWR_CTRL (0x01) */ +#define DAC33_DACRPDNB (0x01 << 0) +#define DAC33_DACLPDNB (0x01 << 1) +#define DAC33_OSCPDNB (0x01 << 2) +#define DAC33_PLLPDNB (0x01 << 3) +#define DAC33_PDNALLB (0x01 << 4) +#define DAC33_SOFT_RESET (0x01 << 7) + +/* DAC33_INT_OSC_CTRL (0x07) */ +#define DAC33_REFSEL (0x01 << 1) + +/* DAC33_INT_OSC_CTRL_B (0x0C) */ +#define DAC33_ADJSTEP(x) (x << 0) +#define DAC33_ADJTHRSHLD(x) (x << 4) + +/* DAC33_INT_OSC_CTRL_C (0x0D) */ +#define DAC33_REFDIV(x) (x << 4) + +/* DAC33_INT_OSC_STATUS (0x0E) */ +#define DAC33_OSCSTATUS_IDLE_CALIB (0x00) +#define DAC33_OSCSTATUS_NORMAL (0x01) +#define DAC33_OSCSTATUS_ADJUSTMENT (0x03) +#define DAC33_OSCSTATUS_NOT_USED (0x02) + +/* DAC33_SER_AUDIOIF_CTRL_A (0x12) */ +#define DAC33_MSWCLK (0x01 << 0) +#define DAC33_MSBCLK (0x01 << 1) +#define DAC33_AFMT_MASK (0x03 << 2) +#define DAC33_AFMT_I2S (0x00 << 2) +#define DAC33_AFMT_DSP (0x01 << 2) +#define DAC33_AFMT_RIGHT_J (0x02 << 2) +#define DAC33_AFMT_LEFT_J (0x03 << 2) +#define DAC33_WLEN_MASK (0x03 << 4) +#define DAC33_WLEN_16 (0x00 << 4) +#define DAC33_WLEN_20 (0x01 << 4) +#define DAC33_WLEN_24 (0x02 << 4) +#define DAC33_WLEN_32 (0x03 << 4) +#define DAC33_NCYCL_MASK (0x03 << 6) +#define DAC33_NCYCL_16 (0x00 << 6) +#define DAC33_NCYCL_20 (0x01 << 6) +#define DAC33_NCYCL_24 (0x02 << 6) +#define DAC33_NCYCL_32 (0x03 << 6) + +/* DAC33_SER_AUDIOIF_CTRL_B (0x13) */ +#define DAC33_DATA_DELAY_MASK (0x03 << 2) +#define DAC33_DATA_DELAY(x) (x << 2) +#define DAC33_BCLKON (0x01 << 5) + +/* DAC33_FIFO_CTRL_A (0x15) */ +#define DAC33_WIDTH (0x01 << 0) +#define DAC33_FBYPAS (0x01 << 1) +#define DAC33_FAUTO (0x01 << 2) +#define DAC33_FIFOFLUSH (0x01 << 3) + +/* + * UTHR, ATHR, LTHR, PREFILL, NSAMPLE (0x16 - 0x1F) + * 13-bit values +*/ +#define DAC33_THRREG(x) (((x) & 0x1FFF) << 3) + +/* DAC33_FIFO_IRQ_MASK (0x29) */ +#define DAC33_MNS (0x01 << 0) +#define DAC33_MPS (0x01 << 1) +#define DAC33_MAT (0x01 << 2) +#define DAC33_MLT (0x01 << 3) +#define DAC33_MUT (0x01 << 4) +#define DAC33_MUF (0x01 << 5) +#define DAC33_MOF (0x01 << 6) + +#define DAC33_FIFO_IRQ_MODE_MASK (0x03) +#define DAC33_FIFO_IRQ_MODE_RISING (0x00) +#define DAC33_FIFO_IRQ_MODE_FALLING (0x01) +#define DAC33_FIFO_IRQ_MODE_LEVEL (0x02) +#define DAC33_FIFO_IRQ_MODE_EDGE (0x03) + +/* DAC33_FIFO_IRQ_MODE_A (0x2A) */ +#define DAC33_UTM(x) (x << 0) +#define DAC33_UFM(x) (x << 2) +#define DAC33_OFM(x) (x << 4) + +/* DAC33_FIFO_IRQ_MODE_B (0x2B) */ +#define DAC33_NSM(x) (x << 0) +#define DAC33_PSM(x) (x << 2) +#define DAC33_ATM(x) (x << 4) +#define DAC33_LTM(x) (x << 6) + +/* DAC33_DAC_CTRL_A (0x2C) */ +#define DAC33_DACRATE(x) (x << 0) +#define DAC33_DACDUAL (0x01 << 4) +#define DAC33_DACLKSEL_MASK (0x03 << 5) +#define DAC33_DACLKSEL_INTSOC (0x00 << 5) +#define DAC33_DACLKSEL_PLL (0x01 << 5) +#define DAC33_DACLKSEL_MCLK (0x02 << 5) +#define DAC33_DACLKSEL_BCLK (0x03 << 5) + +/* DAC33_DAC_CTRL_B (0x2D) */ +#define DAC33_DACSRCR_MASK (0x03 << 0) +#define DAC33_DACSRCR_MUTE (0x00 << 0) +#define DAC33_DACSRCR_RIGHT (0x01 << 0) +#define DAC33_DACSRCR_LEFT (0x02 << 0) +#define DAC33_DACSRCR_MONOMIX (0x03 << 0) +#define DAC33_DACSRCL_MASK (0x03 << 2) +#define DAC33_DACSRCL_MUTE (0x00 << 2) +#define DAC33_DACSRCL_LEFT (0x01 << 2) +#define DAC33_DACSRCL_RIGHT (0x02 << 2) +#define DAC33_DACSRCL_MONOMIX (0x03 << 2) +#define DAC33_DVOLSTEP_MASK (0x03 << 4) +#define DAC33_DVOLSTEP_SS_PERFS (0x00 << 4) +#define DAC33_DVOLSTEP_SS_PER2FS (0x01 << 4) +#define DAC33_DVOLSTEP_SS_DISABLED (0x02 << 4) +#define DAC33_DVOLCTRL_MASK (0x03 << 6) +#define DAC33_DVOLCTRL_LR_INDEPENDENT1 (0x00 << 6) +#define DAC33_DVOLCTRL_LR_RIGHT_CONTROL (0x01 << 6) +#define DAC33_DVOLCTRL_LR_LEFT_CONTROL (0x02 << 6) +#define DAC33_DVOLCTRL_LR_INDEPENDENT2 (0x03 << 6) + +/* DAC33_DAC_CTRL_C (0x2E) */ +#define DAC33_DEEMENR (0x01 << 0) +#define DAC33_EFFENR (0x01 << 1) +#define DAC33_DEEMENL (0x01 << 2) +#define DAC33_EFFENL (0x01 << 3) +#define DAC33_EN3D (0x01 << 4) +#define DAC33_RESYNMUTE (0x01 << 5) +#define DAC33_RESYNEN (0x01 << 6) + +/* DAC33_ASRC_CTRL_A (0x32) */ +#define DAC33_SRCBYP (0x01 << 0) +#define DAC33_SRCLKSEL_MASK (0x03 << 1) +#define DAC33_SRCLKSEL_INTSOC (0x00 << 1) +#define DAC33_SRCLKSEL_PLL (0x01 << 1) +#define DAC33_SRCLKSEL_MCLK (0x02 << 1) +#define DAC33_SRCLKSEL_BCLK (0x03 << 1) +#define DAC33_SRCLKDIV(x) (x << 3) + +/* DAC33_ASRC_CTRL_B (0x33) */ +#define DAC33_SRCSETUP(x) (x << 0) +#define DAC33_SRCREFSEL (0x01 << 4) +#define DAC33_SRCREFDIV(x) (x << 5) + +/* DAC33_INTP_CTRL_A (0x38) */ +#define DAC33_INTPSEL (0x01 << 0) +#define DAC33_INTPM_MASK (0x03 << 1) +#define DAC33_INTPM_ALOW_OPENDRAIN (0x00 << 1) +#define DAC33_INTPM_ALOW (0x01 << 1) +#define DAC33_INTPM_AHIGH (0x02 << 1) + +/* DAC33_LDAC_PWR_CTRL (0x40) */ +/* DAC33_RDAC_PWR_CTRL (0x41) */ +#define DAC33_DACLRNUM (0x01 << 2) +#define DAC33_LROUT_GAIN(x) (x << 0) + +/* DAC33_ANA_VOL_SOFT_STEP_CTRL (0x49) */ +#define DAC33_VOLCLKSEL (0x01 << 0) +#define DAC33_VOLCLKEN (0x01 << 1) +#define DAC33_VOLBYPASS (0x01 << 2) + +#define TLV320DAC33_MCLK 0 +#define TLV320DAC33_SLEEPCLK 1 + +#endif /* __TLV320DAC33_H */ diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c new file mode 100644 index 000000000..616cd4beb --- /dev/null +++ b/sound/soc/codecs/tpa6130a2.c @@ -0,0 +1,342 @@ +/* + * ALSA SoC Texas Instruments TPA6130A2 headset stereo amplifier driver + * + * Copyright (C) Nokia Corporation + * + * Author: Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpa6130a2.h" + +enum tpa_model { + TPA6130A2, + TPA6140A2, +}; + +/* This struct is used to save the context */ +struct tpa6130a2_data { + struct device *dev; + struct regmap *regmap; + struct regulator *supply; + int power_gpio; + enum tpa_model id; +}; + +static int tpa6130a2_power(struct tpa6130a2_data *data, bool enable) +{ + int ret = 0, ret2; + + if (enable) { + ret = regulator_enable(data->supply); + if (ret != 0) { + dev_err(data->dev, + "Failed to enable supply: %d\n", ret); + return ret; + } + /* Power on */ + if (data->power_gpio >= 0) + gpio_set_value(data->power_gpio, 1); + + /* Sync registers */ + regcache_cache_only(data->regmap, false); + ret = regcache_sync(data->regmap); + if (ret != 0) { + dev_err(data->dev, + "Failed to sync registers: %d\n", ret); + regcache_cache_only(data->regmap, true); + if (data->power_gpio >= 0) + gpio_set_value(data->power_gpio, 0); + ret2 = regulator_disable(data->supply); + if (ret2 != 0) + dev_err(data->dev, + "Failed to disable supply: %d\n", ret2); + return ret; + } + } else { + /* Powered off device does not retain registers. While device + * is off, any register updates (i.e. volume changes) should + * happen in cache only. + */ + regcache_mark_dirty(data->regmap); + regcache_cache_only(data->regmap, true); + + /* Power off */ + if (data->power_gpio >= 0) + gpio_set_value(data->power_gpio, 0); + + ret = regulator_disable(data->supply); + if (ret != 0) { + dev_err(data->dev, + "Failed to disable supply: %d\n", ret); + return ret; + } + } + + return ret; +} + +static int tpa6130a2_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctrl, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct tpa6130a2_data *data = snd_soc_component_get_drvdata(c); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* Before widget power up: turn chip on, sync registers */ + return tpa6130a2_power(data, true); + } else { + /* After widget power down: turn chip off */ + return tpa6130a2_power(data, false); + } +} + +/* + * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going + * down in gain. + */ +static const DECLARE_TLV_DB_RANGE(tpa6130_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-5950, 600, 0), + 2, 3, TLV_DB_SCALE_ITEM(-5000, 250, 0), + 4, 5, TLV_DB_SCALE_ITEM(-4550, 160, 0), + 6, 7, TLV_DB_SCALE_ITEM(-4140, 190, 0), + 8, 9, TLV_DB_SCALE_ITEM(-3650, 120, 0), + 10, 11, TLV_DB_SCALE_ITEM(-3330, 160, 0), + 12, 13, TLV_DB_SCALE_ITEM(-3040, 180, 0), + 14, 20, TLV_DB_SCALE_ITEM(-2710, 110, 0), + 21, 37, TLV_DB_SCALE_ITEM(-1960, 74, 0), + 38, 63, TLV_DB_SCALE_ITEM(-720, 45, 0) +); + +static const struct snd_kcontrol_new tpa6130a2_controls[] = { + SOC_SINGLE_TLV("Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, + tpa6130_tlv), +}; + +static const DECLARE_TLV_DB_RANGE(tpa6140_tlv, + 0, 8, TLV_DB_SCALE_ITEM(-5900, 400, 0), + 9, 16, TLV_DB_SCALE_ITEM(-2500, 200, 0), + 17, 31, TLV_DB_SCALE_ITEM(-1000, 100, 0) +); + +static const struct snd_kcontrol_new tpa6140a2_controls[] = { + SOC_SINGLE_TLV("Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, + tpa6140_tlv), +}; + +static int tpa6130a2_component_probe(struct snd_soc_component *component) +{ + struct tpa6130a2_data *data = snd_soc_component_get_drvdata(component); + + if (data->id == TPA6140A2) + return snd_soc_add_component_controls(component, + tpa6140a2_controls, ARRAY_SIZE(tpa6140a2_controls)); + else + return snd_soc_add_component_controls(component, + tpa6130a2_controls, ARRAY_SIZE(tpa6130a2_controls)); +} + +static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LEFTIN"), + SND_SOC_DAPM_INPUT("RIGHTIN"), + SND_SOC_DAPM_OUTPUT("HPLEFT"), + SND_SOC_DAPM_OUTPUT("HPRIGHT"), + + SND_SOC_DAPM_PGA("Left Mute", TPA6130A2_REG_VOL_MUTE, + TPA6130A2_HP_EN_L_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Mute", TPA6130A2_REG_VOL_MUTE, + TPA6130A2_HP_EN_R_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Left PGA", TPA6130A2_REG_CONTROL, + TPA6130A2_HP_EN_L_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", TPA6130A2_REG_CONTROL, + TPA6130A2_HP_EN_R_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Power", TPA6130A2_REG_CONTROL, + TPA6130A2_SWS_SHIFT, 1, tpa6130a2_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route tpa6130a2_dapm_routes[] = { + { "Left PGA", NULL, "LEFTIN" }, + { "Right PGA", NULL, "RIGHTIN" }, + + { "Left Mute", NULL, "Left PGA" }, + { "Right Mute", NULL, "Right PGA" }, + + { "HPLEFT", NULL, "Left Mute" }, + { "HPRIGHT", NULL, "Right Mute" }, + + { "Left PGA", NULL, "Power" }, + { "Right PGA", NULL, "Power" }, +}; + +static const struct snd_soc_component_driver tpa6130a2_component_driver = { + .name = "tpa6130a2", + .probe = tpa6130a2_component_probe, + .dapm_widgets = tpa6130a2_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tpa6130a2_dapm_widgets), + .dapm_routes = tpa6130a2_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tpa6130a2_dapm_routes), +}; + +static const struct reg_default tpa6130a2_reg_defaults[] = { + { TPA6130A2_REG_CONTROL, TPA6130A2_SWS }, + { TPA6130A2_REG_VOL_MUTE, TPA6130A2_MUTE_R | TPA6130A2_MUTE_L }, +}; + +static const struct regmap_config tpa6130a2_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPA6130A2_REG_VERSION, + .reg_defaults = tpa6130a2_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tpa6130a2_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int tpa6130a2_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev; + struct tpa6130a2_data *data; + struct tpa6130a2_platform_data *pdata = client->dev.platform_data; + struct device_node *np = client->dev.of_node; + const char *regulator; + unsigned int version; + int ret; + + dev = &client->dev; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + + data->regmap = devm_regmap_init_i2c(client, &tpa6130a2_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + if (pdata) { + data->power_gpio = pdata->power_gpio; + } else if (np) { + data->power_gpio = of_get_named_gpio(np, "power-gpio", 0); + } else { + dev_err(dev, "Platform data not set\n"); + dump_stack(); + return -ENODEV; + } + + i2c_set_clientdata(client, data); + + data->id = id->driver_data; + + if (data->power_gpio >= 0) { + ret = devm_gpio_request(dev, data->power_gpio, + "tpa6130a2 enable"); + if (ret < 0) { + dev_err(dev, "Failed to request power GPIO (%d)\n", + data->power_gpio); + return ret; + } + gpio_direction_output(data->power_gpio, 0); + } + + switch (data->id) { + default: + dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", + data->id); + /* fall through */ + case TPA6130A2: + regulator = "Vdd"; + break; + case TPA6140A2: + regulator = "AVdd"; + break; + } + + data->supply = devm_regulator_get(dev, regulator); + if (IS_ERR(data->supply)) { + ret = PTR_ERR(data->supply); + dev_err(dev, "Failed to request supply: %d\n", ret); + return ret; + } + + ret = tpa6130a2_power(data, true); + if (ret != 0) + return ret; + + + /* Read version */ + regmap_read(data->regmap, TPA6130A2_REG_VERSION, &version); + version &= TPA6130A2_VERSION_MASK; + if ((version != 1) && (version != 2)) + dev_warn(dev, "UNTESTED version detected (%d)\n", version); + + /* Disable the chip */ + ret = tpa6130a2_power(data, false); + if (ret != 0) + return ret; + + return devm_snd_soc_register_component(&client->dev, + &tpa6130a2_component_driver, NULL, 0); +} + +static const struct i2c_device_id tpa6130a2_id[] = { + { "tpa6130a2", TPA6130A2 }, + { "tpa6140a2", TPA6140A2 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tpa6130a2_id); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tpa6130a2_of_match[] = { + { .compatible = "ti,tpa6130a2", }, + { .compatible = "ti,tpa6140a2" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tpa6130a2_of_match); +#endif + +static struct i2c_driver tpa6130a2_i2c_driver = { + .driver = { + .name = "tpa6130a2", + .of_match_table = of_match_ptr(tpa6130a2_of_match), + }, + .probe = tpa6130a2_probe, + .id_table = tpa6130a2_id, +}; + +module_i2c_driver(tpa6130a2_i2c_driver); + +MODULE_AUTHOR("Peter Ujfalusi "); +MODULE_DESCRIPTION("TPA6130A2 Headphone amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tpa6130a2.h b/sound/soc/codecs/tpa6130a2.h new file mode 100644 index 000000000..f19cad5d4 --- /dev/null +++ b/sound/soc/codecs/tpa6130a2.h @@ -0,0 +1,60 @@ +/* + * ALSA SoC TPA6130A2 amplifier driver + * + * Copyright (C) Nokia Corporation + * + * Author: Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TPA6130A2_H__ +#define __TPA6130A2_H__ + +/* Register addresses */ +#define TPA6130A2_REG_CONTROL 0x01 +#define TPA6130A2_REG_VOL_MUTE 0x02 +#define TPA6130A2_REG_OUT_IMPEDANCE 0x03 +#define TPA6130A2_REG_VERSION 0x04 + +/* Register bits */ +/* TPA6130A2_REG_CONTROL (0x01) */ +#define TPA6130A2_SWS_SHIFT 0 +#define TPA6130A2_SWS (0x01 << TPA6130A2_SWS_SHIFT) +#define TPA6130A2_TERMAL (0x01 << 1) +#define TPA6130A2_MODE(x) (x << 4) +#define TPA6130A2_MODE_STEREO (0x00) +#define TPA6130A2_MODE_DUAL_MONO (0x01) +#define TPA6130A2_MODE_BRIDGE (0x02) +#define TPA6130A2_MODE_MASK (0x03) +#define TPA6130A2_HP_EN_R_SHIFT 6 +#define TPA6130A2_HP_EN_R (0x01 << TPA6130A2_HP_EN_R_SHIFT) +#define TPA6130A2_HP_EN_L_SHIFT 7 +#define TPA6130A2_HP_EN_L (0x01 << TPA6130A2_HP_EN_L_SHIFT) + +/* TPA6130A2_REG_VOL_MUTE (0x02) */ +#define TPA6130A2_VOLUME(x) ((x & 0x3f) << 0) +#define TPA6130A2_MUTE_R (0x01 << 6) +#define TPA6130A2_MUTE_L (0x01 << 7) + +/* TPA6130A2_REG_OUT_IMPEDANCE (0x03) */ +#define TPA6130A2_HIZ_R (0x01 << 0) +#define TPA6130A2_HIZ_L (0x01 << 1) + +/* TPA6130A2_REG_VERSION (0x04) */ +#define TPA6130A2_VERSION_MASK (0x0f) + +#endif /* __TPA6130A2_H__ */ diff --git a/sound/soc/codecs/ts3a227e.c b/sound/soc/codecs/ts3a227e.c new file mode 100644 index 000000000..1271e7e1f --- /dev/null +++ b/sound/soc/codecs/ts3a227e.c @@ -0,0 +1,400 @@ +/* + * TS3A227E Autonomous Audio Accessory Detection and Configuration Switch + * + * Copyright (C) 2014 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ts3a227e.h" + +struct ts3a227e { + struct device *dev; + struct regmap *regmap; + struct snd_soc_jack *jack; + bool plugged; + bool mic_present; + unsigned int buttons_held; + int irq; +}; + +/* Button values to be reported on the jack */ +static const int ts3a227e_buttons[] = { + SND_JACK_BTN_0, + SND_JACK_BTN_1, + SND_JACK_BTN_2, + SND_JACK_BTN_3, +}; + +#define TS3A227E_NUM_BUTTONS 4 +#define TS3A227E_JACK_MASK (SND_JACK_HEADPHONE | \ + SND_JACK_MICROPHONE | \ + SND_JACK_BTN_0 | \ + SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | \ + SND_JACK_BTN_3) + +/* TS3A227E registers */ +#define TS3A227E_REG_DEVICE_ID 0x00 +#define TS3A227E_REG_INTERRUPT 0x01 +#define TS3A227E_REG_KP_INTERRUPT 0x02 +#define TS3A227E_REG_INTERRUPT_DISABLE 0x03 +#define TS3A227E_REG_SETTING_1 0x04 +#define TS3A227E_REG_SETTING_2 0x05 +#define TS3A227E_REG_SETTING_3 0x06 +#define TS3A227E_REG_SWITCH_CONTROL_1 0x07 +#define TS3A227E_REG_SWITCH_CONTROL_2 0x08 +#define TS3A227E_REG_SWITCH_STATUS_1 0x09 +#define TS3A227E_REG_SWITCH_STATUS_2 0x0a +#define TS3A227E_REG_ACCESSORY_STATUS 0x0b +#define TS3A227E_REG_ADC_OUTPUT 0x0c +#define TS3A227E_REG_KP_THRESHOLD_1 0x0d +#define TS3A227E_REG_KP_THRESHOLD_2 0x0e +#define TS3A227E_REG_KP_THRESHOLD_3 0x0f + +/* TS3A227E_REG_INTERRUPT 0x01 */ +#define INS_REM_EVENT 0x01 +#define DETECTION_COMPLETE_EVENT 0x02 + +/* TS3A227E_REG_KP_INTERRUPT 0x02 */ +#define PRESS_MASK(idx) (0x01 << (2 * (idx))) +#define RELEASE_MASK(idx) (0x02 << (2 * (idx))) + +/* TS3A227E_REG_INTERRUPT_DISABLE 0x03 */ +#define INS_REM_INT_DISABLE 0x01 +#define DETECTION_COMPLETE_INT_DISABLE 0x02 +#define ADC_COMPLETE_INT_DISABLE 0x04 +#define INTB_DISABLE 0x08 + +/* TS3A227E_REG_SETTING_2 0x05 */ +#define KP_ENABLE 0x04 + +/* TS3A227E_REG_SETTING_3 0x06 */ +#define MICBIAS_SETTING_SFT (3) +#define MICBIAS_SETTING_MASK (0x7 << MICBIAS_SETTING_SFT) + +/* TS3A227E_REG_ACCESSORY_STATUS 0x0b */ +#define TYPE_3_POLE 0x01 +#define TYPE_4_POLE_OMTP 0x02 +#define TYPE_4_POLE_STANDARD 0x04 +#define JACK_INSERTED 0x08 +#define EITHER_MIC_MASK (TYPE_4_POLE_OMTP | TYPE_4_POLE_STANDARD) + +static const struct reg_default ts3a227e_reg_defaults[] = { + { TS3A227E_REG_DEVICE_ID, 0x10 }, + { TS3A227E_REG_INTERRUPT, 0x00 }, + { TS3A227E_REG_KP_INTERRUPT, 0x00 }, + { TS3A227E_REG_INTERRUPT_DISABLE, 0x08 }, + { TS3A227E_REG_SETTING_1, 0x23 }, + { TS3A227E_REG_SETTING_2, 0x00 }, + { TS3A227E_REG_SETTING_3, 0x0e }, + { TS3A227E_REG_SWITCH_CONTROL_1, 0x00 }, + { TS3A227E_REG_SWITCH_CONTROL_2, 0x00 }, + { TS3A227E_REG_SWITCH_STATUS_1, 0x0c }, + { TS3A227E_REG_SWITCH_STATUS_2, 0x00 }, + { TS3A227E_REG_ACCESSORY_STATUS, 0x00 }, + { TS3A227E_REG_ADC_OUTPUT, 0x00 }, + { TS3A227E_REG_KP_THRESHOLD_1, 0x20 }, + { TS3A227E_REG_KP_THRESHOLD_2, 0x40 }, + { TS3A227E_REG_KP_THRESHOLD_3, 0x68 }, +}; + +static bool ts3a227e_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TS3A227E_REG_DEVICE_ID ... TS3A227E_REG_KP_THRESHOLD_3: + return true; + default: + return false; + } +} + +static bool ts3a227e_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TS3A227E_REG_INTERRUPT_DISABLE ... TS3A227E_REG_SWITCH_CONTROL_2: + case TS3A227E_REG_KP_THRESHOLD_1 ... TS3A227E_REG_KP_THRESHOLD_3: + return true; + default: + return false; + } +} + +static bool ts3a227e_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TS3A227E_REG_INTERRUPT ... TS3A227E_REG_INTERRUPT_DISABLE: + case TS3A227E_REG_SETTING_2: + case TS3A227E_REG_SWITCH_STATUS_1 ... TS3A227E_REG_ADC_OUTPUT: + return true; + default: + return false; + } +} + +static void ts3a227e_jack_report(struct ts3a227e *ts3a227e) +{ + unsigned int i; + int report = 0; + + if (!ts3a227e->jack) + return; + + if (ts3a227e->plugged) + report = SND_JACK_HEADPHONE; + if (ts3a227e->mic_present) + report |= SND_JACK_MICROPHONE; + for (i = 0; i < TS3A227E_NUM_BUTTONS; i++) { + if (ts3a227e->buttons_held & (1 << i)) + report |= ts3a227e_buttons[i]; + } + snd_soc_jack_report(ts3a227e->jack, report, TS3A227E_JACK_MASK); +} + +static void ts3a227e_new_jack_state(struct ts3a227e *ts3a227e, unsigned acc_reg) +{ + bool plugged, mic_present; + + plugged = !!(acc_reg & JACK_INSERTED); + mic_present = plugged && !!(acc_reg & EITHER_MIC_MASK); + + ts3a227e->plugged = plugged; + + if (mic_present != ts3a227e->mic_present) { + ts3a227e->mic_present = mic_present; + ts3a227e->buttons_held = 0; + if (mic_present) { + /* Enable key press detection. */ + regmap_update_bits(ts3a227e->regmap, + TS3A227E_REG_SETTING_2, + KP_ENABLE, KP_ENABLE); + } + } +} + +static irqreturn_t ts3a227e_interrupt(int irq, void *data) +{ + struct ts3a227e *ts3a227e = (struct ts3a227e *)data; + struct regmap *regmap = ts3a227e->regmap; + unsigned int int_reg, kp_int_reg, acc_reg, i; + struct device *dev = ts3a227e->dev; + int ret; + + /* Check for plug/unplug. */ + ret = regmap_read(regmap, TS3A227E_REG_INTERRUPT, &int_reg); + if (ret) { + dev_err(dev, "failed to clear interrupt ret=%d\n", ret); + return IRQ_NONE; + } + + if (int_reg & (DETECTION_COMPLETE_EVENT | INS_REM_EVENT)) { + regmap_read(regmap, TS3A227E_REG_ACCESSORY_STATUS, &acc_reg); + ts3a227e_new_jack_state(ts3a227e, acc_reg); + } + + /* Report any key events. */ + ret = regmap_read(regmap, TS3A227E_REG_KP_INTERRUPT, &kp_int_reg); + if (ret) { + dev_err(dev, "failed to clear key interrupt ret=%d\n", ret); + return IRQ_NONE; + } + + for (i = 0; i < TS3A227E_NUM_BUTTONS; i++) { + if (kp_int_reg & PRESS_MASK(i)) + ts3a227e->buttons_held |= (1 << i); + if (kp_int_reg & RELEASE_MASK(i)) + ts3a227e->buttons_held &= ~(1 << i); + } + + ts3a227e_jack_report(ts3a227e); + + return IRQ_HANDLED; +} + +/** + * ts3a227e_enable_jack_detect - Specify a jack for event reporting + * + * @component: component to register the jack with + * @jack: jack to use to report headset and button events on + * + * After this function has been called the headset insert/remove and button + * events 0-3 will be routed to the given jack. Jack can be null to stop + * reporting. + */ +int ts3a227e_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack) +{ + struct ts3a227e *ts3a227e = snd_soc_component_get_drvdata(component); + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ts3a227e->jack = jack; + ts3a227e_jack_report(ts3a227e); + + return 0; +} +EXPORT_SYMBOL_GPL(ts3a227e_enable_jack_detect); + +static struct snd_soc_component_driver ts3a227e_soc_driver; + +static const struct regmap_config ts3a227e_regmap_config = { + .val_bits = 8, + .reg_bits = 8, + + .max_register = TS3A227E_REG_KP_THRESHOLD_3, + .readable_reg = ts3a227e_readable_reg, + .writeable_reg = ts3a227e_writeable_reg, + .volatile_reg = ts3a227e_volatile_reg, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = ts3a227e_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ts3a227e_reg_defaults), +}; + +static int ts3a227e_parse_device_property(struct ts3a227e *ts3a227e, + struct device *dev) +{ + u32 micbias; + int err; + + err = device_property_read_u32(dev, "ti,micbias", &micbias); + if (!err) { + regmap_update_bits(ts3a227e->regmap, TS3A227E_REG_SETTING_3, + MICBIAS_SETTING_MASK, + (micbias & 0x07) << MICBIAS_SETTING_SFT); + } + + return 0; +} + +static int ts3a227e_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ts3a227e *ts3a227e; + struct device *dev = &i2c->dev; + int ret; + unsigned int acc_reg; + + ts3a227e = devm_kzalloc(&i2c->dev, sizeof(*ts3a227e), GFP_KERNEL); + if (ts3a227e == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, ts3a227e); + ts3a227e->dev = dev; + ts3a227e->irq = i2c->irq; + + ts3a227e->regmap = devm_regmap_init_i2c(i2c, &ts3a227e_regmap_config); + if (IS_ERR(ts3a227e->regmap)) + return PTR_ERR(ts3a227e->regmap); + + ret = ts3a227e_parse_device_property(ts3a227e, dev); + if (ret) { + dev_err(dev, "Failed to parse device property: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, ts3a227e_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "TS3A227E", ts3a227e); + if (ret) { + dev_err(dev, "Cannot request irq %d (%d)\n", i2c->irq, ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, &ts3a227e_soc_driver, + NULL, 0); + if (ret) + return ret; + + /* Enable interrupts except for ADC complete. */ + regmap_update_bits(ts3a227e->regmap, TS3A227E_REG_INTERRUPT_DISABLE, + INTB_DISABLE | ADC_COMPLETE_INT_DISABLE, + ADC_COMPLETE_INT_DISABLE); + + /* Read jack status because chip might not trigger interrupt at boot. */ + regmap_read(ts3a227e->regmap, TS3A227E_REG_ACCESSORY_STATUS, &acc_reg); + ts3a227e_new_jack_state(ts3a227e, acc_reg); + ts3a227e_jack_report(ts3a227e); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ts3a227e_suspend(struct device *dev) +{ + struct ts3a227e *ts3a227e = dev_get_drvdata(dev); + + dev_dbg(ts3a227e->dev, "suspend disable irq\n"); + disable_irq(ts3a227e->irq); + + return 0; +} + +static int ts3a227e_resume(struct device *dev) +{ + struct ts3a227e *ts3a227e = dev_get_drvdata(dev); + + dev_dbg(ts3a227e->dev, "resume enable irq\n"); + enable_irq(ts3a227e->irq); + + return 0; +} +#endif + +static const struct dev_pm_ops ts3a227e_pm = { + SET_SYSTEM_SLEEP_PM_OPS(ts3a227e_suspend, ts3a227e_resume) +}; + +static const struct i2c_device_id ts3a227e_i2c_ids[] = { + { "ts3a227e", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ts3a227e_i2c_ids); + +static const struct of_device_id ts3a227e_of_match[] = { + { .compatible = "ti,ts3a227e", }, + { } +}; +MODULE_DEVICE_TABLE(of, ts3a227e_of_match); + +#ifdef CONFIG_ACPI +static struct acpi_device_id ts3a227e_acpi_match[] = { + { "104C227E", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, ts3a227e_acpi_match); +#endif + +static struct i2c_driver ts3a227e_driver = { + .driver = { + .name = "ts3a227e", + .pm = &ts3a227e_pm, + .of_match_table = of_match_ptr(ts3a227e_of_match), + .acpi_match_table = ACPI_PTR(ts3a227e_acpi_match), + }, + .probe = ts3a227e_i2c_probe, + .id_table = ts3a227e_i2c_ids, +}; +module_i2c_driver(ts3a227e_driver); + +MODULE_DESCRIPTION("ASoC ts3a227e driver"); +MODULE_AUTHOR("Dylan Reid "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ts3a227e.h b/sound/soc/codecs/ts3a227e.h new file mode 100644 index 000000000..e2acf9c5b --- /dev/null +++ b/sound/soc/codecs/ts3a227e.h @@ -0,0 +1,17 @@ +/* + * TS3A227E Autonous Audio Accessory Detection and Configureation Switch + * + * Copyright (C) 2014 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _TS3A227E_H +#define _TS3A227E_H + +int ts3a227e_enable_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack); + +#endif diff --git a/sound/soc/codecs/tscs42xx.c b/sound/soc/codecs/tscs42xx.c new file mode 100644 index 000000000..7396a6e52 --- /dev/null +++ b/sound/soc/codecs/tscs42xx.c @@ -0,0 +1,1516 @@ +// SPDX-License-Identifier: GPL-2.0 +// tscs42xx.c -- TSCS42xx ALSA SoC Audio driver +// Copyright 2017 Tempo Semiconductor, Inc. +// Author: Steven Eckhoff + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tscs42xx.h" + +#define COEFF_SIZE 3 +#define BIQUAD_COEFF_COUNT 5 +#define BIQUAD_SIZE (COEFF_SIZE * BIQUAD_COEFF_COUNT) + +#define COEFF_RAM_MAX_ADDR 0xcd +#define COEFF_RAM_COEFF_COUNT (COEFF_RAM_MAX_ADDR + 1) +#define COEFF_RAM_SIZE (COEFF_SIZE * COEFF_RAM_COEFF_COUNT) + +struct tscs42xx { + + int bclk_ratio; + int samplerate; + struct mutex audio_params_lock; + + u8 coeff_ram[COEFF_RAM_SIZE]; + bool coeff_ram_synced; + struct mutex coeff_ram_lock; + + struct mutex pll_lock; + + struct regmap *regmap; + + struct clk *sysclk; + int sysclk_src_id; +}; + +struct coeff_ram_ctl { + unsigned int addr; + struct soc_bytes_ext bytes_ext; +}; + +static bool tscs42xx_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case R_DACCRWRL: + case R_DACCRWRM: + case R_DACCRWRH: + case R_DACCRRDL: + case R_DACCRRDM: + case R_DACCRRDH: + case R_DACCRSTAT: + case R_DACCRADDR: + case R_PLLCTL0: + return true; + default: + return false; + }; +} + +static bool tscs42xx_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case R_DACCRWRL: + case R_DACCRWRM: + case R_DACCRWRH: + case R_DACCRRDL: + case R_DACCRRDM: + case R_DACCRRDH: + return true; + default: + return false; + }; +} + +static const struct regmap_config tscs42xx_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .volatile_reg = tscs42xx_volatile, + .precious_reg = tscs42xx_precious, + .max_register = R_DACMBCREL3H, + + .cache_type = REGCACHE_RBTREE, + .can_multi_write = true, +}; + +#define MAX_PLL_LOCK_20MS_WAITS 1 +static bool plls_locked(struct snd_soc_component *component) +{ + int ret; + int count = MAX_PLL_LOCK_20MS_WAITS; + + do { + ret = snd_soc_component_read32(component, R_PLLCTL0); + if (ret < 0) { + dev_err(component->dev, + "Failed to read PLL lock status (%d)\n", ret); + return false; + } else if (ret > 0) { + return true; + } + msleep(20); + } while (count--); + + return false; +} + +static int sample_rate_to_pll_freq_out(int sample_rate) +{ + switch (sample_rate) { + case 11025: + case 22050: + case 44100: + case 88200: + return 112896000; + case 8000: + case 16000: + case 32000: + case 48000: + case 96000: + return 122880000; + default: + return -EINVAL; + } +} + +#define DACCRSTAT_MAX_TRYS 10 +static int write_coeff_ram(struct snd_soc_component *component, u8 *coeff_ram, + unsigned int addr, unsigned int coeff_cnt) +{ + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int cnt; + int trys; + int ret; + + for (cnt = 0; cnt < coeff_cnt; cnt++, addr++) { + + for (trys = 0; trys < DACCRSTAT_MAX_TRYS; trys++) { + ret = snd_soc_component_read32(component, R_DACCRSTAT); + if (ret < 0) { + dev_err(component->dev, + "Failed to read stat (%d)\n", ret); + return ret; + } + if (!ret) + break; + } + + if (trys == DACCRSTAT_MAX_TRYS) { + ret = -EIO; + dev_err(component->dev, + "dac coefficient write error (%d)\n", ret); + return ret; + } + + ret = regmap_write(tscs42xx->regmap, R_DACCRADDR, addr); + if (ret < 0) { + dev_err(component->dev, + "Failed to write dac ram address (%d)\n", ret); + return ret; + } + + ret = regmap_bulk_write(tscs42xx->regmap, R_DACCRWRL, + &coeff_ram[addr * COEFF_SIZE], + COEFF_SIZE); + if (ret < 0) { + dev_err(component->dev, + "Failed to write dac ram (%d)\n", ret); + return ret; + } + } + + return 0; +} + +static int power_up_audio_plls(struct snd_soc_component *component) +{ + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int freq_out; + int ret; + unsigned int mask; + unsigned int val; + + freq_out = sample_rate_to_pll_freq_out(tscs42xx->samplerate); + switch (freq_out) { + case 122880000: /* 48k */ + mask = RM_PLLCTL1C_PDB_PLL1; + val = RV_PLLCTL1C_PDB_PLL1_ENABLE; + break; + case 112896000: /* 44.1k */ + mask = RM_PLLCTL1C_PDB_PLL2; + val = RV_PLLCTL1C_PDB_PLL2_ENABLE; + break; + default: + ret = -EINVAL; + dev_err(component->dev, + "Unrecognized PLL output freq (%d)\n", ret); + return ret; + } + + mutex_lock(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, mask, val); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL on (%d)\n", ret); + goto exit; + } + + if (!plls_locked(component)) { + dev_err(component->dev, "Failed to lock plls\n"); + ret = -ENOMSG; + goto exit; + } + + ret = 0; +exit: + mutex_unlock(&tscs42xx->pll_lock); + + return ret; +} + +static int power_down_audio_plls(struct snd_soc_component *component) +{ + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL1, + RV_PLLCTL1C_PDB_PLL1_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); + goto exit; + } + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL2, + RV_PLLCTL1C_PDB_PLL2_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); + goto exit; + } + + ret = 0; +exit: + mutex_unlock(&tscs42xx->pll_lock); + + return ret; +} + +static int coeff_ram_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + struct coeff_ram_ctl *ctl = + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + + mutex_lock(&tscs42xx->coeff_ram_lock); + + memcpy(ucontrol->value.bytes.data, + &tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], params->max); + + mutex_unlock(&tscs42xx->coeff_ram_lock); + + return 0; +} + +static int coeff_ram_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + struct coeff_ram_ctl *ctl = + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + unsigned int coeff_cnt = params->max / COEFF_SIZE; + int ret; + + mutex_lock(&tscs42xx->coeff_ram_lock); + + tscs42xx->coeff_ram_synced = false; + + memcpy(&tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + + mutex_lock(&tscs42xx->pll_lock); + + if (plls_locked(component)) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, + ctl->addr, coeff_cnt); + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); + goto exit; + } + tscs42xx->coeff_ram_synced = true; + } + + ret = 0; +exit: + mutex_unlock(&tscs42xx->pll_lock); + + mutex_unlock(&tscs42xx->coeff_ram_lock); + + return ret; +} + +/* Input L Capture Route */ +static char const * const input_select_text[] = { + "Line 1", "Line 2", "Line 3", "D2S" +}; + +static const struct soc_enum left_input_select_enum = +SOC_ENUM_SINGLE(R_INSELL, FB_INSELL, ARRAY_SIZE(input_select_text), + input_select_text); + +static const struct snd_kcontrol_new left_input_select = +SOC_DAPM_ENUM("LEFT_INPUT_SELECT_ENUM", left_input_select_enum); + +/* Input R Capture Route */ +static const struct soc_enum right_input_select_enum = +SOC_ENUM_SINGLE(R_INSELR, FB_INSELR, ARRAY_SIZE(input_select_text), + input_select_text); + +static const struct snd_kcontrol_new right_input_select = +SOC_DAPM_ENUM("RIGHT_INPUT_SELECT_ENUM", right_input_select_enum); + +/* Input Channel Mapping */ +static char const * const ch_map_select_text[] = { + "Normal", "Left to Right", "Right to Left", "Swap" +}; + +static const struct soc_enum ch_map_select_enum = +SOC_ENUM_SINGLE(R_AIC2, FB_AIC2_ADCDSEL, ARRAY_SIZE(ch_map_select_text), + ch_map_select_text); + +static int dapm_vref_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + msleep(20); + return 0; +} + +static int dapm_micb_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + msleep(20); + return 0; +} + +static int pll_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + int ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = power_up_audio_plls(component); + else + ret = power_down_audio_plls(component); + + return ret; +} + +static int dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + + mutex_lock(&tscs42xx->coeff_ram_lock); + + if (tscs42xx->coeff_ram_synced == false) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, 0x00, + COEFF_RAM_COEFF_COUNT); + if (ret < 0) + goto exit; + tscs42xx->coeff_ram_synced = true; + } + + ret = 0; +exit: + mutex_unlock(&tscs42xx->coeff_ram_lock); + + return ret; +} + +static const struct snd_soc_dapm_widget tscs42xx_dapm_widgets[] = { + /* Vref */ + SND_SOC_DAPM_SUPPLY_S("Vref", 1, R_PWRM2, FB_PWRM2_VREF, 0, + dapm_vref_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + + /* PLL */ + SND_SOC_DAPM_SUPPLY("PLL", SND_SOC_NOPM, 0, 0, pll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* Headphone */ + SND_SOC_DAPM_DAC_E("DAC L", "HiFi Playback", R_PWRM2, FB_PWRM2_HPL, 0, + dac_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_DAC_E("DAC R", "HiFi Playback", R_PWRM2, FB_PWRM2_HPR, 0, + dac_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_OUTPUT("Headphone L"), + SND_SOC_DAPM_OUTPUT("Headphone R"), + + /* Speaker */ + SND_SOC_DAPM_DAC_E("ClassD L", "HiFi Playback", + R_PWRM2, FB_PWRM2_SPKL, 0, + dac_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_DAC_E("ClassD R", "HiFi Playback", + R_PWRM2, FB_PWRM2_SPKR, 0, + dac_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_OUTPUT("Speaker L"), + SND_SOC_DAPM_OUTPUT("Speaker R"), + + /* Capture */ + SND_SOC_DAPM_PGA("Analog In PGA L", R_PWRM1, FB_PWRM1_PGAL, 0, NULL, 0), + SND_SOC_DAPM_PGA("Analog In PGA R", R_PWRM1, FB_PWRM1_PGAR, 0, NULL, 0), + SND_SOC_DAPM_PGA("Analog Boost L", R_PWRM1, FB_PWRM1_BSTL, 0, NULL, 0), + SND_SOC_DAPM_PGA("Analog Boost R", R_PWRM1, FB_PWRM1_BSTR, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC Mute", R_CNVRTR0, FB_CNVRTR0_HPOR, true, NULL, 0), + SND_SOC_DAPM_ADC("ADC L", "HiFi Capture", R_PWRM1, FB_PWRM1_ADCL, 0), + SND_SOC_DAPM_ADC("ADC R", "HiFi Capture", R_PWRM1, FB_PWRM1_ADCR, 0), + + /* Capture Input */ + SND_SOC_DAPM_MUX("Input L Capture Route", R_PWRM2, + FB_PWRM2_INSELL, 0, &left_input_select), + SND_SOC_DAPM_MUX("Input R Capture Route", R_PWRM2, + FB_PWRM2_INSELR, 0, &right_input_select), + + /* Digital Mic */ + SND_SOC_DAPM_SUPPLY_S("Digital Mic Enable", 2, R_DMICCTL, + FB_DMICCTL_DMICEN, 0, NULL, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + + /* Analog Mic */ + SND_SOC_DAPM_SUPPLY_S("Mic Bias", 2, R_PWRM1, FB_PWRM1_MICB, + 0, dapm_micb_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + + /* Line In */ + SND_SOC_DAPM_INPUT("Line In 1 L"), + SND_SOC_DAPM_INPUT("Line In 1 R"), + SND_SOC_DAPM_INPUT("Line In 2 L"), + SND_SOC_DAPM_INPUT("Line In 2 R"), + SND_SOC_DAPM_INPUT("Line In 3 L"), + SND_SOC_DAPM_INPUT("Line In 3 R"), +}; + +static const struct snd_soc_dapm_route tscs42xx_intercon[] = { + {"DAC L", NULL, "PLL"}, + {"DAC R", NULL, "PLL"}, + {"DAC L", NULL, "Vref"}, + {"DAC R", NULL, "Vref"}, + {"Headphone L", NULL, "DAC L"}, + {"Headphone R", NULL, "DAC R"}, + + {"ClassD L", NULL, "PLL"}, + {"ClassD R", NULL, "PLL"}, + {"ClassD L", NULL, "Vref"}, + {"ClassD R", NULL, "Vref"}, + {"Speaker L", NULL, "ClassD L"}, + {"Speaker R", NULL, "ClassD R"}, + + {"Input L Capture Route", NULL, "Vref"}, + {"Input R Capture Route", NULL, "Vref"}, + + {"Mic Bias", NULL, "Vref"}, + + {"Input L Capture Route", "Line 1", "Line In 1 L"}, + {"Input R Capture Route", "Line 1", "Line In 1 R"}, + {"Input L Capture Route", "Line 2", "Line In 2 L"}, + {"Input R Capture Route", "Line 2", "Line In 2 R"}, + {"Input L Capture Route", "Line 3", "Line In 3 L"}, + {"Input R Capture Route", "Line 3", "Line In 3 R"}, + + {"Analog In PGA L", NULL, "Input L Capture Route"}, + {"Analog In PGA R", NULL, "Input R Capture Route"}, + {"Analog Boost L", NULL, "Analog In PGA L"}, + {"Analog Boost R", NULL, "Analog In PGA R"}, + {"ADC Mute", NULL, "Analog Boost L"}, + {"ADC Mute", NULL, "Analog Boost R"}, + {"ADC L", NULL, "PLL"}, + {"ADC R", NULL, "PLL"}, + {"ADC L", NULL, "ADC Mute"}, + {"ADC R", NULL, "ADC Mute"}, +}; + +/************ + * CONTROLS * + ************/ + +static char const * const eq_band_enable_text[] = { + "Prescale only", + "Band1", + "Band1:2", + "Band1:3", + "Band1:4", + "Band1:5", + "Band1:6", +}; + +static char const * const level_detection_text[] = { + "Average", + "Peak", +}; + +static char const * const level_detection_window_text[] = { + "512 Samples", + "64 Samples", +}; + +static char const * const compressor_ratio_text[] = { + "Reserved", "1.5:1", "2:1", "3:1", "4:1", "5:1", "6:1", + "7:1", "8:1", "9:1", "10:1", "11:1", "12:1", "13:1", "14:1", + "15:1", "16:1", "17:1", "18:1", "19:1", "20:1", +}; + +static DECLARE_TLV_DB_SCALE(hpvol_scale, -8850, 75, 0); +static DECLARE_TLV_DB_SCALE(spkvol_scale, -7725, 75, 0); +static DECLARE_TLV_DB_SCALE(dacvol_scale, -9563, 38, 0); +static DECLARE_TLV_DB_SCALE(adcvol_scale, -7125, 38, 0); +static DECLARE_TLV_DB_SCALE(invol_scale, -1725, 75, 0); +static DECLARE_TLV_DB_SCALE(mic_boost_scale, 0, 1000, 0); +static DECLARE_TLV_DB_MINMAX(mugain_scale, 0, 4650); +static DECLARE_TLV_DB_MINMAX(compth_scale, -9562, 0); + +static const struct soc_enum eq1_band_enable_enum = + SOC_ENUM_SINGLE(R_CONFIG1, FB_CONFIG1_EQ1_BE, + ARRAY_SIZE(eq_band_enable_text), eq_band_enable_text); + +static const struct soc_enum eq2_band_enable_enum = + SOC_ENUM_SINGLE(R_CONFIG1, FB_CONFIG1_EQ2_BE, + ARRAY_SIZE(eq_band_enable_text), eq_band_enable_text); + +static const struct soc_enum cle_level_detection_enum = + SOC_ENUM_SINGLE(R_CLECTL, FB_CLECTL_LVL_MODE, + ARRAY_SIZE(level_detection_text), + level_detection_text); + +static const struct soc_enum cle_level_detection_window_enum = + SOC_ENUM_SINGLE(R_CLECTL, FB_CLECTL_WINDOWSEL, + ARRAY_SIZE(level_detection_window_text), + level_detection_window_text); + +static const struct soc_enum mbc_level_detection_enums[] = { + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_LVLMODE1, + ARRAY_SIZE(level_detection_text), + level_detection_text), + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_LVLMODE2, + ARRAY_SIZE(level_detection_text), + level_detection_text), + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_LVLMODE3, + ARRAY_SIZE(level_detection_text), + level_detection_text), +}; + +static const struct soc_enum mbc_level_detection_window_enums[] = { + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_WINSEL1, + ARRAY_SIZE(level_detection_window_text), + level_detection_window_text), + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_WINSEL2, + ARRAY_SIZE(level_detection_window_text), + level_detection_window_text), + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_WINSEL3, + ARRAY_SIZE(level_detection_window_text), + level_detection_window_text), +}; + +static const struct soc_enum compressor_ratio_enum = + SOC_ENUM_SINGLE(R_CMPRAT, FB_CMPRAT, + ARRAY_SIZE(compressor_ratio_text), compressor_ratio_text); + +static const struct soc_enum dac_mbc1_compressor_ratio_enum = + SOC_ENUM_SINGLE(R_DACMBCRAT1, FB_DACMBCRAT1_RATIO, + ARRAY_SIZE(compressor_ratio_text), compressor_ratio_text); + +static const struct soc_enum dac_mbc2_compressor_ratio_enum = + SOC_ENUM_SINGLE(R_DACMBCRAT2, FB_DACMBCRAT2_RATIO, + ARRAY_SIZE(compressor_ratio_text), compressor_ratio_text); + +static const struct soc_enum dac_mbc3_compressor_ratio_enum = + SOC_ENUM_SINGLE(R_DACMBCRAT3, FB_DACMBCRAT3_RATIO, + ARRAY_SIZE(compressor_ratio_text), compressor_ratio_text); + +static int bytes_info_ext(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *ucontrol) +{ + struct coeff_ram_ctl *ctl = + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + + ucontrol->type = SNDRV_CTL_ELEM_TYPE_BYTES; + ucontrol->count = params->max; + + return 0; +} + +#define COEFF_RAM_CTL(xname, xcount, xaddr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = bytes_info_ext, \ + .get = coeff_ram_get, .put = coeff_ram_put, \ + .private_value = (unsigned long)&(struct coeff_ram_ctl) { \ + .addr = xaddr, \ + .bytes_ext = {.max = xcount, }, \ + } \ +} + +static const struct snd_kcontrol_new tscs42xx_snd_controls[] = { + /* Volumes */ + SOC_DOUBLE_R_TLV("Headphone Volume", R_HPVOLL, R_HPVOLR, + FB_HPVOLL, 0x7F, 0, hpvol_scale), + SOC_DOUBLE_R_TLV("Speaker Volume", R_SPKVOLL, R_SPKVOLR, + FB_SPKVOLL, 0x7F, 0, spkvol_scale), + SOC_DOUBLE_R_TLV("Master Volume", R_DACVOLL, R_DACVOLR, + FB_DACVOLL, 0xFF, 0, dacvol_scale), + SOC_DOUBLE_R_TLV("PCM Volume", R_ADCVOLL, R_ADCVOLR, + FB_ADCVOLL, 0xFF, 0, adcvol_scale), + SOC_DOUBLE_R_TLV("Input Volume", R_INVOLL, R_INVOLR, + FB_INVOLL, 0x3F, 0, invol_scale), + + /* INSEL */ + SOC_DOUBLE_R_TLV("Mic Boost Volume", R_INSELL, R_INSELR, + FB_INSELL_MICBSTL, FV_INSELL_MICBSTL_30DB, + 0, mic_boost_scale), + + /* Input Channel Map */ + SOC_ENUM("Input Channel Map", ch_map_select_enum), + + /* Mic Bias */ + SOC_SINGLE("Mic Bias Boost Switch", 0x71, 0x07, 1, 0), + + /* Headphone Auto Switching */ + SOC_SINGLE("Headphone Auto Switching Switch", + R_CTL, FB_CTL_HPSWEN, 1, 0), + SOC_SINGLE("Headphone Detect Polarity Toggle Switch", + R_CTL, FB_CTL_HPSWPOL, 1, 0), + + /* Coefficient Ram */ + COEFF_RAM_CTL("Cascade1L BiQuad1", BIQUAD_SIZE, 0x00), + COEFF_RAM_CTL("Cascade1L BiQuad2", BIQUAD_SIZE, 0x05), + COEFF_RAM_CTL("Cascade1L BiQuad3", BIQUAD_SIZE, 0x0a), + COEFF_RAM_CTL("Cascade1L BiQuad4", BIQUAD_SIZE, 0x0f), + COEFF_RAM_CTL("Cascade1L BiQuad5", BIQUAD_SIZE, 0x14), + COEFF_RAM_CTL("Cascade1L BiQuad6", BIQUAD_SIZE, 0x19), + + COEFF_RAM_CTL("Cascade1R BiQuad1", BIQUAD_SIZE, 0x20), + COEFF_RAM_CTL("Cascade1R BiQuad2", BIQUAD_SIZE, 0x25), + COEFF_RAM_CTL("Cascade1R BiQuad3", BIQUAD_SIZE, 0x2a), + COEFF_RAM_CTL("Cascade1R BiQuad4", BIQUAD_SIZE, 0x2f), + COEFF_RAM_CTL("Cascade1R BiQuad5", BIQUAD_SIZE, 0x34), + COEFF_RAM_CTL("Cascade1R BiQuad6", BIQUAD_SIZE, 0x39), + + COEFF_RAM_CTL("Cascade1L Prescale", COEFF_SIZE, 0x1f), + COEFF_RAM_CTL("Cascade1R Prescale", COEFF_SIZE, 0x3f), + + COEFF_RAM_CTL("Cascade2L BiQuad1", BIQUAD_SIZE, 0x40), + COEFF_RAM_CTL("Cascade2L BiQuad2", BIQUAD_SIZE, 0x45), + COEFF_RAM_CTL("Cascade2L BiQuad3", BIQUAD_SIZE, 0x4a), + COEFF_RAM_CTL("Cascade2L BiQuad4", BIQUAD_SIZE, 0x4f), + COEFF_RAM_CTL("Cascade2L BiQuad5", BIQUAD_SIZE, 0x54), + COEFF_RAM_CTL("Cascade2L BiQuad6", BIQUAD_SIZE, 0x59), + + COEFF_RAM_CTL("Cascade2R BiQuad1", BIQUAD_SIZE, 0x60), + COEFF_RAM_CTL("Cascade2R BiQuad2", BIQUAD_SIZE, 0x65), + COEFF_RAM_CTL("Cascade2R BiQuad3", BIQUAD_SIZE, 0x6a), + COEFF_RAM_CTL("Cascade2R BiQuad4", BIQUAD_SIZE, 0x6f), + COEFF_RAM_CTL("Cascade2R BiQuad5", BIQUAD_SIZE, 0x74), + COEFF_RAM_CTL("Cascade2R BiQuad6", BIQUAD_SIZE, 0x79), + + COEFF_RAM_CTL("Cascade2L Prescale", COEFF_SIZE, 0x5f), + COEFF_RAM_CTL("Cascade2R Prescale", COEFF_SIZE, 0x7f), + + COEFF_RAM_CTL("Bass Extraction BiQuad1", BIQUAD_SIZE, 0x80), + COEFF_RAM_CTL("Bass Extraction BiQuad2", BIQUAD_SIZE, 0x85), + + COEFF_RAM_CTL("Bass Non Linear Function 1", COEFF_SIZE, 0x8a), + COEFF_RAM_CTL("Bass Non Linear Function 2", COEFF_SIZE, 0x8b), + + COEFF_RAM_CTL("Bass Limiter BiQuad", BIQUAD_SIZE, 0x8c), + + COEFF_RAM_CTL("Bass Cut Off BiQuad", BIQUAD_SIZE, 0x91), + + COEFF_RAM_CTL("Bass Mix", COEFF_SIZE, 0x96), + + COEFF_RAM_CTL("Treb Extraction BiQuad1", BIQUAD_SIZE, 0x97), + COEFF_RAM_CTL("Treb Extraction BiQuad2", BIQUAD_SIZE, 0x9c), + + COEFF_RAM_CTL("Treb Non Linear Function 1", COEFF_SIZE, 0xa1), + COEFF_RAM_CTL("Treb Non Linear Function 2", COEFF_SIZE, 0xa2), + + COEFF_RAM_CTL("Treb Limiter BiQuad", BIQUAD_SIZE, 0xa3), + + COEFF_RAM_CTL("Treb Cut Off BiQuad", BIQUAD_SIZE, 0xa8), + + COEFF_RAM_CTL("Treb Mix", COEFF_SIZE, 0xad), + + COEFF_RAM_CTL("3D", COEFF_SIZE, 0xae), + + COEFF_RAM_CTL("3D Mix", COEFF_SIZE, 0xaf), + + COEFF_RAM_CTL("MBC1 BiQuad1", BIQUAD_SIZE, 0xb0), + COEFF_RAM_CTL("MBC1 BiQuad2", BIQUAD_SIZE, 0xb5), + + COEFF_RAM_CTL("MBC2 BiQuad1", BIQUAD_SIZE, 0xba), + COEFF_RAM_CTL("MBC2 BiQuad2", BIQUAD_SIZE, 0xbf), + + COEFF_RAM_CTL("MBC3 BiQuad1", BIQUAD_SIZE, 0xc4), + COEFF_RAM_CTL("MBC3 BiQuad2", BIQUAD_SIZE, 0xc9), + + /* EQ */ + SOC_SINGLE("EQ1 Switch", R_CONFIG1, FB_CONFIG1_EQ1_EN, 1, 0), + SOC_SINGLE("EQ2 Switch", R_CONFIG1, FB_CONFIG1_EQ2_EN, 1, 0), + SOC_ENUM("EQ1 Band Enable", eq1_band_enable_enum), + SOC_ENUM("EQ2 Band Enable", eq2_band_enable_enum), + + /* CLE */ + SOC_ENUM("CLE Level Detect", + cle_level_detection_enum), + SOC_ENUM("CLE Level Detect Win", + cle_level_detection_window_enum), + SOC_SINGLE("Expander Switch", + R_CLECTL, FB_CLECTL_EXP_EN, 1, 0), + SOC_SINGLE("Limiter Switch", + R_CLECTL, FB_CLECTL_LIMIT_EN, 1, 0), + SOC_SINGLE("Comp Switch", + R_CLECTL, FB_CLECTL_COMP_EN, 1, 0), + SOC_SINGLE_TLV("CLE Make-Up Gain Volume", + R_MUGAIN, FB_MUGAIN_CLEMUG, 0x1f, 0, mugain_scale), + SOC_SINGLE_TLV("Comp Thresh Volume", + R_COMPTH, FB_COMPTH, 0xff, 0, compth_scale), + SOC_ENUM("Comp Ratio", compressor_ratio_enum), + SND_SOC_BYTES("Comp Atk Time", R_CATKTCL, 2), + + /* Effects */ + SOC_SINGLE("3D Switch", R_FXCTL, FB_FXCTL_3DEN, 1, 0), + SOC_SINGLE("Treble Switch", R_FXCTL, FB_FXCTL_TEEN, 1, 0), + SOC_SINGLE("Treble Bypass Switch", R_FXCTL, FB_FXCTL_TNLFBYPASS, 1, 0), + SOC_SINGLE("Bass Switch", R_FXCTL, FB_FXCTL_BEEN, 1, 0), + SOC_SINGLE("Bass Bypass Switch", R_FXCTL, FB_FXCTL_BNLFBYPASS, 1, 0), + + /* MBC */ + SOC_SINGLE("MBC Band1 Switch", R_DACMBCEN, FB_DACMBCEN_MBCEN1, 1, 0), + SOC_SINGLE("MBC Band2 Switch", R_DACMBCEN, FB_DACMBCEN_MBCEN2, 1, 0), + SOC_SINGLE("MBC Band3 Switch", R_DACMBCEN, FB_DACMBCEN_MBCEN3, 1, 0), + SOC_ENUM("MBC Band1 Level Detect", + mbc_level_detection_enums[0]), + SOC_ENUM("MBC Band2 Level Detect", + mbc_level_detection_enums[1]), + SOC_ENUM("MBC Band3 Level Detect", + mbc_level_detection_enums[2]), + SOC_ENUM("MBC Band1 Level Detect Win", + mbc_level_detection_window_enums[0]), + SOC_ENUM("MBC Band2 Level Detect Win", + mbc_level_detection_window_enums[1]), + SOC_ENUM("MBC Band3 Level Detect Win", + mbc_level_detection_window_enums[2]), + + SOC_SINGLE("MBC1 Phase Invert Switch", + R_DACMBCMUG1, FB_DACMBCMUG1_PHASE, 1, 0), + SOC_SINGLE_TLV("DAC MBC1 Make-Up Gain Volume", + R_DACMBCMUG1, FB_DACMBCMUG1_MUGAIN, 0x1f, 0, mugain_scale), + SOC_SINGLE_TLV("DAC MBC1 Comp Thresh Volume", + R_DACMBCTHR1, FB_DACMBCTHR1_THRESH, 0xff, 0, compth_scale), + SOC_ENUM("DAC MBC1 Comp Ratio", + dac_mbc1_compressor_ratio_enum), + SND_SOC_BYTES("DAC MBC1 Comp Atk Time", R_DACMBCATK1L, 2), + SND_SOC_BYTES("DAC MBC1 Comp Rel Time Const", + R_DACMBCREL1L, 2), + + SOC_SINGLE("MBC2 Phase Invert Switch", + R_DACMBCMUG2, FB_DACMBCMUG2_PHASE, 1, 0), + SOC_SINGLE_TLV("DAC MBC2 Make-Up Gain Volume", + R_DACMBCMUG2, FB_DACMBCMUG2_MUGAIN, 0x1f, 0, mugain_scale), + SOC_SINGLE_TLV("DAC MBC2 Comp Thresh Volume", + R_DACMBCTHR2, FB_DACMBCTHR2_THRESH, 0xff, 0, compth_scale), + SOC_ENUM("DAC MBC2 Comp Ratio", + dac_mbc2_compressor_ratio_enum), + SND_SOC_BYTES("DAC MBC2 Comp Atk Time", R_DACMBCATK2L, 2), + SND_SOC_BYTES("DAC MBC2 Comp Rel Time Const", + R_DACMBCREL2L, 2), + + SOC_SINGLE("MBC3 Phase Invert Switch", + R_DACMBCMUG3, FB_DACMBCMUG3_PHASE, 1, 0), + SOC_SINGLE_TLV("DAC MBC3 Make-Up Gain Volume", + R_DACMBCMUG3, FB_DACMBCMUG3_MUGAIN, 0x1f, 0, mugain_scale), + SOC_SINGLE_TLV("DAC MBC3 Comp Thresh Volume", + R_DACMBCTHR3, FB_DACMBCTHR3_THRESH, 0xff, 0, compth_scale), + SOC_ENUM("DAC MBC3 Comp Ratio", + dac_mbc3_compressor_ratio_enum), + SND_SOC_BYTES("DAC MBC3 Comp Atk Time", R_DACMBCATK3L, 2), + SND_SOC_BYTES("DAC MBC3 Comp Rel Time Const", + R_DACMBCREL3L, 2), +}; + +static int setup_sample_format(struct snd_soc_component *component, + snd_pcm_format_t format) +{ + unsigned int width; + int ret; + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + width = RV_AIC1_WL_16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + width = RV_AIC1_WL_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + width = RV_AIC1_WL_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + width = RV_AIC1_WL_32; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unsupported format width (%d)\n", ret); + return ret; + } + ret = snd_soc_component_update_bits(component, + R_AIC1, RM_AIC1_WL, width); + if (ret < 0) { + dev_err(component->dev, + "Failed to set sample width (%d)\n", ret); + return ret; + } + + return 0; +} + +static int setup_sample_rate(struct snd_soc_component *component, + unsigned int rate) +{ + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + unsigned int br, bm; + int ret; + + switch (rate) { + case 8000: + br = RV_DACSR_DBR_32; + bm = RV_DACSR_DBM_PT25; + break; + case 16000: + br = RV_DACSR_DBR_32; + bm = RV_DACSR_DBM_PT5; + break; + case 24000: + br = RV_DACSR_DBR_48; + bm = RV_DACSR_DBM_PT5; + break; + case 32000: + br = RV_DACSR_DBR_32; + bm = RV_DACSR_DBM_1; + break; + case 48000: + br = RV_DACSR_DBR_48; + bm = RV_DACSR_DBM_1; + break; + case 96000: + br = RV_DACSR_DBR_48; + bm = RV_DACSR_DBM_2; + break; + case 11025: + br = RV_DACSR_DBR_44_1; + bm = RV_DACSR_DBM_PT25; + break; + case 22050: + br = RV_DACSR_DBR_44_1; + bm = RV_DACSR_DBM_PT5; + break; + case 44100: + br = RV_DACSR_DBR_44_1; + bm = RV_DACSR_DBM_1; + break; + case 88200: + br = RV_DACSR_DBR_44_1; + bm = RV_DACSR_DBM_2; + break; + default: + dev_err(component->dev, "Unsupported sample rate %d\n", rate); + return -EINVAL; + } + + /* DAC and ADC share bit and frame clock */ + ret = snd_soc_component_update_bits(component, + R_DACSR, RM_DACSR_DBR, br); + if (ret < 0) { + dev_err(component->dev, + "Failed to update register (%d)\n", ret); + return ret; + } + ret = snd_soc_component_update_bits(component, + R_DACSR, RM_DACSR_DBM, bm); + if (ret < 0) { + dev_err(component->dev, + "Failed to update register (%d)\n", ret); + return ret; + } + ret = snd_soc_component_update_bits(component, + R_ADCSR, RM_DACSR_DBR, br); + if (ret < 0) { + dev_err(component->dev, + "Failed to update register (%d)\n", ret); + return ret; + } + ret = snd_soc_component_update_bits(component, + R_ADCSR, RM_DACSR_DBM, bm); + if (ret < 0) { + dev_err(component->dev, + "Failed to update register (%d)\n", ret); + return ret; + } + + mutex_lock(&tscs42xx->audio_params_lock); + + tscs42xx->samplerate = rate; + + mutex_unlock(&tscs42xx->audio_params_lock); + + return 0; +} + +struct reg_setting { + unsigned int addr; + unsigned int val; + unsigned int mask; +}; + +#define PLL_REG_SETTINGS_COUNT 13 +struct pll_ctl { + int input_freq; + struct reg_setting settings[PLL_REG_SETTINGS_COUNT]; +}; + +#define PLL_CTL(f, rt, rd, r1b_l, r9, ra, rb, \ + rc, r12, r1b_h, re, rf, r10, r11) \ + { \ + .input_freq = f, \ + .settings = { \ + {R_TIMEBASE, rt, 0xFF}, \ + {R_PLLCTLD, rd, 0xFF}, \ + {R_PLLCTL1B, r1b_l, 0x0F}, \ + {R_PLLCTL9, r9, 0xFF}, \ + {R_PLLCTLA, ra, 0xFF}, \ + {R_PLLCTLB, rb, 0xFF}, \ + {R_PLLCTLC, rc, 0xFF}, \ + {R_PLLCTL12, r12, 0xFF}, \ + {R_PLLCTL1B, r1b_h, 0xF0}, \ + {R_PLLCTLE, re, 0xFF}, \ + {R_PLLCTLF, rf, 0xFF}, \ + {R_PLLCTL10, r10, 0xFF}, \ + {R_PLLCTL11, r11, 0xFF}, \ + }, \ + } + +static const struct pll_ctl pll_ctls[] = { + PLL_CTL(1411200, 0x05, + 0x39, 0x04, 0x07, 0x02, 0xC3, 0x04, + 0x1B, 0x10, 0x03, 0x03, 0xD0, 0x02), + PLL_CTL(1536000, 0x05, + 0x1A, 0x04, 0x02, 0x03, 0xE0, 0x01, + 0x1A, 0x10, 0x02, 0x03, 0xB9, 0x01), + PLL_CTL(2822400, 0x0A, + 0x23, 0x04, 0x07, 0x04, 0xC3, 0x04, + 0x22, 0x10, 0x05, 0x03, 0x58, 0x02), + PLL_CTL(3072000, 0x0B, + 0x22, 0x04, 0x07, 0x03, 0x48, 0x03, + 0x1A, 0x10, 0x04, 0x03, 0xB9, 0x01), + PLL_CTL(5644800, 0x15, + 0x23, 0x04, 0x0E, 0x04, 0xC3, 0x04, + 0x1A, 0x10, 0x08, 0x03, 0xE0, 0x01), + PLL_CTL(6144000, 0x17, + 0x1A, 0x04, 0x08, 0x03, 0xE0, 0x01, + 0x1A, 0x10, 0x08, 0x03, 0xB9, 0x01), + PLL_CTL(12000000, 0x2E, + 0x1B, 0x04, 0x19, 0x03, 0x00, 0x03, + 0x2A, 0x10, 0x19, 0x05, 0x98, 0x04), + PLL_CTL(19200000, 0x4A, + 0x13, 0x04, 0x14, 0x03, 0x80, 0x01, + 0x1A, 0x10, 0x19, 0x03, 0xB9, 0x01), + PLL_CTL(22000000, 0x55, + 0x2A, 0x04, 0x37, 0x05, 0x00, 0x06, + 0x22, 0x10, 0x26, 0x03, 0x49, 0x02), + PLL_CTL(22579200, 0x57, + 0x22, 0x04, 0x31, 0x03, 0x20, 0x03, + 0x1A, 0x10, 0x1D, 0x03, 0xB3, 0x01), + PLL_CTL(24000000, 0x5D, + 0x13, 0x04, 0x19, 0x03, 0x80, 0x01, + 0x1B, 0x10, 0x19, 0x05, 0x4C, 0x02), + PLL_CTL(24576000, 0x5F, + 0x13, 0x04, 0x1D, 0x03, 0xB3, 0x01, + 0x22, 0x10, 0x40, 0x03, 0x72, 0x03), + PLL_CTL(27000000, 0x68, + 0x22, 0x04, 0x4B, 0x03, 0x00, 0x04, + 0x2A, 0x10, 0x7D, 0x03, 0x20, 0x06), + PLL_CTL(36000000, 0x8C, + 0x1B, 0x04, 0x4B, 0x03, 0x00, 0x03, + 0x2A, 0x10, 0x7D, 0x03, 0x98, 0x04), + PLL_CTL(25000000, 0x61, + 0x1B, 0x04, 0x37, 0x03, 0x2B, 0x03, + 0x1A, 0x10, 0x2A, 0x03, 0x39, 0x02), + PLL_CTL(26000000, 0x65, + 0x23, 0x04, 0x41, 0x05, 0x00, 0x06, + 0x1A, 0x10, 0x26, 0x03, 0xEF, 0x01), + PLL_CTL(12288000, 0x2F, + 0x1A, 0x04, 0x12, 0x03, 0x1C, 0x02, + 0x22, 0x10, 0x20, 0x03, 0x72, 0x03), + PLL_CTL(40000000, 0x9B, + 0x22, 0x08, 0x7D, 0x03, 0x80, 0x04, + 0x23, 0x10, 0x7D, 0x05, 0xE4, 0x06), + PLL_CTL(512000, 0x01, + 0x22, 0x04, 0x01, 0x03, 0xD0, 0x02, + 0x1B, 0x10, 0x01, 0x04, 0x72, 0x03), + PLL_CTL(705600, 0x02, + 0x22, 0x04, 0x02, 0x03, 0x15, 0x04, + 0x22, 0x10, 0x01, 0x04, 0x80, 0x02), + PLL_CTL(1024000, 0x03, + 0x22, 0x04, 0x02, 0x03, 0xD0, 0x02, + 0x1B, 0x10, 0x02, 0x04, 0x72, 0x03), + PLL_CTL(2048000, 0x07, + 0x22, 0x04, 0x04, 0x03, 0xD0, 0x02, + 0x1B, 0x10, 0x04, 0x04, 0x72, 0x03), + PLL_CTL(2400000, 0x08, + 0x22, 0x04, 0x05, 0x03, 0x00, 0x03, + 0x23, 0x10, 0x05, 0x05, 0x98, 0x04), +}; + +static const struct pll_ctl *get_pll_ctl(int input_freq) +{ + int i; + const struct pll_ctl *pll_ctl = NULL; + + for (i = 0; i < ARRAY_SIZE(pll_ctls); ++i) + if (input_freq == pll_ctls[i].input_freq) { + pll_ctl = &pll_ctls[i]; + break; + } + + return pll_ctl; +} + +static int set_pll_ctl_from_input_freq(struct snd_soc_component *component, + const int input_freq) +{ + int ret; + int i; + const struct pll_ctl *pll_ctl; + + pll_ctl = get_pll_ctl(input_freq); + if (!pll_ctl) { + ret = -EINVAL; + dev_err(component->dev, "No PLL input entry for %d (%d)\n", + input_freq, ret); + return ret; + } + + for (i = 0; i < PLL_REG_SETTINGS_COUNT; ++i) { + ret = snd_soc_component_update_bits(component, + pll_ctl->settings[i].addr, + pll_ctl->settings[i].mask, + pll_ctl->settings[i].val); + if (ret < 0) { + dev_err(component->dev, "Failed to set pll ctl (%d)\n", + ret); + return ret; + } + } + + return 0; +} + +static int tscs42xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + int ret; + + ret = setup_sample_format(component, params_format(params)); + if (ret < 0) { + dev_err(component->dev, "Failed to setup sample format (%d)\n", + ret); + return ret; + } + + ret = setup_sample_rate(component, params_rate(params)); + if (ret < 0) { + dev_err(component->dev, + "Failed to setup sample rate (%d)\n", ret); + return ret; + } + + return 0; +} + +static inline int dac_mute(struct snd_soc_component *component) +{ + int ret; + + ret = snd_soc_component_update_bits(component, + R_CNVRTR1, RM_CNVRTR1_DACMU, + RV_CNVRTR1_DACMU_ENABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to mute DAC (%d)\n", + ret); + return ret; + } + + return 0; +} + +static inline int dac_unmute(struct snd_soc_component *component) +{ + int ret; + + ret = snd_soc_component_update_bits(component, + R_CNVRTR1, RM_CNVRTR1_DACMU, + RV_CNVRTR1_DACMU_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to unmute DAC (%d)\n", + ret); + return ret; + } + + return 0; +} + +static inline int adc_mute(struct snd_soc_component *component) +{ + int ret; + + ret = snd_soc_component_update_bits(component, + R_CNVRTR0, RM_CNVRTR0_ADCMU, RV_CNVRTR0_ADCMU_ENABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to mute ADC (%d)\n", + ret); + return ret; + } + + return 0; +} + +static inline int adc_unmute(struct snd_soc_component *component) +{ + int ret; + + ret = snd_soc_component_update_bits(component, + R_CNVRTR0, RM_CNVRTR0_ADCMU, RV_CNVRTR0_ADCMU_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to unmute ADC (%d)\n", + ret); + return ret; + } + + return 0; +} + +static int tscs42xx_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_component *component = dai->component; + int ret; + + if (mute) + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = dac_mute(component); + else + ret = adc_mute(component); + else + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = dac_unmute(component); + else + ret = adc_unmute(component); + + return ret; +} + +static int tscs42xx_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + int ret; + + /* Slave mode not supported since it needs always-on frame clock */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ret = snd_soc_component_update_bits(component, + R_AIC1, RM_AIC1_MS, RV_AIC1_MS_MASTER); + if (ret < 0) { + dev_err(component->dev, + "Failed to set codec DAI master (%d)\n", ret); + return ret; + } + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unsupported format (%d)\n", ret); + return ret; + } + + return 0; +} + +static int tscs42xx_set_dai_bclk_ratio(struct snd_soc_dai *codec_dai, + unsigned int ratio) +{ + struct snd_soc_component *component = codec_dai->component; + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + unsigned int value; + int ret = 0; + + switch (ratio) { + case 32: + value = RV_DACSR_DBCM_32; + break; + case 40: + value = RV_DACSR_DBCM_40; + break; + case 64: + value = RV_DACSR_DBCM_64; + break; + default: + dev_err(component->dev, "Unsupported bclk ratio (%d)\n", ret); + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, + R_DACSR, RM_DACSR_DBCM, value); + if (ret < 0) { + dev_err(component->dev, + "Failed to set DAC BCLK ratio (%d)\n", ret); + return ret; + } + ret = snd_soc_component_update_bits(component, + R_ADCSR, RM_ADCSR_ABCM, value); + if (ret < 0) { + dev_err(component->dev, + "Failed to set ADC BCLK ratio (%d)\n", ret); + return ret; + } + + mutex_lock(&tscs42xx->audio_params_lock); + + tscs42xx->bclk_ratio = ratio; + + mutex_unlock(&tscs42xx->audio_params_lock); + + return 0; +} + +static const struct snd_soc_dai_ops tscs42xx_dai_ops = { + .hw_params = tscs42xx_hw_params, + .mute_stream = tscs42xx_mute_stream, + .set_fmt = tscs42xx_set_dai_fmt, + .set_bclk_ratio = tscs42xx_set_dai_bclk_ratio, +}; + +static int part_is_valid(struct tscs42xx *tscs42xx) +{ + int val; + int ret; + unsigned int reg; + + ret = regmap_read(tscs42xx->regmap, R_DEVIDH, ®); + if (ret < 0) + return ret; + + val = reg << 8; + ret = regmap_read(tscs42xx->regmap, R_DEVIDL, ®); + if (ret < 0) + return ret; + + val |= reg; + + switch (val) { + case 0x4A74: + case 0x4A73: + return true; + default: + return false; + }; +} + +static int set_sysclk(struct snd_soc_component *component) +{ + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + unsigned long freq; + int ret; + + switch (tscs42xx->sysclk_src_id) { + case TSCS42XX_PLL_SRC_XTAL: + case TSCS42XX_PLL_SRC_MCLK1: + ret = snd_soc_component_write(component, R_PLLREFSEL, + RV_PLLREFSEL_PLL1_REF_SEL_XTAL_MCLK1 | + RV_PLLREFSEL_PLL2_REF_SEL_XTAL_MCLK1); + if (ret < 0) { + dev_err(component->dev, + "Failed to set pll reference input (%d)\n", + ret); + return ret; + } + break; + case TSCS42XX_PLL_SRC_MCLK2: + ret = snd_soc_component_write(component, R_PLLREFSEL, + RV_PLLREFSEL_PLL1_REF_SEL_MCLK2 | + RV_PLLREFSEL_PLL2_REF_SEL_MCLK2); + if (ret < 0) { + dev_err(component->dev, + "Failed to set PLL reference (%d)\n", ret); + return ret; + } + break; + default: + dev_err(component->dev, "pll src is unsupported\n"); + return -EINVAL; + } + + freq = clk_get_rate(tscs42xx->sysclk); + ret = set_pll_ctl_from_input_freq(component, freq); + if (ret < 0) { + dev_err(component->dev, + "Failed to setup PLL input freq (%d)\n", ret); + return ret; + } + + return 0; +} + +static int tscs42xx_probe(struct snd_soc_component *component) +{ + return set_sysclk(component); +} + +static const struct snd_soc_component_driver soc_codec_dev_tscs42xx = { + .probe = tscs42xx_probe, + .dapm_widgets = tscs42xx_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tscs42xx_dapm_widgets), + .dapm_routes = tscs42xx_intercon, + .num_dapm_routes = ARRAY_SIZE(tscs42xx_intercon), + .controls = tscs42xx_snd_controls, + .num_controls = ARRAY_SIZE(tscs42xx_snd_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static inline void init_coeff_ram_cache(struct tscs42xx *tscs42xx) +{ + static const u8 norm_addrs[] = { + 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x19, 0x1f, 0x20, 0x25, 0x2a, + 0x2f, 0x34, 0x39, 0x3f, 0x40, 0x45, 0x4a, 0x4f, 0x54, 0x59, + 0x5f, 0x60, 0x65, 0x6a, 0x6f, 0x74, 0x79, 0x7f, 0x80, 0x85, + 0x8c, 0x91, 0x96, 0x97, 0x9c, 0xa3, 0xa8, 0xad, 0xaf, 0xb0, + 0xb5, 0xba, 0xbf, 0xc4, 0xc9, + }; + u8 *coeff_ram = tscs42xx->coeff_ram; + int i; + + for (i = 0; i < ARRAY_SIZE(norm_addrs); i++) + coeff_ram[((norm_addrs[i] + 1) * COEFF_SIZE) - 1] = 0x40; +} + +#define TSCS42XX_RATES SNDRV_PCM_RATE_8000_96000 + +#define TSCS42XX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver tscs42xx_dai = { + .name = "tscs42xx-HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = TSCS42XX_RATES, + .formats = TSCS42XX_FORMATS,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 2, + .rates = TSCS42XX_RATES, + .formats = TSCS42XX_FORMATS,}, + .ops = &tscs42xx_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, +}; + +static const struct reg_sequence tscs42xx_patch[] = { + { R_AIC2, RV_AIC2_BLRCM_DAC_BCLK_LRCLK_SHARED }, +}; + +static char const * const src_names[TSCS42XX_PLL_SRC_CNT] = { + "xtal", "mclk1", "mclk2"}; + +static int tscs42xx_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tscs42xx *tscs42xx; + int src; + int ret; + + tscs42xx = devm_kzalloc(&i2c->dev, sizeof(*tscs42xx), GFP_KERNEL); + if (!tscs42xx) { + ret = -ENOMEM; + dev_err(&i2c->dev, + "Failed to allocate memory for data (%d)\n", ret); + return ret; + } + i2c_set_clientdata(i2c, tscs42xx); + + for (src = TSCS42XX_PLL_SRC_XTAL; src < TSCS42XX_PLL_SRC_CNT; src++) { + tscs42xx->sysclk = devm_clk_get(&i2c->dev, src_names[src]); + if (!IS_ERR(tscs42xx->sysclk)) { + break; + } else if (PTR_ERR(tscs42xx->sysclk) != -ENOENT) { + ret = PTR_ERR(tscs42xx->sysclk); + dev_err(&i2c->dev, "Failed to get sysclk (%d)\n", ret); + return ret; + } + } + if (src == TSCS42XX_PLL_SRC_CNT) { + ret = -EINVAL; + dev_err(&i2c->dev, "Failed to get a valid clock name (%d)\n", + ret); + return ret; + } + tscs42xx->sysclk_src_id = src; + + tscs42xx->regmap = devm_regmap_init_i2c(i2c, &tscs42xx_regmap); + if (IS_ERR(tscs42xx->regmap)) { + ret = PTR_ERR(tscs42xx->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap (%d)\n", ret); + return ret; + } + + init_coeff_ram_cache(tscs42xx); + + ret = part_is_valid(tscs42xx); + if (ret <= 0) { + dev_err(&i2c->dev, "No valid part (%d)\n", ret); + ret = -ENODEV; + return ret; + } + + ret = regmap_write(tscs42xx->regmap, R_RESET, RV_RESET_ENABLE); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to reset device (%d)\n", ret); + return ret; + } + + ret = regmap_register_patch(tscs42xx->regmap, tscs42xx_patch, + ARRAY_SIZE(tscs42xx_patch)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to apply patch (%d)\n", ret); + return ret; + } + + mutex_init(&tscs42xx->audio_params_lock); + mutex_init(&tscs42xx->coeff_ram_lock); + mutex_init(&tscs42xx->pll_lock); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_codec_dev_tscs42xx, &tscs42xx_dai, 1); + if (ret) { + dev_err(&i2c->dev, "Failed to register codec (%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct i2c_device_id tscs42xx_i2c_id[] = { + { "tscs42A1", 0 }, + { "tscs42A2", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tscs42xx_i2c_id); + +static const struct of_device_id tscs42xx_of_match[] = { + { .compatible = "tempo,tscs42A1", }, + { .compatible = "tempo,tscs42A2", }, + { } +}; +MODULE_DEVICE_TABLE(of, tscs42xx_of_match); + +static struct i2c_driver tscs42xx_i2c_driver = { + .driver = { + .name = "tscs42xx", + .of_match_table = tscs42xx_of_match, + }, + .probe = tscs42xx_i2c_probe, + .id_table = tscs42xx_i2c_id, +}; + +module_i2c_driver(tscs42xx_i2c_driver); + +MODULE_AUTHOR("Tempo Semiconductor + +#ifndef __WOOKIE_H__ +#define __WOOKIE_H__ + +enum { + TSCS42XX_PLL_SRC_XTAL, + TSCS42XX_PLL_SRC_MCLK1, + TSCS42XX_PLL_SRC_MCLK2, + TSCS42XX_PLL_SRC_CNT, +}; + +#define R_HPVOLL 0x0 +#define R_HPVOLR 0x1 +#define R_SPKVOLL 0x2 +#define R_SPKVOLR 0x3 +#define R_DACVOLL 0x4 +#define R_DACVOLR 0x5 +#define R_ADCVOLL 0x6 +#define R_ADCVOLR 0x7 +#define R_INVOLL 0x8 +#define R_INVOLR 0x9 +#define R_INMODE 0x0B +#define R_INSELL 0x0C +#define R_INSELR 0x0D +#define R_AIC1 0x13 +#define R_AIC2 0x14 +#define R_CNVRTR0 0x16 +#define R_ADCSR 0x17 +#define R_CNVRTR1 0x18 +#define R_DACSR 0x19 +#define R_PWRM1 0x1A +#define R_PWRM2 0x1B +#define R_CTL 0x1C +#define R_CONFIG0 0x1F +#define R_CONFIG1 0x20 +#define R_DMICCTL 0x24 +#define R_CLECTL 0x25 +#define R_MUGAIN 0x26 +#define R_COMPTH 0x27 +#define R_CMPRAT 0x28 +#define R_CATKTCL 0x29 +#define R_CATKTCH 0x2A +#define R_CRELTCL 0x2B +#define R_CRELTCH 0x2C +#define R_LIMTH 0x2D +#define R_LIMTGT 0x2E +#define R_LATKTCL 0x2F +#define R_LATKTCH 0x30 +#define R_LRELTCL 0x31 +#define R_LRELTCH 0x32 +#define R_EXPTH 0x33 +#define R_EXPRAT 0x34 +#define R_XATKTCL 0x35 +#define R_XATKTCH 0x36 +#define R_XRELTCL 0x37 +#define R_XRELTCH 0x38 +#define R_FXCTL 0x39 +#define R_DACCRWRL 0x3A +#define R_DACCRWRM 0x3B +#define R_DACCRWRH 0x3C +#define R_DACCRRDL 0x3D +#define R_DACCRRDM 0x3E +#define R_DACCRRDH 0x3F +#define R_DACCRADDR 0x40 +#define R_DCOFSEL 0x41 +#define R_PLLCTL9 0x4E +#define R_PLLCTLA 0x4F +#define R_PLLCTLB 0x50 +#define R_PLLCTLC 0x51 +#define R_PLLCTLD 0x52 +#define R_PLLCTLE 0x53 +#define R_PLLCTLF 0x54 +#define R_PLLCTL10 0x55 +#define R_PLLCTL11 0x56 +#define R_PLLCTL12 0x57 +#define R_PLLCTL1B 0x60 +#define R_PLLCTL1C 0x61 +#define R_TIMEBASE 0x77 +#define R_DEVIDL 0x7D +#define R_DEVIDH 0x7E +#define R_RESET 0x80 +#define R_DACCRSTAT 0x8A +#define R_PLLCTL0 0x8E +#define R_PLLREFSEL 0x8F +#define R_DACMBCEN 0xC7 +#define R_DACMBCCTL 0xC8 +#define R_DACMBCMUG1 0xC9 +#define R_DACMBCTHR1 0xCA +#define R_DACMBCRAT1 0xCB +#define R_DACMBCATK1L 0xCC +#define R_DACMBCATK1H 0xCD +#define R_DACMBCREL1L 0xCE +#define R_DACMBCREL1H 0xCF +#define R_DACMBCMUG2 0xD0 +#define R_DACMBCTHR2 0xD1 +#define R_DACMBCRAT2 0xD2 +#define R_DACMBCATK2L 0xD3 +#define R_DACMBCATK2H 0xD4 +#define R_DACMBCREL2L 0xD5 +#define R_DACMBCREL2H 0xD6 +#define R_DACMBCMUG3 0xD7 +#define R_DACMBCTHR3 0xD8 +#define R_DACMBCRAT3 0xD9 +#define R_DACMBCATK3L 0xDA +#define R_DACMBCATK3H 0xDB +#define R_DACMBCREL3L 0xDC +#define R_DACMBCREL3H 0xDD + +/* Helpers */ +#define RM(m, b) ((m)<<(b)) +#define RV(v, b) ((v)<<(b)) + +/**************************** + * R_HPVOLL (0x0) * + ****************************/ + +/* Field Offsets */ +#define FB_HPVOLL 0 + +/* Field Masks */ +#define FM_HPVOLL 0X7F + +/* Field Values */ +#define FV_HPVOLL_P6DB 0x7F +#define FV_HPVOLL_N88PT5DB 0x1 +#define FV_HPVOLL_MUTE 0x0 + +/* Register Masks */ +#define RM_HPVOLL RM(FM_HPVOLL, FB_HPVOLL) + +/* Register Values */ +#define RV_HPVOLL_P6DB RV(FV_HPVOLL_P6DB, FB_HPVOLL) +#define RV_HPVOLL_N88PT5DB RV(FV_HPVOLL_N88PT5DB, FB_HPVOLL) +#define RV_HPVOLL_MUTE RV(FV_HPVOLL_MUTE, FB_HPVOLL) + +/**************************** + * R_HPVOLR (0x1) * + ****************************/ + +/* Field Offsets */ +#define FB_HPVOLR 0 + +/* Field Masks */ +#define FM_HPVOLR 0X7F + +/* Field Values */ +#define FV_HPVOLR_P6DB 0x7F +#define FV_HPVOLR_N88PT5DB 0x1 +#define FV_HPVOLR_MUTE 0x0 + +/* Register Masks */ +#define RM_HPVOLR RM(FM_HPVOLR, FB_HPVOLR) + +/* Register Values */ +#define RV_HPVOLR_P6DB RV(FV_HPVOLR_P6DB, FB_HPVOLR) +#define RV_HPVOLR_N88PT5DB RV(FV_HPVOLR_N88PT5DB, FB_HPVOLR) +#define RV_HPVOLR_MUTE RV(FV_HPVOLR_MUTE, FB_HPVOLR) + +/***************************** + * R_SPKVOLL (0x2) * + *****************************/ + +/* Field Offsets */ +#define FB_SPKVOLL 0 + +/* Field Masks */ +#define FM_SPKVOLL 0X7F + +/* Field Values */ +#define FV_SPKVOLL_P12DB 0x7F +#define FV_SPKVOLL_N77PT25DB 0x8 +#define FV_SPKVOLL_MUTE 0x0 + +/* Register Masks */ +#define RM_SPKVOLL RM(FM_SPKVOLL, FB_SPKVOLL) + +/* Register Values */ +#define RV_SPKVOLL_P12DB RV(FV_SPKVOLL_P12DB, FB_SPKVOLL) +#define RV_SPKVOLL_N77PT25DB \ + RV(FV_SPKVOLL_N77PT25DB, FB_SPKVOLL) + +#define RV_SPKVOLL_MUTE RV(FV_SPKVOLL_MUTE, FB_SPKVOLL) + +/***************************** + * R_SPKVOLR (0x3) * + *****************************/ + +/* Field Offsets */ +#define FB_SPKVOLR 0 + +/* Field Masks */ +#define FM_SPKVOLR 0X7F + +/* Field Values */ +#define FV_SPKVOLR_P12DB 0x7F +#define FV_SPKVOLR_N77PT25DB 0x8 +#define FV_SPKVOLR_MUTE 0x0 + +/* Register Masks */ +#define RM_SPKVOLR RM(FM_SPKVOLR, FB_SPKVOLR) + +/* Register Values */ +#define RV_SPKVOLR_P12DB RV(FV_SPKVOLR_P12DB, FB_SPKVOLR) +#define RV_SPKVOLR_N77PT25DB \ + RV(FV_SPKVOLR_N77PT25DB, FB_SPKVOLR) + +#define RV_SPKVOLR_MUTE RV(FV_SPKVOLR_MUTE, FB_SPKVOLR) + +/***************************** + * R_DACVOLL (0x4) * + *****************************/ + +/* Field Offsets */ +#define FB_DACVOLL 0 + +/* Field Masks */ +#define FM_DACVOLL 0XFF + +/* Field Values */ +#define FV_DACVOLL_0DB 0xFF +#define FV_DACVOLL_N95PT625DB 0x1 +#define FV_DACVOLL_MUTE 0x0 + +/* Register Masks */ +#define RM_DACVOLL RM(FM_DACVOLL, FB_DACVOLL) + +/* Register Values */ +#define RV_DACVOLL_0DB RV(FV_DACVOLL_0DB, FB_DACVOLL) +#define RV_DACVOLL_N95PT625DB \ + RV(FV_DACVOLL_N95PT625DB, FB_DACVOLL) + +#define RV_DACVOLL_MUTE RV(FV_DACVOLL_MUTE, FB_DACVOLL) + +/***************************** + * R_DACVOLR (0x5) * + *****************************/ + +/* Field Offsets */ +#define FB_DACVOLR 0 + +/* Field Masks */ +#define FM_DACVOLR 0XFF + +/* Field Values */ +#define FV_DACVOLR_0DB 0xFF +#define FV_DACVOLR_N95PT625DB 0x1 +#define FV_DACVOLR_MUTE 0x0 + +/* Register Masks */ +#define RM_DACVOLR RM(FM_DACVOLR, FB_DACVOLR) + +/* Register Values */ +#define RV_DACVOLR_0DB RV(FV_DACVOLR_0DB, FB_DACVOLR) +#define RV_DACVOLR_N95PT625DB \ + RV(FV_DACVOLR_N95PT625DB, FB_DACVOLR) + +#define RV_DACVOLR_MUTE RV(FV_DACVOLR_MUTE, FB_DACVOLR) + +/***************************** + * R_ADCVOLL (0x6) * + *****************************/ + +/* Field Offsets */ +#define FB_ADCVOLL 0 + +/* Field Masks */ +#define FM_ADCVOLL 0XFF + +/* Field Values */ +#define FV_ADCVOLL_P24DB 0xFF +#define FV_ADCVOLL_N71PT25DB 0x1 +#define FV_ADCVOLL_MUTE 0x0 + +/* Register Masks */ +#define RM_ADCVOLL RM(FM_ADCVOLL, FB_ADCVOLL) + +/* Register Values */ +#define RV_ADCVOLL_P24DB RV(FV_ADCVOLL_P24DB, FB_ADCVOLL) +#define RV_ADCVOLL_N71PT25DB \ + RV(FV_ADCVOLL_N71PT25DB, FB_ADCVOLL) + +#define RV_ADCVOLL_MUTE RV(FV_ADCVOLL_MUTE, FB_ADCVOLL) + +/***************************** + * R_ADCVOLR (0x7) * + *****************************/ + +/* Field Offsets */ +#define FB_ADCVOLR 0 + +/* Field Masks */ +#define FM_ADCVOLR 0XFF + +/* Field Values */ +#define FV_ADCVOLR_P24DB 0xFF +#define FV_ADCVOLR_N71PT25DB 0x1 +#define FV_ADCVOLR_MUTE 0x0 + +/* Register Masks */ +#define RM_ADCVOLR RM(FM_ADCVOLR, FB_ADCVOLR) + +/* Register Values */ +#define RV_ADCVOLR_P24DB RV(FV_ADCVOLR_P24DB, FB_ADCVOLR) +#define RV_ADCVOLR_N71PT25DB \ + RV(FV_ADCVOLR_N71PT25DB, FB_ADCVOLR) + +#define RV_ADCVOLR_MUTE RV(FV_ADCVOLR_MUTE, FB_ADCVOLR) + +/**************************** + * R_INVOLL (0x8) * + ****************************/ + +/* Field Offsets */ +#define FB_INVOLL_INMUTEL 7 +#define FB_INVOLL_IZCL 6 +#define FB_INVOLL 0 + +/* Field Masks */ +#define FM_INVOLL_INMUTEL 0X1 +#define FM_INVOLL_IZCL 0X1 +#define FM_INVOLL 0X3F + +/* Field Values */ +#define FV_INVOLL_INMUTEL_ENABLE 0x1 +#define FV_INVOLL_INMUTEL_DISABLE 0x0 +#define FV_INVOLL_IZCL_ENABLE 0x1 +#define FV_INVOLL_IZCL_DISABLE 0x0 +#define FV_INVOLL_P30DB 0x3F +#define FV_INVOLL_N17PT25DB 0x0 + +/* Register Masks */ +#define RM_INVOLL_INMUTEL \ + RM(FM_INVOLL_INMUTEL, FB_INVOLL_INMUTEL) + +#define RM_INVOLL_IZCL RM(FM_INVOLL_IZCL, FB_INVOLL_IZCL) +#define RM_INVOLL RM(FM_INVOLL, FB_INVOLL) + +/* Register Values */ +#define RV_INVOLL_INMUTEL_ENABLE \ + RV(FV_INVOLL_INMUTEL_ENABLE, FB_INVOLL_INMUTEL) + +#define RV_INVOLL_INMUTEL_DISABLE \ + RV(FV_INVOLL_INMUTEL_DISABLE, FB_INVOLL_INMUTEL) + +#define RV_INVOLL_IZCL_ENABLE \ + RV(FV_INVOLL_IZCL_ENABLE, FB_INVOLL_IZCL) + +#define RV_INVOLL_IZCL_DISABLE \ + RV(FV_INVOLL_IZCL_DISABLE, FB_INVOLL_IZCL) + +#define RV_INVOLL_P30DB RV(FV_INVOLL_P30DB, FB_INVOLL) +#define RV_INVOLL_N17PT25DB RV(FV_INVOLL_N17PT25DB, FB_INVOLL) + +/**************************** + * R_INVOLR (0x9) * + ****************************/ + +/* Field Offsets */ +#define FB_INVOLR_INMUTER 7 +#define FB_INVOLR_IZCR 6 +#define FB_INVOLR 0 + +/* Field Masks */ +#define FM_INVOLR_INMUTER 0X1 +#define FM_INVOLR_IZCR 0X1 +#define FM_INVOLR 0X3F + +/* Field Values */ +#define FV_INVOLR_INMUTER_ENABLE 0x1 +#define FV_INVOLR_INMUTER_DISABLE 0x0 +#define FV_INVOLR_IZCR_ENABLE 0x1 +#define FV_INVOLR_IZCR_DISABLE 0x0 +#define FV_INVOLR_P30DB 0x3F +#define FV_INVOLR_N17PT25DB 0x0 + +/* Register Masks */ +#define RM_INVOLR_INMUTER \ + RM(FM_INVOLR_INMUTER, FB_INVOLR_INMUTER) + +#define RM_INVOLR_IZCR RM(FM_INVOLR_IZCR, FB_INVOLR_IZCR) +#define RM_INVOLR RM(FM_INVOLR, FB_INVOLR) + +/* Register Values */ +#define RV_INVOLR_INMUTER_ENABLE \ + RV(FV_INVOLR_INMUTER_ENABLE, FB_INVOLR_INMUTER) + +#define RV_INVOLR_INMUTER_DISABLE \ + RV(FV_INVOLR_INMUTER_DISABLE, FB_INVOLR_INMUTER) + +#define RV_INVOLR_IZCR_ENABLE \ + RV(FV_INVOLR_IZCR_ENABLE, FB_INVOLR_IZCR) + +#define RV_INVOLR_IZCR_DISABLE \ + RV(FV_INVOLR_IZCR_DISABLE, FB_INVOLR_IZCR) + +#define RV_INVOLR_P30DB RV(FV_INVOLR_P30DB, FB_INVOLR) +#define RV_INVOLR_N17PT25DB RV(FV_INVOLR_N17PT25DB, FB_INVOLR) + +/***************************** + * R_INMODE (0x0B) * + *****************************/ + +/* Field Offsets */ +#define FB_INMODE_DS 0 + +/* Field Masks */ +#define FM_INMODE_DS 0X1 + +/* Field Values */ +#define FV_INMODE_DS_LRIN1 0x0 +#define FV_INMODE_DS_LRIN2 0x1 + +/* Register Masks */ +#define RM_INMODE_DS RM(FM_INMODE_DS, FB_INMODE_DS) + +/* Register Values */ +#define RV_INMODE_DS_LRIN1 \ + RV(FV_INMODE_DS_LRIN1, FB_INMODE_DS) + +#define RV_INMODE_DS_LRIN2 \ + RV(FV_INMODE_DS_LRIN2, FB_INMODE_DS) + + +/***************************** + * R_INSELL (0x0C) * + *****************************/ + +/* Field Offsets */ +#define FB_INSELL 6 +#define FB_INSELL_MICBSTL 4 + +/* Field Masks */ +#define FM_INSELL 0X3 +#define FM_INSELL_MICBSTL 0X3 + +/* Field Values */ +#define FV_INSELL_IN1 0x0 +#define FV_INSELL_IN2 0x1 +#define FV_INSELL_IN3 0x2 +#define FV_INSELL_D2S 0x3 +#define FV_INSELL_MICBSTL_OFF 0x0 +#define FV_INSELL_MICBSTL_10DB 0x1 +#define FV_INSELL_MICBSTL_20DB 0x2 +#define FV_INSELL_MICBSTL_30DB 0x3 + +/* Register Masks */ +#define RM_INSELL RM(FM_INSELL, FB_INSELL) +#define RM_INSELL_MICBSTL \ + RM(FM_INSELL_MICBSTL, FB_INSELL_MICBSTL) + + +/* Register Values */ +#define RV_INSELL_IN1 RV(FV_INSELL_IN1, FB_INSELL) +#define RV_INSELL_IN2 RV(FV_INSELL_IN2, FB_INSELL) +#define RV_INSELL_IN3 RV(FV_INSELL_IN3, FB_INSELL) +#define RV_INSELL_D2S RV(FV_INSELL_D2S, FB_INSELL) +#define RV_INSELL_MICBSTL_OFF \ + RV(FV_INSELL_MICBSTL_OFF, FB_INSELL_MICBSTL) + +#define RV_INSELL_MICBSTL_10DB \ + RV(FV_INSELL_MICBSTL_10DB, FB_INSELL_MICBSTL) + +#define RV_INSELL_MICBSTL_20DB \ + RV(FV_INSELL_MICBSTL_20DB, FB_INSELL_MICBSTL) + +#define RV_INSELL_MICBSTL_30DB \ + RV(FV_INSELL_MICBSTL_30DB, FB_INSELL_MICBSTL) + + +/***************************** + * R_INSELR (0x0D) * + *****************************/ + +/* Field Offsets */ +#define FB_INSELR 6 +#define FB_INSELR_MICBSTR 4 + +/* Field Masks */ +#define FM_INSELR 0X3 +#define FM_INSELR_MICBSTR 0X3 + +/* Field Values */ +#define FV_INSELR_IN1 0x0 +#define FV_INSELR_IN2 0x1 +#define FV_INSELR_IN3 0x2 +#define FV_INSELR_D2S 0x3 +#define FV_INSELR_MICBSTR_OFF 0x0 +#define FV_INSELR_MICBSTR_10DB 0x1 +#define FV_INSELR_MICBSTR_20DB 0x2 +#define FV_INSELR_MICBSTR_30DB 0x3 + +/* Register Masks */ +#define RM_INSELR RM(FM_INSELR, FB_INSELR) +#define RM_INSELR_MICBSTR \ + RM(FM_INSELR_MICBSTR, FB_INSELR_MICBSTR) + + +/* Register Values */ +#define RV_INSELR_IN1 RV(FV_INSELR_IN1, FB_INSELR) +#define RV_INSELR_IN2 RV(FV_INSELR_IN2, FB_INSELR) +#define RV_INSELR_IN3 RV(FV_INSELR_IN3, FB_INSELR) +#define RV_INSELR_D2S RV(FV_INSELR_D2S, FB_INSELR) +#define RV_INSELR_MICBSTR_OFF \ + RV(FV_INSELR_MICBSTR_OFF, FB_INSELR_MICBSTR) + +#define RV_INSELR_MICBSTR_10DB \ + RV(FV_INSELR_MICBSTR_10DB, FB_INSELR_MICBSTR) + +#define RV_INSELR_MICBSTR_20DB \ + RV(FV_INSELR_MICBSTR_20DB, FB_INSELR_MICBSTR) + +#define RV_INSELR_MICBSTR_30DB \ + RV(FV_INSELR_MICBSTR_30DB, FB_INSELR_MICBSTR) + + +/*************************** + * R_AIC1 (0x13) * + ***************************/ + +/* Field Offsets */ +#define FB_AIC1_BCLKINV 6 +#define FB_AIC1_MS 5 +#define FB_AIC1_LRP 4 +#define FB_AIC1_WL 2 +#define FB_AIC1_FORMAT 0 + +/* Field Masks */ +#define FM_AIC1_BCLKINV 0X1 +#define FM_AIC1_MS 0X1 +#define FM_AIC1_LRP 0X1 +#define FM_AIC1_WL 0X3 +#define FM_AIC1_FORMAT 0X3 + +/* Field Values */ +#define FV_AIC1_BCLKINV_ENABLE 0x1 +#define FV_AIC1_BCLKINV_DISABLE 0x0 +#define FV_AIC1_MS_MASTER 0x1 +#define FV_AIC1_MS_SLAVE 0x0 +#define FV_AIC1_LRP_INVERT 0x1 +#define FV_AIC1_LRP_NORMAL 0x0 +#define FV_AIC1_WL_16 0x0 +#define FV_AIC1_WL_20 0x1 +#define FV_AIC1_WL_24 0x2 +#define FV_AIC1_WL_32 0x3 +#define FV_AIC1_FORMAT_RIGHT 0x0 +#define FV_AIC1_FORMAT_LEFT 0x1 +#define FV_AIC1_FORMAT_I2S 0x2 + +/* Register Masks */ +#define RM_AIC1_BCLKINV \ + RM(FM_AIC1_BCLKINV, FB_AIC1_BCLKINV) + +#define RM_AIC1_MS RM(FM_AIC1_MS, FB_AIC1_MS) +#define RM_AIC1_LRP RM(FM_AIC1_LRP, FB_AIC1_LRP) +#define RM_AIC1_WL RM(FM_AIC1_WL, FB_AIC1_WL) +#define RM_AIC1_FORMAT RM(FM_AIC1_FORMAT, FB_AIC1_FORMAT) + +/* Register Values */ +#define RV_AIC1_BCLKINV_ENABLE \ + RV(FV_AIC1_BCLKINV_ENABLE, FB_AIC1_BCLKINV) + +#define RV_AIC1_BCLKINV_DISABLE \ + RV(FV_AIC1_BCLKINV_DISABLE, FB_AIC1_BCLKINV) + +#define RV_AIC1_MS_MASTER RV(FV_AIC1_MS_MASTER, FB_AIC1_MS) +#define RV_AIC1_MS_SLAVE RV(FV_AIC1_MS_SLAVE, FB_AIC1_MS) +#define RV_AIC1_LRP_INVERT \ + RV(FV_AIC1_LRP_INVERT, FB_AIC1_LRP) + +#define RV_AIC1_LRP_NORMAL \ + RV(FV_AIC1_LRP_NORMAL, FB_AIC1_LRP) + +#define RV_AIC1_WL_16 RV(FV_AIC1_WL_16, FB_AIC1_WL) +#define RV_AIC1_WL_20 RV(FV_AIC1_WL_20, FB_AIC1_WL) +#define RV_AIC1_WL_24 RV(FV_AIC1_WL_24, FB_AIC1_WL) +#define RV_AIC1_WL_32 RV(FV_AIC1_WL_32, FB_AIC1_WL) +#define RV_AIC1_FORMAT_RIGHT \ + RV(FV_AIC1_FORMAT_RIGHT, FB_AIC1_FORMAT) + +#define RV_AIC1_FORMAT_LEFT \ + RV(FV_AIC1_FORMAT_LEFT, FB_AIC1_FORMAT) + +#define RV_AIC1_FORMAT_I2S \ + RV(FV_AIC1_FORMAT_I2S, FB_AIC1_FORMAT) + + +/*************************** + * R_AIC2 (0x14) * + ***************************/ + +/* Field Offsets */ +#define FB_AIC2_DACDSEL 6 +#define FB_AIC2_ADCDSEL 4 +#define FB_AIC2_TRI 3 +#define FB_AIC2_BLRCM 0 + +/* Field Masks */ +#define FM_AIC2_DACDSEL 0X3 +#define FM_AIC2_ADCDSEL 0X3 +#define FM_AIC2_TRI 0X1 +#define FM_AIC2_BLRCM 0X7 + +/* Field Values */ +#define FV_AIC2_BLRCM_DAC_BCLK_LRCLK_SHARED 0x3 + +/* Register Masks */ +#define RM_AIC2_DACDSEL \ + RM(FM_AIC2_DACDSEL, FB_AIC2_DACDSEL) + +#define RM_AIC2_ADCDSEL \ + RM(FM_AIC2_ADCDSEL, FB_AIC2_ADCDSEL) + +#define RM_AIC2_TRI RM(FM_AIC2_TRI, FB_AIC2_TRI) +#define RM_AIC2_BLRCM RM(FM_AIC2_BLRCM, FB_AIC2_BLRCM) + +/* Register Values */ +#define RV_AIC2_BLRCM_DAC_BCLK_LRCLK_SHARED \ + RV(FV_AIC2_BLRCM_DAC_BCLK_LRCLK_SHARED, FB_AIC2_BLRCM) + + +/****************************** + * R_CNVRTR0 (0x16) * + ******************************/ + +/* Field Offsets */ +#define FB_CNVRTR0_ADCPOLR 7 +#define FB_CNVRTR0_ADCPOLL 6 +#define FB_CNVRTR0_AMONOMIX 4 +#define FB_CNVRTR0_ADCMU 3 +#define FB_CNVRTR0_HPOR 2 +#define FB_CNVRTR0_ADCHPDR 1 +#define FB_CNVRTR0_ADCHPDL 0 + +/* Field Masks */ +#define FM_CNVRTR0_ADCPOLR 0X1 +#define FM_CNVRTR0_ADCPOLL 0X1 +#define FM_CNVRTR0_AMONOMIX 0X3 +#define FM_CNVRTR0_ADCMU 0X1 +#define FM_CNVRTR0_HPOR 0X1 +#define FM_CNVRTR0_ADCHPDR 0X1 +#define FM_CNVRTR0_ADCHPDL 0X1 + +/* Field Values */ +#define FV_CNVRTR0_ADCPOLR_INVERT 0x1 +#define FV_CNVRTR0_ADCPOLR_NORMAL 0x0 +#define FV_CNVRTR0_ADCPOLL_INVERT 0x1 +#define FV_CNVRTR0_ADCPOLL_NORMAL 0x0 +#define FV_CNVRTR0_ADCMU_ENABLE 0x1 +#define FV_CNVRTR0_ADCMU_DISABLE 0x0 +#define FV_CNVRTR0_ADCHPDR_ENABLE 0x1 +#define FV_CNVRTR0_ADCHPDR_DISABLE 0x0 +#define FV_CNVRTR0_ADCHPDL_ENABLE 0x1 +#define FV_CNVRTR0_ADCHPDL_DISABLE 0x0 + +/* Register Masks */ +#define RM_CNVRTR0_ADCPOLR \ + RM(FM_CNVRTR0_ADCPOLR, FB_CNVRTR0_ADCPOLR) + +#define RM_CNVRTR0_ADCPOLL \ + RM(FM_CNVRTR0_ADCPOLL, FB_CNVRTR0_ADCPOLL) + +#define RM_CNVRTR0_AMONOMIX \ + RM(FM_CNVRTR0_AMONOMIX, FB_CNVRTR0_AMONOMIX) + +#define RM_CNVRTR0_ADCMU \ + RM(FM_CNVRTR0_ADCMU, FB_CNVRTR0_ADCMU) + +#define RM_CNVRTR0_HPOR \ + RM(FM_CNVRTR0_HPOR, FB_CNVRTR0_HPOR) + +#define RM_CNVRTR0_ADCHPDR \ + RM(FM_CNVRTR0_ADCHPDR, FB_CNVRTR0_ADCHPDR) + +#define RM_CNVRTR0_ADCHPDL \ + RM(FM_CNVRTR0_ADCHPDL, FB_CNVRTR0_ADCHPDL) + + +/* Register Values */ +#define RV_CNVRTR0_ADCPOLR_INVERT \ + RV(FV_CNVRTR0_ADCPOLR_INVERT, FB_CNVRTR0_ADCPOLR) + +#define RV_CNVRTR0_ADCPOLR_NORMAL \ + RV(FV_CNVRTR0_ADCPOLR_NORMAL, FB_CNVRTR0_ADCPOLR) + +#define RV_CNVRTR0_ADCPOLL_INVERT \ + RV(FV_CNVRTR0_ADCPOLL_INVERT, FB_CNVRTR0_ADCPOLL) + +#define RV_CNVRTR0_ADCPOLL_NORMAL \ + RV(FV_CNVRTR0_ADCPOLL_NORMAL, FB_CNVRTR0_ADCPOLL) + +#define RV_CNVRTR0_ADCMU_ENABLE \ + RV(FV_CNVRTR0_ADCMU_ENABLE, FB_CNVRTR0_ADCMU) + +#define RV_CNVRTR0_ADCMU_DISABLE \ + RV(FV_CNVRTR0_ADCMU_DISABLE, FB_CNVRTR0_ADCMU) + +#define RV_CNVRTR0_ADCHPDR_ENABLE \ + RV(FV_CNVRTR0_ADCHPDR_ENABLE, FB_CNVRTR0_ADCHPDR) + +#define RV_CNVRTR0_ADCHPDR_DISABLE \ + RV(FV_CNVRTR0_ADCHPDR_DISABLE, FB_CNVRTR0_ADCHPDR) + +#define RV_CNVRTR0_ADCHPDL_ENABLE \ + RV(FV_CNVRTR0_ADCHPDL_ENABLE, FB_CNVRTR0_ADCHPDL) + +#define RV_CNVRTR0_ADCHPDL_DISABLE \ + RV(FV_CNVRTR0_ADCHPDL_DISABLE, FB_CNVRTR0_ADCHPDL) + + +/**************************** + * R_ADCSR (0x17) * + ****************************/ + +/* Field Offsets */ +#define FB_ADCSR_ABCM 6 +#define FB_ADCSR_ABR 3 +#define FB_ADCSR_ABM 0 + +/* Field Masks */ +#define FM_ADCSR_ABCM 0X3 +#define FM_ADCSR_ABR 0X3 +#define FM_ADCSR_ABM 0X7 + +/* Field Values */ +#define FV_ADCSR_ABCM_AUTO 0x0 +#define FV_ADCSR_ABCM_32 0x1 +#define FV_ADCSR_ABCM_40 0x2 +#define FV_ADCSR_ABCM_64 0x3 +#define FV_ADCSR_ABR_32 0x0 +#define FV_ADCSR_ABR_44_1 0x1 +#define FV_ADCSR_ABR_48 0x2 +#define FV_ADCSR_ABM_PT25 0x0 +#define FV_ADCSR_ABM_PT5 0x1 +#define FV_ADCSR_ABM_1 0x2 +#define FV_ADCSR_ABM_2 0x3 + +/* Register Masks */ +#define RM_ADCSR_ABCM RM(FM_ADCSR_ABCM, FB_ADCSR_ABCM) +#define RM_ADCSR_ABR RM(FM_ADCSR_ABR, FB_ADCSR_ABR) +#define RM_ADCSR_ABM RM(FM_ADCSR_ABM, FB_ADCSR_ABM) + +/* Register Values */ +#define RV_ADCSR_ABCM_AUTO \ + RV(FV_ADCSR_ABCM_AUTO, FB_ADCSR_ABCM) + +#define RV_ADCSR_ABCM_32 \ + RV(FV_ADCSR_ABCM_32, FB_ADCSR_ABCM) + +#define RV_ADCSR_ABCM_40 \ + RV(FV_ADCSR_ABCM_40, FB_ADCSR_ABCM) + +#define RV_ADCSR_ABCM_64 \ + RV(FV_ADCSR_ABCM_64, FB_ADCSR_ABCM) + +#define RV_ADCSR_ABR_32 RV(FV_ADCSR_ABR_32, FB_ADCSR_ABR) +#define RV_ADCSR_ABR_44_1 \ + RV(FV_ADCSR_ABR_44_1, FB_ADCSR_ABR) + +#define RV_ADCSR_ABR_48 RV(FV_ADCSR_ABR_48, FB_ADCSR_ABR) +#define RV_ADCSR_ABR_ RV(FV_ADCSR_ABR_, FB_ADCSR_ABR) +#define RV_ADCSR_ABM_PT25 \ + RV(FV_ADCSR_ABM_PT25, FB_ADCSR_ABM) + +#define RV_ADCSR_ABM_PT5 RV(FV_ADCSR_ABM_PT5, FB_ADCSR_ABM) +#define RV_ADCSR_ABM_1 RV(FV_ADCSR_ABM_1, FB_ADCSR_ABM) +#define RV_ADCSR_ABM_2 RV(FV_ADCSR_ABM_2, FB_ADCSR_ABM) + +/****************************** + * R_CNVRTR1 (0x18) * + ******************************/ + +/* Field Offsets */ +#define FB_CNVRTR1_DACPOLR 7 +#define FB_CNVRTR1_DACPOLL 6 +#define FB_CNVRTR1_DMONOMIX 4 +#define FB_CNVRTR1_DACMU 3 +#define FB_CNVRTR1_DEEMPH 2 +#define FB_CNVRTR1_DACDITH 0 + +/* Field Masks */ +#define FM_CNVRTR1_DACPOLR 0X1 +#define FM_CNVRTR1_DACPOLL 0X1 +#define FM_CNVRTR1_DMONOMIX 0X3 +#define FM_CNVRTR1_DACMU 0X1 +#define FM_CNVRTR1_DEEMPH 0X1 +#define FM_CNVRTR1_DACDITH 0X3 + +/* Field Values */ +#define FV_CNVRTR1_DACPOLR_INVERT 0x1 +#define FV_CNVRTR1_DACPOLR_NORMAL 0x0 +#define FV_CNVRTR1_DACPOLL_INVERT 0x1 +#define FV_CNVRTR1_DACPOLL_NORMAL 0x0 +#define FV_CNVRTR1_DMONOMIX_ENABLE 0x1 +#define FV_CNVRTR1_DMONOMIX_DISABLE 0x0 +#define FV_CNVRTR1_DACMU_ENABLE 0x1 +#define FV_CNVRTR1_DACMU_DISABLE 0x0 + +/* Register Masks */ +#define RM_CNVRTR1_DACPOLR \ + RM(FM_CNVRTR1_DACPOLR, FB_CNVRTR1_DACPOLR) + +#define RM_CNVRTR1_DACPOLL \ + RM(FM_CNVRTR1_DACPOLL, FB_CNVRTR1_DACPOLL) + +#define RM_CNVRTR1_DMONOMIX \ + RM(FM_CNVRTR1_DMONOMIX, FB_CNVRTR1_DMONOMIX) + +#define RM_CNVRTR1_DACMU \ + RM(FM_CNVRTR1_DACMU, FB_CNVRTR1_DACMU) + +#define RM_CNVRTR1_DEEMPH \ + RM(FM_CNVRTR1_DEEMPH, FB_CNVRTR1_DEEMPH) + +#define RM_CNVRTR1_DACDITH \ + RM(FM_CNVRTR1_DACDITH, FB_CNVRTR1_DACDITH) + + +/* Register Values */ +#define RV_CNVRTR1_DACPOLR_INVERT \ + RV(FV_CNVRTR1_DACPOLR_INVERT, FB_CNVRTR1_DACPOLR) + +#define RV_CNVRTR1_DACPOLR_NORMAL \ + RV(FV_CNVRTR1_DACPOLR_NORMAL, FB_CNVRTR1_DACPOLR) + +#define RV_CNVRTR1_DACPOLL_INVERT \ + RV(FV_CNVRTR1_DACPOLL_INVERT, FB_CNVRTR1_DACPOLL) + +#define RV_CNVRTR1_DACPOLL_NORMAL \ + RV(FV_CNVRTR1_DACPOLL_NORMAL, FB_CNVRTR1_DACPOLL) + +#define RV_CNVRTR1_DMONOMIX_ENABLE \ + RV(FV_CNVRTR1_DMONOMIX_ENABLE, FB_CNVRTR1_DMONOMIX) + +#define RV_CNVRTR1_DMONOMIX_DISABLE \ + RV(FV_CNVRTR1_DMONOMIX_DISABLE, FB_CNVRTR1_DMONOMIX) + +#define RV_CNVRTR1_DACMU_ENABLE \ + RV(FV_CNVRTR1_DACMU_ENABLE, FB_CNVRTR1_DACMU) + +#define RV_CNVRTR1_DACMU_DISABLE \ + RV(FV_CNVRTR1_DACMU_DISABLE, FB_CNVRTR1_DACMU) + + +/**************************** + * R_DACSR (0x19) * + ****************************/ + +/* Field Offsets */ +#define FB_DACSR_DBCM 6 +#define FB_DACSR_DBR 3 +#define FB_DACSR_DBM 0 + +/* Field Masks */ +#define FM_DACSR_DBCM 0X3 +#define FM_DACSR_DBR 0X3 +#define FM_DACSR_DBM 0X7 + +/* Field Values */ +#define FV_DACSR_DBCM_AUTO 0x0 +#define FV_DACSR_DBCM_32 0x1 +#define FV_DACSR_DBCM_40 0x2 +#define FV_DACSR_DBCM_64 0x3 +#define FV_DACSR_DBR_32 0x0 +#define FV_DACSR_DBR_44_1 0x1 +#define FV_DACSR_DBR_48 0x2 +#define FV_DACSR_DBM_PT25 0x0 +#define FV_DACSR_DBM_PT5 0x1 +#define FV_DACSR_DBM_1 0x2 +#define FV_DACSR_DBM_2 0x3 + +/* Register Masks */ +#define RM_DACSR_DBCM RM(FM_DACSR_DBCM, FB_DACSR_DBCM) +#define RM_DACSR_DBR RM(FM_DACSR_DBR, FB_DACSR_DBR) +#define RM_DACSR_DBM RM(FM_DACSR_DBM, FB_DACSR_DBM) + +/* Register Values */ +#define RV_DACSR_DBCM_AUTO \ + RV(FV_DACSR_DBCM_AUTO, FB_DACSR_DBCM) + +#define RV_DACSR_DBCM_32 \ + RV(FV_DACSR_DBCM_32, FB_DACSR_DBCM) + +#define RV_DACSR_DBCM_40 \ + RV(FV_DACSR_DBCM_40, FB_DACSR_DBCM) + +#define RV_DACSR_DBCM_64 \ + RV(FV_DACSR_DBCM_64, FB_DACSR_DBCM) + +#define RV_DACSR_DBR_32 RV(FV_DACSR_DBR_32, FB_DACSR_DBR) +#define RV_DACSR_DBR_44_1 \ + RV(FV_DACSR_DBR_44_1, FB_DACSR_DBR) + +#define RV_DACSR_DBR_48 RV(FV_DACSR_DBR_48, FB_DACSR_DBR) +#define RV_DACSR_DBM_PT25 \ + RV(FV_DACSR_DBM_PT25, FB_DACSR_DBM) + +#define RV_DACSR_DBM_PT5 RV(FV_DACSR_DBM_PT5, FB_DACSR_DBM) +#define RV_DACSR_DBM_1 RV(FV_DACSR_DBM_1, FB_DACSR_DBM) +#define RV_DACSR_DBM_2 RV(FV_DACSR_DBM_2, FB_DACSR_DBM) + +/**************************** + * R_PWRM1 (0x1A) * + ****************************/ + +/* Field Offsets */ +#define FB_PWRM1_BSTL 7 +#define FB_PWRM1_BSTR 6 +#define FB_PWRM1_PGAL 5 +#define FB_PWRM1_PGAR 4 +#define FB_PWRM1_ADCL 3 +#define FB_PWRM1_ADCR 2 +#define FB_PWRM1_MICB 1 +#define FB_PWRM1_DIGENB 0 + +/* Field Masks */ +#define FM_PWRM1_BSTL 0X1 +#define FM_PWRM1_BSTR 0X1 +#define FM_PWRM1_PGAL 0X1 +#define FM_PWRM1_PGAR 0X1 +#define FM_PWRM1_ADCL 0X1 +#define FM_PWRM1_ADCR 0X1 +#define FM_PWRM1_MICB 0X1 +#define FM_PWRM1_DIGENB 0X1 + +/* Field Values */ +#define FV_PWRM1_BSTL_ENABLE 0x1 +#define FV_PWRM1_BSTL_DISABLE 0x0 +#define FV_PWRM1_BSTR_ENABLE 0x1 +#define FV_PWRM1_BSTR_DISABLE 0x0 +#define FV_PWRM1_PGAL_ENABLE 0x1 +#define FV_PWRM1_PGAL_DISABLE 0x0 +#define FV_PWRM1_PGAR_ENABLE 0x1 +#define FV_PWRM1_PGAR_DISABLE 0x0 +#define FV_PWRM1_ADCL_ENABLE 0x1 +#define FV_PWRM1_ADCL_DISABLE 0x0 +#define FV_PWRM1_ADCR_ENABLE 0x1 +#define FV_PWRM1_ADCR_DISABLE 0x0 +#define FV_PWRM1_MICB_ENABLE 0x1 +#define FV_PWRM1_MICB_DISABLE 0x0 +#define FV_PWRM1_DIGENB_DISABLE 0x1 +#define FV_PWRM1_DIGENB_ENABLE 0x0 + +/* Register Masks */ +#define RM_PWRM1_BSTL RM(FM_PWRM1_BSTL, FB_PWRM1_BSTL) +#define RM_PWRM1_BSTR RM(FM_PWRM1_BSTR, FB_PWRM1_BSTR) +#define RM_PWRM1_PGAL RM(FM_PWRM1_PGAL, FB_PWRM1_PGAL) +#define RM_PWRM1_PGAR RM(FM_PWRM1_PGAR, FB_PWRM1_PGAR) +#define RM_PWRM1_ADCL RM(FM_PWRM1_ADCL, FB_PWRM1_ADCL) +#define RM_PWRM1_ADCR RM(FM_PWRM1_ADCR, FB_PWRM1_ADCR) +#define RM_PWRM1_MICB RM(FM_PWRM1_MICB, FB_PWRM1_MICB) +#define RM_PWRM1_DIGENB \ + RM(FM_PWRM1_DIGENB, FB_PWRM1_DIGENB) + + +/* Register Values */ +#define RV_PWRM1_BSTL_ENABLE \ + RV(FV_PWRM1_BSTL_ENABLE, FB_PWRM1_BSTL) + +#define RV_PWRM1_BSTL_DISABLE \ + RV(FV_PWRM1_BSTL_DISABLE, FB_PWRM1_BSTL) + +#define RV_PWRM1_BSTR_ENABLE \ + RV(FV_PWRM1_BSTR_ENABLE, FB_PWRM1_BSTR) + +#define RV_PWRM1_BSTR_DISABLE \ + RV(FV_PWRM1_BSTR_DISABLE, FB_PWRM1_BSTR) + +#define RV_PWRM1_PGAL_ENABLE \ + RV(FV_PWRM1_PGAL_ENABLE, FB_PWRM1_PGAL) + +#define RV_PWRM1_PGAL_DISABLE \ + RV(FV_PWRM1_PGAL_DISABLE, FB_PWRM1_PGAL) + +#define RV_PWRM1_PGAR_ENABLE \ + RV(FV_PWRM1_PGAR_ENABLE, FB_PWRM1_PGAR) + +#define RV_PWRM1_PGAR_DISABLE \ + RV(FV_PWRM1_PGAR_DISABLE, FB_PWRM1_PGAR) + +#define RV_PWRM1_ADCL_ENABLE \ + RV(FV_PWRM1_ADCL_ENABLE, FB_PWRM1_ADCL) + +#define RV_PWRM1_ADCL_DISABLE \ + RV(FV_PWRM1_ADCL_DISABLE, FB_PWRM1_ADCL) + +#define RV_PWRM1_ADCR_ENABLE \ + RV(FV_PWRM1_ADCR_ENABLE, FB_PWRM1_ADCR) + +#define RV_PWRM1_ADCR_DISABLE \ + RV(FV_PWRM1_ADCR_DISABLE, FB_PWRM1_ADCR) + +#define RV_PWRM1_MICB_ENABLE \ + RV(FV_PWRM1_MICB_ENABLE, FB_PWRM1_MICB) + +#define RV_PWRM1_MICB_DISABLE \ + RV(FV_PWRM1_MICB_DISABLE, FB_PWRM1_MICB) + +#define RV_PWRM1_DIGENB_DISABLE \ + RV(FV_PWRM1_DIGENB_DISABLE, FB_PWRM1_DIGENB) + +#define RV_PWRM1_DIGENB_ENABLE \ + RV(FV_PWRM1_DIGENB_ENABLE, FB_PWRM1_DIGENB) + + +/**************************** + * R_PWRM2 (0x1B) * + ****************************/ + +/* Field Offsets */ +#define FB_PWRM2_D2S 7 +#define FB_PWRM2_HPL 6 +#define FB_PWRM2_HPR 5 +#define FB_PWRM2_SPKL 4 +#define FB_PWRM2_SPKR 3 +#define FB_PWRM2_INSELL 2 +#define FB_PWRM2_INSELR 1 +#define FB_PWRM2_VREF 0 + +/* Field Masks */ +#define FM_PWRM2_D2S 0X1 +#define FM_PWRM2_HPL 0X1 +#define FM_PWRM2_HPR 0X1 +#define FM_PWRM2_SPKL 0X1 +#define FM_PWRM2_SPKR 0X1 +#define FM_PWRM2_INSELL 0X1 +#define FM_PWRM2_INSELR 0X1 +#define FM_PWRM2_VREF 0X1 + +/* Field Values */ +#define FV_PWRM2_D2S_ENABLE 0x1 +#define FV_PWRM2_D2S_DISABLE 0x0 +#define FV_PWRM2_HPL_ENABLE 0x1 +#define FV_PWRM2_HPL_DISABLE 0x0 +#define FV_PWRM2_HPR_ENABLE 0x1 +#define FV_PWRM2_HPR_DISABLE 0x0 +#define FV_PWRM2_SPKL_ENABLE 0x1 +#define FV_PWRM2_SPKL_DISABLE 0x0 +#define FV_PWRM2_SPKR_ENABLE 0x1 +#define FV_PWRM2_SPKR_DISABLE 0x0 +#define FV_PWRM2_INSELL_ENABLE 0x1 +#define FV_PWRM2_INSELL_DISABLE 0x0 +#define FV_PWRM2_INSELR_ENABLE 0x1 +#define FV_PWRM2_INSELR_DISABLE 0x0 +#define FV_PWRM2_VREF_ENABLE 0x1 +#define FV_PWRM2_VREF_DISABLE 0x0 + +/* Register Masks */ +#define RM_PWRM2_D2S RM(FM_PWRM2_D2S, FB_PWRM2_D2S) +#define RM_PWRM2_HPL RM(FM_PWRM2_HPL, FB_PWRM2_HPL) +#define RM_PWRM2_HPR RM(FM_PWRM2_HPR, FB_PWRM2_HPR) +#define RM_PWRM2_SPKL RM(FM_PWRM2_SPKL, FB_PWRM2_SPKL) +#define RM_PWRM2_SPKR RM(FM_PWRM2_SPKR, FB_PWRM2_SPKR) +#define RM_PWRM2_INSELL \ + RM(FM_PWRM2_INSELL, FB_PWRM2_INSELL) + +#define RM_PWRM2_INSELR \ + RM(FM_PWRM2_INSELR, FB_PWRM2_INSELR) + +#define RM_PWRM2_VREF RM(FM_PWRM2_VREF, FB_PWRM2_VREF) + +/* Register Values */ +#define RV_PWRM2_D2S_ENABLE \ + RV(FV_PWRM2_D2S_ENABLE, FB_PWRM2_D2S) + +#define RV_PWRM2_D2S_DISABLE \ + RV(FV_PWRM2_D2S_DISABLE, FB_PWRM2_D2S) + +#define RV_PWRM2_HPL_ENABLE \ + RV(FV_PWRM2_HPL_ENABLE, FB_PWRM2_HPL) + +#define RV_PWRM2_HPL_DISABLE \ + RV(FV_PWRM2_HPL_DISABLE, FB_PWRM2_HPL) + +#define RV_PWRM2_HPR_ENABLE \ + RV(FV_PWRM2_HPR_ENABLE, FB_PWRM2_HPR) + +#define RV_PWRM2_HPR_DISABLE \ + RV(FV_PWRM2_HPR_DISABLE, FB_PWRM2_HPR) + +#define RV_PWRM2_SPKL_ENABLE \ + RV(FV_PWRM2_SPKL_ENABLE, FB_PWRM2_SPKL) + +#define RV_PWRM2_SPKL_DISABLE \ + RV(FV_PWRM2_SPKL_DISABLE, FB_PWRM2_SPKL) + +#define RV_PWRM2_SPKR_ENABLE \ + RV(FV_PWRM2_SPKR_ENABLE, FB_PWRM2_SPKR) + +#define RV_PWRM2_SPKR_DISABLE \ + RV(FV_PWRM2_SPKR_DISABLE, FB_PWRM2_SPKR) + +#define RV_PWRM2_INSELL_ENABLE \ + RV(FV_PWRM2_INSELL_ENABLE, FB_PWRM2_INSELL) + +#define RV_PWRM2_INSELL_DISABLE \ + RV(FV_PWRM2_INSELL_DISABLE, FB_PWRM2_INSELL) + +#define RV_PWRM2_INSELR_ENABLE \ + RV(FV_PWRM2_INSELR_ENABLE, FB_PWRM2_INSELR) + +#define RV_PWRM2_INSELR_DISABLE \ + RV(FV_PWRM2_INSELR_DISABLE, FB_PWRM2_INSELR) + +#define RV_PWRM2_VREF_ENABLE \ + RV(FV_PWRM2_VREF_ENABLE, FB_PWRM2_VREF) + +#define RV_PWRM2_VREF_DISABLE \ + RV(FV_PWRM2_VREF_DISABLE, FB_PWRM2_VREF) + +/****************************** + * R_CTL (0x1C) * + ******************************/ + +/* Fiel Offsets */ +#define FB_CTL_HPSWEN 7 +#define FB_CTL_HPSWPOL 6 + +/****************************** + * R_CONFIG0 (0x1F) * + ******************************/ + +/* Field Offsets */ +#define FB_CONFIG0_ASDM 6 +#define FB_CONFIG0_DSDM 4 +#define FB_CONFIG0_DC_BYPASS 1 +#define FB_CONFIG0_SD_FORCE_ON 0 + +/* Field Masks */ +#define FM_CONFIG0_ASDM 0X3 +#define FM_CONFIG0_DSDM 0X3 +#define FM_CONFIG0_DC_BYPASS 0X1 +#define FM_CONFIG0_SD_FORCE_ON 0X1 + +/* Field Values */ +#define FV_CONFIG0_ASDM_HALF 0x1 +#define FV_CONFIG0_ASDM_FULL 0x2 +#define FV_CONFIG0_ASDM_AUTO 0x3 +#define FV_CONFIG0_DSDM_HALF 0x1 +#define FV_CONFIG0_DSDM_FULL 0x2 +#define FV_CONFIG0_DSDM_AUTO 0x3 +#define FV_CONFIG0_DC_BYPASS_ENABLE 0x1 +#define FV_CONFIG0_DC_BYPASS_DISABLE 0x0 +#define FV_CONFIG0_SD_FORCE_ON_ENABLE 0x1 +#define FV_CONFIG0_SD_FORCE_ON_DISABLE 0x0 + +/* Register Masks */ +#define RM_CONFIG0_ASDM \ + RM(FM_CONFIG0_ASDM, FB_CONFIG0_ASDM) + +#define RM_CONFIG0_DSDM \ + RM(FM_CONFIG0_DSDM, FB_CONFIG0_DSDM) + +#define RM_CONFIG0_DC_BYPASS \ + RM(FM_CONFIG0_DC_BYPASS, FB_CONFIG0_DC_BYPASS) + +#define RM_CONFIG0_SD_FORCE_ON \ + RM(FM_CONFIG0_SD_FORCE_ON, FB_CONFIG0_SD_FORCE_ON) + + +/* Register Values */ +#define RV_CONFIG0_ASDM_HALF \ + RV(FV_CONFIG0_ASDM_HALF, FB_CONFIG0_ASDM) + +#define RV_CONFIG0_ASDM_FULL \ + RV(FV_CONFIG0_ASDM_FULL, FB_CONFIG0_ASDM) + +#define RV_CONFIG0_ASDM_AUTO \ + RV(FV_CONFIG0_ASDM_AUTO, FB_CONFIG0_ASDM) + +#define RV_CONFIG0_DSDM_HALF \ + RV(FV_CONFIG0_DSDM_HALF, FB_CONFIG0_DSDM) + +#define RV_CONFIG0_DSDM_FULL \ + RV(FV_CONFIG0_DSDM_FULL, FB_CONFIG0_DSDM) + +#define RV_CONFIG0_DSDM_AUTO \ + RV(FV_CONFIG0_DSDM_AUTO, FB_CONFIG0_DSDM) + +#define RV_CONFIG0_DC_BYPASS_ENABLE \ + RV(FV_CONFIG0_DC_BYPASS_ENABLE, FB_CONFIG0_DC_BYPASS) + +#define RV_CONFIG0_DC_BYPASS_DISABLE \ + RV(FV_CONFIG0_DC_BYPASS_DISABLE, FB_CONFIG0_DC_BYPASS) + +#define RV_CONFIG0_SD_FORCE_ON_ENABLE \ + RV(FV_CONFIG0_SD_FORCE_ON_ENABLE, FB_CONFIG0_SD_FORCE_ON) + +#define RV_CONFIG0_SD_FORCE_ON_DISABLE \ + RV(FV_CONFIG0_SD_FORCE_ON_DISABLE, FB_CONFIG0_SD_FORCE_ON) + + +/****************************** + * R_CONFIG1 (0x20) * + ******************************/ + +/* Field Offsets */ +#define FB_CONFIG1_EQ2_EN 7 +#define FB_CONFIG1_EQ2_BE 4 +#define FB_CONFIG1_EQ1_EN 3 +#define FB_CONFIG1_EQ1_BE 0 + +/* Field Masks */ +#define FM_CONFIG1_EQ2_EN 0X1 +#define FM_CONFIG1_EQ2_BE 0X7 +#define FM_CONFIG1_EQ1_EN 0X1 +#define FM_CONFIG1_EQ1_BE 0X7 + +/* Field Values */ +#define FV_CONFIG1_EQ2_EN_ENABLE 0x1 +#define FV_CONFIG1_EQ2_EN_DISABLE 0x0 +#define FV_CONFIG1_EQ2_BE_PRE 0x0 +#define FV_CONFIG1_EQ2_BE_PRE_EQ_0 0x1 +#define FV_CONFIG1_EQ2_BE_PRE_EQ0_1 0x2 +#define FV_CONFIG1_EQ2_BE_PRE_EQ0_2 0x3 +#define FV_CONFIG1_EQ2_BE_PRE_EQ0_3 0x4 +#define FV_CONFIG1_EQ2_BE_PRE_EQ0_4 0x5 +#define FV_CONFIG1_EQ2_BE_PRE_EQ0_5 0x6 +#define FV_CONFIG1_EQ1_EN_ENABLE 0x1 +#define FV_CONFIG1_EQ1_EN_DISABLE 0x0 +#define FV_CONFIG1_EQ1_BE_PRE 0x0 +#define FV_CONFIG1_EQ1_BE_PRE_EQ_0 0x1 +#define FV_CONFIG1_EQ1_BE_PRE_EQ0_1 0x2 +#define FV_CONFIG1_EQ1_BE_PRE_EQ0_2 0x3 +#define FV_CONFIG1_EQ1_BE_PRE_EQ0_3 0x4 +#define FV_CONFIG1_EQ1_BE_PRE_EQ0_4 0x5 +#define FV_CONFIG1_EQ1_BE_PRE_EQ0_5 0x6 + +/* Register Masks */ +#define RM_CONFIG1_EQ2_EN \ + RM(FM_CONFIG1_EQ2_EN, FB_CONFIG1_EQ2_EN) + +#define RM_CONFIG1_EQ2_BE \ + RM(FM_CONFIG1_EQ2_BE, FB_CONFIG1_EQ2_BE) + +#define RM_CONFIG1_EQ1_EN \ + RM(FM_CONFIG1_EQ1_EN, FB_CONFIG1_EQ1_EN) + +#define RM_CONFIG1_EQ1_BE \ + RM(FM_CONFIG1_EQ1_BE, FB_CONFIG1_EQ1_BE) + + +/* Register Values */ +#define RV_CONFIG1_EQ2_EN_ENABLE \ + RV(FV_CONFIG1_EQ2_EN_ENABLE, FB_CONFIG1_EQ2_EN) + +#define RV_CONFIG1_EQ2_EN_DISABLE \ + RV(FV_CONFIG1_EQ2_EN_DISABLE, FB_CONFIG1_EQ2_EN) + +#define RV_CONFIG1_EQ2_BE_PRE \ + RV(FV_CONFIG1_EQ2_BE_PRE, FB_CONFIG1_EQ2_BE) + +#define RV_CONFIG1_EQ2_BE_PRE_EQ_0 \ + RV(FV_CONFIG1_EQ2_BE_PRE_EQ_0, FB_CONFIG1_EQ2_BE) + +#define RV_CONFIG1_EQ2_BE_PRE_EQ0_1 \ + RV(FV_CONFIG1_EQ2_BE_PRE_EQ0_1, FB_CONFIG1_EQ2_BE) + +#define RV_CONFIG1_EQ2_BE_PRE_EQ0_2 \ + RV(FV_CONFIG1_EQ2_BE_PRE_EQ0_2, FB_CONFIG1_EQ2_BE) + +#define RV_CONFIG1_EQ2_BE_PRE_EQ0_3 \ + RV(FV_CONFIG1_EQ2_BE_PRE_EQ0_3, FB_CONFIG1_EQ2_BE) + +#define RV_CONFIG1_EQ2_BE_PRE_EQ0_4 \ + RV(FV_CONFIG1_EQ2_BE_PRE_EQ0_4, FB_CONFIG1_EQ2_BE) + +#define RV_CONFIG1_EQ2_BE_PRE_EQ0_5 \ + RV(FV_CONFIG1_EQ2_BE_PRE_EQ0_5, FB_CONFIG1_EQ2_BE) + +#define RV_CONFIG1_EQ1_EN_ENABLE \ + RV(FV_CONFIG1_EQ1_EN_ENABLE, FB_CONFIG1_EQ1_EN) + +#define RV_CONFIG1_EQ1_EN_DISABLE \ + RV(FV_CONFIG1_EQ1_EN_DISABLE, FB_CONFIG1_EQ1_EN) + +#define RV_CONFIG1_EQ1_BE_PRE \ + RV(FV_CONFIG1_EQ1_BE_PRE, FB_CONFIG1_EQ1_BE) + +#define RV_CONFIG1_EQ1_BE_PRE_EQ_0 \ + RV(FV_CONFIG1_EQ1_BE_PRE_EQ_0, FB_CONFIG1_EQ1_BE) + +#define RV_CONFIG1_EQ1_BE_PRE_EQ0_1 \ + RV(FV_CONFIG1_EQ1_BE_PRE_EQ0_1, FB_CONFIG1_EQ1_BE) + +#define RV_CONFIG1_EQ1_BE_PRE_EQ0_2 \ + RV(FV_CONFIG1_EQ1_BE_PRE_EQ0_2, FB_CONFIG1_EQ1_BE) + +#define RV_CONFIG1_EQ1_BE_PRE_EQ0_3 \ + RV(FV_CONFIG1_EQ1_BE_PRE_EQ0_3, FB_CONFIG1_EQ1_BE) + +#define RV_CONFIG1_EQ1_BE_PRE_EQ0_4 \ + RV(FV_CONFIG1_EQ1_BE_PRE_EQ0_4, FB_CONFIG1_EQ1_BE) + +#define RV_CONFIG1_EQ1_BE_PRE_EQ0_5 \ + RV(FV_CONFIG1_EQ1_BE_PRE_EQ0_5, FB_CONFIG1_EQ1_BE) + + +/****************************** + * R_DMICCTL (0x24) * + ******************************/ + +/* Field Offsets */ +#define FB_DMICCTL_DMICEN 7 +#define FB_DMICCTL_DMONO 4 +#define FB_DMICCTL_DMPHADJ 2 +#define FB_DMICCTL_DMRATE 0 + +/* Field Masks */ +#define FM_DMICCTL_DMICEN 0X1 +#define FM_DMICCTL_DMONO 0X1 +#define FM_DMICCTL_DMPHADJ 0X3 +#define FM_DMICCTL_DMRATE 0X3 + +/* Field Values */ +#define FV_DMICCTL_DMICEN_ENABLE 0x1 +#define FV_DMICCTL_DMICEN_DISABLE 0x0 +#define FV_DMICCTL_DMONO_STEREO 0x0 +#define FV_DMICCTL_DMONO_MONO 0x1 + +/* Register Masks */ +#define RM_DMICCTL_DMICEN \ + RM(FM_DMICCTL_DMICEN, FB_DMICCTL_DMICEN) + +#define RM_DMICCTL_DMONO \ + RM(FM_DMICCTL_DMONO, FB_DMICCTL_DMONO) + +#define RM_DMICCTL_DMPHADJ \ + RM(FM_DMICCTL_DMPHADJ, FB_DMICCTL_DMPHADJ) + +#define RM_DMICCTL_DMRATE \ + RM(FM_DMICCTL_DMRATE, FB_DMICCTL_DMRATE) + + +/* Register Values */ +#define RV_DMICCTL_DMICEN_ENABLE \ + RV(FV_DMICCTL_DMICEN_ENABLE, FB_DMICCTL_DMICEN) + +#define RV_DMICCTL_DMICEN_DISABLE \ + RV(FV_DMICCTL_DMICEN_DISABLE, FB_DMICCTL_DMICEN) + +#define RV_DMICCTL_DMONO_STEREO \ + RV(FV_DMICCTL_DMONO_STEREO, FB_DMICCTL_DMONO) + +#define RV_DMICCTL_DMONO_MONO \ + RV(FV_DMICCTL_DMONO_MONO, FB_DMICCTL_DMONO) + + +/***************************** + * R_CLECTL (0x25) * + *****************************/ + +/* Field Offsets */ +#define FB_CLECTL_LVL_MODE 4 +#define FB_CLECTL_WINDOWSEL 3 +#define FB_CLECTL_EXP_EN 2 +#define FB_CLECTL_LIMIT_EN 1 +#define FB_CLECTL_COMP_EN 0 + +/* Field Masks */ +#define FM_CLECTL_LVL_MODE 0X1 +#define FM_CLECTL_WINDOWSEL 0X1 +#define FM_CLECTL_EXP_EN 0X1 +#define FM_CLECTL_LIMIT_EN 0X1 +#define FM_CLECTL_COMP_EN 0X1 + +/* Field Values */ +#define FV_CLECTL_LVL_MODE_AVG 0x0 +#define FV_CLECTL_LVL_MODE_PEAK 0x1 +#define FV_CLECTL_WINDOWSEL_512 0x0 +#define FV_CLECTL_WINDOWSEL_64 0x1 +#define FV_CLECTL_EXP_EN_ENABLE 0x1 +#define FV_CLECTL_EXP_EN_DISABLE 0x0 +#define FV_CLECTL_LIMIT_EN_ENABLE 0x1 +#define FV_CLECTL_LIMIT_EN_DISABLE 0x0 +#define FV_CLECTL_COMP_EN_ENABLE 0x1 +#define FV_CLECTL_COMP_EN_DISABLE 0x0 + +/* Register Masks */ +#define RM_CLECTL_LVL_MODE \ + RM(FM_CLECTL_LVL_MODE, FB_CLECTL_LVL_MODE) + +#define RM_CLECTL_WINDOWSEL \ + RM(FM_CLECTL_WINDOWSEL, FB_CLECTL_WINDOWSEL) + +#define RM_CLECTL_EXP_EN \ + RM(FM_CLECTL_EXP_EN, FB_CLECTL_EXP_EN) + +#define RM_CLECTL_LIMIT_EN \ + RM(FM_CLECTL_LIMIT_EN, FB_CLECTL_LIMIT_EN) + +#define RM_CLECTL_COMP_EN \ + RM(FM_CLECTL_COMP_EN, FB_CLECTL_COMP_EN) + + +/* Register Values */ +#define RV_CLECTL_LVL_MODE_AVG \ + RV(FV_CLECTL_LVL_MODE_AVG, FB_CLECTL_LVL_MODE) + +#define RV_CLECTL_LVL_MODE_PEAK \ + RV(FV_CLECTL_LVL_MODE_PEAK, FB_CLECTL_LVL_MODE) + +#define RV_CLECTL_WINDOWSEL_512 \ + RV(FV_CLECTL_WINDOWSEL_512, FB_CLECTL_WINDOWSEL) + +#define RV_CLECTL_WINDOWSEL_64 \ + RV(FV_CLECTL_WINDOWSEL_64, FB_CLECTL_WINDOWSEL) + +#define RV_CLECTL_EXP_EN_ENABLE \ + RV(FV_CLECTL_EXP_EN_ENABLE, FB_CLECTL_EXP_EN) + +#define RV_CLECTL_EXP_EN_DISABLE \ + RV(FV_CLECTL_EXP_EN_DISABLE, FB_CLECTL_EXP_EN) + +#define RV_CLECTL_LIMIT_EN_ENABLE \ + RV(FV_CLECTL_LIMIT_EN_ENABLE, FB_CLECTL_LIMIT_EN) + +#define RV_CLECTL_LIMIT_EN_DISABLE \ + RV(FV_CLECTL_LIMIT_EN_DISABLE, FB_CLECTL_LIMIT_EN) + +#define RV_CLECTL_COMP_EN_ENABLE \ + RV(FV_CLECTL_COMP_EN_ENABLE, FB_CLECTL_COMP_EN) + +#define RV_CLECTL_COMP_EN_DISABLE \ + RV(FV_CLECTL_COMP_EN_DISABLE, FB_CLECTL_COMP_EN) + + +/***************************** + * R_MUGAIN (0x26) * + *****************************/ + +/* Field Offsets */ +#define FB_MUGAIN_CLEMUG 0 + +/* Field Masks */ +#define FM_MUGAIN_CLEMUG 0X1F + +/* Field Values */ +#define FV_MUGAIN_CLEMUG_46PT5DB 0x1F +#define FV_MUGAIN_CLEMUG_0DB 0x0 + +/* Register Masks */ +#define RM_MUGAIN_CLEMUG \ + RM(FM_MUGAIN_CLEMUG, FB_MUGAIN_CLEMUG) + + +/* Register Values */ +#define RV_MUGAIN_CLEMUG_46PT5DB \ + RV(FV_MUGAIN_CLEMUG_46PT5DB, FB_MUGAIN_CLEMUG) + +#define RV_MUGAIN_CLEMUG_0DB \ + RV(FV_MUGAIN_CLEMUG_0DB, FB_MUGAIN_CLEMUG) + + +/***************************** + * R_COMPTH (0x27) * + *****************************/ + +/* Field Offsets */ +#define FB_COMPTH 0 + +/* Field Masks */ +#define FM_COMPTH 0XFF + +/* Field Values */ +#define FV_COMPTH_0DB 0xFF +#define FV_COMPTH_N95PT625DB 0x0 + +/* Register Masks */ +#define RM_COMPTH RM(FM_COMPTH, FB_COMPTH) + +/* Register Values */ +#define RV_COMPTH_0DB RV(FV_COMPTH_0DB, FB_COMPTH) +#define RV_COMPTH_N95PT625DB \ + RV(FV_COMPTH_N95PT625DB, FB_COMPTH) + + +/***************************** + * R_CMPRAT (0x28) * + *****************************/ + +/* Field Offsets */ +#define FB_CMPRAT 0 + +/* Field Masks */ +#define FM_CMPRAT 0X1F + +/* Register Masks */ +#define RM_CMPRAT RM(FM_CMPRAT, FB_CMPRAT) + +/****************************** + * R_CATKTCL (0x29) * + ******************************/ + +/* Field Offsets */ +#define FB_CATKTCL 0 + +/* Field Masks */ +#define FM_CATKTCL 0XFF + +/* Register Masks */ +#define RM_CATKTCL RM(FM_CATKTCL, FB_CATKTCL) + +/****************************** + * R_CATKTCH (0x2A) * + ******************************/ + +/* Field Offsets */ +#define FB_CATKTCH 0 + +/* Field Masks */ +#define FM_CATKTCH 0XFF + +/* Register Masks */ +#define RM_CATKTCH RM(FM_CATKTCH, FB_CATKTCH) + +/****************************** + * R_CRELTCL (0x2B) * + ******************************/ + +/* Field Offsets */ +#define FB_CRELTCL 0 + +/* Field Masks */ +#define FM_CRELTCL 0XFF + +/* Register Masks */ +#define RM_CRELTCL RM(FM_CRELTCL, FB_CRELTCL) + +/****************************** + * R_CRELTCH (0x2C) * + ******************************/ + +/* Field Offsets */ +#define FB_CRELTCH 0 + +/* Field Masks */ +#define FM_CRELTCH 0XFF + +/* Register Masks */ +#define RM_CRELTCH RM(FM_CRELTCH, FB_CRELTCH) + +/**************************** + * R_LIMTH (0x2D) * + ****************************/ + +/* Field Offsets */ +#define FB_LIMTH 0 + +/* Field Masks */ +#define FM_LIMTH 0XFF + +/* Field Values */ +#define FV_LIMTH_0DB 0xFF +#define FV_LIMTH_N95PT625DB 0x0 + +/* Register Masks */ +#define RM_LIMTH RM(FM_LIMTH, FB_LIMTH) + +/* Register Values */ +#define RV_LIMTH_0DB RV(FV_LIMTH_0DB, FB_LIMTH) +#define RV_LIMTH_N95PT625DB RV(FV_LIMTH_N95PT625DB, FB_LIMTH) + +/***************************** + * R_LIMTGT (0x2E) * + *****************************/ + +/* Field Offsets */ +#define FB_LIMTGT 0 + +/* Field Masks */ +#define FM_LIMTGT 0XFF + +/* Field Values */ +#define FV_LIMTGT_0DB 0xFF +#define FV_LIMTGT_N95PT625DB 0x0 + +/* Register Masks */ +#define RM_LIMTGT RM(FM_LIMTGT, FB_LIMTGT) + +/* Register Values */ +#define RV_LIMTGT_0DB RV(FV_LIMTGT_0DB, FB_LIMTGT) +#define RV_LIMTGT_N95PT625DB \ + RV(FV_LIMTGT_N95PT625DB, FB_LIMTGT) + + +/****************************** + * R_LATKTCL (0x2F) * + ******************************/ + +/* Field Offsets */ +#define FB_LATKTCL 0 + +/* Field Masks */ +#define FM_LATKTCL 0XFF + +/* Register Masks */ +#define RM_LATKTCL RM(FM_LATKTCL, FB_LATKTCL) + +/****************************** + * R_LATKTCH (0x30) * + ******************************/ + +/* Field Offsets */ +#define FB_LATKTCH 0 + +/* Field Masks */ +#define FM_LATKTCH 0XFF + +/* Register Masks */ +#define RM_LATKTCH RM(FM_LATKTCH, FB_LATKTCH) + +/****************************** + * R_LRELTCL (0x31) * + ******************************/ + +/* Field Offsets */ +#define FB_LRELTCL 0 + +/* Field Masks */ +#define FM_LRELTCL 0XFF + +/* Register Masks */ +#define RM_LRELTCL RM(FM_LRELTCL, FB_LRELTCL) + +/****************************** + * R_LRELTCH (0x32) * + ******************************/ + +/* Field Offsets */ +#define FB_LRELTCH 0 + +/* Field Masks */ +#define FM_LRELTCH 0XFF + +/* Register Masks */ +#define RM_LRELTCH RM(FM_LRELTCH, FB_LRELTCH) + +/**************************** + * R_EXPTH (0x33) * + ****************************/ + +/* Field Offsets */ +#define FB_EXPTH 0 + +/* Field Masks */ +#define FM_EXPTH 0XFF + +/* Field Values */ +#define FV_EXPTH_0DB 0xFF +#define FV_EXPTH_N95PT625DB 0x0 + +/* Register Masks */ +#define RM_EXPTH RM(FM_EXPTH, FB_EXPTH) + +/* Register Values */ +#define RV_EXPTH_0DB RV(FV_EXPTH_0DB, FB_EXPTH) +#define RV_EXPTH_N95PT625DB RV(FV_EXPTH_N95PT625DB, FB_EXPTH) + +/***************************** + * R_EXPRAT (0x34) * + *****************************/ + +/* Field Offsets */ +#define FB_EXPRAT 0 + +/* Field Masks */ +#define FM_EXPRAT 0X7 + +/* Register Masks */ +#define RM_EXPRAT RM(FM_EXPRAT, FB_EXPRAT) + +/****************************** + * R_XATKTCL (0x35) * + ******************************/ + +/* Field Offsets */ +#define FB_XATKTCL 0 + +/* Field Masks */ +#define FM_XATKTCL 0XFF + +/* Register Masks */ +#define RM_XATKTCL RM(FM_XATKTCL, FB_XATKTCL) + +/****************************** + * R_XATKTCH (0x36) * + ******************************/ + +/* Field Offsets */ +#define FB_XATKTCH 0 + +/* Field Masks */ +#define FM_XATKTCH 0XFF + +/* Register Masks */ +#define RM_XATKTCH RM(FM_XATKTCH, FB_XATKTCH) + +/****************************** + * R_XRELTCL (0x37) * + ******************************/ + +/* Field Offsets */ +#define FB_XRELTCL 0 + +/* Field Masks */ +#define FM_XRELTCL 0XFF + +/* Register Masks */ +#define RM_XRELTCL RM(FM_XRELTCL, FB_XRELTCL) + +/****************************** + * R_XRELTCH (0x38) * + ******************************/ + +/* Field Offsets */ +#define FB_XRELTCH 0 + +/* Field Masks */ +#define FM_XRELTCH 0XFF + +/* Register Masks */ +#define RM_XRELTCH RM(FM_XRELTCH, FB_XRELTCH) + +/**************************** + * R_FXCTL (0x39) * + ****************************/ + +/* Field Offsets */ +#define FB_FXCTL_3DEN 4 +#define FB_FXCTL_TEEN 3 +#define FB_FXCTL_TNLFBYPASS 2 +#define FB_FXCTL_BEEN 1 +#define FB_FXCTL_BNLFBYPASS 0 + +/* Field Masks */ +#define FM_FXCTL_3DEN 0X1 +#define FM_FXCTL_TEEN 0X1 +#define FM_FXCTL_TNLFBYPASS 0X1 +#define FM_FXCTL_BEEN 0X1 +#define FM_FXCTL_BNLFBYPASS 0X1 + +/* Field Values */ +#define FV_FXCTL_3DEN_ENABLE 0x1 +#define FV_FXCTL_3DEN_DISABLE 0x0 +#define FV_FXCTL_TEEN_ENABLE 0x1 +#define FV_FXCTL_TEEN_DISABLE 0x0 +#define FV_FXCTL_TNLFBYPASS_ENABLE 0x1 +#define FV_FXCTL_TNLFBYPASS_DISABLE 0x0 +#define FV_FXCTL_BEEN_ENABLE 0x1 +#define FV_FXCTL_BEEN_DISABLE 0x0 +#define FV_FXCTL_BNLFBYPASS_ENABLE 0x1 +#define FV_FXCTL_BNLFBYPASS_DISABLE 0x0 + +/* Register Masks */ +#define RM_FXCTL_3DEN RM(FM_FXCTL_3DEN, FB_FXCTL_3DEN) +#define RM_FXCTL_TEEN RM(FM_FXCTL_TEEN, FB_FXCTL_TEEN) +#define RM_FXCTL_TNLFBYPASS \ + RM(FM_FXCTL_TNLFBYPASS, FB_FXCTL_TNLFBYPASS) + +#define RM_FXCTL_BEEN RM(FM_FXCTL_BEEN, FB_FXCTL_BEEN) +#define RM_FXCTL_BNLFBYPASS \ + RM(FM_FXCTL_BNLFBYPASS, FB_FXCTL_BNLFBYPASS) + + +/* Register Values */ +#define RV_FXCTL_3DEN_ENABLE \ + RV(FV_FXCTL_3DEN_ENABLE, FB_FXCTL_3DEN) + +#define RV_FXCTL_3DEN_DISABLE \ + RV(FV_FXCTL_3DEN_DISABLE, FB_FXCTL_3DEN) + +#define RV_FXCTL_TEEN_ENABLE \ + RV(FV_FXCTL_TEEN_ENABLE, FB_FXCTL_TEEN) + +#define RV_FXCTL_TEEN_DISABLE \ + RV(FV_FXCTL_TEEN_DISABLE, FB_FXCTL_TEEN) + +#define RV_FXCTL_TNLFBYPASS_ENABLE \ + RV(FV_FXCTL_TNLFBYPASS_ENABLE, FB_FXCTL_TNLFBYPASS) + +#define RV_FXCTL_TNLFBYPASS_DISABLE \ + RV(FV_FXCTL_TNLFBYPASS_DISABLE, FB_FXCTL_TNLFBYPASS) + +#define RV_FXCTL_BEEN_ENABLE \ + RV(FV_FXCTL_BEEN_ENABLE, FB_FXCTL_BEEN) + +#define RV_FXCTL_BEEN_DISABLE \ + RV(FV_FXCTL_BEEN_DISABLE, FB_FXCTL_BEEN) + +#define RV_FXCTL_BNLFBYPASS_ENABLE \ + RV(FV_FXCTL_BNLFBYPASS_ENABLE, FB_FXCTL_BNLFBYPASS) + +#define RV_FXCTL_BNLFBYPASS_DISABLE \ + RV(FV_FXCTL_BNLFBYPASS_DISABLE, FB_FXCTL_BNLFBYPASS) + + +/******************************* + * R_DACCRWRL (0x3A) * + *******************************/ + +/* Field Offsets */ +#define FB_DACCRWRL_DACCRWDL 0 + +/* Field Masks */ +#define FM_DACCRWRL_DACCRWDL 0XFF + +/* Register Masks */ +#define RM_DACCRWRL_DACCRWDL \ + RM(FM_DACCRWRL_DACCRWDL, FB_DACCRWRL_DACCRWDL) + + +/******************************* + * R_DACCRWRM (0x3B) * + *******************************/ + +/* Field Offsets */ +#define FB_DACCRWRM_DACCRWDM 0 + +/* Field Masks */ +#define FM_DACCRWRM_DACCRWDM 0XFF + +/* Register Masks */ +#define RM_DACCRWRM_DACCRWDM \ + RM(FM_DACCRWRM_DACCRWDM, FB_DACCRWRM_DACCRWDM) + + +/******************************* + * R_DACCRWRH (0x3C) * + *******************************/ + +/* Field Offsets */ +#define FB_DACCRWRH_DACCRWDH 0 + +/* Field Masks */ +#define FM_DACCRWRH_DACCRWDH 0XFF + +/* Register Masks */ +#define RM_DACCRWRH_DACCRWDH \ + RM(FM_DACCRWRH_DACCRWDH, FB_DACCRWRH_DACCRWDH) + + +/******************************* + * R_DACCRRDL (0x3D) * + *******************************/ + +/* Field Offsets */ +#define FB_DACCRRDL 0 + +/* Field Masks */ +#define FM_DACCRRDL 0XFF + +/* Register Masks */ +#define RM_DACCRRDL RM(FM_DACCRRDL, FB_DACCRRDL) + +/******************************* + * R_DACCRRDM (0x3E) * + *******************************/ + +/* Field Offsets */ +#define FB_DACCRRDM 0 + +/* Field Masks */ +#define FM_DACCRRDM 0XFF + +/* Register Masks */ +#define RM_DACCRRDM RM(FM_DACCRRDM, FB_DACCRRDM) + +/******************************* + * R_DACCRRDH (0x3F) * + *******************************/ + +/* Field Offsets */ +#define FB_DACCRRDH 0 + +/* Field Masks */ +#define FM_DACCRRDH 0XFF + +/* Register Masks */ +#define RM_DACCRRDH RM(FM_DACCRRDH, FB_DACCRRDH) + +/******************************** + * R_DACCRADDR (0x40) * + ********************************/ + +/* Field Offsets */ +#define FB_DACCRADDR_DACCRADD 0 + +/* Field Masks */ +#define FM_DACCRADDR_DACCRADD 0XFF + +/* Register Masks */ +#define RM_DACCRADDR_DACCRADD \ + RM(FM_DACCRADDR_DACCRADD, FB_DACCRADDR_DACCRADD) + + +/****************************** + * R_DCOFSEL (0x41) * + ******************************/ + +/* Field Offsets */ +#define FB_DCOFSEL_DC_COEF_SEL 0 + +/* Field Masks */ +#define FM_DCOFSEL_DC_COEF_SEL 0X7 + +/* Field Values */ +#define FV_DCOFSEL_DC_COEF_SEL_2_N8 0x0 +#define FV_DCOFSEL_DC_COEF_SEL_2_N9 0x1 +#define FV_DCOFSEL_DC_COEF_SEL_2_N10 0x2 +#define FV_DCOFSEL_DC_COEF_SEL_2_N11 0x3 +#define FV_DCOFSEL_DC_COEF_SEL_2_N12 0x4 +#define FV_DCOFSEL_DC_COEF_SEL_2_N13 0x5 +#define FV_DCOFSEL_DC_COEF_SEL_2_N14 0x6 +#define FV_DCOFSEL_DC_COEF_SEL_2_N15 0x7 + +/* Register Masks */ +#define RM_DCOFSEL_DC_COEF_SEL \ + RM(FM_DCOFSEL_DC_COEF_SEL, FB_DCOFSEL_DC_COEF_SEL) + + +/* Register Values */ +#define RV_DCOFSEL_DC_COEF_SEL_2_N8 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N8, FB_DCOFSEL_DC_COEF_SEL) + +#define RV_DCOFSEL_DC_COEF_SEL_2_N9 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N9, FB_DCOFSEL_DC_COEF_SEL) + +#define RV_DCOFSEL_DC_COEF_SEL_2_N10 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N10, FB_DCOFSEL_DC_COEF_SEL) + +#define RV_DCOFSEL_DC_COEF_SEL_2_N11 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N11, FB_DCOFSEL_DC_COEF_SEL) + +#define RV_DCOFSEL_DC_COEF_SEL_2_N12 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N12, FB_DCOFSEL_DC_COEF_SEL) + +#define RV_DCOFSEL_DC_COEF_SEL_2_N13 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N13, FB_DCOFSEL_DC_COEF_SEL) + +#define RV_DCOFSEL_DC_COEF_SEL_2_N14 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N14, FB_DCOFSEL_DC_COEF_SEL) + +#define RV_DCOFSEL_DC_COEF_SEL_2_N15 \ + RV(FV_DCOFSEL_DC_COEF_SEL_2_N15, FB_DCOFSEL_DC_COEF_SEL) + + +/****************************** + * R_PLLCTL9 (0x4E) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTL9_REFDIV_PLL1 0 + +/* Field Masks */ +#define FM_PLLCTL9_REFDIV_PLL1 0XFF + +/* Register Masks */ +#define RM_PLLCTL9_REFDIV_PLL1 \ + RM(FM_PLLCTL9_REFDIV_PLL1, FB_PLLCTL9_REFDIV_PLL1) + + +/****************************** + * R_PLLCTLA (0x4F) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTLA_OUTDIV_PLL1 0 + +/* Field Masks */ +#define FM_PLLCTLA_OUTDIV_PLL1 0XFF + +/* Register Masks */ +#define RM_PLLCTLA_OUTDIV_PLL1 \ + RM(FM_PLLCTLA_OUTDIV_PLL1, FB_PLLCTLA_OUTDIV_PLL1) + + +/****************************** + * R_PLLCTLB (0x50) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTLB_FBDIV_PLL1L 0 + +/* Field Masks */ +#define FM_PLLCTLB_FBDIV_PLL1L 0XFF + +/* Register Masks */ +#define RM_PLLCTLB_FBDIV_PLL1L \ + RM(FM_PLLCTLB_FBDIV_PLL1L, FB_PLLCTLB_FBDIV_PLL1L) + + +/****************************** + * R_PLLCTLC (0x51) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTLC_FBDIV_PLL1H 0 + +/* Field Masks */ +#define FM_PLLCTLC_FBDIV_PLL1H 0X7 + +/* Register Masks */ +#define RM_PLLCTLC_FBDIV_PLL1H \ + RM(FM_PLLCTLC_FBDIV_PLL1H, FB_PLLCTLC_FBDIV_PLL1H) + + +/****************************** + * R_PLLCTLD (0x52) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTLD_RZ_PLL1 3 +#define FB_PLLCTLD_CP_PLL1 0 + +/* Field Masks */ +#define FM_PLLCTLD_RZ_PLL1 0X7 +#define FM_PLLCTLD_CP_PLL1 0X7 + +/* Register Masks */ +#define RM_PLLCTLD_RZ_PLL1 \ + RM(FM_PLLCTLD_RZ_PLL1, FB_PLLCTLD_RZ_PLL1) + +#define RM_PLLCTLD_CP_PLL1 \ + RM(FM_PLLCTLD_CP_PLL1, FB_PLLCTLD_CP_PLL1) + + +/****************************** + * R_PLLCTLE (0x53) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTLE_REFDIV_PLL2 0 + +/* Field Masks */ +#define FM_PLLCTLE_REFDIV_PLL2 0XFF + +/* Register Masks */ +#define RM_PLLCTLE_REFDIV_PLL2 \ + RM(FM_PLLCTLE_REFDIV_PLL2, FB_PLLCTLE_REFDIV_PLL2) + + +/****************************** + * R_PLLCTLF (0x54) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTLF_OUTDIV_PLL2 0 + +/* Field Masks */ +#define FM_PLLCTLF_OUTDIV_PLL2 0XFF + +/* Register Masks */ +#define RM_PLLCTLF_OUTDIV_PLL2 \ + RM(FM_PLLCTLF_OUTDIV_PLL2, FB_PLLCTLF_OUTDIV_PLL2) + + +/******************************* + * R_PLLCTL10 (0x55) * + *******************************/ + +/* Field Offsets */ +#define FB_PLLCTL10_FBDIV_PLL2L 0 + +/* Field Masks */ +#define FM_PLLCTL10_FBDIV_PLL2L 0XFF + +/* Register Masks */ +#define RM_PLLCTL10_FBDIV_PLL2L \ + RM(FM_PLLCTL10_FBDIV_PLL2L, FB_PLLCTL10_FBDIV_PLL2L) + + +/******************************* + * R_PLLCTL11 (0x56) * + *******************************/ + +/* Field Offsets */ +#define FB_PLLCTL11_FBDIV_PLL2H 0 + +/* Field Masks */ +#define FM_PLLCTL11_FBDIV_PLL2H 0X7 + +/* Register Masks */ +#define RM_PLLCTL11_FBDIV_PLL2H \ + RM(FM_PLLCTL11_FBDIV_PLL2H, FB_PLLCTL11_FBDIV_PLL2H) + + +/******************************* + * R_PLLCTL12 (0x57) * + *******************************/ + +/* Field Offsets */ +#define FB_PLLCTL12_RZ_PLL2 3 +#define FB_PLLCTL12_CP_PLL2 0 + +/* Field Masks */ +#define FM_PLLCTL12_RZ_PLL2 0X7 +#define FM_PLLCTL12_CP_PLL2 0X7 + +/* Register Masks */ +#define RM_PLLCTL12_RZ_PLL2 \ + RM(FM_PLLCTL12_RZ_PLL2, FB_PLLCTL12_RZ_PLL2) + +#define RM_PLLCTL12_CP_PLL2 \ + RM(FM_PLLCTL12_CP_PLL2, FB_PLLCTL12_CP_PLL2) + + +/******************************* + * R_PLLCTL1B (0x60) * + *******************************/ + +/* Field Offsets */ +#define FB_PLLCTL1B_VCOI_PLL2 4 +#define FB_PLLCTL1B_VCOI_PLL1 2 + +/* Field Masks */ +#define FM_PLLCTL1B_VCOI_PLL2 0X3 +#define FM_PLLCTL1B_VCOI_PLL1 0X3 + +/* Register Masks */ +#define RM_PLLCTL1B_VCOI_PLL2 \ + RM(FM_PLLCTL1B_VCOI_PLL2, FB_PLLCTL1B_VCOI_PLL2) + +#define RM_PLLCTL1B_VCOI_PLL1 \ + RM(FM_PLLCTL1B_VCOI_PLL1, FB_PLLCTL1B_VCOI_PLL1) + + +/******************************* + * R_PLLCTL1C (0x61) * + *******************************/ + +/* Field Offsets */ +#define FB_PLLCTL1C_PDB_PLL2 2 +#define FB_PLLCTL1C_PDB_PLL1 1 + +/* Field Masks */ +#define FM_PLLCTL1C_PDB_PLL2 0X1 +#define FM_PLLCTL1C_PDB_PLL1 0X1 + +/* Field Values */ +#define FV_PLLCTL1C_PDB_PLL2_ENABLE 0x1 +#define FV_PLLCTL1C_PDB_PLL2_DISABLE 0x0 +#define FV_PLLCTL1C_PDB_PLL1_ENABLE 0x1 +#define FV_PLLCTL1C_PDB_PLL1_DISABLE 0x0 + +/* Register Masks */ +#define RM_PLLCTL1C_PDB_PLL2 \ + RM(FM_PLLCTL1C_PDB_PLL2, FB_PLLCTL1C_PDB_PLL2) + +#define RM_PLLCTL1C_PDB_PLL1 \ + RM(FM_PLLCTL1C_PDB_PLL1, FB_PLLCTL1C_PDB_PLL1) + + +/* Register Values */ +#define RV_PLLCTL1C_PDB_PLL2_ENABLE \ + RV(FV_PLLCTL1C_PDB_PLL2_ENABLE, FB_PLLCTL1C_PDB_PLL2) + +#define RV_PLLCTL1C_PDB_PLL2_DISABLE \ + RV(FV_PLLCTL1C_PDB_PLL2_DISABLE, FB_PLLCTL1C_PDB_PLL2) + +#define RV_PLLCTL1C_PDB_PLL1_ENABLE \ + RV(FV_PLLCTL1C_PDB_PLL1_ENABLE, FB_PLLCTL1C_PDB_PLL1) + +#define RV_PLLCTL1C_PDB_PLL1_DISABLE \ + RV(FV_PLLCTL1C_PDB_PLL1_DISABLE, FB_PLLCTL1C_PDB_PLL1) + + +/******************************* + * R_TIMEBASE (0x77) * + *******************************/ + +/* Field Offsets */ +#define FB_TIMEBASE_DIVIDER 0 + +/* Field Masks */ +#define FM_TIMEBASE_DIVIDER 0XFF + +/* Register Masks */ +#define RM_TIMEBASE_DIVIDER \ + RM(FM_TIMEBASE_DIVIDER, FB_TIMEBASE_DIVIDER) + + +/***************************** + * R_DEVIDL (0x7D) * + *****************************/ + +/* Field Offsets */ +#define FB_DEVIDL_DIDL 0 + +/* Field Masks */ +#define FM_DEVIDL_DIDL 0XFF + +/* Register Masks */ +#define RM_DEVIDL_DIDL RM(FM_DEVIDL_DIDL, FB_DEVIDL_DIDL) + +/***************************** + * R_DEVIDH (0x7E) * + *****************************/ + +/* Field Offsets */ +#define FB_DEVIDH_DIDH 0 + +/* Field Masks */ +#define FM_DEVIDH_DIDH 0XFF + +/* Register Masks */ +#define RM_DEVIDH_DIDH RM(FM_DEVIDH_DIDH, FB_DEVIDH_DIDH) + +/**************************** + * R_RESET (0x80) * + ****************************/ + +/* Field Offsets */ +#define FB_RESET 0 + +/* Field Masks */ +#define FM_RESET 0XFF + +/* Field Values */ +#define FV_RESET_ENABLE 0x85 + +/* Register Masks */ +#define RM_RESET RM(FM_RESET, FB_RESET) + +/* Register Values */ +#define RV_RESET_ENABLE RV(FV_RESET_ENABLE, FB_RESET) + +/******************************** + * R_DACCRSTAT (0x8A) * + ********************************/ + +/* Field Offsets */ +#define FB_DACCRSTAT_DACCR_BUSY 7 + +/* Field Masks */ +#define FM_DACCRSTAT_DACCR_BUSY 0X1 + +/* Register Masks */ +#define RM_DACCRSTAT_DACCR_BUSY \ + RM(FM_DACCRSTAT_DACCR_BUSY, FB_DACCRSTAT_DACCR_BUSY) + + +/****************************** + * R_PLLCTL0 (0x8E) * + ******************************/ + +/* Field Offsets */ +#define FB_PLLCTL0_PLL2_LOCK 1 +#define FB_PLLCTL0_PLL1_LOCK 0 + +/* Field Masks */ +#define FM_PLLCTL0_PLL2_LOCK 0X1 +#define FM_PLLCTL0_PLL1_LOCK 0X1 + +/* Register Masks */ +#define RM_PLLCTL0_PLL2_LOCK \ + RM(FM_PLLCTL0_PLL2_LOCK, FB_PLLCTL0_PLL2_LOCK) + +#define RM_PLLCTL0_PLL1_LOCK \ + RM(FM_PLLCTL0_PLL1_LOCK, FB_PLLCTL0_PLL1_LOCK) + + +/******************************** + * R_PLLREFSEL (0x8F) * + ********************************/ + +/* Field Offsets */ +#define FB_PLLREFSEL_PLL2_REF_SEL 4 +#define FB_PLLREFSEL_PLL1_REF_SEL 0 + +/* Field Masks */ +#define FM_PLLREFSEL_PLL2_REF_SEL 0X7 +#define FM_PLLREFSEL_PLL1_REF_SEL 0X7 + +/* Field Values */ +#define FV_PLLREFSEL_PLL2_REF_SEL_XTAL_MCLK1 0x0 +#define FV_PLLREFSEL_PLL2_REF_SEL_MCLK2 0x1 +#define FV_PLLREFSEL_PLL1_REF_SEL_XTAL_MCLK1 0x0 +#define FV_PLLREFSEL_PLL1_REF_SEL_MCLK2 0x1 + +/* Register Masks */ +#define RM_PLLREFSEL_PLL2_REF_SEL \ + RM(FM_PLLREFSEL_PLL2_REF_SEL, FB_PLLREFSEL_PLL2_REF_SEL) + +#define RM_PLLREFSEL_PLL1_REF_SEL \ + RM(FM_PLLREFSEL_PLL1_REF_SEL, FB_PLLREFSEL_PLL1_REF_SEL) + + +/* Register Values */ +#define RV_PLLREFSEL_PLL2_REF_SEL_XTAL_MCLK1 \ + RV(FV_PLLREFSEL_PLL2_REF_SEL_XTAL_MCLK1, FB_PLLREFSEL_PLL2_REF_SEL) + +#define RV_PLLREFSEL_PLL2_REF_SEL_MCLK2 \ + RV(FV_PLLREFSEL_PLL2_REF_SEL_MCLK2, FB_PLLREFSEL_PLL2_REF_SEL) + +#define RV_PLLREFSEL_PLL1_REF_SEL_XTAL_MCLK1 \ + RV(FV_PLLREFSEL_PLL1_REF_SEL_XTAL_MCLK1, FB_PLLREFSEL_PLL1_REF_SEL) + +#define RV_PLLREFSEL_PLL1_REF_SEL_MCLK2 \ + RV(FV_PLLREFSEL_PLL1_REF_SEL_MCLK2, FB_PLLREFSEL_PLL1_REF_SEL) + + +/******************************* + * R_DACMBCEN (0xC7) * + *******************************/ + +/* Field Offsets */ +#define FB_DACMBCEN_MBCEN3 2 +#define FB_DACMBCEN_MBCEN2 1 +#define FB_DACMBCEN_MBCEN1 0 + +/* Field Masks */ +#define FM_DACMBCEN_MBCEN3 0X1 +#define FM_DACMBCEN_MBCEN2 0X1 +#define FM_DACMBCEN_MBCEN1 0X1 + +/* Register Masks */ +#define RM_DACMBCEN_MBCEN3 \ + RM(FM_DACMBCEN_MBCEN3, FB_DACMBCEN_MBCEN3) + +#define RM_DACMBCEN_MBCEN2 \ + RM(FM_DACMBCEN_MBCEN2, FB_DACMBCEN_MBCEN2) + +#define RM_DACMBCEN_MBCEN1 \ + RM(FM_DACMBCEN_MBCEN1, FB_DACMBCEN_MBCEN1) + + +/******************************** + * R_DACMBCCTL (0xC8) * + ********************************/ + +/* Field Offsets */ +#define FB_DACMBCCTL_LVLMODE3 5 +#define FB_DACMBCCTL_WINSEL3 4 +#define FB_DACMBCCTL_LVLMODE2 3 +#define FB_DACMBCCTL_WINSEL2 2 +#define FB_DACMBCCTL_LVLMODE1 1 +#define FB_DACMBCCTL_WINSEL1 0 + +/* Field Masks */ +#define FM_DACMBCCTL_LVLMODE3 0X1 +#define FM_DACMBCCTL_WINSEL3 0X1 +#define FM_DACMBCCTL_LVLMODE2 0X1 +#define FM_DACMBCCTL_WINSEL2 0X1 +#define FM_DACMBCCTL_LVLMODE1 0X1 +#define FM_DACMBCCTL_WINSEL1 0X1 + +/* Register Masks */ +#define RM_DACMBCCTL_LVLMODE3 \ + RM(FM_DACMBCCTL_LVLMODE3, FB_DACMBCCTL_LVLMODE3) + +#define RM_DACMBCCTL_WINSEL3 \ + RM(FM_DACMBCCTL_WINSEL3, FB_DACMBCCTL_WINSEL3) + +#define RM_DACMBCCTL_LVLMODE2 \ + RM(FM_DACMBCCTL_LVLMODE2, FB_DACMBCCTL_LVLMODE2) + +#define RM_DACMBCCTL_WINSEL2 \ + RM(FM_DACMBCCTL_WINSEL2, FB_DACMBCCTL_WINSEL2) + +#define RM_DACMBCCTL_LVLMODE1 \ + RM(FM_DACMBCCTL_LVLMODE1, FB_DACMBCCTL_LVLMODE1) + +#define RM_DACMBCCTL_WINSEL1 \ + RM(FM_DACMBCCTL_WINSEL1, FB_DACMBCCTL_WINSEL1) + + +/********************************* + * R_DACMBCMUG1 (0xC9) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCMUG1_PHASE 5 +#define FB_DACMBCMUG1_MUGAIN 0 + +/* Field Masks */ +#define FM_DACMBCMUG1_PHASE 0X1 +#define FM_DACMBCMUG1_MUGAIN 0X1F + +/* Register Masks */ +#define RM_DACMBCMUG1_PHASE \ + RM(FM_DACMBCMUG1_PHASE, FB_DACMBCMUG1_PHASE) + +#define RM_DACMBCMUG1_MUGAIN \ + RM(FM_DACMBCMUG1_MUGAIN, FB_DACMBCMUG1_MUGAIN) + + +/********************************* + * R_DACMBCTHR1 (0xCA) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCTHR1_THRESH 0 + +/* Field Masks */ +#define FM_DACMBCTHR1_THRESH 0XFF + +/* Register Masks */ +#define RM_DACMBCTHR1_THRESH \ + RM(FM_DACMBCTHR1_THRESH, FB_DACMBCTHR1_THRESH) + + +/********************************* + * R_DACMBCRAT1 (0xCB) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCRAT1_RATIO 0 + +/* Field Masks */ +#define FM_DACMBCRAT1_RATIO 0X1F + +/* Register Masks */ +#define RM_DACMBCRAT1_RATIO \ + RM(FM_DACMBCRAT1_RATIO, FB_DACMBCRAT1_RATIO) + + +/********************************** + * R_DACMBCATK1L (0xCC) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCATK1L_TCATKL 0 + +/* Field Masks */ +#define FM_DACMBCATK1L_TCATKL 0XFF + +/* Register Masks */ +#define RM_DACMBCATK1L_TCATKL \ + RM(FM_DACMBCATK1L_TCATKL, FB_DACMBCATK1L_TCATKL) + + +/********************************** + * R_DACMBCATK1H (0xCD) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCATK1H_TCATKH 0 + +/* Field Masks */ +#define FM_DACMBCATK1H_TCATKH 0XFF + +/* Register Masks */ +#define RM_DACMBCATK1H_TCATKH \ + RM(FM_DACMBCATK1H_TCATKH, FB_DACMBCATK1H_TCATKH) + + +/********************************** + * R_DACMBCREL1L (0xCE) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCREL1L_TCRELL 0 + +/* Field Masks */ +#define FM_DACMBCREL1L_TCRELL 0XFF + +/* Register Masks */ +#define RM_DACMBCREL1L_TCRELL \ + RM(FM_DACMBCREL1L_TCRELL, FB_DACMBCREL1L_TCRELL) + + +/********************************** + * R_DACMBCREL1H (0xCF) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCREL1H_TCRELH 0 + +/* Field Masks */ +#define FM_DACMBCREL1H_TCRELH 0XFF + +/* Register Masks */ +#define RM_DACMBCREL1H_TCRELH \ + RM(FM_DACMBCREL1H_TCRELH, FB_DACMBCREL1H_TCRELH) + + +/********************************* + * R_DACMBCMUG2 (0xD0) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCMUG2_PHASE 5 +#define FB_DACMBCMUG2_MUGAIN 0 + +/* Field Masks */ +#define FM_DACMBCMUG2_PHASE 0X1 +#define FM_DACMBCMUG2_MUGAIN 0X1F + +/* Register Masks */ +#define RM_DACMBCMUG2_PHASE \ + RM(FM_DACMBCMUG2_PHASE, FB_DACMBCMUG2_PHASE) + +#define RM_DACMBCMUG2_MUGAIN \ + RM(FM_DACMBCMUG2_MUGAIN, FB_DACMBCMUG2_MUGAIN) + + +/********************************* + * R_DACMBCTHR2 (0xD1) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCTHR2_THRESH 0 + +/* Field Masks */ +#define FM_DACMBCTHR2_THRESH 0XFF + +/* Register Masks */ +#define RM_DACMBCTHR2_THRESH \ + RM(FM_DACMBCTHR2_THRESH, FB_DACMBCTHR2_THRESH) + + +/********************************* + * R_DACMBCRAT2 (0xD2) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCRAT2_RATIO 0 + +/* Field Masks */ +#define FM_DACMBCRAT2_RATIO 0X1F + +/* Register Masks */ +#define RM_DACMBCRAT2_RATIO \ + RM(FM_DACMBCRAT2_RATIO, FB_DACMBCRAT2_RATIO) + + +/********************************** + * R_DACMBCATK2L (0xD3) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCATK2L_TCATKL 0 + +/* Field Masks */ +#define FM_DACMBCATK2L_TCATKL 0XFF + +/* Register Masks */ +#define RM_DACMBCATK2L_TCATKL \ + RM(FM_DACMBCATK2L_TCATKL, FB_DACMBCATK2L_TCATKL) + + +/********************************** + * R_DACMBCATK2H (0xD4) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCATK2H_TCATKH 0 + +/* Field Masks */ +#define FM_DACMBCATK2H_TCATKH 0XFF + +/* Register Masks */ +#define RM_DACMBCATK2H_TCATKH \ + RM(FM_DACMBCATK2H_TCATKH, FB_DACMBCATK2H_TCATKH) + + +/********************************** + * R_DACMBCREL2L (0xD5) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCREL2L_TCRELL 0 + +/* Field Masks */ +#define FM_DACMBCREL2L_TCRELL 0XFF + +/* Register Masks */ +#define RM_DACMBCREL2L_TCRELL \ + RM(FM_DACMBCREL2L_TCRELL, FB_DACMBCREL2L_TCRELL) + + +/********************************** + * R_DACMBCREL2H (0xD6) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCREL2H_TCRELH 0 + +/* Field Masks */ +#define FM_DACMBCREL2H_TCRELH 0XFF + +/* Register Masks */ +#define RM_DACMBCREL2H_TCRELH \ + RM(FM_DACMBCREL2H_TCRELH, FB_DACMBCREL2H_TCRELH) + + +/********************************* + * R_DACMBCMUG3 (0xD7) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCMUG3_PHASE 5 +#define FB_DACMBCMUG3_MUGAIN 0 + +/* Field Masks */ +#define FM_DACMBCMUG3_PHASE 0X1 +#define FM_DACMBCMUG3_MUGAIN 0X1F + +/* Register Masks */ +#define RM_DACMBCMUG3_PHASE \ + RM(FM_DACMBCMUG3_PHASE, FB_DACMBCMUG3_PHASE) + +#define RM_DACMBCMUG3_MUGAIN \ + RM(FM_DACMBCMUG3_MUGAIN, FB_DACMBCMUG3_MUGAIN) + + +/********************************* + * R_DACMBCTHR3 (0xD8) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCTHR3_THRESH 0 + +/* Field Masks */ +#define FM_DACMBCTHR3_THRESH 0XFF + +/* Register Masks */ +#define RM_DACMBCTHR3_THRESH \ + RM(FM_DACMBCTHR3_THRESH, FB_DACMBCTHR3_THRESH) + + +/********************************* + * R_DACMBCRAT3 (0xD9) * + *********************************/ + +/* Field Offsets */ +#define FB_DACMBCRAT3_RATIO 0 + +/* Field Masks */ +#define FM_DACMBCRAT3_RATIO 0X1F + +/* Register Masks */ +#define RM_DACMBCRAT3_RATIO \ + RM(FM_DACMBCRAT3_RATIO, FB_DACMBCRAT3_RATIO) + + +/********************************** + * R_DACMBCATK3L (0xDA) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCATK3L_TCATKL 0 + +/* Field Masks */ +#define FM_DACMBCATK3L_TCATKL 0XFF + +/* Register Masks */ +#define RM_DACMBCATK3L_TCATKL \ + RM(FM_DACMBCATK3L_TCATKL, FB_DACMBCATK3L_TCATKL) + + +/********************************** + * R_DACMBCATK3H (0xDB) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCATK3H_TCATKH 0 + +/* Field Masks */ +#define FM_DACMBCATK3H_TCATKH 0XFF + +/* Register Masks */ +#define RM_DACMBCATK3H_TCATKH \ + RM(FM_DACMBCATK3H_TCATKH, FB_DACMBCATK3H_TCATKH) + + +/********************************** + * R_DACMBCREL3L (0xDC) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCREL3L_TCRELL 0 + +/* Field Masks */ +#define FM_DACMBCREL3L_TCRELL 0XFF + +/* Register Masks */ +#define RM_DACMBCREL3L_TCRELL \ + RM(FM_DACMBCREL3L_TCRELL, FB_DACMBCREL3L_TCRELL) + + +/********************************** + * R_DACMBCREL3H (0xDD) * + **********************************/ + +/* Field Offsets */ +#define FB_DACMBCREL3H_TCRELH 0 + +/* Field Masks */ +#define FM_DACMBCREL3H_TCRELH 0XFF + +/* Register Masks */ +#define RM_DACMBCREL3H_TCRELH \ + RM(FM_DACMBCREL3H_TCRELH, FB_DACMBCREL3H_TCRELH) + + +#endif /* __WOOKIE_H__ */ diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c new file mode 100644 index 000000000..00a90ccd6 --- /dev/null +++ b/sound/soc/codecs/tscs454.c @@ -0,0 +1,3497 @@ +// SPDX-License-Identifier: GPL-2.0 +// tscs454.c -- TSCS454 ALSA SoC Audio driver +// Copyright 2018 Tempo Semiconductor, Inc. +// Author: Steven Eckhoff + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tscs454.h" + +static const unsigned int PLL_48K_RATE = (48000 * 256); +static const unsigned int PLL_44_1K_RATE = (44100 * 256); + +#define COEFF_SIZE 3 +#define BIQUAD_COEFF_COUNT 5 +#define BIQUAD_SIZE (COEFF_SIZE * BIQUAD_COEFF_COUNT) + +#define COEFF_RAM_MAX_ADDR 0xcd +#define COEFF_RAM_COEFF_COUNT (COEFF_RAM_MAX_ADDR + 1) +#define COEFF_RAM_SIZE (COEFF_SIZE * COEFF_RAM_COEFF_COUNT) + +enum { + TSCS454_DAI1_ID, + TSCS454_DAI2_ID, + TSCS454_DAI3_ID, + TSCS454_DAI_COUNT, +}; + +struct pll { + int id; + unsigned int users; + struct mutex lock; +}; + +static inline void pll_init(struct pll *pll, int id) +{ + pll->id = id; + mutex_init(&pll->lock); +} + +struct internal_rate { + struct pll *pll; +}; + +struct aif { + unsigned int id; + bool master; + struct pll *pll; +}; + +static inline void aif_init(struct aif *aif, unsigned int id) +{ + aif->id = id; +} + +struct coeff_ram { + u8 cache[COEFF_RAM_SIZE]; + bool synced; + struct mutex lock; +}; + +static inline void init_coeff_ram_cache(u8 *cache) +{ + static const u8 norm_addrs[] = { 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x19, + 0x1f, 0x20, 0x25, 0x2a, 0x2f, 0x34, 0x39, 0x3f, 0x40, 0x45, + 0x4a, 0x4f, 0x54, 0x59, 0x5f, 0x60, 0x65, 0x6a, 0x6f, 0x74, + 0x79, 0x7f, 0x80, 0x85, 0x8c, 0x91, 0x96, 0x97, 0x9c, 0xa3, + 0xa8, 0xad, 0xaf, 0xb0, 0xb5, 0xba, 0xbf, 0xc4, 0xc9}; + int i; + + for (i = 0; i < ARRAY_SIZE(norm_addrs); i++) + cache[((norm_addrs[i] + 1) * COEFF_SIZE) - 1] = 0x40; +} + +static inline void coeff_ram_init(struct coeff_ram *ram) +{ + init_coeff_ram_cache(ram->cache); + mutex_init(&ram->lock); +} + +struct aifs_status { + u8 streams; +}; + +static inline void set_aif_status_active(struct aifs_status *status, + int aif_id, bool playback) +{ + u8 mask = 0x01 << (aif_id * 2 + !playback); + + status->streams |= mask; +} + +static inline void set_aif_status_inactive(struct aifs_status *status, + int aif_id, bool playback) +{ + u8 mask = ~(0x01 << (aif_id * 2 + !playback)); + + status->streams &= mask; +} + +static bool aifs_active(struct aifs_status *status) +{ + return status->streams; +} + +static bool aif_active(struct aifs_status *status, int aif_id) +{ + return (0x03 << aif_id * 2) & status->streams; +} + +struct tscs454 { + struct regmap *regmap; + struct aif aifs[TSCS454_DAI_COUNT]; + + struct aifs_status aifs_status; + struct mutex aifs_status_lock; + + struct pll pll1; + struct pll pll2; + struct internal_rate internal_rate; + + struct coeff_ram dac_ram; + struct coeff_ram spk_ram; + struct coeff_ram sub_ram; + + struct clk *sysclk; + int sysclk_src_id; + unsigned int bclk_freq; +}; + +struct coeff_ram_ctl { + unsigned int addr; + struct soc_bytes_ext bytes_ext; +}; + +static const struct reg_sequence tscs454_patch[] = { + /* Assign ASRC out of the box so DAI 1 just works */ + { R_AUDIOMUX1, FV_ASRCIMUX_I2S1 | FV_I2S2MUX_I2S2 }, + { R_AUDIOMUX2, FV_ASRCOMUX_I2S1 | FV_DACMUX_I2S1 | FV_I2S3MUX_I2S3 }, + { R_AUDIOMUX3, FV_CLSSDMUX_I2S1 | FV_SUBMUX_I2S1_LR }, + { R_TDMCTL0, FV_TDMMD_256 }, + { VIRT_ADDR(0x0A, 0x13), 1 << 3 }, +}; + +static bool tscs454_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case R_PLLSTAT: + + case R_SPKCRRDL: + case R_SPKCRRDM: + case R_SPKCRRDH: + case R_SPKCRS: + + case R_DACCRRDL: + case R_DACCRRDM: + case R_DACCRRDH: + case R_DACCRS: + + case R_SUBCRRDL: + case R_SUBCRRDM: + case R_SUBCRRDH: + case R_SUBCRS: + return true; + default: + return false; + }; +} + +static bool tscs454_writable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case R_SPKCRRDL: + case R_SPKCRRDM: + case R_SPKCRRDH: + + case R_DACCRRDL: + case R_DACCRRDM: + case R_DACCRRDH: + + case R_SUBCRRDL: + case R_SUBCRRDM: + case R_SUBCRRDH: + return false; + default: + return true; + }; +} + +static bool tscs454_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case R_SPKCRWDL: + case R_SPKCRWDM: + case R_SPKCRWDH: + + case R_DACCRWDL: + case R_DACCRWDM: + case R_DACCRWDH: + + case R_SUBCRWDL: + case R_SUBCRWDM: + case R_SUBCRWDH: + return false; + default: + return true; + }; +} + +static bool tscs454_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case R_SPKCRWDL: + case R_SPKCRWDM: + case R_SPKCRWDH: + case R_SPKCRRDL: + case R_SPKCRRDM: + case R_SPKCRRDH: + + case R_DACCRWDL: + case R_DACCRWDM: + case R_DACCRWDH: + case R_DACCRRDL: + case R_DACCRRDM: + case R_DACCRRDH: + + case R_SUBCRWDL: + case R_SUBCRWDM: + case R_SUBCRWDH: + case R_SUBCRRDL: + case R_SUBCRRDM: + case R_SUBCRRDH: + return true; + default: + return false; + }; +} + +static const struct regmap_range_cfg tscs454_regmap_range_cfg = { + .name = "Pages", + .range_min = VIRT_BASE, + .range_max = VIRT_ADDR(0xFE, 0x02), + .selector_reg = R_PAGESEL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, +}; + +static struct regmap_config const tscs454_regmap_cfg = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = tscs454_writable, + .readable_reg = tscs454_readable, + .volatile_reg = tscs454_volatile, + .precious_reg = tscs454_precious, + .ranges = &tscs454_regmap_range_cfg, + .num_ranges = 1, + .max_register = VIRT_ADDR(0xFE, 0x02), + .cache_type = REGCACHE_RBTREE, +}; + +static inline int tscs454_data_init(struct tscs454 *tscs454, + struct i2c_client *i2c) +{ + int i; + int ret; + + tscs454->regmap = devm_regmap_init_i2c(i2c, &tscs454_regmap_cfg); + if (IS_ERR(tscs454->regmap)) { + ret = PTR_ERR(tscs454->regmap); + return ret; + } + + for (i = 0; i < TSCS454_DAI_COUNT; i++) + aif_init(&tscs454->aifs[i], i); + + mutex_init(&tscs454->aifs_status_lock); + pll_init(&tscs454->pll1, 1); + pll_init(&tscs454->pll2, 2); + + coeff_ram_init(&tscs454->dac_ram); + coeff_ram_init(&tscs454->spk_ram); + coeff_ram_init(&tscs454->sub_ram); + + return 0; +} + +struct reg_setting { + unsigned int addr; + unsigned int val; +}; + +static int coeff_ram_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + struct coeff_ram_ctl *ctl = + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + u8 *coeff_ram; + struct mutex *coeff_ram_lock; + + if (strstr(kcontrol->id.name, "DAC")) { + coeff_ram = tscs454->dac_ram.cache; + coeff_ram_lock = &tscs454->dac_ram.lock; + } else if (strstr(kcontrol->id.name, "Speaker")) { + coeff_ram = tscs454->spk_ram.cache; + coeff_ram_lock = &tscs454->spk_ram.lock; + } else if (strstr(kcontrol->id.name, "Sub")) { + coeff_ram = tscs454->sub_ram.cache; + coeff_ram_lock = &tscs454->sub_ram.lock; + } else { + return -EINVAL; + } + + mutex_lock(coeff_ram_lock); + + memcpy(ucontrol->value.bytes.data, + &coeff_ram[ctl->addr * COEFF_SIZE], params->max); + + mutex_unlock(coeff_ram_lock); + + return 0; +} + +#define DACCRSTAT_MAX_TRYS 10 +static int write_coeff_ram(struct snd_soc_component *component, u8 *coeff_ram, + unsigned int r_stat, unsigned int r_addr, unsigned int r_wr, + unsigned int coeff_addr, unsigned int coeff_cnt) +{ + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + unsigned int val; + int cnt; + int trys; + int ret; + + for (cnt = 0; cnt < coeff_cnt; cnt++, coeff_addr++) { + + for (trys = 0; trys < DACCRSTAT_MAX_TRYS; trys++) { + ret = snd_soc_component_read(component, r_stat, &val); + if (ret < 0) { + dev_err(component->dev, + "Failed to read stat (%d)\n", ret); + return ret; + } + if (!val) + break; + } + + if (trys == DACCRSTAT_MAX_TRYS) { + ret = -EIO; + dev_err(component->dev, + "Coefficient write error (%d)\n", ret); + return ret; + } + + ret = regmap_write(tscs454->regmap, r_addr, coeff_addr); + if (ret < 0) { + dev_err(component->dev, + "Failed to write dac ram address (%d)\n", ret); + return ret; + } + + ret = regmap_bulk_write(tscs454->regmap, r_wr, + &coeff_ram[coeff_addr * COEFF_SIZE], + COEFF_SIZE); + if (ret < 0) { + dev_err(component->dev, + "Failed to write dac ram (%d)\n", ret); + return ret; + } + } + + return 0; +} + +static int coeff_ram_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + struct coeff_ram_ctl *ctl = + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + unsigned int coeff_cnt = params->max / COEFF_SIZE; + u8 *coeff_ram; + struct mutex *coeff_ram_lock; + bool *coeff_ram_synced; + unsigned int r_stat; + unsigned int r_addr; + unsigned int r_wr; + unsigned int val; + int ret; + + if (strstr(kcontrol->id.name, "DAC")) { + coeff_ram = tscs454->dac_ram.cache; + coeff_ram_lock = &tscs454->dac_ram.lock; + coeff_ram_synced = &tscs454->dac_ram.synced; + r_stat = R_DACCRS; + r_addr = R_DACCRADD; + r_wr = R_DACCRWDL; + } else if (strstr(kcontrol->id.name, "Speaker")) { + coeff_ram = tscs454->spk_ram.cache; + coeff_ram_lock = &tscs454->spk_ram.lock; + coeff_ram_synced = &tscs454->spk_ram.synced; + r_stat = R_SPKCRS; + r_addr = R_SPKCRADD; + r_wr = R_SPKCRWDL; + } else if (strstr(kcontrol->id.name, "Sub")) { + coeff_ram = tscs454->sub_ram.cache; + coeff_ram_lock = &tscs454->sub_ram.lock; + coeff_ram_synced = &tscs454->sub_ram.synced; + r_stat = R_SUBCRS; + r_addr = R_SUBCRADD; + r_wr = R_SUBCRWDL; + } else { + return -EINVAL; + } + + mutex_lock(coeff_ram_lock); + + *coeff_ram_synced = false; + + memcpy(&coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + + mutex_lock(&tscs454->pll1.lock); + mutex_lock(&tscs454->pll2.lock); + + ret = snd_soc_component_read(component, R_PLLSTAT, &val); + if (ret < 0) { + dev_err(component->dev, "Failed to read PLL status (%d)\n", + ret); + goto exit; + } + if (val) { /* PLLs locked */ + ret = write_coeff_ram(component, coeff_ram, + r_stat, r_addr, r_wr, + ctl->addr, coeff_cnt); + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); + goto exit; + } + *coeff_ram_synced = true; + } + + ret = 0; +exit: + mutex_unlock(&tscs454->pll2.lock); + mutex_unlock(&tscs454->pll1.lock); + mutex_unlock(coeff_ram_lock); + + return ret; +} + +static inline int coeff_ram_sync(struct snd_soc_component *component, + struct tscs454 *tscs454) +{ + int ret; + + mutex_lock(&tscs454->dac_ram.lock); + if (!tscs454->dac_ram.synced) { + ret = write_coeff_ram(component, tscs454->dac_ram.cache, + R_DACCRS, R_DACCRADD, R_DACCRWDL, + 0x00, COEFF_RAM_COEFF_COUNT); + if (ret < 0) { + mutex_unlock(&tscs454->dac_ram.lock); + return ret; + } + } + mutex_unlock(&tscs454->dac_ram.lock); + + mutex_lock(&tscs454->spk_ram.lock); + if (!tscs454->spk_ram.synced) { + ret = write_coeff_ram(component, tscs454->spk_ram.cache, + R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, + 0x00, COEFF_RAM_COEFF_COUNT); + if (ret < 0) { + mutex_unlock(&tscs454->spk_ram.lock); + return ret; + } + } + mutex_unlock(&tscs454->spk_ram.lock); + + mutex_lock(&tscs454->sub_ram.lock); + if (!tscs454->sub_ram.synced) { + ret = write_coeff_ram(component, tscs454->sub_ram.cache, + R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, + 0x00, COEFF_RAM_COEFF_COUNT); + if (ret < 0) { + mutex_unlock(&tscs454->sub_ram.lock); + return ret; + } + } + mutex_unlock(&tscs454->sub_ram.lock); + + return 0; +} + +#define PLL_REG_SETTINGS_COUNT 11 +struct pll_ctl { + int freq_in; + struct reg_setting settings[PLL_REG_SETTINGS_COUNT]; +}; + +#define PLL_CTL(f, t, c1, r1, o1, f1l, f1h, c2, r2, o2, f2l, f2h) \ + { \ + .freq_in = f, \ + .settings = { \ + {R_PLL1CTL, c1}, \ + {R_PLL1RDIV, r1}, \ + {R_PLL1ODIV, o1}, \ + {R_PLL1FDIVL, f1l}, \ + {R_PLL1FDIVH, f1h}, \ + {R_PLL2CTL, c2}, \ + {R_PLL2RDIV, r2}, \ + {R_PLL2ODIV, o2}, \ + {R_PLL2FDIVL, f2l}, \ + {R_PLL2FDIVH, f2h}, \ + {R_TIMEBASE, t}, \ + }, \ + } + +static const struct pll_ctl pll_ctls[] = { + PLL_CTL(1411200, 0x05, + 0xB9, 0x07, 0x02, 0xC3, 0x04, + 0x5A, 0x02, 0x03, 0xE0, 0x01), + PLL_CTL(1536000, 0x05, + 0x5A, 0x02, 0x03, 0xE0, 0x01, + 0x5A, 0x02, 0x03, 0xB9, 0x01), + PLL_CTL(2822400, 0x0A, + 0x63, 0x07, 0x04, 0xC3, 0x04, + 0x62, 0x07, 0x03, 0x48, 0x03), + PLL_CTL(3072000, 0x0B, + 0x62, 0x07, 0x03, 0x48, 0x03, + 0x5A, 0x04, 0x03, 0xB9, 0x01), + PLL_CTL(5644800, 0x15, + 0x63, 0x0E, 0x04, 0xC3, 0x04, + 0x5A, 0x08, 0x03, 0xE0, 0x01), + PLL_CTL(6144000, 0x17, + 0x5A, 0x08, 0x03, 0xE0, 0x01, + 0x5A, 0x08, 0x03, 0xB9, 0x01), + PLL_CTL(12000000, 0x2E, + 0x5B, 0x19, 0x03, 0x00, 0x03, + 0x6A, 0x19, 0x05, 0x98, 0x04), + PLL_CTL(19200000, 0x4A, + 0x53, 0x14, 0x03, 0x80, 0x01, + 0x5A, 0x19, 0x03, 0xB9, 0x01), + PLL_CTL(22000000, 0x55, + 0x6A, 0x37, 0x05, 0x00, 0x06, + 0x62, 0x26, 0x03, 0x49, 0x02), + PLL_CTL(22579200, 0x57, + 0x62, 0x31, 0x03, 0x20, 0x03, + 0x53, 0x1D, 0x03, 0xB3, 0x01), + PLL_CTL(24000000, 0x5D, + 0x53, 0x19, 0x03, 0x80, 0x01, + 0x5B, 0x19, 0x05, 0x4C, 0x02), + PLL_CTL(24576000, 0x5F, + 0x53, 0x1D, 0x03, 0xB3, 0x01, + 0x62, 0x40, 0x03, 0x72, 0x03), + PLL_CTL(27000000, 0x68, + 0x62, 0x4B, 0x03, 0x00, 0x04, + 0x6A, 0x7D, 0x03, 0x20, 0x06), + PLL_CTL(36000000, 0x8C, + 0x5B, 0x4B, 0x03, 0x00, 0x03, + 0x6A, 0x7D, 0x03, 0x98, 0x04), + PLL_CTL(11289600, 0x2B, + 0x6A, 0x31, 0x03, 0x40, 0x06, + 0x5A, 0x12, 0x03, 0x1C, 0x02), + PLL_CTL(26000000, 0x65, + 0x63, 0x41, 0x05, 0x00, 0x06, + 0x5A, 0x26, 0x03, 0xEF, 0x01), + PLL_CTL(12288000, 0x2F, + 0x5A, 0x12, 0x03, 0x1C, 0x02, + 0x62, 0x20, 0x03, 0x72, 0x03), + PLL_CTL(40000000, 0x9B, + 0xA2, 0x7D, 0x03, 0x80, 0x04, + 0x63, 0x7D, 0x05, 0xE4, 0x06), + PLL_CTL(512000, 0x01, + 0x62, 0x01, 0x03, 0xD0, 0x02, + 0x5B, 0x01, 0x04, 0x72, 0x03), + PLL_CTL(705600, 0x02, + 0x62, 0x02, 0x03, 0x15, 0x04, + 0x62, 0x01, 0x04, 0x80, 0x02), + PLL_CTL(1024000, 0x03, + 0x62, 0x02, 0x03, 0xD0, 0x02, + 0x5B, 0x02, 0x04, 0x72, 0x03), + PLL_CTL(2048000, 0x07, + 0x62, 0x04, 0x03, 0xD0, 0x02, + 0x5B, 0x04, 0x04, 0x72, 0x03), + PLL_CTL(2400000, 0x08, + 0x62, 0x05, 0x03, 0x00, 0x03, + 0x63, 0x05, 0x05, 0x98, 0x04), +}; + +static inline const struct pll_ctl *get_pll_ctl(unsigned long freq_in) +{ + int i; + struct pll_ctl const *pll_ctl = NULL; + + for (i = 0; i < ARRAY_SIZE(pll_ctls); ++i) + if (pll_ctls[i].freq_in == freq_in) { + pll_ctl = &pll_ctls[i]; + break; + } + + return pll_ctl; +} + +enum { + PLL_INPUT_XTAL = 0, + PLL_INPUT_MCLK1, + PLL_INPUT_MCLK2, + PLL_INPUT_BCLK, +}; + +static int set_sysclk(struct snd_soc_component *component) +{ + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + struct pll_ctl const *pll_ctl; + unsigned long freq; + int i; + int ret; + + if (tscs454->sysclk_src_id < PLL_INPUT_BCLK) + freq = clk_get_rate(tscs454->sysclk); + else + freq = tscs454->bclk_freq; + pll_ctl = get_pll_ctl(freq); + if (!pll_ctl) { + ret = -EINVAL; + dev_err(component->dev, + "Invalid PLL input %lu (%d)\n", freq, ret); + return ret; + } + + for (i = 0; i < PLL_REG_SETTINGS_COUNT; ++i) { + ret = snd_soc_component_write(component, + pll_ctl->settings[i].addr, + pll_ctl->settings[i].val); + if (ret < 0) { + dev_err(component->dev, + "Failed to set pll setting (%d)\n", + ret); + return ret; + } + } + + return 0; +} + +static inline void reserve_pll(struct pll *pll) +{ + mutex_lock(&pll->lock); + pll->users++; + mutex_unlock(&pll->lock); +} + +static inline void free_pll(struct pll *pll) +{ + mutex_lock(&pll->lock); + pll->users--; + mutex_unlock(&pll->lock); +} + +static int pll_connected(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(source->dapm); + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + int users; + + if (strstr(source->name, "PLL 1")) { + mutex_lock(&tscs454->pll1.lock); + users = tscs454->pll1.users; + mutex_unlock(&tscs454->pll1.lock); + dev_dbg(component->dev, "%s(): PLL 1 users = %d\n", __func__, + users); + } else { + mutex_lock(&tscs454->pll2.lock); + users = tscs454->pll2.users; + mutex_unlock(&tscs454->pll2.lock); + dev_dbg(component->dev, "%s(): PLL 2 users = %d\n", __func__, + users); + } + + return users; +} + +/* + * PLL must be enabled after power up and must be disabled before power down + * for proper clock switching. + */ +static int pll_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + bool enable; + bool pll1; + unsigned int msk; + unsigned int val; + int ret; + + if (strstr(w->name, "PLL 1")) + pll1 = true; + else + pll1 = false; + + msk = pll1 ? FM_PLLCTL_PLL1CLKEN : FM_PLLCTL_PLL2CLKEN; + + if (event == SND_SOC_DAPM_POST_PMU) + enable = true; + else + enable = false; + + if (enable) + val = pll1 ? FV_PLL1CLKEN_ENABLE : FV_PLL2CLKEN_ENABLE; + else + val = pll1 ? FV_PLL1CLKEN_DISABLE : FV_PLL2CLKEN_DISABLE; + + ret = snd_soc_component_update_bits(component, R_PLLCTL, msk, val); + if (ret < 0) { + dev_err(component->dev, "Failed to %s PLL %d (%d)\n", + enable ? "enable" : "disable", + pll1 ? 1 : 2, + ret); + return ret; + } + + if (enable) { + msleep(20); // Wait for lock + ret = coeff_ram_sync(component, tscs454); + if (ret < 0) { + dev_err(component->dev, + "Failed to sync coeff ram (%d)\n", ret); + return ret; + } + } + + return 0; +} + +static inline int aif_set_master(struct snd_soc_component *component, + unsigned int aif_id, bool master) +{ + unsigned int reg; + unsigned int mask; + unsigned int val; + int ret; + + switch (aif_id) { + case TSCS454_DAI1_ID: + reg = R_I2SP1CTL; + break; + case TSCS454_DAI2_ID: + reg = R_I2SP2CTL; + break; + case TSCS454_DAI3_ID: + reg = R_I2SP3CTL; + break; + default: + ret = -ENODEV; + dev_err(component->dev, "Unknown DAI %d (%d)\n", aif_id, ret); + return ret; + } + mask = FM_I2SPCTL_PORTMS; + val = master ? FV_PORTMS_MASTER : FV_PORTMS_SLAVE; + + ret = snd_soc_component_update_bits(component, reg, mask, val); + if (ret < 0) { + dev_err(component->dev, "Failed to set DAI %d to %s (%d)\n", + aif_id, master ? "master" : "slave", ret); + return ret; + } + + return 0; +} + +static inline +int aif_prepare(struct snd_soc_component *component, struct aif *aif) +{ + int ret; + + ret = aif_set_master(component, aif->id, aif->master); + if (ret < 0) + return ret; + + return 0; +} + +static inline int aif_free(struct snd_soc_component *component, + struct aif *aif, bool playback) +{ + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + + mutex_lock(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d\n", __func__, aif->id); + + set_aif_status_inactive(&tscs454->aifs_status, aif->id, playback); + + dev_dbg(component->dev, "Set aif %d inactive. Streams status is 0x%x\n", + aif->id, tscs454->aifs_status.streams); + + if (!aif_active(&tscs454->aifs_status, aif->id)) { + /* Do config in slave mode */ + aif_set_master(component, aif->id, false); + dev_dbg(component->dev, "Freeing pll %d from aif %d\n", + aif->pll->id, aif->id); + free_pll(aif->pll); + } + + if (!aifs_active(&tscs454->aifs_status)) { + dev_dbg(component->dev, "Freeing pll %d from ir\n", + tscs454->internal_rate.pll->id); + free_pll(tscs454->internal_rate.pll); + } + + mutex_unlock(&tscs454->aifs_status_lock); + + return 0; +} + +/* R_PLLCTL PG 0 ADDR 0x15 */ +static char const * const bclk_sel_txt[] = { + "BCLK 1", "BCLK 2", "BCLK 3"}; + +static struct soc_enum const bclk_sel_enum = + SOC_ENUM_SINGLE(R_PLLCTL, FB_PLLCTL_BCLKSEL, + ARRAY_SIZE(bclk_sel_txt), bclk_sel_txt); + +/* R_ISRC PG 0 ADDR 0x16 */ +static char const * const isrc_br_txt[] = { + "44.1kHz", "48kHz"}; + +static struct soc_enum const isrc_br_enum = + SOC_ENUM_SINGLE(R_ISRC, FB_ISRC_IBR, + ARRAY_SIZE(isrc_br_txt), isrc_br_txt); + +static char const * const isrc_bm_txt[] = { + "0.25x", "0.5x", "1.0x", "2.0x"}; + +static struct soc_enum const isrc_bm_enum = + SOC_ENUM_SINGLE(R_ISRC, FB_ISRC_IBM, + ARRAY_SIZE(isrc_bm_txt), isrc_bm_txt); + +/* R_SCLKCTL PG 0 ADDR 0x18 */ +static char const * const modular_rate_txt[] = { + "Reserved", "Half", "Full", "Auto",}; + +static struct soc_enum const adc_modular_rate_enum = + SOC_ENUM_SINGLE(R_SCLKCTL, FB_SCLKCTL_ASDM, + ARRAY_SIZE(modular_rate_txt), modular_rate_txt); + +static struct soc_enum const dac_modular_rate_enum = + SOC_ENUM_SINGLE(R_SCLKCTL, FB_SCLKCTL_DSDM, + ARRAY_SIZE(modular_rate_txt), modular_rate_txt); + +/* R_I2SIDCTL PG 0 ADDR 0x38 */ +static char const * const data_ctrl_txt[] = { + "L/R", "L/L", "R/R", "R/L"}; + +static struct soc_enum const data_in_ctrl_enums[] = { + SOC_ENUM_SINGLE(R_I2SIDCTL, FB_I2SIDCTL_I2SI1DCTL, + ARRAY_SIZE(data_ctrl_txt), data_ctrl_txt), + SOC_ENUM_SINGLE(R_I2SIDCTL, FB_I2SIDCTL_I2SI2DCTL, + ARRAY_SIZE(data_ctrl_txt), data_ctrl_txt), + SOC_ENUM_SINGLE(R_I2SIDCTL, FB_I2SIDCTL_I2SI3DCTL, + ARRAY_SIZE(data_ctrl_txt), data_ctrl_txt), +}; + +/* R_I2SODCTL PG 0 ADDR 0x39 */ +static struct soc_enum const data_out_ctrl_enums[] = { + SOC_ENUM_SINGLE(R_I2SODCTL, FB_I2SODCTL_I2SO1DCTL, + ARRAY_SIZE(data_ctrl_txt), data_ctrl_txt), + SOC_ENUM_SINGLE(R_I2SODCTL, FB_I2SODCTL_I2SO2DCTL, + ARRAY_SIZE(data_ctrl_txt), data_ctrl_txt), + SOC_ENUM_SINGLE(R_I2SODCTL, FB_I2SODCTL_I2SO3DCTL, + ARRAY_SIZE(data_ctrl_txt), data_ctrl_txt), +}; + +/* R_AUDIOMUX1 PG 0 ADDR 0x3A */ +static char const * const asrc_mux_txt[] = { + "None", "DAI 1", "DAI 2", "DAI 3"}; + +static struct soc_enum const asrc_in_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX1, FB_AUDIOMUX1_ASRCIMUX, + ARRAY_SIZE(asrc_mux_txt), asrc_mux_txt); + +static char const * const dai_mux_txt[] = { + "CH 0_1", "CH 2_3", "CH 4_5", "ADC/DMic 1", + "DMic 2", "ClassD", "DAC", "Sub"}; + +static struct soc_enum const dai2_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX1, FB_AUDIOMUX1_I2S2MUX, + ARRAY_SIZE(dai_mux_txt), dai_mux_txt); + +static struct snd_kcontrol_new const dai2_mux_dapm_enum = + SOC_DAPM_ENUM("DAI 2 Mux", dai2_mux_enum); + +static struct soc_enum const dai1_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX1, FB_AUDIOMUX1_I2S1MUX, + ARRAY_SIZE(dai_mux_txt), dai_mux_txt); + +static struct snd_kcontrol_new const dai1_mux_dapm_enum = + SOC_DAPM_ENUM("DAI 1 Mux", dai1_mux_enum); + +/* R_AUDIOMUX2 PG 0 ADDR 0x3B */ +static struct soc_enum const asrc_out_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX2, FB_AUDIOMUX2_ASRCOMUX, + ARRAY_SIZE(asrc_mux_txt), asrc_mux_txt); + +static struct soc_enum const dac_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX2, FB_AUDIOMUX2_DACMUX, + ARRAY_SIZE(dai_mux_txt), dai_mux_txt); + +static struct snd_kcontrol_new const dac_mux_dapm_enum = + SOC_DAPM_ENUM("DAC Mux", dac_mux_enum); + +static struct soc_enum const dai3_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX2, FB_AUDIOMUX2_I2S3MUX, + ARRAY_SIZE(dai_mux_txt), dai_mux_txt); + +static struct snd_kcontrol_new const dai3_mux_dapm_enum = + SOC_DAPM_ENUM("DAI 3 Mux", dai3_mux_enum); + +/* R_AUDIOMUX3 PG 0 ADDR 0x3C */ +static char const * const sub_mux_txt[] = { + "CH 0", "CH 1", "CH 0 + 1", + "CH 2", "CH 3", "CH 2 + 3", + "CH 4", "CH 5", "CH 4 + 5", + "ADC/DMic 1 Left", "ADC/DMic 1 Right", + "ADC/DMic 1 Left Plus Right", + "DMic 2 Left", "DMic 2 Right", "DMic 2 Left Plus Right", + "ClassD Left", "ClassD Right", "ClassD Left Plus Right"}; + +static struct soc_enum const sub_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX3, FB_AUDIOMUX3_SUBMUX, + ARRAY_SIZE(sub_mux_txt), sub_mux_txt); + +static struct snd_kcontrol_new const sub_mux_dapm_enum = + SOC_DAPM_ENUM("Sub Mux", sub_mux_enum); + +static struct soc_enum const classd_mux_enum = + SOC_ENUM_SINGLE(R_AUDIOMUX3, FB_AUDIOMUX3_CLSSDMUX, + ARRAY_SIZE(dai_mux_txt), dai_mux_txt); + +static struct snd_kcontrol_new const classd_mux_dapm_enum = + SOC_DAPM_ENUM("ClassD Mux", classd_mux_enum); + +/* R_HSDCTL1 PG 1 ADDR 0x01 */ +static char const * const jack_type_txt[] = { + "3 Terminal", "4 Terminal"}; + +static struct soc_enum const hp_jack_type_enum = + SOC_ENUM_SINGLE(R_HSDCTL1, FB_HSDCTL1_HPJKTYPE, + ARRAY_SIZE(jack_type_txt), jack_type_txt); + +static char const * const hs_det_pol_txt[] = { + "Rising", "Falling"}; + +static struct soc_enum const hs_det_pol_enum = + SOC_ENUM_SINGLE(R_HSDCTL1, FB_HSDCTL1_HSDETPOL, + ARRAY_SIZE(hs_det_pol_txt), hs_det_pol_txt); + +/* R_HSDCTL1 PG 1 ADDR 0x02 */ +static char const * const hs_mic_bias_force_txt[] = { + "Off", "Ring", "Sleeve"}; + +static struct soc_enum const hs_mic_bias_force_enum = + SOC_ENUM_SINGLE(R_HSDCTL2, FB_HSDCTL2_FMICBIAS1, + ARRAY_SIZE(hs_mic_bias_force_txt), + hs_mic_bias_force_txt); + +static char const * const plug_type_txt[] = { + "OMTP", "CTIA", "Reserved", "Headphone"}; + +static struct soc_enum const plug_type_force_enum = + SOC_ENUM_SINGLE(R_HSDCTL2, FB_HSDCTL2_FPLUGTYPE, + ARRAY_SIZE(plug_type_txt), plug_type_txt); + + +/* R_CH0AIC PG 1 ADDR 0x06 */ +static char const * const in_bst_mux_txt[] = { + "Input 1", "Input 2", "Input 3", "D2S"}; + +static struct soc_enum const in_bst_mux_ch0_enum = + SOC_ENUM_SINGLE(R_CH0AIC, FB_CH0AIC_INSELL, + ARRAY_SIZE(in_bst_mux_txt), + in_bst_mux_txt); +static struct snd_kcontrol_new const in_bst_mux_ch0_dapm_enum = + SOC_DAPM_ENUM("Input Boost Channel 0 Enum", + in_bst_mux_ch0_enum); + +static DECLARE_TLV_DB_SCALE(in_bst_vol_tlv_arr, 0, 1000, 0); + +static char const * const adc_mux_txt[] = { + "Input 1 Boost Bypass", "Input 2 Boost Bypass", + "Input 3 Boost Bypass", "Input Boost"}; + +static struct soc_enum const adc_mux_ch0_enum = + SOC_ENUM_SINGLE(R_CH0AIC, FB_CH0AIC_LADCIN, + ARRAY_SIZE(adc_mux_txt), adc_mux_txt); +static struct snd_kcontrol_new const adc_mux_ch0_dapm_enum = + SOC_DAPM_ENUM("ADC Channel 0 Enum", adc_mux_ch0_enum); + +static char const * const in_proc_mux_txt[] = { + "ADC", "DMic"}; + +static struct soc_enum const in_proc_ch0_enum = + SOC_ENUM_SINGLE(R_CH0AIC, FB_CH0AIC_IPCH0S, + ARRAY_SIZE(in_proc_mux_txt), in_proc_mux_txt); +static struct snd_kcontrol_new const in_proc_mux_ch0_dapm_enum = + SOC_DAPM_ENUM("Input Processor Channel 0 Enum", + in_proc_ch0_enum); + +/* R_CH1AIC PG 1 ADDR 0x07 */ +static struct soc_enum const in_bst_mux_ch1_enum = + SOC_ENUM_SINGLE(R_CH1AIC, FB_CH1AIC_INSELR, + ARRAY_SIZE(in_bst_mux_txt), + in_bst_mux_txt); +static struct snd_kcontrol_new const in_bst_mux_ch1_dapm_enum = + SOC_DAPM_ENUM("Input Boost Channel 1 Enum", + in_bst_mux_ch1_enum); + +static struct soc_enum const adc_mux_ch1_enum = + SOC_ENUM_SINGLE(R_CH1AIC, FB_CH1AIC_RADCIN, + ARRAY_SIZE(adc_mux_txt), adc_mux_txt); +static struct snd_kcontrol_new const adc_mux_ch1_dapm_enum = + SOC_DAPM_ENUM("ADC Channel 1 Enum", adc_mux_ch1_enum); + +static struct soc_enum const in_proc_ch1_enum = + SOC_ENUM_SINGLE(R_CH1AIC, FB_CH1AIC_IPCH1S, + ARRAY_SIZE(in_proc_mux_txt), in_proc_mux_txt); +static struct snd_kcontrol_new const in_proc_mux_ch1_dapm_enum = + SOC_DAPM_ENUM("Input Processor Channel 1 Enum", + in_proc_ch1_enum); + +/* R_ICTL0 PG 1 ADDR 0x0A */ +static char const * const pol_txt[] = { + "Normal", "Invert"}; + +static struct soc_enum const in_pol_ch1_enum = + SOC_ENUM_SINGLE(R_ICTL0, FB_ICTL0_IN0POL, + ARRAY_SIZE(pol_txt), pol_txt); + +static struct soc_enum const in_pol_ch0_enum = + SOC_ENUM_SINGLE(R_ICTL0, FB_ICTL0_IN1POL, + ARRAY_SIZE(pol_txt), pol_txt); + +static char const * const in_proc_ch_sel_txt[] = { + "Normal", "Mono Mix to Channel 0", + "Mono Mix to Channel 1", "Add"}; + +static struct soc_enum const in_proc_ch01_sel_enum = + SOC_ENUM_SINGLE(R_ICTL0, FB_ICTL0_INPCH10SEL, + ARRAY_SIZE(in_proc_ch_sel_txt), + in_proc_ch_sel_txt); + +/* R_ICTL1 PG 1 ADDR 0x0B */ +static struct soc_enum const in_pol_ch3_enum = + SOC_ENUM_SINGLE(R_ICTL1, FB_ICTL1_IN2POL, + ARRAY_SIZE(pol_txt), pol_txt); + +static struct soc_enum const in_pol_ch2_enum = + SOC_ENUM_SINGLE(R_ICTL1, FB_ICTL1_IN3POL, + ARRAY_SIZE(pol_txt), pol_txt); + +static struct soc_enum const in_proc_ch23_sel_enum = + SOC_ENUM_SINGLE(R_ICTL1, FB_ICTL1_INPCH32SEL, + ARRAY_SIZE(in_proc_ch_sel_txt), + in_proc_ch_sel_txt); + +/* R_MICBIAS PG 1 ADDR 0x0C */ +static char const * const mic_bias_txt[] = { + "2.5V", "2.1V", "1.8V", "Vdd"}; + +static struct soc_enum const mic_bias_2_enum = + SOC_ENUM_SINGLE(R_MICBIAS, FB_MICBIAS_MICBOV2, + ARRAY_SIZE(mic_bias_txt), mic_bias_txt); + +static struct soc_enum const mic_bias_1_enum = + SOC_ENUM_SINGLE(R_MICBIAS, FB_MICBIAS_MICBOV1, + ARRAY_SIZE(mic_bias_txt), mic_bias_txt); + +/* R_PGACTL0 PG 1 ADDR 0x0D */ +/* R_PGACTL1 PG 1 ADDR 0x0E */ +/* R_PGACTL2 PG 1 ADDR 0x0F */ +/* R_PGACTL3 PG 1 ADDR 0x10 */ +static DECLARE_TLV_DB_SCALE(in_pga_vol_tlv_arr, -1725, 75, 0); + +/* R_ICH0VOL PG1 ADDR 0x12 */ +/* R_ICH1VOL PG1 ADDR 0x13 */ +/* R_ICH2VOL PG1 ADDR 0x14 */ +/* R_ICH3VOL PG1 ADDR 0x15 */ +static DECLARE_TLV_DB_MINMAX(in_vol_tlv_arr, -7125, 2400); + +/* R_ASRCILVOL PG1 ADDR 0x16 */ +/* R_ASRCIRVOL PG1 ADDR 0x17 */ +/* R_ASRCOLVOL PG1 ADDR 0x18 */ +/* R_ASRCORVOL PG1 ADDR 0x19 */ +static DECLARE_TLV_DB_MINMAX(asrc_vol_tlv_arr, -9562, 600); + +/* R_ALCCTL0 PG1 ADDR 0x1D */ +static char const * const alc_mode_txt[] = { + "ALC", "Limiter"}; + +static struct soc_enum const alc_mode_enum = + SOC_ENUM_SINGLE(R_ALCCTL0, FB_ALCCTL0_ALCMODE, + ARRAY_SIZE(alc_mode_txt), alc_mode_txt); + +static char const * const alc_ref_text[] = { + "Channel 0", "Channel 1", "Channel 2", "Channel 3", "Peak"}; + +static struct soc_enum const alc_ref_enum = + SOC_ENUM_SINGLE(R_ALCCTL0, FB_ALCCTL0_ALCREF, + ARRAY_SIZE(alc_ref_text), alc_ref_text); + +/* R_ALCCTL1 PG 1 ADDR 0x1E */ +static DECLARE_TLV_DB_SCALE(alc_max_gain_tlv_arr, -1200, 600, 0); +static DECLARE_TLV_DB_SCALE(alc_target_tlv_arr, -2850, 150, 0); + +/* R_ALCCTL2 PG 1 ADDR 0x1F */ +static DECLARE_TLV_DB_SCALE(alc_min_gain_tlv_arr, -1725, 600, 0); + +/* R_NGATE PG 1 ADDR 0x21 */ +static DECLARE_TLV_DB_SCALE(ngth_tlv_arr, -7650, 150, 0); + +static char const * const ngate_type_txt[] = { + "PGA Constant", "ADC Mute"}; + +static struct soc_enum const ngate_type_enum = + SOC_ENUM_SINGLE(R_NGATE, FB_NGATE_NGG, + ARRAY_SIZE(ngate_type_txt), ngate_type_txt); + +/* R_DMICCTL PG 1 ADDR 0x22 */ +static char const * const dmic_mono_sel_txt[] = { + "Stereo", "Mono"}; + +static struct soc_enum const dmic_mono_sel_enum = + SOC_ENUM_SINGLE(R_DMICCTL, FB_DMICCTL_DMONO, + ARRAY_SIZE(dmic_mono_sel_txt), dmic_mono_sel_txt); + +/* R_DACCTL PG 2 ADDR 0x01 */ +static struct soc_enum const dac_pol_r_enum = + SOC_ENUM_SINGLE(R_DACCTL, FB_DACCTL_DACPOLR, + ARRAY_SIZE(pol_txt), pol_txt); + +static struct soc_enum const dac_pol_l_enum = + SOC_ENUM_SINGLE(R_DACCTL, FB_DACCTL_DACPOLL, + ARRAY_SIZE(pol_txt), pol_txt); + +static char const * const dac_dith_txt[] = { + "Half", "Full", "Disabled", "Static"}; + +static struct soc_enum const dac_dith_enum = + SOC_ENUM_SINGLE(R_DACCTL, FB_DACCTL_DACDITH, + ARRAY_SIZE(dac_dith_txt), dac_dith_txt); + +/* R_SPKCTL PG 2 ADDR 0x02 */ +static struct soc_enum const spk_pol_r_enum = + SOC_ENUM_SINGLE(R_SPKCTL, FB_SPKCTL_SPKPOLR, + ARRAY_SIZE(pol_txt), pol_txt); + +static struct soc_enum const spk_pol_l_enum = + SOC_ENUM_SINGLE(R_SPKCTL, FB_SPKCTL_SPKPOLL, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_SUBCTL PG 2 ADDR 0x03 */ +static struct soc_enum const sub_pol_enum = + SOC_ENUM_SINGLE(R_SUBCTL, FB_SUBCTL_SUBPOL, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_MVOLL PG 2 ADDR 0x08 */ +/* R_MVOLR PG 2 ADDR 0x09 */ +static DECLARE_TLV_DB_MINMAX(mvol_tlv_arr, -9562, 0); + +/* R_HPVOLL PG 2 ADDR 0x0A */ +/* R_HPVOLR PG 2 ADDR 0x0B */ +static DECLARE_TLV_DB_SCALE(hp_vol_tlv_arr, -8850, 75, 0); + +/* R_SPKVOLL PG 2 ADDR 0x0C */ +/* R_SPKVOLR PG 2 ADDR 0x0D */ +static DECLARE_TLV_DB_SCALE(spk_vol_tlv_arr, -7725, 75, 0); + +/* R_SPKEQFILT PG 3 ADDR 0x01 */ +static char const * const eq_txt[] = { + "Pre Scale", + "Pre Scale + EQ Band 0", + "Pre Scale + EQ Band 0 - 1", + "Pre Scale + EQ Band 0 - 2", + "Pre Scale + EQ Band 0 - 3", + "Pre Scale + EQ Band 0 - 4", + "Pre Scale + EQ Band 0 - 5", +}; + +static struct soc_enum const spk_eq_enums[] = { + SOC_ENUM_SINGLE(R_SPKEQFILT, FB_SPKEQFILT_EQ2BE, + ARRAY_SIZE(eq_txt), eq_txt), + SOC_ENUM_SINGLE(R_SPKEQFILT, FB_SPKEQFILT_EQ1BE, + ARRAY_SIZE(eq_txt), eq_txt), +}; + +/* R_SPKMBCCTL PG 3 ADDR 0x0B */ +static char const * const lvl_mode_txt[] = { + "Average", "Peak"}; + +static struct soc_enum const spk_mbc3_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_SPKMBCCTL, FB_SPKMBCCTL_LVLMODE3, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static char const * const win_sel_txt[] = { + "512", "64"}; + +static struct soc_enum const spk_mbc3_win_sel_enum = + SOC_ENUM_SINGLE(R_SPKMBCCTL, FB_SPKMBCCTL_WINSEL3, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +static struct soc_enum const spk_mbc2_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_SPKMBCCTL, FB_SPKMBCCTL_LVLMODE2, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const spk_mbc2_win_sel_enum = + SOC_ENUM_SINGLE(R_SPKMBCCTL, FB_SPKMBCCTL_WINSEL2, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +static struct soc_enum const spk_mbc1_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_SPKMBCCTL, FB_SPKMBCCTL_LVLMODE1, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const spk_mbc1_win_sel_enum = + SOC_ENUM_SINGLE(R_SPKMBCCTL, FB_SPKMBCCTL_WINSEL1, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +/* R_SPKMBCMUG1 PG 3 ADDR 0x0C */ +static struct soc_enum const spk_mbc1_phase_pol_enum = + SOC_ENUM_SINGLE(R_SPKMBCMUG1, FB_SPKMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +static DECLARE_TLV_DB_MINMAX(mbc_mug_tlv_arr, -4650, 0); + +/* R_SPKMBCTHR1 PG 3 ADDR 0x0D */ +static DECLARE_TLV_DB_MINMAX(thr_tlv_arr, -9562, 0); + +/* R_SPKMBCRAT1 PG 3 ADDR 0x0E */ +static char const * const comp_rat_txt[] = { + "Reserved", "1.5:1", "2:1", "3:1", "4:1", "5:1", "6:1", + "7:1", "8:1", "9:1", "10:1", "11:1", "12:1", "13:1", "14:1", + "15:1", "16:1", "17:1", "18:1", "19:1", "20:1"}; + +static struct soc_enum const spk_mbc1_comp_rat_enum = + SOC_ENUM_SINGLE(R_SPKMBCRAT1, FB_SPKMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SPKMBCMUG2 PG 3 ADDR 0x13 */ +static struct soc_enum const spk_mbc2_phase_pol_enum = + SOC_ENUM_SINGLE(R_SPKMBCMUG2, FB_SPKMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_SPKMBCRAT2 PG 3 ADDR 0x15 */ +static struct soc_enum const spk_mbc2_comp_rat_enum = + SOC_ENUM_SINGLE(R_SPKMBCRAT2, FB_SPKMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SPKMBCMUG3 PG 3 ADDR 0x1A */ +static struct soc_enum const spk_mbc3_phase_pol_enum = + SOC_ENUM_SINGLE(R_SPKMBCMUG3, FB_SPKMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_SPKMBCRAT3 PG 3 ADDR 0x1C */ +static struct soc_enum const spk_mbc3_comp_rat_enum = + SOC_ENUM_SINGLE(R_SPKMBCRAT3, FB_SPKMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SPKCLECTL PG 3 ADDR 0x21 */ +static struct soc_enum const spk_cle_lvl_mode_enum = + SOC_ENUM_SINGLE(R_SPKCLECTL, FB_SPKCLECTL_LVLMODE, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const spk_cle_win_sel_enum = + SOC_ENUM_SINGLE(R_SPKCLECTL, FB_SPKCLECTL_WINSEL, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +/* R_SPKCLEMUG PG 3 ADDR 0x22 */ +static DECLARE_TLV_DB_MINMAX(cle_mug_tlv_arr, 0, 4650); + +/* R_SPKCOMPRAT PG 3 ADDR 0x24 */ +static struct soc_enum const spk_comp_rat_enum = + SOC_ENUM_SINGLE(R_SPKCOMPRAT, FB_SPKCOMPRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SPKEXPTHR PG 3 ADDR 0x2F */ +static char const * const exp_rat_txt[] = { + "Reserved", "Reserved", "1:2", "1:3", + "1:4", "1:5", "1:6", "1:7"}; + +static struct soc_enum const spk_exp_rat_enum = + SOC_ENUM_SINGLE(R_SPKEXPRAT, FB_SPKEXPRAT_RATIO, + ARRAY_SIZE(exp_rat_txt), exp_rat_txt); + +/* R_DACEQFILT PG 4 ADDR 0x01 */ +static struct soc_enum const dac_eq_enums[] = { + SOC_ENUM_SINGLE(R_DACEQFILT, FB_DACEQFILT_EQ2BE, + ARRAY_SIZE(eq_txt), eq_txt), + SOC_ENUM_SINGLE(R_DACEQFILT, FB_DACEQFILT_EQ1BE, + ARRAY_SIZE(eq_txt), eq_txt), +}; + +/* R_DACMBCCTL PG 4 ADDR 0x0B */ +static struct soc_enum const dac_mbc3_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_LVLMODE3, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const dac_mbc3_win_sel_enum = + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_WINSEL3, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +static struct soc_enum const dac_mbc2_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_LVLMODE2, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const dac_mbc2_win_sel_enum = + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_WINSEL2, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +static struct soc_enum const dac_mbc1_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_LVLMODE1, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const dac_mbc1_win_sel_enum = + SOC_ENUM_SINGLE(R_DACMBCCTL, FB_DACMBCCTL_WINSEL1, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +/* R_DACMBCMUG1 PG 4 ADDR 0x0C */ +static struct soc_enum const dac_mbc1_phase_pol_enum = + SOC_ENUM_SINGLE(R_DACMBCMUG1, FB_DACMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_DACMBCRAT1 PG 4 ADDR 0x0E */ +static struct soc_enum const dac_mbc1_comp_rat_enum = + SOC_ENUM_SINGLE(R_DACMBCRAT1, FB_DACMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_DACMBCMUG2 PG 4 ADDR 0x13 */ +static struct soc_enum const dac_mbc2_phase_pol_enum = + SOC_ENUM_SINGLE(R_DACMBCMUG2, FB_DACMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_DACMBCRAT2 PG 4 ADDR 0x15 */ +static struct soc_enum const dac_mbc2_comp_rat_enum = + SOC_ENUM_SINGLE(R_DACMBCRAT2, FB_DACMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_DACMBCMUG3 PG 4 ADDR 0x1A */ +static struct soc_enum const dac_mbc3_phase_pol_enum = + SOC_ENUM_SINGLE(R_DACMBCMUG3, FB_DACMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_DACMBCRAT3 PG 4 ADDR 0x1C */ +static struct soc_enum const dac_mbc3_comp_rat_enum = + SOC_ENUM_SINGLE(R_DACMBCRAT3, FB_DACMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_DACCLECTL PG 4 ADDR 0x21 */ +static struct soc_enum const dac_cle_lvl_mode_enum = + SOC_ENUM_SINGLE(R_DACCLECTL, FB_DACCLECTL_LVLMODE, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const dac_cle_win_sel_enum = + SOC_ENUM_SINGLE(R_DACCLECTL, FB_DACCLECTL_WINSEL, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +/* R_DACCOMPRAT PG 4 ADDR 0x24 */ +static struct soc_enum const dac_comp_rat_enum = + SOC_ENUM_SINGLE(R_DACCOMPRAT, FB_DACCOMPRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_DACEXPRAT PG 4 ADDR 0x30 */ +static struct soc_enum const dac_exp_rat_enum = + SOC_ENUM_SINGLE(R_DACEXPRAT, FB_DACEXPRAT_RATIO, + ARRAY_SIZE(exp_rat_txt), exp_rat_txt); + +/* R_SUBEQFILT PG 5 ADDR 0x01 */ +static struct soc_enum const sub_eq_enums[] = { + SOC_ENUM_SINGLE(R_SUBEQFILT, FB_SUBEQFILT_EQ2BE, + ARRAY_SIZE(eq_txt), eq_txt), + SOC_ENUM_SINGLE(R_SUBEQFILT, FB_SUBEQFILT_EQ1BE, + ARRAY_SIZE(eq_txt), eq_txt), +}; + +/* R_SUBMBCCTL PG 5 ADDR 0x0B */ +static struct soc_enum const sub_mbc3_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_SUBMBCCTL, FB_SUBMBCCTL_LVLMODE3, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const sub_mbc3_win_sel_enum = + SOC_ENUM_SINGLE(R_SUBMBCCTL, FB_SUBMBCCTL_WINSEL3, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +static struct soc_enum const sub_mbc2_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_SUBMBCCTL, FB_SUBMBCCTL_LVLMODE2, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const sub_mbc2_win_sel_enum = + SOC_ENUM_SINGLE(R_SUBMBCCTL, FB_SUBMBCCTL_WINSEL2, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +static struct soc_enum const sub_mbc1_lvl_det_mode_enum = + SOC_ENUM_SINGLE(R_SUBMBCCTL, FB_SUBMBCCTL_LVLMODE1, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); + +static struct soc_enum const sub_mbc1_win_sel_enum = + SOC_ENUM_SINGLE(R_SUBMBCCTL, FB_SUBMBCCTL_WINSEL1, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +/* R_SUBMBCMUG1 PG 5 ADDR 0x0C */ +static struct soc_enum const sub_mbc1_phase_pol_enum = + SOC_ENUM_SINGLE(R_SUBMBCMUG1, FB_SUBMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_SUBMBCRAT1 PG 5 ADDR 0x0E */ +static struct soc_enum const sub_mbc1_comp_rat_enum = + SOC_ENUM_SINGLE(R_SUBMBCRAT1, FB_SUBMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SUBMBCMUG2 PG 5 ADDR 0x13 */ +static struct soc_enum const sub_mbc2_phase_pol_enum = + SOC_ENUM_SINGLE(R_SUBMBCMUG2, FB_SUBMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_SUBMBCRAT2 PG 5 ADDR 0x15 */ +static struct soc_enum const sub_mbc2_comp_rat_enum = + SOC_ENUM_SINGLE(R_SUBMBCRAT2, FB_SUBMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SUBMBCMUG3 PG 5 ADDR 0x1A */ +static struct soc_enum const sub_mbc3_phase_pol_enum = + SOC_ENUM_SINGLE(R_SUBMBCMUG3, FB_SUBMBCMUG_PHASE, + ARRAY_SIZE(pol_txt), pol_txt); + +/* R_SUBMBCRAT3 PG 5 ADDR 0x1C */ +static struct soc_enum const sub_mbc3_comp_rat_enum = + SOC_ENUM_SINGLE(R_SUBMBCRAT3, FB_SUBMBCRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SUBCLECTL PG 5 ADDR 0x21 */ +static struct soc_enum const sub_cle_lvl_mode_enum = + SOC_ENUM_SINGLE(R_SUBCLECTL, FB_SUBCLECTL_LVLMODE, + ARRAY_SIZE(lvl_mode_txt), lvl_mode_txt); +static struct soc_enum const sub_cle_win_sel_enum = + SOC_ENUM_SINGLE(R_SUBCLECTL, FB_SUBCLECTL_WINSEL, + ARRAY_SIZE(win_sel_txt), win_sel_txt); + +/* R_SUBCOMPRAT PG 5 ADDR 0x24 */ +static struct soc_enum const sub_comp_rat_enum = + SOC_ENUM_SINGLE(R_SUBCOMPRAT, FB_SUBCOMPRAT_RATIO, + ARRAY_SIZE(comp_rat_txt), comp_rat_txt); + +/* R_SUBEXPRAT PG 5 ADDR 0x30 */ +static struct soc_enum const sub_exp_rat_enum = + SOC_ENUM_SINGLE(R_SUBEXPRAT, FB_SUBEXPRAT_RATIO, + ARRAY_SIZE(exp_rat_txt), exp_rat_txt); + +static int bytes_info_ext(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *ucontrol) +{ + struct coeff_ram_ctl *ctl = + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + + ucontrol->type = SNDRV_CTL_ELEM_TYPE_BYTES; + ucontrol->count = params->max; + + return 0; +} + +/* CH 0_1 Input Mux */ +static char const * const ch_0_1_mux_txt[] = {"DAI 1", "TDM 0_1"}; + +static struct soc_enum const ch_0_1_mux_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(ch_0_1_mux_txt), ch_0_1_mux_txt); + +static struct snd_kcontrol_new const ch_0_1_mux_dapm_enum = + SOC_DAPM_ENUM("CH 0_1 Input Mux", ch_0_1_mux_enum); + +/* CH 2_3 Input Mux */ +static char const * const ch_2_3_mux_txt[] = {"DAI 2", "TDM 2_3"}; + +static struct soc_enum const ch_2_3_mux_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(ch_2_3_mux_txt), ch_2_3_mux_txt); + +static struct snd_kcontrol_new const ch_2_3_mux_dapm_enum = + SOC_DAPM_ENUM("CH 2_3 Input Mux", ch_2_3_mux_enum); + +/* CH 4_5 Input Mux */ +static char const * const ch_4_5_mux_txt[] = {"DAI 3", "TDM 4_5"}; + +static struct soc_enum const ch_4_5_mux_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, + ARRAY_SIZE(ch_4_5_mux_txt), ch_4_5_mux_txt); + +static struct snd_kcontrol_new const ch_4_5_mux_dapm_enum = + SOC_DAPM_ENUM("CH 4_5 Input Mux", ch_4_5_mux_enum); + +#define COEFF_RAM_CTL(xname, xcount, xaddr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = bytes_info_ext, \ + .get = coeff_ram_get, .put = coeff_ram_put, \ + .private_value = (unsigned long)&(struct coeff_ram_ctl) { \ + .addr = xaddr, \ + .bytes_ext = {.max = xcount, }, \ + } \ +} + +static struct snd_kcontrol_new const tscs454_snd_controls[] = { + /* R_PLLCTL PG 0 ADDR 0x15 */ + SOC_ENUM("PLL BCLK Input", bclk_sel_enum), + /* R_ISRC PG 0 ADDR 0x16 */ + SOC_ENUM("Internal Rate", isrc_br_enum), + SOC_ENUM("Internal Rate Multiple", isrc_bm_enum), + /* R_SCLKCTL PG 0 ADDR 0x18 */ + SOC_ENUM("ADC Modular Rate", adc_modular_rate_enum), + SOC_ENUM("DAC Modular Rate", dac_modular_rate_enum), + /* R_ASRC PG 0 ADDR 0x28 */ + SOC_SINGLE("ASRC Out High Bandwidth Switch", + R_ASRC, FB_ASRC_ASRCOBW, 1, 0), + SOC_SINGLE("ASRC In High Bandwidth Switch", + R_ASRC, FB_ASRC_ASRCIBW, 1, 0), + /* R_I2SIDCTL PG 0 ADDR 0x38 */ + SOC_ENUM("I2S 1 Data In Control", data_in_ctrl_enums[0]), + SOC_ENUM("I2S 2 Data In Control", data_in_ctrl_enums[1]), + SOC_ENUM("I2S 3 Data In Control", data_in_ctrl_enums[2]), + /* R_I2SODCTL PG 0 ADDR 0x39 */ + SOC_ENUM("I2S 1 Data Out Control", data_out_ctrl_enums[0]), + SOC_ENUM("I2S 2 Data Out Control", data_out_ctrl_enums[1]), + SOC_ENUM("I2S 3 Data Out Control", data_out_ctrl_enums[2]), + /* R_AUDIOMUX1 PG 0 ADDR 0x3A */ + SOC_ENUM("ASRC In", asrc_in_mux_enum), + /* R_AUDIOMUX2 PG 0 ADDR 0x3B */ + SOC_ENUM("ASRC Out", asrc_out_mux_enum), + /* R_HSDCTL1 PG 1 ADDR 0x01 */ + SOC_ENUM("Headphone Jack Type", hp_jack_type_enum), + SOC_ENUM("Headset Detection Polarity", hs_det_pol_enum), + SOC_SINGLE("Headphone Detection Switch", + R_HSDCTL1, FB_HSDCTL1_HPID_EN, 1, 0), + SOC_SINGLE("Headset OMTP/CTIA Switch", + R_HSDCTL1, FB_HSDCTL1_GBLHS_EN, 1, 0), + /* R_HSDCTL1 PG 1 ADDR 0x02 */ + SOC_ENUM("Headset Mic Bias Force", hs_mic_bias_force_enum), + SOC_SINGLE("Manual Mic Bias Switch", + R_HSDCTL2, FB_HSDCTL2_MB1MODE, 1, 0), + SOC_SINGLE("Ring/Sleeve Auto Switch", + R_HSDCTL2, FB_HSDCTL2_SWMODE, 1, 0), + SOC_ENUM("Manual Mode Plug Type", plug_type_force_enum), + /* R_CH0AIC PG 1 ADDR 0x06 */ + SOC_SINGLE_TLV("Input Boost Channel 0 Volume", R_CH0AIC, + FB_CHAIC_MICBST, 0x3, 0, in_bst_vol_tlv_arr), + /* R_CH1AIC PG 1 ADDR 0x07 */ + SOC_SINGLE_TLV("Input Boost Channel 1 Volume", R_CH1AIC, + FB_CHAIC_MICBST, 0x3, 0, in_bst_vol_tlv_arr), + /* R_CH2AIC PG 1 ADDR 0x08 */ + SOC_SINGLE_TLV("Input Boost Channel 2 Volume", R_CH2AIC, + FB_CHAIC_MICBST, 0x3, 0, in_bst_vol_tlv_arr), + /* R_CH3AIC PG 1 ADDR 0x09 */ + SOC_SINGLE_TLV("Input Boost Channel 3 Volume", R_CH3AIC, + FB_CHAIC_MICBST, 0x3, 0, in_bst_vol_tlv_arr), + /* R_ICTL0 PG 1 ADDR 0x0A */ + SOC_ENUM("Input Channel 1 Polarity", in_pol_ch1_enum), + SOC_ENUM("Input Channel 0 Polarity", in_pol_ch0_enum), + SOC_ENUM("Input Processor Channel 0/1 Operation", + in_proc_ch01_sel_enum), + SOC_SINGLE("Input Channel 1 Mute Switch", + R_ICTL0, FB_ICTL0_IN1MUTE, 1, 0), + SOC_SINGLE("Input Channel 0 Mute Switch", + R_ICTL0, FB_ICTL0_IN0MUTE, 1, 0), + SOC_SINGLE("Input Channel 1 HPF Disable Switch", + R_ICTL0, FB_ICTL0_IN1HP, 1, 0), + SOC_SINGLE("Input Channel 0 HPF Disable Switch", + R_ICTL0, FB_ICTL0_IN0HP, 1, 0), + /* R_ICTL1 PG 1 ADDR 0x0B */ + SOC_ENUM("Input Channel 3 Polarity", in_pol_ch3_enum), + SOC_ENUM("Input Channel 2 Polarity", in_pol_ch2_enum), + SOC_ENUM("Input Processor Channel 2/3 Operation", + in_proc_ch23_sel_enum), + SOC_SINGLE("Input Channel 3 Mute Switch", + R_ICTL1, FB_ICTL1_IN3MUTE, 1, 0), + SOC_SINGLE("Input Channel 2 Mute Switch", + R_ICTL1, FB_ICTL1_IN2MUTE, 1, 0), + SOC_SINGLE("Input Channel 3 HPF Disable Switch", + R_ICTL1, FB_ICTL1_IN3HP, 1, 0), + SOC_SINGLE("Input Channel 2 HPF Disable Switch", + R_ICTL1, FB_ICTL1_IN2HP, 1, 0), + /* R_MICBIAS PG 1 ADDR 0x0C */ + SOC_ENUM("Mic Bias 2 Voltage", mic_bias_2_enum), + SOC_ENUM("Mic Bias 1 Voltage", mic_bias_1_enum), + /* R_PGACTL0 PG 1 ADDR 0x0D */ + SOC_SINGLE("Input Channel 0 PGA Mute Switch", + R_PGACTL0, FB_PGACTL_PGAMUTE, 1, 0), + SOC_SINGLE_TLV("Input Channel 0 PGA Volume", R_PGACTL0, + FB_PGACTL_PGAVOL, + FM_PGACTL_PGAVOL, 0, in_pga_vol_tlv_arr), + /* R_PGACTL1 PG 1 ADDR 0x0E */ + SOC_SINGLE("Input Channel 1 PGA Mute Switch", + R_PGACTL1, FB_PGACTL_PGAMUTE, 1, 0), + SOC_SINGLE_TLV("Input Channel 1 PGA Volume", R_PGACTL1, + FB_PGACTL_PGAVOL, + FM_PGACTL_PGAVOL, 0, in_pga_vol_tlv_arr), + /* R_PGACTL2 PG 1 ADDR 0x0F */ + SOC_SINGLE("Input Channel 2 PGA Mute Switch", + R_PGACTL2, FB_PGACTL_PGAMUTE, 1, 0), + SOC_SINGLE_TLV("Input Channel 2 PGA Volume", R_PGACTL2, + FB_PGACTL_PGAVOL, + FM_PGACTL_PGAVOL, 0, in_pga_vol_tlv_arr), + /* R_PGACTL3 PG 1 ADDR 0x10 */ + SOC_SINGLE("Input Channel 3 PGA Mute Switch", + R_PGACTL3, FB_PGACTL_PGAMUTE, 1, 0), + SOC_SINGLE_TLV("Input Channel 3 PGA Volume", R_PGACTL3, + FB_PGACTL_PGAVOL, + FM_PGACTL_PGAVOL, 0, in_pga_vol_tlv_arr), + /* R_ICH0VOL PG 1 ADDR 0x12 */ + SOC_SINGLE_TLV("Input Channel 0 Volume", R_ICH0VOL, + FB_ICHVOL_ICHVOL, FM_ICHVOL_ICHVOL, 0, in_vol_tlv_arr), + /* R_ICH1VOL PG 1 ADDR 0x13 */ + SOC_SINGLE_TLV("Input Channel 1 Volume", R_ICH1VOL, + FB_ICHVOL_ICHVOL, FM_ICHVOL_ICHVOL, 0, in_vol_tlv_arr), + /* R_ICH2VOL PG 1 ADDR 0x14 */ + SOC_SINGLE_TLV("Input Channel 2 Volume", R_ICH2VOL, + FB_ICHVOL_ICHVOL, FM_ICHVOL_ICHVOL, 0, in_vol_tlv_arr), + /* R_ICH3VOL PG 1 ADDR 0x15 */ + SOC_SINGLE_TLV("Input Channel 3 Volume", R_ICH3VOL, + FB_ICHVOL_ICHVOL, FM_ICHVOL_ICHVOL, 0, in_vol_tlv_arr), + /* R_ASRCILVOL PG 1 ADDR 0x16 */ + SOC_SINGLE_TLV("ASRC Input Left Volume", R_ASRCILVOL, + FB_ASRCILVOL_ASRCILVOL, FM_ASRCILVOL_ASRCILVOL, + 0, asrc_vol_tlv_arr), + /* R_ASRCIRVOL PG 1 ADDR 0x17 */ + SOC_SINGLE_TLV("ASRC Input Right Volume", R_ASRCIRVOL, + FB_ASRCIRVOL_ASRCIRVOL, FM_ASRCIRVOL_ASRCIRVOL, + 0, asrc_vol_tlv_arr), + /* R_ASRCOLVOL PG 1 ADDR 0x18 */ + SOC_SINGLE_TLV("ASRC Output Left Volume", R_ASRCOLVOL, + FB_ASRCOLVOL_ASRCOLVOL, FM_ASRCOLVOL_ASRCOLVOL, + 0, asrc_vol_tlv_arr), + /* R_ASRCORVOL PG 1 ADDR 0x19 */ + SOC_SINGLE_TLV("ASRC Output Right Volume", R_ASRCORVOL, + FB_ASRCORVOL_ASRCOLVOL, FM_ASRCORVOL_ASRCOLVOL, + 0, asrc_vol_tlv_arr), + /* R_IVOLCTLU PG 1 ADDR 0x1C */ + /* R_ALCCTL0 PG 1 ADDR 0x1D */ + SOC_ENUM("ALC Mode", alc_mode_enum), + SOC_ENUM("ALC Reference", alc_ref_enum), + SOC_SINGLE("Input Channel 3 ALC Switch", + R_ALCCTL0, FB_ALCCTL0_ALCEN3, 1, 0), + SOC_SINGLE("Input Channel 2 ALC Switch", + R_ALCCTL0, FB_ALCCTL0_ALCEN2, 1, 0), + SOC_SINGLE("Input Channel 1 ALC Switch", + R_ALCCTL0, FB_ALCCTL0_ALCEN1, 1, 0), + SOC_SINGLE("Input Channel 0 ALC Switch", + R_ALCCTL0, FB_ALCCTL0_ALCEN0, 1, 0), + /* R_ALCCTL1 PG 1 ADDR 0x1E */ + SOC_SINGLE_TLV("ALC Max Gain Volume", R_ALCCTL1, + FB_ALCCTL1_MAXGAIN, FM_ALCCTL1_MAXGAIN, + 0, alc_max_gain_tlv_arr), + SOC_SINGLE_TLV("ALC Target Volume", R_ALCCTL1, + FB_ALCCTL1_ALCL, FM_ALCCTL1_ALCL, + 0, alc_target_tlv_arr), + /* R_ALCCTL2 PG 1 ADDR 0x1F */ + SOC_SINGLE("ALC Zero Cross Switch", + R_ALCCTL2, FB_ALCCTL2_ALCZC, 1, 0), + SOC_SINGLE_TLV("ALC Min Gain Volume", R_ALCCTL2, + FB_ALCCTL2_MINGAIN, FM_ALCCTL2_MINGAIN, + 0, alc_min_gain_tlv_arr), + SOC_SINGLE_RANGE("ALC Hold", R_ALCCTL2, + FB_ALCCTL2_HLD, 0, FM_ALCCTL2_HLD, 0), + /* R_ALCCTL3 PG 1 ADDR 0x20 */ + SOC_SINGLE_RANGE("ALC Decay", R_ALCCTL3, + FB_ALCCTL3_DCY, 0, FM_ALCCTL3_DCY, 0), + SOC_SINGLE_RANGE("ALC Attack", R_ALCCTL3, + FB_ALCCTL3_ATK, 0, FM_ALCCTL3_ATK, 0), + /* R_NGATE PG 1 ADDR 0x21 */ + SOC_SINGLE_TLV("Noise Gate Threshold Volume", R_NGATE, + FB_NGATE_NGTH, FM_NGATE_NGTH, 0, ngth_tlv_arr), + SOC_ENUM("Noise Gate Type", ngate_type_enum), + SOC_SINGLE("Noise Gate Switch", R_NGATE, FB_NGATE_NGAT, 1, 0), + /* R_DMICCTL PG 1 ADDR 0x22 */ + SOC_SINGLE("Digital Mic 2 Switch", R_DMICCTL, FB_DMICCTL_DMIC2EN, 1, 0), + SOC_SINGLE("Digital Mic 1 Switch", R_DMICCTL, FB_DMICCTL_DMIC1EN, 1, 0), + SOC_ENUM("Digital Mic Mono Select", dmic_mono_sel_enum), + /* R_DACCTL PG 2 ADDR 0x01 */ + SOC_ENUM("DAC Polarity Left", dac_pol_r_enum), + SOC_ENUM("DAC Polarity Right", dac_pol_l_enum), + SOC_ENUM("DAC Dither", dac_dith_enum), + SOC_SINGLE("DAC Mute Switch", R_DACCTL, FB_DACCTL_DACMUTE, 1, 0), + SOC_SINGLE("DAC De-Emphasis Switch", R_DACCTL, FB_DACCTL_DACDEM, 1, 0), + /* R_SPKCTL PG 2 ADDR 0x02 */ + SOC_ENUM("Speaker Polarity Right", spk_pol_r_enum), + SOC_ENUM("Speaker Polarity Left", spk_pol_l_enum), + SOC_SINGLE("Speaker Mute Switch", R_SPKCTL, FB_SPKCTL_SPKMUTE, 1, 0), + SOC_SINGLE("Speaker De-Emphasis Switch", + R_SPKCTL, FB_SPKCTL_SPKDEM, 1, 0), + /* R_SUBCTL PG 2 ADDR 0x03 */ + SOC_ENUM("Sub Polarity", sub_pol_enum), + SOC_SINGLE("SUB Mute Switch", R_SUBCTL, FB_SUBCTL_SUBMUTE, 1, 0), + SOC_SINGLE("Sub De-Emphasis Switch", R_SUBCTL, FB_SUBCTL_SUBDEM, 1, 0), + /* R_DCCTL PG 2 ADDR 0x04 */ + SOC_SINGLE("Sub DC Removal Switch", R_DCCTL, FB_DCCTL_SUBDCBYP, 1, 1), + SOC_SINGLE("DAC DC Removal Switch", R_DCCTL, FB_DCCTL_DACDCBYP, 1, 1), + SOC_SINGLE("Speaker DC Removal Switch", + R_DCCTL, FB_DCCTL_SPKDCBYP, 1, 1), + SOC_SINGLE("DC Removal Coefficient Switch", R_DCCTL, FB_DCCTL_DCCOEFSEL, + FM_DCCTL_DCCOEFSEL, 0), + /* R_OVOLCTLU PG 2 ADDR 0x06 */ + SOC_SINGLE("Output Fade Switch", R_OVOLCTLU, FB_OVOLCTLU_OFADE, 1, 0), + /* R_MVOLL PG 2 ADDR 0x08 */ + /* R_MVOLR PG 2 ADDR 0x09 */ + SOC_DOUBLE_R_TLV("Master Volume", R_MVOLL, R_MVOLR, + FB_MVOLL_MVOL_L, FM_MVOLL_MVOL_L, 0, mvol_tlv_arr), + /* R_HPVOLL PG 2 ADDR 0x0A */ + /* R_HPVOLR PG 2 ADDR 0x0B */ + SOC_DOUBLE_R_TLV("Headphone Volume", R_HPVOLL, R_HPVOLR, + FB_HPVOLL_HPVOL_L, FM_HPVOLL_HPVOL_L, 0, + hp_vol_tlv_arr), + /* R_SPKVOLL PG 2 ADDR 0x0C */ + /* R_SPKVOLR PG 2 ADDR 0x0D */ + SOC_DOUBLE_R_TLV("Speaker Volume", R_SPKVOLL, R_SPKVOLR, + FB_SPKVOLL_SPKVOL_L, FM_SPKVOLL_SPKVOL_L, 0, + spk_vol_tlv_arr), + /* R_SUBVOL PG 2 ADDR 0x10 */ + SOC_SINGLE_TLV("Sub Volume", R_SUBVOL, + FB_SUBVOL_SUBVOL, FM_SUBVOL_SUBVOL, 0, spk_vol_tlv_arr), + /* R_SPKEQFILT PG 3 ADDR 0x01 */ + SOC_SINGLE("Speaker EQ 2 Switch", + R_SPKEQFILT, FB_SPKEQFILT_EQ2EN, 1, 0), + SOC_ENUM("Speaker EQ 2 Band", spk_eq_enums[0]), + SOC_SINGLE("Speaker EQ 1 Switch", + R_SPKEQFILT, FB_SPKEQFILT_EQ1EN, 1, 0), + SOC_ENUM("Speaker EQ 1 Band", spk_eq_enums[1]), + /* R_SPKMBCEN PG 3 ADDR 0x0A */ + SOC_SINGLE("Speaker MBC 3 Switch", + R_SPKMBCEN, FB_SPKMBCEN_MBCEN3, 1, 0), + SOC_SINGLE("Speaker MBC 2 Switch", + R_SPKMBCEN, FB_SPKMBCEN_MBCEN2, 1, 0), + SOC_SINGLE("Speaker MBC 1 Switch", + R_SPKMBCEN, FB_SPKMBCEN_MBCEN1, 1, 0), + /* R_SPKMBCCTL PG 3 ADDR 0x0B */ + SOC_ENUM("Speaker MBC 3 Mode", spk_mbc3_lvl_det_mode_enum), + SOC_ENUM("Speaker MBC 3 Window", spk_mbc3_win_sel_enum), + SOC_ENUM("Speaker MBC 2 Mode", spk_mbc2_lvl_det_mode_enum), + SOC_ENUM("Speaker MBC 2 Window", spk_mbc2_win_sel_enum), + SOC_ENUM("Speaker MBC 1 Mode", spk_mbc1_lvl_det_mode_enum), + SOC_ENUM("Speaker MBC 1 Window", spk_mbc1_win_sel_enum), + /* R_SPKMBCMUG1 PG 3 ADDR 0x0C */ + SOC_ENUM("Speaker MBC 1 Phase Polarity", spk_mbc1_phase_pol_enum), + SOC_SINGLE_TLV("Speaker MBC1 Make-Up Gain Volume", R_SPKMBCMUG1, + FB_SPKMBCMUG_MUGAIN, FM_SPKMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_SPKMBCTHR1 PG 3 ADDR 0x0D */ + SOC_SINGLE_TLV("Speaker MBC 1 Compressor Threshold Volume", + R_SPKMBCTHR1, FB_SPKMBCTHR_THRESH, FM_SPKMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_SPKMBCRAT1 PG 3 ADDR 0x0E */ + SOC_ENUM("Speaker MBC 1 Compressor Ratio", spk_mbc1_comp_rat_enum), + /* R_SPKMBCATK1L PG 3 ADDR 0x0F */ + /* R_SPKMBCATK1H PG 3 ADDR 0x10 */ + SND_SOC_BYTES("Speaker MBC 1 Attack", R_SPKMBCATK1L, 2), + /* R_SPKMBCREL1L PG 3 ADDR 0x11 */ + /* R_SPKMBCREL1H PG 3 ADDR 0x12 */ + SND_SOC_BYTES("Speaker MBC 1 Release", R_SPKMBCREL1L, 2), + /* R_SPKMBCMUG2 PG 3 ADDR 0x13 */ + SOC_ENUM("Speaker MBC 2 Phase Polarity", spk_mbc2_phase_pol_enum), + SOC_SINGLE_TLV("Speaker MBC2 Make-Up Gain Volume", R_SPKMBCMUG2, + FB_SPKMBCMUG_MUGAIN, FM_SPKMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_SPKMBCTHR2 PG 3 ADDR 0x14 */ + SOC_SINGLE_TLV("Speaker MBC 2 Compressor Threshold Volume", + R_SPKMBCTHR2, FB_SPKMBCTHR_THRESH, FM_SPKMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_SPKMBCRAT2 PG 3 ADDR 0x15 */ + SOC_ENUM("Speaker MBC 2 Compressor Ratio", spk_mbc2_comp_rat_enum), + /* R_SPKMBCATK2L PG 3 ADDR 0x16 */ + /* R_SPKMBCATK2H PG 3 ADDR 0x17 */ + SND_SOC_BYTES("Speaker MBC 2 Attack", R_SPKMBCATK2L, 2), + /* R_SPKMBCREL2L PG 3 ADDR 0x18 */ + /* R_SPKMBCREL2H PG 3 ADDR 0x19 */ + SND_SOC_BYTES("Speaker MBC 2 Release", R_SPKMBCREL2L, 2), + /* R_SPKMBCMUG3 PG 3 ADDR 0x1A */ + SOC_ENUM("Speaker MBC 3 Phase Polarity", spk_mbc3_phase_pol_enum), + SOC_SINGLE_TLV("Speaker MBC 3 Make-Up Gain Volume", R_SPKMBCMUG3, + FB_SPKMBCMUG_MUGAIN, FM_SPKMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_SPKMBCTHR3 PG 3 ADDR 0x1B */ + SOC_SINGLE_TLV("Speaker MBC 3 Threshold Volume", R_SPKMBCTHR3, + FB_SPKMBCTHR_THRESH, FM_SPKMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_SPKMBCRAT3 PG 3 ADDR 0x1C */ + SOC_ENUM("Speaker MBC 3 Compressor Ratio", spk_mbc3_comp_rat_enum), + /* R_SPKMBCATK3L PG 3 ADDR 0x1D */ + /* R_SPKMBCATK3H PG 3 ADDR 0x1E */ + SND_SOC_BYTES("Speaker MBC 3 Attack", R_SPKMBCATK3L, 3), + /* R_SPKMBCREL3L PG 3 ADDR 0x1F */ + /* R_SPKMBCREL3H PG 3 ADDR 0x20 */ + SND_SOC_BYTES("Speaker MBC 3 Release", R_SPKMBCREL3L, 3), + /* R_SPKCLECTL PG 3 ADDR 0x21 */ + SOC_ENUM("Speaker CLE Level Mode", spk_cle_lvl_mode_enum), + SOC_ENUM("Speaker CLE Window", spk_cle_win_sel_enum), + SOC_SINGLE("Speaker CLE Expander Switch", + R_SPKCLECTL, FB_SPKCLECTL_EXPEN, 1, 0), + SOC_SINGLE("Speaker CLE Limiter Switch", + R_SPKCLECTL, FB_SPKCLECTL_LIMEN, 1, 0), + SOC_SINGLE("Speaker CLE Compressor Switch", + R_SPKCLECTL, FB_SPKCLECTL_COMPEN, 1, 0), + /* R_SPKCLEMUG PG 3 ADDR 0x22 */ + SOC_SINGLE_TLV("Speaker CLE Make-Up Gain Volume", R_SPKCLEMUG, + FB_SPKCLEMUG_MUGAIN, FM_SPKCLEMUG_MUGAIN, + 0, cle_mug_tlv_arr), + /* R_SPKCOMPTHR PG 3 ADDR 0x23 */ + SOC_SINGLE_TLV("Speaker Compressor Threshold Volume", R_SPKCOMPTHR, + FB_SPKCOMPTHR_THRESH, FM_SPKCOMPTHR_THRESH, + 0, thr_tlv_arr), + /* R_SPKCOMPRAT PG 3 ADDR 0x24 */ + SOC_ENUM("Speaker Compressor Ratio", spk_comp_rat_enum), + /* R_SPKCOMPATKL PG 3 ADDR 0x25 */ + /* R_SPKCOMPATKH PG 3 ADDR 0x26 */ + SND_SOC_BYTES("Speaker Compressor Attack", R_SPKCOMPATKL, 2), + /* R_SPKCOMPRELL PG 3 ADDR 0x27 */ + /* R_SPKCOMPRELH PG 3 ADDR 0x28 */ + SND_SOC_BYTES("Speaker Compressor Release", R_SPKCOMPRELL, 2), + /* R_SPKLIMTHR PG 3 ADDR 0x29 */ + SOC_SINGLE_TLV("Speaker Limiter Threshold Volume", R_SPKLIMTHR, + FB_SPKLIMTHR_THRESH, FM_SPKLIMTHR_THRESH, + 0, thr_tlv_arr), + /* R_SPKLIMTGT PG 3 ADDR 0x2A */ + SOC_SINGLE_TLV("Speaker Limiter Target Volume", R_SPKLIMTGT, + FB_SPKLIMTGT_TARGET, FM_SPKLIMTGT_TARGET, + 0, thr_tlv_arr), + /* R_SPKLIMATKL PG 3 ADDR 0x2B */ + /* R_SPKLIMATKH PG 3 ADDR 0x2C */ + SND_SOC_BYTES("Speaker Limiter Attack", R_SPKLIMATKL, 2), + /* R_SPKLIMRELL PG 3 ADDR 0x2D */ + /* R_SPKLIMRELR PG 3 ADDR 0x2E */ + SND_SOC_BYTES("Speaker Limiter Release", R_SPKLIMRELL, 2), + /* R_SPKEXPTHR PG 3 ADDR 0x2F */ + SOC_SINGLE_TLV("Speaker Expander Threshold Volume", R_SPKEXPTHR, + FB_SPKEXPTHR_THRESH, FM_SPKEXPTHR_THRESH, + 0, thr_tlv_arr), + /* R_SPKEXPRAT PG 3 ADDR 0x30 */ + SOC_ENUM("Speaker Expander Ratio", spk_exp_rat_enum), + /* R_SPKEXPATKL PG 3 ADDR 0x31 */ + /* R_SPKEXPATKR PG 3 ADDR 0x32 */ + SND_SOC_BYTES("Speaker Expander Attack", R_SPKEXPATKL, 2), + /* R_SPKEXPRELL PG 3 ADDR 0x33 */ + /* R_SPKEXPRELR PG 3 ADDR 0x34 */ + SND_SOC_BYTES("Speaker Expander Release", R_SPKEXPRELL, 2), + /* R_SPKFXCTL PG 3 ADDR 0x35 */ + SOC_SINGLE("Speaker 3D Switch", R_SPKFXCTL, FB_SPKFXCTL_3DEN, 1, 0), + SOC_SINGLE("Speaker Treble Enhancement Switch", + R_SPKFXCTL, FB_SPKFXCTL_TEEN, 1, 0), + SOC_SINGLE("Speaker Treble NLF Switch", + R_SPKFXCTL, FB_SPKFXCTL_TNLFBYP, 1, 1), + SOC_SINGLE("Speaker Bass Enhancement Switch", + R_SPKFXCTL, FB_SPKFXCTL_BEEN, 1, 0), + SOC_SINGLE("Speaker Bass NLF Switch", + R_SPKFXCTL, FB_SPKFXCTL_BNLFBYP, 1, 1), + /* R_DACEQFILT PG 4 ADDR 0x01 */ + SOC_SINGLE("DAC EQ 2 Switch", + R_DACEQFILT, FB_DACEQFILT_EQ2EN, 1, 0), + SOC_ENUM("DAC EQ 2 Band", dac_eq_enums[0]), + SOC_SINGLE("DAC EQ 1 Switch", R_DACEQFILT, FB_DACEQFILT_EQ1EN, 1, 0), + SOC_ENUM("DAC EQ 1 Band", dac_eq_enums[1]), + /* R_DACMBCEN PG 4 ADDR 0x0A */ + SOC_SINGLE("DAC MBC 3 Switch", R_DACMBCEN, FB_DACMBCEN_MBCEN3, 1, 0), + SOC_SINGLE("DAC MBC 2 Switch", R_DACMBCEN, FB_DACMBCEN_MBCEN2, 1, 0), + SOC_SINGLE("DAC MBC 1 Switch", R_DACMBCEN, FB_DACMBCEN_MBCEN1, 1, 0), + /* R_DACMBCCTL PG 4 ADDR 0x0B */ + SOC_ENUM("DAC MBC 3 Mode", dac_mbc3_lvl_det_mode_enum), + SOC_ENUM("DAC MBC 3 Window", dac_mbc3_win_sel_enum), + SOC_ENUM("DAC MBC 2 Mode", dac_mbc2_lvl_det_mode_enum), + SOC_ENUM("DAC MBC 2 Window", dac_mbc2_win_sel_enum), + SOC_ENUM("DAC MBC 1 Mode", dac_mbc1_lvl_det_mode_enum), + SOC_ENUM("DAC MBC 1 Window", dac_mbc1_win_sel_enum), + /* R_DACMBCMUG1 PG 4 ADDR 0x0C */ + SOC_ENUM("DAC MBC 1 Phase Polarity", dac_mbc1_phase_pol_enum), + SOC_SINGLE_TLV("DAC MBC 1 Make-Up Gain Volume", R_DACMBCMUG1, + FB_DACMBCMUG_MUGAIN, FM_DACMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_DACMBCTHR1 PG 4 ADDR 0x0D */ + SOC_SINGLE_TLV("DAC MBC 1 Compressor Threshold Volume", R_DACMBCTHR1, + FB_DACMBCTHR_THRESH, FM_DACMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_DACMBCRAT1 PG 4 ADDR 0x0E */ + SOC_ENUM("DAC MBC 1 Compressor Ratio", dac_mbc1_comp_rat_enum), + /* R_DACMBCATK1L PG 4 ADDR 0x0F */ + /* R_DACMBCATK1H PG 4 ADDR 0x10 */ + SND_SOC_BYTES("DAC MBC 1 Attack", R_DACMBCATK1L, 2), + /* R_DACMBCREL1L PG 4 ADDR 0x11 */ + /* R_DACMBCREL1H PG 4 ADDR 0x12 */ + SND_SOC_BYTES("DAC MBC 1 Release", R_DACMBCREL1L, 2), + /* R_DACMBCMUG2 PG 4 ADDR 0x13 */ + SOC_ENUM("DAC MBC 2 Phase Polarity", dac_mbc2_phase_pol_enum), + SOC_SINGLE_TLV("DAC MBC 2 Make-Up Gain Volume", R_DACMBCMUG2, + FB_DACMBCMUG_MUGAIN, FM_DACMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_DACMBCTHR2 PG 4 ADDR 0x14 */ + SOC_SINGLE_TLV("DAC MBC 2 Compressor Threshold Volume", R_DACMBCTHR2, + FB_DACMBCTHR_THRESH, FM_DACMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_DACMBCRAT2 PG 4 ADDR 0x15 */ + SOC_ENUM("DAC MBC 2 Compressor Ratio", dac_mbc2_comp_rat_enum), + /* R_DACMBCATK2L PG 4 ADDR 0x16 */ + /* R_DACMBCATK2H PG 4 ADDR 0x17 */ + SND_SOC_BYTES("DAC MBC 2 Attack", R_DACMBCATK2L, 2), + /* R_DACMBCREL2L PG 4 ADDR 0x18 */ + /* R_DACMBCREL2H PG 4 ADDR 0x19 */ + SND_SOC_BYTES("DAC MBC 2 Release", R_DACMBCREL2L, 2), + /* R_DACMBCMUG3 PG 4 ADDR 0x1A */ + SOC_ENUM("DAC MBC 3 Phase Polarity", dac_mbc3_phase_pol_enum), + SOC_SINGLE_TLV("DAC MBC 3 Make-Up Gain Volume", R_DACMBCMUG3, + FB_DACMBCMUG_MUGAIN, FM_DACMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_DACMBCTHR3 PG 4 ADDR 0x1B */ + SOC_SINGLE_TLV("DAC MBC 3 Threshold Volume", R_DACMBCTHR3, + FB_DACMBCTHR_THRESH, FM_DACMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_DACMBCRAT3 PG 4 ADDR 0x1C */ + SOC_ENUM("DAC MBC 3 Compressor Ratio", dac_mbc3_comp_rat_enum), + /* R_DACMBCATK3L PG 4 ADDR 0x1D */ + /* R_DACMBCATK3H PG 4 ADDR 0x1E */ + SND_SOC_BYTES("DAC MBC 3 Attack", R_DACMBCATK3L, 3), + /* R_DACMBCREL3L PG 4 ADDR 0x1F */ + /* R_DACMBCREL3H PG 4 ADDR 0x20 */ + SND_SOC_BYTES("DAC MBC 3 Release", R_DACMBCREL3L, 3), + /* R_DACCLECTL PG 4 ADDR 0x21 */ + SOC_ENUM("DAC CLE Level Mode", dac_cle_lvl_mode_enum), + SOC_ENUM("DAC CLE Window", dac_cle_win_sel_enum), + SOC_SINGLE("DAC CLE Expander Switch", + R_DACCLECTL, FB_DACCLECTL_EXPEN, 1, 0), + SOC_SINGLE("DAC CLE Limiter Switch", + R_DACCLECTL, FB_DACCLECTL_LIMEN, 1, 0), + SOC_SINGLE("DAC CLE Compressor Switch", + R_DACCLECTL, FB_DACCLECTL_COMPEN, 1, 0), + /* R_DACCLEMUG PG 4 ADDR 0x22 */ + SOC_SINGLE_TLV("DAC CLE Make-Up Gain Volume", R_DACCLEMUG, + FB_DACCLEMUG_MUGAIN, FM_DACCLEMUG_MUGAIN, + 0, cle_mug_tlv_arr), + /* R_DACCOMPTHR PG 4 ADDR 0x23 */ + SOC_SINGLE_TLV("DAC Compressor Threshold Volume", R_DACCOMPTHR, + FB_DACCOMPTHR_THRESH, FM_DACCOMPTHR_THRESH, + 0, thr_tlv_arr), + /* R_DACCOMPRAT PG 4 ADDR 0x24 */ + SOC_ENUM("DAC Compressor Ratio", dac_comp_rat_enum), + /* R_DACCOMPATKL PG 4 ADDR 0x25 */ + /* R_DACCOMPATKH PG 4 ADDR 0x26 */ + SND_SOC_BYTES("DAC Compressor Attack", R_DACCOMPATKL, 2), + /* R_DACCOMPRELL PG 4 ADDR 0x27 */ + /* R_DACCOMPRELH PG 4 ADDR 0x28 */ + SND_SOC_BYTES("DAC Compressor Release", R_DACCOMPRELL, 2), + /* R_DACLIMTHR PG 4 ADDR 0x29 */ + SOC_SINGLE_TLV("DAC Limiter Threshold Volume", R_DACLIMTHR, + FB_DACLIMTHR_THRESH, FM_DACLIMTHR_THRESH, + 0, thr_tlv_arr), + /* R_DACLIMTGT PG 4 ADDR 0x2A */ + SOC_SINGLE_TLV("DAC Limiter Target Volume", R_DACLIMTGT, + FB_DACLIMTGT_TARGET, FM_DACLIMTGT_TARGET, + 0, thr_tlv_arr), + /* R_DACLIMATKL PG 4 ADDR 0x2B */ + /* R_DACLIMATKH PG 4 ADDR 0x2C */ + SND_SOC_BYTES("DAC Limiter Attack", R_DACLIMATKL, 2), + /* R_DACLIMRELL PG 4 ADDR 0x2D */ + /* R_DACLIMRELR PG 4 ADDR 0x2E */ + SND_SOC_BYTES("DAC Limiter Release", R_DACLIMRELL, 2), + /* R_DACEXPTHR PG 4 ADDR 0x2F */ + SOC_SINGLE_TLV("DAC Expander Threshold Volume", R_DACEXPTHR, + FB_DACEXPTHR_THRESH, FM_DACEXPTHR_THRESH, + 0, thr_tlv_arr), + /* R_DACEXPRAT PG 4 ADDR 0x30 */ + SOC_ENUM("DAC Expander Ratio", dac_exp_rat_enum), + /* R_DACEXPATKL PG 4 ADDR 0x31 */ + /* R_DACEXPATKR PG 4 ADDR 0x32 */ + SND_SOC_BYTES("DAC Expander Attack", R_DACEXPATKL, 2), + /* R_DACEXPRELL PG 4 ADDR 0x33 */ + /* R_DACEXPRELR PG 4 ADDR 0x34 */ + SND_SOC_BYTES("DAC Expander Release", R_DACEXPRELL, 2), + /* R_DACFXCTL PG 4 ADDR 0x35 */ + SOC_SINGLE("DAC 3D Switch", R_DACFXCTL, FB_DACFXCTL_3DEN, 1, 0), + SOC_SINGLE("DAC Treble Enhancement Switch", + R_DACFXCTL, FB_DACFXCTL_TEEN, 1, 0), + SOC_SINGLE("DAC Treble NLF Switch", + R_DACFXCTL, FB_DACFXCTL_TNLFBYP, 1, 1), + SOC_SINGLE("DAC Bass Enhancement Switch", + R_DACFXCTL, FB_DACFXCTL_BEEN, 1, 0), + SOC_SINGLE("DAC Bass NLF Switch", + R_DACFXCTL, FB_DACFXCTL_BNLFBYP, 1, 1), + /* R_SUBEQFILT PG 5 ADDR 0x01 */ + SOC_SINGLE("Sub EQ 2 Switch", + R_SUBEQFILT, FB_SUBEQFILT_EQ2EN, 1, 0), + SOC_ENUM("Sub EQ 2 Band", sub_eq_enums[0]), + SOC_SINGLE("Sub EQ 1 Switch", R_SUBEQFILT, FB_SUBEQFILT_EQ1EN, 1, 0), + SOC_ENUM("Sub EQ 1 Band", sub_eq_enums[1]), + /* R_SUBMBCEN PG 5 ADDR 0x0A */ + SOC_SINGLE("Sub MBC 3 Switch", R_SUBMBCEN, FB_SUBMBCEN_MBCEN3, 1, 0), + SOC_SINGLE("Sub MBC 2 Switch", R_SUBMBCEN, FB_SUBMBCEN_MBCEN2, 1, 0), + SOC_SINGLE("Sub MBC 1 Switch", R_SUBMBCEN, FB_SUBMBCEN_MBCEN1, 1, 0), + /* R_SUBMBCCTL PG 5 ADDR 0x0B */ + SOC_ENUM("Sub MBC 3 Mode", sub_mbc3_lvl_det_mode_enum), + SOC_ENUM("Sub MBC 3 Window", sub_mbc3_win_sel_enum), + SOC_ENUM("Sub MBC 2 Mode", sub_mbc2_lvl_det_mode_enum), + SOC_ENUM("Sub MBC 2 Window", sub_mbc2_win_sel_enum), + SOC_ENUM("Sub MBC 1 Mode", sub_mbc1_lvl_det_mode_enum), + SOC_ENUM("Sub MBC 1 Window", sub_mbc1_win_sel_enum), + /* R_SUBMBCMUG1 PG 5 ADDR 0x0C */ + SOC_ENUM("Sub MBC 1 Phase Polarity", sub_mbc1_phase_pol_enum), + SOC_SINGLE_TLV("Sub MBC 1 Make-Up Gain Volume", R_SUBMBCMUG1, + FB_SUBMBCMUG_MUGAIN, FM_SUBMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_SUBMBCTHR1 PG 5 ADDR 0x0D */ + SOC_SINGLE_TLV("Sub MBC 1 Compressor Threshold Volume", R_SUBMBCTHR1, + FB_SUBMBCTHR_THRESH, FM_SUBMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_SUBMBCRAT1 PG 5 ADDR 0x0E */ + SOC_ENUM("Sub MBC 1 Compressor Ratio", sub_mbc1_comp_rat_enum), + /* R_SUBMBCATK1L PG 5 ADDR 0x0F */ + /* R_SUBMBCATK1H PG 5 ADDR 0x10 */ + SND_SOC_BYTES("Sub MBC 1 Attack", R_SUBMBCATK1L, 2), + /* R_SUBMBCREL1L PG 5 ADDR 0x11 */ + /* R_SUBMBCREL1H PG 5 ADDR 0x12 */ + SND_SOC_BYTES("Sub MBC 1 Release", R_SUBMBCREL1L, 2), + /* R_SUBMBCMUG2 PG 5 ADDR 0x13 */ + SOC_ENUM("Sub MBC 2 Phase Polarity", sub_mbc2_phase_pol_enum), + SOC_SINGLE_TLV("Sub MBC 2 Make-Up Gain Volume", R_SUBMBCMUG2, + FB_SUBMBCMUG_MUGAIN, FM_SUBMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_SUBMBCTHR2 PG 5 ADDR 0x14 */ + SOC_SINGLE_TLV("Sub MBC 2 Compressor Threshold Volume", R_SUBMBCTHR2, + FB_SUBMBCTHR_THRESH, FM_SUBMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_SUBMBCRAT2 PG 5 ADDR 0x15 */ + SOC_ENUM("Sub MBC 2 Compressor Ratio", sub_mbc2_comp_rat_enum), + /* R_SUBMBCATK2L PG 5 ADDR 0x16 */ + /* R_SUBMBCATK2H PG 5 ADDR 0x17 */ + SND_SOC_BYTES("Sub MBC 2 Attack", R_SUBMBCATK2L, 2), + /* R_SUBMBCREL2L PG 5 ADDR 0x18 */ + /* R_SUBMBCREL2H PG 5 ADDR 0x19 */ + SND_SOC_BYTES("Sub MBC 2 Release", R_SUBMBCREL2L, 2), + /* R_SUBMBCMUG3 PG 5 ADDR 0x1A */ + SOC_ENUM("Sub MBC 3 Phase Polarity", sub_mbc3_phase_pol_enum), + SOC_SINGLE_TLV("Sub MBC 3 Make-Up Gain Volume", R_SUBMBCMUG3, + FB_SUBMBCMUG_MUGAIN, FM_SUBMBCMUG_MUGAIN, + 0, mbc_mug_tlv_arr), + /* R_SUBMBCTHR3 PG 5 ADDR 0x1B */ + SOC_SINGLE_TLV("Sub MBC 3 Threshold Volume", R_SUBMBCTHR3, + FB_SUBMBCTHR_THRESH, FM_SUBMBCTHR_THRESH, + 0, thr_tlv_arr), + /* R_SUBMBCRAT3 PG 5 ADDR 0x1C */ + SOC_ENUM("Sub MBC 3 Compressor Ratio", sub_mbc3_comp_rat_enum), + /* R_SUBMBCATK3L PG 5 ADDR 0x1D */ + /* R_SUBMBCATK3H PG 5 ADDR 0x1E */ + SND_SOC_BYTES("Sub MBC 3 Attack", R_SUBMBCATK3L, 3), + /* R_SUBMBCREL3L PG 5 ADDR 0x1F */ + /* R_SUBMBCREL3H PG 5 ADDR 0x20 */ + SND_SOC_BYTES("Sub MBC 3 Release", R_SUBMBCREL3L, 3), + /* R_SUBCLECTL PG 5 ADDR 0x21 */ + SOC_ENUM("Sub CLE Level Mode", sub_cle_lvl_mode_enum), + SOC_ENUM("Sub CLE Window", sub_cle_win_sel_enum), + SOC_SINGLE("Sub CLE Expander Switch", + R_SUBCLECTL, FB_SUBCLECTL_EXPEN, 1, 0), + SOC_SINGLE("Sub CLE Limiter Switch", + R_SUBCLECTL, FB_SUBCLECTL_LIMEN, 1, 0), + SOC_SINGLE("Sub CLE Compressor Switch", + R_SUBCLECTL, FB_SUBCLECTL_COMPEN, 1, 0), + /* R_SUBCLEMUG PG 5 ADDR 0x22 */ + SOC_SINGLE_TLV("Sub CLE Make-Up Gain Volume", R_SUBCLEMUG, + FB_SUBCLEMUG_MUGAIN, FM_SUBCLEMUG_MUGAIN, + 0, cle_mug_tlv_arr), + /* R_SUBCOMPTHR PG 5 ADDR 0x23 */ + SOC_SINGLE_TLV("Sub Compressor Threshold Volume", R_SUBCOMPTHR, + FB_SUBCOMPTHR_THRESH, FM_SUBCOMPTHR_THRESH, + 0, thr_tlv_arr), + /* R_SUBCOMPRAT PG 5 ADDR 0x24 */ + SOC_ENUM("Sub Compressor Ratio", sub_comp_rat_enum), + /* R_SUBCOMPATKL PG 5 ADDR 0x25 */ + /* R_SUBCOMPATKH PG 5 ADDR 0x26 */ + SND_SOC_BYTES("Sub Compressor Attack", R_SUBCOMPATKL, 2), + /* R_SUBCOMPRELL PG 5 ADDR 0x27 */ + /* R_SUBCOMPRELH PG 5 ADDR 0x28 */ + SND_SOC_BYTES("Sub Compressor Release", R_SUBCOMPRELL, 2), + /* R_SUBLIMTHR PG 5 ADDR 0x29 */ + SOC_SINGLE_TLV("Sub Limiter Threshold Volume", R_SUBLIMTHR, + FB_SUBLIMTHR_THRESH, FM_SUBLIMTHR_THRESH, + 0, thr_tlv_arr), + /* R_SUBLIMTGT PG 5 ADDR 0x2A */ + SOC_SINGLE_TLV("Sub Limiter Target Volume", R_SUBLIMTGT, + FB_SUBLIMTGT_TARGET, FM_SUBLIMTGT_TARGET, + 0, thr_tlv_arr), + /* R_SUBLIMATKL PG 5 ADDR 0x2B */ + /* R_SUBLIMATKH PG 5 ADDR 0x2C */ + SND_SOC_BYTES("Sub Limiter Attack", R_SUBLIMATKL, 2), + /* R_SUBLIMRELL PG 5 ADDR 0x2D */ + /* R_SUBLIMRELR PG 5 ADDR 0x2E */ + SND_SOC_BYTES("Sub Limiter Release", R_SUBLIMRELL, 2), + /* R_SUBEXPTHR PG 5 ADDR 0x2F */ + SOC_SINGLE_TLV("Sub Expander Threshold Volume", R_SUBEXPTHR, + FB_SUBEXPTHR_THRESH, FM_SUBEXPTHR_THRESH, + 0, thr_tlv_arr), + /* R_SUBEXPRAT PG 5 ADDR 0x30 */ + SOC_ENUM("Sub Expander Ratio", sub_exp_rat_enum), + /* R_SUBEXPATKL PG 5 ADDR 0x31 */ + /* R_SUBEXPATKR PG 5 ADDR 0x32 */ + SND_SOC_BYTES("Sub Expander Attack", R_SUBEXPATKL, 2), + /* R_SUBEXPRELL PG 5 ADDR 0x33 */ + /* R_SUBEXPRELR PG 5 ADDR 0x34 */ + SND_SOC_BYTES("Sub Expander Release", R_SUBEXPRELL, 2), + /* R_SUBFXCTL PG 5 ADDR 0x35 */ + SOC_SINGLE("Sub Treble Enhancement Switch", + R_SUBFXCTL, FB_SUBFXCTL_TEEN, 1, 0), + SOC_SINGLE("Sub Treble NLF Switch", + R_SUBFXCTL, FB_SUBFXCTL_TNLFBYP, 1, 1), + SOC_SINGLE("Sub Bass Enhancement Switch", + R_SUBFXCTL, FB_SUBFXCTL_BEEN, 1, 0), + SOC_SINGLE("Sub Bass NLF Switch", + R_SUBFXCTL, FB_SUBFXCTL_BNLFBYP, 1, 1), + COEFF_RAM_CTL("DAC Cascade 1 Left BiQuad 1", BIQUAD_SIZE, 0x00), + COEFF_RAM_CTL("DAC Cascade 1 Left BiQuad 2", BIQUAD_SIZE, 0x05), + COEFF_RAM_CTL("DAC Cascade 1 Left BiQuad 3", BIQUAD_SIZE, 0x0a), + COEFF_RAM_CTL("DAC Cascade 1 Left BiQuad 4", BIQUAD_SIZE, 0x0f), + COEFF_RAM_CTL("DAC Cascade 1 Left BiQuad 5", BIQUAD_SIZE, 0x14), + COEFF_RAM_CTL("DAC Cascade 1 Left BiQuad 6", BIQUAD_SIZE, 0x19), + + COEFF_RAM_CTL("DAC Cascade 1 Right BiQuad 1", BIQUAD_SIZE, 0x20), + COEFF_RAM_CTL("DAC Cascade 1 Right BiQuad 2", BIQUAD_SIZE, 0x25), + COEFF_RAM_CTL("DAC Cascade 1 Right BiQuad 3", BIQUAD_SIZE, 0x2a), + COEFF_RAM_CTL("DAC Cascade 1 Right BiQuad 4", BIQUAD_SIZE, 0x2f), + COEFF_RAM_CTL("DAC Cascade 1 Right BiQuad 5", BIQUAD_SIZE, 0x34), + COEFF_RAM_CTL("DAC Cascade 1 Right BiQuad 6", BIQUAD_SIZE, 0x39), + + COEFF_RAM_CTL("DAC Cascade 1 Left Prescale", COEFF_SIZE, 0x1f), + COEFF_RAM_CTL("DAC Cascade 1 Right Prescale", COEFF_SIZE, 0x3f), + + COEFF_RAM_CTL("DAC Cascade 2 Left BiQuad 1", BIQUAD_SIZE, 0x40), + COEFF_RAM_CTL("DAC Cascade 2 Left BiQuad 2", BIQUAD_SIZE, 0x45), + COEFF_RAM_CTL("DAC Cascade 2 Left BiQuad 3", BIQUAD_SIZE, 0x4a), + COEFF_RAM_CTL("DAC Cascade 2 Left BiQuad 4", BIQUAD_SIZE, 0x4f), + COEFF_RAM_CTL("DAC Cascade 2 Left BiQuad 5", BIQUAD_SIZE, 0x54), + COEFF_RAM_CTL("DAC Cascade 2 Left BiQuad 6", BIQUAD_SIZE, 0x59), + + COEFF_RAM_CTL("DAC Cascade 2 Right BiQuad 1", BIQUAD_SIZE, 0x60), + COEFF_RAM_CTL("DAC Cascade 2 Right BiQuad 2", BIQUAD_SIZE, 0x65), + COEFF_RAM_CTL("DAC Cascade 2 Right BiQuad 3", BIQUAD_SIZE, 0x6a), + COEFF_RAM_CTL("DAC Cascade 2 Right BiQuad 4", BIQUAD_SIZE, 0x6f), + COEFF_RAM_CTL("DAC Cascade 2 Right BiQuad 5", BIQUAD_SIZE, 0x74), + COEFF_RAM_CTL("DAC Cascade 2 Right BiQuad 6", BIQUAD_SIZE, 0x79), + + COEFF_RAM_CTL("DAC Cascade 2 Left Prescale", COEFF_SIZE, 0x5f), + COEFF_RAM_CTL("DAC Cascade 2 Right Prescale", COEFF_SIZE, 0x7f), + + COEFF_RAM_CTL("DAC Bass Extraction BiQuad 1", BIQUAD_SIZE, 0x80), + COEFF_RAM_CTL("DAC Bass Extraction BiQuad 2", BIQUAD_SIZE, 0x85), + + COEFF_RAM_CTL("DAC Bass Non Linear Function 1", COEFF_SIZE, 0x8a), + COEFF_RAM_CTL("DAC Bass Non Linear Function 2", COEFF_SIZE, 0x8b), + + COEFF_RAM_CTL("DAC Bass Limiter BiQuad", BIQUAD_SIZE, 0x8c), + + COEFF_RAM_CTL("DAC Bass Cut Off BiQuad", BIQUAD_SIZE, 0x91), + + COEFF_RAM_CTL("DAC Bass Mix", COEFF_SIZE, 0x96), + + COEFF_RAM_CTL("DAC Treb Extraction BiQuad 1", BIQUAD_SIZE, 0x97), + COEFF_RAM_CTL("DAC Treb Extraction BiQuad 2", BIQUAD_SIZE, 0x9c), + + COEFF_RAM_CTL("DAC Treb Non Linear Function 1", COEFF_SIZE, 0xa1), + COEFF_RAM_CTL("DAC Treb Non Linear Function 2", COEFF_SIZE, 0xa2), + + COEFF_RAM_CTL("DAC Treb Limiter BiQuad", BIQUAD_SIZE, 0xa3), + + COEFF_RAM_CTL("DAC Treb Cut Off BiQuad", BIQUAD_SIZE, 0xa8), + + COEFF_RAM_CTL("DAC Treb Mix", COEFF_SIZE, 0xad), + + COEFF_RAM_CTL("DAC 3D", COEFF_SIZE, 0xae), + + COEFF_RAM_CTL("DAC 3D Mix", COEFF_SIZE, 0xaf), + + COEFF_RAM_CTL("DAC MBC 1 BiQuad 1", BIQUAD_SIZE, 0xb0), + COEFF_RAM_CTL("DAC MBC 1 BiQuad 2", BIQUAD_SIZE, 0xb5), + + COEFF_RAM_CTL("DAC MBC 2 BiQuad 1", BIQUAD_SIZE, 0xba), + COEFF_RAM_CTL("DAC MBC 2 BiQuad 2", BIQUAD_SIZE, 0xbf), + + COEFF_RAM_CTL("DAC MBC 3 BiQuad 1", BIQUAD_SIZE, 0xc4), + COEFF_RAM_CTL("DAC MBC 3 BiQuad 2", BIQUAD_SIZE, 0xc9), + + COEFF_RAM_CTL("Speaker Cascade 1 Left BiQuad 1", BIQUAD_SIZE, 0x00), + COEFF_RAM_CTL("Speaker Cascade 1 Left BiQuad 2", BIQUAD_SIZE, 0x05), + COEFF_RAM_CTL("Speaker Cascade 1 Left BiQuad 3", BIQUAD_SIZE, 0x0a), + COEFF_RAM_CTL("Speaker Cascade 1 Left BiQuad 4", BIQUAD_SIZE, 0x0f), + COEFF_RAM_CTL("Speaker Cascade 1 Left BiQuad 5", BIQUAD_SIZE, 0x14), + COEFF_RAM_CTL("Speaker Cascade 1 Left BiQuad 6", BIQUAD_SIZE, 0x19), + + COEFF_RAM_CTL("Speaker Cascade 1 Right BiQuad 1", BIQUAD_SIZE, 0x20), + COEFF_RAM_CTL("Speaker Cascade 1 Right BiQuad 2", BIQUAD_SIZE, 0x25), + COEFF_RAM_CTL("Speaker Cascade 1 Right BiQuad 3", BIQUAD_SIZE, 0x2a), + COEFF_RAM_CTL("Speaker Cascade 1 Right BiQuad 4", BIQUAD_SIZE, 0x2f), + COEFF_RAM_CTL("Speaker Cascade 1 Right BiQuad 5", BIQUAD_SIZE, 0x34), + COEFF_RAM_CTL("Speaker Cascade 1 Right BiQuad 6", BIQUAD_SIZE, 0x39), + + COEFF_RAM_CTL("Speaker Cascade 1 Left Prescale", COEFF_SIZE, 0x1f), + COEFF_RAM_CTL("Speaker Cascade 1 Right Prescale", COEFF_SIZE, 0x3f), + + COEFF_RAM_CTL("Speaker Cascade 2 Left BiQuad 1", BIQUAD_SIZE, 0x40), + COEFF_RAM_CTL("Speaker Cascade 2 Left BiQuad 2", BIQUAD_SIZE, 0x45), + COEFF_RAM_CTL("Speaker Cascade 2 Left BiQuad 3", BIQUAD_SIZE, 0x4a), + COEFF_RAM_CTL("Speaker Cascade 2 Left BiQuad 4", BIQUAD_SIZE, 0x4f), + COEFF_RAM_CTL("Speaker Cascade 2 Left BiQuad 5", BIQUAD_SIZE, 0x54), + COEFF_RAM_CTL("Speaker Cascade 2 Left BiQuad 6", BIQUAD_SIZE, 0x59), + + COEFF_RAM_CTL("Speaker Cascade 2 Right BiQuad 1", BIQUAD_SIZE, 0x60), + COEFF_RAM_CTL("Speaker Cascade 2 Right BiQuad 2", BIQUAD_SIZE, 0x65), + COEFF_RAM_CTL("Speaker Cascade 2 Right BiQuad 3", BIQUAD_SIZE, 0x6a), + COEFF_RAM_CTL("Speaker Cascade 2 Right BiQuad 4", BIQUAD_SIZE, 0x6f), + COEFF_RAM_CTL("Speaker Cascade 2 Right BiQuad 5", BIQUAD_SIZE, 0x74), + COEFF_RAM_CTL("Speaker Cascade 2 Right BiQuad 6", BIQUAD_SIZE, 0x79), + + COEFF_RAM_CTL("Speaker Cascade 2 Left Prescale", COEFF_SIZE, 0x5f), + COEFF_RAM_CTL("Speaker Cascade 2 Right Prescale", COEFF_SIZE, 0x7f), + + COEFF_RAM_CTL("Speaker Bass Extraction BiQuad 1", BIQUAD_SIZE, 0x80), + COEFF_RAM_CTL("Speaker Bass Extraction BiQuad 2", BIQUAD_SIZE, 0x85), + + COEFF_RAM_CTL("Speaker Bass Non Linear Function 1", COEFF_SIZE, 0x8a), + COEFF_RAM_CTL("Speaker Bass Non Linear Function 2", COEFF_SIZE, 0x8b), + + COEFF_RAM_CTL("Speaker Bass Limiter BiQuad", BIQUAD_SIZE, 0x8c), + + COEFF_RAM_CTL("Speaker Bass Cut Off BiQuad", BIQUAD_SIZE, 0x91), + + COEFF_RAM_CTL("Speaker Bass Mix", COEFF_SIZE, 0x96), + + COEFF_RAM_CTL("Speaker Treb Extraction BiQuad 1", BIQUAD_SIZE, 0x97), + COEFF_RAM_CTL("Speaker Treb Extraction BiQuad 2", BIQUAD_SIZE, 0x9c), + + COEFF_RAM_CTL("Speaker Treb Non Linear Function 1", COEFF_SIZE, 0xa1), + COEFF_RAM_CTL("Speaker Treb Non Linear Function 2", COEFF_SIZE, 0xa2), + + COEFF_RAM_CTL("Speaker Treb Limiter BiQuad", BIQUAD_SIZE, 0xa3), + + COEFF_RAM_CTL("Speaker Treb Cut Off BiQuad", BIQUAD_SIZE, 0xa8), + + COEFF_RAM_CTL("Speaker Treb Mix", COEFF_SIZE, 0xad), + + COEFF_RAM_CTL("Speaker 3D", COEFF_SIZE, 0xae), + + COEFF_RAM_CTL("Speaker 3D Mix", COEFF_SIZE, 0xaf), + + COEFF_RAM_CTL("Speaker MBC 1 BiQuad 1", BIQUAD_SIZE, 0xb0), + COEFF_RAM_CTL("Speaker MBC 1 BiQuad 2", BIQUAD_SIZE, 0xb5), + + COEFF_RAM_CTL("Speaker MBC 2 BiQuad 1", BIQUAD_SIZE, 0xba), + COEFF_RAM_CTL("Speaker MBC 2 BiQuad 2", BIQUAD_SIZE, 0xbf), + + COEFF_RAM_CTL("Speaker MBC 3 BiQuad 1", BIQUAD_SIZE, 0xc4), + COEFF_RAM_CTL("Speaker MBC 3 BiQuad 2", BIQUAD_SIZE, 0xc9), + + COEFF_RAM_CTL("Sub Cascade 1 Left BiQuad 1", BIQUAD_SIZE, 0x00), + COEFF_RAM_CTL("Sub Cascade 1 Left BiQuad 2", BIQUAD_SIZE, 0x05), + COEFF_RAM_CTL("Sub Cascade 1 Left BiQuad 3", BIQUAD_SIZE, 0x0a), + COEFF_RAM_CTL("Sub Cascade 1 Left BiQuad 4", BIQUAD_SIZE, 0x0f), + COEFF_RAM_CTL("Sub Cascade 1 Left BiQuad 5", BIQUAD_SIZE, 0x14), + COEFF_RAM_CTL("Sub Cascade 1 Left BiQuad 6", BIQUAD_SIZE, 0x19), + + COEFF_RAM_CTL("Sub Cascade 1 Right BiQuad 1", BIQUAD_SIZE, 0x20), + COEFF_RAM_CTL("Sub Cascade 1 Right BiQuad 2", BIQUAD_SIZE, 0x25), + COEFF_RAM_CTL("Sub Cascade 1 Right BiQuad 3", BIQUAD_SIZE, 0x2a), + COEFF_RAM_CTL("Sub Cascade 1 Right BiQuad 4", BIQUAD_SIZE, 0x2f), + COEFF_RAM_CTL("Sub Cascade 1 Right BiQuad 5", BIQUAD_SIZE, 0x34), + COEFF_RAM_CTL("Sub Cascade 1 Right BiQuad 6", BIQUAD_SIZE, 0x39), + + COEFF_RAM_CTL("Sub Cascade 1 Left Prescale", COEFF_SIZE, 0x1f), + COEFF_RAM_CTL("Sub Cascade 1 Right Prescale", COEFF_SIZE, 0x3f), + + COEFF_RAM_CTL("Sub Cascade 2 Left BiQuad 1", BIQUAD_SIZE, 0x40), + COEFF_RAM_CTL("Sub Cascade 2 Left BiQuad 2", BIQUAD_SIZE, 0x45), + COEFF_RAM_CTL("Sub Cascade 2 Left BiQuad 3", BIQUAD_SIZE, 0x4a), + COEFF_RAM_CTL("Sub Cascade 2 Left BiQuad 4", BIQUAD_SIZE, 0x4f), + COEFF_RAM_CTL("Sub Cascade 2 Left BiQuad 5", BIQUAD_SIZE, 0x54), + COEFF_RAM_CTL("Sub Cascade 2 Left BiQuad 6", BIQUAD_SIZE, 0x59), + + COEFF_RAM_CTL("Sub Cascade 2 Right BiQuad 1", BIQUAD_SIZE, 0x60), + COEFF_RAM_CTL("Sub Cascade 2 Right BiQuad 2", BIQUAD_SIZE, 0x65), + COEFF_RAM_CTL("Sub Cascade 2 Right BiQuad 3", BIQUAD_SIZE, 0x6a), + COEFF_RAM_CTL("Sub Cascade 2 Right BiQuad 4", BIQUAD_SIZE, 0x6f), + COEFF_RAM_CTL("Sub Cascade 2 Right BiQuad 5", BIQUAD_SIZE, 0x74), + COEFF_RAM_CTL("Sub Cascade 2 Right BiQuad 6", BIQUAD_SIZE, 0x79), + + COEFF_RAM_CTL("Sub Cascade 2 Left Prescale", COEFF_SIZE, 0x5f), + COEFF_RAM_CTL("Sub Cascade 2 Right Prescale", COEFF_SIZE, 0x7f), + + COEFF_RAM_CTL("Sub Bass Extraction BiQuad 1", BIQUAD_SIZE, 0x80), + COEFF_RAM_CTL("Sub Bass Extraction BiQuad 2", BIQUAD_SIZE, 0x85), + + COEFF_RAM_CTL("Sub Bass Non Linear Function 1", COEFF_SIZE, 0x8a), + COEFF_RAM_CTL("Sub Bass Non Linear Function 2", COEFF_SIZE, 0x8b), + + COEFF_RAM_CTL("Sub Bass Limiter BiQuad", BIQUAD_SIZE, 0x8c), + + COEFF_RAM_CTL("Sub Bass Cut Off BiQuad", BIQUAD_SIZE, 0x91), + + COEFF_RAM_CTL("Sub Bass Mix", COEFF_SIZE, 0x96), + + COEFF_RAM_CTL("Sub Treb Extraction BiQuad 1", BIQUAD_SIZE, 0x97), + COEFF_RAM_CTL("Sub Treb Extraction BiQuad 2", BIQUAD_SIZE, 0x9c), + + COEFF_RAM_CTL("Sub Treb Non Linear Function 1", COEFF_SIZE, 0xa1), + COEFF_RAM_CTL("Sub Treb Non Linear Function 2", COEFF_SIZE, 0xa2), + + COEFF_RAM_CTL("Sub Treb Limiter BiQuad", BIQUAD_SIZE, 0xa3), + + COEFF_RAM_CTL("Sub Treb Cut Off BiQuad", BIQUAD_SIZE, 0xa8), + + COEFF_RAM_CTL("Sub Treb Mix", COEFF_SIZE, 0xad), + + COEFF_RAM_CTL("Sub 3D", COEFF_SIZE, 0xae), + + COEFF_RAM_CTL("Sub 3D Mix", COEFF_SIZE, 0xaf), + + COEFF_RAM_CTL("Sub MBC 1 BiQuad 1", BIQUAD_SIZE, 0xb0), + COEFF_RAM_CTL("Sub MBC 1 BiQuad 2", BIQUAD_SIZE, 0xb5), + + COEFF_RAM_CTL("Sub MBC 2 BiQuad 1", BIQUAD_SIZE, 0xba), + COEFF_RAM_CTL("Sub MBC 2 BiQuad 2", BIQUAD_SIZE, 0xbf), + + COEFF_RAM_CTL("Sub MBC 3 BiQuad 1", BIQUAD_SIZE, 0xc4), + COEFF_RAM_CTL("Sub MBC 3 BiQuad 2", BIQUAD_SIZE, 0xc9), +}; + +static struct snd_soc_dapm_widget const tscs454_dapm_widgets[] = { + /* R_PLLCTL PG 0 ADDR 0x15 */ + SND_SOC_DAPM_SUPPLY("PLL 1 Power", R_PLLCTL, FB_PLLCTL_PU_PLL1, 0, + pll_power_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PLL 2 Power", R_PLLCTL, FB_PLLCTL_PU_PLL2, 0, + pll_power_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + /* R_I2SPINC0 PG 0 ADDR 0x22 */ + SND_SOC_DAPM_AIF_OUT("DAI 3 Out", "DAI 3 Capture", 0, + R_I2SPINC0, FB_I2SPINC0_SDO3TRI, 1), + SND_SOC_DAPM_AIF_OUT("DAI 2 Out", "DAI 2 Capture", 0, + R_I2SPINC0, FB_I2SPINC0_SDO2TRI, 1), + SND_SOC_DAPM_AIF_OUT("DAI 1 Out", "DAI 1 Capture", 0, + R_I2SPINC0, FB_I2SPINC0_SDO1TRI, 1), + /* R_PWRM0 PG 0 ADDR 0x33 */ + SND_SOC_DAPM_ADC("Input Processor Channel 3", NULL, + R_PWRM0, FB_PWRM0_INPROC3PU, 0), + SND_SOC_DAPM_ADC("Input Processor Channel 2", NULL, + R_PWRM0, FB_PWRM0_INPROC2PU, 0), + SND_SOC_DAPM_ADC("Input Processor Channel 1", NULL, + R_PWRM0, FB_PWRM0_INPROC1PU, 0), + SND_SOC_DAPM_ADC("Input Processor Channel 0", NULL, + R_PWRM0, FB_PWRM0_INPROC0PU, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias 2", + R_PWRM0, FB_PWRM0_MICB2PU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias 1", R_PWRM0, + FB_PWRM0_MICB1PU, 0, NULL, 0), + /* R_PWRM1 PG 0 ADDR 0x34 */ + SND_SOC_DAPM_SUPPLY("Sub Power", R_PWRM1, FB_PWRM1_SUBPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Headphone Left Power", + R_PWRM1, FB_PWRM1_HPLPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Headphone Right Power", + R_PWRM1, FB_PWRM1_HPRPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Speaker Left Power", + R_PWRM1, FB_PWRM1_SPKLPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Speaker Right Power", + R_PWRM1, FB_PWRM1_SPKRPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Differential Input 2 Power", + R_PWRM1, FB_PWRM1_D2S2PU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Differential Input 1 Power", + R_PWRM1, FB_PWRM1_D2S1PU, 0, NULL, 0), + /* R_PWRM2 PG 0 ADDR 0x35 */ + SND_SOC_DAPM_SUPPLY("DAI 3 Out Power", + R_PWRM2, FB_PWRM2_I2S3OPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAI 2 Out Power", + R_PWRM2, FB_PWRM2_I2S2OPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAI 1 Out Power", + R_PWRM2, FB_PWRM2_I2S1OPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAI 3 In Power", + R_PWRM2, FB_PWRM2_I2S3IPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAI 2 In Power", + R_PWRM2, FB_PWRM2_I2S2IPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DAI 1 In Power", + R_PWRM2, FB_PWRM2_I2S1IPU, 0, NULL, 0), + /* R_PWRM3 PG 0 ADDR 0x36 */ + SND_SOC_DAPM_SUPPLY("Line Out Left Power", + R_PWRM3, FB_PWRM3_LLINEPU, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Line Out Right Power", + R_PWRM3, FB_PWRM3_RLINEPU, 0, NULL, 0), + /* R_PWRM4 PG 0 ADDR 0x37 */ + SND_SOC_DAPM_DAC("Sub", NULL, R_PWRM4, FB_PWRM4_OPSUBPU, 0), + SND_SOC_DAPM_DAC("DAC Left", NULL, R_PWRM4, FB_PWRM4_OPDACLPU, 0), + SND_SOC_DAPM_DAC("DAC Right", NULL, R_PWRM4, FB_PWRM4_OPDACRPU, 0), + SND_SOC_DAPM_DAC("ClassD Left", NULL, R_PWRM4, FB_PWRM4_OPSPKLPU, 0), + SND_SOC_DAPM_DAC("ClassD Right", NULL, R_PWRM4, FB_PWRM4_OPSPKRPU, 0), + /* R_AUDIOMUX1 PG 0 ADDR 0x3A */ + SND_SOC_DAPM_MUX("DAI 2 Out Mux", SND_SOC_NOPM, 0, 0, + &dai2_mux_dapm_enum), + SND_SOC_DAPM_MUX("DAI 1 Out Mux", SND_SOC_NOPM, 0, 0, + &dai1_mux_dapm_enum), + /* R_AUDIOMUX2 PG 0 ADDR 0x3B */ + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, + &dac_mux_dapm_enum), + SND_SOC_DAPM_MUX("DAI 3 Out Mux", SND_SOC_NOPM, 0, 0, + &dai3_mux_dapm_enum), + /* R_AUDIOMUX3 PG 0 ADDR 0x3C */ + SND_SOC_DAPM_MUX("Sub Mux", SND_SOC_NOPM, 0, 0, + &sub_mux_dapm_enum), + SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0, + &classd_mux_dapm_enum), + /* R_HSDCTL1 PG 1 ADDR 0x01 */ + SND_SOC_DAPM_SUPPLY("GHS Detect Power", R_HSDCTL1, + FB_HSDCTL1_CON_DET_PWD, 1, NULL, 0), + /* R_CH0AIC PG 1 ADDR 0x06 */ + SND_SOC_DAPM_MUX("Input Boost Channel 0 Mux", SND_SOC_NOPM, 0, 0, + &in_bst_mux_ch0_dapm_enum), + SND_SOC_DAPM_MUX("ADC Channel 0 Mux", SND_SOC_NOPM, 0, 0, + &adc_mux_ch0_dapm_enum), + SND_SOC_DAPM_MUX("Input Processor Channel 0 Mux", SND_SOC_NOPM, 0, 0, + &in_proc_mux_ch0_dapm_enum), + /* R_CH1AIC PG 1 ADDR 0x07 */ + SND_SOC_DAPM_MUX("Input Boost Channel 1 Mux", SND_SOC_NOPM, 0, 0, + &in_bst_mux_ch1_dapm_enum), + SND_SOC_DAPM_MUX("ADC Channel 1 Mux", SND_SOC_NOPM, 0, 0, + &adc_mux_ch1_dapm_enum), + SND_SOC_DAPM_MUX("Input Processor Channel 1 Mux", SND_SOC_NOPM, 0, 0, + &in_proc_mux_ch1_dapm_enum), + /* Virtual */ + SND_SOC_DAPM_AIF_IN("DAI 3 In", "DAI 3 Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DAI 2 In", "DAI 2 Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DAI 1 In", "DAI 1 Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("PLLs", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("Sub Out"), + SND_SOC_DAPM_OUTPUT("Headphone Left"), + SND_SOC_DAPM_OUTPUT("Headphone Right"), + SND_SOC_DAPM_OUTPUT("Speaker Left"), + SND_SOC_DAPM_OUTPUT("Speaker Right"), + SND_SOC_DAPM_OUTPUT("Line Out Left"), + SND_SOC_DAPM_OUTPUT("Line Out Right"), + SND_SOC_DAPM_INPUT("D2S 2"), + SND_SOC_DAPM_INPUT("D2S 1"), + SND_SOC_DAPM_INPUT("Line In 1 Left"), + SND_SOC_DAPM_INPUT("Line In 1 Right"), + SND_SOC_DAPM_INPUT("Line In 2 Left"), + SND_SOC_DAPM_INPUT("Line In 2 Right"), + SND_SOC_DAPM_INPUT("Line In 3 Left"), + SND_SOC_DAPM_INPUT("Line In 3 Right"), + SND_SOC_DAPM_INPUT("DMic 1"), + SND_SOC_DAPM_INPUT("DMic 2"), + + SND_SOC_DAPM_MUX("CH 0_1 Mux", SND_SOC_NOPM, 0, 0, + &ch_0_1_mux_dapm_enum), + SND_SOC_DAPM_MUX("CH 2_3 Mux", SND_SOC_NOPM, 0, 0, + &ch_2_3_mux_dapm_enum), + SND_SOC_DAPM_MUX("CH 4_5 Mux", SND_SOC_NOPM, 0, 0, + &ch_4_5_mux_dapm_enum), +}; + +static struct snd_soc_dapm_route const tscs454_intercon[] = { + /* PLLs */ + {"PLLs", NULL, "PLL 1 Power", pll_connected}, + {"PLLs", NULL, "PLL 2 Power", pll_connected}, + /* Inputs */ + {"DAI 3 In", NULL, "DAI 3 In Power"}, + {"DAI 2 In", NULL, "DAI 2 In Power"}, + {"DAI 1 In", NULL, "DAI 1 In Power"}, + /* Outputs */ + {"DAI 3 Out", NULL, "DAI 3 Out Power"}, + {"DAI 2 Out", NULL, "DAI 2 Out Power"}, + {"DAI 1 Out", NULL, "DAI 1 Out Power"}, + /* Ch Muxing */ + {"CH 0_1 Mux", "DAI 1", "DAI 1 In"}, + {"CH 0_1 Mux", "TDM 0_1", "DAI 1 In"}, + {"CH 2_3 Mux", "DAI 2", "DAI 2 In"}, + {"CH 2_3 Mux", "TDM 2_3", "DAI 1 In"}, + {"CH 4_5 Mux", "DAI 3", "DAI 2 In"}, + {"CH 4_5 Mux", "TDM 4_5", "DAI 1 In"}, + /* In/Out Muxing */ + {"DAI 1 Out Mux", "CH 0_1", "CH 0_1 Mux"}, + {"DAI 1 Out Mux", "CH 2_3", "CH 2_3 Mux"}, + {"DAI 1 Out Mux", "CH 4_5", "CH 4_5 Mux"}, + {"DAI 2 Out Mux", "CH 0_1", "CH 0_1 Mux"}, + {"DAI 2 Out Mux", "CH 2_3", "CH 2_3 Mux"}, + {"DAI 2 Out Mux", "CH 4_5", "CH 4_5 Mux"}, + {"DAI 3 Out Mux", "CH 0_1", "CH 0_1 Mux"}, + {"DAI 3 Out Mux", "CH 2_3", "CH 2_3 Mux"}, + {"DAI 3 Out Mux", "CH 4_5", "CH 4_5 Mux"}, + /****************** + * Playback Paths * + ******************/ + /* DAC Path */ + {"DAC Mux", "CH 4_5", "CH 4_5 Mux"}, + {"DAC Mux", "CH 2_3", "CH 2_3 Mux"}, + {"DAC Mux", "CH 0_1", "CH 0_1 Mux"}, + {"DAC Left", NULL, "DAC Mux"}, + {"DAC Right", NULL, "DAC Mux"}, + {"DAC Left", NULL, "PLLs"}, + {"DAC Right", NULL, "PLLs"}, + {"Headphone Left", NULL, "Headphone Left Power"}, + {"Headphone Right", NULL, "Headphone Right Power"}, + {"Headphone Left", NULL, "DAC Left"}, + {"Headphone Right", NULL, "DAC Right"}, + /* Line Out */ + {"Line Out Left", NULL, "Line Out Left Power"}, + {"Line Out Right", NULL, "Line Out Right Power"}, + {"Line Out Left", NULL, "DAC Left"}, + {"Line Out Right", NULL, "DAC Right"}, + /* ClassD Path */ + {"Speaker Mux", "CH 4_5", "CH 4_5 Mux"}, + {"Speaker Mux", "CH 2_3", "CH 2_3 Mux"}, + {"Speaker Mux", "CH 0_1", "CH 0_1 Mux"}, + {"ClassD Left", NULL, "Speaker Mux"}, + {"ClassD Right", NULL, "Speaker Mux"}, + {"ClassD Left", NULL, "PLLs"}, + {"ClassD Right", NULL, "PLLs"}, + {"Speaker Left", NULL, "Speaker Left Power"}, + {"Speaker Right", NULL, "Speaker Right Power"}, + {"Speaker Left", NULL, "ClassD Left"}, + {"Speaker Right", NULL, "ClassD Right"}, + /* Sub Path */ + {"Sub Mux", "CH 4", "CH 4_5 Mux"}, + {"Sub Mux", "CH 5", "CH 4_5 Mux"}, + {"Sub Mux", "CH 4 + 5", "CH 4_5 Mux"}, + {"Sub Mux", "CH 2", "CH 2_3 Mux"}, + {"Sub Mux", "CH 3", "CH 2_3 Mux"}, + {"Sub Mux", "CH 2 + 3", "CH 2_3 Mux"}, + {"Sub Mux", "CH 0", "CH 0_1 Mux"}, + {"Sub Mux", "CH 1", "CH 0_1 Mux"}, + {"Sub Mux", "CH 0 + 1", "CH 0_1 Mux"}, + {"Sub Mux", "ADC/DMic 1 Left", "Input Processor Channel 0"}, + {"Sub Mux", "ADC/DMic 1 Right", "Input Processor Channel 1"}, + {"Sub Mux", "ADC/DMic 1 Left Plus Right", "Input Processor Channel 0"}, + {"Sub Mux", "ADC/DMic 1 Left Plus Right", "Input Processor Channel 1"}, + {"Sub Mux", "DMic 2 Left", "DMic 2"}, + {"Sub Mux", "DMic 2 Right", "DMic 2"}, + {"Sub Mux", "DMic 2 Left Plus Right", "DMic 2"}, + {"Sub Mux", "ClassD Left", "ClassD Left"}, + {"Sub Mux", "ClassD Right", "ClassD Right"}, + {"Sub Mux", "ClassD Left Plus Right", "ClassD Left"}, + {"Sub Mux", "ClassD Left Plus Right", "ClassD Right"}, + {"Sub", NULL, "Sub Mux"}, + {"Sub", NULL, "PLLs"}, + {"Sub Out", NULL, "Sub Power"}, + {"Sub Out", NULL, "Sub"}, + /***************** + * Capture Paths * + *****************/ + {"Input Boost Channel 0 Mux", "Input 3", "Line In 3 Left"}, + {"Input Boost Channel 0 Mux", "Input 2", "Line In 2 Left"}, + {"Input Boost Channel 0 Mux", "Input 1", "Line In 1 Left"}, + {"Input Boost Channel 0 Mux", "D2S", "D2S 1"}, + + {"Input Boost Channel 1 Mux", "Input 3", "Line In 3 Right"}, + {"Input Boost Channel 1 Mux", "Input 2", "Line In 2 Right"}, + {"Input Boost Channel 1 Mux", "Input 1", "Line In 1 Right"}, + {"Input Boost Channel 1 Mux", "D2S", "D2S 2"}, + + {"ADC Channel 0 Mux", "Input 3 Boost Bypass", "Line In 3 Left"}, + {"ADC Channel 0 Mux", "Input 2 Boost Bypass", "Line In 2 Left"}, + {"ADC Channel 0 Mux", "Input 1 Boost Bypass", "Line In 1 Left"}, + {"ADC Channel 0 Mux", "Input Boost", "Input Boost Channel 0 Mux"}, + + {"ADC Channel 1 Mux", "Input 3 Boost Bypass", "Line In 3 Right"}, + {"ADC Channel 1 Mux", "Input 2 Boost Bypass", "Line In 2 Right"}, + {"ADC Channel 1 Mux", "Input 1 Boost Bypass", "Line In 1 Right"}, + {"ADC Channel 1 Mux", "Input Boost", "Input Boost Channel 1 Mux"}, + + {"Input Processor Channel 0 Mux", "ADC", "ADC Channel 0 Mux"}, + {"Input Processor Channel 0 Mux", "DMic", "DMic 1"}, + + {"Input Processor Channel 0", NULL, "PLLs"}, + {"Input Processor Channel 0", NULL, "Input Processor Channel 0 Mux"}, + + {"Input Processor Channel 1 Mux", "ADC", "ADC Channel 1 Mux"}, + {"Input Processor Channel 1 Mux", "DMic", "DMic 1"}, + + {"Input Processor Channel 1", NULL, "PLLs"}, + {"Input Processor Channel 1", NULL, "Input Processor Channel 1 Mux"}, + + {"Input Processor Channel 2", NULL, "PLLs"}, + {"Input Processor Channel 2", NULL, "DMic 2"}, + + {"Input Processor Channel 3", NULL, "PLLs"}, + {"Input Processor Channel 3", NULL, "DMic 2"}, + + {"DAI 1 Out Mux", "ADC/DMic 1", "Input Processor Channel 0"}, + {"DAI 1 Out Mux", "ADC/DMic 1", "Input Processor Channel 1"}, + {"DAI 1 Out Mux", "DMic 2", "Input Processor Channel 2"}, + {"DAI 1 Out Mux", "DMic 2", "Input Processor Channel 3"}, + + {"DAI 2 Out Mux", "ADC/DMic 1", "Input Processor Channel 0"}, + {"DAI 2 Out Mux", "ADC/DMic 1", "Input Processor Channel 1"}, + {"DAI 2 Out Mux", "DMic 2", "Input Processor Channel 2"}, + {"DAI 2 Out Mux", "DMic 2", "Input Processor Channel 3"}, + + {"DAI 3 Out Mux", "ADC/DMic 1", "Input Processor Channel 0"}, + {"DAI 3 Out Mux", "ADC/DMic 1", "Input Processor Channel 1"}, + {"DAI 3 Out Mux", "DMic 2", "Input Processor Channel 2"}, + {"DAI 3 Out Mux", "DMic 2", "Input Processor Channel 3"}, + + {"DAI 1 Out", NULL, "DAI 1 Out Mux"}, + {"DAI 2 Out", NULL, "DAI 2 Out Mux"}, + {"DAI 3 Out", NULL, "DAI 3 Out Mux"}, +}; + +/* This is used when BCLK is sourcing the PLLs */ +static int tscs454_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + unsigned int val; + int bclk_dai; + int ret; + + dev_dbg(component->dev, "%s(): freq = %u\n", __func__, freq); + + ret = snd_soc_component_read(component, R_PLLCTL, &val); + if (ret < 0) + return ret; + + bclk_dai = (val & FM_PLLCTL_BCLKSEL) >> FB_PLLCTL_BCLKSEL; + if (bclk_dai != dai->id) + return 0; + + tscs454->bclk_freq = freq; + return set_sysclk(component); +} + +static int tscs454_set_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + unsigned int mask; + int ret; + struct snd_soc_component *component = dai->component; + unsigned int val; + int shift; + + dev_dbg(component->dev, "set_bclk_ratio() id = %d ratio = %u\n", + dai->id, ratio); + + switch (dai->id) { + case TSCS454_DAI1_ID: + mask = FM_I2SCMC_BCMP1; + shift = FB_I2SCMC_BCMP1; + break; + case TSCS454_DAI2_ID: + mask = FM_I2SCMC_BCMP2; + shift = FB_I2SCMC_BCMP2; + break; + case TSCS454_DAI3_ID: + mask = FM_I2SCMC_BCMP3; + shift = FB_I2SCMC_BCMP3; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unknown audio interface (%d)\n", ret); + return ret; + } + + switch (ratio) { + case 32: + val = I2SCMC_BCMP_32X; + break; + case 40: + val = I2SCMC_BCMP_40X; + break; + case 64: + val = I2SCMC_BCMP_64X; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unsupported bclk ratio (%d)\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, + R_I2SCMC, mask, val << shift); + if (ret < 0) { + dev_err(component->dev, + "Failed to set DAI BCLK ratio (%d)\n", ret); + return ret; + } + + return 0; +} + +static inline int set_aif_master_from_fmt(struct snd_soc_component *component, + struct aif *aif, unsigned int fmt) +{ + int ret; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aif->master = true; + break; + case SND_SOC_DAIFMT_CBS_CFS: + aif->master = false; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unsupported format (%d)\n", ret); + return ret; + } + + return 0; +} + +static inline int set_aif_tdm_delay(struct snd_soc_component *component, + unsigned int dai_id, bool delay) +{ + unsigned int reg; + int ret; + + switch (dai_id) { + case TSCS454_DAI1_ID: + reg = R_TDMCTL0; + break; + case TSCS454_DAI2_ID: + reg = R_PCMP2CTL0; + break; + case TSCS454_DAI3_ID: + reg = R_PCMP3CTL0; + break; + default: + ret = -EINVAL; + dev_err(component->dev, + "DAI %d unknown (%d)\n", dai_id + 1, ret); + return ret; + } + ret = snd_soc_component_update_bits(component, + reg, FM_TDMCTL0_BDELAY, delay); + if (ret < 0) { + dev_err(component->dev, "Failed to setup tdm format (%d)\n", + ret); + return ret; + } + + return 0; +} + +static inline int set_aif_format_from_fmt(struct snd_soc_component *component, + unsigned int dai_id, unsigned int fmt) +{ + unsigned int reg; + unsigned int val; + int ret; + + switch (dai_id) { + case TSCS454_DAI1_ID: + reg = R_I2SP1CTL; + break; + case TSCS454_DAI2_ID: + reg = R_I2SP2CTL; + break; + case TSCS454_DAI3_ID: + reg = R_I2SP3CTL; + break; + default: + ret = -EINVAL; + dev_err(component->dev, + "DAI %d unknown (%d)\n", dai_id + 1, ret); + return ret; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = FV_FORMAT_RIGHT; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = FV_FORMAT_LEFT; + break; + case SND_SOC_DAIFMT_I2S: + val = FV_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + ret = set_aif_tdm_delay(component, dai_id, true); + if (ret < 0) + return ret; + val = FV_FORMAT_TDM; + break; + case SND_SOC_DAIFMT_DSP_B: + ret = set_aif_tdm_delay(component, dai_id, false); + if (ret < 0) + return ret; + val = FV_FORMAT_TDM; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Format unsupported (%d)\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, + reg, FM_I2SPCTL_FORMAT, val); + if (ret < 0) { + dev_err(component->dev, "Failed to set DAI %d format (%d)\n", + dai_id + 1, ret); + return ret; + } + + return 0; +} + +static inline int +set_aif_clock_format_from_fmt(struct snd_soc_component *component, + unsigned int dai_id, unsigned int fmt) +{ + unsigned int reg; + unsigned int val; + int ret; + + switch (dai_id) { + case TSCS454_DAI1_ID: + reg = R_I2SP1CTL; + break; + case TSCS454_DAI2_ID: + reg = R_I2SP2CTL; + break; + case TSCS454_DAI3_ID: + reg = R_I2SP3CTL; + break; + default: + ret = -EINVAL; + dev_err(component->dev, + "DAI %d unknown (%d)\n", dai_id + 1, ret); + return ret; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = FV_BCLKP_NOT_INVERTED | FV_LRCLKP_NOT_INVERTED; + break; + case SND_SOC_DAIFMT_NB_IF: + val = FV_BCLKP_NOT_INVERTED | FV_LRCLKP_INVERTED; + break; + case SND_SOC_DAIFMT_IB_NF: + val = FV_BCLKP_INVERTED | FV_LRCLKP_NOT_INVERTED; + break; + case SND_SOC_DAIFMT_IB_IF: + val = FV_BCLKP_INVERTED | FV_LRCLKP_INVERTED; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Format unknown (%d)\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, reg, + FM_I2SPCTL_BCLKP | FM_I2SPCTL_LRCLKP, val); + if (ret < 0) { + dev_err(component->dev, + "Failed to set clock polarity for DAI%d (%d)\n", + dai_id + 1, ret); + return ret; + } + + return 0; +} + +static int tscs454_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + struct aif *aif = &tscs454->aifs[dai->id]; + int ret; + + ret = set_aif_master_from_fmt(component, aif, fmt); + if (ret < 0) + return ret; + + ret = set_aif_format_from_fmt(component, dai->id, fmt); + if (ret < 0) + return ret; + + ret = set_aif_clock_format_from_fmt(component, dai->id, fmt); + if (ret < 0) + return ret; + + return 0; +} + +static int tscs454_dai1_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int val; + int ret; + + if (!slots) + return 0; + + if (tx_mask >= (1 << slots) || rx_mask >= (1 << slots)) { + ret = -EINVAL; + dev_err(component->dev, "Invalid TDM slot mask (%d)\n", ret); + return ret; + } + + switch (slots) { + case 2: + val = FV_TDMSO_2 | FV_TDMSI_2; + break; + case 4: + val = FV_TDMSO_4 | FV_TDMSI_4; + break; + case 6: + val = FV_TDMSO_6 | FV_TDMSI_6; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Invalid number of slots (%d)\n", ret); + return ret; + } + + switch (slot_width) { + case 16: + val = val | FV_TDMDSS_16; + break; + case 24: + val = val | FV_TDMDSS_24; + break; + case 32: + val = val | FV_TDMDSS_32; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Invalid TDM slot width (%d)\n", ret); + return ret; + } + ret = snd_soc_component_write(component, R_TDMCTL1, val); + if (ret < 0) { + dev_err(component->dev, "Failed to set slots (%d)\n", ret); + return ret; + } + + return 0; +} + +static int tscs454_dai23_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_component *component = dai->component; + unsigned int reg; + unsigned int val; + int ret; + + if (!slots) + return 0; + + if (tx_mask >= (1 << slots) || rx_mask >= (1 << slots)) { + ret = -EINVAL; + dev_err(component->dev, "Invalid TDM slot mask (%d)\n", ret); + return ret; + } + + switch (dai->id) { + case TSCS454_DAI2_ID: + reg = R_PCMP2CTL1; + break; + case TSCS454_DAI3_ID: + reg = R_PCMP3CTL1; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unrecognized interface %d (%d)\n", + dai->id, ret); + return ret; + } + + switch (slots) { + case 1: + val = FV_PCMSOP_1 | FV_PCMSIP_1; + break; + case 2: + val = FV_PCMSOP_2 | FV_PCMSIP_2; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Invalid number of slots (%d)\n", ret); + return ret; + } + + switch (slot_width) { + case 16: + val = val | FV_PCMDSSP_16; + break; + case 24: + val = val | FV_PCMDSSP_24; + break; + case 32: + val = val | FV_PCMDSSP_32; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Invalid TDM slot width (%d)\n", ret); + return ret; + } + ret = snd_soc_component_write(component, reg, val); + if (ret < 0) { + dev_err(component->dev, "Failed to set slots (%d)\n", ret); + return ret; + } + + return 0; +} + +static int set_aif_fs(struct snd_soc_component *component, + unsigned int id, + unsigned int rate) +{ + unsigned int reg; + unsigned int br; + unsigned int bm; + int ret; + + switch (rate) { + case 8000: + br = FV_I2SMBR_32; + bm = FV_I2SMBM_0PT25; + break; + case 16000: + br = FV_I2SMBR_32; + bm = FV_I2SMBM_0PT5; + break; + case 24000: + br = FV_I2SMBR_48; + bm = FV_I2SMBM_0PT5; + break; + case 32000: + br = FV_I2SMBR_32; + bm = FV_I2SMBM_1; + break; + case 48000: + br = FV_I2SMBR_48; + bm = FV_I2SMBM_1; + break; + case 96000: + br = FV_I2SMBR_48; + bm = FV_I2SMBM_2; + break; + case 11025: + br = FV_I2SMBR_44PT1; + bm = FV_I2SMBM_0PT25; + break; + case 22050: + br = FV_I2SMBR_44PT1; + bm = FV_I2SMBM_0PT5; + break; + case 44100: + br = FV_I2SMBR_44PT1; + bm = FV_I2SMBM_1; + break; + case 88200: + br = FV_I2SMBR_44PT1; + bm = FV_I2SMBM_2; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unsupported sample rate (%d)\n", ret); + return ret; + } + + switch (id) { + case TSCS454_DAI1_ID: + reg = R_I2S1MRATE; + break; + case TSCS454_DAI2_ID: + reg = R_I2S2MRATE; + break; + case TSCS454_DAI3_ID: + reg = R_I2S3MRATE; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "DAI ID not recognized (%d)\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, reg, + FM_I2SMRATE_I2SMBR | FM_I2SMRATE_I2SMBM, br|bm); + if (ret < 0) { + dev_err(component->dev, + "Failed to update register (%d)\n", ret); + return ret; + } + + return 0; +} + +static int set_aif_sample_format(struct snd_soc_component *component, + snd_pcm_format_t format, + int aif_id) +{ + unsigned int reg; + unsigned int width; + int ret; + + switch (snd_pcm_format_width(format)) { + case 16: + width = FV_WL_16; + break; + case 20: + width = FV_WL_20; + break; + case 24: + width = FV_WL_24; + break; + case 32: + width = FV_WL_32; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Unsupported format width (%d)\n", ret); + return ret; + } + + switch (aif_id) { + case TSCS454_DAI1_ID: + reg = R_I2SP1CTL; + break; + case TSCS454_DAI2_ID: + reg = R_I2SP2CTL; + break; + case TSCS454_DAI3_ID: + reg = R_I2SP3CTL; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "AIF ID not recognized (%d)\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, + reg, FM_I2SPCTL_WL, width); + if (ret < 0) { + dev_err(component->dev, + "Failed to set sample width (%d)\n", ret); + return ret; + } + + return 0; +} + +static int tscs454_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + unsigned int fs = params_rate(params); + struct aif *aif = &tscs454->aifs[dai->id]; + unsigned int val; + int ret; + + mutex_lock(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d fs = %u\n", __func__, + aif->id, fs); + + if (!aif_active(&tscs454->aifs_status, aif->id)) { + if (PLL_44_1K_RATE % fs) + aif->pll = &tscs454->pll1; + else + aif->pll = &tscs454->pll2; + + dev_dbg(component->dev, "Reserving pll %d for aif %d\n", + aif->pll->id, aif->id); + + reserve_pll(aif->pll); + } + + if (!aifs_active(&tscs454->aifs_status)) { /* First active aif */ + ret = snd_soc_component_read(component, R_ISRC, &val); + if (ret < 0) + goto exit; + + if ((val & FM_ISRC_IBR) == FV_IBR_48) + tscs454->internal_rate.pll = &tscs454->pll1; + else + tscs454->internal_rate.pll = &tscs454->pll2; + + dev_dbg(component->dev, "Reserving pll %d for ir\n", + tscs454->internal_rate.pll->id); + + reserve_pll(tscs454->internal_rate.pll); + } + + ret = set_aif_fs(component, aif->id, fs); + if (ret < 0) { + dev_err(component->dev, "Failed to set aif fs (%d)\n", ret); + goto exit; + } + + ret = set_aif_sample_format(component, params_format(params), aif->id); + if (ret < 0) { + dev_err(component->dev, + "Failed to set aif sample format (%d)\n", ret); + goto exit; + } + + set_aif_status_active(&tscs454->aifs_status, aif->id, + substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + dev_dbg(component->dev, "Set aif %d active. Streams status is 0x%x\n", + aif->id, tscs454->aifs_status.streams); + + ret = 0; +exit: + mutex_unlock(&tscs454->aifs_status_lock); + + return ret; +} + +static int tscs454_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + struct aif *aif = &tscs454->aifs[dai->id]; + + return aif_free(component, aif, + substream->stream == SNDRV_PCM_STREAM_PLAYBACK); +} + +static int tscs454_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct snd_soc_component *component = dai->component; + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + struct aif *aif = &tscs454->aifs[dai->id]; + + ret = aif_prepare(component, aif); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_dai_ops const tscs454_dai1_ops = { + .set_sysclk = tscs454_set_sysclk, + .set_bclk_ratio = tscs454_set_bclk_ratio, + .set_fmt = tscs454_set_dai_fmt, + .set_tdm_slot = tscs454_dai1_set_tdm_slot, + .hw_params = tscs454_hw_params, + .hw_free = tscs454_hw_free, + .prepare = tscs454_prepare, +}; + +static struct snd_soc_dai_ops const tscs454_dai23_ops = { + .set_sysclk = tscs454_set_sysclk, + .set_bclk_ratio = tscs454_set_bclk_ratio, + .set_fmt = tscs454_set_dai_fmt, + .set_tdm_slot = tscs454_dai23_set_tdm_slot, + .hw_params = tscs454_hw_params, + .hw_free = tscs454_hw_free, + .prepare = tscs454_prepare, +}; + +static int tscs454_probe(struct snd_soc_component *component) +{ + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + unsigned int val; + int ret = 0; + + switch (tscs454->sysclk_src_id) { + case PLL_INPUT_XTAL: + val = FV_PLLISEL_XTAL; + break; + case PLL_INPUT_MCLK1: + val = FV_PLLISEL_MCLK1; + break; + case PLL_INPUT_MCLK2: + val = FV_PLLISEL_MCLK2; + break; + case PLL_INPUT_BCLK: + val = FV_PLLISEL_BCLK; + break; + default: + ret = -EINVAL; + dev_err(component->dev, "Invalid sysclk src id (%d)\n", ret); + return ret; + } + + ret = snd_soc_component_update_bits(component, R_PLLCTL, + FM_PLLCTL_PLLISEL, val); + if (ret < 0) { + dev_err(component->dev, "Failed to set PLL input (%d)\n", ret); + return ret; + } + + if (tscs454->sysclk_src_id < PLL_INPUT_BCLK) + ret = set_sysclk(component); + + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_tscs454 = { + .probe = tscs454_probe, + .dapm_widgets = tscs454_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tscs454_dapm_widgets), + .dapm_routes = tscs454_intercon, + .num_dapm_routes = ARRAY_SIZE(tscs454_intercon), + .controls = tscs454_snd_controls, + .num_controls = ARRAY_SIZE(tscs454_snd_controls), + .endianness = 1, +}; + +#define TSCS454_RATES SNDRV_PCM_RATE_8000_96000 + +#define TSCS454_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE \ + | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver tscs454_dais[] = { + { + .name = "tscs454-dai1", + .id = TSCS454_DAI1_ID, + .playback = { + .stream_name = "DAI 1 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = TSCS454_RATES, + .formats = TSCS454_FORMATS,}, + .capture = { + .stream_name = "DAI 1 Capture", + .channels_min = 1, + .channels_max = 6, + .rates = TSCS454_RATES, + .formats = TSCS454_FORMATS,}, + .ops = &tscs454_dai1_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, + }, + { + .name = "tscs454-dai2", + .id = TSCS454_DAI2_ID, + .playback = { + .stream_name = "DAI 2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TSCS454_RATES, + .formats = TSCS454_FORMATS,}, + .capture = { + .stream_name = "DAI 2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TSCS454_RATES, + .formats = TSCS454_FORMATS,}, + .ops = &tscs454_dai23_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, + }, + { + .name = "tscs454-dai3", + .id = TSCS454_DAI3_ID, + .playback = { + .stream_name = "DAI 3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TSCS454_RATES, + .formats = TSCS454_FORMATS,}, + .capture = { + .stream_name = "DAI 3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TSCS454_RATES, + .formats = TSCS454_FORMATS,}, + .ops = &tscs454_dai23_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, + .symmetric_samplebits = 1, + }, +}; + +static char const * const src_names[] = { + "xtal", "mclk1", "mclk2", "bclk"}; + +static int tscs454_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tscs454 *tscs454; + int src; + int ret; + + tscs454 = devm_kzalloc(&i2c->dev, sizeof(*tscs454), GFP_KERNEL); + if (!tscs454) + return -ENOMEM; + + ret = tscs454_data_init(tscs454, i2c); + if (ret < 0) + return ret; + + i2c_set_clientdata(i2c, tscs454); + + for (src = PLL_INPUT_XTAL; src < PLL_INPUT_BCLK; src++) { + tscs454->sysclk = devm_clk_get(&i2c->dev, src_names[src]); + if (!IS_ERR(tscs454->sysclk)) { + break; + } else if (PTR_ERR(tscs454->sysclk) != -ENOENT) { + ret = PTR_ERR(tscs454->sysclk); + dev_err(&i2c->dev, "Failed to get sysclk (%d)\n", ret); + return ret; + } + } + dev_dbg(&i2c->dev, "PLL input is %s\n", src_names[src]); + tscs454->sysclk_src_id = src; + + ret = regmap_write(tscs454->regmap, + R_RESET, FV_RESET_PWR_ON_DEFAULTS); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to reset the component (%d)\n", ret); + return ret; + } + regcache_mark_dirty(tscs454->regmap); + + ret = regmap_register_patch(tscs454->regmap, tscs454_patch, + ARRAY_SIZE(tscs454_patch)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to apply patch (%d)\n", ret); + return ret; + } + /* Sync pg sel reg with cache */ + regmap_write(tscs454->regmap, R_PAGESEL, 0x00); + + ret = snd_soc_register_component(&i2c->dev, &soc_component_dev_tscs454, + tscs454_dais, ARRAY_SIZE(tscs454_dais)); + if (ret) { + dev_err(&i2c->dev, "Failed to register component (%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct i2c_device_id tscs454_i2c_id[] = { + { "tscs454", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tscs454_i2c_id); + +static const struct of_device_id tscs454_of_match[] = { + { .compatible = "tempo,tscs454", }, + { } +}; +MODULE_DEVICE_TABLE(of, tscs454_of_match); + +static struct i2c_driver tscs454_i2c_driver = { + .driver = { + .name = "tscs454", + .of_match_table = tscs454_of_match, + }, + .probe = tscs454_i2c_probe, + .id_table = tscs454_i2c_id, +}; + +module_i2c_driver(tscs454_i2c_driver); + +MODULE_AUTHOR("Tempo Semiconductor + +#ifndef __REDWOODPUBLIC_H__ +#define __REDWOODPUBLIC_H__ + +#define VIRT_BASE 0x00 +#define PAGE_LEN 0x100 +#define VIRT_PAGE_BASE(page) (VIRT_BASE + (PAGE_LEN * page)) +#define VIRT_ADDR(page, address) (VIRT_PAGE_BASE(page) + address) +#define ADDR(page, virt_address) (virt_address - VIRT_PAGE_BASE(page)) + +#define R_PAGESEL 0x0 +#define R_RESET VIRT_ADDR(0x0, 0x1) +#define R_IRQEN VIRT_ADDR(0x0, 0x2) +#define R_IRQMASK VIRT_ADDR(0x0, 0x3) +#define R_IRQSTAT VIRT_ADDR(0x0, 0x4) +#define R_DEVADD0 VIRT_ADDR(0x0, 0x6) +#define R_DEVID VIRT_ADDR(0x0, 0x8) +#define R_DEVREV VIRT_ADDR(0x0, 0x9) +#define R_PLLSTAT VIRT_ADDR(0x0, 0x0A) +#define R_PLL1CTL VIRT_ADDR(0x0, 0x0B) +#define R_PLL1RDIV VIRT_ADDR(0x0, 0x0C) +#define R_PLL1ODIV VIRT_ADDR(0x0, 0x0D) +#define R_PLL1FDIVL VIRT_ADDR(0x0, 0x0E) +#define R_PLL1FDIVH VIRT_ADDR(0x0, 0x0F) +#define R_PLL2CTL VIRT_ADDR(0x0, 0x10) +#define R_PLL2RDIV VIRT_ADDR(0x0, 0x11) +#define R_PLL2ODIV VIRT_ADDR(0x0, 0x12) +#define R_PLL2FDIVL VIRT_ADDR(0x0, 0x13) +#define R_PLL2FDIVH VIRT_ADDR(0x0, 0x14) +#define R_PLLCTL VIRT_ADDR(0x0, 0x15) +#define R_ISRC VIRT_ADDR(0x0, 0x16) +#define R_SCLKCTL VIRT_ADDR(0x0, 0x18) +#define R_TIMEBASE VIRT_ADDR(0x0, 0x19) +#define R_I2SP1CTL VIRT_ADDR(0x0, 0x1A) +#define R_I2SP2CTL VIRT_ADDR(0x0, 0x1B) +#define R_I2SP3CTL VIRT_ADDR(0x0, 0x1C) +#define R_I2S1MRATE VIRT_ADDR(0x0, 0x1D) +#define R_I2S2MRATE VIRT_ADDR(0x0, 0x1E) +#define R_I2S3MRATE VIRT_ADDR(0x0, 0x1F) +#define R_I2SCMC VIRT_ADDR(0x0, 0x20) +#define R_MCLK2PINC VIRT_ADDR(0x0, 0x21) +#define R_I2SPINC0 VIRT_ADDR(0x0, 0x22) +#define R_I2SPINC1 VIRT_ADDR(0x0, 0x23) +#define R_I2SPINC2 VIRT_ADDR(0x0, 0x24) +#define R_GPIOCTL0 VIRT_ADDR(0x0, 0x25) +#define R_GPIOCTL1 VIRT_ADDR(0x0, 0x26) +#define R_ASRC VIRT_ADDR(0x0, 0x28) +#define R_TDMCTL0 VIRT_ADDR(0x0, 0x2D) +#define R_TDMCTL1 VIRT_ADDR(0x0, 0x2E) +#define R_PCMP2CTL0 VIRT_ADDR(0x0, 0x2F) +#define R_PCMP2CTL1 VIRT_ADDR(0x0, 0x30) +#define R_PCMP3CTL0 VIRT_ADDR(0x0, 0x31) +#define R_PCMP3CTL1 VIRT_ADDR(0x0, 0x32) +#define R_PWRM0 VIRT_ADDR(0x0, 0x33) +#define R_PWRM1 VIRT_ADDR(0x0, 0x34) +#define R_PWRM2 VIRT_ADDR(0x0, 0x35) +#define R_PWRM3 VIRT_ADDR(0x0, 0x36) +#define R_PWRM4 VIRT_ADDR(0x0, 0x37) +#define R_I2SIDCTL VIRT_ADDR(0x0, 0x38) +#define R_I2SODCTL VIRT_ADDR(0x0, 0x39) +#define R_AUDIOMUX1 VIRT_ADDR(0x0, 0x3A) +#define R_AUDIOMUX2 VIRT_ADDR(0x0, 0x3B) +#define R_AUDIOMUX3 VIRT_ADDR(0x0, 0x3C) +#define R_HSDCTL1 VIRT_ADDR(0x1, 0x1) +#define R_HSDCTL2 VIRT_ADDR(0x1, 0x2) +#define R_HSDSTAT VIRT_ADDR(0x1, 0x3) +#define R_HSDDELAY VIRT_ADDR(0x1, 0x4) +#define R_BUTCTL VIRT_ADDR(0x1, 0x5) +#define R_CH0AIC VIRT_ADDR(0x1, 0x6) +#define R_CH1AIC VIRT_ADDR(0x1, 0x7) +#define R_CH2AIC VIRT_ADDR(0x1, 0x8) +#define R_CH3AIC VIRT_ADDR(0x1, 0x9) +#define R_ICTL0 VIRT_ADDR(0x1, 0x0A) +#define R_ICTL1 VIRT_ADDR(0x1, 0x0B) +#define R_MICBIAS VIRT_ADDR(0x1, 0x0C) +#define R_PGACTL0 VIRT_ADDR(0x1, 0x0D) +#define R_PGACTL1 VIRT_ADDR(0x1, 0x0E) +#define R_PGACTL2 VIRT_ADDR(0x1, 0x0F) +#define R_PGACTL3 VIRT_ADDR(0x1, 0x10) +#define R_PGAZ VIRT_ADDR(0x1, 0x11) +#define R_ICH0VOL VIRT_ADDR(0x1, 0x12) +#define R_ICH1VOL VIRT_ADDR(0x1, 0x13) +#define R_ICH2VOL VIRT_ADDR(0x1, 0x14) +#define R_ICH3VOL VIRT_ADDR(0x1, 0x15) +#define R_ASRCILVOL VIRT_ADDR(0x1, 0x16) +#define R_ASRCIRVOL VIRT_ADDR(0x1, 0x17) +#define R_ASRCOLVOL VIRT_ADDR(0x1, 0x18) +#define R_ASRCORVOL VIRT_ADDR(0x1, 0x19) +#define R_IVOLCTLU VIRT_ADDR(0x1, 0x1C) +#define R_ALCCTL0 VIRT_ADDR(0x1, 0x1D) +#define R_ALCCTL1 VIRT_ADDR(0x1, 0x1E) +#define R_ALCCTL2 VIRT_ADDR(0x1, 0x1F) +#define R_ALCCTL3 VIRT_ADDR(0x1, 0x20) +#define R_NGATE VIRT_ADDR(0x1, 0x21) +#define R_DMICCTL VIRT_ADDR(0x1, 0x22) +#define R_DACCTL VIRT_ADDR(0x2, 0x1) +#define R_SPKCTL VIRT_ADDR(0x2, 0x2) +#define R_SUBCTL VIRT_ADDR(0x2, 0x3) +#define R_DCCTL VIRT_ADDR(0x2, 0x4) +#define R_OVOLCTLU VIRT_ADDR(0x2, 0x6) +#define R_MUTEC VIRT_ADDR(0x2, 0x7) +#define R_MVOLL VIRT_ADDR(0x2, 0x8) +#define R_MVOLR VIRT_ADDR(0x2, 0x9) +#define R_HPVOLL VIRT_ADDR(0x2, 0x0A) +#define R_HPVOLR VIRT_ADDR(0x2, 0x0B) +#define R_SPKVOLL VIRT_ADDR(0x2, 0x0C) +#define R_SPKVOLR VIRT_ADDR(0x2, 0x0D) +#define R_SUBVOL VIRT_ADDR(0x2, 0x10) +#define R_COP0 VIRT_ADDR(0x2, 0x11) +#define R_COP1 VIRT_ADDR(0x2, 0x12) +#define R_COPSTAT VIRT_ADDR(0x2, 0x13) +#define R_PWM0 VIRT_ADDR(0x2, 0x14) +#define R_PWM1 VIRT_ADDR(0x2, 0x15) +#define R_PWM2 VIRT_ADDR(0x2, 0x16) +#define R_PWM3 VIRT_ADDR(0x2, 0x17) +#define R_HPSW VIRT_ADDR(0x2, 0x18) +#define R_THERMTS VIRT_ADDR(0x2, 0x19) +#define R_THERMSPK1 VIRT_ADDR(0x2, 0x1A) +#define R_THERMSTAT VIRT_ADDR(0x2, 0x1B) +#define R_SCSTAT VIRT_ADDR(0x2, 0x1C) +#define R_SDMON VIRT_ADDR(0x2, 0x1D) +#define R_SPKEQFILT VIRT_ADDR(0x3, 0x1) +#define R_SPKCRWDL VIRT_ADDR(0x3, 0x2) +#define R_SPKCRWDM VIRT_ADDR(0x3, 0x3) +#define R_SPKCRWDH VIRT_ADDR(0x3, 0x4) +#define R_SPKCRRDL VIRT_ADDR(0x3, 0x5) +#define R_SPKCRRDM VIRT_ADDR(0x3, 0x6) +#define R_SPKCRRDH VIRT_ADDR(0x3, 0x7) +#define R_SPKCRADD VIRT_ADDR(0x3, 0x8) +#define R_SPKCRS VIRT_ADDR(0x3, 0x9) +#define R_SPKMBCEN VIRT_ADDR(0x3, 0x0A) +#define R_SPKMBCCTL VIRT_ADDR(0x3, 0x0B) +#define R_SPKMBCMUG1 VIRT_ADDR(0x3, 0x0C) +#define R_SPKMBCTHR1 VIRT_ADDR(0x3, 0x0D) +#define R_SPKMBCRAT1 VIRT_ADDR(0x3, 0x0E) +#define R_SPKMBCATK1L VIRT_ADDR(0x3, 0x0F) +#define R_SPKMBCATK1H VIRT_ADDR(0x3, 0x10) +#define R_SPKMBCREL1L VIRT_ADDR(0x3, 0x11) +#define R_SPKMBCREL1H VIRT_ADDR(0x3, 0x12) +#define R_SPKMBCMUG2 VIRT_ADDR(0x3, 0x13) +#define R_SPKMBCTHR2 VIRT_ADDR(0x3, 0x14) +#define R_SPKMBCRAT2 VIRT_ADDR(0x3, 0x15) +#define R_SPKMBCATK2L VIRT_ADDR(0x3, 0x16) +#define R_SPKMBCATK2H VIRT_ADDR(0x3, 0x17) +#define R_SPKMBCREL2L VIRT_ADDR(0x3, 0x18) +#define R_SPKMBCREL2H VIRT_ADDR(0x3, 0x19) +#define R_SPKMBCMUG3 VIRT_ADDR(0x3, 0x1A) +#define R_SPKMBCTHR3 VIRT_ADDR(0x3, 0x1B) +#define R_SPKMBCRAT3 VIRT_ADDR(0x3, 0x1C) +#define R_SPKMBCATK3L VIRT_ADDR(0x3, 0x1D) +#define R_SPKMBCATK3H VIRT_ADDR(0x3, 0x1E) +#define R_SPKMBCREL3L VIRT_ADDR(0x3, 0x1F) +#define R_SPKMBCREL3H VIRT_ADDR(0x3, 0x20) +#define R_SPKCLECTL VIRT_ADDR(0x3, 0x21) +#define R_SPKCLEMUG VIRT_ADDR(0x3, 0x22) +#define R_SPKCOMPTHR VIRT_ADDR(0x3, 0x23) +#define R_SPKCOMPRAT VIRT_ADDR(0x3, 0x24) +#define R_SPKCOMPATKL VIRT_ADDR(0x3, 0x25) +#define R_SPKCOMPATKH VIRT_ADDR(0x3, 0x26) +#define R_SPKCOMPRELL VIRT_ADDR(0x3, 0x27) +#define R_SPKCOMPRELH VIRT_ADDR(0x3, 0x28) +#define R_SPKLIMTHR VIRT_ADDR(0x3, 0x29) +#define R_SPKLIMTGT VIRT_ADDR(0x3, 0x2A) +#define R_SPKLIMATKL VIRT_ADDR(0x3, 0x2B) +#define R_SPKLIMATKH VIRT_ADDR(0x3, 0x2C) +#define R_SPKLIMRELL VIRT_ADDR(0x3, 0x2D) +#define R_SPKLIMRELH VIRT_ADDR(0x3, 0x2E) +#define R_SPKEXPTHR VIRT_ADDR(0x3, 0x2F) +#define R_SPKEXPRAT VIRT_ADDR(0x3, 0x30) +#define R_SPKEXPATKL VIRT_ADDR(0x3, 0x31) +#define R_SPKEXPATKH VIRT_ADDR(0x3, 0x32) +#define R_SPKEXPRELL VIRT_ADDR(0x3, 0x33) +#define R_SPKEXPRELH VIRT_ADDR(0x3, 0x34) +#define R_SPKFXCTL VIRT_ADDR(0x3, 0x35) +#define R_DACEQFILT VIRT_ADDR(0x4, 0x1) +#define R_DACCRWDL VIRT_ADDR(0x4, 0x2) +#define R_DACCRWDM VIRT_ADDR(0x4, 0x3) +#define R_DACCRWDH VIRT_ADDR(0x4, 0x4) +#define R_DACCRRDL VIRT_ADDR(0x4, 0x5) +#define R_DACCRRDM VIRT_ADDR(0x4, 0x6) +#define R_DACCRRDH VIRT_ADDR(0x4, 0x7) +#define R_DACCRADD VIRT_ADDR(0x4, 0x8) +#define R_DACCRS VIRT_ADDR(0x4, 0x9) +#define R_DACMBCEN VIRT_ADDR(0x4, 0x0A) +#define R_DACMBCCTL VIRT_ADDR(0x4, 0x0B) +#define R_DACMBCMUG1 VIRT_ADDR(0x4, 0x0C) +#define R_DACMBCTHR1 VIRT_ADDR(0x4, 0x0D) +#define R_DACMBCRAT1 VIRT_ADDR(0x4, 0x0E) +#define R_DACMBCATK1L VIRT_ADDR(0x4, 0x0F) +#define R_DACMBCATK1H VIRT_ADDR(0x4, 0x10) +#define R_DACMBCREL1L VIRT_ADDR(0x4, 0x11) +#define R_DACMBCREL1H VIRT_ADDR(0x4, 0x12) +#define R_DACMBCMUG2 VIRT_ADDR(0x4, 0x13) +#define R_DACMBCTHR2 VIRT_ADDR(0x4, 0x14) +#define R_DACMBCRAT2 VIRT_ADDR(0x4, 0x15) +#define R_DACMBCATK2L VIRT_ADDR(0x4, 0x16) +#define R_DACMBCATK2H VIRT_ADDR(0x4, 0x17) +#define R_DACMBCREL2L VIRT_ADDR(0x4, 0x18) +#define R_DACMBCREL2H VIRT_ADDR(0x4, 0x19) +#define R_DACMBCMUG3 VIRT_ADDR(0x4, 0x1A) +#define R_DACMBCTHR3 VIRT_ADDR(0x4, 0x1B) +#define R_DACMBCRAT3 VIRT_ADDR(0x4, 0x1C) +#define R_DACMBCATK3L VIRT_ADDR(0x4, 0x1D) +#define R_DACMBCATK3H VIRT_ADDR(0x4, 0x1E) +#define R_DACMBCREL3L VIRT_ADDR(0x4, 0x1F) +#define R_DACMBCREL3H VIRT_ADDR(0x4, 0x20) +#define R_DACCLECTL VIRT_ADDR(0x4, 0x21) +#define R_DACCLEMUG VIRT_ADDR(0x4, 0x22) +#define R_DACCOMPTHR VIRT_ADDR(0x4, 0x23) +#define R_DACCOMPRAT VIRT_ADDR(0x4, 0x24) +#define R_DACCOMPATKL VIRT_ADDR(0x4, 0x25) +#define R_DACCOMPATKH VIRT_ADDR(0x4, 0x26) +#define R_DACCOMPRELL VIRT_ADDR(0x4, 0x27) +#define R_DACCOMPRELH VIRT_ADDR(0x4, 0x28) +#define R_DACLIMTHR VIRT_ADDR(0x4, 0x29) +#define R_DACLIMTGT VIRT_ADDR(0x4, 0x2A) +#define R_DACLIMATKL VIRT_ADDR(0x4, 0x2B) +#define R_DACLIMATKH VIRT_ADDR(0x4, 0x2C) +#define R_DACLIMRELL VIRT_ADDR(0x4, 0x2D) +#define R_DACLIMRELH VIRT_ADDR(0x4, 0x2E) +#define R_DACEXPTHR VIRT_ADDR(0x4, 0x2F) +#define R_DACEXPRAT VIRT_ADDR(0x4, 0x30) +#define R_DACEXPATKL VIRT_ADDR(0x4, 0x31) +#define R_DACEXPATKH VIRT_ADDR(0x4, 0x32) +#define R_DACEXPRELL VIRT_ADDR(0x4, 0x33) +#define R_DACEXPRELH VIRT_ADDR(0x4, 0x34) +#define R_DACFXCTL VIRT_ADDR(0x4, 0x35) +#define R_SUBEQFILT VIRT_ADDR(0x5, 0x1) +#define R_SUBCRWDL VIRT_ADDR(0x5, 0x2) +#define R_SUBCRWDM VIRT_ADDR(0x5, 0x3) +#define R_SUBCRWDH VIRT_ADDR(0x5, 0x4) +#define R_SUBCRRDL VIRT_ADDR(0x5, 0x5) +#define R_SUBCRRDM VIRT_ADDR(0x5, 0x6) +#define R_SUBCRRDH VIRT_ADDR(0x5, 0x7) +#define R_SUBCRADD VIRT_ADDR(0x5, 0x8) +#define R_SUBCRS VIRT_ADDR(0x5, 0x9) +#define R_SUBMBCEN VIRT_ADDR(0x5, 0x0A) +#define R_SUBMBCCTL VIRT_ADDR(0x5, 0x0B) +#define R_SUBMBCMUG1 VIRT_ADDR(0x5, 0x0C) +#define R_SUBMBCTHR1 VIRT_ADDR(0x5, 0x0D) +#define R_SUBMBCRAT1 VIRT_ADDR(0x5, 0x0E) +#define R_SUBMBCATK1L VIRT_ADDR(0x5, 0x0F) +#define R_SUBMBCATK1H VIRT_ADDR(0x5, 0x10) +#define R_SUBMBCREL1L VIRT_ADDR(0x5, 0x11) +#define R_SUBMBCREL1H VIRT_ADDR(0x5, 0x12) +#define R_SUBMBCMUG2 VIRT_ADDR(0x5, 0x13) +#define R_SUBMBCTHR2 VIRT_ADDR(0x5, 0x14) +#define R_SUBMBCRAT2 VIRT_ADDR(0x5, 0x15) +#define R_SUBMBCATK2L VIRT_ADDR(0x5, 0x16) +#define R_SUBMBCATK2H VIRT_ADDR(0x5, 0x17) +#define R_SUBMBCREL2L VIRT_ADDR(0x5, 0x18) +#define R_SUBMBCREL2H VIRT_ADDR(0x5, 0x19) +#define R_SUBMBCMUG3 VIRT_ADDR(0x5, 0x1A) +#define R_SUBMBCTHR3 VIRT_ADDR(0x5, 0x1B) +#define R_SUBMBCRAT3 VIRT_ADDR(0x5, 0x1C) +#define R_SUBMBCATK3L VIRT_ADDR(0x5, 0x1D) +#define R_SUBMBCATK3H VIRT_ADDR(0x5, 0x1E) +#define R_SUBMBCREL3L VIRT_ADDR(0x5, 0x1F) +#define R_SUBMBCREL3H VIRT_ADDR(0x5, 0x20) +#define R_SUBCLECTL VIRT_ADDR(0x5, 0x21) +#define R_SUBCLEMUG VIRT_ADDR(0x5, 0x22) +#define R_SUBCOMPTHR VIRT_ADDR(0x5, 0x23) +#define R_SUBCOMPRAT VIRT_ADDR(0x5, 0x24) +#define R_SUBCOMPATKL VIRT_ADDR(0x5, 0x25) +#define R_SUBCOMPATKH VIRT_ADDR(0x5, 0x26) +#define R_SUBCOMPRELL VIRT_ADDR(0x5, 0x27) +#define R_SUBCOMPRELH VIRT_ADDR(0x5, 0x28) +#define R_SUBLIMTHR VIRT_ADDR(0x5, 0x29) +#define R_SUBLIMTGT VIRT_ADDR(0x5, 0x2A) +#define R_SUBLIMATKL VIRT_ADDR(0x5, 0x2B) +#define R_SUBLIMATKH VIRT_ADDR(0x5, 0x2C) +#define R_SUBLIMRELL VIRT_ADDR(0x5, 0x2D) +#define R_SUBLIMRELH VIRT_ADDR(0x5, 0x2E) +#define R_SUBEXPTHR VIRT_ADDR(0x5, 0x2F) +#define R_SUBEXPRAT VIRT_ADDR(0x5, 0x30) +#define R_SUBEXPATKL VIRT_ADDR(0x5, 0x31) +#define R_SUBEXPATKH VIRT_ADDR(0x5, 0x32) +#define R_SUBEXPRELL VIRT_ADDR(0x5, 0x33) +#define R_SUBEXPRELH VIRT_ADDR(0x5, 0x34) +#define R_SUBFXCTL VIRT_ADDR(0x5, 0x35) + +// *** PLLCTL *** +#define FB_PLLCTL_VCCI_PLL 6 +#define FM_PLLCTL_VCCI_PLL 0xC0 + +#define FB_PLLCTL_RZ_PLL 3 +#define FM_PLLCTL_RZ_PLL 0x38 + +#define FB_PLLCTL_CP_PLL 0 +#define FM_PLLCTL_CP_PLL 0x7 + +// *** PLLRDIV *** +#define FB_PLLRDIV_REFDIV_PLL 0 +#define FM_PLLRDIV_REFDIV_PLL 0xFF + +// *** PLLODIV *** +#define FB_PLLODIV_OUTDIV_PLL 0 +#define FM_PLLODIV_OUTDIV_PLL 0xFF + +// *** PLLFDIVL *** +#define FB_PLLFDIVL_FBDIVL_PLL 0 +#define FM_PLLFDIVL_FBDIVL_PLL 0xFF + +// *** PLLFDIVH *** +#define FB_PLLFDIVH_FBDIVH_PLL 0 +#define FM_PLLFDIVH_FBDIVH_PLL 0xF + +// *** I2SPCTL *** +#define FB_I2SPCTL_BCLKSTAT 7 +#define FM_I2SPCTL_BCLKSTAT 0x80 +#define FV_BCLKSTAT_LOST 0x80 +#define FV_BCLKSTAT_NOT_LOST 0x0 + +#define FB_I2SPCTL_BCLKP 6 +#define FM_I2SPCTL_BCLKP 0x40 +#define FV_BCLKP_NOT_INVERTED 0x0 +#define FV_BCLKP_INVERTED 0x40 + +#define FB_I2SPCTL_PORTMS 5 +#define FM_I2SPCTL_PORTMS 0x20 +#define FV_PORTMS_SLAVE 0x0 +#define FV_PORTMS_MASTER 0x20 + +#define FB_I2SPCTL_LRCLKP 4 +#define FM_I2SPCTL_LRCLKP 0x10 +#define FV_LRCLKP_NOT_INVERTED 0x0 +#define FV_LRCLKP_INVERTED 0x10 + +#define FB_I2SPCTL_WL 2 +#define FM_I2SPCTL_WL 0xC +#define FV_WL_16 0x0 +#define FV_WL_20 0x4 +#define FV_WL_24 0x8 +#define FV_WL_32 0xC + +#define FB_I2SPCTL_FORMAT 0 +#define FM_I2SPCTL_FORMAT 0x3 +#define FV_FORMAT_RIGHT 0x0 +#define FV_FORMAT_LEFT 0x1 +#define FV_FORMAT_I2S 0x2 +#define FV_FORMAT_TDM 0x3 + +// *** I2SMRATE *** +#define FB_I2SMRATE_I2SMCLKHALF 7 +#define FM_I2SMRATE_I2SMCLKHALF 0x80 +#define FV_I2SMCLKHALF_I2S1MCLKDIV_DIV_2 0x0 +#define FV_I2SMCLKHALF_I2S1MCLKDIV_ONLY 0x80 + +#define FB_I2SMRATE_I2SMCLKDIV 5 +#define FM_I2SMRATE_I2SMCLKDIV 0x60 +#define FV_I2SMCLKDIV_125 0x0 +#define FV_I2SMCLKDIV_128 0x20 +#define FV_I2SMCLKDIV_136 0x40 +#define FV_I2SMCLKDIV_192 0x60 + +#define FB_I2SMRATE_I2SMBR 3 +#define FM_I2SMRATE_I2SMBR 0x18 +#define FV_I2SMBR_32 0x0 +#define FV_I2SMBR_44PT1 0x8 +#define FV_I2SMBR_48 0x10 +#define FV_I2SMBR_MCLK_MODE 0x18 + +#define FB_I2SMRATE_I2SMBM 0 +#define FM_I2SMRATE_I2SMBM 0x3 +#define FV_I2SMBM_0PT25 0x0 +#define FV_I2SMBM_0PT5 0x1 +#define FV_I2SMBM_1 0x2 +#define FV_I2SMBM_2 0x3 + +// *** PCMPCTL0 *** +#define FB_PCMPCTL0_PCMFLENP 2 +#define FM_PCMPCTL0_PCMFLENP 0x4 +#define FV_PCMFLENP_128 0x0 +#define FV_PCMFLENP_256 0x4 + +#define FB_PCMPCTL0_SLSYNCP 1 +#define FM_PCMPCTL0_SLSYNCP 0x2 +#define FV_SLSYNCP_SHORT 0x0 +#define FV_SLSYNCP_LONG 0x2 + +#define FB_PCMPCTL0_BDELAYP 0 +#define FM_PCMPCTL0_BDELAYP 0x1 +#define FV_BDELAYP_NO_DELAY 0x0 +#define FV_BDELAYP_1BCLK_DELAY 0x1 + +// *** PCMPCTL1 *** +#define FB_PCMPCTL1_PCMMOMP 6 +#define FM_PCMPCTL1_PCMMOMP 0x40 + +#define FB_PCMPCTL1_PCMSOP 5 +#define FM_PCMPCTL1_PCMSOP 0x20 +#define FV_PCMSOP_1 0x0 +#define FV_PCMSOP_2 0x20 + +#define FB_PCMPCTL1_PCMDSSP 3 +#define FM_PCMPCTL1_PCMDSSP 0x18 +#define FV_PCMDSSP_16 0x0 +#define FV_PCMDSSP_24 0x8 +#define FV_PCMDSSP_32 0x10 + +#define FB_PCMPCTL1_PCMMIMP 1 +#define FM_PCMPCTL1_PCMMIMP 0x2 + +#define FB_PCMPCTL1_PCMSIP 0 +#define FM_PCMPCTL1_PCMSIP 0x1 +#define FV_PCMSIP_1 0x0 +#define FV_PCMSIP_2 0x1 + +// *** CHAIC *** +#define FB_CHAIC_MICBST 4 +#define FM_CHAIC_MICBST 0x30 + +// *** PGACTL *** +#define FB_PGACTL_PGAMUTE 7 +#define FM_PGACTL_PGAMUTE 0x80 + +#define FB_PGACTL_PGAVOL 0 +#define FM_PGACTL_PGAVOL 0x3F + +// *** ICHVOL *** +#define FB_ICHVOL_ICHVOL 0 +#define FM_ICHVOL_ICHVOL 0xFF + +// *** SPKMBCMUG *** +#define FB_SPKMBCMUG_PHASE 5 +#define FM_SPKMBCMUG_PHASE 0x20 + +#define FB_SPKMBCMUG_MUGAIN 0 +#define FM_SPKMBCMUG_MUGAIN 0x1F + +// *** SPKMBCTHR *** +#define FB_SPKMBCTHR_THRESH 0 +#define FM_SPKMBCTHR_THRESH 0xFF + +// *** SPKMBCRAT *** +#define FB_SPKMBCRAT_RATIO 0 +#define FM_SPKMBCRAT_RATIO 0x1F + +// *** SPKMBCATKL *** +#define FB_SPKMBCATKL_TCATKL 0 +#define FM_SPKMBCATKL_TCATKL 0xFF + +// *** SPKMBCATKH *** +#define FB_SPKMBCATKH_TCATKH 0 +#define FM_SPKMBCATKH_TCATKH 0xFF + +// *** SPKMBCRELL *** +#define FB_SPKMBCRELL_TCRELL 0 +#define FM_SPKMBCRELL_TCRELL 0xFF + +// *** SPKMBCRELH *** +#define FB_SPKMBCRELH_TCRELH 0 +#define FM_SPKMBCRELH_TCRELH 0xFF + +// *** DACMBCMUG *** +#define FB_DACMBCMUG_PHASE 5 +#define FM_DACMBCMUG_PHASE 0x20 + +#define FB_DACMBCMUG_MUGAIN 0 +#define FM_DACMBCMUG_MUGAIN 0x1F + +// *** DACMBCTHR *** +#define FB_DACMBCTHR_THRESH 0 +#define FM_DACMBCTHR_THRESH 0xFF + +// *** DACMBCRAT *** +#define FB_DACMBCRAT_RATIO 0 +#define FM_DACMBCRAT_RATIO 0x1F + +// *** DACMBCATKL *** +#define FB_DACMBCATKL_TCATKL 0 +#define FM_DACMBCATKL_TCATKL 0xFF + +// *** DACMBCATKH *** +#define FB_DACMBCATKH_TCATKH 0 +#define FM_DACMBCATKH_TCATKH 0xFF + +// *** DACMBCRELL *** +#define FB_DACMBCRELL_TCRELL 0 +#define FM_DACMBCRELL_TCRELL 0xFF + +// *** DACMBCRELH *** +#define FB_DACMBCRELH_TCRELH 0 +#define FM_DACMBCRELH_TCRELH 0xFF + +// *** SUBMBCMUG *** +#define FB_SUBMBCMUG_PHASE 5 +#define FM_SUBMBCMUG_PHASE 0x20 + +#define FB_SUBMBCMUG_MUGAIN 0 +#define FM_SUBMBCMUG_MUGAIN 0x1F + +// *** SUBMBCTHR *** +#define FB_SUBMBCTHR_THRESH 0 +#define FM_SUBMBCTHR_THRESH 0xFF + +// *** SUBMBCRAT *** +#define FB_SUBMBCRAT_RATIO 0 +#define FM_SUBMBCRAT_RATIO 0x1F + +// *** SUBMBCATKL *** +#define FB_SUBMBCATKL_TCATKL 0 +#define FM_SUBMBCATKL_TCATKL 0xFF + +// *** SUBMBCATKH *** +#define FB_SUBMBCATKH_TCATKH 0 +#define FM_SUBMBCATKH_TCATKH 0xFF + +// *** SUBMBCRELL *** +#define FB_SUBMBCRELL_TCRELL 0 +#define FM_SUBMBCRELL_TCRELL 0xFF + +// *** SUBMBCRELH *** +#define FB_SUBMBCRELH_TCRELH 0 +#define FM_SUBMBCRELH_TCRELH 0xFF + +// *** PAGESEL *** +#define FB_PAGESEL_PAGESEL 0 +#define FM_PAGESEL_PAGESEL 0xFF + +// *** RESET *** +#define FB_RESET_RESET 0 +#define FM_RESET_RESET 0xFF +#define FV_RESET_PWR_ON_DEFAULTS 0x85 + +// *** IRQEN *** +#define FB_IRQEN_THRMINTEN 6 +#define FM_IRQEN_THRMINTEN 0x40 +#define FV_THRMINTEN_ENABLED 0x40 +#define FV_THRMINTEN_DISABLED 0x0 + +#define FB_IRQEN_HBPINTEN 5 +#define FM_IRQEN_HBPINTEN 0x20 +#define FV_HBPINTEN_ENABLED 0x20 +#define FV_HBPINTEN_DISABLED 0x0 + +#define FB_IRQEN_HSDINTEN 4 +#define FM_IRQEN_HSDINTEN 0x10 +#define FV_HSDINTEN_ENABLED 0x10 +#define FV_HSDINTEN_DISABLED 0x0 + +#define FB_IRQEN_HPDINTEN 3 +#define FM_IRQEN_HPDINTEN 0x8 +#define FV_HPDINTEN_ENABLED 0x8 +#define FV_HPDINTEN_DISABLED 0x0 + +#define FB_IRQEN_GPIO3INTEN 1 +#define FM_IRQEN_GPIO3INTEN 0x2 +#define FV_GPIO3INTEN_ENABLED 0x2 +#define FV_GPIO3INTEN_DISABLED 0x0 + +#define FB_IRQEN_GPIO2INTEN 0 +#define FM_IRQEN_GPIO2INTEN 0x1 +#define FV_GPIO2INTEN_ENABLED 0x1 +#define FV_GPIO2INTEN_DISABLED 0x0 + +#define IRQEN_GPIOINTEN_ENABLED 0x1 +#define IRQEN_GPIOINTEN_DISABLED 0x0 + +// *** IRQMASK *** +#define FB_IRQMASK_THRMIM 6 +#define FM_IRQMASK_THRMIM 0x40 +#define FV_THRMIM_MASKED 0x0 +#define FV_THRMIM_NOT_MASKED 0x40 + +#define FB_IRQMASK_HBPIM 5 +#define FM_IRQMASK_HBPIM 0x20 +#define FV_HBPIM_MASKED 0x0 +#define FV_HBPIM_NOT_MASKED 0x20 + +#define FB_IRQMASK_HSDIM 4 +#define FM_IRQMASK_HSDIM 0x10 +#define FV_HSDIM_MASKED 0x0 +#define FV_HSDIM_NOT_MASKED 0x10 + +#define FB_IRQMASK_HPDIM 3 +#define FM_IRQMASK_HPDIM 0x8 +#define FV_HPDIM_MASKED 0x0 +#define FV_HPDIM_NOT_MASKED 0x8 + +#define FB_IRQMASK_GPIO3M 1 +#define FM_IRQMASK_GPIO3M 0x2 +#define FV_GPIO3M_MASKED 0x0 +#define FV_GPIO3M_NOT_MASKED 0x2 + +#define FB_IRQMASK_GPIO2M 0 +#define FM_IRQMASK_GPIO2M 0x1 +#define FV_GPIO2M_MASKED 0x0 +#define FV_GPIO2M_NOT_MASKED 0x1 + +#define IRQMASK_GPIOM_MASKED 0x0 +#define IRQMASK_GPIOM_NOT_MASKED 0x1 + +// *** IRQSTAT *** +#define FB_IRQSTAT_THRMINT 6 +#define FM_IRQSTAT_THRMINT 0x40 +#define FV_THRMINT_INTERRUPTED 0x40 +#define FV_THRMINT_NOT_INTERRUPTED 0x0 + +#define FB_IRQSTAT_HBPINT 5 +#define FM_IRQSTAT_HBPINT 0x20 +#define FV_HBPINT_INTERRUPTED 0x20 +#define FV_HBPINT_NOT_INTERRUPTED 0x0 + +#define FB_IRQSTAT_HSDINT 4 +#define FM_IRQSTAT_HSDINT 0x10 +#define FV_HSDINT_INTERRUPTED 0x10 +#define FV_HSDINT_NOT_INTERRUPTED 0x0 + +#define FB_IRQSTAT_HPDINT 3 +#define FM_IRQSTAT_HPDINT 0x8 +#define FV_HPDINT_INTERRUPTED 0x8 +#define FV_HPDINT_NOT_INTERRUPTED 0x0 + +#define FB_IRQSTAT_GPIO3INT 1 +#define FM_IRQSTAT_GPIO3INT 0x2 +#define FV_GPIO3INT_INTERRUPTED 0x2 +#define FV_GPIO3INT_NOT_INTERRUPTED 0x0 + +#define FB_IRQSTAT_GPIO2INT 0 +#define FM_IRQSTAT_GPIO2INT 0x1 +#define FV_GPIO2INT_INTERRUPTED 0x1 +#define FV_GPIO2INT_NOT_INTERRUPTED 0x0 + +#define IRQSTAT_GPIOINT_INTERRUPTED 0x1 +#define IRQSTAT_GPIOINT_NOT_INTERRUPTED 0x0 + +// *** DEVADD0 *** +#define FB_DEVADD0_DEVADD0 1 +#define FM_DEVADD0_DEVADD0 0xFE + +#define FB_DEVADD0_I2C_ADDRLK 0 +#define FM_DEVADD0_I2C_ADDRLK 0x1 +#define FV_I2C_ADDRLK_LOCK 0x1 + +// *** DEVID *** +#define FB_DEVID_DEV_ID 0 +#define FM_DEVID_DEV_ID 0xFF + +// *** DEVREV *** +#define FB_DEVREV_MAJ_REV 4 +#define FM_DEVREV_MAJ_REV 0xF0 + +#define FB_DEVREV_MIN_REV 0 +#define FM_DEVREV_MIN_REV 0xF + +// *** PLLSTAT *** +#define FB_PLLSTAT_PLL2LK 1 +#define FM_PLLSTAT_PLL2LK 0x2 +#define FV_PLL2LK_LOCKED 0x2 +#define FV_PLL2LK_UNLOCKED 0x0 + +#define FB_PLLSTAT_PLL1LK 0 +#define FM_PLLSTAT_PLL1LK 0x1 +#define FV_PLL1LK_LOCKED 0x1 +#define FV_PLL1LK_UNLOCKED 0x0 + +#define PLLSTAT_PLLLK_LOCKED 0x1 +#define PLLSTAT_PLLLK_UNLOCKED 0x0 + +// *** PLLCTL *** +#define FB_PLLCTL_PU_PLL2 7 +#define FM_PLLCTL_PU_PLL2 0x80 +#define FV_PU_PLL2_PWR_UP 0x80 +#define FV_PU_PLL2_PWR_DWN 0x0 + +#define FB_PLLCTL_PU_PLL1 6 +#define FM_PLLCTL_PU_PLL1 0x40 +#define FV_PU_PLL1_PWR_UP 0x40 +#define FV_PU_PLL1_PWR_DWN 0x0 + +#define FB_PLLCTL_PLL2CLKEN 5 +#define FM_PLLCTL_PLL2CLKEN 0x20 +#define FV_PLL2CLKEN_ENABLE 0x20 +#define FV_PLL2CLKEN_DISABLE 0x0 + +#define FB_PLLCTL_PLL1CLKEN 4 +#define FM_PLLCTL_PLL1CLKEN 0x10 +#define FV_PLL1CLKEN_ENABLE 0x10 +#define FV_PLL1CLKEN_DISABLE 0x0 + +#define FB_PLLCTL_BCLKSEL 2 +#define FM_PLLCTL_BCLKSEL 0xC +#define FV_BCLKSEL_BCLK1 0x0 +#define FV_BCLKSEL_BCLK2 0x4 +#define FV_BCLKSEL_BCLK3 0x8 + +#define FB_PLLCTL_PLLISEL 0 +#define FM_PLLCTL_PLLISEL 0x3 +#define FV_PLLISEL_XTAL 0x0 +#define FV_PLLISEL_MCLK1 0x1 +#define FV_PLLISEL_MCLK2 0x2 +#define FV_PLLISEL_BCLK 0x3 + +#define PLLCTL_PU_PLL_PWR_UP 0x1 +#define PLLCTL_PU_PLL_PWR_DWN 0x0 +#define PLLCTL_PLLCLKEN_ENABLE 0x1 +#define PLLCTL_PLLCLKEN_DISABLE 0x0 + +// *** ISRC *** +#define FB_ISRC_IBR 2 +#define FM_ISRC_IBR 0x4 +#define FV_IBR_44PT1 0x0 +#define FV_IBR_48 0x4 + +#define FB_ISRC_IBM 0 +#define FM_ISRC_IBM 0x3 +#define FV_IBM_0PT25 0x0 +#define FV_IBM_0PT5 0x1 +#define FV_IBM_1 0x2 +#define FV_IBM_2 0x3 + +// *** SCLKCTL *** +#define FB_SCLKCTL_ASDM 6 +#define FM_SCLKCTL_ASDM 0xC0 +#define FV_ASDM_HALF 0x40 +#define FV_ASDM_FULL 0x80 +#define FV_ASDM_AUTO 0xC0 + +#define FB_SCLKCTL_DSDM 4 +#define FM_SCLKCTL_DSDM 0x30 +#define FV_DSDM_HALF 0x10 +#define FV_DSDM_FULL 0x20 +#define FV_DSDM_AUTO 0x30 + +// *** TIMEBASE *** +#define FB_TIMEBASE_TIMEBASE 0 +#define FM_TIMEBASE_TIMEBASE 0xFF + +// *** I2SCMC *** +#define FB_I2SCMC_BCMP3 4 +#define FM_I2SCMC_BCMP3 0x30 +#define FV_BCMP3_AUTO 0x0 +#define FV_BCMP3_32X 0x10 +#define FV_BCMP3_40X 0x20 +#define FV_BCMP3_64X 0x30 + +#define FB_I2SCMC_BCMP2 2 +#define FM_I2SCMC_BCMP2 0xC +#define FV_BCMP2_AUTO 0x0 +#define FV_BCMP2_32X 0x4 +#define FV_BCMP2_40X 0x8 +#define FV_BCMP2_64X 0xC + +#define FB_I2SCMC_BCMP1 0 +#define FM_I2SCMC_BCMP1 0x3 +#define FV_BCMP1_AUTO 0x0 +#define FV_BCMP1_32X 0x1 +#define FV_BCMP1_40X 0x2 +#define FV_BCMP1_64X 0x3 + +#define I2SCMC_BCMP_AUTO 0x0 +#define I2SCMC_BCMP_32X 0x1 +#define I2SCMC_BCMP_40X 0x2 +#define I2SCMC_BCMP_64X 0x3 + +// *** MCLK2PINC *** +#define FB_MCLK2PINC_SLEWOUT 4 +#define FM_MCLK2PINC_SLEWOUT 0xF0 + +#define FB_MCLK2PINC_MCLK2IO 2 +#define FM_MCLK2PINC_MCLK2IO 0x4 +#define FV_MCLK2IO_INPUT 0x0 +#define FV_MCLK2IO_OUTPUT 0x4 + +#define FB_MCLK2PINC_MCLK2OS 0 +#define FM_MCLK2PINC_MCLK2OS 0x3 +#define FV_MCLK2OS_24PT576 0x0 +#define FV_MCLK2OS_22PT5792 0x1 +#define FV_MCLK2OS_PLL2 0x2 + +// *** I2SPINC0 *** +#define FB_I2SPINC0_SDO3TRI 7 +#define FM_I2SPINC0_SDO3TRI 0x80 + +#define FB_I2SPINC0_SDO2TRI 6 +#define FM_I2SPINC0_SDO2TRI 0x40 + +#define FB_I2SPINC0_SDO1TRI 5 +#define FM_I2SPINC0_SDO1TRI 0x20 + +#define FB_I2SPINC0_PCM3TRI 2 +#define FM_I2SPINC0_PCM3TRI 0x4 + +#define FB_I2SPINC0_PCM2TRI 1 +#define FM_I2SPINC0_PCM2TRI 0x2 + +#define FB_I2SPINC0_PCM1TRI 0 +#define FM_I2SPINC0_PCM1TRI 0x1 + +// *** I2SPINC1 *** +#define FB_I2SPINC1_SDO3PDD 2 +#define FM_I2SPINC1_SDO3PDD 0x4 + +#define FB_I2SPINC1_SDO2PDD 1 +#define FM_I2SPINC1_SDO2PDD 0x2 + +#define FB_I2SPINC1_SDO1PDD 0 +#define FM_I2SPINC1_SDO1PDD 0x1 + +// *** I2SPINC2 *** +#define FB_I2SPINC2_LR3PDD 5 +#define FM_I2SPINC2_LR3PDD 0x20 + +#define FB_I2SPINC2_BC3PDD 4 +#define FM_I2SPINC2_BC3PDD 0x10 + +#define FB_I2SPINC2_LR2PDD 3 +#define FM_I2SPINC2_LR2PDD 0x8 + +#define FB_I2SPINC2_BC2PDD 2 +#define FM_I2SPINC2_BC2PDD 0x4 + +#define FB_I2SPINC2_LR1PDD 1 +#define FM_I2SPINC2_LR1PDD 0x2 + +#define FB_I2SPINC2_BC1PDD 0 +#define FM_I2SPINC2_BC1PDD 0x1 + +// *** GPIOCTL0 *** +#define FB_GPIOCTL0_GPIO3INTP 7 +#define FM_GPIOCTL0_GPIO3INTP 0x80 + +#define FB_GPIOCTL0_GPIO2INTP 6 +#define FM_GPIOCTL0_GPIO2INTP 0x40 + +#define FB_GPIOCTL0_GPIO3CFG 5 +#define FM_GPIOCTL0_GPIO3CFG 0x20 + +#define FB_GPIOCTL0_GPIO2CFG 4 +#define FM_GPIOCTL0_GPIO2CFG 0x10 + +#define FB_GPIOCTL0_GPIO3IO 3 +#define FM_GPIOCTL0_GPIO3IO 0x8 + +#define FB_GPIOCTL0_GPIO2IO 2 +#define FM_GPIOCTL0_GPIO2IO 0x4 + +#define FB_GPIOCTL0_GPIO1IO 1 +#define FM_GPIOCTL0_GPIO1IO 0x2 + +#define FB_GPIOCTL0_GPIO0IO 0 +#define FM_GPIOCTL0_GPIO0IO 0x1 + +// *** GPIOCTL1 *** +#define FB_GPIOCTL1_GPIO3 7 +#define FM_GPIOCTL1_GPIO3 0x80 + +#define FB_GPIOCTL1_GPIO2 6 +#define FM_GPIOCTL1_GPIO2 0x40 + +#define FB_GPIOCTL1_GPIO1 5 +#define FM_GPIOCTL1_GPIO1 0x20 + +#define FB_GPIOCTL1_GPIO0 4 +#define FM_GPIOCTL1_GPIO0 0x10 + +#define FB_GPIOCTL1_GPIO3RD 3 +#define FM_GPIOCTL1_GPIO3RD 0x8 + +#define FB_GPIOCTL1_GPIO2RD 2 +#define FM_GPIOCTL1_GPIO2RD 0x4 + +#define FB_GPIOCTL1_GPIO1RD 1 +#define FM_GPIOCTL1_GPIO1RD 0x2 + +#define FB_GPIOCTL1_GPIO0RD 0 +#define FM_GPIOCTL1_GPIO0RD 0x1 + +// *** ASRC *** +#define FB_ASRC_ASRCOBW 7 +#define FM_ASRC_ASRCOBW 0x80 + +#define FB_ASRC_ASRCIBW 6 +#define FM_ASRC_ASRCIBW 0x40 + +#define FB_ASRC_ASRCOB 5 +#define FM_ASRC_ASRCOB 0x20 +#define FV_ASRCOB_ACTIVE 0x0 +#define FV_ASRCOB_BYPASSED 0x20 + +#define FB_ASRC_ASRCIB 4 +#define FM_ASRC_ASRCIB 0x10 +#define FV_ASRCIB_ACTIVE 0x0 +#define FV_ASRCIB_BYPASSED 0x10 + +#define FB_ASRC_ASRCOL 3 +#define FM_ASRC_ASRCOL 0x8 + +#define FB_ASRC_ASRCIL 2 +#define FM_ASRC_ASRCIL 0x4 + +// *** TDMCTL0 *** +#define FB_TDMCTL0_TDMMD 2 +#define FM_TDMCTL0_TDMMD 0x4 +#define FV_TDMMD_200 0x0 +#define FV_TDMMD_256 0x4 + +#define FB_TDMCTL0_SLSYNC 1 +#define FM_TDMCTL0_SLSYNC 0x2 +#define FV_SLSYNC_SHORT 0x0 +#define FV_SLSYNC_LONG 0x2 + +#define FB_TDMCTL0_BDELAY 0 +#define FM_TDMCTL0_BDELAY 0x1 +#define FV_BDELAY_NO_DELAY 0x0 +#define FV_BDELAY_1BCLK_DELAY 0x1 + +// *** TDMCTL1 *** +#define FB_TDMCTL1_TDMSO 5 +#define FM_TDMCTL1_TDMSO 0x60 +#define FV_TDMSO_2 0x0 +#define FV_TDMSO_4 0x20 +#define FV_TDMSO_6 0x40 + +#define FB_TDMCTL1_TDMDSS 3 +#define FM_TDMCTL1_TDMDSS 0x18 +#define FV_TDMDSS_16 0x0 +#define FV_TDMDSS_24 0x10 +#define FV_TDMDSS_32 0x18 + +#define FB_TDMCTL1_TDMSI 0 +#define FM_TDMCTL1_TDMSI 0x3 +#define FV_TDMSI_2 0x0 +#define FV_TDMSI_4 0x1 +#define FV_TDMSI_6 0x2 + +// *** PWRM0 *** +#define FB_PWRM0_INPROC3PU 6 +#define FM_PWRM0_INPROC3PU 0x40 + +#define FB_PWRM0_INPROC2PU 5 +#define FM_PWRM0_INPROC2PU 0x20 + +#define FB_PWRM0_INPROC1PU 4 +#define FM_PWRM0_INPROC1PU 0x10 + +#define FB_PWRM0_INPROC0PU 3 +#define FM_PWRM0_INPROC0PU 0x8 + +#define FB_PWRM0_MICB2PU 2 +#define FM_PWRM0_MICB2PU 0x4 + +#define FB_PWRM0_MICB1PU 1 +#define FM_PWRM0_MICB1PU 0x2 + +#define FB_PWRM0_MCLKPEN 0 +#define FM_PWRM0_MCLKPEN 0x1 + +// *** PWRM1 *** +#define FB_PWRM1_SUBPU 7 +#define FM_PWRM1_SUBPU 0x80 + +#define FB_PWRM1_HPLPU 6 +#define FM_PWRM1_HPLPU 0x40 + +#define FB_PWRM1_HPRPU 5 +#define FM_PWRM1_HPRPU 0x20 + +#define FB_PWRM1_SPKLPU 4 +#define FM_PWRM1_SPKLPU 0x10 + +#define FB_PWRM1_SPKRPU 3 +#define FM_PWRM1_SPKRPU 0x8 + +#define FB_PWRM1_D2S2PU 2 +#define FM_PWRM1_D2S2PU 0x4 + +#define FB_PWRM1_D2S1PU 1 +#define FM_PWRM1_D2S1PU 0x2 + +#define FB_PWRM1_VREFPU 0 +#define FM_PWRM1_VREFPU 0x1 + +// *** PWRM2 *** +#define FB_PWRM2_I2S3OPU 5 +#define FM_PWRM2_I2S3OPU 0x20 +#define FV_I2S3OPU_PWR_DOWN 0x0 +#define FV_I2S3OPU_PWR_UP 0x20 + +#define FB_PWRM2_I2S2OPU 4 +#define FM_PWRM2_I2S2OPU 0x10 +#define FV_I2S2OPU_PWR_DOWN 0x0 +#define FV_I2S2OPU_PWR_UP 0x10 + +#define FB_PWRM2_I2S1OPU 3 +#define FM_PWRM2_I2S1OPU 0x8 +#define FV_I2S1OPU_PWR_DOWN 0x0 +#define FV_I2S1OPU_PWR_UP 0x8 + +#define FB_PWRM2_I2S3IPU 2 +#define FM_PWRM2_I2S3IPU 0x4 +#define FV_I2S3IPU_PWR_DOWN 0x0 +#define FV_I2S3IPU_PWR_UP 0x4 + +#define FB_PWRM2_I2S2IPU 1 +#define FM_PWRM2_I2S2IPU 0x2 +#define FV_I2S2IPU_PWR_DOWN 0x0 +#define FV_I2S2IPU_PWR_UP 0x2 + +#define FB_PWRM2_I2S1IPU 0 +#define FM_PWRM2_I2S1IPU 0x1 +#define FV_I2S1IPU_PWR_DOWN 0x0 +#define FV_I2S1IPU_PWR_UP 0x1 + +#define PWRM2_I2SOPU_PWR_DOWN 0x0 +#define PWRM2_I2SOPU_PWR_UP 0x1 +#define PWRM2_I2SIPU_PWR_DOWN 0x0 +#define PWRM2_I2SIPU_PWR_UP 0x1 + +// *** PWRM3 *** +#define FB_PWRM3_BGSBUP 6 +#define FM_PWRM3_BGSBUP 0x40 +#define FV_BGSBUP_ON 0x0 +#define FV_BGSBUP_OFF 0x40 + +#define FB_PWRM3_VGBAPU 5 +#define FM_PWRM3_VGBAPU 0x20 +#define FV_VGBAPU_ON 0x0 +#define FV_VGBAPU_OFF 0x20 + +#define FB_PWRM3_LLINEPU 4 +#define FM_PWRM3_LLINEPU 0x10 + +#define FB_PWRM3_RLINEPU 3 +#define FM_PWRM3_RLINEPU 0x8 + +// *** PWRM4 *** +#define FB_PWRM4_OPSUBPU 4 +#define FM_PWRM4_OPSUBPU 0x10 + +#define FB_PWRM4_OPDACLPU 3 +#define FM_PWRM4_OPDACLPU 0x8 + +#define FB_PWRM4_OPDACRPU 2 +#define FM_PWRM4_OPDACRPU 0x4 + +#define FB_PWRM4_OPSPKLPU 1 +#define FM_PWRM4_OPSPKLPU 0x2 + +#define FB_PWRM4_OPSPKRPU 0 +#define FM_PWRM4_OPSPKRPU 0x1 + +// *** I2SIDCTL *** +#define FB_I2SIDCTL_I2SI3DCTL 4 +#define FM_I2SIDCTL_I2SI3DCTL 0x30 + +#define FB_I2SIDCTL_I2SI2DCTL 2 +#define FM_I2SIDCTL_I2SI2DCTL 0xC + +#define FB_I2SIDCTL_I2SI1DCTL 0 +#define FM_I2SIDCTL_I2SI1DCTL 0x3 + +// *** I2SODCTL *** +#define FB_I2SODCTL_I2SO3DCTL 4 +#define FM_I2SODCTL_I2SO3DCTL 0x30 + +#define FB_I2SODCTL_I2SO2DCTL 2 +#define FM_I2SODCTL_I2SO2DCTL 0xC + +#define FB_I2SODCTL_I2SO1DCTL 0 +#define FM_I2SODCTL_I2SO1DCTL 0x3 + +// *** AUDIOMUX1 *** +#define FB_AUDIOMUX1_ASRCIMUX 6 +#define FM_AUDIOMUX1_ASRCIMUX 0xC0 +#define FV_ASRCIMUX_NONE 0x0 +#define FV_ASRCIMUX_I2S1 0x40 +#define FV_ASRCIMUX_I2S2 0x80 +#define FV_ASRCIMUX_I2S3 0xC0 + +#define FB_AUDIOMUX1_I2S2MUX 3 +#define FM_AUDIOMUX1_I2S2MUX 0x38 +#define FV_I2S2MUX_I2S1 0x0 +#define FV_I2S2MUX_I2S2 0x8 +#define FV_I2S2MUX_I2S3 0x10 +#define FV_I2S2MUX_ADC_DMIC 0x18 +#define FV_I2S2MUX_DMIC2 0x20 +#define FV_I2S2MUX_CLASSD_DSP 0x28 +#define FV_I2S2MUX_DAC_DSP 0x30 +#define FV_I2S2MUX_SUB_DSP 0x38 + +#define FB_AUDIOMUX1_I2S1MUX 0 +#define FM_AUDIOMUX1_I2S1MUX 0x7 +#define FV_I2S1MUX_I2S1 0x0 +#define FV_I2S1MUX_I2S2 0x1 +#define FV_I2S1MUX_I2S3 0x2 +#define FV_I2S1MUX_ADC_DMIC 0x3 +#define FV_I2S1MUX_DMIC2 0x4 +#define FV_I2S1MUX_CLASSD_DSP 0x5 +#define FV_I2S1MUX_DAC_DSP 0x6 +#define FV_I2S1MUX_SUB_DSP 0x7 + +#define AUDIOMUX1_I2SMUX_I2S1 0x0 +#define AUDIOMUX1_I2SMUX_I2S2 0x1 +#define AUDIOMUX1_I2SMUX_I2S3 0x2 +#define AUDIOMUX1_I2SMUX_ADC_DMIC 0x3 +#define AUDIOMUX1_I2SMUX_DMIC2 0x4 +#define AUDIOMUX1_I2SMUX_CLASSD_DSP 0x5 +#define AUDIOMUX1_I2SMUX_DAC_DSP 0x6 +#define AUDIOMUX1_I2SMUX_SUB_DSP 0x7 + +// *** AUDIOMUX2 *** +#define FB_AUDIOMUX2_ASRCOMUX 6 +#define FM_AUDIOMUX2_ASRCOMUX 0xC0 +#define FV_ASRCOMUX_NONE 0x0 +#define FV_ASRCOMUX_I2S1 0x40 +#define FV_ASRCOMUX_I2S2 0x80 +#define FV_ASRCOMUX_I2S3 0xC0 + +#define FB_AUDIOMUX2_DACMUX 3 +#define FM_AUDIOMUX2_DACMUX 0x38 +#define FV_DACMUX_I2S1 0x0 +#define FV_DACMUX_I2S2 0x8 +#define FV_DACMUX_I2S3 0x10 +#define FV_DACMUX_ADC_DMIC 0x18 +#define FV_DACMUX_DMIC2 0x20 +#define FV_DACMUX_CLASSD_DSP 0x28 +#define FV_DACMUX_DAC_DSP 0x30 +#define FV_DACMUX_SUB_DSP 0x38 + +#define FB_AUDIOMUX2_I2S3MUX 0 +#define FM_AUDIOMUX2_I2S3MUX 0x7 +#define FV_I2S3MUX_I2S1 0x0 +#define FV_I2S3MUX_I2S2 0x1 +#define FV_I2S3MUX_I2S3 0x2 +#define FV_I2S3MUX_ADC_DMIC 0x3 +#define FV_I2S3MUX_DMIC2 0x4 +#define FV_I2S3MUX_CLASSD_DSP 0x5 +#define FV_I2S3MUX_DAC_DSP 0x6 +#define FV_I2S3MUX_SUB_DSP 0x7 + +// *** AUDIOMUX3 *** +#define FB_AUDIOMUX3_SUBMUX 3 +#define FM_AUDIOMUX3_SUBMUX 0xF8 +#define FV_SUBMUX_I2S1_L 0x0 +#define FV_SUBMUX_I2S1_R 0x8 +#define FV_SUBMUX_I2S1_LR 0x10 +#define FV_SUBMUX_I2S2_L 0x18 +#define FV_SUBMUX_I2S2_R 0x20 +#define FV_SUBMUX_I2S2_LR 0x28 +#define FV_SUBMUX_I2S3_L 0x30 +#define FV_SUBMUX_I2S3_R 0x38 +#define FV_SUBMUX_I2S3_LR 0x40 +#define FV_SUBMUX_ADC_DMIC_L 0x48 +#define FV_SUBMUX_ADC_DMIC_R 0x50 +#define FV_SUBMUX_ADC_DMIC_LR 0x58 +#define FV_SUBMUX_DMIC_L 0x60 +#define FV_SUBMUX_DMIC_R 0x68 +#define FV_SUBMUX_DMIC_LR 0x70 +#define FV_SUBMUX_CLASSD_DSP_L 0x78 +#define FV_SUBMUX_CLASSD_DSP_R 0x80 +#define FV_SUBMUX_CLASSD_DSP_LR 0x88 + +#define FB_AUDIOMUX3_CLSSDMUX 0 +#define FM_AUDIOMUX3_CLSSDMUX 0x7 +#define FV_CLSSDMUX_I2S1 0x0 +#define FV_CLSSDMUX_I2S2 0x1 +#define FV_CLSSDMUX_I2S3 0x2 +#define FV_CLSSDMUX_ADC_DMIC 0x3 +#define FV_CLSSDMUX_DMIC2 0x4 +#define FV_CLSSDMUX_CLASSD_DSP 0x5 +#define FV_CLSSDMUX_DAC_DSP 0x6 +#define FV_CLSSDMUX_SUB_DSP 0x7 + +// *** HSDCTL1 *** +#define FB_HSDCTL1_HPJKTYPE 7 +#define FM_HSDCTL1_HPJKTYPE 0x80 + +#define FB_HSDCTL1_CON_DET_PWD 6 +#define FM_HSDCTL1_CON_DET_PWD 0x40 + +#define FB_HSDCTL1_DETCYC 4 +#define FM_HSDCTL1_DETCYC 0x30 + +#define FB_HSDCTL1_HPDLYBYP 3 +#define FM_HSDCTL1_HPDLYBYP 0x8 + +#define FB_HSDCTL1_HSDETPOL 2 +#define FM_HSDCTL1_HSDETPOL 0x4 + +#define FB_HSDCTL1_HPID_EN 1 +#define FM_HSDCTL1_HPID_EN 0x2 + +#define FB_HSDCTL1_GBLHS_EN 0 +#define FM_HSDCTL1_GBLHS_EN 0x1 + +// *** HSDCTL2 *** +#define FB_HSDCTL2_FMICBIAS1 6 +#define FM_HSDCTL2_FMICBIAS1 0xC0 + +#define FB_HSDCTL2_MB1MODE 5 +#define FM_HSDCTL2_MB1MODE 0x20 +#define FV_MB1MODE_AUTO 0x0 +#define FV_MB1MODE_MANUAL 0x20 + +#define FB_HSDCTL2_FORCETRG 4 +#define FM_HSDCTL2_FORCETRG 0x10 + +#define FB_HSDCTL2_SWMODE 3 +#define FM_HSDCTL2_SWMODE 0x8 + +#define FB_HSDCTL2_GHSHIZ 2 +#define FM_HSDCTL2_GHSHIZ 0x4 + +#define FB_HSDCTL2_FPLUGTYPE 0 +#define FM_HSDCTL2_FPLUGTYPE 0x3 + +// *** HSDSTAT *** +#define FB_HSDSTAT_MBIAS1DRV 5 +#define FM_HSDSTAT_MBIAS1DRV 0x60 + +#define FB_HSDSTAT_HSDETSTAT 3 +#define FM_HSDSTAT_HSDETSTAT 0x8 + +#define FB_HSDSTAT_PLUGTYPE 1 +#define FM_HSDSTAT_PLUGTYPE 0x6 + +#define FB_HSDSTAT_HSDETDONE 0 +#define FM_HSDSTAT_HSDETDONE 0x1 + +// *** HSDDELAY *** +#define FB_HSDDELAY_T_STABLE 0 +#define FM_HSDDELAY_T_STABLE 0x7 + +// *** BUTCTL *** +#define FB_BUTCTL_BPUSHSTAT 7 +#define FM_BUTCTL_BPUSHSTAT 0x80 + +#define FB_BUTCTL_BPUSHDET 6 +#define FM_BUTCTL_BPUSHDET 0x40 + +#define FB_BUTCTL_BPUSHEN 5 +#define FM_BUTCTL_BPUSHEN 0x20 + +#define FB_BUTCTL_BSTABLE_L 3 +#define FM_BUTCTL_BSTABLE_L 0x18 + +#define FB_BUTCTL_BSTABLE_S 0 +#define FM_BUTCTL_BSTABLE_S 0x7 + +// *** CH0AIC *** +#define FB_CH0AIC_INSELL 6 +#define FM_CH0AIC_INSELL 0xC0 + +#define FB_CH0AIC_MICBST0 4 +#define FM_CH0AIC_MICBST0 0x30 + +#define FB_CH0AIC_LADCIN 2 +#define FM_CH0AIC_LADCIN 0xC + +#define FB_CH0AIC_IN_BYPS_L_SEL 1 +#define FM_CH0AIC_IN_BYPS_L_SEL 0x2 + +#define FB_CH0AIC_IPCH0S 0 +#define FM_CH0AIC_IPCH0S 0x1 + +// *** CH1AIC *** +#define FB_CH1AIC_INSELR 6 +#define FM_CH1AIC_INSELR 0xC0 + +#define FB_CH1AIC_MICBST1 4 +#define FM_CH1AIC_MICBST1 0x30 + +#define FB_CH1AIC_RADCIN 2 +#define FM_CH1AIC_RADCIN 0xC + +#define FB_CH1AIC_IN_BYPS_R_SEL 1 +#define FM_CH1AIC_IN_BYPS_R_SEL 0x2 + +#define FB_CH1AIC_IPCH1S 0 +#define FM_CH1AIC_IPCH1S 0x1 + +// *** ICTL0 *** +#define FB_ICTL0_IN1POL 7 +#define FM_ICTL0_IN1POL 0x80 + +#define FB_ICTL0_IN0POL 6 +#define FM_ICTL0_IN0POL 0x40 + +#define FB_ICTL0_INPCH10SEL 4 +#define FM_ICTL0_INPCH10SEL 0x30 + +#define FB_ICTL0_IN1MUTE 3 +#define FM_ICTL0_IN1MUTE 0x8 + +#define FB_ICTL0_IN0MUTE 2 +#define FM_ICTL0_IN0MUTE 0x4 + +#define FB_ICTL0_IN1HP 1 +#define FM_ICTL0_IN1HP 0x2 + +#define FB_ICTL0_IN0HP 0 +#define FM_ICTL0_IN0HP 0x1 + +// *** ICTL1 *** +#define FB_ICTL1_IN3POL 7 +#define FM_ICTL1_IN3POL 0x80 + +#define FB_ICTL1_IN2POL 6 +#define FM_ICTL1_IN2POL 0x40 + +#define FB_ICTL1_INPCH32SEL 4 +#define FM_ICTL1_INPCH32SEL 0x30 + +#define FB_ICTL1_IN3MUTE 3 +#define FM_ICTL1_IN3MUTE 0x8 + +#define FB_ICTL1_IN2MUTE 2 +#define FM_ICTL1_IN2MUTE 0x4 + +#define FB_ICTL1_IN3HP 1 +#define FM_ICTL1_IN3HP 0x2 + +#define FB_ICTL1_IN2HP 0 +#define FM_ICTL1_IN2HP 0x1 + +// *** MICBIAS *** +#define FB_MICBIAS_MICBOV2 4 +#define FM_MICBIAS_MICBOV2 0x30 + +#define FB_MICBIAS_MICBOV1 6 +#define FM_MICBIAS_MICBOV1 0xC0 + +#define FB_MICBIAS_SPARE1 2 +#define FM_MICBIAS_SPARE1 0xC + +#define FB_MICBIAS_SPARE2 0 +#define FM_MICBIAS_SPARE2 0x3 + +// *** PGAZ *** +#define FB_PGAZ_INHPOR 1 +#define FM_PGAZ_INHPOR 0x2 + +#define FB_PGAZ_TOEN 0 +#define FM_PGAZ_TOEN 0x1 + +// *** ASRCILVOL *** +#define FB_ASRCILVOL_ASRCILVOL 0 +#define FM_ASRCILVOL_ASRCILVOL 0xFF + +// *** ASRCIRVOL *** +#define FB_ASRCIRVOL_ASRCIRVOL 0 +#define FM_ASRCIRVOL_ASRCIRVOL 0xFF + +// *** ASRCOLVOL *** +#define FB_ASRCOLVOL_ASRCOLVOL 0 +#define FM_ASRCOLVOL_ASRCOLVOL 0xFF + +// *** ASRCORVOL *** +#define FB_ASRCORVOL_ASRCOLVOL 0 +#define FM_ASRCORVOL_ASRCOLVOL 0xFF + +// *** IVOLCTLU *** +#define FB_IVOLCTLU_IFADE 3 +#define FM_IVOLCTLU_IFADE 0x8 + +#define FB_IVOLCTLU_INPVOLU 2 +#define FM_IVOLCTLU_INPVOLU 0x4 + +#define FB_IVOLCTLU_PGAVOLU 1 +#define FM_IVOLCTLU_PGAVOLU 0x2 + +#define FB_IVOLCTLU_ASRCVOLU 0 +#define FM_IVOLCTLU_ASRCVOLU 0x1 + +// *** ALCCTL0 *** +#define FB_ALCCTL0_ALCMODE 7 +#define FM_ALCCTL0_ALCMODE 0x80 + +#define FB_ALCCTL0_ALCREF 4 +#define FM_ALCCTL0_ALCREF 0x70 + +#define FB_ALCCTL0_ALCEN3 3 +#define FM_ALCCTL0_ALCEN3 0x8 + +#define FB_ALCCTL0_ALCEN2 2 +#define FM_ALCCTL0_ALCEN2 0x4 + +#define FB_ALCCTL0_ALCEN1 1 +#define FM_ALCCTL0_ALCEN1 0x2 + +#define FB_ALCCTL0_ALCEN0 0 +#define FM_ALCCTL0_ALCEN0 0x1 + +// *** ALCCTL1 *** +#define FB_ALCCTL1_MAXGAIN 4 +#define FM_ALCCTL1_MAXGAIN 0x70 + +#define FB_ALCCTL1_ALCL 0 +#define FM_ALCCTL1_ALCL 0xF + +// *** ALCCTL2 *** +#define FB_ALCCTL2_ALCZC 7 +#define FM_ALCCTL2_ALCZC 0x80 + +#define FB_ALCCTL2_MINGAIN 4 +#define FM_ALCCTL2_MINGAIN 0x70 + +#define FB_ALCCTL2_HLD 0 +#define FM_ALCCTL2_HLD 0xF + +// *** ALCCTL3 *** +#define FB_ALCCTL3_DCY 4 +#define FM_ALCCTL3_DCY 0xF0 + +#define FB_ALCCTL3_ATK 0 +#define FM_ALCCTL3_ATK 0xF + +// *** NGATE *** +#define FB_NGATE_NGTH 3 +#define FM_NGATE_NGTH 0xF8 + +#define FB_NGATE_NGG 1 +#define FM_NGATE_NGG 0x6 + +#define FB_NGATE_NGAT 0 +#define FM_NGATE_NGAT 0x1 + +// *** DMICCTL *** +#define FB_DMICCTL_DMIC2EN 7 +#define FM_DMICCTL_DMIC2EN 0x80 + +#define FB_DMICCTL_DMIC1EN 6 +#define FM_DMICCTL_DMIC1EN 0x40 + +#define FB_DMICCTL_DMONO 4 +#define FM_DMICCTL_DMONO 0x10 + +#define FB_DMICCTL_DMDCLK 2 +#define FM_DMICCTL_DMDCLK 0xC + +#define FB_DMICCTL_DMRATE 0 +#define FM_DMICCTL_DMRATE 0x3 + +// *** DACCTL *** +#define FB_DACCTL_DACPOLR 7 +#define FM_DACCTL_DACPOLR 0x80 +#define FV_DACPOLR_NORMAL 0x0 +#define FV_DACPOLR_INVERTED 0x80 + +#define FB_DACCTL_DACPOLL 6 +#define FM_DACCTL_DACPOLL 0x40 +#define FV_DACPOLL_NORMAL 0x0 +#define FV_DACPOLL_INVERTED 0x40 + +#define FB_DACCTL_DACDITH 4 +#define FM_DACCTL_DACDITH 0x30 +#define FV_DACDITH_DYNAMIC_HALF 0x0 +#define FV_DACDITH_DYNAMIC_FULL 0x10 +#define FV_DACDITH_DISABLED 0x20 +#define FV_DACDITH_STATIC 0x30 + +#define FB_DACCTL_DACMUTE 3 +#define FM_DACCTL_DACMUTE 0x8 +#define FV_DACMUTE_ENABLE 0x8 +#define FV_DACMUTE_DISABLE 0x0 + +#define FB_DACCTL_DACDEM 2 +#define FM_DACCTL_DACDEM 0x4 +#define FV_DACDEM_ENABLE 0x4 +#define FV_DACDEM_DISABLE 0x0 + +#define FB_DACCTL_ABYPASS 0 +#define FM_DACCTL_ABYPASS 0x1 + +// *** SPKCTL *** +#define FB_SPKCTL_SPKPOLR 7 +#define FM_SPKCTL_SPKPOLR 0x80 +#define FV_SPKPOLR_NORMAL 0x0 +#define FV_SPKPOLR_INVERTED 0x80 + +#define FB_SPKCTL_SPKPOLL 6 +#define FM_SPKCTL_SPKPOLL 0x40 +#define FV_SPKPOLL_NORMAL 0x0 +#define FV_SPKPOLL_INVERTED 0x40 + +#define FB_SPKCTL_SPKMUTE 3 +#define FM_SPKCTL_SPKMUTE 0x8 +#define FV_SPKMUTE_ENABLE 0x8 +#define FV_SPKMUTE_DISABLE 0x0 + +#define FB_SPKCTL_SPKDEM 2 +#define FM_SPKCTL_SPKDEM 0x4 +#define FV_SPKDEM_ENABLE 0x4 +#define FV_SPKDEM_DISABLE 0x0 + +// *** SUBCTL *** +#define FB_SUBCTL_SUBPOL 7 +#define FM_SUBCTL_SUBPOL 0x80 + +#define FB_SUBCTL_SUBMUTE 3 +#define FM_SUBCTL_SUBMUTE 0x8 + +#define FB_SUBCTL_SUBDEM 2 +#define FM_SUBCTL_SUBDEM 0x4 + +#define FB_SUBCTL_SUBMUX 1 +#define FM_SUBCTL_SUBMUX 0x2 + +#define FB_SUBCTL_SUBILMDIS 0 +#define FM_SUBCTL_SUBILMDIS 0x1 + +// *** DCCTL *** +#define FB_DCCTL_SUBDCBYP 7 +#define FM_DCCTL_SUBDCBYP 0x80 + +#define FB_DCCTL_DACDCBYP 6 +#define FM_DCCTL_DACDCBYP 0x40 + +#define FB_DCCTL_SPKDCBYP 5 +#define FM_DCCTL_SPKDCBYP 0x20 + +#define FB_DCCTL_DCCOEFSEL 0 +#define FM_DCCTL_DCCOEFSEL 0x7 + +// *** OVOLCTLU *** +#define FB_OVOLCTLU_OFADE 4 +#define FM_OVOLCTLU_OFADE 0x10 + +#define FB_OVOLCTLU_SUBVOLU 3 +#define FM_OVOLCTLU_SUBVOLU 0x8 + +#define FB_OVOLCTLU_MVOLU 2 +#define FM_OVOLCTLU_MVOLU 0x4 + +#define FB_OVOLCTLU_SPKVOLU 1 +#define FM_OVOLCTLU_SPKVOLU 0x2 + +#define FB_OVOLCTLU_HPVOLU 0 +#define FM_OVOLCTLU_HPVOLU 0x1 + +// *** MUTEC *** +#define FB_MUTEC_ZDSTAT 7 +#define FM_MUTEC_ZDSTAT 0x80 + +#define FB_MUTEC_ZDLEN 4 +#define FM_MUTEC_ZDLEN 0x30 + +#define FB_MUTEC_APWD 3 +#define FM_MUTEC_APWD 0x8 + +#define FB_MUTEC_AMUTE 2 +#define FM_MUTEC_AMUTE 0x4 + +// *** MVOLL *** +#define FB_MVOLL_MVOL_L 0 +#define FM_MVOLL_MVOL_L 0xFF + +// *** MVOLR *** +#define FB_MVOLR_MVOL_R 0 +#define FM_MVOLR_MVOL_R 0xFF + +// *** HPVOLL *** +#define FB_HPVOLL_HPVOL_L 0 +#define FM_HPVOLL_HPVOL_L 0x7F + +// *** HPVOLR *** +#define FB_HPVOLR_HPVOL_R 0 +#define FM_HPVOLR_HPVOL_R 0x7F + +// *** SPKVOLL *** +#define FB_SPKVOLL_SPKVOL_L 0 +#define FM_SPKVOLL_SPKVOL_L 0x7F + +// *** SPKVOLR *** +#define FB_SPKVOLR_SPKVOL_R 0 +#define FM_SPKVOLR_SPKVOL_R 0x7F + +// *** SUBVOL *** +#define FB_SUBVOL_SUBVOL 0 +#define FM_SUBVOL_SUBVOL 0x7F + +// *** COP0 *** +#define FB_COP0_COPATTEN 7 +#define FM_COP0_COPATTEN 0x80 + +#define FB_COP0_COPGAIN 6 +#define FM_COP0_COPGAIN 0x40 + +#define FB_COP0_HDELTAEN 5 +#define FM_COP0_HDELTAEN 0x20 + +#define FB_COP0_COPTARGET 0 +#define FM_COP0_COPTARGET 0x1F + +// *** COP1 *** +#define FB_COP1_HDCOMPMODE 6 +#define FM_COP1_HDCOMPMODE 0x40 + +#define FB_COP1_AVGLENGTH 2 +#define FM_COP1_AVGLENGTH 0x3C + +#define FB_COP1_MONRATE 0 +#define FM_COP1_MONRATE 0x3 + +// *** COPSTAT *** +#define FB_COPSTAT_HDELTADET 7 +#define FM_COPSTAT_HDELTADET 0x80 + +#define FB_COPSTAT_UV 6 +#define FM_COPSTAT_UV 0x40 + +#define FB_COPSTAT_COPADJ 0 +#define FM_COPSTAT_COPADJ 0x3F + +// *** PWM0 *** +#define FB_PWM0_SCTO 6 +#define FM_PWM0_SCTO 0xC0 + +#define FB_PWM0_UVLO 5 +#define FM_PWM0_UVLO 0x20 + +#define FB_PWM0_BFDIS 3 +#define FM_PWM0_BFDIS 0x8 + +#define FB_PWM0_PWMMODE 2 +#define FM_PWM0_PWMMODE 0x4 + +#define FB_PWM0_NOOFFSET 0 +#define FM_PWM0_NOOFFSET 0x1 + +// *** PWM1 *** +#define FB_PWM1_DITHPOS 4 +#define FM_PWM1_DITHPOS 0x70 + +#define FB_PWM1_DYNDITH 1 +#define FM_PWM1_DYNDITH 0x2 + +#define FB_PWM1_DITHDIS 0 +#define FM_PWM1_DITHDIS 0x1 + +// *** PWM2 *** +// *** PWM3 *** +#define FB_PWM3_PWMMUX 6 +#define FM_PWM3_PWMMUX 0xC0 + +#define FB_PWM3_CVALUE 0 +#define FM_PWM3_CVALUE 0x7 + +// *** HPSW *** +#define FB_HPSW_HPDETSTATE 4 +#define FM_HPSW_HPDETSTATE 0x10 + +#define FB_HPSW_HPSWEN 2 +#define FM_HPSW_HPSWEN 0xC + +#define FB_HPSW_HPSWPOL 1 +#define FM_HPSW_HPSWPOL 0x2 + +#define FB_HPSW_TSDEN 0 +#define FM_HPSW_TSDEN 0x1 + +// *** THERMTS *** +#define FB_THERMTS_TRIPHS 7 +#define FM_THERMTS_TRIPHS 0x80 + +#define FB_THERMTS_TRIPLS 6 +#define FM_THERMTS_TRIPLS 0x40 + +#define FB_THERMTS_TRIPSPLIT 4 +#define FM_THERMTS_TRIPSPLIT 0x30 + +#define FB_THERMTS_TRIPSHIFT 2 +#define FM_THERMTS_TRIPSHIFT 0xC + +#define FB_THERMTS_TSPOLL 0 +#define FM_THERMTS_TSPOLL 0x3 + +// *** THERMSPK1 *** +#define FB_THERMSPK1_FORCEPWD 7 +#define FM_THERMSPK1_FORCEPWD 0x80 + +#define FB_THERMSPK1_INSTCUTMODE 6 +#define FM_THERMSPK1_INSTCUTMODE 0x40 + +#define FB_THERMSPK1_INCRATIO 4 +#define FM_THERMSPK1_INCRATIO 0x30 + +#define FB_THERMSPK1_INCSTEP 2 +#define FM_THERMSPK1_INCSTEP 0xC + +#define FB_THERMSPK1_DECSTEP 0 +#define FM_THERMSPK1_DECSTEP 0x3 + +// *** THERMSTAT *** +#define FB_THERMSTAT_FPWDS 7 +#define FM_THERMSTAT_FPWDS 0x80 + +#define FB_THERMSTAT_VOLSTAT 0 +#define FM_THERMSTAT_VOLSTAT 0x7F + +// *** SCSTAT *** +#define FB_SCSTAT_ESDF 3 +#define FM_SCSTAT_ESDF 0x18 + +#define FB_SCSTAT_CPF 2 +#define FM_SCSTAT_CPF 0x4 + +#define FB_SCSTAT_CLSDF 0 +#define FM_SCSTAT_CLSDF 0x3 + +// *** SDMON *** +#define FB_SDMON_SDFORCE 7 +#define FM_SDMON_SDFORCE 0x80 + +#define FB_SDMON_SDVALUE 0 +#define FM_SDMON_SDVALUE 0x1F + +// *** SPKEQFILT *** +#define FB_SPKEQFILT_EQ2EN 7 +#define FM_SPKEQFILT_EQ2EN 0x80 +#define FV_EQ2EN_ENABLE 0x80 +#define FV_EQ2EN_DISABLE 0x0 + +#define FB_SPKEQFILT_EQ2BE 4 +#define FM_SPKEQFILT_EQ2BE 0x70 + +#define FB_SPKEQFILT_EQ1EN 3 +#define FM_SPKEQFILT_EQ1EN 0x8 +#define FV_EQ1EN_ENABLE 0x8 +#define FV_EQ1EN_DISABLE 0x0 + +#define FB_SPKEQFILT_EQ1BE 0 +#define FM_SPKEQFILT_EQ1BE 0x7 + +#define SPKEQFILT_EQEN_ENABLE 0x1 +#define SPKEQFILT_EQEN_DISABLE 0x0 + +// *** SPKCRWDL *** +#define FB_SPKCRWDL_WDATA_L 0 +#define FM_SPKCRWDL_WDATA_L 0xFF + +// *** SPKCRWDM *** +#define FB_SPKCRWDM_WDATA_M 0 +#define FM_SPKCRWDM_WDATA_M 0xFF + +// *** SPKCRWDH *** +#define FB_SPKCRWDH_WDATA_H 0 +#define FM_SPKCRWDH_WDATA_H 0xFF + +// *** SPKCRRDL *** +#define FB_SPKCRRDL_RDATA_L 0 +#define FM_SPKCRRDL_RDATA_L 0xFF + +// *** SPKCRRDM *** +#define FB_SPKCRRDM_RDATA_M 0 +#define FM_SPKCRRDM_RDATA_M 0xFF + +// *** SPKCRRDH *** +#define FB_SPKCRRDH_RDATA_H 0 +#define FM_SPKCRRDH_RDATA_H 0xFF + +// *** SPKCRADD *** +#define FB_SPKCRADD_ADDRESS 0 +#define FM_SPKCRADD_ADDRESS 0xFF + +// *** SPKCRS *** +#define FB_SPKCRS_ACCSTAT 7 +#define FM_SPKCRS_ACCSTAT 0x80 + +// *** SPKMBCEN *** +#define FB_SPKMBCEN_MBCEN3 2 +#define FM_SPKMBCEN_MBCEN3 0x4 +#define FV_MBCEN3_ENABLE 0x4 +#define FV_MBCEN3_DISABLE 0x0 + +#define FB_SPKMBCEN_MBCEN2 1 +#define FM_SPKMBCEN_MBCEN2 0x2 +#define FV_MBCEN2_ENABLE 0x2 +#define FV_MBCEN2_DISABLE 0x0 + +#define FB_SPKMBCEN_MBCEN1 0 +#define FM_SPKMBCEN_MBCEN1 0x1 +#define FV_MBCEN1_ENABLE 0x1 +#define FV_MBCEN1_DISABLE 0x0 + +#define SPKMBCEN_MBCEN_ENABLE 0x1 +#define SPKMBCEN_MBCEN_DISABLE 0x0 + +// *** SPKMBCCTL *** +#define FB_SPKMBCCTL_LVLMODE3 5 +#define FM_SPKMBCCTL_LVLMODE3 0x20 + +#define FB_SPKMBCCTL_WINSEL3 4 +#define FM_SPKMBCCTL_WINSEL3 0x10 + +#define FB_SPKMBCCTL_LVLMODE2 3 +#define FM_SPKMBCCTL_LVLMODE2 0x8 + +#define FB_SPKMBCCTL_WINSEL2 2 +#define FM_SPKMBCCTL_WINSEL2 0x4 + +#define FB_SPKMBCCTL_LVLMODE1 1 +#define FM_SPKMBCCTL_LVLMODE1 0x2 + +#define FB_SPKMBCCTL_WINSEL1 0 +#define FM_SPKMBCCTL_WINSEL1 0x1 + +// *** SPKCLECTL *** +#define FB_SPKCLECTL_LVLMODE 4 +#define FM_SPKCLECTL_LVLMODE 0x10 + +#define FB_SPKCLECTL_WINSEL 3 +#define FM_SPKCLECTL_WINSEL 0x8 + +#define FB_SPKCLECTL_EXPEN 2 +#define FM_SPKCLECTL_EXPEN 0x4 +#define FV_EXPEN_ENABLE 0x4 +#define FV_EXPEN_DISABLE 0x0 + +#define FB_SPKCLECTL_LIMEN 1 +#define FM_SPKCLECTL_LIMEN 0x2 +#define FV_LIMEN_ENABLE 0x2 +#define FV_LIMEN_DISABLE 0x0 + +#define FB_SPKCLECTL_COMPEN 0 +#define FM_SPKCLECTL_COMPEN 0x1 +#define FV_COMPEN_ENABLE 0x1 +#define FV_COMPEN_DISABLE 0x0 + +// *** SPKCLEMUG *** +#define FB_SPKCLEMUG_MUGAIN 0 +#define FM_SPKCLEMUG_MUGAIN 0x1F + +// *** SPKCOMPTHR *** +#define FB_SPKCOMPTHR_THRESH 0 +#define FM_SPKCOMPTHR_THRESH 0xFF + +// *** SPKCOMPRAT *** +#define FB_SPKCOMPRAT_RATIO 0 +#define FM_SPKCOMPRAT_RATIO 0x1F + +// *** SPKCOMPATKL *** +#define FB_SPKCOMPATKL_TCATKL 0 +#define FM_SPKCOMPATKL_TCATKL 0xFF + +// *** SPKCOMPATKH *** +#define FB_SPKCOMPATKH_TCATKH 0 +#define FM_SPKCOMPATKH_TCATKH 0xFF + +// *** SPKCOMPRELL *** +#define FB_SPKCOMPRELL_TCRELL 0 +#define FM_SPKCOMPRELL_TCRELL 0xFF + +// *** SPKCOMPRELH *** +#define FB_SPKCOMPRELH_TCRELH 0 +#define FM_SPKCOMPRELH_TCRELH 0xFF + +// *** SPKLIMTHR *** +#define FB_SPKLIMTHR_THRESH 0 +#define FM_SPKLIMTHR_THRESH 0xFF + +// *** SPKLIMTGT *** +#define FB_SPKLIMTGT_TARGET 0 +#define FM_SPKLIMTGT_TARGET 0xFF + +// *** SPKLIMATKL *** +#define FB_SPKLIMATKL_TCATKL 0 +#define FM_SPKLIMATKL_TCATKL 0xFF + +// *** SPKLIMATKH *** +#define FB_SPKLIMATKH_TCATKH 0 +#define FM_SPKLIMATKH_TCATKH 0xFF + +// *** SPKLIMRELL *** +#define FB_SPKLIMRELL_TCRELL 0 +#define FM_SPKLIMRELL_TCRELL 0xFF + +// *** SPKLIMRELH *** +#define FB_SPKLIMRELH_TCRELH 0 +#define FM_SPKLIMRELH_TCRELH 0xFF + +// *** SPKEXPTHR *** +#define FB_SPKEXPTHR_THRESH 0 +#define FM_SPKEXPTHR_THRESH 0xFF + +// *** SPKEXPRAT *** +#define FB_SPKEXPRAT_RATIO 0 +#define FM_SPKEXPRAT_RATIO 0x7 + +// *** SPKEXPATKL *** +#define FB_SPKEXPATKL_TCATKL 0 +#define FM_SPKEXPATKL_TCATKL 0xFF + +// *** SPKEXPATKH *** +#define FB_SPKEXPATKH_TCATKH 0 +#define FM_SPKEXPATKH_TCATKH 0xFF + +// *** SPKEXPRELL *** +#define FB_SPKEXPRELL_TCRELL 0 +#define FM_SPKEXPRELL_TCRELL 0xFF + +// *** SPKEXPRELH *** +#define FB_SPKEXPRELH_TCRELH 0 +#define FM_SPKEXPRELH_TCRELH 0xFF + +// *** SPKFXCTL *** +#define FB_SPKFXCTL_3DEN 4 +#define FM_SPKFXCTL_3DEN 0x10 + +#define FB_SPKFXCTL_TEEN 3 +#define FM_SPKFXCTL_TEEN 0x8 + +#define FB_SPKFXCTL_TNLFBYP 2 +#define FM_SPKFXCTL_TNLFBYP 0x4 + +#define FB_SPKFXCTL_BEEN 1 +#define FM_SPKFXCTL_BEEN 0x2 + +#define FB_SPKFXCTL_BNLFBYP 0 +#define FM_SPKFXCTL_BNLFBYP 0x1 + +// *** DACEQFILT *** +#define FB_DACEQFILT_EQ2EN 7 +#define FM_DACEQFILT_EQ2EN 0x80 +#define FV_EQ2EN_ENABLE 0x80 +#define FV_EQ2EN_DISABLE 0x0 + +#define FB_DACEQFILT_EQ2BE 4 +#define FM_DACEQFILT_EQ2BE 0x70 + +#define FB_DACEQFILT_EQ1EN 3 +#define FM_DACEQFILT_EQ1EN 0x8 +#define FV_EQ1EN_ENABLE 0x8 +#define FV_EQ1EN_DISABLE 0x0 + +#define FB_DACEQFILT_EQ1BE 0 +#define FM_DACEQFILT_EQ1BE 0x7 + +#define DACEQFILT_EQEN_ENABLE 0x1 +#define DACEQFILT_EQEN_DISABLE 0x0 + +// *** DACCRWDL *** +#define FB_DACCRWDL_WDATA_L 0 +#define FM_DACCRWDL_WDATA_L 0xFF + +// *** DACCRWDM *** +#define FB_DACCRWDM_WDATA_M 0 +#define FM_DACCRWDM_WDATA_M 0xFF + +// *** DACCRWDH *** +#define FB_DACCRWDH_WDATA_H 0 +#define FM_DACCRWDH_WDATA_H 0xFF + +// *** DACCRRDL *** +#define FB_DACCRRDL_RDATA_L 0 +#define FM_DACCRRDL_RDATA_L 0xFF + +// *** DACCRRDM *** +#define FB_DACCRRDM_RDATA_M 0 +#define FM_DACCRRDM_RDATA_M 0xFF + +// *** DACCRRDH *** +#define FB_DACCRRDH_RDATA_H 0 +#define FM_DACCRRDH_RDATA_H 0xFF + +// *** DACCRADD *** +#define FB_DACCRADD_ADDRESS 0 +#define FM_DACCRADD_ADDRESS 0xFF + +// *** DACCRS *** +#define FB_DACCRS_ACCSTAT 7 +#define FM_DACCRS_ACCSTAT 0x80 + +// *** DACMBCEN *** +#define FB_DACMBCEN_MBCEN3 2 +#define FM_DACMBCEN_MBCEN3 0x4 +#define FV_MBCEN3_ENABLE 0x4 +#define FV_MBCEN3_DISABLE 0x0 + +#define FB_DACMBCEN_MBCEN2 1 +#define FM_DACMBCEN_MBCEN2 0x2 +#define FV_MBCEN2_ENABLE 0x2 +#define FV_MBCEN2_DISABLE 0x0 + +#define FB_DACMBCEN_MBCEN1 0 +#define FM_DACMBCEN_MBCEN1 0x1 +#define FV_MBCEN1_ENABLE 0x1 +#define FV_MBCEN1_DISABLE 0x0 + +#define DACMBCEN_MBCEN_ENABLE 0x1 +#define DACMBCEN_MBCEN_DISABLE 0x0 + +// *** DACMBCCTL *** +#define FB_DACMBCCTL_LVLMODE3 5 +#define FM_DACMBCCTL_LVLMODE3 0x20 + +#define FB_DACMBCCTL_WINSEL3 4 +#define FM_DACMBCCTL_WINSEL3 0x10 + +#define FB_DACMBCCTL_LVLMODE2 3 +#define FM_DACMBCCTL_LVLMODE2 0x8 + +#define FB_DACMBCCTL_WINSEL2 2 +#define FM_DACMBCCTL_WINSEL2 0x4 + +#define FB_DACMBCCTL_LVLMODE1 1 +#define FM_DACMBCCTL_LVLMODE1 0x2 + +#define FB_DACMBCCTL_WINSEL1 0 +#define FM_DACMBCCTL_WINSEL1 0x1 + +// *** DACCLECTL *** +#define FB_DACCLECTL_LVLMODE 4 +#define FM_DACCLECTL_LVLMODE 0x10 + +#define FB_DACCLECTL_WINSEL 3 +#define FM_DACCLECTL_WINSEL 0x8 + +#define FB_DACCLECTL_EXPEN 2 +#define FM_DACCLECTL_EXPEN 0x4 +#define FV_EXPEN_ENABLE 0x4 +#define FV_EXPEN_DISABLE 0x0 + +#define FB_DACCLECTL_LIMEN 1 +#define FM_DACCLECTL_LIMEN 0x2 +#define FV_LIMEN_ENABLE 0x2 +#define FV_LIMEN_DISABLE 0x0 + +#define FB_DACCLECTL_COMPEN 0 +#define FM_DACCLECTL_COMPEN 0x1 +#define FV_COMPEN_ENABLE 0x1 +#define FV_COMPEN_DISABLE 0x0 + +// *** DACCLEMUG *** +#define FB_DACCLEMUG_MUGAIN 0 +#define FM_DACCLEMUG_MUGAIN 0x1F + +// *** DACCOMPTHR *** +#define FB_DACCOMPTHR_THRESH 0 +#define FM_DACCOMPTHR_THRESH 0xFF + +// *** DACCOMPRAT *** +#define FB_DACCOMPRAT_RATIO 0 +#define FM_DACCOMPRAT_RATIO 0x1F + +// *** DACCOMPATKL *** +#define FB_DACCOMPATKL_TCATKL 0 +#define FM_DACCOMPATKL_TCATKL 0xFF + +// *** DACCOMPATKH *** +#define FB_DACCOMPATKH_TCATKH 0 +#define FM_DACCOMPATKH_TCATKH 0xFF + +// *** DACCOMPRELL *** +#define FB_DACCOMPRELL_TCRELL 0 +#define FM_DACCOMPRELL_TCRELL 0xFF + +// *** DACCOMPRELH *** +#define FB_DACCOMPRELH_TCRELH 0 +#define FM_DACCOMPRELH_TCRELH 0xFF + +// *** DACLIMTHR *** +#define FB_DACLIMTHR_THRESH 0 +#define FM_DACLIMTHR_THRESH 0xFF + +// *** DACLIMTGT *** +#define FB_DACLIMTGT_TARGET 0 +#define FM_DACLIMTGT_TARGET 0xFF + +// *** DACLIMATKL *** +#define FB_DACLIMATKL_TCATKL 0 +#define FM_DACLIMATKL_TCATKL 0xFF + +// *** DACLIMATKH *** +#define FB_DACLIMATKH_TCATKH 0 +#define FM_DACLIMATKH_TCATKH 0xFF + +// *** DACLIMRELL *** +#define FB_DACLIMRELL_TCRELL 0 +#define FM_DACLIMRELL_TCRELL 0xFF + +// *** DACLIMRELH *** +#define FB_DACLIMRELH_TCRELH 0 +#define FM_DACLIMRELH_TCRELH 0xFF + +// *** DACEXPTHR *** +#define FB_DACEXPTHR_THRESH 0 +#define FM_DACEXPTHR_THRESH 0xFF + +// *** DACEXPRAT *** +#define FB_DACEXPRAT_RATIO 0 +#define FM_DACEXPRAT_RATIO 0x7 + +// *** DACEXPATKL *** +#define FB_DACEXPATKL_TCATKL 0 +#define FM_DACEXPATKL_TCATKL 0xFF + +// *** DACEXPATKH *** +#define FB_DACEXPATKH_TCATKH 0 +#define FM_DACEXPATKH_TCATKH 0xFF + +// *** DACEXPRELL *** +#define FB_DACEXPRELL_TCRELL 0 +#define FM_DACEXPRELL_TCRELL 0xFF + +// *** DACEXPRELH *** +#define FB_DACEXPRELH_TCRELH 0 +#define FM_DACEXPRELH_TCRELH 0xFF + +// *** DACFXCTL *** +#define FB_DACFXCTL_3DEN 4 +#define FM_DACFXCTL_3DEN 0x10 + +#define FB_DACFXCTL_TEEN 3 +#define FM_DACFXCTL_TEEN 0x8 + +#define FB_DACFXCTL_TNLFBYP 2 +#define FM_DACFXCTL_TNLFBYP 0x4 + +#define FB_DACFXCTL_BEEN 1 +#define FM_DACFXCTL_BEEN 0x2 + +#define FB_DACFXCTL_BNLFBYP 0 +#define FM_DACFXCTL_BNLFBYP 0x1 + +// *** SUBEQFILT *** +#define FB_SUBEQFILT_EQ2EN 7 +#define FM_SUBEQFILT_EQ2EN 0x80 +#define FV_EQ2EN_ENABLE 0x80 +#define FV_EQ2EN_DISABLE 0x0 + +#define FB_SUBEQFILT_EQ2BE 4 +#define FM_SUBEQFILT_EQ2BE 0x70 + +#define FB_SUBEQFILT_EQ1EN 3 +#define FM_SUBEQFILT_EQ1EN 0x8 +#define FV_EQ1EN_ENABLE 0x8 +#define FV_EQ1EN_DISABLE 0x0 + +#define FB_SUBEQFILT_EQ1BE 0 +#define FM_SUBEQFILT_EQ1BE 0x7 + +#define SUBEQFILT_EQEN_ENABLE 0x1 +#define SUBEQFILT_EQEN_DISABLE 0x0 + +// *** SUBCRWDL *** +#define FB_SUBCRWDL_WDATA_L 0 +#define FM_SUBCRWDL_WDATA_L 0xFF + +// *** SUBCRWDM *** +#define FB_SUBCRWDM_WDATA_M 0 +#define FM_SUBCRWDM_WDATA_M 0xFF + +// *** SUBCRWDH *** +#define FB_SUBCRWDH_WDATA_H 0 +#define FM_SUBCRWDH_WDATA_H 0xFF + +// *** SUBCRRDL *** +#define FB_SUBCRRDL_RDATA_L 0 +#define FM_SUBCRRDL_RDATA_L 0xFF + +// *** SUBCRRDM *** +#define FB_SUBCRRDM_RDATA_M 0 +#define FM_SUBCRRDM_RDATA_M 0xFF + +// *** SUBCRRDH *** +#define FB_SUBCRRDH_RDATA_H 0 +#define FM_SUBCRRDH_RDATA_H 0xFF + +// *** SUBCRADD *** +#define FB_SUBCRADD_ADDRESS 0 +#define FM_SUBCRADD_ADDRESS 0xFF + +// *** SUBCRS *** +#define FB_SUBCRS_ACCSTAT 7 +#define FM_SUBCRS_ACCSTAT 0x80 + +// *** SUBMBCEN *** +#define FB_SUBMBCEN_MBCEN3 2 +#define FM_SUBMBCEN_MBCEN3 0x4 +#define FV_MBCEN3_ENABLE 0x4 +#define FV_MBCEN3_DISABLE 0x0 + +#define FB_SUBMBCEN_MBCEN2 1 +#define FM_SUBMBCEN_MBCEN2 0x2 +#define FV_MBCEN2_ENABLE 0x2 +#define FV_MBCEN2_DISABLE 0x0 + +#define FB_SUBMBCEN_MBCEN1 0 +#define FM_SUBMBCEN_MBCEN1 0x1 +#define FV_MBCEN1_ENABLE 0x1 +#define FV_MBCEN1_DISABLE 0x0 + +#define SUBMBCEN_MBCEN_ENABLE 0x1 +#define SUBMBCEN_MBCEN_DISABLE 0x0 + +// *** SUBMBCCTL *** +#define FB_SUBMBCCTL_LVLMODE3 5 +#define FM_SUBMBCCTL_LVLMODE3 0x20 + +#define FB_SUBMBCCTL_WINSEL3 4 +#define FM_SUBMBCCTL_WINSEL3 0x10 + +#define FB_SUBMBCCTL_LVLMODE2 3 +#define FM_SUBMBCCTL_LVLMODE2 0x8 + +#define FB_SUBMBCCTL_WINSEL2 2 +#define FM_SUBMBCCTL_WINSEL2 0x4 + +#define FB_SUBMBCCTL_LVLMODE1 1 +#define FM_SUBMBCCTL_LVLMODE1 0x2 + +#define FB_SUBMBCCTL_WINSEL1 0 +#define FM_SUBMBCCTL_WINSEL1 0x1 + +// *** SUBCLECTL *** +#define FB_SUBCLECTL_LVLMODE 4 +#define FM_SUBCLECTL_LVLMODE 0x10 + +#define FB_SUBCLECTL_WINSEL 3 +#define FM_SUBCLECTL_WINSEL 0x8 + +#define FB_SUBCLECTL_EXPEN 2 +#define FM_SUBCLECTL_EXPEN 0x4 +#define FV_EXPEN_ENABLE 0x4 +#define FV_EXPEN_DISABLE 0x0 + +#define FB_SUBCLECTL_LIMEN 1 +#define FM_SUBCLECTL_LIMEN 0x2 +#define FV_LIMEN_ENABLE 0x2 +#define FV_LIMEN_DISABLE 0x0 + +#define FB_SUBCLECTL_COMPEN 0 +#define FM_SUBCLECTL_COMPEN 0x1 +#define FV_COMPEN_ENABLE 0x1 +#define FV_COMPEN_DISABLE 0x0 + +// *** SUBCLEMUG *** +#define FB_SUBCLEMUG_MUGAIN 0 +#define FM_SUBCLEMUG_MUGAIN 0x1F + +// *** SUBCOMPTHR *** +#define FB_SUBCOMPTHR_THRESH 0 +#define FM_SUBCOMPTHR_THRESH 0xFF + +// *** SUBCOMPRAT *** +#define FB_SUBCOMPRAT_RATIO 0 +#define FM_SUBCOMPRAT_RATIO 0x1F + +// *** SUBCOMPATKL *** +#define FB_SUBCOMPATKL_TCATKL 0 +#define FM_SUBCOMPATKL_TCATKL 0xFF + +// *** SUBCOMPATKH *** +#define FB_SUBCOMPATKH_TCATKH 0 +#define FM_SUBCOMPATKH_TCATKH 0xFF + +// *** SUBCOMPRELL *** +#define FB_SUBCOMPRELL_TCRELL 0 +#define FM_SUBCOMPRELL_TCRELL 0xFF + +// *** SUBCOMPRELH *** +#define FB_SUBCOMPRELH_TCRELH 0 +#define FM_SUBCOMPRELH_TCRELH 0xFF + +// *** SUBLIMTHR *** +#define FB_SUBLIMTHR_THRESH 0 +#define FM_SUBLIMTHR_THRESH 0xFF + +// *** SUBLIMTGT *** +#define FB_SUBLIMTGT_TARGET 0 +#define FM_SUBLIMTGT_TARGET 0xFF + +// *** SUBLIMATKL *** +#define FB_SUBLIMATKL_TCATKL 0 +#define FM_SUBLIMATKL_TCATKL 0xFF + +// *** SUBLIMATKH *** +#define FB_SUBLIMATKH_TCATKH 0 +#define FM_SUBLIMATKH_TCATKH 0xFF + +// *** SUBLIMRELL *** +#define FB_SUBLIMRELL_TCRELL 0 +#define FM_SUBLIMRELL_TCRELL 0xFF + +// *** SUBLIMRELH *** +#define FB_SUBLIMRELH_TCRELH 0 +#define FM_SUBLIMRELH_TCRELH 0xFF + +// *** SUBEXPTHR *** +#define FB_SUBEXPTHR_THRESH 0 +#define FM_SUBEXPTHR_THRESH 0xFF + +// *** SUBEXPRAT *** +#define FB_SUBEXPRAT_RATIO 0 +#define FM_SUBEXPRAT_RATIO 0x7 + +// *** SUBEXPATKL *** +#define FB_SUBEXPATKL_TCATKL 0 +#define FM_SUBEXPATKL_TCATKL 0xFF + +// *** SUBEXPATKH *** +#define FB_SUBEXPATKH_TCATKH 0 +#define FM_SUBEXPATKH_TCATKH 0xFF + +// *** SUBEXPRELL *** +#define FB_SUBEXPRELL_TCRELL 0 +#define FM_SUBEXPRELL_TCRELL 0xFF + +// *** SUBEXPRELH *** +#define FB_SUBEXPRELH_TCRELH 0 +#define FM_SUBEXPRELH_TCRELH 0xFF + +// *** SUBFXCTL *** +#define FB_SUBFXCTL_TEEN 3 +#define FM_SUBFXCTL_TEEN 0x8 + +#define FB_SUBFXCTL_TNLFBYP 2 +#define FM_SUBFXCTL_TNLFBYP 0x4 + +#define FB_SUBFXCTL_BEEN 1 +#define FM_SUBFXCTL_BEEN 0x2 + +#define FB_SUBFXCTL_BNLFBYP 0 +#define FM_SUBFXCTL_BNLFBYP 0x1 + +#endif /* __REDWOODPUBLIC_H__ */ diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c new file mode 100644 index 000000000..25b752caf --- /dev/null +++ b/sound/soc/codecs/twl4030.c @@ -0,0 +1,2230 @@ +/* + * ALSA SoC TWL4030 codec driver + * + * Author: Steve Sakoman, + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register descriptions are here */ +#include + +/* TWL4030 PMBR1 Register */ +#define TWL4030_PMBR1_REG 0x0D +/* TWL4030 PMBR1 Register GPIO6 mux bits */ +#define TWL4030_GPIO6_PWM0_MUTE(value) ((value & 0x03) << 2) + +#define TWL4030_CACHEREGNUM (TWL4030_REG_MISC_SET_2 + 1) + +/* codec private data */ +struct twl4030_priv { + unsigned int codec_powered; + + /* reference counts of AIF/APLL users */ + unsigned int apll_enabled; + + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; + + unsigned int configured; + unsigned int rate; + unsigned int sample_bits; + unsigned int channels; + + unsigned int sysclk; + + /* Output (with associated amp) states */ + u8 hsl_enabled, hsr_enabled; + u8 earpiece_enabled; + u8 predrivel_enabled, predriver_enabled; + u8 carkitl_enabled, carkitr_enabled; + u8 ctl_cache[TWL4030_REG_PRECKR_CTL - TWL4030_REG_EAR_CTL + 1]; + + struct twl4030_codec_data *pdata; +}; + +static void tw4030_init_ctl_cache(struct twl4030_priv *twl4030) +{ + int i; + u8 byte; + + for (i = TWL4030_REG_EAR_CTL; i <= TWL4030_REG_PRECKR_CTL; i++) { + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte, i); + twl4030->ctl_cache[i - TWL4030_REG_EAR_CTL] = byte; + } +} + +static unsigned int twl4030_read(struct snd_soc_component *component, unsigned int reg) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + u8 value = 0; + + if (reg >= TWL4030_CACHEREGNUM) + return -EIO; + + switch (reg) { + case TWL4030_REG_EAR_CTL: + case TWL4030_REG_PREDL_CTL: + case TWL4030_REG_PREDR_CTL: + case TWL4030_REG_PRECKL_CTL: + case TWL4030_REG_PRECKR_CTL: + case TWL4030_REG_HS_GAIN_SET: + value = twl4030->ctl_cache[reg - TWL4030_REG_EAR_CTL]; + break; + default: + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &value, reg); + break; + } + + return value; +} + +static bool twl4030_can_write_to_chip(struct twl4030_priv *twl4030, + unsigned int reg) +{ + bool write_to_reg = false; + + /* Decide if the given register can be written */ + switch (reg) { + case TWL4030_REG_EAR_CTL: + if (twl4030->earpiece_enabled) + write_to_reg = true; + break; + case TWL4030_REG_PREDL_CTL: + if (twl4030->predrivel_enabled) + write_to_reg = true; + break; + case TWL4030_REG_PREDR_CTL: + if (twl4030->predriver_enabled) + write_to_reg = true; + break; + case TWL4030_REG_PRECKL_CTL: + if (twl4030->carkitl_enabled) + write_to_reg = true; + break; + case TWL4030_REG_PRECKR_CTL: + if (twl4030->carkitr_enabled) + write_to_reg = true; + break; + case TWL4030_REG_HS_GAIN_SET: + if (twl4030->hsl_enabled || twl4030->hsr_enabled) + write_to_reg = true; + break; + default: + /* All other register can be written */ + write_to_reg = true; + break; + } + + return write_to_reg; +} + +static int twl4030_write(struct snd_soc_component *component, unsigned int reg, + unsigned int value) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + /* Update the ctl cache */ + switch (reg) { + case TWL4030_REG_EAR_CTL: + case TWL4030_REG_PREDL_CTL: + case TWL4030_REG_PREDR_CTL: + case TWL4030_REG_PRECKL_CTL: + case TWL4030_REG_PRECKR_CTL: + case TWL4030_REG_HS_GAIN_SET: + twl4030->ctl_cache[reg - TWL4030_REG_EAR_CTL] = value; + break; + default: + break; + } + + if (twl4030_can_write_to_chip(twl4030, reg)) + return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); + + return 0; +} + +static inline void twl4030_wait_ms(int time) +{ + if (time < 60) { + time *= 1000; + usleep_range(time, time + 500); + } else { + msleep(time); + } +} + +static void twl4030_codec_enable(struct snd_soc_component *component, int enable) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + int mode; + + if (enable == twl4030->codec_powered) + return; + + if (enable) + mode = twl4030_audio_enable_resource(TWL4030_AUDIO_RES_POWER); + else + mode = twl4030_audio_disable_resource(TWL4030_AUDIO_RES_POWER); + + if (mode >= 0) + twl4030->codec_powered = enable; + + /* REVISIT: this delay is present in TI sample drivers */ + /* but there seems to be no TRM requirement for it */ + udelay(10); +} + +static void twl4030_setup_pdata_of(struct twl4030_codec_data *pdata, + struct device_node *node) +{ + int value; + + of_property_read_u32(node, "ti,digimic_delay", + &pdata->digimic_delay); + of_property_read_u32(node, "ti,ramp_delay_value", + &pdata->ramp_delay_value); + of_property_read_u32(node, "ti,offset_cncl_path", + &pdata->offset_cncl_path); + if (!of_property_read_u32(node, "ti,hs_extmute", &value)) + pdata->hs_extmute = value; + + pdata->hs_extmute_gpio = of_get_named_gpio(node, + "ti,hs_extmute_gpio", 0); + if (gpio_is_valid(pdata->hs_extmute_gpio)) + pdata->hs_extmute = 1; +} + +static struct twl4030_codec_data *twl4030_get_pdata(struct snd_soc_component *component) +{ + struct twl4030_codec_data *pdata = dev_get_platdata(component->dev); + struct device_node *twl4030_codec_node = NULL; + + twl4030_codec_node = of_get_child_by_name(component->dev->parent->of_node, + "codec"); + + if (!pdata && twl4030_codec_node) { + pdata = devm_kzalloc(component->dev, + sizeof(struct twl4030_codec_data), + GFP_KERNEL); + if (!pdata) { + of_node_put(twl4030_codec_node); + return NULL; + } + twl4030_setup_pdata_of(pdata, twl4030_codec_node); + of_node_put(twl4030_codec_node); + } + + return pdata; +} + +static void twl4030_init_chip(struct snd_soc_component *component) +{ + struct twl4030_codec_data *pdata; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + u8 reg, byte; + int i = 0; + + pdata = twl4030_get_pdata(component); + + if (pdata && pdata->hs_extmute) { + if (gpio_is_valid(pdata->hs_extmute_gpio)) { + int ret; + + if (!pdata->hs_extmute_gpio) + dev_warn(component->dev, + "Extmute GPIO is 0 is this correct?\n"); + + ret = gpio_request_one(pdata->hs_extmute_gpio, + GPIOF_OUT_INIT_LOW, + "hs_extmute"); + if (ret) { + dev_err(component->dev, + "Failed to get hs_extmute GPIO\n"); + pdata->hs_extmute_gpio = -1; + } + } else { + u8 pin_mux; + + /* Set TWL4030 GPIO6 as EXTMUTE signal */ + twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux, + TWL4030_PMBR1_REG); + pin_mux &= ~TWL4030_GPIO6_PWM0_MUTE(0x03); + pin_mux |= TWL4030_GPIO6_PWM0_MUTE(0x02); + twl_i2c_write_u8(TWL4030_MODULE_INTBR, pin_mux, + TWL4030_PMBR1_REG); + } + } + + /* Initialize the local ctl register cache */ + tw4030_init_ctl_cache(twl4030); + + /* anti-pop when changing analog gain */ + reg = twl4030_read(component, TWL4030_REG_MISC_SET_1); + twl4030_write(component, TWL4030_REG_MISC_SET_1, + reg | TWL4030_SMOOTH_ANAVOL_EN); + + twl4030_write(component, TWL4030_REG_OPTION, + TWL4030_ATXL1_EN | TWL4030_ATXR1_EN | + TWL4030_ARXL2_EN | TWL4030_ARXR2_EN); + + /* REG_ARXR2_APGA_CTL reset according to the TRM: 0dB, DA_EN */ + twl4030_write(component, TWL4030_REG_ARXR2_APGA_CTL, 0x32); + + /* Machine dependent setup */ + if (!pdata) + return; + + twl4030->pdata = pdata; + + reg = twl4030_read(component, TWL4030_REG_HS_POPN_SET); + reg &= ~TWL4030_RAMP_DELAY; + reg |= (pdata->ramp_delay_value << 2); + twl4030_write(component, TWL4030_REG_HS_POPN_SET, reg); + + /* initiate offset cancellation */ + twl4030_codec_enable(component, 1); + + reg = twl4030_read(component, TWL4030_REG_ANAMICL); + reg &= ~TWL4030_OFFSET_CNCL_SEL; + reg |= pdata->offset_cncl_path; + twl4030_write(component, TWL4030_REG_ANAMICL, + reg | TWL4030_CNCL_OFFSET_START); + + /* + * Wait for offset cancellation to complete. + * Since this takes a while, do not slam the i2c. + * Start polling the status after ~20ms. + */ + msleep(20); + do { + usleep_range(1000, 2000); + twl_set_regcache_bypass(TWL4030_MODULE_AUDIO_VOICE, true); + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte, + TWL4030_REG_ANAMICL); + twl_set_regcache_bypass(TWL4030_MODULE_AUDIO_VOICE, false); + } while ((i++ < 100) && + ((byte & TWL4030_CNCL_OFFSET_START) == + TWL4030_CNCL_OFFSET_START)); + + twl4030_codec_enable(component, 0); +} + +static void twl4030_apll_enable(struct snd_soc_component *component, int enable) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + if (enable) { + twl4030->apll_enabled++; + if (twl4030->apll_enabled == 1) + twl4030_audio_enable_resource( + TWL4030_AUDIO_RES_APLL); + } else { + twl4030->apll_enabled--; + if (!twl4030->apll_enabled) + twl4030_audio_disable_resource( + TWL4030_AUDIO_RES_APLL); + } +} + +/* Earpiece */ +static const struct snd_kcontrol_new twl4030_dapm_earpiece_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_EAR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_EAR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_EAR_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_EAR_CTL, 3, 1, 0), +}; + +/* PreDrive Left */ +static const struct snd_kcontrol_new twl4030_dapm_predrivel_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDL_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PREDL_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDL_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDL_CTL, 3, 1, 0), +}; + +/* PreDrive Right */ +static const struct snd_kcontrol_new twl4030_dapm_predriver_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PREDR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDR_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDR_CTL, 3, 1, 0), +}; + +/* Headset Left */ +static const struct snd_kcontrol_new twl4030_dapm_hsol_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_HS_SEL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_HS_SEL, 2, 1, 0), +}; + +/* Headset Right */ +static const struct snd_kcontrol_new twl4030_dapm_hsor_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 3, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_HS_SEL, 4, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_HS_SEL, 5, 1, 0), +}; + +/* Carkit Left */ +static const struct snd_kcontrol_new twl4030_dapm_carkitl_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKL_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PRECKL_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PRECKL_CTL, 2, 1, 0), +}; + +/* Carkit Right */ +static const struct snd_kcontrol_new twl4030_dapm_carkitr_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PRECKR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PRECKR_CTL, 2, 1, 0), +}; + +/* Handsfree Left */ +static const char *twl4030_handsfreel_texts[] = + {"Voice", "AudioL1", "AudioL2", "AudioR2"}; + +static SOC_ENUM_SINGLE_DECL(twl4030_handsfreel_enum, + TWL4030_REG_HFL_CTL, 0, + twl4030_handsfreel_texts); + +static const struct snd_kcontrol_new twl4030_dapm_handsfreel_control = +SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum); + +/* Handsfree Left virtual mute */ +static const struct snd_kcontrol_new twl4030_dapm_handsfreelmute_control = + SOC_DAPM_SINGLE_VIRT("Switch", 1); + +/* Handsfree Right */ +static const char *twl4030_handsfreer_texts[] = + {"Voice", "AudioR1", "AudioR2", "AudioL2"}; + +static SOC_ENUM_SINGLE_DECL(twl4030_handsfreer_enum, + TWL4030_REG_HFR_CTL, 0, + twl4030_handsfreer_texts); + +static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control = +SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum); + +/* Handsfree Right virtual mute */ +static const struct snd_kcontrol_new twl4030_dapm_handsfreermute_control = + SOC_DAPM_SINGLE_VIRT("Switch", 1); + +/* Vibra */ +/* Vibra audio path selection */ +static const char *twl4030_vibra_texts[] = + {"AudioL1", "AudioR1", "AudioL2", "AudioR2"}; + +static SOC_ENUM_SINGLE_DECL(twl4030_vibra_enum, + TWL4030_REG_VIBRA_CTL, 2, + twl4030_vibra_texts); + +static const struct snd_kcontrol_new twl4030_dapm_vibra_control = +SOC_DAPM_ENUM("Route", twl4030_vibra_enum); + +/* Vibra path selection: local vibrator (PWM) or audio driven */ +static const char *twl4030_vibrapath_texts[] = + {"Local vibrator", "Audio"}; + +static SOC_ENUM_SINGLE_DECL(twl4030_vibrapath_enum, + TWL4030_REG_VIBRA_CTL, 4, + twl4030_vibrapath_texts); + +static const struct snd_kcontrol_new twl4030_dapm_vibrapath_control = +SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum); + +/* Left analog microphone selection */ +static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = { + SOC_DAPM_SINGLE("Main Mic Capture Switch", + TWL4030_REG_ANAMICL, 0, 1, 0), + SOC_DAPM_SINGLE("Headset Mic Capture Switch", + TWL4030_REG_ANAMICL, 1, 1, 0), + SOC_DAPM_SINGLE("AUXL Capture Switch", + TWL4030_REG_ANAMICL, 2, 1, 0), + SOC_DAPM_SINGLE("Carkit Mic Capture Switch", + TWL4030_REG_ANAMICL, 3, 1, 0), +}; + +/* Right analog microphone selection */ +static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = { + SOC_DAPM_SINGLE("Sub Mic Capture Switch", TWL4030_REG_ANAMICR, 0, 1, 0), + SOC_DAPM_SINGLE("AUXR Capture Switch", TWL4030_REG_ANAMICR, 2, 1, 0), +}; + +/* TX1 L/R Analog/Digital microphone selection */ +static const char *twl4030_micpathtx1_texts[] = + {"Analog", "Digimic0"}; + +static SOC_ENUM_SINGLE_DECL(twl4030_micpathtx1_enum, + TWL4030_REG_ADCMICSEL, 0, + twl4030_micpathtx1_texts); + +static const struct snd_kcontrol_new twl4030_dapm_micpathtx1_control = +SOC_DAPM_ENUM("Route", twl4030_micpathtx1_enum); + +/* TX2 L/R Analog/Digital microphone selection */ +static const char *twl4030_micpathtx2_texts[] = + {"Analog", "Digimic1"}; + +static SOC_ENUM_SINGLE_DECL(twl4030_micpathtx2_enum, + TWL4030_REG_ADCMICSEL, 2, + twl4030_micpathtx2_texts); + +static const struct snd_kcontrol_new twl4030_dapm_micpathtx2_control = +SOC_DAPM_ENUM("Route", twl4030_micpathtx2_enum); + +/* Analog bypass for AudioR1 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassr1_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR1_APGA_CTL, 2, 1, 0); + +/* Analog bypass for AudioL1 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassl1_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL1_APGA_CTL, 2, 1, 0); + +/* Analog bypass for AudioR2 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR2_APGA_CTL, 2, 1, 0); + +/* Analog bypass for AudioL2 */ +static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0); + +/* Analog bypass for Voice */ +static const struct snd_kcontrol_new twl4030_dapm_abypassv_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0); + +/* Digital bypass gain, mute instead of -30dB */ +static const DECLARE_TLV_DB_RANGE(twl4030_dapm_dbypass_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-3000, 600, 1), + 2, 3, TLV_DB_SCALE_ITEM(-2400, 0, 0), + 4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0) +); + +/* Digital bypass left (TX1L -> RX2L) */ +static const struct snd_kcontrol_new twl4030_dapm_dbypassl_control = + SOC_DAPM_SINGLE_TLV("Volume", + TWL4030_REG_ATX2ARXPGA, 3, 7, 0, + twl4030_dapm_dbypass_tlv); + +/* Digital bypass right (TX1R -> RX2R) */ +static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control = + SOC_DAPM_SINGLE_TLV("Volume", + TWL4030_REG_ATX2ARXPGA, 0, 7, 0, + twl4030_dapm_dbypass_tlv); + +/* + * Voice Sidetone GAIN volume control: + * from -51 to -10 dB in 1 dB steps (mute instead of -51 dB) + */ +static DECLARE_TLV_DB_SCALE(twl4030_dapm_dbypassv_tlv, -5100, 100, 1); + +/* Digital bypass voice: sidetone (VUL -> VDL)*/ +static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control = + SOC_DAPM_SINGLE_TLV("Volume", + TWL4030_REG_VSTPGA, 0, 0x29, 0, + twl4030_dapm_dbypassv_tlv); + +/* + * Output PGA builder: + * Handle the muting and unmuting of the given output (turning off the + * amplifier associated with the output pin) + * On mute bypass the reg_cache and write 0 to the register + * On unmute: restore the register content from the reg_cache + * Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R + */ +#define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \ +static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \ + struct snd_kcontrol *kcontrol, int event) \ +{ \ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); \ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); \ + \ + switch (event) { \ + case SND_SOC_DAPM_POST_PMU: \ + twl4030->pin_name##_enabled = 1; \ + twl4030_write(component, reg, twl4030_read(component, reg)); \ + break; \ + case SND_SOC_DAPM_POST_PMD: \ + twl4030->pin_name##_enabled = 0; \ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 0, reg); \ + break; \ + } \ + return 0; \ +} + +TWL4030_OUTPUT_PGA(earpiece, TWL4030_REG_EAR_CTL, TWL4030_EAR_GAIN); +TWL4030_OUTPUT_PGA(predrivel, TWL4030_REG_PREDL_CTL, TWL4030_PREDL_GAIN); +TWL4030_OUTPUT_PGA(predriver, TWL4030_REG_PREDR_CTL, TWL4030_PREDR_GAIN); +TWL4030_OUTPUT_PGA(carkitl, TWL4030_REG_PRECKL_CTL, TWL4030_PRECKL_GAIN); +TWL4030_OUTPUT_PGA(carkitr, TWL4030_REG_PRECKR_CTL, TWL4030_PRECKR_GAIN); + +static void handsfree_ramp(struct snd_soc_component *component, int reg, int ramp) +{ + unsigned char hs_ctl; + + hs_ctl = twl4030_read(component, reg); + + if (ramp) { + /* HF ramp-up */ + hs_ctl |= TWL4030_HF_CTL_REF_EN; + twl4030_write(component, reg, hs_ctl); + udelay(10); + hs_ctl |= TWL4030_HF_CTL_RAMP_EN; + twl4030_write(component, reg, hs_ctl); + udelay(40); + hs_ctl |= TWL4030_HF_CTL_LOOP_EN; + hs_ctl |= TWL4030_HF_CTL_HB_EN; + twl4030_write(component, reg, hs_ctl); + } else { + /* HF ramp-down */ + hs_ctl &= ~TWL4030_HF_CTL_LOOP_EN; + hs_ctl &= ~TWL4030_HF_CTL_HB_EN; + twl4030_write(component, reg, hs_ctl); + hs_ctl &= ~TWL4030_HF_CTL_RAMP_EN; + twl4030_write(component, reg, hs_ctl); + udelay(40); + hs_ctl &= ~TWL4030_HF_CTL_REF_EN; + twl4030_write(component, reg, hs_ctl); + } +} + +static int handsfreelpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + handsfree_ramp(component, TWL4030_REG_HFL_CTL, 1); + break; + case SND_SOC_DAPM_POST_PMD: + handsfree_ramp(component, TWL4030_REG_HFL_CTL, 0); + break; + } + return 0; +} + +static int handsfreerpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + handsfree_ramp(component, TWL4030_REG_HFR_CTL, 1); + break; + case SND_SOC_DAPM_POST_PMD: + handsfree_ramp(component, TWL4030_REG_HFR_CTL, 0); + break; + } + return 0; +} + +static int vibramux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + twl4030_write(component, TWL4030_REG_VIBRA_SET, 0xff); + return 0; +} + +static int apll_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + twl4030_apll_enable(component, 1); + break; + case SND_SOC_DAPM_POST_PMD: + twl4030_apll_enable(component, 0); + break; + } + return 0; +} + +static int aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u8 audio_if; + + audio_if = twl4030_read(component, TWL4030_REG_AUDIO_IF); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable AIF */ + /* enable the PLL before we use it to clock the DAI */ + twl4030_apll_enable(component, 1); + + twl4030_write(component, TWL4030_REG_AUDIO_IF, + audio_if | TWL4030_AIF_EN); + break; + case SND_SOC_DAPM_POST_PMD: + /* disable the DAI before we stop it's source PLL */ + twl4030_write(component, TWL4030_REG_AUDIO_IF, + audio_if & ~TWL4030_AIF_EN); + twl4030_apll_enable(component, 0); + break; + } + return 0; +} + +static void headset_ramp(struct snd_soc_component *component, int ramp) +{ + unsigned char hs_gain, hs_pop; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + struct twl4030_codec_data *pdata = twl4030->pdata; + /* Base values for ramp delay calculation: 2^19 - 2^26 */ + unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304, + 8388608, 16777216, 33554432, 67108864}; + unsigned int delay; + + hs_gain = twl4030_read(component, TWL4030_REG_HS_GAIN_SET); + hs_pop = twl4030_read(component, TWL4030_REG_HS_POPN_SET); + delay = (ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] / + twl4030->sysclk) + 1; + + /* Enable external mute control, this dramatically reduces + * the pop-noise */ + if (pdata && pdata->hs_extmute) { + if (gpio_is_valid(pdata->hs_extmute_gpio)) { + gpio_set_value(pdata->hs_extmute_gpio, 1); + } else { + hs_pop |= TWL4030_EXTMUTE; + twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); + } + } + + if (ramp) { + /* Headset ramp-up according to the TRM */ + hs_pop |= TWL4030_VMID_EN; + twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Actually write to the register */ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, hs_gain, + TWL4030_REG_HS_GAIN_SET); + hs_pop |= TWL4030_RAMP_EN; + twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Wait ramp delay time + 1, so the VMID can settle */ + twl4030_wait_ms(delay); + } else { + /* Headset ramp-down _not_ according to + * the TRM, but in a way that it is working */ + hs_pop &= ~TWL4030_RAMP_EN; + twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Wait ramp delay time + 1, so the VMID can settle */ + twl4030_wait_ms(delay); + /* Bypass the reg_cache to mute the headset */ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, hs_gain & (~0x0f), + TWL4030_REG_HS_GAIN_SET); + + hs_pop &= ~TWL4030_VMID_EN; + twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); + } + + /* Disable external mute */ + if (pdata && pdata->hs_extmute) { + if (gpio_is_valid(pdata->hs_extmute_gpio)) { + gpio_set_value(pdata->hs_extmute_gpio, 0); + } else { + hs_pop &= ~TWL4030_EXTMUTE; + twl4030_write(component, TWL4030_REG_HS_POPN_SET, hs_pop); + } + } +} + +static int headsetlpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Do the ramp-up only once */ + if (!twl4030->hsr_enabled) + headset_ramp(component, 1); + + twl4030->hsl_enabled = 1; + break; + case SND_SOC_DAPM_POST_PMD: + /* Do the ramp-down only if both headsetL/R is disabled */ + if (!twl4030->hsr_enabled) + headset_ramp(component, 0); + + twl4030->hsl_enabled = 0; + break; + } + return 0; +} + +static int headsetrpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Do the ramp-up only once */ + if (!twl4030->hsl_enabled) + headset_ramp(component, 1); + + twl4030->hsr_enabled = 1; + break; + case SND_SOC_DAPM_POST_PMD: + /* Do the ramp-down only if both headsetL/R is disabled */ + if (!twl4030->hsl_enabled) + headset_ramp(component, 0); + + twl4030->hsr_enabled = 0; + break; + } + return 0; +} + +static int digimic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + struct twl4030_codec_data *pdata = twl4030->pdata; + + if (pdata && pdata->digimic_delay) + twl4030_wait_ms(pdata->digimic_delay); + return 0; +} + +/* + * Some of the gain controls in TWL (mostly those which are associated with + * the outputs) are implemented in an interesting way: + * 0x0 : Power down (mute) + * 0x1 : 6dB + * 0x2 : 0 dB + * 0x3 : -6 dB + * Inverting not going to help with these. + * Custom volsw and volsw_2r get/put functions to handle these gain bits. + */ +static int snd_soc_get_volsw_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int mask = (1 << fls(max)) - 1; + + ucontrol->value.integer.value[0] = + (twl4030_read(component, reg) >> shift) & mask; + if (ucontrol->value.integer.value[0]) + ucontrol->value.integer.value[0] = + max + 1 - ucontrol->value.integer.value[0]; + + if (shift != rshift) { + ucontrol->value.integer.value[1] = + (twl4030_read(component, reg) >> rshift) & mask; + if (ucontrol->value.integer.value[1]) + ucontrol->value.integer.value[1] = + max + 1 - ucontrol->value.integer.value[1]; + } + + return 0; +} + +static int snd_soc_put_volsw_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int mask = (1 << fls(max)) - 1; + unsigned short val, val2, val_mask; + + val = (ucontrol->value.integer.value[0] & mask); + + val_mask = mask << shift; + if (val) + val = max + 1 - val; + val = val << shift; + if (shift != rshift) { + val2 = (ucontrol->value.integer.value[1] & mask); + val_mask |= mask << rshift; + if (val2) + val2 = max + 1 - val2; + val |= val2 << rshift; + } + return snd_soc_component_update_bits(component, reg, val_mask, val); +} + +static int snd_soc_get_volsw_r2_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + int mask = (1<value.integer.value[0] = + (twl4030_read(component, reg) >> shift) & mask; + ucontrol->value.integer.value[1] = + (twl4030_read(component, reg2) >> shift) & mask; + + if (ucontrol->value.integer.value[0]) + ucontrol->value.integer.value[0] = + max + 1 - ucontrol->value.integer.value[0]; + if (ucontrol->value.integer.value[1]) + ucontrol->value.integer.value[1] = + max + 1 - ucontrol->value.integer.value[1]; + + return 0; +} + +static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + int mask = (1 << fls(max)) - 1; + int err; + unsigned short val, val2, val_mask; + + val_mask = mask << shift; + val = (ucontrol->value.integer.value[0] & mask); + val2 = (ucontrol->value.integer.value[1] & mask); + + if (val) + val = max + 1 - val; + if (val2) + val2 = max + 1 - val2; + + val = val << shift; + val2 = val2 << shift; + + err = snd_soc_component_update_bits(component, reg, val_mask, val); + if (err < 0) + return err; + + err = snd_soc_component_update_bits(component, reg2, val_mask, val2); + return err; +} + +/* Codec operation modes */ +static const char *twl4030_op_modes_texts[] = { + "Option 2 (voice/audio)", "Option 1 (audio)" +}; + +static SOC_ENUM_SINGLE_DECL(twl4030_op_modes_enum, + TWL4030_REG_CODEC_MODE, 0, + twl4030_op_modes_texts); + +static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + if (twl4030->configured) { + dev_err(component->dev, + "operation mode cannot be changed on-the-fly\n"); + return -EBUSY; + } + + return snd_soc_put_enum_double(kcontrol, ucontrol); +} + +/* + * FGAIN volume control: + * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) + */ +static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1); + +/* + * CGAIN volume control: + * 0 dB to 12 dB in 6 dB steps + * value 2 and 3 means 12 dB + */ +static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0); + +/* + * Voice Downlink GAIN volume control: + * from -37 to 12 dB in 1 dB steps (mute instead of -37 dB) + */ +static DECLARE_TLV_DB_SCALE(digital_voice_downlink_tlv, -3700, 100, 1); + +/* + * Analog playback gain + * -24 dB to 12 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(analog_tlv, -2400, 200, 0); + +/* + * Gain controls tied to outputs + * -6 dB to 6 dB in 6 dB steps (mute instead of -12) + */ +static DECLARE_TLV_DB_SCALE(output_tvl, -1200, 600, 1); + +/* + * Gain control for earpiece amplifier + * 0 dB to 12 dB in 6 dB steps (mute instead of -6) + */ +static DECLARE_TLV_DB_SCALE(output_ear_tvl, -600, 600, 1); + +/* + * Capture gain after the ADCs + * from 0 dB to 31 dB in 1 dB steps + */ +static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0); + +/* + * Gain control for input amplifiers + * 0 dB to 30 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0); + +/* AVADC clock priority */ +static const char *twl4030_avadc_clk_priority_texts[] = { + "Voice high priority", "HiFi high priority" +}; + +static SOC_ENUM_SINGLE_DECL(twl4030_avadc_clk_priority_enum, + TWL4030_REG_AVADC_CTL, 2, + twl4030_avadc_clk_priority_texts); + +static const char *twl4030_rampdelay_texts[] = { + "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms", + "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms", + "3495/2581/1748 ms" +}; + +static SOC_ENUM_SINGLE_DECL(twl4030_rampdelay_enum, + TWL4030_REG_HS_POPN_SET, 2, + twl4030_rampdelay_texts); + +/* Vibra H-bridge direction mode */ +static const char *twl4030_vibradirmode_texts[] = { + "Vibra H-bridge direction", "Audio data MSB", +}; + +static SOC_ENUM_SINGLE_DECL(twl4030_vibradirmode_enum, + TWL4030_REG_VIBRA_CTL, 5, + twl4030_vibradirmode_texts); + +/* Vibra H-bridge direction */ +static const char *twl4030_vibradir_texts[] = { + "Positive polarity", "Negative polarity", +}; + +static SOC_ENUM_SINGLE_DECL(twl4030_vibradir_enum, + TWL4030_REG_VIBRA_CTL, 1, + twl4030_vibradir_texts); + +/* Digimic Left and right swapping */ +static const char *twl4030_digimicswap_texts[] = { + "Not swapped", "Swapped", +}; + +static SOC_ENUM_SINGLE_DECL(twl4030_digimicswap_enum, + TWL4030_REG_MISC_SET_1, 0, + twl4030_digimicswap_texts); + +static const struct snd_kcontrol_new twl4030_snd_controls[] = { + /* Codec operation mode control */ + SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum, + snd_soc_get_enum_double, + snd_soc_put_twl4030_opmode_enum_double), + + /* Common playback gain controls */ + SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", + TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA, + 0, 0x3f, 0, digital_fine_tlv), + SOC_DOUBLE_R_TLV("DAC2 Digital Fine Playback Volume", + TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, + 0, 0x3f, 0, digital_fine_tlv), + + SOC_DOUBLE_R_TLV("DAC1 Digital Coarse Playback Volume", + TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA, + 6, 0x2, 0, digital_coarse_tlv), + SOC_DOUBLE_R_TLV("DAC2 Digital Coarse Playback Volume", + TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, + 6, 0x2, 0, digital_coarse_tlv), + + SOC_DOUBLE_R_TLV("DAC1 Analog Playback Volume", + TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL, + 3, 0x12, 1, analog_tlv), + SOC_DOUBLE_R_TLV("DAC2 Analog Playback Volume", + TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL, + 3, 0x12, 1, analog_tlv), + SOC_DOUBLE_R("DAC1 Analog Playback Switch", + TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL, + 1, 1, 0), + SOC_DOUBLE_R("DAC2 Analog Playback Switch", + TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL, + 1, 1, 0), + + /* Common voice downlink gain controls */ + SOC_SINGLE_TLV("DAC Voice Digital Downlink Volume", + TWL4030_REG_VRXPGA, 0, 0x31, 0, digital_voice_downlink_tlv), + + SOC_SINGLE_TLV("DAC Voice Analog Downlink Volume", + TWL4030_REG_VDL_APGA_CTL, 3, 0x12, 1, analog_tlv), + + SOC_SINGLE("DAC Voice Analog Downlink Switch", + TWL4030_REG_VDL_APGA_CTL, 1, 1, 0), + + /* Separate output gain controls */ + SOC_DOUBLE_R_EXT_TLV("PreDriv Playback Volume", + TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL, + 4, 3, 0, snd_soc_get_volsw_r2_twl4030, + snd_soc_put_volsw_r2_twl4030, output_tvl), + + SOC_DOUBLE_EXT_TLV("Headset Playback Volume", + TWL4030_REG_HS_GAIN_SET, 0, 2, 3, 0, snd_soc_get_volsw_twl4030, + snd_soc_put_volsw_twl4030, output_tvl), + + SOC_DOUBLE_R_EXT_TLV("Carkit Playback Volume", + TWL4030_REG_PRECKL_CTL, TWL4030_REG_PRECKR_CTL, + 4, 3, 0, snd_soc_get_volsw_r2_twl4030, + snd_soc_put_volsw_r2_twl4030, output_tvl), + + SOC_SINGLE_EXT_TLV("Earpiece Playback Volume", + TWL4030_REG_EAR_CTL, 4, 3, 0, snd_soc_get_volsw_twl4030, + snd_soc_put_volsw_twl4030, output_ear_tvl), + + /* Common capture gain controls */ + SOC_DOUBLE_R_TLV("TX1 Digital Capture Volume", + TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA, + 0, 0x1f, 0, digital_capture_tlv), + SOC_DOUBLE_R_TLV("TX2 Digital Capture Volume", + TWL4030_REG_AVTXL2PGA, TWL4030_REG_AVTXR2PGA, + 0, 0x1f, 0, digital_capture_tlv), + + SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN, + 0, 3, 5, 0, input_gain_tlv), + + SOC_ENUM("AVADC Clock Priority", twl4030_avadc_clk_priority_enum), + + SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum), + + SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum), + SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum), + + SOC_ENUM("Digimic LR Swap", twl4030_digimicswap_enum), +}; + +static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { + /* Left channel inputs */ + SND_SOC_DAPM_INPUT("MAINMIC"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("CARKITMIC"), + /* Right channel inputs */ + SND_SOC_DAPM_INPUT("SUBMIC"), + SND_SOC_DAPM_INPUT("AUXR"), + /* Digital microphones (Stereo) */ + SND_SOC_DAPM_INPUT("DIGIMIC0"), + SND_SOC_DAPM_INPUT("DIGIMIC1"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("EARPIECE"), + SND_SOC_DAPM_OUTPUT("PREDRIVEL"), + SND_SOC_DAPM_OUTPUT("PREDRIVER"), + SND_SOC_DAPM_OUTPUT("HSOL"), + SND_SOC_DAPM_OUTPUT("HSOR"), + SND_SOC_DAPM_OUTPUT("CARKITL"), + SND_SOC_DAPM_OUTPUT("CARKITR"), + SND_SOC_DAPM_OUTPUT("HFL"), + SND_SOC_DAPM_OUTPUT("HFR"), + SND_SOC_DAPM_OUTPUT("VIBRA"), + + /* AIF and APLL clocks for running DAIs (including loopback) */ + SND_SOC_DAPM_OUTPUT("Virtual HiFi OUT"), + SND_SOC_DAPM_INPUT("Virtual HiFi IN"), + SND_SOC_DAPM_OUTPUT("Virtual Voice OUT"), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC Right1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Left1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Right2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Left2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Voice", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("VAIFIN", "Voice Playback", 0, + TWL4030_REG_VOICE_IF, 6, 0), + + /* Analog bypasses */ + SND_SOC_DAPM_SWITCH("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassr1_control), + SND_SOC_DAPM_SWITCH("Left1 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassl1_control), + SND_SOC_DAPM_SWITCH("Right2 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassr2_control), + SND_SOC_DAPM_SWITCH("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassl2_control), + SND_SOC_DAPM_SWITCH("Voice Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassv_control), + + /* Master analog loopback switch */ + SND_SOC_DAPM_SUPPLY("FM Loop Enable", TWL4030_REG_MISC_SET_1, 5, 0, + NULL, 0), + + /* Digital bypasses */ + SND_SOC_DAPM_SWITCH("Left Digital Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_dbypassl_control), + SND_SOC_DAPM_SWITCH("Right Digital Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_dbypassr_control), + SND_SOC_DAPM_SWITCH("Voice Digital Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_dbypassv_control), + + /* Digital mixers, power control for the physical DACs */ + SND_SOC_DAPM_MIXER("Digital R1 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital L1 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 1, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital R2 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital L2 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital Voice Playback Mixer", + TWL4030_REG_AVDAC_CTL, 4, 0, NULL, 0), + + /* Analog mixers, power control for the physical PGAs */ + SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", + TWL4030_REG_ARXR1_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", + TWL4030_REG_ARXL1_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", + TWL4030_REG_ARXR2_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", + TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer", + TWL4030_REG_VDL_APGA_CTL, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("APLL Enable", SND_SOC_NOPM, 0, 0, apll_event, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY("AIF Enable", SND_SOC_NOPM, 0, 0, aif_event, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), + + /* Output MIXER controls */ + /* Earpiece */ + SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_earpiece_controls[0], + ARRAY_SIZE(twl4030_dapm_earpiece_controls)), + SND_SOC_DAPM_PGA_E("Earpiece PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, earpiecepga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + /* PreDrivL/R */ + SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predrivel_controls[0], + ARRAY_SIZE(twl4030_dapm_predrivel_controls)), + SND_SOC_DAPM_PGA_E("PredriveL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, predrivelpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predriver_controls[0], + ARRAY_SIZE(twl4030_dapm_predriver_controls)), + SND_SOC_DAPM_PGA_E("PredriveR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, predriverpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + /* HeadsetL/R */ + SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsol_controls[0], + ARRAY_SIZE(twl4030_dapm_hsol_controls)), + SND_SOC_DAPM_PGA_E("HeadsetL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, headsetlpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsor_controls[0], + ARRAY_SIZE(twl4030_dapm_hsor_controls)), + SND_SOC_DAPM_PGA_E("HeadsetR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, headsetrpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + /* CarkitL/R */ + SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitl_controls[0], + ARRAY_SIZE(twl4030_dapm_carkitl_controls)), + SND_SOC_DAPM_PGA_E("CarkitL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, carkitlpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitr_controls[0], + ARRAY_SIZE(twl4030_dapm_carkitr_controls)), + SND_SOC_DAPM_PGA_E("CarkitR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, carkitrpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + + /* Output MUX controls */ + /* HandsfreeL/R */ + SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreel_control), + SND_SOC_DAPM_SWITCH("HandsfreeL", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreelmute_control), + SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, handsfreelpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0, + &twl4030_dapm_handsfreer_control), + SND_SOC_DAPM_SWITCH("HandsfreeR", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreermute_control), + SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, handsfreerpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + /* Vibra */ + SND_SOC_DAPM_MUX_E("Vibra Mux", TWL4030_REG_VIBRA_CTL, 0, 0, + &twl4030_dapm_vibra_control, vibramux_event, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX("Vibra Route", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_vibrapath_control), + + /* Introducing four virtual ADC, since TWL4030 have four channel for + capture */ + SND_SOC_DAPM_ADC("ADC Virtual Left1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC Virtual Right1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC Virtual Left2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC Virtual Right2", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT("VAIFOUT", "Voice Capture", 0, + TWL4030_REG_VOICE_IF, 5, 0), + + /* Analog/Digital mic path selection. + TX1 Left/Right: either analog Left/Right or Digimic0 + TX2 Left/Right: either analog Left/Right or Digimic1 */ + SND_SOC_DAPM_MUX("TX1 Capture Route", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_micpathtx1_control), + SND_SOC_DAPM_MUX("TX2 Capture Route", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_micpathtx2_control), + + /* Analog input mixers for the capture amplifiers */ + SND_SOC_DAPM_MIXER("Analog Left", + TWL4030_REG_ANAMICL, 4, 0, + &twl4030_dapm_analoglmic_controls[0], + ARRAY_SIZE(twl4030_dapm_analoglmic_controls)), + SND_SOC_DAPM_MIXER("Analog Right", + TWL4030_REG_ANAMICR, 4, 0, + &twl4030_dapm_analogrmic_controls[0], + ARRAY_SIZE(twl4030_dapm_analogrmic_controls)), + + SND_SOC_DAPM_PGA("ADC Physical Left", + TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC Physical Right", + TWL4030_REG_AVADC_CTL, 1, 0, NULL, 0), + + SND_SOC_DAPM_PGA_E("Digimic0 Enable", + TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0, + digimic_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("Digimic1 Enable", + TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0, + digimic_event, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY("micbias1 select", TWL4030_REG_MICBIAS_CTL, 5, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("micbias2 select", TWL4030_REG_MICBIAS_CTL, 6, 0, + NULL, 0), + + /* Microphone bias */ + SND_SOC_DAPM_SUPPLY("Mic Bias 1", + TWL4030_REG_MICBIAS_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias 2", + TWL4030_REG_MICBIAS_CTL, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Headset Mic Bias", + TWL4030_REG_MICBIAS_CTL, 2, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("VIF Enable", TWL4030_REG_VOICE_IF, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Stream -> DAC mapping */ + {"DAC Right1", NULL, "HiFi Playback"}, + {"DAC Left1", NULL, "HiFi Playback"}, + {"DAC Right2", NULL, "HiFi Playback"}, + {"DAC Left2", NULL, "HiFi Playback"}, + {"DAC Voice", NULL, "VAIFIN"}, + + /* ADC -> Stream mapping */ + {"HiFi Capture", NULL, "ADC Virtual Left1"}, + {"HiFi Capture", NULL, "ADC Virtual Right1"}, + {"HiFi Capture", NULL, "ADC Virtual Left2"}, + {"HiFi Capture", NULL, "ADC Virtual Right2"}, + {"VAIFOUT", NULL, "ADC Virtual Left2"}, + {"VAIFOUT", NULL, "ADC Virtual Right2"}, + {"VAIFOUT", NULL, "VIF Enable"}, + + {"Digital L1 Playback Mixer", NULL, "DAC Left1"}, + {"Digital R1 Playback Mixer", NULL, "DAC Right1"}, + {"Digital L2 Playback Mixer", NULL, "DAC Left2"}, + {"Digital R2 Playback Mixer", NULL, "DAC Right2"}, + {"Digital Voice Playback Mixer", NULL, "DAC Voice"}, + + /* Supply for the digital part (APLL) */ + {"Digital Voice Playback Mixer", NULL, "APLL Enable"}, + + {"DAC Left1", NULL, "AIF Enable"}, + {"DAC Right1", NULL, "AIF Enable"}, + {"DAC Left2", NULL, "AIF Enable"}, + {"DAC Right1", NULL, "AIF Enable"}, + {"DAC Voice", NULL, "VIF Enable"}, + + {"Digital R2 Playback Mixer", NULL, "AIF Enable"}, + {"Digital L2 Playback Mixer", NULL, "AIF Enable"}, + + {"Analog L1 Playback Mixer", NULL, "Digital L1 Playback Mixer"}, + {"Analog R1 Playback Mixer", NULL, "Digital R1 Playback Mixer"}, + {"Analog L2 Playback Mixer", NULL, "Digital L2 Playback Mixer"}, + {"Analog R2 Playback Mixer", NULL, "Digital R2 Playback Mixer"}, + {"Analog Voice Playback Mixer", NULL, "Digital Voice Playback Mixer"}, + + /* Internal playback routings */ + /* Earpiece */ + {"Earpiece Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"Earpiece PGA", NULL, "Earpiece Mixer"}, + /* PreDrivL */ + {"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"PredriveL PGA", NULL, "PredriveL Mixer"}, + /* PreDrivR */ + {"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"PredriveR PGA", NULL, "PredriveR Mixer"}, + /* HeadsetL */ + {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"HeadsetL PGA", NULL, "HeadsetL Mixer"}, + /* HeadsetR */ + {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"HeadsetR PGA", NULL, "HeadsetR Mixer"}, + /* CarkitL */ + {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"CarkitL PGA", NULL, "CarkitL Mixer"}, + /* CarkitR */ + {"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"CarkitR PGA", NULL, "CarkitR Mixer"}, + /* HandsfreeL */ + {"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"}, + {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"}, + {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"}, + {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"}, + {"HandsfreeL", "Switch", "HandsfreeL Mux"}, + {"HandsfreeL PGA", NULL, "HandsfreeL"}, + /* HandsfreeR */ + {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"}, + {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"}, + {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"}, + {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"}, + {"HandsfreeR", "Switch", "HandsfreeR Mux"}, + {"HandsfreeR PGA", NULL, "HandsfreeR"}, + /* Vibra */ + {"Vibra Mux", "AudioL1", "DAC Left1"}, + {"Vibra Mux", "AudioR1", "DAC Right1"}, + {"Vibra Mux", "AudioL2", "DAC Left2"}, + {"Vibra Mux", "AudioR2", "DAC Right2"}, + + /* outputs */ + /* Must be always connected (for AIF and APLL) */ + {"Virtual HiFi OUT", NULL, "DAC Left1"}, + {"Virtual HiFi OUT", NULL, "DAC Right1"}, + {"Virtual HiFi OUT", NULL, "DAC Left2"}, + {"Virtual HiFi OUT", NULL, "DAC Right2"}, + /* Must be always connected (for APLL) */ + {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"}, + /* Physical outputs */ + {"EARPIECE", NULL, "Earpiece PGA"}, + {"PREDRIVEL", NULL, "PredriveL PGA"}, + {"PREDRIVER", NULL, "PredriveR PGA"}, + {"HSOL", NULL, "HeadsetL PGA"}, + {"HSOR", NULL, "HeadsetR PGA"}, + {"CARKITL", NULL, "CarkitL PGA"}, + {"CARKITR", NULL, "CarkitR PGA"}, + {"HFL", NULL, "HandsfreeL PGA"}, + {"HFR", NULL, "HandsfreeR PGA"}, + {"Vibra Route", "Audio", "Vibra Mux"}, + {"VIBRA", NULL, "Vibra Route"}, + + /* Capture path */ + /* Must be always connected (for AIF and APLL) */ + {"ADC Virtual Left1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Left2", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right2", NULL, "Virtual HiFi IN"}, + /* Physical inputs */ + {"Analog Left", "Main Mic Capture Switch", "MAINMIC"}, + {"Analog Left", "Headset Mic Capture Switch", "HSMIC"}, + {"Analog Left", "AUXL Capture Switch", "AUXL"}, + {"Analog Left", "Carkit Mic Capture Switch", "CARKITMIC"}, + + {"Analog Right", "Sub Mic Capture Switch", "SUBMIC"}, + {"Analog Right", "AUXR Capture Switch", "AUXR"}, + + {"ADC Physical Left", NULL, "Analog Left"}, + {"ADC Physical Right", NULL, "Analog Right"}, + + {"Digimic0 Enable", NULL, "DIGIMIC0"}, + {"Digimic1 Enable", NULL, "DIGIMIC1"}, + + {"DIGIMIC0", NULL, "micbias1 select"}, + {"DIGIMIC1", NULL, "micbias2 select"}, + + /* TX1 Left capture path */ + {"TX1 Capture Route", "Analog", "ADC Physical Left"}, + {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"}, + /* TX1 Right capture path */ + {"TX1 Capture Route", "Analog", "ADC Physical Right"}, + {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"}, + /* TX2 Left capture path */ + {"TX2 Capture Route", "Analog", "ADC Physical Left"}, + {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"}, + /* TX2 Right capture path */ + {"TX2 Capture Route", "Analog", "ADC Physical Right"}, + {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"}, + + {"ADC Virtual Left1", NULL, "TX1 Capture Route"}, + {"ADC Virtual Right1", NULL, "TX1 Capture Route"}, + {"ADC Virtual Left2", NULL, "TX2 Capture Route"}, + {"ADC Virtual Right2", NULL, "TX2 Capture Route"}, + + {"ADC Virtual Left1", NULL, "AIF Enable"}, + {"ADC Virtual Right1", NULL, "AIF Enable"}, + {"ADC Virtual Left2", NULL, "AIF Enable"}, + {"ADC Virtual Right2", NULL, "AIF Enable"}, + + /* Analog bypass routes */ + {"Right1 Analog Loopback", "Switch", "Analog Right"}, + {"Left1 Analog Loopback", "Switch", "Analog Left"}, + {"Right2 Analog Loopback", "Switch", "Analog Right"}, + {"Left2 Analog Loopback", "Switch", "Analog Left"}, + {"Voice Analog Loopback", "Switch", "Analog Left"}, + + /* Supply for the Analog loopbacks */ + {"Right1 Analog Loopback", NULL, "FM Loop Enable"}, + {"Left1 Analog Loopback", NULL, "FM Loop Enable"}, + {"Right2 Analog Loopback", NULL, "FM Loop Enable"}, + {"Left2 Analog Loopback", NULL, "FM Loop Enable"}, + {"Voice Analog Loopback", NULL, "FM Loop Enable"}, + + {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"}, + {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"}, + {"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"}, + {"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"}, + {"Analog Voice Playback Mixer", NULL, "Voice Analog Loopback"}, + + /* Digital bypass routes */ + {"Right Digital Loopback", "Volume", "TX1 Capture Route"}, + {"Left Digital Loopback", "Volume", "TX1 Capture Route"}, + {"Voice Digital Loopback", "Volume", "TX2 Capture Route"}, + + {"Digital R2 Playback Mixer", NULL, "Right Digital Loopback"}, + {"Digital L2 Playback Mixer", NULL, "Left Digital Loopback"}, + {"Digital Voice Playback Mixer", NULL, "Voice Digital Loopback"}, + +}; + +static int twl4030_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + twl4030_codec_enable(component, 1); + break; + case SND_SOC_BIAS_OFF: + twl4030_codec_enable(component, 0); + break; + } + + return 0; +} + +static void twl4030_constraints(struct twl4030_priv *twl4030, + struct snd_pcm_substream *mst_substream) +{ + struct snd_pcm_substream *slv_substream; + + /* Pick the stream, which need to be constrained */ + if (mst_substream == twl4030->master_substream) + slv_substream = twl4030->slave_substream; + else if (mst_substream == twl4030->slave_substream) + slv_substream = twl4030->master_substream; + else /* This should not happen.. */ + return; + + /* Set the constraints according to the already configured stream */ + snd_pcm_hw_constraint_single(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + twl4030->rate); + + snd_pcm_hw_constraint_single(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + twl4030->sample_bits); + + snd_pcm_hw_constraint_single(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + twl4030->channels); +} + +/* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for + * capture has to be enabled/disabled. */ +static void twl4030_tdm_enable(struct snd_soc_component *component, int direction, + int enable) +{ + u8 reg, mask; + + reg = twl4030_read(component, TWL4030_REG_OPTION); + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + mask = TWL4030_ARXL1_VRX_EN | TWL4030_ARXR1_EN; + else + mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN; + + if (enable) + reg |= mask; + else + reg &= ~mask; + + twl4030_write(component, TWL4030_REG_OPTION, reg); +} + +static int twl4030_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + if (twl4030->master_substream) { + twl4030->slave_substream = substream; + /* The DAI has one configuration for playback and capture, so + * if the DAI has been already configured then constrain this + * substream to match it. */ + if (twl4030->configured) + twl4030_constraints(twl4030, twl4030->master_substream); + } else { + if (!(twl4030_read(component, TWL4030_REG_CODEC_MODE) & + TWL4030_OPTION_1)) { + /* In option2 4 channel is not supported, set the + * constraint for the first stream for channels, the + * second stream will 'inherit' this cosntraint */ + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 2); + } + twl4030->master_substream = substream; + } + + return 0; +} + +static void twl4030_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + if (twl4030->master_substream == substream) + twl4030->master_substream = twl4030->slave_substream; + + twl4030->slave_substream = NULL; + + /* If all streams are closed, or the remaining stream has not yet + * been configured than set the DAI as not configured. */ + if (!twl4030->master_substream) + twl4030->configured = 0; + else if (!twl4030->master_substream->runtime->channels) + twl4030->configured = 0; + + /* If the closing substream had 4 channel, do the necessary cleanup */ + if (substream->runtime->channels == 4) + twl4030_tdm_enable(component, substream->stream, 0); +} + +static int twl4030_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + u8 mode, old_mode, format, old_format; + + /* If the substream has 4 channel, do the necessary setup */ + if (params_channels(params) == 4) { + format = twl4030_read(component, TWL4030_REG_AUDIO_IF); + mode = twl4030_read(component, TWL4030_REG_CODEC_MODE); + + /* Safety check: are we in the correct operating mode and + * the interface is in TDM mode? */ + if ((mode & TWL4030_OPTION_1) && + ((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM)) + twl4030_tdm_enable(component, substream->stream, 1); + else + return -EINVAL; + } + + if (twl4030->configured) + /* Ignoring hw_params for already configured DAI */ + return 0; + + /* bit rate */ + old_mode = twl4030_read(component, + TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ; + mode = old_mode & ~TWL4030_APLL_RATE; + + switch (params_rate(params)) { + case 8000: + mode |= TWL4030_APLL_RATE_8000; + break; + case 11025: + mode |= TWL4030_APLL_RATE_11025; + break; + case 12000: + mode |= TWL4030_APLL_RATE_12000; + break; + case 16000: + mode |= TWL4030_APLL_RATE_16000; + break; + case 22050: + mode |= TWL4030_APLL_RATE_22050; + break; + case 24000: + mode |= TWL4030_APLL_RATE_24000; + break; + case 32000: + mode |= TWL4030_APLL_RATE_32000; + break; + case 44100: + mode |= TWL4030_APLL_RATE_44100; + break; + case 48000: + mode |= TWL4030_APLL_RATE_48000; + break; + case 96000: + mode |= TWL4030_APLL_RATE_96000; + break; + default: + dev_err(component->dev, "%s: unknown rate %d\n", __func__, + params_rate(params)); + return -EINVAL; + } + + /* sample size */ + old_format = twl4030_read(component, TWL4030_REG_AUDIO_IF); + format = old_format; + format &= ~TWL4030_DATA_WIDTH; + switch (params_width(params)) { + case 16: + format |= TWL4030_DATA_WIDTH_16S_16W; + break; + case 32: + format |= TWL4030_DATA_WIDTH_32S_24W; + break; + default: + dev_err(component->dev, "%s: unsupported bits/sample %d\n", + __func__, params_width(params)); + return -EINVAL; + } + + if (format != old_format || mode != old_mode) { + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(component, 0); + twl4030_write(component, TWL4030_REG_CODEC_MODE, mode); + twl4030_write(component, TWL4030_REG_AUDIO_IF, format); + twl4030_codec_enable(component, 1); + } else { + twl4030_write(component, TWL4030_REG_CODEC_MODE, mode); + twl4030_write(component, TWL4030_REG_AUDIO_IF, format); + } + } + + /* Store the important parameters for the DAI configuration and set + * the DAI as configured */ + twl4030->configured = 1; + twl4030->rate = params_rate(params); + twl4030->sample_bits = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + twl4030->channels = params_channels(params); + + /* If both playback and capture streams are open, and one of them + * is setting the hw parameters right now (since we are here), set + * constraints to the other stream to match the current one. */ + if (twl4030->slave_substream) + twl4030_constraints(twl4030, substream); + + return 0; +} + +static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 19200000: + case 26000000: + case 38400000: + break; + default: + dev_err(component->dev, "Unsupported HFCLKIN: %u\n", freq); + return -EINVAL; + } + + if ((freq / 1000) != twl4030->sysclk) { + dev_err(component->dev, + "Mismatch in HFCLKIN: %u (configured: %u)\n", + freq, twl4030->sysclk * 1000); + return -EINVAL; + } + + return 0; +} + +static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + u8 old_format, format; + + /* get format */ + old_format = twl4030_read(component, TWL4030_REG_AUDIO_IF); + format = old_format; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + format &= ~(TWL4030_AIF_SLAVE_EN); + format &= ~(TWL4030_CLK256FS_EN); + break; + case SND_SOC_DAIFMT_CBS_CFS: + format |= TWL4030_AIF_SLAVE_EN; + format |= TWL4030_CLK256FS_EN; + break; + default: + return -EINVAL; + } + + /* interface format */ + format &= ~TWL4030_AIF_FORMAT; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format |= TWL4030_AIF_FORMAT_CODEC; + break; + case SND_SOC_DAIFMT_DSP_A: + format |= TWL4030_AIF_FORMAT_TDM; + break; + default: + return -EINVAL; + } + + if (format != old_format) { + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(component, 0); + twl4030_write(component, TWL4030_REG_AUDIO_IF, format); + twl4030_codec_enable(component, 1); + } else { + twl4030_write(component, TWL4030_REG_AUDIO_IF, format); + } + } + + return 0; +} + +static int twl4030_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_component *component = dai->component; + u8 reg = twl4030_read(component, TWL4030_REG_AUDIO_IF); + + if (tristate) + reg |= TWL4030_AIF_TRI_EN; + else + reg &= ~TWL4030_AIF_TRI_EN; + + return twl4030_write(component, TWL4030_REG_AUDIO_IF, reg); +} + +/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R + * (VTXL, VTXR) for uplink has to be enabled/disabled. */ +static void twl4030_voice_enable(struct snd_soc_component *component, int direction, + int enable) +{ + u8 reg, mask; + + reg = twl4030_read(component, TWL4030_REG_OPTION); + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + mask = TWL4030_ARXL1_VRX_EN; + else + mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN; + + if (enable) + reg |= mask; + else + reg &= ~mask; + + twl4030_write(component, TWL4030_REG_OPTION, reg); +} + +static int twl4030_voice_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + u8 mode; + + /* If the system master clock is not 26MHz, the voice PCM interface is + * not available. + */ + if (twl4030->sysclk != 26000) { + dev_err(component->dev, + "%s: HFCLKIN is %u KHz, voice interface needs 26MHz\n", + __func__, twl4030->sysclk); + return -EINVAL; + } + + /* If the codec mode is not option2, the voice PCM interface is not + * available. + */ + mode = twl4030_read(component, TWL4030_REG_CODEC_MODE) + & TWL4030_OPT_MODE; + + if (mode != TWL4030_OPTION_2) { + dev_err(component->dev, "%s: the codec mode is not option2\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static void twl4030_voice_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + + /* Enable voice digital filters */ + twl4030_voice_enable(component, substream->stream, 0); +} + +static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + u8 old_mode, mode; + + /* Enable voice digital filters */ + twl4030_voice_enable(component, substream->stream, 1); + + /* bit rate */ + old_mode = twl4030_read(component, + TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ; + mode = old_mode; + + switch (params_rate(params)) { + case 8000: + mode &= ~(TWL4030_SEL_16K); + break; + case 16000: + mode |= TWL4030_SEL_16K; + break; + default: + dev_err(component->dev, "%s: unknown rate %d\n", __func__, + params_rate(params)); + return -EINVAL; + } + + if (mode != old_mode) { + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(component, 0); + twl4030_write(component, TWL4030_REG_CODEC_MODE, mode); + twl4030_codec_enable(component, 1); + } else { + twl4030_write(component, TWL4030_REG_CODEC_MODE, mode); + } + } + + return 0; +} + +static int twl4030_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + + if (freq != 26000000) { + dev_err(component->dev, + "%s: HFCLKIN is %u KHz, voice interface needs 26MHz\n", + __func__, freq / 1000); + return -EINVAL; + } + if ((freq / 1000) != twl4030->sysclk) { + dev_err(component->dev, + "Mismatch in HFCLKIN: %u (configured: %u)\n", + freq, twl4030->sysclk * 1000); + return -EINVAL; + } + return 0; +} + +static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + u8 old_format, format; + + /* get format */ + old_format = twl4030_read(component, TWL4030_REG_VOICE_IF); + format = old_format; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + format &= ~(TWL4030_VIF_SLAVE_EN); + break; + case SND_SOC_DAIFMT_CBS_CFS: + format |= TWL4030_VIF_SLAVE_EN; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + format &= ~(TWL4030_VIF_FORMAT); + break; + case SND_SOC_DAIFMT_NB_IF: + format |= TWL4030_VIF_FORMAT; + break; + default: + return -EINVAL; + } + + if (format != old_format) { + if (twl4030->codec_powered) { + /* + * If the codec is powered, than we need to toggle the + * codec power. + */ + twl4030_codec_enable(component, 0); + twl4030_write(component, TWL4030_REG_VOICE_IF, format); + twl4030_codec_enable(component, 1); + } else { + twl4030_write(component, TWL4030_REG_VOICE_IF, format); + } + } + + return 0; +} + +static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_component *component = dai->component; + u8 reg = twl4030_read(component, TWL4030_REG_VOICE_IF); + + if (tristate) + reg |= TWL4030_VIF_TRI_EN; + else + reg &= ~TWL4030_VIF_TRI_EN; + + return twl4030_write(component, TWL4030_REG_VOICE_IF, reg); +} + +#define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) +#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops twl4030_dai_hifi_ops = { + .startup = twl4030_startup, + .shutdown = twl4030_shutdown, + .hw_params = twl4030_hw_params, + .set_sysclk = twl4030_set_dai_sysclk, + .set_fmt = twl4030_set_dai_fmt, + .set_tristate = twl4030_set_tristate, +}; + +static const struct snd_soc_dai_ops twl4030_dai_voice_ops = { + .startup = twl4030_voice_startup, + .shutdown = twl4030_voice_shutdown, + .hw_params = twl4030_voice_hw_params, + .set_sysclk = twl4030_voice_set_dai_sysclk, + .set_fmt = twl4030_voice_set_dai_fmt, + .set_tristate = twl4030_voice_set_tristate, +}; + +static struct snd_soc_dai_driver twl4030_dai[] = { +{ + .name = "twl4030-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 4, + .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000, + .formats = TWL4030_FORMATS, + .sig_bits = 24,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 4, + .rates = TWL4030_RATES, + .formats = TWL4030_FORMATS, + .sig_bits = 24,}, + .ops = &twl4030_dai_hifi_ops, +}, +{ + .name = "twl4030-voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &twl4030_dai_voice_ops, +}, +}; + +static int twl4030_soc_probe(struct snd_soc_component *component) +{ + struct twl4030_priv *twl4030; + + twl4030 = devm_kzalloc(component->dev, sizeof(struct twl4030_priv), + GFP_KERNEL); + if (!twl4030) + return -ENOMEM; + snd_soc_component_set_drvdata(component, twl4030); + /* Set the defaults, and power up the codec */ + twl4030->sysclk = twl4030_audio_get_mclk() / 1000; + + twl4030_init_chip(component); + + return 0; +} + +static void twl4030_soc_remove(struct snd_soc_component *component) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + struct twl4030_codec_data *pdata = twl4030->pdata; + + if (pdata && pdata->hs_extmute && gpio_is_valid(pdata->hs_extmute_gpio)) + gpio_free(pdata->hs_extmute_gpio); +} + +static const struct snd_soc_component_driver soc_component_dev_twl4030 = { + .probe = twl4030_soc_probe, + .remove = twl4030_soc_remove, + .read = twl4030_read, + .write = twl4030_write, + .set_bias_level = twl4030_set_bias_level, + .controls = twl4030_snd_controls, + .num_controls = ARRAY_SIZE(twl4030_snd_controls), + .dapm_widgets = twl4030_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(twl4030_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int twl4030_codec_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_twl4030, + twl4030_dai, ARRAY_SIZE(twl4030_dai)); +} + +MODULE_ALIAS("platform:twl4030-codec"); + +static struct platform_driver twl4030_codec_driver = { + .probe = twl4030_codec_probe, + .driver = { + .name = "twl4030-codec", + }, +}; + +module_platform_driver(twl4030_codec_driver); + +MODULE_DESCRIPTION("ASoC TWL4030 codec driver"); +MODULE_AUTHOR("Steve Sakoman"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c new file mode 100644 index 000000000..94675da51 --- /dev/null +++ b/sound/soc/codecs/twl6040.c @@ -0,0 +1,1192 @@ +/* + * ALSA SoC TWL6040 codec driver + * + * Author: Misael Lopez Cruz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "twl6040.h" + +enum twl6040_dai_id { + TWL6040_DAI_LEGACY = 0, + TWL6040_DAI_UL, + TWL6040_DAI_DL1, + TWL6040_DAI_DL2, + TWL6040_DAI_VIB, +}; + +#define TWL6040_RATES SNDRV_PCM_RATE_8000_96000 +#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +#define TWL6040_OUTHS_0dB 0x00 +#define TWL6040_OUTHS_M30dB 0x0F +#define TWL6040_OUTHF_0dB 0x03 +#define TWL6040_OUTHF_M52dB 0x1D + +#define TWL6040_CACHEREGNUM (TWL6040_REG_STATUS + 1) + +struct twl6040_jack_data { + struct snd_soc_jack *jack; + struct delayed_work work; + int report; +}; + +/* codec private data */ +struct twl6040_data { + int plug_irq; + int codec_powered; + int pll; + int pll_power_mode; + int hs_power_mode; + int hs_power_mode_locked; + bool dl1_unmuted; + bool dl2_unmuted; + u8 dl12_cache[TWL6040_REG_HFRCTL - TWL6040_REG_HSLCTL + 1]; + unsigned int clk_in; + unsigned int sysclk; + struct twl6040_jack_data hs_jack; + struct snd_soc_component *component; + struct mutex mutex; +}; + +/* set of rates for each pll: low-power and high-performance */ +static const unsigned int lp_rates[] = { + 8000, + 11250, + 16000, + 22500, + 32000, + 44100, + 48000, + 88200, + 96000, +}; + +static const unsigned int hp_rates[] = { + 8000, + 16000, + 32000, + 48000, + 96000, +}; + +static const struct snd_pcm_hw_constraint_list sysclk_constraints[] = { + { .count = ARRAY_SIZE(lp_rates), .list = lp_rates, }, + { .count = ARRAY_SIZE(hp_rates), .list = hp_rates, }, +}; + +#define to_twl6040(component) dev_get_drvdata((component)->dev->parent) + +static unsigned int twl6040_read(struct snd_soc_component *component, unsigned int reg) +{ + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + struct twl6040 *twl6040 = to_twl6040(component); + u8 value; + + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + switch (reg) { + case TWL6040_REG_HSLCTL: + case TWL6040_REG_HSRCTL: + case TWL6040_REG_EARCTL: + case TWL6040_REG_HFLCTL: + case TWL6040_REG_HFRCTL: + value = priv->dl12_cache[reg - TWL6040_REG_HSLCTL]; + break; + default: + value = twl6040_reg_read(twl6040, reg); + break; + } + + return value; +} + +static bool twl6040_can_write_to_chip(struct snd_soc_component *component, + unsigned int reg) +{ + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + switch (reg) { + case TWL6040_REG_HSLCTL: + case TWL6040_REG_HSRCTL: + case TWL6040_REG_EARCTL: + /* DL1 path */ + return priv->dl1_unmuted; + case TWL6040_REG_HFLCTL: + case TWL6040_REG_HFRCTL: + return priv->dl2_unmuted; + default: + return true; + } +} + +static inline void twl6040_update_dl12_cache(struct snd_soc_component *component, + u8 reg, u8 value) +{ + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + switch (reg) { + case TWL6040_REG_HSLCTL: + case TWL6040_REG_HSRCTL: + case TWL6040_REG_EARCTL: + case TWL6040_REG_HFLCTL: + case TWL6040_REG_HFRCTL: + priv->dl12_cache[reg - TWL6040_REG_HSLCTL] = value; + break; + default: + break; + } +} + +static int twl6040_write(struct snd_soc_component *component, + unsigned int reg, unsigned int value) +{ + struct twl6040 *twl6040 = to_twl6040(component); + + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + twl6040_update_dl12_cache(component, reg, value); + if (twl6040_can_write_to_chip(component, reg)) + return twl6040_reg_write(twl6040, reg, value); + else + return 0; +} + +static void twl6040_init_chip(struct snd_soc_component *component) +{ + twl6040_read(component, TWL6040_REG_TRIM1); + twl6040_read(component, TWL6040_REG_TRIM2); + twl6040_read(component, TWL6040_REG_TRIM3); + twl6040_read(component, TWL6040_REG_HSOTRIM); + twl6040_read(component, TWL6040_REG_HFOTRIM); + + /* Change chip defaults */ + /* No imput selected for microphone amplifiers */ + twl6040_write(component, TWL6040_REG_MICLCTL, 0x18); + twl6040_write(component, TWL6040_REG_MICRCTL, 0x18); + + /* + * We need to lower the default gain values, so the ramp code + * can work correctly for the first playback. + * This reduces the pop noise heard at the first playback. + */ + twl6040_write(component, TWL6040_REG_HSGAIN, 0xff); + twl6040_write(component, TWL6040_REG_EARCTL, 0x1e); + twl6040_write(component, TWL6040_REG_HFLGAIN, 0x1d); + twl6040_write(component, TWL6040_REG_HFRGAIN, 0x1d); + twl6040_write(component, TWL6040_REG_LINEGAIN, 0); +} + +/* set headset dac and driver power mode */ +static int headset_power_mode(struct snd_soc_component *component, int high_perf) +{ + int hslctl, hsrctl; + int mask = TWL6040_HSDRVMODE | TWL6040_HSDACMODE; + + hslctl = twl6040_read(component, TWL6040_REG_HSLCTL); + hsrctl = twl6040_read(component, TWL6040_REG_HSRCTL); + + if (high_perf) { + hslctl &= ~mask; + hsrctl &= ~mask; + } else { + hslctl |= mask; + hsrctl |= mask; + } + + twl6040_write(component, TWL6040_REG_HSLCTL, hslctl); + twl6040_write(component, TWL6040_REG_HSRCTL, hsrctl); + + return 0; +} + +static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u8 hslctl, hsrctl; + + /* + * Workaround for Headset DC offset caused pop noise: + * Both HS DAC need to be turned on (before the HS driver) and off at + * the same time. + */ + hslctl = twl6040_read(component, TWL6040_REG_HSLCTL); + hsrctl = twl6040_read(component, TWL6040_REG_HSRCTL); + if (SND_SOC_DAPM_EVENT_ON(event)) { + hslctl |= TWL6040_HSDACENA; + hsrctl |= TWL6040_HSDACENA; + } else { + hslctl &= ~TWL6040_HSDACENA; + hsrctl &= ~TWL6040_HSDACENA; + } + twl6040_write(component, TWL6040_REG_HSLCTL, hslctl); + twl6040_write(component, TWL6040_REG_HSRCTL, hsrctl); + + msleep(1); + return 0; +} + +static int twl6040_ep_drv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* Earphone doesn't support low power mode */ + priv->hs_power_mode_locked = 1; + ret = headset_power_mode(component, 1); + } else { + priv->hs_power_mode_locked = 0; + ret = headset_power_mode(component, priv->hs_power_mode); + } + + msleep(1); + + return ret; +} + +static void twl6040_hs_jack_report(struct snd_soc_component *component, + struct snd_soc_jack *jack, int report) +{ + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int status; + + mutex_lock(&priv->mutex); + + /* Sync status */ + status = twl6040_read(component, TWL6040_REG_STATUS); + if (status & TWL6040_PLUGCOMP) + snd_soc_jack_report(jack, report, report); + else + snd_soc_jack_report(jack, 0, report); + + mutex_unlock(&priv->mutex); +} + +void twl6040_hs_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, int report) +{ + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + struct twl6040_jack_data *hs_jack = &priv->hs_jack; + + hs_jack->jack = jack; + hs_jack->report = report; + + twl6040_hs_jack_report(component, hs_jack->jack, hs_jack->report); +} +EXPORT_SYMBOL_GPL(twl6040_hs_jack_detect); + +static void twl6040_accessory_work(struct work_struct *work) +{ + struct twl6040_data *priv = container_of(work, + struct twl6040_data, hs_jack.work.work); + struct snd_soc_component *component = priv->component; + struct twl6040_jack_data *hs_jack = &priv->hs_jack; + + twl6040_hs_jack_report(component, hs_jack->jack, hs_jack->report); +} + +/* audio interrupt handler */ +static irqreturn_t twl6040_audio_handler(int irq, void *data) +{ + struct snd_soc_component *component = data; + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + queue_delayed_work(system_power_efficient_wq, + &priv->hs_jack.work, msecs_to_jiffies(200)); + + return IRQ_HANDLED; +} + +static int twl6040_soc_dapm_put_vibra_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val; + + /* Do not allow changes while Input/FF efect is running */ + val = twl6040_read(component, e->reg); + if (val & TWL6040_VIBENA && !(val & TWL6040_VIBSEL)) + return -EBUSY; + + return snd_soc_dapm_put_enum_double(kcontrol, ucontrol); +} + +/* + * MICATT volume control: + * from -6 to 0 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0); + +/* + * MICGAIN volume control: + * from 6 to 30 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0); + +/* + * AFMGAIN volume control: + * from -18 to 24 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(afm_amp_tlv, -1800, 600, 0); + +/* + * HSGAIN volume control: + * from -30 to 0 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(hs_tlv, -3000, 200, 0); + +/* + * HFGAIN volume control: + * from -52 to 6 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(hf_tlv, -5200, 200, 0); + +/* + * EPGAIN volume control: + * from -24 to 6 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(ep_tlv, -2400, 200, 0); + +/* Left analog microphone selection */ +static const char *twl6040_amicl_texts[] = + {"Headset Mic", "Main Mic", "Aux/FM Left", "Off"}; + +/* Right analog microphone selection */ +static const char *twl6040_amicr_texts[] = + {"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"}; + +static const struct soc_enum twl6040_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, + ARRAY_SIZE(twl6040_amicl_texts), twl6040_amicl_texts), + SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, + ARRAY_SIZE(twl6040_amicr_texts), twl6040_amicr_texts), +}; + +static const char *twl6040_hs_texts[] = { + "Off", "HS DAC", "Line-In amp" +}; + +static const struct soc_enum twl6040_hs_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_HSLCTL, 5, ARRAY_SIZE(twl6040_hs_texts), + twl6040_hs_texts), + SOC_ENUM_SINGLE(TWL6040_REG_HSRCTL, 5, ARRAY_SIZE(twl6040_hs_texts), + twl6040_hs_texts), +}; + +static const char *twl6040_hf_texts[] = { + "Off", "HF DAC", "Line-In amp" +}; + +static const struct soc_enum twl6040_hf_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_HFLCTL, 2, ARRAY_SIZE(twl6040_hf_texts), + twl6040_hf_texts), + SOC_ENUM_SINGLE(TWL6040_REG_HFRCTL, 2, ARRAY_SIZE(twl6040_hf_texts), + twl6040_hf_texts), +}; + +static const char *twl6040_vibrapath_texts[] = { + "Input FF", "Audio PDM" +}; + +static const struct soc_enum twl6040_vibra_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_VIBCTLL, 1, + ARRAY_SIZE(twl6040_vibrapath_texts), + twl6040_vibrapath_texts), + SOC_ENUM_SINGLE(TWL6040_REG_VIBCTLR, 1, + ARRAY_SIZE(twl6040_vibrapath_texts), + twl6040_vibrapath_texts), +}; + +static const struct snd_kcontrol_new amicl_control = + SOC_DAPM_ENUM("Route", twl6040_enum[0]); + +static const struct snd_kcontrol_new amicr_control = + SOC_DAPM_ENUM("Route", twl6040_enum[1]); + +/* Headset DAC playback switches */ +static const struct snd_kcontrol_new hsl_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hs_enum[0]); + +static const struct snd_kcontrol_new hsr_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hs_enum[1]); + +/* Handsfree DAC playback switches */ +static const struct snd_kcontrol_new hfl_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hf_enum[0]); + +static const struct snd_kcontrol_new hfr_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hf_enum[1]); + +static const struct snd_kcontrol_new ep_path_enable_control = + SOC_DAPM_SINGLE_VIRT("Switch", 1); + +static const struct snd_kcontrol_new auxl_switch_control = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 6, 1, 0); + +static const struct snd_kcontrol_new auxr_switch_control = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 6, 1, 0); + +/* Vibra playback switches */ +static const struct snd_kcontrol_new vibral_mux_controls = + SOC_DAPM_ENUM_EXT("Route", twl6040_vibra_enum[0], + snd_soc_dapm_get_enum_double, + twl6040_soc_dapm_put_vibra_enum); + +static const struct snd_kcontrol_new vibrar_mux_controls = + SOC_DAPM_ENUM_EXT("Route", twl6040_vibra_enum[1], + snd_soc_dapm_get_enum_double, + twl6040_soc_dapm_put_vibra_enum); + +/* Headset power mode */ +static const char *twl6040_power_mode_texts[] = { + "Low-Power", "High-Performance", +}; + +static SOC_ENUM_SINGLE_EXT_DECL(twl6040_power_mode_enum, + twl6040_power_mode_texts); + +static int twl6040_headset_power_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = priv->hs_power_mode; + + return 0; +} + +static int twl6040_headset_power_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int high_perf = ucontrol->value.enumerated.item[0]; + int ret = 0; + + if (!priv->hs_power_mode_locked) + ret = headset_power_mode(component, high_perf); + + if (!ret) + priv->hs_power_mode = high_perf; + + return ret; +} + +static int twl6040_pll_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = priv->pll_power_mode; + + return 0; +} + +static int twl6040_pll_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + priv->pll_power_mode = ucontrol->value.enumerated.item[0]; + + return 0; +} + +int twl6040_get_dl1_gain(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (snd_soc_dapm_get_pin_status(dapm, "EP")) + return -1; /* -1dB */ + + if (snd_soc_dapm_get_pin_status(dapm, "HSOR") || + snd_soc_dapm_get_pin_status(dapm, "HSOL")) { + + u8 val = twl6040_read(component, TWL6040_REG_HSLCTL); + if (val & TWL6040_HSDACMODE) + /* HSDACL in LP mode */ + return -8; /* -8dB */ + else + /* HSDACL in HP mode */ + return -1; /* -1dB */ + } + return 0; /* 0dB */ +} +EXPORT_SYMBOL_GPL(twl6040_get_dl1_gain); + +int twl6040_get_clk_id(struct snd_soc_component *component) +{ + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + return priv->pll_power_mode; +} +EXPORT_SYMBOL_GPL(twl6040_get_clk_id); + +int twl6040_get_trim_value(struct snd_soc_component *component, enum twl6040_trim trim) +{ + if (unlikely(trim >= TWL6040_TRIM_INVAL)) + return -EINVAL; + + return twl6040_read(component, TWL6040_REG_TRIM1 + trim); +} +EXPORT_SYMBOL_GPL(twl6040_get_trim_value); + +int twl6040_get_hs_step_size(struct snd_soc_component *component) +{ + struct twl6040 *twl6040 = to_twl6040(component); + + if (twl6040_get_revid(twl6040) < TWL6040_REV_ES1_3) + /* For ES under ES_1.3 HS step is 2 mV */ + return 2; + else + /* For ES_1.3 HS step is 1 mV */ + return 1; +} +EXPORT_SYMBOL_GPL(twl6040_get_hs_step_size); + +static const struct snd_kcontrol_new twl6040_snd_controls[] = { + /* Capture gains */ + SOC_DOUBLE_TLV("Capture Preamplifier Volume", + TWL6040_REG_MICGAIN, 6, 7, 1, 1, mic_preamp_tlv), + SOC_DOUBLE_TLV("Capture Volume", + TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv), + + /* AFM gains */ + SOC_DOUBLE_TLV("Aux FM Volume", + TWL6040_REG_LINEGAIN, 0, 3, 7, 0, afm_amp_tlv), + + /* Playback gains */ + SOC_DOUBLE_TLV("Headset Playback Volume", + TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv), + SOC_DOUBLE_R_TLV("Handsfree Playback Volume", + TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv), + SOC_SINGLE_TLV("Earphone Playback Volume", + TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv), + + SOC_ENUM_EXT("Headset Power Mode", twl6040_power_mode_enum, + twl6040_headset_power_get_enum, + twl6040_headset_power_put_enum), + + /* Left HS PDM data routed to Right HSDAC */ + SOC_SINGLE("Headset Mono to Stereo Playback Switch", + TWL6040_REG_HSRCTL, 7, 1, 0), + + /* Left HF PDM data routed to Right HFDAC */ + SOC_SINGLE("Handsfree Mono to Stereo Playback Switch", + TWL6040_REG_HFRCTL, 5, 1, 0), + + SOC_ENUM_EXT("PLL Selection", twl6040_power_mode_enum, + twl6040_pll_get_enum, twl6040_pll_put_enum), +}; + +static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("MAINMIC"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("SUBMIC"), + SND_SOC_DAPM_INPUT("AFML"), + SND_SOC_DAPM_INPUT("AFMR"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HSOL"), + SND_SOC_DAPM_OUTPUT("HSOR"), + SND_SOC_DAPM_OUTPUT("HFL"), + SND_SOC_DAPM_OUTPUT("HFR"), + SND_SOC_DAPM_OUTPUT("EP"), + SND_SOC_DAPM_OUTPUT("AUXL"), + SND_SOC_DAPM_OUTPUT("AUXR"), + SND_SOC_DAPM_OUTPUT("VIBRAL"), + SND_SOC_DAPM_OUTPUT("VIBRAR"), + + /* Analog input muxes for the capture amplifiers */ + SND_SOC_DAPM_MUX("Analog Left Capture Route", + SND_SOC_NOPM, 0, 0, &amicl_control), + SND_SOC_DAPM_MUX("Analog Right Capture Route", + SND_SOC_NOPM, 0, 0, &amicr_control), + + /* Analog capture PGAs */ + SND_SOC_DAPM_PGA("MicAmpL", + TWL6040_REG_MICLCTL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("MicAmpR", + TWL6040_REG_MICRCTL, 0, 0, NULL, 0), + + /* Auxiliary FM PGAs */ + SND_SOC_DAPM_PGA("AFMAmpL", + TWL6040_REG_MICLCTL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("AFMAmpR", + TWL6040_REG_MICRCTL, 1, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC Left", NULL, TWL6040_REG_MICLCTL, 2, 0), + SND_SOC_DAPM_ADC("ADC Right", NULL, TWL6040_REG_MICRCTL, 2, 0), + + /* Microphone bias */ + SND_SOC_DAPM_SUPPLY("Headset Mic Bias", + TWL6040_REG_AMICBCTL, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Main Mic Bias", + TWL6040_REG_AMICBCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Digital Mic1 Bias", + TWL6040_REG_DMICBCTL, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Digital Mic2 Bias", + TWL6040_REG_DMICBCTL, 4, 0, NULL, 0), + + /* DACs */ + SND_SOC_DAPM_DAC("HSDAC Left", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HSDAC Right", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HFDAC Left", NULL, TWL6040_REG_HFLCTL, 0, 0), + SND_SOC_DAPM_DAC("HFDAC Right", NULL, TWL6040_REG_HFRCTL, 0, 0), + /* Virtual DAC for vibra path (DL4 channel) */ + SND_SOC_DAPM_DAC("VIBRA DAC", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("Handsfree Left Playback", + SND_SOC_NOPM, 0, 0, &hfl_mux_controls), + SND_SOC_DAPM_MUX("Handsfree Right Playback", + SND_SOC_NOPM, 0, 0, &hfr_mux_controls), + /* Analog playback Muxes */ + SND_SOC_DAPM_MUX("Headset Left Playback", + SND_SOC_NOPM, 0, 0, &hsl_mux_controls), + SND_SOC_DAPM_MUX("Headset Right Playback", + SND_SOC_NOPM, 0, 0, &hsr_mux_controls), + + SND_SOC_DAPM_MUX("Vibra Left Playback", SND_SOC_NOPM, 0, 0, + &vibral_mux_controls), + SND_SOC_DAPM_MUX("Vibra Right Playback", SND_SOC_NOPM, 0, 0, + &vibrar_mux_controls), + + SND_SOC_DAPM_SWITCH("Earphone Playback", SND_SOC_NOPM, 0, 0, + &ep_path_enable_control), + SND_SOC_DAPM_SWITCH("AUXL Playback", SND_SOC_NOPM, 0, 0, + &auxl_switch_control), + SND_SOC_DAPM_SWITCH("AUXR Playback", SND_SOC_NOPM, 0, 0, + &auxr_switch_control), + + /* Analog playback drivers */ + SND_SOC_DAPM_OUT_DRV("HF Left Driver", + TWL6040_REG_HFLCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HF Right Driver", + TWL6040_REG_HFRCTL, 4, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HS Left Driver", + TWL6040_REG_HSLCTL, 2, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("HS Right Driver", + TWL6040_REG_HSRCTL, 2, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV_E("Earphone Driver", + TWL6040_REG_EARCTL, 0, 0, NULL, 0, + twl6040_ep_drv_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV("Vibra Left Driver", + TWL6040_REG_VIBCTLL, 0, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV("Vibra Right Driver", + TWL6040_REG_VIBCTLR, 0, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Vibra Left Control", TWL6040_REG_VIBCTLL, 2, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("Vibra Right Control", TWL6040_REG_VIBCTLR, 2, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("HSDAC Power", 1, SND_SOC_NOPM, 0, 0, + twl6040_hs_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Analog playback PGAs */ + SND_SOC_DAPM_PGA("HF Left PGA", + TWL6040_REG_HFLCTL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HF Right PGA", + TWL6040_REG_HFRCTL, 1, 0, NULL, 0), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Stream -> DAC mapping */ + {"HSDAC Left", NULL, "Legacy Playback"}, + {"HSDAC Left", NULL, "Headset Playback"}, + {"HSDAC Right", NULL, "Legacy Playback"}, + {"HSDAC Right", NULL, "Headset Playback"}, + + {"HFDAC Left", NULL, "Legacy Playback"}, + {"HFDAC Left", NULL, "Handsfree Playback"}, + {"HFDAC Right", NULL, "Legacy Playback"}, + {"HFDAC Right", NULL, "Handsfree Playback"}, + + {"VIBRA DAC", NULL, "Legacy Playback"}, + {"VIBRA DAC", NULL, "Vibra Playback"}, + + /* ADC -> Stream mapping */ + {"Legacy Capture" , NULL, "ADC Left"}, + {"Capture", NULL, "ADC Left"}, + {"Legacy Capture", NULL, "ADC Right"}, + {"Capture" , NULL, "ADC Right"}, + + /* Capture path */ + {"Analog Left Capture Route", "Headset Mic", "HSMIC"}, + {"Analog Left Capture Route", "Main Mic", "MAINMIC"}, + {"Analog Left Capture Route", "Aux/FM Left", "AFML"}, + + {"Analog Right Capture Route", "Headset Mic", "HSMIC"}, + {"Analog Right Capture Route", "Sub Mic", "SUBMIC"}, + {"Analog Right Capture Route", "Aux/FM Right", "AFMR"}, + + {"MicAmpL", NULL, "Analog Left Capture Route"}, + {"MicAmpR", NULL, "Analog Right Capture Route"}, + + {"ADC Left", NULL, "MicAmpL"}, + {"ADC Right", NULL, "MicAmpR"}, + + /* AFM path */ + {"AFMAmpL", NULL, "AFML"}, + {"AFMAmpR", NULL, "AFMR"}, + + {"HSDAC Left", NULL, "HSDAC Power"}, + {"HSDAC Right", NULL, "HSDAC Power"}, + + {"Headset Left Playback", "HS DAC", "HSDAC Left"}, + {"Headset Left Playback", "Line-In amp", "AFMAmpL"}, + + {"Headset Right Playback", "HS DAC", "HSDAC Right"}, + {"Headset Right Playback", "Line-In amp", "AFMAmpR"}, + + {"HS Left Driver", NULL, "Headset Left Playback"}, + {"HS Right Driver", NULL, "Headset Right Playback"}, + + {"HSOL", NULL, "HS Left Driver"}, + {"HSOR", NULL, "HS Right Driver"}, + + /* Earphone playback path */ + {"Earphone Playback", "Switch", "HSDAC Left"}, + {"Earphone Driver", NULL, "Earphone Playback"}, + {"EP", NULL, "Earphone Driver"}, + + {"Handsfree Left Playback", "HF DAC", "HFDAC Left"}, + {"Handsfree Left Playback", "Line-In amp", "AFMAmpL"}, + + {"Handsfree Right Playback", "HF DAC", "HFDAC Right"}, + {"Handsfree Right Playback", "Line-In amp", "AFMAmpR"}, + + {"HF Left PGA", NULL, "Handsfree Left Playback"}, + {"HF Right PGA", NULL, "Handsfree Right Playback"}, + + {"HF Left Driver", NULL, "HF Left PGA"}, + {"HF Right Driver", NULL, "HF Right PGA"}, + + {"HFL", NULL, "HF Left Driver"}, + {"HFR", NULL, "HF Right Driver"}, + + {"AUXL Playback", "Switch", "HF Left PGA"}, + {"AUXR Playback", "Switch", "HF Right PGA"}, + + {"AUXL", NULL, "AUXL Playback"}, + {"AUXR", NULL, "AUXR Playback"}, + + /* Vibrator paths */ + {"Vibra Left Playback", "Audio PDM", "VIBRA DAC"}, + {"Vibra Right Playback", "Audio PDM", "VIBRA DAC"}, + + {"Vibra Left Driver", NULL, "Vibra Left Playback"}, + {"Vibra Right Driver", NULL, "Vibra Right Playback"}, + {"Vibra Left Driver", NULL, "Vibra Left Control"}, + {"Vibra Right Driver", NULL, "Vibra Right Control"}, + + {"VIBRAL", NULL, "Vibra Left Driver"}, + {"VIBRAR", NULL, "Vibra Right Driver"}, +}; + +static int twl6040_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct twl6040 *twl6040 = to_twl6040(component); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (priv->codec_powered) { + /* Select low power PLL in standby */ + ret = twl6040_set_pll(twl6040, TWL6040_SYSCLK_SEL_LPPLL, + 32768, 19200000); + break; + } + + ret = twl6040_power(twl6040, 1); + if (ret) + break; + + priv->codec_powered = 1; + + /* Set external boost GPO */ + twl6040_write(component, TWL6040_REG_GPOCTL, 0x02); + break; + case SND_SOC_BIAS_OFF: + if (!priv->codec_powered) + break; + + twl6040_power(twl6040, 0); + priv->codec_powered = 0; + break; + } + + return ret; +} + +static int twl6040_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &sysclk_constraints[priv->pll_power_mode]); + + return 0; +} + +static int twl6040_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int rate; + + rate = params_rate(params); + switch (rate) { + case 11250: + case 22500: + case 44100: + case 88200: + /* These rates are not supported when HPPLL is in use */ + if (unlikely(priv->pll == TWL6040_SYSCLK_SEL_HPPLL)) { + dev_err(component->dev, "HPPLL does not support rate %d\n", + rate); + return -EINVAL; + } + priv->sysclk = 17640000; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 96000: + priv->sysclk = 19200000; + break; + default: + dev_err(component->dev, "unsupported rate %d\n", rate); + return -EINVAL; + } + + return 0; +} + +static int twl6040_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct twl6040 *twl6040 = to_twl6040(component); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int ret; + + if (!priv->sysclk) { + dev_err(component->dev, + "no mclk configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + ret = twl6040_set_pll(twl6040, priv->pll, priv->clk_in, priv->sysclk); + if (ret) { + dev_err(component->dev, "Can not set PLL (%d)\n", ret); + return -EPERM; + } + + return 0; +} + +static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case TWL6040_SYSCLK_SEL_LPPLL: + case TWL6040_SYSCLK_SEL_HPPLL: + priv->pll = clk_id; + priv->clk_in = freq; + break; + default: + dev_err(component->dev, "unknown clk_id %d\n", clk_id); + return -EINVAL; + } + + return 0; +} + +static void twl6040_mute_path(struct snd_soc_component *component, enum twl6040_dai_id id, + int mute) +{ + struct twl6040 *twl6040 = to_twl6040(component); + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int hslctl, hsrctl, earctl; + int hflctl, hfrctl; + + switch (id) { + case TWL6040_DAI_DL1: + hslctl = twl6040_read(component, TWL6040_REG_HSLCTL); + hsrctl = twl6040_read(component, TWL6040_REG_HSRCTL); + earctl = twl6040_read(component, TWL6040_REG_EARCTL); + + if (mute) { + /* Power down drivers and DACs */ + earctl &= ~0x01; + hslctl &= ~(TWL6040_HSDRVENA | TWL6040_HSDACENA); + hsrctl &= ~(TWL6040_HSDRVENA | TWL6040_HSDACENA); + + } + + twl6040_reg_write(twl6040, TWL6040_REG_EARCTL, earctl); + twl6040_reg_write(twl6040, TWL6040_REG_HSLCTL, hslctl); + twl6040_reg_write(twl6040, TWL6040_REG_HSRCTL, hsrctl); + priv->dl1_unmuted = !mute; + break; + case TWL6040_DAI_DL2: + hflctl = twl6040_read(component, TWL6040_REG_HFLCTL); + hfrctl = twl6040_read(component, TWL6040_REG_HFRCTL); + + if (mute) { + /* Power down drivers and DACs */ + hflctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA | + TWL6040_HFDRVENA | TWL6040_HFSWENA); + hfrctl &= ~(TWL6040_HFDACENA | TWL6040_HFPGAENA | + TWL6040_HFDRVENA | TWL6040_HFSWENA); + } + + twl6040_reg_write(twl6040, TWL6040_REG_HFLCTL, hflctl); + twl6040_reg_write(twl6040, TWL6040_REG_HFRCTL, hfrctl); + priv->dl2_unmuted = !mute; + break; + default: + break; + } +} + +static int twl6040_digital_mute(struct snd_soc_dai *dai, int mute) +{ + switch (dai->id) { + case TWL6040_DAI_LEGACY: + twl6040_mute_path(dai->component, TWL6040_DAI_DL1, mute); + twl6040_mute_path(dai->component, TWL6040_DAI_DL2, mute); + break; + case TWL6040_DAI_DL1: + case TWL6040_DAI_DL2: + twl6040_mute_path(dai->component, dai->id, mute); + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_dai_ops twl6040_dai_ops = { + .startup = twl6040_startup, + .hw_params = twl6040_hw_params, + .prepare = twl6040_prepare, + .set_sysclk = twl6040_set_dai_sysclk, + .digital_mute = twl6040_digital_mute, +}; + +static struct snd_soc_dai_driver twl6040_dai[] = { +{ + .name = "twl6040-legacy", + .id = TWL6040_DAI_LEGACY, + .playback = { + .stream_name = "Legacy Playback", + .channels_min = 1, + .channels_max = 5, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .capture = { + .stream_name = "Legacy Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .ops = &twl6040_dai_ops, +}, +{ + .name = "twl6040-ul", + .id = TWL6040_DAI_UL, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .ops = &twl6040_dai_ops, +}, +{ + .name = "twl6040-dl1", + .id = TWL6040_DAI_DL1, + .playback = { + .stream_name = "Headset Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .ops = &twl6040_dai_ops, +}, +{ + .name = "twl6040-dl2", + .id = TWL6040_DAI_DL2, + .playback = { + .stream_name = "Handsfree Playback", + .channels_min = 1, + .channels_max = 2, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .ops = &twl6040_dai_ops, +}, +{ + .name = "twl6040-vib", + .id = TWL6040_DAI_VIB, + .playback = { + .stream_name = "Vibra Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = TWL6040_FORMATS, + }, + .ops = &twl6040_dai_ops, +}, +}; + +static int twl6040_probe(struct snd_soc_component *component) +{ + struct twl6040_data *priv; + struct platform_device *pdev = to_platform_device(component->dev); + int ret = 0; + + priv = devm_kzalloc(component->dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + snd_soc_component_set_drvdata(component, priv); + + priv->component = component; + + priv->plug_irq = platform_get_irq(pdev, 0); + if (priv->plug_irq < 0) { + dev_err(component->dev, "invalid irq: %d\n", priv->plug_irq); + return priv->plug_irq; + } + + INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work); + + mutex_init(&priv->mutex); + + ret = request_threaded_irq(priv->plug_irq, NULL, + twl6040_audio_handler, + IRQF_NO_SUSPEND | IRQF_ONESHOT, + "twl6040_irq_plug", component); + if (ret) { + dev_err(component->dev, "PLUG IRQ request failed: %d\n", ret); + return ret; + } + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + twl6040_init_chip(component); + + return 0; +} + +static void twl6040_remove(struct snd_soc_component *component) +{ + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + + free_irq(priv->plug_irq, component); +} + +static const struct snd_soc_component_driver soc_component_dev_twl6040 = { + .probe = twl6040_probe, + .remove = twl6040_remove, + .read = twl6040_read, + .write = twl6040_write, + .set_bias_level = twl6040_set_bias_level, + .controls = twl6040_snd_controls, + .num_controls = ARRAY_SIZE(twl6040_snd_controls), + .dapm_widgets = twl6040_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(twl6040_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int twl6040_codec_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_twl6040, + twl6040_dai, ARRAY_SIZE(twl6040_dai)); +} + +static struct platform_driver twl6040_codec_driver = { + .driver = { + .name = "twl6040-codec", + }, + .probe = twl6040_codec_probe, +}; + +module_platform_driver(twl6040_codec_driver); + +MODULE_DESCRIPTION("ASoC TWL6040 codec driver"); +MODULE_AUTHOR("Misael Lopez Cruz"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/twl6040.h b/sound/soc/codecs/twl6040.h new file mode 100644 index 000000000..3d9f95707 --- /dev/null +++ b/sound/soc/codecs/twl6040.h @@ -0,0 +1,44 @@ +/* + * ALSA SoC TWL6040 codec driver + * + * Author: Misael Lopez Cruz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TWL6040_H__ +#define __TWL6040_H__ + +enum twl6040_trim { + TWL6040_TRIM_TRIM1 = 0, + TWL6040_TRIM_TRIM2, + TWL6040_TRIM_TRIM3, + TWL6040_TRIM_HSOTRIM, + TWL6040_TRIM_HFOTRIM, + TWL6040_TRIM_INVAL, +}; + +#define TWL6040_HSF_TRIM_LEFT(x) (x & 0x0f) +#define TWL6040_HSF_TRIM_RIGHT(x) ((x >> 4) & 0x0f) + +int twl6040_get_dl1_gain(struct snd_soc_component *component); +void twl6040_hs_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, int report); +int twl6040_get_clk_id(struct snd_soc_component *component); +int twl6040_get_trim_value(struct snd_soc_component *component, enum twl6040_trim trim); +int twl6040_get_hs_step_size(struct snd_soc_component *component); + +#endif /* End of __TWL6040_H__ */ diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c new file mode 100644 index 000000000..3c935a941 --- /dev/null +++ b/sound/soc/codecs/uda134x.c @@ -0,0 +1,590 @@ +/* + * uda134x.c -- UDA134X ALSA SoC Codec driver + * + * Modifications by Christian Pellegrin + * + * Copyright 2007 Dension Audio Systems Ltd. + * Author: Zoltan Devai + * + * Based on the WM87xx drivers by Liam Girdwood and Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "uda134x.h" + + +#define UDA134X_RATES SNDRV_PCM_RATE_8000_48000 +#define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE) + +struct uda134x_priv { + int sysclk; + int dai_fmt; + + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; + + struct regmap *regmap; + struct uda134x_platform_data *pd; +}; + +static const struct reg_default uda134x_reg_defaults[] = { + { UDA134X_EA000, 0x04 }, + { UDA134X_EA001, 0x04 }, + { UDA134X_EA010, 0x04 }, + { UDA134X_EA011, 0x00 }, + { UDA134X_EA100, 0x00 }, + { UDA134X_EA101, 0x00 }, + { UDA134X_EA110, 0x00 }, + { UDA134X_EA111, 0x00 }, + { UDA134X_STATUS0, 0x00 }, + { UDA134X_STATUS1, 0x03 }, + { UDA134X_DATA000, 0x00 }, + { UDA134X_DATA001, 0x00 }, + { UDA134X_DATA010, 0x00 }, + { UDA134X_DATA011, 0x00 }, + { UDA134X_DATA1, 0x00 }, +}; + +/* + * Write to the uda134x registers + * + */ +static int uda134x_regmap_write(void *context, unsigned int reg, + unsigned int value) +{ + struct uda134x_platform_data *pd = context; + int ret; + u8 addr; + u8 data = value; + + switch (reg) { + case UDA134X_STATUS0: + case UDA134X_STATUS1: + addr = UDA134X_STATUS_ADDR; + data |= (reg - UDA134X_STATUS0) << 7; + break; + case UDA134X_DATA000: + case UDA134X_DATA001: + case UDA134X_DATA010: + case UDA134X_DATA011: + addr = UDA134X_DATA0_ADDR; + data |= (reg - UDA134X_DATA000) << 6; + break; + case UDA134X_DATA1: + addr = UDA134X_DATA1_ADDR; + break; + default: + /* It's an extended address register */ + addr = (reg | UDA134X_EXTADDR_PREFIX); + + ret = l3_write(&pd->l3, + UDA134X_DATA0_ADDR, &addr, 1); + if (ret != 1) + return -EIO; + + addr = UDA134X_DATA0_ADDR; + data = (value | UDA134X_EXTDATA_PREFIX); + break; + } + + ret = l3_write(&pd->l3, + addr, &data, 1); + if (ret != 1) + return -EIO; + + return 0; +} + +static inline void uda134x_reset(struct snd_soc_component *component) +{ + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + unsigned int mask = 1<<6; + + regmap_update_bits(uda134x->regmap, UDA134X_STATUS0, mask, mask); + msleep(1); + regmap_update_bits(uda134x->regmap, UDA134X_STATUS0, mask, 0); +} + +static int uda134x_mute(struct snd_soc_dai *dai, int mute) +{ + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(dai->component); + unsigned int mask = 1<<2; + unsigned int val; + + pr_debug("%s mute: %d\n", __func__, mute); + + if (mute) + val = mask; + else + val = 0; + + return regmap_update_bits(uda134x->regmap, UDA134X_DATA010, mask, val); +} + +static int uda134x_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *master_runtime; + + if (uda134x->master_substream) { + master_runtime = uda134x->master_substream->runtime; + + pr_debug("%s constraining to %d bits at %d\n", __func__, + master_runtime->sample_bits, + master_runtime->rate); + + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate); + + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits); + + uda134x->slave_substream = substream; + } else + uda134x->master_substream = substream; + + return 0; +} + +static void uda134x_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + + if (uda134x->master_substream == substream) + uda134x->master_substream = uda134x->slave_substream; + + uda134x->slave_substream = NULL; +} + +static int uda134x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + unsigned int hw_params = 0; + + if (substream == uda134x->slave_substream) { + pr_debug("%s ignoring hw_params for slave substream\n", + __func__); + return 0; + } + + pr_debug("%s sysclk: %d, rate:%d\n", __func__, + uda134x->sysclk, params_rate(params)); + + /* set SYSCLK / fs ratio */ + switch (uda134x->sysclk / params_rate(params)) { + case 512: + break; + case 384: + hw_params |= (1<<4); + break; + case 256: + hw_params |= (1<<5); + break; + default: + printk(KERN_ERR "%s unsupported fs\n", __func__); + return -EINVAL; + } + + pr_debug("%s dai_fmt: %d, params_format:%d\n", __func__, + uda134x->dai_fmt, params_format(params)); + + /* set DAI format and word length */ + switch (uda134x->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_width(params)) { + case 16: + hw_params |= (1<<1); + break; + case 18: + hw_params |= (1<<2); + break; + case 20: + hw_params |= ((1<<2) | (1<<1)); + break; + default: + printk(KERN_ERR "%s unsupported format (right)\n", + __func__); + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_LEFT_J: + hw_params |= (1<<3); + break; + default: + printk(KERN_ERR "%s unsupported format\n", __func__); + return -EINVAL; + } + + return regmap_update_bits(uda134x->regmap, UDA134X_STATUS0, + STATUS0_SYSCLK_MASK | STATUS0_DAIFMT_MASK, hw_params); +} + +static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + + pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__, + clk_id, freq, dir); + + /* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable + because the codec is slave. Of course limitations of the clock + master (the IIS controller) apply. + We'll error out on set_hw_params if it's not OK */ + if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) { + uda134x->sysclk = freq; + return 0; + } + + printk(KERN_ERR "%s unsupported sysclk\n", __func__); + return -EINVAL; +} + +static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + + pr_debug("%s fmt: %08X\n", __func__, fmt); + + /* codec supports only full slave mode */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + printk(KERN_ERR "%s unsupported slave mode\n", __func__); + return -EINVAL; + } + + /* no support for clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) { + printk(KERN_ERR "%s unsupported clock inversion\n", __func__); + return -EINVAL; + } + + /* We can't setup DAI format here as it depends on the word bit num */ + /* so let's just store the value for later */ + uda134x->dai_fmt = fmt; + + return 0; +} + +static int uda134x_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + struct uda134x_platform_data *pd = uda134x->pd; + pr_debug("%s bias level %d\n", __func__, level); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + /* power on */ + if (pd->power) { + pd->power(1); + regcache_sync(uda134x->regmap); + } + break; + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + /* power off */ + if (pd->power) { + pd->power(0); + regcache_mark_dirty(uda134x->regmap); + } + break; + } + return 0; +} + +static const char *uda134x_dsp_setting[] = {"Flat", "Minimum1", + "Minimum2", "Maximum"}; +static const char *uda134x_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *uda134x_mixmode[] = {"Differential", "Analog1", + "Analog2", "Both"}; + +static const struct soc_enum uda134x_mixer_enum[] = { +SOC_ENUM_SINGLE(UDA134X_DATA010, 0, 0x04, uda134x_dsp_setting), +SOC_ENUM_SINGLE(UDA134X_DATA010, 3, 0x04, uda134x_deemph), +SOC_ENUM_SINGLE(UDA134X_EA010, 0, 0x04, uda134x_mixmode), +}; + +static const struct snd_kcontrol_new uda1341_snd_controls[] = { +SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), +SOC_SINGLE("Capture Volume", UDA134X_EA010, 2, 0x07, 0), +SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1), +SOC_SINGLE("Analog2 Volume", UDA134X_EA001, 0, 0x1F, 1), + +SOC_SINGLE("Mic Sensitivity", UDA134X_EA010, 2, 7, 0), +SOC_SINGLE("Mic Volume", UDA134X_EA101, 0, 0x1F, 0), + +SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0), +SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0), + +SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]), +SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), +SOC_ENUM("Input Mux", uda134x_mixer_enum[2]), + +SOC_SINGLE("AGC Switch", UDA134X_EA100, 4, 1, 0), +SOC_SINGLE("AGC Target Volume", UDA134X_EA110, 0, 0x03, 1), +SOC_SINGLE("AGC Timing", UDA134X_EA110, 2, 0x07, 0), + +SOC_SINGLE("DAC +6dB Switch", UDA134X_STATUS1, 6, 1, 0), +SOC_SINGLE("ADC +6dB Switch", UDA134X_STATUS1, 5, 1, 0), +SOC_SINGLE("ADC Polarity Switch", UDA134X_STATUS1, 4, 1, 0), +SOC_SINGLE("DAC Polarity Switch", UDA134X_STATUS1, 3, 1, 0), +SOC_SINGLE("Double Speed Playback Switch", UDA134X_STATUS1, 2, 1, 0), +SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new uda1340_snd_controls[] = { +SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), + +SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0), +SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0), + +SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]), +SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), + +SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new uda1345_snd_controls[] = { +SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), + +SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), + +SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), +}; + +/* UDA1341 has the DAC/ADC power down in STATUS1 */ +static const struct snd_soc_dapm_widget uda1341_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", UDA134X_STATUS1, 0, 0), + SND_SOC_DAPM_ADC("ADC", "Capture", UDA134X_STATUS1, 1, 0), +}; + +/* UDA1340/4/5 has the DAC/ADC pwoer down in DATA0 11 */ +static const struct snd_soc_dapm_widget uda1340_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", UDA134X_DATA011, 0, 0), + SND_SOC_DAPM_ADC("ADC", "Capture", UDA134X_DATA011, 1, 0), +}; + +/* Common DAPM widgets */ +static const struct snd_soc_dapm_widget uda134x_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("VINL1"), + SND_SOC_DAPM_INPUT("VINR1"), + SND_SOC_DAPM_INPUT("VINL2"), + SND_SOC_DAPM_INPUT("VINR2"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), +}; + +static const struct snd_soc_dapm_route uda134x_dapm_routes[] = { + { "ADC", NULL, "VINL1" }, + { "ADC", NULL, "VINR1" }, + { "ADC", NULL, "VINL2" }, + { "ADC", NULL, "VINR2" }, + { "VOUTL", NULL, "DAC" }, + { "VOUTR", NULL, "DAC" }, +}; + +static const struct snd_soc_dai_ops uda134x_dai_ops = { + .startup = uda134x_startup, + .shutdown = uda134x_shutdown, + .hw_params = uda134x_hw_params, + .digital_mute = uda134x_mute, + .set_sysclk = uda134x_set_dai_sysclk, + .set_fmt = uda134x_set_dai_fmt, +}; + +static struct snd_soc_dai_driver uda134x_dai = { + .name = "uda134x-hifi", + /* playback capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA134X_RATES, + .formats = UDA134X_FORMATS, + }, + /* capture capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA134X_RATES, + .formats = UDA134X_FORMATS, + }, + /* pcm operations */ + .ops = &uda134x_dai_ops, +}; + +static int uda134x_soc_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component); + struct uda134x_platform_data *pd = uda134x->pd; + const struct snd_soc_dapm_widget *widgets; + unsigned num_widgets; + int ret; + + printk(KERN_INFO "UDA134X SoC Audio Codec\n"); + + switch (pd->model) { + case UDA134X_UDA1340: + case UDA134X_UDA1341: + case UDA134X_UDA1344: + case UDA134X_UDA1345: + break; + default: + printk(KERN_ERR "UDA134X SoC codec: " + "unsupported model %d\n", + pd->model); + return -EINVAL; + } + + if (pd->power) + pd->power(1); + + uda134x_reset(component); + + if (pd->model == UDA134X_UDA1341) { + widgets = uda1341_dapm_widgets; + num_widgets = ARRAY_SIZE(uda1341_dapm_widgets); + } else { + widgets = uda1340_dapm_widgets; + num_widgets = ARRAY_SIZE(uda1340_dapm_widgets); + } + + ret = snd_soc_dapm_new_controls(dapm, widgets, num_widgets); + if (ret) { + printk(KERN_ERR "%s failed to register dapm controls: %d", + __func__, ret); + return ret; + } + + switch (pd->model) { + case UDA134X_UDA1340: + case UDA134X_UDA1344: + ret = snd_soc_add_component_controls(component, uda1340_snd_controls, + ARRAY_SIZE(uda1340_snd_controls)); + break; + case UDA134X_UDA1341: + ret = snd_soc_add_component_controls(component, uda1341_snd_controls, + ARRAY_SIZE(uda1341_snd_controls)); + break; + case UDA134X_UDA1345: + ret = snd_soc_add_component_controls(component, uda1345_snd_controls, + ARRAY_SIZE(uda1345_snd_controls)); + break; + default: + printk(KERN_ERR "%s unknown codec type: %d", + __func__, pd->model); + return -EINVAL; + } + + if (ret < 0) { + printk(KERN_ERR "UDA134X: failed to register controls\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_uda134x = { + .probe = uda134x_soc_probe, + .set_bias_level = uda134x_set_bias_level, + .dapm_widgets = uda134x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(uda134x_dapm_widgets), + .dapm_routes = uda134x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(uda134x_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config uda134x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = UDA134X_DATA1, + .reg_defaults = uda134x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(uda134x_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .reg_write = uda134x_regmap_write, +}; + +static int uda134x_codec_probe(struct platform_device *pdev) +{ + struct uda134x_platform_data *pd = pdev->dev.platform_data; + struct uda134x_priv *uda134x; + int ret; + + if (!pd) { + dev_err(&pdev->dev, "Missing L3 bitbang function\n"); + return -ENODEV; + } + + uda134x = devm_kzalloc(&pdev->dev, sizeof(*uda134x), GFP_KERNEL); + if (!uda134x) + return -ENOMEM; + + uda134x->pd = pd; + platform_set_drvdata(pdev, uda134x); + + if (pd->l3.use_gpios) { + ret = l3_set_gpio_ops(&pdev->dev, &uda134x->pd->l3); + if (ret < 0) + return ret; + } + + uda134x->regmap = devm_regmap_init(&pdev->dev, NULL, pd, + &uda134x_regmap_config); + if (IS_ERR(uda134x->regmap)) + return PTR_ERR(uda134x->regmap); + + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_uda134x, &uda134x_dai, 1); +} + +static struct platform_driver uda134x_codec_driver = { + .driver = { + .name = "uda134x-codec", + }, + .probe = uda134x_codec_probe, +}; + +module_platform_driver(uda134x_codec_driver); + +MODULE_DESCRIPTION("UDA134X ALSA soc codec driver"); +MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/uda134x.h b/sound/soc/codecs/uda134x.h new file mode 100644 index 000000000..664618c25 --- /dev/null +++ b/sound/soc/codecs/uda134x.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _UDA134X_CODEC_H +#define _UDA134X_CODEC_H + +#define UDA134X_L3ADDR 5 +#define UDA134X_DATA0_ADDR ((UDA134X_L3ADDR << 2) | 0) +#define UDA134X_DATA1_ADDR ((UDA134X_L3ADDR << 2) | 1) +#define UDA134X_STATUS_ADDR ((UDA134X_L3ADDR << 2) | 2) + +#define UDA134X_EXTADDR_PREFIX 0xC0 +#define UDA134X_EXTDATA_PREFIX 0xE0 + +/* UDA134X registers */ +#define UDA134X_EA000 0 +#define UDA134X_EA001 1 +#define UDA134X_EA010 2 +#define UDA134X_EA011 3 +#define UDA134X_EA100 4 +#define UDA134X_EA101 5 +#define UDA134X_EA110 6 +#define UDA134X_EA111 7 +#define UDA134X_STATUS0 8 +#define UDA134X_STATUS1 9 +#define UDA134X_DATA000 10 +#define UDA134X_DATA001 11 +#define UDA134X_DATA010 12 +#define UDA134X_DATA011 13 +#define UDA134X_DATA1 14 + +#define STATUS0_DAIFMT_MASK (~(7<<1)) +#define STATUS0_SYSCLK_MASK (~(3<<4)) + +#endif diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c new file mode 100644 index 000000000..584a032b3 --- /dev/null +++ b/sound/soc/codecs/uda1380.c @@ -0,0 +1,814 @@ +/* + * uda1380.c - Philips UDA1380 ALSA SoC audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (c) 2007-2009 Philipp Zabel + * + * Modified by Richard Purdie to fit into SoC + * codec model. + * + * Copyright (c) 2005 Giorgio Padrin + * Copyright 2005 Openedhand Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uda1380.h" + +/* codec private data */ +struct uda1380_priv { + struct snd_soc_component *component; + unsigned int dac_clk; + struct work_struct work; + struct i2c_client *i2c; + u16 *reg_cache; +}; + +/* + * uda1380 register cache + */ +static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { + 0x0502, 0x0000, 0x0000, 0x3f3f, + 0x0202, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0xff00, 0x0000, 0x4800, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x8000, 0x0002, 0x0000, +}; + +static unsigned long uda1380_cache_dirty; + +/* + * read uda1380 register cache + */ +static inline unsigned int uda1380_read_reg_cache(struct snd_soc_component *component, + unsigned int reg) +{ + struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); + u16 *cache = uda1380->reg_cache; + + if (reg == UDA1380_RESET) + return 0; + if (reg >= UDA1380_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write uda1380 register cache + */ +static inline void uda1380_write_reg_cache(struct snd_soc_component *component, + u16 reg, unsigned int value) +{ + struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); + u16 *cache = uda1380->reg_cache; + + if (reg >= UDA1380_CACHEREGNUM) + return; + if ((reg >= 0x10) && (cache[reg] != value)) + set_bit(reg - 0x10, &uda1380_cache_dirty); + cache[reg] = value; +} + +/* + * write to the UDA1380 register space + */ +static int uda1380_write(struct snd_soc_component *component, unsigned int reg, + unsigned int value) +{ + struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); + u8 data[3]; + + /* data is + * data[0] is register offset + * data[1] is MS byte + * data[2] is LS byte + */ + data[0] = reg; + data[1] = (value & 0xff00) >> 8; + data[2] = value & 0x00ff; + + uda1380_write_reg_cache(component, reg, value); + + /* the interpolator & decimator regs must only be written when the + * codec DAI is active. + */ + if (!snd_soc_component_is_active(component) && (reg >= UDA1380_MVOL)) + return 0; + pr_debug("uda1380: hw write %x val %x\n", reg, value); + if (i2c_master_send(uda1380->i2c, data, 3) == 3) { + unsigned int val; + i2c_master_send(uda1380->i2c, data, 1); + i2c_master_recv(uda1380->i2c, data, 2); + val = (data[0]<<8) | data[1]; + if (val != value) { + pr_debug("uda1380: READ BACK VAL %x\n", + (data[0]<<8) | data[1]); + return -EIO; + } + if (reg >= 0x10) + clear_bit(reg - 0x10, &uda1380_cache_dirty); + return 0; + } else + return -EIO; +} + +static void uda1380_sync_cache(struct snd_soc_component *component) +{ + struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); + int reg; + u8 data[3]; + u16 *cache = uda1380->reg_cache; + + /* Sync reg_cache with the hardware */ + for (reg = 0; reg < UDA1380_MVOL; reg++) { + data[0] = reg; + data[1] = (cache[reg] & 0xff00) >> 8; + data[2] = cache[reg] & 0x00ff; + if (i2c_master_send(uda1380->i2c, data, 3) != 3) + dev_err(component->dev, "%s: write to reg 0x%x failed\n", + __func__, reg); + } +} + +static int uda1380_reset(struct snd_soc_component *component) +{ + struct uda1380_platform_data *pdata = component->dev->platform_data; + struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); + + if (gpio_is_valid(pdata->gpio_reset)) { + gpio_set_value(pdata->gpio_reset, 1); + mdelay(1); + gpio_set_value(pdata->gpio_reset, 0); + } else { + u8 data[3]; + + data[0] = UDA1380_RESET; + data[1] = 0; + data[2] = 0; + + if (i2c_master_send(uda1380->i2c, data, 3) != 3) { + dev_err(component->dev, "%s: failed\n", __func__); + return -EIO; + } + } + + return 0; +} + +static void uda1380_flush_work(struct work_struct *work) +{ + struct uda1380_priv *uda1380 = container_of(work, struct uda1380_priv, work); + struct snd_soc_component *uda1380_component = uda1380->component; + int bit, reg; + + for_each_set_bit(bit, &uda1380_cache_dirty, UDA1380_CACHEREGNUM - 0x10) { + reg = 0x10 + bit; + pr_debug("uda1380: flush reg %x val %x:\n", reg, + uda1380_read_reg_cache(uda1380_component, reg)); + uda1380_write(uda1380_component, reg, + uda1380_read_reg_cache(uda1380_component, reg)); + clear_bit(bit, &uda1380_cache_dirty); + } + +} + +/* declarations of ALSA reg_elem_REAL controls */ +static const char *uda1380_deemp[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", + "96kHz", +}; +static const char *uda1380_input_sel[] = { + "Line", + "Mic + Line R", + "Line L", + "Mic", +}; +static const char *uda1380_output_sel[] = { + "DAC", + "Analog Mixer", +}; +static const char *uda1380_spf_mode[] = { + "Flat", + "Minimum1", + "Minimum2", + "Maximum" +}; +static const char *uda1380_capture_sel[] = { + "ADC", + "Digital Mixer" +}; +static const char *uda1380_sel_ns[] = { + "3rd-order", + "5th-order" +}; +static const char *uda1380_mix_control[] = { + "off", + "PCM only", + "before sound processing", + "after sound processing" +}; +static const char *uda1380_sdet_setting[] = { + "3200", + "4800", + "9600", + "19200" +}; +static const char *uda1380_os_setting[] = { + "single-speed", + "double-speed (no mixing)", + "quad-speed (no mixing)" +}; + +static const struct soc_enum uda1380_deemp_enum[] = { + SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, ARRAY_SIZE(uda1380_deemp), + uda1380_deemp), + SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, ARRAY_SIZE(uda1380_deemp), + uda1380_deemp), +}; +static SOC_ENUM_SINGLE_DECL(uda1380_input_sel_enum, + UDA1380_ADC, 2, uda1380_input_sel); /* SEL_MIC, SEL_LNA */ +static SOC_ENUM_SINGLE_DECL(uda1380_output_sel_enum, + UDA1380_PM, 7, uda1380_output_sel); /* R02_EN_AVC */ +static SOC_ENUM_SINGLE_DECL(uda1380_spf_enum, + UDA1380_MODE, 14, uda1380_spf_mode); /* M */ +static SOC_ENUM_SINGLE_DECL(uda1380_capture_sel_enum, + UDA1380_IFACE, 6, uda1380_capture_sel); /* SEL_SOURCE */ +static SOC_ENUM_SINGLE_DECL(uda1380_sel_ns_enum, + UDA1380_MIXER, 14, uda1380_sel_ns); /* SEL_NS */ +static SOC_ENUM_SINGLE_DECL(uda1380_mix_enum, + UDA1380_MIXER, 12, uda1380_mix_control); /* MIX, MIX_POS */ +static SOC_ENUM_SINGLE_DECL(uda1380_sdet_enum, + UDA1380_MIXER, 4, uda1380_sdet_setting); /* SD_VALUE */ +static SOC_ENUM_SINGLE_DECL(uda1380_os_enum, + UDA1380_MIXER, 0, uda1380_os_setting); /* OS */ + +/* + * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB) + */ +static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1); + +/* + * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored), + * from -66 dB in 0.5 dB steps (2 dB steps, really) and + * from -52 dB in 0.25 dB steps + */ +static const DECLARE_TLV_DB_RANGE(mvol_tlv, + 0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1), + 16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0), + 44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0) +); + +/* + * from -72 dB in 1.5 dB steps (6 dB steps really), + * from -66 dB in 0.75 dB steps (3 dB steps really), + * from -60 dB in 0.5 dB steps (2 dB steps really) and + * from -46 dB in 0.25 dB steps + */ +static const DECLARE_TLV_DB_RANGE(vc_tlv, + 0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1), + 8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0), + 16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0), + 44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0) +); + +/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */ +static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0); + +/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts + * off at 18 dB max) */ +static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0); + +/* from -63 to 24 dB in 0.5 dB steps (-128...48) */ +static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1); + +/* from 0 to 24 dB in 3 dB steps */ +static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); + +/* from 0 to 30 dB in 2 dB steps */ +static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0); + +static const struct snd_kcontrol_new uda1380_snd_controls[] = { + SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */ + SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */ + SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */ + SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */ + SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */ + SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */ + SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */ +/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */ + SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */ + SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */ + SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */ + SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */ + SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */ + SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */ + SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */ + SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */ + SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */ + SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */ + SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */ +/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */ + SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */ + SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */ + SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */ + SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */ + SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */ + SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */ + SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */ + /* -5.5, -8, -11.5, -14 dBFS */ + SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new uda1380_input_mux_control = + SOC_DAPM_ENUM("Route", uda1380_input_sel_enum); + +/* Output mux */ +static const struct snd_kcontrol_new uda1380_output_mux_control = + SOC_DAPM_ENUM("Route", uda1380_output_sel_enum); + +/* Capture mux */ +static const struct snd_kcontrol_new uda1380_capture_mux_control = + SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum); + + +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &uda1380_input_mux_control), + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0, + &uda1380_output_mux_control), + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, + &uda1380_capture_mux_control), + SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0), + SND_SOC_DAPM_INPUT("VINM"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("VOUTLHP"), + SND_SOC_DAPM_OUTPUT("VOUTRHP"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0), + SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route uda1380_dapm_routes[] = { + + /* output mux */ + {"HeadPhone Driver", NULL, "Output Mux"}, + {"VOUTR", NULL, "Output Mux"}, + {"VOUTL", NULL, "Output Mux"}, + + {"Analog Mixer", NULL, "VINR"}, + {"Analog Mixer", NULL, "VINL"}, + {"Analog Mixer", NULL, "DAC"}, + + {"Output Mux", "DAC", "DAC"}, + {"Output Mux", "Analog Mixer", "Analog Mixer"}, + + /* {"DAC", "Digital Mixer", "I2S" } */ + + /* headphone driver */ + {"VOUTLHP", NULL, "HeadPhone Driver"}, + {"VOUTRHP", NULL, "HeadPhone Driver"}, + + /* input mux */ + {"Left ADC", NULL, "Input Mux"}, + {"Input Mux", "Mic", "Mic LNA"}, + {"Input Mux", "Mic + Line R", "Mic LNA"}, + {"Input Mux", "Line L", "Left PGA"}, + {"Input Mux", "Line", "Left PGA"}, + + /* right input */ + {"Right ADC", "Mic + Line R", "Right PGA"}, + {"Right ADC", "Line", "Right PGA"}, + + /* inputs */ + {"Mic LNA", NULL, "VINM"}, + {"Left PGA", NULL, "VINL"}, + {"Right PGA", NULL, "VINR"}, +}; + +static int uda1380_set_dai_fmt_both(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(component, UDA1380_IFACE); + iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16 | R01_SFORO_LSB16; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB | R01_SFORO_MSB; + } + + /* DATAI is slave only, so in single-link mode, this has to be slave */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + + uda1380_write_reg_cache(component, UDA1380_IFACE, iface); + + return 0; +} + +static int uda1380_set_dai_fmt_playback(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(component, UDA1380_IFACE); + iface &= ~R01_SFORI_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB; + } + + /* DATAI is slave only, so this has to be slave */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + + uda1380_write(component, UDA1380_IFACE, iface); + + return 0; +} + +static int uda1380_set_dai_fmt_capture(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(component, UDA1380_IFACE); + iface &= ~(R01_SIM | R01_SFORO_MASK); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORO_LSB16; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORO_MSB; + } + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) + iface |= R01_SIM; + + uda1380_write(component, UDA1380_IFACE, iface); + + return 0; +} + +static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); + int mixer = uda1380_read_reg_cache(component, UDA1380_MIXER); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + uda1380_write_reg_cache(component, UDA1380_MIXER, + mixer & ~R14_SILENCE); + schedule_work(&uda1380->work); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + uda1380_write_reg_cache(component, UDA1380_MIXER, + mixer | R14_SILENCE); + schedule_work(&uda1380->work); + break; + } + return 0; +} + +static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 clk = uda1380_read_reg_cache(component, UDA1380_CLK); + + /* set WSPLL power and divider if running from this clock */ + if (clk & R00_DAC_CLK) { + int rate = params_rate(params); + u16 pm = uda1380_read_reg_cache(component, UDA1380_PM); + clk &= ~0x3; /* clear SEL_LOOP_DIV */ + switch (rate) { + case 6250 ... 12500: + clk |= 0x0; + break; + case 12501 ... 25000: + clk |= 0x1; + break; + case 25001 ... 50000: + clk |= 0x2; + break; + case 50001 ... 100000: + clk |= 0x3; + break; + } + uda1380_write(component, UDA1380_PM, R02_PON_PLL | pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk |= R00_EN_DAC | R00_EN_INT; + else + clk |= R00_EN_ADC | R00_EN_DEC; + + uda1380_write(component, UDA1380_CLK, clk); + return 0; +} + +static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 clk = uda1380_read_reg_cache(component, UDA1380_CLK); + + /* shut down WSPLL power if running from this clock */ + if (clk & R00_DAC_CLK) { + u16 pm = uda1380_read_reg_cache(component, UDA1380_PM); + uda1380_write(component, UDA1380_PM, ~R02_PON_PLL & pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk &= ~(R00_EN_DAC | R00_EN_INT); + else + clk &= ~(R00_EN_ADC | R00_EN_DEC); + + uda1380_write(component, UDA1380_CLK, clk); +} + +static int uda1380_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int pm = uda1380_read_reg_cache(component, UDA1380_PM); + int reg; + struct uda1380_platform_data *pdata = component->dev->platform_data; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + /* ADC, DAC on */ + uda1380_write(component, UDA1380_PM, R02_PON_BIAS | pm); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + if (gpio_is_valid(pdata->gpio_power)) { + gpio_set_value(pdata->gpio_power, 1); + mdelay(1); + uda1380_reset(component); + } + + uda1380_sync_cache(component); + } + uda1380_write(component, UDA1380_PM, 0x0); + break; + case SND_SOC_BIAS_OFF: + if (!gpio_is_valid(pdata->gpio_power)) + break; + + gpio_set_value(pdata->gpio_power, 0); + + /* Mark mixer regs cache dirty to sync them with + * codec regs on power on. + */ + for (reg = UDA1380_MVOL; reg < UDA1380_CACHEREGNUM; reg++) + set_bit(reg - 0x10, &uda1380_cache_dirty); + } + return 0; +} + +#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +static const struct snd_soc_dai_ops uda1380_dai_ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .trigger = uda1380_trigger, + .set_fmt = uda1380_set_dai_fmt_both, +}; + +static const struct snd_soc_dai_ops uda1380_dai_ops_playback = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .trigger = uda1380_trigger, + .set_fmt = uda1380_set_dai_fmt_playback, +}; + +static const struct snd_soc_dai_ops uda1380_dai_ops_capture = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .trigger = uda1380_trigger, + .set_fmt = uda1380_set_dai_fmt_capture, +}; + +static struct snd_soc_dai_driver uda1380_dai[] = { +{ + .name = "uda1380-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &uda1380_dai_ops, +}, +{ /* playback only - dual interface */ + .name = "uda1380-hifi-playback", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &uda1380_dai_ops_playback, +}, +{ /* capture only - dual interface*/ + .name = "uda1380-hifi-capture", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &uda1380_dai_ops_capture, +}, +}; + +static int uda1380_probe(struct snd_soc_component *component) +{ + struct uda1380_platform_data *pdata =component->dev->platform_data; + struct uda1380_priv *uda1380 = snd_soc_component_get_drvdata(component); + int ret; + + uda1380->component = component; + + if (!gpio_is_valid(pdata->gpio_power)) { + ret = uda1380_reset(component); + if (ret) + return ret; + } + + INIT_WORK(&uda1380->work, uda1380_flush_work); + + /* set clock input */ + switch (pdata->dac_clk) { + case UDA1380_DAC_CLK_SYSCLK: + uda1380_write_reg_cache(component, UDA1380_CLK, 0); + break; + case UDA1380_DAC_CLK_WSPLL: + uda1380_write_reg_cache(component, UDA1380_CLK, + R00_DAC_CLK); + break; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_uda1380 = { + .probe = uda1380_probe, + .read = uda1380_read_reg_cache, + .write = uda1380_write, + .set_bias_level = uda1380_set_bias_level, + .controls = uda1380_snd_controls, + .num_controls = ARRAY_SIZE(uda1380_snd_controls), + .dapm_widgets = uda1380_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets), + .dapm_routes = uda1380_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(uda1380_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int uda1380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct uda1380_platform_data *pdata = i2c->dev.platform_data; + struct uda1380_priv *uda1380; + int ret; + + if (!pdata) + return -EINVAL; + + uda1380 = devm_kzalloc(&i2c->dev, sizeof(struct uda1380_priv), + GFP_KERNEL); + if (uda1380 == NULL) + return -ENOMEM; + + if (gpio_is_valid(pdata->gpio_reset)) { + ret = devm_gpio_request_one(&i2c->dev, pdata->gpio_reset, + GPIOF_OUT_INIT_LOW, "uda1380 reset"); + if (ret) + return ret; + } + + if (gpio_is_valid(pdata->gpio_power)) { + ret = devm_gpio_request_one(&i2c->dev, pdata->gpio_power, + GPIOF_OUT_INIT_LOW, "uda1380 power"); + if (ret) + return ret; + } + + uda1380->reg_cache = devm_kmemdup(&i2c->dev, + uda1380_reg, + ARRAY_SIZE(uda1380_reg) * sizeof(u16), + GFP_KERNEL); + if (!uda1380->reg_cache) + return -ENOMEM; + + i2c_set_clientdata(i2c, uda1380); + uda1380->i2c = i2c; + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_uda1380, uda1380_dai, ARRAY_SIZE(uda1380_dai)); + return ret; +} + +static const struct i2c_device_id uda1380_i2c_id[] = { + { "uda1380", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); + +static const struct of_device_id uda1380_of_match[] = { + { .compatible = "nxp,uda1380", }, + { } +}; +MODULE_DEVICE_TABLE(of, uda1380_of_match); + +static struct i2c_driver uda1380_i2c_driver = { + .driver = { + .name = "uda1380-codec", + .of_match_table = uda1380_of_match, + }, + .probe = uda1380_i2c_probe, + .id_table = uda1380_i2c_id, +}; + +module_i2c_driver(uda1380_i2c_driver); + +MODULE_AUTHOR("Giorgio Padrin"); +MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h new file mode 100644 index 000000000..69a326ac3 --- /dev/null +++ b/sound/soc/codecs/uda1380.h @@ -0,0 +1,75 @@ +/* + * Audio support for Philips UDA1380 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (c) 2005 Giorgio Padrin + */ + +#ifndef _UDA1380_H +#define _UDA1380_H + +#define UDA1380_CLK 0x00 +#define UDA1380_IFACE 0x01 +#define UDA1380_PM 0x02 +#define UDA1380_AMIX 0x03 +#define UDA1380_HP 0x04 +#define UDA1380_MVOL 0x10 +#define UDA1380_MIXVOL 0x11 +#define UDA1380_MODE 0x12 +#define UDA1380_DEEMP 0x13 +#define UDA1380_MIXER 0x14 +#define UDA1380_INTSTAT 0x18 +#define UDA1380_DEC 0x20 +#define UDA1380_PGA 0x21 +#define UDA1380_ADC 0x22 +#define UDA1380_AGC 0x23 +#define UDA1380_DECSTAT 0x28 +#define UDA1380_RESET 0x7f + +#define UDA1380_CACHEREGNUM 0x24 + +/* Register flags */ +#define R00_EN_ADC 0x0800 +#define R00_EN_DEC 0x0400 +#define R00_EN_DAC 0x0200 +#define R00_EN_INT 0x0100 +#define R00_DAC_CLK 0x0010 +#define R01_SFORI_I2S 0x0000 +#define R01_SFORI_LSB16 0x0100 +#define R01_SFORI_LSB18 0x0200 +#define R01_SFORI_LSB20 0x0300 +#define R01_SFORI_MSB 0x0500 +#define R01_SFORI_MASK 0x0700 +#define R01_SFORO_I2S 0x0000 +#define R01_SFORO_LSB16 0x0001 +#define R01_SFORO_LSB18 0x0002 +#define R01_SFORO_LSB20 0x0003 +#define R01_SFORO_LSB24 0x0004 +#define R01_SFORO_MSB 0x0005 +#define R01_SFORO_MASK 0x0007 +#define R01_SEL_SOURCE 0x0040 +#define R01_SIM 0x0010 +#define R02_PON_PLL 0x8000 +#define R02_PON_HP 0x2000 +#define R02_PON_DAC 0x0400 +#define R02_PON_BIAS 0x0100 +#define R02_EN_AVC 0x0080 +#define R02_PON_AVC 0x0040 +#define R02_PON_LNA 0x0010 +#define R02_PON_PGAL 0x0008 +#define R02_PON_ADCL 0x0004 +#define R02_PON_PGAR 0x0002 +#define R02_PON_ADCR 0x0001 +#define R13_MTM 0x4000 +#define R14_SILENCE 0x0080 +#define R14_SDET_ON 0x0040 +#define R21_MT_ADC 0x8000 +#define R22_SEL_LNA 0x0008 +#define R22_SEL_MIC 0x0004 +#define R22_SKIP_DCFIL 0x0002 +#define R23_AGC_EN 0x0001 + +#endif /* _UDA1380_H */ diff --git a/sound/soc/codecs/wl1273.c b/sound/soc/codecs/wl1273.c new file mode 100644 index 000000000..929ef1fdb --- /dev/null +++ b/sound/soc/codecs/wl1273.c @@ -0,0 +1,522 @@ +/* + * ALSA SoC WL1273 codec driver + * + * Author: Matti Aaltonen, + * + * Copyright: (C) 2010, 2011 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "wl1273.h" + +enum wl1273_mode { WL1273_MODE_BT, WL1273_MODE_FM_RX, WL1273_MODE_FM_TX }; + +/* codec private data */ +struct wl1273_priv { + enum wl1273_mode mode; + struct wl1273_core *core; + unsigned int channels; +}; + +static int snd_wl1273_fm_set_i2s_mode(struct wl1273_core *core, + int rate, int width) +{ + struct device *dev = &core->client->dev; + int r = 0; + u16 mode; + + dev_dbg(dev, "rate: %d\n", rate); + dev_dbg(dev, "width: %d\n", width); + + mutex_lock(&core->lock); + + mode = core->i2s_mode & ~WL1273_IS2_WIDTH & ~WL1273_IS2_RATE; + + switch (rate) { + case 48000: + mode |= WL1273_IS2_RATE_48K; + break; + case 44100: + mode |= WL1273_IS2_RATE_44_1K; + break; + case 32000: + mode |= WL1273_IS2_RATE_32K; + break; + case 22050: + mode |= WL1273_IS2_RATE_22_05K; + break; + case 16000: + mode |= WL1273_IS2_RATE_16K; + break; + case 12000: + mode |= WL1273_IS2_RATE_12K; + break; + case 11025: + mode |= WL1273_IS2_RATE_11_025; + break; + case 8000: + mode |= WL1273_IS2_RATE_8K; + break; + default: + dev_err(dev, "Sampling rate: %d not supported\n", rate); + r = -EINVAL; + goto out; + } + + switch (width) { + case 16: + mode |= WL1273_IS2_WIDTH_32; + break; + case 20: + mode |= WL1273_IS2_WIDTH_40; + break; + case 24: + mode |= WL1273_IS2_WIDTH_48; + break; + case 25: + mode |= WL1273_IS2_WIDTH_50; + break; + case 30: + mode |= WL1273_IS2_WIDTH_60; + break; + case 32: + mode |= WL1273_IS2_WIDTH_64; + break; + case 40: + mode |= WL1273_IS2_WIDTH_80; + break; + case 48: + mode |= WL1273_IS2_WIDTH_96; + break; + case 64: + mode |= WL1273_IS2_WIDTH_128; + break; + default: + dev_err(dev, "Data width: %d not supported\n", width); + r = -EINVAL; + goto out; + } + + dev_dbg(dev, "WL1273_I2S_DEF_MODE: 0x%04x\n", WL1273_I2S_DEF_MODE); + dev_dbg(dev, "core->i2s_mode: 0x%04x\n", core->i2s_mode); + dev_dbg(dev, "mode: 0x%04x\n", mode); + + if (core->i2s_mode != mode) { + r = core->write(core, WL1273_I2S_MODE_CONFIG_SET, mode); + if (r) + goto out; + + core->i2s_mode = mode; + r = core->write(core, WL1273_AUDIO_ENABLE, + WL1273_AUDIO_ENABLE_I2S); + if (r) + goto out; + } +out: + mutex_unlock(&core->lock); + + return r; +} + +static int snd_wl1273_fm_set_channel_number(struct wl1273_core *core, + int channel_number) +{ + struct device *dev = &core->client->dev; + int r = 0; + + dev_dbg(dev, "%s\n", __func__); + + mutex_lock(&core->lock); + + if (core->channel_number == channel_number) + goto out; + + if (channel_number == 1 && core->mode == WL1273_MODE_RX) + r = core->write(core, WL1273_MOST_MODE_SET, WL1273_RX_MONO); + else if (channel_number == 1 && core->mode == WL1273_MODE_TX) + r = core->write(core, WL1273_MONO_SET, WL1273_TX_MONO); + else if (channel_number == 2 && core->mode == WL1273_MODE_RX) + r = core->write(core, WL1273_MOST_MODE_SET, WL1273_RX_STEREO); + else if (channel_number == 2 && core->mode == WL1273_MODE_TX) + r = core->write(core, WL1273_MONO_SET, WL1273_TX_STEREO); + else + r = -EINVAL; +out: + mutex_unlock(&core->lock); + + return r; +} + +static int snd_wl1273_get_audio_route(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wl1273->mode; + + return 0; +} + +/* + * TODO: Implement the audio routing in the driver. Now this control + * only indicates the setting that has been done elsewhere (in the user + * space). + */ +static const char * const wl1273_audio_route[] = { "Bt", "FmRx", "FmTx" }; + +static int snd_wl1273_set_audio_route(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + + if (wl1273->mode == ucontrol->value.enumerated.item[0]) + return 0; + + /* Do not allow changes while stream is running */ + if (snd_soc_component_is_active(component)) + return -EPERM; + + if (ucontrol->value.enumerated.item[0] >= ARRAY_SIZE(wl1273_audio_route)) + return -EINVAL; + + wl1273->mode = ucontrol->value.enumerated.item[0]; + + return 1; +} + +static SOC_ENUM_SINGLE_EXT_DECL(wl1273_enum, wl1273_audio_route); + +static int snd_wl1273_fm_audio_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s: enter.\n", __func__); + + ucontrol->value.enumerated.item[0] = wl1273->core->audio_mode; + + return 0; +} + +static int snd_wl1273_fm_audio_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + int val, r = 0; + + dev_dbg(component->dev, "%s: enter.\n", __func__); + + val = ucontrol->value.enumerated.item[0]; + if (wl1273->core->audio_mode == val) + return 0; + + r = wl1273->core->set_audio(wl1273->core, val); + if (r < 0) + return r; + + return 1; +} + +static const char * const wl1273_audio_strings[] = { "Digital", "Analog" }; + +static SOC_ENUM_SINGLE_EXT_DECL(wl1273_audio_enum, wl1273_audio_strings); + +static int snd_wl1273_fm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s: enter.\n", __func__); + + ucontrol->value.integer.value[0] = wl1273->core->volume; + + return 0; +} + +static int snd_wl1273_fm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + int r; + + dev_dbg(component->dev, "%s: enter.\n", __func__); + + r = wl1273->core->set_volume(wl1273->core, + ucontrol->value.integer.value[0]); + if (r) + return r; + + return 1; +} + +static const struct snd_kcontrol_new wl1273_controls[] = { + SOC_ENUM_EXT("Codec Mode", wl1273_enum, + snd_wl1273_get_audio_route, snd_wl1273_set_audio_route), + SOC_ENUM_EXT("Audio Switch", wl1273_audio_enum, + snd_wl1273_fm_audio_get, snd_wl1273_fm_audio_put), + SOC_SINGLE_EXT("Volume", 0, 0, WL1273_MAX_VOLUME, 0, + snd_wl1273_fm_volume_get, snd_wl1273_fm_volume_put), +}; + +static const struct snd_soc_dapm_widget wl1273_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("RX"), + + SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route wl1273_dapm_routes[] = { + { "Capture", NULL, "RX" }, + + { "TX", NULL, "Playback" }, +}; + +static int wl1273_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + + switch (wl1273->mode) { + case WL1273_MODE_BT: + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 8000); + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 1); + break; + case WL1273_MODE_FM_RX: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_err("Cannot play in RX mode.\n"); + return -EINVAL; + } + break; + case WL1273_MODE_FM_TX: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + pr_err("Cannot capture in TX mode.\n"); + return -EINVAL; + } + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +static int wl1273_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(dai->component); + struct wl1273_core *core = wl1273->core; + unsigned int rate, width, r; + + if (params_width(params) != 16) { + dev_err(dai->dev, "%d bits/sample not supported\n", + params_width(params)); + return -EINVAL; + } + + rate = params_rate(params); + width = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + + if (wl1273->mode == WL1273_MODE_BT) { + if (rate != 8000) { + pr_err("Rate %d not supported.\n", params_rate(params)); + return -EINVAL; + } + + if (params_channels(params) != 1) { + pr_err("Only mono supported.\n"); + return -EINVAL; + } + + return 0; + } + + if (wl1273->mode == WL1273_MODE_FM_TX && + substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + pr_err("Only playback supported with TX.\n"); + return -EINVAL; + } + + if (wl1273->mode == WL1273_MODE_FM_RX && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_err("Only capture supported with RX.\n"); + return -EINVAL; + } + + if (wl1273->mode != WL1273_MODE_FM_RX && + wl1273->mode != WL1273_MODE_FM_TX) { + pr_err("Unexpected mode: %d.\n", wl1273->mode); + return -EINVAL; + } + + r = snd_wl1273_fm_set_i2s_mode(core, rate, width); + if (r) + return r; + + wl1273->channels = params_channels(params); + r = snd_wl1273_fm_set_channel_number(core, wl1273->channels); + if (r) + return r; + + return 0; +} + +static const struct snd_soc_dai_ops wl1273_dai_ops = { + .startup = wl1273_startup, + .hw_params = wl1273_hw_params, +}; + +static struct snd_soc_dai_driver wl1273_dai = { + .name = "wl1273-fm", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE}, + .ops = &wl1273_dai_ops, +}; + +/* Audio interface format for the soc_card driver */ +int wl1273_get_format(struct snd_soc_component *component, unsigned int *fmt) +{ + struct wl1273_priv *wl1273; + + if (component == NULL || fmt == NULL) + return -EINVAL; + + wl1273 = snd_soc_component_get_drvdata(component); + + switch (wl1273->mode) { + case WL1273_MODE_FM_RX: + case WL1273_MODE_FM_TX: + *fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + break; + case WL1273_MODE_BT: + *fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wl1273_get_format); + +static int wl1273_probe(struct snd_soc_component *component) +{ + struct wl1273_core **core = component->dev->platform_data; + struct wl1273_priv *wl1273; + + dev_dbg(component->dev, "%s.\n", __func__); + + if (!core) { + dev_err(component->dev, "Platform data is missing.\n"); + return -EINVAL; + } + + wl1273 = kzalloc(sizeof(struct wl1273_priv), GFP_KERNEL); + if (!wl1273) + return -ENOMEM; + + wl1273->mode = WL1273_MODE_BT; + wl1273->core = *core; + + snd_soc_component_set_drvdata(component, wl1273); + + return 0; +} + +static void wl1273_remove(struct snd_soc_component *component) +{ + struct wl1273_priv *wl1273 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "%s\n", __func__); + kfree(wl1273); +} + +static const struct snd_soc_component_driver soc_component_dev_wl1273 = { + .probe = wl1273_probe, + .remove = wl1273_remove, + .controls = wl1273_controls, + .num_controls = ARRAY_SIZE(wl1273_controls), + .dapm_widgets = wl1273_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wl1273_dapm_widgets), + .dapm_routes = wl1273_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wl1273_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wl1273_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wl1273, + &wl1273_dai, 1); +} + +static int wl1273_platform_remove(struct platform_device *pdev) +{ + return 0; +} + +MODULE_ALIAS("platform:wl1273-codec"); + +static struct platform_driver wl1273_platform_driver = { + .driver = { + .name = "wl1273-codec", + }, + .probe = wl1273_platform_probe, + .remove = wl1273_platform_remove, +}; + +module_platform_driver(wl1273_platform_driver); + +MODULE_AUTHOR("Matti Aaltonen "); +MODULE_DESCRIPTION("ASoC WL1273 codec driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wl1273.h b/sound/soc/codecs/wl1273.h new file mode 100644 index 000000000..43a81d5ca --- /dev/null +++ b/sound/soc/codecs/wl1273.h @@ -0,0 +1,30 @@ +/* + * sound/soc/codec/wl1273.h + * + * ALSA SoC WL1273 codec driver + * + * Copyright (C) Nokia Corporation + * Author: Matti Aaltonen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1273_CODEC_H__ +#define __WL1273_CODEC_H__ + +int wl1273_get_format(struct snd_soc_component *component, unsigned int *fmt); + +#endif /* End of __WL1273_CODEC_H__ */ diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c new file mode 100644 index 000000000..abd2defe7 --- /dev/null +++ b/sound/soc/codecs/wm0010.c @@ -0,0 +1,1005 @@ +/* + * wm0010.c -- WM0010 DSP Driver + * + * Copyright 2012 Wolfson Microelectronics PLC. + * + * Authors: Mark Brown + * Dimitris Papastamos + * Scott Ling + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DEVICE_ID_WM0010 10 + +/* We only support v1 of the .dfw INFO record */ +#define INFO_VERSION 1 + +enum dfw_cmd { + DFW_CMD_FUSE = 0x01, + DFW_CMD_CODE_HDR, + DFW_CMD_CODE_DATA, + DFW_CMD_PLL, + DFW_CMD_INFO = 0xff +}; + +struct dfw_binrec { + u8 command; + u32 length:24; + u32 address; + uint8_t data[0]; +} __packed; + +struct dfw_inforec { + u8 info_version; + u8 tool_major_version; + u8 tool_minor_version; + u8 dsp_target; +}; + +struct dfw_pllrec { + u8 command; + u32 length:24; + u32 address; + u32 clkctrl1; + u32 clkctrl2; + u32 clkctrl3; + u32 ldetctrl; + u32 uart_div; + u32 spi_div; +} __packed; + +static struct pll_clock_map { + int max_sysclk; + int max_pll_spi_speed; + u32 pll_clkctrl1; +} pll_clock_map[] = { /* Dividers */ + { 22000000, 26000000, 0x00201f11 }, /* 2,32,2 */ + { 18000000, 26000000, 0x00203f21 }, /* 2,64,4 */ + { 14000000, 26000000, 0x00202620 }, /* 1,39,4 */ + { 10000000, 22000000, 0x00203120 }, /* 1,50,4 */ + { 6500000, 22000000, 0x00204520 }, /* 1,70,4 */ + { 5500000, 22000000, 0x00103f10 }, /* 1,64,2 */ +}; + +enum wm0010_state { + WM0010_POWER_OFF, + WM0010_OUT_OF_RESET, + WM0010_BOOTROM, + WM0010_STAGE2, + WM0010_FIRMWARE, +}; + +struct wm0010_priv { + struct snd_soc_component *component; + + struct mutex lock; + struct device *dev; + + struct wm0010_pdata pdata; + + int gpio_reset; + int gpio_reset_value; + + struct regulator_bulk_data core_supplies[2]; + struct regulator *dbvdd; + + int sysclk; + + enum wm0010_state state; + bool boot_failed; + bool ready; + bool pll_running; + int max_spi_freq; + int board_max_spi_speed; + u32 pll_clkctrl1; + + spinlock_t irq_lock; + int irq; + + struct completion boot_completion; +}; + +struct wm0010_spi_msg { + struct spi_message m; + struct spi_transfer t; + u8 *tx_buf; + u8 *rx_buf; + size_t len; +}; + +static const struct snd_soc_dapm_widget wm0010_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("CLKIN", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route wm0010_dapm_routes[] = { + { "SDI2 Capture", NULL, "SDI1 Playback" }, + { "SDI1 Capture", NULL, "SDI2 Playback" }, + + { "SDI1 Capture", NULL, "CLKIN" }, + { "SDI2 Capture", NULL, "CLKIN" }, + { "SDI1 Playback", NULL, "CLKIN" }, + { "SDI2 Playback", NULL, "CLKIN" }, +}; + +static const char *wm0010_state_to_str(enum wm0010_state state) +{ + static const char * const state_to_str[] = { + "Power off", + "Out of reset", + "Boot ROM", + "Stage2", + "Firmware" + }; + + if (state < 0 || state >= ARRAY_SIZE(state_to_str)) + return "null"; + return state_to_str[state]; +} + +/* Called with wm0010->lock held */ +static void wm0010_halt(struct snd_soc_component *component) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + unsigned long flags; + enum wm0010_state state; + + /* Fetch the wm0010 state */ + spin_lock_irqsave(&wm0010->irq_lock, flags); + state = wm0010->state; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + switch (state) { + case WM0010_POWER_OFF: + /* If there's nothing to do, bail out */ + return; + case WM0010_OUT_OF_RESET: + case WM0010_BOOTROM: + case WM0010_STAGE2: + case WM0010_FIRMWARE: + /* Remember to put chip back into reset */ + gpio_set_value_cansleep(wm0010->gpio_reset, + wm0010->gpio_reset_value); + /* Disable the regulators */ + regulator_disable(wm0010->dbvdd); + regulator_bulk_disable(ARRAY_SIZE(wm0010->core_supplies), + wm0010->core_supplies); + break; + } + + spin_lock_irqsave(&wm0010->irq_lock, flags); + wm0010->state = WM0010_POWER_OFF; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); +} + +struct wm0010_boot_xfer { + struct list_head list; + struct snd_soc_component *component; + struct completion *done; + struct spi_message m; + struct spi_transfer t; +}; + +/* Called with wm0010->lock held */ +static void wm0010_mark_boot_failure(struct wm0010_priv *wm0010) +{ + enum wm0010_state state; + unsigned long flags; + + spin_lock_irqsave(&wm0010->irq_lock, flags); + state = wm0010->state; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + dev_err(wm0010->dev, "Failed to transition from `%s' state to `%s' state\n", + wm0010_state_to_str(state), wm0010_state_to_str(state + 1)); + + wm0010->boot_failed = true; +} + +static void wm0010_boot_xfer_complete(void *data) +{ + struct wm0010_boot_xfer *xfer = data; + struct snd_soc_component *component = xfer->component; + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + u32 *out32 = xfer->t.rx_buf; + int i; + + if (xfer->m.status != 0) { + dev_err(component->dev, "SPI transfer failed: %d\n", + xfer->m.status); + wm0010_mark_boot_failure(wm0010); + if (xfer->done) + complete(xfer->done); + return; + } + + for (i = 0; i < xfer->t.len / 4; i++) { + dev_dbg(component->dev, "%d: %04x\n", i, out32[i]); + + switch (be32_to_cpu(out32[i])) { + case 0xe0e0e0e0: + dev_err(component->dev, + "%d: ROM error reported in stage 2\n", i); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x55555555: + if (wm0010->state < WM0010_STAGE2) + break; + dev_err(component->dev, + "%d: ROM bootloader running in stage 2\n", i); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0000: + dev_dbg(component->dev, "Stage2 loader running\n"); + break; + + case 0x0fed0007: + dev_dbg(component->dev, "CODE_HDR packet received\n"); + break; + + case 0x0fed0008: + dev_dbg(component->dev, "CODE_DATA packet received\n"); + break; + + case 0x0fed0009: + dev_dbg(component->dev, "Download complete\n"); + break; + + case 0x0fed000c: + dev_dbg(component->dev, "Application start\n"); + break; + + case 0x0fed000e: + dev_dbg(component->dev, "PLL packet received\n"); + wm0010->pll_running = true; + break; + + case 0x0fed0025: + dev_err(component->dev, "Device reports image too long\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed002c: + dev_err(component->dev, "Device reports bad SPI packet\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0031: + dev_err(component->dev, "Device reports SPI read overflow\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0032: + dev_err(component->dev, "Device reports SPI underclock\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0033: + dev_err(component->dev, "Device reports bad header packet\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0034: + dev_err(component->dev, "Device reports invalid packet type\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0035: + dev_err(component->dev, "Device reports data before header error\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0038: + dev_err(component->dev, "Device reports invalid PLL packet\n"); + break; + + case 0x0fed003a: + dev_err(component->dev, "Device reports packet alignment error\n"); + wm0010_mark_boot_failure(wm0010); + break; + + default: + dev_err(component->dev, "Unrecognised return 0x%x\n", + be32_to_cpu(out32[i])); + wm0010_mark_boot_failure(wm0010); + break; + } + + if (wm0010->boot_failed) + break; + } + + if (xfer->done) + complete(xfer->done); +} + +static void byte_swap_64(u64 *data_in, u64 *data_out, u32 len) +{ + int i; + + for (i = 0; i < len / 8; i++) + data_out[i] = cpu_to_be64(le64_to_cpu(data_in[i])); +} + +static int wm0010_firmware_load(const char *name, struct snd_soc_component *component) +{ + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + struct list_head xfer_list; + struct wm0010_boot_xfer *xfer; + int ret; + struct completion done; + const struct firmware *fw; + const struct dfw_binrec *rec; + const struct dfw_inforec *inforec; + u64 *img; + u8 *out, dsp; + u32 len, offset; + + INIT_LIST_HEAD(&xfer_list); + + ret = request_firmware(&fw, name, component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request application(%s): %d\n", + name, ret); + return ret; + } + + rec = (const struct dfw_binrec *)fw->data; + inforec = (const struct dfw_inforec *)rec->data; + offset = 0; + dsp = inforec->dsp_target; + wm0010->boot_failed = false; + if (WARN_ON(!list_empty(&xfer_list))) + return -EINVAL; + init_completion(&done); + + /* First record should be INFO */ + if (rec->command != DFW_CMD_INFO) { + dev_err(component->dev, "First record not INFO\r\n"); + ret = -EINVAL; + goto abort; + } + + if (inforec->info_version != INFO_VERSION) { + dev_err(component->dev, + "Unsupported version (%02d) of INFO record\r\n", + inforec->info_version); + ret = -EINVAL; + goto abort; + } + + dev_dbg(component->dev, "Version v%02d INFO record found\r\n", + inforec->info_version); + + /* Check it's a DSP file */ + if (dsp != DEVICE_ID_WM0010) { + dev_err(component->dev, "Not a WM0010 firmware file.\r\n"); + ret = -EINVAL; + goto abort; + } + + /* Skip the info record as we don't need to send it */ + offset += ((rec->length) + 8); + rec = (void *)&rec->data[rec->length]; + + while (offset < fw->size) { + dev_dbg(component->dev, + "Packet: command %d, data length = 0x%x\r\n", + rec->command, rec->length); + len = rec->length + 8; + + xfer = kzalloc(sizeof(*xfer), GFP_KERNEL); + if (!xfer) { + ret = -ENOMEM; + goto abort; + } + + xfer->component = component; + list_add_tail(&xfer->list, &xfer_list); + + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; + goto abort1; + } + xfer->t.rx_buf = out; + + img = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; + goto abort1; + } + xfer->t.tx_buf = img; + + byte_swap_64((u64 *)&rec->command, img, len); + + spi_message_init(&xfer->m); + xfer->m.complete = wm0010_boot_xfer_complete; + xfer->m.context = xfer; + xfer->t.len = len; + xfer->t.bits_per_word = 8; + + if (!wm0010->pll_running) { + xfer->t.speed_hz = wm0010->sysclk / 6; + } else { + xfer->t.speed_hz = wm0010->max_spi_freq; + + if (wm0010->board_max_spi_speed && + (wm0010->board_max_spi_speed < wm0010->max_spi_freq)) + xfer->t.speed_hz = wm0010->board_max_spi_speed; + } + + /* Store max usable spi frequency for later use */ + wm0010->max_spi_freq = xfer->t.speed_hz; + + spi_message_add_tail(&xfer->t, &xfer->m); + + offset += ((rec->length) + 8); + rec = (void *)&rec->data[rec->length]; + + if (offset >= fw->size) { + dev_dbg(component->dev, "All transfers scheduled\n"); + xfer->done = &done; + } + + ret = spi_async(spi, &xfer->m); + if (ret != 0) { + dev_err(component->dev, "Write failed: %d\n", ret); + goto abort1; + } + + if (wm0010->boot_failed) { + dev_dbg(component->dev, "Boot fail!\n"); + ret = -EINVAL; + goto abort1; + } + } + + wait_for_completion(&done); + + ret = 0; + +abort1: + while (!list_empty(&xfer_list)) { + xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer, + list); + kfree(xfer->t.rx_buf); + kfree(xfer->t.tx_buf); + list_del(&xfer->list); + kfree(xfer); + } + +abort: + release_firmware(fw); + return ret; +} + +static int wm0010_stage2_load(struct snd_soc_component *component) +{ + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + const struct firmware *fw; + struct spi_message m; + struct spi_transfer t; + u32 *img; + u8 *out; + int i; + int ret = 0; + + ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request stage2 loader: %d\n", + ret); + return ret; + } + + dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); + + /* Copy to local buffer first as vmalloc causes problems for dma */ + img = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; + goto abort2; + } + + out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; + goto abort1; + } + + memcpy(img, &fw->data[0], fw->size); + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + t.rx_buf = out; + t.tx_buf = img; + t.len = fw->size; + t.bits_per_word = 8; + t.speed_hz = wm0010->sysclk / 10; + spi_message_add_tail(&t, &m); + + dev_dbg(component->dev, "Starting initial download at %dHz\n", + t.speed_hz); + + ret = spi_sync(spi, &m); + if (ret != 0) { + dev_err(component->dev, "Initial download failed: %d\n", ret); + goto abort; + } + + /* Look for errors from the boot ROM */ + for (i = 0; i < fw->size; i++) { + if (out[i] != 0x55) { + dev_err(component->dev, "Boot ROM error: %x in %d\n", + out[i], i); + wm0010_mark_boot_failure(wm0010); + ret = -EBUSY; + goto abort; + } + } +abort: + kfree(out); +abort1: + kfree(img); +abort2: + release_firmware(fw); + + return ret; +} + +static int wm0010_boot(struct snd_soc_component *component) +{ + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + unsigned long flags; + int ret; + struct spi_message m; + struct spi_transfer t; + struct dfw_pllrec pll_rec; + u32 *p, len; + u64 *img_swap; + u8 *out; + int i; + + spin_lock_irqsave(&wm0010->irq_lock, flags); + if (wm0010->state != WM0010_POWER_OFF) + dev_warn(wm0010->dev, "DSP already powered up!\n"); + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + if (wm0010->sysclk > 26000000) { + dev_err(component->dev, "Max DSP clock frequency is 26MHz\n"); + ret = -ECANCELED; + goto err; + } + + mutex_lock(&wm0010->lock); + wm0010->pll_running = false; + + dev_dbg(component->dev, "max_spi_freq: %d\n", wm0010->max_spi_freq); + + ret = regulator_bulk_enable(ARRAY_SIZE(wm0010->core_supplies), + wm0010->core_supplies); + if (ret != 0) { + dev_err(&spi->dev, "Failed to enable core supplies: %d\n", + ret); + mutex_unlock(&wm0010->lock); + goto err; + } + + ret = regulator_enable(wm0010->dbvdd); + if (ret != 0) { + dev_err(&spi->dev, "Failed to enable DBVDD: %d\n", ret); + goto err_core; + } + + /* Release reset */ + gpio_set_value_cansleep(wm0010->gpio_reset, !wm0010->gpio_reset_value); + spin_lock_irqsave(&wm0010->irq_lock, flags); + wm0010->state = WM0010_OUT_OF_RESET; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + if (!wait_for_completion_timeout(&wm0010->boot_completion, + msecs_to_jiffies(20))) + dev_err(component->dev, "Failed to get interrupt from DSP\n"); + + spin_lock_irqsave(&wm0010->irq_lock, flags); + wm0010->state = WM0010_BOOTROM; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + ret = wm0010_stage2_load(component); + if (ret) + goto abort; + + if (!wait_for_completion_timeout(&wm0010->boot_completion, + msecs_to_jiffies(20))) + dev_err(component->dev, "Failed to get interrupt from DSP loader.\n"); + + spin_lock_irqsave(&wm0010->irq_lock, flags); + wm0010->state = WM0010_STAGE2; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + /* Only initialise PLL if max_spi_freq initialised */ + if (wm0010->max_spi_freq) { + + /* Initialise a PLL record */ + memset(&pll_rec, 0, sizeof(pll_rec)); + pll_rec.command = DFW_CMD_PLL; + pll_rec.length = (sizeof(pll_rec) - 8); + + /* On wm0010 only the CLKCTRL1 value is used */ + pll_rec.clkctrl1 = wm0010->pll_clkctrl1; + + ret = -ENOMEM; + len = pll_rec.length + 8; + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) + goto abort; + + img_swap = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img_swap) + goto abort_out; + + /* We need to re-order for 0010 */ + byte_swap_64((u64 *)&pll_rec, img_swap, len); + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + t.rx_buf = out; + t.tx_buf = img_swap; + t.len = len; + t.bits_per_word = 8; + t.speed_hz = wm0010->sysclk / 6; + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + if (ret) { + dev_err(component->dev, "First PLL write failed: %d\n", ret); + goto abort_swap; + } + + /* Use a second send of the message to get the return status */ + ret = spi_sync(spi, &m); + if (ret) { + dev_err(component->dev, "Second PLL write failed: %d\n", ret); + goto abort_swap; + } + + p = (u32 *)out; + + /* Look for PLL active code from the DSP */ + for (i = 0; i < len / 4; i++) { + if (*p == 0x0e00ed0f) { + dev_dbg(component->dev, "PLL packet received\n"); + wm0010->pll_running = true; + break; + } + p++; + } + + kfree(img_swap); + kfree(out); + } else + dev_dbg(component->dev, "Not enabling DSP PLL."); + + ret = wm0010_firmware_load("wm0010.dfw", component); + + if (ret != 0) + goto abort; + + spin_lock_irqsave(&wm0010->irq_lock, flags); + wm0010->state = WM0010_FIRMWARE; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + mutex_unlock(&wm0010->lock); + + return 0; + +abort_swap: + kfree(img_swap); +abort_out: + kfree(out); +abort: + /* Put the chip back into reset */ + wm0010_halt(component); + mutex_unlock(&wm0010->lock); + return ret; + +err_core: + mutex_unlock(&wm0010->lock); + regulator_bulk_disable(ARRAY_SIZE(wm0010->core_supplies), + wm0010->core_supplies); +err: + return ret; +} + +static int wm0010_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_PREPARE) + wm0010_boot(component); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_PREPARE) { + mutex_lock(&wm0010->lock); + wm0010_halt(component); + mutex_unlock(&wm0010->lock); + } + break; + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static int wm0010_set_sysclk(struct snd_soc_component *component, int source, + int clk_id, unsigned int freq, int dir) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + unsigned int i; + + wm0010->sysclk = freq; + + if (freq < pll_clock_map[ARRAY_SIZE(pll_clock_map)-1].max_sysclk) { + wm0010->max_spi_freq = 0; + } else { + for (i = 0; i < ARRAY_SIZE(pll_clock_map); i++) + if (freq >= pll_clock_map[i].max_sysclk) { + wm0010->max_spi_freq = pll_clock_map[i].max_pll_spi_speed; + wm0010->pll_clkctrl1 = pll_clock_map[i].pll_clkctrl1; + break; + } + } + + return 0; +} + +static int wm0010_probe(struct snd_soc_component *component); + +static const struct snd_soc_component_driver soc_component_dev_wm0010 = { + .probe = wm0010_probe, + .set_bias_level = wm0010_set_bias_level, + .set_sysclk = wm0010_set_sysclk, + .dapm_widgets = wm0010_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm0010_dapm_widgets), + .dapm_routes = wm0010_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm0010_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +#define WM0010_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define WM0010_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm0010_dai[] = { + { + .name = "wm0010-sdi1", + .playback = { + .stream_name = "SDI1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM0010_RATES, + .formats = WM0010_FORMATS, + }, + .capture = { + .stream_name = "SDI1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM0010_RATES, + .formats = WM0010_FORMATS, + }, + }, + { + .name = "wm0010-sdi2", + .playback = { + .stream_name = "SDI2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM0010_RATES, + .formats = WM0010_FORMATS, + }, + .capture = { + .stream_name = "SDI2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM0010_RATES, + .formats = WM0010_FORMATS, + }, + }, +}; + +static irqreturn_t wm0010_irq(int irq, void *data) +{ + struct wm0010_priv *wm0010 = data; + + switch (wm0010->state) { + case WM0010_OUT_OF_RESET: + case WM0010_BOOTROM: + case WM0010_STAGE2: + spin_lock(&wm0010->irq_lock); + complete(&wm0010->boot_completion); + spin_unlock(&wm0010->irq_lock); + return IRQ_HANDLED; + default: + return IRQ_NONE; + } + + return IRQ_NONE; +} + +static int wm0010_probe(struct snd_soc_component *component) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + + wm0010->component = component; + + return 0; +} + +static int wm0010_spi_probe(struct spi_device *spi) +{ + unsigned long gpio_flags; + int ret; + int trigger; + int irq; + struct wm0010_priv *wm0010; + + wm0010 = devm_kzalloc(&spi->dev, sizeof(*wm0010), + GFP_KERNEL); + if (!wm0010) + return -ENOMEM; + + mutex_init(&wm0010->lock); + spin_lock_init(&wm0010->irq_lock); + + spi_set_drvdata(spi, wm0010); + wm0010->dev = &spi->dev; + + if (dev_get_platdata(&spi->dev)) + memcpy(&wm0010->pdata, dev_get_platdata(&spi->dev), + sizeof(wm0010->pdata)); + + init_completion(&wm0010->boot_completion); + + wm0010->core_supplies[0].supply = "AVDD"; + wm0010->core_supplies[1].supply = "DCVDD"; + ret = devm_regulator_bulk_get(wm0010->dev, ARRAY_SIZE(wm0010->core_supplies), + wm0010->core_supplies); + if (ret != 0) { + dev_err(wm0010->dev, "Failed to obtain core supplies: %d\n", + ret); + return ret; + } + + wm0010->dbvdd = devm_regulator_get(wm0010->dev, "DBVDD"); + if (IS_ERR(wm0010->dbvdd)) { + ret = PTR_ERR(wm0010->dbvdd); + dev_err(wm0010->dev, "Failed to obtain DBVDD: %d\n", ret); + return ret; + } + + if (wm0010->pdata.gpio_reset) { + wm0010->gpio_reset = wm0010->pdata.gpio_reset; + + if (wm0010->pdata.reset_active_high) + wm0010->gpio_reset_value = 1; + else + wm0010->gpio_reset_value = 0; + + if (wm0010->gpio_reset_value) + gpio_flags = GPIOF_OUT_INIT_HIGH; + else + gpio_flags = GPIOF_OUT_INIT_LOW; + + ret = devm_gpio_request_one(wm0010->dev, wm0010->gpio_reset, + gpio_flags, "wm0010 reset"); + if (ret < 0) { + dev_err(wm0010->dev, + "Failed to request GPIO for DSP reset: %d\n", + ret); + return ret; + } + } else { + dev_err(wm0010->dev, "No reset GPIO configured\n"); + return -EINVAL; + } + + wm0010->state = WM0010_POWER_OFF; + + irq = spi->irq; + if (wm0010->pdata.irq_flags) + trigger = wm0010->pdata.irq_flags; + else + trigger = IRQF_TRIGGER_FALLING; + trigger |= IRQF_ONESHOT; + + ret = request_threaded_irq(irq, NULL, wm0010_irq, trigger, + "wm0010", wm0010); + if (ret) { + dev_err(wm0010->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + return ret; + } + wm0010->irq = irq; + + ret = irq_set_irq_wake(irq, 1); + if (ret) { + dev_err(wm0010->dev, "Failed to set IRQ %d as wake source: %d\n", + irq, ret); + return ret; + } + + if (spi->max_speed_hz) + wm0010->board_max_spi_speed = spi->max_speed_hz; + else + wm0010->board_max_spi_speed = 0; + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm0010, wm0010_dai, + ARRAY_SIZE(wm0010_dai)); + if (ret < 0) + return ret; + + return 0; +} + +static int wm0010_spi_remove(struct spi_device *spi) +{ + struct wm0010_priv *wm0010 = spi_get_drvdata(spi); + + gpio_set_value_cansleep(wm0010->gpio_reset, + wm0010->gpio_reset_value); + + irq_set_irq_wake(wm0010->irq, 0); + + if (wm0010->irq) + free_irq(wm0010->irq, wm0010); + + return 0; +} + +static struct spi_driver wm0010_spi_driver = { + .driver = { + .name = "wm0010", + }, + .probe = wm0010_spi_probe, + .remove = wm0010_spi_remove, +}; + +module_spi_driver(wm0010_spi_driver); + +MODULE_DESCRIPTION("ASoC WM0010 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm1250-ev1.c b/sound/soc/codecs/wm1250-ev1.c new file mode 100644 index 000000000..9727eec6d --- /dev/null +++ b/sound/soc/codecs/wm1250-ev1.c @@ -0,0 +1,264 @@ +/* + * Driver for the 1250-EV1 audio I/O module + * + * Copyright 2011 Wolfson Microelectronics plc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static const char *wm1250_gpio_names[WM1250_EV1_NUM_GPIOS] = { + "WM1250 CLK_ENA", + "WM1250 CLK_SEL0", + "WM1250 CLK_SEL1", + "WM1250 OSR", + "WM1250 MASTER", +}; + +struct wm1250_priv { + struct gpio gpios[WM1250_EV1_NUM_GPIOS]; +}; + +static int wm1250_ev1_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm1250_priv *wm1250 = dev_get_drvdata(component->dev); + int ena; + + if (wm1250) + ena = wm1250->gpios[WM1250_EV1_GPIO_CLK_ENA].gpio; + else + ena = -1; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (ena >= 0) + gpio_set_value_cansleep(ena, 1); + break; + + case SND_SOC_BIAS_OFF: + if (ena >= 0) + gpio_set_value_cansleep(ena, 0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget wm1250_ev1_dapm_widgets[] = { +SND_SOC_DAPM_ADC("ADC", "wm1250-ev1 Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_DAC("DAC", "wm1250-ev1 Playback", SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_INPUT("WM1250 Input"), +SND_SOC_DAPM_OUTPUT("WM1250 Output"), +}; + +static const struct snd_soc_dapm_route wm1250_ev1_dapm_routes[] = { + { "ADC", NULL, "WM1250 Input" }, + { "WM1250 Output", NULL, "DAC" }, +}; + +static int wm1250_ev1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct wm1250_priv *wm1250 = snd_soc_component_get_drvdata(dai->component); + + switch (params_rate(params)) { + case 8000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 1); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 1); + break; + case 16000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 0); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 1); + break; + case 32000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 1); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 0); + break; + case 64000: + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].gpio, + 0); + gpio_set_value(wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].gpio, + 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops wm1250_ev1_ops = { + .hw_params = wm1250_ev1_hw_params, +}; + +#define WM1250_EV1_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_64000) + +static struct snd_soc_dai_driver wm1250_ev1_dai = { + .name = "wm1250-ev1", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM1250_EV1_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM1250_EV1_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &wm1250_ev1_ops, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm1250_ev1 = { + .dapm_widgets = wm1250_ev1_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm1250_ev1_dapm_widgets), + .dapm_routes = wm1250_ev1_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm1250_ev1_dapm_routes), + .set_bias_level = wm1250_ev1_set_bias_level, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm1250_ev1_pdata(struct i2c_client *i2c) +{ + struct wm1250_ev1_pdata *pdata = dev_get_platdata(&i2c->dev); + struct wm1250_priv *wm1250; + int i, ret; + + if (!pdata) + return 0; + + wm1250 = devm_kzalloc(&i2c->dev, sizeof(*wm1250), GFP_KERNEL); + if (!wm1250) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < ARRAY_SIZE(wm1250->gpios); i++) { + wm1250->gpios[i].gpio = pdata->gpios[i]; + wm1250->gpios[i].label = wm1250_gpio_names[i]; + wm1250->gpios[i].flags = GPIOF_OUT_INIT_LOW; + } + wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL0].flags = GPIOF_OUT_INIT_HIGH; + wm1250->gpios[WM1250_EV1_GPIO_CLK_SEL1].flags = GPIOF_OUT_INIT_HIGH; + + ret = gpio_request_array(wm1250->gpios, ARRAY_SIZE(wm1250->gpios)); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to get GPIOs: %d\n", ret); + goto err; + } + + dev_set_drvdata(&i2c->dev, wm1250); + + return ret; + +err: + return ret; +} + +static void wm1250_ev1_free(struct i2c_client *i2c) +{ + struct wm1250_priv *wm1250 = dev_get_drvdata(&i2c->dev); + + if (wm1250) + gpio_free_array(wm1250->gpios, ARRAY_SIZE(wm1250->gpios)); +} + +static int wm1250_ev1_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + int id, board, rev, ret; + + dev_set_drvdata(&i2c->dev, NULL); + + board = i2c_smbus_read_byte_data(i2c, 0); + if (board < 0) { + dev_err(&i2c->dev, "Failed to read ID: %d\n", board); + return board; + } + + id = (board & 0xfe) >> 2; + rev = board & 0x3; + + if (id != 1) { + dev_err(&i2c->dev, "Unknown board ID %d\n", id); + return -ENODEV; + } + + dev_info(&i2c->dev, "revision %d\n", rev + 1); + + ret = wm1250_ev1_pdata(i2c); + if (ret != 0) + return ret; + + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm1250_ev1, + &wm1250_ev1_dai, 1); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + wm1250_ev1_free(i2c); + return ret; + } + + return 0; +} + +static int wm1250_ev1_remove(struct i2c_client *i2c) +{ + wm1250_ev1_free(i2c); + + return 0; +} + +static const struct i2c_device_id wm1250_ev1_i2c_id[] = { + { "wm1250-ev1", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm1250_ev1_i2c_id); + +static struct i2c_driver wm1250_ev1_i2c_driver = { + .driver = { + .name = "wm1250-ev1", + }, + .probe = wm1250_ev1_probe, + .remove = wm1250_ev1_remove, + .id_table = wm1250_ev1_i2c_id, +}; + +module_i2c_driver(wm1250_ev1_i2c_driver); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM1250-EV1 audio I/O module driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c new file mode 100644 index 000000000..cad39f63b --- /dev/null +++ b/sound/soc/codecs/wm2000.c @@ -0,0 +1,949 @@ +/* + * wm2000.c -- WM2000 ALSA Soc Audio driver + * + * Copyright 2008-2011 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The download image for the WM2000 will be requested as + * 'wm2000_anc.bin' by default (overridable via platform data) at + * runtime and is expected to be in flat binary format. This is + * generated by Wolfson configuration tools and includes + * system-specific calibration information. If supplied as a + * sequence of ASCII-encoded hexidecimal bytes this can be converted + * into a flat binary with a command such as this on the command line: + * + * perl -e 'while (<>) { s/[\r\n]+// ; printf("%c", hex($_)); }' + * < file > wm2000_anc.bin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wm2000.h" + +#define WM2000_NUM_SUPPLIES 3 + +static const char *wm2000_supplies[WM2000_NUM_SUPPLIES] = { + "SPKVDD", + "DBVDD", + "DCVDD", +}; + +enum wm2000_anc_mode { + ANC_ACTIVE = 0, + ANC_BYPASS = 1, + ANC_STANDBY = 2, + ANC_OFF = 3, +}; + +struct wm2000_priv { + struct i2c_client *i2c; + struct regmap *regmap; + struct clk *mclk; + + struct regulator_bulk_data supplies[WM2000_NUM_SUPPLIES]; + + enum wm2000_anc_mode anc_mode; + + unsigned int anc_active:1; + unsigned int anc_eng_ena:1; + unsigned int spk_ena:1; + + unsigned int speech_clarity:1; + + int anc_download_size; + char *anc_download; + + struct mutex lock; +}; + +static int wm2000_write(struct i2c_client *i2c, unsigned int reg, + unsigned int value) +{ + struct wm2000_priv *wm2000 = i2c_get_clientdata(i2c); + return regmap_write(wm2000->regmap, reg, value); +} + +static unsigned int wm2000_read(struct i2c_client *i2c, unsigned int r) +{ + struct wm2000_priv *wm2000 = i2c_get_clientdata(i2c); + unsigned int val; + int ret; + + ret = regmap_read(wm2000->regmap, r, &val); + if (ret < 0) + return -1; + + return val; +} + +static void wm2000_reset(struct wm2000_priv *wm2000) +{ + struct i2c_client *i2c = wm2000->i2c; + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR); + wm2000_write(i2c, WM2000_REG_ID1, 0); + + wm2000->anc_mode = ANC_OFF; +} + +static int wm2000_poll_bit(struct i2c_client *i2c, + unsigned int reg, u8 mask) +{ + int timeout = 4000; + int val; + + val = wm2000_read(i2c, reg); + + while (!(val & mask) && --timeout) { + msleep(1); + val = wm2000_read(i2c, reg); + } + + if (timeout == 0) + return 0; + else + return 1; +} + +static int wm2000_power_up(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + unsigned long rate; + int ret; + + if (WARN_ON(wm2000->anc_mode != ANC_OFF)) + return -EINVAL; + + dev_dbg(&i2c->dev, "Beginning power up\n"); + + ret = regulator_bulk_enable(WM2000_NUM_SUPPLIES, wm2000->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + rate = clk_get_rate(wm2000->mclk); + if (rate <= 13500000) { + dev_dbg(&i2c->dev, "Disabling MCLK divider\n"); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, + WM2000_MCLK_DIV2_ENA_CLR); + } else { + dev_dbg(&i2c->dev, "Enabling MCLK divider\n"); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, + WM2000_MCLK_DIV2_ENA_SET); + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_SET); + + /* Wait for ANC engine to become ready */ + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, + WM2000_ANC_ENG_IDLE)) { + dev_err(&i2c->dev, "ANC engine failed to reset\n"); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_BOOT_COMPLETE)) { + dev_err(&i2c->dev, "ANC engine failed to initialise\n"); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + return -ETIMEDOUT; + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET); + + /* Open code download of the data since it is the only bulk + * write we do. */ + dev_dbg(&i2c->dev, "Downloading %d bytes\n", + wm2000->anc_download_size - 2); + + ret = i2c_master_send(i2c, wm2000->anc_download, + wm2000->anc_download_size); + if (ret < 0) { + dev_err(&i2c->dev, "i2c_transfer() failed: %d\n", ret); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + return ret; + } + if (ret != wm2000->anc_download_size) { + dev_err(&i2c->dev, "i2c_transfer() failed, %d != %d\n", + ret, wm2000->anc_download_size); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + return -EIO; + } + + dev_dbg(&i2c->dev, "Download complete\n"); + + if (analogue) { + wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, 248 / 4); + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } + + ret = wm2000_read(i2c, WM2000_REG_SPEECH_CLARITY); + if (wm2000->speech_clarity) + ret |= WM2000_SPEECH_CLARITY; + else + ret &= ~WM2000_SPEECH_CLARITY; + wm2000_write(i2c, WM2000_REG_SPEECH_CLARITY, ret); + + wm2000_write(i2c, WM2000_REG_SYS_START0, 0x33); + wm2000_write(i2c, WM2000_REG_SYS_START1, 0x02); + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR); + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_MOUSE_ACTIVE)) { + dev_err(&i2c->dev, "Timed out waiting for device\n"); + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + return -ETIMEDOUT; + } + + dev_dbg(&i2c->dev, "ANC active\n"); + if (analogue) + dev_dbg(&i2c->dev, "Analogue active\n"); + wm2000->anc_mode = ANC_ACTIVE; + + return 0; +} + +static int wm2000_power_down(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + if (analogue) { + wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, 248 / 4); + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_POWER_DOWN); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_POWER_DOWN); + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_POWER_DOWN_COMPLETE)) { + dev_err(&i2c->dev, "Timeout waiting for ANC power down\n"); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, + WM2000_ANC_ENG_IDLE)) { + dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n"); + return -ETIMEDOUT; + } + + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + + dev_dbg(&i2c->dev, "powered off\n"); + wm2000->anc_mode = ANC_OFF; + + return 0; +} + +static int wm2000_enter_bypass(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + if (WARN_ON(wm2000->anc_mode != ANC_ACTIVE)) + return -EINVAL; + + if (analogue) { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_BYPASS_ENTRY); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_BYPASS_ENTRY); + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_ANC_DISABLED)) { + dev_err(&i2c->dev, "Timeout waiting for ANC disable\n"); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, + WM2000_ANC_ENG_IDLE)) { + dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n"); + return -ETIMEDOUT; + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR); + + wm2000->anc_mode = ANC_BYPASS; + dev_dbg(&i2c->dev, "bypass enabled\n"); + + return 0; +} + +static int wm2000_exit_bypass(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + if (WARN_ON(wm2000->anc_mode != ANC_BYPASS)) + return -EINVAL; + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0); + + if (analogue) { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR); + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_MOUSE_ACTIVE)) { + dev_err(&i2c->dev, "Timed out waiting for MOUSE\n"); + return -ETIMEDOUT; + } + + wm2000->anc_mode = ANC_ACTIVE; + dev_dbg(&i2c->dev, "MOUSE active\n"); + + return 0; +} + +static int wm2000_enter_standby(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + if (WARN_ON(wm2000->anc_mode != ANC_ACTIVE)) + return -EINVAL; + + if (analogue) { + wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, 248 / 4); + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_STANDBY_ENTRY); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_STANDBY_ENTRY); + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_ANC_DISABLED)) { + dev_err(&i2c->dev, + "Timed out waiting for ANC disable after 1ms\n"); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, WM2000_ANC_ENG_IDLE)) { + dev_err(&i2c->dev, + "Timed out waiting for standby\n"); + return -ETIMEDOUT; + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR); + + wm2000->anc_mode = ANC_STANDBY; + dev_dbg(&i2c->dev, "standby\n"); + if (analogue) + dev_dbg(&i2c->dev, "Analogue disabled\n"); + + return 0; +} + +static int wm2000_exit_standby(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + if (WARN_ON(wm2000->anc_mode != ANC_STANDBY)) + return -EINVAL; + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0); + + if (analogue) { + wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, 248 / 4); + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_MOUSE_ENABLE); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_MOUSE_ENABLE); + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR); + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_MOUSE_ACTIVE)) { + dev_err(&i2c->dev, "Timed out waiting for MOUSE\n"); + return -ETIMEDOUT; + } + + wm2000->anc_mode = ANC_ACTIVE; + dev_dbg(&i2c->dev, "MOUSE active\n"); + if (analogue) + dev_dbg(&i2c->dev, "Analogue enabled\n"); + + return 0; +} + +typedef int (*wm2000_mode_fn)(struct i2c_client *i2c, int analogue); + +static struct { + enum wm2000_anc_mode source; + enum wm2000_anc_mode dest; + int analogue; + wm2000_mode_fn step[2]; +} anc_transitions[] = { + { + .source = ANC_OFF, + .dest = ANC_ACTIVE, + .analogue = 1, + .step = { + wm2000_power_up, + }, + }, + { + .source = ANC_OFF, + .dest = ANC_STANDBY, + .step = { + wm2000_power_up, + wm2000_enter_standby, + }, + }, + { + .source = ANC_OFF, + .dest = ANC_BYPASS, + .analogue = 1, + .step = { + wm2000_power_up, + wm2000_enter_bypass, + }, + }, + { + .source = ANC_ACTIVE, + .dest = ANC_BYPASS, + .analogue = 1, + .step = { + wm2000_enter_bypass, + }, + }, + { + .source = ANC_ACTIVE, + .dest = ANC_STANDBY, + .analogue = 1, + .step = { + wm2000_enter_standby, + }, + }, + { + .source = ANC_ACTIVE, + .dest = ANC_OFF, + .analogue = 1, + .step = { + wm2000_power_down, + }, + }, + { + .source = ANC_BYPASS, + .dest = ANC_ACTIVE, + .analogue = 1, + .step = { + wm2000_exit_bypass, + }, + }, + { + .source = ANC_BYPASS, + .dest = ANC_STANDBY, + .analogue = 1, + .step = { + wm2000_exit_bypass, + wm2000_enter_standby, + }, + }, + { + .source = ANC_BYPASS, + .dest = ANC_OFF, + .step = { + wm2000_exit_bypass, + wm2000_power_down, + }, + }, + { + .source = ANC_STANDBY, + .dest = ANC_ACTIVE, + .analogue = 1, + .step = { + wm2000_exit_standby, + }, + }, + { + .source = ANC_STANDBY, + .dest = ANC_BYPASS, + .analogue = 1, + .step = { + wm2000_exit_standby, + wm2000_enter_bypass, + }, + }, + { + .source = ANC_STANDBY, + .dest = ANC_OFF, + .step = { + wm2000_exit_standby, + wm2000_power_down, + }, + }, +}; + +static int wm2000_anc_transition(struct wm2000_priv *wm2000, + enum wm2000_anc_mode mode) +{ + struct i2c_client *i2c = wm2000->i2c; + int i, j; + int ret = 0; + + if (wm2000->anc_mode == mode) + return 0; + + for (i = 0; i < ARRAY_SIZE(anc_transitions); i++) + if (anc_transitions[i].source == wm2000->anc_mode && + anc_transitions[i].dest == mode) + break; + if (i == ARRAY_SIZE(anc_transitions)) { + dev_err(&i2c->dev, "No transition for %d->%d\n", + wm2000->anc_mode, mode); + return -EINVAL; + } + + /* Maintain clock while active */ + if (anc_transitions[i].source == ANC_OFF) { + ret = clk_prepare_enable(wm2000->mclk); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + } + + for (j = 0; j < ARRAY_SIZE(anc_transitions[j].step); j++) { + if (!anc_transitions[i].step[j]) + break; + ret = anc_transitions[i].step[j](i2c, + anc_transitions[i].analogue); + if (ret != 0) + break; + } + + if (anc_transitions[i].dest == ANC_OFF) + clk_disable_unprepare(wm2000->mclk); + + return ret; +} + +static int wm2000_anc_set_mode(struct wm2000_priv *wm2000) +{ + struct i2c_client *i2c = wm2000->i2c; + enum wm2000_anc_mode mode; + + if (wm2000->anc_eng_ena && wm2000->spk_ena) + if (wm2000->anc_active) + mode = ANC_ACTIVE; + else + mode = ANC_BYPASS; + else + mode = ANC_STANDBY; + + dev_dbg(&i2c->dev, "Set mode %d (enabled %d, mute %d, active %d)\n", + mode, wm2000->anc_eng_ena, !wm2000->spk_ena, + wm2000->anc_active); + + return wm2000_anc_transition(wm2000, mode); +} + +static int wm2000_anc_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + + ucontrol->value.integer.value[0] = wm2000->anc_active; + + return 0; +} + +static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int anc_active = ucontrol->value.integer.value[0]; + int ret; + + if (anc_active > 1) + return -EINVAL; + + mutex_lock(&wm2000->lock); + + wm2000->anc_active = anc_active; + + ret = wm2000_anc_set_mode(wm2000); + + mutex_unlock(&wm2000->lock); + + return ret; +} + +static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + + ucontrol->value.integer.value[0] = wm2000->spk_ena; + + return 0; +} + +static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int val = ucontrol->value.integer.value[0]; + int ret; + + if (val > 1) + return -EINVAL; + + mutex_lock(&wm2000->lock); + + wm2000->spk_ena = val; + + ret = wm2000_anc_set_mode(wm2000); + + mutex_unlock(&wm2000->lock); + + return ret; +} + +static const struct snd_kcontrol_new wm2000_controls[] = { + SOC_SINGLE("ANC Volume", WM2000_REG_ANC_GAIN_CTRL, 0, 255, 0), + SOC_SINGLE_BOOL_EXT("WM2000 ANC Switch", 0, + wm2000_anc_mode_get, + wm2000_anc_mode_put), + SOC_SINGLE_BOOL_EXT("WM2000 Switch", 0, + wm2000_speaker_get, + wm2000_speaker_put), +}; + +static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + int ret; + + mutex_lock(&wm2000->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + wm2000->anc_eng_ena = 1; + + if (SND_SOC_DAPM_EVENT_OFF(event)) + wm2000->anc_eng_ena = 0; + + ret = wm2000_anc_set_mode(wm2000); + + mutex_unlock(&wm2000->lock); + + return ret; +} + +static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { +/* Externally visible pins */ +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), + +SND_SOC_DAPM_INPUT("LINN"), +SND_SOC_DAPM_INPUT("LINP"), + +SND_SOC_DAPM_PGA_E("ANC Engine", SND_SOC_NOPM, 0, 0, NULL, 0, + wm2000_anc_power_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +}; + +/* Target, Path, Source */ +static const struct snd_soc_dapm_route wm2000_audio_map[] = { + { "SPKN", NULL, "ANC Engine" }, + { "SPKP", NULL, "ANC Engine" }, + { "ANC Engine", NULL, "LINN" }, + { "ANC Engine", NULL, "LINP" }, +}; + +#ifdef CONFIG_PM +static int wm2000_suspend(struct snd_soc_component *component) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + + return wm2000_anc_transition(wm2000, ANC_OFF); +} + +static int wm2000_resume(struct snd_soc_component *component) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + + return wm2000_anc_set_mode(wm2000); +} +#else +#define wm2000_suspend NULL +#define wm2000_resume NULL +#endif + +static bool wm2000_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM2000_REG_SYS_START: + case WM2000_REG_ANC_GAIN_CTRL: + case WM2000_REG_MSE_TH1: + case WM2000_REG_MSE_TH2: + case WM2000_REG_SPEECH_CLARITY: + case WM2000_REG_SYS_WATCHDOG: + case WM2000_REG_ANA_VMID_PD_TIME: + case WM2000_REG_ANA_VMID_PU_TIME: + case WM2000_REG_CAT_FLTR_INDX: + case WM2000_REG_CAT_GAIN_0: + case WM2000_REG_SYS_STATUS: + case WM2000_REG_SYS_MODE_CNTRL: + case WM2000_REG_SYS_START0: + case WM2000_REG_SYS_START1: + case WM2000_REG_ID1: + case WM2000_REG_ID2: + case WM2000_REG_REVISON: + case WM2000_REG_SYS_CTL1: + case WM2000_REG_SYS_CTL2: + case WM2000_REG_ANC_STAT: + case WM2000_REG_IF_CTL: + case WM2000_REG_ANA_MIC_CTL: + case WM2000_REG_SPK_CTL: + return true; + default: + return false; + } +} + +static const struct regmap_config wm2000_regmap = { + .reg_bits = 16, + .val_bits = 8, + + .max_register = WM2000_REG_SPK_CTL, + .readable_reg = wm2000_readable_reg, +}; + +static int wm2000_probe(struct snd_soc_component *component) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + + /* This will trigger a transition to standby mode by default */ + wm2000_anc_set_mode(wm2000); + + return 0; +} + +static void wm2000_remove(struct snd_soc_component *component) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + + wm2000_anc_transition(wm2000, ANC_OFF); +} + +static const struct snd_soc_component_driver soc_component_dev_wm2000 = { + .probe = wm2000_probe, + .remove = wm2000_remove, + .suspend = wm2000_suspend, + .resume = wm2000_resume, + .controls = wm2000_controls, + .num_controls = ARRAY_SIZE(wm2000_controls), + .dapm_widgets = wm2000_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm2000_dapm_widgets), + .dapm_routes = wm2000_audio_map, + .num_dapm_routes = ARRAY_SIZE(wm2000_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm2000_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct wm2000_priv *wm2000; + struct wm2000_platform_data *pdata; + const char *filename; + const struct firmware *fw = NULL; + int ret, i; + int reg; + u16 id; + + wm2000 = devm_kzalloc(&i2c->dev, sizeof(*wm2000), GFP_KERNEL); + if (!wm2000) + return -ENOMEM; + + mutex_init(&wm2000->lock); + + dev_set_drvdata(&i2c->dev, wm2000); + + wm2000->regmap = devm_regmap_init_i2c(i2c, &wm2000_regmap); + if (IS_ERR(wm2000->regmap)) { + ret = PTR_ERR(wm2000->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + goto out; + } + + for (i = 0; i < WM2000_NUM_SUPPLIES; i++) + wm2000->supplies[i].supply = wm2000_supplies[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, WM2000_NUM_SUPPLIES, + wm2000->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to get supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(WM2000_NUM_SUPPLIES, wm2000->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + /* Verify that this is a WM2000 */ + reg = wm2000_read(i2c, WM2000_REG_ID1); + id = reg << 8; + reg = wm2000_read(i2c, WM2000_REG_ID2); + id |= reg & 0xff; + + if (id != 0x2000) { + dev_err(&i2c->dev, "Device is not a WM2000 - ID %x\n", id); + ret = -ENODEV; + goto err_supplies; + } + + reg = wm2000_read(i2c, WM2000_REG_REVISON); + dev_info(&i2c->dev, "revision %c\n", reg + 'A'); + + wm2000->mclk = devm_clk_get(&i2c->dev, "MCLK"); + if (IS_ERR(wm2000->mclk)) { + ret = PTR_ERR(wm2000->mclk); + dev_err(&i2c->dev, "Failed to get MCLK: %d\n", ret); + goto err_supplies; + } + + filename = "wm2000_anc.bin"; + pdata = dev_get_platdata(&i2c->dev); + if (pdata) { + wm2000->speech_clarity = !pdata->speech_enh_disable; + + if (pdata->download_file) + filename = pdata->download_file; + } + + ret = request_firmware(&fw, filename, &i2c->dev); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to acquire ANC data: %d\n", ret); + goto err_supplies; + } + + /* Pre-cook the concatenation of the register address onto the image */ + wm2000->anc_download_size = fw->size + 2; + wm2000->anc_download = devm_kzalloc(&i2c->dev, + wm2000->anc_download_size, + GFP_KERNEL); + if (wm2000->anc_download == NULL) { + ret = -ENOMEM; + goto err_supplies; + } + + wm2000->anc_download[0] = 0x80; + wm2000->anc_download[1] = 0x00; + memcpy(wm2000->anc_download + 2, fw->data, fw->size); + + wm2000->anc_eng_ena = 1; + wm2000->anc_active = 1; + wm2000->spk_ena = 1; + wm2000->i2c = i2c; + + wm2000_reset(wm2000); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm2000, NULL, 0); + +err_supplies: + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); + +out: + release_firmware(fw); + return ret; +} + +static const struct i2c_device_id wm2000_i2c_id[] = { + { "wm2000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm2000_i2c_id); + +static struct i2c_driver wm2000_i2c_driver = { + .driver = { + .name = "wm2000", + }, + .probe = wm2000_i2c_probe, + .id_table = wm2000_i2c_id, +}; + +module_i2c_driver(wm2000_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM2000 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm2000.h b/sound/soc/codecs/wm2000.h new file mode 100644 index 000000000..3870c0e1d --- /dev/null +++ b/sound/soc/codecs/wm2000.h @@ -0,0 +1,74 @@ +/* + * wm2000.h -- WM2000 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM2000_H +#define _WM2000_H + +#define WM2000_REG_SYS_START 0x8000 +#define WM2000_REG_ANC_GAIN_CTRL 0x8fa2 +#define WM2000_REG_MSE_TH2 0x8fdf +#define WM2000_REG_MSE_TH1 0x8fe0 +#define WM2000_REG_SPEECH_CLARITY 0x8fef +#define WM2000_REG_SYS_WATCHDOG 0x8ff6 +#define WM2000_REG_ANA_VMID_PD_TIME 0x8ff7 +#define WM2000_REG_ANA_VMID_PU_TIME 0x8ff8 +#define WM2000_REG_CAT_FLTR_INDX 0x8ff9 +#define WM2000_REG_CAT_GAIN_0 0x8ffa +#define WM2000_REG_SYS_STATUS 0x8ffc +#define WM2000_REG_SYS_MODE_CNTRL 0x8ffd +#define WM2000_REG_SYS_START0 0x8ffe +#define WM2000_REG_SYS_START1 0x8fff +#define WM2000_REG_ID1 0xf000 +#define WM2000_REG_ID2 0xf001 +#define WM2000_REG_REVISON 0xf002 +#define WM2000_REG_SYS_CTL1 0xf003 +#define WM2000_REG_SYS_CTL2 0xf004 +#define WM2000_REG_ANC_STAT 0xf005 +#define WM2000_REG_IF_CTL 0xf006 +#define WM2000_REG_ANA_MIC_CTL 0xf028 +#define WM2000_REG_SPK_CTL 0xf034 + +/* SPEECH_CLARITY */ +#define WM2000_SPEECH_CLARITY 0x01 + +/* SYS_STATUS */ +#define WM2000_STATUS_MOUSE_ACTIVE 0x40 +#define WM2000_STATUS_CAT_FREQ_COMPLETE 0x20 +#define WM2000_STATUS_CAT_GAIN_COMPLETE 0x10 +#define WM2000_STATUS_THERMAL_SHUTDOWN_COMPLETE 0x08 +#define WM2000_STATUS_ANC_DISABLED 0x04 +#define WM2000_STATUS_POWER_DOWN_COMPLETE 0x02 +#define WM2000_STATUS_BOOT_COMPLETE 0x01 + +/* SYS_MODE_CNTRL */ +#define WM2000_MODE_ANA_SEQ_INCLUDE 0x80 +#define WM2000_MODE_MOUSE_ENABLE 0x40 +#define WM2000_MODE_CAT_FREQ_ENABLE 0x20 +#define WM2000_MODE_CAT_GAIN_ENABLE 0x10 +#define WM2000_MODE_BYPASS_ENTRY 0x08 +#define WM2000_MODE_STANDBY_ENTRY 0x04 +#define WM2000_MODE_THERMAL_ENABLE 0x02 +#define WM2000_MODE_POWER_DOWN 0x01 + +/* SYS_CTL1 */ +#define WM2000_SYS_STBY 0x01 + +/* SYS_CTL2 */ +#define WM2000_MCLK_DIV2_ENA_CLR 0x80 +#define WM2000_MCLK_DIV2_ENA_SET 0x40 +#define WM2000_ANC_ENG_CLR 0x20 +#define WM2000_ANC_ENG_SET 0x10 +#define WM2000_ANC_INT_N_CLR 0x08 +#define WM2000_ANC_INT_N_SET 0x04 +#define WM2000_RAM_CLR 0x02 +#define WM2000_RAM_SET 0x01 + +/* ANC_STAT */ +#define WM2000_ANC_ENG_IDLE 0x01 + +#endif diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c new file mode 100644 index 000000000..0a3b746fb --- /dev/null +++ b/sound/soc/codecs/wm2200.c @@ -0,0 +1,2512 @@ +/* + * wm2200.c -- WM2200 ALSA SoC Audio driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm2200.h" +#include "wmfw.h" +#include "wm_adsp.h" + +#define WM2200_DSP_CONTROL_1 0x00 +#define WM2200_DSP_CONTROL_2 0x02 +#define WM2200_DSP_CONTROL_3 0x03 +#define WM2200_DSP_CONTROL_4 0x04 +#define WM2200_DSP_CONTROL_5 0x06 +#define WM2200_DSP_CONTROL_6 0x07 +#define WM2200_DSP_CONTROL_7 0x08 +#define WM2200_DSP_CONTROL_8 0x09 +#define WM2200_DSP_CONTROL_9 0x0A +#define WM2200_DSP_CONTROL_10 0x0B +#define WM2200_DSP_CONTROL_11 0x0C +#define WM2200_DSP_CONTROL_12 0x0D +#define WM2200_DSP_CONTROL_13 0x0F +#define WM2200_DSP_CONTROL_14 0x10 +#define WM2200_DSP_CONTROL_15 0x11 +#define WM2200_DSP_CONTROL_16 0x12 +#define WM2200_DSP_CONTROL_17 0x13 +#define WM2200_DSP_CONTROL_18 0x14 +#define WM2200_DSP_CONTROL_19 0x16 +#define WM2200_DSP_CONTROL_20 0x17 +#define WM2200_DSP_CONTROL_21 0x18 +#define WM2200_DSP_CONTROL_22 0x1A +#define WM2200_DSP_CONTROL_23 0x1B +#define WM2200_DSP_CONTROL_24 0x1C +#define WM2200_DSP_CONTROL_25 0x1E +#define WM2200_DSP_CONTROL_26 0x20 +#define WM2200_DSP_CONTROL_27 0x21 +#define WM2200_DSP_CONTROL_28 0x22 +#define WM2200_DSP_CONTROL_29 0x23 +#define WM2200_DSP_CONTROL_30 0x24 +#define WM2200_DSP_CONTROL_31 0x26 + +/* The code assumes DCVDD is generated internally */ +#define WM2200_NUM_CORE_SUPPLIES 2 +static const char *wm2200_core_supply_names[WM2200_NUM_CORE_SUPPLIES] = { + "DBVDD", + "LDOVDD", +}; + +struct wm2200_fll { + int fref; + int fout; + int src; + struct completion lock; +}; + +/* codec private data */ +struct wm2200_priv { + struct wm_adsp dsp[2]; + struct regmap *regmap; + struct device *dev; + struct snd_soc_component *component; + struct wm2200_pdata pdata; + struct regulator_bulk_data core_supplies[WM2200_NUM_CORE_SUPPLIES]; + + struct completion fll_lock; + int fll_fout; + int fll_fref; + int fll_src; + + int rev; + int sysclk; + + unsigned int symmetric_rates:1; +}; + +#define WM2200_DSP_RANGE_BASE (WM2200_MAX_REGISTER + 1) +#define WM2200_DSP_SPACING 12288 + +#define WM2200_DSP1_DM_BASE (WM2200_DSP_RANGE_BASE + (0 * WM2200_DSP_SPACING)) +#define WM2200_DSP1_PM_BASE (WM2200_DSP_RANGE_BASE + (1 * WM2200_DSP_SPACING)) +#define WM2200_DSP1_ZM_BASE (WM2200_DSP_RANGE_BASE + (2 * WM2200_DSP_SPACING)) +#define WM2200_DSP2_DM_BASE (WM2200_DSP_RANGE_BASE + (3 * WM2200_DSP_SPACING)) +#define WM2200_DSP2_PM_BASE (WM2200_DSP_RANGE_BASE + (4 * WM2200_DSP_SPACING)) +#define WM2200_DSP2_ZM_BASE (WM2200_DSP_RANGE_BASE + (5 * WM2200_DSP_SPACING)) + +static const struct regmap_range_cfg wm2200_ranges[] = { + { .name = "DSP1DM", .range_min = WM2200_DSP1_DM_BASE, + .range_max = WM2200_DSP1_DM_BASE + 12287, + .selector_reg = WM2200_DSP1_CONTROL_3, + .selector_mask = WM2200_DSP1_PAGE_BASE_DM_0_MASK, + .selector_shift = WM2200_DSP1_PAGE_BASE_DM_0_SHIFT, + .window_start = WM2200_DSP1_DM_0, .window_len = 2048, }, + + { .name = "DSP1PM", .range_min = WM2200_DSP1_PM_BASE, + .range_max = WM2200_DSP1_PM_BASE + 12287, + .selector_reg = WM2200_DSP1_CONTROL_2, + .selector_mask = WM2200_DSP1_PAGE_BASE_PM_0_MASK, + .selector_shift = WM2200_DSP1_PAGE_BASE_PM_0_SHIFT, + .window_start = WM2200_DSP1_PM_0, .window_len = 768, }, + + { .name = "DSP1ZM", .range_min = WM2200_DSP1_ZM_BASE, + .range_max = WM2200_DSP1_ZM_BASE + 2047, + .selector_reg = WM2200_DSP1_CONTROL_4, + .selector_mask = WM2200_DSP1_PAGE_BASE_ZM_0_MASK, + .selector_shift = WM2200_DSP1_PAGE_BASE_ZM_0_SHIFT, + .window_start = WM2200_DSP1_ZM_0, .window_len = 1024, }, + + { .name = "DSP2DM", .range_min = WM2200_DSP2_DM_BASE, + .range_max = WM2200_DSP2_DM_BASE + 4095, + .selector_reg = WM2200_DSP2_CONTROL_3, + .selector_mask = WM2200_DSP2_PAGE_BASE_DM_0_MASK, + .selector_shift = WM2200_DSP2_PAGE_BASE_DM_0_SHIFT, + .window_start = WM2200_DSP2_DM_0, .window_len = 2048, }, + + { .name = "DSP2PM", .range_min = WM2200_DSP2_PM_BASE, + .range_max = WM2200_DSP2_PM_BASE + 11287, + .selector_reg = WM2200_DSP2_CONTROL_2, + .selector_mask = WM2200_DSP2_PAGE_BASE_PM_0_MASK, + .selector_shift = WM2200_DSP2_PAGE_BASE_PM_0_SHIFT, + .window_start = WM2200_DSP2_PM_0, .window_len = 768, }, + + { .name = "DSP2ZM", .range_min = WM2200_DSP2_ZM_BASE, + .range_max = WM2200_DSP2_ZM_BASE + 2047, + .selector_reg = WM2200_DSP2_CONTROL_4, + .selector_mask = WM2200_DSP2_PAGE_BASE_ZM_0_MASK, + .selector_shift = WM2200_DSP2_PAGE_BASE_ZM_0_SHIFT, + .window_start = WM2200_DSP2_ZM_0, .window_len = 1024, }, +}; + +static const struct wm_adsp_region wm2200_dsp1_regions[] = { + { .type = WMFW_ADSP1_PM, .base = WM2200_DSP1_PM_BASE }, + { .type = WMFW_ADSP1_DM, .base = WM2200_DSP1_DM_BASE }, + { .type = WMFW_ADSP1_ZM, .base = WM2200_DSP1_ZM_BASE }, +}; + +static const struct wm_adsp_region wm2200_dsp2_regions[] = { + { .type = WMFW_ADSP1_PM, .base = WM2200_DSP2_PM_BASE }, + { .type = WMFW_ADSP1_DM, .base = WM2200_DSP2_DM_BASE }, + { .type = WMFW_ADSP1_ZM, .base = WM2200_DSP2_ZM_BASE }, +}; + +static const struct reg_default wm2200_reg_defaults[] = { + { 0x000B, 0x0000 }, /* R11 - Tone Generator 1 */ + { 0x0102, 0x0000 }, /* R258 - Clocking 3 */ + { 0x0103, 0x0011 }, /* R259 - Clocking 4 */ + { 0x0111, 0x0000 }, /* R273 - FLL Control 1 */ + { 0x0112, 0x0000 }, /* R274 - FLL Control 2 */ + { 0x0113, 0x0000 }, /* R275 - FLL Control 3 */ + { 0x0114, 0x0000 }, /* R276 - FLL Control 4 */ + { 0x0116, 0x0177 }, /* R278 - FLL Control 6 */ + { 0x0117, 0x0004 }, /* R279 - FLL Control 7 */ + { 0x0119, 0x0000 }, /* R281 - FLL EFS 1 */ + { 0x011A, 0x0002 }, /* R282 - FLL EFS 2 */ + { 0x0200, 0x0000 }, /* R512 - Mic Charge Pump 1 */ + { 0x0201, 0x03FF }, /* R513 - Mic Charge Pump 2 */ + { 0x0202, 0x9BDE }, /* R514 - DM Charge Pump 1 */ + { 0x020C, 0x0000 }, /* R524 - Mic Bias Ctrl 1 */ + { 0x020D, 0x0000 }, /* R525 - Mic Bias Ctrl 2 */ + { 0x020F, 0x0000 }, /* R527 - Ear Piece Ctrl 1 */ + { 0x0210, 0x0000 }, /* R528 - Ear Piece Ctrl 2 */ + { 0x0301, 0x0000 }, /* R769 - Input Enables */ + { 0x0302, 0x2240 }, /* R770 - IN1L Control */ + { 0x0303, 0x0040 }, /* R771 - IN1R Control */ + { 0x0304, 0x2240 }, /* R772 - IN2L Control */ + { 0x0305, 0x0040 }, /* R773 - IN2R Control */ + { 0x0306, 0x2240 }, /* R774 - IN3L Control */ + { 0x0307, 0x0040 }, /* R775 - IN3R Control */ + { 0x030A, 0x0000 }, /* R778 - RXANC_SRC */ + { 0x030B, 0x0022 }, /* R779 - Input Volume Ramp */ + { 0x030C, 0x0180 }, /* R780 - ADC Digital Volume 1L */ + { 0x030D, 0x0180 }, /* R781 - ADC Digital Volume 1R */ + { 0x030E, 0x0180 }, /* R782 - ADC Digital Volume 2L */ + { 0x030F, 0x0180 }, /* R783 - ADC Digital Volume 2R */ + { 0x0310, 0x0180 }, /* R784 - ADC Digital Volume 3L */ + { 0x0311, 0x0180 }, /* R785 - ADC Digital Volume 3R */ + { 0x0400, 0x0000 }, /* R1024 - Output Enables */ + { 0x0401, 0x0000 }, /* R1025 - DAC Volume Limit 1L */ + { 0x0402, 0x0000 }, /* R1026 - DAC Volume Limit 1R */ + { 0x0403, 0x0000 }, /* R1027 - DAC Volume Limit 2L */ + { 0x0404, 0x0000 }, /* R1028 - DAC Volume Limit 2R */ + { 0x0409, 0x0000 }, /* R1033 - DAC AEC Control 1 */ + { 0x040A, 0x0022 }, /* R1034 - Output Volume Ramp */ + { 0x040B, 0x0180 }, /* R1035 - DAC Digital Volume 1L */ + { 0x040C, 0x0180 }, /* R1036 - DAC Digital Volume 1R */ + { 0x040D, 0x0180 }, /* R1037 - DAC Digital Volume 2L */ + { 0x040E, 0x0180 }, /* R1038 - DAC Digital Volume 2R */ + { 0x0417, 0x0069 }, /* R1047 - PDM 1 */ + { 0x0418, 0x0000 }, /* R1048 - PDM 2 */ + { 0x0500, 0x0000 }, /* R1280 - Audio IF 1_1 */ + { 0x0501, 0x0008 }, /* R1281 - Audio IF 1_2 */ + { 0x0502, 0x0000 }, /* R1282 - Audio IF 1_3 */ + { 0x0503, 0x0000 }, /* R1283 - Audio IF 1_4 */ + { 0x0504, 0x0000 }, /* R1284 - Audio IF 1_5 */ + { 0x0505, 0x0001 }, /* R1285 - Audio IF 1_6 */ + { 0x0506, 0x0001 }, /* R1286 - Audio IF 1_7 */ + { 0x0507, 0x0000 }, /* R1287 - Audio IF 1_8 */ + { 0x0508, 0x0000 }, /* R1288 - Audio IF 1_9 */ + { 0x0509, 0x0000 }, /* R1289 - Audio IF 1_10 */ + { 0x050A, 0x0000 }, /* R1290 - Audio IF 1_11 */ + { 0x050B, 0x0000 }, /* R1291 - Audio IF 1_12 */ + { 0x050C, 0x0000 }, /* R1292 - Audio IF 1_13 */ + { 0x050D, 0x0000 }, /* R1293 - Audio IF 1_14 */ + { 0x050E, 0x0000 }, /* R1294 - Audio IF 1_15 */ + { 0x050F, 0x0000 }, /* R1295 - Audio IF 1_16 */ + { 0x0510, 0x0000 }, /* R1296 - Audio IF 1_17 */ + { 0x0511, 0x0000 }, /* R1297 - Audio IF 1_18 */ + { 0x0512, 0x0000 }, /* R1298 - Audio IF 1_19 */ + { 0x0513, 0x0000 }, /* R1299 - Audio IF 1_20 */ + { 0x0514, 0x0000 }, /* R1300 - Audio IF 1_21 */ + { 0x0515, 0x0001 }, /* R1301 - Audio IF 1_22 */ + { 0x0600, 0x0000 }, /* R1536 - OUT1LMIX Input 1 Source */ + { 0x0601, 0x0080 }, /* R1537 - OUT1LMIX Input 1 Volume */ + { 0x0602, 0x0000 }, /* R1538 - OUT1LMIX Input 2 Source */ + { 0x0603, 0x0080 }, /* R1539 - OUT1LMIX Input 2 Volume */ + { 0x0604, 0x0000 }, /* R1540 - OUT1LMIX Input 3 Source */ + { 0x0605, 0x0080 }, /* R1541 - OUT1LMIX Input 3 Volume */ + { 0x0606, 0x0000 }, /* R1542 - OUT1LMIX Input 4 Source */ + { 0x0607, 0x0080 }, /* R1543 - OUT1LMIX Input 4 Volume */ + { 0x0608, 0x0000 }, /* R1544 - OUT1RMIX Input 1 Source */ + { 0x0609, 0x0080 }, /* R1545 - OUT1RMIX Input 1 Volume */ + { 0x060A, 0x0000 }, /* R1546 - OUT1RMIX Input 2 Source */ + { 0x060B, 0x0080 }, /* R1547 - OUT1RMIX Input 2 Volume */ + { 0x060C, 0x0000 }, /* R1548 - OUT1RMIX Input 3 Source */ + { 0x060D, 0x0080 }, /* R1549 - OUT1RMIX Input 3 Volume */ + { 0x060E, 0x0000 }, /* R1550 - OUT1RMIX Input 4 Source */ + { 0x060F, 0x0080 }, /* R1551 - OUT1RMIX Input 4 Volume */ + { 0x0610, 0x0000 }, /* R1552 - OUT2LMIX Input 1 Source */ + { 0x0611, 0x0080 }, /* R1553 - OUT2LMIX Input 1 Volume */ + { 0x0612, 0x0000 }, /* R1554 - OUT2LMIX Input 2 Source */ + { 0x0613, 0x0080 }, /* R1555 - OUT2LMIX Input 2 Volume */ + { 0x0614, 0x0000 }, /* R1556 - OUT2LMIX Input 3 Source */ + { 0x0615, 0x0080 }, /* R1557 - OUT2LMIX Input 3 Volume */ + { 0x0616, 0x0000 }, /* R1558 - OUT2LMIX Input 4 Source */ + { 0x0617, 0x0080 }, /* R1559 - OUT2LMIX Input 4 Volume */ + { 0x0618, 0x0000 }, /* R1560 - OUT2RMIX Input 1 Source */ + { 0x0619, 0x0080 }, /* R1561 - OUT2RMIX Input 1 Volume */ + { 0x061A, 0x0000 }, /* R1562 - OUT2RMIX Input 2 Source */ + { 0x061B, 0x0080 }, /* R1563 - OUT2RMIX Input 2 Volume */ + { 0x061C, 0x0000 }, /* R1564 - OUT2RMIX Input 3 Source */ + { 0x061D, 0x0080 }, /* R1565 - OUT2RMIX Input 3 Volume */ + { 0x061E, 0x0000 }, /* R1566 - OUT2RMIX Input 4 Source */ + { 0x061F, 0x0080 }, /* R1567 - OUT2RMIX Input 4 Volume */ + { 0x0620, 0x0000 }, /* R1568 - AIF1TX1MIX Input 1 Source */ + { 0x0621, 0x0080 }, /* R1569 - AIF1TX1MIX Input 1 Volume */ + { 0x0622, 0x0000 }, /* R1570 - AIF1TX1MIX Input 2 Source */ + { 0x0623, 0x0080 }, /* R1571 - AIF1TX1MIX Input 2 Volume */ + { 0x0624, 0x0000 }, /* R1572 - AIF1TX1MIX Input 3 Source */ + { 0x0625, 0x0080 }, /* R1573 - AIF1TX1MIX Input 3 Volume */ + { 0x0626, 0x0000 }, /* R1574 - AIF1TX1MIX Input 4 Source */ + { 0x0627, 0x0080 }, /* R1575 - AIF1TX1MIX Input 4 Volume */ + { 0x0628, 0x0000 }, /* R1576 - AIF1TX2MIX Input 1 Source */ + { 0x0629, 0x0080 }, /* R1577 - AIF1TX2MIX Input 1 Volume */ + { 0x062A, 0x0000 }, /* R1578 - AIF1TX2MIX Input 2 Source */ + { 0x062B, 0x0080 }, /* R1579 - AIF1TX2MIX Input 2 Volume */ + { 0x062C, 0x0000 }, /* R1580 - AIF1TX2MIX Input 3 Source */ + { 0x062D, 0x0080 }, /* R1581 - AIF1TX2MIX Input 3 Volume */ + { 0x062E, 0x0000 }, /* R1582 - AIF1TX2MIX Input 4 Source */ + { 0x062F, 0x0080 }, /* R1583 - AIF1TX2MIX Input 4 Volume */ + { 0x0630, 0x0000 }, /* R1584 - AIF1TX3MIX Input 1 Source */ + { 0x0631, 0x0080 }, /* R1585 - AIF1TX3MIX Input 1 Volume */ + { 0x0632, 0x0000 }, /* R1586 - AIF1TX3MIX Input 2 Source */ + { 0x0633, 0x0080 }, /* R1587 - AIF1TX3MIX Input 2 Volume */ + { 0x0634, 0x0000 }, /* R1588 - AIF1TX3MIX Input 3 Source */ + { 0x0635, 0x0080 }, /* R1589 - AIF1TX3MIX Input 3 Volume */ + { 0x0636, 0x0000 }, /* R1590 - AIF1TX3MIX Input 4 Source */ + { 0x0637, 0x0080 }, /* R1591 - AIF1TX3MIX Input 4 Volume */ + { 0x0638, 0x0000 }, /* R1592 - AIF1TX4MIX Input 1 Source */ + { 0x0639, 0x0080 }, /* R1593 - AIF1TX4MIX Input 1 Volume */ + { 0x063A, 0x0000 }, /* R1594 - AIF1TX4MIX Input 2 Source */ + { 0x063B, 0x0080 }, /* R1595 - AIF1TX4MIX Input 2 Volume */ + { 0x063C, 0x0000 }, /* R1596 - AIF1TX4MIX Input 3 Source */ + { 0x063D, 0x0080 }, /* R1597 - AIF1TX4MIX Input 3 Volume */ + { 0x063E, 0x0000 }, /* R1598 - AIF1TX4MIX Input 4 Source */ + { 0x063F, 0x0080 }, /* R1599 - AIF1TX4MIX Input 4 Volume */ + { 0x0640, 0x0000 }, /* R1600 - AIF1TX5MIX Input 1 Source */ + { 0x0641, 0x0080 }, /* R1601 - AIF1TX5MIX Input 1 Volume */ + { 0x0642, 0x0000 }, /* R1602 - AIF1TX5MIX Input 2 Source */ + { 0x0643, 0x0080 }, /* R1603 - AIF1TX5MIX Input 2 Volume */ + { 0x0644, 0x0000 }, /* R1604 - AIF1TX5MIX Input 3 Source */ + { 0x0645, 0x0080 }, /* R1605 - AIF1TX5MIX Input 3 Volume */ + { 0x0646, 0x0000 }, /* R1606 - AIF1TX5MIX Input 4 Source */ + { 0x0647, 0x0080 }, /* R1607 - AIF1TX5MIX Input 4 Volume */ + { 0x0648, 0x0000 }, /* R1608 - AIF1TX6MIX Input 1 Source */ + { 0x0649, 0x0080 }, /* R1609 - AIF1TX6MIX Input 1 Volume */ + { 0x064A, 0x0000 }, /* R1610 - AIF1TX6MIX Input 2 Source */ + { 0x064B, 0x0080 }, /* R1611 - AIF1TX6MIX Input 2 Volume */ + { 0x064C, 0x0000 }, /* R1612 - AIF1TX6MIX Input 3 Source */ + { 0x064D, 0x0080 }, /* R1613 - AIF1TX6MIX Input 3 Volume */ + { 0x064E, 0x0000 }, /* R1614 - AIF1TX6MIX Input 4 Source */ + { 0x064F, 0x0080 }, /* R1615 - AIF1TX6MIX Input 4 Volume */ + { 0x0650, 0x0000 }, /* R1616 - EQLMIX Input 1 Source */ + { 0x0651, 0x0080 }, /* R1617 - EQLMIX Input 1 Volume */ + { 0x0652, 0x0000 }, /* R1618 - EQLMIX Input 2 Source */ + { 0x0653, 0x0080 }, /* R1619 - EQLMIX Input 2 Volume */ + { 0x0654, 0x0000 }, /* R1620 - EQLMIX Input 3 Source */ + { 0x0655, 0x0080 }, /* R1621 - EQLMIX Input 3 Volume */ + { 0x0656, 0x0000 }, /* R1622 - EQLMIX Input 4 Source */ + { 0x0657, 0x0080 }, /* R1623 - EQLMIX Input 4 Volume */ + { 0x0658, 0x0000 }, /* R1624 - EQRMIX Input 1 Source */ + { 0x0659, 0x0080 }, /* R1625 - EQRMIX Input 1 Volume */ + { 0x065A, 0x0000 }, /* R1626 - EQRMIX Input 2 Source */ + { 0x065B, 0x0080 }, /* R1627 - EQRMIX Input 2 Volume */ + { 0x065C, 0x0000 }, /* R1628 - EQRMIX Input 3 Source */ + { 0x065D, 0x0080 }, /* R1629 - EQRMIX Input 3 Volume */ + { 0x065E, 0x0000 }, /* R1630 - EQRMIX Input 4 Source */ + { 0x065F, 0x0080 }, /* R1631 - EQRMIX Input 4 Volume */ + { 0x0660, 0x0000 }, /* R1632 - LHPF1MIX Input 1 Source */ + { 0x0661, 0x0080 }, /* R1633 - LHPF1MIX Input 1 Volume */ + { 0x0662, 0x0000 }, /* R1634 - LHPF1MIX Input 2 Source */ + { 0x0663, 0x0080 }, /* R1635 - LHPF1MIX Input 2 Volume */ + { 0x0664, 0x0000 }, /* R1636 - LHPF1MIX Input 3 Source */ + { 0x0665, 0x0080 }, /* R1637 - LHPF1MIX Input 3 Volume */ + { 0x0666, 0x0000 }, /* R1638 - LHPF1MIX Input 4 Source */ + { 0x0667, 0x0080 }, /* R1639 - LHPF1MIX Input 4 Volume */ + { 0x0668, 0x0000 }, /* R1640 - LHPF2MIX Input 1 Source */ + { 0x0669, 0x0080 }, /* R1641 - LHPF2MIX Input 1 Volume */ + { 0x066A, 0x0000 }, /* R1642 - LHPF2MIX Input 2 Source */ + { 0x066B, 0x0080 }, /* R1643 - LHPF2MIX Input 2 Volume */ + { 0x066C, 0x0000 }, /* R1644 - LHPF2MIX Input 3 Source */ + { 0x066D, 0x0080 }, /* R1645 - LHPF2MIX Input 3 Volume */ + { 0x066E, 0x0000 }, /* R1646 - LHPF2MIX Input 4 Source */ + { 0x066F, 0x0080 }, /* R1647 - LHPF2MIX Input 4 Volume */ + { 0x0670, 0x0000 }, /* R1648 - DSP1LMIX Input 1 Source */ + { 0x0671, 0x0080 }, /* R1649 - DSP1LMIX Input 1 Volume */ + { 0x0672, 0x0000 }, /* R1650 - DSP1LMIX Input 2 Source */ + { 0x0673, 0x0080 }, /* R1651 - DSP1LMIX Input 2 Volume */ + { 0x0674, 0x0000 }, /* R1652 - DSP1LMIX Input 3 Source */ + { 0x0675, 0x0080 }, /* R1653 - DSP1LMIX Input 3 Volume */ + { 0x0676, 0x0000 }, /* R1654 - DSP1LMIX Input 4 Source */ + { 0x0677, 0x0080 }, /* R1655 - DSP1LMIX Input 4 Volume */ + { 0x0678, 0x0000 }, /* R1656 - DSP1RMIX Input 1 Source */ + { 0x0679, 0x0080 }, /* R1657 - DSP1RMIX Input 1 Volume */ + { 0x067A, 0x0000 }, /* R1658 - DSP1RMIX Input 2 Source */ + { 0x067B, 0x0080 }, /* R1659 - DSP1RMIX Input 2 Volume */ + { 0x067C, 0x0000 }, /* R1660 - DSP1RMIX Input 3 Source */ + { 0x067D, 0x0080 }, /* R1661 - DSP1RMIX Input 3 Volume */ + { 0x067E, 0x0000 }, /* R1662 - DSP1RMIX Input 4 Source */ + { 0x067F, 0x0080 }, /* R1663 - DSP1RMIX Input 4 Volume */ + { 0x0680, 0x0000 }, /* R1664 - DSP1AUX1MIX Input 1 Source */ + { 0x0681, 0x0000 }, /* R1665 - DSP1AUX2MIX Input 1 Source */ + { 0x0682, 0x0000 }, /* R1666 - DSP1AUX3MIX Input 1 Source */ + { 0x0683, 0x0000 }, /* R1667 - DSP1AUX4MIX Input 1 Source */ + { 0x0684, 0x0000 }, /* R1668 - DSP1AUX5MIX Input 1 Source */ + { 0x0685, 0x0000 }, /* R1669 - DSP1AUX6MIX Input 1 Source */ + { 0x0686, 0x0000 }, /* R1670 - DSP2LMIX Input 1 Source */ + { 0x0687, 0x0080 }, /* R1671 - DSP2LMIX Input 1 Volume */ + { 0x0688, 0x0000 }, /* R1672 - DSP2LMIX Input 2 Source */ + { 0x0689, 0x0080 }, /* R1673 - DSP2LMIX Input 2 Volume */ + { 0x068A, 0x0000 }, /* R1674 - DSP2LMIX Input 3 Source */ + { 0x068B, 0x0080 }, /* R1675 - DSP2LMIX Input 3 Volume */ + { 0x068C, 0x0000 }, /* R1676 - DSP2LMIX Input 4 Source */ + { 0x068D, 0x0080 }, /* R1677 - DSP2LMIX Input 4 Volume */ + { 0x068E, 0x0000 }, /* R1678 - DSP2RMIX Input 1 Source */ + { 0x068F, 0x0080 }, /* R1679 - DSP2RMIX Input 1 Volume */ + { 0x0690, 0x0000 }, /* R1680 - DSP2RMIX Input 2 Source */ + { 0x0691, 0x0080 }, /* R1681 - DSP2RMIX Input 2 Volume */ + { 0x0692, 0x0000 }, /* R1682 - DSP2RMIX Input 3 Source */ + { 0x0693, 0x0080 }, /* R1683 - DSP2RMIX Input 3 Volume */ + { 0x0694, 0x0000 }, /* R1684 - DSP2RMIX Input 4 Source */ + { 0x0695, 0x0080 }, /* R1685 - DSP2RMIX Input 4 Volume */ + { 0x0696, 0x0000 }, /* R1686 - DSP2AUX1MIX Input 1 Source */ + { 0x0697, 0x0000 }, /* R1687 - DSP2AUX2MIX Input 1 Source */ + { 0x0698, 0x0000 }, /* R1688 - DSP2AUX3MIX Input 1 Source */ + { 0x0699, 0x0000 }, /* R1689 - DSP2AUX4MIX Input 1 Source */ + { 0x069A, 0x0000 }, /* R1690 - DSP2AUX5MIX Input 1 Source */ + { 0x069B, 0x0000 }, /* R1691 - DSP2AUX6MIX Input 1 Source */ + { 0x0700, 0xA101 }, /* R1792 - GPIO CTRL 1 */ + { 0x0701, 0xA101 }, /* R1793 - GPIO CTRL 2 */ + { 0x0702, 0xA101 }, /* R1794 - GPIO CTRL 3 */ + { 0x0703, 0xA101 }, /* R1795 - GPIO CTRL 4 */ + { 0x0709, 0x0000 }, /* R1801 - Misc Pad Ctrl 1 */ + { 0x0801, 0x00FF }, /* R2049 - Interrupt Status 1 Mask */ + { 0x0804, 0xFFFF }, /* R2052 - Interrupt Status 2 Mask */ + { 0x0808, 0x0000 }, /* R2056 - Interrupt Control */ + { 0x0900, 0x0000 }, /* R2304 - EQL_1 */ + { 0x0901, 0x0000 }, /* R2305 - EQL_2 */ + { 0x0902, 0x0000 }, /* R2306 - EQL_3 */ + { 0x0903, 0x0000 }, /* R2307 - EQL_4 */ + { 0x0904, 0x0000 }, /* R2308 - EQL_5 */ + { 0x0905, 0x0000 }, /* R2309 - EQL_6 */ + { 0x0906, 0x0000 }, /* R2310 - EQL_7 */ + { 0x0907, 0x0000 }, /* R2311 - EQL_8 */ + { 0x0908, 0x0000 }, /* R2312 - EQL_9 */ + { 0x0909, 0x0000 }, /* R2313 - EQL_10 */ + { 0x090A, 0x0000 }, /* R2314 - EQL_11 */ + { 0x090B, 0x0000 }, /* R2315 - EQL_12 */ + { 0x090C, 0x0000 }, /* R2316 - EQL_13 */ + { 0x090D, 0x0000 }, /* R2317 - EQL_14 */ + { 0x090E, 0x0000 }, /* R2318 - EQL_15 */ + { 0x090F, 0x0000 }, /* R2319 - EQL_16 */ + { 0x0910, 0x0000 }, /* R2320 - EQL_17 */ + { 0x0911, 0x0000 }, /* R2321 - EQL_18 */ + { 0x0912, 0x0000 }, /* R2322 - EQL_19 */ + { 0x0913, 0x0000 }, /* R2323 - EQL_20 */ + { 0x0916, 0x0000 }, /* R2326 - EQR_1 */ + { 0x0917, 0x0000 }, /* R2327 - EQR_2 */ + { 0x0918, 0x0000 }, /* R2328 - EQR_3 */ + { 0x0919, 0x0000 }, /* R2329 - EQR_4 */ + { 0x091A, 0x0000 }, /* R2330 - EQR_5 */ + { 0x091B, 0x0000 }, /* R2331 - EQR_6 */ + { 0x091C, 0x0000 }, /* R2332 - EQR_7 */ + { 0x091D, 0x0000 }, /* R2333 - EQR_8 */ + { 0x091E, 0x0000 }, /* R2334 - EQR_9 */ + { 0x091F, 0x0000 }, /* R2335 - EQR_10 */ + { 0x0920, 0x0000 }, /* R2336 - EQR_11 */ + { 0x0921, 0x0000 }, /* R2337 - EQR_12 */ + { 0x0922, 0x0000 }, /* R2338 - EQR_13 */ + { 0x0923, 0x0000 }, /* R2339 - EQR_14 */ + { 0x0924, 0x0000 }, /* R2340 - EQR_15 */ + { 0x0925, 0x0000 }, /* R2341 - EQR_16 */ + { 0x0926, 0x0000 }, /* R2342 - EQR_17 */ + { 0x0927, 0x0000 }, /* R2343 - EQR_18 */ + { 0x0928, 0x0000 }, /* R2344 - EQR_19 */ + { 0x0929, 0x0000 }, /* R2345 - EQR_20 */ + { 0x093E, 0x0000 }, /* R2366 - HPLPF1_1 */ + { 0x093F, 0x0000 }, /* R2367 - HPLPF1_2 */ + { 0x0942, 0x0000 }, /* R2370 - HPLPF2_1 */ + { 0x0943, 0x0000 }, /* R2371 - HPLPF2_2 */ + { 0x0A00, 0x0000 }, /* R2560 - DSP1 Control 1 */ + { 0x0A02, 0x0000 }, /* R2562 - DSP1 Control 2 */ + { 0x0A03, 0x0000 }, /* R2563 - DSP1 Control 3 */ + { 0x0A04, 0x0000 }, /* R2564 - DSP1 Control 4 */ + { 0x0A06, 0x0000 }, /* R2566 - DSP1 Control 5 */ + { 0x0A07, 0x0000 }, /* R2567 - DSP1 Control 6 */ + { 0x0A08, 0x0000 }, /* R2568 - DSP1 Control 7 */ + { 0x0A09, 0x0000 }, /* R2569 - DSP1 Control 8 */ + { 0x0A0A, 0x0000 }, /* R2570 - DSP1 Control 9 */ + { 0x0A0B, 0x0000 }, /* R2571 - DSP1 Control 10 */ + { 0x0A0C, 0x0000 }, /* R2572 - DSP1 Control 11 */ + { 0x0A0D, 0x0000 }, /* R2573 - DSP1 Control 12 */ + { 0x0A0F, 0x0000 }, /* R2575 - DSP1 Control 13 */ + { 0x0A10, 0x0000 }, /* R2576 - DSP1 Control 14 */ + { 0x0A11, 0x0000 }, /* R2577 - DSP1 Control 15 */ + { 0x0A12, 0x0000 }, /* R2578 - DSP1 Control 16 */ + { 0x0A13, 0x0000 }, /* R2579 - DSP1 Control 17 */ + { 0x0A14, 0x0000 }, /* R2580 - DSP1 Control 18 */ + { 0x0A16, 0x0000 }, /* R2582 - DSP1 Control 19 */ + { 0x0A17, 0x0000 }, /* R2583 - DSP1 Control 20 */ + { 0x0A18, 0x0000 }, /* R2584 - DSP1 Control 21 */ + { 0x0A1A, 0x1800 }, /* R2586 - DSP1 Control 22 */ + { 0x0A1B, 0x1000 }, /* R2587 - DSP1 Control 23 */ + { 0x0A1C, 0x0400 }, /* R2588 - DSP1 Control 24 */ + { 0x0A1E, 0x0000 }, /* R2590 - DSP1 Control 25 */ + { 0x0A20, 0x0000 }, /* R2592 - DSP1 Control 26 */ + { 0x0A21, 0x0000 }, /* R2593 - DSP1 Control 27 */ + { 0x0A22, 0x0000 }, /* R2594 - DSP1 Control 28 */ + { 0x0A23, 0x0000 }, /* R2595 - DSP1 Control 29 */ + { 0x0A24, 0x0000 }, /* R2596 - DSP1 Control 30 */ + { 0x0A26, 0x0000 }, /* R2598 - DSP1 Control 31 */ + { 0x0B00, 0x0000 }, /* R2816 - DSP2 Control 1 */ + { 0x0B02, 0x0000 }, /* R2818 - DSP2 Control 2 */ + { 0x0B03, 0x0000 }, /* R2819 - DSP2 Control 3 */ + { 0x0B04, 0x0000 }, /* R2820 - DSP2 Control 4 */ + { 0x0B06, 0x0000 }, /* R2822 - DSP2 Control 5 */ + { 0x0B07, 0x0000 }, /* R2823 - DSP2 Control 6 */ + { 0x0B08, 0x0000 }, /* R2824 - DSP2 Control 7 */ + { 0x0B09, 0x0000 }, /* R2825 - DSP2 Control 8 */ + { 0x0B0A, 0x0000 }, /* R2826 - DSP2 Control 9 */ + { 0x0B0B, 0x0000 }, /* R2827 - DSP2 Control 10 */ + { 0x0B0C, 0x0000 }, /* R2828 - DSP2 Control 11 */ + { 0x0B0D, 0x0000 }, /* R2829 - DSP2 Control 12 */ + { 0x0B0F, 0x0000 }, /* R2831 - DSP2 Control 13 */ + { 0x0B10, 0x0000 }, /* R2832 - DSP2 Control 14 */ + { 0x0B11, 0x0000 }, /* R2833 - DSP2 Control 15 */ + { 0x0B12, 0x0000 }, /* R2834 - DSP2 Control 16 */ + { 0x0B13, 0x0000 }, /* R2835 - DSP2 Control 17 */ + { 0x0B14, 0x0000 }, /* R2836 - DSP2 Control 18 */ + { 0x0B16, 0x0000 }, /* R2838 - DSP2 Control 19 */ + { 0x0B17, 0x0000 }, /* R2839 - DSP2 Control 20 */ + { 0x0B18, 0x0000 }, /* R2840 - DSP2 Control 21 */ + { 0x0B1A, 0x0800 }, /* R2842 - DSP2 Control 22 */ + { 0x0B1B, 0x1000 }, /* R2843 - DSP2 Control 23 */ + { 0x0B1C, 0x0400 }, /* R2844 - DSP2 Control 24 */ + { 0x0B1E, 0x0000 }, /* R2846 - DSP2 Control 25 */ + { 0x0B20, 0x0000 }, /* R2848 - DSP2 Control 26 */ + { 0x0B21, 0x0000 }, /* R2849 - DSP2 Control 27 */ + { 0x0B22, 0x0000 }, /* R2850 - DSP2 Control 28 */ + { 0x0B23, 0x0000 }, /* R2851 - DSP2 Control 29 */ + { 0x0B24, 0x0000 }, /* R2852 - DSP2 Control 30 */ + { 0x0B26, 0x0000 }, /* R2854 - DSP2 Control 31 */ +}; + +static bool wm2200_volatile_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm2200_ranges); i++) + if ((reg >= wm2200_ranges[i].window_start && + reg <= wm2200_ranges[i].window_start + + wm2200_ranges[i].window_len) || + (reg >= wm2200_ranges[i].range_min && + reg <= wm2200_ranges[i].range_max)) + return true; + + switch (reg) { + case WM2200_SOFTWARE_RESET: + case WM2200_DEVICE_REVISION: + case WM2200_ADPS1_IRQ0: + case WM2200_ADPS1_IRQ1: + case WM2200_INTERRUPT_STATUS_1: + case WM2200_INTERRUPT_STATUS_2: + case WM2200_INTERRUPT_RAW_STATUS_2: + return true; + default: + return false; + } +} + +static bool wm2200_readable_register(struct device *dev, unsigned int reg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm2200_ranges); i++) + if ((reg >= wm2200_ranges[i].window_start && + reg <= wm2200_ranges[i].window_start + + wm2200_ranges[i].window_len) || + (reg >= wm2200_ranges[i].range_min && + reg <= wm2200_ranges[i].range_max)) + return true; + + switch (reg) { + case WM2200_SOFTWARE_RESET: + case WM2200_DEVICE_REVISION: + case WM2200_TONE_GENERATOR_1: + case WM2200_CLOCKING_3: + case WM2200_CLOCKING_4: + case WM2200_FLL_CONTROL_1: + case WM2200_FLL_CONTROL_2: + case WM2200_FLL_CONTROL_3: + case WM2200_FLL_CONTROL_4: + case WM2200_FLL_CONTROL_6: + case WM2200_FLL_CONTROL_7: + case WM2200_FLL_EFS_1: + case WM2200_FLL_EFS_2: + case WM2200_MIC_CHARGE_PUMP_1: + case WM2200_MIC_CHARGE_PUMP_2: + case WM2200_DM_CHARGE_PUMP_1: + case WM2200_MIC_BIAS_CTRL_1: + case WM2200_MIC_BIAS_CTRL_2: + case WM2200_EAR_PIECE_CTRL_1: + case WM2200_EAR_PIECE_CTRL_2: + case WM2200_INPUT_ENABLES: + case WM2200_IN1L_CONTROL: + case WM2200_IN1R_CONTROL: + case WM2200_IN2L_CONTROL: + case WM2200_IN2R_CONTROL: + case WM2200_IN3L_CONTROL: + case WM2200_IN3R_CONTROL: + case WM2200_RXANC_SRC: + case WM2200_INPUT_VOLUME_RAMP: + case WM2200_ADC_DIGITAL_VOLUME_1L: + case WM2200_ADC_DIGITAL_VOLUME_1R: + case WM2200_ADC_DIGITAL_VOLUME_2L: + case WM2200_ADC_DIGITAL_VOLUME_2R: + case WM2200_ADC_DIGITAL_VOLUME_3L: + case WM2200_ADC_DIGITAL_VOLUME_3R: + case WM2200_OUTPUT_ENABLES: + case WM2200_DAC_VOLUME_LIMIT_1L: + case WM2200_DAC_VOLUME_LIMIT_1R: + case WM2200_DAC_VOLUME_LIMIT_2L: + case WM2200_DAC_VOLUME_LIMIT_2R: + case WM2200_DAC_AEC_CONTROL_1: + case WM2200_OUTPUT_VOLUME_RAMP: + case WM2200_DAC_DIGITAL_VOLUME_1L: + case WM2200_DAC_DIGITAL_VOLUME_1R: + case WM2200_DAC_DIGITAL_VOLUME_2L: + case WM2200_DAC_DIGITAL_VOLUME_2R: + case WM2200_PDM_1: + case WM2200_PDM_2: + case WM2200_AUDIO_IF_1_1: + case WM2200_AUDIO_IF_1_2: + case WM2200_AUDIO_IF_1_3: + case WM2200_AUDIO_IF_1_4: + case WM2200_AUDIO_IF_1_5: + case WM2200_AUDIO_IF_1_6: + case WM2200_AUDIO_IF_1_7: + case WM2200_AUDIO_IF_1_8: + case WM2200_AUDIO_IF_1_9: + case WM2200_AUDIO_IF_1_10: + case WM2200_AUDIO_IF_1_11: + case WM2200_AUDIO_IF_1_12: + case WM2200_AUDIO_IF_1_13: + case WM2200_AUDIO_IF_1_14: + case WM2200_AUDIO_IF_1_15: + case WM2200_AUDIO_IF_1_16: + case WM2200_AUDIO_IF_1_17: + case WM2200_AUDIO_IF_1_18: + case WM2200_AUDIO_IF_1_19: + case WM2200_AUDIO_IF_1_20: + case WM2200_AUDIO_IF_1_21: + case WM2200_AUDIO_IF_1_22: + case WM2200_OUT1LMIX_INPUT_1_SOURCE: + case WM2200_OUT1LMIX_INPUT_1_VOLUME: + case WM2200_OUT1LMIX_INPUT_2_SOURCE: + case WM2200_OUT1LMIX_INPUT_2_VOLUME: + case WM2200_OUT1LMIX_INPUT_3_SOURCE: + case WM2200_OUT1LMIX_INPUT_3_VOLUME: + case WM2200_OUT1LMIX_INPUT_4_SOURCE: + case WM2200_OUT1LMIX_INPUT_4_VOLUME: + case WM2200_OUT1RMIX_INPUT_1_SOURCE: + case WM2200_OUT1RMIX_INPUT_1_VOLUME: + case WM2200_OUT1RMIX_INPUT_2_SOURCE: + case WM2200_OUT1RMIX_INPUT_2_VOLUME: + case WM2200_OUT1RMIX_INPUT_3_SOURCE: + case WM2200_OUT1RMIX_INPUT_3_VOLUME: + case WM2200_OUT1RMIX_INPUT_4_SOURCE: + case WM2200_OUT1RMIX_INPUT_4_VOLUME: + case WM2200_OUT2LMIX_INPUT_1_SOURCE: + case WM2200_OUT2LMIX_INPUT_1_VOLUME: + case WM2200_OUT2LMIX_INPUT_2_SOURCE: + case WM2200_OUT2LMIX_INPUT_2_VOLUME: + case WM2200_OUT2LMIX_INPUT_3_SOURCE: + case WM2200_OUT2LMIX_INPUT_3_VOLUME: + case WM2200_OUT2LMIX_INPUT_4_SOURCE: + case WM2200_OUT2LMIX_INPUT_4_VOLUME: + case WM2200_OUT2RMIX_INPUT_1_SOURCE: + case WM2200_OUT2RMIX_INPUT_1_VOLUME: + case WM2200_OUT2RMIX_INPUT_2_SOURCE: + case WM2200_OUT2RMIX_INPUT_2_VOLUME: + case WM2200_OUT2RMIX_INPUT_3_SOURCE: + case WM2200_OUT2RMIX_INPUT_3_VOLUME: + case WM2200_OUT2RMIX_INPUT_4_SOURCE: + case WM2200_OUT2RMIX_INPUT_4_VOLUME: + case WM2200_AIF1TX1MIX_INPUT_1_SOURCE: + case WM2200_AIF1TX1MIX_INPUT_1_VOLUME: + case WM2200_AIF1TX1MIX_INPUT_2_SOURCE: + case WM2200_AIF1TX1MIX_INPUT_2_VOLUME: + case WM2200_AIF1TX1MIX_INPUT_3_SOURCE: + case WM2200_AIF1TX1MIX_INPUT_3_VOLUME: + case WM2200_AIF1TX1MIX_INPUT_4_SOURCE: + case WM2200_AIF1TX1MIX_INPUT_4_VOLUME: + case WM2200_AIF1TX2MIX_INPUT_1_SOURCE: + case WM2200_AIF1TX2MIX_INPUT_1_VOLUME: + case WM2200_AIF1TX2MIX_INPUT_2_SOURCE: + case WM2200_AIF1TX2MIX_INPUT_2_VOLUME: + case WM2200_AIF1TX2MIX_INPUT_3_SOURCE: + case WM2200_AIF1TX2MIX_INPUT_3_VOLUME: + case WM2200_AIF1TX2MIX_INPUT_4_SOURCE: + case WM2200_AIF1TX2MIX_INPUT_4_VOLUME: + case WM2200_AIF1TX3MIX_INPUT_1_SOURCE: + case WM2200_AIF1TX3MIX_INPUT_1_VOLUME: + case WM2200_AIF1TX3MIX_INPUT_2_SOURCE: + case WM2200_AIF1TX3MIX_INPUT_2_VOLUME: + case WM2200_AIF1TX3MIX_INPUT_3_SOURCE: + case WM2200_AIF1TX3MIX_INPUT_3_VOLUME: + case WM2200_AIF1TX3MIX_INPUT_4_SOURCE: + case WM2200_AIF1TX3MIX_INPUT_4_VOLUME: + case WM2200_AIF1TX4MIX_INPUT_1_SOURCE: + case WM2200_AIF1TX4MIX_INPUT_1_VOLUME: + case WM2200_AIF1TX4MIX_INPUT_2_SOURCE: + case WM2200_AIF1TX4MIX_INPUT_2_VOLUME: + case WM2200_AIF1TX4MIX_INPUT_3_SOURCE: + case WM2200_AIF1TX4MIX_INPUT_3_VOLUME: + case WM2200_AIF1TX4MIX_INPUT_4_SOURCE: + case WM2200_AIF1TX4MIX_INPUT_4_VOLUME: + case WM2200_AIF1TX5MIX_INPUT_1_SOURCE: + case WM2200_AIF1TX5MIX_INPUT_1_VOLUME: + case WM2200_AIF1TX5MIX_INPUT_2_SOURCE: + case WM2200_AIF1TX5MIX_INPUT_2_VOLUME: + case WM2200_AIF1TX5MIX_INPUT_3_SOURCE: + case WM2200_AIF1TX5MIX_INPUT_3_VOLUME: + case WM2200_AIF1TX5MIX_INPUT_4_SOURCE: + case WM2200_AIF1TX5MIX_INPUT_4_VOLUME: + case WM2200_AIF1TX6MIX_INPUT_1_SOURCE: + case WM2200_AIF1TX6MIX_INPUT_1_VOLUME: + case WM2200_AIF1TX6MIX_INPUT_2_SOURCE: + case WM2200_AIF1TX6MIX_INPUT_2_VOLUME: + case WM2200_AIF1TX6MIX_INPUT_3_SOURCE: + case WM2200_AIF1TX6MIX_INPUT_3_VOLUME: + case WM2200_AIF1TX6MIX_INPUT_4_SOURCE: + case WM2200_AIF1TX6MIX_INPUT_4_VOLUME: + case WM2200_EQLMIX_INPUT_1_SOURCE: + case WM2200_EQLMIX_INPUT_1_VOLUME: + case WM2200_EQLMIX_INPUT_2_SOURCE: + case WM2200_EQLMIX_INPUT_2_VOLUME: + case WM2200_EQLMIX_INPUT_3_SOURCE: + case WM2200_EQLMIX_INPUT_3_VOLUME: + case WM2200_EQLMIX_INPUT_4_SOURCE: + case WM2200_EQLMIX_INPUT_4_VOLUME: + case WM2200_EQRMIX_INPUT_1_SOURCE: + case WM2200_EQRMIX_INPUT_1_VOLUME: + case WM2200_EQRMIX_INPUT_2_SOURCE: + case WM2200_EQRMIX_INPUT_2_VOLUME: + case WM2200_EQRMIX_INPUT_3_SOURCE: + case WM2200_EQRMIX_INPUT_3_VOLUME: + case WM2200_EQRMIX_INPUT_4_SOURCE: + case WM2200_EQRMIX_INPUT_4_VOLUME: + case WM2200_LHPF1MIX_INPUT_1_SOURCE: + case WM2200_LHPF1MIX_INPUT_1_VOLUME: + case WM2200_LHPF1MIX_INPUT_2_SOURCE: + case WM2200_LHPF1MIX_INPUT_2_VOLUME: + case WM2200_LHPF1MIX_INPUT_3_SOURCE: + case WM2200_LHPF1MIX_INPUT_3_VOLUME: + case WM2200_LHPF1MIX_INPUT_4_SOURCE: + case WM2200_LHPF1MIX_INPUT_4_VOLUME: + case WM2200_LHPF2MIX_INPUT_1_SOURCE: + case WM2200_LHPF2MIX_INPUT_1_VOLUME: + case WM2200_LHPF2MIX_INPUT_2_SOURCE: + case WM2200_LHPF2MIX_INPUT_2_VOLUME: + case WM2200_LHPF2MIX_INPUT_3_SOURCE: + case WM2200_LHPF2MIX_INPUT_3_VOLUME: + case WM2200_LHPF2MIX_INPUT_4_SOURCE: + case WM2200_LHPF2MIX_INPUT_4_VOLUME: + case WM2200_DSP1LMIX_INPUT_1_SOURCE: + case WM2200_DSP1LMIX_INPUT_1_VOLUME: + case WM2200_DSP1LMIX_INPUT_2_SOURCE: + case WM2200_DSP1LMIX_INPUT_2_VOLUME: + case WM2200_DSP1LMIX_INPUT_3_SOURCE: + case WM2200_DSP1LMIX_INPUT_3_VOLUME: + case WM2200_DSP1LMIX_INPUT_4_SOURCE: + case WM2200_DSP1LMIX_INPUT_4_VOLUME: + case WM2200_DSP1RMIX_INPUT_1_SOURCE: + case WM2200_DSP1RMIX_INPUT_1_VOLUME: + case WM2200_DSP1RMIX_INPUT_2_SOURCE: + case WM2200_DSP1RMIX_INPUT_2_VOLUME: + case WM2200_DSP1RMIX_INPUT_3_SOURCE: + case WM2200_DSP1RMIX_INPUT_3_VOLUME: + case WM2200_DSP1RMIX_INPUT_4_SOURCE: + case WM2200_DSP1RMIX_INPUT_4_VOLUME: + case WM2200_DSP1AUX1MIX_INPUT_1_SOURCE: + case WM2200_DSP1AUX2MIX_INPUT_1_SOURCE: + case WM2200_DSP1AUX3MIX_INPUT_1_SOURCE: + case WM2200_DSP1AUX4MIX_INPUT_1_SOURCE: + case WM2200_DSP1AUX5MIX_INPUT_1_SOURCE: + case WM2200_DSP1AUX6MIX_INPUT_1_SOURCE: + case WM2200_DSP2LMIX_INPUT_1_SOURCE: + case WM2200_DSP2LMIX_INPUT_1_VOLUME: + case WM2200_DSP2LMIX_INPUT_2_SOURCE: + case WM2200_DSP2LMIX_INPUT_2_VOLUME: + case WM2200_DSP2LMIX_INPUT_3_SOURCE: + case WM2200_DSP2LMIX_INPUT_3_VOLUME: + case WM2200_DSP2LMIX_INPUT_4_SOURCE: + case WM2200_DSP2LMIX_INPUT_4_VOLUME: + case WM2200_DSP2RMIX_INPUT_1_SOURCE: + case WM2200_DSP2RMIX_INPUT_1_VOLUME: + case WM2200_DSP2RMIX_INPUT_2_SOURCE: + case WM2200_DSP2RMIX_INPUT_2_VOLUME: + case WM2200_DSP2RMIX_INPUT_3_SOURCE: + case WM2200_DSP2RMIX_INPUT_3_VOLUME: + case WM2200_DSP2RMIX_INPUT_4_SOURCE: + case WM2200_DSP2RMIX_INPUT_4_VOLUME: + case WM2200_DSP2AUX1MIX_INPUT_1_SOURCE: + case WM2200_DSP2AUX2MIX_INPUT_1_SOURCE: + case WM2200_DSP2AUX3MIX_INPUT_1_SOURCE: + case WM2200_DSP2AUX4MIX_INPUT_1_SOURCE: + case WM2200_DSP2AUX5MIX_INPUT_1_SOURCE: + case WM2200_DSP2AUX6MIX_INPUT_1_SOURCE: + case WM2200_GPIO_CTRL_1: + case WM2200_GPIO_CTRL_2: + case WM2200_GPIO_CTRL_3: + case WM2200_GPIO_CTRL_4: + case WM2200_ADPS1_IRQ0: + case WM2200_ADPS1_IRQ1: + case WM2200_MISC_PAD_CTRL_1: + case WM2200_INTERRUPT_STATUS_1: + case WM2200_INTERRUPT_STATUS_1_MASK: + case WM2200_INTERRUPT_STATUS_2: + case WM2200_INTERRUPT_RAW_STATUS_2: + case WM2200_INTERRUPT_STATUS_2_MASK: + case WM2200_INTERRUPT_CONTROL: + case WM2200_EQL_1: + case WM2200_EQL_2: + case WM2200_EQL_3: + case WM2200_EQL_4: + case WM2200_EQL_5: + case WM2200_EQL_6: + case WM2200_EQL_7: + case WM2200_EQL_8: + case WM2200_EQL_9: + case WM2200_EQL_10: + case WM2200_EQL_11: + case WM2200_EQL_12: + case WM2200_EQL_13: + case WM2200_EQL_14: + case WM2200_EQL_15: + case WM2200_EQL_16: + case WM2200_EQL_17: + case WM2200_EQL_18: + case WM2200_EQL_19: + case WM2200_EQL_20: + case WM2200_EQR_1: + case WM2200_EQR_2: + case WM2200_EQR_3: + case WM2200_EQR_4: + case WM2200_EQR_5: + case WM2200_EQR_6: + case WM2200_EQR_7: + case WM2200_EQR_8: + case WM2200_EQR_9: + case WM2200_EQR_10: + case WM2200_EQR_11: + case WM2200_EQR_12: + case WM2200_EQR_13: + case WM2200_EQR_14: + case WM2200_EQR_15: + case WM2200_EQR_16: + case WM2200_EQR_17: + case WM2200_EQR_18: + case WM2200_EQR_19: + case WM2200_EQR_20: + case WM2200_HPLPF1_1: + case WM2200_HPLPF1_2: + case WM2200_HPLPF2_1: + case WM2200_HPLPF2_2: + case WM2200_DSP1_CONTROL_1: + case WM2200_DSP1_CONTROL_2: + case WM2200_DSP1_CONTROL_3: + case WM2200_DSP1_CONTROL_4: + case WM2200_DSP1_CONTROL_5: + case WM2200_DSP1_CONTROL_6: + case WM2200_DSP1_CONTROL_7: + case WM2200_DSP1_CONTROL_8: + case WM2200_DSP1_CONTROL_9: + case WM2200_DSP1_CONTROL_10: + case WM2200_DSP1_CONTROL_11: + case WM2200_DSP1_CONTROL_12: + case WM2200_DSP1_CONTROL_13: + case WM2200_DSP1_CONTROL_14: + case WM2200_DSP1_CONTROL_15: + case WM2200_DSP1_CONTROL_16: + case WM2200_DSP1_CONTROL_17: + case WM2200_DSP1_CONTROL_18: + case WM2200_DSP1_CONTROL_19: + case WM2200_DSP1_CONTROL_20: + case WM2200_DSP1_CONTROL_21: + case WM2200_DSP1_CONTROL_22: + case WM2200_DSP1_CONTROL_23: + case WM2200_DSP1_CONTROL_24: + case WM2200_DSP1_CONTROL_25: + case WM2200_DSP1_CONTROL_26: + case WM2200_DSP1_CONTROL_27: + case WM2200_DSP1_CONTROL_28: + case WM2200_DSP1_CONTROL_29: + case WM2200_DSP1_CONTROL_30: + case WM2200_DSP1_CONTROL_31: + case WM2200_DSP2_CONTROL_1: + case WM2200_DSP2_CONTROL_2: + case WM2200_DSP2_CONTROL_3: + case WM2200_DSP2_CONTROL_4: + case WM2200_DSP2_CONTROL_5: + case WM2200_DSP2_CONTROL_6: + case WM2200_DSP2_CONTROL_7: + case WM2200_DSP2_CONTROL_8: + case WM2200_DSP2_CONTROL_9: + case WM2200_DSP2_CONTROL_10: + case WM2200_DSP2_CONTROL_11: + case WM2200_DSP2_CONTROL_12: + case WM2200_DSP2_CONTROL_13: + case WM2200_DSP2_CONTROL_14: + case WM2200_DSP2_CONTROL_15: + case WM2200_DSP2_CONTROL_16: + case WM2200_DSP2_CONTROL_17: + case WM2200_DSP2_CONTROL_18: + case WM2200_DSP2_CONTROL_19: + case WM2200_DSP2_CONTROL_20: + case WM2200_DSP2_CONTROL_21: + case WM2200_DSP2_CONTROL_22: + case WM2200_DSP2_CONTROL_23: + case WM2200_DSP2_CONTROL_24: + case WM2200_DSP2_CONTROL_25: + case WM2200_DSP2_CONTROL_26: + case WM2200_DSP2_CONTROL_27: + case WM2200_DSP2_CONTROL_28: + case WM2200_DSP2_CONTROL_29: + case WM2200_DSP2_CONTROL_30: + case WM2200_DSP2_CONTROL_31: + return true; + default: + return false; + } +} + +static const struct reg_sequence wm2200_reva_patch[] = { + { 0x07, 0x0003 }, + { 0x102, 0x0200 }, + { 0x203, 0x0084 }, + { 0x201, 0x83FF }, + { 0x20C, 0x0062 }, + { 0x20D, 0x0062 }, + { 0x207, 0x2002 }, + { 0x208, 0x20C0 }, + { 0x21D, 0x01C0 }, + { 0x50A, 0x0001 }, + { 0x50B, 0x0002 }, + { 0x50C, 0x0003 }, + { 0x50D, 0x0004 }, + { 0x50E, 0x0005 }, + { 0x510, 0x0001 }, + { 0x511, 0x0002 }, + { 0x512, 0x0003 }, + { 0x513, 0x0004 }, + { 0x514, 0x0005 }, + { 0x515, 0x0000 }, + { 0x201, 0x8084 }, + { 0x202, 0xBBDE }, + { 0x203, 0x00EC }, + { 0x500, 0x8000 }, + { 0x507, 0x1820 }, + { 0x508, 0x1820 }, + { 0x505, 0x0300 }, + { 0x506, 0x0300 }, + { 0x302, 0x2280 }, + { 0x303, 0x0080 }, + { 0x304, 0x2280 }, + { 0x305, 0x0080 }, + { 0x306, 0x2280 }, + { 0x307, 0x0080 }, + { 0x401, 0x0080 }, + { 0x402, 0x0080 }, + { 0x417, 0x3069 }, + { 0x900, 0x6318 }, + { 0x901, 0x6300 }, + { 0x902, 0x0FC8 }, + { 0x903, 0x03FE }, + { 0x904, 0x00E0 }, + { 0x905, 0x1EC4 }, + { 0x906, 0xF136 }, + { 0x907, 0x0409 }, + { 0x908, 0x04CC }, + { 0x909, 0x1C9B }, + { 0x90A, 0xF337 }, + { 0x90B, 0x040B }, + { 0x90C, 0x0CBB }, + { 0x90D, 0x16F8 }, + { 0x90E, 0xF7D9 }, + { 0x90F, 0x040A }, + { 0x910, 0x1F14 }, + { 0x911, 0x058C }, + { 0x912, 0x0563 }, + { 0x913, 0x4000 }, + { 0x916, 0x6318 }, + { 0x917, 0x6300 }, + { 0x918, 0x0FC8 }, + { 0x919, 0x03FE }, + { 0x91A, 0x00E0 }, + { 0x91B, 0x1EC4 }, + { 0x91C, 0xF136 }, + { 0x91D, 0x0409 }, + { 0x91E, 0x04CC }, + { 0x91F, 0x1C9B }, + { 0x920, 0xF337 }, + { 0x921, 0x040B }, + { 0x922, 0x0CBB }, + { 0x923, 0x16F8 }, + { 0x924, 0xF7D9 }, + { 0x925, 0x040A }, + { 0x926, 0x1F14 }, + { 0x927, 0x058C }, + { 0x928, 0x0563 }, + { 0x929, 0x4000 }, + { 0x709, 0x2000 }, + { 0x207, 0x200E }, + { 0x208, 0x20D4 }, + { 0x20A, 0x0080 }, + { 0x07, 0x0000 }, +}; + +static int wm2200_reset(struct wm2200_priv *wm2200) +{ + if (wm2200->pdata.reset) { + gpio_set_value_cansleep(wm2200->pdata.reset, 0); + gpio_set_value_cansleep(wm2200->pdata.reset, 1); + + return 0; + } else { + return regmap_write(wm2200->regmap, WM2200_SOFTWARE_RESET, + 0x2200); + } +} + +static DECLARE_TLV_DB_SCALE(in_tlv, -6300, 100, 0); +static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); +static DECLARE_TLV_DB_SCALE(out_tlv, -6400, 100, 0); + +static const char * const wm2200_mixer_texts[] = { + "None", + "Tone Generator", + "AEC Loopback", + "IN1L", + "IN1R", + "IN2L", + "IN2R", + "IN3L", + "IN3R", + "AIF1RX1", + "AIF1RX2", + "AIF1RX3", + "AIF1RX4", + "AIF1RX5", + "AIF1RX6", + "EQL", + "EQR", + "LHPF1", + "LHPF2", + "DSP1.1", + "DSP1.2", + "DSP1.3", + "DSP1.4", + "DSP1.5", + "DSP1.6", + "DSP2.1", + "DSP2.2", + "DSP2.3", + "DSP2.4", + "DSP2.5", + "DSP2.6", +}; + +static unsigned int wm2200_mixer_values[] = { + 0x00, + 0x04, /* Tone */ + 0x08, /* AEC */ + 0x10, /* Input */ + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x20, /* AIF */ + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x50, /* EQ */ + 0x51, + 0x60, /* LHPF1 */ + 0x61, /* LHPF2 */ + 0x68, /* DSP1 */ + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x70, /* DSP2 */ + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, +}; + +#define WM2200_MIXER_CONTROLS(name, base) \ + SOC_SINGLE_TLV(name " Input 1 Volume", base + 1 , \ + WM2200_MIXER_VOL_SHIFT, 80, 0, mixer_tlv), \ + SOC_SINGLE_TLV(name " Input 2 Volume", base + 3 , \ + WM2200_MIXER_VOL_SHIFT, 80, 0, mixer_tlv), \ + SOC_SINGLE_TLV(name " Input 3 Volume", base + 5 , \ + WM2200_MIXER_VOL_SHIFT, 80, 0, mixer_tlv), \ + SOC_SINGLE_TLV(name " Input 4 Volume", base + 7 , \ + WM2200_MIXER_VOL_SHIFT, 80, 0, mixer_tlv) + +#define WM2200_MUX_ENUM_DECL(name, reg) \ + SOC_VALUE_ENUM_SINGLE_DECL(name, reg, 0, 0xff, \ + wm2200_mixer_texts, wm2200_mixer_values) + +#define WM2200_MUX_CTL_DECL(name) \ + const struct snd_kcontrol_new name##_mux = \ + SOC_DAPM_ENUM("Route", name##_enum) + +#define WM2200_MIXER_ENUMS(name, base_reg) \ + static WM2200_MUX_ENUM_DECL(name##_in1_enum, base_reg); \ + static WM2200_MUX_ENUM_DECL(name##_in2_enum, base_reg + 2); \ + static WM2200_MUX_ENUM_DECL(name##_in3_enum, base_reg + 4); \ + static WM2200_MUX_ENUM_DECL(name##_in4_enum, base_reg + 6); \ + static WM2200_MUX_CTL_DECL(name##_in1); \ + static WM2200_MUX_CTL_DECL(name##_in2); \ + static WM2200_MUX_CTL_DECL(name##_in3); \ + static WM2200_MUX_CTL_DECL(name##_in4) + +#define WM2200_DSP_ENUMS(name, base_reg) \ + static WM2200_MUX_ENUM_DECL(name##_aux1_enum, base_reg); \ + static WM2200_MUX_ENUM_DECL(name##_aux2_enum, base_reg + 1); \ + static WM2200_MUX_ENUM_DECL(name##_aux3_enum, base_reg + 2); \ + static WM2200_MUX_ENUM_DECL(name##_aux4_enum, base_reg + 3); \ + static WM2200_MUX_ENUM_DECL(name##_aux5_enum, base_reg + 4); \ + static WM2200_MUX_ENUM_DECL(name##_aux6_enum, base_reg + 5); \ + static WM2200_MUX_CTL_DECL(name##_aux1); \ + static WM2200_MUX_CTL_DECL(name##_aux2); \ + static WM2200_MUX_CTL_DECL(name##_aux3); \ + static WM2200_MUX_CTL_DECL(name##_aux4); \ + static WM2200_MUX_CTL_DECL(name##_aux5); \ + static WM2200_MUX_CTL_DECL(name##_aux6); + +static const char *wm2200_rxanc_input_sel_texts[] = { + "None", "IN1", "IN2", "IN3", +}; + +static SOC_ENUM_SINGLE_DECL(wm2200_rxanc_input_sel, + WM2200_RXANC_SRC, + WM2200_IN_RXANC_SEL_SHIFT, + wm2200_rxanc_input_sel_texts); + +static const struct snd_kcontrol_new wm2200_snd_controls[] = { +SOC_SINGLE("IN1 High Performance Switch", WM2200_IN1L_CONTROL, + WM2200_IN1_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN2 High Performance Switch", WM2200_IN2L_CONTROL, + WM2200_IN2_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN3 High Performance Switch", WM2200_IN3L_CONTROL, + WM2200_IN3_OSR_SHIFT, 1, 0), + +SOC_DOUBLE_R_TLV("IN1 Volume", WM2200_IN1L_CONTROL, WM2200_IN1R_CONTROL, + WM2200_IN1L_PGA_VOL_SHIFT, 0x5f, 0, in_tlv), +SOC_DOUBLE_R_TLV("IN2 Volume", WM2200_IN2L_CONTROL, WM2200_IN2R_CONTROL, + WM2200_IN2L_PGA_VOL_SHIFT, 0x5f, 0, in_tlv), +SOC_DOUBLE_R_TLV("IN3 Volume", WM2200_IN3L_CONTROL, WM2200_IN3R_CONTROL, + WM2200_IN3L_PGA_VOL_SHIFT, 0x5f, 0, in_tlv), + +SOC_DOUBLE_R("IN1 Digital Switch", WM2200_ADC_DIGITAL_VOLUME_1L, + WM2200_ADC_DIGITAL_VOLUME_1R, WM2200_IN1L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("IN2 Digital Switch", WM2200_ADC_DIGITAL_VOLUME_2L, + WM2200_ADC_DIGITAL_VOLUME_2R, WM2200_IN2L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("IN3 Digital Switch", WM2200_ADC_DIGITAL_VOLUME_3L, + WM2200_ADC_DIGITAL_VOLUME_3R, WM2200_IN3L_MUTE_SHIFT, 1, 1), + +SOC_DOUBLE_R_TLV("IN1 Digital Volume", WM2200_ADC_DIGITAL_VOLUME_1L, + WM2200_ADC_DIGITAL_VOLUME_1R, WM2200_IN1L_DIG_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("IN2 Digital Volume", WM2200_ADC_DIGITAL_VOLUME_2L, + WM2200_ADC_DIGITAL_VOLUME_2R, WM2200_IN2L_DIG_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("IN3 Digital Volume", WM2200_ADC_DIGITAL_VOLUME_3L, + WM2200_ADC_DIGITAL_VOLUME_3R, WM2200_IN3L_DIG_VOL_SHIFT, + 0xbf, 0, digital_tlv), + +SND_SOC_BYTES_MASK("EQL Coefficients", WM2200_EQL_1, 20, WM2200_EQL_ENA), +SND_SOC_BYTES_MASK("EQR Coefficients", WM2200_EQR_1, 20, WM2200_EQR_ENA), + +SND_SOC_BYTES("LHPF1 Coefficients", WM2200_HPLPF1_2, 1), +SND_SOC_BYTES("LHPF2 Coefficients", WM2200_HPLPF2_2, 1), + +SOC_SINGLE("OUT1 High Performance Switch", WM2200_DAC_DIGITAL_VOLUME_1L, + WM2200_OUT1_OSR_SHIFT, 1, 0), +SOC_SINGLE("OUT2 High Performance Switch", WM2200_DAC_DIGITAL_VOLUME_2L, + WM2200_OUT2_OSR_SHIFT, 1, 0), + +SOC_DOUBLE_R("OUT1 Digital Switch", WM2200_DAC_DIGITAL_VOLUME_1L, + WM2200_DAC_DIGITAL_VOLUME_1R, WM2200_OUT1L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R_TLV("OUT1 Digital Volume", WM2200_DAC_DIGITAL_VOLUME_1L, + WM2200_DAC_DIGITAL_VOLUME_1R, WM2200_OUT1L_VOL_SHIFT, 0x9f, 0, + digital_tlv), +SOC_DOUBLE_R_TLV("OUT1 Volume", WM2200_DAC_VOLUME_LIMIT_1L, + WM2200_DAC_VOLUME_LIMIT_1R, WM2200_OUT1L_PGA_VOL_SHIFT, + 0x46, 0, out_tlv), + +SOC_DOUBLE_R("OUT2 Digital Switch", WM2200_DAC_DIGITAL_VOLUME_2L, + WM2200_DAC_DIGITAL_VOLUME_2R, WM2200_OUT2L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R_TLV("OUT2 Digital Volume", WM2200_DAC_DIGITAL_VOLUME_2L, + WM2200_DAC_DIGITAL_VOLUME_2R, WM2200_OUT2L_VOL_SHIFT, 0x9f, 0, + digital_tlv), +SOC_DOUBLE("OUT2 Switch", WM2200_PDM_1, WM2200_SPK1L_MUTE_SHIFT, + WM2200_SPK1R_MUTE_SHIFT, 1, 1), +SOC_ENUM("RxANC Src", wm2200_rxanc_input_sel), + +WM_ADSP_FW_CONTROL("DSP1", 0), +WM_ADSP_FW_CONTROL("DSP2", 1), +}; + +WM2200_MIXER_ENUMS(OUT1L, WM2200_OUT1LMIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(OUT1R, WM2200_OUT1RMIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(OUT2L, WM2200_OUT2LMIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(OUT2R, WM2200_OUT2RMIX_INPUT_1_SOURCE); + +WM2200_MIXER_ENUMS(AIF1TX1, WM2200_AIF1TX1MIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(AIF1TX2, WM2200_AIF1TX2MIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(AIF1TX3, WM2200_AIF1TX3MIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(AIF1TX4, WM2200_AIF1TX4MIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(AIF1TX5, WM2200_AIF1TX5MIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(AIF1TX6, WM2200_AIF1TX6MIX_INPUT_1_SOURCE); + +WM2200_MIXER_ENUMS(EQL, WM2200_EQLMIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(EQR, WM2200_EQRMIX_INPUT_1_SOURCE); + +WM2200_MIXER_ENUMS(DSP1L, WM2200_DSP1LMIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(DSP1R, WM2200_DSP1RMIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(DSP2L, WM2200_DSP2LMIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(DSP2R, WM2200_DSP2RMIX_INPUT_1_SOURCE); + +WM2200_DSP_ENUMS(DSP1, WM2200_DSP1AUX1MIX_INPUT_1_SOURCE); +WM2200_DSP_ENUMS(DSP2, WM2200_DSP2AUX1MIX_INPUT_1_SOURCE); + +WM2200_MIXER_ENUMS(LHPF1, WM2200_LHPF1MIX_INPUT_1_SOURCE); +WM2200_MIXER_ENUMS(LHPF2, WM2200_LHPF2MIX_INPUT_1_SOURCE); + +#define WM2200_MUX(name, ctrl) \ + SND_SOC_DAPM_MUX(name, SND_SOC_NOPM, 0, 0, ctrl) + +#define WM2200_MIXER_WIDGETS(name, name_str) \ + WM2200_MUX(name_str " Input 1", &name##_in1_mux), \ + WM2200_MUX(name_str " Input 2", &name##_in2_mux), \ + WM2200_MUX(name_str " Input 3", &name##_in3_mux), \ + WM2200_MUX(name_str " Input 4", &name##_in4_mux), \ + SND_SOC_DAPM_MIXER(name_str " Mixer", SND_SOC_NOPM, 0, 0, NULL, 0) + +#define WM2200_DSP_WIDGETS(name, name_str) \ + WM2200_MIXER_WIDGETS(name##L, name_str "L"), \ + WM2200_MIXER_WIDGETS(name##R, name_str "R"), \ + WM2200_MUX(name_str " Aux 1", &name##_aux1_mux), \ + WM2200_MUX(name_str " Aux 2", &name##_aux2_mux), \ + WM2200_MUX(name_str " Aux 3", &name##_aux3_mux), \ + WM2200_MUX(name_str " Aux 4", &name##_aux4_mux), \ + WM2200_MUX(name_str " Aux 5", &name##_aux5_mux), \ + WM2200_MUX(name_str " Aux 6", &name##_aux6_mux) + +#define WM2200_MIXER_INPUT_ROUTES(name) \ + { name, "Tone Generator", "Tone Generator" }, \ + { name, "AEC Loopback", "AEC Loopback" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2L PGA" }, \ + { name, "IN2R", "IN2R PGA" }, \ + { name, "IN3L", "IN3L PGA" }, \ + { name, "IN3R", "IN3R PGA" }, \ + { name, "DSP1.1", "DSP1" }, \ + { name, "DSP1.2", "DSP1" }, \ + { name, "DSP1.3", "DSP1" }, \ + { name, "DSP1.4", "DSP1" }, \ + { name, "DSP1.5", "DSP1" }, \ + { name, "DSP1.6", "DSP1" }, \ + { name, "DSP2.1", "DSP2" }, \ + { name, "DSP2.2", "DSP2" }, \ + { name, "DSP2.3", "DSP2" }, \ + { name, "DSP2.4", "DSP2" }, \ + { name, "DSP2.5", "DSP2" }, \ + { name, "DSP2.6", "DSP2" }, \ + { name, "AIF1RX1", "AIF1RX1" }, \ + { name, "AIF1RX2", "AIF1RX2" }, \ + { name, "AIF1RX3", "AIF1RX3" }, \ + { name, "AIF1RX4", "AIF1RX4" }, \ + { name, "AIF1RX5", "AIF1RX5" }, \ + { name, "AIF1RX6", "AIF1RX6" }, \ + { name, "EQL", "EQL" }, \ + { name, "EQR", "EQR" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" } + +#define WM2200_MIXER_ROUTES(widget, name) \ + { widget, NULL, name " Mixer" }, \ + { name " Mixer", NULL, name " Input 1" }, \ + { name " Mixer", NULL, name " Input 2" }, \ + { name " Mixer", NULL, name " Input 3" }, \ + { name " Mixer", NULL, name " Input 4" }, \ + WM2200_MIXER_INPUT_ROUTES(name " Input 1"), \ + WM2200_MIXER_INPUT_ROUTES(name " Input 2"), \ + WM2200_MIXER_INPUT_ROUTES(name " Input 3"), \ + WM2200_MIXER_INPUT_ROUTES(name " Input 4") + +#define WM2200_DSP_AUX_ROUTES(name) \ + { name, NULL, name " Aux 1" }, \ + { name, NULL, name " Aux 2" }, \ + { name, NULL, name " Aux 3" }, \ + { name, NULL, name " Aux 4" }, \ + { name, NULL, name " Aux 5" }, \ + { name, NULL, name " Aux 6" }, \ + WM2200_MIXER_INPUT_ROUTES(name " Aux 1"), \ + WM2200_MIXER_INPUT_ROUTES(name " Aux 2"), \ + WM2200_MIXER_INPUT_ROUTES(name " Aux 3"), \ + WM2200_MIXER_INPUT_ROUTES(name " Aux 4"), \ + WM2200_MIXER_INPUT_ROUTES(name " Aux 5"), \ + WM2200_MIXER_INPUT_ROUTES(name " Aux 6") + +static const char *wm2200_aec_loopback_texts[] = { + "OUT1L", "OUT1R", "OUT2L", "OUT2R", +}; + +static SOC_ENUM_SINGLE_DECL(wm2200_aec_loopback, + WM2200_DAC_AEC_CONTROL_1, + WM2200_AEC_LOOPBACK_SRC_SHIFT, + wm2200_aec_loopback_texts); + +static const struct snd_kcontrol_new wm2200_aec_loopback_mux = + SOC_DAPM_ENUM("AEC Loopback", wm2200_aec_loopback); + +static const struct snd_soc_dapm_widget wm2200_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", WM2200_CLOCKING_3, WM2200_SYSCLK_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_SUPPLY("CP1", WM2200_DM_CHARGE_PUMP_1, WM2200_CPDM_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_SUPPLY("CP2", WM2200_MIC_CHARGE_PUMP_1, WM2200_CPMIC_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS1", WM2200_MIC_BIAS_CTRL_1, WM2200_MICB1_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", WM2200_MIC_BIAS_CTRL_2, WM2200_MICB2_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("AVDD", 20, 0), + +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), + +SND_SOC_DAPM_SIGGEN("TONE"), +SND_SOC_DAPM_PGA("Tone Generator", WM2200_TONE_GENERATOR_1, + WM2200_TONE_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("IN1L PGA", WM2200_INPUT_ENABLES, WM2200_IN1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("IN1R PGA", WM2200_INPUT_ENABLES, WM2200_IN1R_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("IN2L PGA", WM2200_INPUT_ENABLES, WM2200_IN2L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("IN2R PGA", WM2200_INPUT_ENABLES, WM2200_IN2R_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("IN3L PGA", WM2200_INPUT_ENABLES, WM2200_IN3L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("IN3R PGA", WM2200_INPUT_ENABLES, WM2200_IN3R_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX1", "Playback", 0, + WM2200_AUDIO_IF_1_22, WM2200_AIF1RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", "Playback", 1, + WM2200_AUDIO_IF_1_22, WM2200_AIF1RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", "Playback", 2, + WM2200_AUDIO_IF_1_22, WM2200_AIF1RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", "Playback", 3, + WM2200_AUDIO_IF_1_22, WM2200_AIF1RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX5", "Playback", 4, + WM2200_AUDIO_IF_1_22, WM2200_AIF1RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX6", "Playback", 5, + WM2200_AUDIO_IF_1_22, WM2200_AIF1RX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_PGA("EQL", WM2200_EQL_1, WM2200_EQL_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQR", WM2200_EQR_1, WM2200_EQR_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", WM2200_HPLPF1_1, WM2200_LHPF1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", WM2200_HPLPF2_1, WM2200_LHPF2_ENA_SHIFT, 0, + NULL, 0), + +WM_ADSP1("DSP1", 0), +WM_ADSP1("DSP2", 1), + +SND_SOC_DAPM_AIF_OUT("AIF1TX1", "Capture", 0, + WM2200_AUDIO_IF_1_22, WM2200_AIF1TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", "Capture", 1, + WM2200_AUDIO_IF_1_22, WM2200_AIF1TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", "Capture", 2, + WM2200_AUDIO_IF_1_22, WM2200_AIF1TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", "Capture", 3, + WM2200_AUDIO_IF_1_22, WM2200_AIF1TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX5", "Capture", 4, + WM2200_AUDIO_IF_1_22, WM2200_AIF1TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX6", "Capture", 5, + WM2200_AUDIO_IF_1_22, WM2200_AIF1TX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_MUX("AEC Loopback", WM2200_DAC_AEC_CONTROL_1, + WM2200_AEC_LOOPBACK_ENA_SHIFT, 0, &wm2200_aec_loopback_mux), + +SND_SOC_DAPM_PGA_S("OUT1L", 0, WM2200_OUTPUT_ENABLES, + WM2200_OUT1L_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("OUT1R", 0, WM2200_OUTPUT_ENABLES, + WM2200_OUT1R_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("EPD_LP", 1, WM2200_EAR_PIECE_CTRL_1, + WM2200_EPD_LP_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_OUTP_LP", 1, WM2200_EAR_PIECE_CTRL_1, + WM2200_EPD_OUTP_LP_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_RMV_SHRT_LP", 1, WM2200_EAR_PIECE_CTRL_1, + WM2200_EPD_RMV_SHRT_LP_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("EPD_LN", 1, WM2200_EAR_PIECE_CTRL_1, + WM2200_EPD_LN_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_OUTP_LN", 1, WM2200_EAR_PIECE_CTRL_1, + WM2200_EPD_OUTP_LN_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_RMV_SHRT_LN", 1, WM2200_EAR_PIECE_CTRL_1, + WM2200_EPD_RMV_SHRT_LN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("EPD_RP", 1, WM2200_EAR_PIECE_CTRL_2, + WM2200_EPD_RP_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_OUTP_RP", 1, WM2200_EAR_PIECE_CTRL_2, + WM2200_EPD_OUTP_RP_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_RMV_SHRT_RP", 1, WM2200_EAR_PIECE_CTRL_2, + WM2200_EPD_RMV_SHRT_RP_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("EPD_RN", 1, WM2200_EAR_PIECE_CTRL_2, + WM2200_EPD_RN_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_OUTP_RN", 1, WM2200_EAR_PIECE_CTRL_2, + WM2200_EPD_OUTP_RN_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("EPD_RMV_SHRT_RN", 1, WM2200_EAR_PIECE_CTRL_2, + WM2200_EPD_RMV_SHRT_RN_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("OUT2L", WM2200_OUTPUT_ENABLES, WM2200_OUT2L_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_PGA("OUT2R", WM2200_OUTPUT_ENABLES, WM2200_OUT2R_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("EPOUTLN"), +SND_SOC_DAPM_OUTPUT("EPOUTLP"), +SND_SOC_DAPM_OUTPUT("EPOUTRN"), +SND_SOC_DAPM_OUTPUT("EPOUTRP"), +SND_SOC_DAPM_OUTPUT("SPK"), + +WM2200_MIXER_WIDGETS(EQL, "EQL"), +WM2200_MIXER_WIDGETS(EQR, "EQR"), + +WM2200_MIXER_WIDGETS(LHPF1, "LHPF1"), +WM2200_MIXER_WIDGETS(LHPF2, "LHPF2"), + +WM2200_DSP_WIDGETS(DSP1, "DSP1"), +WM2200_DSP_WIDGETS(DSP2, "DSP2"), + +WM2200_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"), +WM2200_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"), +WM2200_MIXER_WIDGETS(AIF1TX3, "AIF1TX3"), +WM2200_MIXER_WIDGETS(AIF1TX4, "AIF1TX4"), +WM2200_MIXER_WIDGETS(AIF1TX5, "AIF1TX5"), +WM2200_MIXER_WIDGETS(AIF1TX6, "AIF1TX6"), + +WM2200_MIXER_WIDGETS(OUT1L, "OUT1L"), +WM2200_MIXER_WIDGETS(OUT1R, "OUT1R"), +WM2200_MIXER_WIDGETS(OUT2L, "OUT2L"), +WM2200_MIXER_WIDGETS(OUT2R, "OUT2R"), +}; + +static const struct snd_soc_dapm_route wm2200_dapm_routes[] = { + /* Everything needs SYSCLK but only hook up things on the edge + * of the chip */ + { "IN1L", NULL, "SYSCLK" }, + { "IN1R", NULL, "SYSCLK" }, + { "IN2L", NULL, "SYSCLK" }, + { "IN2R", NULL, "SYSCLK" }, + { "IN3L", NULL, "SYSCLK" }, + { "IN3R", NULL, "SYSCLK" }, + { "OUT1L", NULL, "SYSCLK" }, + { "OUT1R", NULL, "SYSCLK" }, + { "OUT2L", NULL, "SYSCLK" }, + { "OUT2R", NULL, "SYSCLK" }, + { "AIF1RX1", NULL, "SYSCLK" }, + { "AIF1RX2", NULL, "SYSCLK" }, + { "AIF1RX3", NULL, "SYSCLK" }, + { "AIF1RX4", NULL, "SYSCLK" }, + { "AIF1RX5", NULL, "SYSCLK" }, + { "AIF1RX6", NULL, "SYSCLK" }, + { "AIF1TX1", NULL, "SYSCLK" }, + { "AIF1TX2", NULL, "SYSCLK" }, + { "AIF1TX3", NULL, "SYSCLK" }, + { "AIF1TX4", NULL, "SYSCLK" }, + { "AIF1TX5", NULL, "SYSCLK" }, + { "AIF1TX6", NULL, "SYSCLK" }, + + { "IN1L", NULL, "AVDD" }, + { "IN1R", NULL, "AVDD" }, + { "IN2L", NULL, "AVDD" }, + { "IN2R", NULL, "AVDD" }, + { "IN3L", NULL, "AVDD" }, + { "IN3R", NULL, "AVDD" }, + { "OUT1L", NULL, "AVDD" }, + { "OUT1R", NULL, "AVDD" }, + + { "IN1L PGA", NULL, "IN1L" }, + { "IN1R PGA", NULL, "IN1R" }, + { "IN2L PGA", NULL, "IN2L" }, + { "IN2R PGA", NULL, "IN2R" }, + { "IN3L PGA", NULL, "IN3L" }, + { "IN3R PGA", NULL, "IN3R" }, + + { "Tone Generator", NULL, "TONE" }, + + { "CP2", NULL, "CPVDD" }, + { "MICBIAS1", NULL, "CP2" }, + { "MICBIAS2", NULL, "CP2" }, + + { "CP1", NULL, "CPVDD" }, + { "EPD_LN", NULL, "CP1" }, + { "EPD_LP", NULL, "CP1" }, + { "EPD_RN", NULL, "CP1" }, + { "EPD_RP", NULL, "CP1" }, + + { "EPD_LP", NULL, "OUT1L" }, + { "EPD_OUTP_LP", NULL, "EPD_LP" }, + { "EPD_RMV_SHRT_LP", NULL, "EPD_OUTP_LP" }, + { "EPOUTLP", NULL, "EPD_RMV_SHRT_LP" }, + + { "EPD_LN", NULL, "OUT1L" }, + { "EPD_OUTP_LN", NULL, "EPD_LN" }, + { "EPD_RMV_SHRT_LN", NULL, "EPD_OUTP_LN" }, + { "EPOUTLN", NULL, "EPD_RMV_SHRT_LN" }, + + { "EPD_RP", NULL, "OUT1R" }, + { "EPD_OUTP_RP", NULL, "EPD_RP" }, + { "EPD_RMV_SHRT_RP", NULL, "EPD_OUTP_RP" }, + { "EPOUTRP", NULL, "EPD_RMV_SHRT_RP" }, + + { "EPD_RN", NULL, "OUT1R" }, + { "EPD_OUTP_RN", NULL, "EPD_RN" }, + { "EPD_RMV_SHRT_RN", NULL, "EPD_OUTP_RN" }, + { "EPOUTRN", NULL, "EPD_RMV_SHRT_RN" }, + + { "SPK", NULL, "OUT2L" }, + { "SPK", NULL, "OUT2R" }, + + { "AEC Loopback", "OUT1L", "OUT1L" }, + { "AEC Loopback", "OUT1R", "OUT1R" }, + { "AEC Loopback", "OUT2L", "OUT2L" }, + { "AEC Loopback", "OUT2R", "OUT2R" }, + + WM2200_MIXER_ROUTES("DSP1", "DSP1L"), + WM2200_MIXER_ROUTES("DSP1", "DSP1R"), + WM2200_MIXER_ROUTES("DSP2", "DSP2L"), + WM2200_MIXER_ROUTES("DSP2", "DSP2R"), + + WM2200_DSP_AUX_ROUTES("DSP1"), + WM2200_DSP_AUX_ROUTES("DSP2"), + + WM2200_MIXER_ROUTES("OUT1L", "OUT1L"), + WM2200_MIXER_ROUTES("OUT1R", "OUT1R"), + WM2200_MIXER_ROUTES("OUT2L", "OUT2L"), + WM2200_MIXER_ROUTES("OUT2R", "OUT2R"), + + WM2200_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), + WM2200_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), + WM2200_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), + WM2200_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), + WM2200_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), + WM2200_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), + + WM2200_MIXER_ROUTES("EQL", "EQL"), + WM2200_MIXER_ROUTES("EQR", "EQR"), + + WM2200_MIXER_ROUTES("LHPF1", "LHPF1"), + WM2200_MIXER_ROUTES("LHPF2", "LHPF2"), +}; + +static int wm2200_probe(struct snd_soc_component *component) +{ + struct wm2200_priv *wm2200 = snd_soc_component_get_drvdata(component); + + wm2200->component = component; + + return 0; +} + +static int wm2200_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + int lrclk, bclk, fmt_val; + + lrclk = 0; + bclk = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + fmt_val = 0; + break; + case SND_SOC_DAIFMT_I2S: + fmt_val = 2; + break; + default: + dev_err(component->dev, "Unsupported DAI format %d\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + lrclk |= WM2200_AIF1TX_LRCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + bclk |= WM2200_AIF1_BCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + lrclk |= WM2200_AIF1TX_LRCLK_MSTR; + bclk |= WM2200_AIF1_BCLK_MSTR; + break; + default: + dev_err(component->dev, "Unsupported master mode %d\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + bclk |= WM2200_AIF1_BCLK_INV; + lrclk |= WM2200_AIF1TX_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + bclk |= WM2200_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + lrclk |= WM2200_AIF1TX_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_1, WM2200_AIF1_BCLK_MSTR | + WM2200_AIF1_BCLK_INV, bclk); + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_2, + WM2200_AIF1TX_LRCLK_MSTR | WM2200_AIF1TX_LRCLK_INV, + lrclk); + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_3, + WM2200_AIF1TX_LRCLK_MSTR | WM2200_AIF1TX_LRCLK_INV, + lrclk); + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_5, + WM2200_AIF1_FMT_MASK, fmt_val); + + return 0; +} + +static int wm2200_sr_code[] = { + 0, + 12000, + 24000, + 48000, + 96000, + 192000, + 384000, + 768000, + 0, + 11025, + 22050, + 44100, + 88200, + 176400, + 352800, + 705600, + 4000, + 8000, + 16000, + 32000, + 64000, + 128000, + 256000, + 512000, +}; + +#define WM2200_NUM_BCLK_RATES 12 + +static int wm2200_bclk_rates_dat[WM2200_NUM_BCLK_RATES] = { + 6144000, + 3072000, + 2048000, + 1536000, + 768000, + 512000, + 384000, + 256000, + 192000, + 128000, + 96000, + 64000, +}; + +static int wm2200_bclk_rates_cd[WM2200_NUM_BCLK_RATES] = { + 5644800, + 3763200, + 2882400, + 1881600, + 1411200, + 705600, + 470400, + 352800, + 176400, + 117600, + 88200, + 58800, +}; + +static int wm2200_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm2200_priv *wm2200 = snd_soc_component_get_drvdata(component); + int i, bclk, lrclk, wl, fl, sr_code; + int *bclk_rates; + + /* Data sizes if not using TDM */ + wl = params_width(params); + if (wl < 0) + return wl; + fl = snd_soc_params_to_frame_size(params); + if (fl < 0) + return fl; + + dev_dbg(component->dev, "Word length %d bits, frame length %d bits\n", + wl, fl); + + /* Target BCLK rate */ + bclk = snd_soc_params_to_bclk(params); + if (bclk < 0) + return bclk; + + if (!wm2200->sysclk) { + dev_err(component->dev, "SYSCLK has no rate set\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(wm2200_sr_code); i++) + if (wm2200_sr_code[i] == params_rate(params)) + break; + if (i == ARRAY_SIZE(wm2200_sr_code)) { + dev_err(component->dev, "Unsupported sample rate: %dHz\n", + params_rate(params)); + return -EINVAL; + } + sr_code = i; + + dev_dbg(component->dev, "Target BCLK is %dHz, using %dHz SYSCLK\n", + bclk, wm2200->sysclk); + + if (wm2200->sysclk % 4000) + bclk_rates = wm2200_bclk_rates_cd; + else + bclk_rates = wm2200_bclk_rates_dat; + + for (i = 0; i < WM2200_NUM_BCLK_RATES; i++) + if (bclk_rates[i] >= bclk && (bclk_rates[i] % bclk == 0)) + break; + if (i == WM2200_NUM_BCLK_RATES) { + dev_err(component->dev, + "No valid BCLK for %dHz found from %dHz SYSCLK\n", + bclk, wm2200->sysclk); + return -EINVAL; + } + + bclk = i; + dev_dbg(component->dev, "Setting %dHz BCLK\n", bclk_rates[bclk]); + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_1, + WM2200_AIF1_BCLK_DIV_MASK, bclk); + + lrclk = bclk_rates[bclk] / params_rate(params); + dev_dbg(component->dev, "Setting %dHz LRCLK\n", bclk_rates[bclk] / lrclk); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + wm2200->symmetric_rates) + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_7, + WM2200_AIF1RX_BCPF_MASK, lrclk); + else + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_6, + WM2200_AIF1TX_BCPF_MASK, lrclk); + + i = (wl << WM2200_AIF1TX_WL_SHIFT) | wl; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_9, + WM2200_AIF1RX_WL_MASK | + WM2200_AIF1RX_SLOT_LEN_MASK, i); + else + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_8, + WM2200_AIF1TX_WL_MASK | + WM2200_AIF1TX_SLOT_LEN_MASK, i); + + snd_soc_component_update_bits(component, WM2200_CLOCKING_4, + WM2200_SAMPLE_RATE_1_MASK, sr_code); + + return 0; +} + +static const struct snd_soc_dai_ops wm2200_dai_ops = { + .set_fmt = wm2200_set_fmt, + .hw_params = wm2200_hw_params, +}; + +static int wm2200_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct wm2200_priv *wm2200 = snd_soc_component_get_drvdata(component); + int fval; + + switch (clk_id) { + case WM2200_CLK_SYSCLK: + break; + + default: + dev_err(component->dev, "Unknown clock %d\n", clk_id); + return -EINVAL; + } + + switch (source) { + case WM2200_CLKSRC_MCLK1: + case WM2200_CLKSRC_MCLK2: + case WM2200_CLKSRC_FLL: + case WM2200_CLKSRC_BCLK1: + break; + default: + dev_err(component->dev, "Invalid source %d\n", source); + return -EINVAL; + } + + switch (freq) { + case 22579200: + case 24576000: + fval = 2; + break; + default: + dev_err(component->dev, "Invalid clock rate: %d\n", freq); + return -EINVAL; + } + + /* TODO: Check if MCLKs are in use and enable/disable pulls to + * match. + */ + + snd_soc_component_update_bits(component, WM2200_CLOCKING_3, WM2200_SYSCLK_FREQ_MASK | + WM2200_SYSCLK_SRC_MASK, + fval << WM2200_SYSCLK_FREQ_SHIFT | source); + + wm2200->sysclk = freq; + + return 0; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_refclk_div; + u16 n; + u16 theta; + u16 lambda; +}; + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + unsigned int target; + unsigned int div; + unsigned int fratio, gcd_fll; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + fll_div->fll_refclk_div = 0; + while ((Fref / div) > 13500000) { + div *= 2; + fll_div->fll_refclk_div++; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("FLL Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 2; + while (Fout * div < 90000000) { + div++; + if (div > 64) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * div; + fll_div->fll_outdiv = div - 1; + + pr_debug("FLL Fvco=%dHz\n", target); + + /* Find an appropraite FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + fratio = fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + fll_div->n = target / (fratio * Fref); + + if (target % Fref == 0) { + fll_div->theta = 0; + fll_div->lambda = 0; + } else { + gcd_fll = gcd(target, fratio * Fref); + + fll_div->theta = (target - (fll_div->n * fratio * Fref)) + / gcd_fll; + fll_div->lambda = (fratio * Fref) / gcd_fll; + } + + pr_debug("FLL N=%x THETA=%x LAMBDA=%x\n", + fll_div->n, fll_div->theta, fll_div->lambda); + pr_debug("FLL_FRATIO=%x(%d) FLL_OUTDIV=%x FLL_REFCLK_DIV=%x\n", + fll_div->fll_fratio, fratio, fll_div->fll_outdiv, + fll_div->fll_refclk_div); + + return 0; +} + +static int wm2200_set_fll(struct snd_soc_component *component, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + struct i2c_client *i2c = to_i2c_client(component->dev); + struct wm2200_priv *wm2200 = snd_soc_component_get_drvdata(component); + struct _fll_div factors; + int ret, i, timeout; + unsigned long time_left; + + if (!Fout) { + dev_dbg(component->dev, "FLL disabled"); + + if (wm2200->fll_fout) + pm_runtime_put(component->dev); + + wm2200->fll_fout = 0; + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_1, + WM2200_FLL_ENA, 0); + return 0; + } + + switch (source) { + case WM2200_FLL_SRC_MCLK1: + case WM2200_FLL_SRC_MCLK2: + case WM2200_FLL_SRC_BCLK: + break; + default: + dev_err(component->dev, "Invalid FLL source %d\n", source); + return -EINVAL; + } + + ret = fll_factors(&factors, Fref, Fout); + if (ret < 0) + return ret; + + /* Disable the FLL while we reconfigure */ + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_1, WM2200_FLL_ENA, 0); + + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_2, + WM2200_FLL_OUTDIV_MASK | WM2200_FLL_FRATIO_MASK, + (factors.fll_outdiv << WM2200_FLL_OUTDIV_SHIFT) | + factors.fll_fratio); + if (factors.theta) { + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_3, + WM2200_FLL_FRACN_ENA, + WM2200_FLL_FRACN_ENA); + snd_soc_component_update_bits(component, WM2200_FLL_EFS_2, + WM2200_FLL_EFS_ENA, + WM2200_FLL_EFS_ENA); + } else { + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_3, + WM2200_FLL_FRACN_ENA, 0); + snd_soc_component_update_bits(component, WM2200_FLL_EFS_2, + WM2200_FLL_EFS_ENA, 0); + } + + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_4, WM2200_FLL_THETA_MASK, + factors.theta); + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_6, WM2200_FLL_N_MASK, + factors.n); + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_7, + WM2200_FLL_CLK_REF_DIV_MASK | + WM2200_FLL_CLK_REF_SRC_MASK, + (factors.fll_refclk_div + << WM2200_FLL_CLK_REF_DIV_SHIFT) | source); + snd_soc_component_update_bits(component, WM2200_FLL_EFS_1, + WM2200_FLL_LAMBDA_MASK, factors.lambda); + + /* Clear any pending completions */ + try_wait_for_completion(&wm2200->fll_lock); + + pm_runtime_get_sync(component->dev); + + snd_soc_component_update_bits(component, WM2200_FLL_CONTROL_1, + WM2200_FLL_ENA, WM2200_FLL_ENA); + + if (i2c->irq) + timeout = 2; + else + timeout = 50; + + snd_soc_component_update_bits(component, WM2200_CLOCKING_3, WM2200_SYSCLK_ENA, + WM2200_SYSCLK_ENA); + + /* Poll for the lock; will use the interrupt to exit quickly */ + for (i = 0; i < timeout; i++) { + if (i2c->irq) { + time_left = wait_for_completion_timeout( + &wm2200->fll_lock, + msecs_to_jiffies(25)); + if (time_left > 0) + break; + } else { + msleep(1); + } + + ret = snd_soc_component_read32(component, + WM2200_INTERRUPT_RAW_STATUS_2); + if (ret < 0) { + dev_err(component->dev, + "Failed to read FLL status: %d\n", + ret); + continue; + } + if (ret & WM2200_FLL_LOCK_STS) + break; + } + if (i == timeout) { + dev_err(component->dev, "FLL lock timed out\n"); + pm_runtime_put(component->dev); + return -ETIMEDOUT; + } + + wm2200->fll_src = source; + wm2200->fll_fref = Fref; + wm2200->fll_fout = Fout; + + dev_dbg(component->dev, "FLL running %dHz->%dHz\n", Fref, Fout); + + return 0; +} + +static int wm2200_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm2200_priv *wm2200 = snd_soc_component_get_drvdata(component); + unsigned int val = 0; + int ret; + + ret = snd_soc_component_read32(component, WM2200_GPIO_CTRL_1); + if (ret >= 0) { + if ((ret & WM2200_GP1_FN_MASK) != 0) { + wm2200->symmetric_rates = true; + val = WM2200_AIF1TX_LRCLK_SRC; + } + } else { + dev_err(component->dev, "Failed to read GPIO 1 config: %d\n", ret); + } + + snd_soc_component_update_bits(component, WM2200_AUDIO_IF_1_2, + WM2200_AIF1TX_LRCLK_SRC, val); + + return 0; +} + +#define WM2200_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM2200_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm2200_dai = { + .name = "wm2200", + .probe = wm2200_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM2200_RATES, + .formats = WM2200_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM2200_RATES, + .formats = WM2200_FORMATS, + }, + .ops = &wm2200_dai_ops, +}; + +static const struct snd_soc_component_driver soc_component_wm2200 = { + .probe = wm2200_probe, + .set_sysclk = wm2200_set_sysclk, + .set_pll = wm2200_set_fll, + .controls = wm2200_snd_controls, + .num_controls = ARRAY_SIZE(wm2200_snd_controls), + .dapm_widgets = wm2200_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm2200_dapm_widgets), + .dapm_routes = wm2200_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm2200_dapm_routes), + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static irqreturn_t wm2200_irq(int irq, void *data) +{ + struct wm2200_priv *wm2200 = data; + unsigned int val, mask; + int ret; + + ret = regmap_read(wm2200->regmap, WM2200_INTERRUPT_STATUS_2, &val); + if (ret != 0) { + dev_err(wm2200->dev, "Failed to read IRQ status: %d\n", ret); + return IRQ_NONE; + } + + ret = regmap_read(wm2200->regmap, WM2200_INTERRUPT_STATUS_2_MASK, + &mask); + if (ret != 0) { + dev_warn(wm2200->dev, "Failed to read IRQ mask: %d\n", ret); + mask = 0; + } + + val &= ~mask; + + if (val & WM2200_FLL_LOCK_EINT) { + dev_dbg(wm2200->dev, "FLL locked\n"); + complete(&wm2200->fll_lock); + } + + if (val) { + regmap_write(wm2200->regmap, WM2200_INTERRUPT_STATUS_2, val); + + return IRQ_HANDLED; + } else { + return IRQ_NONE; + } +} + +static const struct regmap_config wm2200_regmap = { + .reg_bits = 16, + .val_bits = 16, + + .max_register = WM2200_MAX_REGISTER + (ARRAY_SIZE(wm2200_ranges) * + WM2200_DSP_SPACING), + .reg_defaults = wm2200_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm2200_reg_defaults), + .volatile_reg = wm2200_volatile_register, + .readable_reg = wm2200_readable_register, + .cache_type = REGCACHE_RBTREE, + .ranges = wm2200_ranges, + .num_ranges = ARRAY_SIZE(wm2200_ranges), +}; + +static const unsigned int wm2200_dig_vu[] = { + WM2200_DAC_DIGITAL_VOLUME_1L, + WM2200_DAC_DIGITAL_VOLUME_1R, + WM2200_DAC_DIGITAL_VOLUME_2L, + WM2200_DAC_DIGITAL_VOLUME_2R, + WM2200_ADC_DIGITAL_VOLUME_1L, + WM2200_ADC_DIGITAL_VOLUME_1R, + WM2200_ADC_DIGITAL_VOLUME_2L, + WM2200_ADC_DIGITAL_VOLUME_2R, + WM2200_ADC_DIGITAL_VOLUME_3L, + WM2200_ADC_DIGITAL_VOLUME_3R, +}; + +static const unsigned int wm2200_mic_ctrl_reg[] = { + WM2200_IN1L_CONTROL, + WM2200_IN2L_CONTROL, + WM2200_IN3L_CONTROL, +}; + +static int wm2200_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm2200_pdata *pdata = dev_get_platdata(&i2c->dev); + struct wm2200_priv *wm2200; + unsigned int reg; + int ret, i; + int val; + + wm2200 = devm_kzalloc(&i2c->dev, sizeof(struct wm2200_priv), + GFP_KERNEL); + if (wm2200 == NULL) + return -ENOMEM; + + wm2200->dev = &i2c->dev; + init_completion(&wm2200->fll_lock); + + wm2200->regmap = devm_regmap_init_i2c(i2c, &wm2200_regmap); + if (IS_ERR(wm2200->regmap)) { + ret = PTR_ERR(wm2200->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + for (i = 0; i < 2; i++) { + wm2200->dsp[i].type = WMFW_ADSP1; + wm2200->dsp[i].part = "wm2200"; + wm2200->dsp[i].num = i + 1; + wm2200->dsp[i].dev = &i2c->dev; + wm2200->dsp[i].regmap = wm2200->regmap; + wm2200->dsp[i].sysclk_reg = WM2200_CLOCKING_3; + wm2200->dsp[i].sysclk_mask = WM2200_SYSCLK_FREQ_MASK; + wm2200->dsp[i].sysclk_shift = WM2200_SYSCLK_FREQ_SHIFT; + } + + wm2200->dsp[0].base = WM2200_DSP1_CONTROL_1; + wm2200->dsp[0].mem = wm2200_dsp1_regions; + wm2200->dsp[0].num_mems = ARRAY_SIZE(wm2200_dsp1_regions); + + wm2200->dsp[1].base = WM2200_DSP2_CONTROL_1; + wm2200->dsp[1].mem = wm2200_dsp2_regions; + wm2200->dsp[1].num_mems = ARRAY_SIZE(wm2200_dsp2_regions); + + for (i = 0; i < ARRAY_SIZE(wm2200->dsp); i++) + wm_adsp1_init(&wm2200->dsp[i]); + + if (pdata) + wm2200->pdata = *pdata; + + i2c_set_clientdata(i2c, wm2200); + + for (i = 0; i < ARRAY_SIZE(wm2200->core_supplies); i++) + wm2200->core_supplies[i].supply = wm2200_core_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, + ARRAY_SIZE(wm2200->core_supplies), + wm2200->core_supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request core supplies: %d\n", + ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm2200->core_supplies), + wm2200->core_supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable core supplies: %d\n", + ret); + return ret; + } + + if (wm2200->pdata.ldo_ena) { + ret = devm_gpio_request_one(&i2c->dev, wm2200->pdata.ldo_ena, + GPIOF_OUT_INIT_HIGH, + "WM2200 LDOENA"); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request LDOENA %d: %d\n", + wm2200->pdata.ldo_ena, ret); + goto err_enable; + } + msleep(2); + } + + if (wm2200->pdata.reset) { + ret = devm_gpio_request_one(&i2c->dev, wm2200->pdata.reset, + GPIOF_OUT_INIT_HIGH, + "WM2200 /RESET"); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request /RESET %d: %d\n", + wm2200->pdata.reset, ret); + goto err_ldo; + } + } + + ret = regmap_read(wm2200->regmap, WM2200_SOFTWARE_RESET, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read ID register: %d\n", ret); + goto err_reset; + } + switch (reg) { + case 0x2200: + break; + + default: + dev_err(&i2c->dev, "Device is not a WM2200, ID is %x\n", reg); + ret = -EINVAL; + goto err_reset; + } + + ret = regmap_read(wm2200->regmap, WM2200_DEVICE_REVISION, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read revision register\n"); + goto err_reset; + } + + wm2200->rev = reg & WM2200_DEVICE_REVISION_MASK; + + dev_info(&i2c->dev, "revision %c\n", wm2200->rev + 'A'); + + switch (wm2200->rev) { + case 0: + case 1: + ret = regmap_register_patch(wm2200->regmap, wm2200_reva_patch, + ARRAY_SIZE(wm2200_reva_patch)); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register patch: %d\n", + ret); + } + break; + default: + break; + } + + ret = wm2200_reset(wm2200); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + goto err_reset; + } + + for (i = 0; i < ARRAY_SIZE(wm2200->pdata.gpio_defaults); i++) { + if (!wm2200->pdata.gpio_defaults[i]) + continue; + + regmap_write(wm2200->regmap, WM2200_GPIO_CTRL_1 + i, + wm2200->pdata.gpio_defaults[i]); + } + + for (i = 0; i < ARRAY_SIZE(wm2200_dig_vu); i++) + regmap_update_bits(wm2200->regmap, wm2200_dig_vu[i], + WM2200_OUT_VU, WM2200_OUT_VU); + + /* Assign slots 1-6 to channels 1-6 for both TX and RX */ + for (i = 0; i < 6; i++) { + regmap_write(wm2200->regmap, WM2200_AUDIO_IF_1_10 + i, i); + regmap_write(wm2200->regmap, WM2200_AUDIO_IF_1_16 + i, i); + } + + for (i = 0; i < WM2200_MAX_MICBIAS; i++) { + if (!wm2200->pdata.micbias[i].mb_lvl && + !wm2200->pdata.micbias[i].bypass) + continue; + + /* Apply default for bypass mode */ + if (!wm2200->pdata.micbias[i].mb_lvl) + wm2200->pdata.micbias[i].mb_lvl + = WM2200_MBIAS_LVL_1V5; + + val = (wm2200->pdata.micbias[i].mb_lvl -1) + << WM2200_MICB1_LVL_SHIFT; + + if (wm2200->pdata.micbias[i].discharge) + val |= WM2200_MICB1_DISCH; + + if (wm2200->pdata.micbias[i].fast_start) + val |= WM2200_MICB1_RATE; + + if (wm2200->pdata.micbias[i].bypass) + val |= WM2200_MICB1_MODE; + + regmap_update_bits(wm2200->regmap, + WM2200_MIC_BIAS_CTRL_1 + i, + WM2200_MICB1_LVL_MASK | + WM2200_MICB1_DISCH | + WM2200_MICB1_MODE | + WM2200_MICB1_RATE, val); + } + + for (i = 0; i < ARRAY_SIZE(wm2200->pdata.in_mode); i++) { + regmap_update_bits(wm2200->regmap, wm2200_mic_ctrl_reg[i], + WM2200_IN1_MODE_MASK | + WM2200_IN1_DMIC_SUP_MASK, + (wm2200->pdata.in_mode[i] << + WM2200_IN1_MODE_SHIFT) | + (wm2200->pdata.dmic_sup[i] << + WM2200_IN1_DMIC_SUP_SHIFT)); + } + + if (i2c->irq) { + ret = request_threaded_irq(i2c->irq, NULL, wm2200_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "wm2200", wm2200); + if (ret == 0) + regmap_update_bits(wm2200->regmap, + WM2200_INTERRUPT_STATUS_2_MASK, + WM2200_FLL_LOCK_EINT, 0); + else + dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", + i2c->irq, ret); + } + + pm_runtime_set_active(&i2c->dev); + pm_runtime_enable(&i2c->dev); + pm_request_idle(&i2c->dev); + + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_wm2200, + &wm2200_dai, 1); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + goto err_pm_runtime; + } + + return 0; + +err_pm_runtime: + pm_runtime_disable(&i2c->dev); + if (i2c->irq) + free_irq(i2c->irq, wm2200); +err_reset: + if (wm2200->pdata.reset) + gpio_set_value_cansleep(wm2200->pdata.reset, 0); +err_ldo: + if (wm2200->pdata.ldo_ena) + gpio_set_value_cansleep(wm2200->pdata.ldo_ena, 0); +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), + wm2200->core_supplies); + return ret; +} + +static int wm2200_i2c_remove(struct i2c_client *i2c) +{ + struct wm2200_priv *wm2200 = i2c_get_clientdata(i2c); + + pm_runtime_disable(&i2c->dev); + if (i2c->irq) + free_irq(i2c->irq, wm2200); + if (wm2200->pdata.reset) + gpio_set_value_cansleep(wm2200->pdata.reset, 0); + if (wm2200->pdata.ldo_ena) + gpio_set_value_cansleep(wm2200->pdata.ldo_ena, 0); + regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), + wm2200->core_supplies); + + return 0; +} + +#ifdef CONFIG_PM +static int wm2200_runtime_suspend(struct device *dev) +{ + struct wm2200_priv *wm2200 = dev_get_drvdata(dev); + + regcache_cache_only(wm2200->regmap, true); + regcache_mark_dirty(wm2200->regmap); + if (wm2200->pdata.ldo_ena) + gpio_set_value_cansleep(wm2200->pdata.ldo_ena, 0); + regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), + wm2200->core_supplies); + + return 0; +} + +static int wm2200_runtime_resume(struct device *dev) +{ + struct wm2200_priv *wm2200 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm2200->core_supplies), + wm2200->core_supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", + ret); + return ret; + } + + if (wm2200->pdata.ldo_ena) { + gpio_set_value_cansleep(wm2200->pdata.ldo_ena, 1); + msleep(2); + } + + regcache_cache_only(wm2200->regmap, false); + regcache_sync(wm2200->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops wm2200_pm = { + SET_RUNTIME_PM_OPS(wm2200_runtime_suspend, wm2200_runtime_resume, + NULL) +}; + +static const struct i2c_device_id wm2200_i2c_id[] = { + { "wm2200", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm2200_i2c_id); + +static struct i2c_driver wm2200_i2c_driver = { + .driver = { + .name = "wm2200", + .pm = &wm2200_pm, + }, + .probe = wm2200_i2c_probe, + .remove = wm2200_i2c_remove, + .id_table = wm2200_i2c_id, +}; + +module_i2c_driver(wm2200_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM2200 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm2200.h b/sound/soc/codecs/wm2200.h new file mode 100644 index 000000000..5d719d6b4 --- /dev/null +++ b/sound/soc/codecs/wm2200.h @@ -0,0 +1,3674 @@ +/* + * wm2200.h - WM2200 audio codec interface + * + * Copyright 2012 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM2200_H +#define _WM2200_H + +#define WM2200_CLK_SYSCLK 1 + +#define WM2200_CLKSRC_MCLK1 0 +#define WM2200_CLKSRC_MCLK2 1 +#define WM2200_CLKSRC_FLL 4 +#define WM2200_CLKSRC_BCLK1 8 + +#define WM2200_FLL_SRC_MCLK1 0 +#define WM2200_FLL_SRC_MCLK2 1 +#define WM2200_FLL_SRC_BCLK 2 + +/* + * Register values. + */ +#define WM2200_SOFTWARE_RESET 0x00 +#define WM2200_DEVICE_REVISION 0x01 +#define WM2200_TONE_GENERATOR_1 0x0B +#define WM2200_CLOCKING_3 0x102 +#define WM2200_CLOCKING_4 0x103 +#define WM2200_FLL_CONTROL_1 0x111 +#define WM2200_FLL_CONTROL_2 0x112 +#define WM2200_FLL_CONTROL_3 0x113 +#define WM2200_FLL_CONTROL_4 0x114 +#define WM2200_FLL_CONTROL_6 0x116 +#define WM2200_FLL_CONTROL_7 0x117 +#define WM2200_FLL_EFS_1 0x119 +#define WM2200_FLL_EFS_2 0x11A +#define WM2200_MIC_CHARGE_PUMP_1 0x200 +#define WM2200_MIC_CHARGE_PUMP_2 0x201 +#define WM2200_DM_CHARGE_PUMP_1 0x202 +#define WM2200_MIC_BIAS_CTRL_1 0x20C +#define WM2200_MIC_BIAS_CTRL_2 0x20D +#define WM2200_EAR_PIECE_CTRL_1 0x20F +#define WM2200_EAR_PIECE_CTRL_2 0x210 +#define WM2200_INPUT_ENABLES 0x301 +#define WM2200_IN1L_CONTROL 0x302 +#define WM2200_IN1R_CONTROL 0x303 +#define WM2200_IN2L_CONTROL 0x304 +#define WM2200_IN2R_CONTROL 0x305 +#define WM2200_IN3L_CONTROL 0x306 +#define WM2200_IN3R_CONTROL 0x307 +#define WM2200_RXANC_SRC 0x30A +#define WM2200_INPUT_VOLUME_RAMP 0x30B +#define WM2200_ADC_DIGITAL_VOLUME_1L 0x30C +#define WM2200_ADC_DIGITAL_VOLUME_1R 0x30D +#define WM2200_ADC_DIGITAL_VOLUME_2L 0x30E +#define WM2200_ADC_DIGITAL_VOLUME_2R 0x30F +#define WM2200_ADC_DIGITAL_VOLUME_3L 0x310 +#define WM2200_ADC_DIGITAL_VOLUME_3R 0x311 +#define WM2200_OUTPUT_ENABLES 0x400 +#define WM2200_DAC_VOLUME_LIMIT_1L 0x401 +#define WM2200_DAC_VOLUME_LIMIT_1R 0x402 +#define WM2200_DAC_VOLUME_LIMIT_2L 0x403 +#define WM2200_DAC_VOLUME_LIMIT_2R 0x404 +#define WM2200_DAC_AEC_CONTROL_1 0x409 +#define WM2200_OUTPUT_VOLUME_RAMP 0x40A +#define WM2200_DAC_DIGITAL_VOLUME_1L 0x40B +#define WM2200_DAC_DIGITAL_VOLUME_1R 0x40C +#define WM2200_DAC_DIGITAL_VOLUME_2L 0x40D +#define WM2200_DAC_DIGITAL_VOLUME_2R 0x40E +#define WM2200_PDM_1 0x417 +#define WM2200_PDM_2 0x418 +#define WM2200_AUDIO_IF_1_1 0x500 +#define WM2200_AUDIO_IF_1_2 0x501 +#define WM2200_AUDIO_IF_1_3 0x502 +#define WM2200_AUDIO_IF_1_4 0x503 +#define WM2200_AUDIO_IF_1_5 0x504 +#define WM2200_AUDIO_IF_1_6 0x505 +#define WM2200_AUDIO_IF_1_7 0x506 +#define WM2200_AUDIO_IF_1_8 0x507 +#define WM2200_AUDIO_IF_1_9 0x508 +#define WM2200_AUDIO_IF_1_10 0x509 +#define WM2200_AUDIO_IF_1_11 0x50A +#define WM2200_AUDIO_IF_1_12 0x50B +#define WM2200_AUDIO_IF_1_13 0x50C +#define WM2200_AUDIO_IF_1_14 0x50D +#define WM2200_AUDIO_IF_1_15 0x50E +#define WM2200_AUDIO_IF_1_16 0x50F +#define WM2200_AUDIO_IF_1_17 0x510 +#define WM2200_AUDIO_IF_1_18 0x511 +#define WM2200_AUDIO_IF_1_19 0x512 +#define WM2200_AUDIO_IF_1_20 0x513 +#define WM2200_AUDIO_IF_1_21 0x514 +#define WM2200_AUDIO_IF_1_22 0x515 +#define WM2200_OUT1LMIX_INPUT_1_SOURCE 0x600 +#define WM2200_OUT1LMIX_INPUT_1_VOLUME 0x601 +#define WM2200_OUT1LMIX_INPUT_2_SOURCE 0x602 +#define WM2200_OUT1LMIX_INPUT_2_VOLUME 0x603 +#define WM2200_OUT1LMIX_INPUT_3_SOURCE 0x604 +#define WM2200_OUT1LMIX_INPUT_3_VOLUME 0x605 +#define WM2200_OUT1LMIX_INPUT_4_SOURCE 0x606 +#define WM2200_OUT1LMIX_INPUT_4_VOLUME 0x607 +#define WM2200_OUT1RMIX_INPUT_1_SOURCE 0x608 +#define WM2200_OUT1RMIX_INPUT_1_VOLUME 0x609 +#define WM2200_OUT1RMIX_INPUT_2_SOURCE 0x60A +#define WM2200_OUT1RMIX_INPUT_2_VOLUME 0x60B +#define WM2200_OUT1RMIX_INPUT_3_SOURCE 0x60C +#define WM2200_OUT1RMIX_INPUT_3_VOLUME 0x60D +#define WM2200_OUT1RMIX_INPUT_4_SOURCE 0x60E +#define WM2200_OUT1RMIX_INPUT_4_VOLUME 0x60F +#define WM2200_OUT2LMIX_INPUT_1_SOURCE 0x610 +#define WM2200_OUT2LMIX_INPUT_1_VOLUME 0x611 +#define WM2200_OUT2LMIX_INPUT_2_SOURCE 0x612 +#define WM2200_OUT2LMIX_INPUT_2_VOLUME 0x613 +#define WM2200_OUT2LMIX_INPUT_3_SOURCE 0x614 +#define WM2200_OUT2LMIX_INPUT_3_VOLUME 0x615 +#define WM2200_OUT2LMIX_INPUT_4_SOURCE 0x616 +#define WM2200_OUT2LMIX_INPUT_4_VOLUME 0x617 +#define WM2200_OUT2RMIX_INPUT_1_SOURCE 0x618 +#define WM2200_OUT2RMIX_INPUT_1_VOLUME 0x619 +#define WM2200_OUT2RMIX_INPUT_2_SOURCE 0x61A +#define WM2200_OUT2RMIX_INPUT_2_VOLUME 0x61B +#define WM2200_OUT2RMIX_INPUT_3_SOURCE 0x61C +#define WM2200_OUT2RMIX_INPUT_3_VOLUME 0x61D +#define WM2200_OUT2RMIX_INPUT_4_SOURCE 0x61E +#define WM2200_OUT2RMIX_INPUT_4_VOLUME 0x61F +#define WM2200_AIF1TX1MIX_INPUT_1_SOURCE 0x620 +#define WM2200_AIF1TX1MIX_INPUT_1_VOLUME 0x621 +#define WM2200_AIF1TX1MIX_INPUT_2_SOURCE 0x622 +#define WM2200_AIF1TX1MIX_INPUT_2_VOLUME 0x623 +#define WM2200_AIF1TX1MIX_INPUT_3_SOURCE 0x624 +#define WM2200_AIF1TX1MIX_INPUT_3_VOLUME 0x625 +#define WM2200_AIF1TX1MIX_INPUT_4_SOURCE 0x626 +#define WM2200_AIF1TX1MIX_INPUT_4_VOLUME 0x627 +#define WM2200_AIF1TX2MIX_INPUT_1_SOURCE 0x628 +#define WM2200_AIF1TX2MIX_INPUT_1_VOLUME 0x629 +#define WM2200_AIF1TX2MIX_INPUT_2_SOURCE 0x62A +#define WM2200_AIF1TX2MIX_INPUT_2_VOLUME 0x62B +#define WM2200_AIF1TX2MIX_INPUT_3_SOURCE 0x62C +#define WM2200_AIF1TX2MIX_INPUT_3_VOLUME 0x62D +#define WM2200_AIF1TX2MIX_INPUT_4_SOURCE 0x62E +#define WM2200_AIF1TX2MIX_INPUT_4_VOLUME 0x62F +#define WM2200_AIF1TX3MIX_INPUT_1_SOURCE 0x630 +#define WM2200_AIF1TX3MIX_INPUT_1_VOLUME 0x631 +#define WM2200_AIF1TX3MIX_INPUT_2_SOURCE 0x632 +#define WM2200_AIF1TX3MIX_INPUT_2_VOLUME 0x633 +#define WM2200_AIF1TX3MIX_INPUT_3_SOURCE 0x634 +#define WM2200_AIF1TX3MIX_INPUT_3_VOLUME 0x635 +#define WM2200_AIF1TX3MIX_INPUT_4_SOURCE 0x636 +#define WM2200_AIF1TX3MIX_INPUT_4_VOLUME 0x637 +#define WM2200_AIF1TX4MIX_INPUT_1_SOURCE 0x638 +#define WM2200_AIF1TX4MIX_INPUT_1_VOLUME 0x639 +#define WM2200_AIF1TX4MIX_INPUT_2_SOURCE 0x63A +#define WM2200_AIF1TX4MIX_INPUT_2_VOLUME 0x63B +#define WM2200_AIF1TX4MIX_INPUT_3_SOURCE 0x63C +#define WM2200_AIF1TX4MIX_INPUT_3_VOLUME 0x63D +#define WM2200_AIF1TX4MIX_INPUT_4_SOURCE 0x63E +#define WM2200_AIF1TX4MIX_INPUT_4_VOLUME 0x63F +#define WM2200_AIF1TX5MIX_INPUT_1_SOURCE 0x640 +#define WM2200_AIF1TX5MIX_INPUT_1_VOLUME 0x641 +#define WM2200_AIF1TX5MIX_INPUT_2_SOURCE 0x642 +#define WM2200_AIF1TX5MIX_INPUT_2_VOLUME 0x643 +#define WM2200_AIF1TX5MIX_INPUT_3_SOURCE 0x644 +#define WM2200_AIF1TX5MIX_INPUT_3_VOLUME 0x645 +#define WM2200_AIF1TX5MIX_INPUT_4_SOURCE 0x646 +#define WM2200_AIF1TX5MIX_INPUT_4_VOLUME 0x647 +#define WM2200_AIF1TX6MIX_INPUT_1_SOURCE 0x648 +#define WM2200_AIF1TX6MIX_INPUT_1_VOLUME 0x649 +#define WM2200_AIF1TX6MIX_INPUT_2_SOURCE 0x64A +#define WM2200_AIF1TX6MIX_INPUT_2_VOLUME 0x64B +#define WM2200_AIF1TX6MIX_INPUT_3_SOURCE 0x64C +#define WM2200_AIF1TX6MIX_INPUT_3_VOLUME 0x64D +#define WM2200_AIF1TX6MIX_INPUT_4_SOURCE 0x64E +#define WM2200_AIF1TX6MIX_INPUT_4_VOLUME 0x64F +#define WM2200_EQLMIX_INPUT_1_SOURCE 0x650 +#define WM2200_EQLMIX_INPUT_1_VOLUME 0x651 +#define WM2200_EQLMIX_INPUT_2_SOURCE 0x652 +#define WM2200_EQLMIX_INPUT_2_VOLUME 0x653 +#define WM2200_EQLMIX_INPUT_3_SOURCE 0x654 +#define WM2200_EQLMIX_INPUT_3_VOLUME 0x655 +#define WM2200_EQLMIX_INPUT_4_SOURCE 0x656 +#define WM2200_EQLMIX_INPUT_4_VOLUME 0x657 +#define WM2200_EQRMIX_INPUT_1_SOURCE 0x658 +#define WM2200_EQRMIX_INPUT_1_VOLUME 0x659 +#define WM2200_EQRMIX_INPUT_2_SOURCE 0x65A +#define WM2200_EQRMIX_INPUT_2_VOLUME 0x65B +#define WM2200_EQRMIX_INPUT_3_SOURCE 0x65C +#define WM2200_EQRMIX_INPUT_3_VOLUME 0x65D +#define WM2200_EQRMIX_INPUT_4_SOURCE 0x65E +#define WM2200_EQRMIX_INPUT_4_VOLUME 0x65F +#define WM2200_LHPF1MIX_INPUT_1_SOURCE 0x660 +#define WM2200_LHPF1MIX_INPUT_1_VOLUME 0x661 +#define WM2200_LHPF1MIX_INPUT_2_SOURCE 0x662 +#define WM2200_LHPF1MIX_INPUT_2_VOLUME 0x663 +#define WM2200_LHPF1MIX_INPUT_3_SOURCE 0x664 +#define WM2200_LHPF1MIX_INPUT_3_VOLUME 0x665 +#define WM2200_LHPF1MIX_INPUT_4_SOURCE 0x666 +#define WM2200_LHPF1MIX_INPUT_4_VOLUME 0x667 +#define WM2200_LHPF2MIX_INPUT_1_SOURCE 0x668 +#define WM2200_LHPF2MIX_INPUT_1_VOLUME 0x669 +#define WM2200_LHPF2MIX_INPUT_2_SOURCE 0x66A +#define WM2200_LHPF2MIX_INPUT_2_VOLUME 0x66B +#define WM2200_LHPF2MIX_INPUT_3_SOURCE 0x66C +#define WM2200_LHPF2MIX_INPUT_3_VOLUME 0x66D +#define WM2200_LHPF2MIX_INPUT_4_SOURCE 0x66E +#define WM2200_LHPF2MIX_INPUT_4_VOLUME 0x66F +#define WM2200_DSP1LMIX_INPUT_1_SOURCE 0x670 +#define WM2200_DSP1LMIX_INPUT_1_VOLUME 0x671 +#define WM2200_DSP1LMIX_INPUT_2_SOURCE 0x672 +#define WM2200_DSP1LMIX_INPUT_2_VOLUME 0x673 +#define WM2200_DSP1LMIX_INPUT_3_SOURCE 0x674 +#define WM2200_DSP1LMIX_INPUT_3_VOLUME 0x675 +#define WM2200_DSP1LMIX_INPUT_4_SOURCE 0x676 +#define WM2200_DSP1LMIX_INPUT_4_VOLUME 0x677 +#define WM2200_DSP1RMIX_INPUT_1_SOURCE 0x678 +#define WM2200_DSP1RMIX_INPUT_1_VOLUME 0x679 +#define WM2200_DSP1RMIX_INPUT_2_SOURCE 0x67A +#define WM2200_DSP1RMIX_INPUT_2_VOLUME 0x67B +#define WM2200_DSP1RMIX_INPUT_3_SOURCE 0x67C +#define WM2200_DSP1RMIX_INPUT_3_VOLUME 0x67D +#define WM2200_DSP1RMIX_INPUT_4_SOURCE 0x67E +#define WM2200_DSP1RMIX_INPUT_4_VOLUME 0x67F +#define WM2200_DSP1AUX1MIX_INPUT_1_SOURCE 0x680 +#define WM2200_DSP1AUX2MIX_INPUT_1_SOURCE 0x681 +#define WM2200_DSP1AUX3MIX_INPUT_1_SOURCE 0x682 +#define WM2200_DSP1AUX4MIX_INPUT_1_SOURCE 0x683 +#define WM2200_DSP1AUX5MIX_INPUT_1_SOURCE 0x684 +#define WM2200_DSP1AUX6MIX_INPUT_1_SOURCE 0x685 +#define WM2200_DSP2LMIX_INPUT_1_SOURCE 0x686 +#define WM2200_DSP2LMIX_INPUT_1_VOLUME 0x687 +#define WM2200_DSP2LMIX_INPUT_2_SOURCE 0x688 +#define WM2200_DSP2LMIX_INPUT_2_VOLUME 0x689 +#define WM2200_DSP2LMIX_INPUT_3_SOURCE 0x68A +#define WM2200_DSP2LMIX_INPUT_3_VOLUME 0x68B +#define WM2200_DSP2LMIX_INPUT_4_SOURCE 0x68C +#define WM2200_DSP2LMIX_INPUT_4_VOLUME 0x68D +#define WM2200_DSP2RMIX_INPUT_1_SOURCE 0x68E +#define WM2200_DSP2RMIX_INPUT_1_VOLUME 0x68F +#define WM2200_DSP2RMIX_INPUT_2_SOURCE 0x690 +#define WM2200_DSP2RMIX_INPUT_2_VOLUME 0x691 +#define WM2200_DSP2RMIX_INPUT_3_SOURCE 0x692 +#define WM2200_DSP2RMIX_INPUT_3_VOLUME 0x693 +#define WM2200_DSP2RMIX_INPUT_4_SOURCE 0x694 +#define WM2200_DSP2RMIX_INPUT_4_VOLUME 0x695 +#define WM2200_DSP2AUX1MIX_INPUT_1_SOURCE 0x696 +#define WM2200_DSP2AUX2MIX_INPUT_1_SOURCE 0x697 +#define WM2200_DSP2AUX3MIX_INPUT_1_SOURCE 0x698 +#define WM2200_DSP2AUX4MIX_INPUT_1_SOURCE 0x699 +#define WM2200_DSP2AUX5MIX_INPUT_1_SOURCE 0x69A +#define WM2200_DSP2AUX6MIX_INPUT_1_SOURCE 0x69B +#define WM2200_GPIO_CTRL_1 0x700 +#define WM2200_GPIO_CTRL_2 0x701 +#define WM2200_GPIO_CTRL_3 0x702 +#define WM2200_GPIO_CTRL_4 0x703 +#define WM2200_ADPS1_IRQ0 0x707 +#define WM2200_ADPS1_IRQ1 0x708 +#define WM2200_MISC_PAD_CTRL_1 0x709 +#define WM2200_INTERRUPT_STATUS_1 0x800 +#define WM2200_INTERRUPT_STATUS_1_MASK 0x801 +#define WM2200_INTERRUPT_STATUS_2 0x802 +#define WM2200_INTERRUPT_RAW_STATUS_2 0x803 +#define WM2200_INTERRUPT_STATUS_2_MASK 0x804 +#define WM2200_INTERRUPT_CONTROL 0x808 +#define WM2200_EQL_1 0x900 +#define WM2200_EQL_2 0x901 +#define WM2200_EQL_3 0x902 +#define WM2200_EQL_4 0x903 +#define WM2200_EQL_5 0x904 +#define WM2200_EQL_6 0x905 +#define WM2200_EQL_7 0x906 +#define WM2200_EQL_8 0x907 +#define WM2200_EQL_9 0x908 +#define WM2200_EQL_10 0x909 +#define WM2200_EQL_11 0x90A +#define WM2200_EQL_12 0x90B +#define WM2200_EQL_13 0x90C +#define WM2200_EQL_14 0x90D +#define WM2200_EQL_15 0x90E +#define WM2200_EQL_16 0x90F +#define WM2200_EQL_17 0x910 +#define WM2200_EQL_18 0x911 +#define WM2200_EQL_19 0x912 +#define WM2200_EQL_20 0x913 +#define WM2200_EQR_1 0x916 +#define WM2200_EQR_2 0x917 +#define WM2200_EQR_3 0x918 +#define WM2200_EQR_4 0x919 +#define WM2200_EQR_5 0x91A +#define WM2200_EQR_6 0x91B +#define WM2200_EQR_7 0x91C +#define WM2200_EQR_8 0x91D +#define WM2200_EQR_9 0x91E +#define WM2200_EQR_10 0x91F +#define WM2200_EQR_11 0x920 +#define WM2200_EQR_12 0x921 +#define WM2200_EQR_13 0x922 +#define WM2200_EQR_14 0x923 +#define WM2200_EQR_15 0x924 +#define WM2200_EQR_16 0x925 +#define WM2200_EQR_17 0x926 +#define WM2200_EQR_18 0x927 +#define WM2200_EQR_19 0x928 +#define WM2200_EQR_20 0x929 +#define WM2200_HPLPF1_1 0x93E +#define WM2200_HPLPF1_2 0x93F +#define WM2200_HPLPF2_1 0x942 +#define WM2200_HPLPF2_2 0x943 +#define WM2200_DSP1_CONTROL_1 0xA00 +#define WM2200_DSP1_CONTROL_2 0xA02 +#define WM2200_DSP1_CONTROL_3 0xA03 +#define WM2200_DSP1_CONTROL_4 0xA04 +#define WM2200_DSP1_CONTROL_5 0xA06 +#define WM2200_DSP1_CONTROL_6 0xA07 +#define WM2200_DSP1_CONTROL_7 0xA08 +#define WM2200_DSP1_CONTROL_8 0xA09 +#define WM2200_DSP1_CONTROL_9 0xA0A +#define WM2200_DSP1_CONTROL_10 0xA0B +#define WM2200_DSP1_CONTROL_11 0xA0C +#define WM2200_DSP1_CONTROL_12 0xA0D +#define WM2200_DSP1_CONTROL_13 0xA0F +#define WM2200_DSP1_CONTROL_14 0xA10 +#define WM2200_DSP1_CONTROL_15 0xA11 +#define WM2200_DSP1_CONTROL_16 0xA12 +#define WM2200_DSP1_CONTROL_17 0xA13 +#define WM2200_DSP1_CONTROL_18 0xA14 +#define WM2200_DSP1_CONTROL_19 0xA16 +#define WM2200_DSP1_CONTROL_20 0xA17 +#define WM2200_DSP1_CONTROL_21 0xA18 +#define WM2200_DSP1_CONTROL_22 0xA1A +#define WM2200_DSP1_CONTROL_23 0xA1B +#define WM2200_DSP1_CONTROL_24 0xA1C +#define WM2200_DSP1_CONTROL_25 0xA1E +#define WM2200_DSP1_CONTROL_26 0xA20 +#define WM2200_DSP1_CONTROL_27 0xA21 +#define WM2200_DSP1_CONTROL_28 0xA22 +#define WM2200_DSP1_CONTROL_29 0xA23 +#define WM2200_DSP1_CONTROL_30 0xA24 +#define WM2200_DSP1_CONTROL_31 0xA26 +#define WM2200_DSP2_CONTROL_1 0xB00 +#define WM2200_DSP2_CONTROL_2 0xB02 +#define WM2200_DSP2_CONTROL_3 0xB03 +#define WM2200_DSP2_CONTROL_4 0xB04 +#define WM2200_DSP2_CONTROL_5 0xB06 +#define WM2200_DSP2_CONTROL_6 0xB07 +#define WM2200_DSP2_CONTROL_7 0xB08 +#define WM2200_DSP2_CONTROL_8 0xB09 +#define WM2200_DSP2_CONTROL_9 0xB0A +#define WM2200_DSP2_CONTROL_10 0xB0B +#define WM2200_DSP2_CONTROL_11 0xB0C +#define WM2200_DSP2_CONTROL_12 0xB0D +#define WM2200_DSP2_CONTROL_13 0xB0F +#define WM2200_DSP2_CONTROL_14 0xB10 +#define WM2200_DSP2_CONTROL_15 0xB11 +#define WM2200_DSP2_CONTROL_16 0xB12 +#define WM2200_DSP2_CONTROL_17 0xB13 +#define WM2200_DSP2_CONTROL_18 0xB14 +#define WM2200_DSP2_CONTROL_19 0xB16 +#define WM2200_DSP2_CONTROL_20 0xB17 +#define WM2200_DSP2_CONTROL_21 0xB18 +#define WM2200_DSP2_CONTROL_22 0xB1A +#define WM2200_DSP2_CONTROL_23 0xB1B +#define WM2200_DSP2_CONTROL_24 0xB1C +#define WM2200_DSP2_CONTROL_25 0xB1E +#define WM2200_DSP2_CONTROL_26 0xB20 +#define WM2200_DSP2_CONTROL_27 0xB21 +#define WM2200_DSP2_CONTROL_28 0xB22 +#define WM2200_DSP2_CONTROL_29 0xB23 +#define WM2200_DSP2_CONTROL_30 0xB24 +#define WM2200_DSP2_CONTROL_31 0xB26 +#define WM2200_ANC_CTRL1 0xD00 +#define WM2200_ANC_CTRL2 0xD01 +#define WM2200_ANC_CTRL3 0xD02 +#define WM2200_ANC_CTRL7 0xD08 +#define WM2200_ANC_CTRL8 0xD09 +#define WM2200_ANC_CTRL9 0xD0A +#define WM2200_ANC_CTRL10 0xD0B +#define WM2200_ANC_CTRL11 0xD0C +#define WM2200_ANC_CTRL12 0xD0D +#define WM2200_ANC_CTRL13 0xD0E +#define WM2200_ANC_CTRL14 0xD0F +#define WM2200_ANC_CTRL15 0xD10 +#define WM2200_ANC_CTRL16 0xD11 +#define WM2200_ANC_CTRL17 0xD12 +#define WM2200_ANC_CTRL18 0xD15 +#define WM2200_ANC_CTRL19 0xD16 +#define WM2200_ANC_CTRL20 0xD17 +#define WM2200_ANC_CTRL21 0xD18 +#define WM2200_ANC_CTRL22 0xD19 +#define WM2200_ANC_CTRL23 0xD1A +#define WM2200_ANC_CTRL24 0xD1B +#define WM2200_ANC_CTRL25 0xD1C +#define WM2200_ANC_CTRL26 0xD1D +#define WM2200_ANC_CTRL27 0xD1E +#define WM2200_ANC_CTRL28 0xD1F +#define WM2200_ANC_CTRL29 0xD20 +#define WM2200_ANC_CTRL30 0xD21 +#define WM2200_ANC_CTRL31 0xD23 +#define WM2200_ANC_CTRL32 0xD24 +#define WM2200_ANC_CTRL33 0xD25 +#define WM2200_ANC_CTRL34 0xD27 +#define WM2200_ANC_CTRL35 0xD28 +#define WM2200_ANC_CTRL36 0xD29 +#define WM2200_ANC_CTRL37 0xD2A +#define WM2200_ANC_CTRL38 0xD2B +#define WM2200_ANC_CTRL39 0xD2C +#define WM2200_ANC_CTRL40 0xD2D +#define WM2200_ANC_CTRL41 0xD2E +#define WM2200_ANC_CTRL42 0xD2F +#define WM2200_ANC_CTRL43 0xD30 +#define WM2200_ANC_CTRL44 0xD31 +#define WM2200_ANC_CTRL45 0xD32 +#define WM2200_ANC_CTRL46 0xD33 +#define WM2200_ANC_CTRL47 0xD34 +#define WM2200_ANC_CTRL48 0xD35 +#define WM2200_ANC_CTRL49 0xD36 +#define WM2200_ANC_CTRL50 0xD37 +#define WM2200_ANC_CTRL51 0xD38 +#define WM2200_ANC_CTRL52 0xD39 +#define WM2200_ANC_CTRL53 0xD3A +#define WM2200_ANC_CTRL54 0xD3B +#define WM2200_ANC_CTRL55 0xD3C +#define WM2200_ANC_CTRL56 0xD3D +#define WM2200_ANC_CTRL57 0xD3E +#define WM2200_ANC_CTRL58 0xD3F +#define WM2200_ANC_CTRL59 0xD40 +#define WM2200_ANC_CTRL60 0xD41 +#define WM2200_ANC_CTRL61 0xD42 +#define WM2200_ANC_CTRL62 0xD43 +#define WM2200_ANC_CTRL63 0xD44 +#define WM2200_ANC_CTRL64 0xD45 +#define WM2200_ANC_CTRL65 0xD46 +#define WM2200_ANC_CTRL66 0xD47 +#define WM2200_ANC_CTRL67 0xD48 +#define WM2200_ANC_CTRL68 0xD49 +#define WM2200_ANC_CTRL69 0xD4A +#define WM2200_ANC_CTRL70 0xD4B +#define WM2200_ANC_CTRL71 0xD4C +#define WM2200_ANC_CTRL72 0xD4D +#define WM2200_ANC_CTRL73 0xD4E +#define WM2200_ANC_CTRL74 0xD4F +#define WM2200_ANC_CTRL75 0xD50 +#define WM2200_ANC_CTRL76 0xD51 +#define WM2200_ANC_CTRL77 0xD52 +#define WM2200_ANC_CTRL78 0xD53 +#define WM2200_ANC_CTRL79 0xD54 +#define WM2200_ANC_CTRL80 0xD55 +#define WM2200_ANC_CTRL81 0xD56 +#define WM2200_ANC_CTRL82 0xD57 +#define WM2200_ANC_CTRL83 0xD58 +#define WM2200_ANC_CTRL84 0xD5B +#define WM2200_ANC_CTRL85 0xD5C +#define WM2200_ANC_CTRL86 0xD5F +#define WM2200_ANC_CTRL87 0xD60 +#define WM2200_ANC_CTRL88 0xD61 +#define WM2200_ANC_CTRL89 0xD62 +#define WM2200_ANC_CTRL90 0xD63 +#define WM2200_ANC_CTRL91 0xD64 +#define WM2200_ANC_CTRL92 0xD65 +#define WM2200_ANC_CTRL93 0xD66 +#define WM2200_ANC_CTRL94 0xD67 +#define WM2200_ANC_CTRL95 0xD68 +#define WM2200_ANC_CTRL96 0xD69 +#define WM2200_DSP1_DM_0 0x3000 +#define WM2200_DSP1_DM_1 0x3001 +#define WM2200_DSP1_DM_2 0x3002 +#define WM2200_DSP1_DM_3 0x3003 +#define WM2200_DSP1_DM_2044 0x37FC +#define WM2200_DSP1_DM_2045 0x37FD +#define WM2200_DSP1_DM_2046 0x37FE +#define WM2200_DSP1_DM_2047 0x37FF +#define WM2200_DSP1_PM_0 0x3800 +#define WM2200_DSP1_PM_1 0x3801 +#define WM2200_DSP1_PM_2 0x3802 +#define WM2200_DSP1_PM_3 0x3803 +#define WM2200_DSP1_PM_4 0x3804 +#define WM2200_DSP1_PM_5 0x3805 +#define WM2200_DSP1_PM_762 0x3AFA +#define WM2200_DSP1_PM_763 0x3AFB +#define WM2200_DSP1_PM_764 0x3AFC +#define WM2200_DSP1_PM_765 0x3AFD +#define WM2200_DSP1_PM_766 0x3AFE +#define WM2200_DSP1_PM_767 0x3AFF +#define WM2200_DSP1_ZM_0 0x3C00 +#define WM2200_DSP1_ZM_1 0x3C01 +#define WM2200_DSP1_ZM_2 0x3C02 +#define WM2200_DSP1_ZM_3 0x3C03 +#define WM2200_DSP1_ZM_1020 0x3FFC +#define WM2200_DSP1_ZM_1021 0x3FFD +#define WM2200_DSP1_ZM_1022 0x3FFE +#define WM2200_DSP1_ZM_1023 0x3FFF +#define WM2200_DSP2_DM_0 0x4000 +#define WM2200_DSP2_DM_1 0x4001 +#define WM2200_DSP2_DM_2 0x4002 +#define WM2200_DSP2_DM_3 0x4003 +#define WM2200_DSP2_DM_2044 0x47FC +#define WM2200_DSP2_DM_2045 0x47FD +#define WM2200_DSP2_DM_2046 0x47FE +#define WM2200_DSP2_DM_2047 0x47FF +#define WM2200_DSP2_PM_0 0x4800 +#define WM2200_DSP2_PM_1 0x4801 +#define WM2200_DSP2_PM_2 0x4802 +#define WM2200_DSP2_PM_3 0x4803 +#define WM2200_DSP2_PM_4 0x4804 +#define WM2200_DSP2_PM_5 0x4805 +#define WM2200_DSP2_PM_762 0x4AFA +#define WM2200_DSP2_PM_763 0x4AFB +#define WM2200_DSP2_PM_764 0x4AFC +#define WM2200_DSP2_PM_765 0x4AFD +#define WM2200_DSP2_PM_766 0x4AFE +#define WM2200_DSP2_PM_767 0x4AFF +#define WM2200_DSP2_ZM_0 0x4C00 +#define WM2200_DSP2_ZM_1 0x4C01 +#define WM2200_DSP2_ZM_2 0x4C02 +#define WM2200_DSP2_ZM_3 0x4C03 +#define WM2200_DSP2_ZM_1020 0x4FFC +#define WM2200_DSP2_ZM_1021 0x4FFD +#define WM2200_DSP2_ZM_1022 0x4FFE +#define WM2200_DSP2_ZM_1023 0x4FFF + +#define WM2200_REGISTER_COUNT 494 +#define WM2200_MAX_REGISTER 0x4FFF + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - software reset + */ +#define WM2200_SW_RESET_CHIP_ID1_MASK 0xFFFF /* SW_RESET_CHIP_ID1 - [15:0] */ +#define WM2200_SW_RESET_CHIP_ID1_SHIFT 0 /* SW_RESET_CHIP_ID1 - [15:0] */ +#define WM2200_SW_RESET_CHIP_ID1_WIDTH 16 /* SW_RESET_CHIP_ID1 - [15:0] */ + +/* + * R1 (0x01) - Device Revision + */ +#define WM2200_DEVICE_REVISION_MASK 0x000F /* DEVICE_REVISION - [3:0] */ +#define WM2200_DEVICE_REVISION_SHIFT 0 /* DEVICE_REVISION - [3:0] */ +#define WM2200_DEVICE_REVISION_WIDTH 4 /* DEVICE_REVISION - [3:0] */ + +/* + * R11 (0x0B) - Tone Generator 1 + */ +#define WM2200_TONE_ENA 0x0001 /* TONE_ENA */ +#define WM2200_TONE_ENA_MASK 0x0001 /* TONE_ENA */ +#define WM2200_TONE_ENA_SHIFT 0 /* TONE_ENA */ +#define WM2200_TONE_ENA_WIDTH 1 /* TONE_ENA */ + +/* + * R258 (0x102) - Clocking 3 + */ +#define WM2200_SYSCLK_FREQ_MASK 0x0700 /* SYSCLK_FREQ - [10:8] */ +#define WM2200_SYSCLK_FREQ_SHIFT 8 /* SYSCLK_FREQ - [10:8] */ +#define WM2200_SYSCLK_FREQ_WIDTH 3 /* SYSCLK_FREQ - [10:8] */ +#define WM2200_SYSCLK_ENA 0x0040 /* SYSCLK_ENA */ +#define WM2200_SYSCLK_ENA_MASK 0x0040 /* SYSCLK_ENA */ +#define WM2200_SYSCLK_ENA_SHIFT 6 /* SYSCLK_ENA */ +#define WM2200_SYSCLK_ENA_WIDTH 1 /* SYSCLK_ENA */ +#define WM2200_SYSCLK_SRC_MASK 0x000F /* SYSCLK_SRC - [3:0] */ +#define WM2200_SYSCLK_SRC_SHIFT 0 /* SYSCLK_SRC - [3:0] */ +#define WM2200_SYSCLK_SRC_WIDTH 4 /* SYSCLK_SRC - [3:0] */ + +/* + * R259 (0x103) - Clocking 4 + */ +#define WM2200_SAMPLE_RATE_1_MASK 0x001F /* SAMPLE_RATE_1 - [4:0] */ +#define WM2200_SAMPLE_RATE_1_SHIFT 0 /* SAMPLE_RATE_1 - [4:0] */ +#define WM2200_SAMPLE_RATE_1_WIDTH 5 /* SAMPLE_RATE_1 - [4:0] */ + +/* + * R273 (0x111) - FLL Control 1 + */ +#define WM2200_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM2200_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM2200_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM2200_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R274 (0x112) - FLL Control 2 + */ +#define WM2200_FLL_OUTDIV_MASK 0x3F00 /* FLL_OUTDIV - [13:8] */ +#define WM2200_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [13:8] */ +#define WM2200_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [13:8] */ +#define WM2200_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM2200_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM2200_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R275 (0x113) - FLL Control 3 + */ +#define WM2200_FLL_FRACN_ENA 0x0001 /* FLL_FRACN_ENA */ +#define WM2200_FLL_FRACN_ENA_MASK 0x0001 /* FLL_FRACN_ENA */ +#define WM2200_FLL_FRACN_ENA_SHIFT 0 /* FLL_FRACN_ENA */ +#define WM2200_FLL_FRACN_ENA_WIDTH 1 /* FLL_FRACN_ENA */ + +/* + * R276 (0x114) - FLL Control 4 + */ +#define WM2200_FLL_THETA_MASK 0xFFFF /* FLL_THETA - [15:0] */ +#define WM2200_FLL_THETA_SHIFT 0 /* FLL_THETA - [15:0] */ +#define WM2200_FLL_THETA_WIDTH 16 /* FLL_THETA - [15:0] */ + +/* + * R278 (0x116) - FLL Control 6 + */ +#define WM2200_FLL_N_MASK 0x03FF /* FLL_N - [9:0] */ +#define WM2200_FLL_N_SHIFT 0 /* FLL_N - [9:0] */ +#define WM2200_FLL_N_WIDTH 10 /* FLL_N - [9:0] */ + +/* + * R279 (0x117) - FLL Control 7 + */ +#define WM2200_FLL_CLK_REF_DIV_MASK 0x0030 /* FLL_CLK_REF_DIV - [5:4] */ +#define WM2200_FLL_CLK_REF_DIV_SHIFT 4 /* FLL_CLK_REF_DIV - [5:4] */ +#define WM2200_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [5:4] */ +#define WM2200_FLL_CLK_REF_SRC_MASK 0x0003 /* FLL_CLK_REF_SRC - [1:0] */ +#define WM2200_FLL_CLK_REF_SRC_SHIFT 0 /* FLL_CLK_REF_SRC - [1:0] */ +#define WM2200_FLL_CLK_REF_SRC_WIDTH 2 /* FLL_CLK_REF_SRC - [1:0] */ + +/* + * R281 (0x119) - FLL EFS 1 + */ +#define WM2200_FLL_LAMBDA_MASK 0xFFFF /* FLL_LAMBDA - [15:0] */ +#define WM2200_FLL_LAMBDA_SHIFT 0 /* FLL_LAMBDA - [15:0] */ +#define WM2200_FLL_LAMBDA_WIDTH 16 /* FLL_LAMBDA - [15:0] */ + +/* + * R282 (0x11A) - FLL EFS 2 + */ +#define WM2200_FLL_EFS_ENA 0x0001 /* FLL_EFS_ENA */ +#define WM2200_FLL_EFS_ENA_MASK 0x0001 /* FLL_EFS_ENA */ +#define WM2200_FLL_EFS_ENA_SHIFT 0 /* FLL_EFS_ENA */ +#define WM2200_FLL_EFS_ENA_WIDTH 1 /* FLL_EFS_ENA */ + +/* + * R512 (0x200) - Mic Charge Pump 1 + */ +#define WM2200_CPMIC_BYPASS_MODE 0x0020 /* CPMIC_BYPASS_MODE */ +#define WM2200_CPMIC_BYPASS_MODE_MASK 0x0020 /* CPMIC_BYPASS_MODE */ +#define WM2200_CPMIC_BYPASS_MODE_SHIFT 5 /* CPMIC_BYPASS_MODE */ +#define WM2200_CPMIC_BYPASS_MODE_WIDTH 1 /* CPMIC_BYPASS_MODE */ +#define WM2200_CPMIC_ENA 0x0001 /* CPMIC_ENA */ +#define WM2200_CPMIC_ENA_MASK 0x0001 /* CPMIC_ENA */ +#define WM2200_CPMIC_ENA_SHIFT 0 /* CPMIC_ENA */ +#define WM2200_CPMIC_ENA_WIDTH 1 /* CPMIC_ENA */ + +/* + * R513 (0x201) - Mic Charge Pump 2 + */ +#define WM2200_CPMIC_LDO_VSEL_OVERRIDE_MASK 0xF800 /* CPMIC_LDO_VSEL_OVERRIDE - [15:11] */ +#define WM2200_CPMIC_LDO_VSEL_OVERRIDE_SHIFT 11 /* CPMIC_LDO_VSEL_OVERRIDE - [15:11] */ +#define WM2200_CPMIC_LDO_VSEL_OVERRIDE_WIDTH 5 /* CPMIC_LDO_VSEL_OVERRIDE - [15:11] */ + +/* + * R514 (0x202) - DM Charge Pump 1 + */ +#define WM2200_CPDM_ENA 0x0001 /* CPDM_ENA */ +#define WM2200_CPDM_ENA_MASK 0x0001 /* CPDM_ENA */ +#define WM2200_CPDM_ENA_SHIFT 0 /* CPDM_ENA */ +#define WM2200_CPDM_ENA_WIDTH 1 /* CPDM_ENA */ + +/* + * R524 (0x20C) - Mic Bias Ctrl 1 + */ +#define WM2200_MICB1_DISCH 0x0040 /* MICB1_DISCH */ +#define WM2200_MICB1_DISCH_MASK 0x0040 /* MICB1_DISCH */ +#define WM2200_MICB1_DISCH_SHIFT 6 /* MICB1_DISCH */ +#define WM2200_MICB1_DISCH_WIDTH 1 /* MICB1_DISCH */ +#define WM2200_MICB1_RATE 0x0020 /* MICB1_RATE */ +#define WM2200_MICB1_RATE_MASK 0x0020 /* MICB1_RATE */ +#define WM2200_MICB1_RATE_SHIFT 5 /* MICB1_RATE */ +#define WM2200_MICB1_RATE_WIDTH 1 /* MICB1_RATE */ +#define WM2200_MICB1_LVL_MASK 0x001C /* MICB1_LVL - [4:2] */ +#define WM2200_MICB1_LVL_SHIFT 2 /* MICB1_LVL - [4:2] */ +#define WM2200_MICB1_LVL_WIDTH 3 /* MICB1_LVL - [4:2] */ +#define WM2200_MICB1_MODE 0x0002 /* MICB1_MODE */ +#define WM2200_MICB1_MODE_MASK 0x0002 /* MICB1_MODE */ +#define WM2200_MICB1_MODE_SHIFT 1 /* MICB1_MODE */ +#define WM2200_MICB1_MODE_WIDTH 1 /* MICB1_MODE */ +#define WM2200_MICB1_ENA 0x0001 /* MICB1_ENA */ +#define WM2200_MICB1_ENA_MASK 0x0001 /* MICB1_ENA */ +#define WM2200_MICB1_ENA_SHIFT 0 /* MICB1_ENA */ +#define WM2200_MICB1_ENA_WIDTH 1 /* MICB1_ENA */ + +/* + * R525 (0x20D) - Mic Bias Ctrl 2 + */ +#define WM2200_MICB2_DISCH 0x0040 /* MICB2_DISCH */ +#define WM2200_MICB2_DISCH_MASK 0x0040 /* MICB2_DISCH */ +#define WM2200_MICB2_DISCH_SHIFT 6 /* MICB2_DISCH */ +#define WM2200_MICB2_DISCH_WIDTH 1 /* MICB2_DISCH */ +#define WM2200_MICB2_RATE 0x0020 /* MICB2_RATE */ +#define WM2200_MICB2_RATE_MASK 0x0020 /* MICB2_RATE */ +#define WM2200_MICB2_RATE_SHIFT 5 /* MICB2_RATE */ +#define WM2200_MICB2_RATE_WIDTH 1 /* MICB2_RATE */ +#define WM2200_MICB2_LVL_MASK 0x001C /* MICB2_LVL - [4:2] */ +#define WM2200_MICB2_LVL_SHIFT 2 /* MICB2_LVL - [4:2] */ +#define WM2200_MICB2_LVL_WIDTH 3 /* MICB2_LVL - [4:2] */ +#define WM2200_MICB2_MODE 0x0002 /* MICB2_MODE */ +#define WM2200_MICB2_MODE_MASK 0x0002 /* MICB2_MODE */ +#define WM2200_MICB2_MODE_SHIFT 1 /* MICB2_MODE */ +#define WM2200_MICB2_MODE_WIDTH 1 /* MICB2_MODE */ +#define WM2200_MICB2_ENA 0x0001 /* MICB2_ENA */ +#define WM2200_MICB2_ENA_MASK 0x0001 /* MICB2_ENA */ +#define WM2200_MICB2_ENA_SHIFT 0 /* MICB2_ENA */ +#define WM2200_MICB2_ENA_WIDTH 1 /* MICB2_ENA */ + +/* + * R527 (0x20F) - Ear Piece Ctrl 1 + */ +#define WM2200_EPD_LP_ENA 0x4000 /* EPD_LP_ENA */ +#define WM2200_EPD_LP_ENA_MASK 0x4000 /* EPD_LP_ENA */ +#define WM2200_EPD_LP_ENA_SHIFT 14 /* EPD_LP_ENA */ +#define WM2200_EPD_LP_ENA_WIDTH 1 /* EPD_LP_ENA */ +#define WM2200_EPD_OUTP_LP_ENA 0x2000 /* EPD_OUTP_LP_ENA */ +#define WM2200_EPD_OUTP_LP_ENA_MASK 0x2000 /* EPD_OUTP_LP_ENA */ +#define WM2200_EPD_OUTP_LP_ENA_SHIFT 13 /* EPD_OUTP_LP_ENA */ +#define WM2200_EPD_OUTP_LP_ENA_WIDTH 1 /* EPD_OUTP_LP_ENA */ +#define WM2200_EPD_RMV_SHRT_LP 0x1000 /* EPD_RMV_SHRT_LP */ +#define WM2200_EPD_RMV_SHRT_LP_MASK 0x1000 /* EPD_RMV_SHRT_LP */ +#define WM2200_EPD_RMV_SHRT_LP_SHIFT 12 /* EPD_RMV_SHRT_LP */ +#define WM2200_EPD_RMV_SHRT_LP_WIDTH 1 /* EPD_RMV_SHRT_LP */ +#define WM2200_EPD_LN_ENA 0x0800 /* EPD_LN_ENA */ +#define WM2200_EPD_LN_ENA_MASK 0x0800 /* EPD_LN_ENA */ +#define WM2200_EPD_LN_ENA_SHIFT 11 /* EPD_LN_ENA */ +#define WM2200_EPD_LN_ENA_WIDTH 1 /* EPD_LN_ENA */ +#define WM2200_EPD_OUTP_LN_ENA 0x0400 /* EPD_OUTP_LN_ENA */ +#define WM2200_EPD_OUTP_LN_ENA_MASK 0x0400 /* EPD_OUTP_LN_ENA */ +#define WM2200_EPD_OUTP_LN_ENA_SHIFT 10 /* EPD_OUTP_LN_ENA */ +#define WM2200_EPD_OUTP_LN_ENA_WIDTH 1 /* EPD_OUTP_LN_ENA */ +#define WM2200_EPD_RMV_SHRT_LN 0x0200 /* EPD_RMV_SHRT_LN */ +#define WM2200_EPD_RMV_SHRT_LN_MASK 0x0200 /* EPD_RMV_SHRT_LN */ +#define WM2200_EPD_RMV_SHRT_LN_SHIFT 9 /* EPD_RMV_SHRT_LN */ +#define WM2200_EPD_RMV_SHRT_LN_WIDTH 1 /* EPD_RMV_SHRT_LN */ + +/* + * R528 (0x210) - Ear Piece Ctrl 2 + */ +#define WM2200_EPD_RP_ENA 0x4000 /* EPD_RP_ENA */ +#define WM2200_EPD_RP_ENA_MASK 0x4000 /* EPD_RP_ENA */ +#define WM2200_EPD_RP_ENA_SHIFT 14 /* EPD_RP_ENA */ +#define WM2200_EPD_RP_ENA_WIDTH 1 /* EPD_RP_ENA */ +#define WM2200_EPD_OUTP_RP_ENA 0x2000 /* EPD_OUTP_RP_ENA */ +#define WM2200_EPD_OUTP_RP_ENA_MASK 0x2000 /* EPD_OUTP_RP_ENA */ +#define WM2200_EPD_OUTP_RP_ENA_SHIFT 13 /* EPD_OUTP_RP_ENA */ +#define WM2200_EPD_OUTP_RP_ENA_WIDTH 1 /* EPD_OUTP_RP_ENA */ +#define WM2200_EPD_RMV_SHRT_RP 0x1000 /* EPD_RMV_SHRT_RP */ +#define WM2200_EPD_RMV_SHRT_RP_MASK 0x1000 /* EPD_RMV_SHRT_RP */ +#define WM2200_EPD_RMV_SHRT_RP_SHIFT 12 /* EPD_RMV_SHRT_RP */ +#define WM2200_EPD_RMV_SHRT_RP_WIDTH 1 /* EPD_RMV_SHRT_RP */ +#define WM2200_EPD_RN_ENA 0x0800 /* EPD_RN_ENA */ +#define WM2200_EPD_RN_ENA_MASK 0x0800 /* EPD_RN_ENA */ +#define WM2200_EPD_RN_ENA_SHIFT 11 /* EPD_RN_ENA */ +#define WM2200_EPD_RN_ENA_WIDTH 1 /* EPD_RN_ENA */ +#define WM2200_EPD_OUTP_RN_ENA 0x0400 /* EPD_OUTP_RN_ENA */ +#define WM2200_EPD_OUTP_RN_ENA_MASK 0x0400 /* EPD_OUTP_RN_ENA */ +#define WM2200_EPD_OUTP_RN_ENA_SHIFT 10 /* EPD_OUTP_RN_ENA */ +#define WM2200_EPD_OUTP_RN_ENA_WIDTH 1 /* EPD_OUTP_RN_ENA */ +#define WM2200_EPD_RMV_SHRT_RN 0x0200 /* EPD_RMV_SHRT_RN */ +#define WM2200_EPD_RMV_SHRT_RN_MASK 0x0200 /* EPD_RMV_SHRT_RN */ +#define WM2200_EPD_RMV_SHRT_RN_SHIFT 9 /* EPD_RMV_SHRT_RN */ +#define WM2200_EPD_RMV_SHRT_RN_WIDTH 1 /* EPD_RMV_SHRT_RN */ + +/* + * R769 (0x301) - Input Enables + */ +#define WM2200_IN3L_ENA 0x0020 /* IN3L_ENA */ +#define WM2200_IN3L_ENA_MASK 0x0020 /* IN3L_ENA */ +#define WM2200_IN3L_ENA_SHIFT 5 /* IN3L_ENA */ +#define WM2200_IN3L_ENA_WIDTH 1 /* IN3L_ENA */ +#define WM2200_IN3R_ENA 0x0010 /* IN3R_ENA */ +#define WM2200_IN3R_ENA_MASK 0x0010 /* IN3R_ENA */ +#define WM2200_IN3R_ENA_SHIFT 4 /* IN3R_ENA */ +#define WM2200_IN3R_ENA_WIDTH 1 /* IN3R_ENA */ +#define WM2200_IN2L_ENA 0x0008 /* IN2L_ENA */ +#define WM2200_IN2L_ENA_MASK 0x0008 /* IN2L_ENA */ +#define WM2200_IN2L_ENA_SHIFT 3 /* IN2L_ENA */ +#define WM2200_IN2L_ENA_WIDTH 1 /* IN2L_ENA */ +#define WM2200_IN2R_ENA 0x0004 /* IN2R_ENA */ +#define WM2200_IN2R_ENA_MASK 0x0004 /* IN2R_ENA */ +#define WM2200_IN2R_ENA_SHIFT 2 /* IN2R_ENA */ +#define WM2200_IN2R_ENA_WIDTH 1 /* IN2R_ENA */ +#define WM2200_IN1L_ENA 0x0002 /* IN1L_ENA */ +#define WM2200_IN1L_ENA_MASK 0x0002 /* IN1L_ENA */ +#define WM2200_IN1L_ENA_SHIFT 1 /* IN1L_ENA */ +#define WM2200_IN1L_ENA_WIDTH 1 /* IN1L_ENA */ +#define WM2200_IN1R_ENA 0x0001 /* IN1R_ENA */ +#define WM2200_IN1R_ENA_MASK 0x0001 /* IN1R_ENA */ +#define WM2200_IN1R_ENA_SHIFT 0 /* IN1R_ENA */ +#define WM2200_IN1R_ENA_WIDTH 1 /* IN1R_ENA */ + +/* + * R770 (0x302) - IN1L Control + */ +#define WM2200_IN1_OSR 0x2000 /* IN1_OSR */ +#define WM2200_IN1_OSR_MASK 0x2000 /* IN1_OSR */ +#define WM2200_IN1_OSR_SHIFT 13 /* IN1_OSR */ +#define WM2200_IN1_OSR_WIDTH 1 /* IN1_OSR */ +#define WM2200_IN1_DMIC_SUP_MASK 0x1800 /* IN1_DMIC_SUP - [12:11] */ +#define WM2200_IN1_DMIC_SUP_SHIFT 11 /* IN1_DMIC_SUP - [12:11] */ +#define WM2200_IN1_DMIC_SUP_WIDTH 2 /* IN1_DMIC_SUP - [12:11] */ +#define WM2200_IN1_MODE_MASK 0x0600 /* IN1_MODE - [10:9] */ +#define WM2200_IN1_MODE_SHIFT 9 /* IN1_MODE - [10:9] */ +#define WM2200_IN1_MODE_WIDTH 2 /* IN1_MODE - [10:9] */ +#define WM2200_IN1L_PGA_VOL_MASK 0x00FE /* IN1L_PGA_VOL - [7:1] */ +#define WM2200_IN1L_PGA_VOL_SHIFT 1 /* IN1L_PGA_VOL - [7:1] */ +#define WM2200_IN1L_PGA_VOL_WIDTH 7 /* IN1L_PGA_VOL - [7:1] */ + +/* + * R771 (0x303) - IN1R Control + */ +#define WM2200_IN1R_PGA_VOL_MASK 0x00FE /* IN1R_PGA_VOL - [7:1] */ +#define WM2200_IN1R_PGA_VOL_SHIFT 1 /* IN1R_PGA_VOL - [7:1] */ +#define WM2200_IN1R_PGA_VOL_WIDTH 7 /* IN1R_PGA_VOL - [7:1] */ + +/* + * R772 (0x304) - IN2L Control + */ +#define WM2200_IN2_OSR 0x2000 /* IN2_OSR */ +#define WM2200_IN2_OSR_MASK 0x2000 /* IN2_OSR */ +#define WM2200_IN2_OSR_SHIFT 13 /* IN2_OSR */ +#define WM2200_IN2_OSR_WIDTH 1 /* IN2_OSR */ +#define WM2200_IN2_DMIC_SUP_MASK 0x1800 /* IN2_DMIC_SUP - [12:11] */ +#define WM2200_IN2_DMIC_SUP_SHIFT 11 /* IN2_DMIC_SUP - [12:11] */ +#define WM2200_IN2_DMIC_SUP_WIDTH 2 /* IN2_DMIC_SUP - [12:11] */ +#define WM2200_IN2_MODE_MASK 0x0600 /* IN2_MODE - [10:9] */ +#define WM2200_IN2_MODE_SHIFT 9 /* IN2_MODE - [10:9] */ +#define WM2200_IN2_MODE_WIDTH 2 /* IN2_MODE - [10:9] */ +#define WM2200_IN2L_PGA_VOL_MASK 0x00FE /* IN2L_PGA_VOL - [7:1] */ +#define WM2200_IN2L_PGA_VOL_SHIFT 1 /* IN2L_PGA_VOL - [7:1] */ +#define WM2200_IN2L_PGA_VOL_WIDTH 7 /* IN2L_PGA_VOL - [7:1] */ + +/* + * R773 (0x305) - IN2R Control + */ +#define WM2200_IN2R_PGA_VOL_MASK 0x00FE /* IN2R_PGA_VOL - [7:1] */ +#define WM2200_IN2R_PGA_VOL_SHIFT 1 /* IN2R_PGA_VOL - [7:1] */ +#define WM2200_IN2R_PGA_VOL_WIDTH 7 /* IN2R_PGA_VOL - [7:1] */ + +/* + * R774 (0x306) - IN3L Control + */ +#define WM2200_IN3_OSR 0x2000 /* IN3_OSR */ +#define WM2200_IN3_OSR_MASK 0x2000 /* IN3_OSR */ +#define WM2200_IN3_OSR_SHIFT 13 /* IN3_OSR */ +#define WM2200_IN3_OSR_WIDTH 1 /* IN3_OSR */ +#define WM2200_IN3_DMIC_SUP_MASK 0x1800 /* IN3_DMIC_SUP - [12:11] */ +#define WM2200_IN3_DMIC_SUP_SHIFT 11 /* IN3_DMIC_SUP - [12:11] */ +#define WM2200_IN3_DMIC_SUP_WIDTH 2 /* IN3_DMIC_SUP - [12:11] */ +#define WM2200_IN3_MODE_MASK 0x0600 /* IN3_MODE - [10:9] */ +#define WM2200_IN3_MODE_SHIFT 9 /* IN3_MODE - [10:9] */ +#define WM2200_IN3_MODE_WIDTH 2 /* IN3_MODE - [10:9] */ +#define WM2200_IN3L_PGA_VOL_MASK 0x00FE /* IN3L_PGA_VOL - [7:1] */ +#define WM2200_IN3L_PGA_VOL_SHIFT 1 /* IN3L_PGA_VOL - [7:1] */ +#define WM2200_IN3L_PGA_VOL_WIDTH 7 /* IN3L_PGA_VOL - [7:1] */ + +/* + * R775 (0x307) - IN3R Control + */ +#define WM2200_IN3R_PGA_VOL_MASK 0x00FE /* IN3R_PGA_VOL - [7:1] */ +#define WM2200_IN3R_PGA_VOL_SHIFT 1 /* IN3R_PGA_VOL - [7:1] */ +#define WM2200_IN3R_PGA_VOL_WIDTH 7 /* IN3R_PGA_VOL - [7:1] */ + +/* + * R778 (0x30A) - RXANC_SRC + */ +#define WM2200_IN_RXANC_SEL_MASK 0x0007 /* IN_RXANC_SEL - [2:0] */ +#define WM2200_IN_RXANC_SEL_SHIFT 0 /* IN_RXANC_SEL - [2:0] */ +#define WM2200_IN_RXANC_SEL_WIDTH 3 /* IN_RXANC_SEL - [2:0] */ + +/* + * R779 (0x30B) - Input Volume Ramp + */ +#define WM2200_IN_VD_RAMP_MASK 0x0070 /* IN_VD_RAMP - [6:4] */ +#define WM2200_IN_VD_RAMP_SHIFT 4 /* IN_VD_RAMP - [6:4] */ +#define WM2200_IN_VD_RAMP_WIDTH 3 /* IN_VD_RAMP - [6:4] */ +#define WM2200_IN_VI_RAMP_MASK 0x0007 /* IN_VI_RAMP - [2:0] */ +#define WM2200_IN_VI_RAMP_SHIFT 0 /* IN_VI_RAMP - [2:0] */ +#define WM2200_IN_VI_RAMP_WIDTH 3 /* IN_VI_RAMP - [2:0] */ + +/* + * R780 (0x30C) - ADC Digital Volume 1L + */ +#define WM2200_IN_VU 0x0200 /* IN_VU */ +#define WM2200_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM2200_IN_VU_SHIFT 9 /* IN_VU */ +#define WM2200_IN_VU_WIDTH 1 /* IN_VU */ +#define WM2200_IN1L_MUTE 0x0100 /* IN1L_MUTE */ +#define WM2200_IN1L_MUTE_MASK 0x0100 /* IN1L_MUTE */ +#define WM2200_IN1L_MUTE_SHIFT 8 /* IN1L_MUTE */ +#define WM2200_IN1L_MUTE_WIDTH 1 /* IN1L_MUTE */ +#define WM2200_IN1L_DIG_VOL_MASK 0x00FF /* IN1L_DIG_VOL - [7:0] */ +#define WM2200_IN1L_DIG_VOL_SHIFT 0 /* IN1L_DIG_VOL - [7:0] */ +#define WM2200_IN1L_DIG_VOL_WIDTH 8 /* IN1L_DIG_VOL - [7:0] */ + +/* + * R781 (0x30D) - ADC Digital Volume 1R + */ +#define WM2200_IN_VU 0x0200 /* IN_VU */ +#define WM2200_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM2200_IN_VU_SHIFT 9 /* IN_VU */ +#define WM2200_IN_VU_WIDTH 1 /* IN_VU */ +#define WM2200_IN1R_MUTE 0x0100 /* IN1R_MUTE */ +#define WM2200_IN1R_MUTE_MASK 0x0100 /* IN1R_MUTE */ +#define WM2200_IN1R_MUTE_SHIFT 8 /* IN1R_MUTE */ +#define WM2200_IN1R_MUTE_WIDTH 1 /* IN1R_MUTE */ +#define WM2200_IN1R_DIG_VOL_MASK 0x00FF /* IN1R_DIG_VOL - [7:0] */ +#define WM2200_IN1R_DIG_VOL_SHIFT 0 /* IN1R_DIG_VOL - [7:0] */ +#define WM2200_IN1R_DIG_VOL_WIDTH 8 /* IN1R_DIG_VOL - [7:0] */ + +/* + * R782 (0x30E) - ADC Digital Volume 2L + */ +#define WM2200_IN_VU 0x0200 /* IN_VU */ +#define WM2200_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM2200_IN_VU_SHIFT 9 /* IN_VU */ +#define WM2200_IN_VU_WIDTH 1 /* IN_VU */ +#define WM2200_IN2L_MUTE 0x0100 /* IN2L_MUTE */ +#define WM2200_IN2L_MUTE_MASK 0x0100 /* IN2L_MUTE */ +#define WM2200_IN2L_MUTE_SHIFT 8 /* IN2L_MUTE */ +#define WM2200_IN2L_MUTE_WIDTH 1 /* IN2L_MUTE */ +#define WM2200_IN2L_DIG_VOL_MASK 0x00FF /* IN2L_DIG_VOL - [7:0] */ +#define WM2200_IN2L_DIG_VOL_SHIFT 0 /* IN2L_DIG_VOL - [7:0] */ +#define WM2200_IN2L_DIG_VOL_WIDTH 8 /* IN2L_DIG_VOL - [7:0] */ + +/* + * R783 (0x30F) - ADC Digital Volume 2R + */ +#define WM2200_IN_VU 0x0200 /* IN_VU */ +#define WM2200_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM2200_IN_VU_SHIFT 9 /* IN_VU */ +#define WM2200_IN_VU_WIDTH 1 /* IN_VU */ +#define WM2200_IN2R_MUTE 0x0100 /* IN2R_MUTE */ +#define WM2200_IN2R_MUTE_MASK 0x0100 /* IN2R_MUTE */ +#define WM2200_IN2R_MUTE_SHIFT 8 /* IN2R_MUTE */ +#define WM2200_IN2R_MUTE_WIDTH 1 /* IN2R_MUTE */ +#define WM2200_IN2R_DIG_VOL_MASK 0x00FF /* IN2R_DIG_VOL - [7:0] */ +#define WM2200_IN2R_DIG_VOL_SHIFT 0 /* IN2R_DIG_VOL - [7:0] */ +#define WM2200_IN2R_DIG_VOL_WIDTH 8 /* IN2R_DIG_VOL - [7:0] */ + +/* + * R784 (0x310) - ADC Digital Volume 3L + */ +#define WM2200_IN_VU 0x0200 /* IN_VU */ +#define WM2200_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM2200_IN_VU_SHIFT 9 /* IN_VU */ +#define WM2200_IN_VU_WIDTH 1 /* IN_VU */ +#define WM2200_IN3L_MUTE 0x0100 /* IN3L_MUTE */ +#define WM2200_IN3L_MUTE_MASK 0x0100 /* IN3L_MUTE */ +#define WM2200_IN3L_MUTE_SHIFT 8 /* IN3L_MUTE */ +#define WM2200_IN3L_MUTE_WIDTH 1 /* IN3L_MUTE */ +#define WM2200_IN3L_DIG_VOL_MASK 0x00FF /* IN3L_DIG_VOL - [7:0] */ +#define WM2200_IN3L_DIG_VOL_SHIFT 0 /* IN3L_DIG_VOL - [7:0] */ +#define WM2200_IN3L_DIG_VOL_WIDTH 8 /* IN3L_DIG_VOL - [7:0] */ + +/* + * R785 (0x311) - ADC Digital Volume 3R + */ +#define WM2200_IN_VU 0x0200 /* IN_VU */ +#define WM2200_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM2200_IN_VU_SHIFT 9 /* IN_VU */ +#define WM2200_IN_VU_WIDTH 1 /* IN_VU */ +#define WM2200_IN3R_MUTE 0x0100 /* IN3R_MUTE */ +#define WM2200_IN3R_MUTE_MASK 0x0100 /* IN3R_MUTE */ +#define WM2200_IN3R_MUTE_SHIFT 8 /* IN3R_MUTE */ +#define WM2200_IN3R_MUTE_WIDTH 1 /* IN3R_MUTE */ +#define WM2200_IN3R_DIG_VOL_MASK 0x00FF /* IN3R_DIG_VOL - [7:0] */ +#define WM2200_IN3R_DIG_VOL_SHIFT 0 /* IN3R_DIG_VOL - [7:0] */ +#define WM2200_IN3R_DIG_VOL_WIDTH 8 /* IN3R_DIG_VOL - [7:0] */ + +/* + * R1024 (0x400) - Output Enables + */ +#define WM2200_OUT2L_ENA 0x0008 /* OUT2L_ENA */ +#define WM2200_OUT2L_ENA_MASK 0x0008 /* OUT2L_ENA */ +#define WM2200_OUT2L_ENA_SHIFT 3 /* OUT2L_ENA */ +#define WM2200_OUT2L_ENA_WIDTH 1 /* OUT2L_ENA */ +#define WM2200_OUT2R_ENA 0x0004 /* OUT2R_ENA */ +#define WM2200_OUT2R_ENA_MASK 0x0004 /* OUT2R_ENA */ +#define WM2200_OUT2R_ENA_SHIFT 2 /* OUT2R_ENA */ +#define WM2200_OUT2R_ENA_WIDTH 1 /* OUT2R_ENA */ +#define WM2200_OUT1L_ENA 0x0002 /* OUT1L_ENA */ +#define WM2200_OUT1L_ENA_MASK 0x0002 /* OUT1L_ENA */ +#define WM2200_OUT1L_ENA_SHIFT 1 /* OUT1L_ENA */ +#define WM2200_OUT1L_ENA_WIDTH 1 /* OUT1L_ENA */ +#define WM2200_OUT1R_ENA 0x0001 /* OUT1R_ENA */ +#define WM2200_OUT1R_ENA_MASK 0x0001 /* OUT1R_ENA */ +#define WM2200_OUT1R_ENA_SHIFT 0 /* OUT1R_ENA */ +#define WM2200_OUT1R_ENA_WIDTH 1 /* OUT1R_ENA */ + +/* + * R1025 (0x401) - DAC Volume Limit 1L + */ +#define WM2200_OUT1_OSR 0x2000 /* OUT1_OSR */ +#define WM2200_OUT1_OSR_MASK 0x2000 /* OUT1_OSR */ +#define WM2200_OUT1_OSR_SHIFT 13 /* OUT1_OSR */ +#define WM2200_OUT1_OSR_WIDTH 1 /* OUT1_OSR */ +#define WM2200_OUT1L_ANC_SRC 0x0800 /* OUT1L_ANC_SRC */ +#define WM2200_OUT1L_ANC_SRC_MASK 0x0800 /* OUT1L_ANC_SRC */ +#define WM2200_OUT1L_ANC_SRC_SHIFT 11 /* OUT1L_ANC_SRC */ +#define WM2200_OUT1L_ANC_SRC_WIDTH 1 /* OUT1L_ANC_SRC */ +#define WM2200_OUT1L_PGA_VOL_MASK 0x00FE /* OUT1L_PGA_VOL - [7:1] */ +#define WM2200_OUT1L_PGA_VOL_SHIFT 1 /* OUT1L_PGA_VOL - [7:1] */ +#define WM2200_OUT1L_PGA_VOL_WIDTH 7 /* OUT1L_PGA_VOL - [7:1] */ + +/* + * R1026 (0x402) - DAC Volume Limit 1R + */ +#define WM2200_OUT1R_ANC_SRC 0x0800 /* OUT1R_ANC_SRC */ +#define WM2200_OUT1R_ANC_SRC_MASK 0x0800 /* OUT1R_ANC_SRC */ +#define WM2200_OUT1R_ANC_SRC_SHIFT 11 /* OUT1R_ANC_SRC */ +#define WM2200_OUT1R_ANC_SRC_WIDTH 1 /* OUT1R_ANC_SRC */ +#define WM2200_OUT1R_PGA_VOL_MASK 0x00FE /* OUT1R_PGA_VOL - [7:1] */ +#define WM2200_OUT1R_PGA_VOL_SHIFT 1 /* OUT1R_PGA_VOL - [7:1] */ +#define WM2200_OUT1R_PGA_VOL_WIDTH 7 /* OUT1R_PGA_VOL - [7:1] */ + +/* + * R1027 (0x403) - DAC Volume Limit 2L + */ +#define WM2200_OUT2_OSR 0x2000 /* OUT2_OSR */ +#define WM2200_OUT2_OSR_MASK 0x2000 /* OUT2_OSR */ +#define WM2200_OUT2_OSR_SHIFT 13 /* OUT2_OSR */ +#define WM2200_OUT2_OSR_WIDTH 1 /* OUT2_OSR */ +#define WM2200_OUT2L_ANC_SRC 0x0800 /* OUT2L_ANC_SRC */ +#define WM2200_OUT2L_ANC_SRC_MASK 0x0800 /* OUT2L_ANC_SRC */ +#define WM2200_OUT2L_ANC_SRC_SHIFT 11 /* OUT2L_ANC_SRC */ +#define WM2200_OUT2L_ANC_SRC_WIDTH 1 /* OUT2L_ANC_SRC */ + +/* + * R1028 (0x404) - DAC Volume Limit 2R + */ +#define WM2200_OUT2R_ANC_SRC 0x0800 /* OUT2R_ANC_SRC */ +#define WM2200_OUT2R_ANC_SRC_MASK 0x0800 /* OUT2R_ANC_SRC */ +#define WM2200_OUT2R_ANC_SRC_SHIFT 11 /* OUT2R_ANC_SRC */ +#define WM2200_OUT2R_ANC_SRC_WIDTH 1 /* OUT2R_ANC_SRC */ + +/* + * R1033 (0x409) - DAC AEC Control 1 + */ +#define WM2200_AEC_LOOPBACK_ENA 0x0004 /* AEC_LOOPBACK_ENA */ +#define WM2200_AEC_LOOPBACK_ENA_MASK 0x0004 /* AEC_LOOPBACK_ENA */ +#define WM2200_AEC_LOOPBACK_ENA_SHIFT 2 /* AEC_LOOPBACK_ENA */ +#define WM2200_AEC_LOOPBACK_ENA_WIDTH 1 /* AEC_LOOPBACK_ENA */ +#define WM2200_AEC_LOOPBACK_SRC_MASK 0x0003 /* AEC_LOOPBACK_SRC - [1:0] */ +#define WM2200_AEC_LOOPBACK_SRC_SHIFT 0 /* AEC_LOOPBACK_SRC - [1:0] */ +#define WM2200_AEC_LOOPBACK_SRC_WIDTH 2 /* AEC_LOOPBACK_SRC - [1:0] */ + +/* + * R1034 (0x40A) - Output Volume Ramp + */ +#define WM2200_OUT_VD_RAMP_MASK 0x0070 /* OUT_VD_RAMP - [6:4] */ +#define WM2200_OUT_VD_RAMP_SHIFT 4 /* OUT_VD_RAMP - [6:4] */ +#define WM2200_OUT_VD_RAMP_WIDTH 3 /* OUT_VD_RAMP - [6:4] */ +#define WM2200_OUT_VI_RAMP_MASK 0x0007 /* OUT_VI_RAMP - [2:0] */ +#define WM2200_OUT_VI_RAMP_SHIFT 0 /* OUT_VI_RAMP - [2:0] */ +#define WM2200_OUT_VI_RAMP_WIDTH 3 /* OUT_VI_RAMP - [2:0] */ + +/* + * R1035 (0x40B) - DAC Digital Volume 1L + */ +#define WM2200_OUT_VU 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM2200_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM2200_OUT1L_MUTE 0x0100 /* OUT1L_MUTE */ +#define WM2200_OUT1L_MUTE_MASK 0x0100 /* OUT1L_MUTE */ +#define WM2200_OUT1L_MUTE_SHIFT 8 /* OUT1L_MUTE */ +#define WM2200_OUT1L_MUTE_WIDTH 1 /* OUT1L_MUTE */ +#define WM2200_OUT1L_VOL_MASK 0x00FF /* OUT1L_VOL - [7:0] */ +#define WM2200_OUT1L_VOL_SHIFT 0 /* OUT1L_VOL - [7:0] */ +#define WM2200_OUT1L_VOL_WIDTH 8 /* OUT1L_VOL - [7:0] */ + +/* + * R1036 (0x40C) - DAC Digital Volume 1R + */ +#define WM2200_OUT_VU 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM2200_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM2200_OUT1R_MUTE 0x0100 /* OUT1R_MUTE */ +#define WM2200_OUT1R_MUTE_MASK 0x0100 /* OUT1R_MUTE */ +#define WM2200_OUT1R_MUTE_SHIFT 8 /* OUT1R_MUTE */ +#define WM2200_OUT1R_MUTE_WIDTH 1 /* OUT1R_MUTE */ +#define WM2200_OUT1R_VOL_MASK 0x00FF /* OUT1R_VOL - [7:0] */ +#define WM2200_OUT1R_VOL_SHIFT 0 /* OUT1R_VOL - [7:0] */ +#define WM2200_OUT1R_VOL_WIDTH 8 /* OUT1R_VOL - [7:0] */ + +/* + * R1037 (0x40D) - DAC Digital Volume 2L + */ +#define WM2200_OUT_VU 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM2200_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM2200_OUT2L_MUTE 0x0100 /* OUT2L_MUTE */ +#define WM2200_OUT2L_MUTE_MASK 0x0100 /* OUT2L_MUTE */ +#define WM2200_OUT2L_MUTE_SHIFT 8 /* OUT2L_MUTE */ +#define WM2200_OUT2L_MUTE_WIDTH 1 /* OUT2L_MUTE */ +#define WM2200_OUT2L_VOL_MASK 0x00FF /* OUT2L_VOL - [7:0] */ +#define WM2200_OUT2L_VOL_SHIFT 0 /* OUT2L_VOL - [7:0] */ +#define WM2200_OUT2L_VOL_WIDTH 8 /* OUT2L_VOL - [7:0] */ + +/* + * R1038 (0x40E) - DAC Digital Volume 2R + */ +#define WM2200_OUT_VU 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM2200_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM2200_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM2200_OUT2R_MUTE 0x0100 /* OUT2R_MUTE */ +#define WM2200_OUT2R_MUTE_MASK 0x0100 /* OUT2R_MUTE */ +#define WM2200_OUT2R_MUTE_SHIFT 8 /* OUT2R_MUTE */ +#define WM2200_OUT2R_MUTE_WIDTH 1 /* OUT2R_MUTE */ +#define WM2200_OUT2R_VOL_MASK 0x00FF /* OUT2R_VOL - [7:0] */ +#define WM2200_OUT2R_VOL_SHIFT 0 /* OUT2R_VOL - [7:0] */ +#define WM2200_OUT2R_VOL_WIDTH 8 /* OUT2R_VOL - [7:0] */ + +/* + * R1047 (0x417) - PDM 1 + */ +#define WM2200_SPK1R_MUTE 0x2000 /* SPK1R_MUTE */ +#define WM2200_SPK1R_MUTE_MASK 0x2000 /* SPK1R_MUTE */ +#define WM2200_SPK1R_MUTE_SHIFT 13 /* SPK1R_MUTE */ +#define WM2200_SPK1R_MUTE_WIDTH 1 /* SPK1R_MUTE */ +#define WM2200_SPK1L_MUTE 0x1000 /* SPK1L_MUTE */ +#define WM2200_SPK1L_MUTE_MASK 0x1000 /* SPK1L_MUTE */ +#define WM2200_SPK1L_MUTE_SHIFT 12 /* SPK1L_MUTE */ +#define WM2200_SPK1L_MUTE_WIDTH 1 /* SPK1L_MUTE */ +#define WM2200_SPK1_MUTE_ENDIAN 0x0100 /* SPK1_MUTE_ENDIAN */ +#define WM2200_SPK1_MUTE_ENDIAN_MASK 0x0100 /* SPK1_MUTE_ENDIAN */ +#define WM2200_SPK1_MUTE_ENDIAN_SHIFT 8 /* SPK1_MUTE_ENDIAN */ +#define WM2200_SPK1_MUTE_ENDIAN_WIDTH 1 /* SPK1_MUTE_ENDIAN */ +#define WM2200_SPK1_MUTE_SEQL_MASK 0x00FF /* SPK1_MUTE_SEQL - [7:0] */ +#define WM2200_SPK1_MUTE_SEQL_SHIFT 0 /* SPK1_MUTE_SEQL - [7:0] */ +#define WM2200_SPK1_MUTE_SEQL_WIDTH 8 /* SPK1_MUTE_SEQL - [7:0] */ + +/* + * R1048 (0x418) - PDM 2 + */ +#define WM2200_SPK1_FMT 0x0001 /* SPK1_FMT */ +#define WM2200_SPK1_FMT_MASK 0x0001 /* SPK1_FMT */ +#define WM2200_SPK1_FMT_SHIFT 0 /* SPK1_FMT */ +#define WM2200_SPK1_FMT_WIDTH 1 /* SPK1_FMT */ + +/* + * R1280 (0x500) - Audio IF 1_1 + */ +#define WM2200_AIF1_BCLK_INV 0x0040 /* AIF1_BCLK_INV */ +#define WM2200_AIF1_BCLK_INV_MASK 0x0040 /* AIF1_BCLK_INV */ +#define WM2200_AIF1_BCLK_INV_SHIFT 6 /* AIF1_BCLK_INV */ +#define WM2200_AIF1_BCLK_INV_WIDTH 1 /* AIF1_BCLK_INV */ +#define WM2200_AIF1_BCLK_FRC 0x0020 /* AIF1_BCLK_FRC */ +#define WM2200_AIF1_BCLK_FRC_MASK 0x0020 /* AIF1_BCLK_FRC */ +#define WM2200_AIF1_BCLK_FRC_SHIFT 5 /* AIF1_BCLK_FRC */ +#define WM2200_AIF1_BCLK_FRC_WIDTH 1 /* AIF1_BCLK_FRC */ +#define WM2200_AIF1_BCLK_MSTR 0x0010 /* AIF1_BCLK_MSTR */ +#define WM2200_AIF1_BCLK_MSTR_MASK 0x0010 /* AIF1_BCLK_MSTR */ +#define WM2200_AIF1_BCLK_MSTR_SHIFT 4 /* AIF1_BCLK_MSTR */ +#define WM2200_AIF1_BCLK_MSTR_WIDTH 1 /* AIF1_BCLK_MSTR */ +#define WM2200_AIF1_BCLK_DIV_MASK 0x000F /* AIF1_BCLK_DIV - [3:0] */ +#define WM2200_AIF1_BCLK_DIV_SHIFT 0 /* AIF1_BCLK_DIV - [3:0] */ +#define WM2200_AIF1_BCLK_DIV_WIDTH 4 /* AIF1_BCLK_DIV - [3:0] */ + +/* + * R1281 (0x501) - Audio IF 1_2 + */ +#define WM2200_AIF1TX_DAT_TRI 0x0020 /* AIF1TX_DAT_TRI */ +#define WM2200_AIF1TX_DAT_TRI_MASK 0x0020 /* AIF1TX_DAT_TRI */ +#define WM2200_AIF1TX_DAT_TRI_SHIFT 5 /* AIF1TX_DAT_TRI */ +#define WM2200_AIF1TX_DAT_TRI_WIDTH 1 /* AIF1TX_DAT_TRI */ +#define WM2200_AIF1TX_LRCLK_SRC 0x0008 /* AIF1TX_LRCLK_SRC */ +#define WM2200_AIF1TX_LRCLK_SRC_MASK 0x0008 /* AIF1TX_LRCLK_SRC */ +#define WM2200_AIF1TX_LRCLK_SRC_SHIFT 3 /* AIF1TX_LRCLK_SRC */ +#define WM2200_AIF1TX_LRCLK_SRC_WIDTH 1 /* AIF1TX_LRCLK_SRC */ +#define WM2200_AIF1TX_LRCLK_INV 0x0004 /* AIF1TX_LRCLK_INV */ +#define WM2200_AIF1TX_LRCLK_INV_MASK 0x0004 /* AIF1TX_LRCLK_INV */ +#define WM2200_AIF1TX_LRCLK_INV_SHIFT 2 /* AIF1TX_LRCLK_INV */ +#define WM2200_AIF1TX_LRCLK_INV_WIDTH 1 /* AIF1TX_LRCLK_INV */ +#define WM2200_AIF1TX_LRCLK_FRC 0x0002 /* AIF1TX_LRCLK_FRC */ +#define WM2200_AIF1TX_LRCLK_FRC_MASK 0x0002 /* AIF1TX_LRCLK_FRC */ +#define WM2200_AIF1TX_LRCLK_FRC_SHIFT 1 /* AIF1TX_LRCLK_FRC */ +#define WM2200_AIF1TX_LRCLK_FRC_WIDTH 1 /* AIF1TX_LRCLK_FRC */ +#define WM2200_AIF1TX_LRCLK_MSTR 0x0001 /* AIF1TX_LRCLK_MSTR */ +#define WM2200_AIF1TX_LRCLK_MSTR_MASK 0x0001 /* AIF1TX_LRCLK_MSTR */ +#define WM2200_AIF1TX_LRCLK_MSTR_SHIFT 0 /* AIF1TX_LRCLK_MSTR */ +#define WM2200_AIF1TX_LRCLK_MSTR_WIDTH 1 /* AIF1TX_LRCLK_MSTR */ + +/* + * R1282 (0x502) - Audio IF 1_3 + */ +#define WM2200_AIF1RX_LRCLK_INV 0x0004 /* AIF1RX_LRCLK_INV */ +#define WM2200_AIF1RX_LRCLK_INV_MASK 0x0004 /* AIF1RX_LRCLK_INV */ +#define WM2200_AIF1RX_LRCLK_INV_SHIFT 2 /* AIF1RX_LRCLK_INV */ +#define WM2200_AIF1RX_LRCLK_INV_WIDTH 1 /* AIF1RX_LRCLK_INV */ +#define WM2200_AIF1RX_LRCLK_FRC 0x0002 /* AIF1RX_LRCLK_FRC */ +#define WM2200_AIF1RX_LRCLK_FRC_MASK 0x0002 /* AIF1RX_LRCLK_FRC */ +#define WM2200_AIF1RX_LRCLK_FRC_SHIFT 1 /* AIF1RX_LRCLK_FRC */ +#define WM2200_AIF1RX_LRCLK_FRC_WIDTH 1 /* AIF1RX_LRCLK_FRC */ +#define WM2200_AIF1RX_LRCLK_MSTR 0x0001 /* AIF1RX_LRCLK_MSTR */ +#define WM2200_AIF1RX_LRCLK_MSTR_MASK 0x0001 /* AIF1RX_LRCLK_MSTR */ +#define WM2200_AIF1RX_LRCLK_MSTR_SHIFT 0 /* AIF1RX_LRCLK_MSTR */ +#define WM2200_AIF1RX_LRCLK_MSTR_WIDTH 1 /* AIF1RX_LRCLK_MSTR */ + +/* + * R1283 (0x503) - Audio IF 1_4 + */ +#define WM2200_AIF1_TRI 0x0040 /* AIF1_TRI */ +#define WM2200_AIF1_TRI_MASK 0x0040 /* AIF1_TRI */ +#define WM2200_AIF1_TRI_SHIFT 6 /* AIF1_TRI */ +#define WM2200_AIF1_TRI_WIDTH 1 /* AIF1_TRI */ + +/* + * R1284 (0x504) - Audio IF 1_5 + */ +#define WM2200_AIF1_FMT_MASK 0x0007 /* AIF1_FMT - [2:0] */ +#define WM2200_AIF1_FMT_SHIFT 0 /* AIF1_FMT - [2:0] */ +#define WM2200_AIF1_FMT_WIDTH 3 /* AIF1_FMT - [2:0] */ + +/* + * R1285 (0x505) - Audio IF 1_6 + */ +#define WM2200_AIF1TX_BCPF_MASK 0x07FF /* AIF1TX_BCPF - [10:0] */ +#define WM2200_AIF1TX_BCPF_SHIFT 0 /* AIF1TX_BCPF - [10:0] */ +#define WM2200_AIF1TX_BCPF_WIDTH 11 /* AIF1TX_BCPF - [10:0] */ + +/* + * R1286 (0x506) - Audio IF 1_7 + */ +#define WM2200_AIF1RX_BCPF_MASK 0x07FF /* AIF1RX_BCPF - [10:0] */ +#define WM2200_AIF1RX_BCPF_SHIFT 0 /* AIF1RX_BCPF - [10:0] */ +#define WM2200_AIF1RX_BCPF_WIDTH 11 /* AIF1RX_BCPF - [10:0] */ + +/* + * R1287 (0x507) - Audio IF 1_8 + */ +#define WM2200_AIF1TX_WL_MASK 0x3F00 /* AIF1TX_WL - [13:8] */ +#define WM2200_AIF1TX_WL_SHIFT 8 /* AIF1TX_WL - [13:8] */ +#define WM2200_AIF1TX_WL_WIDTH 6 /* AIF1TX_WL - [13:8] */ +#define WM2200_AIF1TX_SLOT_LEN_MASK 0x00FF /* AIF1TX_SLOT_LEN - [7:0] */ +#define WM2200_AIF1TX_SLOT_LEN_SHIFT 0 /* AIF1TX_SLOT_LEN - [7:0] */ +#define WM2200_AIF1TX_SLOT_LEN_WIDTH 8 /* AIF1TX_SLOT_LEN - [7:0] */ + +/* + * R1288 (0x508) - Audio IF 1_9 + */ +#define WM2200_AIF1RX_WL_MASK 0x3F00 /* AIF1RX_WL - [13:8] */ +#define WM2200_AIF1RX_WL_SHIFT 8 /* AIF1RX_WL - [13:8] */ +#define WM2200_AIF1RX_WL_WIDTH 6 /* AIF1RX_WL - [13:8] */ +#define WM2200_AIF1RX_SLOT_LEN_MASK 0x00FF /* AIF1RX_SLOT_LEN - [7:0] */ +#define WM2200_AIF1RX_SLOT_LEN_SHIFT 0 /* AIF1RX_SLOT_LEN - [7:0] */ +#define WM2200_AIF1RX_SLOT_LEN_WIDTH 8 /* AIF1RX_SLOT_LEN - [7:0] */ + +/* + * R1289 (0x509) - Audio IF 1_10 + */ +#define WM2200_AIF1TX1_SLOT_MASK 0x003F /* AIF1TX1_SLOT - [5:0] */ +#define WM2200_AIF1TX1_SLOT_SHIFT 0 /* AIF1TX1_SLOT - [5:0] */ +#define WM2200_AIF1TX1_SLOT_WIDTH 6 /* AIF1TX1_SLOT - [5:0] */ + +/* + * R1290 (0x50A) - Audio IF 1_11 + */ +#define WM2200_AIF1TX2_SLOT_MASK 0x003F /* AIF1TX2_SLOT - [5:0] */ +#define WM2200_AIF1TX2_SLOT_SHIFT 0 /* AIF1TX2_SLOT - [5:0] */ +#define WM2200_AIF1TX2_SLOT_WIDTH 6 /* AIF1TX2_SLOT - [5:0] */ + +/* + * R1291 (0x50B) - Audio IF 1_12 + */ +#define WM2200_AIF1TX3_SLOT_MASK 0x003F /* AIF1TX3_SLOT - [5:0] */ +#define WM2200_AIF1TX3_SLOT_SHIFT 0 /* AIF1TX3_SLOT - [5:0] */ +#define WM2200_AIF1TX3_SLOT_WIDTH 6 /* AIF1TX3_SLOT - [5:0] */ + +/* + * R1292 (0x50C) - Audio IF 1_13 + */ +#define WM2200_AIF1TX4_SLOT_MASK 0x003F /* AIF1TX4_SLOT - [5:0] */ +#define WM2200_AIF1TX4_SLOT_SHIFT 0 /* AIF1TX4_SLOT - [5:0] */ +#define WM2200_AIF1TX4_SLOT_WIDTH 6 /* AIF1TX4_SLOT - [5:0] */ + +/* + * R1293 (0x50D) - Audio IF 1_14 + */ +#define WM2200_AIF1TX5_SLOT_MASK 0x003F /* AIF1TX5_SLOT - [5:0] */ +#define WM2200_AIF1TX5_SLOT_SHIFT 0 /* AIF1TX5_SLOT - [5:0] */ +#define WM2200_AIF1TX5_SLOT_WIDTH 6 /* AIF1TX5_SLOT - [5:0] */ + +/* + * R1294 (0x50E) - Audio IF 1_15 + */ +#define WM2200_AIF1TX6_SLOT_MASK 0x003F /* AIF1TX6_SLOT - [5:0] */ +#define WM2200_AIF1TX6_SLOT_SHIFT 0 /* AIF1TX6_SLOT - [5:0] */ +#define WM2200_AIF1TX6_SLOT_WIDTH 6 /* AIF1TX6_SLOT - [5:0] */ + +/* + * R1295 (0x50F) - Audio IF 1_16 + */ +#define WM2200_AIF1RX1_SLOT_MASK 0x003F /* AIF1RX1_SLOT - [5:0] */ +#define WM2200_AIF1RX1_SLOT_SHIFT 0 /* AIF1RX1_SLOT - [5:0] */ +#define WM2200_AIF1RX1_SLOT_WIDTH 6 /* AIF1RX1_SLOT - [5:0] */ + +/* + * R1296 (0x510) - Audio IF 1_17 + */ +#define WM2200_AIF1RX2_SLOT_MASK 0x003F /* AIF1RX2_SLOT - [5:0] */ +#define WM2200_AIF1RX2_SLOT_SHIFT 0 /* AIF1RX2_SLOT - [5:0] */ +#define WM2200_AIF1RX2_SLOT_WIDTH 6 /* AIF1RX2_SLOT - [5:0] */ + +/* + * R1297 (0x511) - Audio IF 1_18 + */ +#define WM2200_AIF1RX3_SLOT_MASK 0x003F /* AIF1RX3_SLOT - [5:0] */ +#define WM2200_AIF1RX3_SLOT_SHIFT 0 /* AIF1RX3_SLOT - [5:0] */ +#define WM2200_AIF1RX3_SLOT_WIDTH 6 /* AIF1RX3_SLOT - [5:0] */ + +/* + * R1298 (0x512) - Audio IF 1_19 + */ +#define WM2200_AIF1RX4_SLOT_MASK 0x003F /* AIF1RX4_SLOT - [5:0] */ +#define WM2200_AIF1RX4_SLOT_SHIFT 0 /* AIF1RX4_SLOT - [5:0] */ +#define WM2200_AIF1RX4_SLOT_WIDTH 6 /* AIF1RX4_SLOT - [5:0] */ + +/* + * R1299 (0x513) - Audio IF 1_20 + */ +#define WM2200_AIF1RX5_SLOT_MASK 0x003F /* AIF1RX5_SLOT - [5:0] */ +#define WM2200_AIF1RX5_SLOT_SHIFT 0 /* AIF1RX5_SLOT - [5:0] */ +#define WM2200_AIF1RX5_SLOT_WIDTH 6 /* AIF1RX5_SLOT - [5:0] */ + +/* + * R1300 (0x514) - Audio IF 1_21 + */ +#define WM2200_AIF1RX6_SLOT_MASK 0x003F /* AIF1RX6_SLOT - [5:0] */ +#define WM2200_AIF1RX6_SLOT_SHIFT 0 /* AIF1RX6_SLOT - [5:0] */ +#define WM2200_AIF1RX6_SLOT_WIDTH 6 /* AIF1RX6_SLOT - [5:0] */ + +/* + * R1301 (0x515) - Audio IF 1_22 + */ +#define WM2200_AIF1RX6_ENA 0x0800 /* AIF1RX6_ENA */ +#define WM2200_AIF1RX6_ENA_MASK 0x0800 /* AIF1RX6_ENA */ +#define WM2200_AIF1RX6_ENA_SHIFT 11 /* AIF1RX6_ENA */ +#define WM2200_AIF1RX6_ENA_WIDTH 1 /* AIF1RX6_ENA */ +#define WM2200_AIF1RX5_ENA 0x0400 /* AIF1RX5_ENA */ +#define WM2200_AIF1RX5_ENA_MASK 0x0400 /* AIF1RX5_ENA */ +#define WM2200_AIF1RX5_ENA_SHIFT 10 /* AIF1RX5_ENA */ +#define WM2200_AIF1RX5_ENA_WIDTH 1 /* AIF1RX5_ENA */ +#define WM2200_AIF1RX4_ENA 0x0200 /* AIF1RX4_ENA */ +#define WM2200_AIF1RX4_ENA_MASK 0x0200 /* AIF1RX4_ENA */ +#define WM2200_AIF1RX4_ENA_SHIFT 9 /* AIF1RX4_ENA */ +#define WM2200_AIF1RX4_ENA_WIDTH 1 /* AIF1RX4_ENA */ +#define WM2200_AIF1RX3_ENA 0x0100 /* AIF1RX3_ENA */ +#define WM2200_AIF1RX3_ENA_MASK 0x0100 /* AIF1RX3_ENA */ +#define WM2200_AIF1RX3_ENA_SHIFT 8 /* AIF1RX3_ENA */ +#define WM2200_AIF1RX3_ENA_WIDTH 1 /* AIF1RX3_ENA */ +#define WM2200_AIF1RX2_ENA 0x0080 /* AIF1RX2_ENA */ +#define WM2200_AIF1RX2_ENA_MASK 0x0080 /* AIF1RX2_ENA */ +#define WM2200_AIF1RX2_ENA_SHIFT 7 /* AIF1RX2_ENA */ +#define WM2200_AIF1RX2_ENA_WIDTH 1 /* AIF1RX2_ENA */ +#define WM2200_AIF1RX1_ENA 0x0040 /* AIF1RX1_ENA */ +#define WM2200_AIF1RX1_ENA_MASK 0x0040 /* AIF1RX1_ENA */ +#define WM2200_AIF1RX1_ENA_SHIFT 6 /* AIF1RX1_ENA */ +#define WM2200_AIF1RX1_ENA_WIDTH 1 /* AIF1RX1_ENA */ +#define WM2200_AIF1TX6_ENA 0x0020 /* AIF1TX6_ENA */ +#define WM2200_AIF1TX6_ENA_MASK 0x0020 /* AIF1TX6_ENA */ +#define WM2200_AIF1TX6_ENA_SHIFT 5 /* AIF1TX6_ENA */ +#define WM2200_AIF1TX6_ENA_WIDTH 1 /* AIF1TX6_ENA */ +#define WM2200_AIF1TX5_ENA 0x0010 /* AIF1TX5_ENA */ +#define WM2200_AIF1TX5_ENA_MASK 0x0010 /* AIF1TX5_ENA */ +#define WM2200_AIF1TX5_ENA_SHIFT 4 /* AIF1TX5_ENA */ +#define WM2200_AIF1TX5_ENA_WIDTH 1 /* AIF1TX5_ENA */ +#define WM2200_AIF1TX4_ENA 0x0008 /* AIF1TX4_ENA */ +#define WM2200_AIF1TX4_ENA_MASK 0x0008 /* AIF1TX4_ENA */ +#define WM2200_AIF1TX4_ENA_SHIFT 3 /* AIF1TX4_ENA */ +#define WM2200_AIF1TX4_ENA_WIDTH 1 /* AIF1TX4_ENA */ +#define WM2200_AIF1TX3_ENA 0x0004 /* AIF1TX3_ENA */ +#define WM2200_AIF1TX3_ENA_MASK 0x0004 /* AIF1TX3_ENA */ +#define WM2200_AIF1TX3_ENA_SHIFT 2 /* AIF1TX3_ENA */ +#define WM2200_AIF1TX3_ENA_WIDTH 1 /* AIF1TX3_ENA */ +#define WM2200_AIF1TX2_ENA 0x0002 /* AIF1TX2_ENA */ +#define WM2200_AIF1TX2_ENA_MASK 0x0002 /* AIF1TX2_ENA */ +#define WM2200_AIF1TX2_ENA_SHIFT 1 /* AIF1TX2_ENA */ +#define WM2200_AIF1TX2_ENA_WIDTH 1 /* AIF1TX2_ENA */ +#define WM2200_AIF1TX1_ENA 0x0001 /* AIF1TX1_ENA */ +#define WM2200_AIF1TX1_ENA_MASK 0x0001 /* AIF1TX1_ENA */ +#define WM2200_AIF1TX1_ENA_SHIFT 0 /* AIF1TX1_ENA */ +#define WM2200_AIF1TX1_ENA_WIDTH 1 /* AIF1TX1_ENA */ + +/* + * R1536 (0x600) - OUT1LMIX Input 1 Source + */ +#define WM2200_OUT1LMIX_SRC1_MASK 0x007F /* OUT1LMIX_SRC1 - [6:0] */ +#define WM2200_OUT1LMIX_SRC1_SHIFT 0 /* OUT1LMIX_SRC1 - [6:0] */ +#define WM2200_OUT1LMIX_SRC1_WIDTH 7 /* OUT1LMIX_SRC1 - [6:0] */ + +/* + * R1537 (0x601) - OUT1LMIX Input 1 Volume + */ +#define WM2200_OUT1LMIX_VOL1_MASK 0x00FE /* OUT1LMIX_VOL1 - [7:1] */ +#define WM2200_OUT1LMIX_VOL1_SHIFT 1 /* OUT1LMIX_VOL1 - [7:1] */ +#define WM2200_OUT1LMIX_VOL1_WIDTH 7 /* OUT1LMIX_VOL1 - [7:1] */ + +/* + * R1538 (0x602) - OUT1LMIX Input 2 Source + */ +#define WM2200_OUT1LMIX_SRC2_MASK 0x007F /* OUT1LMIX_SRC2 - [6:0] */ +#define WM2200_OUT1LMIX_SRC2_SHIFT 0 /* OUT1LMIX_SRC2 - [6:0] */ +#define WM2200_OUT1LMIX_SRC2_WIDTH 7 /* OUT1LMIX_SRC2 - [6:0] */ + +/* + * R1539 (0x603) - OUT1LMIX Input 2 Volume + */ +#define WM2200_OUT1LMIX_VOL2_MASK 0x00FE /* OUT1LMIX_VOL2 - [7:1] */ +#define WM2200_OUT1LMIX_VOL2_SHIFT 1 /* OUT1LMIX_VOL2 - [7:1] */ +#define WM2200_OUT1LMIX_VOL2_WIDTH 7 /* OUT1LMIX_VOL2 - [7:1] */ + +/* + * R1540 (0x604) - OUT1LMIX Input 3 Source + */ +#define WM2200_OUT1LMIX_SRC3_MASK 0x007F /* OUT1LMIX_SRC3 - [6:0] */ +#define WM2200_OUT1LMIX_SRC3_SHIFT 0 /* OUT1LMIX_SRC3 - [6:0] */ +#define WM2200_OUT1LMIX_SRC3_WIDTH 7 /* OUT1LMIX_SRC3 - [6:0] */ + +/* + * R1541 (0x605) - OUT1LMIX Input 3 Volume + */ +#define WM2200_OUT1LMIX_VOL3_MASK 0x00FE /* OUT1LMIX_VOL3 - [7:1] */ +#define WM2200_OUT1LMIX_VOL3_SHIFT 1 /* OUT1LMIX_VOL3 - [7:1] */ +#define WM2200_OUT1LMIX_VOL3_WIDTH 7 /* OUT1LMIX_VOL3 - [7:1] */ + +/* + * R1542 (0x606) - OUT1LMIX Input 4 Source + */ +#define WM2200_OUT1LMIX_SRC4_MASK 0x007F /* OUT1LMIX_SRC4 - [6:0] */ +#define WM2200_OUT1LMIX_SRC4_SHIFT 0 /* OUT1LMIX_SRC4 - [6:0] */ +#define WM2200_OUT1LMIX_SRC4_WIDTH 7 /* OUT1LMIX_SRC4 - [6:0] */ + +/* + * R1543 (0x607) - OUT1LMIX Input 4 Volume + */ +#define WM2200_OUT1LMIX_VOL4_MASK 0x00FE /* OUT1LMIX_VOL4 - [7:1] */ +#define WM2200_OUT1LMIX_VOL4_SHIFT 1 /* OUT1LMIX_VOL4 - [7:1] */ +#define WM2200_OUT1LMIX_VOL4_WIDTH 7 /* OUT1LMIX_VOL4 - [7:1] */ + +/* + * R1544 (0x608) - OUT1RMIX Input 1 Source + */ +#define WM2200_OUT1RMIX_SRC1_MASK 0x007F /* OUT1RMIX_SRC1 - [6:0] */ +#define WM2200_OUT1RMIX_SRC1_SHIFT 0 /* OUT1RMIX_SRC1 - [6:0] */ +#define WM2200_OUT1RMIX_SRC1_WIDTH 7 /* OUT1RMIX_SRC1 - [6:0] */ + +/* + * R1545 (0x609) - OUT1RMIX Input 1 Volume + */ +#define WM2200_OUT1RMIX_VOL1_MASK 0x00FE /* OUT1RMIX_VOL1 - [7:1] */ +#define WM2200_OUT1RMIX_VOL1_SHIFT 1 /* OUT1RMIX_VOL1 - [7:1] */ +#define WM2200_OUT1RMIX_VOL1_WIDTH 7 /* OUT1RMIX_VOL1 - [7:1] */ + +/* + * R1546 (0x60A) - OUT1RMIX Input 2 Source + */ +#define WM2200_OUT1RMIX_SRC2_MASK 0x007F /* OUT1RMIX_SRC2 - [6:0] */ +#define WM2200_OUT1RMIX_SRC2_SHIFT 0 /* OUT1RMIX_SRC2 - [6:0] */ +#define WM2200_OUT1RMIX_SRC2_WIDTH 7 /* OUT1RMIX_SRC2 - [6:0] */ + +/* + * R1547 (0x60B) - OUT1RMIX Input 2 Volume + */ +#define WM2200_OUT1RMIX_VOL2_MASK 0x00FE /* OUT1RMIX_VOL2 - [7:1] */ +#define WM2200_OUT1RMIX_VOL2_SHIFT 1 /* OUT1RMIX_VOL2 - [7:1] */ +#define WM2200_OUT1RMIX_VOL2_WIDTH 7 /* OUT1RMIX_VOL2 - [7:1] */ + +/* + * R1548 (0x60C) - OUT1RMIX Input 3 Source + */ +#define WM2200_OUT1RMIX_SRC3_MASK 0x007F /* OUT1RMIX_SRC3 - [6:0] */ +#define WM2200_OUT1RMIX_SRC3_SHIFT 0 /* OUT1RMIX_SRC3 - [6:0] */ +#define WM2200_OUT1RMIX_SRC3_WIDTH 7 /* OUT1RMIX_SRC3 - [6:0] */ + +/* + * R1549 (0x60D) - OUT1RMIX Input 3 Volume + */ +#define WM2200_OUT1RMIX_VOL3_MASK 0x00FE /* OUT1RMIX_VOL3 - [7:1] */ +#define WM2200_OUT1RMIX_VOL3_SHIFT 1 /* OUT1RMIX_VOL3 - [7:1] */ +#define WM2200_OUT1RMIX_VOL3_WIDTH 7 /* OUT1RMIX_VOL3 - [7:1] */ + +/* + * R1550 (0x60E) - OUT1RMIX Input 4 Source + */ +#define WM2200_OUT1RMIX_SRC4_MASK 0x007F /* OUT1RMIX_SRC4 - [6:0] */ +#define WM2200_OUT1RMIX_SRC4_SHIFT 0 /* OUT1RMIX_SRC4 - [6:0] */ +#define WM2200_OUT1RMIX_SRC4_WIDTH 7 /* OUT1RMIX_SRC4 - [6:0] */ + +/* + * R1551 (0x60F) - OUT1RMIX Input 4 Volume + */ +#define WM2200_OUT1RMIX_VOL4_MASK 0x00FE /* OUT1RMIX_VOL4 - [7:1] */ +#define WM2200_OUT1RMIX_VOL4_SHIFT 1 /* OUT1RMIX_VOL4 - [7:1] */ +#define WM2200_OUT1RMIX_VOL4_WIDTH 7 /* OUT1RMIX_VOL4 - [7:1] */ + +/* + * R1552 (0x610) - OUT2LMIX Input 1 Source + */ +#define WM2200_OUT2LMIX_SRC1_MASK 0x007F /* OUT2LMIX_SRC1 - [6:0] */ +#define WM2200_OUT2LMIX_SRC1_SHIFT 0 /* OUT2LMIX_SRC1 - [6:0] */ +#define WM2200_OUT2LMIX_SRC1_WIDTH 7 /* OUT2LMIX_SRC1 - [6:0] */ + +/* + * R1553 (0x611) - OUT2LMIX Input 1 Volume + */ +#define WM2200_OUT2LMIX_VOL1_MASK 0x00FE /* OUT2LMIX_VOL1 - [7:1] */ +#define WM2200_OUT2LMIX_VOL1_SHIFT 1 /* OUT2LMIX_VOL1 - [7:1] */ +#define WM2200_OUT2LMIX_VOL1_WIDTH 7 /* OUT2LMIX_VOL1 - [7:1] */ + +/* + * R1554 (0x612) - OUT2LMIX Input 2 Source + */ +#define WM2200_OUT2LMIX_SRC2_MASK 0x007F /* OUT2LMIX_SRC2 - [6:0] */ +#define WM2200_OUT2LMIX_SRC2_SHIFT 0 /* OUT2LMIX_SRC2 - [6:0] */ +#define WM2200_OUT2LMIX_SRC2_WIDTH 7 /* OUT2LMIX_SRC2 - [6:0] */ + +/* + * R1555 (0x613) - OUT2LMIX Input 2 Volume + */ +#define WM2200_OUT2LMIX_VOL2_MASK 0x00FE /* OUT2LMIX_VOL2 - [7:1] */ +#define WM2200_OUT2LMIX_VOL2_SHIFT 1 /* OUT2LMIX_VOL2 - [7:1] */ +#define WM2200_OUT2LMIX_VOL2_WIDTH 7 /* OUT2LMIX_VOL2 - [7:1] */ + +/* + * R1556 (0x614) - OUT2LMIX Input 3 Source + */ +#define WM2200_OUT2LMIX_SRC3_MASK 0x007F /* OUT2LMIX_SRC3 - [6:0] */ +#define WM2200_OUT2LMIX_SRC3_SHIFT 0 /* OUT2LMIX_SRC3 - [6:0] */ +#define WM2200_OUT2LMIX_SRC3_WIDTH 7 /* OUT2LMIX_SRC3 - [6:0] */ + +/* + * R1557 (0x615) - OUT2LMIX Input 3 Volume + */ +#define WM2200_OUT2LMIX_VOL3_MASK 0x00FE /* OUT2LMIX_VOL3 - [7:1] */ +#define WM2200_OUT2LMIX_VOL3_SHIFT 1 /* OUT2LMIX_VOL3 - [7:1] */ +#define WM2200_OUT2LMIX_VOL3_WIDTH 7 /* OUT2LMIX_VOL3 - [7:1] */ + +/* + * R1558 (0x616) - OUT2LMIX Input 4 Source + */ +#define WM2200_OUT2LMIX_SRC4_MASK 0x007F /* OUT2LMIX_SRC4 - [6:0] */ +#define WM2200_OUT2LMIX_SRC4_SHIFT 0 /* OUT2LMIX_SRC4 - [6:0] */ +#define WM2200_OUT2LMIX_SRC4_WIDTH 7 /* OUT2LMIX_SRC4 - [6:0] */ + +/* + * R1559 (0x617) - OUT2LMIX Input 4 Volume + */ +#define WM2200_OUT2LMIX_VOL4_MASK 0x00FE /* OUT2LMIX_VOL4 - [7:1] */ +#define WM2200_OUT2LMIX_VOL4_SHIFT 1 /* OUT2LMIX_VOL4 - [7:1] */ +#define WM2200_OUT2LMIX_VOL4_WIDTH 7 /* OUT2LMIX_VOL4 - [7:1] */ + +/* + * R1560 (0x618) - OUT2RMIX Input 1 Source + */ +#define WM2200_OUT2RMIX_SRC1_MASK 0x007F /* OUT2RMIX_SRC1 - [6:0] */ +#define WM2200_OUT2RMIX_SRC1_SHIFT 0 /* OUT2RMIX_SRC1 - [6:0] */ +#define WM2200_OUT2RMIX_SRC1_WIDTH 7 /* OUT2RMIX_SRC1 - [6:0] */ + +/* + * R1561 (0x619) - OUT2RMIX Input 1 Volume + */ +#define WM2200_OUT2RMIX_VOL1_MASK 0x00FE /* OUT2RMIX_VOL1 - [7:1] */ +#define WM2200_OUT2RMIX_VOL1_SHIFT 1 /* OUT2RMIX_VOL1 - [7:1] */ +#define WM2200_OUT2RMIX_VOL1_WIDTH 7 /* OUT2RMIX_VOL1 - [7:1] */ + +/* + * R1562 (0x61A) - OUT2RMIX Input 2 Source + */ +#define WM2200_OUT2RMIX_SRC2_MASK 0x007F /* OUT2RMIX_SRC2 - [6:0] */ +#define WM2200_OUT2RMIX_SRC2_SHIFT 0 /* OUT2RMIX_SRC2 - [6:0] */ +#define WM2200_OUT2RMIX_SRC2_WIDTH 7 /* OUT2RMIX_SRC2 - [6:0] */ + +/* + * R1563 (0x61B) - OUT2RMIX Input 2 Volume + */ +#define WM2200_OUT2RMIX_VOL2_MASK 0x00FE /* OUT2RMIX_VOL2 - [7:1] */ +#define WM2200_OUT2RMIX_VOL2_SHIFT 1 /* OUT2RMIX_VOL2 - [7:1] */ +#define WM2200_OUT2RMIX_VOL2_WIDTH 7 /* OUT2RMIX_VOL2 - [7:1] */ + +/* + * R1564 (0x61C) - OUT2RMIX Input 3 Source + */ +#define WM2200_OUT2RMIX_SRC3_MASK 0x007F /* OUT2RMIX_SRC3 - [6:0] */ +#define WM2200_OUT2RMIX_SRC3_SHIFT 0 /* OUT2RMIX_SRC3 - [6:0] */ +#define WM2200_OUT2RMIX_SRC3_WIDTH 7 /* OUT2RMIX_SRC3 - [6:0] */ + +/* + * R1565 (0x61D) - OUT2RMIX Input 3 Volume + */ +#define WM2200_OUT2RMIX_VOL3_MASK 0x00FE /* OUT2RMIX_VOL3 - [7:1] */ +#define WM2200_OUT2RMIX_VOL3_SHIFT 1 /* OUT2RMIX_VOL3 - [7:1] */ +#define WM2200_OUT2RMIX_VOL3_WIDTH 7 /* OUT2RMIX_VOL3 - [7:1] */ + +/* + * R1566 (0x61E) - OUT2RMIX Input 4 Source + */ +#define WM2200_OUT2RMIX_SRC4_MASK 0x007F /* OUT2RMIX_SRC4 - [6:0] */ +#define WM2200_OUT2RMIX_SRC4_SHIFT 0 /* OUT2RMIX_SRC4 - [6:0] */ +#define WM2200_OUT2RMIX_SRC4_WIDTH 7 /* OUT2RMIX_SRC4 - [6:0] */ + +/* + * R1567 (0x61F) - OUT2RMIX Input 4 Volume + */ +#define WM2200_OUT2RMIX_VOL4_MASK 0x00FE /* OUT2RMIX_VOL4 - [7:1] */ +#define WM2200_OUT2RMIX_VOL4_SHIFT 1 /* OUT2RMIX_VOL4 - [7:1] */ +#define WM2200_OUT2RMIX_VOL4_WIDTH 7 /* OUT2RMIX_VOL4 - [7:1] */ + +/* + * R1568 (0x620) - AIF1TX1MIX Input 1 Source + */ +#define WM2200_AIF1TX1MIX_SRC1_MASK 0x007F /* AIF1TX1MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC1_SHIFT 0 /* AIF1TX1MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC1_WIDTH 7 /* AIF1TX1MIX_SRC1 - [6:0] */ + +/* + * R1569 (0x621) - AIF1TX1MIX Input 1 Volume + */ +#define WM2200_AIF1TX1MIX_VOL1_MASK 0x00FE /* AIF1TX1MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL1_SHIFT 1 /* AIF1TX1MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL1_WIDTH 7 /* AIF1TX1MIX_VOL1 - [7:1] */ + +/* + * R1570 (0x622) - AIF1TX1MIX Input 2 Source + */ +#define WM2200_AIF1TX1MIX_SRC2_MASK 0x007F /* AIF1TX1MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC2_SHIFT 0 /* AIF1TX1MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC2_WIDTH 7 /* AIF1TX1MIX_SRC2 - [6:0] */ + +/* + * R1571 (0x623) - AIF1TX1MIX Input 2 Volume + */ +#define WM2200_AIF1TX1MIX_VOL2_MASK 0x00FE /* AIF1TX1MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL2_SHIFT 1 /* AIF1TX1MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL2_WIDTH 7 /* AIF1TX1MIX_VOL2 - [7:1] */ + +/* + * R1572 (0x624) - AIF1TX1MIX Input 3 Source + */ +#define WM2200_AIF1TX1MIX_SRC3_MASK 0x007F /* AIF1TX1MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC3_SHIFT 0 /* AIF1TX1MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC3_WIDTH 7 /* AIF1TX1MIX_SRC3 - [6:0] */ + +/* + * R1573 (0x625) - AIF1TX1MIX Input 3 Volume + */ +#define WM2200_AIF1TX1MIX_VOL3_MASK 0x00FE /* AIF1TX1MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL3_SHIFT 1 /* AIF1TX1MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL3_WIDTH 7 /* AIF1TX1MIX_VOL3 - [7:1] */ + +/* + * R1574 (0x626) - AIF1TX1MIX Input 4 Source + */ +#define WM2200_AIF1TX1MIX_SRC4_MASK 0x007F /* AIF1TX1MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC4_SHIFT 0 /* AIF1TX1MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX1MIX_SRC4_WIDTH 7 /* AIF1TX1MIX_SRC4 - [6:0] */ + +/* + * R1575 (0x627) - AIF1TX1MIX Input 4 Volume + */ +#define WM2200_AIF1TX1MIX_VOL4_MASK 0x00FE /* AIF1TX1MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL4_SHIFT 1 /* AIF1TX1MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX1MIX_VOL4_WIDTH 7 /* AIF1TX1MIX_VOL4 - [7:1] */ + +/* + * R1576 (0x628) - AIF1TX2MIX Input 1 Source + */ +#define WM2200_AIF1TX2MIX_SRC1_MASK 0x007F /* AIF1TX2MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC1_SHIFT 0 /* AIF1TX2MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC1_WIDTH 7 /* AIF1TX2MIX_SRC1 - [6:0] */ + +/* + * R1577 (0x629) - AIF1TX2MIX Input 1 Volume + */ +#define WM2200_AIF1TX2MIX_VOL1_MASK 0x00FE /* AIF1TX2MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL1_SHIFT 1 /* AIF1TX2MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL1_WIDTH 7 /* AIF1TX2MIX_VOL1 - [7:1] */ + +/* + * R1578 (0x62A) - AIF1TX2MIX Input 2 Source + */ +#define WM2200_AIF1TX2MIX_SRC2_MASK 0x007F /* AIF1TX2MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC2_SHIFT 0 /* AIF1TX2MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC2_WIDTH 7 /* AIF1TX2MIX_SRC2 - [6:0] */ + +/* + * R1579 (0x62B) - AIF1TX2MIX Input 2 Volume + */ +#define WM2200_AIF1TX2MIX_VOL2_MASK 0x00FE /* AIF1TX2MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL2_SHIFT 1 /* AIF1TX2MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL2_WIDTH 7 /* AIF1TX2MIX_VOL2 - [7:1] */ + +/* + * R1580 (0x62C) - AIF1TX2MIX Input 3 Source + */ +#define WM2200_AIF1TX2MIX_SRC3_MASK 0x007F /* AIF1TX2MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC3_SHIFT 0 /* AIF1TX2MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC3_WIDTH 7 /* AIF1TX2MIX_SRC3 - [6:0] */ + +/* + * R1581 (0x62D) - AIF1TX2MIX Input 3 Volume + */ +#define WM2200_AIF1TX2MIX_VOL3_MASK 0x00FE /* AIF1TX2MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL3_SHIFT 1 /* AIF1TX2MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL3_WIDTH 7 /* AIF1TX2MIX_VOL3 - [7:1] */ + +/* + * R1582 (0x62E) - AIF1TX2MIX Input 4 Source + */ +#define WM2200_AIF1TX2MIX_SRC4_MASK 0x007F /* AIF1TX2MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC4_SHIFT 0 /* AIF1TX2MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX2MIX_SRC4_WIDTH 7 /* AIF1TX2MIX_SRC4 - [6:0] */ + +/* + * R1583 (0x62F) - AIF1TX2MIX Input 4 Volume + */ +#define WM2200_AIF1TX2MIX_VOL4_MASK 0x00FE /* AIF1TX2MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL4_SHIFT 1 /* AIF1TX2MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX2MIX_VOL4_WIDTH 7 /* AIF1TX2MIX_VOL4 - [7:1] */ + +/* + * R1584 (0x630) - AIF1TX3MIX Input 1 Source + */ +#define WM2200_AIF1TX3MIX_SRC1_MASK 0x007F /* AIF1TX3MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC1_SHIFT 0 /* AIF1TX3MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC1_WIDTH 7 /* AIF1TX3MIX_SRC1 - [6:0] */ + +/* + * R1585 (0x631) - AIF1TX3MIX Input 1 Volume + */ +#define WM2200_AIF1TX3MIX_VOL1_MASK 0x00FE /* AIF1TX3MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL1_SHIFT 1 /* AIF1TX3MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL1_WIDTH 7 /* AIF1TX3MIX_VOL1 - [7:1] */ + +/* + * R1586 (0x632) - AIF1TX3MIX Input 2 Source + */ +#define WM2200_AIF1TX3MIX_SRC2_MASK 0x007F /* AIF1TX3MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC2_SHIFT 0 /* AIF1TX3MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC2_WIDTH 7 /* AIF1TX3MIX_SRC2 - [6:0] */ + +/* + * R1587 (0x633) - AIF1TX3MIX Input 2 Volume + */ +#define WM2200_AIF1TX3MIX_VOL2_MASK 0x00FE /* AIF1TX3MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL2_SHIFT 1 /* AIF1TX3MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL2_WIDTH 7 /* AIF1TX3MIX_VOL2 - [7:1] */ + +/* + * R1588 (0x634) - AIF1TX3MIX Input 3 Source + */ +#define WM2200_AIF1TX3MIX_SRC3_MASK 0x007F /* AIF1TX3MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC3_SHIFT 0 /* AIF1TX3MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC3_WIDTH 7 /* AIF1TX3MIX_SRC3 - [6:0] */ + +/* + * R1589 (0x635) - AIF1TX3MIX Input 3 Volume + */ +#define WM2200_AIF1TX3MIX_VOL3_MASK 0x00FE /* AIF1TX3MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL3_SHIFT 1 /* AIF1TX3MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL3_WIDTH 7 /* AIF1TX3MIX_VOL3 - [7:1] */ + +/* + * R1590 (0x636) - AIF1TX3MIX Input 4 Source + */ +#define WM2200_AIF1TX3MIX_SRC4_MASK 0x007F /* AIF1TX3MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC4_SHIFT 0 /* AIF1TX3MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX3MIX_SRC4_WIDTH 7 /* AIF1TX3MIX_SRC4 - [6:0] */ + +/* + * R1591 (0x637) - AIF1TX3MIX Input 4 Volume + */ +#define WM2200_AIF1TX3MIX_VOL4_MASK 0x00FE /* AIF1TX3MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL4_SHIFT 1 /* AIF1TX3MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX3MIX_VOL4_WIDTH 7 /* AIF1TX3MIX_VOL4 - [7:1] */ + +/* + * R1592 (0x638) - AIF1TX4MIX Input 1 Source + */ +#define WM2200_AIF1TX4MIX_SRC1_MASK 0x007F /* AIF1TX4MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC1_SHIFT 0 /* AIF1TX4MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC1_WIDTH 7 /* AIF1TX4MIX_SRC1 - [6:0] */ + +/* + * R1593 (0x639) - AIF1TX4MIX Input 1 Volume + */ +#define WM2200_AIF1TX4MIX_VOL1_MASK 0x00FE /* AIF1TX4MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL1_SHIFT 1 /* AIF1TX4MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL1_WIDTH 7 /* AIF1TX4MIX_VOL1 - [7:1] */ + +/* + * R1594 (0x63A) - AIF1TX4MIX Input 2 Source + */ +#define WM2200_AIF1TX4MIX_SRC2_MASK 0x007F /* AIF1TX4MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC2_SHIFT 0 /* AIF1TX4MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC2_WIDTH 7 /* AIF1TX4MIX_SRC2 - [6:0] */ + +/* + * R1595 (0x63B) - AIF1TX4MIX Input 2 Volume + */ +#define WM2200_AIF1TX4MIX_VOL2_MASK 0x00FE /* AIF1TX4MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL2_SHIFT 1 /* AIF1TX4MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL2_WIDTH 7 /* AIF1TX4MIX_VOL2 - [7:1] */ + +/* + * R1596 (0x63C) - AIF1TX4MIX Input 3 Source + */ +#define WM2200_AIF1TX4MIX_SRC3_MASK 0x007F /* AIF1TX4MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC3_SHIFT 0 /* AIF1TX4MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC3_WIDTH 7 /* AIF1TX4MIX_SRC3 - [6:0] */ + +/* + * R1597 (0x63D) - AIF1TX4MIX Input 3 Volume + */ +#define WM2200_AIF1TX4MIX_VOL3_MASK 0x00FE /* AIF1TX4MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL3_SHIFT 1 /* AIF1TX4MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL3_WIDTH 7 /* AIF1TX4MIX_VOL3 - [7:1] */ + +/* + * R1598 (0x63E) - AIF1TX4MIX Input 4 Source + */ +#define WM2200_AIF1TX4MIX_SRC4_MASK 0x007F /* AIF1TX4MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC4_SHIFT 0 /* AIF1TX4MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX4MIX_SRC4_WIDTH 7 /* AIF1TX4MIX_SRC4 - [6:0] */ + +/* + * R1599 (0x63F) - AIF1TX4MIX Input 4 Volume + */ +#define WM2200_AIF1TX4MIX_VOL4_MASK 0x00FE /* AIF1TX4MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL4_SHIFT 1 /* AIF1TX4MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX4MIX_VOL4_WIDTH 7 /* AIF1TX4MIX_VOL4 - [7:1] */ + +/* + * R1600 (0x640) - AIF1TX5MIX Input 1 Source + */ +#define WM2200_AIF1TX5MIX_SRC1_MASK 0x007F /* AIF1TX5MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC1_SHIFT 0 /* AIF1TX5MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC1_WIDTH 7 /* AIF1TX5MIX_SRC1 - [6:0] */ + +/* + * R1601 (0x641) - AIF1TX5MIX Input 1 Volume + */ +#define WM2200_AIF1TX5MIX_VOL1_MASK 0x00FE /* AIF1TX5MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL1_SHIFT 1 /* AIF1TX5MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL1_WIDTH 7 /* AIF1TX5MIX_VOL1 - [7:1] */ + +/* + * R1602 (0x642) - AIF1TX5MIX Input 2 Source + */ +#define WM2200_AIF1TX5MIX_SRC2_MASK 0x007F /* AIF1TX5MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC2_SHIFT 0 /* AIF1TX5MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC2_WIDTH 7 /* AIF1TX5MIX_SRC2 - [6:0] */ + +/* + * R1603 (0x643) - AIF1TX5MIX Input 2 Volume + */ +#define WM2200_AIF1TX5MIX_VOL2_MASK 0x00FE /* AIF1TX5MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL2_SHIFT 1 /* AIF1TX5MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL2_WIDTH 7 /* AIF1TX5MIX_VOL2 - [7:1] */ + +/* + * R1604 (0x644) - AIF1TX5MIX Input 3 Source + */ +#define WM2200_AIF1TX5MIX_SRC3_MASK 0x007F /* AIF1TX5MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC3_SHIFT 0 /* AIF1TX5MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC3_WIDTH 7 /* AIF1TX5MIX_SRC3 - [6:0] */ + +/* + * R1605 (0x645) - AIF1TX5MIX Input 3 Volume + */ +#define WM2200_AIF1TX5MIX_VOL3_MASK 0x00FE /* AIF1TX5MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL3_SHIFT 1 /* AIF1TX5MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL3_WIDTH 7 /* AIF1TX5MIX_VOL3 - [7:1] */ + +/* + * R1606 (0x646) - AIF1TX5MIX Input 4 Source + */ +#define WM2200_AIF1TX5MIX_SRC4_MASK 0x007F /* AIF1TX5MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC4_SHIFT 0 /* AIF1TX5MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX5MIX_SRC4_WIDTH 7 /* AIF1TX5MIX_SRC4 - [6:0] */ + +/* + * R1607 (0x647) - AIF1TX5MIX Input 4 Volume + */ +#define WM2200_AIF1TX5MIX_VOL4_MASK 0x00FE /* AIF1TX5MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL4_SHIFT 1 /* AIF1TX5MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX5MIX_VOL4_WIDTH 7 /* AIF1TX5MIX_VOL4 - [7:1] */ + +/* + * R1608 (0x648) - AIF1TX6MIX Input 1 Source + */ +#define WM2200_AIF1TX6MIX_SRC1_MASK 0x007F /* AIF1TX6MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC1_SHIFT 0 /* AIF1TX6MIX_SRC1 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC1_WIDTH 7 /* AIF1TX6MIX_SRC1 - [6:0] */ + +/* + * R1609 (0x649) - AIF1TX6MIX Input 1 Volume + */ +#define WM2200_AIF1TX6MIX_VOL1_MASK 0x00FE /* AIF1TX6MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL1_SHIFT 1 /* AIF1TX6MIX_VOL1 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL1_WIDTH 7 /* AIF1TX6MIX_VOL1 - [7:1] */ + +/* + * R1610 (0x64A) - AIF1TX6MIX Input 2 Source + */ +#define WM2200_AIF1TX6MIX_SRC2_MASK 0x007F /* AIF1TX6MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC2_SHIFT 0 /* AIF1TX6MIX_SRC2 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC2_WIDTH 7 /* AIF1TX6MIX_SRC2 - [6:0] */ + +/* + * R1611 (0x64B) - AIF1TX6MIX Input 2 Volume + */ +#define WM2200_AIF1TX6MIX_VOL2_MASK 0x00FE /* AIF1TX6MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL2_SHIFT 1 /* AIF1TX6MIX_VOL2 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL2_WIDTH 7 /* AIF1TX6MIX_VOL2 - [7:1] */ + +/* + * R1612 (0x64C) - AIF1TX6MIX Input 3 Source + */ +#define WM2200_AIF1TX6MIX_SRC3_MASK 0x007F /* AIF1TX6MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC3_SHIFT 0 /* AIF1TX6MIX_SRC3 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC3_WIDTH 7 /* AIF1TX6MIX_SRC3 - [6:0] */ + +/* + * R1613 (0x64D) - AIF1TX6MIX Input 3 Volume + */ +#define WM2200_AIF1TX6MIX_VOL3_MASK 0x00FE /* AIF1TX6MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL3_SHIFT 1 /* AIF1TX6MIX_VOL3 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL3_WIDTH 7 /* AIF1TX6MIX_VOL3 - [7:1] */ + +/* + * R1614 (0x64E) - AIF1TX6MIX Input 4 Source + */ +#define WM2200_AIF1TX6MIX_SRC4_MASK 0x007F /* AIF1TX6MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC4_SHIFT 0 /* AIF1TX6MIX_SRC4 - [6:0] */ +#define WM2200_AIF1TX6MIX_SRC4_WIDTH 7 /* AIF1TX6MIX_SRC4 - [6:0] */ + +/* + * R1615 (0x64F) - AIF1TX6MIX Input 4 Volume + */ +#define WM2200_AIF1TX6MIX_VOL4_MASK 0x00FE /* AIF1TX6MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL4_SHIFT 1 /* AIF1TX6MIX_VOL4 - [7:1] */ +#define WM2200_AIF1TX6MIX_VOL4_WIDTH 7 /* AIF1TX6MIX_VOL4 - [7:1] */ + +/* + * R1616 (0x650) - EQLMIX Input 1 Source + */ +#define WM2200_EQLMIX_SRC1_MASK 0x007F /* EQLMIX_SRC1 - [6:0] */ +#define WM2200_EQLMIX_SRC1_SHIFT 0 /* EQLMIX_SRC1 - [6:0] */ +#define WM2200_EQLMIX_SRC1_WIDTH 7 /* EQLMIX_SRC1 - [6:0] */ + +/* + * R1617 (0x651) - EQLMIX Input 1 Volume + */ +#define WM2200_EQLMIX_VOL1_MASK 0x00FE /* EQLMIX_VOL1 - [7:1] */ +#define WM2200_EQLMIX_VOL1_SHIFT 1 /* EQLMIX_VOL1 - [7:1] */ +#define WM2200_EQLMIX_VOL1_WIDTH 7 /* EQLMIX_VOL1 - [7:1] */ + +/* + * R1618 (0x652) - EQLMIX Input 2 Source + */ +#define WM2200_EQLMIX_SRC2_MASK 0x007F /* EQLMIX_SRC2 - [6:0] */ +#define WM2200_EQLMIX_SRC2_SHIFT 0 /* EQLMIX_SRC2 - [6:0] */ +#define WM2200_EQLMIX_SRC2_WIDTH 7 /* EQLMIX_SRC2 - [6:0] */ + +/* + * R1619 (0x653) - EQLMIX Input 2 Volume + */ +#define WM2200_EQLMIX_VOL2_MASK 0x00FE /* EQLMIX_VOL2 - [7:1] */ +#define WM2200_EQLMIX_VOL2_SHIFT 1 /* EQLMIX_VOL2 - [7:1] */ +#define WM2200_EQLMIX_VOL2_WIDTH 7 /* EQLMIX_VOL2 - [7:1] */ + +/* + * R1620 (0x654) - EQLMIX Input 3 Source + */ +#define WM2200_EQLMIX_SRC3_MASK 0x007F /* EQLMIX_SRC3 - [6:0] */ +#define WM2200_EQLMIX_SRC3_SHIFT 0 /* EQLMIX_SRC3 - [6:0] */ +#define WM2200_EQLMIX_SRC3_WIDTH 7 /* EQLMIX_SRC3 - [6:0] */ + +/* + * R1621 (0x655) - EQLMIX Input 3 Volume + */ +#define WM2200_EQLMIX_VOL3_MASK 0x00FE /* EQLMIX_VOL3 - [7:1] */ +#define WM2200_EQLMIX_VOL3_SHIFT 1 /* EQLMIX_VOL3 - [7:1] */ +#define WM2200_EQLMIX_VOL3_WIDTH 7 /* EQLMIX_VOL3 - [7:1] */ + +/* + * R1622 (0x656) - EQLMIX Input 4 Source + */ +#define WM2200_EQLMIX_SRC4_MASK 0x007F /* EQLMIX_SRC4 - [6:0] */ +#define WM2200_EQLMIX_SRC4_SHIFT 0 /* EQLMIX_SRC4 - [6:0] */ +#define WM2200_EQLMIX_SRC4_WIDTH 7 /* EQLMIX_SRC4 - [6:0] */ + +/* + * R1623 (0x657) - EQLMIX Input 4 Volume + */ +#define WM2200_EQLMIX_VOL4_MASK 0x00FE /* EQLMIX_VOL4 - [7:1] */ +#define WM2200_EQLMIX_VOL4_SHIFT 1 /* EQLMIX_VOL4 - [7:1] */ +#define WM2200_EQLMIX_VOL4_WIDTH 7 /* EQLMIX_VOL4 - [7:1] */ + +/* + * R1624 (0x658) - EQRMIX Input 1 Source + */ +#define WM2200_EQRMIX_SRC1_MASK 0x007F /* EQRMIX_SRC1 - [6:0] */ +#define WM2200_EQRMIX_SRC1_SHIFT 0 /* EQRMIX_SRC1 - [6:0] */ +#define WM2200_EQRMIX_SRC1_WIDTH 7 /* EQRMIX_SRC1 - [6:0] */ + +/* + * R1625 (0x659) - EQRMIX Input 1 Volume + */ +#define WM2200_EQRMIX_VOL1_MASK 0x00FE /* EQRMIX_VOL1 - [7:1] */ +#define WM2200_EQRMIX_VOL1_SHIFT 1 /* EQRMIX_VOL1 - [7:1] */ +#define WM2200_EQRMIX_VOL1_WIDTH 7 /* EQRMIX_VOL1 - [7:1] */ + +/* + * R1626 (0x65A) - EQRMIX Input 2 Source + */ +#define WM2200_EQRMIX_SRC2_MASK 0x007F /* EQRMIX_SRC2 - [6:0] */ +#define WM2200_EQRMIX_SRC2_SHIFT 0 /* EQRMIX_SRC2 - [6:0] */ +#define WM2200_EQRMIX_SRC2_WIDTH 7 /* EQRMIX_SRC2 - [6:0] */ + +/* + * R1627 (0x65B) - EQRMIX Input 2 Volume + */ +#define WM2200_EQRMIX_VOL2_MASK 0x00FE /* EQRMIX_VOL2 - [7:1] */ +#define WM2200_EQRMIX_VOL2_SHIFT 1 /* EQRMIX_VOL2 - [7:1] */ +#define WM2200_EQRMIX_VOL2_WIDTH 7 /* EQRMIX_VOL2 - [7:1] */ + +/* + * R1628 (0x65C) - EQRMIX Input 3 Source + */ +#define WM2200_EQRMIX_SRC3_MASK 0x007F /* EQRMIX_SRC3 - [6:0] */ +#define WM2200_EQRMIX_SRC3_SHIFT 0 /* EQRMIX_SRC3 - [6:0] */ +#define WM2200_EQRMIX_SRC3_WIDTH 7 /* EQRMIX_SRC3 - [6:0] */ + +/* + * R1629 (0x65D) - EQRMIX Input 3 Volume + */ +#define WM2200_EQRMIX_VOL3_MASK 0x00FE /* EQRMIX_VOL3 - [7:1] */ +#define WM2200_EQRMIX_VOL3_SHIFT 1 /* EQRMIX_VOL3 - [7:1] */ +#define WM2200_EQRMIX_VOL3_WIDTH 7 /* EQRMIX_VOL3 - [7:1] */ + +/* + * R1630 (0x65E) - EQRMIX Input 4 Source + */ +#define WM2200_EQRMIX_SRC4_MASK 0x007F /* EQRMIX_SRC4 - [6:0] */ +#define WM2200_EQRMIX_SRC4_SHIFT 0 /* EQRMIX_SRC4 - [6:0] */ +#define WM2200_EQRMIX_SRC4_WIDTH 7 /* EQRMIX_SRC4 - [6:0] */ + +/* + * R1631 (0x65F) - EQRMIX Input 4 Volume + */ +#define WM2200_EQRMIX_VOL4_MASK 0x00FE /* EQRMIX_VOL4 - [7:1] */ +#define WM2200_EQRMIX_VOL4_SHIFT 1 /* EQRMIX_VOL4 - [7:1] */ +#define WM2200_EQRMIX_VOL4_WIDTH 7 /* EQRMIX_VOL4 - [7:1] */ + +/* + * R1632 (0x660) - LHPF1MIX Input 1 Source + */ +#define WM2200_LHPF1MIX_SRC1_MASK 0x007F /* LHPF1MIX_SRC1 - [6:0] */ +#define WM2200_LHPF1MIX_SRC1_SHIFT 0 /* LHPF1MIX_SRC1 - [6:0] */ +#define WM2200_LHPF1MIX_SRC1_WIDTH 7 /* LHPF1MIX_SRC1 - [6:0] */ + +/* + * R1633 (0x661) - LHPF1MIX Input 1 Volume + */ +#define WM2200_LHPF1MIX_VOL1_MASK 0x00FE /* LHPF1MIX_VOL1 - [7:1] */ +#define WM2200_LHPF1MIX_VOL1_SHIFT 1 /* LHPF1MIX_VOL1 - [7:1] */ +#define WM2200_LHPF1MIX_VOL1_WIDTH 7 /* LHPF1MIX_VOL1 - [7:1] */ + +/* + * R1634 (0x662) - LHPF1MIX Input 2 Source + */ +#define WM2200_LHPF1MIX_SRC2_MASK 0x007F /* LHPF1MIX_SRC2 - [6:0] */ +#define WM2200_LHPF1MIX_SRC2_SHIFT 0 /* LHPF1MIX_SRC2 - [6:0] */ +#define WM2200_LHPF1MIX_SRC2_WIDTH 7 /* LHPF1MIX_SRC2 - [6:0] */ + +/* + * R1635 (0x663) - LHPF1MIX Input 2 Volume + */ +#define WM2200_LHPF1MIX_VOL2_MASK 0x00FE /* LHPF1MIX_VOL2 - [7:1] */ +#define WM2200_LHPF1MIX_VOL2_SHIFT 1 /* LHPF1MIX_VOL2 - [7:1] */ +#define WM2200_LHPF1MIX_VOL2_WIDTH 7 /* LHPF1MIX_VOL2 - [7:1] */ + +/* + * R1636 (0x664) - LHPF1MIX Input 3 Source + */ +#define WM2200_LHPF1MIX_SRC3_MASK 0x007F /* LHPF1MIX_SRC3 - [6:0] */ +#define WM2200_LHPF1MIX_SRC3_SHIFT 0 /* LHPF1MIX_SRC3 - [6:0] */ +#define WM2200_LHPF1MIX_SRC3_WIDTH 7 /* LHPF1MIX_SRC3 - [6:0] */ + +/* + * R1637 (0x665) - LHPF1MIX Input 3 Volume + */ +#define WM2200_LHPF1MIX_VOL3_MASK 0x00FE /* LHPF1MIX_VOL3 - [7:1] */ +#define WM2200_LHPF1MIX_VOL3_SHIFT 1 /* LHPF1MIX_VOL3 - [7:1] */ +#define WM2200_LHPF1MIX_VOL3_WIDTH 7 /* LHPF1MIX_VOL3 - [7:1] */ + +/* + * R1638 (0x666) - LHPF1MIX Input 4 Source + */ +#define WM2200_LHPF1MIX_SRC4_MASK 0x007F /* LHPF1MIX_SRC4 - [6:0] */ +#define WM2200_LHPF1MIX_SRC4_SHIFT 0 /* LHPF1MIX_SRC4 - [6:0] */ +#define WM2200_LHPF1MIX_SRC4_WIDTH 7 /* LHPF1MIX_SRC4 - [6:0] */ + +/* + * R1639 (0x667) - LHPF1MIX Input 4 Volume + */ +#define WM2200_LHPF1MIX_VOL4_MASK 0x00FE /* LHPF1MIX_VOL4 - [7:1] */ +#define WM2200_LHPF1MIX_VOL4_SHIFT 1 /* LHPF1MIX_VOL4 - [7:1] */ +#define WM2200_LHPF1MIX_VOL4_WIDTH 7 /* LHPF1MIX_VOL4 - [7:1] */ + +/* + * R1640 (0x668) - LHPF2MIX Input 1 Source + */ +#define WM2200_LHPF2MIX_SRC1_MASK 0x007F /* LHPF2MIX_SRC1 - [6:0] */ +#define WM2200_LHPF2MIX_SRC1_SHIFT 0 /* LHPF2MIX_SRC1 - [6:0] */ +#define WM2200_LHPF2MIX_SRC1_WIDTH 7 /* LHPF2MIX_SRC1 - [6:0] */ + +/* + * R1641 (0x669) - LHPF2MIX Input 1 Volume + */ +#define WM2200_LHPF2MIX_VOL1_MASK 0x00FE /* LHPF2MIX_VOL1 - [7:1] */ +#define WM2200_LHPF2MIX_VOL1_SHIFT 1 /* LHPF2MIX_VOL1 - [7:1] */ +#define WM2200_LHPF2MIX_VOL1_WIDTH 7 /* LHPF2MIX_VOL1 - [7:1] */ + +/* + * R1642 (0x66A) - LHPF2MIX Input 2 Source + */ +#define WM2200_LHPF2MIX_SRC2_MASK 0x007F /* LHPF2MIX_SRC2 - [6:0] */ +#define WM2200_LHPF2MIX_SRC2_SHIFT 0 /* LHPF2MIX_SRC2 - [6:0] */ +#define WM2200_LHPF2MIX_SRC2_WIDTH 7 /* LHPF2MIX_SRC2 - [6:0] */ + +/* + * R1643 (0x66B) - LHPF2MIX Input 2 Volume + */ +#define WM2200_LHPF2MIX_VOL2_MASK 0x00FE /* LHPF2MIX_VOL2 - [7:1] */ +#define WM2200_LHPF2MIX_VOL2_SHIFT 1 /* LHPF2MIX_VOL2 - [7:1] */ +#define WM2200_LHPF2MIX_VOL2_WIDTH 7 /* LHPF2MIX_VOL2 - [7:1] */ + +/* + * R1644 (0x66C) - LHPF2MIX Input 3 Source + */ +#define WM2200_LHPF2MIX_SRC3_MASK 0x007F /* LHPF2MIX_SRC3 - [6:0] */ +#define WM2200_LHPF2MIX_SRC3_SHIFT 0 /* LHPF2MIX_SRC3 - [6:0] */ +#define WM2200_LHPF2MIX_SRC3_WIDTH 7 /* LHPF2MIX_SRC3 - [6:0] */ + +/* + * R1645 (0x66D) - LHPF2MIX Input 3 Volume + */ +#define WM2200_LHPF2MIX_VOL3_MASK 0x00FE /* LHPF2MIX_VOL3 - [7:1] */ +#define WM2200_LHPF2MIX_VOL3_SHIFT 1 /* LHPF2MIX_VOL3 - [7:1] */ +#define WM2200_LHPF2MIX_VOL3_WIDTH 7 /* LHPF2MIX_VOL3 - [7:1] */ + +/* + * R1646 (0x66E) - LHPF2MIX Input 4 Source + */ +#define WM2200_LHPF2MIX_SRC4_MASK 0x007F /* LHPF2MIX_SRC4 - [6:0] */ +#define WM2200_LHPF2MIX_SRC4_SHIFT 0 /* LHPF2MIX_SRC4 - [6:0] */ +#define WM2200_LHPF2MIX_SRC4_WIDTH 7 /* LHPF2MIX_SRC4 - [6:0] */ + +/* + * R1647 (0x66F) - LHPF2MIX Input 4 Volume + */ +#define WM2200_LHPF2MIX_VOL4_MASK 0x00FE /* LHPF2MIX_VOL4 - [7:1] */ +#define WM2200_LHPF2MIX_VOL4_SHIFT 1 /* LHPF2MIX_VOL4 - [7:1] */ +#define WM2200_LHPF2MIX_VOL4_WIDTH 7 /* LHPF2MIX_VOL4 - [7:1] */ + +/* + * R1648 (0x670) - DSP1LMIX Input 1 Source + */ +#define WM2200_DSP1LMIX_SRC1_MASK 0x007F /* DSP1LMIX_SRC1 - [6:0] */ +#define WM2200_DSP1LMIX_SRC1_SHIFT 0 /* DSP1LMIX_SRC1 - [6:0] */ +#define WM2200_DSP1LMIX_SRC1_WIDTH 7 /* DSP1LMIX_SRC1 - [6:0] */ + +/* + * R1649 (0x671) - DSP1LMIX Input 1 Volume + */ +#define WM2200_DSP1LMIX_VOL1_MASK 0x00FE /* DSP1LMIX_VOL1 - [7:1] */ +#define WM2200_DSP1LMIX_VOL1_SHIFT 1 /* DSP1LMIX_VOL1 - [7:1] */ +#define WM2200_DSP1LMIX_VOL1_WIDTH 7 /* DSP1LMIX_VOL1 - [7:1] */ + +/* + * R1650 (0x672) - DSP1LMIX Input 2 Source + */ +#define WM2200_DSP1LMIX_SRC2_MASK 0x007F /* DSP1LMIX_SRC2 - [6:0] */ +#define WM2200_DSP1LMIX_SRC2_SHIFT 0 /* DSP1LMIX_SRC2 - [6:0] */ +#define WM2200_DSP1LMIX_SRC2_WIDTH 7 /* DSP1LMIX_SRC2 - [6:0] */ + +/* + * R1651 (0x673) - DSP1LMIX Input 2 Volume + */ +#define WM2200_DSP1LMIX_VOL2_MASK 0x00FE /* DSP1LMIX_VOL2 - [7:1] */ +#define WM2200_DSP1LMIX_VOL2_SHIFT 1 /* DSP1LMIX_VOL2 - [7:1] */ +#define WM2200_DSP1LMIX_VOL2_WIDTH 7 /* DSP1LMIX_VOL2 - [7:1] */ + +/* + * R1652 (0x674) - DSP1LMIX Input 3 Source + */ +#define WM2200_DSP1LMIX_SRC3_MASK 0x007F /* DSP1LMIX_SRC3 - [6:0] */ +#define WM2200_DSP1LMIX_SRC3_SHIFT 0 /* DSP1LMIX_SRC3 - [6:0] */ +#define WM2200_DSP1LMIX_SRC3_WIDTH 7 /* DSP1LMIX_SRC3 - [6:0] */ + +/* + * R1653 (0x675) - DSP1LMIX Input 3 Volume + */ +#define WM2200_DSP1LMIX_VOL3_MASK 0x00FE /* DSP1LMIX_VOL3 - [7:1] */ +#define WM2200_DSP1LMIX_VOL3_SHIFT 1 /* DSP1LMIX_VOL3 - [7:1] */ +#define WM2200_DSP1LMIX_VOL3_WIDTH 7 /* DSP1LMIX_VOL3 - [7:1] */ + +/* + * R1654 (0x676) - DSP1LMIX Input 4 Source + */ +#define WM2200_DSP1LMIX_SRC4_MASK 0x007F /* DSP1LMIX_SRC4 - [6:0] */ +#define WM2200_DSP1LMIX_SRC4_SHIFT 0 /* DSP1LMIX_SRC4 - [6:0] */ +#define WM2200_DSP1LMIX_SRC4_WIDTH 7 /* DSP1LMIX_SRC4 - [6:0] */ + +/* + * R1655 (0x677) - DSP1LMIX Input 4 Volume + */ +#define WM2200_DSP1LMIX_VOL4_MASK 0x00FE /* DSP1LMIX_VOL4 - [7:1] */ +#define WM2200_DSP1LMIX_VOL4_SHIFT 1 /* DSP1LMIX_VOL4 - [7:1] */ +#define WM2200_DSP1LMIX_VOL4_WIDTH 7 /* DSP1LMIX_VOL4 - [7:1] */ + +/* + * R1656 (0x678) - DSP1RMIX Input 1 Source + */ +#define WM2200_DSP1RMIX_SRC1_MASK 0x007F /* DSP1RMIX_SRC1 - [6:0] */ +#define WM2200_DSP1RMIX_SRC1_SHIFT 0 /* DSP1RMIX_SRC1 - [6:0] */ +#define WM2200_DSP1RMIX_SRC1_WIDTH 7 /* DSP1RMIX_SRC1 - [6:0] */ + +/* + * R1657 (0x679) - DSP1RMIX Input 1 Volume + */ +#define WM2200_DSP1RMIX_VOL1_MASK 0x00FE /* DSP1RMIX_VOL1 - [7:1] */ +#define WM2200_DSP1RMIX_VOL1_SHIFT 1 /* DSP1RMIX_VOL1 - [7:1] */ +#define WM2200_DSP1RMIX_VOL1_WIDTH 7 /* DSP1RMIX_VOL1 - [7:1] */ + +/* + * R1658 (0x67A) - DSP1RMIX Input 2 Source + */ +#define WM2200_DSP1RMIX_SRC2_MASK 0x007F /* DSP1RMIX_SRC2 - [6:0] */ +#define WM2200_DSP1RMIX_SRC2_SHIFT 0 /* DSP1RMIX_SRC2 - [6:0] */ +#define WM2200_DSP1RMIX_SRC2_WIDTH 7 /* DSP1RMIX_SRC2 - [6:0] */ + +/* + * R1659 (0x67B) - DSP1RMIX Input 2 Volume + */ +#define WM2200_DSP1RMIX_VOL2_MASK 0x00FE /* DSP1RMIX_VOL2 - [7:1] */ +#define WM2200_DSP1RMIX_VOL2_SHIFT 1 /* DSP1RMIX_VOL2 - [7:1] */ +#define WM2200_DSP1RMIX_VOL2_WIDTH 7 /* DSP1RMIX_VOL2 - [7:1] */ + +/* + * R1660 (0x67C) - DSP1RMIX Input 3 Source + */ +#define WM2200_DSP1RMIX_SRC3_MASK 0x007F /* DSP1RMIX_SRC3 - [6:0] */ +#define WM2200_DSP1RMIX_SRC3_SHIFT 0 /* DSP1RMIX_SRC3 - [6:0] */ +#define WM2200_DSP1RMIX_SRC3_WIDTH 7 /* DSP1RMIX_SRC3 - [6:0] */ + +/* + * R1661 (0x67D) - DSP1RMIX Input 3 Volume + */ +#define WM2200_DSP1RMIX_VOL3_MASK 0x00FE /* DSP1RMIX_VOL3 - [7:1] */ +#define WM2200_DSP1RMIX_VOL3_SHIFT 1 /* DSP1RMIX_VOL3 - [7:1] */ +#define WM2200_DSP1RMIX_VOL3_WIDTH 7 /* DSP1RMIX_VOL3 - [7:1] */ + +/* + * R1662 (0x67E) - DSP1RMIX Input 4 Source + */ +#define WM2200_DSP1RMIX_SRC4_MASK 0x007F /* DSP1RMIX_SRC4 - [6:0] */ +#define WM2200_DSP1RMIX_SRC4_SHIFT 0 /* DSP1RMIX_SRC4 - [6:0] */ +#define WM2200_DSP1RMIX_SRC4_WIDTH 7 /* DSP1RMIX_SRC4 - [6:0] */ + +/* + * R1663 (0x67F) - DSP1RMIX Input 4 Volume + */ +#define WM2200_DSP1RMIX_VOL4_MASK 0x00FE /* DSP1RMIX_VOL4 - [7:1] */ +#define WM2200_DSP1RMIX_VOL4_SHIFT 1 /* DSP1RMIX_VOL4 - [7:1] */ +#define WM2200_DSP1RMIX_VOL4_WIDTH 7 /* DSP1RMIX_VOL4 - [7:1] */ + +/* + * R1664 (0x680) - DSP1AUX1MIX Input 1 Source + */ +#define WM2200_DSP1AUX1MIX_SRC1_MASK 0x007F /* DSP1AUX1MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX1MIX_SRC1_SHIFT 0 /* DSP1AUX1MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX1MIX_SRC1_WIDTH 7 /* DSP1AUX1MIX_SRC1 - [6:0] */ + +/* + * R1665 (0x681) - DSP1AUX2MIX Input 1 Source + */ +#define WM2200_DSP1AUX2MIX_SRC1_MASK 0x007F /* DSP1AUX2MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX2MIX_SRC1_SHIFT 0 /* DSP1AUX2MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX2MIX_SRC1_WIDTH 7 /* DSP1AUX2MIX_SRC1 - [6:0] */ + +/* + * R1666 (0x682) - DSP1AUX3MIX Input 1 Source + */ +#define WM2200_DSP1AUX3MIX_SRC1_MASK 0x007F /* DSP1AUX3MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX3MIX_SRC1_SHIFT 0 /* DSP1AUX3MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX3MIX_SRC1_WIDTH 7 /* DSP1AUX3MIX_SRC1 - [6:0] */ + +/* + * R1667 (0x683) - DSP1AUX4MIX Input 1 Source + */ +#define WM2200_DSP1AUX4MIX_SRC1_MASK 0x007F /* DSP1AUX4MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX4MIX_SRC1_SHIFT 0 /* DSP1AUX4MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX4MIX_SRC1_WIDTH 7 /* DSP1AUX4MIX_SRC1 - [6:0] */ + +/* + * R1668 (0x684) - DSP1AUX5MIX Input 1 Source + */ +#define WM2200_DSP1AUX5MIX_SRC1_MASK 0x007F /* DSP1AUX5MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX5MIX_SRC1_SHIFT 0 /* DSP1AUX5MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX5MIX_SRC1_WIDTH 7 /* DSP1AUX5MIX_SRC1 - [6:0] */ + +/* + * R1669 (0x685) - DSP1AUX6MIX Input 1 Source + */ +#define WM2200_DSP1AUX6MIX_SRC1_MASK 0x007F /* DSP1AUX6MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX6MIX_SRC1_SHIFT 0 /* DSP1AUX6MIX_SRC1 - [6:0] */ +#define WM2200_DSP1AUX6MIX_SRC1_WIDTH 7 /* DSP1AUX6MIX_SRC1 - [6:0] */ + +/* + * R1670 (0x686) - DSP2LMIX Input 1 Source + */ +#define WM2200_DSP2LMIX_SRC1_MASK 0x007F /* DSP2LMIX_SRC1 - [6:0] */ +#define WM2200_DSP2LMIX_SRC1_SHIFT 0 /* DSP2LMIX_SRC1 - [6:0] */ +#define WM2200_DSP2LMIX_SRC1_WIDTH 7 /* DSP2LMIX_SRC1 - [6:0] */ + +/* + * R1671 (0x687) - DSP2LMIX Input 1 Volume + */ +#define WM2200_DSP2LMIX_VOL1_MASK 0x00FE /* DSP2LMIX_VOL1 - [7:1] */ +#define WM2200_DSP2LMIX_VOL1_SHIFT 1 /* DSP2LMIX_VOL1 - [7:1] */ +#define WM2200_DSP2LMIX_VOL1_WIDTH 7 /* DSP2LMIX_VOL1 - [7:1] */ + +/* + * R1672 (0x688) - DSP2LMIX Input 2 Source + */ +#define WM2200_DSP2LMIX_SRC2_MASK 0x007F /* DSP2LMIX_SRC2 - [6:0] */ +#define WM2200_DSP2LMIX_SRC2_SHIFT 0 /* DSP2LMIX_SRC2 - [6:0] */ +#define WM2200_DSP2LMIX_SRC2_WIDTH 7 /* DSP2LMIX_SRC2 - [6:0] */ + +/* + * R1673 (0x689) - DSP2LMIX Input 2 Volume + */ +#define WM2200_DSP2LMIX_VOL2_MASK 0x00FE /* DSP2LMIX_VOL2 - [7:1] */ +#define WM2200_DSP2LMIX_VOL2_SHIFT 1 /* DSP2LMIX_VOL2 - [7:1] */ +#define WM2200_DSP2LMIX_VOL2_WIDTH 7 /* DSP2LMIX_VOL2 - [7:1] */ + +/* + * R1674 (0x68A) - DSP2LMIX Input 3 Source + */ +#define WM2200_DSP2LMIX_SRC3_MASK 0x007F /* DSP2LMIX_SRC3 - [6:0] */ +#define WM2200_DSP2LMIX_SRC3_SHIFT 0 /* DSP2LMIX_SRC3 - [6:0] */ +#define WM2200_DSP2LMIX_SRC3_WIDTH 7 /* DSP2LMIX_SRC3 - [6:0] */ + +/* + * R1675 (0x68B) - DSP2LMIX Input 3 Volume + */ +#define WM2200_DSP2LMIX_VOL3_MASK 0x00FE /* DSP2LMIX_VOL3 - [7:1] */ +#define WM2200_DSP2LMIX_VOL3_SHIFT 1 /* DSP2LMIX_VOL3 - [7:1] */ +#define WM2200_DSP2LMIX_VOL3_WIDTH 7 /* DSP2LMIX_VOL3 - [7:1] */ + +/* + * R1676 (0x68C) - DSP2LMIX Input 4 Source + */ +#define WM2200_DSP2LMIX_SRC4_MASK 0x007F /* DSP2LMIX_SRC4 - [6:0] */ +#define WM2200_DSP2LMIX_SRC4_SHIFT 0 /* DSP2LMIX_SRC4 - [6:0] */ +#define WM2200_DSP2LMIX_SRC4_WIDTH 7 /* DSP2LMIX_SRC4 - [6:0] */ + +/* + * R1677 (0x68D) - DSP2LMIX Input 4 Volume + */ +#define WM2200_DSP2LMIX_VOL4_MASK 0x00FE /* DSP2LMIX_VOL4 - [7:1] */ +#define WM2200_DSP2LMIX_VOL4_SHIFT 1 /* DSP2LMIX_VOL4 - [7:1] */ +#define WM2200_DSP2LMIX_VOL4_WIDTH 7 /* DSP2LMIX_VOL4 - [7:1] */ + +/* + * R1678 (0x68E) - DSP2RMIX Input 1 Source + */ +#define WM2200_DSP2RMIX_SRC1_MASK 0x007F /* DSP2RMIX_SRC1 - [6:0] */ +#define WM2200_DSP2RMIX_SRC1_SHIFT 0 /* DSP2RMIX_SRC1 - [6:0] */ +#define WM2200_DSP2RMIX_SRC1_WIDTH 7 /* DSP2RMIX_SRC1 - [6:0] */ + +/* + * R1679 (0x68F) - DSP2RMIX Input 1 Volume + */ +#define WM2200_DSP2RMIX_VOL1_MASK 0x00FE /* DSP2RMIX_VOL1 - [7:1] */ +#define WM2200_DSP2RMIX_VOL1_SHIFT 1 /* DSP2RMIX_VOL1 - [7:1] */ +#define WM2200_DSP2RMIX_VOL1_WIDTH 7 /* DSP2RMIX_VOL1 - [7:1] */ + +/* + * R1680 (0x690) - DSP2RMIX Input 2 Source + */ +#define WM2200_DSP2RMIX_SRC2_MASK 0x007F /* DSP2RMIX_SRC2 - [6:0] */ +#define WM2200_DSP2RMIX_SRC2_SHIFT 0 /* DSP2RMIX_SRC2 - [6:0] */ +#define WM2200_DSP2RMIX_SRC2_WIDTH 7 /* DSP2RMIX_SRC2 - [6:0] */ + +/* + * R1681 (0x691) - DSP2RMIX Input 2 Volume + */ +#define WM2200_DSP2RMIX_VOL2_MASK 0x00FE /* DSP2RMIX_VOL2 - [7:1] */ +#define WM2200_DSP2RMIX_VOL2_SHIFT 1 /* DSP2RMIX_VOL2 - [7:1] */ +#define WM2200_DSP2RMIX_VOL2_WIDTH 7 /* DSP2RMIX_VOL2 - [7:1] */ + +/* + * R1682 (0x692) - DSP2RMIX Input 3 Source + */ +#define WM2200_DSP2RMIX_SRC3_MASK 0x007F /* DSP2RMIX_SRC3 - [6:0] */ +#define WM2200_DSP2RMIX_SRC3_SHIFT 0 /* DSP2RMIX_SRC3 - [6:0] */ +#define WM2200_DSP2RMIX_SRC3_WIDTH 7 /* DSP2RMIX_SRC3 - [6:0] */ + +/* + * R1683 (0x693) - DSP2RMIX Input 3 Volume + */ +#define WM2200_DSP2RMIX_VOL3_MASK 0x00FE /* DSP2RMIX_VOL3 - [7:1] */ +#define WM2200_DSP2RMIX_VOL3_SHIFT 1 /* DSP2RMIX_VOL3 - [7:1] */ +#define WM2200_DSP2RMIX_VOL3_WIDTH 7 /* DSP2RMIX_VOL3 - [7:1] */ + +/* + * R1684 (0x694) - DSP2RMIX Input 4 Source + */ +#define WM2200_DSP2RMIX_SRC4_MASK 0x007F /* DSP2RMIX_SRC4 - [6:0] */ +#define WM2200_DSP2RMIX_SRC4_SHIFT 0 /* DSP2RMIX_SRC4 - [6:0] */ +#define WM2200_DSP2RMIX_SRC4_WIDTH 7 /* DSP2RMIX_SRC4 - [6:0] */ + +/* + * R1685 (0x695) - DSP2RMIX Input 4 Volume + */ +#define WM2200_DSP2RMIX_VOL4_MASK 0x00FE /* DSP2RMIX_VOL4 - [7:1] */ +#define WM2200_DSP2RMIX_VOL4_SHIFT 1 /* DSP2RMIX_VOL4 - [7:1] */ +#define WM2200_DSP2RMIX_VOL4_WIDTH 7 /* DSP2RMIX_VOL4 - [7:1] */ + +/* + * R1686 (0x696) - DSP2AUX1MIX Input 1 Source + */ +#define WM2200_DSP2AUX1MIX_SRC1_MASK 0x007F /* DSP2AUX1MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX1MIX_SRC1_SHIFT 0 /* DSP2AUX1MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX1MIX_SRC1_WIDTH 7 /* DSP2AUX1MIX_SRC1 - [6:0] */ + +/* + * R1687 (0x697) - DSP2AUX2MIX Input 1 Source + */ +#define WM2200_DSP2AUX2MIX_SRC1_MASK 0x007F /* DSP2AUX2MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX2MIX_SRC1_SHIFT 0 /* DSP2AUX2MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX2MIX_SRC1_WIDTH 7 /* DSP2AUX2MIX_SRC1 - [6:0] */ + +/* + * R1688 (0x698) - DSP2AUX3MIX Input 1 Source + */ +#define WM2200_DSP2AUX3MIX_SRC1_MASK 0x007F /* DSP2AUX3MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX3MIX_SRC1_SHIFT 0 /* DSP2AUX3MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX3MIX_SRC1_WIDTH 7 /* DSP2AUX3MIX_SRC1 - [6:0] */ + +/* + * R1689 (0x699) - DSP2AUX4MIX Input 1 Source + */ +#define WM2200_DSP2AUX4MIX_SRC1_MASK 0x007F /* DSP2AUX4MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX4MIX_SRC1_SHIFT 0 /* DSP2AUX4MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX4MIX_SRC1_WIDTH 7 /* DSP2AUX4MIX_SRC1 - [6:0] */ + +/* + * R1690 (0x69A) - DSP2AUX5MIX Input 1 Source + */ +#define WM2200_DSP2AUX5MIX_SRC1_MASK 0x007F /* DSP2AUX5MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX5MIX_SRC1_SHIFT 0 /* DSP2AUX5MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX5MIX_SRC1_WIDTH 7 /* DSP2AUX5MIX_SRC1 - [6:0] */ + +/* + * R1691 (0x69B) - DSP2AUX6MIX Input 1 Source + */ +#define WM2200_DSP2AUX6MIX_SRC1_MASK 0x007F /* DSP2AUX6MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX6MIX_SRC1_SHIFT 0 /* DSP2AUX6MIX_SRC1 - [6:0] */ +#define WM2200_DSP2AUX6MIX_SRC1_WIDTH 7 /* DSP2AUX6MIX_SRC1 - [6:0] */ + +/* + * R1792 (0x700) - GPIO CTRL 1 + */ +#define WM2200_GP1_DIR 0x8000 /* GP1_DIR */ +#define WM2200_GP1_DIR_MASK 0x8000 /* GP1_DIR */ +#define WM2200_GP1_DIR_SHIFT 15 /* GP1_DIR */ +#define WM2200_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM2200_GP1_PU 0x4000 /* GP1_PU */ +#define WM2200_GP1_PU_MASK 0x4000 /* GP1_PU */ +#define WM2200_GP1_PU_SHIFT 14 /* GP1_PU */ +#define WM2200_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM2200_GP1_PD 0x2000 /* GP1_PD */ +#define WM2200_GP1_PD_MASK 0x2000 /* GP1_PD */ +#define WM2200_GP1_PD_SHIFT 13 /* GP1_PD */ +#define WM2200_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM2200_GP1_POL 0x0400 /* GP1_POL */ +#define WM2200_GP1_POL_MASK 0x0400 /* GP1_POL */ +#define WM2200_GP1_POL_SHIFT 10 /* GP1_POL */ +#define WM2200_GP1_POL_WIDTH 1 /* GP1_POL */ +#define WM2200_GP1_OP_CFG 0x0200 /* GP1_OP_CFG */ +#define WM2200_GP1_OP_CFG_MASK 0x0200 /* GP1_OP_CFG */ +#define WM2200_GP1_OP_CFG_SHIFT 9 /* GP1_OP_CFG */ +#define WM2200_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM2200_GP1_DB 0x0100 /* GP1_DB */ +#define WM2200_GP1_DB_MASK 0x0100 /* GP1_DB */ +#define WM2200_GP1_DB_SHIFT 8 /* GP1_DB */ +#define WM2200_GP1_DB_WIDTH 1 /* GP1_DB */ +#define WM2200_GP1_LVL 0x0040 /* GP1_LVL */ +#define WM2200_GP1_LVL_MASK 0x0040 /* GP1_LVL */ +#define WM2200_GP1_LVL_SHIFT 6 /* GP1_LVL */ +#define WM2200_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM2200_GP1_FN_MASK 0x003F /* GP1_FN - [5:0] */ +#define WM2200_GP1_FN_SHIFT 0 /* GP1_FN - [5:0] */ +#define WM2200_GP1_FN_WIDTH 6 /* GP1_FN - [5:0] */ + +/* + * R1793 (0x701) - GPIO CTRL 2 + */ +#define WM2200_GP2_DIR 0x8000 /* GP2_DIR */ +#define WM2200_GP2_DIR_MASK 0x8000 /* GP2_DIR */ +#define WM2200_GP2_DIR_SHIFT 15 /* GP2_DIR */ +#define WM2200_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM2200_GP2_PU 0x4000 /* GP2_PU */ +#define WM2200_GP2_PU_MASK 0x4000 /* GP2_PU */ +#define WM2200_GP2_PU_SHIFT 14 /* GP2_PU */ +#define WM2200_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM2200_GP2_PD 0x2000 /* GP2_PD */ +#define WM2200_GP2_PD_MASK 0x2000 /* GP2_PD */ +#define WM2200_GP2_PD_SHIFT 13 /* GP2_PD */ +#define WM2200_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM2200_GP2_POL 0x0400 /* GP2_POL */ +#define WM2200_GP2_POL_MASK 0x0400 /* GP2_POL */ +#define WM2200_GP2_POL_SHIFT 10 /* GP2_POL */ +#define WM2200_GP2_POL_WIDTH 1 /* GP2_POL */ +#define WM2200_GP2_OP_CFG 0x0200 /* GP2_OP_CFG */ +#define WM2200_GP2_OP_CFG_MASK 0x0200 /* GP2_OP_CFG */ +#define WM2200_GP2_OP_CFG_SHIFT 9 /* GP2_OP_CFG */ +#define WM2200_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM2200_GP2_DB 0x0100 /* GP2_DB */ +#define WM2200_GP2_DB_MASK 0x0100 /* GP2_DB */ +#define WM2200_GP2_DB_SHIFT 8 /* GP2_DB */ +#define WM2200_GP2_DB_WIDTH 1 /* GP2_DB */ +#define WM2200_GP2_LVL 0x0040 /* GP2_LVL */ +#define WM2200_GP2_LVL_MASK 0x0040 /* GP2_LVL */ +#define WM2200_GP2_LVL_SHIFT 6 /* GP2_LVL */ +#define WM2200_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM2200_GP2_FN_MASK 0x003F /* GP2_FN - [5:0] */ +#define WM2200_GP2_FN_SHIFT 0 /* GP2_FN - [5:0] */ +#define WM2200_GP2_FN_WIDTH 6 /* GP2_FN - [5:0] */ + +/* + * R1794 (0x702) - GPIO CTRL 3 + */ +#define WM2200_GP3_DIR 0x8000 /* GP3_DIR */ +#define WM2200_GP3_DIR_MASK 0x8000 /* GP3_DIR */ +#define WM2200_GP3_DIR_SHIFT 15 /* GP3_DIR */ +#define WM2200_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM2200_GP3_PU 0x4000 /* GP3_PU */ +#define WM2200_GP3_PU_MASK 0x4000 /* GP3_PU */ +#define WM2200_GP3_PU_SHIFT 14 /* GP3_PU */ +#define WM2200_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM2200_GP3_PD 0x2000 /* GP3_PD */ +#define WM2200_GP3_PD_MASK 0x2000 /* GP3_PD */ +#define WM2200_GP3_PD_SHIFT 13 /* GP3_PD */ +#define WM2200_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM2200_GP3_POL 0x0400 /* GP3_POL */ +#define WM2200_GP3_POL_MASK 0x0400 /* GP3_POL */ +#define WM2200_GP3_POL_SHIFT 10 /* GP3_POL */ +#define WM2200_GP3_POL_WIDTH 1 /* GP3_POL */ +#define WM2200_GP3_OP_CFG 0x0200 /* GP3_OP_CFG */ +#define WM2200_GP3_OP_CFG_MASK 0x0200 /* GP3_OP_CFG */ +#define WM2200_GP3_OP_CFG_SHIFT 9 /* GP3_OP_CFG */ +#define WM2200_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM2200_GP3_DB 0x0100 /* GP3_DB */ +#define WM2200_GP3_DB_MASK 0x0100 /* GP3_DB */ +#define WM2200_GP3_DB_SHIFT 8 /* GP3_DB */ +#define WM2200_GP3_DB_WIDTH 1 /* GP3_DB */ +#define WM2200_GP3_LVL 0x0040 /* GP3_LVL */ +#define WM2200_GP3_LVL_MASK 0x0040 /* GP3_LVL */ +#define WM2200_GP3_LVL_SHIFT 6 /* GP3_LVL */ +#define WM2200_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM2200_GP3_FN_MASK 0x003F /* GP3_FN - [5:0] */ +#define WM2200_GP3_FN_SHIFT 0 /* GP3_FN - [5:0] */ +#define WM2200_GP3_FN_WIDTH 6 /* GP3_FN - [5:0] */ + +/* + * R1795 (0x703) - GPIO CTRL 4 + */ +#define WM2200_GP4_DIR 0x8000 /* GP4_DIR */ +#define WM2200_GP4_DIR_MASK 0x8000 /* GP4_DIR */ +#define WM2200_GP4_DIR_SHIFT 15 /* GP4_DIR */ +#define WM2200_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM2200_GP4_PU 0x4000 /* GP4_PU */ +#define WM2200_GP4_PU_MASK 0x4000 /* GP4_PU */ +#define WM2200_GP4_PU_SHIFT 14 /* GP4_PU */ +#define WM2200_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM2200_GP4_PD 0x2000 /* GP4_PD */ +#define WM2200_GP4_PD_MASK 0x2000 /* GP4_PD */ +#define WM2200_GP4_PD_SHIFT 13 /* GP4_PD */ +#define WM2200_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM2200_GP4_POL 0x0400 /* GP4_POL */ +#define WM2200_GP4_POL_MASK 0x0400 /* GP4_POL */ +#define WM2200_GP4_POL_SHIFT 10 /* GP4_POL */ +#define WM2200_GP4_POL_WIDTH 1 /* GP4_POL */ +#define WM2200_GP4_OP_CFG 0x0200 /* GP4_OP_CFG */ +#define WM2200_GP4_OP_CFG_MASK 0x0200 /* GP4_OP_CFG */ +#define WM2200_GP4_OP_CFG_SHIFT 9 /* GP4_OP_CFG */ +#define WM2200_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM2200_GP4_DB 0x0100 /* GP4_DB */ +#define WM2200_GP4_DB_MASK 0x0100 /* GP4_DB */ +#define WM2200_GP4_DB_SHIFT 8 /* GP4_DB */ +#define WM2200_GP4_DB_WIDTH 1 /* GP4_DB */ +#define WM2200_GP4_LVL 0x0040 /* GP4_LVL */ +#define WM2200_GP4_LVL_MASK 0x0040 /* GP4_LVL */ +#define WM2200_GP4_LVL_SHIFT 6 /* GP4_LVL */ +#define WM2200_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM2200_GP4_FN_MASK 0x003F /* GP4_FN - [5:0] */ +#define WM2200_GP4_FN_SHIFT 0 /* GP4_FN - [5:0] */ +#define WM2200_GP4_FN_WIDTH 6 /* GP4_FN - [5:0] */ + +/* + * R1799 (0x707) - ADPS1 IRQ0 + */ +#define WM2200_DSP_IRQ1 0x0002 /* DSP_IRQ1 */ +#define WM2200_DSP_IRQ1_MASK 0x0002 /* DSP_IRQ1 */ +#define WM2200_DSP_IRQ1_SHIFT 1 /* DSP_IRQ1 */ +#define WM2200_DSP_IRQ1_WIDTH 1 /* DSP_IRQ1 */ +#define WM2200_DSP_IRQ0 0x0001 /* DSP_IRQ0 */ +#define WM2200_DSP_IRQ0_MASK 0x0001 /* DSP_IRQ0 */ +#define WM2200_DSP_IRQ0_SHIFT 0 /* DSP_IRQ0 */ +#define WM2200_DSP_IRQ0_WIDTH 1 /* DSP_IRQ0 */ + +/* + * R1800 (0x708) - ADPS1 IRQ1 + */ +#define WM2200_DSP_IRQ3 0x0002 /* DSP_IRQ3 */ +#define WM2200_DSP_IRQ3_MASK 0x0002 /* DSP_IRQ3 */ +#define WM2200_DSP_IRQ3_SHIFT 1 /* DSP_IRQ3 */ +#define WM2200_DSP_IRQ3_WIDTH 1 /* DSP_IRQ3 */ +#define WM2200_DSP_IRQ2 0x0001 /* DSP_IRQ2 */ +#define WM2200_DSP_IRQ2_MASK 0x0001 /* DSP_IRQ2 */ +#define WM2200_DSP_IRQ2_SHIFT 0 /* DSP_IRQ2 */ +#define WM2200_DSP_IRQ2_WIDTH 1 /* DSP_IRQ2 */ + +/* + * R1801 (0x709) - Misc Pad Ctrl 1 + */ +#define WM2200_LDO1ENA_PD 0x8000 /* LDO1ENA_PD */ +#define WM2200_LDO1ENA_PD_MASK 0x8000 /* LDO1ENA_PD */ +#define WM2200_LDO1ENA_PD_SHIFT 15 /* LDO1ENA_PD */ +#define WM2200_LDO1ENA_PD_WIDTH 1 /* LDO1ENA_PD */ +#define WM2200_MCLK2_PD 0x2000 /* MCLK2_PD */ +#define WM2200_MCLK2_PD_MASK 0x2000 /* MCLK2_PD */ +#define WM2200_MCLK2_PD_SHIFT 13 /* MCLK2_PD */ +#define WM2200_MCLK2_PD_WIDTH 1 /* MCLK2_PD */ +#define WM2200_MCLK1_PD 0x1000 /* MCLK1_PD */ +#define WM2200_MCLK1_PD_MASK 0x1000 /* MCLK1_PD */ +#define WM2200_MCLK1_PD_SHIFT 12 /* MCLK1_PD */ +#define WM2200_MCLK1_PD_WIDTH 1 /* MCLK1_PD */ +#define WM2200_DACLRCLK1_PU 0x0400 /* DACLRCLK1_PU */ +#define WM2200_DACLRCLK1_PU_MASK 0x0400 /* DACLRCLK1_PU */ +#define WM2200_DACLRCLK1_PU_SHIFT 10 /* DACLRCLK1_PU */ +#define WM2200_DACLRCLK1_PU_WIDTH 1 /* DACLRCLK1_PU */ +#define WM2200_DACLRCLK1_PD 0x0200 /* DACLRCLK1_PD */ +#define WM2200_DACLRCLK1_PD_MASK 0x0200 /* DACLRCLK1_PD */ +#define WM2200_DACLRCLK1_PD_SHIFT 9 /* DACLRCLK1_PD */ +#define WM2200_DACLRCLK1_PD_WIDTH 1 /* DACLRCLK1_PD */ +#define WM2200_BCLK1_PU 0x0100 /* BCLK1_PU */ +#define WM2200_BCLK1_PU_MASK 0x0100 /* BCLK1_PU */ +#define WM2200_BCLK1_PU_SHIFT 8 /* BCLK1_PU */ +#define WM2200_BCLK1_PU_WIDTH 1 /* BCLK1_PU */ +#define WM2200_BCLK1_PD 0x0080 /* BCLK1_PD */ +#define WM2200_BCLK1_PD_MASK 0x0080 /* BCLK1_PD */ +#define WM2200_BCLK1_PD_SHIFT 7 /* BCLK1_PD */ +#define WM2200_BCLK1_PD_WIDTH 1 /* BCLK1_PD */ +#define WM2200_DACDAT1_PU 0x0040 /* DACDAT1_PU */ +#define WM2200_DACDAT1_PU_MASK 0x0040 /* DACDAT1_PU */ +#define WM2200_DACDAT1_PU_SHIFT 6 /* DACDAT1_PU */ +#define WM2200_DACDAT1_PU_WIDTH 1 /* DACDAT1_PU */ +#define WM2200_DACDAT1_PD 0x0020 /* DACDAT1_PD */ +#define WM2200_DACDAT1_PD_MASK 0x0020 /* DACDAT1_PD */ +#define WM2200_DACDAT1_PD_SHIFT 5 /* DACDAT1_PD */ +#define WM2200_DACDAT1_PD_WIDTH 1 /* DACDAT1_PD */ +#define WM2200_DMICDAT3_PD 0x0010 /* DMICDAT3_PD */ +#define WM2200_DMICDAT3_PD_MASK 0x0010 /* DMICDAT3_PD */ +#define WM2200_DMICDAT3_PD_SHIFT 4 /* DMICDAT3_PD */ +#define WM2200_DMICDAT3_PD_WIDTH 1 /* DMICDAT3_PD */ +#define WM2200_DMICDAT2_PD 0x0008 /* DMICDAT2_PD */ +#define WM2200_DMICDAT2_PD_MASK 0x0008 /* DMICDAT2_PD */ +#define WM2200_DMICDAT2_PD_SHIFT 3 /* DMICDAT2_PD */ +#define WM2200_DMICDAT2_PD_WIDTH 1 /* DMICDAT2_PD */ +#define WM2200_DMICDAT1_PD 0x0004 /* DMICDAT1_PD */ +#define WM2200_DMICDAT1_PD_MASK 0x0004 /* DMICDAT1_PD */ +#define WM2200_DMICDAT1_PD_SHIFT 2 /* DMICDAT1_PD */ +#define WM2200_DMICDAT1_PD_WIDTH 1 /* DMICDAT1_PD */ +#define WM2200_RSTB_PU 0x0002 /* RSTB_PU */ +#define WM2200_RSTB_PU_MASK 0x0002 /* RSTB_PU */ +#define WM2200_RSTB_PU_SHIFT 1 /* RSTB_PU */ +#define WM2200_RSTB_PU_WIDTH 1 /* RSTB_PU */ +#define WM2200_ADDR_PD 0x0001 /* ADDR_PD */ +#define WM2200_ADDR_PD_MASK 0x0001 /* ADDR_PD */ +#define WM2200_ADDR_PD_SHIFT 0 /* ADDR_PD */ +#define WM2200_ADDR_PD_WIDTH 1 /* ADDR_PD */ + +/* + * R2048 (0x800) - Interrupt Status 1 + */ +#define WM2200_DSP_IRQ0_EINT 0x0080 /* DSP_IRQ0_EINT */ +#define WM2200_DSP_IRQ0_EINT_MASK 0x0080 /* DSP_IRQ0_EINT */ +#define WM2200_DSP_IRQ0_EINT_SHIFT 7 /* DSP_IRQ0_EINT */ +#define WM2200_DSP_IRQ0_EINT_WIDTH 1 /* DSP_IRQ0_EINT */ +#define WM2200_DSP_IRQ1_EINT 0x0040 /* DSP_IRQ1_EINT */ +#define WM2200_DSP_IRQ1_EINT_MASK 0x0040 /* DSP_IRQ1_EINT */ +#define WM2200_DSP_IRQ1_EINT_SHIFT 6 /* DSP_IRQ1_EINT */ +#define WM2200_DSP_IRQ1_EINT_WIDTH 1 /* DSP_IRQ1_EINT */ +#define WM2200_DSP_IRQ2_EINT 0x0020 /* DSP_IRQ2_EINT */ +#define WM2200_DSP_IRQ2_EINT_MASK 0x0020 /* DSP_IRQ2_EINT */ +#define WM2200_DSP_IRQ2_EINT_SHIFT 5 /* DSP_IRQ2_EINT */ +#define WM2200_DSP_IRQ2_EINT_WIDTH 1 /* DSP_IRQ2_EINT */ +#define WM2200_DSP_IRQ3_EINT 0x0010 /* DSP_IRQ3_EINT */ +#define WM2200_DSP_IRQ3_EINT_MASK 0x0010 /* DSP_IRQ3_EINT */ +#define WM2200_DSP_IRQ3_EINT_SHIFT 4 /* DSP_IRQ3_EINT */ +#define WM2200_DSP_IRQ3_EINT_WIDTH 1 /* DSP_IRQ3_EINT */ +#define WM2200_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM2200_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM2200_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM2200_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM2200_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM2200_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM2200_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM2200_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM2200_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM2200_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM2200_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM2200_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM2200_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM2200_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM2200_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM2200_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R2049 (0x801) - Interrupt Status 1 Mask + */ +#define WM2200_IM_DSP_IRQ0_EINT 0x0080 /* IM_DSP_IRQ0_EINT */ +#define WM2200_IM_DSP_IRQ0_EINT_MASK 0x0080 /* IM_DSP_IRQ0_EINT */ +#define WM2200_IM_DSP_IRQ0_EINT_SHIFT 7 /* IM_DSP_IRQ0_EINT */ +#define WM2200_IM_DSP_IRQ0_EINT_WIDTH 1 /* IM_DSP_IRQ0_EINT */ +#define WM2200_IM_DSP_IRQ1_EINT 0x0040 /* IM_DSP_IRQ1_EINT */ +#define WM2200_IM_DSP_IRQ1_EINT_MASK 0x0040 /* IM_DSP_IRQ1_EINT */ +#define WM2200_IM_DSP_IRQ1_EINT_SHIFT 6 /* IM_DSP_IRQ1_EINT */ +#define WM2200_IM_DSP_IRQ1_EINT_WIDTH 1 /* IM_DSP_IRQ1_EINT */ +#define WM2200_IM_DSP_IRQ2_EINT 0x0020 /* IM_DSP_IRQ2_EINT */ +#define WM2200_IM_DSP_IRQ2_EINT_MASK 0x0020 /* IM_DSP_IRQ2_EINT */ +#define WM2200_IM_DSP_IRQ2_EINT_SHIFT 5 /* IM_DSP_IRQ2_EINT */ +#define WM2200_IM_DSP_IRQ2_EINT_WIDTH 1 /* IM_DSP_IRQ2_EINT */ +#define WM2200_IM_DSP_IRQ3_EINT 0x0010 /* IM_DSP_IRQ3_EINT */ +#define WM2200_IM_DSP_IRQ3_EINT_MASK 0x0010 /* IM_DSP_IRQ3_EINT */ +#define WM2200_IM_DSP_IRQ3_EINT_SHIFT 4 /* IM_DSP_IRQ3_EINT */ +#define WM2200_IM_DSP_IRQ3_EINT_WIDTH 1 /* IM_DSP_IRQ3_EINT */ +#define WM2200_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM2200_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM2200_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM2200_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM2200_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM2200_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM2200_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM2200_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM2200_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM2200_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM2200_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM2200_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM2200_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM2200_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM2200_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM2200_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R2050 (0x802) - Interrupt Status 2 + */ +#define WM2200_WSEQ_BUSY_EINT 0x0100 /* WSEQ_BUSY_EINT */ +#define WM2200_WSEQ_BUSY_EINT_MASK 0x0100 /* WSEQ_BUSY_EINT */ +#define WM2200_WSEQ_BUSY_EINT_SHIFT 8 /* WSEQ_BUSY_EINT */ +#define WM2200_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM2200_FLL_LOCK_EINT 0x0002 /* FLL_LOCK_EINT */ +#define WM2200_FLL_LOCK_EINT_MASK 0x0002 /* FLL_LOCK_EINT */ +#define WM2200_FLL_LOCK_EINT_SHIFT 1 /* FLL_LOCK_EINT */ +#define WM2200_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */ +#define WM2200_CLKGEN_EINT 0x0001 /* CLKGEN_EINT */ +#define WM2200_CLKGEN_EINT_MASK 0x0001 /* CLKGEN_EINT */ +#define WM2200_CLKGEN_EINT_SHIFT 0 /* CLKGEN_EINT */ +#define WM2200_CLKGEN_EINT_WIDTH 1 /* CLKGEN_EINT */ + +/* + * R2051 (0x803) - Interrupt Raw Status 2 + */ +#define WM2200_WSEQ_BUSY_STS 0x0100 /* WSEQ_BUSY_STS */ +#define WM2200_WSEQ_BUSY_STS_MASK 0x0100 /* WSEQ_BUSY_STS */ +#define WM2200_WSEQ_BUSY_STS_SHIFT 8 /* WSEQ_BUSY_STS */ +#define WM2200_WSEQ_BUSY_STS_WIDTH 1 /* WSEQ_BUSY_STS */ +#define WM2200_FLL_LOCK_STS 0x0002 /* FLL_LOCK_STS */ +#define WM2200_FLL_LOCK_STS_MASK 0x0002 /* FLL_LOCK_STS */ +#define WM2200_FLL_LOCK_STS_SHIFT 1 /* FLL_LOCK_STS */ +#define WM2200_FLL_LOCK_STS_WIDTH 1 /* FLL_LOCK_STS */ +#define WM2200_CLKGEN_STS 0x0001 /* CLKGEN_STS */ +#define WM2200_CLKGEN_STS_MASK 0x0001 /* CLKGEN_STS */ +#define WM2200_CLKGEN_STS_SHIFT 0 /* CLKGEN_STS */ +#define WM2200_CLKGEN_STS_WIDTH 1 /* CLKGEN_STS */ + +/* + * R2052 (0x804) - Interrupt Status 2 Mask + */ +#define WM2200_IM_WSEQ_BUSY_EINT 0x0100 /* IM_WSEQ_BUSY_EINT */ +#define WM2200_IM_WSEQ_BUSY_EINT_MASK 0x0100 /* IM_WSEQ_BUSY_EINT */ +#define WM2200_IM_WSEQ_BUSY_EINT_SHIFT 8 /* IM_WSEQ_BUSY_EINT */ +#define WM2200_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM2200_IM_FLL_LOCK_EINT 0x0002 /* IM_FLL_LOCK_EINT */ +#define WM2200_IM_FLL_LOCK_EINT_MASK 0x0002 /* IM_FLL_LOCK_EINT */ +#define WM2200_IM_FLL_LOCK_EINT_SHIFT 1 /* IM_FLL_LOCK_EINT */ +#define WM2200_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */ +#define WM2200_IM_CLKGEN_EINT 0x0001 /* IM_CLKGEN_EINT */ +#define WM2200_IM_CLKGEN_EINT_MASK 0x0001 /* IM_CLKGEN_EINT */ +#define WM2200_IM_CLKGEN_EINT_SHIFT 0 /* IM_CLKGEN_EINT */ +#define WM2200_IM_CLKGEN_EINT_WIDTH 1 /* IM_CLKGEN_EINT */ + +/* + * R2056 (0x808) - Interrupt Control + */ +#define WM2200_IM_IRQ 0x0001 /* IM_IRQ */ +#define WM2200_IM_IRQ_MASK 0x0001 /* IM_IRQ */ +#define WM2200_IM_IRQ_SHIFT 0 /* IM_IRQ */ +#define WM2200_IM_IRQ_WIDTH 1 /* IM_IRQ */ + +/* + * R2304 (0x900) - EQL_1 + */ +#define WM2200_EQL_B1_GAIN_MASK 0xF800 /* EQL_B1_GAIN - [15:11] */ +#define WM2200_EQL_B1_GAIN_SHIFT 11 /* EQL_B1_GAIN - [15:11] */ +#define WM2200_EQL_B1_GAIN_WIDTH 5 /* EQL_B1_GAIN - [15:11] */ +#define WM2200_EQL_B2_GAIN_MASK 0x07C0 /* EQL_B2_GAIN - [10:6] */ +#define WM2200_EQL_B2_GAIN_SHIFT 6 /* EQL_B2_GAIN - [10:6] */ +#define WM2200_EQL_B2_GAIN_WIDTH 5 /* EQL_B2_GAIN - [10:6] */ +#define WM2200_EQL_B3_GAIN_MASK 0x003E /* EQL_B3_GAIN - [5:1] */ +#define WM2200_EQL_B3_GAIN_SHIFT 1 /* EQL_B3_GAIN - [5:1] */ +#define WM2200_EQL_B3_GAIN_WIDTH 5 /* EQL_B3_GAIN - [5:1] */ +#define WM2200_EQL_ENA 0x0001 /* EQL_ENA */ +#define WM2200_EQL_ENA_MASK 0x0001 /* EQL_ENA */ +#define WM2200_EQL_ENA_SHIFT 0 /* EQL_ENA */ +#define WM2200_EQL_ENA_WIDTH 1 /* EQL_ENA */ + +/* + * R2305 (0x901) - EQL_2 + */ +#define WM2200_EQL_B4_GAIN_MASK 0xF800 /* EQL_B4_GAIN - [15:11] */ +#define WM2200_EQL_B4_GAIN_SHIFT 11 /* EQL_B4_GAIN - [15:11] */ +#define WM2200_EQL_B4_GAIN_WIDTH 5 /* EQL_B4_GAIN - [15:11] */ +#define WM2200_EQL_B5_GAIN_MASK 0x07C0 /* EQL_B5_GAIN - [10:6] */ +#define WM2200_EQL_B5_GAIN_SHIFT 6 /* EQL_B5_GAIN - [10:6] */ +#define WM2200_EQL_B5_GAIN_WIDTH 5 /* EQL_B5_GAIN - [10:6] */ + +/* + * R2306 (0x902) - EQL_3 + */ +#define WM2200_EQL_B1_A_MASK 0xFFFF /* EQL_B1_A - [15:0] */ +#define WM2200_EQL_B1_A_SHIFT 0 /* EQL_B1_A - [15:0] */ +#define WM2200_EQL_B1_A_WIDTH 16 /* EQL_B1_A - [15:0] */ + +/* + * R2307 (0x903) - EQL_4 + */ +#define WM2200_EQL_B1_B_MASK 0xFFFF /* EQL_B1_B - [15:0] */ +#define WM2200_EQL_B1_B_SHIFT 0 /* EQL_B1_B - [15:0] */ +#define WM2200_EQL_B1_B_WIDTH 16 /* EQL_B1_B - [15:0] */ + +/* + * R2308 (0x904) - EQL_5 + */ +#define WM2200_EQL_B1_PG_MASK 0xFFFF /* EQL_B1_PG - [15:0] */ +#define WM2200_EQL_B1_PG_SHIFT 0 /* EQL_B1_PG - [15:0] */ +#define WM2200_EQL_B1_PG_WIDTH 16 /* EQL_B1_PG - [15:0] */ + +/* + * R2309 (0x905) - EQL_6 + */ +#define WM2200_EQL_B2_A_MASK 0xFFFF /* EQL_B2_A - [15:0] */ +#define WM2200_EQL_B2_A_SHIFT 0 /* EQL_B2_A - [15:0] */ +#define WM2200_EQL_B2_A_WIDTH 16 /* EQL_B2_A - [15:0] */ + +/* + * R2310 (0x906) - EQL_7 + */ +#define WM2200_EQL_B2_B_MASK 0xFFFF /* EQL_B2_B - [15:0] */ +#define WM2200_EQL_B2_B_SHIFT 0 /* EQL_B2_B - [15:0] */ +#define WM2200_EQL_B2_B_WIDTH 16 /* EQL_B2_B - [15:0] */ + +/* + * R2311 (0x907) - EQL_8 + */ +#define WM2200_EQL_B2_C_MASK 0xFFFF /* EQL_B2_C - [15:0] */ +#define WM2200_EQL_B2_C_SHIFT 0 /* EQL_B2_C - [15:0] */ +#define WM2200_EQL_B2_C_WIDTH 16 /* EQL_B2_C - [15:0] */ + +/* + * R2312 (0x908) - EQL_9 + */ +#define WM2200_EQL_B2_PG_MASK 0xFFFF /* EQL_B2_PG - [15:0] */ +#define WM2200_EQL_B2_PG_SHIFT 0 /* EQL_B2_PG - [15:0] */ +#define WM2200_EQL_B2_PG_WIDTH 16 /* EQL_B2_PG - [15:0] */ + +/* + * R2313 (0x909) - EQL_10 + */ +#define WM2200_EQL_B3_A_MASK 0xFFFF /* EQL_B3_A - [15:0] */ +#define WM2200_EQL_B3_A_SHIFT 0 /* EQL_B3_A - [15:0] */ +#define WM2200_EQL_B3_A_WIDTH 16 /* EQL_B3_A - [15:0] */ + +/* + * R2314 (0x90A) - EQL_11 + */ +#define WM2200_EQL_B3_B_MASK 0xFFFF /* EQL_B3_B - [15:0] */ +#define WM2200_EQL_B3_B_SHIFT 0 /* EQL_B3_B - [15:0] */ +#define WM2200_EQL_B3_B_WIDTH 16 /* EQL_B3_B - [15:0] */ + +/* + * R2315 (0x90B) - EQL_12 + */ +#define WM2200_EQL_B3_C_MASK 0xFFFF /* EQL_B3_C - [15:0] */ +#define WM2200_EQL_B3_C_SHIFT 0 /* EQL_B3_C - [15:0] */ +#define WM2200_EQL_B3_C_WIDTH 16 /* EQL_B3_C - [15:0] */ + +/* + * R2316 (0x90C) - EQL_13 + */ +#define WM2200_EQL_B3_PG_MASK 0xFFFF /* EQL_B3_PG - [15:0] */ +#define WM2200_EQL_B3_PG_SHIFT 0 /* EQL_B3_PG - [15:0] */ +#define WM2200_EQL_B3_PG_WIDTH 16 /* EQL_B3_PG - [15:0] */ + +/* + * R2317 (0x90D) - EQL_14 + */ +#define WM2200_EQL_B4_A_MASK 0xFFFF /* EQL_B4_A - [15:0] */ +#define WM2200_EQL_B4_A_SHIFT 0 /* EQL_B4_A - [15:0] */ +#define WM2200_EQL_B4_A_WIDTH 16 /* EQL_B4_A - [15:0] */ + +/* + * R2318 (0x90E) - EQL_15 + */ +#define WM2200_EQL_B4_B_MASK 0xFFFF /* EQL_B4_B - [15:0] */ +#define WM2200_EQL_B4_B_SHIFT 0 /* EQL_B4_B - [15:0] */ +#define WM2200_EQL_B4_B_WIDTH 16 /* EQL_B4_B - [15:0] */ + +/* + * R2319 (0x90F) - EQL_16 + */ +#define WM2200_EQL_B4_C_MASK 0xFFFF /* EQL_B4_C - [15:0] */ +#define WM2200_EQL_B4_C_SHIFT 0 /* EQL_B4_C - [15:0] */ +#define WM2200_EQL_B4_C_WIDTH 16 /* EQL_B4_C - [15:0] */ + +/* + * R2320 (0x910) - EQL_17 + */ +#define WM2200_EQL_B4_PG_MASK 0xFFFF /* EQL_B4_PG - [15:0] */ +#define WM2200_EQL_B4_PG_SHIFT 0 /* EQL_B4_PG - [15:0] */ +#define WM2200_EQL_B4_PG_WIDTH 16 /* EQL_B4_PG - [15:0] */ + +/* + * R2321 (0x911) - EQL_18 + */ +#define WM2200_EQL_B5_A_MASK 0xFFFF /* EQL_B5_A - [15:0] */ +#define WM2200_EQL_B5_A_SHIFT 0 /* EQL_B5_A - [15:0] */ +#define WM2200_EQL_B5_A_WIDTH 16 /* EQL_B5_A - [15:0] */ + +/* + * R2322 (0x912) - EQL_19 + */ +#define WM2200_EQL_B5_B_MASK 0xFFFF /* EQL_B5_B - [15:0] */ +#define WM2200_EQL_B5_B_SHIFT 0 /* EQL_B5_B - [15:0] */ +#define WM2200_EQL_B5_B_WIDTH 16 /* EQL_B5_B - [15:0] */ + +/* + * R2323 (0x913) - EQL_20 + */ +#define WM2200_EQL_B5_PG_MASK 0xFFFF /* EQL_B5_PG - [15:0] */ +#define WM2200_EQL_B5_PG_SHIFT 0 /* EQL_B5_PG - [15:0] */ +#define WM2200_EQL_B5_PG_WIDTH 16 /* EQL_B5_PG - [15:0] */ + +/* + * R2326 (0x916) - EQR_1 + */ +#define WM2200_EQR_B1_GAIN_MASK 0xF800 /* EQR_B1_GAIN - [15:11] */ +#define WM2200_EQR_B1_GAIN_SHIFT 11 /* EQR_B1_GAIN - [15:11] */ +#define WM2200_EQR_B1_GAIN_WIDTH 5 /* EQR_B1_GAIN - [15:11] */ +#define WM2200_EQR_B2_GAIN_MASK 0x07C0 /* EQR_B2_GAIN - [10:6] */ +#define WM2200_EQR_B2_GAIN_SHIFT 6 /* EQR_B2_GAIN - [10:6] */ +#define WM2200_EQR_B2_GAIN_WIDTH 5 /* EQR_B2_GAIN - [10:6] */ +#define WM2200_EQR_B3_GAIN_MASK 0x003E /* EQR_B3_GAIN - [5:1] */ +#define WM2200_EQR_B3_GAIN_SHIFT 1 /* EQR_B3_GAIN - [5:1] */ +#define WM2200_EQR_B3_GAIN_WIDTH 5 /* EQR_B3_GAIN - [5:1] */ +#define WM2200_EQR_ENA 0x0001 /* EQR_ENA */ +#define WM2200_EQR_ENA_MASK 0x0001 /* EQR_ENA */ +#define WM2200_EQR_ENA_SHIFT 0 /* EQR_ENA */ +#define WM2200_EQR_ENA_WIDTH 1 /* EQR_ENA */ + +/* + * R2327 (0x917) - EQR_2 + */ +#define WM2200_EQR_B4_GAIN_MASK 0xF800 /* EQR_B4_GAIN - [15:11] */ +#define WM2200_EQR_B4_GAIN_SHIFT 11 /* EQR_B4_GAIN - [15:11] */ +#define WM2200_EQR_B4_GAIN_WIDTH 5 /* EQR_B4_GAIN - [15:11] */ +#define WM2200_EQR_B5_GAIN_MASK 0x07C0 /* EQR_B5_GAIN - [10:6] */ +#define WM2200_EQR_B5_GAIN_SHIFT 6 /* EQR_B5_GAIN - [10:6] */ +#define WM2200_EQR_B5_GAIN_WIDTH 5 /* EQR_B5_GAIN - [10:6] */ + +/* + * R2328 (0x918) - EQR_3 + */ +#define WM2200_EQR_B1_A_MASK 0xFFFF /* EQR_B1_A - [15:0] */ +#define WM2200_EQR_B1_A_SHIFT 0 /* EQR_B1_A - [15:0] */ +#define WM2200_EQR_B1_A_WIDTH 16 /* EQR_B1_A - [15:0] */ + +/* + * R2329 (0x919) - EQR_4 + */ +#define WM2200_EQR_B1_B_MASK 0xFFFF /* EQR_B1_B - [15:0] */ +#define WM2200_EQR_B1_B_SHIFT 0 /* EQR_B1_B - [15:0] */ +#define WM2200_EQR_B1_B_WIDTH 16 /* EQR_B1_B - [15:0] */ + +/* + * R2330 (0x91A) - EQR_5 + */ +#define WM2200_EQR_B1_PG_MASK 0xFFFF /* EQR_B1_PG - [15:0] */ +#define WM2200_EQR_B1_PG_SHIFT 0 /* EQR_B1_PG - [15:0] */ +#define WM2200_EQR_B1_PG_WIDTH 16 /* EQR_B1_PG - [15:0] */ + +/* + * R2331 (0x91B) - EQR_6 + */ +#define WM2200_EQR_B2_A_MASK 0xFFFF /* EQR_B2_A - [15:0] */ +#define WM2200_EQR_B2_A_SHIFT 0 /* EQR_B2_A - [15:0] */ +#define WM2200_EQR_B2_A_WIDTH 16 /* EQR_B2_A - [15:0] */ + +/* + * R2332 (0x91C) - EQR_7 + */ +#define WM2200_EQR_B2_B_MASK 0xFFFF /* EQR_B2_B - [15:0] */ +#define WM2200_EQR_B2_B_SHIFT 0 /* EQR_B2_B - [15:0] */ +#define WM2200_EQR_B2_B_WIDTH 16 /* EQR_B2_B - [15:0] */ + +/* + * R2333 (0x91D) - EQR_8 + */ +#define WM2200_EQR_B2_C_MASK 0xFFFF /* EQR_B2_C - [15:0] */ +#define WM2200_EQR_B2_C_SHIFT 0 /* EQR_B2_C - [15:0] */ +#define WM2200_EQR_B2_C_WIDTH 16 /* EQR_B2_C - [15:0] */ + +/* + * R2334 (0x91E) - EQR_9 + */ +#define WM2200_EQR_B2_PG_MASK 0xFFFF /* EQR_B2_PG - [15:0] */ +#define WM2200_EQR_B2_PG_SHIFT 0 /* EQR_B2_PG - [15:0] */ +#define WM2200_EQR_B2_PG_WIDTH 16 /* EQR_B2_PG - [15:0] */ + +/* + * R2335 (0x91F) - EQR_10 + */ +#define WM2200_EQR_B3_A_MASK 0xFFFF /* EQR_B3_A - [15:0] */ +#define WM2200_EQR_B3_A_SHIFT 0 /* EQR_B3_A - [15:0] */ +#define WM2200_EQR_B3_A_WIDTH 16 /* EQR_B3_A - [15:0] */ + +/* + * R2336 (0x920) - EQR_11 + */ +#define WM2200_EQR_B3_B_MASK 0xFFFF /* EQR_B3_B - [15:0] */ +#define WM2200_EQR_B3_B_SHIFT 0 /* EQR_B3_B - [15:0] */ +#define WM2200_EQR_B3_B_WIDTH 16 /* EQR_B3_B - [15:0] */ + +/* + * R2337 (0x921) - EQR_12 + */ +#define WM2200_EQR_B3_C_MASK 0xFFFF /* EQR_B3_C - [15:0] */ +#define WM2200_EQR_B3_C_SHIFT 0 /* EQR_B3_C - [15:0] */ +#define WM2200_EQR_B3_C_WIDTH 16 /* EQR_B3_C - [15:0] */ + +/* + * R2338 (0x922) - EQR_13 + */ +#define WM2200_EQR_B3_PG_MASK 0xFFFF /* EQR_B3_PG - [15:0] */ +#define WM2200_EQR_B3_PG_SHIFT 0 /* EQR_B3_PG - [15:0] */ +#define WM2200_EQR_B3_PG_WIDTH 16 /* EQR_B3_PG - [15:0] */ + +/* + * R2339 (0x923) - EQR_14 + */ +#define WM2200_EQR_B4_A_MASK 0xFFFF /* EQR_B4_A - [15:0] */ +#define WM2200_EQR_B4_A_SHIFT 0 /* EQR_B4_A - [15:0] */ +#define WM2200_EQR_B4_A_WIDTH 16 /* EQR_B4_A - [15:0] */ + +/* + * R2340 (0x924) - EQR_15 + */ +#define WM2200_EQR_B4_B_MASK 0xFFFF /* EQR_B4_B - [15:0] */ +#define WM2200_EQR_B4_B_SHIFT 0 /* EQR_B4_B - [15:0] */ +#define WM2200_EQR_B4_B_WIDTH 16 /* EQR_B4_B - [15:0] */ + +/* + * R2341 (0x925) - EQR_16 + */ +#define WM2200_EQR_B4_C_MASK 0xFFFF /* EQR_B4_C - [15:0] */ +#define WM2200_EQR_B4_C_SHIFT 0 /* EQR_B4_C - [15:0] */ +#define WM2200_EQR_B4_C_WIDTH 16 /* EQR_B4_C - [15:0] */ + +/* + * R2342 (0x926) - EQR_17 + */ +#define WM2200_EQR_B4_PG_MASK 0xFFFF /* EQR_B4_PG - [15:0] */ +#define WM2200_EQR_B4_PG_SHIFT 0 /* EQR_B4_PG - [15:0] */ +#define WM2200_EQR_B4_PG_WIDTH 16 /* EQR_B4_PG - [15:0] */ + +/* + * R2343 (0x927) - EQR_18 + */ +#define WM2200_EQR_B5_A_MASK 0xFFFF /* EQR_B5_A - [15:0] */ +#define WM2200_EQR_B5_A_SHIFT 0 /* EQR_B5_A - [15:0] */ +#define WM2200_EQR_B5_A_WIDTH 16 /* EQR_B5_A - [15:0] */ + +/* + * R2344 (0x928) - EQR_19 + */ +#define WM2200_EQR_B5_B_MASK 0xFFFF /* EQR_B5_B - [15:0] */ +#define WM2200_EQR_B5_B_SHIFT 0 /* EQR_B5_B - [15:0] */ +#define WM2200_EQR_B5_B_WIDTH 16 /* EQR_B5_B - [15:0] */ + +/* + * R2345 (0x929) - EQR_20 + */ +#define WM2200_EQR_B5_PG_MASK 0xFFFF /* EQR_B5_PG - [15:0] */ +#define WM2200_EQR_B5_PG_SHIFT 0 /* EQR_B5_PG - [15:0] */ +#define WM2200_EQR_B5_PG_WIDTH 16 /* EQR_B5_PG - [15:0] */ + +/* + * R2366 (0x93E) - HPLPF1_1 + */ +#define WM2200_LHPF1_MODE 0x0002 /* LHPF1_MODE */ +#define WM2200_LHPF1_MODE_MASK 0x0002 /* LHPF1_MODE */ +#define WM2200_LHPF1_MODE_SHIFT 1 /* LHPF1_MODE */ +#define WM2200_LHPF1_MODE_WIDTH 1 /* LHPF1_MODE */ +#define WM2200_LHPF1_ENA 0x0001 /* LHPF1_ENA */ +#define WM2200_LHPF1_ENA_MASK 0x0001 /* LHPF1_ENA */ +#define WM2200_LHPF1_ENA_SHIFT 0 /* LHPF1_ENA */ +#define WM2200_LHPF1_ENA_WIDTH 1 /* LHPF1_ENA */ + +/* + * R2367 (0x93F) - HPLPF1_2 + */ +#define WM2200_LHPF1_COEFF_MASK 0xFFFF /* LHPF1_COEFF - [15:0] */ +#define WM2200_LHPF1_COEFF_SHIFT 0 /* LHPF1_COEFF - [15:0] */ +#define WM2200_LHPF1_COEFF_WIDTH 16 /* LHPF1_COEFF - [15:0] */ + +/* + * R2370 (0x942) - HPLPF2_1 + */ +#define WM2200_LHPF2_MODE 0x0002 /* LHPF2_MODE */ +#define WM2200_LHPF2_MODE_MASK 0x0002 /* LHPF2_MODE */ +#define WM2200_LHPF2_MODE_SHIFT 1 /* LHPF2_MODE */ +#define WM2200_LHPF2_MODE_WIDTH 1 /* LHPF2_MODE */ +#define WM2200_LHPF2_ENA 0x0001 /* LHPF2_ENA */ +#define WM2200_LHPF2_ENA_MASK 0x0001 /* LHPF2_ENA */ +#define WM2200_LHPF2_ENA_SHIFT 0 /* LHPF2_ENA */ +#define WM2200_LHPF2_ENA_WIDTH 1 /* LHPF2_ENA */ + +/* + * R2371 (0x943) - HPLPF2_2 + */ +#define WM2200_LHPF2_COEFF_MASK 0xFFFF /* LHPF2_COEFF - [15:0] */ +#define WM2200_LHPF2_COEFF_SHIFT 0 /* LHPF2_COEFF - [15:0] */ +#define WM2200_LHPF2_COEFF_WIDTH 16 /* LHPF2_COEFF - [15:0] */ + +/* + * R2560 (0xA00) - DSP1 Control 1 + */ +#define WM2200_DSP1_RW_SEQUENCE_ENA 0x0001 /* DSP1_RW_SEQUENCE_ENA */ +#define WM2200_DSP1_RW_SEQUENCE_ENA_MASK 0x0001 /* DSP1_RW_SEQUENCE_ENA */ +#define WM2200_DSP1_RW_SEQUENCE_ENA_SHIFT 0 /* DSP1_RW_SEQUENCE_ENA */ +#define WM2200_DSP1_RW_SEQUENCE_ENA_WIDTH 1 /* DSP1_RW_SEQUENCE_ENA */ + +/* + * R2562 (0xA02) - DSP1 Control 2 + */ +#define WM2200_DSP1_PAGE_BASE_PM_0_MASK 0xFF00 /* DSP1_PAGE_BASE_PM - [15:8] */ +#define WM2200_DSP1_PAGE_BASE_PM_0_SHIFT 8 /* DSP1_PAGE_BASE_PM - [15:8] */ +#define WM2200_DSP1_PAGE_BASE_PM_0_WIDTH 8 /* DSP1_PAGE_BASE_PM - [15:8] */ + +/* + * R2563 (0xA03) - DSP1 Control 3 + */ +#define WM2200_DSP1_PAGE_BASE_DM_0_MASK 0xFF00 /* DSP1_PAGE_BASE_DM - [15:8] */ +#define WM2200_DSP1_PAGE_BASE_DM_0_SHIFT 8 /* DSP1_PAGE_BASE_DM - [15:8] */ +#define WM2200_DSP1_PAGE_BASE_DM_0_WIDTH 8 /* DSP1_PAGE_BASE_DM - [15:8] */ + +/* + * R2564 (0xA04) - DSP1 Control 4 + */ +#define WM2200_DSP1_PAGE_BASE_ZM_0_MASK 0xFF00 /* DSP1_PAGE_BASE_ZM - [15:8] */ +#define WM2200_DSP1_PAGE_BASE_ZM_0_SHIFT 8 /* DSP1_PAGE_BASE_ZM - [15:8] */ +#define WM2200_DSP1_PAGE_BASE_ZM_0_WIDTH 8 /* DSP1_PAGE_BASE_ZM - [15:8] */ + +/* + * R2566 (0xA06) - DSP1 Control 5 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_0_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_0_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_0_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_0 - [13:0] */ + +/* + * R2567 (0xA07) - DSP1 Control 6 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_1_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_1_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_1_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_1 - [13:0] */ + +/* + * R2568 (0xA08) - DSP1 Control 7 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_2_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_2_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_2_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_2 - [13:0] */ + +/* + * R2569 (0xA09) - DSP1 Control 8 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_3_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_3_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_3_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_3 - [13:0] */ + +/* + * R2570 (0xA0A) - DSP1 Control 9 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_4_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_4_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_4_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_4 - [13:0] */ + +/* + * R2571 (0xA0B) - DSP1 Control 10 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_5_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_5_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_5_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_5 - [13:0] */ + +/* + * R2572 (0xA0C) - DSP1 Control 11 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_6_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_6 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_6_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_6 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_6_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_6 - [13:0] */ + +/* + * R2573 (0xA0D) - DSP1 Control 12 + */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_7_MASK 0x3FFF /* DSP1_START_ADDRESS_WDMA_BUFFER_7 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_7_SHIFT 0 /* DSP1_START_ADDRESS_WDMA_BUFFER_7 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_WDMA_BUFFER_7_WIDTH 14 /* DSP1_START_ADDRESS_WDMA_BUFFER_7 - [13:0] */ + +/* + * R2575 (0xA0F) - DSP1 Control 13 + */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_0_MASK 0x3FFF /* DSP1_START_ADDRESS_RDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_0_SHIFT 0 /* DSP1_START_ADDRESS_RDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_0_WIDTH 14 /* DSP1_START_ADDRESS_RDMA_BUFFER_0 - [13:0] */ + +/* + * R2576 (0xA10) - DSP1 Control 14 + */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_1_MASK 0x3FFF /* DSP1_START_ADDRESS_RDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_1_SHIFT 0 /* DSP1_START_ADDRESS_RDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_1_WIDTH 14 /* DSP1_START_ADDRESS_RDMA_BUFFER_1 - [13:0] */ + +/* + * R2577 (0xA11) - DSP1 Control 15 + */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_2_MASK 0x3FFF /* DSP1_START_ADDRESS_RDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_2_SHIFT 0 /* DSP1_START_ADDRESS_RDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_2_WIDTH 14 /* DSP1_START_ADDRESS_RDMA_BUFFER_2 - [13:0] */ + +/* + * R2578 (0xA12) - DSP1 Control 16 + */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_3_MASK 0x3FFF /* DSP1_START_ADDRESS_RDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_3_SHIFT 0 /* DSP1_START_ADDRESS_RDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_3_WIDTH 14 /* DSP1_START_ADDRESS_RDMA_BUFFER_3 - [13:0] */ + +/* + * R2579 (0xA13) - DSP1 Control 17 + */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_4_MASK 0x3FFF /* DSP1_START_ADDRESS_RDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_4_SHIFT 0 /* DSP1_START_ADDRESS_RDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_4_WIDTH 14 /* DSP1_START_ADDRESS_RDMA_BUFFER_4 - [13:0] */ + +/* + * R2580 (0xA14) - DSP1 Control 18 + */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_5_MASK 0x3FFF /* DSP1_START_ADDRESS_RDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_5_SHIFT 0 /* DSP1_START_ADDRESS_RDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP1_START_ADDRESS_RDMA_BUFFER_5_WIDTH 14 /* DSP1_START_ADDRESS_RDMA_BUFFER_5 - [13:0] */ + +/* + * R2582 (0xA16) - DSP1 Control 19 + */ +#define WM2200_DSP1_WDMA_BUFFER_LENGTH_MASK 0x00FF /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define WM2200_DSP1_WDMA_BUFFER_LENGTH_SHIFT 0 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define WM2200_DSP1_WDMA_BUFFER_LENGTH_WIDTH 8 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ + +/* + * R2583 (0xA17) - DSP1 Control 20 + */ +#define WM2200_DSP1_WDMA_CHANNEL_ENABLE_MASK 0x00FF /* DSP1_WDMA_CHANNEL_ENABLE - [7:0] */ +#define WM2200_DSP1_WDMA_CHANNEL_ENABLE_SHIFT 0 /* DSP1_WDMA_CHANNEL_ENABLE - [7:0] */ +#define WM2200_DSP1_WDMA_CHANNEL_ENABLE_WIDTH 8 /* DSP1_WDMA_CHANNEL_ENABLE - [7:0] */ + +/* + * R2584 (0xA18) - DSP1 Control 21 + */ +#define WM2200_DSP1_RDMA_CHANNEL_ENABLE_MASK 0x003F /* DSP1_RDMA_CHANNEL_ENABLE - [5:0] */ +#define WM2200_DSP1_RDMA_CHANNEL_ENABLE_SHIFT 0 /* DSP1_RDMA_CHANNEL_ENABLE - [5:0] */ +#define WM2200_DSP1_RDMA_CHANNEL_ENABLE_WIDTH 6 /* DSP1_RDMA_CHANNEL_ENABLE - [5:0] */ + +/* + * R2586 (0xA1A) - DSP1 Control 22 + */ +#define WM2200_DSP1_DM_SIZE_MASK 0xFFFF /* DSP1_DM_SIZE - [15:0] */ +#define WM2200_DSP1_DM_SIZE_SHIFT 0 /* DSP1_DM_SIZE - [15:0] */ +#define WM2200_DSP1_DM_SIZE_WIDTH 16 /* DSP1_DM_SIZE - [15:0] */ + +/* + * R2587 (0xA1B) - DSP1 Control 23 + */ +#define WM2200_DSP1_PM_SIZE_MASK 0xFFFF /* DSP1_PM_SIZE - [15:0] */ +#define WM2200_DSP1_PM_SIZE_SHIFT 0 /* DSP1_PM_SIZE - [15:0] */ +#define WM2200_DSP1_PM_SIZE_WIDTH 16 /* DSP1_PM_SIZE - [15:0] */ + +/* + * R2588 (0xA1C) - DSP1 Control 24 + */ +#define WM2200_DSP1_ZM_SIZE_MASK 0xFFFF /* DSP1_ZM_SIZE - [15:0] */ +#define WM2200_DSP1_ZM_SIZE_SHIFT 0 /* DSP1_ZM_SIZE - [15:0] */ +#define WM2200_DSP1_ZM_SIZE_WIDTH 16 /* DSP1_ZM_SIZE - [15:0] */ + +/* + * R2590 (0xA1E) - DSP1 Control 25 + */ +#define WM2200_DSP1_PING_FULL 0x8000 /* DSP1_PING_FULL */ +#define WM2200_DSP1_PING_FULL_MASK 0x8000 /* DSP1_PING_FULL */ +#define WM2200_DSP1_PING_FULL_SHIFT 15 /* DSP1_PING_FULL */ +#define WM2200_DSP1_PING_FULL_WIDTH 1 /* DSP1_PING_FULL */ +#define WM2200_DSP1_PONG_FULL 0x4000 /* DSP1_PONG_FULL */ +#define WM2200_DSP1_PONG_FULL_MASK 0x4000 /* DSP1_PONG_FULL */ +#define WM2200_DSP1_PONG_FULL_SHIFT 14 /* DSP1_PONG_FULL */ +#define WM2200_DSP1_PONG_FULL_WIDTH 1 /* DSP1_PONG_FULL */ +#define WM2200_DSP1_WDMA_ACTIVE_CHANNELS_MASK 0x00FF /* DSP1_WDMA_ACTIVE_CHANNELS - [7:0] */ +#define WM2200_DSP1_WDMA_ACTIVE_CHANNELS_SHIFT 0 /* DSP1_WDMA_ACTIVE_CHANNELS - [7:0] */ +#define WM2200_DSP1_WDMA_ACTIVE_CHANNELS_WIDTH 8 /* DSP1_WDMA_ACTIVE_CHANNELS - [7:0] */ + +/* + * R2592 (0xA20) - DSP1 Control 26 + */ +#define WM2200_DSP1_SCRATCH_0_MASK 0xFFFF /* DSP1_SCRATCH_0 - [15:0] */ +#define WM2200_DSP1_SCRATCH_0_SHIFT 0 /* DSP1_SCRATCH_0 - [15:0] */ +#define WM2200_DSP1_SCRATCH_0_WIDTH 16 /* DSP1_SCRATCH_0 - [15:0] */ + +/* + * R2593 (0xA21) - DSP1 Control 27 + */ +#define WM2200_DSP1_SCRATCH_1_MASK 0xFFFF /* DSP1_SCRATCH_1 - [15:0] */ +#define WM2200_DSP1_SCRATCH_1_SHIFT 0 /* DSP1_SCRATCH_1 - [15:0] */ +#define WM2200_DSP1_SCRATCH_1_WIDTH 16 /* DSP1_SCRATCH_1 - [15:0] */ + +/* + * R2594 (0xA22) - DSP1 Control 28 + */ +#define WM2200_DSP1_SCRATCH_2_MASK 0xFFFF /* DSP1_SCRATCH_2 - [15:0] */ +#define WM2200_DSP1_SCRATCH_2_SHIFT 0 /* DSP1_SCRATCH_2 - [15:0] */ +#define WM2200_DSP1_SCRATCH_2_WIDTH 16 /* DSP1_SCRATCH_2 - [15:0] */ + +/* + * R2595 (0xA23) - DSP1 Control 29 + */ +#define WM2200_DSP1_SCRATCH_3_MASK 0xFFFF /* DSP1_SCRATCH_3 - [15:0] */ +#define WM2200_DSP1_SCRATCH_3_SHIFT 0 /* DSP1_SCRATCH_3 - [15:0] */ +#define WM2200_DSP1_SCRATCH_3_WIDTH 16 /* DSP1_SCRATCH_3 - [15:0] */ + +/* + * R2596 (0xA24) - DSP1 Control 30 + */ +#define WM2200_DSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */ +#define WM2200_DSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */ +#define WM2200_DSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */ +#define WM2200_DSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */ +#define WM2200_DSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ +#define WM2200_DSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ +#define WM2200_DSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ +#define WM2200_DSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ +#define WM2200_DSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ +#define WM2200_DSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ +#define WM2200_DSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ +#define WM2200_DSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ +#define WM2200_DSP1_START 0x0001 /* DSP1_START */ +#define WM2200_DSP1_START_MASK 0x0001 /* DSP1_START */ +#define WM2200_DSP1_START_SHIFT 0 /* DSP1_START */ +#define WM2200_DSP1_START_WIDTH 1 /* DSP1_START */ + +/* + * R2598 (0xA26) - DSP1 Control 31 + */ +#define WM2200_DSP1_CLK_RATE_MASK 0x0018 /* DSP1_CLK_RATE - [4:3] */ +#define WM2200_DSP1_CLK_RATE_SHIFT 3 /* DSP1_CLK_RATE - [4:3] */ +#define WM2200_DSP1_CLK_RATE_WIDTH 2 /* DSP1_CLK_RATE - [4:3] */ +#define WM2200_DSP1_CLK_AVAIL 0x0004 /* DSP1_CLK_AVAIL */ +#define WM2200_DSP1_CLK_AVAIL_MASK 0x0004 /* DSP1_CLK_AVAIL */ +#define WM2200_DSP1_CLK_AVAIL_SHIFT 2 /* DSP1_CLK_AVAIL */ +#define WM2200_DSP1_CLK_AVAIL_WIDTH 1 /* DSP1_CLK_AVAIL */ +#define WM2200_DSP1_CLK_REQ_MASK 0x0003 /* DSP1_CLK_REQ - [1:0] */ +#define WM2200_DSP1_CLK_REQ_SHIFT 0 /* DSP1_CLK_REQ - [1:0] */ +#define WM2200_DSP1_CLK_REQ_WIDTH 2 /* DSP1_CLK_REQ - [1:0] */ + +/* + * R2816 (0xB00) - DSP2 Control 1 + */ +#define WM2200_DSP2_RW_SEQUENCE_ENA 0x0001 /* DSP2_RW_SEQUENCE_ENA */ +#define WM2200_DSP2_RW_SEQUENCE_ENA_MASK 0x0001 /* DSP2_RW_SEQUENCE_ENA */ +#define WM2200_DSP2_RW_SEQUENCE_ENA_SHIFT 0 /* DSP2_RW_SEQUENCE_ENA */ +#define WM2200_DSP2_RW_SEQUENCE_ENA_WIDTH 1 /* DSP2_RW_SEQUENCE_ENA */ + +/* + * R2818 (0xB02) - DSP2 Control 2 + */ +#define WM2200_DSP2_PAGE_BASE_PM_0_MASK 0xFF00 /* DSP2_PAGE_BASE_PM - [15:8] */ +#define WM2200_DSP2_PAGE_BASE_PM_0_SHIFT 8 /* DSP2_PAGE_BASE_PM - [15:8] */ +#define WM2200_DSP2_PAGE_BASE_PM_0_WIDTH 8 /* DSP2_PAGE_BASE_PM - [15:8] */ + +/* + * R2819 (0xB03) - DSP2 Control 3 + */ +#define WM2200_DSP2_PAGE_BASE_DM_0_MASK 0xFF00 /* DSP2_PAGE_BASE_DM - [15:8] */ +#define WM2200_DSP2_PAGE_BASE_DM_0_SHIFT 8 /* DSP2_PAGE_BASE_DM - [15:8] */ +#define WM2200_DSP2_PAGE_BASE_DM_0_WIDTH 8 /* DSP2_PAGE_BASE_DM - [15:8] */ + +/* + * R2820 (0xB04) - DSP2 Control 4 + */ +#define WM2200_DSP2_PAGE_BASE_ZM_0_MASK 0xFF00 /* DSP2_PAGE_BASE_ZM - [15:8] */ +#define WM2200_DSP2_PAGE_BASE_ZM_0_SHIFT 8 /* DSP2_PAGE_BASE_ZM - [15:8] */ +#define WM2200_DSP2_PAGE_BASE_ZM_0_WIDTH 8 /* DSP2_PAGE_BASE_ZM - [15:8] */ + +/* + * R2822 (0xB06) - DSP2 Control 5 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_0_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_0_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_0_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_0 - [13:0] */ + +/* + * R2823 (0xB07) - DSP2 Control 6 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_1_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_1_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_1_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_1 - [13:0] */ + +/* + * R2824 (0xB08) - DSP2 Control 7 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_2_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_2_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_2_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_2 - [13:0] */ + +/* + * R2825 (0xB09) - DSP2 Control 8 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_3_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_3_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_3_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_3 - [13:0] */ + +/* + * R2826 (0xB0A) - DSP2 Control 9 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_4_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_4_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_4_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_4 - [13:0] */ + +/* + * R2827 (0xB0B) - DSP2 Control 10 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_5_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_5_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_5_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_5 - [13:0] */ + +/* + * R2828 (0xB0C) - DSP2 Control 11 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_6_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_6 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_6_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_6 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_6_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_6 - [13:0] */ + +/* + * R2829 (0xB0D) - DSP2 Control 12 + */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_7_MASK 0x3FFF /* DSP2_START_ADDRESS_WDMA_BUFFER_7 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_7_SHIFT 0 /* DSP2_START_ADDRESS_WDMA_BUFFER_7 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_WDMA_BUFFER_7_WIDTH 14 /* DSP2_START_ADDRESS_WDMA_BUFFER_7 - [13:0] */ + +/* + * R2831 (0xB0F) - DSP2 Control 13 + */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_0_MASK 0x3FFF /* DSP2_START_ADDRESS_RDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_0_SHIFT 0 /* DSP2_START_ADDRESS_RDMA_BUFFER_0 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_0_WIDTH 14 /* DSP2_START_ADDRESS_RDMA_BUFFER_0 - [13:0] */ + +/* + * R2832 (0xB10) - DSP2 Control 14 + */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_1_MASK 0x3FFF /* DSP2_START_ADDRESS_RDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_1_SHIFT 0 /* DSP2_START_ADDRESS_RDMA_BUFFER_1 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_1_WIDTH 14 /* DSP2_START_ADDRESS_RDMA_BUFFER_1 - [13:0] */ + +/* + * R2833 (0xB11) - DSP2 Control 15 + */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_2_MASK 0x3FFF /* DSP2_START_ADDRESS_RDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_2_SHIFT 0 /* DSP2_START_ADDRESS_RDMA_BUFFER_2 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_2_WIDTH 14 /* DSP2_START_ADDRESS_RDMA_BUFFER_2 - [13:0] */ + +/* + * R2834 (0xB12) - DSP2 Control 16 + */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_3_MASK 0x3FFF /* DSP2_START_ADDRESS_RDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_3_SHIFT 0 /* DSP2_START_ADDRESS_RDMA_BUFFER_3 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_3_WIDTH 14 /* DSP2_START_ADDRESS_RDMA_BUFFER_3 - [13:0] */ + +/* + * R2835 (0xB13) - DSP2 Control 17 + */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_4_MASK 0x3FFF /* DSP2_START_ADDRESS_RDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_4_SHIFT 0 /* DSP2_START_ADDRESS_RDMA_BUFFER_4 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_4_WIDTH 14 /* DSP2_START_ADDRESS_RDMA_BUFFER_4 - [13:0] */ + +/* + * R2836 (0xB14) - DSP2 Control 18 + */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_5_MASK 0x3FFF /* DSP2_START_ADDRESS_RDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_5_SHIFT 0 /* DSP2_START_ADDRESS_RDMA_BUFFER_5 - [13:0] */ +#define WM2200_DSP2_START_ADDRESS_RDMA_BUFFER_5_WIDTH 14 /* DSP2_START_ADDRESS_RDMA_BUFFER_5 - [13:0] */ + +/* + * R2838 (0xB16) - DSP2 Control 19 + */ +#define WM2200_DSP2_WDMA_BUFFER_LENGTH_MASK 0x00FF /* DSP2_WDMA_BUFFER_LENGTH - [7:0] */ +#define WM2200_DSP2_WDMA_BUFFER_LENGTH_SHIFT 0 /* DSP2_WDMA_BUFFER_LENGTH - [7:0] */ +#define WM2200_DSP2_WDMA_BUFFER_LENGTH_WIDTH 8 /* DSP2_WDMA_BUFFER_LENGTH - [7:0] */ + +/* + * R2839 (0xB17) - DSP2 Control 20 + */ +#define WM2200_DSP2_WDMA_CHANNEL_ENABLE_MASK 0x00FF /* DSP2_WDMA_CHANNEL_ENABLE - [7:0] */ +#define WM2200_DSP2_WDMA_CHANNEL_ENABLE_SHIFT 0 /* DSP2_WDMA_CHANNEL_ENABLE - [7:0] */ +#define WM2200_DSP2_WDMA_CHANNEL_ENABLE_WIDTH 8 /* DSP2_WDMA_CHANNEL_ENABLE - [7:0] */ + +/* + * R2840 (0xB18) - DSP2 Control 21 + */ +#define WM2200_DSP2_RDMA_CHANNEL_ENABLE_MASK 0x003F /* DSP2_RDMA_CHANNEL_ENABLE - [5:0] */ +#define WM2200_DSP2_RDMA_CHANNEL_ENABLE_SHIFT 0 /* DSP2_RDMA_CHANNEL_ENABLE - [5:0] */ +#define WM2200_DSP2_RDMA_CHANNEL_ENABLE_WIDTH 6 /* DSP2_RDMA_CHANNEL_ENABLE - [5:0] */ + +/* + * R2842 (0xB1A) - DSP2 Control 22 + */ +#define WM2200_DSP2_DM_SIZE_MASK 0xFFFF /* DSP2_DM_SIZE - [15:0] */ +#define WM2200_DSP2_DM_SIZE_SHIFT 0 /* DSP2_DM_SIZE - [15:0] */ +#define WM2200_DSP2_DM_SIZE_WIDTH 16 /* DSP2_DM_SIZE - [15:0] */ + +/* + * R2843 (0xB1B) - DSP2 Control 23 + */ +#define WM2200_DSP2_PM_SIZE_MASK 0xFFFF /* DSP2_PM_SIZE - [15:0] */ +#define WM2200_DSP2_PM_SIZE_SHIFT 0 /* DSP2_PM_SIZE - [15:0] */ +#define WM2200_DSP2_PM_SIZE_WIDTH 16 /* DSP2_PM_SIZE - [15:0] */ + +/* + * R2844 (0xB1C) - DSP2 Control 24 + */ +#define WM2200_DSP2_ZM_SIZE_MASK 0xFFFF /* DSP2_ZM_SIZE - [15:0] */ +#define WM2200_DSP2_ZM_SIZE_SHIFT 0 /* DSP2_ZM_SIZE - [15:0] */ +#define WM2200_DSP2_ZM_SIZE_WIDTH 16 /* DSP2_ZM_SIZE - [15:0] */ + +/* + * R2846 (0xB1E) - DSP2 Control 25 + */ +#define WM2200_DSP2_PING_FULL 0x8000 /* DSP2_PING_FULL */ +#define WM2200_DSP2_PING_FULL_MASK 0x8000 /* DSP2_PING_FULL */ +#define WM2200_DSP2_PING_FULL_SHIFT 15 /* DSP2_PING_FULL */ +#define WM2200_DSP2_PING_FULL_WIDTH 1 /* DSP2_PING_FULL */ +#define WM2200_DSP2_PONG_FULL 0x4000 /* DSP2_PONG_FULL */ +#define WM2200_DSP2_PONG_FULL_MASK 0x4000 /* DSP2_PONG_FULL */ +#define WM2200_DSP2_PONG_FULL_SHIFT 14 /* DSP2_PONG_FULL */ +#define WM2200_DSP2_PONG_FULL_WIDTH 1 /* DSP2_PONG_FULL */ +#define WM2200_DSP2_WDMA_ACTIVE_CHANNELS_MASK 0x00FF /* DSP2_WDMA_ACTIVE_CHANNELS - [7:0] */ +#define WM2200_DSP2_WDMA_ACTIVE_CHANNELS_SHIFT 0 /* DSP2_WDMA_ACTIVE_CHANNELS - [7:0] */ +#define WM2200_DSP2_WDMA_ACTIVE_CHANNELS_WIDTH 8 /* DSP2_WDMA_ACTIVE_CHANNELS - [7:0] */ + +/* + * R2848 (0xB20) - DSP2 Control 26 + */ +#define WM2200_DSP2_SCRATCH_0_MASK 0xFFFF /* DSP2_SCRATCH_0 - [15:0] */ +#define WM2200_DSP2_SCRATCH_0_SHIFT 0 /* DSP2_SCRATCH_0 - [15:0] */ +#define WM2200_DSP2_SCRATCH_0_WIDTH 16 /* DSP2_SCRATCH_0 - [15:0] */ + +/* + * R2849 (0xB21) - DSP2 Control 27 + */ +#define WM2200_DSP2_SCRATCH_1_MASK 0xFFFF /* DSP2_SCRATCH_1 - [15:0] */ +#define WM2200_DSP2_SCRATCH_1_SHIFT 0 /* DSP2_SCRATCH_1 - [15:0] */ +#define WM2200_DSP2_SCRATCH_1_WIDTH 16 /* DSP2_SCRATCH_1 - [15:0] */ + +/* + * R2850 (0xB22) - DSP2 Control 28 + */ +#define WM2200_DSP2_SCRATCH_2_MASK 0xFFFF /* DSP2_SCRATCH_2 - [15:0] */ +#define WM2200_DSP2_SCRATCH_2_SHIFT 0 /* DSP2_SCRATCH_2 - [15:0] */ +#define WM2200_DSP2_SCRATCH_2_WIDTH 16 /* DSP2_SCRATCH_2 - [15:0] */ + +/* + * R2851 (0xB23) - DSP2 Control 29 + */ +#define WM2200_DSP2_SCRATCH_3_MASK 0xFFFF /* DSP2_SCRATCH_3 - [15:0] */ +#define WM2200_DSP2_SCRATCH_3_SHIFT 0 /* DSP2_SCRATCH_3 - [15:0] */ +#define WM2200_DSP2_SCRATCH_3_WIDTH 16 /* DSP2_SCRATCH_3 - [15:0] */ + +/* + * R2852 (0xB24) - DSP2 Control 30 + */ +#define WM2200_DSP2_DBG_CLK_ENA 0x0008 /* DSP2_DBG_CLK_ENA */ +#define WM2200_DSP2_DBG_CLK_ENA_MASK 0x0008 /* DSP2_DBG_CLK_ENA */ +#define WM2200_DSP2_DBG_CLK_ENA_SHIFT 3 /* DSP2_DBG_CLK_ENA */ +#define WM2200_DSP2_DBG_CLK_ENA_WIDTH 1 /* DSP2_DBG_CLK_ENA */ +#define WM2200_DSP2_SYS_ENA 0x0004 /* DSP2_SYS_ENA */ +#define WM2200_DSP2_SYS_ENA_MASK 0x0004 /* DSP2_SYS_ENA */ +#define WM2200_DSP2_SYS_ENA_SHIFT 2 /* DSP2_SYS_ENA */ +#define WM2200_DSP2_SYS_ENA_WIDTH 1 /* DSP2_SYS_ENA */ +#define WM2200_DSP2_CORE_ENA 0x0002 /* DSP2_CORE_ENA */ +#define WM2200_DSP2_CORE_ENA_MASK 0x0002 /* DSP2_CORE_ENA */ +#define WM2200_DSP2_CORE_ENA_SHIFT 1 /* DSP2_CORE_ENA */ +#define WM2200_DSP2_CORE_ENA_WIDTH 1 /* DSP2_CORE_ENA */ +#define WM2200_DSP2_START 0x0001 /* DSP2_START */ +#define WM2200_DSP2_START_MASK 0x0001 /* DSP2_START */ +#define WM2200_DSP2_START_SHIFT 0 /* DSP2_START */ +#define WM2200_DSP2_START_WIDTH 1 /* DSP2_START */ + +/* + * R2854 (0xB26) - DSP2 Control 31 + */ +#define WM2200_DSP2_CLK_RATE_MASK 0x0018 /* DSP2_CLK_RATE - [4:3] */ +#define WM2200_DSP2_CLK_RATE_SHIFT 3 /* DSP2_CLK_RATE - [4:3] */ +#define WM2200_DSP2_CLK_RATE_WIDTH 2 /* DSP2_CLK_RATE - [4:3] */ +#define WM2200_DSP2_CLK_AVAIL 0x0004 /* DSP2_CLK_AVAIL */ +#define WM2200_DSP2_CLK_AVAIL_MASK 0x0004 /* DSP2_CLK_AVAIL */ +#define WM2200_DSP2_CLK_AVAIL_SHIFT 2 /* DSP2_CLK_AVAIL */ +#define WM2200_DSP2_CLK_AVAIL_WIDTH 1 /* DSP2_CLK_AVAIL */ +#define WM2200_DSP2_CLK_REQ_MASK 0x0003 /* DSP2_CLK_REQ - [1:0] */ +#define WM2200_DSP2_CLK_REQ_SHIFT 0 /* DSP2_CLK_REQ - [1:0] */ +#define WM2200_DSP2_CLK_REQ_WIDTH 2 /* DSP2_CLK_REQ - [1:0] */ + +#endif diff --git a/sound/soc/codecs/wm5100-tables.c b/sound/soc/codecs/wm5100-tables.c new file mode 100644 index 000000000..9e987cf07 --- /dev/null +++ b/sound/soc/codecs/wm5100-tables.c @@ -0,0 +1,1485 @@ +/* + * wm5100-tables.c -- WM5100 ALSA SoC Audio driver data + * + * Copyright 2011-2 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "wm5100.h" + +bool wm5100_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM5100_SOFTWARE_RESET: + case WM5100_DEVICE_REVISION: + case WM5100_FX_CTRL: + case WM5100_INTERRUPT_STATUS_1: + case WM5100_INTERRUPT_STATUS_2: + case WM5100_INTERRUPT_STATUS_3: + case WM5100_INTERRUPT_STATUS_4: + case WM5100_INTERRUPT_RAW_STATUS_2: + case WM5100_INTERRUPT_RAW_STATUS_3: + case WM5100_INTERRUPT_RAW_STATUS_4: + case WM5100_OUTPUT_STATUS_1: + case WM5100_OUTPUT_STATUS_2: + case WM5100_INPUT_ENABLES_STATUS: + case WM5100_MIC_DETECT_3: + return true; + default: + if ((reg >= WM5100_DSP1_PM_0 && reg <= WM5100_DSP1_PM_1535) || + (reg >= WM5100_DSP1_ZM_0 && reg <= WM5100_DSP1_ZM_2047) || + (reg >= WM5100_DSP1_DM_0 && reg <= WM5100_DSP1_DM_511) || + (reg >= WM5100_DSP2_PM_0 && reg <= WM5100_DSP2_PM_1535) || + (reg >= WM5100_DSP2_ZM_0 && reg <= WM5100_DSP2_ZM_2047) || + (reg >= WM5100_DSP2_DM_0 && reg <= WM5100_DSP2_DM_511) || + (reg >= WM5100_DSP3_PM_0 && reg <= WM5100_DSP3_PM_1535) || + (reg >= WM5100_DSP3_ZM_0 && reg <= WM5100_DSP3_ZM_2047) || + (reg >= WM5100_DSP3_DM_0 && reg <= WM5100_DSP3_DM_511)) + return true; + else + return false; + } +} + +bool wm5100_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM5100_SOFTWARE_RESET: + case WM5100_DEVICE_REVISION: + case WM5100_CTRL_IF_1: + case WM5100_TONE_GENERATOR_1: + case WM5100_PWM_DRIVE_1: + case WM5100_PWM_DRIVE_2: + case WM5100_PWM_DRIVE_3: + case WM5100_CLOCKING_1: + case WM5100_CLOCKING_3: + case WM5100_CLOCKING_4: + case WM5100_CLOCKING_5: + case WM5100_CLOCKING_6: + case WM5100_CLOCKING_7: + case WM5100_CLOCKING_8: + case WM5100_ASRC_ENABLE: + case WM5100_ASRC_STATUS: + case WM5100_ASRC_RATE1: + case WM5100_ISRC_1_CTRL_1: + case WM5100_ISRC_1_CTRL_2: + case WM5100_ISRC_2_CTRL1: + case WM5100_ISRC_2_CTRL_2: + case WM5100_FLL1_CONTROL_1: + case WM5100_FLL1_CONTROL_2: + case WM5100_FLL1_CONTROL_3: + case WM5100_FLL1_CONTROL_5: + case WM5100_FLL1_CONTROL_6: + case WM5100_FLL1_EFS_1: + case WM5100_FLL2_CONTROL_1: + case WM5100_FLL2_CONTROL_2: + case WM5100_FLL2_CONTROL_3: + case WM5100_FLL2_CONTROL_5: + case WM5100_FLL2_CONTROL_6: + case WM5100_FLL2_EFS_1: + case WM5100_MIC_CHARGE_PUMP_1: + case WM5100_MIC_CHARGE_PUMP_2: + case WM5100_HP_CHARGE_PUMP_1: + case WM5100_LDO1_CONTROL: + case WM5100_MIC_BIAS_CTRL_1: + case WM5100_MIC_BIAS_CTRL_2: + case WM5100_MIC_BIAS_CTRL_3: + case WM5100_ACCESSORY_DETECT_MODE_1: + case WM5100_HEADPHONE_DETECT_1: + case WM5100_HEADPHONE_DETECT_2: + case WM5100_MIC_DETECT_1: + case WM5100_MIC_DETECT_2: + case WM5100_MIC_DETECT_3: + case WM5100_MISC_CONTROL: + case WM5100_INPUT_ENABLES: + case WM5100_INPUT_ENABLES_STATUS: + case WM5100_IN1L_CONTROL: + case WM5100_IN1R_CONTROL: + case WM5100_IN2L_CONTROL: + case WM5100_IN2R_CONTROL: + case WM5100_IN3L_CONTROL: + case WM5100_IN3R_CONTROL: + case WM5100_IN4L_CONTROL: + case WM5100_IN4R_CONTROL: + case WM5100_RXANC_SRC: + case WM5100_INPUT_VOLUME_RAMP: + case WM5100_ADC_DIGITAL_VOLUME_1L: + case WM5100_ADC_DIGITAL_VOLUME_1R: + case WM5100_ADC_DIGITAL_VOLUME_2L: + case WM5100_ADC_DIGITAL_VOLUME_2R: + case WM5100_ADC_DIGITAL_VOLUME_3L: + case WM5100_ADC_DIGITAL_VOLUME_3R: + case WM5100_ADC_DIGITAL_VOLUME_4L: + case WM5100_ADC_DIGITAL_VOLUME_4R: + case WM5100_OUTPUT_ENABLES_2: + case WM5100_OUTPUT_STATUS_1: + case WM5100_OUTPUT_STATUS_2: + case WM5100_CHANNEL_ENABLES_1: + case WM5100_OUT_VOLUME_1L: + case WM5100_OUT_VOLUME_1R: + case WM5100_DAC_VOLUME_LIMIT_1L: + case WM5100_DAC_VOLUME_LIMIT_1R: + case WM5100_OUT_VOLUME_2L: + case WM5100_OUT_VOLUME_2R: + case WM5100_DAC_VOLUME_LIMIT_2L: + case WM5100_DAC_VOLUME_LIMIT_2R: + case WM5100_OUT_VOLUME_3L: + case WM5100_OUT_VOLUME_3R: + case WM5100_DAC_VOLUME_LIMIT_3L: + case WM5100_DAC_VOLUME_LIMIT_3R: + case WM5100_OUT_VOLUME_4L: + case WM5100_OUT_VOLUME_4R: + case WM5100_DAC_VOLUME_LIMIT_5L: + case WM5100_DAC_VOLUME_LIMIT_5R: + case WM5100_DAC_VOLUME_LIMIT_6L: + case WM5100_DAC_VOLUME_LIMIT_6R: + case WM5100_DAC_AEC_CONTROL_1: + case WM5100_OUTPUT_VOLUME_RAMP: + case WM5100_DAC_DIGITAL_VOLUME_1L: + case WM5100_DAC_DIGITAL_VOLUME_1R: + case WM5100_DAC_DIGITAL_VOLUME_2L: + case WM5100_DAC_DIGITAL_VOLUME_2R: + case WM5100_DAC_DIGITAL_VOLUME_3L: + case WM5100_DAC_DIGITAL_VOLUME_3R: + case WM5100_DAC_DIGITAL_VOLUME_4L: + case WM5100_DAC_DIGITAL_VOLUME_4R: + case WM5100_DAC_DIGITAL_VOLUME_5L: + case WM5100_DAC_DIGITAL_VOLUME_5R: + case WM5100_DAC_DIGITAL_VOLUME_6L: + case WM5100_DAC_DIGITAL_VOLUME_6R: + case WM5100_PDM_SPK1_CTRL_1: + case WM5100_PDM_SPK1_CTRL_2: + case WM5100_PDM_SPK2_CTRL_1: + case WM5100_PDM_SPK2_CTRL_2: + case WM5100_AUDIO_IF_1_1: + case WM5100_AUDIO_IF_1_2: + case WM5100_AUDIO_IF_1_3: + case WM5100_AUDIO_IF_1_4: + case WM5100_AUDIO_IF_1_5: + case WM5100_AUDIO_IF_1_6: + case WM5100_AUDIO_IF_1_7: + case WM5100_AUDIO_IF_1_8: + case WM5100_AUDIO_IF_1_9: + case WM5100_AUDIO_IF_1_10: + case WM5100_AUDIO_IF_1_11: + case WM5100_AUDIO_IF_1_12: + case WM5100_AUDIO_IF_1_13: + case WM5100_AUDIO_IF_1_14: + case WM5100_AUDIO_IF_1_15: + case WM5100_AUDIO_IF_1_16: + case WM5100_AUDIO_IF_1_17: + case WM5100_AUDIO_IF_1_18: + case WM5100_AUDIO_IF_1_19: + case WM5100_AUDIO_IF_1_20: + case WM5100_AUDIO_IF_1_21: + case WM5100_AUDIO_IF_1_22: + case WM5100_AUDIO_IF_1_23: + case WM5100_AUDIO_IF_1_24: + case WM5100_AUDIO_IF_1_25: + case WM5100_AUDIO_IF_1_26: + case WM5100_AUDIO_IF_1_27: + case WM5100_AUDIO_IF_2_1: + case WM5100_AUDIO_IF_2_2: + case WM5100_AUDIO_IF_2_3: + case WM5100_AUDIO_IF_2_4: + case WM5100_AUDIO_IF_2_5: + case WM5100_AUDIO_IF_2_6: + case WM5100_AUDIO_IF_2_7: + case WM5100_AUDIO_IF_2_8: + case WM5100_AUDIO_IF_2_9: + case WM5100_AUDIO_IF_2_10: + case WM5100_AUDIO_IF_2_11: + case WM5100_AUDIO_IF_2_18: + case WM5100_AUDIO_IF_2_19: + case WM5100_AUDIO_IF_2_26: + case WM5100_AUDIO_IF_2_27: + case WM5100_AUDIO_IF_3_1: + case WM5100_AUDIO_IF_3_2: + case WM5100_AUDIO_IF_3_3: + case WM5100_AUDIO_IF_3_4: + case WM5100_AUDIO_IF_3_5: + case WM5100_AUDIO_IF_3_6: + case WM5100_AUDIO_IF_3_7: + case WM5100_AUDIO_IF_3_8: + case WM5100_AUDIO_IF_3_9: + case WM5100_AUDIO_IF_3_10: + case WM5100_AUDIO_IF_3_11: + case WM5100_AUDIO_IF_3_18: + case WM5100_AUDIO_IF_3_19: + case WM5100_AUDIO_IF_3_26: + case WM5100_AUDIO_IF_3_27: + case WM5100_PWM1MIX_INPUT_1_SOURCE: + case WM5100_PWM1MIX_INPUT_1_VOLUME: + case WM5100_PWM1MIX_INPUT_2_SOURCE: + case WM5100_PWM1MIX_INPUT_2_VOLUME: + case WM5100_PWM1MIX_INPUT_3_SOURCE: + case WM5100_PWM1MIX_INPUT_3_VOLUME: + case WM5100_PWM1MIX_INPUT_4_SOURCE: + case WM5100_PWM1MIX_INPUT_4_VOLUME: + case WM5100_PWM2MIX_INPUT_1_SOURCE: + case WM5100_PWM2MIX_INPUT_1_VOLUME: + case WM5100_PWM2MIX_INPUT_2_SOURCE: + case WM5100_PWM2MIX_INPUT_2_VOLUME: + case WM5100_PWM2MIX_INPUT_3_SOURCE: + case WM5100_PWM2MIX_INPUT_3_VOLUME: + case WM5100_PWM2MIX_INPUT_4_SOURCE: + case WM5100_PWM2MIX_INPUT_4_VOLUME: + case WM5100_OUT1LMIX_INPUT_1_SOURCE: + case WM5100_OUT1LMIX_INPUT_1_VOLUME: + case WM5100_OUT1LMIX_INPUT_2_SOURCE: + case WM5100_OUT1LMIX_INPUT_2_VOLUME: + case WM5100_OUT1LMIX_INPUT_3_SOURCE: + case WM5100_OUT1LMIX_INPUT_3_VOLUME: + case WM5100_OUT1LMIX_INPUT_4_SOURCE: + case WM5100_OUT1LMIX_INPUT_4_VOLUME: + case WM5100_OUT1RMIX_INPUT_1_SOURCE: + case WM5100_OUT1RMIX_INPUT_1_VOLUME: + case WM5100_OUT1RMIX_INPUT_2_SOURCE: + case WM5100_OUT1RMIX_INPUT_2_VOLUME: + case WM5100_OUT1RMIX_INPUT_3_SOURCE: + case WM5100_OUT1RMIX_INPUT_3_VOLUME: + case WM5100_OUT1RMIX_INPUT_4_SOURCE: + case WM5100_OUT1RMIX_INPUT_4_VOLUME: + case WM5100_OUT2LMIX_INPUT_1_SOURCE: + case WM5100_OUT2LMIX_INPUT_1_VOLUME: + case WM5100_OUT2LMIX_INPUT_2_SOURCE: + case WM5100_OUT2LMIX_INPUT_2_VOLUME: + case WM5100_OUT2LMIX_INPUT_3_SOURCE: + case WM5100_OUT2LMIX_INPUT_3_VOLUME: + case WM5100_OUT2LMIX_INPUT_4_SOURCE: + case WM5100_OUT2LMIX_INPUT_4_VOLUME: + case WM5100_OUT2RMIX_INPUT_1_SOURCE: + case WM5100_OUT2RMIX_INPUT_1_VOLUME: + case WM5100_OUT2RMIX_INPUT_2_SOURCE: + case WM5100_OUT2RMIX_INPUT_2_VOLUME: + case WM5100_OUT2RMIX_INPUT_3_SOURCE: + case WM5100_OUT2RMIX_INPUT_3_VOLUME: + case WM5100_OUT2RMIX_INPUT_4_SOURCE: + case WM5100_OUT2RMIX_INPUT_4_VOLUME: + case WM5100_OUT3LMIX_INPUT_1_SOURCE: + case WM5100_OUT3LMIX_INPUT_1_VOLUME: + case WM5100_OUT3LMIX_INPUT_2_SOURCE: + case WM5100_OUT3LMIX_INPUT_2_VOLUME: + case WM5100_OUT3LMIX_INPUT_3_SOURCE: + case WM5100_OUT3LMIX_INPUT_3_VOLUME: + case WM5100_OUT3LMIX_INPUT_4_SOURCE: + case WM5100_OUT3LMIX_INPUT_4_VOLUME: + case WM5100_OUT3RMIX_INPUT_1_SOURCE: + case WM5100_OUT3RMIX_INPUT_1_VOLUME: + case WM5100_OUT3RMIX_INPUT_2_SOURCE: + case WM5100_OUT3RMIX_INPUT_2_VOLUME: + case WM5100_OUT3RMIX_INPUT_3_SOURCE: + case WM5100_OUT3RMIX_INPUT_3_VOLUME: + case WM5100_OUT3RMIX_INPUT_4_SOURCE: + case WM5100_OUT3RMIX_INPUT_4_VOLUME: + case WM5100_OUT4LMIX_INPUT_1_SOURCE: + case WM5100_OUT4LMIX_INPUT_1_VOLUME: + case WM5100_OUT4LMIX_INPUT_2_SOURCE: + case WM5100_OUT4LMIX_INPUT_2_VOLUME: + case WM5100_OUT4LMIX_INPUT_3_SOURCE: + case WM5100_OUT4LMIX_INPUT_3_VOLUME: + case WM5100_OUT4LMIX_INPUT_4_SOURCE: + case WM5100_OUT4LMIX_INPUT_4_VOLUME: + case WM5100_OUT4RMIX_INPUT_1_SOURCE: + case WM5100_OUT4RMIX_INPUT_1_VOLUME: + case WM5100_OUT4RMIX_INPUT_2_SOURCE: + case WM5100_OUT4RMIX_INPUT_2_VOLUME: + case WM5100_OUT4RMIX_INPUT_3_SOURCE: + case WM5100_OUT4RMIX_INPUT_3_VOLUME: + case WM5100_OUT4RMIX_INPUT_4_SOURCE: + case WM5100_OUT4RMIX_INPUT_4_VOLUME: + case WM5100_OUT5LMIX_INPUT_1_SOURCE: + case WM5100_OUT5LMIX_INPUT_1_VOLUME: + case WM5100_OUT5LMIX_INPUT_2_SOURCE: + case WM5100_OUT5LMIX_INPUT_2_VOLUME: + case WM5100_OUT5LMIX_INPUT_3_SOURCE: + case WM5100_OUT5LMIX_INPUT_3_VOLUME: + case WM5100_OUT5LMIX_INPUT_4_SOURCE: + case WM5100_OUT5LMIX_INPUT_4_VOLUME: + case WM5100_OUT5RMIX_INPUT_1_SOURCE: + case WM5100_OUT5RMIX_INPUT_1_VOLUME: + case WM5100_OUT5RMIX_INPUT_2_SOURCE: + case WM5100_OUT5RMIX_INPUT_2_VOLUME: + case WM5100_OUT5RMIX_INPUT_3_SOURCE: + case WM5100_OUT5RMIX_INPUT_3_VOLUME: + case WM5100_OUT5RMIX_INPUT_4_SOURCE: + case WM5100_OUT5RMIX_INPUT_4_VOLUME: + case WM5100_OUT6LMIX_INPUT_1_SOURCE: + case WM5100_OUT6LMIX_INPUT_1_VOLUME: + case WM5100_OUT6LMIX_INPUT_2_SOURCE: + case WM5100_OUT6LMIX_INPUT_2_VOLUME: + case WM5100_OUT6LMIX_INPUT_3_SOURCE: + case WM5100_OUT6LMIX_INPUT_3_VOLUME: + case WM5100_OUT6LMIX_INPUT_4_SOURCE: + case WM5100_OUT6LMIX_INPUT_4_VOLUME: + case WM5100_OUT6RMIX_INPUT_1_SOURCE: + case WM5100_OUT6RMIX_INPUT_1_VOLUME: + case WM5100_OUT6RMIX_INPUT_2_SOURCE: + case WM5100_OUT6RMIX_INPUT_2_VOLUME: + case WM5100_OUT6RMIX_INPUT_3_SOURCE: + case WM5100_OUT6RMIX_INPUT_3_VOLUME: + case WM5100_OUT6RMIX_INPUT_4_SOURCE: + case WM5100_OUT6RMIX_INPUT_4_VOLUME: + case WM5100_AIF1TX1MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX1MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX1MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX1MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX1MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX1MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX1MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX1MIX_INPUT_4_VOLUME: + case WM5100_AIF1TX2MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX2MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX2MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX2MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX2MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX2MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX2MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX2MIX_INPUT_4_VOLUME: + case WM5100_AIF1TX3MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX3MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX3MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX3MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX3MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX3MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX3MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX3MIX_INPUT_4_VOLUME: + case WM5100_AIF1TX4MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX4MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX4MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX4MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX4MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX4MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX4MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX4MIX_INPUT_4_VOLUME: + case WM5100_AIF1TX5MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX5MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX5MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX5MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX5MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX5MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX5MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX5MIX_INPUT_4_VOLUME: + case WM5100_AIF1TX6MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX6MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX6MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX6MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX6MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX6MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX6MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX6MIX_INPUT_4_VOLUME: + case WM5100_AIF1TX7MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX7MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX7MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX7MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX7MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX7MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX7MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX7MIX_INPUT_4_VOLUME: + case WM5100_AIF1TX8MIX_INPUT_1_SOURCE: + case WM5100_AIF1TX8MIX_INPUT_1_VOLUME: + case WM5100_AIF1TX8MIX_INPUT_2_SOURCE: + case WM5100_AIF1TX8MIX_INPUT_2_VOLUME: + case WM5100_AIF1TX8MIX_INPUT_3_SOURCE: + case WM5100_AIF1TX8MIX_INPUT_3_VOLUME: + case WM5100_AIF1TX8MIX_INPUT_4_SOURCE: + case WM5100_AIF1TX8MIX_INPUT_4_VOLUME: + case WM5100_AIF2TX1MIX_INPUT_1_SOURCE: + case WM5100_AIF2TX1MIX_INPUT_1_VOLUME: + case WM5100_AIF2TX1MIX_INPUT_2_SOURCE: + case WM5100_AIF2TX1MIX_INPUT_2_VOLUME: + case WM5100_AIF2TX1MIX_INPUT_3_SOURCE: + case WM5100_AIF2TX1MIX_INPUT_3_VOLUME: + case WM5100_AIF2TX1MIX_INPUT_4_SOURCE: + case WM5100_AIF2TX1MIX_INPUT_4_VOLUME: + case WM5100_AIF2TX2MIX_INPUT_1_SOURCE: + case WM5100_AIF2TX2MIX_INPUT_1_VOLUME: + case WM5100_AIF2TX2MIX_INPUT_2_SOURCE: + case WM5100_AIF2TX2MIX_INPUT_2_VOLUME: + case WM5100_AIF2TX2MIX_INPUT_3_SOURCE: + case WM5100_AIF2TX2MIX_INPUT_3_VOLUME: + case WM5100_AIF2TX2MIX_INPUT_4_SOURCE: + case WM5100_AIF2TX2MIX_INPUT_4_VOLUME: + case WM5100_AIF3TX1MIX_INPUT_1_SOURCE: + case WM5100_AIF3TX1MIX_INPUT_1_VOLUME: + case WM5100_AIF3TX1MIX_INPUT_2_SOURCE: + case WM5100_AIF3TX1MIX_INPUT_2_VOLUME: + case WM5100_AIF3TX1MIX_INPUT_3_SOURCE: + case WM5100_AIF3TX1MIX_INPUT_3_VOLUME: + case WM5100_AIF3TX1MIX_INPUT_4_SOURCE: + case WM5100_AIF3TX1MIX_INPUT_4_VOLUME: + case WM5100_AIF3TX2MIX_INPUT_1_SOURCE: + case WM5100_AIF3TX2MIX_INPUT_1_VOLUME: + case WM5100_AIF3TX2MIX_INPUT_2_SOURCE: + case WM5100_AIF3TX2MIX_INPUT_2_VOLUME: + case WM5100_AIF3TX2MIX_INPUT_3_SOURCE: + case WM5100_AIF3TX2MIX_INPUT_3_VOLUME: + case WM5100_AIF3TX2MIX_INPUT_4_SOURCE: + case WM5100_AIF3TX2MIX_INPUT_4_VOLUME: + case WM5100_EQ1MIX_INPUT_1_SOURCE: + case WM5100_EQ1MIX_INPUT_1_VOLUME: + case WM5100_EQ1MIX_INPUT_2_SOURCE: + case WM5100_EQ1MIX_INPUT_2_VOLUME: + case WM5100_EQ1MIX_INPUT_3_SOURCE: + case WM5100_EQ1MIX_INPUT_3_VOLUME: + case WM5100_EQ1MIX_INPUT_4_SOURCE: + case WM5100_EQ1MIX_INPUT_4_VOLUME: + case WM5100_EQ2MIX_INPUT_1_SOURCE: + case WM5100_EQ2MIX_INPUT_1_VOLUME: + case WM5100_EQ2MIX_INPUT_2_SOURCE: + case WM5100_EQ2MIX_INPUT_2_VOLUME: + case WM5100_EQ2MIX_INPUT_3_SOURCE: + case WM5100_EQ2MIX_INPUT_3_VOLUME: + case WM5100_EQ2MIX_INPUT_4_SOURCE: + case WM5100_EQ2MIX_INPUT_4_VOLUME: + case WM5100_EQ3MIX_INPUT_1_SOURCE: + case WM5100_EQ3MIX_INPUT_1_VOLUME: + case WM5100_EQ3MIX_INPUT_2_SOURCE: + case WM5100_EQ3MIX_INPUT_2_VOLUME: + case WM5100_EQ3MIX_INPUT_3_SOURCE: + case WM5100_EQ3MIX_INPUT_3_VOLUME: + case WM5100_EQ3MIX_INPUT_4_SOURCE: + case WM5100_EQ3MIX_INPUT_4_VOLUME: + case WM5100_EQ4MIX_INPUT_1_SOURCE: + case WM5100_EQ4MIX_INPUT_1_VOLUME: + case WM5100_EQ4MIX_INPUT_2_SOURCE: + case WM5100_EQ4MIX_INPUT_2_VOLUME: + case WM5100_EQ4MIX_INPUT_3_SOURCE: + case WM5100_EQ4MIX_INPUT_3_VOLUME: + case WM5100_EQ4MIX_INPUT_4_SOURCE: + case WM5100_EQ4MIX_INPUT_4_VOLUME: + case WM5100_DRC1LMIX_INPUT_1_SOURCE: + case WM5100_DRC1LMIX_INPUT_1_VOLUME: + case WM5100_DRC1LMIX_INPUT_2_SOURCE: + case WM5100_DRC1LMIX_INPUT_2_VOLUME: + case WM5100_DRC1LMIX_INPUT_3_SOURCE: + case WM5100_DRC1LMIX_INPUT_3_VOLUME: + case WM5100_DRC1LMIX_INPUT_4_SOURCE: + case WM5100_DRC1LMIX_INPUT_4_VOLUME: + case WM5100_DRC1RMIX_INPUT_1_SOURCE: + case WM5100_DRC1RMIX_INPUT_1_VOLUME: + case WM5100_DRC1RMIX_INPUT_2_SOURCE: + case WM5100_DRC1RMIX_INPUT_2_VOLUME: + case WM5100_DRC1RMIX_INPUT_3_SOURCE: + case WM5100_DRC1RMIX_INPUT_3_VOLUME: + case WM5100_DRC1RMIX_INPUT_4_SOURCE: + case WM5100_DRC1RMIX_INPUT_4_VOLUME: + case WM5100_HPLP1MIX_INPUT_1_SOURCE: + case WM5100_HPLP1MIX_INPUT_1_VOLUME: + case WM5100_HPLP1MIX_INPUT_2_SOURCE: + case WM5100_HPLP1MIX_INPUT_2_VOLUME: + case WM5100_HPLP1MIX_INPUT_3_SOURCE: + case WM5100_HPLP1MIX_INPUT_3_VOLUME: + case WM5100_HPLP1MIX_INPUT_4_SOURCE: + case WM5100_HPLP1MIX_INPUT_4_VOLUME: + case WM5100_HPLP2MIX_INPUT_1_SOURCE: + case WM5100_HPLP2MIX_INPUT_1_VOLUME: + case WM5100_HPLP2MIX_INPUT_2_SOURCE: + case WM5100_HPLP2MIX_INPUT_2_VOLUME: + case WM5100_HPLP2MIX_INPUT_3_SOURCE: + case WM5100_HPLP2MIX_INPUT_3_VOLUME: + case WM5100_HPLP2MIX_INPUT_4_SOURCE: + case WM5100_HPLP2MIX_INPUT_4_VOLUME: + case WM5100_HPLP3MIX_INPUT_1_SOURCE: + case WM5100_HPLP3MIX_INPUT_1_VOLUME: + case WM5100_HPLP3MIX_INPUT_2_SOURCE: + case WM5100_HPLP3MIX_INPUT_2_VOLUME: + case WM5100_HPLP3MIX_INPUT_3_SOURCE: + case WM5100_HPLP3MIX_INPUT_3_VOLUME: + case WM5100_HPLP3MIX_INPUT_4_SOURCE: + case WM5100_HPLP3MIX_INPUT_4_VOLUME: + case WM5100_HPLP4MIX_INPUT_1_SOURCE: + case WM5100_HPLP4MIX_INPUT_1_VOLUME: + case WM5100_HPLP4MIX_INPUT_2_SOURCE: + case WM5100_HPLP4MIX_INPUT_2_VOLUME: + case WM5100_HPLP4MIX_INPUT_3_SOURCE: + case WM5100_HPLP4MIX_INPUT_3_VOLUME: + case WM5100_HPLP4MIX_INPUT_4_SOURCE: + case WM5100_HPLP4MIX_INPUT_4_VOLUME: + case WM5100_DSP1LMIX_INPUT_1_SOURCE: + case WM5100_DSP1LMIX_INPUT_1_VOLUME: + case WM5100_DSP1LMIX_INPUT_2_SOURCE: + case WM5100_DSP1LMIX_INPUT_2_VOLUME: + case WM5100_DSP1LMIX_INPUT_3_SOURCE: + case WM5100_DSP1LMIX_INPUT_3_VOLUME: + case WM5100_DSP1LMIX_INPUT_4_SOURCE: + case WM5100_DSP1LMIX_INPUT_4_VOLUME: + case WM5100_DSP1RMIX_INPUT_1_SOURCE: + case WM5100_DSP1RMIX_INPUT_1_VOLUME: + case WM5100_DSP1RMIX_INPUT_2_SOURCE: + case WM5100_DSP1RMIX_INPUT_2_VOLUME: + case WM5100_DSP1RMIX_INPUT_3_SOURCE: + case WM5100_DSP1RMIX_INPUT_3_VOLUME: + case WM5100_DSP1RMIX_INPUT_4_SOURCE: + case WM5100_DSP1RMIX_INPUT_4_VOLUME: + case WM5100_DSP1AUX1MIX_INPUT_1_SOURCE: + case WM5100_DSP1AUX2MIX_INPUT_1_SOURCE: + case WM5100_DSP1AUX3MIX_INPUT_1_SOURCE: + case WM5100_DSP1AUX4MIX_INPUT_1_SOURCE: + case WM5100_DSP1AUX5MIX_INPUT_1_SOURCE: + case WM5100_DSP1AUX6MIX_INPUT_1_SOURCE: + case WM5100_DSP2LMIX_INPUT_1_SOURCE: + case WM5100_DSP2LMIX_INPUT_1_VOLUME: + case WM5100_DSP2LMIX_INPUT_2_SOURCE: + case WM5100_DSP2LMIX_INPUT_2_VOLUME: + case WM5100_DSP2LMIX_INPUT_3_SOURCE: + case WM5100_DSP2LMIX_INPUT_3_VOLUME: + case WM5100_DSP2LMIX_INPUT_4_SOURCE: + case WM5100_DSP2LMIX_INPUT_4_VOLUME: + case WM5100_DSP2RMIX_INPUT_1_SOURCE: + case WM5100_DSP2RMIX_INPUT_1_VOLUME: + case WM5100_DSP2RMIX_INPUT_2_SOURCE: + case WM5100_DSP2RMIX_INPUT_2_VOLUME: + case WM5100_DSP2RMIX_INPUT_3_SOURCE: + case WM5100_DSP2RMIX_INPUT_3_VOLUME: + case WM5100_DSP2RMIX_INPUT_4_SOURCE: + case WM5100_DSP2RMIX_INPUT_4_VOLUME: + case WM5100_DSP2AUX1MIX_INPUT_1_SOURCE: + case WM5100_DSP2AUX2MIX_INPUT_1_SOURCE: + case WM5100_DSP2AUX3MIX_INPUT_1_SOURCE: + case WM5100_DSP2AUX4MIX_INPUT_1_SOURCE: + case WM5100_DSP2AUX5MIX_INPUT_1_SOURCE: + case WM5100_DSP2AUX6MIX_INPUT_1_SOURCE: + case WM5100_DSP3LMIX_INPUT_1_SOURCE: + case WM5100_DSP3LMIX_INPUT_1_VOLUME: + case WM5100_DSP3LMIX_INPUT_2_SOURCE: + case WM5100_DSP3LMIX_INPUT_2_VOLUME: + case WM5100_DSP3LMIX_INPUT_3_SOURCE: + case WM5100_DSP3LMIX_INPUT_3_VOLUME: + case WM5100_DSP3LMIX_INPUT_4_SOURCE: + case WM5100_DSP3LMIX_INPUT_4_VOLUME: + case WM5100_DSP3RMIX_INPUT_1_SOURCE: + case WM5100_DSP3RMIX_INPUT_1_VOLUME: + case WM5100_DSP3RMIX_INPUT_2_SOURCE: + case WM5100_DSP3RMIX_INPUT_2_VOLUME: + case WM5100_DSP3RMIX_INPUT_3_SOURCE: + case WM5100_DSP3RMIX_INPUT_3_VOLUME: + case WM5100_DSP3RMIX_INPUT_4_SOURCE: + case WM5100_DSP3RMIX_INPUT_4_VOLUME: + case WM5100_DSP3AUX1MIX_INPUT_1_SOURCE: + case WM5100_DSP3AUX2MIX_INPUT_1_SOURCE: + case WM5100_DSP3AUX3MIX_INPUT_1_SOURCE: + case WM5100_DSP3AUX4MIX_INPUT_1_SOURCE: + case WM5100_DSP3AUX5MIX_INPUT_1_SOURCE: + case WM5100_DSP3AUX6MIX_INPUT_1_SOURCE: + case WM5100_ASRC1LMIX_INPUT_1_SOURCE: + case WM5100_ASRC1RMIX_INPUT_1_SOURCE: + case WM5100_ASRC2LMIX_INPUT_1_SOURCE: + case WM5100_ASRC2RMIX_INPUT_1_SOURCE: + case WM5100_ISRC1DEC1MIX_INPUT_1_SOURCE: + case WM5100_ISRC1DEC2MIX_INPUT_1_SOURCE: + case WM5100_ISRC1DEC3MIX_INPUT_1_SOURCE: + case WM5100_ISRC1DEC4MIX_INPUT_1_SOURCE: + case WM5100_ISRC1INT1MIX_INPUT_1_SOURCE: + case WM5100_ISRC1INT2MIX_INPUT_1_SOURCE: + case WM5100_ISRC1INT3MIX_INPUT_1_SOURCE: + case WM5100_ISRC1INT4MIX_INPUT_1_SOURCE: + case WM5100_ISRC2DEC1MIX_INPUT_1_SOURCE: + case WM5100_ISRC2DEC2MIX_INPUT_1_SOURCE: + case WM5100_ISRC2DEC3MIX_INPUT_1_SOURCE: + case WM5100_ISRC2DEC4MIX_INPUT_1_SOURCE: + case WM5100_ISRC2INT1MIX_INPUT_1_SOURCE: + case WM5100_ISRC2INT2MIX_INPUT_1_SOURCE: + case WM5100_ISRC2INT3MIX_INPUT_1_SOURCE: + case WM5100_ISRC2INT4MIX_INPUT_1_SOURCE: + case WM5100_GPIO_CTRL_1: + case WM5100_GPIO_CTRL_2: + case WM5100_GPIO_CTRL_3: + case WM5100_GPIO_CTRL_4: + case WM5100_GPIO_CTRL_5: + case WM5100_GPIO_CTRL_6: + case WM5100_MISC_PAD_CTRL_1: + case WM5100_MISC_PAD_CTRL_2: + case WM5100_MISC_PAD_CTRL_3: + case WM5100_MISC_PAD_CTRL_4: + case WM5100_MISC_PAD_CTRL_5: + case WM5100_MISC_GPIO_1: + case WM5100_INTERRUPT_STATUS_1: + case WM5100_INTERRUPT_STATUS_2: + case WM5100_INTERRUPT_STATUS_3: + case WM5100_INTERRUPT_STATUS_4: + case WM5100_INTERRUPT_RAW_STATUS_2: + case WM5100_INTERRUPT_RAW_STATUS_3: + case WM5100_INTERRUPT_RAW_STATUS_4: + case WM5100_INTERRUPT_STATUS_1_MASK: + case WM5100_INTERRUPT_STATUS_2_MASK: + case WM5100_INTERRUPT_STATUS_3_MASK: + case WM5100_INTERRUPT_STATUS_4_MASK: + case WM5100_INTERRUPT_CONTROL: + case WM5100_IRQ_DEBOUNCE_1: + case WM5100_IRQ_DEBOUNCE_2: + case WM5100_FX_CTRL: + case WM5100_EQ1_1: + case WM5100_EQ1_2: + case WM5100_EQ1_3: + case WM5100_EQ1_4: + case WM5100_EQ1_5: + case WM5100_EQ1_6: + case WM5100_EQ1_7: + case WM5100_EQ1_8: + case WM5100_EQ1_9: + case WM5100_EQ1_10: + case WM5100_EQ1_11: + case WM5100_EQ1_12: + case WM5100_EQ1_13: + case WM5100_EQ1_14: + case WM5100_EQ1_15: + case WM5100_EQ1_16: + case WM5100_EQ1_17: + case WM5100_EQ1_18: + case WM5100_EQ1_19: + case WM5100_EQ1_20: + case WM5100_EQ2_1: + case WM5100_EQ2_2: + case WM5100_EQ2_3: + case WM5100_EQ2_4: + case WM5100_EQ2_5: + case WM5100_EQ2_6: + case WM5100_EQ2_7: + case WM5100_EQ2_8: + case WM5100_EQ2_9: + case WM5100_EQ2_10: + case WM5100_EQ2_11: + case WM5100_EQ2_12: + case WM5100_EQ2_13: + case WM5100_EQ2_14: + case WM5100_EQ2_15: + case WM5100_EQ2_16: + case WM5100_EQ2_17: + case WM5100_EQ2_18: + case WM5100_EQ2_19: + case WM5100_EQ2_20: + case WM5100_EQ3_1: + case WM5100_EQ3_2: + case WM5100_EQ3_3: + case WM5100_EQ3_4: + case WM5100_EQ3_5: + case WM5100_EQ3_6: + case WM5100_EQ3_7: + case WM5100_EQ3_8: + case WM5100_EQ3_9: + case WM5100_EQ3_10: + case WM5100_EQ3_11: + case WM5100_EQ3_12: + case WM5100_EQ3_13: + case WM5100_EQ3_14: + case WM5100_EQ3_15: + case WM5100_EQ3_16: + case WM5100_EQ3_17: + case WM5100_EQ3_18: + case WM5100_EQ3_19: + case WM5100_EQ3_20: + case WM5100_EQ4_1: + case WM5100_EQ4_2: + case WM5100_EQ4_3: + case WM5100_EQ4_4: + case WM5100_EQ4_5: + case WM5100_EQ4_6: + case WM5100_EQ4_7: + case WM5100_EQ4_8: + case WM5100_EQ4_9: + case WM5100_EQ4_10: + case WM5100_EQ4_11: + case WM5100_EQ4_12: + case WM5100_EQ4_13: + case WM5100_EQ4_14: + case WM5100_EQ4_15: + case WM5100_EQ4_16: + case WM5100_EQ4_17: + case WM5100_EQ4_18: + case WM5100_EQ4_19: + case WM5100_EQ4_20: + case WM5100_DRC1_CTRL1: + case WM5100_DRC1_CTRL2: + case WM5100_DRC1_CTRL3: + case WM5100_DRC1_CTRL4: + case WM5100_DRC1_CTRL5: + case WM5100_HPLPF1_1: + case WM5100_HPLPF1_2: + case WM5100_HPLPF2_1: + case WM5100_HPLPF2_2: + case WM5100_HPLPF3_1: + case WM5100_HPLPF3_2: + case WM5100_HPLPF4_1: + case WM5100_HPLPF4_2: + case WM5100_DSP1_CONTROL_1: + case WM5100_DSP1_CONTROL_2: + case WM5100_DSP1_CONTROL_3: + case WM5100_DSP1_CONTROL_4: + case WM5100_DSP1_CONTROL_5: + case WM5100_DSP1_CONTROL_6: + case WM5100_DSP1_CONTROL_7: + case WM5100_DSP1_CONTROL_8: + case WM5100_DSP1_CONTROL_9: + case WM5100_DSP1_CONTROL_10: + case WM5100_DSP1_CONTROL_11: + case WM5100_DSP1_CONTROL_12: + case WM5100_DSP1_CONTROL_13: + case WM5100_DSP1_CONTROL_14: + case WM5100_DSP1_CONTROL_15: + case WM5100_DSP1_CONTROL_16: + case WM5100_DSP1_CONTROL_17: + case WM5100_DSP1_CONTROL_18: + case WM5100_DSP1_CONTROL_19: + case WM5100_DSP1_CONTROL_20: + case WM5100_DSP1_CONTROL_21: + case WM5100_DSP1_CONTROL_22: + case WM5100_DSP1_CONTROL_23: + case WM5100_DSP1_CONTROL_24: + case WM5100_DSP1_CONTROL_25: + case WM5100_DSP1_CONTROL_26: + case WM5100_DSP1_CONTROL_27: + case WM5100_DSP1_CONTROL_28: + case WM5100_DSP1_CONTROL_29: + case WM5100_DSP1_CONTROL_30: + case WM5100_DSP2_CONTROL_1: + case WM5100_DSP2_CONTROL_2: + case WM5100_DSP2_CONTROL_3: + case WM5100_DSP2_CONTROL_4: + case WM5100_DSP2_CONTROL_5: + case WM5100_DSP2_CONTROL_6: + case WM5100_DSP2_CONTROL_7: + case WM5100_DSP2_CONTROL_8: + case WM5100_DSP2_CONTROL_9: + case WM5100_DSP2_CONTROL_10: + case WM5100_DSP2_CONTROL_11: + case WM5100_DSP2_CONTROL_12: + case WM5100_DSP2_CONTROL_13: + case WM5100_DSP2_CONTROL_14: + case WM5100_DSP2_CONTROL_15: + case WM5100_DSP2_CONTROL_16: + case WM5100_DSP2_CONTROL_17: + case WM5100_DSP2_CONTROL_18: + case WM5100_DSP2_CONTROL_19: + case WM5100_DSP2_CONTROL_20: + case WM5100_DSP2_CONTROL_21: + case WM5100_DSP2_CONTROL_22: + case WM5100_DSP2_CONTROL_23: + case WM5100_DSP2_CONTROL_24: + case WM5100_DSP2_CONTROL_25: + case WM5100_DSP2_CONTROL_26: + case WM5100_DSP2_CONTROL_27: + case WM5100_DSP2_CONTROL_28: + case WM5100_DSP2_CONTROL_29: + case WM5100_DSP2_CONTROL_30: + case WM5100_DSP3_CONTROL_1: + case WM5100_DSP3_CONTROL_2: + case WM5100_DSP3_CONTROL_3: + case WM5100_DSP3_CONTROL_4: + case WM5100_DSP3_CONTROL_5: + case WM5100_DSP3_CONTROL_6: + case WM5100_DSP3_CONTROL_7: + case WM5100_DSP3_CONTROL_8: + case WM5100_DSP3_CONTROL_9: + case WM5100_DSP3_CONTROL_10: + case WM5100_DSP3_CONTROL_11: + case WM5100_DSP3_CONTROL_12: + case WM5100_DSP3_CONTROL_13: + case WM5100_DSP3_CONTROL_14: + case WM5100_DSP3_CONTROL_15: + case WM5100_DSP3_CONTROL_16: + case WM5100_DSP3_CONTROL_17: + case WM5100_DSP3_CONTROL_18: + case WM5100_DSP3_CONTROL_19: + case WM5100_DSP3_CONTROL_20: + case WM5100_DSP3_CONTROL_21: + case WM5100_DSP3_CONTROL_22: + case WM5100_DSP3_CONTROL_23: + case WM5100_DSP3_CONTROL_24: + case WM5100_DSP3_CONTROL_25: + case WM5100_DSP3_CONTROL_26: + case WM5100_DSP3_CONTROL_27: + case WM5100_DSP3_CONTROL_28: + case WM5100_DSP3_CONTROL_29: + case WM5100_DSP3_CONTROL_30: + return true; + default: + if ((reg >= WM5100_DSP1_PM_0 && reg <= WM5100_DSP1_PM_1535) || + (reg >= WM5100_DSP1_ZM_0 && reg <= WM5100_DSP1_ZM_2047) || + (reg >= WM5100_DSP1_DM_0 && reg <= WM5100_DSP1_DM_511) || + (reg >= WM5100_DSP2_PM_0 && reg <= WM5100_DSP2_PM_1535) || + (reg >= WM5100_DSP2_ZM_0 && reg <= WM5100_DSP2_ZM_2047) || + (reg >= WM5100_DSP2_DM_0 && reg <= WM5100_DSP2_DM_511) || + (reg >= WM5100_DSP3_PM_0 && reg <= WM5100_DSP3_PM_1535) || + (reg >= WM5100_DSP3_ZM_0 && reg <= WM5100_DSP3_ZM_2047) || + (reg >= WM5100_DSP3_DM_0 && reg <= WM5100_DSP3_DM_511)) + return true; + else + return false; + } +} + +struct reg_default wm5100_reg_defaults[WM5100_REGISTER_COUNT] = { + { 0x0000, 0x0000 }, /* R0 - software reset */ + { 0x0001, 0x0000 }, /* R1 - Device Revision */ + { 0x0010, 0x0801 }, /* R16 - Ctrl IF 1 */ + { 0x0020, 0x0000 }, /* R32 - Tone Generator 1 */ + { 0x0030, 0x0000 }, /* R48 - PWM Drive 1 */ + { 0x0031, 0x0100 }, /* R49 - PWM Drive 2 */ + { 0x0032, 0x0100 }, /* R50 - PWM Drive 3 */ + { 0x0100, 0x0002 }, /* R256 - Clocking 1 */ + { 0x0101, 0x0000 }, /* R257 - Clocking 3 */ + { 0x0102, 0x0011 }, /* R258 - Clocking 4 */ + { 0x0103, 0x0011 }, /* R259 - Clocking 5 */ + { 0x0104, 0x0011 }, /* R260 - Clocking 6 */ + { 0x0107, 0x0000 }, /* R263 - Clocking 7 */ + { 0x0108, 0x0000 }, /* R264 - Clocking 8 */ + { 0x0120, 0x0000 }, /* R288 - ASRC_ENABLE */ + { 0x0121, 0x0000 }, /* R289 - ASRC_STATUS */ + { 0x0122, 0x0000 }, /* R290 - ASRC_RATE1 */ + { 0x0141, 0x8000 }, /* R321 - ISRC 1 CTRL 1 */ + { 0x0142, 0x0000 }, /* R322 - ISRC 1 CTRL 2 */ + { 0x0143, 0x8000 }, /* R323 - ISRC 2 CTRL1 */ + { 0x0144, 0x0000 }, /* R324 - ISRC 2 CTRL 2 */ + { 0x0182, 0x0000 }, /* R386 - FLL1 Control 1 */ + { 0x0183, 0x0000 }, /* R387 - FLL1 Control 2 */ + { 0x0184, 0x0000 }, /* R388 - FLL1 Control 3 */ + { 0x0186, 0x0177 }, /* R390 - FLL1 Control 5 */ + { 0x0187, 0x0001 }, /* R391 - FLL1 Control 6 */ + { 0x0188, 0x0000 }, /* R392 - FLL1 EFS 1 */ + { 0x01A2, 0x0000 }, /* R418 - FLL2 Control 1 */ + { 0x01A3, 0x0000 }, /* R419 - FLL2 Control 2 */ + { 0x01A4, 0x0000 }, /* R420 - FLL2 Control 3 */ + { 0x01A6, 0x0177 }, /* R422 - FLL2 Control 5 */ + { 0x01A7, 0x0001 }, /* R423 - FLL2 Control 6 */ + { 0x01A8, 0x0000 }, /* R424 - FLL2 EFS 1 */ + { 0x0200, 0x0020 }, /* R512 - Mic Charge Pump 1 */ + { 0x0201, 0xB084 }, /* R513 - Mic Charge Pump 2 */ + { 0x0202, 0xBBDE }, /* R514 - HP Charge Pump 1 */ + { 0x0211, 0x20D4 }, /* R529 - LDO1 Control */ + { 0x0215, 0x0062 }, /* R533 - Mic Bias Ctrl 1 */ + { 0x0216, 0x0062 }, /* R534 - Mic Bias Ctrl 2 */ + { 0x0217, 0x0062 }, /* R535 - Mic Bias Ctrl 3 */ + { 0x0280, 0x0004 }, /* R640 - Accessory Detect Mode 1 */ + { 0x0288, 0x0020 }, /* R648 - Headphone Detect 1 */ + { 0x0289, 0x0000 }, /* R649 - Headphone Detect 2 */ + { 0x0290, 0x1100 }, /* R656 - Mic Detect 1 */ + { 0x0291, 0x009F }, /* R657 - Mic Detect 2 */ + { 0x0292, 0x0000 }, /* R658 - Mic Detect 3 */ + { 0x0301, 0x0000 }, /* R769 - Input Enables */ + { 0x0302, 0x0000 }, /* R770 - Input Enables Status */ + { 0x0310, 0x2280 }, /* R784 - Status */ + { 0x0311, 0x0080 }, /* R785 - IN1R Control */ + { 0x0312, 0x2280 }, /* R786 - IN2L Control */ + { 0x0313, 0x0080 }, /* R787 - IN2R Control */ + { 0x0314, 0x2280 }, /* R788 - IN3L Control */ + { 0x0315, 0x0080 }, /* R789 - IN3R Control */ + { 0x0316, 0x2280 }, /* R790 - IN4L Control */ + { 0x0317, 0x0080 }, /* R791 - IN4R Control */ + { 0x0318, 0x0000 }, /* R792 - RXANC_SRC */ + { 0x0319, 0x0022 }, /* R793 - Input Volume Ramp */ + { 0x0320, 0x0180 }, /* R800 - ADC Digital Volume 1L */ + { 0x0321, 0x0180 }, /* R801 - ADC Digital Volume 1R */ + { 0x0322, 0x0180 }, /* R802 - ADC Digital Volume 2L */ + { 0x0323, 0x0180 }, /* R803 - ADC Digital Volume 2R */ + { 0x0324, 0x0180 }, /* R804 - ADC Digital Volume 3L */ + { 0x0325, 0x0180 }, /* R805 - ADC Digital Volume 3R */ + { 0x0326, 0x0180 }, /* R806 - ADC Digital Volume 4L */ + { 0x0327, 0x0180 }, /* R807 - ADC Digital Volume 4R */ + { 0x0401, 0x0000 }, /* R1025 - Output Enables 2 */ + { 0x0402, 0x0000 }, /* R1026 - Output Status 1 */ + { 0x0403, 0x0000 }, /* R1027 - Output Status 2 */ + { 0x0408, 0x0000 }, /* R1032 - Channel Enables 1 */ + { 0x0410, 0x0080 }, /* R1040 - Out Volume 1L */ + { 0x0411, 0x0080 }, /* R1041 - Out Volume 1R */ + { 0x0412, 0x0080 }, /* R1042 - DAC Volume Limit 1L */ + { 0x0413, 0x0080 }, /* R1043 - DAC Volume Limit 1R */ + { 0x0414, 0x0080 }, /* R1044 - Out Volume 2L */ + { 0x0415, 0x0080 }, /* R1045 - Out Volume 2R */ + { 0x0416, 0x0080 }, /* R1046 - DAC Volume Limit 2L */ + { 0x0417, 0x0080 }, /* R1047 - DAC Volume Limit 2R */ + { 0x0418, 0x0080 }, /* R1048 - Out Volume 3L */ + { 0x0419, 0x0080 }, /* R1049 - Out Volume 3R */ + { 0x041A, 0x0080 }, /* R1050 - DAC Volume Limit 3L */ + { 0x041B, 0x0080 }, /* R1051 - DAC Volume Limit 3R */ + { 0x041C, 0x0080 }, /* R1052 - Out Volume 4L */ + { 0x041D, 0x0080 }, /* R1053 - Out Volume 4R */ + { 0x041E, 0x0080 }, /* R1054 - DAC Volume Limit 5L */ + { 0x041F, 0x0080 }, /* R1055 - DAC Volume Limit 5R */ + { 0x0420, 0x0080 }, /* R1056 - DAC Volume Limit 6L */ + { 0x0421, 0x0080 }, /* R1057 - DAC Volume Limit 6R */ + { 0x0440, 0x0000 }, /* R1088 - DAC AEC Control 1 */ + { 0x0441, 0x0022 }, /* R1089 - Output Volume Ramp */ + { 0x0480, 0x0180 }, /* R1152 - DAC Digital Volume 1L */ + { 0x0481, 0x0180 }, /* R1153 - DAC Digital Volume 1R */ + { 0x0482, 0x0180 }, /* R1154 - DAC Digital Volume 2L */ + { 0x0483, 0x0180 }, /* R1155 - DAC Digital Volume 2R */ + { 0x0484, 0x0180 }, /* R1156 - DAC Digital Volume 3L */ + { 0x0485, 0x0180 }, /* R1157 - DAC Digital Volume 3R */ + { 0x0486, 0x0180 }, /* R1158 - DAC Digital Volume 4L */ + { 0x0487, 0x0180 }, /* R1159 - DAC Digital Volume 4R */ + { 0x0488, 0x0180 }, /* R1160 - DAC Digital Volume 5L */ + { 0x0489, 0x0180 }, /* R1161 - DAC Digital Volume 5R */ + { 0x048A, 0x0180 }, /* R1162 - DAC Digital Volume 6L */ + { 0x048B, 0x0180 }, /* R1163 - DAC Digital Volume 6R */ + { 0x04C0, 0x0069 }, /* R1216 - PDM SPK1 CTRL 1 */ + { 0x04C1, 0x0000 }, /* R1217 - PDM SPK1 CTRL 2 */ + { 0x04C2, 0x0069 }, /* R1218 - PDM SPK2 CTRL 1 */ + { 0x04C3, 0x0000 }, /* R1219 - PDM SPK2 CTRL 2 */ + { 0x0500, 0x000C }, /* R1280 - Audio IF 1_1 */ + { 0x0501, 0x0008 }, /* R1281 - Audio IF 1_2 */ + { 0x0502, 0x0000 }, /* R1282 - Audio IF 1_3 */ + { 0x0503, 0x0000 }, /* R1283 - Audio IF 1_4 */ + { 0x0504, 0x0000 }, /* R1284 - Audio IF 1_5 */ + { 0x0505, 0x0300 }, /* R1285 - Audio IF 1_6 */ + { 0x0506, 0x0300 }, /* R1286 - Audio IF 1_7 */ + { 0x0507, 0x1820 }, /* R1287 - Audio IF 1_8 */ + { 0x0508, 0x1820 }, /* R1288 - Audio IF 1_9 */ + { 0x0509, 0x0000 }, /* R1289 - Audio IF 1_10 */ + { 0x050A, 0x0001 }, /* R1290 - Audio IF 1_11 */ + { 0x050B, 0x0002 }, /* R1291 - Audio IF 1_12 */ + { 0x050C, 0x0003 }, /* R1292 - Audio IF 1_13 */ + { 0x050D, 0x0004 }, /* R1293 - Audio IF 1_14 */ + { 0x050E, 0x0005 }, /* R1294 - Audio IF 1_15 */ + { 0x050F, 0x0006 }, /* R1295 - Audio IF 1_16 */ + { 0x0510, 0x0007 }, /* R1296 - Audio IF 1_17 */ + { 0x0511, 0x0000 }, /* R1297 - Audio IF 1_18 */ + { 0x0512, 0x0001 }, /* R1298 - Audio IF 1_19 */ + { 0x0513, 0x0002 }, /* R1299 - Audio IF 1_20 */ + { 0x0514, 0x0003 }, /* R1300 - Audio IF 1_21 */ + { 0x0515, 0x0004 }, /* R1301 - Audio IF 1_22 */ + { 0x0516, 0x0005 }, /* R1302 - Audio IF 1_23 */ + { 0x0517, 0x0006 }, /* R1303 - Audio IF 1_24 */ + { 0x0518, 0x0007 }, /* R1304 - Audio IF 1_25 */ + { 0x0519, 0x0000 }, /* R1305 - Audio IF 1_26 */ + { 0x051A, 0x0000 }, /* R1306 - Audio IF 1_27 */ + { 0x0540, 0x000C }, /* R1344 - Audio IF 2_1 */ + { 0x0541, 0x0008 }, /* R1345 - Audio IF 2_2 */ + { 0x0542, 0x0000 }, /* R1346 - Audio IF 2_3 */ + { 0x0543, 0x0000 }, /* R1347 - Audio IF 2_4 */ + { 0x0544, 0x0000 }, /* R1348 - Audio IF 2_5 */ + { 0x0545, 0x0300 }, /* R1349 - Audio IF 2_6 */ + { 0x0546, 0x0300 }, /* R1350 - Audio IF 2_7 */ + { 0x0547, 0x1820 }, /* R1351 - Audio IF 2_8 */ + { 0x0548, 0x1820 }, /* R1352 - Audio IF 2_9 */ + { 0x0549, 0x0000 }, /* R1353 - Audio IF 2_10 */ + { 0x054A, 0x0001 }, /* R1354 - Audio IF 2_11 */ + { 0x0551, 0x0000 }, /* R1361 - Audio IF 2_18 */ + { 0x0552, 0x0001 }, /* R1362 - Audio IF 2_19 */ + { 0x0559, 0x0000 }, /* R1369 - Audio IF 2_26 */ + { 0x055A, 0x0000 }, /* R1370 - Audio IF 2_27 */ + { 0x0580, 0x000C }, /* R1408 - Audio IF 3_1 */ + { 0x0581, 0x0008 }, /* R1409 - Audio IF 3_2 */ + { 0x0582, 0x0000 }, /* R1410 - Audio IF 3_3 */ + { 0x0583, 0x0000 }, /* R1411 - Audio IF 3_4 */ + { 0x0584, 0x0000 }, /* R1412 - Audio IF 3_5 */ + { 0x0585, 0x0300 }, /* R1413 - Audio IF 3_6 */ + { 0x0586, 0x0300 }, /* R1414 - Audio IF 3_7 */ + { 0x0587, 0x1820 }, /* R1415 - Audio IF 3_8 */ + { 0x0588, 0x1820 }, /* R1416 - Audio IF 3_9 */ + { 0x0589, 0x0000 }, /* R1417 - Audio IF 3_10 */ + { 0x058A, 0x0001 }, /* R1418 - Audio IF 3_11 */ + { 0x0591, 0x0000 }, /* R1425 - Audio IF 3_18 */ + { 0x0592, 0x0001 }, /* R1426 - Audio IF 3_19 */ + { 0x0599, 0x0000 }, /* R1433 - Audio IF 3_26 */ + { 0x059A, 0x0000 }, /* R1434 - Audio IF 3_27 */ + { 0x0640, 0x0000 }, /* R1600 - PWM1MIX Input 1 Source */ + { 0x0641, 0x0080 }, /* R1601 - PWM1MIX Input 1 Volume */ + { 0x0642, 0x0000 }, /* R1602 - PWM1MIX Input 2 Source */ + { 0x0643, 0x0080 }, /* R1603 - PWM1MIX Input 2 Volume */ + { 0x0644, 0x0000 }, /* R1604 - PWM1MIX Input 3 Source */ + { 0x0645, 0x0080 }, /* R1605 - PWM1MIX Input 3 Volume */ + { 0x0646, 0x0000 }, /* R1606 - PWM1MIX Input 4 Source */ + { 0x0647, 0x0080 }, /* R1607 - PWM1MIX Input 4 Volume */ + { 0x0648, 0x0000 }, /* R1608 - PWM2MIX Input 1 Source */ + { 0x0649, 0x0080 }, /* R1609 - PWM2MIX Input 1 Volume */ + { 0x064A, 0x0000 }, /* R1610 - PWM2MIX Input 2 Source */ + { 0x064B, 0x0080 }, /* R1611 - PWM2MIX Input 2 Volume */ + { 0x064C, 0x0000 }, /* R1612 - PWM2MIX Input 3 Source */ + { 0x064D, 0x0080 }, /* R1613 - PWM2MIX Input 3 Volume */ + { 0x064E, 0x0000 }, /* R1614 - PWM2MIX Input 4 Source */ + { 0x064F, 0x0080 }, /* R1615 - PWM2MIX Input 4 Volume */ + { 0x0680, 0x0000 }, /* R1664 - OUT1LMIX Input 1 Source */ + { 0x0681, 0x0080 }, /* R1665 - OUT1LMIX Input 1 Volume */ + { 0x0682, 0x0000 }, /* R1666 - OUT1LMIX Input 2 Source */ + { 0x0683, 0x0080 }, /* R1667 - OUT1LMIX Input 2 Volume */ + { 0x0684, 0x0000 }, /* R1668 - OUT1LMIX Input 3 Source */ + { 0x0685, 0x0080 }, /* R1669 - OUT1LMIX Input 3 Volume */ + { 0x0686, 0x0000 }, /* R1670 - OUT1LMIX Input 4 Source */ + { 0x0687, 0x0080 }, /* R1671 - OUT1LMIX Input 4 Volume */ + { 0x0688, 0x0000 }, /* R1672 - OUT1RMIX Input 1 Source */ + { 0x0689, 0x0080 }, /* R1673 - OUT1RMIX Input 1 Volume */ + { 0x068A, 0x0000 }, /* R1674 - OUT1RMIX Input 2 Source */ + { 0x068B, 0x0080 }, /* R1675 - OUT1RMIX Input 2 Volume */ + { 0x068C, 0x0000 }, /* R1676 - OUT1RMIX Input 3 Source */ + { 0x068D, 0x0080 }, /* R1677 - OUT1RMIX Input 3 Volume */ + { 0x068E, 0x0000 }, /* R1678 - OUT1RMIX Input 4 Source */ + { 0x068F, 0x0080 }, /* R1679 - OUT1RMIX Input 4 Volume */ + { 0x0690, 0x0000 }, /* R1680 - OUT2LMIX Input 1 Source */ + { 0x0691, 0x0080 }, /* R1681 - OUT2LMIX Input 1 Volume */ + { 0x0692, 0x0000 }, /* R1682 - OUT2LMIX Input 2 Source */ + { 0x0693, 0x0080 }, /* R1683 - OUT2LMIX Input 2 Volume */ + { 0x0694, 0x0000 }, /* R1684 - OUT2LMIX Input 3 Source */ + { 0x0695, 0x0080 }, /* R1685 - OUT2LMIX Input 3 Volume */ + { 0x0696, 0x0000 }, /* R1686 - OUT2LMIX Input 4 Source */ + { 0x0697, 0x0080 }, /* R1687 - OUT2LMIX Input 4 Volume */ + { 0x0698, 0x0000 }, /* R1688 - OUT2RMIX Input 1 Source */ + { 0x0699, 0x0080 }, /* R1689 - OUT2RMIX Input 1 Volume */ + { 0x069A, 0x0000 }, /* R1690 - OUT2RMIX Input 2 Source */ + { 0x069B, 0x0080 }, /* R1691 - OUT2RMIX Input 2 Volume */ + { 0x069C, 0x0000 }, /* R1692 - OUT2RMIX Input 3 Source */ + { 0x069D, 0x0080 }, /* R1693 - OUT2RMIX Input 3 Volume */ + { 0x069E, 0x0000 }, /* R1694 - OUT2RMIX Input 4 Source */ + { 0x069F, 0x0080 }, /* R1695 - OUT2RMIX Input 4 Volume */ + { 0x06A0, 0x0000 }, /* R1696 - OUT3LMIX Input 1 Source */ + { 0x06A1, 0x0080 }, /* R1697 - OUT3LMIX Input 1 Volume */ + { 0x06A2, 0x0000 }, /* R1698 - OUT3LMIX Input 2 Source */ + { 0x06A3, 0x0080 }, /* R1699 - OUT3LMIX Input 2 Volume */ + { 0x06A4, 0x0000 }, /* R1700 - OUT3LMIX Input 3 Source */ + { 0x06A5, 0x0080 }, /* R1701 - OUT3LMIX Input 3 Volume */ + { 0x06A6, 0x0000 }, /* R1702 - OUT3LMIX Input 4 Source */ + { 0x06A7, 0x0080 }, /* R1703 - OUT3LMIX Input 4 Volume */ + { 0x06A8, 0x0000 }, /* R1704 - OUT3RMIX Input 1 Source */ + { 0x06A9, 0x0080 }, /* R1705 - OUT3RMIX Input 1 Volume */ + { 0x06AA, 0x0000 }, /* R1706 - OUT3RMIX Input 2 Source */ + { 0x06AB, 0x0080 }, /* R1707 - OUT3RMIX Input 2 Volume */ + { 0x06AC, 0x0000 }, /* R1708 - OUT3RMIX Input 3 Source */ + { 0x06AD, 0x0080 }, /* R1709 - OUT3RMIX Input 3 Volume */ + { 0x06AE, 0x0000 }, /* R1710 - OUT3RMIX Input 4 Source */ + { 0x06AF, 0x0080 }, /* R1711 - OUT3RMIX Input 4 Volume */ + { 0x06B0, 0x0000 }, /* R1712 - OUT4LMIX Input 1 Source */ + { 0x06B1, 0x0080 }, /* R1713 - OUT4LMIX Input 1 Volume */ + { 0x06B2, 0x0000 }, /* R1714 - OUT4LMIX Input 2 Source */ + { 0x06B3, 0x0080 }, /* R1715 - OUT4LMIX Input 2 Volume */ + { 0x06B4, 0x0000 }, /* R1716 - OUT4LMIX Input 3 Source */ + { 0x06B5, 0x0080 }, /* R1717 - OUT4LMIX Input 3 Volume */ + { 0x06B6, 0x0000 }, /* R1718 - OUT4LMIX Input 4 Source */ + { 0x06B7, 0x0080 }, /* R1719 - OUT4LMIX Input 4 Volume */ + { 0x06B8, 0x0000 }, /* R1720 - OUT4RMIX Input 1 Source */ + { 0x06B9, 0x0080 }, /* R1721 - OUT4RMIX Input 1 Volume */ + { 0x06BA, 0x0000 }, /* R1722 - OUT4RMIX Input 2 Source */ + { 0x06BB, 0x0080 }, /* R1723 - OUT4RMIX Input 2 Volume */ + { 0x06BC, 0x0000 }, /* R1724 - OUT4RMIX Input 3 Source */ + { 0x06BD, 0x0080 }, /* R1725 - OUT4RMIX Input 3 Volume */ + { 0x06BE, 0x0000 }, /* R1726 - OUT4RMIX Input 4 Source */ + { 0x06BF, 0x0080 }, /* R1727 - OUT4RMIX Input 4 Volume */ + { 0x06C0, 0x0000 }, /* R1728 - OUT5LMIX Input 1 Source */ + { 0x06C1, 0x0080 }, /* R1729 - OUT5LMIX Input 1 Volume */ + { 0x06C2, 0x0000 }, /* R1730 - OUT5LMIX Input 2 Source */ + { 0x06C3, 0x0080 }, /* R1731 - OUT5LMIX Input 2 Volume */ + { 0x06C4, 0x0000 }, /* R1732 - OUT5LMIX Input 3 Source */ + { 0x06C5, 0x0080 }, /* R1733 - OUT5LMIX Input 3 Volume */ + { 0x06C6, 0x0000 }, /* R1734 - OUT5LMIX Input 4 Source */ + { 0x06C7, 0x0080 }, /* R1735 - OUT5LMIX Input 4 Volume */ + { 0x06C8, 0x0000 }, /* R1736 - OUT5RMIX Input 1 Source */ + { 0x06C9, 0x0080 }, /* R1737 - OUT5RMIX Input 1 Volume */ + { 0x06CA, 0x0000 }, /* R1738 - OUT5RMIX Input 2 Source */ + { 0x06CB, 0x0080 }, /* R1739 - OUT5RMIX Input 2 Volume */ + { 0x06CC, 0x0000 }, /* R1740 - OUT5RMIX Input 3 Source */ + { 0x06CD, 0x0080 }, /* R1741 - OUT5RMIX Input 3 Volume */ + { 0x06CE, 0x0000 }, /* R1742 - OUT5RMIX Input 4 Source */ + { 0x06CF, 0x0080 }, /* R1743 - OUT5RMIX Input 4 Volume */ + { 0x06D0, 0x0000 }, /* R1744 - OUT6LMIX Input 1 Source */ + { 0x06D1, 0x0080 }, /* R1745 - OUT6LMIX Input 1 Volume */ + { 0x06D2, 0x0000 }, /* R1746 - OUT6LMIX Input 2 Source */ + { 0x06D3, 0x0080 }, /* R1747 - OUT6LMIX Input 2 Volume */ + { 0x06D4, 0x0000 }, /* R1748 - OUT6LMIX Input 3 Source */ + { 0x06D5, 0x0080 }, /* R1749 - OUT6LMIX Input 3 Volume */ + { 0x06D6, 0x0000 }, /* R1750 - OUT6LMIX Input 4 Source */ + { 0x06D7, 0x0080 }, /* R1751 - OUT6LMIX Input 4 Volume */ + { 0x06D8, 0x0000 }, /* R1752 - OUT6RMIX Input 1 Source */ + { 0x06D9, 0x0080 }, /* R1753 - OUT6RMIX Input 1 Volume */ + { 0x06DA, 0x0000 }, /* R1754 - OUT6RMIX Input 2 Source */ + { 0x06DB, 0x0080 }, /* R1755 - OUT6RMIX Input 2 Volume */ + { 0x06DC, 0x0000 }, /* R1756 - OUT6RMIX Input 3 Source */ + { 0x06DD, 0x0080 }, /* R1757 - OUT6RMIX Input 3 Volume */ + { 0x06DE, 0x0000 }, /* R1758 - OUT6RMIX Input 4 Source */ + { 0x06DF, 0x0080 }, /* R1759 - OUT6RMIX Input 4 Volume */ + { 0x0700, 0x0000 }, /* R1792 - AIF1TX1MIX Input 1 Source */ + { 0x0701, 0x0080 }, /* R1793 - AIF1TX1MIX Input 1 Volume */ + { 0x0702, 0x0000 }, /* R1794 - AIF1TX1MIX Input 2 Source */ + { 0x0703, 0x0080 }, /* R1795 - AIF1TX1MIX Input 2 Volume */ + { 0x0704, 0x0000 }, /* R1796 - AIF1TX1MIX Input 3 Source */ + { 0x0705, 0x0080 }, /* R1797 - AIF1TX1MIX Input 3 Volume */ + { 0x0706, 0x0000 }, /* R1798 - AIF1TX1MIX Input 4 Source */ + { 0x0707, 0x0080 }, /* R1799 - AIF1TX1MIX Input 4 Volume */ + { 0x0708, 0x0000 }, /* R1800 - AIF1TX2MIX Input 1 Source */ + { 0x0709, 0x0080 }, /* R1801 - AIF1TX2MIX Input 1 Volume */ + { 0x070A, 0x0000 }, /* R1802 - AIF1TX2MIX Input 2 Source */ + { 0x070B, 0x0080 }, /* R1803 - AIF1TX2MIX Input 2 Volume */ + { 0x070C, 0x0000 }, /* R1804 - AIF1TX2MIX Input 3 Source */ + { 0x070D, 0x0080 }, /* R1805 - AIF1TX2MIX Input 3 Volume */ + { 0x070E, 0x0000 }, /* R1806 - AIF1TX2MIX Input 4 Source */ + { 0x070F, 0x0080 }, /* R1807 - AIF1TX2MIX Input 4 Volume */ + { 0x0710, 0x0000 }, /* R1808 - AIF1TX3MIX Input 1 Source */ + { 0x0711, 0x0080 }, /* R1809 - AIF1TX3MIX Input 1 Volume */ + { 0x0712, 0x0000 }, /* R1810 - AIF1TX3MIX Input 2 Source */ + { 0x0713, 0x0080 }, /* R1811 - AIF1TX3MIX Input 2 Volume */ + { 0x0714, 0x0000 }, /* R1812 - AIF1TX3MIX Input 3 Source */ + { 0x0715, 0x0080 }, /* R1813 - AIF1TX3MIX Input 3 Volume */ + { 0x0716, 0x0000 }, /* R1814 - AIF1TX3MIX Input 4 Source */ + { 0x0717, 0x0080 }, /* R1815 - AIF1TX3MIX Input 4 Volume */ + { 0x0718, 0x0000 }, /* R1816 - AIF1TX4MIX Input 1 Source */ + { 0x0719, 0x0080 }, /* R1817 - AIF1TX4MIX Input 1 Volume */ + { 0x071A, 0x0000 }, /* R1818 - AIF1TX4MIX Input 2 Source */ + { 0x071B, 0x0080 }, /* R1819 - AIF1TX4MIX Input 2 Volume */ + { 0x071C, 0x0000 }, /* R1820 - AIF1TX4MIX Input 3 Source */ + { 0x071D, 0x0080 }, /* R1821 - AIF1TX4MIX Input 3 Volume */ + { 0x071E, 0x0000 }, /* R1822 - AIF1TX4MIX Input 4 Source */ + { 0x071F, 0x0080 }, /* R1823 - AIF1TX4MIX Input 4 Volume */ + { 0x0720, 0x0000 }, /* R1824 - AIF1TX5MIX Input 1 Source */ + { 0x0721, 0x0080 }, /* R1825 - AIF1TX5MIX Input 1 Volume */ + { 0x0722, 0x0000 }, /* R1826 - AIF1TX5MIX Input 2 Source */ + { 0x0723, 0x0080 }, /* R1827 - AIF1TX5MIX Input 2 Volume */ + { 0x0724, 0x0000 }, /* R1828 - AIF1TX5MIX Input 3 Source */ + { 0x0725, 0x0080 }, /* R1829 - AIF1TX5MIX Input 3 Volume */ + { 0x0726, 0x0000 }, /* R1830 - AIF1TX5MIX Input 4 Source */ + { 0x0727, 0x0080 }, /* R1831 - AIF1TX5MIX Input 4 Volume */ + { 0x0728, 0x0000 }, /* R1832 - AIF1TX6MIX Input 1 Source */ + { 0x0729, 0x0080 }, /* R1833 - AIF1TX6MIX Input 1 Volume */ + { 0x072A, 0x0000 }, /* R1834 - AIF1TX6MIX Input 2 Source */ + { 0x072B, 0x0080 }, /* R1835 - AIF1TX6MIX Input 2 Volume */ + { 0x072C, 0x0000 }, /* R1836 - AIF1TX6MIX Input 3 Source */ + { 0x072D, 0x0080 }, /* R1837 - AIF1TX6MIX Input 3 Volume */ + { 0x072E, 0x0000 }, /* R1838 - AIF1TX6MIX Input 4 Source */ + { 0x072F, 0x0080 }, /* R1839 - AIF1TX6MIX Input 4 Volume */ + { 0x0730, 0x0000 }, /* R1840 - AIF1TX7MIX Input 1 Source */ + { 0x0731, 0x0080 }, /* R1841 - AIF1TX7MIX Input 1 Volume */ + { 0x0732, 0x0000 }, /* R1842 - AIF1TX7MIX Input 2 Source */ + { 0x0733, 0x0080 }, /* R1843 - AIF1TX7MIX Input 2 Volume */ + { 0x0734, 0x0000 }, /* R1844 - AIF1TX7MIX Input 3 Source */ + { 0x0735, 0x0080 }, /* R1845 - AIF1TX7MIX Input 3 Volume */ + { 0x0736, 0x0000 }, /* R1846 - AIF1TX7MIX Input 4 Source */ + { 0x0737, 0x0080 }, /* R1847 - AIF1TX7MIX Input 4 Volume */ + { 0x0738, 0x0000 }, /* R1848 - AIF1TX8MIX Input 1 Source */ + { 0x0739, 0x0080 }, /* R1849 - AIF1TX8MIX Input 1 Volume */ + { 0x073A, 0x0000 }, /* R1850 - AIF1TX8MIX Input 2 Source */ + { 0x073B, 0x0080 }, /* R1851 - AIF1TX8MIX Input 2 Volume */ + { 0x073C, 0x0000 }, /* R1852 - AIF1TX8MIX Input 3 Source */ + { 0x073D, 0x0080 }, /* R1853 - AIF1TX8MIX Input 3 Volume */ + { 0x073E, 0x0000 }, /* R1854 - AIF1TX8MIX Input 4 Source */ + { 0x073F, 0x0080 }, /* R1855 - AIF1TX8MIX Input 4 Volume */ + { 0x0740, 0x0000 }, /* R1856 - AIF2TX1MIX Input 1 Source */ + { 0x0741, 0x0080 }, /* R1857 - AIF2TX1MIX Input 1 Volume */ + { 0x0742, 0x0000 }, /* R1858 - AIF2TX1MIX Input 2 Source */ + { 0x0743, 0x0080 }, /* R1859 - AIF2TX1MIX Input 2 Volume */ + { 0x0744, 0x0000 }, /* R1860 - AIF2TX1MIX Input 3 Source */ + { 0x0745, 0x0080 }, /* R1861 - AIF2TX1MIX Input 3 Volume */ + { 0x0746, 0x0000 }, /* R1862 - AIF2TX1MIX Input 4 Source */ + { 0x0747, 0x0080 }, /* R1863 - AIF2TX1MIX Input 4 Volume */ + { 0x0748, 0x0000 }, /* R1864 - AIF2TX2MIX Input 1 Source */ + { 0x0749, 0x0080 }, /* R1865 - AIF2TX2MIX Input 1 Volume */ + { 0x074A, 0x0000 }, /* R1866 - AIF2TX2MIX Input 2 Source */ + { 0x074B, 0x0080 }, /* R1867 - AIF2TX2MIX Input 2 Volume */ + { 0x074C, 0x0000 }, /* R1868 - AIF2TX2MIX Input 3 Source */ + { 0x074D, 0x0080 }, /* R1869 - AIF2TX2MIX Input 3 Volume */ + { 0x074E, 0x0000 }, /* R1870 - AIF2TX2MIX Input 4 Source */ + { 0x074F, 0x0080 }, /* R1871 - AIF2TX2MIX Input 4 Volume */ + { 0x0780, 0x0000 }, /* R1920 - AIF3TX1MIX Input 1 Source */ + { 0x0781, 0x0080 }, /* R1921 - AIF3TX1MIX Input 1 Volume */ + { 0x0782, 0x0000 }, /* R1922 - AIF3TX1MIX Input 2 Source */ + { 0x0783, 0x0080 }, /* R1923 - AIF3TX1MIX Input 2 Volume */ + { 0x0784, 0x0000 }, /* R1924 - AIF3TX1MIX Input 3 Source */ + { 0x0785, 0x0080 }, /* R1925 - AIF3TX1MIX Input 3 Volume */ + { 0x0786, 0x0000 }, /* R1926 - AIF3TX1MIX Input 4 Source */ + { 0x0787, 0x0080 }, /* R1927 - AIF3TX1MIX Input 4 Volume */ + { 0x0788, 0x0000 }, /* R1928 - AIF3TX2MIX Input 1 Source */ + { 0x0789, 0x0080 }, /* R1929 - AIF3TX2MIX Input 1 Volume */ + { 0x078A, 0x0000 }, /* R1930 - AIF3TX2MIX Input 2 Source */ + { 0x078B, 0x0080 }, /* R1931 - AIF3TX2MIX Input 2 Volume */ + { 0x078C, 0x0000 }, /* R1932 - AIF3TX2MIX Input 3 Source */ + { 0x078D, 0x0080 }, /* R1933 - AIF3TX2MIX Input 3 Volume */ + { 0x078E, 0x0000 }, /* R1934 - AIF3TX2MIX Input 4 Source */ + { 0x078F, 0x0080 }, /* R1935 - AIF3TX2MIX Input 4 Volume */ + { 0x0880, 0x0000 }, /* R2176 - EQ1MIX Input 1 Source */ + { 0x0881, 0x0080 }, /* R2177 - EQ1MIX Input 1 Volume */ + { 0x0882, 0x0000 }, /* R2178 - EQ1MIX Input 2 Source */ + { 0x0883, 0x0080 }, /* R2179 - EQ1MIX Input 2 Volume */ + { 0x0884, 0x0000 }, /* R2180 - EQ1MIX Input 3 Source */ + { 0x0885, 0x0080 }, /* R2181 - EQ1MIX Input 3 Volume */ + { 0x0886, 0x0000 }, /* R2182 - EQ1MIX Input 4 Source */ + { 0x0887, 0x0080 }, /* R2183 - EQ1MIX Input 4 Volume */ + { 0x0888, 0x0000 }, /* R2184 - EQ2MIX Input 1 Source */ + { 0x0889, 0x0080 }, /* R2185 - EQ2MIX Input 1 Volume */ + { 0x088A, 0x0000 }, /* R2186 - EQ2MIX Input 2 Source */ + { 0x088B, 0x0080 }, /* R2187 - EQ2MIX Input 2 Volume */ + { 0x088C, 0x0000 }, /* R2188 - EQ2MIX Input 3 Source */ + { 0x088D, 0x0080 }, /* R2189 - EQ2MIX Input 3 Volume */ + { 0x088E, 0x0000 }, /* R2190 - EQ2MIX Input 4 Source */ + { 0x088F, 0x0080 }, /* R2191 - EQ2MIX Input 4 Volume */ + { 0x0890, 0x0000 }, /* R2192 - EQ3MIX Input 1 Source */ + { 0x0891, 0x0080 }, /* R2193 - EQ3MIX Input 1 Volume */ + { 0x0892, 0x0000 }, /* R2194 - EQ3MIX Input 2 Source */ + { 0x0893, 0x0080 }, /* R2195 - EQ3MIX Input 2 Volume */ + { 0x0894, 0x0000 }, /* R2196 - EQ3MIX Input 3 Source */ + { 0x0895, 0x0080 }, /* R2197 - EQ3MIX Input 3 Volume */ + { 0x0896, 0x0000 }, /* R2198 - EQ3MIX Input 4 Source */ + { 0x0897, 0x0080 }, /* R2199 - EQ3MIX Input 4 Volume */ + { 0x0898, 0x0000 }, /* R2200 - EQ4MIX Input 1 Source */ + { 0x0899, 0x0080 }, /* R2201 - EQ4MIX Input 1 Volume */ + { 0x089A, 0x0000 }, /* R2202 - EQ4MIX Input 2 Source */ + { 0x089B, 0x0080 }, /* R2203 - EQ4MIX Input 2 Volume */ + { 0x089C, 0x0000 }, /* R2204 - EQ4MIX Input 3 Source */ + { 0x089D, 0x0080 }, /* R2205 - EQ4MIX Input 3 Volume */ + { 0x089E, 0x0000 }, /* R2206 - EQ4MIX Input 4 Source */ + { 0x089F, 0x0080 }, /* R2207 - EQ4MIX Input 4 Volume */ + { 0x08C0, 0x0000 }, /* R2240 - DRC1LMIX Input 1 Source */ + { 0x08C1, 0x0080 }, /* R2241 - DRC1LMIX Input 1 Volume */ + { 0x08C2, 0x0000 }, /* R2242 - DRC1LMIX Input 2 Source */ + { 0x08C3, 0x0080 }, /* R2243 - DRC1LMIX Input 2 Volume */ + { 0x08C4, 0x0000 }, /* R2244 - DRC1LMIX Input 3 Source */ + { 0x08C5, 0x0080 }, /* R2245 - DRC1LMIX Input 3 Volume */ + { 0x08C6, 0x0000 }, /* R2246 - DRC1LMIX Input 4 Source */ + { 0x08C7, 0x0080 }, /* R2247 - DRC1LMIX Input 4 Volume */ + { 0x08C8, 0x0000 }, /* R2248 - DRC1RMIX Input 1 Source */ + { 0x08C9, 0x0080 }, /* R2249 - DRC1RMIX Input 1 Volume */ + { 0x08CA, 0x0000 }, /* R2250 - DRC1RMIX Input 2 Source */ + { 0x08CB, 0x0080 }, /* R2251 - DRC1RMIX Input 2 Volume */ + { 0x08CC, 0x0000 }, /* R2252 - DRC1RMIX Input 3 Source */ + { 0x08CD, 0x0080 }, /* R2253 - DRC1RMIX Input 3 Volume */ + { 0x08CE, 0x0000 }, /* R2254 - DRC1RMIX Input 4 Source */ + { 0x08CF, 0x0080 }, /* R2255 - DRC1RMIX Input 4 Volume */ + { 0x0900, 0x0000 }, /* R2304 - HPLP1MIX Input 1 Source */ + { 0x0901, 0x0080 }, /* R2305 - HPLP1MIX Input 1 Volume */ + { 0x0902, 0x0000 }, /* R2306 - HPLP1MIX Input 2 Source */ + { 0x0903, 0x0080 }, /* R2307 - HPLP1MIX Input 2 Volume */ + { 0x0904, 0x0000 }, /* R2308 - HPLP1MIX Input 3 Source */ + { 0x0905, 0x0080 }, /* R2309 - HPLP1MIX Input 3 Volume */ + { 0x0906, 0x0000 }, /* R2310 - HPLP1MIX Input 4 Source */ + { 0x0907, 0x0080 }, /* R2311 - HPLP1MIX Input 4 Volume */ + { 0x0908, 0x0000 }, /* R2312 - HPLP2MIX Input 1 Source */ + { 0x0909, 0x0080 }, /* R2313 - HPLP2MIX Input 1 Volume */ + { 0x090A, 0x0000 }, /* R2314 - HPLP2MIX Input 2 Source */ + { 0x090B, 0x0080 }, /* R2315 - HPLP2MIX Input 2 Volume */ + { 0x090C, 0x0000 }, /* R2316 - HPLP2MIX Input 3 Source */ + { 0x090D, 0x0080 }, /* R2317 - HPLP2MIX Input 3 Volume */ + { 0x090E, 0x0000 }, /* R2318 - HPLP2MIX Input 4 Source */ + { 0x090F, 0x0080 }, /* R2319 - HPLP2MIX Input 4 Volume */ + { 0x0910, 0x0000 }, /* R2320 - HPLP3MIX Input 1 Source */ + { 0x0911, 0x0080 }, /* R2321 - HPLP3MIX Input 1 Volume */ + { 0x0912, 0x0000 }, /* R2322 - HPLP3MIX Input 2 Source */ + { 0x0913, 0x0080 }, /* R2323 - HPLP3MIX Input 2 Volume */ + { 0x0914, 0x0000 }, /* R2324 - HPLP3MIX Input 3 Source */ + { 0x0915, 0x0080 }, /* R2325 - HPLP3MIX Input 3 Volume */ + { 0x0916, 0x0000 }, /* R2326 - HPLP3MIX Input 4 Source */ + { 0x0917, 0x0080 }, /* R2327 - HPLP3MIX Input 4 Volume */ + { 0x0918, 0x0000 }, /* R2328 - HPLP4MIX Input 1 Source */ + { 0x0919, 0x0080 }, /* R2329 - HPLP4MIX Input 1 Volume */ + { 0x091A, 0x0000 }, /* R2330 - HPLP4MIX Input 2 Source */ + { 0x091B, 0x0080 }, /* R2331 - HPLP4MIX Input 2 Volume */ + { 0x091C, 0x0000 }, /* R2332 - HPLP4MIX Input 3 Source */ + { 0x091D, 0x0080 }, /* R2333 - HPLP4MIX Input 3 Volume */ + { 0x091E, 0x0000 }, /* R2334 - HPLP4MIX Input 4 Source */ + { 0x091F, 0x0080 }, /* R2335 - HPLP4MIX Input 4 Volume */ + { 0x0940, 0x0000 }, /* R2368 - DSP1LMIX Input 1 Source */ + { 0x0941, 0x0080 }, /* R2369 - DSP1LMIX Input 1 Volume */ + { 0x0942, 0x0000 }, /* R2370 - DSP1LMIX Input 2 Source */ + { 0x0943, 0x0080 }, /* R2371 - DSP1LMIX Input 2 Volume */ + { 0x0944, 0x0000 }, /* R2372 - DSP1LMIX Input 3 Source */ + { 0x0945, 0x0080 }, /* R2373 - DSP1LMIX Input 3 Volume */ + { 0x0946, 0x0000 }, /* R2374 - DSP1LMIX Input 4 Source */ + { 0x0947, 0x0080 }, /* R2375 - DSP1LMIX Input 4 Volume */ + { 0x0948, 0x0000 }, /* R2376 - DSP1RMIX Input 1 Source */ + { 0x0949, 0x0080 }, /* R2377 - DSP1RMIX Input 1 Volume */ + { 0x094A, 0x0000 }, /* R2378 - DSP1RMIX Input 2 Source */ + { 0x094B, 0x0080 }, /* R2379 - DSP1RMIX Input 2 Volume */ + { 0x094C, 0x0000 }, /* R2380 - DSP1RMIX Input 3 Source */ + { 0x094D, 0x0080 }, /* R2381 - DSP1RMIX Input 3 Volume */ + { 0x094E, 0x0000 }, /* R2382 - DSP1RMIX Input 4 Source */ + { 0x094F, 0x0080 }, /* R2383 - DSP1RMIX Input 4 Volume */ + { 0x0950, 0x0000 }, /* R2384 - DSP1AUX1MIX Input 1 Source */ + { 0x0958, 0x0000 }, /* R2392 - DSP1AUX2MIX Input 1 Source */ + { 0x0960, 0x0000 }, /* R2400 - DSP1AUX3MIX Input 1 Source */ + { 0x0968, 0x0000 }, /* R2408 - DSP1AUX4MIX Input 1 Source */ + { 0x0970, 0x0000 }, /* R2416 - DSP1AUX5MIX Input 1 Source */ + { 0x0978, 0x0000 }, /* R2424 - DSP1AUX6MIX Input 1 Source */ + { 0x0980, 0x0000 }, /* R2432 - DSP2LMIX Input 1 Source */ + { 0x0981, 0x0080 }, /* R2433 - DSP2LMIX Input 1 Volume */ + { 0x0982, 0x0000 }, /* R2434 - DSP2LMIX Input 2 Source */ + { 0x0983, 0x0080 }, /* R2435 - DSP2LMIX Input 2 Volume */ + { 0x0984, 0x0000 }, /* R2436 - DSP2LMIX Input 3 Source */ + { 0x0985, 0x0080 }, /* R2437 - DSP2LMIX Input 3 Volume */ + { 0x0986, 0x0000 }, /* R2438 - DSP2LMIX Input 4 Source */ + { 0x0987, 0x0080 }, /* R2439 - DSP2LMIX Input 4 Volume */ + { 0x0988, 0x0000 }, /* R2440 - DSP2RMIX Input 1 Source */ + { 0x0989, 0x0080 }, /* R2441 - DSP2RMIX Input 1 Volume */ + { 0x098A, 0x0000 }, /* R2442 - DSP2RMIX Input 2 Source */ + { 0x098B, 0x0080 }, /* R2443 - DSP2RMIX Input 2 Volume */ + { 0x098C, 0x0000 }, /* R2444 - DSP2RMIX Input 3 Source */ + { 0x098D, 0x0080 }, /* R2445 - DSP2RMIX Input 3 Volume */ + { 0x098E, 0x0000 }, /* R2446 - DSP2RMIX Input 4 Source */ + { 0x098F, 0x0080 }, /* R2447 - DSP2RMIX Input 4 Volume */ + { 0x0990, 0x0000 }, /* R2448 - DSP2AUX1MIX Input 1 Source */ + { 0x0998, 0x0000 }, /* R2456 - DSP2AUX2MIX Input 1 Source */ + { 0x09A0, 0x0000 }, /* R2464 - DSP2AUX3MIX Input 1 Source */ + { 0x09A8, 0x0000 }, /* R2472 - DSP2AUX4MIX Input 1 Source */ + { 0x09B0, 0x0000 }, /* R2480 - DSP2AUX5MIX Input 1 Source */ + { 0x09B8, 0x0000 }, /* R2488 - DSP2AUX6MIX Input 1 Source */ + { 0x09C0, 0x0000 }, /* R2496 - DSP3LMIX Input 1 Source */ + { 0x09C1, 0x0080 }, /* R2497 - DSP3LMIX Input 1 Volume */ + { 0x09C2, 0x0000 }, /* R2498 - DSP3LMIX Input 2 Source */ + { 0x09C3, 0x0080 }, /* R2499 - DSP3LMIX Input 2 Volume */ + { 0x09C4, 0x0000 }, /* R2500 - DSP3LMIX Input 3 Source */ + { 0x09C5, 0x0080 }, /* R2501 - DSP3LMIX Input 3 Volume */ + { 0x09C6, 0x0000 }, /* R2502 - DSP3LMIX Input 4 Source */ + { 0x09C7, 0x0080 }, /* R2503 - DSP3LMIX Input 4 Volume */ + { 0x09C8, 0x0000 }, /* R2504 - DSP3RMIX Input 1 Source */ + { 0x09C9, 0x0080 }, /* R2505 - DSP3RMIX Input 1 Volume */ + { 0x09CA, 0x0000 }, /* R2506 - DSP3RMIX Input 2 Source */ + { 0x09CB, 0x0080 }, /* R2507 - DSP3RMIX Input 2 Volume */ + { 0x09CC, 0x0000 }, /* R2508 - DSP3RMIX Input 3 Source */ + { 0x09CD, 0x0080 }, /* R2509 - DSP3RMIX Input 3 Volume */ + { 0x09CE, 0x0000 }, /* R2510 - DSP3RMIX Input 4 Source */ + { 0x09CF, 0x0080 }, /* R2511 - DSP3RMIX Input 4 Volume */ + { 0x09D0, 0x0000 }, /* R2512 - DSP3AUX1MIX Input 1 Source */ + { 0x09D8, 0x0000 }, /* R2520 - DSP3AUX2MIX Input 1 Source */ + { 0x09E0, 0x0000 }, /* R2528 - DSP3AUX3MIX Input 1 Source */ + { 0x09E8, 0x0000 }, /* R2536 - DSP3AUX4MIX Input 1 Source */ + { 0x09F0, 0x0000 }, /* R2544 - DSP3AUX5MIX Input 1 Source */ + { 0x09F8, 0x0000 }, /* R2552 - DSP3AUX6MIX Input 1 Source */ + { 0x0A80, 0x0000 }, /* R2688 - ASRC1LMIX Input 1 Source */ + { 0x0A88, 0x0000 }, /* R2696 - ASRC1RMIX Input 1 Source */ + { 0x0A90, 0x0000 }, /* R2704 - ASRC2LMIX Input 1 Source */ + { 0x0A98, 0x0000 }, /* R2712 - ASRC2RMIX Input 1 Source */ + { 0x0B00, 0x0000 }, /* R2816 - ISRC1DEC1MIX Input 1 Source */ + { 0x0B08, 0x0000 }, /* R2824 - ISRC1DEC2MIX Input 1 Source */ + { 0x0B10, 0x0000 }, /* R2832 - ISRC1DEC3MIX Input 1 Source */ + { 0x0B18, 0x0000 }, /* R2840 - ISRC1DEC4MIX Input 1 Source */ + { 0x0B20, 0x0000 }, /* R2848 - ISRC1INT1MIX Input 1 Source */ + { 0x0B28, 0x0000 }, /* R2856 - ISRC1INT2MIX Input 1 Source */ + { 0x0B30, 0x0000 }, /* R2864 - ISRC1INT3MIX Input 1 Source */ + { 0x0B38, 0x0000 }, /* R2872 - ISRC1INT4MIX Input 1 Source */ + { 0x0B40, 0x0000 }, /* R2880 - ISRC2DEC1MIX Input 1 Source */ + { 0x0B48, 0x0000 }, /* R2888 - ISRC2DEC2MIX Input 1 Source */ + { 0x0B50, 0x0000 }, /* R2896 - ISRC2DEC3MIX Input 1 Source */ + { 0x0B58, 0x0000 }, /* R2904 - ISRC2DEC4MIX Input 1 Source */ + { 0x0B60, 0x0000 }, /* R2912 - ISRC2INT1MIX Input 1 Source */ + { 0x0B68, 0x0000 }, /* R2920 - ISRC2INT2MIX Input 1 Source */ + { 0x0B70, 0x0000 }, /* R2928 - ISRC2INT3MIX Input 1 Source */ + { 0x0B78, 0x0000 }, /* R2936 - ISRC2INT4MIX Input 1 Source */ + { 0x0C00, 0xA001 }, /* R3072 - GPIO CTRL 1 */ + { 0x0C01, 0xA001 }, /* R3073 - GPIO CTRL 2 */ + { 0x0C02, 0xA001 }, /* R3074 - GPIO CTRL 3 */ + { 0x0C03, 0xA001 }, /* R3075 - GPIO CTRL 4 */ + { 0x0C04, 0xA001 }, /* R3076 - GPIO CTRL 5 */ + { 0x0C05, 0xA001 }, /* R3077 - GPIO CTRL 6 */ + { 0x0C23, 0x4003 }, /* R3107 - Misc Pad Ctrl 1 */ + { 0x0C24, 0x0000 }, /* R3108 - Misc Pad Ctrl 2 */ + { 0x0C25, 0x0000 }, /* R3109 - Misc Pad Ctrl 3 */ + { 0x0C26, 0x0000 }, /* R3110 - Misc Pad Ctrl 4 */ + { 0x0C27, 0x0000 }, /* R3111 - Misc Pad Ctrl 5 */ + { 0x0C28, 0x0000 }, /* R3112 - Misc GPIO 1 */ + { 0x0D00, 0x0000 }, /* R3328 - Interrupt Status 1 */ + { 0x0D01, 0x0000 }, /* R3329 - Interrupt Status 2 */ + { 0x0D02, 0x0000 }, /* R3330 - Interrupt Status 3 */ + { 0x0D03, 0x0000 }, /* R3331 - Interrupt Status 4 */ + { 0x0D04, 0x0000 }, /* R3332 - Interrupt Raw Status 2 */ + { 0x0D05, 0x0000 }, /* R3333 - Interrupt Raw Status 3 */ + { 0x0D06, 0x0000 }, /* R3334 - Interrupt Raw Status 4 */ + { 0x0D07, 0xFFFF }, /* R3335 - Interrupt Status 1 Mask */ + { 0x0D08, 0xFFFF }, /* R3336 - Interrupt Status 2 Mask */ + { 0x0D09, 0xFFFF }, /* R3337 - Interrupt Status 3 Mask */ + { 0x0D0A, 0xFFFF }, /* R3338 - Interrupt Status 4 Mask */ + { 0x0D1F, 0x0000 }, /* R3359 - Interrupt Control */ + { 0x0D20, 0xFFFF }, /* R3360 - IRQ Debounce 1 */ + { 0x0D21, 0xFFFF }, /* R3361 - IRQ Debounce 2 */ + { 0x0E00, 0x0000 }, /* R3584 - FX_Ctrl */ + { 0x0E10, 0x6318 }, /* R3600 - EQ1_1 */ + { 0x0E11, 0x6300 }, /* R3601 - EQ1_2 */ + { 0x0E12, 0x0FC8 }, /* R3602 - EQ1_3 */ + { 0x0E13, 0x03FE }, /* R3603 - EQ1_4 */ + { 0x0E14, 0x00E0 }, /* R3604 - EQ1_5 */ + { 0x0E15, 0x1EC4 }, /* R3605 - EQ1_6 */ + { 0x0E16, 0xF136 }, /* R3606 - EQ1_7 */ + { 0x0E17, 0x0409 }, /* R3607 - EQ1_8 */ + { 0x0E18, 0x04CC }, /* R3608 - EQ1_9 */ + { 0x0E19, 0x1C9B }, /* R3609 - EQ1_10 */ + { 0x0E1A, 0xF337 }, /* R3610 - EQ1_11 */ + { 0x0E1B, 0x040B }, /* R3611 - EQ1_12 */ + { 0x0E1C, 0x0CBB }, /* R3612 - EQ1_13 */ + { 0x0E1D, 0x16F8 }, /* R3613 - EQ1_14 */ + { 0x0E1E, 0xF7D9 }, /* R3614 - EQ1_15 */ + { 0x0E1F, 0x040A }, /* R3615 - EQ1_16 */ + { 0x0E20, 0x1F14 }, /* R3616 - EQ1_17 */ + { 0x0E21, 0x058C }, /* R3617 - EQ1_18 */ + { 0x0E22, 0x0563 }, /* R3618 - EQ1_19 */ + { 0x0E23, 0x4000 }, /* R3619 - EQ1_20 */ + { 0x0E26, 0x6318 }, /* R3622 - EQ2_1 */ + { 0x0E27, 0x6300 }, /* R3623 - EQ2_2 */ + { 0x0E28, 0x0FC8 }, /* R3624 - EQ2_3 */ + { 0x0E29, 0x03FE }, /* R3625 - EQ2_4 */ + { 0x0E2A, 0x00E0 }, /* R3626 - EQ2_5 */ + { 0x0E2B, 0x1EC4 }, /* R3627 - EQ2_6 */ + { 0x0E2C, 0xF136 }, /* R3628 - EQ2_7 */ + { 0x0E2D, 0x0409 }, /* R3629 - EQ2_8 */ + { 0x0E2E, 0x04CC }, /* R3630 - EQ2_9 */ + { 0x0E2F, 0x1C9B }, /* R3631 - EQ2_10 */ + { 0x0E30, 0xF337 }, /* R3632 - EQ2_11 */ + { 0x0E31, 0x040B }, /* R3633 - EQ2_12 */ + { 0x0E32, 0x0CBB }, /* R3634 - EQ2_13 */ + { 0x0E33, 0x16F8 }, /* R3635 - EQ2_14 */ + { 0x0E34, 0xF7D9 }, /* R3636 - EQ2_15 */ + { 0x0E35, 0x040A }, /* R3637 - EQ2_16 */ + { 0x0E36, 0x1F14 }, /* R3638 - EQ2_17 */ + { 0x0E37, 0x058C }, /* R3639 - EQ2_18 */ + { 0x0E38, 0x0563 }, /* R3640 - EQ2_19 */ + { 0x0E39, 0x4000 }, /* R3641 - EQ2_20 */ + { 0x0E3C, 0x6318 }, /* R3644 - EQ3_1 */ + { 0x0E3D, 0x6300 }, /* R3645 - EQ3_2 */ + { 0x0E3E, 0x0FC8 }, /* R3646 - EQ3_3 */ + { 0x0E3F, 0x03FE }, /* R3647 - EQ3_4 */ + { 0x0E40, 0x00E0 }, /* R3648 - EQ3_5 */ + { 0x0E41, 0x1EC4 }, /* R3649 - EQ3_6 */ + { 0x0E42, 0xF136 }, /* R3650 - EQ3_7 */ + { 0x0E43, 0x0409 }, /* R3651 - EQ3_8 */ + { 0x0E44, 0x04CC }, /* R3652 - EQ3_9 */ + { 0x0E45, 0x1C9B }, /* R3653 - EQ3_10 */ + { 0x0E46, 0xF337 }, /* R3654 - EQ3_11 */ + { 0x0E47, 0x040B }, /* R3655 - EQ3_12 */ + { 0x0E48, 0x0CBB }, /* R3656 - EQ3_13 */ + { 0x0E49, 0x16F8 }, /* R3657 - EQ3_14 */ + { 0x0E4A, 0xF7D9 }, /* R3658 - EQ3_15 */ + { 0x0E4B, 0x040A }, /* R3659 - EQ3_16 */ + { 0x0E4C, 0x1F14 }, /* R3660 - EQ3_17 */ + { 0x0E4D, 0x058C }, /* R3661 - EQ3_18 */ + { 0x0E4E, 0x0563 }, /* R3662 - EQ3_19 */ + { 0x0E4F, 0x4000 }, /* R3663 - EQ3_20 */ + { 0x0E52, 0x6318 }, /* R3666 - EQ4_1 */ + { 0x0E53, 0x6300 }, /* R3667 - EQ4_2 */ + { 0x0E54, 0x0FC8 }, /* R3668 - EQ4_3 */ + { 0x0E55, 0x03FE }, /* R3669 - EQ4_4 */ + { 0x0E56, 0x00E0 }, /* R3670 - EQ4_5 */ + { 0x0E57, 0x1EC4 }, /* R3671 - EQ4_6 */ + { 0x0E58, 0xF136 }, /* R3672 - EQ4_7 */ + { 0x0E59, 0x0409 }, /* R3673 - EQ4_8 */ + { 0x0E5A, 0x04CC }, /* R3674 - EQ4_9 */ + { 0x0E5B, 0x1C9B }, /* R3675 - EQ4_10 */ + { 0x0E5C, 0xF337 }, /* R3676 - EQ4_11 */ + { 0x0E5D, 0x040B }, /* R3677 - EQ4_12 */ + { 0x0E5E, 0x0CBB }, /* R3678 - EQ4_13 */ + { 0x0E5F, 0x16F8 }, /* R3679 - EQ4_14 */ + { 0x0E60, 0xF7D9 }, /* R3680 - EQ4_15 */ + { 0x0E61, 0x040A }, /* R3681 - EQ4_16 */ + { 0x0E62, 0x1F14 }, /* R3682 - EQ4_17 */ + { 0x0E63, 0x058C }, /* R3683 - EQ4_18 */ + { 0x0E64, 0x0563 }, /* R3684 - EQ4_19 */ + { 0x0E65, 0x4000 }, /* R3685 - EQ4_20 */ + { 0x0E80, 0x0018 }, /* R3712 - DRC1 ctrl1 */ + { 0x0E81, 0x0933 }, /* R3713 - DRC1 ctrl2 */ + { 0x0E82, 0x0018 }, /* R3714 - DRC1 ctrl3 */ + { 0x0E83, 0x0000 }, /* R3715 - DRC1 ctrl4 */ + { 0x0E84, 0x0000 }, /* R3716 - DRC1 ctrl5 */ + { 0x0EC0, 0x0000 }, /* R3776 - HPLPF1_1 */ + { 0x0EC1, 0x0000 }, /* R3777 - HPLPF1_2 */ + { 0x0EC4, 0x0000 }, /* R3780 - HPLPF2_1 */ + { 0x0EC5, 0x0000 }, /* R3781 - HPLPF2_2 */ + { 0x0EC8, 0x0000 }, /* R3784 - HPLPF3_1 */ + { 0x0EC9, 0x0000 }, /* R3785 - HPLPF3_2 */ + { 0x0ECC, 0x0000 }, /* R3788 - HPLPF4_1 */ + { 0x0ECD, 0x0000 }, /* R3789 - HPLPF4_2 */ + { 0x0F02, 0x0000 }, /* R3842 - DSP1 Control 2 */ + { 0x0F03, 0x0000 }, /* R3843 - DSP1 Control 3 */ + { 0x0F04, 0x0000 }, /* R3844 - DSP1 Control 4 */ + { 0x1002, 0x0000 }, /* R4098 - DSP2 Control 2 */ + { 0x1003, 0x0000 }, /* R4099 - DSP2 Control 3 */ + { 0x1004, 0x0000 }, /* R4100 - DSP2 Control 4 */ + { 0x1102, 0x0000 }, /* R4354 - DSP3 Control 2 */ + { 0x1103, 0x0000 }, /* R4355 - DSP3 Control 3 */ + { 0x1104, 0x0000 }, /* R4356 - DSP3 Control 4 */ +}; diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c new file mode 100644 index 000000000..b793701aa --- /dev/null +++ b/sound/soc/codecs/wm5100.c @@ -0,0 +1,2728 @@ +/* + * wm5100.c -- WM5100 ALSA SoC Audio driver + * + * Copyright 2011-2 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm5100.h" + +#define WM5100_NUM_CORE_SUPPLIES 2 +static const char *wm5100_core_supply_names[WM5100_NUM_CORE_SUPPLIES] = { + "DBVDD1", + "LDOVDD", /* If DCVDD is supplied externally specify as LDOVDD */ +}; + +#define WM5100_AIFS 3 +#define WM5100_SYNC_SRS 3 + +struct wm5100_fll { + int fref; + int fout; + int src; + struct completion lock; +}; + +/* codec private data */ +struct wm5100_priv { + struct device *dev; + struct regmap *regmap; + struct snd_soc_component *component; + + struct regulator_bulk_data core_supplies[WM5100_NUM_CORE_SUPPLIES]; + + int rev; + + int sysclk; + int asyncclk; + + bool aif_async[WM5100_AIFS]; + bool aif_symmetric[WM5100_AIFS]; + int sr_ref[WM5100_SYNC_SRS]; + + bool out_ena[2]; + + struct snd_soc_jack *jack; + bool jack_detecting; + bool jack_mic; + int jack_mode; + int jack_flips; + + struct wm5100_fll fll[2]; + + struct wm5100_pdata pdata; + +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif +}; + +static int wm5100_sr_code[] = { + 0, + 12000, + 24000, + 48000, + 96000, + 192000, + 384000, + 768000, + 0, + 11025, + 22050, + 44100, + 88200, + 176400, + 352800, + 705600, + 4000, + 8000, + 16000, + 32000, + 64000, + 128000, + 256000, + 512000, +}; + +static int wm5100_sr_regs[WM5100_SYNC_SRS] = { + WM5100_CLOCKING_4, + WM5100_CLOCKING_5, + WM5100_CLOCKING_6, +}; + +static int wm5100_alloc_sr(struct snd_soc_component *component, int rate) +{ + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + int sr_code, sr_free, i; + + for (i = 0; i < ARRAY_SIZE(wm5100_sr_code); i++) + if (wm5100_sr_code[i] == rate) + break; + if (i == ARRAY_SIZE(wm5100_sr_code)) { + dev_err(component->dev, "Unsupported sample rate: %dHz\n", rate); + return -EINVAL; + } + sr_code = i; + + if ((wm5100->sysclk % rate) == 0) { + /* Is this rate already in use? */ + sr_free = -1; + for (i = 0; i < ARRAY_SIZE(wm5100_sr_regs); i++) { + if (!wm5100->sr_ref[i] && sr_free == -1) { + sr_free = i; + continue; + } + if ((snd_soc_component_read32(component, wm5100_sr_regs[i]) & + WM5100_SAMPLE_RATE_1_MASK) == sr_code) + break; + } + + if (i < ARRAY_SIZE(wm5100_sr_regs)) { + wm5100->sr_ref[i]++; + dev_dbg(component->dev, "SR %dHz, slot %d, ref %d\n", + rate, i, wm5100->sr_ref[i]); + return i; + } + + if (sr_free == -1) { + dev_err(component->dev, "All SR slots already in use\n"); + return -EBUSY; + } + + dev_dbg(component->dev, "Allocating SR slot %d for %dHz\n", + sr_free, rate); + wm5100->sr_ref[sr_free]++; + snd_soc_component_update_bits(component, wm5100_sr_regs[sr_free], + WM5100_SAMPLE_RATE_1_MASK, + sr_code); + + return sr_free; + + } else { + dev_err(component->dev, + "SR %dHz incompatible with %dHz SYSCLK and %dHz ASYNCCLK\n", + rate, wm5100->sysclk, wm5100->asyncclk); + return -EINVAL; + } +} + +static void wm5100_free_sr(struct snd_soc_component *component, int rate) +{ + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + int i, sr_code; + + for (i = 0; i < ARRAY_SIZE(wm5100_sr_code); i++) + if (wm5100_sr_code[i] == rate) + break; + if (i == ARRAY_SIZE(wm5100_sr_code)) { + dev_err(component->dev, "Unsupported sample rate: %dHz\n", rate); + return; + } + sr_code = wm5100_sr_code[i]; + + for (i = 0; i < ARRAY_SIZE(wm5100_sr_regs); i++) { + if (!wm5100->sr_ref[i]) + continue; + + if ((snd_soc_component_read32(component, wm5100_sr_regs[i]) & + WM5100_SAMPLE_RATE_1_MASK) == sr_code) + break; + } + if (i < ARRAY_SIZE(wm5100_sr_regs)) { + wm5100->sr_ref[i]--; + dev_dbg(component->dev, "Dereference SR %dHz, count now %d\n", + rate, wm5100->sr_ref[i]); + } else { + dev_warn(component->dev, "Freeing unreferenced sample rate %dHz\n", + rate); + } +} + +static int wm5100_reset(struct wm5100_priv *wm5100) +{ + if (wm5100->pdata.reset) { + gpio_set_value_cansleep(wm5100->pdata.reset, 0); + gpio_set_value_cansleep(wm5100->pdata.reset, 1); + + return 0; + } else { + return regmap_write(wm5100->regmap, WM5100_SOFTWARE_RESET, 0); + } +} + +static DECLARE_TLV_DB_SCALE(in_tlv, -6300, 100, 0); +static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static DECLARE_TLV_DB_SCALE(mixer_tlv, -3200, 100, 0); +static DECLARE_TLV_DB_SCALE(out_tlv, -6400, 100, 0); +static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); + +static const char *wm5100_mixer_texts[] = { + "None", + "Tone Generator 1", + "Tone Generator 2", + "AEC loopback", + "IN1L", + "IN1R", + "IN2L", + "IN2R", + "IN3L", + "IN3R", + "IN4L", + "IN4R", + "AIF1RX1", + "AIF1RX2", + "AIF1RX3", + "AIF1RX4", + "AIF1RX5", + "AIF1RX6", + "AIF1RX7", + "AIF1RX8", + "AIF2RX1", + "AIF2RX2", + "AIF3RX1", + "AIF3RX2", + "EQ1", + "EQ2", + "EQ3", + "EQ4", + "DRC1L", + "DRC1R", + "LHPF1", + "LHPF2", + "LHPF3", + "LHPF4", + "DSP1.1", + "DSP1.2", + "DSP1.3", + "DSP1.4", + "DSP1.5", + "DSP1.6", + "DSP2.1", + "DSP2.2", + "DSP2.3", + "DSP2.4", + "DSP2.5", + "DSP2.6", + "DSP3.1", + "DSP3.2", + "DSP3.3", + "DSP3.4", + "DSP3.5", + "DSP3.6", + "ASRC1L", + "ASRC1R", + "ASRC2L", + "ASRC2R", + "ISRC1INT1", + "ISRC1INT2", + "ISRC1INT3", + "ISRC1INT4", + "ISRC2INT1", + "ISRC2INT2", + "ISRC2INT3", + "ISRC2INT4", + "ISRC1DEC1", + "ISRC1DEC2", + "ISRC1DEC3", + "ISRC1DEC4", + "ISRC2DEC1", + "ISRC2DEC2", + "ISRC2DEC3", + "ISRC2DEC4", +}; + +static int wm5100_mixer_values[] = { + 0x00, + 0x04, /* Tone */ + 0x05, + 0x08, /* AEC */ + 0x10, /* Input */ + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x20, /* AIF */ + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x30, /* AIF3 - check */ + 0x31, + 0x50, /* EQ */ + 0x51, + 0x52, + 0x53, + 0x54, + 0x58, /* DRC */ + 0x59, + 0x60, /* LHPF1 */ + 0x61, /* LHPF2 */ + 0x62, /* LHPF3 */ + 0x63, /* LHPF4 */ + 0x68, /* DSP1 */ + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x70, /* DSP2 */ + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x78, /* DSP3 */ + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x90, /* ASRC1 */ + 0x91, + 0x92, /* ASRC2 */ + 0x93, + 0xa0, /* ISRC1DEC1 */ + 0xa1, + 0xa2, + 0xa3, + 0xa4, /* ISRC1INT1 */ + 0xa5, + 0xa6, + 0xa7, + 0xa8, /* ISRC2DEC1 */ + 0xa9, + 0xaa, + 0xab, + 0xac, /* ISRC2INT1 */ + 0xad, + 0xae, + 0xaf, +}; + +#define WM5100_MIXER_CONTROLS(name, base) \ + SOC_SINGLE_TLV(name " Input 1 Volume", base + 1 , \ + WM5100_MIXER_VOL_SHIFT, 80, 0, mixer_tlv), \ + SOC_SINGLE_TLV(name " Input 2 Volume", base + 3 , \ + WM5100_MIXER_VOL_SHIFT, 80, 0, mixer_tlv), \ + SOC_SINGLE_TLV(name " Input 3 Volume", base + 5 , \ + WM5100_MIXER_VOL_SHIFT, 80, 0, mixer_tlv), \ + SOC_SINGLE_TLV(name " Input 4 Volume", base + 7 , \ + WM5100_MIXER_VOL_SHIFT, 80, 0, mixer_tlv) + +#define WM5100_MUX_ENUM_DECL(name, reg) \ + SOC_VALUE_ENUM_SINGLE_DECL(name, reg, 0, 0xff, \ + wm5100_mixer_texts, wm5100_mixer_values) + +#define WM5100_MUX_CTL_DECL(name) \ + const struct snd_kcontrol_new name##_mux = \ + SOC_DAPM_ENUM("Route", name##_enum) + +#define WM5100_MIXER_ENUMS(name, base_reg) \ + static WM5100_MUX_ENUM_DECL(name##_in1_enum, base_reg); \ + static WM5100_MUX_ENUM_DECL(name##_in2_enum, base_reg + 2); \ + static WM5100_MUX_ENUM_DECL(name##_in3_enum, base_reg + 4); \ + static WM5100_MUX_ENUM_DECL(name##_in4_enum, base_reg + 6); \ + static WM5100_MUX_CTL_DECL(name##_in1); \ + static WM5100_MUX_CTL_DECL(name##_in2); \ + static WM5100_MUX_CTL_DECL(name##_in3); \ + static WM5100_MUX_CTL_DECL(name##_in4) + +WM5100_MIXER_ENUMS(HPOUT1L, WM5100_OUT1LMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(HPOUT1R, WM5100_OUT1RMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(HPOUT2L, WM5100_OUT2LMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(HPOUT2R, WM5100_OUT2RMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(HPOUT3L, WM5100_OUT3LMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(HPOUT3R, WM5100_OUT3RMIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(SPKOUTL, WM5100_OUT4LMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(SPKOUTR, WM5100_OUT4RMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(SPKDAT1L, WM5100_OUT5LMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(SPKDAT1R, WM5100_OUT5RMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(SPKDAT2L, WM5100_OUT6LMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(SPKDAT2R, WM5100_OUT6RMIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(PWM1, WM5100_PWM1MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(PWM2, WM5100_PWM1MIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(AIF1TX1, WM5100_AIF1TX1MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF1TX2, WM5100_AIF1TX2MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF1TX3, WM5100_AIF1TX3MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF1TX4, WM5100_AIF1TX4MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF1TX5, WM5100_AIF1TX5MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF1TX6, WM5100_AIF1TX6MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF1TX7, WM5100_AIF1TX7MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF1TX8, WM5100_AIF1TX8MIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(AIF2TX1, WM5100_AIF2TX1MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF2TX2, WM5100_AIF2TX2MIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(AIF3TX1, WM5100_AIF1TX1MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(AIF3TX2, WM5100_AIF1TX2MIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(EQ1, WM5100_EQ1MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(EQ2, WM5100_EQ2MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(EQ3, WM5100_EQ3MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(EQ4, WM5100_EQ4MIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(DRC1L, WM5100_DRC1LMIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(DRC1R, WM5100_DRC1RMIX_INPUT_1_SOURCE); + +WM5100_MIXER_ENUMS(LHPF1, WM5100_HPLP1MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(LHPF2, WM5100_HPLP2MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(LHPF3, WM5100_HPLP3MIX_INPUT_1_SOURCE); +WM5100_MIXER_ENUMS(LHPF4, WM5100_HPLP4MIX_INPUT_1_SOURCE); + +#define WM5100_MUX(name, ctrl) \ + SND_SOC_DAPM_MUX(name, SND_SOC_NOPM, 0, 0, ctrl) + +#define WM5100_MIXER_WIDGETS(name, name_str) \ + WM5100_MUX(name_str " Input 1", &name##_in1_mux), \ + WM5100_MUX(name_str " Input 2", &name##_in2_mux), \ + WM5100_MUX(name_str " Input 3", &name##_in3_mux), \ + WM5100_MUX(name_str " Input 4", &name##_in4_mux), \ + SND_SOC_DAPM_MIXER(name_str " Mixer", SND_SOC_NOPM, 0, 0, NULL, 0) + +#define WM5100_MIXER_INPUT_ROUTES(name) \ + { name, "Tone Generator 1", "Tone Generator 1" }, \ + { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2L PGA" }, \ + { name, "IN2R", "IN2R PGA" }, \ + { name, "IN3L", "IN3L PGA" }, \ + { name, "IN3R", "IN3R PGA" }, \ + { name, "IN4L", "IN4L PGA" }, \ + { name, "IN4R", "IN4R PGA" }, \ + { name, "AIF1RX1", "AIF1RX1" }, \ + { name, "AIF1RX2", "AIF1RX2" }, \ + { name, "AIF1RX3", "AIF1RX3" }, \ + { name, "AIF1RX4", "AIF1RX4" }, \ + { name, "AIF1RX5", "AIF1RX5" }, \ + { name, "AIF1RX6", "AIF1RX6" }, \ + { name, "AIF1RX7", "AIF1RX7" }, \ + { name, "AIF1RX8", "AIF1RX8" }, \ + { name, "AIF2RX1", "AIF2RX1" }, \ + { name, "AIF2RX2", "AIF2RX2" }, \ + { name, "AIF3RX1", "AIF3RX1" }, \ + { name, "AIF3RX2", "AIF3RX2" }, \ + { name, "EQ1", "EQ1" }, \ + { name, "EQ2", "EQ2" }, \ + { name, "EQ3", "EQ3" }, \ + { name, "EQ4", "EQ4" }, \ + { name, "DRC1L", "DRC1L" }, \ + { name, "DRC1R", "DRC1R" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" }, \ + { name, "LHPF3", "LHPF3" }, \ + { name, "LHPF4", "LHPF4" } + +#define WM5100_MIXER_ROUTES(widget, name) \ + { widget, NULL, name " Mixer" }, \ + { name " Mixer", NULL, name " Input 1" }, \ + { name " Mixer", NULL, name " Input 2" }, \ + { name " Mixer", NULL, name " Input 3" }, \ + { name " Mixer", NULL, name " Input 4" }, \ + WM5100_MIXER_INPUT_ROUTES(name " Input 1"), \ + WM5100_MIXER_INPUT_ROUTES(name " Input 2"), \ + WM5100_MIXER_INPUT_ROUTES(name " Input 3"), \ + WM5100_MIXER_INPUT_ROUTES(name " Input 4") + +static const char *wm5100_lhpf_mode_text[] = { + "Low-pass", "High-pass" +}; + +static SOC_ENUM_SINGLE_DECL(wm5100_lhpf1_mode, + WM5100_HPLPF1_1, WM5100_LHPF1_MODE_SHIFT, + wm5100_lhpf_mode_text); + +static SOC_ENUM_SINGLE_DECL(wm5100_lhpf2_mode, + WM5100_HPLPF2_1, WM5100_LHPF2_MODE_SHIFT, + wm5100_lhpf_mode_text); + +static SOC_ENUM_SINGLE_DECL(wm5100_lhpf3_mode, + WM5100_HPLPF3_1, WM5100_LHPF3_MODE_SHIFT, + wm5100_lhpf_mode_text); + +static SOC_ENUM_SINGLE_DECL(wm5100_lhpf4_mode, + WM5100_HPLPF4_1, WM5100_LHPF4_MODE_SHIFT, + wm5100_lhpf_mode_text); + +static const struct snd_kcontrol_new wm5100_snd_controls[] = { +SOC_SINGLE("IN1 High Performance Switch", WM5100_IN1L_CONTROL, + WM5100_IN1_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN2 High Performance Switch", WM5100_IN2L_CONTROL, + WM5100_IN2_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN3 High Performance Switch", WM5100_IN3L_CONTROL, + WM5100_IN3_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN4 High Performance Switch", WM5100_IN4L_CONTROL, + WM5100_IN4_OSR_SHIFT, 1, 0), + +/* Only applicable for analogue inputs */ +SOC_DOUBLE_R_TLV("IN1 Volume", WM5100_IN1L_CONTROL, WM5100_IN1R_CONTROL, + WM5100_IN1L_PGA_VOL_SHIFT, 94, 0, in_tlv), +SOC_DOUBLE_R_TLV("IN2 Volume", WM5100_IN2L_CONTROL, WM5100_IN2R_CONTROL, + WM5100_IN2L_PGA_VOL_SHIFT, 94, 0, in_tlv), +SOC_DOUBLE_R_TLV("IN3 Volume", WM5100_IN3L_CONTROL, WM5100_IN3R_CONTROL, + WM5100_IN3L_PGA_VOL_SHIFT, 94, 0, in_tlv), +SOC_DOUBLE_R_TLV("IN4 Volume", WM5100_IN4L_CONTROL, WM5100_IN4R_CONTROL, + WM5100_IN4L_PGA_VOL_SHIFT, 94, 0, in_tlv), + +SOC_DOUBLE_R_TLV("IN1 Digital Volume", WM5100_ADC_DIGITAL_VOLUME_1L, + WM5100_ADC_DIGITAL_VOLUME_1R, WM5100_IN1L_VOL_SHIFT, 191, + 0, digital_tlv), +SOC_DOUBLE_R_TLV("IN2 Digital Volume", WM5100_ADC_DIGITAL_VOLUME_2L, + WM5100_ADC_DIGITAL_VOLUME_2R, WM5100_IN2L_VOL_SHIFT, 191, + 0, digital_tlv), +SOC_DOUBLE_R_TLV("IN3 Digital Volume", WM5100_ADC_DIGITAL_VOLUME_3L, + WM5100_ADC_DIGITAL_VOLUME_3R, WM5100_IN3L_VOL_SHIFT, 191, + 0, digital_tlv), +SOC_DOUBLE_R_TLV("IN4 Digital Volume", WM5100_ADC_DIGITAL_VOLUME_4L, + WM5100_ADC_DIGITAL_VOLUME_4R, WM5100_IN4L_VOL_SHIFT, 191, + 0, digital_tlv), + +SOC_DOUBLE_R("IN1 Switch", WM5100_ADC_DIGITAL_VOLUME_1L, + WM5100_ADC_DIGITAL_VOLUME_1R, WM5100_IN1L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("IN2 Switch", WM5100_ADC_DIGITAL_VOLUME_2L, + WM5100_ADC_DIGITAL_VOLUME_2R, WM5100_IN2L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("IN3 Switch", WM5100_ADC_DIGITAL_VOLUME_3L, + WM5100_ADC_DIGITAL_VOLUME_3R, WM5100_IN3L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("IN4 Switch", WM5100_ADC_DIGITAL_VOLUME_4L, + WM5100_ADC_DIGITAL_VOLUME_4R, WM5100_IN4L_MUTE_SHIFT, 1, 1), + +SND_SOC_BYTES_MASK("EQ1 Coefficients", WM5100_EQ1_1, 20, WM5100_EQ1_ENA), +SND_SOC_BYTES_MASK("EQ2 Coefficients", WM5100_EQ2_1, 20, WM5100_EQ2_ENA), +SND_SOC_BYTES_MASK("EQ3 Coefficients", WM5100_EQ3_1, 20, WM5100_EQ3_ENA), +SND_SOC_BYTES_MASK("EQ4 Coefficients", WM5100_EQ4_1, 20, WM5100_EQ4_ENA), + +SND_SOC_BYTES_MASK("DRC Coefficients", WM5100_DRC1_CTRL1, 5, + WM5100_DRCL_ENA | WM5100_DRCR_ENA), + +SND_SOC_BYTES("LHPF1 Coefficients", WM5100_HPLPF1_2, 1), +SND_SOC_BYTES("LHPF2 Coefficients", WM5100_HPLPF2_2, 1), +SND_SOC_BYTES("LHPF3 Coefficients", WM5100_HPLPF3_2, 1), +SND_SOC_BYTES("LHPF4 Coefficients", WM5100_HPLPF4_2, 1), + +SOC_SINGLE("HPOUT1 High Performance Switch", WM5100_OUT_VOLUME_1L, + WM5100_OUT1_OSR_SHIFT, 1, 0), +SOC_SINGLE("HPOUT2 High Performance Switch", WM5100_OUT_VOLUME_2L, + WM5100_OUT2_OSR_SHIFT, 1, 0), +SOC_SINGLE("HPOUT3 High Performance Switch", WM5100_OUT_VOLUME_3L, + WM5100_OUT3_OSR_SHIFT, 1, 0), +SOC_SINGLE("SPKOUT High Performance Switch", WM5100_OUT_VOLUME_4L, + WM5100_OUT4_OSR_SHIFT, 1, 0), +SOC_SINGLE("SPKDAT1 High Performance Switch", WM5100_DAC_VOLUME_LIMIT_5L, + WM5100_OUT5_OSR_SHIFT, 1, 0), +SOC_SINGLE("SPKDAT2 High Performance Switch", WM5100_DAC_VOLUME_LIMIT_6L, + WM5100_OUT6_OSR_SHIFT, 1, 0), + +SOC_DOUBLE_R_TLV("HPOUT1 Digital Volume", WM5100_DAC_DIGITAL_VOLUME_1L, + WM5100_DAC_DIGITAL_VOLUME_1R, WM5100_OUT1L_VOL_SHIFT, 159, 0, + digital_tlv), +SOC_DOUBLE_R_TLV("HPOUT2 Digital Volume", WM5100_DAC_DIGITAL_VOLUME_2L, + WM5100_DAC_DIGITAL_VOLUME_2R, WM5100_OUT2L_VOL_SHIFT, 159, 0, + digital_tlv), +SOC_DOUBLE_R_TLV("HPOUT3 Digital Volume", WM5100_DAC_DIGITAL_VOLUME_3L, + WM5100_DAC_DIGITAL_VOLUME_3R, WM5100_OUT3L_VOL_SHIFT, 159, 0, + digital_tlv), +SOC_DOUBLE_R_TLV("SPKOUT Digital Volume", WM5100_DAC_DIGITAL_VOLUME_4L, + WM5100_DAC_DIGITAL_VOLUME_4R, WM5100_OUT4L_VOL_SHIFT, 159, 0, + digital_tlv), +SOC_DOUBLE_R_TLV("SPKDAT1 Digital Volume", WM5100_DAC_DIGITAL_VOLUME_5L, + WM5100_DAC_DIGITAL_VOLUME_5R, WM5100_OUT5L_VOL_SHIFT, 159, 0, + digital_tlv), +SOC_DOUBLE_R_TLV("SPKDAT2 Digital Volume", WM5100_DAC_DIGITAL_VOLUME_6L, + WM5100_DAC_DIGITAL_VOLUME_6R, WM5100_OUT6L_VOL_SHIFT, 159, 0, + digital_tlv), + +SOC_DOUBLE_R("HPOUT1 Digital Switch", WM5100_DAC_DIGITAL_VOLUME_1L, + WM5100_DAC_DIGITAL_VOLUME_1R, WM5100_OUT1L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("HPOUT2 Digital Switch", WM5100_DAC_DIGITAL_VOLUME_2L, + WM5100_DAC_DIGITAL_VOLUME_2R, WM5100_OUT2L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("HPOUT3 Digital Switch", WM5100_DAC_DIGITAL_VOLUME_3L, + WM5100_DAC_DIGITAL_VOLUME_3R, WM5100_OUT3L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKOUT Digital Switch", WM5100_DAC_DIGITAL_VOLUME_4L, + WM5100_DAC_DIGITAL_VOLUME_4R, WM5100_OUT4L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKDAT1 Digital Switch", WM5100_DAC_DIGITAL_VOLUME_5L, + WM5100_DAC_DIGITAL_VOLUME_5R, WM5100_OUT5L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKDAT2 Digital Switch", WM5100_DAC_DIGITAL_VOLUME_6L, + WM5100_DAC_DIGITAL_VOLUME_6R, WM5100_OUT6L_MUTE_SHIFT, 1, 1), + +/* FIXME: Only valid from -12dB to 0dB (52-64) */ +SOC_DOUBLE_R_TLV("HPOUT1 Volume", WM5100_OUT_VOLUME_1L, WM5100_OUT_VOLUME_1R, + WM5100_OUT1L_PGA_VOL_SHIFT, 64, 0, out_tlv), +SOC_DOUBLE_R_TLV("HPOUT2 Volume", WM5100_OUT_VOLUME_2L, WM5100_OUT_VOLUME_2R, + WM5100_OUT2L_PGA_VOL_SHIFT, 64, 0, out_tlv), +SOC_DOUBLE_R_TLV("HPOUT3 Volume", WM5100_OUT_VOLUME_3L, WM5100_OUT_VOLUME_3R, + WM5100_OUT2L_PGA_VOL_SHIFT, 64, 0, out_tlv), + +SOC_DOUBLE("SPKDAT1 Switch", WM5100_PDM_SPK1_CTRL_1, WM5100_SPK1L_MUTE_SHIFT, + WM5100_SPK1R_MUTE_SHIFT, 1, 1), +SOC_DOUBLE("SPKDAT2 Switch", WM5100_PDM_SPK2_CTRL_1, WM5100_SPK2L_MUTE_SHIFT, + WM5100_SPK2R_MUTE_SHIFT, 1, 1), + +SOC_SINGLE_TLV("EQ1 Band 1 Volume", WM5100_EQ1_1, WM5100_EQ1_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 Band 2 Volume", WM5100_EQ1_1, WM5100_EQ1_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 Band 3 Volume", WM5100_EQ1_1, WM5100_EQ1_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 Band 4 Volume", WM5100_EQ1_2, WM5100_EQ1_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 Band 5 Volume", WM5100_EQ1_2, WM5100_EQ1_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +SOC_SINGLE_TLV("EQ2 Band 1 Volume", WM5100_EQ2_1, WM5100_EQ2_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Band 2 Volume", WM5100_EQ2_1, WM5100_EQ2_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Band 3 Volume", WM5100_EQ2_1, WM5100_EQ2_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Band 4 Volume", WM5100_EQ2_2, WM5100_EQ2_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Band 5 Volume", WM5100_EQ2_2, WM5100_EQ2_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +SOC_SINGLE_TLV("EQ3 Band 1 Volume", WM5100_EQ1_1, WM5100_EQ3_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Band 2 Volume", WM5100_EQ3_1, WM5100_EQ3_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Band 3 Volume", WM5100_EQ3_1, WM5100_EQ3_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Band 4 Volume", WM5100_EQ3_2, WM5100_EQ3_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Band 5 Volume", WM5100_EQ3_2, WM5100_EQ3_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +SOC_SINGLE_TLV("EQ4 Band 1 Volume", WM5100_EQ4_1, WM5100_EQ4_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Band 2 Volume", WM5100_EQ4_1, WM5100_EQ4_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Band 3 Volume", WM5100_EQ4_1, WM5100_EQ4_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Band 4 Volume", WM5100_EQ4_2, WM5100_EQ4_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Band 5 Volume", WM5100_EQ4_2, WM5100_EQ4_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +SOC_ENUM("LHPF1 Mode", wm5100_lhpf1_mode), +SOC_ENUM("LHPF2 Mode", wm5100_lhpf2_mode), +SOC_ENUM("LHPF3 Mode", wm5100_lhpf3_mode), +SOC_ENUM("LHPF4 Mode", wm5100_lhpf4_mode), + +WM5100_MIXER_CONTROLS("HPOUT1L", WM5100_OUT1LMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("HPOUT1R", WM5100_OUT1RMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("HPOUT2L", WM5100_OUT2LMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("HPOUT2R", WM5100_OUT2RMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("HPOUT3L", WM5100_OUT3LMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("HPOUT3R", WM5100_OUT3RMIX_INPUT_1_SOURCE), + +WM5100_MIXER_CONTROLS("SPKOUTL", WM5100_OUT4LMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("SPKOUTR", WM5100_OUT4RMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("SPKDAT1L", WM5100_OUT5LMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("SPKDAT1R", WM5100_OUT5RMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("SPKDAT2L", WM5100_OUT6LMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("SPKDAT2R", WM5100_OUT6RMIX_INPUT_1_SOURCE), + +WM5100_MIXER_CONTROLS("PWM1", WM5100_PWM1MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("PWM2", WM5100_PWM2MIX_INPUT_1_SOURCE), + +WM5100_MIXER_CONTROLS("AIF1TX1", WM5100_AIF1TX1MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF1TX2", WM5100_AIF1TX2MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF1TX3", WM5100_AIF1TX3MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF1TX4", WM5100_AIF1TX4MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF1TX5", WM5100_AIF1TX5MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF1TX6", WM5100_AIF1TX6MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF1TX7", WM5100_AIF1TX7MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF1TX8", WM5100_AIF1TX8MIX_INPUT_1_SOURCE), + +WM5100_MIXER_CONTROLS("AIF2TX1", WM5100_AIF2TX1MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF2TX2", WM5100_AIF2TX2MIX_INPUT_1_SOURCE), + +WM5100_MIXER_CONTROLS("AIF3TX1", WM5100_AIF3TX1MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("AIF3TX2", WM5100_AIF3TX2MIX_INPUT_1_SOURCE), + +WM5100_MIXER_CONTROLS("EQ1", WM5100_EQ1MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("EQ2", WM5100_EQ2MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("EQ3", WM5100_EQ3MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("EQ4", WM5100_EQ4MIX_INPUT_1_SOURCE), + +WM5100_MIXER_CONTROLS("DRC1L", WM5100_DRC1LMIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("DRC1R", WM5100_DRC1RMIX_INPUT_1_SOURCE), +SND_SOC_BYTES_MASK("DRC", WM5100_DRC1_CTRL1, 5, + WM5100_DRCL_ENA | WM5100_DRCR_ENA), + +WM5100_MIXER_CONTROLS("LHPF1", WM5100_HPLP1MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("LHPF2", WM5100_HPLP2MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("LHPF3", WM5100_HPLP3MIX_INPUT_1_SOURCE), +WM5100_MIXER_CONTROLS("LHPF4", WM5100_HPLP4MIX_INPUT_1_SOURCE), +}; + +static void wm5100_seq_notifier(struct snd_soc_component *component, + enum snd_soc_dapm_type event, int subseq) +{ + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + u16 val, expect, i; + + /* Wait for the outputs to flag themselves as enabled */ + if (wm5100->out_ena[0]) { + expect = snd_soc_component_read32(component, WM5100_CHANNEL_ENABLES_1); + for (i = 0; i < 200; i++) { + val = snd_soc_component_read32(component, WM5100_OUTPUT_STATUS_1); + if (val == expect) { + wm5100->out_ena[0] = false; + break; + } + } + if (i == 200) { + dev_err(component->dev, "Timeout waiting for OUTPUT1 %x\n", + expect); + } + } + + if (wm5100->out_ena[1]) { + expect = snd_soc_component_read32(component, WM5100_OUTPUT_ENABLES_2); + for (i = 0; i < 200; i++) { + val = snd_soc_component_read32(component, WM5100_OUTPUT_STATUS_2); + if (val == expect) { + wm5100->out_ena[1] = false; + break; + } + } + if (i == 200) { + dev_err(component->dev, "Timeout waiting for OUTPUT2 %x\n", + expect); + } + } +} + +static int wm5100_out_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + + switch (w->reg) { + case WM5100_CHANNEL_ENABLES_1: + wm5100->out_ena[0] = true; + break; + case WM5100_OUTPUT_ENABLES_2: + wm5100->out_ena[0] = true; + break; + default: + break; + } + + return 0; +} + +static void wm5100_log_status3(struct wm5100_priv *wm5100, int val) +{ + if (val & WM5100_SPK_SHUTDOWN_WARN_EINT) + dev_crit(wm5100->dev, "Speaker shutdown warning\n"); + if (val & WM5100_SPK_SHUTDOWN_EINT) + dev_crit(wm5100->dev, "Speaker shutdown\n"); + if (val & WM5100_CLKGEN_ERR_EINT) + dev_crit(wm5100->dev, "SYSCLK underclocked\n"); + if (val & WM5100_CLKGEN_ERR_ASYNC_EINT) + dev_crit(wm5100->dev, "ASYNCCLK underclocked\n"); +} + +static void wm5100_log_status4(struct wm5100_priv *wm5100, int val) +{ + if (val & WM5100_AIF3_ERR_EINT) + dev_err(wm5100->dev, "AIF3 configuration error\n"); + if (val & WM5100_AIF2_ERR_EINT) + dev_err(wm5100->dev, "AIF2 configuration error\n"); + if (val & WM5100_AIF1_ERR_EINT) + dev_err(wm5100->dev, "AIF1 configuration error\n"); + if (val & WM5100_CTRLIF_ERR_EINT) + dev_err(wm5100->dev, "Control interface error\n"); + if (val & WM5100_ISRC2_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "ISRC2 underclocked\n"); + if (val & WM5100_ISRC1_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "ISRC1 underclocked\n"); + if (val & WM5100_FX_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "FX underclocked\n"); + if (val & WM5100_AIF3_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "AIF3 underclocked\n"); + if (val & WM5100_AIF2_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "AIF2 underclocked\n"); + if (val & WM5100_AIF1_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "AIF1 underclocked\n"); + if (val & WM5100_ASRC_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "ASRC underclocked\n"); + if (val & WM5100_DAC_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "DAC underclocked\n"); + if (val & WM5100_ADC_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "ADC underclocked\n"); + if (val & WM5100_MIXER_UNDERCLOCKED_EINT) + dev_err(wm5100->dev, "Mixer underclocked\n"); +} + +static int wm5100_post_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_component_read32(component, WM5100_INTERRUPT_RAW_STATUS_3); + ret &= WM5100_SPK_SHUTDOWN_WARN_STS | + WM5100_SPK_SHUTDOWN_STS | WM5100_CLKGEN_ERR_STS | + WM5100_CLKGEN_ERR_ASYNC_STS; + wm5100_log_status3(wm5100, ret); + + ret = snd_soc_component_read32(component, WM5100_INTERRUPT_RAW_STATUS_4); + wm5100_log_status4(wm5100, ret); + + return 0; +} + +static const struct snd_soc_dapm_widget wm5100_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", WM5100_CLOCKING_3, WM5100_SYSCLK_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_SUPPLY("ASYNCCLK", WM5100_CLOCKING_6, WM5100_ASYNC_CLK_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD2", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD3", 0, 0), + +SND_SOC_DAPM_SUPPLY("CP1", WM5100_HP_CHARGE_PUMP_1, WM5100_CP1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_SUPPLY("CP2", WM5100_MIC_CHARGE_PUMP_1, WM5100_CP2_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_SUPPLY("CP2 Active", WM5100_MIC_CHARGE_PUMP_1, + WM5100_CP2_BYPASS_SHIFT, 1, NULL, 0), + +SND_SOC_DAPM_SUPPLY("MICBIAS1", WM5100_MIC_BIAS_CTRL_1, WM5100_MICB1_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", WM5100_MIC_BIAS_CTRL_2, WM5100_MICB2_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS3", WM5100_MIC_BIAS_CTRL_3, WM5100_MICB3_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), +SND_SOC_DAPM_INPUT("IN4L"), +SND_SOC_DAPM_INPUT("IN4R"), +SND_SOC_DAPM_SIGGEN("TONE"), + +SND_SOC_DAPM_PGA_E("IN1L PGA", WM5100_INPUT_ENABLES, WM5100_IN1L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN1R PGA", WM5100_INPUT_ENABLES, WM5100_IN1R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2L PGA", WM5100_INPUT_ENABLES, WM5100_IN2L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2R PGA", WM5100_INPUT_ENABLES, WM5100_IN2R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN3L PGA", WM5100_INPUT_ENABLES, WM5100_IN3L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN3R PGA", WM5100_INPUT_ENABLES, WM5100_IN3R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN4L PGA", WM5100_INPUT_ENABLES, WM5100_IN4L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN4R PGA", WM5100_INPUT_ENABLES, WM5100_IN4R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_PGA("Tone Generator 1", WM5100_TONE_GENERATOR_1, + WM5100_TONE1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("Tone Generator 2", WM5100_TONE_GENERATOR_1, + WM5100_TONE2_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX1", "AIF1 Playback", 0, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", "AIF1 Playback", 1, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", "AIF1 Playback", 2, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", "AIF1 Playback", 3, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX5", "AIF1 Playback", 4, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX6", "AIF1 Playback", 5, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX7", "AIF1 Playback", 6, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX8", "AIF1 Playback", 7, + WM5100_AUDIO_IF_1_27, WM5100_AIF1RX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF2RX1", "AIF2 Playback", 0, + WM5100_AUDIO_IF_2_27, WM5100_AIF2RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX2", "AIF2 Playback", 1, + WM5100_AUDIO_IF_2_27, WM5100_AIF2RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF3RX1", "AIF3 Playback", 0, + WM5100_AUDIO_IF_3_27, WM5100_AIF3RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF3RX2", "AIF3 Playback", 1, + WM5100_AUDIO_IF_3_27, WM5100_AIF3RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF1TX1", "AIF1 Capture", 0, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", "AIF1 Capture", 1, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", "AIF1 Capture", 2, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", "AIF1 Capture", 3, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX5", "AIF1 Capture", 4, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX6", "AIF1 Capture", 5, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX7", "AIF1 Capture", 6, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX8", "AIF1 Capture", 7, + WM5100_AUDIO_IF_1_26, WM5100_AIF1TX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF2TX1", "AIF2 Capture", 0, + WM5100_AUDIO_IF_2_26, WM5100_AIF2TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX2", "AIF2 Capture", 1, + WM5100_AUDIO_IF_2_26, WM5100_AIF2TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF3TX1", "AIF3 Capture", 0, + WM5100_AUDIO_IF_3_26, WM5100_AIF3TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF3TX2", "AIF3 Capture", 1, + WM5100_AUDIO_IF_3_26, WM5100_AIF3TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_PGA_E("OUT6L", WM5100_OUTPUT_ENABLES_2, WM5100_OUT6L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT6R", WM5100_OUTPUT_ENABLES_2, WM5100_OUT6R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5L", WM5100_OUTPUT_ENABLES_2, WM5100_OUT5L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5R", WM5100_OUTPUT_ENABLES_2, WM5100_OUT5R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT4L", WM5100_OUTPUT_ENABLES_2, WM5100_OUT4L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT4R", WM5100_OUTPUT_ENABLES_2, WM5100_OUT4R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3L", WM5100_CHANNEL_ENABLES_1, WM5100_HP3L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3R", WM5100_CHANNEL_ENABLES_1, WM5100_HP3R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2L", WM5100_CHANNEL_ENABLES_1, WM5100_HP2L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2R", WM5100_CHANNEL_ENABLES_1, WM5100_HP2R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT1L", WM5100_CHANNEL_ENABLES_1, WM5100_HP1L_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT1R", WM5100_CHANNEL_ENABLES_1, WM5100_HP1R_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("PWM1 Driver", WM5100_PWM_DRIVE_1, WM5100_PWM1_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("PWM2 Driver", WM5100_PWM_DRIVE_1, WM5100_PWM2_ENA_SHIFT, 0, + NULL, 0, wm5100_out_ev, SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_PGA("EQ1", WM5100_EQ1_1, WM5100_EQ1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ2", WM5100_EQ2_1, WM5100_EQ2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ3", WM5100_EQ3_1, WM5100_EQ3_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ4", WM5100_EQ4_1, WM5100_EQ4_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("DRC1L", WM5100_DRC1_CTRL1, WM5100_DRCL_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC1R", WM5100_DRC1_CTRL1, WM5100_DRCR_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", WM5100_HPLPF1_1, WM5100_LHPF1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", WM5100_HPLPF2_1, WM5100_LHPF2_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF3", WM5100_HPLPF3_1, WM5100_LHPF3_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF4", WM5100_HPLPF4_1, WM5100_LHPF4_ENA_SHIFT, 0, + NULL, 0), + +WM5100_MIXER_WIDGETS(EQ1, "EQ1"), +WM5100_MIXER_WIDGETS(EQ2, "EQ2"), +WM5100_MIXER_WIDGETS(EQ3, "EQ3"), +WM5100_MIXER_WIDGETS(EQ4, "EQ4"), + +WM5100_MIXER_WIDGETS(DRC1L, "DRC1L"), +WM5100_MIXER_WIDGETS(DRC1R, "DRC1R"), + +WM5100_MIXER_WIDGETS(LHPF1, "LHPF1"), +WM5100_MIXER_WIDGETS(LHPF2, "LHPF2"), +WM5100_MIXER_WIDGETS(LHPF3, "LHPF3"), +WM5100_MIXER_WIDGETS(LHPF4, "LHPF4"), + +WM5100_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"), +WM5100_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"), +WM5100_MIXER_WIDGETS(AIF1TX3, "AIF1TX3"), +WM5100_MIXER_WIDGETS(AIF1TX4, "AIF1TX4"), +WM5100_MIXER_WIDGETS(AIF1TX5, "AIF1TX5"), +WM5100_MIXER_WIDGETS(AIF1TX6, "AIF1TX6"), +WM5100_MIXER_WIDGETS(AIF1TX7, "AIF1TX7"), +WM5100_MIXER_WIDGETS(AIF1TX8, "AIF1TX8"), + +WM5100_MIXER_WIDGETS(AIF2TX1, "AIF2TX1"), +WM5100_MIXER_WIDGETS(AIF2TX2, "AIF2TX2"), + +WM5100_MIXER_WIDGETS(AIF3TX1, "AIF3TX1"), +WM5100_MIXER_WIDGETS(AIF3TX2, "AIF3TX2"), + +WM5100_MIXER_WIDGETS(HPOUT1L, "HPOUT1L"), +WM5100_MIXER_WIDGETS(HPOUT1R, "HPOUT1R"), +WM5100_MIXER_WIDGETS(HPOUT2L, "HPOUT2L"), +WM5100_MIXER_WIDGETS(HPOUT2R, "HPOUT2R"), +WM5100_MIXER_WIDGETS(HPOUT3L, "HPOUT3L"), +WM5100_MIXER_WIDGETS(HPOUT3R, "HPOUT3R"), + +WM5100_MIXER_WIDGETS(SPKOUTL, "SPKOUTL"), +WM5100_MIXER_WIDGETS(SPKOUTR, "SPKOUTR"), +WM5100_MIXER_WIDGETS(SPKDAT1L, "SPKDAT1L"), +WM5100_MIXER_WIDGETS(SPKDAT1R, "SPKDAT1R"), +WM5100_MIXER_WIDGETS(SPKDAT2L, "SPKDAT2L"), +WM5100_MIXER_WIDGETS(SPKDAT2R, "SPKDAT2R"), + +WM5100_MIXER_WIDGETS(PWM1, "PWM1"), +WM5100_MIXER_WIDGETS(PWM2, "PWM2"), + +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("HPOUT2L"), +SND_SOC_DAPM_OUTPUT("HPOUT2R"), +SND_SOC_DAPM_OUTPUT("HPOUT3L"), +SND_SOC_DAPM_OUTPUT("HPOUT3R"), +SND_SOC_DAPM_OUTPUT("SPKOUTL"), +SND_SOC_DAPM_OUTPUT("SPKOUTR"), +SND_SOC_DAPM_OUTPUT("SPKDAT1"), +SND_SOC_DAPM_OUTPUT("SPKDAT2"), +SND_SOC_DAPM_OUTPUT("PWM1"), +SND_SOC_DAPM_OUTPUT("PWM2"), +}; + +/* We register a _POST event if we don't have IRQ support so we can + * look at the error status from the CODEC - if we've got the IRQ + * hooked up then we will get prompted to look by an interrupt. + */ +static const struct snd_soc_dapm_widget wm5100_dapm_widgets_noirq[] = { +SND_SOC_DAPM_POST("Post", wm5100_post_ev), +}; + +static const struct snd_soc_dapm_route wm5100_dapm_routes[] = { + { "CP1", NULL, "CPVDD" }, + { "CP2 Active", NULL, "CPVDD" }, + + { "IN1L", NULL, "SYSCLK" }, + { "IN1R", NULL, "SYSCLK" }, + { "IN2L", NULL, "SYSCLK" }, + { "IN2R", NULL, "SYSCLK" }, + { "IN3L", NULL, "SYSCLK" }, + { "IN3R", NULL, "SYSCLK" }, + { "IN4L", NULL, "SYSCLK" }, + { "IN4R", NULL, "SYSCLK" }, + + { "OUT1L", NULL, "SYSCLK" }, + { "OUT1R", NULL, "SYSCLK" }, + { "OUT2L", NULL, "SYSCLK" }, + { "OUT2R", NULL, "SYSCLK" }, + { "OUT3L", NULL, "SYSCLK" }, + { "OUT3R", NULL, "SYSCLK" }, + { "OUT4L", NULL, "SYSCLK" }, + { "OUT4R", NULL, "SYSCLK" }, + { "OUT5L", NULL, "SYSCLK" }, + { "OUT5R", NULL, "SYSCLK" }, + { "OUT6L", NULL, "SYSCLK" }, + { "OUT6R", NULL, "SYSCLK" }, + + { "AIF1RX1", NULL, "SYSCLK" }, + { "AIF1RX2", NULL, "SYSCLK" }, + { "AIF1RX3", NULL, "SYSCLK" }, + { "AIF1RX4", NULL, "SYSCLK" }, + { "AIF1RX5", NULL, "SYSCLK" }, + { "AIF1RX6", NULL, "SYSCLK" }, + { "AIF1RX7", NULL, "SYSCLK" }, + { "AIF1RX8", NULL, "SYSCLK" }, + + { "AIF2RX1", NULL, "SYSCLK" }, + { "AIF2RX1", NULL, "DBVDD2" }, + { "AIF2RX2", NULL, "SYSCLK" }, + { "AIF2RX2", NULL, "DBVDD2" }, + + { "AIF3RX1", NULL, "SYSCLK" }, + { "AIF3RX1", NULL, "DBVDD3" }, + { "AIF3RX2", NULL, "SYSCLK" }, + { "AIF3RX2", NULL, "DBVDD3" }, + + { "AIF1TX1", NULL, "SYSCLK" }, + { "AIF1TX2", NULL, "SYSCLK" }, + { "AIF1TX3", NULL, "SYSCLK" }, + { "AIF1TX4", NULL, "SYSCLK" }, + { "AIF1TX5", NULL, "SYSCLK" }, + { "AIF1TX6", NULL, "SYSCLK" }, + { "AIF1TX7", NULL, "SYSCLK" }, + { "AIF1TX8", NULL, "SYSCLK" }, + + { "AIF2TX1", NULL, "SYSCLK" }, + { "AIF2TX1", NULL, "DBVDD2" }, + { "AIF2TX2", NULL, "SYSCLK" }, + { "AIF2TX2", NULL, "DBVDD2" }, + + { "AIF3TX1", NULL, "SYSCLK" }, + { "AIF3TX1", NULL, "DBVDD3" }, + { "AIF3TX2", NULL, "SYSCLK" }, + { "AIF3TX2", NULL, "DBVDD3" }, + + { "MICBIAS1", NULL, "CP2" }, + { "MICBIAS2", NULL, "CP2" }, + { "MICBIAS3", NULL, "CP2" }, + + { "IN1L PGA", NULL, "CP2" }, + { "IN1R PGA", NULL, "CP2" }, + { "IN2L PGA", NULL, "CP2" }, + { "IN2R PGA", NULL, "CP2" }, + { "IN3L PGA", NULL, "CP2" }, + { "IN3R PGA", NULL, "CP2" }, + { "IN4L PGA", NULL, "CP2" }, + { "IN4R PGA", NULL, "CP2" }, + + { "IN1L PGA", NULL, "CP2 Active" }, + { "IN1R PGA", NULL, "CP2 Active" }, + { "IN2L PGA", NULL, "CP2 Active" }, + { "IN2R PGA", NULL, "CP2 Active" }, + { "IN3L PGA", NULL, "CP2 Active" }, + { "IN3R PGA", NULL, "CP2 Active" }, + { "IN4L PGA", NULL, "CP2 Active" }, + { "IN4R PGA", NULL, "CP2 Active" }, + + { "OUT1L", NULL, "CP1" }, + { "OUT1R", NULL, "CP1" }, + { "OUT2L", NULL, "CP1" }, + { "OUT2R", NULL, "CP1" }, + { "OUT3L", NULL, "CP1" }, + { "OUT3R", NULL, "CP1" }, + + { "Tone Generator 1", NULL, "TONE" }, + { "Tone Generator 2", NULL, "TONE" }, + + { "IN1L PGA", NULL, "IN1L" }, + { "IN1R PGA", NULL, "IN1R" }, + { "IN2L PGA", NULL, "IN2L" }, + { "IN2R PGA", NULL, "IN2R" }, + { "IN3L PGA", NULL, "IN3L" }, + { "IN3R PGA", NULL, "IN3R" }, + { "IN4L PGA", NULL, "IN4L" }, + { "IN4R PGA", NULL, "IN4R" }, + + WM5100_MIXER_ROUTES("OUT1L", "HPOUT1L"), + WM5100_MIXER_ROUTES("OUT1R", "HPOUT1R"), + WM5100_MIXER_ROUTES("OUT2L", "HPOUT2L"), + WM5100_MIXER_ROUTES("OUT2R", "HPOUT2R"), + WM5100_MIXER_ROUTES("OUT3L", "HPOUT3L"), + WM5100_MIXER_ROUTES("OUT3R", "HPOUT3R"), + + WM5100_MIXER_ROUTES("OUT4L", "SPKOUTL"), + WM5100_MIXER_ROUTES("OUT4R", "SPKOUTR"), + WM5100_MIXER_ROUTES("OUT5L", "SPKDAT1L"), + WM5100_MIXER_ROUTES("OUT5R", "SPKDAT1R"), + WM5100_MIXER_ROUTES("OUT6L", "SPKDAT2L"), + WM5100_MIXER_ROUTES("OUT6R", "SPKDAT2R"), + + WM5100_MIXER_ROUTES("PWM1 Driver", "PWM1"), + WM5100_MIXER_ROUTES("PWM2 Driver", "PWM2"), + + WM5100_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), + WM5100_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), + WM5100_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), + WM5100_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), + WM5100_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), + WM5100_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), + WM5100_MIXER_ROUTES("AIF1TX7", "AIF1TX7"), + WM5100_MIXER_ROUTES("AIF1TX8", "AIF1TX8"), + + WM5100_MIXER_ROUTES("AIF2TX1", "AIF2TX1"), + WM5100_MIXER_ROUTES("AIF2TX2", "AIF2TX2"), + + WM5100_MIXER_ROUTES("AIF3TX1", "AIF3TX1"), + WM5100_MIXER_ROUTES("AIF3TX2", "AIF3TX2"), + + WM5100_MIXER_ROUTES("EQ1", "EQ1"), + WM5100_MIXER_ROUTES("EQ2", "EQ2"), + WM5100_MIXER_ROUTES("EQ3", "EQ3"), + WM5100_MIXER_ROUTES("EQ4", "EQ4"), + + WM5100_MIXER_ROUTES("DRC1L", "DRC1L"), + WM5100_MIXER_ROUTES("DRC1R", "DRC1R"), + + WM5100_MIXER_ROUTES("LHPF1", "LHPF1"), + WM5100_MIXER_ROUTES("LHPF2", "LHPF2"), + WM5100_MIXER_ROUTES("LHPF3", "LHPF3"), + WM5100_MIXER_ROUTES("LHPF4", "LHPF4"), + + { "HPOUT1L", NULL, "OUT1L" }, + { "HPOUT1R", NULL, "OUT1R" }, + { "HPOUT2L", NULL, "OUT2L" }, + { "HPOUT2R", NULL, "OUT2R" }, + { "HPOUT3L", NULL, "OUT3L" }, + { "HPOUT3R", NULL, "OUT3R" }, + { "SPKOUTL", NULL, "OUT4L" }, + { "SPKOUTR", NULL, "OUT4R" }, + { "SPKDAT1", NULL, "OUT5L" }, + { "SPKDAT1", NULL, "OUT5R" }, + { "SPKDAT2", NULL, "OUT6L" }, + { "SPKDAT2", NULL, "OUT6R" }, + { "PWM1", NULL, "PWM1 Driver" }, + { "PWM2", NULL, "PWM2 Driver" }, +}; + +static const struct reg_sequence wm5100_reva_patches[] = { + { WM5100_AUDIO_IF_1_10, 0 }, + { WM5100_AUDIO_IF_1_11, 1 }, + { WM5100_AUDIO_IF_1_12, 2 }, + { WM5100_AUDIO_IF_1_13, 3 }, + { WM5100_AUDIO_IF_1_14, 4 }, + { WM5100_AUDIO_IF_1_15, 5 }, + { WM5100_AUDIO_IF_1_16, 6 }, + { WM5100_AUDIO_IF_1_17, 7 }, + + { WM5100_AUDIO_IF_1_18, 0 }, + { WM5100_AUDIO_IF_1_19, 1 }, + { WM5100_AUDIO_IF_1_20, 2 }, + { WM5100_AUDIO_IF_1_21, 3 }, + { WM5100_AUDIO_IF_1_22, 4 }, + { WM5100_AUDIO_IF_1_23, 5 }, + { WM5100_AUDIO_IF_1_24, 6 }, + { WM5100_AUDIO_IF_1_25, 7 }, + + { WM5100_AUDIO_IF_2_10, 0 }, + { WM5100_AUDIO_IF_2_11, 1 }, + + { WM5100_AUDIO_IF_2_18, 0 }, + { WM5100_AUDIO_IF_2_19, 1 }, + + { WM5100_AUDIO_IF_3_10, 0 }, + { WM5100_AUDIO_IF_3_11, 1 }, + + { WM5100_AUDIO_IF_3_18, 0 }, + { WM5100_AUDIO_IF_3_19, 1 }, +}; + +static int wm5100_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + int lrclk, bclk, mask, base; + + base = dai->driver->base; + + lrclk = 0; + bclk = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + mask = 0; + break; + case SND_SOC_DAIFMT_I2S: + mask = 2; + break; + default: + dev_err(component->dev, "Unsupported DAI format %d\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + lrclk |= WM5100_AIF1TX_LRCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + bclk |= WM5100_AIF1_BCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + lrclk |= WM5100_AIF1TX_LRCLK_MSTR; + bclk |= WM5100_AIF1_BCLK_MSTR; + break; + default: + dev_err(component->dev, "Unsupported master mode %d\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + bclk |= WM5100_AIF1_BCLK_INV; + lrclk |= WM5100_AIF1TX_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + bclk |= WM5100_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + lrclk |= WM5100_AIF1TX_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, base + 1, WM5100_AIF1_BCLK_MSTR | + WM5100_AIF1_BCLK_INV, bclk); + snd_soc_component_update_bits(component, base + 2, WM5100_AIF1TX_LRCLK_MSTR | + WM5100_AIF1TX_LRCLK_INV, lrclk); + snd_soc_component_update_bits(component, base + 3, WM5100_AIF1TX_LRCLK_MSTR | + WM5100_AIF1TX_LRCLK_INV, lrclk); + snd_soc_component_update_bits(component, base + 5, WM5100_AIF1_FMT_MASK, mask); + + return 0; +} + +#define WM5100_NUM_BCLK_RATES 19 + +static int wm5100_bclk_rates_dat[WM5100_NUM_BCLK_RATES] = { + 32000, + 48000, + 64000, + 96000, + 128000, + 192000, + 256000, + 384000, + 512000, + 768000, + 1024000, + 1536000, + 2048000, + 3072000, + 4096000, + 6144000, + 8192000, + 12288000, + 24576000, +}; + +static int wm5100_bclk_rates_cd[WM5100_NUM_BCLK_RATES] = { + 29400, + 44100, + 58800, + 88200, + 117600, + 176400, + 235200, + 352800, + 470400, + 705600, + 940800, + 1411200, + 1881600, + 2882400, + 3763200, + 5644800, + 7526400, + 11289600, + 22579600, +}; + +static int wm5100_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + bool async = wm5100->aif_async[dai->id]; + int i, base, bclk, aif_rate, lrclk, wl, fl, sr; + int *bclk_rates; + + base = dai->driver->base; + + /* Data sizes if not using TDM */ + wl = params_width(params); + if (wl < 0) + return wl; + fl = snd_soc_params_to_frame_size(params); + if (fl < 0) + return fl; + + dev_dbg(component->dev, "Word length %d bits, frame length %d bits\n", + wl, fl); + + /* Target BCLK rate */ + bclk = snd_soc_params_to_bclk(params); + if (bclk < 0) + return bclk; + + /* Root for BCLK depends on SYS/ASYNCCLK */ + if (!async) { + aif_rate = wm5100->sysclk; + sr = wm5100_alloc_sr(component, params_rate(params)); + if (sr < 0) + return sr; + } else { + /* If we're in ASYNCCLK set the ASYNC sample rate */ + aif_rate = wm5100->asyncclk; + sr = 3; + + for (i = 0; i < ARRAY_SIZE(wm5100_sr_code); i++) + if (params_rate(params) == wm5100_sr_code[i]) + break; + if (i == ARRAY_SIZE(wm5100_sr_code)) { + dev_err(component->dev, "Invalid rate %dHzn", + params_rate(params)); + return -EINVAL; + } + + /* TODO: We should really check for symmetry */ + snd_soc_component_update_bits(component, WM5100_CLOCKING_8, + WM5100_ASYNC_SAMPLE_RATE_MASK, i); + } + + if (!aif_rate) { + dev_err(component->dev, "%s has no rate set\n", + async ? "ASYNCCLK" : "SYSCLK"); + return -EINVAL; + } + + dev_dbg(component->dev, "Target BCLK is %dHz, using %dHz %s\n", + bclk, aif_rate, async ? "ASYNCCLK" : "SYSCLK"); + + if (aif_rate % 4000) + bclk_rates = wm5100_bclk_rates_cd; + else + bclk_rates = wm5100_bclk_rates_dat; + + for (i = 0; i < WM5100_NUM_BCLK_RATES; i++) + if (bclk_rates[i] >= bclk && (bclk_rates[i] % bclk == 0)) + break; + if (i == WM5100_NUM_BCLK_RATES) { + dev_err(component->dev, + "No valid BCLK for %dHz found from %dHz %s\n", + bclk, aif_rate, async ? "ASYNCCLK" : "SYSCLK"); + return -EINVAL; + } + + bclk = i; + dev_dbg(component->dev, "Setting %dHz BCLK\n", bclk_rates[bclk]); + snd_soc_component_update_bits(component, base + 1, WM5100_AIF1_BCLK_FREQ_MASK, bclk); + + lrclk = bclk_rates[bclk] / params_rate(params); + dev_dbg(component->dev, "Setting %dHz LRCLK\n", bclk_rates[bclk] / lrclk); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + wm5100->aif_symmetric[dai->id]) + snd_soc_component_update_bits(component, base + 7, + WM5100_AIF1RX_BCPF_MASK, lrclk); + else + snd_soc_component_update_bits(component, base + 6, + WM5100_AIF1TX_BCPF_MASK, lrclk); + + i = (wl << WM5100_AIF1TX_WL_SHIFT) | fl; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_component_update_bits(component, base + 9, + WM5100_AIF1RX_WL_MASK | + WM5100_AIF1RX_SLOT_LEN_MASK, i); + else + snd_soc_component_update_bits(component, base + 8, + WM5100_AIF1TX_WL_MASK | + WM5100_AIF1TX_SLOT_LEN_MASK, i); + + snd_soc_component_update_bits(component, base + 4, WM5100_AIF1_RATE_MASK, sr); + + return 0; +} + +static const struct snd_soc_dai_ops wm5100_dai_ops = { + .set_fmt = wm5100_set_fmt, + .hw_params = wm5100_hw_params, +}; + +static int wm5100_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + int *rate_store; + int fval, audio_rate, ret, reg; + + switch (clk_id) { + case WM5100_CLK_SYSCLK: + reg = WM5100_CLOCKING_3; + rate_store = &wm5100->sysclk; + break; + case WM5100_CLK_ASYNCCLK: + reg = WM5100_CLOCKING_7; + rate_store = &wm5100->asyncclk; + break; + case WM5100_CLK_32KHZ: + /* The 32kHz clock is slightly different to the others */ + switch (source) { + case WM5100_CLKSRC_MCLK1: + case WM5100_CLKSRC_MCLK2: + case WM5100_CLKSRC_SYSCLK: + snd_soc_component_update_bits(component, WM5100_CLOCKING_1, + WM5100_CLK_32K_SRC_MASK, + source); + break; + default: + return -EINVAL; + } + return 0; + + case WM5100_CLK_AIF1: + case WM5100_CLK_AIF2: + case WM5100_CLK_AIF3: + /* Not real clocks, record which clock domain they're in */ + switch (source) { + case WM5100_CLKSRC_SYSCLK: + wm5100->aif_async[clk_id - 1] = false; + break; + case WM5100_CLKSRC_ASYNCCLK: + wm5100->aif_async[clk_id - 1] = true; + break; + default: + dev_err(component->dev, "Invalid source %d\n", source); + return -EINVAL; + } + return 0; + + case WM5100_CLK_OPCLK: + switch (freq) { + case 5644800: + case 6144000: + snd_soc_component_update_bits(component, WM5100_MISC_GPIO_1, + WM5100_OPCLK_SEL_MASK, 0); + break; + case 11289600: + case 12288000: + snd_soc_component_update_bits(component, WM5100_MISC_GPIO_1, + WM5100_OPCLK_SEL_MASK, 0); + break; + case 22579200: + case 24576000: + snd_soc_component_update_bits(component, WM5100_MISC_GPIO_1, + WM5100_OPCLK_SEL_MASK, 0); + break; + default: + dev_err(component->dev, "Unsupported OPCLK %dHz\n", + freq); + return -EINVAL; + } + return 0; + + default: + dev_err(component->dev, "Unknown clock %d\n", clk_id); + return -EINVAL; + } + + switch (source) { + case WM5100_CLKSRC_SYSCLK: + case WM5100_CLKSRC_ASYNCCLK: + dev_err(component->dev, "Invalid source %d\n", source); + return -EINVAL; + } + + switch (freq) { + case 5644800: + case 6144000: + fval = 0; + break; + case 11289600: + case 12288000: + fval = 1; + break; + case 22579200: + case 24576000: + fval = 2; + break; + default: + dev_err(component->dev, "Invalid clock rate: %d\n", freq); + return -EINVAL; + } + + switch (freq) { + case 5644800: + case 11289600: + case 22579200: + audio_rate = 44100; + break; + + case 6144000: + case 12288000: + case 24576000: + audio_rate = 48000; + break; + + default: + BUG(); + audio_rate = 0; + break; + } + + /* TODO: Check if MCLKs are in use and enable/disable pulls to + * match. + */ + + snd_soc_component_update_bits(component, reg, WM5100_SYSCLK_FREQ_MASK | + WM5100_SYSCLK_SRC_MASK, + fval << WM5100_SYSCLK_FREQ_SHIFT | source); + + /* If this is SYSCLK then configure the clock rate for the + * internal audio functions to the natural sample rate for + * this clock rate. + */ + if (clk_id == WM5100_CLK_SYSCLK) { + dev_dbg(component->dev, "Setting primary audio rate to %dHz", + audio_rate); + if (0 && *rate_store) + wm5100_free_sr(component, audio_rate); + ret = wm5100_alloc_sr(component, audio_rate); + if (ret != 0) + dev_warn(component->dev, "Primary audio slot is %d\n", + ret); + } + + *rate_store = freq; + + return 0; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_refclk_div; + u16 n; + u16 theta; + u16 lambda; +}; + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + unsigned int target; + unsigned int div; + unsigned int fratio, gcd_fll; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + fll_div->fll_refclk_div = 0; + while ((Fref / div) > 13500000) { + div *= 2; + fll_div->fll_refclk_div++; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("FLL Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 2; + while (Fout * div < 90000000) { + div++; + if (div > 64) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * div; + fll_div->fll_outdiv = div - 1; + + pr_debug("FLL Fvco=%dHz\n", target); + + /* Find an appropraite FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + fratio = fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + fll_div->n = target / (fratio * Fref); + + if (target % Fref == 0) { + fll_div->theta = 0; + fll_div->lambda = 0; + } else { + gcd_fll = gcd(target, fratio * Fref); + + fll_div->theta = (target - (fll_div->n * fratio * Fref)) + / gcd_fll; + fll_div->lambda = (fratio * Fref) / gcd_fll; + } + + pr_debug("FLL N=%x THETA=%x LAMBDA=%x\n", + fll_div->n, fll_div->theta, fll_div->lambda); + pr_debug("FLL_FRATIO=%x(%d) FLL_OUTDIV=%x FLL_REFCLK_DIV=%x\n", + fll_div->fll_fratio, fratio, fll_div->fll_outdiv, + fll_div->fll_refclk_div); + + return 0; +} + +static int wm5100_set_fll(struct snd_soc_component *component, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + struct i2c_client *i2c = to_i2c_client(component->dev); + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + struct _fll_div factors; + struct wm5100_fll *fll; + int ret, base, lock, i, timeout; + unsigned long time_left; + + switch (fll_id) { + case WM5100_FLL1: + fll = &wm5100->fll[0]; + base = WM5100_FLL1_CONTROL_1 - 1; + lock = WM5100_FLL1_LOCK_STS; + break; + case WM5100_FLL2: + fll = &wm5100->fll[1]; + base = WM5100_FLL2_CONTROL_2 - 1; + lock = WM5100_FLL2_LOCK_STS; + break; + default: + dev_err(component->dev, "Unknown FLL %d\n",fll_id); + return -EINVAL; + } + + if (!Fout) { + dev_dbg(component->dev, "FLL%d disabled", fll_id); + if (fll->fout) + pm_runtime_put(component->dev); + fll->fout = 0; + snd_soc_component_update_bits(component, base + 1, WM5100_FLL1_ENA, 0); + return 0; + } + + switch (source) { + case WM5100_FLL_SRC_MCLK1: + case WM5100_FLL_SRC_MCLK2: + case WM5100_FLL_SRC_FLL1: + case WM5100_FLL_SRC_FLL2: + case WM5100_FLL_SRC_AIF1BCLK: + case WM5100_FLL_SRC_AIF2BCLK: + case WM5100_FLL_SRC_AIF3BCLK: + break; + default: + dev_err(component->dev, "Invalid FLL source %d\n", source); + return -EINVAL; + } + + ret = fll_factors(&factors, Fref, Fout); + if (ret < 0) + return ret; + + /* Disable the FLL while we reconfigure */ + snd_soc_component_update_bits(component, base + 1, WM5100_FLL1_ENA, 0); + + snd_soc_component_update_bits(component, base + 2, + WM5100_FLL1_OUTDIV_MASK | WM5100_FLL1_FRATIO_MASK, + (factors.fll_outdiv << WM5100_FLL1_OUTDIV_SHIFT) | + factors.fll_fratio); + snd_soc_component_update_bits(component, base + 3, WM5100_FLL1_THETA_MASK, + factors.theta); + snd_soc_component_update_bits(component, base + 5, WM5100_FLL1_N_MASK, factors.n); + snd_soc_component_update_bits(component, base + 6, + WM5100_FLL1_REFCLK_DIV_MASK | + WM5100_FLL1_REFCLK_SRC_MASK, + (factors.fll_refclk_div + << WM5100_FLL1_REFCLK_DIV_SHIFT) | source); + snd_soc_component_update_bits(component, base + 7, WM5100_FLL1_LAMBDA_MASK, + factors.lambda); + + /* Clear any pending completions */ + try_wait_for_completion(&fll->lock); + + pm_runtime_get_sync(component->dev); + + snd_soc_component_update_bits(component, base + 1, WM5100_FLL1_ENA, WM5100_FLL1_ENA); + + if (i2c->irq) + timeout = 2; + else + timeout = 50; + + snd_soc_component_update_bits(component, WM5100_CLOCKING_3, WM5100_SYSCLK_ENA, + WM5100_SYSCLK_ENA); + + /* Poll for the lock; will use interrupt when we can test */ + for (i = 0; i < timeout; i++) { + if (i2c->irq) { + time_left = wait_for_completion_timeout(&fll->lock, + msecs_to_jiffies(25)); + if (time_left > 0) + break; + } else { + msleep(1); + } + + ret = snd_soc_component_read32(component, + WM5100_INTERRUPT_RAW_STATUS_3); + if (ret < 0) { + dev_err(component->dev, + "Failed to read FLL status: %d\n", + ret); + continue; + } + if (ret & lock) + break; + } + if (i == timeout) { + dev_err(component->dev, "FLL%d lock timed out\n", fll_id); + pm_runtime_put(component->dev); + return -ETIMEDOUT; + } + + fll->src = source; + fll->fref = Fref; + fll->fout = Fout; + + dev_dbg(component->dev, "FLL%d running %dHz->%dHz\n", fll_id, + Fref, Fout); + + return 0; +} + +/* Actually go much higher */ +#define WM5100_RATES SNDRV_PCM_RATE_8000_192000 + +#define WM5100_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm5100_dai[] = { + { + .name = "wm5100-aif1", + .base = WM5100_AUDIO_IF_1_1 - 1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM5100_RATES, + .formats = WM5100_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM5100_RATES, + .formats = WM5100_FORMATS, + }, + .ops = &wm5100_dai_ops, + }, + { + .name = "wm5100-aif2", + .id = 1, + .base = WM5100_AUDIO_IF_2_1 - 1, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM5100_RATES, + .formats = WM5100_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM5100_RATES, + .formats = WM5100_FORMATS, + }, + .ops = &wm5100_dai_ops, + }, + { + .name = "wm5100-aif3", + .id = 2, + .base = WM5100_AUDIO_IF_3_1 - 1, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM5100_RATES, + .formats = WM5100_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM5100_RATES, + .formats = WM5100_FORMATS, + }, + .ops = &wm5100_dai_ops, + }, +}; + +static int wm5100_dig_vu[] = { + WM5100_ADC_DIGITAL_VOLUME_1L, + WM5100_ADC_DIGITAL_VOLUME_1R, + WM5100_ADC_DIGITAL_VOLUME_2L, + WM5100_ADC_DIGITAL_VOLUME_2R, + WM5100_ADC_DIGITAL_VOLUME_3L, + WM5100_ADC_DIGITAL_VOLUME_3R, + WM5100_ADC_DIGITAL_VOLUME_4L, + WM5100_ADC_DIGITAL_VOLUME_4R, + + WM5100_DAC_DIGITAL_VOLUME_1L, + WM5100_DAC_DIGITAL_VOLUME_1R, + WM5100_DAC_DIGITAL_VOLUME_2L, + WM5100_DAC_DIGITAL_VOLUME_2R, + WM5100_DAC_DIGITAL_VOLUME_3L, + WM5100_DAC_DIGITAL_VOLUME_3R, + WM5100_DAC_DIGITAL_VOLUME_4L, + WM5100_DAC_DIGITAL_VOLUME_4R, + WM5100_DAC_DIGITAL_VOLUME_5L, + WM5100_DAC_DIGITAL_VOLUME_5R, + WM5100_DAC_DIGITAL_VOLUME_6L, + WM5100_DAC_DIGITAL_VOLUME_6R, +}; + +static void wm5100_set_detect_mode(struct wm5100_priv *wm5100, int the_mode) +{ + struct wm5100_jack_mode *mode = &wm5100->pdata.jack_modes[the_mode]; + + if (WARN_ON(the_mode >= ARRAY_SIZE(wm5100->pdata.jack_modes))) + return; + + gpio_set_value_cansleep(wm5100->pdata.hp_pol, mode->hp_pol); + regmap_update_bits(wm5100->regmap, WM5100_ACCESSORY_DETECT_MODE_1, + WM5100_ACCDET_BIAS_SRC_MASK | + WM5100_ACCDET_SRC, + (mode->bias << WM5100_ACCDET_BIAS_SRC_SHIFT) | + mode->micd_src << WM5100_ACCDET_SRC_SHIFT); + regmap_update_bits(wm5100->regmap, WM5100_MISC_CONTROL, + WM5100_HPCOM_SRC, + mode->micd_src << WM5100_HPCOM_SRC_SHIFT); + + wm5100->jack_mode = the_mode; + + dev_dbg(wm5100->dev, "Set microphone polarity to %d\n", + wm5100->jack_mode); +} + +static void wm5100_report_headphone(struct wm5100_priv *wm5100) +{ + dev_dbg(wm5100->dev, "Headphone detected\n"); + wm5100->jack_detecting = false; + snd_soc_jack_report(wm5100->jack, SND_JACK_HEADPHONE, + SND_JACK_HEADPHONE); + + /* Increase the detection rate a bit for responsiveness. */ + regmap_update_bits(wm5100->regmap, WM5100_MIC_DETECT_1, + WM5100_ACCDET_RATE_MASK, + 7 << WM5100_ACCDET_RATE_SHIFT); +} + +static void wm5100_micd_irq(struct wm5100_priv *wm5100) +{ + unsigned int val; + int ret; + + ret = regmap_read(wm5100->regmap, WM5100_MIC_DETECT_3, &val); + if (ret != 0) { + dev_err(wm5100->dev, "Failed to read microphone status: %d\n", + ret); + return; + } + + dev_dbg(wm5100->dev, "Microphone event: %x\n", val); + + if (!(val & WM5100_ACCDET_VALID)) { + dev_warn(wm5100->dev, "Microphone detection state invalid\n"); + return; + } + + /* No accessory, reset everything and report removal */ + if (!(val & WM5100_ACCDET_STS)) { + dev_dbg(wm5100->dev, "Jack removal detected\n"); + wm5100->jack_mic = false; + wm5100->jack_detecting = true; + wm5100->jack_flips = 0; + snd_soc_jack_report(wm5100->jack, 0, + SND_JACK_LINEOUT | SND_JACK_HEADSET | + SND_JACK_BTN_0); + + regmap_update_bits(wm5100->regmap, WM5100_MIC_DETECT_1, + WM5100_ACCDET_RATE_MASK, + WM5100_ACCDET_RATE_MASK); + return; + } + + /* If the measurement is very high we've got a microphone, + * either we just detected one or if we already reported then + * we've got a button release event. + */ + if (val & 0x400) { + if (wm5100->jack_detecting) { + dev_dbg(wm5100->dev, "Microphone detected\n"); + wm5100->jack_mic = true; + wm5100->jack_detecting = false; + snd_soc_jack_report(wm5100->jack, + SND_JACK_HEADSET, + SND_JACK_HEADSET | SND_JACK_BTN_0); + + /* Increase poll rate to give better responsiveness + * for buttons */ + regmap_update_bits(wm5100->regmap, WM5100_MIC_DETECT_1, + WM5100_ACCDET_RATE_MASK, + 5 << WM5100_ACCDET_RATE_SHIFT); + } else { + dev_dbg(wm5100->dev, "Mic button up\n"); + snd_soc_jack_report(wm5100->jack, 0, SND_JACK_BTN_0); + } + + return; + } + + /* If we detected a lower impedence during initial startup + * then we probably have the wrong polarity, flip it. Don't + * do this for the lowest impedences to speed up detection of + * plain headphones and give up if neither polarity looks + * sensible. + */ + if (wm5100->jack_detecting && (val & 0x3f8)) { + wm5100->jack_flips++; + + if (wm5100->jack_flips > 1) + wm5100_report_headphone(wm5100); + else + wm5100_set_detect_mode(wm5100, !wm5100->jack_mode); + + return; + } + + /* Don't distinguish between buttons, just report any low + * impedence as BTN_0. + */ + if (val & 0x3fc) { + if (wm5100->jack_mic) { + dev_dbg(wm5100->dev, "Mic button detected\n"); + snd_soc_jack_report(wm5100->jack, SND_JACK_BTN_0, + SND_JACK_BTN_0); + } else if (wm5100->jack_detecting) { + wm5100_report_headphone(wm5100); + } + } +} + +int wm5100_detect(struct snd_soc_component *component, struct snd_soc_jack *jack) +{ + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + if (jack) { + wm5100->jack = jack; + wm5100->jack_detecting = true; + wm5100->jack_flips = 0; + + wm5100_set_detect_mode(wm5100, 0); + + /* Slowest detection rate, gives debounce for initial + * detection */ + snd_soc_component_update_bits(component, WM5100_MIC_DETECT_1, + WM5100_ACCDET_BIAS_STARTTIME_MASK | + WM5100_ACCDET_RATE_MASK, + (7 << WM5100_ACCDET_BIAS_STARTTIME_SHIFT) | + WM5100_ACCDET_RATE_MASK); + + /* We need the charge pump to power MICBIAS */ + snd_soc_dapm_mutex_lock(dapm); + + snd_soc_dapm_force_enable_pin_unlocked(dapm, "CP2"); + snd_soc_dapm_force_enable_pin_unlocked(dapm, "SYSCLK"); + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); + + /* We start off just enabling microphone detection - even a + * plain headphone will trigger detection. + */ + snd_soc_component_update_bits(component, WM5100_MIC_DETECT_1, + WM5100_ACCDET_ENA, WM5100_ACCDET_ENA); + + snd_soc_component_update_bits(component, WM5100_INTERRUPT_STATUS_3_MASK, + WM5100_IM_ACCDET_EINT, 0); + } else { + snd_soc_component_update_bits(component, WM5100_INTERRUPT_STATUS_3_MASK, + WM5100_IM_HPDET_EINT | + WM5100_IM_ACCDET_EINT, + WM5100_IM_HPDET_EINT | + WM5100_IM_ACCDET_EINT); + snd_soc_component_update_bits(component, WM5100_MIC_DETECT_1, + WM5100_ACCDET_ENA, 0); + wm5100->jack = NULL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm5100_detect); + +static irqreturn_t wm5100_irq(int irq, void *data) +{ + struct wm5100_priv *wm5100 = data; + irqreturn_t status = IRQ_NONE; + unsigned int irq_val, mask_val; + int ret; + + ret = regmap_read(wm5100->regmap, WM5100_INTERRUPT_STATUS_3, &irq_val); + if (ret < 0) { + dev_err(wm5100->dev, "Failed to read IRQ status 3: %d\n", + ret); + irq_val = 0; + } + + ret = regmap_read(wm5100->regmap, WM5100_INTERRUPT_STATUS_3_MASK, + &mask_val); + if (ret < 0) { + dev_err(wm5100->dev, "Failed to read IRQ mask 3: %d\n", + ret); + mask_val = 0xffff; + } + + irq_val &= ~mask_val; + + regmap_write(wm5100->regmap, WM5100_INTERRUPT_STATUS_3, irq_val); + + if (irq_val) + status = IRQ_HANDLED; + + wm5100_log_status3(wm5100, irq_val); + + if (irq_val & WM5100_FLL1_LOCK_EINT) { + dev_dbg(wm5100->dev, "FLL1 locked\n"); + complete(&wm5100->fll[0].lock); + } + if (irq_val & WM5100_FLL2_LOCK_EINT) { + dev_dbg(wm5100->dev, "FLL2 locked\n"); + complete(&wm5100->fll[1].lock); + } + + if (irq_val & WM5100_ACCDET_EINT) + wm5100_micd_irq(wm5100); + + ret = regmap_read(wm5100->regmap, WM5100_INTERRUPT_STATUS_4, &irq_val); + if (ret < 0) { + dev_err(wm5100->dev, "Failed to read IRQ status 4: %d\n", + ret); + irq_val = 0; + } + + ret = regmap_read(wm5100->regmap, WM5100_INTERRUPT_STATUS_4_MASK, + &mask_val); + if (ret < 0) { + dev_err(wm5100->dev, "Failed to read IRQ mask 4: %d\n", + ret); + mask_val = 0xffff; + } + + irq_val &= ~mask_val; + + if (irq_val) + status = IRQ_HANDLED; + + regmap_write(wm5100->regmap, WM5100_INTERRUPT_STATUS_4, irq_val); + + wm5100_log_status4(wm5100, irq_val); + + return status; +} + +static irqreturn_t wm5100_edge_irq(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + irqreturn_t val; + + do { + val = wm5100_irq(irq, data); + if (val != IRQ_NONE) + ret = val; + } while (val != IRQ_NONE); + + return ret; +} + +#ifdef CONFIG_GPIOLIB +static void wm5100_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm5100_priv *wm5100 = gpiochip_get_data(chip); + + regmap_update_bits(wm5100->regmap, WM5100_GPIO_CTRL_1 + offset, + WM5100_GP1_LVL, !!value << WM5100_GP1_LVL_SHIFT); +} + +static int wm5100_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm5100_priv *wm5100 = gpiochip_get_data(chip); + int val, ret; + + val = (1 << WM5100_GP1_FN_SHIFT) | (!!value << WM5100_GP1_LVL_SHIFT); + + ret = regmap_update_bits(wm5100->regmap, WM5100_GPIO_CTRL_1 + offset, + WM5100_GP1_FN_MASK | WM5100_GP1_DIR | + WM5100_GP1_LVL, val); + if (ret < 0) + return ret; + else + return 0; +} + +static int wm5100_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm5100_priv *wm5100 = gpiochip_get_data(chip); + unsigned int reg; + int ret; + + ret = regmap_read(wm5100->regmap, WM5100_GPIO_CTRL_1 + offset, ®); + if (ret < 0) + return ret; + + return (reg & WM5100_GP1_LVL) != 0; +} + +static int wm5100_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm5100_priv *wm5100 = gpiochip_get_data(chip); + + return regmap_update_bits(wm5100->regmap, WM5100_GPIO_CTRL_1 + offset, + WM5100_GP1_FN_MASK | WM5100_GP1_DIR, + (1 << WM5100_GP1_FN_SHIFT) | + (1 << WM5100_GP1_DIR_SHIFT)); +} + +static const struct gpio_chip wm5100_template_chip = { + .label = "wm5100", + .owner = THIS_MODULE, + .direction_output = wm5100_gpio_direction_out, + .set = wm5100_gpio_set, + .direction_input = wm5100_gpio_direction_in, + .get = wm5100_gpio_get, + .can_sleep = 1, +}; + +static void wm5100_init_gpio(struct i2c_client *i2c) +{ + struct wm5100_priv *wm5100 = i2c_get_clientdata(i2c); + int ret; + + wm5100->gpio_chip = wm5100_template_chip; + wm5100->gpio_chip.ngpio = 6; + wm5100->gpio_chip.parent = &i2c->dev; + + if (wm5100->pdata.gpio_base) + wm5100->gpio_chip.base = wm5100->pdata.gpio_base; + else + wm5100->gpio_chip.base = -1; + + ret = gpiochip_add_data(&wm5100->gpio_chip, wm5100); + if (ret != 0) + dev_err(&i2c->dev, "Failed to add GPIOs: %d\n", ret); +} + +static void wm5100_free_gpio(struct i2c_client *i2c) +{ + struct wm5100_priv *wm5100 = i2c_get_clientdata(i2c); + + gpiochip_remove(&wm5100->gpio_chip); +} +#else +static void wm5100_init_gpio(struct i2c_client *i2c) +{ +} + +static void wm5100_free_gpio(struct i2c_client *i2c) +{ +} +#endif + +static int wm5100_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct i2c_client *i2c = to_i2c_client(component->dev); + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + int ret, i; + + wm5100->component = component; + + for (i = 0; i < ARRAY_SIZE(wm5100_dig_vu); i++) + snd_soc_component_update_bits(component, wm5100_dig_vu[i], WM5100_OUT_VU, + WM5100_OUT_VU); + + /* Don't debounce interrupts to support use of SYSCLK only */ + snd_soc_component_write(component, WM5100_IRQ_DEBOUNCE_1, 0); + snd_soc_component_write(component, WM5100_IRQ_DEBOUNCE_2, 0); + + /* TODO: check if we're symmetric */ + + if (i2c->irq) + snd_soc_dapm_new_controls(dapm, wm5100_dapm_widgets_noirq, + ARRAY_SIZE(wm5100_dapm_widgets_noirq)); + + if (wm5100->pdata.hp_pol) { + ret = gpio_request_one(wm5100->pdata.hp_pol, + GPIOF_OUT_INIT_HIGH, "WM5100 HP_POL"); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request HP_POL %d: %d\n", + wm5100->pdata.hp_pol, ret); + goto err_gpio; + } + } + + return 0; + +err_gpio: + + return ret; +} + +static void wm5100_remove(struct snd_soc_component *component) +{ + struct wm5100_priv *wm5100 = snd_soc_component_get_drvdata(component); + + if (wm5100->pdata.hp_pol) { + gpio_free(wm5100->pdata.hp_pol); + } +} + +static const struct snd_soc_component_driver soc_component_dev_wm5100 = { + .probe = wm5100_probe, + .remove = wm5100_remove, + .set_sysclk = wm5100_set_sysclk, + .set_pll = wm5100_set_fll, + .seq_notifier = wm5100_seq_notifier, + .controls = wm5100_snd_controls, + .num_controls = ARRAY_SIZE(wm5100_snd_controls), + .dapm_widgets = wm5100_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm5100_dapm_widgets), + .dapm_routes = wm5100_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm5100_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm5100_regmap = { + .reg_bits = 16, + .val_bits = 16, + + .max_register = WM5100_MAX_REGISTER, + .reg_defaults = wm5100_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm5100_reg_defaults), + .volatile_reg = wm5100_volatile_register, + .readable_reg = wm5100_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static const unsigned int wm5100_mic_ctrl_reg[] = { + WM5100_IN1L_CONTROL, + WM5100_IN2L_CONTROL, + WM5100_IN3L_CONTROL, + WM5100_IN4L_CONTROL, +}; + +static int wm5100_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm5100_pdata *pdata = dev_get_platdata(&i2c->dev); + struct wm5100_priv *wm5100; + unsigned int reg; + int ret, i, irq_flags; + + wm5100 = devm_kzalloc(&i2c->dev, sizeof(struct wm5100_priv), + GFP_KERNEL); + if (wm5100 == NULL) + return -ENOMEM; + + wm5100->dev = &i2c->dev; + + wm5100->regmap = devm_regmap_init_i2c(i2c, &wm5100_regmap); + if (IS_ERR(wm5100->regmap)) { + ret = PTR_ERR(wm5100->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(wm5100->fll); i++) + init_completion(&wm5100->fll[i].lock); + + if (pdata) + wm5100->pdata = *pdata; + + i2c_set_clientdata(i2c, wm5100); + + for (i = 0; i < ARRAY_SIZE(wm5100->core_supplies); i++) + wm5100->core_supplies[i].supply = wm5100_core_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, + ARRAY_SIZE(wm5100->core_supplies), + wm5100->core_supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request core supplies: %d\n", + ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm5100->core_supplies), + wm5100->core_supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable core supplies: %d\n", + ret); + goto err; + } + + if (wm5100->pdata.ldo_ena) { + ret = gpio_request_one(wm5100->pdata.ldo_ena, + GPIOF_OUT_INIT_HIGH, "WM5100 LDOENA"); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request LDOENA %d: %d\n", + wm5100->pdata.ldo_ena, ret); + goto err_enable; + } + msleep(2); + } + + if (wm5100->pdata.reset) { + ret = gpio_request_one(wm5100->pdata.reset, + GPIOF_OUT_INIT_HIGH, "WM5100 /RESET"); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request /RESET %d: %d\n", + wm5100->pdata.reset, ret); + goto err_ldo; + } + } + + ret = regmap_read(wm5100->regmap, WM5100_SOFTWARE_RESET, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read ID register: %d\n", ret); + goto err_reset; + } + switch (reg) { + case 0x8997: + case 0x5100: + break; + + default: + dev_err(&i2c->dev, "Device is not a WM5100, ID is %x\n", reg); + ret = -EINVAL; + goto err_reset; + } + + ret = regmap_read(wm5100->regmap, WM5100_DEVICE_REVISION, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read revision register\n"); + goto err_reset; + } + wm5100->rev = reg & WM5100_DEVICE_REVISION_MASK; + + dev_info(&i2c->dev, "revision %c\n", wm5100->rev + 'A'); + + ret = wm5100_reset(wm5100); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + goto err_reset; + } + + switch (wm5100->rev) { + case 0: + ret = regmap_register_patch(wm5100->regmap, + wm5100_reva_patches, + ARRAY_SIZE(wm5100_reva_patches)); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register patches: %d\n", + ret); + goto err_reset; + } + break; + default: + break; + } + + + wm5100_init_gpio(i2c); + + for (i = 0; i < ARRAY_SIZE(wm5100->pdata.gpio_defaults); i++) { + if (!wm5100->pdata.gpio_defaults[i]) + continue; + + regmap_write(wm5100->regmap, WM5100_GPIO_CTRL_1 + i, + wm5100->pdata.gpio_defaults[i]); + } + + for (i = 0; i < ARRAY_SIZE(wm5100->pdata.in_mode); i++) { + regmap_update_bits(wm5100->regmap, wm5100_mic_ctrl_reg[i], + WM5100_IN1_MODE_MASK | + WM5100_IN1_DMIC_SUP_MASK, + (wm5100->pdata.in_mode[i] << + WM5100_IN1_MODE_SHIFT) | + (wm5100->pdata.dmic_sup[i] << + WM5100_IN1_DMIC_SUP_SHIFT)); + } + + if (i2c->irq) { + if (wm5100->pdata.irq_flags) + irq_flags = wm5100->pdata.irq_flags; + else + irq_flags = IRQF_TRIGGER_LOW; + + irq_flags |= IRQF_ONESHOT; + + if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) + ret = request_threaded_irq(i2c->irq, NULL, + wm5100_edge_irq, irq_flags, + "wm5100", wm5100); + else + ret = request_threaded_irq(i2c->irq, NULL, wm5100_irq, + irq_flags, "wm5100", + wm5100); + + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", + i2c->irq, ret); + } else { + /* Enable default interrupts */ + regmap_update_bits(wm5100->regmap, + WM5100_INTERRUPT_STATUS_3_MASK, + WM5100_IM_SPK_SHUTDOWN_WARN_EINT | + WM5100_IM_SPK_SHUTDOWN_EINT | + WM5100_IM_ASRC2_LOCK_EINT | + WM5100_IM_ASRC1_LOCK_EINT | + WM5100_IM_FLL2_LOCK_EINT | + WM5100_IM_FLL1_LOCK_EINT | + WM5100_CLKGEN_ERR_EINT | + WM5100_CLKGEN_ERR_ASYNC_EINT, 0); + + regmap_update_bits(wm5100->regmap, + WM5100_INTERRUPT_STATUS_4_MASK, + WM5100_AIF3_ERR_EINT | + WM5100_AIF2_ERR_EINT | + WM5100_AIF1_ERR_EINT | + WM5100_CTRLIF_ERR_EINT | + WM5100_ISRC2_UNDERCLOCKED_EINT | + WM5100_ISRC1_UNDERCLOCKED_EINT | + WM5100_FX_UNDERCLOCKED_EINT | + WM5100_AIF3_UNDERCLOCKED_EINT | + WM5100_AIF2_UNDERCLOCKED_EINT | + WM5100_AIF1_UNDERCLOCKED_EINT | + WM5100_ASRC_UNDERCLOCKED_EINT | + WM5100_DAC_UNDERCLOCKED_EINT | + WM5100_ADC_UNDERCLOCKED_EINT | + WM5100_MIXER_UNDERCLOCKED_EINT, 0); + } + } + + pm_runtime_set_active(&i2c->dev); + pm_runtime_enable(&i2c->dev); + pm_request_idle(&i2c->dev); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm5100, wm5100_dai, + ARRAY_SIZE(wm5100_dai)); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register WM5100: %d\n", ret); + goto err_reset; + } + + return ret; + +err_reset: + pm_runtime_disable(&i2c->dev); + if (i2c->irq) + free_irq(i2c->irq, wm5100); + wm5100_free_gpio(i2c); + if (wm5100->pdata.reset) { + gpio_set_value_cansleep(wm5100->pdata.reset, 0); + gpio_free(wm5100->pdata.reset); + } +err_ldo: + if (wm5100->pdata.ldo_ena) { + gpio_set_value_cansleep(wm5100->pdata.ldo_ena, 0); + gpio_free(wm5100->pdata.ldo_ena); + } +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), + wm5100->core_supplies); +err: + return ret; +} + +static int wm5100_i2c_remove(struct i2c_client *i2c) +{ + struct wm5100_priv *wm5100 = i2c_get_clientdata(i2c); + + pm_runtime_disable(&i2c->dev); + if (i2c->irq) + free_irq(i2c->irq, wm5100); + wm5100_free_gpio(i2c); + if (wm5100->pdata.reset) { + gpio_set_value_cansleep(wm5100->pdata.reset, 0); + gpio_free(wm5100->pdata.reset); + } + if (wm5100->pdata.ldo_ena) { + gpio_set_value_cansleep(wm5100->pdata.ldo_ena, 0); + gpio_free(wm5100->pdata.ldo_ena); + } + + return 0; +} + +#ifdef CONFIG_PM +static int wm5100_runtime_suspend(struct device *dev) +{ + struct wm5100_priv *wm5100 = dev_get_drvdata(dev); + + regcache_cache_only(wm5100->regmap, true); + regcache_mark_dirty(wm5100->regmap); + if (wm5100->pdata.ldo_ena) + gpio_set_value_cansleep(wm5100->pdata.ldo_ena, 0); + regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), + wm5100->core_supplies); + + return 0; +} + +static int wm5100_runtime_resume(struct device *dev) +{ + struct wm5100_priv *wm5100 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm5100->core_supplies), + wm5100->core_supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", + ret); + return ret; + } + + if (wm5100->pdata.ldo_ena) { + gpio_set_value_cansleep(wm5100->pdata.ldo_ena, 1); + msleep(2); + } + + regcache_cache_only(wm5100->regmap, false); + regcache_sync(wm5100->regmap); + + return 0; +} +#endif + +static const struct dev_pm_ops wm5100_pm = { + SET_RUNTIME_PM_OPS(wm5100_runtime_suspend, wm5100_runtime_resume, + NULL) +}; + +static const struct i2c_device_id wm5100_i2c_id[] = { + { "wm5100", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm5100_i2c_id); + +static struct i2c_driver wm5100_i2c_driver = { + .driver = { + .name = "wm5100", + .pm = &wm5100_pm, + }, + .probe = wm5100_i2c_probe, + .remove = wm5100_i2c_remove, + .id_table = wm5100_i2c_id, +}; + +module_i2c_driver(wm5100_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM5100 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm5100.h b/sound/soc/codecs/wm5100.h new file mode 100644 index 000000000..6076493cf --- /dev/null +++ b/sound/soc/codecs/wm5100.h @@ -0,0 +1,5315 @@ +/* + * wm5100.h -- WM5100 ALSA SoC Audio driver + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef WM5100_ASOC_H +#define WM5100_ASOC_H + +#include +#include + +int wm5100_detect(struct snd_soc_component *component, struct snd_soc_jack *jack); + +#define WM5100_CLK_AIF1 1 +#define WM5100_CLK_AIF2 2 +#define WM5100_CLK_AIF3 3 +#define WM5100_CLK_SYSCLK 4 +#define WM5100_CLK_ASYNCCLK 5 +#define WM5100_CLK_32KHZ 6 +#define WM5100_CLK_OPCLK 7 + +#define WM5100_CLKSRC_MCLK1 0 +#define WM5100_CLKSRC_MCLK2 1 +#define WM5100_CLKSRC_SYSCLK 2 +#define WM5100_CLKSRC_FLL1 4 +#define WM5100_CLKSRC_FLL2 5 +#define WM5100_CLKSRC_AIF1BCLK 8 +#define WM5100_CLKSRC_AIF2BCLK 9 +#define WM5100_CLKSRC_AIF3BCLK 10 +#define WM5100_CLKSRC_ASYNCCLK 0x100 + +#define WM5100_FLL1 1 +#define WM5100_FLL2 2 + +#define WM5100_FLL_SRC_MCLK1 0x0 +#define WM5100_FLL_SRC_MCLK2 0x1 +#define WM5100_FLL_SRC_FLL1 0x4 +#define WM5100_FLL_SRC_FLL2 0x5 +#define WM5100_FLL_SRC_AIF1BCLK 0x8 +#define WM5100_FLL_SRC_AIF2BCLK 0x9 +#define WM5100_FLL_SRC_AIF3BCLK 0xa + +/* + * Register values. + */ +#define WM5100_SOFTWARE_RESET 0x00 +#define WM5100_DEVICE_REVISION 0x01 +#define WM5100_CTRL_IF_1 0x10 +#define WM5100_TONE_GENERATOR_1 0x20 +#define WM5100_PWM_DRIVE_1 0x30 +#define WM5100_PWM_DRIVE_2 0x31 +#define WM5100_PWM_DRIVE_3 0x32 +#define WM5100_CLOCKING_1 0x100 +#define WM5100_CLOCKING_3 0x101 +#define WM5100_CLOCKING_4 0x102 +#define WM5100_CLOCKING_5 0x103 +#define WM5100_CLOCKING_6 0x104 +#define WM5100_CLOCKING_7 0x107 +#define WM5100_CLOCKING_8 0x108 +#define WM5100_ASRC_ENABLE 0x120 +#define WM5100_ASRC_STATUS 0x121 +#define WM5100_ASRC_RATE1 0x122 +#define WM5100_ISRC_1_CTRL_1 0x141 +#define WM5100_ISRC_1_CTRL_2 0x142 +#define WM5100_ISRC_2_CTRL1 0x143 +#define WM5100_ISRC_2_CTRL_2 0x144 +#define WM5100_FLL1_CONTROL_1 0x182 +#define WM5100_FLL1_CONTROL_2 0x183 +#define WM5100_FLL1_CONTROL_3 0x184 +#define WM5100_FLL1_CONTROL_5 0x186 +#define WM5100_FLL1_CONTROL_6 0x187 +#define WM5100_FLL1_EFS_1 0x188 +#define WM5100_FLL2_CONTROL_1 0x1A2 +#define WM5100_FLL2_CONTROL_2 0x1A3 +#define WM5100_FLL2_CONTROL_3 0x1A4 +#define WM5100_FLL2_CONTROL_5 0x1A6 +#define WM5100_FLL2_CONTROL_6 0x1A7 +#define WM5100_FLL2_EFS_1 0x1A8 +#define WM5100_MIC_CHARGE_PUMP_1 0x200 +#define WM5100_MIC_CHARGE_PUMP_2 0x201 +#define WM5100_HP_CHARGE_PUMP_1 0x202 +#define WM5100_LDO1_CONTROL 0x211 +#define WM5100_MIC_BIAS_CTRL_1 0x215 +#define WM5100_MIC_BIAS_CTRL_2 0x216 +#define WM5100_MIC_BIAS_CTRL_3 0x217 +#define WM5100_ACCESSORY_DETECT_MODE_1 0x280 +#define WM5100_HEADPHONE_DETECT_1 0x288 +#define WM5100_HEADPHONE_DETECT_2 0x289 +#define WM5100_MIC_DETECT_1 0x290 +#define WM5100_MIC_DETECT_2 0x291 +#define WM5100_MIC_DETECT_3 0x292 +#define WM5100_MISC_CONTROL 0x2BB +#define WM5100_INPUT_ENABLES 0x301 +#define WM5100_INPUT_ENABLES_STATUS 0x302 +#define WM5100_IN1L_CONTROL 0x310 +#define WM5100_IN1R_CONTROL 0x311 +#define WM5100_IN2L_CONTROL 0x312 +#define WM5100_IN2R_CONTROL 0x313 +#define WM5100_IN3L_CONTROL 0x314 +#define WM5100_IN3R_CONTROL 0x315 +#define WM5100_IN4L_CONTROL 0x316 +#define WM5100_IN4R_CONTROL 0x317 +#define WM5100_RXANC_SRC 0x318 +#define WM5100_INPUT_VOLUME_RAMP 0x319 +#define WM5100_ADC_DIGITAL_VOLUME_1L 0x320 +#define WM5100_ADC_DIGITAL_VOLUME_1R 0x321 +#define WM5100_ADC_DIGITAL_VOLUME_2L 0x322 +#define WM5100_ADC_DIGITAL_VOLUME_2R 0x323 +#define WM5100_ADC_DIGITAL_VOLUME_3L 0x324 +#define WM5100_ADC_DIGITAL_VOLUME_3R 0x325 +#define WM5100_ADC_DIGITAL_VOLUME_4L 0x326 +#define WM5100_ADC_DIGITAL_VOLUME_4R 0x327 +#define WM5100_OUTPUT_ENABLES_2 0x401 +#define WM5100_OUTPUT_STATUS_1 0x402 +#define WM5100_OUTPUT_STATUS_2 0x403 +#define WM5100_CHANNEL_ENABLES_1 0x408 +#define WM5100_OUT_VOLUME_1L 0x410 +#define WM5100_OUT_VOLUME_1R 0x411 +#define WM5100_DAC_VOLUME_LIMIT_1L 0x412 +#define WM5100_DAC_VOLUME_LIMIT_1R 0x413 +#define WM5100_OUT_VOLUME_2L 0x414 +#define WM5100_OUT_VOLUME_2R 0x415 +#define WM5100_DAC_VOLUME_LIMIT_2L 0x416 +#define WM5100_DAC_VOLUME_LIMIT_2R 0x417 +#define WM5100_OUT_VOLUME_3L 0x418 +#define WM5100_OUT_VOLUME_3R 0x419 +#define WM5100_DAC_VOLUME_LIMIT_3L 0x41A +#define WM5100_DAC_VOLUME_LIMIT_3R 0x41B +#define WM5100_OUT_VOLUME_4L 0x41C +#define WM5100_OUT_VOLUME_4R 0x41D +#define WM5100_DAC_VOLUME_LIMIT_5L 0x41E +#define WM5100_DAC_VOLUME_LIMIT_5R 0x41F +#define WM5100_DAC_VOLUME_LIMIT_6L 0x420 +#define WM5100_DAC_VOLUME_LIMIT_6R 0x421 +#define WM5100_DAC_AEC_CONTROL_1 0x440 +#define WM5100_OUTPUT_VOLUME_RAMP 0x441 +#define WM5100_DAC_DIGITAL_VOLUME_1L 0x480 +#define WM5100_DAC_DIGITAL_VOLUME_1R 0x481 +#define WM5100_DAC_DIGITAL_VOLUME_2L 0x482 +#define WM5100_DAC_DIGITAL_VOLUME_2R 0x483 +#define WM5100_DAC_DIGITAL_VOLUME_3L 0x484 +#define WM5100_DAC_DIGITAL_VOLUME_3R 0x485 +#define WM5100_DAC_DIGITAL_VOLUME_4L 0x486 +#define WM5100_DAC_DIGITAL_VOLUME_4R 0x487 +#define WM5100_DAC_DIGITAL_VOLUME_5L 0x488 +#define WM5100_DAC_DIGITAL_VOLUME_5R 0x489 +#define WM5100_DAC_DIGITAL_VOLUME_6L 0x48A +#define WM5100_DAC_DIGITAL_VOLUME_6R 0x48B +#define WM5100_PDM_SPK1_CTRL_1 0x4C0 +#define WM5100_PDM_SPK1_CTRL_2 0x4C1 +#define WM5100_PDM_SPK2_CTRL_1 0x4C2 +#define WM5100_PDM_SPK2_CTRL_2 0x4C3 +#define WM5100_AUDIO_IF_1_1 0x500 +#define WM5100_AUDIO_IF_1_2 0x501 +#define WM5100_AUDIO_IF_1_3 0x502 +#define WM5100_AUDIO_IF_1_4 0x503 +#define WM5100_AUDIO_IF_1_5 0x504 +#define WM5100_AUDIO_IF_1_6 0x505 +#define WM5100_AUDIO_IF_1_7 0x506 +#define WM5100_AUDIO_IF_1_8 0x507 +#define WM5100_AUDIO_IF_1_9 0x508 +#define WM5100_AUDIO_IF_1_10 0x509 +#define WM5100_AUDIO_IF_1_11 0x50A +#define WM5100_AUDIO_IF_1_12 0x50B +#define WM5100_AUDIO_IF_1_13 0x50C +#define WM5100_AUDIO_IF_1_14 0x50D +#define WM5100_AUDIO_IF_1_15 0x50E +#define WM5100_AUDIO_IF_1_16 0x50F +#define WM5100_AUDIO_IF_1_17 0x510 +#define WM5100_AUDIO_IF_1_18 0x511 +#define WM5100_AUDIO_IF_1_19 0x512 +#define WM5100_AUDIO_IF_1_20 0x513 +#define WM5100_AUDIO_IF_1_21 0x514 +#define WM5100_AUDIO_IF_1_22 0x515 +#define WM5100_AUDIO_IF_1_23 0x516 +#define WM5100_AUDIO_IF_1_24 0x517 +#define WM5100_AUDIO_IF_1_25 0x518 +#define WM5100_AUDIO_IF_1_26 0x519 +#define WM5100_AUDIO_IF_1_27 0x51A +#define WM5100_AUDIO_IF_2_1 0x540 +#define WM5100_AUDIO_IF_2_2 0x541 +#define WM5100_AUDIO_IF_2_3 0x542 +#define WM5100_AUDIO_IF_2_4 0x543 +#define WM5100_AUDIO_IF_2_5 0x544 +#define WM5100_AUDIO_IF_2_6 0x545 +#define WM5100_AUDIO_IF_2_7 0x546 +#define WM5100_AUDIO_IF_2_8 0x547 +#define WM5100_AUDIO_IF_2_9 0x548 +#define WM5100_AUDIO_IF_2_10 0x549 +#define WM5100_AUDIO_IF_2_11 0x54A +#define WM5100_AUDIO_IF_2_18 0x551 +#define WM5100_AUDIO_IF_2_19 0x552 +#define WM5100_AUDIO_IF_2_26 0x559 +#define WM5100_AUDIO_IF_2_27 0x55A +#define WM5100_AUDIO_IF_3_1 0x580 +#define WM5100_AUDIO_IF_3_2 0x581 +#define WM5100_AUDIO_IF_3_3 0x582 +#define WM5100_AUDIO_IF_3_4 0x583 +#define WM5100_AUDIO_IF_3_5 0x584 +#define WM5100_AUDIO_IF_3_6 0x585 +#define WM5100_AUDIO_IF_3_7 0x586 +#define WM5100_AUDIO_IF_3_8 0x587 +#define WM5100_AUDIO_IF_3_9 0x588 +#define WM5100_AUDIO_IF_3_10 0x589 +#define WM5100_AUDIO_IF_3_11 0x58A +#define WM5100_AUDIO_IF_3_18 0x591 +#define WM5100_AUDIO_IF_3_19 0x592 +#define WM5100_AUDIO_IF_3_26 0x599 +#define WM5100_AUDIO_IF_3_27 0x59A +#define WM5100_PWM1MIX_INPUT_1_SOURCE 0x640 +#define WM5100_PWM1MIX_INPUT_1_VOLUME 0x641 +#define WM5100_PWM1MIX_INPUT_2_SOURCE 0x642 +#define WM5100_PWM1MIX_INPUT_2_VOLUME 0x643 +#define WM5100_PWM1MIX_INPUT_3_SOURCE 0x644 +#define WM5100_PWM1MIX_INPUT_3_VOLUME 0x645 +#define WM5100_PWM1MIX_INPUT_4_SOURCE 0x646 +#define WM5100_PWM1MIX_INPUT_4_VOLUME 0x647 +#define WM5100_PWM2MIX_INPUT_1_SOURCE 0x648 +#define WM5100_PWM2MIX_INPUT_1_VOLUME 0x649 +#define WM5100_PWM2MIX_INPUT_2_SOURCE 0x64A +#define WM5100_PWM2MIX_INPUT_2_VOLUME 0x64B +#define WM5100_PWM2MIX_INPUT_3_SOURCE 0x64C +#define WM5100_PWM2MIX_INPUT_3_VOLUME 0x64D +#define WM5100_PWM2MIX_INPUT_4_SOURCE 0x64E +#define WM5100_PWM2MIX_INPUT_4_VOLUME 0x64F +#define WM5100_OUT1LMIX_INPUT_1_SOURCE 0x680 +#define WM5100_OUT1LMIX_INPUT_1_VOLUME 0x681 +#define WM5100_OUT1LMIX_INPUT_2_SOURCE 0x682 +#define WM5100_OUT1LMIX_INPUT_2_VOLUME 0x683 +#define WM5100_OUT1LMIX_INPUT_3_SOURCE 0x684 +#define WM5100_OUT1LMIX_INPUT_3_VOLUME 0x685 +#define WM5100_OUT1LMIX_INPUT_4_SOURCE 0x686 +#define WM5100_OUT1LMIX_INPUT_4_VOLUME 0x687 +#define WM5100_OUT1RMIX_INPUT_1_SOURCE 0x688 +#define WM5100_OUT1RMIX_INPUT_1_VOLUME 0x689 +#define WM5100_OUT1RMIX_INPUT_2_SOURCE 0x68A +#define WM5100_OUT1RMIX_INPUT_2_VOLUME 0x68B +#define WM5100_OUT1RMIX_INPUT_3_SOURCE 0x68C +#define WM5100_OUT1RMIX_INPUT_3_VOLUME 0x68D +#define WM5100_OUT1RMIX_INPUT_4_SOURCE 0x68E +#define WM5100_OUT1RMIX_INPUT_4_VOLUME 0x68F +#define WM5100_OUT2LMIX_INPUT_1_SOURCE 0x690 +#define WM5100_OUT2LMIX_INPUT_1_VOLUME 0x691 +#define WM5100_OUT2LMIX_INPUT_2_SOURCE 0x692 +#define WM5100_OUT2LMIX_INPUT_2_VOLUME 0x693 +#define WM5100_OUT2LMIX_INPUT_3_SOURCE 0x694 +#define WM5100_OUT2LMIX_INPUT_3_VOLUME 0x695 +#define WM5100_OUT2LMIX_INPUT_4_SOURCE 0x696 +#define WM5100_OUT2LMIX_INPUT_4_VOLUME 0x697 +#define WM5100_OUT2RMIX_INPUT_1_SOURCE 0x698 +#define WM5100_OUT2RMIX_INPUT_1_VOLUME 0x699 +#define WM5100_OUT2RMIX_INPUT_2_SOURCE 0x69A +#define WM5100_OUT2RMIX_INPUT_2_VOLUME 0x69B +#define WM5100_OUT2RMIX_INPUT_3_SOURCE 0x69C +#define WM5100_OUT2RMIX_INPUT_3_VOLUME 0x69D +#define WM5100_OUT2RMIX_INPUT_4_SOURCE 0x69E +#define WM5100_OUT2RMIX_INPUT_4_VOLUME 0x69F +#define WM5100_OUT3LMIX_INPUT_1_SOURCE 0x6A0 +#define WM5100_OUT3LMIX_INPUT_1_VOLUME 0x6A1 +#define WM5100_OUT3LMIX_INPUT_2_SOURCE 0x6A2 +#define WM5100_OUT3LMIX_INPUT_2_VOLUME 0x6A3 +#define WM5100_OUT3LMIX_INPUT_3_SOURCE 0x6A4 +#define WM5100_OUT3LMIX_INPUT_3_VOLUME 0x6A5 +#define WM5100_OUT3LMIX_INPUT_4_SOURCE 0x6A6 +#define WM5100_OUT3LMIX_INPUT_4_VOLUME 0x6A7 +#define WM5100_OUT3RMIX_INPUT_1_SOURCE 0x6A8 +#define WM5100_OUT3RMIX_INPUT_1_VOLUME 0x6A9 +#define WM5100_OUT3RMIX_INPUT_2_SOURCE 0x6AA +#define WM5100_OUT3RMIX_INPUT_2_VOLUME 0x6AB +#define WM5100_OUT3RMIX_INPUT_3_SOURCE 0x6AC +#define WM5100_OUT3RMIX_INPUT_3_VOLUME 0x6AD +#define WM5100_OUT3RMIX_INPUT_4_SOURCE 0x6AE +#define WM5100_OUT3RMIX_INPUT_4_VOLUME 0x6AF +#define WM5100_OUT4LMIX_INPUT_1_SOURCE 0x6B0 +#define WM5100_OUT4LMIX_INPUT_1_VOLUME 0x6B1 +#define WM5100_OUT4LMIX_INPUT_2_SOURCE 0x6B2 +#define WM5100_OUT4LMIX_INPUT_2_VOLUME 0x6B3 +#define WM5100_OUT4LMIX_INPUT_3_SOURCE 0x6B4 +#define WM5100_OUT4LMIX_INPUT_3_VOLUME 0x6B5 +#define WM5100_OUT4LMIX_INPUT_4_SOURCE 0x6B6 +#define WM5100_OUT4LMIX_INPUT_4_VOLUME 0x6B7 +#define WM5100_OUT4RMIX_INPUT_1_SOURCE 0x6B8 +#define WM5100_OUT4RMIX_INPUT_1_VOLUME 0x6B9 +#define WM5100_OUT4RMIX_INPUT_2_SOURCE 0x6BA +#define WM5100_OUT4RMIX_INPUT_2_VOLUME 0x6BB +#define WM5100_OUT4RMIX_INPUT_3_SOURCE 0x6BC +#define WM5100_OUT4RMIX_INPUT_3_VOLUME 0x6BD +#define WM5100_OUT4RMIX_INPUT_4_SOURCE 0x6BE +#define WM5100_OUT4RMIX_INPUT_4_VOLUME 0x6BF +#define WM5100_OUT5LMIX_INPUT_1_SOURCE 0x6C0 +#define WM5100_OUT5LMIX_INPUT_1_VOLUME 0x6C1 +#define WM5100_OUT5LMIX_INPUT_2_SOURCE 0x6C2 +#define WM5100_OUT5LMIX_INPUT_2_VOLUME 0x6C3 +#define WM5100_OUT5LMIX_INPUT_3_SOURCE 0x6C4 +#define WM5100_OUT5LMIX_INPUT_3_VOLUME 0x6C5 +#define WM5100_OUT5LMIX_INPUT_4_SOURCE 0x6C6 +#define WM5100_OUT5LMIX_INPUT_4_VOLUME 0x6C7 +#define WM5100_OUT5RMIX_INPUT_1_SOURCE 0x6C8 +#define WM5100_OUT5RMIX_INPUT_1_VOLUME 0x6C9 +#define WM5100_OUT5RMIX_INPUT_2_SOURCE 0x6CA +#define WM5100_OUT5RMIX_INPUT_2_VOLUME 0x6CB +#define WM5100_OUT5RMIX_INPUT_3_SOURCE 0x6CC +#define WM5100_OUT5RMIX_INPUT_3_VOLUME 0x6CD +#define WM5100_OUT5RMIX_INPUT_4_SOURCE 0x6CE +#define WM5100_OUT5RMIX_INPUT_4_VOLUME 0x6CF +#define WM5100_OUT6LMIX_INPUT_1_SOURCE 0x6D0 +#define WM5100_OUT6LMIX_INPUT_1_VOLUME 0x6D1 +#define WM5100_OUT6LMIX_INPUT_2_SOURCE 0x6D2 +#define WM5100_OUT6LMIX_INPUT_2_VOLUME 0x6D3 +#define WM5100_OUT6LMIX_INPUT_3_SOURCE 0x6D4 +#define WM5100_OUT6LMIX_INPUT_3_VOLUME 0x6D5 +#define WM5100_OUT6LMIX_INPUT_4_SOURCE 0x6D6 +#define WM5100_OUT6LMIX_INPUT_4_VOLUME 0x6D7 +#define WM5100_OUT6RMIX_INPUT_1_SOURCE 0x6D8 +#define WM5100_OUT6RMIX_INPUT_1_VOLUME 0x6D9 +#define WM5100_OUT6RMIX_INPUT_2_SOURCE 0x6DA +#define WM5100_OUT6RMIX_INPUT_2_VOLUME 0x6DB +#define WM5100_OUT6RMIX_INPUT_3_SOURCE 0x6DC +#define WM5100_OUT6RMIX_INPUT_3_VOLUME 0x6DD +#define WM5100_OUT6RMIX_INPUT_4_SOURCE 0x6DE +#define WM5100_OUT6RMIX_INPUT_4_VOLUME 0x6DF +#define WM5100_AIF1TX1MIX_INPUT_1_SOURCE 0x700 +#define WM5100_AIF1TX1MIX_INPUT_1_VOLUME 0x701 +#define WM5100_AIF1TX1MIX_INPUT_2_SOURCE 0x702 +#define WM5100_AIF1TX1MIX_INPUT_2_VOLUME 0x703 +#define WM5100_AIF1TX1MIX_INPUT_3_SOURCE 0x704 +#define WM5100_AIF1TX1MIX_INPUT_3_VOLUME 0x705 +#define WM5100_AIF1TX1MIX_INPUT_4_SOURCE 0x706 +#define WM5100_AIF1TX1MIX_INPUT_4_VOLUME 0x707 +#define WM5100_AIF1TX2MIX_INPUT_1_SOURCE 0x708 +#define WM5100_AIF1TX2MIX_INPUT_1_VOLUME 0x709 +#define WM5100_AIF1TX2MIX_INPUT_2_SOURCE 0x70A +#define WM5100_AIF1TX2MIX_INPUT_2_VOLUME 0x70B +#define WM5100_AIF1TX2MIX_INPUT_3_SOURCE 0x70C +#define WM5100_AIF1TX2MIX_INPUT_3_VOLUME 0x70D +#define WM5100_AIF1TX2MIX_INPUT_4_SOURCE 0x70E +#define WM5100_AIF1TX2MIX_INPUT_4_VOLUME 0x70F +#define WM5100_AIF1TX3MIX_INPUT_1_SOURCE 0x710 +#define WM5100_AIF1TX3MIX_INPUT_1_VOLUME 0x711 +#define WM5100_AIF1TX3MIX_INPUT_2_SOURCE 0x712 +#define WM5100_AIF1TX3MIX_INPUT_2_VOLUME 0x713 +#define WM5100_AIF1TX3MIX_INPUT_3_SOURCE 0x714 +#define WM5100_AIF1TX3MIX_INPUT_3_VOLUME 0x715 +#define WM5100_AIF1TX3MIX_INPUT_4_SOURCE 0x716 +#define WM5100_AIF1TX3MIX_INPUT_4_VOLUME 0x717 +#define WM5100_AIF1TX4MIX_INPUT_1_SOURCE 0x718 +#define WM5100_AIF1TX4MIX_INPUT_1_VOLUME 0x719 +#define WM5100_AIF1TX4MIX_INPUT_2_SOURCE 0x71A +#define WM5100_AIF1TX4MIX_INPUT_2_VOLUME 0x71B +#define WM5100_AIF1TX4MIX_INPUT_3_SOURCE 0x71C +#define WM5100_AIF1TX4MIX_INPUT_3_VOLUME 0x71D +#define WM5100_AIF1TX4MIX_INPUT_4_SOURCE 0x71E +#define WM5100_AIF1TX4MIX_INPUT_4_VOLUME 0x71F +#define WM5100_AIF1TX5MIX_INPUT_1_SOURCE 0x720 +#define WM5100_AIF1TX5MIX_INPUT_1_VOLUME 0x721 +#define WM5100_AIF1TX5MIX_INPUT_2_SOURCE 0x722 +#define WM5100_AIF1TX5MIX_INPUT_2_VOLUME 0x723 +#define WM5100_AIF1TX5MIX_INPUT_3_SOURCE 0x724 +#define WM5100_AIF1TX5MIX_INPUT_3_VOLUME 0x725 +#define WM5100_AIF1TX5MIX_INPUT_4_SOURCE 0x726 +#define WM5100_AIF1TX5MIX_INPUT_4_VOLUME 0x727 +#define WM5100_AIF1TX6MIX_INPUT_1_SOURCE 0x728 +#define WM5100_AIF1TX6MIX_INPUT_1_VOLUME 0x729 +#define WM5100_AIF1TX6MIX_INPUT_2_SOURCE 0x72A +#define WM5100_AIF1TX6MIX_INPUT_2_VOLUME 0x72B +#define WM5100_AIF1TX6MIX_INPUT_3_SOURCE 0x72C +#define WM5100_AIF1TX6MIX_INPUT_3_VOLUME 0x72D +#define WM5100_AIF1TX6MIX_INPUT_4_SOURCE 0x72E +#define WM5100_AIF1TX6MIX_INPUT_4_VOLUME 0x72F +#define WM5100_AIF1TX7MIX_INPUT_1_SOURCE 0x730 +#define WM5100_AIF1TX7MIX_INPUT_1_VOLUME 0x731 +#define WM5100_AIF1TX7MIX_INPUT_2_SOURCE 0x732 +#define WM5100_AIF1TX7MIX_INPUT_2_VOLUME 0x733 +#define WM5100_AIF1TX7MIX_INPUT_3_SOURCE 0x734 +#define WM5100_AIF1TX7MIX_INPUT_3_VOLUME 0x735 +#define WM5100_AIF1TX7MIX_INPUT_4_SOURCE 0x736 +#define WM5100_AIF1TX7MIX_INPUT_4_VOLUME 0x737 +#define WM5100_AIF1TX8MIX_INPUT_1_SOURCE 0x738 +#define WM5100_AIF1TX8MIX_INPUT_1_VOLUME 0x739 +#define WM5100_AIF1TX8MIX_INPUT_2_SOURCE 0x73A +#define WM5100_AIF1TX8MIX_INPUT_2_VOLUME 0x73B +#define WM5100_AIF1TX8MIX_INPUT_3_SOURCE 0x73C +#define WM5100_AIF1TX8MIX_INPUT_3_VOLUME 0x73D +#define WM5100_AIF1TX8MIX_INPUT_4_SOURCE 0x73E +#define WM5100_AIF1TX8MIX_INPUT_4_VOLUME 0x73F +#define WM5100_AIF2TX1MIX_INPUT_1_SOURCE 0x740 +#define WM5100_AIF2TX1MIX_INPUT_1_VOLUME 0x741 +#define WM5100_AIF2TX1MIX_INPUT_2_SOURCE 0x742 +#define WM5100_AIF2TX1MIX_INPUT_2_VOLUME 0x743 +#define WM5100_AIF2TX1MIX_INPUT_3_SOURCE 0x744 +#define WM5100_AIF2TX1MIX_INPUT_3_VOLUME 0x745 +#define WM5100_AIF2TX1MIX_INPUT_4_SOURCE 0x746 +#define WM5100_AIF2TX1MIX_INPUT_4_VOLUME 0x747 +#define WM5100_AIF2TX2MIX_INPUT_1_SOURCE 0x748 +#define WM5100_AIF2TX2MIX_INPUT_1_VOLUME 0x749 +#define WM5100_AIF2TX2MIX_INPUT_2_SOURCE 0x74A +#define WM5100_AIF2TX2MIX_INPUT_2_VOLUME 0x74B +#define WM5100_AIF2TX2MIX_INPUT_3_SOURCE 0x74C +#define WM5100_AIF2TX2MIX_INPUT_3_VOLUME 0x74D +#define WM5100_AIF2TX2MIX_INPUT_4_SOURCE 0x74E +#define WM5100_AIF2TX2MIX_INPUT_4_VOLUME 0x74F +#define WM5100_AIF3TX1MIX_INPUT_1_SOURCE 0x780 +#define WM5100_AIF3TX1MIX_INPUT_1_VOLUME 0x781 +#define WM5100_AIF3TX1MIX_INPUT_2_SOURCE 0x782 +#define WM5100_AIF3TX1MIX_INPUT_2_VOLUME 0x783 +#define WM5100_AIF3TX1MIX_INPUT_3_SOURCE 0x784 +#define WM5100_AIF3TX1MIX_INPUT_3_VOLUME 0x785 +#define WM5100_AIF3TX1MIX_INPUT_4_SOURCE 0x786 +#define WM5100_AIF3TX1MIX_INPUT_4_VOLUME 0x787 +#define WM5100_AIF3TX2MIX_INPUT_1_SOURCE 0x788 +#define WM5100_AIF3TX2MIX_INPUT_1_VOLUME 0x789 +#define WM5100_AIF3TX2MIX_INPUT_2_SOURCE 0x78A +#define WM5100_AIF3TX2MIX_INPUT_2_VOLUME 0x78B +#define WM5100_AIF3TX2MIX_INPUT_3_SOURCE 0x78C +#define WM5100_AIF3TX2MIX_INPUT_3_VOLUME 0x78D +#define WM5100_AIF3TX2MIX_INPUT_4_SOURCE 0x78E +#define WM5100_AIF3TX2MIX_INPUT_4_VOLUME 0x78F +#define WM5100_EQ1MIX_INPUT_1_SOURCE 0x880 +#define WM5100_EQ1MIX_INPUT_1_VOLUME 0x881 +#define WM5100_EQ1MIX_INPUT_2_SOURCE 0x882 +#define WM5100_EQ1MIX_INPUT_2_VOLUME 0x883 +#define WM5100_EQ1MIX_INPUT_3_SOURCE 0x884 +#define WM5100_EQ1MIX_INPUT_3_VOLUME 0x885 +#define WM5100_EQ1MIX_INPUT_4_SOURCE 0x886 +#define WM5100_EQ1MIX_INPUT_4_VOLUME 0x887 +#define WM5100_EQ2MIX_INPUT_1_SOURCE 0x888 +#define WM5100_EQ2MIX_INPUT_1_VOLUME 0x889 +#define WM5100_EQ2MIX_INPUT_2_SOURCE 0x88A +#define WM5100_EQ2MIX_INPUT_2_VOLUME 0x88B +#define WM5100_EQ2MIX_INPUT_3_SOURCE 0x88C +#define WM5100_EQ2MIX_INPUT_3_VOLUME 0x88D +#define WM5100_EQ2MIX_INPUT_4_SOURCE 0x88E +#define WM5100_EQ2MIX_INPUT_4_VOLUME 0x88F +#define WM5100_EQ3MIX_INPUT_1_SOURCE 0x890 +#define WM5100_EQ3MIX_INPUT_1_VOLUME 0x891 +#define WM5100_EQ3MIX_INPUT_2_SOURCE 0x892 +#define WM5100_EQ3MIX_INPUT_2_VOLUME 0x893 +#define WM5100_EQ3MIX_INPUT_3_SOURCE 0x894 +#define WM5100_EQ3MIX_INPUT_3_VOLUME 0x895 +#define WM5100_EQ3MIX_INPUT_4_SOURCE 0x896 +#define WM5100_EQ3MIX_INPUT_4_VOLUME 0x897 +#define WM5100_EQ4MIX_INPUT_1_SOURCE 0x898 +#define WM5100_EQ4MIX_INPUT_1_VOLUME 0x899 +#define WM5100_EQ4MIX_INPUT_2_SOURCE 0x89A +#define WM5100_EQ4MIX_INPUT_2_VOLUME 0x89B +#define WM5100_EQ4MIX_INPUT_3_SOURCE 0x89C +#define WM5100_EQ4MIX_INPUT_3_VOLUME 0x89D +#define WM5100_EQ4MIX_INPUT_4_SOURCE 0x89E +#define WM5100_EQ4MIX_INPUT_4_VOLUME 0x89F +#define WM5100_DRC1LMIX_INPUT_1_SOURCE 0x8C0 +#define WM5100_DRC1LMIX_INPUT_1_VOLUME 0x8C1 +#define WM5100_DRC1LMIX_INPUT_2_SOURCE 0x8C2 +#define WM5100_DRC1LMIX_INPUT_2_VOLUME 0x8C3 +#define WM5100_DRC1LMIX_INPUT_3_SOURCE 0x8C4 +#define WM5100_DRC1LMIX_INPUT_3_VOLUME 0x8C5 +#define WM5100_DRC1LMIX_INPUT_4_SOURCE 0x8C6 +#define WM5100_DRC1LMIX_INPUT_4_VOLUME 0x8C7 +#define WM5100_DRC1RMIX_INPUT_1_SOURCE 0x8C8 +#define WM5100_DRC1RMIX_INPUT_1_VOLUME 0x8C9 +#define WM5100_DRC1RMIX_INPUT_2_SOURCE 0x8CA +#define WM5100_DRC1RMIX_INPUT_2_VOLUME 0x8CB +#define WM5100_DRC1RMIX_INPUT_3_SOURCE 0x8CC +#define WM5100_DRC1RMIX_INPUT_3_VOLUME 0x8CD +#define WM5100_DRC1RMIX_INPUT_4_SOURCE 0x8CE +#define WM5100_DRC1RMIX_INPUT_4_VOLUME 0x8CF +#define WM5100_HPLP1MIX_INPUT_1_SOURCE 0x900 +#define WM5100_HPLP1MIX_INPUT_1_VOLUME 0x901 +#define WM5100_HPLP1MIX_INPUT_2_SOURCE 0x902 +#define WM5100_HPLP1MIX_INPUT_2_VOLUME 0x903 +#define WM5100_HPLP1MIX_INPUT_3_SOURCE 0x904 +#define WM5100_HPLP1MIX_INPUT_3_VOLUME 0x905 +#define WM5100_HPLP1MIX_INPUT_4_SOURCE 0x906 +#define WM5100_HPLP1MIX_INPUT_4_VOLUME 0x907 +#define WM5100_HPLP2MIX_INPUT_1_SOURCE 0x908 +#define WM5100_HPLP2MIX_INPUT_1_VOLUME 0x909 +#define WM5100_HPLP2MIX_INPUT_2_SOURCE 0x90A +#define WM5100_HPLP2MIX_INPUT_2_VOLUME 0x90B +#define WM5100_HPLP2MIX_INPUT_3_SOURCE 0x90C +#define WM5100_HPLP2MIX_INPUT_3_VOLUME 0x90D +#define WM5100_HPLP2MIX_INPUT_4_SOURCE 0x90E +#define WM5100_HPLP2MIX_INPUT_4_VOLUME 0x90F +#define WM5100_HPLP3MIX_INPUT_1_SOURCE 0x910 +#define WM5100_HPLP3MIX_INPUT_1_VOLUME 0x911 +#define WM5100_HPLP3MIX_INPUT_2_SOURCE 0x912 +#define WM5100_HPLP3MIX_INPUT_2_VOLUME 0x913 +#define WM5100_HPLP3MIX_INPUT_3_SOURCE 0x914 +#define WM5100_HPLP3MIX_INPUT_3_VOLUME 0x915 +#define WM5100_HPLP3MIX_INPUT_4_SOURCE 0x916 +#define WM5100_HPLP3MIX_INPUT_4_VOLUME 0x917 +#define WM5100_HPLP4MIX_INPUT_1_SOURCE 0x918 +#define WM5100_HPLP4MIX_INPUT_1_VOLUME 0x919 +#define WM5100_HPLP4MIX_INPUT_2_SOURCE 0x91A +#define WM5100_HPLP4MIX_INPUT_2_VOLUME 0x91B +#define WM5100_HPLP4MIX_INPUT_3_SOURCE 0x91C +#define WM5100_HPLP4MIX_INPUT_3_VOLUME 0x91D +#define WM5100_HPLP4MIX_INPUT_4_SOURCE 0x91E +#define WM5100_HPLP4MIX_INPUT_4_VOLUME 0x91F +#define WM5100_DSP1LMIX_INPUT_1_SOURCE 0x940 +#define WM5100_DSP1LMIX_INPUT_1_VOLUME 0x941 +#define WM5100_DSP1LMIX_INPUT_2_SOURCE 0x942 +#define WM5100_DSP1LMIX_INPUT_2_VOLUME 0x943 +#define WM5100_DSP1LMIX_INPUT_3_SOURCE 0x944 +#define WM5100_DSP1LMIX_INPUT_3_VOLUME 0x945 +#define WM5100_DSP1LMIX_INPUT_4_SOURCE 0x946 +#define WM5100_DSP1LMIX_INPUT_4_VOLUME 0x947 +#define WM5100_DSP1RMIX_INPUT_1_SOURCE 0x948 +#define WM5100_DSP1RMIX_INPUT_1_VOLUME 0x949 +#define WM5100_DSP1RMIX_INPUT_2_SOURCE 0x94A +#define WM5100_DSP1RMIX_INPUT_2_VOLUME 0x94B +#define WM5100_DSP1RMIX_INPUT_3_SOURCE 0x94C +#define WM5100_DSP1RMIX_INPUT_3_VOLUME 0x94D +#define WM5100_DSP1RMIX_INPUT_4_SOURCE 0x94E +#define WM5100_DSP1RMIX_INPUT_4_VOLUME 0x94F +#define WM5100_DSP1AUX1MIX_INPUT_1_SOURCE 0x950 +#define WM5100_DSP1AUX2MIX_INPUT_1_SOURCE 0x958 +#define WM5100_DSP1AUX3MIX_INPUT_1_SOURCE 0x960 +#define WM5100_DSP1AUX4MIX_INPUT_1_SOURCE 0x968 +#define WM5100_DSP1AUX5MIX_INPUT_1_SOURCE 0x970 +#define WM5100_DSP1AUX6MIX_INPUT_1_SOURCE 0x978 +#define WM5100_DSP2LMIX_INPUT_1_SOURCE 0x980 +#define WM5100_DSP2LMIX_INPUT_1_VOLUME 0x981 +#define WM5100_DSP2LMIX_INPUT_2_SOURCE 0x982 +#define WM5100_DSP2LMIX_INPUT_2_VOLUME 0x983 +#define WM5100_DSP2LMIX_INPUT_3_SOURCE 0x984 +#define WM5100_DSP2LMIX_INPUT_3_VOLUME 0x985 +#define WM5100_DSP2LMIX_INPUT_4_SOURCE 0x986 +#define WM5100_DSP2LMIX_INPUT_4_VOLUME 0x987 +#define WM5100_DSP2RMIX_INPUT_1_SOURCE 0x988 +#define WM5100_DSP2RMIX_INPUT_1_VOLUME 0x989 +#define WM5100_DSP2RMIX_INPUT_2_SOURCE 0x98A +#define WM5100_DSP2RMIX_INPUT_2_VOLUME 0x98B +#define WM5100_DSP2RMIX_INPUT_3_SOURCE 0x98C +#define WM5100_DSP2RMIX_INPUT_3_VOLUME 0x98D +#define WM5100_DSP2RMIX_INPUT_4_SOURCE 0x98E +#define WM5100_DSP2RMIX_INPUT_4_VOLUME 0x98F +#define WM5100_DSP2AUX1MIX_INPUT_1_SOURCE 0x990 +#define WM5100_DSP2AUX2MIX_INPUT_1_SOURCE 0x998 +#define WM5100_DSP2AUX3MIX_INPUT_1_SOURCE 0x9A0 +#define WM5100_DSP2AUX4MIX_INPUT_1_SOURCE 0x9A8 +#define WM5100_DSP2AUX5MIX_INPUT_1_SOURCE 0x9B0 +#define WM5100_DSP2AUX6MIX_INPUT_1_SOURCE 0x9B8 +#define WM5100_DSP3LMIX_INPUT_1_SOURCE 0x9C0 +#define WM5100_DSP3LMIX_INPUT_1_VOLUME 0x9C1 +#define WM5100_DSP3LMIX_INPUT_2_SOURCE 0x9C2 +#define WM5100_DSP3LMIX_INPUT_2_VOLUME 0x9C3 +#define WM5100_DSP3LMIX_INPUT_3_SOURCE 0x9C4 +#define WM5100_DSP3LMIX_INPUT_3_VOLUME 0x9C5 +#define WM5100_DSP3LMIX_INPUT_4_SOURCE 0x9C6 +#define WM5100_DSP3LMIX_INPUT_4_VOLUME 0x9C7 +#define WM5100_DSP3RMIX_INPUT_1_SOURCE 0x9C8 +#define WM5100_DSP3RMIX_INPUT_1_VOLUME 0x9C9 +#define WM5100_DSP3RMIX_INPUT_2_SOURCE 0x9CA +#define WM5100_DSP3RMIX_INPUT_2_VOLUME 0x9CB +#define WM5100_DSP3RMIX_INPUT_3_SOURCE 0x9CC +#define WM5100_DSP3RMIX_INPUT_3_VOLUME 0x9CD +#define WM5100_DSP3RMIX_INPUT_4_SOURCE 0x9CE +#define WM5100_DSP3RMIX_INPUT_4_VOLUME 0x9CF +#define WM5100_DSP3AUX1MIX_INPUT_1_SOURCE 0x9D0 +#define WM5100_DSP3AUX2MIX_INPUT_1_SOURCE 0x9D8 +#define WM5100_DSP3AUX3MIX_INPUT_1_SOURCE 0x9E0 +#define WM5100_DSP3AUX4MIX_INPUT_1_SOURCE 0x9E8 +#define WM5100_DSP3AUX5MIX_INPUT_1_SOURCE 0x9F0 +#define WM5100_DSP3AUX6MIX_INPUT_1_SOURCE 0x9F8 +#define WM5100_ASRC1LMIX_INPUT_1_SOURCE 0xA80 +#define WM5100_ASRC1RMIX_INPUT_1_SOURCE 0xA88 +#define WM5100_ASRC2LMIX_INPUT_1_SOURCE 0xA90 +#define WM5100_ASRC2RMIX_INPUT_1_SOURCE 0xA98 +#define WM5100_ISRC1DEC1MIX_INPUT_1_SOURCE 0xB00 +#define WM5100_ISRC1DEC2MIX_INPUT_1_SOURCE 0xB08 +#define WM5100_ISRC1DEC3MIX_INPUT_1_SOURCE 0xB10 +#define WM5100_ISRC1DEC4MIX_INPUT_1_SOURCE 0xB18 +#define WM5100_ISRC1INT1MIX_INPUT_1_SOURCE 0xB20 +#define WM5100_ISRC1INT2MIX_INPUT_1_SOURCE 0xB28 +#define WM5100_ISRC1INT3MIX_INPUT_1_SOURCE 0xB30 +#define WM5100_ISRC1INT4MIX_INPUT_1_SOURCE 0xB38 +#define WM5100_ISRC2DEC1MIX_INPUT_1_SOURCE 0xB40 +#define WM5100_ISRC2DEC2MIX_INPUT_1_SOURCE 0xB48 +#define WM5100_ISRC2DEC3MIX_INPUT_1_SOURCE 0xB50 +#define WM5100_ISRC2DEC4MIX_INPUT_1_SOURCE 0xB58 +#define WM5100_ISRC2INT1MIX_INPUT_1_SOURCE 0xB60 +#define WM5100_ISRC2INT2MIX_INPUT_1_SOURCE 0xB68 +#define WM5100_ISRC2INT3MIX_INPUT_1_SOURCE 0xB70 +#define WM5100_ISRC2INT4MIX_INPUT_1_SOURCE 0xB78 +#define WM5100_GPIO_CTRL_1 0xC00 +#define WM5100_GPIO_CTRL_2 0xC01 +#define WM5100_GPIO_CTRL_3 0xC02 +#define WM5100_GPIO_CTRL_4 0xC03 +#define WM5100_GPIO_CTRL_5 0xC04 +#define WM5100_GPIO_CTRL_6 0xC05 +#define WM5100_MISC_PAD_CTRL_1 0xC23 +#define WM5100_MISC_PAD_CTRL_2 0xC24 +#define WM5100_MISC_PAD_CTRL_3 0xC25 +#define WM5100_MISC_PAD_CTRL_4 0xC26 +#define WM5100_MISC_PAD_CTRL_5 0xC27 +#define WM5100_MISC_GPIO_1 0xC28 +#define WM5100_INTERRUPT_STATUS_1 0xD00 +#define WM5100_INTERRUPT_STATUS_2 0xD01 +#define WM5100_INTERRUPT_STATUS_3 0xD02 +#define WM5100_INTERRUPT_STATUS_4 0xD03 +#define WM5100_INTERRUPT_RAW_STATUS_2 0xD04 +#define WM5100_INTERRUPT_RAW_STATUS_3 0xD05 +#define WM5100_INTERRUPT_RAW_STATUS_4 0xD06 +#define WM5100_INTERRUPT_STATUS_1_MASK 0xD07 +#define WM5100_INTERRUPT_STATUS_2_MASK 0xD08 +#define WM5100_INTERRUPT_STATUS_3_MASK 0xD09 +#define WM5100_INTERRUPT_STATUS_4_MASK 0xD0A +#define WM5100_INTERRUPT_CONTROL 0xD1F +#define WM5100_IRQ_DEBOUNCE_1 0xD20 +#define WM5100_IRQ_DEBOUNCE_2 0xD21 +#define WM5100_FX_CTRL 0xE00 +#define WM5100_EQ1_1 0xE10 +#define WM5100_EQ1_2 0xE11 +#define WM5100_EQ1_3 0xE12 +#define WM5100_EQ1_4 0xE13 +#define WM5100_EQ1_5 0xE14 +#define WM5100_EQ1_6 0xE15 +#define WM5100_EQ1_7 0xE16 +#define WM5100_EQ1_8 0xE17 +#define WM5100_EQ1_9 0xE18 +#define WM5100_EQ1_10 0xE19 +#define WM5100_EQ1_11 0xE1A +#define WM5100_EQ1_12 0xE1B +#define WM5100_EQ1_13 0xE1C +#define WM5100_EQ1_14 0xE1D +#define WM5100_EQ1_15 0xE1E +#define WM5100_EQ1_16 0xE1F +#define WM5100_EQ1_17 0xE20 +#define WM5100_EQ1_18 0xE21 +#define WM5100_EQ1_19 0xE22 +#define WM5100_EQ1_20 0xE23 +#define WM5100_EQ2_1 0xE26 +#define WM5100_EQ2_2 0xE27 +#define WM5100_EQ2_3 0xE28 +#define WM5100_EQ2_4 0xE29 +#define WM5100_EQ2_5 0xE2A +#define WM5100_EQ2_6 0xE2B +#define WM5100_EQ2_7 0xE2C +#define WM5100_EQ2_8 0xE2D +#define WM5100_EQ2_9 0xE2E +#define WM5100_EQ2_10 0xE2F +#define WM5100_EQ2_11 0xE30 +#define WM5100_EQ2_12 0xE31 +#define WM5100_EQ2_13 0xE32 +#define WM5100_EQ2_14 0xE33 +#define WM5100_EQ2_15 0xE34 +#define WM5100_EQ2_16 0xE35 +#define WM5100_EQ2_17 0xE36 +#define WM5100_EQ2_18 0xE37 +#define WM5100_EQ2_19 0xE38 +#define WM5100_EQ2_20 0xE39 +#define WM5100_EQ3_1 0xE3C +#define WM5100_EQ3_2 0xE3D +#define WM5100_EQ3_3 0xE3E +#define WM5100_EQ3_4 0xE3F +#define WM5100_EQ3_5 0xE40 +#define WM5100_EQ3_6 0xE41 +#define WM5100_EQ3_7 0xE42 +#define WM5100_EQ3_8 0xE43 +#define WM5100_EQ3_9 0xE44 +#define WM5100_EQ3_10 0xE45 +#define WM5100_EQ3_11 0xE46 +#define WM5100_EQ3_12 0xE47 +#define WM5100_EQ3_13 0xE48 +#define WM5100_EQ3_14 0xE49 +#define WM5100_EQ3_15 0xE4A +#define WM5100_EQ3_16 0xE4B +#define WM5100_EQ3_17 0xE4C +#define WM5100_EQ3_18 0xE4D +#define WM5100_EQ3_19 0xE4E +#define WM5100_EQ3_20 0xE4F +#define WM5100_EQ4_1 0xE52 +#define WM5100_EQ4_2 0xE53 +#define WM5100_EQ4_3 0xE54 +#define WM5100_EQ4_4 0xE55 +#define WM5100_EQ4_5 0xE56 +#define WM5100_EQ4_6 0xE57 +#define WM5100_EQ4_7 0xE58 +#define WM5100_EQ4_8 0xE59 +#define WM5100_EQ4_9 0xE5A +#define WM5100_EQ4_10 0xE5B +#define WM5100_EQ4_11 0xE5C +#define WM5100_EQ4_12 0xE5D +#define WM5100_EQ4_13 0xE5E +#define WM5100_EQ4_14 0xE5F +#define WM5100_EQ4_15 0xE60 +#define WM5100_EQ4_16 0xE61 +#define WM5100_EQ4_17 0xE62 +#define WM5100_EQ4_18 0xE63 +#define WM5100_EQ4_19 0xE64 +#define WM5100_EQ4_20 0xE65 +#define WM5100_DRC1_CTRL1 0xE80 +#define WM5100_DRC1_CTRL2 0xE81 +#define WM5100_DRC1_CTRL3 0xE82 +#define WM5100_DRC1_CTRL4 0xE83 +#define WM5100_DRC1_CTRL5 0xE84 +#define WM5100_HPLPF1_1 0xEC0 +#define WM5100_HPLPF1_2 0xEC1 +#define WM5100_HPLPF2_1 0xEC4 +#define WM5100_HPLPF2_2 0xEC5 +#define WM5100_HPLPF3_1 0xEC8 +#define WM5100_HPLPF3_2 0xEC9 +#define WM5100_HPLPF4_1 0xECC +#define WM5100_HPLPF4_2 0xECD +#define WM5100_DSP1_CONTROL_1 0xF00 +#define WM5100_DSP1_CONTROL_2 0xF02 +#define WM5100_DSP1_CONTROL_3 0xF03 +#define WM5100_DSP1_CONTROL_4 0xF04 +#define WM5100_DSP1_CONTROL_5 0xF06 +#define WM5100_DSP1_CONTROL_6 0xF07 +#define WM5100_DSP1_CONTROL_7 0xF08 +#define WM5100_DSP1_CONTROL_8 0xF09 +#define WM5100_DSP1_CONTROL_9 0xF0A +#define WM5100_DSP1_CONTROL_10 0xF0B +#define WM5100_DSP1_CONTROL_11 0xF0C +#define WM5100_DSP1_CONTROL_12 0xF0D +#define WM5100_DSP1_CONTROL_13 0xF0F +#define WM5100_DSP1_CONTROL_14 0xF10 +#define WM5100_DSP1_CONTROL_15 0xF11 +#define WM5100_DSP1_CONTROL_16 0xF12 +#define WM5100_DSP1_CONTROL_17 0xF13 +#define WM5100_DSP1_CONTROL_18 0xF14 +#define WM5100_DSP1_CONTROL_19 0xF16 +#define WM5100_DSP1_CONTROL_20 0xF17 +#define WM5100_DSP1_CONTROL_21 0xF18 +#define WM5100_DSP1_CONTROL_22 0xF1A +#define WM5100_DSP1_CONTROL_23 0xF1B +#define WM5100_DSP1_CONTROL_24 0xF1C +#define WM5100_DSP1_CONTROL_25 0xF1E +#define WM5100_DSP1_CONTROL_26 0xF20 +#define WM5100_DSP1_CONTROL_27 0xF21 +#define WM5100_DSP1_CONTROL_28 0xF22 +#define WM5100_DSP1_CONTROL_29 0xF23 +#define WM5100_DSP1_CONTROL_30 0xF24 +#define WM5100_DSP2_CONTROL_1 0x1000 +#define WM5100_DSP2_CONTROL_2 0x1002 +#define WM5100_DSP2_CONTROL_3 0x1003 +#define WM5100_DSP2_CONTROL_4 0x1004 +#define WM5100_DSP2_CONTROL_5 0x1006 +#define WM5100_DSP2_CONTROL_6 0x1007 +#define WM5100_DSP2_CONTROL_7 0x1008 +#define WM5100_DSP2_CONTROL_8 0x1009 +#define WM5100_DSP2_CONTROL_9 0x100A +#define WM5100_DSP2_CONTROL_10 0x100B +#define WM5100_DSP2_CONTROL_11 0x100C +#define WM5100_DSP2_CONTROL_12 0x100D +#define WM5100_DSP2_CONTROL_13 0x100F +#define WM5100_DSP2_CONTROL_14 0x1010 +#define WM5100_DSP2_CONTROL_15 0x1011 +#define WM5100_DSP2_CONTROL_16 0x1012 +#define WM5100_DSP2_CONTROL_17 0x1013 +#define WM5100_DSP2_CONTROL_18 0x1014 +#define WM5100_DSP2_CONTROL_19 0x1016 +#define WM5100_DSP2_CONTROL_20 0x1017 +#define WM5100_DSP2_CONTROL_21 0x1018 +#define WM5100_DSP2_CONTROL_22 0x101A +#define WM5100_DSP2_CONTROL_23 0x101B +#define WM5100_DSP2_CONTROL_24 0x101C +#define WM5100_DSP2_CONTROL_25 0x101E +#define WM5100_DSP2_CONTROL_26 0x1020 +#define WM5100_DSP2_CONTROL_27 0x1021 +#define WM5100_DSP2_CONTROL_28 0x1022 +#define WM5100_DSP2_CONTROL_29 0x1023 +#define WM5100_DSP2_CONTROL_30 0x1024 +#define WM5100_DSP3_CONTROL_1 0x1100 +#define WM5100_DSP3_CONTROL_2 0x1102 +#define WM5100_DSP3_CONTROL_3 0x1103 +#define WM5100_DSP3_CONTROL_4 0x1104 +#define WM5100_DSP3_CONTROL_5 0x1106 +#define WM5100_DSP3_CONTROL_6 0x1107 +#define WM5100_DSP3_CONTROL_7 0x1108 +#define WM5100_DSP3_CONTROL_8 0x1109 +#define WM5100_DSP3_CONTROL_9 0x110A +#define WM5100_DSP3_CONTROL_10 0x110B +#define WM5100_DSP3_CONTROL_11 0x110C +#define WM5100_DSP3_CONTROL_12 0x110D +#define WM5100_DSP3_CONTROL_13 0x110F +#define WM5100_DSP3_CONTROL_14 0x1110 +#define WM5100_DSP3_CONTROL_15 0x1111 +#define WM5100_DSP3_CONTROL_16 0x1112 +#define WM5100_DSP3_CONTROL_17 0x1113 +#define WM5100_DSP3_CONTROL_18 0x1114 +#define WM5100_DSP3_CONTROL_19 0x1116 +#define WM5100_DSP3_CONTROL_20 0x1117 +#define WM5100_DSP3_CONTROL_21 0x1118 +#define WM5100_DSP3_CONTROL_22 0x111A +#define WM5100_DSP3_CONTROL_23 0x111B +#define WM5100_DSP3_CONTROL_24 0x111C +#define WM5100_DSP3_CONTROL_25 0x111E +#define WM5100_DSP3_CONTROL_26 0x1120 +#define WM5100_DSP3_CONTROL_27 0x1121 +#define WM5100_DSP3_CONTROL_28 0x1122 +#define WM5100_DSP3_CONTROL_29 0x1123 +#define WM5100_DSP3_CONTROL_30 0x1124 +#define WM5100_DSP1_DM_0 0x4000 +#define WM5100_DSP1_DM_1 0x4001 +#define WM5100_DSP1_DM_2 0x4002 +#define WM5100_DSP1_DM_3 0x4003 +#define WM5100_DSP1_DM_508 0x41FC +#define WM5100_DSP1_DM_509 0x41FD +#define WM5100_DSP1_DM_510 0x41FE +#define WM5100_DSP1_DM_511 0x41FF +#define WM5100_DSP1_PM_0 0x4800 +#define WM5100_DSP1_PM_1 0x4801 +#define WM5100_DSP1_PM_2 0x4802 +#define WM5100_DSP1_PM_3 0x4803 +#define WM5100_DSP1_PM_4 0x4804 +#define WM5100_DSP1_PM_5 0x4805 +#define WM5100_DSP1_PM_1530 0x4DFA +#define WM5100_DSP1_PM_1531 0x4DFB +#define WM5100_DSP1_PM_1532 0x4DFC +#define WM5100_DSP1_PM_1533 0x4DFD +#define WM5100_DSP1_PM_1534 0x4DFE +#define WM5100_DSP1_PM_1535 0x4DFF +#define WM5100_DSP1_ZM_0 0x5000 +#define WM5100_DSP1_ZM_1 0x5001 +#define WM5100_DSP1_ZM_2 0x5002 +#define WM5100_DSP1_ZM_3 0x5003 +#define WM5100_DSP1_ZM_2044 0x57FC +#define WM5100_DSP1_ZM_2045 0x57FD +#define WM5100_DSP1_ZM_2046 0x57FE +#define WM5100_DSP1_ZM_2047 0x57FF +#define WM5100_DSP2_DM_0 0x6000 +#define WM5100_DSP2_DM_1 0x6001 +#define WM5100_DSP2_DM_2 0x6002 +#define WM5100_DSP2_DM_3 0x6003 +#define WM5100_DSP2_DM_508 0x61FC +#define WM5100_DSP2_DM_509 0x61FD +#define WM5100_DSP2_DM_510 0x61FE +#define WM5100_DSP2_DM_511 0x61FF +#define WM5100_DSP2_PM_0 0x6800 +#define WM5100_DSP2_PM_1 0x6801 +#define WM5100_DSP2_PM_2 0x6802 +#define WM5100_DSP2_PM_3 0x6803 +#define WM5100_DSP2_PM_4 0x6804 +#define WM5100_DSP2_PM_5 0x6805 +#define WM5100_DSP2_PM_1530 0x6DFA +#define WM5100_DSP2_PM_1531 0x6DFB +#define WM5100_DSP2_PM_1532 0x6DFC +#define WM5100_DSP2_PM_1533 0x6DFD +#define WM5100_DSP2_PM_1534 0x6DFE +#define WM5100_DSP2_PM_1535 0x6DFF +#define WM5100_DSP2_ZM_0 0x7000 +#define WM5100_DSP2_ZM_1 0x7001 +#define WM5100_DSP2_ZM_2 0x7002 +#define WM5100_DSP2_ZM_3 0x7003 +#define WM5100_DSP2_ZM_2044 0x77FC +#define WM5100_DSP2_ZM_2045 0x77FD +#define WM5100_DSP2_ZM_2046 0x77FE +#define WM5100_DSP2_ZM_2047 0x77FF +#define WM5100_DSP3_DM_0 0x8000 +#define WM5100_DSP3_DM_1 0x8001 +#define WM5100_DSP3_DM_2 0x8002 +#define WM5100_DSP3_DM_3 0x8003 +#define WM5100_DSP3_DM_508 0x81FC +#define WM5100_DSP3_DM_509 0x81FD +#define WM5100_DSP3_DM_510 0x81FE +#define WM5100_DSP3_DM_511 0x81FF +#define WM5100_DSP3_PM_0 0x8800 +#define WM5100_DSP3_PM_1 0x8801 +#define WM5100_DSP3_PM_2 0x8802 +#define WM5100_DSP3_PM_3 0x8803 +#define WM5100_DSP3_PM_4 0x8804 +#define WM5100_DSP3_PM_5 0x8805 +#define WM5100_DSP3_PM_1530 0x8DFA +#define WM5100_DSP3_PM_1531 0x8DFB +#define WM5100_DSP3_PM_1532 0x8DFC +#define WM5100_DSP3_PM_1533 0x8DFD +#define WM5100_DSP3_PM_1534 0x8DFE +#define WM5100_DSP3_PM_1535 0x8DFF +#define WM5100_DSP3_ZM_0 0x9000 +#define WM5100_DSP3_ZM_1 0x9001 +#define WM5100_DSP3_ZM_2 0x9002 +#define WM5100_DSP3_ZM_3 0x9003 +#define WM5100_DSP3_ZM_2044 0x97FC +#define WM5100_DSP3_ZM_2045 0x97FD +#define WM5100_DSP3_ZM_2046 0x97FE +#define WM5100_DSP3_ZM_2047 0x97FF + +#define WM5100_REGISTER_COUNT 1435 +#define WM5100_MAX_REGISTER 0x97FF + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - software reset + */ +#define WM5100_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM5100_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM5100_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R1 (0x01) - Device Revision + */ +#define WM5100_DEVICE_REVISION_MASK 0x000F /* DEVICE_REVISION - [3:0] */ +#define WM5100_DEVICE_REVISION_SHIFT 0 /* DEVICE_REVISION - [3:0] */ +#define WM5100_DEVICE_REVISION_WIDTH 4 /* DEVICE_REVISION - [3:0] */ + +/* + * R16 (0x10) - Ctrl IF 1 + */ +#define WM5100_AUTO_INC 0x0001 /* AUTO_INC */ +#define WM5100_AUTO_INC_MASK 0x0001 /* AUTO_INC */ +#define WM5100_AUTO_INC_SHIFT 0 /* AUTO_INC */ +#define WM5100_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +/* + * R32 (0x20) - Tone Generator 1 + */ +#define WM5100_TONE_RATE_MASK 0x3000 /* TONE_RATE - [13:12] */ +#define WM5100_TONE_RATE_SHIFT 12 /* TONE_RATE - [13:12] */ +#define WM5100_TONE_RATE_WIDTH 2 /* TONE_RATE - [13:12] */ +#define WM5100_TONE_OFFSET_MASK 0x0300 /* TONE_OFFSET - [9:8] */ +#define WM5100_TONE_OFFSET_SHIFT 8 /* TONE_OFFSET - [9:8] */ +#define WM5100_TONE_OFFSET_WIDTH 2 /* TONE_OFFSET - [9:8] */ +#define WM5100_TONE2_ENA 0x0002 /* TONE2_ENA */ +#define WM5100_TONE2_ENA_MASK 0x0002 /* TONE2_ENA */ +#define WM5100_TONE2_ENA_SHIFT 1 /* TONE2_ENA */ +#define WM5100_TONE2_ENA_WIDTH 1 /* TONE2_ENA */ +#define WM5100_TONE1_ENA 0x0001 /* TONE1_ENA */ +#define WM5100_TONE1_ENA_MASK 0x0001 /* TONE1_ENA */ +#define WM5100_TONE1_ENA_SHIFT 0 /* TONE1_ENA */ +#define WM5100_TONE1_ENA_WIDTH 1 /* TONE1_ENA */ + +/* + * R48 (0x30) - PWM Drive 1 + */ +#define WM5100_PWM_RATE_MASK 0x3000 /* PWM_RATE - [13:12] */ +#define WM5100_PWM_RATE_SHIFT 12 /* PWM_RATE - [13:12] */ +#define WM5100_PWM_RATE_WIDTH 2 /* PWM_RATE - [13:12] */ +#define WM5100_PWM_CLK_SEL_MASK 0x0300 /* PWM_CLK_SEL - [9:8] */ +#define WM5100_PWM_CLK_SEL_SHIFT 8 /* PWM_CLK_SEL - [9:8] */ +#define WM5100_PWM_CLK_SEL_WIDTH 2 /* PWM_CLK_SEL - [9:8] */ +#define WM5100_PWM2_OVD 0x0020 /* PWM2_OVD */ +#define WM5100_PWM2_OVD_MASK 0x0020 /* PWM2_OVD */ +#define WM5100_PWM2_OVD_SHIFT 5 /* PWM2_OVD */ +#define WM5100_PWM2_OVD_WIDTH 1 /* PWM2_OVD */ +#define WM5100_PWM1_OVD 0x0010 /* PWM1_OVD */ +#define WM5100_PWM1_OVD_MASK 0x0010 /* PWM1_OVD */ +#define WM5100_PWM1_OVD_SHIFT 4 /* PWM1_OVD */ +#define WM5100_PWM1_OVD_WIDTH 1 /* PWM1_OVD */ +#define WM5100_PWM2_ENA 0x0002 /* PWM2_ENA */ +#define WM5100_PWM2_ENA_MASK 0x0002 /* PWM2_ENA */ +#define WM5100_PWM2_ENA_SHIFT 1 /* PWM2_ENA */ +#define WM5100_PWM2_ENA_WIDTH 1 /* PWM2_ENA */ +#define WM5100_PWM1_ENA 0x0001 /* PWM1_ENA */ +#define WM5100_PWM1_ENA_MASK 0x0001 /* PWM1_ENA */ +#define WM5100_PWM1_ENA_SHIFT 0 /* PWM1_ENA */ +#define WM5100_PWM1_ENA_WIDTH 1 /* PWM1_ENA */ + +/* + * R49 (0x31) - PWM Drive 2 + */ +#define WM5100_PWM1_LVL_MASK 0x03FF /* PWM1_LVL - [9:0] */ +#define WM5100_PWM1_LVL_SHIFT 0 /* PWM1_LVL - [9:0] */ +#define WM5100_PWM1_LVL_WIDTH 10 /* PWM1_LVL - [9:0] */ + +/* + * R50 (0x32) - PWM Drive 3 + */ +#define WM5100_PWM2_LVL_MASK 0x03FF /* PWM2_LVL - [9:0] */ +#define WM5100_PWM2_LVL_SHIFT 0 /* PWM2_LVL - [9:0] */ +#define WM5100_PWM2_LVL_WIDTH 10 /* PWM2_LVL - [9:0] */ + +/* + * R256 (0x100) - Clocking 1 + */ +#define WM5100_CLK_32K_SRC_MASK 0x000F /* CLK_32K_SRC - [3:0] */ +#define WM5100_CLK_32K_SRC_SHIFT 0 /* CLK_32K_SRC - [3:0] */ +#define WM5100_CLK_32K_SRC_WIDTH 4 /* CLK_32K_SRC - [3:0] */ + +/* + * R257 (0x101) - Clocking 3 + */ +#define WM5100_SYSCLK_FREQ_MASK 0x0700 /* SYSCLK_FREQ - [10:8] */ +#define WM5100_SYSCLK_FREQ_SHIFT 8 /* SYSCLK_FREQ - [10:8] */ +#define WM5100_SYSCLK_FREQ_WIDTH 3 /* SYSCLK_FREQ - [10:8] */ +#define WM5100_SYSCLK_ENA 0x0040 /* SYSCLK_ENA */ +#define WM5100_SYSCLK_ENA_MASK 0x0040 /* SYSCLK_ENA */ +#define WM5100_SYSCLK_ENA_SHIFT 6 /* SYSCLK_ENA */ +#define WM5100_SYSCLK_ENA_WIDTH 1 /* SYSCLK_ENA */ +#define WM5100_SYSCLK_SRC_MASK 0x000F /* SYSCLK_SRC - [3:0] */ +#define WM5100_SYSCLK_SRC_SHIFT 0 /* SYSCLK_SRC - [3:0] */ +#define WM5100_SYSCLK_SRC_WIDTH 4 /* SYSCLK_SRC - [3:0] */ + +/* + * R258 (0x102) - Clocking 4 + */ +#define WM5100_SAMPLE_RATE_1_MASK 0x001F /* SAMPLE_RATE_1 - [4:0] */ +#define WM5100_SAMPLE_RATE_1_SHIFT 0 /* SAMPLE_RATE_1 - [4:0] */ +#define WM5100_SAMPLE_RATE_1_WIDTH 5 /* SAMPLE_RATE_1 - [4:0] */ + +/* + * R259 (0x103) - Clocking 5 + */ +#define WM5100_SAMPLE_RATE_2_MASK 0x001F /* SAMPLE_RATE_2 - [4:0] */ +#define WM5100_SAMPLE_RATE_2_SHIFT 0 /* SAMPLE_RATE_2 - [4:0] */ +#define WM5100_SAMPLE_RATE_2_WIDTH 5 /* SAMPLE_RATE_2 - [4:0] */ + +/* + * R260 (0x104) - Clocking 6 + */ +#define WM5100_SAMPLE_RATE_3_MASK 0x001F /* SAMPLE_RATE_3 - [4:0] */ +#define WM5100_SAMPLE_RATE_3_SHIFT 0 /* SAMPLE_RATE_3 - [4:0] */ +#define WM5100_SAMPLE_RATE_3_WIDTH 5 /* SAMPLE_RATE_3 - [4:0] */ + +/* + * R263 (0x107) - Clocking 7 + */ +#define WM5100_ASYNC_CLK_FREQ_MASK 0x0700 /* ASYNC_CLK_FREQ - [10:8] */ +#define WM5100_ASYNC_CLK_FREQ_SHIFT 8 /* ASYNC_CLK_FREQ - [10:8] */ +#define WM5100_ASYNC_CLK_FREQ_WIDTH 3 /* ASYNC_CLK_FREQ - [10:8] */ +#define WM5100_ASYNC_CLK_ENA 0x0040 /* ASYNC_CLK_ENA */ +#define WM5100_ASYNC_CLK_ENA_MASK 0x0040 /* ASYNC_CLK_ENA */ +#define WM5100_ASYNC_CLK_ENA_SHIFT 6 /* ASYNC_CLK_ENA */ +#define WM5100_ASYNC_CLK_ENA_WIDTH 1 /* ASYNC_CLK_ENA */ +#define WM5100_ASYNC_CLK_SRC_MASK 0x000F /* ASYNC_CLK_SRC - [3:0] */ +#define WM5100_ASYNC_CLK_SRC_SHIFT 0 /* ASYNC_CLK_SRC - [3:0] */ +#define WM5100_ASYNC_CLK_SRC_WIDTH 4 /* ASYNC_CLK_SRC - [3:0] */ + +/* + * R264 (0x108) - Clocking 8 + */ +#define WM5100_ASYNC_SAMPLE_RATE_MASK 0x001F /* ASYNC_SAMPLE_RATE - [4:0] */ +#define WM5100_ASYNC_SAMPLE_RATE_SHIFT 0 /* ASYNC_SAMPLE_RATE - [4:0] */ +#define WM5100_ASYNC_SAMPLE_RATE_WIDTH 5 /* ASYNC_SAMPLE_RATE - [4:0] */ + +/* + * R288 (0x120) - ASRC_ENABLE + */ +#define WM5100_ASRC2L_ENA 0x0008 /* ASRC2L_ENA */ +#define WM5100_ASRC2L_ENA_MASK 0x0008 /* ASRC2L_ENA */ +#define WM5100_ASRC2L_ENA_SHIFT 3 /* ASRC2L_ENA */ +#define WM5100_ASRC2L_ENA_WIDTH 1 /* ASRC2L_ENA */ +#define WM5100_ASRC2R_ENA 0x0004 /* ASRC2R_ENA */ +#define WM5100_ASRC2R_ENA_MASK 0x0004 /* ASRC2R_ENA */ +#define WM5100_ASRC2R_ENA_SHIFT 2 /* ASRC2R_ENA */ +#define WM5100_ASRC2R_ENA_WIDTH 1 /* ASRC2R_ENA */ +#define WM5100_ASRC1L_ENA 0x0002 /* ASRC1L_ENA */ +#define WM5100_ASRC1L_ENA_MASK 0x0002 /* ASRC1L_ENA */ +#define WM5100_ASRC1L_ENA_SHIFT 1 /* ASRC1L_ENA */ +#define WM5100_ASRC1L_ENA_WIDTH 1 /* ASRC1L_ENA */ +#define WM5100_ASRC1R_ENA 0x0001 /* ASRC1R_ENA */ +#define WM5100_ASRC1R_ENA_MASK 0x0001 /* ASRC1R_ENA */ +#define WM5100_ASRC1R_ENA_SHIFT 0 /* ASRC1R_ENA */ +#define WM5100_ASRC1R_ENA_WIDTH 1 /* ASRC1R_ENA */ + +/* + * R289 (0x121) - ASRC_STATUS + */ +#define WM5100_ASRC2L_ENA_STS 0x0008 /* ASRC2L_ENA_STS */ +#define WM5100_ASRC2L_ENA_STS_MASK 0x0008 /* ASRC2L_ENA_STS */ +#define WM5100_ASRC2L_ENA_STS_SHIFT 3 /* ASRC2L_ENA_STS */ +#define WM5100_ASRC2L_ENA_STS_WIDTH 1 /* ASRC2L_ENA_STS */ +#define WM5100_ASRC2R_ENA_STS 0x0004 /* ASRC2R_ENA_STS */ +#define WM5100_ASRC2R_ENA_STS_MASK 0x0004 /* ASRC2R_ENA_STS */ +#define WM5100_ASRC2R_ENA_STS_SHIFT 2 /* ASRC2R_ENA_STS */ +#define WM5100_ASRC2R_ENA_STS_WIDTH 1 /* ASRC2R_ENA_STS */ +#define WM5100_ASRC1L_ENA_STS 0x0002 /* ASRC1L_ENA_STS */ +#define WM5100_ASRC1L_ENA_STS_MASK 0x0002 /* ASRC1L_ENA_STS */ +#define WM5100_ASRC1L_ENA_STS_SHIFT 1 /* ASRC1L_ENA_STS */ +#define WM5100_ASRC1L_ENA_STS_WIDTH 1 /* ASRC1L_ENA_STS */ +#define WM5100_ASRC1R_ENA_STS 0x0001 /* ASRC1R_ENA_STS */ +#define WM5100_ASRC1R_ENA_STS_MASK 0x0001 /* ASRC1R_ENA_STS */ +#define WM5100_ASRC1R_ENA_STS_SHIFT 0 /* ASRC1R_ENA_STS */ +#define WM5100_ASRC1R_ENA_STS_WIDTH 1 /* ASRC1R_ENA_STS */ + +/* + * R290 (0x122) - ASRC_RATE1 + */ +#define WM5100_ASRC_RATE1_MASK 0x0006 /* ASRC_RATE1 - [2:1] */ +#define WM5100_ASRC_RATE1_SHIFT 1 /* ASRC_RATE1 - [2:1] */ +#define WM5100_ASRC_RATE1_WIDTH 2 /* ASRC_RATE1 - [2:1] */ + +/* + * R321 (0x141) - ISRC 1 CTRL 1 + */ +#define WM5100_ISRC1_DFS_ENA 0x2000 /* ISRC1_DFS_ENA */ +#define WM5100_ISRC1_DFS_ENA_MASK 0x2000 /* ISRC1_DFS_ENA */ +#define WM5100_ISRC1_DFS_ENA_SHIFT 13 /* ISRC1_DFS_ENA */ +#define WM5100_ISRC1_DFS_ENA_WIDTH 1 /* ISRC1_DFS_ENA */ +#define WM5100_ISRC1_CLK_SEL_MASK 0x0300 /* ISRC1_CLK_SEL - [9:8] */ +#define WM5100_ISRC1_CLK_SEL_SHIFT 8 /* ISRC1_CLK_SEL - [9:8] */ +#define WM5100_ISRC1_CLK_SEL_WIDTH 2 /* ISRC1_CLK_SEL - [9:8] */ +#define WM5100_ISRC1_FSH_MASK 0x000C /* ISRC1_FSH - [3:2] */ +#define WM5100_ISRC1_FSH_SHIFT 2 /* ISRC1_FSH - [3:2] */ +#define WM5100_ISRC1_FSH_WIDTH 2 /* ISRC1_FSH - [3:2] */ +#define WM5100_ISRC1_FSL_MASK 0x0003 /* ISRC1_FSL - [1:0] */ +#define WM5100_ISRC1_FSL_SHIFT 0 /* ISRC1_FSL - [1:0] */ +#define WM5100_ISRC1_FSL_WIDTH 2 /* ISRC1_FSL - [1:0] */ + +/* + * R322 (0x142) - ISRC 1 CTRL 2 + */ +#define WM5100_ISRC1_INT1_ENA 0x8000 /* ISRC1_INT1_ENA */ +#define WM5100_ISRC1_INT1_ENA_MASK 0x8000 /* ISRC1_INT1_ENA */ +#define WM5100_ISRC1_INT1_ENA_SHIFT 15 /* ISRC1_INT1_ENA */ +#define WM5100_ISRC1_INT1_ENA_WIDTH 1 /* ISRC1_INT1_ENA */ +#define WM5100_ISRC1_INT2_ENA 0x4000 /* ISRC1_INT2_ENA */ +#define WM5100_ISRC1_INT2_ENA_MASK 0x4000 /* ISRC1_INT2_ENA */ +#define WM5100_ISRC1_INT2_ENA_SHIFT 14 /* ISRC1_INT2_ENA */ +#define WM5100_ISRC1_INT2_ENA_WIDTH 1 /* ISRC1_INT2_ENA */ +#define WM5100_ISRC1_INT3_ENA 0x2000 /* ISRC1_INT3_ENA */ +#define WM5100_ISRC1_INT3_ENA_MASK 0x2000 /* ISRC1_INT3_ENA */ +#define WM5100_ISRC1_INT3_ENA_SHIFT 13 /* ISRC1_INT3_ENA */ +#define WM5100_ISRC1_INT3_ENA_WIDTH 1 /* ISRC1_INT3_ENA */ +#define WM5100_ISRC1_INT4_ENA 0x1000 /* ISRC1_INT4_ENA */ +#define WM5100_ISRC1_INT4_ENA_MASK 0x1000 /* ISRC1_INT4_ENA */ +#define WM5100_ISRC1_INT4_ENA_SHIFT 12 /* ISRC1_INT4_ENA */ +#define WM5100_ISRC1_INT4_ENA_WIDTH 1 /* ISRC1_INT4_ENA */ +#define WM5100_ISRC1_DEC1_ENA 0x0200 /* ISRC1_DEC1_ENA */ +#define WM5100_ISRC1_DEC1_ENA_MASK 0x0200 /* ISRC1_DEC1_ENA */ +#define WM5100_ISRC1_DEC1_ENA_SHIFT 9 /* ISRC1_DEC1_ENA */ +#define WM5100_ISRC1_DEC1_ENA_WIDTH 1 /* ISRC1_DEC1_ENA */ +#define WM5100_ISRC1_DEC2_ENA 0x0100 /* ISRC1_DEC2_ENA */ +#define WM5100_ISRC1_DEC2_ENA_MASK 0x0100 /* ISRC1_DEC2_ENA */ +#define WM5100_ISRC1_DEC2_ENA_SHIFT 8 /* ISRC1_DEC2_ENA */ +#define WM5100_ISRC1_DEC2_ENA_WIDTH 1 /* ISRC1_DEC2_ENA */ +#define WM5100_ISRC1_DEC3_ENA 0x0080 /* ISRC1_DEC3_ENA */ +#define WM5100_ISRC1_DEC3_ENA_MASK 0x0080 /* ISRC1_DEC3_ENA */ +#define WM5100_ISRC1_DEC3_ENA_SHIFT 7 /* ISRC1_DEC3_ENA */ +#define WM5100_ISRC1_DEC3_ENA_WIDTH 1 /* ISRC1_DEC3_ENA */ +#define WM5100_ISRC1_DEC4_ENA 0x0040 /* ISRC1_DEC4_ENA */ +#define WM5100_ISRC1_DEC4_ENA_MASK 0x0040 /* ISRC1_DEC4_ENA */ +#define WM5100_ISRC1_DEC4_ENA_SHIFT 6 /* ISRC1_DEC4_ENA */ +#define WM5100_ISRC1_DEC4_ENA_WIDTH 1 /* ISRC1_DEC4_ENA */ +#define WM5100_ISRC1_NOTCH_ENA 0x0001 /* ISRC1_NOTCH_ENA */ +#define WM5100_ISRC1_NOTCH_ENA_MASK 0x0001 /* ISRC1_NOTCH_ENA */ +#define WM5100_ISRC1_NOTCH_ENA_SHIFT 0 /* ISRC1_NOTCH_ENA */ +#define WM5100_ISRC1_NOTCH_ENA_WIDTH 1 /* ISRC1_NOTCH_ENA */ + +/* + * R323 (0x143) - ISRC 2 CTRL1 + */ +#define WM5100_ISRC2_DFS_ENA 0x2000 /* ISRC2_DFS_ENA */ +#define WM5100_ISRC2_DFS_ENA_MASK 0x2000 /* ISRC2_DFS_ENA */ +#define WM5100_ISRC2_DFS_ENA_SHIFT 13 /* ISRC2_DFS_ENA */ +#define WM5100_ISRC2_DFS_ENA_WIDTH 1 /* ISRC2_DFS_ENA */ +#define WM5100_ISRC2_CLK_SEL_MASK 0x0300 /* ISRC2_CLK_SEL - [9:8] */ +#define WM5100_ISRC2_CLK_SEL_SHIFT 8 /* ISRC2_CLK_SEL - [9:8] */ +#define WM5100_ISRC2_CLK_SEL_WIDTH 2 /* ISRC2_CLK_SEL - [9:8] */ +#define WM5100_ISRC2_FSH_MASK 0x000C /* ISRC2_FSH - [3:2] */ +#define WM5100_ISRC2_FSH_SHIFT 2 /* ISRC2_FSH - [3:2] */ +#define WM5100_ISRC2_FSH_WIDTH 2 /* ISRC2_FSH - [3:2] */ +#define WM5100_ISRC2_FSL_MASK 0x0003 /* ISRC2_FSL - [1:0] */ +#define WM5100_ISRC2_FSL_SHIFT 0 /* ISRC2_FSL - [1:0] */ +#define WM5100_ISRC2_FSL_WIDTH 2 /* ISRC2_FSL - [1:0] */ + +/* + * R324 (0x144) - ISRC 2 CTRL 2 + */ +#define WM5100_ISRC2_INT1_ENA 0x8000 /* ISRC2_INT1_ENA */ +#define WM5100_ISRC2_INT1_ENA_MASK 0x8000 /* ISRC2_INT1_ENA */ +#define WM5100_ISRC2_INT1_ENA_SHIFT 15 /* ISRC2_INT1_ENA */ +#define WM5100_ISRC2_INT1_ENA_WIDTH 1 /* ISRC2_INT1_ENA */ +#define WM5100_ISRC2_INT2_ENA 0x4000 /* ISRC2_INT2_ENA */ +#define WM5100_ISRC2_INT2_ENA_MASK 0x4000 /* ISRC2_INT2_ENA */ +#define WM5100_ISRC2_INT2_ENA_SHIFT 14 /* ISRC2_INT2_ENA */ +#define WM5100_ISRC2_INT2_ENA_WIDTH 1 /* ISRC2_INT2_ENA */ +#define WM5100_ISRC2_INT3_ENA 0x2000 /* ISRC2_INT3_ENA */ +#define WM5100_ISRC2_INT3_ENA_MASK 0x2000 /* ISRC2_INT3_ENA */ +#define WM5100_ISRC2_INT3_ENA_SHIFT 13 /* ISRC2_INT3_ENA */ +#define WM5100_ISRC2_INT3_ENA_WIDTH 1 /* ISRC2_INT3_ENA */ +#define WM5100_ISRC2_INT4_ENA 0x1000 /* ISRC2_INT4_ENA */ +#define WM5100_ISRC2_INT4_ENA_MASK 0x1000 /* ISRC2_INT4_ENA */ +#define WM5100_ISRC2_INT4_ENA_SHIFT 12 /* ISRC2_INT4_ENA */ +#define WM5100_ISRC2_INT4_ENA_WIDTH 1 /* ISRC2_INT4_ENA */ +#define WM5100_ISRC2_DEC1_ENA 0x0200 /* ISRC2_DEC1_ENA */ +#define WM5100_ISRC2_DEC1_ENA_MASK 0x0200 /* ISRC2_DEC1_ENA */ +#define WM5100_ISRC2_DEC1_ENA_SHIFT 9 /* ISRC2_DEC1_ENA */ +#define WM5100_ISRC2_DEC1_ENA_WIDTH 1 /* ISRC2_DEC1_ENA */ +#define WM5100_ISRC2_DEC2_ENA 0x0100 /* ISRC2_DEC2_ENA */ +#define WM5100_ISRC2_DEC2_ENA_MASK 0x0100 /* ISRC2_DEC2_ENA */ +#define WM5100_ISRC2_DEC2_ENA_SHIFT 8 /* ISRC2_DEC2_ENA */ +#define WM5100_ISRC2_DEC2_ENA_WIDTH 1 /* ISRC2_DEC2_ENA */ +#define WM5100_ISRC2_DEC3_ENA 0x0080 /* ISRC2_DEC3_ENA */ +#define WM5100_ISRC2_DEC3_ENA_MASK 0x0080 /* ISRC2_DEC3_ENA */ +#define WM5100_ISRC2_DEC3_ENA_SHIFT 7 /* ISRC2_DEC3_ENA */ +#define WM5100_ISRC2_DEC3_ENA_WIDTH 1 /* ISRC2_DEC3_ENA */ +#define WM5100_ISRC2_DEC4_ENA 0x0040 /* ISRC2_DEC4_ENA */ +#define WM5100_ISRC2_DEC4_ENA_MASK 0x0040 /* ISRC2_DEC4_ENA */ +#define WM5100_ISRC2_DEC4_ENA_SHIFT 6 /* ISRC2_DEC4_ENA */ +#define WM5100_ISRC2_DEC4_ENA_WIDTH 1 /* ISRC2_DEC4_ENA */ +#define WM5100_ISRC2_NOTCH_ENA 0x0001 /* ISRC2_NOTCH_ENA */ +#define WM5100_ISRC2_NOTCH_ENA_MASK 0x0001 /* ISRC2_NOTCH_ENA */ +#define WM5100_ISRC2_NOTCH_ENA_SHIFT 0 /* ISRC2_NOTCH_ENA */ +#define WM5100_ISRC2_NOTCH_ENA_WIDTH 1 /* ISRC2_NOTCH_ENA */ + +/* + * R386 (0x182) - FLL1 Control 1 + */ +#define WM5100_FLL1_ENA 0x0001 /* FLL1_ENA */ +#define WM5100_FLL1_ENA_MASK 0x0001 /* FLL1_ENA */ +#define WM5100_FLL1_ENA_SHIFT 0 /* FLL1_ENA */ +#define WM5100_FLL1_ENA_WIDTH 1 /* FLL1_ENA */ + +/* + * R387 (0x183) - FLL1 Control 2 + */ +#define WM5100_FLL1_OUTDIV_MASK 0x3F00 /* FLL1_OUTDIV - [13:8] */ +#define WM5100_FLL1_OUTDIV_SHIFT 8 /* FLL1_OUTDIV - [13:8] */ +#define WM5100_FLL1_OUTDIV_WIDTH 6 /* FLL1_OUTDIV - [13:8] */ +#define WM5100_FLL1_FRATIO_MASK 0x0007 /* FLL1_FRATIO - [2:0] */ +#define WM5100_FLL1_FRATIO_SHIFT 0 /* FLL1_FRATIO - [2:0] */ +#define WM5100_FLL1_FRATIO_WIDTH 3 /* FLL1_FRATIO - [2:0] */ + +/* + * R388 (0x184) - FLL1 Control 3 + */ +#define WM5100_FLL1_THETA_MASK 0xFFFF /* FLL1_THETA - [15:0] */ +#define WM5100_FLL1_THETA_SHIFT 0 /* FLL1_THETA - [15:0] */ +#define WM5100_FLL1_THETA_WIDTH 16 /* FLL1_THETA - [15:0] */ + +/* + * R390 (0x186) - FLL1 Control 5 + */ +#define WM5100_FLL1_N_MASK 0x03FF /* FLL1_N - [9:0] */ +#define WM5100_FLL1_N_SHIFT 0 /* FLL1_N - [9:0] */ +#define WM5100_FLL1_N_WIDTH 10 /* FLL1_N - [9:0] */ + +/* + * R391 (0x187) - FLL1 Control 6 + */ +#define WM5100_FLL1_REFCLK_DIV_MASK 0x00C0 /* FLL1_REFCLK_DIV - [7:6] */ +#define WM5100_FLL1_REFCLK_DIV_SHIFT 6 /* FLL1_REFCLK_DIV - [7:6] */ +#define WM5100_FLL1_REFCLK_DIV_WIDTH 2 /* FLL1_REFCLK_DIV - [7:6] */ +#define WM5100_FLL1_REFCLK_SRC_MASK 0x000F /* FLL1_REFCLK_SRC - [3:0] */ +#define WM5100_FLL1_REFCLK_SRC_SHIFT 0 /* FLL1_REFCLK_SRC - [3:0] */ +#define WM5100_FLL1_REFCLK_SRC_WIDTH 4 /* FLL1_REFCLK_SRC - [3:0] */ + +/* + * R392 (0x188) - FLL1 EFS 1 + */ +#define WM5100_FLL1_LAMBDA_MASK 0xFFFF /* FLL1_LAMBDA - [15:0] */ +#define WM5100_FLL1_LAMBDA_SHIFT 0 /* FLL1_LAMBDA - [15:0] */ +#define WM5100_FLL1_LAMBDA_WIDTH 16 /* FLL1_LAMBDA - [15:0] */ + +/* + * R418 (0x1A2) - FLL2 Control 1 + */ +#define WM5100_FLL2_ENA 0x0001 /* FLL2_ENA */ +#define WM5100_FLL2_ENA_MASK 0x0001 /* FLL2_ENA */ +#define WM5100_FLL2_ENA_SHIFT 0 /* FLL2_ENA */ +#define WM5100_FLL2_ENA_WIDTH 1 /* FLL2_ENA */ + +/* + * R419 (0x1A3) - FLL2 Control 2 + */ +#define WM5100_FLL2_OUTDIV_MASK 0x3F00 /* FLL2_OUTDIV - [13:8] */ +#define WM5100_FLL2_OUTDIV_SHIFT 8 /* FLL2_OUTDIV - [13:8] */ +#define WM5100_FLL2_OUTDIV_WIDTH 6 /* FLL2_OUTDIV - [13:8] */ +#define WM5100_FLL2_FRATIO_MASK 0x0007 /* FLL2_FRATIO - [2:0] */ +#define WM5100_FLL2_FRATIO_SHIFT 0 /* FLL2_FRATIO - [2:0] */ +#define WM5100_FLL2_FRATIO_WIDTH 3 /* FLL2_FRATIO - [2:0] */ + +/* + * R420 (0x1A4) - FLL2 Control 3 + */ +#define WM5100_FLL2_THETA_MASK 0xFFFF /* FLL2_THETA - [15:0] */ +#define WM5100_FLL2_THETA_SHIFT 0 /* FLL2_THETA - [15:0] */ +#define WM5100_FLL2_THETA_WIDTH 16 /* FLL2_THETA - [15:0] */ + +/* + * R422 (0x1A6) - FLL2 Control 5 + */ +#define WM5100_FLL2_N_MASK 0x03FF /* FLL2_N - [9:0] */ +#define WM5100_FLL2_N_SHIFT 0 /* FLL2_N - [9:0] */ +#define WM5100_FLL2_N_WIDTH 10 /* FLL2_N - [9:0] */ + +/* + * R423 (0x1A7) - FLL2 Control 6 + */ +#define WM5100_FLL2_REFCLK_DIV_MASK 0x00C0 /* FLL2_REFCLK_DIV - [7:6] */ +#define WM5100_FLL2_REFCLK_DIV_SHIFT 6 /* FLL2_REFCLK_DIV - [7:6] */ +#define WM5100_FLL2_REFCLK_DIV_WIDTH 2 /* FLL2_REFCLK_DIV - [7:6] */ +#define WM5100_FLL2_REFCLK_SRC_MASK 0x000F /* FLL2_REFCLK_SRC - [3:0] */ +#define WM5100_FLL2_REFCLK_SRC_SHIFT 0 /* FLL2_REFCLK_SRC - [3:0] */ +#define WM5100_FLL2_REFCLK_SRC_WIDTH 4 /* FLL2_REFCLK_SRC - [3:0] */ + +/* + * R424 (0x1A8) - FLL2 EFS 1 + */ +#define WM5100_FLL2_LAMBDA_MASK 0xFFFF /* FLL2_LAMBDA - [15:0] */ +#define WM5100_FLL2_LAMBDA_SHIFT 0 /* FLL2_LAMBDA - [15:0] */ +#define WM5100_FLL2_LAMBDA_WIDTH 16 /* FLL2_LAMBDA - [15:0] */ + +/* + * R512 (0x200) - Mic Charge Pump 1 + */ +#define WM5100_CP2_BYPASS 0x0020 /* CP2_BYPASS */ +#define WM5100_CP2_BYPASS_MASK 0x0020 /* CP2_BYPASS */ +#define WM5100_CP2_BYPASS_SHIFT 5 /* CP2_BYPASS */ +#define WM5100_CP2_BYPASS_WIDTH 1 /* CP2_BYPASS */ +#define WM5100_CP2_ENA 0x0001 /* CP2_ENA */ +#define WM5100_CP2_ENA_MASK 0x0001 /* CP2_ENA */ +#define WM5100_CP2_ENA_SHIFT 0 /* CP2_ENA */ +#define WM5100_CP2_ENA_WIDTH 1 /* CP2_ENA */ + +/* + * R513 (0x201) - Mic Charge Pump 2 + */ +#define WM5100_LDO2_VSEL_MASK 0xF800 /* LDO2_VSEL - [15:11] */ +#define WM5100_LDO2_VSEL_SHIFT 11 /* LDO2_VSEL - [15:11] */ +#define WM5100_LDO2_VSEL_WIDTH 5 /* LDO2_VSEL - [15:11] */ + +/* + * R514 (0x202) - HP Charge Pump 1 + */ +#define WM5100_CP1_ENA 0x0001 /* CP1_ENA */ +#define WM5100_CP1_ENA_MASK 0x0001 /* CP1_ENA */ +#define WM5100_CP1_ENA_SHIFT 0 /* CP1_ENA */ +#define WM5100_CP1_ENA_WIDTH 1 /* CP1_ENA */ + +/* + * R529 (0x211) - LDO1 Control + */ +#define WM5100_LDO1_BYPASS 0x0002 /* LDO1_BYPASS */ +#define WM5100_LDO1_BYPASS_MASK 0x0002 /* LDO1_BYPASS */ +#define WM5100_LDO1_BYPASS_SHIFT 1 /* LDO1_BYPASS */ +#define WM5100_LDO1_BYPASS_WIDTH 1 /* LDO1_BYPASS */ + +/* + * R533 (0x215) - Mic Bias Ctrl 1 + */ +#define WM5100_MICB1_DISCH 0x0040 /* MICB1_DISCH */ +#define WM5100_MICB1_DISCH_MASK 0x0040 /* MICB1_DISCH */ +#define WM5100_MICB1_DISCH_SHIFT 6 /* MICB1_DISCH */ +#define WM5100_MICB1_DISCH_WIDTH 1 /* MICB1_DISCH */ +#define WM5100_MICB1_RATE 0x0020 /* MICB1_RATE */ +#define WM5100_MICB1_RATE_MASK 0x0020 /* MICB1_RATE */ +#define WM5100_MICB1_RATE_SHIFT 5 /* MICB1_RATE */ +#define WM5100_MICB1_RATE_WIDTH 1 /* MICB1_RATE */ +#define WM5100_MICB1_LVL_MASK 0x001C /* MICB1_LVL - [4:2] */ +#define WM5100_MICB1_LVL_SHIFT 2 /* MICB1_LVL - [4:2] */ +#define WM5100_MICB1_LVL_WIDTH 3 /* MICB1_LVL - [4:2] */ +#define WM5100_MICB1_BYPASS 0x0002 /* MICB1_BYPASS */ +#define WM5100_MICB1_BYPASS_MASK 0x0002 /* MICB1_BYPASS */ +#define WM5100_MICB1_BYPASS_SHIFT 1 /* MICB1_BYPASS */ +#define WM5100_MICB1_BYPASS_WIDTH 1 /* MICB1_BYPASS */ +#define WM5100_MICB1_ENA 0x0001 /* MICB1_ENA */ +#define WM5100_MICB1_ENA_MASK 0x0001 /* MICB1_ENA */ +#define WM5100_MICB1_ENA_SHIFT 0 /* MICB1_ENA */ +#define WM5100_MICB1_ENA_WIDTH 1 /* MICB1_ENA */ + +/* + * R534 (0x216) - Mic Bias Ctrl 2 + */ +#define WM5100_MICB2_DISCH 0x0040 /* MICB2_DISCH */ +#define WM5100_MICB2_DISCH_MASK 0x0040 /* MICB2_DISCH */ +#define WM5100_MICB2_DISCH_SHIFT 6 /* MICB2_DISCH */ +#define WM5100_MICB2_DISCH_WIDTH 1 /* MICB2_DISCH */ +#define WM5100_MICB2_RATE 0x0020 /* MICB2_RATE */ +#define WM5100_MICB2_RATE_MASK 0x0020 /* MICB2_RATE */ +#define WM5100_MICB2_RATE_SHIFT 5 /* MICB2_RATE */ +#define WM5100_MICB2_RATE_WIDTH 1 /* MICB2_RATE */ +#define WM5100_MICB2_LVL_MASK 0x001C /* MICB2_LVL - [4:2] */ +#define WM5100_MICB2_LVL_SHIFT 2 /* MICB2_LVL - [4:2] */ +#define WM5100_MICB2_LVL_WIDTH 3 /* MICB2_LVL - [4:2] */ +#define WM5100_MICB2_BYPASS 0x0002 /* MICB2_BYPASS */ +#define WM5100_MICB2_BYPASS_MASK 0x0002 /* MICB2_BYPASS */ +#define WM5100_MICB2_BYPASS_SHIFT 1 /* MICB2_BYPASS */ +#define WM5100_MICB2_BYPASS_WIDTH 1 /* MICB2_BYPASS */ +#define WM5100_MICB2_ENA 0x0001 /* MICB2_ENA */ +#define WM5100_MICB2_ENA_MASK 0x0001 /* MICB2_ENA */ +#define WM5100_MICB2_ENA_SHIFT 0 /* MICB2_ENA */ +#define WM5100_MICB2_ENA_WIDTH 1 /* MICB2_ENA */ + +/* + * R535 (0x217) - Mic Bias Ctrl 3 + */ +#define WM5100_MICB3_DISCH 0x0040 /* MICB3_DISCH */ +#define WM5100_MICB3_DISCH_MASK 0x0040 /* MICB3_DISCH */ +#define WM5100_MICB3_DISCH_SHIFT 6 /* MICB3_DISCH */ +#define WM5100_MICB3_DISCH_WIDTH 1 /* MICB3_DISCH */ +#define WM5100_MICB3_RATE 0x0020 /* MICB3_RATE */ +#define WM5100_MICB3_RATE_MASK 0x0020 /* MICB3_RATE */ +#define WM5100_MICB3_RATE_SHIFT 5 /* MICB3_RATE */ +#define WM5100_MICB3_RATE_WIDTH 1 /* MICB3_RATE */ +#define WM5100_MICB3_LVL_MASK 0x001C /* MICB3_LVL - [4:2] */ +#define WM5100_MICB3_LVL_SHIFT 2 /* MICB3_LVL - [4:2] */ +#define WM5100_MICB3_LVL_WIDTH 3 /* MICB3_LVL - [4:2] */ +#define WM5100_MICB3_BYPASS 0x0002 /* MICB3_BYPASS */ +#define WM5100_MICB3_BYPASS_MASK 0x0002 /* MICB3_BYPASS */ +#define WM5100_MICB3_BYPASS_SHIFT 1 /* MICB3_BYPASS */ +#define WM5100_MICB3_BYPASS_WIDTH 1 /* MICB3_BYPASS */ +#define WM5100_MICB3_ENA 0x0001 /* MICB3_ENA */ +#define WM5100_MICB3_ENA_MASK 0x0001 /* MICB3_ENA */ +#define WM5100_MICB3_ENA_SHIFT 0 /* MICB3_ENA */ +#define WM5100_MICB3_ENA_WIDTH 1 /* MICB3_ENA */ + +/* + * R640 (0x280) - Accessory Detect Mode 1 + */ +#define WM5100_ACCDET_BIAS_SRC_MASK 0xC000 /* ACCDET_BIAS_SRC - [15:14] */ +#define WM5100_ACCDET_BIAS_SRC_SHIFT 14 /* ACCDET_BIAS_SRC - [15:14] */ +#define WM5100_ACCDET_BIAS_SRC_WIDTH 2 /* ACCDET_BIAS_SRC - [15:14] */ +#define WM5100_ACCDET_SRC 0x2000 /* ACCDET_SRC */ +#define WM5100_ACCDET_SRC_MASK 0x2000 /* ACCDET_SRC */ +#define WM5100_ACCDET_SRC_SHIFT 13 /* ACCDET_SRC */ +#define WM5100_ACCDET_SRC_WIDTH 1 /* ACCDET_SRC */ +#define WM5100_ACCDET_MODE_MASK 0x0003 /* ACCDET_MODE - [1:0] */ +#define WM5100_ACCDET_MODE_SHIFT 0 /* ACCDET_MODE - [1:0] */ +#define WM5100_ACCDET_MODE_WIDTH 2 /* ACCDET_MODE - [1:0] */ + +/* + * R648 (0x288) - Headphone Detect 1 + */ +#define WM5100_HP_HOLDTIME_MASK 0x00E0 /* HP_HOLDTIME - [7:5] */ +#define WM5100_HP_HOLDTIME_SHIFT 5 /* HP_HOLDTIME - [7:5] */ +#define WM5100_HP_HOLDTIME_WIDTH 3 /* HP_HOLDTIME - [7:5] */ +#define WM5100_HP_CLK_DIV_MASK 0x0018 /* HP_CLK_DIV - [4:3] */ +#define WM5100_HP_CLK_DIV_SHIFT 3 /* HP_CLK_DIV - [4:3] */ +#define WM5100_HP_CLK_DIV_WIDTH 2 /* HP_CLK_DIV - [4:3] */ +#define WM5100_HP_STEP_SIZE 0x0002 /* HP_STEP_SIZE */ +#define WM5100_HP_STEP_SIZE_MASK 0x0002 /* HP_STEP_SIZE */ +#define WM5100_HP_STEP_SIZE_SHIFT 1 /* HP_STEP_SIZE */ +#define WM5100_HP_STEP_SIZE_WIDTH 1 /* HP_STEP_SIZE */ +#define WM5100_HP_POLL 0x0001 /* HP_POLL */ +#define WM5100_HP_POLL_MASK 0x0001 /* HP_POLL */ +#define WM5100_HP_POLL_SHIFT 0 /* HP_POLL */ +#define WM5100_HP_POLL_WIDTH 1 /* HP_POLL */ + +/* + * R649 (0x289) - Headphone Detect 2 + */ +#define WM5100_HP_DONE 0x0080 /* HP_DONE */ +#define WM5100_HP_DONE_MASK 0x0080 /* HP_DONE */ +#define WM5100_HP_DONE_SHIFT 7 /* HP_DONE */ +#define WM5100_HP_DONE_WIDTH 1 /* HP_DONE */ +#define WM5100_HP_LVL_MASK 0x007F /* HP_LVL - [6:0] */ +#define WM5100_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */ +#define WM5100_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */ + +/* + * R656 (0x290) - Mic Detect 1 + */ +#define WM5100_ACCDET_BIAS_STARTTIME_MASK 0xF000 /* ACCDET_BIAS_STARTTIME - [15:12] */ +#define WM5100_ACCDET_BIAS_STARTTIME_SHIFT 12 /* ACCDET_BIAS_STARTTIME - [15:12] */ +#define WM5100_ACCDET_BIAS_STARTTIME_WIDTH 4 /* ACCDET_BIAS_STARTTIME - [15:12] */ +#define WM5100_ACCDET_RATE_MASK 0x0F00 /* ACCDET_RATE - [11:8] */ +#define WM5100_ACCDET_RATE_SHIFT 8 /* ACCDET_RATE - [11:8] */ +#define WM5100_ACCDET_RATE_WIDTH 4 /* ACCDET_RATE - [11:8] */ +#define WM5100_ACCDET_DBTIME 0x0002 /* ACCDET_DBTIME */ +#define WM5100_ACCDET_DBTIME_MASK 0x0002 /* ACCDET_DBTIME */ +#define WM5100_ACCDET_DBTIME_SHIFT 1 /* ACCDET_DBTIME */ +#define WM5100_ACCDET_DBTIME_WIDTH 1 /* ACCDET_DBTIME */ +#define WM5100_ACCDET_ENA 0x0001 /* ACCDET_ENA */ +#define WM5100_ACCDET_ENA_MASK 0x0001 /* ACCDET_ENA */ +#define WM5100_ACCDET_ENA_SHIFT 0 /* ACCDET_ENA */ +#define WM5100_ACCDET_ENA_WIDTH 1 /* ACCDET_ENA */ + +/* + * R657 (0x291) - Mic Detect 2 + */ +#define WM5100_ACCDET_LVL_SEL_MASK 0x00FF /* ACCDET_LVL_SEL - [7:0] */ +#define WM5100_ACCDET_LVL_SEL_SHIFT 0 /* ACCDET_LVL_SEL - [7:0] */ +#define WM5100_ACCDET_LVL_SEL_WIDTH 8 /* ACCDET_LVL_SEL - [7:0] */ + +/* + * R658 (0x292) - Mic Detect 3 + */ +#define WM5100_ACCDET_LVL_MASK 0x07FC /* ACCDET_LVL - [10:2] */ +#define WM5100_ACCDET_LVL_SHIFT 2 /* ACCDET_LVL - [10:2] */ +#define WM5100_ACCDET_LVL_WIDTH 9 /* ACCDET_LVL - [10:2] */ +#define WM5100_ACCDET_VALID 0x0002 /* ACCDET_VALID */ +#define WM5100_ACCDET_VALID_MASK 0x0002 /* ACCDET_VALID */ +#define WM5100_ACCDET_VALID_SHIFT 1 /* ACCDET_VALID */ +#define WM5100_ACCDET_VALID_WIDTH 1 /* ACCDET_VALID */ +#define WM5100_ACCDET_STS 0x0001 /* ACCDET_STS */ +#define WM5100_ACCDET_STS_MASK 0x0001 /* ACCDET_STS */ +#define WM5100_ACCDET_STS_SHIFT 0 /* ACCDET_STS */ +#define WM5100_ACCDET_STS_WIDTH 1 /* ACCDET_STS */ + +/* + * R699 (0x2BB) - Misc Control + */ +#define WM5100_HPCOM_SRC 0x200 /* HPCOM_SRC */ +#define WM5100_HPCOM_SRC_SHIFT 9 /* HPCOM_SRC */ + +/* + * R769 (0x301) - Input Enables + */ +#define WM5100_IN4L_ENA 0x0080 /* IN4L_ENA */ +#define WM5100_IN4L_ENA_MASK 0x0080 /* IN4L_ENA */ +#define WM5100_IN4L_ENA_SHIFT 7 /* IN4L_ENA */ +#define WM5100_IN4L_ENA_WIDTH 1 /* IN4L_ENA */ +#define WM5100_IN4R_ENA 0x0040 /* IN4R_ENA */ +#define WM5100_IN4R_ENA_MASK 0x0040 /* IN4R_ENA */ +#define WM5100_IN4R_ENA_SHIFT 6 /* IN4R_ENA */ +#define WM5100_IN4R_ENA_WIDTH 1 /* IN4R_ENA */ +#define WM5100_IN3L_ENA 0x0020 /* IN3L_ENA */ +#define WM5100_IN3L_ENA_MASK 0x0020 /* IN3L_ENA */ +#define WM5100_IN3L_ENA_SHIFT 5 /* IN3L_ENA */ +#define WM5100_IN3L_ENA_WIDTH 1 /* IN3L_ENA */ +#define WM5100_IN3R_ENA 0x0010 /* IN3R_ENA */ +#define WM5100_IN3R_ENA_MASK 0x0010 /* IN3R_ENA */ +#define WM5100_IN3R_ENA_SHIFT 4 /* IN3R_ENA */ +#define WM5100_IN3R_ENA_WIDTH 1 /* IN3R_ENA */ +#define WM5100_IN2L_ENA 0x0008 /* IN2L_ENA */ +#define WM5100_IN2L_ENA_MASK 0x0008 /* IN2L_ENA */ +#define WM5100_IN2L_ENA_SHIFT 3 /* IN2L_ENA */ +#define WM5100_IN2L_ENA_WIDTH 1 /* IN2L_ENA */ +#define WM5100_IN2R_ENA 0x0004 /* IN2R_ENA */ +#define WM5100_IN2R_ENA_MASK 0x0004 /* IN2R_ENA */ +#define WM5100_IN2R_ENA_SHIFT 2 /* IN2R_ENA */ +#define WM5100_IN2R_ENA_WIDTH 1 /* IN2R_ENA */ +#define WM5100_IN1L_ENA 0x0002 /* IN1L_ENA */ +#define WM5100_IN1L_ENA_MASK 0x0002 /* IN1L_ENA */ +#define WM5100_IN1L_ENA_SHIFT 1 /* IN1L_ENA */ +#define WM5100_IN1L_ENA_WIDTH 1 /* IN1L_ENA */ +#define WM5100_IN1R_ENA 0x0001 /* IN1R_ENA */ +#define WM5100_IN1R_ENA_MASK 0x0001 /* IN1R_ENA */ +#define WM5100_IN1R_ENA_SHIFT 0 /* IN1R_ENA */ +#define WM5100_IN1R_ENA_WIDTH 1 /* IN1R_ENA */ + +/* + * R770 (0x302) - Input Enables Status + */ +#define WM5100_IN4L_ENA_STS 0x0080 /* IN4L_ENA_STS */ +#define WM5100_IN4L_ENA_STS_MASK 0x0080 /* IN4L_ENA_STS */ +#define WM5100_IN4L_ENA_STS_SHIFT 7 /* IN4L_ENA_STS */ +#define WM5100_IN4L_ENA_STS_WIDTH 1 /* IN4L_ENA_STS */ +#define WM5100_IN4R_ENA_STS 0x0040 /* IN4R_ENA_STS */ +#define WM5100_IN4R_ENA_STS_MASK 0x0040 /* IN4R_ENA_STS */ +#define WM5100_IN4R_ENA_STS_SHIFT 6 /* IN4R_ENA_STS */ +#define WM5100_IN4R_ENA_STS_WIDTH 1 /* IN4R_ENA_STS */ +#define WM5100_IN3L_ENA_STS 0x0020 /* IN3L_ENA_STS */ +#define WM5100_IN3L_ENA_STS_MASK 0x0020 /* IN3L_ENA_STS */ +#define WM5100_IN3L_ENA_STS_SHIFT 5 /* IN3L_ENA_STS */ +#define WM5100_IN3L_ENA_STS_WIDTH 1 /* IN3L_ENA_STS */ +#define WM5100_IN3R_ENA_STS 0x0010 /* IN3R_ENA_STS */ +#define WM5100_IN3R_ENA_STS_MASK 0x0010 /* IN3R_ENA_STS */ +#define WM5100_IN3R_ENA_STS_SHIFT 4 /* IN3R_ENA_STS */ +#define WM5100_IN3R_ENA_STS_WIDTH 1 /* IN3R_ENA_STS */ +#define WM5100_IN2L_ENA_STS 0x0008 /* IN2L_ENA_STS */ +#define WM5100_IN2L_ENA_STS_MASK 0x0008 /* IN2L_ENA_STS */ +#define WM5100_IN2L_ENA_STS_SHIFT 3 /* IN2L_ENA_STS */ +#define WM5100_IN2L_ENA_STS_WIDTH 1 /* IN2L_ENA_STS */ +#define WM5100_IN2R_ENA_STS 0x0004 /* IN2R_ENA_STS */ +#define WM5100_IN2R_ENA_STS_MASK 0x0004 /* IN2R_ENA_STS */ +#define WM5100_IN2R_ENA_STS_SHIFT 2 /* IN2R_ENA_STS */ +#define WM5100_IN2R_ENA_STS_WIDTH 1 /* IN2R_ENA_STS */ +#define WM5100_IN1L_ENA_STS 0x0002 /* IN1L_ENA_STS */ +#define WM5100_IN1L_ENA_STS_MASK 0x0002 /* IN1L_ENA_STS */ +#define WM5100_IN1L_ENA_STS_SHIFT 1 /* IN1L_ENA_STS */ +#define WM5100_IN1L_ENA_STS_WIDTH 1 /* IN1L_ENA_STS */ +#define WM5100_IN1R_ENA_STS 0x0001 /* IN1R_ENA_STS */ +#define WM5100_IN1R_ENA_STS_MASK 0x0001 /* IN1R_ENA_STS */ +#define WM5100_IN1R_ENA_STS_SHIFT 0 /* IN1R_ENA_STS */ +#define WM5100_IN1R_ENA_STS_WIDTH 1 /* IN1R_ENA_STS */ + +/* + * R784 (0x310) - IN1L Control + */ +#define WM5100_IN_RATE_MASK 0xC000 /* IN_RATE - [15:14] */ +#define WM5100_IN_RATE_SHIFT 14 /* IN_RATE - [15:14] */ +#define WM5100_IN_RATE_WIDTH 2 /* IN_RATE - [15:14] */ +#define WM5100_IN1_OSR 0x2000 /* IN1_OSR */ +#define WM5100_IN1_OSR_MASK 0x2000 /* IN1_OSR */ +#define WM5100_IN1_OSR_SHIFT 13 /* IN1_OSR */ +#define WM5100_IN1_OSR_WIDTH 1 /* IN1_OSR */ +#define WM5100_IN1_DMIC_SUP_MASK 0x1800 /* IN1_DMIC_SUP - [12:11] */ +#define WM5100_IN1_DMIC_SUP_SHIFT 11 /* IN1_DMIC_SUP - [12:11] */ +#define WM5100_IN1_DMIC_SUP_WIDTH 2 /* IN1_DMIC_SUP - [12:11] */ +#define WM5100_IN1_MODE_MASK 0x0600 /* IN1_MODE - [10:9] */ +#define WM5100_IN1_MODE_SHIFT 9 /* IN1_MODE - [10:9] */ +#define WM5100_IN1_MODE_WIDTH 2 /* IN1_MODE - [10:9] */ +#define WM5100_IN1L_PGA_VOL_MASK 0x00FE /* IN1L_PGA_VOL - [7:1] */ +#define WM5100_IN1L_PGA_VOL_SHIFT 1 /* IN1L_PGA_VOL - [7:1] */ +#define WM5100_IN1L_PGA_VOL_WIDTH 7 /* IN1L_PGA_VOL - [7:1] */ + +/* + * R785 (0x311) - IN1R Control + */ +#define WM5100_IN1R_PGA_VOL_MASK 0x00FE /* IN1R_PGA_VOL - [7:1] */ +#define WM5100_IN1R_PGA_VOL_SHIFT 1 /* IN1R_PGA_VOL - [7:1] */ +#define WM5100_IN1R_PGA_VOL_WIDTH 7 /* IN1R_PGA_VOL - [7:1] */ + +/* + * R786 (0x312) - IN2L Control + */ +#define WM5100_IN2_OSR 0x2000 /* IN2_OSR */ +#define WM5100_IN2_OSR_MASK 0x2000 /* IN2_OSR */ +#define WM5100_IN2_OSR_SHIFT 13 /* IN2_OSR */ +#define WM5100_IN2_OSR_WIDTH 1 /* IN2_OSR */ +#define WM5100_IN2_DMIC_SUP_MASK 0x1800 /* IN2_DMIC_SUP - [12:11] */ +#define WM5100_IN2_DMIC_SUP_SHIFT 11 /* IN2_DMIC_SUP - [12:11] */ +#define WM5100_IN2_DMIC_SUP_WIDTH 2 /* IN2_DMIC_SUP - [12:11] */ +#define WM5100_IN2_MODE_MASK 0x0600 /* IN2_MODE - [10:9] */ +#define WM5100_IN2_MODE_SHIFT 9 /* IN2_MODE - [10:9] */ +#define WM5100_IN2_MODE_WIDTH 2 /* IN2_MODE - [10:9] */ +#define WM5100_IN2L_PGA_VOL_MASK 0x00FE /* IN2L_PGA_VOL - [7:1] */ +#define WM5100_IN2L_PGA_VOL_SHIFT 1 /* IN2L_PGA_VOL - [7:1] */ +#define WM5100_IN2L_PGA_VOL_WIDTH 7 /* IN2L_PGA_VOL - [7:1] */ + +/* + * R787 (0x313) - IN2R Control + */ +#define WM5100_IN2R_PGA_VOL_MASK 0x00FE /* IN2R_PGA_VOL - [7:1] */ +#define WM5100_IN2R_PGA_VOL_SHIFT 1 /* IN2R_PGA_VOL - [7:1] */ +#define WM5100_IN2R_PGA_VOL_WIDTH 7 /* IN2R_PGA_VOL - [7:1] */ + +/* + * R788 (0x314) - IN3L Control + */ +#define WM5100_IN3_OSR 0x2000 /* IN3_OSR */ +#define WM5100_IN3_OSR_MASK 0x2000 /* IN3_OSR */ +#define WM5100_IN3_OSR_SHIFT 13 /* IN3_OSR */ +#define WM5100_IN3_OSR_WIDTH 1 /* IN3_OSR */ +#define WM5100_IN3_DMIC_SUP_MASK 0x1800 /* IN3_DMIC_SUP - [12:11] */ +#define WM5100_IN3_DMIC_SUP_SHIFT 11 /* IN3_DMIC_SUP - [12:11] */ +#define WM5100_IN3_DMIC_SUP_WIDTH 2 /* IN3_DMIC_SUP - [12:11] */ +#define WM5100_IN3_MODE_MASK 0x0600 /* IN3_MODE - [10:9] */ +#define WM5100_IN3_MODE_SHIFT 9 /* IN3_MODE - [10:9] */ +#define WM5100_IN3_MODE_WIDTH 2 /* IN3_MODE - [10:9] */ +#define WM5100_IN3L_PGA_VOL_MASK 0x00FE /* IN3L_PGA_VOL - [7:1] */ +#define WM5100_IN3L_PGA_VOL_SHIFT 1 /* IN3L_PGA_VOL - [7:1] */ +#define WM5100_IN3L_PGA_VOL_WIDTH 7 /* IN3L_PGA_VOL - [7:1] */ + +/* + * R789 (0x315) - IN3R Control + */ +#define WM5100_IN3R_PGA_VOL_MASK 0x00FE /* IN3R_PGA_VOL - [7:1] */ +#define WM5100_IN3R_PGA_VOL_SHIFT 1 /* IN3R_PGA_VOL - [7:1] */ +#define WM5100_IN3R_PGA_VOL_WIDTH 7 /* IN3R_PGA_VOL - [7:1] */ + +/* + * R790 (0x316) - IN4L Control + */ +#define WM5100_IN4_OSR 0x2000 /* IN4_OSR */ +#define WM5100_IN4_OSR_MASK 0x2000 /* IN4_OSR */ +#define WM5100_IN4_OSR_SHIFT 13 /* IN4_OSR */ +#define WM5100_IN4_OSR_WIDTH 1 /* IN4_OSR */ +#define WM5100_IN4_DMIC_SUP_MASK 0x1800 /* IN4_DMIC_SUP - [12:11] */ +#define WM5100_IN4_DMIC_SUP_SHIFT 11 /* IN4_DMIC_SUP - [12:11] */ +#define WM5100_IN4_DMIC_SUP_WIDTH 2 /* IN4_DMIC_SUP - [12:11] */ +#define WM5100_IN4_MODE_MASK 0x0600 /* IN4_MODE - [10:9] */ +#define WM5100_IN4_MODE_SHIFT 9 /* IN4_MODE - [10:9] */ +#define WM5100_IN4_MODE_WIDTH 2 /* IN4_MODE - [10:9] */ +#define WM5100_IN4L_PGA_VOL_MASK 0x00FE /* IN4L_PGA_VOL - [7:1] */ +#define WM5100_IN4L_PGA_VOL_SHIFT 1 /* IN4L_PGA_VOL - [7:1] */ +#define WM5100_IN4L_PGA_VOL_WIDTH 7 /* IN4L_PGA_VOL - [7:1] */ + +/* + * R791 (0x317) - IN4R Control + */ +#define WM5100_IN4R_PGA_VOL_MASK 0x00FE /* IN4R_PGA_VOL - [7:1] */ +#define WM5100_IN4R_PGA_VOL_SHIFT 1 /* IN4R_PGA_VOL - [7:1] */ +#define WM5100_IN4R_PGA_VOL_WIDTH 7 /* IN4R_PGA_VOL - [7:1] */ + +/* + * R792 (0x318) - RXANC_SRC + */ +#define WM5100_IN_RXANC_SEL_MASK 0x0007 /* IN_RXANC_SEL - [2:0] */ +#define WM5100_IN_RXANC_SEL_SHIFT 0 /* IN_RXANC_SEL - [2:0] */ +#define WM5100_IN_RXANC_SEL_WIDTH 3 /* IN_RXANC_SEL - [2:0] */ + +/* + * R793 (0x319) - Input Volume Ramp + */ +#define WM5100_IN_VD_RAMP_MASK 0x0070 /* IN_VD_RAMP - [6:4] */ +#define WM5100_IN_VD_RAMP_SHIFT 4 /* IN_VD_RAMP - [6:4] */ +#define WM5100_IN_VD_RAMP_WIDTH 3 /* IN_VD_RAMP - [6:4] */ +#define WM5100_IN_VI_RAMP_MASK 0x0007 /* IN_VI_RAMP - [2:0] */ +#define WM5100_IN_VI_RAMP_SHIFT 0 /* IN_VI_RAMP - [2:0] */ +#define WM5100_IN_VI_RAMP_WIDTH 3 /* IN_VI_RAMP - [2:0] */ + +/* + * R800 (0x320) - ADC Digital Volume 1L + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN1L_MUTE 0x0100 /* IN1L_MUTE */ +#define WM5100_IN1L_MUTE_MASK 0x0100 /* IN1L_MUTE */ +#define WM5100_IN1L_MUTE_SHIFT 8 /* IN1L_MUTE */ +#define WM5100_IN1L_MUTE_WIDTH 1 /* IN1L_MUTE */ +#define WM5100_IN1L_VOL_MASK 0x00FF /* IN1L_VOL - [7:0] */ +#define WM5100_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [7:0] */ +#define WM5100_IN1L_VOL_WIDTH 8 /* IN1L_VOL - [7:0] */ + +/* + * R801 (0x321) - ADC Digital Volume 1R + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN1R_MUTE 0x0100 /* IN1R_MUTE */ +#define WM5100_IN1R_MUTE_MASK 0x0100 /* IN1R_MUTE */ +#define WM5100_IN1R_MUTE_SHIFT 8 /* IN1R_MUTE */ +#define WM5100_IN1R_MUTE_WIDTH 1 /* IN1R_MUTE */ +#define WM5100_IN1R_VOL_MASK 0x00FF /* IN1R_VOL - [7:0] */ +#define WM5100_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [7:0] */ +#define WM5100_IN1R_VOL_WIDTH 8 /* IN1R_VOL - [7:0] */ + +/* + * R802 (0x322) - ADC Digital Volume 2L + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN2L_MUTE 0x0100 /* IN2L_MUTE */ +#define WM5100_IN2L_MUTE_MASK 0x0100 /* IN2L_MUTE */ +#define WM5100_IN2L_MUTE_SHIFT 8 /* IN2L_MUTE */ +#define WM5100_IN2L_MUTE_WIDTH 1 /* IN2L_MUTE */ +#define WM5100_IN2L_VOL_MASK 0x00FF /* IN2L_VOL - [7:0] */ +#define WM5100_IN2L_VOL_SHIFT 0 /* IN2L_VOL - [7:0] */ +#define WM5100_IN2L_VOL_WIDTH 8 /* IN2L_VOL - [7:0] */ + +/* + * R803 (0x323) - ADC Digital Volume 2R + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN2R_MUTE 0x0100 /* IN2R_MUTE */ +#define WM5100_IN2R_MUTE_MASK 0x0100 /* IN2R_MUTE */ +#define WM5100_IN2R_MUTE_SHIFT 8 /* IN2R_MUTE */ +#define WM5100_IN2R_MUTE_WIDTH 1 /* IN2R_MUTE */ +#define WM5100_IN2R_VOL_MASK 0x00FF /* IN2R_VOL - [7:0] */ +#define WM5100_IN2R_VOL_SHIFT 0 /* IN2R_VOL - [7:0] */ +#define WM5100_IN2R_VOL_WIDTH 8 /* IN2R_VOL - [7:0] */ + +/* + * R804 (0x324) - ADC Digital Volume 3L + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN3L_MUTE 0x0100 /* IN3L_MUTE */ +#define WM5100_IN3L_MUTE_MASK 0x0100 /* IN3L_MUTE */ +#define WM5100_IN3L_MUTE_SHIFT 8 /* IN3L_MUTE */ +#define WM5100_IN3L_MUTE_WIDTH 1 /* IN3L_MUTE */ +#define WM5100_IN3L_VOL_MASK 0x00FF /* IN3L_VOL - [7:0] */ +#define WM5100_IN3L_VOL_SHIFT 0 /* IN3L_VOL - [7:0] */ +#define WM5100_IN3L_VOL_WIDTH 8 /* IN3L_VOL - [7:0] */ + +/* + * R805 (0x325) - ADC Digital Volume 3R + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN3R_MUTE 0x0100 /* IN3R_MUTE */ +#define WM5100_IN3R_MUTE_MASK 0x0100 /* IN3R_MUTE */ +#define WM5100_IN3R_MUTE_SHIFT 8 /* IN3R_MUTE */ +#define WM5100_IN3R_MUTE_WIDTH 1 /* IN3R_MUTE */ +#define WM5100_IN3R_VOL_MASK 0x00FF /* IN3R_VOL - [7:0] */ +#define WM5100_IN3R_VOL_SHIFT 0 /* IN3R_VOL - [7:0] */ +#define WM5100_IN3R_VOL_WIDTH 8 /* IN3R_VOL - [7:0] */ + +/* + * R806 (0x326) - ADC Digital Volume 4L + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN4L_MUTE 0x0100 /* IN4L_MUTE */ +#define WM5100_IN4L_MUTE_MASK 0x0100 /* IN4L_MUTE */ +#define WM5100_IN4L_MUTE_SHIFT 8 /* IN4L_MUTE */ +#define WM5100_IN4L_MUTE_WIDTH 1 /* IN4L_MUTE */ +#define WM5100_IN4L_VOL_MASK 0x00FF /* IN4L_VOL - [7:0] */ +#define WM5100_IN4L_VOL_SHIFT 0 /* IN4L_VOL - [7:0] */ +#define WM5100_IN4L_VOL_WIDTH 8 /* IN4L_VOL - [7:0] */ + +/* + * R807 (0x327) - ADC Digital Volume 4R + */ +#define WM5100_IN_VU 0x0200 /* IN_VU */ +#define WM5100_IN_VU_MASK 0x0200 /* IN_VU */ +#define WM5100_IN_VU_SHIFT 9 /* IN_VU */ +#define WM5100_IN_VU_WIDTH 1 /* IN_VU */ +#define WM5100_IN4R_MUTE 0x0100 /* IN4R_MUTE */ +#define WM5100_IN4R_MUTE_MASK 0x0100 /* IN4R_MUTE */ +#define WM5100_IN4R_MUTE_SHIFT 8 /* IN4R_MUTE */ +#define WM5100_IN4R_MUTE_WIDTH 1 /* IN4R_MUTE */ +#define WM5100_IN4R_VOL_MASK 0x00FF /* IN4R_VOL - [7:0] */ +#define WM5100_IN4R_VOL_SHIFT 0 /* IN4R_VOL - [7:0] */ +#define WM5100_IN4R_VOL_WIDTH 8 /* IN4R_VOL - [7:0] */ + +/* + * R1025 (0x401) - Output Enables 2 + */ +#define WM5100_OUT6L_ENA 0x0800 /* OUT6L_ENA */ +#define WM5100_OUT6L_ENA_MASK 0x0800 /* OUT6L_ENA */ +#define WM5100_OUT6L_ENA_SHIFT 11 /* OUT6L_ENA */ +#define WM5100_OUT6L_ENA_WIDTH 1 /* OUT6L_ENA */ +#define WM5100_OUT6R_ENA 0x0400 /* OUT6R_ENA */ +#define WM5100_OUT6R_ENA_MASK 0x0400 /* OUT6R_ENA */ +#define WM5100_OUT6R_ENA_SHIFT 10 /* OUT6R_ENA */ +#define WM5100_OUT6R_ENA_WIDTH 1 /* OUT6R_ENA */ +#define WM5100_OUT5L_ENA 0x0200 /* OUT5L_ENA */ +#define WM5100_OUT5L_ENA_MASK 0x0200 /* OUT5L_ENA */ +#define WM5100_OUT5L_ENA_SHIFT 9 /* OUT5L_ENA */ +#define WM5100_OUT5L_ENA_WIDTH 1 /* OUT5L_ENA */ +#define WM5100_OUT5R_ENA 0x0100 /* OUT5R_ENA */ +#define WM5100_OUT5R_ENA_MASK 0x0100 /* OUT5R_ENA */ +#define WM5100_OUT5R_ENA_SHIFT 8 /* OUT5R_ENA */ +#define WM5100_OUT5R_ENA_WIDTH 1 /* OUT5R_ENA */ +#define WM5100_OUT4L_ENA 0x0080 /* OUT4L_ENA */ +#define WM5100_OUT4L_ENA_MASK 0x0080 /* OUT4L_ENA */ +#define WM5100_OUT4L_ENA_SHIFT 7 /* OUT4L_ENA */ +#define WM5100_OUT4L_ENA_WIDTH 1 /* OUT4L_ENA */ +#define WM5100_OUT4R_ENA 0x0040 /* OUT4R_ENA */ +#define WM5100_OUT4R_ENA_MASK 0x0040 /* OUT4R_ENA */ +#define WM5100_OUT4R_ENA_SHIFT 6 /* OUT4R_ENA */ +#define WM5100_OUT4R_ENA_WIDTH 1 /* OUT4R_ENA */ + +/* + * R1026 (0x402) - Output Status 1 + */ +#define WM5100_OUT3L_ENA_STS 0x0020 /* OUT3L_ENA_STS */ +#define WM5100_OUT3L_ENA_STS_MASK 0x0020 /* OUT3L_ENA_STS */ +#define WM5100_OUT3L_ENA_STS_SHIFT 5 /* OUT3L_ENA_STS */ +#define WM5100_OUT3L_ENA_STS_WIDTH 1 /* OUT3L_ENA_STS */ +#define WM5100_OUT3R_ENA_STS 0x0010 /* OUT3R_ENA_STS */ +#define WM5100_OUT3R_ENA_STS_MASK 0x0010 /* OUT3R_ENA_STS */ +#define WM5100_OUT3R_ENA_STS_SHIFT 4 /* OUT3R_ENA_STS */ +#define WM5100_OUT3R_ENA_STS_WIDTH 1 /* OUT3R_ENA_STS */ +#define WM5100_OUT2L_ENA_STS 0x0008 /* OUT2L_ENA_STS */ +#define WM5100_OUT2L_ENA_STS_MASK 0x0008 /* OUT2L_ENA_STS */ +#define WM5100_OUT2L_ENA_STS_SHIFT 3 /* OUT2L_ENA_STS */ +#define WM5100_OUT2L_ENA_STS_WIDTH 1 /* OUT2L_ENA_STS */ +#define WM5100_OUT2R_ENA_STS 0x0004 /* OUT2R_ENA_STS */ +#define WM5100_OUT2R_ENA_STS_MASK 0x0004 /* OUT2R_ENA_STS */ +#define WM5100_OUT2R_ENA_STS_SHIFT 2 /* OUT2R_ENA_STS */ +#define WM5100_OUT2R_ENA_STS_WIDTH 1 /* OUT2R_ENA_STS */ +#define WM5100_OUT1L_ENA_STS 0x0002 /* OUT1L_ENA_STS */ +#define WM5100_OUT1L_ENA_STS_MASK 0x0002 /* OUT1L_ENA_STS */ +#define WM5100_OUT1L_ENA_STS_SHIFT 1 /* OUT1L_ENA_STS */ +#define WM5100_OUT1L_ENA_STS_WIDTH 1 /* OUT1L_ENA_STS */ +#define WM5100_OUT1R_ENA_STS 0x0001 /* OUT1R_ENA_STS */ +#define WM5100_OUT1R_ENA_STS_MASK 0x0001 /* OUT1R_ENA_STS */ +#define WM5100_OUT1R_ENA_STS_SHIFT 0 /* OUT1R_ENA_STS */ +#define WM5100_OUT1R_ENA_STS_WIDTH 1 /* OUT1R_ENA_STS */ + +/* + * R1027 (0x403) - Output Status 2 + */ +#define WM5100_OUT6L_ENA_STS 0x0800 /* OUT6L_ENA_STS */ +#define WM5100_OUT6L_ENA_STS_MASK 0x0800 /* OUT6L_ENA_STS */ +#define WM5100_OUT6L_ENA_STS_SHIFT 11 /* OUT6L_ENA_STS */ +#define WM5100_OUT6L_ENA_STS_WIDTH 1 /* OUT6L_ENA_STS */ +#define WM5100_OUT6R_ENA_STS 0x0400 /* OUT6R_ENA_STS */ +#define WM5100_OUT6R_ENA_STS_MASK 0x0400 /* OUT6R_ENA_STS */ +#define WM5100_OUT6R_ENA_STS_SHIFT 10 /* OUT6R_ENA_STS */ +#define WM5100_OUT6R_ENA_STS_WIDTH 1 /* OUT6R_ENA_STS */ +#define WM5100_OUT5L_ENA_STS 0x0200 /* OUT5L_ENA_STS */ +#define WM5100_OUT5L_ENA_STS_MASK 0x0200 /* OUT5L_ENA_STS */ +#define WM5100_OUT5L_ENA_STS_SHIFT 9 /* OUT5L_ENA_STS */ +#define WM5100_OUT5L_ENA_STS_WIDTH 1 /* OUT5L_ENA_STS */ +#define WM5100_OUT5R_ENA_STS 0x0100 /* OUT5R_ENA_STS */ +#define WM5100_OUT5R_ENA_STS_MASK 0x0100 /* OUT5R_ENA_STS */ +#define WM5100_OUT5R_ENA_STS_SHIFT 8 /* OUT5R_ENA_STS */ +#define WM5100_OUT5R_ENA_STS_WIDTH 1 /* OUT5R_ENA_STS */ +#define WM5100_OUT4L_ENA_STS 0x0080 /* OUT4L_ENA_STS */ +#define WM5100_OUT4L_ENA_STS_MASK 0x0080 /* OUT4L_ENA_STS */ +#define WM5100_OUT4L_ENA_STS_SHIFT 7 /* OUT4L_ENA_STS */ +#define WM5100_OUT4L_ENA_STS_WIDTH 1 /* OUT4L_ENA_STS */ +#define WM5100_OUT4R_ENA_STS 0x0040 /* OUT4R_ENA_STS */ +#define WM5100_OUT4R_ENA_STS_MASK 0x0040 /* OUT4R_ENA_STS */ +#define WM5100_OUT4R_ENA_STS_SHIFT 6 /* OUT4R_ENA_STS */ +#define WM5100_OUT4R_ENA_STS_WIDTH 1 /* OUT4R_ENA_STS */ + +/* + * R1032 (0x408) - Channel Enables 1 + */ +#define WM5100_HP3L_ENA 0x0020 /* HP3L_ENA */ +#define WM5100_HP3L_ENA_MASK 0x0020 /* HP3L_ENA */ +#define WM5100_HP3L_ENA_SHIFT 5 /* HP3L_ENA */ +#define WM5100_HP3L_ENA_WIDTH 1 /* HP3L_ENA */ +#define WM5100_HP3R_ENA 0x0010 /* HP3R_ENA */ +#define WM5100_HP3R_ENA_MASK 0x0010 /* HP3R_ENA */ +#define WM5100_HP3R_ENA_SHIFT 4 /* HP3R_ENA */ +#define WM5100_HP3R_ENA_WIDTH 1 /* HP3R_ENA */ +#define WM5100_HP2L_ENA 0x0008 /* HP2L_ENA */ +#define WM5100_HP2L_ENA_MASK 0x0008 /* HP2L_ENA */ +#define WM5100_HP2L_ENA_SHIFT 3 /* HP2L_ENA */ +#define WM5100_HP2L_ENA_WIDTH 1 /* HP2L_ENA */ +#define WM5100_HP2R_ENA 0x0004 /* HP2R_ENA */ +#define WM5100_HP2R_ENA_MASK 0x0004 /* HP2R_ENA */ +#define WM5100_HP2R_ENA_SHIFT 2 /* HP2R_ENA */ +#define WM5100_HP2R_ENA_WIDTH 1 /* HP2R_ENA */ +#define WM5100_HP1L_ENA 0x0002 /* HP1L_ENA */ +#define WM5100_HP1L_ENA_MASK 0x0002 /* HP1L_ENA */ +#define WM5100_HP1L_ENA_SHIFT 1 /* HP1L_ENA */ +#define WM5100_HP1L_ENA_WIDTH 1 /* HP1L_ENA */ +#define WM5100_HP1R_ENA 0x0001 /* HP1R_ENA */ +#define WM5100_HP1R_ENA_MASK 0x0001 /* HP1R_ENA */ +#define WM5100_HP1R_ENA_SHIFT 0 /* HP1R_ENA */ +#define WM5100_HP1R_ENA_WIDTH 1 /* HP1R_ENA */ + +/* + * R1040 (0x410) - Out Volume 1L + */ +#define WM5100_OUT_RATE_MASK 0xC000 /* OUT_RATE - [15:14] */ +#define WM5100_OUT_RATE_SHIFT 14 /* OUT_RATE - [15:14] */ +#define WM5100_OUT_RATE_WIDTH 2 /* OUT_RATE - [15:14] */ +#define WM5100_OUT1_OSR 0x2000 /* OUT1_OSR */ +#define WM5100_OUT1_OSR_MASK 0x2000 /* OUT1_OSR */ +#define WM5100_OUT1_OSR_SHIFT 13 /* OUT1_OSR */ +#define WM5100_OUT1_OSR_WIDTH 1 /* OUT1_OSR */ +#define WM5100_OUT1_MONO 0x1000 /* OUT1_MONO */ +#define WM5100_OUT1_MONO_MASK 0x1000 /* OUT1_MONO */ +#define WM5100_OUT1_MONO_SHIFT 12 /* OUT1_MONO */ +#define WM5100_OUT1_MONO_WIDTH 1 /* OUT1_MONO */ +#define WM5100_OUT1L_ANC_SRC 0x0800 /* OUT1L_ANC_SRC */ +#define WM5100_OUT1L_ANC_SRC_MASK 0x0800 /* OUT1L_ANC_SRC */ +#define WM5100_OUT1L_ANC_SRC_SHIFT 11 /* OUT1L_ANC_SRC */ +#define WM5100_OUT1L_ANC_SRC_WIDTH 1 /* OUT1L_ANC_SRC */ +#define WM5100_OUT1L_PGA_VOL_MASK 0x00FE /* OUT1L_PGA_VOL - [7:1] */ +#define WM5100_OUT1L_PGA_VOL_SHIFT 1 /* OUT1L_PGA_VOL - [7:1] */ +#define WM5100_OUT1L_PGA_VOL_WIDTH 7 /* OUT1L_PGA_VOL - [7:1] */ + +/* + * R1041 (0x411) - Out Volume 1R + */ +#define WM5100_OUT1R_ANC_SRC 0x0800 /* OUT1R_ANC_SRC */ +#define WM5100_OUT1R_ANC_SRC_MASK 0x0800 /* OUT1R_ANC_SRC */ +#define WM5100_OUT1R_ANC_SRC_SHIFT 11 /* OUT1R_ANC_SRC */ +#define WM5100_OUT1R_ANC_SRC_WIDTH 1 /* OUT1R_ANC_SRC */ +#define WM5100_OUT1R_PGA_VOL_MASK 0x00FE /* OUT1R_PGA_VOL - [7:1] */ +#define WM5100_OUT1R_PGA_VOL_SHIFT 1 /* OUT1R_PGA_VOL - [7:1] */ +#define WM5100_OUT1R_PGA_VOL_WIDTH 7 /* OUT1R_PGA_VOL - [7:1] */ + +/* + * R1042 (0x412) - DAC Volume Limit 1L + */ +#define WM5100_OUT1L_VOL_LIM_MASK 0x00FF /* OUT1L_VOL_LIM - [7:0] */ +#define WM5100_OUT1L_VOL_LIM_SHIFT 0 /* OUT1L_VOL_LIM - [7:0] */ +#define WM5100_OUT1L_VOL_LIM_WIDTH 8 /* OUT1L_VOL_LIM - [7:0] */ + +/* + * R1043 (0x413) - DAC Volume Limit 1R + */ +#define WM5100_OUT1R_VOL_LIM_MASK 0x00FF /* OUT1R_VOL_LIM - [7:0] */ +#define WM5100_OUT1R_VOL_LIM_SHIFT 0 /* OUT1R_VOL_LIM - [7:0] */ +#define WM5100_OUT1R_VOL_LIM_WIDTH 8 /* OUT1R_VOL_LIM - [7:0] */ + +/* + * R1044 (0x414) - Out Volume 2L + */ +#define WM5100_OUT2_OSR 0x2000 /* OUT2_OSR */ +#define WM5100_OUT2_OSR_MASK 0x2000 /* OUT2_OSR */ +#define WM5100_OUT2_OSR_SHIFT 13 /* OUT2_OSR */ +#define WM5100_OUT2_OSR_WIDTH 1 /* OUT2_OSR */ +#define WM5100_OUT2_MONO 0x1000 /* OUT2_MONO */ +#define WM5100_OUT2_MONO_MASK 0x1000 /* OUT2_MONO */ +#define WM5100_OUT2_MONO_SHIFT 12 /* OUT2_MONO */ +#define WM5100_OUT2_MONO_WIDTH 1 /* OUT2_MONO */ +#define WM5100_OUT2L_ANC_SRC 0x0800 /* OUT2L_ANC_SRC */ +#define WM5100_OUT2L_ANC_SRC_MASK 0x0800 /* OUT2L_ANC_SRC */ +#define WM5100_OUT2L_ANC_SRC_SHIFT 11 /* OUT2L_ANC_SRC */ +#define WM5100_OUT2L_ANC_SRC_WIDTH 1 /* OUT2L_ANC_SRC */ +#define WM5100_OUT2L_PGA_VOL_MASK 0x00FE /* OUT2L_PGA_VOL - [7:1] */ +#define WM5100_OUT2L_PGA_VOL_SHIFT 1 /* OUT2L_PGA_VOL - [7:1] */ +#define WM5100_OUT2L_PGA_VOL_WIDTH 7 /* OUT2L_PGA_VOL - [7:1] */ + +/* + * R1045 (0x415) - Out Volume 2R + */ +#define WM5100_OUT2R_ANC_SRC 0x0800 /* OUT2R_ANC_SRC */ +#define WM5100_OUT2R_ANC_SRC_MASK 0x0800 /* OUT2R_ANC_SRC */ +#define WM5100_OUT2R_ANC_SRC_SHIFT 11 /* OUT2R_ANC_SRC */ +#define WM5100_OUT2R_ANC_SRC_WIDTH 1 /* OUT2R_ANC_SRC */ +#define WM5100_OUT2R_PGA_VOL_MASK 0x00FE /* OUT2R_PGA_VOL - [7:1] */ +#define WM5100_OUT2R_PGA_VOL_SHIFT 1 /* OUT2R_PGA_VOL - [7:1] */ +#define WM5100_OUT2R_PGA_VOL_WIDTH 7 /* OUT2R_PGA_VOL - [7:1] */ + +/* + * R1046 (0x416) - DAC Volume Limit 2L + */ +#define WM5100_OUT2L_VOL_LIM_MASK 0x00FF /* OUT2L_VOL_LIM - [7:0] */ +#define WM5100_OUT2L_VOL_LIM_SHIFT 0 /* OUT2L_VOL_LIM - [7:0] */ +#define WM5100_OUT2L_VOL_LIM_WIDTH 8 /* OUT2L_VOL_LIM - [7:0] */ + +/* + * R1047 (0x417) - DAC Volume Limit 2R + */ +#define WM5100_OUT2R_VOL_LIM_MASK 0x00FF /* OUT2R_VOL_LIM - [7:0] */ +#define WM5100_OUT2R_VOL_LIM_SHIFT 0 /* OUT2R_VOL_LIM - [7:0] */ +#define WM5100_OUT2R_VOL_LIM_WIDTH 8 /* OUT2R_VOL_LIM - [7:0] */ + +/* + * R1048 (0x418) - Out Volume 3L + */ +#define WM5100_OUT3_OSR 0x2000 /* OUT3_OSR */ +#define WM5100_OUT3_OSR_MASK 0x2000 /* OUT3_OSR */ +#define WM5100_OUT3_OSR_SHIFT 13 /* OUT3_OSR */ +#define WM5100_OUT3_OSR_WIDTH 1 /* OUT3_OSR */ +#define WM5100_OUT3_MONO 0x1000 /* OUT3_MONO */ +#define WM5100_OUT3_MONO_MASK 0x1000 /* OUT3_MONO */ +#define WM5100_OUT3_MONO_SHIFT 12 /* OUT3_MONO */ +#define WM5100_OUT3_MONO_WIDTH 1 /* OUT3_MONO */ +#define WM5100_OUT3L_ANC_SRC 0x0800 /* OUT3L_ANC_SRC */ +#define WM5100_OUT3L_ANC_SRC_MASK 0x0800 /* OUT3L_ANC_SRC */ +#define WM5100_OUT3L_ANC_SRC_SHIFT 11 /* OUT3L_ANC_SRC */ +#define WM5100_OUT3L_ANC_SRC_WIDTH 1 /* OUT3L_ANC_SRC */ +#define WM5100_OUT3L_PGA_VOL_MASK 0x00FE /* OUT3L_PGA_VOL - [7:1] */ +#define WM5100_OUT3L_PGA_VOL_SHIFT 1 /* OUT3L_PGA_VOL - [7:1] */ +#define WM5100_OUT3L_PGA_VOL_WIDTH 7 /* OUT3L_PGA_VOL - [7:1] */ + +/* + * R1049 (0x419) - Out Volume 3R + */ +#define WM5100_OUT3R_ANC_SRC 0x0800 /* OUT3R_ANC_SRC */ +#define WM5100_OUT3R_ANC_SRC_MASK 0x0800 /* OUT3R_ANC_SRC */ +#define WM5100_OUT3R_ANC_SRC_SHIFT 11 /* OUT3R_ANC_SRC */ +#define WM5100_OUT3R_ANC_SRC_WIDTH 1 /* OUT3R_ANC_SRC */ +#define WM5100_OUT3R_PGA_VOL_MASK 0x00FE /* OUT3R_PGA_VOL - [7:1] */ +#define WM5100_OUT3R_PGA_VOL_SHIFT 1 /* OUT3R_PGA_VOL - [7:1] */ +#define WM5100_OUT3R_PGA_VOL_WIDTH 7 /* OUT3R_PGA_VOL - [7:1] */ + +/* + * R1050 (0x41A) - DAC Volume Limit 3L + */ +#define WM5100_OUT3L_VOL_LIM_MASK 0x00FF /* OUT3L_VOL_LIM - [7:0] */ +#define WM5100_OUT3L_VOL_LIM_SHIFT 0 /* OUT3L_VOL_LIM - [7:0] */ +#define WM5100_OUT3L_VOL_LIM_WIDTH 8 /* OUT3L_VOL_LIM - [7:0] */ + +/* + * R1051 (0x41B) - DAC Volume Limit 3R + */ +#define WM5100_OUT3R_VOL_LIM_MASK 0x00FF /* OUT3R_VOL_LIM - [7:0] */ +#define WM5100_OUT3R_VOL_LIM_SHIFT 0 /* OUT3R_VOL_LIM - [7:0] */ +#define WM5100_OUT3R_VOL_LIM_WIDTH 8 /* OUT3R_VOL_LIM - [7:0] */ + +/* + * R1052 (0x41C) - Out Volume 4L + */ +#define WM5100_OUT4_OSR 0x2000 /* OUT4_OSR */ +#define WM5100_OUT4_OSR_MASK 0x2000 /* OUT4_OSR */ +#define WM5100_OUT4_OSR_SHIFT 13 /* OUT4_OSR */ +#define WM5100_OUT4_OSR_WIDTH 1 /* OUT4_OSR */ +#define WM5100_OUT4L_ANC_SRC 0x0800 /* OUT4L_ANC_SRC */ +#define WM5100_OUT4L_ANC_SRC_MASK 0x0800 /* OUT4L_ANC_SRC */ +#define WM5100_OUT4L_ANC_SRC_SHIFT 11 /* OUT4L_ANC_SRC */ +#define WM5100_OUT4L_ANC_SRC_WIDTH 1 /* OUT4L_ANC_SRC */ +#define WM5100_OUT4L_VOL_LIM_MASK 0x00FF /* OUT4L_VOL_LIM - [7:0] */ +#define WM5100_OUT4L_VOL_LIM_SHIFT 0 /* OUT4L_VOL_LIM - [7:0] */ +#define WM5100_OUT4L_VOL_LIM_WIDTH 8 /* OUT4L_VOL_LIM - [7:0] */ + +/* + * R1053 (0x41D) - Out Volume 4R + */ +#define WM5100_OUT4R_ANC_SRC 0x0800 /* OUT4R_ANC_SRC */ +#define WM5100_OUT4R_ANC_SRC_MASK 0x0800 /* OUT4R_ANC_SRC */ +#define WM5100_OUT4R_ANC_SRC_SHIFT 11 /* OUT4R_ANC_SRC */ +#define WM5100_OUT4R_ANC_SRC_WIDTH 1 /* OUT4R_ANC_SRC */ +#define WM5100_OUT4R_VOL_LIM_MASK 0x00FF /* OUT4R_VOL_LIM - [7:0] */ +#define WM5100_OUT4R_VOL_LIM_SHIFT 0 /* OUT4R_VOL_LIM - [7:0] */ +#define WM5100_OUT4R_VOL_LIM_WIDTH 8 /* OUT4R_VOL_LIM - [7:0] */ + +/* + * R1054 (0x41E) - DAC Volume Limit 5L + */ +#define WM5100_OUT5_OSR 0x2000 /* OUT5_OSR */ +#define WM5100_OUT5_OSR_MASK 0x2000 /* OUT5_OSR */ +#define WM5100_OUT5_OSR_SHIFT 13 /* OUT5_OSR */ +#define WM5100_OUT5_OSR_WIDTH 1 /* OUT5_OSR */ +#define WM5100_OUT5L_ANC_SRC 0x0800 /* OUT5L_ANC_SRC */ +#define WM5100_OUT5L_ANC_SRC_MASK 0x0800 /* OUT5L_ANC_SRC */ +#define WM5100_OUT5L_ANC_SRC_SHIFT 11 /* OUT5L_ANC_SRC */ +#define WM5100_OUT5L_ANC_SRC_WIDTH 1 /* OUT5L_ANC_SRC */ +#define WM5100_OUT5L_VOL_LIM_MASK 0x00FF /* OUT5L_VOL_LIM - [7:0] */ +#define WM5100_OUT5L_VOL_LIM_SHIFT 0 /* OUT5L_VOL_LIM - [7:0] */ +#define WM5100_OUT5L_VOL_LIM_WIDTH 8 /* OUT5L_VOL_LIM - [7:0] */ + +/* + * R1055 (0x41F) - DAC Volume Limit 5R + */ +#define WM5100_OUT5R_ANC_SRC 0x0800 /* OUT5R_ANC_SRC */ +#define WM5100_OUT5R_ANC_SRC_MASK 0x0800 /* OUT5R_ANC_SRC */ +#define WM5100_OUT5R_ANC_SRC_SHIFT 11 /* OUT5R_ANC_SRC */ +#define WM5100_OUT5R_ANC_SRC_WIDTH 1 /* OUT5R_ANC_SRC */ +#define WM5100_OUT5R_VOL_LIM_MASK 0x00FF /* OUT5R_VOL_LIM - [7:0] */ +#define WM5100_OUT5R_VOL_LIM_SHIFT 0 /* OUT5R_VOL_LIM - [7:0] */ +#define WM5100_OUT5R_VOL_LIM_WIDTH 8 /* OUT5R_VOL_LIM - [7:0] */ + +/* + * R1056 (0x420) - DAC Volume Limit 6L + */ +#define WM5100_OUT6_OSR 0x2000 /* OUT6_OSR */ +#define WM5100_OUT6_OSR_MASK 0x2000 /* OUT6_OSR */ +#define WM5100_OUT6_OSR_SHIFT 13 /* OUT6_OSR */ +#define WM5100_OUT6_OSR_WIDTH 1 /* OUT6_OSR */ +#define WM5100_OUT6L_ANC_SRC 0x0800 /* OUT6L_ANC_SRC */ +#define WM5100_OUT6L_ANC_SRC_MASK 0x0800 /* OUT6L_ANC_SRC */ +#define WM5100_OUT6L_ANC_SRC_SHIFT 11 /* OUT6L_ANC_SRC */ +#define WM5100_OUT6L_ANC_SRC_WIDTH 1 /* OUT6L_ANC_SRC */ +#define WM5100_OUT6L_VOL_LIM_MASK 0x00FF /* OUT6L_VOL_LIM - [7:0] */ +#define WM5100_OUT6L_VOL_LIM_SHIFT 0 /* OUT6L_VOL_LIM - [7:0] */ +#define WM5100_OUT6L_VOL_LIM_WIDTH 8 /* OUT6L_VOL_LIM - [7:0] */ + +/* + * R1057 (0x421) - DAC Volume Limit 6R + */ +#define WM5100_OUT6R_ANC_SRC 0x0800 /* OUT6R_ANC_SRC */ +#define WM5100_OUT6R_ANC_SRC_MASK 0x0800 /* OUT6R_ANC_SRC */ +#define WM5100_OUT6R_ANC_SRC_SHIFT 11 /* OUT6R_ANC_SRC */ +#define WM5100_OUT6R_ANC_SRC_WIDTH 1 /* OUT6R_ANC_SRC */ +#define WM5100_OUT6R_VOL_LIM_MASK 0x00FF /* OUT6R_VOL_LIM - [7:0] */ +#define WM5100_OUT6R_VOL_LIM_SHIFT 0 /* OUT6R_VOL_LIM - [7:0] */ +#define WM5100_OUT6R_VOL_LIM_WIDTH 8 /* OUT6R_VOL_LIM - [7:0] */ + +/* + * R1088 (0x440) - DAC AEC Control 1 + */ +#define WM5100_AEC_LOOPBACK_SRC_MASK 0x003C /* AEC_LOOPBACK_SRC - [5:2] */ +#define WM5100_AEC_LOOPBACK_SRC_SHIFT 2 /* AEC_LOOPBACK_SRC - [5:2] */ +#define WM5100_AEC_LOOPBACK_SRC_WIDTH 4 /* AEC_LOOPBACK_SRC - [5:2] */ +#define WM5100_AEC_ENA_STS 0x0002 /* AEC_ENA_STS */ +#define WM5100_AEC_ENA_STS_MASK 0x0002 /* AEC_ENA_STS */ +#define WM5100_AEC_ENA_STS_SHIFT 1 /* AEC_ENA_STS */ +#define WM5100_AEC_ENA_STS_WIDTH 1 /* AEC_ENA_STS */ +#define WM5100_AEC_LOOPBACK_ENA 0x0001 /* AEC_LOOPBACK_ENA */ +#define WM5100_AEC_LOOPBACK_ENA_MASK 0x0001 /* AEC_LOOPBACK_ENA */ +#define WM5100_AEC_LOOPBACK_ENA_SHIFT 0 /* AEC_LOOPBACK_ENA */ +#define WM5100_AEC_LOOPBACK_ENA_WIDTH 1 /* AEC_LOOPBACK_ENA */ + +/* + * R1089 (0x441) - Output Volume Ramp + */ +#define WM5100_OUT_VD_RAMP_MASK 0x0070 /* OUT_VD_RAMP - [6:4] */ +#define WM5100_OUT_VD_RAMP_SHIFT 4 /* OUT_VD_RAMP - [6:4] */ +#define WM5100_OUT_VD_RAMP_WIDTH 3 /* OUT_VD_RAMP - [6:4] */ +#define WM5100_OUT_VI_RAMP_MASK 0x0007 /* OUT_VI_RAMP - [2:0] */ +#define WM5100_OUT_VI_RAMP_SHIFT 0 /* OUT_VI_RAMP - [2:0] */ +#define WM5100_OUT_VI_RAMP_WIDTH 3 /* OUT_VI_RAMP - [2:0] */ + +/* + * R1152 (0x480) - DAC Digital Volume 1L + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT1L_MUTE 0x0100 /* OUT1L_MUTE */ +#define WM5100_OUT1L_MUTE_MASK 0x0100 /* OUT1L_MUTE */ +#define WM5100_OUT1L_MUTE_SHIFT 8 /* OUT1L_MUTE */ +#define WM5100_OUT1L_MUTE_WIDTH 1 /* OUT1L_MUTE */ +#define WM5100_OUT1L_VOL_MASK 0x00FF /* OUT1L_VOL - [7:0] */ +#define WM5100_OUT1L_VOL_SHIFT 0 /* OUT1L_VOL - [7:0] */ +#define WM5100_OUT1L_VOL_WIDTH 8 /* OUT1L_VOL - [7:0] */ + +/* + * R1153 (0x481) - DAC Digital Volume 1R + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT1R_MUTE 0x0100 /* OUT1R_MUTE */ +#define WM5100_OUT1R_MUTE_MASK 0x0100 /* OUT1R_MUTE */ +#define WM5100_OUT1R_MUTE_SHIFT 8 /* OUT1R_MUTE */ +#define WM5100_OUT1R_MUTE_WIDTH 1 /* OUT1R_MUTE */ +#define WM5100_OUT1R_VOL_MASK 0x00FF /* OUT1R_VOL - [7:0] */ +#define WM5100_OUT1R_VOL_SHIFT 0 /* OUT1R_VOL - [7:0] */ +#define WM5100_OUT1R_VOL_WIDTH 8 /* OUT1R_VOL - [7:0] */ + +/* + * R1154 (0x482) - DAC Digital Volume 2L + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT2L_MUTE 0x0100 /* OUT2L_MUTE */ +#define WM5100_OUT2L_MUTE_MASK 0x0100 /* OUT2L_MUTE */ +#define WM5100_OUT2L_MUTE_SHIFT 8 /* OUT2L_MUTE */ +#define WM5100_OUT2L_MUTE_WIDTH 1 /* OUT2L_MUTE */ +#define WM5100_OUT2L_VOL_MASK 0x00FF /* OUT2L_VOL - [7:0] */ +#define WM5100_OUT2L_VOL_SHIFT 0 /* OUT2L_VOL - [7:0] */ +#define WM5100_OUT2L_VOL_WIDTH 8 /* OUT2L_VOL - [7:0] */ + +/* + * R1155 (0x483) - DAC Digital Volume 2R + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT2R_MUTE 0x0100 /* OUT2R_MUTE */ +#define WM5100_OUT2R_MUTE_MASK 0x0100 /* OUT2R_MUTE */ +#define WM5100_OUT2R_MUTE_SHIFT 8 /* OUT2R_MUTE */ +#define WM5100_OUT2R_MUTE_WIDTH 1 /* OUT2R_MUTE */ +#define WM5100_OUT2R_VOL_MASK 0x00FF /* OUT2R_VOL - [7:0] */ +#define WM5100_OUT2R_VOL_SHIFT 0 /* OUT2R_VOL - [7:0] */ +#define WM5100_OUT2R_VOL_WIDTH 8 /* OUT2R_VOL - [7:0] */ + +/* + * R1156 (0x484) - DAC Digital Volume 3L + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT3L_MUTE 0x0100 /* OUT3L_MUTE */ +#define WM5100_OUT3L_MUTE_MASK 0x0100 /* OUT3L_MUTE */ +#define WM5100_OUT3L_MUTE_SHIFT 8 /* OUT3L_MUTE */ +#define WM5100_OUT3L_MUTE_WIDTH 1 /* OUT3L_MUTE */ +#define WM5100_OUT3L_VOL_MASK 0x00FF /* OUT3L_VOL - [7:0] */ +#define WM5100_OUT3L_VOL_SHIFT 0 /* OUT3L_VOL - [7:0] */ +#define WM5100_OUT3L_VOL_WIDTH 8 /* OUT3L_VOL - [7:0] */ + +/* + * R1157 (0x485) - DAC Digital Volume 3R + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT3R_MUTE 0x0100 /* OUT3R_MUTE */ +#define WM5100_OUT3R_MUTE_MASK 0x0100 /* OUT3R_MUTE */ +#define WM5100_OUT3R_MUTE_SHIFT 8 /* OUT3R_MUTE */ +#define WM5100_OUT3R_MUTE_WIDTH 1 /* OUT3R_MUTE */ +#define WM5100_OUT3R_VOL_MASK 0x00FF /* OUT3R_VOL - [7:0] */ +#define WM5100_OUT3R_VOL_SHIFT 0 /* OUT3R_VOL - [7:0] */ +#define WM5100_OUT3R_VOL_WIDTH 8 /* OUT3R_VOL - [7:0] */ + +/* + * R1158 (0x486) - DAC Digital Volume 4L + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT4L_MUTE 0x0100 /* OUT4L_MUTE */ +#define WM5100_OUT4L_MUTE_MASK 0x0100 /* OUT4L_MUTE */ +#define WM5100_OUT4L_MUTE_SHIFT 8 /* OUT4L_MUTE */ +#define WM5100_OUT4L_MUTE_WIDTH 1 /* OUT4L_MUTE */ +#define WM5100_OUT4L_VOL_MASK 0x00FF /* OUT4L_VOL - [7:0] */ +#define WM5100_OUT4L_VOL_SHIFT 0 /* OUT4L_VOL - [7:0] */ +#define WM5100_OUT4L_VOL_WIDTH 8 /* OUT4L_VOL - [7:0] */ + +/* + * R1159 (0x487) - DAC Digital Volume 4R + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT4R_MUTE 0x0100 /* OUT4R_MUTE */ +#define WM5100_OUT4R_MUTE_MASK 0x0100 /* OUT4R_MUTE */ +#define WM5100_OUT4R_MUTE_SHIFT 8 /* OUT4R_MUTE */ +#define WM5100_OUT4R_MUTE_WIDTH 1 /* OUT4R_MUTE */ +#define WM5100_OUT4R_VOL_MASK 0x00FF /* OUT4R_VOL - [7:0] */ +#define WM5100_OUT4R_VOL_SHIFT 0 /* OUT4R_VOL - [7:0] */ +#define WM5100_OUT4R_VOL_WIDTH 8 /* OUT4R_VOL - [7:0] */ + +/* + * R1160 (0x488) - DAC Digital Volume 5L + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT5L_MUTE 0x0100 /* OUT5L_MUTE */ +#define WM5100_OUT5L_MUTE_MASK 0x0100 /* OUT5L_MUTE */ +#define WM5100_OUT5L_MUTE_SHIFT 8 /* OUT5L_MUTE */ +#define WM5100_OUT5L_MUTE_WIDTH 1 /* OUT5L_MUTE */ +#define WM5100_OUT5L_VOL_MASK 0x00FF /* OUT5L_VOL - [7:0] */ +#define WM5100_OUT5L_VOL_SHIFT 0 /* OUT5L_VOL - [7:0] */ +#define WM5100_OUT5L_VOL_WIDTH 8 /* OUT5L_VOL - [7:0] */ + +/* + * R1161 (0x489) - DAC Digital Volume 5R + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT5R_MUTE 0x0100 /* OUT5R_MUTE */ +#define WM5100_OUT5R_MUTE_MASK 0x0100 /* OUT5R_MUTE */ +#define WM5100_OUT5R_MUTE_SHIFT 8 /* OUT5R_MUTE */ +#define WM5100_OUT5R_MUTE_WIDTH 1 /* OUT5R_MUTE */ +#define WM5100_OUT5R_VOL_MASK 0x00FF /* OUT5R_VOL - [7:0] */ +#define WM5100_OUT5R_VOL_SHIFT 0 /* OUT5R_VOL - [7:0] */ +#define WM5100_OUT5R_VOL_WIDTH 8 /* OUT5R_VOL - [7:0] */ + +/* + * R1162 (0x48A) - DAC Digital Volume 6L + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT6L_MUTE 0x0100 /* OUT6L_MUTE */ +#define WM5100_OUT6L_MUTE_MASK 0x0100 /* OUT6L_MUTE */ +#define WM5100_OUT6L_MUTE_SHIFT 8 /* OUT6L_MUTE */ +#define WM5100_OUT6L_MUTE_WIDTH 1 /* OUT6L_MUTE */ +#define WM5100_OUT6L_VOL_MASK 0x00FF /* OUT6L_VOL - [7:0] */ +#define WM5100_OUT6L_VOL_SHIFT 0 /* OUT6L_VOL - [7:0] */ +#define WM5100_OUT6L_VOL_WIDTH 8 /* OUT6L_VOL - [7:0] */ + +/* + * R1163 (0x48B) - DAC Digital Volume 6R + */ +#define WM5100_OUT_VU 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_MASK 0x0200 /* OUT_VU */ +#define WM5100_OUT_VU_SHIFT 9 /* OUT_VU */ +#define WM5100_OUT_VU_WIDTH 1 /* OUT_VU */ +#define WM5100_OUT6R_MUTE 0x0100 /* OUT6R_MUTE */ +#define WM5100_OUT6R_MUTE_MASK 0x0100 /* OUT6R_MUTE */ +#define WM5100_OUT6R_MUTE_SHIFT 8 /* OUT6R_MUTE */ +#define WM5100_OUT6R_MUTE_WIDTH 1 /* OUT6R_MUTE */ +#define WM5100_OUT6R_VOL_MASK 0x00FF /* OUT6R_VOL - [7:0] */ +#define WM5100_OUT6R_VOL_SHIFT 0 /* OUT6R_VOL - [7:0] */ +#define WM5100_OUT6R_VOL_WIDTH 8 /* OUT6R_VOL - [7:0] */ + +/* + * R1216 (0x4C0) - PDM SPK1 CTRL 1 + */ +#define WM5100_SPK1R_MUTE 0x2000 /* SPK1R_MUTE */ +#define WM5100_SPK1R_MUTE_MASK 0x2000 /* SPK1R_MUTE */ +#define WM5100_SPK1R_MUTE_SHIFT 13 /* SPK1R_MUTE */ +#define WM5100_SPK1R_MUTE_WIDTH 1 /* SPK1R_MUTE */ +#define WM5100_SPK1L_MUTE 0x1000 /* SPK1L_MUTE */ +#define WM5100_SPK1L_MUTE_MASK 0x1000 /* SPK1L_MUTE */ +#define WM5100_SPK1L_MUTE_SHIFT 12 /* SPK1L_MUTE */ +#define WM5100_SPK1L_MUTE_WIDTH 1 /* SPK1L_MUTE */ +#define WM5100_SPK1_MUTE_ENDIAN 0x0100 /* SPK1_MUTE_ENDIAN */ +#define WM5100_SPK1_MUTE_ENDIAN_MASK 0x0100 /* SPK1_MUTE_ENDIAN */ +#define WM5100_SPK1_MUTE_ENDIAN_SHIFT 8 /* SPK1_MUTE_ENDIAN */ +#define WM5100_SPK1_MUTE_ENDIAN_WIDTH 1 /* SPK1_MUTE_ENDIAN */ +#define WM5100_SPK1_MUTE_SEQ1_MASK 0x00FF /* SPK1_MUTE_SEQ1 - [7:0] */ +#define WM5100_SPK1_MUTE_SEQ1_SHIFT 0 /* SPK1_MUTE_SEQ1 - [7:0] */ +#define WM5100_SPK1_MUTE_SEQ1_WIDTH 8 /* SPK1_MUTE_SEQ1 - [7:0] */ + +/* + * R1217 (0x4C1) - PDM SPK1 CTRL 2 + */ +#define WM5100_SPK1_FMT 0x0001 /* SPK1_FMT */ +#define WM5100_SPK1_FMT_MASK 0x0001 /* SPK1_FMT */ +#define WM5100_SPK1_FMT_SHIFT 0 /* SPK1_FMT */ +#define WM5100_SPK1_FMT_WIDTH 1 /* SPK1_FMT */ + +/* + * R1218 (0x4C2) - PDM SPK2 CTRL 1 + */ +#define WM5100_SPK2R_MUTE 0x2000 /* SPK2R_MUTE */ +#define WM5100_SPK2R_MUTE_MASK 0x2000 /* SPK2R_MUTE */ +#define WM5100_SPK2R_MUTE_SHIFT 13 /* SPK2R_MUTE */ +#define WM5100_SPK2R_MUTE_WIDTH 1 /* SPK2R_MUTE */ +#define WM5100_SPK2L_MUTE 0x1000 /* SPK2L_MUTE */ +#define WM5100_SPK2L_MUTE_MASK 0x1000 /* SPK2L_MUTE */ +#define WM5100_SPK2L_MUTE_SHIFT 12 /* SPK2L_MUTE */ +#define WM5100_SPK2L_MUTE_WIDTH 1 /* SPK2L_MUTE */ +#define WM5100_SPK2_MUTE_ENDIAN 0x0100 /* SPK2_MUTE_ENDIAN */ +#define WM5100_SPK2_MUTE_ENDIAN_MASK 0x0100 /* SPK2_MUTE_ENDIAN */ +#define WM5100_SPK2_MUTE_ENDIAN_SHIFT 8 /* SPK2_MUTE_ENDIAN */ +#define WM5100_SPK2_MUTE_ENDIAN_WIDTH 1 /* SPK2_MUTE_ENDIAN */ +#define WM5100_SPK2_MUTE_SEQ1_MASK 0x00FF /* SPK2_MUTE_SEQ1 - [7:0] */ +#define WM5100_SPK2_MUTE_SEQ1_SHIFT 0 /* SPK2_MUTE_SEQ1 - [7:0] */ +#define WM5100_SPK2_MUTE_SEQ1_WIDTH 8 /* SPK2_MUTE_SEQ1 - [7:0] */ + +/* + * R1219 (0x4C3) - PDM SPK2 CTRL 2 + */ +#define WM5100_SPK2_FMT 0x0001 /* SPK2_FMT */ +#define WM5100_SPK2_FMT_MASK 0x0001 /* SPK2_FMT */ +#define WM5100_SPK2_FMT_SHIFT 0 /* SPK2_FMT */ +#define WM5100_SPK2_FMT_WIDTH 1 /* SPK2_FMT */ + +/* + * R1280 (0x500) - Audio IF 1_1 + */ +#define WM5100_AIF1_BCLK_INV 0x0080 /* AIF1_BCLK_INV */ +#define WM5100_AIF1_BCLK_INV_MASK 0x0080 /* AIF1_BCLK_INV */ +#define WM5100_AIF1_BCLK_INV_SHIFT 7 /* AIF1_BCLK_INV */ +#define WM5100_AIF1_BCLK_INV_WIDTH 1 /* AIF1_BCLK_INV */ +#define WM5100_AIF1_BCLK_FRC 0x0040 /* AIF1_BCLK_FRC */ +#define WM5100_AIF1_BCLK_FRC_MASK 0x0040 /* AIF1_BCLK_FRC */ +#define WM5100_AIF1_BCLK_FRC_SHIFT 6 /* AIF1_BCLK_FRC */ +#define WM5100_AIF1_BCLK_FRC_WIDTH 1 /* AIF1_BCLK_FRC */ +#define WM5100_AIF1_BCLK_MSTR 0x0020 /* AIF1_BCLK_MSTR */ +#define WM5100_AIF1_BCLK_MSTR_MASK 0x0020 /* AIF1_BCLK_MSTR */ +#define WM5100_AIF1_BCLK_MSTR_SHIFT 5 /* AIF1_BCLK_MSTR */ +#define WM5100_AIF1_BCLK_MSTR_WIDTH 1 /* AIF1_BCLK_MSTR */ +#define WM5100_AIF1_BCLK_FREQ_MASK 0x001F /* AIF1_BCLK_FREQ - [4:0] */ +#define WM5100_AIF1_BCLK_FREQ_SHIFT 0 /* AIF1_BCLK_FREQ - [4:0] */ +#define WM5100_AIF1_BCLK_FREQ_WIDTH 5 /* AIF1_BCLK_FREQ - [4:0] */ + +/* + * R1281 (0x501) - Audio IF 1_2 + */ +#define WM5100_AIF1TX_DAT_TRI 0x0020 /* AIF1TX_DAT_TRI */ +#define WM5100_AIF1TX_DAT_TRI_MASK 0x0020 /* AIF1TX_DAT_TRI */ +#define WM5100_AIF1TX_DAT_TRI_SHIFT 5 /* AIF1TX_DAT_TRI */ +#define WM5100_AIF1TX_DAT_TRI_WIDTH 1 /* AIF1TX_DAT_TRI */ +#define WM5100_AIF1TX_LRCLK_SRC 0x0008 /* AIF1TX_LRCLK_SRC */ +#define WM5100_AIF1TX_LRCLK_SRC_MASK 0x0008 /* AIF1TX_LRCLK_SRC */ +#define WM5100_AIF1TX_LRCLK_SRC_SHIFT 3 /* AIF1TX_LRCLK_SRC */ +#define WM5100_AIF1TX_LRCLK_SRC_WIDTH 1 /* AIF1TX_LRCLK_SRC */ +#define WM5100_AIF1TX_LRCLK_INV 0x0004 /* AIF1TX_LRCLK_INV */ +#define WM5100_AIF1TX_LRCLK_INV_MASK 0x0004 /* AIF1TX_LRCLK_INV */ +#define WM5100_AIF1TX_LRCLK_INV_SHIFT 2 /* AIF1TX_LRCLK_INV */ +#define WM5100_AIF1TX_LRCLK_INV_WIDTH 1 /* AIF1TX_LRCLK_INV */ +#define WM5100_AIF1TX_LRCLK_FRC 0x0002 /* AIF1TX_LRCLK_FRC */ +#define WM5100_AIF1TX_LRCLK_FRC_MASK 0x0002 /* AIF1TX_LRCLK_FRC */ +#define WM5100_AIF1TX_LRCLK_FRC_SHIFT 1 /* AIF1TX_LRCLK_FRC */ +#define WM5100_AIF1TX_LRCLK_FRC_WIDTH 1 /* AIF1TX_LRCLK_FRC */ +#define WM5100_AIF1TX_LRCLK_MSTR 0x0001 /* AIF1TX_LRCLK_MSTR */ +#define WM5100_AIF1TX_LRCLK_MSTR_MASK 0x0001 /* AIF1TX_LRCLK_MSTR */ +#define WM5100_AIF1TX_LRCLK_MSTR_SHIFT 0 /* AIF1TX_LRCLK_MSTR */ +#define WM5100_AIF1TX_LRCLK_MSTR_WIDTH 1 /* AIF1TX_LRCLK_MSTR */ + +/* + * R1282 (0x502) - Audio IF 1_3 + */ +#define WM5100_AIF1RX_LRCLK_INV 0x0004 /* AIF1RX_LRCLK_INV */ +#define WM5100_AIF1RX_LRCLK_INV_MASK 0x0004 /* AIF1RX_LRCLK_INV */ +#define WM5100_AIF1RX_LRCLK_INV_SHIFT 2 /* AIF1RX_LRCLK_INV */ +#define WM5100_AIF1RX_LRCLK_INV_WIDTH 1 /* AIF1RX_LRCLK_INV */ +#define WM5100_AIF1RX_LRCLK_FRC 0x0002 /* AIF1RX_LRCLK_FRC */ +#define WM5100_AIF1RX_LRCLK_FRC_MASK 0x0002 /* AIF1RX_LRCLK_FRC */ +#define WM5100_AIF1RX_LRCLK_FRC_SHIFT 1 /* AIF1RX_LRCLK_FRC */ +#define WM5100_AIF1RX_LRCLK_FRC_WIDTH 1 /* AIF1RX_LRCLK_FRC */ +#define WM5100_AIF1RX_LRCLK_MSTR 0x0001 /* AIF1RX_LRCLK_MSTR */ +#define WM5100_AIF1RX_LRCLK_MSTR_MASK 0x0001 /* AIF1RX_LRCLK_MSTR */ +#define WM5100_AIF1RX_LRCLK_MSTR_SHIFT 0 /* AIF1RX_LRCLK_MSTR */ +#define WM5100_AIF1RX_LRCLK_MSTR_WIDTH 1 /* AIF1RX_LRCLK_MSTR */ + +/* + * R1283 (0x503) - Audio IF 1_4 + */ +#define WM5100_AIF1_TRI 0x0040 /* AIF1_TRI */ +#define WM5100_AIF1_TRI_MASK 0x0040 /* AIF1_TRI */ +#define WM5100_AIF1_TRI_SHIFT 6 /* AIF1_TRI */ +#define WM5100_AIF1_TRI_WIDTH 1 /* AIF1_TRI */ +#define WM5100_AIF1_RATE_MASK 0x0003 /* AIF1_RATE - [1:0] */ +#define WM5100_AIF1_RATE_SHIFT 0 /* AIF1_RATE - [1:0] */ +#define WM5100_AIF1_RATE_WIDTH 2 /* AIF1_RATE - [1:0] */ + +/* + * R1284 (0x504) - Audio IF 1_5 + */ +#define WM5100_AIF1_FMT_MASK 0x0007 /* AIF1_FMT - [2:0] */ +#define WM5100_AIF1_FMT_SHIFT 0 /* AIF1_FMT - [2:0] */ +#define WM5100_AIF1_FMT_WIDTH 3 /* AIF1_FMT - [2:0] */ + +/* + * R1285 (0x505) - Audio IF 1_6 + */ +#define WM5100_AIF1TX_BCPF_MASK 0x1FFF /* AIF1TX_BCPF - [12:0] */ +#define WM5100_AIF1TX_BCPF_SHIFT 0 /* AIF1TX_BCPF - [12:0] */ +#define WM5100_AIF1TX_BCPF_WIDTH 13 /* AIF1TX_BCPF - [12:0] */ + +/* + * R1286 (0x506) - Audio IF 1_7 + */ +#define WM5100_AIF1RX_BCPF_MASK 0x1FFF /* AIF1RX_BCPF - [12:0] */ +#define WM5100_AIF1RX_BCPF_SHIFT 0 /* AIF1RX_BCPF - [12:0] */ +#define WM5100_AIF1RX_BCPF_WIDTH 13 /* AIF1RX_BCPF - [12:0] */ + +/* + * R1287 (0x507) - Audio IF 1_8 + */ +#define WM5100_AIF1TX_WL_MASK 0x3F00 /* AIF1TX_WL - [13:8] */ +#define WM5100_AIF1TX_WL_SHIFT 8 /* AIF1TX_WL - [13:8] */ +#define WM5100_AIF1TX_WL_WIDTH 6 /* AIF1TX_WL - [13:8] */ +#define WM5100_AIF1TX_SLOT_LEN_MASK 0x00FF /* AIF1TX_SLOT_LEN - [7:0] */ +#define WM5100_AIF1TX_SLOT_LEN_SHIFT 0 /* AIF1TX_SLOT_LEN - [7:0] */ +#define WM5100_AIF1TX_SLOT_LEN_WIDTH 8 /* AIF1TX_SLOT_LEN - [7:0] */ + +/* + * R1288 (0x508) - Audio IF 1_9 + */ +#define WM5100_AIF1RX_WL_MASK 0x3F00 /* AIF1RX_WL - [13:8] */ +#define WM5100_AIF1RX_WL_SHIFT 8 /* AIF1RX_WL - [13:8] */ +#define WM5100_AIF1RX_WL_WIDTH 6 /* AIF1RX_WL - [13:8] */ +#define WM5100_AIF1RX_SLOT_LEN_MASK 0x00FF /* AIF1RX_SLOT_LEN - [7:0] */ +#define WM5100_AIF1RX_SLOT_LEN_SHIFT 0 /* AIF1RX_SLOT_LEN - [7:0] */ +#define WM5100_AIF1RX_SLOT_LEN_WIDTH 8 /* AIF1RX_SLOT_LEN - [7:0] */ + +/* + * R1289 (0x509) - Audio IF 1_10 + */ +#define WM5100_AIF1TX1_SLOT_MASK 0x003F /* AIF1TX1_SLOT - [5:0] */ +#define WM5100_AIF1TX1_SLOT_SHIFT 0 /* AIF1TX1_SLOT - [5:0] */ +#define WM5100_AIF1TX1_SLOT_WIDTH 6 /* AIF1TX1_SLOT - [5:0] */ + +/* + * R1290 (0x50A) - Audio IF 1_11 + */ +#define WM5100_AIF1TX2_SLOT_MASK 0x003F /* AIF1TX2_SLOT - [5:0] */ +#define WM5100_AIF1TX2_SLOT_SHIFT 0 /* AIF1TX2_SLOT - [5:0] */ +#define WM5100_AIF1TX2_SLOT_WIDTH 6 /* AIF1TX2_SLOT - [5:0] */ + +/* + * R1291 (0x50B) - Audio IF 1_12 + */ +#define WM5100_AIF1TX3_SLOT_MASK 0x003F /* AIF1TX3_SLOT - [5:0] */ +#define WM5100_AIF1TX3_SLOT_SHIFT 0 /* AIF1TX3_SLOT - [5:0] */ +#define WM5100_AIF1TX3_SLOT_WIDTH 6 /* AIF1TX3_SLOT - [5:0] */ + +/* + * R1292 (0x50C) - Audio IF 1_13 + */ +#define WM5100_AIF1TX4_SLOT_MASK 0x003F /* AIF1TX4_SLOT - [5:0] */ +#define WM5100_AIF1TX4_SLOT_SHIFT 0 /* AIF1TX4_SLOT - [5:0] */ +#define WM5100_AIF1TX4_SLOT_WIDTH 6 /* AIF1TX4_SLOT - [5:0] */ + +/* + * R1293 (0x50D) - Audio IF 1_14 + */ +#define WM5100_AIF1TX5_SLOT_MASK 0x003F /* AIF1TX5_SLOT - [5:0] */ +#define WM5100_AIF1TX5_SLOT_SHIFT 0 /* AIF1TX5_SLOT - [5:0] */ +#define WM5100_AIF1TX5_SLOT_WIDTH 6 /* AIF1TX5_SLOT - [5:0] */ + +/* + * R1294 (0x50E) - Audio IF 1_15 + */ +#define WM5100_AIF1TX6_SLOT_MASK 0x003F /* AIF1TX6_SLOT - [5:0] */ +#define WM5100_AIF1TX6_SLOT_SHIFT 0 /* AIF1TX6_SLOT - [5:0] */ +#define WM5100_AIF1TX6_SLOT_WIDTH 6 /* AIF1TX6_SLOT - [5:0] */ + +/* + * R1295 (0x50F) - Audio IF 1_16 + */ +#define WM5100_AIF1TX7_SLOT_MASK 0x003F /* AIF1TX7_SLOT - [5:0] */ +#define WM5100_AIF1TX7_SLOT_SHIFT 0 /* AIF1TX7_SLOT - [5:0] */ +#define WM5100_AIF1TX7_SLOT_WIDTH 6 /* AIF1TX7_SLOT - [5:0] */ + +/* + * R1296 (0x510) - Audio IF 1_17 + */ +#define WM5100_AIF1TX8_SLOT_MASK 0x003F /* AIF1TX8_SLOT - [5:0] */ +#define WM5100_AIF1TX8_SLOT_SHIFT 0 /* AIF1TX8_SLOT - [5:0] */ +#define WM5100_AIF1TX8_SLOT_WIDTH 6 /* AIF1TX8_SLOT - [5:0] */ + +/* + * R1297 (0x511) - Audio IF 1_18 + */ +#define WM5100_AIF1RX1_SLOT_MASK 0x003F /* AIF1RX1_SLOT - [5:0] */ +#define WM5100_AIF1RX1_SLOT_SHIFT 0 /* AIF1RX1_SLOT - [5:0] */ +#define WM5100_AIF1RX1_SLOT_WIDTH 6 /* AIF1RX1_SLOT - [5:0] */ + +/* + * R1298 (0x512) - Audio IF 1_19 + */ +#define WM5100_AIF1RX2_SLOT_MASK 0x003F /* AIF1RX2_SLOT - [5:0] */ +#define WM5100_AIF1RX2_SLOT_SHIFT 0 /* AIF1RX2_SLOT - [5:0] */ +#define WM5100_AIF1RX2_SLOT_WIDTH 6 /* AIF1RX2_SLOT - [5:0] */ + +/* + * R1299 (0x513) - Audio IF 1_20 + */ +#define WM5100_AIF1RX3_SLOT_MASK 0x003F /* AIF1RX3_SLOT - [5:0] */ +#define WM5100_AIF1RX3_SLOT_SHIFT 0 /* AIF1RX3_SLOT - [5:0] */ +#define WM5100_AIF1RX3_SLOT_WIDTH 6 /* AIF1RX3_SLOT - [5:0] */ + +/* + * R1300 (0x514) - Audio IF 1_21 + */ +#define WM5100_AIF1RX4_SLOT_MASK 0x003F /* AIF1RX4_SLOT - [5:0] */ +#define WM5100_AIF1RX4_SLOT_SHIFT 0 /* AIF1RX4_SLOT - [5:0] */ +#define WM5100_AIF1RX4_SLOT_WIDTH 6 /* AIF1RX4_SLOT - [5:0] */ + +/* + * R1301 (0x515) - Audio IF 1_22 + */ +#define WM5100_AIF1RX5_SLOT_MASK 0x003F /* AIF1RX5_SLOT - [5:0] */ +#define WM5100_AIF1RX5_SLOT_SHIFT 0 /* AIF1RX5_SLOT - [5:0] */ +#define WM5100_AIF1RX5_SLOT_WIDTH 6 /* AIF1RX5_SLOT - [5:0] */ + +/* + * R1302 (0x516) - Audio IF 1_23 + */ +#define WM5100_AIF1RX6_SLOT_MASK 0x003F /* AIF1RX6_SLOT - [5:0] */ +#define WM5100_AIF1RX6_SLOT_SHIFT 0 /* AIF1RX6_SLOT - [5:0] */ +#define WM5100_AIF1RX6_SLOT_WIDTH 6 /* AIF1RX6_SLOT - [5:0] */ + +/* + * R1303 (0x517) - Audio IF 1_24 + */ +#define WM5100_AIF1RX7_SLOT_MASK 0x003F /* AIF1RX7_SLOT - [5:0] */ +#define WM5100_AIF1RX7_SLOT_SHIFT 0 /* AIF1RX7_SLOT - [5:0] */ +#define WM5100_AIF1RX7_SLOT_WIDTH 6 /* AIF1RX7_SLOT - [5:0] */ + +/* + * R1304 (0x518) - Audio IF 1_25 + */ +#define WM5100_AIF1RX8_SLOT_MASK 0x003F /* AIF1RX8_SLOT - [5:0] */ +#define WM5100_AIF1RX8_SLOT_SHIFT 0 /* AIF1RX8_SLOT - [5:0] */ +#define WM5100_AIF1RX8_SLOT_WIDTH 6 /* AIF1RX8_SLOT - [5:0] */ + +/* + * R1305 (0x519) - Audio IF 1_26 + */ +#define WM5100_AIF1TX8_ENA 0x0080 /* AIF1TX8_ENA */ +#define WM5100_AIF1TX8_ENA_MASK 0x0080 /* AIF1TX8_ENA */ +#define WM5100_AIF1TX8_ENA_SHIFT 7 /* AIF1TX8_ENA */ +#define WM5100_AIF1TX8_ENA_WIDTH 1 /* AIF1TX8_ENA */ +#define WM5100_AIF1TX7_ENA 0x0040 /* AIF1TX7_ENA */ +#define WM5100_AIF1TX7_ENA_MASK 0x0040 /* AIF1TX7_ENA */ +#define WM5100_AIF1TX7_ENA_SHIFT 6 /* AIF1TX7_ENA */ +#define WM5100_AIF1TX7_ENA_WIDTH 1 /* AIF1TX7_ENA */ +#define WM5100_AIF1TX6_ENA 0x0020 /* AIF1TX6_ENA */ +#define WM5100_AIF1TX6_ENA_MASK 0x0020 /* AIF1TX6_ENA */ +#define WM5100_AIF1TX6_ENA_SHIFT 5 /* AIF1TX6_ENA */ +#define WM5100_AIF1TX6_ENA_WIDTH 1 /* AIF1TX6_ENA */ +#define WM5100_AIF1TX5_ENA 0x0010 /* AIF1TX5_ENA */ +#define WM5100_AIF1TX5_ENA_MASK 0x0010 /* AIF1TX5_ENA */ +#define WM5100_AIF1TX5_ENA_SHIFT 4 /* AIF1TX5_ENA */ +#define WM5100_AIF1TX5_ENA_WIDTH 1 /* AIF1TX5_ENA */ +#define WM5100_AIF1TX4_ENA 0x0008 /* AIF1TX4_ENA */ +#define WM5100_AIF1TX4_ENA_MASK 0x0008 /* AIF1TX4_ENA */ +#define WM5100_AIF1TX4_ENA_SHIFT 3 /* AIF1TX4_ENA */ +#define WM5100_AIF1TX4_ENA_WIDTH 1 /* AIF1TX4_ENA */ +#define WM5100_AIF1TX3_ENA 0x0004 /* AIF1TX3_ENA */ +#define WM5100_AIF1TX3_ENA_MASK 0x0004 /* AIF1TX3_ENA */ +#define WM5100_AIF1TX3_ENA_SHIFT 2 /* AIF1TX3_ENA */ +#define WM5100_AIF1TX3_ENA_WIDTH 1 /* AIF1TX3_ENA */ +#define WM5100_AIF1TX2_ENA 0x0002 /* AIF1TX2_ENA */ +#define WM5100_AIF1TX2_ENA_MASK 0x0002 /* AIF1TX2_ENA */ +#define WM5100_AIF1TX2_ENA_SHIFT 1 /* AIF1TX2_ENA */ +#define WM5100_AIF1TX2_ENA_WIDTH 1 /* AIF1TX2_ENA */ +#define WM5100_AIF1TX1_ENA 0x0001 /* AIF1TX1_ENA */ +#define WM5100_AIF1TX1_ENA_MASK 0x0001 /* AIF1TX1_ENA */ +#define WM5100_AIF1TX1_ENA_SHIFT 0 /* AIF1TX1_ENA */ +#define WM5100_AIF1TX1_ENA_WIDTH 1 /* AIF1TX1_ENA */ + +/* + * R1306 (0x51A) - Audio IF 1_27 + */ +#define WM5100_AIF1RX8_ENA 0x0080 /* AIF1RX8_ENA */ +#define WM5100_AIF1RX8_ENA_MASK 0x0080 /* AIF1RX8_ENA */ +#define WM5100_AIF1RX8_ENA_SHIFT 7 /* AIF1RX8_ENA */ +#define WM5100_AIF1RX8_ENA_WIDTH 1 /* AIF1RX8_ENA */ +#define WM5100_AIF1RX7_ENA 0x0040 /* AIF1RX7_ENA */ +#define WM5100_AIF1RX7_ENA_MASK 0x0040 /* AIF1RX7_ENA */ +#define WM5100_AIF1RX7_ENA_SHIFT 6 /* AIF1RX7_ENA */ +#define WM5100_AIF1RX7_ENA_WIDTH 1 /* AIF1RX7_ENA */ +#define WM5100_AIF1RX6_ENA 0x0020 /* AIF1RX6_ENA */ +#define WM5100_AIF1RX6_ENA_MASK 0x0020 /* AIF1RX6_ENA */ +#define WM5100_AIF1RX6_ENA_SHIFT 5 /* AIF1RX6_ENA */ +#define WM5100_AIF1RX6_ENA_WIDTH 1 /* AIF1RX6_ENA */ +#define WM5100_AIF1RX5_ENA 0x0010 /* AIF1RX5_ENA */ +#define WM5100_AIF1RX5_ENA_MASK 0x0010 /* AIF1RX5_ENA */ +#define WM5100_AIF1RX5_ENA_SHIFT 4 /* AIF1RX5_ENA */ +#define WM5100_AIF1RX5_ENA_WIDTH 1 /* AIF1RX5_ENA */ +#define WM5100_AIF1RX4_ENA 0x0008 /* AIF1RX4_ENA */ +#define WM5100_AIF1RX4_ENA_MASK 0x0008 /* AIF1RX4_ENA */ +#define WM5100_AIF1RX4_ENA_SHIFT 3 /* AIF1RX4_ENA */ +#define WM5100_AIF1RX4_ENA_WIDTH 1 /* AIF1RX4_ENA */ +#define WM5100_AIF1RX3_ENA 0x0004 /* AIF1RX3_ENA */ +#define WM5100_AIF1RX3_ENA_MASK 0x0004 /* AIF1RX3_ENA */ +#define WM5100_AIF1RX3_ENA_SHIFT 2 /* AIF1RX3_ENA */ +#define WM5100_AIF1RX3_ENA_WIDTH 1 /* AIF1RX3_ENA */ +#define WM5100_AIF1RX2_ENA 0x0002 /* AIF1RX2_ENA */ +#define WM5100_AIF1RX2_ENA_MASK 0x0002 /* AIF1RX2_ENA */ +#define WM5100_AIF1RX2_ENA_SHIFT 1 /* AIF1RX2_ENA */ +#define WM5100_AIF1RX2_ENA_WIDTH 1 /* AIF1RX2_ENA */ +#define WM5100_AIF1RX1_ENA 0x0001 /* AIF1RX1_ENA */ +#define WM5100_AIF1RX1_ENA_MASK 0x0001 /* AIF1RX1_ENA */ +#define WM5100_AIF1RX1_ENA_SHIFT 0 /* AIF1RX1_ENA */ +#define WM5100_AIF1RX1_ENA_WIDTH 1 /* AIF1RX1_ENA */ + +/* + * R1344 (0x540) - Audio IF 2_1 + */ +#define WM5100_AIF2_BCLK_INV 0x0080 /* AIF2_BCLK_INV */ +#define WM5100_AIF2_BCLK_INV_MASK 0x0080 /* AIF2_BCLK_INV */ +#define WM5100_AIF2_BCLK_INV_SHIFT 7 /* AIF2_BCLK_INV */ +#define WM5100_AIF2_BCLK_INV_WIDTH 1 /* AIF2_BCLK_INV */ +#define WM5100_AIF2_BCLK_FRC 0x0040 /* AIF2_BCLK_FRC */ +#define WM5100_AIF2_BCLK_FRC_MASK 0x0040 /* AIF2_BCLK_FRC */ +#define WM5100_AIF2_BCLK_FRC_SHIFT 6 /* AIF2_BCLK_FRC */ +#define WM5100_AIF2_BCLK_FRC_WIDTH 1 /* AIF2_BCLK_FRC */ +#define WM5100_AIF2_BCLK_MSTR 0x0020 /* AIF2_BCLK_MSTR */ +#define WM5100_AIF2_BCLK_MSTR_MASK 0x0020 /* AIF2_BCLK_MSTR */ +#define WM5100_AIF2_BCLK_MSTR_SHIFT 5 /* AIF2_BCLK_MSTR */ +#define WM5100_AIF2_BCLK_MSTR_WIDTH 1 /* AIF2_BCLK_MSTR */ +#define WM5100_AIF2_BCLK_FREQ_MASK 0x001F /* AIF2_BCLK_FREQ - [4:0] */ +#define WM5100_AIF2_BCLK_FREQ_SHIFT 0 /* AIF2_BCLK_FREQ - [4:0] */ +#define WM5100_AIF2_BCLK_FREQ_WIDTH 5 /* AIF2_BCLK_FREQ - [4:0] */ + +/* + * R1345 (0x541) - Audio IF 2_2 + */ +#define WM5100_AIF2TX_DAT_TRI 0x0020 /* AIF2TX_DAT_TRI */ +#define WM5100_AIF2TX_DAT_TRI_MASK 0x0020 /* AIF2TX_DAT_TRI */ +#define WM5100_AIF2TX_DAT_TRI_SHIFT 5 /* AIF2TX_DAT_TRI */ +#define WM5100_AIF2TX_DAT_TRI_WIDTH 1 /* AIF2TX_DAT_TRI */ +#define WM5100_AIF2TX_LRCLK_SRC 0x0008 /* AIF2TX_LRCLK_SRC */ +#define WM5100_AIF2TX_LRCLK_SRC_MASK 0x0008 /* AIF2TX_LRCLK_SRC */ +#define WM5100_AIF2TX_LRCLK_SRC_SHIFT 3 /* AIF2TX_LRCLK_SRC */ +#define WM5100_AIF2TX_LRCLK_SRC_WIDTH 1 /* AIF2TX_LRCLK_SRC */ +#define WM5100_AIF2TX_LRCLK_INV 0x0004 /* AIF2TX_LRCLK_INV */ +#define WM5100_AIF2TX_LRCLK_INV_MASK 0x0004 /* AIF2TX_LRCLK_INV */ +#define WM5100_AIF2TX_LRCLK_INV_SHIFT 2 /* AIF2TX_LRCLK_INV */ +#define WM5100_AIF2TX_LRCLK_INV_WIDTH 1 /* AIF2TX_LRCLK_INV */ +#define WM5100_AIF2TX_LRCLK_FRC 0x0002 /* AIF2TX_LRCLK_FRC */ +#define WM5100_AIF2TX_LRCLK_FRC_MASK 0x0002 /* AIF2TX_LRCLK_FRC */ +#define WM5100_AIF2TX_LRCLK_FRC_SHIFT 1 /* AIF2TX_LRCLK_FRC */ +#define WM5100_AIF2TX_LRCLK_FRC_WIDTH 1 /* AIF2TX_LRCLK_FRC */ +#define WM5100_AIF2TX_LRCLK_MSTR 0x0001 /* AIF2TX_LRCLK_MSTR */ +#define WM5100_AIF2TX_LRCLK_MSTR_MASK 0x0001 /* AIF2TX_LRCLK_MSTR */ +#define WM5100_AIF2TX_LRCLK_MSTR_SHIFT 0 /* AIF2TX_LRCLK_MSTR */ +#define WM5100_AIF2TX_LRCLK_MSTR_WIDTH 1 /* AIF2TX_LRCLK_MSTR */ + +/* + * R1346 (0x542) - Audio IF 2_3 + */ +#define WM5100_AIF2RX_LRCLK_INV 0x0004 /* AIF2RX_LRCLK_INV */ +#define WM5100_AIF2RX_LRCLK_INV_MASK 0x0004 /* AIF2RX_LRCLK_INV */ +#define WM5100_AIF2RX_LRCLK_INV_SHIFT 2 /* AIF2RX_LRCLK_INV */ +#define WM5100_AIF2RX_LRCLK_INV_WIDTH 1 /* AIF2RX_LRCLK_INV */ +#define WM5100_AIF2RX_LRCLK_FRC 0x0002 /* AIF2RX_LRCLK_FRC */ +#define WM5100_AIF2RX_LRCLK_FRC_MASK 0x0002 /* AIF2RX_LRCLK_FRC */ +#define WM5100_AIF2RX_LRCLK_FRC_SHIFT 1 /* AIF2RX_LRCLK_FRC */ +#define WM5100_AIF2RX_LRCLK_FRC_WIDTH 1 /* AIF2RX_LRCLK_FRC */ +#define WM5100_AIF2RX_LRCLK_MSTR 0x0001 /* AIF2RX_LRCLK_MSTR */ +#define WM5100_AIF2RX_LRCLK_MSTR_MASK 0x0001 /* AIF2RX_LRCLK_MSTR */ +#define WM5100_AIF2RX_LRCLK_MSTR_SHIFT 0 /* AIF2RX_LRCLK_MSTR */ +#define WM5100_AIF2RX_LRCLK_MSTR_WIDTH 1 /* AIF2RX_LRCLK_MSTR */ + +/* + * R1347 (0x543) - Audio IF 2_4 + */ +#define WM5100_AIF2_TRI 0x0040 /* AIF2_TRI */ +#define WM5100_AIF2_TRI_MASK 0x0040 /* AIF2_TRI */ +#define WM5100_AIF2_TRI_SHIFT 6 /* AIF2_TRI */ +#define WM5100_AIF2_TRI_WIDTH 1 /* AIF2_TRI */ +#define WM5100_AIF2_RATE_MASK 0x0003 /* AIF2_RATE - [1:0] */ +#define WM5100_AIF2_RATE_SHIFT 0 /* AIF2_RATE - [1:0] */ +#define WM5100_AIF2_RATE_WIDTH 2 /* AIF2_RATE - [1:0] */ + +/* + * R1348 (0x544) - Audio IF 2_5 + */ +#define WM5100_AIF2_FMT_MASK 0x0007 /* AIF2_FMT - [2:0] */ +#define WM5100_AIF2_FMT_SHIFT 0 /* AIF2_FMT - [2:0] */ +#define WM5100_AIF2_FMT_WIDTH 3 /* AIF2_FMT - [2:0] */ + +/* + * R1349 (0x545) - Audio IF 2_6 + */ +#define WM5100_AIF2TX_BCPF_MASK 0x1FFF /* AIF2TX_BCPF - [12:0] */ +#define WM5100_AIF2TX_BCPF_SHIFT 0 /* AIF2TX_BCPF - [12:0] */ +#define WM5100_AIF2TX_BCPF_WIDTH 13 /* AIF2TX_BCPF - [12:0] */ + +/* + * R1350 (0x546) - Audio IF 2_7 + */ +#define WM5100_AIF2RX_BCPF_MASK 0x1FFF /* AIF2RX_BCPF - [12:0] */ +#define WM5100_AIF2RX_BCPF_SHIFT 0 /* AIF2RX_BCPF - [12:0] */ +#define WM5100_AIF2RX_BCPF_WIDTH 13 /* AIF2RX_BCPF - [12:0] */ + +/* + * R1351 (0x547) - Audio IF 2_8 + */ +#define WM5100_AIF2TX_WL_MASK 0x3F00 /* AIF2TX_WL - [13:8] */ +#define WM5100_AIF2TX_WL_SHIFT 8 /* AIF2TX_WL - [13:8] */ +#define WM5100_AIF2TX_WL_WIDTH 6 /* AIF2TX_WL - [13:8] */ +#define WM5100_AIF2TX_SLOT_LEN_MASK 0x00FF /* AIF2TX_SLOT_LEN - [7:0] */ +#define WM5100_AIF2TX_SLOT_LEN_SHIFT 0 /* AIF2TX_SLOT_LEN - [7:0] */ +#define WM5100_AIF2TX_SLOT_LEN_WIDTH 8 /* AIF2TX_SLOT_LEN - [7:0] */ + +/* + * R1352 (0x548) - Audio IF 2_9 + */ +#define WM5100_AIF2RX_WL_MASK 0x3F00 /* AIF2RX_WL - [13:8] */ +#define WM5100_AIF2RX_WL_SHIFT 8 /* AIF2RX_WL - [13:8] */ +#define WM5100_AIF2RX_WL_WIDTH 6 /* AIF2RX_WL - [13:8] */ +#define WM5100_AIF2RX_SLOT_LEN_MASK 0x00FF /* AIF2RX_SLOT_LEN - [7:0] */ +#define WM5100_AIF2RX_SLOT_LEN_SHIFT 0 /* AIF2RX_SLOT_LEN - [7:0] */ +#define WM5100_AIF2RX_SLOT_LEN_WIDTH 8 /* AIF2RX_SLOT_LEN - [7:0] */ + +/* + * R1353 (0x549) - Audio IF 2_10 + */ +#define WM5100_AIF2TX1_SLOT_MASK 0x003F /* AIF2TX1_SLOT - [5:0] */ +#define WM5100_AIF2TX1_SLOT_SHIFT 0 /* AIF2TX1_SLOT - [5:0] */ +#define WM5100_AIF2TX1_SLOT_WIDTH 6 /* AIF2TX1_SLOT - [5:0] */ + +/* + * R1354 (0x54A) - Audio IF 2_11 + */ +#define WM5100_AIF2TX2_SLOT_MASK 0x003F /* AIF2TX2_SLOT - [5:0] */ +#define WM5100_AIF2TX2_SLOT_SHIFT 0 /* AIF2TX2_SLOT - [5:0] */ +#define WM5100_AIF2TX2_SLOT_WIDTH 6 /* AIF2TX2_SLOT - [5:0] */ + +/* + * R1361 (0x551) - Audio IF 2_18 + */ +#define WM5100_AIF2RX1_SLOT_MASK 0x003F /* AIF2RX1_SLOT - [5:0] */ +#define WM5100_AIF2RX1_SLOT_SHIFT 0 /* AIF2RX1_SLOT - [5:0] */ +#define WM5100_AIF2RX1_SLOT_WIDTH 6 /* AIF2RX1_SLOT - [5:0] */ + +/* + * R1362 (0x552) - Audio IF 2_19 + */ +#define WM5100_AIF2RX2_SLOT_MASK 0x003F /* AIF2RX2_SLOT - [5:0] */ +#define WM5100_AIF2RX2_SLOT_SHIFT 0 /* AIF2RX2_SLOT - [5:0] */ +#define WM5100_AIF2RX2_SLOT_WIDTH 6 /* AIF2RX2_SLOT - [5:0] */ + +/* + * R1369 (0x559) - Audio IF 2_26 + */ +#define WM5100_AIF2TX2_ENA 0x0002 /* AIF2TX2_ENA */ +#define WM5100_AIF2TX2_ENA_MASK 0x0002 /* AIF2TX2_ENA */ +#define WM5100_AIF2TX2_ENA_SHIFT 1 /* AIF2TX2_ENA */ +#define WM5100_AIF2TX2_ENA_WIDTH 1 /* AIF2TX2_ENA */ +#define WM5100_AIF2TX1_ENA 0x0001 /* AIF2TX1_ENA */ +#define WM5100_AIF2TX1_ENA_MASK 0x0001 /* AIF2TX1_ENA */ +#define WM5100_AIF2TX1_ENA_SHIFT 0 /* AIF2TX1_ENA */ +#define WM5100_AIF2TX1_ENA_WIDTH 1 /* AIF2TX1_ENA */ + +/* + * R1370 (0x55A) - Audio IF 2_27 + */ +#define WM5100_AIF2RX2_ENA 0x0002 /* AIF2RX2_ENA */ +#define WM5100_AIF2RX2_ENA_MASK 0x0002 /* AIF2RX2_ENA */ +#define WM5100_AIF2RX2_ENA_SHIFT 1 /* AIF2RX2_ENA */ +#define WM5100_AIF2RX2_ENA_WIDTH 1 /* AIF2RX2_ENA */ +#define WM5100_AIF2RX1_ENA 0x0001 /* AIF2RX1_ENA */ +#define WM5100_AIF2RX1_ENA_MASK 0x0001 /* AIF2RX1_ENA */ +#define WM5100_AIF2RX1_ENA_SHIFT 0 /* AIF2RX1_ENA */ +#define WM5100_AIF2RX1_ENA_WIDTH 1 /* AIF2RX1_ENA */ + +/* + * R1408 (0x580) - Audio IF 3_1 + */ +#define WM5100_AIF3_BCLK_INV 0x0080 /* AIF3_BCLK_INV */ +#define WM5100_AIF3_BCLK_INV_MASK 0x0080 /* AIF3_BCLK_INV */ +#define WM5100_AIF3_BCLK_INV_SHIFT 7 /* AIF3_BCLK_INV */ +#define WM5100_AIF3_BCLK_INV_WIDTH 1 /* AIF3_BCLK_INV */ +#define WM5100_AIF3_BCLK_FRC 0x0040 /* AIF3_BCLK_FRC */ +#define WM5100_AIF3_BCLK_FRC_MASK 0x0040 /* AIF3_BCLK_FRC */ +#define WM5100_AIF3_BCLK_FRC_SHIFT 6 /* AIF3_BCLK_FRC */ +#define WM5100_AIF3_BCLK_FRC_WIDTH 1 /* AIF3_BCLK_FRC */ +#define WM5100_AIF3_BCLK_MSTR 0x0020 /* AIF3_BCLK_MSTR */ +#define WM5100_AIF3_BCLK_MSTR_MASK 0x0020 /* AIF3_BCLK_MSTR */ +#define WM5100_AIF3_BCLK_MSTR_SHIFT 5 /* AIF3_BCLK_MSTR */ +#define WM5100_AIF3_BCLK_MSTR_WIDTH 1 /* AIF3_BCLK_MSTR */ +#define WM5100_AIF3_BCLK_FREQ_MASK 0x001F /* AIF3_BCLK_FREQ - [4:0] */ +#define WM5100_AIF3_BCLK_FREQ_SHIFT 0 /* AIF3_BCLK_FREQ - [4:0] */ +#define WM5100_AIF3_BCLK_FREQ_WIDTH 5 /* AIF3_BCLK_FREQ - [4:0] */ + +/* + * R1409 (0x581) - Audio IF 3_2 + */ +#define WM5100_AIF3TX_DAT_TRI 0x0020 /* AIF3TX_DAT_TRI */ +#define WM5100_AIF3TX_DAT_TRI_MASK 0x0020 /* AIF3TX_DAT_TRI */ +#define WM5100_AIF3TX_DAT_TRI_SHIFT 5 /* AIF3TX_DAT_TRI */ +#define WM5100_AIF3TX_DAT_TRI_WIDTH 1 /* AIF3TX_DAT_TRI */ +#define WM5100_AIF3TX_LRCLK_SRC 0x0008 /* AIF3TX_LRCLK_SRC */ +#define WM5100_AIF3TX_LRCLK_SRC_MASK 0x0008 /* AIF3TX_LRCLK_SRC */ +#define WM5100_AIF3TX_LRCLK_SRC_SHIFT 3 /* AIF3TX_LRCLK_SRC */ +#define WM5100_AIF3TX_LRCLK_SRC_WIDTH 1 /* AIF3TX_LRCLK_SRC */ +#define WM5100_AIF3TX_LRCLK_INV 0x0004 /* AIF3TX_LRCLK_INV */ +#define WM5100_AIF3TX_LRCLK_INV_MASK 0x0004 /* AIF3TX_LRCLK_INV */ +#define WM5100_AIF3TX_LRCLK_INV_SHIFT 2 /* AIF3TX_LRCLK_INV */ +#define WM5100_AIF3TX_LRCLK_INV_WIDTH 1 /* AIF3TX_LRCLK_INV */ +#define WM5100_AIF3TX_LRCLK_FRC 0x0002 /* AIF3TX_LRCLK_FRC */ +#define WM5100_AIF3TX_LRCLK_FRC_MASK 0x0002 /* AIF3TX_LRCLK_FRC */ +#define WM5100_AIF3TX_LRCLK_FRC_SHIFT 1 /* AIF3TX_LRCLK_FRC */ +#define WM5100_AIF3TX_LRCLK_FRC_WIDTH 1 /* AIF3TX_LRCLK_FRC */ +#define WM5100_AIF3TX_LRCLK_MSTR 0x0001 /* AIF3TX_LRCLK_MSTR */ +#define WM5100_AIF3TX_LRCLK_MSTR_MASK 0x0001 /* AIF3TX_LRCLK_MSTR */ +#define WM5100_AIF3TX_LRCLK_MSTR_SHIFT 0 /* AIF3TX_LRCLK_MSTR */ +#define WM5100_AIF3TX_LRCLK_MSTR_WIDTH 1 /* AIF3TX_LRCLK_MSTR */ + +/* + * R1410 (0x582) - Audio IF 3_3 + */ +#define WM5100_AIF3RX_LRCLK_INV 0x0004 /* AIF3RX_LRCLK_INV */ +#define WM5100_AIF3RX_LRCLK_INV_MASK 0x0004 /* AIF3RX_LRCLK_INV */ +#define WM5100_AIF3RX_LRCLK_INV_SHIFT 2 /* AIF3RX_LRCLK_INV */ +#define WM5100_AIF3RX_LRCLK_INV_WIDTH 1 /* AIF3RX_LRCLK_INV */ +#define WM5100_AIF3RX_LRCLK_FRC 0x0002 /* AIF3RX_LRCLK_FRC */ +#define WM5100_AIF3RX_LRCLK_FRC_MASK 0x0002 /* AIF3RX_LRCLK_FRC */ +#define WM5100_AIF3RX_LRCLK_FRC_SHIFT 1 /* AIF3RX_LRCLK_FRC */ +#define WM5100_AIF3RX_LRCLK_FRC_WIDTH 1 /* AIF3RX_LRCLK_FRC */ +#define WM5100_AIF3RX_LRCLK_MSTR 0x0001 /* AIF3RX_LRCLK_MSTR */ +#define WM5100_AIF3RX_LRCLK_MSTR_MASK 0x0001 /* AIF3RX_LRCLK_MSTR */ +#define WM5100_AIF3RX_LRCLK_MSTR_SHIFT 0 /* AIF3RX_LRCLK_MSTR */ +#define WM5100_AIF3RX_LRCLK_MSTR_WIDTH 1 /* AIF3RX_LRCLK_MSTR */ + +/* + * R1411 (0x583) - Audio IF 3_4 + */ +#define WM5100_AIF3_TRI 0x0040 /* AIF3_TRI */ +#define WM5100_AIF3_TRI_MASK 0x0040 /* AIF3_TRI */ +#define WM5100_AIF3_TRI_SHIFT 6 /* AIF3_TRI */ +#define WM5100_AIF3_TRI_WIDTH 1 /* AIF3_TRI */ +#define WM5100_AIF3_RATE_MASK 0x0003 /* AIF3_RATE - [1:0] */ +#define WM5100_AIF3_RATE_SHIFT 0 /* AIF3_RATE - [1:0] */ +#define WM5100_AIF3_RATE_WIDTH 2 /* AIF3_RATE - [1:0] */ + +/* + * R1412 (0x584) - Audio IF 3_5 + */ +#define WM5100_AIF3_FMT_MASK 0x0007 /* AIF3_FMT - [2:0] */ +#define WM5100_AIF3_FMT_SHIFT 0 /* AIF3_FMT - [2:0] */ +#define WM5100_AIF3_FMT_WIDTH 3 /* AIF3_FMT - [2:0] */ + +/* + * R1413 (0x585) - Audio IF 3_6 + */ +#define WM5100_AIF3TX_BCPF_MASK 0x1FFF /* AIF3TX_BCPF - [12:0] */ +#define WM5100_AIF3TX_BCPF_SHIFT 0 /* AIF3TX_BCPF - [12:0] */ +#define WM5100_AIF3TX_BCPF_WIDTH 13 /* AIF3TX_BCPF - [12:0] */ + +/* + * R1414 (0x586) - Audio IF 3_7 + */ +#define WM5100_AIF3RX_BCPF_MASK 0x1FFF /* AIF3RX_BCPF - [12:0] */ +#define WM5100_AIF3RX_BCPF_SHIFT 0 /* AIF3RX_BCPF - [12:0] */ +#define WM5100_AIF3RX_BCPF_WIDTH 13 /* AIF3RX_BCPF - [12:0] */ + +/* + * R1415 (0x587) - Audio IF 3_8 + */ +#define WM5100_AIF3TX_WL_MASK 0x3F00 /* AIF3TX_WL - [13:8] */ +#define WM5100_AIF3TX_WL_SHIFT 8 /* AIF3TX_WL - [13:8] */ +#define WM5100_AIF3TX_WL_WIDTH 6 /* AIF3TX_WL - [13:8] */ +#define WM5100_AIF3TX_SLOT_LEN_MASK 0x00FF /* AIF3TX_SLOT_LEN - [7:0] */ +#define WM5100_AIF3TX_SLOT_LEN_SHIFT 0 /* AIF3TX_SLOT_LEN - [7:0] */ +#define WM5100_AIF3TX_SLOT_LEN_WIDTH 8 /* AIF3TX_SLOT_LEN - [7:0] */ + +/* + * R1416 (0x588) - Audio IF 3_9 + */ +#define WM5100_AIF3RX_WL_MASK 0x3F00 /* AIF3RX_WL - [13:8] */ +#define WM5100_AIF3RX_WL_SHIFT 8 /* AIF3RX_WL - [13:8] */ +#define WM5100_AIF3RX_WL_WIDTH 6 /* AIF3RX_WL - [13:8] */ +#define WM5100_AIF3RX_SLOT_LEN_MASK 0x00FF /* AIF3RX_SLOT_LEN - [7:0] */ +#define WM5100_AIF3RX_SLOT_LEN_SHIFT 0 /* AIF3RX_SLOT_LEN - [7:0] */ +#define WM5100_AIF3RX_SLOT_LEN_WIDTH 8 /* AIF3RX_SLOT_LEN - [7:0] */ + +/* + * R1417 (0x589) - Audio IF 3_10 + */ +#define WM5100_AIF3TX1_SLOT_MASK 0x003F /* AIF3TX1_SLOT - [5:0] */ +#define WM5100_AIF3TX1_SLOT_SHIFT 0 /* AIF3TX1_SLOT - [5:0] */ +#define WM5100_AIF3TX1_SLOT_WIDTH 6 /* AIF3TX1_SLOT - [5:0] */ + +/* + * R1418 (0x58A) - Audio IF 3_11 + */ +#define WM5100_AIF3TX2_SLOT_MASK 0x003F /* AIF3TX2_SLOT - [5:0] */ +#define WM5100_AIF3TX2_SLOT_SHIFT 0 /* AIF3TX2_SLOT - [5:0] */ +#define WM5100_AIF3TX2_SLOT_WIDTH 6 /* AIF3TX2_SLOT - [5:0] */ + +/* + * R1425 (0x591) - Audio IF 3_18 + */ +#define WM5100_AIF3RX1_SLOT_MASK 0x003F /* AIF3RX1_SLOT - [5:0] */ +#define WM5100_AIF3RX1_SLOT_SHIFT 0 /* AIF3RX1_SLOT - [5:0] */ +#define WM5100_AIF3RX1_SLOT_WIDTH 6 /* AIF3RX1_SLOT - [5:0] */ + +/* + * R1426 (0x592) - Audio IF 3_19 + */ +#define WM5100_AIF3RX2_SLOT_MASK 0x003F /* AIF3RX2_SLOT - [5:0] */ +#define WM5100_AIF3RX2_SLOT_SHIFT 0 /* AIF3RX2_SLOT - [5:0] */ +#define WM5100_AIF3RX2_SLOT_WIDTH 6 /* AIF3RX2_SLOT - [5:0] */ + +/* + * R1433 (0x599) - Audio IF 3_26 + */ +#define WM5100_AIF3TX2_ENA 0x0002 /* AIF3TX2_ENA */ +#define WM5100_AIF3TX2_ENA_MASK 0x0002 /* AIF3TX2_ENA */ +#define WM5100_AIF3TX2_ENA_SHIFT 1 /* AIF3TX2_ENA */ +#define WM5100_AIF3TX2_ENA_WIDTH 1 /* AIF3TX2_ENA */ +#define WM5100_AIF3TX1_ENA 0x0001 /* AIF3TX1_ENA */ +#define WM5100_AIF3TX1_ENA_MASK 0x0001 /* AIF3TX1_ENA */ +#define WM5100_AIF3TX1_ENA_SHIFT 0 /* AIF3TX1_ENA */ +#define WM5100_AIF3TX1_ENA_WIDTH 1 /* AIF3TX1_ENA */ + +/* + * R1434 (0x59A) - Audio IF 3_27 + */ +#define WM5100_AIF3RX2_ENA 0x0002 /* AIF3RX2_ENA */ +#define WM5100_AIF3RX2_ENA_MASK 0x0002 /* AIF3RX2_ENA */ +#define WM5100_AIF3RX2_ENA_SHIFT 1 /* AIF3RX2_ENA */ +#define WM5100_AIF3RX2_ENA_WIDTH 1 /* AIF3RX2_ENA */ +#define WM5100_AIF3RX1_ENA 0x0001 /* AIF3RX1_ENA */ +#define WM5100_AIF3RX1_ENA_MASK 0x0001 /* AIF3RX1_ENA */ +#define WM5100_AIF3RX1_ENA_SHIFT 0 /* AIF3RX1_ENA */ +#define WM5100_AIF3RX1_ENA_WIDTH 1 /* AIF3RX1_ENA */ + +#define WM5100_MIXER_VOL_MASK 0x00FE /* MIXER_VOL - [7:1] */ +#define WM5100_MIXER_VOL_SHIFT 1 /* MIXER_VOL - [7:1] */ +#define WM5100_MIXER_VOL_WIDTH 7 /* MIXER_VOL - [7:1] */ + +/* + * R3072 (0xC00) - GPIO CTRL 1 + */ +#define WM5100_GP1_DIR 0x8000 /* GP1_DIR */ +#define WM5100_GP1_DIR_MASK 0x8000 /* GP1_DIR */ +#define WM5100_GP1_DIR_SHIFT 15 /* GP1_DIR */ +#define WM5100_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM5100_GP1_PU 0x4000 /* GP1_PU */ +#define WM5100_GP1_PU_MASK 0x4000 /* GP1_PU */ +#define WM5100_GP1_PU_SHIFT 14 /* GP1_PU */ +#define WM5100_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM5100_GP1_PD 0x2000 /* GP1_PD */ +#define WM5100_GP1_PD_MASK 0x2000 /* GP1_PD */ +#define WM5100_GP1_PD_SHIFT 13 /* GP1_PD */ +#define WM5100_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM5100_GP1_POL 0x0400 /* GP1_POL */ +#define WM5100_GP1_POL_MASK 0x0400 /* GP1_POL */ +#define WM5100_GP1_POL_SHIFT 10 /* GP1_POL */ +#define WM5100_GP1_POL_WIDTH 1 /* GP1_POL */ +#define WM5100_GP1_OP_CFG 0x0200 /* GP1_OP_CFG */ +#define WM5100_GP1_OP_CFG_MASK 0x0200 /* GP1_OP_CFG */ +#define WM5100_GP1_OP_CFG_SHIFT 9 /* GP1_OP_CFG */ +#define WM5100_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM5100_GP1_DB 0x0100 /* GP1_DB */ +#define WM5100_GP1_DB_MASK 0x0100 /* GP1_DB */ +#define WM5100_GP1_DB_SHIFT 8 /* GP1_DB */ +#define WM5100_GP1_DB_WIDTH 1 /* GP1_DB */ +#define WM5100_GP1_LVL 0x0040 /* GP1_LVL */ +#define WM5100_GP1_LVL_MASK 0x0040 /* GP1_LVL */ +#define WM5100_GP1_LVL_SHIFT 6 /* GP1_LVL */ +#define WM5100_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM5100_GP1_FN_MASK 0x003F /* GP1_FN - [5:0] */ +#define WM5100_GP1_FN_SHIFT 0 /* GP1_FN - [5:0] */ +#define WM5100_GP1_FN_WIDTH 6 /* GP1_FN - [5:0] */ + +/* + * R3073 (0xC01) - GPIO CTRL 2 + */ +#define WM5100_GP2_DIR 0x8000 /* GP2_DIR */ +#define WM5100_GP2_DIR_MASK 0x8000 /* GP2_DIR */ +#define WM5100_GP2_DIR_SHIFT 15 /* GP2_DIR */ +#define WM5100_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM5100_GP2_PU 0x4000 /* GP2_PU */ +#define WM5100_GP2_PU_MASK 0x4000 /* GP2_PU */ +#define WM5100_GP2_PU_SHIFT 14 /* GP2_PU */ +#define WM5100_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM5100_GP2_PD 0x2000 /* GP2_PD */ +#define WM5100_GP2_PD_MASK 0x2000 /* GP2_PD */ +#define WM5100_GP2_PD_SHIFT 13 /* GP2_PD */ +#define WM5100_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM5100_GP2_POL 0x0400 /* GP2_POL */ +#define WM5100_GP2_POL_MASK 0x0400 /* GP2_POL */ +#define WM5100_GP2_POL_SHIFT 10 /* GP2_POL */ +#define WM5100_GP2_POL_WIDTH 1 /* GP2_POL */ +#define WM5100_GP2_OP_CFG 0x0200 /* GP2_OP_CFG */ +#define WM5100_GP2_OP_CFG_MASK 0x0200 /* GP2_OP_CFG */ +#define WM5100_GP2_OP_CFG_SHIFT 9 /* GP2_OP_CFG */ +#define WM5100_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM5100_GP2_DB 0x0100 /* GP2_DB */ +#define WM5100_GP2_DB_MASK 0x0100 /* GP2_DB */ +#define WM5100_GP2_DB_SHIFT 8 /* GP2_DB */ +#define WM5100_GP2_DB_WIDTH 1 /* GP2_DB */ +#define WM5100_GP2_LVL 0x0040 /* GP2_LVL */ +#define WM5100_GP2_LVL_MASK 0x0040 /* GP2_LVL */ +#define WM5100_GP2_LVL_SHIFT 6 /* GP2_LVL */ +#define WM5100_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM5100_GP2_FN_MASK 0x003F /* GP2_FN - [5:0] */ +#define WM5100_GP2_FN_SHIFT 0 /* GP2_FN - [5:0] */ +#define WM5100_GP2_FN_WIDTH 6 /* GP2_FN - [5:0] */ + +/* + * R3074 (0xC02) - GPIO CTRL 3 + */ +#define WM5100_GP3_DIR 0x8000 /* GP3_DIR */ +#define WM5100_GP3_DIR_MASK 0x8000 /* GP3_DIR */ +#define WM5100_GP3_DIR_SHIFT 15 /* GP3_DIR */ +#define WM5100_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM5100_GP3_PU 0x4000 /* GP3_PU */ +#define WM5100_GP3_PU_MASK 0x4000 /* GP3_PU */ +#define WM5100_GP3_PU_SHIFT 14 /* GP3_PU */ +#define WM5100_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM5100_GP3_PD 0x2000 /* GP3_PD */ +#define WM5100_GP3_PD_MASK 0x2000 /* GP3_PD */ +#define WM5100_GP3_PD_SHIFT 13 /* GP3_PD */ +#define WM5100_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM5100_GP3_POL 0x0400 /* GP3_POL */ +#define WM5100_GP3_POL_MASK 0x0400 /* GP3_POL */ +#define WM5100_GP3_POL_SHIFT 10 /* GP3_POL */ +#define WM5100_GP3_POL_WIDTH 1 /* GP3_POL */ +#define WM5100_GP3_OP_CFG 0x0200 /* GP3_OP_CFG */ +#define WM5100_GP3_OP_CFG_MASK 0x0200 /* GP3_OP_CFG */ +#define WM5100_GP3_OP_CFG_SHIFT 9 /* GP3_OP_CFG */ +#define WM5100_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM5100_GP3_DB 0x0100 /* GP3_DB */ +#define WM5100_GP3_DB_MASK 0x0100 /* GP3_DB */ +#define WM5100_GP3_DB_SHIFT 8 /* GP3_DB */ +#define WM5100_GP3_DB_WIDTH 1 /* GP3_DB */ +#define WM5100_GP3_LVL 0x0040 /* GP3_LVL */ +#define WM5100_GP3_LVL_MASK 0x0040 /* GP3_LVL */ +#define WM5100_GP3_LVL_SHIFT 6 /* GP3_LVL */ +#define WM5100_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM5100_GP3_FN_MASK 0x003F /* GP3_FN - [5:0] */ +#define WM5100_GP3_FN_SHIFT 0 /* GP3_FN - [5:0] */ +#define WM5100_GP3_FN_WIDTH 6 /* GP3_FN - [5:0] */ + +/* + * R3075 (0xC03) - GPIO CTRL 4 + */ +#define WM5100_GP4_DIR 0x8000 /* GP4_DIR */ +#define WM5100_GP4_DIR_MASK 0x8000 /* GP4_DIR */ +#define WM5100_GP4_DIR_SHIFT 15 /* GP4_DIR */ +#define WM5100_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM5100_GP4_PU 0x4000 /* GP4_PU */ +#define WM5100_GP4_PU_MASK 0x4000 /* GP4_PU */ +#define WM5100_GP4_PU_SHIFT 14 /* GP4_PU */ +#define WM5100_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM5100_GP4_PD 0x2000 /* GP4_PD */ +#define WM5100_GP4_PD_MASK 0x2000 /* GP4_PD */ +#define WM5100_GP4_PD_SHIFT 13 /* GP4_PD */ +#define WM5100_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM5100_GP4_POL 0x0400 /* GP4_POL */ +#define WM5100_GP4_POL_MASK 0x0400 /* GP4_POL */ +#define WM5100_GP4_POL_SHIFT 10 /* GP4_POL */ +#define WM5100_GP4_POL_WIDTH 1 /* GP4_POL */ +#define WM5100_GP4_OP_CFG 0x0200 /* GP4_OP_CFG */ +#define WM5100_GP4_OP_CFG_MASK 0x0200 /* GP4_OP_CFG */ +#define WM5100_GP4_OP_CFG_SHIFT 9 /* GP4_OP_CFG */ +#define WM5100_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM5100_GP4_DB 0x0100 /* GP4_DB */ +#define WM5100_GP4_DB_MASK 0x0100 /* GP4_DB */ +#define WM5100_GP4_DB_SHIFT 8 /* GP4_DB */ +#define WM5100_GP4_DB_WIDTH 1 /* GP4_DB */ +#define WM5100_GP4_LVL 0x0040 /* GP4_LVL */ +#define WM5100_GP4_LVL_MASK 0x0040 /* GP4_LVL */ +#define WM5100_GP4_LVL_SHIFT 6 /* GP4_LVL */ +#define WM5100_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM5100_GP4_FN_MASK 0x003F /* GP4_FN - [5:0] */ +#define WM5100_GP4_FN_SHIFT 0 /* GP4_FN - [5:0] */ +#define WM5100_GP4_FN_WIDTH 6 /* GP4_FN - [5:0] */ + +/* + * R3076 (0xC04) - GPIO CTRL 5 + */ +#define WM5100_GP5_DIR 0x8000 /* GP5_DIR */ +#define WM5100_GP5_DIR_MASK 0x8000 /* GP5_DIR */ +#define WM5100_GP5_DIR_SHIFT 15 /* GP5_DIR */ +#define WM5100_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM5100_GP5_PU 0x4000 /* GP5_PU */ +#define WM5100_GP5_PU_MASK 0x4000 /* GP5_PU */ +#define WM5100_GP5_PU_SHIFT 14 /* GP5_PU */ +#define WM5100_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM5100_GP5_PD 0x2000 /* GP5_PD */ +#define WM5100_GP5_PD_MASK 0x2000 /* GP5_PD */ +#define WM5100_GP5_PD_SHIFT 13 /* GP5_PD */ +#define WM5100_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM5100_GP5_POL 0x0400 /* GP5_POL */ +#define WM5100_GP5_POL_MASK 0x0400 /* GP5_POL */ +#define WM5100_GP5_POL_SHIFT 10 /* GP5_POL */ +#define WM5100_GP5_POL_WIDTH 1 /* GP5_POL */ +#define WM5100_GP5_OP_CFG 0x0200 /* GP5_OP_CFG */ +#define WM5100_GP5_OP_CFG_MASK 0x0200 /* GP5_OP_CFG */ +#define WM5100_GP5_OP_CFG_SHIFT 9 /* GP5_OP_CFG */ +#define WM5100_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM5100_GP5_DB 0x0100 /* GP5_DB */ +#define WM5100_GP5_DB_MASK 0x0100 /* GP5_DB */ +#define WM5100_GP5_DB_SHIFT 8 /* GP5_DB */ +#define WM5100_GP5_DB_WIDTH 1 /* GP5_DB */ +#define WM5100_GP5_LVL 0x0040 /* GP5_LVL */ +#define WM5100_GP5_LVL_MASK 0x0040 /* GP5_LVL */ +#define WM5100_GP5_LVL_SHIFT 6 /* GP5_LVL */ +#define WM5100_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM5100_GP5_FN_MASK 0x003F /* GP5_FN - [5:0] */ +#define WM5100_GP5_FN_SHIFT 0 /* GP5_FN - [5:0] */ +#define WM5100_GP5_FN_WIDTH 6 /* GP5_FN - [5:0] */ + +/* + * R3077 (0xC05) - GPIO CTRL 6 + */ +#define WM5100_GP6_DIR 0x8000 /* GP6_DIR */ +#define WM5100_GP6_DIR_MASK 0x8000 /* GP6_DIR */ +#define WM5100_GP6_DIR_SHIFT 15 /* GP6_DIR */ +#define WM5100_GP6_DIR_WIDTH 1 /* GP6_DIR */ +#define WM5100_GP6_PU 0x4000 /* GP6_PU */ +#define WM5100_GP6_PU_MASK 0x4000 /* GP6_PU */ +#define WM5100_GP6_PU_SHIFT 14 /* GP6_PU */ +#define WM5100_GP6_PU_WIDTH 1 /* GP6_PU */ +#define WM5100_GP6_PD 0x2000 /* GP6_PD */ +#define WM5100_GP6_PD_MASK 0x2000 /* GP6_PD */ +#define WM5100_GP6_PD_SHIFT 13 /* GP6_PD */ +#define WM5100_GP6_PD_WIDTH 1 /* GP6_PD */ +#define WM5100_GP6_POL 0x0400 /* GP6_POL */ +#define WM5100_GP6_POL_MASK 0x0400 /* GP6_POL */ +#define WM5100_GP6_POL_SHIFT 10 /* GP6_POL */ +#define WM5100_GP6_POL_WIDTH 1 /* GP6_POL */ +#define WM5100_GP6_OP_CFG 0x0200 /* GP6_OP_CFG */ +#define WM5100_GP6_OP_CFG_MASK 0x0200 /* GP6_OP_CFG */ +#define WM5100_GP6_OP_CFG_SHIFT 9 /* GP6_OP_CFG */ +#define WM5100_GP6_OP_CFG_WIDTH 1 /* GP6_OP_CFG */ +#define WM5100_GP6_DB 0x0100 /* GP6_DB */ +#define WM5100_GP6_DB_MASK 0x0100 /* GP6_DB */ +#define WM5100_GP6_DB_SHIFT 8 /* GP6_DB */ +#define WM5100_GP6_DB_WIDTH 1 /* GP6_DB */ +#define WM5100_GP6_LVL 0x0040 /* GP6_LVL */ +#define WM5100_GP6_LVL_MASK 0x0040 /* GP6_LVL */ +#define WM5100_GP6_LVL_SHIFT 6 /* GP6_LVL */ +#define WM5100_GP6_LVL_WIDTH 1 /* GP6_LVL */ +#define WM5100_GP6_FN_MASK 0x003F /* GP6_FN - [5:0] */ +#define WM5100_GP6_FN_SHIFT 0 /* GP6_FN - [5:0] */ +#define WM5100_GP6_FN_WIDTH 6 /* GP6_FN - [5:0] */ + +/* + * R3107 (0xC23) - Misc Pad Ctrl 1 + */ +#define WM5100_LDO1ENA_PD 0x8000 /* LDO1ENA_PD */ +#define WM5100_LDO1ENA_PD_MASK 0x8000 /* LDO1ENA_PD */ +#define WM5100_LDO1ENA_PD_SHIFT 15 /* LDO1ENA_PD */ +#define WM5100_LDO1ENA_PD_WIDTH 1 /* LDO1ENA_PD */ +#define WM5100_MCLK2_PD 0x2000 /* MCLK2_PD */ +#define WM5100_MCLK2_PD_MASK 0x2000 /* MCLK2_PD */ +#define WM5100_MCLK2_PD_SHIFT 13 /* MCLK2_PD */ +#define WM5100_MCLK2_PD_WIDTH 1 /* MCLK2_PD */ +#define WM5100_MCLK1_PD 0x1000 /* MCLK1_PD */ +#define WM5100_MCLK1_PD_MASK 0x1000 /* MCLK1_PD */ +#define WM5100_MCLK1_PD_SHIFT 12 /* MCLK1_PD */ +#define WM5100_MCLK1_PD_WIDTH 1 /* MCLK1_PD */ +#define WM5100_RESET_PU 0x0002 /* RESET_PU */ +#define WM5100_RESET_PU_MASK 0x0002 /* RESET_PU */ +#define WM5100_RESET_PU_SHIFT 1 /* RESET_PU */ +#define WM5100_RESET_PU_WIDTH 1 /* RESET_PU */ +#define WM5100_ADDR_PD 0x0001 /* ADDR_PD */ +#define WM5100_ADDR_PD_MASK 0x0001 /* ADDR_PD */ +#define WM5100_ADDR_PD_SHIFT 0 /* ADDR_PD */ +#define WM5100_ADDR_PD_WIDTH 1 /* ADDR_PD */ + +/* + * R3108 (0xC24) - Misc Pad Ctrl 2 + */ +#define WM5100_DMICDAT4_PD 0x0008 /* DMICDAT4_PD */ +#define WM5100_DMICDAT4_PD_MASK 0x0008 /* DMICDAT4_PD */ +#define WM5100_DMICDAT4_PD_SHIFT 3 /* DMICDAT4_PD */ +#define WM5100_DMICDAT4_PD_WIDTH 1 /* DMICDAT4_PD */ +#define WM5100_DMICDAT3_PD 0x0004 /* DMICDAT3_PD */ +#define WM5100_DMICDAT3_PD_MASK 0x0004 /* DMICDAT3_PD */ +#define WM5100_DMICDAT3_PD_SHIFT 2 /* DMICDAT3_PD */ +#define WM5100_DMICDAT3_PD_WIDTH 1 /* DMICDAT3_PD */ +#define WM5100_DMICDAT2_PD 0x0002 /* DMICDAT2_PD */ +#define WM5100_DMICDAT2_PD_MASK 0x0002 /* DMICDAT2_PD */ +#define WM5100_DMICDAT2_PD_SHIFT 1 /* DMICDAT2_PD */ +#define WM5100_DMICDAT2_PD_WIDTH 1 /* DMICDAT2_PD */ +#define WM5100_DMICDAT1_PD 0x0001 /* DMICDAT1_PD */ +#define WM5100_DMICDAT1_PD_MASK 0x0001 /* DMICDAT1_PD */ +#define WM5100_DMICDAT1_PD_SHIFT 0 /* DMICDAT1_PD */ +#define WM5100_DMICDAT1_PD_WIDTH 1 /* DMICDAT1_PD */ + +/* + * R3109 (0xC25) - Misc Pad Ctrl 3 + */ +#define WM5100_AIF1RXLRCLK_PU 0x0020 /* AIF1RXLRCLK_PU */ +#define WM5100_AIF1RXLRCLK_PU_MASK 0x0020 /* AIF1RXLRCLK_PU */ +#define WM5100_AIF1RXLRCLK_PU_SHIFT 5 /* AIF1RXLRCLK_PU */ +#define WM5100_AIF1RXLRCLK_PU_WIDTH 1 /* AIF1RXLRCLK_PU */ +#define WM5100_AIF1RXLRCLK_PD 0x0010 /* AIF1RXLRCLK_PD */ +#define WM5100_AIF1RXLRCLK_PD_MASK 0x0010 /* AIF1RXLRCLK_PD */ +#define WM5100_AIF1RXLRCLK_PD_SHIFT 4 /* AIF1RXLRCLK_PD */ +#define WM5100_AIF1RXLRCLK_PD_WIDTH 1 /* AIF1RXLRCLK_PD */ +#define WM5100_AIF1BCLK_PU 0x0008 /* AIF1BCLK_PU */ +#define WM5100_AIF1BCLK_PU_MASK 0x0008 /* AIF1BCLK_PU */ +#define WM5100_AIF1BCLK_PU_SHIFT 3 /* AIF1BCLK_PU */ +#define WM5100_AIF1BCLK_PU_WIDTH 1 /* AIF1BCLK_PU */ +#define WM5100_AIF1BCLK_PD 0x0004 /* AIF1BCLK_PD */ +#define WM5100_AIF1BCLK_PD_MASK 0x0004 /* AIF1BCLK_PD */ +#define WM5100_AIF1BCLK_PD_SHIFT 2 /* AIF1BCLK_PD */ +#define WM5100_AIF1BCLK_PD_WIDTH 1 /* AIF1BCLK_PD */ +#define WM5100_AIF1RXDAT_PU 0x0002 /* AIF1RXDAT_PU */ +#define WM5100_AIF1RXDAT_PU_MASK 0x0002 /* AIF1RXDAT_PU */ +#define WM5100_AIF1RXDAT_PU_SHIFT 1 /* AIF1RXDAT_PU */ +#define WM5100_AIF1RXDAT_PU_WIDTH 1 /* AIF1RXDAT_PU */ +#define WM5100_AIF1RXDAT_PD 0x0001 /* AIF1RXDAT_PD */ +#define WM5100_AIF1RXDAT_PD_MASK 0x0001 /* AIF1RXDAT_PD */ +#define WM5100_AIF1RXDAT_PD_SHIFT 0 /* AIF1RXDAT_PD */ +#define WM5100_AIF1RXDAT_PD_WIDTH 1 /* AIF1RXDAT_PD */ + +/* + * R3110 (0xC26) - Misc Pad Ctrl 4 + */ +#define WM5100_AIF2RXLRCLK_PU 0x0020 /* AIF2RXLRCLK_PU */ +#define WM5100_AIF2RXLRCLK_PU_MASK 0x0020 /* AIF2RXLRCLK_PU */ +#define WM5100_AIF2RXLRCLK_PU_SHIFT 5 /* AIF2RXLRCLK_PU */ +#define WM5100_AIF2RXLRCLK_PU_WIDTH 1 /* AIF2RXLRCLK_PU */ +#define WM5100_AIF2RXLRCLK_PD 0x0010 /* AIF2RXLRCLK_PD */ +#define WM5100_AIF2RXLRCLK_PD_MASK 0x0010 /* AIF2RXLRCLK_PD */ +#define WM5100_AIF2RXLRCLK_PD_SHIFT 4 /* AIF2RXLRCLK_PD */ +#define WM5100_AIF2RXLRCLK_PD_WIDTH 1 /* AIF2RXLRCLK_PD */ +#define WM5100_AIF2BCLK_PU 0x0008 /* AIF2BCLK_PU */ +#define WM5100_AIF2BCLK_PU_MASK 0x0008 /* AIF2BCLK_PU */ +#define WM5100_AIF2BCLK_PU_SHIFT 3 /* AIF2BCLK_PU */ +#define WM5100_AIF2BCLK_PU_WIDTH 1 /* AIF2BCLK_PU */ +#define WM5100_AIF2BCLK_PD 0x0004 /* AIF2BCLK_PD */ +#define WM5100_AIF2BCLK_PD_MASK 0x0004 /* AIF2BCLK_PD */ +#define WM5100_AIF2BCLK_PD_SHIFT 2 /* AIF2BCLK_PD */ +#define WM5100_AIF2BCLK_PD_WIDTH 1 /* AIF2BCLK_PD */ +#define WM5100_AIF2RXDAT_PU 0x0002 /* AIF2RXDAT_PU */ +#define WM5100_AIF2RXDAT_PU_MASK 0x0002 /* AIF2RXDAT_PU */ +#define WM5100_AIF2RXDAT_PU_SHIFT 1 /* AIF2RXDAT_PU */ +#define WM5100_AIF2RXDAT_PU_WIDTH 1 /* AIF2RXDAT_PU */ +#define WM5100_AIF2RXDAT_PD 0x0001 /* AIF2RXDAT_PD */ +#define WM5100_AIF2RXDAT_PD_MASK 0x0001 /* AIF2RXDAT_PD */ +#define WM5100_AIF2RXDAT_PD_SHIFT 0 /* AIF2RXDAT_PD */ +#define WM5100_AIF2RXDAT_PD_WIDTH 1 /* AIF2RXDAT_PD */ + +/* + * R3111 (0xC27) - Misc Pad Ctrl 5 + */ +#define WM5100_AIF3RXLRCLK_PU 0x0020 /* AIF3RXLRCLK_PU */ +#define WM5100_AIF3RXLRCLK_PU_MASK 0x0020 /* AIF3RXLRCLK_PU */ +#define WM5100_AIF3RXLRCLK_PU_SHIFT 5 /* AIF3RXLRCLK_PU */ +#define WM5100_AIF3RXLRCLK_PU_WIDTH 1 /* AIF3RXLRCLK_PU */ +#define WM5100_AIF3RXLRCLK_PD 0x0010 /* AIF3RXLRCLK_PD */ +#define WM5100_AIF3RXLRCLK_PD_MASK 0x0010 /* AIF3RXLRCLK_PD */ +#define WM5100_AIF3RXLRCLK_PD_SHIFT 4 /* AIF3RXLRCLK_PD */ +#define WM5100_AIF3RXLRCLK_PD_WIDTH 1 /* AIF3RXLRCLK_PD */ +#define WM5100_AIF3BCLK_PU 0x0008 /* AIF3BCLK_PU */ +#define WM5100_AIF3BCLK_PU_MASK 0x0008 /* AIF3BCLK_PU */ +#define WM5100_AIF3BCLK_PU_SHIFT 3 /* AIF3BCLK_PU */ +#define WM5100_AIF3BCLK_PU_WIDTH 1 /* AIF3BCLK_PU */ +#define WM5100_AIF3BCLK_PD 0x0004 /* AIF3BCLK_PD */ +#define WM5100_AIF3BCLK_PD_MASK 0x0004 /* AIF3BCLK_PD */ +#define WM5100_AIF3BCLK_PD_SHIFT 2 /* AIF3BCLK_PD */ +#define WM5100_AIF3BCLK_PD_WIDTH 1 /* AIF3BCLK_PD */ +#define WM5100_AIF3RXDAT_PU 0x0002 /* AIF3RXDAT_PU */ +#define WM5100_AIF3RXDAT_PU_MASK 0x0002 /* AIF3RXDAT_PU */ +#define WM5100_AIF3RXDAT_PU_SHIFT 1 /* AIF3RXDAT_PU */ +#define WM5100_AIF3RXDAT_PU_WIDTH 1 /* AIF3RXDAT_PU */ +#define WM5100_AIF3RXDAT_PD 0x0001 /* AIF3RXDAT_PD */ +#define WM5100_AIF3RXDAT_PD_MASK 0x0001 /* AIF3RXDAT_PD */ +#define WM5100_AIF3RXDAT_PD_SHIFT 0 /* AIF3RXDAT_PD */ +#define WM5100_AIF3RXDAT_PD_WIDTH 1 /* AIF3RXDAT_PD */ + +/* + * R3112 (0xC28) - Misc GPIO 1 + */ +#define WM5100_OPCLK_SEL_MASK 0x0003 /* OPCLK_SEL - [1:0] */ +#define WM5100_OPCLK_SEL_SHIFT 0 /* OPCLK_SEL - [1:0] */ +#define WM5100_OPCLK_SEL_WIDTH 2 /* OPCLK_SEL - [1:0] */ + +/* + * R3328 (0xD00) - Interrupt Status 1 + */ +#define WM5100_GP6_EINT 0x0020 /* GP6_EINT */ +#define WM5100_GP6_EINT_MASK 0x0020 /* GP6_EINT */ +#define WM5100_GP6_EINT_SHIFT 5 /* GP6_EINT */ +#define WM5100_GP6_EINT_WIDTH 1 /* GP6_EINT */ +#define WM5100_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM5100_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM5100_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM5100_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM5100_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM5100_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM5100_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM5100_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM5100_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM5100_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM5100_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM5100_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM5100_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM5100_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM5100_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM5100_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM5100_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM5100_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM5100_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM5100_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R3329 (0xD01) - Interrupt Status 2 + */ +#define WM5100_DSP_IRQ6_EINT 0x0020 /* DSP_IRQ6_EINT */ +#define WM5100_DSP_IRQ6_EINT_MASK 0x0020 /* DSP_IRQ6_EINT */ +#define WM5100_DSP_IRQ6_EINT_SHIFT 5 /* DSP_IRQ6_EINT */ +#define WM5100_DSP_IRQ6_EINT_WIDTH 1 /* DSP_IRQ6_EINT */ +#define WM5100_DSP_IRQ5_EINT 0x0010 /* DSP_IRQ5_EINT */ +#define WM5100_DSP_IRQ5_EINT_MASK 0x0010 /* DSP_IRQ5_EINT */ +#define WM5100_DSP_IRQ5_EINT_SHIFT 4 /* DSP_IRQ5_EINT */ +#define WM5100_DSP_IRQ5_EINT_WIDTH 1 /* DSP_IRQ5_EINT */ +#define WM5100_DSP_IRQ4_EINT 0x0008 /* DSP_IRQ4_EINT */ +#define WM5100_DSP_IRQ4_EINT_MASK 0x0008 /* DSP_IRQ4_EINT */ +#define WM5100_DSP_IRQ4_EINT_SHIFT 3 /* DSP_IRQ4_EINT */ +#define WM5100_DSP_IRQ4_EINT_WIDTH 1 /* DSP_IRQ4_EINT */ +#define WM5100_DSP_IRQ3_EINT 0x0004 /* DSP_IRQ3_EINT */ +#define WM5100_DSP_IRQ3_EINT_MASK 0x0004 /* DSP_IRQ3_EINT */ +#define WM5100_DSP_IRQ3_EINT_SHIFT 2 /* DSP_IRQ3_EINT */ +#define WM5100_DSP_IRQ3_EINT_WIDTH 1 /* DSP_IRQ3_EINT */ +#define WM5100_DSP_IRQ2_EINT 0x0002 /* DSP_IRQ2_EINT */ +#define WM5100_DSP_IRQ2_EINT_MASK 0x0002 /* DSP_IRQ2_EINT */ +#define WM5100_DSP_IRQ2_EINT_SHIFT 1 /* DSP_IRQ2_EINT */ +#define WM5100_DSP_IRQ2_EINT_WIDTH 1 /* DSP_IRQ2_EINT */ +#define WM5100_DSP_IRQ1_EINT 0x0001 /* DSP_IRQ1_EINT */ +#define WM5100_DSP_IRQ1_EINT_MASK 0x0001 /* DSP_IRQ1_EINT */ +#define WM5100_DSP_IRQ1_EINT_SHIFT 0 /* DSP_IRQ1_EINT */ +#define WM5100_DSP_IRQ1_EINT_WIDTH 1 /* DSP_IRQ1_EINT */ + +/* + * R3330 (0xD02) - Interrupt Status 3 + */ +#define WM5100_SPK_SHUTDOWN_WARN_EINT 0x8000 /* SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_SPK_SHUTDOWN_WARN_EINT_MASK 0x8000 /* SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_SPK_SHUTDOWN_WARN_EINT_SHIFT 15 /* SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_SPK_SHUTDOWN_WARN_EINT_WIDTH 1 /* SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_SPK_SHUTDOWN_EINT 0x4000 /* SPK_SHUTDOWN_EINT */ +#define WM5100_SPK_SHUTDOWN_EINT_MASK 0x4000 /* SPK_SHUTDOWN_EINT */ +#define WM5100_SPK_SHUTDOWN_EINT_SHIFT 14 /* SPK_SHUTDOWN_EINT */ +#define WM5100_SPK_SHUTDOWN_EINT_WIDTH 1 /* SPK_SHUTDOWN_EINT */ +#define WM5100_HPDET_EINT 0x2000 /* HPDET_EINT */ +#define WM5100_HPDET_EINT_MASK 0x2000 /* HPDET_EINT */ +#define WM5100_HPDET_EINT_SHIFT 13 /* HPDET_EINT */ +#define WM5100_HPDET_EINT_WIDTH 1 /* HPDET_EINT */ +#define WM5100_ACCDET_EINT 0x1000 /* ACCDET_EINT */ +#define WM5100_ACCDET_EINT_MASK 0x1000 /* ACCDET_EINT */ +#define WM5100_ACCDET_EINT_SHIFT 12 /* ACCDET_EINT */ +#define WM5100_ACCDET_EINT_WIDTH 1 /* ACCDET_EINT */ +#define WM5100_DRC_SIG_DET_EINT 0x0200 /* DRC_SIG_DET_EINT */ +#define WM5100_DRC_SIG_DET_EINT_MASK 0x0200 /* DRC_SIG_DET_EINT */ +#define WM5100_DRC_SIG_DET_EINT_SHIFT 9 /* DRC_SIG_DET_EINT */ +#define WM5100_DRC_SIG_DET_EINT_WIDTH 1 /* DRC_SIG_DET_EINT */ +#define WM5100_ASRC2_LOCK_EINT 0x0100 /* ASRC2_LOCK_EINT */ +#define WM5100_ASRC2_LOCK_EINT_MASK 0x0100 /* ASRC2_LOCK_EINT */ +#define WM5100_ASRC2_LOCK_EINT_SHIFT 8 /* ASRC2_LOCK_EINT */ +#define WM5100_ASRC2_LOCK_EINT_WIDTH 1 /* ASRC2_LOCK_EINT */ +#define WM5100_ASRC1_LOCK_EINT 0x0080 /* ASRC1_LOCK_EINT */ +#define WM5100_ASRC1_LOCK_EINT_MASK 0x0080 /* ASRC1_LOCK_EINT */ +#define WM5100_ASRC1_LOCK_EINT_SHIFT 7 /* ASRC1_LOCK_EINT */ +#define WM5100_ASRC1_LOCK_EINT_WIDTH 1 /* ASRC1_LOCK_EINT */ +#define WM5100_FLL2_LOCK_EINT 0x0008 /* FLL2_LOCK_EINT */ +#define WM5100_FLL2_LOCK_EINT_MASK 0x0008 /* FLL2_LOCK_EINT */ +#define WM5100_FLL2_LOCK_EINT_SHIFT 3 /* FLL2_LOCK_EINT */ +#define WM5100_FLL2_LOCK_EINT_WIDTH 1 /* FLL2_LOCK_EINT */ +#define WM5100_FLL1_LOCK_EINT 0x0004 /* FLL1_LOCK_EINT */ +#define WM5100_FLL1_LOCK_EINT_MASK 0x0004 /* FLL1_LOCK_EINT */ +#define WM5100_FLL1_LOCK_EINT_SHIFT 2 /* FLL1_LOCK_EINT */ +#define WM5100_FLL1_LOCK_EINT_WIDTH 1 /* FLL1_LOCK_EINT */ +#define WM5100_CLKGEN_ERR_EINT 0x0002 /* CLKGEN_ERR_EINT */ +#define WM5100_CLKGEN_ERR_EINT_MASK 0x0002 /* CLKGEN_ERR_EINT */ +#define WM5100_CLKGEN_ERR_EINT_SHIFT 1 /* CLKGEN_ERR_EINT */ +#define WM5100_CLKGEN_ERR_EINT_WIDTH 1 /* CLKGEN_ERR_EINT */ +#define WM5100_CLKGEN_ERR_ASYNC_EINT 0x0001 /* CLKGEN_ERR_ASYNC_EINT */ +#define WM5100_CLKGEN_ERR_ASYNC_EINT_MASK 0x0001 /* CLKGEN_ERR_ASYNC_EINT */ +#define WM5100_CLKGEN_ERR_ASYNC_EINT_SHIFT 0 /* CLKGEN_ERR_ASYNC_EINT */ +#define WM5100_CLKGEN_ERR_ASYNC_EINT_WIDTH 1 /* CLKGEN_ERR_ASYNC_EINT */ + +/* + * R3331 (0xD03) - Interrupt Status 4 + */ +#define WM5100_AIF3_ERR_EINT 0x2000 /* AIF3_ERR_EINT */ +#define WM5100_AIF3_ERR_EINT_MASK 0x2000 /* AIF3_ERR_EINT */ +#define WM5100_AIF3_ERR_EINT_SHIFT 13 /* AIF3_ERR_EINT */ +#define WM5100_AIF3_ERR_EINT_WIDTH 1 /* AIF3_ERR_EINT */ +#define WM5100_AIF2_ERR_EINT 0x1000 /* AIF2_ERR_EINT */ +#define WM5100_AIF2_ERR_EINT_MASK 0x1000 /* AIF2_ERR_EINT */ +#define WM5100_AIF2_ERR_EINT_SHIFT 12 /* AIF2_ERR_EINT */ +#define WM5100_AIF2_ERR_EINT_WIDTH 1 /* AIF2_ERR_EINT */ +#define WM5100_AIF1_ERR_EINT 0x0800 /* AIF1_ERR_EINT */ +#define WM5100_AIF1_ERR_EINT_MASK 0x0800 /* AIF1_ERR_EINT */ +#define WM5100_AIF1_ERR_EINT_SHIFT 11 /* AIF1_ERR_EINT */ +#define WM5100_AIF1_ERR_EINT_WIDTH 1 /* AIF1_ERR_EINT */ +#define WM5100_CTRLIF_ERR_EINT 0x0400 /* CTRLIF_ERR_EINT */ +#define WM5100_CTRLIF_ERR_EINT_MASK 0x0400 /* CTRLIF_ERR_EINT */ +#define WM5100_CTRLIF_ERR_EINT_SHIFT 10 /* CTRLIF_ERR_EINT */ +#define WM5100_CTRLIF_ERR_EINT_WIDTH 1 /* CTRLIF_ERR_EINT */ +#define WM5100_ISRC2_UNDERCLOCKED_EINT 0x0200 /* ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_ISRC2_UNDERCLOCKED_EINT_MASK 0x0200 /* ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_ISRC2_UNDERCLOCKED_EINT_SHIFT 9 /* ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_ISRC2_UNDERCLOCKED_EINT_WIDTH 1 /* ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_ISRC1_UNDERCLOCKED_EINT 0x0100 /* ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_ISRC1_UNDERCLOCKED_EINT_MASK 0x0100 /* ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_ISRC1_UNDERCLOCKED_EINT_SHIFT 8 /* ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_ISRC1_UNDERCLOCKED_EINT_WIDTH 1 /* ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_FX_UNDERCLOCKED_EINT 0x0080 /* FX_UNDERCLOCKED_EINT */ +#define WM5100_FX_UNDERCLOCKED_EINT_MASK 0x0080 /* FX_UNDERCLOCKED_EINT */ +#define WM5100_FX_UNDERCLOCKED_EINT_SHIFT 7 /* FX_UNDERCLOCKED_EINT */ +#define WM5100_FX_UNDERCLOCKED_EINT_WIDTH 1 /* FX_UNDERCLOCKED_EINT */ +#define WM5100_AIF3_UNDERCLOCKED_EINT 0x0040 /* AIF3_UNDERCLOCKED_EINT */ +#define WM5100_AIF3_UNDERCLOCKED_EINT_MASK 0x0040 /* AIF3_UNDERCLOCKED_EINT */ +#define WM5100_AIF3_UNDERCLOCKED_EINT_SHIFT 6 /* AIF3_UNDERCLOCKED_EINT */ +#define WM5100_AIF3_UNDERCLOCKED_EINT_WIDTH 1 /* AIF3_UNDERCLOCKED_EINT */ +#define WM5100_AIF2_UNDERCLOCKED_EINT 0x0020 /* AIF2_UNDERCLOCKED_EINT */ +#define WM5100_AIF2_UNDERCLOCKED_EINT_MASK 0x0020 /* AIF2_UNDERCLOCKED_EINT */ +#define WM5100_AIF2_UNDERCLOCKED_EINT_SHIFT 5 /* AIF2_UNDERCLOCKED_EINT */ +#define WM5100_AIF2_UNDERCLOCKED_EINT_WIDTH 1 /* AIF2_UNDERCLOCKED_EINT */ +#define WM5100_AIF1_UNDERCLOCKED_EINT 0x0010 /* AIF1_UNDERCLOCKED_EINT */ +#define WM5100_AIF1_UNDERCLOCKED_EINT_MASK 0x0010 /* AIF1_UNDERCLOCKED_EINT */ +#define WM5100_AIF1_UNDERCLOCKED_EINT_SHIFT 4 /* AIF1_UNDERCLOCKED_EINT */ +#define WM5100_AIF1_UNDERCLOCKED_EINT_WIDTH 1 /* AIF1_UNDERCLOCKED_EINT */ +#define WM5100_ASRC_UNDERCLOCKED_EINT 0x0008 /* ASRC_UNDERCLOCKED_EINT */ +#define WM5100_ASRC_UNDERCLOCKED_EINT_MASK 0x0008 /* ASRC_UNDERCLOCKED_EINT */ +#define WM5100_ASRC_UNDERCLOCKED_EINT_SHIFT 3 /* ASRC_UNDERCLOCKED_EINT */ +#define WM5100_ASRC_UNDERCLOCKED_EINT_WIDTH 1 /* ASRC_UNDERCLOCKED_EINT */ +#define WM5100_DAC_UNDERCLOCKED_EINT 0x0004 /* DAC_UNDERCLOCKED_EINT */ +#define WM5100_DAC_UNDERCLOCKED_EINT_MASK 0x0004 /* DAC_UNDERCLOCKED_EINT */ +#define WM5100_DAC_UNDERCLOCKED_EINT_SHIFT 2 /* DAC_UNDERCLOCKED_EINT */ +#define WM5100_DAC_UNDERCLOCKED_EINT_WIDTH 1 /* DAC_UNDERCLOCKED_EINT */ +#define WM5100_ADC_UNDERCLOCKED_EINT 0x0002 /* ADC_UNDERCLOCKED_EINT */ +#define WM5100_ADC_UNDERCLOCKED_EINT_MASK 0x0002 /* ADC_UNDERCLOCKED_EINT */ +#define WM5100_ADC_UNDERCLOCKED_EINT_SHIFT 1 /* ADC_UNDERCLOCKED_EINT */ +#define WM5100_ADC_UNDERCLOCKED_EINT_WIDTH 1 /* ADC_UNDERCLOCKED_EINT */ +#define WM5100_MIXER_UNDERCLOCKED_EINT 0x0001 /* MIXER_UNDERCLOCKED_EINT */ +#define WM5100_MIXER_UNDERCLOCKED_EINT_MASK 0x0001 /* MIXER_UNDERCLOCKED_EINT */ +#define WM5100_MIXER_UNDERCLOCKED_EINT_SHIFT 0 /* MIXER_UNDERCLOCKED_EINT */ +#define WM5100_MIXER_UNDERCLOCKED_EINT_WIDTH 1 /* MIXER_UNDERCLOCKED_EINT */ + +/* + * R3332 (0xD04) - Interrupt Raw Status 2 + */ +#define WM5100_DSP_IRQ6_STS 0x0020 /* DSP_IRQ6_STS */ +#define WM5100_DSP_IRQ6_STS_MASK 0x0020 /* DSP_IRQ6_STS */ +#define WM5100_DSP_IRQ6_STS_SHIFT 5 /* DSP_IRQ6_STS */ +#define WM5100_DSP_IRQ6_STS_WIDTH 1 /* DSP_IRQ6_STS */ +#define WM5100_DSP_IRQ5_STS 0x0010 /* DSP_IRQ5_STS */ +#define WM5100_DSP_IRQ5_STS_MASK 0x0010 /* DSP_IRQ5_STS */ +#define WM5100_DSP_IRQ5_STS_SHIFT 4 /* DSP_IRQ5_STS */ +#define WM5100_DSP_IRQ5_STS_WIDTH 1 /* DSP_IRQ5_STS */ +#define WM5100_DSP_IRQ4_STS 0x0008 /* DSP_IRQ4_STS */ +#define WM5100_DSP_IRQ4_STS_MASK 0x0008 /* DSP_IRQ4_STS */ +#define WM5100_DSP_IRQ4_STS_SHIFT 3 /* DSP_IRQ4_STS */ +#define WM5100_DSP_IRQ4_STS_WIDTH 1 /* DSP_IRQ4_STS */ +#define WM5100_DSP_IRQ3_STS 0x0004 /* DSP_IRQ3_STS */ +#define WM5100_DSP_IRQ3_STS_MASK 0x0004 /* DSP_IRQ3_STS */ +#define WM5100_DSP_IRQ3_STS_SHIFT 2 /* DSP_IRQ3_STS */ +#define WM5100_DSP_IRQ3_STS_WIDTH 1 /* DSP_IRQ3_STS */ +#define WM5100_DSP_IRQ2_STS 0x0002 /* DSP_IRQ2_STS */ +#define WM5100_DSP_IRQ2_STS_MASK 0x0002 /* DSP_IRQ2_STS */ +#define WM5100_DSP_IRQ2_STS_SHIFT 1 /* DSP_IRQ2_STS */ +#define WM5100_DSP_IRQ2_STS_WIDTH 1 /* DSP_IRQ2_STS */ +#define WM5100_DSP_IRQ1_STS 0x0001 /* DSP_IRQ1_STS */ +#define WM5100_DSP_IRQ1_STS_MASK 0x0001 /* DSP_IRQ1_STS */ +#define WM5100_DSP_IRQ1_STS_SHIFT 0 /* DSP_IRQ1_STS */ +#define WM5100_DSP_IRQ1_STS_WIDTH 1 /* DSP_IRQ1_STS */ + +/* + * R3333 (0xD05) - Interrupt Raw Status 3 + */ +#define WM5100_SPK_SHUTDOWN_WARN_STS 0x8000 /* SPK_SHUTDOWN_WARN_STS */ +#define WM5100_SPK_SHUTDOWN_WARN_STS_MASK 0x8000 /* SPK_SHUTDOWN_WARN_STS */ +#define WM5100_SPK_SHUTDOWN_WARN_STS_SHIFT 15 /* SPK_SHUTDOWN_WARN_STS */ +#define WM5100_SPK_SHUTDOWN_WARN_STS_WIDTH 1 /* SPK_SHUTDOWN_WARN_STS */ +#define WM5100_SPK_SHUTDOWN_STS 0x4000 /* SPK_SHUTDOWN_STS */ +#define WM5100_SPK_SHUTDOWN_STS_MASK 0x4000 /* SPK_SHUTDOWN_STS */ +#define WM5100_SPK_SHUTDOWN_STS_SHIFT 14 /* SPK_SHUTDOWN_STS */ +#define WM5100_SPK_SHUTDOWN_STS_WIDTH 1 /* SPK_SHUTDOWN_STS */ +#define WM5100_HPDET_STS 0x2000 /* HPDET_STS */ +#define WM5100_HPDET_STS_MASK 0x2000 /* HPDET_STS */ +#define WM5100_HPDET_STS_SHIFT 13 /* HPDET_STS */ +#define WM5100_HPDET_STS_WIDTH 1 /* HPDET_STS */ +#define WM5100_DRC_SID_DET_STS 0x0200 /* DRC_SID_DET_STS */ +#define WM5100_DRC_SID_DET_STS_MASK 0x0200 /* DRC_SID_DET_STS */ +#define WM5100_DRC_SID_DET_STS_SHIFT 9 /* DRC_SID_DET_STS */ +#define WM5100_DRC_SID_DET_STS_WIDTH 1 /* DRC_SID_DET_STS */ +#define WM5100_ASRC2_LOCK_STS 0x0100 /* ASRC2_LOCK_STS */ +#define WM5100_ASRC2_LOCK_STS_MASK 0x0100 /* ASRC2_LOCK_STS */ +#define WM5100_ASRC2_LOCK_STS_SHIFT 8 /* ASRC2_LOCK_STS */ +#define WM5100_ASRC2_LOCK_STS_WIDTH 1 /* ASRC2_LOCK_STS */ +#define WM5100_ASRC1_LOCK_STS 0x0080 /* ASRC1_LOCK_STS */ +#define WM5100_ASRC1_LOCK_STS_MASK 0x0080 /* ASRC1_LOCK_STS */ +#define WM5100_ASRC1_LOCK_STS_SHIFT 7 /* ASRC1_LOCK_STS */ +#define WM5100_ASRC1_LOCK_STS_WIDTH 1 /* ASRC1_LOCK_STS */ +#define WM5100_FLL2_LOCK_STS 0x0008 /* FLL2_LOCK_STS */ +#define WM5100_FLL2_LOCK_STS_MASK 0x0008 /* FLL2_LOCK_STS */ +#define WM5100_FLL2_LOCK_STS_SHIFT 3 /* FLL2_LOCK_STS */ +#define WM5100_FLL2_LOCK_STS_WIDTH 1 /* FLL2_LOCK_STS */ +#define WM5100_FLL1_LOCK_STS 0x0004 /* FLL1_LOCK_STS */ +#define WM5100_FLL1_LOCK_STS_MASK 0x0004 /* FLL1_LOCK_STS */ +#define WM5100_FLL1_LOCK_STS_SHIFT 2 /* FLL1_LOCK_STS */ +#define WM5100_FLL1_LOCK_STS_WIDTH 1 /* FLL1_LOCK_STS */ +#define WM5100_CLKGEN_ERR_STS 0x0002 /* CLKGEN_ERR_STS */ +#define WM5100_CLKGEN_ERR_STS_MASK 0x0002 /* CLKGEN_ERR_STS */ +#define WM5100_CLKGEN_ERR_STS_SHIFT 1 /* CLKGEN_ERR_STS */ +#define WM5100_CLKGEN_ERR_STS_WIDTH 1 /* CLKGEN_ERR_STS */ +#define WM5100_CLKGEN_ERR_ASYNC_STS 0x0001 /* CLKGEN_ERR_ASYNC_STS */ +#define WM5100_CLKGEN_ERR_ASYNC_STS_MASK 0x0001 /* CLKGEN_ERR_ASYNC_STS */ +#define WM5100_CLKGEN_ERR_ASYNC_STS_SHIFT 0 /* CLKGEN_ERR_ASYNC_STS */ +#define WM5100_CLKGEN_ERR_ASYNC_STS_WIDTH 1 /* CLKGEN_ERR_ASYNC_STS */ + +/* + * R3334 (0xD06) - Interrupt Raw Status 4 + */ +#define WM5100_AIF3_ERR_STS 0x2000 /* AIF3_ERR_STS */ +#define WM5100_AIF3_ERR_STS_MASK 0x2000 /* AIF3_ERR_STS */ +#define WM5100_AIF3_ERR_STS_SHIFT 13 /* AIF3_ERR_STS */ +#define WM5100_AIF3_ERR_STS_WIDTH 1 /* AIF3_ERR_STS */ +#define WM5100_AIF2_ERR_STS 0x1000 /* AIF2_ERR_STS */ +#define WM5100_AIF2_ERR_STS_MASK 0x1000 /* AIF2_ERR_STS */ +#define WM5100_AIF2_ERR_STS_SHIFT 12 /* AIF2_ERR_STS */ +#define WM5100_AIF2_ERR_STS_WIDTH 1 /* AIF2_ERR_STS */ +#define WM5100_AIF1_ERR_STS 0x0800 /* AIF1_ERR_STS */ +#define WM5100_AIF1_ERR_STS_MASK 0x0800 /* AIF1_ERR_STS */ +#define WM5100_AIF1_ERR_STS_SHIFT 11 /* AIF1_ERR_STS */ +#define WM5100_AIF1_ERR_STS_WIDTH 1 /* AIF1_ERR_STS */ +#define WM5100_CTRLIF_ERR_STS 0x0400 /* CTRLIF_ERR_STS */ +#define WM5100_CTRLIF_ERR_STS_MASK 0x0400 /* CTRLIF_ERR_STS */ +#define WM5100_CTRLIF_ERR_STS_SHIFT 10 /* CTRLIF_ERR_STS */ +#define WM5100_CTRLIF_ERR_STS_WIDTH 1 /* CTRLIF_ERR_STS */ +#define WM5100_ISRC2_UNDERCLOCKED_STS 0x0200 /* ISRC2_UNDERCLOCKED_STS */ +#define WM5100_ISRC2_UNDERCLOCKED_STS_MASK 0x0200 /* ISRC2_UNDERCLOCKED_STS */ +#define WM5100_ISRC2_UNDERCLOCKED_STS_SHIFT 9 /* ISRC2_UNDERCLOCKED_STS */ +#define WM5100_ISRC2_UNDERCLOCKED_STS_WIDTH 1 /* ISRC2_UNDERCLOCKED_STS */ +#define WM5100_ISRC1_UNDERCLOCKED_STS 0x0100 /* ISRC1_UNDERCLOCKED_STS */ +#define WM5100_ISRC1_UNDERCLOCKED_STS_MASK 0x0100 /* ISRC1_UNDERCLOCKED_STS */ +#define WM5100_ISRC1_UNDERCLOCKED_STS_SHIFT 8 /* ISRC1_UNDERCLOCKED_STS */ +#define WM5100_ISRC1_UNDERCLOCKED_STS_WIDTH 1 /* ISRC1_UNDERCLOCKED_STS */ +#define WM5100_FX_UNDERCLOCKED_STS 0x0080 /* FX_UNDERCLOCKED_STS */ +#define WM5100_FX_UNDERCLOCKED_STS_MASK 0x0080 /* FX_UNDERCLOCKED_STS */ +#define WM5100_FX_UNDERCLOCKED_STS_SHIFT 7 /* FX_UNDERCLOCKED_STS */ +#define WM5100_FX_UNDERCLOCKED_STS_WIDTH 1 /* FX_UNDERCLOCKED_STS */ +#define WM5100_AIF3_UNDERCLOCKED_STS 0x0040 /* AIF3_UNDERCLOCKED_STS */ +#define WM5100_AIF3_UNDERCLOCKED_STS_MASK 0x0040 /* AIF3_UNDERCLOCKED_STS */ +#define WM5100_AIF3_UNDERCLOCKED_STS_SHIFT 6 /* AIF3_UNDERCLOCKED_STS */ +#define WM5100_AIF3_UNDERCLOCKED_STS_WIDTH 1 /* AIF3_UNDERCLOCKED_STS */ +#define WM5100_AIF2_UNDERCLOCKED_STS 0x0020 /* AIF2_UNDERCLOCKED_STS */ +#define WM5100_AIF2_UNDERCLOCKED_STS_MASK 0x0020 /* AIF2_UNDERCLOCKED_STS */ +#define WM5100_AIF2_UNDERCLOCKED_STS_SHIFT 5 /* AIF2_UNDERCLOCKED_STS */ +#define WM5100_AIF2_UNDERCLOCKED_STS_WIDTH 1 /* AIF2_UNDERCLOCKED_STS */ +#define WM5100_AIF1_UNDERCLOCKED_STS 0x0010 /* AIF1_UNDERCLOCKED_STS */ +#define WM5100_AIF1_UNDERCLOCKED_STS_MASK 0x0010 /* AIF1_UNDERCLOCKED_STS */ +#define WM5100_AIF1_UNDERCLOCKED_STS_SHIFT 4 /* AIF1_UNDERCLOCKED_STS */ +#define WM5100_AIF1_UNDERCLOCKED_STS_WIDTH 1 /* AIF1_UNDERCLOCKED_STS */ +#define WM5100_ASRC_UNDERCLOCKED_STS 0x0008 /* ASRC_UNDERCLOCKED_STS */ +#define WM5100_ASRC_UNDERCLOCKED_STS_MASK 0x0008 /* ASRC_UNDERCLOCKED_STS */ +#define WM5100_ASRC_UNDERCLOCKED_STS_SHIFT 3 /* ASRC_UNDERCLOCKED_STS */ +#define WM5100_ASRC_UNDERCLOCKED_STS_WIDTH 1 /* ASRC_UNDERCLOCKED_STS */ +#define WM5100_DAC_UNDERCLOCKED_STS 0x0004 /* DAC_UNDERCLOCKED_STS */ +#define WM5100_DAC_UNDERCLOCKED_STS_MASK 0x0004 /* DAC_UNDERCLOCKED_STS */ +#define WM5100_DAC_UNDERCLOCKED_STS_SHIFT 2 /* DAC_UNDERCLOCKED_STS */ +#define WM5100_DAC_UNDERCLOCKED_STS_WIDTH 1 /* DAC_UNDERCLOCKED_STS */ +#define WM5100_ADC_UNDERCLOCKED_STS 0x0002 /* ADC_UNDERCLOCKED_STS */ +#define WM5100_ADC_UNDERCLOCKED_STS_MASK 0x0002 /* ADC_UNDERCLOCKED_STS */ +#define WM5100_ADC_UNDERCLOCKED_STS_SHIFT 1 /* ADC_UNDERCLOCKED_STS */ +#define WM5100_ADC_UNDERCLOCKED_STS_WIDTH 1 /* ADC_UNDERCLOCKED_STS */ +#define WM5100_MIXER_UNDERCLOCKED_STS 0x0001 /* MIXER_UNDERCLOCKED_STS */ +#define WM5100_MIXER_UNDERCLOCKED_STS_MASK 0x0001 /* MIXER_UNDERCLOCKED_STS */ +#define WM5100_MIXER_UNDERCLOCKED_STS_SHIFT 0 /* MIXER_UNDERCLOCKED_STS */ +#define WM5100_MIXER_UNDERCLOCKED_STS_WIDTH 1 /* MIXER_UNDERCLOCKED_STS */ + +/* + * R3335 (0xD07) - Interrupt Status 1 Mask + */ +#define WM5100_IM_GP6_EINT 0x0020 /* IM_GP6_EINT */ +#define WM5100_IM_GP6_EINT_MASK 0x0020 /* IM_GP6_EINT */ +#define WM5100_IM_GP6_EINT_SHIFT 5 /* IM_GP6_EINT */ +#define WM5100_IM_GP6_EINT_WIDTH 1 /* IM_GP6_EINT */ +#define WM5100_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM5100_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM5100_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM5100_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM5100_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM5100_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM5100_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM5100_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM5100_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM5100_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM5100_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM5100_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM5100_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM5100_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM5100_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM5100_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM5100_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM5100_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM5100_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM5100_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R3336 (0xD08) - Interrupt Status 2 Mask + */ +#define WM5100_IM_DSP_IRQ6_EINT 0x0020 /* IM_DSP_IRQ6_EINT */ +#define WM5100_IM_DSP_IRQ6_EINT_MASK 0x0020 /* IM_DSP_IRQ6_EINT */ +#define WM5100_IM_DSP_IRQ6_EINT_SHIFT 5 /* IM_DSP_IRQ6_EINT */ +#define WM5100_IM_DSP_IRQ6_EINT_WIDTH 1 /* IM_DSP_IRQ6_EINT */ +#define WM5100_IM_DSP_IRQ5_EINT 0x0010 /* IM_DSP_IRQ5_EINT */ +#define WM5100_IM_DSP_IRQ5_EINT_MASK 0x0010 /* IM_DSP_IRQ5_EINT */ +#define WM5100_IM_DSP_IRQ5_EINT_SHIFT 4 /* IM_DSP_IRQ5_EINT */ +#define WM5100_IM_DSP_IRQ5_EINT_WIDTH 1 /* IM_DSP_IRQ5_EINT */ +#define WM5100_IM_DSP_IRQ4_EINT 0x0008 /* IM_DSP_IRQ4_EINT */ +#define WM5100_IM_DSP_IRQ4_EINT_MASK 0x0008 /* IM_DSP_IRQ4_EINT */ +#define WM5100_IM_DSP_IRQ4_EINT_SHIFT 3 /* IM_DSP_IRQ4_EINT */ +#define WM5100_IM_DSP_IRQ4_EINT_WIDTH 1 /* IM_DSP_IRQ4_EINT */ +#define WM5100_IM_DSP_IRQ3_EINT 0x0004 /* IM_DSP_IRQ3_EINT */ +#define WM5100_IM_DSP_IRQ3_EINT_MASK 0x0004 /* IM_DSP_IRQ3_EINT */ +#define WM5100_IM_DSP_IRQ3_EINT_SHIFT 2 /* IM_DSP_IRQ3_EINT */ +#define WM5100_IM_DSP_IRQ3_EINT_WIDTH 1 /* IM_DSP_IRQ3_EINT */ +#define WM5100_IM_DSP_IRQ2_EINT 0x0002 /* IM_DSP_IRQ2_EINT */ +#define WM5100_IM_DSP_IRQ2_EINT_MASK 0x0002 /* IM_DSP_IRQ2_EINT */ +#define WM5100_IM_DSP_IRQ2_EINT_SHIFT 1 /* IM_DSP_IRQ2_EINT */ +#define WM5100_IM_DSP_IRQ2_EINT_WIDTH 1 /* IM_DSP_IRQ2_EINT */ +#define WM5100_IM_DSP_IRQ1_EINT 0x0001 /* IM_DSP_IRQ1_EINT */ +#define WM5100_IM_DSP_IRQ1_EINT_MASK 0x0001 /* IM_DSP_IRQ1_EINT */ +#define WM5100_IM_DSP_IRQ1_EINT_SHIFT 0 /* IM_DSP_IRQ1_EINT */ +#define WM5100_IM_DSP_IRQ1_EINT_WIDTH 1 /* IM_DSP_IRQ1_EINT */ + +/* + * R3337 (0xD09) - Interrupt Status 3 Mask + */ +#define WM5100_IM_SPK_SHUTDOWN_WARN_EINT 0x8000 /* IM_SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_IM_SPK_SHUTDOWN_WARN_EINT_MASK 0x8000 /* IM_SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_IM_SPK_SHUTDOWN_WARN_EINT_SHIFT 15 /* IM_SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_IM_SPK_SHUTDOWN_WARN_EINT_WIDTH 1 /* IM_SPK_SHUTDOWN_WARN_EINT */ +#define WM5100_IM_SPK_SHUTDOWN_EINT 0x4000 /* IM_SPK_SHUTDOWN_EINT */ +#define WM5100_IM_SPK_SHUTDOWN_EINT_MASK 0x4000 /* IM_SPK_SHUTDOWN_EINT */ +#define WM5100_IM_SPK_SHUTDOWN_EINT_SHIFT 14 /* IM_SPK_SHUTDOWN_EINT */ +#define WM5100_IM_SPK_SHUTDOWN_EINT_WIDTH 1 /* IM_SPK_SHUTDOWN_EINT */ +#define WM5100_IM_HPDET_EINT 0x2000 /* IM_HPDET_EINT */ +#define WM5100_IM_HPDET_EINT_MASK 0x2000 /* IM_HPDET_EINT */ +#define WM5100_IM_HPDET_EINT_SHIFT 13 /* IM_HPDET_EINT */ +#define WM5100_IM_HPDET_EINT_WIDTH 1 /* IM_HPDET_EINT */ +#define WM5100_IM_ACCDET_EINT 0x1000 /* IM_ACCDET_EINT */ +#define WM5100_IM_ACCDET_EINT_MASK 0x1000 /* IM_ACCDET_EINT */ +#define WM5100_IM_ACCDET_EINT_SHIFT 12 /* IM_ACCDET_EINT */ +#define WM5100_IM_ACCDET_EINT_WIDTH 1 /* IM_ACCDET_EINT */ +#define WM5100_IM_DRC_SIG_DET_EINT 0x0200 /* IM_DRC_SIG_DET_EINT */ +#define WM5100_IM_DRC_SIG_DET_EINT_MASK 0x0200 /* IM_DRC_SIG_DET_EINT */ +#define WM5100_IM_DRC_SIG_DET_EINT_SHIFT 9 /* IM_DRC_SIG_DET_EINT */ +#define WM5100_IM_DRC_SIG_DET_EINT_WIDTH 1 /* IM_DRC_SIG_DET_EINT */ +#define WM5100_IM_ASRC2_LOCK_EINT 0x0100 /* IM_ASRC2_LOCK_EINT */ +#define WM5100_IM_ASRC2_LOCK_EINT_MASK 0x0100 /* IM_ASRC2_LOCK_EINT */ +#define WM5100_IM_ASRC2_LOCK_EINT_SHIFT 8 /* IM_ASRC2_LOCK_EINT */ +#define WM5100_IM_ASRC2_LOCK_EINT_WIDTH 1 /* IM_ASRC2_LOCK_EINT */ +#define WM5100_IM_ASRC1_LOCK_EINT 0x0080 /* IM_ASRC1_LOCK_EINT */ +#define WM5100_IM_ASRC1_LOCK_EINT_MASK 0x0080 /* IM_ASRC1_LOCK_EINT */ +#define WM5100_IM_ASRC1_LOCK_EINT_SHIFT 7 /* IM_ASRC1_LOCK_EINT */ +#define WM5100_IM_ASRC1_LOCK_EINT_WIDTH 1 /* IM_ASRC1_LOCK_EINT */ +#define WM5100_IM_FLL2_LOCK_EINT 0x0008 /* IM_FLL2_LOCK_EINT */ +#define WM5100_IM_FLL2_LOCK_EINT_MASK 0x0008 /* IM_FLL2_LOCK_EINT */ +#define WM5100_IM_FLL2_LOCK_EINT_SHIFT 3 /* IM_FLL2_LOCK_EINT */ +#define WM5100_IM_FLL2_LOCK_EINT_WIDTH 1 /* IM_FLL2_LOCK_EINT */ +#define WM5100_IM_FLL1_LOCK_EINT 0x0004 /* IM_FLL1_LOCK_EINT */ +#define WM5100_IM_FLL1_LOCK_EINT_MASK 0x0004 /* IM_FLL1_LOCK_EINT */ +#define WM5100_IM_FLL1_LOCK_EINT_SHIFT 2 /* IM_FLL1_LOCK_EINT */ +#define WM5100_IM_FLL1_LOCK_EINT_WIDTH 1 /* IM_FLL1_LOCK_EINT */ +#define WM5100_IM_CLKGEN_ERR_EINT 0x0002 /* IM_CLKGEN_ERR_EINT */ +#define WM5100_IM_CLKGEN_ERR_EINT_MASK 0x0002 /* IM_CLKGEN_ERR_EINT */ +#define WM5100_IM_CLKGEN_ERR_EINT_SHIFT 1 /* IM_CLKGEN_ERR_EINT */ +#define WM5100_IM_CLKGEN_ERR_EINT_WIDTH 1 /* IM_CLKGEN_ERR_EINT */ +#define WM5100_IM_CLKGEN_ERR_ASYNC_EINT 0x0001 /* IM_CLKGEN_ERR_ASYNC_EINT */ +#define WM5100_IM_CLKGEN_ERR_ASYNC_EINT_MASK 0x0001 /* IM_CLKGEN_ERR_ASYNC_EINT */ +#define WM5100_IM_CLKGEN_ERR_ASYNC_EINT_SHIFT 0 /* IM_CLKGEN_ERR_ASYNC_EINT */ +#define WM5100_IM_CLKGEN_ERR_ASYNC_EINT_WIDTH 1 /* IM_CLKGEN_ERR_ASYNC_EINT */ + +/* + * R3338 (0xD0A) - Interrupt Status 4 Mask + */ +#define WM5100_IM_AIF3_ERR_EINT 0x2000 /* IM_AIF3_ERR_EINT */ +#define WM5100_IM_AIF3_ERR_EINT_MASK 0x2000 /* IM_AIF3_ERR_EINT */ +#define WM5100_IM_AIF3_ERR_EINT_SHIFT 13 /* IM_AIF3_ERR_EINT */ +#define WM5100_IM_AIF3_ERR_EINT_WIDTH 1 /* IM_AIF3_ERR_EINT */ +#define WM5100_IM_AIF2_ERR_EINT 0x1000 /* IM_AIF2_ERR_EINT */ +#define WM5100_IM_AIF2_ERR_EINT_MASK 0x1000 /* IM_AIF2_ERR_EINT */ +#define WM5100_IM_AIF2_ERR_EINT_SHIFT 12 /* IM_AIF2_ERR_EINT */ +#define WM5100_IM_AIF2_ERR_EINT_WIDTH 1 /* IM_AIF2_ERR_EINT */ +#define WM5100_IM_AIF1_ERR_EINT 0x0800 /* IM_AIF1_ERR_EINT */ +#define WM5100_IM_AIF1_ERR_EINT_MASK 0x0800 /* IM_AIF1_ERR_EINT */ +#define WM5100_IM_AIF1_ERR_EINT_SHIFT 11 /* IM_AIF1_ERR_EINT */ +#define WM5100_IM_AIF1_ERR_EINT_WIDTH 1 /* IM_AIF1_ERR_EINT */ +#define WM5100_IM_CTRLIF_ERR_EINT 0x0400 /* IM_CTRLIF_ERR_EINT */ +#define WM5100_IM_CTRLIF_ERR_EINT_MASK 0x0400 /* IM_CTRLIF_ERR_EINT */ +#define WM5100_IM_CTRLIF_ERR_EINT_SHIFT 10 /* IM_CTRLIF_ERR_EINT */ +#define WM5100_IM_CTRLIF_ERR_EINT_WIDTH 1 /* IM_CTRLIF_ERR_EINT */ +#define WM5100_IM_ISRC2_UNDERCLOCKED_EINT 0x0200 /* IM_ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_IM_ISRC2_UNDERCLOCKED_EINT_MASK 0x0200 /* IM_ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_IM_ISRC2_UNDERCLOCKED_EINT_SHIFT 9 /* IM_ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_IM_ISRC2_UNDERCLOCKED_EINT_WIDTH 1 /* IM_ISRC2_UNDERCLOCKED_EINT */ +#define WM5100_IM_ISRC1_UNDERCLOCKED_EINT 0x0100 /* IM_ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_IM_ISRC1_UNDERCLOCKED_EINT_MASK 0x0100 /* IM_ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_IM_ISRC1_UNDERCLOCKED_EINT_SHIFT 8 /* IM_ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_IM_ISRC1_UNDERCLOCKED_EINT_WIDTH 1 /* IM_ISRC1_UNDERCLOCKED_EINT */ +#define WM5100_IM_FX_UNDERCLOCKED_EINT 0x0080 /* IM_FX_UNDERCLOCKED_EINT */ +#define WM5100_IM_FX_UNDERCLOCKED_EINT_MASK 0x0080 /* IM_FX_UNDERCLOCKED_EINT */ +#define WM5100_IM_FX_UNDERCLOCKED_EINT_SHIFT 7 /* IM_FX_UNDERCLOCKED_EINT */ +#define WM5100_IM_FX_UNDERCLOCKED_EINT_WIDTH 1 /* IM_FX_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF3_UNDERCLOCKED_EINT 0x0040 /* IM_AIF3_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF3_UNDERCLOCKED_EINT_MASK 0x0040 /* IM_AIF3_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF3_UNDERCLOCKED_EINT_SHIFT 6 /* IM_AIF3_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF3_UNDERCLOCKED_EINT_WIDTH 1 /* IM_AIF3_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF2_UNDERCLOCKED_EINT 0x0020 /* IM_AIF2_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF2_UNDERCLOCKED_EINT_MASK 0x0020 /* IM_AIF2_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF2_UNDERCLOCKED_EINT_SHIFT 5 /* IM_AIF2_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF2_UNDERCLOCKED_EINT_WIDTH 1 /* IM_AIF2_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF1_UNDERCLOCKED_EINT 0x0010 /* IM_AIF1_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF1_UNDERCLOCKED_EINT_MASK 0x0010 /* IM_AIF1_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF1_UNDERCLOCKED_EINT_SHIFT 4 /* IM_AIF1_UNDERCLOCKED_EINT */ +#define WM5100_IM_AIF1_UNDERCLOCKED_EINT_WIDTH 1 /* IM_AIF1_UNDERCLOCKED_EINT */ +#define WM5100_IM_ASRC_UNDERCLOCKED_EINT 0x0008 /* IM_ASRC_UNDERCLOCKED_EINT */ +#define WM5100_IM_ASRC_UNDERCLOCKED_EINT_MASK 0x0008 /* IM_ASRC_UNDERCLOCKED_EINT */ +#define WM5100_IM_ASRC_UNDERCLOCKED_EINT_SHIFT 3 /* IM_ASRC_UNDERCLOCKED_EINT */ +#define WM5100_IM_ASRC_UNDERCLOCKED_EINT_WIDTH 1 /* IM_ASRC_UNDERCLOCKED_EINT */ +#define WM5100_IM_DAC_UNDERCLOCKED_EINT 0x0004 /* IM_DAC_UNDERCLOCKED_EINT */ +#define WM5100_IM_DAC_UNDERCLOCKED_EINT_MASK 0x0004 /* IM_DAC_UNDERCLOCKED_EINT */ +#define WM5100_IM_DAC_UNDERCLOCKED_EINT_SHIFT 2 /* IM_DAC_UNDERCLOCKED_EINT */ +#define WM5100_IM_DAC_UNDERCLOCKED_EINT_WIDTH 1 /* IM_DAC_UNDERCLOCKED_EINT */ +#define WM5100_IM_ADC_UNDERCLOCKED_EINT 0x0002 /* IM_ADC_UNDERCLOCKED_EINT */ +#define WM5100_IM_ADC_UNDERCLOCKED_EINT_MASK 0x0002 /* IM_ADC_UNDERCLOCKED_EINT */ +#define WM5100_IM_ADC_UNDERCLOCKED_EINT_SHIFT 1 /* IM_ADC_UNDERCLOCKED_EINT */ +#define WM5100_IM_ADC_UNDERCLOCKED_EINT_WIDTH 1 /* IM_ADC_UNDERCLOCKED_EINT */ +#define WM5100_IM_MIXER_UNDERCLOCKED_EINT 0x0001 /* IM_MIXER_UNDERCLOCKED_EINT */ +#define WM5100_IM_MIXER_UNDERCLOCKED_EINT_MASK 0x0001 /* IM_MIXER_UNDERCLOCKED_EINT */ +#define WM5100_IM_MIXER_UNDERCLOCKED_EINT_SHIFT 0 /* IM_MIXER_UNDERCLOCKED_EINT */ +#define WM5100_IM_MIXER_UNDERCLOCKED_EINT_WIDTH 1 /* IM_MIXER_UNDERCLOCKED_EINT */ + +/* + * R3359 (0xD1F) - Interrupt Control + */ +#define WM5100_IM_IRQ 0x0001 /* IM_IRQ */ +#define WM5100_IM_IRQ_MASK 0x0001 /* IM_IRQ */ +#define WM5100_IM_IRQ_SHIFT 0 /* IM_IRQ */ +#define WM5100_IM_IRQ_WIDTH 1 /* IM_IRQ */ + +/* + * R3360 (0xD20) - IRQ Debounce 1 + */ +#define WM5100_SPK_SHUTDOWN_WARN_DB 0x0200 /* SPK_SHUTDOWN_WARN_DB */ +#define WM5100_SPK_SHUTDOWN_WARN_DB_MASK 0x0200 /* SPK_SHUTDOWN_WARN_DB */ +#define WM5100_SPK_SHUTDOWN_WARN_DB_SHIFT 9 /* SPK_SHUTDOWN_WARN_DB */ +#define WM5100_SPK_SHUTDOWN_WARN_DB_WIDTH 1 /* SPK_SHUTDOWN_WARN_DB */ +#define WM5100_SPK_SHUTDOWN_DB 0x0100 /* SPK_SHUTDOWN_DB */ +#define WM5100_SPK_SHUTDOWN_DB_MASK 0x0100 /* SPK_SHUTDOWN_DB */ +#define WM5100_SPK_SHUTDOWN_DB_SHIFT 8 /* SPK_SHUTDOWN_DB */ +#define WM5100_SPK_SHUTDOWN_DB_WIDTH 1 /* SPK_SHUTDOWN_DB */ +#define WM5100_FLL1_LOCK_IRQ_DB 0x0008 /* FLL1_LOCK_IRQ_DB */ +#define WM5100_FLL1_LOCK_IRQ_DB_MASK 0x0008 /* FLL1_LOCK_IRQ_DB */ +#define WM5100_FLL1_LOCK_IRQ_DB_SHIFT 3 /* FLL1_LOCK_IRQ_DB */ +#define WM5100_FLL1_LOCK_IRQ_DB_WIDTH 1 /* FLL1_LOCK_IRQ_DB */ +#define WM5100_FLL2_LOCK_IRQ_DB 0x0004 /* FLL2_LOCK_IRQ_DB */ +#define WM5100_FLL2_LOCK_IRQ_DB_MASK 0x0004 /* FLL2_LOCK_IRQ_DB */ +#define WM5100_FLL2_LOCK_IRQ_DB_SHIFT 2 /* FLL2_LOCK_IRQ_DB */ +#define WM5100_FLL2_LOCK_IRQ_DB_WIDTH 1 /* FLL2_LOCK_IRQ_DB */ +#define WM5100_CLKGEN_ERR_IRQ_DB 0x0002 /* CLKGEN_ERR_IRQ_DB */ +#define WM5100_CLKGEN_ERR_IRQ_DB_MASK 0x0002 /* CLKGEN_ERR_IRQ_DB */ +#define WM5100_CLKGEN_ERR_IRQ_DB_SHIFT 1 /* CLKGEN_ERR_IRQ_DB */ +#define WM5100_CLKGEN_ERR_IRQ_DB_WIDTH 1 /* CLKGEN_ERR_IRQ_DB */ +#define WM5100_CLKGEN_ERR_ASYNC_IRQ_DB 0x0001 /* CLKGEN_ERR_ASYNC_IRQ_DB */ +#define WM5100_CLKGEN_ERR_ASYNC_IRQ_DB_MASK 0x0001 /* CLKGEN_ERR_ASYNC_IRQ_DB */ +#define WM5100_CLKGEN_ERR_ASYNC_IRQ_DB_SHIFT 0 /* CLKGEN_ERR_ASYNC_IRQ_DB */ +#define WM5100_CLKGEN_ERR_ASYNC_IRQ_DB_WIDTH 1 /* CLKGEN_ERR_ASYNC_IRQ_DB */ + +/* + * R3361 (0xD21) - IRQ Debounce 2 + */ +#define WM5100_AIF_ERR_DB 0x0001 /* AIF_ERR_DB */ +#define WM5100_AIF_ERR_DB_MASK 0x0001 /* AIF_ERR_DB */ +#define WM5100_AIF_ERR_DB_SHIFT 0 /* AIF_ERR_DB */ +#define WM5100_AIF_ERR_DB_WIDTH 1 /* AIF_ERR_DB */ + +/* + * R3584 (0xE00) - FX_Ctrl + */ +#define WM5100_FX_STS_MASK 0xFFC0 /* FX_STS - [15:6] */ +#define WM5100_FX_STS_SHIFT 6 /* FX_STS - [15:6] */ +#define WM5100_FX_STS_WIDTH 10 /* FX_STS - [15:6] */ +#define WM5100_FX_RATE_MASK 0x0003 /* FX_RATE - [1:0] */ +#define WM5100_FX_RATE_SHIFT 0 /* FX_RATE - [1:0] */ +#define WM5100_FX_RATE_WIDTH 2 /* FX_RATE - [1:0] */ + +/* + * R3600 (0xE10) - EQ1_1 + */ +#define WM5100_EQ1_B1_GAIN_MASK 0xF800 /* EQ1_B1_GAIN - [15:11] */ +#define WM5100_EQ1_B1_GAIN_SHIFT 11 /* EQ1_B1_GAIN - [15:11] */ +#define WM5100_EQ1_B1_GAIN_WIDTH 5 /* EQ1_B1_GAIN - [15:11] */ +#define WM5100_EQ1_B2_GAIN_MASK 0x07C0 /* EQ1_B2_GAIN - [10:6] */ +#define WM5100_EQ1_B2_GAIN_SHIFT 6 /* EQ1_B2_GAIN - [10:6] */ +#define WM5100_EQ1_B2_GAIN_WIDTH 5 /* EQ1_B2_GAIN - [10:6] */ +#define WM5100_EQ1_B3_GAIN_MASK 0x003E /* EQ1_B3_GAIN - [5:1] */ +#define WM5100_EQ1_B3_GAIN_SHIFT 1 /* EQ1_B3_GAIN - [5:1] */ +#define WM5100_EQ1_B3_GAIN_WIDTH 5 /* EQ1_B3_GAIN - [5:1] */ +#define WM5100_EQ1_ENA 0x0001 /* EQ1_ENA */ +#define WM5100_EQ1_ENA_MASK 0x0001 /* EQ1_ENA */ +#define WM5100_EQ1_ENA_SHIFT 0 /* EQ1_ENA */ +#define WM5100_EQ1_ENA_WIDTH 1 /* EQ1_ENA */ + +/* + * R3601 (0xE11) - EQ1_2 + */ +#define WM5100_EQ1_B4_GAIN_MASK 0xF800 /* EQ1_B4_GAIN - [15:11] */ +#define WM5100_EQ1_B4_GAIN_SHIFT 11 /* EQ1_B4_GAIN - [15:11] */ +#define WM5100_EQ1_B4_GAIN_WIDTH 5 /* EQ1_B4_GAIN - [15:11] */ +#define WM5100_EQ1_B5_GAIN_MASK 0x07C0 /* EQ1_B5_GAIN - [10:6] */ +#define WM5100_EQ1_B5_GAIN_SHIFT 6 /* EQ1_B5_GAIN - [10:6] */ +#define WM5100_EQ1_B5_GAIN_WIDTH 5 /* EQ1_B5_GAIN - [10:6] */ + +/* + * R3602 (0xE12) - EQ1_3 + */ +#define WM5100_EQ1_B1_A_MASK 0xFFFF /* EQ1_B1_A - [15:0] */ +#define WM5100_EQ1_B1_A_SHIFT 0 /* EQ1_B1_A - [15:0] */ +#define WM5100_EQ1_B1_A_WIDTH 16 /* EQ1_B1_A - [15:0] */ + +/* + * R3603 (0xE13) - EQ1_4 + */ +#define WM5100_EQ1_B1_B_MASK 0xFFFF /* EQ1_B1_B - [15:0] */ +#define WM5100_EQ1_B1_B_SHIFT 0 /* EQ1_B1_B - [15:0] */ +#define WM5100_EQ1_B1_B_WIDTH 16 /* EQ1_B1_B - [15:0] */ + +/* + * R3604 (0xE14) - EQ1_5 + */ +#define WM5100_EQ1_B1_PG_MASK 0xFFFF /* EQ1_B1_PG - [15:0] */ +#define WM5100_EQ1_B1_PG_SHIFT 0 /* EQ1_B1_PG - [15:0] */ +#define WM5100_EQ1_B1_PG_WIDTH 16 /* EQ1_B1_PG - [15:0] */ + +/* + * R3605 (0xE15) - EQ1_6 + */ +#define WM5100_EQ1_B2_A_MASK 0xFFFF /* EQ1_B2_A - [15:0] */ +#define WM5100_EQ1_B2_A_SHIFT 0 /* EQ1_B2_A - [15:0] */ +#define WM5100_EQ1_B2_A_WIDTH 16 /* EQ1_B2_A - [15:0] */ + +/* + * R3606 (0xE16) - EQ1_7 + */ +#define WM5100_EQ1_B2_B_MASK 0xFFFF /* EQ1_B2_B - [15:0] */ +#define WM5100_EQ1_B2_B_SHIFT 0 /* EQ1_B2_B - [15:0] */ +#define WM5100_EQ1_B2_B_WIDTH 16 /* EQ1_B2_B - [15:0] */ + +/* + * R3607 (0xE17) - EQ1_8 + */ +#define WM5100_EQ1_B2_C_MASK 0xFFFF /* EQ1_B2_C - [15:0] */ +#define WM5100_EQ1_B2_C_SHIFT 0 /* EQ1_B2_C - [15:0] */ +#define WM5100_EQ1_B2_C_WIDTH 16 /* EQ1_B2_C - [15:0] */ + +/* + * R3608 (0xE18) - EQ1_9 + */ +#define WM5100_EQ1_B2_PG_MASK 0xFFFF /* EQ1_B2_PG - [15:0] */ +#define WM5100_EQ1_B2_PG_SHIFT 0 /* EQ1_B2_PG - [15:0] */ +#define WM5100_EQ1_B2_PG_WIDTH 16 /* EQ1_B2_PG - [15:0] */ + +/* + * R3609 (0xE19) - EQ1_10 + */ +#define WM5100_EQ1_B3_A_MASK 0xFFFF /* EQ1_B3_A - [15:0] */ +#define WM5100_EQ1_B3_A_SHIFT 0 /* EQ1_B3_A - [15:0] */ +#define WM5100_EQ1_B3_A_WIDTH 16 /* EQ1_B3_A - [15:0] */ + +/* + * R3610 (0xE1A) - EQ1_11 + */ +#define WM5100_EQ1_B3_B_MASK 0xFFFF /* EQ1_B3_B - [15:0] */ +#define WM5100_EQ1_B3_B_SHIFT 0 /* EQ1_B3_B - [15:0] */ +#define WM5100_EQ1_B3_B_WIDTH 16 /* EQ1_B3_B - [15:0] */ + +/* + * R3611 (0xE1B) - EQ1_12 + */ +#define WM5100_EQ1_B3_C_MASK 0xFFFF /* EQ1_B3_C - [15:0] */ +#define WM5100_EQ1_B3_C_SHIFT 0 /* EQ1_B3_C - [15:0] */ +#define WM5100_EQ1_B3_C_WIDTH 16 /* EQ1_B3_C - [15:0] */ + +/* + * R3612 (0xE1C) - EQ1_13 + */ +#define WM5100_EQ1_B3_PG_MASK 0xFFFF /* EQ1_B3_PG - [15:0] */ +#define WM5100_EQ1_B3_PG_SHIFT 0 /* EQ1_B3_PG - [15:0] */ +#define WM5100_EQ1_B3_PG_WIDTH 16 /* EQ1_B3_PG - [15:0] */ + +/* + * R3613 (0xE1D) - EQ1_14 + */ +#define WM5100_EQ1_B4_A_MASK 0xFFFF /* EQ1_B4_A - [15:0] */ +#define WM5100_EQ1_B4_A_SHIFT 0 /* EQ1_B4_A - [15:0] */ +#define WM5100_EQ1_B4_A_WIDTH 16 /* EQ1_B4_A - [15:0] */ + +/* + * R3614 (0xE1E) - EQ1_15 + */ +#define WM5100_EQ1_B4_B_MASK 0xFFFF /* EQ1_B4_B - [15:0] */ +#define WM5100_EQ1_B4_B_SHIFT 0 /* EQ1_B4_B - [15:0] */ +#define WM5100_EQ1_B4_B_WIDTH 16 /* EQ1_B4_B - [15:0] */ + +/* + * R3615 (0xE1F) - EQ1_16 + */ +#define WM5100_EQ1_B4_C_MASK 0xFFFF /* EQ1_B4_C - [15:0] */ +#define WM5100_EQ1_B4_C_SHIFT 0 /* EQ1_B4_C - [15:0] */ +#define WM5100_EQ1_B4_C_WIDTH 16 /* EQ1_B4_C - [15:0] */ + +/* + * R3616 (0xE20) - EQ1_17 + */ +#define WM5100_EQ1_B4_PG_MASK 0xFFFF /* EQ1_B4_PG - [15:0] */ +#define WM5100_EQ1_B4_PG_SHIFT 0 /* EQ1_B4_PG - [15:0] */ +#define WM5100_EQ1_B4_PG_WIDTH 16 /* EQ1_B4_PG - [15:0] */ + +/* + * R3617 (0xE21) - EQ1_18 + */ +#define WM5100_EQ1_B5_A_MASK 0xFFFF /* EQ1_B5_A - [15:0] */ +#define WM5100_EQ1_B5_A_SHIFT 0 /* EQ1_B5_A - [15:0] */ +#define WM5100_EQ1_B5_A_WIDTH 16 /* EQ1_B5_A - [15:0] */ + +/* + * R3618 (0xE22) - EQ1_19 + */ +#define WM5100_EQ1_B5_B_MASK 0xFFFF /* EQ1_B5_B - [15:0] */ +#define WM5100_EQ1_B5_B_SHIFT 0 /* EQ1_B5_B - [15:0] */ +#define WM5100_EQ1_B5_B_WIDTH 16 /* EQ1_B5_B - [15:0] */ + +/* + * R3619 (0xE23) - EQ1_20 + */ +#define WM5100_EQ1_B5_PG_MASK 0xFFFF /* EQ1_B5_PG - [15:0] */ +#define WM5100_EQ1_B5_PG_SHIFT 0 /* EQ1_B5_PG - [15:0] */ +#define WM5100_EQ1_B5_PG_WIDTH 16 /* EQ1_B5_PG - [15:0] */ + +/* + * R3622 (0xE26) - EQ2_1 + */ +#define WM5100_EQ2_B1_GAIN_MASK 0xF800 /* EQ2_B1_GAIN - [15:11] */ +#define WM5100_EQ2_B1_GAIN_SHIFT 11 /* EQ2_B1_GAIN - [15:11] */ +#define WM5100_EQ2_B1_GAIN_WIDTH 5 /* EQ2_B1_GAIN - [15:11] */ +#define WM5100_EQ2_B2_GAIN_MASK 0x07C0 /* EQ2_B2_GAIN - [10:6] */ +#define WM5100_EQ2_B2_GAIN_SHIFT 6 /* EQ2_B2_GAIN - [10:6] */ +#define WM5100_EQ2_B2_GAIN_WIDTH 5 /* EQ2_B2_GAIN - [10:6] */ +#define WM5100_EQ2_B3_GAIN_MASK 0x003E /* EQ2_B3_GAIN - [5:1] */ +#define WM5100_EQ2_B3_GAIN_SHIFT 1 /* EQ2_B3_GAIN - [5:1] */ +#define WM5100_EQ2_B3_GAIN_WIDTH 5 /* EQ2_B3_GAIN - [5:1] */ +#define WM5100_EQ2_ENA 0x0001 /* EQ2_ENA */ +#define WM5100_EQ2_ENA_MASK 0x0001 /* EQ2_ENA */ +#define WM5100_EQ2_ENA_SHIFT 0 /* EQ2_ENA */ +#define WM5100_EQ2_ENA_WIDTH 1 /* EQ2_ENA */ + +/* + * R3623 (0xE27) - EQ2_2 + */ +#define WM5100_EQ2_B4_GAIN_MASK 0xF800 /* EQ2_B4_GAIN - [15:11] */ +#define WM5100_EQ2_B4_GAIN_SHIFT 11 /* EQ2_B4_GAIN - [15:11] */ +#define WM5100_EQ2_B4_GAIN_WIDTH 5 /* EQ2_B4_GAIN - [15:11] */ +#define WM5100_EQ2_B5_GAIN_MASK 0x07C0 /* EQ2_B5_GAIN - [10:6] */ +#define WM5100_EQ2_B5_GAIN_SHIFT 6 /* EQ2_B5_GAIN - [10:6] */ +#define WM5100_EQ2_B5_GAIN_WIDTH 5 /* EQ2_B5_GAIN - [10:6] */ + +/* + * R3624 (0xE28) - EQ2_3 + */ +#define WM5100_EQ2_B1_A_MASK 0xFFFF /* EQ2_B1_A - [15:0] */ +#define WM5100_EQ2_B1_A_SHIFT 0 /* EQ2_B1_A - [15:0] */ +#define WM5100_EQ2_B1_A_WIDTH 16 /* EQ2_B1_A - [15:0] */ + +/* + * R3625 (0xE29) - EQ2_4 + */ +#define WM5100_EQ2_B1_B_MASK 0xFFFF /* EQ2_B1_B - [15:0] */ +#define WM5100_EQ2_B1_B_SHIFT 0 /* EQ2_B1_B - [15:0] */ +#define WM5100_EQ2_B1_B_WIDTH 16 /* EQ2_B1_B - [15:0] */ + +/* + * R3626 (0xE2A) - EQ2_5 + */ +#define WM5100_EQ2_B1_PG_MASK 0xFFFF /* EQ2_B1_PG - [15:0] */ +#define WM5100_EQ2_B1_PG_SHIFT 0 /* EQ2_B1_PG - [15:0] */ +#define WM5100_EQ2_B1_PG_WIDTH 16 /* EQ2_B1_PG - [15:0] */ + +/* + * R3627 (0xE2B) - EQ2_6 + */ +#define WM5100_EQ2_B2_A_MASK 0xFFFF /* EQ2_B2_A - [15:0] */ +#define WM5100_EQ2_B2_A_SHIFT 0 /* EQ2_B2_A - [15:0] */ +#define WM5100_EQ2_B2_A_WIDTH 16 /* EQ2_B2_A - [15:0] */ + +/* + * R3628 (0xE2C) - EQ2_7 + */ +#define WM5100_EQ2_B2_B_MASK 0xFFFF /* EQ2_B2_B - [15:0] */ +#define WM5100_EQ2_B2_B_SHIFT 0 /* EQ2_B2_B - [15:0] */ +#define WM5100_EQ2_B2_B_WIDTH 16 /* EQ2_B2_B - [15:0] */ + +/* + * R3629 (0xE2D) - EQ2_8 + */ +#define WM5100_EQ2_B2_C_MASK 0xFFFF /* EQ2_B2_C - [15:0] */ +#define WM5100_EQ2_B2_C_SHIFT 0 /* EQ2_B2_C - [15:0] */ +#define WM5100_EQ2_B2_C_WIDTH 16 /* EQ2_B2_C - [15:0] */ + +/* + * R3630 (0xE2E) - EQ2_9 + */ +#define WM5100_EQ2_B2_PG_MASK 0xFFFF /* EQ2_B2_PG - [15:0] */ +#define WM5100_EQ2_B2_PG_SHIFT 0 /* EQ2_B2_PG - [15:0] */ +#define WM5100_EQ2_B2_PG_WIDTH 16 /* EQ2_B2_PG - [15:0] */ + +/* + * R3631 (0xE2F) - EQ2_10 + */ +#define WM5100_EQ2_B3_A_MASK 0xFFFF /* EQ2_B3_A - [15:0] */ +#define WM5100_EQ2_B3_A_SHIFT 0 /* EQ2_B3_A - [15:0] */ +#define WM5100_EQ2_B3_A_WIDTH 16 /* EQ2_B3_A - [15:0] */ + +/* + * R3632 (0xE30) - EQ2_11 + */ +#define WM5100_EQ2_B3_B_MASK 0xFFFF /* EQ2_B3_B - [15:0] */ +#define WM5100_EQ2_B3_B_SHIFT 0 /* EQ2_B3_B - [15:0] */ +#define WM5100_EQ2_B3_B_WIDTH 16 /* EQ2_B3_B - [15:0] */ + +/* + * R3633 (0xE31) - EQ2_12 + */ +#define WM5100_EQ2_B3_C_MASK 0xFFFF /* EQ2_B3_C - [15:0] */ +#define WM5100_EQ2_B3_C_SHIFT 0 /* EQ2_B3_C - [15:0] */ +#define WM5100_EQ2_B3_C_WIDTH 16 /* EQ2_B3_C - [15:0] */ + +/* + * R3634 (0xE32) - EQ2_13 + */ +#define WM5100_EQ2_B3_PG_MASK 0xFFFF /* EQ2_B3_PG - [15:0] */ +#define WM5100_EQ2_B3_PG_SHIFT 0 /* EQ2_B3_PG - [15:0] */ +#define WM5100_EQ2_B3_PG_WIDTH 16 /* EQ2_B3_PG - [15:0] */ + +/* + * R3635 (0xE33) - EQ2_14 + */ +#define WM5100_EQ2_B4_A_MASK 0xFFFF /* EQ2_B4_A - [15:0] */ +#define WM5100_EQ2_B4_A_SHIFT 0 /* EQ2_B4_A - [15:0] */ +#define WM5100_EQ2_B4_A_WIDTH 16 /* EQ2_B4_A - [15:0] */ + +/* + * R3636 (0xE34) - EQ2_15 + */ +#define WM5100_EQ2_B4_B_MASK 0xFFFF /* EQ2_B4_B - [15:0] */ +#define WM5100_EQ2_B4_B_SHIFT 0 /* EQ2_B4_B - [15:0] */ +#define WM5100_EQ2_B4_B_WIDTH 16 /* EQ2_B4_B - [15:0] */ + +/* + * R3637 (0xE35) - EQ2_16 + */ +#define WM5100_EQ2_B4_C_MASK 0xFFFF /* EQ2_B4_C - [15:0] */ +#define WM5100_EQ2_B4_C_SHIFT 0 /* EQ2_B4_C - [15:0] */ +#define WM5100_EQ2_B4_C_WIDTH 16 /* EQ2_B4_C - [15:0] */ + +/* + * R3638 (0xE36) - EQ2_17 + */ +#define WM5100_EQ2_B4_PG_MASK 0xFFFF /* EQ2_B4_PG - [15:0] */ +#define WM5100_EQ2_B4_PG_SHIFT 0 /* EQ2_B4_PG - [15:0] */ +#define WM5100_EQ2_B4_PG_WIDTH 16 /* EQ2_B4_PG - [15:0] */ + +/* + * R3639 (0xE37) - EQ2_18 + */ +#define WM5100_EQ2_B5_A_MASK 0xFFFF /* EQ2_B5_A - [15:0] */ +#define WM5100_EQ2_B5_A_SHIFT 0 /* EQ2_B5_A - [15:0] */ +#define WM5100_EQ2_B5_A_WIDTH 16 /* EQ2_B5_A - [15:0] */ + +/* + * R3640 (0xE38) - EQ2_19 + */ +#define WM5100_EQ2_B5_B_MASK 0xFFFF /* EQ2_B5_B - [15:0] */ +#define WM5100_EQ2_B5_B_SHIFT 0 /* EQ2_B5_B - [15:0] */ +#define WM5100_EQ2_B5_B_WIDTH 16 /* EQ2_B5_B - [15:0] */ + +/* + * R3641 (0xE39) - EQ2_20 + */ +#define WM5100_EQ2_B5_PG_MASK 0xFFFF /* EQ2_B5_PG - [15:0] */ +#define WM5100_EQ2_B5_PG_SHIFT 0 /* EQ2_B5_PG - [15:0] */ +#define WM5100_EQ2_B5_PG_WIDTH 16 /* EQ2_B5_PG - [15:0] */ + +/* + * R3644 (0xE3C) - EQ3_1 + */ +#define WM5100_EQ3_B1_GAIN_MASK 0xF800 /* EQ3_B1_GAIN - [15:11] */ +#define WM5100_EQ3_B1_GAIN_SHIFT 11 /* EQ3_B1_GAIN - [15:11] */ +#define WM5100_EQ3_B1_GAIN_WIDTH 5 /* EQ3_B1_GAIN - [15:11] */ +#define WM5100_EQ3_B2_GAIN_MASK 0x07C0 /* EQ3_B2_GAIN - [10:6] */ +#define WM5100_EQ3_B2_GAIN_SHIFT 6 /* EQ3_B2_GAIN - [10:6] */ +#define WM5100_EQ3_B2_GAIN_WIDTH 5 /* EQ3_B2_GAIN - [10:6] */ +#define WM5100_EQ3_B3_GAIN_MASK 0x003E /* EQ3_B3_GAIN - [5:1] */ +#define WM5100_EQ3_B3_GAIN_SHIFT 1 /* EQ3_B3_GAIN - [5:1] */ +#define WM5100_EQ3_B3_GAIN_WIDTH 5 /* EQ3_B3_GAIN - [5:1] */ +#define WM5100_EQ3_ENA 0x0001 /* EQ3_ENA */ +#define WM5100_EQ3_ENA_MASK 0x0001 /* EQ3_ENA */ +#define WM5100_EQ3_ENA_SHIFT 0 /* EQ3_ENA */ +#define WM5100_EQ3_ENA_WIDTH 1 /* EQ3_ENA */ + +/* + * R3645 (0xE3D) - EQ3_2 + */ +#define WM5100_EQ3_B4_GAIN_MASK 0xF800 /* EQ3_B4_GAIN - [15:11] */ +#define WM5100_EQ3_B4_GAIN_SHIFT 11 /* EQ3_B4_GAIN - [15:11] */ +#define WM5100_EQ3_B4_GAIN_WIDTH 5 /* EQ3_B4_GAIN - [15:11] */ +#define WM5100_EQ3_B5_GAIN_MASK 0x07C0 /* EQ3_B5_GAIN - [10:6] */ +#define WM5100_EQ3_B5_GAIN_SHIFT 6 /* EQ3_B5_GAIN - [10:6] */ +#define WM5100_EQ3_B5_GAIN_WIDTH 5 /* EQ3_B5_GAIN - [10:6] */ + +/* + * R3646 (0xE3E) - EQ3_3 + */ +#define WM5100_EQ3_B1_A_MASK 0xFFFF /* EQ3_B1_A - [15:0] */ +#define WM5100_EQ3_B1_A_SHIFT 0 /* EQ3_B1_A - [15:0] */ +#define WM5100_EQ3_B1_A_WIDTH 16 /* EQ3_B1_A - [15:0] */ + +/* + * R3647 (0xE3F) - EQ3_4 + */ +#define WM5100_EQ3_B1_B_MASK 0xFFFF /* EQ3_B1_B - [15:0] */ +#define WM5100_EQ3_B1_B_SHIFT 0 /* EQ3_B1_B - [15:0] */ +#define WM5100_EQ3_B1_B_WIDTH 16 /* EQ3_B1_B - [15:0] */ + +/* + * R3648 (0xE40) - EQ3_5 + */ +#define WM5100_EQ3_B1_PG_MASK 0xFFFF /* EQ3_B1_PG - [15:0] */ +#define WM5100_EQ3_B1_PG_SHIFT 0 /* EQ3_B1_PG - [15:0] */ +#define WM5100_EQ3_B1_PG_WIDTH 16 /* EQ3_B1_PG - [15:0] */ + +/* + * R3649 (0xE41) - EQ3_6 + */ +#define WM5100_EQ3_B2_A_MASK 0xFFFF /* EQ3_B2_A - [15:0] */ +#define WM5100_EQ3_B2_A_SHIFT 0 /* EQ3_B2_A - [15:0] */ +#define WM5100_EQ3_B2_A_WIDTH 16 /* EQ3_B2_A - [15:0] */ + +/* + * R3650 (0xE42) - EQ3_7 + */ +#define WM5100_EQ3_B2_B_MASK 0xFFFF /* EQ3_B2_B - [15:0] */ +#define WM5100_EQ3_B2_B_SHIFT 0 /* EQ3_B2_B - [15:0] */ +#define WM5100_EQ3_B2_B_WIDTH 16 /* EQ3_B2_B - [15:0] */ + +/* + * R3651 (0xE43) - EQ3_8 + */ +#define WM5100_EQ3_B2_C_MASK 0xFFFF /* EQ3_B2_C - [15:0] */ +#define WM5100_EQ3_B2_C_SHIFT 0 /* EQ3_B2_C - [15:0] */ +#define WM5100_EQ3_B2_C_WIDTH 16 /* EQ3_B2_C - [15:0] */ + +/* + * R3652 (0xE44) - EQ3_9 + */ +#define WM5100_EQ3_B2_PG_MASK 0xFFFF /* EQ3_B2_PG - [15:0] */ +#define WM5100_EQ3_B2_PG_SHIFT 0 /* EQ3_B2_PG - [15:0] */ +#define WM5100_EQ3_B2_PG_WIDTH 16 /* EQ3_B2_PG - [15:0] */ + +/* + * R3653 (0xE45) - EQ3_10 + */ +#define WM5100_EQ3_B3_A_MASK 0xFFFF /* EQ3_B3_A - [15:0] */ +#define WM5100_EQ3_B3_A_SHIFT 0 /* EQ3_B3_A - [15:0] */ +#define WM5100_EQ3_B3_A_WIDTH 16 /* EQ3_B3_A - [15:0] */ + +/* + * R3654 (0xE46) - EQ3_11 + */ +#define WM5100_EQ3_B3_B_MASK 0xFFFF /* EQ3_B3_B - [15:0] */ +#define WM5100_EQ3_B3_B_SHIFT 0 /* EQ3_B3_B - [15:0] */ +#define WM5100_EQ3_B3_B_WIDTH 16 /* EQ3_B3_B - [15:0] */ + +/* + * R3655 (0xE47) - EQ3_12 + */ +#define WM5100_EQ3_B3_C_MASK 0xFFFF /* EQ3_B3_C - [15:0] */ +#define WM5100_EQ3_B3_C_SHIFT 0 /* EQ3_B3_C - [15:0] */ +#define WM5100_EQ3_B3_C_WIDTH 16 /* EQ3_B3_C - [15:0] */ + +/* + * R3656 (0xE48) - EQ3_13 + */ +#define WM5100_EQ3_B3_PG_MASK 0xFFFF /* EQ3_B3_PG - [15:0] */ +#define WM5100_EQ3_B3_PG_SHIFT 0 /* EQ3_B3_PG - [15:0] */ +#define WM5100_EQ3_B3_PG_WIDTH 16 /* EQ3_B3_PG - [15:0] */ + +/* + * R3657 (0xE49) - EQ3_14 + */ +#define WM5100_EQ3_B4_A_MASK 0xFFFF /* EQ3_B4_A - [15:0] */ +#define WM5100_EQ3_B4_A_SHIFT 0 /* EQ3_B4_A - [15:0] */ +#define WM5100_EQ3_B4_A_WIDTH 16 /* EQ3_B4_A - [15:0] */ + +/* + * R3658 (0xE4A) - EQ3_15 + */ +#define WM5100_EQ3_B4_B_MASK 0xFFFF /* EQ3_B4_B - [15:0] */ +#define WM5100_EQ3_B4_B_SHIFT 0 /* EQ3_B4_B - [15:0] */ +#define WM5100_EQ3_B4_B_WIDTH 16 /* EQ3_B4_B - [15:0] */ + +/* + * R3659 (0xE4B) - EQ3_16 + */ +#define WM5100_EQ3_B4_C_MASK 0xFFFF /* EQ3_B4_C - [15:0] */ +#define WM5100_EQ3_B4_C_SHIFT 0 /* EQ3_B4_C - [15:0] */ +#define WM5100_EQ3_B4_C_WIDTH 16 /* EQ3_B4_C - [15:0] */ + +/* + * R3660 (0xE4C) - EQ3_17 + */ +#define WM5100_EQ3_B4_PG_MASK 0xFFFF /* EQ3_B4_PG - [15:0] */ +#define WM5100_EQ3_B4_PG_SHIFT 0 /* EQ3_B4_PG - [15:0] */ +#define WM5100_EQ3_B4_PG_WIDTH 16 /* EQ3_B4_PG - [15:0] */ + +/* + * R3661 (0xE4D) - EQ3_18 + */ +#define WM5100_EQ3_B5_A_MASK 0xFFFF /* EQ3_B5_A - [15:0] */ +#define WM5100_EQ3_B5_A_SHIFT 0 /* EQ3_B5_A - [15:0] */ +#define WM5100_EQ3_B5_A_WIDTH 16 /* EQ3_B5_A - [15:0] */ + +/* + * R3662 (0xE4E) - EQ3_19 + */ +#define WM5100_EQ3_B5_B_MASK 0xFFFF /* EQ3_B5_B - [15:0] */ +#define WM5100_EQ3_B5_B_SHIFT 0 /* EQ3_B5_B - [15:0] */ +#define WM5100_EQ3_B5_B_WIDTH 16 /* EQ3_B5_B - [15:0] */ + +/* + * R3663 (0xE4F) - EQ3_20 + */ +#define WM5100_EQ3_B5_PG_MASK 0xFFFF /* EQ3_B5_PG - [15:0] */ +#define WM5100_EQ3_B5_PG_SHIFT 0 /* EQ3_B5_PG - [15:0] */ +#define WM5100_EQ3_B5_PG_WIDTH 16 /* EQ3_B5_PG - [15:0] */ + +/* + * R3666 (0xE52) - EQ4_1 + */ +#define WM5100_EQ4_B1_GAIN_MASK 0xF800 /* EQ4_B1_GAIN - [15:11] */ +#define WM5100_EQ4_B1_GAIN_SHIFT 11 /* EQ4_B1_GAIN - [15:11] */ +#define WM5100_EQ4_B1_GAIN_WIDTH 5 /* EQ4_B1_GAIN - [15:11] */ +#define WM5100_EQ4_B2_GAIN_MASK 0x07C0 /* EQ4_B2_GAIN - [10:6] */ +#define WM5100_EQ4_B2_GAIN_SHIFT 6 /* EQ4_B2_GAIN - [10:6] */ +#define WM5100_EQ4_B2_GAIN_WIDTH 5 /* EQ4_B2_GAIN - [10:6] */ +#define WM5100_EQ4_B3_GAIN_MASK 0x003E /* EQ4_B3_GAIN - [5:1] */ +#define WM5100_EQ4_B3_GAIN_SHIFT 1 /* EQ4_B3_GAIN - [5:1] */ +#define WM5100_EQ4_B3_GAIN_WIDTH 5 /* EQ4_B3_GAIN - [5:1] */ +#define WM5100_EQ4_ENA 0x0001 /* EQ4_ENA */ +#define WM5100_EQ4_ENA_MASK 0x0001 /* EQ4_ENA */ +#define WM5100_EQ4_ENA_SHIFT 0 /* EQ4_ENA */ +#define WM5100_EQ4_ENA_WIDTH 1 /* EQ4_ENA */ + +/* + * R3667 (0xE53) - EQ4_2 + */ +#define WM5100_EQ4_B4_GAIN_MASK 0xF800 /* EQ4_B4_GAIN - [15:11] */ +#define WM5100_EQ4_B4_GAIN_SHIFT 11 /* EQ4_B4_GAIN - [15:11] */ +#define WM5100_EQ4_B4_GAIN_WIDTH 5 /* EQ4_B4_GAIN - [15:11] */ +#define WM5100_EQ4_B5_GAIN_MASK 0x07C0 /* EQ4_B5_GAIN - [10:6] */ +#define WM5100_EQ4_B5_GAIN_SHIFT 6 /* EQ4_B5_GAIN - [10:6] */ +#define WM5100_EQ4_B5_GAIN_WIDTH 5 /* EQ4_B5_GAIN - [10:6] */ + +/* + * R3668 (0xE54) - EQ4_3 + */ +#define WM5100_EQ4_B1_A_MASK 0xFFFF /* EQ4_B1_A - [15:0] */ +#define WM5100_EQ4_B1_A_SHIFT 0 /* EQ4_B1_A - [15:0] */ +#define WM5100_EQ4_B1_A_WIDTH 16 /* EQ4_B1_A - [15:0] */ + +/* + * R3669 (0xE55) - EQ4_4 + */ +#define WM5100_EQ4_B1_B_MASK 0xFFFF /* EQ4_B1_B - [15:0] */ +#define WM5100_EQ4_B1_B_SHIFT 0 /* EQ4_B1_B - [15:0] */ +#define WM5100_EQ4_B1_B_WIDTH 16 /* EQ4_B1_B - [15:0] */ + +/* + * R3670 (0xE56) - EQ4_5 + */ +#define WM5100_EQ4_B1_PG_MASK 0xFFFF /* EQ4_B1_PG - [15:0] */ +#define WM5100_EQ4_B1_PG_SHIFT 0 /* EQ4_B1_PG - [15:0] */ +#define WM5100_EQ4_B1_PG_WIDTH 16 /* EQ4_B1_PG - [15:0] */ + +/* + * R3671 (0xE57) - EQ4_6 + */ +#define WM5100_EQ4_B2_A_MASK 0xFFFF /* EQ4_B2_A - [15:0] */ +#define WM5100_EQ4_B2_A_SHIFT 0 /* EQ4_B2_A - [15:0] */ +#define WM5100_EQ4_B2_A_WIDTH 16 /* EQ4_B2_A - [15:0] */ + +/* + * R3672 (0xE58) - EQ4_7 + */ +#define WM5100_EQ4_B2_B_MASK 0xFFFF /* EQ4_B2_B - [15:0] */ +#define WM5100_EQ4_B2_B_SHIFT 0 /* EQ4_B2_B - [15:0] */ +#define WM5100_EQ4_B2_B_WIDTH 16 /* EQ4_B2_B - [15:0] */ + +/* + * R3673 (0xE59) - EQ4_8 + */ +#define WM5100_EQ4_B2_C_MASK 0xFFFF /* EQ4_B2_C - [15:0] */ +#define WM5100_EQ4_B2_C_SHIFT 0 /* EQ4_B2_C - [15:0] */ +#define WM5100_EQ4_B2_C_WIDTH 16 /* EQ4_B2_C - [15:0] */ + +/* + * R3674 (0xE5A) - EQ4_9 + */ +#define WM5100_EQ4_B2_PG_MASK 0xFFFF /* EQ4_B2_PG - [15:0] */ +#define WM5100_EQ4_B2_PG_SHIFT 0 /* EQ4_B2_PG - [15:0] */ +#define WM5100_EQ4_B2_PG_WIDTH 16 /* EQ4_B2_PG - [15:0] */ + +/* + * R3675 (0xE5B) - EQ4_10 + */ +#define WM5100_EQ4_B3_A_MASK 0xFFFF /* EQ4_B3_A - [15:0] */ +#define WM5100_EQ4_B3_A_SHIFT 0 /* EQ4_B3_A - [15:0] */ +#define WM5100_EQ4_B3_A_WIDTH 16 /* EQ4_B3_A - [15:0] */ + +/* + * R3676 (0xE5C) - EQ4_11 + */ +#define WM5100_EQ4_B3_B_MASK 0xFFFF /* EQ4_B3_B - [15:0] */ +#define WM5100_EQ4_B3_B_SHIFT 0 /* EQ4_B3_B - [15:0] */ +#define WM5100_EQ4_B3_B_WIDTH 16 /* EQ4_B3_B - [15:0] */ + +/* + * R3677 (0xE5D) - EQ4_12 + */ +#define WM5100_EQ4_B3_C_MASK 0xFFFF /* EQ4_B3_C - [15:0] */ +#define WM5100_EQ4_B3_C_SHIFT 0 /* EQ4_B3_C - [15:0] */ +#define WM5100_EQ4_B3_C_WIDTH 16 /* EQ4_B3_C - [15:0] */ + +/* + * R3678 (0xE5E) - EQ4_13 + */ +#define WM5100_EQ4_B3_PG_MASK 0xFFFF /* EQ4_B3_PG - [15:0] */ +#define WM5100_EQ4_B3_PG_SHIFT 0 /* EQ4_B3_PG - [15:0] */ +#define WM5100_EQ4_B3_PG_WIDTH 16 /* EQ4_B3_PG - [15:0] */ + +/* + * R3679 (0xE5F) - EQ4_14 + */ +#define WM5100_EQ4_B4_A_MASK 0xFFFF /* EQ4_B4_A - [15:0] */ +#define WM5100_EQ4_B4_A_SHIFT 0 /* EQ4_B4_A - [15:0] */ +#define WM5100_EQ4_B4_A_WIDTH 16 /* EQ4_B4_A - [15:0] */ + +/* + * R3680 (0xE60) - EQ4_15 + */ +#define WM5100_EQ4_B4_B_MASK 0xFFFF /* EQ4_B4_B - [15:0] */ +#define WM5100_EQ4_B4_B_SHIFT 0 /* EQ4_B4_B - [15:0] */ +#define WM5100_EQ4_B4_B_WIDTH 16 /* EQ4_B4_B - [15:0] */ + +/* + * R3681 (0xE61) - EQ4_16 + */ +#define WM5100_EQ4_B4_C_MASK 0xFFFF /* EQ4_B4_C - [15:0] */ +#define WM5100_EQ4_B4_C_SHIFT 0 /* EQ4_B4_C - [15:0] */ +#define WM5100_EQ4_B4_C_WIDTH 16 /* EQ4_B4_C - [15:0] */ + +/* + * R3682 (0xE62) - EQ4_17 + */ +#define WM5100_EQ4_B4_PG_MASK 0xFFFF /* EQ4_B4_PG - [15:0] */ +#define WM5100_EQ4_B4_PG_SHIFT 0 /* EQ4_B4_PG - [15:0] */ +#define WM5100_EQ4_B4_PG_WIDTH 16 /* EQ4_B4_PG - [15:0] */ + +/* + * R3683 (0xE63) - EQ4_18 + */ +#define WM5100_EQ4_B5_A_MASK 0xFFFF /* EQ4_B5_A - [15:0] */ +#define WM5100_EQ4_B5_A_SHIFT 0 /* EQ4_B5_A - [15:0] */ +#define WM5100_EQ4_B5_A_WIDTH 16 /* EQ4_B5_A - [15:0] */ + +/* + * R3684 (0xE64) - EQ4_19 + */ +#define WM5100_EQ4_B5_B_MASK 0xFFFF /* EQ4_B5_B - [15:0] */ +#define WM5100_EQ4_B5_B_SHIFT 0 /* EQ4_B5_B - [15:0] */ +#define WM5100_EQ4_B5_B_WIDTH 16 /* EQ4_B5_B - [15:0] */ + +/* + * R3685 (0xE65) - EQ4_20 + */ +#define WM5100_EQ4_B5_PG_MASK 0xFFFF /* EQ4_B5_PG - [15:0] */ +#define WM5100_EQ4_B5_PG_SHIFT 0 /* EQ4_B5_PG - [15:0] */ +#define WM5100_EQ4_B5_PG_WIDTH 16 /* EQ4_B5_PG - [15:0] */ + +/* + * R3712 (0xE80) - DRC1 ctrl1 + */ +#define WM5100_DRC_SIG_DET_RMS_MASK 0xF800 /* DRC_SIG_DET_RMS - [15:11] */ +#define WM5100_DRC_SIG_DET_RMS_SHIFT 11 /* DRC_SIG_DET_RMS - [15:11] */ +#define WM5100_DRC_SIG_DET_RMS_WIDTH 5 /* DRC_SIG_DET_RMS - [15:11] */ +#define WM5100_DRC_SIG_DET_PK_MASK 0x0600 /* DRC_SIG_DET_PK - [10:9] */ +#define WM5100_DRC_SIG_DET_PK_SHIFT 9 /* DRC_SIG_DET_PK - [10:9] */ +#define WM5100_DRC_SIG_DET_PK_WIDTH 2 /* DRC_SIG_DET_PK - [10:9] */ +#define WM5100_DRC_NG_ENA 0x0100 /* DRC_NG_ENA */ +#define WM5100_DRC_NG_ENA_MASK 0x0100 /* DRC_NG_ENA */ +#define WM5100_DRC_NG_ENA_SHIFT 8 /* DRC_NG_ENA */ +#define WM5100_DRC_NG_ENA_WIDTH 1 /* DRC_NG_ENA */ +#define WM5100_DRC_SIG_DET_MODE 0x0080 /* DRC_SIG_DET_MODE */ +#define WM5100_DRC_SIG_DET_MODE_MASK 0x0080 /* DRC_SIG_DET_MODE */ +#define WM5100_DRC_SIG_DET_MODE_SHIFT 7 /* DRC_SIG_DET_MODE */ +#define WM5100_DRC_SIG_DET_MODE_WIDTH 1 /* DRC_SIG_DET_MODE */ +#define WM5100_DRC_SIG_DET 0x0040 /* DRC_SIG_DET */ +#define WM5100_DRC_SIG_DET_MASK 0x0040 /* DRC_SIG_DET */ +#define WM5100_DRC_SIG_DET_SHIFT 6 /* DRC_SIG_DET */ +#define WM5100_DRC_SIG_DET_WIDTH 1 /* DRC_SIG_DET */ +#define WM5100_DRC_KNEE2_OP_ENA 0x0020 /* DRC_KNEE2_OP_ENA */ +#define WM5100_DRC_KNEE2_OP_ENA_MASK 0x0020 /* DRC_KNEE2_OP_ENA */ +#define WM5100_DRC_KNEE2_OP_ENA_SHIFT 5 /* DRC_KNEE2_OP_ENA */ +#define WM5100_DRC_KNEE2_OP_ENA_WIDTH 1 /* DRC_KNEE2_OP_ENA */ +#define WM5100_DRC_QR 0x0010 /* DRC_QR */ +#define WM5100_DRC_QR_MASK 0x0010 /* DRC_QR */ +#define WM5100_DRC_QR_SHIFT 4 /* DRC_QR */ +#define WM5100_DRC_QR_WIDTH 1 /* DRC_QR */ +#define WM5100_DRC_ANTICLIP 0x0008 /* DRC_ANTICLIP */ +#define WM5100_DRC_ANTICLIP_MASK 0x0008 /* DRC_ANTICLIP */ +#define WM5100_DRC_ANTICLIP_SHIFT 3 /* DRC_ANTICLIP */ +#define WM5100_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */ +#define WM5100_DRCL_ENA 0x0002 /* DRCL_ENA */ +#define WM5100_DRCL_ENA_MASK 0x0002 /* DRCL_ENA */ +#define WM5100_DRCL_ENA_SHIFT 1 /* DRCL_ENA */ +#define WM5100_DRCL_ENA_WIDTH 1 /* DRCL_ENA */ +#define WM5100_DRCR_ENA 0x0001 /* DRCR_ENA */ +#define WM5100_DRCR_ENA_MASK 0x0001 /* DRCR_ENA */ +#define WM5100_DRCR_ENA_SHIFT 0 /* DRCR_ENA */ +#define WM5100_DRCR_ENA_WIDTH 1 /* DRCR_ENA */ + +/* + * R3713 (0xE81) - DRC1 ctrl2 + */ +#define WM5100_DRC_ATK_MASK 0x1E00 /* DRC_ATK - [12:9] */ +#define WM5100_DRC_ATK_SHIFT 9 /* DRC_ATK - [12:9] */ +#define WM5100_DRC_ATK_WIDTH 4 /* DRC_ATK - [12:9] */ +#define WM5100_DRC_DCY_MASK 0x01E0 /* DRC_DCY - [8:5] */ +#define WM5100_DRC_DCY_SHIFT 5 /* DRC_DCY - [8:5] */ +#define WM5100_DRC_DCY_WIDTH 4 /* DRC_DCY - [8:5] */ +#define WM5100_DRC_MINGAIN_MASK 0x001C /* DRC_MINGAIN - [4:2] */ +#define WM5100_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [4:2] */ +#define WM5100_DRC_MINGAIN_WIDTH 3 /* DRC_MINGAIN - [4:2] */ +#define WM5100_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM5100_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM5100_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R3714 (0xE82) - DRC1 ctrl3 + */ +#define WM5100_DRC_NG_MINGAIN_MASK 0xF000 /* DRC_NG_MINGAIN - [15:12] */ +#define WM5100_DRC_NG_MINGAIN_SHIFT 12 /* DRC_NG_MINGAIN - [15:12] */ +#define WM5100_DRC_NG_MINGAIN_WIDTH 4 /* DRC_NG_MINGAIN - [15:12] */ +#define WM5100_DRC_NG_EXP_MASK 0x0C00 /* DRC_NG_EXP - [11:10] */ +#define WM5100_DRC_NG_EXP_SHIFT 10 /* DRC_NG_EXP - [11:10] */ +#define WM5100_DRC_NG_EXP_WIDTH 2 /* DRC_NG_EXP - [11:10] */ +#define WM5100_DRC_QR_THR_MASK 0x0300 /* DRC_QR_THR - [9:8] */ +#define WM5100_DRC_QR_THR_SHIFT 8 /* DRC_QR_THR - [9:8] */ +#define WM5100_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [9:8] */ +#define WM5100_DRC_QR_DCY_MASK 0x00C0 /* DRC_QR_DCY - [7:6] */ +#define WM5100_DRC_QR_DCY_SHIFT 6 /* DRC_QR_DCY - [7:6] */ +#define WM5100_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [7:6] */ +#define WM5100_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */ +#define WM5100_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */ +#define WM5100_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */ +#define WM5100_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */ +#define WM5100_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */ +#define WM5100_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */ + +/* + * R3715 (0xE83) - DRC1 ctrl4 + */ +#define WM5100_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */ +#define WM5100_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */ +#define WM5100_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */ +#define WM5100_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */ +#define WM5100_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */ +#define WM5100_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */ + +/* + * R3716 (0xE84) - DRC1 ctrl5 + */ +#define WM5100_DRC_KNEE2_IP_MASK 0x03E0 /* DRC_KNEE2_IP - [9:5] */ +#define WM5100_DRC_KNEE2_IP_SHIFT 5 /* DRC_KNEE2_IP - [9:5] */ +#define WM5100_DRC_KNEE2_IP_WIDTH 5 /* DRC_KNEE2_IP - [9:5] */ +#define WM5100_DRC_KNEE2_OP_MASK 0x001F /* DRC_KNEE2_OP - [4:0] */ +#define WM5100_DRC_KNEE2_OP_SHIFT 0 /* DRC_KNEE2_OP - [4:0] */ +#define WM5100_DRC_KNEE2_OP_WIDTH 5 /* DRC_KNEE2_OP - [4:0] */ + +/* + * R3776 (0xEC0) - HPLPF1_1 + */ +#define WM5100_LHPF1_MODE 0x0002 /* LHPF1_MODE */ +#define WM5100_LHPF1_MODE_MASK 0x0002 /* LHPF1_MODE */ +#define WM5100_LHPF1_MODE_SHIFT 1 /* LHPF1_MODE */ +#define WM5100_LHPF1_MODE_WIDTH 1 /* LHPF1_MODE */ +#define WM5100_LHPF1_ENA 0x0001 /* LHPF1_ENA */ +#define WM5100_LHPF1_ENA_MASK 0x0001 /* LHPF1_ENA */ +#define WM5100_LHPF1_ENA_SHIFT 0 /* LHPF1_ENA */ +#define WM5100_LHPF1_ENA_WIDTH 1 /* LHPF1_ENA */ + +/* + * R3777 (0xEC1) - HPLPF1_2 + */ +#define WM5100_LHPF1_COEFF_MASK 0xFFFF /* LHPF1_COEFF - [15:0] */ +#define WM5100_LHPF1_COEFF_SHIFT 0 /* LHPF1_COEFF - [15:0] */ +#define WM5100_LHPF1_COEFF_WIDTH 16 /* LHPF1_COEFF - [15:0] */ + +/* + * R3780 (0xEC4) - HPLPF2_1 + */ +#define WM5100_LHPF2_MODE 0x0002 /* LHPF2_MODE */ +#define WM5100_LHPF2_MODE_MASK 0x0002 /* LHPF2_MODE */ +#define WM5100_LHPF2_MODE_SHIFT 1 /* LHPF2_MODE */ +#define WM5100_LHPF2_MODE_WIDTH 1 /* LHPF2_MODE */ +#define WM5100_LHPF2_ENA 0x0001 /* LHPF2_ENA */ +#define WM5100_LHPF2_ENA_MASK 0x0001 /* LHPF2_ENA */ +#define WM5100_LHPF2_ENA_SHIFT 0 /* LHPF2_ENA */ +#define WM5100_LHPF2_ENA_WIDTH 1 /* LHPF2_ENA */ + +/* + * R3781 (0xEC5) - HPLPF2_2 + */ +#define WM5100_LHPF2_COEFF_MASK 0xFFFF /* LHPF2_COEFF - [15:0] */ +#define WM5100_LHPF2_COEFF_SHIFT 0 /* LHPF2_COEFF - [15:0] */ +#define WM5100_LHPF2_COEFF_WIDTH 16 /* LHPF2_COEFF - [15:0] */ + +/* + * R3784 (0xEC8) - HPLPF3_1 + */ +#define WM5100_LHPF3_MODE 0x0002 /* LHPF3_MODE */ +#define WM5100_LHPF3_MODE_MASK 0x0002 /* LHPF3_MODE */ +#define WM5100_LHPF3_MODE_SHIFT 1 /* LHPF3_MODE */ +#define WM5100_LHPF3_MODE_WIDTH 1 /* LHPF3_MODE */ +#define WM5100_LHPF3_ENA 0x0001 /* LHPF3_ENA */ +#define WM5100_LHPF3_ENA_MASK 0x0001 /* LHPF3_ENA */ +#define WM5100_LHPF3_ENA_SHIFT 0 /* LHPF3_ENA */ +#define WM5100_LHPF3_ENA_WIDTH 1 /* LHPF3_ENA */ + +/* + * R3785 (0xEC9) - HPLPF3_2 + */ +#define WM5100_LHPF3_COEFF_MASK 0xFFFF /* LHPF3_COEFF - [15:0] */ +#define WM5100_LHPF3_COEFF_SHIFT 0 /* LHPF3_COEFF - [15:0] */ +#define WM5100_LHPF3_COEFF_WIDTH 16 /* LHPF3_COEFF - [15:0] */ + +/* + * R3788 (0xECC) - HPLPF4_1 + */ +#define WM5100_LHPF4_MODE 0x0002 /* LHPF4_MODE */ +#define WM5100_LHPF4_MODE_MASK 0x0002 /* LHPF4_MODE */ +#define WM5100_LHPF4_MODE_SHIFT 1 /* LHPF4_MODE */ +#define WM5100_LHPF4_MODE_WIDTH 1 /* LHPF4_MODE */ +#define WM5100_LHPF4_ENA 0x0001 /* LHPF4_ENA */ +#define WM5100_LHPF4_ENA_MASK 0x0001 /* LHPF4_ENA */ +#define WM5100_LHPF4_ENA_SHIFT 0 /* LHPF4_ENA */ +#define WM5100_LHPF4_ENA_WIDTH 1 /* LHPF4_ENA */ + +/* + * R3789 (0xECD) - HPLPF4_2 + */ +#define WM5100_LHPF4_COEFF_MASK 0xFFFF /* LHPF4_COEFF - [15:0] */ +#define WM5100_LHPF4_COEFF_SHIFT 0 /* LHPF4_COEFF - [15:0] */ +#define WM5100_LHPF4_COEFF_WIDTH 16 /* LHPF4_COEFF - [15:0] */ + +/* + * R4132 (0x1024) - DSP2 Control 30 + */ +#define WM5100_DSP2_RATE_MASK 0xC000 /* DSP2_RATE - [15:14] */ +#define WM5100_DSP2_RATE_SHIFT 14 /* DSP2_RATE - [15:14] */ +#define WM5100_DSP2_RATE_WIDTH 2 /* DSP2_RATE - [15:14] */ +#define WM5100_DSP2_DBG_CLK_ENA 0x0008 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_DBG_CLK_ENA_MASK 0x0008 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_DBG_CLK_ENA_SHIFT 3 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_DBG_CLK_ENA_WIDTH 1 /* DSP2_DBG_CLK_ENA */ +#define WM5100_DSP2_SYS_ENA 0x0004 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_SYS_ENA_MASK 0x0004 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_SYS_ENA_SHIFT 2 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_SYS_ENA_WIDTH 1 /* DSP2_SYS_ENA */ +#define WM5100_DSP2_CORE_ENA 0x0002 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_CORE_ENA_MASK 0x0002 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_CORE_ENA_SHIFT 1 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_CORE_ENA_WIDTH 1 /* DSP2_CORE_ENA */ +#define WM5100_DSP2_START 0x0001 /* DSP2_START */ +#define WM5100_DSP2_START_MASK 0x0001 /* DSP2_START */ +#define WM5100_DSP2_START_SHIFT 0 /* DSP2_START */ +#define WM5100_DSP2_START_WIDTH 1 /* DSP2_START */ + +/* + * R3876 (0xF24) - DSP1 Control 30 + */ +#define WM5100_DSP1_RATE_MASK 0xC000 /* DSP1_RATE - [15:14] */ +#define WM5100_DSP1_RATE_SHIFT 14 /* DSP1_RATE - [15:14] */ +#define WM5100_DSP1_RATE_WIDTH 2 /* DSP1_RATE - [15:14] */ +#define WM5100_DSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */ +#define WM5100_DSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ +#define WM5100_DSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ +#define WM5100_DSP1_START 0x0001 /* DSP1_START */ +#define WM5100_DSP1_START_MASK 0x0001 /* DSP1_START */ +#define WM5100_DSP1_START_SHIFT 0 /* DSP1_START */ +#define WM5100_DSP1_START_WIDTH 1 /* DSP1_START */ + +/* + * R4388 (0x1124) - DSP3 Control 30 + */ +#define WM5100_DSP3_RATE_MASK 0xC000 /* DSP3_RATE - [15:14] */ +#define WM5100_DSP3_RATE_SHIFT 14 /* DSP3_RATE - [15:14] */ +#define WM5100_DSP3_RATE_WIDTH 2 /* DSP3_RATE - [15:14] */ +#define WM5100_DSP3_DBG_CLK_ENA 0x0008 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_DBG_CLK_ENA_MASK 0x0008 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_DBG_CLK_ENA_SHIFT 3 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_DBG_CLK_ENA_WIDTH 1 /* DSP3_DBG_CLK_ENA */ +#define WM5100_DSP3_SYS_ENA 0x0004 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_SYS_ENA_MASK 0x0004 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_SYS_ENA_SHIFT 2 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_SYS_ENA_WIDTH 1 /* DSP3_SYS_ENA */ +#define WM5100_DSP3_CORE_ENA 0x0002 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_CORE_ENA_MASK 0x0002 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_CORE_ENA_SHIFT 1 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_CORE_ENA_WIDTH 1 /* DSP3_CORE_ENA */ +#define WM5100_DSP3_START 0x0001 /* DSP3_START */ +#define WM5100_DSP3_START_MASK 0x0001 /* DSP3_START */ +#define WM5100_DSP3_START_SHIFT 0 /* DSP3_START */ +#define WM5100_DSP3_START_WIDTH 1 /* DSP3_START */ + +/* + * R16384 (0x4000) - DSP1 DM 0 + */ +#define WM5100_DSP1_DM_START_1_MASK 0x00FF /* DSP1_DM_START - [7:0] */ +#define WM5100_DSP1_DM_START_1_SHIFT 0 /* DSP1_DM_START - [7:0] */ +#define WM5100_DSP1_DM_START_1_WIDTH 8 /* DSP1_DM_START - [7:0] */ + +/* + * R16385 (0x4001) - DSP1 DM 1 + */ +#define WM5100_DSP1_DM_START_MASK 0xFFFF /* DSP1_DM_START - [15:0] */ +#define WM5100_DSP1_DM_START_SHIFT 0 /* DSP1_DM_START - [15:0] */ +#define WM5100_DSP1_DM_START_WIDTH 16 /* DSP1_DM_START - [15:0] */ + +/* + * R16386 (0x4002) - DSP1 DM 2 + */ +#define WM5100_DSP1_DM_1_1_MASK 0x00FF /* DSP1_DM_1 - [7:0] */ +#define WM5100_DSP1_DM_1_1_SHIFT 0 /* DSP1_DM_1 - [7:0] */ +#define WM5100_DSP1_DM_1_1_WIDTH 8 /* DSP1_DM_1 - [7:0] */ + +/* + * R16387 (0x4003) - DSP1 DM 3 + */ +#define WM5100_DSP1_DM_1_MASK 0xFFFF /* DSP1_DM_1 - [15:0] */ +#define WM5100_DSP1_DM_1_SHIFT 0 /* DSP1_DM_1 - [15:0] */ +#define WM5100_DSP1_DM_1_WIDTH 16 /* DSP1_DM_1 - [15:0] */ + +/* + * R16892 (0x41FC) - DSP1 DM 508 + */ +#define WM5100_DSP1_DM_254_1_MASK 0x00FF /* DSP1_DM_254 - [7:0] */ +#define WM5100_DSP1_DM_254_1_SHIFT 0 /* DSP1_DM_254 - [7:0] */ +#define WM5100_DSP1_DM_254_1_WIDTH 8 /* DSP1_DM_254 - [7:0] */ + +/* + * R16893 (0x41FD) - DSP1 DM 509 + */ +#define WM5100_DSP1_DM_254_MASK 0xFFFF /* DSP1_DM_254 - [15:0] */ +#define WM5100_DSP1_DM_254_SHIFT 0 /* DSP1_DM_254 - [15:0] */ +#define WM5100_DSP1_DM_254_WIDTH 16 /* DSP1_DM_254 - [15:0] */ + +/* + * R16894 (0x41FE) - DSP1 DM 510 + */ +#define WM5100_DSP1_DM_END_1_MASK 0x00FF /* DSP1_DM_END - [7:0] */ +#define WM5100_DSP1_DM_END_1_SHIFT 0 /* DSP1_DM_END - [7:0] */ +#define WM5100_DSP1_DM_END_1_WIDTH 8 /* DSP1_DM_END - [7:0] */ + +/* + * R16895 (0x41FF) - DSP1 DM 511 + */ +#define WM5100_DSP1_DM_END_MASK 0xFFFF /* DSP1_DM_END - [15:0] */ +#define WM5100_DSP1_DM_END_SHIFT 0 /* DSP1_DM_END - [15:0] */ +#define WM5100_DSP1_DM_END_WIDTH 16 /* DSP1_DM_END - [15:0] */ + +/* + * R18432 (0x4800) - DSP1 PM 0 + */ +#define WM5100_DSP1_PM_START_2_MASK 0x00FF /* DSP1_PM_START - [7:0] */ +#define WM5100_DSP1_PM_START_2_SHIFT 0 /* DSP1_PM_START - [7:0] */ +#define WM5100_DSP1_PM_START_2_WIDTH 8 /* DSP1_PM_START - [7:0] */ + +/* + * R18433 (0x4801) - DSP1 PM 1 + */ +#define WM5100_DSP1_PM_START_1_MASK 0xFFFF /* DSP1_PM_START - [15:0] */ +#define WM5100_DSP1_PM_START_1_SHIFT 0 /* DSP1_PM_START - [15:0] */ +#define WM5100_DSP1_PM_START_1_WIDTH 16 /* DSP1_PM_START - [15:0] */ + +/* + * R18434 (0x4802) - DSP1 PM 2 + */ +#define WM5100_DSP1_PM_START_MASK 0xFFFF /* DSP1_PM_START - [15:0] */ +#define WM5100_DSP1_PM_START_SHIFT 0 /* DSP1_PM_START - [15:0] */ +#define WM5100_DSP1_PM_START_WIDTH 16 /* DSP1_PM_START - [15:0] */ + +/* + * R18435 (0x4803) - DSP1 PM 3 + */ +#define WM5100_DSP1_PM_1_2_MASK 0x00FF /* DSP1_PM_1 - [7:0] */ +#define WM5100_DSP1_PM_1_2_SHIFT 0 /* DSP1_PM_1 - [7:0] */ +#define WM5100_DSP1_PM_1_2_WIDTH 8 /* DSP1_PM_1 - [7:0] */ + +/* + * R18436 (0x4804) - DSP1 PM 4 + */ +#define WM5100_DSP1_PM_1_1_MASK 0xFFFF /* DSP1_PM_1 - [15:0] */ +#define WM5100_DSP1_PM_1_1_SHIFT 0 /* DSP1_PM_1 - [15:0] */ +#define WM5100_DSP1_PM_1_1_WIDTH 16 /* DSP1_PM_1 - [15:0] */ + +/* + * R18437 (0x4805) - DSP1 PM 5 + */ +#define WM5100_DSP1_PM_1_MASK 0xFFFF /* DSP1_PM_1 - [15:0] */ +#define WM5100_DSP1_PM_1_SHIFT 0 /* DSP1_PM_1 - [15:0] */ +#define WM5100_DSP1_PM_1_WIDTH 16 /* DSP1_PM_1 - [15:0] */ + +/* + * R19962 (0x4DFA) - DSP1 PM 1530 + */ +#define WM5100_DSP1_PM_510_2_MASK 0x00FF /* DSP1_PM_510 - [7:0] */ +#define WM5100_DSP1_PM_510_2_SHIFT 0 /* DSP1_PM_510 - [7:0] */ +#define WM5100_DSP1_PM_510_2_WIDTH 8 /* DSP1_PM_510 - [7:0] */ + +/* + * R19963 (0x4DFB) - DSP1 PM 1531 + */ +#define WM5100_DSP1_PM_510_1_MASK 0xFFFF /* DSP1_PM_510 - [15:0] */ +#define WM5100_DSP1_PM_510_1_SHIFT 0 /* DSP1_PM_510 - [15:0] */ +#define WM5100_DSP1_PM_510_1_WIDTH 16 /* DSP1_PM_510 - [15:0] */ + +/* + * R19964 (0x4DFC) - DSP1 PM 1532 + */ +#define WM5100_DSP1_PM_510_MASK 0xFFFF /* DSP1_PM_510 - [15:0] */ +#define WM5100_DSP1_PM_510_SHIFT 0 /* DSP1_PM_510 - [15:0] */ +#define WM5100_DSP1_PM_510_WIDTH 16 /* DSP1_PM_510 - [15:0] */ + +/* + * R19965 (0x4DFD) - DSP1 PM 1533 + */ +#define WM5100_DSP1_PM_END_2_MASK 0x00FF /* DSP1_PM_END - [7:0] */ +#define WM5100_DSP1_PM_END_2_SHIFT 0 /* DSP1_PM_END - [7:0] */ +#define WM5100_DSP1_PM_END_2_WIDTH 8 /* DSP1_PM_END - [7:0] */ + +/* + * R19966 (0x4DFE) - DSP1 PM 1534 + */ +#define WM5100_DSP1_PM_END_1_MASK 0xFFFF /* DSP1_PM_END - [15:0] */ +#define WM5100_DSP1_PM_END_1_SHIFT 0 /* DSP1_PM_END - [15:0] */ +#define WM5100_DSP1_PM_END_1_WIDTH 16 /* DSP1_PM_END - [15:0] */ + +/* + * R19967 (0x4DFF) - DSP1 PM 1535 + */ +#define WM5100_DSP1_PM_END_MASK 0xFFFF /* DSP1_PM_END - [15:0] */ +#define WM5100_DSP1_PM_END_SHIFT 0 /* DSP1_PM_END - [15:0] */ +#define WM5100_DSP1_PM_END_WIDTH 16 /* DSP1_PM_END - [15:0] */ + +/* + * R20480 (0x5000) - DSP1 ZM 0 + */ +#define WM5100_DSP1_ZM_START_1_MASK 0x00FF /* DSP1_ZM_START - [7:0] */ +#define WM5100_DSP1_ZM_START_1_SHIFT 0 /* DSP1_ZM_START - [7:0] */ +#define WM5100_DSP1_ZM_START_1_WIDTH 8 /* DSP1_ZM_START - [7:0] */ + +/* + * R20481 (0x5001) - DSP1 ZM 1 + */ +#define WM5100_DSP1_ZM_START_MASK 0xFFFF /* DSP1_ZM_START - [15:0] */ +#define WM5100_DSP1_ZM_START_SHIFT 0 /* DSP1_ZM_START - [15:0] */ +#define WM5100_DSP1_ZM_START_WIDTH 16 /* DSP1_ZM_START - [15:0] */ + +/* + * R20482 (0x5002) - DSP1 ZM 2 + */ +#define WM5100_DSP1_ZM_1_1_MASK 0x00FF /* DSP1_ZM_1 - [7:0] */ +#define WM5100_DSP1_ZM_1_1_SHIFT 0 /* DSP1_ZM_1 - [7:0] */ +#define WM5100_DSP1_ZM_1_1_WIDTH 8 /* DSP1_ZM_1 - [7:0] */ + +/* + * R20483 (0x5003) - DSP1 ZM 3 + */ +#define WM5100_DSP1_ZM_1_MASK 0xFFFF /* DSP1_ZM_1 - [15:0] */ +#define WM5100_DSP1_ZM_1_SHIFT 0 /* DSP1_ZM_1 - [15:0] */ +#define WM5100_DSP1_ZM_1_WIDTH 16 /* DSP1_ZM_1 - [15:0] */ + +/* + * R22524 (0x57FC) - DSP1 ZM 2044 + */ +#define WM5100_DSP1_ZM_1022_1_MASK 0x00FF /* DSP1_ZM_1022 - [7:0] */ +#define WM5100_DSP1_ZM_1022_1_SHIFT 0 /* DSP1_ZM_1022 - [7:0] */ +#define WM5100_DSP1_ZM_1022_1_WIDTH 8 /* DSP1_ZM_1022 - [7:0] */ + +/* + * R22525 (0x57FD) - DSP1 ZM 2045 + */ +#define WM5100_DSP1_ZM_1022_MASK 0xFFFF /* DSP1_ZM_1022 - [15:0] */ +#define WM5100_DSP1_ZM_1022_SHIFT 0 /* DSP1_ZM_1022 - [15:0] */ +#define WM5100_DSP1_ZM_1022_WIDTH 16 /* DSP1_ZM_1022 - [15:0] */ + +/* + * R22526 (0x57FE) - DSP1 ZM 2046 + */ +#define WM5100_DSP1_ZM_END_1_MASK 0x00FF /* DSP1_ZM_END - [7:0] */ +#define WM5100_DSP1_ZM_END_1_SHIFT 0 /* DSP1_ZM_END - [7:0] */ +#define WM5100_DSP1_ZM_END_1_WIDTH 8 /* DSP1_ZM_END - [7:0] */ + +/* + * R22527 (0x57FF) - DSP1 ZM 2047 + */ +#define WM5100_DSP1_ZM_END_MASK 0xFFFF /* DSP1_ZM_END - [15:0] */ +#define WM5100_DSP1_ZM_END_SHIFT 0 /* DSP1_ZM_END - [15:0] */ +#define WM5100_DSP1_ZM_END_WIDTH 16 /* DSP1_ZM_END - [15:0] */ + +/* + * R24576 (0x6000) - DSP2 DM 0 + */ +#define WM5100_DSP2_DM_START_1_MASK 0x00FF /* DSP2_DM_START - [7:0] */ +#define WM5100_DSP2_DM_START_1_SHIFT 0 /* DSP2_DM_START - [7:0] */ +#define WM5100_DSP2_DM_START_1_WIDTH 8 /* DSP2_DM_START - [7:0] */ + +/* + * R24577 (0x6001) - DSP2 DM 1 + */ +#define WM5100_DSP2_DM_START_MASK 0xFFFF /* DSP2_DM_START - [15:0] */ +#define WM5100_DSP2_DM_START_SHIFT 0 /* DSP2_DM_START - [15:0] */ +#define WM5100_DSP2_DM_START_WIDTH 16 /* DSP2_DM_START - [15:0] */ + +/* + * R24578 (0x6002) - DSP2 DM 2 + */ +#define WM5100_DSP2_DM_1_1_MASK 0x00FF /* DSP2_DM_1 - [7:0] */ +#define WM5100_DSP2_DM_1_1_SHIFT 0 /* DSP2_DM_1 - [7:0] */ +#define WM5100_DSP2_DM_1_1_WIDTH 8 /* DSP2_DM_1 - [7:0] */ + +/* + * R24579 (0x6003) - DSP2 DM 3 + */ +#define WM5100_DSP2_DM_1_MASK 0xFFFF /* DSP2_DM_1 - [15:0] */ +#define WM5100_DSP2_DM_1_SHIFT 0 /* DSP2_DM_1 - [15:0] */ +#define WM5100_DSP2_DM_1_WIDTH 16 /* DSP2_DM_1 - [15:0] */ + +/* + * R25084 (0x61FC) - DSP2 DM 508 + */ +#define WM5100_DSP2_DM_254_1_MASK 0x00FF /* DSP2_DM_254 - [7:0] */ +#define WM5100_DSP2_DM_254_1_SHIFT 0 /* DSP2_DM_254 - [7:0] */ +#define WM5100_DSP2_DM_254_1_WIDTH 8 /* DSP2_DM_254 - [7:0] */ + +/* + * R25085 (0x61FD) - DSP2 DM 509 + */ +#define WM5100_DSP2_DM_254_MASK 0xFFFF /* DSP2_DM_254 - [15:0] */ +#define WM5100_DSP2_DM_254_SHIFT 0 /* DSP2_DM_254 - [15:0] */ +#define WM5100_DSP2_DM_254_WIDTH 16 /* DSP2_DM_254 - [15:0] */ + +/* + * R25086 (0x61FE) - DSP2 DM 510 + */ +#define WM5100_DSP2_DM_END_1_MASK 0x00FF /* DSP2_DM_END - [7:0] */ +#define WM5100_DSP2_DM_END_1_SHIFT 0 /* DSP2_DM_END - [7:0] */ +#define WM5100_DSP2_DM_END_1_WIDTH 8 /* DSP2_DM_END - [7:0] */ + +/* + * R25087 (0x61FF) - DSP2 DM 511 + */ +#define WM5100_DSP2_DM_END_MASK 0xFFFF /* DSP2_DM_END - [15:0] */ +#define WM5100_DSP2_DM_END_SHIFT 0 /* DSP2_DM_END - [15:0] */ +#define WM5100_DSP2_DM_END_WIDTH 16 /* DSP2_DM_END - [15:0] */ + +/* + * R26624 (0x6800) - DSP2 PM 0 + */ +#define WM5100_DSP2_PM_START_2_MASK 0x00FF /* DSP2_PM_START - [7:0] */ +#define WM5100_DSP2_PM_START_2_SHIFT 0 /* DSP2_PM_START - [7:0] */ +#define WM5100_DSP2_PM_START_2_WIDTH 8 /* DSP2_PM_START - [7:0] */ + +/* + * R26625 (0x6801) - DSP2 PM 1 + */ +#define WM5100_DSP2_PM_START_1_MASK 0xFFFF /* DSP2_PM_START - [15:0] */ +#define WM5100_DSP2_PM_START_1_SHIFT 0 /* DSP2_PM_START - [15:0] */ +#define WM5100_DSP2_PM_START_1_WIDTH 16 /* DSP2_PM_START - [15:0] */ + +/* + * R26626 (0x6802) - DSP2 PM 2 + */ +#define WM5100_DSP2_PM_START_MASK 0xFFFF /* DSP2_PM_START - [15:0] */ +#define WM5100_DSP2_PM_START_SHIFT 0 /* DSP2_PM_START - [15:0] */ +#define WM5100_DSP2_PM_START_WIDTH 16 /* DSP2_PM_START - [15:0] */ + +/* + * R26627 (0x6803) - DSP2 PM 3 + */ +#define WM5100_DSP2_PM_1_2_MASK 0x00FF /* DSP2_PM_1 - [7:0] */ +#define WM5100_DSP2_PM_1_2_SHIFT 0 /* DSP2_PM_1 - [7:0] */ +#define WM5100_DSP2_PM_1_2_WIDTH 8 /* DSP2_PM_1 - [7:0] */ + +/* + * R26628 (0x6804) - DSP2 PM 4 + */ +#define WM5100_DSP2_PM_1_1_MASK 0xFFFF /* DSP2_PM_1 - [15:0] */ +#define WM5100_DSP2_PM_1_1_SHIFT 0 /* DSP2_PM_1 - [15:0] */ +#define WM5100_DSP2_PM_1_1_WIDTH 16 /* DSP2_PM_1 - [15:0] */ + +/* + * R26629 (0x6805) - DSP2 PM 5 + */ +#define WM5100_DSP2_PM_1_MASK 0xFFFF /* DSP2_PM_1 - [15:0] */ +#define WM5100_DSP2_PM_1_SHIFT 0 /* DSP2_PM_1 - [15:0] */ +#define WM5100_DSP2_PM_1_WIDTH 16 /* DSP2_PM_1 - [15:0] */ + +/* + * R28154 (0x6DFA) - DSP2 PM 1530 + */ +#define WM5100_DSP2_PM_510_2_MASK 0x00FF /* DSP2_PM_510 - [7:0] */ +#define WM5100_DSP2_PM_510_2_SHIFT 0 /* DSP2_PM_510 - [7:0] */ +#define WM5100_DSP2_PM_510_2_WIDTH 8 /* DSP2_PM_510 - [7:0] */ + +/* + * R28155 (0x6DFB) - DSP2 PM 1531 + */ +#define WM5100_DSP2_PM_510_1_MASK 0xFFFF /* DSP2_PM_510 - [15:0] */ +#define WM5100_DSP2_PM_510_1_SHIFT 0 /* DSP2_PM_510 - [15:0] */ +#define WM5100_DSP2_PM_510_1_WIDTH 16 /* DSP2_PM_510 - [15:0] */ + +/* + * R28156 (0x6DFC) - DSP2 PM 1532 + */ +#define WM5100_DSP2_PM_510_MASK 0xFFFF /* DSP2_PM_510 - [15:0] */ +#define WM5100_DSP2_PM_510_SHIFT 0 /* DSP2_PM_510 - [15:0] */ +#define WM5100_DSP2_PM_510_WIDTH 16 /* DSP2_PM_510 - [15:0] */ + +/* + * R28157 (0x6DFD) - DSP2 PM 1533 + */ +#define WM5100_DSP2_PM_END_2_MASK 0x00FF /* DSP2_PM_END - [7:0] */ +#define WM5100_DSP2_PM_END_2_SHIFT 0 /* DSP2_PM_END - [7:0] */ +#define WM5100_DSP2_PM_END_2_WIDTH 8 /* DSP2_PM_END - [7:0] */ + +/* + * R28158 (0x6DFE) - DSP2 PM 1534 + */ +#define WM5100_DSP2_PM_END_1_MASK 0xFFFF /* DSP2_PM_END - [15:0] */ +#define WM5100_DSP2_PM_END_1_SHIFT 0 /* DSP2_PM_END - [15:0] */ +#define WM5100_DSP2_PM_END_1_WIDTH 16 /* DSP2_PM_END - [15:0] */ + +/* + * R28159 (0x6DFF) - DSP2 PM 1535 + */ +#define WM5100_DSP2_PM_END_MASK 0xFFFF /* DSP2_PM_END - [15:0] */ +#define WM5100_DSP2_PM_END_SHIFT 0 /* DSP2_PM_END - [15:0] */ +#define WM5100_DSP2_PM_END_WIDTH 16 /* DSP2_PM_END - [15:0] */ + +/* + * R28672 (0x7000) - DSP2 ZM 0 + */ +#define WM5100_DSP2_ZM_START_1_MASK 0x00FF /* DSP2_ZM_START - [7:0] */ +#define WM5100_DSP2_ZM_START_1_SHIFT 0 /* DSP2_ZM_START - [7:0] */ +#define WM5100_DSP2_ZM_START_1_WIDTH 8 /* DSP2_ZM_START - [7:0] */ + +/* + * R28673 (0x7001) - DSP2 ZM 1 + */ +#define WM5100_DSP2_ZM_START_MASK 0xFFFF /* DSP2_ZM_START - [15:0] */ +#define WM5100_DSP2_ZM_START_SHIFT 0 /* DSP2_ZM_START - [15:0] */ +#define WM5100_DSP2_ZM_START_WIDTH 16 /* DSP2_ZM_START - [15:0] */ + +/* + * R28674 (0x7002) - DSP2 ZM 2 + */ +#define WM5100_DSP2_ZM_1_1_MASK 0x00FF /* DSP2_ZM_1 - [7:0] */ +#define WM5100_DSP2_ZM_1_1_SHIFT 0 /* DSP2_ZM_1 - [7:0] */ +#define WM5100_DSP2_ZM_1_1_WIDTH 8 /* DSP2_ZM_1 - [7:0] */ + +/* + * R28675 (0x7003) - DSP2 ZM 3 + */ +#define WM5100_DSP2_ZM_1_MASK 0xFFFF /* DSP2_ZM_1 - [15:0] */ +#define WM5100_DSP2_ZM_1_SHIFT 0 /* DSP2_ZM_1 - [15:0] */ +#define WM5100_DSP2_ZM_1_WIDTH 16 /* DSP2_ZM_1 - [15:0] */ + +/* + * R30716 (0x77FC) - DSP2 ZM 2044 + */ +#define WM5100_DSP2_ZM_1022_1_MASK 0x00FF /* DSP2_ZM_1022 - [7:0] */ +#define WM5100_DSP2_ZM_1022_1_SHIFT 0 /* DSP2_ZM_1022 - [7:0] */ +#define WM5100_DSP2_ZM_1022_1_WIDTH 8 /* DSP2_ZM_1022 - [7:0] */ + +/* + * R30717 (0x77FD) - DSP2 ZM 2045 + */ +#define WM5100_DSP2_ZM_1022_MASK 0xFFFF /* DSP2_ZM_1022 - [15:0] */ +#define WM5100_DSP2_ZM_1022_SHIFT 0 /* DSP2_ZM_1022 - [15:0] */ +#define WM5100_DSP2_ZM_1022_WIDTH 16 /* DSP2_ZM_1022 - [15:0] */ + +/* + * R30718 (0x77FE) - DSP2 ZM 2046 + */ +#define WM5100_DSP2_ZM_END_1_MASK 0x00FF /* DSP2_ZM_END - [7:0] */ +#define WM5100_DSP2_ZM_END_1_SHIFT 0 /* DSP2_ZM_END - [7:0] */ +#define WM5100_DSP2_ZM_END_1_WIDTH 8 /* DSP2_ZM_END - [7:0] */ + +/* + * R30719 (0x77FF) - DSP2 ZM 2047 + */ +#define WM5100_DSP2_ZM_END_MASK 0xFFFF /* DSP2_ZM_END - [15:0] */ +#define WM5100_DSP2_ZM_END_SHIFT 0 /* DSP2_ZM_END - [15:0] */ +#define WM5100_DSP2_ZM_END_WIDTH 16 /* DSP2_ZM_END - [15:0] */ + +/* + * R32768 (0x8000) - DSP3 DM 0 + */ +#define WM5100_DSP3_DM_START_1_MASK 0x00FF /* DSP3_DM_START - [7:0] */ +#define WM5100_DSP3_DM_START_1_SHIFT 0 /* DSP3_DM_START - [7:0] */ +#define WM5100_DSP3_DM_START_1_WIDTH 8 /* DSP3_DM_START - [7:0] */ + +/* + * R32769 (0x8001) - DSP3 DM 1 + */ +#define WM5100_DSP3_DM_START_MASK 0xFFFF /* DSP3_DM_START - [15:0] */ +#define WM5100_DSP3_DM_START_SHIFT 0 /* DSP3_DM_START - [15:0] */ +#define WM5100_DSP3_DM_START_WIDTH 16 /* DSP3_DM_START - [15:0] */ + +/* + * R32770 (0x8002) - DSP3 DM 2 + */ +#define WM5100_DSP3_DM_1_1_MASK 0x00FF /* DSP3_DM_1 - [7:0] */ +#define WM5100_DSP3_DM_1_1_SHIFT 0 /* DSP3_DM_1 - [7:0] */ +#define WM5100_DSP3_DM_1_1_WIDTH 8 /* DSP3_DM_1 - [7:0] */ + +/* + * R32771 (0x8003) - DSP3 DM 3 + */ +#define WM5100_DSP3_DM_1_MASK 0xFFFF /* DSP3_DM_1 - [15:0] */ +#define WM5100_DSP3_DM_1_SHIFT 0 /* DSP3_DM_1 - [15:0] */ +#define WM5100_DSP3_DM_1_WIDTH 16 /* DSP3_DM_1 - [15:0] */ + +/* + * R33276 (0x81FC) - DSP3 DM 508 + */ +#define WM5100_DSP3_DM_254_1_MASK 0x00FF /* DSP3_DM_254 - [7:0] */ +#define WM5100_DSP3_DM_254_1_SHIFT 0 /* DSP3_DM_254 - [7:0] */ +#define WM5100_DSP3_DM_254_1_WIDTH 8 /* DSP3_DM_254 - [7:0] */ + +/* + * R33277 (0x81FD) - DSP3 DM 509 + */ +#define WM5100_DSP3_DM_254_MASK 0xFFFF /* DSP3_DM_254 - [15:0] */ +#define WM5100_DSP3_DM_254_SHIFT 0 /* DSP3_DM_254 - [15:0] */ +#define WM5100_DSP3_DM_254_WIDTH 16 /* DSP3_DM_254 - [15:0] */ + +/* + * R33278 (0x81FE) - DSP3 DM 510 + */ +#define WM5100_DSP3_DM_END_1_MASK 0x00FF /* DSP3_DM_END - [7:0] */ +#define WM5100_DSP3_DM_END_1_SHIFT 0 /* DSP3_DM_END - [7:0] */ +#define WM5100_DSP3_DM_END_1_WIDTH 8 /* DSP3_DM_END - [7:0] */ + +/* + * R33279 (0x81FF) - DSP3 DM 511 + */ +#define WM5100_DSP3_DM_END_MASK 0xFFFF /* DSP3_DM_END - [15:0] */ +#define WM5100_DSP3_DM_END_SHIFT 0 /* DSP3_DM_END - [15:0] */ +#define WM5100_DSP3_DM_END_WIDTH 16 /* DSP3_DM_END - [15:0] */ + +/* + * R34816 (0x8800) - DSP3 PM 0 + */ +#define WM5100_DSP3_PM_START_2_MASK 0x00FF /* DSP3_PM_START - [7:0] */ +#define WM5100_DSP3_PM_START_2_SHIFT 0 /* DSP3_PM_START - [7:0] */ +#define WM5100_DSP3_PM_START_2_WIDTH 8 /* DSP3_PM_START - [7:0] */ + +/* + * R34817 (0x8801) - DSP3 PM 1 + */ +#define WM5100_DSP3_PM_START_1_MASK 0xFFFF /* DSP3_PM_START - [15:0] */ +#define WM5100_DSP3_PM_START_1_SHIFT 0 /* DSP3_PM_START - [15:0] */ +#define WM5100_DSP3_PM_START_1_WIDTH 16 /* DSP3_PM_START - [15:0] */ + +/* + * R34818 (0x8802) - DSP3 PM 2 + */ +#define WM5100_DSP3_PM_START_MASK 0xFFFF /* DSP3_PM_START - [15:0] */ +#define WM5100_DSP3_PM_START_SHIFT 0 /* DSP3_PM_START - [15:0] */ +#define WM5100_DSP3_PM_START_WIDTH 16 /* DSP3_PM_START - [15:0] */ + +/* + * R34819 (0x8803) - DSP3 PM 3 + */ +#define WM5100_DSP3_PM_1_2_MASK 0x00FF /* DSP3_PM_1 - [7:0] */ +#define WM5100_DSP3_PM_1_2_SHIFT 0 /* DSP3_PM_1 - [7:0] */ +#define WM5100_DSP3_PM_1_2_WIDTH 8 /* DSP3_PM_1 - [7:0] */ + +/* + * R34820 (0x8804) - DSP3 PM 4 + */ +#define WM5100_DSP3_PM_1_1_MASK 0xFFFF /* DSP3_PM_1 - [15:0] */ +#define WM5100_DSP3_PM_1_1_SHIFT 0 /* DSP3_PM_1 - [15:0] */ +#define WM5100_DSP3_PM_1_1_WIDTH 16 /* DSP3_PM_1 - [15:0] */ + +/* + * R34821 (0x8805) - DSP3 PM 5 + */ +#define WM5100_DSP3_PM_1_MASK 0xFFFF /* DSP3_PM_1 - [15:0] */ +#define WM5100_DSP3_PM_1_SHIFT 0 /* DSP3_PM_1 - [15:0] */ +#define WM5100_DSP3_PM_1_WIDTH 16 /* DSP3_PM_1 - [15:0] */ + +/* + * R36346 (0x8DFA) - DSP3 PM 1530 + */ +#define WM5100_DSP3_PM_510_2_MASK 0x00FF /* DSP3_PM_510 - [7:0] */ +#define WM5100_DSP3_PM_510_2_SHIFT 0 /* DSP3_PM_510 - [7:0] */ +#define WM5100_DSP3_PM_510_2_WIDTH 8 /* DSP3_PM_510 - [7:0] */ + +/* + * R36347 (0x8DFB) - DSP3 PM 1531 + */ +#define WM5100_DSP3_PM_510_1_MASK 0xFFFF /* DSP3_PM_510 - [15:0] */ +#define WM5100_DSP3_PM_510_1_SHIFT 0 /* DSP3_PM_510 - [15:0] */ +#define WM5100_DSP3_PM_510_1_WIDTH 16 /* DSP3_PM_510 - [15:0] */ + +/* + * R36348 (0x8DFC) - DSP3 PM 1532 + */ +#define WM5100_DSP3_PM_510_MASK 0xFFFF /* DSP3_PM_510 - [15:0] */ +#define WM5100_DSP3_PM_510_SHIFT 0 /* DSP3_PM_510 - [15:0] */ +#define WM5100_DSP3_PM_510_WIDTH 16 /* DSP3_PM_510 - [15:0] */ + +/* + * R36349 (0x8DFD) - DSP3 PM 1533 + */ +#define WM5100_DSP3_PM_END_2_MASK 0x00FF /* DSP3_PM_END - [7:0] */ +#define WM5100_DSP3_PM_END_2_SHIFT 0 /* DSP3_PM_END - [7:0] */ +#define WM5100_DSP3_PM_END_2_WIDTH 8 /* DSP3_PM_END - [7:0] */ + +/* + * R36350 (0x8DFE) - DSP3 PM 1534 + */ +#define WM5100_DSP3_PM_END_1_MASK 0xFFFF /* DSP3_PM_END - [15:0] */ +#define WM5100_DSP3_PM_END_1_SHIFT 0 /* DSP3_PM_END - [15:0] */ +#define WM5100_DSP3_PM_END_1_WIDTH 16 /* DSP3_PM_END - [15:0] */ + +/* + * R36351 (0x8DFF) - DSP3 PM 1535 + */ +#define WM5100_DSP3_PM_END_MASK 0xFFFF /* DSP3_PM_END - [15:0] */ +#define WM5100_DSP3_PM_END_SHIFT 0 /* DSP3_PM_END - [15:0] */ +#define WM5100_DSP3_PM_END_WIDTH 16 /* DSP3_PM_END - [15:0] */ + +/* + * R36864 (0x9000) - DSP3 ZM 0 + */ +#define WM5100_DSP3_ZM_START_1_MASK 0x00FF /* DSP3_ZM_START - [7:0] */ +#define WM5100_DSP3_ZM_START_1_SHIFT 0 /* DSP3_ZM_START - [7:0] */ +#define WM5100_DSP3_ZM_START_1_WIDTH 8 /* DSP3_ZM_START - [7:0] */ + +/* + * R36865 (0x9001) - DSP3 ZM 1 + */ +#define WM5100_DSP3_ZM_START_MASK 0xFFFF /* DSP3_ZM_START - [15:0] */ +#define WM5100_DSP3_ZM_START_SHIFT 0 /* DSP3_ZM_START - [15:0] */ +#define WM5100_DSP3_ZM_START_WIDTH 16 /* DSP3_ZM_START - [15:0] */ + +/* + * R36866 (0x9002) - DSP3 ZM 2 + */ +#define WM5100_DSP3_ZM_1_1_MASK 0x00FF /* DSP3_ZM_1 - [7:0] */ +#define WM5100_DSP3_ZM_1_1_SHIFT 0 /* DSP3_ZM_1 - [7:0] */ +#define WM5100_DSP3_ZM_1_1_WIDTH 8 /* DSP3_ZM_1 - [7:0] */ + +/* + * R36867 (0x9003) - DSP3 ZM 3 + */ +#define WM5100_DSP3_ZM_1_MASK 0xFFFF /* DSP3_ZM_1 - [15:0] */ +#define WM5100_DSP3_ZM_1_SHIFT 0 /* DSP3_ZM_1 - [15:0] */ +#define WM5100_DSP3_ZM_1_WIDTH 16 /* DSP3_ZM_1 - [15:0] */ + +/* + * R38908 (0x97FC) - DSP3 ZM 2044 + */ +#define WM5100_DSP3_ZM_1022_1_MASK 0x00FF /* DSP3_ZM_1022 - [7:0] */ +#define WM5100_DSP3_ZM_1022_1_SHIFT 0 /* DSP3_ZM_1022 - [7:0] */ +#define WM5100_DSP3_ZM_1022_1_WIDTH 8 /* DSP3_ZM_1022 - [7:0] */ + +/* + * R38909 (0x97FD) - DSP3 ZM 2045 + */ +#define WM5100_DSP3_ZM_1022_MASK 0xFFFF /* DSP3_ZM_1022 - [15:0] */ +#define WM5100_DSP3_ZM_1022_SHIFT 0 /* DSP3_ZM_1022 - [15:0] */ +#define WM5100_DSP3_ZM_1022_WIDTH 16 /* DSP3_ZM_1022 - [15:0] */ + +/* + * R38910 (0x97FE) - DSP3 ZM 2046 + */ +#define WM5100_DSP3_ZM_END_1_MASK 0x00FF /* DSP3_ZM_END - [7:0] */ +#define WM5100_DSP3_ZM_END_1_SHIFT 0 /* DSP3_ZM_END - [7:0] */ +#define WM5100_DSP3_ZM_END_1_WIDTH 8 /* DSP3_ZM_END - [7:0] */ + +/* + * R38911 (0x97FF) - DSP3 ZM 2047 + */ +#define WM5100_DSP3_ZM_END_MASK 0xFFFF /* DSP3_ZM_END - [15:0] */ +#define WM5100_DSP3_ZM_END_SHIFT 0 /* DSP3_ZM_END - [15:0] */ +#define WM5100_DSP3_ZM_END_WIDTH 16 /* DSP3_ZM_END - [15:0] */ + +bool wm5100_readable_register(struct device *dev, unsigned int reg); +bool wm5100_volatile_register(struct device *dev, unsigned int reg); + +extern struct reg_default wm5100_reg_defaults[WM5100_REGISTER_COUNT]; + +#endif diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c new file mode 100644 index 000000000..7e817e187 --- /dev/null +++ b/sound/soc/codecs/wm5102.c @@ -0,0 +1,2164 @@ +/* + * wm5102.c -- WM5102 ALSA SoC Audio driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "arizona.h" +#include "wm5102.h" +#include "wm_adsp.h" + +#define DRV_NAME "wm5102-codec" + +struct wm5102_priv { + struct arizona_priv core; + struct arizona_fll fll[2]; +}; + +static DECLARE_TLV_DB_SCALE(ana_tlv, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); +static DECLARE_TLV_DB_SCALE(noise_tlv, -13200, 600, 0); +static DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); + +static const struct wm_adsp_region wm5102_dsp1_regions[] = { + { .type = WMFW_ADSP2_PM, .base = 0x100000 }, + { .type = WMFW_ADSP2_ZM, .base = 0x180000 }, + { .type = WMFW_ADSP2_XM, .base = 0x190000 }, + { .type = WMFW_ADSP2_YM, .base = 0x1a8000 }, +}; + +static const struct reg_default wm5102_sysclk_reva_patch[] = { + { 0x3000, 0x2225 }, + { 0x3001, 0x3a03 }, + { 0x3002, 0x0225 }, + { 0x3003, 0x0801 }, + { 0x3004, 0x6249 }, + { 0x3005, 0x0c04 }, + { 0x3006, 0x0225 }, + { 0x3007, 0x5901 }, + { 0x3008, 0xe249 }, + { 0x3009, 0x030d }, + { 0x300a, 0x0249 }, + { 0x300b, 0x2c01 }, + { 0x300c, 0xe249 }, + { 0x300d, 0x4342 }, + { 0x300e, 0xe249 }, + { 0x300f, 0x73c0 }, + { 0x3010, 0x4249 }, + { 0x3011, 0x0c00 }, + { 0x3012, 0x0225 }, + { 0x3013, 0x1f01 }, + { 0x3014, 0x0225 }, + { 0x3015, 0x1e01 }, + { 0x3016, 0x0225 }, + { 0x3017, 0xfa00 }, + { 0x3018, 0x0000 }, + { 0x3019, 0xf000 }, + { 0x301a, 0x0000 }, + { 0x301b, 0xf000 }, + { 0x301c, 0x0000 }, + { 0x301d, 0xf000 }, + { 0x301e, 0x0000 }, + { 0x301f, 0xf000 }, + { 0x3020, 0x0000 }, + { 0x3021, 0xf000 }, + { 0x3022, 0x0000 }, + { 0x3023, 0xf000 }, + { 0x3024, 0x0000 }, + { 0x3025, 0xf000 }, + { 0x3026, 0x0000 }, + { 0x3027, 0xf000 }, + { 0x3028, 0x0000 }, + { 0x3029, 0xf000 }, + { 0x302a, 0x0000 }, + { 0x302b, 0xf000 }, + { 0x302c, 0x0000 }, + { 0x302d, 0xf000 }, + { 0x302e, 0x0000 }, + { 0x302f, 0xf000 }, + { 0x3030, 0x0225 }, + { 0x3031, 0x1a01 }, + { 0x3032, 0x0225 }, + { 0x3033, 0x1e00 }, + { 0x3034, 0x0225 }, + { 0x3035, 0x1f00 }, + { 0x3036, 0x6225 }, + { 0x3037, 0xf800 }, + { 0x3038, 0x0000 }, + { 0x3039, 0xf000 }, + { 0x303a, 0x0000 }, + { 0x303b, 0xf000 }, + { 0x303c, 0x0000 }, + { 0x303d, 0xf000 }, + { 0x303e, 0x0000 }, + { 0x303f, 0xf000 }, + { 0x3040, 0x2226 }, + { 0x3041, 0x3a03 }, + { 0x3042, 0x0226 }, + { 0x3043, 0x0801 }, + { 0x3044, 0x6249 }, + { 0x3045, 0x0c06 }, + { 0x3046, 0x0226 }, + { 0x3047, 0x5901 }, + { 0x3048, 0xe249 }, + { 0x3049, 0x030d }, + { 0x304a, 0x0249 }, + { 0x304b, 0x2c01 }, + { 0x304c, 0xe249 }, + { 0x304d, 0x4342 }, + { 0x304e, 0xe249 }, + { 0x304f, 0x73c0 }, + { 0x3050, 0x4249 }, + { 0x3051, 0x0c00 }, + { 0x3052, 0x0226 }, + { 0x3053, 0x1f01 }, + { 0x3054, 0x0226 }, + { 0x3055, 0x1e01 }, + { 0x3056, 0x0226 }, + { 0x3057, 0xfa00 }, + { 0x3058, 0x0000 }, + { 0x3059, 0xf000 }, + { 0x305a, 0x0000 }, + { 0x305b, 0xf000 }, + { 0x305c, 0x0000 }, + { 0x305d, 0xf000 }, + { 0x305e, 0x0000 }, + { 0x305f, 0xf000 }, + { 0x3060, 0x0000 }, + { 0x3061, 0xf000 }, + { 0x3062, 0x0000 }, + { 0x3063, 0xf000 }, + { 0x3064, 0x0000 }, + { 0x3065, 0xf000 }, + { 0x3066, 0x0000 }, + { 0x3067, 0xf000 }, + { 0x3068, 0x0000 }, + { 0x3069, 0xf000 }, + { 0x306a, 0x0000 }, + { 0x306b, 0xf000 }, + { 0x306c, 0x0000 }, + { 0x306d, 0xf000 }, + { 0x306e, 0x0000 }, + { 0x306f, 0xf000 }, + { 0x3070, 0x0226 }, + { 0x3071, 0x1a01 }, + { 0x3072, 0x0226 }, + { 0x3073, 0x1e00 }, + { 0x3074, 0x0226 }, + { 0x3075, 0x1f00 }, + { 0x3076, 0x6226 }, + { 0x3077, 0xf800 }, + { 0x3078, 0x0000 }, + { 0x3079, 0xf000 }, + { 0x307a, 0x0000 }, + { 0x307b, 0xf000 }, + { 0x307c, 0x0000 }, + { 0x307d, 0xf000 }, + { 0x307e, 0x0000 }, + { 0x307f, 0xf000 }, + { 0x3080, 0x2227 }, + { 0x3081, 0x3a03 }, + { 0x3082, 0x0227 }, + { 0x3083, 0x0801 }, + { 0x3084, 0x6255 }, + { 0x3085, 0x0c04 }, + { 0x3086, 0x0227 }, + { 0x3087, 0x5901 }, + { 0x3088, 0xe255 }, + { 0x3089, 0x030d }, + { 0x308a, 0x0255 }, + { 0x308b, 0x2c01 }, + { 0x308c, 0xe255 }, + { 0x308d, 0x4342 }, + { 0x308e, 0xe255 }, + { 0x308f, 0x73c0 }, + { 0x3090, 0x4255 }, + { 0x3091, 0x0c00 }, + { 0x3092, 0x0227 }, + { 0x3093, 0x1f01 }, + { 0x3094, 0x0227 }, + { 0x3095, 0x1e01 }, + { 0x3096, 0x0227 }, + { 0x3097, 0xfa00 }, + { 0x3098, 0x0000 }, + { 0x3099, 0xf000 }, + { 0x309a, 0x0000 }, + { 0x309b, 0xf000 }, + { 0x309c, 0x0000 }, + { 0x309d, 0xf000 }, + { 0x309e, 0x0000 }, + { 0x309f, 0xf000 }, + { 0x30a0, 0x0000 }, + { 0x30a1, 0xf000 }, + { 0x30a2, 0x0000 }, + { 0x30a3, 0xf000 }, + { 0x30a4, 0x0000 }, + { 0x30a5, 0xf000 }, + { 0x30a6, 0x0000 }, + { 0x30a7, 0xf000 }, + { 0x30a8, 0x0000 }, + { 0x30a9, 0xf000 }, + { 0x30aa, 0x0000 }, + { 0x30ab, 0xf000 }, + { 0x30ac, 0x0000 }, + { 0x30ad, 0xf000 }, + { 0x30ae, 0x0000 }, + { 0x30af, 0xf000 }, + { 0x30b0, 0x0227 }, + { 0x30b1, 0x1a01 }, + { 0x30b2, 0x0227 }, + { 0x30b3, 0x1e00 }, + { 0x30b4, 0x0227 }, + { 0x30b5, 0x1f00 }, + { 0x30b6, 0x6227 }, + { 0x30b7, 0xf800 }, + { 0x30b8, 0x0000 }, + { 0x30b9, 0xf000 }, + { 0x30ba, 0x0000 }, + { 0x30bb, 0xf000 }, + { 0x30bc, 0x0000 }, + { 0x30bd, 0xf000 }, + { 0x30be, 0x0000 }, + { 0x30bf, 0xf000 }, + { 0x30c0, 0x2228 }, + { 0x30c1, 0x3a03 }, + { 0x30c2, 0x0228 }, + { 0x30c3, 0x0801 }, + { 0x30c4, 0x6255 }, + { 0x30c5, 0x0c06 }, + { 0x30c6, 0x0228 }, + { 0x30c7, 0x5901 }, + { 0x30c8, 0xe255 }, + { 0x30c9, 0x030d }, + { 0x30ca, 0x0255 }, + { 0x30cb, 0x2c01 }, + { 0x30cc, 0xe255 }, + { 0x30cd, 0x4342 }, + { 0x30ce, 0xe255 }, + { 0x30cf, 0x73c0 }, + { 0x30d0, 0x4255 }, + { 0x30d1, 0x0c00 }, + { 0x30d2, 0x0228 }, + { 0x30d3, 0x1f01 }, + { 0x30d4, 0x0228 }, + { 0x30d5, 0x1e01 }, + { 0x30d6, 0x0228 }, + { 0x30d7, 0xfa00 }, + { 0x30d8, 0x0000 }, + { 0x30d9, 0xf000 }, + { 0x30da, 0x0000 }, + { 0x30db, 0xf000 }, + { 0x30dc, 0x0000 }, + { 0x30dd, 0xf000 }, + { 0x30de, 0x0000 }, + { 0x30df, 0xf000 }, + { 0x30e0, 0x0000 }, + { 0x30e1, 0xf000 }, + { 0x30e2, 0x0000 }, + { 0x30e3, 0xf000 }, + { 0x30e4, 0x0000 }, + { 0x30e5, 0xf000 }, + { 0x30e6, 0x0000 }, + { 0x30e7, 0xf000 }, + { 0x30e8, 0x0000 }, + { 0x30e9, 0xf000 }, + { 0x30ea, 0x0000 }, + { 0x30eb, 0xf000 }, + { 0x30ec, 0x0000 }, + { 0x30ed, 0xf000 }, + { 0x30ee, 0x0000 }, + { 0x30ef, 0xf000 }, + { 0x30f0, 0x0228 }, + { 0x30f1, 0x1a01 }, + { 0x30f2, 0x0228 }, + { 0x30f3, 0x1e00 }, + { 0x30f4, 0x0228 }, + { 0x30f5, 0x1f00 }, + { 0x30f6, 0x6228 }, + { 0x30f7, 0xf800 }, + { 0x30f8, 0x0000 }, + { 0x30f9, 0xf000 }, + { 0x30fa, 0x0000 }, + { 0x30fb, 0xf000 }, + { 0x30fc, 0x0000 }, + { 0x30fd, 0xf000 }, + { 0x30fe, 0x0000 }, + { 0x30ff, 0xf000 }, + { 0x3100, 0x222b }, + { 0x3101, 0x3a03 }, + { 0x3102, 0x222b }, + { 0x3103, 0x5803 }, + { 0x3104, 0xe26f }, + { 0x3105, 0x030d }, + { 0x3106, 0x626f }, + { 0x3107, 0x2c01 }, + { 0x3108, 0xe26f }, + { 0x3109, 0x4342 }, + { 0x310a, 0xe26f }, + { 0x310b, 0x73c0 }, + { 0x310c, 0x026f }, + { 0x310d, 0x0c00 }, + { 0x310e, 0x022b }, + { 0x310f, 0x1f01 }, + { 0x3110, 0x022b }, + { 0x3111, 0x1e01 }, + { 0x3112, 0x022b }, + { 0x3113, 0xfa00 }, + { 0x3114, 0x0000 }, + { 0x3115, 0xf000 }, + { 0x3116, 0x0000 }, + { 0x3117, 0xf000 }, + { 0x3118, 0x0000 }, + { 0x3119, 0xf000 }, + { 0x311a, 0x0000 }, + { 0x311b, 0xf000 }, + { 0x311c, 0x0000 }, + { 0x311d, 0xf000 }, + { 0x311e, 0x0000 }, + { 0x311f, 0xf000 }, + { 0x3120, 0x022b }, + { 0x3121, 0x0a01 }, + { 0x3122, 0x022b }, + { 0x3123, 0x1e00 }, + { 0x3124, 0x022b }, + { 0x3125, 0x1f00 }, + { 0x3126, 0x622b }, + { 0x3127, 0xf800 }, + { 0x3128, 0x0000 }, + { 0x3129, 0xf000 }, + { 0x312a, 0x0000 }, + { 0x312b, 0xf000 }, + { 0x312c, 0x0000 }, + { 0x312d, 0xf000 }, + { 0x312e, 0x0000 }, + { 0x312f, 0xf000 }, + { 0x3130, 0x0000 }, + { 0x3131, 0xf000 }, + { 0x3132, 0x0000 }, + { 0x3133, 0xf000 }, + { 0x3134, 0x0000 }, + { 0x3135, 0xf000 }, + { 0x3136, 0x0000 }, + { 0x3137, 0xf000 }, + { 0x3138, 0x0000 }, + { 0x3139, 0xf000 }, + { 0x313a, 0x0000 }, + { 0x313b, 0xf000 }, + { 0x313c, 0x0000 }, + { 0x313d, 0xf000 }, + { 0x313e, 0x0000 }, + { 0x313f, 0xf000 }, + { 0x3140, 0x0000 }, + { 0x3141, 0xf000 }, + { 0x3142, 0x0000 }, + { 0x3143, 0xf000 }, + { 0x3144, 0x0000 }, + { 0x3145, 0xf000 }, + { 0x3146, 0x0000 }, + { 0x3147, 0xf000 }, + { 0x3148, 0x0000 }, + { 0x3149, 0xf000 }, + { 0x314a, 0x0000 }, + { 0x314b, 0xf000 }, + { 0x314c, 0x0000 }, + { 0x314d, 0xf000 }, + { 0x314e, 0x0000 }, + { 0x314f, 0xf000 }, + { 0x3150, 0x0000 }, + { 0x3151, 0xf000 }, + { 0x3152, 0x0000 }, + { 0x3153, 0xf000 }, + { 0x3154, 0x0000 }, + { 0x3155, 0xf000 }, + { 0x3156, 0x0000 }, + { 0x3157, 0xf000 }, + { 0x3158, 0x0000 }, + { 0x3159, 0xf000 }, + { 0x315a, 0x0000 }, + { 0x315b, 0xf000 }, + { 0x315c, 0x0000 }, + { 0x315d, 0xf000 }, + { 0x315e, 0x0000 }, + { 0x315f, 0xf000 }, + { 0x3160, 0x0000 }, + { 0x3161, 0xf000 }, + { 0x3162, 0x0000 }, + { 0x3163, 0xf000 }, + { 0x3164, 0x0000 }, + { 0x3165, 0xf000 }, + { 0x3166, 0x0000 }, + { 0x3167, 0xf000 }, + { 0x3168, 0x0000 }, + { 0x3169, 0xf000 }, + { 0x316a, 0x0000 }, + { 0x316b, 0xf000 }, + { 0x316c, 0x0000 }, + { 0x316d, 0xf000 }, + { 0x316e, 0x0000 }, + { 0x316f, 0xf000 }, + { 0x3170, 0x0000 }, + { 0x3171, 0xf000 }, + { 0x3172, 0x0000 }, + { 0x3173, 0xf000 }, + { 0x3174, 0x0000 }, + { 0x3175, 0xf000 }, + { 0x3176, 0x0000 }, + { 0x3177, 0xf000 }, + { 0x3178, 0x0000 }, + { 0x3179, 0xf000 }, + { 0x317a, 0x0000 }, + { 0x317b, 0xf000 }, + { 0x317c, 0x0000 }, + { 0x317d, 0xf000 }, + { 0x317e, 0x0000 }, + { 0x317f, 0xf000 }, + { 0x3180, 0x2001 }, + { 0x3181, 0xf101 }, + { 0x3182, 0x0000 }, + { 0x3183, 0xf000 }, + { 0x3184, 0x0000 }, + { 0x3185, 0xf000 }, + { 0x3186, 0x0000 }, + { 0x3187, 0xf000 }, + { 0x3188, 0x0000 }, + { 0x3189, 0xf000 }, + { 0x318a, 0x0000 }, + { 0x318b, 0xf000 }, + { 0x318c, 0x0000 }, + { 0x318d, 0xf000 }, + { 0x318e, 0x0000 }, + { 0x318f, 0xf000 }, + { 0x3190, 0x0000 }, + { 0x3191, 0xf000 }, + { 0x3192, 0x0000 }, + { 0x3193, 0xf000 }, + { 0x3194, 0x0000 }, + { 0x3195, 0xf000 }, + { 0x3196, 0x0000 }, + { 0x3197, 0xf000 }, + { 0x3198, 0x0000 }, + { 0x3199, 0xf000 }, + { 0x319a, 0x0000 }, + { 0x319b, 0xf000 }, + { 0x319c, 0x0000 }, + { 0x319d, 0xf000 }, + { 0x319e, 0x0000 }, + { 0x319f, 0xf000 }, + { 0x31a0, 0x0000 }, + { 0x31a1, 0xf000 }, + { 0x31a2, 0x0000 }, + { 0x31a3, 0xf000 }, + { 0x31a4, 0x0000 }, + { 0x31a5, 0xf000 }, + { 0x31a6, 0x0000 }, + { 0x31a7, 0xf000 }, + { 0x31a8, 0x0000 }, + { 0x31a9, 0xf000 }, + { 0x31aa, 0x0000 }, + { 0x31ab, 0xf000 }, + { 0x31ac, 0x0000 }, + { 0x31ad, 0xf000 }, + { 0x31ae, 0x0000 }, + { 0x31af, 0xf000 }, + { 0x31b0, 0x0000 }, + { 0x31b1, 0xf000 }, + { 0x31b2, 0x0000 }, + { 0x31b3, 0xf000 }, + { 0x31b4, 0x0000 }, + { 0x31b5, 0xf000 }, + { 0x31b6, 0x0000 }, + { 0x31b7, 0xf000 }, + { 0x31b8, 0x0000 }, + { 0x31b9, 0xf000 }, + { 0x31ba, 0x0000 }, + { 0x31bb, 0xf000 }, + { 0x31bc, 0x0000 }, + { 0x31bd, 0xf000 }, + { 0x31be, 0x0000 }, + { 0x31bf, 0xf000 }, + { 0x31c0, 0x0000 }, + { 0x31c1, 0xf000 }, + { 0x31c2, 0x0000 }, + { 0x31c3, 0xf000 }, + { 0x31c4, 0x0000 }, + { 0x31c5, 0xf000 }, + { 0x31c6, 0x0000 }, + { 0x31c7, 0xf000 }, + { 0x31c8, 0x0000 }, + { 0x31c9, 0xf000 }, + { 0x31ca, 0x0000 }, + { 0x31cb, 0xf000 }, + { 0x31cc, 0x0000 }, + { 0x31cd, 0xf000 }, + { 0x31ce, 0x0000 }, + { 0x31cf, 0xf000 }, + { 0x31d0, 0x0000 }, + { 0x31d1, 0xf000 }, + { 0x31d2, 0x0000 }, + { 0x31d3, 0xf000 }, + { 0x31d4, 0x0000 }, + { 0x31d5, 0xf000 }, + { 0x31d6, 0x0000 }, + { 0x31d7, 0xf000 }, + { 0x31d8, 0x0000 }, + { 0x31d9, 0xf000 }, + { 0x31da, 0x0000 }, + { 0x31db, 0xf000 }, + { 0x31dc, 0x0000 }, + { 0x31dd, 0xf000 }, + { 0x31de, 0x0000 }, + { 0x31df, 0xf000 }, + { 0x31e0, 0x0000 }, + { 0x31e1, 0xf000 }, + { 0x31e2, 0x0000 }, + { 0x31e3, 0xf000 }, + { 0x31e4, 0x0000 }, + { 0x31e5, 0xf000 }, + { 0x31e6, 0x0000 }, + { 0x31e7, 0xf000 }, + { 0x31e8, 0x0000 }, + { 0x31e9, 0xf000 }, + { 0x31ea, 0x0000 }, + { 0x31eb, 0xf000 }, + { 0x31ec, 0x0000 }, + { 0x31ed, 0xf000 }, + { 0x31ee, 0x0000 }, + { 0x31ef, 0xf000 }, + { 0x31f0, 0x0000 }, + { 0x31f1, 0xf000 }, + { 0x31f2, 0x0000 }, + { 0x31f3, 0xf000 }, + { 0x31f4, 0x0000 }, + { 0x31f5, 0xf000 }, + { 0x31f6, 0x0000 }, + { 0x31f7, 0xf000 }, + { 0x31f8, 0x0000 }, + { 0x31f9, 0xf000 }, + { 0x31fa, 0x0000 }, + { 0x31fb, 0xf000 }, + { 0x31fc, 0x0000 }, + { 0x31fd, 0xf000 }, + { 0x31fe, 0x0000 }, + { 0x31ff, 0xf000 }, + { 0x024d, 0xff50 }, + { 0x0252, 0xff50 }, + { 0x0259, 0x0112 }, + { 0x025e, 0x0112 }, +}; + +static const struct reg_default wm5102_sysclk_revb_patch[] = { + { 0x3081, 0x08FE }, + { 0x3083, 0x00ED }, + { 0x30C1, 0x08FE }, + { 0x30C3, 0x00ED }, +}; + +static int wm5102_sysclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct regmap *regmap = arizona->regmap; + const struct reg_default *patch = NULL; + int i, patch_size; + + switch (arizona->rev) { + case 0: + patch = wm5102_sysclk_reva_patch; + patch_size = ARRAY_SIZE(wm5102_sysclk_reva_patch); + break; + default: + patch = wm5102_sysclk_revb_patch; + patch_size = ARRAY_SIZE(wm5102_sysclk_revb_patch); + break; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (patch) + for (i = 0; i < patch_size; i++) + regmap_write_async(regmap, patch[i].reg, + patch[i].def); + break; + case SND_SOC_DAPM_PRE_PMD: + break; + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_POST_PMD: + return arizona_clk_ev(w, kcontrol, event); + default: + return 0; + } + + return arizona_dvfs_sysclk_ev(w, kcontrol, event); +} + +static int wm5102_adsp_power_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + unsigned int v = 0; + int ret; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &v); + if (ret != 0) { + dev_err(component->dev, + "Failed to read SYSCLK state: %d\n", ret); + return -EIO; + } + + v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT; + + if (v >= 3) { + ret = arizona_dvfs_up(component, ARIZONA_DVFS_ADSP1_RQ); + if (ret) { + dev_err(component->dev, + "Failed to raise DVFS: %d\n", ret); + return ret; + } + } + break; + + case SND_SOC_DAPM_POST_PMD: + ret = arizona_dvfs_down(component, ARIZONA_DVFS_ADSP1_RQ); + if (ret) + dev_warn(component->dev, + "Failed to lower DVFS: %d\n", ret); + break; + + default: + break; + } + + return wm_adsp2_early_event(w, kcontrol, event, v); +} + +static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + + mutex_lock(&arizona->dac_comp_lock); + put_unaligned_be16(arizona->dac_comp_coeff, + ucontrol->value.bytes.data); + mutex_unlock(&arizona->dac_comp_lock); + + return 0; +} + +static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + + mutex_lock(&arizona->dac_comp_lock); + memcpy(&arizona->dac_comp_coeff, ucontrol->value.bytes.data, + sizeof(arizona->dac_comp_coeff)); + arizona->dac_comp_coeff = be16_to_cpu(arizona->dac_comp_coeff); + mutex_unlock(&arizona->dac_comp_lock); + + return 0; +} + +static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + + mutex_lock(&arizona->dac_comp_lock); + ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; + mutex_unlock(&arizona->dac_comp_lock); + + return 0; +} + +static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + + mutex_lock(&arizona->dac_comp_lock); + arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; + mutex_unlock(&arizona->dac_comp_lock); + + return 0; +} + +static const char * const wm5102_osr_text[] = { + "Low power", "Normal", "High performance", +}; + +static const unsigned int wm5102_osr_val[] = { + 0x0, 0x3, 0x5, +}; + +static const struct soc_enum wm5102_hpout_osr[] = { + SOC_VALUE_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_1L, + ARIZONA_OUT1_OSR_SHIFT, 0x7, + ARRAY_SIZE(wm5102_osr_text), + wm5102_osr_text, wm5102_osr_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_2L, + ARIZONA_OUT2_OSR_SHIFT, 0x7, + ARRAY_SIZE(wm5102_osr_text), + wm5102_osr_text, wm5102_osr_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_3L, + ARIZONA_OUT3_OSR_SHIFT, 0x7, + ARRAY_SIZE(wm5102_osr_text), + wm5102_osr_text, wm5102_osr_val), +}; + +#define WM5102_NG_SRC(name, base) \ + SOC_SINGLE(name " NG HPOUT1L Switch", base, 0, 1, 0), \ + SOC_SINGLE(name " NG HPOUT1R Switch", base, 1, 1, 0), \ + SOC_SINGLE(name " NG HPOUT2L Switch", base, 2, 1, 0), \ + SOC_SINGLE(name " NG HPOUT2R Switch", base, 3, 1, 0), \ + SOC_SINGLE(name " NG EPOUT Switch", base, 4, 1, 0), \ + SOC_SINGLE(name " NG SPKOUTL Switch", base, 6, 1, 0), \ + SOC_SINGLE(name " NG SPKOUTR Switch", base, 7, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT1L Switch", base, 8, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT1R Switch", base, 9, 1, 0) + +static const struct snd_kcontrol_new wm5102_snd_controls[] = { +SOC_SINGLE("IN1 High Performance Switch", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN2 High Performance Switch", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN3 High Performance Switch", ARIZONA_IN3L_CONTROL, + ARIZONA_IN3_OSR_SHIFT, 1, 0), + +SOC_SINGLE_RANGE_TLV("IN1L Volume", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN1R Volume", ARIZONA_IN1R_CONTROL, + ARIZONA_IN1R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN2L Volume", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN2R Volume", ARIZONA_IN2R_CONTROL, + ARIZONA_IN2R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN3L Volume", ARIZONA_IN3L_CONTROL, + ARIZONA_IN3L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN3R Volume", ARIZONA_IN3R_CONTROL, + ARIZONA_IN3R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), + +SOC_SINGLE_TLV("IN1L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1L, + ARIZONA_IN1L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN1R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1R, + ARIZONA_IN1R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2L, + ARIZONA_IN2L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2R, + ARIZONA_IN2R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN3L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_3L, + ARIZONA_IN3L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN3R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_3R, + ARIZONA_IN3R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), + +SOC_ENUM("Input Ramp Up", arizona_in_vi_ramp), +SOC_ENUM("Input Ramp Down", arizona_in_vd_ramp), + +ARIZONA_MIXER_CONTROLS("EQ1", ARIZONA_EQ1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ2", ARIZONA_EQ2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ3", ARIZONA_EQ3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ4", ARIZONA_EQ4MIX_INPUT_1_SOURCE), + +ARIZONA_EQ_CONTROL("EQ1 Coefficients", ARIZONA_EQ1_2), +SOC_SINGLE_TLV("EQ1 B1 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B2 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B3 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B4 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B5 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ2 Coefficients", ARIZONA_EQ2_2), +SOC_SINGLE_TLV("EQ2 B1 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B2 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B3 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B4 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B5 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ3 Coefficients", ARIZONA_EQ3_2), +SOC_SINGLE_TLV("EQ3 B1 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B2 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B3 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B4 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B5 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ4 Coefficients", ARIZONA_EQ4_2), +SOC_SINGLE_TLV("EQ4 B1 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B2 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B3 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B4 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B5 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_MIXER_CONTROLS("DRC1L", ARIZONA_DRC1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC1R", ARIZONA_DRC1RMIX_INPUT_1_SOURCE), + +SND_SOC_BYTES_MASK("DRC1", ARIZONA_DRC1_CTRL1, 5, + ARIZONA_DRC1R_ENA | ARIZONA_DRC1L_ENA), + +ARIZONA_MIXER_CONTROLS("LHPF1", ARIZONA_HPLP1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF2", ARIZONA_HPLP2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF3", ARIZONA_HPLP3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF4", ARIZONA_HPLP4MIX_INPUT_1_SOURCE), + +ARIZONA_LHPF_CONTROL("LHPF1 Coefficients", ARIZONA_HPLPF1_2), +ARIZONA_LHPF_CONTROL("LHPF2 Coefficients", ARIZONA_HPLPF2_2), +ARIZONA_LHPF_CONTROL("LHPF3 Coefficients", ARIZONA_HPLPF3_2), +ARIZONA_LHPF_CONTROL("LHPF4 Coefficients", ARIZONA_HPLPF4_2), + +WM_ADSP2_PRELOAD_SWITCH("DSP1", 1), + +ARIZONA_MIXER_CONTROLS("DSP1L", ARIZONA_DSP1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP1R", ARIZONA_DSP1RMIX_INPUT_1_SOURCE), + +SOC_ENUM("LHPF1 Mode", arizona_lhpf1_mode), +SOC_ENUM("LHPF2 Mode", arizona_lhpf2_mode), +SOC_ENUM("LHPF3 Mode", arizona_lhpf3_mode), +SOC_ENUM("LHPF4 Mode", arizona_lhpf4_mode), + +SOC_ENUM("ISRC1 FSL", arizona_isrc_fsl[0]), +SOC_ENUM("ISRC2 FSL", arizona_isrc_fsl[1]), + +ARIZONA_MIXER_CONTROLS("Mic", ARIZONA_MICMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("Noise", ARIZONA_NOISEMIX_INPUT_1_SOURCE), + +SOC_SINGLE_TLV("Noise Generator Volume", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_GAIN_SHIFT, 0x16, 0, noise_tlv), + +ARIZONA_MIXER_CONTROLS("HPOUT1L", ARIZONA_OUT1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT1R", ARIZONA_OUT1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT2L", ARIZONA_OUT2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT2R", ARIZONA_OUT2RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EPOUT", ARIZONA_OUT3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUTL", ARIZONA_OUT4LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUTR", ARIZONA_OUT4RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT1L", ARIZONA_OUT5LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT1R", ARIZONA_OUT5RMIX_INPUT_1_SOURCE), + +SOC_SINGLE("Speaker High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_4L, + ARIZONA_OUT4_OSR_SHIFT, 1, 0), +SOC_SINGLE("SPKDAT1 High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_5L, + ARIZONA_OUT5_OSR_SHIFT, 1, 0), + +SOC_DOUBLE_R("HPOUT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("HPOUT2 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_MUTE_SHIFT, 1, 1), +SOC_SINGLE("EPOUT Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_OUT3L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("Speaker Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKDAT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_MUTE_SHIFT, 1, 1), + +SOC_DOUBLE_R_TLV("HPOUT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("HPOUT2 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("EPOUT Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_OUT3L_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("Speaker Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("SPKDAT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_VOL_SHIFT, + 0xbf, 0, digital_tlv), + +SOC_ENUM("HPOUT1 OSR", wm5102_hpout_osr[0]), +SOC_ENUM("HPOUT2 OSR", wm5102_hpout_osr[1]), +SOC_ENUM("EPOUT OSR", wm5102_hpout_osr[2]), + +SOC_DOUBLE("HPOUT1 DRE Switch", ARIZONA_DRE_ENABLE, + ARIZONA_DRE1L_ENA_SHIFT, ARIZONA_DRE1R_ENA_SHIFT, 1, 0), +SOC_DOUBLE("HPOUT2 DRE Switch", ARIZONA_DRE_ENABLE, + ARIZONA_DRE2L_ENA_SHIFT, ARIZONA_DRE2R_ENA_SHIFT, 1, 0), +SOC_SINGLE("EPOUT DRE Switch", ARIZONA_DRE_ENABLE, + ARIZONA_DRE3L_ENA_SHIFT, 1, 0), + +SOC_SINGLE("DRE Threshold", ARIZONA_DRE_CONTROL_2, + ARIZONA_DRE_T_LOW_SHIFT, 63, 0), + +SOC_SINGLE("DRE Low Level ABS", ARIZONA_DRE_CONTROL_3, + ARIZONA_DRE_LOW_LEVEL_ABS_SHIFT, 15, 0), + +SOC_ENUM("Output Ramp Up", arizona_out_vi_ramp), +SOC_ENUM("Output Ramp Down", arizona_out_vd_ramp), + +SOC_DOUBLE("SPKDAT1 Switch", ARIZONA_PDM_SPK1_CTRL_1, ARIZONA_SPK1L_MUTE_SHIFT, + ARIZONA_SPK1R_MUTE_SHIFT, 1, 1), + +SOC_SINGLE("Noise Gate Switch", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_ENA_SHIFT, 1, 0), +SOC_SINGLE_TLV("Noise Gate Threshold Volume", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_THR_SHIFT, 7, 1, ng_tlv), +SOC_ENUM("Noise Gate Hold", arizona_ng_hold), + +SND_SOC_BYTES_EXT("Output Compensation Coefficient", 2, + wm5102_out_comp_coeff_get, wm5102_out_comp_coeff_put), + +SOC_SINGLE_EXT("Output Compensation Switch", 0, 0, 1, 0, + wm5102_out_comp_switch_get, wm5102_out_comp_switch_put), + +WM5102_NG_SRC("HPOUT1L", ARIZONA_NOISE_GATE_SELECT_1L), +WM5102_NG_SRC("HPOUT1R", ARIZONA_NOISE_GATE_SELECT_1R), +WM5102_NG_SRC("HPOUT2L", ARIZONA_NOISE_GATE_SELECT_2L), +WM5102_NG_SRC("HPOUT2R", ARIZONA_NOISE_GATE_SELECT_2R), +WM5102_NG_SRC("EPOUT", ARIZONA_NOISE_GATE_SELECT_3L), +WM5102_NG_SRC("SPKOUTL", ARIZONA_NOISE_GATE_SELECT_4L), +WM5102_NG_SRC("SPKOUTR", ARIZONA_NOISE_GATE_SELECT_4R), +WM5102_NG_SRC("SPKDAT1L", ARIZONA_NOISE_GATE_SELECT_5L), +WM5102_NG_SRC("SPKDAT1R", ARIZONA_NOISE_GATE_SELECT_5R), + +ARIZONA_MIXER_CONTROLS("AIF1TX1", ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX2", ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX3", ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX4", ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX5", ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX6", ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX7", ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX8", ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF2TX1", ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX2", ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF3TX1", ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF3TX2", ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("SLIMTX1", ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX2", ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX3", ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX4", ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX5", ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX6", ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX7", ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX8", ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE), + +WM_ADSP_FW_CONTROL("DSP1", 0), +}; + +ARIZONA_MIXER_ENUMS(EQ1, ARIZONA_EQ1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ2, ARIZONA_EQ2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ3, ARIZONA_EQ3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ4, ARIZONA_EQ4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DRC1L, ARIZONA_DRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC1R, ARIZONA_DRC1RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(LHPF1, ARIZONA_HPLP1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF2, ARIZONA_HPLP2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF3, ARIZONA_HPLP3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF4, ARIZONA_HPLP4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(Mic, ARIZONA_MICMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(Noise, ARIZONA_NOISEMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(PWM1, ARIZONA_PWM1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(PWM2, ARIZONA_PWM2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(OUT1L, ARIZONA_OUT1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT1R, ARIZONA_OUT1RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT2L, ARIZONA_OUT2LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT2R, ARIZONA_OUT2RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT3, ARIZONA_OUT3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUTL, ARIZONA_OUT4LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUTR, ARIZONA_OUT4RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT1L, ARIZONA_OUT5LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT1R, ARIZONA_OUT5RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF1TX1, ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX2, ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX3, ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX4, ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX5, ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX6, ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX7, ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX8, ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF2TX1, ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX2, ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF3TX1, ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF3TX2, ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(SLIMTX1, ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX2, ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX3, ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX4, ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX5, ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX6, ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX7, ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX8, ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ASRC1L, ARIZONA_ASRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC1R, ARIZONA_ASRC1RMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2L, ARIZONA_ASRC2LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2R, ARIZONA_ASRC2RMIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1INT1, ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT2, ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1DEC1, ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC2, ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2INT1, ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT2, ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2DEC1, ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC2, ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DSP1L, ARIZONA_DSP1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DSP1R, ARIZONA_DSP1RMIX_INPUT_1_SOURCE); + +ARIZONA_DSP_AUX_ENUMS(DSP1, ARIZONA_DSP1AUX1MIX_INPUT_1_SOURCE); + +static const char * const wm5102_aec_loopback_texts[] = { + "HPOUT1L", "HPOUT1R", "HPOUT2L", "HPOUT2R", "EPOUT", + "SPKOUTL", "SPKOUTR", "SPKDAT1L", "SPKDAT1R", +}; + +static const unsigned int wm5102_aec_loopback_values[] = { + 0, 1, 2, 3, 4, 6, 7, 8, 9, +}; + +static const struct soc_enum wm5102_aec_loopback = + SOC_VALUE_ENUM_SINGLE(ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_SRC_SHIFT, 0xf, + ARRAY_SIZE(wm5102_aec_loopback_texts), + wm5102_aec_loopback_texts, + wm5102_aec_loopback_values); + +static const struct snd_kcontrol_new wm5102_aec_loopback_mux = + SOC_DAPM_ENUM("AEC Loopback", wm5102_aec_loopback); + +static const struct snd_soc_dapm_widget wm5102_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT, + 0, wm5102_sysclk_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1, + ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK, + ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK, + ARIZONA_OPCLK_ASYNC_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD2", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD3", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, SND_SOC_DAPM_REGULATOR_BYPASS), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDL", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDR", 0, 0), + +SND_SOC_DAPM_SIGGEN("TONE"), +SND_SOC_DAPM_SIGGEN("NOISE"), +SND_SOC_DAPM_SIGGEN("HAPTICS"), + +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), + +SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), + +SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN1R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN3L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN3L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN3R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN3R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_SUPPLY("MICBIAS1", ARIZONA_MIC_BIAS_CTRL_1, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", ARIZONA_MIC_BIAS_CTRL_2, + ARIZONA_MICB2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS3", ARIZONA_MIC_BIAS_CTRL_3, + ARIZONA_MICB3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Noise Generator", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Tone Generator 1", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("Tone Generator 2", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE2_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Mic Mute Mixer", ARIZONA_MIC_NOISE_MIX_CONTROL_1, + ARIZONA_MICMUTE_MIX_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("EQ1", ARIZONA_EQ1_1, ARIZONA_EQ1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ2", ARIZONA_EQ2_1, ARIZONA_EQ2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ3", ARIZONA_EQ3_1, ARIZONA_EQ3_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ4", ARIZONA_EQ4_1, ARIZONA_EQ4_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("DRC1L", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC1R", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1R_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", ARIZONA_HPLPF1_1, ARIZONA_LHPF1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", ARIZONA_HPLPF2_1, ARIZONA_LHPF2_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF3", ARIZONA_HPLPF3_1, ARIZONA_LHPF3_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF4", ARIZONA_HPLPF4_1, ARIZONA_LHPF4_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("PWM1 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM1_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_PGA("PWM2 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM2_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_PGA("ASRC1L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC1R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1R_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC2L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC2R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2R_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1INT1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1DEC1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2INT1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2DEC1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_AIF_OUT("AIF1TX1", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX5", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX6", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX7", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX8", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX1", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX5", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX6", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX7", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX8", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF2TX1", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX2", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF2RX1", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX2", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF3TX1", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF3TX2", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF3RX1", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF3RX2", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("SLIMTX1", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX2", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX3", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX4", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX5", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX6", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX7", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX8", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("SLIMRX1", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX2", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX3", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX4", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX5", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX6", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX7", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX8", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX8_ENA_SHIFT, 0), + +ARIZONA_DSP_WIDGETS(DSP1, "DSP1"), + +SND_SOC_DAPM_MUX("AEC Loopback", ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_ENA_SHIFT, 0, &wm5102_aec_loopback_mux), + +SND_SOC_DAPM_PGA_E("OUT1L", SND_SOC_NOPM, + ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT1R", SND_SOC_NOPM, + ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT2L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT2R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT3L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + +ARIZONA_MIXER_WIDGETS(EQ1, "EQ1"), +ARIZONA_MIXER_WIDGETS(EQ2, "EQ2"), +ARIZONA_MIXER_WIDGETS(EQ3, "EQ3"), +ARIZONA_MIXER_WIDGETS(EQ4, "EQ4"), + +ARIZONA_MIXER_WIDGETS(DRC1L, "DRC1L"), +ARIZONA_MIXER_WIDGETS(DRC1R, "DRC1R"), + +ARIZONA_MIXER_WIDGETS(LHPF1, "LHPF1"), +ARIZONA_MIXER_WIDGETS(LHPF2, "LHPF2"), +ARIZONA_MIXER_WIDGETS(LHPF3, "LHPF3"), +ARIZONA_MIXER_WIDGETS(LHPF4, "LHPF4"), + +ARIZONA_MIXER_WIDGETS(Mic, "Mic"), +ARIZONA_MIXER_WIDGETS(Noise, "Noise"), + +ARIZONA_MIXER_WIDGETS(PWM1, "PWM1"), +ARIZONA_MIXER_WIDGETS(PWM2, "PWM2"), + +ARIZONA_MIXER_WIDGETS(OUT1L, "HPOUT1L"), +ARIZONA_MIXER_WIDGETS(OUT1R, "HPOUT1R"), +ARIZONA_MIXER_WIDGETS(OUT2L, "HPOUT2L"), +ARIZONA_MIXER_WIDGETS(OUT2R, "HPOUT2R"), +ARIZONA_MIXER_WIDGETS(OUT3, "EPOUT"), +ARIZONA_MIXER_WIDGETS(SPKOUTL, "SPKOUTL"), +ARIZONA_MIXER_WIDGETS(SPKOUTR, "SPKOUTR"), +ARIZONA_MIXER_WIDGETS(SPKDAT1L, "SPKDAT1L"), +ARIZONA_MIXER_WIDGETS(SPKDAT1R, "SPKDAT1R"), + +ARIZONA_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"), +ARIZONA_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"), +ARIZONA_MIXER_WIDGETS(AIF1TX3, "AIF1TX3"), +ARIZONA_MIXER_WIDGETS(AIF1TX4, "AIF1TX4"), +ARIZONA_MIXER_WIDGETS(AIF1TX5, "AIF1TX5"), +ARIZONA_MIXER_WIDGETS(AIF1TX6, "AIF1TX6"), +ARIZONA_MIXER_WIDGETS(AIF1TX7, "AIF1TX7"), +ARIZONA_MIXER_WIDGETS(AIF1TX8, "AIF1TX8"), + +ARIZONA_MIXER_WIDGETS(AIF2TX1, "AIF2TX1"), +ARIZONA_MIXER_WIDGETS(AIF2TX2, "AIF2TX2"), + +ARIZONA_MIXER_WIDGETS(AIF3TX1, "AIF3TX1"), +ARIZONA_MIXER_WIDGETS(AIF3TX2, "AIF3TX2"), + +ARIZONA_MIXER_WIDGETS(SLIMTX1, "SLIMTX1"), +ARIZONA_MIXER_WIDGETS(SLIMTX2, "SLIMTX2"), +ARIZONA_MIXER_WIDGETS(SLIMTX3, "SLIMTX3"), +ARIZONA_MIXER_WIDGETS(SLIMTX4, "SLIMTX4"), +ARIZONA_MIXER_WIDGETS(SLIMTX5, "SLIMTX5"), +ARIZONA_MIXER_WIDGETS(SLIMTX6, "SLIMTX6"), +ARIZONA_MIXER_WIDGETS(SLIMTX7, "SLIMTX7"), +ARIZONA_MIXER_WIDGETS(SLIMTX8, "SLIMTX8"), + +ARIZONA_MUX_WIDGETS(ASRC1L, "ASRC1L"), +ARIZONA_MUX_WIDGETS(ASRC1R, "ASRC1R"), +ARIZONA_MUX_WIDGETS(ASRC2L, "ASRC2L"), +ARIZONA_MUX_WIDGETS(ASRC2R, "ASRC2R"), + +ARIZONA_MUX_WIDGETS(ISRC1DEC1, "ISRC1DEC1"), +ARIZONA_MUX_WIDGETS(ISRC1DEC2, "ISRC1DEC2"), + +ARIZONA_MUX_WIDGETS(ISRC1INT1, "ISRC1INT1"), +ARIZONA_MUX_WIDGETS(ISRC1INT2, "ISRC1INT2"), + +ARIZONA_MUX_WIDGETS(ISRC2DEC1, "ISRC2DEC1"), +ARIZONA_MUX_WIDGETS(ISRC2DEC2, "ISRC2DEC2"), + +ARIZONA_MUX_WIDGETS(ISRC2INT1, "ISRC2INT1"), +ARIZONA_MUX_WIDGETS(ISRC2INT2, "ISRC2INT2"), + +WM_ADSP2("DSP1", 0, wm5102_adsp_power_ev), + +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("HPOUT2L"), +SND_SOC_DAPM_OUTPUT("HPOUT2R"), +SND_SOC_DAPM_OUTPUT("EPOUTN"), +SND_SOC_DAPM_OUTPUT("EPOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTLN"), +SND_SOC_DAPM_OUTPUT("SPKOUTLP"), +SND_SOC_DAPM_OUTPUT("SPKOUTRN"), +SND_SOC_DAPM_OUTPUT("SPKOUTRP"), +SND_SOC_DAPM_OUTPUT("SPKDAT1L"), +SND_SOC_DAPM_OUTPUT("SPKDAT1R"), + +SND_SOC_DAPM_OUTPUT("MICSUPP"), +}; + +#define ARIZONA_MIXER_INPUT_ROUTES(name) \ + { name, "Noise Generator", "Noise Generator" }, \ + { name, "Tone Generator 1", "Tone Generator 1" }, \ + { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "Haptics", "HAPTICS" }, \ + { name, "AEC", "AEC Loopback" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2L PGA" }, \ + { name, "IN2R", "IN2R PGA" }, \ + { name, "IN3L", "IN3L PGA" }, \ + { name, "IN3R", "IN3R PGA" }, \ + { name, "Mic Mute Mixer", "Mic Mute Mixer" }, \ + { name, "AIF1RX1", "AIF1RX1" }, \ + { name, "AIF1RX2", "AIF1RX2" }, \ + { name, "AIF1RX3", "AIF1RX3" }, \ + { name, "AIF1RX4", "AIF1RX4" }, \ + { name, "AIF1RX5", "AIF1RX5" }, \ + { name, "AIF1RX6", "AIF1RX6" }, \ + { name, "AIF1RX7", "AIF1RX7" }, \ + { name, "AIF1RX8", "AIF1RX8" }, \ + { name, "AIF2RX1", "AIF2RX1" }, \ + { name, "AIF2RX2", "AIF2RX2" }, \ + { name, "AIF3RX1", "AIF3RX1" }, \ + { name, "AIF3RX2", "AIF3RX2" }, \ + { name, "SLIMRX1", "SLIMRX1" }, \ + { name, "SLIMRX2", "SLIMRX2" }, \ + { name, "SLIMRX3", "SLIMRX3" }, \ + { name, "SLIMRX4", "SLIMRX4" }, \ + { name, "SLIMRX5", "SLIMRX5" }, \ + { name, "SLIMRX6", "SLIMRX6" }, \ + { name, "SLIMRX7", "SLIMRX7" }, \ + { name, "SLIMRX8", "SLIMRX8" }, \ + { name, "EQ1", "EQ1" }, \ + { name, "EQ2", "EQ2" }, \ + { name, "EQ3", "EQ3" }, \ + { name, "EQ4", "EQ4" }, \ + { name, "DRC1L", "DRC1L" }, \ + { name, "DRC1R", "DRC1R" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" }, \ + { name, "LHPF3", "LHPF3" }, \ + { name, "LHPF4", "LHPF4" }, \ + { name, "ASRC1L", "ASRC1L" }, \ + { name, "ASRC1R", "ASRC1R" }, \ + { name, "ASRC2L", "ASRC2L" }, \ + { name, "ASRC2R", "ASRC2R" }, \ + { name, "ISRC1DEC1", "ISRC1DEC1" }, \ + { name, "ISRC1DEC2", "ISRC1DEC2" }, \ + { name, "ISRC1INT1", "ISRC1INT1" }, \ + { name, "ISRC1INT2", "ISRC1INT2" }, \ + { name, "ISRC2DEC1", "ISRC2DEC1" }, \ + { name, "ISRC2DEC2", "ISRC2DEC2" }, \ + { name, "ISRC2INT1", "ISRC2INT1" }, \ + { name, "ISRC2INT2", "ISRC2INT2" }, \ + { name, "DSP1.1", "DSP1" }, \ + { name, "DSP1.2", "DSP1" }, \ + { name, "DSP1.3", "DSP1" }, \ + { name, "DSP1.4", "DSP1" }, \ + { name, "DSP1.5", "DSP1" }, \ + { name, "DSP1.6", "DSP1" } + +static const struct snd_soc_dapm_route wm5102_dapm_routes[] = { + { "AIF2 Capture", NULL, "DBVDD2" }, + { "AIF2 Playback", NULL, "DBVDD2" }, + + { "AIF3 Capture", NULL, "DBVDD3" }, + { "AIF3 Playback", NULL, "DBVDD3" }, + + { "OUT1L", NULL, "CPVDD" }, + { "OUT1R", NULL, "CPVDD" }, + { "OUT2L", NULL, "CPVDD" }, + { "OUT2R", NULL, "CPVDD" }, + { "OUT3L", NULL, "CPVDD" }, + + { "OUT4L", NULL, "SPKVDDL" }, + { "OUT4R", NULL, "SPKVDDR" }, + + { "OUT1L", NULL, "SYSCLK" }, + { "OUT1R", NULL, "SYSCLK" }, + { "OUT2L", NULL, "SYSCLK" }, + { "OUT2R", NULL, "SYSCLK" }, + { "OUT3L", NULL, "SYSCLK" }, + { "OUT4L", NULL, "SYSCLK" }, + { "OUT4R", NULL, "SYSCLK" }, + { "OUT5L", NULL, "SYSCLK" }, + { "OUT5R", NULL, "SYSCLK" }, + + { "IN1L", NULL, "SYSCLK" }, + { "IN1R", NULL, "SYSCLK" }, + { "IN2L", NULL, "SYSCLK" }, + { "IN2R", NULL, "SYSCLK" }, + { "IN3L", NULL, "SYSCLK" }, + { "IN3R", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "SYSCLK" }, + { "ASRC1R", NULL, "SYSCLK" }, + { "ASRC2L", NULL, "SYSCLK" }, + { "ASRC2R", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "ASYNCCLK" }, + { "ASRC1R", NULL, "ASYNCCLK" }, + { "ASRC2L", NULL, "ASYNCCLK" }, + { "ASRC2R", NULL, "ASYNCCLK" }, + + { "MICBIAS1", NULL, "MICVDD" }, + { "MICBIAS2", NULL, "MICVDD" }, + { "MICBIAS3", NULL, "MICVDD" }, + + { "Noise Generator", NULL, "SYSCLK" }, + { "Tone Generator 1", NULL, "SYSCLK" }, + { "Tone Generator 2", NULL, "SYSCLK" }, + + { "Noise Generator", NULL, "NOISE" }, + { "Tone Generator 1", NULL, "TONE" }, + { "Tone Generator 2", NULL, "TONE" }, + + { "AIF1 Capture", NULL, "AIF1TX1" }, + { "AIF1 Capture", NULL, "AIF1TX2" }, + { "AIF1 Capture", NULL, "AIF1TX3" }, + { "AIF1 Capture", NULL, "AIF1TX4" }, + { "AIF1 Capture", NULL, "AIF1TX5" }, + { "AIF1 Capture", NULL, "AIF1TX6" }, + { "AIF1 Capture", NULL, "AIF1TX7" }, + { "AIF1 Capture", NULL, "AIF1TX8" }, + + { "AIF1RX1", NULL, "AIF1 Playback" }, + { "AIF1RX2", NULL, "AIF1 Playback" }, + { "AIF1RX3", NULL, "AIF1 Playback" }, + { "AIF1RX4", NULL, "AIF1 Playback" }, + { "AIF1RX5", NULL, "AIF1 Playback" }, + { "AIF1RX6", NULL, "AIF1 Playback" }, + { "AIF1RX7", NULL, "AIF1 Playback" }, + { "AIF1RX8", NULL, "AIF1 Playback" }, + + { "AIF2 Capture", NULL, "AIF2TX1" }, + { "AIF2 Capture", NULL, "AIF2TX2" }, + + { "AIF2RX1", NULL, "AIF2 Playback" }, + { "AIF2RX2", NULL, "AIF2 Playback" }, + + { "AIF3 Capture", NULL, "AIF3TX1" }, + { "AIF3 Capture", NULL, "AIF3TX2" }, + + { "AIF3RX1", NULL, "AIF3 Playback" }, + { "AIF3RX2", NULL, "AIF3 Playback" }, + + { "Slim1 Capture", NULL, "SLIMTX1" }, + { "Slim1 Capture", NULL, "SLIMTX2" }, + { "Slim1 Capture", NULL, "SLIMTX3" }, + { "Slim1 Capture", NULL, "SLIMTX4" }, + + { "SLIMRX1", NULL, "Slim1 Playback" }, + { "SLIMRX2", NULL, "Slim1 Playback" }, + { "SLIMRX3", NULL, "Slim1 Playback" }, + { "SLIMRX4", NULL, "Slim1 Playback" }, + + { "Slim2 Capture", NULL, "SLIMTX5" }, + { "Slim2 Capture", NULL, "SLIMTX6" }, + + { "SLIMRX5", NULL, "Slim2 Playback" }, + { "SLIMRX6", NULL, "Slim2 Playback" }, + + { "Slim3 Capture", NULL, "SLIMTX7" }, + { "Slim3 Capture", NULL, "SLIMTX8" }, + + { "SLIMRX7", NULL, "Slim3 Playback" }, + { "SLIMRX8", NULL, "Slim3 Playback" }, + + { "AIF1 Playback", NULL, "SYSCLK" }, + { "AIF2 Playback", NULL, "SYSCLK" }, + { "AIF3 Playback", NULL, "SYSCLK" }, + { "Slim1 Playback", NULL, "SYSCLK" }, + { "Slim2 Playback", NULL, "SYSCLK" }, + { "Slim3 Playback", NULL, "SYSCLK" }, + + { "AIF1 Capture", NULL, "SYSCLK" }, + { "AIF2 Capture", NULL, "SYSCLK" }, + { "AIF3 Capture", NULL, "SYSCLK" }, + { "Slim1 Capture", NULL, "SYSCLK" }, + { "Slim2 Capture", NULL, "SYSCLK" }, + { "Slim3 Capture", NULL, "SYSCLK" }, + + { "Audio Trace DSP", NULL, "DSP1" }, + + { "IN1L PGA", NULL, "IN1L" }, + { "IN1R PGA", NULL, "IN1R" }, + + { "IN2L PGA", NULL, "IN2L" }, + { "IN2R PGA", NULL, "IN2R" }, + + { "IN3L PGA", NULL, "IN3L" }, + { "IN3R PGA", NULL, "IN3R" }, + + ARIZONA_MIXER_ROUTES("OUT1L", "HPOUT1L"), + ARIZONA_MIXER_ROUTES("OUT1R", "HPOUT1R"), + ARIZONA_MIXER_ROUTES("OUT2L", "HPOUT2L"), + ARIZONA_MIXER_ROUTES("OUT2R", "HPOUT2R"), + ARIZONA_MIXER_ROUTES("OUT3L", "EPOUT"), + + ARIZONA_MIXER_ROUTES("OUT4L", "SPKOUTL"), + ARIZONA_MIXER_ROUTES("OUT4R", "SPKOUTR"), + ARIZONA_MIXER_ROUTES("OUT5L", "SPKDAT1L"), + ARIZONA_MIXER_ROUTES("OUT5R", "SPKDAT1R"), + + ARIZONA_MIXER_ROUTES("PWM1 Driver", "PWM1"), + ARIZONA_MIXER_ROUTES("PWM2 Driver", "PWM2"), + + ARIZONA_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), + ARIZONA_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), + ARIZONA_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), + ARIZONA_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), + ARIZONA_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), + ARIZONA_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), + ARIZONA_MIXER_ROUTES("AIF1TX7", "AIF1TX7"), + ARIZONA_MIXER_ROUTES("AIF1TX8", "AIF1TX8"), + + ARIZONA_MIXER_ROUTES("AIF2TX1", "AIF2TX1"), + ARIZONA_MIXER_ROUTES("AIF2TX2", "AIF2TX2"), + + ARIZONA_MIXER_ROUTES("AIF3TX1", "AIF3TX1"), + ARIZONA_MIXER_ROUTES("AIF3TX2", "AIF3TX2"), + + ARIZONA_MIXER_ROUTES("SLIMTX1", "SLIMTX1"), + ARIZONA_MIXER_ROUTES("SLIMTX2", "SLIMTX2"), + ARIZONA_MIXER_ROUTES("SLIMTX3", "SLIMTX3"), + ARIZONA_MIXER_ROUTES("SLIMTX4", "SLIMTX4"), + ARIZONA_MIXER_ROUTES("SLIMTX5", "SLIMTX5"), + ARIZONA_MIXER_ROUTES("SLIMTX6", "SLIMTX6"), + ARIZONA_MIXER_ROUTES("SLIMTX7", "SLIMTX7"), + ARIZONA_MIXER_ROUTES("SLIMTX8", "SLIMTX8"), + + ARIZONA_MIXER_ROUTES("EQ1", "EQ1"), + ARIZONA_MIXER_ROUTES("EQ2", "EQ2"), + ARIZONA_MIXER_ROUTES("EQ3", "EQ3"), + ARIZONA_MIXER_ROUTES("EQ4", "EQ4"), + + ARIZONA_MIXER_ROUTES("DRC1L", "DRC1L"), + ARIZONA_MIXER_ROUTES("DRC1R", "DRC1R"), + + ARIZONA_MIXER_ROUTES("LHPF1", "LHPF1"), + ARIZONA_MIXER_ROUTES("LHPF2", "LHPF2"), + ARIZONA_MIXER_ROUTES("LHPF3", "LHPF3"), + ARIZONA_MIXER_ROUTES("LHPF4", "LHPF4"), + + ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Noise"), + ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Mic"), + + ARIZONA_MUX_ROUTES("ASRC1L", "ASRC1L"), + ARIZONA_MUX_ROUTES("ASRC1R", "ASRC1R"), + ARIZONA_MUX_ROUTES("ASRC2L", "ASRC2L"), + ARIZONA_MUX_ROUTES("ASRC2R", "ASRC2R"), + + ARIZONA_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), + ARIZONA_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), + + ARIZONA_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), + ARIZONA_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), + + ARIZONA_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), + ARIZONA_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), + + ARIZONA_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), + ARIZONA_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), + + ARIZONA_DSP_ROUTES("DSP1"), + + { "AEC Loopback", "HPOUT1L", "OUT1L" }, + { "AEC Loopback", "HPOUT1R", "OUT1R" }, + { "HPOUT1L", NULL, "OUT1L" }, + { "HPOUT1R", NULL, "OUT1R" }, + + { "AEC Loopback", "HPOUT2L", "OUT2L" }, + { "AEC Loopback", "HPOUT2R", "OUT2R" }, + { "HPOUT2L", NULL, "OUT2L" }, + { "HPOUT2R", NULL, "OUT2R" }, + + { "AEC Loopback", "EPOUT", "OUT3L" }, + { "EPOUTN", NULL, "OUT3L" }, + { "EPOUTP", NULL, "OUT3L" }, + + { "AEC Loopback", "SPKOUTL", "OUT4L" }, + { "SPKOUTLN", NULL, "OUT4L" }, + { "SPKOUTLP", NULL, "OUT4L" }, + + { "AEC Loopback", "SPKOUTR", "OUT4R" }, + { "SPKOUTRN", NULL, "OUT4R" }, + { "SPKOUTRP", NULL, "OUT4R" }, + + { "AEC Loopback", "SPKDAT1L", "OUT5L" }, + { "AEC Loopback", "SPKDAT1R", "OUT5R" }, + { "SPKDAT1L", NULL, "OUT5L" }, + { "SPKDAT1R", NULL, "OUT5R" }, + + { "MICSUPP", NULL, "SYSCLK" }, + + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "DRC1L" }, + { "DRC1 Signal Activity", NULL, "DRC1R" }, +}; + +static int wm5102_set_fll(struct snd_soc_component *component, int fll_id, + int source, unsigned int Fref, unsigned int Fout) +{ + struct wm5102_priv *wm5102 = snd_soc_component_get_drvdata(component); + + switch (fll_id) { + case WM5102_FLL1: + return arizona_set_fll(&wm5102->fll[0], source, Fref, Fout); + case WM5102_FLL2: + return arizona_set_fll(&wm5102->fll[1], source, Fref, Fout); + case WM5102_FLL1_REFCLK: + return arizona_set_fll_refclk(&wm5102->fll[0], source, Fref, + Fout); + case WM5102_FLL2_REFCLK: + return arizona_set_fll_refclk(&wm5102->fll[1], source, Fref, + Fout); + default: + return -EINVAL; + } +} + +#define WM5102_RATES SNDRV_PCM_RATE_KNOT + +#define WM5102_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm5102_dai[] = { + { + .name = "wm5102-aif1", + .id = 1, + .base = ARIZONA_AIF1_BCLK_CTRL, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm5102-aif2", + .id = 2, + .base = ARIZONA_AIF2_BCLK_CTRL, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm5102-aif3", + .id = 3, + .base = ARIZONA_AIF3_BCLK_CTRL, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm5102-slim1", + .id = 4, + .playback = { + .stream_name = "Slim1 Playback", + .channels_min = 1, + .channels_max = 4, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .capture = { + .stream_name = "Slim1 Capture", + .channels_min = 1, + .channels_max = 4, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm5102-slim2", + .id = 5, + .playback = { + .stream_name = "Slim2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .capture = { + .stream_name = "Slim2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm5102-slim3", + .id = 6, + .playback = { + .stream_name = "Slim3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .capture = { + .stream_name = "Slim3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm5102-cpu-trace", + .capture = { + .stream_name = "Audio Trace CPU", + .channels_min = 1, + .channels_max = 4, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + .compress_new = snd_soc_new_compress, + }, + { + .name = "wm5102-dsp-trace", + .capture = { + .stream_name = "Audio Trace DSP", + .channels_min = 1, + .channels_max = 4, + .rates = WM5102_RATES, + .formats = WM5102_FORMATS, + }, + }, +}; + +static int wm5102_open(struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct wm5102_priv *priv = snd_soc_component_get_drvdata(component); + + return wm_adsp_compr_open(&priv->core.adsp[0], stream); +} + +static irqreturn_t wm5102_adsp2_irq(int irq, void *data) +{ + struct wm5102_priv *priv = data; + struct arizona *arizona = priv->core.arizona; + int ret; + + ret = wm_adsp_compr_handle_irq(&priv->core.adsp[0]); + if (ret == -ENODEV) { + dev_err(arizona->dev, "Spurious compressed data IRQ\n"); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int wm5102_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm5102_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->core.arizona; + int ret; + + snd_soc_component_init_regmap(component, arizona->regmap); + + ret = wm_adsp2_component_probe(&priv->core.adsp[0], component); + if (ret) + return ret; + + ret = snd_soc_add_component_controls(component, + arizona_adsp2_rate_controls, 1); + if (ret) + goto err_adsp2_codec_probe; + + ret = arizona_init_spk(component); + if (ret < 0) + return ret; + + arizona_init_gpio(component); + + snd_soc_component_disable_pin(component, "HAPTICS"); + + priv->core.arizona->dapm = dapm; + + return 0; + +err_adsp2_codec_probe: + wm_adsp2_component_remove(&priv->core.adsp[0], component); + + return ret; +} + +static void wm5102_component_remove(struct snd_soc_component *component) +{ + struct wm5102_priv *priv = snd_soc_component_get_drvdata(component); + + wm_adsp2_component_remove(&priv->core.adsp[0], component); + + priv->core.arizona->dapm = NULL; +} + +#define WM5102_DIG_VU 0x0200 + +static unsigned int wm5102_digital_vu[] = { + ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, + ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, + ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, + ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, +}; + +static struct snd_compr_ops wm5102_compr_ops = { + .open = wm5102_open, + .free = wm_adsp_compr_free, + .set_params = wm_adsp_compr_set_params, + .get_caps = wm_adsp_compr_get_caps, + .trigger = wm_adsp_compr_trigger, + .pointer = wm_adsp_compr_pointer, + .copy = wm_adsp_compr_copy, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm5102 = { + .probe = wm5102_component_probe, + .remove = wm5102_component_remove, + .set_sysclk = arizona_set_sysclk, + .set_pll = wm5102_set_fll, + .name = DRV_NAME, + .compr_ops = &wm5102_compr_ops, + .controls = wm5102_snd_controls, + .num_controls = ARRAY_SIZE(wm5102_snd_controls), + .dapm_widgets = wm5102_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm5102_dapm_widgets), + .dapm_routes = wm5102_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm5102_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm5102_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + struct wm5102_priv *wm5102; + int i, ret; + + wm5102 = devm_kzalloc(&pdev->dev, sizeof(struct wm5102_priv), + GFP_KERNEL); + if (wm5102 == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, wm5102); + + if (IS_ENABLED(CONFIG_OF)) { + if (!dev_get_platdata(arizona->dev)) { + ret = arizona_of_get_audio_pdata(arizona); + if (ret < 0) + return ret; + } + } + + mutex_init(&arizona->dac_comp_lock); + + wm5102->core.arizona = arizona; + wm5102->core.num_inputs = 6; + + arizona_init_dvfs(&wm5102->core); + + wm5102->core.adsp[0].part = "wm5102"; + wm5102->core.adsp[0].num = 1; + wm5102->core.adsp[0].type = WMFW_ADSP2; + wm5102->core.adsp[0].base = ARIZONA_DSP1_CONTROL_1; + wm5102->core.adsp[0].dev = arizona->dev; + wm5102->core.adsp[0].regmap = arizona->regmap; + wm5102->core.adsp[0].mem = wm5102_dsp1_regions; + wm5102->core.adsp[0].num_mems = ARRAY_SIZE(wm5102_dsp1_regions); + + ret = wm_adsp2_init(&wm5102->core.adsp[0]); + if (ret != 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(wm5102->fll); i++) + wm5102->fll[i].vco_mult = 1; + + arizona_init_fll(arizona, 1, ARIZONA_FLL1_CONTROL_1 - 1, + ARIZONA_IRQ_FLL1_LOCK, ARIZONA_IRQ_FLL1_CLOCK_OK, + &wm5102->fll[0]); + arizona_init_fll(arizona, 2, ARIZONA_FLL2_CONTROL_1 - 1, + ARIZONA_IRQ_FLL2_LOCK, ARIZONA_IRQ_FLL2_CLOCK_OK, + &wm5102->fll[1]); + + /* SR2 fixed at 8kHz, SR3 fixed at 16kHz */ + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_2, + ARIZONA_SAMPLE_RATE_2_MASK, 0x11); + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_3, + ARIZONA_SAMPLE_RATE_3_MASK, 0x12); + + for (i = 0; i < ARRAY_SIZE(wm5102_dai); i++) + arizona_init_dai(&wm5102->core, i); + + /* Latch volume update bits */ + for (i = 0; i < ARRAY_SIZE(wm5102_digital_vu); i++) + regmap_update_bits(arizona->regmap, wm5102_digital_vu[i], + WM5102_DIG_VU, WM5102_DIG_VU); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, + "ADSP2 Compressed IRQ", wm5102_adsp2_irq, + wm5102); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request DSP IRQ: %d\n", ret); + return ret; + } + + ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 1); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to set compressed IRQ as a wake source: %d\n", + ret); + + arizona_init_common(arizona); + + ret = arizona_init_vol_limit(arizona); + if (ret < 0) + goto err_dsp_irq; + ret = arizona_init_spk_irqs(arizona); + if (ret < 0) + goto err_dsp_irq; + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm5102, + wm5102_dai, + ARRAY_SIZE(wm5102_dai)); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register component: %d\n", ret); + goto err_spk_irqs; + } + + return ret; + +err_spk_irqs: + arizona_free_spk_irqs(arizona); +err_dsp_irq: + arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5102); + + return ret; +} + +static int wm5102_remove(struct platform_device *pdev) +{ + struct wm5102_priv *wm5102 = platform_get_drvdata(pdev); + struct arizona *arizona = wm5102->core.arizona; + + pm_runtime_disable(&pdev->dev); + + wm_adsp2_remove(&wm5102->core.adsp[0]); + + arizona_free_spk_irqs(arizona); + + arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5102); + + return 0; +} + +static struct platform_driver wm5102_codec_driver = { + .driver = { + .name = "wm5102-codec", + }, + .probe = wm5102_probe, + .remove = wm5102_remove, +}; + +module_platform_driver(wm5102_codec_driver); + +MODULE_DESCRIPTION("ASoC WM5102 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm5102-codec"); diff --git a/sound/soc/codecs/wm5102.h b/sound/soc/codecs/wm5102.h new file mode 100644 index 000000000..adb38040f --- /dev/null +++ b/sound/soc/codecs/wm5102.h @@ -0,0 +1,23 @@ +/* + * wm5102.h -- WM5102 ALSA SoC Audio driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM5102_H +#define _WM5102_H + +#include "arizona.h" + +#define WM5102_FLL1 1 +#define WM5102_FLL2 2 +#define WM5102_FLL1_REFCLK 3 +#define WM5102_FLL2_REFCLK 4 + +#endif diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c new file mode 100644 index 000000000..b0789a03d --- /dev/null +++ b/sound/soc/codecs/wm5110.c @@ -0,0 +1,2530 @@ +/* + * wm5110.c -- WM5110 ALSA SoC Audio driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arizona.h" +#include "wm_adsp.h" +#include "wm5110.h" + +#define WM5110_NUM_ADSP 4 + +#define DRV_NAME "wm5110-codec" + +struct wm5110_priv { + struct arizona_priv core; + struct arizona_fll fll[2]; + + unsigned int in_value; + int in_pre_pending; + int in_post_pending; + + unsigned int in_pga_cache[6]; +}; + +static const struct wm_adsp_region wm5110_dsp1_regions[] = { + { .type = WMFW_ADSP2_PM, .base = 0x100000 }, + { .type = WMFW_ADSP2_ZM, .base = 0x180000 }, + { .type = WMFW_ADSP2_XM, .base = 0x190000 }, + { .type = WMFW_ADSP2_YM, .base = 0x1a8000 }, +}; + +static const struct wm_adsp_region wm5110_dsp2_regions[] = { + { .type = WMFW_ADSP2_PM, .base = 0x200000 }, + { .type = WMFW_ADSP2_ZM, .base = 0x280000 }, + { .type = WMFW_ADSP2_XM, .base = 0x290000 }, + { .type = WMFW_ADSP2_YM, .base = 0x2a8000 }, +}; + +static const struct wm_adsp_region wm5110_dsp3_regions[] = { + { .type = WMFW_ADSP2_PM, .base = 0x300000 }, + { .type = WMFW_ADSP2_ZM, .base = 0x380000 }, + { .type = WMFW_ADSP2_XM, .base = 0x390000 }, + { .type = WMFW_ADSP2_YM, .base = 0x3a8000 }, +}; + +static const struct wm_adsp_region wm5110_dsp4_regions[] = { + { .type = WMFW_ADSP2_PM, .base = 0x400000 }, + { .type = WMFW_ADSP2_ZM, .base = 0x480000 }, + { .type = WMFW_ADSP2_XM, .base = 0x490000 }, + { .type = WMFW_ADSP2_YM, .base = 0x4a8000 }, +}; + +static const struct wm_adsp_region *wm5110_dsp_regions[] = { + wm5110_dsp1_regions, + wm5110_dsp2_regions, + wm5110_dsp3_regions, + wm5110_dsp4_regions, +}; + +static const struct reg_default wm5110_sysclk_revd_patch[] = { + { 0x3093, 0x1001 }, + { 0x30E3, 0x1301 }, + { 0x3133, 0x1201 }, + { 0x3183, 0x1501 }, + { 0x31D3, 0x1401 }, + { 0x0049, 0x01ea }, + { 0x004a, 0x01f2 }, + { 0x0057, 0x01e7 }, + { 0x0058, 0x01fb }, + { 0x33ce, 0xc4f5 }, + { 0x33cf, 0x1361 }, + { 0x33d0, 0x0402 }, + { 0x33d1, 0x4700 }, + { 0x33d2, 0x026d }, + { 0x33d3, 0xff00 }, + { 0x33d4, 0x026d }, + { 0x33d5, 0x0101 }, + { 0x33d6, 0xc4f5 }, + { 0x33d7, 0x0361 }, + { 0x33d8, 0x0402 }, + { 0x33d9, 0x6701 }, + { 0x33da, 0xc4f5 }, + { 0x33db, 0x136f }, + { 0x33dc, 0xc4f5 }, + { 0x33dd, 0x134f }, + { 0x33de, 0xc4f5 }, + { 0x33df, 0x131f }, + { 0x33e0, 0x026d }, + { 0x33e1, 0x4f01 }, + { 0x33e2, 0x026d }, + { 0x33e3, 0xf100 }, + { 0x33e4, 0x026d }, + { 0x33e5, 0x0001 }, + { 0x33e6, 0xc4f5 }, + { 0x33e7, 0x0361 }, + { 0x33e8, 0x0402 }, + { 0x33e9, 0x6601 }, + { 0x33ea, 0xc4f5 }, + { 0x33eb, 0x136f }, + { 0x33ec, 0xc4f5 }, + { 0x33ed, 0x134f }, + { 0x33ee, 0xc4f5 }, + { 0x33ef, 0x131f }, + { 0x33f0, 0x026d }, + { 0x33f1, 0x4e01 }, + { 0x33f2, 0x026d }, + { 0x33f3, 0xf000 }, + { 0x33f6, 0xc4f5 }, + { 0x33f7, 0x1361 }, + { 0x33f8, 0x0402 }, + { 0x33f9, 0x4600 }, + { 0x33fa, 0x026d }, + { 0x33fb, 0xfe00 }, +}; + +static const struct reg_default wm5110_sysclk_reve_patch[] = { + { 0x3270, 0xE410 }, + { 0x3271, 0x3078 }, + { 0x3272, 0xE410 }, + { 0x3273, 0x3070 }, + { 0x3274, 0xE410 }, + { 0x3275, 0x3066 }, + { 0x3276, 0xE410 }, + { 0x3277, 0x3056 }, + { 0x327A, 0xE414 }, + { 0x327B, 0x3078 }, + { 0x327C, 0xE414 }, + { 0x327D, 0x3070 }, + { 0x327E, 0xE414 }, + { 0x327F, 0x3066 }, + { 0x3280, 0xE414 }, + { 0x3281, 0x3056 }, +}; + +static int wm5110_sysclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct regmap *regmap = arizona->regmap; + const struct reg_default *patch = NULL; + int i, patch_size; + + switch (arizona->rev) { + case 3: + patch = wm5110_sysclk_revd_patch; + patch_size = ARRAY_SIZE(wm5110_sysclk_revd_patch); + break; + default: + patch = wm5110_sysclk_reve_patch; + patch_size = ARRAY_SIZE(wm5110_sysclk_reve_patch); + break; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (patch) + for (i = 0; i < patch_size; i++) + regmap_write_async(regmap, patch[i].reg, + patch[i].def); + break; + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_POST_PMD: + return arizona_clk_ev(w, kcontrol, event); + default: + break; + } + + return 0; +} + +static int wm5110_adsp_power_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + unsigned int v; + int ret; + + ret = regmap_read(arizona->regmap, ARIZONA_SYSTEM_CLOCK_1, &v); + if (ret != 0) { + dev_err(component->dev, "Failed to read SYSCLK state: %d\n", ret); + return ret; + } + + v = (v & ARIZONA_SYSCLK_FREQ_MASK) >> ARIZONA_SYSCLK_FREQ_SHIFT; + + return wm_adsp2_early_event(w, kcontrol, event, v); +} + +static const struct reg_sequence wm5110_no_dre_left_enable[] = { + { 0x3024, 0xE410 }, + { 0x3025, 0x0056 }, + { 0x301B, 0x0224 }, + { 0x301F, 0x4263 }, + { 0x3021, 0x5291 }, + { 0x3030, 0xE410 }, + { 0x3031, 0x3066 }, + { 0x3032, 0xE410 }, + { 0x3033, 0x3070 }, + { 0x3034, 0xE410 }, + { 0x3035, 0x3078 }, + { 0x3036, 0xE410 }, + { 0x3037, 0x3080 }, + { 0x3038, 0xE410 }, + { 0x3039, 0x3080 }, +}; + +static const struct reg_sequence wm5110_dre_left_enable[] = { + { 0x3024, 0x0231 }, + { 0x3025, 0x0B00 }, + { 0x301B, 0x0227 }, + { 0x301F, 0x4266 }, + { 0x3021, 0x5294 }, + { 0x3030, 0xE231 }, + { 0x3031, 0x0266 }, + { 0x3032, 0x8231 }, + { 0x3033, 0x4B15 }, + { 0x3034, 0x8231 }, + { 0x3035, 0x0B15 }, + { 0x3036, 0xE231 }, + { 0x3037, 0x5294 }, + { 0x3038, 0x0231 }, + { 0x3039, 0x0B00 }, +}; + +static const struct reg_sequence wm5110_no_dre_right_enable[] = { + { 0x3074, 0xE414 }, + { 0x3075, 0x0056 }, + { 0x306B, 0x0224 }, + { 0x306F, 0x4263 }, + { 0x3071, 0x5291 }, + { 0x3080, 0xE414 }, + { 0x3081, 0x3066 }, + { 0x3082, 0xE414 }, + { 0x3083, 0x3070 }, + { 0x3084, 0xE414 }, + { 0x3085, 0x3078 }, + { 0x3086, 0xE414 }, + { 0x3087, 0x3080 }, + { 0x3088, 0xE414 }, + { 0x3089, 0x3080 }, +}; + +static const struct reg_sequence wm5110_dre_right_enable[] = { + { 0x3074, 0x0231 }, + { 0x3075, 0x0B00 }, + { 0x306B, 0x0227 }, + { 0x306F, 0x4266 }, + { 0x3071, 0x5294 }, + { 0x3080, 0xE231 }, + { 0x3081, 0x0266 }, + { 0x3082, 0x8231 }, + { 0x3083, 0x4B17 }, + { 0x3084, 0x8231 }, + { 0x3085, 0x0B17 }, + { 0x3086, 0xE231 }, + { 0x3087, 0x5294 }, + { 0x3088, 0x0231 }, + { 0x3089, 0x0B00 }, +}; + +static int wm5110_hp_pre_enable(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + unsigned int val = snd_soc_component_read32(component, ARIZONA_DRE_ENABLE); + const struct reg_sequence *wseq; + int nregs; + + switch (w->shift) { + case ARIZONA_OUT1L_ENA_SHIFT: + if (val & ARIZONA_DRE1L_ENA_MASK) { + wseq = wm5110_dre_left_enable; + nregs = ARRAY_SIZE(wm5110_dre_left_enable); + } else { + wseq = wm5110_no_dre_left_enable; + nregs = ARRAY_SIZE(wm5110_no_dre_left_enable); + priv->out_up_delay += 10; + } + break; + case ARIZONA_OUT1R_ENA_SHIFT: + if (val & ARIZONA_DRE1R_ENA_MASK) { + wseq = wm5110_dre_right_enable; + nregs = ARRAY_SIZE(wm5110_dre_right_enable); + } else { + wseq = wm5110_no_dre_right_enable; + nregs = ARRAY_SIZE(wm5110_no_dre_right_enable); + priv->out_up_delay += 10; + } + break; + default: + return 0; + } + + return regmap_multi_reg_write(arizona->regmap, wseq, nregs); +} + +static int wm5110_hp_pre_disable(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int val = snd_soc_component_read32(component, ARIZONA_DRE_ENABLE); + + switch (w->shift) { + case ARIZONA_OUT1L_ENA_SHIFT: + if (!(val & ARIZONA_DRE1L_ENA_MASK)) { + snd_soc_component_update_bits(component, + ARIZONA_SPARE_TRIGGERS, + ARIZONA_WS_TRG1, + ARIZONA_WS_TRG1); + snd_soc_component_update_bits(component, + ARIZONA_SPARE_TRIGGERS, + ARIZONA_WS_TRG1, 0); + priv->out_down_delay += 27; + } + break; + case ARIZONA_OUT1R_ENA_SHIFT: + if (!(val & ARIZONA_DRE1R_ENA_MASK)) { + snd_soc_component_update_bits(component, + ARIZONA_SPARE_TRIGGERS, + ARIZONA_WS_TRG2, + ARIZONA_WS_TRG2); + snd_soc_component_update_bits(component, + ARIZONA_SPARE_TRIGGERS, + ARIZONA_WS_TRG2, 0); + priv->out_down_delay += 27; + } + break; + default: + break; + } + + return 0; +} + +static int wm5110_hp_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + + switch (priv->arizona->rev) { + case 0 ... 3: + break; + default: + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wm5110_hp_pre_enable(w); + break; + case SND_SOC_DAPM_PRE_PMD: + wm5110_hp_pre_disable(w); + break; + default: + break; + } + break; + } + + return arizona_hp_ev(w, kcontrol, event); +} + +static int wm5110_clear_pga_volume(struct arizona *arizona, int output) +{ + unsigned int reg = ARIZONA_OUTPUT_PATH_CONFIG_1L + output * 4; + int ret; + + ret = regmap_write(arizona->regmap, reg, 0x80); + if (ret) + dev_err(arizona->dev, "Failed to clear PGA (0x%x): %d\n", + reg, ret); + + return ret; +} + +static int wm5110_put_dre(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int ena, dre; + unsigned int mask = (0x1 << mc->shift) | (0x1 << mc->rshift); + unsigned int lnew = (!!ucontrol->value.integer.value[0]) << mc->shift; + unsigned int rnew = (!!ucontrol->value.integer.value[1]) << mc->rshift; + unsigned int lold, rold; + unsigned int lena, rena; + int ret; + + snd_soc_dapm_mutex_lock(dapm); + + ret = regmap_read(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, &ena); + if (ret) { + dev_err(arizona->dev, "Failed to read output state: %d\n", ret); + goto err; + } + ret = regmap_read(arizona->regmap, ARIZONA_DRE_ENABLE, &dre); + if (ret) { + dev_err(arizona->dev, "Failed to read DRE state: %d\n", ret); + goto err; + } + + lold = dre & (1 << mc->shift); + rold = dre & (1 << mc->rshift); + /* Enables are channel wise swapped from the DRE enables */ + lena = ena & (1 << mc->rshift); + rena = ena & (1 << mc->shift); + + if ((lena && lnew != lold) || (rena && rnew != rold)) { + dev_err(arizona->dev, "Can't change DRE on active outputs\n"); + ret = -EBUSY; + goto err; + } + + ret = regmap_update_bits(arizona->regmap, ARIZONA_DRE_ENABLE, + mask, lnew | rnew); + if (ret) { + dev_err(arizona->dev, "Failed to set DRE: %d\n", ret); + goto err; + } + + /* Force reset of PGA volumes, if turning DRE off */ + if (!lnew && lold) + wm5110_clear_pga_volume(arizona, mc->shift); + + if (!rnew && rold) + wm5110_clear_pga_volume(arizona, mc->rshift); + +err: + snd_soc_dapm_mutex_unlock(dapm); + + return ret; +} + +static int wm5110_in_pga_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int ret; + + /* + * PGA Volume is also used as part of the enable sequence, so + * usage of it should be avoided whilst that is running. + */ + snd_soc_dapm_mutex_lock(dapm); + + ret = snd_soc_get_volsw_range(kcontrol, ucontrol); + + snd_soc_dapm_mutex_unlock(dapm); + + return ret; +} + +static int wm5110_in_pga_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int ret; + + /* + * PGA Volume is also used as part of the enable sequence, so + * usage of it should be avoided whilst that is running. + */ + snd_soc_dapm_mutex_lock(dapm); + + ret = snd_soc_put_volsw_range(kcontrol, ucontrol); + + snd_soc_dapm_mutex_unlock(dapm); + + return ret; +} + +static int wm5110_in_analog_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct wm5110_priv *wm5110 = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + unsigned int reg, mask; + struct reg_sequence analog_seq[] = { + { 0x80, 0x3 }, + { 0x35d, 0 }, + { 0x80, 0x0 }, + }; + + reg = ARIZONA_IN1L_CONTROL + ((w->shift ^ 0x1) * 4); + mask = ARIZONA_IN1L_PGA_VOL_MASK; + + switch (event) { + case SND_SOC_DAPM_WILL_PMU: + wm5110->in_value |= 0x3 << ((w->shift ^ 0x1) * 2); + wm5110->in_pre_pending++; + wm5110->in_post_pending++; + return 0; + case SND_SOC_DAPM_PRE_PMU: + wm5110->in_pga_cache[w->shift] = snd_soc_component_read32(component, reg); + + snd_soc_component_update_bits(component, reg, mask, + 0x40 << ARIZONA_IN1L_PGA_VOL_SHIFT); + + wm5110->in_pre_pending--; + if (wm5110->in_pre_pending == 0) { + analog_seq[1].def = wm5110->in_value; + regmap_multi_reg_write_bypassed(arizona->regmap, + analog_seq, + ARRAY_SIZE(analog_seq)); + + msleep(55); + + wm5110->in_value = 0; + } + + break; + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, reg, mask, + wm5110->in_pga_cache[w->shift]); + + wm5110->in_post_pending--; + if (wm5110->in_post_pending == 0) + regmap_multi_reg_write_bypassed(arizona->regmap, + analog_seq, + ARRAY_SIZE(analog_seq)); + break; + default: + break; + } + + return 0; +} + +static int wm5110_in_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->arizona; + + switch (arizona->rev) { + case 0 ... 4: + if (arizona_input_analog(component, w->shift)) + wm5110_in_analog_ev(w, kcontrol, event); + + break; + default: + break; + } + + return arizona_in_ev(w, kcontrol, event); +} + +static DECLARE_TLV_DB_SCALE(ana_tlv, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); +static DECLARE_TLV_DB_SCALE(noise_tlv, -13200, 600, 0); +static DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); + +#define WM5110_NG_SRC(name, base) \ + SOC_SINGLE(name " NG HPOUT1L Switch", base, 0, 1, 0), \ + SOC_SINGLE(name " NG HPOUT1R Switch", base, 1, 1, 0), \ + SOC_SINGLE(name " NG HPOUT2L Switch", base, 2, 1, 0), \ + SOC_SINGLE(name " NG HPOUT2R Switch", base, 3, 1, 0), \ + SOC_SINGLE(name " NG HPOUT3L Switch", base, 4, 1, 0), \ + SOC_SINGLE(name " NG HPOUT3R Switch", base, 5, 1, 0), \ + SOC_SINGLE(name " NG SPKOUTL Switch", base, 6, 1, 0), \ + SOC_SINGLE(name " NG SPKOUTR Switch", base, 7, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT1L Switch", base, 8, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT1R Switch", base, 9, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT2L Switch", base, 10, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT2R Switch", base, 11, 1, 0) + +#define WM5110_RXANC_INPUT_ROUTES(widget, name) \ + { widget, NULL, name " NG Mux" }, \ + { name " NG Internal", NULL, "RXANC NG Clock" }, \ + { name " NG Internal", NULL, name " Channel" }, \ + { name " NG External", NULL, "RXANC NG External Clock" }, \ + { name " NG External", NULL, name " Channel" }, \ + { name " NG Mux", "None", name " Channel" }, \ + { name " NG Mux", "Internal", name " NG Internal" }, \ + { name " NG Mux", "External", name " NG External" }, \ + { name " Channel", "Left", name " Left Input" }, \ + { name " Channel", "Combine", name " Left Input" }, \ + { name " Channel", "Right", name " Right Input" }, \ + { name " Channel", "Combine", name " Right Input" }, \ + { name " Left Input", "IN1", "IN1L PGA" }, \ + { name " Right Input", "IN1", "IN1R PGA" }, \ + { name " Left Input", "IN2", "IN2L PGA" }, \ + { name " Right Input", "IN2", "IN2R PGA" }, \ + { name " Left Input", "IN3", "IN3L PGA" }, \ + { name " Right Input", "IN3", "IN3R PGA" }, \ + { name " Left Input", "IN4", "IN4L PGA" }, \ + { name " Right Input", "IN4", "IN4R PGA" } + +#define WM5110_RXANC_OUTPUT_ROUTES(widget, name) \ + { widget, NULL, name " ANC Source" }, \ + { name " ANC Source", "RXANCL", "RXANCL" }, \ + { name " ANC Source", "RXANCR", "RXANCR" } + +static const struct snd_kcontrol_new wm5110_snd_controls[] = { +SOC_ENUM("IN1 OSR", arizona_in_dmic_osr[0]), +SOC_ENUM("IN2 OSR", arizona_in_dmic_osr[1]), +SOC_ENUM("IN3 OSR", arizona_in_dmic_osr[2]), +SOC_ENUM("IN4 OSR", arizona_in_dmic_osr[3]), + +SOC_SINGLE_RANGE_EXT_TLV("IN1L Volume", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, + wm5110_in_pga_get, wm5110_in_pga_put, ana_tlv), +SOC_SINGLE_RANGE_EXT_TLV("IN1R Volume", ARIZONA_IN1R_CONTROL, + ARIZONA_IN1R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, + wm5110_in_pga_get, wm5110_in_pga_put, ana_tlv), +SOC_SINGLE_RANGE_EXT_TLV("IN2L Volume", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, + wm5110_in_pga_get, wm5110_in_pga_put, ana_tlv), +SOC_SINGLE_RANGE_EXT_TLV("IN2R Volume", ARIZONA_IN2R_CONTROL, + ARIZONA_IN2R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, + wm5110_in_pga_get, wm5110_in_pga_put, ana_tlv), +SOC_SINGLE_RANGE_EXT_TLV("IN3L Volume", ARIZONA_IN3L_CONTROL, + ARIZONA_IN3L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, + wm5110_in_pga_get, wm5110_in_pga_put, ana_tlv), +SOC_SINGLE_RANGE_EXT_TLV("IN3R Volume", ARIZONA_IN3R_CONTROL, + ARIZONA_IN3R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, + wm5110_in_pga_get, wm5110_in_pga_put, ana_tlv), + +SOC_ENUM("IN HPF Cutoff Frequency", arizona_in_hpf_cut_enum), + +SOC_SINGLE("IN1L HPF Switch", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1L_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN1R HPF Switch", ARIZONA_IN1R_CONTROL, + ARIZONA_IN1R_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN2L HPF Switch", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2L_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN2R HPF Switch", ARIZONA_IN2R_CONTROL, + ARIZONA_IN2R_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN3L HPF Switch", ARIZONA_IN3L_CONTROL, + ARIZONA_IN3L_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN3R HPF Switch", ARIZONA_IN3R_CONTROL, + ARIZONA_IN3R_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN4L HPF Switch", ARIZONA_IN4L_CONTROL, + ARIZONA_IN4L_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN4R HPF Switch", ARIZONA_IN4R_CONTROL, + ARIZONA_IN4R_HPF_SHIFT, 1, 0), + +SOC_SINGLE_TLV("IN1L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1L, + ARIZONA_IN1L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN1R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1R, + ARIZONA_IN1R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2L, + ARIZONA_IN2L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2R, + ARIZONA_IN2R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN3L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_3L, + ARIZONA_IN3L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN3R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_3R, + ARIZONA_IN3R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN4L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_4L, + ARIZONA_IN4L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN4R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_4R, + ARIZONA_IN4R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), + +SOC_ENUM("Input Ramp Up", arizona_in_vi_ramp), +SOC_ENUM("Input Ramp Down", arizona_in_vd_ramp), + +SND_SOC_BYTES("RXANC Coefficients", ARIZONA_ANC_COEFF_START, + ARIZONA_ANC_COEFF_END - ARIZONA_ANC_COEFF_START + 1), +SND_SOC_BYTES("RXANCL Config", ARIZONA_FCL_FILTER_CONTROL, 1), +SND_SOC_BYTES("RXANCL Coefficients", ARIZONA_FCL_COEFF_START, + ARIZONA_FCL_COEFF_END - ARIZONA_FCL_COEFF_START + 1), +SND_SOC_BYTES("RXANCR Config", ARIZONA_FCR_FILTER_CONTROL, 1), +SND_SOC_BYTES("RXANCR Coefficients", ARIZONA_FCR_COEFF_START, + ARIZONA_FCR_COEFF_END - ARIZONA_FCR_COEFF_START + 1), + +ARIZONA_MIXER_CONTROLS("EQ1", ARIZONA_EQ1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ2", ARIZONA_EQ2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ3", ARIZONA_EQ3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ4", ARIZONA_EQ4MIX_INPUT_1_SOURCE), + +ARIZONA_EQ_CONTROL("EQ1 Coefficients", ARIZONA_EQ1_2), +SOC_SINGLE_TLV("EQ1 B1 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B2 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B3 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B4 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B5 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ2 Coefficients", ARIZONA_EQ2_2), +SOC_SINGLE_TLV("EQ2 B1 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B2 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B3 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B4 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B5 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ3 Coefficients", ARIZONA_EQ3_2), +SOC_SINGLE_TLV("EQ3 B1 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B2 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B3 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B4 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B5 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ4 Coefficients", ARIZONA_EQ4_2), +SOC_SINGLE_TLV("EQ4 B1 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B2 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B3 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B4 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B5 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_MIXER_CONTROLS("DRC1L", ARIZONA_DRC1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC1R", ARIZONA_DRC1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC2L", ARIZONA_DRC2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC2R", ARIZONA_DRC2RMIX_INPUT_1_SOURCE), + +SND_SOC_BYTES_MASK("DRC1", ARIZONA_DRC1_CTRL1, 5, + ARIZONA_DRC1R_ENA | ARIZONA_DRC1L_ENA), +SND_SOC_BYTES_MASK("DRC2", ARIZONA_DRC2_CTRL1, 5, + ARIZONA_DRC2R_ENA | ARIZONA_DRC2L_ENA), + +ARIZONA_MIXER_CONTROLS("LHPF1", ARIZONA_HPLP1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF2", ARIZONA_HPLP2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF3", ARIZONA_HPLP3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF4", ARIZONA_HPLP4MIX_INPUT_1_SOURCE), + +ARIZONA_LHPF_CONTROL("LHPF1 Coefficients", ARIZONA_HPLPF1_2), +ARIZONA_LHPF_CONTROL("LHPF2 Coefficients", ARIZONA_HPLPF2_2), +ARIZONA_LHPF_CONTROL("LHPF3 Coefficients", ARIZONA_HPLPF3_2), +ARIZONA_LHPF_CONTROL("LHPF4 Coefficients", ARIZONA_HPLPF4_2), + +SOC_ENUM("LHPF1 Mode", arizona_lhpf1_mode), +SOC_ENUM("LHPF2 Mode", arizona_lhpf2_mode), +SOC_ENUM("LHPF3 Mode", arizona_lhpf3_mode), +SOC_ENUM("LHPF4 Mode", arizona_lhpf4_mode), + +SOC_ENUM("ISRC1 FSL", arizona_isrc_fsl[0]), +SOC_ENUM("ISRC2 FSL", arizona_isrc_fsl[1]), +SOC_ENUM("ISRC3 FSL", arizona_isrc_fsl[2]), +SOC_ENUM("ISRC1 FSH", arizona_isrc_fsh[0]), +SOC_ENUM("ISRC2 FSH", arizona_isrc_fsh[1]), +SOC_ENUM("ISRC3 FSH", arizona_isrc_fsh[2]), +SOC_ENUM("ASRC RATE 1", arizona_asrc_rate1), + +WM_ADSP2_PRELOAD_SWITCH("DSP1", 1), +WM_ADSP2_PRELOAD_SWITCH("DSP2", 2), +WM_ADSP2_PRELOAD_SWITCH("DSP3", 3), +WM_ADSP2_PRELOAD_SWITCH("DSP4", 4), + +ARIZONA_MIXER_CONTROLS("DSP1L", ARIZONA_DSP1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP1R", ARIZONA_DSP1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP2L", ARIZONA_DSP2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP2R", ARIZONA_DSP2RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP3L", ARIZONA_DSP3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP3R", ARIZONA_DSP3RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP4L", ARIZONA_DSP4LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DSP4R", ARIZONA_DSP4RMIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("Mic", ARIZONA_MICMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("Noise", ARIZONA_NOISEMIX_INPUT_1_SOURCE), + +SOC_SINGLE_TLV("Noise Generator Volume", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_GAIN_SHIFT, 0x16, 0, noise_tlv), + +ARIZONA_MIXER_CONTROLS("HPOUT1L", ARIZONA_OUT1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT1R", ARIZONA_OUT1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT2L", ARIZONA_OUT2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT2R", ARIZONA_OUT2RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT3L", ARIZONA_OUT3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT3R", ARIZONA_OUT3RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUTL", ARIZONA_OUT4LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUTR", ARIZONA_OUT4RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT1L", ARIZONA_OUT5LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT1R", ARIZONA_OUT5RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT2L", ARIZONA_OUT6LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT2R", ARIZONA_OUT6RMIX_INPUT_1_SOURCE), + +SOC_SINGLE("HPOUT1 SC Protect Switch", ARIZONA_HP1_SHORT_CIRCUIT_CTRL, + ARIZONA_HP1_SC_ENA_SHIFT, 1, 0), +SOC_SINGLE("HPOUT2 SC Protect Switch", ARIZONA_HP2_SHORT_CIRCUIT_CTRL, + ARIZONA_HP2_SC_ENA_SHIFT, 1, 0), +SOC_SINGLE("HPOUT3 SC Protect Switch", ARIZONA_HP3_SHORT_CIRCUIT_CTRL, + ARIZONA_HP3_SC_ENA_SHIFT, 1, 0), + +SOC_SINGLE("SPKDAT1 High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_5L, + ARIZONA_OUT5_OSR_SHIFT, 1, 0), +SOC_SINGLE("SPKDAT2 High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_6L, + ARIZONA_OUT6_OSR_SHIFT, 1, 0), + +SOC_DOUBLE_R("HPOUT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("HPOUT2 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("HPOUT3 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_3R, ARIZONA_OUT3L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("Speaker Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKDAT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKDAT2 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_6L, + ARIZONA_DAC_DIGITAL_VOLUME_6R, ARIZONA_OUT6L_MUTE_SHIFT, 1, 1), + +SOC_DOUBLE_R_TLV("HPOUT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("HPOUT2 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("HPOUT3 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_3R, ARIZONA_OUT3L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("Speaker Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("SPKDAT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("SPKDAT2 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_6L, + ARIZONA_DAC_DIGITAL_VOLUME_6R, ARIZONA_OUT6L_VOL_SHIFT, + 0xbf, 0, digital_tlv), + +SOC_DOUBLE("SPKDAT1 Switch", ARIZONA_PDM_SPK1_CTRL_1, ARIZONA_SPK1L_MUTE_SHIFT, + ARIZONA_SPK1R_MUTE_SHIFT, 1, 1), +SOC_DOUBLE("SPKDAT2 Switch", ARIZONA_PDM_SPK2_CTRL_1, ARIZONA_SPK2L_MUTE_SHIFT, + ARIZONA_SPK2R_MUTE_SHIFT, 1, 1), + +SOC_DOUBLE_EXT("HPOUT1 DRE Switch", ARIZONA_DRE_ENABLE, + ARIZONA_DRE1L_ENA_SHIFT, ARIZONA_DRE1R_ENA_SHIFT, 1, 0, + snd_soc_get_volsw, wm5110_put_dre), +SOC_DOUBLE_EXT("HPOUT2 DRE Switch", ARIZONA_DRE_ENABLE, + ARIZONA_DRE2L_ENA_SHIFT, ARIZONA_DRE2R_ENA_SHIFT, 1, 0, + snd_soc_get_volsw, wm5110_put_dre), +SOC_DOUBLE_EXT("HPOUT3 DRE Switch", ARIZONA_DRE_ENABLE, + ARIZONA_DRE3L_ENA_SHIFT, ARIZONA_DRE3R_ENA_SHIFT, 1, 0, + snd_soc_get_volsw, wm5110_put_dre), + +SOC_ENUM("Output Ramp Up", arizona_out_vi_ramp), +SOC_ENUM("Output Ramp Down", arizona_out_vd_ramp), + +SOC_SINGLE("Noise Gate Switch", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_ENA_SHIFT, 1, 0), +SOC_SINGLE_TLV("Noise Gate Threshold Volume", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_THR_SHIFT, 7, 1, ng_tlv), +SOC_ENUM("Noise Gate Hold", arizona_ng_hold), + +WM5110_NG_SRC("HPOUT1L", ARIZONA_NOISE_GATE_SELECT_1L), +WM5110_NG_SRC("HPOUT1R", ARIZONA_NOISE_GATE_SELECT_1R), +WM5110_NG_SRC("HPOUT2L", ARIZONA_NOISE_GATE_SELECT_2L), +WM5110_NG_SRC("HPOUT2R", ARIZONA_NOISE_GATE_SELECT_2R), +WM5110_NG_SRC("HPOUT3L", ARIZONA_NOISE_GATE_SELECT_3L), +WM5110_NG_SRC("HPOUT3R", ARIZONA_NOISE_GATE_SELECT_3R), +WM5110_NG_SRC("SPKOUTL", ARIZONA_NOISE_GATE_SELECT_4L), +WM5110_NG_SRC("SPKOUTR", ARIZONA_NOISE_GATE_SELECT_4R), +WM5110_NG_SRC("SPKDAT1L", ARIZONA_NOISE_GATE_SELECT_5L), +WM5110_NG_SRC("SPKDAT1R", ARIZONA_NOISE_GATE_SELECT_5R), +WM5110_NG_SRC("SPKDAT2L", ARIZONA_NOISE_GATE_SELECT_6L), +WM5110_NG_SRC("SPKDAT2R", ARIZONA_NOISE_GATE_SELECT_6R), + +ARIZONA_MIXER_CONTROLS("AIF1TX1", ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX2", ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX3", ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX4", ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX5", ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX6", ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX7", ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX8", ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF2TX1", ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX2", ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX3", ARIZONA_AIF2TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX4", ARIZONA_AIF2TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX5", ARIZONA_AIF2TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX6", ARIZONA_AIF2TX6MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF3TX1", ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF3TX2", ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("SLIMTX1", ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX2", ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX3", ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX4", ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX5", ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX6", ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX7", ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX8", ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE), + +WM_ADSP_FW_CONTROL("DSP1", 0), +WM_ADSP_FW_CONTROL("DSP2", 1), +WM_ADSP_FW_CONTROL("DSP3", 2), +WM_ADSP_FW_CONTROL("DSP4", 3), +}; + +ARIZONA_MIXER_ENUMS(EQ1, ARIZONA_EQ1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ2, ARIZONA_EQ2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ3, ARIZONA_EQ3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ4, ARIZONA_EQ4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DRC1L, ARIZONA_DRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC1R, ARIZONA_DRC1RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC2L, ARIZONA_DRC2LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC2R, ARIZONA_DRC2RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(LHPF1, ARIZONA_HPLP1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF2, ARIZONA_HPLP2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF3, ARIZONA_HPLP3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF4, ARIZONA_HPLP4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DSP1L, ARIZONA_DSP1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DSP1R, ARIZONA_DSP1RMIX_INPUT_1_SOURCE); +ARIZONA_DSP_AUX_ENUMS(DSP1, ARIZONA_DSP1AUX1MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DSP2L, ARIZONA_DSP2LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DSP2R, ARIZONA_DSP2RMIX_INPUT_1_SOURCE); +ARIZONA_DSP_AUX_ENUMS(DSP2, ARIZONA_DSP2AUX1MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DSP3L, ARIZONA_DSP3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DSP3R, ARIZONA_DSP3RMIX_INPUT_1_SOURCE); +ARIZONA_DSP_AUX_ENUMS(DSP3, ARIZONA_DSP3AUX1MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DSP4L, ARIZONA_DSP4LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DSP4R, ARIZONA_DSP4RMIX_INPUT_1_SOURCE); +ARIZONA_DSP_AUX_ENUMS(DSP4, ARIZONA_DSP4AUX1MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(Mic, ARIZONA_MICMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(Noise, ARIZONA_NOISEMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(PWM1, ARIZONA_PWM1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(PWM2, ARIZONA_PWM2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(OUT1L, ARIZONA_OUT1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT1R, ARIZONA_OUT1RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT2L, ARIZONA_OUT2LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT2R, ARIZONA_OUT2RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT3L, ARIZONA_OUT3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT3R, ARIZONA_OUT3RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUTL, ARIZONA_OUT4LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUTR, ARIZONA_OUT4RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT1L, ARIZONA_OUT5LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT1R, ARIZONA_OUT5RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT2L, ARIZONA_OUT6LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT2R, ARIZONA_OUT6RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF1TX1, ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX2, ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX3, ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX4, ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX5, ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX6, ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX7, ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX8, ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF2TX1, ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX2, ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX3, ARIZONA_AIF2TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX4, ARIZONA_AIF2TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX5, ARIZONA_AIF2TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX6, ARIZONA_AIF2TX6MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF3TX1, ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF3TX2, ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(SLIMTX1, ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX2, ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX3, ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX4, ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX5, ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX6, ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX7, ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX8, ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ASRC1L, ARIZONA_ASRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC1R, ARIZONA_ASRC1RMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2L, ARIZONA_ASRC2LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2R, ARIZONA_ASRC2RMIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1INT1, ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT2, ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT3, ARIZONA_ISRC1INT3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT4, ARIZONA_ISRC1INT4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1DEC1, ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC2, ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC3, ARIZONA_ISRC1DEC3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC4, ARIZONA_ISRC1DEC4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2INT1, ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT2, ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT3, ARIZONA_ISRC2INT3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT4, ARIZONA_ISRC2INT4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2DEC1, ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC2, ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC3, ARIZONA_ISRC2DEC3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC4, ARIZONA_ISRC2DEC4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC3INT1, ARIZONA_ISRC3INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3INT2, ARIZONA_ISRC3INT2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3INT3, ARIZONA_ISRC3INT3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3INT4, ARIZONA_ISRC3INT4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC3DEC1, ARIZONA_ISRC3DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3DEC2, ARIZONA_ISRC3DEC2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3DEC3, ARIZONA_ISRC3DEC3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC3DEC4, ARIZONA_ISRC3DEC4MIX_INPUT_1_SOURCE); + +static const char * const wm5110_aec_loopback_texts[] = { + "HPOUT1L", "HPOUT1R", "HPOUT2L", "HPOUT2R", "HPOUT3L", "HPOUT3R", + "SPKOUTL", "SPKOUTR", "SPKDAT1L", "SPKDAT1R", "SPKDAT2L", "SPKDAT2R", +}; + +static const unsigned int wm5110_aec_loopback_values[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +}; + +static const struct soc_enum wm5110_aec_loopback = + SOC_VALUE_ENUM_SINGLE(ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_SRC_SHIFT, 0xf, + ARRAY_SIZE(wm5110_aec_loopback_texts), + wm5110_aec_loopback_texts, + wm5110_aec_loopback_values); + +static const struct snd_kcontrol_new wm5110_aec_loopback_mux = + SOC_DAPM_ENUM("AEC Loopback", wm5110_aec_loopback); + +static const struct snd_kcontrol_new wm5110_anc_input_mux[] = { + SOC_DAPM_ENUM("RXANCL Input", arizona_anc_input_src[0]), + SOC_DAPM_ENUM("RXANCL Channel", arizona_anc_input_src[1]), + SOC_DAPM_ENUM("RXANCR Input", arizona_anc_input_src[2]), + SOC_DAPM_ENUM("RXANCR Channel", arizona_anc_input_src[3]), +}; + +static const struct snd_kcontrol_new wm5110_anc_ng_mux = + SOC_DAPM_ENUM("RXANC NG Source", arizona_anc_ng_enum); + +static const struct snd_kcontrol_new wm5110_output_anc_src[] = { + SOC_DAPM_ENUM("HPOUT1L ANC Source", arizona_output_anc_src[0]), + SOC_DAPM_ENUM("HPOUT1R ANC Source", arizona_output_anc_src[1]), + SOC_DAPM_ENUM("HPOUT2L ANC Source", arizona_output_anc_src[2]), + SOC_DAPM_ENUM("HPOUT2R ANC Source", arizona_output_anc_src[3]), + SOC_DAPM_ENUM("HPOUT3L ANC Source", arizona_output_anc_src[4]), + SOC_DAPM_ENUM("HPOUT3R ANC Source", arizona_output_anc_src[5]), + SOC_DAPM_ENUM("SPKOUTL ANC Source", arizona_output_anc_src[6]), + SOC_DAPM_ENUM("SPKOUTR ANC Source", arizona_output_anc_src[7]), + SOC_DAPM_ENUM("SPKDAT1L ANC Source", arizona_output_anc_src[8]), + SOC_DAPM_ENUM("SPKDAT1R ANC Source", arizona_output_anc_src[9]), + SOC_DAPM_ENUM("SPKDAT2L ANC Source", arizona_output_anc_src[10]), + SOC_DAPM_ENUM("SPKDAT2R ANC Source", arizona_output_anc_src[11]), +}; + +static const struct snd_soc_dapm_widget wm5110_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT, + 0, wm5110_sysclk_ev, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1, + ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK, + ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK, + ARIZONA_OPCLK_ASYNC_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD2", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD3", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, SND_SOC_DAPM_REGULATOR_BYPASS), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDL", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDR", 0, 0), + +SND_SOC_DAPM_SIGGEN("TONE"), +SND_SOC_DAPM_SIGGEN("NOISE"), +SND_SOC_DAPM_SIGGEN("HAPTICS"), + +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), +SND_SOC_DAPM_INPUT("IN4L"), +SND_SOC_DAPM_INPUT("IN4R"), + +SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), +SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"), + +SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"), + +SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0, + &arizona_voice_trigger_switch[2]), + +SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, + 0, NULL, 0, wm5110_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_WILL_PMU), +SND_SOC_DAPM_PGA_E("IN1R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1R_ENA_SHIFT, + 0, NULL, 0, wm5110_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_WILL_PMU), +SND_SOC_DAPM_PGA_E("IN2L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2L_ENA_SHIFT, + 0, NULL, 0, wm5110_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_WILL_PMU), +SND_SOC_DAPM_PGA_E("IN2R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2R_ENA_SHIFT, + 0, NULL, 0, wm5110_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_WILL_PMU), +SND_SOC_DAPM_PGA_E("IN3L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN3L_ENA_SHIFT, + 0, NULL, 0, wm5110_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_WILL_PMU), +SND_SOC_DAPM_PGA_E("IN3R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN3R_ENA_SHIFT, + 0, NULL, 0, wm5110_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_WILL_PMU), +SND_SOC_DAPM_PGA_E("IN4L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN4L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN4R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN4R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_SUPPLY("MICBIAS1", ARIZONA_MIC_BIAS_CTRL_1, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", ARIZONA_MIC_BIAS_CTRL_2, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS3", ARIZONA_MIC_BIAS_CTRL_3, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Noise Generator", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Tone Generator 1", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("Tone Generator 2", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE2_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Mic Mute Mixer", ARIZONA_MIC_NOISE_MIX_CONTROL_1, + ARIZONA_MICMUTE_MIX_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("EQ1", ARIZONA_EQ1_1, ARIZONA_EQ1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ2", ARIZONA_EQ2_1, ARIZONA_EQ2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ3", ARIZONA_EQ3_1, ARIZONA_EQ3_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ4", ARIZONA_EQ4_1, ARIZONA_EQ4_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("DRC1L", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC1R", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1R_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC2L", ARIZONA_DRC2_CTRL1, ARIZONA_DRC2L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC2R", ARIZONA_DRC2_CTRL1, ARIZONA_DRC2R_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", ARIZONA_HPLPF1_1, ARIZONA_LHPF1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", ARIZONA_HPLPF2_1, ARIZONA_LHPF2_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF3", ARIZONA_HPLPF3_1, ARIZONA_LHPF3_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF4", ARIZONA_HPLPF4_1, ARIZONA_LHPF4_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("PWM1 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM1_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_PGA("PWM2 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM2_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_PGA("ASRC1L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC1R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1R_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC2L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("ASRC2R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2R_ENA_SHIFT, 0, + NULL, 0), + +WM_ADSP2("DSP1", 0, wm5110_adsp_power_ev), +WM_ADSP2("DSP2", 1, wm5110_adsp_power_ev), +WM_ADSP2("DSP3", 2, wm5110_adsp_power_ev), +WM_ADSP2("DSP4", 3, wm5110_adsp_power_ev), + +SND_SOC_DAPM_PGA("ISRC1INT1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT3", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT4", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1DEC1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC3", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC4", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2INT1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT3", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT4", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2DEC1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC3", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC4", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC3INT1", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3INT2", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3INT3", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3INT4", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_INT3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC3DEC1", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3DEC2", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3DEC3", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC3DEC4", ARIZONA_ISRC_3_CTRL_3, + ARIZONA_ISRC3_DEC3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_MUX("AEC Loopback", ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_ENA_SHIFT, 0, &wm5110_aec_loopback_mux), + +SND_SOC_DAPM_SUPPLY("RXANC NG External Clock", SND_SOC_NOPM, + ARIZONA_EXT_NG_SEL_SET_SHIFT, 0, arizona_anc_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA("RXANCL NG External", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("RXANCR NG External", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("RXANC NG Clock", SND_SOC_NOPM, + ARIZONA_CLK_NG_ENA_SET_SHIFT, 0, arizona_anc_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA("RXANCL NG Internal", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("RXANCR NG Internal", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_MUX("RXANCL Left Input", SND_SOC_NOPM, 0, 0, + &wm5110_anc_input_mux[0]), +SND_SOC_DAPM_MUX("RXANCL Right Input", SND_SOC_NOPM, 0, 0, + &wm5110_anc_input_mux[0]), +SND_SOC_DAPM_MUX("RXANCL Channel", SND_SOC_NOPM, 0, 0, + &wm5110_anc_input_mux[1]), +SND_SOC_DAPM_MUX("RXANCL NG Mux", SND_SOC_NOPM, 0, 0, &wm5110_anc_ng_mux), +SND_SOC_DAPM_MUX("RXANCR Left Input", SND_SOC_NOPM, 0, 0, + &wm5110_anc_input_mux[2]), +SND_SOC_DAPM_MUX("RXANCR Right Input", SND_SOC_NOPM, 0, 0, + &wm5110_anc_input_mux[2]), +SND_SOC_DAPM_MUX("RXANCR Channel", SND_SOC_NOPM, 0, 0, + &wm5110_anc_input_mux[3]), +SND_SOC_DAPM_MUX("RXANCR NG Mux", SND_SOC_NOPM, 0, 0, &wm5110_anc_ng_mux), + +SND_SOC_DAPM_PGA_E("RXANCL", SND_SOC_NOPM, ARIZONA_CLK_L_ENA_SET_SHIFT, + 0, NULL, 0, arizona_anc_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_E("RXANCR", SND_SOC_NOPM, ARIZONA_CLK_R_ENA_SET_SHIFT, + 0, NULL, 0, arizona_anc_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_MUX("HPOUT1L ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[0]), +SND_SOC_DAPM_MUX("HPOUT1R ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[1]), +SND_SOC_DAPM_MUX("HPOUT2L ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[2]), +SND_SOC_DAPM_MUX("HPOUT2R ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[3]), +SND_SOC_DAPM_MUX("HPOUT3L ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[4]), +SND_SOC_DAPM_MUX("HPOUT3R ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[5]), +SND_SOC_DAPM_MUX("SPKOUTL ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[6]), +SND_SOC_DAPM_MUX("SPKOUTR ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[7]), +SND_SOC_DAPM_MUX("SPKDAT1L ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[8]), +SND_SOC_DAPM_MUX("SPKDAT1R ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[9]), +SND_SOC_DAPM_MUX("SPKDAT2L ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[10]), +SND_SOC_DAPM_MUX("SPKDAT2R ANC Source", SND_SOC_NOPM, 0, 0, + &wm5110_output_anc_src[11]), + +SND_SOC_DAPM_AIF_OUT("AIF1TX1", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX5", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX6", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX7", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX8", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX1", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX5", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX6", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX7", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX8", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF2TX1", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX2", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX3", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX4", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX5", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX6", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF2RX1", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX2", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX3", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX4", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX5", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX6", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("SLIMRX1", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX2", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX3", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX4", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX5", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX6", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX7", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX8", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("SLIMTX1", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX2", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX3", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX4", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX5", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX6", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX7", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX8", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF3TX1", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF3TX2", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF3RX1", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF3RX2", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_PGA_E("OUT1L", SND_SOC_NOPM, + ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, wm5110_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT1R", SND_SOC_NOPM, + ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, wm5110_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT2L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT2R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT3L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT3R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT6L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT6L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT6R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT6R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + +ARIZONA_MIXER_WIDGETS(EQ1, "EQ1"), +ARIZONA_MIXER_WIDGETS(EQ2, "EQ2"), +ARIZONA_MIXER_WIDGETS(EQ3, "EQ3"), +ARIZONA_MIXER_WIDGETS(EQ4, "EQ4"), + +ARIZONA_MIXER_WIDGETS(DRC1L, "DRC1L"), +ARIZONA_MIXER_WIDGETS(DRC1R, "DRC1R"), +ARIZONA_MIXER_WIDGETS(DRC2L, "DRC2L"), +ARIZONA_MIXER_WIDGETS(DRC2R, "DRC2R"), + +ARIZONA_MIXER_WIDGETS(LHPF1, "LHPF1"), +ARIZONA_MIXER_WIDGETS(LHPF2, "LHPF2"), +ARIZONA_MIXER_WIDGETS(LHPF3, "LHPF3"), +ARIZONA_MIXER_WIDGETS(LHPF4, "LHPF4"), + +ARIZONA_MIXER_WIDGETS(Mic, "Mic"), +ARIZONA_MIXER_WIDGETS(Noise, "Noise"), + +ARIZONA_MIXER_WIDGETS(PWM1, "PWM1"), +ARIZONA_MIXER_WIDGETS(PWM2, "PWM2"), + +ARIZONA_MIXER_WIDGETS(OUT1L, "HPOUT1L"), +ARIZONA_MIXER_WIDGETS(OUT1R, "HPOUT1R"), +ARIZONA_MIXER_WIDGETS(OUT2L, "HPOUT2L"), +ARIZONA_MIXER_WIDGETS(OUT2R, "HPOUT2R"), +ARIZONA_MIXER_WIDGETS(OUT3L, "HPOUT3L"), +ARIZONA_MIXER_WIDGETS(OUT3R, "HPOUT3R"), +ARIZONA_MIXER_WIDGETS(SPKOUTL, "SPKOUTL"), +ARIZONA_MIXER_WIDGETS(SPKOUTR, "SPKOUTR"), +ARIZONA_MIXER_WIDGETS(SPKDAT1L, "SPKDAT1L"), +ARIZONA_MIXER_WIDGETS(SPKDAT1R, "SPKDAT1R"), +ARIZONA_MIXER_WIDGETS(SPKDAT2L, "SPKDAT2L"), +ARIZONA_MIXER_WIDGETS(SPKDAT2R, "SPKDAT2R"), + +ARIZONA_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"), +ARIZONA_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"), +ARIZONA_MIXER_WIDGETS(AIF1TX3, "AIF1TX3"), +ARIZONA_MIXER_WIDGETS(AIF1TX4, "AIF1TX4"), +ARIZONA_MIXER_WIDGETS(AIF1TX5, "AIF1TX5"), +ARIZONA_MIXER_WIDGETS(AIF1TX6, "AIF1TX6"), +ARIZONA_MIXER_WIDGETS(AIF1TX7, "AIF1TX7"), +ARIZONA_MIXER_WIDGETS(AIF1TX8, "AIF1TX8"), + +ARIZONA_MIXER_WIDGETS(AIF2TX1, "AIF2TX1"), +ARIZONA_MIXER_WIDGETS(AIF2TX2, "AIF2TX2"), +ARIZONA_MIXER_WIDGETS(AIF2TX3, "AIF2TX3"), +ARIZONA_MIXER_WIDGETS(AIF2TX4, "AIF2TX4"), +ARIZONA_MIXER_WIDGETS(AIF2TX5, "AIF2TX5"), +ARIZONA_MIXER_WIDGETS(AIF2TX6, "AIF2TX6"), + +ARIZONA_MIXER_WIDGETS(AIF3TX1, "AIF3TX1"), +ARIZONA_MIXER_WIDGETS(AIF3TX2, "AIF3TX2"), + +ARIZONA_MIXER_WIDGETS(SLIMTX1, "SLIMTX1"), +ARIZONA_MIXER_WIDGETS(SLIMTX2, "SLIMTX2"), +ARIZONA_MIXER_WIDGETS(SLIMTX3, "SLIMTX3"), +ARIZONA_MIXER_WIDGETS(SLIMTX4, "SLIMTX4"), +ARIZONA_MIXER_WIDGETS(SLIMTX5, "SLIMTX5"), +ARIZONA_MIXER_WIDGETS(SLIMTX6, "SLIMTX6"), +ARIZONA_MIXER_WIDGETS(SLIMTX7, "SLIMTX7"), +ARIZONA_MIXER_WIDGETS(SLIMTX8, "SLIMTX8"), + +ARIZONA_MUX_WIDGETS(ASRC1L, "ASRC1L"), +ARIZONA_MUX_WIDGETS(ASRC1R, "ASRC1R"), +ARIZONA_MUX_WIDGETS(ASRC2L, "ASRC2L"), +ARIZONA_MUX_WIDGETS(ASRC2R, "ASRC2R"), + +ARIZONA_DSP_WIDGETS(DSP1, "DSP1"), +ARIZONA_DSP_WIDGETS(DSP2, "DSP2"), +ARIZONA_DSP_WIDGETS(DSP3, "DSP3"), +ARIZONA_DSP_WIDGETS(DSP4, "DSP4"), + +ARIZONA_MUX_WIDGETS(ISRC1DEC1, "ISRC1DEC1"), +ARIZONA_MUX_WIDGETS(ISRC1DEC2, "ISRC1DEC2"), +ARIZONA_MUX_WIDGETS(ISRC1DEC3, "ISRC1DEC3"), +ARIZONA_MUX_WIDGETS(ISRC1DEC4, "ISRC1DEC4"), + +ARIZONA_MUX_WIDGETS(ISRC1INT1, "ISRC1INT1"), +ARIZONA_MUX_WIDGETS(ISRC1INT2, "ISRC1INT2"), +ARIZONA_MUX_WIDGETS(ISRC1INT3, "ISRC1INT3"), +ARIZONA_MUX_WIDGETS(ISRC1INT4, "ISRC1INT4"), + +ARIZONA_MUX_WIDGETS(ISRC2DEC1, "ISRC2DEC1"), +ARIZONA_MUX_WIDGETS(ISRC2DEC2, "ISRC2DEC2"), +ARIZONA_MUX_WIDGETS(ISRC2DEC3, "ISRC2DEC3"), +ARIZONA_MUX_WIDGETS(ISRC2DEC4, "ISRC2DEC4"), + +ARIZONA_MUX_WIDGETS(ISRC2INT1, "ISRC2INT1"), +ARIZONA_MUX_WIDGETS(ISRC2INT2, "ISRC2INT2"), +ARIZONA_MUX_WIDGETS(ISRC2INT3, "ISRC2INT3"), +ARIZONA_MUX_WIDGETS(ISRC2INT4, "ISRC2INT4"), + +ARIZONA_MUX_WIDGETS(ISRC3DEC1, "ISRC3DEC1"), +ARIZONA_MUX_WIDGETS(ISRC3DEC2, "ISRC3DEC2"), +ARIZONA_MUX_WIDGETS(ISRC3DEC3, "ISRC3DEC3"), +ARIZONA_MUX_WIDGETS(ISRC3DEC4, "ISRC3DEC4"), + +ARIZONA_MUX_WIDGETS(ISRC3INT1, "ISRC3INT1"), +ARIZONA_MUX_WIDGETS(ISRC3INT2, "ISRC3INT2"), +ARIZONA_MUX_WIDGETS(ISRC3INT3, "ISRC3INT3"), +ARIZONA_MUX_WIDGETS(ISRC3INT4, "ISRC3INT4"), + +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("HPOUT2L"), +SND_SOC_DAPM_OUTPUT("HPOUT2R"), +SND_SOC_DAPM_OUTPUT("HPOUT3L"), +SND_SOC_DAPM_OUTPUT("HPOUT3R"), +SND_SOC_DAPM_OUTPUT("SPKOUTLN"), +SND_SOC_DAPM_OUTPUT("SPKOUTLP"), +SND_SOC_DAPM_OUTPUT("SPKOUTRN"), +SND_SOC_DAPM_OUTPUT("SPKOUTRP"), +SND_SOC_DAPM_OUTPUT("SPKDAT1L"), +SND_SOC_DAPM_OUTPUT("SPKDAT1R"), +SND_SOC_DAPM_OUTPUT("SPKDAT2L"), +SND_SOC_DAPM_OUTPUT("SPKDAT2R"), + +SND_SOC_DAPM_OUTPUT("MICSUPP"), +}; + +#define ARIZONA_MIXER_INPUT_ROUTES(name) \ + { name, "Noise Generator", "Noise Generator" }, \ + { name, "Tone Generator 1", "Tone Generator 1" }, \ + { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "Haptics", "HAPTICS" }, \ + { name, "AEC", "AEC Loopback" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2L PGA" }, \ + { name, "IN2R", "IN2R PGA" }, \ + { name, "IN3L", "IN3L PGA" }, \ + { name, "IN3R", "IN3R PGA" }, \ + { name, "IN4L", "IN4L PGA" }, \ + { name, "IN4R", "IN4R PGA" }, \ + { name, "Mic Mute Mixer", "Mic Mute Mixer" }, \ + { name, "AIF1RX1", "AIF1RX1" }, \ + { name, "AIF1RX2", "AIF1RX2" }, \ + { name, "AIF1RX3", "AIF1RX3" }, \ + { name, "AIF1RX4", "AIF1RX4" }, \ + { name, "AIF1RX5", "AIF1RX5" }, \ + { name, "AIF1RX6", "AIF1RX6" }, \ + { name, "AIF1RX7", "AIF1RX7" }, \ + { name, "AIF1RX8", "AIF1RX8" }, \ + { name, "AIF2RX1", "AIF2RX1" }, \ + { name, "AIF2RX2", "AIF2RX2" }, \ + { name, "AIF2RX3", "AIF2RX3" }, \ + { name, "AIF2RX4", "AIF2RX4" }, \ + { name, "AIF2RX5", "AIF2RX5" }, \ + { name, "AIF2RX6", "AIF2RX6" }, \ + { name, "AIF3RX1", "AIF3RX1" }, \ + { name, "AIF3RX2", "AIF3RX2" }, \ + { name, "SLIMRX1", "SLIMRX1" }, \ + { name, "SLIMRX2", "SLIMRX2" }, \ + { name, "SLIMRX3", "SLIMRX3" }, \ + { name, "SLIMRX4", "SLIMRX4" }, \ + { name, "SLIMRX5", "SLIMRX5" }, \ + { name, "SLIMRX6", "SLIMRX6" }, \ + { name, "SLIMRX7", "SLIMRX7" }, \ + { name, "SLIMRX8", "SLIMRX8" }, \ + { name, "EQ1", "EQ1" }, \ + { name, "EQ2", "EQ2" }, \ + { name, "EQ3", "EQ3" }, \ + { name, "EQ4", "EQ4" }, \ + { name, "DRC1L", "DRC1L" }, \ + { name, "DRC1R", "DRC1R" }, \ + { name, "DRC2L", "DRC2L" }, \ + { name, "DRC2R", "DRC2R" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" }, \ + { name, "LHPF3", "LHPF3" }, \ + { name, "LHPF4", "LHPF4" }, \ + { name, "ASRC1L", "ASRC1L" }, \ + { name, "ASRC1R", "ASRC1R" }, \ + { name, "ASRC2L", "ASRC2L" }, \ + { name, "ASRC2R", "ASRC2R" }, \ + { name, "ISRC1DEC1", "ISRC1DEC1" }, \ + { name, "ISRC1DEC2", "ISRC1DEC2" }, \ + { name, "ISRC1DEC3", "ISRC1DEC3" }, \ + { name, "ISRC1DEC4", "ISRC1DEC4" }, \ + { name, "ISRC1INT1", "ISRC1INT1" }, \ + { name, "ISRC1INT2", "ISRC1INT2" }, \ + { name, "ISRC1INT3", "ISRC1INT3" }, \ + { name, "ISRC1INT4", "ISRC1INT4" }, \ + { name, "ISRC2DEC1", "ISRC2DEC1" }, \ + { name, "ISRC2DEC2", "ISRC2DEC2" }, \ + { name, "ISRC2DEC3", "ISRC2DEC3" }, \ + { name, "ISRC2DEC4", "ISRC2DEC4" }, \ + { name, "ISRC2INT1", "ISRC2INT1" }, \ + { name, "ISRC2INT2", "ISRC2INT2" }, \ + { name, "ISRC2INT3", "ISRC2INT3" }, \ + { name, "ISRC2INT4", "ISRC2INT4" }, \ + { name, "ISRC3DEC1", "ISRC3DEC1" }, \ + { name, "ISRC3DEC2", "ISRC3DEC2" }, \ + { name, "ISRC3DEC3", "ISRC3DEC3" }, \ + { name, "ISRC3DEC4", "ISRC3DEC4" }, \ + { name, "ISRC3INT1", "ISRC3INT1" }, \ + { name, "ISRC3INT2", "ISRC3INT2" }, \ + { name, "ISRC3INT3", "ISRC3INT3" }, \ + { name, "ISRC3INT4", "ISRC3INT4" }, \ + { name, "DSP1.1", "DSP1" }, \ + { name, "DSP1.2", "DSP1" }, \ + { name, "DSP1.3", "DSP1" }, \ + { name, "DSP1.4", "DSP1" }, \ + { name, "DSP1.5", "DSP1" }, \ + { name, "DSP1.6", "DSP1" }, \ + { name, "DSP2.1", "DSP2" }, \ + { name, "DSP2.2", "DSP2" }, \ + { name, "DSP2.3", "DSP2" }, \ + { name, "DSP2.4", "DSP2" }, \ + { name, "DSP2.5", "DSP2" }, \ + { name, "DSP2.6", "DSP2" }, \ + { name, "DSP3.1", "DSP3" }, \ + { name, "DSP3.2", "DSP3" }, \ + { name, "DSP3.3", "DSP3" }, \ + { name, "DSP3.4", "DSP3" }, \ + { name, "DSP3.5", "DSP3" }, \ + { name, "DSP3.6", "DSP3" }, \ + { name, "DSP4.1", "DSP4" }, \ + { name, "DSP4.2", "DSP4" }, \ + { name, "DSP4.3", "DSP4" }, \ + { name, "DSP4.4", "DSP4" }, \ + { name, "DSP4.5", "DSP4" }, \ + { name, "DSP4.6", "DSP4" } + +static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { + { "AIF2 Capture", NULL, "DBVDD2" }, + { "AIF2 Playback", NULL, "DBVDD2" }, + + { "AIF3 Capture", NULL, "DBVDD3" }, + { "AIF3 Playback", NULL, "DBVDD3" }, + + { "OUT1L", NULL, "CPVDD" }, + { "OUT1R", NULL, "CPVDD" }, + { "OUT2L", NULL, "CPVDD" }, + { "OUT2R", NULL, "CPVDD" }, + { "OUT3L", NULL, "CPVDD" }, + { "OUT3R", NULL, "CPVDD" }, + + { "OUT4L", NULL, "SPKVDDL" }, + { "OUT4R", NULL, "SPKVDDR" }, + + { "OUT1L", NULL, "SYSCLK" }, + { "OUT1R", NULL, "SYSCLK" }, + { "OUT2L", NULL, "SYSCLK" }, + { "OUT2R", NULL, "SYSCLK" }, + { "OUT3L", NULL, "SYSCLK" }, + { "OUT3R", NULL, "SYSCLK" }, + { "OUT4L", NULL, "SYSCLK" }, + { "OUT4R", NULL, "SYSCLK" }, + { "OUT5L", NULL, "SYSCLK" }, + { "OUT5R", NULL, "SYSCLK" }, + { "OUT6L", NULL, "SYSCLK" }, + { "OUT6R", NULL, "SYSCLK" }, + + { "IN1L", NULL, "SYSCLK" }, + { "IN1R", NULL, "SYSCLK" }, + { "IN2L", NULL, "SYSCLK" }, + { "IN2R", NULL, "SYSCLK" }, + { "IN3L", NULL, "SYSCLK" }, + { "IN3R", NULL, "SYSCLK" }, + { "IN4L", NULL, "SYSCLK" }, + { "IN4R", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "SYSCLK" }, + { "ASRC1R", NULL, "SYSCLK" }, + { "ASRC2L", NULL, "SYSCLK" }, + { "ASRC2R", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "ASYNCCLK" }, + { "ASRC1R", NULL, "ASYNCCLK" }, + { "ASRC2L", NULL, "ASYNCCLK" }, + { "ASRC2R", NULL, "ASYNCCLK" }, + + { "MICBIAS1", NULL, "MICVDD" }, + { "MICBIAS2", NULL, "MICVDD" }, + { "MICBIAS3", NULL, "MICVDD" }, + + { "Noise Generator", NULL, "SYSCLK" }, + { "Tone Generator 1", NULL, "SYSCLK" }, + { "Tone Generator 2", NULL, "SYSCLK" }, + + { "Noise Generator", NULL, "NOISE" }, + { "Tone Generator 1", NULL, "TONE" }, + { "Tone Generator 2", NULL, "TONE" }, + + { "AIF1 Capture", NULL, "AIF1TX1" }, + { "AIF1 Capture", NULL, "AIF1TX2" }, + { "AIF1 Capture", NULL, "AIF1TX3" }, + { "AIF1 Capture", NULL, "AIF1TX4" }, + { "AIF1 Capture", NULL, "AIF1TX5" }, + { "AIF1 Capture", NULL, "AIF1TX6" }, + { "AIF1 Capture", NULL, "AIF1TX7" }, + { "AIF1 Capture", NULL, "AIF1TX8" }, + + { "AIF1RX1", NULL, "AIF1 Playback" }, + { "AIF1RX2", NULL, "AIF1 Playback" }, + { "AIF1RX3", NULL, "AIF1 Playback" }, + { "AIF1RX4", NULL, "AIF1 Playback" }, + { "AIF1RX5", NULL, "AIF1 Playback" }, + { "AIF1RX6", NULL, "AIF1 Playback" }, + { "AIF1RX7", NULL, "AIF1 Playback" }, + { "AIF1RX8", NULL, "AIF1 Playback" }, + + { "AIF2 Capture", NULL, "AIF2TX1" }, + { "AIF2 Capture", NULL, "AIF2TX2" }, + { "AIF2 Capture", NULL, "AIF2TX3" }, + { "AIF2 Capture", NULL, "AIF2TX4" }, + { "AIF2 Capture", NULL, "AIF2TX5" }, + { "AIF2 Capture", NULL, "AIF2TX6" }, + + { "AIF2RX1", NULL, "AIF2 Playback" }, + { "AIF2RX2", NULL, "AIF2 Playback" }, + { "AIF2RX3", NULL, "AIF2 Playback" }, + { "AIF2RX4", NULL, "AIF2 Playback" }, + { "AIF2RX5", NULL, "AIF2 Playback" }, + { "AIF2RX6", NULL, "AIF2 Playback" }, + + { "AIF3 Capture", NULL, "AIF3TX1" }, + { "AIF3 Capture", NULL, "AIF3TX2" }, + + { "AIF3RX1", NULL, "AIF3 Playback" }, + { "AIF3RX2", NULL, "AIF3 Playback" }, + + { "Slim1 Capture", NULL, "SLIMTX1" }, + { "Slim1 Capture", NULL, "SLIMTX2" }, + { "Slim1 Capture", NULL, "SLIMTX3" }, + { "Slim1 Capture", NULL, "SLIMTX4" }, + + { "SLIMRX1", NULL, "Slim1 Playback" }, + { "SLIMRX2", NULL, "Slim1 Playback" }, + { "SLIMRX3", NULL, "Slim1 Playback" }, + { "SLIMRX4", NULL, "Slim1 Playback" }, + + { "Slim2 Capture", NULL, "SLIMTX5" }, + { "Slim2 Capture", NULL, "SLIMTX6" }, + + { "SLIMRX5", NULL, "Slim2 Playback" }, + { "SLIMRX6", NULL, "Slim2 Playback" }, + + { "Slim3 Capture", NULL, "SLIMTX7" }, + { "Slim3 Capture", NULL, "SLIMTX8" }, + + { "SLIMRX7", NULL, "Slim3 Playback" }, + { "SLIMRX8", NULL, "Slim3 Playback" }, + + { "AIF1 Playback", NULL, "SYSCLK" }, + { "AIF2 Playback", NULL, "SYSCLK" }, + { "AIF3 Playback", NULL, "SYSCLK" }, + { "Slim1 Playback", NULL, "SYSCLK" }, + { "Slim2 Playback", NULL, "SYSCLK" }, + { "Slim3 Playback", NULL, "SYSCLK" }, + + { "AIF1 Capture", NULL, "SYSCLK" }, + { "AIF2 Capture", NULL, "SYSCLK" }, + { "AIF3 Capture", NULL, "SYSCLK" }, + { "Slim1 Capture", NULL, "SYSCLK" }, + { "Slim2 Capture", NULL, "SYSCLK" }, + { "Slim3 Capture", NULL, "SYSCLK" }, + + { "Voice Control DSP", NULL, "DSP3" }, + + { "Audio Trace DSP", NULL, "DSP1" }, + + { "IN1L PGA", NULL, "IN1L" }, + { "IN1R PGA", NULL, "IN1R" }, + + { "IN2L PGA", NULL, "IN2L" }, + { "IN2R PGA", NULL, "IN2R" }, + + { "IN3L PGA", NULL, "IN3L" }, + { "IN3R PGA", NULL, "IN3R" }, + + { "IN4L PGA", NULL, "IN4L" }, + { "IN4R PGA", NULL, "IN4R" }, + + ARIZONA_MIXER_ROUTES("OUT1L", "HPOUT1L"), + ARIZONA_MIXER_ROUTES("OUT1R", "HPOUT1R"), + ARIZONA_MIXER_ROUTES("OUT2L", "HPOUT2L"), + ARIZONA_MIXER_ROUTES("OUT2R", "HPOUT2R"), + ARIZONA_MIXER_ROUTES("OUT3L", "HPOUT3L"), + ARIZONA_MIXER_ROUTES("OUT3R", "HPOUT3R"), + + ARIZONA_MIXER_ROUTES("OUT4L", "SPKOUTL"), + ARIZONA_MIXER_ROUTES("OUT4R", "SPKOUTR"), + ARIZONA_MIXER_ROUTES("OUT5L", "SPKDAT1L"), + ARIZONA_MIXER_ROUTES("OUT5R", "SPKDAT1R"), + ARIZONA_MIXER_ROUTES("OUT6L", "SPKDAT2L"), + ARIZONA_MIXER_ROUTES("OUT6R", "SPKDAT2R"), + + ARIZONA_MIXER_ROUTES("PWM1 Driver", "PWM1"), + ARIZONA_MIXER_ROUTES("PWM2 Driver", "PWM2"), + + ARIZONA_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), + ARIZONA_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), + ARIZONA_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), + ARIZONA_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), + ARIZONA_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), + ARIZONA_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), + ARIZONA_MIXER_ROUTES("AIF1TX7", "AIF1TX7"), + ARIZONA_MIXER_ROUTES("AIF1TX8", "AIF1TX8"), + + ARIZONA_MIXER_ROUTES("AIF2TX1", "AIF2TX1"), + ARIZONA_MIXER_ROUTES("AIF2TX2", "AIF2TX2"), + ARIZONA_MIXER_ROUTES("AIF2TX3", "AIF2TX3"), + ARIZONA_MIXER_ROUTES("AIF2TX4", "AIF2TX4"), + ARIZONA_MIXER_ROUTES("AIF2TX5", "AIF2TX5"), + ARIZONA_MIXER_ROUTES("AIF2TX6", "AIF2TX6"), + + ARIZONA_MIXER_ROUTES("AIF3TX1", "AIF3TX1"), + ARIZONA_MIXER_ROUTES("AIF3TX2", "AIF3TX2"), + + ARIZONA_MIXER_ROUTES("SLIMTX1", "SLIMTX1"), + ARIZONA_MIXER_ROUTES("SLIMTX2", "SLIMTX2"), + ARIZONA_MIXER_ROUTES("SLIMTX3", "SLIMTX3"), + ARIZONA_MIXER_ROUTES("SLIMTX4", "SLIMTX4"), + ARIZONA_MIXER_ROUTES("SLIMTX5", "SLIMTX5"), + ARIZONA_MIXER_ROUTES("SLIMTX6", "SLIMTX6"), + ARIZONA_MIXER_ROUTES("SLIMTX7", "SLIMTX7"), + ARIZONA_MIXER_ROUTES("SLIMTX8", "SLIMTX8"), + + ARIZONA_MIXER_ROUTES("EQ1", "EQ1"), + ARIZONA_MIXER_ROUTES("EQ2", "EQ2"), + ARIZONA_MIXER_ROUTES("EQ3", "EQ3"), + ARIZONA_MIXER_ROUTES("EQ4", "EQ4"), + + ARIZONA_MIXER_ROUTES("DRC1L", "DRC1L"), + ARIZONA_MIXER_ROUTES("DRC1R", "DRC1R"), + ARIZONA_MIXER_ROUTES("DRC2L", "DRC2L"), + ARIZONA_MIXER_ROUTES("DRC2R", "DRC2R"), + + ARIZONA_MIXER_ROUTES("LHPF1", "LHPF1"), + ARIZONA_MIXER_ROUTES("LHPF2", "LHPF2"), + ARIZONA_MIXER_ROUTES("LHPF3", "LHPF3"), + ARIZONA_MIXER_ROUTES("LHPF4", "LHPF4"), + + ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Noise"), + ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Mic"), + + ARIZONA_MUX_ROUTES("ASRC1L", "ASRC1L"), + ARIZONA_MUX_ROUTES("ASRC1R", "ASRC1R"), + ARIZONA_MUX_ROUTES("ASRC2L", "ASRC2L"), + ARIZONA_MUX_ROUTES("ASRC2R", "ASRC2R"), + + ARIZONA_DSP_ROUTES("DSP1"), + ARIZONA_DSP_ROUTES("DSP2"), + ARIZONA_DSP_ROUTES("DSP3"), + ARIZONA_DSP_ROUTES("DSP4"), + + ARIZONA_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), + ARIZONA_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), + ARIZONA_MUX_ROUTES("ISRC1INT3", "ISRC1INT3"), + ARIZONA_MUX_ROUTES("ISRC1INT4", "ISRC1INT4"), + + ARIZONA_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), + ARIZONA_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), + ARIZONA_MUX_ROUTES("ISRC1DEC3", "ISRC1DEC3"), + ARIZONA_MUX_ROUTES("ISRC1DEC4", "ISRC1DEC4"), + + ARIZONA_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), + ARIZONA_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), + ARIZONA_MUX_ROUTES("ISRC2INT3", "ISRC2INT3"), + ARIZONA_MUX_ROUTES("ISRC2INT4", "ISRC2INT4"), + + ARIZONA_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), + ARIZONA_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), + ARIZONA_MUX_ROUTES("ISRC2DEC3", "ISRC2DEC3"), + ARIZONA_MUX_ROUTES("ISRC2DEC4", "ISRC2DEC4"), + + ARIZONA_MUX_ROUTES("ISRC3INT1", "ISRC3INT1"), + ARIZONA_MUX_ROUTES("ISRC3INT2", "ISRC3INT2"), + ARIZONA_MUX_ROUTES("ISRC3INT3", "ISRC3INT3"), + ARIZONA_MUX_ROUTES("ISRC3INT4", "ISRC3INT4"), + + ARIZONA_MUX_ROUTES("ISRC3DEC1", "ISRC3DEC1"), + ARIZONA_MUX_ROUTES("ISRC3DEC2", "ISRC3DEC2"), + ARIZONA_MUX_ROUTES("ISRC3DEC3", "ISRC3DEC3"), + ARIZONA_MUX_ROUTES("ISRC3DEC4", "ISRC3DEC4"), + + { "AEC Loopback", "HPOUT1L", "OUT1L" }, + { "AEC Loopback", "HPOUT1R", "OUT1R" }, + { "HPOUT1L", NULL, "OUT1L" }, + { "HPOUT1R", NULL, "OUT1R" }, + + { "AEC Loopback", "HPOUT2L", "OUT2L" }, + { "AEC Loopback", "HPOUT2R", "OUT2R" }, + { "HPOUT2L", NULL, "OUT2L" }, + { "HPOUT2R", NULL, "OUT2R" }, + + { "AEC Loopback", "HPOUT3L", "OUT3L" }, + { "AEC Loopback", "HPOUT3R", "OUT3R" }, + { "HPOUT3L", NULL, "OUT3L" }, + { "HPOUT3R", NULL, "OUT3R" }, + + { "AEC Loopback", "SPKOUTL", "OUT4L" }, + { "SPKOUTLN", NULL, "OUT4L" }, + { "SPKOUTLP", NULL, "OUT4L" }, + + { "AEC Loopback", "SPKOUTR", "OUT4R" }, + { "SPKOUTRN", NULL, "OUT4R" }, + { "SPKOUTRP", NULL, "OUT4R" }, + + { "AEC Loopback", "SPKDAT1L", "OUT5L" }, + { "AEC Loopback", "SPKDAT1R", "OUT5R" }, + { "SPKDAT1L", NULL, "OUT5L" }, + { "SPKDAT1R", NULL, "OUT5R" }, + + { "AEC Loopback", "SPKDAT2L", "OUT6L" }, + { "AEC Loopback", "SPKDAT2R", "OUT6R" }, + { "SPKDAT2L", NULL, "OUT6L" }, + { "SPKDAT2R", NULL, "OUT6R" }, + + WM5110_RXANC_INPUT_ROUTES("RXANCL", "RXANCL"), + WM5110_RXANC_INPUT_ROUTES("RXANCR", "RXANCR"), + + WM5110_RXANC_OUTPUT_ROUTES("OUT1L", "HPOUT1L"), + WM5110_RXANC_OUTPUT_ROUTES("OUT1R", "HPOUT1R"), + WM5110_RXANC_OUTPUT_ROUTES("OUT2L", "HPOUT2L"), + WM5110_RXANC_OUTPUT_ROUTES("OUT2R", "HPOUT2R"), + WM5110_RXANC_OUTPUT_ROUTES("OUT3L", "HPOUT3L"), + WM5110_RXANC_OUTPUT_ROUTES("OUT3R", "HPOUT3R"), + WM5110_RXANC_OUTPUT_ROUTES("OUT4L", "SPKOUTL"), + WM5110_RXANC_OUTPUT_ROUTES("OUT4R", "SPKOUTR"), + WM5110_RXANC_OUTPUT_ROUTES("OUT5L", "SPKDAT1L"), + WM5110_RXANC_OUTPUT_ROUTES("OUT5R", "SPKDAT1R"), + WM5110_RXANC_OUTPUT_ROUTES("OUT6L", "SPKDAT2L"), + WM5110_RXANC_OUTPUT_ROUTES("OUT6R", "SPKDAT2R"), + + { "MICSUPP", NULL, "SYSCLK" }, + + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC2 Signal Activity", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "DRC1L" }, + { "DRC1 Signal Activity", NULL, "DRC1R" }, + { "DRC2 Signal Activity", NULL, "DRC2L" }, + { "DRC2 Signal Activity", NULL, "DRC2R" }, + + { "DSP Voice Trigger", NULL, "SYSCLK" }, + { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" }, + { "DSP3 Voice Trigger", "Switch", "DSP3" }, +}; + +static int wm5110_set_fll(struct snd_soc_component *component, int fll_id, + int source, unsigned int Fref, unsigned int Fout) +{ + struct wm5110_priv *wm5110 = snd_soc_component_get_drvdata(component); + + switch (fll_id) { + case WM5110_FLL1: + return arizona_set_fll(&wm5110->fll[0], source, Fref, Fout); + case WM5110_FLL2: + return arizona_set_fll(&wm5110->fll[1], source, Fref, Fout); + case WM5110_FLL1_REFCLK: + return arizona_set_fll_refclk(&wm5110->fll[0], source, Fref, + Fout); + case WM5110_FLL2_REFCLK: + return arizona_set_fll_refclk(&wm5110->fll[1], source, Fref, + Fout); + default: + return -EINVAL; + } +} + +#define WM5110_RATES SNDRV_PCM_RATE_KNOT + +#define WM5110_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm5110_dai[] = { + { + .name = "wm5110-aif1", + .id = 1, + .base = ARIZONA_AIF1_BCLK_CTRL, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm5110-aif2", + .id = 2, + .base = ARIZONA_AIF2_BCLK_CTRL, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 6, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm5110-aif3", + .id = 3, + .base = ARIZONA_AIF3_BCLK_CTRL, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm5110-slim1", + .id = 4, + .playback = { + .stream_name = "Slim1 Playback", + .channels_min = 1, + .channels_max = 4, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .capture = { + .stream_name = "Slim1 Capture", + .channels_min = 1, + .channels_max = 4, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm5110-slim2", + .id = 5, + .playback = { + .stream_name = "Slim2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .capture = { + .stream_name = "Slim2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm5110-slim3", + .id = 6, + .playback = { + .stream_name = "Slim3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .capture = { + .stream_name = "Slim3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm5110-cpu-voicectrl", + .capture = { + .stream_name = "Voice Control CPU", + .channels_min = 1, + .channels_max = 1, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .compress_new = snd_soc_new_compress, + }, + { + .name = "wm5110-dsp-voicectrl", + .capture = { + .stream_name = "Voice Control DSP", + .channels_min = 1, + .channels_max = 1, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + }, + { + .name = "wm5110-cpu-trace", + .capture = { + .stream_name = "Audio Trace CPU", + .channels_min = 1, + .channels_max = 6, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + .compress_new = snd_soc_new_compress, + }, + { + .name = "wm5110-dsp-trace", + .capture = { + .stream_name = "Audio Trace DSP", + .channels_min = 1, + .channels_max = 6, + .rates = WM5110_RATES, + .formats = WM5110_FORMATS, + }, + }, +}; + +static int wm5110_open(struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct wm5110_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->core.arizona; + int n_adsp; + + if (strcmp(rtd->codec_dai->name, "wm5110-dsp-voicectrl") == 0) { + n_adsp = 2; + } else if (strcmp(rtd->codec_dai->name, "wm5110-dsp-trace") == 0) { + n_adsp = 0; + } else { + dev_err(arizona->dev, + "No suitable compressed stream for DAI '%s'\n", + rtd->codec_dai->name); + return -EINVAL; + } + + return wm_adsp_compr_open(&priv->core.adsp[n_adsp], stream); +} + +static irqreturn_t wm5110_adsp2_irq(int irq, void *data) +{ + struct wm5110_priv *priv = data; + struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; + int serviced = 0; + int i, ret; + + for (i = 0; i < WM5110_NUM_ADSP; ++i) { + ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); + if (ret != -ENODEV) + serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } + } + + if (!serviced) { + dev_err(arizona->dev, "Spurious compressed data IRQ\n"); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int wm5110_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm5110_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->core.arizona; + int i, ret; + + arizona->dapm = dapm; + snd_soc_component_init_regmap(component, arizona->regmap); + + ret = arizona_init_spk(component); + if (ret < 0) + return ret; + + arizona_init_gpio(component); + arizona_init_mono(component); + + for (i = 0; i < WM5110_NUM_ADSP; ++i) { + ret = wm_adsp2_component_probe(&priv->core.adsp[i], component); + if (ret) + goto err_adsp2_codec_probe; + } + + ret = snd_soc_add_component_controls(component, + arizona_adsp2_rate_controls, + WM5110_NUM_ADSP); + if (ret) + goto err_adsp2_codec_probe; + + snd_soc_component_disable_pin(component, "HAPTICS"); + + return 0; + +err_adsp2_codec_probe: + for (--i; i >= 0; --i) + wm_adsp2_component_remove(&priv->core.adsp[i], component); + + return ret; +} + +static void wm5110_component_remove(struct snd_soc_component *component) +{ + struct wm5110_priv *priv = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < WM5110_NUM_ADSP; ++i) + wm_adsp2_component_remove(&priv->core.adsp[i], component); + + priv->core.arizona->dapm = NULL; +} + +#define WM5110_DIG_VU 0x0200 + +static unsigned int wm5110_digital_vu[] = { + ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, + ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, + ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_3R, + ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, + ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, + ARIZONA_DAC_DIGITAL_VOLUME_6L, + ARIZONA_DAC_DIGITAL_VOLUME_6R, +}; + +static struct snd_compr_ops wm5110_compr_ops = { + .open = wm5110_open, + .free = wm_adsp_compr_free, + .set_params = wm_adsp_compr_set_params, + .get_caps = wm_adsp_compr_get_caps, + .trigger = wm_adsp_compr_trigger, + .pointer = wm_adsp_compr_pointer, + .copy = wm_adsp_compr_copy, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm5110 = { + .probe = wm5110_component_probe, + .remove = wm5110_component_remove, + .set_sysclk = arizona_set_sysclk, + .set_pll = wm5110_set_fll, + .name = DRV_NAME, + .compr_ops = &wm5110_compr_ops, + .controls = wm5110_snd_controls, + .num_controls = ARRAY_SIZE(wm5110_snd_controls), + .dapm_widgets = wm5110_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm5110_dapm_widgets), + .dapm_routes = wm5110_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm5110_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm5110_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + struct wm5110_priv *wm5110; + int i, ret; + + wm5110 = devm_kzalloc(&pdev->dev, sizeof(struct wm5110_priv), + GFP_KERNEL); + if (wm5110 == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, wm5110); + + if (IS_ENABLED(CONFIG_OF)) { + if (!dev_get_platdata(arizona->dev)) { + ret = arizona_of_get_audio_pdata(arizona); + if (ret < 0) + return ret; + } + } + + wm5110->core.arizona = arizona; + wm5110->core.num_inputs = 8; + + for (i = 0; i < WM5110_NUM_ADSP; i++) { + wm5110->core.adsp[i].part = "wm5110"; + wm5110->core.adsp[i].num = i + 1; + wm5110->core.adsp[i].type = WMFW_ADSP2; + wm5110->core.adsp[i].dev = arizona->dev; + wm5110->core.adsp[i].regmap = arizona->regmap; + + wm5110->core.adsp[i].base = ARIZONA_DSP1_CONTROL_1 + + (0x100 * i); + wm5110->core.adsp[i].mem = wm5110_dsp_regions[i]; + wm5110->core.adsp[i].num_mems + = ARRAY_SIZE(wm5110_dsp1_regions); + + ret = wm_adsp2_init(&wm5110->core.adsp[i]); + if (ret != 0) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(wm5110->fll); i++) + wm5110->fll[i].vco_mult = 3; + + arizona_init_fll(arizona, 1, ARIZONA_FLL1_CONTROL_1 - 1, + ARIZONA_IRQ_FLL1_LOCK, ARIZONA_IRQ_FLL1_CLOCK_OK, + &wm5110->fll[0]); + arizona_init_fll(arizona, 2, ARIZONA_FLL2_CONTROL_1 - 1, + ARIZONA_IRQ_FLL2_LOCK, ARIZONA_IRQ_FLL2_CLOCK_OK, + &wm5110->fll[1]); + + /* SR2 fixed at 8kHz, SR3 fixed at 16kHz */ + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_2, + ARIZONA_SAMPLE_RATE_2_MASK, 0x11); + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_3, + ARIZONA_SAMPLE_RATE_3_MASK, 0x12); + + for (i = 0; i < ARRAY_SIZE(wm5110_dai); i++) + arizona_init_dai(&wm5110->core, i); + + /* Latch volume update bits */ + for (i = 0; i < ARRAY_SIZE(wm5110_digital_vu); i++) + regmap_update_bits(arizona->regmap, wm5110_digital_vu[i], + WM5110_DIG_VU, WM5110_DIG_VU); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + + ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, + "ADSP2 Compressed IRQ", wm5110_adsp2_irq, + wm5110); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to request DSP IRQ: %d\n", ret); + return ret; + } + + ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 1); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to set compressed IRQ as a wake source: %d\n", + ret); + + arizona_init_common(arizona); + + ret = arizona_init_vol_limit(arizona); + if (ret < 0) + goto err_dsp_irq; + ret = arizona_init_spk_irqs(arizona); + if (ret < 0) + goto err_dsp_irq; + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm5110, + wm5110_dai, + ARRAY_SIZE(wm5110_dai)); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register component: %d\n", ret); + goto err_spk_irqs; + } + + return ret; + +err_spk_irqs: + arizona_free_spk_irqs(arizona); +err_dsp_irq: + arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5110); + + return ret; +} + +static int wm5110_remove(struct platform_device *pdev) +{ + struct wm5110_priv *wm5110 = platform_get_drvdata(pdev); + struct arizona *arizona = wm5110->core.arizona; + int i; + + pm_runtime_disable(&pdev->dev); + + for (i = 0; i < WM5110_NUM_ADSP; i++) + wm_adsp2_remove(&wm5110->core.adsp[i]); + + arizona_free_spk_irqs(arizona); + + arizona_set_irq_wake(arizona, ARIZONA_IRQ_DSP_IRQ1, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, wm5110); + + return 0; +} + +static struct platform_driver wm5110_codec_driver = { + .driver = { + .name = "wm5110-codec", + }, + .probe = wm5110_probe, + .remove = wm5110_remove, +}; + +module_platform_driver(wm5110_codec_driver); + +MODULE_DESCRIPTION("ASoC WM5110 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm5110-codec"); diff --git a/sound/soc/codecs/wm5110.h b/sound/soc/codecs/wm5110.h new file mode 100644 index 000000000..e6c0cd423 --- /dev/null +++ b/sound/soc/codecs/wm5110.h @@ -0,0 +1,23 @@ +/* + * wm5110.h -- WM5110 ALSA SoC Audio driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM5110_H +#define _WM5110_H + +#include "arizona.h" + +#define WM5110_FLL1 1 +#define WM5110_FLL2 2 +#define WM5110_FLL1_REFCLK 3 +#define WM5110_FLL2_REFCLK 4 + +#endif diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c new file mode 100644 index 000000000..707b31ef9 --- /dev/null +++ b/sound/soc/codecs/wm8350.c @@ -0,0 +1,1639 @@ +/* + * wm8350.c -- WM8350 ALSA SoC audio driver + * + * Copyright (C) 2007-12 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8350.h" + +#define WM8350_OUTn_0dB 0x39 + +#define WM8350_RAMP_NONE 0 +#define WM8350_RAMP_UP 1 +#define WM8350_RAMP_DOWN 2 + +/* We only include the analogue supplies here; the digital supplies + * need to be available well before this driver can be probed. + */ +static const char *supply_names[] = { + "AVDD", + "HPVDD", +}; + +struct wm8350_output { + u16 active; + u16 left_vol; + u16 right_vol; + u16 ramp; + u16 mute; +}; + +struct wm8350_jack_data { + struct snd_soc_jack *jack; + struct delayed_work work; + int report; + int short_report; +}; + +struct wm8350_data { + struct wm8350 *wm8350; + struct wm8350_output out1; + struct wm8350_output out2; + struct wm8350_jack_data hpl; + struct wm8350_jack_data hpr; + struct wm8350_jack_data mic; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; + int fll_freq_out; + int fll_freq_in; + struct delayed_work pga_work; +}; + +/* + * Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown. + */ +static inline int wm8350_out1_ramp_step(struct wm8350_data *wm8350_data) +{ + struct wm8350_output *out1 = &wm8350_data->out1; + struct wm8350 *wm8350 = wm8350_data->wm8350; + int left_complete = 0, right_complete = 0; + u16 reg, val; + + /* left channel */ + reg = wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME); + val = (reg & WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + + if (out1->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out1->left_vol) { + val++; + reg &= ~WM8350_OUT1L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else if (out1->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT1L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else + return 1; + + /* right channel */ + reg = wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME); + val = (reg & WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + if (out1->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out1->right_vol) { + val++; + reg &= ~WM8350_OUT1R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } else if (out1->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT1R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } + + /* only hit the update bit if either volume has changed this step */ + if (!left_complete || !right_complete) + wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU); + + return left_complete & right_complete; +} + +/* + * Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown. + */ +static inline int wm8350_out2_ramp_step(struct wm8350_data *wm8350_data) +{ + struct wm8350_output *out2 = &wm8350_data->out2; + struct wm8350 *wm8350 = wm8350_data->wm8350; + int left_complete = 0, right_complete = 0; + u16 reg, val; + + /* left channel */ + reg = wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME); + val = (reg & WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + if (out2->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out2->left_vol) { + val++; + reg &= ~WM8350_OUT2L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else if (out2->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT2L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else + return 1; + + /* right channel */ + reg = wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME); + val = (reg & WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + if (out2->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out2->right_vol) { + val++; + reg &= ~WM8350_OUT2R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } else if (out2->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT2R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } + + /* only hit the update bit if either volume has changed this step */ + if (!left_complete || !right_complete) + wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU); + + return left_complete & right_complete; +} + +/* + * This work ramps both output PGAs at stream start/stop time to + * minimise pop associated with DAPM power switching. + * It's best to enable Zero Cross when ramping occurs to minimise any + * zipper noises. + */ +static void wm8350_pga_work(struct work_struct *work) +{ + struct wm8350_data *wm8350_data = + container_of(work, struct wm8350_data, pga_work.work); + struct wm8350_output *out1 = &wm8350_data->out1, + *out2 = &wm8350_data->out2; + int i, out1_complete, out2_complete; + + /* do we need to ramp at all ? */ + if (out1->ramp == WM8350_RAMP_NONE && out2->ramp == WM8350_RAMP_NONE) + return; + + /* PGA volumes have 6 bits of resolution to ramp */ + for (i = 0; i <= 63; i++) { + out1_complete = 1, out2_complete = 1; + if (out1->ramp != WM8350_RAMP_NONE) + out1_complete = wm8350_out1_ramp_step(wm8350_data); + if (out2->ramp != WM8350_RAMP_NONE) + out2_complete = wm8350_out2_ramp_step(wm8350_data); + + /* ramp finished ? */ + if (out1_complete && out2_complete) + break; + + /* we need to delay longer on the up ramp */ + if (out1->ramp == WM8350_RAMP_UP || + out2->ramp == WM8350_RAMP_UP) { + /* delay is longer over 0dB as increases are larger */ + if (i >= WM8350_OUTn_0dB) + schedule_timeout_interruptible(msecs_to_jiffies + (2)); + else + schedule_timeout_interruptible(msecs_to_jiffies + (1)); + } else + udelay(50); /* doesn't matter if we delay longer */ + } + + out1->ramp = WM8350_RAMP_NONE; + out2->ramp = WM8350_RAMP_NONE; +} + +/* + * WM8350 Controls + */ + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8350_data *wm8350_data = snd_soc_component_get_drvdata(component); + struct wm8350_output *out; + + switch (w->shift) { + case 0: + case 1: + out = &wm8350_data->out1; + break; + case 2: + case 3: + out = &wm8350_data->out2; + break; + + default: + WARN(1, "Invalid shift %d\n", w->shift); + return -1; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + out->ramp = WM8350_RAMP_UP; + out->active = 1; + + schedule_delayed_work(&wm8350_data->pga_work, + msecs_to_jiffies(1)); + break; + + case SND_SOC_DAPM_PRE_PMD: + out->ramp = WM8350_RAMP_DOWN; + out->active = 0; + + schedule_delayed_work(&wm8350_data->pga_work, + msecs_to_jiffies(1)); + break; + } + + return 0; +} + +static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8350_data *wm8350_priv = snd_soc_component_get_drvdata(component); + struct wm8350_output *out = NULL; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int ret; + unsigned int reg = mc->reg; + u16 val; + + /* For OUT1 and OUT2 we shadow the values and only actually write + * them out when active in order to ensure the amplifier comes on + * as quietly as possible. */ + switch (reg) { + case WM8350_LOUT1_VOLUME: + out = &wm8350_priv->out1; + break; + case WM8350_LOUT2_VOLUME: + out = &wm8350_priv->out2; + break; + default: + break; + } + + if (out) { + out->left_vol = ucontrol->value.integer.value[0]; + out->right_vol = ucontrol->value.integer.value[1]; + if (!out->active) + return 1; + } + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = snd_soc_component_read32(component, reg); + snd_soc_component_write(component, reg, val | WM8350_OUT1_VU); + return 1; +} + +static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8350_data *wm8350_priv = snd_soc_component_get_drvdata(component); + struct wm8350_output *out1 = &wm8350_priv->out1; + struct wm8350_output *out2 = &wm8350_priv->out2; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + + /* If these are cached registers use the cache */ + switch (reg) { + case WM8350_LOUT1_VOLUME: + ucontrol->value.integer.value[0] = out1->left_vol; + ucontrol->value.integer.value[1] = out1->right_vol; + return 0; + + case WM8350_LOUT2_VOLUME: + ucontrol->value.integer.value[0] = out2->left_vol; + ucontrol->value.integer.value[1] = out2->right_vol; + return 0; + + default: + break; + } + + return snd_soc_get_volsw(kcontrol, ucontrol); +} + +static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" }; +static const char *wm8350_dacmutem[] = { "Normal", "Soft" }; +static const char *wm8350_dacmutes[] = { "Fast", "Slow" }; +static const char *wm8350_adcfilter[] = { "None", "High Pass" }; +static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" }; +static const char *wm8350_lr[] = { "Left", "Right" }; + +static const struct soc_enum wm8350_enum[] = { + SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 4, 4, wm8350_deemp), + SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol), + SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem), + SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes), + SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter), + SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp), + SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol), + SOC_ENUM_SINGLE(WM8350_INPUT_MIXER_VOLUME, 15, 2, wm8350_lr), +}; + +static DECLARE_TLV_DB_SCALE(pre_amp_tlv, -1200, 3525, 0); +static DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 600, 0); +static DECLARE_TLV_DB_SCALE(dac_pcm_tlv, -7163, 36, 1); +static DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -12700, 50, 1); +static DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 1); + +static const DECLARE_TLV_DB_RANGE(capture_sd_tlv, + 0, 12, TLV_DB_SCALE_ITEM(-3600, 300, 1), + 13, 15, TLV_DB_SCALE_ITEM(0, 0, 0) +); + +static const struct snd_kcontrol_new wm8350_snd_controls[] = { + SOC_ENUM("Playback Deemphasis", wm8350_enum[0]), + SOC_ENUM("Playback DAC Inversion", wm8350_enum[1]), + SOC_DOUBLE_R_EXT_TLV("Playback PCM Volume", + WM8350_DAC_DIGITAL_VOLUME_L, + WM8350_DAC_DIGITAL_VOLUME_R, + 0, 255, 0, wm8350_get_volsw_2r, + wm8350_put_volsw_2r_vu, dac_pcm_tlv), + SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]), + SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]), + SOC_ENUM("Capture PCM Filter", wm8350_enum[4]), + SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]), + SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]), + SOC_DOUBLE_R_EXT_TLV("Capture PCM Volume", + WM8350_ADC_DIGITAL_VOLUME_L, + WM8350_ADC_DIGITAL_VOLUME_R, + 0, 255, 0, wm8350_get_volsw_2r, + wm8350_put_volsw_2r_vu, adc_pcm_tlv), + SOC_DOUBLE_TLV("Capture Sidetone Volume", + WM8350_ADC_DIVIDER, + 8, 4, 15, 1, capture_sd_tlv), + SOC_DOUBLE_R_EXT_TLV("Capture Volume", + WM8350_LEFT_INPUT_VOLUME, + WM8350_RIGHT_INPUT_VOLUME, + 2, 63, 0, wm8350_get_volsw_2r, + wm8350_put_volsw_2r_vu, pre_amp_tlv), + SOC_DOUBLE_R("Capture ZC Switch", + WM8350_LEFT_INPUT_VOLUME, + WM8350_RIGHT_INPUT_VOLUME, 13, 1, 0), + SOC_SINGLE_TLV("Left Input Left Sidetone Volume", + WM8350_OUTPUT_LEFT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Left Input Right Sidetone Volume", + WM8350_OUTPUT_LEFT_MIXER_VOLUME, + 5, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Left Input Bypass Volume", + WM8350_OUTPUT_LEFT_MIXER_VOLUME, + 9, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Right Input Left Sidetone Volume", + WM8350_OUTPUT_RIGHT_MIXER_VOLUME, + 1, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Right Input Right Sidetone Volume", + WM8350_OUTPUT_RIGHT_MIXER_VOLUME, + 5, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Right Input Bypass Volume", + WM8350_OUTPUT_RIGHT_MIXER_VOLUME, + 13, 7, 0, out_mix_tlv), + SOC_SINGLE("Left Input Mixer +20dB Switch", + WM8350_INPUT_MIXER_VOLUME_L, 0, 1, 0), + SOC_SINGLE("Right Input Mixer +20dB Switch", + WM8350_INPUT_MIXER_VOLUME_R, 0, 1, 0), + SOC_SINGLE_TLV("Out4 Capture Volume", + WM8350_INPUT_MIXER_VOLUME, + 1, 7, 0, out_mix_tlv), + SOC_DOUBLE_R_EXT_TLV("Out1 Playback Volume", + WM8350_LOUT1_VOLUME, + WM8350_ROUT1_VOLUME, + 2, 63, 0, wm8350_get_volsw_2r, + wm8350_put_volsw_2r_vu, out_pga_tlv), + SOC_DOUBLE_R("Out1 Playback ZC Switch", + WM8350_LOUT1_VOLUME, + WM8350_ROUT1_VOLUME, 13, 1, 0), + SOC_DOUBLE_R_EXT_TLV("Out2 Playback Volume", + WM8350_LOUT2_VOLUME, + WM8350_ROUT2_VOLUME, + 2, 63, 0, wm8350_get_volsw_2r, + wm8350_put_volsw_2r_vu, out_pga_tlv), + SOC_DOUBLE_R("Out2 Playback ZC Switch", WM8350_LOUT2_VOLUME, + WM8350_ROUT2_VOLUME, 13, 1, 0), + SOC_SINGLE("Out2 Right Invert Switch", WM8350_ROUT2_VOLUME, 10, 1, 0), + SOC_SINGLE_TLV("Out2 Beep Volume", WM8350_BEEP_VOLUME, + 5, 7, 0, out_mix_tlv), + + SOC_DOUBLE_R("Out1 Playback Switch", + WM8350_LOUT1_VOLUME, + WM8350_ROUT1_VOLUME, + 14, 1, 1), + SOC_DOUBLE_R("Out2 Playback Switch", + WM8350_LOUT2_VOLUME, + WM8350_ROUT2_VOLUME, + 14, 1, 1), +}; + +/* + * DAPM Controls + */ + +/* Left Playback Mixer */ +static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", + WM8350_LEFT_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", + WM8350_LEFT_MIXER_CONTROL, 2, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", + WM8350_LEFT_MIXER_CONTROL, 12, 1, 0), + SOC_DAPM_SINGLE("Left Sidetone Switch", + WM8350_LEFT_MIXER_CONTROL, 0, 1, 0), + SOC_DAPM_SINGLE("Right Sidetone Switch", + WM8350_LEFT_MIXER_CONTROL, 1, 1, 0), +}; + +/* Right Playback Mixer */ +static const struct snd_kcontrol_new wm8350_right_play_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", + WM8350_RIGHT_MIXER_CONTROL, 12, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", + WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0), + SOC_DAPM_SINGLE("Left Playback Switch", + WM8350_RIGHT_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Left Sidetone Switch", + WM8350_RIGHT_MIXER_CONTROL, 0, 1, 0), + SOC_DAPM_SINGLE("Right Sidetone Switch", + WM8350_RIGHT_MIXER_CONTROL, 1, 1, 0), +}; + +/* Out4 Mixer */ +static const struct snd_kcontrol_new wm8350_out4_mixer_controls[] = { + SOC_DAPM_SINGLE("Right Playback Switch", + WM8350_OUT4_MIXER_CONTROL, 12, 1, 0), + SOC_DAPM_SINGLE("Left Playback Switch", + WM8350_OUT4_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Right Capture Switch", + WM8350_OUT4_MIXER_CONTROL, 9, 1, 0), + SOC_DAPM_SINGLE("Out3 Playback Switch", + WM8350_OUT4_MIXER_CONTROL, 2, 1, 0), + SOC_DAPM_SINGLE("Right Mixer Switch", + WM8350_OUT4_MIXER_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("Left Mixer Switch", + WM8350_OUT4_MIXER_CONTROL, 0, 1, 0), +}; + +/* Out3 Mixer */ +static const struct snd_kcontrol_new wm8350_out3_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", + WM8350_OUT3_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Left Capture Switch", + WM8350_OUT3_MIXER_CONTROL, 8, 1, 0), + SOC_DAPM_SINGLE("Out4 Playback Switch", + WM8350_OUT3_MIXER_CONTROL, 3, 1, 0), + SOC_DAPM_SINGLE("Left Mixer Switch", + WM8350_OUT3_MIXER_CONTROL, 0, 1, 0), +}; + +/* Left Input Mixer */ +static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("L2 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_L, 1, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE_TLV("L3 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE("PGA Capture Switch", + WM8350_LEFT_INPUT_VOLUME, 14, 1, 1), +}; + +/* Right Input Mixer */ +static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("L2 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_R, 5, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE_TLV("L3 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE("PGA Capture Switch", + WM8350_RIGHT_INPUT_VOLUME, 14, 1, 1), +}; + +/* Left Mic Mixer */ +static const struct snd_kcontrol_new wm8350_left_mic_mixer_controls[] = { + SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 0, 1, 0), + SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 2, 1, 0), +}; + +/* Right Mic Mixer */ +static const struct snd_kcontrol_new wm8350_right_mic_mixer_controls[] = { + SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 9, 1, 0), + SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 8, 1, 0), + SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 10, 1, 0), +}; + +/* Beep Switch */ +static const struct snd_kcontrol_new wm8350_beep_switch_controls = +SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1); + +/* Out4 Capture Mux */ +static const struct snd_kcontrol_new wm8350_out4_capture_controls = +SOC_DAPM_ENUM("Route", wm8350_enum[7]); + +static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = { + + SND_SOC_DAPM_PGA("IN3R PGA", WM8350_POWER_MGMT_2, 11, 0, NULL, 0), + SND_SOC_DAPM_PGA("IN3L PGA", WM8350_POWER_MGMT_2, 10, 0, NULL, 0), + SND_SOC_DAPM_PGA_E("Right Out2 PGA", WM8350_POWER_MGMT_3, 3, 0, NULL, + 0, pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Left Out2 PGA", WM8350_POWER_MGMT_3, 2, 0, NULL, 0, + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Right Out1 PGA", WM8350_POWER_MGMT_3, 1, 0, NULL, + 0, pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Left Out1 PGA", WM8350_POWER_MGMT_3, 0, 0, NULL, 0, + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MIXER("Right Capture Mixer", WM8350_POWER_MGMT_2, + 7, 0, &wm8350_right_capt_mixer_controls[0], + ARRAY_SIZE(wm8350_right_capt_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Capture Mixer", WM8350_POWER_MGMT_2, + 6, 0, &wm8350_left_capt_mixer_controls[0], + ARRAY_SIZE(wm8350_left_capt_mixer_controls)), + + SND_SOC_DAPM_MIXER("Out4 Mixer", WM8350_POWER_MGMT_2, 5, 0, + &wm8350_out4_mixer_controls[0], + ARRAY_SIZE(wm8350_out4_mixer_controls)), + + SND_SOC_DAPM_MIXER("Out3 Mixer", WM8350_POWER_MGMT_2, 4, 0, + &wm8350_out3_mixer_controls[0], + ARRAY_SIZE(wm8350_out3_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Playback Mixer", WM8350_POWER_MGMT_2, 1, 0, + &wm8350_right_play_mixer_controls[0], + ARRAY_SIZE(wm8350_right_play_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Playback Mixer", WM8350_POWER_MGMT_2, 0, 0, + &wm8350_left_play_mixer_controls[0], + ARRAY_SIZE(wm8350_left_play_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Mic Mixer", WM8350_POWER_MGMT_2, 8, 0, + &wm8350_left_mic_mixer_controls[0], + ARRAY_SIZE(wm8350_left_mic_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Mic Mixer", WM8350_POWER_MGMT_2, 9, 0, + &wm8350_right_mic_mixer_controls[0], + ARRAY_SIZE(wm8350_right_mic_mixer_controls)), + + /* virtual mixer for Beep and Out2R */ + SND_SOC_DAPM_MIXER("Out2 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("Beep", WM8350_POWER_MGMT_3, 7, 0, + &wm8350_beep_switch_controls), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", + WM8350_POWER_MGMT_4, 3, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", + WM8350_POWER_MGMT_4, 2, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", + WM8350_POWER_MGMT_4, 5, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", + WM8350_POWER_MGMT_4, 4, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8350_POWER_MGMT_1, 4, 0), + + SND_SOC_DAPM_MUX("Out4 Capture Channel", SND_SOC_NOPM, 0, 0, + &wm8350_out4_capture_controls), + + SND_SOC_DAPM_OUTPUT("OUT1R"), + SND_SOC_DAPM_OUTPUT("OUT1L"), + SND_SOC_DAPM_OUTPUT("OUT2R"), + SND_SOC_DAPM_OUTPUT("OUT2L"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("OUT4"), + + SND_SOC_DAPM_INPUT("IN1RN"), + SND_SOC_DAPM_INPUT("IN1RP"), + SND_SOC_DAPM_INPUT("IN2R"), + SND_SOC_DAPM_INPUT("IN1LP"), + SND_SOC_DAPM_INPUT("IN1LN"), + SND_SOC_DAPM_INPUT("IN2L"), + SND_SOC_DAPM_INPUT("IN3R"), + SND_SOC_DAPM_INPUT("IN3L"), +}; + +static const struct snd_soc_dapm_route wm8350_dapm_routes[] = { + + /* left playback mixer */ + {"Left Playback Mixer", "Playback Switch", "Left DAC"}, + {"Left Playback Mixer", "Left Bypass Switch", "IN3L PGA"}, + {"Left Playback Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"}, + {"Left Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"}, + + /* right playback mixer */ + {"Right Playback Mixer", "Playback Switch", "Right DAC"}, + {"Right Playback Mixer", "Right Bypass Switch", "IN3R PGA"}, + {"Right Playback Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"}, + {"Right Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"}, + + /* out4 playback mixer */ + {"Out4 Mixer", "Right Playback Switch", "Right DAC"}, + {"Out4 Mixer", "Left Playback Switch", "Left DAC"}, + {"Out4 Mixer", "Right Capture Switch", "Right Capture Mixer"}, + {"Out4 Mixer", "Out3 Playback Switch", "Out3 Mixer"}, + {"Out4 Mixer", "Right Mixer Switch", "Right Playback Mixer"}, + {"Out4 Mixer", "Left Mixer Switch", "Left Playback Mixer"}, + {"OUT4", NULL, "Out4 Mixer"}, + + /* out3 playback mixer */ + {"Out3 Mixer", "Left Playback Switch", "Left DAC"}, + {"Out3 Mixer", "Left Capture Switch", "Left Capture Mixer"}, + {"Out3 Mixer", "Left Mixer Switch", "Left Playback Mixer"}, + {"Out3 Mixer", "Out4 Playback Switch", "Out4 Mixer"}, + {"OUT3", NULL, "Out3 Mixer"}, + + /* out2 */ + {"Right Out2 PGA", NULL, "Right Playback Mixer"}, + {"Left Out2 PGA", NULL, "Left Playback Mixer"}, + {"OUT2L", NULL, "Left Out2 PGA"}, + {"OUT2R", NULL, "Right Out2 PGA"}, + + /* out1 */ + {"Right Out1 PGA", NULL, "Right Playback Mixer"}, + {"Left Out1 PGA", NULL, "Left Playback Mixer"}, + {"OUT1L", NULL, "Left Out1 PGA"}, + {"OUT1R", NULL, "Right Out1 PGA"}, + + /* ADCs */ + {"Left ADC", NULL, "Left Capture Mixer"}, + {"Right ADC", NULL, "Right Capture Mixer"}, + + /* Left capture mixer */ + {"Left Capture Mixer", "L2 Capture Volume", "IN2L"}, + {"Left Capture Mixer", "L3 Capture Volume", "IN3L PGA"}, + {"Left Capture Mixer", "PGA Capture Switch", "Left Mic Mixer"}, + {"Left Capture Mixer", NULL, "Out4 Capture Channel"}, + + /* Right capture mixer */ + {"Right Capture Mixer", "L2 Capture Volume", "IN2R"}, + {"Right Capture Mixer", "L3 Capture Volume", "IN3R PGA"}, + {"Right Capture Mixer", "PGA Capture Switch", "Right Mic Mixer"}, + {"Right Capture Mixer", NULL, "Out4 Capture Channel"}, + + /* L3 Inputs */ + {"IN3L PGA", NULL, "IN3L"}, + {"IN3R PGA", NULL, "IN3R"}, + + /* Left Mic mixer */ + {"Left Mic Mixer", "INN Capture Switch", "IN1LN"}, + {"Left Mic Mixer", "INP Capture Switch", "IN1LP"}, + {"Left Mic Mixer", "IN2 Capture Switch", "IN2L"}, + + /* Right Mic mixer */ + {"Right Mic Mixer", "INN Capture Switch", "IN1RN"}, + {"Right Mic Mixer", "INP Capture Switch", "IN1RP"}, + {"Right Mic Mixer", "IN2 Capture Switch", "IN2R"}, + + /* out 4 capture */ + {"Out4 Capture Channel", NULL, "Out4 Mixer"}, + + /* Beep */ + {"Beep", NULL, "IN3R PGA"}, +}; + +static int wm8350_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8350_data *wm8350_data = snd_soc_component_get_drvdata(component); + struct wm8350 *wm8350 = wm8350_data->wm8350; + u16 fll_4; + + switch (clk_id) { + case WM8350_MCLK_SEL_MCLK: + wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_1, + WM8350_MCLK_SEL); + break; + case WM8350_MCLK_SEL_PLL_MCLK: + case WM8350_MCLK_SEL_PLL_DAC: + case WM8350_MCLK_SEL_PLL_ADC: + case WM8350_MCLK_SEL_PLL_32K: + wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_1, + WM8350_MCLK_SEL); + fll_4 = snd_soc_component_read32(component, WM8350_FLL_CONTROL_4) & + ~WM8350_FLL_CLK_SRC_MASK; + snd_soc_component_write(component, WM8350_FLL_CONTROL_4, fll_4 | clk_id); + break; + } + + /* MCLK direction */ + if (dir == SND_SOC_CLOCK_OUT) + wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_MCLK_DIR); + else + wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_MCLK_DIR); + + return 0; +} + +static int wm8350_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 val; + + switch (div_id) { + case WM8350_ADC_CLKDIV: + val = snd_soc_component_read32(component, WM8350_ADC_DIVIDER) & + ~WM8350_ADC_CLKDIV_MASK; + snd_soc_component_write(component, WM8350_ADC_DIVIDER, val | div); + break; + case WM8350_DAC_CLKDIV: + val = snd_soc_component_read32(component, WM8350_DAC_CLOCK_CONTROL) & + ~WM8350_DAC_CLKDIV_MASK; + snd_soc_component_write(component, WM8350_DAC_CLOCK_CONTROL, val | div); + break; + case WM8350_BCLK_CLKDIV: + val = snd_soc_component_read32(component, WM8350_CLOCK_CONTROL_1) & + ~WM8350_BCLK_DIV_MASK; + snd_soc_component_write(component, WM8350_CLOCK_CONTROL_1, val | div); + break; + case WM8350_OPCLK_CLKDIV: + val = snd_soc_component_read32(component, WM8350_CLOCK_CONTROL_1) & + ~WM8350_OPCLK_DIV_MASK; + snd_soc_component_write(component, WM8350_CLOCK_CONTROL_1, val | div); + break; + case WM8350_SYS_CLKDIV: + val = snd_soc_component_read32(component, WM8350_CLOCK_CONTROL_1) & + ~WM8350_MCLK_DIV_MASK; + snd_soc_component_write(component, WM8350_CLOCK_CONTROL_1, val | div); + break; + case WM8350_DACLR_CLKDIV: + val = snd_soc_component_read32(component, WM8350_DAC_LR_RATE) & + ~WM8350_DACLRC_RATE_MASK; + snd_soc_component_write(component, WM8350_DAC_LR_RATE, val | div); + break; + case WM8350_ADCLR_CLKDIV: + val = snd_soc_component_read32(component, WM8350_ADC_LR_RATE) & + ~WM8350_ADCLRC_RATE_MASK; + snd_soc_component_write(component, WM8350_ADC_LR_RATE, val | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = snd_soc_component_read32(component, WM8350_AI_FORMATING) & + ~(WM8350_AIF_BCLK_INV | WM8350_AIF_LRCLK_INV | WM8350_AIF_FMT_MASK); + u16 master = snd_soc_component_read32(component, WM8350_AI_DAC_CONTROL) & + ~WM8350_BCLK_MSTR; + u16 dac_lrc = snd_soc_component_read32(component, WM8350_DAC_LR_RATE) & + ~WM8350_DACLRC_ENA; + u16 adc_lrc = snd_soc_component_read32(component, WM8350_ADC_LR_RATE) & + ~WM8350_ADCLRC_ENA; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + master |= WM8350_BCLK_MSTR; + dac_lrc |= WM8350_DACLRC_ENA; + adc_lrc |= WM8350_ADCLRC_ENA; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x2 << 8; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x1 << 8; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x3 << 8; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x3 << 8 | WM8350_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= WM8350_AIF_LRCLK_INV | WM8350_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= WM8350_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= WM8350_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8350_AI_FORMATING, iface); + snd_soc_component_write(component, WM8350_AI_DAC_CONTROL, master); + snd_soc_component_write(component, WM8350_DAC_LR_RATE, dac_lrc); + snd_soc_component_write(component, WM8350_ADC_LR_RATE, adc_lrc); + return 0; +} + +static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8350_data *wm8350_data = snd_soc_component_get_drvdata(component); + struct wm8350 *wm8350 = wm8350_data->wm8350; + u16 iface = snd_soc_component_read32(component, WM8350_AI_FORMATING) & + ~WM8350_AIF_WL_MASK; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x1 << 10; + break; + case 24: + iface |= 0x2 << 10; + break; + case 32: + iface |= 0x3 << 10; + break; + } + + snd_soc_component_write(component, WM8350_AI_FORMATING, iface); + + /* The sloping stopband filter is recommended for use with + * lower sample rates to improve performance. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (params_rate(params) < 24000) + wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + else + wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + } + + return 0; +} + +static int wm8350_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + unsigned int val; + + if (mute) + val = WM8350_DAC_MUTE_ENA; + else + val = 0; + + snd_soc_component_update_bits(component, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA, val); + + return 0; +} + +/* FLL divisors */ +struct _fll_div { + int div; /* FLL_OUTDIV */ + int n; + int k; + int ratio; /* FLL_FRATIO */ +}; + +/* The size in bits of the fll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static inline int fll_factors(struct _fll_div *fll_div, unsigned int input, + unsigned int output) +{ + u64 Kpart; + unsigned int t1, t2, K, Nmod; + + if (output >= 2815250 && output <= 3125000) + fll_div->div = 0x4; + else if (output >= 5625000 && output <= 6250000) + fll_div->div = 0x3; + else if (output >= 11250000 && output <= 12500000) + fll_div->div = 0x2; + else if (output >= 22500000 && output <= 25000000) + fll_div->div = 0x1; + else { + printk(KERN_ERR "wm8350: fll freq %d out of range\n", output); + return -EINVAL; + } + + if (input > 48000) + fll_div->ratio = 1; + else + fll_div->ratio = 8; + + t1 = output * (1 << (fll_div->div + 1)); + t2 = input * fll_div->ratio; + + fll_div->n = t1 / t2; + Nmod = t1 % t2; + + if (Nmod) { + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + do_div(Kpart, t2); + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + fll_div->k = K; + } else + fll_div->k = 0; + + return 0; +} + +static int wm8350_set_fll(struct snd_soc_dai *codec_dai, + int pll_id, int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8350_data *priv = snd_soc_component_get_drvdata(component); + struct wm8350 *wm8350 = priv->wm8350; + struct _fll_div fll_div; + int ret = 0; + u16 fll_1, fll_4; + + if (freq_in == priv->fll_freq_in && freq_out == priv->fll_freq_out) + return 0; + + /* power down FLL - we need to do this for reconfiguration */ + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, + WM8350_FLL_ENA | WM8350_FLL_OSC_ENA); + + if (freq_out == 0 || freq_in == 0) + return ret; + + ret = fll_factors(&fll_div, freq_in, freq_out); + if (ret < 0) + return ret; + dev_dbg(wm8350->dev, + "FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d", + freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div, + fll_div.ratio); + + /* set up N.K & dividers */ + fll_1 = snd_soc_component_read32(component, WM8350_FLL_CONTROL_1) & + ~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000); + snd_soc_component_write(component, WM8350_FLL_CONTROL_1, + fll_1 | (fll_div.div << 8) | 0x50); + snd_soc_component_write(component, WM8350_FLL_CONTROL_2, + (fll_div.ratio << 11) | (fll_div. + n & WM8350_FLL_N_MASK)); + snd_soc_component_write(component, WM8350_FLL_CONTROL_3, fll_div.k); + fll_4 = snd_soc_component_read32(component, WM8350_FLL_CONTROL_4) & + ~(WM8350_FLL_FRAC | WM8350_FLL_SLOW_LOCK_REF); + snd_soc_component_write(component, WM8350_FLL_CONTROL_4, + fll_4 | (fll_div.k ? WM8350_FLL_FRAC : 0) | + (fll_div.ratio == 8 ? WM8350_FLL_SLOW_LOCK_REF : 0)); + + /* power FLL on */ + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_OSC_ENA); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_ENA); + + priv->fll_freq_out = freq_out; + priv->fll_freq_in = freq_in; + + return 0; +} + +static int wm8350_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8350_data *priv = snd_soc_component_get_drvdata(component); + struct wm8350 *wm8350 = priv->wm8350; + struct wm8350_audio_platform_data *platform = + wm8350->codec.platform_data; + u16 pm1; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_VMID_50K | + platform->codec_current_on << 14); + break; + + case SND_SOC_BIAS_PREPARE: + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1); + pm1 &= ~WM8350_VMID_MASK; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_VMID_50K); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret != 0) + return ret; + + /* Enable the system clock */ + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, + WM8350_SYSCLK_ENA); + + /* mute DAC & outputs */ + wm8350_set_bits(wm8350, WM8350_DAC_MUTE, + WM8350_DAC_MUTE_ENA); + + /* discharge cap memory */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + platform->dis_out1 | + (platform->dis_out2 << 2) | + (platform->dis_out3 << 4) | + (platform->dis_out4 << 6)); + + /* wait for discharge */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform-> + cap_discharge_msecs)); + + /* enable antipop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8)); + + /* ramp up vmid */ + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + (platform-> + codec_current_charge << 14) | + WM8350_VMID_5K | WM8350_VMIDEN | + WM8350_VBUFEN); + + /* wait for vmid */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform-> + vmid_charge_msecs)); + + /* turn on vmid 300k */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK); + pm1 |= WM8350_VMID_300K | + (platform->codec_current_standby << 14); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1); + + + /* enable analogue bias */ + pm1 |= WM8350_BIASEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* disable antipop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0); + + } else { + /* turn on vmid 300k and reduce current */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_VMID_300K | + (platform-> + codec_current_standby << 14)); + + } + break; + + case SND_SOC_BIAS_OFF: + + /* mute DAC & enable outputs */ + wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA); + + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_3, + WM8350_OUT1L_ENA | WM8350_OUT1R_ENA | + WM8350_OUT2L_ENA | WM8350_OUT2R_ENA); + + /* enable anti pop S curve */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8)); + + /* turn off vmid */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~WM8350_VMIDEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* wait */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform-> + vmid_discharge_msecs)); + + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8) | + platform->dis_out1 | + (platform->dis_out2 << 2) | + (platform->dis_out3 << 4) | + (platform->dis_out4 << 6)); + + /* turn off VBuf and drain */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VBUFEN | WM8350_VMID_MASK); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_OUTPUT_DRAIN_EN); + + /* wait */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform->drain_msecs)); + + pm1 &= ~WM8350_BIASEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* disable anti-pop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0); + + wm8350_clear_bits(wm8350, WM8350_LOUT1_VOLUME, + WM8350_OUT1L_ENA); + wm8350_clear_bits(wm8350, WM8350_ROUT1_VOLUME, + WM8350_OUT1R_ENA); + wm8350_clear_bits(wm8350, WM8350_LOUT2_VOLUME, + WM8350_OUT2L_ENA); + wm8350_clear_bits(wm8350, WM8350_ROUT2_VOLUME, + WM8350_OUT2R_ENA); + + /* disable clock gen */ + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, + WM8350_SYSCLK_ENA); + + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + break; + } + return 0; +} + +static void wm8350_hp_work(struct wm8350_data *priv, + struct wm8350_jack_data *jack, + u16 mask) +{ + struct wm8350 *wm8350 = priv->wm8350; + u16 reg; + int report; + + reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS); + if (reg & mask) + report = jack->report; + else + report = 0; + + snd_soc_jack_report(jack->jack, report, jack->report); + +} + +static void wm8350_hpl_work(struct work_struct *work) +{ + struct wm8350_data *priv = + container_of(work, struct wm8350_data, hpl.work.work); + + wm8350_hp_work(priv, &priv->hpl, WM8350_JACK_L_LVL); +} + +static void wm8350_hpr_work(struct work_struct *work) +{ + struct wm8350_data *priv = + container_of(work, struct wm8350_data, hpr.work.work); + + wm8350_hp_work(priv, &priv->hpr, WM8350_JACK_R_LVL); +} + +static irqreturn_t wm8350_hpl_jack_handler(int irq, void *data) +{ + struct wm8350_data *priv = data; + struct wm8350 *wm8350 = priv->wm8350; + +#ifndef CONFIG_SND_SOC_WM8350_MODULE + trace_snd_soc_jack_irq("WM8350 HPL"); +#endif + + if (device_may_wakeup(wm8350->dev)) + pm_wakeup_event(wm8350->dev, 250); + + queue_delayed_work(system_power_efficient_wq, + &priv->hpl.work, msecs_to_jiffies(200)); + + return IRQ_HANDLED; +} + +static irqreturn_t wm8350_hpr_jack_handler(int irq, void *data) +{ + struct wm8350_data *priv = data; + struct wm8350 *wm8350 = priv->wm8350; + +#ifndef CONFIG_SND_SOC_WM8350_MODULE + trace_snd_soc_jack_irq("WM8350 HPR"); +#endif + + if (device_may_wakeup(wm8350->dev)) + pm_wakeup_event(wm8350->dev, 250); + + queue_delayed_work(system_power_efficient_wq, + &priv->hpr.work, msecs_to_jiffies(200)); + + return IRQ_HANDLED; +} + +/** + * wm8350_hp_jack_detect - Enable headphone jack detection. + * + * @component: WM8350 component + * @which: left or right jack detect signal + * @jack: jack to report detection events on + * @report: value to report + * + * Enables the headphone jack detection of the WM8350. If no report + * is specified then detection is disabled. + */ +int wm8350_hp_jack_detect(struct snd_soc_component *component, enum wm8350_jack which, + struct snd_soc_jack *jack, int report) +{ + struct wm8350_data *priv = snd_soc_component_get_drvdata(component); + struct wm8350 *wm8350 = priv->wm8350; + int ena; + + switch (which) { + case WM8350_JDL: + priv->hpl.jack = jack; + priv->hpl.report = report; + ena = WM8350_JDL_ENA; + break; + + case WM8350_JDR: + priv->hpr.jack = jack; + priv->hpr.report = report; + ena = WM8350_JDR_ENA; + break; + + default: + return -EINVAL; + } + + if (report) { + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena); + } else { + wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, ena); + } + + /* Sync status */ + switch (which) { + case WM8350_JDL: + wm8350_hpl_jack_handler(0, priv); + break; + case WM8350_JDR: + wm8350_hpr_jack_handler(0, priv); + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect); + +static irqreturn_t wm8350_mic_handler(int irq, void *data) +{ + struct wm8350_data *priv = data; + struct wm8350 *wm8350 = priv->wm8350; + u16 reg; + int report = 0; + +#ifndef CONFIG_SND_SOC_WM8350_MODULE + trace_snd_soc_jack_irq("WM8350 mic"); +#endif + + reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS); + if (reg & WM8350_JACK_MICSCD_LVL) + report |= priv->mic.short_report; + if (reg & WM8350_JACK_MICSD_LVL) + report |= priv->mic.report; + + snd_soc_jack_report(priv->mic.jack, report, + priv->mic.report | priv->mic.short_report); + + return IRQ_HANDLED; +} + +/** + * wm8350_mic_jack_detect - Enable microphone jack detection. + * + * @component: WM8350 component + * @jack: jack to report detection events on + * @detect_report: value to report when presence detected + * @short_report: value to report when microphone short detected + * + * Enables the microphone jack detection of the WM8350. If both reports + * are specified as zero then detection is disabled. + */ +int wm8350_mic_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, + int detect_report, int short_report) +{ + struct wm8350_data *priv = snd_soc_component_get_drvdata(component); + struct wm8350 *wm8350 = priv->wm8350; + + priv->mic.jack = jack; + priv->mic.report = detect_report; + priv->mic.short_report = short_report; + + if (detect_report || short_report) { + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1, + WM8350_MIC_DET_ENA); + } else { + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_1, + WM8350_MIC_DET_ENA); + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect); + +#define WM8350_RATES (SNDRV_PCM_RATE_8000_96000) + +#define WM8350_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8350_dai_ops = { + .hw_params = wm8350_pcm_hw_params, + .digital_mute = wm8350_mute, + .set_fmt = wm8350_set_dai_fmt, + .set_sysclk = wm8350_set_dai_sysclk, + .set_pll = wm8350_set_fll, + .set_clkdiv = wm8350_set_clkdiv, +}; + +static struct snd_soc_dai_driver wm8350_dai = { + .name = "wm8350-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8350_RATES, + .formats = WM8350_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8350_RATES, + .formats = WM8350_FORMATS, + }, + .ops = &wm8350_dai_ops, +}; + +static int wm8350_component_probe(struct snd_soc_component *component) +{ + struct wm8350 *wm8350 = dev_get_platdata(component->dev); + struct wm8350_data *priv; + struct wm8350_output *out1; + struct wm8350_output *out2; + int ret, i; + + if (wm8350->codec.platform_data == NULL) { + dev_err(component->dev, "No audio platform data supplied\n"); + return -EINVAL; + } + + priv = devm_kzalloc(component->dev, sizeof(struct wm8350_data), + GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + snd_soc_component_init_regmap(component, wm8350->regmap); + snd_soc_component_set_drvdata(component, priv); + + priv->wm8350 = wm8350; + + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + priv->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(wm8350->dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret != 0) + return ret; + + /* Put the codec into reset if it wasn't already */ + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); + + INIT_DELAYED_WORK(&priv->pga_work, wm8350_pga_work); + INIT_DELAYED_WORK(&priv->hpl.work, wm8350_hpl_work); + INIT_DELAYED_WORK(&priv->hpr.work, wm8350_hpr_work); + + /* Enable the codec */ + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); + + /* Enable robust clocking mode in ADC */ + snd_soc_component_write(component, WM8350_SECURITY, 0xa7); + snd_soc_component_write(component, 0xde, 0x13); + snd_soc_component_write(component, WM8350_SECURITY, 0); + + /* read OUT1 & OUT2 volumes */ + out1 = &priv->out1; + out2 = &priv->out2; + out1->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME) & + WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + out1->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME) & + WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + out2->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME) & + WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + out2->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME) & + WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, 0); + wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, 0); + wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, 0); + wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, 0); + + /* Latch VU bits & mute */ + wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, + WM8350_OUT1_VU | WM8350_OUT1L_MUTE); + wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, + WM8350_OUT2_VU | WM8350_OUT2L_MUTE); + wm8350_set_bits(wm8350, WM8350_ROUT1_VOLUME, + WM8350_OUT1_VU | WM8350_OUT1R_MUTE); + wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME, + WM8350_OUT2_VU | WM8350_OUT2R_MUTE); + + /* Make sure AIF tristating is disabled by default */ + wm8350_clear_bits(wm8350, WM8350_AI_FORMATING, WM8350_AIF_TRI); + + /* Make sure we've got a sane companding setup too */ + wm8350_clear_bits(wm8350, WM8350_ADC_DAC_COMP, + WM8350_DAC_COMP | WM8350_LOOPBACK); + + /* Make sure jack detect is disabled to start off with */ + wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, + WM8350_JDL_ENA | WM8350_JDR_ENA); + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, + wm8350_hpl_jack_handler, 0, "Left jack detect", + priv); + if (ret != 0) + goto err; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, + wm8350_hpr_jack_handler, 0, "Right jack detect", + priv); + if (ret != 0) + goto free_jck_det_l; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, + wm8350_mic_handler, 0, "Microphone short", priv); + if (ret != 0) + goto free_jck_det_r; + + ret = wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICD, + wm8350_mic_handler, 0, "Microphone detect", priv); + if (ret != 0) + goto free_micscd; + + return 0; + +free_micscd: + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, priv); +free_jck_det_r: + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv); +free_jck_det_l: + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv); +err: + return ret; +} + +static void wm8350_component_remove(struct snd_soc_component *component) +{ + struct wm8350_data *priv = snd_soc_component_get_drvdata(component); + struct wm8350 *wm8350 = dev_get_platdata(component->dev); + + wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, + WM8350_JDL_ENA | WM8350_JDR_ENA); + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICD, priv); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, priv); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv); + + priv->hpl.jack = NULL; + priv->hpr.jack = NULL; + priv->mic.jack = NULL; + + cancel_delayed_work_sync(&priv->hpl.work); + cancel_delayed_work_sync(&priv->hpr.work); + + /* if there was any work waiting then we run it now and + * wait for its completion */ + flush_delayed_work(&priv->pga_work); + + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8350 = { + .probe = wm8350_component_probe, + .remove = wm8350_component_remove, + .set_bias_level = wm8350_set_bias_level, + .controls = wm8350_snd_controls, + .num_controls = ARRAY_SIZE(wm8350_snd_controls), + .dapm_widgets = wm8350_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8350_dapm_widgets), + .dapm_routes = wm8350_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8350_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8350_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm8350, + &wm8350_dai, 1); +} + +static struct platform_driver wm8350_codec_driver = { + .driver = { + .name = "wm8350-codec", + }, + .probe = wm8350_probe, +}; + +module_platform_driver(wm8350_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8350 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-codec"); diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h new file mode 100644 index 000000000..1191326c4 --- /dev/null +++ b/sound/soc/codecs/wm8350.h @@ -0,0 +1,29 @@ +/* + * wm8350.h - WM8903 audio codec interface + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8350_H +#define _WM8350_H + +#include +#include + +enum wm8350_jack { + WM8350_JDL = 1, + WM8350_JDR = 2, +}; + +int wm8350_hp_jack_detect(struct snd_soc_component *component, enum wm8350_jack which, + struct snd_soc_jack *jack, int report); +int wm8350_mic_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, + int detect_report, int short_report); + +#endif diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c new file mode 100644 index 000000000..57b22065a --- /dev/null +++ b/sound/soc/codecs/wm8400.c @@ -0,0 +1,1363 @@ +/* + * wm8400.c -- WM8400 ALSA Soc Audio driver + * + * Copyright 2008-11 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8400.h" + +static struct regulator_bulk_data power[] = { + { + .supply = "I2S1VDD", + }, + { + .supply = "I2S2VDD", + }, + { + .supply = "DCVDD", + }, + { + .supply = "AVDD", + }, + { + .supply = "FLLVDD", + }, + { + .supply = "HPVDD", + }, + { + .supply = "SPKVDD", + }, +}; + +/* codec private data */ +struct wm8400_priv { + struct wm8400 *wm8400; + u16 fake_register; + unsigned int sysclk; + unsigned int pcmclk; + int fll_in, fll_out; +}; + +static void wm8400_component_reset(struct snd_soc_component *component) +{ + struct wm8400_priv *wm8400 = snd_soc_component_get_drvdata(component); + + wm8400_reset_codec_reg_cache(wm8400->wm8400); +} + +static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 600, 0); + +static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1650, 3000, 0); + +static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -2100, 0, 0); + +static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -7300, 600, 0); + +static const DECLARE_TLV_DB_SCALE(out_omix_tlv, -600, 0, 0); + +static const DECLARE_TLV_DB_SCALE(out_dac_tlv, -7163, 0, 0); + +static const DECLARE_TLV_DB_SCALE(in_adc_tlv, -7163, 1763, 0); + +static const DECLARE_TLV_DB_SCALE(out_sidetone_tlv, -3600, 0, 0); + +static int wm8400_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = snd_soc_component_read32(component, reg); + return snd_soc_component_write(component, reg, val | 0x0100); +} + +#define WM8400_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, reg, shift, max, invert, \ + snd_soc_get_volsw, wm8400_outpga_put_volsw_vu, tlv_array) + + +static const char *wm8400_digital_sidetone[] = + {"None", "Left ADC", "Right ADC", "Reserved"}; + +static SOC_ENUM_SINGLE_DECL(wm8400_left_digital_sidetone_enum, + WM8400_DIGITAL_SIDE_TONE, + WM8400_ADC_TO_DACL_SHIFT, + wm8400_digital_sidetone); + +static SOC_ENUM_SINGLE_DECL(wm8400_right_digital_sidetone_enum, + WM8400_DIGITAL_SIDE_TONE, + WM8400_ADC_TO_DACR_SHIFT, + wm8400_digital_sidetone); + +static const char *wm8400_adcmode[] = + {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static SOC_ENUM_SINGLE_DECL(wm8400_right_adcmode_enum, + WM8400_ADC_CTRL, + WM8400_ADC_HPF_CUT_SHIFT, + wm8400_adcmode); + +static const struct snd_kcontrol_new wm8400_snd_controls[] = { +/* INMIXL */ +SOC_SINGLE("LIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L12MNBST_SHIFT, + 1, 0), +SOC_SINGLE("LIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L34MNBST_SHIFT, + 1, 0), +/* INMIXR */ +SOC_SINGLE("RIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R12MNBST_SHIFT, + 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R34MNBST_SHIFT, + 1, 0), + +/* LOMIX */ +SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LLI3LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LR12LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LL12LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRI3LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv), + +/* ROMIX */ +SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RRI3ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RL12ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RR12ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RLI3ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RLBROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RRBROVOL_SHIFT, 7, 0, out_mix_tlv), + +/* LOUT */ +WM8400_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8400_LEFT_OUTPUT_VOLUME, + WM8400_LOUTVOL_SHIFT, WM8400_LOUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOUT ZC", WM8400_LEFT_OUTPUT_VOLUME, WM8400_LOZC_SHIFT, 1, 0), + +/* ROUT */ +WM8400_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8400_RIGHT_OUTPUT_VOLUME, + WM8400_ROUTVOL_SHIFT, WM8400_ROUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROUT ZC", WM8400_RIGHT_OUTPUT_VOLUME, WM8400_ROZC_SHIFT, 1, 0), + +/* LOPGA */ +WM8400_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8400_LEFT_OPGA_VOLUME, + WM8400_LOPGAVOL_SHIFT, WM8400_LOPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOPGA ZC Switch", WM8400_LEFT_OPGA_VOLUME, + WM8400_LOPGAZC_SHIFT, 1, 0), + +/* ROPGA */ +WM8400_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8400_RIGHT_OPGA_VOLUME, + WM8400_ROPGAVOL_SHIFT, WM8400_ROPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROPGA ZC Switch", WM8400_RIGHT_OPGA_VOLUME, + WM8400_ROPGAZC_SHIFT, 1, 0), + +SOC_SINGLE("LON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LONMUTE_SHIFT, 1, 0), +SOC_SINGLE("LOP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LOPMUTE_SHIFT, 1, 0), +SOC_SINGLE("LOP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LOATTN_SHIFT, 1, 0), +SOC_SINGLE("RON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_RONMUTE_SHIFT, 1, 0), +SOC_SINGLE("ROP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_ROPMUTE_SHIFT, 1, 0), +SOC_SINGLE("ROP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_ROATTN_SHIFT, 1, 0), + +SOC_SINGLE("OUT3 Mute Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT3MUTE_SHIFT, 1, 0), +SOC_SINGLE("OUT3 Attenuation Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT3ATTN_SHIFT, 1, 0), + +SOC_SINGLE("OUT4 Mute Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT4MUTE_SHIFT, 1, 0), +SOC_SINGLE("OUT4 Attenuation Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT4ATTN_SHIFT, 1, 0), + +SOC_SINGLE("Speaker Mode Switch", WM8400_CLASSD1, + WM8400_CDMODE_SHIFT, 1, 0), + +SOC_SINGLE("Speaker Output Attenuation Volume", WM8400_SPEAKER_VOLUME, + WM8400_SPKATTN_SHIFT, WM8400_SPKATTN_MASK, 0), +SOC_SINGLE("Speaker DC Boost Volume", WM8400_CLASSD3, + WM8400_DCGAIN_SHIFT, 6, 0), +SOC_SINGLE("Speaker AC Boost Volume", WM8400_CLASSD3, + WM8400_ACGAIN_SHIFT, 6, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8400_LEFT_DAC_DIGITAL_VOLUME, WM8400_DACL_VOL_SHIFT, + 127, 0, out_dac_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8400_RIGHT_DAC_DIGITAL_VOLUME, WM8400_DACR_VOL_SHIFT, + 127, 0, out_dac_tlv), + +SOC_ENUM("Left Digital Sidetone", wm8400_left_digital_sidetone_enum), +SOC_ENUM("Right Digital Sidetone", wm8400_right_digital_sidetone_enum), + +SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE, + WM8400_ADCL_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv), +SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE, + WM8400_ADCR_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv), + +SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8400_ADC_CTRL, + WM8400_ADC_HPF_ENA_SHIFT, 1, 0), + +SOC_ENUM("ADC HPF Mode", wm8400_right_adcmode_enum), + +WM8400_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8400_LEFT_ADC_DIGITAL_VOLUME, + WM8400_ADCL_VOL_SHIFT, + WM8400_ADCL_VOL_MASK, + 0, + in_adc_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8400_RIGHT_ADC_DIGITAL_VOLUME, + WM8400_ADCR_VOL_SHIFT, + WM8400_ADCR_VOL_MASK, + 0, + in_adc_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LIN12VOL_SHIFT, + WM8400_LIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN12 ZC Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LI12ZC_SHIFT, 1, 0), + +SOC_SINGLE("LIN12 Mute Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LI12MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LIN34VOL_SHIFT, + WM8400_LIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN34 ZC Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LI34ZC_SHIFT, 1, 0), + +SOC_SINGLE("LIN34 Mute Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LI34MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RIN12VOL_SHIFT, + WM8400_RIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN12 ZC Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RI12ZC_SHIFT, 1, 0), + +SOC_SINGLE("RIN12 Mute Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RI12MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RIN34VOL_SHIFT, + WM8400_RIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN34 ZC Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RI34ZC_SHIFT, 1, 0), + +SOC_SINGLE("RIN34 Mute Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RI34MUTE_SHIFT, 1, 0), + +}; + +/* + * _DAPM_ Controls + */ + +static int outmixer_event (struct snd_soc_dapm_widget *w, + struct snd_kcontrol * kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 reg_shift = mc->shift; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8400_SPEAKER_MIXER | (WM8400_LDSPK << 8) : + reg = snd_soc_component_read32(component, WM8400_OUTPUT_MIXER1); + if (reg & WM8400_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + case WM8400_SPEAKER_MIXER | (WM8400_RDSPK << 8): + reg = snd_soc_component_read32(component, WM8400_OUTPUT_MIXER2); + if (reg & WM8400_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + case WM8400_OUTPUT_MIXER1 | (WM8400_LDLO << 8): + reg = snd_soc_component_read32(component, WM8400_SPEAKER_MIXER); + if (reg & WM8400_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + case WM8400_OUTPUT_MIXER2 | (WM8400_RDRO << 8): + reg = snd_soc_component_read32(component, WM8400_SPEAKER_MIXER); + if (reg & WM8400_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const DECLARE_TLV_DB_SCALE(in_mix_tlv, -1200, 600, 0); + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8400_dapm_lin12_pga_controls[] = { +SOC_DAPM_SINGLE("LIN1 Switch", WM8400_INPUT_MIXER2, WM8400_LMN1_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LIN2 Switch", WM8400_INPUT_MIXER2, WM8400_LMP2_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8400_dapm_lin34_pga_controls[] = { +SOC_DAPM_SINGLE("LIN3 Switch", WM8400_INPUT_MIXER2, WM8400_LMN3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LIN4 Switch", WM8400_INPUT_MIXER2, WM8400_LMP4_SHIFT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8400_dapm_rin12_pga_controls[] = { +SOC_DAPM_SINGLE("RIN1 Switch", WM8400_INPUT_MIXER2, WM8400_RMN1_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RIN2 Switch", WM8400_INPUT_MIXER2, WM8400_RMP2_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8400_dapm_rin34_pga_controls[] = { +SOC_DAPM_SINGLE("RIN3 Switch", WM8400_INPUT_MIXER2, WM8400_RMN3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RIN4 Switch", WM8400_INPUT_MIXER2, WM8400_RMP4_SHIFT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8400_dapm_inmixl_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8400_INPUT_MIXER3, + WM8400_LDBVOL_SHIFT, WM8400_LDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8400_INPUT_MIXER5, WM8400_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("LINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT, + 1, 0), +SOC_DAPM_SINGLE("LINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8400_dapm_inmixr_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8400_INPUT_MIXER4, + WM8400_RDBVOL_SHIFT, WM8400_RDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8400_INPUT_MIXER6, WM8400_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT, + 1, 0), +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8400_ainlmux[] = + {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static SOC_ENUM_SINGLE_DECL(wm8400_ainlmux_enum, + WM8400_INPUT_MIXER1, + WM8400_AINLMODE_SHIFT, + wm8400_ainlmux); + +static const struct snd_kcontrol_new wm8400_dapm_ainlmux_controls = +SOC_DAPM_ENUM("Route", wm8400_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8400_ainrmux[] = + {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static SOC_ENUM_SINGLE_DECL(wm8400_ainrmux_enum, + WM8400_INPUT_MIXER1, + WM8400_AINRMODE_SHIFT, + wm8400_ainrmux); + +static const struct snd_kcontrol_new wm8400_dapm_ainrmux_controls = +SOC_DAPM_ENUM("Route", wm8400_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8400_dapm_rxvoice_controls[] = { +SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8400_INPUT_MIXER5, WM8400_LR4BVOL_SHIFT, + WM8400_LR4BVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8400_INPUT_MIXER6, WM8400_RL4BVOL_SHIFT, + WM8400_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lomix_controls[] = { +SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LRBLO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LLBLO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LRI3LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LLI3LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LR12LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LL12LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8400_OUTPUT_MIXER1, + WM8400_LDLO_SHIFT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8400_dapm_romix_controls[] = { +SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RLBRO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RRBRO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RLI3RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RRI3RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RL12RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RR12RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8400_OUTPUT_MIXER2, + WM8400_RDRO_SHIFT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lonmix_controls[] = { +SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LLOPGALON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LROPGALON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8400_LINE_MIXER1, + WM8400_LOPLON_SHIFT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lopmix_controls[] = { +SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER1, + WM8400_LR12LOP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER1, + WM8400_LL12LOP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LLOPGALOP_SHIFT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8400_dapm_ronmix_controls[] = { +SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RROPGARON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RLOPGARON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8400_LINE_MIXER2, + WM8400_ROPRON_SHIFT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8400_dapm_ropmix_controls[] = { +SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER2, + WM8400_RL12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER2, + WM8400_RR12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RROPGAROP_SHIFT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8400_dapm_out3mix_controls[] = { +SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER, + WM8400_LI4O3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8400_OUT3_4_MIXER, + WM8400_LPGAO3_SHIFT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8400_dapm_out4mix_controls[] = { +SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8400_OUT3_4_MIXER, + WM8400_RPGAO4_SHIFT, 1, 0), +SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER, + WM8400_RI4O4_SHIFT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8400_dapm_spkmix_controls[] = { +SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_LI2SPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_LB2SPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8400_SPEAKER_MIXER, + WM8400_LOPGASPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8400_SPEAKER_MIXER, + WM8400_LDSPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8400_SPEAKER_MIXER, + WM8400_RDSPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8400_SPEAKER_MIXER, + WM8400_ROPGASPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_RL12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_RI2SPK_SHIFT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8400_dapm_widgets[] = { +/* Input Side */ +/* Input Lines */ +SND_SOC_DAPM_INPUT("LIN1"), +SND_SOC_DAPM_INPUT("LIN2"), +SND_SOC_DAPM_INPUT("LIN3"), +SND_SOC_DAPM_INPUT("LIN4/RXN"), +SND_SOC_DAPM_INPUT("RIN3"), +SND_SOC_DAPM_INPUT("RIN4/RXP"), +SND_SOC_DAPM_INPUT("RIN1"), +SND_SOC_DAPM_INPUT("RIN2"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +/* DACs */ +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8400_POWER_MANAGEMENT_2, + WM8400_ADCL_ENA_SHIFT, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8400_POWER_MANAGEMENT_2, + WM8400_ADCR_ENA_SHIFT, 0), + +/* Input PGAs */ +SND_SOC_DAPM_MIXER("LIN12 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_LIN12_ENA_SHIFT, + 0, &wm8400_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_lin12_pga_controls)), +SND_SOC_DAPM_MIXER("LIN34 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_LIN34_ENA_SHIFT, + 0, &wm8400_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_lin34_pga_controls)), +SND_SOC_DAPM_MIXER("RIN12 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_RIN12_ENA_SHIFT, + 0, &wm8400_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_rin12_pga_controls)), +SND_SOC_DAPM_MIXER("RIN34 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_RIN34_ENA_SHIFT, + 0, &wm8400_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_rin34_pga_controls)), + +SND_SOC_DAPM_SUPPLY("INL", WM8400_POWER_MANAGEMENT_2, WM8400_AINL_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("INR", WM8400_POWER_MANAGEMENT_2, WM8400_AINR_ENA_SHIFT, + 0, NULL, 0), + +/* INMIXL */ +SND_SOC_DAPM_MIXER("INMIXL", SND_SOC_NOPM, 0, 0, + &wm8400_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8400_dapm_inmixl_controls)), + +/* AINLMUX */ +SND_SOC_DAPM_MUX("AILNMUX", SND_SOC_NOPM, 0, 0, &wm8400_dapm_ainlmux_controls), + +/* INMIXR */ +SND_SOC_DAPM_MIXER("INMIXR", SND_SOC_NOPM, 0, 0, + &wm8400_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8400_dapm_inmixr_controls)), + +/* AINRMUX */ +SND_SOC_DAPM_MUX("AIRNMUX", SND_SOC_NOPM, 0, 0, &wm8400_dapm_ainrmux_controls), + +/* Output Side */ +/* DACs */ +SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8400_POWER_MANAGEMENT_3, + WM8400_DACL_ENA_SHIFT, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8400_POWER_MANAGEMENT_3, + WM8400_DACR_ENA_SHIFT, 0), + +/* LOMIX */ +SND_SOC_DAPM_MIXER_E("LOMIX", WM8400_POWER_MANAGEMENT_3, + WM8400_LOMIX_ENA_SHIFT, + 0, &wm8400_dapm_lomix_controls[0], + ARRAY_SIZE(wm8400_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LONMIX */ +SND_SOC_DAPM_MIXER("LONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LON_ENA_SHIFT, + 0, &wm8400_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8400_dapm_lonmix_controls)), + +/* LOPMIX */ +SND_SOC_DAPM_MIXER("LOPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LOP_ENA_SHIFT, + 0, &wm8400_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8400_dapm_lopmix_controls)), + +/* OUT3MIX */ +SND_SOC_DAPM_MIXER("OUT3MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT3_ENA_SHIFT, + 0, &wm8400_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8400_dapm_out3mix_controls)), + +/* SPKMIX */ +SND_SOC_DAPM_MIXER_E("SPKMIX", WM8400_POWER_MANAGEMENT_1, WM8400_SPK_ENA_SHIFT, + 0, &wm8400_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8400_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + +/* OUT4MIX */ +SND_SOC_DAPM_MIXER("OUT4MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT4_ENA_SHIFT, + 0, &wm8400_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8400_dapm_out4mix_controls)), + +/* ROPMIX */ +SND_SOC_DAPM_MIXER("ROPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_ROP_ENA_SHIFT, + 0, &wm8400_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8400_dapm_ropmix_controls)), + +/* RONMIX */ +SND_SOC_DAPM_MIXER("RONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_RON_ENA_SHIFT, + 0, &wm8400_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8400_dapm_ronmix_controls)), + +/* ROMIX */ +SND_SOC_DAPM_MIXER_E("ROMIX", WM8400_POWER_MANAGEMENT_3, + WM8400_ROMIX_ENA_SHIFT, + 0, &wm8400_dapm_romix_controls[0], + ARRAY_SIZE(wm8400_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LOUT PGA */ +SND_SOC_DAPM_PGA("LOUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_LOUT_ENA_SHIFT, + 0, NULL, 0), + +/* ROUT PGA */ +SND_SOC_DAPM_PGA("ROUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_ROUT_ENA_SHIFT, + 0, NULL, 0), + +/* LOPGA */ +SND_SOC_DAPM_PGA("LOPGA", WM8400_POWER_MANAGEMENT_3, WM8400_LOPGA_ENA_SHIFT, 0, + NULL, 0), + +/* ROPGA */ +SND_SOC_DAPM_PGA("ROPGA", WM8400_POWER_MANAGEMENT_3, WM8400_ROPGA_ENA_SHIFT, 0, + NULL, 0), + +/* MICBIAS */ +SND_SOC_DAPM_SUPPLY("MICBIAS", WM8400_POWER_MANAGEMENT_1, + WM8400_MIC1BIAS_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route wm8400_dapm_routes[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording + * even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"}, + /* INMIXL */ + {"INMIXL", NULL, "INL"}, + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AILNMUX */ + {"AILNMUX", NULL, "INL"}, + {"AILNMUX", "INMIXL Mix", "INMIXL"}, + {"AILNMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AILNMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Left ADC", NULL, "AILNMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"}, + /* INMIXR */ + {"INMIXR", NULL, "INR"}, + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AIRNMUX */ + {"AIRNMUX", NULL, "INR"}, + {"AIRNMUX", "INMIXR Mix", "INMIXR"}, + {"AIRNMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AIRNMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AIRNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Right ADC", NULL, "AIRNMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AIRNMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AILNMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AIRNMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AILNMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AILNMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AIRNMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT3", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +/* + * Clock after FLL and dividers + */ +static int wm8400_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8400_priv *wm8400 = snd_soc_component_get_drvdata(component); + + wm8400->sysclk = freq; + return 0; +} + +struct fll_factors { + u16 n; + u16 k; + u16 outdiv; + u16 fratio; + u16 freq_ref; +}; + +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors, + unsigned int Fref, unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Nmod, target; + + factors->outdiv = 2; + while (Fout * factors->outdiv < 90000000 || + Fout * factors->outdiv > 100000000) { + factors->outdiv *= 2; + if (factors->outdiv > 32) { + dev_err(wm8400->wm8400->dev, + "Unsupported FLL output frequency %uHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * factors->outdiv; + factors->outdiv = factors->outdiv >> 2; + + if (Fref < 48000) + factors->freq_ref = 1; + else + factors->freq_ref = 0; + + if (Fref < 1000000) + factors->fratio = 9; + else + factors->fratio = 0; + + /* Ensure we have a fractional part */ + do { + if (Fref < 1000000) + factors->fratio--; + else + factors->fratio++; + + if (factors->fratio < 1 || factors->fratio > 8) { + dev_err(wm8400->wm8400->dev, + "Unable to calculate FRATIO\n"); + return -EINVAL; + } + + factors->n = target / (Fref * factors->fratio); + Nmod = target % (Fref * factors->fratio); + } while (Nmod == 0); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, (Fref * factors->fratio)); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + factors->k = K / 10; + + dev_dbg(wm8400->wm8400->dev, + "FLL: Fref=%u Fout=%u N=%x K=%x, FRATIO=%x OUTDIV=%x\n", + Fref, Fout, + factors->n, factors->k, factors->fratio, factors->outdiv); + + return 0; +} + +static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8400_priv *wm8400 = snd_soc_component_get_drvdata(component); + struct fll_factors factors; + int ret; + u16 reg; + + if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out) + return 0; + + if (freq_out) { + ret = fll_factors(wm8400, &factors, freq_in, freq_out); + if (ret != 0) + return ret; + } else { + /* Bodge GCC 4.4.0 uninitialised variable warning - it + * doesn't seem capable of working out that we exit if + * freq_out is 0 before any of the uses. */ + memset(&factors, 0, sizeof(factors)); + } + + wm8400->fll_out = freq_out; + wm8400->fll_in = freq_in; + + /* We *must* disable the FLL before any changes */ + reg = snd_soc_component_read32(component, WM8400_POWER_MANAGEMENT_2); + reg &= ~WM8400_FLL_ENA; + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_2, reg); + + reg = snd_soc_component_read32(component, WM8400_FLL_CONTROL_1); + reg &= ~WM8400_FLL_OSC_ENA; + snd_soc_component_write(component, WM8400_FLL_CONTROL_1, reg); + + if (!freq_out) + return 0; + + reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK); + reg |= WM8400_FLL_FRAC | factors.fratio; + reg |= factors.freq_ref << WM8400_FLL_REF_FREQ_SHIFT; + snd_soc_component_write(component, WM8400_FLL_CONTROL_1, reg); + + snd_soc_component_write(component, WM8400_FLL_CONTROL_2, factors.k); + snd_soc_component_write(component, WM8400_FLL_CONTROL_3, factors.n); + + reg = snd_soc_component_read32(component, WM8400_FLL_CONTROL_4); + reg &= ~WM8400_FLL_OUTDIV_MASK; + reg |= factors.outdiv; + snd_soc_component_write(component, WM8400_FLL_CONTROL_4, reg); + + return 0; +} + +/* + * Sets ADC and Voice DAC format. + */ +static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 audio1, audio3; + + audio1 = snd_soc_component_read32(component, WM8400_AUDIO_INTERFACE_1); + audio3 = snd_soc_component_read32(component, WM8400_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8400_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8400_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8400_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8400_AIF_FMT_I2S; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8400_AIF_FMT_RIGHTJ; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8400_AIF_FMT_LEFTJ; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8400_AIF_FMT_DSP; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8400_AIF_FMT_DSP | WM8400_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8400_AUDIO_INTERFACE_1, audio1); + snd_soc_component_write(component, WM8400_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8400_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + switch (div_id) { + case WM8400_MCLK_DIV: + reg = snd_soc_component_read32(component, WM8400_CLOCKING_2) & + ~WM8400_MCLK_DIV_MASK; + snd_soc_component_write(component, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_DACCLK_DIV: + reg = snd_soc_component_read32(component, WM8400_CLOCKING_2) & + ~WM8400_DAC_CLKDIV_MASK; + snd_soc_component_write(component, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_ADCCLK_DIV: + reg = snd_soc_component_read32(component, WM8400_CLOCKING_2) & + ~WM8400_ADC_CLKDIV_MASK; + snd_soc_component_write(component, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_BCLK_DIV: + reg = snd_soc_component_read32(component, WM8400_CLOCKING_1) & + ~WM8400_BCLK_DIV_MASK; + snd_soc_component_write(component, WM8400_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8400_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 audio1 = snd_soc_component_read32(component, WM8400_AUDIO_INTERFACE_1); + + audio1 &= ~WM8400_AIF_WL_MASK; + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + audio1 |= WM8400_AIF_WL_20BITS; + break; + case 24: + audio1 |= WM8400_AIF_WL_24BITS; + break; + case 32: + audio1 |= WM8400_AIF_WL_32BITS; + break; + } + + snd_soc_component_write(component, WM8400_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8400_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 val = snd_soc_component_read32(component, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE; + + if (mute) + snd_soc_component_write(component, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + else + snd_soc_component_write(component, WM8400_DAC_CTRL, val); + + return 0; +} + +/* TODO: set bias for best performance at standby */ +static int wm8400_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8400_priv *wm8400 = snd_soc_component_get_drvdata(component); + u16 val; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + val = snd_soc_component_read32(component, WM8400_POWER_MANAGEMENT_1) & + ~WM8400_VMID_MODE_MASK; + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, val | 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(power), + &power[0]); + if (ret != 0) { + dev_err(wm8400->wm8400->dev, + "Failed to enable regulators: %d\n", + ret); + return ret; + } + + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, + WM8400_CODEC_ENA | WM8400_SYSCLK_ENA); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + snd_soc_component_write(component, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL); + + msleep(50); + + /* Enable VREF & VMID at 2x50k */ + val = snd_soc_component_read32(component, WM8400_POWER_MANAGEMENT_1); + val |= 0x2 | WM8400_VREF_ENA; + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, val); + + /* Enable BUFIOEN */ + snd_soc_component_write(component, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL | + WM8400_BUFIOEN); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8400_ANTIPOP2, WM8400_BUFIOEN); + } + + /* VMID=2*300k */ + val = snd_soc_component_read32(component, WM8400_POWER_MANAGEMENT_1) & + ~WM8400_VMID_MODE_MASK; + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, val | 0x4); + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + snd_soc_component_write(component, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_POBCTRL | WM8400_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL | + WM8400_BUFIOEN); + + /* mute DAC */ + val = snd_soc_component_read32(component, WM8400_DAC_CTRL); + snd_soc_component_write(component, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + + /* Enable any disabled outputs */ + val = snd_soc_component_read32(component, WM8400_POWER_MANAGEMENT_1); + val |= WM8400_SPK_ENA | WM8400_OUT3_ENA | + WM8400_OUT4_ENA | WM8400_LOUT_ENA | + WM8400_ROUT_ENA; + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, val); + + /* Disable VMID */ + val &= ~WM8400_VMID_MODE_MASK; + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, val); + + msleep(300); + + /* Enable all output discharge bits */ + snd_soc_component_write(component, WM8400_ANTIPOP1, WM8400_DIS_LLINE | + WM8400_DIS_RLINE | WM8400_DIS_OUT3 | + WM8400_DIS_OUT4 | WM8400_DIS_LOUT | + WM8400_DIS_ROUT); + + /* Disable VREF */ + val &= ~WM8400_VREF_ENA; + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, val); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8400_ANTIPOP2, 0x0); + + ret = regulator_bulk_disable(ARRAY_SIZE(power), + &power[0]); + if (ret != 0) + return ret; + + break; + } + + return 0; +} + +#define WM8400_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8400_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8400_dai_ops = { + .hw_params = wm8400_hw_params, + .digital_mute = wm8400_mute, + .set_fmt = wm8400_set_dai_fmt, + .set_clkdiv = wm8400_set_dai_clkdiv, + .set_sysclk = wm8400_set_dai_sysclk, + .set_pll = wm8400_set_dai_pll, +}; + +/* + * The WM8400 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +static struct snd_soc_dai_driver wm8400_dai = { +/* ADC/DAC on primary */ + .name = "wm8400-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8400_RATES, + .formats = WM8400_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8400_RATES, + .formats = WM8400_FORMATS, + }, + .ops = &wm8400_dai_ops, +}; + +static int wm8400_component_probe(struct snd_soc_component *component) +{ + struct wm8400 *wm8400 = dev_get_platdata(component->dev); + struct wm8400_priv *priv; + int ret; + u16 reg; + + priv = devm_kzalloc(component->dev, sizeof(struct wm8400_priv), + GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + snd_soc_component_init_regmap(component, wm8400->regmap); + snd_soc_component_set_drvdata(component, priv); + priv->wm8400 = wm8400; + + ret = devm_regulator_bulk_get(wm8400->dev, + ARRAY_SIZE(power), &power[0]); + if (ret != 0) { + dev_err(component->dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + wm8400_component_reset(component); + + reg = snd_soc_component_read32(component, WM8400_POWER_MANAGEMENT_1); + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, reg | WM8400_CODEC_ENA); + + /* Latch volume update bits */ + reg = snd_soc_component_read32(component, WM8400_LEFT_LINE_INPUT_1_2_VOLUME); + snd_soc_component_write(component, WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + reg & WM8400_IPVU); + reg = snd_soc_component_read32(component, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME); + snd_soc_component_write(component, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + reg & WM8400_IPVU); + + snd_soc_component_write(component, WM8400_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + snd_soc_component_write(component, WM8400_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + return 0; +} + +static void wm8400_component_remove(struct snd_soc_component *component) +{ + u16 reg; + + reg = snd_soc_component_read32(component, WM8400_POWER_MANAGEMENT_1); + snd_soc_component_write(component, WM8400_POWER_MANAGEMENT_1, + reg & (~WM8400_CODEC_ENA)); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8400 = { + .probe = wm8400_component_probe, + .remove = wm8400_component_remove, + .set_bias_level = wm8400_set_bias_level, + .controls = wm8400_snd_controls, + .num_controls = ARRAY_SIZE(wm8400_snd_controls), + .dapm_widgets = wm8400_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8400_dapm_widgets), + .dapm_routes = wm8400_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8400_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8400_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm8400, + &wm8400_dai, 1); +} + +static struct platform_driver wm8400_codec_driver = { + .driver = { + .name = "wm8400-codec", + }, + .probe = wm8400_probe, +}; + +module_platform_driver(wm8400_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8400 driver"); +MODULE_AUTHOR("Mark Brown"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8400-codec"); diff --git a/sound/soc/codecs/wm8400.h b/sound/soc/codecs/wm8400.h new file mode 100644 index 000000000..521adb193 --- /dev/null +++ b/sound/soc/codecs/wm8400.h @@ -0,0 +1,59 @@ +/* + * wm8400.h -- audio driver for WM8400 + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8400_CODEC_H +#define _WM8400_CODEC_H + +#define WM8400_MCLK_DIV 0 +#define WM8400_DACCLK_DIV 1 +#define WM8400_ADCCLK_DIV 2 +#define WM8400_BCLK_DIV 3 + +#define WM8400_MCLK_DIV_1 0x400 +#define WM8400_MCLK_DIV_2 0x800 + +#define WM8400_DAC_CLKDIV_1 0x00 +#define WM8400_DAC_CLKDIV_1_5 0x04 +#define WM8400_DAC_CLKDIV_2 0x08 +#define WM8400_DAC_CLKDIV_3 0x0c +#define WM8400_DAC_CLKDIV_4 0x10 +#define WM8400_DAC_CLKDIV_5_5 0x14 +#define WM8400_DAC_CLKDIV_6 0x18 + +#define WM8400_ADC_CLKDIV_1 0x00 +#define WM8400_ADC_CLKDIV_1_5 0x20 +#define WM8400_ADC_CLKDIV_2 0x40 +#define WM8400_ADC_CLKDIV_3 0x60 +#define WM8400_ADC_CLKDIV_4 0x80 +#define WM8400_ADC_CLKDIV_5_5 0xa0 +#define WM8400_ADC_CLKDIV_6 0xc0 + + +#define WM8400_BCLK_DIV_1 (0x0 << 1) +#define WM8400_BCLK_DIV_1_5 (0x1 << 1) +#define WM8400_BCLK_DIV_2 (0x2 << 1) +#define WM8400_BCLK_DIV_3 (0x3 << 1) +#define WM8400_BCLK_DIV_4 (0x4 << 1) +#define WM8400_BCLK_DIV_5_5 (0x5 << 1) +#define WM8400_BCLK_DIV_6 (0x6 << 1) +#define WM8400_BCLK_DIV_8 (0x7 << 1) +#define WM8400_BCLK_DIV_11 (0x8 << 1) +#define WM8400_BCLK_DIV_12 (0x9 << 1) +#define WM8400_BCLK_DIV_16 (0xA << 1) +#define WM8400_BCLK_DIV_22 (0xB << 1) +#define WM8400_BCLK_DIV_24 (0xC << 1) +#define WM8400_BCLK_DIV_32 (0xD << 1) +#define WM8400_BCLK_DIV_44 (0xE << 1) +#define WM8400_BCLK_DIV_48 (0xF << 1) + +#endif diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c new file mode 100644 index 000000000..1a2412d73 --- /dev/null +++ b/sound/soc/codecs/wm8510.c @@ -0,0 +1,724 @@ +/* + * wm8510.c -- WM8510 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8510.h" + +/* + * wm8510 register cache + * We can't read the WM8510 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8510_reg_defaults[] = { + { 1, 0x0000 }, + { 2, 0x0000 }, + { 3, 0x0000 }, + { 4, 0x0050 }, + { 5, 0x0000 }, + { 6, 0x0140 }, + { 7, 0x0000 }, + { 8, 0x0000 }, + { 9, 0x0000 }, + { 10, 0x0000 }, + { 11, 0x00ff }, + { 12, 0x0000 }, + { 13, 0x0000 }, + { 14, 0x0100 }, + { 15, 0x00ff }, + { 16, 0x0000 }, + { 17, 0x0000 }, + { 18, 0x012c }, + { 19, 0x002c }, + { 20, 0x002c }, + { 21, 0x002c }, + { 22, 0x002c }, + { 23, 0x0000 }, + { 24, 0x0032 }, + { 25, 0x0000 }, + { 26, 0x0000 }, + { 27, 0x0000 }, + { 28, 0x0000 }, + { 29, 0x0000 }, + { 30, 0x0000 }, + { 31, 0x0000 }, + { 32, 0x0038 }, + { 33, 0x000b }, + { 34, 0x0032 }, + { 35, 0x0000 }, + { 36, 0x0008 }, + { 37, 0x000c }, + { 38, 0x0093 }, + { 39, 0x00e9 }, + { 40, 0x0000 }, + { 41, 0x0000 }, + { 42, 0x0000 }, + { 43, 0x0000 }, + { 44, 0x0003 }, + { 45, 0x0010 }, + { 46, 0x0000 }, + { 47, 0x0000 }, + { 48, 0x0000 }, + { 49, 0x0002 }, + { 50, 0x0001 }, + { 51, 0x0000 }, + { 52, 0x0000 }, + { 53, 0x0000 }, + { 54, 0x0039 }, + { 55, 0x0000 }, + { 56, 0x0001 }, +}; + +static bool wm8510_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8510_RESET: + return true; + default: + return false; + } +} + +#define WM8510_POWER1_BIASEN 0x08 +#define WM8510_POWER1_BUFIOEN 0x10 + +#define wm8510_reset(c) snd_soc_component_write(c, WM8510_RESET, 0) + +/* codec private data */ +struct wm8510_priv { + struct regmap *regmap; +}; + +static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" }; +static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8510_alc[] = { "ALC", "Limiter" }; + +static const struct soc_enum wm8510_enum[] = { + SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */ + SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */ + SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp), + SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc), +}; + +static const struct snd_kcontrol_new wm8510_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8510_enum[1]), +SOC_ENUM("ADC Companding", wm8510_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8510_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0), + +SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), + +SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8510_enum[3]), +SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1), +SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0), +SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1), +}; + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8510_boost_controls[] = { +SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 1), +SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0), +SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0), +}; + +static const struct snd_kcontrol_new wm8510_micpga_controls[] = { +SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0), +SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0), +SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0, + &wm8510_speaker_mixer_controls[0], + ARRAY_SIZE(wm8510_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0, + &wm8510_mono_mixer_controls[0], + ARRAY_SIZE(wm8510_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0, + &wm8510_micpga_controls[0], + ARRAY_SIZE(wm8510_micpga_controls)), +SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, + &wm8510_boost_controls[0], + ARRAY_SIZE(wm8510_boost_controls)), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const struct snd_soc_dapm_route wm8510_dapm_routes[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Microphone PGA */ + {"Mic PGA", "MICN Switch", "MICN"}, + {"Mic PGA", "MICP Switch", "MICP"}, + { "Mic PGA", "AUX Switch", "Aux Input" }, + + /* Boost Mixer */ + {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, + {"Boost Mixer", "Mic Volume", "MICP"}, + {"Boost Mixer", "Aux Volume", "Aux Input"}, + + {"ADC", NULL, "Boost Mixer"}, +}; + +struct pll_ { + unsigned int pre_div:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8510 N value %u outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = snd_soc_component_read32(component, WM8510_CLOCK); + snd_soc_component_write(component, WM8510_CLOCK, reg & 0x0ff); + + /* Turn off PLL */ + reg = snd_soc_component_read32(component, WM8510_POWER1); + snd_soc_component_write(component, WM8510_POWER1, reg & 0x1df); + return 0; + } + + pll_factors(freq_out*4, freq_in); + + snd_soc_component_write(component, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); + snd_soc_component_write(component, WM8510_PLLK1, pll_div.k >> 18); + snd_soc_component_write(component, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff); + snd_soc_component_write(component, WM8510_PLLK3, pll_div.k & 0x1ff); + reg = snd_soc_component_read32(component, WM8510_POWER1); + snd_soc_component_write(component, WM8510_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = snd_soc_component_read32(component, WM8510_CLOCK); + snd_soc_component_write(component, WM8510_CLOCK, reg | 0x100); + + return 0; +} + +/* + * Configure WM8510 clock dividers. + */ +static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + switch (div_id) { + case WM8510_OPCLKDIV: + reg = snd_soc_component_read32(component, WM8510_GPIO) & 0x1cf; + snd_soc_component_write(component, WM8510_GPIO, reg | div); + break; + case WM8510_MCLKDIV: + reg = snd_soc_component_read32(component, WM8510_CLOCK) & 0x11f; + snd_soc_component_write(component, WM8510_CLOCK, reg | div); + break; + case WM8510_ADCCLK: + reg = snd_soc_component_read32(component, WM8510_ADC) & 0x1f7; + snd_soc_component_write(component, WM8510_ADC, reg | div); + break; + case WM8510_DACCLK: + reg = snd_soc_component_read32(component, WM8510_DAC) & 0x1f7; + snd_soc_component_write(component, WM8510_DAC, reg | div); + break; + case WM8510_BCLKDIV: + reg = snd_soc_component_read32(component, WM8510_CLOCK) & 0x1e3; + snd_soc_component_write(component, WM8510_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + u16 clk = snd_soc_component_read32(component, WM8510_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8510_IFACE, iface); + snd_soc_component_write(component, WM8510_CLOCK, clk); + return 0; +} + +static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 iface = snd_soc_component_read32(component, WM8510_IFACE) & 0x19f; + u16 adn = snd_soc_component_read32(component, WM8510_ADD) & 0x1f1; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0020; + break; + case 24: + iface |= 0x0040; + break; + case 32: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case 8000: + adn |= 0x5 << 1; + break; + case 11025: + adn |= 0x4 << 1; + break; + case 16000: + adn |= 0x3 << 1; + break; + case 22050: + adn |= 0x2 << 1; + break; + case 32000: + adn |= 0x1 << 1; + break; + case 44100: + case 48000: + break; + } + + snd_soc_component_write(component, WM8510_IFACE, iface); + snd_soc_component_write(component, WM8510_ADD, adn); + return 0; +} + +static int wm8510_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8510_DAC) & 0xffbf; + + if (mute) + snd_soc_component_write(component, WM8510_DAC, mute_reg | 0x40); + else + snd_soc_component_write(component, WM8510_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8510_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8510_priv *wm8510 = snd_soc_component_get_drvdata(component); + u16 power1 = snd_soc_component_read32(component, WM8510_POWER1) & ~0x3; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + power1 |= 0x1; /* VMID 50k */ + snd_soc_component_write(component, WM8510_POWER1, power1); + break; + + case SND_SOC_BIAS_STANDBY: + power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8510->regmap); + + /* Initial cap charge at VMID 5k */ + snd_soc_component_write(component, WM8510_POWER1, power1 | 0x3); + mdelay(100); + } + + power1 |= 0x2; /* VMID 500k */ + snd_soc_component_write(component, WM8510_POWER1, power1); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, WM8510_POWER1, 0); + snd_soc_component_write(component, WM8510_POWER2, 0); + snd_soc_component_write(component, WM8510_POWER3, 0); + break; + } + + return 0; +} + +#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8510_dai_ops = { + .hw_params = wm8510_pcm_hw_params, + .digital_mute = wm8510_mute, + .set_fmt = wm8510_set_dai_fmt, + .set_clkdiv = wm8510_set_dai_clkdiv, + .set_pll = wm8510_set_dai_pll, +}; + +static struct snd_soc_dai_driver wm8510_dai = { + .name = "wm8510-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .ops = &wm8510_dai_ops, + .symmetric_rates = 1, +}; + +static int wm8510_probe(struct snd_soc_component *component) +{ + wm8510_reset(component); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8510 = { + .probe = wm8510_probe, + .set_bias_level = wm8510_set_bias_level, + .controls = wm8510_snd_controls, + .num_controls = ARRAY_SIZE(wm8510_snd_controls), + .dapm_widgets = wm8510_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8510_dapm_widgets), + .dapm_routes = wm8510_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8510_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8510_of_match[] = { + { .compatible = "wlf,wm8510" }, + { }, +}; +MODULE_DEVICE_TABLE(of, wm8510_of_match); + +static const struct regmap_config wm8510_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8510_MONOMIX, + + .reg_defaults = wm8510_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8510_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8510_volatile, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8510_spi_probe(struct spi_device *spi) +{ + struct wm8510_priv *wm8510; + int ret; + + wm8510 = devm_kzalloc(&spi->dev, sizeof(struct wm8510_priv), + GFP_KERNEL); + if (wm8510 == NULL) + return -ENOMEM; + + wm8510->regmap = devm_regmap_init_spi(spi, &wm8510_regmap); + if (IS_ERR(wm8510->regmap)) + return PTR_ERR(wm8510->regmap); + + spi_set_drvdata(spi, wm8510); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8510, &wm8510_dai, 1); + + return ret; +} + +static struct spi_driver wm8510_spi_driver = { + .driver = { + .name = "wm8510", + .of_match_table = wm8510_of_match, + }, + .probe = wm8510_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8510_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8510_priv *wm8510; + int ret; + + wm8510 = devm_kzalloc(&i2c->dev, sizeof(struct wm8510_priv), + GFP_KERNEL); + if (wm8510 == NULL) + return -ENOMEM; + + wm8510->regmap = devm_regmap_init_i2c(i2c, &wm8510_regmap); + if (IS_ERR(wm8510->regmap)) + return PTR_ERR(wm8510->regmap); + + i2c_set_clientdata(i2c, wm8510); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8510, &wm8510_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8510_i2c_id[] = { + { "wm8510", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id); + +static struct i2c_driver wm8510_i2c_driver = { + .driver = { + .name = "wm8510", + .of_match_table = wm8510_of_match, + }, + .probe = wm8510_i2c_probe, + .id_table = wm8510_i2c_id, +}; +#endif + +static int __init wm8510_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8510_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8510 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8510_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8510 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8510_modinit); + +static void __exit wm8510_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8510_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8510_spi_driver); +#endif +} +module_exit(wm8510_exit); + +MODULE_DESCRIPTION("ASoC WM8510 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h new file mode 100644 index 000000000..b3e26ed9f --- /dev/null +++ b/sound/soc/codecs/wm8510.h @@ -0,0 +1,102 @@ +/* + * wm8510.h -- WM8510 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8510_H +#define _WM8510_H + +/* WM8510 register space */ + +#define WM8510_RESET 0x0 +#define WM8510_POWER1 0x1 +#define WM8510_POWER2 0x2 +#define WM8510_POWER3 0x3 +#define WM8510_IFACE 0x4 +#define WM8510_COMP 0x5 +#define WM8510_CLOCK 0x6 +#define WM8510_ADD 0x7 +#define WM8510_GPIO 0x8 +#define WM8510_DAC 0xa +#define WM8510_DACVOL 0xb +#define WM8510_ADC 0xe +#define WM8510_ADCVOL 0xf +#define WM8510_EQ1 0x12 +#define WM8510_EQ2 0x13 +#define WM8510_EQ3 0x14 +#define WM8510_EQ4 0x15 +#define WM8510_EQ5 0x16 +#define WM8510_DACLIM1 0x18 +#define WM8510_DACLIM2 0x19 +#define WM8510_NOTCH1 0x1b +#define WM8510_NOTCH2 0x1c +#define WM8510_NOTCH3 0x1d +#define WM8510_NOTCH4 0x1e +#define WM8510_ALC1 0x20 +#define WM8510_ALC2 0x21 +#define WM8510_ALC3 0x22 +#define WM8510_NGATE 0x23 +#define WM8510_PLLN 0x24 +#define WM8510_PLLK1 0x25 +#define WM8510_PLLK2 0x26 +#define WM8510_PLLK3 0x27 +#define WM8510_ATTEN 0x28 +#define WM8510_INPUT 0x2c +#define WM8510_INPPGA 0x2d +#define WM8510_ADCBOOST 0x2f +#define WM8510_OUTPUT 0x31 +#define WM8510_SPKMIX 0x32 +#define WM8510_SPKVOL 0x36 +#define WM8510_MONOMIX 0x38 + +#define WM8510_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8510_OPCLKDIV 0 +#define WM8510_MCLKDIV 1 +#define WM8510_ADCCLK 2 +#define WM8510_DACCLK 3 +#define WM8510_BCLKDIV 4 + +/* DAC clock dividers */ +#define WM8510_DACCLK_F2 (1 << 3) +#define WM8510_DACCLK_F4 (0 << 3) + +/* ADC clock dividers */ +#define WM8510_ADCCLK_F2 (1 << 3) +#define WM8510_ADCCLK_F4 (0 << 3) + +/* PLL Out dividers */ +#define WM8510_OPCLKDIV_1 (0 << 4) +#define WM8510_OPCLKDIV_2 (1 << 4) +#define WM8510_OPCLKDIV_3 (2 << 4) +#define WM8510_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8510_BCLKDIV_1 (0 << 2) +#define WM8510_BCLKDIV_2 (1 << 2) +#define WM8510_BCLKDIV_4 (2 << 2) +#define WM8510_BCLKDIV_8 (3 << 2) +#define WM8510_BCLKDIV_16 (4 << 2) +#define WM8510_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8510_MCLKDIV_1 (0 << 5) +#define WM8510_MCLKDIV_1_5 (1 << 5) +#define WM8510_MCLKDIV_2 (2 << 5) +#define WM8510_MCLKDIV_3 (3 << 5) +#define WM8510_MCLKDIV_4 (4 << 5) +#define WM8510_MCLKDIV_6 (5 << 5) +#define WM8510_MCLKDIV_8 (6 << 5) +#define WM8510_MCLKDIV_12 (7 << 5) + +struct wm8510_setup_data { + int spi; + int i2c_bus; + unsigned short i2c_address; +}; + +#endif diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c new file mode 100644 index 000000000..f4a9e25fb --- /dev/null +++ b/sound/soc/codecs/wm8523.c @@ -0,0 +1,544 @@ +/* + * wm8523.c -- WM8523 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8523.h" + +#define WM8523_NUM_SUPPLIES 2 +static const char *wm8523_supply_names[WM8523_NUM_SUPPLIES] = { + "AVDD", + "LINEVDD", +}; + +#define WM8523_NUM_RATES 7 + +/* codec private data */ +struct wm8523_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8523_NUM_SUPPLIES]; + unsigned int sysclk; + unsigned int rate_constraint_list[WM8523_NUM_RATES]; + struct snd_pcm_hw_constraint_list rate_constraint; +}; + +static const struct reg_default wm8523_reg_defaults[] = { + { 2, 0x0000 }, /* R2 - PSCTRL1 */ + { 3, 0x1812 }, /* R3 - AIF_CTRL1 */ + { 4, 0x0000 }, /* R4 - AIF_CTRL2 */ + { 5, 0x0001 }, /* R5 - DAC_CTRL3 */ + { 6, 0x0190 }, /* R6 - DAC_GAINL */ + { 7, 0x0190 }, /* R7 - DAC_GAINR */ + { 8, 0x0000 }, /* R8 - ZERO_DETECT */ +}; + +static bool wm8523_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8523_DEVICE_ID: + case WM8523_REVISION: + return true; + default: + return false; + } +} + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -10000, 25, 0); + +static const char *wm8523_zd_count_text[] = { + "1024", + "2048", +}; + +static SOC_ENUM_SINGLE_DECL(wm8523_zc_count, WM8523_ZERO_DETECT, 0, + wm8523_zd_count_text); + +static const struct snd_kcontrol_new wm8523_controls[] = { +SOC_DOUBLE_R_TLV("Playback Volume", WM8523_DAC_GAINL, WM8523_DAC_GAINR, + 0, 448, 0, dac_tlv), +SOC_SINGLE("ZC Switch", WM8523_DAC_CTRL3, 4, 1, 0), +SOC_SINGLE("Playback Deemphasis Switch", WM8523_AIF_CTRL1, 8, 1, 0), +SOC_DOUBLE("Playback Switch", WM8523_DAC_CTRL3, 2, 3, 1, 1), +SOC_SINGLE("Volume Ramp Up Switch", WM8523_DAC_CTRL3, 1, 1, 0), +SOC_SINGLE("Volume Ramp Down Switch", WM8523_DAC_CTRL3, 0, 1, 0), +SOC_ENUM("Zero Detect Count", wm8523_zc_count), +}; + +static const struct snd_soc_dapm_widget wm8523_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("LINEVOUTL"), +SND_SOC_DAPM_OUTPUT("LINEVOUTR"), +}; + +static const struct snd_soc_dapm_route wm8523_dapm_routes[] = { + { "LINEVOUTL", NULL, "DAC" }, + { "LINEVOUTR", NULL, "DAC" }, +}; + +static const struct { + int value; + int ratio; +} lrclk_ratios[WM8523_NUM_RATES] = { + { 1, 128 }, + { 2, 192 }, + { 3, 256 }, + { 4, 384 }, + { 5, 512 }, + { 6, 768 }, + { 7, 1152 }, +}; + +static const struct { + int value; + int ratio; +} bclk_ratios[] = { + { 2, 32 }, + { 3, 64 }, + { 4, 128 }, +}; + +static int wm8523_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8523_priv *wm8523 = snd_soc_component_get_drvdata(component); + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8523->sysclk) { + dev_err(component->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8523->rate_constraint); + + return 0; +} + +static int wm8523_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8523_priv *wm8523 = snd_soc_component_get_drvdata(component); + int i; + u16 aifctrl1 = snd_soc_component_read32(component, WM8523_AIF_CTRL1); + u16 aifctrl2 = snd_soc_component_read32(component, WM8523_AIF_CTRL2); + + /* Find a supported LRCLK ratio */ + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + if (wm8523->sysclk / params_rate(params) == + lrclk_ratios[i].ratio) + break; + } + + /* Should never happen, should be handled by constraints */ + if (i == ARRAY_SIZE(lrclk_ratios)) { + dev_err(component->dev, "MCLK/fs ratio %d unsupported\n", + wm8523->sysclk / params_rate(params)); + return -EINVAL; + } + + aifctrl2 &= ~WM8523_SR_MASK; + aifctrl2 |= lrclk_ratios[i].value; + + if (aifctrl1 & WM8523_AIF_MSTR) { + /* Find a fs->bclk ratio */ + for (i = 0; i < ARRAY_SIZE(bclk_ratios); i++) + if (params_width(params) * 2 <= bclk_ratios[i].ratio) + break; + + if (i == ARRAY_SIZE(bclk_ratios)) { + dev_err(component->dev, + "No matching BCLK/fs ratio for word length %d\n", + params_width(params)); + return -EINVAL; + } + + aifctrl2 &= ~WM8523_BCLKDIV_MASK; + aifctrl2 |= bclk_ratios[i].value << WM8523_BCLKDIV_SHIFT; + } + + aifctrl1 &= ~WM8523_WL_MASK; + switch (params_width(params)) { + case 16: + break; + case 20: + aifctrl1 |= 0x8; + break; + case 24: + aifctrl1 |= 0x10; + break; + case 32: + aifctrl1 |= 0x18; + break; + } + + snd_soc_component_write(component, WM8523_AIF_CTRL1, aifctrl1); + snd_soc_component_write(component, WM8523_AIF_CTRL2, aifctrl2); + + return 0; +} + +static int wm8523_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8523_priv *wm8523 = snd_soc_component_get_drvdata(component); + unsigned int val; + int i; + + wm8523->sysclk = freq; + + wm8523->rate_constraint.count = 0; + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + val = freq / lrclk_ratios[i].ratio; + /* Check that it's a standard rate since core can't + * cope with others and having the odd rates confuses + * constraint matching. + */ + switch (val) { + case 8000: + case 11025: + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + case 64000: + case 88200: + case 96000: + case 176400: + case 192000: + dev_dbg(component->dev, "Supported sample rate: %dHz\n", + val); + wm8523->rate_constraint_list[i] = val; + wm8523->rate_constraint.count++; + break; + default: + dev_dbg(component->dev, "Skipping sample rate: %dHz\n", + val); + } + } + + /* Need at least one supported rate... */ + if (wm8523->rate_constraint.count == 0) + return -EINVAL; + + return 0; +} + + +static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 aifctrl1 = snd_soc_component_read32(component, WM8523_AIF_CTRL1); + + aifctrl1 &= ~(WM8523_BCLK_INV_MASK | WM8523_LRCLK_INV_MASK | + WM8523_FMT_MASK | WM8523_AIF_MSTR_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aifctrl1 |= WM8523_AIF_MSTR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aifctrl1 |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aifctrl1 |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + aifctrl1 |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + aifctrl1 |= 0x0023; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aifctrl1 |= WM8523_BCLK_INV | WM8523_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aifctrl1 |= WM8523_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aifctrl1 |= WM8523_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8523_AIF_CTRL1, aifctrl1); + + return 0; +} + +static int wm8523_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8523_priv *wm8523 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* Full power on */ + snd_soc_component_update_bits(component, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 3); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + /* Sync back default/cached values */ + regcache_sync(wm8523->regmap); + + /* Initial power up */ + snd_soc_component_update_bits(component, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 1); + + msleep(100); + } + + /* Power up to mute */ + snd_soc_component_update_bits(component, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 2); + + break; + + case SND_SOC_BIAS_OFF: + /* The chip runs through the power down sequence for us. */ + snd_soc_component_update_bits(component, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 0); + msleep(100); + + regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + break; + } + return 0; +} + +#define WM8523_RATES SNDRV_PCM_RATE_8000_192000 + +#define WM8523_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8523_dai_ops = { + .startup = wm8523_startup, + .hw_params = wm8523_hw_params, + .set_sysclk = wm8523_set_dai_sysclk, + .set_fmt = wm8523_set_dai_fmt, +}; + +static struct snd_soc_dai_driver wm8523_dai = { + .name = "wm8523-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, /* Mono modes not yet supported */ + .channels_max = 2, + .rates = WM8523_RATES, + .formats = WM8523_FORMATS, + }, + .ops = &wm8523_dai_ops, +}; + +static int wm8523_probe(struct snd_soc_component *component) +{ + struct wm8523_priv *wm8523 = snd_soc_component_get_drvdata(component); + + wm8523->rate_constraint.list = &wm8523->rate_constraint_list[0]; + wm8523->rate_constraint.count = + ARRAY_SIZE(wm8523->rate_constraint_list); + + /* Change some default settings - latch VU and enable ZC */ + snd_soc_component_update_bits(component, WM8523_DAC_GAINR, + WM8523_DACR_VU, WM8523_DACR_VU); + snd_soc_component_update_bits(component, WM8523_DAC_CTRL3, WM8523_ZC, WM8523_ZC); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8523 = { + .probe = wm8523_probe, + .set_bias_level = wm8523_set_bias_level, + .controls = wm8523_controls, + .num_controls = ARRAY_SIZE(wm8523_controls), + .dapm_widgets = wm8523_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8523_dapm_widgets), + .dapm_routes = wm8523_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8523_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8523_of_match[] = { + { .compatible = "wlf,wm8523" }, + { }, +}; +MODULE_DEVICE_TABLE(of, wm8523_of_match); + +static const struct regmap_config wm8523_regmap = { + .reg_bits = 8, + .val_bits = 16, + .max_register = WM8523_ZERO_DETECT, + + .reg_defaults = wm8523_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8523_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8523_volatile_register, +}; + +static int wm8523_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8523_priv *wm8523; + unsigned int val; + int ret, i; + + wm8523 = devm_kzalloc(&i2c->dev, sizeof(struct wm8523_priv), + GFP_KERNEL); + if (wm8523 == NULL) + return -ENOMEM; + + wm8523->regmap = devm_regmap_init_i2c(i2c, &wm8523_regmap); + if (IS_ERR(wm8523->regmap)) { + ret = PTR_ERR(wm8523->regmap); + dev_err(&i2c->dev, "Failed to create regmap: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(wm8523->supplies); i++) + wm8523->supplies[i].supply = wm8523_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_read(wm8523->regmap, WM8523_DEVICE_ID, &val); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read ID register\n"); + goto err_enable; + } + if (val != 0x8523) { + dev_err(&i2c->dev, "Device is not a WM8523, ID is %x\n", ret); + ret = -EINVAL; + goto err_enable; + } + + ret = regmap_read(wm8523->regmap, WM8523_REVISION, &val); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read revision register\n"); + goto err_enable; + } + dev_info(&i2c->dev, "revision %c\n", + (val & WM8523_CHIP_REV_MASK) + 'A'); + + ret = regmap_write(wm8523->regmap, WM8523_DEVICE_ID, 0x8523); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to reset device: %d\n", ret); + goto err_enable; + } + + regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); + + i2c_set_clientdata(i2c, wm8523); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8523, &wm8523_dai, 1); + + return ret; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); + return ret; +} + +static const struct i2c_device_id wm8523_i2c_id[] = { + { "wm8523", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8523_i2c_id); + +static struct i2c_driver wm8523_i2c_driver = { + .driver = { + .name = "wm8523", + .of_match_table = wm8523_of_match, + }, + .probe = wm8523_i2c_probe, + .id_table = wm8523_i2c_id, +}; + +module_i2c_driver(wm8523_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8523 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8523.h b/sound/soc/codecs/wm8523.h new file mode 100644 index 000000000..4d5b1eb8f --- /dev/null +++ b/sound/soc/codecs/wm8523.h @@ -0,0 +1,157 @@ +/* + * wm8523.h -- WM8423 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics, plc + * + * Author: Mark Brown + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8523_H +#define _WM8523_H + +/* + * Register values. + */ +#define WM8523_DEVICE_ID 0x00 +#define WM8523_REVISION 0x01 +#define WM8523_PSCTRL1 0x02 +#define WM8523_AIF_CTRL1 0x03 +#define WM8523_AIF_CTRL2 0x04 +#define WM8523_DAC_CTRL3 0x05 +#define WM8523_DAC_GAINL 0x06 +#define WM8523_DAC_GAINR 0x07 +#define WM8523_ZERO_DETECT 0x08 + +#define WM8523_REGISTER_COUNT 9 +#define WM8523_MAX_REGISTER 0x08 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - DEVICE_ID + */ +#define WM8523_CHIP_ID_MASK 0xFFFF /* CHIP_ID - [15:0] */ +#define WM8523_CHIP_ID_SHIFT 0 /* CHIP_ID - [15:0] */ +#define WM8523_CHIP_ID_WIDTH 16 /* CHIP_ID - [15:0] */ + +/* + * R1 (0x01) - REVISION + */ +#define WM8523_CHIP_REV_MASK 0x0007 /* CHIP_REV - [2:0] */ +#define WM8523_CHIP_REV_SHIFT 0 /* CHIP_REV - [2:0] */ +#define WM8523_CHIP_REV_WIDTH 3 /* CHIP_REV - [2:0] */ + +/* + * R2 (0x02) - PSCTRL1 + */ +#define WM8523_SYS_ENA_MASK 0x0003 /* SYS_ENA - [1:0] */ +#define WM8523_SYS_ENA_SHIFT 0 /* SYS_ENA - [1:0] */ +#define WM8523_SYS_ENA_WIDTH 2 /* SYS_ENA - [1:0] */ + +/* + * R3 (0x03) - AIF_CTRL1 + */ +#define WM8523_TDM_MODE_MASK 0x1800 /* TDM_MODE - [12:11] */ +#define WM8523_TDM_MODE_SHIFT 11 /* TDM_MODE - [12:11] */ +#define WM8523_TDM_MODE_WIDTH 2 /* TDM_MODE - [12:11] */ +#define WM8523_TDM_SLOT_MASK 0x0600 /* TDM_SLOT - [10:9] */ +#define WM8523_TDM_SLOT_SHIFT 9 /* TDM_SLOT - [10:9] */ +#define WM8523_TDM_SLOT_WIDTH 2 /* TDM_SLOT - [10:9] */ +#define WM8523_DEEMPH 0x0100 /* DEEMPH */ +#define WM8523_DEEMPH_MASK 0x0100 /* DEEMPH */ +#define WM8523_DEEMPH_SHIFT 8 /* DEEMPH */ +#define WM8523_DEEMPH_WIDTH 1 /* DEEMPH */ +#define WM8523_AIF_MSTR 0x0080 /* AIF_MSTR */ +#define WM8523_AIF_MSTR_MASK 0x0080 /* AIF_MSTR */ +#define WM8523_AIF_MSTR_SHIFT 7 /* AIF_MSTR */ +#define WM8523_AIF_MSTR_WIDTH 1 /* AIF_MSTR */ +#define WM8523_LRCLK_INV 0x0040 /* LRCLK_INV */ +#define WM8523_LRCLK_INV_MASK 0x0040 /* LRCLK_INV */ +#define WM8523_LRCLK_INV_SHIFT 6 /* LRCLK_INV */ +#define WM8523_LRCLK_INV_WIDTH 1 /* LRCLK_INV */ +#define WM8523_BCLK_INV 0x0020 /* BCLK_INV */ +#define WM8523_BCLK_INV_MASK 0x0020 /* BCLK_INV */ +#define WM8523_BCLK_INV_SHIFT 5 /* BCLK_INV */ +#define WM8523_BCLK_INV_WIDTH 1 /* BCLK_INV */ +#define WM8523_WL_MASK 0x0018 /* WL - [4:3] */ +#define WM8523_WL_SHIFT 3 /* WL - [4:3] */ +#define WM8523_WL_WIDTH 2 /* WL - [4:3] */ +#define WM8523_FMT_MASK 0x0007 /* FMT - [2:0] */ +#define WM8523_FMT_SHIFT 0 /* FMT - [2:0] */ +#define WM8523_FMT_WIDTH 3 /* FMT - [2:0] */ + +/* + * R4 (0x04) - AIF_CTRL2 + */ +#define WM8523_DAC_OP_MUX_MASK 0x00C0 /* DAC_OP_MUX - [7:6] */ +#define WM8523_DAC_OP_MUX_SHIFT 6 /* DAC_OP_MUX - [7:6] */ +#define WM8523_DAC_OP_MUX_WIDTH 2 /* DAC_OP_MUX - [7:6] */ +#define WM8523_BCLKDIV_MASK 0x0038 /* BCLKDIV - [5:3] */ +#define WM8523_BCLKDIV_SHIFT 3 /* BCLKDIV - [5:3] */ +#define WM8523_BCLKDIV_WIDTH 3 /* BCLKDIV - [5:3] */ +#define WM8523_SR_MASK 0x0007 /* SR - [2:0] */ +#define WM8523_SR_SHIFT 0 /* SR - [2:0] */ +#define WM8523_SR_WIDTH 3 /* SR - [2:0] */ + +/* + * R5 (0x05) - DAC_CTRL3 + */ +#define WM8523_ZC 0x0010 /* ZC */ +#define WM8523_ZC_MASK 0x0010 /* ZC */ +#define WM8523_ZC_SHIFT 4 /* ZC */ +#define WM8523_ZC_WIDTH 1 /* ZC */ +#define WM8523_DACR 0x0008 /* DACR */ +#define WM8523_DACR_MASK 0x0008 /* DACR */ +#define WM8523_DACR_SHIFT 3 /* DACR */ +#define WM8523_DACR_WIDTH 1 /* DACR */ +#define WM8523_DACL 0x0004 /* DACL */ +#define WM8523_DACL_MASK 0x0004 /* DACL */ +#define WM8523_DACL_SHIFT 2 /* DACL */ +#define WM8523_DACL_WIDTH 1 /* DACL */ +#define WM8523_VOL_UP_RAMP 0x0002 /* VOL_UP_RAMP */ +#define WM8523_VOL_UP_RAMP_MASK 0x0002 /* VOL_UP_RAMP */ +#define WM8523_VOL_UP_RAMP_SHIFT 1 /* VOL_UP_RAMP */ +#define WM8523_VOL_UP_RAMP_WIDTH 1 /* VOL_UP_RAMP */ +#define WM8523_VOL_DOWN_RAMP 0x0001 /* VOL_DOWN_RAMP */ +#define WM8523_VOL_DOWN_RAMP_MASK 0x0001 /* VOL_DOWN_RAMP */ +#define WM8523_VOL_DOWN_RAMP_SHIFT 0 /* VOL_DOWN_RAMP */ +#define WM8523_VOL_DOWN_RAMP_WIDTH 1 /* VOL_DOWN_RAMP */ + +/* + * R6 (0x06) - DAC_GAINL + */ +#define WM8523_DACL_VU 0x0200 /* DACL_VU */ +#define WM8523_DACL_VU_MASK 0x0200 /* DACL_VU */ +#define WM8523_DACL_VU_SHIFT 9 /* DACL_VU */ +#define WM8523_DACL_VU_WIDTH 1 /* DACL_VU */ +#define WM8523_DACL_VOL_MASK 0x01FF /* DACL_VOL - [8:0] */ +#define WM8523_DACL_VOL_SHIFT 0 /* DACL_VOL - [8:0] */ +#define WM8523_DACL_VOL_WIDTH 9 /* DACL_VOL - [8:0] */ + +/* + * R7 (0x07) - DAC_GAINR + */ +#define WM8523_DACR_VU 0x0200 /* DACR_VU */ +#define WM8523_DACR_VU_MASK 0x0200 /* DACR_VU */ +#define WM8523_DACR_VU_SHIFT 9 /* DACR_VU */ +#define WM8523_DACR_VU_WIDTH 1 /* DACR_VU */ +#define WM8523_DACR_VOL_MASK 0x01FF /* DACR_VOL - [8:0] */ +#define WM8523_DACR_VOL_SHIFT 0 /* DACR_VOL - [8:0] */ +#define WM8523_DACR_VOL_WIDTH 9 /* DACR_VOL - [8:0] */ + +/* + * R8 (0x08) - ZERO_DETECT + */ +#define WM8523_ZD_COUNT_MASK 0x0003 /* ZD_COUNT - [1:0] */ +#define WM8523_ZD_COUNT_SHIFT 0 /* ZD_COUNT - [1:0] */ +#define WM8523_ZD_COUNT_WIDTH 2 /* ZD_COUNT - [1:0] */ + +#endif diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c new file mode 100644 index 000000000..fde444d82 --- /dev/null +++ b/sound/soc/codecs/wm8524.c @@ -0,0 +1,255 @@ +/* + * wm8524.c -- WM8524 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * Copyright 2017 NXP + * + * Based on WM8523 ALSA SoC Audio driver written by Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WM8524_NUM_RATES 7 + +/* codec private data */ +struct wm8524_priv { + struct gpio_desc *mute; + unsigned int sysclk; + unsigned int rate_constraint_list[WM8524_NUM_RATES]; + struct snd_pcm_hw_constraint_list rate_constraint; +}; + + +static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("LINEVOUTL"), +SND_SOC_DAPM_OUTPUT("LINEVOUTR"), +}; + +static const struct snd_soc_dapm_route wm8524_dapm_routes[] = { + { "LINEVOUTL", NULL, "DAC" }, + { "LINEVOUTR", NULL, "DAC" }, +}; + +static const struct { + int value; + int ratio; +} lrclk_ratios[WM8524_NUM_RATES] = { + { 1, 128 }, + { 2, 192 }, + { 3, 256 }, + { 4, 384 }, + { 5, 512 }, + { 6, 768 }, + { 7, 1152 }, +}; + +static int wm8524_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8524->sysclk) { + dev_err(component->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8524->rate_constraint); + + gpiod_set_value_cansleep(wm8524->mute, 1); + + return 0; +} + +static void wm8524_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); + + gpiod_set_value_cansleep(wm8524->mute, 0); +} + +static int wm8524_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); + unsigned int val; + int i, j = 0; + + wm8524->sysclk = freq; + + wm8524->rate_constraint.count = 0; + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + val = freq / lrclk_ratios[i].ratio; + /* Check that it's a standard rate since core can't + * cope with others and having the odd rates confuses + * constraint matching. + */ + switch (val) { + case 8000: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + dev_dbg(component->dev, "Supported sample rate: %dHz\n", + val); + wm8524->rate_constraint_list[j++] = val; + wm8524->rate_constraint.count++; + break; + default: + dev_dbg(component->dev, "Skipping sample rate: %dHz\n", + val); + } + } + + /* Need at least one supported rate... */ + if (wm8524->rate_constraint.count == 0) + return -EINVAL; + + return 0; +} + +static int wm8524_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK | + SND_SOC_DAIFMT_MASTER_MASK); + + if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS)) { + dev_err(codec_dai->dev, "Invalid DAI format\n"); + return -EINVAL; + } + + return 0; +} + +static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(dai->component); + + if (wm8524->mute) + gpiod_set_value_cansleep(wm8524->mute, mute); + + return 0; +} + +#define WM8524_RATES SNDRV_PCM_RATE_8000_192000 + +#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8524_dai_ops = { + .startup = wm8524_startup, + .shutdown = wm8524_shutdown, + .set_sysclk = wm8524_set_dai_sysclk, + .set_fmt = wm8524_set_fmt, + .mute_stream = wm8524_mute_stream, +}; + +static struct snd_soc_dai_driver wm8524_dai = { + .name = "wm8524-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8524_RATES, + .formats = WM8524_FORMATS, + }, + .ops = &wm8524_dai_ops, +}; + +static int wm8524_probe(struct snd_soc_component *component) +{ + struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component); + + wm8524->rate_constraint.list = &wm8524->rate_constraint_list[0]; + wm8524->rate_constraint.count = + ARRAY_SIZE(wm8524->rate_constraint_list); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8524 = { + .probe = wm8524_probe, + .dapm_widgets = wm8524_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8524_dapm_widgets), + .dapm_routes = wm8524_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8524_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8524_of_match[] = { + { .compatible = "wlf,wm8524" }, + { /* sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, wm8524_of_match); + +static int wm8524_codec_probe(struct platform_device *pdev) +{ + struct wm8524_priv *wm8524; + int ret; + + wm8524 = devm_kzalloc(&pdev->dev, sizeof(struct wm8524_priv), + GFP_KERNEL); + if (wm8524 == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, wm8524); + + wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW); + if (IS_ERR(wm8524->mute)) { + ret = PTR_ERR(wm8524->mute); + dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm8524, &wm8524_dai, 1); + if (ret < 0) + dev_err(&pdev->dev, "Failed to register component: %d\n", ret); + + return ret; +} + +static struct platform_driver wm8524_codec_driver = { + .probe = wm8524_codec_probe, + .driver = { + .name = "wm8524-codec", + .of_match_table = wm8524_of_match, + }, +}; +module_platform_driver(wm8524_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8524 driver"); +MODULE_AUTHOR("Mihai Serban "); +MODULE_ALIAS("platform:wm8524-codec"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c new file mode 100644 index 000000000..fa4ad1b09 --- /dev/null +++ b/sound/soc/codecs/wm8580.c @@ -0,0 +1,1066 @@ +/* + * wm8580.c -- WM8580 and WM8581 ALSA Soc Audio driver + * + * Copyright 2008-12 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * The WM8580 is a multichannel codec with S/PDIF support, featuring six + * DAC channels and two ADC channels. + * + * The WM8581 is a multichannel codec with S/PDIF support, featuring eight + * DAC channels and two ADC channels. + * + * Currently only the primary audio interface is supported - S/PDIF and + * the secondary audio interfaces are not. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "wm8580.h" + +/* WM8580 register space */ +#define WM8580_PLLA1 0x00 +#define WM8580_PLLA2 0x01 +#define WM8580_PLLA3 0x02 +#define WM8580_PLLA4 0x03 +#define WM8580_PLLB1 0x04 +#define WM8580_PLLB2 0x05 +#define WM8580_PLLB3 0x06 +#define WM8580_PLLB4 0x07 +#define WM8580_CLKSEL 0x08 +#define WM8580_PAIF1 0x09 +#define WM8580_PAIF2 0x0A +#define WM8580_SAIF1 0x0B +#define WM8580_PAIF3 0x0C +#define WM8580_PAIF4 0x0D +#define WM8580_SAIF2 0x0E +#define WM8580_DAC_CONTROL1 0x0F +#define WM8580_DAC_CONTROL2 0x10 +#define WM8580_DAC_CONTROL3 0x11 +#define WM8580_DAC_CONTROL4 0x12 +#define WM8580_DAC_CONTROL5 0x13 +#define WM8580_DIGITAL_ATTENUATION_DACL1 0x14 +#define WM8580_DIGITAL_ATTENUATION_DACR1 0x15 +#define WM8580_DIGITAL_ATTENUATION_DACL2 0x16 +#define WM8580_DIGITAL_ATTENUATION_DACR2 0x17 +#define WM8580_DIGITAL_ATTENUATION_DACL3 0x18 +#define WM8580_DIGITAL_ATTENUATION_DACR3 0x19 +#define WM8581_DIGITAL_ATTENUATION_DACL4 0x1A +#define WM8581_DIGITAL_ATTENUATION_DACR4 0x1B +#define WM8580_MASTER_DIGITAL_ATTENUATION 0x1C +#define WM8580_ADC_CONTROL1 0x1D +#define WM8580_SPDTXCHAN0 0x1E +#define WM8580_SPDTXCHAN1 0x1F +#define WM8580_SPDTXCHAN2 0x20 +#define WM8580_SPDTXCHAN3 0x21 +#define WM8580_SPDTXCHAN4 0x22 +#define WM8580_SPDTXCHAN5 0x23 +#define WM8580_SPDMODE 0x24 +#define WM8580_INTMASK 0x25 +#define WM8580_GPO1 0x26 +#define WM8580_GPO2 0x27 +#define WM8580_GPO3 0x28 +#define WM8580_GPO4 0x29 +#define WM8580_GPO5 0x2A +#define WM8580_INTSTAT 0x2B +#define WM8580_SPDRXCHAN1 0x2C +#define WM8580_SPDRXCHAN2 0x2D +#define WM8580_SPDRXCHAN3 0x2E +#define WM8580_SPDRXCHAN4 0x2F +#define WM8580_SPDRXCHAN5 0x30 +#define WM8580_SPDSTAT 0x31 +#define WM8580_PWRDN1 0x32 +#define WM8580_PWRDN2 0x33 +#define WM8580_READBACK 0x34 +#define WM8580_RESET 0x35 + +#define WM8580_MAX_REGISTER 0x35 + +#define WM8580_DACOSR 0x40 + +/* PLLB4 (register 7h) */ +#define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60 +#define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20 +#define WM8580_PLLB4_MCLKOUTSRC_PLLB 0x40 +#define WM8580_PLLB4_MCLKOUTSRC_OSC 0x60 + +#define WM8580_PLLB4_CLKOUTSRC_MASK 0x180 +#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080 +#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100 +#define WM8580_PLLB4_CLKOUTSRC_OSCCLK 0x180 + +/* CLKSEL (register 8h) */ +#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02 + +/* AIF control 1 (registers 9h-bh) */ +#define WM8580_AIF_RATE_MASK 0x7 +#define WM8580_AIF_BCLKSEL_MASK 0x18 + +#define WM8580_AIF_MS 0x20 + +#define WM8580_AIF_CLKSRC_MASK 0xc0 +#define WM8580_AIF_CLKSRC_PLLA 0x40 +#define WM8580_AIF_CLKSRC_PLLB 0x40 +#define WM8580_AIF_CLKSRC_MCLK 0xc0 + +/* AIF control 2 (registers ch-eh) */ +#define WM8580_AIF_FMT_MASK 0x03 +#define WM8580_AIF_FMT_RIGHTJ 0x00 +#define WM8580_AIF_FMT_LEFTJ 0x01 +#define WM8580_AIF_FMT_I2S 0x02 +#define WM8580_AIF_FMT_DSP 0x03 + +#define WM8580_AIF_LENGTH_MASK 0x0c +#define WM8580_AIF_LENGTH_16 0x00 +#define WM8580_AIF_LENGTH_20 0x04 +#define WM8580_AIF_LENGTH_24 0x08 +#define WM8580_AIF_LENGTH_32 0x0c + +#define WM8580_AIF_LRP 0x10 +#define WM8580_AIF_BCP 0x20 + +/* Powerdown Register 1 (register 32h) */ +#define WM8580_PWRDN1_PWDN 0x001 +#define WM8580_PWRDN1_ALLDACPD 0x040 + +/* Powerdown Register 2 (register 33h) */ +#define WM8580_PWRDN2_OSSCPD 0x001 +#define WM8580_PWRDN2_PLLAPD 0x002 +#define WM8580_PWRDN2_PLLBPD 0x004 +#define WM8580_PWRDN2_SPDIFPD 0x008 +#define WM8580_PWRDN2_SPDIFTXD 0x010 +#define WM8580_PWRDN2_SPDIFRXD 0x020 + +#define WM8580_DAC_CONTROL5_MUTEALL 0x10 + +/* + * wm8580 register cache + * We can't read the WM8580 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8580_reg_defaults[] = { + { 0, 0x0121 }, + { 1, 0x017e }, + { 2, 0x007d }, + { 3, 0x0014 }, + { 4, 0x0121 }, + { 5, 0x017e }, + { 6, 0x007d }, + { 7, 0x0194 }, + { 8, 0x0010 }, + { 9, 0x0002 }, + { 10, 0x0002 }, + { 11, 0x00c2 }, + { 12, 0x0182 }, + { 13, 0x0082 }, + { 14, 0x000a }, + { 15, 0x0024 }, + { 16, 0x0009 }, + { 17, 0x0000 }, + { 18, 0x00ff }, + { 19, 0x0000 }, + { 20, 0x00ff }, + { 21, 0x00ff }, + { 22, 0x00ff }, + { 23, 0x00ff }, + { 24, 0x00ff }, + { 25, 0x00ff }, + { 26, 0x00ff }, + { 27, 0x00ff }, + { 28, 0x01f0 }, + { 29, 0x0040 }, + { 30, 0x0000 }, + { 31, 0x0000 }, + { 32, 0x0000 }, + { 33, 0x0000 }, + { 34, 0x0031 }, + { 35, 0x000b }, + { 36, 0x0039 }, + { 37, 0x0000 }, + { 38, 0x0010 }, + { 39, 0x0032 }, + { 40, 0x0054 }, + { 41, 0x0076 }, + { 42, 0x0098 }, + { 43, 0x0000 }, + { 44, 0x0000 }, + { 45, 0x0000 }, + { 46, 0x0000 }, + { 47, 0x0000 }, + { 48, 0x0000 }, + { 49, 0x0000 }, + { 50, 0x005e }, + { 51, 0x003e }, + { 52, 0x0000 }, +}; + +static bool wm8580_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8580_RESET: + return true; + default: + return false; + } +} + +struct pll_state { + unsigned int in; + unsigned int out; +}; + +#define WM8580_NUM_SUPPLIES 3 +static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = { + "AVDD", + "DVDD", + "PVDD", +}; + +struct wm8580_driver_data { + int num_dacs; +}; + +/* codec private data */ +struct wm8580_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES]; + struct pll_state a; + struct pll_state b; + const struct wm8580_driver_data *drvdata; + int sysclk[2]; +}; + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); + +static int wm8580_out_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int ret; + + /* Clear the register cache VU so we write without VU set */ + regcache_cache_only(wm8580->regmap, true); + regmap_update_bits(wm8580->regmap, reg, 0x100, 0x000); + regmap_update_bits(wm8580->regmap, reg2, 0x100, 0x000); + regcache_cache_only(wm8580->regmap, false); + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* Now write again with the volume update bit set */ + snd_soc_component_update_bits(component, reg, 0x100, 0x100); + snd_soc_component_update_bits(component, reg2, 0x100, 0x100); + + return 0; +} + +static const struct snd_kcontrol_new wm8580_snd_controls[] = { +SOC_DOUBLE_R_EXT_TLV("DAC1 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL1, + WM8580_DIGITAL_ATTENUATION_DACR1, + 0, 0xff, 0, snd_soc_get_volsw, wm8580_out_vu, dac_tlv), +SOC_DOUBLE_R_EXT_TLV("DAC2 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL2, + WM8580_DIGITAL_ATTENUATION_DACR2, + 0, 0xff, 0, snd_soc_get_volsw, wm8580_out_vu, dac_tlv), +SOC_DOUBLE_R_EXT_TLV("DAC3 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL3, + WM8580_DIGITAL_ATTENUATION_DACR3, + 0, 0xff, 0, snd_soc_get_volsw, wm8580_out_vu, dac_tlv), + +SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0), +SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0), +SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0), + +SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4, 0, 1, 1, 0), +SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4, 2, 3, 1, 0), +SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4, 4, 5, 1, 0), + +SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0), +SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 1), +SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 1), +SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 1), + +SOC_DOUBLE("Capture Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 1), +SOC_SINGLE("Capture High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0), +}; + +static const struct snd_kcontrol_new wm8581_snd_controls[] = { +SOC_DOUBLE_R_EXT_TLV("DAC4 Playback Volume", + WM8581_DIGITAL_ATTENUATION_DACL4, + WM8581_DIGITAL_ATTENUATION_DACR4, + 0, 0xff, 0, snd_soc_get_volsw, wm8580_out_vu, dac_tlv), + +SOC_SINGLE("DAC4 Deemphasis Switch", WM8580_DAC_CONTROL3, 3, 1, 0), + +SOC_DOUBLE("DAC4 Invert Switch", WM8580_DAC_CONTROL4, 8, 7, 1, 0), + +SOC_SINGLE("DAC4 Switch", WM8580_DAC_CONTROL5, 3, 1, 1), +}; + +static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1), +SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1), +SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1), + +SND_SOC_DAPM_OUTPUT("VOUT1L"), +SND_SOC_DAPM_OUTPUT("VOUT1R"), +SND_SOC_DAPM_OUTPUT("VOUT2L"), +SND_SOC_DAPM_OUTPUT("VOUT2R"), +SND_SOC_DAPM_OUTPUT("VOUT3L"), +SND_SOC_DAPM_OUTPUT("VOUT3R"), + +SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1), + +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), +}; + +static const struct snd_soc_dapm_widget wm8581_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC4", "Playback", WM8580_PWRDN1, 5, 1), + +SND_SOC_DAPM_OUTPUT("VOUT4L"), +SND_SOC_DAPM_OUTPUT("VOUT4R"), +}; + +static const struct snd_soc_dapm_route wm8580_dapm_routes[] = { + { "VOUT1L", NULL, "DAC1" }, + { "VOUT1R", NULL, "DAC1" }, + + { "VOUT2L", NULL, "DAC2" }, + { "VOUT2R", NULL, "DAC2" }, + + { "VOUT3L", NULL, "DAC3" }, + { "VOUT3R", NULL, "DAC3" }, + + { "ADC", NULL, "AINL" }, + { "ADC", NULL, "AINR" }, +}; + +static const struct snd_soc_dapm_route wm8581_dapm_routes[] = { + { "VOUT4L", NULL, "DAC4" }, + { "VOUT4R", NULL, "DAC4" }, +}; + +/* PLL divisors */ +struct _pll_div { + u32 prescale:1; + u32 postscale:1; + u32 freqmode:2; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide */ +#define FIXED_PLL_SIZE (1 << 22) + +/* PLL rate to output rate divisions */ +static struct { + unsigned int div; + unsigned int freqmode; + unsigned int postscale; +} post_table[] = { + { 2, 0, 0 }, + { 4, 0, 1 }, + { 4, 1, 0 }, + { 8, 1, 1 }, + { 8, 2, 0 }, + { 16, 2, 1 }, + { 12, 3, 0 }, + { 24, 3, 1 } +}; + +static int pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + int i; + + pr_debug("wm8580: PLL %uHz->%uHz\n", source, target); + + /* Scale the output frequency up; the PLL should run in the + * region of 90-100MHz. + */ + for (i = 0; i < ARRAY_SIZE(post_table); i++) { + if (target * post_table[i].div >= 90000000 && + target * post_table[i].div <= 100000000) { + pll_div->freqmode = post_table[i].freqmode; + pll_div->postscale = post_table[i].postscale; + target *= post_table[i].div; + break; + } + } + + if (i == ARRAY_SIZE(post_table)) { + printk(KERN_ERR "wm8580: Unable to scale output frequency " + "%u\n", target); + return -EINVAL; + } + + Ndiv = target / source; + + if (Ndiv < 5) { + source /= 2; + pll_div->prescale = 1; + Ndiv = target / source; + } else + pll_div->prescale = 0; + + if ((Ndiv < 5) || (Ndiv > 13)) { + printk(KERN_ERR + "WM8580 N=%u outside supported range\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + pll_div->k = K; + + pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n", + pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode, + pll_div->postscale); + + return 0; +} + +static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + int offset; + struct snd_soc_component *component = codec_dai->component; + struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + struct pll_state *state; + struct _pll_div pll_div; + unsigned int reg; + unsigned int pwr_mask; + int ret; + + /* GCC isn't able to work out the ifs below for initialising/using + * pll_div so suppress warnings. + */ + memset(&pll_div, 0, sizeof(pll_div)); + + switch (pll_id) { + case WM8580_PLLA: + state = &wm8580->a; + offset = 0; + pwr_mask = WM8580_PWRDN2_PLLAPD; + break; + case WM8580_PLLB: + state = &wm8580->b; + offset = 4; + pwr_mask = WM8580_PWRDN2_PLLBPD; + break; + default: + return -ENODEV; + } + + if (freq_in && freq_out) { + ret = pll_factors(&pll_div, freq_out, freq_in); + if (ret != 0) + return ret; + } + + state->in = freq_in; + state->out = freq_out; + + /* Always disable the PLL - it is not safe to leave it running + * while reprogramming it. + */ + snd_soc_component_update_bits(component, WM8580_PWRDN2, pwr_mask, pwr_mask); + + if (!freq_in || !freq_out) + return 0; + + snd_soc_component_write(component, WM8580_PLLA1 + offset, pll_div.k & 0x1ff); + snd_soc_component_write(component, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0x1ff); + snd_soc_component_write(component, WM8580_PLLA3 + offset, + (pll_div.k >> 18 & 0xf) | (pll_div.n << 4)); + + reg = snd_soc_component_read32(component, WM8580_PLLA4 + offset); + reg &= ~0x1b; + reg |= pll_div.prescale | pll_div.postscale << 1 | + pll_div.freqmode << 3; + + snd_soc_component_write(component, WM8580_PLLA4 + offset, reg); + + /* All done, turn it on */ + snd_soc_component_update_bits(component, WM8580_PWRDN2, pwr_mask, 0); + + return 0; +} + +static const int wm8580_sysclk_ratios[] = { + 128, 192, 256, 384, 512, 768, 1152, +}; + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + u16 paifa = 0; + u16 paifb = 0; + int i, ratio, osr; + + /* bit size */ + switch (params_width(params)) { + case 16: + paifa |= 0x8; + break; + case 20: + paifa |= 0x0; + paifb |= WM8580_AIF_LENGTH_20; + break; + case 24: + paifa |= 0x0; + paifb |= WM8580_AIF_LENGTH_24; + break; + case 32: + paifa |= 0x0; + paifb |= WM8580_AIF_LENGTH_32; + break; + default: + return -EINVAL; + } + + /* Look up the SYSCLK ratio; accept only exact matches */ + ratio = wm8580->sysclk[dai->driver->id] / params_rate(params); + for (i = 0; i < ARRAY_SIZE(wm8580_sysclk_ratios); i++) + if (ratio == wm8580_sysclk_ratios[i]) + break; + if (i == ARRAY_SIZE(wm8580_sysclk_ratios)) { + dev_err(component->dev, "Invalid clock ratio %d/%d\n", + wm8580->sysclk[dai->driver->id], params_rate(params)); + return -EINVAL; + } + paifa |= i; + dev_dbg(component->dev, "Running at %dfs with %dHz clock\n", + wm8580_sysclk_ratios[i], wm8580->sysclk[dai->driver->id]); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (ratio) { + case 128: + case 192: + osr = WM8580_DACOSR; + dev_dbg(component->dev, "Selecting 64x OSR\n"); + break; + default: + osr = 0; + dev_dbg(component->dev, "Selecting 128x OSR\n"); + break; + } + + snd_soc_component_update_bits(component, WM8580_PAIF3, WM8580_DACOSR, osr); + } + + snd_soc_component_update_bits(component, WM8580_PAIF1 + dai->driver->id, + WM8580_AIF_RATE_MASK | WM8580_AIF_BCLKSEL_MASK, + paifa); + snd_soc_component_update_bits(component, WM8580_PAIF3 + dai->driver->id, + WM8580_AIF_LENGTH_MASK, paifb); + return 0; +} + +static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int aifa; + unsigned int aifb; + int can_invert_lrclk; + + aifa = snd_soc_component_read32(component, WM8580_PAIF1 + codec_dai->driver->id); + aifb = snd_soc_component_read32(component, WM8580_PAIF3 + codec_dai->driver->id); + + aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aifa &= ~WM8580_AIF_MS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aifa |= WM8580_AIF_MS; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_RIGHTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_LEFTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + break; + case SND_SOC_DAIFMT_DSP_B: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + aifb |= WM8580_AIF_LRP; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_IB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_BCP; + aifb |= WM8580_AIF_LRP; + break; + + case SND_SOC_DAIFMT_IB_NF: + aifb |= WM8580_AIF_BCP; + break; + + case SND_SOC_DAIFMT_NB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_LRP; + break; + + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8580_PAIF1 + codec_dai->driver->id, aifa); + snd_soc_component_write(component, WM8580_PAIF3 + codec_dai->driver->id, aifb); + + return 0; +} + +static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int reg; + + switch (div_id) { + case WM8580_MCLK: + reg = snd_soc_component_read32(component, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_MCLK: + /* Input */ + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA; + break; + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_MCLKOUTSRC_OSC; + break; + + default: + return -EINVAL; + } + snd_soc_component_write(component, WM8580_PLLB4, reg); + break; + + case WM8580_CLKOUTSRC: + reg = snd_soc_component_read32(component, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_NONE: + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK; + break; + + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK; + break; + + default: + return -EINVAL; + } + snd_soc_component_write(component, WM8580_PLLB4, reg); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8580_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + int ret, sel, sel_mask, sel_shift; + + switch (dai->driver->id) { + case WM8580_DAI_PAIFRX: + sel_mask = 0x3; + sel_shift = 0; + break; + + case WM8580_DAI_PAIFTX: + sel_mask = 0xc; + sel_shift = 2; + break; + + default: + WARN(1, "Unknown DAI driver ID\n"); + return -EINVAL; + } + + switch (clk_id) { + case WM8580_CLKSRC_ADCMCLK: + if (dai->driver->id != WM8580_DAI_PAIFTX) + return -EINVAL; + sel = 0 << sel_shift; + break; + case WM8580_CLKSRC_PLLA: + sel = 1 << sel_shift; + break; + case WM8580_CLKSRC_PLLB: + sel = 2 << sel_shift; + break; + case WM8580_CLKSRC_MCLK: + sel = 3 << sel_shift; + break; + default: + dev_err(component->dev, "Unknown clock %d\n", clk_id); + return -EINVAL; + } + + /* We really should validate PLL settings but not yet */ + wm8580->sysclk[dai->driver->id] = freq; + + ret = snd_soc_component_update_bits(component, WM8580_CLKSEL, sel_mask, sel); + if (ret < 0) + return ret; + + return 0; +} + +static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int reg; + + reg = snd_soc_component_read32(component, WM8580_DAC_CONTROL5); + + if (mute) + reg |= WM8580_DAC_CONTROL5_MUTEALL; + else + reg &= ~WM8580_DAC_CONTROL5_MUTEALL; + + snd_soc_component_write(component, WM8580_DAC_CONTROL5, reg); + + return 0; +} + +static int wm8580_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Power up and get individual control of the DACs */ + snd_soc_component_update_bits(component, WM8580_PWRDN1, + WM8580_PWRDN1_PWDN | + WM8580_PWRDN1_ALLDACPD, 0); + + /* Make VMID high impedance */ + snd_soc_component_update_bits(component, WM8580_ADC_CONTROL1, + 0x100, 0); + } + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, WM8580_PWRDN1, + WM8580_PWRDN1_PWDN, WM8580_PWRDN1_PWDN); + break; + } + return 0; +} + +static int wm8580_playback_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + + return snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 1, wm8580->drvdata->num_dacs * 2); +} + +#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8580_dai_ops_playback = { + .startup = wm8580_playback_startup, + .set_sysclk = wm8580_set_sysclk, + .hw_params = wm8580_paif_hw_params, + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + .digital_mute = wm8580_digital_mute, +}; + +static const struct snd_soc_dai_ops wm8580_dai_ops_capture = { + .set_sysclk = wm8580_set_sysclk, + .hw_params = wm8580_paif_hw_params, + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, +}; + +static struct snd_soc_dai_driver wm8580_dai[] = { + { + .name = "wm8580-hifi-playback", + .id = WM8580_DAI_PAIFRX, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = &wm8580_dai_ops_playback, + }, + { + .name = "wm8580-hifi-capture", + .id = WM8580_DAI_PAIFTX, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = &wm8580_dai_ops_capture, + }, +}; + +static int wm8580_probe(struct snd_soc_component *component) +{ + struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int ret = 0; + + switch (wm8580->drvdata->num_dacs) { + case 4: + snd_soc_add_component_controls(component, wm8581_snd_controls, + ARRAY_SIZE(wm8581_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8581_dapm_widgets, + ARRAY_SIZE(wm8581_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, wm8581_dapm_routes, + ARRAY_SIZE(wm8581_dapm_routes)); + break; + default: + break; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + goto err_regulator_get; + } + + /* Get the codec into a known state */ + ret = snd_soc_component_write(component, WM8580_RESET, 0); + if (ret != 0) { + dev_err(component->dev, "Failed to reset component: %d\n", ret); + goto err_regulator_enable; + } + + return 0; + +err_regulator_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); +err_regulator_get: + return ret; +} + +/* power down chip */ +static void wm8580_remove(struct snd_soc_component *component) +{ + struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8580 = { + .probe = wm8580_probe, + .remove = wm8580_remove, + .set_bias_level = wm8580_set_bias_level, + .controls = wm8580_snd_controls, + .num_controls = ARRAY_SIZE(wm8580_snd_controls), + .dapm_widgets = wm8580_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8580_dapm_widgets), + .dapm_routes = wm8580_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8580_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8580_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8580_MAX_REGISTER, + + .reg_defaults = wm8580_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8580_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8580_volatile, +}; + +static const struct wm8580_driver_data wm8580_data = { + .num_dacs = 3, +}; + +static const struct wm8580_driver_data wm8581_data = { + .num_dacs = 4, +}; + +static const struct of_device_id wm8580_of_match[] = { + { .compatible = "wlf,wm8580", .data = &wm8580_data }, + { .compatible = "wlf,wm8581", .data = &wm8581_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, wm8580_of_match); + +static int wm8580_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + const struct of_device_id *of_id; + struct wm8580_priv *wm8580; + int ret, i; + + wm8580 = devm_kzalloc(&i2c->dev, sizeof(struct wm8580_priv), + GFP_KERNEL); + if (wm8580 == NULL) + return -ENOMEM; + + wm8580->regmap = devm_regmap_init_i2c(i2c, &wm8580_regmap); + if (IS_ERR(wm8580->regmap)) + return PTR_ERR(wm8580->regmap); + + for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++) + wm8580->supplies[i].supply = wm8580_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8580); + + of_id = of_match_device(wm8580_of_match, &i2c->dev); + if (of_id) + wm8580->drvdata = of_id->data; + + if (!wm8580->drvdata) { + dev_err(&i2c->dev, "failed to find driver data\n"); + return -EINVAL; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8580, wm8580_dai, ARRAY_SIZE(wm8580_dai)); + + return ret; +} + +static const struct i2c_device_id wm8580_i2c_id[] = { + { "wm8580", (kernel_ulong_t)&wm8580_data }, + { "wm8581", (kernel_ulong_t)&wm8581_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8580_i2c_id); + +static struct i2c_driver wm8580_i2c_driver = { + .driver = { + .name = "wm8580", + .of_match_table = wm8580_of_match, + }, + .probe = wm8580_i2c_probe, + .id_table = wm8580_i2c_id, +}; + +module_i2c_driver(wm8580_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8580 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_AUTHOR("Matt Flax "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h new file mode 100644 index 000000000..1d34656d0 --- /dev/null +++ b/sound/soc/codecs/wm8580.h @@ -0,0 +1,35 @@ +/* + * wm8580.h -- audio driver for WM8580 + * + * Copyright 2008 Samsung Electronics. + * Author: Ryu Euiyoul + * ryu.real@gmail.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8580_H +#define _WM8580_H + +#define WM8580_PLLA 1 +#define WM8580_PLLB 2 + +#define WM8580_MCLK 1 +#define WM8580_CLKOUTSRC 2 + +#define WM8580_CLKSRC_MCLK 1 +#define WM8580_CLKSRC_PLLA 2 +#define WM8580_CLKSRC_PLLB 3 +#define WM8580_CLKSRC_OSC 4 +#define WM8580_CLKSRC_NONE 5 +#define WM8580_CLKSRC_ADCMCLK 6 + +#define WM8580_DAI_PAIFRX 0 +#define WM8580_DAI_PAIFTX 1 + +#endif + diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c new file mode 100644 index 000000000..1da08d281 --- /dev/null +++ b/sound/soc/codecs/wm8711.c @@ -0,0 +1,510 @@ +/* + * wm8711.c -- WM8711 ALSA SoC Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur + * + * Based on wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8711.h" + +/* codec private data */ +struct wm8711_priv { + struct regmap *regmap; + unsigned int sysclk; +}; + +/* + * wm8711 register cache + * We can't read the WM8711 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const struct reg_default wm8711_reg_defaults[] = { + { 0, 0x0079 }, { 1, 0x0079 }, { 2, 0x000a }, { 3, 0x0008 }, + { 4, 0x009f }, { 5, 0x000a }, { 6, 0x0000 }, { 7, 0x0000 }, +}; + +static bool wm8711_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8711_RESET: + return true; + default: + return false; + } +} + +#define wm8711_reset(c) snd_soc_component_write(c, WM8711_RESET, 0) + +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); + +static const struct snd_kcontrol_new wm8711_snd_controls[] = { + +SOC_DOUBLE_R_TLV("Master Playback Volume", WM8711_LOUT1V, WM8711_ROUT1V, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Master Playback ZC Switch", WM8711_LOUT1V, WM8711_ROUT1V, + 7, 1, 0), + +}; + +/* Output Mixer */ +static const struct snd_kcontrol_new wm8711_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8711_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", WM8711_APANA, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8711_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", WM8711_PWR, 4, 1, + &wm8711_output_mixer_controls[0], + ARRAY_SIZE(wm8711_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8711_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +}; + +static const struct snd_soc_dapm_route wm8711_intercon[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return 0; +} + +static int wm8711_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8711_priv *wm8711 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8711_IFACE) & 0xfff3; + int i = get_coeff(wm8711->sysclk, params_rate(params)); + u16 srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + snd_soc_component_write(component, WM8711_SRATE, srate); + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + } + + snd_soc_component_write(component, WM8711_IFACE, iface); + return 0; +} + +static int wm8711_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + + /* set active */ + snd_soc_component_write(component, WM8711_ACTIVE, 0x0001); + + return 0; +} + +static void wm8711_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + + /* deactivate */ + if (!snd_soc_component_is_active(component)) { + udelay(50); + snd_soc_component_write(component, WM8711_ACTIVE, 0x0); + } +} + +static int wm8711_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8711_APDIGI) & 0xfff7; + + if (mute) + snd_soc_component_write(component, WM8711_APDIGI, mute_reg | 0x8); + else + snd_soc_component_write(component, WM8711_APDIGI, mute_reg); + + return 0; +} + +static int wm8711_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8711_priv *wm8711 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8711->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8711_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = snd_soc_component_read32(component, WM8711_IFACE) & 0x000c; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + snd_soc_component_write(component, WM8711_IFACE, iface); + return 0; +} + +static int wm8711_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8711_priv *wm8711 = snd_soc_component_get_drvdata(component); + u16 reg = snd_soc_component_read32(component, WM8711_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_component_write(component, WM8711_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + regcache_sync(wm8711->regmap); + + snd_soc_component_write(component, WM8711_PWR, reg | 0x0040); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, WM8711_ACTIVE, 0x0); + snd_soc_component_write(component, WM8711_PWR, 0xffff); + break; + } + return 0; +} + +#define WM8711_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8711_ops = { + .prepare = wm8711_pcm_prepare, + .hw_params = wm8711_hw_params, + .shutdown = wm8711_shutdown, + .digital_mute = wm8711_mute, + .set_sysclk = wm8711_set_dai_sysclk, + .set_fmt = wm8711_set_dai_fmt, +}; + +static struct snd_soc_dai_driver wm8711_dai = { + .name = "wm8711-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8711_RATES, + .formats = WM8711_FORMATS, + }, + .ops = &wm8711_ops, +}; + +static int wm8711_probe(struct snd_soc_component *component) +{ + int ret; + + ret = wm8711_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset\n"); + return ret; + } + + /* Latch the update bits */ + snd_soc_component_update_bits(component, WM8711_LOUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8711_ROUT1V, 0x0100, 0x0100); + + return ret; + +} + +static const struct snd_soc_component_driver soc_component_dev_wm8711 = { + .probe = wm8711_probe, + .set_bias_level = wm8711_set_bias_level, + .controls = wm8711_snd_controls, + .num_controls = ARRAY_SIZE(wm8711_snd_controls), + .dapm_widgets = wm8711_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8711_dapm_widgets), + .dapm_routes = wm8711_intercon, + .num_dapm_routes = ARRAY_SIZE(wm8711_intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8711_of_match[] = { + { .compatible = "wlf,wm8711", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8711_of_match); + +static const struct regmap_config wm8711_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8711_RESET, + + .reg_defaults = wm8711_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8711_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8711_volatile, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8711_spi_probe(struct spi_device *spi) +{ + struct wm8711_priv *wm8711; + int ret; + + wm8711 = devm_kzalloc(&spi->dev, sizeof(struct wm8711_priv), + GFP_KERNEL); + if (wm8711 == NULL) + return -ENOMEM; + + wm8711->regmap = devm_regmap_init_spi(spi, &wm8711_regmap); + if (IS_ERR(wm8711->regmap)) + return PTR_ERR(wm8711->regmap); + + spi_set_drvdata(spi, wm8711); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8711, &wm8711_dai, 1); + + return ret; +} + +static struct spi_driver wm8711_spi_driver = { + .driver = { + .name = "wm8711", + .of_match_table = wm8711_of_match, + }, + .probe = wm8711_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8711_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wm8711_priv *wm8711; + int ret; + + wm8711 = devm_kzalloc(&client->dev, sizeof(struct wm8711_priv), + GFP_KERNEL); + if (wm8711 == NULL) + return -ENOMEM; + + wm8711->regmap = devm_regmap_init_i2c(client, &wm8711_regmap); + if (IS_ERR(wm8711->regmap)) + return PTR_ERR(wm8711->regmap); + + i2c_set_clientdata(client, wm8711); + + ret = devm_snd_soc_register_component(&client->dev, + &soc_component_dev_wm8711, &wm8711_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8711_i2c_id[] = { + { "wm8711", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8711_i2c_id); + +static struct i2c_driver wm8711_i2c_driver = { + .driver = { + .name = "wm8711", + .of_match_table = wm8711_of_match, + }, + .probe = wm8711_i2c_probe, + .id_table = wm8711_i2c_id, +}; +#endif + +static int __init wm8711_modinit(void) +{ + int ret; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8711_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8711 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8711_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8711 SPI driver: %d\n", + ret); + } +#endif + return 0; +} +module_init(wm8711_modinit); + +static void __exit wm8711_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8711_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8711_spi_driver); +#endif +} +module_exit(wm8711_exit); + +MODULE_DESCRIPTION("ASoC WM8711 driver"); +MODULE_AUTHOR("Mike Arthur"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8711.h b/sound/soc/codecs/wm8711.h new file mode 100644 index 000000000..a61db9854 --- /dev/null +++ b/sound/soc/codecs/wm8711.h @@ -0,0 +1,39 @@ +/* + * wm8711.h -- WM8711 Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur + * + * Based on wm8731.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8711_H +#define _WM8711_H + +/* WM8711 register space */ + +#define WM8711_LOUT1V 0x02 +#define WM8711_ROUT1V 0x03 +#define WM8711_APANA 0x04 +#define WM8711_APDIGI 0x05 +#define WM8711_PWR 0x06 +#define WM8711_IFACE 0x07 +#define WM8711_SRATE 0x08 +#define WM8711_ACTIVE 0x09 +#define WM8711_RESET 0x0f + +#define WM8711_CACHEREGNUM 8 + +#define WM8711_SYSCLK 0 +#define WM8711_DAI 0 + +struct wm8711_setup_data { + unsigned short i2c_address; +}; + +#endif diff --git a/sound/soc/codecs/wm8727.c b/sound/soc/codecs/wm8727.c new file mode 100644 index 000000000..087ec6db8 --- /dev/null +++ b/sound/soc/codecs/wm8727.c @@ -0,0 +1,83 @@ +/* + * wm8727.c + * + * Created on: 15-Oct-2009 + * Author: neil.jones@imgtec.com + * + * Copyright (C) 2009 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct snd_soc_dapm_widget wm8727_dapm_widgets[] = { +SND_SOC_DAPM_OUTPUT("VOUTL"), +SND_SOC_DAPM_OUTPUT("VOUTR"), +}; + +static const struct snd_soc_dapm_route wm8727_dapm_routes[] = { + { "VOUTL", NULL, "Playback" }, + { "VOUTR", NULL, "Playback" }, +}; + +/* + * Note this is a simple chip with no configuration interface, sample rate is + * determined automatically by examining the Master clock and Bit clock ratios + */ +#define WM8727_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_192000) + +static struct snd_soc_dai_driver wm8727_dai = { + .name = "wm8727-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8727_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8727 = { + .dapm_widgets = wm8727_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8727_dapm_widgets), + .dapm_routes = wm8727_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8727_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8727_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm8727, &wm8727_dai, 1); +} + +static struct platform_driver wm8727_codec_driver = { + .driver = { + .name = "wm8727", + }, + + .probe = wm8727_probe, +}; + +module_platform_driver(wm8727_codec_driver); + +MODULE_DESCRIPTION("ASoC wm8727 driver"); +MODULE_AUTHOR("Neil Jones"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c new file mode 100644 index 000000000..839aee35a --- /dev/null +++ b/sound/soc/codecs/wm8728.c @@ -0,0 +1,351 @@ +/* + * wm8728.c -- WM8728 ALSA SoC Audio driver + * + * Copyright 2008 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8728.h" + +/* + * We can't read the WM8728 register space so we cache them instead. + * Note that the defaults here aren't the physical defaults, we latch + * the volume update bits, mute the output and enable infinite zero + * detect. + */ +static const struct reg_default wm8728_reg_defaults[] = { + { 0, 0x1ff }, + { 1, 0x1ff }, + { 2, 0x001 }, + { 3, 0x100 }, +}; + +/* codec private data */ +struct wm8728_priv { + struct regmap *regmap; +}; + +static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1); + +static const struct snd_kcontrol_new wm8728_snd_controls[] = { + +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL, + 0, 255, 0, wm8728_tlv), + +SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0), +}; + +/* + * DAPM controls. + */ +static const struct snd_soc_dapm_widget wm8728_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("VOUTL"), +SND_SOC_DAPM_OUTPUT("VOUTR"), +}; + +static const struct snd_soc_dapm_route wm8728_intercon[] = { + {"VOUTL", NULL, "DAC"}, + {"VOUTR", NULL, "DAC"}, +}; + +static int wm8728_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8728_DACCTL); + + if (mute) + snd_soc_component_write(component, WM8728_DACCTL, mute_reg | 1); + else + snd_soc_component_write(component, WM8728_DACCTL, mute_reg & ~1); + + return 0; +} + +static int wm8728_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 dac = snd_soc_component_read32(component, WM8728_DACCTL); + + dac &= ~0x18; + + switch (params_width(params)) { + case 16: + break; + case 20: + dac |= 0x10; + break; + case 24: + dac |= 0x08; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8728_DACCTL, dac); + + return 0; +} + +static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = snd_soc_component_read32(component, WM8728_IFCTL); + + /* Currently only I2S is supported by the driver, though the + * hardware is more flexible. + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 1; + break; + default: + return -EINVAL; + } + + /* The hardware only support full slave mode */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + iface &= ~0x22; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x20; + iface &= ~0x02; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x02; + iface &= ~0x20; + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x22; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8728_IFCTL, iface); + return 0; +} + +static int wm8728_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8728_priv *wm8728 = snd_soc_component_get_drvdata(component); + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Power everything up... */ + reg = snd_soc_component_read32(component, WM8728_DACCTL); + snd_soc_component_write(component, WM8728_DACCTL, reg & ~0x4); + + /* ..then sync in the register cache. */ + regcache_sync(wm8728->regmap); + } + break; + + case SND_SOC_BIAS_OFF: + reg = snd_soc_component_read32(component, WM8728_DACCTL); + snd_soc_component_write(component, WM8728_DACCTL, reg | 0x4); + break; + } + return 0; +} + +#define WM8728_RATES (SNDRV_PCM_RATE_8000_192000) + +#define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8728_dai_ops = { + .hw_params = wm8728_hw_params, + .digital_mute = wm8728_mute, + .set_fmt = wm8728_set_dai_fmt, +}; + +static struct snd_soc_dai_driver wm8728_dai = { + .name = "wm8728-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8728_RATES, + .formats = WM8728_FORMATS, + }, + .ops = &wm8728_dai_ops, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8728 = { + .set_bias_level = wm8728_set_bias_level, + .controls = wm8728_snd_controls, + .num_controls = ARRAY_SIZE(wm8728_snd_controls), + .dapm_widgets = wm8728_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8728_dapm_widgets), + .dapm_routes = wm8728_intercon, + .num_dapm_routes = ARRAY_SIZE(wm8728_intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8728_of_match[] = { + { .compatible = "wlf,wm8728", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8728_of_match); + +static const struct regmap_config wm8728_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8728_IFCTL, + + .reg_defaults = wm8728_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8728_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8728_spi_probe(struct spi_device *spi) +{ + struct wm8728_priv *wm8728; + int ret; + + wm8728 = devm_kzalloc(&spi->dev, sizeof(struct wm8728_priv), + GFP_KERNEL); + if (wm8728 == NULL) + return -ENOMEM; + + wm8728->regmap = devm_regmap_init_spi(spi, &wm8728_regmap); + if (IS_ERR(wm8728->regmap)) + return PTR_ERR(wm8728->regmap); + + spi_set_drvdata(spi, wm8728); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8728, &wm8728_dai, 1); + + return ret; +} + +static struct spi_driver wm8728_spi_driver = { + .driver = { + .name = "wm8728", + .of_match_table = wm8728_of_match, + }, + .probe = wm8728_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8728_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8728_priv *wm8728; + int ret; + + wm8728 = devm_kzalloc(&i2c->dev, sizeof(struct wm8728_priv), + GFP_KERNEL); + if (wm8728 == NULL) + return -ENOMEM; + + wm8728->regmap = devm_regmap_init_i2c(i2c, &wm8728_regmap); + if (IS_ERR(wm8728->regmap)) + return PTR_ERR(wm8728->regmap); + + i2c_set_clientdata(i2c, wm8728); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8728, &wm8728_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8728_i2c_id[] = { + { "wm8728", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id); + +static struct i2c_driver wm8728_i2c_driver = { + .driver = { + .name = "wm8728", + .of_match_table = wm8728_of_match, + }, + .probe = wm8728_i2c_probe, + .id_table = wm8728_i2c_id, +}; +#endif + +static int __init wm8728_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8728_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8728 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8728_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8728 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8728_modinit); + +static void __exit wm8728_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8728_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8728_spi_driver); +#endif +} +module_exit(wm8728_exit); + +MODULE_DESCRIPTION("ASoC WM8728 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8728.h b/sound/soc/codecs/wm8728.h new file mode 100644 index 000000000..8aea362ff --- /dev/null +++ b/sound/soc/codecs/wm8728.h @@ -0,0 +1,21 @@ +/* + * wm8728.h -- WM8728 ASoC codec driver + * + * Copyright 2008 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8728_H +#define _WM8728_H + +#define WM8728_DACLVOL 0x00 +#define WM8728_DACRVOL 0x01 +#define WM8728_DACCTL 0x02 +#define WM8728_IFCTL 0x03 + +#endif diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c new file mode 100644 index 000000000..3c5c02b03 --- /dev/null +++ b/sound/soc/codecs/wm8731.c @@ -0,0 +1,847 @@ +/* + * wm8731.c -- WM8731 ALSA SoC Audio driver + * + * Copyright 2005 Openedhand Ltd. + * Copyright 2006-12 Wolfson Microelectronics, plc + * + * Author: Richard Purdie + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8731.h" + +#define WM8731_NUM_SUPPLIES 4 +static const char *wm8731_supply_names[WM8731_NUM_SUPPLIES] = { + "AVDD", + "HPVDD", + "DCVDD", + "DBVDD", +}; + +/* codec private data */ +struct wm8731_priv { + struct regmap *regmap; + struct clk *mclk; + struct regulator_bulk_data supplies[WM8731_NUM_SUPPLIES]; + const struct snd_pcm_hw_constraint_list *constraints; + unsigned int sysclk; + int sysclk_type; + int playback_fs; + bool deemph; + + struct mutex lock; +}; + + +/* + * wm8731 register cache + */ +static const struct reg_default wm8731_reg_defaults[] = { + { 0, 0x0097 }, + { 1, 0x0097 }, + { 2, 0x0079 }, + { 3, 0x0079 }, + { 4, 0x000a }, + { 5, 0x0008 }, + { 6, 0x009f }, + { 7, 0x000a }, + { 8, 0x0000 }, + { 9, 0x0000 }, +}; + +static bool wm8731_volatile(struct device *dev, unsigned int reg) +{ + return reg == WM8731_RESET; +} + +#define wm8731_reset(m) regmap_write(m, WM8731_RESET, 0) + +static const char *wm8731_input_select[] = {"Line In", "Mic"}; + +static SOC_ENUM_SINGLE_DECL(wm8731_insel_enum, + WM8731_APANA, 2, wm8731_input_select); + +static int wm8731_deemph[] = { 0, 32000, 44100, 48000 }; + +static int wm8731_set_deemph(struct snd_soc_component *component) +{ + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8731->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(wm8731_deemph); i++) { + if (abs(wm8731_deemph[i] - wm8731->playback_fs) < + abs(wm8731_deemph[best] - wm8731->playback_fs)) + best = i; + } + + val = best << 1; + } else { + best = 0; + val = 0; + } + + dev_dbg(component->dev, "Set deemphasis %d (%dHz)\n", + best, wm8731_deemph[best]); + + return snd_soc_component_update_bits(component, WM8731_APDIGI, 0x6, val); +} + +static int wm8731_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8731->deemph; + + return 0; +} + +static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + int ret = 0; + + if (deemph > 1) + return -EINVAL; + + mutex_lock(&wm8731->lock); + if (wm8731->deemph != deemph) { + wm8731->deemph = deemph; + + wm8731_set_deemph(component); + + ret = 1; + } + mutex_unlock(&wm8731->lock); + + return ret; +} + +static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 2000, 0); + +static const struct snd_kcontrol_new wm8731_snd_controls[] = { + +SOC_DOUBLE_R_TLV("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0, + in_tlv), +SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1), + +SOC_SINGLE_TLV("Mic Boost Volume", WM8731_APANA, 0, 1, 0, mic_tlv), +SOC_SINGLE("Mic Capture Switch", WM8731_APANA, 1, 1, 1), + +SOC_SINGLE_TLV("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1, + sidetone_tlv), + +SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0), + +SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0, + wm8731_get_deemph, wm8731_put_deemph), +}; + +/* Output Mixer */ +static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new wm8731_input_mux_controls = +SOC_DAPM_ENUM("Input Select", wm8731_insel_enum); + +static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("ACTIVE",WM8731_ACTIVE, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("OSC", WM8731_PWR, 5, 1, NULL, 0), +SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, + &wm8731_output_mixer_controls[0], + ARRAY_SIZE(wm8731_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8731_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8731_PWR, 2, 1), +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux_controls), +SND_SOC_DAPM_PGA("Line Input", WM8731_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8731_PWR, 1, 1), +SND_SOC_DAPM_INPUT("MICIN"), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static int wm8731_check_osc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + + return wm8731->sysclk_type == WM8731_SYSCLK_XTAL; +} + +static const struct snd_soc_dapm_route wm8731_intercon[] = { + {"DAC", NULL, "OSC", wm8731_check_osc}, + {"ADC", NULL, "OSC", wm8731_check_osc}, + {"DAC", NULL, "ACTIVE"}, + {"ADC", NULL, "ACTIVE"}, + + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line In", "Line Input"}, + {"Input Mux", "Mic", "Mic Bias"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Bias", NULL, "MICIN"}, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +/* rates constraints */ +static const unsigned int wm8731_rates_12000000[] = { + 8000, 32000, 44100, 48000, 96000, 88200, +}; + +static const unsigned int wm8731_rates_12288000_18432000[] = { + 8000, 32000, 48000, 96000, +}; + +static const unsigned int wm8731_rates_11289600_16934400[] = { + 8000, 44100, 88200, +}; + +static const struct snd_pcm_hw_constraint_list wm8731_constraints_12000000 = { + .list = wm8731_rates_12000000, + .count = ARRAY_SIZE(wm8731_rates_12000000), +}; + +static const +struct snd_pcm_hw_constraint_list wm8731_constraints_12288000_18432000 = { + .list = wm8731_rates_12288000_18432000, + .count = ARRAY_SIZE(wm8731_rates_12288000_18432000), +}; + +static const +struct snd_pcm_hw_constraint_list wm8731_constraints_11289600_16934400 = { + .list = wm8731_rates_11289600_16934400, + .count = ARRAY_SIZE(wm8731_rates_11289600_16934400), +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return 0; +} + +static int wm8731_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8731_IFACE) & 0xfff3; + int i = get_coeff(wm8731->sysclk, params_rate(params)); + u16 srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + wm8731->playback_fs = params_rate(params); + + snd_soc_component_write(component, WM8731_SRATE, srate); + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + iface |= 0x000c; + break; + } + + wm8731_set_deemph(component); + + snd_soc_component_write(component, WM8731_IFACE, iface); + return 0; +} + +static int wm8731_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8731_APDIGI) & 0xfff7; + + if (mute) + snd_soc_component_write(component, WM8731_APDIGI, mute_reg | 0x8); + else + snd_soc_component_write(component, WM8731_APDIGI, mute_reg); + return 0; +} + +static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8731_SYSCLK_XTAL: + case WM8731_SYSCLK_MCLK: + if (wm8731->mclk && clk_set_rate(wm8731->mclk, freq)) + return -EINVAL; + wm8731->sysclk_type = clk_id; + break; + default: + return -EINVAL; + } + + switch (freq) { + case 0: + wm8731->constraints = NULL; + break; + case 12000000: + wm8731->constraints = &wm8731_constraints_12000000; + break; + case 12288000: + case 18432000: + wm8731->constraints = &wm8731_constraints_12288000_18432000; + break; + case 16934400: + case 11289600: + wm8731->constraints = &wm8731_constraints_11289600_16934400; + break; + default: + return -EINVAL; + } + + wm8731->sysclk = freq; + + snd_soc_dapm_sync(dapm); + + return 0; +} + + +static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0013; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0003; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + snd_soc_component_write(component, WM8731_IFACE, iface); + return 0; +} + +static int wm8731_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + int ret; + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + if (wm8731->mclk) { + ret = clk_prepare_enable(wm8731->mclk); + if (ret) + return ret; + } + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); + if (ret != 0) + return ret; + + regcache_sync(wm8731->regmap); + } + + /* Clear PWROFF, gate CLKOUT, everything else as-is */ + reg = snd_soc_component_read32(component, WM8731_PWR) & 0xff7f; + snd_soc_component_write(component, WM8731_PWR, reg | 0x0040); + break; + case SND_SOC_BIAS_OFF: + if (wm8731->mclk) + clk_disable_unprepare(wm8731->mclk); + snd_soc_component_write(component, WM8731_PWR, 0xffff); + regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); + regcache_mark_dirty(wm8731->regmap); + break; + } + return 0; +} + +static int wm8731_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(dai->component); + + if (wm8731->constraints) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + wm8731->constraints); + + return 0; +} + +#define WM8731_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8731_dai_ops = { + .startup = wm8731_startup, + .hw_params = wm8731_hw_params, + .digital_mute = wm8731_mute, + .set_sysclk = wm8731_set_dai_sysclk, + .set_fmt = wm8731_set_dai_fmt, +}; + +static struct snd_soc_dai_driver wm8731_dai = { + .name = "wm8731-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8731_RATES, + .formats = WM8731_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8731_RATES, + .formats = WM8731_FORMATS,}, + .ops = &wm8731_dai_ops, + .symmetric_rates = 1, +}; + +static int wm8731_request_supplies(struct device *dev, + struct wm8731_priv *wm8731) +{ + int ret = 0, i; + + for (i = 0; i < ARRAY_SIZE(wm8731->supplies); i++) + wm8731->supplies[i].supply = wm8731_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); + if (ret != 0) { + dev_err(dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + return 0; +} + +static int wm8731_hw_init(struct device *dev, struct wm8731_priv *wm8731) +{ + int ret = 0; + + ret = wm8731_reset(wm8731->regmap); + if (ret < 0) { + dev_err(dev, "Failed to issue reset: %d\n", ret); + goto err; + } + + /* Clear POWEROFF, keep everything else disabled */ + regmap_write(wm8731->regmap, WM8731_PWR, 0x7f); + + /* Latch the update bits */ + regmap_update_bits(wm8731->regmap, WM8731_LOUT1V, 0x100, 0); + regmap_update_bits(wm8731->regmap, WM8731_ROUT1V, 0x100, 0); + regmap_update_bits(wm8731->regmap, WM8731_LINVOL, 0x100, 0); + regmap_update_bits(wm8731->regmap, WM8731_RINVOL, 0x100, 0); + + /* Disable bypass path by default */ + regmap_update_bits(wm8731->regmap, WM8731_APANA, 0x8, 0); + + regcache_mark_dirty(wm8731->regmap); + +err: + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8731 = { + .set_bias_level = wm8731_set_bias_level, + .controls = wm8731_snd_controls, + .num_controls = ARRAY_SIZE(wm8731_snd_controls), + .dapm_widgets = wm8731_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), + .dapm_routes = wm8731_intercon, + .num_dapm_routes = ARRAY_SIZE(wm8731_intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8731_of_match[] = { + { .compatible = "wlf,wm8731", }, + { } +}; + +MODULE_DEVICE_TABLE(of, wm8731_of_match); + +static const struct regmap_config wm8731_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = WM8731_RESET, + .volatile_reg = wm8731_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8731_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8731_reg_defaults), +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8731_spi_probe(struct spi_device *spi) +{ + struct wm8731_priv *wm8731; + int ret; + + wm8731 = devm_kzalloc(&spi->dev, sizeof(*wm8731), GFP_KERNEL); + if (wm8731 == NULL) + return -ENOMEM; + + wm8731->mclk = devm_clk_get(&spi->dev, "mclk"); + if (IS_ERR(wm8731->mclk)) { + ret = PTR_ERR(wm8731->mclk); + if (ret == -ENOENT) { + wm8731->mclk = NULL; + dev_warn(&spi->dev, "Assuming static MCLK\n"); + } else { + dev_err(&spi->dev, "Failed to get MCLK: %d\n", + ret); + return ret; + } + } + + mutex_init(&wm8731->lock); + + spi_set_drvdata(spi, wm8731); + + ret = wm8731_request_supplies(&spi->dev, wm8731); + if (ret != 0) + return ret; + + wm8731->regmap = devm_regmap_init_spi(spi, &wm8731_regmap); + if (IS_ERR(wm8731->regmap)) { + ret = PTR_ERR(wm8731->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = wm8731_hw_init(&spi->dev, wm8731); + if (ret != 0) + return ret; + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8731, &wm8731_dai, 1); + if (ret != 0) { + dev_err(&spi->dev, "Failed to register CODEC: %d\n", ret); + return ret; + } + + return 0; +} + +static int wm8731_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8731_spi_driver = { + .driver = { + .name = "wm8731", + .of_match_table = wm8731_of_match, + }, + .probe = wm8731_spi_probe, + .remove = wm8731_spi_remove, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8731_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8731_priv *wm8731; + int ret; + + wm8731 = devm_kzalloc(&i2c->dev, sizeof(struct wm8731_priv), + GFP_KERNEL); + if (wm8731 == NULL) + return -ENOMEM; + + wm8731->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(wm8731->mclk)) { + ret = PTR_ERR(wm8731->mclk); + if (ret == -ENOENT) { + wm8731->mclk = NULL; + dev_warn(&i2c->dev, "Assuming static MCLK\n"); + } else { + dev_err(&i2c->dev, "Failed to get MCLK: %d\n", + ret); + return ret; + } + } + + mutex_init(&wm8731->lock); + + i2c_set_clientdata(i2c, wm8731); + + ret = wm8731_request_supplies(&i2c->dev, wm8731); + if (ret != 0) + return ret; + + wm8731->regmap = devm_regmap_init_i2c(i2c, &wm8731_regmap); + if (IS_ERR(wm8731->regmap)) { + ret = PTR_ERR(wm8731->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + goto err_regulator_enable; + } + + ret = wm8731_hw_init(&i2c->dev, wm8731); + if (ret != 0) + goto err_regulator_enable; + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8731, &wm8731_dai, 1); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + goto err_regulator_enable; + } + + return 0; + +err_regulator_enable: + /* Regulators will be enabled by bias management */ + regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); + + return ret; +} + +static int wm8731_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wm8731_i2c_id[] = { + { "wm8731", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); + +static struct i2c_driver wm8731_i2c_driver = { + .driver = { + .name = "wm8731", + .of_match_table = wm8731_of_match, + }, + .probe = wm8731_i2c_probe, + .remove = wm8731_i2c_remove, + .id_table = wm8731_i2c_id, +}; +#endif + +static int __init wm8731_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8731_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8731 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8731_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8731 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8731_modinit); + +static void __exit wm8731_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8731_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8731_spi_driver); +#endif +} +module_exit(wm8731_exit); + +MODULE_DESCRIPTION("ASoC WM8731 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h new file mode 100644 index 000000000..c7c6f15b0 --- /dev/null +++ b/sound/soc/codecs/wm8731.h @@ -0,0 +1,39 @@ +/* + * wm8731.h -- WM8731 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8731_H +#define _WM8731_H + +/* WM8731 register space */ + +#define WM8731_LINVOL 0x00 +#define WM8731_RINVOL 0x01 +#define WM8731_LOUT1V 0x02 +#define WM8731_ROUT1V 0x03 +#define WM8731_APANA 0x04 +#define WM8731_APDIGI 0x05 +#define WM8731_PWR 0x06 +#define WM8731_IFACE 0x07 +#define WM8731_SRATE 0x08 +#define WM8731_ACTIVE 0x09 +#define WM8731_RESET 0x0f + +#define WM8731_CACHEREGNUM 10 + +#define WM8731_SYSCLK_MCLK 0 +#define WM8731_SYSCLK_XTAL 1 + +#define WM8731_DAI 0 + +#endif diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c new file mode 100644 index 000000000..568b77692 --- /dev/null +++ b/sound/soc/codecs/wm8737.c @@ -0,0 +1,738 @@ +/* + * wm8737.c -- WM8737 ALSA SoC Audio driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8737.h" + +#define WM8737_NUM_SUPPLIES 4 +static const char *wm8737_supply_names[WM8737_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "AVDD", + "MVDD", +}; + +/* codec private data */ +struct wm8737_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8737_NUM_SUPPLIES]; + unsigned int mclk; +}; + +static const struct reg_default wm8737_reg_defaults[] = { + { 0, 0x00C3 }, /* R0 - Left PGA volume */ + { 1, 0x00C3 }, /* R1 - Right PGA volume */ + { 2, 0x0007 }, /* R2 - AUDIO path L */ + { 3, 0x0007 }, /* R3 - AUDIO path R */ + { 4, 0x0000 }, /* R4 - 3D Enhance */ + { 5, 0x0000 }, /* R5 - ADC Control */ + { 6, 0x0000 }, /* R6 - Power Management */ + { 7, 0x000A }, /* R7 - Audio Format */ + { 8, 0x0000 }, /* R8 - Clocking */ + { 9, 0x000F }, /* R9 - MIC Preamp Control */ + { 10, 0x0003 }, /* R10 - Misc Bias Control */ + { 11, 0x0000 }, /* R11 - Noise Gate */ + { 12, 0x007C }, /* R12 - ALC1 */ + { 13, 0x0000 }, /* R13 - ALC2 */ + { 14, 0x0032 }, /* R14 - ALC3 */ +}; + +static bool wm8737_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8737_RESET: + return true; + default: + return false; + } +} + +static int wm8737_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, WM8737_RESET, 0); +} + +static const DECLARE_TLV_DB_RANGE(micboost_tlv, + 0, 0, TLV_DB_SCALE_ITEM(1300, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2800, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(3300, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(pga_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(ng_tlv, -7800, 600, 0); +static const DECLARE_TLV_DB_SCALE(alc_max_tlv, -1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(alc_target_tlv, -1800, 100, 0); + +static const char *micbias_enum_text[] = { + "25%", + "50%", + "75%", + "100%", +}; + +static SOC_ENUM_SINGLE_DECL(micbias_enum, + WM8737_MIC_PREAMP_CONTROL, 0, micbias_enum_text); + +static const char *low_cutoff_text[] = { + "Low", "High" +}; + +static SOC_ENUM_SINGLE_DECL(low_3d, + WM8737_3D_ENHANCE, 6, low_cutoff_text); + +static const char *high_cutoff_text[] = { + "High", "Low" +}; + +static SOC_ENUM_SINGLE_DECL(high_3d, + WM8737_3D_ENHANCE, 5, high_cutoff_text); + +static const char *alc_fn_text[] = { + "Disabled", "Right", "Left", "Stereo" +}; + +static SOC_ENUM_SINGLE_DECL(alc_fn, + WM8737_ALC1, 7, alc_fn_text); + +static const char *alc_hold_text[] = { + "0", "2.67ms", "5.33ms", "10.66ms", "21.32ms", "42.64ms", "85.28ms", + "170.56ms", "341.12ms", "682.24ms", "1.364s", "2.728s", "5.458s", + "10.916s", "21.832s", "43.691s" +}; + +static SOC_ENUM_SINGLE_DECL(alc_hold, + WM8737_ALC2, 0, alc_hold_text); + +static const char *alc_atk_text[] = { + "8.4ms", "16.8ms", "33.6ms", "67.2ms", "134.4ms", "268.8ms", "537.6ms", + "1.075s", "2.15s", "4.3s", "8.6s" +}; + +static SOC_ENUM_SINGLE_DECL(alc_atk, + WM8737_ALC3, 0, alc_atk_text); + +static const char *alc_dcy_text[] = { + "33.6ms", "67.2ms", "134.4ms", "268.8ms", "537.6ms", "1.075s", "2.15s", + "4.3s", "8.6s", "17.2s", "34.41s" +}; + +static SOC_ENUM_SINGLE_DECL(alc_dcy, + WM8737_ALC3, 4, alc_dcy_text); + +static const struct snd_kcontrol_new wm8737_snd_controls[] = { +SOC_DOUBLE_R_TLV("Mic Boost Volume", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 6, 3, 0, micboost_tlv), +SOC_DOUBLE_R("Mic Boost Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 4, 1, 0), +SOC_DOUBLE("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 3, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8737_LEFT_PGA_VOLUME, + WM8737_RIGHT_PGA_VOLUME, 0, 255, 0, pga_tlv), +SOC_DOUBLE("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 2, 1, 0), + +SOC_DOUBLE("INPUT1 DC Bias Switch", WM8737_MISC_BIAS_CONTROL, 0, 1, 1, 0), + +SOC_ENUM("Mic PGA Bias", micbias_enum), +SOC_SINGLE("ADC Low Power Switch", WM8737_ADC_CONTROL, 2, 1, 0), +SOC_SINGLE("High Pass Filter Switch", WM8737_ADC_CONTROL, 0, 1, 1), +SOC_DOUBLE("Polarity Invert Switch", WM8737_ADC_CONTROL, 5, 6, 1, 0), + +SOC_SINGLE("3D Switch", WM8737_3D_ENHANCE, 0, 1, 0), +SOC_SINGLE("3D Depth", WM8737_3D_ENHANCE, 1, 15, 0), +SOC_ENUM("3D Low Cut-off", low_3d), +SOC_ENUM("3D High Cut-off", high_3d), +SOC_SINGLE_TLV("3D ADC Volume", WM8737_3D_ENHANCE, 7, 1, 1, adc_tlv), + +SOC_SINGLE("Noise Gate Switch", WM8737_NOISE_GATE, 0, 1, 0), +SOC_SINGLE_TLV("Noise Gate Threshold Volume", WM8737_NOISE_GATE, 2, 7, 0, + ng_tlv), + +SOC_ENUM("ALC", alc_fn), +SOC_SINGLE_TLV("ALC Max Gain Volume", WM8737_ALC1, 4, 7, 0, alc_max_tlv), +SOC_SINGLE_TLV("ALC Target Volume", WM8737_ALC1, 0, 15, 0, alc_target_tlv), +SOC_ENUM("ALC Hold Time", alc_hold), +SOC_SINGLE("ALC ZC Switch", WM8737_ALC2, 4, 1, 0), +SOC_ENUM("ALC Attack Time", alc_atk), +SOC_ENUM("ALC Decay Time", alc_dcy), +}; + +static const char *linsel_text[] = { + "LINPUT1", "LINPUT2", "LINPUT3", "LINPUT1 DC", +}; + +static SOC_ENUM_SINGLE_DECL(linsel_enum, + WM8737_AUDIO_PATH_L, 7, linsel_text); + +static const struct snd_kcontrol_new linsel_mux = + SOC_DAPM_ENUM("LINSEL", linsel_enum); + + +static const char *rinsel_text[] = { + "RINPUT1", "RINPUT2", "RINPUT3", "RINPUT1 DC", +}; + +static SOC_ENUM_SINGLE_DECL(rinsel_enum, + WM8737_AUDIO_PATH_R, 7, rinsel_text); + +static const struct snd_kcontrol_new rinsel_mux = + SOC_DAPM_ENUM("RINSEL", rinsel_enum); + +static const char *bypass_text[] = { + "Direct", "Preamp" +}; + +static SOC_ENUM_SINGLE_DECL(lbypass_enum, + WM8737_MIC_PREAMP_CONTROL, 2, bypass_text); + +static const struct snd_kcontrol_new lbypass_mux = + SOC_DAPM_ENUM("Left Bypass", lbypass_enum); + + +static SOC_ENUM_SINGLE_DECL(rbypass_enum, + WM8737_MIC_PREAMP_CONTROL, 3, bypass_text); + +static const struct snd_kcontrol_new rbypass_mux = + SOC_DAPM_ENUM("Left Bypass", rbypass_enum); + +static const struct snd_soc_dapm_widget wm8737_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT3"), +SND_SOC_DAPM_INPUT("LACIN"), +SND_SOC_DAPM_INPUT("RACIN"), + +SND_SOC_DAPM_MUX("LINSEL", SND_SOC_NOPM, 0, 0, &linsel_mux), +SND_SOC_DAPM_MUX("RINSEL", SND_SOC_NOPM, 0, 0, &rinsel_mux), + +SND_SOC_DAPM_MUX("Left Preamp Mux", SND_SOC_NOPM, 0, 0, &lbypass_mux), +SND_SOC_DAPM_MUX("Right Preamp Mux", SND_SOC_NOPM, 0, 0, &rbypass_mux), + +SND_SOC_DAPM_PGA("PGAL", WM8737_POWER_MANAGEMENT, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("PGAR", WM8737_POWER_MANAGEMENT, 4, 0, NULL, 0), + +SND_SOC_DAPM_DAC("ADCL", NULL, WM8737_POWER_MANAGEMENT, 3, 0), +SND_SOC_DAPM_DAC("ADCR", NULL, WM8737_POWER_MANAGEMENT, 2, 0), + +SND_SOC_DAPM_AIF_OUT("AIF", "Capture", 0, WM8737_POWER_MANAGEMENT, 6, 0), +}; + +static const struct snd_soc_dapm_route intercon[] = { + { "LINSEL", "LINPUT1", "LINPUT1" }, + { "LINSEL", "LINPUT2", "LINPUT2" }, + { "LINSEL", "LINPUT3", "LINPUT3" }, + { "LINSEL", "LINPUT1 DC", "LINPUT1" }, + + { "RINSEL", "RINPUT1", "RINPUT1" }, + { "RINSEL", "RINPUT2", "RINPUT2" }, + { "RINSEL", "RINPUT3", "RINPUT3" }, + { "RINSEL", "RINPUT1 DC", "RINPUT1" }, + + { "Left Preamp Mux", "Preamp", "LINSEL" }, + { "Left Preamp Mux", "Direct", "LACIN" }, + + { "Right Preamp Mux", "Preamp", "RINSEL" }, + { "Right Preamp Mux", "Direct", "RACIN" }, + + { "PGAL", NULL, "Left Preamp Mux" }, + { "PGAR", NULL, "Right Preamp Mux" }, + + { "ADCL", NULL, "PGAL" }, + { "ADCR", NULL, "PGAR" }, + + { "AIF", NULL, "ADCL" }, + { "AIF", NULL, "ADCR" }, +}; + +/* codec mclk clock divider coefficients */ +static const struct { + u32 mclk; + u32 rate; + u8 usb; + u8 sr; +} coeff_div[] = { + { 12288000, 8000, 0, 0x4 }, + { 12288000, 12000, 0, 0x8 }, + { 12288000, 16000, 0, 0xa }, + { 12288000, 24000, 0, 0x1c }, + { 12288000, 32000, 0, 0xc }, + { 12288000, 48000, 0, 0 }, + { 12288000, 96000, 0, 0xe }, + + { 11289600, 8000, 0, 0x14 }, + { 11289600, 11025, 0, 0x18 }, + { 11289600, 22050, 0, 0x1a }, + { 11289600, 44100, 0, 0x10 }, + { 11289600, 88200, 0, 0x1e }, + + { 18432000, 8000, 0, 0x5 }, + { 18432000, 12000, 0, 0x9 }, + { 18432000, 16000, 0, 0xb }, + { 18432000, 24000, 0, 0x1b }, + { 18432000, 32000, 0, 0xd }, + { 18432000, 48000, 0, 0x1 }, + { 18432000, 96000, 0, 0x1f }, + + { 16934400, 8000, 0, 0x15 }, + { 16934400, 11025, 0, 0x19 }, + { 16934400, 22050, 0, 0x1b }, + { 16934400, 44100, 0, 0x11 }, + { 16934400, 88200, 0, 0x1f }, + + { 12000000, 8000, 1, 0x4 }, + { 12000000, 11025, 1, 0x19 }, + { 12000000, 12000, 1, 0x8 }, + { 12000000, 16000, 1, 0xa }, + { 12000000, 22050, 1, 0x1b }, + { 12000000, 24000, 1, 0x1c }, + { 12000000, 32000, 1, 0xc }, + { 12000000, 44100, 1, 0x11 }, + { 12000000, 48000, 1, 0x0 }, + { 12000000, 88200, 1, 0x1f }, + { 12000000, 96000, 1, 0xe }, +}; + +static int wm8737_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8737_priv *wm8737 = snd_soc_component_get_drvdata(component); + int i; + u16 clocking = 0; + u16 af = 0; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate != params_rate(params)) + continue; + + if (coeff_div[i].mclk == wm8737->mclk) + break; + + if (coeff_div[i].mclk == wm8737->mclk * 2) { + clocking |= WM8737_CLKDIV2; + break; + } + } + + if (i == ARRAY_SIZE(coeff_div)) { + dev_err(component->dev, "%dHz MCLK can't support %dHz\n", + wm8737->mclk, params_rate(params)); + return -EINVAL; + } + + clocking |= coeff_div[i].usb | (coeff_div[i].sr << WM8737_SR_SHIFT); + + switch (params_width(params)) { + case 16: + break; + case 20: + af |= 0x8; + break; + case 24: + af |= 0x10; + break; + case 32: + af |= 0x18; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8737_AUDIO_FORMAT, WM8737_WL_MASK, af); + snd_soc_component_update_bits(component, WM8737_CLOCKING, + WM8737_USB_MODE | WM8737_CLKDIV2 | WM8737_SR_MASK, + clocking); + + return 0; +} + +static int wm8737_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8737_priv *wm8737 = snd_soc_component_get_drvdata(component); + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (freq == coeff_div[i].mclk || + freq == coeff_div[i].mclk * 2) { + wm8737->mclk = freq; + return 0; + } + } + + dev_err(component->dev, "MCLK rate %dHz not supported\n", freq); + + return -EINVAL; +} + + +static int wm8737_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 af = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + af |= WM8737_MS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + af |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + af |= 0x1; + break; + case SND_SOC_DAIFMT_DSP_A: + af |= 0x3; + break; + case SND_SOC_DAIFMT_DSP_B: + af |= 0x13; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + af |= WM8737_LRP; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8737_AUDIO_FORMAT, + WM8737_FORMAT_MASK | WM8737_LRP | WM8737_MS, af); + + return 0; +} + +static int wm8737_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8737_priv *wm8737 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID at 2*75k */ + snd_soc_component_update_bits(component, WM8737_MISC_BIAS_CONTROL, + WM8737_VMIDSEL_MASK, 0); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8737->supplies), + wm8737->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + regcache_sync(wm8737->regmap); + + /* Fast VMID ramp at 2*2.5k */ + snd_soc_component_update_bits(component, WM8737_MISC_BIAS_CONTROL, + WM8737_VMIDSEL_MASK, + 2 << WM8737_VMIDSEL_SHIFT); + + /* Bring VMID up */ + snd_soc_component_update_bits(component, WM8737_POWER_MANAGEMENT, + WM8737_VMID_MASK | + WM8737_VREF_MASK, + WM8737_VMID_MASK | + WM8737_VREF_MASK); + + msleep(500); + } + + /* VMID at 2*300k */ + snd_soc_component_update_bits(component, WM8737_MISC_BIAS_CONTROL, + WM8737_VMIDSEL_MASK, + 1 << WM8737_VMIDSEL_SHIFT); + + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, WM8737_POWER_MANAGEMENT, + WM8737_VMID_MASK | WM8737_VREF_MASK, 0); + + regulator_bulk_disable(ARRAY_SIZE(wm8737->supplies), + wm8737->supplies); + break; + } + + return 0; +} + +#define WM8737_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8737_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8737_dai_ops = { + .hw_params = wm8737_hw_params, + .set_sysclk = wm8737_set_dai_sysclk, + .set_fmt = wm8737_set_dai_fmt, +}; + +static struct snd_soc_dai_driver wm8737_dai = { + .name = "wm8737", + .capture = { + .stream_name = "Capture", + .channels_min = 2, /* Mono modes not yet supported */ + .channels_max = 2, + .rates = WM8737_RATES, + .formats = WM8737_FORMATS, + }, + .ops = &wm8737_dai_ops, +}; + +static int wm8737_probe(struct snd_soc_component *component) +{ + struct wm8737_priv *wm8737 = snd_soc_component_get_drvdata(component); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8737->supplies), + wm8737->supplies); + if (ret != 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + goto err_get; + } + + ret = wm8737_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset\n"); + goto err_enable; + } + + snd_soc_component_update_bits(component, WM8737_LEFT_PGA_VOLUME, WM8737_LVU, + WM8737_LVU); + snd_soc_component_update_bits(component, WM8737_RIGHT_PGA_VOLUME, WM8737_RVU, + WM8737_RVU); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + /* Bias level configuration will have done an extra enable */ + regulator_bulk_disable(ARRAY_SIZE(wm8737->supplies), wm8737->supplies); + + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8737->supplies), wm8737->supplies); +err_get: + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8737 = { + .probe = wm8737_probe, + .set_bias_level = wm8737_set_bias_level, + .controls = wm8737_snd_controls, + .num_controls = ARRAY_SIZE(wm8737_snd_controls), + .dapm_widgets = wm8737_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8737_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8737_of_match[] = { + { .compatible = "wlf,wm8737", }, + { } +}; + +MODULE_DEVICE_TABLE(of, wm8737_of_match); + +static const struct regmap_config wm8737_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8737_MAX_REGISTER, + + .reg_defaults = wm8737_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8737_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8737_volatile, +}; + +#if IS_ENABLED(CONFIG_I2C) +static int wm8737_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8737_priv *wm8737; + int ret, i; + + wm8737 = devm_kzalloc(&i2c->dev, sizeof(struct wm8737_priv), + GFP_KERNEL); + if (wm8737 == NULL) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(wm8737->supplies); i++) + wm8737->supplies[i].supply = wm8737_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8737->supplies), + wm8737->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8737->regmap = devm_regmap_init_i2c(i2c, &wm8737_regmap); + if (IS_ERR(wm8737->regmap)) + return PTR_ERR(wm8737->regmap); + + i2c_set_clientdata(i2c, wm8737); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8737, &wm8737_dai, 1); + + return ret; + +} + +static const struct i2c_device_id wm8737_i2c_id[] = { + { "wm8737", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8737_i2c_id); + +static struct i2c_driver wm8737_i2c_driver = { + .driver = { + .name = "wm8737", + .of_match_table = wm8737_of_match, + }, + .probe = wm8737_i2c_probe, + .id_table = wm8737_i2c_id, +}; +#endif + +#if defined(CONFIG_SPI_MASTER) +static int wm8737_spi_probe(struct spi_device *spi) +{ + struct wm8737_priv *wm8737; + int ret, i; + + wm8737 = devm_kzalloc(&spi->dev, sizeof(struct wm8737_priv), + GFP_KERNEL); + if (wm8737 == NULL) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(wm8737->supplies); i++) + wm8737->supplies[i].supply = wm8737_supply_names[i]; + + ret = devm_regulator_bulk_get(&spi->dev, ARRAY_SIZE(wm8737->supplies), + wm8737->supplies); + if (ret != 0) { + dev_err(&spi->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8737->regmap = devm_regmap_init_spi(spi, &wm8737_regmap); + if (IS_ERR(wm8737->regmap)) + return PTR_ERR(wm8737->regmap); + + spi_set_drvdata(spi, wm8737); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8737, &wm8737_dai, 1); + + return ret; +} + +static struct spi_driver wm8737_spi_driver = { + .driver = { + .name = "wm8737", + .of_match_table = wm8737_of_match, + }, + .probe = wm8737_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +static int __init wm8737_modinit(void) +{ + int ret; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8737_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8737 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8737_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8737 SPI driver: %d\n", + ret); + } +#endif + return 0; +} +module_init(wm8737_modinit); + +static void __exit wm8737_exit(void) +{ +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8737_spi_driver); +#endif +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8737_i2c_driver); +#endif +} +module_exit(wm8737_exit); + +MODULE_DESCRIPTION("ASoC WM8737 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8737.h b/sound/soc/codecs/wm8737.h new file mode 100644 index 000000000..23d14c8ff --- /dev/null +++ b/sound/soc/codecs/wm8737.h @@ -0,0 +1,322 @@ +#ifndef _WM8737_H +#define _WM8737_H + +/* + * wm8737.c -- WM8523 ALSA SoC Audio driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * Register values. + */ +#define WM8737_LEFT_PGA_VOLUME 0x00 +#define WM8737_RIGHT_PGA_VOLUME 0x01 +#define WM8737_AUDIO_PATH_L 0x02 +#define WM8737_AUDIO_PATH_R 0x03 +#define WM8737_3D_ENHANCE 0x04 +#define WM8737_ADC_CONTROL 0x05 +#define WM8737_POWER_MANAGEMENT 0x06 +#define WM8737_AUDIO_FORMAT 0x07 +#define WM8737_CLOCKING 0x08 +#define WM8737_MIC_PREAMP_CONTROL 0x09 +#define WM8737_MISC_BIAS_CONTROL 0x0A +#define WM8737_NOISE_GATE 0x0B +#define WM8737_ALC1 0x0C +#define WM8737_ALC2 0x0D +#define WM8737_ALC3 0x0E +#define WM8737_RESET 0x0F + +#define WM8737_REGISTER_COUNT 16 +#define WM8737_MAX_REGISTER 0x0F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Left PGA volume + */ +#define WM8737_LVU 0x0100 /* LVU */ +#define WM8737_LVU_MASK 0x0100 /* LVU */ +#define WM8737_LVU_SHIFT 8 /* LVU */ +#define WM8737_LVU_WIDTH 1 /* LVU */ +#define WM8737_LINVOL_MASK 0x00FF /* LINVOL - [7:0] */ +#define WM8737_LINVOL_SHIFT 0 /* LINVOL - [7:0] */ +#define WM8737_LINVOL_WIDTH 8 /* LINVOL - [7:0] */ + +/* + * R1 (0x01) - Right PGA volume + */ +#define WM8737_RVU 0x0100 /* RVU */ +#define WM8737_RVU_MASK 0x0100 /* RVU */ +#define WM8737_RVU_SHIFT 8 /* RVU */ +#define WM8737_RVU_WIDTH 1 /* RVU */ +#define WM8737_RINVOL_MASK 0x00FF /* RINVOL - [7:0] */ +#define WM8737_RINVOL_SHIFT 0 /* RINVOL - [7:0] */ +#define WM8737_RINVOL_WIDTH 8 /* RINVOL - [7:0] */ + +/* + * R2 (0x02) - AUDIO path L + */ +#define WM8737_LINSEL_MASK 0x0180 /* LINSEL - [8:7] */ +#define WM8737_LINSEL_SHIFT 7 /* LINSEL - [8:7] */ +#define WM8737_LINSEL_WIDTH 2 /* LINSEL - [8:7] */ +#define WM8737_LMICBOOST_MASK 0x0060 /* LMICBOOST - [6:5] */ +#define WM8737_LMICBOOST_SHIFT 5 /* LMICBOOST - [6:5] */ +#define WM8737_LMICBOOST_WIDTH 2 /* LMICBOOST - [6:5] */ +#define WM8737_LMBE 0x0010 /* LMBE */ +#define WM8737_LMBE_MASK 0x0010 /* LMBE */ +#define WM8737_LMBE_SHIFT 4 /* LMBE */ +#define WM8737_LMBE_WIDTH 1 /* LMBE */ +#define WM8737_LMZC 0x0008 /* LMZC */ +#define WM8737_LMZC_MASK 0x0008 /* LMZC */ +#define WM8737_LMZC_SHIFT 3 /* LMZC */ +#define WM8737_LMZC_WIDTH 1 /* LMZC */ +#define WM8737_LPZC 0x0004 /* LPZC */ +#define WM8737_LPZC_MASK 0x0004 /* LPZC */ +#define WM8737_LPZC_SHIFT 2 /* LPZC */ +#define WM8737_LPZC_WIDTH 1 /* LPZC */ +#define WM8737_LZCTO_MASK 0x0003 /* LZCTO - [1:0] */ +#define WM8737_LZCTO_SHIFT 0 /* LZCTO - [1:0] */ +#define WM8737_LZCTO_WIDTH 2 /* LZCTO - [1:0] */ + +/* + * R3 (0x03) - AUDIO path R + */ +#define WM8737_RINSEL_MASK 0x0180 /* RINSEL - [8:7] */ +#define WM8737_RINSEL_SHIFT 7 /* RINSEL - [8:7] */ +#define WM8737_RINSEL_WIDTH 2 /* RINSEL - [8:7] */ +#define WM8737_RMICBOOST_MASK 0x0060 /* RMICBOOST - [6:5] */ +#define WM8737_RMICBOOST_SHIFT 5 /* RMICBOOST - [6:5] */ +#define WM8737_RMICBOOST_WIDTH 2 /* RMICBOOST - [6:5] */ +#define WM8737_RMBE 0x0010 /* RMBE */ +#define WM8737_RMBE_MASK 0x0010 /* RMBE */ +#define WM8737_RMBE_SHIFT 4 /* RMBE */ +#define WM8737_RMBE_WIDTH 1 /* RMBE */ +#define WM8737_RMZC 0x0008 /* RMZC */ +#define WM8737_RMZC_MASK 0x0008 /* RMZC */ +#define WM8737_RMZC_SHIFT 3 /* RMZC */ +#define WM8737_RMZC_WIDTH 1 /* RMZC */ +#define WM8737_RPZC 0x0004 /* RPZC */ +#define WM8737_RPZC_MASK 0x0004 /* RPZC */ +#define WM8737_RPZC_SHIFT 2 /* RPZC */ +#define WM8737_RPZC_WIDTH 1 /* RPZC */ +#define WM8737_RZCTO_MASK 0x0003 /* RZCTO - [1:0] */ +#define WM8737_RZCTO_SHIFT 0 /* RZCTO - [1:0] */ +#define WM8737_RZCTO_WIDTH 2 /* RZCTO - [1:0] */ + +/* + * R4 (0x04) - 3D Enhance + */ +#define WM8737_DIV2 0x0080 /* DIV2 */ +#define WM8737_DIV2_MASK 0x0080 /* DIV2 */ +#define WM8737_DIV2_SHIFT 7 /* DIV2 */ +#define WM8737_DIV2_WIDTH 1 /* DIV2 */ +#define WM8737_3DLC 0x0040 /* 3DLC */ +#define WM8737_3DLC_MASK 0x0040 /* 3DLC */ +#define WM8737_3DLC_SHIFT 6 /* 3DLC */ +#define WM8737_3DLC_WIDTH 1 /* 3DLC */ +#define WM8737_3DUC 0x0020 /* 3DUC */ +#define WM8737_3DUC_MASK 0x0020 /* 3DUC */ +#define WM8737_3DUC_SHIFT 5 /* 3DUC */ +#define WM8737_3DUC_WIDTH 1 /* 3DUC */ +#define WM8737_3DDEPTH_MASK 0x001E /* 3DDEPTH - [4:1] */ +#define WM8737_3DDEPTH_SHIFT 1 /* 3DDEPTH - [4:1] */ +#define WM8737_3DDEPTH_WIDTH 4 /* 3DDEPTH - [4:1] */ +#define WM8737_3DE 0x0001 /* 3DE */ +#define WM8737_3DE_MASK 0x0001 /* 3DE */ +#define WM8737_3DE_SHIFT 0 /* 3DE */ +#define WM8737_3DE_WIDTH 1 /* 3DE */ + +/* + * R5 (0x05) - ADC Control + */ +#define WM8737_MONOMIX_MASK 0x0180 /* MONOMIX - [8:7] */ +#define WM8737_MONOMIX_SHIFT 7 /* MONOMIX - [8:7] */ +#define WM8737_MONOMIX_WIDTH 2 /* MONOMIX - [8:7] */ +#define WM8737_POLARITY_MASK 0x0060 /* POLARITY - [6:5] */ +#define WM8737_POLARITY_SHIFT 5 /* POLARITY - [6:5] */ +#define WM8737_POLARITY_WIDTH 2 /* POLARITY - [6:5] */ +#define WM8737_HPOR 0x0010 /* HPOR */ +#define WM8737_HPOR_MASK 0x0010 /* HPOR */ +#define WM8737_HPOR_SHIFT 4 /* HPOR */ +#define WM8737_HPOR_WIDTH 1 /* HPOR */ +#define WM8737_LP 0x0004 /* LP */ +#define WM8737_LP_MASK 0x0004 /* LP */ +#define WM8737_LP_SHIFT 2 /* LP */ +#define WM8737_LP_WIDTH 1 /* LP */ +#define WM8737_MONOUT 0x0002 /* MONOUT */ +#define WM8737_MONOUT_MASK 0x0002 /* MONOUT */ +#define WM8737_MONOUT_SHIFT 1 /* MONOUT */ +#define WM8737_MONOUT_WIDTH 1 /* MONOUT */ +#define WM8737_ADCHPD 0x0001 /* ADCHPD */ +#define WM8737_ADCHPD_MASK 0x0001 /* ADCHPD */ +#define WM8737_ADCHPD_SHIFT 0 /* ADCHPD */ +#define WM8737_ADCHPD_WIDTH 1 /* ADCHPD */ + +/* + * R6 (0x06) - Power Management + */ +#define WM8737_VMID 0x0100 /* VMID */ +#define WM8737_VMID_MASK 0x0100 /* VMID */ +#define WM8737_VMID_SHIFT 8 /* VMID */ +#define WM8737_VMID_WIDTH 1 /* VMID */ +#define WM8737_VREF 0x0080 /* VREF */ +#define WM8737_VREF_MASK 0x0080 /* VREF */ +#define WM8737_VREF_SHIFT 7 /* VREF */ +#define WM8737_VREF_WIDTH 1 /* VREF */ +#define WM8737_AI 0x0040 /* AI */ +#define WM8737_AI_MASK 0x0040 /* AI */ +#define WM8737_AI_SHIFT 6 /* AI */ +#define WM8737_AI_WIDTH 1 /* AI */ +#define WM8737_PGL 0x0020 /* PGL */ +#define WM8737_PGL_MASK 0x0020 /* PGL */ +#define WM8737_PGL_SHIFT 5 /* PGL */ +#define WM8737_PGL_WIDTH 1 /* PGL */ +#define WM8737_PGR 0x0010 /* PGR */ +#define WM8737_PGR_MASK 0x0010 /* PGR */ +#define WM8737_PGR_SHIFT 4 /* PGR */ +#define WM8737_PGR_WIDTH 1 /* PGR */ +#define WM8737_ADL 0x0008 /* ADL */ +#define WM8737_ADL_MASK 0x0008 /* ADL */ +#define WM8737_ADL_SHIFT 3 /* ADL */ +#define WM8737_ADL_WIDTH 1 /* ADL */ +#define WM8737_ADR 0x0004 /* ADR */ +#define WM8737_ADR_MASK 0x0004 /* ADR */ +#define WM8737_ADR_SHIFT 2 /* ADR */ +#define WM8737_ADR_WIDTH 1 /* ADR */ +#define WM8737_MICBIAS_MASK 0x0003 /* MICBIAS - [1:0] */ +#define WM8737_MICBIAS_SHIFT 0 /* MICBIAS - [1:0] */ +#define WM8737_MICBIAS_WIDTH 2 /* MICBIAS - [1:0] */ + +/* + * R7 (0x07) - Audio Format + */ +#define WM8737_SDODIS 0x0080 /* SDODIS */ +#define WM8737_SDODIS_MASK 0x0080 /* SDODIS */ +#define WM8737_SDODIS_SHIFT 7 /* SDODIS */ +#define WM8737_SDODIS_WIDTH 1 /* SDODIS */ +#define WM8737_MS 0x0040 /* MS */ +#define WM8737_MS_MASK 0x0040 /* MS */ +#define WM8737_MS_SHIFT 6 /* MS */ +#define WM8737_MS_WIDTH 1 /* MS */ +#define WM8737_LRP 0x0010 /* LRP */ +#define WM8737_LRP_MASK 0x0010 /* LRP */ +#define WM8737_LRP_SHIFT 4 /* LRP */ +#define WM8737_LRP_WIDTH 1 /* LRP */ +#define WM8737_WL_MASK 0x000C /* WL - [3:2] */ +#define WM8737_WL_SHIFT 2 /* WL - [3:2] */ +#define WM8737_WL_WIDTH 2 /* WL - [3:2] */ +#define WM8737_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */ +#define WM8737_FORMAT_SHIFT 0 /* FORMAT - [1:0] */ +#define WM8737_FORMAT_WIDTH 2 /* FORMAT - [1:0] */ + +/* + * R8 (0x08) - Clocking + */ +#define WM8737_AUTODETECT 0x0080 /* AUTODETECT */ +#define WM8737_AUTODETECT_MASK 0x0080 /* AUTODETECT */ +#define WM8737_AUTODETECT_SHIFT 7 /* AUTODETECT */ +#define WM8737_AUTODETECT_WIDTH 1 /* AUTODETECT */ +#define WM8737_CLKDIV2 0x0040 /* CLKDIV2 */ +#define WM8737_CLKDIV2_MASK 0x0040 /* CLKDIV2 */ +#define WM8737_CLKDIV2_SHIFT 6 /* CLKDIV2 */ +#define WM8737_CLKDIV2_WIDTH 1 /* CLKDIV2 */ +#define WM8737_SR_MASK 0x003E /* SR - [5:1] */ +#define WM8737_SR_SHIFT 1 /* SR - [5:1] */ +#define WM8737_SR_WIDTH 5 /* SR - [5:1] */ +#define WM8737_USB_MODE 0x0001 /* USB MODE */ +#define WM8737_USB_MODE_MASK 0x0001 /* USB MODE */ +#define WM8737_USB_MODE_SHIFT 0 /* USB MODE */ +#define WM8737_USB_MODE_WIDTH 1 /* USB MODE */ + +/* + * R9 (0x09) - MIC Preamp Control + */ +#define WM8737_RBYPEN 0x0008 /* RBYPEN */ +#define WM8737_RBYPEN_MASK 0x0008 /* RBYPEN */ +#define WM8737_RBYPEN_SHIFT 3 /* RBYPEN */ +#define WM8737_RBYPEN_WIDTH 1 /* RBYPEN */ +#define WM8737_LBYPEN 0x0004 /* LBYPEN */ +#define WM8737_LBYPEN_MASK 0x0004 /* LBYPEN */ +#define WM8737_LBYPEN_SHIFT 2 /* LBYPEN */ +#define WM8737_LBYPEN_WIDTH 1 /* LBYPEN */ +#define WM8737_MBCTRL_MASK 0x0003 /* MBCTRL - [1:0] */ +#define WM8737_MBCTRL_SHIFT 0 /* MBCTRL - [1:0] */ +#define WM8737_MBCTRL_WIDTH 2 /* MBCTRL - [1:0] */ + +/* + * R10 (0x0A) - Misc Bias Control + */ +#define WM8737_VMIDSEL_MASK 0x000C /* VMIDSEL - [3:2] */ +#define WM8737_VMIDSEL_SHIFT 2 /* VMIDSEL - [3:2] */ +#define WM8737_VMIDSEL_WIDTH 2 /* VMIDSEL - [3:2] */ +#define WM8737_LINPUT1_DC_BIAS_ENABLE 0x0002 /* LINPUT1 DC BIAS ENABLE */ +#define WM8737_LINPUT1_DC_BIAS_ENABLE_MASK 0x0002 /* LINPUT1 DC BIAS ENABLE */ +#define WM8737_LINPUT1_DC_BIAS_ENABLE_SHIFT 1 /* LINPUT1 DC BIAS ENABLE */ +#define WM8737_LINPUT1_DC_BIAS_ENABLE_WIDTH 1 /* LINPUT1 DC BIAS ENABLE */ +#define WM8737_RINPUT1_DC_BIAS_ENABLE 0x0001 /* RINPUT1 DC BIAS ENABLE */ +#define WM8737_RINPUT1_DC_BIAS_ENABLE_MASK 0x0001 /* RINPUT1 DC BIAS ENABLE */ +#define WM8737_RINPUT1_DC_BIAS_ENABLE_SHIFT 0 /* RINPUT1 DC BIAS ENABLE */ +#define WM8737_RINPUT1_DC_BIAS_ENABLE_WIDTH 1 /* RINPUT1 DC BIAS ENABLE */ + +/* + * R11 (0x0B) - Noise Gate + */ +#define WM8737_NGTH_MASK 0x001C /* NGTH - [4:2] */ +#define WM8737_NGTH_SHIFT 2 /* NGTH - [4:2] */ +#define WM8737_NGTH_WIDTH 3 /* NGTH - [4:2] */ +#define WM8737_NGAT 0x0001 /* NGAT */ +#define WM8737_NGAT_MASK 0x0001 /* NGAT */ +#define WM8737_NGAT_SHIFT 0 /* NGAT */ +#define WM8737_NGAT_WIDTH 1 /* NGAT */ + +/* + * R12 (0x0C) - ALC1 + */ +#define WM8737_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */ +#define WM8737_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */ +#define WM8737_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */ +#define WM8737_MAX_GAIN_MASK 0x0070 /* MAX GAIN - [6:4] */ +#define WM8737_MAX_GAIN_SHIFT 4 /* MAX GAIN - [6:4] */ +#define WM8737_MAX_GAIN_WIDTH 3 /* MAX GAIN - [6:4] */ +#define WM8737_ALCL_MASK 0x000F /* ALCL - [3:0] */ +#define WM8737_ALCL_SHIFT 0 /* ALCL - [3:0] */ +#define WM8737_ALCL_WIDTH 4 /* ALCL - [3:0] */ + +/* + * R13 (0x0D) - ALC2 + */ +#define WM8737_ALCZCE 0x0010 /* ALCZCE */ +#define WM8737_ALCZCE_MASK 0x0010 /* ALCZCE */ +#define WM8737_ALCZCE_SHIFT 4 /* ALCZCE */ +#define WM8737_ALCZCE_WIDTH 1 /* ALCZCE */ +#define WM8737_HLD_MASK 0x000F /* HLD - [3:0] */ +#define WM8737_HLD_SHIFT 0 /* HLD - [3:0] */ +#define WM8737_HLD_WIDTH 4 /* HLD - [3:0] */ + +/* + * R14 (0x0E) - ALC3 + */ +#define WM8737_DCY_MASK 0x00F0 /* DCY - [7:4] */ +#define WM8737_DCY_SHIFT 4 /* DCY - [7:4] */ +#define WM8737_DCY_WIDTH 4 /* DCY - [7:4] */ +#define WM8737_ATK_MASK 0x000F /* ATK - [3:0] */ +#define WM8737_ATK_SHIFT 0 /* ATK - [3:0] */ +#define WM8737_ATK_WIDTH 4 /* ATK - [3:0] */ + +/* + * R15 (0x0F) - Reset + */ +#define WM8737_RESET_MASK 0x01FF /* RESET - [8:0] */ +#define WM8737_RESET_SHIFT 0 /* RESET - [8:0] */ +#define WM8737_RESET_WIDTH 9 /* RESET - [8:0] */ + +#endif diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c new file mode 100644 index 000000000..1fedf74da --- /dev/null +++ b/sound/soc/codecs/wm8741.c @@ -0,0 +1,693 @@ +/* + * wm8741.c -- WM8741 ALSA SoC Audio driver + * + * Copyright 2010-1 Wolfson Microelectronics plc + * + * Author: Ian Lartey + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8741.h" + +#define WM8741_NUM_SUPPLIES 2 +static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = { + "AVDD", + "DVDD", +}; + +/* codec private data */ +struct wm8741_priv { + struct wm8741_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES]; + unsigned int sysclk; + const struct snd_pcm_hw_constraint_list *sysclk_constraints; +}; + +static const struct reg_default wm8741_reg_defaults[] = { + { 0, 0x0000 }, /* R0 - DACLLSB Attenuation */ + { 1, 0x0000 }, /* R1 - DACLMSB Attenuation */ + { 2, 0x0000 }, /* R2 - DACRLSB Attenuation */ + { 3, 0x0000 }, /* R3 - DACRMSB Attenuation */ + { 4, 0x0000 }, /* R4 - Volume Control */ + { 5, 0x000A }, /* R5 - Format Control */ + { 6, 0x0000 }, /* R6 - Filter Control */ + { 7, 0x0000 }, /* R7 - Mode Control 1 */ + { 8, 0x0002 }, /* R8 - Mode Control 2 */ + { 32, 0x0002 }, /* R32 - ADDITONAL_CONTROL_1 */ +}; + +static int wm8741_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, WM8741_RESET, 0); +} + +static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, -12700, 13, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 400, 0); + +static const struct snd_kcontrol_new wm8741_snd_controls_stereo[] = { +SOC_DOUBLE_R_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + WM8741_DACRLSB_ATTENUATION, 1, 255, 1, dac_tlv_fine), +SOC_DOUBLE_R_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION, + WM8741_DACRMSB_ATTENUATION, 0, 511, 1, dac_tlv), +}; + +static const struct snd_kcontrol_new wm8741_snd_controls_mono_left[] = { +SOC_SINGLE_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION, + 1, 255, 1, dac_tlv_fine), +SOC_SINGLE_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION, + 0, 511, 1, dac_tlv), +}; + +static const struct snd_kcontrol_new wm8741_snd_controls_mono_right[] = { +SOC_SINGLE_TLV("Fine Playback Volume", WM8741_DACRLSB_ATTENUATION, + 1, 255, 1, dac_tlv_fine), +SOC_SINGLE_TLV("Playback Volume", WM8741_DACRMSB_ATTENUATION, + 0, 511, 1, dac_tlv), +}; + +static const struct snd_soc_dapm_widget wm8741_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DACL", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_DAC("DACR", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("VOUTLP"), +SND_SOC_DAPM_OUTPUT("VOUTLN"), +SND_SOC_DAPM_OUTPUT("VOUTRP"), +SND_SOC_DAPM_OUTPUT("VOUTRN"), +}; + +static const struct snd_soc_dapm_route wm8741_dapm_routes[] = { + { "VOUTLP", NULL, "DACL" }, + { "VOUTLN", NULL, "DACL" }, + { "VOUTRP", NULL, "DACR" }, + { "VOUTRN", NULL, "DACR" }, +}; + +static const unsigned int rates_11289[] = { + 44100, 88200, +}; + +static const struct snd_pcm_hw_constraint_list constraints_11289 = { + .count = ARRAY_SIZE(rates_11289), + .list = rates_11289, +}; + +static const unsigned int rates_12288[] = { + 32000, 48000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_12288 = { + .count = ARRAY_SIZE(rates_12288), + .list = rates_12288, +}; + +static const unsigned int rates_16384[] = { + 32000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16384 = { + .count = ARRAY_SIZE(rates_16384), + .list = rates_16384, +}; + +static const unsigned int rates_16934[] = { + 44100, 88200, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16934 = { + .count = ARRAY_SIZE(rates_16934), + .list = rates_16934, +}; + +static const unsigned int rates_18432[] = { + 48000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_18432 = { + .count = ARRAY_SIZE(rates_18432), + .list = rates_18432, +}; + +static const unsigned int rates_22579[] = { + 44100, 88200, 176400 +}; + +static const struct snd_pcm_hw_constraint_list constraints_22579 = { + .count = ARRAY_SIZE(rates_22579), + .list = rates_22579, +}; + +static const unsigned int rates_24576[] = { + 32000, 48000, 96000, 192000 +}; + +static const struct snd_pcm_hw_constraint_list constraints_24576 = { + .count = ARRAY_SIZE(rates_24576), + .list = rates_24576, +}; + +static const unsigned int rates_36864[] = { + 48000, 96000, 192000 +}; + +static const struct snd_pcm_hw_constraint_list constraints_36864 = { + .count = ARRAY_SIZE(rates_36864), + .list = rates_36864, +}; + +static int wm8741_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + + if (wm8741->sysclk) + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + wm8741->sysclk_constraints); + + return 0; +} + +static int wm8741_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + unsigned int iface; + int i; + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8741->sysclk) { + dev_err(component->dev, + "No MCLK configured, call set_sysclk() on init or in hw_params\n"); + return -EINVAL; + } + + /* Find a supported LRCLK rate */ + for (i = 0; i < wm8741->sysclk_constraints->count; i++) { + if (wm8741->sysclk_constraints->list[i] == params_rate(params)) + break; + } + + if (i == wm8741->sysclk_constraints->count) { + dev_err(component->dev, "LRCLK %d unsupported with MCLK %d\n", + params_rate(params), wm8741->sysclk); + return -EINVAL; + } + + /* bit size */ + switch (params_width(params)) { + case 16: + iface = 0x0; + break; + case 20: + iface = 0x1; + break; + case 24: + iface = 0x2; + break; + case 32: + iface = 0x3; + break; + default: + dev_dbg(component->dev, "wm8741_hw_params: Unsupported bit size param = %d", + params_width(params)); + return -EINVAL; + } + + dev_dbg(component->dev, "wm8741_hw_params: bit size param = %d, rate param = %d", + params_width(params), params_rate(params)); + + snd_soc_component_update_bits(component, WM8741_FORMAT_CONTROL, WM8741_IWL_MASK, + iface); + + return 0; +} + +static int wm8741_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "wm8741_set_dai_sysclk info: freq=%dHz\n", freq); + + switch (freq) { + case 0: + wm8741->sysclk_constraints = NULL; + break; + case 11289600: + wm8741->sysclk_constraints = &constraints_11289; + break; + case 12288000: + wm8741->sysclk_constraints = &constraints_12288; + break; + case 16384000: + wm8741->sysclk_constraints = &constraints_16384; + break; + case 16934400: + wm8741->sysclk_constraints = &constraints_16934; + break; + case 18432000: + wm8741->sysclk_constraints = &constraints_18432; + break; + case 22579200: + case 33868800: + wm8741->sysclk_constraints = &constraints_22579; + break; + case 24576000: + wm8741->sysclk_constraints = &constraints_24576; + break; + case 36864000: + wm8741->sysclk_constraints = &constraints_36864; + break; + default: + return -EINVAL; + } + + wm8741->sysclk = freq; + return 0; +} + +static int wm8741_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int iface; + + /* check master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface = 0x08; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface = 0x00; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface = 0x04; + break; + case SND_SOC_DAIFMT_DSP_A: + iface = 0x0C; + break; + case SND_SOC_DAIFMT_DSP_B: + iface = 0x1C; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x10; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x20; + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x30; + break; + default: + return -EINVAL; + } + + + dev_dbg(component->dev, "wm8741_set_dai_fmt: Format=%x, Clock Inv=%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK, + ((fmt & SND_SOC_DAIFMT_INV_MASK))); + + snd_soc_component_update_bits(component, WM8741_FORMAT_CONTROL, + WM8741_BCP_MASK | WM8741_LRP_MASK | WM8741_FMT_MASK, + iface); + + return 0; +} + +#define WM8741_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 WM8741_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8741_dai_ops = { + .startup = wm8741_startup, + .hw_params = wm8741_hw_params, + .set_sysclk = wm8741_set_dai_sysclk, + .set_fmt = wm8741_set_dai_fmt, +}; + +static struct snd_soc_dai_driver wm8741_dai = { + .name = "wm8741", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8741_RATES, + .formats = WM8741_FORMATS, + }, + .ops = &wm8741_dai_ops, +}; + +#ifdef CONFIG_PM +static int wm8741_resume(struct snd_soc_component *component) +{ + snd_soc_component_cache_sync(component); + return 0; +} +#else +#define wm8741_resume NULL +#endif + +static int wm8741_configure(struct snd_soc_component *component) +{ + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + + /* Configure differential mode */ + switch (wm8741->pdata.diff_mode) { + case WM8741_DIFF_MODE_STEREO: + case WM8741_DIFF_MODE_STEREO_REVERSED: + case WM8741_DIFF_MODE_MONO_LEFT: + case WM8741_DIFF_MODE_MONO_RIGHT: + snd_soc_component_update_bits(component, WM8741_MODE_CONTROL_2, + WM8741_DIFF_MASK, + wm8741->pdata.diff_mode << WM8741_DIFF_SHIFT); + break; + default: + return -EINVAL; + } + + /* Change some default settings - latch VU */ + snd_soc_component_update_bits(component, WM8741_DACLLSB_ATTENUATION, + WM8741_UPDATELL, WM8741_UPDATELL); + snd_soc_component_update_bits(component, WM8741_DACLMSB_ATTENUATION, + WM8741_UPDATELM, WM8741_UPDATELM); + snd_soc_component_update_bits(component, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERL, WM8741_UPDATERL); + snd_soc_component_update_bits(component, WM8741_DACRMSB_ATTENUATION, + WM8741_UPDATERM, WM8741_UPDATERM); + + return 0; +} + +static int wm8741_add_controls(struct snd_soc_component *component) +{ + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + + switch (wm8741->pdata.diff_mode) { + case WM8741_DIFF_MODE_STEREO: + case WM8741_DIFF_MODE_STEREO_REVERSED: + snd_soc_add_component_controls(component, + wm8741_snd_controls_stereo, + ARRAY_SIZE(wm8741_snd_controls_stereo)); + break; + case WM8741_DIFF_MODE_MONO_LEFT: + snd_soc_add_component_controls(component, + wm8741_snd_controls_mono_left, + ARRAY_SIZE(wm8741_snd_controls_mono_left)); + break; + case WM8741_DIFF_MODE_MONO_RIGHT: + snd_soc_add_component_controls(component, + wm8741_snd_controls_mono_right, + ARRAY_SIZE(wm8741_snd_controls_mono_right)); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8741_probe(struct snd_soc_component *component) +{ + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + int ret = 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8741->supplies), + wm8741->supplies); + if (ret != 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + goto err_get; + } + + ret = wm8741_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset\n"); + goto err_enable; + } + + ret = wm8741_configure(component); + if (ret < 0) { + dev_err(component->dev, "Failed to change default settings\n"); + goto err_enable; + } + + ret = wm8741_add_controls(component); + if (ret < 0) { + dev_err(component->dev, "Failed to add controls\n"); + goto err_enable; + } + + dev_dbg(component->dev, "Successful registration\n"); + return ret; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); +err_get: + return ret; +} + +static void wm8741_remove(struct snd_soc_component *component) +{ + struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component); + + regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8741 = { + .probe = wm8741_probe, + .remove = wm8741_remove, + .resume = wm8741_resume, + .dapm_widgets = wm8741_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8741_dapm_widgets), + .dapm_routes = wm8741_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8741_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8741_of_match[] = { + { .compatible = "wlf,wm8741", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8741_of_match); + +static const struct regmap_config wm8741_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8741_MAX_REGISTER, + + .reg_defaults = wm8741_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8741_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int wm8741_set_pdata(struct device *dev, struct wm8741_priv *wm8741) +{ + const struct wm8741_platform_data *pdata = dev_get_platdata(dev); + u32 diff_mode; + + if (dev->of_node) { + if (of_property_read_u32(dev->of_node, "diff-mode", &diff_mode) + >= 0) + wm8741->pdata.diff_mode = diff_mode; + } else { + if (pdata != NULL) + memcpy(&wm8741->pdata, pdata, sizeof(wm8741->pdata)); + } + + return 0; +} + +#if IS_ENABLED(CONFIG_I2C) +static int wm8741_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8741_priv *wm8741; + int ret, i; + + wm8741 = devm_kzalloc(&i2c->dev, sizeof(struct wm8741_priv), + GFP_KERNEL); + if (wm8741 == NULL) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(wm8741->supplies); i++) + wm8741->supplies[i].supply = wm8741_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8741->supplies), + wm8741->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8741->regmap = devm_regmap_init_i2c(i2c, &wm8741_regmap); + if (IS_ERR(wm8741->regmap)) { + ret = PTR_ERR(wm8741->regmap); + dev_err(&i2c->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + ret = wm8741_set_pdata(&i2c->dev, wm8741); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to set pdata: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8741); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8741, &wm8741_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8741_i2c_id[] = { + { "wm8741", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8741_i2c_id); + +static struct i2c_driver wm8741_i2c_driver = { + .driver = { + .name = "wm8741", + .of_match_table = wm8741_of_match, + }, + .probe = wm8741_i2c_probe, + .id_table = wm8741_i2c_id, +}; +#endif + +#if defined(CONFIG_SPI_MASTER) +static int wm8741_spi_probe(struct spi_device *spi) +{ + struct wm8741_priv *wm8741; + int ret, i; + + wm8741 = devm_kzalloc(&spi->dev, sizeof(struct wm8741_priv), + GFP_KERNEL); + if (wm8741 == NULL) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(wm8741->supplies); i++) + wm8741->supplies[i].supply = wm8741_supply_names[i]; + + ret = devm_regulator_bulk_get(&spi->dev, ARRAY_SIZE(wm8741->supplies), + wm8741->supplies); + if (ret != 0) { + dev_err(&spi->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8741->regmap = devm_regmap_init_spi(spi, &wm8741_regmap); + if (IS_ERR(wm8741->regmap)) { + ret = PTR_ERR(wm8741->regmap); + dev_err(&spi->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + ret = wm8741_set_pdata(&spi->dev, wm8741); + if (ret != 0) { + dev_err(&spi->dev, "Failed to set pdata: %d\n", ret); + return ret; + } + + spi_set_drvdata(spi, wm8741); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8741, &wm8741_dai, 1); + return ret; +} + +static struct spi_driver wm8741_spi_driver = { + .driver = { + .name = "wm8741", + .of_match_table = wm8741_of_match, + }, + .probe = wm8741_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +static int __init wm8741_modinit(void) +{ + int ret = 0; + +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8741_i2c_driver); + if (ret != 0) + pr_err("Failed to register WM8741 I2C driver: %d\n", ret); +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8741_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8741 SPI driver: %d\n", + ret); + } +#endif + + return ret; +} +module_init(wm8741_modinit); + +static void __exit wm8741_exit(void) +{ +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8741_spi_driver); +#endif +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8741_i2c_driver); +#endif +} +module_exit(wm8741_exit); + +MODULE_DESCRIPTION("ASoC WM8741 driver"); +MODULE_AUTHOR("Ian Lartey "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8741.h b/sound/soc/codecs/wm8741.h new file mode 100644 index 000000000..c8835f65f --- /dev/null +++ b/sound/soc/codecs/wm8741.h @@ -0,0 +1,221 @@ +/* + * wm8741.h -- WM8423 ASoC driver + * + * Copyright 2010 Wolfson Microelectronics, plc + * + * Author: Ian Lartey + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8741_H +#define _WM8741_H + +/* + * Register values. + */ +#define WM8741_DACLLSB_ATTENUATION 0x00 +#define WM8741_DACLMSB_ATTENUATION 0x01 +#define WM8741_DACRLSB_ATTENUATION 0x02 +#define WM8741_DACRMSB_ATTENUATION 0x03 +#define WM8741_VOLUME_CONTROL 0x04 +#define WM8741_FORMAT_CONTROL 0x05 +#define WM8741_FILTER_CONTROL 0x06 +#define WM8741_MODE_CONTROL_1 0x07 +#define WM8741_MODE_CONTROL_2 0x08 +#define WM8741_RESET 0x09 +#define WM8741_ADDITIONAL_CONTROL_1 0x20 + +#define WM8741_REGISTER_COUNT 11 +#define WM8741_MAX_REGISTER 0x20 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - DACLLSB_ATTENUATION + */ +#define WM8741_UPDATELL 0x0020 /* UPDATELL */ +#define WM8741_UPDATELL_MASK 0x0020 /* UPDATELL */ +#define WM8741_UPDATELL_SHIFT 5 /* UPDATELL */ +#define WM8741_UPDATELL_WIDTH 1 /* UPDATELL */ +#define WM8741_LAT_4_0_MASK 0x001F /* LAT[4:0] - [4:0] */ +#define WM8741_LAT_4_0_SHIFT 0 /* LAT[4:0] - [4:0] */ +#define WM8741_LAT_4_0_WIDTH 5 /* LAT[4:0] - [4:0] */ + +/* + * R1 (0x01) - DACLMSB_ATTENUATION + */ +#define WM8741_UPDATELM 0x0020 /* UPDATELM */ +#define WM8741_UPDATELM_MASK 0x0020 /* UPDATELM */ +#define WM8741_UPDATELM_SHIFT 5 /* UPDATELM */ +#define WM8741_UPDATELM_WIDTH 1 /* UPDATELM */ +#define WM8741_LAT_9_5_0_MASK 0x001F /* LAT[9:5] - [4:0] */ +#define WM8741_LAT_9_5_0_SHIFT 0 /* LAT[9:5] - [4:0] */ +#define WM8741_LAT_9_5_0_WIDTH 5 /* LAT[9:5] - [4:0] */ + +/* + * R2 (0x02) - DACRLSB_ATTENUATION + */ +#define WM8741_UPDATERL 0x0020 /* UPDATERL */ +#define WM8741_UPDATERL_MASK 0x0020 /* UPDATERL */ +#define WM8741_UPDATERL_SHIFT 5 /* UPDATERL */ +#define WM8741_UPDATERL_WIDTH 1 /* UPDATERL */ +#define WM8741_RAT_4_0_MASK 0x001F /* RAT[4:0] - [4:0] */ +#define WM8741_RAT_4_0_SHIFT 0 /* RAT[4:0] - [4:0] */ +#define WM8741_RAT_4_0_WIDTH 5 /* RAT[4:0] - [4:0] */ + +/* + * R3 (0x03) - DACRMSB_ATTENUATION + */ +#define WM8741_UPDATERM 0x0020 /* UPDATERM */ +#define WM8741_UPDATERM_MASK 0x0020 /* UPDATERM */ +#define WM8741_UPDATERM_SHIFT 5 /* UPDATERM */ +#define WM8741_UPDATERM_WIDTH 1 /* UPDATERM */ +#define WM8741_RAT_9_5_0_MASK 0x001F /* RAT[9:5] - [4:0] */ +#define WM8741_RAT_9_5_0_SHIFT 0 /* RAT[9:5] - [4:0] */ +#define WM8741_RAT_9_5_0_WIDTH 5 /* RAT[9:5] - [4:0] */ + +/* + * R4 (0x04) - VOLUME_CONTROL + */ +#define WM8741_AMUTE 0x0080 /* AMUTE */ +#define WM8741_AMUTE_MASK 0x0080 /* AMUTE */ +#define WM8741_AMUTE_SHIFT 7 /* AMUTE */ +#define WM8741_AMUTE_WIDTH 1 /* AMUTE */ +#define WM8741_ZFLAG_MASK 0x0060 /* ZFLAG - [6:5] */ +#define WM8741_ZFLAG_SHIFT 5 /* ZFLAG - [6:5] */ +#define WM8741_ZFLAG_WIDTH 2 /* ZFLAG - [6:5] */ +#define WM8741_IZD 0x0010 /* IZD */ +#define WM8741_IZD_MASK 0x0010 /* IZD */ +#define WM8741_IZD_SHIFT 4 /* IZD */ +#define WM8741_IZD_WIDTH 1 /* IZD */ +#define WM8741_SOFT 0x0008 /* SOFT MUTE */ +#define WM8741_SOFT_MASK 0x0008 /* SOFT MUTE */ +#define WM8741_SOFT_SHIFT 3 /* SOFT MUTE */ +#define WM8741_SOFT_WIDTH 1 /* SOFT MUTE */ +#define WM8741_ATC 0x0004 /* ATC */ +#define WM8741_ATC_MASK 0x0004 /* ATC */ +#define WM8741_ATC_SHIFT 2 /* ATC */ +#define WM8741_ATC_WIDTH 1 /* ATC */ +#define WM8741_ATT2DB 0x0002 /* ATT2DB */ +#define WM8741_ATT2DB_MASK 0x0002 /* ATT2DB */ +#define WM8741_ATT2DB_SHIFT 1 /* ATT2DB */ +#define WM8741_ATT2DB_WIDTH 1 /* ATT2DB */ +#define WM8741_VOL_RAMP 0x0001 /* VOL_RAMP */ +#define WM8741_VOL_RAMP_MASK 0x0001 /* VOL_RAMP */ +#define WM8741_VOL_RAMP_SHIFT 0 /* VOL_RAMP */ +#define WM8741_VOL_RAMP_WIDTH 1 /* VOL_RAMP */ + +/* + * R5 (0x05) - FORMAT_CONTROL + */ +#define WM8741_PWDN 0x0080 /* PWDN */ +#define WM8741_PWDN_MASK 0x0080 /* PWDN */ +#define WM8741_PWDN_SHIFT 7 /* PWDN */ +#define WM8741_PWDN_WIDTH 1 /* PWDN */ +#define WM8741_REV 0x0040 /* REV */ +#define WM8741_REV_MASK 0x0040 /* REV */ +#define WM8741_REV_SHIFT 6 /* REV */ +#define WM8741_REV_WIDTH 1 /* REV */ +#define WM8741_BCP 0x0020 /* BCP */ +#define WM8741_BCP_MASK 0x0020 /* BCP */ +#define WM8741_BCP_SHIFT 5 /* BCP */ +#define WM8741_BCP_WIDTH 1 /* BCP */ +#define WM8741_LRP 0x0010 /* LRP */ +#define WM8741_LRP_MASK 0x0010 /* LRP */ +#define WM8741_LRP_SHIFT 4 /* LRP */ +#define WM8741_LRP_WIDTH 1 /* LRP */ +#define WM8741_FMT_MASK 0x000C /* FMT - [3:2] */ +#define WM8741_FMT_SHIFT 2 /* FMT - [3:2] */ +#define WM8741_FMT_WIDTH 2 /* FMT - [3:2] */ +#define WM8741_IWL_MASK 0x0003 /* IWL - [1:0] */ +#define WM8741_IWL_SHIFT 0 /* IWL - [1:0] */ +#define WM8741_IWL_WIDTH 2 /* IWL - [1:0] */ + +/* + * R6 (0x06) - FILTER_CONTROL + */ +#define WM8741_ZFLAG_HI 0x0080 /* ZFLAG_HI */ +#define WM8741_ZFLAG_HI_MASK 0x0080 /* ZFLAG_HI */ +#define WM8741_ZFLAG_HI_SHIFT 7 /* ZFLAG_HI */ +#define WM8741_ZFLAG_HI_WIDTH 1 /* ZFLAG_HI */ +#define WM8741_DEEMPH_MASK 0x0060 /* DEEMPH - [6:5] */ +#define WM8741_DEEMPH_SHIFT 5 /* DEEMPH - [6:5] */ +#define WM8741_DEEMPH_WIDTH 2 /* DEEMPH - [6:5] */ +#define WM8741_DSDFILT_MASK 0x0018 /* DSDFILT - [4:3] */ +#define WM8741_DSDFILT_SHIFT 3 /* DSDFILT - [4:3] */ +#define WM8741_DSDFILT_WIDTH 2 /* DSDFILT - [4:3] */ +#define WM8741_FIRSEL_MASK 0x0007 /* FIRSEL - [2:0] */ +#define WM8741_FIRSEL_SHIFT 0 /* FIRSEL - [2:0] */ +#define WM8741_FIRSEL_WIDTH 3 /* FIRSEL - [2:0] */ + +/* + * R7 (0x07) - MODE_CONTROL_1 + */ +#define WM8741_MODE8X 0x0080 /* MODE8X */ +#define WM8741_MODE8X_MASK 0x0080 /* MODE8X */ +#define WM8741_MODE8X_SHIFT 7 /* MODE8X */ +#define WM8741_MODE8X_WIDTH 1 /* MODE8X */ +#define WM8741_OSR_MASK 0x0060 /* OSR - [6:5] */ +#define WM8741_OSR_SHIFT 5 /* OSR - [6:5] */ +#define WM8741_OSR_WIDTH 2 /* OSR - [6:5] */ +#define WM8741_SR_MASK 0x001C /* SR - [4:2] */ +#define WM8741_SR_SHIFT 2 /* SR - [4:2] */ +#define WM8741_SR_WIDTH 3 /* SR - [4:2] */ +#define WM8741_MODESEL_MASK 0x0003 /* MODESEL - [1:0] */ +#define WM8741_MODESEL_SHIFT 0 /* MODESEL - [1:0] */ +#define WM8741_MODESEL_WIDTH 2 /* MODESEL - [1:0] */ + +/* + * R8 (0x08) - MODE_CONTROL_2 + */ +#define WM8741_DSD_GAIN 0x0040 /* DSD_GAIN */ +#define WM8741_DSD_GAIN_MASK 0x0040 /* DSD_GAIN */ +#define WM8741_DSD_GAIN_SHIFT 6 /* DSD_GAIN */ +#define WM8741_DSD_GAIN_WIDTH 1 /* DSD_GAIN */ +#define WM8741_SDOUT 0x0020 /* SDOUT */ +#define WM8741_SDOUT_MASK 0x0020 /* SDOUT */ +#define WM8741_SDOUT_SHIFT 5 /* SDOUT */ +#define WM8741_SDOUT_WIDTH 1 /* SDOUT */ +#define WM8741_DOUT 0x0010 /* DOUT */ +#define WM8741_DOUT_MASK 0x0010 /* DOUT */ +#define WM8741_DOUT_SHIFT 4 /* DOUT */ +#define WM8741_DOUT_WIDTH 1 /* DOUT */ +#define WM8741_DIFF_MASK 0x000C /* DIFF - [3:2] */ +#define WM8741_DIFF_SHIFT 2 /* DIFF - [3:2] */ +#define WM8741_DIFF_WIDTH 2 /* DIFF - [3:2] */ +#define WM8741_DITHER_MASK 0x0003 /* DITHER - [1:0] */ +#define WM8741_DITHER_SHIFT 0 /* DITHER - [1:0] */ +#define WM8741_DITHER_WIDTH 2 /* DITHER - [1:0] */ + +/* DIFF field values */ +#define WM8741_DIFF_MODE_STEREO 0 /* stereo normal */ +#define WM8741_DIFF_MODE_STEREO_REVERSED 2 /* stereo reversed */ +#define WM8741_DIFF_MODE_MONO_LEFT 1 /* mono left */ +#define WM8741_DIFF_MODE_MONO_RIGHT 3 /* mono right */ + +/* + * R32 (0x20) - ADDITONAL_CONTROL_1 + */ +#define WM8741_DSD_LEVEL 0x0002 /* DSD_LEVEL */ +#define WM8741_DSD_LEVEL_MASK 0x0002 /* DSD_LEVEL */ +#define WM8741_DSD_LEVEL_SHIFT 1 /* DSD_LEVEL */ +#define WM8741_DSD_LEVEL_WIDTH 1 /* DSD_LEVEL */ +#define WM8741_DSD_NO_NOTCH 0x0001 /* DSD_NO_NOTCH */ +#define WM8741_DSD_NO_NOTCH_MASK 0x0001 /* DSD_NO_NOTCH */ +#define WM8741_DSD_NO_NOTCH_SHIFT 0 /* DSD_NO_NOTCH */ +#define WM8741_DSD_NO_NOTCH_WIDTH 1 /* DSD_NO_NOTCH */ + +#define WM8741_SYSCLK 0 + +struct wm8741_platform_data { + u32 diff_mode; /* Differential Output Mode */ +}; + +#endif diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c new file mode 100644 index 000000000..97239bc9d --- /dev/null +++ b/sound/soc/codecs/wm8750.c @@ -0,0 +1,859 @@ +/* + * wm8750.c -- WM8750 ALSA SoC audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on WM8753.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8750.h" + +/* + * wm8750 register cache + * We can't read the WM8750 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8750_reg_defaults[] = { + { 0, 0x0097 }, + { 1, 0x0097 }, + { 2, 0x0079 }, + { 3, 0x0079 }, + { 4, 0x0000 }, + { 5, 0x0008 }, + { 6, 0x0000 }, + { 7, 0x000a }, + { 8, 0x0000 }, + { 9, 0x0000 }, + { 10, 0x00ff }, + { 11, 0x00ff }, + { 12, 0x000f }, + { 13, 0x000f }, + { 14, 0x0000 }, + { 15, 0x0000 }, + { 16, 0x0000 }, + { 17, 0x007b }, + { 18, 0x0000 }, + { 19, 0x0032 }, + { 20, 0x0000 }, + { 21, 0x00c3 }, + { 22, 0x00c3 }, + { 23, 0x00c0 }, + { 24, 0x0000 }, + { 25, 0x0000 }, + { 26, 0x0000 }, + { 27, 0x0000 }, + { 28, 0x0000 }, + { 29, 0x0000 }, + { 30, 0x0000 }, + { 31, 0x0000 }, + { 32, 0x0000 }, + { 33, 0x0000 }, + { 34, 0x0050 }, + { 35, 0x0050 }, + { 36, 0x0050 }, + { 37, 0x0050 }, + { 38, 0x0050 }, + { 39, 0x0050 }, + { 40, 0x0079 }, + { 41, 0x0079 }, + { 42, 0x0079 }, +}; + +/* codec private data */ +struct wm8750_priv { + unsigned int sysclk; +}; + +#define wm8750_reset(c) snd_soc_component_write(c, WM8750_RESET, 0) + +/* + * WM8750 Controls + */ +static const char *wm8750_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm8750_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" }; +static const char *wm8750_treble[] = {"8kHz", "4kHz"}; +static const char *wm8750_3d_lc[] = {"200Hz", "500Hz"}; +static const char *wm8750_3d_uc[] = {"2.2kHz", "1.5kHz"}; +static const char *wm8750_3d_func[] = {"Capture", "Playback"}; +static const char *wm8750_alc_func[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8750_ng_type[] = {"Constant PGA Gain", + "Mute ADC Output"}; +static const char *wm8750_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA", + "Differential"}; +static const char *wm8750_pga_sel[] = {"Line 1", "Line 2", "Line 3", + "Differential"}; +static const char *wm8750_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut", + "ROUT1"}; +static const char *wm8750_diff_sel[] = {"Line 1", "Line 2"}; +static const char *wm8750_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static const char *wm8750_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *wm8750_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; + +static const struct soc_enum wm8750_enum[] = { +SOC_ENUM_SINGLE(WM8750_BASS, 7, 2, wm8750_bass), +SOC_ENUM_SINGLE(WM8750_BASS, 6, 2, wm8750_bass_filter), +SOC_ENUM_SINGLE(WM8750_TREBLE, 6, 2, wm8750_treble), +SOC_ENUM_SINGLE(WM8750_3D, 5, 2, wm8750_3d_lc), +SOC_ENUM_SINGLE(WM8750_3D, 6, 2, wm8750_3d_uc), +SOC_ENUM_SINGLE(WM8750_3D, 7, 2, wm8750_3d_func), +SOC_ENUM_SINGLE(WM8750_ALC1, 7, 4, wm8750_alc_func), +SOC_ENUM_SINGLE(WM8750_NGATE, 1, 2, wm8750_ng_type), +SOC_ENUM_SINGLE(WM8750_LOUTM1, 0, 5, wm8750_line_mux), +SOC_ENUM_SINGLE(WM8750_ROUTM1, 0, 5, wm8750_line_mux), +SOC_ENUM_SINGLE(WM8750_LADCIN, 6, 4, wm8750_pga_sel), /* 10 */ +SOC_ENUM_SINGLE(WM8750_RADCIN, 6, 4, wm8750_pga_sel), +SOC_ENUM_SINGLE(WM8750_ADCTL2, 7, 4, wm8750_out3), +SOC_ENUM_SINGLE(WM8750_ADCIN, 8, 2, wm8750_diff_sel), +SOC_ENUM_SINGLE(WM8750_ADCDAC, 5, 4, wm8750_adcpol), +SOC_ENUM_SINGLE(WM8750_ADCDAC, 1, 4, wm8750_deemph), +SOC_ENUM_SINGLE(WM8750_ADCIN, 6, 4, wm8750_mono_mux), /* 16 */ + +}; + +static const struct snd_kcontrol_new wm8750_snd_controls[] = { + +SOC_DOUBLE_R("Capture Volume", WM8750_LINVOL, WM8750_RINVOL, 0, 63, 0), +SOC_DOUBLE_R("Capture ZC Switch", WM8750_LINVOL, WM8750_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8750_LINVOL, WM8750_RINVOL, 7, 1, 1), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8750_LOUT1V, + WM8750_ROUT1V, 7, 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8750_LOUT2V, + WM8750_ROUT2V, 7, 1, 0), + +SOC_ENUM("Playback De-emphasis", wm8750_enum[15]), + +SOC_ENUM("Capture Polarity", wm8750_enum[14]), +SOC_SINGLE("Playback 6dB Attenuate", WM8750_ADCDAC, 7, 1, 0), +SOC_SINGLE("Capture 6dB Attenuate", WM8750_ADCDAC, 8, 1, 0), + +SOC_DOUBLE_R("PCM Volume", WM8750_LDAC, WM8750_RDAC, 0, 255, 0), + +SOC_ENUM("Bass Boost", wm8750_enum[0]), +SOC_ENUM("Bass Filter", wm8750_enum[1]), +SOC_SINGLE("Bass Volume", WM8750_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 1), +SOC_ENUM("Treble Cut-off", wm8750_enum[2]), + +SOC_SINGLE("3D Switch", WM8750_3D, 0, 1, 0), +SOC_SINGLE("3D Volume", WM8750_3D, 1, 15, 0), +SOC_ENUM("3D Lower Cut-off", wm8750_enum[3]), +SOC_ENUM("3D Upper Cut-off", wm8750_enum[4]), +SOC_ENUM("3D Mode", wm8750_enum[5]), + +SOC_SINGLE("ALC Capture Target Volume", WM8750_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8750_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", wm8750_enum[6]), +SOC_SINGLE("ALC Capture ZC Switch", WM8750_ALC2, 7, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0), +SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]), +SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0), + +SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0), +SOC_SINGLE("Right ADC Capture Volume", WM8750_RADC, 0, 255, 0), + +SOC_SINGLE("ZC Timeout Switch", WM8750_ADCTL1, 0, 1, 0), +SOC_SINGLE("Playback Invert Switch", WM8750_ADCTL1, 1, 1, 0), + +SOC_SINGLE("Right Speaker Playback Invert Switch", WM8750_ADCTL2, 4, 1, 0), + +/* Unimplemented */ +/* ADCDAC Bit 0 - ADCHPD */ +/* ADCDAC Bit 4 - HPOR */ +/* ADCTL1 Bit 2,3 - DATSEL */ +/* ADCTL1 Bit 4,5 - DMONOMIX */ +/* ADCTL1 Bit 6,7 - VSEL */ +/* ADCTL2 Bit 2 - LRCM */ +/* ADCTL2 Bit 3 - TRI */ +/* ADCTL3 Bit 5 - HPFLREN */ +/* ADCTL3 Bit 6 - VROI */ +/* ADCTL3 Bit 7,8 - ADCLRM */ +/* ADCIN Bit 4 - LDCM */ +/* ADCIN Bit 5 - RDCM */ + +SOC_DOUBLE_R("Mic Boost", WM8750_LADCIN, WM8750_RADCIN, 4, 3, 0), + +SOC_DOUBLE_R("Bypass Left Playback Volume", WM8750_LOUTM1, + WM8750_LOUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Right Playback Volume", WM8750_ROUTM1, + WM8750_ROUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8750_MOUTM1, + WM8750_MOUTM2, 4, 7, 1), + +SOC_SINGLE("Mono Playback ZC Switch", WM8750_MOUTV, 7, 1, 0), + +SOC_DOUBLE_R("Headphone Playback Volume", WM8750_LOUT1V, WM8750_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Speaker Playback Volume", WM8750_LOUT2V, WM8750_ROUT2V, + 0, 127, 0), + +SOC_SINGLE("Mono Playback Volume", WM8750_MOUTV, 0, 127, 0), + +}; + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8750_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8750_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8750_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8750_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8750_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8750_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8750_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8750_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8750_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8750_left_line_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8750_right_line_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8750_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8750_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[11]); + +/* Out 3 Mux */ +static const struct snd_kcontrol_new wm8750_out3_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[12]); + +/* Differential Mux */ +static const struct snd_kcontrol_new wm8750_diffmux_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[13]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8750_monomux_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[16]); + +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8750_left_mixer_controls[0], + ARRAY_SIZE(wm8750_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8750_right_mixer_controls[0], + ARRAY_SIZE(wm8750_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8750_PWR2, 2, 0, + &wm8750_mono_mixer_controls[0], + ARRAY_SIZE(wm8750_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8750_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8750_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8750_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8750_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8750_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8750_PWR2, 8, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8750_PWR1, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8750_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8750_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8750_PWR1, 5, 0, + &wm8750_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8750_PWR1, 4, 0, + &wm8750_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8750_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8750_right_line_controls), + + SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8750_out3_controls), + SND_SOC_DAPM_PGA("Out 3", WM8750_PWR2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8750_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &wm8750_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8750_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8750_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO1"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_VMID("VREF"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("LINPUT3"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT3"), +}; + +static const struct snd_soc_dapm_route wm8750_dapm_routes[] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out 1", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out 1"}, + + /* out 3 */ + {"Out3 Mux", "VREF", "VREF"}, + {"Out3 Mux", "ROUT1 + Vol", "ROUT1"}, + {"Out3 Mux", "ROUT1", "Right Mixer"}, + {"Out3 Mux", "MonoOut", "MONO1"}, + {"Out 3", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line 1", "LINPUT1"}, + {"Left Line Mux", "Line 2", "LINPUT2"}, + {"Left Line Mux", "Line 3", "LINPUT3"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line 1", "RINPUT1"}, + {"Right Line Mux", "Line 2", "RINPUT2"}, + {"Right Line Mux", "Line 3", "RINPUT3"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line 1", "LINPUT1"}, + {"Left PGA Mux", "Line 2", "LINPUT2"}, + {"Left PGA Mux", "Line 3", "LINPUT3"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line 1", "RINPUT1"}, + {"Right PGA Mux", "Line 2", "RINPUT2"}, + {"Right PGA Mux", "Line 3", "RINPUT3"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line 1", "LINPUT1"}, + {"Differential Mux", "Line 1", "RINPUT1"}, + {"Differential Mux", "Line 2", "LINPUT2"}, + {"Differential Mux", "Line 2", "RINPUT2"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + + printk(KERN_ERR "wm8750: could not get coeff for mclk %d @ rate %d\n", + mclk, rate); + return -EINVAL; +} + +static int wm8750_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8750_priv *wm8750 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8750->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8750_IFACE, iface); + return 0; +} + +static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8750_priv *wm8750 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8750_IFACE) & 0x1f3; + u16 srate = snd_soc_component_read32(component, WM8750_SRATE) & 0x1c0; + int coeff = get_coeff(wm8750->sysclk, params_rate(params)); + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + snd_soc_component_write(component, WM8750_IFACE, iface); + if (coeff >= 0) + snd_soc_component_write(component, WM8750_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8750_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8750_ADCDAC) & 0xfff7; + + if (mute) + snd_soc_component_write(component, WM8750_ADCDAC, mute_reg | 0x8); + else + snd_soc_component_write(component, WM8750_ADCDAC, mute_reg); + return 0; +} + +static int wm8750_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = snd_soc_component_read32(component, WM8750_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + snd_soc_component_write(component, WM8750_PWR1, pwr_reg | 0x00c0); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_cache_sync(component); + + /* Set VMID to 5k */ + snd_soc_component_write(component, WM8750_PWR1, pwr_reg | 0x01c1); + + /* ...and ramp */ + msleep(1000); + } + + /* mute dac and set vmid to 500k, enable VREF */ + snd_soc_component_write(component, WM8750_PWR1, pwr_reg | 0x0141); + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, WM8750_PWR1, 0x0001); + break; + } + return 0; +} + +#define WM8750_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8750_dai_ops = { + .hw_params = wm8750_pcm_hw_params, + .digital_mute = wm8750_mute, + .set_fmt = wm8750_set_dai_fmt, + .set_sysclk = wm8750_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver wm8750_dai = { + .name = "wm8750-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8750_RATES, + .formats = WM8750_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8750_RATES, + .formats = WM8750_FORMATS,}, + .ops = &wm8750_dai_ops, +}; + +static int wm8750_probe(struct snd_soc_component *component) +{ + int ret; + + ret = wm8750_reset(component); + if (ret < 0) { + printk(KERN_ERR "wm8750: failed to reset: %d\n", ret); + return ret; + } + + /* set the update bits */ + snd_soc_component_update_bits(component, WM8750_LDAC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8750_RDAC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8750_LOUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8750_ROUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8750_LOUT2V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8750_ROUT2V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8750_LINVOL, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8750_RINVOL, 0x0100, 0x0100); + + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8750 = { + .probe = wm8750_probe, + .set_bias_level = wm8750_set_bias_level, + .controls = wm8750_snd_controls, + .num_controls = ARRAY_SIZE(wm8750_snd_controls), + .dapm_widgets = wm8750_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets), + .dapm_routes = wm8750_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8750_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8750_of_match[] = { + { .compatible = "wlf,wm8750", }, + { .compatible = "wlf,wm8987", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8750_of_match); + +static const struct regmap_config wm8750_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8750_MOUTV, + + .reg_defaults = wm8750_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8750_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8750_spi_probe(struct spi_device *spi) +{ + struct wm8750_priv *wm8750; + struct regmap *regmap; + int ret; + + wm8750 = devm_kzalloc(&spi->dev, sizeof(struct wm8750_priv), + GFP_KERNEL); + if (wm8750 == NULL) + return -ENOMEM; + + regmap = devm_regmap_init_spi(spi, &wm8750_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + spi_set_drvdata(spi, wm8750); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8750, &wm8750_dai, 1); + return ret; +} + +static const struct spi_device_id wm8750_spi_ids[] = { + { "wm8750", 0 }, + { "wm8987", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, wm8750_spi_ids); + +static struct spi_driver wm8750_spi_driver = { + .driver = { + .name = "wm8750", + .of_match_table = wm8750_of_match, + }, + .id_table = wm8750_spi_ids, + .probe = wm8750_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8750_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8750_priv *wm8750; + struct regmap *regmap; + int ret; + + wm8750 = devm_kzalloc(&i2c->dev, sizeof(struct wm8750_priv), + GFP_KERNEL); + if (wm8750 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8750); + + regmap = devm_regmap_init_i2c(i2c, &wm8750_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8750, &wm8750_dai, 1); + return ret; +} + +static const struct i2c_device_id wm8750_i2c_id[] = { + { "wm8750", 0 }, + { "wm8987", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id); + +static struct i2c_driver wm8750_i2c_driver = { + .driver = { + .name = "wm8750", + .of_match_table = wm8750_of_match, + }, + .probe = wm8750_i2c_probe, + .id_table = wm8750_i2c_id, +}; +#endif + +static int __init wm8750_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8750_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8750 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8750_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8750 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8750_modinit); + +static void __exit wm8750_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8750_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8750_spi_driver); +#endif +} +module_exit(wm8750_exit); + +MODULE_DESCRIPTION("ASoC WM8750 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h new file mode 100644 index 000000000..121427c04 --- /dev/null +++ b/sound/soc/codecs/wm8750.h @@ -0,0 +1,60 @@ +/* + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on WM8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _WM8750_H +#define _WM8750_H + +/* WM8750 register space */ + +#define WM8750_LINVOL 0x00 +#define WM8750_RINVOL 0x01 +#define WM8750_LOUT1V 0x02 +#define WM8750_ROUT1V 0x03 +#define WM8750_ADCDAC 0x05 +#define WM8750_IFACE 0x07 +#define WM8750_SRATE 0x08 +#define WM8750_LDAC 0x0a +#define WM8750_RDAC 0x0b +#define WM8750_BASS 0x0c +#define WM8750_TREBLE 0x0d +#define WM8750_RESET 0x0f +#define WM8750_3D 0x10 +#define WM8750_ALC1 0x11 +#define WM8750_ALC2 0x12 +#define WM8750_ALC3 0x13 +#define WM8750_NGATE 0x14 +#define WM8750_LADC 0x15 +#define WM8750_RADC 0x16 +#define WM8750_ADCTL1 0x17 +#define WM8750_ADCTL2 0x18 +#define WM8750_PWR1 0x19 +#define WM8750_PWR2 0x1a +#define WM8750_ADCTL3 0x1b +#define WM8750_ADCIN 0x1f +#define WM8750_LADCIN 0x20 +#define WM8750_RADCIN 0x21 +#define WM8750_LOUTM1 0x22 +#define WM8750_LOUTM2 0x23 +#define WM8750_ROUTM1 0x24 +#define WM8750_ROUTM2 0x25 +#define WM8750_MOUTM1 0x26 +#define WM8750_MOUTM2 0x27 +#define WM8750_LOUT2V 0x28 +#define WM8750_ROUT2V 0x29 +#define WM8750_MOUTV 0x2a + +#define WM8750_CACHE_REGNUM 0x2a + +#define WM8750_SYSCLK 0 + +#endif diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c new file mode 100644 index 000000000..1e2823e2a --- /dev/null +++ b/sound/soc/codecs/wm8753.c @@ -0,0 +1,1635 @@ +/* + * wm8753.c -- WM8753 ALSA Soc Audio driver + * + * Copyright 2003-11 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * The WM8753 is a low power, high quality stereo codec with integrated PCM + * codec designed for portable digital telephony applications. + * + * Dual DAI:- + * + * This driver support 2 DAI PCM's. This makes the default PCM available for + * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for + * voice. + * + * Please note that the voice PCM can be connected directly to a Bluetooth + * codec or GSM modem and thus cannot be read or written to, although it is + * available to be configured with snd_hw_params(), etc and kcontrols in the + * normal alsa manner. + * + * Fast DAI switching:- + * + * The driver can now fast switch between the DAI configurations via a + * an alsa kcontrol. This allows the PCM to remain open. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8753.h" + +static int caps_charge = 2000; +module_param(caps_charge, int, 0); +MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); + +static int wm8753_hifi_write_dai_fmt(struct snd_soc_component *component, + unsigned int fmt); +static int wm8753_voice_write_dai_fmt(struct snd_soc_component *component, + unsigned int fmt); + +/* + * wm8753 register cache + * We can't read the WM8753 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8753_reg_defaults[] = { + { 0x00, 0x0000 }, + { 0x01, 0x0008 }, + { 0x02, 0x0000 }, + { 0x03, 0x000a }, + { 0x04, 0x000a }, + { 0x05, 0x0033 }, + { 0x06, 0x0000 }, + { 0x07, 0x0007 }, + { 0x08, 0x00ff }, + { 0x09, 0x00ff }, + { 0x0a, 0x000f }, + { 0x0b, 0x000f }, + { 0x0c, 0x007b }, + { 0x0d, 0x0000 }, + { 0x0e, 0x0032 }, + { 0x0f, 0x0000 }, + { 0x10, 0x00c3 }, + { 0x11, 0x00c3 }, + { 0x12, 0x00c0 }, + { 0x13, 0x0000 }, + { 0x14, 0x0000 }, + { 0x15, 0x0000 }, + { 0x16, 0x0000 }, + { 0x17, 0x0000 }, + { 0x18, 0x0000 }, + { 0x19, 0x0000 }, + { 0x1a, 0x0000 }, + { 0x1b, 0x0000 }, + { 0x1c, 0x0000 }, + { 0x1d, 0x0000 }, + { 0x1e, 0x0000 }, + { 0x1f, 0x0000 }, + { 0x20, 0x0055 }, + { 0x21, 0x0005 }, + { 0x22, 0x0050 }, + { 0x23, 0x0055 }, + { 0x24, 0x0050 }, + { 0x25, 0x0055 }, + { 0x26, 0x0050 }, + { 0x27, 0x0055 }, + { 0x28, 0x0079 }, + { 0x29, 0x0079 }, + { 0x2a, 0x0079 }, + { 0x2b, 0x0079 }, + { 0x2c, 0x0079 }, + { 0x2d, 0x0000 }, + { 0x2e, 0x0000 }, + { 0x2f, 0x0000 }, + { 0x30, 0x0000 }, + { 0x31, 0x0097 }, + { 0x32, 0x0097 }, + { 0x33, 0x0000 }, + { 0x34, 0x0004 }, + { 0x35, 0x0000 }, + { 0x36, 0x0083 }, + { 0x37, 0x0024 }, + { 0x38, 0x01ba }, + { 0x39, 0x0000 }, + { 0x3a, 0x0083 }, + { 0x3b, 0x0024 }, + { 0x3c, 0x01ba }, + { 0x3d, 0x0000 }, + { 0x3e, 0x0000 }, + { 0x3f, 0x0000 }, +}; + +static bool wm8753_volatile(struct device *dev, unsigned int reg) +{ + return reg == WM8753_RESET; +} + +/* codec private data */ +struct wm8753_priv { + struct regmap *regmap; + unsigned int sysclk; + unsigned int pcmclk; + + unsigned int voice_fmt; + unsigned int hifi_fmt; + + int dai_func; + struct delayed_work charge_work; +}; + +#define wm8753_reset(c) snd_soc_component_write(c, WM8753_RESET, 0) + +/* + * WM8753 Controls + */ +static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm8753_base_filter[] = + {"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz", + "100Hz @ 8kHz", "200Hz @ 8kHz"}; +static const char *wm8753_treble[] = {"8kHz", "4kHz"}; +static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"}; +static const char *wm8753_3d_func[] = {"Capture", "Playback"}; +static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"}; +static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"}; +static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"}; +static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"}; +static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"}; +static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2", + "Line 1", "Line 2"}; +static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"}; +static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"}; +static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"}; +static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"}; +static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2", + "Right PGA"}; +static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right", + "Left + Right"}; +static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"}; +static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"}; +static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"}; +static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"}; +static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left", + "Analogue Mix Right", "Digital Mono Mix"}; +static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k", + "82Hz @ 8kHz", "170Hz @ 8kHz"}; +static const char *wm8753_adc_filter[] = {"HiFi", "Voice"}; +static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"}; +static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"}; +static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC", + "Channel Swap"}; +static const char *wm8753_rout2_phase[] = {"Non Inverted", "Inverted"}; + +static const struct soc_enum wm8753_enum[] = { +SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base), +SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter), +SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble), +SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func), +SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type), +SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func), +SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc), +SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc), +SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp), +SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix), +SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase), +SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix), +SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux), +SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux), +SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux), +SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel), +SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux), +SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src), +SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3), +SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4), +SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel), +SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel), +SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc), +SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp), +SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter), +SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel), +SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode), +SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel), +SOC_ENUM_SINGLE(WM8753_OUTCTL, 2, 2, wm8753_rout2_phase), +}; + + +static int wm8753_get_dai(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wm8753->dai_func; + return 0; +} + +static int wm8753_set_dai(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + u16 ioctl; + + if (wm8753->dai_func == ucontrol->value.enumerated.item[0]) + return 0; + + if (snd_soc_component_is_active(component)) + return -EBUSY; + + ioctl = snd_soc_component_read32(component, WM8753_IOCTL); + + wm8753->dai_func = ucontrol->value.enumerated.item[0]; + + if (((ioctl >> 2) & 0x3) == wm8753->dai_func) + return 1; + + ioctl = (ioctl & 0x1f3) | (wm8753->dai_func << 2); + snd_soc_component_write(component, WM8753_IOCTL, ioctl); + + + wm8753_hifi_write_dai_fmt(component, wm8753->hifi_fmt); + wm8753_voice_write_dai_fmt(component, wm8753->voice_fmt); + + return 1; +} + +static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_RANGE(out_tlv, + /* 0000000 - 0101111 = "Analogue mute" */ + 0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0), + 48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0) +); +static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0); + +static const struct snd_kcontrol_new wm8753_snd_controls[] = { +SOC_SINGLE("Hi-Fi DAC Left/Right channel Swap", WM8753_HIFI, 5, 1, 0), +SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0, + adc_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, + 127, 0, out_tlv), + +SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv), + +SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, + 1, mix_tlv), +SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, + 7, 1, mix_tlv), +SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, + 1, voice_mix_tlv), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, + 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, + 1, 0), + +SOC_SINGLE_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv), +SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1, + mix_tlv), +SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1, + voice_mix_tlv), +SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0), + +SOC_ENUM("Bass Boost", wm8753_enum[0]), +SOC_ENUM("Bass Filter", wm8753_enum[1]), +SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), +SOC_ENUM("Treble Cut-off", wm8753_enum[2]), + +SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, + rec_mix_tlv), +SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, + rec_mix_tlv), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0, + pga_tlv), +SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1), + +SOC_ENUM("Capture Filter Select", wm8753_enum[23]), +SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]), +SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1), + +SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", wm8753_enum[3]), +SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1), +SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1), +SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]), +SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0), + +SOC_ENUM("3D Function", wm8753_enum[5]), +SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]), +SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]), +SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0), + +SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0), +SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0), + +SOC_ENUM("De-emphasis", wm8753_enum[8]), +SOC_ENUM("Playback Mono Mix", wm8753_enum[9]), +SOC_ENUM("Playback Phase", wm8753_enum[10]), + +SOC_SINGLE_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv), +SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv), + +SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai), + +SOC_ENUM("ADC Data Select", wm8753_enum[27]), +SOC_ENUM("ROUT2 Phase", wm8753_enum[28]), +}; + +/* + * _DAPM_ Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0), +}; + +/* Right mixer */ +static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0), +}; + +/* Mono mixer */ +static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0), +}; + +/* Mono 2 Mux */ +static const struct snd_kcontrol_new wm8753_mono2_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[17]); + +/* Out 3 Mux */ +static const struct snd_kcontrol_new wm8753_out3_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[18]); + +/* Out 4 Mux */ +static const struct snd_kcontrol_new wm8753_out4_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[19]); + +/* ADC Mono Mix */ +static const struct snd_kcontrol_new wm8753_adc_mono_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[22]); + +/* Record mixer */ +static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0), +SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0), +SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0), +}; + +/* Left ADC mux */ +static const struct snd_kcontrol_new wm8753_adc_left_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[21]); + +/* Right ADC mux */ +static const struct snd_kcontrol_new wm8753_adc_right_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[20]); + +/* MIC mux */ +static const struct snd_kcontrol_new wm8753_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[16]); + +/* ALC mixer */ +static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0), +SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0), +SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0), +SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0), +}; + +/* Left Line mux */ +static const struct snd_kcontrol_new wm8753_line_left_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[14]); + +/* Right Line mux */ +static const struct snd_kcontrol_new wm8753_line_right_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[13]); + +/* Mono Line mux */ +static const struct snd_kcontrol_new wm8753_line_mono_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[12]); + +/* Line mux and mixer */ +static const struct snd_kcontrol_new wm8753_line_mux_mix_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[11]); + +/* Rx mux and mixer */ +static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[15]); + +/* Mic Selector Mux */ +static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[25]); + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0), +SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0, + &wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)), +SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0), +SND_SOC_DAPM_OUTPUT("LOUT1"), +SND_SOC_DAPM_OUTPUT("LOUT2"), +SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0, + &wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)), +SND_SOC_DAPM_PGA("Right Out 1", WM8753_PWR3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Out 2", WM8753_PWR3, 5, 0, NULL, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", WM8753_PWR1, 2, 0), +SND_SOC_DAPM_OUTPUT("ROUT1"), +SND_SOC_DAPM_OUTPUT("ROUT2"), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8753_PWR4, 2, 0, + &wm8753_mono_mixer_controls[0], ARRAY_SIZE(wm8753_mono_mixer_controls)), +SND_SOC_DAPM_PGA("Mono Out 1", WM8753_PWR3, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out 2", WM8753_PWR3, 1, 0, NULL, 0), +SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0), +SND_SOC_DAPM_OUTPUT("MONO1"), +SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls), +SND_SOC_DAPM_OUTPUT("MONO2"), +SND_SOC_DAPM_MIXER("Out3 Left + Right", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls), +SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_MUX("Out4 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out4_controls), +SND_SOC_DAPM_PGA("Out 4", WM8753_PWR3, 3, 0, NULL, 0), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_MIXER("Playback Mixer", WM8753_PWR4, 3, 0, + &wm8753_record_mixer_controls[0], + ARRAY_SIZE(wm8753_record_mixer_controls)), +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8753_PWR2, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8753_PWR2, 2, 0), +SND_SOC_DAPM_MUX("Capture Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8753_adc_mono_controls), +SND_SOC_DAPM_MUX("Capture Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8753_adc_mono_controls), +SND_SOC_DAPM_MUX("Capture Left Mux", SND_SOC_NOPM, 0, 0, + &wm8753_adc_left_controls), +SND_SOC_DAPM_MUX("Capture Right Mux", SND_SOC_NOPM, 0, 0, + &wm8753_adc_right_controls), +SND_SOC_DAPM_MUX("Mic Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm8753_mic_mux_controls), +SND_SOC_DAPM_PGA("Left Capture Volume", WM8753_PWR2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Capture Volume", WM8753_PWR2, 4, 0, NULL, 0), +SND_SOC_DAPM_MIXER("ALC Mixer", WM8753_PWR2, 6, 0, + &wm8753_alc_mixer_controls[0], ARRAY_SIZE(wm8753_alc_mixer_controls)), +SND_SOC_DAPM_MUX("Line Left Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_left_controls), +SND_SOC_DAPM_MUX("Line Right Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_right_controls), +SND_SOC_DAPM_MUX("Line Mono Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_mono_controls), +SND_SOC_DAPM_MUX("Line Mixer", WM8753_PWR2, 0, 0, + &wm8753_line_mux_mix_controls), +SND_SOC_DAPM_MUX("Rx Mixer", WM8753_PWR2, 1, 0, + &wm8753_rx_mux_mix_controls), +SND_SOC_DAPM_PGA("Mic 1 Volume", WM8753_PWR2, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic 2 Volume", WM8753_PWR2, 7, 0, NULL, 0), +SND_SOC_DAPM_MUX("Mic Selection Mux", SND_SOC_NOPM, 0, 0, + &wm8753_mic_sel_mux_controls), +SND_SOC_DAPM_INPUT("LINE1"), +SND_SOC_DAPM_INPUT("LINE2"), +SND_SOC_DAPM_INPUT("RXP"), +SND_SOC_DAPM_INPUT("RXN"), +SND_SOC_DAPM_INPUT("ACIN"), +SND_SOC_DAPM_OUTPUT("ACOP"), +SND_SOC_DAPM_INPUT("MIC1N"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2N"), +SND_SOC_DAPM_INPUT("MIC2"), +SND_SOC_DAPM_VMID("VREF"), +}; + +static const struct snd_soc_dapm_route wm8753_dapm_routes[] = { + /* left mixer */ + {"Left Mixer", "Left Playback Switch", "Left DAC"}, + {"Left Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Left Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Left Mixer", "Bypass Playback Switch", "Line Left Mux"}, + + /* right mixer */ + {"Right Mixer", "Right Playback Switch", "Right DAC"}, + {"Right Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Right Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Right Mixer", "Bypass Playback Switch", "Line Right Mux"}, + + /* mono mixer */ + {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Mono Mixer", "Bypass Playback Switch", "Line Mono Mux"}, + + /* left out */ + {"Left Out 1", NULL, "Left Mixer"}, + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out */ + {"Right Out 1", NULL, "Right Mixer"}, + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono 1 out */ + {"Mono Out 1", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out 1"}, + + /* mono 2 out */ + {"Mono 2 Mux", "Left + Right", "Out3 Left + Right"}, + {"Mono 2 Mux", "Inverted Mono 1", "MONO1"}, + {"Mono 2 Mux", "Left", "Left Mixer"}, + {"Mono 2 Mux", "Right", "Right Mixer"}, + {"Mono Out 2", NULL, "Mono 2 Mux"}, + {"MONO2", NULL, "Mono Out 2"}, + + /* out 3 */ + {"Out3 Left + Right", NULL, "Left Mixer"}, + {"Out3 Left + Right", NULL, "Right Mixer"}, + {"Out3 Mux", "VREF", "VREF"}, + {"Out3 Mux", "Left + Right", "Out3 Left + Right"}, + {"Out3 Mux", "ROUT2", "ROUT2"}, + {"Out 3", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3"}, + + /* out 4 */ + {"Out4 Mux", "VREF", "VREF"}, + {"Out4 Mux", "Capture ST", "Playback Mixer"}, + {"Out4 Mux", "LOUT2", "LOUT2"}, + {"Out 4", NULL, "Out4 Mux"}, + {"OUT4", NULL, "Out 4"}, + + /* record mixer */ + {"Playback Mixer", "Left Capture Switch", "Left Mixer"}, + {"Playback Mixer", "Voice Capture Switch", "Mono Mixer"}, + {"Playback Mixer", "Right Capture Switch", "Right Mixer"}, + + /* Mic/SideTone Mux */ + {"Mic Sidetone Mux", "Left PGA", "Left Capture Volume"}, + {"Mic Sidetone Mux", "Right PGA", "Right Capture Volume"}, + {"Mic Sidetone Mux", "Mic 1", "Mic 1 Volume"}, + {"Mic Sidetone Mux", "Mic 2", "Mic 2 Volume"}, + + /* Capture Left Mux */ + {"Capture Left Mux", "PGA", "Left Capture Volume"}, + {"Capture Left Mux", "Line or RXP-RXN", "Line Left Mux"}, + {"Capture Left Mux", "Line", "LINE1"}, + + /* Capture Right Mux */ + {"Capture Right Mux", "PGA", "Right Capture Volume"}, + {"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"}, + {"Capture Right Mux", "Sidetone", "Playback Mixer"}, + + /* Mono Capture mixer-mux */ + {"Capture Right Mixer", "Stereo", "Capture Right Mux"}, + {"Capture Left Mixer", "Stereo", "Capture Left Mux"}, + {"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"}, + {"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"}, + {"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"}, + {"Capture Right Mixer", "Analogue Mix Right", "Capture Right Mux"}, + {"Capture Left Mixer", "Digital Mono Mix", "Capture Left Mux"}, + {"Capture Left Mixer", "Digital Mono Mix", "Capture Right Mux"}, + {"Capture Right Mixer", "Digital Mono Mix", "Capture Left Mux"}, + {"Capture Right Mixer", "Digital Mono Mix", "Capture Right Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Capture Left Mixer"}, + {"Right ADC", NULL, "Capture Right Mixer"}, + + /* Left Capture Volume */ + {"Left Capture Volume", NULL, "ACIN"}, + + /* Right Capture Volume */ + {"Right Capture Volume", NULL, "Mic 2 Volume"}, + + /* ALC Mixer */ + {"ALC Mixer", "Line Capture Switch", "Line Mixer"}, + {"ALC Mixer", "Mic2 Capture Switch", "Mic 2 Volume"}, + {"ALC Mixer", "Mic1 Capture Switch", "Mic 1 Volume"}, + {"ALC Mixer", "Rx Capture Switch", "Rx Mixer"}, + + /* Line Left Mux */ + {"Line Left Mux", "Line 1", "LINE1"}, + {"Line Left Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Right Mux */ + {"Line Right Mux", "Line 2", "LINE2"}, + {"Line Right Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Mono Mux */ + {"Line Mono Mux", "Line Mix", "Line Mixer"}, + {"Line Mono Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Mixer/Mux */ + {"Line Mixer", "Line 1 + 2", "LINE1"}, + {"Line Mixer", "Line 1 - 2", "LINE1"}, + {"Line Mixer", "Line 1 + 2", "LINE2"}, + {"Line Mixer", "Line 1 - 2", "LINE2"}, + {"Line Mixer", "Line 1", "LINE1"}, + {"Line Mixer", "Line 2", "LINE2"}, + + /* Rx Mixer/Mux */ + {"Rx Mixer", "RXP - RXN", "RXP"}, + {"Rx Mixer", "RXP + RXN", "RXP"}, + {"Rx Mixer", "RXP - RXN", "RXN"}, + {"Rx Mixer", "RXP + RXN", "RXN"}, + {"Rx Mixer", "RXP", "RXP"}, + {"Rx Mixer", "RXN", "RXN"}, + + /* Mic 1 Volume */ + {"Mic 1 Volume", NULL, "MIC1N"}, + {"Mic 1 Volume", NULL, "Mic Selection Mux"}, + + /* Mic 2 Volume */ + {"Mic 2 Volume", NULL, "MIC2N"}, + {"Mic 2 Volume", NULL, "MIC2"}, + + /* Mic Selector Mux */ + {"Mic Selection Mux", "Mic 1", "MIC1"}, + {"Mic Selection Mux", "Mic 2", "MIC2N"}, + {"Mic Selection Mux", "Mic 3", "MIC2"}, + + /* ACOP */ + {"ACOP", NULL, "ALC Mixer"}, +}; + +/* PLL divisors */ +struct _pll_div { + u32 div2:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 22) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "wm8753: unsupported N = %u\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg, enable; + int offset; + struct snd_soc_component *component = codec_dai->component; + + if (pll_id < WM8753_PLL1 || pll_id > WM8753_PLL2) + return -ENODEV; + + if (pll_id == WM8753_PLL1) { + offset = 0; + enable = 0x10; + reg = snd_soc_component_read32(component, WM8753_CLOCK) & 0xffef; + } else { + offset = 4; + enable = 0x8; + reg = snd_soc_component_read32(component, WM8753_CLOCK) & 0xfff7; + } + + if (!freq_in || !freq_out) { + /* disable PLL */ + snd_soc_component_write(component, WM8753_PLL1CTL1 + offset, 0x0026); + snd_soc_component_write(component, WM8753_CLOCK, reg); + return 0; + } else { + u16 value = 0; + struct _pll_div pll_div; + + pll_factors(&pll_div, freq_out * 8, freq_in); + + /* set up N and K PLL divisor ratios */ + /* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */ + value = (pll_div.n << 5) + ((pll_div.k & 0x3c0000) >> 18); + snd_soc_component_write(component, WM8753_PLL1CTL2 + offset, value); + + /* bits 8:0 = PLL_K[17:9] */ + value = (pll_div.k & 0x03fe00) >> 9; + snd_soc_component_write(component, WM8753_PLL1CTL3 + offset, value); + + /* bits 8:0 = PLL_K[8:0] */ + value = pll_div.k & 0x0001ff; + snd_soc_component_write(component, WM8753_PLL1CTL4 + offset, value); + + /* set PLL as input and enable */ + snd_soc_component_write(component, WM8753_PLL1CTL1 + offset, 0x0027 | + (pll_div.div2 << 3)); + snd_soc_component_write(component, WM8753_CLOCK, reg | enable); + } + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk (after PLL) clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 0x6, 0x0}, + {11289600, 8000, 0x16, 0x0}, + {18432000, 8000, 0x7, 0x0}, + {16934400, 8000, 0x17, 0x0}, + {12000000, 8000, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 0x18, 0x0}, + {16934400, 11025, 0x19, 0x0}, + {12000000, 11025, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 0xa, 0x0}, + {18432000, 16000, 0xb, 0x0}, + {12000000, 16000, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 0x1a, 0x0}, + {16934400, 22050, 0x1b, 0x0}, + {12000000, 22050, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 0xc, 0x0}, + {18432000, 32000, 0xd, 0x0}, + {12000000, 32000, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 0x10, 0x0}, + {16934400, 44100, 0x11, 0x0}, + {12000000, 44100, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 0x0, 0x0}, + {18432000, 48000, 0x1, 0x0}, + {12000000, 48000, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 0x1e, 0x0}, + {16934400, 88200, 0x1f, 0x0}, + {12000000, 88200, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 0xe, 0x0}, + {18432000, 96000, 0xf, 0x0}, + {12000000, 96000, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +/* + * Clock after PLL and dividers + */ +static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + if (clk_id == WM8753_MCLK) { + wm8753->sysclk = freq; + return 0; + } else if (clk_id == WM8753_PCMCLK) { + wm8753->pcmclk = freq; + return 0; + } + break; + } + return -EINVAL; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + u16 voice = snd_soc_component_read32(component, WM8753_PCM) & 0x01ec; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + voice |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + voice |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + voice |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + voice |= 0x0013; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8753_PCM, voice); + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + u16 voice = snd_soc_component_read32(component, WM8753_PCM) & 0x01f3; + u16 srate = snd_soc_component_read32(component, WM8753_SRATE1) & 0x017f; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + voice |= 0x0004; + break; + case 24: + voice |= 0x0008; + break; + case 32: + voice |= 0x000c; + break; + } + + /* sample rate */ + if (params_rate(params) * 384 == wm8753->pcmclk) + srate |= 0x80; + snd_soc_component_write(component, WM8753_SRATE1, srate); + + snd_soc_component_write(component, WM8753_PCM, voice); + return 0; +} + +/* + * Set's PCM dai fmt and BCLK. + */ +static int wm8753_pcm_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + u16 voice, ioctl; + + voice = snd_soc_component_read32(component, WM8753_PCM) & 0x011f; + ioctl = snd_soc_component_read32(component, WM8753_IOCTL) & 0x015d; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ioctl |= 0x2; /* fall through */ + case SND_SOC_DAIFMT_CBM_CFS: + voice |= 0x0040; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + voice |= 0x0080; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + voice &= ~0x0010; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + voice |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + voice |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + voice |= 0x0010; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8753_PCM, voice); + snd_soc_component_write(component, WM8753_IOCTL, ioctl); + return 0; +} + +static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + switch (div_id) { + case WM8753_PCMDIV: + reg = snd_soc_component_read32(component, WM8753_CLOCK) & 0x003f; + snd_soc_component_write(component, WM8753_CLOCK, reg | div); + break; + case WM8753_BCLKDIV: + reg = snd_soc_component_read32(component, WM8753_SRATE2) & 0x01c7; + snd_soc_component_write(component, WM8753_SRATE2, reg | div); + break; + case WM8753_VXCLKDIV: + reg = snd_soc_component_read32(component, WM8753_SRATE2) & 0x003f; + snd_soc_component_write(component, WM8753_SRATE2, reg | div); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * Set's HiFi DAC format. + */ +static int wm8753_hdac_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + u16 hifi = snd_soc_component_read32(component, WM8753_HIFI) & 0x01e0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + hifi |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + hifi |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + hifi |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + hifi |= 0x0013; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8753_HIFI, hifi); + return 0; +} + +/* + * Set's I2S DAI format. + */ +static int wm8753_i2s_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + u16 ioctl, hifi; + + hifi = snd_soc_component_read32(component, WM8753_HIFI) & 0x013f; + ioctl = snd_soc_component_read32(component, WM8753_IOCTL) & 0x00ae; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ioctl |= 0x1; /* fall through */ + case SND_SOC_DAIFMT_CBM_CFS: + hifi |= 0x0040; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + hifi |= 0x0080; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + hifi &= ~0x0010; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + hifi |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + hifi |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + hifi |= 0x0010; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8753_HIFI, hifi); + snd_soc_component_write(component, WM8753_IOCTL, ioctl); + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + u16 srate = snd_soc_component_read32(component, WM8753_SRATE1) & 0x01c0; + u16 hifi = snd_soc_component_read32(component, WM8753_HIFI) & 0x01f3; + int coeff; + + /* is digital filter coefficient valid ? */ + coeff = get_coeff(wm8753->sysclk, params_rate(params)); + if (coeff < 0) { + printk(KERN_ERR "wm8753 invalid MCLK or rate\n"); + return coeff; + } + snd_soc_component_write(component, WM8753_SRATE1, srate | (coeff_div[coeff].sr << 1) | + coeff_div[coeff].usb); + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + hifi |= 0x0004; + break; + case 24: + hifi |= 0x0008; + break; + case 32: + hifi |= 0x000c; + break; + } + + snd_soc_component_write(component, WM8753_HIFI, hifi); + return 0; +} + +static int wm8753_mode1v_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + u16 clock; + + /* set clk source as pcmclk */ + clock = snd_soc_component_read32(component, WM8753_CLOCK) & 0xfffb; + snd_soc_component_write(component, WM8753_CLOCK, clock); + + return wm8753_vdac_adc_set_dai_fmt(component, fmt); +} + +static int wm8753_mode1h_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + return wm8753_hdac_set_dai_fmt(component, fmt); +} + +static int wm8753_mode2_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + u16 clock; + + /* set clk source as pcmclk */ + clock = snd_soc_component_read32(component, WM8753_CLOCK) & 0xfffb; + snd_soc_component_write(component, WM8753_CLOCK, clock); + + return wm8753_vdac_adc_set_dai_fmt(component, fmt); +} + +static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + u16 clock; + + /* set clk source as mclk */ + clock = snd_soc_component_read32(component, WM8753_CLOCK) & 0xfffb; + snd_soc_component_write(component, WM8753_CLOCK, clock | 0x4); + + if (wm8753_hdac_set_dai_fmt(component, fmt) < 0) + return -EINVAL; + return wm8753_vdac_adc_set_dai_fmt(component, fmt); +} + +static int wm8753_hifi_write_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (wm8753->dai_func) { + case 0: + ret = wm8753_mode1h_set_dai_fmt(component, fmt); + break; + case 1: + ret = wm8753_mode2_set_dai_fmt(component, fmt); + break; + case 2: + case 3: + ret = wm8753_mode3_4_set_dai_fmt(component, fmt); + break; + default: + break; + } + if (ret) + return ret; + + return wm8753_i2s_set_dai_fmt(component, fmt); +} + +static int wm8753_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + + wm8753->hifi_fmt = fmt; + + return wm8753_hifi_write_dai_fmt(component, fmt); +}; + +static int wm8753_voice_write_dai_fmt(struct snd_soc_component *component, + unsigned int fmt) +{ + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + int ret = 0; + + if (wm8753->dai_func != 0) + return 0; + + ret = wm8753_mode1v_set_dai_fmt(component, fmt); + if (ret) + return ret; + ret = wm8753_pcm_set_dai_fmt(component, fmt); + if (ret) + return ret; + + return 0; +}; + +static int wm8753_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + + wm8753->voice_fmt = fmt; + + return wm8753_voice_write_dai_fmt(component, fmt); +}; + +static int wm8753_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8753_DAC) & 0xfff7; + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + + /* the digital mute covers the HiFi and Voice DAC's on the WM8753. + * make sure we check if they are not both active when we mute */ + if (mute && wm8753->dai_func == 1) { + if (!snd_soc_component_is_active(component)) + snd_soc_component_write(component, WM8753_DAC, mute_reg | 0x8); + } else { + if (mute) + snd_soc_component_write(component, WM8753_DAC, mute_reg | 0x8); + else + snd_soc_component_write(component, WM8753_DAC, mute_reg); + } + + return 0; +} + +static void wm8753_charge_work(struct work_struct *work) +{ + struct wm8753_priv *wm8753 = + container_of(work, struct wm8753_priv, charge_work.work); + + /* Set to 500k */ + regmap_update_bits(wm8753->regmap, WM8753_PWR1, 0x0180, 0x0100); +} + +static int wm8753_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + u16 pwr_reg = snd_soc_component_read32(component, WM8753_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + snd_soc_component_write(component, WM8753_PWR1, pwr_reg | 0x00c0); + break; + case SND_SOC_BIAS_PREPARE: + /* Wait until fully charged */ + flush_delayed_work(&wm8753->charge_work); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* set vmid to 5k for quick power up */ + snd_soc_component_write(component, WM8753_PWR1, pwr_reg | 0x01c1); + schedule_delayed_work(&wm8753->charge_work, + msecs_to_jiffies(caps_charge)); + } else { + /* mute dac and set vmid to 500k, enable VREF */ + snd_soc_component_write(component, WM8753_PWR1, pwr_reg | 0x0141); + } + break; + case SND_SOC_BIAS_OFF: + cancel_delayed_work_sync(&wm8753->charge_work); + snd_soc_component_write(component, WM8753_PWR1, 0x0001); + break; + } + return 0; +} + +#define WM8753_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8753_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +/* + * The WM8753 supports up to 4 different and mutually exclusive DAI + * configurations. This gives 2 PCM's available for use, hifi and voice. + * NOTE: The Voice PCM cannot play or capture audio to the CPU as it's DAI + * is connected between the wm8753 and a BT codec or GSM modem. + * + * 1. Voice over PCM DAI - HIFI DAC over HIFI DAI + * 2. Voice over HIFI DAI - HIFI disabled + * 3. Voice disabled - HIFI over HIFI + * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture + */ +static const struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = { + .hw_params = wm8753_i2s_hw_params, + .digital_mute = wm8753_mute, + .set_fmt = wm8753_hifi_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, +}; + +static const struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = { + .hw_params = wm8753_pcm_hw_params, + .digital_mute = wm8753_mute, + .set_fmt = wm8753_voice_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver wm8753_dai[] = { +/* DAI HiFi mode 1 */ +{ .name = "wm8753-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS + }, + .capture = { /* dummy for fast DAI switching */ + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS + }, + .ops = &wm8753_dai_ops_hifi_mode, +}, +/* DAI Voice mode 1 */ +{ .name = "wm8753-voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS, + }, + .ops = &wm8753_dai_ops_voice_mode, +}, +}; + +static int wm8753_resume(struct snd_soc_component *component) +{ + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + + regcache_sync(wm8753->regmap); + + return 0; +} + +static int wm8753_probe(struct snd_soc_component *component) +{ + struct wm8753_priv *wm8753 = snd_soc_component_get_drvdata(component); + int ret; + + INIT_DELAYED_WORK(&wm8753->charge_work, wm8753_charge_work); + + ret = wm8753_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + wm8753->dai_func = 0; + + /* set the update bits */ + snd_soc_component_update_bits(component, WM8753_LDAC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_RDAC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_LADC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_RADC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_LOUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_ROUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_LOUT2V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_ROUT2V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_LINVOL, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8753_RINVOL, 0x0100, 0x0100); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8753 = { + .probe = wm8753_probe, + .resume = wm8753_resume, + .set_bias_level = wm8753_set_bias_level, + .controls = wm8753_snd_controls, + .num_controls = ARRAY_SIZE(wm8753_snd_controls), + .dapm_widgets = wm8753_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8753_dapm_widgets), + .dapm_routes = wm8753_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8753_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8753_of_match[] = { + { .compatible = "wlf,wm8753", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8753_of_match); + +static const struct regmap_config wm8753_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = WM8753_ADCTL2, + .volatile_reg = wm8753_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8753_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8753_reg_defaults), +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8753_spi_probe(struct spi_device *spi) +{ + struct wm8753_priv *wm8753; + int ret; + + wm8753 = devm_kzalloc(&spi->dev, sizeof(struct wm8753_priv), + GFP_KERNEL); + if (wm8753 == NULL) + return -ENOMEM; + + spi_set_drvdata(spi, wm8753); + + wm8753->regmap = devm_regmap_init_spi(spi, &wm8753_regmap); + if (IS_ERR(wm8753->regmap)) { + ret = PTR_ERR(wm8753->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_component(&spi->dev, &soc_component_dev_wm8753, + wm8753_dai, ARRAY_SIZE(wm8753_dai)); + if (ret != 0) + dev_err(&spi->dev, "Failed to register CODEC: %d\n", ret); + + return ret; +} + +static struct spi_driver wm8753_spi_driver = { + .driver = { + .name = "wm8753", + .of_match_table = wm8753_of_match, + }, + .probe = wm8753_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8753_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8753_priv *wm8753; + int ret; + + wm8753 = devm_kzalloc(&i2c->dev, sizeof(struct wm8753_priv), + GFP_KERNEL); + if (wm8753 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8753); + + wm8753->regmap = devm_regmap_init_i2c(i2c, &wm8753_regmap); + if (IS_ERR(wm8753->regmap)) { + ret = PTR_ERR(wm8753->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8753, + wm8753_dai, ARRAY_SIZE(wm8753_dai)); + if (ret != 0) + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id wm8753_i2c_id[] = { + { "wm8753", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id); + +static struct i2c_driver wm8753_i2c_driver = { + .driver = { + .name = "wm8753", + .of_match_table = wm8753_of_match, + }, + .probe = wm8753_i2c_probe, + .id_table = wm8753_i2c_id, +}; +#endif + +static int __init wm8753_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8753_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8753 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8753_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8753 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8753_modinit); + +static void __exit wm8753_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8753_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8753_spi_driver); +#endif +} +module_exit(wm8753_exit); + +MODULE_DESCRIPTION("ASoC WM8753 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h new file mode 100644 index 000000000..8b39e3677 --- /dev/null +++ b/sound/soc/codecs/wm8753.h @@ -0,0 +1,115 @@ +/* + * wm8753.h -- audio driver for WM8753 + * + * Copyright 2003 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8753_H +#define _WM8753_H + +/* WM8753 register space */ + +#define WM8753_DAC 0x01 +#define WM8753_ADC 0x02 +#define WM8753_PCM 0x03 +#define WM8753_HIFI 0x04 +#define WM8753_IOCTL 0x05 +#define WM8753_SRATE1 0x06 +#define WM8753_SRATE2 0x07 +#define WM8753_LDAC 0x08 +#define WM8753_RDAC 0x09 +#define WM8753_BASS 0x0a +#define WM8753_TREBLE 0x0b +#define WM8753_ALC1 0x0c +#define WM8753_ALC2 0x0d +#define WM8753_ALC3 0x0e +#define WM8753_NGATE 0x0f +#define WM8753_LADC 0x10 +#define WM8753_RADC 0x11 +#define WM8753_ADCTL1 0x12 +#define WM8753_3D 0x13 +#define WM8753_PWR1 0x14 +#define WM8753_PWR2 0x15 +#define WM8753_PWR3 0x16 +#define WM8753_PWR4 0x17 +#define WM8753_ID 0x18 +#define WM8753_INTPOL 0x19 +#define WM8753_INTEN 0x1a +#define WM8753_GPIO1 0x1b +#define WM8753_GPIO2 0x1c +#define WM8753_RESET 0x1f +#define WM8753_RECMIX1 0x20 +#define WM8753_RECMIX2 0x21 +#define WM8753_LOUTM1 0x22 +#define WM8753_LOUTM2 0x23 +#define WM8753_ROUTM1 0x24 +#define WM8753_ROUTM2 0x25 +#define WM8753_MOUTM1 0x26 +#define WM8753_MOUTM2 0x27 +#define WM8753_LOUT1V 0x28 +#define WM8753_ROUT1V 0x29 +#define WM8753_LOUT2V 0x2a +#define WM8753_ROUT2V 0x2b +#define WM8753_MOUTV 0x2c +#define WM8753_OUTCTL 0x2d +#define WM8753_ADCIN 0x2e +#define WM8753_INCTL1 0x2f +#define WM8753_INCTL2 0x30 +#define WM8753_LINVOL 0x31 +#define WM8753_RINVOL 0x32 +#define WM8753_MICBIAS 0x33 +#define WM8753_CLOCK 0x34 +#define WM8753_PLL1CTL1 0x35 +#define WM8753_PLL1CTL2 0x36 +#define WM8753_PLL1CTL3 0x37 +#define WM8753_PLL1CTL4 0x38 +#define WM8753_PLL2CTL1 0x39 +#define WM8753_PLL2CTL2 0x3a +#define WM8753_PLL2CTL3 0x3b +#define WM8753_PLL2CTL4 0x3c +#define WM8753_BIASCTL 0x3d +#define WM8753_ADCTL2 0x3f + +#define WM8753_PLL1 0 +#define WM8753_PLL2 1 + +/* clock inputs */ +#define WM8753_MCLK 0 +#define WM8753_PCMCLK 1 + +/* clock divider id's */ +#define WM8753_PCMDIV 0 +#define WM8753_BCLKDIV 1 +#define WM8753_VXCLKDIV 2 + +/* PCM clock dividers */ +#define WM8753_PCM_DIV_1 (0 << 6) +#define WM8753_PCM_DIV_3 (2 << 6) +#define WM8753_PCM_DIV_5_5 (3 << 6) +#define WM8753_PCM_DIV_2 (4 << 6) +#define WM8753_PCM_DIV_4 (5 << 6) +#define WM8753_PCM_DIV_6 (6 << 6) +#define WM8753_PCM_DIV_8 (7 << 6) + +/* BCLK clock dividers */ +#define WM8753_BCLK_DIV_1 (0 << 3) +#define WM8753_BCLK_DIV_2 (1 << 3) +#define WM8753_BCLK_DIV_4 (2 << 3) +#define WM8753_BCLK_DIV_8 (3 << 3) +#define WM8753_BCLK_DIV_16 (4 << 3) + +/* VXCLK clock dividers */ +#define WM8753_VXCLK_DIV_1 (0 << 6) +#define WM8753_VXCLK_DIV_2 (1 << 6) +#define WM8753_VXCLK_DIV_4 (2 << 6) +#define WM8753_VXCLK_DIV_8 (3 << 6) +#define WM8753_VXCLK_DIV_16 (4 << 6) + +#endif diff --git a/sound/soc/codecs/wm8770.c b/sound/soc/codecs/wm8770.c new file mode 100644 index 000000000..806245c70 --- /dev/null +++ b/sound/soc/codecs/wm8770.c @@ -0,0 +1,715 @@ +/* + * wm8770.c -- WM8770 ALSA SoC Audio driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8770.h" + +#define WM8770_NUM_SUPPLIES 3 +static const char *wm8770_supply_names[WM8770_NUM_SUPPLIES] = { + "AVDD1", + "AVDD2", + "DVDD" +}; + +static const struct reg_default wm8770_reg_defaults[] = { + { 0, 0x7f }, + { 1, 0x7f }, + { 2, 0x7f }, + { 3, 0x7f }, + { 4, 0x7f }, + { 5, 0x7f }, + { 6, 0x7f }, + { 7, 0x7f }, + { 8, 0x7f }, + { 9, 0xff }, + { 10, 0xff }, + { 11, 0xff }, + { 12, 0xff }, + { 13, 0xff }, + { 14, 0xff }, + { 15, 0xff }, + { 16, 0xff }, + { 17, 0xff }, + { 18, 0 }, + { 19, 0x90 }, + { 20, 0 }, + { 21, 0 }, + { 22, 0x22 }, + { 23, 0x22 }, + { 24, 0x3e }, + { 25, 0xc }, + { 26, 0xc }, + { 27, 0x100 }, + { 28, 0x189 }, + { 29, 0x189 }, + { 30, 0x8770 }, +}; + +static bool wm8770_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8770_RESET: + return true; + default: + return false; + } +} + +struct wm8770_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8770_NUM_SUPPLIES]; + struct notifier_block disable_nb[WM8770_NUM_SUPPLIES]; + struct snd_soc_component *component; + int sysclk; +}; + +static int vout12supply_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +static int vout34supply_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +/* + * We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define WM8770_REGULATOR_EVENT(n) \ +static int wm8770_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct wm8770_priv *wm8770 = container_of(nb, struct wm8770_priv, \ + disable_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + regcache_mark_dirty(wm8770->regmap); \ + } \ + return 0; \ +} + +WM8770_REGULATOR_EVENT(0) +WM8770_REGULATOR_EVENT(1) +WM8770_REGULATOR_EVENT(2) + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(dac_dig_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_alg_tlv, -12700, 100, 1); + +static const char *dac_phase_text[][2] = { + { "DAC1 Normal", "DAC1 Inverted" }, + { "DAC2 Normal", "DAC2 Inverted" }, + { "DAC3 Normal", "DAC3 Inverted" }, + { "DAC4 Normal", "DAC4 Inverted" }, +}; + +static const struct soc_enum dac_phase[] = { + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 0, 1, 2, dac_phase_text[0]), + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 2, 3, 2, dac_phase_text[1]), + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 4, 5, 2, dac_phase_text[2]), + SOC_ENUM_DOUBLE(WM8770_DACPHASE, 6, 7, 2, dac_phase_text[3]), +}; + +static const struct snd_kcontrol_new wm8770_snd_controls[] = { + /* global DAC playback controls */ + SOC_SINGLE_TLV("DAC Playback Volume", WM8770_MSDIGVOL, 0, 255, 0, + dac_dig_tlv), + SOC_SINGLE("DAC Playback Switch", WM8770_DACMUTE, 4, 1, 1), + SOC_SINGLE("DAC Playback ZC Switch", WM8770_DACCTRL1, 0, 1, 0), + + /* global VOUT playback controls */ + SOC_SINGLE_TLV("VOUT Playback Volume", WM8770_MSALGVOL, 0, 127, 0, + dac_alg_tlv), + SOC_SINGLE("VOUT Playback ZC Switch", WM8770_MSALGVOL, 7, 1, 0), + + /* VOUT1/2/3/4 specific controls */ + SOC_DOUBLE_R_TLV("VOUT1 Playback Volume", WM8770_VOUT1LVOL, + WM8770_VOUT1RVOL, 0, 127, 0, dac_alg_tlv), + SOC_DOUBLE_R("VOUT1 Playback ZC Switch", WM8770_VOUT1LVOL, + WM8770_VOUT1RVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("VOUT2 Playback Volume", WM8770_VOUT2LVOL, + WM8770_VOUT2RVOL, 0, 127, 0, dac_alg_tlv), + SOC_DOUBLE_R("VOUT2 Playback ZC Switch", WM8770_VOUT2LVOL, + WM8770_VOUT2RVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("VOUT3 Playback Volume", WM8770_VOUT3LVOL, + WM8770_VOUT3RVOL, 0, 127, 0, dac_alg_tlv), + SOC_DOUBLE_R("VOUT3 Playback ZC Switch", WM8770_VOUT3LVOL, + WM8770_VOUT3RVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("VOUT4 Playback Volume", WM8770_VOUT4LVOL, + WM8770_VOUT4RVOL, 0, 127, 0, dac_alg_tlv), + SOC_DOUBLE_R("VOUT4 Playback ZC Switch", WM8770_VOUT4LVOL, + WM8770_VOUT4RVOL, 7, 1, 0), + + /* DAC1/2/3/4 specific controls */ + SOC_DOUBLE_R_TLV("DAC1 Playback Volume", WM8770_DAC1LVOL, + WM8770_DAC1RVOL, 0, 255, 0, dac_dig_tlv), + SOC_SINGLE("DAC1 Deemphasis Switch", WM8770_DACCTRL2, 0, 1, 0), + SOC_ENUM("DAC1 Phase", dac_phase[0]), + SOC_DOUBLE_R_TLV("DAC2 Playback Volume", WM8770_DAC2LVOL, + WM8770_DAC2RVOL, 0, 255, 0, dac_dig_tlv), + SOC_SINGLE("DAC2 Deemphasis Switch", WM8770_DACCTRL2, 1, 1, 0), + SOC_ENUM("DAC2 Phase", dac_phase[1]), + SOC_DOUBLE_R_TLV("DAC3 Playback Volume", WM8770_DAC3LVOL, + WM8770_DAC3RVOL, 0, 255, 0, dac_dig_tlv), + SOC_SINGLE("DAC3 Deemphasis Switch", WM8770_DACCTRL2, 2, 1, 0), + SOC_ENUM("DAC3 Phase", dac_phase[2]), + SOC_DOUBLE_R_TLV("DAC4 Playback Volume", WM8770_DAC4LVOL, + WM8770_DAC4RVOL, 0, 255, 0, dac_dig_tlv), + SOC_SINGLE("DAC4 Deemphasis Switch", WM8770_DACCTRL2, 3, 1, 0), + SOC_ENUM("DAC4 Phase", dac_phase[3]), + + /* ADC specific controls */ + SOC_DOUBLE_R_TLV("Capture Volume", WM8770_ADCLCTRL, WM8770_ADCRCTRL, + 0, 31, 0, adc_tlv), + SOC_DOUBLE_R("Capture Switch", WM8770_ADCLCTRL, WM8770_ADCRCTRL, + 5, 1, 1), + + /* other controls */ + SOC_SINGLE("ADC 128x Oversampling Switch", WM8770_MSTRCTRL, 3, 1, 0), + SOC_SINGLE("ADC Highpass Filter Switch", WM8770_IFACECTRL, 8, 1, 1) +}; + +static const char *ain_text[] = { + "AIN1", "AIN2", "AIN3", "AIN4", + "AIN5", "AIN6", "AIN7", "AIN8" +}; + +static SOC_ENUM_DOUBLE_DECL(ain_enum, + WM8770_ADCMUX, 0, 4, ain_text); + +static const struct snd_kcontrol_new ain_mux = + SOC_DAPM_ENUM("Capture Mux", ain_enum); + +static const struct snd_kcontrol_new vout1_mix_controls[] = { + SOC_DAPM_SINGLE("DAC1 Switch", WM8770_OUTMUX1, 0, 1, 0), + SOC_DAPM_SINGLE("AUX1 Switch", WM8770_OUTMUX1, 1, 1, 0), + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 2, 1, 0) +}; + +static const struct snd_kcontrol_new vout2_mix_controls[] = { + SOC_DAPM_SINGLE("DAC2 Switch", WM8770_OUTMUX1, 3, 1, 0), + SOC_DAPM_SINGLE("AUX2 Switch", WM8770_OUTMUX1, 4, 1, 0), + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX1, 5, 1, 0) +}; + +static const struct snd_kcontrol_new vout3_mix_controls[] = { + SOC_DAPM_SINGLE("DAC3 Switch", WM8770_OUTMUX2, 0, 1, 0), + SOC_DAPM_SINGLE("AUX3 Switch", WM8770_OUTMUX2, 1, 1, 0), + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 2, 1, 0) +}; + +static const struct snd_kcontrol_new vout4_mix_controls[] = { + SOC_DAPM_SINGLE("DAC4 Switch", WM8770_OUTMUX2, 3, 1, 0), + SOC_DAPM_SINGLE("Bypass Switch", WM8770_OUTMUX2, 4, 1, 0) +}; + +static const struct snd_soc_dapm_widget wm8770_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("AUX1"), + SND_SOC_DAPM_INPUT("AUX2"), + SND_SOC_DAPM_INPUT("AUX3"), + + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + SND_SOC_DAPM_INPUT("AIN3"), + SND_SOC_DAPM_INPUT("AIN4"), + SND_SOC_DAPM_INPUT("AIN5"), + SND_SOC_DAPM_INPUT("AIN6"), + SND_SOC_DAPM_INPUT("AIN7"), + SND_SOC_DAPM_INPUT("AIN8"), + + SND_SOC_DAPM_MUX("Capture Mux", WM8770_ADCMUX, 8, 1, &ain_mux), + + SND_SOC_DAPM_ADC("ADC", "Capture", WM8770_PWDNCTRL, 1, 1), + + SND_SOC_DAPM_DAC("DAC1", "Playback", WM8770_PWDNCTRL, 2, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", WM8770_PWDNCTRL, 3, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", WM8770_PWDNCTRL, 4, 1), + SND_SOC_DAPM_DAC("DAC4", "Playback", WM8770_PWDNCTRL, 5, 1), + + SND_SOC_DAPM_SUPPLY("VOUT12 Supply", SND_SOC_NOPM, 0, 0, + vout12supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("VOUT34 Supply", SND_SOC_NOPM, 0, 0, + vout34supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MIXER("VOUT1 Mixer", SND_SOC_NOPM, 0, 0, + vout1_mix_controls, ARRAY_SIZE(vout1_mix_controls)), + SND_SOC_DAPM_MIXER("VOUT2 Mixer", SND_SOC_NOPM, 0, 0, + vout2_mix_controls, ARRAY_SIZE(vout2_mix_controls)), + SND_SOC_DAPM_MIXER("VOUT3 Mixer", SND_SOC_NOPM, 0, 0, + vout3_mix_controls, ARRAY_SIZE(vout3_mix_controls)), + SND_SOC_DAPM_MIXER("VOUT4 Mixer", SND_SOC_NOPM, 0, 0, + vout4_mix_controls, ARRAY_SIZE(vout4_mix_controls)), + + SND_SOC_DAPM_OUTPUT("VOUT1"), + SND_SOC_DAPM_OUTPUT("VOUT2"), + SND_SOC_DAPM_OUTPUT("VOUT3"), + SND_SOC_DAPM_OUTPUT("VOUT4") +}; + +static const struct snd_soc_dapm_route wm8770_intercon[] = { + { "Capture Mux", "AIN1", "AIN1" }, + { "Capture Mux", "AIN2", "AIN2" }, + { "Capture Mux", "AIN3", "AIN3" }, + { "Capture Mux", "AIN4", "AIN4" }, + { "Capture Mux", "AIN5", "AIN5" }, + { "Capture Mux", "AIN6", "AIN6" }, + { "Capture Mux", "AIN7", "AIN7" }, + { "Capture Mux", "AIN8", "AIN8" }, + + { "ADC", NULL, "Capture Mux" }, + + { "VOUT1 Mixer", NULL, "VOUT12 Supply" }, + { "VOUT1 Mixer", "DAC1 Switch", "DAC1" }, + { "VOUT1 Mixer", "AUX1 Switch", "AUX1" }, + { "VOUT1 Mixer", "Bypass Switch", "Capture Mux" }, + + { "VOUT2 Mixer", NULL, "VOUT12 Supply" }, + { "VOUT2 Mixer", "DAC2 Switch", "DAC2" }, + { "VOUT2 Mixer", "AUX2 Switch", "AUX2" }, + { "VOUT2 Mixer", "Bypass Switch", "Capture Mux" }, + + { "VOUT3 Mixer", NULL, "VOUT34 Supply" }, + { "VOUT3 Mixer", "DAC3 Switch", "DAC3" }, + { "VOUT3 Mixer", "AUX3 Switch", "AUX3" }, + { "VOUT3 Mixer", "Bypass Switch", "Capture Mux" }, + + { "VOUT4 Mixer", NULL, "VOUT34 Supply" }, + { "VOUT4 Mixer", "DAC4 Switch", "DAC4" }, + { "VOUT4 Mixer", "Bypass Switch", "Capture Mux" }, + + { "VOUT1", NULL, "VOUT1 Mixer" }, + { "VOUT2", NULL, "VOUT2 Mixer" }, + { "VOUT3", NULL, "VOUT3 Mixer" }, + { "VOUT4", NULL, "VOUT4 Mixer" } +}; + +static int vout12supply_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, WM8770_OUTMUX1, 0x180, 0); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, WM8770_OUTMUX1, 0x180, 0x180); + break; + } + + return 0; +} + +static int vout34supply_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_component_update_bits(component, WM8770_OUTMUX2, 0x180, 0); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, WM8770_OUTMUX2, 0x180, 0x180); + break; + } + + return 0; +} + +static int wm8770_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, WM8770_RESET, 0); +} + +static int wm8770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component; + int iface, master; + + component = dai->component; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + master = 0x100; + break; + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + default: + return -EINVAL; + } + + iface = 0; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0xc; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x8; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x4; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8770_IFACECTRL, 0xf, iface); + snd_soc_component_update_bits(component, WM8770_MSTRCTRL, 0x100, master); + + return 0; +} + +static const int mclk_ratios[] = { + 128, + 192, + 256, + 384, + 512, + 768 +}; + +static int wm8770_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component; + struct wm8770_priv *wm8770; + int i; + int iface; + int shift; + int ratio; + + component = dai->component; + wm8770 = snd_soc_component_get_drvdata(component); + + iface = 0; + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x10; + break; + case 24: + iface |= 0x20; + break; + case 32: + iface |= 0x30; + break; + } + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + i = 0; + shift = 4; + break; + case SNDRV_PCM_STREAM_CAPTURE: + i = 2; + shift = 0; + break; + default: + return -EINVAL; + } + + /* Only need to set MCLK/LRCLK ratio if we're master */ + if (snd_soc_component_read32(component, WM8770_MSTRCTRL) & 0x100) { + for (; i < ARRAY_SIZE(mclk_ratios); ++i) { + ratio = wm8770->sysclk / params_rate(params); + if (ratio == mclk_ratios[i]) + break; + } + + if (i == ARRAY_SIZE(mclk_ratios)) { + dev_err(component->dev, + "Unable to configure MCLK ratio %d/%d\n", + wm8770->sysclk, params_rate(params)); + return -EINVAL; + } + + dev_dbg(component->dev, "MCLK is %dfs\n", mclk_ratios[i]); + + snd_soc_component_update_bits(component, WM8770_MSTRCTRL, 0x7 << shift, + i << shift); + } + + snd_soc_component_update_bits(component, WM8770_IFACECTRL, 0x30, iface); + + return 0; +} + +static int wm8770_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component; + + component = dai->component; + return snd_soc_component_update_bits(component, WM8770_DACMUTE, 0x10, + !!mute << 4); +} + +static int wm8770_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component; + struct wm8770_priv *wm8770; + + component = dai->component; + wm8770 = snd_soc_component_get_drvdata(component); + wm8770->sysclk = freq; + return 0; +} + +static int wm8770_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int ret; + struct wm8770_priv *wm8770; + + wm8770 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies), + wm8770->supplies); + if (ret) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + regcache_sync(wm8770->regmap); + + /* global powerup */ + snd_soc_component_write(component, WM8770_PWDNCTRL, 0); + } + break; + case SND_SOC_BIAS_OFF: + /* global powerdown */ + snd_soc_component_write(component, WM8770_PWDNCTRL, 1); + regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies), + wm8770->supplies); + break; + } + + return 0; +} + +#define WM8770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8770_dai_ops = { + .digital_mute = wm8770_mute, + .hw_params = wm8770_hw_params, + .set_fmt = wm8770_set_fmt, + .set_sysclk = wm8770_set_sysclk, +}; + +static struct snd_soc_dai_driver wm8770_dai = { + .name = "wm8770-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8770_FORMATS + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8770_FORMATS + }, + .ops = &wm8770_dai_ops, + .symmetric_rates = 1 +}; + +static int wm8770_probe(struct snd_soc_component *component) +{ + struct wm8770_priv *wm8770; + int ret; + + wm8770 = snd_soc_component_get_drvdata(component); + wm8770->component = component; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8770->supplies), + wm8770->supplies); + if (ret) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = wm8770_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + goto err_reg_enable; + } + + /* latch the volume update bits */ + snd_soc_component_update_bits(component, WM8770_MSDIGVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_MSALGVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_VOUT1RVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_VOUT2RVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_VOUT3RVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_VOUT4RVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_DAC1RVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_DAC2RVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_DAC3RVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8770_DAC4RVOL, 0x100, 0x100); + + /* mute all DACs */ + snd_soc_component_update_bits(component, WM8770_DACMUTE, 0x10, 0x10); + +err_reg_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8770->supplies), wm8770->supplies); + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8770 = { + .probe = wm8770_probe, + .set_bias_level = wm8770_set_bias_level, + .controls = wm8770_snd_controls, + .num_controls = ARRAY_SIZE(wm8770_snd_controls), + .dapm_widgets = wm8770_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8770_dapm_widgets), + .dapm_routes = wm8770_intercon, + .num_dapm_routes = ARRAY_SIZE(wm8770_intercon), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8770_of_match[] = { + { .compatible = "wlf,wm8770", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8770_of_match); + +static const struct regmap_config wm8770_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8770_RESET, + + .reg_defaults = wm8770_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8770_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8770_volatile_reg, +}; + +static int wm8770_spi_probe(struct spi_device *spi) +{ + struct wm8770_priv *wm8770; + int ret, i; + + wm8770 = devm_kzalloc(&spi->dev, sizeof(struct wm8770_priv), + GFP_KERNEL); + if (!wm8770) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++) + wm8770->supplies[i].supply = wm8770_supply_names[i]; + + ret = devm_regulator_bulk_get(&spi->dev, ARRAY_SIZE(wm8770->supplies), + wm8770->supplies); + if (ret) { + dev_err(&spi->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8770->disable_nb[0].notifier_call = wm8770_regulator_event_0; + wm8770->disable_nb[1].notifier_call = wm8770_regulator_event_1; + wm8770->disable_nb[2].notifier_call = wm8770_regulator_event_2; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8770->supplies); i++) { + ret = regulator_register_notifier(wm8770->supplies[i].consumer, + &wm8770->disable_nb[i]); + if (ret) { + dev_err(&spi->dev, + "Failed to register regulator notifier: %d\n", + ret); + } + } + + wm8770->regmap = devm_regmap_init_spi(spi, &wm8770_regmap); + if (IS_ERR(wm8770->regmap)) + return PTR_ERR(wm8770->regmap); + + spi_set_drvdata(spi, wm8770); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8770, &wm8770_dai, 1); + + return ret; +} + +static int wm8770_spi_remove(struct spi_device *spi) +{ + struct wm8770_priv *wm8770 = spi_get_drvdata(spi); + int i; + + for (i = 0; i < ARRAY_SIZE(wm8770->supplies); ++i) + regulator_unregister_notifier(wm8770->supplies[i].consumer, + &wm8770->disable_nb[i]); + + return 0; +} + +static struct spi_driver wm8770_spi_driver = { + .driver = { + .name = "wm8770", + .of_match_table = wm8770_of_match, + }, + .probe = wm8770_spi_probe, + .remove = wm8770_spi_remove +}; + +module_spi_driver(wm8770_spi_driver); + +MODULE_DESCRIPTION("ASoC WM8770 driver"); +MODULE_AUTHOR("Dimitris Papastamos "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8770.h b/sound/soc/codecs/wm8770.h new file mode 100644 index 000000000..5f1b3bda6 --- /dev/null +++ b/sound/soc/codecs/wm8770.h @@ -0,0 +1,51 @@ +/* + * wm8770.h -- WM8770 ASoC driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8770_H +#define _WM8770_H + +/* Registers */ +#define WM8770_VOUT1LVOL 0 +#define WM8770_VOUT1RVOL 0x1 +#define WM8770_VOUT2LVOL 0x2 +#define WM8770_VOUT2RVOL 0x3 +#define WM8770_VOUT3LVOL 0x4 +#define WM8770_VOUT3RVOL 0x5 +#define WM8770_VOUT4LVOL 0x6 +#define WM8770_VOUT4RVOL 0x7 +#define WM8770_MSALGVOL 0x8 +#define WM8770_DAC1LVOL 0x9 +#define WM8770_DAC1RVOL 0xa +#define WM8770_DAC2LVOL 0xb +#define WM8770_DAC2RVOL 0xc +#define WM8770_DAC3LVOL 0xd +#define WM8770_DAC3RVOL 0xe +#define WM8770_DAC4LVOL 0xf +#define WM8770_DAC4RVOL 0x10 +#define WM8770_MSDIGVOL 0x11 +#define WM8770_DACPHASE 0x12 +#define WM8770_DACCTRL1 0x13 +#define WM8770_DACMUTE 0x14 +#define WM8770_DACCTRL2 0x15 +#define WM8770_IFACECTRL 0x16 +#define WM8770_MSTRCTRL 0x17 +#define WM8770_PWDNCTRL 0x18 +#define WM8770_ADCLCTRL 0x19 +#define WM8770_ADCRCTRL 0x1a +#define WM8770_ADCMUX 0x1b +#define WM8770_OUTMUX1 0x1c +#define WM8770_OUTMUX2 0x1d +#define WM8770_RESET 0x31 + +#define WM8770_CACHEREGNUM 0x20 + +#endif diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c new file mode 100644 index 000000000..fb357e23f --- /dev/null +++ b/sound/soc/codecs/wm8776.c @@ -0,0 +1,569 @@ +/* + * wm8776.c -- WM8776 ALSA SoC Audio driver + * + * Copyright 2009-12 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: Input ALC/limiter support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8776.h" + +enum wm8776_chip_type { + WM8775 = 1, + WM8776, +}; + +/* codec private data */ +struct wm8776_priv { + struct regmap *regmap; + int sysclk[2]; +}; + +static const struct reg_default wm8776_reg_defaults[] = { + { 0, 0x79 }, + { 1, 0x79 }, + { 2, 0x79 }, + { 3, 0xff }, + { 4, 0xff }, + { 5, 0xff }, + { 6, 0x00 }, + { 7, 0x90 }, + { 8, 0x00 }, + { 9, 0x00 }, + { 10, 0x22 }, + { 11, 0x22 }, + { 12, 0x22 }, + { 13, 0x08 }, + { 14, 0xcf }, + { 15, 0xcf }, + { 16, 0x7b }, + { 17, 0x00 }, + { 18, 0x32 }, + { 19, 0x00 }, + { 20, 0xa6 }, + { 21, 0x01 }, + { 22, 0x01 }, +}; + +static bool wm8776_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8776_RESET: + return true; + default: + return false; + } +} + +static int wm8776_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, WM8776_RESET, 0); +} + +static const DECLARE_TLV_DB_SCALE(hp_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -10350, 50, 1); + +static const struct snd_kcontrol_new wm8776_snd_controls[] = { +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8776_HPLVOL, WM8776_HPRVOL, + 0, 127, 0, hp_tlv), +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8776_DACLVOL, WM8776_DACRVOL, + 0, 255, 0, dac_tlv), +SOC_SINGLE("Digital Playback ZC Switch", WM8776_DACCTRL1, 0, 1, 0), + +SOC_SINGLE("Deemphasis Switch", WM8776_DACCTRL2, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8776_ADCLVOL, WM8776_ADCRVOL, + 0, 255, 0, adc_tlv), +SOC_DOUBLE("Capture Switch", WM8776_ADCMUX, 7, 6, 1, 1), +SOC_DOUBLE_R("Capture ZC Switch", WM8776_ADCLVOL, WM8776_ADCRVOL, 8, 1, 0), +SOC_SINGLE("Capture HPF Switch", WM8776_ADCIFCTRL, 8, 1, 1), +}; + +static const struct snd_kcontrol_new inmix_controls[] = { +SOC_DAPM_SINGLE("AIN1 Switch", WM8776_ADCMUX, 0, 1, 0), +SOC_DAPM_SINGLE("AIN2 Switch", WM8776_ADCMUX, 1, 1, 0), +SOC_DAPM_SINGLE("AIN3 Switch", WM8776_ADCMUX, 2, 1, 0), +SOC_DAPM_SINGLE("AIN4 Switch", WM8776_ADCMUX, 3, 1, 0), +SOC_DAPM_SINGLE("AIN5 Switch", WM8776_ADCMUX, 4, 1, 0), +}; + +static const struct snd_kcontrol_new outmix_controls[] = { +SOC_DAPM_SINGLE("DAC Switch", WM8776_OUTMUX, 0, 1, 0), +SOC_DAPM_SINGLE("AUX Switch", WM8776_OUTMUX, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Switch", WM8776_OUTMUX, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8776_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("AUX"), + +SND_SOC_DAPM_INPUT("AIN1"), +SND_SOC_DAPM_INPUT("AIN2"), +SND_SOC_DAPM_INPUT("AIN3"), +SND_SOC_DAPM_INPUT("AIN4"), +SND_SOC_DAPM_INPUT("AIN5"), + +SND_SOC_DAPM_MIXER("Input Mixer", WM8776_PWRDOWN, 6, 1, + inmix_controls, ARRAY_SIZE(inmix_controls)), + +SND_SOC_DAPM_ADC("ADC", "Capture", WM8776_PWRDOWN, 1, 1), +SND_SOC_DAPM_DAC("DAC", "Playback", WM8776_PWRDOWN, 2, 1), + +SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, + outmix_controls, ARRAY_SIZE(outmix_controls)), + +SND_SOC_DAPM_PGA("Headphone PGA", WM8776_PWRDOWN, 3, 1, NULL, 0), + +SND_SOC_DAPM_OUTPUT("VOUT"), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +}; + +static const struct snd_soc_dapm_route routes[] = { + { "Input Mixer", "AIN1 Switch", "AIN1" }, + { "Input Mixer", "AIN2 Switch", "AIN2" }, + { "Input Mixer", "AIN3 Switch", "AIN3" }, + { "Input Mixer", "AIN4 Switch", "AIN4" }, + { "Input Mixer", "AIN5 Switch", "AIN5" }, + + { "ADC", NULL, "Input Mixer" }, + + { "Output Mixer", "DAC Switch", "DAC" }, + { "Output Mixer", "AUX Switch", "AUX" }, + { "Output Mixer", "Bypass Switch", "Input Mixer" }, + + { "VOUT", NULL, "Output Mixer" }, + + { "Headphone PGA", NULL, "Output Mixer" }, + + { "HPOUTL", NULL, "Headphone PGA" }, + { "HPOUTR", NULL, "Headphone PGA" }, +}; + +static int wm8776_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + int reg, iface, master; + + switch (dai->driver->id) { + case WM8776_DAI_DAC: + reg = WM8776_DACIFCTRL; + master = 0x80; + break; + case WM8776_DAI_ADC: + reg = WM8776_ADCIFCTRL; + master = 0x100; + break; + default: + return -EINVAL; + } + + iface = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x00c; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x008; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x004; + break; + default: + return -EINVAL; + } + + /* Finally, write out the values */ + snd_soc_component_update_bits(component, reg, 0xf, iface); + snd_soc_component_update_bits(component, WM8776_MSTRCTRL, 0x180, master); + + return 0; +} + +static int mclk_ratios[] = { + 128, + 192, + 256, + 384, + 512, + 768, +}; + +static int wm8776_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8776_priv *wm8776 = snd_soc_component_get_drvdata(component); + int iface_reg, iface; + int ratio_shift, master; + int i; + + switch (dai->driver->id) { + case WM8776_DAI_DAC: + iface_reg = WM8776_DACIFCTRL; + master = 0x80; + ratio_shift = 4; + break; + case WM8776_DAI_ADC: + iface_reg = WM8776_ADCIFCTRL; + master = 0x100; + ratio_shift = 0; + break; + default: + return -EINVAL; + } + + /* Set word length */ + switch (params_width(params)) { + case 16: + iface = 0; + break; + case 20: + iface = 0x10; + break; + case 24: + iface = 0x20; + break; + case 32: + iface = 0x30; + break; + default: + dev_err(component->dev, "Unsupported sample size: %i\n", + params_width(params)); + return -EINVAL; + } + + /* Only need to set MCLK/LRCLK ratio if we're master */ + if (snd_soc_component_read32(component, WM8776_MSTRCTRL) & master) { + for (i = 0; i < ARRAY_SIZE(mclk_ratios); i++) { + if (wm8776->sysclk[dai->driver->id] / params_rate(params) + == mclk_ratios[i]) + break; + } + + if (i == ARRAY_SIZE(mclk_ratios)) { + dev_err(component->dev, + "Unable to configure MCLK ratio %d/%d\n", + wm8776->sysclk[dai->driver->id], params_rate(params)); + return -EINVAL; + } + + dev_dbg(component->dev, "MCLK is %dfs\n", mclk_ratios[i]); + + snd_soc_component_update_bits(component, WM8776_MSTRCTRL, + 0x7 << ratio_shift, i << ratio_shift); + } else { + dev_dbg(component->dev, "DAI in slave mode\n"); + } + + snd_soc_component_update_bits(component, iface_reg, 0x30, iface); + + return 0; +} + +static int wm8776_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + return snd_soc_component_write(component, WM8776_DACMUTE, !!mute); +} + +static int wm8776_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8776_priv *wm8776 = snd_soc_component_get_drvdata(component); + + if (WARN_ON(dai->driver->id >= ARRAY_SIZE(wm8776->sysclk))) + return -EINVAL; + + wm8776->sysclk[dai->driver->id] = freq; + + return 0; +} + +static int wm8776_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8776_priv *wm8776 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8776->regmap); + + /* Disable the global powerdown; DAPM does the rest */ + snd_soc_component_update_bits(component, WM8776_PWRDOWN, 1, 0); + } + + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, WM8776_PWRDOWN, 1, 1); + break; + } + + return 0; +} + +#define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8776_dac_ops = { + .digital_mute = wm8776_mute, + .hw_params = wm8776_hw_params, + .set_fmt = wm8776_set_fmt, + .set_sysclk = wm8776_set_sysclk, +}; + +static const struct snd_soc_dai_ops wm8776_adc_ops = { + .hw_params = wm8776_hw_params, + .set_fmt = wm8776_set_fmt, + .set_sysclk = wm8776_set_sysclk, +}; + +static struct snd_soc_dai_driver wm8776_dai[] = { + { + .name = "wm8776-hifi-playback", + .id = WM8776_DAI_DAC, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 32000, + .rate_max = 192000, + .formats = WM8776_FORMATS, + }, + .ops = &wm8776_dac_ops, + }, + { + .name = "wm8776-hifi-capture", + .id = WM8776_DAI_ADC, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 32000, + .rate_max = 96000, + .formats = WM8776_FORMATS, + }, + .ops = &wm8776_adc_ops, + }, +}; + +static int wm8776_probe(struct snd_soc_component *component) +{ + int ret = 0; + + ret = wm8776_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + /* Latch the update bits; right channel only since we always + * update both. */ + snd_soc_component_update_bits(component, WM8776_HPRVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8776_DACRVOL, 0x100, 0x100); + + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8776 = { + .probe = wm8776_probe, + .set_bias_level = wm8776_set_bias_level, + .controls = wm8776_snd_controls, + .num_controls = ARRAY_SIZE(wm8776_snd_controls), + .dapm_widgets = wm8776_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8776_dapm_widgets), + .dapm_routes = routes, + .num_dapm_routes = ARRAY_SIZE(routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct of_device_id wm8776_of_match[] = { + { .compatible = "wlf,wm8776", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8776_of_match); + +static const struct regmap_config wm8776_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8776_RESET, + + .reg_defaults = wm8776_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8776_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8776_volatile, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8776_spi_probe(struct spi_device *spi) +{ + struct wm8776_priv *wm8776; + int ret; + + wm8776 = devm_kzalloc(&spi->dev, sizeof(struct wm8776_priv), + GFP_KERNEL); + if (wm8776 == NULL) + return -ENOMEM; + + wm8776->regmap = devm_regmap_init_spi(spi, &wm8776_regmap); + if (IS_ERR(wm8776->regmap)) + return PTR_ERR(wm8776->regmap); + + spi_set_drvdata(spi, wm8776); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8776, wm8776_dai, ARRAY_SIZE(wm8776_dai)); + + return ret; +} + +static struct spi_driver wm8776_spi_driver = { + .driver = { + .name = "wm8776", + .of_match_table = wm8776_of_match, + }, + .probe = wm8776_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8776_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8776_priv *wm8776; + int ret; + + wm8776 = devm_kzalloc(&i2c->dev, sizeof(struct wm8776_priv), + GFP_KERNEL); + if (wm8776 == NULL) + return -ENOMEM; + + wm8776->regmap = devm_regmap_init_i2c(i2c, &wm8776_regmap); + if (IS_ERR(wm8776->regmap)) + return PTR_ERR(wm8776->regmap); + + i2c_set_clientdata(i2c, wm8776); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8776, wm8776_dai, ARRAY_SIZE(wm8776_dai)); + + return ret; +} + +static const struct i2c_device_id wm8776_i2c_id[] = { + { "wm8775", WM8775 }, + { "wm8776", WM8776 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8776_i2c_id); + +static struct i2c_driver wm8776_i2c_driver = { + .driver = { + .name = "wm8776", + .of_match_table = wm8776_of_match, + }, + .probe = wm8776_i2c_probe, + .id_table = wm8776_i2c_id, +}; +#endif + +static int __init wm8776_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8776_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8776 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8776_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8776 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8776_modinit); + +static void __exit wm8776_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8776_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8776_spi_driver); +#endif +} +module_exit(wm8776_exit); + +MODULE_DESCRIPTION("ASoC WM8776 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8776.h b/sound/soc/codecs/wm8776.h new file mode 100644 index 000000000..4cf1c8e0b --- /dev/null +++ b/sound/soc/codecs/wm8776.h @@ -0,0 +1,48 @@ +/* + * wm8776.h -- WM8776 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8776_H +#define _WM8776_H + +/* Registers */ + +#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 + +#define WM8776_CACHEREGNUM 0x17 + +#define WM8776_DAI_DAC 0 +#define WM8776_DAI_ADC 1 + +#endif diff --git a/sound/soc/codecs/wm8782.c b/sound/soc/codecs/wm8782.c new file mode 100644 index 000000000..317db9a14 --- /dev/null +++ b/sound/soc/codecs/wm8782.c @@ -0,0 +1,90 @@ +/* + * sound/soc/codecs/wm8782.c + * simple, strap-pin configured 24bit 2ch ADC + * + * Copyright: 2011 Raumfeld GmbH + * Author: Johannes Stezenbach + * + * based on ad73311.c + * Copyright: Analog Device Inc. + * Author: Cliff Cai + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct snd_soc_dapm_widget wm8782_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), +}; + +static const struct snd_soc_dapm_route wm8782_dapm_routes[] = { + { "Capture", NULL, "AINL" }, + { "Capture", NULL, "AINR" }, +}; + +static struct snd_soc_dai_driver wm8782_dai = { + .name = "wm8782", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + /* For configurations with FSAMPEN=0 */ + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8782 = { + .dapm_widgets = wm8782_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8782_dapm_widgets), + .dapm_routes = wm8782_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8782_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8782_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm8782, &wm8782_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id wm8782_of_match[] = { + { .compatible = "wlf,wm8782", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8782_of_match); +#endif + +static struct platform_driver wm8782_codec_driver = { + .driver = { + .name = "wm8782", + .of_match_table = of_match_ptr(wm8782_of_match), + }, + .probe = wm8782_probe, +}; + +module_platform_driver(wm8782_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8782 driver"); +MODULE_AUTHOR("Johannes Stezenbach "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8804-i2c.c b/sound/soc/codecs/wm8804-i2c.c new file mode 100644 index 000000000..79541960f --- /dev/null +++ b/sound/soc/codecs/wm8804-i2c.c @@ -0,0 +1,77 @@ +/* + * wm8804-i2c.c -- WM8804 S/PDIF transceiver driver - I2C + * + * Copyright 2015 Cirrus Logic Inc + * + * Author: Charles Keepax + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include "wm8804.h" + +static int wm8804_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &wm8804_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return wm8804_probe(&i2c->dev, regmap); +} + +static int wm8804_i2c_remove(struct i2c_client *i2c) +{ + wm8804_remove(&i2c->dev); + return 0; +} + +static const struct i2c_device_id wm8804_i2c_id[] = { + { "wm8804", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8804_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id wm8804_of_match[] = { + { .compatible = "wlf,wm8804", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8804_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id wm8804_acpi_match[] = { + { "1AEC8804", 0 }, /* Wolfson PCI ID + part ID */ + { "10138804", 0 }, /* Cirrus Logic PCI ID + part ID */ + { }, +}; +MODULE_DEVICE_TABLE(acpi, wm8804_acpi_match); +#endif + +static struct i2c_driver wm8804_i2c_driver = { + .driver = { + .name = "wm8804", + .pm = &wm8804_pm, + .of_match_table = of_match_ptr(wm8804_of_match), + .acpi_match_table = ACPI_PTR(wm8804_acpi_match), + }, + .probe = wm8804_i2c_probe, + .remove = wm8804_i2c_remove, + .id_table = wm8804_i2c_id +}; + +module_i2c_driver(wm8804_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8804 driver - I2C"); +MODULE_AUTHOR("Charles Keepax "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8804-spi.c b/sound/soc/codecs/wm8804-spi.c new file mode 100644 index 000000000..9998c78a2 --- /dev/null +++ b/sound/soc/codecs/wm8804-spi.c @@ -0,0 +1,56 @@ +/* + * wm8804-spi.c -- WM8804 S/PDIF transceiver driver - SPI + * + * Copyright 2015 Cirrus Logic Inc + * + * Author: Charles Keepax + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "wm8804.h" + +static int wm8804_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &wm8804_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return wm8804_probe(&spi->dev, regmap); +} + +static int wm8804_spi_remove(struct spi_device *spi) +{ + wm8804_remove(&spi->dev); + return 0; +} + +static const struct of_device_id wm8804_of_match[] = { + { .compatible = "wlf,wm8804", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8804_of_match); + +static struct spi_driver wm8804_spi_driver = { + .driver = { + .name = "wm8804", + .pm = &wm8804_pm, + .of_match_table = wm8804_of_match, + }, + .probe = wm8804_spi_probe, + .remove = wm8804_spi_remove +}; + +module_spi_driver(wm8804_spi_driver); + +MODULE_DESCRIPTION("ASoC WM8804 driver - SPI"); +MODULE_AUTHOR("Charles Keepax "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c new file mode 100644 index 000000000..89f132499 --- /dev/null +++ b/sound/soc/codecs/wm8804.c @@ -0,0 +1,730 @@ +/* + * wm8804.c -- WM8804 S/PDIF transceiver driver + * + * Copyright 2010-11 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8804.h" + +#define WM8804_NUM_SUPPLIES 2 +static const char *wm8804_supply_names[WM8804_NUM_SUPPLIES] = { + "PVDD", + "DVDD" +}; + +static const struct reg_default wm8804_reg_defaults[] = { + { 3, 0x21 }, /* R3 - PLL1 */ + { 4, 0xFD }, /* R4 - PLL2 */ + { 5, 0x36 }, /* R5 - PLL3 */ + { 6, 0x07 }, /* R6 - PLL4 */ + { 7, 0x16 }, /* R7 - PLL5 */ + { 8, 0x18 }, /* R8 - PLL6 */ + { 9, 0xFF }, /* R9 - SPDMODE */ + { 10, 0x00 }, /* R10 - INTMASK */ + { 18, 0x00 }, /* R18 - SPDTX1 */ + { 19, 0x00 }, /* R19 - SPDTX2 */ + { 20, 0x00 }, /* R20 - SPDTX3 */ + { 21, 0x71 }, /* R21 - SPDTX4 */ + { 22, 0x0B }, /* R22 - SPDTX5 */ + { 23, 0x70 }, /* R23 - GPO0 */ + { 24, 0x57 }, /* R24 - GPO1 */ + { 26, 0x42 }, /* R26 - GPO2 */ + { 27, 0x06 }, /* R27 - AIFTX */ + { 28, 0x06 }, /* R28 - AIFRX */ + { 29, 0x80 }, /* R29 - SPDRX1 */ + { 30, 0x07 }, /* R30 - PWRDN */ +}; + +struct wm8804_priv { + struct device *dev; + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES]; + struct notifier_block disable_nb[WM8804_NUM_SUPPLIES]; + int mclk_div; + + struct gpio_desc *reset; + + int aif_pwr; +}; + +static int txsrc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static int wm8804_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +/* + * We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define WM8804_REGULATOR_EVENT(n) \ +static int wm8804_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct wm8804_priv *wm8804 = container_of(nb, struct wm8804_priv, \ + disable_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + regcache_mark_dirty(wm8804->regmap); \ + } \ + return 0; \ +} + +WM8804_REGULATOR_EVENT(0) +WM8804_REGULATOR_EVENT(1) + +static const char *txsrc_text[] = { "S/PDIF RX", "AIF" }; +static SOC_ENUM_SINGLE_DECL(txsrc, WM8804_SPDTX4, 6, txsrc_text); + +static const struct snd_kcontrol_new wm8804_tx_source_mux[] = { + SOC_DAPM_ENUM_EXT("Input Source", txsrc, + snd_soc_dapm_get_enum_double, txsrc_put), +}; + +static const struct snd_soc_dapm_widget wm8804_dapm_widgets[] = { +SND_SOC_DAPM_OUTPUT("SPDIF Out"), +SND_SOC_DAPM_INPUT("SPDIF In"), + +SND_SOC_DAPM_PGA("SPDIFTX", WM8804_PWRDN, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("SPDIFRX", WM8804_PWRDN, 1, 1, NULL, 0), + +SND_SOC_DAPM_MUX("Tx Source", SND_SOC_NOPM, 6, 0, wm8804_tx_source_mux), + +SND_SOC_DAPM_AIF_OUT_E("AIFTX", NULL, 0, SND_SOC_NOPM, 0, 0, wm8804_aif_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_AIF_IN_E("AIFRX", NULL, 0, SND_SOC_NOPM, 0, 0, wm8804_aif_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route wm8804_dapm_routes[] = { + { "AIFRX", NULL, "Playback" }, + { "Tx Source", "AIF", "AIFRX" }, + + { "SPDIFRX", NULL, "SPDIF In" }, + { "Tx Source", "S/PDIF RX", "SPDIFRX" }, + + { "SPDIFTX", NULL, "Tx Source" }, + { "SPDIF Out", NULL, "SPDIFTX" }, + + { "AIFTX", NULL, "SPDIFRX" }, + { "Capture", NULL, "AIFTX" }, +}; + +static int wm8804_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8804_priv *wm8804 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* power up the aif */ + if (!wm8804->aif_pwr) + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x10, 0x0); + wm8804->aif_pwr++; + break; + case SND_SOC_DAPM_POST_PMD: + /* power down only both paths are disabled */ + wm8804->aif_pwr--; + if (!wm8804->aif_pwr) + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x10, 0x10); + break; + } + + return 0; +} + +static int txsrc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val = ucontrol->value.enumerated.item[0] << e->shift_l; + unsigned int mask = 1 << e->shift_l; + unsigned int txpwr; + + if (val != 0 && val != mask) + return -EINVAL; + + snd_soc_dapm_mutex_lock(dapm); + + if (snd_soc_component_test_bits(component, e->reg, mask, val)) { + /* save the current power state of the transmitter */ + txpwr = snd_soc_component_read32(component, WM8804_PWRDN) & 0x4; + + /* power down the transmitter */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x4); + + /* set the tx source */ + snd_soc_component_update_bits(component, e->reg, mask, val); + + /* restore the transmitter's configuration */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, txpwr); + } + + snd_soc_dapm_mutex_unlock(dapm); + + return 0; +} + +static bool wm8804_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8804_RST_DEVID1: + case WM8804_DEVID2: + case WM8804_DEVREV: + case WM8804_INTSTAT: + case WM8804_SPDSTAT: + case WM8804_RXCHAN1: + case WM8804_RXCHAN2: + case WM8804_RXCHAN3: + case WM8804_RXCHAN4: + case WM8804_RXCHAN5: + return true; + default: + return false; + } +} + +static int wm8804_soft_reset(struct wm8804_priv *wm8804) +{ + return regmap_write(wm8804->regmap, WM8804_RST_DEVID1, 0x0); +} + +static int wm8804_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component; + u16 format, master, bcp, lrp; + + component = dai->component; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + format = 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = 0x1; + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + format = 0x3; + break; + default: + dev_err(dai->dev, "Unknown dai format\n"); + return -EINVAL; + } + + /* set data format */ + snd_soc_component_update_bits(component, WM8804_AIFTX, 0x3, format); + snd_soc_component_update_bits(component, WM8804_AIFRX, 0x3, format); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + default: + dev_err(dai->dev, "Unknown master/slave configuration\n"); + return -EINVAL; + } + + /* set master/slave mode */ + snd_soc_component_update_bits(component, WM8804_AIFRX, 0x40, master << 6); + + bcp = lrp = 0; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + bcp = lrp = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + bcp = 1; + break; + case SND_SOC_DAIFMT_NB_IF: + lrp = 1; + break; + default: + dev_err(dai->dev, "Unknown polarity configuration\n"); + return -EINVAL; + } + + /* set frame inversion */ + snd_soc_component_update_bits(component, WM8804_AIFTX, 0x10 | 0x20, + (bcp << 4) | (lrp << 5)); + snd_soc_component_update_bits(component, WM8804_AIFRX, 0x10 | 0x20, + (bcp << 4) | (lrp << 5)); + return 0; +} + +static int wm8804_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component; + u16 blen; + + component = dai->component; + + switch (params_width(params)) { + case 16: + blen = 0x0; + break; + case 20: + blen = 0x1; + break; + case 24: + blen = 0x2; + break; + default: + dev_err(dai->dev, "Unsupported word length: %u\n", + params_width(params)); + return -EINVAL; + } + + /* set word length */ + snd_soc_component_update_bits(component, WM8804_AIFTX, 0xc, blen << 2); + snd_soc_component_update_bits(component, WM8804_AIFRX, 0xc, blen << 2); + + return 0; +} + +struct pll_div { + u32 prescale:1; + u32 mclkdiv:1; + u32 freqmode:2; + u32 n:4; + u32 k:22; +}; + +/* PLL rate to output rate divisions */ +static struct { + unsigned int div; + unsigned int freqmode; + unsigned int mclkdiv; +} post_table[] = { + { 2, 0, 0 }, + { 4, 0, 1 }, + { 4, 1, 0 }, + { 8, 1, 1 }, + { 8, 2, 0 }, + { 16, 2, 1 }, + { 12, 3, 0 }, + { 24, 3, 1 } +}; + +#define FIXED_PLL_SIZE ((1ULL << 22) * 10) +static int pll_factors(struct pll_div *pll_div, unsigned int target, + unsigned int source, unsigned int mclk_div) +{ + u64 Kpart; + unsigned long int K, Ndiv, Nmod, tmp; + int i; + + /* + * Scale the output frequency up; the PLL should run in the + * region of 90-100MHz. + */ + for (i = 0; i < ARRAY_SIZE(post_table); i++) { + tmp = target * post_table[i].div; + if ((tmp >= 90000000 && tmp <= 100000000) && + (mclk_div == post_table[i].mclkdiv)) { + pll_div->freqmode = post_table[i].freqmode; + pll_div->mclkdiv = post_table[i].mclkdiv; + target *= post_table[i].div; + break; + } + } + + if (i == ARRAY_SIZE(post_table)) { + pr_err("%s: Unable to scale output frequency: %uHz\n", + __func__, target); + return -EINVAL; + } + + pll_div->prescale = 0; + Ndiv = target / source; + if (Ndiv < 5) { + source >>= 1; + pll_div->prescale = 1; + Ndiv = target / source; + } + + if (Ndiv < 5 || Ndiv > 13) { + pr_err("%s: WM8804 N value is not within the recommended range: %lu\n", + __func__, Ndiv); + return -EINVAL; + } + pll_div->n = Ndiv; + + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (u64)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xffffffff; + if ((K % 10) >= 5) + K += 5; + K /= 10; + pll_div->k = K; + + return 0; +} + +static int wm8804_set_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct snd_soc_component *component = dai->component; + struct wm8804_priv *wm8804 = snd_soc_component_get_drvdata(component); + bool change; + + if (!freq_in || !freq_out) { + /* disable the PLL */ + regmap_update_bits_check(wm8804->regmap, WM8804_PWRDN, + 0x1, 0x1, &change); + if (change) + pm_runtime_put(wm8804->dev); + } else { + int ret; + struct pll_div pll_div; + + ret = pll_factors(&pll_div, freq_out, freq_in, + wm8804->mclk_div); + if (ret) + return ret; + + /* power down the PLL before reprogramming it */ + regmap_update_bits_check(wm8804->regmap, WM8804_PWRDN, + 0x1, 0x1, &change); + if (!change) + pm_runtime_get_sync(wm8804->dev); + + /* set PLLN and PRESCALE */ + snd_soc_component_update_bits(component, WM8804_PLL4, 0xf | 0x10, + pll_div.n | (pll_div.prescale << 4)); + /* set mclkdiv and freqmode */ + snd_soc_component_update_bits(component, WM8804_PLL5, 0x3 | 0x8, + pll_div.freqmode | (pll_div.mclkdiv << 3)); + /* set PLLK */ + snd_soc_component_write(component, WM8804_PLL1, pll_div.k & 0xff); + snd_soc_component_write(component, WM8804_PLL2, (pll_div.k >> 8) & 0xff); + snd_soc_component_write(component, WM8804_PLL3, pll_div.k >> 16); + + /* power up the PLL */ + snd_soc_component_update_bits(component, WM8804_PWRDN, 0x1, 0); + } + + return 0; +} + +static int wm8804_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component; + + component = dai->component; + + switch (clk_id) { + case WM8804_TX_CLKSRC_MCLK: + if ((freq >= 10000000 && freq <= 14400000) + || (freq >= 16280000 && freq <= 27000000)) + snd_soc_component_update_bits(component, WM8804_PLL6, 0x80, 0x80); + else { + dev_err(dai->dev, "OSCCLOCK is not within the " + "recommended range: %uHz\n", freq); + return -EINVAL; + } + break; + case WM8804_TX_CLKSRC_PLL: + snd_soc_component_update_bits(component, WM8804_PLL6, 0x80, 0); + break; + case WM8804_CLKOUT_SRC_CLK1: + snd_soc_component_update_bits(component, WM8804_PLL6, 0x8, 0); + break; + case WM8804_CLKOUT_SRC_OSCCLK: + snd_soc_component_update_bits(component, WM8804_PLL6, 0x8, 0x8); + break; + default: + dev_err(dai->dev, "Unknown clock source: %d\n", clk_id); + return -EINVAL; + } + + return 0; +} + +static int wm8804_set_clkdiv(struct snd_soc_dai *dai, + int div_id, int div) +{ + struct snd_soc_component *component; + struct wm8804_priv *wm8804; + + component = dai->component; + switch (div_id) { + case WM8804_CLKOUT_DIV: + snd_soc_component_update_bits(component, WM8804_PLL5, 0x30, + (div & 0x3) << 4); + break; + case WM8804_MCLK_DIV: + wm8804 = snd_soc_component_get_drvdata(component); + wm8804->mclk_div = div; + break; + default: + dev_err(dai->dev, "Unknown clock divider: %d\n", div_id); + return -EINVAL; + } + return 0; +} + +static const struct snd_soc_dai_ops wm8804_dai_ops = { + .hw_params = wm8804_hw_params, + .set_fmt = wm8804_set_fmt, + .set_sysclk = wm8804_set_sysclk, + .set_clkdiv = wm8804_set_clkdiv, + .set_pll = wm8804_set_pll +}; + +#define WM8804_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +#define WM8804_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) + +static struct snd_soc_dai_driver wm8804_dai = { + .name = "wm8804-spdif", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8804_RATES, + .formats = WM8804_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8804_RATES, + .formats = WM8804_FORMATS, + }, + .ops = &wm8804_dai_ops, + .symmetric_rates = 1 +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8804 = { + .dapm_widgets = wm8804_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8804_dapm_widgets), + .dapm_routes = wm8804_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8804_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +const struct regmap_config wm8804_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = WM8804_MAX_REGISTER, + .volatile_reg = wm8804_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8804_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8804_reg_defaults), +}; +EXPORT_SYMBOL_GPL(wm8804_regmap_config); + +int wm8804_probe(struct device *dev, struct regmap *regmap) +{ + struct wm8804_priv *wm8804; + unsigned int id1, id2; + int i, ret; + + wm8804 = devm_kzalloc(dev, sizeof(*wm8804), GFP_KERNEL); + if (!wm8804) + return -ENOMEM; + + dev_set_drvdata(dev, wm8804); + + wm8804->dev = dev; + wm8804->regmap = regmap; + + wm8804->reset = devm_gpiod_get_optional(dev, "wlf,reset", + GPIOD_OUT_LOW); + if (IS_ERR(wm8804->reset)) { + ret = PTR_ERR(wm8804->reset); + dev_err(dev, "Failed to get reset line: %d\n", ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) + wm8804->supplies[i].supply = wm8804_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8804->supplies), + wm8804->supplies); + if (ret) { + dev_err(dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8804->disable_nb[0].notifier_call = wm8804_regulator_event_0; + wm8804->disable_nb[1].notifier_call = wm8804_regulator_event_1; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8804->supplies); i++) { + struct regulator *regulator = wm8804->supplies[i].consumer; + + ret = devm_regulator_register_notifier(regulator, + &wm8804->disable_nb[i]); + if (ret != 0) { + dev_err(dev, + "Failed to register regulator notifier: %d\n", + ret); + return ret; + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), + wm8804->supplies); + if (ret) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + gpiod_set_value_cansleep(wm8804->reset, 1); + + ret = regmap_read(regmap, WM8804_RST_DEVID1, &id1); + if (ret < 0) { + dev_err(dev, "Failed to read device ID: %d\n", ret); + goto err_reg_enable; + } + + ret = regmap_read(regmap, WM8804_DEVID2, &id2); + if (ret < 0) { + dev_err(dev, "Failed to read device ID: %d\n", ret); + goto err_reg_enable; + } + + id2 = (id2 << 8) | id1; + + if (id2 != 0x8805) { + dev_err(dev, "Invalid device ID: %#x\n", id2); + ret = -EINVAL; + goto err_reg_enable; + } + + ret = regmap_read(regmap, WM8804_DEVREV, &id1); + if (ret < 0) { + dev_err(dev, "Failed to read device revision: %d\n", + ret); + goto err_reg_enable; + } + dev_info(dev, "revision %c\n", id1 + 'A'); + + if (!wm8804->reset) { + ret = wm8804_soft_reset(wm8804); + if (ret < 0) { + dev_err(dev, "Failed to issue reset: %d\n", ret); + goto err_reg_enable; + } + } + + ret = devm_snd_soc_register_component(dev, &soc_component_dev_wm8804, + &wm8804_dai, 1); + if (ret < 0) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + goto err_reg_enable; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_reg_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), wm8804->supplies); + return ret; +} +EXPORT_SYMBOL_GPL(wm8804_probe); + +void wm8804_remove(struct device *dev) +{ + pm_runtime_disable(dev); +} +EXPORT_SYMBOL_GPL(wm8804_remove); + +#if IS_ENABLED(CONFIG_PM) +static int wm8804_runtime_resume(struct device *dev) +{ + struct wm8804_priv *wm8804 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), + wm8804->supplies); + if (ret) { + dev_err(wm8804->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + regcache_sync(wm8804->regmap); + + /* Power up OSCCLK */ + regmap_update_bits(wm8804->regmap, WM8804_PWRDN, 0x8, 0x0); + + return 0; +} + +static int wm8804_runtime_suspend(struct device *dev) +{ + struct wm8804_priv *wm8804 = dev_get_drvdata(dev); + + /* Power down OSCCLK */ + regmap_update_bits(wm8804->regmap, WM8804_PWRDN, 0x8, 0x8); + + regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), + wm8804->supplies); + + return 0; +} +#endif + +const struct dev_pm_ops wm8804_pm = { + SET_RUNTIME_PM_OPS(wm8804_runtime_suspend, wm8804_runtime_resume, NULL) +}; +EXPORT_SYMBOL_GPL(wm8804_pm); + +MODULE_DESCRIPTION("ASoC WM8804 driver"); +MODULE_AUTHOR("Dimitris Papastamos "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8804.h b/sound/soc/codecs/wm8804.h new file mode 100644 index 000000000..aa72fa66c --- /dev/null +++ b/sound/soc/codecs/wm8804.h @@ -0,0 +1,73 @@ +/* + * wm8804.h -- WM8804 S/PDIF transceiver driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8804_H +#define _WM8804_H + +#include + +/* + * Register values. + */ +#define WM8804_RST_DEVID1 0x00 +#define WM8804_DEVID2 0x01 +#define WM8804_DEVREV 0x02 +#define WM8804_PLL1 0x03 +#define WM8804_PLL2 0x04 +#define WM8804_PLL3 0x05 +#define WM8804_PLL4 0x06 +#define WM8804_PLL5 0x07 +#define WM8804_PLL6 0x08 +#define WM8804_SPDMODE 0x09 +#define WM8804_INTMASK 0x0A +#define WM8804_INTSTAT 0x0B +#define WM8804_SPDSTAT 0x0C +#define WM8804_RXCHAN1 0x0D +#define WM8804_RXCHAN2 0x0E +#define WM8804_RXCHAN3 0x0F +#define WM8804_RXCHAN4 0x10 +#define WM8804_RXCHAN5 0x11 +#define WM8804_SPDTX1 0x12 +#define WM8804_SPDTX2 0x13 +#define WM8804_SPDTX3 0x14 +#define WM8804_SPDTX4 0x15 +#define WM8804_SPDTX5 0x16 +#define WM8804_GPO0 0x17 +#define WM8804_GPO1 0x18 +#define WM8804_GPO2 0x1A +#define WM8804_AIFTX 0x1B +#define WM8804_AIFRX 0x1C +#define WM8804_SPDRX1 0x1D +#define WM8804_PWRDN 0x1E + +#define WM8804_REGISTER_COUNT 30 +#define WM8804_MAX_REGISTER 0x1E + +#define WM8804_TX_CLKSRC_MCLK 1 +#define WM8804_TX_CLKSRC_PLL 2 + +#define WM8804_CLKOUT_SRC_CLK1 3 +#define WM8804_CLKOUT_SRC_OSCCLK 4 + +#define WM8804_CLKOUT_DIV 1 +#define WM8804_MCLK_DIV 2 + +#define WM8804_MCLKDIV_256FS 0 +#define WM8804_MCLKDIV_128FS 1 + +extern const struct regmap_config wm8804_regmap_config; +extern const struct dev_pm_ops wm8804_pm; + +int wm8804_probe(struct device *dev, struct regmap *regmap); +void wm8804_remove(struct device *dev); + +#endif /* _WM8804_H */ diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c new file mode 100644 index 000000000..1a14e9029 --- /dev/null +++ b/sound/soc/codecs/wm8900.c @@ -0,0 +1,1356 @@ +/* + * wm8900.c -- WM8900 ALSA Soc Audio driver + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - Tristating. + * - TDM. + * - Jack detect. + * - FLL source configuration, currently only MCLK is supported. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8900.h" + +/* WM8900 register space */ +#define WM8900_REG_RESET 0x0 +#define WM8900_REG_ID 0x0 +#define WM8900_REG_POWER1 0x1 +#define WM8900_REG_POWER2 0x2 +#define WM8900_REG_POWER3 0x3 +#define WM8900_REG_AUDIO1 0x4 +#define WM8900_REG_AUDIO2 0x5 +#define WM8900_REG_CLOCKING1 0x6 +#define WM8900_REG_CLOCKING2 0x7 +#define WM8900_REG_AUDIO3 0x8 +#define WM8900_REG_AUDIO4 0x9 +#define WM8900_REG_DACCTRL 0xa +#define WM8900_REG_LDAC_DV 0xb +#define WM8900_REG_RDAC_DV 0xc +#define WM8900_REG_SIDETONE 0xd +#define WM8900_REG_ADCCTRL 0xe +#define WM8900_REG_LADC_DV 0xf +#define WM8900_REG_RADC_DV 0x10 +#define WM8900_REG_GPIO 0x12 +#define WM8900_REG_INCTL 0x15 +#define WM8900_REG_LINVOL 0x16 +#define WM8900_REG_RINVOL 0x17 +#define WM8900_REG_INBOOSTMIX1 0x18 +#define WM8900_REG_INBOOSTMIX2 0x19 +#define WM8900_REG_ADCPATH 0x1a +#define WM8900_REG_AUXBOOST 0x1b +#define WM8900_REG_ADDCTL 0x1e +#define WM8900_REG_FLLCTL1 0x24 +#define WM8900_REG_FLLCTL2 0x25 +#define WM8900_REG_FLLCTL3 0x26 +#define WM8900_REG_FLLCTL4 0x27 +#define WM8900_REG_FLLCTL5 0x28 +#define WM8900_REG_FLLCTL6 0x29 +#define WM8900_REG_LOUTMIXCTL1 0x2c +#define WM8900_REG_ROUTMIXCTL1 0x2d +#define WM8900_REG_BYPASS1 0x2e +#define WM8900_REG_BYPASS2 0x2f +#define WM8900_REG_AUXOUT_CTL 0x30 +#define WM8900_REG_LOUT1CTL 0x33 +#define WM8900_REG_ROUT1CTL 0x34 +#define WM8900_REG_LOUT2CTL 0x35 +#define WM8900_REG_ROUT2CTL 0x36 +#define WM8900_REG_HPCTL1 0x3a +#define WM8900_REG_OUTBIASCTL 0x73 + +#define WM8900_MAXREG 0x80 + +#define WM8900_REG_ADDCTL_OUT1_DIS 0x80 +#define WM8900_REG_ADDCTL_OUT2_DIS 0x40 +#define WM8900_REG_ADDCTL_VMID_DIS 0x20 +#define WM8900_REG_ADDCTL_BIAS_SRC 0x10 +#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04 +#define WM8900_REG_ADDCTL_TEMP_SD 0x02 + +#define WM8900_REG_GPIO_TEMP_ENA 0x2 + +#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100 +#define WM8900_REG_POWER1_BIAS_ENA 0x0008 +#define WM8900_REG_POWER1_VMID_BUF_ENA 0x0004 +#define WM8900_REG_POWER1_FLL_ENA 0x0040 + +#define WM8900_REG_POWER2_SYSCLK_ENA 0x8000 +#define WM8900_REG_POWER2_ADCL_ENA 0x0002 +#define WM8900_REG_POWER2_ADCR_ENA 0x0001 + +#define WM8900_REG_POWER3_DACL_ENA 0x0002 +#define WM8900_REG_POWER3_DACR_ENA 0x0001 + +#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018 +#define WM8900_REG_AUDIO1_LRCLK_INV 0x0080 +#define WM8900_REG_AUDIO1_BCLK_INV 0x0100 + +#define WM8900_REG_CLOCKING1_BCLK_DIR 0x1 +#define WM8900_REG_CLOCKING1_MCLK_SRC 0x100 +#define WM8900_REG_CLOCKING1_BCLK_MASK 0x01e +#define WM8900_REG_CLOCKING1_OPCLK_MASK 0x7000 + +#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0 +#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c + +#define WM8900_REG_DACCTRL_MUTE 0x004 +#define WM8900_REG_DACCTRL_DAC_SB_FILT 0x100 +#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400 + +#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800 + +#define WM8900_REG_AUDIO4_DACLRC_DIR 0x0800 + +#define WM8900_REG_FLLCTL1_OSC_ENA 0x100 + +#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100 + +#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80 +#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40 +#define WM8900_REG_HPCTL1_HP_CLAMP_IP 0x20 +#define WM8900_REG_HPCTL1_HP_CLAMP_OP 0x10 +#define WM8900_REG_HPCTL1_HP_SHORT 0x08 +#define WM8900_REG_HPCTL1_HP_SHORT2 0x04 + +#define WM8900_LRC_MASK 0x03ff + +struct wm8900_priv { + struct regmap *regmap; + + u32 fll_in; /* FLL input frequency */ + u32 fll_out; /* FLL output frequency */ +}; + +/* + * wm8900 register cache. We can't read the entire register space and we + * have slow control buses so we cache the registers. + */ +static const struct reg_default wm8900_reg_defaults[] = { + { 1, 0x0000 }, + { 2, 0xc000 }, + { 3, 0x0000 }, + { 4, 0x4050 }, + { 5, 0x4000 }, + { 6, 0x0008 }, + { 7, 0x0000 }, + { 8, 0x0040 }, + { 9, 0x0040 }, + { 10, 0x1004 }, + { 11, 0x00c0 }, + { 12, 0x00c0 }, + { 13, 0x0000 }, + { 14, 0x0100 }, + { 15, 0x00c0 }, + { 16, 0x00c0 }, + { 17, 0x0000 }, + { 18, 0xb001 }, + { 19, 0x0000 }, + { 20, 0x0000 }, + { 21, 0x0044 }, + { 22, 0x004c }, + { 23, 0x004c }, + { 24, 0x0044 }, + { 25, 0x0044 }, + { 26, 0x0000 }, + { 27, 0x0044 }, + { 28, 0x0000 }, + { 29, 0x0000 }, + { 30, 0x0002 }, + { 31, 0x0000 }, + { 32, 0x0000 }, + { 33, 0x0000 }, + { 34, 0x0000 }, + { 35, 0x0000 }, + { 36, 0x0008 }, + { 37, 0x0000 }, + { 38, 0x0000 }, + { 39, 0x0008 }, + { 40, 0x0097 }, + { 41, 0x0100 }, + { 42, 0x0000 }, + { 43, 0x0000 }, + { 44, 0x0050 }, + { 45, 0x0050 }, + { 46, 0x0055 }, + { 47, 0x0055 }, + { 48, 0x0055 }, + { 49, 0x0000 }, + { 50, 0x0000 }, + { 51, 0x0079 }, + { 52, 0x0079 }, + { 53, 0x0079 }, + { 54, 0x0079 }, + { 55, 0x0000 }, +}; + +static bool wm8900_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8900_REG_ID: + return true; + default: + return false; + } +} + +static void wm8900_reset(struct snd_soc_component *component) +{ + snd_soc_component_write(component, WM8900_REG_RESET, 0); +} + +static int wm8900_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 hpctl1 = snd_soc_component_read32(component, WM8900_REG_HPCTL1); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Clamp headphone outputs */ + hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMU: + /* Enable the input stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP; + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT | + WM8900_REG_HPCTL1_HP_SHORT2 | + WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + + msleep(400); + + /* Enable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + + /* Remove the shorts */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Short the output */ + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + + /* Disable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + + /* Clamp the outputs and power down input */ + hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + snd_soc_component_write(component, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Disable everything */ + snd_soc_component_write(component, WM8900_REG_HPCTL1, 0); + break; + + default: + WARN(1, "Invalid event %d\n", event); + break; + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0); + +static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0); + +static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0); + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); + +static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" }; + +static SOC_ENUM_SINGLE_DECL(mic_bias_level, + WM8900_REG_INCTL, 8, mic_bias_level_txt); + +static const char *dac_mute_rate_txt[] = { "Fast", "Slow" }; + +static SOC_ENUM_SINGLE_DECL(dac_mute_rate, + WM8900_REG_DACCTRL, 7, dac_mute_rate_txt); + +static const char *dac_deemphasis_txt[] = { + "Disabled", "32kHz", "44.1kHz", "48kHz" +}; + +static SOC_ENUM_SINGLE_DECL(dac_deemphasis, + WM8900_REG_DACCTRL, 4, dac_deemphasis_txt); + +static const char *adc_hpf_cut_txt[] = { + "Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3" +}; + +static SOC_ENUM_SINGLE_DECL(adc_hpf_cut, + WM8900_REG_ADCCTRL, 5, adc_hpf_cut_txt); + +static const char *lr_txt[] = { + "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(aifl_src, + WM8900_REG_AUDIO1, 15, lr_txt); + +static SOC_ENUM_SINGLE_DECL(aifr_src, + WM8900_REG_AUDIO1, 14, lr_txt); + +static SOC_ENUM_SINGLE_DECL(dacl_src, + WM8900_REG_AUDIO2, 15, lr_txt); + +static SOC_ENUM_SINGLE_DECL(dacr_src, + WM8900_REG_AUDIO2, 14, lr_txt); + +static const char *sidetone_txt[] = { + "Disabled", "Left ADC", "Right ADC" +}; + +static SOC_ENUM_SINGLE_DECL(dacl_sidetone, + WM8900_REG_SIDETONE, 2, sidetone_txt); + +static SOC_ENUM_SINGLE_DECL(dacr_sidetone, + WM8900_REG_SIDETONE, 0, sidetone_txt); + +static const struct snd_kcontrol_new wm8900_snd_controls[] = { +SOC_ENUM("Mic Bias Level", mic_bias_level), + +SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1), +SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0), + +SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1), +SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0), + +SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1), +SOC_ENUM("DAC Mute Rate", dac_mute_rate), +SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0), +SOC_ENUM("DAC Deemphasis", dac_deemphasis), +SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL, + 12, 1, 0), + +SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0), +SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut), +SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0), +SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0, + adc_svol_tlv), +SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0, + adc_svol_tlv), +SOC_ENUM("Left Digital Audio Source", aifl_src), +SOC_ENUM("Right Digital Audio Source", aifr_src), + +SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0, + dac_boost_tlv), +SOC_ENUM("Left DAC Source", dacl_src), +SOC_ENUM("Right DAC Source", dacr_src), +SOC_ENUM("Left DAC Sidetone", dacl_sidetone), +SOC_ENUM("Right DAC Sidetone", dacr_sidetone), +SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Digital Playback Volume", + WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV, + 1, 96, 0, dac_tlv), +SOC_DOUBLE_R_TLV("Digital Capture Volume", + WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv), + +SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0, + in_boost_tlv), + +SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 6, 1, 1), +SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("LINEOUT2 Volume", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT2 Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1), +SOC_DOUBLE_R("LINEOUT2 ZC Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0), +SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1, + 0, 1, 1), + +}; + +static const struct snd_kcontrol_new wm8900_dapm_loutput2_control = +SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0); + +static const struct snd_kcontrol_new wm8900_dapm_routput2_control = +SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0); + +static const struct snd_kcontrol_new wm8900_loutmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0), +SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_routmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linpga_controls[] = { +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinpga_controls[] = { +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0), +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0), +}; + +static const char *wm8900_lp_mux[] = { "Disabled", "Enabled" }; + +static SOC_ENUM_SINGLE_DECL(wm8900_lineout2_lp_mux, + WM8900_REG_LOUTMIXCTL1, 1, wm8900_lp_mux); + +static const struct snd_kcontrol_new wm8900_lineout2_lp = +SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux); + +static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = { + +/* Externally visible pins */ +SND_SOC_DAPM_OUTPUT("LINEOUT1L"), +SND_SOC_DAPM_OUTPUT("LINEOUT1R"), +SND_SOC_DAPM_OUTPUT("LINEOUT2L"), +SND_SOC_DAPM_OUTPUT("LINEOUT2R"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), + +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT3"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("AUX"), + +SND_SOC_DAPM_VMID("VMID"), + +/* Input */ +SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0, + wm8900_linpga_controls, + ARRAY_SIZE(wm8900_linpga_controls)), +SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0, + wm8900_rinpga_controls, + ARRAY_SIZE(wm8900_rinpga_controls)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0, + wm8900_linmix_controls, + ARRAY_SIZE(wm8900_linmix_controls)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0, + wm8900_rinmix_controls, + ARRAY_SIZE(wm8900_rinmix_controls)), + +SND_SOC_DAPM_SUPPLY("Mic Bias", WM8900_REG_POWER1, 4, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0), + +/* Output */ +SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0), +SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0), + +SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0, + wm8900_hp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0), + +SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp), +SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0, + wm8900_loutmix_controls, + ARRAY_SIZE(wm8900_loutmix_controls)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0, + wm8900_routmix_controls, + ARRAY_SIZE(wm8900_routmix_controls)), +}; + +/* Target, Path, Source */ +static const struct snd_soc_dapm_route wm8900_dapm_routes[] = { +/* Inputs */ +{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"}, +{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"}, + +{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"}, +{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"}, + +{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"}, +{"Left Input Mixer", "AUX Switch", "AUX"}, +{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"}, + +{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"}, +{"Right Input Mixer", "AUX Switch", "AUX"}, +{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"}, + +{"ADCL", NULL, "Left Input Mixer"}, +{"ADCR", NULL, "Right Input Mixer"}, + +/* Outputs */ +{"LINEOUT1L", NULL, "LINEOUT1L PGA"}, +{"LINEOUT1L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT1R", NULL, "LINEOUT1R PGA"}, +{"LINEOUT1R PGA", NULL, "Right Output Mixer"}, + +{"LINEOUT2L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"}, +{"LINEOUT2 LP", "Enabled", "Left Output Mixer"}, +{"LINEOUT2L", NULL, "LINEOUT2 LP"}, + +{"LINEOUT2R PGA", NULL, "Right Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"}, +{"LINEOUT2 LP", "Enabled", "Right Output Mixer"}, +{"LINEOUT2R", NULL, "LINEOUT2 LP"}, + +{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"}, +{"Left Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Left Output Mixer", "DACL Switch", "DACL"}, + +{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"}, +{"Right Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Right Output Mixer", "DACR Switch", "DACR"}, + +/* Note that the headphone output stage needs to be connected + * externally to LINEOUT2 via DC blocking capacitors. Other + * configurations are not supported. + * + * Note also that left and right headphone paths are treated as a + * mono path. + */ +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"HP_L", NULL, "Headphone Amplifier"}, +{"HP_R", NULL, "Headphone Amplifier"}, +}; + +static int wm8900_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 reg; + + reg = snd_soc_component_read32(component, WM8900_REG_AUDIO1) & ~0x60; + + switch (params_width(params)) { + case 16: + break; + case 20: + reg |= 0x20; + break; + case 24: + reg |= 0x40; + break; + case 32: + reg |= 0x60; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8900_REG_AUDIO1, reg); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = snd_soc_component_read32(component, WM8900_REG_DACCTRL); + + if (params_rate(params) <= 24000) + reg |= WM8900_REG_DACCTRL_DAC_SB_FILT; + else + reg &= ~WM8900_REG_DACCTRL_DAC_SB_FILT; + + snd_soc_component_write(component, WM8900_REG_DACCTRL, reg); + } + + return 0; +} + +/* FLL divisors */ +struct _fll_div { + u16 fll_ratio; + u16 fllclk_div; + u16 fll_slow_lock_ref; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + + if (WARN_ON(!Fout)) + return -EINVAL; + + /* The FLL must run at 90-100MHz which is then scaled down to + * the output value by FLLCLK_DIV. */ + target = Fout; + div = 1; + while (target < 90000000) { + div *= 2; + target *= 2; + } + + if (target > 100000000) + printk(KERN_WARNING "wm8900: FLL rate %u out of range, Fref=%u" + " Fout=%u\n", target, Fref, Fout); + if (div > 32) { + printk(KERN_ERR "wm8900: Invalid FLL division rate %u, " + "Fref=%u, Fout=%u, target=%u\n", + div, Fref, Fout, target); + return -EINVAL; + } + + fll_div->fllclk_div = div >> 2; + + if (Fref < 48000) + fll_div->fll_slow_lock_ref = 1; + else + fll_div->fll_slow_lock_ref = 0; + + Ndiv = target / Fref; + + if (Fref < 1000000) + fll_div->fll_ratio = 8; + else + fll_div->fll_ratio = 1; + + fll_div->n = Ndiv / fll_div->fll_ratio; + Nmod = (target / fll_div->fll_ratio) % Fref; + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + if (WARN_ON(target != Fout * (fll_div->fllclk_div << 2)) || + WARN_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n)) + return -EINVAL; + + return 0; +} + +static int wm8900_set_fll(struct snd_soc_component *component, + int fll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm8900_priv *wm8900 = snd_soc_component_get_drvdata(component); + struct _fll_div fll_div; + + if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out) + return 0; + + /* The digital side should be disabled during any change. */ + snd_soc_component_update_bits(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_FLL_ENA, 0); + + /* Disable the FLL? */ + if (!freq_in || !freq_out) { + snd_soc_component_update_bits(component, WM8900_REG_CLOCKING1, + WM8900_REG_CLOCKING1_MCLK_SRC, 0); + snd_soc_component_update_bits(component, WM8900_REG_FLLCTL1, + WM8900_REG_FLLCTL1_OSC_ENA, 0); + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + return 0; + } + + if (fll_factors(&fll_div, freq_in, freq_out) != 0) + goto reenable; + + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + /* The osclilator *MUST* be enabled before we enable the + * digital circuit. */ + snd_soc_component_write(component, WM8900_REG_FLLCTL1, + fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA); + + snd_soc_component_write(component, WM8900_REG_FLLCTL4, fll_div.n >> 5); + snd_soc_component_write(component, WM8900_REG_FLLCTL5, + (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f)); + + if (fll_div.k) { + snd_soc_component_write(component, WM8900_REG_FLLCTL2, + (fll_div.k >> 8) | 0x100); + snd_soc_component_write(component, WM8900_REG_FLLCTL3, fll_div.k & 0xff); + } else + snd_soc_component_write(component, WM8900_REG_FLLCTL2, 0); + + if (fll_div.fll_slow_lock_ref) + snd_soc_component_write(component, WM8900_REG_FLLCTL6, + WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF); + else + snd_soc_component_write(component, WM8900_REG_FLLCTL6, 0); + + snd_soc_component_update_bits(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_FLL_ENA, + WM8900_REG_POWER1_FLL_ENA); + +reenable: + snd_soc_component_update_bits(component, WM8900_REG_CLOCKING1, + WM8900_REG_CLOCKING1_MCLK_SRC, + WM8900_REG_CLOCKING1_MCLK_SRC); + return 0; +} + +static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + return wm8900_set_fll(codec_dai->component, pll_id, freq_in, freq_out); +} + +static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + + switch (div_id) { + case WM8900_BCLK_DIV: + snd_soc_component_update_bits(component, WM8900_REG_CLOCKING1, + WM8900_REG_CLOCKING1_BCLK_MASK, div); + break; + case WM8900_OPCLK_DIV: + snd_soc_component_update_bits(component, WM8900_REG_CLOCKING1, + WM8900_REG_CLOCKING1_OPCLK_MASK, div); + break; + case WM8900_DAC_LRCLK: + snd_soc_component_update_bits(component, WM8900_REG_AUDIO4, + WM8900_LRC_MASK, div); + break; + case WM8900_ADC_LRCLK: + snd_soc_component_update_bits(component, WM8900_REG_AUDIO3, + WM8900_LRC_MASK, div); + break; + case WM8900_DAC_CLKDIV: + snd_soc_component_update_bits(component, WM8900_REG_CLOCKING2, + WM8900_REG_CLOCKING2_DAC_CLKDIV, div); + break; + case WM8900_ADC_CLKDIV: + snd_soc_component_update_bits(component, WM8900_REG_CLOCKING2, + WM8900_REG_CLOCKING2_ADC_CLKDIV, div); + break; + case WM8900_LRCLK_MODE: + snd_soc_component_update_bits(component, WM8900_REG_DACCTRL, + WM8900_REG_DACCTRL_AIF_LRCLKRATE, div); + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int clocking1, aif1, aif3, aif4; + + clocking1 = snd_soc_component_read32(component, WM8900_REG_CLOCKING1); + aif1 = snd_soc_component_read32(component, WM8900_REG_AUDIO1); + aif3 = snd_soc_component_read32(component, WM8900_REG_AUDIO3); + aif4 = snd_soc_component_read32(component, WM8900_REG_AUDIO4); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8900_REG_CLOCKING1, clocking1); + snd_soc_component_write(component, WM8900_REG_AUDIO1, aif1); + snd_soc_component_write(component, WM8900_REG_AUDIO3, aif3); + snd_soc_component_write(component, WM8900_REG_AUDIO4, aif4); + + return 0; +} + +static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + reg = snd_soc_component_read32(component, WM8900_REG_DACCTRL); + + if (mute) + reg |= WM8900_REG_DACCTRL_MUTE; + else + reg &= ~WM8900_REG_DACCTRL_MUTE; + + snd_soc_component_write(component, WM8900_REG_DACCTRL, reg); + + return 0; +} + +#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8900_PCM_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8900_dai_ops = { + .hw_params = wm8900_hw_params, + .set_clkdiv = wm8900_set_dai_clkdiv, + .set_pll = wm8900_set_dai_pll, + .set_fmt = wm8900_set_dai_fmt, + .digital_mute = wm8900_digital_mute, +}; + +static struct snd_soc_dai_driver wm8900_dai = { + .name = "wm8900-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .ops = &wm8900_dai_ops, +}; + +static int wm8900_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + /* Enable thermal shutdown */ + snd_soc_component_update_bits(component, WM8900_REG_GPIO, + WM8900_REG_GPIO_TEMP_ENA, + WM8900_REG_GPIO_TEMP_ENA); + snd_soc_component_update_bits(component, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_TEMP_SD, + WM8900_REG_ADDCTL_TEMP_SD); + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + /* Charge capacitors if initial power up */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* STARTUP_BIAS_ENA on */ + snd_soc_component_write(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + + /* Startup bias mode */ + snd_soc_component_write(component, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* VMID 2x50k */ + snd_soc_component_write(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1); + + /* Allow capacitors to charge */ + schedule_timeout_interruptible(msecs_to_jiffies(400)); + + /* Enable bias */ + snd_soc_component_write(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + + snd_soc_component_write(component, WM8900_REG_ADDCTL, 0); + + snd_soc_component_write(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_BIAS_ENA | 0x1); + } + + reg = snd_soc_component_read32(component, WM8900_REG_POWER1); + snd_soc_component_write(component, WM8900_REG_POWER1, + (reg & WM8900_REG_POWER1_FLL_ENA) | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + snd_soc_component_write(component, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + snd_soc_component_write(component, WM8900_REG_POWER3, 0); + break; + + case SND_SOC_BIAS_OFF: + /* Startup bias enable */ + reg = snd_soc_component_read32(component, WM8900_REG_POWER1); + snd_soc_component_write(component, WM8900_REG_POWER1, + reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA); + snd_soc_component_write(component, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* Discharge caps */ + snd_soc_component_write(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + schedule_timeout_interruptible(msecs_to_jiffies(500)); + + /* Remove clamp */ + snd_soc_component_write(component, WM8900_REG_HPCTL1, 0); + + /* Power down */ + snd_soc_component_write(component, WM8900_REG_ADDCTL, 0); + snd_soc_component_write(component, WM8900_REG_POWER1, 0); + snd_soc_component_write(component, WM8900_REG_POWER2, 0); + snd_soc_component_write(component, WM8900_REG_POWER3, 0); + + /* Need to let things settle before stopping the clock + * to ensure that restart works, see "Stopping the + * master clock" in the datasheet. */ + schedule_timeout_interruptible(msecs_to_jiffies(1)); + snd_soc_component_write(component, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + break; + } + return 0; +} + +static int wm8900_suspend(struct snd_soc_component *component) +{ + struct wm8900_priv *wm8900 = snd_soc_component_get_drvdata(component); + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + int ret; + + /* Stop the FLL in an orderly fashion */ + ret = wm8900_set_fll(component, 0, 0, 0); + if (ret != 0) { + dev_err(component->dev, "Failed to stop FLL\n"); + return ret; + } + + wm8900->fll_out = fll_out; + wm8900->fll_in = fll_in; + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8900_resume(struct snd_soc_component *component) +{ + struct wm8900_priv *wm8900 = snd_soc_component_get_drvdata(component); + int ret; + + wm8900_reset(component); + + ret = regcache_sync(wm8900->regmap); + if (ret != 0) { + dev_err(component->dev, "Failed to restore cache: %d\n", ret); + return ret; + } + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + /* Restart the FLL? */ + if (wm8900->fll_out) { + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + + wm8900->fll_in = 0; + wm8900->fll_out = 0; + + ret = wm8900_set_fll(component, 0, fll_in, fll_out); + if (ret != 0) { + dev_err(component->dev, "Failed to restart FLL\n"); + return ret; + } + } + + return 0; +} + +static int wm8900_probe(struct snd_soc_component *component) +{ + int reg; + + reg = snd_soc_component_read32(component, WM8900_REG_ID); + if (reg != 0x8900) { + dev_err(component->dev, "Device is not a WM8900 - ID %x\n", reg); + return -ENODEV; + } + + wm8900_reset(component); + + /* Turn the chip on */ + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + /* Latch the volume update bits */ + snd_soc_component_update_bits(component, WM8900_REG_LINVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_RINVOL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_LOUT1CTL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_ROUT1CTL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_LOUT2CTL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_ROUT2CTL, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_LDAC_DV, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_RDAC_DV, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_LADC_DV, 0x100, 0x100); + snd_soc_component_update_bits(component, WM8900_REG_RADC_DV, 0x100, 0x100); + + /* Set the DAC and mixer output bias */ + snd_soc_component_write(component, WM8900_REG_OUTBIASCTL, 0x81); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8900 = { + .probe = wm8900_probe, + .suspend = wm8900_suspend, + .resume = wm8900_resume, + .set_bias_level = wm8900_set_bias_level, + .controls = wm8900_snd_controls, + .num_controls = ARRAY_SIZE(wm8900_snd_controls), + .dapm_widgets = wm8900_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8900_dapm_widgets), + .dapm_routes = wm8900_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8900_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8900_regmap = { + .reg_bits = 8, + .val_bits = 16, + .max_register = WM8900_MAXREG, + + .reg_defaults = wm8900_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8900_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8900_volatile_register, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8900_spi_probe(struct spi_device *spi) +{ + struct wm8900_priv *wm8900; + int ret; + + wm8900 = devm_kzalloc(&spi->dev, sizeof(struct wm8900_priv), + GFP_KERNEL); + if (wm8900 == NULL) + return -ENOMEM; + + wm8900->regmap = devm_regmap_init_spi(spi, &wm8900_regmap); + if (IS_ERR(wm8900->regmap)) + return PTR_ERR(wm8900->regmap); + + spi_set_drvdata(spi, wm8900); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8900, &wm8900_dai, 1); + + return ret; +} + +static int wm8900_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8900_spi_driver = { + .driver = { + .name = "wm8900", + }, + .probe = wm8900_spi_probe, + .remove = wm8900_spi_remove, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8900_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8900_priv *wm8900; + int ret; + + wm8900 = devm_kzalloc(&i2c->dev, sizeof(struct wm8900_priv), + GFP_KERNEL); + if (wm8900 == NULL) + return -ENOMEM; + + wm8900->regmap = devm_regmap_init_i2c(i2c, &wm8900_regmap); + if (IS_ERR(wm8900->regmap)) + return PTR_ERR(wm8900->regmap); + + i2c_set_clientdata(i2c, wm8900); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8900, &wm8900_dai, 1); + + return ret; +} + +static int wm8900_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wm8900_i2c_id[] = { + { "wm8900", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8900_i2c_id); + +static struct i2c_driver wm8900_i2c_driver = { + .driver = { + .name = "wm8900", + }, + .probe = wm8900_i2c_probe, + .remove = wm8900_i2c_remove, + .id_table = wm8900_i2c_id, +}; +#endif + +static int __init wm8900_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8900_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8900 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8900_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8900 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8900_modinit); + +static void __exit wm8900_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8900_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8900_spi_driver); +#endif +} +module_exit(wm8900_exit); + +MODULE_DESCRIPTION("ASoC WM8900 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8900.h b/sound/soc/codecs/wm8900.h new file mode 100644 index 000000000..583f257e7 --- /dev/null +++ b/sound/soc/codecs/wm8900.h @@ -0,0 +1,55 @@ +/* + * wm8900.h -- WM890 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8900_H +#define _WM8900_H + +#define WM8900_FLL 1 + +#define WM8900_BCLK_DIV 1 +#define WM8900_ADC_CLKDIV 2 +#define WM8900_DAC_CLKDIV 3 +#define WM8900_ADC_LRCLK 4 +#define WM8900_DAC_LRCLK 5 +#define WM8900_OPCLK_DIV 6 +#define WM8900_LRCLK_MODE 7 + +#define WM8900_BCLK_DIV_1 0x00 +#define WM8900_BCLK_DIV_1_5 0x02 +#define WM8900_BCLK_DIV_2 0x04 +#define WM8900_BCLK_DIV_3 0x06 +#define WM8900_BCLK_DIV_4 0x08 +#define WM8900_BCLK_DIV_5_5 0x0a +#define WM8900_BCLK_DIV_6 0x0c +#define WM8900_BCLK_DIV_8 0x0e +#define WM8900_BCLK_DIV_11 0x10 +#define WM8900_BCLK_DIV_12 0x12 +#define WM8900_BCLK_DIV_16 0x14 +#define WM8900_BCLK_DIV_22 0x16 +#define WM8900_BCLK_DIV_24 0x18 +#define WM8900_BCLK_DIV_32 0x1a +#define WM8900_BCLK_DIV_44 0x1c +#define WM8900_BCLK_DIV_48 0x1e + +#define WM8900_ADC_CLKDIV_1 0x00 +#define WM8900_ADC_CLKDIV_1_5 0x20 +#define WM8900_ADC_CLKDIV_2 0x40 +#define WM8900_ADC_CLKDIV_3 0x60 +#define WM8900_ADC_CLKDIV_4 0x80 +#define WM8900_ADC_CLKDIV_5_5 0xa0 +#define WM8900_ADC_CLKDIV_6 0xc0 + +#define WM8900_DAC_CLKDIV_1 0x00 +#define WM8900_DAC_CLKDIV_1_5 0x04 +#define WM8900_DAC_CLKDIV_2 0x08 +#define WM8900_DAC_CLKDIV_3 0x0c +#define WM8900_DAC_CLKDIV_4 0x10 +#define WM8900_DAC_CLKDIV_5_5 0x14 +#define WM8900_DAC_CLKDIV_6 0x18 + +#endif diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c new file mode 100644 index 000000000..6cb3c153b --- /dev/null +++ b/sound/soc/codecs/wm8903.c @@ -0,0 +1,2230 @@ +/* + * wm8903.c -- WM8903 ALSA SoC Audio driver + * + * Copyright 2008-12 Wolfson Microelectronics + * Copyright 2011-2012 NVIDIA, Inc. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - TDM mode configuration. + * - Digital microphone support. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8903.h" + +/* Register defaults at reset */ +static const struct reg_default wm8903_reg_defaults[] = { + { 4, 0x0018 }, /* R4 - Bias Control 0 */ + { 5, 0x0000 }, /* R5 - VMID Control 0 */ + { 6, 0x0000 }, /* R6 - Mic Bias Control 0 */ + { 8, 0x0001 }, /* R8 - Analogue DAC 0 */ + { 10, 0x0001 }, /* R10 - Analogue ADC 0 */ + { 12, 0x0000 }, /* R12 - Power Management 0 */ + { 13, 0x0000 }, /* R13 - Power Management 1 */ + { 14, 0x0000 }, /* R14 - Power Management 2 */ + { 15, 0x0000 }, /* R15 - Power Management 3 */ + { 16, 0x0000 }, /* R16 - Power Management 4 */ + { 17, 0x0000 }, /* R17 - Power Management 5 */ + { 18, 0x0000 }, /* R18 - Power Management 6 */ + { 20, 0x0400 }, /* R20 - Clock Rates 0 */ + { 21, 0x0D07 }, /* R21 - Clock Rates 1 */ + { 22, 0x0000 }, /* R22 - Clock Rates 2 */ + { 24, 0x0050 }, /* R24 - Audio Interface 0 */ + { 25, 0x0242 }, /* R25 - Audio Interface 1 */ + { 26, 0x0008 }, /* R26 - Audio Interface 2 */ + { 27, 0x0022 }, /* R27 - Audio Interface 3 */ + { 30, 0x00C0 }, /* R30 - DAC Digital Volume Left */ + { 31, 0x00C0 }, /* R31 - DAC Digital Volume Right */ + { 32, 0x0000 }, /* R32 - DAC Digital 0 */ + { 33, 0x0000 }, /* R33 - DAC Digital 1 */ + { 36, 0x00C0 }, /* R36 - ADC Digital Volume Left */ + { 37, 0x00C0 }, /* R37 - ADC Digital Volume Right */ + { 38, 0x0000 }, /* R38 - ADC Digital 0 */ + { 39, 0x0073 }, /* R39 - Digital Microphone 0 */ + { 40, 0x09BF }, /* R40 - DRC 0 */ + { 41, 0x3241 }, /* R41 - DRC 1 */ + { 42, 0x0020 }, /* R42 - DRC 2 */ + { 43, 0x0000 }, /* R43 - DRC 3 */ + { 44, 0x0085 }, /* R44 - Analogue Left Input 0 */ + { 45, 0x0085 }, /* R45 - Analogue Right Input 0 */ + { 46, 0x0044 }, /* R46 - Analogue Left Input 1 */ + { 47, 0x0044 }, /* R47 - Analogue Right Input 1 */ + { 50, 0x0008 }, /* R50 - Analogue Left Mix 0 */ + { 51, 0x0004 }, /* R51 - Analogue Right Mix 0 */ + { 52, 0x0000 }, /* R52 - Analogue Spk Mix Left 0 */ + { 53, 0x0000 }, /* R53 - Analogue Spk Mix Left 1 */ + { 54, 0x0000 }, /* R54 - Analogue Spk Mix Right 0 */ + { 55, 0x0000 }, /* R55 - Analogue Spk Mix Right 1 */ + { 57, 0x002D }, /* R57 - Analogue OUT1 Left */ + { 58, 0x002D }, /* R58 - Analogue OUT1 Right */ + { 59, 0x0039 }, /* R59 - Analogue OUT2 Left */ + { 60, 0x0039 }, /* R60 - Analogue OUT2 Right */ + { 62, 0x0139 }, /* R62 - Analogue OUT3 Left */ + { 63, 0x0139 }, /* R63 - Analogue OUT3 Right */ + { 64, 0x0000 }, /* R65 - Analogue SPK Output Control 0 */ + { 67, 0x0010 }, /* R67 - DC Servo 0 */ + { 69, 0x00A4 }, /* R69 - DC Servo 2 */ + { 90, 0x0000 }, /* R90 - Analogue HP 0 */ + { 94, 0x0000 }, /* R94 - Analogue Lineout 0 */ + { 98, 0x0000 }, /* R98 - Charge Pump 0 */ + { 104, 0x0000 }, /* R104 - Class W 0 */ + { 108, 0x0000 }, /* R108 - Write Sequencer 0 */ + { 109, 0x0000 }, /* R109 - Write Sequencer 1 */ + { 110, 0x0000 }, /* R110 - Write Sequencer 2 */ + { 111, 0x0000 }, /* R111 - Write Sequencer 3 */ + { 112, 0x0000 }, /* R112 - Write Sequencer 4 */ + { 114, 0x0000 }, /* R114 - Control Interface */ + { 116, 0x00A8 }, /* R116 - GPIO Control 1 */ + { 117, 0x00A8 }, /* R117 - GPIO Control 2 */ + { 118, 0x00A8 }, /* R118 - GPIO Control 3 */ + { 119, 0x0220 }, /* R119 - GPIO Control 4 */ + { 120, 0x01A0 }, /* R120 - GPIO Control 5 */ + { 122, 0xFFFF }, /* R122 - Interrupt Status 1 Mask */ + { 123, 0x0000 }, /* R123 - Interrupt Polarity 1 */ + { 126, 0x0000 }, /* R126 - Interrupt Control */ + { 129, 0x0000 }, /* R129 - Control Interface Test 1 */ + { 149, 0x6810 }, /* R149 - Charge Pump Test 1 */ + { 164, 0x0028 }, /* R164 - Clock Rate Test 4 */ + { 172, 0x0000 }, /* R172 - Analogue Output Bias 0 */ +}; + +#define WM8903_NUM_SUPPLIES 4 +static const char *wm8903_supply_names[WM8903_NUM_SUPPLIES] = { + "AVDD", + "CPVDD", + "DBVDD", + "DCVDD", +}; + +struct wm8903_priv { + struct wm8903_platform_data *pdata; + struct device *dev; + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8903_NUM_SUPPLIES]; + + int sysclk; + int irq; + + struct mutex lock; + int fs; + int deemph; + + int dcs_pending; + int dcs_cache[4]; + + /* Reference count */ + int class_w_users; + + struct snd_soc_jack *mic_jack; + int mic_det; + int mic_short; + int mic_last_report; + int mic_delay; + +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif +}; + +static bool wm8903_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + case WM8903_BIAS_CONTROL_0: + case WM8903_VMID_CONTROL_0: + case WM8903_MIC_BIAS_CONTROL_0: + case WM8903_ANALOGUE_DAC_0: + case WM8903_ANALOGUE_ADC_0: + case WM8903_POWER_MANAGEMENT_0: + case WM8903_POWER_MANAGEMENT_1: + case WM8903_POWER_MANAGEMENT_2: + case WM8903_POWER_MANAGEMENT_3: + case WM8903_POWER_MANAGEMENT_4: + case WM8903_POWER_MANAGEMENT_5: + case WM8903_POWER_MANAGEMENT_6: + case WM8903_CLOCK_RATES_0: + case WM8903_CLOCK_RATES_1: + case WM8903_CLOCK_RATES_2: + case WM8903_AUDIO_INTERFACE_0: + case WM8903_AUDIO_INTERFACE_1: + case WM8903_AUDIO_INTERFACE_2: + case WM8903_AUDIO_INTERFACE_3: + case WM8903_DAC_DIGITAL_VOLUME_LEFT: + case WM8903_DAC_DIGITAL_VOLUME_RIGHT: + case WM8903_DAC_DIGITAL_0: + case WM8903_DAC_DIGITAL_1: + case WM8903_ADC_DIGITAL_VOLUME_LEFT: + case WM8903_ADC_DIGITAL_VOLUME_RIGHT: + case WM8903_ADC_DIGITAL_0: + case WM8903_DIGITAL_MICROPHONE_0: + case WM8903_DRC_0: + case WM8903_DRC_1: + case WM8903_DRC_2: + case WM8903_DRC_3: + case WM8903_ANALOGUE_LEFT_INPUT_0: + case WM8903_ANALOGUE_RIGHT_INPUT_0: + case WM8903_ANALOGUE_LEFT_INPUT_1: + case WM8903_ANALOGUE_RIGHT_INPUT_1: + case WM8903_ANALOGUE_LEFT_MIX_0: + case WM8903_ANALOGUE_RIGHT_MIX_0: + case WM8903_ANALOGUE_SPK_MIX_LEFT_0: + case WM8903_ANALOGUE_SPK_MIX_LEFT_1: + case WM8903_ANALOGUE_SPK_MIX_RIGHT_0: + case WM8903_ANALOGUE_SPK_MIX_RIGHT_1: + case WM8903_ANALOGUE_OUT1_LEFT: + case WM8903_ANALOGUE_OUT1_RIGHT: + case WM8903_ANALOGUE_OUT2_LEFT: + case WM8903_ANALOGUE_OUT2_RIGHT: + case WM8903_ANALOGUE_OUT3_LEFT: + case WM8903_ANALOGUE_OUT3_RIGHT: + case WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0: + case WM8903_DC_SERVO_0: + case WM8903_DC_SERVO_2: + case WM8903_DC_SERVO_READBACK_1: + case WM8903_DC_SERVO_READBACK_2: + case WM8903_DC_SERVO_READBACK_3: + case WM8903_DC_SERVO_READBACK_4: + case WM8903_ANALOGUE_HP_0: + case WM8903_ANALOGUE_LINEOUT_0: + case WM8903_CHARGE_PUMP_0: + case WM8903_CLASS_W_0: + case WM8903_WRITE_SEQUENCER_0: + case WM8903_WRITE_SEQUENCER_1: + case WM8903_WRITE_SEQUENCER_2: + case WM8903_WRITE_SEQUENCER_3: + case WM8903_WRITE_SEQUENCER_4: + case WM8903_CONTROL_INTERFACE: + case WM8903_GPIO_CONTROL_1: + case WM8903_GPIO_CONTROL_2: + case WM8903_GPIO_CONTROL_3: + case WM8903_GPIO_CONTROL_4: + case WM8903_GPIO_CONTROL_5: + case WM8903_INTERRUPT_STATUS_1: + case WM8903_INTERRUPT_STATUS_1_MASK: + case WM8903_INTERRUPT_POLARITY_1: + case WM8903_INTERRUPT_CONTROL: + case WM8903_CLOCK_RATE_TEST_4: + case WM8903_ANALOGUE_OUTPUT_BIAS_0: + return true; + default: + return false; + } +} + +static bool wm8903_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + case WM8903_INTERRUPT_STATUS_1: + case WM8903_WRITE_SEQUENCER_4: + case WM8903_DC_SERVO_READBACK_1: + case WM8903_DC_SERVO_READBACK_2: + case WM8903_DC_SERVO_READBACK_3: + case WM8903_DC_SERVO_READBACK_4: + return true; + + default: + return false; + } +} + +static int wm8903_cp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + WARN_ON(event != SND_SOC_DAPM_POST_PMU); + mdelay(4); + + return 0; +} + +static int wm8903_dcs_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + wm8903->dcs_pending |= 1 << w->shift; + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, WM8903_DC_SERVO_0, + 1 << w->shift, 0); + break; + } + + return 0; +} + +#define WM8903_DCS_MODE_WRITE_STOP 0 +#define WM8903_DCS_MODE_START_STOP 2 + +static void wm8903_seq_notifier(struct snd_soc_component *component, + enum snd_soc_dapm_type event, int subseq) +{ + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + int dcs_mode = WM8903_DCS_MODE_WRITE_STOP; + int i, val; + + /* Complete any pending DC servo starts */ + if (wm8903->dcs_pending) { + dev_dbg(component->dev, "Starting DC servo for %x\n", + wm8903->dcs_pending); + + /* If we've no cached values then we need to do startup */ + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; + + if (wm8903->dcs_cache[i]) { + dev_dbg(component->dev, + "Restore DC servo %d value %x\n", + 3 - i, wm8903->dcs_cache[i]); + + snd_soc_component_write(component, WM8903_DC_SERVO_4 + i, + wm8903->dcs_cache[i] & 0xff); + } else { + dev_dbg(component->dev, + "Calibrate DC servo %d\n", 3 - i); + dcs_mode = WM8903_DCS_MODE_START_STOP; + } + } + + /* Don't trust the cache for analogue */ + if (wm8903->class_w_users) + dcs_mode = WM8903_DCS_MODE_START_STOP; + + snd_soc_component_update_bits(component, WM8903_DC_SERVO_2, + WM8903_DCS_MODE_MASK, dcs_mode); + + snd_soc_component_update_bits(component, WM8903_DC_SERVO_0, + WM8903_DCS_ENA_MASK, wm8903->dcs_pending); + + switch (dcs_mode) { + case WM8903_DCS_MODE_WRITE_STOP: + break; + + case WM8903_DCS_MODE_START_STOP: + msleep(270); + + /* Cache the measured offsets for digital */ + if (wm8903->class_w_users) + break; + + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; + + val = snd_soc_component_read32(component, + WM8903_DC_SERVO_READBACK_1 + i); + dev_dbg(component->dev, "DC servo %d: %x\n", + 3 - i, val); + wm8903->dcs_cache[i] = val; + } + break; + + default: + pr_warn("DCS mode %d delay not set\n", dcs_mode); + break; + } + + wm8903->dcs_pending = 0; + } +} + +/* + * When used with DAC outputs only the WM8903 charge pump supports + * operation in class W mode, providing very low power consumption + * when used with digital sources. Enable and disable this mode + * automatically depending on the mixer configuration. + * + * All the relevant controls are simple switches. + */ +static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + u16 reg; + int ret; + + reg = snd_soc_component_read32(component, WM8903_CLASS_W_0); + + /* Turn it off if we're about to enable bypass */ + if (ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 0) { + dev_dbg(component->dev, "Disabling Class W\n"); + snd_soc_component_write(component, WM8903_CLASS_W_0, reg & + ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V)); + } + wm8903->class_w_users++; + } + + /* Implement the change */ + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + /* If we've just disabled the last bypass path turn Class W on */ + if (!ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 1) { + dev_dbg(component->dev, "Enabling Class W\n"); + snd_soc_component_write(component, WM8903_CLASS_W_0, reg | + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); + } + wm8903->class_w_users--; + } + + dev_dbg(component->dev, "Bypass use count now %d\n", + wm8903->class_w_users); + + return ret; +} + +#define SOC_DAPM_SINGLE_W(xname, reg, shift, max, invert) \ + SOC_SINGLE_EXT(xname, reg, shift, max, invert, \ + snd_soc_dapm_get_volsw, wm8903_class_w_put) + + +static int wm8903_deemph[] = { 0, 32000, 44100, 48000 }; + +static int wm8903_set_deemph(struct snd_soc_component *component) +{ + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8903->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(wm8903_deemph); i++) { + if (abs(wm8903_deemph[i] - wm8903->fs) < + abs(wm8903_deemph[best] - wm8903->fs)) + best = i; + } + + val = best << WM8903_DEEMPH_SHIFT; + } else { + best = 0; + val = 0; + } + + dev_dbg(component->dev, "Set deemphasis %d (%dHz)\n", + best, wm8903_deemph[best]); + + return snd_soc_component_update_bits(component, WM8903_DAC_DIGITAL_1, + WM8903_DEEMPH_MASK, val); +} + +static int wm8903_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8903->deemph; + + return 0; +} + +static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + int ret = 0; + + if (deemph > 1) + return -EINVAL; + + mutex_lock(&wm8903->lock); + if (wm8903->deemph != deemph) { + wm8903->deemph = deemph; + + wm8903_set_deemph(component); + + ret = 1; + } + mutex_unlock(&wm8903->lock); + + return ret; +} + +/* ALSA can only do steps of .01dB */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); + +static const DECLARE_TLV_DB_SCALE(digital_sidetone_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0); + +static const char *hpf_mode_text[] = { + "Hi-fi", "Voice 1", "Voice 2", "Voice 3" +}; + +static SOC_ENUM_SINGLE_DECL(hpf_mode, + WM8903_ADC_DIGITAL_0, 5, hpf_mode_text); + +static const char *osr_text[] = { + "Low power", "High performance" +}; + +static SOC_ENUM_SINGLE_DECL(adc_osr, + WM8903_ANALOGUE_ADC_0, 0, osr_text); + +static SOC_ENUM_SINGLE_DECL(dac_osr, + WM8903_DAC_DIGITAL_1, 0, osr_text); + +static const char *drc_slope_text[] = { + "1", "1/2", "1/4", "1/8", "1/16", "0" +}; + +static SOC_ENUM_SINGLE_DECL(drc_slope_r0, + WM8903_DRC_2, 3, drc_slope_text); + +static SOC_ENUM_SINGLE_DECL(drc_slope_r1, + WM8903_DRC_2, 0, drc_slope_text); + +static const char *drc_attack_text[] = { + "instantaneous", + "363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms", + "46.4ms", "92.8ms", "185.6ms" +}; + +static SOC_ENUM_SINGLE_DECL(drc_attack, + WM8903_DRC_1, 12, drc_attack_text); + +static const char *drc_decay_text[] = { + "186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s", + "23.87s", "47.56s" +}; + +static SOC_ENUM_SINGLE_DECL(drc_decay, + WM8903_DRC_1, 8, drc_decay_text); + +static const char *drc_ff_delay_text[] = { + "5 samples", "9 samples" +}; + +static SOC_ENUM_SINGLE_DECL(drc_ff_delay, + WM8903_DRC_0, 5, drc_ff_delay_text); + +static const char *drc_qr_decay_text[] = { + "0.725ms", "1.45ms", "5.8ms" +}; + +static SOC_ENUM_SINGLE_DECL(drc_qr_decay, + WM8903_DRC_1, 4, drc_qr_decay_text); + +static const char *drc_smoothing_text[] = { + "Low", "Medium", "High" +}; + +static SOC_ENUM_SINGLE_DECL(drc_smoothing, + WM8903_DRC_0, 11, drc_smoothing_text); + +static const char *soft_mute_text[] = { + "Fast (fs/2)", "Slow (fs/32)" +}; + +static SOC_ENUM_SINGLE_DECL(soft_mute, + WM8903_DAC_DIGITAL_1, 10, soft_mute_text); + +static const char *mute_mode_text[] = { + "Hard", "Soft" +}; + +static SOC_ENUM_SINGLE_DECL(mute_mode, + WM8903_DAC_DIGITAL_1, 9, mute_mode_text); + +static const char *companding_text[] = { + "ulaw", "alaw" +}; + +static SOC_ENUM_SINGLE_DECL(dac_companding, + WM8903_AUDIO_INTERFACE_0, 0, companding_text); + +static SOC_ENUM_SINGLE_DECL(adc_companding, + WM8903_AUDIO_INTERFACE_0, 2, companding_text); + +static const char *input_mode_text[] = { + "Single-Ended", "Differential Line", "Differential Mic" +}; + +static SOC_ENUM_SINGLE_DECL(linput_mode_enum, + WM8903_ANALOGUE_LEFT_INPUT_1, 0, input_mode_text); + +static SOC_ENUM_SINGLE_DECL(rinput_mode_enum, + WM8903_ANALOGUE_RIGHT_INPUT_1, 0, input_mode_text); + +static const char *linput_mux_text[] = { + "IN1L", "IN2L", "IN3L" +}; + +static SOC_ENUM_SINGLE_DECL(linput_enum, + WM8903_ANALOGUE_LEFT_INPUT_1, 2, linput_mux_text); + +static SOC_ENUM_SINGLE_DECL(linput_inv_enum, + WM8903_ANALOGUE_LEFT_INPUT_1, 4, linput_mux_text); + +static const char *rinput_mux_text[] = { + "IN1R", "IN2R", "IN3R" +}; + +static SOC_ENUM_SINGLE_DECL(rinput_enum, + WM8903_ANALOGUE_RIGHT_INPUT_1, 2, rinput_mux_text); + +static SOC_ENUM_SINGLE_DECL(rinput_inv_enum, + WM8903_ANALOGUE_RIGHT_INPUT_1, 4, rinput_mux_text); + + +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(lsidetone_enum, + WM8903_DAC_DIGITAL_0, 2, sidetone_text); + +static SOC_ENUM_SINGLE_DECL(rsidetone_enum, + WM8903_DAC_DIGITAL_0, 0, sidetone_text); + +static const char *adcinput_text[] = { + "ADC", "DMIC" +}; + +static SOC_ENUM_SINGLE_DECL(adcinput_enum, + WM8903_CLOCK_RATE_TEST_4, 9, adcinput_text); + +static const char *aif_text[] = { + "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(lcapture_enum, + WM8903_AUDIO_INTERFACE_0, 7, aif_text); + +static SOC_ENUM_SINGLE_DECL(rcapture_enum, + WM8903_AUDIO_INTERFACE_0, 6, aif_text); + +static SOC_ENUM_SINGLE_DECL(lplay_enum, + WM8903_AUDIO_INTERFACE_0, 5, aif_text); + +static SOC_ENUM_SINGLE_DECL(rplay_enum, + WM8903_AUDIO_INTERFACE_0, 4, aif_text); + +static const struct snd_kcontrol_new wm8903_snd_controls[] = { + +/* Input PGAs - No TLV since the scale depends on PGA mode */ +SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1, + 6, 1, 0), + +SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1, + 6, 1, 0), + +/* ADCs */ +SOC_ENUM("ADC OSR", adc_osr), +SOC_SINGLE("HPF Switch", WM8903_ADC_DIGITAL_0, 4, 1, 0), +SOC_ENUM("HPF Mode", hpf_mode), +SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0), +SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0), +SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1), +SOC_SINGLE_TLV("DRC Compressor Threshold Volume", WM8903_DRC_3, 5, 124, 1, + drc_tlv_thresh), +SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp), +SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min), +SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max), +SOC_ENUM("DRC Attack Rate", drc_attack), +SOC_ENUM("DRC Decay Rate", drc_decay), +SOC_ENUM("DRC FF Delay", drc_ff_delay), +SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), +SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), +SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), +SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), +SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +SOC_ENUM("DRC Smoothing Threshold", drc_smoothing), +SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup), + +SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT, + WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv), +SOC_ENUM("ADC Companding Mode", adc_companding), +SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0), + +SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8903_DAC_DIGITAL_0, 4, 8, + 12, 0, digital_sidetone_tlv), + +/* DAC */ +SOC_ENUM("DAC OSR", dac_osr), +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT, + WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv), +SOC_ENUM("DAC Soft Mute Rate", soft_mute), +SOC_ENUM("DAC Mute Mode", mute_mode), +SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0), +SOC_ENUM("DAC Companding Mode", dac_companding), +SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0), +SOC_SINGLE_TLV("DAC Boost Volume", WM8903_AUDIO_INTERFACE_0, 9, 3, 0, + dac_boost_tlv), +SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0, + wm8903_get_deemph, wm8903_put_deemph), + +/* Headphones */ +SOC_DOUBLE_R("Headphone Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Headphone Volume", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 0, 63, 0, out_tlv), + +/* Line out */ +SOC_DOUBLE_R("Line Out Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Line Out ZC Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Line Out Volume", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 0, 63, 0, out_tlv), + +/* Speaker */ +SOC_DOUBLE_R("Speaker Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1), +SOC_DOUBLE_R("Speaker ZC Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0), +SOC_DOUBLE_R_TLV("Speaker Volume", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, + 0, 63, 0, out_tlv), +}; + +static const struct snd_kcontrol_new linput_mode_mux = + SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum); + +static const struct snd_kcontrol_new rinput_mode_mux = + SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum); + +static const struct snd_kcontrol_new linput_mux = + SOC_DAPM_ENUM("Left Input Mux", linput_enum); + +static const struct snd_kcontrol_new linput_inv_mux = + SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum); + +static const struct snd_kcontrol_new rinput_mux = + SOC_DAPM_ENUM("Right Input Mux", rinput_enum); + +static const struct snd_kcontrol_new rinput_inv_mux = + SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum); + +static const struct snd_kcontrol_new lsidetone_mux = + SOC_DAPM_ENUM("DACL Sidetone Mux", lsidetone_enum); + +static const struct snd_kcontrol_new rsidetone_mux = + SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum); + +static const struct snd_kcontrol_new adcinput_mux = + SOC_DAPM_ENUM("ADC Input", adcinput_enum); + +static const struct snd_kcontrol_new lcapture_mux = + SOC_DAPM_ENUM("Left Capture Mux", lcapture_enum); + +static const struct snd_kcontrol_new rcapture_mux = + SOC_DAPM_ENUM("Right Capture Mux", rcapture_enum); + +static const struct snd_kcontrol_new lplay_mux = + SOC_DAPM_ENUM("Left Playback Mux", lplay_enum); + +static const struct snd_kcontrol_new rplay_mux = + SOC_DAPM_ENUM("Right Playback Mux", rplay_enum); + +static const struct snd_kcontrol_new left_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), +SND_SOC_DAPM_INPUT("DMICDAT"), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LINEOUTL"), +SND_SOC_DAPM_OUTPUT("LINEOUTR"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_SUPPLY("MICBIAS", WM8903_MIC_BIAS_CONTROL_0, 0, 0, NULL, 0), + +SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux), +SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &linput_inv_mux), +SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux), + +SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux), +SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &rinput_inv_mux), +SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux), + +SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), + +SND_SOC_DAPM_MUX("Left ADC Input", SND_SOC_NOPM, 0, 0, &adcinput_mux), +SND_SOC_DAPM_MUX("Right ADC Input", SND_SOC_NOPM, 0, 0, &adcinput_mux), + +SND_SOC_DAPM_ADC("ADCL", NULL, WM8903_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, WM8903_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lcapture_mux), +SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rcapture_mux), + +SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux), +SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux), + +SND_SOC_DAPM_AIF_IN("AIFRXL", "Left Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFRXR", "Right Playback", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("Left Playback Mux", SND_SOC_NOPM, 0, 0, &lplay_mux), +SND_SOC_DAPM_MUX("Right Playback Mux", SND_SOC_NOPM, 0, 0, &rplay_mux), + +SND_SOC_DAPM_DAC("DACL", NULL, WM8903_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", NULL, WM8903_POWER_MANAGEMENT_6, 2, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + +SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), + +SND_SOC_DAPM_PGA_S("Left Headphone Output PGA", 0, WM8903_POWER_MANAGEMENT_2, + 1, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("Right Headphone Output PGA", 0, WM8903_POWER_MANAGEMENT_2, + 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("Left Line Output PGA", 0, WM8903_POWER_MANAGEMENT_3, 1, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("Right Line Output PGA", 0, WM8903_POWER_MANAGEMENT_3, 0, 0, + NULL, 0), + +SND_SOC_DAPM_PGA_S("HPL_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_DLY", 2, WM8903_ANALOGUE_HP_0, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA", 1, WM8903_ANALOGUE_HP_0, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_DLY", 2, WM8903_ANALOGUE_HP_0, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA", 1, WM8903_ANALOGUE_HP_0, 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("LINEOUTL_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 7, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 6, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_DLY", 2, WM8903_ANALOGUE_LINEOUT_0, 5, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA", 1, WM8903_ANALOGUE_LINEOUT_0, 4, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 3, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 2, WM8903_ANALOGUE_LINEOUT_0, 1, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA", 1, WM8903_ANALOGUE_LINEOUT_0, 0, 0, + NULL, 0), + +SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, + NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, + NULL, 0), + +SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0, + wm8903_cp_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route wm8903_intercon[] = { + + { "CLK_DSP", NULL, "CLK_SYS" }, + { "MICBIAS", NULL, "CLK_SYS" }, + { "HPL_DCS", NULL, "CLK_SYS" }, + { "HPR_DCS", NULL, "CLK_SYS" }, + { "LINEOUTL_DCS", NULL, "CLK_SYS" }, + { "LINEOUTR_DCS", NULL, "CLK_SYS" }, + + { "Left Input Mux", "IN1L", "IN1L" }, + { "Left Input Mux", "IN2L", "IN2L" }, + { "Left Input Mux", "IN3L", "IN3L" }, + + { "Left Input Inverting Mux", "IN1L", "IN1L" }, + { "Left Input Inverting Mux", "IN2L", "IN2L" }, + { "Left Input Inverting Mux", "IN3L", "IN3L" }, + + { "Right Input Mux", "IN1R", "IN1R" }, + { "Right Input Mux", "IN2R", "IN2R" }, + { "Right Input Mux", "IN3R", "IN3R" }, + + { "Right Input Inverting Mux", "IN1R", "IN1R" }, + { "Right Input Inverting Mux", "IN2R", "IN2R" }, + { "Right Input Inverting Mux", "IN3R", "IN3R" }, + + { "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Inverting Mux" }, + + { "Right Input Mode Mux", "Single-Ended", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Inverting Mux" }, + + { "Left Input PGA", NULL, "Left Input Mode Mux" }, + { "Right Input PGA", NULL, "Right Input Mode Mux" }, + + { "Left ADC Input", "ADC", "Left Input PGA" }, + { "Left ADC Input", "DMIC", "DMICDAT" }, + { "Right ADC Input", "ADC", "Right Input PGA" }, + { "Right ADC Input", "DMIC", "DMICDAT" }, + + { "Left Capture Mux", "Left", "ADCL" }, + { "Left Capture Mux", "Right", "ADCR" }, + + { "Right Capture Mux", "Left", "ADCL" }, + { "Right Capture Mux", "Right", "ADCR" }, + + { "AIFTXL", NULL, "Left Capture Mux" }, + { "AIFTXR", NULL, "Right Capture Mux" }, + + { "ADCL", NULL, "Left ADC Input" }, + { "ADCL", NULL, "CLK_DSP" }, + { "ADCR", NULL, "Right ADC Input" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "Left Playback Mux", "Left", "AIFRXL" }, + { "Left Playback Mux", "Right", "AIFRXR" }, + + { "Right Playback Mux", "Left", "AIFRXL" }, + { "Right Playback Mux", "Right", "AIFRXR" }, + + { "DACL Sidetone", "Left", "ADCL" }, + { "DACL Sidetone", "Right", "ADCR" }, + { "DACR Sidetone", "Left", "ADCL" }, + { "DACR Sidetone", "Right", "ADCR" }, + + { "DACL", NULL, "Left Playback Mux" }, + { "DACL", NULL, "DACL Sidetone" }, + { "DACL", NULL, "CLK_DSP" }, + + { "DACR", NULL, "Right Playback Mux" }, + { "DACR", NULL, "DACR Sidetone" }, + { "DACR", NULL, "CLK_DSP" }, + + { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Output Mixer", "DACL Switch", "DACL" }, + { "Left Output Mixer", "DACR Switch", "DACR" }, + + { "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Output Mixer", "DACL Switch", "DACL" }, + { "Right Output Mixer", "DACR Switch", "DACR" }, + + { "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Speaker Mixer", "DACL Switch", "DACL" }, + { "Left Speaker Mixer", "DACR Switch", "DACR" }, + + { "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Speaker Mixer", "DACL Switch", "DACL" }, + { "Right Speaker Mixer", "DACR Switch", "DACR" }, + + { "Left Line Output PGA", NULL, "Left Output Mixer" }, + { "Right Line Output PGA", NULL, "Right Output Mixer" }, + + { "Left Headphone Output PGA", NULL, "Left Output Mixer" }, + { "Right Headphone Output PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker PGA", NULL, "Left Speaker Mixer" }, + { "Right Speaker PGA", NULL, "Right Speaker Mixer" }, + + { "HPL_ENA", NULL, "Left Headphone Output PGA" }, + { "HPR_ENA", NULL, "Right Headphone Output PGA" }, + { "HPL_ENA_DLY", NULL, "HPL_ENA" }, + { "HPR_ENA_DLY", NULL, "HPR_ENA" }, + { "LINEOUTL_ENA", NULL, "Left Line Output PGA" }, + { "LINEOUTR_ENA", NULL, "Right Line Output PGA" }, + { "LINEOUTL_ENA_DLY", NULL, "LINEOUTL_ENA" }, + { "LINEOUTR_ENA_DLY", NULL, "LINEOUTR_ENA" }, + + { "HPL_DCS", NULL, "DCS Master" }, + { "HPR_DCS", NULL, "DCS Master" }, + { "LINEOUTL_DCS", NULL, "DCS Master" }, + { "LINEOUTR_DCS", NULL, "DCS Master" }, + + { "HPL_DCS", NULL, "HPL_ENA_DLY" }, + { "HPR_DCS", NULL, "HPR_ENA_DLY" }, + { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" }, + { "LINEOUTR_DCS", NULL, "LINEOUTR_ENA_DLY" }, + + { "HPL_ENA_OUTP", NULL, "HPL_DCS" }, + { "HPR_ENA_OUTP", NULL, "HPR_DCS" }, + { "LINEOUTL_ENA_OUTP", NULL, "LINEOUTL_DCS" }, + { "LINEOUTR_ENA_OUTP", NULL, "LINEOUTR_DCS" }, + + { "HPL_RMV_SHORT", NULL, "HPL_ENA_OUTP" }, + { "HPR_RMV_SHORT", NULL, "HPR_ENA_OUTP" }, + { "LINEOUTL_RMV_SHORT", NULL, "LINEOUTL_ENA_OUTP" }, + { "LINEOUTR_RMV_SHORT", NULL, "LINEOUTR_ENA_OUTP" }, + + { "HPOUTL", NULL, "HPL_RMV_SHORT" }, + { "HPOUTR", NULL, "HPR_RMV_SHORT" }, + { "LINEOUTL", NULL, "LINEOUTL_RMV_SHORT" }, + { "LINEOUTR", NULL, "LINEOUTR_RMV_SHORT" }, + + { "LOP", NULL, "Left Speaker PGA" }, + { "LON", NULL, "Left Speaker PGA" }, + + { "ROP", NULL, "Right Speaker PGA" }, + { "RON", NULL, "Right Speaker PGA" }, + + { "Charge Pump", NULL, "CLK_DSP" }, + + { "Left Headphone Output PGA", NULL, "Charge Pump" }, + { "Right Headphone Output PGA", NULL, "Charge Pump" }, + { "Left Line Output PGA", NULL, "Charge Pump" }, + { "Right Line Output PGA", NULL, "Charge Pump" }, +}; + +static int wm8903_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_update_bits(component, WM8903_BIAS_CONTROL_0, + WM8903_POBCTRL | WM8903_ISEL_MASK | + WM8903_STARTUP_BIAS_ENA | + WM8903_BIAS_ENA, + WM8903_POBCTRL | + (2 << WM8903_ISEL_SHIFT) | + WM8903_STARTUP_BIAS_ENA); + + snd_soc_component_update_bits(component, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, + WM8903_SPK_DISCHARGE); + + msleep(33); + + snd_soc_component_update_bits(component, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + WM8903_SPKL_ENA | WM8903_SPKR_ENA); + + snd_soc_component_update_bits(component, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, 0); + + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_RES_MASK | + WM8903_VMID_BUF_ENA, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + (2 << WM8903_VMID_SOFT_SHIFT) | + WM8903_VMID_RES_250K | + WM8903_VMID_BUF_ENA); + + msleep(129); + + snd_soc_component_update_bits(component, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + 0); + + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, 0); + + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); + + snd_soc_component_update_bits(component, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA | WM8903_POBCTRL, + WM8903_BIAS_ENA); + + /* By default no bypass paths are enabled so + * enable Class W support. + */ + dev_dbg(component->dev, "Enabling Class W\n"); + snd_soc_component_update_bits(component, WM8903_CLASS_W_0, + WM8903_CP_DYN_FREQ | + WM8903_CP_DYN_V, + WM8903_CP_DYN_FREQ | + WM8903_CP_DYN_V); + } + + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_250K); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA, 0); + + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, + 2 << WM8903_VMID_SOFT_SHIFT); + + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_BUF_ENA, 0); + + msleep(290); + + snd_soc_component_update_bits(component, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | WM8903_VMID_RES_MASK | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_BUF_ENA, 0); + + snd_soc_component_update_bits(component, WM8903_BIAS_CONTROL_0, + WM8903_STARTUP_BIAS_ENA, 0); + break; + } + + return 0; +} + +static int wm8903_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + + wm8903->sysclk = freq; + + return 0; +} + +static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 aif1 = snd_soc_component_read32(component, WM8903_AUDIO_INTERFACE_1); + + aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK | + WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif1 |= WM8903_LRCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif1 |= WM8903_BCLK_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x3; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= 0x3 | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 |= 0x1; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8903_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8903_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8903_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8903_AUDIO_INTERFACE_1, aif1); + + return 0; +} + +static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + reg = snd_soc_component_read32(component, WM8903_DAC_DIGITAL_1); + + if (mute) + reg |= WM8903_DAC_MUTE; + else + reg &= ~WM8903_DAC_MUTE; + + snd_soc_component_write(component, WM8903_DAC_DIGITAL_1, reg); + + return 0; +} + +/* Lookup table for CLK_SYS/fs ratio. 256fs or more is recommended + * for optimal performance so we list the lower rates first and match + * on the last match we find. */ +static struct { + int div; + int rate; + int mode; + int mclk_div; +} clk_sys_ratios[] = { + { 64, 0x0, 0x0, 1 }, + { 68, 0x0, 0x1, 1 }, + { 125, 0x0, 0x2, 1 }, + { 128, 0x1, 0x0, 1 }, + { 136, 0x1, 0x1, 1 }, + { 192, 0x2, 0x0, 1 }, + { 204, 0x2, 0x1, 1 }, + + { 64, 0x0, 0x0, 2 }, + { 68, 0x0, 0x1, 2 }, + { 125, 0x0, 0x2, 2 }, + { 128, 0x1, 0x0, 2 }, + { 136, 0x1, 0x1, 2 }, + { 192, 0x2, 0x0, 2 }, + { 204, 0x2, 0x1, 2 }, + + { 250, 0x2, 0x2, 1 }, + { 256, 0x3, 0x0, 1 }, + { 272, 0x3, 0x1, 1 }, + { 384, 0x4, 0x0, 1 }, + { 408, 0x4, 0x1, 1 }, + { 375, 0x4, 0x2, 1 }, + { 512, 0x5, 0x0, 1 }, + { 544, 0x5, 0x1, 1 }, + { 500, 0x5, 0x2, 1 }, + { 768, 0x6, 0x0, 1 }, + { 816, 0x6, 0x1, 1 }, + { 750, 0x6, 0x2, 1 }, + { 1024, 0x7, 0x0, 1 }, + { 1088, 0x7, 0x1, 1 }, + { 1000, 0x7, 0x2, 1 }, + { 1408, 0x8, 0x0, 1 }, + { 1496, 0x8, 0x1, 1 }, + { 1536, 0x9, 0x0, 1 }, + { 1632, 0x9, 0x1, 1 }, + { 1500, 0x9, 0x2, 1 }, + + { 250, 0x2, 0x2, 2 }, + { 256, 0x3, 0x0, 2 }, + { 272, 0x3, 0x1, 2 }, + { 384, 0x4, 0x0, 2 }, + { 408, 0x4, 0x1, 2 }, + { 375, 0x4, 0x2, 2 }, + { 512, 0x5, 0x0, 2 }, + { 544, 0x5, 0x1, 2 }, + { 500, 0x5, 0x2, 2 }, + { 768, 0x6, 0x0, 2 }, + { 816, 0x6, 0x1, 2 }, + { 750, 0x6, 0x2, 2 }, + { 1024, 0x7, 0x0, 2 }, + { 1088, 0x7, 0x1, 2 }, + { 1000, 0x7, 0x2, 2 }, + { 1408, 0x8, 0x0, 2 }, + { 1496, 0x8, 0x1, 2 }, + { 1536, 0x9, 0x0, 2 }, + { 1632, 0x9, 0x1, 2 }, + { 1500, 0x9, 0x2, 2 }, +}; + +/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */ +static struct { + int ratio; + int div; +} bclk_divs[] = { + { 10, 0 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 15 }, + { 300, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + +/* Sample rates for DSP */ +static struct { + int rate; + int value; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 88200, 9 }, + { 96000, 10 }, + { 0, 0 }, +}; + +static int wm8903_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + int fs = params_rate(params); + int bclk; + int bclk_div; + int i; + int dsp_config; + int clk_config; + int best_val; + int cur_val; + int clk_sys; + + u16 aif1 = snd_soc_component_read32(component, WM8903_AUDIO_INTERFACE_1); + u16 aif2 = snd_soc_component_read32(component, WM8903_AUDIO_INTERFACE_2); + u16 aif3 = snd_soc_component_read32(component, WM8903_AUDIO_INTERFACE_3); + u16 clock0 = snd_soc_component_read32(component, WM8903_CLOCK_RATES_0); + u16 clock1 = snd_soc_component_read32(component, WM8903_CLOCK_RATES_1); + u16 dac_digital1 = snd_soc_component_read32(component, WM8903_DAC_DIGITAL_1); + + /* Enable sloping stopband filter for low sample rates */ + if (fs <= 24000) + dac_digital1 |= WM8903_DAC_SB_FILT; + else + dac_digital1 &= ~WM8903_DAC_SB_FILT; + + /* Configure sample rate logic for DSP - choose nearest rate */ + dsp_config = 0; + best_val = abs(sample_rates[dsp_config].rate - fs); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + cur_val = abs(sample_rates[i].rate - fs); + if (cur_val <= best_val) { + dsp_config = i; + best_val = cur_val; + } + } + + dev_dbg(component->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate); + clock1 &= ~WM8903_SAMPLE_RATE_MASK; + clock1 |= sample_rates[dsp_config].value; + + aif1 &= ~WM8903_AIF_WL_MASK; + bclk = 2 * fs; + switch (params_width(params)) { + case 16: + bclk *= 16; + break; + case 20: + bclk *= 20; + aif1 |= 0x4; + break; + case 24: + bclk *= 24; + aif1 |= 0x8; + break; + case 32: + bclk *= 32; + aif1 |= 0xc; + break; + default: + return -EINVAL; + } + + dev_dbg(component->dev, "MCLK = %dHz, target sample rate = %dHz\n", + wm8903->sysclk, fs); + + /* We may not have an MCLK which allows us to generate exactly + * the clock we want, particularly with USB derived inputs, so + * approximate. + */ + clk_config = 0; + best_val = abs((wm8903->sysclk / + (clk_sys_ratios[0].mclk_div * + clk_sys_ratios[0].div)) - fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) { + cur_val = abs((wm8903->sysclk / + (clk_sys_ratios[i].mclk_div * + clk_sys_ratios[i].div)) - fs); + + if (cur_val <= best_val) { + clk_config = i; + best_val = cur_val; + } + } + + if (clk_sys_ratios[clk_config].mclk_div == 2) { + clock0 |= WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk / 2; + } else { + clock0 &= ~WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk; + } + + clock1 &= ~(WM8903_CLK_SYS_RATE_MASK | + WM8903_CLK_SYS_MODE_MASK); + clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT; + clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT; + + dev_dbg(component->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n", + clk_sys_ratios[clk_config].rate, + clk_sys_ratios[clk_config].mode, + clk_sys_ratios[clk_config].div); + + dev_dbg(component->dev, "Actual CLK_SYS = %dHz\n", clk_sys); + + /* We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + bclk_div = 0; + best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk; + i = 1; + while (i < ARRAY_SIZE(bclk_divs)) { + cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk; + if (cur_val < 0) /* BCLK table is sorted */ + break; + bclk_div = i; + best_val = cur_val; + i++; + } + + aif2 &= ~WM8903_BCLK_DIV_MASK; + aif3 &= ~WM8903_LRCLK_RATE_MASK; + + dev_dbg(component->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n", + bclk_divs[bclk_div].ratio / 10, bclk, + (clk_sys * 10) / bclk_divs[bclk_div].ratio); + + aif2 |= bclk_divs[bclk_div].div; + aif3 |= bclk / fs; + + wm8903->fs = params_rate(params); + wm8903_set_deemph(component); + + snd_soc_component_write(component, WM8903_CLOCK_RATES_0, clock0); + snd_soc_component_write(component, WM8903_CLOCK_RATES_1, clock1); + snd_soc_component_write(component, WM8903_AUDIO_INTERFACE_1, aif1); + snd_soc_component_write(component, WM8903_AUDIO_INTERFACE_2, aif2); + snd_soc_component_write(component, WM8903_AUDIO_INTERFACE_3, aif3); + snd_soc_component_write(component, WM8903_DAC_DIGITAL_1, dac_digital1); + + return 0; +} + +/** + * wm8903_mic_detect - Enable microphone detection via the WM8903 IRQ + * + * @component: WM8903 component + * @jack: jack to report detection events on + * @det: value to report for presence detection + * @shrt: value to report for short detection + * + * Enable microphone detection via IRQ on the WM8903. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for WM8903 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * The current threasholds for detection should be configured using + * micdet_cfg in the platform data. Using this function will force on + * the microphone bias for the device. + */ +int wm8903_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack, + int det, int shrt) +{ + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + int irq_mask = WM8903_MICDET_EINT | WM8903_MICSHRT_EINT; + + dev_dbg(component->dev, "Enabling microphone detection: %x %x\n", + det, shrt); + + /* Store the configuration */ + wm8903->mic_jack = jack; + wm8903->mic_det = det; + wm8903->mic_short = shrt; + + /* Enable interrupts we've got a report configured for */ + if (det) + irq_mask &= ~WM8903_MICDET_EINT; + if (shrt) + irq_mask &= ~WM8903_MICSHRT_EINT; + + snd_soc_component_update_bits(component, WM8903_INTERRUPT_STATUS_1_MASK, + WM8903_MICDET_EINT | WM8903_MICSHRT_EINT, + irq_mask); + + if (det || shrt) { + /* Enable mic detection, this may not have been set through + * platform data (eg, if the defaults are OK). */ + snd_soc_component_update_bits(component, WM8903_WRITE_SEQUENCER_0, + WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + snd_soc_component_update_bits(component, WM8903_MIC_BIAS_CONTROL_0, + WM8903_MICDET_ENA, WM8903_MICDET_ENA); + } else { + snd_soc_component_update_bits(component, WM8903_MIC_BIAS_CONTROL_0, + WM8903_MICDET_ENA, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm8903_mic_detect); + +static irqreturn_t wm8903_irq(int irq, void *data) +{ + struct wm8903_priv *wm8903 = data; + int mic_report, ret; + unsigned int int_val, mask, int_pol; + + ret = regmap_read(wm8903->regmap, WM8903_INTERRUPT_STATUS_1_MASK, + &mask); + if (ret != 0) { + dev_err(wm8903->dev, "Failed to read IRQ mask: %d\n", ret); + return IRQ_NONE; + } + + ret = regmap_read(wm8903->regmap, WM8903_INTERRUPT_STATUS_1, &int_val); + if (ret != 0) { + dev_err(wm8903->dev, "Failed to read IRQ status: %d\n", ret); + return IRQ_NONE; + } + + int_val &= ~mask; + + if (int_val & WM8903_WSEQ_BUSY_EINT) { + dev_warn(wm8903->dev, "Write sequencer done\n"); + } + + /* + * The rest is microphone jack detection. We need to manually + * invert the polarity of the interrupt after each event - to + * simplify the code keep track of the last state we reported + * and just invert the relevant bits in both the report and + * the polarity register. + */ + mic_report = wm8903->mic_last_report; + ret = regmap_read(wm8903->regmap, WM8903_INTERRUPT_POLARITY_1, + &int_pol); + if (ret != 0) { + dev_err(wm8903->dev, "Failed to read interrupt polarity: %d\n", + ret); + return IRQ_HANDLED; + } + +#ifndef CONFIG_SND_SOC_WM8903_MODULE + if (int_val & (WM8903_MICSHRT_EINT | WM8903_MICDET_EINT)) + trace_snd_soc_jack_irq(dev_name(wm8903->dev)); +#endif + + if (int_val & WM8903_MICSHRT_EINT) { + dev_dbg(wm8903->dev, "Microphone short (pol=%x)\n", int_pol); + + mic_report ^= wm8903->mic_short; + int_pol ^= WM8903_MICSHRT_INV; + } + + if (int_val & WM8903_MICDET_EINT) { + dev_dbg(wm8903->dev, "Microphone detect (pol=%x)\n", int_pol); + + mic_report ^= wm8903->mic_det; + int_pol ^= WM8903_MICDET_INV; + + msleep(wm8903->mic_delay); + } + + regmap_update_bits(wm8903->regmap, WM8903_INTERRUPT_POLARITY_1, + WM8903_MICSHRT_INV | WM8903_MICDET_INV, int_pol); + + snd_soc_jack_report(wm8903->mic_jack, mic_report, + wm8903->mic_short | wm8903->mic_det); + + wm8903->mic_last_report = mic_report; + + return IRQ_HANDLED; +} + +#define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define WM8903_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8903_dai_ops = { + .hw_params = wm8903_hw_params, + .digital_mute = wm8903_digital_mute, + .set_fmt = wm8903_set_dai_fmt, + .set_sysclk = wm8903_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver wm8903_dai = { + .name = "wm8903-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_PLAYBACK_RATES, + .formats = WM8903_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_CAPTURE_RATES, + .formats = WM8903_FORMATS, + }, + .ops = &wm8903_dai_ops, + .symmetric_rates = 1, +}; + +static int wm8903_resume(struct snd_soc_component *component) +{ + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + + regcache_sync(wm8903->regmap); + + return 0; +} + +#ifdef CONFIG_GPIOLIB +static int wm8903_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + if (offset >= WM8903_NUM_GPIO) + return -EINVAL; + + return 0; +} + +static int wm8903_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8903_priv *wm8903 = gpiochip_get_data(chip); + unsigned int mask, val; + int ret; + + mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK; + val = (WM8903_GPn_FN_GPIO_INPUT << WM8903_GP1_FN_SHIFT) | + WM8903_GP1_DIR; + + ret = regmap_update_bits(wm8903->regmap, + WM8903_GPIO_CONTROL_1 + offset, mask, val); + if (ret < 0) + return ret; + + return 0; +} + +static int wm8903_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8903_priv *wm8903 = gpiochip_get_data(chip); + unsigned int reg; + + regmap_read(wm8903->regmap, WM8903_GPIO_CONTROL_1 + offset, ®); + + return !!((reg & WM8903_GP1_LVL_MASK) >> WM8903_GP1_LVL_SHIFT); +} + +static int wm8903_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8903_priv *wm8903 = gpiochip_get_data(chip); + unsigned int mask, val; + int ret; + + mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK | WM8903_GP1_LVL_MASK; + val = (WM8903_GPn_FN_GPIO_OUTPUT << WM8903_GP1_FN_SHIFT) | + (value << WM8903_GP2_LVL_SHIFT); + + ret = regmap_update_bits(wm8903->regmap, + WM8903_GPIO_CONTROL_1 + offset, mask, val); + if (ret < 0) + return ret; + + return 0; +} + +static void wm8903_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8903_priv *wm8903 = gpiochip_get_data(chip); + + regmap_update_bits(wm8903->regmap, WM8903_GPIO_CONTROL_1 + offset, + WM8903_GP1_LVL_MASK, + !!value << WM8903_GP1_LVL_SHIFT); +} + +static const struct gpio_chip wm8903_template_chip = { + .label = "wm8903", + .owner = THIS_MODULE, + .request = wm8903_gpio_request, + .direction_input = wm8903_gpio_direction_in, + .get = wm8903_gpio_get, + .direction_output = wm8903_gpio_direction_out, + .set = wm8903_gpio_set, + .can_sleep = 1, +}; + +static void wm8903_init_gpio(struct wm8903_priv *wm8903) +{ + struct wm8903_platform_data *pdata = wm8903->pdata; + int ret; + + wm8903->gpio_chip = wm8903_template_chip; + wm8903->gpio_chip.ngpio = WM8903_NUM_GPIO; + wm8903->gpio_chip.parent = wm8903->dev; + + if (pdata->gpio_base) + wm8903->gpio_chip.base = pdata->gpio_base; + else + wm8903->gpio_chip.base = -1; + + ret = gpiochip_add_data(&wm8903->gpio_chip, wm8903); + if (ret != 0) + dev_err(wm8903->dev, "Failed to add GPIOs: %d\n", ret); +} + +static void wm8903_free_gpio(struct wm8903_priv *wm8903) +{ + gpiochip_remove(&wm8903->gpio_chip); +} +#else +static void wm8903_init_gpio(struct wm8903_priv *wm8903) +{ +} + +static void wm8903_free_gpio(struct wm8903_priv *wm8903) +{ +} +#endif + +static const struct snd_soc_component_driver soc_component_dev_wm8903 = { + .resume = wm8903_resume, + .set_bias_level = wm8903_set_bias_level, + .seq_notifier = wm8903_seq_notifier, + .controls = wm8903_snd_controls, + .num_controls = ARRAY_SIZE(wm8903_snd_controls), + .dapm_widgets = wm8903_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8903_dapm_widgets), + .dapm_routes = wm8903_intercon, + .num_dapm_routes = ARRAY_SIZE(wm8903_intercon), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8903_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM8903_MAX_REGISTER, + .volatile_reg = wm8903_volatile_register, + .readable_reg = wm8903_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8903_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8903_reg_defaults), +}; + +static int wm8903_set_pdata_irq_trigger(struct i2c_client *i2c, + struct wm8903_platform_data *pdata) +{ + struct irq_data *irq_data = irq_get_irq_data(i2c->irq); + if (!irq_data) { + dev_err(&i2c->dev, "Invalid IRQ: %d\n", + i2c->irq); + return -EINVAL; + } + + switch (irqd_get_trigger_type(irq_data)) { + case IRQ_TYPE_NONE: + default: + /* + * We assume the controller imposes no restrictions, + * so we are able to select active-high + */ + /* Fall-through */ + case IRQ_TYPE_LEVEL_HIGH: + pdata->irq_active_low = false; + break; + case IRQ_TYPE_LEVEL_LOW: + pdata->irq_active_low = true; + break; + } + + return 0; +} + +static int wm8903_set_pdata_from_of(struct i2c_client *i2c, + struct wm8903_platform_data *pdata) +{ + const struct device_node *np = i2c->dev.of_node; + u32 val32; + int i; + + if (of_property_read_u32(np, "micdet-cfg", &val32) >= 0) + pdata->micdet_cfg = val32; + + if (of_property_read_u32(np, "micdet-delay", &val32) >= 0) + pdata->micdet_delay = val32; + + if (of_property_read_u32_array(np, "gpio-cfg", pdata->gpio_cfg, + ARRAY_SIZE(pdata->gpio_cfg)) >= 0) { + /* + * In device tree: 0 means "write 0", + * 0xffffffff means "don't touch". + * + * In platform data: 0 means "don't touch", + * 0x8000 means "write 0". + * + * Note: WM8903_GPIO_CONFIG_ZERO == 0x8000. + * + * Convert from DT to pdata representation here, + * so no other code needs to change. + */ + for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { + if (pdata->gpio_cfg[i] == 0) { + pdata->gpio_cfg[i] = WM8903_GPIO_CONFIG_ZERO; + } else if (pdata->gpio_cfg[i] == 0xffffffff) { + pdata->gpio_cfg[i] = 0; + } else if (pdata->gpio_cfg[i] > 0x7fff) { + dev_err(&i2c->dev, "Invalid gpio-cfg[%d] %x\n", + i, pdata->gpio_cfg[i]); + return -EINVAL; + } + } + } + + return 0; +} + +static int wm8903_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8903_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct wm8903_priv *wm8903; + int trigger; + bool mic_gpio = false; + unsigned int val, irq_pol; + int ret, i; + + wm8903 = devm_kzalloc(&i2c->dev, sizeof(*wm8903), GFP_KERNEL); + if (wm8903 == NULL) + return -ENOMEM; + + mutex_init(&wm8903->lock); + wm8903->dev = &i2c->dev; + + wm8903->regmap = devm_regmap_init_i2c(i2c, &wm8903_regmap); + if (IS_ERR(wm8903->regmap)) { + ret = PTR_ERR(wm8903->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8903); + + /* If no platform data was supplied, create storage for defaults */ + if (pdata) { + wm8903->pdata = pdata; + } else { + wm8903->pdata = devm_kzalloc(&i2c->dev, sizeof(*wm8903->pdata), + GFP_KERNEL); + if (!wm8903->pdata) + return -ENOMEM; + + if (i2c->irq) { + ret = wm8903_set_pdata_irq_trigger(i2c, wm8903->pdata); + if (ret != 0) + return ret; + } + + if (i2c->dev.of_node) { + ret = wm8903_set_pdata_from_of(i2c, wm8903->pdata); + if (ret != 0) + return ret; + } + } + + pdata = wm8903->pdata; + + for (i = 0; i < ARRAY_SIZE(wm8903->supplies); i++) + wm8903->supplies[i].supply = wm8903_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_read(wm8903->regmap, WM8903_SW_RESET_AND_ID, &val); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read chip ID: %d\n", ret); + goto err; + } + if (val != 0x8903) { + dev_err(&i2c->dev, "Device with ID %x is not a WM8903\n", val); + ret = -ENODEV; + goto err; + } + + ret = regmap_read(wm8903->regmap, WM8903_REVISION_NUMBER, &val); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read chip revision: %d\n", ret); + goto err; + } + dev_info(&i2c->dev, "WM8903 revision %c\n", + (val & WM8903_CHIP_REV_MASK) + 'A'); + + /* Reset the device */ + regmap_write(wm8903->regmap, WM8903_SW_RESET_AND_ID, 0x8903); + + wm8903_init_gpio(wm8903); + + /* Set up GPIO pin state, detect if any are MIC detect outputs */ + for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { + if ((!pdata->gpio_cfg[i]) || + (pdata->gpio_cfg[i] > WM8903_GPIO_CONFIG_ZERO)) + continue; + + regmap_write(wm8903->regmap, WM8903_GPIO_CONTROL_1 + i, + pdata->gpio_cfg[i] & 0x7fff); + + val = (pdata->gpio_cfg[i] & WM8903_GP1_FN_MASK) + >> WM8903_GP1_FN_SHIFT; + + switch (val) { + case WM8903_GPn_FN_MICBIAS_CURRENT_DETECT: + case WM8903_GPn_FN_MICBIAS_SHORT_DETECT: + mic_gpio = true; + break; + default: + break; + } + } + + /* Set up microphone detection */ + regmap_write(wm8903->regmap, WM8903_MIC_BIAS_CONTROL_0, + pdata->micdet_cfg); + + /* Microphone detection needs the WSEQ clock */ + if (pdata->micdet_cfg) + regmap_update_bits(wm8903->regmap, WM8903_WRITE_SEQUENCER_0, + WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + + /* If microphone detection is enabled by pdata but + * detected via IRQ then interrupts can be lost before + * the machine driver has set up microphone detection + * IRQs as the IRQs are clear on read. The detection + * will be enabled when the machine driver configures. + */ + WARN_ON(!mic_gpio && (pdata->micdet_cfg & WM8903_MICDET_ENA)); + + wm8903->mic_delay = pdata->micdet_delay; + + if (i2c->irq) { + if (pdata->irq_active_low) { + trigger = IRQF_TRIGGER_LOW; + irq_pol = WM8903_IRQ_POL; + } else { + trigger = IRQF_TRIGGER_HIGH; + irq_pol = 0; + } + + regmap_update_bits(wm8903->regmap, WM8903_INTERRUPT_CONTROL, + WM8903_IRQ_POL, irq_pol); + + ret = request_threaded_irq(i2c->irq, NULL, wm8903_irq, + trigger | IRQF_ONESHOT, + "wm8903", wm8903); + if (ret != 0) { + dev_err(wm8903->dev, "Failed to request IRQ: %d\n", + ret); + return ret; + } + + /* Enable write sequencer interrupts */ + regmap_update_bits(wm8903->regmap, + WM8903_INTERRUPT_STATUS_1_MASK, + WM8903_IM_WSEQ_BUSY_EINT, 0); + } + + /* Latch volume update bits */ + regmap_update_bits(wm8903->regmap, WM8903_ADC_DIGITAL_VOLUME_LEFT, + WM8903_ADCVU, WM8903_ADCVU); + regmap_update_bits(wm8903->regmap, WM8903_ADC_DIGITAL_VOLUME_RIGHT, + WM8903_ADCVU, WM8903_ADCVU); + + regmap_update_bits(wm8903->regmap, WM8903_DAC_DIGITAL_VOLUME_LEFT, + WM8903_DACVU, WM8903_DACVU); + regmap_update_bits(wm8903->regmap, WM8903_DAC_DIGITAL_VOLUME_RIGHT, + WM8903_DACVU, WM8903_DACVU); + + regmap_update_bits(wm8903->regmap, WM8903_ANALOGUE_OUT1_LEFT, + WM8903_HPOUTVU, WM8903_HPOUTVU); + regmap_update_bits(wm8903->regmap, WM8903_ANALOGUE_OUT1_RIGHT, + WM8903_HPOUTVU, WM8903_HPOUTVU); + + regmap_update_bits(wm8903->regmap, WM8903_ANALOGUE_OUT2_LEFT, + WM8903_LINEOUTVU, WM8903_LINEOUTVU); + regmap_update_bits(wm8903->regmap, WM8903_ANALOGUE_OUT2_RIGHT, + WM8903_LINEOUTVU, WM8903_LINEOUTVU); + + regmap_update_bits(wm8903->regmap, WM8903_ANALOGUE_OUT3_LEFT, + WM8903_SPKVU, WM8903_SPKVU); + regmap_update_bits(wm8903->regmap, WM8903_ANALOGUE_OUT3_RIGHT, + WM8903_SPKVU, WM8903_SPKVU); + + /* Enable DAC soft mute by default */ + regmap_update_bits(wm8903->regmap, WM8903_DAC_DIGITAL_1, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8903, &wm8903_dai, 1); + if (ret != 0) + goto err; + + return 0; +err: + regulator_bulk_disable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + return ret; +} + +static int wm8903_i2c_remove(struct i2c_client *client) +{ + struct wm8903_priv *wm8903 = i2c_get_clientdata(client); + + regulator_bulk_disable(ARRAY_SIZE(wm8903->supplies), + wm8903->supplies); + if (client->irq) + free_irq(client->irq, wm8903); + wm8903_free_gpio(wm8903); + + return 0; +} + +static const struct of_device_id wm8903_of_match[] = { + { .compatible = "wlf,wm8903", }, + {}, +}; +MODULE_DEVICE_TABLE(of, wm8903_of_match); + +static const struct i2c_device_id wm8903_i2c_id[] = { + { "wm8903", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id); + +static struct i2c_driver wm8903_i2c_driver = { + .driver = { + .name = "wm8903", + .of_match_table = wm8903_of_match, + }, + .probe = wm8903_i2c_probe, + .remove = wm8903_i2c_remove, + .id_table = wm8903_i2c_id, +}; + +module_i2c_driver(wm8903_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8903 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h new file mode 100644 index 000000000..1301aa9dc --- /dev/null +++ b/sound/soc/codecs/wm8903.h @@ -0,0 +1,1225 @@ +/* + * wm8903.h - WM8903 audio codec interface + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8903_H +#define _WM8903_H + +#include + +extern int wm8903_mic_detect(struct snd_soc_component *component, + struct snd_soc_jack *jack, + int det, int shrt); + + +/* + * Register values. + */ +#define WM8903_SW_RESET_AND_ID 0x00 +#define WM8903_REVISION_NUMBER 0x01 +#define WM8903_BIAS_CONTROL_0 0x04 +#define WM8903_VMID_CONTROL_0 0x05 +#define WM8903_MIC_BIAS_CONTROL_0 0x06 +#define WM8903_ANALOGUE_DAC_0 0x08 +#define WM8903_ANALOGUE_ADC_0 0x0A +#define WM8903_POWER_MANAGEMENT_0 0x0C +#define WM8903_POWER_MANAGEMENT_1 0x0D +#define WM8903_POWER_MANAGEMENT_2 0x0E +#define WM8903_POWER_MANAGEMENT_3 0x0F +#define WM8903_POWER_MANAGEMENT_4 0x10 +#define WM8903_POWER_MANAGEMENT_5 0x11 +#define WM8903_POWER_MANAGEMENT_6 0x12 +#define WM8903_CLOCK_RATES_0 0x14 +#define WM8903_CLOCK_RATES_1 0x15 +#define WM8903_CLOCK_RATES_2 0x16 +#define WM8903_AUDIO_INTERFACE_0 0x18 +#define WM8903_AUDIO_INTERFACE_1 0x19 +#define WM8903_AUDIO_INTERFACE_2 0x1A +#define WM8903_AUDIO_INTERFACE_3 0x1B +#define WM8903_DAC_DIGITAL_VOLUME_LEFT 0x1E +#define WM8903_DAC_DIGITAL_VOLUME_RIGHT 0x1F +#define WM8903_DAC_DIGITAL_0 0x20 +#define WM8903_DAC_DIGITAL_1 0x21 +#define WM8903_ADC_DIGITAL_VOLUME_LEFT 0x24 +#define WM8903_ADC_DIGITAL_VOLUME_RIGHT 0x25 +#define WM8903_ADC_DIGITAL_0 0x26 +#define WM8903_DIGITAL_MICROPHONE_0 0x27 +#define WM8903_DRC_0 0x28 +#define WM8903_DRC_1 0x29 +#define WM8903_DRC_2 0x2A +#define WM8903_DRC_3 0x2B +#define WM8903_ANALOGUE_LEFT_INPUT_0 0x2C +#define WM8903_ANALOGUE_RIGHT_INPUT_0 0x2D +#define WM8903_ANALOGUE_LEFT_INPUT_1 0x2E +#define WM8903_ANALOGUE_RIGHT_INPUT_1 0x2F +#define WM8903_ANALOGUE_LEFT_MIX_0 0x32 +#define WM8903_ANALOGUE_RIGHT_MIX_0 0x33 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_0 0x34 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_1 0x35 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0 0x36 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1 0x37 +#define WM8903_ANALOGUE_OUT1_LEFT 0x39 +#define WM8903_ANALOGUE_OUT1_RIGHT 0x3A +#define WM8903_ANALOGUE_OUT2_LEFT 0x3B +#define WM8903_ANALOGUE_OUT2_RIGHT 0x3C +#define WM8903_ANALOGUE_OUT3_LEFT 0x3E +#define WM8903_ANALOGUE_OUT3_RIGHT 0x3F +#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41 +#define WM8903_DC_SERVO_0 0x43 +#define WM8903_DC_SERVO_2 0x45 +#define WM8903_DC_SERVO_4 0x47 +#define WM8903_DC_SERVO_5 0x48 +#define WM8903_DC_SERVO_6 0x49 +#define WM8903_DC_SERVO_7 0x4A +#define WM8903_DC_SERVO_READBACK_1 0x51 +#define WM8903_DC_SERVO_READBACK_2 0x52 +#define WM8903_DC_SERVO_READBACK_3 0x53 +#define WM8903_DC_SERVO_READBACK_4 0x54 +#define WM8903_ANALOGUE_HP_0 0x5A +#define WM8903_ANALOGUE_LINEOUT_0 0x5E +#define WM8903_CHARGE_PUMP_0 0x62 +#define WM8903_CLASS_W_0 0x68 +#define WM8903_WRITE_SEQUENCER_0 0x6C +#define WM8903_WRITE_SEQUENCER_1 0x6D +#define WM8903_WRITE_SEQUENCER_2 0x6E +#define WM8903_WRITE_SEQUENCER_3 0x6F +#define WM8903_WRITE_SEQUENCER_4 0x70 +#define WM8903_CONTROL_INTERFACE 0x72 +#define WM8903_GPIO_CONTROL_1 0x74 +#define WM8903_GPIO_CONTROL_2 0x75 +#define WM8903_GPIO_CONTROL_3 0x76 +#define WM8903_GPIO_CONTROL_4 0x77 +#define WM8903_GPIO_CONTROL_5 0x78 +#define WM8903_INTERRUPT_STATUS_1 0x79 +#define WM8903_INTERRUPT_STATUS_1_MASK 0x7A +#define WM8903_INTERRUPT_POLARITY_1 0x7B +#define WM8903_INTERRUPT_CONTROL 0x7E +#define WM8903_CLOCK_RATE_TEST_4 0xA4 +#define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC + +#define WM8903_REGISTER_COUNT 75 +#define WM8903_MAX_REGISTER 0xAC + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - SW Reset and ID + */ +#define WM8903_SW_RESET_DEV_ID1_MASK 0xFFFF /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_SHIFT 0 /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_WIDTH 16 /* SW_RESET_DEV_ID1 - [15:0] */ + +/* + * R1 (0x01) - Revision Number + */ +#define WM8903_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */ + +/* + * R4 (0x04) - Bias Control 0 + */ +#define WM8903_POBCTRL 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_MASK 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_SHIFT 4 /* POBCTRL */ +#define WM8903_POBCTRL_WIDTH 1 /* POBCTRL */ +#define WM8903_ISEL_MASK 0x000C /* ISEL - [3:2] */ +#define WM8903_ISEL_SHIFT 2 /* ISEL - [3:2] */ +#define WM8903_ISEL_WIDTH 2 /* ISEL - [3:2] */ +#define WM8903_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8903_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R5 (0x05) - VMID Control 0 + */ +#define WM8903_VMID_TIE_ENA 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_MASK 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_SHIFT 7 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_WIDTH 1 /* VMID_TIE_ENA */ +#define WM8903_BUFIO_ENA 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_MASK 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_SHIFT 6 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_WIDTH 1 /* BUFIO_ENA */ +#define WM8903_VMID_IO_ENA 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_MASK 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_SHIFT 5 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_WIDTH 1 /* VMID_IO_ENA */ +#define WM8903_VMID_SOFT_MASK 0x0018 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_SHIFT 3 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_WIDTH 2 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM8903_VMID_BUF_ENA 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_MASK 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_SHIFT 0 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ + +#define WM8903_VMID_RES_50K 2 +#define WM8903_VMID_RES_250K 4 +#define WM8903_VMID_RES_5K 6 + +/* + * R8 (0x08) - Analogue DAC 0 + */ +#define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_SHIFT 3 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_WIDTH 2 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACVMID_BIAS_SEL_MASK 0x0006 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_SHIFT 1 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_WIDTH 2 /* DACVMID_BIAS_SEL - [2:1] */ + +/* + * R10 (0x0A) - Analogue ADC 0 + */ +#define WM8903_ADC_OSR128 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ + +/* + * R12 (0x0C) - Power Management 0 + */ +#define WM8903_INL_ENA 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_MASK 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_SHIFT 1 /* INL_ENA */ +#define WM8903_INL_ENA_WIDTH 1 /* INL_ENA */ +#define WM8903_INR_ENA 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_MASK 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_SHIFT 0 /* INR_ENA */ +#define WM8903_INR_ENA_WIDTH 1 /* INR_ENA */ + +/* + * R13 (0x0D) - Power Management 1 + */ +#define WM8903_MIXOUTL_ENA 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_MASK 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_SHIFT 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTR_ENA 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_MASK 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_SHIFT 0 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ + +/* + * R14 (0x0E) - Power Management 2 + */ +#define WM8903_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */ +#define WM8903_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */ + +/* + * R15 (0x0F) - Power Management 3 + */ +#define WM8903_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */ + +/* + * R16 (0x10) - Power Management 4 + */ +#define WM8903_MIXSPKL_ENA 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_MASK 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_SHIFT 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_WIDTH 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKR_ENA 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_MASK 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_SHIFT 0 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_WIDTH 1 /* MIXSPKR_ENA */ + +/* + * R17 (0x11) - Power Management 5 + */ +#define WM8903_SPKL_ENA 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_MASK 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_SHIFT 1 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ +#define WM8903_SPKR_ENA 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_MASK 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_SHIFT 0 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ + +/* + * R18 (0x12) - Power Management 6 + */ +#define WM8903_DACL_ENA 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_MASK 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_SHIFT 3 /* DACL_ENA */ +#define WM8903_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8903_DACR_ENA 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_MASK 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_SHIFT 2 /* DACR_ENA */ +#define WM8903_DACR_ENA_WIDTH 1 /* DACR_ENA */ +#define WM8903_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8903_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R20 (0x14) - Clock Rates 0 + */ +#define WM8903_MCLKDIV2 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_MASK 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_SHIFT 0 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ + +/* + * R21 (0x15) - Clock Rates 1 + */ +#define WM8903_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_MODE_MASK 0x0300 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_SHIFT 8 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_WIDTH 2 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */ + +/* + * R22 (0x16) - Clock Rates 2 + */ +#define WM8903_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8903_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8903_TO_ENA 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_MASK 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_SHIFT 0 /* TO_ENA */ +#define WM8903_TO_ENA_WIDTH 1 /* TO_ENA */ + +/* + * R24 (0x18) - Audio Interface 0 + */ +#define WM8903_DACL_DATINV 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_SHIFT 12 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_WIDTH 1 /* DACL_DATINV */ +#define WM8903_DACR_DATINV 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_SHIFT 11 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_WIDTH 1 /* DACR_DATINV */ +#define WM8903_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */ +#define WM8903_LOOPBACK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_MASK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_SHIFT 8 /* LOOPBACK */ +#define WM8903_LOOPBACK_WIDTH 1 /* LOOPBACK */ +#define WM8903_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */ +#define WM8903_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */ +#define WM8903_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */ +#define WM8903_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */ +#define WM8903_ADC_COMP 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_MASK 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_SHIFT 3 /* ADC_COMP */ +#define WM8903_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8903_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8903_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM8903_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8903_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R25 (0x19) - Audio Interface 1 + */ +#define WM8903_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFADC_TDM 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */ +#define WM8903_LRCLK_DIR 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_MASK 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_SHIFT 9 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM8903_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM8903_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM8903_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM8903_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R26 (0x1A) - Audio Interface 2 + */ +#define WM8903_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R27 (0x1B) - Audio Interface 3 + */ +#define WM8903_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R30 (0x1E) - DAC Digital Volume Left + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital Volume Right + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R32 (0x20) - DAC Digital 0 + */ +#define WM8903_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */ + +/* + * R33 (0x21) - DAC Digital 1 + */ +#define WM8903_DAC_MONO 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_MASK 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_SHIFT 12 /* DAC_MONO */ +#define WM8903_DAC_MONO_WIDTH 1 /* DAC_MONO */ +#define WM8903_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */ +#define WM8903_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8903_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R36 (0x24) - ADC Digital Volume Left + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R37 (0x25) - ADC Digital Volume Right + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R38 (0x26) - ADC Digital 0 + */ +#define WM8903_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_ENA 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_MASK 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_SHIFT 4 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_WIDTH 1 /* ADC_HPF_ENA */ +#define WM8903_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */ +#define WM8903_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */ + +/* + * R39 (0x27) - Digital Microphone 0 + */ +#define WM8903_DIGMIC_MODE_SEL 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_MASK 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_SHIFT 8 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_WIDTH 1 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_CLK_SEL_L_MASK 0x00C0 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_SHIFT 6 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_WIDTH 2 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_R_MASK 0x0030 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_SHIFT 4 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_WIDTH 2 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_RT_MASK 0x000C /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_MASK 0x0003 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_SHIFT 0 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_WIDTH 2 /* DIGMIC_CLK_SEL - [1:0] */ + +/* + * R40 (0x28) - DRC 0 + */ +#define WM8903_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM8903_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM8903_DRC_THRESH_HYST_MASK 0x1800 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_SHIFT 11 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */ +#define WM8903_DRC_SMOOTH_ENA 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_MASK 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_SHIFT 3 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_QR_ENA 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_MASK 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_SHIFT 2 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */ +#define WM8903_DRC_ANTICLIP_ENA 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_MASK 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_SHIFT 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_HYST_ENA 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_MASK 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_SHIFT 0 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */ + +/* + * R41 (0x29) - DRC 1 + */ +#define WM8903_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_THRESH_QR_MASK 0x00C0 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_SHIFT 6 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_RATE_QR_MASK 0x0030 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_SHIFT 4 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R42 (0x2A) - DRC 2 + */ +#define WM8903_DRC_R0_SLOPE_COMP_MASK 0x0038 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_SHIFT 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R1_SLOPE_COMP_MASK 0x0007 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_SHIFT 0 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [2:0] */ + +/* + * R43 (0x2B) - DRC 3 + */ +#define WM8903_DRC_THRESH_COMP_MASK 0x07E0 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_SHIFT 5 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_AMP_COMP_MASK 0x001F /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_SHIFT 0 /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [4:0] */ + +/* + * R44 (0x2C) - Analogue Left Input 0 + */ +#define WM8903_LINMUTE 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8903_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8903_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */ + +/* + * R45 (0x2D) - Analogue Right Input 0 + */ +#define WM8903_RINMUTE 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8903_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8903_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */ + +/* + * R46 (0x2E) - Analogue Left Input 1 + */ +#define WM8903_INL_CM_ENA 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */ +#define WM8903_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_SHIFT 0 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_WIDTH 2 /* L_MODE - [1:0] */ + +/* + * R47 (0x2F) - Analogue Right Input 1 + */ +#define WM8903_INR_CM_ENA 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */ +#define WM8903_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_SHIFT 0 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_WIDTH 2 /* R_MODE - [1:0] */ + +/* + * R50 (0x32) - Analogue Left Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTL 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_MASK 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_SHIFT 3 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_MASK 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_SHIFT 2 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_WIDTH 1 /* DACR_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_MASK 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_MASK 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT 0 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH 1 /* BYPASSR_TO_MIXOUTL */ + +/* + * R51 (0x33) - Analogue Right Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTR 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_MASK 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_SHIFT 3 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_WIDTH 1 /* DACL_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_MASK 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_SHIFT 2 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_MASK 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_MASK 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT 0 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH 1 /* BYPASSR_TO_MIXOUTR */ + +/* + * R52 (0x34) - Analogue Spk Mix Left 0 + */ +#define WM8903_DACL_TO_MIXSPKL 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_MASK 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_SHIFT 3 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_WIDTH 1 /* DACL_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_MASK 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_SHIFT 2 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_WIDTH 1 /* DACR_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_MASK 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_MASK 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT 0 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH 1 /* BYPASSR_TO_MIXSPKL */ + +/* + * R53 (0x35) - Analogue Spk Mix Left 1 + */ +#define WM8903_DACL_MIXSPKL_VOL 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_MASK 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_SHIFT 3 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_WIDTH 1 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_MASK 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_SHIFT 2 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_WIDTH 1 /* DACR_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_MASK 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_MASK 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT 0 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH 1 /* BYPASSR_MIXSPKL_VOL */ + +/* + * R54 (0x36) - Analogue Spk Mix Right 0 + */ +#define WM8903_DACL_TO_MIXSPKR 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_MASK 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_SHIFT 3 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_WIDTH 1 /* DACL_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_MASK 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_SHIFT 2 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_WIDTH 1 /* DACR_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_MASK 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_MASK 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT 0 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH 1 /* BYPASSR_TO_MIXSPKR */ + +/* + * R55 (0x37) - Analogue Spk Mix Right 1 + */ +#define WM8903_DACL_MIXSPKR_VOL 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_MASK 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_SHIFT 3 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_WIDTH 1 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_MASK 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_SHIFT 2 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_WIDTH 1 /* DACR_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_MASK 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_MASK 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT 0 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH 1 /* BYPASSR_MIXSPKR_VOL */ + +/* + * R57 (0x39) - Analogue OUT1 Left + */ +#define WM8903_HPL_MUTE 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_MASK 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_SHIFT 8 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_WIDTH 1 /* HPL_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTLZC 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_SHIFT 6 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_WIDTH 1 /* HPOUTLZC */ +#define WM8903_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */ + +/* + * R58 (0x3A) - Analogue OUT1 Right + */ +#define WM8903_HPR_MUTE 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_MASK 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_SHIFT 8 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_WIDTH 1 /* HPR_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTRZC 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_SHIFT 6 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_WIDTH 1 /* HPOUTRZC */ +#define WM8903_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */ + +/* + * R59 (0x3B) - Analogue OUT2 Left + */ +#define WM8903_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTLZC 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */ +#define WM8903_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */ + +/* + * R60 (0x3C) - Analogue OUT2 Right + */ +#define WM8903_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTRZC 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */ +#define WM8903_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */ + +/* + * R62 (0x3E) - Analogue OUT3 Left + */ +#define WM8903_SPKL_MUTE 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_MASK 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_SHIFT 8 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKLZC 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_MASK 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_SHIFT 6 /* SPKLZC */ +#define WM8903_SPKLZC_WIDTH 1 /* SPKLZC */ +#define WM8903_SPKL_VOL_MASK 0x003F /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_WIDTH 6 /* SPKL_VOL - [5:0] */ + +/* + * R63 (0x3F) - Analogue OUT3 Right + */ +#define WM8903_SPKR_MUTE 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_MASK 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_SHIFT 8 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKRZC 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_MASK 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_SHIFT 6 /* SPKRZC */ +#define WM8903_SPKRZC_WIDTH 1 /* SPKRZC */ +#define WM8903_SPKR_VOL_MASK 0x003F /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_SHIFT 0 /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_WIDTH 6 /* SPKR_VOL - [5:0] */ + +/* + * R65 (0x41) - Analogue SPK Output Control 0 + */ +#define WM8903_SPK_DISCHARGE 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_MASK 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_SHIFT 1 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_WIDTH 1 /* SPK_DISCHARGE */ +#define WM8903_VROI 0x0001 /* VROI */ +#define WM8903_VROI_MASK 0x0001 /* VROI */ +#define WM8903_VROI_SHIFT 0 /* VROI */ +#define WM8903_VROI_WIDTH 1 /* VROI */ + +/* + * R67 (0x43) - DC Servo 0 + */ +#define WM8903_DCS_MASTER_ENA 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_MASK 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_SHIFT 4 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_WIDTH 1 /* DCS_MASTER_ENA */ +#define WM8903_DCS_ENA_MASK 0x000F /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_SHIFT 0 /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_WIDTH 4 /* DCS_ENA - [3:0] */ + +/* + * R69 (0x45) - DC Servo 2 + */ +#define WM8903_DCS_MODE_MASK 0x0003 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_SHIFT 0 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_WIDTH 2 /* DCS_MODE - [1:0] */ + +/* + * R90 (0x5A) - Analogue HP 0 + */ +#define WM8903_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8903_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8903_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8903_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8903_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8903_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R94 (0x5E) - Analogue Lineout 0 + */ +#define WM8903_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */ + +/* + * R98 (0x62) - Charge Pump 0 + */ +#define WM8903_CP_ENA 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8903_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R104 (0x68) - Class W 0 + */ +#define WM8903_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_V 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_SHIFT 0 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_WIDTH 1 /* CP_DYN_V */ + +/* + * R108 (0x6C) - Write Sequencer 0 + */ +#define WM8903_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8903_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R109 (0x6D) - Write Sequencer 1 + */ +#define WM8903_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R110 (0x6E) - Write Sequencer 2 + */ +#define WM8903_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8903_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R111 (0x6F) - Write Sequencer 3 + */ +#define WM8903_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8903_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8903_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8903_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R112 (0x70) - Write Sequencer 4 + */ +#define WM8903_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R114 (0x72) - Control Interface + */ +#define WM8903_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ + +/* + * R121 (0x79) - Interrupt Status 1 + */ +#define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_MASK 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_SHIFT 15 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_WIDTH 1 /* MICSHRT_EINT */ +#define WM8903_MICDET_EINT 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_MASK 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_SHIFT 14 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_WIDTH 1 /* MICDET_EINT */ +#define WM8903_WSEQ_BUSY_EINT 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_MASK 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_SHIFT 13 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM8903_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM8903_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM8903_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM8903_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM8903_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM8903_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM8903_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM8903_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM8903_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM8903_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R122 (0x7A) - Interrupt Status 1 Mask + */ +#define WM8903_IM_MICSHRT_EINT 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_MASK 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_SHIFT 15 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_WIDTH 1 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICDET_EINT 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_MASK 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_SHIFT 14 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_WIDTH 1 /* IM_MICDET_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_MASK 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT 13 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM8903_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM8903_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM8903_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R123 (0x7B) - Interrupt Polarity 1 + */ +#define WM8903_MICSHRT_INV 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_MASK 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_SHIFT 15 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_WIDTH 1 /* MICSHRT_INV */ +#define WM8903_MICDET_INV 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_MASK 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_SHIFT 14 /* MICDET_INV */ +#define WM8903_MICDET_INV_WIDTH 1 /* MICDET_INV */ + +/* + * R126 (0x7E) - Interrupt Control + */ +#define WM8903_IRQ_POL 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_MASK 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_SHIFT 0 /* IRQ_POL */ +#define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */ + +/* + * R164 (0xA4) - Clock Rate Test 4 + */ +#define WM8903_ADC_DIG_MIC 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_MASK 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_SHIFT 9 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_WIDTH 1 /* ADC_DIG_MIC */ + +/* + * R172 (0xAC) - Analogue Output Bias 0 + */ +#define WM8903_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */ + +#endif diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c new file mode 100644 index 000000000..d14e851b9 --- /dev/null +++ b/sound/soc/codecs/wm8904.c @@ -0,0 +1,2304 @@ +/* + * wm8904.c -- WM8904 ALSA SoC Audio driver + * + * Copyright 2009-12 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8904.h" + +enum wm8904_type { + WM8904, + WM8912, +}; + +#define WM8904_NUM_DCS_CHANNELS 4 + +#define WM8904_NUM_SUPPLIES 5 +static const char *wm8904_supply_names[WM8904_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "AVDD", + "CPVDD", + "MICVDD", +}; + +/* codec private data */ +struct wm8904_priv { + struct regmap *regmap; + struct clk *mclk; + + enum wm8904_type devtype; + + struct regulator_bulk_data supplies[WM8904_NUM_SUPPLIES]; + + struct wm8904_pdata *pdata; + + int deemph; + + /* Platform provided DRC configuration */ + const char **drc_texts; + int drc_cfg; + struct soc_enum drc_enum; + + /* Platform provided ReTune mobile configuration */ + int num_retune_mobile_texts; + const char **retune_mobile_texts; + int retune_mobile_cfg; + struct soc_enum retune_mobile_enum; + + /* FLL setup */ + int fll_src; + int fll_fref; + int fll_fout; + + /* Clocking configuration */ + unsigned int mclk_rate; + int sysclk_src; + unsigned int sysclk_rate; + + int tdm_width; + int tdm_slots; + int bclk; + int fs; + + /* DC servo configuration - cached offset values */ + int dcs_state[WM8904_NUM_DCS_CHANNELS]; +}; + +static const struct reg_default wm8904_reg_defaults[] = { + { 4, 0x0018 }, /* R4 - Bias Control 0 */ + { 5, 0x0000 }, /* R5 - VMID Control 0 */ + { 6, 0x0000 }, /* R6 - Mic Bias Control 0 */ + { 7, 0x0000 }, /* R7 - Mic Bias Control 1 */ + { 8, 0x0001 }, /* R8 - Analogue DAC 0 */ + { 9, 0x9696 }, /* R9 - mic Filter Control */ + { 10, 0x0001 }, /* R10 - Analogue ADC 0 */ + { 12, 0x0000 }, /* R12 - Power Management 0 */ + { 14, 0x0000 }, /* R14 - Power Management 2 */ + { 15, 0x0000 }, /* R15 - Power Management 3 */ + { 18, 0x0000 }, /* R18 - Power Management 6 */ + { 20, 0x945E }, /* R20 - Clock Rates 0 */ + { 21, 0x0C05 }, /* R21 - Clock Rates 1 */ + { 22, 0x0006 }, /* R22 - Clock Rates 2 */ + { 24, 0x0050 }, /* R24 - Audio Interface 0 */ + { 25, 0x000A }, /* R25 - Audio Interface 1 */ + { 26, 0x00E4 }, /* R26 - Audio Interface 2 */ + { 27, 0x0040 }, /* R27 - Audio Interface 3 */ + { 30, 0x00C0 }, /* R30 - DAC Digital Volume Left */ + { 31, 0x00C0 }, /* R31 - DAC Digital Volume Right */ + { 32, 0x0000 }, /* R32 - DAC Digital 0 */ + { 33, 0x0008 }, /* R33 - DAC Digital 1 */ + { 36, 0x00C0 }, /* R36 - ADC Digital Volume Left */ + { 37, 0x00C0 }, /* R37 - ADC Digital Volume Right */ + { 38, 0x0010 }, /* R38 - ADC Digital 0 */ + { 39, 0x0000 }, /* R39 - Digital Microphone 0 */ + { 40, 0x01AF }, /* R40 - DRC 0 */ + { 41, 0x3248 }, /* R41 - DRC 1 */ + { 42, 0x0000 }, /* R42 - DRC 2 */ + { 43, 0x0000 }, /* R43 - DRC 3 */ + { 44, 0x0085 }, /* R44 - Analogue Left Input 0 */ + { 45, 0x0085 }, /* R45 - Analogue Right Input 0 */ + { 46, 0x0044 }, /* R46 - Analogue Left Input 1 */ + { 47, 0x0044 }, /* R47 - Analogue Right Input 1 */ + { 57, 0x002D }, /* R57 - Analogue OUT1 Left */ + { 58, 0x002D }, /* R58 - Analogue OUT1 Right */ + { 59, 0x0039 }, /* R59 - Analogue OUT2 Left */ + { 60, 0x0039 }, /* R60 - Analogue OUT2 Right */ + { 61, 0x0000 }, /* R61 - Analogue OUT12 ZC */ + { 67, 0x0000 }, /* R67 - DC Servo 0 */ + { 69, 0xAAAA }, /* R69 - DC Servo 2 */ + { 71, 0xAAAA }, /* R71 - DC Servo 4 */ + { 72, 0xAAAA }, /* R72 - DC Servo 5 */ + { 90, 0x0000 }, /* R90 - Analogue HP 0 */ + { 94, 0x0000 }, /* R94 - Analogue Lineout 0 */ + { 98, 0x0000 }, /* R98 - Charge Pump 0 */ + { 104, 0x0004 }, /* R104 - Class W 0 */ + { 108, 0x0000 }, /* R108 - Write Sequencer 0 */ + { 109, 0x0000 }, /* R109 - Write Sequencer 1 */ + { 110, 0x0000 }, /* R110 - Write Sequencer 2 */ + { 111, 0x0000 }, /* R111 - Write Sequencer 3 */ + { 112, 0x0000 }, /* R112 - Write Sequencer 4 */ + { 116, 0x0000 }, /* R116 - FLL Control 1 */ + { 117, 0x0007 }, /* R117 - FLL Control 2 */ + { 118, 0x0000 }, /* R118 - FLL Control 3 */ + { 119, 0x2EE0 }, /* R119 - FLL Control 4 */ + { 120, 0x0004 }, /* R120 - FLL Control 5 */ + { 121, 0x0014 }, /* R121 - GPIO Control 1 */ + { 122, 0x0010 }, /* R122 - GPIO Control 2 */ + { 123, 0x0010 }, /* R123 - GPIO Control 3 */ + { 124, 0x0000 }, /* R124 - GPIO Control 4 */ + { 126, 0x0000 }, /* R126 - Digital Pulls */ + { 128, 0xFFFF }, /* R128 - Interrupt Status Mask */ + { 129, 0x0000 }, /* R129 - Interrupt Polarity */ + { 130, 0x0000 }, /* R130 - Interrupt Debounce */ + { 134, 0x0000 }, /* R134 - EQ1 */ + { 135, 0x000C }, /* R135 - EQ2 */ + { 136, 0x000C }, /* R136 - EQ3 */ + { 137, 0x000C }, /* R137 - EQ4 */ + { 138, 0x000C }, /* R138 - EQ5 */ + { 139, 0x000C }, /* R139 - EQ6 */ + { 140, 0x0FCA }, /* R140 - EQ7 */ + { 141, 0x0400 }, /* R141 - EQ8 */ + { 142, 0x00D8 }, /* R142 - EQ9 */ + { 143, 0x1EB5 }, /* R143 - EQ10 */ + { 144, 0xF145 }, /* R144 - EQ11 */ + { 145, 0x0B75 }, /* R145 - EQ12 */ + { 146, 0x01C5 }, /* R146 - EQ13 */ + { 147, 0x1C58 }, /* R147 - EQ14 */ + { 148, 0xF373 }, /* R148 - EQ15 */ + { 149, 0x0A54 }, /* R149 - EQ16 */ + { 150, 0x0558 }, /* R150 - EQ17 */ + { 151, 0x168E }, /* R151 - EQ18 */ + { 152, 0xF829 }, /* R152 - EQ19 */ + { 153, 0x07AD }, /* R153 - EQ20 */ + { 154, 0x1103 }, /* R154 - EQ21 */ + { 155, 0x0564 }, /* R155 - EQ22 */ + { 156, 0x0559 }, /* R156 - EQ23 */ + { 157, 0x4000 }, /* R157 - EQ24 */ + { 161, 0x0000 }, /* R161 - Control Interface Test 1 */ + { 204, 0x0000 }, /* R204 - Analogue Output Bias 0 */ + { 247, 0x0000 }, /* R247 - FLL NCO Test 0 */ + { 248, 0x0019 }, /* R248 - FLL NCO Test 1 */ +}; + +static bool wm8904_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8904_SW_RESET_AND_ID: + case WM8904_REVISION: + case WM8904_DC_SERVO_1: + case WM8904_DC_SERVO_6: + case WM8904_DC_SERVO_7: + case WM8904_DC_SERVO_8: + case WM8904_DC_SERVO_9: + case WM8904_DC_SERVO_READBACK_0: + case WM8904_INTERRUPT_STATUS: + return true; + default: + return false; + } +} + +static bool wm8904_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8904_SW_RESET_AND_ID: + case WM8904_REVISION: + case WM8904_BIAS_CONTROL_0: + case WM8904_VMID_CONTROL_0: + case WM8904_MIC_BIAS_CONTROL_0: + case WM8904_MIC_BIAS_CONTROL_1: + case WM8904_ANALOGUE_DAC_0: + case WM8904_MIC_FILTER_CONTROL: + case WM8904_ANALOGUE_ADC_0: + case WM8904_POWER_MANAGEMENT_0: + case WM8904_POWER_MANAGEMENT_2: + case WM8904_POWER_MANAGEMENT_3: + case WM8904_POWER_MANAGEMENT_6: + case WM8904_CLOCK_RATES_0: + case WM8904_CLOCK_RATES_1: + case WM8904_CLOCK_RATES_2: + case WM8904_AUDIO_INTERFACE_0: + case WM8904_AUDIO_INTERFACE_1: + case WM8904_AUDIO_INTERFACE_2: + case WM8904_AUDIO_INTERFACE_3: + case WM8904_DAC_DIGITAL_VOLUME_LEFT: + case WM8904_DAC_DIGITAL_VOLUME_RIGHT: + case WM8904_DAC_DIGITAL_0: + case WM8904_DAC_DIGITAL_1: + case WM8904_ADC_DIGITAL_VOLUME_LEFT: + case WM8904_ADC_DIGITAL_VOLUME_RIGHT: + case WM8904_ADC_DIGITAL_0: + case WM8904_DIGITAL_MICROPHONE_0: + case WM8904_DRC_0: + case WM8904_DRC_1: + case WM8904_DRC_2: + case WM8904_DRC_3: + case WM8904_ANALOGUE_LEFT_INPUT_0: + case WM8904_ANALOGUE_RIGHT_INPUT_0: + case WM8904_ANALOGUE_LEFT_INPUT_1: + case WM8904_ANALOGUE_RIGHT_INPUT_1: + case WM8904_ANALOGUE_OUT1_LEFT: + case WM8904_ANALOGUE_OUT1_RIGHT: + case WM8904_ANALOGUE_OUT2_LEFT: + case WM8904_ANALOGUE_OUT2_RIGHT: + case WM8904_ANALOGUE_OUT12_ZC: + case WM8904_DC_SERVO_0: + case WM8904_DC_SERVO_1: + case WM8904_DC_SERVO_2: + case WM8904_DC_SERVO_4: + case WM8904_DC_SERVO_5: + case WM8904_DC_SERVO_6: + case WM8904_DC_SERVO_7: + case WM8904_DC_SERVO_8: + case WM8904_DC_SERVO_9: + case WM8904_DC_SERVO_READBACK_0: + case WM8904_ANALOGUE_HP_0: + case WM8904_ANALOGUE_LINEOUT_0: + case WM8904_CHARGE_PUMP_0: + case WM8904_CLASS_W_0: + case WM8904_WRITE_SEQUENCER_0: + case WM8904_WRITE_SEQUENCER_1: + case WM8904_WRITE_SEQUENCER_2: + case WM8904_WRITE_SEQUENCER_3: + case WM8904_WRITE_SEQUENCER_4: + case WM8904_FLL_CONTROL_1: + case WM8904_FLL_CONTROL_2: + case WM8904_FLL_CONTROL_3: + case WM8904_FLL_CONTROL_4: + case WM8904_FLL_CONTROL_5: + case WM8904_GPIO_CONTROL_1: + case WM8904_GPIO_CONTROL_2: + case WM8904_GPIO_CONTROL_3: + case WM8904_GPIO_CONTROL_4: + case WM8904_DIGITAL_PULLS: + case WM8904_INTERRUPT_STATUS: + case WM8904_INTERRUPT_STATUS_MASK: + case WM8904_INTERRUPT_POLARITY: + case WM8904_INTERRUPT_DEBOUNCE: + case WM8904_EQ1: + case WM8904_EQ2: + case WM8904_EQ3: + case WM8904_EQ4: + case WM8904_EQ5: + case WM8904_EQ6: + case WM8904_EQ7: + case WM8904_EQ8: + case WM8904_EQ9: + case WM8904_EQ10: + case WM8904_EQ11: + case WM8904_EQ12: + case WM8904_EQ13: + case WM8904_EQ14: + case WM8904_EQ15: + case WM8904_EQ16: + case WM8904_EQ17: + case WM8904_EQ18: + case WM8904_EQ19: + case WM8904_EQ20: + case WM8904_EQ21: + case WM8904_EQ22: + case WM8904_EQ23: + case WM8904_EQ24: + case WM8904_CONTROL_INTERFACE_TEST_1: + case WM8904_ADC_TEST_0: + case WM8904_ANALOGUE_OUTPUT_BIAS_0: + case WM8904_FLL_NCO_TEST_0: + case WM8904_FLL_NCO_TEST_1: + return true; + default: + return false; + } +} + +static int wm8904_configure_clocking(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + unsigned int clock0, clock2, rate; + + /* Gate the clock while we're updating to avoid misclocking */ + clock2 = snd_soc_component_read32(component, WM8904_CLOCK_RATES_2); + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, + WM8904_SYSCLK_SRC, 0); + + /* This should be done on init() for bypass paths */ + switch (wm8904->sysclk_src) { + case WM8904_CLK_MCLK: + dev_dbg(component->dev, "Using %dHz MCLK\n", wm8904->mclk_rate); + + clock2 &= ~WM8904_SYSCLK_SRC; + rate = wm8904->mclk_rate; + + /* Ensure the FLL is stopped */ + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); + break; + + case WM8904_CLK_FLL: + dev_dbg(component->dev, "Using %dHz FLL clock\n", + wm8904->fll_fout); + + clock2 |= WM8904_SYSCLK_SRC; + rate = wm8904->fll_fout; + break; + + default: + dev_err(component->dev, "System clock not configured\n"); + return -EINVAL; + } + + /* SYSCLK shouldn't be over 13.5MHz */ + if (rate > 13500000) { + clock0 = WM8904_MCLK_DIV; + wm8904->sysclk_rate = rate / 2; + } else { + clock0 = 0; + wm8904->sysclk_rate = rate; + } + + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_0, WM8904_MCLK_DIV, + clock0); + + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, + WM8904_CLK_SYS_ENA | WM8904_SYSCLK_SRC, clock2); + + dev_dbg(component->dev, "CLK_SYS is %dHz\n", wm8904->sysclk_rate); + + return 0; +} + +static void wm8904_set_drc(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct wm8904_pdata *pdata = wm8904->pdata; + int save, i; + + /* Save any enables; the configuration should clear them. */ + save = snd_soc_component_read32(component, WM8904_DRC_0); + + for (i = 0; i < WM8904_DRC_REGS; i++) + snd_soc_component_update_bits(component, WM8904_DRC_0 + i, 0xffff, + pdata->drc_cfgs[wm8904->drc_cfg].regs[i]); + + /* Reenable the DRC */ + snd_soc_component_update_bits(component, WM8904_DRC_0, + WM8904_DRC_ENA | WM8904_DRC_DAC_PATH, save); +} + +static int wm8904_put_drc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct wm8904_pdata *pdata = wm8904->pdata; + int value = ucontrol->value.enumerated.item[0]; + + if (value >= pdata->num_drc_cfgs) + return -EINVAL; + + wm8904->drc_cfg = value; + + wm8904_set_drc(component); + + return 0; +} + +static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wm8904->drc_cfg; + + return 0; +} + +static void wm8904_set_retune_mobile(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct wm8904_pdata *pdata = wm8904->pdata; + int best, best_val, save, i, cfg; + + if (!pdata || !wm8904->num_retune_mobile_texts) + return; + + /* Find the version of the currently selected configuration + * with the nearest sample rate. */ + cfg = wm8904->retune_mobile_cfg; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8904->retune_mobile_texts[cfg]) == 0 && + abs(pdata->retune_mobile_cfgs[i].rate + - wm8904->fs) < best_val) { + best = i; + best_val = abs(pdata->retune_mobile_cfgs[i].rate + - wm8904->fs); + } + } + + dev_dbg(component->dev, "ReTune Mobile %s/%dHz for %dHz sample rate\n", + pdata->retune_mobile_cfgs[best].name, + pdata->retune_mobile_cfgs[best].rate, + wm8904->fs); + + /* The EQ will be disabled while reconfiguring it, remember the + * current configuration. + */ + save = snd_soc_component_read32(component, WM8904_EQ1); + + for (i = 0; i < WM8904_EQ_REGS; i++) + snd_soc_component_update_bits(component, WM8904_EQ1 + i, 0xffff, + pdata->retune_mobile_cfgs[best].regs[i]); + + snd_soc_component_update_bits(component, WM8904_EQ1, WM8904_EQ_ENA, save); +} + +static int wm8904_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct wm8904_pdata *pdata = wm8904->pdata; + int value = ucontrol->value.enumerated.item[0]; + + if (value >= pdata->num_retune_mobile_cfgs) + return -EINVAL; + + wm8904->retune_mobile_cfg = value; + + wm8904_set_retune_mobile(component); + + return 0; +} + +static int wm8904_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wm8904->retune_mobile_cfg; + + return 0; +} + +static int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8904_set_deemph(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8904->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8904->fs) < + abs(deemph_settings[best] - wm8904->fs)) + best = i; + } + + val = best << WM8904_DEEMPH_SHIFT; + } else { + val = 0; + } + + dev_dbg(component->dev, "Set deemphasis %d\n", val); + + return snd_soc_component_update_bits(component, WM8904_DAC_DIGITAL_1, + WM8904_DEEMPH_MASK, val); +} + +static int wm8904_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8904->deemph; + return 0; +} + +static int wm8904_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + wm8904->deemph = deemph; + + return wm8904_set_deemph(component); +} + +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); + +static const char *input_mode_text[] = { + "Single-Ended", "Differential Line", "Differential Mic" +}; + +static SOC_ENUM_SINGLE_DECL(lin_mode, + WM8904_ANALOGUE_LEFT_INPUT_1, 0, + input_mode_text); + +static SOC_ENUM_SINGLE_DECL(rin_mode, + WM8904_ANALOGUE_RIGHT_INPUT_1, 0, + input_mode_text); + +static const char *hpf_mode_text[] = { + "Hi-fi", "Voice 1", "Voice 2", "Voice 3" +}; + +static SOC_ENUM_SINGLE_DECL(hpf_mode, WM8904_ADC_DIGITAL_0, 5, + hpf_mode_text); + +static int wm8904_adc_osr_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int val; + int ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + if (ucontrol->value.integer.value[0]) + val = 0; + else + val = WM8904_ADC_128_OSR_TST_MODE | WM8904_ADC_BIASX1P5; + + snd_soc_component_update_bits(component, WM8904_ADC_TEST_0, + WM8904_ADC_128_OSR_TST_MODE | WM8904_ADC_BIASX1P5, + val); + + return ret; +} + +static const struct snd_kcontrol_new wm8904_adc_snd_controls[] = { +SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8904_ADC_DIGITAL_VOLUME_LEFT, + WM8904_ADC_DIGITAL_VOLUME_RIGHT, 1, 119, 0, digital_tlv), + +SOC_ENUM("Left Capture Mode", lin_mode), +SOC_ENUM("Right Capture Mode", rin_mode), + +/* No TLV since it depends on mode */ +SOC_DOUBLE_R("Capture Volume", WM8904_ANALOGUE_LEFT_INPUT_0, + WM8904_ANALOGUE_RIGHT_INPUT_0, 0, 31, 0), +SOC_DOUBLE_R("Capture Switch", WM8904_ANALOGUE_LEFT_INPUT_0, + WM8904_ANALOGUE_RIGHT_INPUT_0, 7, 1, 1), + +SOC_SINGLE("High Pass Filter Switch", WM8904_ADC_DIGITAL_0, 4, 1, 0), +SOC_ENUM("High Pass Filter Mode", hpf_mode), +SOC_SINGLE_EXT("ADC 128x OSR Switch", WM8904_ANALOGUE_ADC_0, 0, 1, 0, + snd_soc_get_volsw, wm8904_adc_osr_put), +}; + +static const char *drc_path_text[] = { + "ADC", "DAC" +}; + +static SOC_ENUM_SINGLE_DECL(drc_path, WM8904_DRC_0, 14, drc_path_text); + +static const struct snd_kcontrol_new wm8904_dac_snd_controls[] = { +SOC_SINGLE_TLV("Digital Playback Boost Volume", + WM8904_AUDIO_INTERFACE_0, 9, 3, 0, dac_boost_tlv), +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8904_DAC_DIGITAL_VOLUME_LEFT, + WM8904_DAC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv), + +SOC_DOUBLE_R_TLV("Headphone Volume", WM8904_ANALOGUE_OUT1_LEFT, + WM8904_ANALOGUE_OUT1_RIGHT, 0, 63, 0, out_tlv), +SOC_DOUBLE_R("Headphone Switch", WM8904_ANALOGUE_OUT1_LEFT, + WM8904_ANALOGUE_OUT1_RIGHT, 8, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", WM8904_ANALOGUE_OUT1_LEFT, + WM8904_ANALOGUE_OUT1_RIGHT, 6, 1, 0), + +SOC_DOUBLE_R_TLV("Line Output Volume", WM8904_ANALOGUE_OUT2_LEFT, + WM8904_ANALOGUE_OUT2_RIGHT, 0, 63, 0, out_tlv), +SOC_DOUBLE_R("Line Output Switch", WM8904_ANALOGUE_OUT2_LEFT, + WM8904_ANALOGUE_OUT2_RIGHT, 8, 1, 1), +SOC_DOUBLE_R("Line Output ZC Switch", WM8904_ANALOGUE_OUT2_LEFT, + WM8904_ANALOGUE_OUT2_RIGHT, 6, 1, 0), + +SOC_SINGLE("EQ Switch", WM8904_EQ1, 0, 1, 0), +SOC_SINGLE("DRC Switch", WM8904_DRC_0, 15, 1, 0), +SOC_ENUM("DRC Path", drc_path), +SOC_SINGLE("DAC OSRx2 Switch", WM8904_DAC_DIGITAL_1, 6, 1, 0), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8904_get_deemph, wm8904_put_deemph), +}; + +static const struct snd_kcontrol_new wm8904_snd_controls[] = { +SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8904_DAC_DIGITAL_0, 4, 8, 15, 0, + sidetone_tlv), +}; + +static const struct snd_kcontrol_new wm8904_eq_controls[] = { +SOC_SINGLE_TLV("EQ1 Volume", WM8904_EQ2, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Volume", WM8904_EQ3, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Volume", WM8904_EQ4, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Volume", WM8904_EQ5, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ5 Volume", WM8904_EQ6, 0, 24, 0, eq_tlv), +}; + +static int cp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (WARN_ON(event != SND_SOC_DAPM_POST_PMU)) + return -EINVAL; + + /* Maximum startup time */ + udelay(500); + + return 0; +} + +static int sysclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* If we're using the FLL then we only start it when + * required; we assume that the configuration has been + * done previously and all we need to do is kick it + * off. + */ + switch (wm8904->sysclk_src) { + case WM8904_CLK_FLL: + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_OSC_ENA, + WM8904_FLL_OSC_ENA); + + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_ENA, + WM8904_FLL_ENA); + break; + + default: + break; + } + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); + break; + } + + return 0; +} + +static int out_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + int reg, val; + int dcs_mask; + int dcs_l, dcs_r; + int dcs_l_reg, dcs_r_reg; + int timeout; + int pwr_reg; + + /* This code is shared between HP and LINEOUT; we do all our + * power management in stereo pairs to avoid latency issues so + * we reuse shift to identify which rather than strcmp() the + * name. */ + reg = w->shift; + + switch (reg) { + case WM8904_ANALOGUE_HP_0: + pwr_reg = WM8904_POWER_MANAGEMENT_2; + dcs_mask = WM8904_DCS_ENA_CHAN_0 | WM8904_DCS_ENA_CHAN_1; + dcs_r_reg = WM8904_DC_SERVO_8; + dcs_l_reg = WM8904_DC_SERVO_9; + dcs_l = 0; + dcs_r = 1; + break; + case WM8904_ANALOGUE_LINEOUT_0: + pwr_reg = WM8904_POWER_MANAGEMENT_3; + dcs_mask = WM8904_DCS_ENA_CHAN_2 | WM8904_DCS_ENA_CHAN_3; + dcs_r_reg = WM8904_DC_SERVO_6; + dcs_l_reg = WM8904_DC_SERVO_7; + dcs_l = 2; + dcs_r = 3; + break; + default: + WARN(1, "Invalid reg %d\n", reg); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Power on the PGAs */ + snd_soc_component_update_bits(component, pwr_reg, + WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA, + WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA); + + /* Power on the amplifier */ + snd_soc_component_update_bits(component, reg, + WM8904_HPL_ENA | WM8904_HPR_ENA, + WM8904_HPL_ENA | WM8904_HPR_ENA); + + + /* Enable the first stage */ + snd_soc_component_update_bits(component, reg, + WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY, + WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY); + + /* Power up the DC servo */ + snd_soc_component_update_bits(component, WM8904_DC_SERVO_0, + dcs_mask, dcs_mask); + + /* Either calibrate the DC servo or restore cached state + * if we have that. + */ + if (wm8904->dcs_state[dcs_l] || wm8904->dcs_state[dcs_r]) { + dev_dbg(component->dev, "Restoring DC servo state\n"); + + snd_soc_component_write(component, dcs_l_reg, + wm8904->dcs_state[dcs_l]); + snd_soc_component_write(component, dcs_r_reg, + wm8904->dcs_state[dcs_r]); + + snd_soc_component_write(component, WM8904_DC_SERVO_1, dcs_mask); + + timeout = 20; + } else { + dev_dbg(component->dev, "Calibrating DC servo\n"); + + snd_soc_component_write(component, WM8904_DC_SERVO_1, + dcs_mask << WM8904_DCS_TRIG_STARTUP_0_SHIFT); + + timeout = 500; + } + + /* Wait for DC servo to complete */ + dcs_mask <<= WM8904_DCS_CAL_COMPLETE_SHIFT; + do { + val = snd_soc_component_read32(component, WM8904_DC_SERVO_READBACK_0); + if ((val & dcs_mask) == dcs_mask) + break; + + msleep(1); + } while (--timeout); + + if ((val & dcs_mask) != dcs_mask) + dev_warn(component->dev, "DC servo timed out\n"); + else + dev_dbg(component->dev, "DC servo ready\n"); + + /* Enable the output stage */ + snd_soc_component_update_bits(component, reg, + WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP, + WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP); + break; + + case SND_SOC_DAPM_POST_PMU: + /* Unshort the output itself */ + snd_soc_component_update_bits(component, reg, + WM8904_HPL_RMV_SHORT | + WM8904_HPR_RMV_SHORT, + WM8904_HPL_RMV_SHORT | + WM8904_HPR_RMV_SHORT); + + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Short the output */ + snd_soc_component_update_bits(component, reg, + WM8904_HPL_RMV_SHORT | + WM8904_HPR_RMV_SHORT, 0); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Cache the DC servo configuration; this will be + * invalidated if we change the configuration. */ + wm8904->dcs_state[dcs_l] = snd_soc_component_read32(component, dcs_l_reg); + wm8904->dcs_state[dcs_r] = snd_soc_component_read32(component, dcs_r_reg); + + snd_soc_component_update_bits(component, WM8904_DC_SERVO_0, + dcs_mask, 0); + + /* Disable the amplifier input and output stages */ + snd_soc_component_update_bits(component, reg, + WM8904_HPL_ENA | WM8904_HPR_ENA | + WM8904_HPL_ENA_DLY | WM8904_HPR_ENA_DLY | + WM8904_HPL_ENA_OUTP | WM8904_HPR_ENA_OUTP, + 0); + + /* PGAs too */ + snd_soc_component_update_bits(component, pwr_reg, + WM8904_HPL_PGA_ENA | WM8904_HPR_PGA_ENA, + 0); + break; + } + + return 0; +} + +static const char *lin_text[] = { + "IN1L", "IN2L", "IN3L" +}; + +static SOC_ENUM_SINGLE_DECL(lin_enum, WM8904_ANALOGUE_LEFT_INPUT_1, 2, + lin_text); + +static const struct snd_kcontrol_new lin_mux = + SOC_DAPM_ENUM("Left Capture Mux", lin_enum); + +static SOC_ENUM_SINGLE_DECL(lin_inv_enum, WM8904_ANALOGUE_LEFT_INPUT_1, 4, + lin_text); + +static const struct snd_kcontrol_new lin_inv_mux = + SOC_DAPM_ENUM("Left Capture Inveting Mux", lin_inv_enum); + +static const char *rin_text[] = { + "IN1R", "IN2R", "IN3R" +}; + +static SOC_ENUM_SINGLE_DECL(rin_enum, WM8904_ANALOGUE_RIGHT_INPUT_1, 2, + rin_text); + +static const struct snd_kcontrol_new rin_mux = + SOC_DAPM_ENUM("Right Capture Mux", rin_enum); + +static SOC_ENUM_SINGLE_DECL(rin_inv_enum, WM8904_ANALOGUE_RIGHT_INPUT_1, 4, + rin_text); + +static const struct snd_kcontrol_new rin_inv_mux = + SOC_DAPM_ENUM("Right Capture Inveting Mux", rin_inv_enum); + +static const char *aif_text[] = { + "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(aifoutl_enum, WM8904_AUDIO_INTERFACE_0, 7, + aif_text); + +static const struct snd_kcontrol_new aifoutl_mux = + SOC_DAPM_ENUM("AIFOUTL Mux", aifoutl_enum); + +static SOC_ENUM_SINGLE_DECL(aifoutr_enum, WM8904_AUDIO_INTERFACE_0, 6, + aif_text); + +static const struct snd_kcontrol_new aifoutr_mux = + SOC_DAPM_ENUM("AIFOUTR Mux", aifoutr_enum); + +static SOC_ENUM_SINGLE_DECL(aifinl_enum, WM8904_AUDIO_INTERFACE_0, 5, + aif_text); + +static const struct snd_kcontrol_new aifinl_mux = + SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum); + +static SOC_ENUM_SINGLE_DECL(aifinr_enum, WM8904_AUDIO_INTERFACE_0, 4, + aif_text); + +static const struct snd_kcontrol_new aifinr_mux = + SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum); + +static const struct snd_soc_dapm_widget wm8904_core_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", WM8904_CLOCK_RATES_2, 2, 0, sysclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8904_CLOCK_RATES_2, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TOCLK", WM8904_CLOCK_RATES_2, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_widget wm8904_adc_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), + +SND_SOC_DAPM_SUPPLY("MICBIAS", WM8904_MIC_BIAS_CONTROL_0, 0, 0, NULL, 0), + +SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lin_mux), +SND_SOC_DAPM_MUX("Left Capture Inverting Mux", SND_SOC_NOPM, 0, 0, + &lin_inv_mux), +SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rin_mux), +SND_SOC_DAPM_MUX("Right Capture Inverting Mux", SND_SOC_NOPM, 0, 0, + &rin_inv_mux), + +SND_SOC_DAPM_PGA("Left Capture PGA", WM8904_POWER_MANAGEMENT_0, 1, 0, + NULL, 0), +SND_SOC_DAPM_PGA("Right Capture PGA", WM8904_POWER_MANAGEMENT_0, 0, 0, + NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", NULL, WM8904_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, WM8904_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_MUX("AIFOUTL Mux", SND_SOC_NOPM, 0, 0, &aifoutl_mux), +SND_SOC_DAPM_MUX("AIFOUTR Mux", SND_SOC_NOPM, 0, 0, &aifoutr_mux), + +SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_widget wm8904_dac_dapm_widgets[] = { +SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux), +SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux), + +SND_SOC_DAPM_DAC("DACL", NULL, WM8904_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", NULL, WM8904_POWER_MANAGEMENT_6, 2, 0), + +SND_SOC_DAPM_SUPPLY("Charge pump", WM8904_CHARGE_PUMP_0, 0, 0, cp_event, + SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_PGA("HPL PGA", SND_SOC_NOPM, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("HPR PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA("LINEL PGA", SND_SOC_NOPM, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINER PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, WM8904_ANALOGUE_HP_0, + 0, NULL, 0, out_pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA_E("Line Output", SND_SOC_NOPM, WM8904_ANALOGUE_LINEOUT_0, + 0, NULL, 0, out_pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LINEOUTL"), +SND_SOC_DAPM_OUTPUT("LINEOUTR"), +}; + +static const char *out_mux_text[] = { + "DAC", "Bypass" +}; + +static SOC_ENUM_SINGLE_DECL(hpl_enum, WM8904_ANALOGUE_OUT12_ZC, 3, + out_mux_text); + +static const struct snd_kcontrol_new hpl_mux = + SOC_DAPM_ENUM("HPL Mux", hpl_enum); + +static SOC_ENUM_SINGLE_DECL(hpr_enum, WM8904_ANALOGUE_OUT12_ZC, 2, + out_mux_text); + +static const struct snd_kcontrol_new hpr_mux = + SOC_DAPM_ENUM("HPR Mux", hpr_enum); + +static SOC_ENUM_SINGLE_DECL(linel_enum, WM8904_ANALOGUE_OUT12_ZC, 1, + out_mux_text); + +static const struct snd_kcontrol_new linel_mux = + SOC_DAPM_ENUM("LINEL Mux", linel_enum); + +static SOC_ENUM_SINGLE_DECL(liner_enum, WM8904_ANALOGUE_OUT12_ZC, 0, + out_mux_text); + +static const struct snd_kcontrol_new liner_mux = + SOC_DAPM_ENUM("LINER Mux", liner_enum); + +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(dacl_sidetone_enum, WM8904_DAC_DIGITAL_0, 2, + sidetone_text); + +static const struct snd_kcontrol_new dacl_sidetone_mux = + SOC_DAPM_ENUM("Left Sidetone Mux", dacl_sidetone_enum); + +static SOC_ENUM_SINGLE_DECL(dacr_sidetone_enum, WM8904_DAC_DIGITAL_0, 0, + sidetone_text); + +static const struct snd_kcontrol_new dacr_sidetone_mux = + SOC_DAPM_ENUM("Right Sidetone Mux", dacr_sidetone_enum); + +static const struct snd_soc_dapm_widget wm8904_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("Class G", WM8904_CLASS_W_0, 0, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Bypass", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Bypass", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &dacl_sidetone_mux), +SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &dacr_sidetone_mux), + +SND_SOC_DAPM_MUX("HPL Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), +SND_SOC_DAPM_MUX("HPR Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), +SND_SOC_DAPM_MUX("LINEL Mux", SND_SOC_NOPM, 0, 0, &linel_mux), +SND_SOC_DAPM_MUX("LINER Mux", SND_SOC_NOPM, 0, 0, &liner_mux), +}; + +static const struct snd_soc_dapm_route core_intercon[] = { + { "CLK_DSP", NULL, "SYSCLK" }, + { "TOCLK", NULL, "SYSCLK" }, +}; + +static const struct snd_soc_dapm_route adc_intercon[] = { + { "Left Capture Mux", "IN1L", "IN1L" }, + { "Left Capture Mux", "IN2L", "IN2L" }, + { "Left Capture Mux", "IN3L", "IN3L" }, + + { "Left Capture Inverting Mux", "IN1L", "IN1L" }, + { "Left Capture Inverting Mux", "IN2L", "IN2L" }, + { "Left Capture Inverting Mux", "IN3L", "IN3L" }, + + { "Right Capture Mux", "IN1R", "IN1R" }, + { "Right Capture Mux", "IN2R", "IN2R" }, + { "Right Capture Mux", "IN3R", "IN3R" }, + + { "Right Capture Inverting Mux", "IN1R", "IN1R" }, + { "Right Capture Inverting Mux", "IN2R", "IN2R" }, + { "Right Capture Inverting Mux", "IN3R", "IN3R" }, + + { "Left Capture PGA", NULL, "Left Capture Mux" }, + { "Left Capture PGA", NULL, "Left Capture Inverting Mux" }, + + { "Right Capture PGA", NULL, "Right Capture Mux" }, + { "Right Capture PGA", NULL, "Right Capture Inverting Mux" }, + + { "AIFOUTL Mux", "Left", "ADCL" }, + { "AIFOUTL Mux", "Right", "ADCR" }, + { "AIFOUTR Mux", "Left", "ADCL" }, + { "AIFOUTR Mux", "Right", "ADCR" }, + + { "AIFOUTL", NULL, "AIFOUTL Mux" }, + { "AIFOUTR", NULL, "AIFOUTR Mux" }, + + { "ADCL", NULL, "CLK_DSP" }, + { "ADCL", NULL, "Left Capture PGA" }, + + { "ADCR", NULL, "CLK_DSP" }, + { "ADCR", NULL, "Right Capture PGA" }, +}; + +static const struct snd_soc_dapm_route dac_intercon[] = { + { "DACL Mux", "Left", "AIFINL" }, + { "DACL Mux", "Right", "AIFINR" }, + + { "DACR Mux", "Left", "AIFINL" }, + { "DACR Mux", "Right", "AIFINR" }, + + { "DACL", NULL, "DACL Mux" }, + { "DACL", NULL, "CLK_DSP" }, + + { "DACR", NULL, "DACR Mux" }, + { "DACR", NULL, "CLK_DSP" }, + + { "Charge pump", NULL, "SYSCLK" }, + + { "Headphone Output", NULL, "HPL PGA" }, + { "Headphone Output", NULL, "HPR PGA" }, + { "Headphone Output", NULL, "Charge pump" }, + { "Headphone Output", NULL, "TOCLK" }, + + { "Line Output", NULL, "LINEL PGA" }, + { "Line Output", NULL, "LINER PGA" }, + { "Line Output", NULL, "Charge pump" }, + { "Line Output", NULL, "TOCLK" }, + + { "HPOUTL", NULL, "Headphone Output" }, + { "HPOUTR", NULL, "Headphone Output" }, + + { "LINEOUTL", NULL, "Line Output" }, + { "LINEOUTR", NULL, "Line Output" }, +}; + +static const struct snd_soc_dapm_route wm8904_intercon[] = { + { "Left Sidetone", "Left", "ADCL" }, + { "Left Sidetone", "Right", "ADCR" }, + { "DACL", NULL, "Left Sidetone" }, + + { "Right Sidetone", "Left", "ADCL" }, + { "Right Sidetone", "Right", "ADCR" }, + { "DACR", NULL, "Right Sidetone" }, + + { "Left Bypass", NULL, "Class G" }, + { "Left Bypass", NULL, "Left Capture PGA" }, + + { "Right Bypass", NULL, "Class G" }, + { "Right Bypass", NULL, "Right Capture PGA" }, + + { "HPL Mux", "DAC", "DACL" }, + { "HPL Mux", "Bypass", "Left Bypass" }, + + { "HPR Mux", "DAC", "DACR" }, + { "HPR Mux", "Bypass", "Right Bypass" }, + + { "LINEL Mux", "DAC", "DACL" }, + { "LINEL Mux", "Bypass", "Left Bypass" }, + + { "LINER Mux", "DAC", "DACR" }, + { "LINER Mux", "Bypass", "Right Bypass" }, + + { "HPL PGA", NULL, "HPL Mux" }, + { "HPR PGA", NULL, "HPR Mux" }, + + { "LINEL PGA", NULL, "LINEL Mux" }, + { "LINER PGA", NULL, "LINER Mux" }, +}; + +static const struct snd_soc_dapm_route wm8912_intercon[] = { + { "HPL PGA", NULL, "DACL" }, + { "HPR PGA", NULL, "DACR" }, + + { "LINEL PGA", NULL, "DACL" }, + { "LINER PGA", NULL, "DACR" }, +}; + +static int wm8904_add_widgets(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + snd_soc_dapm_new_controls(dapm, wm8904_core_dapm_widgets, + ARRAY_SIZE(wm8904_core_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, core_intercon, + ARRAY_SIZE(core_intercon)); + + switch (wm8904->devtype) { + case WM8904: + snd_soc_add_component_controls(component, wm8904_adc_snd_controls, + ARRAY_SIZE(wm8904_adc_snd_controls)); + snd_soc_add_component_controls(component, wm8904_dac_snd_controls, + ARRAY_SIZE(wm8904_dac_snd_controls)); + snd_soc_add_component_controls(component, wm8904_snd_controls, + ARRAY_SIZE(wm8904_snd_controls)); + + snd_soc_dapm_new_controls(dapm, wm8904_adc_dapm_widgets, + ARRAY_SIZE(wm8904_adc_dapm_widgets)); + snd_soc_dapm_new_controls(dapm, wm8904_dac_dapm_widgets, + ARRAY_SIZE(wm8904_dac_dapm_widgets)); + snd_soc_dapm_new_controls(dapm, wm8904_dapm_widgets, + ARRAY_SIZE(wm8904_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, adc_intercon, + ARRAY_SIZE(adc_intercon)); + snd_soc_dapm_add_routes(dapm, dac_intercon, + ARRAY_SIZE(dac_intercon)); + snd_soc_dapm_add_routes(dapm, wm8904_intercon, + ARRAY_SIZE(wm8904_intercon)); + break; + + case WM8912: + snd_soc_add_component_controls(component, wm8904_dac_snd_controls, + ARRAY_SIZE(wm8904_dac_snd_controls)); + + snd_soc_dapm_new_controls(dapm, wm8904_dac_dapm_widgets, + ARRAY_SIZE(wm8904_dac_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, dac_intercon, + ARRAY_SIZE(dac_intercon)); + snd_soc_dapm_add_routes(dapm, wm8912_intercon, + ARRAY_SIZE(wm8912_intercon)); + break; + } + + return 0; +} + +static struct { + int ratio; + unsigned int clk_sys_rate; +} clk_sys_rates[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 786, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +static struct { + int rate; + int sample_rate; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 1 }, + { 16000, 2 }, + { 22050, 3 }, + { 24000, 3 }, + { 32000, 4 }, + { 44100, 5 }, + { 48000, 5 }, +}; + +static struct { + int div; /* *10 due to .5s */ + int bclk_div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 55, 6 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 110, 10 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 16 }, + { 200, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + + +static int wm8904_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + int ret, i, best, best_val, cur_val; + unsigned int aif1 = 0; + unsigned int aif2 = 0; + unsigned int aif3 = 0; + unsigned int clock1 = 0; + unsigned int dac_digital1 = 0; + + /* What BCLK do we need? */ + wm8904->fs = params_rate(params); + if (wm8904->tdm_slots) { + dev_dbg(component->dev, "Configuring for %d %d bit TDM slots\n", + wm8904->tdm_slots, wm8904->tdm_width); + wm8904->bclk = snd_soc_calc_bclk(wm8904->fs, + wm8904->tdm_width, 2, + wm8904->tdm_slots); + } else { + wm8904->bclk = snd_soc_params_to_bclk(params); + } + + switch (params_width(params)) { + case 16: + break; + case 20: + aif1 |= 0x40; + break; + case 24: + aif1 |= 0x80; + break; + case 32: + aif1 |= 0xc0; + break; + default: + return -EINVAL; + } + + + dev_dbg(component->dev, "Target BCLK is %dHz\n", wm8904->bclk); + + ret = wm8904_configure_clocking(component); + if (ret != 0) + return ret; + + /* Select nearest CLK_SYS_RATE */ + best = 0; + best_val = abs((wm8904->sysclk_rate / clk_sys_rates[0].ratio) + - wm8904->fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) { + cur_val = abs((wm8904->sysclk_rate / + clk_sys_rates[i].ratio) - wm8904->fs); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(component->dev, "Selected CLK_SYS_RATIO of %d\n", + clk_sys_rates[best].ratio); + clock1 |= (clk_sys_rates[best].clk_sys_rate + << WM8904_CLK_SYS_RATE_SHIFT); + + /* SAMPLE_RATE */ + best = 0; + best_val = abs(wm8904->fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(wm8904->fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(component->dev, "Selected SAMPLE_RATE of %dHz\n", + sample_rates[best].rate); + clock1 |= (sample_rates[best].sample_rate + << WM8904_SAMPLE_RATE_SHIFT); + + /* Enable sloping stopband filter for low sample rates */ + if (wm8904->fs <= 24000) + dac_digital1 |= WM8904_DAC_SB_FILT; + + /* BCLK_DIV */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = ((wm8904->sysclk_rate * 10) / bclk_divs[i].div) + - wm8904->bclk; + if (cur_val < 0) /* Table is sorted */ + break; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + wm8904->bclk = (wm8904->sysclk_rate * 10) / bclk_divs[best].div; + dev_dbg(component->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n", + bclk_divs[best].div, wm8904->bclk); + aif2 |= bclk_divs[best].bclk_div; + + /* LRCLK is a simple fraction of BCLK */ + dev_dbg(component->dev, "LRCLK_RATE is %d\n", wm8904->bclk / wm8904->fs); + aif3 |= wm8904->bclk / wm8904->fs; + + /* Apply the settings */ + snd_soc_component_update_bits(component, WM8904_DAC_DIGITAL_1, + WM8904_DAC_SB_FILT, dac_digital1); + snd_soc_component_update_bits(component, WM8904_AUDIO_INTERFACE_1, + WM8904_AIF_WL_MASK, aif1); + snd_soc_component_update_bits(component, WM8904_AUDIO_INTERFACE_2, + WM8904_BCLK_DIV_MASK, aif2); + snd_soc_component_update_bits(component, WM8904_AUDIO_INTERFACE_3, + WM8904_LRCLK_RATE_MASK, aif3); + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_1, + WM8904_SAMPLE_RATE_MASK | + WM8904_CLK_SYS_RATE_MASK, clock1); + + /* Update filters for the new settings */ + wm8904_set_retune_mobile(component); + wm8904_set_deemph(component); + + return 0; +} + + +static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8904_priv *priv = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8904_CLK_MCLK: + priv->sysclk_src = clk_id; + priv->mclk_rate = freq; + break; + + case WM8904_CLK_FLL: + priv->sysclk_src = clk_id; + break; + + default: + return -EINVAL; + } + + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + + wm8904_configure_clocking(component); + + return 0; +} + +static int wm8904_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + unsigned int aif1 = 0; + unsigned int aif3 = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif3 |= WM8904_LRCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif1 |= WM8904_BCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 |= WM8904_BCLK_DIR; + aif3 |= WM8904_LRCLK_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif1 |= 0x3 | WM8904_AIF_LRCLK_INV; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x3; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8904_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8904_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8904_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8904_AUDIO_INTERFACE_1, + WM8904_AIF_BCLK_INV | WM8904_AIF_LRCLK_INV | + WM8904_AIF_FMT_MASK | WM8904_BCLK_DIR, aif1); + snd_soc_component_update_bits(component, WM8904_AUDIO_INTERFACE_3, + WM8904_LRCLK_DIR, aif3); + + return 0; +} + + +static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + int aif1 = 0; + + /* Don't need to validate anything if we're turning off TDM */ + if (slots == 0) + goto out; + + /* Note that we allow configurations we can't handle ourselves - + * for example, we can generate clocks for slots 2 and up even if + * we can't use those slots ourselves. + */ + aif1 |= WM8904_AIFADC_TDM | WM8904_AIFDAC_TDM; + + switch (rx_mask) { + case 3: + break; + case 0xc: + aif1 |= WM8904_AIFADC_TDM_CHAN; + break; + default: + return -EINVAL; + } + + + switch (tx_mask) { + case 3: + break; + case 0xc: + aif1 |= WM8904_AIFDAC_TDM_CHAN; + break; + default: + return -EINVAL; + } + +out: + wm8904->tdm_width = slot_width; + wm8904->tdm_slots = slots / 2; + + snd_soc_component_update_bits(component, WM8904_AUDIO_INTERFACE_1, + WM8904_AIFADC_TDM | WM8904_AIFADC_TDM_CHAN | + WM8904_AIFDAC_TDM | WM8904_AIFDAC_TDM_CHAN, aif1); + + return 0; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_clk_ref_div; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + fll_div->fll_clk_ref_div = 0; + while ((Fref / div) > 13500000) { + div *= 2; + fll_div->fll_clk_ref_div++; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 4; + while (Fout * div < 90000000) { + div++; + if (div > 64) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * div; + fll_div->fll_outdiv = div - 1; + + pr_debug("Fvco=%dHz\n", target); + + /* Find an appropriate FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + target /= fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + fll_div->n = Ndiv; + Nmod = target % Fref; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", + fll_div->n, fll_div->k, + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_clk_ref_div); + + return 0; +} + +static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + struct snd_soc_component *component = dai->component; + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct _fll_div fll_div; + int ret, val; + int clock2, fll1; + + /* Any change? */ + if (source == wm8904->fll_src && Fref == wm8904->fll_fref && + Fout == wm8904->fll_fout) + return 0; + + clock2 = snd_soc_component_read32(component, WM8904_CLOCK_RATES_2); + + if (Fout == 0) { + dev_dbg(component->dev, "FLL disabled\n"); + + wm8904->fll_fref = 0; + wm8904->fll_fout = 0; + + /* Gate SYSCLK to avoid glitches */ + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, + WM8904_CLK_SYS_ENA, 0); + + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); + + goto out; + } + + /* Validate the FLL ID */ + switch (source) { + case WM8904_FLL_MCLK: + case WM8904_FLL_LRCLK: + case WM8904_FLL_BCLK: + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + break; + + case WM8904_FLL_FREE_RUNNING: + dev_dbg(component->dev, "Using free running FLL\n"); + /* Force 12MHz and output/4 for now */ + Fout = 12000000; + Fref = 12000000; + + memset(&fll_div, 0, sizeof(fll_div)); + fll_div.fll_outdiv = 3; + break; + + default: + dev_err(component->dev, "Unknown FLL ID %d\n", fll_id); + return -EINVAL; + } + + /* Save current state then disable the FLL and SYSCLK to avoid + * misclocking */ + fll1 = snd_soc_component_read32(component, WM8904_FLL_CONTROL_1); + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, + WM8904_CLK_SYS_ENA, 0); + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); + + /* Unlock forced oscilator control to switch it on/off */ + snd_soc_component_update_bits(component, WM8904_CONTROL_INTERFACE_TEST_1, + WM8904_USER_KEY, WM8904_USER_KEY); + + if (fll_id == WM8904_FLL_FREE_RUNNING) { + val = WM8904_FLL_FRC_NCO; + } else { + val = 0; + } + + snd_soc_component_update_bits(component, WM8904_FLL_NCO_TEST_1, WM8904_FLL_FRC_NCO, + val); + snd_soc_component_update_bits(component, WM8904_CONTROL_INTERFACE_TEST_1, + WM8904_USER_KEY, 0); + + switch (fll_id) { + case WM8904_FLL_MCLK: + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, + WM8904_FLL_CLK_REF_SRC_MASK, 0); + break; + + case WM8904_FLL_LRCLK: + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, + WM8904_FLL_CLK_REF_SRC_MASK, 1); + break; + + case WM8904_FLL_BCLK: + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, + WM8904_FLL_CLK_REF_SRC_MASK, 2); + break; + } + + if (fll_div.k) + val = WM8904_FLL_FRACN_ENA; + else + val = 0; + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_FRACN_ENA, val); + + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_2, + WM8904_FLL_OUTDIV_MASK | WM8904_FLL_FRATIO_MASK, + (fll_div.fll_outdiv << WM8904_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio << WM8904_FLL_FRATIO_SHIFT)); + + snd_soc_component_write(component, WM8904_FLL_CONTROL_3, fll_div.k); + + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_4, WM8904_FLL_N_MASK, + fll_div.n << WM8904_FLL_N_SHIFT); + + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, + WM8904_FLL_CLK_REF_DIV_MASK, + fll_div.fll_clk_ref_div + << WM8904_FLL_CLK_REF_DIV_SHIFT); + + dev_dbg(component->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout); + + wm8904->fll_fref = Fref; + wm8904->fll_fout = Fout; + wm8904->fll_src = source; + + /* Enable the FLL if it was previously active */ + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_OSC_ENA, fll1); + snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, + WM8904_FLL_ENA, fll1); + +out: + /* Reenable SYSCLK if it was previously active */ + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, + WM8904_CLK_SYS_ENA, clock2); + + return 0; +} + +static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + int val; + + if (mute) + val = WM8904_DAC_MUTE; + else + val = 0; + + snd_soc_component_update_bits(component, WM8904_DAC_DIGITAL_1, WM8904_DAC_MUTE, val); + + return 0; +} + +static int wm8904_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + ret = clk_prepare_enable(wm8904->mclk); + if (ret) + return ret; + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID resistance 2*50k */ + snd_soc_component_update_bits(component, WM8904_VMID_CONTROL_0, + WM8904_VMID_RES_MASK, + 0x1 << WM8904_VMID_RES_SHIFT); + + /* Normal bias current */ + snd_soc_component_update_bits(component, WM8904_BIAS_CONTROL_0, + WM8904_ISEL_MASK, 2 << WM8904_ISEL_SHIFT); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies), + wm8904->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + regcache_cache_only(wm8904->regmap, false); + regcache_sync(wm8904->regmap); + + /* Enable bias */ + snd_soc_component_update_bits(component, WM8904_BIAS_CONTROL_0, + WM8904_BIAS_ENA, WM8904_BIAS_ENA); + + /* Enable VMID, VMID buffering, 2*5k resistance */ + snd_soc_component_update_bits(component, WM8904_VMID_CONTROL_0, + WM8904_VMID_ENA | + WM8904_VMID_RES_MASK, + WM8904_VMID_ENA | + 0x3 << WM8904_VMID_RES_SHIFT); + + /* Let VMID ramp */ + msleep(1); + } + + /* Maintain VMID with 2*250k */ + snd_soc_component_update_bits(component, WM8904_VMID_CONTROL_0, + WM8904_VMID_RES_MASK, + 0x2 << WM8904_VMID_RES_SHIFT); + + /* Bias current *0.5 */ + snd_soc_component_update_bits(component, WM8904_BIAS_CONTROL_0, + WM8904_ISEL_MASK, 0); + break; + + case SND_SOC_BIAS_OFF: + /* Turn off VMID */ + snd_soc_component_update_bits(component, WM8904_VMID_CONTROL_0, + WM8904_VMID_RES_MASK | WM8904_VMID_ENA, 0); + + /* Stop bias generation */ + snd_soc_component_update_bits(component, WM8904_BIAS_CONTROL_0, + WM8904_BIAS_ENA, 0); + + snd_soc_component_write(component, WM8904_SW_RESET_AND_ID, 0); + regcache_cache_only(wm8904->regmap, true); + regcache_mark_dirty(wm8904->regmap); + + regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), + wm8904->supplies); + clk_disable_unprepare(wm8904->mclk); + break; + } + return 0; +} + +#define WM8904_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8904_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8904_dai_ops = { + .set_sysclk = wm8904_set_sysclk, + .set_fmt = wm8904_set_fmt, + .set_tdm_slot = wm8904_set_tdm_slot, + .set_pll = wm8904_set_fll, + .hw_params = wm8904_hw_params, + .digital_mute = wm8904_digital_mute, +}; + +static struct snd_soc_dai_driver wm8904_dai = { + .name = "wm8904-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8904_RATES, + .formats = WM8904_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8904_RATES, + .formats = WM8904_FORMATS, + }, + .ops = &wm8904_dai_ops, + .symmetric_rates = 1, +}; + +static void wm8904_handle_retune_mobile_pdata(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct wm8904_pdata *pdata = wm8904->pdata; + struct snd_kcontrol_new control = + SOC_ENUM_EXT("EQ Mode", + wm8904->retune_mobile_enum, + wm8904_get_retune_mobile_enum, + wm8904_put_retune_mobile_enum); + int ret, i, j; + const char **t; + + /* We need an array of texts for the enum API but the number + * of texts is likely to be less than the number of + * configurations due to the sample rate dependency of the + * configurations. */ + wm8904->num_retune_mobile_texts = 0; + wm8904->retune_mobile_texts = NULL; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + for (j = 0; j < wm8904->num_retune_mobile_texts; j++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8904->retune_mobile_texts[j]) == 0) + break; + } + + if (j != wm8904->num_retune_mobile_texts) + continue; + + /* Expand the array... */ + t = krealloc(wm8904->retune_mobile_texts, + sizeof(char *) * + (wm8904->num_retune_mobile_texts + 1), + GFP_KERNEL); + if (t == NULL) + continue; + + /* ...store the new entry... */ + t[wm8904->num_retune_mobile_texts] = + pdata->retune_mobile_cfgs[i].name; + + /* ...and remember the new version. */ + wm8904->num_retune_mobile_texts++; + wm8904->retune_mobile_texts = t; + } + + dev_dbg(component->dev, "Allocated %d unique ReTune Mobile names\n", + wm8904->num_retune_mobile_texts); + + wm8904->retune_mobile_enum.items = wm8904->num_retune_mobile_texts; + wm8904->retune_mobile_enum.texts = wm8904->retune_mobile_texts; + + ret = snd_soc_add_component_controls(component, &control, 1); + if (ret != 0) + dev_err(component->dev, + "Failed to add ReTune Mobile control: %d\n", ret); +} + +static void wm8904_handle_pdata(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + struct wm8904_pdata *pdata = wm8904->pdata; + int ret, i; + + if (!pdata) { + snd_soc_add_component_controls(component, wm8904_eq_controls, + ARRAY_SIZE(wm8904_eq_controls)); + return; + } + + dev_dbg(component->dev, "%d DRC configurations\n", pdata->num_drc_cfgs); + + if (pdata->num_drc_cfgs) { + struct snd_kcontrol_new control = + SOC_ENUM_EXT("DRC Mode", wm8904->drc_enum, + wm8904_get_drc_enum, wm8904_put_drc_enum); + + /* We need an array of texts for the enum API */ + wm8904->drc_texts = kmalloc_array(pdata->num_drc_cfgs, + sizeof(char *), + GFP_KERNEL); + if (!wm8904->drc_texts) + return; + + for (i = 0; i < pdata->num_drc_cfgs; i++) + wm8904->drc_texts[i] = pdata->drc_cfgs[i].name; + + wm8904->drc_enum.items = pdata->num_drc_cfgs; + wm8904->drc_enum.texts = wm8904->drc_texts; + + ret = snd_soc_add_component_controls(component, &control, 1); + if (ret != 0) + dev_err(component->dev, + "Failed to add DRC mode control: %d\n", ret); + + wm8904_set_drc(component); + } + + dev_dbg(component->dev, "%d ReTune Mobile configurations\n", + pdata->num_retune_mobile_cfgs); + + if (pdata->num_retune_mobile_cfgs) + wm8904_handle_retune_mobile_pdata(component); + else + snd_soc_add_component_controls(component, wm8904_eq_controls, + ARRAY_SIZE(wm8904_eq_controls)); +} + + +static int wm8904_probe(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + + switch (wm8904->devtype) { + case WM8904: + break; + case WM8912: + memset(&wm8904_dai.capture, 0, sizeof(wm8904_dai.capture)); + break; + default: + dev_err(component->dev, "Unknown device type %d\n", + wm8904->devtype); + return -EINVAL; + } + + wm8904_handle_pdata(component); + + wm8904_add_widgets(component); + + return 0; +} + +static void wm8904_remove(struct snd_soc_component *component) +{ + struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + + kfree(wm8904->retune_mobile_texts); + kfree(wm8904->drc_texts); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8904 = { + .probe = wm8904_probe, + .remove = wm8904_remove, + .set_bias_level = wm8904_set_bias_level, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8904_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM8904_MAX_REGISTER, + .volatile_reg = wm8904_volatile_register, + .readable_reg = wm8904_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8904_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8904_reg_defaults), +}; + +#ifdef CONFIG_OF +static enum wm8904_type wm8904_data = WM8904; +static enum wm8904_type wm8912_data = WM8912; + +static const struct of_device_id wm8904_of_match[] = { + { + .compatible = "wlf,wm8904", + .data = &wm8904_data, + }, { + .compatible = "wlf,wm8912", + .data = &wm8912_data, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, wm8904_of_match); +#endif + +static int wm8904_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8904_priv *wm8904; + unsigned int val; + int ret, i; + + wm8904 = devm_kzalloc(&i2c->dev, sizeof(struct wm8904_priv), + GFP_KERNEL); + if (wm8904 == NULL) + return -ENOMEM; + + wm8904->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(wm8904->mclk)) { + ret = PTR_ERR(wm8904->mclk); + dev_err(&i2c->dev, "Failed to get MCLK\n"); + return ret; + } + + wm8904->regmap = devm_regmap_init_i2c(i2c, &wm8904_regmap); + if (IS_ERR(wm8904->regmap)) { + ret = PTR_ERR(wm8904->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + if (i2c->dev.of_node) { + const struct of_device_id *match; + + match = of_match_node(wm8904_of_match, i2c->dev.of_node); + if (match == NULL) + return -EINVAL; + wm8904->devtype = *((enum wm8904_type *)match->data); + } else { + wm8904->devtype = id->driver_data; + } + + i2c_set_clientdata(i2c, wm8904); + wm8904->pdata = i2c->dev.platform_data; + + for (i = 0; i < ARRAY_SIZE(wm8904->supplies); i++) + wm8904->supplies[i].supply = wm8904_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8904->supplies), + wm8904->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8904->supplies), + wm8904->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_read(wm8904->regmap, WM8904_SW_RESET_AND_ID, &val); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read ID register: %d\n", ret); + goto err_enable; + } + if (val != 0x8904) { + dev_err(&i2c->dev, "Device is not a WM8904, ID is %x\n", val); + ret = -EINVAL; + goto err_enable; + } + + ret = regmap_read(wm8904->regmap, WM8904_REVISION, &val); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read device revision: %d\n", + ret); + goto err_enable; + } + dev_info(&i2c->dev, "revision %c\n", val + 'A'); + + ret = regmap_write(wm8904->regmap, WM8904_SW_RESET_AND_ID, 0); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret); + goto err_enable; + } + + /* Change some default settings - latch VU and enable ZC */ + regmap_update_bits(wm8904->regmap, WM8904_ADC_DIGITAL_VOLUME_LEFT, + WM8904_ADC_VU, WM8904_ADC_VU); + regmap_update_bits(wm8904->regmap, WM8904_ADC_DIGITAL_VOLUME_RIGHT, + WM8904_ADC_VU, WM8904_ADC_VU); + regmap_update_bits(wm8904->regmap, WM8904_DAC_DIGITAL_VOLUME_LEFT, + WM8904_DAC_VU, WM8904_DAC_VU); + regmap_update_bits(wm8904->regmap, WM8904_DAC_DIGITAL_VOLUME_RIGHT, + WM8904_DAC_VU, WM8904_DAC_VU); + regmap_update_bits(wm8904->regmap, WM8904_ANALOGUE_OUT1_LEFT, + WM8904_HPOUT_VU | WM8904_HPOUTLZC, + WM8904_HPOUT_VU | WM8904_HPOUTLZC); + regmap_update_bits(wm8904->regmap, WM8904_ANALOGUE_OUT1_RIGHT, + WM8904_HPOUT_VU | WM8904_HPOUTRZC, + WM8904_HPOUT_VU | WM8904_HPOUTRZC); + regmap_update_bits(wm8904->regmap, WM8904_ANALOGUE_OUT2_LEFT, + WM8904_LINEOUT_VU | WM8904_LINEOUTLZC, + WM8904_LINEOUT_VU | WM8904_LINEOUTLZC); + regmap_update_bits(wm8904->regmap, WM8904_ANALOGUE_OUT2_RIGHT, + WM8904_LINEOUT_VU | WM8904_LINEOUTRZC, + WM8904_LINEOUT_VU | WM8904_LINEOUTRZC); + regmap_update_bits(wm8904->regmap, WM8904_CLOCK_RATES_0, + WM8904_SR_MODE, 0); + + /* Apply configuration from the platform data. */ + if (wm8904->pdata) { + for (i = 0; i < WM8904_GPIO_REGS; i++) { + if (!wm8904->pdata->gpio_cfg[i]) + continue; + + regmap_update_bits(wm8904->regmap, + WM8904_GPIO_CONTROL_1 + i, + 0xffff, + wm8904->pdata->gpio_cfg[i]); + } + + /* Zero is the default value for these anyway */ + for (i = 0; i < WM8904_MIC_REGS; i++) + regmap_update_bits(wm8904->regmap, + WM8904_MIC_BIAS_CONTROL_0 + i, + 0xffff, + wm8904->pdata->mic_cfg[i]); + } + + /* Set Class W by default - this will be managed by the Class + * G widget at runtime where bypass paths are available. + */ + regmap_update_bits(wm8904->regmap, WM8904_CLASS_W_0, + WM8904_CP_DYN_PWR, WM8904_CP_DYN_PWR); + + /* Use normal bias source */ + regmap_update_bits(wm8904->regmap, WM8904_BIAS_CONTROL_0, + WM8904_POBCTRL, 0); + + /* Can leave the device powered off until we need it */ + regcache_cache_only(wm8904->regmap, true); + regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8904, &wm8904_dai, 1); + if (ret != 0) + return ret; + + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies); + return ret; +} + +static const struct i2c_device_id wm8904_i2c_id[] = { + { "wm8904", WM8904 }, + { "wm8912", WM8912 }, + { "wm8918", WM8904 }, /* Actually a subset, updates to follow */ + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id); + +static struct i2c_driver wm8904_i2c_driver = { + .driver = { + .name = "wm8904", + .of_match_table = of_match_ptr(wm8904_of_match), + }, + .probe = wm8904_i2c_probe, + .id_table = wm8904_i2c_id, +}; + +module_i2c_driver(wm8904_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8904 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h new file mode 100644 index 000000000..c29a0e813 --- /dev/null +++ b/sound/soc/codecs/wm8904.h @@ -0,0 +1,1592 @@ +/* + * wm8904.h -- WM8904 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics, plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8904_H +#define _WM8904_H + +#define WM8904_CLK_MCLK 1 +#define WM8904_CLK_FLL 2 + +#define WM8904_FLL_MCLK 1 +#define WM8904_FLL_BCLK 2 +#define WM8904_FLL_LRCLK 3 +#define WM8904_FLL_FREE_RUNNING 4 + +/* + * Register values. + */ +#define WM8904_SW_RESET_AND_ID 0x00 +#define WM8904_REVISION 0x01 +#define WM8904_BIAS_CONTROL_0 0x04 +#define WM8904_VMID_CONTROL_0 0x05 +#define WM8904_MIC_BIAS_CONTROL_0 0x06 +#define WM8904_MIC_BIAS_CONTROL_1 0x07 +#define WM8904_ANALOGUE_DAC_0 0x08 +#define WM8904_MIC_FILTER_CONTROL 0x09 +#define WM8904_ANALOGUE_ADC_0 0x0A +#define WM8904_POWER_MANAGEMENT_0 0x0C +#define WM8904_POWER_MANAGEMENT_2 0x0E +#define WM8904_POWER_MANAGEMENT_3 0x0F +#define WM8904_POWER_MANAGEMENT_6 0x12 +#define WM8904_CLOCK_RATES_0 0x14 +#define WM8904_CLOCK_RATES_1 0x15 +#define WM8904_CLOCK_RATES_2 0x16 +#define WM8904_AUDIO_INTERFACE_0 0x18 +#define WM8904_AUDIO_INTERFACE_1 0x19 +#define WM8904_AUDIO_INTERFACE_2 0x1A +#define WM8904_AUDIO_INTERFACE_3 0x1B +#define WM8904_DAC_DIGITAL_VOLUME_LEFT 0x1E +#define WM8904_DAC_DIGITAL_VOLUME_RIGHT 0x1F +#define WM8904_DAC_DIGITAL_0 0x20 +#define WM8904_DAC_DIGITAL_1 0x21 +#define WM8904_ADC_DIGITAL_VOLUME_LEFT 0x24 +#define WM8904_ADC_DIGITAL_VOLUME_RIGHT 0x25 +#define WM8904_ADC_DIGITAL_0 0x26 +#define WM8904_DIGITAL_MICROPHONE_0 0x27 +#define WM8904_DRC_0 0x28 +#define WM8904_DRC_1 0x29 +#define WM8904_DRC_2 0x2A +#define WM8904_DRC_3 0x2B +#define WM8904_ANALOGUE_LEFT_INPUT_0 0x2C +#define WM8904_ANALOGUE_RIGHT_INPUT_0 0x2D +#define WM8904_ANALOGUE_LEFT_INPUT_1 0x2E +#define WM8904_ANALOGUE_RIGHT_INPUT_1 0x2F +#define WM8904_ANALOGUE_OUT1_LEFT 0x39 +#define WM8904_ANALOGUE_OUT1_RIGHT 0x3A +#define WM8904_ANALOGUE_OUT2_LEFT 0x3B +#define WM8904_ANALOGUE_OUT2_RIGHT 0x3C +#define WM8904_ANALOGUE_OUT12_ZC 0x3D +#define WM8904_DC_SERVO_0 0x43 +#define WM8904_DC_SERVO_1 0x44 +#define WM8904_DC_SERVO_2 0x45 +#define WM8904_DC_SERVO_4 0x47 +#define WM8904_DC_SERVO_5 0x48 +#define WM8904_DC_SERVO_6 0x49 +#define WM8904_DC_SERVO_7 0x4A +#define WM8904_DC_SERVO_8 0x4B +#define WM8904_DC_SERVO_9 0x4C +#define WM8904_DC_SERVO_READBACK_0 0x4D +#define WM8904_ANALOGUE_HP_0 0x5A +#define WM8904_ANALOGUE_LINEOUT_0 0x5E +#define WM8904_CHARGE_PUMP_0 0x62 +#define WM8904_CLASS_W_0 0x68 +#define WM8904_WRITE_SEQUENCER_0 0x6C +#define WM8904_WRITE_SEQUENCER_1 0x6D +#define WM8904_WRITE_SEQUENCER_2 0x6E +#define WM8904_WRITE_SEQUENCER_3 0x6F +#define WM8904_WRITE_SEQUENCER_4 0x70 +#define WM8904_FLL_CONTROL_1 0x74 +#define WM8904_FLL_CONTROL_2 0x75 +#define WM8904_FLL_CONTROL_3 0x76 +#define WM8904_FLL_CONTROL_4 0x77 +#define WM8904_FLL_CONTROL_5 0x78 +#define WM8904_GPIO_CONTROL_1 0x79 +#define WM8904_GPIO_CONTROL_2 0x7A +#define WM8904_GPIO_CONTROL_3 0x7B +#define WM8904_GPIO_CONTROL_4 0x7C +#define WM8904_DIGITAL_PULLS 0x7E +#define WM8904_INTERRUPT_STATUS 0x7F +#define WM8904_INTERRUPT_STATUS_MASK 0x80 +#define WM8904_INTERRUPT_POLARITY 0x81 +#define WM8904_INTERRUPT_DEBOUNCE 0x82 +#define WM8904_EQ1 0x86 +#define WM8904_EQ2 0x87 +#define WM8904_EQ3 0x88 +#define WM8904_EQ4 0x89 +#define WM8904_EQ5 0x8A +#define WM8904_EQ6 0x8B +#define WM8904_EQ7 0x8C +#define WM8904_EQ8 0x8D +#define WM8904_EQ9 0x8E +#define WM8904_EQ10 0x8F +#define WM8904_EQ11 0x90 +#define WM8904_EQ12 0x91 +#define WM8904_EQ13 0x92 +#define WM8904_EQ14 0x93 +#define WM8904_EQ15 0x94 +#define WM8904_EQ16 0x95 +#define WM8904_EQ17 0x96 +#define WM8904_EQ18 0x97 +#define WM8904_EQ19 0x98 +#define WM8904_EQ20 0x99 +#define WM8904_EQ21 0x9A +#define WM8904_EQ22 0x9B +#define WM8904_EQ23 0x9C +#define WM8904_EQ24 0x9D +#define WM8904_CONTROL_INTERFACE_TEST_1 0xA1 +#define WM8904_ADC_TEST_0 0xC6 +#define WM8904_ANALOGUE_OUTPUT_BIAS_0 0xCC +#define WM8904_FLL_NCO_TEST_0 0xF7 +#define WM8904_FLL_NCO_TEST_1 0xF8 + +#define WM8904_REGISTER_COUNT 101 +#define WM8904_MAX_REGISTER 0xF8 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - SW Reset and ID + */ +#define WM8904_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8904_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8904_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R1 (0x01) - Revision + */ +#define WM8904_REVISION_MASK 0x000F /* REVISION - [3:0] */ +#define WM8904_REVISION_SHIFT 0 /* REVISION - [3:0] */ +#define WM8904_REVISION_WIDTH 16 /* REVISION - [3:0] */ + +/* + * R4 (0x04) - Bias Control 0 + */ +#define WM8904_POBCTRL 0x0010 /* POBCTRL */ +#define WM8904_POBCTRL_MASK 0x0010 /* POBCTRL */ +#define WM8904_POBCTRL_SHIFT 4 /* POBCTRL */ +#define WM8904_POBCTRL_WIDTH 1 /* POBCTRL */ +#define WM8904_ISEL_MASK 0x000C /* ISEL - [3:2] */ +#define WM8904_ISEL_SHIFT 2 /* ISEL - [3:2] */ +#define WM8904_ISEL_WIDTH 2 /* ISEL - [3:2] */ +#define WM8904_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8904_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8904_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */ +#define WM8904_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8904_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM8904_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM8904_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM8904_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R5 (0x05) - VMID Control 0 + */ +#define WM8904_VMID_BUF_ENA 0x0040 /* VMID_BUF_ENA */ +#define WM8904_VMID_BUF_ENA_MASK 0x0040 /* VMID_BUF_ENA */ +#define WM8904_VMID_BUF_ENA_SHIFT 6 /* VMID_BUF_ENA */ +#define WM8904_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM8904_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM8904_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM8904_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM8904_VMID_ENA 0x0001 /* VMID_ENA */ +#define WM8904_VMID_ENA_MASK 0x0001 /* VMID_ENA */ +#define WM8904_VMID_ENA_SHIFT 0 /* VMID_ENA */ +#define WM8904_VMID_ENA_WIDTH 1 /* VMID_ENA */ + +/* + * R8 (0x08) - Analogue DAC 0 + */ +#define WM8904_DAC_BIAS_SEL_MASK 0x0018 /* DAC_BIAS_SEL - [4:3] */ +#define WM8904_DAC_BIAS_SEL_SHIFT 3 /* DAC_BIAS_SEL - [4:3] */ +#define WM8904_DAC_BIAS_SEL_WIDTH 2 /* DAC_BIAS_SEL - [4:3] */ +#define WM8904_DAC_VMID_BIAS_SEL_MASK 0x0006 /* DAC_VMID_BIAS_SEL - [2:1] */ +#define WM8904_DAC_VMID_BIAS_SEL_SHIFT 1 /* DAC_VMID_BIAS_SEL - [2:1] */ +#define WM8904_DAC_VMID_BIAS_SEL_WIDTH 2 /* DAC_VMID_BIAS_SEL - [2:1] */ + +/* + * R9 (0x09) - mic Filter Control + */ +#define WM8904_MIC_DET_SET_THRESHOLD_MASK 0xF000 /* MIC_DET_SET_THRESHOLD - [15:12] */ +#define WM8904_MIC_DET_SET_THRESHOLD_SHIFT 12 /* MIC_DET_SET_THRESHOLD - [15:12] */ +#define WM8904_MIC_DET_SET_THRESHOLD_WIDTH 4 /* MIC_DET_SET_THRESHOLD - [15:12] */ +#define WM8904_MIC_DET_RESET_THRESHOLD_MASK 0x0F00 /* MIC_DET_RESET_THRESHOLD - [11:8] */ +#define WM8904_MIC_DET_RESET_THRESHOLD_SHIFT 8 /* MIC_DET_RESET_THRESHOLD - [11:8] */ +#define WM8904_MIC_DET_RESET_THRESHOLD_WIDTH 4 /* MIC_DET_RESET_THRESHOLD - [11:8] */ +#define WM8904_MIC_SHORT_SET_THRESHOLD_MASK 0x00F0 /* MIC_SHORT_SET_THRESHOLD - [7:4] */ +#define WM8904_MIC_SHORT_SET_THRESHOLD_SHIFT 4 /* MIC_SHORT_SET_THRESHOLD - [7:4] */ +#define WM8904_MIC_SHORT_SET_THRESHOLD_WIDTH 4 /* MIC_SHORT_SET_THRESHOLD - [7:4] */ +#define WM8904_MIC_SHORT_RESET_THRESHOLD_MASK 0x000F /* MIC_SHORT_RESET_THRESHOLD - [3:0] */ +#define WM8904_MIC_SHORT_RESET_THRESHOLD_SHIFT 0 /* MIC_SHORT_RESET_THRESHOLD - [3:0] */ +#define WM8904_MIC_SHORT_RESET_THRESHOLD_WIDTH 4 /* MIC_SHORT_RESET_THRESHOLD - [3:0] */ + +/* + * R10 (0x0A) - Analogue ADC 0 + */ +#define WM8904_ADC_OSR128 0x0001 /* ADC_OSR128 */ +#define WM8904_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */ +#define WM8904_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */ +#define WM8904_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ + +/* + * R12 (0x0C) - Power Management 0 + */ +#define WM8904_INL_ENA 0x0002 /* INL_ENA */ +#define WM8904_INL_ENA_MASK 0x0002 /* INL_ENA */ +#define WM8904_INL_ENA_SHIFT 1 /* INL_ENA */ +#define WM8904_INL_ENA_WIDTH 1 /* INL_ENA */ +#define WM8904_INR_ENA 0x0001 /* INR_ENA */ +#define WM8904_INR_ENA_MASK 0x0001 /* INR_ENA */ +#define WM8904_INR_ENA_SHIFT 0 /* INR_ENA */ +#define WM8904_INR_ENA_WIDTH 1 /* INR_ENA */ + +/* + * R14 (0x0E) - Power Management 2 + */ +#define WM8904_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */ +#define WM8904_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */ +#define WM8904_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */ +#define WM8904_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */ +#define WM8904_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */ +#define WM8904_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */ +#define WM8904_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */ +#define WM8904_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */ + +/* + * R15 (0x0F) - Power Management 3 + */ +#define WM8904_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8904_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8904_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */ +#define WM8904_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */ +#define WM8904_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8904_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8904_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */ +#define WM8904_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */ + +/* + * R18 (0x12) - Power Management 6 + */ +#define WM8904_DACL_ENA 0x0008 /* DACL_ENA */ +#define WM8904_DACL_ENA_MASK 0x0008 /* DACL_ENA */ +#define WM8904_DACL_ENA_SHIFT 3 /* DACL_ENA */ +#define WM8904_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8904_DACR_ENA 0x0004 /* DACR_ENA */ +#define WM8904_DACR_ENA_MASK 0x0004 /* DACR_ENA */ +#define WM8904_DACR_ENA_SHIFT 2 /* DACR_ENA */ +#define WM8904_DACR_ENA_WIDTH 1 /* DACR_ENA */ +#define WM8904_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8904_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8904_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8904_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8904_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8904_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8904_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8904_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R20 (0x14) - Clock Rates 0 + */ +#define WM8904_TOCLK_RATE_DIV16 0x4000 /* TOCLK_RATE_DIV16 */ +#define WM8904_TOCLK_RATE_DIV16_MASK 0x4000 /* TOCLK_RATE_DIV16 */ +#define WM8904_TOCLK_RATE_DIV16_SHIFT 14 /* TOCLK_RATE_DIV16 */ +#define WM8904_TOCLK_RATE_DIV16_WIDTH 1 /* TOCLK_RATE_DIV16 */ +#define WM8904_TOCLK_RATE_X4 0x2000 /* TOCLK_RATE_X4 */ +#define WM8904_TOCLK_RATE_X4_MASK 0x2000 /* TOCLK_RATE_X4 */ +#define WM8904_TOCLK_RATE_X4_SHIFT 13 /* TOCLK_RATE_X4 */ +#define WM8904_TOCLK_RATE_X4_WIDTH 1 /* TOCLK_RATE_X4 */ +#define WM8904_SR_MODE 0x1000 /* SR_MODE */ +#define WM8904_SR_MODE_MASK 0x1000 /* SR_MODE */ +#define WM8904_SR_MODE_SHIFT 12 /* SR_MODE */ +#define WM8904_SR_MODE_WIDTH 1 /* SR_MODE */ +#define WM8904_MCLK_DIV 0x0001 /* MCLK_DIV */ +#define WM8904_MCLK_DIV_MASK 0x0001 /* MCLK_DIV */ +#define WM8904_MCLK_DIV_SHIFT 0 /* MCLK_DIV */ +#define WM8904_MCLK_DIV_WIDTH 1 /* MCLK_DIV */ + +/* + * R21 (0x15) - Clock Rates 1 + */ +#define WM8904_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */ +#define WM8904_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */ +#define WM8904_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */ +#define WM8904_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */ +#define WM8904_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */ +#define WM8904_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */ + +/* + * R22 (0x16) - Clock Rates 2 + */ +#define WM8904_MCLK_INV 0x8000 /* MCLK_INV */ +#define WM8904_MCLK_INV_MASK 0x8000 /* MCLK_INV */ +#define WM8904_MCLK_INV_SHIFT 15 /* MCLK_INV */ +#define WM8904_MCLK_INV_WIDTH 1 /* MCLK_INV */ +#define WM8904_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8904_SYSCLK_SRC_MASK 0x4000 /* SYSCLK_SRC */ +#define WM8904_SYSCLK_SRC_SHIFT 14 /* SYSCLK_SRC */ +#define WM8904_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */ +#define WM8904_TOCLK_RATE 0x1000 /* TOCLK_RATE */ +#define WM8904_TOCLK_RATE_MASK 0x1000 /* TOCLK_RATE */ +#define WM8904_TOCLK_RATE_SHIFT 12 /* TOCLK_RATE */ +#define WM8904_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */ +#define WM8904_OPCLK_ENA 0x0008 /* OPCLK_ENA */ +#define WM8904_OPCLK_ENA_MASK 0x0008 /* OPCLK_ENA */ +#define WM8904_OPCLK_ENA_SHIFT 3 /* OPCLK_ENA */ +#define WM8904_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */ +#define WM8904_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */ +#define WM8904_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */ +#define WM8904_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */ +#define WM8904_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8904_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM8904_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM8904_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM8904_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8904_TOCLK_ENA 0x0001 /* TOCLK_ENA */ +#define WM8904_TOCLK_ENA_MASK 0x0001 /* TOCLK_ENA */ +#define WM8904_TOCLK_ENA_SHIFT 0 /* TOCLK_ENA */ +#define WM8904_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ + +/* + * R24 (0x18) - Audio Interface 0 + */ +#define WM8904_DACL_DATINV 0x1000 /* DACL_DATINV */ +#define WM8904_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */ +#define WM8904_DACL_DATINV_SHIFT 12 /* DACL_DATINV */ +#define WM8904_DACL_DATINV_WIDTH 1 /* DACL_DATINV */ +#define WM8904_DACR_DATINV 0x0800 /* DACR_DATINV */ +#define WM8904_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */ +#define WM8904_DACR_DATINV_SHIFT 11 /* DACR_DATINV */ +#define WM8904_DACR_DATINV_WIDTH 1 /* DACR_DATINV */ +#define WM8904_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */ +#define WM8904_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */ +#define WM8904_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */ +#define WM8904_LOOPBACK 0x0100 /* LOOPBACK */ +#define WM8904_LOOPBACK_MASK 0x0100 /* LOOPBACK */ +#define WM8904_LOOPBACK_SHIFT 8 /* LOOPBACK */ +#define WM8904_LOOPBACK_WIDTH 1 /* LOOPBACK */ +#define WM8904_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */ +#define WM8904_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */ +#define WM8904_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */ +#define WM8904_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */ +#define WM8904_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */ +#define WM8904_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */ +#define WM8904_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */ +#define WM8904_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */ +#define WM8904_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */ +#define WM8904_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */ +#define WM8904_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */ +#define WM8904_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */ +#define WM8904_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */ +#define WM8904_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */ +#define WM8904_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */ +#define WM8904_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */ +#define WM8904_ADC_COMP 0x0008 /* ADC_COMP */ +#define WM8904_ADC_COMP_MASK 0x0008 /* ADC_COMP */ +#define WM8904_ADC_COMP_SHIFT 3 /* ADC_COMP */ +#define WM8904_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8904_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */ +#define WM8904_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */ +#define WM8904_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */ +#define WM8904_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8904_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM8904_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM8904_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM8904_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8904_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM8904_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM8904_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM8904_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R25 (0x19) - Audio Interface 1 + */ +#define WM8904_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8904_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */ +#define WM8904_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */ +#define WM8904_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */ +#define WM8904_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8904_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8904_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */ +#define WM8904_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */ +#define WM8904_AIFADC_TDM 0x0800 /* AIFADC_TDM */ +#define WM8904_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */ +#define WM8904_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */ +#define WM8904_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */ +#define WM8904_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8904_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8904_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */ +#define WM8904_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */ +#define WM8904_AIF_TRIS 0x0100 /* AIF_TRIS */ +#define WM8904_AIF_TRIS_MASK 0x0100 /* AIF_TRIS */ +#define WM8904_AIF_TRIS_SHIFT 8 /* AIF_TRIS */ +#define WM8904_AIF_TRIS_WIDTH 1 /* AIF_TRIS */ +#define WM8904_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM8904_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM8904_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM8904_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM8904_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM8904_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM8904_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM8904_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM8904_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM8904_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM8904_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM8904_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM8904_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM8904_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM8904_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM8904_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM8904_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM8904_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R26 (0x1A) - Audio Interface 2 + */ +#define WM8904_OPCLK_DIV_MASK 0x0F00 /* OPCLK_DIV - [11:8] */ +#define WM8904_OPCLK_DIV_SHIFT 8 /* OPCLK_DIV - [11:8] */ +#define WM8904_OPCLK_DIV_WIDTH 4 /* OPCLK_DIV - [11:8] */ +#define WM8904_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM8904_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM8904_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R27 (0x1B) - Audio Interface 3 + */ +#define WM8904_LRCLK_DIR 0x0800 /* LRCLK_DIR */ +#define WM8904_LRCLK_DIR_MASK 0x0800 /* LRCLK_DIR */ +#define WM8904_LRCLK_DIR_SHIFT 11 /* LRCLK_DIR */ +#define WM8904_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM8904_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM8904_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM8904_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R30 (0x1E) - DAC Digital Volume Left + */ +#define WM8904_DAC_VU 0x0100 /* DAC_VU */ +#define WM8904_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8904_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8904_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8904_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8904_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8904_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital Volume Right + */ +#define WM8904_DAC_VU 0x0100 /* DAC_VU */ +#define WM8904_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8904_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8904_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8904_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8904_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8904_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R32 (0x20) - DAC Digital 0 + */ +#define WM8904_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8904_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8904_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8904_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8904_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8904_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8904_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8904_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8904_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ +#define WM8904_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */ +#define WM8904_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */ +#define WM8904_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */ + +/* + * R33 (0x21) - DAC Digital 1 + */ +#define WM8904_DAC_MONO 0x1000 /* DAC_MONO */ +#define WM8904_DAC_MONO_MASK 0x1000 /* DAC_MONO */ +#define WM8904_DAC_MONO_SHIFT 12 /* DAC_MONO */ +#define WM8904_DAC_MONO_WIDTH 1 /* DAC_MONO */ +#define WM8904_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */ +#define WM8904_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */ +#define WM8904_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */ +#define WM8904_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */ +#define WM8904_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM8904_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM8904_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM8904_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8904_DAC_UNMUTE_RAMP 0x0200 /* DAC_UNMUTE_RAMP */ +#define WM8904_DAC_UNMUTE_RAMP_MASK 0x0200 /* DAC_UNMUTE_RAMP */ +#define WM8904_DAC_UNMUTE_RAMP_SHIFT 9 /* DAC_UNMUTE_RAMP */ +#define WM8904_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */ +#define WM8904_DAC_OSR128 0x0040 /* DAC_OSR128 */ +#define WM8904_DAC_OSR128_MASK 0x0040 /* DAC_OSR128 */ +#define WM8904_DAC_OSR128_SHIFT 6 /* DAC_OSR128 */ +#define WM8904_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ +#define WM8904_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM8904_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM8904_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM8904_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8904_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8904_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8904_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R36 (0x24) - ADC Digital Volume Left + */ +#define WM8904_ADC_VU 0x0100 /* ADC_VU */ +#define WM8904_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8904_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8904_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8904_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8904_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8904_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R37 (0x25) - ADC Digital Volume Right + */ +#define WM8904_ADC_VU 0x0100 /* ADC_VU */ +#define WM8904_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8904_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8904_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8904_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8904_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8904_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R38 (0x26) - ADC Digital 0 + */ +#define WM8904_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */ +#define WM8904_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */ +#define WM8904_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */ +#define WM8904_ADC_HPF 0x0010 /* ADC_HPF */ +#define WM8904_ADC_HPF_MASK 0x0010 /* ADC_HPF */ +#define WM8904_ADC_HPF_SHIFT 4 /* ADC_HPF */ +#define WM8904_ADC_HPF_WIDTH 1 /* ADC_HPF */ +#define WM8904_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8904_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */ +#define WM8904_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */ +#define WM8904_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */ +#define WM8904_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8904_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */ +#define WM8904_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */ +#define WM8904_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */ + +/* + * R39 (0x27) - Digital Microphone 0 + */ +#define WM8904_DMIC_ENA 0x1000 /* DMIC_ENA */ +#define WM8904_DMIC_ENA_MASK 0x1000 /* DMIC_ENA */ +#define WM8904_DMIC_ENA_SHIFT 12 /* DMIC_ENA */ +#define WM8904_DMIC_ENA_WIDTH 1 /* DMIC_ENA */ +#define WM8904_DMIC_SRC 0x0800 /* DMIC_SRC */ +#define WM8904_DMIC_SRC_MASK 0x0800 /* DMIC_SRC */ +#define WM8904_DMIC_SRC_SHIFT 11 /* DMIC_SRC */ +#define WM8904_DMIC_SRC_WIDTH 1 /* DMIC_SRC */ + +/* + * R40 (0x28) - DRC 0 + */ +#define WM8904_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM8904_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM8904_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM8904_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM8904_DRC_DAC_PATH 0x4000 /* DRC_DAC_PATH */ +#define WM8904_DRC_DAC_PATH_MASK 0x4000 /* DRC_DAC_PATH */ +#define WM8904_DRC_DAC_PATH_SHIFT 14 /* DRC_DAC_PATH */ +#define WM8904_DRC_DAC_PATH_WIDTH 1 /* DRC_DAC_PATH */ +#define WM8904_DRC_GS_HYST_LVL_MASK 0x1800 /* DRC_GS_HYST_LVL - [12:11] */ +#define WM8904_DRC_GS_HYST_LVL_SHIFT 11 /* DRC_GS_HYST_LVL - [12:11] */ +#define WM8904_DRC_GS_HYST_LVL_WIDTH 2 /* DRC_GS_HYST_LVL - [12:11] */ +#define WM8904_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8904_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8904_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8904_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */ +#define WM8904_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */ +#define WM8904_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */ +#define WM8904_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */ +#define WM8904_DRC_GS_ENA 0x0008 /* DRC_GS_ENA */ +#define WM8904_DRC_GS_ENA_MASK 0x0008 /* DRC_GS_ENA */ +#define WM8904_DRC_GS_ENA_SHIFT 3 /* DRC_GS_ENA */ +#define WM8904_DRC_GS_ENA_WIDTH 1 /* DRC_GS_ENA */ +#define WM8904_DRC_QR 0x0004 /* DRC_QR */ +#define WM8904_DRC_QR_MASK 0x0004 /* DRC_QR */ +#define WM8904_DRC_QR_SHIFT 2 /* DRC_QR */ +#define WM8904_DRC_QR_WIDTH 1 /* DRC_QR */ +#define WM8904_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */ +#define WM8904_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */ +#define WM8904_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */ +#define WM8904_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */ +#define WM8904_DRC_GS_HYST 0x0001 /* DRC_GS_HYST */ +#define WM8904_DRC_GS_HYST_MASK 0x0001 /* DRC_GS_HYST */ +#define WM8904_DRC_GS_HYST_SHIFT 0 /* DRC_GS_HYST */ +#define WM8904_DRC_GS_HYST_WIDTH 1 /* DRC_GS_HYST */ + +/* + * R41 (0x29) - DRC 1 + */ +#define WM8904_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */ +#define WM8904_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */ +#define WM8904_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */ +#define WM8904_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */ +#define WM8904_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */ +#define WM8904_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */ +#define WM8904_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */ +#define WM8904_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */ +#define WM8904_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */ +#define WM8904_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */ +#define WM8904_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */ +#define WM8904_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */ +#define WM8904_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM8904_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM8904_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM8904_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8904_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8904_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R42 (0x2A) - DRC 2 + */ +#define WM8904_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */ +#define WM8904_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */ +#define WM8904_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */ +#define WM8904_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */ +#define WM8904_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */ +#define WM8904_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */ + +/* + * R43 (0x2B) - DRC 3 + */ +#define WM8904_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */ +#define WM8904_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */ +#define WM8904_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */ +#define WM8904_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */ +#define WM8904_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */ +#define WM8904_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */ + +/* + * R44 (0x2C) - Analogue Left Input 0 + */ +#define WM8904_LINMUTE 0x0080 /* LINMUTE */ +#define WM8904_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8904_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8904_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8904_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */ +#define WM8904_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */ +#define WM8904_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */ + +/* + * R45 (0x2D) - Analogue Right Input 0 + */ +#define WM8904_RINMUTE 0x0080 /* RINMUTE */ +#define WM8904_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8904_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8904_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8904_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */ +#define WM8904_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */ +#define WM8904_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */ + +/* + * R46 (0x2E) - Analogue Left Input 1 + */ +#define WM8904_INL_CM_ENA 0x0040 /* INL_CM_ENA */ +#define WM8904_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */ +#define WM8904_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */ +#define WM8904_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */ +#define WM8904_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */ +#define WM8904_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */ +#define WM8904_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */ +#define WM8904_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */ +#define WM8904_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */ +#define WM8904_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */ +#define WM8904_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */ +#define WM8904_L_MODE_SHIFT 0 /* L_MODE - [1:0] */ +#define WM8904_L_MODE_WIDTH 2 /* L_MODE - [1:0] */ + +/* + * R47 (0x2F) - Analogue Right Input 1 + */ +#define WM8904_INR_CM_ENA 0x0040 /* INR_CM_ENA */ +#define WM8904_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */ +#define WM8904_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */ +#define WM8904_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */ +#define WM8904_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */ +#define WM8904_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */ +#define WM8904_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */ +#define WM8904_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */ +#define WM8904_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */ +#define WM8904_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */ +#define WM8904_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */ +#define WM8904_R_MODE_SHIFT 0 /* R_MODE - [1:0] */ +#define WM8904_R_MODE_WIDTH 2 /* R_MODE - [1:0] */ + +/* + * R57 (0x39) - Analogue OUT1 Left + */ +#define WM8904_HPOUTL_MUTE 0x0100 /* HPOUTL_MUTE */ +#define WM8904_HPOUTL_MUTE_MASK 0x0100 /* HPOUTL_MUTE */ +#define WM8904_HPOUTL_MUTE_SHIFT 8 /* HPOUTL_MUTE */ +#define WM8904_HPOUTL_MUTE_WIDTH 1 /* HPOUTL_MUTE */ +#define WM8904_HPOUT_VU 0x0080 /* HPOUT_VU */ +#define WM8904_HPOUT_VU_MASK 0x0080 /* HPOUT_VU */ +#define WM8904_HPOUT_VU_SHIFT 7 /* HPOUT_VU */ +#define WM8904_HPOUT_VU_WIDTH 1 /* HPOUT_VU */ +#define WM8904_HPOUTLZC 0x0040 /* HPOUTLZC */ +#define WM8904_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */ +#define WM8904_HPOUTLZC_SHIFT 6 /* HPOUTLZC */ +#define WM8904_HPOUTLZC_WIDTH 1 /* HPOUTLZC */ +#define WM8904_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */ +#define WM8904_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */ +#define WM8904_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */ + +/* + * R58 (0x3A) - Analogue OUT1 Right + */ +#define WM8904_HPOUTR_MUTE 0x0100 /* HPOUTR_MUTE */ +#define WM8904_HPOUTR_MUTE_MASK 0x0100 /* HPOUTR_MUTE */ +#define WM8904_HPOUTR_MUTE_SHIFT 8 /* HPOUTR_MUTE */ +#define WM8904_HPOUTR_MUTE_WIDTH 1 /* HPOUTR_MUTE */ +#define WM8904_HPOUT_VU 0x0080 /* HPOUT_VU */ +#define WM8904_HPOUT_VU_MASK 0x0080 /* HPOUT_VU */ +#define WM8904_HPOUT_VU_SHIFT 7 /* HPOUT_VU */ +#define WM8904_HPOUT_VU_WIDTH 1 /* HPOUT_VU */ +#define WM8904_HPOUTRZC 0x0040 /* HPOUTRZC */ +#define WM8904_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */ +#define WM8904_HPOUTRZC_SHIFT 6 /* HPOUTRZC */ +#define WM8904_HPOUTRZC_WIDTH 1 /* HPOUTRZC */ +#define WM8904_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */ +#define WM8904_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */ +#define WM8904_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */ + +/* + * R59 (0x3B) - Analogue OUT2 Left + */ +#define WM8904_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */ +#define WM8904_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */ +#define WM8904_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */ +#define WM8904_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */ +#define WM8904_LINEOUT_VU 0x0080 /* LINEOUT_VU */ +#define WM8904_LINEOUT_VU_MASK 0x0080 /* LINEOUT_VU */ +#define WM8904_LINEOUT_VU_SHIFT 7 /* LINEOUT_VU */ +#define WM8904_LINEOUT_VU_WIDTH 1 /* LINEOUT_VU */ +#define WM8904_LINEOUTLZC 0x0040 /* LINEOUTLZC */ +#define WM8904_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */ +#define WM8904_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */ +#define WM8904_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */ +#define WM8904_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */ +#define WM8904_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */ +#define WM8904_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */ + +/* + * R60 (0x3C) - Analogue OUT2 Right + */ +#define WM8904_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */ +#define WM8904_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */ +#define WM8904_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */ +#define WM8904_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */ +#define WM8904_LINEOUT_VU 0x0080 /* LINEOUT_VU */ +#define WM8904_LINEOUT_VU_MASK 0x0080 /* LINEOUT_VU */ +#define WM8904_LINEOUT_VU_SHIFT 7 /* LINEOUT_VU */ +#define WM8904_LINEOUT_VU_WIDTH 1 /* LINEOUT_VU */ +#define WM8904_LINEOUTRZC 0x0040 /* LINEOUTRZC */ +#define WM8904_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */ +#define WM8904_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */ +#define WM8904_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */ +#define WM8904_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */ +#define WM8904_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */ +#define WM8904_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */ + +/* + * R61 (0x3D) - Analogue OUT12 ZC + */ +#define WM8904_HPL_BYP_ENA 0x0008 /* HPL_BYP_ENA */ +#define WM8904_HPL_BYP_ENA_MASK 0x0008 /* HPL_BYP_ENA */ +#define WM8904_HPL_BYP_ENA_SHIFT 3 /* HPL_BYP_ENA */ +#define WM8904_HPL_BYP_ENA_WIDTH 1 /* HPL_BYP_ENA */ +#define WM8904_HPR_BYP_ENA 0x0004 /* HPR_BYP_ENA */ +#define WM8904_HPR_BYP_ENA_MASK 0x0004 /* HPR_BYP_ENA */ +#define WM8904_HPR_BYP_ENA_SHIFT 2 /* HPR_BYP_ENA */ +#define WM8904_HPR_BYP_ENA_WIDTH 1 /* HPR_BYP_ENA */ +#define WM8904_LINEOUTL_BYP_ENA 0x0002 /* LINEOUTL_BYP_ENA */ +#define WM8904_LINEOUTL_BYP_ENA_MASK 0x0002 /* LINEOUTL_BYP_ENA */ +#define WM8904_LINEOUTL_BYP_ENA_SHIFT 1 /* LINEOUTL_BYP_ENA */ +#define WM8904_LINEOUTL_BYP_ENA_WIDTH 1 /* LINEOUTL_BYP_ENA */ +#define WM8904_LINEOUTR_BYP_ENA 0x0001 /* LINEOUTR_BYP_ENA */ +#define WM8904_LINEOUTR_BYP_ENA_MASK 0x0001 /* LINEOUTR_BYP_ENA */ +#define WM8904_LINEOUTR_BYP_ENA_SHIFT 0 /* LINEOUTR_BYP_ENA */ +#define WM8904_LINEOUTR_BYP_ENA_WIDTH 1 /* LINEOUTR_BYP_ENA */ + +/* + * R67 (0x43) - DC Servo 0 + */ +#define WM8904_DCS_ENA_CHAN_3 0x0008 /* DCS_ENA_CHAN_3 */ +#define WM8904_DCS_ENA_CHAN_3_MASK 0x0008 /* DCS_ENA_CHAN_3 */ +#define WM8904_DCS_ENA_CHAN_3_SHIFT 3 /* DCS_ENA_CHAN_3 */ +#define WM8904_DCS_ENA_CHAN_3_WIDTH 1 /* DCS_ENA_CHAN_3 */ +#define WM8904_DCS_ENA_CHAN_2 0x0004 /* DCS_ENA_CHAN_2 */ +#define WM8904_DCS_ENA_CHAN_2_MASK 0x0004 /* DCS_ENA_CHAN_2 */ +#define WM8904_DCS_ENA_CHAN_2_SHIFT 2 /* DCS_ENA_CHAN_2 */ +#define WM8904_DCS_ENA_CHAN_2_WIDTH 1 /* DCS_ENA_CHAN_2 */ +#define WM8904_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8904_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8904_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM8904_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM8904_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8904_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8904_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM8904_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R68 (0x44) - DC Servo 1 + */ +#define WM8904_DCS_TRIG_SINGLE_3 0x8000 /* DCS_TRIG_SINGLE_3 */ +#define WM8904_DCS_TRIG_SINGLE_3_MASK 0x8000 /* DCS_TRIG_SINGLE_3 */ +#define WM8904_DCS_TRIG_SINGLE_3_SHIFT 15 /* DCS_TRIG_SINGLE_3 */ +#define WM8904_DCS_TRIG_SINGLE_3_WIDTH 1 /* DCS_TRIG_SINGLE_3 */ +#define WM8904_DCS_TRIG_SINGLE_2 0x4000 /* DCS_TRIG_SINGLE_2 */ +#define WM8904_DCS_TRIG_SINGLE_2_MASK 0x4000 /* DCS_TRIG_SINGLE_2 */ +#define WM8904_DCS_TRIG_SINGLE_2_SHIFT 14 /* DCS_TRIG_SINGLE_2 */ +#define WM8904_DCS_TRIG_SINGLE_2_WIDTH 1 /* DCS_TRIG_SINGLE_2 */ +#define WM8904_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8904_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8904_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM8904_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM8904_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8904_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8904_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM8904_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM8904_DCS_TRIG_SERIES_3 0x0800 /* DCS_TRIG_SERIES_3 */ +#define WM8904_DCS_TRIG_SERIES_3_MASK 0x0800 /* DCS_TRIG_SERIES_3 */ +#define WM8904_DCS_TRIG_SERIES_3_SHIFT 11 /* DCS_TRIG_SERIES_3 */ +#define WM8904_DCS_TRIG_SERIES_3_WIDTH 1 /* DCS_TRIG_SERIES_3 */ +#define WM8904_DCS_TRIG_SERIES_2 0x0400 /* DCS_TRIG_SERIES_2 */ +#define WM8904_DCS_TRIG_SERIES_2_MASK 0x0400 /* DCS_TRIG_SERIES_2 */ +#define WM8904_DCS_TRIG_SERIES_2_SHIFT 10 /* DCS_TRIG_SERIES_2 */ +#define WM8904_DCS_TRIG_SERIES_2_WIDTH 1 /* DCS_TRIG_SERIES_2 */ +#define WM8904_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8904_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8904_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM8904_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM8904_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8904_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8904_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM8904_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM8904_DCS_TRIG_STARTUP_3 0x0080 /* DCS_TRIG_STARTUP_3 */ +#define WM8904_DCS_TRIG_STARTUP_3_MASK 0x0080 /* DCS_TRIG_STARTUP_3 */ +#define WM8904_DCS_TRIG_STARTUP_3_SHIFT 7 /* DCS_TRIG_STARTUP_3 */ +#define WM8904_DCS_TRIG_STARTUP_3_WIDTH 1 /* DCS_TRIG_STARTUP_3 */ +#define WM8904_DCS_TRIG_STARTUP_2 0x0040 /* DCS_TRIG_STARTUP_2 */ +#define WM8904_DCS_TRIG_STARTUP_2_MASK 0x0040 /* DCS_TRIG_STARTUP_2 */ +#define WM8904_DCS_TRIG_STARTUP_2_SHIFT 6 /* DCS_TRIG_STARTUP_2 */ +#define WM8904_DCS_TRIG_STARTUP_2_WIDTH 1 /* DCS_TRIG_STARTUP_2 */ +#define WM8904_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8904_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8904_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM8904_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM8904_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8904_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8904_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM8904_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM8904_DCS_TRIG_DAC_WR_3 0x0008 /* DCS_TRIG_DAC_WR_3 */ +#define WM8904_DCS_TRIG_DAC_WR_3_MASK 0x0008 /* DCS_TRIG_DAC_WR_3 */ +#define WM8904_DCS_TRIG_DAC_WR_3_SHIFT 3 /* DCS_TRIG_DAC_WR_3 */ +#define WM8904_DCS_TRIG_DAC_WR_3_WIDTH 1 /* DCS_TRIG_DAC_WR_3 */ +#define WM8904_DCS_TRIG_DAC_WR_2 0x0004 /* DCS_TRIG_DAC_WR_2 */ +#define WM8904_DCS_TRIG_DAC_WR_2_MASK 0x0004 /* DCS_TRIG_DAC_WR_2 */ +#define WM8904_DCS_TRIG_DAC_WR_2_SHIFT 2 /* DCS_TRIG_DAC_WR_2 */ +#define WM8904_DCS_TRIG_DAC_WR_2_WIDTH 1 /* DCS_TRIG_DAC_WR_2 */ +#define WM8904_DCS_TRIG_DAC_WR_1 0x0002 /* DCS_TRIG_DAC_WR_1 */ +#define WM8904_DCS_TRIG_DAC_WR_1_MASK 0x0002 /* DCS_TRIG_DAC_WR_1 */ +#define WM8904_DCS_TRIG_DAC_WR_1_SHIFT 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8904_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8904_DCS_TRIG_DAC_WR_0 0x0001 /* DCS_TRIG_DAC_WR_0 */ +#define WM8904_DCS_TRIG_DAC_WR_0_MASK 0x0001 /* DCS_TRIG_DAC_WR_0 */ +#define WM8904_DCS_TRIG_DAC_WR_0_SHIFT 0 /* DCS_TRIG_DAC_WR_0 */ +#define WM8904_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ + +/* + * R69 (0x45) - DC Servo 2 + */ +#define WM8904_DCS_TIMER_PERIOD_23_MASK 0x0F00 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8904_DCS_TIMER_PERIOD_23_SHIFT 8 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8904_DCS_TIMER_PERIOD_23_WIDTH 4 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8904_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8904_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8904_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R71 (0x47) - DC Servo 4 + */ +#define WM8904_DCS_SERIES_NO_23_MASK 0x007F /* DCS_SERIES_NO_23 - [6:0] */ +#define WM8904_DCS_SERIES_NO_23_SHIFT 0 /* DCS_SERIES_NO_23 - [6:0] */ +#define WM8904_DCS_SERIES_NO_23_WIDTH 7 /* DCS_SERIES_NO_23 - [6:0] */ + +/* + * R72 (0x48) - DC Servo 5 + */ +#define WM8904_DCS_SERIES_NO_01_MASK 0x007F /* DCS_SERIES_NO_01 - [6:0] */ +#define WM8904_DCS_SERIES_NO_01_SHIFT 0 /* DCS_SERIES_NO_01 - [6:0] */ +#define WM8904_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [6:0] */ + +/* + * R73 (0x49) - DC Servo 6 + */ +#define WM8904_DCS_DAC_WR_VAL_3_MASK 0x00FF /* DCS_DAC_WR_VAL_3 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_3_SHIFT 0 /* DCS_DAC_WR_VAL_3 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_3_WIDTH 8 /* DCS_DAC_WR_VAL_3 - [7:0] */ + +/* + * R74 (0x4A) - DC Servo 7 + */ +#define WM8904_DCS_DAC_WR_VAL_2_MASK 0x00FF /* DCS_DAC_WR_VAL_2 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_2_SHIFT 0 /* DCS_DAC_WR_VAL_2 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_2_WIDTH 8 /* DCS_DAC_WR_VAL_2 - [7:0] */ + +/* + * R75 (0x4B) - DC Servo 8 + */ +#define WM8904_DCS_DAC_WR_VAL_1_MASK 0x00FF /* DCS_DAC_WR_VAL_1 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_1_SHIFT 0 /* DCS_DAC_WR_VAL_1 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [7:0] */ + +/* + * R76 (0x4C) - DC Servo 9 + */ +#define WM8904_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8904_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R77 (0x4D) - DC Servo Readback 0 + */ +#define WM8904_DCS_CAL_COMPLETE_MASK 0x0F00 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8904_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8904_DCS_CAL_COMPLETE_WIDTH 4 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8904_DCS_DAC_WR_COMPLETE_MASK 0x00F0 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8904_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8904_DCS_DAC_WR_COMPLETE_WIDTH 4 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8904_DCS_STARTUP_COMPLETE_MASK 0x000F /* DCS_STARTUP_COMPLETE - [3:0] */ +#define WM8904_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [3:0] */ +#define WM8904_DCS_STARTUP_COMPLETE_WIDTH 4 /* DCS_STARTUP_COMPLETE - [3:0] */ + +/* + * R90 (0x5A) - Analogue HP 0 + */ +#define WM8904_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8904_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8904_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8904_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8904_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8904_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8904_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8904_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8904_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8904_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8904_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8904_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8904_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8904_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8904_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8904_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8904_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8904_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8904_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8904_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8904_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8904_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8904_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8904_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8904_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8904_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8904_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8904_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8904_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8904_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8904_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8904_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R94 (0x5E) - Analogue Lineout 0 + */ +#define WM8904_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8904_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8904_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */ +#define WM8904_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */ +#define WM8904_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8904_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8904_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */ +#define WM8904_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */ +#define WM8904_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8904_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8904_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */ +#define WM8904_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */ +#define WM8904_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */ +#define WM8904_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */ +#define WM8904_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */ +#define WM8904_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */ +#define WM8904_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8904_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8904_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */ +#define WM8904_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */ +#define WM8904_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8904_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8904_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */ +#define WM8904_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */ +#define WM8904_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8904_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8904_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */ +#define WM8904_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */ +#define WM8904_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */ +#define WM8904_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */ +#define WM8904_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */ +#define WM8904_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */ + +/* + * R98 (0x62) - Charge Pump 0 + */ +#define WM8904_CP_ENA 0x0001 /* CP_ENA */ +#define WM8904_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8904_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8904_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R104 (0x68) - Class W 0 + */ +#define WM8904_CP_DYN_PWR 0x0001 /* CP_DYN_PWR */ +#define WM8904_CP_DYN_PWR_MASK 0x0001 /* CP_DYN_PWR */ +#define WM8904_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR */ +#define WM8904_CP_DYN_PWR_WIDTH 1 /* CP_DYN_PWR */ + +/* + * R108 (0x6C) - Write Sequencer 0 + */ +#define WM8904_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM8904_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM8904_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM8904_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8904_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8904_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8904_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R109 (0x6D) - Write Sequencer 1 + */ +#define WM8904_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8904_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8904_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8904_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM8904_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM8904_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM8904_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8904_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8904_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R110 (0x6E) - Write Sequencer 2 + */ +#define WM8904_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM8904_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM8904_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM8904_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8904_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM8904_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM8904_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM8904_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8904_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8904_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R111 (0x6F) - Write Sequencer 3 + */ +#define WM8904_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8904_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8904_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8904_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8904_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8904_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8904_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8904_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8904_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8904_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8904_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R112 (0x70) - Write Sequencer 4 + */ +#define WM8904_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8904_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8904_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8904_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8904_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8904_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8904_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R116 (0x74) - FLL Control 1 + */ +#define WM8904_FLL_FRACN_ENA 0x0004 /* FLL_FRACN_ENA */ +#define WM8904_FLL_FRACN_ENA_MASK 0x0004 /* FLL_FRACN_ENA */ +#define WM8904_FLL_FRACN_ENA_SHIFT 2 /* FLL_FRACN_ENA */ +#define WM8904_FLL_FRACN_ENA_WIDTH 1 /* FLL_FRACN_ENA */ +#define WM8904_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */ +#define WM8904_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */ +#define WM8904_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */ +#define WM8904_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */ +#define WM8904_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM8904_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM8904_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM8904_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R117 (0x75) - FLL Control 2 + */ +#define WM8904_FLL_OUTDIV_MASK 0x3F00 /* FLL_OUTDIV - [13:8] */ +#define WM8904_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [13:8] */ +#define WM8904_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [13:8] */ +#define WM8904_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */ +#define WM8904_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */ +#define WM8904_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */ +#define WM8904_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM8904_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM8904_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R118 (0x76) - FLL Control 3 + */ +#define WM8904_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */ +#define WM8904_FLL_K_SHIFT 0 /* FLL_K - [15:0] */ +#define WM8904_FLL_K_WIDTH 16 /* FLL_K - [15:0] */ + +/* + * R119 (0x77) - FLL Control 4 + */ +#define WM8904_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */ +#define WM8904_FLL_N_SHIFT 5 /* FLL_N - [14:5] */ +#define WM8904_FLL_N_WIDTH 10 /* FLL_N - [14:5] */ +#define WM8904_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */ +#define WM8904_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */ +#define WM8904_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */ + +/* + * R120 (0x78) - FLL Control 5 + */ +#define WM8904_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8904_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8904_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8904_FLL_CLK_REF_SRC_MASK 0x0003 /* FLL_CLK_REF_SRC - [1:0] */ +#define WM8904_FLL_CLK_REF_SRC_SHIFT 0 /* FLL_CLK_REF_SRC - [1:0] */ +#define WM8904_FLL_CLK_REF_SRC_WIDTH 2 /* FLL_CLK_REF_SRC - [1:0] */ + +/* + * R126 (0x7E) - Digital Pulls + */ +#define WM8904_MCLK_PU 0x0080 /* MCLK_PU */ +#define WM8904_MCLK_PU_MASK 0x0080 /* MCLK_PU */ +#define WM8904_MCLK_PU_SHIFT 7 /* MCLK_PU */ +#define WM8904_MCLK_PU_WIDTH 1 /* MCLK_PU */ +#define WM8904_MCLK_PD 0x0040 /* MCLK_PD */ +#define WM8904_MCLK_PD_MASK 0x0040 /* MCLK_PD */ +#define WM8904_MCLK_PD_SHIFT 6 /* MCLK_PD */ +#define WM8904_MCLK_PD_WIDTH 1 /* MCLK_PD */ +#define WM8904_DACDAT_PU 0x0020 /* DACDAT_PU */ +#define WM8904_DACDAT_PU_MASK 0x0020 /* DACDAT_PU */ +#define WM8904_DACDAT_PU_SHIFT 5 /* DACDAT_PU */ +#define WM8904_DACDAT_PU_WIDTH 1 /* DACDAT_PU */ +#define WM8904_DACDAT_PD 0x0010 /* DACDAT_PD */ +#define WM8904_DACDAT_PD_MASK 0x0010 /* DACDAT_PD */ +#define WM8904_DACDAT_PD_SHIFT 4 /* DACDAT_PD */ +#define WM8904_DACDAT_PD_WIDTH 1 /* DACDAT_PD */ +#define WM8904_LRCLK_PU 0x0008 /* LRCLK_PU */ +#define WM8904_LRCLK_PU_MASK 0x0008 /* LRCLK_PU */ +#define WM8904_LRCLK_PU_SHIFT 3 /* LRCLK_PU */ +#define WM8904_LRCLK_PU_WIDTH 1 /* LRCLK_PU */ +#define WM8904_LRCLK_PD 0x0004 /* LRCLK_PD */ +#define WM8904_LRCLK_PD_MASK 0x0004 /* LRCLK_PD */ +#define WM8904_LRCLK_PD_SHIFT 2 /* LRCLK_PD */ +#define WM8904_LRCLK_PD_WIDTH 1 /* LRCLK_PD */ +#define WM8904_BCLK_PU 0x0002 /* BCLK_PU */ +#define WM8904_BCLK_PU_MASK 0x0002 /* BCLK_PU */ +#define WM8904_BCLK_PU_SHIFT 1 /* BCLK_PU */ +#define WM8904_BCLK_PU_WIDTH 1 /* BCLK_PU */ +#define WM8904_BCLK_PD 0x0001 /* BCLK_PD */ +#define WM8904_BCLK_PD_MASK 0x0001 /* BCLK_PD */ +#define WM8904_BCLK_PD_SHIFT 0 /* BCLK_PD */ +#define WM8904_BCLK_PD_WIDTH 1 /* BCLK_PD */ + +/* + * R127 (0x7F) - Interrupt Status + */ +#define WM8904_IRQ 0x0400 /* IRQ */ +#define WM8904_IRQ_MASK 0x0400 /* IRQ */ +#define WM8904_IRQ_SHIFT 10 /* IRQ */ +#define WM8904_IRQ_WIDTH 1 /* IRQ */ +#define WM8904_GPIO_BCLK_EINT 0x0200 /* GPIO_BCLK_EINT */ +#define WM8904_GPIO_BCLK_EINT_MASK 0x0200 /* GPIO_BCLK_EINT */ +#define WM8904_GPIO_BCLK_EINT_SHIFT 9 /* GPIO_BCLK_EINT */ +#define WM8904_GPIO_BCLK_EINT_WIDTH 1 /* GPIO_BCLK_EINT */ +#define WM8904_WSEQ_EINT 0x0100 /* WSEQ_EINT */ +#define WM8904_WSEQ_EINT_MASK 0x0100 /* WSEQ_EINT */ +#define WM8904_WSEQ_EINT_SHIFT 8 /* WSEQ_EINT */ +#define WM8904_WSEQ_EINT_WIDTH 1 /* WSEQ_EINT */ +#define WM8904_GPIO3_EINT 0x0080 /* GPIO3_EINT */ +#define WM8904_GPIO3_EINT_MASK 0x0080 /* GPIO3_EINT */ +#define WM8904_GPIO3_EINT_SHIFT 7 /* GPIO3_EINT */ +#define WM8904_GPIO3_EINT_WIDTH 1 /* GPIO3_EINT */ +#define WM8904_GPIO2_EINT 0x0040 /* GPIO2_EINT */ +#define WM8904_GPIO2_EINT_MASK 0x0040 /* GPIO2_EINT */ +#define WM8904_GPIO2_EINT_SHIFT 6 /* GPIO2_EINT */ +#define WM8904_GPIO2_EINT_WIDTH 1 /* GPIO2_EINT */ +#define WM8904_GPIO1_EINT 0x0020 /* GPIO1_EINT */ +#define WM8904_GPIO1_EINT_MASK 0x0020 /* GPIO1_EINT */ +#define WM8904_GPIO1_EINT_SHIFT 5 /* GPIO1_EINT */ +#define WM8904_GPIO1_EINT_WIDTH 1 /* GPIO1_EINT */ +#define WM8904_GPI8_EINT 0x0010 /* GPI8_EINT */ +#define WM8904_GPI8_EINT_MASK 0x0010 /* GPI8_EINT */ +#define WM8904_GPI8_EINT_SHIFT 4 /* GPI8_EINT */ +#define WM8904_GPI8_EINT_WIDTH 1 /* GPI8_EINT */ +#define WM8904_GPI7_EINT 0x0008 /* GPI7_EINT */ +#define WM8904_GPI7_EINT_MASK 0x0008 /* GPI7_EINT */ +#define WM8904_GPI7_EINT_SHIFT 3 /* GPI7_EINT */ +#define WM8904_GPI7_EINT_WIDTH 1 /* GPI7_EINT */ +#define WM8904_FLL_LOCK_EINT 0x0004 /* FLL_LOCK_EINT */ +#define WM8904_FLL_LOCK_EINT_MASK 0x0004 /* FLL_LOCK_EINT */ +#define WM8904_FLL_LOCK_EINT_SHIFT 2 /* FLL_LOCK_EINT */ +#define WM8904_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */ +#define WM8904_MIC_SHRT_EINT 0x0002 /* MIC_SHRT_EINT */ +#define WM8904_MIC_SHRT_EINT_MASK 0x0002 /* MIC_SHRT_EINT */ +#define WM8904_MIC_SHRT_EINT_SHIFT 1 /* MIC_SHRT_EINT */ +#define WM8904_MIC_SHRT_EINT_WIDTH 1 /* MIC_SHRT_EINT */ +#define WM8904_MIC_DET_EINT 0x0001 /* MIC_DET_EINT */ +#define WM8904_MIC_DET_EINT_MASK 0x0001 /* MIC_DET_EINT */ +#define WM8904_MIC_DET_EINT_SHIFT 0 /* MIC_DET_EINT */ +#define WM8904_MIC_DET_EINT_WIDTH 1 /* MIC_DET_EINT */ + +/* + * R128 (0x80) - Interrupt Status Mask + */ +#define WM8904_IM_GPIO_BCLK_EINT 0x0200 /* IM_GPIO_BCLK_EINT */ +#define WM8904_IM_GPIO_BCLK_EINT_MASK 0x0200 /* IM_GPIO_BCLK_EINT */ +#define WM8904_IM_GPIO_BCLK_EINT_SHIFT 9 /* IM_GPIO_BCLK_EINT */ +#define WM8904_IM_GPIO_BCLK_EINT_WIDTH 1 /* IM_GPIO_BCLK_EINT */ +#define WM8904_IM_WSEQ_EINT 0x0100 /* IM_WSEQ_EINT */ +#define WM8904_IM_WSEQ_EINT_MASK 0x0100 /* IM_WSEQ_EINT */ +#define WM8904_IM_WSEQ_EINT_SHIFT 8 /* IM_WSEQ_EINT */ +#define WM8904_IM_WSEQ_EINT_WIDTH 1 /* IM_WSEQ_EINT */ +#define WM8904_IM_GPIO3_EINT 0x0080 /* IM_GPIO3_EINT */ +#define WM8904_IM_GPIO3_EINT_MASK 0x0080 /* IM_GPIO3_EINT */ +#define WM8904_IM_GPIO3_EINT_SHIFT 7 /* IM_GPIO3_EINT */ +#define WM8904_IM_GPIO3_EINT_WIDTH 1 /* IM_GPIO3_EINT */ +#define WM8904_IM_GPIO2_EINT 0x0040 /* IM_GPIO2_EINT */ +#define WM8904_IM_GPIO2_EINT_MASK 0x0040 /* IM_GPIO2_EINT */ +#define WM8904_IM_GPIO2_EINT_SHIFT 6 /* IM_GPIO2_EINT */ +#define WM8904_IM_GPIO2_EINT_WIDTH 1 /* IM_GPIO2_EINT */ +#define WM8904_IM_GPIO1_EINT 0x0020 /* IM_GPIO1_EINT */ +#define WM8904_IM_GPIO1_EINT_MASK 0x0020 /* IM_GPIO1_EINT */ +#define WM8904_IM_GPIO1_EINT_SHIFT 5 /* IM_GPIO1_EINT */ +#define WM8904_IM_GPIO1_EINT_WIDTH 1 /* IM_GPIO1_EINT */ +#define WM8904_IM_GPI8_EINT 0x0010 /* IM_GPI8_EINT */ +#define WM8904_IM_GPI8_EINT_MASK 0x0010 /* IM_GPI8_EINT */ +#define WM8904_IM_GPI8_EINT_SHIFT 4 /* IM_GPI8_EINT */ +#define WM8904_IM_GPI8_EINT_WIDTH 1 /* IM_GPI8_EINT */ +#define WM8904_IM_GPI7_EINT 0x0008 /* IM_GPI7_EINT */ +#define WM8904_IM_GPI7_EINT_MASK 0x0008 /* IM_GPI7_EINT */ +#define WM8904_IM_GPI7_EINT_SHIFT 3 /* IM_GPI7_EINT */ +#define WM8904_IM_GPI7_EINT_WIDTH 1 /* IM_GPI7_EINT */ +#define WM8904_IM_FLL_LOCK_EINT 0x0004 /* IM_FLL_LOCK_EINT */ +#define WM8904_IM_FLL_LOCK_EINT_MASK 0x0004 /* IM_FLL_LOCK_EINT */ +#define WM8904_IM_FLL_LOCK_EINT_SHIFT 2 /* IM_FLL_LOCK_EINT */ +#define WM8904_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */ +#define WM8904_IM_MIC_SHRT_EINT 0x0002 /* IM_MIC_SHRT_EINT */ +#define WM8904_IM_MIC_SHRT_EINT_MASK 0x0002 /* IM_MIC_SHRT_EINT */ +#define WM8904_IM_MIC_SHRT_EINT_SHIFT 1 /* IM_MIC_SHRT_EINT */ +#define WM8904_IM_MIC_SHRT_EINT_WIDTH 1 /* IM_MIC_SHRT_EINT */ +#define WM8904_IM_MIC_DET_EINT 0x0001 /* IM_MIC_DET_EINT */ +#define WM8904_IM_MIC_DET_EINT_MASK 0x0001 /* IM_MIC_DET_EINT */ +#define WM8904_IM_MIC_DET_EINT_SHIFT 0 /* IM_MIC_DET_EINT */ +#define WM8904_IM_MIC_DET_EINT_WIDTH 1 /* IM_MIC_DET_EINT */ + +/* + * R129 (0x81) - Interrupt Polarity + */ +#define WM8904_GPIO_BCLK_EINT_POL 0x0200 /* GPIO_BCLK_EINT_POL */ +#define WM8904_GPIO_BCLK_EINT_POL_MASK 0x0200 /* GPIO_BCLK_EINT_POL */ +#define WM8904_GPIO_BCLK_EINT_POL_SHIFT 9 /* GPIO_BCLK_EINT_POL */ +#define WM8904_GPIO_BCLK_EINT_POL_WIDTH 1 /* GPIO_BCLK_EINT_POL */ +#define WM8904_WSEQ_EINT_POL 0x0100 /* WSEQ_EINT_POL */ +#define WM8904_WSEQ_EINT_POL_MASK 0x0100 /* WSEQ_EINT_POL */ +#define WM8904_WSEQ_EINT_POL_SHIFT 8 /* WSEQ_EINT_POL */ +#define WM8904_WSEQ_EINT_POL_WIDTH 1 /* WSEQ_EINT_POL */ +#define WM8904_GPIO3_EINT_POL 0x0080 /* GPIO3_EINT_POL */ +#define WM8904_GPIO3_EINT_POL_MASK 0x0080 /* GPIO3_EINT_POL */ +#define WM8904_GPIO3_EINT_POL_SHIFT 7 /* GPIO3_EINT_POL */ +#define WM8904_GPIO3_EINT_POL_WIDTH 1 /* GPIO3_EINT_POL */ +#define WM8904_GPIO2_EINT_POL 0x0040 /* GPIO2_EINT_POL */ +#define WM8904_GPIO2_EINT_POL_MASK 0x0040 /* GPIO2_EINT_POL */ +#define WM8904_GPIO2_EINT_POL_SHIFT 6 /* GPIO2_EINT_POL */ +#define WM8904_GPIO2_EINT_POL_WIDTH 1 /* GPIO2_EINT_POL */ +#define WM8904_GPIO1_EINT_POL 0x0020 /* GPIO1_EINT_POL */ +#define WM8904_GPIO1_EINT_POL_MASK 0x0020 /* GPIO1_EINT_POL */ +#define WM8904_GPIO1_EINT_POL_SHIFT 5 /* GPIO1_EINT_POL */ +#define WM8904_GPIO1_EINT_POL_WIDTH 1 /* GPIO1_EINT_POL */ +#define WM8904_GPI8_EINT_POL 0x0010 /* GPI8_EINT_POL */ +#define WM8904_GPI8_EINT_POL_MASK 0x0010 /* GPI8_EINT_POL */ +#define WM8904_GPI8_EINT_POL_SHIFT 4 /* GPI8_EINT_POL */ +#define WM8904_GPI8_EINT_POL_WIDTH 1 /* GPI8_EINT_POL */ +#define WM8904_GPI7_EINT_POL 0x0008 /* GPI7_EINT_POL */ +#define WM8904_GPI7_EINT_POL_MASK 0x0008 /* GPI7_EINT_POL */ +#define WM8904_GPI7_EINT_POL_SHIFT 3 /* GPI7_EINT_POL */ +#define WM8904_GPI7_EINT_POL_WIDTH 1 /* GPI7_EINT_POL */ +#define WM8904_FLL_LOCK_EINT_POL 0x0004 /* FLL_LOCK_EINT_POL */ +#define WM8904_FLL_LOCK_EINT_POL_MASK 0x0004 /* FLL_LOCK_EINT_POL */ +#define WM8904_FLL_LOCK_EINT_POL_SHIFT 2 /* FLL_LOCK_EINT_POL */ +#define WM8904_FLL_LOCK_EINT_POL_WIDTH 1 /* FLL_LOCK_EINT_POL */ +#define WM8904_MIC_SHRT_EINT_POL 0x0002 /* MIC_SHRT_EINT_POL */ +#define WM8904_MIC_SHRT_EINT_POL_MASK 0x0002 /* MIC_SHRT_EINT_POL */ +#define WM8904_MIC_SHRT_EINT_POL_SHIFT 1 /* MIC_SHRT_EINT_POL */ +#define WM8904_MIC_SHRT_EINT_POL_WIDTH 1 /* MIC_SHRT_EINT_POL */ +#define WM8904_MIC_DET_EINT_POL 0x0001 /* MIC_DET_EINT_POL */ +#define WM8904_MIC_DET_EINT_POL_MASK 0x0001 /* MIC_DET_EINT_POL */ +#define WM8904_MIC_DET_EINT_POL_SHIFT 0 /* MIC_DET_EINT_POL */ +#define WM8904_MIC_DET_EINT_POL_WIDTH 1 /* MIC_DET_EINT_POL */ + +/* + * R130 (0x82) - Interrupt Debounce + */ +#define WM8904_GPIO_BCLK_EINT_DB 0x0200 /* GPIO_BCLK_EINT_DB */ +#define WM8904_GPIO_BCLK_EINT_DB_MASK 0x0200 /* GPIO_BCLK_EINT_DB */ +#define WM8904_GPIO_BCLK_EINT_DB_SHIFT 9 /* GPIO_BCLK_EINT_DB */ +#define WM8904_GPIO_BCLK_EINT_DB_WIDTH 1 /* GPIO_BCLK_EINT_DB */ +#define WM8904_WSEQ_EINT_DB 0x0100 /* WSEQ_EINT_DB */ +#define WM8904_WSEQ_EINT_DB_MASK 0x0100 /* WSEQ_EINT_DB */ +#define WM8904_WSEQ_EINT_DB_SHIFT 8 /* WSEQ_EINT_DB */ +#define WM8904_WSEQ_EINT_DB_WIDTH 1 /* WSEQ_EINT_DB */ +#define WM8904_GPIO3_EINT_DB 0x0080 /* GPIO3_EINT_DB */ +#define WM8904_GPIO3_EINT_DB_MASK 0x0080 /* GPIO3_EINT_DB */ +#define WM8904_GPIO3_EINT_DB_SHIFT 7 /* GPIO3_EINT_DB */ +#define WM8904_GPIO3_EINT_DB_WIDTH 1 /* GPIO3_EINT_DB */ +#define WM8904_GPIO2_EINT_DB 0x0040 /* GPIO2_EINT_DB */ +#define WM8904_GPIO2_EINT_DB_MASK 0x0040 /* GPIO2_EINT_DB */ +#define WM8904_GPIO2_EINT_DB_SHIFT 6 /* GPIO2_EINT_DB */ +#define WM8904_GPIO2_EINT_DB_WIDTH 1 /* GPIO2_EINT_DB */ +#define WM8904_GPIO1_EINT_DB 0x0020 /* GPIO1_EINT_DB */ +#define WM8904_GPIO1_EINT_DB_MASK 0x0020 /* GPIO1_EINT_DB */ +#define WM8904_GPIO1_EINT_DB_SHIFT 5 /* GPIO1_EINT_DB */ +#define WM8904_GPIO1_EINT_DB_WIDTH 1 /* GPIO1_EINT_DB */ +#define WM8904_GPI8_EINT_DB 0x0010 /* GPI8_EINT_DB */ +#define WM8904_GPI8_EINT_DB_MASK 0x0010 /* GPI8_EINT_DB */ +#define WM8904_GPI8_EINT_DB_SHIFT 4 /* GPI8_EINT_DB */ +#define WM8904_GPI8_EINT_DB_WIDTH 1 /* GPI8_EINT_DB */ +#define WM8904_GPI7_EINT_DB 0x0008 /* GPI7_EINT_DB */ +#define WM8904_GPI7_EINT_DB_MASK 0x0008 /* GPI7_EINT_DB */ +#define WM8904_GPI7_EINT_DB_SHIFT 3 /* GPI7_EINT_DB */ +#define WM8904_GPI7_EINT_DB_WIDTH 1 /* GPI7_EINT_DB */ +#define WM8904_FLL_LOCK_EINT_DB 0x0004 /* FLL_LOCK_EINT_DB */ +#define WM8904_FLL_LOCK_EINT_DB_MASK 0x0004 /* FLL_LOCK_EINT_DB */ +#define WM8904_FLL_LOCK_EINT_DB_SHIFT 2 /* FLL_LOCK_EINT_DB */ +#define WM8904_FLL_LOCK_EINT_DB_WIDTH 1 /* FLL_LOCK_EINT_DB */ +#define WM8904_MIC_SHRT_EINT_DB 0x0002 /* MIC_SHRT_EINT_DB */ +#define WM8904_MIC_SHRT_EINT_DB_MASK 0x0002 /* MIC_SHRT_EINT_DB */ +#define WM8904_MIC_SHRT_EINT_DB_SHIFT 1 /* MIC_SHRT_EINT_DB */ +#define WM8904_MIC_SHRT_EINT_DB_WIDTH 1 /* MIC_SHRT_EINT_DB */ +#define WM8904_MIC_DET_EINT_DB 0x0001 /* MIC_DET_EINT_DB */ +#define WM8904_MIC_DET_EINT_DB_MASK 0x0001 /* MIC_DET_EINT_DB */ +#define WM8904_MIC_DET_EINT_DB_SHIFT 0 /* MIC_DET_EINT_DB */ +#define WM8904_MIC_DET_EINT_DB_WIDTH 1 /* MIC_DET_EINT_DB */ + +/* + * R134 (0x86) - EQ1 + */ +#define WM8904_EQ_ENA 0x0001 /* EQ_ENA */ +#define WM8904_EQ_ENA_MASK 0x0001 /* EQ_ENA */ +#define WM8904_EQ_ENA_SHIFT 0 /* EQ_ENA */ +#define WM8904_EQ_ENA_WIDTH 1 /* EQ_ENA */ + +/* + * R135 (0x87) - EQ2 + */ +#define WM8904_EQ_B1_GAIN_MASK 0x001F /* EQ_B1_GAIN - [4:0] */ +#define WM8904_EQ_B1_GAIN_SHIFT 0 /* EQ_B1_GAIN - [4:0] */ +#define WM8904_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [4:0] */ + +/* + * R136 (0x88) - EQ3 + */ +#define WM8904_EQ_B2_GAIN_MASK 0x001F /* EQ_B2_GAIN - [4:0] */ +#define WM8904_EQ_B2_GAIN_SHIFT 0 /* EQ_B2_GAIN - [4:0] */ +#define WM8904_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [4:0] */ + +/* + * R137 (0x89) - EQ4 + */ +#define WM8904_EQ_B3_GAIN_MASK 0x001F /* EQ_B3_GAIN - [4:0] */ +#define WM8904_EQ_B3_GAIN_SHIFT 0 /* EQ_B3_GAIN - [4:0] */ +#define WM8904_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [4:0] */ + +/* + * R138 (0x8A) - EQ5 + */ +#define WM8904_EQ_B4_GAIN_MASK 0x001F /* EQ_B4_GAIN - [4:0] */ +#define WM8904_EQ_B4_GAIN_SHIFT 0 /* EQ_B4_GAIN - [4:0] */ +#define WM8904_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [4:0] */ + +/* + * R139 (0x8B) - EQ6 + */ +#define WM8904_EQ_B5_GAIN_MASK 0x001F /* EQ_B5_GAIN - [4:0] */ +#define WM8904_EQ_B5_GAIN_SHIFT 0 /* EQ_B5_GAIN - [4:0] */ +#define WM8904_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [4:0] */ + +/* + * R140 (0x8C) - EQ7 + */ +#define WM8904_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */ +#define WM8904_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */ +#define WM8904_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */ + +/* + * R141 (0x8D) - EQ8 + */ +#define WM8904_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */ +#define WM8904_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */ +#define WM8904_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */ + +/* + * R142 (0x8E) - EQ9 + */ +#define WM8904_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */ +#define WM8904_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */ +#define WM8904_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */ + +/* + * R143 (0x8F) - EQ10 + */ +#define WM8904_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */ +#define WM8904_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */ +#define WM8904_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */ + +/* + * R144 (0x90) - EQ11 + */ +#define WM8904_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */ +#define WM8904_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */ +#define WM8904_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */ + +/* + * R145 (0x91) - EQ12 + */ +#define WM8904_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */ +#define WM8904_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */ +#define WM8904_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */ + +/* + * R146 (0x92) - EQ13 + */ +#define WM8904_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */ +#define WM8904_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */ +#define WM8904_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */ + +/* + * R147 (0x93) - EQ14 + */ +#define WM8904_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */ +#define WM8904_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */ +#define WM8904_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */ + +/* + * R148 (0x94) - EQ15 + */ +#define WM8904_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */ +#define WM8904_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */ +#define WM8904_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */ + +/* + * R149 (0x95) - EQ16 + */ +#define WM8904_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */ +#define WM8904_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */ +#define WM8904_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */ + +/* + * R150 (0x96) - EQ17 + */ +#define WM8904_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */ +#define WM8904_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */ +#define WM8904_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */ + +/* + * R151 (0x97) - EQ18 + */ +#define WM8904_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */ +#define WM8904_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */ +#define WM8904_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */ + +/* + * R152 (0x98) - EQ19 + */ +#define WM8904_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */ +#define WM8904_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */ +#define WM8904_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */ + +/* + * R153 (0x99) - EQ20 + */ +#define WM8904_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */ +#define WM8904_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */ +#define WM8904_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */ + +/* + * R154 (0x9A) - EQ21 + */ +#define WM8904_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */ +#define WM8904_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */ +#define WM8904_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */ + +/* + * R155 (0x9B) - EQ22 + */ +#define WM8904_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */ +#define WM8904_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */ +#define WM8904_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */ + +/* + * R156 (0x9C) - EQ23 + */ +#define WM8904_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */ +#define WM8904_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */ +#define WM8904_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */ + +/* + * R157 (0x9D) - EQ24 + */ +#define WM8904_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */ +#define WM8904_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */ +#define WM8904_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */ + +/* + * R161 (0xA1) - Control Interface Test 1 + */ +#define WM8904_USER_KEY 0x0002 /* USER_KEY */ +#define WM8904_USER_KEY_MASK 0x0002 /* USER_KEY */ +#define WM8904_USER_KEY_SHIFT 1 /* USER_KEY */ +#define WM8904_USER_KEY_WIDTH 1 /* USER_KEY */ + +/* + * R198 (0xC6) - ADC Test 0 + */ +#define WM8904_ADC_128_OSR_TST_MODE 0x0004 /* ADC_128_OSR_TST_MODE */ +#define WM8904_ADC_128_OSR_TST_MODE_SHIFT 2 /* ADC_128_OSR_TST_MODE */ +#define WM8904_ADC_128_OSR_TST_MODE_WIDTH 1 /* ADC_128_OSR_TST_MODE */ +#define WM8904_ADC_BIASX1P5 0x0001 /* ADC_BIASX1P5 */ +#define WM8904_ADC_BIASX1P5_SHIFT 0 /* ADC_BIASX1P5 */ +#define WM8904_ADC_BIASX1P5_WIDTH 1 /* ADC_BIASX1P5 */ + +/* + * R204 (0xCC) - Analogue Output Bias 0 + */ +#define WM8904_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */ +#define WM8904_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */ +#define WM8904_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */ + +/* + * R247 (0xF7) - FLL NCO Test 0 + */ +#define WM8904_FLL_FRC_NCO 0x0001 /* FLL_FRC_NCO */ +#define WM8904_FLL_FRC_NCO_MASK 0x0001 /* FLL_FRC_NCO */ +#define WM8904_FLL_FRC_NCO_SHIFT 0 /* FLL_FRC_NCO */ +#define WM8904_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */ + +/* + * R248 (0xF8) - FLL NCO Test 1 + */ +#define WM8904_FLL_FRC_NCO_VAL_MASK 0x003F /* FLL_FRC_NCO_VAL - [5:0] */ +#define WM8904_FLL_FRC_NCO_VAL_SHIFT 0 /* FLL_FRC_NCO_VAL - [5:0] */ +#define WM8904_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [5:0] */ + +#endif diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c new file mode 100644 index 000000000..be4fce0c7 --- /dev/null +++ b/sound/soc/codecs/wm8940.c @@ -0,0 +1,796 @@ +/* + * wm8940.c -- WM8940 ALSA Soc Audio driver + * + * Author: Jonathan Cameron + * + * Based on wm8510.c + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Not currently handled: + * Notch filter control + * AUXMode (inverting vs mixer) + * No means to obtain current gain if alc enabled. + * No use made of gpio + * Fast VMID discharge for power down + * Soft Start + * DLR and ALR Swaps not enabled + * Digital Sidetone not supported + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8940.h" + +struct wm8940_priv { + unsigned int sysclk; + struct regmap *regmap; +}; + +static bool wm8940_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8940_SOFTRESET: + return true; + default: + return false; + } +} + +static bool wm8940_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8940_SOFTRESET: + case WM8940_POWER1: + case WM8940_POWER2: + case WM8940_POWER3: + case WM8940_IFACE: + case WM8940_COMPANDINGCTL: + case WM8940_CLOCK: + case WM8940_ADDCNTRL: + case WM8940_GPIO: + case WM8940_CTLINT: + case WM8940_DAC: + case WM8940_DACVOL: + case WM8940_ADC: + case WM8940_ADCVOL: + case WM8940_NOTCH1: + case WM8940_NOTCH2: + case WM8940_NOTCH3: + case WM8940_NOTCH4: + case WM8940_NOTCH5: + case WM8940_NOTCH6: + case WM8940_NOTCH7: + case WM8940_NOTCH8: + case WM8940_DACLIM1: + case WM8940_DACLIM2: + case WM8940_ALC1: + case WM8940_ALC2: + case WM8940_ALC3: + case WM8940_NOISEGATE: + case WM8940_PLLN: + case WM8940_PLLK1: + case WM8940_PLLK2: + case WM8940_PLLK3: + case WM8940_ALC4: + case WM8940_INPUTCTL: + case WM8940_PGAGAIN: + case WM8940_ADCBOOST: + case WM8940_OUTPUTCTL: + case WM8940_SPKMIX: + case WM8940_SPKVOL: + case WM8940_MONOMIX: + return true; + default: + return false; + } +} + +static const struct reg_default wm8940_reg_defaults[] = { + { 0x1, 0x0000 }, /* Power 1 */ + { 0x2, 0x0000 }, /* Power 2 */ + { 0x3, 0x0000 }, /* Power 3 */ + { 0x4, 0x0010 }, /* Interface Control */ + { 0x5, 0x0000 }, /* Companding Control */ + { 0x6, 0x0140 }, /* Clock Control */ + { 0x7, 0x0000 }, /* Additional Controls */ + { 0x8, 0x0000 }, /* GPIO Control */ + { 0x9, 0x0002 }, /* Auto Increment Control */ + { 0xa, 0x0000 }, /* DAC Control */ + { 0xb, 0x00FF }, /* DAC Volume */ + + { 0xe, 0x0100 }, /* ADC Control */ + { 0xf, 0x00FF }, /* ADC Volume */ + { 0x10, 0x0000 }, /* Notch Filter 1 Control 1 */ + { 0x11, 0x0000 }, /* Notch Filter 1 Control 2 */ + { 0x12, 0x0000 }, /* Notch Filter 2 Control 1 */ + { 0x13, 0x0000 }, /* Notch Filter 2 Control 2 */ + { 0x14, 0x0000 }, /* Notch Filter 3 Control 1 */ + { 0x15, 0x0000 }, /* Notch Filter 3 Control 2 */ + { 0x16, 0x0000 }, /* Notch Filter 4 Control 1 */ + { 0x17, 0x0000 }, /* Notch Filter 4 Control 2 */ + { 0x18, 0x0032 }, /* DAC Limit Control 1 */ + { 0x19, 0x0000 }, /* DAC Limit Control 2 */ + + { 0x20, 0x0038 }, /* ALC Control 1 */ + { 0x21, 0x000B }, /* ALC Control 2 */ + { 0x22, 0x0032 }, /* ALC Control 3 */ + { 0x23, 0x0000 }, /* Noise Gate */ + { 0x24, 0x0041 }, /* PLLN */ + { 0x25, 0x000C }, /* PLLK1 */ + { 0x26, 0x0093 }, /* PLLK2 */ + { 0x27, 0x00E9 }, /* PLLK3 */ + + { 0x2a, 0x0030 }, /* ALC Control 4 */ + + { 0x2c, 0x0002 }, /* Input Control */ + { 0x2d, 0x0050 }, /* PGA Gain */ + + { 0x2f, 0x0002 }, /* ADC Boost Control */ + + { 0x31, 0x0002 }, /* Output Control */ + { 0x32, 0x0000 }, /* Speaker Mixer Control */ + + { 0x36, 0x0079 }, /* Speaker Volume */ + + { 0x38, 0x0000 }, /* Mono Mixer Control */ +}; + +static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" }; +static SOC_ENUM_SINGLE_DECL(wm8940_adc_companding_enum, + WM8940_COMPANDINGCTL, 1, wm8940_companding); +static SOC_ENUM_SINGLE_DECL(wm8940_dac_companding_enum, + WM8940_COMPANDINGCTL, 3, wm8940_companding); + +static const char *wm8940_alc_mode_text[] = {"ALC", "Limiter"}; +static SOC_ENUM_SINGLE_DECL(wm8940_alc_mode_enum, + WM8940_ALC3, 8, wm8940_alc_mode_text); + +static const char *wm8940_mic_bias_level_text[] = {"0.9", "0.65"}; +static SOC_ENUM_SINGLE_DECL(wm8940_mic_bias_level_enum, + WM8940_INPUTCTL, 8, wm8940_mic_bias_level_text); + +static const char *wm8940_filter_mode_text[] = {"Audio", "Application"}; +static SOC_ENUM_SINGLE_DECL(wm8940_filter_mode_enum, + WM8940_ADC, 7, wm8940_filter_mode_text); + +static DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1); +static DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0); +static DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0); +static DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0); +static DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1); +static DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0); + +static const struct snd_kcontrol_new wm8940_snd_controls[] = { + SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL, + 6, 1, 0), + SOC_ENUM("DAC Companding", wm8940_dac_companding_enum), + SOC_ENUM("ADC Companding", wm8940_adc_companding_enum), + + SOC_ENUM("ALC Mode", wm8940_alc_mode_enum), + SOC_SINGLE("ALC Switch", WM8940_ALC1, 8, 1, 0), + SOC_SINGLE_TLV("ALC Capture Max Gain", WM8940_ALC1, + 3, 7, 1, wm8940_alc_max_tlv), + SOC_SINGLE_TLV("ALC Capture Min Gain", WM8940_ALC1, + 0, 7, 0, wm8940_alc_min_tlv), + SOC_SINGLE_TLV("ALC Capture Target", WM8940_ALC2, + 0, 14, 0, wm8940_alc_tar_tlv), + SOC_SINGLE("ALC Capture Hold", WM8940_ALC2, 4, 10, 0), + SOC_SINGLE("ALC Capture Decay", WM8940_ALC3, 4, 10, 0), + SOC_SINGLE("ALC Capture Attach", WM8940_ALC3, 0, 10, 0), + SOC_SINGLE("ALC ZC Switch", WM8940_ALC4, 1, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Switch", WM8940_NOISEGATE, + 3, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8940_NOISEGATE, + 0, 7, 0), + + SOC_SINGLE("DAC Playback Limiter Switch", WM8940_DACLIM1, 8, 1, 0), + SOC_SINGLE("DAC Playback Limiter Attack", WM8940_DACLIM1, 0, 9, 0), + SOC_SINGLE("DAC Playback Limiter Decay", WM8940_DACLIM1, 4, 11, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8940_DACLIM2, + 4, 9, 1, wm8940_lim_thresh_tlv), + SOC_SINGLE_TLV("DAC Playback Limiter Boost", WM8940_DACLIM2, + 0, 12, 0, wm8940_lim_boost_tlv), + + SOC_SINGLE("Capture PGA ZC Switch", WM8940_PGAGAIN, 7, 1, 0), + SOC_SINGLE_TLV("Capture PGA Volume", WM8940_PGAGAIN, + 0, 63, 0, wm8940_pga_vol_tlv), + SOC_SINGLE_TLV("Digital Playback Volume", WM8940_DACVOL, + 0, 255, 0, wm8940_adc_tlv), + SOC_SINGLE_TLV("Digital Capture Volume", WM8940_ADCVOL, + 0, 255, 0, wm8940_adc_tlv), + SOC_ENUM("Mic Bias Level", wm8940_mic_bias_level_enum), + SOC_SINGLE_TLV("Capture Boost Volue", WM8940_ADCBOOST, + 8, 1, 0, wm8940_capture_boost_vol_tlv), + SOC_SINGLE_TLV("Speaker Playback Volume", WM8940_SPKVOL, + 0, 63, 0, wm8940_spk_vol_tlv), + SOC_SINGLE("Speaker Playback Switch", WM8940_SPKVOL, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Mixer Line Bypass Volume", WM8940_SPKVOL, + 8, 1, 1, wm8940_att_tlv), + SOC_SINGLE("Speaker Playback ZC Switch", WM8940_SPKVOL, 7, 1, 0), + + SOC_SINGLE("Mono Out Switch", WM8940_MONOMIX, 6, 1, 1), + SOC_SINGLE_TLV("Mono Mixer Line Bypass Volume", WM8940_MONOMIX, + 7, 1, 1, wm8940_att_tlv), + + SOC_SINGLE("High Pass Filter Switch", WM8940_ADC, 8, 1, 0), + SOC_ENUM("High Pass Filter Mode", wm8940_filter_mode_enum), + SOC_SINGLE("High Pass Filter Cut Off", WM8940_ADC, 4, 7, 0), + SOC_SINGLE("ADC Inversion Switch", WM8940_ADC, 0, 1, 0), + SOC_SINGLE("DAC Inversion Switch", WM8940_DAC, 0, 1, 0), + SOC_SINGLE("DAC Auto Mute Switch", WM8940_DAC, 2, 1, 0), + SOC_SINGLE("ZC Timeout Clock Switch", WM8940_ADDCNTRL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8940_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_SPKMIX, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_SPKMIX, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_SPKMIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_MONOMIX, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_MONOMIX, 2, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0), +}; + +static DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1); +static const struct snd_kcontrol_new wm8940_input_boost_controls[] = { + SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1), + SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST, + 0, 7, 0, wm8940_boost_vol_tlv), + SOC_DAPM_SINGLE_TLV("Mic Volume", WM8940_ADCBOOST, + 4, 7, 0, wm8940_boost_vol_tlv), +}; + +static const struct snd_kcontrol_new wm8940_micpga_controls[] = { + SOC_DAPM_SINGLE("AUX Switch", WM8940_INPUTCTL, 2, 1, 0), + SOC_DAPM_SINGLE("MICP Switch", WM8940_INPUTCTL, 0, 1, 0), + SOC_DAPM_SINGLE("MICN Switch", WM8940_INPUTCTL, 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8940_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Speaker Mixer", WM8940_POWER3, 2, 0, + &wm8940_speaker_mixer_controls[0], + ARRAY_SIZE(wm8940_speaker_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8940_POWER3, 3, 0, + &wm8940_mono_mixer_controls[0], + ARRAY_SIZE(wm8940_mono_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8940_POWER3, 0, 0), + + SND_SOC_DAPM_PGA("SpkN Out", WM8940_POWER3, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("SpkP Out", WM8940_POWER3, 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", WM8940_POWER3, 7, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_OUTPUT("SPKOUTP"), + SND_SOC_DAPM_OUTPUT("SPKOUTN"), + + SND_SOC_DAPM_PGA("Aux Input", WM8940_POWER1, 6, 0, NULL, 0), + SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8940_POWER2, 0, 0), + SND_SOC_DAPM_MIXER("Mic PGA", WM8940_POWER2, 2, 0, + &wm8940_micpga_controls[0], + ARRAY_SIZE(wm8940_micpga_controls)), + SND_SOC_DAPM_MIXER("Boost Mixer", WM8940_POWER2, 4, 0, + &wm8940_input_boost_controls[0], + ARRAY_SIZE(wm8940_input_boost_controls)), + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8940_POWER1, 4, 0), + + SND_SOC_DAPM_INPUT("MICN"), + SND_SOC_DAPM_INPUT("MICP"), + SND_SOC_DAPM_INPUT("AUX"), +}; + +static const struct snd_soc_dapm_route wm8940_dapm_routes[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Microphone PGA */ + {"Mic PGA", "MICN Switch", "MICN"}, + {"Mic PGA", "MICP Switch", "MICP"}, + {"Mic PGA", "AUX Switch", "AUX"}, + + /* Boost Mixer */ + {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, + {"Boost Mixer", "Mic Volume", "MICP"}, + {"Boost Mixer", "Aux Volume", "Aux Input"}, + + {"ADC", NULL, "Boost Mixer"}, +}; + +#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0); + +static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = snd_soc_component_read32(component, WM8940_IFACE) & 0xFE67; + u16 clk = snd_soc_component_read32(component, WM8940_CLOCK) & 0x1fe; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + snd_soc_component_write(component, WM8940_CLOCK, clk); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= (2 << 3); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= (1 << 3); + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= (3 << 3); + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= (3 << 3) | (1 << 7); + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= (1 << 7); + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= (1 << 8); + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= (1 << 8) | (1 << 7); + break; + } + + snd_soc_component_write(component, WM8940_IFACE, iface); + + return 0; +} + +static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 iface = snd_soc_component_read32(component, WM8940_IFACE) & 0xFD9F; + u16 addcntrl = snd_soc_component_read32(component, WM8940_ADDCNTRL) & 0xFFF1; + u16 companding = snd_soc_component_read32(component, + WM8940_COMPANDINGCTL) & 0xFFDF; + int ret; + + /* LoutR control */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE + && params_channels(params) == 2) + iface |= (1 << 9); + + switch (params_rate(params)) { + case 8000: + addcntrl |= (0x5 << 1); + break; + case 11025: + addcntrl |= (0x4 << 1); + break; + case 16000: + addcntrl |= (0x3 << 1); + break; + case 22050: + addcntrl |= (0x2 << 1); + break; + case 32000: + addcntrl |= (0x1 << 1); + break; + case 44100: + case 48000: + break; + } + ret = snd_soc_component_write(component, WM8940_ADDCNTRL, addcntrl); + if (ret) + goto error_ret; + + switch (params_width(params)) { + case 8: + companding = companding | (1 << 5); + break; + case 16: + break; + case 20: + iface |= (1 << 5); + break; + case 24: + iface |= (2 << 5); + break; + case 32: + iface |= (3 << 5); + break; + } + ret = snd_soc_component_write(component, WM8940_COMPANDINGCTL, companding); + if (ret) + goto error_ret; + ret = snd_soc_component_write(component, WM8940_IFACE, iface); + +error_ret: + return ret; +} + +static int wm8940_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8940_DAC) & 0xffbf; + + if (mute) + mute_reg |= 0x40; + + return snd_soc_component_write(component, WM8940_DAC, mute_reg); +} + +static int wm8940_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8940_priv *wm8940 = snd_soc_component_get_drvdata(component); + u16 val; + u16 pwr_reg = snd_soc_component_read32(component, WM8940_POWER1) & 0x1F0; + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + /* Enable thermal shutdown */ + val = snd_soc_component_read32(component, WM8940_OUTPUTCTL); + ret = snd_soc_component_write(component, WM8940_OUTPUTCTL, val | 0x2); + if (ret) + break; + /* set vmid to 75k */ + ret = snd_soc_component_write(component, WM8940_POWER1, pwr_reg | 0x1); + break; + case SND_SOC_BIAS_PREPARE: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + ret = snd_soc_component_write(component, WM8940_POWER1, pwr_reg | 0x1); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regcache_sync(wm8940->regmap); + if (ret < 0) { + dev_err(component->dev, "Failed to sync cache: %d\n", ret); + return ret; + } + } + + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + /* set vmid to 300k for standby */ + ret = snd_soc_component_write(component, WM8940_POWER1, pwr_reg | 0x2); + break; + case SND_SOC_BIAS_OFF: + ret = snd_soc_component_write(component, WM8940_POWER1, pwr_reg); + break; + } + + return ret; +} + +struct pll_ { + unsigned int pre_scale:2; + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + /* The left shift ist to avoid accuracy loss when right shifting */ + Ndiv = target / source; + + if (Ndiv > 12) { + source <<= 1; + /* Multiply by 2 */ + pll_div.pre_scale = 0; + Ndiv = target / source; + } else if (Ndiv < 3) { + source >>= 2; + /* Divide by 4 */ + pll_div.pre_scale = 3; + Ndiv = target / source; + } else if (Ndiv < 6) { + source >>= 1; + /* divide by 2 */ + pll_div.pre_scale = 2; + Ndiv = target / source; + } else + pll_div.pre_scale = 1; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8940 N value %d outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +/* Untested at the moment */ +static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + /* Turn off PLL */ + reg = snd_soc_component_read32(component, WM8940_POWER1); + snd_soc_component_write(component, WM8940_POWER1, reg & 0x1df); + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = snd_soc_component_read32(component, WM8940_CLOCK); + snd_soc_component_write(component, WM8940_CLOCK, reg & 0x0ff); + /* Pll power down */ + snd_soc_component_write(component, WM8940_PLLN, (1 << 7)); + return 0; + } + + /* Pll is followed by a frequency divide by 4 */ + pll_factors(freq_out*4, freq_in); + if (pll_div.k) + snd_soc_component_write(component, WM8940_PLLN, + (pll_div.pre_scale << 4) | pll_div.n | (1 << 6)); + else /* No factional component */ + snd_soc_component_write(component, WM8940_PLLN, + (pll_div.pre_scale << 4) | pll_div.n); + snd_soc_component_write(component, WM8940_PLLK1, pll_div.k >> 18); + snd_soc_component_write(component, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff); + snd_soc_component_write(component, WM8940_PLLK3, pll_div.k & 0x1ff); + /* Enable the PLL */ + reg = snd_soc_component_read32(component, WM8940_POWER1); + snd_soc_component_write(component, WM8940_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = snd_soc_component_read32(component, WM8940_CLOCK); + snd_soc_component_write(component, WM8940_CLOCK, reg | 0x100); + + return 0; +} + +static int wm8940_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8940_priv *wm8940 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8940->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + int ret = 0; + + switch (div_id) { + case WM8940_BCLKDIV: + reg = snd_soc_component_read32(component, WM8940_CLOCK) & 0xFFE3; + ret = snd_soc_component_write(component, WM8940_CLOCK, reg | (div << 2)); + break; + case WM8940_MCLKDIV: + reg = snd_soc_component_read32(component, WM8940_CLOCK) & 0xFF1F; + ret = snd_soc_component_write(component, WM8940_CLOCK, reg | (div << 5)); + break; + case WM8940_OPCLKDIV: + reg = snd_soc_component_read32(component, WM8940_GPIO) & 0xFFCF; + ret = snd_soc_component_write(component, WM8940_GPIO, reg | (div << 4)); + break; + } + return ret; +} + +#define WM8940_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8940_dai_ops = { + .hw_params = wm8940_i2s_hw_params, + .set_sysclk = wm8940_set_dai_sysclk, + .digital_mute = wm8940_mute, + .set_fmt = wm8940_set_dai_fmt, + .set_clkdiv = wm8940_set_dai_clkdiv, + .set_pll = wm8940_set_dai_pll, +}; + +static struct snd_soc_dai_driver wm8940_dai = { + .name = "wm8940-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8940_RATES, + .formats = WM8940_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8940_RATES, + .formats = WM8940_FORMATS, + }, + .ops = &wm8940_dai_ops, + .symmetric_rates = 1, +}; + +static int wm8940_probe(struct snd_soc_component *component) +{ + struct wm8940_setup_data *pdata = component->dev->platform_data; + int ret; + u16 reg; + + ret = wm8940_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset\n"); + return ret; + } + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_component_write(component, WM8940_POWER1, 0x180); + if (ret < 0) + return ret; + + if (!pdata) + dev_warn(component->dev, "No platform data supplied\n"); + else { + reg = snd_soc_component_read32(component, WM8940_OUTPUTCTL); + ret = snd_soc_component_write(component, WM8940_OUTPUTCTL, reg | pdata->vroi); + if (ret < 0) + return ret; + } + + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8940 = { + .probe = wm8940_probe, + .set_bias_level = wm8940_set_bias_level, + .controls = wm8940_snd_controls, + .num_controls = ARRAY_SIZE(wm8940_snd_controls), + .dapm_widgets = wm8940_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8940_dapm_widgets), + .dapm_routes = wm8940_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8940_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8940_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM8940_MONOMIX, + .reg_defaults = wm8940_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8940_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .readable_reg = wm8940_readable_register, + .volatile_reg = wm8940_volatile_register, +}; + +static int wm8940_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8940_priv *wm8940; + int ret; + + wm8940 = devm_kzalloc(&i2c->dev, sizeof(struct wm8940_priv), + GFP_KERNEL); + if (wm8940 == NULL) + return -ENOMEM; + + wm8940->regmap = devm_regmap_init_i2c(i2c, &wm8940_regmap); + if (IS_ERR(wm8940->regmap)) + return PTR_ERR(wm8940->regmap); + + i2c_set_clientdata(i2c, wm8940); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8940, &wm8940_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8940_i2c_id[] = { + { "wm8940", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8940_i2c_id); + +static struct i2c_driver wm8940_i2c_driver = { + .driver = { + .name = "wm8940", + }, + .probe = wm8940_i2c_probe, + .id_table = wm8940_i2c_id, +}; + +module_i2c_driver(wm8940_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8940 driver"); +MODULE_AUTHOR("Jonathan Cameron"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8940.h b/sound/soc/codecs/wm8940.h new file mode 100644 index 000000000..907fe192e --- /dev/null +++ b/sound/soc/codecs/wm8940.h @@ -0,0 +1,102 @@ +/* + * wm8940.h -- WM8940 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8940_H +#define _WM8940_H + +struct wm8940_setup_data { + /* Vref to analogue output resistance */ +#define WM8940_VROI_1K 0 +#define WM8940_VROI_30K 1 + unsigned int vroi:1; +}; + +/* WM8940 register space */ +#define WM8940_SOFTRESET 0x00 +#define WM8940_POWER1 0x01 +#define WM8940_POWER2 0x02 +#define WM8940_POWER3 0x03 +#define WM8940_IFACE 0x04 +#define WM8940_COMPANDINGCTL 0x05 +#define WM8940_CLOCK 0x06 +#define WM8940_ADDCNTRL 0x07 +#define WM8940_GPIO 0x08 +#define WM8940_CTLINT 0x09 +#define WM8940_DAC 0x0A +#define WM8940_DACVOL 0x0B + +#define WM8940_ADC 0x0E +#define WM8940_ADCVOL 0x0F +#define WM8940_NOTCH1 0x10 +#define WM8940_NOTCH2 0x11 +#define WM8940_NOTCH3 0x12 +#define WM8940_NOTCH4 0x13 +#define WM8940_NOTCH5 0x14 +#define WM8940_NOTCH6 0x15 +#define WM8940_NOTCH7 0x16 +#define WM8940_NOTCH8 0x17 +#define WM8940_DACLIM1 0x18 +#define WM8940_DACLIM2 0x19 + +#define WM8940_ALC1 0x20 +#define WM8940_ALC2 0x21 +#define WM8940_ALC3 0x22 +#define WM8940_NOISEGATE 0x23 +#define WM8940_PLLN 0x24 +#define WM8940_PLLK1 0x25 +#define WM8940_PLLK2 0x26 +#define WM8940_PLLK3 0x27 + +#define WM8940_ALC4 0x2A + +#define WM8940_INPUTCTL 0x2C +#define WM8940_PGAGAIN 0x2D + +#define WM8940_ADCBOOST 0x2F + +#define WM8940_OUTPUTCTL 0x31 +#define WM8940_SPKMIX 0x32 + +#define WM8940_SPKVOL 0x36 + +#define WM8940_MONOMIX 0x38 + +#define WM8940_CACHEREGNUM 0x57 + + +/* Clock divider Id's */ +#define WM8940_BCLKDIV 0 +#define WM8940_MCLKDIV 1 +#define WM8940_OPCLKDIV 2 + +/* MCLK clock dividers */ +#define WM8940_MCLKDIV_1 0 +#define WM8940_MCLKDIV_1_5 1 +#define WM8940_MCLKDIV_2 2 +#define WM8940_MCLKDIV_3 3 +#define WM8940_MCLKDIV_4 4 +#define WM8940_MCLKDIV_6 5 +#define WM8940_MCLKDIV_8 6 +#define WM8940_MCLKDIV_12 7 + +/* BCLK clock dividers */ +#define WM8940_BCLKDIV_1 0 +#define WM8940_BCLKDIV_2 1 +#define WM8940_BCLKDIV_4 2 +#define WM8940_BCLKDIV_8 3 +#define WM8940_BCLKDIV_16 4 +#define WM8940_BCLKDIV_32 5 + +/* PLL Out Dividers */ +#define WM8940_OPCLKDIV_1 0 +#define WM8940_OPCLKDIV_2 1 +#define WM8940_OPCLKDIV_3 2 +#define WM8940_OPCLKDIV_4 3 + +#endif /* _WM8940_H */ + diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c new file mode 100644 index 000000000..cd204f796 --- /dev/null +++ b/sound/soc/codecs/wm8955.c @@ -0,0 +1,1018 @@ +/* + * wm8955.c -- WM8955 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8955.h" + +#define WM8955_NUM_SUPPLIES 4 +static const char *wm8955_supply_names[WM8955_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "HPVDD", + "AVDD", +}; + +/* codec private data */ +struct wm8955_priv { + struct regmap *regmap; + + unsigned int mclk_rate; + + int deemph; + int fs; + + struct regulator_bulk_data supplies[WM8955_NUM_SUPPLIES]; +}; + +static const struct reg_default wm8955_reg_defaults[] = { + { 2, 0x0079 }, /* R2 - LOUT1 volume */ + { 3, 0x0079 }, /* R3 - ROUT1 volume */ + { 5, 0x0008 }, /* R5 - DAC Control */ + { 7, 0x000A }, /* R7 - Audio Interface */ + { 8, 0x0000 }, /* R8 - Sample Rate */ + { 10, 0x00FF }, /* R10 - Left DAC volume */ + { 11, 0x00FF }, /* R11 - Right DAC volume */ + { 12, 0x000F }, /* R12 - Bass control */ + { 13, 0x000F }, /* R13 - Treble control */ + { 23, 0x00C1 }, /* R23 - Additional control (1) */ + { 24, 0x0000 }, /* R24 - Additional control (2) */ + { 25, 0x0000 }, /* R25 - Power Management (1) */ + { 26, 0x0000 }, /* R26 - Power Management (2) */ + { 27, 0x0000 }, /* R27 - Additional Control (3) */ + { 34, 0x0050 }, /* R34 - Left out Mix (1) */ + { 35, 0x0050 }, /* R35 - Left out Mix (2) */ + { 36, 0x0050 }, /* R36 - Right out Mix (1) */ + { 37, 0x0050 }, /* R37 - Right Out Mix (2) */ + { 38, 0x0050 }, /* R38 - Mono out Mix (1) */ + { 39, 0x0050 }, /* R39 - Mono out Mix (2) */ + { 40, 0x0079 }, /* R40 - LOUT2 volume */ + { 41, 0x0079 }, /* R41 - ROUT2 volume */ + { 42, 0x0079 }, /* R42 - MONOOUT volume */ + { 43, 0x0000 }, /* R43 - Clocking / PLL */ + { 44, 0x0103 }, /* R44 - PLL Control 1 */ + { 45, 0x0024 }, /* R45 - PLL Control 2 */ + { 46, 0x01BA }, /* R46 - PLL Control 3 */ + { 59, 0x0000 }, /* R59 - PLL Control 4 */ +}; + +static bool wm8955_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8955_LOUT1_VOLUME: + case WM8955_ROUT1_VOLUME: + case WM8955_DAC_CONTROL: + case WM8955_AUDIO_INTERFACE: + case WM8955_SAMPLE_RATE: + case WM8955_LEFT_DAC_VOLUME: + case WM8955_RIGHT_DAC_VOLUME: + case WM8955_BASS_CONTROL: + case WM8955_TREBLE_CONTROL: + case WM8955_RESET: + case WM8955_ADDITIONAL_CONTROL_1: + case WM8955_ADDITIONAL_CONTROL_2: + case WM8955_POWER_MANAGEMENT_1: + case WM8955_POWER_MANAGEMENT_2: + case WM8955_ADDITIONAL_CONTROL_3: + case WM8955_LEFT_OUT_MIX_1: + case WM8955_LEFT_OUT_MIX_2: + case WM8955_RIGHT_OUT_MIX_1: + case WM8955_RIGHT_OUT_MIX_2: + case WM8955_MONO_OUT_MIX_1: + case WM8955_MONO_OUT_MIX_2: + case WM8955_LOUT2_VOLUME: + case WM8955_ROUT2_VOLUME: + case WM8955_MONOOUT_VOLUME: + case WM8955_CLOCKING_PLL: + case WM8955_PLL_CONTROL_1: + case WM8955_PLL_CONTROL_2: + case WM8955_PLL_CONTROL_3: + case WM8955_PLL_CONTROL_4: + return true; + default: + return false; + } +} + +static bool wm8955_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8955_RESET: + return true; + default: + return false; + } +} + +static int wm8955_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, WM8955_RESET, 0); +} + +struct pll_factors { + int n; + int k; + int outdiv; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 22) * 10) + +static int wm8995_pll_factors(struct device *dev, + int Fref, int Fout, struct pll_factors *pll) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + + dev_dbg(dev, "Fref=%u Fout=%u\n", Fref, Fout); + + /* The oscilator should run at should be 90-100MHz, and + * there's a divide by 4 plus an optional divide by 2 in the + * output path to generate the system clock. The clock table + * is sortd so we should always generate a suitable target. */ + target = Fout * 4; + if (target < 90000000) { + pll->outdiv = 1; + target *= 2; + } else { + pll->outdiv = 0; + } + + WARN_ON(target < 90000000 || target > 100000000); + + dev_dbg(dev, "Fvco=%dHz\n", target); + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + pll->n = Ndiv; + Nmod = target % Fref; + dev_dbg(dev, "Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + pll->k = K / 10; + + dev_dbg(dev, "N=%x K=%x OUTDIV=%x\n", pll->n, pll->k, pll->outdiv); + + return 0; +} + +/* Lookup table specifying SRATE (table 25 in datasheet); some of the + * output frequencies have been rounded to the standard frequencies + * they are intended to match where the error is slight. */ +static struct { + int mclk; + int fs; + int usb; + int sr; +} clock_cfgs[] = { + { 18432000, 8000, 0, 3, }, + { 18432000, 12000, 0, 9, }, + { 18432000, 16000, 0, 11, }, + { 18432000, 24000, 0, 29, }, + { 18432000, 32000, 0, 13, }, + { 18432000, 48000, 0, 1, }, + { 18432000, 96000, 0, 15, }, + + { 16934400, 8018, 0, 19, }, + { 16934400, 11025, 0, 25, }, + { 16934400, 22050, 0, 27, }, + { 16934400, 44100, 0, 17, }, + { 16934400, 88200, 0, 31, }, + + { 12000000, 8000, 1, 2, }, + { 12000000, 11025, 1, 25, }, + { 12000000, 12000, 1, 8, }, + { 12000000, 16000, 1, 10, }, + { 12000000, 22050, 1, 27, }, + { 12000000, 24000, 1, 28, }, + { 12000000, 32000, 1, 12, }, + { 12000000, 44100, 1, 17, }, + { 12000000, 48000, 1, 0, }, + { 12000000, 88200, 1, 31, }, + { 12000000, 96000, 1, 14, }, + + { 12288000, 8000, 0, 2, }, + { 12288000, 12000, 0, 8, }, + { 12288000, 16000, 0, 10, }, + { 12288000, 24000, 0, 28, }, + { 12288000, 32000, 0, 12, }, + { 12288000, 48000, 0, 0, }, + { 12288000, 96000, 0, 14, }, + + { 12289600, 8018, 0, 18, }, + { 12289600, 11025, 0, 24, }, + { 12289600, 22050, 0, 26, }, + { 11289600, 44100, 0, 16, }, + { 11289600, 88200, 0, 31, }, +}; + +static int wm8955_configure_clocking(struct snd_soc_component *component) +{ + struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); + int i, ret, val; + int clocking = 0; + int srate = 0; + int sr = -1; + struct pll_factors pll; + + /* If we're not running a sample rate currently just pick one */ + if (wm8955->fs == 0) + wm8955->fs = 8000; + + /* Can we generate an exact output? */ + for (i = 0; i < ARRAY_SIZE(clock_cfgs); i++) { + if (wm8955->fs != clock_cfgs[i].fs) + continue; + sr = i; + + if (wm8955->mclk_rate == clock_cfgs[i].mclk) + break; + } + + /* We should never get here with an unsupported sample rate */ + if (sr == -1) { + dev_err(component->dev, "Sample rate %dHz unsupported\n", + wm8955->fs); + WARN_ON(sr == -1); + return -EINVAL; + } + + if (i == ARRAY_SIZE(clock_cfgs)) { + /* If we can't generate the right clock from MCLK then + * we should configure the PLL to supply us with an + * appropriate clock. + */ + clocking |= WM8955_MCLKSEL; + + /* Use the last divider configuration we saw for the + * sample rate. */ + ret = wm8995_pll_factors(component->dev, wm8955->mclk_rate, + clock_cfgs[sr].mclk, &pll); + if (ret != 0) { + dev_err(component->dev, + "Unable to generate %dHz from %dHz MCLK\n", + wm8955->fs, wm8955->mclk_rate); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8955_PLL_CONTROL_1, + WM8955_N_MASK | WM8955_K_21_18_MASK, + (pll.n << WM8955_N_SHIFT) | + pll.k >> 18); + snd_soc_component_update_bits(component, WM8955_PLL_CONTROL_2, + WM8955_K_17_9_MASK, + (pll.k >> 9) & WM8955_K_17_9_MASK); + snd_soc_component_update_bits(component, WM8955_PLL_CONTROL_3, + WM8955_K_8_0_MASK, + pll.k & WM8955_K_8_0_MASK); + if (pll.k) + snd_soc_component_update_bits(component, WM8955_PLL_CONTROL_4, + WM8955_KEN, WM8955_KEN); + else + snd_soc_component_update_bits(component, WM8955_PLL_CONTROL_4, + WM8955_KEN, 0); + + if (pll.outdiv) + val = WM8955_PLL_RB | WM8955_PLLOUTDIV2; + else + val = WM8955_PLL_RB; + + /* Now start the PLL running */ + snd_soc_component_update_bits(component, WM8955_CLOCKING_PLL, + WM8955_PLL_RB | WM8955_PLLOUTDIV2, val); + snd_soc_component_update_bits(component, WM8955_CLOCKING_PLL, + WM8955_PLLEN, WM8955_PLLEN); + } + + srate = clock_cfgs[sr].usb | (clock_cfgs[sr].sr << WM8955_SR_SHIFT); + + snd_soc_component_update_bits(component, WM8955_SAMPLE_RATE, + WM8955_USB | WM8955_SR_MASK, srate); + snd_soc_component_update_bits(component, WM8955_CLOCKING_PLL, + WM8955_MCLKSEL, clocking); + + return 0; +} + +static int wm8955_sysclk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int ret = 0; + + /* Always disable the clocks - if we're doing reconfiguration this + * avoids misclocking. + */ + snd_soc_component_update_bits(component, WM8955_POWER_MANAGEMENT_1, + WM8955_DIGENB, 0); + snd_soc_component_update_bits(component, WM8955_CLOCKING_PLL, + WM8955_PLL_RB | WM8955_PLLEN, 0); + + switch (event) { + case SND_SOC_DAPM_POST_PMD: + break; + case SND_SOC_DAPM_PRE_PMU: + ret = wm8955_configure_clocking(component); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8955_set_deemph(struct snd_soc_component *component) +{ + struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8955->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8955->fs) < + abs(deemph_settings[best] - wm8955->fs)) + best = i; + } + + val = best << WM8955_DEEMPH_SHIFT; + } else { + val = 0; + } + + dev_dbg(component->dev, "Set deemphasis %d\n", val); + + return snd_soc_component_update_bits(component, WM8955_DAC_CONTROL, + WM8955_DEEMPH_MASK, val); +} + +static int wm8955_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8955->deemph; + return 0; +} + +static int wm8955_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + wm8955->deemph = deemph; + + return wm8955_set_deemph(component); +} + +static const char *bass_mode_text[] = { + "Linear", "Adaptive", +}; + +static SOC_ENUM_SINGLE_DECL(bass_mode, WM8955_BASS_CONTROL, 7, bass_mode_text); + +static const char *bass_cutoff_text[] = { + "Low", "High" +}; + +static SOC_ENUM_SINGLE_DECL(bass_cutoff, WM8955_BASS_CONTROL, 6, + bass_cutoff_text); + +static const char *treble_cutoff_text[] = { + "High", "Low" +}; + +static SOC_ENUM_SINGLE_DECL(treble_cutoff, WM8955_TREBLE_CONTROL, 2, + treble_cutoff_text); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(atten_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mono_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(treble_tlv, -1200, 150, 1); + +static const struct snd_kcontrol_new wm8955_snd_controls[] = { +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8955_LEFT_DAC_VOLUME, + WM8955_RIGHT_DAC_VOLUME, 0, 255, 0, digital_tlv), +SOC_SINGLE_TLV("Playback Attenuation Volume", WM8955_DAC_CONTROL, 7, 1, 1, + atten_tlv), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8955_get_deemph, wm8955_put_deemph), + +SOC_ENUM("Bass Mode", bass_mode), +SOC_ENUM("Bass Cutoff", bass_cutoff), +SOC_SINGLE("Bass Volume", WM8955_BASS_CONTROL, 0, 15, 1), + +SOC_ENUM("Treble Cutoff", treble_cutoff), +SOC_SINGLE_TLV("Treble Volume", WM8955_TREBLE_CONTROL, 0, 14, 1, treble_tlv), + +SOC_SINGLE_TLV("Left Bypass Volume", WM8955_LEFT_OUT_MIX_1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Left Mono Volume", WM8955_LEFT_OUT_MIX_2, 4, 7, 1, + bypass_tlv), + +SOC_SINGLE_TLV("Right Mono Volume", WM8955_RIGHT_OUT_MIX_1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Bypass Volume", WM8955_RIGHT_OUT_MIX_2, 4, 7, 1, + bypass_tlv), + +/* Not a stereo pair so they line up with the DAPM switches */ +SOC_SINGLE_TLV("Mono Left Bypass Volume", WM8955_MONO_OUT_MIX_1, 4, 7, 1, + mono_tlv), +SOC_SINGLE_TLV("Mono Right Bypass Volume", WM8955_MONO_OUT_MIX_2, 4, 7, 1, + mono_tlv), + +SOC_DOUBLE_R_TLV("Headphone Volume", WM8955_LOUT1_VOLUME, + WM8955_ROUT1_VOLUME, 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone ZC Switch", WM8955_LOUT1_VOLUME, + WM8955_ROUT1_VOLUME, 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Volume", WM8955_LOUT2_VOLUME, + WM8955_ROUT2_VOLUME, 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker ZC Switch", WM8955_LOUT2_VOLUME, + WM8955_ROUT2_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("Mono Volume", WM8955_MONOOUT_VOLUME, 0, 127, 0, out_tlv), +SOC_SINGLE("Mono ZC Switch", WM8955_MONOOUT_VOLUME, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lmixer[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8955_LEFT_OUT_MIX_1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Switch", WM8955_LEFT_OUT_MIX_1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8955_LEFT_OUT_MIX_2, 8, 1, 0), +SOC_DAPM_SINGLE("Mono Switch", WM8955_LEFT_OUT_MIX_2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new rmixer[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8955_RIGHT_OUT_MIX_1, 8, 1, 0), +SOC_DAPM_SINGLE("Mono Switch", WM8955_RIGHT_OUT_MIX_1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8955_RIGHT_OUT_MIX_2, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Switch", WM8955_RIGHT_OUT_MIX_2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new mmixer[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8955_MONO_OUT_MIX_1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8955_MONO_OUT_MIX_1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8955_MONO_OUT_MIX_2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8955_MONO_OUT_MIX_2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8955_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("MONOIN-"), +SND_SOC_DAPM_INPUT("MONOIN+"), +SND_SOC_DAPM_INPUT("LINEINR"), +SND_SOC_DAPM_INPUT("LINEINL"), + +SND_SOC_DAPM_PGA("Mono Input", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("SYSCLK", WM8955_POWER_MANAGEMENT_1, 0, 1, wm8955_sysclk, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("TSDEN", WM8955_ADDITIONAL_CONTROL_1, 8, 0, NULL, 0), + +SND_SOC_DAPM_DAC("DACL", "Playback", WM8955_POWER_MANAGEMENT_2, 8, 0), +SND_SOC_DAPM_DAC("DACR", "Playback", WM8955_POWER_MANAGEMENT_2, 7, 0), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8955_POWER_MANAGEMENT_2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8955_POWER_MANAGEMENT_2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("LOUT2 PGA", WM8955_POWER_MANAGEMENT_2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT2 PGA", WM8955_POWER_MANAGEMENT_2, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA("MOUT PGA", WM8955_POWER_MANAGEMENT_2, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("OUT3 PGA", WM8955_POWER_MANAGEMENT_2, 1, 0, NULL, 0), + +/* The names are chosen to make the control names nice */ +SND_SOC_DAPM_MIXER("Left", SND_SOC_NOPM, 0, 0, + lmixer, ARRAY_SIZE(lmixer)), +SND_SOC_DAPM_MIXER("Right", SND_SOC_NOPM, 0, 0, + rmixer, ARRAY_SIZE(rmixer)), +SND_SOC_DAPM_MIXER("Mono", SND_SOC_NOPM, 0, 0, + mmixer, ARRAY_SIZE(mmixer)), + +SND_SOC_DAPM_OUTPUT("LOUT1"), +SND_SOC_DAPM_OUTPUT("ROUT1"), +SND_SOC_DAPM_OUTPUT("LOUT2"), +SND_SOC_DAPM_OUTPUT("ROUT2"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_route wm8955_dapm_routes[] = { + { "DACL", NULL, "SYSCLK" }, + { "DACR", NULL, "SYSCLK" }, + + { "Mono Input", NULL, "MONOIN-" }, + { "Mono Input", NULL, "MONOIN+" }, + + { "Left", "Playback Switch", "DACL" }, + { "Left", "Right Playback Switch", "DACR" }, + { "Left", "Bypass Switch", "LINEINL" }, + { "Left", "Mono Switch", "Mono Input" }, + + { "Right", "Playback Switch", "DACR" }, + { "Right", "Left Playback Switch", "DACL" }, + { "Right", "Bypass Switch", "LINEINR" }, + { "Right", "Mono Switch", "Mono Input" }, + + { "Mono", "Left Playback Switch", "DACL" }, + { "Mono", "Right Playback Switch", "DACR" }, + { "Mono", "Left Bypass Switch", "LINEINL" }, + { "Mono", "Right Bypass Switch", "LINEINR" }, + + { "LOUT1 PGA", NULL, "Left" }, + { "LOUT1", NULL, "TSDEN" }, + { "LOUT1", NULL, "LOUT1 PGA" }, + + { "ROUT1 PGA", NULL, "Right" }, + { "ROUT1", NULL, "TSDEN" }, + { "ROUT1", NULL, "ROUT1 PGA" }, + + { "LOUT2 PGA", NULL, "Left" }, + { "LOUT2", NULL, "TSDEN" }, + { "LOUT2", NULL, "LOUT2 PGA" }, + + { "ROUT2 PGA", NULL, "Right" }, + { "ROUT2", NULL, "TSDEN" }, + { "ROUT2", NULL, "ROUT2 PGA" }, + + { "MOUT PGA", NULL, "Mono" }, + { "MONOOUT", NULL, "MOUT PGA" }, + + /* OUT3 not currently implemented */ + { "OUT3", NULL, "OUT3 PGA" }, +}; + +static int wm8955_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); + int ret; + int wl; + + switch (params_width(params)) { + case 16: + wl = 0; + break; + case 20: + wl = 0x4; + break; + case 24: + wl = 0x8; + break; + case 32: + wl = 0xc; + break; + default: + return -EINVAL; + } + snd_soc_component_update_bits(component, WM8955_AUDIO_INTERFACE, + WM8955_WL_MASK, wl); + + wm8955->fs = params_rate(params); + wm8955_set_deemph(component); + + /* If the chip is clocked then disable the clocks and force a + * reconfiguration, otherwise DAPM will power up the + * clocks for us later. */ + ret = snd_soc_component_read32(component, WM8955_POWER_MANAGEMENT_1); + if (ret < 0) + return ret; + if (ret & WM8955_DIGENB) { + snd_soc_component_update_bits(component, WM8955_POWER_MANAGEMENT_1, + WM8955_DIGENB, 0); + snd_soc_component_update_bits(component, WM8955_CLOCKING_PLL, + WM8955_PLL_RB | WM8955_PLLEN, 0); + + wm8955_configure_clocking(component); + } + + return 0; +} + + +static int wm8955_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8955_priv *priv = snd_soc_component_get_drvdata(component); + int div; + + switch (clk_id) { + case WM8955_CLK_MCLK: + if (freq > 15000000) { + priv->mclk_rate = freq /= 2; + div = WM8955_MCLKDIV2; + } else { + priv->mclk_rate = freq; + div = 0; + } + + snd_soc_component_update_bits(component, WM8955_SAMPLE_RATE, + WM8955_MCLKDIV2, div); + break; + + default: + return -EINVAL; + } + + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + + return 0; +} + +static int wm8955_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u16 aif = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif |= WM8955_MS; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8955_LRP; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif |= 0x3; + break; + case SND_SOC_DAIFMT_I2S: + aif |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8955_BCLKINV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8955_BCLKINV | WM8955_LRP; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8955_BCLKINV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8955_LRP; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8955_AUDIO_INTERFACE, + WM8955_MS | WM8955_FORMAT_MASK | WM8955_BCLKINV | + WM8955_LRP, aif); + + return 0; +} + + +static int wm8955_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + int val; + + if (mute) + val = WM8955_DACMU; + else + val = 0; + + snd_soc_component_update_bits(component, WM8955_DAC_CONTROL, WM8955_DACMU, val); + + return 0; +} + +static int wm8955_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID resistance 2*50k */ + snd_soc_component_update_bits(component, WM8955_POWER_MANAGEMENT_1, + WM8955_VMIDSEL_MASK, + 0x1 << WM8955_VMIDSEL_SHIFT); + + /* Default bias current */ + snd_soc_component_update_bits(component, WM8955_ADDITIONAL_CONTROL_1, + WM8955_VSEL_MASK, + 0x2 << WM8955_VSEL_SHIFT); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + regcache_sync(wm8955->regmap); + + /* Enable VREF and VMID */ + snd_soc_component_update_bits(component, WM8955_POWER_MANAGEMENT_1, + WM8955_VREF | + WM8955_VMIDSEL_MASK, + WM8955_VREF | + 0x3 << WM8955_VREF_SHIFT); + + /* Let VMID ramp */ + msleep(500); + + /* High resistance VROI to maintain outputs */ + snd_soc_component_update_bits(component, + WM8955_ADDITIONAL_CONTROL_3, + WM8955_VROI, WM8955_VROI); + } + + /* Maintain VMID with 2*250k */ + snd_soc_component_update_bits(component, WM8955_POWER_MANAGEMENT_1, + WM8955_VMIDSEL_MASK, + 0x2 << WM8955_VMIDSEL_SHIFT); + + /* Minimum bias current */ + snd_soc_component_update_bits(component, WM8955_ADDITIONAL_CONTROL_1, + WM8955_VSEL_MASK, 0); + break; + + case SND_SOC_BIAS_OFF: + /* Low resistance VROI to help discharge */ + snd_soc_component_update_bits(component, + WM8955_ADDITIONAL_CONTROL_3, + WM8955_VROI, 0); + + /* Turn off VMID and VREF */ + snd_soc_component_update_bits(component, WM8955_POWER_MANAGEMENT_1, + WM8955_VREF | + WM8955_VMIDSEL_MASK, 0); + + regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + break; + } + return 0; +} + +#define WM8955_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8955_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8955_dai_ops = { + .set_sysclk = wm8955_set_sysclk, + .set_fmt = wm8955_set_fmt, + .hw_params = wm8955_hw_params, + .digital_mute = wm8955_digital_mute, +}; + +static struct snd_soc_dai_driver wm8955_dai = { + .name = "wm8955-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8955_RATES, + .formats = WM8955_FORMATS, + }, + .ops = &wm8955_dai_ops, +}; + +static int wm8955_probe(struct snd_soc_component *component) +{ + struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); + struct wm8955_pdata *pdata = dev_get_platdata(component->dev); + int ret, i; + + for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) + wm8955->supplies[i].supply = wm8955_supply_names[i]; + + ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(component->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = wm8955_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + goto err_enable; + } + + /* Change some default settings - latch VU and enable ZC */ + snd_soc_component_update_bits(component, WM8955_LEFT_DAC_VOLUME, + WM8955_LDVU, WM8955_LDVU); + snd_soc_component_update_bits(component, WM8955_RIGHT_DAC_VOLUME, + WM8955_RDVU, WM8955_RDVU); + snd_soc_component_update_bits(component, WM8955_LOUT1_VOLUME, + WM8955_LO1VU | WM8955_LO1ZC, + WM8955_LO1VU | WM8955_LO1ZC); + snd_soc_component_update_bits(component, WM8955_ROUT1_VOLUME, + WM8955_RO1VU | WM8955_RO1ZC, + WM8955_RO1VU | WM8955_RO1ZC); + snd_soc_component_update_bits(component, WM8955_LOUT2_VOLUME, + WM8955_LO2VU | WM8955_LO2ZC, + WM8955_LO2VU | WM8955_LO2ZC); + snd_soc_component_update_bits(component, WM8955_ROUT2_VOLUME, + WM8955_RO2VU | WM8955_RO2ZC, + WM8955_RO2VU | WM8955_RO2ZC); + snd_soc_component_update_bits(component, WM8955_MONOOUT_VOLUME, + WM8955_MOZC, WM8955_MOZC); + + /* Also enable adaptive bass boost by default */ + snd_soc_component_update_bits(component, WM8955_BASS_CONTROL, WM8955_BB, WM8955_BB); + + /* Set platform data values */ + if (pdata) { + if (pdata->out2_speaker) + snd_soc_component_update_bits(component, WM8955_ADDITIONAL_CONTROL_2, + WM8955_ROUT2INV, WM8955_ROUT2INV); + + if (pdata->monoin_diff) + snd_soc_component_update_bits(component, WM8955_MONO_OUT_MIX_1, + WM8955_DMEN, WM8955_DMEN); + } + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + /* Bias level configuration will have done an extra enable */ + regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); + + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); + return ret; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8955 = { + .probe = wm8955_probe, + .set_bias_level = wm8955_set_bias_level, + .controls = wm8955_snd_controls, + .num_controls = ARRAY_SIZE(wm8955_snd_controls), + .dapm_widgets = wm8955_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8955_dapm_widgets), + .dapm_routes = wm8955_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8955_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8955_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = WM8955_MAX_REGISTER, + .volatile_reg = wm8955_volatile, + .writeable_reg = wm8955_writeable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8955_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8955_reg_defaults), +}; + +static int wm8955_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8955_priv *wm8955; + int ret; + + wm8955 = devm_kzalloc(&i2c->dev, sizeof(struct wm8955_priv), + GFP_KERNEL); + if (wm8955 == NULL) + return -ENOMEM; + + wm8955->regmap = devm_regmap_init_i2c(i2c, &wm8955_regmap); + if (IS_ERR(wm8955->regmap)) { + ret = PTR_ERR(wm8955->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8955); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8955, &wm8955_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8955_i2c_id[] = { + { "wm8955", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8955_i2c_id); + +static struct i2c_driver wm8955_i2c_driver = { + .driver = { + .name = "wm8955", + }, + .probe = wm8955_i2c_probe, + .id_table = wm8955_i2c_id, +}; + +module_i2c_driver(wm8955_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8955 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8955.h b/sound/soc/codecs/wm8955.h new file mode 100644 index 000000000..d13fd5c5f --- /dev/null +++ b/sound/soc/codecs/wm8955.h @@ -0,0 +1,486 @@ +/* + * wm8955.h -- WM8904 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics, plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8955_H +#define _WM8955_H + +#define WM8955_CLK_MCLK 1 + +/* + * Register values. + */ +#define WM8955_LOUT1_VOLUME 0x02 +#define WM8955_ROUT1_VOLUME 0x03 +#define WM8955_DAC_CONTROL 0x05 +#define WM8955_AUDIO_INTERFACE 0x07 +#define WM8955_SAMPLE_RATE 0x08 +#define WM8955_LEFT_DAC_VOLUME 0x0A +#define WM8955_RIGHT_DAC_VOLUME 0x0B +#define WM8955_BASS_CONTROL 0x0C +#define WM8955_TREBLE_CONTROL 0x0D +#define WM8955_RESET 0x0F +#define WM8955_ADDITIONAL_CONTROL_1 0x17 +#define WM8955_ADDITIONAL_CONTROL_2 0x18 +#define WM8955_POWER_MANAGEMENT_1 0x19 +#define WM8955_POWER_MANAGEMENT_2 0x1A +#define WM8955_ADDITIONAL_CONTROL_3 0x1B +#define WM8955_LEFT_OUT_MIX_1 0x22 +#define WM8955_LEFT_OUT_MIX_2 0x23 +#define WM8955_RIGHT_OUT_MIX_1 0x24 +#define WM8955_RIGHT_OUT_MIX_2 0x25 +#define WM8955_MONO_OUT_MIX_1 0x26 +#define WM8955_MONO_OUT_MIX_2 0x27 +#define WM8955_LOUT2_VOLUME 0x28 +#define WM8955_ROUT2_VOLUME 0x29 +#define WM8955_MONOOUT_VOLUME 0x2A +#define WM8955_CLOCKING_PLL 0x2B +#define WM8955_PLL_CONTROL_1 0x2C +#define WM8955_PLL_CONTROL_2 0x2D +#define WM8955_PLL_CONTROL_3 0x2E +#define WM8955_PLL_CONTROL_4 0x3B + +#define WM8955_REGISTER_COUNT 29 +#define WM8955_MAX_REGISTER 0x3B + +/* + * Field Definitions. + */ + +/* + * R2 (0x02) - LOUT1 volume + */ +#define WM8955_LO1VU 0x0100 /* LO1VU */ +#define WM8955_LO1VU_MASK 0x0100 /* LO1VU */ +#define WM8955_LO1VU_SHIFT 8 /* LO1VU */ +#define WM8955_LO1VU_WIDTH 1 /* LO1VU */ +#define WM8955_LO1ZC 0x0080 /* LO1ZC */ +#define WM8955_LO1ZC_MASK 0x0080 /* LO1ZC */ +#define WM8955_LO1ZC_SHIFT 7 /* LO1ZC */ +#define WM8955_LO1ZC_WIDTH 1 /* LO1ZC */ +#define WM8955_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8955_LOUTVOL_SHIFT 0 /* LOUTVOL - [6:0] */ +#define WM8955_LOUTVOL_WIDTH 7 /* LOUTVOL - [6:0] */ + +/* + * R3 (0x03) - ROUT1 volume + */ +#define WM8955_RO1VU 0x0100 /* RO1VU */ +#define WM8955_RO1VU_MASK 0x0100 /* RO1VU */ +#define WM8955_RO1VU_SHIFT 8 /* RO1VU */ +#define WM8955_RO1VU_WIDTH 1 /* RO1VU */ +#define WM8955_RO1ZC 0x0080 /* RO1ZC */ +#define WM8955_RO1ZC_MASK 0x0080 /* RO1ZC */ +#define WM8955_RO1ZC_SHIFT 7 /* RO1ZC */ +#define WM8955_RO1ZC_WIDTH 1 /* RO1ZC */ +#define WM8955_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8955_ROUTVOL_SHIFT 0 /* ROUTVOL - [6:0] */ +#define WM8955_ROUTVOL_WIDTH 7 /* ROUTVOL - [6:0] */ + +/* + * R5 (0x05) - DAC Control + */ +#define WM8955_DAT 0x0080 /* DAT */ +#define WM8955_DAT_MASK 0x0080 /* DAT */ +#define WM8955_DAT_SHIFT 7 /* DAT */ +#define WM8955_DAT_WIDTH 1 /* DAT */ +#define WM8955_DACMU 0x0008 /* DACMU */ +#define WM8955_DACMU_MASK 0x0008 /* DACMU */ +#define WM8955_DACMU_SHIFT 3 /* DACMU */ +#define WM8955_DACMU_WIDTH 1 /* DACMU */ +#define WM8955_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8955_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8955_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R7 (0x07) - Audio Interface + */ +#define WM8955_BCLKINV 0x0080 /* BCLKINV */ +#define WM8955_BCLKINV_MASK 0x0080 /* BCLKINV */ +#define WM8955_BCLKINV_SHIFT 7 /* BCLKINV */ +#define WM8955_BCLKINV_WIDTH 1 /* BCLKINV */ +#define WM8955_MS 0x0040 /* MS */ +#define WM8955_MS_MASK 0x0040 /* MS */ +#define WM8955_MS_SHIFT 6 /* MS */ +#define WM8955_MS_WIDTH 1 /* MS */ +#define WM8955_LRSWAP 0x0020 /* LRSWAP */ +#define WM8955_LRSWAP_MASK 0x0020 /* LRSWAP */ +#define WM8955_LRSWAP_SHIFT 5 /* LRSWAP */ +#define WM8955_LRSWAP_WIDTH 1 /* LRSWAP */ +#define WM8955_LRP 0x0010 /* LRP */ +#define WM8955_LRP_MASK 0x0010 /* LRP */ +#define WM8955_LRP_SHIFT 4 /* LRP */ +#define WM8955_LRP_WIDTH 1 /* LRP */ +#define WM8955_WL_MASK 0x000C /* WL - [3:2] */ +#define WM8955_WL_SHIFT 2 /* WL - [3:2] */ +#define WM8955_WL_WIDTH 2 /* WL - [3:2] */ +#define WM8955_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */ +#define WM8955_FORMAT_SHIFT 0 /* FORMAT - [1:0] */ +#define WM8955_FORMAT_WIDTH 2 /* FORMAT - [1:0] */ + +/* + * R8 (0x08) - Sample Rate + */ +#define WM8955_BCLKDIV2 0x0080 /* BCLKDIV2 */ +#define WM8955_BCLKDIV2_MASK 0x0080 /* BCLKDIV2 */ +#define WM8955_BCLKDIV2_SHIFT 7 /* BCLKDIV2 */ +#define WM8955_BCLKDIV2_WIDTH 1 /* BCLKDIV2 */ +#define WM8955_MCLKDIV2 0x0040 /* MCLKDIV2 */ +#define WM8955_MCLKDIV2_MASK 0x0040 /* MCLKDIV2 */ +#define WM8955_MCLKDIV2_SHIFT 6 /* MCLKDIV2 */ +#define WM8955_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ +#define WM8955_SR_MASK 0x003E /* SR - [5:1] */ +#define WM8955_SR_SHIFT 1 /* SR - [5:1] */ +#define WM8955_SR_WIDTH 5 /* SR - [5:1] */ +#define WM8955_USB 0x0001 /* USB */ +#define WM8955_USB_MASK 0x0001 /* USB */ +#define WM8955_USB_SHIFT 0 /* USB */ +#define WM8955_USB_WIDTH 1 /* USB */ + +/* + * R10 (0x0A) - Left DAC volume + */ +#define WM8955_LDVU 0x0100 /* LDVU */ +#define WM8955_LDVU_MASK 0x0100 /* LDVU */ +#define WM8955_LDVU_SHIFT 8 /* LDVU */ +#define WM8955_LDVU_WIDTH 1 /* LDVU */ +#define WM8955_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */ +#define WM8955_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */ +#define WM8955_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */ + +/* + * R11 (0x0B) - Right DAC volume + */ +#define WM8955_RDVU 0x0100 /* RDVU */ +#define WM8955_RDVU_MASK 0x0100 /* RDVU */ +#define WM8955_RDVU_SHIFT 8 /* RDVU */ +#define WM8955_RDVU_WIDTH 1 /* RDVU */ +#define WM8955_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */ +#define WM8955_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */ +#define WM8955_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */ + +/* + * R12 (0x0C) - Bass control + */ +#define WM8955_BB 0x0080 /* BB */ +#define WM8955_BB_MASK 0x0080 /* BB */ +#define WM8955_BB_SHIFT 7 /* BB */ +#define WM8955_BB_WIDTH 1 /* BB */ +#define WM8955_BC 0x0040 /* BC */ +#define WM8955_BC_MASK 0x0040 /* BC */ +#define WM8955_BC_SHIFT 6 /* BC */ +#define WM8955_BC_WIDTH 1 /* BC */ +#define WM8955_BASS_MASK 0x000F /* BASS - [3:0] */ +#define WM8955_BASS_SHIFT 0 /* BASS - [3:0] */ +#define WM8955_BASS_WIDTH 4 /* BASS - [3:0] */ + +/* + * R13 (0x0D) - Treble control + */ +#define WM8955_TC 0x0040 /* TC */ +#define WM8955_TC_MASK 0x0040 /* TC */ +#define WM8955_TC_SHIFT 6 /* TC */ +#define WM8955_TC_WIDTH 1 /* TC */ +#define WM8955_TRBL_MASK 0x000F /* TRBL - [3:0] */ +#define WM8955_TRBL_SHIFT 0 /* TRBL - [3:0] */ +#define WM8955_TRBL_WIDTH 4 /* TRBL - [3:0] */ + +/* + * R15 (0x0F) - Reset + */ +#define WM8955_RESET_MASK 0x01FF /* RESET - [8:0] */ +#define WM8955_RESET_SHIFT 0 /* RESET - [8:0] */ +#define WM8955_RESET_WIDTH 9 /* RESET - [8:0] */ + +/* + * R23 (0x17) - Additional control (1) + */ +#define WM8955_TSDEN 0x0100 /* TSDEN */ +#define WM8955_TSDEN_MASK 0x0100 /* TSDEN */ +#define WM8955_TSDEN_SHIFT 8 /* TSDEN */ +#define WM8955_TSDEN_WIDTH 1 /* TSDEN */ +#define WM8955_VSEL_MASK 0x00C0 /* VSEL - [7:6] */ +#define WM8955_VSEL_SHIFT 6 /* VSEL - [7:6] */ +#define WM8955_VSEL_WIDTH 2 /* VSEL - [7:6] */ +#define WM8955_DMONOMIX_MASK 0x0030 /* DMONOMIX - [5:4] */ +#define WM8955_DMONOMIX_SHIFT 4 /* DMONOMIX - [5:4] */ +#define WM8955_DMONOMIX_WIDTH 2 /* DMONOMIX - [5:4] */ +#define WM8955_DACINV 0x0002 /* DACINV */ +#define WM8955_DACINV_MASK 0x0002 /* DACINV */ +#define WM8955_DACINV_SHIFT 1 /* DACINV */ +#define WM8955_DACINV_WIDTH 1 /* DACINV */ +#define WM8955_TOEN 0x0001 /* TOEN */ +#define WM8955_TOEN_MASK 0x0001 /* TOEN */ +#define WM8955_TOEN_SHIFT 0 /* TOEN */ +#define WM8955_TOEN_WIDTH 1 /* TOEN */ + +/* + * R24 (0x18) - Additional control (2) + */ +#define WM8955_OUT3SW_MASK 0x0180 /* OUT3SW - [8:7] */ +#define WM8955_OUT3SW_SHIFT 7 /* OUT3SW - [8:7] */ +#define WM8955_OUT3SW_WIDTH 2 /* OUT3SW - [8:7] */ +#define WM8955_ROUT2INV 0x0010 /* ROUT2INV */ +#define WM8955_ROUT2INV_MASK 0x0010 /* ROUT2INV */ +#define WM8955_ROUT2INV_SHIFT 4 /* ROUT2INV */ +#define WM8955_ROUT2INV_WIDTH 1 /* ROUT2INV */ +#define WM8955_DACOSR 0x0001 /* DACOSR */ +#define WM8955_DACOSR_MASK 0x0001 /* DACOSR */ +#define WM8955_DACOSR_SHIFT 0 /* DACOSR */ +#define WM8955_DACOSR_WIDTH 1 /* DACOSR */ + +/* + * R25 (0x19) - Power Management (1) + */ +#define WM8955_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */ +#define WM8955_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */ +#define WM8955_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */ +#define WM8955_VREF 0x0040 /* VREF */ +#define WM8955_VREF_MASK 0x0040 /* VREF */ +#define WM8955_VREF_SHIFT 6 /* VREF */ +#define WM8955_VREF_WIDTH 1 /* VREF */ +#define WM8955_DIGENB 0x0001 /* DIGENB */ +#define WM8955_DIGENB_MASK 0x0001 /* DIGENB */ +#define WM8955_DIGENB_SHIFT 0 /* DIGENB */ +#define WM8955_DIGENB_WIDTH 1 /* DIGENB */ + +/* + * R26 (0x1A) - Power Management (2) + */ +#define WM8955_DACL 0x0100 /* DACL */ +#define WM8955_DACL_MASK 0x0100 /* DACL */ +#define WM8955_DACL_SHIFT 8 /* DACL */ +#define WM8955_DACL_WIDTH 1 /* DACL */ +#define WM8955_DACR 0x0080 /* DACR */ +#define WM8955_DACR_MASK 0x0080 /* DACR */ +#define WM8955_DACR_SHIFT 7 /* DACR */ +#define WM8955_DACR_WIDTH 1 /* DACR */ +#define WM8955_LOUT1 0x0040 /* LOUT1 */ +#define WM8955_LOUT1_MASK 0x0040 /* LOUT1 */ +#define WM8955_LOUT1_SHIFT 6 /* LOUT1 */ +#define WM8955_LOUT1_WIDTH 1 /* LOUT1 */ +#define WM8955_ROUT1 0x0020 /* ROUT1 */ +#define WM8955_ROUT1_MASK 0x0020 /* ROUT1 */ +#define WM8955_ROUT1_SHIFT 5 /* ROUT1 */ +#define WM8955_ROUT1_WIDTH 1 /* ROUT1 */ +#define WM8955_LOUT2 0x0010 /* LOUT2 */ +#define WM8955_LOUT2_MASK 0x0010 /* LOUT2 */ +#define WM8955_LOUT2_SHIFT 4 /* LOUT2 */ +#define WM8955_LOUT2_WIDTH 1 /* LOUT2 */ +#define WM8955_ROUT2 0x0008 /* ROUT2 */ +#define WM8955_ROUT2_MASK 0x0008 /* ROUT2 */ +#define WM8955_ROUT2_SHIFT 3 /* ROUT2 */ +#define WM8955_ROUT2_WIDTH 1 /* ROUT2 */ +#define WM8955_MONO 0x0004 /* MONO */ +#define WM8955_MONO_MASK 0x0004 /* MONO */ +#define WM8955_MONO_SHIFT 2 /* MONO */ +#define WM8955_MONO_WIDTH 1 /* MONO */ +#define WM8955_OUT3 0x0002 /* OUT3 */ +#define WM8955_OUT3_MASK 0x0002 /* OUT3 */ +#define WM8955_OUT3_SHIFT 1 /* OUT3 */ +#define WM8955_OUT3_WIDTH 1 /* OUT3 */ + +/* + * R27 (0x1B) - Additional Control (3) + */ +#define WM8955_VROI 0x0040 /* VROI */ +#define WM8955_VROI_MASK 0x0040 /* VROI */ +#define WM8955_VROI_SHIFT 6 /* VROI */ +#define WM8955_VROI_WIDTH 1 /* VROI */ + +/* + * R34 (0x22) - Left out Mix (1) + */ +#define WM8955_LD2LO 0x0100 /* LD2LO */ +#define WM8955_LD2LO_MASK 0x0100 /* LD2LO */ +#define WM8955_LD2LO_SHIFT 8 /* LD2LO */ +#define WM8955_LD2LO_WIDTH 1 /* LD2LO */ +#define WM8955_LI2LO 0x0080 /* LI2LO */ +#define WM8955_LI2LO_MASK 0x0080 /* LI2LO */ +#define WM8955_LI2LO_SHIFT 7 /* LI2LO */ +#define WM8955_LI2LO_WIDTH 1 /* LI2LO */ +#define WM8955_LI2LOVOL_MASK 0x0070 /* LI2LOVOL - [6:4] */ +#define WM8955_LI2LOVOL_SHIFT 4 /* LI2LOVOL - [6:4] */ +#define WM8955_LI2LOVOL_WIDTH 3 /* LI2LOVOL - [6:4] */ + +/* + * R35 (0x23) - Left out Mix (2) + */ +#define WM8955_RD2LO 0x0100 /* RD2LO */ +#define WM8955_RD2LO_MASK 0x0100 /* RD2LO */ +#define WM8955_RD2LO_SHIFT 8 /* RD2LO */ +#define WM8955_RD2LO_WIDTH 1 /* RD2LO */ +#define WM8955_RI2LO 0x0080 /* RI2LO */ +#define WM8955_RI2LO_MASK 0x0080 /* RI2LO */ +#define WM8955_RI2LO_SHIFT 7 /* RI2LO */ +#define WM8955_RI2LO_WIDTH 1 /* RI2LO */ +#define WM8955_RI2LOVOL_MASK 0x0070 /* RI2LOVOL - [6:4] */ +#define WM8955_RI2LOVOL_SHIFT 4 /* RI2LOVOL - [6:4] */ +#define WM8955_RI2LOVOL_WIDTH 3 /* RI2LOVOL - [6:4] */ + +/* + * R36 (0x24) - Right out Mix (1) + */ +#define WM8955_LD2RO 0x0100 /* LD2RO */ +#define WM8955_LD2RO_MASK 0x0100 /* LD2RO */ +#define WM8955_LD2RO_SHIFT 8 /* LD2RO */ +#define WM8955_LD2RO_WIDTH 1 /* LD2RO */ +#define WM8955_LI2RO 0x0080 /* LI2RO */ +#define WM8955_LI2RO_MASK 0x0080 /* LI2RO */ +#define WM8955_LI2RO_SHIFT 7 /* LI2RO */ +#define WM8955_LI2RO_WIDTH 1 /* LI2RO */ +#define WM8955_LI2ROVOL_MASK 0x0070 /* LI2ROVOL - [6:4] */ +#define WM8955_LI2ROVOL_SHIFT 4 /* LI2ROVOL - [6:4] */ +#define WM8955_LI2ROVOL_WIDTH 3 /* LI2ROVOL - [6:4] */ + +/* + * R37 (0x25) - Right Out Mix (2) + */ +#define WM8955_RD2RO 0x0100 /* RD2RO */ +#define WM8955_RD2RO_MASK 0x0100 /* RD2RO */ +#define WM8955_RD2RO_SHIFT 8 /* RD2RO */ +#define WM8955_RD2RO_WIDTH 1 /* RD2RO */ +#define WM8955_RI2RO 0x0080 /* RI2RO */ +#define WM8955_RI2RO_MASK 0x0080 /* RI2RO */ +#define WM8955_RI2RO_SHIFT 7 /* RI2RO */ +#define WM8955_RI2RO_WIDTH 1 /* RI2RO */ +#define WM8955_RI2ROVOL_MASK 0x0070 /* RI2ROVOL - [6:4] */ +#define WM8955_RI2ROVOL_SHIFT 4 /* RI2ROVOL - [6:4] */ +#define WM8955_RI2ROVOL_WIDTH 3 /* RI2ROVOL - [6:4] */ + +/* + * R38 (0x26) - Mono out Mix (1) + */ +#define WM8955_LD2MO 0x0100 /* LD2MO */ +#define WM8955_LD2MO_MASK 0x0100 /* LD2MO */ +#define WM8955_LD2MO_SHIFT 8 /* LD2MO */ +#define WM8955_LD2MO_WIDTH 1 /* LD2MO */ +#define WM8955_LI2MO 0x0080 /* LI2MO */ +#define WM8955_LI2MO_MASK 0x0080 /* LI2MO */ +#define WM8955_LI2MO_SHIFT 7 /* LI2MO */ +#define WM8955_LI2MO_WIDTH 1 /* LI2MO */ +#define WM8955_LI2MOVOL_MASK 0x0070 /* LI2MOVOL - [6:4] */ +#define WM8955_LI2MOVOL_SHIFT 4 /* LI2MOVOL - [6:4] */ +#define WM8955_LI2MOVOL_WIDTH 3 /* LI2MOVOL - [6:4] */ +#define WM8955_DMEN 0x0001 /* DMEN */ +#define WM8955_DMEN_MASK 0x0001 /* DMEN */ +#define WM8955_DMEN_SHIFT 0 /* DMEN */ +#define WM8955_DMEN_WIDTH 1 /* DMEN */ + +/* + * R39 (0x27) - Mono out Mix (2) + */ +#define WM8955_RD2MO 0x0100 /* RD2MO */ +#define WM8955_RD2MO_MASK 0x0100 /* RD2MO */ +#define WM8955_RD2MO_SHIFT 8 /* RD2MO */ +#define WM8955_RD2MO_WIDTH 1 /* RD2MO */ +#define WM8955_RI2MO 0x0080 /* RI2MO */ +#define WM8955_RI2MO_MASK 0x0080 /* RI2MO */ +#define WM8955_RI2MO_SHIFT 7 /* RI2MO */ +#define WM8955_RI2MO_WIDTH 1 /* RI2MO */ +#define WM8955_RI2MOVOL_MASK 0x0070 /* RI2MOVOL - [6:4] */ +#define WM8955_RI2MOVOL_SHIFT 4 /* RI2MOVOL - [6:4] */ +#define WM8955_RI2MOVOL_WIDTH 3 /* RI2MOVOL - [6:4] */ + +/* + * R40 (0x28) - LOUT2 volume + */ +#define WM8955_LO2VU 0x0100 /* LO2VU */ +#define WM8955_LO2VU_MASK 0x0100 /* LO2VU */ +#define WM8955_LO2VU_SHIFT 8 /* LO2VU */ +#define WM8955_LO2VU_WIDTH 1 /* LO2VU */ +#define WM8955_LO2ZC 0x0080 /* LO2ZC */ +#define WM8955_LO2ZC_MASK 0x0080 /* LO2ZC */ +#define WM8955_LO2ZC_SHIFT 7 /* LO2ZC */ +#define WM8955_LO2ZC_WIDTH 1 /* LO2ZC */ +#define WM8955_LOUT2VOL_MASK 0x007F /* LOUT2VOL - [6:0] */ +#define WM8955_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [6:0] */ +#define WM8955_LOUT2VOL_WIDTH 7 /* LOUT2VOL - [6:0] */ + +/* + * R41 (0x29) - ROUT2 volume + */ +#define WM8955_RO2VU 0x0100 /* RO2VU */ +#define WM8955_RO2VU_MASK 0x0100 /* RO2VU */ +#define WM8955_RO2VU_SHIFT 8 /* RO2VU */ +#define WM8955_RO2VU_WIDTH 1 /* RO2VU */ +#define WM8955_RO2ZC 0x0080 /* RO2ZC */ +#define WM8955_RO2ZC_MASK 0x0080 /* RO2ZC */ +#define WM8955_RO2ZC_SHIFT 7 /* RO2ZC */ +#define WM8955_RO2ZC_WIDTH 1 /* RO2ZC */ +#define WM8955_ROUT2VOL_MASK 0x007F /* ROUT2VOL - [6:0] */ +#define WM8955_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [6:0] */ +#define WM8955_ROUT2VOL_WIDTH 7 /* ROUT2VOL - [6:0] */ + +/* + * R42 (0x2A) - MONOOUT volume + */ +#define WM8955_MOZC 0x0080 /* MOZC */ +#define WM8955_MOZC_MASK 0x0080 /* MOZC */ +#define WM8955_MOZC_SHIFT 7 /* MOZC */ +#define WM8955_MOZC_WIDTH 1 /* MOZC */ +#define WM8955_MOUTVOL_MASK 0x007F /* MOUTVOL - [6:0] */ +#define WM8955_MOUTVOL_SHIFT 0 /* MOUTVOL - [6:0] */ +#define WM8955_MOUTVOL_WIDTH 7 /* MOUTVOL - [6:0] */ + +/* + * R43 (0x2B) - Clocking / PLL + */ +#define WM8955_MCLKSEL 0x0100 /* MCLKSEL */ +#define WM8955_MCLKSEL_MASK 0x0100 /* MCLKSEL */ +#define WM8955_MCLKSEL_SHIFT 8 /* MCLKSEL */ +#define WM8955_MCLKSEL_WIDTH 1 /* MCLKSEL */ +#define WM8955_PLLOUTDIV2 0x0020 /* PLLOUTDIV2 */ +#define WM8955_PLLOUTDIV2_MASK 0x0020 /* PLLOUTDIV2 */ +#define WM8955_PLLOUTDIV2_SHIFT 5 /* PLLOUTDIV2 */ +#define WM8955_PLLOUTDIV2_WIDTH 1 /* PLLOUTDIV2 */ +#define WM8955_PLL_RB 0x0010 /* PLL_RB */ +#define WM8955_PLL_RB_MASK 0x0010 /* PLL_RB */ +#define WM8955_PLL_RB_SHIFT 4 /* PLL_RB */ +#define WM8955_PLL_RB_WIDTH 1 /* PLL_RB */ +#define WM8955_PLLEN 0x0008 /* PLLEN */ +#define WM8955_PLLEN_MASK 0x0008 /* PLLEN */ +#define WM8955_PLLEN_SHIFT 3 /* PLLEN */ +#define WM8955_PLLEN_WIDTH 1 /* PLLEN */ + +/* + * R44 (0x2C) - PLL Control 1 + */ +#define WM8955_N_MASK 0x01E0 /* N - [8:5] */ +#define WM8955_N_SHIFT 5 /* N - [8:5] */ +#define WM8955_N_WIDTH 4 /* N - [8:5] */ +#define WM8955_K_21_18_MASK 0x000F /* K(21:18) - [3:0] */ +#define WM8955_K_21_18_SHIFT 0 /* K(21:18) - [3:0] */ +#define WM8955_K_21_18_WIDTH 4 /* K(21:18) - [3:0] */ + +/* + * R45 (0x2D) - PLL Control 2 + */ +#define WM8955_K_17_9_MASK 0x01FF /* K(17:9) - [8:0] */ +#define WM8955_K_17_9_SHIFT 0 /* K(17:9) - [8:0] */ +#define WM8955_K_17_9_WIDTH 9 /* K(17:9) - [8:0] */ + +/* + * R46 (0x2E) - PLL Control 3 + */ +#define WM8955_K_8_0_MASK 0x01FF /* K(8:0) - [8:0] */ +#define WM8955_K_8_0_SHIFT 0 /* K(8:0) - [8:0] */ +#define WM8955_K_8_0_WIDTH 9 /* K(8:0) - [8:0] */ + +/* + * R59 (0x3B) - PLL Control 4 + */ +#define WM8955_KEN 0x0080 /* KEN */ +#define WM8955_KEN_MASK 0x0080 /* KEN */ +#define WM8955_KEN_SHIFT 7 /* KEN */ +#define WM8955_KEN_WIDTH 1 /* KEN */ + +#endif diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c new file mode 100644 index 000000000..91de7ff3a --- /dev/null +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -0,0 +1,1039 @@ +/* + * wm8958-dsp2.c -- WM8958 DSP2 support + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "wm8994.h" + +#define WM_FW_BLOCK_INFO 0xff +#define WM_FW_BLOCK_PM 0x00 +#define WM_FW_BLOCK_X 0x01 +#define WM_FW_BLOCK_Y 0x02 +#define WM_FW_BLOCK_Z 0x03 +#define WM_FW_BLOCK_I 0x06 +#define WM_FW_BLOCK_A 0x08 +#define WM_FW_BLOCK_C 0x0c + +static int wm8958_dsp2_fw(struct snd_soc_component *component, const char *name, + const struct firmware *fw, bool check) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + u64 data64; + u32 data32; + const u8 *data; + char *str; + size_t block_len, len; + int ret = 0; + + /* Suppress unneeded downloads */ + if (wm8994->cur_fw == fw) + return 0; + + if (fw->size < 32) { + dev_err(component->dev, "%s: firmware too short (%zd bytes)\n", + name, fw->size); + goto err; + } + + if (memcmp(fw->data, "WMFW", 4) != 0) { + memcpy(&data32, fw->data, sizeof(data32)); + data32 = be32_to_cpu(data32); + dev_err(component->dev, "%s: firmware has bad file magic %08x\n", + name, data32); + goto err; + } + + memcpy(&data32, fw->data + 4, sizeof(data32)); + len = be32_to_cpu(data32); + + memcpy(&data32, fw->data + 8, sizeof(data32)); + data32 = be32_to_cpu(data32); + if ((data32 >> 24) & 0xff) { + dev_err(component->dev, "%s: unsupported firmware version %d\n", + name, (data32 >> 24) & 0xff); + goto err; + } + if ((data32 & 0xffff) != 8958) { + dev_err(component->dev, "%s: unsupported target device %d\n", + name, data32 & 0xffff); + goto err; + } + if (((data32 >> 16) & 0xff) != 0xc) { + dev_err(component->dev, "%s: unsupported target core %d\n", + name, (data32 >> 16) & 0xff); + goto err; + } + + if (check) { + memcpy(&data64, fw->data + 24, sizeof(u64)); + dev_info(component->dev, "%s timestamp %llx\n", + name, be64_to_cpu(data64)); + } else { + snd_soc_component_write(component, 0x102, 0x2); + snd_soc_component_write(component, 0x900, 0x2); + } + + data = fw->data + len; + len = fw->size - len; + while (len) { + if (len < 12) { + dev_err(component->dev, "%s short data block of %zd\n", + name, len); + goto err; + } + + memcpy(&data32, data + 4, sizeof(data32)); + block_len = be32_to_cpu(data32); + if (block_len + 8 > len) { + dev_err(component->dev, "%zd byte block longer than file\n", + block_len); + goto err; + } + if (block_len == 0) { + dev_err(component->dev, "Zero length block\n"); + goto err; + } + + memcpy(&data32, data, sizeof(data32)); + data32 = be32_to_cpu(data32); + + switch ((data32 >> 24) & 0xff) { + case WM_FW_BLOCK_INFO: + /* Informational text */ + if (!check) + break; + + str = kzalloc(block_len + 1, GFP_KERNEL); + if (str) { + memcpy(str, data + 8, block_len); + dev_info(component->dev, "%s: %s\n", name, str); + kfree(str); + } else { + dev_err(component->dev, "Out of memory\n"); + } + break; + case WM_FW_BLOCK_PM: + case WM_FW_BLOCK_X: + case WM_FW_BLOCK_Y: + case WM_FW_BLOCK_Z: + case WM_FW_BLOCK_I: + case WM_FW_BLOCK_A: + case WM_FW_BLOCK_C: + dev_dbg(component->dev, "%s: %zd bytes of %x@%x\n", name, + block_len, (data32 >> 24) & 0xff, + data32 & 0xffffff); + + if (check) + break; + + data32 &= 0xffffff; + + wm8994_bulk_write(wm8994->wm8994, + data32 & 0xffffff, + block_len / 2, + (void *)(data + 8)); + + break; + default: + dev_warn(component->dev, "%s: unknown block type %d\n", + name, (data32 >> 24) & 0xff); + break; + } + + /* Round up to the next 32 bit word */ + block_len += block_len % 4; + + data += block_len + 8; + len -= block_len + 8; + } + + if (!check) { + dev_dbg(component->dev, "%s: download done\n", name); + wm8994->cur_fw = fw; + } else { + dev_info(component->dev, "%s: got firmware\n", name); + } + + goto ok; + +err: + ret = -EINVAL; +ok: + if (!check) { + snd_soc_component_write(component, 0x900, 0x0); + snd_soc_component_write(component, 0x102, 0x0); + } + + return ret; +} + +static void wm8958_dsp_start_mbc(struct snd_soc_component *component, int path) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int i; + + /* If the DSP is already running then noop */ + if (snd_soc_component_read32(component, WM8958_DSP2_PROGRAM) & WM8958_DSP2_ENA) + return; + + /* If we have MBC firmware download it */ + if (wm8994->mbc) + wm8958_dsp2_fw(component, "MBC", wm8994->mbc, false); + + snd_soc_component_update_bits(component, WM8958_DSP2_PROGRAM, + WM8958_DSP2_ENA, WM8958_DSP2_ENA); + + /* If we've got user supplied MBC settings use them */ + if (control->pdata.num_mbc_cfgs) { + struct wm8958_mbc_cfg *cfg + = &control->pdata.mbc_cfgs[wm8994->mbc_cfg]; + + for (i = 0; i < ARRAY_SIZE(cfg->coeff_regs); i++) + snd_soc_component_write(component, i + WM8958_MBC_BAND_1_K_1, + cfg->coeff_regs[i]); + + for (i = 0; i < ARRAY_SIZE(cfg->cutoff_regs); i++) + snd_soc_component_write(component, + i + WM8958_MBC_BAND_2_LOWER_CUTOFF_C1_1, + cfg->cutoff_regs[i]); + } + + /* Run the DSP */ + snd_soc_component_write(component, WM8958_DSP2_EXECCONTROL, + WM8958_DSP2_RUNR); + + /* And we're off! */ + snd_soc_component_update_bits(component, WM8958_DSP2_CONFIG, + WM8958_MBC_ENA | + WM8958_MBC_SEL_MASK, + path << WM8958_MBC_SEL_SHIFT | + WM8958_MBC_ENA); +} + +static void wm8958_dsp_start_vss(struct snd_soc_component *component, int path) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int i, ena; + + if (wm8994->mbc_vss) + wm8958_dsp2_fw(component, "MBC+VSS", wm8994->mbc_vss, false); + + snd_soc_component_update_bits(component, WM8958_DSP2_PROGRAM, + WM8958_DSP2_ENA, WM8958_DSP2_ENA); + + /* If we've got user supplied settings use them */ + if (control->pdata.num_mbc_cfgs) { + struct wm8958_mbc_cfg *cfg + = &control->pdata.mbc_cfgs[wm8994->mbc_cfg]; + + for (i = 0; i < ARRAY_SIZE(cfg->combined_regs); i++) + snd_soc_component_write(component, i + 0x2800, + cfg->combined_regs[i]); + } + + if (control->pdata.num_vss_cfgs) { + struct wm8958_vss_cfg *cfg + = &control->pdata.vss_cfgs[wm8994->vss_cfg]; + + for (i = 0; i < ARRAY_SIZE(cfg->regs); i++) + snd_soc_component_write(component, i + 0x2600, cfg->regs[i]); + } + + if (control->pdata.num_vss_hpf_cfgs) { + struct wm8958_vss_hpf_cfg *cfg + = &control->pdata.vss_hpf_cfgs[wm8994->vss_hpf_cfg]; + + for (i = 0; i < ARRAY_SIZE(cfg->regs); i++) + snd_soc_component_write(component, i + 0x2400, cfg->regs[i]); + } + + /* Run the DSP */ + snd_soc_component_write(component, WM8958_DSP2_EXECCONTROL, + WM8958_DSP2_RUNR); + + /* Enable the algorithms we've selected */ + ena = 0; + if (wm8994->mbc_ena[path]) + ena |= 0x8; + if (wm8994->hpf2_ena[path]) + ena |= 0x4; + if (wm8994->hpf1_ena[path]) + ena |= 0x2; + if (wm8994->vss_ena[path]) + ena |= 0x1; + + snd_soc_component_write(component, 0x2201, ena); + + /* Switch the DSP into the data path */ + snd_soc_component_update_bits(component, WM8958_DSP2_CONFIG, + WM8958_MBC_SEL_MASK | WM8958_MBC_ENA, + path << WM8958_MBC_SEL_SHIFT | WM8958_MBC_ENA); +} + +static void wm8958_dsp_start_enh_eq(struct snd_soc_component *component, int path) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int i; + + wm8958_dsp2_fw(component, "ENH_EQ", wm8994->enh_eq, false); + + snd_soc_component_update_bits(component, WM8958_DSP2_PROGRAM, + WM8958_DSP2_ENA, WM8958_DSP2_ENA); + + /* If we've got user supplied settings use them */ + if (control->pdata.num_enh_eq_cfgs) { + struct wm8958_enh_eq_cfg *cfg + = &control->pdata.enh_eq_cfgs[wm8994->enh_eq_cfg]; + + for (i = 0; i < ARRAY_SIZE(cfg->regs); i++) + snd_soc_component_write(component, i + 0x2200, + cfg->regs[i]); + } + + /* Run the DSP */ + snd_soc_component_write(component, WM8958_DSP2_EXECCONTROL, + WM8958_DSP2_RUNR); + + /* Switch the DSP into the data path */ + snd_soc_component_update_bits(component, WM8958_DSP2_CONFIG, + WM8958_MBC_SEL_MASK | WM8958_MBC_ENA, + path << WM8958_MBC_SEL_SHIFT | WM8958_MBC_ENA); +} + +static void wm8958_dsp_apply(struct snd_soc_component *component, int path, int start) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int pwr_reg = snd_soc_component_read32(component, WM8994_POWER_MANAGEMENT_5); + int ena, reg, aif; + + switch (path) { + case 0: + pwr_reg &= (WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC1R_ENA); + aif = 0; + break; + case 1: + pwr_reg &= (WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA); + aif = 0; + break; + case 2: + pwr_reg &= (WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA); + aif = 1; + break; + default: + WARN(1, "Invalid path %d\n", path); + return; + } + + /* Do we have both an active AIF and an active algorithm? */ + ena = wm8994->mbc_ena[path] || wm8994->vss_ena[path] || + wm8994->hpf1_ena[path] || wm8994->hpf2_ena[path] || + wm8994->enh_eq_ena[path]; + if (!pwr_reg) + ena = 0; + + reg = snd_soc_component_read32(component, WM8958_DSP2_PROGRAM); + + dev_dbg(component->dev, "DSP path %d %d startup: %d, power: %x, DSP: %x\n", + path, wm8994->dsp_active, start, pwr_reg, reg); + + if (start && ena) { + /* If the DSP is already running then noop */ + if (reg & WM8958_DSP2_ENA) + return; + + /* If either AIFnCLK is not yet enabled postpone */ + if (!(snd_soc_component_read32(component, WM8994_AIF1_CLOCKING_1) + & WM8994_AIF1CLK_ENA_MASK) && + !(snd_soc_component_read32(component, WM8994_AIF2_CLOCKING_1) + & WM8994_AIF2CLK_ENA_MASK)) + return; + + /* Switch the clock over to the appropriate AIF */ + snd_soc_component_update_bits(component, WM8994_CLOCKING_1, + WM8958_DSP2CLK_SRC | WM8958_DSP2CLK_ENA, + aif << WM8958_DSP2CLK_SRC_SHIFT | + WM8958_DSP2CLK_ENA); + + if (wm8994->enh_eq_ena[path]) + wm8958_dsp_start_enh_eq(component, path); + else if (wm8994->vss_ena[path] || wm8994->hpf1_ena[path] || + wm8994->hpf2_ena[path]) + wm8958_dsp_start_vss(component, path); + else if (wm8994->mbc_ena[path]) + wm8958_dsp_start_mbc(component, path); + + wm8994->dsp_active = path; + + dev_dbg(component->dev, "DSP running in path %d\n", path); + } + + if (!start && wm8994->dsp_active == path) { + /* If the DSP is already stopped then noop */ + if (!(reg & WM8958_DSP2_ENA)) + return; + + snd_soc_component_update_bits(component, WM8958_DSP2_CONFIG, + WM8958_MBC_ENA, 0); + snd_soc_component_write(component, WM8958_DSP2_EXECCONTROL, + WM8958_DSP2_STOP); + snd_soc_component_update_bits(component, WM8958_DSP2_PROGRAM, + WM8958_DSP2_ENA, 0); + snd_soc_component_update_bits(component, WM8994_CLOCKING_1, + WM8958_DSP2CLK_ENA, 0); + + wm8994->dsp_active = -1; + + dev_dbg(component->dev, "DSP stopped\n"); + } +} + +int wm8958_aif_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8994 *control = dev_get_drvdata(component->dev->parent); + int i; + + if (control->type != WM8958) + return 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + case SND_SOC_DAPM_PRE_PMU: + for (i = 0; i < 3; i++) + wm8958_dsp_apply(component, i, 1); + break; + case SND_SOC_DAPM_POST_PMD: + case SND_SOC_DAPM_PRE_PMD: + for (i = 0; i < 3; i++) + wm8958_dsp_apply(component, i, 0); + break; + } + + return 0; +} + +/* Check if DSP2 is in use on another AIF */ +static int wm8958_dsp2_busy(struct wm8994_priv *wm8994, int aif) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm8994->mbc_ena); i++) { + if (i == aif) + continue; + if (wm8994->mbc_ena[i] || wm8994->vss_ena[i] || + wm8994->hpf1_ena[i] || wm8994->hpf2_ena[i]) + return 1; + } + + return 0; +} + +static int wm8958_put_mbc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int value = ucontrol->value.enumerated.item[0]; + int reg; + + /* Don't allow on the fly reconfiguration */ + reg = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (reg < 0 || reg & WM8958_DSP2CLK_ENA) + return -EBUSY; + + if (value >= control->pdata.num_mbc_cfgs) + return -EINVAL; + + wm8994->mbc_cfg = value; + + return 0; +} + +static int wm8958_get_mbc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wm8994->mbc_cfg; + + return 0; +} + +static int wm8958_mbc_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 wm8958_mbc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mbc = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8994->mbc_ena[mbc]; + + return 0; +} + +static int wm8958_mbc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mbc = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (wm8994->mbc_ena[mbc] == ucontrol->value.integer.value[0]) + return 0; + + if (ucontrol->value.integer.value[0] > 1) + return -EINVAL; + + if (wm8958_dsp2_busy(wm8994, mbc)) { + dev_dbg(component->dev, "DSP2 active on %d already\n", mbc); + return -EBUSY; + } + + if (wm8994->enh_eq_ena[mbc]) + return -EBUSY; + + wm8994->mbc_ena[mbc] = ucontrol->value.integer.value[0]; + + wm8958_dsp_apply(component, mbc, wm8994->mbc_ena[mbc]); + + return 1; +} + +#define WM8958_MBC_SWITCH(xname, xval) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .info = wm8958_mbc_info, \ + .get = wm8958_mbc_get, .put = wm8958_mbc_put, \ + .private_value = xval } + +static int wm8958_put_vss_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int value = ucontrol->value.enumerated.item[0]; + int reg; + + /* Don't allow on the fly reconfiguration */ + reg = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (reg < 0 || reg & WM8958_DSP2CLK_ENA) + return -EBUSY; + + if (value >= control->pdata.num_vss_cfgs) + return -EINVAL; + + wm8994->vss_cfg = value; + + return 0; +} + +static int wm8958_get_vss_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wm8994->vss_cfg; + + return 0; +} + +static int wm8958_put_vss_hpf_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int value = ucontrol->value.enumerated.item[0]; + int reg; + + /* Don't allow on the fly reconfiguration */ + reg = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (reg < 0 || reg & WM8958_DSP2CLK_ENA) + return -EBUSY; + + if (value >= control->pdata.num_vss_hpf_cfgs) + return -EINVAL; + + wm8994->vss_hpf_cfg = value; + + return 0; +} + +static int wm8958_get_vss_hpf_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wm8994->vss_hpf_cfg; + + return 0; +} + +static int wm8958_vss_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 wm8958_vss_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int vss = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8994->vss_ena[vss]; + + return 0; +} + +static int wm8958_vss_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int vss = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (wm8994->vss_ena[vss] == ucontrol->value.integer.value[0]) + return 0; + + if (ucontrol->value.integer.value[0] > 1) + return -EINVAL; + + if (!wm8994->mbc_vss) + return -ENODEV; + + if (wm8958_dsp2_busy(wm8994, vss)) { + dev_dbg(component->dev, "DSP2 active on %d already\n", vss); + return -EBUSY; + } + + if (wm8994->enh_eq_ena[vss]) + return -EBUSY; + + wm8994->vss_ena[vss] = ucontrol->value.integer.value[0]; + + wm8958_dsp_apply(component, vss, wm8994->vss_ena[vss]); + + return 1; +} + + +#define WM8958_VSS_SWITCH(xname, xval) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .info = wm8958_vss_info, \ + .get = wm8958_vss_get, .put = wm8958_vss_put, \ + .private_value = xval } + +static int wm8958_hpf_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 wm8958_hpf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int hpf = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (hpf < 3) + ucontrol->value.integer.value[0] = wm8994->hpf1_ena[hpf % 3]; + else + ucontrol->value.integer.value[0] = wm8994->hpf2_ena[hpf % 3]; + + return 0; +} + +static int wm8958_hpf_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int hpf = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (hpf < 3) { + if (wm8994->hpf1_ena[hpf % 3] == + ucontrol->value.integer.value[0]) + return 0; + } else { + if (wm8994->hpf2_ena[hpf % 3] == + ucontrol->value.integer.value[0]) + return 0; + } + + if (ucontrol->value.integer.value[0] > 1) + return -EINVAL; + + if (!wm8994->mbc_vss) + return -ENODEV; + + if (wm8958_dsp2_busy(wm8994, hpf % 3)) { + dev_dbg(component->dev, "DSP2 active on %d already\n", hpf); + return -EBUSY; + } + + if (wm8994->enh_eq_ena[hpf % 3]) + return -EBUSY; + + if (hpf < 3) + wm8994->hpf1_ena[hpf % 3] = ucontrol->value.integer.value[0]; + else + wm8994->hpf2_ena[hpf % 3] = ucontrol->value.integer.value[0]; + + wm8958_dsp_apply(component, hpf % 3, ucontrol->value.integer.value[0]); + + return 1; +} + +#define WM8958_HPF_SWITCH(xname, xval) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .info = wm8958_hpf_info, \ + .get = wm8958_hpf_get, .put = wm8958_hpf_put, \ + .private_value = xval } + +static int wm8958_put_enh_eq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int value = ucontrol->value.enumerated.item[0]; + int reg; + + /* Don't allow on the fly reconfiguration */ + reg = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (reg < 0 || reg & WM8958_DSP2CLK_ENA) + return -EBUSY; + + if (value >= control->pdata.num_enh_eq_cfgs) + return -EINVAL; + + wm8994->enh_eq_cfg = value; + + return 0; +} + +static int wm8958_get_enh_eq_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wm8994->enh_eq_cfg; + + return 0; +} + +static int wm8958_enh_eq_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 wm8958_enh_eq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8994->enh_eq_ena[eq]; + + return 0; +} + +static int wm8958_enh_eq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int eq = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (wm8994->enh_eq_ena[eq] == ucontrol->value.integer.value[0]) + return 0; + + if (ucontrol->value.integer.value[0] > 1) + return -EINVAL; + + if (!wm8994->enh_eq) + return -ENODEV; + + if (wm8958_dsp2_busy(wm8994, eq)) { + dev_dbg(component->dev, "DSP2 active on %d already\n", eq); + return -EBUSY; + } + + if (wm8994->mbc_ena[eq] || wm8994->vss_ena[eq] || + wm8994->hpf1_ena[eq] || wm8994->hpf2_ena[eq]) + return -EBUSY; + + wm8994->enh_eq_ena[eq] = ucontrol->value.integer.value[0]; + + wm8958_dsp_apply(component, eq, ucontrol->value.integer.value[0]); + + return 1; +} + +#define WM8958_ENH_EQ_SWITCH(xname, xval) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .info = wm8958_enh_eq_info, \ + .get = wm8958_enh_eq_get, .put = wm8958_enh_eq_put, \ + .private_value = xval } + +static const struct snd_kcontrol_new wm8958_mbc_snd_controls[] = { +WM8958_MBC_SWITCH("AIF1DAC1 MBC Switch", 0), +WM8958_MBC_SWITCH("AIF1DAC2 MBC Switch", 1), +WM8958_MBC_SWITCH("AIF2DAC MBC Switch", 2), +}; + +static const struct snd_kcontrol_new wm8958_vss_snd_controls[] = { +WM8958_VSS_SWITCH("AIF1DAC1 VSS Switch", 0), +WM8958_VSS_SWITCH("AIF1DAC2 VSS Switch", 1), +WM8958_VSS_SWITCH("AIF2DAC VSS Switch", 2), +WM8958_HPF_SWITCH("AIF1DAC1 HPF1 Switch", 0), +WM8958_HPF_SWITCH("AIF1DAC2 HPF1 Switch", 1), +WM8958_HPF_SWITCH("AIF2DAC HPF1 Switch", 2), +WM8958_HPF_SWITCH("AIF1DAC1 HPF2 Switch", 3), +WM8958_HPF_SWITCH("AIF1DAC2 HPF2 Switch", 4), +WM8958_HPF_SWITCH("AIF2DAC HPF2 Switch", 5), +}; + +static const struct snd_kcontrol_new wm8958_enh_eq_snd_controls[] = { +WM8958_ENH_EQ_SWITCH("AIF1DAC1 Enhanced EQ Switch", 0), +WM8958_ENH_EQ_SWITCH("AIF1DAC2 Enhanced EQ Switch", 1), +WM8958_ENH_EQ_SWITCH("AIF2DAC Enhanced EQ Switch", 2), +}; + +static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context) +{ + struct snd_soc_component *component = context; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "ENH_EQ", fw, true) == 0)) { + mutex_lock(&wm8994->fw_lock); + wm8994->enh_eq = fw; + mutex_unlock(&wm8994->fw_lock); + } +} + +static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context) +{ + struct snd_soc_component *component = context; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC+VSS", fw, true) == 0)) { + mutex_lock(&wm8994->fw_lock); + wm8994->mbc_vss = fw; + mutex_unlock(&wm8994->fw_lock); + } +} + +static void wm8958_mbc_loaded(const struct firmware *fw, void *context) +{ + struct snd_soc_component *component = context; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC", fw, true) == 0)) { + mutex_lock(&wm8994->fw_lock); + wm8994->mbc = fw; + mutex_unlock(&wm8994->fw_lock); + } +} + +void wm8958_dsp2_init(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + int ret, i; + + wm8994->dsp_active = -1; + + snd_soc_add_component_controls(component, wm8958_mbc_snd_controls, + ARRAY_SIZE(wm8958_mbc_snd_controls)); + snd_soc_add_component_controls(component, wm8958_vss_snd_controls, + ARRAY_SIZE(wm8958_vss_snd_controls)); + snd_soc_add_component_controls(component, wm8958_enh_eq_snd_controls, + ARRAY_SIZE(wm8958_enh_eq_snd_controls)); + + + /* We don't *require* firmware and don't want to delay boot */ + request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "wm8958_mbc.wfw", component->dev, GFP_KERNEL, + component, wm8958_mbc_loaded); + request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "wm8958_mbc_vss.wfw", component->dev, GFP_KERNEL, + component, wm8958_mbc_vss_loaded); + request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "wm8958_enh_eq.wfw", component->dev, GFP_KERNEL, + component, wm8958_enh_eq_loaded); + + if (pdata->num_mbc_cfgs) { + struct snd_kcontrol_new control[] = { + SOC_ENUM_EXT("MBC Mode", wm8994->mbc_enum, + wm8958_get_mbc_enum, wm8958_put_mbc_enum), + }; + + /* We need an array of texts for the enum API */ + wm8994->mbc_texts = kmalloc_array(pdata->num_mbc_cfgs, + sizeof(char *), + GFP_KERNEL); + if (!wm8994->mbc_texts) + return; + + for (i = 0; i < pdata->num_mbc_cfgs; i++) + wm8994->mbc_texts[i] = pdata->mbc_cfgs[i].name; + + wm8994->mbc_enum.items = pdata->num_mbc_cfgs; + wm8994->mbc_enum.texts = wm8994->mbc_texts; + + ret = snd_soc_add_component_controls(wm8994->hubs.component, + control, 1); + if (ret != 0) + dev_err(wm8994->hubs.component->dev, + "Failed to add MBC mode controls: %d\n", ret); + } + + if (pdata->num_vss_cfgs) { + struct snd_kcontrol_new control[] = { + SOC_ENUM_EXT("VSS Mode", wm8994->vss_enum, + wm8958_get_vss_enum, wm8958_put_vss_enum), + }; + + /* We need an array of texts for the enum API */ + wm8994->vss_texts = kmalloc_array(pdata->num_vss_cfgs, + sizeof(char *), + GFP_KERNEL); + if (!wm8994->vss_texts) + return; + + for (i = 0; i < pdata->num_vss_cfgs; i++) + wm8994->vss_texts[i] = pdata->vss_cfgs[i].name; + + wm8994->vss_enum.items = pdata->num_vss_cfgs; + wm8994->vss_enum.texts = wm8994->vss_texts; + + ret = snd_soc_add_component_controls(wm8994->hubs.component, + control, 1); + if (ret != 0) + dev_err(wm8994->hubs.component->dev, + "Failed to add VSS mode controls: %d\n", ret); + } + + if (pdata->num_vss_hpf_cfgs) { + struct snd_kcontrol_new control[] = { + SOC_ENUM_EXT("VSS HPF Mode", wm8994->vss_hpf_enum, + wm8958_get_vss_hpf_enum, + wm8958_put_vss_hpf_enum), + }; + + /* We need an array of texts for the enum API */ + wm8994->vss_hpf_texts = kmalloc_array(pdata->num_vss_hpf_cfgs, + sizeof(char *), + GFP_KERNEL); + if (!wm8994->vss_hpf_texts) + return; + + for (i = 0; i < pdata->num_vss_hpf_cfgs; i++) + wm8994->vss_hpf_texts[i] = pdata->vss_hpf_cfgs[i].name; + + wm8994->vss_hpf_enum.items = pdata->num_vss_hpf_cfgs; + wm8994->vss_hpf_enum.texts = wm8994->vss_hpf_texts; + + ret = snd_soc_add_component_controls(wm8994->hubs.component, + control, 1); + if (ret != 0) + dev_err(wm8994->hubs.component->dev, + "Failed to add VSS HPFmode controls: %d\n", + ret); + } + + if (pdata->num_enh_eq_cfgs) { + struct snd_kcontrol_new control[] = { + SOC_ENUM_EXT("Enhanced EQ Mode", wm8994->enh_eq_enum, + wm8958_get_enh_eq_enum, + wm8958_put_enh_eq_enum), + }; + + /* We need an array of texts for the enum API */ + wm8994->enh_eq_texts = kmalloc_array(pdata->num_enh_eq_cfgs, + sizeof(char *), + GFP_KERNEL); + if (!wm8994->enh_eq_texts) + return; + + for (i = 0; i < pdata->num_enh_eq_cfgs; i++) + wm8994->enh_eq_texts[i] = pdata->enh_eq_cfgs[i].name; + + wm8994->enh_eq_enum.items = pdata->num_enh_eq_cfgs; + wm8994->enh_eq_enum.texts = wm8994->enh_eq_texts; + + ret = snd_soc_add_component_controls(wm8994->hubs.component, + control, 1); + if (ret != 0) + dev_err(wm8994->hubs.component->dev, + "Failed to add enhanced EQ controls: %d\n", + ret); + } +} diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c new file mode 100644 index 000000000..abd5c1276 --- /dev/null +++ b/sound/soc/codecs/wm8960.c @@ -0,0 +1,1504 @@ +/* + * wm8960.c -- WM8960 ALSA SoC Audio driver + * + * Copyright 2007-11 Wolfson Microelectronics, plc + * + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8960.h" + +/* R25 - Power 1 */ +#define WM8960_VMID_MASK 0x180 +#define WM8960_VREF 0x40 + +/* R26 - Power 2 */ +#define WM8960_PWR2_LOUT1 0x40 +#define WM8960_PWR2_ROUT1 0x20 +#define WM8960_PWR2_OUT3 0x02 + +/* R28 - Anti-pop 1 */ +#define WM8960_POBCTRL 0x80 +#define WM8960_BUFDCOPEN 0x10 +#define WM8960_BUFIOEN 0x08 +#define WM8960_SOFT_ST 0x04 +#define WM8960_HPSTBY 0x01 + +/* R29 - Anti-pop 2 */ +#define WM8960_DISOP 0x40 +#define WM8960_DRES_MASK 0x30 + +static bool is_pll_freq_available(unsigned int source, unsigned int target); +static int wm8960_set_pll(struct snd_soc_component *component, + unsigned int freq_in, unsigned int freq_out); +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8960_reg_defaults[] = { + { 0x0, 0x00a7 }, + { 0x1, 0x00a7 }, + { 0x2, 0x0000 }, + { 0x3, 0x0000 }, + { 0x4, 0x0000 }, + { 0x5, 0x0008 }, + { 0x6, 0x0000 }, + { 0x7, 0x000a }, + { 0x8, 0x01c0 }, + { 0x9, 0x0000 }, + { 0xa, 0x00ff }, + { 0xb, 0x00ff }, + + { 0x10, 0x0000 }, + { 0x11, 0x007b }, + { 0x12, 0x0100 }, + { 0x13, 0x0032 }, + { 0x14, 0x0000 }, + { 0x15, 0x00c3 }, + { 0x16, 0x00c3 }, + { 0x17, 0x01c0 }, + { 0x18, 0x0000 }, + { 0x19, 0x0000 }, + { 0x1a, 0x0000 }, + { 0x1b, 0x0000 }, + { 0x1c, 0x0000 }, + { 0x1d, 0x0000 }, + + { 0x20, 0x0100 }, + { 0x21, 0x0100 }, + { 0x22, 0x0050 }, + + { 0x25, 0x0050 }, + { 0x26, 0x0000 }, + { 0x27, 0x0000 }, + { 0x28, 0x0000 }, + { 0x29, 0x0000 }, + { 0x2a, 0x0040 }, + { 0x2b, 0x0000 }, + { 0x2c, 0x0000 }, + { 0x2d, 0x0050 }, + { 0x2e, 0x0050 }, + { 0x2f, 0x0000 }, + { 0x30, 0x0002 }, + { 0x31, 0x0037 }, + + { 0x33, 0x0080 }, + { 0x34, 0x0008 }, + { 0x35, 0x0031 }, + { 0x36, 0x0026 }, + { 0x37, 0x00e9 }, +}; + +static bool wm8960_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8960_RESET: + return true; + default: + return false; + } +} + +struct wm8960_priv { + struct clk *mclk; + struct regmap *regmap; + int (*set_bias_level)(struct snd_soc_component *, + enum snd_soc_bias_level level); + struct snd_soc_dapm_widget *lout1; + struct snd_soc_dapm_widget *rout1; + struct snd_soc_dapm_widget *out3; + bool deemph; + int lrclk; + int bclk; + int sysclk; + int clk_id; + int freq_in; + bool is_stream_in_use[2]; + struct wm8960_data pdata; +}; + +#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) + +/* enumerated controls */ +static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", + "Right Inverted", "Stereo Inversion"}; +static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; +static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; +static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; +static const char *wm8960_adc_data_output_sel[] = { + "Left Data = Left ADC; Right Data = Right ADC", + "Left Data = Left ADC; Right Data = Left ADC", + "Left Data = Right ADC; Right Data = Right ADC", + "Left Data = Right ADC; Right Data = Left ADC", +}; +static const char *wm8960_dmonomix[] = {"Stereo", "Mono"}; + +static const struct soc_enum wm8960_enum[] = { + SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), + SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), + SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), + SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix), +}; + +static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8960_set_deemph(struct snd_soc_component *component) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8960->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8960->lrclk) < + abs(deemph_settings[best] - wm8960->lrclk)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(component->dev, "Set deemphasis %d\n", val); + + return snd_soc_component_update_bits(component, WM8960_DACCTL1, + 0x6, val); +} + +static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = wm8960->deemph; + return 0; +} + +static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + wm8960->deemph = deemph; + + return wm8960_set_deemph(component); +} + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 1300, 0), + 2, 3, TLV_DB_SCALE_ITEM(2000, 900, 0), +); + +static const struct snd_kcontrol_new wm8960_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, + 0, 63, 0, inpga_tlv), +SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, + 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, + 7, 1, 1), + +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume", + WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume", + WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume", + WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume", + WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume", + WM8960_RINPATH, 4, 3, 0, micboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume", + WM8960_LINPATH, 4, 3, 0, micboost_tlv), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, + 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, + 7, 1, 0), +SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), + +SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), +SOC_ENUM("ADC Polarity", wm8960_enum[0]), +SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), + +SOC_ENUM("DAC Polarity", wm8960_enum[1]), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8960_get_deemph, wm8960_put_deemph), + +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), +SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), + +SOC_ENUM("ALC Function", wm8960_enum[4]), +SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), +SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), +SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), +SOC_ENUM("ALC Mode", wm8960_enum[5]), +SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), + +SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), +SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), + +SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC, + 0, 255, 0, adc_tlv), + +SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", + WM8960_BYPASS1, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", + WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", + WM8960_BYPASS2, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", + WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), + +SOC_ENUM("ADC Data Output Select", wm8960_enum[6]), +SOC_ENUM("DAC Mono Mix", wm8960_enum[7]), +}; + +static const struct snd_kcontrol_new wm8960_lin_boost[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_lin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin_boost[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_routput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_mono_out[] = { +SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("RINPUT3"), + +SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, + wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), +SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, + wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, + wm8960_lin, ARRAY_SIZE(wm8960_lin)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, + wm8960_rin, ARRAY_SIZE(wm8960_rin)), + +SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0), + +SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), +SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, + &wm8960_loutput_mixer[0], + ARRAY_SIZE(wm8960_loutput_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, + &wm8960_routput_mixer[0], + ARRAY_SIZE(wm8960_routput_mixer)), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), +}; + +/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { +SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, + { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, + { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, + + { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" }, + { "Left Input Mixer", "Boost Switch", "LINPUT1" }, /* Really Boost Switch */ + { "Left Input Mixer", NULL, "LINPUT2" }, + { "Left Input Mixer", NULL, "LINPUT3" }, + + { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, + { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, + { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, + + { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" }, + { "Right Input Mixer", "Boost Switch", "RINPUT1" }, /* Really Boost Switch */ + { "Right Input Mixer", NULL, "RINPUT2" }, + { "Right Input Mixer", NULL, "RINPUT3" }, + + { "Left ADC", NULL, "Left Input Mixer" }, + { "Right ADC", NULL, "Right Input Mixer" }, + + { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, + { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" }, + { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, + + { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, + { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" }, + { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, + + { "LOUT1 PGA", NULL, "Left Output Mixer" }, + { "ROUT1 PGA", NULL, "Right Output Mixer" }, + + { "HP_L", NULL, "LOUT1 PGA" }, + { "HP_R", NULL, "ROUT1 PGA" }, + + { "Left Speaker PGA", NULL, "Left Output Mixer" }, + { "Right Speaker PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker Output", NULL, "Left Speaker PGA" }, + { "Right Speaker Output", NULL, "Right Speaker PGA" }, + + { "SPK_LN", NULL, "Left Speaker Output" }, + { "SPK_LP", NULL, "Left Speaker Output" }, + { "SPK_RN", NULL, "Right Speaker Output" }, + { "SPK_RP", NULL, "Right Speaker Output" }, +}; + +static const struct snd_soc_dapm_route audio_paths_out3[] = { + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, + + { "OUT3", NULL, "Mono Output Mixer", } +}; + +static const struct snd_soc_dapm_route audio_paths_capless[] = { + { "HP_L", NULL, "OUT3 VMID" }, + { "HP_R", NULL, "OUT3 VMID" }, + + { "OUT3 VMID", NULL, "Left Output Mixer" }, + { "OUT3 VMID", NULL, "Right Output Mixer" }, +}; + +static int wm8960_add_widgets(struct snd_soc_component *component) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct wm8960_data *pdata = &wm8960->pdata; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct snd_soc_dapm_widget *w; + + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, + ARRAY_SIZE(wm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); + + /* In capless mode OUT3 is used to provide VMID for the + * headphone outputs, otherwise it is used as a mono mixer. + */ + if (pdata && pdata->capless) { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, + ARRAY_SIZE(wm8960_dapm_widgets_capless)); + + snd_soc_dapm_add_routes(dapm, audio_paths_capless, + ARRAY_SIZE(audio_paths_capless)); + } else { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, + ARRAY_SIZE(wm8960_dapm_widgets_out3)); + + snd_soc_dapm_add_routes(dapm, audio_paths_out3, + ARRAY_SIZE(audio_paths_out3)); + } + + /* We need to power up the headphone output stage out of + * sequence for capless mode. To save scanning the widget + * list each time to find the desired power state do so now + * and save the result. + */ + list_for_each_entry(w, &component->card->widgets, list) { + if (w->dapm != dapm) + continue; + if (strcmp(w->name, "LOUT1 PGA") == 0) + wm8960->lout1 = w; + if (strcmp(w->name, "ROUT1 PGA") == 0) + wm8960->rout1 = w; + if (strcmp(w->name, "OUT3 VMID") == 0) + wm8960->out3 = w; + } + + return 0; +} + +static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + snd_soc_component_write(component, WM8960_IFACE1, iface); + return 0; +} + +static struct { + int rate; + unsigned int val; +} alc_rates[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11025, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + +/* -1 for reserved value */ +static const int sysclk_divs[] = { 1, -1, 2, -1 }; + +/* Multiply 256 for internal 256 div */ +static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 }; + +/* Multiply 10 to eliminate decimials */ +static const int bclk_divs[] = { + 10, 15, 20, 30, 40, 55, 60, 80, 110, + 120, 160, 220, 240, 320, 320, 320 +}; + +/** + * wm8960_configure_sysclk - checks if there is a sysclk frequency available + * The sysclk must be chosen such that: + * - sysclk = MCLK / sysclk_divs + * - lrclk = sysclk / dac_divs + * - 10 * bclk = sysclk / bclk_divs + * + * If we cannot find an exact match for (sysclk, lrclk, bclk) + * triplet, we relax the bclk such that bclk is chosen as the + * closest available frequency greater than expected bclk. + * + * @wm8960_priv: wm8960 codec private data + * @mclk: MCLK used to derive sysclk + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no sysclk frequency available found + * >=0, in case we could derive bclk and lrclk from sysclk using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + int sysclk, bclk, lrclk; + int i, j, k; + int diff, closest = mclk; + + /* marker for no match */ + *bclk_idx = -1; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = mclk / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + break; + } + if (diff > 0 && closest > diff) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + closest = diff; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + return *bclk_idx; +} + +/** + * wm8960_configure_pll - checks if there is a PLL out frequency available + * The PLL out frequency must be chosen such that: + * - sysclk = lrclk * dac_divs + * - freq_out = sysclk * sysclk_divs + * - 10 * sysclk = bclk * bclk_divs + * + * If we cannot find an exact match for (sysclk, lrclk, bclk) + * triplet, we relax the bclk such that bclk is chosen as the + * closest available frequency greater than expected bclk. + * + * @component: component structure + * @freq_in: input frequency used to derive freq out via PLL + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * < 0, in case no PLL frequency out available was found + * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int sysclk, bclk, lrclk, freq_out; + int diff, closest, best_freq_out; + int i, j, k; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + closest = freq_in; + + best_freq_out = -EINVAL; + *sysclk_idx = *dac_idx = *bclk_idx = -1; + + /* + * From Datasheet, the PLL performs best when f2 is between + * 90MHz and 100MHz, the desired sysclk output is 11.2896MHz + * or 12.288MHz, then sysclkdiv = 2 is the best choice. + * So search sysclk_divs from 2 to 1 other than from 1 to 2. + */ + for (i = ARRAY_SIZE(sysclk_divs) - 1; i >= 0; --i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (!is_pll_freq_available(freq_in, freq_out)) + continue; + + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + return freq_out; + } + if (diff > 0 && closest > diff) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + closest = diff; + best_freq_out = freq_out; + } + } + } + } + + return best_freq_out; +} +static int wm8960_configure_clocking(struct snd_soc_component *component) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int freq_out, freq_in; + u16 iface1 = snd_soc_component_read32(component, WM8960_IFACE1); + int i, j, k; + int ret; + + /* + * For Slave mode clocking should still be configured, + * so this if statement should be removed, but some platform + * may not work if the sysclk is not configured, to avoid such + * compatible issue, just add '!wm8960->sysclk' condition in + * this if statement. + */ + if (!(iface1 & (1 << 6)) && !wm8960->sysclk) { + dev_warn(component->dev, + "slave mode, but proceeding with no clock configuration\n"); + return 0; + } + + if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) { + dev_err(component->dev, "No MCLK configured\n"); + return -EINVAL; + } + + freq_in = wm8960->freq_in; + /* + * If it's sysclk auto mode, check if the MCLK can provide sysclk or + * not. If MCLK can provide sysclk, using MCLK to provide sysclk + * directly. Otherwise, auto select a available pll out frequency + * and set PLL. + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO) { + /* disable the PLL and using MCLK to provide sysclk */ + wm8960_set_pll(component, 0, 0); + freq_out = freq_in; + } else if (wm8960->sysclk) { + freq_out = wm8960->sysclk; + } else { + dev_err(component->dev, "No SYSCLK configured\n"); + return -EINVAL; + } + + if (wm8960->clk_id != WM8960_SYSCLK_PLL) { + ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); + if (ret >= 0) { + goto configure_clock; + } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { + dev_err(component->dev, "failed to configure clock\n"); + return -EINVAL; + } + } + + freq_out = wm8960_configure_pll(component, freq_in, &i, &j, &k); + if (freq_out < 0) { + dev_err(component->dev, "failed to configure clock via PLL\n"); + return freq_out; + } + wm8960_set_pll(component, freq_in, freq_out); + +configure_clock: + /* configure sysclk clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 3 << 1, i << 1); + + /* configure frame clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 3, j << 3); + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 6, j << 6); + + /* configure bit clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK2, 0xf, k); + + return 0; +} + +static int wm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8960_IFACE1) & 0xfff3; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i; + + wm8960->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8960->bclk *= 2; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + /* right justify mode does not support 32 word length */ + if ((iface & 0x3) != 0) { + iface |= 0x000c; + break; + } + /* fall through */ + default: + dev_err(component->dev, "unsupported width %d\n", + params_width(params)); + return -EINVAL; + } + + wm8960->lrclk = params_rate(params); + /* Update filters for the new rate */ + if (tx) { + wm8960_set_deemph(component); + } else { + for (i = 0; i < ARRAY_SIZE(alc_rates); i++) + if (alc_rates[i].rate == params_rate(params)) + snd_soc_component_update_bits(component, + WM8960_ADDCTL3, 0x7, + alc_rates[i].val); + } + + /* set iface */ + snd_soc_component_write(component, WM8960_IFACE1, iface); + + wm8960->is_stream_in_use[tx] = true; + + if (!wm8960->is_stream_in_use[!tx]) + return wm8960_configure_clocking(component); + + return 0; +} + +static int wm8960_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + wm8960->is_stream_in_use[tx] = false; + + return 0; +} + +static int wm8960_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + if (mute) + snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0x8); + else + snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0); + return 0; +} + +static int wm8960_set_bias_level_out3(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_STANDBY: + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(component); + if (ret) + return ret; + + /* Set VMID to 2x50k */ + snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x80); + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(component, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + break; + + default: + break; + } + + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8960->regmap); + + /* Enable anti-pop features */ + snd_soc_component_write(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Enable & ramp VMID at 2x50k */ + snd_soc_component_update_bits(component, WM8960_POWER1, 0x80, 0x80); + msleep(100); + + /* Enable VREF */ + snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VREF, + WM8960_VREF); + + /* Disable anti-pop features */ + snd_soc_component_write(component, WM8960_APOP1, WM8960_BUFIOEN); + } + + /* Set VMID to 2x250k */ + snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x100); + break; + + case SND_SOC_BIAS_OFF: + /* Enable anti-pop features */ + snd_soc_component_write(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Disable VMID and VREF, let them discharge */ + snd_soc_component_write(component, WM8960_POWER1, 0); + msleep(600); + break; + } + + return 0; +} + +static int wm8960_set_bias_level_capless(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2); + int reg, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_STANDBY: + /* Enable anti pop mode */ + snd_soc_component_update_bits(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ + reg = 0; + if (wm8960->lout1 && wm8960->lout1->power) + reg |= WM8960_PWR2_LOUT1; + if (wm8960->rout1 && wm8960->rout1->power) + reg |= WM8960_PWR2_ROUT1; + if (wm8960->out3 && wm8960->out3->power) + reg |= WM8960_PWR2_OUT3; + snd_soc_component_update_bits(component, WM8960_POWER2, + WM8960_PWR2_LOUT1 | + WM8960_PWR2_ROUT1 | + WM8960_PWR2_OUT3, reg); + + /* Enable VMID at 2*50k */ + snd_soc_component_update_bits(component, WM8960_POWER1, + WM8960_VMID_MASK, 0x80); + + /* Ramp */ + msleep(100); + + /* Enable VREF */ + snd_soc_component_update_bits(component, WM8960_POWER1, + WM8960_VREF, WM8960_VREF); + + msleep(100); + + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(component); + if (ret) + return ret; + + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(component, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + + /* Enable anti-pop mode */ + snd_soc_component_update_bits(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Disable VMID and VREF */ + snd_soc_component_update_bits(component, WM8960_POWER1, + WM8960_VREF | WM8960_VMID_MASK, 0); + break; + + case SND_SOC_BIAS_OFF: + regcache_sync(wm8960->regmap); + break; + default: + break; + } + break; + + case SND_SOC_BIAS_STANDBY: + switch (snd_soc_component_get_bias_level(component)) { + case SND_SOC_BIAS_PREPARE: + /* Disable HP discharge */ + snd_soc_component_update_bits(component, WM8960_APOP2, + WM8960_DISOP | WM8960_DRES_MASK, + 0); + + /* Disable anti-pop features */ + snd_soc_component_update_bits(component, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + break; + + default: + break; + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +static bool is_pll_freq_available(unsigned int source, unsigned int target) +{ + unsigned int Ndiv; + + if (source == 0 || target == 0) + return false; + + /* Scale up target to PLL operating frequency */ + target *= 4; + Ndiv = target / source; + + if (Ndiv < 6) { + source >>= 1; + Ndiv = target / source; + } + + if ((Ndiv < 6) || (Ndiv > 12)) + return false; + + return true; +} + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static int pll_factors(unsigned int source, unsigned int target, + struct _pll_div *pll_div) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); + + /* Scale up target to PLL operating frequency */ + target *= 4; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->pre_div = 1; + Ndiv = target / source; + } else + pll_div->pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) { + pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; + + pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", + pll_div->n, pll_div->k, pll_div->pre_div); + + return 0; +} + +static int wm8960_set_pll(struct snd_soc_component *component, + unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + static struct _pll_div pll_div; + int ret; + + if (freq_in && freq_out) { + ret = pll_factors(freq_in, freq_out, &pll_div); + if (ret != 0) + return ret; + } + + /* Disable the PLL: even if we are changing the frequency the + * PLL needs to be disabled while we do so. */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0); + snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0); + + if (!freq_in || !freq_out) + return 0; + + reg = snd_soc_component_read32(component, WM8960_PLL1) & ~0x3f; + reg |= pll_div.pre_div << 4; + reg |= pll_div.n; + + if (pll_div.k) { + reg |= 0x20; + + snd_soc_component_write(component, WM8960_PLL2, (pll_div.k >> 16) & 0xff); + snd_soc_component_write(component, WM8960_PLL3, (pll_div.k >> 8) & 0xff); + snd_soc_component_write(component, WM8960_PLL4, pll_div.k & 0xff); + } + snd_soc_component_write(component, WM8960_PLL1, reg); + + /* Turn it on */ + snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0x1); + msleep(250); + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0x1); + + return 0; +} + +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + wm8960->freq_in = freq_in; + + if (pll_id == WM8960_SYSCLK_AUTO) + return 0; + + return wm8960_set_pll(component, freq_in, freq_out); +} + +static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + switch (div_id) { + case WM8960_SYSCLKDIV: + reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1f9; + snd_soc_component_write(component, WM8960_CLOCK1, reg | div); + break; + case WM8960_DACDIV: + reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1c7; + snd_soc_component_write(component, WM8960_CLOCK1, reg | div); + break; + case WM8960_OPCLKDIV: + reg = snd_soc_component_read32(component, WM8960_PLL1) & 0x03f; + snd_soc_component_write(component, WM8960_PLL1, reg | div); + break; + case WM8960_DCLKDIV: + reg = snd_soc_component_read32(component, WM8960_CLOCK2) & 0x03f; + snd_soc_component_write(component, WM8960_CLOCK2, reg | div); + break; + case WM8960_TOCLKSEL: + reg = snd_soc_component_read32(component, WM8960_ADDCTL1) & 0x1fd; + snd_soc_component_write(component, WM8960_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8960_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + return wm8960->set_bias_level(component, level); +} + +static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8960_SYSCLK_MCLK: + snd_soc_component_update_bits(component, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_MCLK); + break; + case WM8960_SYSCLK_PLL: + snd_soc_component_update_bits(component, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_PLL); + break; + case WM8960_SYSCLK_AUTO: + break; + default: + return -EINVAL; + } + + wm8960->sysclk = freq; + wm8960->clk_id = clk_id; + + return 0; +} + +#define WM8960_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8960_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .hw_free = wm8960_hw_free, + .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver wm8960_dai = { + .name = "wm8960-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .ops = &wm8960_dai_ops, + .symmetric_rates = 1, +}; + +static int wm8960_probe(struct snd_soc_component *component) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct wm8960_data *pdata = &wm8960->pdata; + + if (pdata->capless) + wm8960->set_bias_level = wm8960_set_bias_level_capless; + else + wm8960->set_bias_level = wm8960_set_bias_level_out3; + + snd_soc_add_component_controls(component, wm8960_snd_controls, + ARRAY_SIZE(wm8960_snd_controls)); + wm8960_add_widgets(component); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8960 = { + .probe = wm8960_probe, + .set_bias_level = wm8960_set_bias_level, + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8960_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8960_PLL4, + + .reg_defaults = wm8960_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8960_volatile, +}; + +static void wm8960_set_pdata_from_of(struct i2c_client *i2c, + struct wm8960_data *pdata) +{ + const struct device_node *np = i2c->dev.of_node; + + if (of_property_read_bool(np, "wlf,capless")) + pdata->capless = true; + + if (of_property_read_bool(np, "wlf,shared-lrclk")) + pdata->shared_lrclk = true; +} + +static int wm8960_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); + struct wm8960_priv *wm8960; + int ret; + + wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), + GFP_KERNEL); + if (wm8960 == NULL) + return -ENOMEM; + + wm8960->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(wm8960->mclk)) { + if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + + wm8960->regmap = devm_regmap_init_i2c(i2c, &wm8960_regmap); + if (IS_ERR(wm8960->regmap)) + return PTR_ERR(wm8960->regmap); + + if (pdata) + memcpy(&wm8960->pdata, pdata, sizeof(struct wm8960_data)); + else if (i2c->dev.of_node) + wm8960_set_pdata_from_of(i2c, &wm8960->pdata); + + ret = wm8960_reset(wm8960->regmap); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + return ret; + } + + if (wm8960->pdata.shared_lrclk) { + ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, + 0x4, 0x4); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable LRCM: %d\n", + ret); + return ret; + } + } + + /* Latch the update bits */ + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); + + i2c_set_clientdata(i2c, wm8960); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8960, &wm8960_dai, 1); + + return ret; +} + +static int wm8960_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wm8960_i2c_id[] = { + { "wm8960", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); + +static const struct of_device_id wm8960_of_match[] = { + { .compatible = "wlf,wm8960", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8960_of_match); + +static struct i2c_driver wm8960_i2c_driver = { + .driver = { + .name = "wm8960", + .of_match_table = wm8960_of_match, + }, + .probe = wm8960_i2c_probe, + .remove = wm8960_i2c_remove, + .id_table = wm8960_i2c_id, +}; + +module_i2c_driver(wm8960_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8960 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h new file mode 100644 index 000000000..ab3220d34 --- /dev/null +++ b/sound/soc/codecs/wm8960.h @@ -0,0 +1,114 @@ +/* + * wm8960.h -- WM8960 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8960_H +#define _WM8960_H + +/* WM8960 register space */ + + +#define WM8960_CACHEREGNUM 56 + +#define WM8960_LINVOL 0x0 +#define WM8960_RINVOL 0x1 +#define WM8960_LOUT1 0x2 +#define WM8960_ROUT1 0x3 +#define WM8960_CLOCK1 0x4 +#define WM8960_DACCTL1 0x5 +#define WM8960_DACCTL2 0x6 +#define WM8960_IFACE1 0x7 +#define WM8960_CLOCK2 0x8 +#define WM8960_IFACE2 0x9 +#define WM8960_LDAC 0xa +#define WM8960_RDAC 0xb + +#define WM8960_RESET 0xf +#define WM8960_3D 0x10 +#define WM8960_ALC1 0x11 +#define WM8960_ALC2 0x12 +#define WM8960_ALC3 0x13 +#define WM8960_NOISEG 0x14 +#define WM8960_LADC 0x15 +#define WM8960_RADC 0x16 +#define WM8960_ADDCTL1 0x17 +#define WM8960_ADDCTL2 0x18 +#define WM8960_POWER1 0x19 +#define WM8960_POWER2 0x1a +#define WM8960_ADDCTL3 0x1b +#define WM8960_APOP1 0x1c +#define WM8960_APOP2 0x1d + +#define WM8960_LINPATH 0x20 +#define WM8960_RINPATH 0x21 +#define WM8960_LOUTMIX 0x22 + +#define WM8960_ROUTMIX 0x25 +#define WM8960_MONOMIX1 0x26 +#define WM8960_MONOMIX2 0x27 +#define WM8960_LOUT2 0x28 +#define WM8960_ROUT2 0x29 +#define WM8960_MONO 0x2a +#define WM8960_INBMIX1 0x2b +#define WM8960_INBMIX2 0x2c +#define WM8960_BYPASS1 0x2d +#define WM8960_BYPASS2 0x2e +#define WM8960_POWER3 0x2f +#define WM8960_ADDCTL4 0x30 +#define WM8960_CLASSD1 0x31 + +#define WM8960_CLASSD3 0x33 +#define WM8960_PLL1 0x34 +#define WM8960_PLL2 0x35 +#define WM8960_PLL3 0x36 +#define WM8960_PLL4 0x37 + + +/* + * WM8960 Clock dividers + */ +#define WM8960_SYSCLKDIV 0 +#define WM8960_DACDIV 1 +#define WM8960_OPCLKDIV 2 +#define WM8960_DCLKDIV 3 +#define WM8960_TOCLKSEL 4 + +#define WM8960_SYSCLK_DIV_1 (0 << 1) +#define WM8960_SYSCLK_DIV_2 (2 << 1) + +#define WM8960_SYSCLK_MCLK (0 << 0) +#define WM8960_SYSCLK_PLL (1 << 0) +#define WM8960_SYSCLK_AUTO (2 << 0) + +#define WM8960_DAC_DIV_1 (0 << 3) +#define WM8960_DAC_DIV_1_5 (1 << 3) +#define WM8960_DAC_DIV_2 (2 << 3) +#define WM8960_DAC_DIV_3 (3 << 3) +#define WM8960_DAC_DIV_4 (4 << 3) +#define WM8960_DAC_DIV_5_5 (5 << 3) +#define WM8960_DAC_DIV_6 (6 << 3) + +#define WM8960_DCLK_DIV_1_5 (0 << 6) +#define WM8960_DCLK_DIV_2 (1 << 6) +#define WM8960_DCLK_DIV_3 (2 << 6) +#define WM8960_DCLK_DIV_4 (3 << 6) +#define WM8960_DCLK_DIV_6 (4 << 6) +#define WM8960_DCLK_DIV_8 (5 << 6) +#define WM8960_DCLK_DIV_12 (6 << 6) +#define WM8960_DCLK_DIV_16 (7 << 6) + +#define WM8960_TOCLK_F19 (0 << 1) +#define WM8960_TOCLK_F21 (1 << 1) + +#define WM8960_OPCLK_DIV_1 (0 << 0) +#define WM8960_OPCLK_DIV_2 (1 << 0) +#define WM8960_OPCLK_DIV_3 (2 << 0) +#define WM8960_OPCLK_DIV_4 (3 << 0) +#define WM8960_OPCLK_DIV_5_5 (4 << 0) +#define WM8960_OPCLK_DIV_6 (5 << 0) + +#endif diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c new file mode 100644 index 000000000..68b4cadc3 --- /dev/null +++ b/sound/soc/codecs/wm8961.c @@ -0,0 +1,990 @@ +/* + * wm8961.c -- WM8961 ALSA SoC Audio driver + * + * Copyright 2009-10 Wolfson Microelectronics, plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Currently unimplemented features: + * - ALC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8961.h" + +#define WM8961_MAX_REGISTER 0xFC + +static const struct reg_default wm8961_reg_defaults[] = { + { 0, 0x009F }, /* R0 - Left Input volume */ + { 1, 0x009F }, /* R1 - Right Input volume */ + { 2, 0x0000 }, /* R2 - LOUT1 volume */ + { 3, 0x0000 }, /* R3 - ROUT1 volume */ + { 4, 0x0020 }, /* R4 - Clocking1 */ + { 5, 0x0008 }, /* R5 - ADC & DAC Control 1 */ + { 6, 0x0000 }, /* R6 - ADC & DAC Control 2 */ + { 7, 0x000A }, /* R7 - Audio Interface 0 */ + { 8, 0x01F4 }, /* R8 - Clocking2 */ + { 9, 0x0000 }, /* R9 - Audio Interface 1 */ + { 10, 0x00FF }, /* R10 - Left DAC volume */ + { 11, 0x00FF }, /* R11 - Right DAC volume */ + + { 14, 0x0040 }, /* R14 - Audio Interface 2 */ + + { 17, 0x007B }, /* R17 - ALC1 */ + { 18, 0x0000 }, /* R18 - ALC2 */ + { 19, 0x0032 }, /* R19 - ALC3 */ + { 20, 0x0000 }, /* R20 - Noise Gate */ + { 21, 0x00C0 }, /* R21 - Left ADC volume */ + { 22, 0x00C0 }, /* R22 - Right ADC volume */ + { 23, 0x0120 }, /* R23 - Additional control(1) */ + { 24, 0x0000 }, /* R24 - Additional control(2) */ + { 25, 0x0000 }, /* R25 - Pwr Mgmt (1) */ + { 26, 0x0000 }, /* R26 - Pwr Mgmt (2) */ + { 27, 0x0000 }, /* R27 - Additional Control (3) */ + { 28, 0x0000 }, /* R28 - Anti-pop */ + + { 30, 0x005F }, /* R30 - Clocking 3 */ + + { 32, 0x0000 }, /* R32 - ADCL signal path */ + { 33, 0x0000 }, /* R33 - ADCR signal path */ + + { 40, 0x0000 }, /* R40 - LOUT2 volume */ + { 41, 0x0000 }, /* R41 - ROUT2 volume */ + + { 47, 0x0000 }, /* R47 - Pwr Mgmt (3) */ + { 48, 0x0023 }, /* R48 - Additional Control (4) */ + { 49, 0x0000 }, /* R49 - Class D Control 1 */ + + { 51, 0x0003 }, /* R51 - Class D Control 2 */ + + { 56, 0x0106 }, /* R56 - Clocking 4 */ + { 57, 0x0000 }, /* R57 - DSP Sidetone 0 */ + { 58, 0x0000 }, /* R58 - DSP Sidetone 1 */ + + { 60, 0x0000 }, /* R60 - DC Servo 0 */ + { 61, 0x0000 }, /* R61 - DC Servo 1 */ + + { 63, 0x015E }, /* R63 - DC Servo 3 */ + + { 65, 0x0010 }, /* R65 - DC Servo 5 */ + + { 68, 0x0003 }, /* R68 - Analogue PGA Bias */ + { 69, 0x0000 }, /* R69 - Analogue HP 0 */ + + { 71, 0x01FB }, /* R71 - Analogue HP 2 */ + { 72, 0x0000 }, /* R72 - Charge Pump 1 */ + + { 82, 0x0000 }, /* R82 - Charge Pump B */ + + { 87, 0x0000 }, /* R87 - Write Sequencer 1 */ + { 88, 0x0000 }, /* R88 - Write Sequencer 2 */ + { 89, 0x0000 }, /* R89 - Write Sequencer 3 */ + { 90, 0x0000 }, /* R90 - Write Sequencer 4 */ + { 91, 0x0000 }, /* R91 - Write Sequencer 5 */ + { 92, 0x0000 }, /* R92 - Write Sequencer 6 */ + { 93, 0x0000 }, /* R93 - Write Sequencer 7 */ + + { 252, 0x0001 }, /* R252 - General test 1 */ +}; + +struct wm8961_priv { + struct regmap *regmap; + int sysclk; +}; + +static bool wm8961_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8961_SOFTWARE_RESET: + case WM8961_WRITE_SEQUENCER_7: + case WM8961_DC_SERVO_1: + return true; + + default: + return false; + } +} + +static bool wm8961_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8961_LEFT_INPUT_VOLUME: + case WM8961_RIGHT_INPUT_VOLUME: + case WM8961_LOUT1_VOLUME: + case WM8961_ROUT1_VOLUME: + case WM8961_CLOCKING1: + case WM8961_ADC_DAC_CONTROL_1: + case WM8961_ADC_DAC_CONTROL_2: + case WM8961_AUDIO_INTERFACE_0: + case WM8961_CLOCKING2: + case WM8961_AUDIO_INTERFACE_1: + case WM8961_LEFT_DAC_VOLUME: + case WM8961_RIGHT_DAC_VOLUME: + case WM8961_AUDIO_INTERFACE_2: + case WM8961_SOFTWARE_RESET: + case WM8961_ALC1: + case WM8961_ALC2: + case WM8961_ALC3: + case WM8961_NOISE_GATE: + case WM8961_LEFT_ADC_VOLUME: + case WM8961_RIGHT_ADC_VOLUME: + case WM8961_ADDITIONAL_CONTROL_1: + case WM8961_ADDITIONAL_CONTROL_2: + case WM8961_PWR_MGMT_1: + case WM8961_PWR_MGMT_2: + case WM8961_ADDITIONAL_CONTROL_3: + case WM8961_ANTI_POP: + case WM8961_CLOCKING_3: + case WM8961_ADCL_SIGNAL_PATH: + case WM8961_ADCR_SIGNAL_PATH: + case WM8961_LOUT2_VOLUME: + case WM8961_ROUT2_VOLUME: + case WM8961_PWR_MGMT_3: + case WM8961_ADDITIONAL_CONTROL_4: + case WM8961_CLASS_D_CONTROL_1: + case WM8961_CLASS_D_CONTROL_2: + case WM8961_CLOCKING_4: + case WM8961_DSP_SIDETONE_0: + case WM8961_DSP_SIDETONE_1: + case WM8961_DC_SERVO_0: + case WM8961_DC_SERVO_1: + case WM8961_DC_SERVO_3: + case WM8961_DC_SERVO_5: + case WM8961_ANALOGUE_PGA_BIAS: + case WM8961_ANALOGUE_HP_0: + case WM8961_ANALOGUE_HP_2: + case WM8961_CHARGE_PUMP_1: + case WM8961_CHARGE_PUMP_B: + case WM8961_WRITE_SEQUENCER_1: + case WM8961_WRITE_SEQUENCER_2: + case WM8961_WRITE_SEQUENCER_3: + case WM8961_WRITE_SEQUENCER_4: + case WM8961_WRITE_SEQUENCER_5: + case WM8961_WRITE_SEQUENCER_6: + case WM8961_WRITE_SEQUENCER_7: + case WM8961_GENERAL_TEST_1: + return true; + default: + return false; + } +} + +/* + * The headphone output supports special anti-pop sequences giving + * silent power up and power down. + */ +static int wm8961_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 hp_reg = snd_soc_component_read32(component, WM8961_ANALOGUE_HP_0); + u16 cp_reg = snd_soc_component_read32(component, WM8961_CHARGE_PUMP_1); + u16 pwr_reg = snd_soc_component_read32(component, WM8961_PWR_MGMT_2); + u16 dcs_reg = snd_soc_component_read32(component, WM8961_DC_SERVO_1); + int timeout = 500; + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Make sure the output is shorted */ + hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Enable the charge pump */ + cp_reg |= WM8961_CP_ENA; + snd_soc_component_write(component, WM8961_CHARGE_PUMP_1, cp_reg); + mdelay(5); + + /* Enable the PGA */ + pwr_reg |= WM8961_LOUT1_PGA | WM8961_ROUT1_PGA; + snd_soc_component_write(component, WM8961_PWR_MGMT_2, pwr_reg); + + /* Enable the amplifier */ + hp_reg |= WM8961_HPR_ENA | WM8961_HPL_ENA; + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Second stage enable */ + hp_reg |= WM8961_HPR_ENA_DLY | WM8961_HPL_ENA_DLY; + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Enable the DC servo & trigger startup */ + dcs_reg |= + WM8961_DCS_ENA_CHAN_HPR | WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_ENA_CHAN_HPL | WM8961_DCS_TRIG_STARTUP_HPL; + dev_dbg(component->dev, "Enabling DC servo\n"); + + snd_soc_component_write(component, WM8961_DC_SERVO_1, dcs_reg); + do { + msleep(1); + dcs_reg = snd_soc_component_read32(component, WM8961_DC_SERVO_1); + } while (--timeout && + dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_TRIG_STARTUP_HPL)); + if (dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_TRIG_STARTUP_HPL)) + dev_err(component->dev, "DC servo timed out\n"); + else + dev_dbg(component->dev, "DC servo startup complete\n"); + + /* Enable the output stage */ + hp_reg |= WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP; + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Remove the short on the output stage */ + hp_reg |= WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT; + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + /* Short the output */ + hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable the output stage */ + hp_reg &= ~(WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP); + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable DC offset cancellation */ + dcs_reg &= ~(WM8961_DCS_ENA_CHAN_HPR | + WM8961_DCS_ENA_CHAN_HPL); + snd_soc_component_write(component, WM8961_DC_SERVO_1, dcs_reg); + + /* Finish up */ + hp_reg &= ~(WM8961_HPR_ENA_DLY | WM8961_HPR_ENA | + WM8961_HPL_ENA_DLY | WM8961_HPL_ENA); + snd_soc_component_write(component, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable the PGA */ + pwr_reg &= ~(WM8961_LOUT1_PGA | WM8961_ROUT1_PGA); + snd_soc_component_write(component, WM8961_PWR_MGMT_2, pwr_reg); + + /* Disable the charge pump */ + dev_dbg(component->dev, "Disabling charge pump\n"); + snd_soc_component_write(component, WM8961_CHARGE_PUMP_1, + cp_reg & ~WM8961_CP_ENA); + } + + return 0; +} + +static int wm8961_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 pwr_reg = snd_soc_component_read32(component, WM8961_PWR_MGMT_2); + u16 spk_reg = snd_soc_component_read32(component, WM8961_CLASS_D_CONTROL_1); + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Enable the PGA */ + pwr_reg |= WM8961_SPKL_PGA | WM8961_SPKR_PGA; + snd_soc_component_write(component, WM8961_PWR_MGMT_2, pwr_reg); + + /* Enable the amplifier */ + spk_reg |= WM8961_SPKL_ENA | WM8961_SPKR_ENA; + snd_soc_component_write(component, WM8961_CLASS_D_CONTROL_1, spk_reg); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + /* Disable the amplifier */ + spk_reg &= ~(WM8961_SPKL_ENA | WM8961_SPKR_ENA); + snd_soc_component_write(component, WM8961_CLASS_D_CONTROL_1, spk_reg); + + /* Disable the PGA */ + pwr_reg &= ~(WM8961_SPKL_PGA | WM8961_SPKR_PGA); + snd_soc_component_write(component, WM8961_PWR_MGMT_2, pwr_reg); + } + + return 0; +} + +static const char *adc_hpf_text[] = { + "Hi-fi", "Voice 1", "Voice 2", "Voice 3", +}; + +static SOC_ENUM_SINGLE_DECL(adc_hpf, + WM8961_ADC_DAC_CONTROL_2, 7, adc_hpf_text); + +static const char *dac_deemph_text[] = { + "None", "32kHz", "44.1kHz", "48kHz", +}; + +static SOC_ENUM_SINGLE_DECL(dac_deemph, + WM8961_ADC_DAC_CONTROL_1, 1, dac_deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(hp_sec_tlv, -700, 100, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_RANGE(boost_tlv, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(13, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(20, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(29, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(pga_tlv, -2325, 75, 0); + +static const struct snd_kcontrol_new wm8961_snd_controls[] = { +SOC_DOUBLE_R_TLV("Headphone Volume", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, + 0, 127, 0, out_tlv), +SOC_DOUBLE_TLV("Headphone Secondary Volume", WM8961_ANALOGUE_HP_2, + 6, 3, 7, 0, hp_sec_tlv), +SOC_DOUBLE_R("Headphone ZC Switch", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Volume", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker ZC Switch", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, + 7, 1, 0), +SOC_SINGLE("Speaker AC Gain", WM8961_CLASS_D_CONTROL_2, 0, 7, 0), + +SOC_SINGLE("DAC x128 OSR Switch", WM8961_ADC_DAC_CONTROL_2, 0, 1, 0), +SOC_ENUM("DAC Deemphasis", dac_deemph), +SOC_SINGLE("DAC Soft Mute Switch", WM8961_ADC_DAC_CONTROL_2, 3, 1, 0), + +SOC_DOUBLE_R_TLV("Sidetone Volume", WM8961_DSP_SIDETONE_0, + WM8961_DSP_SIDETONE_1, 4, 12, 0, sidetone_tlv), + +SOC_SINGLE("ADC High Pass Filter Switch", WM8961_ADC_DAC_CONTROL_1, 0, 1, 0), +SOC_ENUM("ADC High Pass Filter Mode", adc_hpf), + +SOC_DOUBLE_R_TLV("Capture Volume", + WM8961_LEFT_ADC_VOLUME, WM8961_RIGHT_ADC_VOLUME, + 1, 119, 0, adc_tlv), +SOC_DOUBLE_R_TLV("Capture Boost Volume", + WM8961_ADCL_SIGNAL_PATH, WM8961_ADCR_SIGNAL_PATH, + 4, 3, 0, boost_tlv), +SOC_DOUBLE_R_TLV("Capture PGA Volume", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 0, 62, 0, pga_tlv), +SOC_DOUBLE_R("Capture PGA ZC Switch", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 6, 1, 1), +SOC_DOUBLE_R("Capture PGA Switch", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 7, 1, 1), +}; + +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(dacl_sidetone, + WM8961_DSP_SIDETONE_0, 2, sidetone_text); + +static SOC_ENUM_SINGLE_DECL(dacr_sidetone, + WM8961_DSP_SIDETONE_1, 2, sidetone_text); + +static const struct snd_kcontrol_new dacl_mux = + SOC_DAPM_ENUM("DACL Sidetone", dacl_sidetone); + +static const struct snd_kcontrol_new dacr_mux = + SOC_DAPM_ENUM("DACR Sidetone", dacr_sidetone); + +static const struct snd_soc_dapm_widget wm8961_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT"), +SND_SOC_DAPM_INPUT("RINPUT"), + +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8961_CLOCKING2, 4, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Input", WM8961_PWR_MGMT_1, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input", WM8961_PWR_MGMT_1, 4, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", WM8961_PWR_MGMT_1, 3, 0), +SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", WM8961_PWR_MGMT_1, 2, 0), + +SND_SOC_DAPM_SUPPLY("MICBIAS", WM8961_PWR_MGMT_1, 1, 0, NULL, 0), + +SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &dacl_mux), +SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &dacr_mux), + +SND_SOC_DAPM_DAC("DACL", "HiFi Playback", WM8961_PWR_MGMT_2, 8, 0), +SND_SOC_DAPM_DAC("DACR", "HiFi Playback", WM8961_PWR_MGMT_2, 7, 0), + +/* Handle as a mono path for DCS */ +SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, + 4, 0, NULL, 0, wm8961_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_E("Speaker Output", SND_SOC_NOPM, + 4, 0, NULL, 0, wm8961_spk_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +}; + + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DACL", NULL, "CLK_DSP" }, + { "DACL", NULL, "DACL Sidetone" }, + { "DACR", NULL, "CLK_DSP" }, + { "DACR", NULL, "DACR Sidetone" }, + + { "DACL Sidetone", "Left", "ADCL" }, + { "DACL Sidetone", "Right", "ADCR" }, + + { "DACR Sidetone", "Left", "ADCL" }, + { "DACR Sidetone", "Right", "ADCR" }, + + { "HP_L", NULL, "Headphone Output" }, + { "HP_R", NULL, "Headphone Output" }, + { "Headphone Output", NULL, "DACL" }, + { "Headphone Output", NULL, "DACR" }, + + { "SPK_LN", NULL, "Speaker Output" }, + { "SPK_LP", NULL, "Speaker Output" }, + { "SPK_RN", NULL, "Speaker Output" }, + { "SPK_RP", NULL, "Speaker Output" }, + + { "Speaker Output", NULL, "DACL" }, + { "Speaker Output", NULL, "DACR" }, + + { "ADCL", NULL, "Left Input" }, + { "ADCL", NULL, "CLK_DSP" }, + { "ADCR", NULL, "Right Input" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "Left Input", NULL, "LINPUT" }, + { "Right Input", NULL, "RINPUT" }, + +}; + +/* Values for CLK_SYS_RATE */ +static struct { + int ratio; + u16 val; +} wm8961_clk_sys_ratio[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +/* Values for SAMPLE_RATE */ +static struct { + int rate; + u16 val; +} wm8961_srate[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11250, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + +static int wm8961_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8961_priv *wm8961 = snd_soc_component_get_drvdata(component); + int i, best, target, fs; + u16 reg; + + fs = params_rate(params); + + if (!wm8961->sysclk) { + dev_err(component->dev, "MCLK has not been specified\n"); + return -EINVAL; + } + + /* Find the closest sample rate for the filters */ + best = 0; + for (i = 0; i < ARRAY_SIZE(wm8961_srate); i++) { + if (abs(wm8961_srate[i].rate - fs) < + abs(wm8961_srate[best].rate - fs)) + best = i; + } + reg = snd_soc_component_read32(component, WM8961_ADDITIONAL_CONTROL_3); + reg &= ~WM8961_SAMPLE_RATE_MASK; + reg |= wm8961_srate[best].val; + snd_soc_component_write(component, WM8961_ADDITIONAL_CONTROL_3, reg); + dev_dbg(component->dev, "Selected SRATE %dHz for %dHz\n", + wm8961_srate[best].rate, fs); + + /* Select a CLK_SYS/fs ratio equal to or higher than required */ + target = wm8961->sysclk / fs; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && target < 64) { + dev_err(component->dev, + "SYSCLK must be at least 64*fs for DAC\n"); + return -EINVAL; + } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && target < 256) { + dev_err(component->dev, + "SYSCLK must be at least 256*fs for ADC\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(wm8961_clk_sys_ratio); i++) { + if (wm8961_clk_sys_ratio[i].ratio >= target) + break; + } + if (i == ARRAY_SIZE(wm8961_clk_sys_ratio)) { + dev_err(component->dev, "Unable to generate CLK_SYS_RATE\n"); + return -EINVAL; + } + dev_dbg(component->dev, "Selected CLK_SYS_RATE of %d for %d/%d=%d\n", + wm8961_clk_sys_ratio[i].ratio, wm8961->sysclk, fs, + wm8961->sysclk / fs); + + reg = snd_soc_component_read32(component, WM8961_CLOCKING_4); + reg &= ~WM8961_CLK_SYS_RATE_MASK; + reg |= wm8961_clk_sys_ratio[i].val << WM8961_CLK_SYS_RATE_SHIFT; + snd_soc_component_write(component, WM8961_CLOCKING_4, reg); + + reg = snd_soc_component_read32(component, WM8961_AUDIO_INTERFACE_0); + reg &= ~WM8961_WL_MASK; + switch (params_width(params)) { + case 16: + break; + case 20: + reg |= 1 << WM8961_WL_SHIFT; + break; + case 24: + reg |= 2 << WM8961_WL_SHIFT; + break; + case 32: + reg |= 3 << WM8961_WL_SHIFT; + break; + default: + return -EINVAL; + } + snd_soc_component_write(component, WM8961_AUDIO_INTERFACE_0, reg); + + /* Sloping stop-band filter is recommended for <= 24kHz */ + reg = snd_soc_component_read32(component, WM8961_ADC_DAC_CONTROL_2); + if (fs <= 24000) + reg |= WM8961_DACSLOPE; + else + reg &= ~WM8961_DACSLOPE; + snd_soc_component_write(component, WM8961_ADC_DAC_CONTROL_2, reg); + + return 0; +} + +static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, + int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8961_priv *wm8961 = snd_soc_component_get_drvdata(component); + u16 reg = snd_soc_component_read32(component, WM8961_CLOCKING1); + + if (freq > 33000000) { + dev_err(component->dev, "MCLK must be <33MHz\n"); + return -EINVAL; + } + + if (freq > 16500000) { + dev_dbg(component->dev, "Using MCLK/2 for %dHz MCLK\n", freq); + reg |= WM8961_MCLKDIV; + freq /= 2; + } else { + dev_dbg(component->dev, "Using MCLK/1 for %dHz MCLK\n", freq); + reg &= ~WM8961_MCLKDIV; + } + + snd_soc_component_write(component, WM8961_CLOCKING1, reg); + + wm8961->sysclk = freq; + + return 0; +} + +static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u16 aif = snd_soc_component_read32(component, WM8961_AUDIO_INTERFACE_0); + + aif &= ~(WM8961_BCLKINV | WM8961_LRP | + WM8961_MS | WM8961_FORMAT_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aif |= WM8961_MS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + break; + + case SND_SOC_DAIFMT_LEFT_J: + aif |= 1; + break; + + case SND_SOC_DAIFMT_I2S: + aif |= 2; + break; + + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8961_LRP; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif |= 3; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_IB_NF: + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8961_LRP; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8961_BCLKINV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8961_BCLKINV | WM8961_LRP; + break; + default: + return -EINVAL; + } + + return snd_soc_component_write(component, WM8961_AUDIO_INTERFACE_0, aif); +} + +static int wm8961_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_component *component = dai->component; + u16 reg = snd_soc_component_read32(component, WM8961_ADDITIONAL_CONTROL_2); + + if (tristate) + reg |= WM8961_TRIS; + else + reg &= ~WM8961_TRIS; + + return snd_soc_component_write(component, WM8961_ADDITIONAL_CONTROL_2, reg); +} + +static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 reg = snd_soc_component_read32(component, WM8961_ADC_DAC_CONTROL_1); + + if (mute) + reg |= WM8961_DACMU; + else + reg &= ~WM8961_DACMU; + + msleep(17); + + return snd_soc_component_write(component, WM8961_ADC_DAC_CONTROL_1, reg); +} + +static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) +{ + struct snd_soc_component *component = dai->component; + u16 reg; + + switch (div_id) { + case WM8961_BCLK: + reg = snd_soc_component_read32(component, WM8961_CLOCKING2); + reg &= ~WM8961_BCLKDIV_MASK; + reg |= div; + snd_soc_component_write(component, WM8961_CLOCKING2, reg); + break; + + case WM8961_LRCLK: + reg = snd_soc_component_read32(component, WM8961_AUDIO_INTERFACE_2); + reg &= ~WM8961_LRCLK_RATE_MASK; + reg |= div; + snd_soc_component_write(component, WM8961_AUDIO_INTERFACE_2, reg); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8961_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + u16 reg; + + /* This is all slightly unusual since we have no bypass paths + * and the output amplifier structure means we can just slam + * the biases straight up rather than having to ramp them + * slowly. + */ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) { + /* Enable bias generation */ + reg = snd_soc_component_read32(component, WM8961_ANTI_POP); + reg |= WM8961_BUFIOEN | WM8961_BUFDCOPEN; + snd_soc_component_write(component, WM8961_ANTI_POP, reg); + + /* VMID=2*50k, VREF */ + reg = snd_soc_component_read32(component, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VMIDSEL_MASK; + reg |= (1 << WM8961_VMIDSEL_SHIFT) | WM8961_VREF; + snd_soc_component_write(component, WM8961_PWR_MGMT_1, reg); + } + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_PREPARE) { + /* VREF off */ + reg = snd_soc_component_read32(component, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VREF; + snd_soc_component_write(component, WM8961_PWR_MGMT_1, reg); + + /* Bias generation off */ + reg = snd_soc_component_read32(component, WM8961_ANTI_POP); + reg &= ~(WM8961_BUFIOEN | WM8961_BUFDCOPEN); + snd_soc_component_write(component, WM8961_ANTI_POP, reg); + + /* VMID off */ + reg = snd_soc_component_read32(component, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VMIDSEL_MASK; + snd_soc_component_write(component, WM8961_PWR_MGMT_1, reg); + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + + +#define WM8961_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8961_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8961_dai_ops = { + .hw_params = wm8961_hw_params, + .set_sysclk = wm8961_set_sysclk, + .set_fmt = wm8961_set_fmt, + .digital_mute = wm8961_digital_mute, + .set_tristate = wm8961_set_tristate, + .set_clkdiv = wm8961_set_clkdiv, +}; + +static struct snd_soc_dai_driver wm8961_dai = { + .name = "wm8961-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8961_RATES, + .formats = WM8961_FORMATS,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8961_RATES, + .formats = WM8961_FORMATS,}, + .ops = &wm8961_dai_ops, +}; + +static int wm8961_probe(struct snd_soc_component *component) +{ + u16 reg; + + /* Enable class W */ + reg = snd_soc_component_read32(component, WM8961_CHARGE_PUMP_B); + reg |= WM8961_CP_DYN_PWR_MASK; + snd_soc_component_write(component, WM8961_CHARGE_PUMP_B, reg); + + /* Latch volume update bits (right channel only, we always + * write both out) and default ZC on. */ + reg = snd_soc_component_read32(component, WM8961_ROUT1_VOLUME); + snd_soc_component_write(component, WM8961_ROUT1_VOLUME, + reg | WM8961_LO1ZC | WM8961_OUT1VU); + snd_soc_component_write(component, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC); + reg = snd_soc_component_read32(component, WM8961_ROUT2_VOLUME); + snd_soc_component_write(component, WM8961_ROUT2_VOLUME, + reg | WM8961_SPKRZC | WM8961_SPKVU); + snd_soc_component_write(component, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC); + + reg = snd_soc_component_read32(component, WM8961_RIGHT_ADC_VOLUME); + snd_soc_component_write(component, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU); + reg = snd_soc_component_read32(component, WM8961_RIGHT_INPUT_VOLUME); + snd_soc_component_write(component, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU); + + /* Use soft mute by default */ + reg = snd_soc_component_read32(component, WM8961_ADC_DAC_CONTROL_2); + reg |= WM8961_DACSMM; + snd_soc_component_write(component, WM8961_ADC_DAC_CONTROL_2, reg); + + /* Use automatic clocking mode by default; for now this is all + * we support. + */ + reg = snd_soc_component_read32(component, WM8961_CLOCKING_3); + reg &= ~WM8961_MANUAL_MODE; + snd_soc_component_write(component, WM8961_CLOCKING_3, reg); + + return 0; +} + +#ifdef CONFIG_PM + +static int wm8961_resume(struct snd_soc_component *component) +{ + snd_soc_component_cache_sync(component); + + return 0; +} +#else +#define wm8961_resume NULL +#endif + +static const struct snd_soc_component_driver soc_component_dev_wm8961 = { + .probe = wm8961_probe, + .resume = wm8961_resume, + .set_bias_level = wm8961_set_bias_level, + .controls = wm8961_snd_controls, + .num_controls = ARRAY_SIZE(wm8961_snd_controls), + .dapm_widgets = wm8961_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8961_dapm_widgets), + .dapm_routes = audio_paths, + .num_dapm_routes = ARRAY_SIZE(audio_paths), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8961_regmap = { + .reg_bits = 8, + .val_bits = 16, + .max_register = WM8961_MAX_REGISTER, + + .reg_defaults = wm8961_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8961_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8961_volatile, + .readable_reg = wm8961_readable, +}; + +static int wm8961_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8961_priv *wm8961; + unsigned int val; + int ret; + + wm8961 = devm_kzalloc(&i2c->dev, sizeof(struct wm8961_priv), + GFP_KERNEL); + if (wm8961 == NULL) + return -ENOMEM; + + wm8961->regmap = devm_regmap_init_i2c(i2c, &wm8961_regmap); + if (IS_ERR(wm8961->regmap)) + return PTR_ERR(wm8961->regmap); + + ret = regmap_read(wm8961->regmap, WM8961_SOFTWARE_RESET, &val); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read chip ID: %d\n", ret); + return ret; + } + + if (val != 0x1801) { + dev_err(&i2c->dev, "Device is not a WM8961: ID=0x%x\n", val); + return -EINVAL; + } + + /* This isn't volatile - readback doesn't correspond to write */ + regcache_cache_bypass(wm8961->regmap, true); + ret = regmap_read(wm8961->regmap, WM8961_RIGHT_INPUT_VOLUME, &val); + regcache_cache_bypass(wm8961->regmap, false); + + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read chip revision: %d\n", ret); + return ret; + } + + dev_info(&i2c->dev, "WM8961 family %d revision %c\n", + (val & WM8961_DEVICE_ID_MASK) >> WM8961_DEVICE_ID_SHIFT, + ((val & WM8961_CHIP_REV_MASK) >> WM8961_CHIP_REV_SHIFT) + + 'A'); + + ret = regmap_write(wm8961->regmap, WM8961_SOFTWARE_RESET, 0x1801); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8961); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8961, &wm8961_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8961_i2c_id[] = { + { "wm8961", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8961_i2c_id); + +static struct i2c_driver wm8961_i2c_driver = { + .driver = { + .name = "wm8961", + }, + .probe = wm8961_i2c_probe, + .id_table = wm8961_i2c_id, +}; + +module_i2c_driver(wm8961_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8961 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8961.h b/sound/soc/codecs/wm8961.h new file mode 100644 index 000000000..1d736e570 --- /dev/null +++ b/sound/soc/codecs/wm8961.h @@ -0,0 +1,863 @@ +/* + * wm8961.h -- WM8961 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8961_H +#define _WM8961_H + +#include + +#define WM8961_BCLK 1 +#define WM8961_LRCLK 2 + +#define WM8961_BCLK_DIV_1 0 +#define WM8961_BCLK_DIV_1_5 1 +#define WM8961_BCLK_DIV_2 2 +#define WM8961_BCLK_DIV_3 3 +#define WM8961_BCLK_DIV_4 4 +#define WM8961_BCLK_DIV_5_5 5 +#define WM8961_BCLK_DIV_6 6 +#define WM8961_BCLK_DIV_8 7 +#define WM8961_BCLK_DIV_11 8 +#define WM8961_BCLK_DIV_12 9 +#define WM8961_BCLK_DIV_16 10 +#define WM8961_BCLK_DIV_24 11 +#define WM8961_BCLK_DIV_32 13 + + +/* + * Register values. + */ +#define WM8961_LEFT_INPUT_VOLUME 0x00 +#define WM8961_RIGHT_INPUT_VOLUME 0x01 +#define WM8961_LOUT1_VOLUME 0x02 +#define WM8961_ROUT1_VOLUME 0x03 +#define WM8961_CLOCKING1 0x04 +#define WM8961_ADC_DAC_CONTROL_1 0x05 +#define WM8961_ADC_DAC_CONTROL_2 0x06 +#define WM8961_AUDIO_INTERFACE_0 0x07 +#define WM8961_CLOCKING2 0x08 +#define WM8961_AUDIO_INTERFACE_1 0x09 +#define WM8961_LEFT_DAC_VOLUME 0x0A +#define WM8961_RIGHT_DAC_VOLUME 0x0B +#define WM8961_AUDIO_INTERFACE_2 0x0E +#define WM8961_SOFTWARE_RESET 0x0F +#define WM8961_ALC1 0x11 +#define WM8961_ALC2 0x12 +#define WM8961_ALC3 0x13 +#define WM8961_NOISE_GATE 0x14 +#define WM8961_LEFT_ADC_VOLUME 0x15 +#define WM8961_RIGHT_ADC_VOLUME 0x16 +#define WM8961_ADDITIONAL_CONTROL_1 0x17 +#define WM8961_ADDITIONAL_CONTROL_2 0x18 +#define WM8961_PWR_MGMT_1 0x19 +#define WM8961_PWR_MGMT_2 0x1A +#define WM8961_ADDITIONAL_CONTROL_3 0x1B +#define WM8961_ANTI_POP 0x1C +#define WM8961_CLOCKING_3 0x1E +#define WM8961_ADCL_SIGNAL_PATH 0x20 +#define WM8961_ADCR_SIGNAL_PATH 0x21 +#define WM8961_LOUT2_VOLUME 0x28 +#define WM8961_ROUT2_VOLUME 0x29 +#define WM8961_PWR_MGMT_3 0x2F +#define WM8961_ADDITIONAL_CONTROL_4 0x30 +#define WM8961_CLASS_D_CONTROL_1 0x31 +#define WM8961_CLASS_D_CONTROL_2 0x33 +#define WM8961_CLOCKING_4 0x38 +#define WM8961_DSP_SIDETONE_0 0x39 +#define WM8961_DSP_SIDETONE_1 0x3A +#define WM8961_DC_SERVO_0 0x3C +#define WM8961_DC_SERVO_1 0x3D +#define WM8961_DC_SERVO_3 0x3F +#define WM8961_DC_SERVO_5 0x41 +#define WM8961_ANALOGUE_PGA_BIAS 0x44 +#define WM8961_ANALOGUE_HP_0 0x45 +#define WM8961_ANALOGUE_HP_2 0x47 +#define WM8961_CHARGE_PUMP_1 0x48 +#define WM8961_CHARGE_PUMP_B 0x52 +#define WM8961_WRITE_SEQUENCER_1 0x57 +#define WM8961_WRITE_SEQUENCER_2 0x58 +#define WM8961_WRITE_SEQUENCER_3 0x59 +#define WM8961_WRITE_SEQUENCER_4 0x5A +#define WM8961_WRITE_SEQUENCER_5 0x5B +#define WM8961_WRITE_SEQUENCER_6 0x5C +#define WM8961_WRITE_SEQUENCER_7 0x5D +#define WM8961_GENERAL_TEST_1 0xFC + + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Left Input volume + */ +#define WM8961_IPVU 0x0100 /* IPVU */ +#define WM8961_IPVU_MASK 0x0100 /* IPVU */ +#define WM8961_IPVU_SHIFT 8 /* IPVU */ +#define WM8961_IPVU_WIDTH 1 /* IPVU */ +#define WM8961_LINMUTE 0x0080 /* LINMUTE */ +#define WM8961_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8961_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8961_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8961_LIZC 0x0040 /* LIZC */ +#define WM8961_LIZC_MASK 0x0040 /* LIZC */ +#define WM8961_LIZC_SHIFT 6 /* LIZC */ +#define WM8961_LIZC_WIDTH 1 /* LIZC */ +#define WM8961_LINVOL_MASK 0x003F /* LINVOL - [5:0] */ +#define WM8961_LINVOL_SHIFT 0 /* LINVOL - [5:0] */ +#define WM8961_LINVOL_WIDTH 6 /* LINVOL - [5:0] */ + +/* + * R1 (0x01) - Right Input volume + */ +#define WM8961_DEVICE_ID_MASK 0xF000 /* DEVICE_ID - [15:12] */ +#define WM8961_DEVICE_ID_SHIFT 12 /* DEVICE_ID - [15:12] */ +#define WM8961_DEVICE_ID_WIDTH 4 /* DEVICE_ID - [15:12] */ +#define WM8961_CHIP_REV_MASK 0x0E00 /* CHIP_REV - [11:9] */ +#define WM8961_CHIP_REV_SHIFT 9 /* CHIP_REV - [11:9] */ +#define WM8961_CHIP_REV_WIDTH 3 /* CHIP_REV - [11:9] */ +#define WM8961_IPVU 0x0100 /* IPVU */ +#define WM8961_IPVU_MASK 0x0100 /* IPVU */ +#define WM8961_IPVU_SHIFT 8 /* IPVU */ +#define WM8961_IPVU_WIDTH 1 /* IPVU */ +#define WM8961_RINMUTE 0x0080 /* RINMUTE */ +#define WM8961_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8961_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8961_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8961_RIZC 0x0040 /* RIZC */ +#define WM8961_RIZC_MASK 0x0040 /* RIZC */ +#define WM8961_RIZC_SHIFT 6 /* RIZC */ +#define WM8961_RIZC_WIDTH 1 /* RIZC */ +#define WM8961_RINVOL_MASK 0x003F /* RINVOL - [5:0] */ +#define WM8961_RINVOL_SHIFT 0 /* RINVOL - [5:0] */ +#define WM8961_RINVOL_WIDTH 6 /* RINVOL - [5:0] */ + +/* + * R2 (0x02) - LOUT1 volume + */ +#define WM8961_OUT1VU 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8961_LO1ZC 0x0080 /* LO1ZC */ +#define WM8961_LO1ZC_MASK 0x0080 /* LO1ZC */ +#define WM8961_LO1ZC_SHIFT 7 /* LO1ZC */ +#define WM8961_LO1ZC_WIDTH 1 /* LO1ZC */ +#define WM8961_LOUT1VOL_MASK 0x007F /* LOUT1VOL - [6:0] */ +#define WM8961_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [6:0] */ +#define WM8961_LOUT1VOL_WIDTH 7 /* LOUT1VOL - [6:0] */ + +/* + * R3 (0x03) - ROUT1 volume + */ +#define WM8961_OUT1VU 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8961_RO1ZC 0x0080 /* RO1ZC */ +#define WM8961_RO1ZC_MASK 0x0080 /* RO1ZC */ +#define WM8961_RO1ZC_SHIFT 7 /* RO1ZC */ +#define WM8961_RO1ZC_WIDTH 1 /* RO1ZC */ +#define WM8961_ROUT1VOL_MASK 0x007F /* ROUT1VOL - [6:0] */ +#define WM8961_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [6:0] */ +#define WM8961_ROUT1VOL_WIDTH 7 /* ROUT1VOL - [6:0] */ + +/* + * R4 (0x04) - Clocking1 + */ +#define WM8961_ADCDIV_MASK 0x01C0 /* ADCDIV - [8:6] */ +#define WM8961_ADCDIV_SHIFT 6 /* ADCDIV - [8:6] */ +#define WM8961_ADCDIV_WIDTH 3 /* ADCDIV - [8:6] */ +#define WM8961_DACDIV_MASK 0x0038 /* DACDIV - [5:3] */ +#define WM8961_DACDIV_SHIFT 3 /* DACDIV - [5:3] */ +#define WM8961_DACDIV_WIDTH 3 /* DACDIV - [5:3] */ +#define WM8961_MCLKDIV 0x0004 /* MCLKDIV */ +#define WM8961_MCLKDIV_MASK 0x0004 /* MCLKDIV */ +#define WM8961_MCLKDIV_SHIFT 2 /* MCLKDIV */ +#define WM8961_MCLKDIV_WIDTH 1 /* MCLKDIV */ + +/* + * R5 (0x05) - ADC & DAC Control 1 + */ +#define WM8961_ADCPOL_MASK 0x0060 /* ADCPOL - [6:5] */ +#define WM8961_ADCPOL_SHIFT 5 /* ADCPOL - [6:5] */ +#define WM8961_ADCPOL_WIDTH 2 /* ADCPOL - [6:5] */ +#define WM8961_DACMU 0x0008 /* DACMU */ +#define WM8961_DACMU_MASK 0x0008 /* DACMU */ +#define WM8961_DACMU_SHIFT 3 /* DACMU */ +#define WM8961_DACMU_WIDTH 1 /* DACMU */ +#define WM8961_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8961_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8961_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ +#define WM8961_ADCHPD 0x0001 /* ADCHPD */ +#define WM8961_ADCHPD_MASK 0x0001 /* ADCHPD */ +#define WM8961_ADCHPD_SHIFT 0 /* ADCHPD */ +#define WM8961_ADCHPD_WIDTH 1 /* ADCHPD */ + +/* + * R6 (0x06) - ADC & DAC Control 2 + */ +#define WM8961_ADC_HPF_CUT_MASK 0x0180 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_ADC_HPF_CUT_SHIFT 7 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_DACPOL_MASK 0x0060 /* DACPOL - [6:5] */ +#define WM8961_DACPOL_SHIFT 5 /* DACPOL - [6:5] */ +#define WM8961_DACPOL_WIDTH 2 /* DACPOL - [6:5] */ +#define WM8961_DACSMM 0x0008 /* DACSMM */ +#define WM8961_DACSMM_MASK 0x0008 /* DACSMM */ +#define WM8961_DACSMM_SHIFT 3 /* DACSMM */ +#define WM8961_DACSMM_WIDTH 1 /* DACSMM */ +#define WM8961_DACMR 0x0004 /* DACMR */ +#define WM8961_DACMR_MASK 0x0004 /* DACMR */ +#define WM8961_DACMR_SHIFT 2 /* DACMR */ +#define WM8961_DACMR_WIDTH 1 /* DACMR */ +#define WM8961_DACSLOPE 0x0002 /* DACSLOPE */ +#define WM8961_DACSLOPE_MASK 0x0002 /* DACSLOPE */ +#define WM8961_DACSLOPE_SHIFT 1 /* DACSLOPE */ +#define WM8961_DACSLOPE_WIDTH 1 /* DACSLOPE */ +#define WM8961_DAC_OSR128 0x0001 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ + +/* + * R7 (0x07) - Audio Interface 0 + */ +#define WM8961_ALRSWAP 0x0100 /* ALRSWAP */ +#define WM8961_ALRSWAP_MASK 0x0100 /* ALRSWAP */ +#define WM8961_ALRSWAP_SHIFT 8 /* ALRSWAP */ +#define WM8961_ALRSWAP_WIDTH 1 /* ALRSWAP */ +#define WM8961_BCLKINV 0x0080 /* BCLKINV */ +#define WM8961_BCLKINV_MASK 0x0080 /* BCLKINV */ +#define WM8961_BCLKINV_SHIFT 7 /* BCLKINV */ +#define WM8961_BCLKINV_WIDTH 1 /* BCLKINV */ +#define WM8961_MS 0x0040 /* MS */ +#define WM8961_MS_MASK 0x0040 /* MS */ +#define WM8961_MS_SHIFT 6 /* MS */ +#define WM8961_MS_WIDTH 1 /* MS */ +#define WM8961_DLRSWAP 0x0020 /* DLRSWAP */ +#define WM8961_DLRSWAP_MASK 0x0020 /* DLRSWAP */ +#define WM8961_DLRSWAP_SHIFT 5 /* DLRSWAP */ +#define WM8961_DLRSWAP_WIDTH 1 /* DLRSWAP */ +#define WM8961_LRP 0x0010 /* LRP */ +#define WM8961_LRP_MASK 0x0010 /* LRP */ +#define WM8961_LRP_SHIFT 4 /* LRP */ +#define WM8961_LRP_WIDTH 1 /* LRP */ +#define WM8961_WL_MASK 0x000C /* WL - [3:2] */ +#define WM8961_WL_SHIFT 2 /* WL - [3:2] */ +#define WM8961_WL_WIDTH 2 /* WL - [3:2] */ +#define WM8961_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */ +#define WM8961_FORMAT_SHIFT 0 /* FORMAT - [1:0] */ +#define WM8961_FORMAT_WIDTH 2 /* FORMAT - [1:0] */ + +/* + * R8 (0x08) - Clocking2 + */ +#define WM8961_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8961_DCLKDIV_SHIFT 6 /* DCLKDIV - [8:6] */ +#define WM8961_DCLKDIV_WIDTH 3 /* DCLKDIV - [8:6] */ +#define WM8961_CLK_SYS_ENA 0x0020 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_MASK 0x0020 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_SHIFT 5 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8961_CLK_DSP_ENA 0x0010 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_MASK 0x0010 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_SHIFT 4 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8961_BCLKDIV_MASK 0x000F /* BCLKDIV - [3:0] */ +#define WM8961_BCLKDIV_SHIFT 0 /* BCLKDIV - [3:0] */ +#define WM8961_BCLKDIV_WIDTH 4 /* BCLKDIV - [3:0] */ + +/* + * R9 (0x09) - Audio Interface 1 + */ +#define WM8961_DACCOMP_MASK 0x0018 /* DACCOMP - [4:3] */ +#define WM8961_DACCOMP_SHIFT 3 /* DACCOMP - [4:3] */ +#define WM8961_DACCOMP_WIDTH 2 /* DACCOMP - [4:3] */ +#define WM8961_ADCCOMP_MASK 0x0006 /* ADCCOMP - [2:1] */ +#define WM8961_ADCCOMP_SHIFT 1 /* ADCCOMP - [2:1] */ +#define WM8961_ADCCOMP_WIDTH 2 /* ADCCOMP - [2:1] */ +#define WM8961_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8961_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8961_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8961_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R10 (0x0A) - Left DAC volume + */ +#define WM8961_DACVU 0x0100 /* DACVU */ +#define WM8961_DACVU_MASK 0x0100 /* DACVU */ +#define WM8961_DACVU_SHIFT 8 /* DACVU */ +#define WM8961_DACVU_WIDTH 1 /* DACVU */ +#define WM8961_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */ +#define WM8961_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */ +#define WM8961_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */ + +/* + * R11 (0x0B) - Right DAC volume + */ +#define WM8961_DACVU 0x0100 /* DACVU */ +#define WM8961_DACVU_MASK 0x0100 /* DACVU */ +#define WM8961_DACVU_SHIFT 8 /* DACVU */ +#define WM8961_DACVU_WIDTH 1 /* DACVU */ +#define WM8961_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */ +#define WM8961_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */ +#define WM8961_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */ + +/* + * R14 (0x0E) - Audio Interface 2 + */ +#define WM8961_LRCLK_RATE_MASK 0x01FF /* LRCLK_RATE - [8:0] */ +#define WM8961_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [8:0] */ +#define WM8961_LRCLK_RATE_WIDTH 9 /* LRCLK_RATE - [8:0] */ + +/* + * R15 (0x0F) - Software Reset + */ +#define WM8961_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8961_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8961_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R17 (0x11) - ALC1 + */ +#define WM8961_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */ +#define WM8961_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */ +#define WM8961_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */ +#define WM8961_MAXGAIN_MASK 0x0070 /* MAXGAIN - [6:4] */ +#define WM8961_MAXGAIN_SHIFT 4 /* MAXGAIN - [6:4] */ +#define WM8961_MAXGAIN_WIDTH 3 /* MAXGAIN - [6:4] */ +#define WM8961_ALCL_MASK 0x000F /* ALCL - [3:0] */ +#define WM8961_ALCL_SHIFT 0 /* ALCL - [3:0] */ +#define WM8961_ALCL_WIDTH 4 /* ALCL - [3:0] */ + +/* + * R18 (0x12) - ALC2 + */ +#define WM8961_ALCZC 0x0080 /* ALCZC */ +#define WM8961_ALCZC_MASK 0x0080 /* ALCZC */ +#define WM8961_ALCZC_SHIFT 7 /* ALCZC */ +#define WM8961_ALCZC_WIDTH 1 /* ALCZC */ +#define WM8961_MINGAIN_MASK 0x0070 /* MINGAIN - [6:4] */ +#define WM8961_MINGAIN_SHIFT 4 /* MINGAIN - [6:4] */ +#define WM8961_MINGAIN_WIDTH 3 /* MINGAIN - [6:4] */ +#define WM8961_HLD_MASK 0x000F /* HLD - [3:0] */ +#define WM8961_HLD_SHIFT 0 /* HLD - [3:0] */ +#define WM8961_HLD_WIDTH 4 /* HLD - [3:0] */ + +/* + * R19 (0x13) - ALC3 + */ +#define WM8961_ALCMODE 0x0100 /* ALCMODE */ +#define WM8961_ALCMODE_MASK 0x0100 /* ALCMODE */ +#define WM8961_ALCMODE_SHIFT 8 /* ALCMODE */ +#define WM8961_ALCMODE_WIDTH 1 /* ALCMODE */ +#define WM8961_DCY_MASK 0x00F0 /* DCY - [7:4] */ +#define WM8961_DCY_SHIFT 4 /* DCY - [7:4] */ +#define WM8961_DCY_WIDTH 4 /* DCY - [7:4] */ +#define WM8961_ATK_MASK 0x000F /* ATK - [3:0] */ +#define WM8961_ATK_SHIFT 0 /* ATK - [3:0] */ +#define WM8961_ATK_WIDTH 4 /* ATK - [3:0] */ + +/* + * R20 (0x14) - Noise Gate + */ +#define WM8961_NGTH_MASK 0x00F8 /* NGTH - [7:3] */ +#define WM8961_NGTH_SHIFT 3 /* NGTH - [7:3] */ +#define WM8961_NGTH_WIDTH 5 /* NGTH - [7:3] */ +#define WM8961_NGG 0x0002 /* NGG */ +#define WM8961_NGG_MASK 0x0002 /* NGG */ +#define WM8961_NGG_SHIFT 1 /* NGG */ +#define WM8961_NGG_WIDTH 1 /* NGG */ +#define WM8961_NGAT 0x0001 /* NGAT */ +#define WM8961_NGAT_MASK 0x0001 /* NGAT */ +#define WM8961_NGAT_SHIFT 0 /* NGAT */ +#define WM8961_NGAT_WIDTH 1 /* NGAT */ + +/* + * R21 (0x15) - Left ADC volume + */ +#define WM8961_ADCVU 0x0100 /* ADCVU */ +#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8961_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8961_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8961_LADCVOL_MASK 0x00FF /* LADCVOL - [7:0] */ +#define WM8961_LADCVOL_SHIFT 0 /* LADCVOL - [7:0] */ +#define WM8961_LADCVOL_WIDTH 8 /* LADCVOL - [7:0] */ + +/* + * R22 (0x16) - Right ADC volume + */ +#define WM8961_ADCVU 0x0100 /* ADCVU */ +#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8961_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8961_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8961_RADCVOL_MASK 0x00FF /* RADCVOL - [7:0] */ +#define WM8961_RADCVOL_SHIFT 0 /* RADCVOL - [7:0] */ +#define WM8961_RADCVOL_WIDTH 8 /* RADCVOL - [7:0] */ + +/* + * R23 (0x17) - Additional control(1) + */ +#define WM8961_TSDEN 0x0100 /* TSDEN */ +#define WM8961_TSDEN_MASK 0x0100 /* TSDEN */ +#define WM8961_TSDEN_SHIFT 8 /* TSDEN */ +#define WM8961_TSDEN_WIDTH 1 /* TSDEN */ +#define WM8961_DMONOMIX 0x0010 /* DMONOMIX */ +#define WM8961_DMONOMIX_MASK 0x0010 /* DMONOMIX */ +#define WM8961_DMONOMIX_SHIFT 4 /* DMONOMIX */ +#define WM8961_DMONOMIX_WIDTH 1 /* DMONOMIX */ +#define WM8961_TOEN 0x0001 /* TOEN */ +#define WM8961_TOEN_MASK 0x0001 /* TOEN */ +#define WM8961_TOEN_SHIFT 0 /* TOEN */ +#define WM8961_TOEN_WIDTH 1 /* TOEN */ + +/* + * R24 (0x18) - Additional control(2) + */ +#define WM8961_TRIS 0x0008 /* TRIS */ +#define WM8961_TRIS_MASK 0x0008 /* TRIS */ +#define WM8961_TRIS_SHIFT 3 /* TRIS */ +#define WM8961_TRIS_WIDTH 1 /* TRIS */ + +/* + * R25 (0x19) - Pwr Mgmt (1) + */ +#define WM8961_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */ +#define WM8961_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */ +#define WM8961_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */ +#define WM8961_VREF 0x0040 /* VREF */ +#define WM8961_VREF_MASK 0x0040 /* VREF */ +#define WM8961_VREF_SHIFT 6 /* VREF */ +#define WM8961_VREF_WIDTH 1 /* VREF */ +#define WM8961_AINL 0x0020 /* AINL */ +#define WM8961_AINL_MASK 0x0020 /* AINL */ +#define WM8961_AINL_SHIFT 5 /* AINL */ +#define WM8961_AINL_WIDTH 1 /* AINL */ +#define WM8961_AINR 0x0010 /* AINR */ +#define WM8961_AINR_MASK 0x0010 /* AINR */ +#define WM8961_AINR_SHIFT 4 /* AINR */ +#define WM8961_AINR_WIDTH 1 /* AINR */ +#define WM8961_ADCL 0x0008 /* ADCL */ +#define WM8961_ADCL_MASK 0x0008 /* ADCL */ +#define WM8961_ADCL_SHIFT 3 /* ADCL */ +#define WM8961_ADCL_WIDTH 1 /* ADCL */ +#define WM8961_ADCR 0x0004 /* ADCR */ +#define WM8961_ADCR_MASK 0x0004 /* ADCR */ +#define WM8961_ADCR_SHIFT 2 /* ADCR */ +#define WM8961_ADCR_WIDTH 1 /* ADCR */ +#define WM8961_MICB 0x0002 /* MICB */ +#define WM8961_MICB_MASK 0x0002 /* MICB */ +#define WM8961_MICB_SHIFT 1 /* MICB */ +#define WM8961_MICB_WIDTH 1 /* MICB */ + +/* + * R26 (0x1A) - Pwr Mgmt (2) + */ +#define WM8961_DACL 0x0100 /* DACL */ +#define WM8961_DACL_MASK 0x0100 /* DACL */ +#define WM8961_DACL_SHIFT 8 /* DACL */ +#define WM8961_DACL_WIDTH 1 /* DACL */ +#define WM8961_DACR 0x0080 /* DACR */ +#define WM8961_DACR_MASK 0x0080 /* DACR */ +#define WM8961_DACR_SHIFT 7 /* DACR */ +#define WM8961_DACR_WIDTH 1 /* DACR */ +#define WM8961_LOUT1_PGA 0x0040 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_MASK 0x0040 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_SHIFT 6 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_WIDTH 1 /* LOUT1_PGA */ +#define WM8961_ROUT1_PGA 0x0020 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_MASK 0x0020 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_SHIFT 5 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_WIDTH 1 /* ROUT1_PGA */ +#define WM8961_SPKL_PGA 0x0010 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_MASK 0x0010 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_SHIFT 4 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_WIDTH 1 /* SPKL_PGA */ +#define WM8961_SPKR_PGA 0x0008 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_MASK 0x0008 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_SHIFT 3 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_WIDTH 1 /* SPKR_PGA */ + +/* + * R27 (0x1B) - Additional Control (3) + */ +#define WM8961_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */ +#define WM8961_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */ +#define WM8961_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */ + +/* + * R28 (0x1C) - Anti-pop + */ +#define WM8961_BUFDCOPEN 0x0010 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_MASK 0x0010 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_SHIFT 4 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_WIDTH 1 /* BUFDCOPEN */ +#define WM8961_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8961_BUFIOEN_MASK 0x0008 /* BUFIOEN */ +#define WM8961_BUFIOEN_SHIFT 3 /* BUFIOEN */ +#define WM8961_BUFIOEN_WIDTH 1 /* BUFIOEN */ +#define WM8961_SOFT_ST 0x0004 /* SOFT_ST */ +#define WM8961_SOFT_ST_MASK 0x0004 /* SOFT_ST */ +#define WM8961_SOFT_ST_SHIFT 2 /* SOFT_ST */ +#define WM8961_SOFT_ST_WIDTH 1 /* SOFT_ST */ + +/* + * R30 (0x1E) - Clocking 3 + */ +#define WM8961_CLK_TO_DIV_MASK 0x0180 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_TO_DIV_SHIFT 7 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */ +#define WM8961_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */ +#define WM8961_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */ +#define WM8961_MANUAL_MODE 0x0001 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_MASK 0x0001 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_SHIFT 0 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_WIDTH 1 /* MANUAL_MODE */ + +/* + * R32 (0x20) - ADCL signal path + */ +#define WM8961_LMICBOOST_MASK 0x0030 /* LMICBOOST - [5:4] */ +#define WM8961_LMICBOOST_SHIFT 4 /* LMICBOOST - [5:4] */ +#define WM8961_LMICBOOST_WIDTH 2 /* LMICBOOST - [5:4] */ + +/* + * R33 (0x21) - ADCR signal path + */ +#define WM8961_RMICBOOST_MASK 0x0030 /* RMICBOOST - [5:4] */ +#define WM8961_RMICBOOST_SHIFT 4 /* RMICBOOST - [5:4] */ +#define WM8961_RMICBOOST_WIDTH 2 /* RMICBOOST - [5:4] */ + +/* + * R40 (0x28) - LOUT2 volume + */ +#define WM8961_SPKVU 0x0100 /* SPKVU */ +#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */ +#define WM8961_SPKVU_SHIFT 8 /* SPKVU */ +#define WM8961_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8961_SPKLZC 0x0080 /* SPKLZC */ +#define WM8961_SPKLZC_MASK 0x0080 /* SPKLZC */ +#define WM8961_SPKLZC_SHIFT 7 /* SPKLZC */ +#define WM8961_SPKLZC_WIDTH 1 /* SPKLZC */ +#define WM8961_SPKLVOL_MASK 0x007F /* SPKLVOL - [6:0] */ +#define WM8961_SPKLVOL_SHIFT 0 /* SPKLVOL - [6:0] */ +#define WM8961_SPKLVOL_WIDTH 7 /* SPKLVOL - [6:0] */ + +/* + * R41 (0x29) - ROUT2 volume + */ +#define WM8961_SPKVU 0x0100 /* SPKVU */ +#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */ +#define WM8961_SPKVU_SHIFT 8 /* SPKVU */ +#define WM8961_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8961_SPKRZC 0x0080 /* SPKRZC */ +#define WM8961_SPKRZC_MASK 0x0080 /* SPKRZC */ +#define WM8961_SPKRZC_SHIFT 7 /* SPKRZC */ +#define WM8961_SPKRZC_WIDTH 1 /* SPKRZC */ +#define WM8961_SPKRVOL_MASK 0x007F /* SPKRVOL - [6:0] */ +#define WM8961_SPKRVOL_SHIFT 0 /* SPKRVOL - [6:0] */ +#define WM8961_SPKRVOL_WIDTH 7 /* SPKRVOL - [6:0] */ + +/* + * R47 (0x2F) - Pwr Mgmt (3) + */ +#define WM8961_TEMP_SHUT 0x0002 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_MASK 0x0002 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_SHIFT 1 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */ +#define WM8961_TEMP_WARN 0x0001 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_MASK 0x0001 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_SHIFT 0 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_WIDTH 1 /* TEMP_WARN */ + +/* + * R48 (0x30) - Additional Control (4) + */ +#define WM8961_TSENSEN 0x0002 /* TSENSEN */ +#define WM8961_TSENSEN_MASK 0x0002 /* TSENSEN */ +#define WM8961_TSENSEN_SHIFT 1 /* TSENSEN */ +#define WM8961_TSENSEN_WIDTH 1 /* TSENSEN */ +#define WM8961_MBSEL 0x0001 /* MBSEL */ +#define WM8961_MBSEL_MASK 0x0001 /* MBSEL */ +#define WM8961_MBSEL_SHIFT 0 /* MBSEL */ +#define WM8961_MBSEL_WIDTH 1 /* MBSEL */ + +/* + * R49 (0x31) - Class D Control 1 + */ +#define WM8961_SPKR_ENA 0x0080 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_MASK 0x0080 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_SHIFT 7 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ +#define WM8961_SPKL_ENA 0x0040 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_MASK 0x0040 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_SHIFT 6 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ + +/* + * R51 (0x33) - Class D Control 2 + */ +#define WM8961_CLASSD_ACGAIN_MASK 0x0007 /* CLASSD_ACGAIN - [2:0] */ +#define WM8961_CLASSD_ACGAIN_SHIFT 0 /* CLASSD_ACGAIN - [2:0] */ +#define WM8961_CLASSD_ACGAIN_WIDTH 3 /* CLASSD_ACGAIN - [2:0] */ + +/* + * R56 (0x38) - Clocking 4 + */ +#define WM8961_CLK_DCS_DIV_MASK 0x01E0 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_DCS_DIV_SHIFT 5 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */ +#define WM8961_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */ +#define WM8961_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */ + +/* + * R57 (0x39) - DSP Sidetone 0 + */ +#define WM8961_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADC_TO_DACR_MASK 0x000C /* ADC_TO_DACR - [3:2] */ +#define WM8961_ADC_TO_DACR_SHIFT 2 /* ADC_TO_DACR - [3:2] */ +#define WM8961_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [3:2] */ + +/* + * R58 (0x3A) - DSP Sidetone 1 + */ +#define WM8961_ADCL_DAC_SVOL_MASK 0x00F0 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADCL_DAC_SVOL_SHIFT 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8961_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8961_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ + +/* + * R60 (0x3C) - DC Servo 0 + */ +#define WM8961_DCS_ENA_CHAN_INL 0x0080 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_MASK 0x0080 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_SHIFT 7 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_WIDTH 1 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL 0x0040 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_MASK 0x0040 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_SHIFT 6 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_WIDTH 1 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_SERIES_INL 0x0010 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_MASK 0x0010 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_SHIFT 4 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_WIDTH 1 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_ENA_CHAN_INR 0x0008 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_MASK 0x0008 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_SHIFT 3 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_WIDTH 1 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR 0x0004 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_MASK 0x0004 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_SHIFT 2 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_WIDTH 1 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_SERIES_INR 0x0001 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_MASK 0x0001 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_SHIFT 0 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_WIDTH 1 /* DCS_TRIG_SERIES_INR */ + +/* + * R61 (0x3D) - DC Servo 1 + */ +#define WM8961_DCS_ENA_CHAN_HPL 0x0080 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_MASK 0x0080 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_SHIFT 7 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_WIDTH 1 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL 0x0040 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_MASK 0x0040 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_SHIFT 6 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_WIDTH 1 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL 0x0010 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_MASK 0x0010 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_SHIFT 4 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_WIDTH 1 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_ENA_CHAN_HPR 0x0008 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_MASK 0x0008 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_SHIFT 3 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_WIDTH 1 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR 0x0004 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_MASK 0x0004 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_SHIFT 2 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_WIDTH 1 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR 0x0001 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_MASK 0x0001 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_SHIFT 0 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_WIDTH 1 /* DCS_TRIG_SERIES_HPR */ + +/* + * R63 (0x3F) - DC Servo 3 + */ +#define WM8961_DCS_FILT_BW_SERIES_MASK 0x0030 /* DCS_FILT_BW_SERIES - [5:4] */ +#define WM8961_DCS_FILT_BW_SERIES_SHIFT 4 /* DCS_FILT_BW_SERIES - [5:4] */ +#define WM8961_DCS_FILT_BW_SERIES_WIDTH 2 /* DCS_FILT_BW_SERIES - [5:4] */ + +/* + * R65 (0x41) - DC Servo 5 + */ +#define WM8961_DCS_SERIES_NO_HP_MASK 0x007F /* DCS_SERIES_NO_HP - [6:0] */ +#define WM8961_DCS_SERIES_NO_HP_SHIFT 0 /* DCS_SERIES_NO_HP - [6:0] */ +#define WM8961_DCS_SERIES_NO_HP_WIDTH 7 /* DCS_SERIES_NO_HP - [6:0] */ + +/* + * R68 (0x44) - Analogue PGA Bias + */ +#define WM8961_HP_PGAS_BIAS_MASK 0x0007 /* HP_PGAS_BIAS - [2:0] */ +#define WM8961_HP_PGAS_BIAS_SHIFT 0 /* HP_PGAS_BIAS - [2:0] */ +#define WM8961_HP_PGAS_BIAS_WIDTH 3 /* HP_PGAS_BIAS - [2:0] */ + +/* + * R69 (0x45) - Analogue HP 0 + */ +#define WM8961_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8961_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8961_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8961_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8961_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8961_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8961_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8961_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8961_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8961_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R71 (0x47) - Analogue HP 2 + */ +#define WM8961_HPL_VOL_MASK 0x01C0 /* HPL_VOL - [8:6] */ +#define WM8961_HPL_VOL_SHIFT 6 /* HPL_VOL - [8:6] */ +#define WM8961_HPL_VOL_WIDTH 3 /* HPL_VOL - [8:6] */ +#define WM8961_HPR_VOL_MASK 0x0038 /* HPR_VOL - [5:3] */ +#define WM8961_HPR_VOL_SHIFT 3 /* HPR_VOL - [5:3] */ +#define WM8961_HPR_VOL_WIDTH 3 /* HPR_VOL - [5:3] */ +#define WM8961_HP_BIAS_BOOST_MASK 0x0007 /* HP_BIAS_BOOST - [2:0] */ +#define WM8961_HP_BIAS_BOOST_SHIFT 0 /* HP_BIAS_BOOST - [2:0] */ +#define WM8961_HP_BIAS_BOOST_WIDTH 3 /* HP_BIAS_BOOST - [2:0] */ + +/* + * R72 (0x48) - Charge Pump 1 + */ +#define WM8961_CP_ENA 0x0001 /* CP_ENA */ +#define WM8961_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8961_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8961_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R82 (0x52) - Charge Pump B + */ +#define WM8961_CP_DYN_PWR_MASK 0x0003 /* CP_DYN_PWR - [1:0] */ +#define WM8961_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR - [1:0] */ +#define WM8961_CP_DYN_PWR_WIDTH 2 /* CP_DYN_PWR - [1:0] */ + +/* + * R87 (0x57) - Write Sequencer 1 + */ +#define WM8961_WSEQ_ENA 0x0020 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_MASK 0x0020 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_SHIFT 5 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8961_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8961_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8961_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R88 (0x58) - Write Sequencer 2 + */ +#define WM8961_WSEQ_EOS 0x0100 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_MASK 0x0100 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_SHIFT 8 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8961_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8961_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8961_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R89 (0x59) - Write Sequencer 3 + */ +#define WM8961_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8961_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8961_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R90 (0x5A) - Write Sequencer 4 + */ +#define WM8961_WSEQ_ABORT 0x0100 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_MASK 0x0100 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_SHIFT 8 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8961_WSEQ_START 0x0080 /* WSEQ_START */ +#define WM8961_WSEQ_START_MASK 0x0080 /* WSEQ_START */ +#define WM8961_WSEQ_START_SHIFT 7 /* WSEQ_START */ +#define WM8961_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8961_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8961_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8961_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R91 (0x5B) - Write Sequencer 5 + */ +#define WM8961_WSEQ_DATA_WIDTH_MASK 0x0070 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_WIDTH_SHIFT 4 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_START_MASK 0x000F /* WSEQ_DATA_START - [3:0] */ +#define WM8961_WSEQ_DATA_START_SHIFT 0 /* WSEQ_DATA_START - [3:0] */ +#define WM8961_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [3:0] */ + +/* + * R92 (0x5C) - Write Sequencer 6 + */ +#define WM8961_WSEQ_DELAY_MASK 0x000F /* WSEQ_DELAY - [3:0] */ +#define WM8961_WSEQ_DELAY_SHIFT 0 /* WSEQ_DELAY - [3:0] */ +#define WM8961_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [3:0] */ + +/* + * R93 (0x5D) - Write Sequencer 7 + */ +#define WM8961_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R252 (0xFC) - General test 1 + */ +#define WM8961_ARA_ENA 0x0002 /* ARA_ENA */ +#define WM8961_ARA_ENA_MASK 0x0002 /* ARA_ENA */ +#define WM8961_ARA_ENA_SHIFT 1 /* ARA_ENA */ +#define WM8961_ARA_ENA_WIDTH 1 /* ARA_ENA */ +#define WM8961_AUTO_INC 0x0001 /* AUTO_INC */ +#define WM8961_AUTO_INC_MASK 0x0001 /* AUTO_INC */ +#define WM8961_AUTO_INC_SHIFT 0 /* AUTO_INC */ +#define WM8961_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +#endif diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c new file mode 100644 index 000000000..3f75cb320 --- /dev/null +++ b/sound/soc/codecs/wm8962.c @@ -0,0 +1,3895 @@ +/* + * wm8962.c -- WM8962 ALSA SoC Audio driver + * + * Copyright 2010-2 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8962.h" + +#define WM8962_NUM_SUPPLIES 8 +static const char *wm8962_supply_names[WM8962_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "AVDD", + "CPVDD", + "MICVDD", + "PLLVDD", + "SPKVDD1", + "SPKVDD2", +}; + +/* codec private data */ +struct wm8962_priv { + struct wm8962_pdata pdata; + struct regmap *regmap; + struct snd_soc_component *component; + + int sysclk; + int sysclk_rate; + + int bclk; /* Desired BCLK */ + int lrclk; + + struct completion fll_lock; + int fll_src; + int fll_fref; + int fll_fout; + + struct mutex dsp2_ena_lock; + u16 dsp2_ena; + + struct delayed_work mic_work; + struct snd_soc_jack *jack; + + struct regulator_bulk_data supplies[WM8962_NUM_SUPPLIES]; + struct notifier_block disable_nb[WM8962_NUM_SUPPLIES]; + + struct input_dev *beep; + struct work_struct beep_work; + int beep_rate; + +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif + + int irq; +}; + +/* We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define WM8962_REGULATOR_EVENT(n) \ +static int wm8962_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct wm8962_priv *wm8962 = container_of(nb, struct wm8962_priv, \ + disable_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + regcache_mark_dirty(wm8962->regmap); \ + } \ + return 0; \ +} + +WM8962_REGULATOR_EVENT(0) +WM8962_REGULATOR_EVENT(1) +WM8962_REGULATOR_EVENT(2) +WM8962_REGULATOR_EVENT(3) +WM8962_REGULATOR_EVENT(4) +WM8962_REGULATOR_EVENT(5) +WM8962_REGULATOR_EVENT(6) +WM8962_REGULATOR_EVENT(7) + +static const struct reg_default wm8962_reg[] = { + { 0, 0x009F }, /* R0 - Left Input volume */ + { 1, 0x049F }, /* R1 - Right Input volume */ + { 2, 0x0000 }, /* R2 - HPOUTL volume */ + { 3, 0x0000 }, /* R3 - HPOUTR volume */ + + { 5, 0x0018 }, /* R5 - ADC & DAC Control 1 */ + { 6, 0x2008 }, /* R6 - ADC & DAC Control 2 */ + { 7, 0x000A }, /* R7 - Audio Interface 0 */ + + { 9, 0x0300 }, /* R9 - Audio Interface 1 */ + { 10, 0x00C0 }, /* R10 - Left DAC volume */ + { 11, 0x00C0 }, /* R11 - Right DAC volume */ + + { 14, 0x0040 }, /* R14 - Audio Interface 2 */ + { 15, 0x6243 }, /* R15 - Software Reset */ + + { 17, 0x007B }, /* R17 - ALC1 */ + { 18, 0x0000 }, /* R18 - ALC2 */ + { 19, 0x1C32 }, /* R19 - ALC3 */ + { 20, 0x3200 }, /* R20 - Noise Gate */ + { 21, 0x00C0 }, /* R21 - Left ADC volume */ + { 22, 0x00C0 }, /* R22 - Right ADC volume */ + { 23, 0x0160 }, /* R23 - Additional control(1) */ + { 24, 0x0000 }, /* R24 - Additional control(2) */ + { 25, 0x0000 }, /* R25 - Pwr Mgmt (1) */ + { 26, 0x0000 }, /* R26 - Pwr Mgmt (2) */ + { 27, 0x0010 }, /* R27 - Additional Control (3) */ + { 28, 0x0000 }, /* R28 - Anti-pop */ + + { 30, 0x005E }, /* R30 - Clocking 3 */ + { 31, 0x0000 }, /* R31 - Input mixer control (1) */ + { 32, 0x0145 }, /* R32 - Left input mixer volume */ + { 33, 0x0145 }, /* R33 - Right input mixer volume */ + { 34, 0x0009 }, /* R34 - Input mixer control (2) */ + { 35, 0x0003 }, /* R35 - Input bias control */ + { 37, 0x0008 }, /* R37 - Left input PGA control */ + { 38, 0x0008 }, /* R38 - Right input PGA control */ + + { 40, 0x0000 }, /* R40 - SPKOUTL volume */ + { 41, 0x0000 }, /* R41 - SPKOUTR volume */ + + { 49, 0x0010 }, /* R49 - Class D Control 1 */ + { 51, 0x0003 }, /* R51 - Class D Control 2 */ + + { 56, 0x0506 }, /* R56 - Clocking 4 */ + { 57, 0x0000 }, /* R57 - DAC DSP Mixing (1) */ + { 58, 0x0000 }, /* R58 - DAC DSP Mixing (2) */ + + { 60, 0x0300 }, /* R60 - DC Servo 0 */ + { 61, 0x0300 }, /* R61 - DC Servo 1 */ + + { 64, 0x0810 }, /* R64 - DC Servo 4 */ + + { 68, 0x001B }, /* R68 - Analogue PGA Bias */ + { 69, 0x0000 }, /* R69 - Analogue HP 0 */ + + { 71, 0x01FB }, /* R71 - Analogue HP 2 */ + { 72, 0x0000 }, /* R72 - Charge Pump 1 */ + + { 82, 0x0004 }, /* R82 - Charge Pump B */ + + { 87, 0x0000 }, /* R87 - Write Sequencer Control 1 */ + + { 90, 0x0000 }, /* R90 - Write Sequencer Control 2 */ + + { 93, 0x0000 }, /* R93 - Write Sequencer Control 3 */ + { 94, 0x0000 }, /* R94 - Control Interface */ + + { 99, 0x0000 }, /* R99 - Mixer Enables */ + { 100, 0x0000 }, /* R100 - Headphone Mixer (1) */ + { 101, 0x0000 }, /* R101 - Headphone Mixer (2) */ + { 102, 0x013F }, /* R102 - Headphone Mixer (3) */ + { 103, 0x013F }, /* R103 - Headphone Mixer (4) */ + + { 105, 0x0000 }, /* R105 - Speaker Mixer (1) */ + { 106, 0x0000 }, /* R106 - Speaker Mixer (2) */ + { 107, 0x013F }, /* R107 - Speaker Mixer (3) */ + { 108, 0x013F }, /* R108 - Speaker Mixer (4) */ + { 109, 0x0003 }, /* R109 - Speaker Mixer (5) */ + { 110, 0x0002 }, /* R110 - Beep Generator (1) */ + + { 115, 0x0006 }, /* R115 - Oscillator Trim (3) */ + { 116, 0x0026 }, /* R116 - Oscillator Trim (4) */ + + { 119, 0x0000 }, /* R119 - Oscillator Trim (7) */ + + { 124, 0x0011 }, /* R124 - Analogue Clocking1 */ + { 125, 0x004B }, /* R125 - Analogue Clocking2 */ + { 126, 0x000D }, /* R126 - Analogue Clocking3 */ + { 127, 0x0000 }, /* R127 - PLL Software Reset */ + + { 131, 0x0000 }, /* R131 - PLL 4 */ + + { 136, 0x0067 }, /* R136 - PLL 9 */ + { 137, 0x001C }, /* R137 - PLL 10 */ + { 138, 0x0071 }, /* R138 - PLL 11 */ + { 139, 0x00C7 }, /* R139 - PLL 12 */ + { 140, 0x0067 }, /* R140 - PLL 13 */ + { 141, 0x0048 }, /* R141 - PLL 14 */ + { 142, 0x0022 }, /* R142 - PLL 15 */ + { 143, 0x0097 }, /* R143 - PLL 16 */ + + { 155, 0x000C }, /* R155 - FLL Control (1) */ + { 156, 0x0039 }, /* R156 - FLL Control (2) */ + { 157, 0x0180 }, /* R157 - FLL Control (3) */ + + { 159, 0x0032 }, /* R159 - FLL Control (5) */ + { 160, 0x0018 }, /* R160 - FLL Control (6) */ + { 161, 0x007D }, /* R161 - FLL Control (7) */ + { 162, 0x0008 }, /* R162 - FLL Control (8) */ + + { 252, 0x0005 }, /* R252 - General test 1 */ + + { 256, 0x0000 }, /* R256 - DF1 */ + { 257, 0x0000 }, /* R257 - DF2 */ + { 258, 0x0000 }, /* R258 - DF3 */ + { 259, 0x0000 }, /* R259 - DF4 */ + { 260, 0x0000 }, /* R260 - DF5 */ + { 261, 0x0000 }, /* R261 - DF6 */ + { 262, 0x0000 }, /* R262 - DF7 */ + + { 264, 0x0000 }, /* R264 - LHPF1 */ + { 265, 0x0000 }, /* R265 - LHPF2 */ + + { 268, 0x0000 }, /* R268 - THREED1 */ + { 269, 0x0000 }, /* R269 - THREED2 */ + { 270, 0x0000 }, /* R270 - THREED3 */ + { 271, 0x0000 }, /* R271 - THREED4 */ + + { 276, 0x000C }, /* R276 - DRC 1 */ + { 277, 0x0925 }, /* R277 - DRC 2 */ + { 278, 0x0000 }, /* R278 - DRC 3 */ + { 279, 0x0000 }, /* R279 - DRC 4 */ + { 280, 0x0000 }, /* R280 - DRC 5 */ + + { 285, 0x0000 }, /* R285 - Tloopback */ + + { 335, 0x0004 }, /* R335 - EQ1 */ + { 336, 0x6318 }, /* R336 - EQ2 */ + { 337, 0x6300 }, /* R337 - EQ3 */ + { 338, 0x0FCA }, /* R338 - EQ4 */ + { 339, 0x0400 }, /* R339 - EQ5 */ + { 340, 0x00D8 }, /* R340 - EQ6 */ + { 341, 0x1EB5 }, /* R341 - EQ7 */ + { 342, 0xF145 }, /* R342 - EQ8 */ + { 343, 0x0B75 }, /* R343 - EQ9 */ + { 344, 0x01C5 }, /* R344 - EQ10 */ + { 345, 0x1C58 }, /* R345 - EQ11 */ + { 346, 0xF373 }, /* R346 - EQ12 */ + { 347, 0x0A54 }, /* R347 - EQ13 */ + { 348, 0x0558 }, /* R348 - EQ14 */ + { 349, 0x168E }, /* R349 - EQ15 */ + { 350, 0xF829 }, /* R350 - EQ16 */ + { 351, 0x07AD }, /* R351 - EQ17 */ + { 352, 0x1103 }, /* R352 - EQ18 */ + { 353, 0x0564 }, /* R353 - EQ19 */ + { 354, 0x0559 }, /* R354 - EQ20 */ + { 355, 0x4000 }, /* R355 - EQ21 */ + { 356, 0x6318 }, /* R356 - EQ22 */ + { 357, 0x6300 }, /* R357 - EQ23 */ + { 358, 0x0FCA }, /* R358 - EQ24 */ + { 359, 0x0400 }, /* R359 - EQ25 */ + { 360, 0x00D8 }, /* R360 - EQ26 */ + { 361, 0x1EB5 }, /* R361 - EQ27 */ + { 362, 0xF145 }, /* R362 - EQ28 */ + { 363, 0x0B75 }, /* R363 - EQ29 */ + { 364, 0x01C5 }, /* R364 - EQ30 */ + { 365, 0x1C58 }, /* R365 - EQ31 */ + { 366, 0xF373 }, /* R366 - EQ32 */ + { 367, 0x0A54 }, /* R367 - EQ33 */ + { 368, 0x0558 }, /* R368 - EQ34 */ + { 369, 0x168E }, /* R369 - EQ35 */ + { 370, 0xF829 }, /* R370 - EQ36 */ + { 371, 0x07AD }, /* R371 - EQ37 */ + { 372, 0x1103 }, /* R372 - EQ38 */ + { 373, 0x0564 }, /* R373 - EQ39 */ + { 374, 0x0559 }, /* R374 - EQ40 */ + { 375, 0x4000 }, /* R375 - EQ41 */ + + { 513, 0x0000 }, /* R513 - GPIO 2 */ + { 514, 0x0000 }, /* R514 - GPIO 3 */ + + { 516, 0x8100 }, /* R516 - GPIO 5 */ + { 517, 0x8100 }, /* R517 - GPIO 6 */ + + { 568, 0x0030 }, /* R568 - Interrupt Status 1 Mask */ + { 569, 0xFFED }, /* R569 - Interrupt Status 2 Mask */ + + { 576, 0x0000 }, /* R576 - Interrupt Control */ + + { 584, 0x002D }, /* R584 - IRQ Debounce */ + + { 586, 0x0000 }, /* R586 - MICINT Source Pol */ + + { 768, 0x1C00 }, /* R768 - DSP2 Power Management */ + + { 8192, 0x0000 }, /* R8192 - DSP2 Instruction RAM 0 */ + + { 9216, 0x0030 }, /* R9216 - DSP2 Address RAM 2 */ + { 9217, 0x0000 }, /* R9217 - DSP2 Address RAM 1 */ + { 9218, 0x0000 }, /* R9218 - DSP2 Address RAM 0 */ + + { 12288, 0x0000 }, /* R12288 - DSP2 Data1 RAM 1 */ + { 12289, 0x0000 }, /* R12289 - DSP2 Data1 RAM 0 */ + + { 13312, 0x0000 }, /* R13312 - DSP2 Data2 RAM 1 */ + { 13313, 0x0000 }, /* R13313 - DSP2 Data2 RAM 0 */ + + { 14336, 0x0000 }, /* R14336 - DSP2 Data3 RAM 1 */ + { 14337, 0x0000 }, /* R14337 - DSP2 Data3 RAM 0 */ + + { 15360, 0x000A }, /* R15360 - DSP2 Coeff RAM 0 */ + + { 16384, 0x0000 }, /* R16384 - RETUNEADC_SHARED_COEFF_1 */ + { 16385, 0x0000 }, /* R16385 - RETUNEADC_SHARED_COEFF_0 */ + { 16386, 0x0000 }, /* R16386 - RETUNEDAC_SHARED_COEFF_1 */ + { 16387, 0x0000 }, /* R16387 - RETUNEDAC_SHARED_COEFF_0 */ + { 16388, 0x0000 }, /* R16388 - SOUNDSTAGE_ENABLES_1 */ + { 16389, 0x0000 }, /* R16389 - SOUNDSTAGE_ENABLES_0 */ + + { 16896, 0x0002 }, /* R16896 - HDBASS_AI_1 */ + { 16897, 0xBD12 }, /* R16897 - HDBASS_AI_0 */ + { 16898, 0x007C }, /* R16898 - HDBASS_AR_1 */ + { 16899, 0x586C }, /* R16899 - HDBASS_AR_0 */ + { 16900, 0x0053 }, /* R16900 - HDBASS_B_1 */ + { 16901, 0x8121 }, /* R16901 - HDBASS_B_0 */ + { 16902, 0x003F }, /* R16902 - HDBASS_K_1 */ + { 16903, 0x8BD8 }, /* R16903 - HDBASS_K_0 */ + { 16904, 0x0032 }, /* R16904 - HDBASS_N1_1 */ + { 16905, 0xF52D }, /* R16905 - HDBASS_N1_0 */ + { 16906, 0x0065 }, /* R16906 - HDBASS_N2_1 */ + { 16907, 0xAC8C }, /* R16907 - HDBASS_N2_0 */ + { 16908, 0x006B }, /* R16908 - HDBASS_N3_1 */ + { 16909, 0xE087 }, /* R16909 - HDBASS_N3_0 */ + { 16910, 0x0072 }, /* R16910 - HDBASS_N4_1 */ + { 16911, 0x1483 }, /* R16911 - HDBASS_N4_0 */ + { 16912, 0x0072 }, /* R16912 - HDBASS_N5_1 */ + { 16913, 0x1483 }, /* R16913 - HDBASS_N5_0 */ + { 16914, 0x0043 }, /* R16914 - HDBASS_X1_1 */ + { 16915, 0x3525 }, /* R16915 - HDBASS_X1_0 */ + { 16916, 0x0006 }, /* R16916 - HDBASS_X2_1 */ + { 16917, 0x6A4A }, /* R16917 - HDBASS_X2_0 */ + { 16918, 0x0043 }, /* R16918 - HDBASS_X3_1 */ + { 16919, 0x6079 }, /* R16919 - HDBASS_X3_0 */ + { 16920, 0x0008 }, /* R16920 - HDBASS_ATK_1 */ + { 16921, 0x0000 }, /* R16921 - HDBASS_ATK_0 */ + { 16922, 0x0001 }, /* R16922 - HDBASS_DCY_1 */ + { 16923, 0x0000 }, /* R16923 - HDBASS_DCY_0 */ + { 16924, 0x0059 }, /* R16924 - HDBASS_PG_1 */ + { 16925, 0x999A }, /* R16925 - HDBASS_PG_0 */ + + { 17408, 0x0083 }, /* R17408 - HPF_C_1 */ + { 17409, 0x98AD }, /* R17409 - HPF_C_0 */ + + { 17920, 0x007F }, /* R17920 - ADCL_RETUNE_C1_1 */ + { 17921, 0xFFFF }, /* R17921 - ADCL_RETUNE_C1_0 */ + { 17922, 0x0000 }, /* R17922 - ADCL_RETUNE_C2_1 */ + { 17923, 0x0000 }, /* R17923 - ADCL_RETUNE_C2_0 */ + { 17924, 0x0000 }, /* R17924 - ADCL_RETUNE_C3_1 */ + { 17925, 0x0000 }, /* R17925 - ADCL_RETUNE_C3_0 */ + { 17926, 0x0000 }, /* R17926 - ADCL_RETUNE_C4_1 */ + { 17927, 0x0000 }, /* R17927 - ADCL_RETUNE_C4_0 */ + { 17928, 0x0000 }, /* R17928 - ADCL_RETUNE_C5_1 */ + { 17929, 0x0000 }, /* R17929 - ADCL_RETUNE_C5_0 */ + { 17930, 0x0000 }, /* R17930 - ADCL_RETUNE_C6_1 */ + { 17931, 0x0000 }, /* R17931 - ADCL_RETUNE_C6_0 */ + { 17932, 0x0000 }, /* R17932 - ADCL_RETUNE_C7_1 */ + { 17933, 0x0000 }, /* R17933 - ADCL_RETUNE_C7_0 */ + { 17934, 0x0000 }, /* R17934 - ADCL_RETUNE_C8_1 */ + { 17935, 0x0000 }, /* R17935 - ADCL_RETUNE_C8_0 */ + { 17936, 0x0000 }, /* R17936 - ADCL_RETUNE_C9_1 */ + { 17937, 0x0000 }, /* R17937 - ADCL_RETUNE_C9_0 */ + { 17938, 0x0000 }, /* R17938 - ADCL_RETUNE_C10_1 */ + { 17939, 0x0000 }, /* R17939 - ADCL_RETUNE_C10_0 */ + { 17940, 0x0000 }, /* R17940 - ADCL_RETUNE_C11_1 */ + { 17941, 0x0000 }, /* R17941 - ADCL_RETUNE_C11_0 */ + { 17942, 0x0000 }, /* R17942 - ADCL_RETUNE_C12_1 */ + { 17943, 0x0000 }, /* R17943 - ADCL_RETUNE_C12_0 */ + { 17944, 0x0000 }, /* R17944 - ADCL_RETUNE_C13_1 */ + { 17945, 0x0000 }, /* R17945 - ADCL_RETUNE_C13_0 */ + { 17946, 0x0000 }, /* R17946 - ADCL_RETUNE_C14_1 */ + { 17947, 0x0000 }, /* R17947 - ADCL_RETUNE_C14_0 */ + { 17948, 0x0000 }, /* R17948 - ADCL_RETUNE_C15_1 */ + { 17949, 0x0000 }, /* R17949 - ADCL_RETUNE_C15_0 */ + { 17950, 0x0000 }, /* R17950 - ADCL_RETUNE_C16_1 */ + { 17951, 0x0000 }, /* R17951 - ADCL_RETUNE_C16_0 */ + { 17952, 0x0000 }, /* R17952 - ADCL_RETUNE_C17_1 */ + { 17953, 0x0000 }, /* R17953 - ADCL_RETUNE_C17_0 */ + { 17954, 0x0000 }, /* R17954 - ADCL_RETUNE_C18_1 */ + { 17955, 0x0000 }, /* R17955 - ADCL_RETUNE_C18_0 */ + { 17956, 0x0000 }, /* R17956 - ADCL_RETUNE_C19_1 */ + { 17957, 0x0000 }, /* R17957 - ADCL_RETUNE_C19_0 */ + { 17958, 0x0000 }, /* R17958 - ADCL_RETUNE_C20_1 */ + { 17959, 0x0000 }, /* R17959 - ADCL_RETUNE_C20_0 */ + { 17960, 0x0000 }, /* R17960 - ADCL_RETUNE_C21_1 */ + { 17961, 0x0000 }, /* R17961 - ADCL_RETUNE_C21_0 */ + { 17962, 0x0000 }, /* R17962 - ADCL_RETUNE_C22_1 */ + { 17963, 0x0000 }, /* R17963 - ADCL_RETUNE_C22_0 */ + { 17964, 0x0000 }, /* R17964 - ADCL_RETUNE_C23_1 */ + { 17965, 0x0000 }, /* R17965 - ADCL_RETUNE_C23_0 */ + { 17966, 0x0000 }, /* R17966 - ADCL_RETUNE_C24_1 */ + { 17967, 0x0000 }, /* R17967 - ADCL_RETUNE_C24_0 */ + { 17968, 0x0000 }, /* R17968 - ADCL_RETUNE_C25_1 */ + { 17969, 0x0000 }, /* R17969 - ADCL_RETUNE_C25_0 */ + { 17970, 0x0000 }, /* R17970 - ADCL_RETUNE_C26_1 */ + { 17971, 0x0000 }, /* R17971 - ADCL_RETUNE_C26_0 */ + { 17972, 0x0000 }, /* R17972 - ADCL_RETUNE_C27_1 */ + { 17973, 0x0000 }, /* R17973 - ADCL_RETUNE_C27_0 */ + { 17974, 0x0000 }, /* R17974 - ADCL_RETUNE_C28_1 */ + { 17975, 0x0000 }, /* R17975 - ADCL_RETUNE_C28_0 */ + { 17976, 0x0000 }, /* R17976 - ADCL_RETUNE_C29_1 */ + { 17977, 0x0000 }, /* R17977 - ADCL_RETUNE_C29_0 */ + { 17978, 0x0000 }, /* R17978 - ADCL_RETUNE_C30_1 */ + { 17979, 0x0000 }, /* R17979 - ADCL_RETUNE_C30_0 */ + { 17980, 0x0000 }, /* R17980 - ADCL_RETUNE_C31_1 */ + { 17981, 0x0000 }, /* R17981 - ADCL_RETUNE_C31_0 */ + { 17982, 0x0000 }, /* R17982 - ADCL_RETUNE_C32_1 */ + { 17983, 0x0000 }, /* R17983 - ADCL_RETUNE_C32_0 */ + + { 18432, 0x0020 }, /* R18432 - RETUNEADC_PG2_1 */ + { 18433, 0x0000 }, /* R18433 - RETUNEADC_PG2_0 */ + { 18434, 0x0040 }, /* R18434 - RETUNEADC_PG_1 */ + { 18435, 0x0000 }, /* R18435 - RETUNEADC_PG_0 */ + + { 18944, 0x007F }, /* R18944 - ADCR_RETUNE_C1_1 */ + { 18945, 0xFFFF }, /* R18945 - ADCR_RETUNE_C1_0 */ + { 18946, 0x0000 }, /* R18946 - ADCR_RETUNE_C2_1 */ + { 18947, 0x0000 }, /* R18947 - ADCR_RETUNE_C2_0 */ + { 18948, 0x0000 }, /* R18948 - ADCR_RETUNE_C3_1 */ + { 18949, 0x0000 }, /* R18949 - ADCR_RETUNE_C3_0 */ + { 18950, 0x0000 }, /* R18950 - ADCR_RETUNE_C4_1 */ + { 18951, 0x0000 }, /* R18951 - ADCR_RETUNE_C4_0 */ + { 18952, 0x0000 }, /* R18952 - ADCR_RETUNE_C5_1 */ + { 18953, 0x0000 }, /* R18953 - ADCR_RETUNE_C5_0 */ + { 18954, 0x0000 }, /* R18954 - ADCR_RETUNE_C6_1 */ + { 18955, 0x0000 }, /* R18955 - ADCR_RETUNE_C6_0 */ + { 18956, 0x0000 }, /* R18956 - ADCR_RETUNE_C7_1 */ + { 18957, 0x0000 }, /* R18957 - ADCR_RETUNE_C7_0 */ + { 18958, 0x0000 }, /* R18958 - ADCR_RETUNE_C8_1 */ + { 18959, 0x0000 }, /* R18959 - ADCR_RETUNE_C8_0 */ + { 18960, 0x0000 }, /* R18960 - ADCR_RETUNE_C9_1 */ + { 18961, 0x0000 }, /* R18961 - ADCR_RETUNE_C9_0 */ + { 18962, 0x0000 }, /* R18962 - ADCR_RETUNE_C10_1 */ + { 18963, 0x0000 }, /* R18963 - ADCR_RETUNE_C10_0 */ + { 18964, 0x0000 }, /* R18964 - ADCR_RETUNE_C11_1 */ + { 18965, 0x0000 }, /* R18965 - ADCR_RETUNE_C11_0 */ + { 18966, 0x0000 }, /* R18966 - ADCR_RETUNE_C12_1 */ + { 18967, 0x0000 }, /* R18967 - ADCR_RETUNE_C12_0 */ + { 18968, 0x0000 }, /* R18968 - ADCR_RETUNE_C13_1 */ + { 18969, 0x0000 }, /* R18969 - ADCR_RETUNE_C13_0 */ + { 18970, 0x0000 }, /* R18970 - ADCR_RETUNE_C14_1 */ + { 18971, 0x0000 }, /* R18971 - ADCR_RETUNE_C14_0 */ + { 18972, 0x0000 }, /* R18972 - ADCR_RETUNE_C15_1 */ + { 18973, 0x0000 }, /* R18973 - ADCR_RETUNE_C15_0 */ + { 18974, 0x0000 }, /* R18974 - ADCR_RETUNE_C16_1 */ + { 18975, 0x0000 }, /* R18975 - ADCR_RETUNE_C16_0 */ + { 18976, 0x0000 }, /* R18976 - ADCR_RETUNE_C17_1 */ + { 18977, 0x0000 }, /* R18977 - ADCR_RETUNE_C17_0 */ + { 18978, 0x0000 }, /* R18978 - ADCR_RETUNE_C18_1 */ + { 18979, 0x0000 }, /* R18979 - ADCR_RETUNE_C18_0 */ + { 18980, 0x0000 }, /* R18980 - ADCR_RETUNE_C19_1 */ + { 18981, 0x0000 }, /* R18981 - ADCR_RETUNE_C19_0 */ + { 18982, 0x0000 }, /* R18982 - ADCR_RETUNE_C20_1 */ + { 18983, 0x0000 }, /* R18983 - ADCR_RETUNE_C20_0 */ + { 18984, 0x0000 }, /* R18984 - ADCR_RETUNE_C21_1 */ + { 18985, 0x0000 }, /* R18985 - ADCR_RETUNE_C21_0 */ + { 18986, 0x0000 }, /* R18986 - ADCR_RETUNE_C22_1 */ + { 18987, 0x0000 }, /* R18987 - ADCR_RETUNE_C22_0 */ + { 18988, 0x0000 }, /* R18988 - ADCR_RETUNE_C23_1 */ + { 18989, 0x0000 }, /* R18989 - ADCR_RETUNE_C23_0 */ + { 18990, 0x0000 }, /* R18990 - ADCR_RETUNE_C24_1 */ + { 18991, 0x0000 }, /* R18991 - ADCR_RETUNE_C24_0 */ + { 18992, 0x0000 }, /* R18992 - ADCR_RETUNE_C25_1 */ + { 18993, 0x0000 }, /* R18993 - ADCR_RETUNE_C25_0 */ + { 18994, 0x0000 }, /* R18994 - ADCR_RETUNE_C26_1 */ + { 18995, 0x0000 }, /* R18995 - ADCR_RETUNE_C26_0 */ + { 18996, 0x0000 }, /* R18996 - ADCR_RETUNE_C27_1 */ + { 18997, 0x0000 }, /* R18997 - ADCR_RETUNE_C27_0 */ + { 18998, 0x0000 }, /* R18998 - ADCR_RETUNE_C28_1 */ + { 18999, 0x0000 }, /* R18999 - ADCR_RETUNE_C28_0 */ + { 19000, 0x0000 }, /* R19000 - ADCR_RETUNE_C29_1 */ + { 19001, 0x0000 }, /* R19001 - ADCR_RETUNE_C29_0 */ + { 19002, 0x0000 }, /* R19002 - ADCR_RETUNE_C30_1 */ + { 19003, 0x0000 }, /* R19003 - ADCR_RETUNE_C30_0 */ + { 19004, 0x0000 }, /* R19004 - ADCR_RETUNE_C31_1 */ + { 19005, 0x0000 }, /* R19005 - ADCR_RETUNE_C31_0 */ + { 19006, 0x0000 }, /* R19006 - ADCR_RETUNE_C32_1 */ + { 19007, 0x0000 }, /* R19007 - ADCR_RETUNE_C32_0 */ + + { 19456, 0x007F }, /* R19456 - DACL_RETUNE_C1_1 */ + { 19457, 0xFFFF }, /* R19457 - DACL_RETUNE_C1_0 */ + { 19458, 0x0000 }, /* R19458 - DACL_RETUNE_C2_1 */ + { 19459, 0x0000 }, /* R19459 - DACL_RETUNE_C2_0 */ + { 19460, 0x0000 }, /* R19460 - DACL_RETUNE_C3_1 */ + { 19461, 0x0000 }, /* R19461 - DACL_RETUNE_C3_0 */ + { 19462, 0x0000 }, /* R19462 - DACL_RETUNE_C4_1 */ + { 19463, 0x0000 }, /* R19463 - DACL_RETUNE_C4_0 */ + { 19464, 0x0000 }, /* R19464 - DACL_RETUNE_C5_1 */ + { 19465, 0x0000 }, /* R19465 - DACL_RETUNE_C5_0 */ + { 19466, 0x0000 }, /* R19466 - DACL_RETUNE_C6_1 */ + { 19467, 0x0000 }, /* R19467 - DACL_RETUNE_C6_0 */ + { 19468, 0x0000 }, /* R19468 - DACL_RETUNE_C7_1 */ + { 19469, 0x0000 }, /* R19469 - DACL_RETUNE_C7_0 */ + { 19470, 0x0000 }, /* R19470 - DACL_RETUNE_C8_1 */ + { 19471, 0x0000 }, /* R19471 - DACL_RETUNE_C8_0 */ + { 19472, 0x0000 }, /* R19472 - DACL_RETUNE_C9_1 */ + { 19473, 0x0000 }, /* R19473 - DACL_RETUNE_C9_0 */ + { 19474, 0x0000 }, /* R19474 - DACL_RETUNE_C10_1 */ + { 19475, 0x0000 }, /* R19475 - DACL_RETUNE_C10_0 */ + { 19476, 0x0000 }, /* R19476 - DACL_RETUNE_C11_1 */ + { 19477, 0x0000 }, /* R19477 - DACL_RETUNE_C11_0 */ + { 19478, 0x0000 }, /* R19478 - DACL_RETUNE_C12_1 */ + { 19479, 0x0000 }, /* R19479 - DACL_RETUNE_C12_0 */ + { 19480, 0x0000 }, /* R19480 - DACL_RETUNE_C13_1 */ + { 19481, 0x0000 }, /* R19481 - DACL_RETUNE_C13_0 */ + { 19482, 0x0000 }, /* R19482 - DACL_RETUNE_C14_1 */ + { 19483, 0x0000 }, /* R19483 - DACL_RETUNE_C14_0 */ + { 19484, 0x0000 }, /* R19484 - DACL_RETUNE_C15_1 */ + { 19485, 0x0000 }, /* R19485 - DACL_RETUNE_C15_0 */ + { 19486, 0x0000 }, /* R19486 - DACL_RETUNE_C16_1 */ + { 19487, 0x0000 }, /* R19487 - DACL_RETUNE_C16_0 */ + { 19488, 0x0000 }, /* R19488 - DACL_RETUNE_C17_1 */ + { 19489, 0x0000 }, /* R19489 - DACL_RETUNE_C17_0 */ + { 19490, 0x0000 }, /* R19490 - DACL_RETUNE_C18_1 */ + { 19491, 0x0000 }, /* R19491 - DACL_RETUNE_C18_0 */ + { 19492, 0x0000 }, /* R19492 - DACL_RETUNE_C19_1 */ + { 19493, 0x0000 }, /* R19493 - DACL_RETUNE_C19_0 */ + { 19494, 0x0000 }, /* R19494 - DACL_RETUNE_C20_1 */ + { 19495, 0x0000 }, /* R19495 - DACL_RETUNE_C20_0 */ + { 19496, 0x0000 }, /* R19496 - DACL_RETUNE_C21_1 */ + { 19497, 0x0000 }, /* R19497 - DACL_RETUNE_C21_0 */ + { 19498, 0x0000 }, /* R19498 - DACL_RETUNE_C22_1 */ + { 19499, 0x0000 }, /* R19499 - DACL_RETUNE_C22_0 */ + { 19500, 0x0000 }, /* R19500 - DACL_RETUNE_C23_1 */ + { 19501, 0x0000 }, /* R19501 - DACL_RETUNE_C23_0 */ + { 19502, 0x0000 }, /* R19502 - DACL_RETUNE_C24_1 */ + { 19503, 0x0000 }, /* R19503 - DACL_RETUNE_C24_0 */ + { 19504, 0x0000 }, /* R19504 - DACL_RETUNE_C25_1 */ + { 19505, 0x0000 }, /* R19505 - DACL_RETUNE_C25_0 */ + { 19506, 0x0000 }, /* R19506 - DACL_RETUNE_C26_1 */ + { 19507, 0x0000 }, /* R19507 - DACL_RETUNE_C26_0 */ + { 19508, 0x0000 }, /* R19508 - DACL_RETUNE_C27_1 */ + { 19509, 0x0000 }, /* R19509 - DACL_RETUNE_C27_0 */ + { 19510, 0x0000 }, /* R19510 - DACL_RETUNE_C28_1 */ + { 19511, 0x0000 }, /* R19511 - DACL_RETUNE_C28_0 */ + { 19512, 0x0000 }, /* R19512 - DACL_RETUNE_C29_1 */ + { 19513, 0x0000 }, /* R19513 - DACL_RETUNE_C29_0 */ + { 19514, 0x0000 }, /* R19514 - DACL_RETUNE_C30_1 */ + { 19515, 0x0000 }, /* R19515 - DACL_RETUNE_C30_0 */ + { 19516, 0x0000 }, /* R19516 - DACL_RETUNE_C31_1 */ + { 19517, 0x0000 }, /* R19517 - DACL_RETUNE_C31_0 */ + { 19518, 0x0000 }, /* R19518 - DACL_RETUNE_C32_1 */ + { 19519, 0x0000 }, /* R19519 - DACL_RETUNE_C32_0 */ + + { 19968, 0x0020 }, /* R19968 - RETUNEDAC_PG2_1 */ + { 19969, 0x0000 }, /* R19969 - RETUNEDAC_PG2_0 */ + { 19970, 0x0040 }, /* R19970 - RETUNEDAC_PG_1 */ + { 19971, 0x0000 }, /* R19971 - RETUNEDAC_PG_0 */ + + { 20480, 0x007F }, /* R20480 - DACR_RETUNE_C1_1 */ + { 20481, 0xFFFF }, /* R20481 - DACR_RETUNE_C1_0 */ + { 20482, 0x0000 }, /* R20482 - DACR_RETUNE_C2_1 */ + { 20483, 0x0000 }, /* R20483 - DACR_RETUNE_C2_0 */ + { 20484, 0x0000 }, /* R20484 - DACR_RETUNE_C3_1 */ + { 20485, 0x0000 }, /* R20485 - DACR_RETUNE_C3_0 */ + { 20486, 0x0000 }, /* R20486 - DACR_RETUNE_C4_1 */ + { 20487, 0x0000 }, /* R20487 - DACR_RETUNE_C4_0 */ + { 20488, 0x0000 }, /* R20488 - DACR_RETUNE_C5_1 */ + { 20489, 0x0000 }, /* R20489 - DACR_RETUNE_C5_0 */ + { 20490, 0x0000 }, /* R20490 - DACR_RETUNE_C6_1 */ + { 20491, 0x0000 }, /* R20491 - DACR_RETUNE_C6_0 */ + { 20492, 0x0000 }, /* R20492 - DACR_RETUNE_C7_1 */ + { 20493, 0x0000 }, /* R20493 - DACR_RETUNE_C7_0 */ + { 20494, 0x0000 }, /* R20494 - DACR_RETUNE_C8_1 */ + { 20495, 0x0000 }, /* R20495 - DACR_RETUNE_C8_0 */ + { 20496, 0x0000 }, /* R20496 - DACR_RETUNE_C9_1 */ + { 20497, 0x0000 }, /* R20497 - DACR_RETUNE_C9_0 */ + { 20498, 0x0000 }, /* R20498 - DACR_RETUNE_C10_1 */ + { 20499, 0x0000 }, /* R20499 - DACR_RETUNE_C10_0 */ + { 20500, 0x0000 }, /* R20500 - DACR_RETUNE_C11_1 */ + { 20501, 0x0000 }, /* R20501 - DACR_RETUNE_C11_0 */ + { 20502, 0x0000 }, /* R20502 - DACR_RETUNE_C12_1 */ + { 20503, 0x0000 }, /* R20503 - DACR_RETUNE_C12_0 */ + { 20504, 0x0000 }, /* R20504 - DACR_RETUNE_C13_1 */ + { 20505, 0x0000 }, /* R20505 - DACR_RETUNE_C13_0 */ + { 20506, 0x0000 }, /* R20506 - DACR_RETUNE_C14_1 */ + { 20507, 0x0000 }, /* R20507 - DACR_RETUNE_C14_0 */ + { 20508, 0x0000 }, /* R20508 - DACR_RETUNE_C15_1 */ + { 20509, 0x0000 }, /* R20509 - DACR_RETUNE_C15_0 */ + { 20510, 0x0000 }, /* R20510 - DACR_RETUNE_C16_1 */ + { 20511, 0x0000 }, /* R20511 - DACR_RETUNE_C16_0 */ + { 20512, 0x0000 }, /* R20512 - DACR_RETUNE_C17_1 */ + { 20513, 0x0000 }, /* R20513 - DACR_RETUNE_C17_0 */ + { 20514, 0x0000 }, /* R20514 - DACR_RETUNE_C18_1 */ + { 20515, 0x0000 }, /* R20515 - DACR_RETUNE_C18_0 */ + { 20516, 0x0000 }, /* R20516 - DACR_RETUNE_C19_1 */ + { 20517, 0x0000 }, /* R20517 - DACR_RETUNE_C19_0 */ + { 20518, 0x0000 }, /* R20518 - DACR_RETUNE_C20_1 */ + { 20519, 0x0000 }, /* R20519 - DACR_RETUNE_C20_0 */ + { 20520, 0x0000 }, /* R20520 - DACR_RETUNE_C21_1 */ + { 20521, 0x0000 }, /* R20521 - DACR_RETUNE_C21_0 */ + { 20522, 0x0000 }, /* R20522 - DACR_RETUNE_C22_1 */ + { 20523, 0x0000 }, /* R20523 - DACR_RETUNE_C22_0 */ + { 20524, 0x0000 }, /* R20524 - DACR_RETUNE_C23_1 */ + { 20525, 0x0000 }, /* R20525 - DACR_RETUNE_C23_0 */ + { 20526, 0x0000 }, /* R20526 - DACR_RETUNE_C24_1 */ + { 20527, 0x0000 }, /* R20527 - DACR_RETUNE_C24_0 */ + { 20528, 0x0000 }, /* R20528 - DACR_RETUNE_C25_1 */ + { 20529, 0x0000 }, /* R20529 - DACR_RETUNE_C25_0 */ + { 20530, 0x0000 }, /* R20530 - DACR_RETUNE_C26_1 */ + { 20531, 0x0000 }, /* R20531 - DACR_RETUNE_C26_0 */ + { 20532, 0x0000 }, /* R20532 - DACR_RETUNE_C27_1 */ + { 20533, 0x0000 }, /* R20533 - DACR_RETUNE_C27_0 */ + { 20534, 0x0000 }, /* R20534 - DACR_RETUNE_C28_1 */ + { 20535, 0x0000 }, /* R20535 - DACR_RETUNE_C28_0 */ + { 20536, 0x0000 }, /* R20536 - DACR_RETUNE_C29_1 */ + { 20537, 0x0000 }, /* R20537 - DACR_RETUNE_C29_0 */ + { 20538, 0x0000 }, /* R20538 - DACR_RETUNE_C30_1 */ + { 20539, 0x0000 }, /* R20539 - DACR_RETUNE_C30_0 */ + { 20540, 0x0000 }, /* R20540 - DACR_RETUNE_C31_1 */ + { 20541, 0x0000 }, /* R20541 - DACR_RETUNE_C31_0 */ + { 20542, 0x0000 }, /* R20542 - DACR_RETUNE_C32_1 */ + { 20543, 0x0000 }, /* R20543 - DACR_RETUNE_C32_0 */ + + { 20992, 0x008C }, /* R20992 - VSS_XHD2_1 */ + { 20993, 0x0200 }, /* R20993 - VSS_XHD2_0 */ + { 20994, 0x0035 }, /* R20994 - VSS_XHD3_1 */ + { 20995, 0x0700 }, /* R20995 - VSS_XHD3_0 */ + { 20996, 0x003A }, /* R20996 - VSS_XHN1_1 */ + { 20997, 0x4100 }, /* R20997 - VSS_XHN1_0 */ + { 20998, 0x008B }, /* R20998 - VSS_XHN2_1 */ + { 20999, 0x7D00 }, /* R20999 - VSS_XHN2_0 */ + { 21000, 0x003A }, /* R21000 - VSS_XHN3_1 */ + { 21001, 0x4100 }, /* R21001 - VSS_XHN3_0 */ + { 21002, 0x008C }, /* R21002 - VSS_XLA_1 */ + { 21003, 0xFEE8 }, /* R21003 - VSS_XLA_0 */ + { 21004, 0x0078 }, /* R21004 - VSS_XLB_1 */ + { 21005, 0x0000 }, /* R21005 - VSS_XLB_0 */ + { 21006, 0x003F }, /* R21006 - VSS_XLG_1 */ + { 21007, 0xB260 }, /* R21007 - VSS_XLG_0 */ + { 21008, 0x002D }, /* R21008 - VSS_PG2_1 */ + { 21009, 0x1818 }, /* R21009 - VSS_PG2_0 */ + { 21010, 0x0020 }, /* R21010 - VSS_PG_1 */ + { 21011, 0x0000 }, /* R21011 - VSS_PG_0 */ + { 21012, 0x00F1 }, /* R21012 - VSS_XTD1_1 */ + { 21013, 0x8340 }, /* R21013 - VSS_XTD1_0 */ + { 21014, 0x00FB }, /* R21014 - VSS_XTD2_1 */ + { 21015, 0x8300 }, /* R21015 - VSS_XTD2_0 */ + { 21016, 0x00EE }, /* R21016 - VSS_XTD3_1 */ + { 21017, 0xAEC0 }, /* R21017 - VSS_XTD3_0 */ + { 21018, 0x00FB }, /* R21018 - VSS_XTD4_1 */ + { 21019, 0xAC40 }, /* R21019 - VSS_XTD4_0 */ + { 21020, 0x00F1 }, /* R21020 - VSS_XTD5_1 */ + { 21021, 0x7F80 }, /* R21021 - VSS_XTD5_0 */ + { 21022, 0x00F4 }, /* R21022 - VSS_XTD6_1 */ + { 21023, 0x3B40 }, /* R21023 - VSS_XTD6_0 */ + { 21024, 0x00F5 }, /* R21024 - VSS_XTD7_1 */ + { 21025, 0xFB00 }, /* R21025 - VSS_XTD7_0 */ + { 21026, 0x00EA }, /* R21026 - VSS_XTD8_1 */ + { 21027, 0x10C0 }, /* R21027 - VSS_XTD8_0 */ + { 21028, 0x00FC }, /* R21028 - VSS_XTD9_1 */ + { 21029, 0xC580 }, /* R21029 - VSS_XTD9_0 */ + { 21030, 0x00E2 }, /* R21030 - VSS_XTD10_1 */ + { 21031, 0x75C0 }, /* R21031 - VSS_XTD10_0 */ + { 21032, 0x0004 }, /* R21032 - VSS_XTD11_1 */ + { 21033, 0xB480 }, /* R21033 - VSS_XTD11_0 */ + { 21034, 0x00D4 }, /* R21034 - VSS_XTD12_1 */ + { 21035, 0xF980 }, /* R21035 - VSS_XTD12_0 */ + { 21036, 0x0004 }, /* R21036 - VSS_XTD13_1 */ + { 21037, 0x9140 }, /* R21037 - VSS_XTD13_0 */ + { 21038, 0x00D8 }, /* R21038 - VSS_XTD14_1 */ + { 21039, 0xA480 }, /* R21039 - VSS_XTD14_0 */ + { 21040, 0x0002 }, /* R21040 - VSS_XTD15_1 */ + { 21041, 0x3DC0 }, /* R21041 - VSS_XTD15_0 */ + { 21042, 0x00CF }, /* R21042 - VSS_XTD16_1 */ + { 21043, 0x7A80 }, /* R21043 - VSS_XTD16_0 */ + { 21044, 0x00DC }, /* R21044 - VSS_XTD17_1 */ + { 21045, 0x0600 }, /* R21045 - VSS_XTD17_0 */ + { 21046, 0x00F2 }, /* R21046 - VSS_XTD18_1 */ + { 21047, 0xDAC0 }, /* R21047 - VSS_XTD18_0 */ + { 21048, 0x00BA }, /* R21048 - VSS_XTD19_1 */ + { 21049, 0xF340 }, /* R21049 - VSS_XTD19_0 */ + { 21050, 0x000A }, /* R21050 - VSS_XTD20_1 */ + { 21051, 0x7940 }, /* R21051 - VSS_XTD20_0 */ + { 21052, 0x001C }, /* R21052 - VSS_XTD21_1 */ + { 21053, 0x0680 }, /* R21053 - VSS_XTD21_0 */ + { 21054, 0x00FD }, /* R21054 - VSS_XTD22_1 */ + { 21055, 0x2D00 }, /* R21055 - VSS_XTD22_0 */ + { 21056, 0x001C }, /* R21056 - VSS_XTD23_1 */ + { 21057, 0xE840 }, /* R21057 - VSS_XTD23_0 */ + { 21058, 0x000D }, /* R21058 - VSS_XTD24_1 */ + { 21059, 0xDC40 }, /* R21059 - VSS_XTD24_0 */ + { 21060, 0x00FC }, /* R21060 - VSS_XTD25_1 */ + { 21061, 0x9D00 }, /* R21061 - VSS_XTD25_0 */ + { 21062, 0x0009 }, /* R21062 - VSS_XTD26_1 */ + { 21063, 0x5580 }, /* R21063 - VSS_XTD26_0 */ + { 21064, 0x00FE }, /* R21064 - VSS_XTD27_1 */ + { 21065, 0x7E80 }, /* R21065 - VSS_XTD27_0 */ + { 21066, 0x000E }, /* R21066 - VSS_XTD28_1 */ + { 21067, 0xAB40 }, /* R21067 - VSS_XTD28_0 */ + { 21068, 0x00F9 }, /* R21068 - VSS_XTD29_1 */ + { 21069, 0x9880 }, /* R21069 - VSS_XTD29_0 */ + { 21070, 0x0009 }, /* R21070 - VSS_XTD30_1 */ + { 21071, 0x87C0 }, /* R21071 - VSS_XTD30_0 */ + { 21072, 0x00FD }, /* R21072 - VSS_XTD31_1 */ + { 21073, 0x2C40 }, /* R21073 - VSS_XTD31_0 */ + { 21074, 0x0009 }, /* R21074 - VSS_XTD32_1 */ + { 21075, 0x4800 }, /* R21075 - VSS_XTD32_0 */ + { 21076, 0x0003 }, /* R21076 - VSS_XTS1_1 */ + { 21077, 0x5F40 }, /* R21077 - VSS_XTS1_0 */ + { 21078, 0x0000 }, /* R21078 - VSS_XTS2_1 */ + { 21079, 0x8700 }, /* R21079 - VSS_XTS2_0 */ + { 21080, 0x00FA }, /* R21080 - VSS_XTS3_1 */ + { 21081, 0xE4C0 }, /* R21081 - VSS_XTS3_0 */ + { 21082, 0x0000 }, /* R21082 - VSS_XTS4_1 */ + { 21083, 0x0B40 }, /* R21083 - VSS_XTS4_0 */ + { 21084, 0x0004 }, /* R21084 - VSS_XTS5_1 */ + { 21085, 0xE180 }, /* R21085 - VSS_XTS5_0 */ + { 21086, 0x0001 }, /* R21086 - VSS_XTS6_1 */ + { 21087, 0x1F40 }, /* R21087 - VSS_XTS6_0 */ + { 21088, 0x00F8 }, /* R21088 - VSS_XTS7_1 */ + { 21089, 0xB000 }, /* R21089 - VSS_XTS7_0 */ + { 21090, 0x00FB }, /* R21090 - VSS_XTS8_1 */ + { 21091, 0xCBC0 }, /* R21091 - VSS_XTS8_0 */ + { 21092, 0x0004 }, /* R21092 - VSS_XTS9_1 */ + { 21093, 0xF380 }, /* R21093 - VSS_XTS9_0 */ + { 21094, 0x0007 }, /* R21094 - VSS_XTS10_1 */ + { 21095, 0xDF40 }, /* R21095 - VSS_XTS10_0 */ + { 21096, 0x00FF }, /* R21096 - VSS_XTS11_1 */ + { 21097, 0x0700 }, /* R21097 - VSS_XTS11_0 */ + { 21098, 0x00EF }, /* R21098 - VSS_XTS12_1 */ + { 21099, 0xD700 }, /* R21099 - VSS_XTS12_0 */ + { 21100, 0x00FB }, /* R21100 - VSS_XTS13_1 */ + { 21101, 0xAF40 }, /* R21101 - VSS_XTS13_0 */ + { 21102, 0x0010 }, /* R21102 - VSS_XTS14_1 */ + { 21103, 0x8A80 }, /* R21103 - VSS_XTS14_0 */ + { 21104, 0x0011 }, /* R21104 - VSS_XTS15_1 */ + { 21105, 0x07C0 }, /* R21105 - VSS_XTS15_0 */ + { 21106, 0x00E0 }, /* R21106 - VSS_XTS16_1 */ + { 21107, 0x0800 }, /* R21107 - VSS_XTS16_0 */ + { 21108, 0x00D2 }, /* R21108 - VSS_XTS17_1 */ + { 21109, 0x7600 }, /* R21109 - VSS_XTS17_0 */ + { 21110, 0x0020 }, /* R21110 - VSS_XTS18_1 */ + { 21111, 0xCF40 }, /* R21111 - VSS_XTS18_0 */ + { 21112, 0x0030 }, /* R21112 - VSS_XTS19_1 */ + { 21113, 0x2340 }, /* R21113 - VSS_XTS19_0 */ + { 21114, 0x00FD }, /* R21114 - VSS_XTS20_1 */ + { 21115, 0x69C0 }, /* R21115 - VSS_XTS20_0 */ + { 21116, 0x0028 }, /* R21116 - VSS_XTS21_1 */ + { 21117, 0x3500 }, /* R21117 - VSS_XTS21_0 */ + { 21118, 0x0006 }, /* R21118 - VSS_XTS22_1 */ + { 21119, 0x3300 }, /* R21119 - VSS_XTS22_0 */ + { 21120, 0x00D9 }, /* R21120 - VSS_XTS23_1 */ + { 21121, 0xF6C0 }, /* R21121 - VSS_XTS23_0 */ + { 21122, 0x00F3 }, /* R21122 - VSS_XTS24_1 */ + { 21123, 0x3340 }, /* R21123 - VSS_XTS24_0 */ + { 21124, 0x000F }, /* R21124 - VSS_XTS25_1 */ + { 21125, 0x4200 }, /* R21125 - VSS_XTS25_0 */ + { 21126, 0x0004 }, /* R21126 - VSS_XTS26_1 */ + { 21127, 0x0C80 }, /* R21127 - VSS_XTS26_0 */ + { 21128, 0x00FB }, /* R21128 - VSS_XTS27_1 */ + { 21129, 0x3F80 }, /* R21129 - VSS_XTS27_0 */ + { 21130, 0x00F7 }, /* R21130 - VSS_XTS28_1 */ + { 21131, 0x57C0 }, /* R21131 - VSS_XTS28_0 */ + { 21132, 0x0003 }, /* R21132 - VSS_XTS29_1 */ + { 21133, 0x5400 }, /* R21133 - VSS_XTS29_0 */ + { 21134, 0x0000 }, /* R21134 - VSS_XTS30_1 */ + { 21135, 0xC6C0 }, /* R21135 - VSS_XTS30_0 */ + { 21136, 0x0003 }, /* R21136 - VSS_XTS31_1 */ + { 21137, 0x12C0 }, /* R21137 - VSS_XTS31_0 */ + { 21138, 0x00FD }, /* R21138 - VSS_XTS32_1 */ + { 21139, 0x8580 }, /* R21139 - VSS_XTS32_0 */ +}; + +static bool wm8962_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8962_CLOCKING1: + case WM8962_CLOCKING2: + case WM8962_SOFTWARE_RESET: + case WM8962_THERMAL_SHUTDOWN_STATUS: + case WM8962_ADDITIONAL_CONTROL_4: + case WM8962_DC_SERVO_6: + case WM8962_INTERRUPT_STATUS_1: + case WM8962_INTERRUPT_STATUS_2: + case WM8962_DSP2_EXECCONTROL: + return true; + default: + return false; + } +} + +static bool wm8962_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8962_LEFT_INPUT_VOLUME: + case WM8962_RIGHT_INPUT_VOLUME: + case WM8962_HPOUTL_VOLUME: + case WM8962_HPOUTR_VOLUME: + case WM8962_CLOCKING1: + case WM8962_ADC_DAC_CONTROL_1: + case WM8962_ADC_DAC_CONTROL_2: + case WM8962_AUDIO_INTERFACE_0: + case WM8962_CLOCKING2: + case WM8962_AUDIO_INTERFACE_1: + case WM8962_LEFT_DAC_VOLUME: + case WM8962_RIGHT_DAC_VOLUME: + case WM8962_AUDIO_INTERFACE_2: + case WM8962_SOFTWARE_RESET: + case WM8962_ALC1: + case WM8962_ALC2: + case WM8962_ALC3: + case WM8962_NOISE_GATE: + case WM8962_LEFT_ADC_VOLUME: + case WM8962_RIGHT_ADC_VOLUME: + case WM8962_ADDITIONAL_CONTROL_1: + case WM8962_ADDITIONAL_CONTROL_2: + case WM8962_PWR_MGMT_1: + case WM8962_PWR_MGMT_2: + case WM8962_ADDITIONAL_CONTROL_3: + case WM8962_ANTI_POP: + case WM8962_CLOCKING_3: + case WM8962_INPUT_MIXER_CONTROL_1: + case WM8962_LEFT_INPUT_MIXER_VOLUME: + case WM8962_RIGHT_INPUT_MIXER_VOLUME: + case WM8962_INPUT_MIXER_CONTROL_2: + case WM8962_INPUT_BIAS_CONTROL: + case WM8962_LEFT_INPUT_PGA_CONTROL: + case WM8962_RIGHT_INPUT_PGA_CONTROL: + case WM8962_SPKOUTL_VOLUME: + case WM8962_SPKOUTR_VOLUME: + case WM8962_THERMAL_SHUTDOWN_STATUS: + case WM8962_ADDITIONAL_CONTROL_4: + case WM8962_CLASS_D_CONTROL_1: + case WM8962_CLASS_D_CONTROL_2: + case WM8962_CLOCKING_4: + case WM8962_DAC_DSP_MIXING_1: + case WM8962_DAC_DSP_MIXING_2: + case WM8962_DC_SERVO_0: + case WM8962_DC_SERVO_1: + case WM8962_DC_SERVO_4: + case WM8962_DC_SERVO_6: + case WM8962_ANALOGUE_PGA_BIAS: + case WM8962_ANALOGUE_HP_0: + case WM8962_ANALOGUE_HP_2: + case WM8962_CHARGE_PUMP_1: + case WM8962_CHARGE_PUMP_B: + case WM8962_WRITE_SEQUENCER_CONTROL_1: + case WM8962_WRITE_SEQUENCER_CONTROL_2: + case WM8962_WRITE_SEQUENCER_CONTROL_3: + case WM8962_CONTROL_INTERFACE: + case WM8962_MIXER_ENABLES: + case WM8962_HEADPHONE_MIXER_1: + case WM8962_HEADPHONE_MIXER_2: + case WM8962_HEADPHONE_MIXER_3: + case WM8962_HEADPHONE_MIXER_4: + case WM8962_SPEAKER_MIXER_1: + case WM8962_SPEAKER_MIXER_2: + case WM8962_SPEAKER_MIXER_3: + case WM8962_SPEAKER_MIXER_4: + case WM8962_SPEAKER_MIXER_5: + case WM8962_BEEP_GENERATOR_1: + case WM8962_OSCILLATOR_TRIM_3: + case WM8962_OSCILLATOR_TRIM_4: + case WM8962_OSCILLATOR_TRIM_7: + case WM8962_ANALOGUE_CLOCKING1: + case WM8962_ANALOGUE_CLOCKING2: + case WM8962_ANALOGUE_CLOCKING3: + case WM8962_PLL_SOFTWARE_RESET: + case WM8962_PLL2: + case WM8962_PLL_4: + case WM8962_PLL_9: + case WM8962_PLL_10: + case WM8962_PLL_11: + case WM8962_PLL_12: + case WM8962_PLL_13: + case WM8962_PLL_14: + case WM8962_PLL_15: + case WM8962_PLL_16: + case WM8962_FLL_CONTROL_1: + case WM8962_FLL_CONTROL_2: + case WM8962_FLL_CONTROL_3: + case WM8962_FLL_CONTROL_5: + case WM8962_FLL_CONTROL_6: + case WM8962_FLL_CONTROL_7: + case WM8962_FLL_CONTROL_8: + case WM8962_GENERAL_TEST_1: + case WM8962_DF1: + case WM8962_DF2: + case WM8962_DF3: + case WM8962_DF4: + case WM8962_DF5: + case WM8962_DF6: + case WM8962_DF7: + case WM8962_LHPF1: + case WM8962_LHPF2: + case WM8962_THREED1: + case WM8962_THREED2: + case WM8962_THREED3: + case WM8962_THREED4: + case WM8962_DRC_1: + case WM8962_DRC_2: + case WM8962_DRC_3: + case WM8962_DRC_4: + case WM8962_DRC_5: + case WM8962_TLOOPBACK: + case WM8962_EQ1: + case WM8962_EQ2: + case WM8962_EQ3: + case WM8962_EQ4: + case WM8962_EQ5: + case WM8962_EQ6: + case WM8962_EQ7: + case WM8962_EQ8: + case WM8962_EQ9: + case WM8962_EQ10: + case WM8962_EQ11: + case WM8962_EQ12: + case WM8962_EQ13: + case WM8962_EQ14: + case WM8962_EQ15: + case WM8962_EQ16: + case WM8962_EQ17: + case WM8962_EQ18: + case WM8962_EQ19: + case WM8962_EQ20: + case WM8962_EQ21: + case WM8962_EQ22: + case WM8962_EQ23: + case WM8962_EQ24: + case WM8962_EQ25: + case WM8962_EQ26: + case WM8962_EQ27: + case WM8962_EQ28: + case WM8962_EQ29: + case WM8962_EQ30: + case WM8962_EQ31: + case WM8962_EQ32: + case WM8962_EQ33: + case WM8962_EQ34: + case WM8962_EQ35: + case WM8962_EQ36: + case WM8962_EQ37: + case WM8962_EQ38: + case WM8962_EQ39: + case WM8962_EQ40: + case WM8962_EQ41: + case WM8962_GPIO_BASE: + case WM8962_GPIO_2: + case WM8962_GPIO_3: + case WM8962_GPIO_5: + case WM8962_GPIO_6: + case WM8962_INTERRUPT_STATUS_1: + case WM8962_INTERRUPT_STATUS_2: + case WM8962_INTERRUPT_STATUS_1_MASK: + case WM8962_INTERRUPT_STATUS_2_MASK: + case WM8962_INTERRUPT_CONTROL: + case WM8962_IRQ_DEBOUNCE: + case WM8962_MICINT_SOURCE_POL: + case WM8962_DSP2_POWER_MANAGEMENT: + case WM8962_DSP2_EXECCONTROL: + case WM8962_DSP2_INSTRUCTION_RAM_0: + case WM8962_DSP2_ADDRESS_RAM_2: + case WM8962_DSP2_ADDRESS_RAM_1: + case WM8962_DSP2_ADDRESS_RAM_0: + case WM8962_DSP2_DATA1_RAM_1: + case WM8962_DSP2_DATA1_RAM_0: + case WM8962_DSP2_DATA2_RAM_1: + case WM8962_DSP2_DATA2_RAM_0: + case WM8962_DSP2_DATA3_RAM_1: + case WM8962_DSP2_DATA3_RAM_0: + case WM8962_DSP2_COEFF_RAM_0: + case WM8962_RETUNEADC_SHARED_COEFF_1: + case WM8962_RETUNEADC_SHARED_COEFF_0: + case WM8962_RETUNEDAC_SHARED_COEFF_1: + case WM8962_RETUNEDAC_SHARED_COEFF_0: + case WM8962_SOUNDSTAGE_ENABLES_1: + case WM8962_SOUNDSTAGE_ENABLES_0: + case WM8962_HDBASS_AI_1: + case WM8962_HDBASS_AI_0: + case WM8962_HDBASS_AR_1: + case WM8962_HDBASS_AR_0: + case WM8962_HDBASS_B_1: + case WM8962_HDBASS_B_0: + case WM8962_HDBASS_K_1: + case WM8962_HDBASS_K_0: + case WM8962_HDBASS_N1_1: + case WM8962_HDBASS_N1_0: + case WM8962_HDBASS_N2_1: + case WM8962_HDBASS_N2_0: + case WM8962_HDBASS_N3_1: + case WM8962_HDBASS_N3_0: + case WM8962_HDBASS_N4_1: + case WM8962_HDBASS_N4_0: + case WM8962_HDBASS_N5_1: + case WM8962_HDBASS_N5_0: + case WM8962_HDBASS_X1_1: + case WM8962_HDBASS_X1_0: + case WM8962_HDBASS_X2_1: + case WM8962_HDBASS_X2_0: + case WM8962_HDBASS_X3_1: + case WM8962_HDBASS_X3_0: + case WM8962_HDBASS_ATK_1: + case WM8962_HDBASS_ATK_0: + case WM8962_HDBASS_DCY_1: + case WM8962_HDBASS_DCY_0: + case WM8962_HDBASS_PG_1: + case WM8962_HDBASS_PG_0: + case WM8962_HPF_C_1: + case WM8962_HPF_C_0: + case WM8962_ADCL_RETUNE_C1_1: + case WM8962_ADCL_RETUNE_C1_0: + case WM8962_ADCL_RETUNE_C2_1: + case WM8962_ADCL_RETUNE_C2_0: + case WM8962_ADCL_RETUNE_C3_1: + case WM8962_ADCL_RETUNE_C3_0: + case WM8962_ADCL_RETUNE_C4_1: + case WM8962_ADCL_RETUNE_C4_0: + case WM8962_ADCL_RETUNE_C5_1: + case WM8962_ADCL_RETUNE_C5_0: + case WM8962_ADCL_RETUNE_C6_1: + case WM8962_ADCL_RETUNE_C6_0: + case WM8962_ADCL_RETUNE_C7_1: + case WM8962_ADCL_RETUNE_C7_0: + case WM8962_ADCL_RETUNE_C8_1: + case WM8962_ADCL_RETUNE_C8_0: + case WM8962_ADCL_RETUNE_C9_1: + case WM8962_ADCL_RETUNE_C9_0: + case WM8962_ADCL_RETUNE_C10_1: + case WM8962_ADCL_RETUNE_C10_0: + case WM8962_ADCL_RETUNE_C11_1: + case WM8962_ADCL_RETUNE_C11_0: + case WM8962_ADCL_RETUNE_C12_1: + case WM8962_ADCL_RETUNE_C12_0: + case WM8962_ADCL_RETUNE_C13_1: + case WM8962_ADCL_RETUNE_C13_0: + case WM8962_ADCL_RETUNE_C14_1: + case WM8962_ADCL_RETUNE_C14_0: + case WM8962_ADCL_RETUNE_C15_1: + case WM8962_ADCL_RETUNE_C15_0: + case WM8962_ADCL_RETUNE_C16_1: + case WM8962_ADCL_RETUNE_C16_0: + case WM8962_ADCL_RETUNE_C17_1: + case WM8962_ADCL_RETUNE_C17_0: + case WM8962_ADCL_RETUNE_C18_1: + case WM8962_ADCL_RETUNE_C18_0: + case WM8962_ADCL_RETUNE_C19_1: + case WM8962_ADCL_RETUNE_C19_0: + case WM8962_ADCL_RETUNE_C20_1: + case WM8962_ADCL_RETUNE_C20_0: + case WM8962_ADCL_RETUNE_C21_1: + case WM8962_ADCL_RETUNE_C21_0: + case WM8962_ADCL_RETUNE_C22_1: + case WM8962_ADCL_RETUNE_C22_0: + case WM8962_ADCL_RETUNE_C23_1: + case WM8962_ADCL_RETUNE_C23_0: + case WM8962_ADCL_RETUNE_C24_1: + case WM8962_ADCL_RETUNE_C24_0: + case WM8962_ADCL_RETUNE_C25_1: + case WM8962_ADCL_RETUNE_C25_0: + case WM8962_ADCL_RETUNE_C26_1: + case WM8962_ADCL_RETUNE_C26_0: + case WM8962_ADCL_RETUNE_C27_1: + case WM8962_ADCL_RETUNE_C27_0: + case WM8962_ADCL_RETUNE_C28_1: + case WM8962_ADCL_RETUNE_C28_0: + case WM8962_ADCL_RETUNE_C29_1: + case WM8962_ADCL_RETUNE_C29_0: + case WM8962_ADCL_RETUNE_C30_1: + case WM8962_ADCL_RETUNE_C30_0: + case WM8962_ADCL_RETUNE_C31_1: + case WM8962_ADCL_RETUNE_C31_0: + case WM8962_ADCL_RETUNE_C32_1: + case WM8962_ADCL_RETUNE_C32_0: + case WM8962_RETUNEADC_PG2_1: + case WM8962_RETUNEADC_PG2_0: + case WM8962_RETUNEADC_PG_1: + case WM8962_RETUNEADC_PG_0: + case WM8962_ADCR_RETUNE_C1_1: + case WM8962_ADCR_RETUNE_C1_0: + case WM8962_ADCR_RETUNE_C2_1: + case WM8962_ADCR_RETUNE_C2_0: + case WM8962_ADCR_RETUNE_C3_1: + case WM8962_ADCR_RETUNE_C3_0: + case WM8962_ADCR_RETUNE_C4_1: + case WM8962_ADCR_RETUNE_C4_0: + case WM8962_ADCR_RETUNE_C5_1: + case WM8962_ADCR_RETUNE_C5_0: + case WM8962_ADCR_RETUNE_C6_1: + case WM8962_ADCR_RETUNE_C6_0: + case WM8962_ADCR_RETUNE_C7_1: + case WM8962_ADCR_RETUNE_C7_0: + case WM8962_ADCR_RETUNE_C8_1: + case WM8962_ADCR_RETUNE_C8_0: + case WM8962_ADCR_RETUNE_C9_1: + case WM8962_ADCR_RETUNE_C9_0: + case WM8962_ADCR_RETUNE_C10_1: + case WM8962_ADCR_RETUNE_C10_0: + case WM8962_ADCR_RETUNE_C11_1: + case WM8962_ADCR_RETUNE_C11_0: + case WM8962_ADCR_RETUNE_C12_1: + case WM8962_ADCR_RETUNE_C12_0: + case WM8962_ADCR_RETUNE_C13_1: + case WM8962_ADCR_RETUNE_C13_0: + case WM8962_ADCR_RETUNE_C14_1: + case WM8962_ADCR_RETUNE_C14_0: + case WM8962_ADCR_RETUNE_C15_1: + case WM8962_ADCR_RETUNE_C15_0: + case WM8962_ADCR_RETUNE_C16_1: + case WM8962_ADCR_RETUNE_C16_0: + case WM8962_ADCR_RETUNE_C17_1: + case WM8962_ADCR_RETUNE_C17_0: + case WM8962_ADCR_RETUNE_C18_1: + case WM8962_ADCR_RETUNE_C18_0: + case WM8962_ADCR_RETUNE_C19_1: + case WM8962_ADCR_RETUNE_C19_0: + case WM8962_ADCR_RETUNE_C20_1: + case WM8962_ADCR_RETUNE_C20_0: + case WM8962_ADCR_RETUNE_C21_1: + case WM8962_ADCR_RETUNE_C21_0: + case WM8962_ADCR_RETUNE_C22_1: + case WM8962_ADCR_RETUNE_C22_0: + case WM8962_ADCR_RETUNE_C23_1: + case WM8962_ADCR_RETUNE_C23_0: + case WM8962_ADCR_RETUNE_C24_1: + case WM8962_ADCR_RETUNE_C24_0: + case WM8962_ADCR_RETUNE_C25_1: + case WM8962_ADCR_RETUNE_C25_0: + case WM8962_ADCR_RETUNE_C26_1: + case WM8962_ADCR_RETUNE_C26_0: + case WM8962_ADCR_RETUNE_C27_1: + case WM8962_ADCR_RETUNE_C27_0: + case WM8962_ADCR_RETUNE_C28_1: + case WM8962_ADCR_RETUNE_C28_0: + case WM8962_ADCR_RETUNE_C29_1: + case WM8962_ADCR_RETUNE_C29_0: + case WM8962_ADCR_RETUNE_C30_1: + case WM8962_ADCR_RETUNE_C30_0: + case WM8962_ADCR_RETUNE_C31_1: + case WM8962_ADCR_RETUNE_C31_0: + case WM8962_ADCR_RETUNE_C32_1: + case WM8962_ADCR_RETUNE_C32_0: + case WM8962_DACL_RETUNE_C1_1: + case WM8962_DACL_RETUNE_C1_0: + case WM8962_DACL_RETUNE_C2_1: + case WM8962_DACL_RETUNE_C2_0: + case WM8962_DACL_RETUNE_C3_1: + case WM8962_DACL_RETUNE_C3_0: + case WM8962_DACL_RETUNE_C4_1: + case WM8962_DACL_RETUNE_C4_0: + case WM8962_DACL_RETUNE_C5_1: + case WM8962_DACL_RETUNE_C5_0: + case WM8962_DACL_RETUNE_C6_1: + case WM8962_DACL_RETUNE_C6_0: + case WM8962_DACL_RETUNE_C7_1: + case WM8962_DACL_RETUNE_C7_0: + case WM8962_DACL_RETUNE_C8_1: + case WM8962_DACL_RETUNE_C8_0: + case WM8962_DACL_RETUNE_C9_1: + case WM8962_DACL_RETUNE_C9_0: + case WM8962_DACL_RETUNE_C10_1: + case WM8962_DACL_RETUNE_C10_0: + case WM8962_DACL_RETUNE_C11_1: + case WM8962_DACL_RETUNE_C11_0: + case WM8962_DACL_RETUNE_C12_1: + case WM8962_DACL_RETUNE_C12_0: + case WM8962_DACL_RETUNE_C13_1: + case WM8962_DACL_RETUNE_C13_0: + case WM8962_DACL_RETUNE_C14_1: + case WM8962_DACL_RETUNE_C14_0: + case WM8962_DACL_RETUNE_C15_1: + case WM8962_DACL_RETUNE_C15_0: + case WM8962_DACL_RETUNE_C16_1: + case WM8962_DACL_RETUNE_C16_0: + case WM8962_DACL_RETUNE_C17_1: + case WM8962_DACL_RETUNE_C17_0: + case WM8962_DACL_RETUNE_C18_1: + case WM8962_DACL_RETUNE_C18_0: + case WM8962_DACL_RETUNE_C19_1: + case WM8962_DACL_RETUNE_C19_0: + case WM8962_DACL_RETUNE_C20_1: + case WM8962_DACL_RETUNE_C20_0: + case WM8962_DACL_RETUNE_C21_1: + case WM8962_DACL_RETUNE_C21_0: + case WM8962_DACL_RETUNE_C22_1: + case WM8962_DACL_RETUNE_C22_0: + case WM8962_DACL_RETUNE_C23_1: + case WM8962_DACL_RETUNE_C23_0: + case WM8962_DACL_RETUNE_C24_1: + case WM8962_DACL_RETUNE_C24_0: + case WM8962_DACL_RETUNE_C25_1: + case WM8962_DACL_RETUNE_C25_0: + case WM8962_DACL_RETUNE_C26_1: + case WM8962_DACL_RETUNE_C26_0: + case WM8962_DACL_RETUNE_C27_1: + case WM8962_DACL_RETUNE_C27_0: + case WM8962_DACL_RETUNE_C28_1: + case WM8962_DACL_RETUNE_C28_0: + case WM8962_DACL_RETUNE_C29_1: + case WM8962_DACL_RETUNE_C29_0: + case WM8962_DACL_RETUNE_C30_1: + case WM8962_DACL_RETUNE_C30_0: + case WM8962_DACL_RETUNE_C31_1: + case WM8962_DACL_RETUNE_C31_0: + case WM8962_DACL_RETUNE_C32_1: + case WM8962_DACL_RETUNE_C32_0: + case WM8962_RETUNEDAC_PG2_1: + case WM8962_RETUNEDAC_PG2_0: + case WM8962_RETUNEDAC_PG_1: + case WM8962_RETUNEDAC_PG_0: + case WM8962_DACR_RETUNE_C1_1: + case WM8962_DACR_RETUNE_C1_0: + case WM8962_DACR_RETUNE_C2_1: + case WM8962_DACR_RETUNE_C2_0: + case WM8962_DACR_RETUNE_C3_1: + case WM8962_DACR_RETUNE_C3_0: + case WM8962_DACR_RETUNE_C4_1: + case WM8962_DACR_RETUNE_C4_0: + case WM8962_DACR_RETUNE_C5_1: + case WM8962_DACR_RETUNE_C5_0: + case WM8962_DACR_RETUNE_C6_1: + case WM8962_DACR_RETUNE_C6_0: + case WM8962_DACR_RETUNE_C7_1: + case WM8962_DACR_RETUNE_C7_0: + case WM8962_DACR_RETUNE_C8_1: + case WM8962_DACR_RETUNE_C8_0: + case WM8962_DACR_RETUNE_C9_1: + case WM8962_DACR_RETUNE_C9_0: + case WM8962_DACR_RETUNE_C10_1: + case WM8962_DACR_RETUNE_C10_0: + case WM8962_DACR_RETUNE_C11_1: + case WM8962_DACR_RETUNE_C11_0: + case WM8962_DACR_RETUNE_C12_1: + case WM8962_DACR_RETUNE_C12_0: + case WM8962_DACR_RETUNE_C13_1: + case WM8962_DACR_RETUNE_C13_0: + case WM8962_DACR_RETUNE_C14_1: + case WM8962_DACR_RETUNE_C14_0: + case WM8962_DACR_RETUNE_C15_1: + case WM8962_DACR_RETUNE_C15_0: + case WM8962_DACR_RETUNE_C16_1: + case WM8962_DACR_RETUNE_C16_0: + case WM8962_DACR_RETUNE_C17_1: + case WM8962_DACR_RETUNE_C17_0: + case WM8962_DACR_RETUNE_C18_1: + case WM8962_DACR_RETUNE_C18_0: + case WM8962_DACR_RETUNE_C19_1: + case WM8962_DACR_RETUNE_C19_0: + case WM8962_DACR_RETUNE_C20_1: + case WM8962_DACR_RETUNE_C20_0: + case WM8962_DACR_RETUNE_C21_1: + case WM8962_DACR_RETUNE_C21_0: + case WM8962_DACR_RETUNE_C22_1: + case WM8962_DACR_RETUNE_C22_0: + case WM8962_DACR_RETUNE_C23_1: + case WM8962_DACR_RETUNE_C23_0: + case WM8962_DACR_RETUNE_C24_1: + case WM8962_DACR_RETUNE_C24_0: + case WM8962_DACR_RETUNE_C25_1: + case WM8962_DACR_RETUNE_C25_0: + case WM8962_DACR_RETUNE_C26_1: + case WM8962_DACR_RETUNE_C26_0: + case WM8962_DACR_RETUNE_C27_1: + case WM8962_DACR_RETUNE_C27_0: + case WM8962_DACR_RETUNE_C28_1: + case WM8962_DACR_RETUNE_C28_0: + case WM8962_DACR_RETUNE_C29_1: + case WM8962_DACR_RETUNE_C29_0: + case WM8962_DACR_RETUNE_C30_1: + case WM8962_DACR_RETUNE_C30_0: + case WM8962_DACR_RETUNE_C31_1: + case WM8962_DACR_RETUNE_C31_0: + case WM8962_DACR_RETUNE_C32_1: + case WM8962_DACR_RETUNE_C32_0: + case WM8962_VSS_XHD2_1: + case WM8962_VSS_XHD2_0: + case WM8962_VSS_XHD3_1: + case WM8962_VSS_XHD3_0: + case WM8962_VSS_XHN1_1: + case WM8962_VSS_XHN1_0: + case WM8962_VSS_XHN2_1: + case WM8962_VSS_XHN2_0: + case WM8962_VSS_XHN3_1: + case WM8962_VSS_XHN3_0: + case WM8962_VSS_XLA_1: + case WM8962_VSS_XLA_0: + case WM8962_VSS_XLB_1: + case WM8962_VSS_XLB_0: + case WM8962_VSS_XLG_1: + case WM8962_VSS_XLG_0: + case WM8962_VSS_PG2_1: + case WM8962_VSS_PG2_0: + case WM8962_VSS_PG_1: + case WM8962_VSS_PG_0: + case WM8962_VSS_XTD1_1: + case WM8962_VSS_XTD1_0: + case WM8962_VSS_XTD2_1: + case WM8962_VSS_XTD2_0: + case WM8962_VSS_XTD3_1: + case WM8962_VSS_XTD3_0: + case WM8962_VSS_XTD4_1: + case WM8962_VSS_XTD4_0: + case WM8962_VSS_XTD5_1: + case WM8962_VSS_XTD5_0: + case WM8962_VSS_XTD6_1: + case WM8962_VSS_XTD6_0: + case WM8962_VSS_XTD7_1: + case WM8962_VSS_XTD7_0: + case WM8962_VSS_XTD8_1: + case WM8962_VSS_XTD8_0: + case WM8962_VSS_XTD9_1: + case WM8962_VSS_XTD9_0: + case WM8962_VSS_XTD10_1: + case WM8962_VSS_XTD10_0: + case WM8962_VSS_XTD11_1: + case WM8962_VSS_XTD11_0: + case WM8962_VSS_XTD12_1: + case WM8962_VSS_XTD12_0: + case WM8962_VSS_XTD13_1: + case WM8962_VSS_XTD13_0: + case WM8962_VSS_XTD14_1: + case WM8962_VSS_XTD14_0: + case WM8962_VSS_XTD15_1: + case WM8962_VSS_XTD15_0: + case WM8962_VSS_XTD16_1: + case WM8962_VSS_XTD16_0: + case WM8962_VSS_XTD17_1: + case WM8962_VSS_XTD17_0: + case WM8962_VSS_XTD18_1: + case WM8962_VSS_XTD18_0: + case WM8962_VSS_XTD19_1: + case WM8962_VSS_XTD19_0: + case WM8962_VSS_XTD20_1: + case WM8962_VSS_XTD20_0: + case WM8962_VSS_XTD21_1: + case WM8962_VSS_XTD21_0: + case WM8962_VSS_XTD22_1: + case WM8962_VSS_XTD22_0: + case WM8962_VSS_XTD23_1: + case WM8962_VSS_XTD23_0: + case WM8962_VSS_XTD24_1: + case WM8962_VSS_XTD24_0: + case WM8962_VSS_XTD25_1: + case WM8962_VSS_XTD25_0: + case WM8962_VSS_XTD26_1: + case WM8962_VSS_XTD26_0: + case WM8962_VSS_XTD27_1: + case WM8962_VSS_XTD27_0: + case WM8962_VSS_XTD28_1: + case WM8962_VSS_XTD28_0: + case WM8962_VSS_XTD29_1: + case WM8962_VSS_XTD29_0: + case WM8962_VSS_XTD30_1: + case WM8962_VSS_XTD30_0: + case WM8962_VSS_XTD31_1: + case WM8962_VSS_XTD31_0: + case WM8962_VSS_XTD32_1: + case WM8962_VSS_XTD32_0: + case WM8962_VSS_XTS1_1: + case WM8962_VSS_XTS1_0: + case WM8962_VSS_XTS2_1: + case WM8962_VSS_XTS2_0: + case WM8962_VSS_XTS3_1: + case WM8962_VSS_XTS3_0: + case WM8962_VSS_XTS4_1: + case WM8962_VSS_XTS4_0: + case WM8962_VSS_XTS5_1: + case WM8962_VSS_XTS5_0: + case WM8962_VSS_XTS6_1: + case WM8962_VSS_XTS6_0: + case WM8962_VSS_XTS7_1: + case WM8962_VSS_XTS7_0: + case WM8962_VSS_XTS8_1: + case WM8962_VSS_XTS8_0: + case WM8962_VSS_XTS9_1: + case WM8962_VSS_XTS9_0: + case WM8962_VSS_XTS10_1: + case WM8962_VSS_XTS10_0: + case WM8962_VSS_XTS11_1: + case WM8962_VSS_XTS11_0: + case WM8962_VSS_XTS12_1: + case WM8962_VSS_XTS12_0: + case WM8962_VSS_XTS13_1: + case WM8962_VSS_XTS13_0: + case WM8962_VSS_XTS14_1: + case WM8962_VSS_XTS14_0: + case WM8962_VSS_XTS15_1: + case WM8962_VSS_XTS15_0: + case WM8962_VSS_XTS16_1: + case WM8962_VSS_XTS16_0: + case WM8962_VSS_XTS17_1: + case WM8962_VSS_XTS17_0: + case WM8962_VSS_XTS18_1: + case WM8962_VSS_XTS18_0: + case WM8962_VSS_XTS19_1: + case WM8962_VSS_XTS19_0: + case WM8962_VSS_XTS20_1: + case WM8962_VSS_XTS20_0: + case WM8962_VSS_XTS21_1: + case WM8962_VSS_XTS21_0: + case WM8962_VSS_XTS22_1: + case WM8962_VSS_XTS22_0: + case WM8962_VSS_XTS23_1: + case WM8962_VSS_XTS23_0: + case WM8962_VSS_XTS24_1: + case WM8962_VSS_XTS24_0: + case WM8962_VSS_XTS25_1: + case WM8962_VSS_XTS25_0: + case WM8962_VSS_XTS26_1: + case WM8962_VSS_XTS26_0: + case WM8962_VSS_XTS27_1: + case WM8962_VSS_XTS27_0: + case WM8962_VSS_XTS28_1: + case WM8962_VSS_XTS28_0: + case WM8962_VSS_XTS29_1: + case WM8962_VSS_XTS29_0: + case WM8962_VSS_XTS30_1: + case WM8962_VSS_XTS30_0: + case WM8962_VSS_XTS31_1: + case WM8962_VSS_XTS31_0: + case WM8962_VSS_XTS32_1: + case WM8962_VSS_XTS32_0: + return true; + default: + return false; + } +} + +static int wm8962_reset(struct wm8962_priv *wm8962) +{ + int ret; + + ret = regmap_write(wm8962->regmap, WM8962_SOFTWARE_RESET, 0x6243); + if (ret != 0) + return ret; + + return regmap_write(wm8962->regmap, WM8962_PLL_SOFTWARE_RESET, 0); +} + +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -2325, 75, 0); +static const DECLARE_TLV_DB_SCALE(mixin_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_RANGE(mixinpga_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 600, 0), + 2, 2, TLV_DB_SCALE_ITEM(1300, 1300, 0), + 3, 4, TLV_DB_SCALE_ITEM(1800, 200, 0), + 5, 5, TLV_DB_SCALE_ITEM(2400, 0, 0), + 6, 7, TLV_DB_SCALE_ITEM(2700, 300, 0) +); +static const DECLARE_TLV_DB_SCALE(beep_tlv, -9600, 600, 1); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_SCALE(inmix_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(hp_tlv, -700, 100, 0); +static const DECLARE_TLV_DB_RANGE(classd_tlv, + 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), + 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); + +static int wm8962_dsp2_write_config(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + + return regcache_sync_region(wm8962->regmap, + WM8962_HDBASS_AI_1, WM8962_MAX_REGISTER); +} + +static int wm8962_dsp2_set_enable(struct snd_soc_component *component, u16 val) +{ + u16 adcl = snd_soc_component_read32(component, WM8962_LEFT_ADC_VOLUME); + u16 adcr = snd_soc_component_read32(component, WM8962_RIGHT_ADC_VOLUME); + u16 dac = snd_soc_component_read32(component, WM8962_ADC_DAC_CONTROL_1); + + /* Mute the ADCs and DACs */ + snd_soc_component_write(component, WM8962_LEFT_ADC_VOLUME, 0); + snd_soc_component_write(component, WM8962_RIGHT_ADC_VOLUME, WM8962_ADC_VU); + snd_soc_component_update_bits(component, WM8962_ADC_DAC_CONTROL_1, + WM8962_DAC_MUTE, WM8962_DAC_MUTE); + + snd_soc_component_write(component, WM8962_SOUNDSTAGE_ENABLES_0, val); + + /* Restore the ADCs and DACs */ + snd_soc_component_write(component, WM8962_LEFT_ADC_VOLUME, adcl); + snd_soc_component_write(component, WM8962_RIGHT_ADC_VOLUME, adcr); + snd_soc_component_update_bits(component, WM8962_ADC_DAC_CONTROL_1, + WM8962_DAC_MUTE, dac); + + return 0; +} + +static int wm8962_dsp2_start(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + + wm8962_dsp2_write_config(component); + + snd_soc_component_write(component, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_RUNR); + + wm8962_dsp2_set_enable(component, wm8962->dsp2_ena); + + return 0; +} + +static int wm8962_dsp2_stop(struct snd_soc_component *component) +{ + wm8962_dsp2_set_enable(component, 0); + + snd_soc_component_write(component, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_STOP); + + return 0; +} + +#define WM8962_DSP2_ENABLE(xname, xshift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = wm8962_dsp2_ena_info, \ + .get = wm8962_dsp2_ena_get, .put = wm8962_dsp2_ena_put, \ + .private_value = xshift } + +static int wm8962_dsp2_ena_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 wm8962_dsp2_ena_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int shift = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = !!(wm8962->dsp2_ena & 1 << shift); + + return 0; +} + +static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int shift = kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int old = wm8962->dsp2_ena; + int ret = 0; + int dsp2_running = snd_soc_component_read32(component, WM8962_DSP2_POWER_MANAGEMENT) & + WM8962_DSP2_ENA; + + mutex_lock(&wm8962->dsp2_ena_lock); + + if (ucontrol->value.integer.value[0]) + wm8962->dsp2_ena |= 1 << shift; + else + wm8962->dsp2_ena &= ~(1 << shift); + + if (wm8962->dsp2_ena == old) + goto out; + + ret = 1; + + if (dsp2_running) { + if (wm8962->dsp2_ena) + wm8962_dsp2_set_enable(component, wm8962->dsp2_ena); + else + wm8962_dsp2_stop(component); + } + +out: + mutex_unlock(&wm8962->dsp2_ena_lock); + + return ret; +} + +/* The VU bits for the headphones are in a different register to the mute + * bits and only take effect on the PGA if it is actually powered. + */ +static int wm8962_put_hp_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int ret; + + /* Apply the update (if any) */ + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret == 0) + return 0; + + /* If the left PGA is enabled hit that VU bit... */ + ret = snd_soc_component_read32(component, WM8962_PWR_MGMT_2); + if (ret & WM8962_HPOUTL_PGA_ENA) { + snd_soc_component_write(component, WM8962_HPOUTL_VOLUME, + snd_soc_component_read32(component, WM8962_HPOUTL_VOLUME)); + return 1; + } + + /* ...otherwise the right. The VU is stereo. */ + if (ret & WM8962_HPOUTR_PGA_ENA) + snd_soc_component_write(component, WM8962_HPOUTR_VOLUME, + snd_soc_component_read32(component, WM8962_HPOUTR_VOLUME)); + + return 1; +} + +/* The VU bits for the speakers are in a different register to the mute + * bits and only take effect on the PGA if it is actually powered. + */ +static int wm8962_put_spk_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int ret; + + /* Apply the update (if any) */ + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret == 0) + return 0; + + /* If the left PGA is enabled hit that VU bit... */ + ret = snd_soc_component_read32(component, WM8962_PWR_MGMT_2); + if (ret & WM8962_SPKOUTL_PGA_ENA) { + snd_soc_component_write(component, WM8962_SPKOUTL_VOLUME, + snd_soc_component_read32(component, WM8962_SPKOUTL_VOLUME)); + return 1; + } + + /* ...otherwise the right. The VU is stereo. */ + if (ret & WM8962_SPKOUTR_PGA_ENA) + snd_soc_component_write(component, WM8962_SPKOUTR_VOLUME, + snd_soc_component_read32(component, WM8962_SPKOUTR_VOLUME)); + + return 1; +} + +static const char *cap_hpf_mode_text[] = { + "Hi-fi", "Application" +}; + +static SOC_ENUM_SINGLE_DECL(cap_hpf_mode, + WM8962_ADC_DAC_CONTROL_2, 10, cap_hpf_mode_text); + + +static const char *cap_lhpf_mode_text[] = { + "LPF", "HPF" +}; + +static SOC_ENUM_SINGLE_DECL(cap_lhpf_mode, + WM8962_LHPF1, 1, cap_lhpf_mode_text); + +static const struct snd_kcontrol_new wm8962_snd_controls[] = { +SOC_DOUBLE("Input Mixer Switch", WM8962_INPUT_MIXER_CONTROL_1, 3, 2, 1, 1), + +SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 6, 7, 0, + mixin_tlv), +SOC_SINGLE_TLV("MIXINL PGA Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 3, 7, 0, + mixinpga_tlv), +SOC_SINGLE_TLV("MIXINL IN3L Volume", WM8962_LEFT_INPUT_MIXER_VOLUME, 0, 7, 0, + mixin_tlv), + +SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 6, 7, 0, + mixin_tlv), +SOC_SINGLE_TLV("MIXINR PGA Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 3, 7, 0, + mixinpga_tlv), +SOC_SINGLE_TLV("MIXINR IN3R Volume", WM8962_RIGHT_INPUT_MIXER_VOLUME, 0, 7, 0, + mixin_tlv), + +SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8962_LEFT_ADC_VOLUME, + WM8962_RIGHT_ADC_VOLUME, 1, 127, 0, digital_tlv), +SOC_DOUBLE_R_TLV("Capture Volume", WM8962_LEFT_INPUT_VOLUME, + WM8962_RIGHT_INPUT_VOLUME, 0, 63, 0, inpga_tlv), +SOC_DOUBLE_R("Capture Switch", WM8962_LEFT_INPUT_VOLUME, + WM8962_RIGHT_INPUT_VOLUME, 7, 1, 1), +SOC_DOUBLE_R("Capture ZC Switch", WM8962_LEFT_INPUT_VOLUME, + WM8962_RIGHT_INPUT_VOLUME, 6, 1, 1), +SOC_SINGLE("Capture HPF Switch", WM8962_ADC_DAC_CONTROL_1, 0, 1, 1), +SOC_ENUM("Capture HPF Mode", cap_hpf_mode), +SOC_SINGLE("Capture HPF Cutoff", WM8962_ADC_DAC_CONTROL_2, 7, 7, 0), +SOC_SINGLE("Capture LHPF Switch", WM8962_LHPF1, 0, 1, 0), +SOC_ENUM("Capture LHPF Mode", cap_lhpf_mode), + +SOC_DOUBLE_R_TLV("Sidetone Volume", WM8962_DAC_DSP_MIXING_1, + WM8962_DAC_DSP_MIXING_2, 4, 12, 0, st_tlv), + +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8962_LEFT_DAC_VOLUME, + WM8962_RIGHT_DAC_VOLUME, 1, 127, 0, digital_tlv), +SOC_SINGLE("DAC High Performance Switch", WM8962_ADC_DAC_CONTROL_2, 0, 1, 0), +SOC_SINGLE("DAC L/R Swap Switch", WM8962_AUDIO_INTERFACE_0, 5, 1, 0), +SOC_SINGLE("ADC L/R Swap Switch", WM8962_AUDIO_INTERFACE_0, 8, 1, 0), + +SOC_SINGLE("ADC High Performance Switch", WM8962_ADDITIONAL_CONTROL_1, + 5, 1, 0), + +SOC_SINGLE_TLV("Beep Volume", WM8962_BEEP_GENERATOR_1, 4, 15, 0, beep_tlv), + +SOC_DOUBLE_R_TLV("Headphone Volume", WM8962_HPOUTL_VOLUME, + WM8962_HPOUTR_VOLUME, 0, 127, 0, out_tlv), +SOC_DOUBLE_EXT("Headphone Switch", WM8962_PWR_MGMT_2, 1, 0, 1, 1, + snd_soc_get_volsw, wm8962_put_hp_sw), +SOC_DOUBLE_R("Headphone ZC Switch", WM8962_HPOUTL_VOLUME, WM8962_HPOUTR_VOLUME, + 7, 1, 0), +SOC_DOUBLE_TLV("Headphone Aux Volume", WM8962_ANALOGUE_HP_2, 3, 6, 7, 0, + hp_tlv), + +SOC_DOUBLE_R("Headphone Mixer Switch", WM8962_HEADPHONE_MIXER_3, + WM8962_HEADPHONE_MIXER_4, 8, 1, 1), + +SOC_SINGLE_TLV("HPMIXL IN4L Volume", WM8962_HEADPHONE_MIXER_3, + 3, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("HPMIXL IN4R Volume", WM8962_HEADPHONE_MIXER_3, + 0, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("HPMIXL MIXINL Volume", WM8962_HEADPHONE_MIXER_3, + 7, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("HPMIXL MIXINR Volume", WM8962_HEADPHONE_MIXER_3, + 6, 1, 1, inmix_tlv), + +SOC_SINGLE_TLV("HPMIXR IN4L Volume", WM8962_HEADPHONE_MIXER_4, + 3, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("HPMIXR IN4R Volume", WM8962_HEADPHONE_MIXER_4, + 0, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("HPMIXR MIXINL Volume", WM8962_HEADPHONE_MIXER_4, + 7, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("HPMIXR MIXINR Volume", WM8962_HEADPHONE_MIXER_4, + 6, 1, 1, inmix_tlv), + +SOC_SINGLE_TLV("Speaker Boost Volume", WM8962_CLASS_D_CONTROL_2, 0, 7, 0, + classd_tlv), + +SOC_SINGLE("EQ Switch", WM8962_EQ1, WM8962_EQ_ENA_SHIFT, 1, 0), +SOC_DOUBLE_R_TLV("EQ1 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B1_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ2 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B2_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ3 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B3_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23, + WM8962_EQL_B4_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23, + WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv), +SND_SOC_BYTES("EQL Coefficients", WM8962_EQ4, 18), +SND_SOC_BYTES("EQR Coefficients", WM8962_EQ24, 18), + + +SOC_SINGLE("3D Switch", WM8962_THREED1, 0, 1, 0), +SND_SOC_BYTES_MASK("3D Coefficients", WM8962_THREED1, 4, WM8962_THREED_ENA), + +SOC_SINGLE("DF1 Switch", WM8962_DF1, 0, 1, 0), +SND_SOC_BYTES_MASK("DF1 Coefficients", WM8962_DF1, 7, WM8962_DF1_ENA), + +SOC_SINGLE("DRC Switch", WM8962_DRC_1, 0, 1, 0), +SND_SOC_BYTES_MASK("DRC Coefficients", WM8962_DRC_1, 5, WM8962_DRC_ENA), + +WM8962_DSP2_ENABLE("VSS Switch", WM8962_VSS_ENA_SHIFT), +SND_SOC_BYTES("VSS Coefficients", WM8962_VSS_XHD2_1, 148), +WM8962_DSP2_ENABLE("HPF1 Switch", WM8962_HPF1_ENA_SHIFT), +WM8962_DSP2_ENABLE("HPF2 Switch", WM8962_HPF2_ENA_SHIFT), +SND_SOC_BYTES("HPF Coefficients", WM8962_LHPF2, 1), +WM8962_DSP2_ENABLE("HD Bass Switch", WM8962_HDBASS_ENA_SHIFT), +SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30), + +SOC_DOUBLE("ALC Switch", WM8962_ALC1, WM8962_ALCL_ENA_SHIFT, + WM8962_ALCR_ENA_SHIFT, 1, 0), +SND_SOC_BYTES_MASK("ALC Coefficients", WM8962_ALC1, 4, + WM8962_ALCL_ENA_MASK | WM8962_ALCR_ENA_MASK), +}; + +static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { +SOC_SINGLE_TLV("Speaker Volume", WM8962_SPKOUTL_VOLUME, 0, 127, 0, out_tlv), +SOC_SINGLE_EXT("Speaker Switch", WM8962_CLASS_D_CONTROL_1, 1, 1, 1, + snd_soc_get_volsw, wm8962_put_spk_sw), +SOC_SINGLE("Speaker ZC Switch", WM8962_SPKOUTL_VOLUME, 7, 1, 0), + +SOC_SINGLE("Speaker Mixer Switch", WM8962_SPEAKER_MIXER_3, 8, 1, 1), +SOC_SINGLE_TLV("Speaker Mixer IN4L Volume", WM8962_SPEAKER_MIXER_3, + 3, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("Speaker Mixer IN4R Volume", WM8962_SPEAKER_MIXER_3, + 0, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("Speaker Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_3, + 7, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("Speaker Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_3, + 6, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("Speaker Mixer DACL Volume", WM8962_SPEAKER_MIXER_5, + 7, 1, 0, inmix_tlv), +SOC_SINGLE_TLV("Speaker Mixer DACR Volume", WM8962_SPEAKER_MIXER_5, + 6, 1, 0, inmix_tlv), +}; + +static const struct snd_kcontrol_new wm8962_spk_stereo_controls[] = { +SOC_DOUBLE_R_TLV("Speaker Volume", WM8962_SPKOUTL_VOLUME, + WM8962_SPKOUTR_VOLUME, 0, 127, 0, out_tlv), +SOC_DOUBLE_EXT("Speaker Switch", WM8962_CLASS_D_CONTROL_1, 1, 0, 1, 1, + snd_soc_get_volsw, wm8962_put_spk_sw), +SOC_DOUBLE_R("Speaker ZC Switch", WM8962_SPKOUTL_VOLUME, WM8962_SPKOUTR_VOLUME, + 7, 1, 0), + +SOC_DOUBLE_R("Speaker Mixer Switch", WM8962_SPEAKER_MIXER_3, + WM8962_SPEAKER_MIXER_4, 8, 1, 1), + +SOC_SINGLE_TLV("SPKOUTL Mixer IN4L Volume", WM8962_SPEAKER_MIXER_3, + 3, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("SPKOUTL Mixer IN4R Volume", WM8962_SPEAKER_MIXER_3, + 0, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("SPKOUTL Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_3, + 7, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("SPKOUTL Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_3, + 6, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("SPKOUTL Mixer DACL Volume", WM8962_SPEAKER_MIXER_5, + 7, 1, 0, inmix_tlv), +SOC_SINGLE_TLV("SPKOUTL Mixer DACR Volume", WM8962_SPEAKER_MIXER_5, + 6, 1, 0, inmix_tlv), + +SOC_SINGLE_TLV("SPKOUTR Mixer IN4L Volume", WM8962_SPEAKER_MIXER_4, + 3, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("SPKOUTR Mixer IN4R Volume", WM8962_SPEAKER_MIXER_4, + 0, 7, 0, bypass_tlv), +SOC_SINGLE_TLV("SPKOUTR Mixer MIXINL Volume", WM8962_SPEAKER_MIXER_4, + 7, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("SPKOUTR Mixer MIXINR Volume", WM8962_SPEAKER_MIXER_4, + 6, 1, 1, inmix_tlv), +SOC_SINGLE_TLV("SPKOUTR Mixer DACL Volume", WM8962_SPEAKER_MIXER_5, + 5, 1, 0, inmix_tlv), +SOC_SINGLE_TLV("SPKOUTR Mixer DACR Volume", WM8962_SPEAKER_MIXER_5, + 4, 1, 0, inmix_tlv), +}; + +static int cp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(5); + break; + + default: + WARN(1, "Invalid event %d\n", event); + return -EINVAL; + } + + return 0; +} + +static int hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int timeout; + int reg; + int expected = (WM8962_DCS_STARTUP_DONE_HP1L | + WM8962_DCS_STARTUP_DONE_HP1R); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, WM8962_ANALOGUE_HP_0, + WM8962_HP1L_ENA | WM8962_HP1R_ENA, + WM8962_HP1L_ENA | WM8962_HP1R_ENA); + udelay(20); + + snd_soc_component_update_bits(component, WM8962_ANALOGUE_HP_0, + WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY, + WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY); + + /* Start the DC servo */ + snd_soc_component_update_bits(component, WM8962_DC_SERVO_1, + WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA | + WM8962_HP1L_DCS_STARTUP | + WM8962_HP1R_DCS_STARTUP, + WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA | + WM8962_HP1L_DCS_STARTUP | + WM8962_HP1R_DCS_STARTUP); + + /* Wait for it to complete, should be well under 100ms */ + timeout = 0; + do { + msleep(1); + reg = snd_soc_component_read32(component, WM8962_DC_SERVO_6); + if (reg < 0) { + dev_err(component->dev, + "Failed to read DCS status: %d\n", + reg); + continue; + } + dev_dbg(component->dev, "DCS status: %x\n", reg); + } while (++timeout < 200 && (reg & expected) != expected); + + if ((reg & expected) != expected) + dev_err(component->dev, "DC servo timed out\n"); + else + dev_dbg(component->dev, "DC servo complete after %dms\n", + timeout); + + snd_soc_component_update_bits(component, WM8962_ANALOGUE_HP_0, + WM8962_HP1L_ENA_OUTP | + WM8962_HP1R_ENA_OUTP, + WM8962_HP1L_ENA_OUTP | + WM8962_HP1R_ENA_OUTP); + udelay(20); + + snd_soc_component_update_bits(component, WM8962_ANALOGUE_HP_0, + WM8962_HP1L_RMV_SHORT | + WM8962_HP1R_RMV_SHORT, + WM8962_HP1L_RMV_SHORT | + WM8962_HP1R_RMV_SHORT); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, WM8962_ANALOGUE_HP_0, + WM8962_HP1L_RMV_SHORT | + WM8962_HP1R_RMV_SHORT, 0); + + udelay(20); + + snd_soc_component_update_bits(component, WM8962_DC_SERVO_1, + WM8962_HP1L_DCS_ENA | WM8962_HP1R_DCS_ENA | + WM8962_HP1L_DCS_STARTUP | + WM8962_HP1R_DCS_STARTUP, + 0); + + snd_soc_component_update_bits(component, WM8962_ANALOGUE_HP_0, + WM8962_HP1L_ENA | WM8962_HP1R_ENA | + WM8962_HP1L_ENA_DLY | WM8962_HP1R_ENA_DLY | + WM8962_HP1L_ENA_OUTP | + WM8962_HP1R_ENA_OUTP, 0); + + break; + + default: + WARN(1, "Invalid event %d\n", event); + return -EINVAL; + + } + + return 0; +} + +/* VU bits for the output PGAs only take effect while the PGA is powered */ +static int out_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int reg; + + switch (w->shift) { + case WM8962_HPOUTR_PGA_ENA_SHIFT: + reg = WM8962_HPOUTR_VOLUME; + break; + case WM8962_HPOUTL_PGA_ENA_SHIFT: + reg = WM8962_HPOUTL_VOLUME; + break; + case WM8962_SPKOUTR_PGA_ENA_SHIFT: + reg = WM8962_SPKOUTR_VOLUME; + break; + case WM8962_SPKOUTL_PGA_ENA_SHIFT: + reg = WM8962_SPKOUTL_VOLUME; + break; + default: + WARN(1, "Invalid shift %d\n", w->shift); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + return snd_soc_component_write(component, reg, snd_soc_component_read32(component, reg)); + default: + WARN(1, "Invalid event %d\n", event); + return -EINVAL; + } +} + +static int dsp2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (wm8962->dsp2_ena) + wm8962_dsp2_start(component); + break; + + case SND_SOC_DAPM_PRE_PMD: + if (wm8962->dsp2_ena) + wm8962_dsp2_stop(component); + break; + + default: + WARN(1, "Invalid event %d\n", event); + return -EINVAL; + } + + return 0; +} + +static const char *st_text[] = { "None", "Left", "Right" }; + +static SOC_ENUM_SINGLE_DECL(str_enum, + WM8962_DAC_DSP_MIXING_1, 2, st_text); + +static const struct snd_kcontrol_new str_mux = + SOC_DAPM_ENUM("Right Sidetone", str_enum); + +static SOC_ENUM_SINGLE_DECL(stl_enum, + WM8962_DAC_DSP_MIXING_2, 2, st_text); + +static const struct snd_kcontrol_new stl_mux = + SOC_DAPM_ENUM("Left Sidetone", stl_enum); + +static const char *outmux_text[] = { "DAC", "Mixer" }; + +static SOC_ENUM_SINGLE_DECL(spkoutr_enum, + WM8962_SPEAKER_MIXER_2, 7, outmux_text); + +static const struct snd_kcontrol_new spkoutr_mux = + SOC_DAPM_ENUM("SPKOUTR Mux", spkoutr_enum); + +static SOC_ENUM_SINGLE_DECL(spkoutl_enum, + WM8962_SPEAKER_MIXER_1, 7, outmux_text); + +static const struct snd_kcontrol_new spkoutl_mux = + SOC_DAPM_ENUM("SPKOUTL Mux", spkoutl_enum); + +static SOC_ENUM_SINGLE_DECL(hpoutr_enum, + WM8962_HEADPHONE_MIXER_2, 7, outmux_text); + +static const struct snd_kcontrol_new hpoutr_mux = + SOC_DAPM_ENUM("HPOUTR Mux", hpoutr_enum); + +static SOC_ENUM_SINGLE_DECL(hpoutl_enum, + WM8962_HEADPHONE_MIXER_1, 7, outmux_text); + +static const struct snd_kcontrol_new hpoutl_mux = + SOC_DAPM_ENUM("HPOUTL Mux", hpoutl_enum); + +static const struct snd_kcontrol_new inpgal[] = { +SOC_DAPM_SINGLE("IN1L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 3, 1, 0), +SOC_DAPM_SINGLE("IN2L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 2, 1, 0), +SOC_DAPM_SINGLE("IN3L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 1, 1, 0), +SOC_DAPM_SINGLE("IN4L Switch", WM8962_LEFT_INPUT_PGA_CONTROL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new inpgar[] = { +SOC_DAPM_SINGLE("IN1R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 3, 1, 0), +SOC_DAPM_SINGLE("IN2R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 2, 1, 0), +SOC_DAPM_SINGLE("IN3R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 1, 1, 0), +SOC_DAPM_SINGLE("IN4R Switch", WM8962_RIGHT_INPUT_PGA_CONTROL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mixinl[] = { +SOC_DAPM_SINGLE("IN2L Switch", WM8962_INPUT_MIXER_CONTROL_2, 5, 1, 0), +SOC_DAPM_SINGLE("IN3L Switch", WM8962_INPUT_MIXER_CONTROL_2, 4, 1, 0), +SOC_DAPM_SINGLE("PGA Switch", WM8962_INPUT_MIXER_CONTROL_2, 3, 1, 0), +}; + +static const struct snd_kcontrol_new mixinr[] = { +SOC_DAPM_SINGLE("IN2R Switch", WM8962_INPUT_MIXER_CONTROL_2, 2, 1, 0), +SOC_DAPM_SINGLE("IN3R Switch", WM8962_INPUT_MIXER_CONTROL_2, 1, 1, 0), +SOC_DAPM_SINGLE("PGA Switch", WM8962_INPUT_MIXER_CONTROL_2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new hpmixl[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8962_HEADPHONE_MIXER_1, 5, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8962_HEADPHONE_MIXER_1, 4, 1, 0), +SOC_DAPM_SINGLE("MIXINL Switch", WM8962_HEADPHONE_MIXER_1, 3, 1, 0), +SOC_DAPM_SINGLE("MIXINR Switch", WM8962_HEADPHONE_MIXER_1, 2, 1, 0), +SOC_DAPM_SINGLE("IN4L Switch", WM8962_HEADPHONE_MIXER_1, 1, 1, 0), +SOC_DAPM_SINGLE("IN4R Switch", WM8962_HEADPHONE_MIXER_1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new hpmixr[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8962_HEADPHONE_MIXER_2, 5, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8962_HEADPHONE_MIXER_2, 4, 1, 0), +SOC_DAPM_SINGLE("MIXINL Switch", WM8962_HEADPHONE_MIXER_2, 3, 1, 0), +SOC_DAPM_SINGLE("MIXINR Switch", WM8962_HEADPHONE_MIXER_2, 2, 1, 0), +SOC_DAPM_SINGLE("IN4L Switch", WM8962_HEADPHONE_MIXER_2, 1, 1, 0), +SOC_DAPM_SINGLE("IN4R Switch", WM8962_HEADPHONE_MIXER_2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new spkmixl[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8962_SPEAKER_MIXER_1, 5, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8962_SPEAKER_MIXER_1, 4, 1, 0), +SOC_DAPM_SINGLE("MIXINL Switch", WM8962_SPEAKER_MIXER_1, 3, 1, 0), +SOC_DAPM_SINGLE("MIXINR Switch", WM8962_SPEAKER_MIXER_1, 2, 1, 0), +SOC_DAPM_SINGLE("IN4L Switch", WM8962_SPEAKER_MIXER_1, 1, 1, 0), +SOC_DAPM_SINGLE("IN4R Switch", WM8962_SPEAKER_MIXER_1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new spkmixr[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8962_SPEAKER_MIXER_2, 5, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8962_SPEAKER_MIXER_2, 4, 1, 0), +SOC_DAPM_SINGLE("MIXINL Switch", WM8962_SPEAKER_MIXER_2, 3, 1, 0), +SOC_DAPM_SINGLE("MIXINR Switch", WM8962_SPEAKER_MIXER_2, 2, 1, 0), +SOC_DAPM_SINGLE("IN4L Switch", WM8962_SPEAKER_MIXER_2, 1, 1, 0), +SOC_DAPM_SINGLE("IN4R Switch", WM8962_SPEAKER_MIXER_2, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8962_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), +SND_SOC_DAPM_INPUT("IN4L"), +SND_SOC_DAPM_INPUT("IN4R"), +SND_SOC_DAPM_SIGGEN("Beep"), +SND_SOC_DAPM_INPUT("DMICDAT"), + +SND_SOC_DAPM_SUPPLY("MICBIAS", WM8962_PWR_MGMT_1, 1, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("Class G", WM8962_CHARGE_PUMP_B, 0, 1, NULL, 0), +SND_SOC_DAPM_SUPPLY("SYSCLK", WM8962_CLOCKING2, 5, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("Charge Pump", WM8962_CHARGE_PUMP_1, 0, 0, cp_event, + SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_SUPPLY("TOCLK", WM8962_ADDITIONAL_CONTROL_1, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY_S("DSP2", 1, WM8962_DSP2_POWER_MANAGEMENT, + WM8962_DSP2_ENA_SHIFT, 0, dsp2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_SUPPLY("TEMP_HP", WM8962_ADDITIONAL_CONTROL_4, 2, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TEMP_SPK", WM8962_ADDITIONAL_CONTROL_4, 1, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("INPGAL", WM8962_LEFT_INPUT_PGA_CONTROL, 4, 0, + inpgal, ARRAY_SIZE(inpgal)), +SND_SOC_DAPM_MIXER("INPGAR", WM8962_RIGHT_INPUT_PGA_CONTROL, 4, 0, + inpgar, ARRAY_SIZE(inpgar)), +SND_SOC_DAPM_MIXER("MIXINL", WM8962_PWR_MGMT_1, 5, 0, + mixinl, ARRAY_SIZE(mixinl)), +SND_SOC_DAPM_MIXER("MIXINR", WM8962_PWR_MGMT_1, 4, 0, + mixinr, ARRAY_SIZE(mixinr)), + +SND_SOC_DAPM_AIF_IN("DMIC_ENA", NULL, 0, WM8962_PWR_MGMT_1, 10, 0), + +SND_SOC_DAPM_ADC("ADCL", "Capture", WM8962_PWR_MGMT_1, 3, 0), +SND_SOC_DAPM_ADC("ADCR", "Capture", WM8962_PWR_MGMT_1, 2, 0), + +SND_SOC_DAPM_MUX("STL", SND_SOC_NOPM, 0, 0, &stl_mux), +SND_SOC_DAPM_MUX("STR", SND_SOC_NOPM, 0, 0, &str_mux), + +SND_SOC_DAPM_DAC("DACL", "Playback", WM8962_PWR_MGMT_2, 8, 0), +SND_SOC_DAPM_DAC("DACR", "Playback", WM8962_PWR_MGMT_2, 7, 0), + +SND_SOC_DAPM_PGA("Left Bypass", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Bypass", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("HPMIXL", WM8962_MIXER_ENABLES, 3, 0, + hpmixl, ARRAY_SIZE(hpmixl)), +SND_SOC_DAPM_MIXER("HPMIXR", WM8962_MIXER_ENABLES, 2, 0, + hpmixr, ARRAY_SIZE(hpmixr)), + +SND_SOC_DAPM_MUX_E("HPOUTL PGA", WM8962_PWR_MGMT_2, 6, 0, &hpoutl_mux, + out_pga_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_MUX_E("HPOUTR PGA", WM8962_PWR_MGMT_2, 5, 0, &hpoutr_mux, + out_pga_event, SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_PGA_E("HPOUT", SND_SOC_NOPM, 0, 0, NULL, 0, hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +}; + +static const struct snd_soc_dapm_widget wm8962_dapm_spk_mono_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8962_MIXER_ENABLES, 1, 0, + spkmixl, ARRAY_SIZE(spkmixl)), +SND_SOC_DAPM_MUX_E("Speaker PGA", WM8962_PWR_MGMT_2, 4, 0, &spkoutl_mux, + out_pga_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA("Speaker Output", WM8962_CLASS_D_CONTROL_1, 7, 0, NULL, 0), +SND_SOC_DAPM_OUTPUT("SPKOUT"), +}; + +static const struct snd_soc_dapm_widget wm8962_dapm_spk_stereo_widgets[] = { +SND_SOC_DAPM_MIXER("SPKOUTL Mixer", WM8962_MIXER_ENABLES, 1, 0, + spkmixl, ARRAY_SIZE(spkmixl)), +SND_SOC_DAPM_MIXER("SPKOUTR Mixer", WM8962_MIXER_ENABLES, 0, 0, + spkmixr, ARRAY_SIZE(spkmixr)), + +SND_SOC_DAPM_MUX_E("SPKOUTL PGA", WM8962_PWR_MGMT_2, 4, 0, &spkoutl_mux, + out_pga_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_MUX_E("SPKOUTR PGA", WM8962_PWR_MGMT_2, 3, 0, &spkoutr_mux, + out_pga_event, SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_PGA("SPKOUTR Output", WM8962_CLASS_D_CONTROL_1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("SPKOUTL Output", WM8962_CLASS_D_CONTROL_1, 6, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPKOUTL"), +SND_SOC_DAPM_OUTPUT("SPKOUTR"), +}; + +static const struct snd_soc_dapm_route wm8962_intercon[] = { + { "INPGAL", "IN1L Switch", "IN1L" }, + { "INPGAL", "IN2L Switch", "IN2L" }, + { "INPGAL", "IN3L Switch", "IN3L" }, + { "INPGAL", "IN4L Switch", "IN4L" }, + + { "INPGAR", "IN1R Switch", "IN1R" }, + { "INPGAR", "IN2R Switch", "IN2R" }, + { "INPGAR", "IN3R Switch", "IN3R" }, + { "INPGAR", "IN4R Switch", "IN4R" }, + + { "MIXINL", "IN2L Switch", "IN2L" }, + { "MIXINL", "IN3L Switch", "IN3L" }, + { "MIXINL", "PGA Switch", "INPGAL" }, + + { "MIXINR", "IN2R Switch", "IN2R" }, + { "MIXINR", "IN3R Switch", "IN3R" }, + { "MIXINR", "PGA Switch", "INPGAR" }, + + { "MICBIAS", NULL, "SYSCLK" }, + + { "DMIC_ENA", NULL, "DMICDAT" }, + + { "ADCL", NULL, "SYSCLK" }, + { "ADCL", NULL, "TOCLK" }, + { "ADCL", NULL, "MIXINL" }, + { "ADCL", NULL, "DMIC_ENA" }, + { "ADCL", NULL, "DSP2" }, + + { "ADCR", NULL, "SYSCLK" }, + { "ADCR", NULL, "TOCLK" }, + { "ADCR", NULL, "MIXINR" }, + { "ADCR", NULL, "DMIC_ENA" }, + { "ADCR", NULL, "DSP2" }, + + { "STL", "Left", "ADCL" }, + { "STL", "Right", "ADCR" }, + { "STL", NULL, "Class G" }, + + { "STR", "Left", "ADCL" }, + { "STR", "Right", "ADCR" }, + { "STR", NULL, "Class G" }, + + { "DACL", NULL, "SYSCLK" }, + { "DACL", NULL, "TOCLK" }, + { "DACL", NULL, "Beep" }, + { "DACL", NULL, "STL" }, + { "DACL", NULL, "DSP2" }, + + { "DACR", NULL, "SYSCLK" }, + { "DACR", NULL, "TOCLK" }, + { "DACR", NULL, "Beep" }, + { "DACR", NULL, "STR" }, + { "DACR", NULL, "DSP2" }, + + { "HPMIXL", "IN4L Switch", "IN4L" }, + { "HPMIXL", "IN4R Switch", "IN4R" }, + { "HPMIXL", "DACL Switch", "DACL" }, + { "HPMIXL", "DACR Switch", "DACR" }, + { "HPMIXL", "MIXINL Switch", "MIXINL" }, + { "HPMIXL", "MIXINR Switch", "MIXINR" }, + + { "HPMIXR", "IN4L Switch", "IN4L" }, + { "HPMIXR", "IN4R Switch", "IN4R" }, + { "HPMIXR", "DACL Switch", "DACL" }, + { "HPMIXR", "DACR Switch", "DACR" }, + { "HPMIXR", "MIXINL Switch", "MIXINL" }, + { "HPMIXR", "MIXINR Switch", "MIXINR" }, + + { "Left Bypass", NULL, "HPMIXL" }, + { "Left Bypass", NULL, "Class G" }, + + { "Right Bypass", NULL, "HPMIXR" }, + { "Right Bypass", NULL, "Class G" }, + + { "HPOUTL PGA", "Mixer", "Left Bypass" }, + { "HPOUTL PGA", "DAC", "DACL" }, + + { "HPOUTR PGA", "Mixer", "Right Bypass" }, + { "HPOUTR PGA", "DAC", "DACR" }, + + { "HPOUT", NULL, "HPOUTL PGA" }, + { "HPOUT", NULL, "HPOUTR PGA" }, + { "HPOUT", NULL, "Charge Pump" }, + { "HPOUT", NULL, "SYSCLK" }, + { "HPOUT", NULL, "TOCLK" }, + + { "HPOUTL", NULL, "HPOUT" }, + { "HPOUTR", NULL, "HPOUT" }, + + { "HPOUTL", NULL, "TEMP_HP" }, + { "HPOUTR", NULL, "TEMP_HP" }, +}; + +static const struct snd_soc_dapm_route wm8962_spk_mono_intercon[] = { + { "Speaker Mixer", "IN4L Switch", "IN4L" }, + { "Speaker Mixer", "IN4R Switch", "IN4R" }, + { "Speaker Mixer", "DACL Switch", "DACL" }, + { "Speaker Mixer", "DACR Switch", "DACR" }, + { "Speaker Mixer", "MIXINL Switch", "MIXINL" }, + { "Speaker Mixer", "MIXINR Switch", "MIXINR" }, + + { "Speaker PGA", "Mixer", "Speaker Mixer" }, + { "Speaker PGA", "DAC", "DACL" }, + + { "Speaker Output", NULL, "Speaker PGA" }, + { "Speaker Output", NULL, "SYSCLK" }, + { "Speaker Output", NULL, "TOCLK" }, + { "Speaker Output", NULL, "TEMP_SPK" }, + + { "SPKOUT", NULL, "Speaker Output" }, +}; + +static const struct snd_soc_dapm_route wm8962_spk_stereo_intercon[] = { + { "SPKOUTL Mixer", "IN4L Switch", "IN4L" }, + { "SPKOUTL Mixer", "IN4R Switch", "IN4R" }, + { "SPKOUTL Mixer", "DACL Switch", "DACL" }, + { "SPKOUTL Mixer", "DACR Switch", "DACR" }, + { "SPKOUTL Mixer", "MIXINL Switch", "MIXINL" }, + { "SPKOUTL Mixer", "MIXINR Switch", "MIXINR" }, + + { "SPKOUTR Mixer", "IN4L Switch", "IN4L" }, + { "SPKOUTR Mixer", "IN4R Switch", "IN4R" }, + { "SPKOUTR Mixer", "DACL Switch", "DACL" }, + { "SPKOUTR Mixer", "DACR Switch", "DACR" }, + { "SPKOUTR Mixer", "MIXINL Switch", "MIXINL" }, + { "SPKOUTR Mixer", "MIXINR Switch", "MIXINR" }, + + { "SPKOUTL PGA", "Mixer", "SPKOUTL Mixer" }, + { "SPKOUTL PGA", "DAC", "DACL" }, + + { "SPKOUTR PGA", "Mixer", "SPKOUTR Mixer" }, + { "SPKOUTR PGA", "DAC", "DACR" }, + + { "SPKOUTL Output", NULL, "SPKOUTL PGA" }, + { "SPKOUTL Output", NULL, "SYSCLK" }, + { "SPKOUTL Output", NULL, "TOCLK" }, + { "SPKOUTL Output", NULL, "TEMP_SPK" }, + + { "SPKOUTR Output", NULL, "SPKOUTR PGA" }, + { "SPKOUTR Output", NULL, "SYSCLK" }, + { "SPKOUTR Output", NULL, "TOCLK" }, + { "SPKOUTR Output", NULL, "TEMP_SPK" }, + + { "SPKOUTL", NULL, "SPKOUTL Output" }, + { "SPKOUTR", NULL, "SPKOUTR Output" }, +}; + +static int wm8962_add_widgets(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + struct wm8962_pdata *pdata = &wm8962->pdata; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + snd_soc_add_component_controls(component, wm8962_snd_controls, + ARRAY_SIZE(wm8962_snd_controls)); + if (pdata->spk_mono) + snd_soc_add_component_controls(component, wm8962_spk_mono_controls, + ARRAY_SIZE(wm8962_spk_mono_controls)); + else + snd_soc_add_component_controls(component, wm8962_spk_stereo_controls, + ARRAY_SIZE(wm8962_spk_stereo_controls)); + + + snd_soc_dapm_new_controls(dapm, wm8962_dapm_widgets, + ARRAY_SIZE(wm8962_dapm_widgets)); + if (pdata->spk_mono) + snd_soc_dapm_new_controls(dapm, wm8962_dapm_spk_mono_widgets, + ARRAY_SIZE(wm8962_dapm_spk_mono_widgets)); + else + snd_soc_dapm_new_controls(dapm, wm8962_dapm_spk_stereo_widgets, + ARRAY_SIZE(wm8962_dapm_spk_stereo_widgets)); + + snd_soc_dapm_add_routes(dapm, wm8962_intercon, + ARRAY_SIZE(wm8962_intercon)); + if (pdata->spk_mono) + snd_soc_dapm_add_routes(dapm, wm8962_spk_mono_intercon, + ARRAY_SIZE(wm8962_spk_mono_intercon)); + else + snd_soc_dapm_add_routes(dapm, wm8962_spk_stereo_intercon, + ARRAY_SIZE(wm8962_spk_stereo_intercon)); + + + snd_soc_dapm_disable_pin(dapm, "Beep"); + + return 0; +} + +/* -1 for reserved values */ +static const int bclk_divs[] = { + 1, -1, 2, 3, 4, -1, 6, 8, -1, 12, 16, 24, -1, 32, 32, 32 +}; + +static const int sysclk_rates[] = { + 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536, 3072, 6144 +}; + +static void wm8962_configure_bclk(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int dspclk, i; + int clocking2 = 0; + int clocking4 = 0; + int aif2 = 0; + + if (!wm8962->sysclk_rate) { + dev_dbg(component->dev, "No SYSCLK configured\n"); + return; + } + + if (!wm8962->bclk || !wm8962->lrclk) { + dev_dbg(component->dev, "No audio clocks configured\n"); + return; + } + + for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) { + if (sysclk_rates[i] == wm8962->sysclk_rate / wm8962->lrclk) { + clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT; + break; + } + } + + if (i == ARRAY_SIZE(sysclk_rates)) { + dev_err(component->dev, "Unsupported sysclk ratio %d\n", + wm8962->sysclk_rate / wm8962->lrclk); + return; + } + + dev_dbg(component->dev, "Selected sysclk ratio %d\n", sysclk_rates[i]); + + snd_soc_component_update_bits(component, WM8962_CLOCKING_4, + WM8962_SYSCLK_RATE_MASK, clocking4); + + /* DSPCLK_DIV can be only generated correctly after enabling SYSCLK. + * So we here provisionally enable it and then disable it afterward + * if current bias_level hasn't reached SND_SOC_BIAS_ON. + */ + if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_ON) + snd_soc_component_update_bits(component, WM8962_CLOCKING2, + WM8962_SYSCLK_ENA_MASK, WM8962_SYSCLK_ENA); + + dspclk = snd_soc_component_read32(component, WM8962_CLOCKING1); + + if (snd_soc_component_get_bias_level(component) != SND_SOC_BIAS_ON) + snd_soc_component_update_bits(component, WM8962_CLOCKING2, + WM8962_SYSCLK_ENA_MASK, 0); + + if (dspclk < 0) { + dev_err(component->dev, "Failed to read DSPCLK: %d\n", dspclk); + return; + } + + dspclk = (dspclk & WM8962_DSPCLK_DIV_MASK) >> WM8962_DSPCLK_DIV_SHIFT; + switch (dspclk) { + case 0: + dspclk = wm8962->sysclk_rate; + break; + case 1: + dspclk = wm8962->sysclk_rate / 2; + break; + case 2: + dspclk = wm8962->sysclk_rate / 4; + break; + default: + dev_warn(component->dev, "Unknown DSPCLK divisor read back\n"); + dspclk = wm8962->sysclk_rate; + } + + dev_dbg(component->dev, "DSPCLK is %dHz, BCLK %d\n", dspclk, wm8962->bclk); + + /* We're expecting an exact match */ + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + if (bclk_divs[i] < 0) + continue; + + if (dspclk / bclk_divs[i] == wm8962->bclk) { + dev_dbg(component->dev, "Selected BCLK_DIV %d for %dHz\n", + bclk_divs[i], wm8962->bclk); + clocking2 |= i; + break; + } + } + if (i == ARRAY_SIZE(bclk_divs)) { + dev_err(component->dev, "Unsupported BCLK ratio %d\n", + dspclk / wm8962->bclk); + return; + } + + aif2 |= wm8962->bclk / wm8962->lrclk; + dev_dbg(component->dev, "Selected LRCLK divisor %d for %dHz\n", + wm8962->bclk / wm8962->lrclk, wm8962->lrclk); + + snd_soc_component_update_bits(component, WM8962_CLOCKING2, + WM8962_BCLK_DIV_MASK, clocking2); + snd_soc_component_update_bits(component, WM8962_AUDIO_INTERFACE_2, + WM8962_AIF_RATE_MASK, aif2); +} + +static int wm8962_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID 2*50k */ + snd_soc_component_update_bits(component, WM8962_PWR_MGMT_1, + WM8962_VMID_SEL_MASK, 0x80); + + wm8962_configure_bclk(component); + break; + + case SND_SOC_BIAS_STANDBY: + /* VMID 2*250k */ + snd_soc_component_update_bits(component, WM8962_PWR_MGMT_1, + WM8962_VMID_SEL_MASK, 0x100); + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) + msleep(100); + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static const struct { + int rate; + int reg; +} sr_vals[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11025, 4 }, + { 12000, 4 }, + { 8000, 5 }, + { 88200, 6 }, + { 96000, 6 }, +}; + +static int wm8962_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int i; + int aif0 = 0; + int adctl3 = 0; + + wm8962->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8962->bclk *= 2; + + wm8962->lrclk = params_rate(params); + + for (i = 0; i < ARRAY_SIZE(sr_vals); i++) { + if (sr_vals[i].rate == wm8962->lrclk) { + adctl3 |= sr_vals[i].reg; + break; + } + } + if (i == ARRAY_SIZE(sr_vals)) { + dev_err(component->dev, "Unsupported rate %dHz\n", wm8962->lrclk); + return -EINVAL; + } + + if (wm8962->lrclk % 8000 == 0) + adctl3 |= WM8962_SAMPLE_RATE_INT_MODE; + + switch (params_width(params)) { + case 16: + break; + case 20: + aif0 |= 0x4; + break; + case 24: + aif0 |= 0x8; + break; + case 32: + aif0 |= 0xc; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8962_AUDIO_INTERFACE_0, + WM8962_WL_MASK, aif0); + snd_soc_component_update_bits(component, WM8962_ADDITIONAL_CONTROL_3, + WM8962_SAMPLE_RATE_INT_MODE | + WM8962_SAMPLE_RATE_MASK, adctl3); + + dev_dbg(component->dev, "hw_params set BCLK %dHz LRCLK %dHz\n", + wm8962->bclk, wm8962->lrclk); + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON) + wm8962_configure_bclk(component); + + return 0; +} + +static int wm8962_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int src; + + switch (clk_id) { + case WM8962_SYSCLK_MCLK: + wm8962->sysclk = WM8962_SYSCLK_MCLK; + src = 0; + break; + case WM8962_SYSCLK_FLL: + wm8962->sysclk = WM8962_SYSCLK_FLL; + src = 1 << WM8962_SYSCLK_SRC_SHIFT; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8962_CLOCKING2, WM8962_SYSCLK_SRC_MASK, + src); + + wm8962->sysclk_rate = freq; + + return 0; +} + +static int wm8962_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + int aif0 = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif0 |= WM8962_LRCLK_INV | 3; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif0 |= 3; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_IB_NF: + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif0 |= 1; + break; + case SND_SOC_DAIFMT_I2S: + aif0 |= 2; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif0 |= WM8962_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif0 |= WM8962_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif0 |= WM8962_BCLK_INV | WM8962_LRCLK_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aif0 |= WM8962_MSTR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8962_AUDIO_INTERFACE_0, + WM8962_FMT_MASK | WM8962_BCLK_INV | WM8962_MSTR | + WM8962_LRCLK_INV, aif0); + + return 0; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_refclk_div; + u16 n; + u16 theta; + u16 lambda; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + unsigned int target; + unsigned int div; + unsigned int fratio, gcd_fll; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + fll_div->fll_refclk_div = 0; + while ((Fref / div) > 13500000) { + div *= 2; + fll_div->fll_refclk_div++; + + if (div > 4) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("FLL Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 2; + while (Fout * div < 90000000) { + div++; + if (div > 64) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * div; + fll_div->fll_outdiv = div - 1; + + pr_debug("FLL Fvco=%dHz\n", target); + + /* Find an appropriate FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + fratio = fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + fll_div->n = target / (fratio * Fref); + + if (target % Fref == 0) { + fll_div->theta = 0; + fll_div->lambda = 1; + } else { + gcd_fll = gcd(target, fratio * Fref); + + fll_div->theta = (target - (fll_div->n * fratio * Fref)) + / gcd_fll; + fll_div->lambda = (fratio * Fref) / gcd_fll; + } + + pr_debug("FLL N=%x THETA=%x LAMBDA=%x\n", + fll_div->n, fll_div->theta, fll_div->lambda); + pr_debug("FLL_FRATIO=%x FLL_OUTDIV=%x FLL_REFCLK_DIV=%x\n", + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_refclk_div); + + return 0; +} + +static int wm8962_set_fll(struct snd_soc_component *component, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + struct _fll_div fll_div; + unsigned long timeout; + int ret; + int fll1 = 0; + + /* Any change? */ + if (source == wm8962->fll_src && Fref == wm8962->fll_fref && + Fout == wm8962->fll_fout) + return 0; + + if (Fout == 0) { + dev_dbg(component->dev, "FLL disabled\n"); + + wm8962->fll_fref = 0; + wm8962->fll_fout = 0; + + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_1, + WM8962_FLL_ENA, 0); + + pm_runtime_put(component->dev); + + return 0; + } + + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + + /* Parameters good, disable so we can reprogram */ + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_1, WM8962_FLL_ENA, 0); + + switch (fll_id) { + case WM8962_FLL_MCLK: + case WM8962_FLL_BCLK: + case WM8962_FLL_OSC: + fll1 |= (fll_id - 1) << WM8962_FLL_REFCLK_SRC_SHIFT; + break; + case WM8962_FLL_INT: + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_1, + WM8962_FLL_OSC_ENA, WM8962_FLL_OSC_ENA); + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_5, + WM8962_FLL_FRC_NCO, WM8962_FLL_FRC_NCO); + break; + default: + dev_err(component->dev, "Unknown FLL source %d\n", ret); + return -EINVAL; + } + + if (fll_div.theta) + fll1 |= WM8962_FLL_FRAC; + + /* Stop the FLL while we reconfigure */ + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_1, WM8962_FLL_ENA, 0); + + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_2, + WM8962_FLL_OUTDIV_MASK | + WM8962_FLL_REFCLK_DIV_MASK, + (fll_div.fll_outdiv << WM8962_FLL_OUTDIV_SHIFT) | + (fll_div.fll_refclk_div)); + + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_3, + WM8962_FLL_FRATIO_MASK, fll_div.fll_fratio); + + snd_soc_component_write(component, WM8962_FLL_CONTROL_6, fll_div.theta); + snd_soc_component_write(component, WM8962_FLL_CONTROL_7, fll_div.lambda); + snd_soc_component_write(component, WM8962_FLL_CONTROL_8, fll_div.n); + + reinit_completion(&wm8962->fll_lock); + + ret = pm_runtime_get_sync(component->dev); + if (ret < 0) { + dev_err(component->dev, "Failed to resume device: %d\n", ret); + return ret; + } + + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_1, + WM8962_FLL_FRAC | WM8962_FLL_REFCLK_SRC_MASK | + WM8962_FLL_ENA, fll1 | WM8962_FLL_ENA); + + dev_dbg(component->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout); + + /* This should be a massive overestimate but go even + * higher if we'll error out + */ + if (wm8962->irq) + timeout = msecs_to_jiffies(5); + else + timeout = msecs_to_jiffies(1); + + timeout = wait_for_completion_timeout(&wm8962->fll_lock, + timeout); + + if (timeout == 0 && wm8962->irq) { + dev_err(component->dev, "FLL lock timed out"); + snd_soc_component_update_bits(component, WM8962_FLL_CONTROL_1, + WM8962_FLL_ENA, 0); + pm_runtime_put(component->dev); + return -ETIMEDOUT; + } + + wm8962->fll_fref = Fref; + wm8962->fll_fout = Fout; + wm8962->fll_src = source; + + return 0; +} + +static int wm8962_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + int val, ret; + + if (mute) + val = WM8962_DAC_MUTE | WM8962_DAC_MUTE_ALT; + else + val = 0; + + /** + * The DAC mute bit is mirrored in two registers, update both to keep + * the register cache consistent. + */ + ret = snd_soc_component_update_bits(component, WM8962_CLASS_D_CONTROL_1, + WM8962_DAC_MUTE_ALT, val); + if (ret < 0) + return ret; + + return snd_soc_component_update_bits(component, WM8962_ADC_DAC_CONTROL_1, + WM8962_DAC_MUTE, val); +} + +#define WM8962_RATES (SNDRV_PCM_RATE_8000_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8962_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8962_dai_ops = { + .hw_params = wm8962_hw_params, + .set_sysclk = wm8962_set_dai_sysclk, + .set_fmt = wm8962_set_dai_fmt, + .digital_mute = wm8962_mute, +}; + +static struct snd_soc_dai_driver wm8962_dai = { + .name = "wm8962", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8962_RATES, + .formats = WM8962_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8962_RATES, + .formats = WM8962_FORMATS, + }, + .ops = &wm8962_dai_ops, + .symmetric_rates = 1, +}; + +static void wm8962_mic_work(struct work_struct *work) +{ + struct wm8962_priv *wm8962 = container_of(work, + struct wm8962_priv, + mic_work.work); + struct snd_soc_component *component = wm8962->component; + int status = 0; + int irq_pol = 0; + int reg; + + reg = snd_soc_component_read32(component, WM8962_ADDITIONAL_CONTROL_4); + + if (reg & WM8962_MICDET_STS) { + status |= SND_JACK_MICROPHONE; + irq_pol |= WM8962_MICD_IRQ_POL; + } + + if (reg & WM8962_MICSHORT_STS) { + status |= SND_JACK_BTN_0; + irq_pol |= WM8962_MICSCD_IRQ_POL; + } + + snd_soc_jack_report(wm8962->jack, status, + SND_JACK_MICROPHONE | SND_JACK_BTN_0); + + snd_soc_component_update_bits(component, WM8962_MICINT_SOURCE_POL, + WM8962_MICSCD_IRQ_POL | + WM8962_MICD_IRQ_POL, irq_pol); +} + +static irqreturn_t wm8962_irq(int irq, void *data) +{ + struct device *dev = data; + struct wm8962_priv *wm8962 = dev_get_drvdata(dev); + unsigned int mask; + unsigned int active; + int reg, ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "Failed to resume: %d\n", ret); + return IRQ_NONE; + } + + ret = regmap_read(wm8962->regmap, WM8962_INTERRUPT_STATUS_2_MASK, + &mask); + if (ret != 0) { + pm_runtime_put(dev); + dev_err(dev, "Failed to read interrupt mask: %d\n", + ret); + return IRQ_NONE; + } + + ret = regmap_read(wm8962->regmap, WM8962_INTERRUPT_STATUS_2, &active); + if (ret != 0) { + pm_runtime_put(dev); + dev_err(dev, "Failed to read interrupt: %d\n", ret); + return IRQ_NONE; + } + + active &= ~mask; + + if (!active) { + pm_runtime_put(dev); + return IRQ_NONE; + } + + /* Acknowledge the interrupts */ + ret = regmap_write(wm8962->regmap, WM8962_INTERRUPT_STATUS_2, active); + if (ret != 0) + dev_warn(dev, "Failed to ack interrupt: %d\n", ret); + + if (active & WM8962_FLL_LOCK_EINT) { + dev_dbg(dev, "FLL locked\n"); + complete(&wm8962->fll_lock); + } + + if (active & WM8962_FIFOS_ERR_EINT) + dev_err(dev, "FIFO error\n"); + + if (active & WM8962_TEMP_SHUT_EINT) { + dev_crit(dev, "Thermal shutdown\n"); + + ret = regmap_read(wm8962->regmap, + WM8962_THERMAL_SHUTDOWN_STATUS, ®); + if (ret != 0) { + dev_warn(dev, "Failed to read thermal status: %d\n", + ret); + reg = 0; + } + + if (reg & WM8962_TEMP_ERR_HP) + dev_crit(dev, "Headphone thermal error\n"); + if (reg & WM8962_TEMP_WARN_HP) + dev_crit(dev, "Headphone thermal warning\n"); + if (reg & WM8962_TEMP_ERR_SPK) + dev_crit(dev, "Speaker thermal error\n"); + if (reg & WM8962_TEMP_WARN_SPK) + dev_crit(dev, "Speaker thermal warning\n"); + } + + if (active & (WM8962_MICSCD_EINT | WM8962_MICD_EINT)) { + dev_dbg(dev, "Microphone event detected\n"); + +#ifndef CONFIG_SND_SOC_WM8962_MODULE + trace_snd_soc_jack_irq(dev_name(dev)); +#endif + + pm_wakeup_event(dev, 300); + + queue_delayed_work(system_power_efficient_wq, + &wm8962->mic_work, + msecs_to_jiffies(250)); + } + + pm_runtime_put(dev); + + return IRQ_HANDLED; +} + +/** + * wm8962_mic_detect - Enable microphone detection via the WM8962 IRQ + * + * @component: WM8962 component + * @jack: jack to report detection events on + * + * Enable microphone detection via IRQ on the WM8962. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for WM8962 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * If no jack is supplied detection will be disabled. + */ +int wm8962_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int irq_mask, enable; + + wm8962->jack = jack; + if (jack) { + irq_mask = 0; + enable = WM8962_MICDET_ENA; + } else { + irq_mask = WM8962_MICD_EINT | WM8962_MICSCD_EINT; + enable = 0; + } + + snd_soc_component_update_bits(component, WM8962_INTERRUPT_STATUS_2_MASK, + WM8962_MICD_EINT | WM8962_MICSCD_EINT, irq_mask); + snd_soc_component_update_bits(component, WM8962_ADDITIONAL_CONTROL_4, + WM8962_MICDET_ENA, enable); + + /* Send an initial empty report */ + snd_soc_jack_report(wm8962->jack, 0, + SND_JACK_MICROPHONE | SND_JACK_BTN_0); + + snd_soc_dapm_mutex_lock(dapm); + + if (jack) { + snd_soc_dapm_force_enable_pin_unlocked(dapm, "SYSCLK"); + snd_soc_dapm_force_enable_pin_unlocked(dapm, "MICBIAS"); + } else { + snd_soc_dapm_disable_pin_unlocked(dapm, "SYSCLK"); + snd_soc_dapm_disable_pin_unlocked(dapm, "MICBIAS"); + } + + snd_soc_dapm_mutex_unlock(dapm); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8962_mic_detect); + +static int beep_rates[] = { + 500, 1000, 2000, 4000, +}; + +static void wm8962_beep_work(struct work_struct *work) +{ + struct wm8962_priv *wm8962 = + container_of(work, struct wm8962_priv, beep_work); + struct snd_soc_component *component = wm8962->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int i; + int reg = 0; + int best = 0; + + if (wm8962->beep_rate) { + for (i = 0; i < ARRAY_SIZE(beep_rates); i++) { + if (abs(wm8962->beep_rate - beep_rates[i]) < + abs(wm8962->beep_rate - beep_rates[best])) + best = i; + } + + dev_dbg(component->dev, "Set beep rate %dHz for requested %dHz\n", + beep_rates[best], wm8962->beep_rate); + + reg = WM8962_BEEP_ENA | (best << WM8962_BEEP_RATE_SHIFT); + + snd_soc_dapm_enable_pin(dapm, "Beep"); + } else { + dev_dbg(component->dev, "Disabling beep\n"); + snd_soc_dapm_disable_pin(dapm, "Beep"); + } + + snd_soc_component_update_bits(component, WM8962_BEEP_GENERATOR_1, + WM8962_BEEP_ENA | WM8962_BEEP_RATE_MASK, reg); + + snd_soc_dapm_sync(dapm); +} + +/* For usability define a way of injecting beep events for the device - + * many systems will not have a keyboard. + */ +static int wm8962_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + struct snd_soc_component *component = input_get_drvdata(dev); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + + dev_dbg(component->dev, "Beep event %x %x\n", code, hz); + + switch (code) { + case SND_BELL: + if (hz) + hz = 1000; + case SND_TONE: + break; + default: + return -1; + } + + /* Kick the beep from a workqueue */ + wm8962->beep_rate = hz; + schedule_work(&wm8962->beep_work); + return 0; +} + +static ssize_t wm8962_beep_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wm8962_priv *wm8962 = dev_get_drvdata(dev); + long int time; + int ret; + + ret = kstrtol(buf, 10, &time); + if (ret != 0) + return ret; + + input_event(wm8962->beep, EV_SND, SND_TONE, time); + + return count; +} + +static DEVICE_ATTR(beep, 0200, NULL, wm8962_beep_set); + +static void wm8962_init_beep(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int ret; + + wm8962->beep = devm_input_allocate_device(component->dev); + if (!wm8962->beep) { + dev_err(component->dev, "Failed to allocate beep device\n"); + return; + } + + INIT_WORK(&wm8962->beep_work, wm8962_beep_work); + wm8962->beep_rate = 0; + + wm8962->beep->name = "WM8962 Beep Generator"; + wm8962->beep->phys = dev_name(component->dev); + wm8962->beep->id.bustype = BUS_I2C; + + wm8962->beep->evbit[0] = BIT_MASK(EV_SND); + wm8962->beep->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + wm8962->beep->event = wm8962_beep_event; + wm8962->beep->dev.parent = component->dev; + input_set_drvdata(wm8962->beep, component); + + ret = input_register_device(wm8962->beep); + if (ret != 0) { + wm8962->beep = NULL; + dev_err(component->dev, "Failed to register beep device\n"); + } + + ret = device_create_file(component->dev, &dev_attr_beep); + if (ret != 0) { + dev_err(component->dev, "Failed to create keyclick file: %d\n", + ret); + } +} + +static void wm8962_free_beep(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + + device_remove_file(component->dev, &dev_attr_beep); + cancel_work_sync(&wm8962->beep_work); + wm8962->beep = NULL; + + snd_soc_component_update_bits(component, WM8962_BEEP_GENERATOR_1, WM8962_BEEP_ENA,0); +} + +static void wm8962_set_gpio_mode(struct wm8962_priv *wm8962, int gpio) +{ + int mask = 0; + int val = 0; + + /* Some of the GPIOs are behind MFP configuration and need to + * be put into GPIO mode. */ + switch (gpio) { + case 2: + mask = WM8962_CLKOUT2_SEL_MASK; + val = 1 << WM8962_CLKOUT2_SEL_SHIFT; + break; + case 3: + mask = WM8962_CLKOUT3_SEL_MASK; + val = 1 << WM8962_CLKOUT3_SEL_SHIFT; + break; + default: + break; + } + + if (mask) + regmap_update_bits(wm8962->regmap, WM8962_ANALOGUE_CLOCKING1, + mask, val); +} + +#ifdef CONFIG_GPIOLIB +static int wm8962_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct wm8962_priv *wm8962 = gpiochip_get_data(chip); + + /* The WM8962 GPIOs aren't linearly numbered. For simplicity + * we export linear numbers and error out if the unsupported + * ones are requsted. + */ + switch (offset + 1) { + case 2: + case 3: + case 5: + case 6: + break; + default: + return -EINVAL; + } + + wm8962_set_gpio_mode(wm8962, offset + 1); + + return 0; +} + +static void wm8962_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8962_priv *wm8962 = gpiochip_get_data(chip); + struct snd_soc_component *component = wm8962->component; + + snd_soc_component_update_bits(component, WM8962_GPIO_BASE + offset, + WM8962_GP2_LVL, !!value << WM8962_GP2_LVL_SHIFT); +} + +static int wm8962_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8962_priv *wm8962 = gpiochip_get_data(chip); + struct snd_soc_component *component = wm8962->component; + int ret, val; + + /* Force function 1 (logic output) */ + val = (1 << WM8962_GP2_FN_SHIFT) | (value << WM8962_GP2_LVL_SHIFT); + + ret = snd_soc_component_update_bits(component, WM8962_GPIO_BASE + offset, + WM8962_GP2_FN_MASK | WM8962_GP2_LVL, val); + if (ret < 0) + return ret; + + return 0; +} + +static const struct gpio_chip wm8962_template_chip = { + .label = "wm8962", + .owner = THIS_MODULE, + .request = wm8962_gpio_request, + .direction_output = wm8962_gpio_direction_out, + .set = wm8962_gpio_set, + .can_sleep = 1, +}; + +static void wm8962_init_gpio(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + struct wm8962_pdata *pdata = &wm8962->pdata; + int ret; + + wm8962->gpio_chip = wm8962_template_chip; + wm8962->gpio_chip.ngpio = WM8962_MAX_GPIO; + wm8962->gpio_chip.parent = component->dev; + + if (pdata->gpio_base) + wm8962->gpio_chip.base = pdata->gpio_base; + else + wm8962->gpio_chip.base = -1; + + ret = gpiochip_add_data(&wm8962->gpio_chip, wm8962); + if (ret != 0) + dev_err(component->dev, "Failed to add GPIOs: %d\n", ret); +} + +static void wm8962_free_gpio(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + + gpiochip_remove(&wm8962->gpio_chip); +} +#else +static void wm8962_init_gpio(struct snd_soc_component *component) +{ +} + +static void wm8962_free_gpio(struct snd_soc_component *component) +{ +} +#endif + +static int wm8962_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int ret; + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int i; + bool dmicclk, dmicdat; + + wm8962->component = component; + + wm8962->disable_nb[0].notifier_call = wm8962_regulator_event_0; + wm8962->disable_nb[1].notifier_call = wm8962_regulator_event_1; + wm8962->disable_nb[2].notifier_call = wm8962_regulator_event_2; + wm8962->disable_nb[3].notifier_call = wm8962_regulator_event_3; + wm8962->disable_nb[4].notifier_call = wm8962_regulator_event_4; + wm8962->disable_nb[5].notifier_call = wm8962_regulator_event_5; + wm8962->disable_nb[6].notifier_call = wm8962_regulator_event_6; + wm8962->disable_nb[7].notifier_call = wm8962_regulator_event_7; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) { + ret = regulator_register_notifier(wm8962->supplies[i].consumer, + &wm8962->disable_nb[i]); + if (ret != 0) { + dev_err(component->dev, + "Failed to register regulator notifier: %d\n", + ret); + } + } + + wm8962_add_widgets(component); + + /* Save boards having to disable DMIC when not in use */ + dmicclk = false; + dmicdat = false; + for (i = 0; i < WM8962_MAX_GPIO; i++) { + switch (snd_soc_component_read32(component, WM8962_GPIO_BASE + i) + & WM8962_GP2_FN_MASK) { + case WM8962_GPIO_FN_DMICCLK: + dmicclk = true; + break; + case WM8962_GPIO_FN_DMICDAT: + dmicdat = true; + break; + default: + break; + } + } + if (!dmicclk || !dmicdat) { + dev_dbg(component->dev, "DMIC not in use, disabling\n"); + snd_soc_dapm_nc_pin(dapm, "DMICDAT"); + } + if (dmicclk != dmicdat) + dev_warn(component->dev, "DMIC GPIOs partially configured\n"); + + wm8962_init_beep(component); + wm8962_init_gpio(component); + + return 0; +} + +static void wm8962_remove(struct snd_soc_component *component) +{ + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int i; + + cancel_delayed_work_sync(&wm8962->mic_work); + + wm8962_free_gpio(component); + wm8962_free_beep(component); + for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) + regulator_unregister_notifier(wm8962->supplies[i].consumer, + &wm8962->disable_nb[i]); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8962 = { + .probe = wm8962_probe, + .remove = wm8962_remove, + .set_bias_level = wm8962_set_bias_level, + .set_pll = wm8962_set_fll, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* Improve power consumption for IN4 DC measurement mode */ +static const struct reg_sequence wm8962_dc_measure[] = { + { 0xfd, 0x1 }, + { 0xcc, 0x40 }, + { 0xfd, 0 }, +}; + +static const struct regmap_config wm8962_regmap = { + .reg_bits = 16, + .val_bits = 16, + + .max_register = WM8962_MAX_REGISTER, + .reg_defaults = wm8962_reg, + .num_reg_defaults = ARRAY_SIZE(wm8962_reg), + .volatile_reg = wm8962_volatile_register, + .readable_reg = wm8962_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int wm8962_set_pdata_from_of(struct i2c_client *i2c, + struct wm8962_pdata *pdata) +{ + const struct device_node *np = i2c->dev.of_node; + u32 val32; + int i; + + if (of_property_read_bool(np, "spk-mono")) + pdata->spk_mono = true; + + if (of_property_read_u32(np, "mic-cfg", &val32) >= 0) + pdata->mic_cfg = val32; + + if (of_property_read_u32_array(np, "gpio-cfg", pdata->gpio_init, + ARRAY_SIZE(pdata->gpio_init)) >= 0) + for (i = 0; i < ARRAY_SIZE(pdata->gpio_init); i++) { + /* + * The range of GPIO register value is [0x0, 0xffff] + * While the default value of each register is 0x0 + * Any other value will be regarded as default value + */ + if (pdata->gpio_init[i] > 0xffff) + pdata->gpio_init[i] = 0x0; + } + + pdata->mclk = devm_clk_get(&i2c->dev, NULL); + + return 0; +} + +static int wm8962_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8962_pdata *pdata = dev_get_platdata(&i2c->dev); + struct wm8962_priv *wm8962; + unsigned int reg; + int ret, i, irq_pol, trigger; + + wm8962 = devm_kzalloc(&i2c->dev, sizeof(*wm8962), GFP_KERNEL); + if (wm8962 == NULL) + return -ENOMEM; + + mutex_init(&wm8962->dsp2_ena_lock); + + i2c_set_clientdata(i2c, wm8962); + + INIT_DELAYED_WORK(&wm8962->mic_work, wm8962_mic_work); + init_completion(&wm8962->fll_lock); + wm8962->irq = i2c->irq; + + /* If platform data was supplied, update the default data in priv */ + if (pdata) { + memcpy(&wm8962->pdata, pdata, sizeof(struct wm8962_pdata)); + } else if (i2c->dev.of_node) { + ret = wm8962_set_pdata_from_of(i2c, &wm8962->pdata); + if (ret != 0) + return ret; + } + + /* Mark the mclk pointer to NULL if no mclk assigned */ + if (IS_ERR(wm8962->pdata.mclk)) { + /* But do not ignore the request for probe defer */ + if (PTR_ERR(wm8962->pdata.mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + wm8962->pdata.mclk = NULL; + } + + for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) + wm8962->supplies[i].supply = wm8962_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8962->supplies), + wm8962->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies), + wm8962->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + wm8962->regmap = devm_regmap_init_i2c(i2c, &wm8962_regmap); + if (IS_ERR(wm8962->regmap)) { + ret = PTR_ERR(wm8962->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + goto err_enable; + } + + /* + * We haven't marked the chip revision as volatile due to + * sharing a register with the right input volume; explicitly + * bypass the cache to read it. + */ + regcache_cache_bypass(wm8962->regmap, true); + + ret = regmap_read(wm8962->regmap, WM8962_SOFTWARE_RESET, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read ID register\n"); + goto err_enable; + } + if (reg != 0x6243) { + dev_err(&i2c->dev, + "Device is not a WM8962, ID %x != 0x6243\n", reg); + ret = -EINVAL; + goto err_enable; + } + + ret = regmap_read(wm8962->regmap, WM8962_RIGHT_INPUT_VOLUME, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read device revision: %d\n", + ret); + goto err_enable; + } + + dev_info(&i2c->dev, "customer id %x revision %c\n", + (reg & WM8962_CUST_ID_MASK) >> WM8962_CUST_ID_SHIFT, + ((reg & WM8962_CHIP_REV_MASK) >> WM8962_CHIP_REV_SHIFT) + + 'A'); + + regcache_cache_bypass(wm8962->regmap, false); + + ret = wm8962_reset(wm8962); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + goto err_enable; + } + + /* SYSCLK defaults to on; make sure it is off so we can safely + * write to registers if the device is declocked. + */ + regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2, + WM8962_SYSCLK_ENA, 0); + + /* Ensure we have soft control over all registers */ + regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2, + WM8962_CLKREG_OVD, WM8962_CLKREG_OVD); + + /* Ensure that the oscillator and PLLs are disabled */ + regmap_update_bits(wm8962->regmap, WM8962_PLL2, + WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, + 0); + + /* Apply static configuration for GPIOs */ + for (i = 0; i < ARRAY_SIZE(wm8962->pdata.gpio_init); i++) + if (wm8962->pdata.gpio_init[i]) { + wm8962_set_gpio_mode(wm8962, i + 1); + regmap_write(wm8962->regmap, 0x200 + i, + wm8962->pdata.gpio_init[i] & 0xffff); + } + + + /* Put the speakers into mono mode? */ + if (wm8962->pdata.spk_mono) + regmap_update_bits(wm8962->regmap, WM8962_CLASS_D_CONTROL_2, + WM8962_SPK_MONO_MASK, WM8962_SPK_MONO); + + /* Micbias setup, detection enable and detection + * threasholds. */ + if (wm8962->pdata.mic_cfg) + regmap_update_bits(wm8962->regmap, WM8962_ADDITIONAL_CONTROL_4, + WM8962_MICDET_ENA | + WM8962_MICDET_THR_MASK | + WM8962_MICSHORT_THR_MASK | + WM8962_MICBIAS_LVL, + wm8962->pdata.mic_cfg); + + /* Latch volume update bits */ + regmap_update_bits(wm8962->regmap, WM8962_LEFT_INPUT_VOLUME, + WM8962_IN_VU, WM8962_IN_VU); + regmap_update_bits(wm8962->regmap, WM8962_RIGHT_INPUT_VOLUME, + WM8962_IN_VU, WM8962_IN_VU); + regmap_update_bits(wm8962->regmap, WM8962_LEFT_ADC_VOLUME, + WM8962_ADC_VU, WM8962_ADC_VU); + regmap_update_bits(wm8962->regmap, WM8962_RIGHT_ADC_VOLUME, + WM8962_ADC_VU, WM8962_ADC_VU); + regmap_update_bits(wm8962->regmap, WM8962_LEFT_DAC_VOLUME, + WM8962_DAC_VU, WM8962_DAC_VU); + regmap_update_bits(wm8962->regmap, WM8962_RIGHT_DAC_VOLUME, + WM8962_DAC_VU, WM8962_DAC_VU); + regmap_update_bits(wm8962->regmap, WM8962_SPKOUTL_VOLUME, + WM8962_SPKOUT_VU, WM8962_SPKOUT_VU); + regmap_update_bits(wm8962->regmap, WM8962_SPKOUTR_VOLUME, + WM8962_SPKOUT_VU, WM8962_SPKOUT_VU); + regmap_update_bits(wm8962->regmap, WM8962_HPOUTL_VOLUME, + WM8962_HPOUT_VU, WM8962_HPOUT_VU); + regmap_update_bits(wm8962->regmap, WM8962_HPOUTR_VOLUME, + WM8962_HPOUT_VU, WM8962_HPOUT_VU); + + /* Stereo control for EQ */ + regmap_update_bits(wm8962->regmap, WM8962_EQ1, + WM8962_EQ_SHARED_COEFF, 0); + + /* Don't debouce interrupts so we don't need SYSCLK */ + regmap_update_bits(wm8962->regmap, WM8962_IRQ_DEBOUNCE, + WM8962_FLL_LOCK_DB | WM8962_PLL3_LOCK_DB | + WM8962_PLL2_LOCK_DB | WM8962_TEMP_SHUT_DB, + 0); + + if (wm8962->pdata.in4_dc_measure) { + ret = regmap_register_patch(wm8962->regmap, + wm8962_dc_measure, + ARRAY_SIZE(wm8962_dc_measure)); + if (ret != 0) + dev_err(&i2c->dev, + "Failed to configure for DC measurement: %d\n", + ret); + } + + if (wm8962->irq) { + if (wm8962->pdata.irq_active_low) { + trigger = IRQF_TRIGGER_LOW; + irq_pol = WM8962_IRQ_POL; + } else { + trigger = IRQF_TRIGGER_HIGH; + irq_pol = 0; + } + + regmap_update_bits(wm8962->regmap, WM8962_INTERRUPT_CONTROL, + WM8962_IRQ_POL, irq_pol); + + ret = devm_request_threaded_irq(&i2c->dev, wm8962->irq, NULL, + wm8962_irq, + trigger | IRQF_ONESHOT, + "wm8962", &i2c->dev); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", + wm8962->irq, ret); + wm8962->irq = 0; + /* Non-fatal */ + } else { + /* Enable some IRQs by default */ + regmap_update_bits(wm8962->regmap, + WM8962_INTERRUPT_STATUS_2_MASK, + WM8962_FLL_LOCK_EINT | + WM8962_TEMP_SHUT_EINT | + WM8962_FIFOS_ERR_EINT, 0); + } + } + + pm_runtime_enable(&i2c->dev); + pm_request_idle(&i2c->dev); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8962, &wm8962_dai, 1); + if (ret < 0) + goto err_pm_runtime; + + regcache_cache_only(wm8962->regmap, true); + + /* The drivers should power up as needed */ + regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies); + + return 0; + +err_pm_runtime: + pm_runtime_disable(&i2c->dev); +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), wm8962->supplies); +err: + return ret; +} + +static int wm8962_i2c_remove(struct i2c_client *client) +{ + pm_runtime_disable(&client->dev); + return 0; +} + +#ifdef CONFIG_PM +static int wm8962_runtime_resume(struct device *dev) +{ + struct wm8962_priv *wm8962 = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(wm8962->pdata.mclk); + if (ret) { + dev_err(dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8962->supplies), + wm8962->supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable supplies: %d\n", ret); + goto disable_clock; + } + + regcache_cache_only(wm8962->regmap, false); + + wm8962_reset(wm8962); + + regcache_mark_dirty(wm8962->regmap); + + /* SYSCLK defaults to on; make sure it is off so we can safely + * write to registers if the device is declocked. + */ + regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2, + WM8962_SYSCLK_ENA, 0); + + /* Ensure we have soft control over all registers */ + regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2, + WM8962_CLKREG_OVD, WM8962_CLKREG_OVD); + + /* Ensure that the oscillator and PLLs are disabled */ + regmap_update_bits(wm8962->regmap, WM8962_PLL2, + WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, + 0); + + regcache_sync(wm8962->regmap); + + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, + WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, + WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA); + + /* Bias enable at 2*5k (fast start-up) */ + regmap_update_bits(wm8962->regmap, WM8962_PWR_MGMT_1, + WM8962_BIAS_ENA | WM8962_VMID_SEL_MASK, + WM8962_BIAS_ENA | 0x180); + + msleep(5); + + return 0; + +disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; +} + +static int wm8962_runtime_suspend(struct device *dev) +{ + struct wm8962_priv *wm8962 = dev_get_drvdata(dev); + + regmap_update_bits(wm8962->regmap, WM8962_PWR_MGMT_1, + WM8962_VMID_SEL_MASK | WM8962_BIAS_ENA, 0); + + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, + WM8962_STARTUP_BIAS_ENA | + WM8962_VMID_BUF_ENA, 0); + + regcache_cache_only(wm8962->regmap, true); + + regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), + wm8962->supplies); + + clk_disable_unprepare(wm8962->pdata.mclk); + + return 0; +} +#endif + +static const struct dev_pm_ops wm8962_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(wm8962_runtime_suspend, wm8962_runtime_resume, NULL) +}; + +static const struct i2c_device_id wm8962_i2c_id[] = { + { "wm8962", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8962_i2c_id); + +static const struct of_device_id wm8962_of_match[] = { + { .compatible = "wlf,wm8962", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8962_of_match); + +static struct i2c_driver wm8962_i2c_driver = { + .driver = { + .name = "wm8962", + .of_match_table = wm8962_of_match, + .pm = &wm8962_pm, + }, + .probe = wm8962_i2c_probe, + .remove = wm8962_i2c_remove, + .id_table = wm8962_i2c_id, +}; + +module_i2c_driver(wm8962_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8962 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8962.h b/sound/soc/codecs/wm8962.h new file mode 100644 index 000000000..a4a42d269 --- /dev/null +++ b/sound/soc/codecs/wm8962.h @@ -0,0 +1,3784 @@ +/* + * wm8962.h -- WM8962 ASoC driver + * + * Copyright 2010 Wolfson Microelectronics, plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8962_H +#define _WM8962_H + +#include +#include + +#define WM8962_SYSCLK_MCLK 0 +#define WM8962_SYSCLK_FLL 1 +#define WM8962_SYSCLK_PLL3 2 + +#define WM8962_FLL 1 + +#define WM8962_FLL_MCLK 1 +#define WM8962_FLL_BCLK 2 +#define WM8962_FLL_OSC 3 +#define WM8962_FLL_INT 4 + +/* + * Register values. + */ +#define WM8962_LEFT_INPUT_VOLUME 0x00 +#define WM8962_RIGHT_INPUT_VOLUME 0x01 +#define WM8962_HPOUTL_VOLUME 0x02 +#define WM8962_HPOUTR_VOLUME 0x03 +#define WM8962_CLOCKING1 0x04 +#define WM8962_ADC_DAC_CONTROL_1 0x05 +#define WM8962_ADC_DAC_CONTROL_2 0x06 +#define WM8962_AUDIO_INTERFACE_0 0x07 +#define WM8962_CLOCKING2 0x08 +#define WM8962_AUDIO_INTERFACE_1 0x09 +#define WM8962_LEFT_DAC_VOLUME 0x0A +#define WM8962_RIGHT_DAC_VOLUME 0x0B +#define WM8962_AUDIO_INTERFACE_2 0x0E +#define WM8962_SOFTWARE_RESET 0x0F +#define WM8962_ALC1 0x11 +#define WM8962_ALC2 0x12 +#define WM8962_ALC3 0x13 +#define WM8962_NOISE_GATE 0x14 +#define WM8962_LEFT_ADC_VOLUME 0x15 +#define WM8962_RIGHT_ADC_VOLUME 0x16 +#define WM8962_ADDITIONAL_CONTROL_1 0x17 +#define WM8962_ADDITIONAL_CONTROL_2 0x18 +#define WM8962_PWR_MGMT_1 0x19 +#define WM8962_PWR_MGMT_2 0x1A +#define WM8962_ADDITIONAL_CONTROL_3 0x1B +#define WM8962_ANTI_POP 0x1C +#define WM8962_CLOCKING_3 0x1E +#define WM8962_INPUT_MIXER_CONTROL_1 0x1F +#define WM8962_LEFT_INPUT_MIXER_VOLUME 0x20 +#define WM8962_RIGHT_INPUT_MIXER_VOLUME 0x21 +#define WM8962_INPUT_MIXER_CONTROL_2 0x22 +#define WM8962_INPUT_BIAS_CONTROL 0x23 +#define WM8962_LEFT_INPUT_PGA_CONTROL 0x25 +#define WM8962_RIGHT_INPUT_PGA_CONTROL 0x26 +#define WM8962_SPKOUTL_VOLUME 0x28 +#define WM8962_SPKOUTR_VOLUME 0x29 +#define WM8962_THERMAL_SHUTDOWN_STATUS 0x2F +#define WM8962_ADDITIONAL_CONTROL_4 0x30 +#define WM8962_CLASS_D_CONTROL_1 0x31 +#define WM8962_CLASS_D_CONTROL_2 0x33 +#define WM8962_CLOCKING_4 0x38 +#define WM8962_DAC_DSP_MIXING_1 0x39 +#define WM8962_DAC_DSP_MIXING_2 0x3A +#define WM8962_DC_SERVO_0 0x3C +#define WM8962_DC_SERVO_1 0x3D +#define WM8962_DC_SERVO_4 0x40 +#define WM8962_DC_SERVO_6 0x42 +#define WM8962_ANALOGUE_PGA_BIAS 0x44 +#define WM8962_ANALOGUE_HP_0 0x45 +#define WM8962_ANALOGUE_HP_2 0x47 +#define WM8962_CHARGE_PUMP_1 0x48 +#define WM8962_CHARGE_PUMP_B 0x52 +#define WM8962_WRITE_SEQUENCER_CONTROL_1 0x57 +#define WM8962_WRITE_SEQUENCER_CONTROL_2 0x5A +#define WM8962_WRITE_SEQUENCER_CONTROL_3 0x5D +#define WM8962_CONTROL_INTERFACE 0x5E +#define WM8962_MIXER_ENABLES 0x63 +#define WM8962_HEADPHONE_MIXER_1 0x64 +#define WM8962_HEADPHONE_MIXER_2 0x65 +#define WM8962_HEADPHONE_MIXER_3 0x66 +#define WM8962_HEADPHONE_MIXER_4 0x67 +#define WM8962_SPEAKER_MIXER_1 0x69 +#define WM8962_SPEAKER_MIXER_2 0x6A +#define WM8962_SPEAKER_MIXER_3 0x6B +#define WM8962_SPEAKER_MIXER_4 0x6C +#define WM8962_SPEAKER_MIXER_5 0x6D +#define WM8962_BEEP_GENERATOR_1 0x6E +#define WM8962_OSCILLATOR_TRIM_3 0x73 +#define WM8962_OSCILLATOR_TRIM_4 0x74 +#define WM8962_OSCILLATOR_TRIM_7 0x77 +#define WM8962_ANALOGUE_CLOCKING1 0x7C +#define WM8962_ANALOGUE_CLOCKING2 0x7D +#define WM8962_ANALOGUE_CLOCKING3 0x7E +#define WM8962_PLL_SOFTWARE_RESET 0x7F +#define WM8962_PLL2 0x81 +#define WM8962_PLL_4 0x83 +#define WM8962_PLL_9 0x88 +#define WM8962_PLL_10 0x89 +#define WM8962_PLL_11 0x8A +#define WM8962_PLL_12 0x8B +#define WM8962_PLL_13 0x8C +#define WM8962_PLL_14 0x8D +#define WM8962_PLL_15 0x8E +#define WM8962_PLL_16 0x8F +#define WM8962_FLL_CONTROL_1 0x9B +#define WM8962_FLL_CONTROL_2 0x9C +#define WM8962_FLL_CONTROL_3 0x9D +#define WM8962_FLL_CONTROL_5 0x9F +#define WM8962_FLL_CONTROL_6 0xA0 +#define WM8962_FLL_CONTROL_7 0xA1 +#define WM8962_FLL_CONTROL_8 0xA2 +#define WM8962_GENERAL_TEST_1 0xFC +#define WM8962_DF1 0x100 +#define WM8962_DF2 0x101 +#define WM8962_DF3 0x102 +#define WM8962_DF4 0x103 +#define WM8962_DF5 0x104 +#define WM8962_DF6 0x105 +#define WM8962_DF7 0x106 +#define WM8962_LHPF1 0x108 +#define WM8962_LHPF2 0x109 +#define WM8962_THREED1 0x10C +#define WM8962_THREED2 0x10D +#define WM8962_THREED3 0x10E +#define WM8962_THREED4 0x10F +#define WM8962_DRC_1 0x114 +#define WM8962_DRC_2 0x115 +#define WM8962_DRC_3 0x116 +#define WM8962_DRC_4 0x117 +#define WM8962_DRC_5 0x118 +#define WM8962_TLOOPBACK 0x11D +#define WM8962_EQ1 0x14F +#define WM8962_EQ2 0x150 +#define WM8962_EQ3 0x151 +#define WM8962_EQ4 0x152 +#define WM8962_EQ5 0x153 +#define WM8962_EQ6 0x154 +#define WM8962_EQ7 0x155 +#define WM8962_EQ8 0x156 +#define WM8962_EQ9 0x157 +#define WM8962_EQ10 0x158 +#define WM8962_EQ11 0x159 +#define WM8962_EQ12 0x15A +#define WM8962_EQ13 0x15B +#define WM8962_EQ14 0x15C +#define WM8962_EQ15 0x15D +#define WM8962_EQ16 0x15E +#define WM8962_EQ17 0x15F +#define WM8962_EQ18 0x160 +#define WM8962_EQ19 0x161 +#define WM8962_EQ20 0x162 +#define WM8962_EQ21 0x163 +#define WM8962_EQ22 0x164 +#define WM8962_EQ23 0x165 +#define WM8962_EQ24 0x166 +#define WM8962_EQ25 0x167 +#define WM8962_EQ26 0x168 +#define WM8962_EQ27 0x169 +#define WM8962_EQ28 0x16A +#define WM8962_EQ29 0x16B +#define WM8962_EQ30 0x16C +#define WM8962_EQ31 0x16D +#define WM8962_EQ32 0x16E +#define WM8962_EQ33 0x16F +#define WM8962_EQ34 0x170 +#define WM8962_EQ35 0x171 +#define WM8962_EQ36 0x172 +#define WM8962_EQ37 0x173 +#define WM8962_EQ38 0x174 +#define WM8962_EQ39 0x175 +#define WM8962_EQ40 0x176 +#define WM8962_EQ41 0x177 +#define WM8962_GPIO_BASE 0x200 +#define WM8962_GPIO_2 0x201 +#define WM8962_GPIO_3 0x202 +#define WM8962_GPIO_5 0x204 +#define WM8962_GPIO_6 0x205 +#define WM8962_INTERRUPT_STATUS_1 0x230 +#define WM8962_INTERRUPT_STATUS_2 0x231 +#define WM8962_INTERRUPT_STATUS_1_MASK 0x238 +#define WM8962_INTERRUPT_STATUS_2_MASK 0x239 +#define WM8962_INTERRUPT_CONTROL 0x240 +#define WM8962_IRQ_DEBOUNCE 0x248 +#define WM8962_MICINT_SOURCE_POL 0x24A +#define WM8962_DSP2_POWER_MANAGEMENT 0x300 +#define WM8962_DSP2_EXECCONTROL 0x40D +#define WM8962_WRITE_SEQUENCER_0 0x1000 +#define WM8962_WRITE_SEQUENCER_1 0x1001 +#define WM8962_WRITE_SEQUENCER_2 0x1002 +#define WM8962_WRITE_SEQUENCER_3 0x1003 +#define WM8962_WRITE_SEQUENCER_4 0x1004 +#define WM8962_WRITE_SEQUENCER_5 0x1005 +#define WM8962_WRITE_SEQUENCER_6 0x1006 +#define WM8962_WRITE_SEQUENCER_7 0x1007 +#define WM8962_WRITE_SEQUENCER_8 0x1008 +#define WM8962_WRITE_SEQUENCER_9 0x1009 +#define WM8962_WRITE_SEQUENCER_10 0x100A +#define WM8962_WRITE_SEQUENCER_11 0x100B +#define WM8962_WRITE_SEQUENCER_12 0x100C +#define WM8962_WRITE_SEQUENCER_13 0x100D +#define WM8962_WRITE_SEQUENCER_14 0x100E +#define WM8962_WRITE_SEQUENCER_15 0x100F +#define WM8962_WRITE_SEQUENCER_16 0x1010 +#define WM8962_WRITE_SEQUENCER_17 0x1011 +#define WM8962_WRITE_SEQUENCER_18 0x1012 +#define WM8962_WRITE_SEQUENCER_19 0x1013 +#define WM8962_WRITE_SEQUENCER_20 0x1014 +#define WM8962_WRITE_SEQUENCER_21 0x1015 +#define WM8962_WRITE_SEQUENCER_22 0x1016 +#define WM8962_WRITE_SEQUENCER_23 0x1017 +#define WM8962_WRITE_SEQUENCER_24 0x1018 +#define WM8962_WRITE_SEQUENCER_25 0x1019 +#define WM8962_WRITE_SEQUENCER_26 0x101A +#define WM8962_WRITE_SEQUENCER_27 0x101B +#define WM8962_WRITE_SEQUENCER_28 0x101C +#define WM8962_WRITE_SEQUENCER_29 0x101D +#define WM8962_WRITE_SEQUENCER_30 0x101E +#define WM8962_WRITE_SEQUENCER_31 0x101F +#define WM8962_WRITE_SEQUENCER_32 0x1020 +#define WM8962_WRITE_SEQUENCER_33 0x1021 +#define WM8962_WRITE_SEQUENCER_34 0x1022 +#define WM8962_WRITE_SEQUENCER_35 0x1023 +#define WM8962_WRITE_SEQUENCER_36 0x1024 +#define WM8962_WRITE_SEQUENCER_37 0x1025 +#define WM8962_WRITE_SEQUENCER_38 0x1026 +#define WM8962_WRITE_SEQUENCER_39 0x1027 +#define WM8962_WRITE_SEQUENCER_40 0x1028 +#define WM8962_WRITE_SEQUENCER_41 0x1029 +#define WM8962_WRITE_SEQUENCER_42 0x102A +#define WM8962_WRITE_SEQUENCER_43 0x102B +#define WM8962_WRITE_SEQUENCER_44 0x102C +#define WM8962_WRITE_SEQUENCER_45 0x102D +#define WM8962_WRITE_SEQUENCER_46 0x102E +#define WM8962_WRITE_SEQUENCER_47 0x102F +#define WM8962_WRITE_SEQUENCER_48 0x1030 +#define WM8962_WRITE_SEQUENCER_49 0x1031 +#define WM8962_WRITE_SEQUENCER_50 0x1032 +#define WM8962_WRITE_SEQUENCER_51 0x1033 +#define WM8962_WRITE_SEQUENCER_52 0x1034 +#define WM8962_WRITE_SEQUENCER_53 0x1035 +#define WM8962_WRITE_SEQUENCER_54 0x1036 +#define WM8962_WRITE_SEQUENCER_55 0x1037 +#define WM8962_WRITE_SEQUENCER_56 0x1038 +#define WM8962_WRITE_SEQUENCER_57 0x1039 +#define WM8962_WRITE_SEQUENCER_58 0x103A +#define WM8962_WRITE_SEQUENCER_59 0x103B +#define WM8962_WRITE_SEQUENCER_60 0x103C +#define WM8962_WRITE_SEQUENCER_61 0x103D +#define WM8962_WRITE_SEQUENCER_62 0x103E +#define WM8962_WRITE_SEQUENCER_63 0x103F +#define WM8962_WRITE_SEQUENCER_64 0x1040 +#define WM8962_WRITE_SEQUENCER_65 0x1041 +#define WM8962_WRITE_SEQUENCER_66 0x1042 +#define WM8962_WRITE_SEQUENCER_67 0x1043 +#define WM8962_WRITE_SEQUENCER_68 0x1044 +#define WM8962_WRITE_SEQUENCER_69 0x1045 +#define WM8962_WRITE_SEQUENCER_70 0x1046 +#define WM8962_WRITE_SEQUENCER_71 0x1047 +#define WM8962_WRITE_SEQUENCER_72 0x1048 +#define WM8962_WRITE_SEQUENCER_73 0x1049 +#define WM8962_WRITE_SEQUENCER_74 0x104A +#define WM8962_WRITE_SEQUENCER_75 0x104B +#define WM8962_WRITE_SEQUENCER_76 0x104C +#define WM8962_WRITE_SEQUENCER_77 0x104D +#define WM8962_WRITE_SEQUENCER_78 0x104E +#define WM8962_WRITE_SEQUENCER_79 0x104F +#define WM8962_WRITE_SEQUENCER_80 0x1050 +#define WM8962_WRITE_SEQUENCER_81 0x1051 +#define WM8962_WRITE_SEQUENCER_82 0x1052 +#define WM8962_WRITE_SEQUENCER_83 0x1053 +#define WM8962_WRITE_SEQUENCER_84 0x1054 +#define WM8962_WRITE_SEQUENCER_85 0x1055 +#define WM8962_WRITE_SEQUENCER_86 0x1056 +#define WM8962_WRITE_SEQUENCER_87 0x1057 +#define WM8962_WRITE_SEQUENCER_88 0x1058 +#define WM8962_WRITE_SEQUENCER_89 0x1059 +#define WM8962_WRITE_SEQUENCER_90 0x105A +#define WM8962_WRITE_SEQUENCER_91 0x105B +#define WM8962_WRITE_SEQUENCER_92 0x105C +#define WM8962_WRITE_SEQUENCER_93 0x105D +#define WM8962_WRITE_SEQUENCER_94 0x105E +#define WM8962_WRITE_SEQUENCER_95 0x105F +#define WM8962_WRITE_SEQUENCER_96 0x1060 +#define WM8962_WRITE_SEQUENCER_97 0x1061 +#define WM8962_WRITE_SEQUENCER_98 0x1062 +#define WM8962_WRITE_SEQUENCER_99 0x1063 +#define WM8962_WRITE_SEQUENCER_100 0x1064 +#define WM8962_WRITE_SEQUENCER_101 0x1065 +#define WM8962_WRITE_SEQUENCER_102 0x1066 +#define WM8962_WRITE_SEQUENCER_103 0x1067 +#define WM8962_WRITE_SEQUENCER_104 0x1068 +#define WM8962_WRITE_SEQUENCER_105 0x1069 +#define WM8962_WRITE_SEQUENCER_106 0x106A +#define WM8962_WRITE_SEQUENCER_107 0x106B +#define WM8962_WRITE_SEQUENCER_108 0x106C +#define WM8962_WRITE_SEQUENCER_109 0x106D +#define WM8962_WRITE_SEQUENCER_110 0x106E +#define WM8962_WRITE_SEQUENCER_111 0x106F +#define WM8962_WRITE_SEQUENCER_112 0x1070 +#define WM8962_WRITE_SEQUENCER_113 0x1071 +#define WM8962_WRITE_SEQUENCER_114 0x1072 +#define WM8962_WRITE_SEQUENCER_115 0x1073 +#define WM8962_WRITE_SEQUENCER_116 0x1074 +#define WM8962_WRITE_SEQUENCER_117 0x1075 +#define WM8962_WRITE_SEQUENCER_118 0x1076 +#define WM8962_WRITE_SEQUENCER_119 0x1077 +#define WM8962_WRITE_SEQUENCER_120 0x1078 +#define WM8962_WRITE_SEQUENCER_121 0x1079 +#define WM8962_WRITE_SEQUENCER_122 0x107A +#define WM8962_WRITE_SEQUENCER_123 0x107B +#define WM8962_WRITE_SEQUENCER_124 0x107C +#define WM8962_WRITE_SEQUENCER_125 0x107D +#define WM8962_WRITE_SEQUENCER_126 0x107E +#define WM8962_WRITE_SEQUENCER_127 0x107F +#define WM8962_WRITE_SEQUENCER_128 0x1080 +#define WM8962_WRITE_SEQUENCER_129 0x1081 +#define WM8962_WRITE_SEQUENCER_130 0x1082 +#define WM8962_WRITE_SEQUENCER_131 0x1083 +#define WM8962_WRITE_SEQUENCER_132 0x1084 +#define WM8962_WRITE_SEQUENCER_133 0x1085 +#define WM8962_WRITE_SEQUENCER_134 0x1086 +#define WM8962_WRITE_SEQUENCER_135 0x1087 +#define WM8962_WRITE_SEQUENCER_136 0x1088 +#define WM8962_WRITE_SEQUENCER_137 0x1089 +#define WM8962_WRITE_SEQUENCER_138 0x108A +#define WM8962_WRITE_SEQUENCER_139 0x108B +#define WM8962_WRITE_SEQUENCER_140 0x108C +#define WM8962_WRITE_SEQUENCER_141 0x108D +#define WM8962_WRITE_SEQUENCER_142 0x108E +#define WM8962_WRITE_SEQUENCER_143 0x108F +#define WM8962_WRITE_SEQUENCER_144 0x1090 +#define WM8962_WRITE_SEQUENCER_145 0x1091 +#define WM8962_WRITE_SEQUENCER_146 0x1092 +#define WM8962_WRITE_SEQUENCER_147 0x1093 +#define WM8962_WRITE_SEQUENCER_148 0x1094 +#define WM8962_WRITE_SEQUENCER_149 0x1095 +#define WM8962_WRITE_SEQUENCER_150 0x1096 +#define WM8962_WRITE_SEQUENCER_151 0x1097 +#define WM8962_WRITE_SEQUENCER_152 0x1098 +#define WM8962_WRITE_SEQUENCER_153 0x1099 +#define WM8962_WRITE_SEQUENCER_154 0x109A +#define WM8962_WRITE_SEQUENCER_155 0x109B +#define WM8962_WRITE_SEQUENCER_156 0x109C +#define WM8962_WRITE_SEQUENCER_157 0x109D +#define WM8962_WRITE_SEQUENCER_158 0x109E +#define WM8962_WRITE_SEQUENCER_159 0x109F +#define WM8962_WRITE_SEQUENCER_160 0x10A0 +#define WM8962_WRITE_SEQUENCER_161 0x10A1 +#define WM8962_WRITE_SEQUENCER_162 0x10A2 +#define WM8962_WRITE_SEQUENCER_163 0x10A3 +#define WM8962_WRITE_SEQUENCER_164 0x10A4 +#define WM8962_WRITE_SEQUENCER_165 0x10A5 +#define WM8962_WRITE_SEQUENCER_166 0x10A6 +#define WM8962_WRITE_SEQUENCER_167 0x10A7 +#define WM8962_WRITE_SEQUENCER_168 0x10A8 +#define WM8962_WRITE_SEQUENCER_169 0x10A9 +#define WM8962_WRITE_SEQUENCER_170 0x10AA +#define WM8962_WRITE_SEQUENCER_171 0x10AB +#define WM8962_WRITE_SEQUENCER_172 0x10AC +#define WM8962_WRITE_SEQUENCER_173 0x10AD +#define WM8962_WRITE_SEQUENCER_174 0x10AE +#define WM8962_WRITE_SEQUENCER_175 0x10AF +#define WM8962_WRITE_SEQUENCER_176 0x10B0 +#define WM8962_WRITE_SEQUENCER_177 0x10B1 +#define WM8962_WRITE_SEQUENCER_178 0x10B2 +#define WM8962_WRITE_SEQUENCER_179 0x10B3 +#define WM8962_WRITE_SEQUENCER_180 0x10B4 +#define WM8962_WRITE_SEQUENCER_181 0x10B5 +#define WM8962_WRITE_SEQUENCER_182 0x10B6 +#define WM8962_WRITE_SEQUENCER_183 0x10B7 +#define WM8962_WRITE_SEQUENCER_184 0x10B8 +#define WM8962_WRITE_SEQUENCER_185 0x10B9 +#define WM8962_WRITE_SEQUENCER_186 0x10BA +#define WM8962_WRITE_SEQUENCER_187 0x10BB +#define WM8962_WRITE_SEQUENCER_188 0x10BC +#define WM8962_WRITE_SEQUENCER_189 0x10BD +#define WM8962_WRITE_SEQUENCER_190 0x10BE +#define WM8962_WRITE_SEQUENCER_191 0x10BF +#define WM8962_WRITE_SEQUENCER_192 0x10C0 +#define WM8962_WRITE_SEQUENCER_193 0x10C1 +#define WM8962_WRITE_SEQUENCER_194 0x10C2 +#define WM8962_WRITE_SEQUENCER_195 0x10C3 +#define WM8962_WRITE_SEQUENCER_196 0x10C4 +#define WM8962_WRITE_SEQUENCER_197 0x10C5 +#define WM8962_WRITE_SEQUENCER_198 0x10C6 +#define WM8962_WRITE_SEQUENCER_199 0x10C7 +#define WM8962_WRITE_SEQUENCER_200 0x10C8 +#define WM8962_WRITE_SEQUENCER_201 0x10C9 +#define WM8962_WRITE_SEQUENCER_202 0x10CA +#define WM8962_WRITE_SEQUENCER_203 0x10CB +#define WM8962_WRITE_SEQUENCER_204 0x10CC +#define WM8962_WRITE_SEQUENCER_205 0x10CD +#define WM8962_WRITE_SEQUENCER_206 0x10CE +#define WM8962_WRITE_SEQUENCER_207 0x10CF +#define WM8962_WRITE_SEQUENCER_208 0x10D0 +#define WM8962_WRITE_SEQUENCER_209 0x10D1 +#define WM8962_WRITE_SEQUENCER_210 0x10D2 +#define WM8962_WRITE_SEQUENCER_211 0x10D3 +#define WM8962_WRITE_SEQUENCER_212 0x10D4 +#define WM8962_WRITE_SEQUENCER_213 0x10D5 +#define WM8962_WRITE_SEQUENCER_214 0x10D6 +#define WM8962_WRITE_SEQUENCER_215 0x10D7 +#define WM8962_WRITE_SEQUENCER_216 0x10D8 +#define WM8962_WRITE_SEQUENCER_217 0x10D9 +#define WM8962_WRITE_SEQUENCER_218 0x10DA +#define WM8962_WRITE_SEQUENCER_219 0x10DB +#define WM8962_WRITE_SEQUENCER_220 0x10DC +#define WM8962_WRITE_SEQUENCER_221 0x10DD +#define WM8962_WRITE_SEQUENCER_222 0x10DE +#define WM8962_WRITE_SEQUENCER_223 0x10DF +#define WM8962_WRITE_SEQUENCER_224 0x10E0 +#define WM8962_WRITE_SEQUENCER_225 0x10E1 +#define WM8962_WRITE_SEQUENCER_226 0x10E2 +#define WM8962_WRITE_SEQUENCER_227 0x10E3 +#define WM8962_WRITE_SEQUENCER_228 0x10E4 +#define WM8962_WRITE_SEQUENCER_229 0x10E5 +#define WM8962_WRITE_SEQUENCER_230 0x10E6 +#define WM8962_WRITE_SEQUENCER_231 0x10E7 +#define WM8962_WRITE_SEQUENCER_232 0x10E8 +#define WM8962_WRITE_SEQUENCER_233 0x10E9 +#define WM8962_WRITE_SEQUENCER_234 0x10EA +#define WM8962_WRITE_SEQUENCER_235 0x10EB +#define WM8962_WRITE_SEQUENCER_236 0x10EC +#define WM8962_WRITE_SEQUENCER_237 0x10ED +#define WM8962_WRITE_SEQUENCER_238 0x10EE +#define WM8962_WRITE_SEQUENCER_239 0x10EF +#define WM8962_WRITE_SEQUENCER_240 0x10F0 +#define WM8962_WRITE_SEQUENCER_241 0x10F1 +#define WM8962_WRITE_SEQUENCER_242 0x10F2 +#define WM8962_WRITE_SEQUENCER_243 0x10F3 +#define WM8962_WRITE_SEQUENCER_244 0x10F4 +#define WM8962_WRITE_SEQUENCER_245 0x10F5 +#define WM8962_WRITE_SEQUENCER_246 0x10F6 +#define WM8962_WRITE_SEQUENCER_247 0x10F7 +#define WM8962_WRITE_SEQUENCER_248 0x10F8 +#define WM8962_WRITE_SEQUENCER_249 0x10F9 +#define WM8962_WRITE_SEQUENCER_250 0x10FA +#define WM8962_WRITE_SEQUENCER_251 0x10FB +#define WM8962_WRITE_SEQUENCER_252 0x10FC +#define WM8962_WRITE_SEQUENCER_253 0x10FD +#define WM8962_WRITE_SEQUENCER_254 0x10FE +#define WM8962_WRITE_SEQUENCER_255 0x10FF +#define WM8962_WRITE_SEQUENCER_256 0x1100 +#define WM8962_WRITE_SEQUENCER_257 0x1101 +#define WM8962_WRITE_SEQUENCER_258 0x1102 +#define WM8962_WRITE_SEQUENCER_259 0x1103 +#define WM8962_WRITE_SEQUENCER_260 0x1104 +#define WM8962_WRITE_SEQUENCER_261 0x1105 +#define WM8962_WRITE_SEQUENCER_262 0x1106 +#define WM8962_WRITE_SEQUENCER_263 0x1107 +#define WM8962_WRITE_SEQUENCER_264 0x1108 +#define WM8962_WRITE_SEQUENCER_265 0x1109 +#define WM8962_WRITE_SEQUENCER_266 0x110A +#define WM8962_WRITE_SEQUENCER_267 0x110B +#define WM8962_WRITE_SEQUENCER_268 0x110C +#define WM8962_WRITE_SEQUENCER_269 0x110D +#define WM8962_WRITE_SEQUENCER_270 0x110E +#define WM8962_WRITE_SEQUENCER_271 0x110F +#define WM8962_WRITE_SEQUENCER_272 0x1110 +#define WM8962_WRITE_SEQUENCER_273 0x1111 +#define WM8962_WRITE_SEQUENCER_274 0x1112 +#define WM8962_WRITE_SEQUENCER_275 0x1113 +#define WM8962_WRITE_SEQUENCER_276 0x1114 +#define WM8962_WRITE_SEQUENCER_277 0x1115 +#define WM8962_WRITE_SEQUENCER_278 0x1116 +#define WM8962_WRITE_SEQUENCER_279 0x1117 +#define WM8962_WRITE_SEQUENCER_280 0x1118 +#define WM8962_WRITE_SEQUENCER_281 0x1119 +#define WM8962_WRITE_SEQUENCER_282 0x111A +#define WM8962_WRITE_SEQUENCER_283 0x111B +#define WM8962_WRITE_SEQUENCER_284 0x111C +#define WM8962_WRITE_SEQUENCER_285 0x111D +#define WM8962_WRITE_SEQUENCER_286 0x111E +#define WM8962_WRITE_SEQUENCER_287 0x111F +#define WM8962_WRITE_SEQUENCER_288 0x1120 +#define WM8962_WRITE_SEQUENCER_289 0x1121 +#define WM8962_WRITE_SEQUENCER_290 0x1122 +#define WM8962_WRITE_SEQUENCER_291 0x1123 +#define WM8962_WRITE_SEQUENCER_292 0x1124 +#define WM8962_WRITE_SEQUENCER_293 0x1125 +#define WM8962_WRITE_SEQUENCER_294 0x1126 +#define WM8962_WRITE_SEQUENCER_295 0x1127 +#define WM8962_WRITE_SEQUENCER_296 0x1128 +#define WM8962_WRITE_SEQUENCER_297 0x1129 +#define WM8962_WRITE_SEQUENCER_298 0x112A +#define WM8962_WRITE_SEQUENCER_299 0x112B +#define WM8962_WRITE_SEQUENCER_300 0x112C +#define WM8962_WRITE_SEQUENCER_301 0x112D +#define WM8962_WRITE_SEQUENCER_302 0x112E +#define WM8962_WRITE_SEQUENCER_303 0x112F +#define WM8962_WRITE_SEQUENCER_304 0x1130 +#define WM8962_WRITE_SEQUENCER_305 0x1131 +#define WM8962_WRITE_SEQUENCER_306 0x1132 +#define WM8962_WRITE_SEQUENCER_307 0x1133 +#define WM8962_WRITE_SEQUENCER_308 0x1134 +#define WM8962_WRITE_SEQUENCER_309 0x1135 +#define WM8962_WRITE_SEQUENCER_310 0x1136 +#define WM8962_WRITE_SEQUENCER_311 0x1137 +#define WM8962_WRITE_SEQUENCER_312 0x1138 +#define WM8962_WRITE_SEQUENCER_313 0x1139 +#define WM8962_WRITE_SEQUENCER_314 0x113A +#define WM8962_WRITE_SEQUENCER_315 0x113B +#define WM8962_WRITE_SEQUENCER_316 0x113C +#define WM8962_WRITE_SEQUENCER_317 0x113D +#define WM8962_WRITE_SEQUENCER_318 0x113E +#define WM8962_WRITE_SEQUENCER_319 0x113F +#define WM8962_WRITE_SEQUENCER_320 0x1140 +#define WM8962_WRITE_SEQUENCER_321 0x1141 +#define WM8962_WRITE_SEQUENCER_322 0x1142 +#define WM8962_WRITE_SEQUENCER_323 0x1143 +#define WM8962_WRITE_SEQUENCER_324 0x1144 +#define WM8962_WRITE_SEQUENCER_325 0x1145 +#define WM8962_WRITE_SEQUENCER_326 0x1146 +#define WM8962_WRITE_SEQUENCER_327 0x1147 +#define WM8962_WRITE_SEQUENCER_328 0x1148 +#define WM8962_WRITE_SEQUENCER_329 0x1149 +#define WM8962_WRITE_SEQUENCER_330 0x114A +#define WM8962_WRITE_SEQUENCER_331 0x114B +#define WM8962_WRITE_SEQUENCER_332 0x114C +#define WM8962_WRITE_SEQUENCER_333 0x114D +#define WM8962_WRITE_SEQUENCER_334 0x114E +#define WM8962_WRITE_SEQUENCER_335 0x114F +#define WM8962_WRITE_SEQUENCER_336 0x1150 +#define WM8962_WRITE_SEQUENCER_337 0x1151 +#define WM8962_WRITE_SEQUENCER_338 0x1152 +#define WM8962_WRITE_SEQUENCER_339 0x1153 +#define WM8962_WRITE_SEQUENCER_340 0x1154 +#define WM8962_WRITE_SEQUENCER_341 0x1155 +#define WM8962_WRITE_SEQUENCER_342 0x1156 +#define WM8962_WRITE_SEQUENCER_343 0x1157 +#define WM8962_WRITE_SEQUENCER_344 0x1158 +#define WM8962_WRITE_SEQUENCER_345 0x1159 +#define WM8962_WRITE_SEQUENCER_346 0x115A +#define WM8962_WRITE_SEQUENCER_347 0x115B +#define WM8962_WRITE_SEQUENCER_348 0x115C +#define WM8962_WRITE_SEQUENCER_349 0x115D +#define WM8962_WRITE_SEQUENCER_350 0x115E +#define WM8962_WRITE_SEQUENCER_351 0x115F +#define WM8962_WRITE_SEQUENCER_352 0x1160 +#define WM8962_WRITE_SEQUENCER_353 0x1161 +#define WM8962_WRITE_SEQUENCER_354 0x1162 +#define WM8962_WRITE_SEQUENCER_355 0x1163 +#define WM8962_WRITE_SEQUENCER_356 0x1164 +#define WM8962_WRITE_SEQUENCER_357 0x1165 +#define WM8962_WRITE_SEQUENCER_358 0x1166 +#define WM8962_WRITE_SEQUENCER_359 0x1167 +#define WM8962_WRITE_SEQUENCER_360 0x1168 +#define WM8962_WRITE_SEQUENCER_361 0x1169 +#define WM8962_WRITE_SEQUENCER_362 0x116A +#define WM8962_WRITE_SEQUENCER_363 0x116B +#define WM8962_WRITE_SEQUENCER_364 0x116C +#define WM8962_WRITE_SEQUENCER_365 0x116D +#define WM8962_WRITE_SEQUENCER_366 0x116E +#define WM8962_WRITE_SEQUENCER_367 0x116F +#define WM8962_WRITE_SEQUENCER_368 0x1170 +#define WM8962_WRITE_SEQUENCER_369 0x1171 +#define WM8962_WRITE_SEQUENCER_370 0x1172 +#define WM8962_WRITE_SEQUENCER_371 0x1173 +#define WM8962_WRITE_SEQUENCER_372 0x1174 +#define WM8962_WRITE_SEQUENCER_373 0x1175 +#define WM8962_WRITE_SEQUENCER_374 0x1176 +#define WM8962_WRITE_SEQUENCER_375 0x1177 +#define WM8962_WRITE_SEQUENCER_376 0x1178 +#define WM8962_WRITE_SEQUENCER_377 0x1179 +#define WM8962_WRITE_SEQUENCER_378 0x117A +#define WM8962_WRITE_SEQUENCER_379 0x117B +#define WM8962_WRITE_SEQUENCER_380 0x117C +#define WM8962_WRITE_SEQUENCER_381 0x117D +#define WM8962_WRITE_SEQUENCER_382 0x117E +#define WM8962_WRITE_SEQUENCER_383 0x117F +#define WM8962_WRITE_SEQUENCER_384 0x1180 +#define WM8962_WRITE_SEQUENCER_385 0x1181 +#define WM8962_WRITE_SEQUENCER_386 0x1182 +#define WM8962_WRITE_SEQUENCER_387 0x1183 +#define WM8962_WRITE_SEQUENCER_388 0x1184 +#define WM8962_WRITE_SEQUENCER_389 0x1185 +#define WM8962_WRITE_SEQUENCER_390 0x1186 +#define WM8962_WRITE_SEQUENCER_391 0x1187 +#define WM8962_WRITE_SEQUENCER_392 0x1188 +#define WM8962_WRITE_SEQUENCER_393 0x1189 +#define WM8962_WRITE_SEQUENCER_394 0x118A +#define WM8962_WRITE_SEQUENCER_395 0x118B +#define WM8962_WRITE_SEQUENCER_396 0x118C +#define WM8962_WRITE_SEQUENCER_397 0x118D +#define WM8962_WRITE_SEQUENCER_398 0x118E +#define WM8962_WRITE_SEQUENCER_399 0x118F +#define WM8962_WRITE_SEQUENCER_400 0x1190 +#define WM8962_WRITE_SEQUENCER_401 0x1191 +#define WM8962_WRITE_SEQUENCER_402 0x1192 +#define WM8962_WRITE_SEQUENCER_403 0x1193 +#define WM8962_WRITE_SEQUENCER_404 0x1194 +#define WM8962_WRITE_SEQUENCER_405 0x1195 +#define WM8962_WRITE_SEQUENCER_406 0x1196 +#define WM8962_WRITE_SEQUENCER_407 0x1197 +#define WM8962_WRITE_SEQUENCER_408 0x1198 +#define WM8962_WRITE_SEQUENCER_409 0x1199 +#define WM8962_WRITE_SEQUENCER_410 0x119A +#define WM8962_WRITE_SEQUENCER_411 0x119B +#define WM8962_WRITE_SEQUENCER_412 0x119C +#define WM8962_WRITE_SEQUENCER_413 0x119D +#define WM8962_WRITE_SEQUENCER_414 0x119E +#define WM8962_WRITE_SEQUENCER_415 0x119F +#define WM8962_WRITE_SEQUENCER_416 0x11A0 +#define WM8962_WRITE_SEQUENCER_417 0x11A1 +#define WM8962_WRITE_SEQUENCER_418 0x11A2 +#define WM8962_WRITE_SEQUENCER_419 0x11A3 +#define WM8962_WRITE_SEQUENCER_420 0x11A4 +#define WM8962_WRITE_SEQUENCER_421 0x11A5 +#define WM8962_WRITE_SEQUENCER_422 0x11A6 +#define WM8962_WRITE_SEQUENCER_423 0x11A7 +#define WM8962_WRITE_SEQUENCER_424 0x11A8 +#define WM8962_WRITE_SEQUENCER_425 0x11A9 +#define WM8962_WRITE_SEQUENCER_426 0x11AA +#define WM8962_WRITE_SEQUENCER_427 0x11AB +#define WM8962_WRITE_SEQUENCER_428 0x11AC +#define WM8962_WRITE_SEQUENCER_429 0x11AD +#define WM8962_WRITE_SEQUENCER_430 0x11AE +#define WM8962_WRITE_SEQUENCER_431 0x11AF +#define WM8962_WRITE_SEQUENCER_432 0x11B0 +#define WM8962_WRITE_SEQUENCER_433 0x11B1 +#define WM8962_WRITE_SEQUENCER_434 0x11B2 +#define WM8962_WRITE_SEQUENCER_435 0x11B3 +#define WM8962_WRITE_SEQUENCER_436 0x11B4 +#define WM8962_WRITE_SEQUENCER_437 0x11B5 +#define WM8962_WRITE_SEQUENCER_438 0x11B6 +#define WM8962_WRITE_SEQUENCER_439 0x11B7 +#define WM8962_WRITE_SEQUENCER_440 0x11B8 +#define WM8962_WRITE_SEQUENCER_441 0x11B9 +#define WM8962_WRITE_SEQUENCER_442 0x11BA +#define WM8962_WRITE_SEQUENCER_443 0x11BB +#define WM8962_WRITE_SEQUENCER_444 0x11BC +#define WM8962_WRITE_SEQUENCER_445 0x11BD +#define WM8962_WRITE_SEQUENCER_446 0x11BE +#define WM8962_WRITE_SEQUENCER_447 0x11BF +#define WM8962_WRITE_SEQUENCER_448 0x11C0 +#define WM8962_WRITE_SEQUENCER_449 0x11C1 +#define WM8962_WRITE_SEQUENCER_450 0x11C2 +#define WM8962_WRITE_SEQUENCER_451 0x11C3 +#define WM8962_WRITE_SEQUENCER_452 0x11C4 +#define WM8962_WRITE_SEQUENCER_453 0x11C5 +#define WM8962_WRITE_SEQUENCER_454 0x11C6 +#define WM8962_WRITE_SEQUENCER_455 0x11C7 +#define WM8962_WRITE_SEQUENCER_456 0x11C8 +#define WM8962_WRITE_SEQUENCER_457 0x11C9 +#define WM8962_WRITE_SEQUENCER_458 0x11CA +#define WM8962_WRITE_SEQUENCER_459 0x11CB +#define WM8962_WRITE_SEQUENCER_460 0x11CC +#define WM8962_WRITE_SEQUENCER_461 0x11CD +#define WM8962_WRITE_SEQUENCER_462 0x11CE +#define WM8962_WRITE_SEQUENCER_463 0x11CF +#define WM8962_WRITE_SEQUENCER_464 0x11D0 +#define WM8962_WRITE_SEQUENCER_465 0x11D1 +#define WM8962_WRITE_SEQUENCER_466 0x11D2 +#define WM8962_WRITE_SEQUENCER_467 0x11D3 +#define WM8962_WRITE_SEQUENCER_468 0x11D4 +#define WM8962_WRITE_SEQUENCER_469 0x11D5 +#define WM8962_WRITE_SEQUENCER_470 0x11D6 +#define WM8962_WRITE_SEQUENCER_471 0x11D7 +#define WM8962_WRITE_SEQUENCER_472 0x11D8 +#define WM8962_WRITE_SEQUENCER_473 0x11D9 +#define WM8962_WRITE_SEQUENCER_474 0x11DA +#define WM8962_WRITE_SEQUENCER_475 0x11DB +#define WM8962_WRITE_SEQUENCER_476 0x11DC +#define WM8962_WRITE_SEQUENCER_477 0x11DD +#define WM8962_WRITE_SEQUENCER_478 0x11DE +#define WM8962_WRITE_SEQUENCER_479 0x11DF +#define WM8962_WRITE_SEQUENCER_480 0x11E0 +#define WM8962_WRITE_SEQUENCER_481 0x11E1 +#define WM8962_WRITE_SEQUENCER_482 0x11E2 +#define WM8962_WRITE_SEQUENCER_483 0x11E3 +#define WM8962_WRITE_SEQUENCER_484 0x11E4 +#define WM8962_WRITE_SEQUENCER_485 0x11E5 +#define WM8962_WRITE_SEQUENCER_486 0x11E6 +#define WM8962_WRITE_SEQUENCER_487 0x11E7 +#define WM8962_WRITE_SEQUENCER_488 0x11E8 +#define WM8962_WRITE_SEQUENCER_489 0x11E9 +#define WM8962_WRITE_SEQUENCER_490 0x11EA +#define WM8962_WRITE_SEQUENCER_491 0x11EB +#define WM8962_WRITE_SEQUENCER_492 0x11EC +#define WM8962_WRITE_SEQUENCER_493 0x11ED +#define WM8962_WRITE_SEQUENCER_494 0x11EE +#define WM8962_WRITE_SEQUENCER_495 0x11EF +#define WM8962_WRITE_SEQUENCER_496 0x11F0 +#define WM8962_WRITE_SEQUENCER_497 0x11F1 +#define WM8962_WRITE_SEQUENCER_498 0x11F2 +#define WM8962_WRITE_SEQUENCER_499 0x11F3 +#define WM8962_WRITE_SEQUENCER_500 0x11F4 +#define WM8962_WRITE_SEQUENCER_501 0x11F5 +#define WM8962_WRITE_SEQUENCER_502 0x11F6 +#define WM8962_WRITE_SEQUENCER_503 0x11F7 +#define WM8962_WRITE_SEQUENCER_504 0x11F8 +#define WM8962_WRITE_SEQUENCER_505 0x11F9 +#define WM8962_WRITE_SEQUENCER_506 0x11FA +#define WM8962_WRITE_SEQUENCER_507 0x11FB +#define WM8962_WRITE_SEQUENCER_508 0x11FC +#define WM8962_WRITE_SEQUENCER_509 0x11FD +#define WM8962_WRITE_SEQUENCER_510 0x11FE +#define WM8962_WRITE_SEQUENCER_511 0x11FF +#define WM8962_DSP2_INSTRUCTION_RAM_0 0x2000 +#define WM8962_DSP2_ADDRESS_RAM_2 0x2400 +#define WM8962_DSP2_ADDRESS_RAM_1 0x2401 +#define WM8962_DSP2_ADDRESS_RAM_0 0x2402 +#define WM8962_DSP2_DATA1_RAM_1 0x3000 +#define WM8962_DSP2_DATA1_RAM_0 0x3001 +#define WM8962_DSP2_DATA2_RAM_1 0x3400 +#define WM8962_DSP2_DATA2_RAM_0 0x3401 +#define WM8962_DSP2_DATA3_RAM_1 0x3800 +#define WM8962_DSP2_DATA3_RAM_0 0x3801 +#define WM8962_DSP2_COEFF_RAM_0 0x3C00 +#define WM8962_RETUNEADC_SHARED_COEFF_1 0x4000 +#define WM8962_RETUNEADC_SHARED_COEFF_0 0x4001 +#define WM8962_RETUNEDAC_SHARED_COEFF_1 0x4002 +#define WM8962_RETUNEDAC_SHARED_COEFF_0 0x4003 +#define WM8962_SOUNDSTAGE_ENABLES_1 0x4004 +#define WM8962_SOUNDSTAGE_ENABLES_0 0x4005 +#define WM8962_HDBASS_AI_1 0x4200 +#define WM8962_HDBASS_AI_0 0x4201 +#define WM8962_HDBASS_AR_1 0x4202 +#define WM8962_HDBASS_AR_0 0x4203 +#define WM8962_HDBASS_B_1 0x4204 +#define WM8962_HDBASS_B_0 0x4205 +#define WM8962_HDBASS_K_1 0x4206 +#define WM8962_HDBASS_K_0 0x4207 +#define WM8962_HDBASS_N1_1 0x4208 +#define WM8962_HDBASS_N1_0 0x4209 +#define WM8962_HDBASS_N2_1 0x420A +#define WM8962_HDBASS_N2_0 0x420B +#define WM8962_HDBASS_N3_1 0x420C +#define WM8962_HDBASS_N3_0 0x420D +#define WM8962_HDBASS_N4_1 0x420E +#define WM8962_HDBASS_N4_0 0x420F +#define WM8962_HDBASS_N5_1 0x4210 +#define WM8962_HDBASS_N5_0 0x4211 +#define WM8962_HDBASS_X1_1 0x4212 +#define WM8962_HDBASS_X1_0 0x4213 +#define WM8962_HDBASS_X2_1 0x4214 +#define WM8962_HDBASS_X2_0 0x4215 +#define WM8962_HDBASS_X3_1 0x4216 +#define WM8962_HDBASS_X3_0 0x4217 +#define WM8962_HDBASS_ATK_1 0x4218 +#define WM8962_HDBASS_ATK_0 0x4219 +#define WM8962_HDBASS_DCY_1 0x421A +#define WM8962_HDBASS_DCY_0 0x421B +#define WM8962_HDBASS_PG_1 0x421C +#define WM8962_HDBASS_PG_0 0x421D +#define WM8962_HPF_C_1 0x4400 +#define WM8962_HPF_C_0 0x4401 +#define WM8962_ADCL_RETUNE_C1_1 0x4600 +#define WM8962_ADCL_RETUNE_C1_0 0x4601 +#define WM8962_ADCL_RETUNE_C2_1 0x4602 +#define WM8962_ADCL_RETUNE_C2_0 0x4603 +#define WM8962_ADCL_RETUNE_C3_1 0x4604 +#define WM8962_ADCL_RETUNE_C3_0 0x4605 +#define WM8962_ADCL_RETUNE_C4_1 0x4606 +#define WM8962_ADCL_RETUNE_C4_0 0x4607 +#define WM8962_ADCL_RETUNE_C5_1 0x4608 +#define WM8962_ADCL_RETUNE_C5_0 0x4609 +#define WM8962_ADCL_RETUNE_C6_1 0x460A +#define WM8962_ADCL_RETUNE_C6_0 0x460B +#define WM8962_ADCL_RETUNE_C7_1 0x460C +#define WM8962_ADCL_RETUNE_C7_0 0x460D +#define WM8962_ADCL_RETUNE_C8_1 0x460E +#define WM8962_ADCL_RETUNE_C8_0 0x460F +#define WM8962_ADCL_RETUNE_C9_1 0x4610 +#define WM8962_ADCL_RETUNE_C9_0 0x4611 +#define WM8962_ADCL_RETUNE_C10_1 0x4612 +#define WM8962_ADCL_RETUNE_C10_0 0x4613 +#define WM8962_ADCL_RETUNE_C11_1 0x4614 +#define WM8962_ADCL_RETUNE_C11_0 0x4615 +#define WM8962_ADCL_RETUNE_C12_1 0x4616 +#define WM8962_ADCL_RETUNE_C12_0 0x4617 +#define WM8962_ADCL_RETUNE_C13_1 0x4618 +#define WM8962_ADCL_RETUNE_C13_0 0x4619 +#define WM8962_ADCL_RETUNE_C14_1 0x461A +#define WM8962_ADCL_RETUNE_C14_0 0x461B +#define WM8962_ADCL_RETUNE_C15_1 0x461C +#define WM8962_ADCL_RETUNE_C15_0 0x461D +#define WM8962_ADCL_RETUNE_C16_1 0x461E +#define WM8962_ADCL_RETUNE_C16_0 0x461F +#define WM8962_ADCL_RETUNE_C17_1 0x4620 +#define WM8962_ADCL_RETUNE_C17_0 0x4621 +#define WM8962_ADCL_RETUNE_C18_1 0x4622 +#define WM8962_ADCL_RETUNE_C18_0 0x4623 +#define WM8962_ADCL_RETUNE_C19_1 0x4624 +#define WM8962_ADCL_RETUNE_C19_0 0x4625 +#define WM8962_ADCL_RETUNE_C20_1 0x4626 +#define WM8962_ADCL_RETUNE_C20_0 0x4627 +#define WM8962_ADCL_RETUNE_C21_1 0x4628 +#define WM8962_ADCL_RETUNE_C21_0 0x4629 +#define WM8962_ADCL_RETUNE_C22_1 0x462A +#define WM8962_ADCL_RETUNE_C22_0 0x462B +#define WM8962_ADCL_RETUNE_C23_1 0x462C +#define WM8962_ADCL_RETUNE_C23_0 0x462D +#define WM8962_ADCL_RETUNE_C24_1 0x462E +#define WM8962_ADCL_RETUNE_C24_0 0x462F +#define WM8962_ADCL_RETUNE_C25_1 0x4630 +#define WM8962_ADCL_RETUNE_C25_0 0x4631 +#define WM8962_ADCL_RETUNE_C26_1 0x4632 +#define WM8962_ADCL_RETUNE_C26_0 0x4633 +#define WM8962_ADCL_RETUNE_C27_1 0x4634 +#define WM8962_ADCL_RETUNE_C27_0 0x4635 +#define WM8962_ADCL_RETUNE_C28_1 0x4636 +#define WM8962_ADCL_RETUNE_C28_0 0x4637 +#define WM8962_ADCL_RETUNE_C29_1 0x4638 +#define WM8962_ADCL_RETUNE_C29_0 0x4639 +#define WM8962_ADCL_RETUNE_C30_1 0x463A +#define WM8962_ADCL_RETUNE_C30_0 0x463B +#define WM8962_ADCL_RETUNE_C31_1 0x463C +#define WM8962_ADCL_RETUNE_C31_0 0x463D +#define WM8962_ADCL_RETUNE_C32_1 0x463E +#define WM8962_ADCL_RETUNE_C32_0 0x463F +#define WM8962_RETUNEADC_PG2_1 0x4800 +#define WM8962_RETUNEADC_PG2_0 0x4801 +#define WM8962_RETUNEADC_PG_1 0x4802 +#define WM8962_RETUNEADC_PG_0 0x4803 +#define WM8962_ADCR_RETUNE_C1_1 0x4A00 +#define WM8962_ADCR_RETUNE_C1_0 0x4A01 +#define WM8962_ADCR_RETUNE_C2_1 0x4A02 +#define WM8962_ADCR_RETUNE_C2_0 0x4A03 +#define WM8962_ADCR_RETUNE_C3_1 0x4A04 +#define WM8962_ADCR_RETUNE_C3_0 0x4A05 +#define WM8962_ADCR_RETUNE_C4_1 0x4A06 +#define WM8962_ADCR_RETUNE_C4_0 0x4A07 +#define WM8962_ADCR_RETUNE_C5_1 0x4A08 +#define WM8962_ADCR_RETUNE_C5_0 0x4A09 +#define WM8962_ADCR_RETUNE_C6_1 0x4A0A +#define WM8962_ADCR_RETUNE_C6_0 0x4A0B +#define WM8962_ADCR_RETUNE_C7_1 0x4A0C +#define WM8962_ADCR_RETUNE_C7_0 0x4A0D +#define WM8962_ADCR_RETUNE_C8_1 0x4A0E +#define WM8962_ADCR_RETUNE_C8_0 0x4A0F +#define WM8962_ADCR_RETUNE_C9_1 0x4A10 +#define WM8962_ADCR_RETUNE_C9_0 0x4A11 +#define WM8962_ADCR_RETUNE_C10_1 0x4A12 +#define WM8962_ADCR_RETUNE_C10_0 0x4A13 +#define WM8962_ADCR_RETUNE_C11_1 0x4A14 +#define WM8962_ADCR_RETUNE_C11_0 0x4A15 +#define WM8962_ADCR_RETUNE_C12_1 0x4A16 +#define WM8962_ADCR_RETUNE_C12_0 0x4A17 +#define WM8962_ADCR_RETUNE_C13_1 0x4A18 +#define WM8962_ADCR_RETUNE_C13_0 0x4A19 +#define WM8962_ADCR_RETUNE_C14_1 0x4A1A +#define WM8962_ADCR_RETUNE_C14_0 0x4A1B +#define WM8962_ADCR_RETUNE_C15_1 0x4A1C +#define WM8962_ADCR_RETUNE_C15_0 0x4A1D +#define WM8962_ADCR_RETUNE_C16_1 0x4A1E +#define WM8962_ADCR_RETUNE_C16_0 0x4A1F +#define WM8962_ADCR_RETUNE_C17_1 0x4A20 +#define WM8962_ADCR_RETUNE_C17_0 0x4A21 +#define WM8962_ADCR_RETUNE_C18_1 0x4A22 +#define WM8962_ADCR_RETUNE_C18_0 0x4A23 +#define WM8962_ADCR_RETUNE_C19_1 0x4A24 +#define WM8962_ADCR_RETUNE_C19_0 0x4A25 +#define WM8962_ADCR_RETUNE_C20_1 0x4A26 +#define WM8962_ADCR_RETUNE_C20_0 0x4A27 +#define WM8962_ADCR_RETUNE_C21_1 0x4A28 +#define WM8962_ADCR_RETUNE_C21_0 0x4A29 +#define WM8962_ADCR_RETUNE_C22_1 0x4A2A +#define WM8962_ADCR_RETUNE_C22_0 0x4A2B +#define WM8962_ADCR_RETUNE_C23_1 0x4A2C +#define WM8962_ADCR_RETUNE_C23_0 0x4A2D +#define WM8962_ADCR_RETUNE_C24_1 0x4A2E +#define WM8962_ADCR_RETUNE_C24_0 0x4A2F +#define WM8962_ADCR_RETUNE_C25_1 0x4A30 +#define WM8962_ADCR_RETUNE_C25_0 0x4A31 +#define WM8962_ADCR_RETUNE_C26_1 0x4A32 +#define WM8962_ADCR_RETUNE_C26_0 0x4A33 +#define WM8962_ADCR_RETUNE_C27_1 0x4A34 +#define WM8962_ADCR_RETUNE_C27_0 0x4A35 +#define WM8962_ADCR_RETUNE_C28_1 0x4A36 +#define WM8962_ADCR_RETUNE_C28_0 0x4A37 +#define WM8962_ADCR_RETUNE_C29_1 0x4A38 +#define WM8962_ADCR_RETUNE_C29_0 0x4A39 +#define WM8962_ADCR_RETUNE_C30_1 0x4A3A +#define WM8962_ADCR_RETUNE_C30_0 0x4A3B +#define WM8962_ADCR_RETUNE_C31_1 0x4A3C +#define WM8962_ADCR_RETUNE_C31_0 0x4A3D +#define WM8962_ADCR_RETUNE_C32_1 0x4A3E +#define WM8962_ADCR_RETUNE_C32_0 0x4A3F +#define WM8962_DACL_RETUNE_C1_1 0x4C00 +#define WM8962_DACL_RETUNE_C1_0 0x4C01 +#define WM8962_DACL_RETUNE_C2_1 0x4C02 +#define WM8962_DACL_RETUNE_C2_0 0x4C03 +#define WM8962_DACL_RETUNE_C3_1 0x4C04 +#define WM8962_DACL_RETUNE_C3_0 0x4C05 +#define WM8962_DACL_RETUNE_C4_1 0x4C06 +#define WM8962_DACL_RETUNE_C4_0 0x4C07 +#define WM8962_DACL_RETUNE_C5_1 0x4C08 +#define WM8962_DACL_RETUNE_C5_0 0x4C09 +#define WM8962_DACL_RETUNE_C6_1 0x4C0A +#define WM8962_DACL_RETUNE_C6_0 0x4C0B +#define WM8962_DACL_RETUNE_C7_1 0x4C0C +#define WM8962_DACL_RETUNE_C7_0 0x4C0D +#define WM8962_DACL_RETUNE_C8_1 0x4C0E +#define WM8962_DACL_RETUNE_C8_0 0x4C0F +#define WM8962_DACL_RETUNE_C9_1 0x4C10 +#define WM8962_DACL_RETUNE_C9_0 0x4C11 +#define WM8962_DACL_RETUNE_C10_1 0x4C12 +#define WM8962_DACL_RETUNE_C10_0 0x4C13 +#define WM8962_DACL_RETUNE_C11_1 0x4C14 +#define WM8962_DACL_RETUNE_C11_0 0x4C15 +#define WM8962_DACL_RETUNE_C12_1 0x4C16 +#define WM8962_DACL_RETUNE_C12_0 0x4C17 +#define WM8962_DACL_RETUNE_C13_1 0x4C18 +#define WM8962_DACL_RETUNE_C13_0 0x4C19 +#define WM8962_DACL_RETUNE_C14_1 0x4C1A +#define WM8962_DACL_RETUNE_C14_0 0x4C1B +#define WM8962_DACL_RETUNE_C15_1 0x4C1C +#define WM8962_DACL_RETUNE_C15_0 0x4C1D +#define WM8962_DACL_RETUNE_C16_1 0x4C1E +#define WM8962_DACL_RETUNE_C16_0 0x4C1F +#define WM8962_DACL_RETUNE_C17_1 0x4C20 +#define WM8962_DACL_RETUNE_C17_0 0x4C21 +#define WM8962_DACL_RETUNE_C18_1 0x4C22 +#define WM8962_DACL_RETUNE_C18_0 0x4C23 +#define WM8962_DACL_RETUNE_C19_1 0x4C24 +#define WM8962_DACL_RETUNE_C19_0 0x4C25 +#define WM8962_DACL_RETUNE_C20_1 0x4C26 +#define WM8962_DACL_RETUNE_C20_0 0x4C27 +#define WM8962_DACL_RETUNE_C21_1 0x4C28 +#define WM8962_DACL_RETUNE_C21_0 0x4C29 +#define WM8962_DACL_RETUNE_C22_1 0x4C2A +#define WM8962_DACL_RETUNE_C22_0 0x4C2B +#define WM8962_DACL_RETUNE_C23_1 0x4C2C +#define WM8962_DACL_RETUNE_C23_0 0x4C2D +#define WM8962_DACL_RETUNE_C24_1 0x4C2E +#define WM8962_DACL_RETUNE_C24_0 0x4C2F +#define WM8962_DACL_RETUNE_C25_1 0x4C30 +#define WM8962_DACL_RETUNE_C25_0 0x4C31 +#define WM8962_DACL_RETUNE_C26_1 0x4C32 +#define WM8962_DACL_RETUNE_C26_0 0x4C33 +#define WM8962_DACL_RETUNE_C27_1 0x4C34 +#define WM8962_DACL_RETUNE_C27_0 0x4C35 +#define WM8962_DACL_RETUNE_C28_1 0x4C36 +#define WM8962_DACL_RETUNE_C28_0 0x4C37 +#define WM8962_DACL_RETUNE_C29_1 0x4C38 +#define WM8962_DACL_RETUNE_C29_0 0x4C39 +#define WM8962_DACL_RETUNE_C30_1 0x4C3A +#define WM8962_DACL_RETUNE_C30_0 0x4C3B +#define WM8962_DACL_RETUNE_C31_1 0x4C3C +#define WM8962_DACL_RETUNE_C31_0 0x4C3D +#define WM8962_DACL_RETUNE_C32_1 0x4C3E +#define WM8962_DACL_RETUNE_C32_0 0x4C3F +#define WM8962_RETUNEDAC_PG2_1 0x4E00 +#define WM8962_RETUNEDAC_PG2_0 0x4E01 +#define WM8962_RETUNEDAC_PG_1 0x4E02 +#define WM8962_RETUNEDAC_PG_0 0x4E03 +#define WM8962_DACR_RETUNE_C1_1 0x5000 +#define WM8962_DACR_RETUNE_C1_0 0x5001 +#define WM8962_DACR_RETUNE_C2_1 0x5002 +#define WM8962_DACR_RETUNE_C2_0 0x5003 +#define WM8962_DACR_RETUNE_C3_1 0x5004 +#define WM8962_DACR_RETUNE_C3_0 0x5005 +#define WM8962_DACR_RETUNE_C4_1 0x5006 +#define WM8962_DACR_RETUNE_C4_0 0x5007 +#define WM8962_DACR_RETUNE_C5_1 0x5008 +#define WM8962_DACR_RETUNE_C5_0 0x5009 +#define WM8962_DACR_RETUNE_C6_1 0x500A +#define WM8962_DACR_RETUNE_C6_0 0x500B +#define WM8962_DACR_RETUNE_C7_1 0x500C +#define WM8962_DACR_RETUNE_C7_0 0x500D +#define WM8962_DACR_RETUNE_C8_1 0x500E +#define WM8962_DACR_RETUNE_C8_0 0x500F +#define WM8962_DACR_RETUNE_C9_1 0x5010 +#define WM8962_DACR_RETUNE_C9_0 0x5011 +#define WM8962_DACR_RETUNE_C10_1 0x5012 +#define WM8962_DACR_RETUNE_C10_0 0x5013 +#define WM8962_DACR_RETUNE_C11_1 0x5014 +#define WM8962_DACR_RETUNE_C11_0 0x5015 +#define WM8962_DACR_RETUNE_C12_1 0x5016 +#define WM8962_DACR_RETUNE_C12_0 0x5017 +#define WM8962_DACR_RETUNE_C13_1 0x5018 +#define WM8962_DACR_RETUNE_C13_0 0x5019 +#define WM8962_DACR_RETUNE_C14_1 0x501A +#define WM8962_DACR_RETUNE_C14_0 0x501B +#define WM8962_DACR_RETUNE_C15_1 0x501C +#define WM8962_DACR_RETUNE_C15_0 0x501D +#define WM8962_DACR_RETUNE_C16_1 0x501E +#define WM8962_DACR_RETUNE_C16_0 0x501F +#define WM8962_DACR_RETUNE_C17_1 0x5020 +#define WM8962_DACR_RETUNE_C17_0 0x5021 +#define WM8962_DACR_RETUNE_C18_1 0x5022 +#define WM8962_DACR_RETUNE_C18_0 0x5023 +#define WM8962_DACR_RETUNE_C19_1 0x5024 +#define WM8962_DACR_RETUNE_C19_0 0x5025 +#define WM8962_DACR_RETUNE_C20_1 0x5026 +#define WM8962_DACR_RETUNE_C20_0 0x5027 +#define WM8962_DACR_RETUNE_C21_1 0x5028 +#define WM8962_DACR_RETUNE_C21_0 0x5029 +#define WM8962_DACR_RETUNE_C22_1 0x502A +#define WM8962_DACR_RETUNE_C22_0 0x502B +#define WM8962_DACR_RETUNE_C23_1 0x502C +#define WM8962_DACR_RETUNE_C23_0 0x502D +#define WM8962_DACR_RETUNE_C24_1 0x502E +#define WM8962_DACR_RETUNE_C24_0 0x502F +#define WM8962_DACR_RETUNE_C25_1 0x5030 +#define WM8962_DACR_RETUNE_C25_0 0x5031 +#define WM8962_DACR_RETUNE_C26_1 0x5032 +#define WM8962_DACR_RETUNE_C26_0 0x5033 +#define WM8962_DACR_RETUNE_C27_1 0x5034 +#define WM8962_DACR_RETUNE_C27_0 0x5035 +#define WM8962_DACR_RETUNE_C28_1 0x5036 +#define WM8962_DACR_RETUNE_C28_0 0x5037 +#define WM8962_DACR_RETUNE_C29_1 0x5038 +#define WM8962_DACR_RETUNE_C29_0 0x5039 +#define WM8962_DACR_RETUNE_C30_1 0x503A +#define WM8962_DACR_RETUNE_C30_0 0x503B +#define WM8962_DACR_RETUNE_C31_1 0x503C +#define WM8962_DACR_RETUNE_C31_0 0x503D +#define WM8962_DACR_RETUNE_C32_1 0x503E +#define WM8962_DACR_RETUNE_C32_0 0x503F +#define WM8962_VSS_XHD2_1 0x5200 +#define WM8962_VSS_XHD2_0 0x5201 +#define WM8962_VSS_XHD3_1 0x5202 +#define WM8962_VSS_XHD3_0 0x5203 +#define WM8962_VSS_XHN1_1 0x5204 +#define WM8962_VSS_XHN1_0 0x5205 +#define WM8962_VSS_XHN2_1 0x5206 +#define WM8962_VSS_XHN2_0 0x5207 +#define WM8962_VSS_XHN3_1 0x5208 +#define WM8962_VSS_XHN3_0 0x5209 +#define WM8962_VSS_XLA_1 0x520A +#define WM8962_VSS_XLA_0 0x520B +#define WM8962_VSS_XLB_1 0x520C +#define WM8962_VSS_XLB_0 0x520D +#define WM8962_VSS_XLG_1 0x520E +#define WM8962_VSS_XLG_0 0x520F +#define WM8962_VSS_PG2_1 0x5210 +#define WM8962_VSS_PG2_0 0x5211 +#define WM8962_VSS_PG_1 0x5212 +#define WM8962_VSS_PG_0 0x5213 +#define WM8962_VSS_XTD1_1 0x5214 +#define WM8962_VSS_XTD1_0 0x5215 +#define WM8962_VSS_XTD2_1 0x5216 +#define WM8962_VSS_XTD2_0 0x5217 +#define WM8962_VSS_XTD3_1 0x5218 +#define WM8962_VSS_XTD3_0 0x5219 +#define WM8962_VSS_XTD4_1 0x521A +#define WM8962_VSS_XTD4_0 0x521B +#define WM8962_VSS_XTD5_1 0x521C +#define WM8962_VSS_XTD5_0 0x521D +#define WM8962_VSS_XTD6_1 0x521E +#define WM8962_VSS_XTD6_0 0x521F +#define WM8962_VSS_XTD7_1 0x5220 +#define WM8962_VSS_XTD7_0 0x5221 +#define WM8962_VSS_XTD8_1 0x5222 +#define WM8962_VSS_XTD8_0 0x5223 +#define WM8962_VSS_XTD9_1 0x5224 +#define WM8962_VSS_XTD9_0 0x5225 +#define WM8962_VSS_XTD10_1 0x5226 +#define WM8962_VSS_XTD10_0 0x5227 +#define WM8962_VSS_XTD11_1 0x5228 +#define WM8962_VSS_XTD11_0 0x5229 +#define WM8962_VSS_XTD12_1 0x522A +#define WM8962_VSS_XTD12_0 0x522B +#define WM8962_VSS_XTD13_1 0x522C +#define WM8962_VSS_XTD13_0 0x522D +#define WM8962_VSS_XTD14_1 0x522E +#define WM8962_VSS_XTD14_0 0x522F +#define WM8962_VSS_XTD15_1 0x5230 +#define WM8962_VSS_XTD15_0 0x5231 +#define WM8962_VSS_XTD16_1 0x5232 +#define WM8962_VSS_XTD16_0 0x5233 +#define WM8962_VSS_XTD17_1 0x5234 +#define WM8962_VSS_XTD17_0 0x5235 +#define WM8962_VSS_XTD18_1 0x5236 +#define WM8962_VSS_XTD18_0 0x5237 +#define WM8962_VSS_XTD19_1 0x5238 +#define WM8962_VSS_XTD19_0 0x5239 +#define WM8962_VSS_XTD20_1 0x523A +#define WM8962_VSS_XTD20_0 0x523B +#define WM8962_VSS_XTD21_1 0x523C +#define WM8962_VSS_XTD21_0 0x523D +#define WM8962_VSS_XTD22_1 0x523E +#define WM8962_VSS_XTD22_0 0x523F +#define WM8962_VSS_XTD23_1 0x5240 +#define WM8962_VSS_XTD23_0 0x5241 +#define WM8962_VSS_XTD24_1 0x5242 +#define WM8962_VSS_XTD24_0 0x5243 +#define WM8962_VSS_XTD25_1 0x5244 +#define WM8962_VSS_XTD25_0 0x5245 +#define WM8962_VSS_XTD26_1 0x5246 +#define WM8962_VSS_XTD26_0 0x5247 +#define WM8962_VSS_XTD27_1 0x5248 +#define WM8962_VSS_XTD27_0 0x5249 +#define WM8962_VSS_XTD28_1 0x524A +#define WM8962_VSS_XTD28_0 0x524B +#define WM8962_VSS_XTD29_1 0x524C +#define WM8962_VSS_XTD29_0 0x524D +#define WM8962_VSS_XTD30_1 0x524E +#define WM8962_VSS_XTD30_0 0x524F +#define WM8962_VSS_XTD31_1 0x5250 +#define WM8962_VSS_XTD31_0 0x5251 +#define WM8962_VSS_XTD32_1 0x5252 +#define WM8962_VSS_XTD32_0 0x5253 +#define WM8962_VSS_XTS1_1 0x5254 +#define WM8962_VSS_XTS1_0 0x5255 +#define WM8962_VSS_XTS2_1 0x5256 +#define WM8962_VSS_XTS2_0 0x5257 +#define WM8962_VSS_XTS3_1 0x5258 +#define WM8962_VSS_XTS3_0 0x5259 +#define WM8962_VSS_XTS4_1 0x525A +#define WM8962_VSS_XTS4_0 0x525B +#define WM8962_VSS_XTS5_1 0x525C +#define WM8962_VSS_XTS5_0 0x525D +#define WM8962_VSS_XTS6_1 0x525E +#define WM8962_VSS_XTS6_0 0x525F +#define WM8962_VSS_XTS7_1 0x5260 +#define WM8962_VSS_XTS7_0 0x5261 +#define WM8962_VSS_XTS8_1 0x5262 +#define WM8962_VSS_XTS8_0 0x5263 +#define WM8962_VSS_XTS9_1 0x5264 +#define WM8962_VSS_XTS9_0 0x5265 +#define WM8962_VSS_XTS10_1 0x5266 +#define WM8962_VSS_XTS10_0 0x5267 +#define WM8962_VSS_XTS11_1 0x5268 +#define WM8962_VSS_XTS11_0 0x5269 +#define WM8962_VSS_XTS12_1 0x526A +#define WM8962_VSS_XTS12_0 0x526B +#define WM8962_VSS_XTS13_1 0x526C +#define WM8962_VSS_XTS13_0 0x526D +#define WM8962_VSS_XTS14_1 0x526E +#define WM8962_VSS_XTS14_0 0x526F +#define WM8962_VSS_XTS15_1 0x5270 +#define WM8962_VSS_XTS15_0 0x5271 +#define WM8962_VSS_XTS16_1 0x5272 +#define WM8962_VSS_XTS16_0 0x5273 +#define WM8962_VSS_XTS17_1 0x5274 +#define WM8962_VSS_XTS17_0 0x5275 +#define WM8962_VSS_XTS18_1 0x5276 +#define WM8962_VSS_XTS18_0 0x5277 +#define WM8962_VSS_XTS19_1 0x5278 +#define WM8962_VSS_XTS19_0 0x5279 +#define WM8962_VSS_XTS20_1 0x527A +#define WM8962_VSS_XTS20_0 0x527B +#define WM8962_VSS_XTS21_1 0x527C +#define WM8962_VSS_XTS21_0 0x527D +#define WM8962_VSS_XTS22_1 0x527E +#define WM8962_VSS_XTS22_0 0x527F +#define WM8962_VSS_XTS23_1 0x5280 +#define WM8962_VSS_XTS23_0 0x5281 +#define WM8962_VSS_XTS24_1 0x5282 +#define WM8962_VSS_XTS24_0 0x5283 +#define WM8962_VSS_XTS25_1 0x5284 +#define WM8962_VSS_XTS25_0 0x5285 +#define WM8962_VSS_XTS26_1 0x5286 +#define WM8962_VSS_XTS26_0 0x5287 +#define WM8962_VSS_XTS27_1 0x5288 +#define WM8962_VSS_XTS27_0 0x5289 +#define WM8962_VSS_XTS28_1 0x528A +#define WM8962_VSS_XTS28_0 0x528B +#define WM8962_VSS_XTS29_1 0x528C +#define WM8962_VSS_XTS29_0 0x528D +#define WM8962_VSS_XTS30_1 0x528E +#define WM8962_VSS_XTS30_0 0x528F +#define WM8962_VSS_XTS31_1 0x5290 +#define WM8962_VSS_XTS31_0 0x5291 +#define WM8962_VSS_XTS32_1 0x5292 +#define WM8962_VSS_XTS32_0 0x5293 + +#define WM8962_REGISTER_COUNT 1138 +#define WM8962_MAX_REGISTER 0x5293 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Left Input volume + */ +#define WM8962_IN_VU 0x0100 /* IN_VU */ +#define WM8962_IN_VU_MASK 0x0100 /* IN_VU */ +#define WM8962_IN_VU_SHIFT 8 /* IN_VU */ +#define WM8962_IN_VU_WIDTH 1 /* IN_VU */ +#define WM8962_INPGAL_MUTE 0x0080 /* INPGAL_MUTE */ +#define WM8962_INPGAL_MUTE_MASK 0x0080 /* INPGAL_MUTE */ +#define WM8962_INPGAL_MUTE_SHIFT 7 /* INPGAL_MUTE */ +#define WM8962_INPGAL_MUTE_WIDTH 1 /* INPGAL_MUTE */ +#define WM8962_INL_ZC 0x0040 /* INL_ZC */ +#define WM8962_INL_ZC_MASK 0x0040 /* INL_ZC */ +#define WM8962_INL_ZC_SHIFT 6 /* INL_ZC */ +#define WM8962_INL_ZC_WIDTH 1 /* INL_ZC */ +#define WM8962_INL_VOL_MASK 0x003F /* INL_VOL - [5:0] */ +#define WM8962_INL_VOL_SHIFT 0 /* INL_VOL - [5:0] */ +#define WM8962_INL_VOL_WIDTH 6 /* INL_VOL - [5:0] */ + +/* + * R1 (0x01) - Right Input volume + */ +#define WM8962_CUST_ID_MASK 0xF000 /* CUST_ID - [15:12] */ +#define WM8962_CUST_ID_SHIFT 12 /* CUST_ID - [15:12] */ +#define WM8962_CUST_ID_WIDTH 4 /* CUST_ID - [15:12] */ +#define WM8962_CHIP_REV_MASK 0x0E00 /* CHIP_REV - [11:9] */ +#define WM8962_CHIP_REV_SHIFT 9 /* CHIP_REV - [11:9] */ +#define WM8962_CHIP_REV_WIDTH 3 /* CHIP_REV - [11:9] */ +#define WM8962_IN_VU 0x0100 /* IN_VU */ +#define WM8962_IN_VU_MASK 0x0100 /* IN_VU */ +#define WM8962_IN_VU_SHIFT 8 /* IN_VU */ +#define WM8962_IN_VU_WIDTH 1 /* IN_VU */ +#define WM8962_INPGAR_MUTE 0x0080 /* INPGAR_MUTE */ +#define WM8962_INPGAR_MUTE_MASK 0x0080 /* INPGAR_MUTE */ +#define WM8962_INPGAR_MUTE_SHIFT 7 /* INPGAR_MUTE */ +#define WM8962_INPGAR_MUTE_WIDTH 1 /* INPGAR_MUTE */ +#define WM8962_INR_ZC 0x0040 /* INR_ZC */ +#define WM8962_INR_ZC_MASK 0x0040 /* INR_ZC */ +#define WM8962_INR_ZC_SHIFT 6 /* INR_ZC */ +#define WM8962_INR_ZC_WIDTH 1 /* INR_ZC */ +#define WM8962_INR_VOL_MASK 0x003F /* INR_VOL - [5:0] */ +#define WM8962_INR_VOL_SHIFT 0 /* INR_VOL - [5:0] */ +#define WM8962_INR_VOL_WIDTH 6 /* INR_VOL - [5:0] */ + +/* + * R2 (0x02) - HPOUTL volume + */ +#define WM8962_HPOUT_VU 0x0100 /* HPOUT_VU */ +#define WM8962_HPOUT_VU_MASK 0x0100 /* HPOUT_VU */ +#define WM8962_HPOUT_VU_SHIFT 8 /* HPOUT_VU */ +#define WM8962_HPOUT_VU_WIDTH 1 /* HPOUT_VU */ +#define WM8962_HPOUTL_ZC 0x0080 /* HPOUTL_ZC */ +#define WM8962_HPOUTL_ZC_MASK 0x0080 /* HPOUTL_ZC */ +#define WM8962_HPOUTL_ZC_SHIFT 7 /* HPOUTL_ZC */ +#define WM8962_HPOUTL_ZC_WIDTH 1 /* HPOUTL_ZC */ +#define WM8962_HPOUTL_VOL_MASK 0x007F /* HPOUTL_VOL - [6:0] */ +#define WM8962_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [6:0] */ +#define WM8962_HPOUTL_VOL_WIDTH 7 /* HPOUTL_VOL - [6:0] */ + +/* + * R3 (0x03) - HPOUTR volume + */ +#define WM8962_HPOUT_VU 0x0100 /* HPOUT_VU */ +#define WM8962_HPOUT_VU_MASK 0x0100 /* HPOUT_VU */ +#define WM8962_HPOUT_VU_SHIFT 8 /* HPOUT_VU */ +#define WM8962_HPOUT_VU_WIDTH 1 /* HPOUT_VU */ +#define WM8962_HPOUTR_ZC 0x0080 /* HPOUTR_ZC */ +#define WM8962_HPOUTR_ZC_MASK 0x0080 /* HPOUTR_ZC */ +#define WM8962_HPOUTR_ZC_SHIFT 7 /* HPOUTR_ZC */ +#define WM8962_HPOUTR_ZC_WIDTH 1 /* HPOUTR_ZC */ +#define WM8962_HPOUTR_VOL_MASK 0x007F /* HPOUTR_VOL - [6:0] */ +#define WM8962_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [6:0] */ +#define WM8962_HPOUTR_VOL_WIDTH 7 /* HPOUTR_VOL - [6:0] */ + +/* + * R4 (0x04) - Clocking1 + */ +#define WM8962_DSPCLK_DIV_MASK 0x0600 /* DSPCLK_DIV - [10:9] */ +#define WM8962_DSPCLK_DIV_SHIFT 9 /* DSPCLK_DIV - [10:9] */ +#define WM8962_DSPCLK_DIV_WIDTH 2 /* DSPCLK_DIV - [10:9] */ +#define WM8962_ADCSYS_CLK_DIV_MASK 0x01C0 /* ADCSYS_CLK_DIV - [8:6] */ +#define WM8962_ADCSYS_CLK_DIV_SHIFT 6 /* ADCSYS_CLK_DIV - [8:6] */ +#define WM8962_ADCSYS_CLK_DIV_WIDTH 3 /* ADCSYS_CLK_DIV - [8:6] */ +#define WM8962_DACSYS_CLK_DIV_MASK 0x0038 /* DACSYS_CLK_DIV - [5:3] */ +#define WM8962_DACSYS_CLK_DIV_SHIFT 3 /* DACSYS_CLK_DIV - [5:3] */ +#define WM8962_DACSYS_CLK_DIV_WIDTH 3 /* DACSYS_CLK_DIV - [5:3] */ +#define WM8962_MCLKDIV_MASK 0x0006 /* MCLKDIV - [2:1] */ +#define WM8962_MCLKDIV_SHIFT 1 /* MCLKDIV - [2:1] */ +#define WM8962_MCLKDIV_WIDTH 2 /* MCLKDIV - [2:1] */ + +/* + * R5 (0x05) - ADC & DAC Control 1 + */ +#define WM8962_ADCR_DAT_INV 0x0040 /* ADCR_DAT_INV */ +#define WM8962_ADCR_DAT_INV_MASK 0x0040 /* ADCR_DAT_INV */ +#define WM8962_ADCR_DAT_INV_SHIFT 6 /* ADCR_DAT_INV */ +#define WM8962_ADCR_DAT_INV_WIDTH 1 /* ADCR_DAT_INV */ +#define WM8962_ADCL_DAT_INV 0x0020 /* ADCL_DAT_INV */ +#define WM8962_ADCL_DAT_INV_MASK 0x0020 /* ADCL_DAT_INV */ +#define WM8962_ADCL_DAT_INV_SHIFT 5 /* ADCL_DAT_INV */ +#define WM8962_ADCL_DAT_INV_WIDTH 1 /* ADCL_DAT_INV */ +#define WM8962_DAC_MUTE_RAMP 0x0010 /* DAC_MUTE_RAMP */ +#define WM8962_DAC_MUTE_RAMP_MASK 0x0010 /* DAC_MUTE_RAMP */ +#define WM8962_DAC_MUTE_RAMP_SHIFT 4 /* DAC_MUTE_RAMP */ +#define WM8962_DAC_MUTE_RAMP_WIDTH 1 /* DAC_MUTE_RAMP */ +#define WM8962_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM8962_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM8962_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM8962_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8962_DAC_DEEMP_MASK 0x0006 /* DAC_DEEMP - [2:1] */ +#define WM8962_DAC_DEEMP_SHIFT 1 /* DAC_DEEMP - [2:1] */ +#define WM8962_DAC_DEEMP_WIDTH 2 /* DAC_DEEMP - [2:1] */ +#define WM8962_ADC_HPF_DIS 0x0001 /* ADC_HPF_DIS */ +#define WM8962_ADC_HPF_DIS_MASK 0x0001 /* ADC_HPF_DIS */ +#define WM8962_ADC_HPF_DIS_SHIFT 0 /* ADC_HPF_DIS */ +#define WM8962_ADC_HPF_DIS_WIDTH 1 /* ADC_HPF_DIS */ + +/* + * R6 (0x06) - ADC & DAC Control 2 + */ +#define WM8962_ADC_HPF_SR_MASK 0x3000 /* ADC_HPF_SR - [13:12] */ +#define WM8962_ADC_HPF_SR_SHIFT 12 /* ADC_HPF_SR - [13:12] */ +#define WM8962_ADC_HPF_SR_WIDTH 2 /* ADC_HPF_SR - [13:12] */ +#define WM8962_ADC_HPF_MODE 0x0400 /* ADC_HPF_MODE */ +#define WM8962_ADC_HPF_MODE_MASK 0x0400 /* ADC_HPF_MODE */ +#define WM8962_ADC_HPF_MODE_SHIFT 10 /* ADC_HPF_MODE */ +#define WM8962_ADC_HPF_MODE_WIDTH 1 /* ADC_HPF_MODE */ +#define WM8962_ADC_HPF_CUT_MASK 0x0380 /* ADC_HPF_CUT - [9:7] */ +#define WM8962_ADC_HPF_CUT_SHIFT 7 /* ADC_HPF_CUT - [9:7] */ +#define WM8962_ADC_HPF_CUT_WIDTH 3 /* ADC_HPF_CUT - [9:7] */ +#define WM8962_DACR_DAT_INV 0x0040 /* DACR_DAT_INV */ +#define WM8962_DACR_DAT_INV_MASK 0x0040 /* DACR_DAT_INV */ +#define WM8962_DACR_DAT_INV_SHIFT 6 /* DACR_DAT_INV */ +#define WM8962_DACR_DAT_INV_WIDTH 1 /* DACR_DAT_INV */ +#define WM8962_DACL_DAT_INV 0x0020 /* DACL_DAT_INV */ +#define WM8962_DACL_DAT_INV_MASK 0x0020 /* DACL_DAT_INV */ +#define WM8962_DACL_DAT_INV_SHIFT 5 /* DACL_DAT_INV */ +#define WM8962_DACL_DAT_INV_WIDTH 1 /* DACL_DAT_INV */ +#define WM8962_DAC_UNMUTE_RAMP 0x0008 /* DAC_UNMUTE_RAMP */ +#define WM8962_DAC_UNMUTE_RAMP_MASK 0x0008 /* DAC_UNMUTE_RAMP */ +#define WM8962_DAC_UNMUTE_RAMP_SHIFT 3 /* DAC_UNMUTE_RAMP */ +#define WM8962_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */ +#define WM8962_DAC_MUTERATE 0x0004 /* DAC_MUTERATE */ +#define WM8962_DAC_MUTERATE_MASK 0x0004 /* DAC_MUTERATE */ +#define WM8962_DAC_MUTERATE_SHIFT 2 /* DAC_MUTERATE */ +#define WM8962_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8962_DAC_HP 0x0001 /* DAC_HP */ +#define WM8962_DAC_HP_MASK 0x0001 /* DAC_HP */ +#define WM8962_DAC_HP_SHIFT 0 /* DAC_HP */ +#define WM8962_DAC_HP_WIDTH 1 /* DAC_HP */ + +/* + * R7 (0x07) - Audio Interface 0 + */ +#define WM8962_AIFDAC_TDM_MODE 0x1000 /* AIFDAC_TDM_MODE */ +#define WM8962_AIFDAC_TDM_MODE_MASK 0x1000 /* AIFDAC_TDM_MODE */ +#define WM8962_AIFDAC_TDM_MODE_SHIFT 12 /* AIFDAC_TDM_MODE */ +#define WM8962_AIFDAC_TDM_MODE_WIDTH 1 /* AIFDAC_TDM_MODE */ +#define WM8962_AIFDAC_TDM_SLOT 0x0800 /* AIFDAC_TDM_SLOT */ +#define WM8962_AIFDAC_TDM_SLOT_MASK 0x0800 /* AIFDAC_TDM_SLOT */ +#define WM8962_AIFDAC_TDM_SLOT_SHIFT 11 /* AIFDAC_TDM_SLOT */ +#define WM8962_AIFDAC_TDM_SLOT_WIDTH 1 /* AIFDAC_TDM_SLOT */ +#define WM8962_AIFADC_TDM_MODE 0x0400 /* AIFADC_TDM_MODE */ +#define WM8962_AIFADC_TDM_MODE_MASK 0x0400 /* AIFADC_TDM_MODE */ +#define WM8962_AIFADC_TDM_MODE_SHIFT 10 /* AIFADC_TDM_MODE */ +#define WM8962_AIFADC_TDM_MODE_WIDTH 1 /* AIFADC_TDM_MODE */ +#define WM8962_AIFADC_TDM_SLOT 0x0200 /* AIFADC_TDM_SLOT */ +#define WM8962_AIFADC_TDM_SLOT_MASK 0x0200 /* AIFADC_TDM_SLOT */ +#define WM8962_AIFADC_TDM_SLOT_SHIFT 9 /* AIFADC_TDM_SLOT */ +#define WM8962_AIFADC_TDM_SLOT_WIDTH 1 /* AIFADC_TDM_SLOT */ +#define WM8962_ADC_LRSWAP 0x0100 /* ADC_LRSWAP */ +#define WM8962_ADC_LRSWAP_MASK 0x0100 /* ADC_LRSWAP */ +#define WM8962_ADC_LRSWAP_SHIFT 8 /* ADC_LRSWAP */ +#define WM8962_ADC_LRSWAP_WIDTH 1 /* ADC_LRSWAP */ +#define WM8962_BCLK_INV 0x0080 /* BCLK_INV */ +#define WM8962_BCLK_INV_MASK 0x0080 /* BCLK_INV */ +#define WM8962_BCLK_INV_SHIFT 7 /* BCLK_INV */ +#define WM8962_BCLK_INV_WIDTH 1 /* BCLK_INV */ +#define WM8962_MSTR 0x0040 /* MSTR */ +#define WM8962_MSTR_MASK 0x0040 /* MSTR */ +#define WM8962_MSTR_SHIFT 6 /* MSTR */ +#define WM8962_MSTR_WIDTH 1 /* MSTR */ +#define WM8962_DAC_LRSWAP 0x0020 /* DAC_LRSWAP */ +#define WM8962_DAC_LRSWAP_MASK 0x0020 /* DAC_LRSWAP */ +#define WM8962_DAC_LRSWAP_SHIFT 5 /* DAC_LRSWAP */ +#define WM8962_DAC_LRSWAP_WIDTH 1 /* DAC_LRSWAP */ +#define WM8962_LRCLK_INV 0x0010 /* LRCLK_INV */ +#define WM8962_LRCLK_INV_MASK 0x0010 /* LRCLK_INV */ +#define WM8962_LRCLK_INV_SHIFT 4 /* LRCLK_INV */ +#define WM8962_LRCLK_INV_WIDTH 1 /* LRCLK_INV */ +#define WM8962_WL_MASK 0x000C /* WL - [3:2] */ +#define WM8962_WL_SHIFT 2 /* WL - [3:2] */ +#define WM8962_WL_WIDTH 2 /* WL - [3:2] */ +#define WM8962_FMT_MASK 0x0003 /* FMT - [1:0] */ +#define WM8962_FMT_SHIFT 0 /* FMT - [1:0] */ +#define WM8962_FMT_WIDTH 2 /* FMT - [1:0] */ + +/* + * R8 (0x08) - Clocking2 + */ +#define WM8962_CLKREG_OVD 0x0800 /* CLKREG_OVD */ +#define WM8962_CLKREG_OVD_MASK 0x0800 /* CLKREG_OVD */ +#define WM8962_CLKREG_OVD_SHIFT 11 /* CLKREG_OVD */ +#define WM8962_CLKREG_OVD_WIDTH 1 /* CLKREG_OVD */ +#define WM8962_SYSCLK_SRC_MASK 0x0600 /* SYSCLK_SRC - [10:9] */ +#define WM8962_SYSCLK_SRC_SHIFT 9 /* SYSCLK_SRC - [10:9] */ +#define WM8962_SYSCLK_SRC_WIDTH 2 /* SYSCLK_SRC - [10:9] */ +#define WM8962_CLASSD_CLK_DIV_MASK 0x01C0 /* CLASSD_CLK_DIV - [8:6] */ +#define WM8962_CLASSD_CLK_DIV_SHIFT 6 /* CLASSD_CLK_DIV - [8:6] */ +#define WM8962_CLASSD_CLK_DIV_WIDTH 3 /* CLASSD_CLK_DIV - [8:6] */ +#define WM8962_SYSCLK_ENA 0x0020 /* SYSCLK_ENA */ +#define WM8962_SYSCLK_ENA_MASK 0x0020 /* SYSCLK_ENA */ +#define WM8962_SYSCLK_ENA_SHIFT 5 /* SYSCLK_ENA */ +#define WM8962_SYSCLK_ENA_WIDTH 1 /* SYSCLK_ENA */ +#define WM8962_BCLK_DIV_MASK 0x000F /* BCLK_DIV - [3:0] */ +#define WM8962_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [3:0] */ +#define WM8962_BCLK_DIV_WIDTH 4 /* BCLK_DIV - [3:0] */ + +/* + * R9 (0x09) - Audio Interface 1 + */ +#define WM8962_AUTOMUTE_STS 0x0800 /* AUTOMUTE_STS */ +#define WM8962_AUTOMUTE_STS_MASK 0x0800 /* AUTOMUTE_STS */ +#define WM8962_AUTOMUTE_STS_SHIFT 11 /* AUTOMUTE_STS */ +#define WM8962_AUTOMUTE_STS_WIDTH 1 /* AUTOMUTE_STS */ +#define WM8962_DAC_AUTOMUTE_SAMPLES_MASK 0x0300 /* DAC_AUTOMUTE_SAMPLES - [9:8] */ +#define WM8962_DAC_AUTOMUTE_SAMPLES_SHIFT 8 /* DAC_AUTOMUTE_SAMPLES - [9:8] */ +#define WM8962_DAC_AUTOMUTE_SAMPLES_WIDTH 2 /* DAC_AUTOMUTE_SAMPLES - [9:8] */ +#define WM8962_DAC_AUTOMUTE 0x0080 /* DAC_AUTOMUTE */ +#define WM8962_DAC_AUTOMUTE_MASK 0x0080 /* DAC_AUTOMUTE */ +#define WM8962_DAC_AUTOMUTE_SHIFT 7 /* DAC_AUTOMUTE */ +#define WM8962_DAC_AUTOMUTE_WIDTH 1 /* DAC_AUTOMUTE */ +#define WM8962_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8962_DAC_COMP_MASK 0x0010 /* DAC_COMP */ +#define WM8962_DAC_COMP_SHIFT 4 /* DAC_COMP */ +#define WM8962_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8962_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8962_DAC_COMPMODE_MASK 0x0008 /* DAC_COMPMODE */ +#define WM8962_DAC_COMPMODE_SHIFT 3 /* DAC_COMPMODE */ +#define WM8962_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ +#define WM8962_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8962_ADC_COMP_MASK 0x0004 /* ADC_COMP */ +#define WM8962_ADC_COMP_SHIFT 2 /* ADC_COMP */ +#define WM8962_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8962_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8962_ADC_COMPMODE_MASK 0x0002 /* ADC_COMPMODE */ +#define WM8962_ADC_COMPMODE_SHIFT 1 /* ADC_COMPMODE */ +#define WM8962_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8962_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8962_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8962_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8962_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R10 (0x0A) - Left DAC volume + */ +#define WM8962_DAC_VU 0x0100 /* DAC_VU */ +#define WM8962_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8962_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8962_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8962_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8962_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8962_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R11 (0x0B) - Right DAC volume + */ +#define WM8962_DAC_VU 0x0100 /* DAC_VU */ +#define WM8962_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8962_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8962_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8962_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8962_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8962_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R14 (0x0E) - Audio Interface 2 + */ +#define WM8962_AIF_RATE_MASK 0x07FF /* AIF_RATE - [10:0] */ +#define WM8962_AIF_RATE_SHIFT 0 /* AIF_RATE - [10:0] */ +#define WM8962_AIF_RATE_WIDTH 11 /* AIF_RATE - [10:0] */ + +/* + * R15 (0x0F) - Software Reset + */ +#define WM8962_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM8962_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM8962_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R17 (0x11) - ALC1 + */ +#define WM8962_ALC_INACTIVE_ENA 0x0400 /* ALC_INACTIVE_ENA */ +#define WM8962_ALC_INACTIVE_ENA_MASK 0x0400 /* ALC_INACTIVE_ENA */ +#define WM8962_ALC_INACTIVE_ENA_SHIFT 10 /* ALC_INACTIVE_ENA */ +#define WM8962_ALC_INACTIVE_ENA_WIDTH 1 /* ALC_INACTIVE_ENA */ +#define WM8962_ALC_LVL_MODE 0x0200 /* ALC_LVL_MODE */ +#define WM8962_ALC_LVL_MODE_MASK 0x0200 /* ALC_LVL_MODE */ +#define WM8962_ALC_LVL_MODE_SHIFT 9 /* ALC_LVL_MODE */ +#define WM8962_ALC_LVL_MODE_WIDTH 1 /* ALC_LVL_MODE */ +#define WM8962_ALCL_ENA 0x0100 /* ALCL_ENA */ +#define WM8962_ALCL_ENA_MASK 0x0100 /* ALCL_ENA */ +#define WM8962_ALCL_ENA_SHIFT 8 /* ALCL_ENA */ +#define WM8962_ALCL_ENA_WIDTH 1 /* ALCL_ENA */ +#define WM8962_ALCR_ENA 0x0080 /* ALCR_ENA */ +#define WM8962_ALCR_ENA_MASK 0x0080 /* ALCR_ENA */ +#define WM8962_ALCR_ENA_SHIFT 7 /* ALCR_ENA */ +#define WM8962_ALCR_ENA_WIDTH 1 /* ALCR_ENA */ +#define WM8962_ALC_MAXGAIN_MASK 0x0070 /* ALC_MAXGAIN - [6:4] */ +#define WM8962_ALC_MAXGAIN_SHIFT 4 /* ALC_MAXGAIN - [6:4] */ +#define WM8962_ALC_MAXGAIN_WIDTH 3 /* ALC_MAXGAIN - [6:4] */ +#define WM8962_ALC_LVL_MASK 0x000F /* ALC_LVL - [3:0] */ +#define WM8962_ALC_LVL_SHIFT 0 /* ALC_LVL - [3:0] */ +#define WM8962_ALC_LVL_WIDTH 4 /* ALC_LVL - [3:0] */ + +/* + * R18 (0x12) - ALC2 + */ +#define WM8962_ALC_LOCK_STS 0x8000 /* ALC_LOCK_STS */ +#define WM8962_ALC_LOCK_STS_MASK 0x8000 /* ALC_LOCK_STS */ +#define WM8962_ALC_LOCK_STS_SHIFT 15 /* ALC_LOCK_STS */ +#define WM8962_ALC_LOCK_STS_WIDTH 1 /* ALC_LOCK_STS */ +#define WM8962_ALC_THRESH_STS 0x4000 /* ALC_THRESH_STS */ +#define WM8962_ALC_THRESH_STS_MASK 0x4000 /* ALC_THRESH_STS */ +#define WM8962_ALC_THRESH_STS_SHIFT 14 /* ALC_THRESH_STS */ +#define WM8962_ALC_THRESH_STS_WIDTH 1 /* ALC_THRESH_STS */ +#define WM8962_ALC_SAT_STS 0x2000 /* ALC_SAT_STS */ +#define WM8962_ALC_SAT_STS_MASK 0x2000 /* ALC_SAT_STS */ +#define WM8962_ALC_SAT_STS_SHIFT 13 /* ALC_SAT_STS */ +#define WM8962_ALC_SAT_STS_WIDTH 1 /* ALC_SAT_STS */ +#define WM8962_ALC_PKOVR_STS 0x1000 /* ALC_PKOVR_STS */ +#define WM8962_ALC_PKOVR_STS_MASK 0x1000 /* ALC_PKOVR_STS */ +#define WM8962_ALC_PKOVR_STS_SHIFT 12 /* ALC_PKOVR_STS */ +#define WM8962_ALC_PKOVR_STS_WIDTH 1 /* ALC_PKOVR_STS */ +#define WM8962_ALC_NGATE_STS 0x0800 /* ALC_NGATE_STS */ +#define WM8962_ALC_NGATE_STS_MASK 0x0800 /* ALC_NGATE_STS */ +#define WM8962_ALC_NGATE_STS_SHIFT 11 /* ALC_NGATE_STS */ +#define WM8962_ALC_NGATE_STS_WIDTH 1 /* ALC_NGATE_STS */ +#define WM8962_ALC_ZC 0x0080 /* ALC_ZC */ +#define WM8962_ALC_ZC_MASK 0x0080 /* ALC_ZC */ +#define WM8962_ALC_ZC_SHIFT 7 /* ALC_ZC */ +#define WM8962_ALC_ZC_WIDTH 1 /* ALC_ZC */ +#define WM8962_ALC_MINGAIN_MASK 0x0070 /* ALC_MINGAIN - [6:4] */ +#define WM8962_ALC_MINGAIN_SHIFT 4 /* ALC_MINGAIN - [6:4] */ +#define WM8962_ALC_MINGAIN_WIDTH 3 /* ALC_MINGAIN - [6:4] */ +#define WM8962_ALC_HLD_MASK 0x000F /* ALC_HLD - [3:0] */ +#define WM8962_ALC_HLD_SHIFT 0 /* ALC_HLD - [3:0] */ +#define WM8962_ALC_HLD_WIDTH 4 /* ALC_HLD - [3:0] */ + +/* + * R19 (0x13) - ALC3 + */ +#define WM8962_ALC_NGATE_GAIN_MASK 0x1C00 /* ALC_NGATE_GAIN - [12:10] */ +#define WM8962_ALC_NGATE_GAIN_SHIFT 10 /* ALC_NGATE_GAIN - [12:10] */ +#define WM8962_ALC_NGATE_GAIN_WIDTH 3 /* ALC_NGATE_GAIN - [12:10] */ +#define WM8962_ALC_MODE 0x0100 /* ALC_MODE */ +#define WM8962_ALC_MODE_MASK 0x0100 /* ALC_MODE */ +#define WM8962_ALC_MODE_SHIFT 8 /* ALC_MODE */ +#define WM8962_ALC_MODE_WIDTH 1 /* ALC_MODE */ +#define WM8962_ALC_DCY_MASK 0x00F0 /* ALC_DCY - [7:4] */ +#define WM8962_ALC_DCY_SHIFT 4 /* ALC_DCY - [7:4] */ +#define WM8962_ALC_DCY_WIDTH 4 /* ALC_DCY - [7:4] */ +#define WM8962_ALC_ATK_MASK 0x000F /* ALC_ATK - [3:0] */ +#define WM8962_ALC_ATK_SHIFT 0 /* ALC_ATK - [3:0] */ +#define WM8962_ALC_ATK_WIDTH 4 /* ALC_ATK - [3:0] */ + +/* + * R20 (0x14) - Noise Gate + */ +#define WM8962_ALC_NGATE_DCY_MASK 0xF000 /* ALC_NGATE_DCY - [15:12] */ +#define WM8962_ALC_NGATE_DCY_SHIFT 12 /* ALC_NGATE_DCY - [15:12] */ +#define WM8962_ALC_NGATE_DCY_WIDTH 4 /* ALC_NGATE_DCY - [15:12] */ +#define WM8962_ALC_NGATE_ATK_MASK 0x0F00 /* ALC_NGATE_ATK - [11:8] */ +#define WM8962_ALC_NGATE_ATK_SHIFT 8 /* ALC_NGATE_ATK - [11:8] */ +#define WM8962_ALC_NGATE_ATK_WIDTH 4 /* ALC_NGATE_ATK - [11:8] */ +#define WM8962_ALC_NGATE_THR_MASK 0x00F8 /* ALC_NGATE_THR - [7:3] */ +#define WM8962_ALC_NGATE_THR_SHIFT 3 /* ALC_NGATE_THR - [7:3] */ +#define WM8962_ALC_NGATE_THR_WIDTH 5 /* ALC_NGATE_THR - [7:3] */ +#define WM8962_ALC_NGATE_MODE_MASK 0x0006 /* ALC_NGATE_MODE - [2:1] */ +#define WM8962_ALC_NGATE_MODE_SHIFT 1 /* ALC_NGATE_MODE - [2:1] */ +#define WM8962_ALC_NGATE_MODE_WIDTH 2 /* ALC_NGATE_MODE - [2:1] */ +#define WM8962_ALC_NGATE_ENA 0x0001 /* ALC_NGATE_ENA */ +#define WM8962_ALC_NGATE_ENA_MASK 0x0001 /* ALC_NGATE_ENA */ +#define WM8962_ALC_NGATE_ENA_SHIFT 0 /* ALC_NGATE_ENA */ +#define WM8962_ALC_NGATE_ENA_WIDTH 1 /* ALC_NGATE_ENA */ + +/* + * R21 (0x15) - Left ADC volume + */ +#define WM8962_ADC_VU 0x0100 /* ADC_VU */ +#define WM8962_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8962_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8962_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8962_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8962_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8962_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R22 (0x16) - Right ADC volume + */ +#define WM8962_ADC_VU 0x0100 /* ADC_VU */ +#define WM8962_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8962_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8962_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8962_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8962_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8962_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R23 (0x17) - Additional control(1) + */ +#define WM8962_THERR_ACT 0x0100 /* THERR_ACT */ +#define WM8962_THERR_ACT_MASK 0x0100 /* THERR_ACT */ +#define WM8962_THERR_ACT_SHIFT 8 /* THERR_ACT */ +#define WM8962_THERR_ACT_WIDTH 1 /* THERR_ACT */ +#define WM8962_ADC_BIAS 0x0040 /* ADC_BIAS */ +#define WM8962_ADC_BIAS_MASK 0x0040 /* ADC_BIAS */ +#define WM8962_ADC_BIAS_SHIFT 6 /* ADC_BIAS */ +#define WM8962_ADC_BIAS_WIDTH 1 /* ADC_BIAS */ +#define WM8962_ADC_HP 0x0020 /* ADC_HP */ +#define WM8962_ADC_HP_MASK 0x0020 /* ADC_HP */ +#define WM8962_ADC_HP_SHIFT 5 /* ADC_HP */ +#define WM8962_ADC_HP_WIDTH 1 /* ADC_HP */ +#define WM8962_TOCLK_ENA 0x0001 /* TOCLK_ENA */ +#define WM8962_TOCLK_ENA_MASK 0x0001 /* TOCLK_ENA */ +#define WM8962_TOCLK_ENA_SHIFT 0 /* TOCLK_ENA */ +#define WM8962_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ + +/* + * R24 (0x18) - Additional control(2) + */ +#define WM8962_AIF_TRI 0x0008 /* AIF_TRI */ +#define WM8962_AIF_TRI_MASK 0x0008 /* AIF_TRI */ +#define WM8962_AIF_TRI_SHIFT 3 /* AIF_TRI */ +#define WM8962_AIF_TRI_WIDTH 1 /* AIF_TRI */ + +/* + * R25 (0x19) - Pwr Mgmt (1) + */ +#define WM8962_DMIC_ENA 0x0400 /* DMIC_ENA */ +#define WM8962_DMIC_ENA_MASK 0x0400 /* DMIC_ENA */ +#define WM8962_DMIC_ENA_SHIFT 10 /* DMIC_ENA */ +#define WM8962_DMIC_ENA_WIDTH 1 /* DMIC_ENA */ +#define WM8962_OPCLK_ENA 0x0200 /* OPCLK_ENA */ +#define WM8962_OPCLK_ENA_MASK 0x0200 /* OPCLK_ENA */ +#define WM8962_OPCLK_ENA_SHIFT 9 /* OPCLK_ENA */ +#define WM8962_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */ +#define WM8962_VMID_SEL_MASK 0x0180 /* VMID_SEL - [8:7] */ +#define WM8962_VMID_SEL_SHIFT 7 /* VMID_SEL - [8:7] */ +#define WM8962_VMID_SEL_WIDTH 2 /* VMID_SEL - [8:7] */ +#define WM8962_BIAS_ENA 0x0040 /* BIAS_ENA */ +#define WM8962_BIAS_ENA_MASK 0x0040 /* BIAS_ENA */ +#define WM8962_BIAS_ENA_SHIFT 6 /* BIAS_ENA */ +#define WM8962_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ +#define WM8962_INL_ENA 0x0020 /* INL_ENA */ +#define WM8962_INL_ENA_MASK 0x0020 /* INL_ENA */ +#define WM8962_INL_ENA_SHIFT 5 /* INL_ENA */ +#define WM8962_INL_ENA_WIDTH 1 /* INL_ENA */ +#define WM8962_INR_ENA 0x0010 /* INR_ENA */ +#define WM8962_INR_ENA_MASK 0x0010 /* INR_ENA */ +#define WM8962_INR_ENA_SHIFT 4 /* INR_ENA */ +#define WM8962_INR_ENA_WIDTH 1 /* INR_ENA */ +#define WM8962_ADCL_ENA 0x0008 /* ADCL_ENA */ +#define WM8962_ADCL_ENA_MASK 0x0008 /* ADCL_ENA */ +#define WM8962_ADCL_ENA_SHIFT 3 /* ADCL_ENA */ +#define WM8962_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8962_ADCR_ENA 0x0004 /* ADCR_ENA */ +#define WM8962_ADCR_ENA_MASK 0x0004 /* ADCR_ENA */ +#define WM8962_ADCR_ENA_SHIFT 2 /* ADCR_ENA */ +#define WM8962_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ +#define WM8962_MICBIAS_ENA 0x0002 /* MICBIAS_ENA */ +#define WM8962_MICBIAS_ENA_MASK 0x0002 /* MICBIAS_ENA */ +#define WM8962_MICBIAS_ENA_SHIFT 1 /* MICBIAS_ENA */ +#define WM8962_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + +/* + * R26 (0x1A) - Pwr Mgmt (2) + */ +#define WM8962_DACL_ENA 0x0100 /* DACL_ENA */ +#define WM8962_DACL_ENA_MASK 0x0100 /* DACL_ENA */ +#define WM8962_DACL_ENA_SHIFT 8 /* DACL_ENA */ +#define WM8962_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8962_DACR_ENA 0x0080 /* DACR_ENA */ +#define WM8962_DACR_ENA_MASK 0x0080 /* DACR_ENA */ +#define WM8962_DACR_ENA_SHIFT 7 /* DACR_ENA */ +#define WM8962_DACR_ENA_WIDTH 1 /* DACR_ENA */ +#define WM8962_HPOUTL_PGA_ENA 0x0040 /* HPOUTL_PGA_ENA */ +#define WM8962_HPOUTL_PGA_ENA_MASK 0x0040 /* HPOUTL_PGA_ENA */ +#define WM8962_HPOUTL_PGA_ENA_SHIFT 6 /* HPOUTL_PGA_ENA */ +#define WM8962_HPOUTL_PGA_ENA_WIDTH 1 /* HPOUTL_PGA_ENA */ +#define WM8962_HPOUTR_PGA_ENA 0x0020 /* HPOUTR_PGA_ENA */ +#define WM8962_HPOUTR_PGA_ENA_MASK 0x0020 /* HPOUTR_PGA_ENA */ +#define WM8962_HPOUTR_PGA_ENA_SHIFT 5 /* HPOUTR_PGA_ENA */ +#define WM8962_HPOUTR_PGA_ENA_WIDTH 1 /* HPOUTR_PGA_ENA */ +#define WM8962_SPKOUTL_PGA_ENA 0x0010 /* SPKOUTL_PGA_ENA */ +#define WM8962_SPKOUTL_PGA_ENA_MASK 0x0010 /* SPKOUTL_PGA_ENA */ +#define WM8962_SPKOUTL_PGA_ENA_SHIFT 4 /* SPKOUTL_PGA_ENA */ +#define WM8962_SPKOUTL_PGA_ENA_WIDTH 1 /* SPKOUTL_PGA_ENA */ +#define WM8962_SPKOUTR_PGA_ENA 0x0008 /* SPKOUTR_PGA_ENA */ +#define WM8962_SPKOUTR_PGA_ENA_MASK 0x0008 /* SPKOUTR_PGA_ENA */ +#define WM8962_SPKOUTR_PGA_ENA_SHIFT 3 /* SPKOUTR_PGA_ENA */ +#define WM8962_SPKOUTR_PGA_ENA_WIDTH 1 /* SPKOUTR_PGA_ENA */ +#define WM8962_HPOUTL_PGA_MUTE 0x0002 /* HPOUTL_PGA_MUTE */ +#define WM8962_HPOUTL_PGA_MUTE_MASK 0x0002 /* HPOUTL_PGA_MUTE */ +#define WM8962_HPOUTL_PGA_MUTE_SHIFT 1 /* HPOUTL_PGA_MUTE */ +#define WM8962_HPOUTL_PGA_MUTE_WIDTH 1 /* HPOUTL_PGA_MUTE */ +#define WM8962_HPOUTR_PGA_MUTE 0x0001 /* HPOUTR_PGA_MUTE */ +#define WM8962_HPOUTR_PGA_MUTE_MASK 0x0001 /* HPOUTR_PGA_MUTE */ +#define WM8962_HPOUTR_PGA_MUTE_SHIFT 0 /* HPOUTR_PGA_MUTE */ +#define WM8962_HPOUTR_PGA_MUTE_WIDTH 1 /* HPOUTR_PGA_MUTE */ + +/* + * R27 (0x1B) - Additional Control (3) + */ +#define WM8962_SAMPLE_RATE_INT_MODE 0x0010 /* SAMPLE_RATE_INT_MODE */ +#define WM8962_SAMPLE_RATE_INT_MODE_MASK 0x0010 /* SAMPLE_RATE_INT_MODE */ +#define WM8962_SAMPLE_RATE_INT_MODE_SHIFT 4 /* SAMPLE_RATE_INT_MODE */ +#define WM8962_SAMPLE_RATE_INT_MODE_WIDTH 1 /* SAMPLE_RATE_INT_MODE */ +#define WM8962_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */ +#define WM8962_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */ +#define WM8962_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */ + +/* + * R28 (0x1C) - Anti-pop + */ +#define WM8962_STARTUP_BIAS_ENA 0x0010 /* STARTUP_BIAS_ENA */ +#define WM8962_STARTUP_BIAS_ENA_MASK 0x0010 /* STARTUP_BIAS_ENA */ +#define WM8962_STARTUP_BIAS_ENA_SHIFT 4 /* STARTUP_BIAS_ENA */ +#define WM8962_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8962_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */ +#define WM8962_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */ +#define WM8962_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */ +#define WM8962_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM8962_VMID_RAMP 0x0004 /* VMID_RAMP */ +#define WM8962_VMID_RAMP_MASK 0x0004 /* VMID_RAMP */ +#define WM8962_VMID_RAMP_SHIFT 2 /* VMID_RAMP */ +#define WM8962_VMID_RAMP_WIDTH 1 /* VMID_RAMP */ + +/* + * R30 (0x1E) - Clocking 3 + */ +#define WM8962_DBCLK_DIV_MASK 0xE000 /* DBCLK_DIV - [15:13] */ +#define WM8962_DBCLK_DIV_SHIFT 13 /* DBCLK_DIV - [15:13] */ +#define WM8962_DBCLK_DIV_WIDTH 3 /* DBCLK_DIV - [15:13] */ +#define WM8962_OPCLK_DIV_MASK 0x1C00 /* OPCLK_DIV - [12:10] */ +#define WM8962_OPCLK_DIV_SHIFT 10 /* OPCLK_DIV - [12:10] */ +#define WM8962_OPCLK_DIV_WIDTH 3 /* OPCLK_DIV - [12:10] */ +#define WM8962_TOCLK_DIV_MASK 0x0380 /* TOCLK_DIV - [9:7] */ +#define WM8962_TOCLK_DIV_SHIFT 7 /* TOCLK_DIV - [9:7] */ +#define WM8962_TOCLK_DIV_WIDTH 3 /* TOCLK_DIV - [9:7] */ +#define WM8962_F256KCLK_DIV_MASK 0x007E /* F256KCLK_DIV - [6:1] */ +#define WM8962_F256KCLK_DIV_SHIFT 1 /* F256KCLK_DIV - [6:1] */ +#define WM8962_F256KCLK_DIV_WIDTH 6 /* F256KCLK_DIV - [6:1] */ + +/* + * R31 (0x1F) - Input mixer control (1) + */ +#define WM8962_MIXINL_MUTE 0x0008 /* MIXINL_MUTE */ +#define WM8962_MIXINL_MUTE_MASK 0x0008 /* MIXINL_MUTE */ +#define WM8962_MIXINL_MUTE_SHIFT 3 /* MIXINL_MUTE */ +#define WM8962_MIXINL_MUTE_WIDTH 1 /* MIXINL_MUTE */ +#define WM8962_MIXINR_MUTE 0x0004 /* MIXINR_MUTE */ +#define WM8962_MIXINR_MUTE_MASK 0x0004 /* MIXINR_MUTE */ +#define WM8962_MIXINR_MUTE_SHIFT 2 /* MIXINR_MUTE */ +#define WM8962_MIXINR_MUTE_WIDTH 1 /* MIXINR_MUTE */ +#define WM8962_MIXINL_ENA 0x0002 /* MIXINL_ENA */ +#define WM8962_MIXINL_ENA_MASK 0x0002 /* MIXINL_ENA */ +#define WM8962_MIXINL_ENA_SHIFT 1 /* MIXINL_ENA */ +#define WM8962_MIXINL_ENA_WIDTH 1 /* MIXINL_ENA */ +#define WM8962_MIXINR_ENA 0x0001 /* MIXINR_ENA */ +#define WM8962_MIXINR_ENA_MASK 0x0001 /* MIXINR_ENA */ +#define WM8962_MIXINR_ENA_SHIFT 0 /* MIXINR_ENA */ +#define WM8962_MIXINR_ENA_WIDTH 1 /* MIXINR_ENA */ + +/* + * R32 (0x20) - Left input mixer volume + */ +#define WM8962_IN2L_MIXINL_VOL_MASK 0x01C0 /* IN2L_MIXINL_VOL - [8:6] */ +#define WM8962_IN2L_MIXINL_VOL_SHIFT 6 /* IN2L_MIXINL_VOL - [8:6] */ +#define WM8962_IN2L_MIXINL_VOL_WIDTH 3 /* IN2L_MIXINL_VOL - [8:6] */ +#define WM8962_INPGAL_MIXINL_VOL_MASK 0x0038 /* INPGAL_MIXINL_VOL - [5:3] */ +#define WM8962_INPGAL_MIXINL_VOL_SHIFT 3 /* INPGAL_MIXINL_VOL - [5:3] */ +#define WM8962_INPGAL_MIXINL_VOL_WIDTH 3 /* INPGAL_MIXINL_VOL - [5:3] */ +#define WM8962_IN3L_MIXINL_VOL_MASK 0x0007 /* IN3L_MIXINL_VOL - [2:0] */ +#define WM8962_IN3L_MIXINL_VOL_SHIFT 0 /* IN3L_MIXINL_VOL - [2:0] */ +#define WM8962_IN3L_MIXINL_VOL_WIDTH 3 /* IN3L_MIXINL_VOL - [2:0] */ + +/* + * R33 (0x21) - Right input mixer volume + */ +#define WM8962_IN2R_MIXINR_VOL_MASK 0x01C0 /* IN2R_MIXINR_VOL - [8:6] */ +#define WM8962_IN2R_MIXINR_VOL_SHIFT 6 /* IN2R_MIXINR_VOL - [8:6] */ +#define WM8962_IN2R_MIXINR_VOL_WIDTH 3 /* IN2R_MIXINR_VOL - [8:6] */ +#define WM8962_INPGAR_MIXINR_VOL_MASK 0x0038 /* INPGAR_MIXINR_VOL - [5:3] */ +#define WM8962_INPGAR_MIXINR_VOL_SHIFT 3 /* INPGAR_MIXINR_VOL - [5:3] */ +#define WM8962_INPGAR_MIXINR_VOL_WIDTH 3 /* INPGAR_MIXINR_VOL - [5:3] */ +#define WM8962_IN3R_MIXINR_VOL_MASK 0x0007 /* IN3R_MIXINR_VOL - [2:0] */ +#define WM8962_IN3R_MIXINR_VOL_SHIFT 0 /* IN3R_MIXINR_VOL - [2:0] */ +#define WM8962_IN3R_MIXINR_VOL_WIDTH 3 /* IN3R_MIXINR_VOL - [2:0] */ + +/* + * R34 (0x22) - Input mixer control (2) + */ +#define WM8962_IN2L_TO_MIXINL 0x0020 /* IN2L_TO_MIXINL */ +#define WM8962_IN2L_TO_MIXINL_MASK 0x0020 /* IN2L_TO_MIXINL */ +#define WM8962_IN2L_TO_MIXINL_SHIFT 5 /* IN2L_TO_MIXINL */ +#define WM8962_IN2L_TO_MIXINL_WIDTH 1 /* IN2L_TO_MIXINL */ +#define WM8962_IN3L_TO_MIXINL 0x0010 /* IN3L_TO_MIXINL */ +#define WM8962_IN3L_TO_MIXINL_MASK 0x0010 /* IN3L_TO_MIXINL */ +#define WM8962_IN3L_TO_MIXINL_SHIFT 4 /* IN3L_TO_MIXINL */ +#define WM8962_IN3L_TO_MIXINL_WIDTH 1 /* IN3L_TO_MIXINL */ +#define WM8962_INPGAL_TO_MIXINL 0x0008 /* INPGAL_TO_MIXINL */ +#define WM8962_INPGAL_TO_MIXINL_MASK 0x0008 /* INPGAL_TO_MIXINL */ +#define WM8962_INPGAL_TO_MIXINL_SHIFT 3 /* INPGAL_TO_MIXINL */ +#define WM8962_INPGAL_TO_MIXINL_WIDTH 1 /* INPGAL_TO_MIXINL */ +#define WM8962_IN2R_TO_MIXINR 0x0004 /* IN2R_TO_MIXINR */ +#define WM8962_IN2R_TO_MIXINR_MASK 0x0004 /* IN2R_TO_MIXINR */ +#define WM8962_IN2R_TO_MIXINR_SHIFT 2 /* IN2R_TO_MIXINR */ +#define WM8962_IN2R_TO_MIXINR_WIDTH 1 /* IN2R_TO_MIXINR */ +#define WM8962_IN3R_TO_MIXINR 0x0002 /* IN3R_TO_MIXINR */ +#define WM8962_IN3R_TO_MIXINR_MASK 0x0002 /* IN3R_TO_MIXINR */ +#define WM8962_IN3R_TO_MIXINR_SHIFT 1 /* IN3R_TO_MIXINR */ +#define WM8962_IN3R_TO_MIXINR_WIDTH 1 /* IN3R_TO_MIXINR */ +#define WM8962_INPGAR_TO_MIXINR 0x0001 /* INPGAR_TO_MIXINR */ +#define WM8962_INPGAR_TO_MIXINR_MASK 0x0001 /* INPGAR_TO_MIXINR */ +#define WM8962_INPGAR_TO_MIXINR_SHIFT 0 /* INPGAR_TO_MIXINR */ +#define WM8962_INPGAR_TO_MIXINR_WIDTH 1 /* INPGAR_TO_MIXINR */ + +/* + * R35 (0x23) - Input bias control + */ +#define WM8962_MIXIN_BIAS_MASK 0x0038 /* MIXIN_BIAS - [5:3] */ +#define WM8962_MIXIN_BIAS_SHIFT 3 /* MIXIN_BIAS - [5:3] */ +#define WM8962_MIXIN_BIAS_WIDTH 3 /* MIXIN_BIAS - [5:3] */ +#define WM8962_INPGA_BIAS_MASK 0x0007 /* INPGA_BIAS - [2:0] */ +#define WM8962_INPGA_BIAS_SHIFT 0 /* INPGA_BIAS - [2:0] */ +#define WM8962_INPGA_BIAS_WIDTH 3 /* INPGA_BIAS - [2:0] */ + +/* + * R37 (0x25) - Left input PGA control + */ +#define WM8962_INPGAL_ENA 0x0010 /* INPGAL_ENA */ +#define WM8962_INPGAL_ENA_MASK 0x0010 /* INPGAL_ENA */ +#define WM8962_INPGAL_ENA_SHIFT 4 /* INPGAL_ENA */ +#define WM8962_INPGAL_ENA_WIDTH 1 /* INPGAL_ENA */ +#define WM8962_IN1L_TO_INPGAL 0x0008 /* IN1L_TO_INPGAL */ +#define WM8962_IN1L_TO_INPGAL_MASK 0x0008 /* IN1L_TO_INPGAL */ +#define WM8962_IN1L_TO_INPGAL_SHIFT 3 /* IN1L_TO_INPGAL */ +#define WM8962_IN1L_TO_INPGAL_WIDTH 1 /* IN1L_TO_INPGAL */ +#define WM8962_IN2L_TO_INPGAL 0x0004 /* IN2L_TO_INPGAL */ +#define WM8962_IN2L_TO_INPGAL_MASK 0x0004 /* IN2L_TO_INPGAL */ +#define WM8962_IN2L_TO_INPGAL_SHIFT 2 /* IN2L_TO_INPGAL */ +#define WM8962_IN2L_TO_INPGAL_WIDTH 1 /* IN2L_TO_INPGAL */ +#define WM8962_IN3L_TO_INPGAL 0x0002 /* IN3L_TO_INPGAL */ +#define WM8962_IN3L_TO_INPGAL_MASK 0x0002 /* IN3L_TO_INPGAL */ +#define WM8962_IN3L_TO_INPGAL_SHIFT 1 /* IN3L_TO_INPGAL */ +#define WM8962_IN3L_TO_INPGAL_WIDTH 1 /* IN3L_TO_INPGAL */ +#define WM8962_IN4L_TO_INPGAL 0x0001 /* IN4L_TO_INPGAL */ +#define WM8962_IN4L_TO_INPGAL_MASK 0x0001 /* IN4L_TO_INPGAL */ +#define WM8962_IN4L_TO_INPGAL_SHIFT 0 /* IN4L_TO_INPGAL */ +#define WM8962_IN4L_TO_INPGAL_WIDTH 1 /* IN4L_TO_INPGAL */ + +/* + * R38 (0x26) - Right input PGA control + */ +#define WM8962_INPGAR_ENA 0x0010 /* INPGAR_ENA */ +#define WM8962_INPGAR_ENA_MASK 0x0010 /* INPGAR_ENA */ +#define WM8962_INPGAR_ENA_SHIFT 4 /* INPGAR_ENA */ +#define WM8962_INPGAR_ENA_WIDTH 1 /* INPGAR_ENA */ +#define WM8962_IN1R_TO_INPGAR 0x0008 /* IN1R_TO_INPGAR */ +#define WM8962_IN1R_TO_INPGAR_MASK 0x0008 /* IN1R_TO_INPGAR */ +#define WM8962_IN1R_TO_INPGAR_SHIFT 3 /* IN1R_TO_INPGAR */ +#define WM8962_IN1R_TO_INPGAR_WIDTH 1 /* IN1R_TO_INPGAR */ +#define WM8962_IN2R_TO_INPGAR 0x0004 /* IN2R_TO_INPGAR */ +#define WM8962_IN2R_TO_INPGAR_MASK 0x0004 /* IN2R_TO_INPGAR */ +#define WM8962_IN2R_TO_INPGAR_SHIFT 2 /* IN2R_TO_INPGAR */ +#define WM8962_IN2R_TO_INPGAR_WIDTH 1 /* IN2R_TO_INPGAR */ +#define WM8962_IN3R_TO_INPGAR 0x0002 /* IN3R_TO_INPGAR */ +#define WM8962_IN3R_TO_INPGAR_MASK 0x0002 /* IN3R_TO_INPGAR */ +#define WM8962_IN3R_TO_INPGAR_SHIFT 1 /* IN3R_TO_INPGAR */ +#define WM8962_IN3R_TO_INPGAR_WIDTH 1 /* IN3R_TO_INPGAR */ +#define WM8962_IN4R_TO_INPGAR 0x0001 /* IN4R_TO_INPGAR */ +#define WM8962_IN4R_TO_INPGAR_MASK 0x0001 /* IN4R_TO_INPGAR */ +#define WM8962_IN4R_TO_INPGAR_SHIFT 0 /* IN4R_TO_INPGAR */ +#define WM8962_IN4R_TO_INPGAR_WIDTH 1 /* IN4R_TO_INPGAR */ + +/* + * R40 (0x28) - SPKOUTL volume + */ +#define WM8962_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM8962_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM8962_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM8962_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM8962_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */ +#define WM8962_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */ +#define WM8962_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */ +#define WM8962_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */ +#define WM8962_SPKOUTL_VOL_MASK 0x007F /* SPKOUTL_VOL - [6:0] */ +#define WM8962_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [6:0] */ +#define WM8962_SPKOUTL_VOL_WIDTH 7 /* SPKOUTL_VOL - [6:0] */ + +/* + * R41 (0x29) - SPKOUTR volume + */ +#define WM8962_SPKOUTR_ZC 0x0080 /* SPKOUTR_ZC */ +#define WM8962_SPKOUTR_ZC_MASK 0x0080 /* SPKOUTR_ZC */ +#define WM8962_SPKOUTR_ZC_SHIFT 7 /* SPKOUTR_ZC */ +#define WM8962_SPKOUTR_ZC_WIDTH 1 /* SPKOUTR_ZC */ +#define WM8962_SPKOUTR_VOL_MASK 0x007F /* SPKOUTR_VOL - [6:0] */ +#define WM8962_SPKOUTR_VOL_SHIFT 0 /* SPKOUTR_VOL - [6:0] */ +#define WM8962_SPKOUTR_VOL_WIDTH 7 /* SPKOUTR_VOL - [6:0] */ + +/* + * R47 (0x2F) - Thermal Shutdown Status + */ +#define WM8962_TEMP_ERR_HP 0x0008 /* TEMP_ERR_HP */ +#define WM8962_TEMP_ERR_HP_MASK 0x0008 /* TEMP_ERR_HP */ +#define WM8962_TEMP_ERR_HP_SHIFT 3 /* TEMP_ERR_HP */ +#define WM8962_TEMP_ERR_HP_WIDTH 1 /* TEMP_ERR_HP */ +#define WM8962_TEMP_WARN_HP 0x0004 /* TEMP_WARN_HP */ +#define WM8962_TEMP_WARN_HP_MASK 0x0004 /* TEMP_WARN_HP */ +#define WM8962_TEMP_WARN_HP_SHIFT 2 /* TEMP_WARN_HP */ +#define WM8962_TEMP_WARN_HP_WIDTH 1 /* TEMP_WARN_HP */ +#define WM8962_TEMP_ERR_SPK 0x0002 /* TEMP_ERR_SPK */ +#define WM8962_TEMP_ERR_SPK_MASK 0x0002 /* TEMP_ERR_SPK */ +#define WM8962_TEMP_ERR_SPK_SHIFT 1 /* TEMP_ERR_SPK */ +#define WM8962_TEMP_ERR_SPK_WIDTH 1 /* TEMP_ERR_SPK */ +#define WM8962_TEMP_WARN_SPK 0x0001 /* TEMP_WARN_SPK */ +#define WM8962_TEMP_WARN_SPK_MASK 0x0001 /* TEMP_WARN_SPK */ +#define WM8962_TEMP_WARN_SPK_SHIFT 0 /* TEMP_WARN_SPK */ +#define WM8962_TEMP_WARN_SPK_WIDTH 1 /* TEMP_WARN_SPK */ + +/* + * R48 (0x30) - Additional Control (4) + */ +#define WM8962_MICDET_THR_MASK 0x7000 /* MICDET_THR - [14:12] */ +#define WM8962_MICDET_THR_SHIFT 12 /* MICDET_THR - [14:12] */ +#define WM8962_MICDET_THR_WIDTH 3 /* MICDET_THR - [14:12] */ +#define WM8962_MICSHORT_THR_MASK 0x0C00 /* MICSHORT_THR - [11:10] */ +#define WM8962_MICSHORT_THR_SHIFT 10 /* MICSHORT_THR - [11:10] */ +#define WM8962_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [11:10] */ +#define WM8962_MICDET_ENA 0x0200 /* MICDET_ENA */ +#define WM8962_MICDET_ENA_MASK 0x0200 /* MICDET_ENA */ +#define WM8962_MICDET_ENA_SHIFT 9 /* MICDET_ENA */ +#define WM8962_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8962_MICDET_STS 0x0080 /* MICDET_STS */ +#define WM8962_MICDET_STS_MASK 0x0080 /* MICDET_STS */ +#define WM8962_MICDET_STS_SHIFT 7 /* MICDET_STS */ +#define WM8962_MICDET_STS_WIDTH 1 /* MICDET_STS */ +#define WM8962_MICSHORT_STS 0x0040 /* MICSHORT_STS */ +#define WM8962_MICSHORT_STS_MASK 0x0040 /* MICSHORT_STS */ +#define WM8962_MICSHORT_STS_SHIFT 6 /* MICSHORT_STS */ +#define WM8962_MICSHORT_STS_WIDTH 1 /* MICSHORT_STS */ +#define WM8962_TEMP_ENA_HP 0x0004 /* TEMP_ENA_HP */ +#define WM8962_TEMP_ENA_HP_MASK 0x0004 /* TEMP_ENA_HP */ +#define WM8962_TEMP_ENA_HP_SHIFT 2 /* TEMP_ENA_HP */ +#define WM8962_TEMP_ENA_HP_WIDTH 1 /* TEMP_ENA_HP */ +#define WM8962_TEMP_ENA_SPK 0x0002 /* TEMP_ENA_SPK */ +#define WM8962_TEMP_ENA_SPK_MASK 0x0002 /* TEMP_ENA_SPK */ +#define WM8962_TEMP_ENA_SPK_SHIFT 1 /* TEMP_ENA_SPK */ +#define WM8962_TEMP_ENA_SPK_WIDTH 1 /* TEMP_ENA_SPK */ +#define WM8962_MICBIAS_LVL 0x0001 /* MICBIAS_LVL */ +#define WM8962_MICBIAS_LVL_MASK 0x0001 /* MICBIAS_LVL */ +#define WM8962_MICBIAS_LVL_SHIFT 0 /* MICBIAS_LVL */ +#define WM8962_MICBIAS_LVL_WIDTH 1 /* MICBIAS_LVL */ + +/* + * R49 (0x31) - Class D Control 1 + */ +#define WM8962_SPKOUTR_ENA 0x0080 /* SPKOUTR_ENA */ +#define WM8962_SPKOUTR_ENA_MASK 0x0080 /* SPKOUTR_ENA */ +#define WM8962_SPKOUTR_ENA_SHIFT 7 /* SPKOUTR_ENA */ +#define WM8962_SPKOUTR_ENA_WIDTH 1 /* SPKOUTR_ENA */ +#define WM8962_SPKOUTL_ENA 0x0040 /* SPKOUTL_ENA */ +#define WM8962_SPKOUTL_ENA_MASK 0x0040 /* SPKOUTL_ENA */ +#define WM8962_SPKOUTL_ENA_SHIFT 6 /* SPKOUTL_ENA */ +#define WM8962_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */ +#define WM8962_DAC_MUTE_ALT 0x0010 /* DAC_MUTE */ +#define WM8962_DAC_MUTE_ALT_MASK 0x0010 /* DAC_MUTE */ +#define WM8962_DAC_MUTE_ALT_SHIFT 4 /* DAC_MUTE */ +#define WM8962_DAC_MUTE_ALT_WIDTH 1 /* DAC_MUTE */ +#define WM8962_SPKOUTL_PGA_MUTE 0x0002 /* SPKOUTL_PGA_MUTE */ +#define WM8962_SPKOUTL_PGA_MUTE_MASK 0x0002 /* SPKOUTL_PGA_MUTE */ +#define WM8962_SPKOUTL_PGA_MUTE_SHIFT 1 /* SPKOUTL_PGA_MUTE */ +#define WM8962_SPKOUTL_PGA_MUTE_WIDTH 1 /* SPKOUTL_PGA_MUTE */ +#define WM8962_SPKOUTR_PGA_MUTE 0x0001 /* SPKOUTR_PGA_MUTE */ +#define WM8962_SPKOUTR_PGA_MUTE_MASK 0x0001 /* SPKOUTR_PGA_MUTE */ +#define WM8962_SPKOUTR_PGA_MUTE_SHIFT 0 /* SPKOUTR_PGA_MUTE */ +#define WM8962_SPKOUTR_PGA_MUTE_WIDTH 1 /* SPKOUTR_PGA_MUTE */ + +/* + * R51 (0x33) - Class D Control 2 + */ +#define WM8962_SPK_MONO 0x0040 /* SPK_MONO */ +#define WM8962_SPK_MONO_MASK 0x0040 /* SPK_MONO */ +#define WM8962_SPK_MONO_SHIFT 6 /* SPK_MONO */ +#define WM8962_SPK_MONO_WIDTH 1 /* SPK_MONO */ +#define WM8962_CLASSD_VOL_MASK 0x0007 /* CLASSD_VOL - [2:0] */ +#define WM8962_CLASSD_VOL_SHIFT 0 /* CLASSD_VOL - [2:0] */ +#define WM8962_CLASSD_VOL_WIDTH 3 /* CLASSD_VOL - [2:0] */ + +/* + * R56 (0x38) - Clocking 4 + */ +#define WM8962_SYSCLK_RATE_MASK 0x001E /* SYSCLK_RATE - [4:1] */ +#define WM8962_SYSCLK_RATE_SHIFT 1 /* SYSCLK_RATE - [4:1] */ +#define WM8962_SYSCLK_RATE_WIDTH 4 /* SYSCLK_RATE - [4:1] */ + +/* + * R57 (0x39) - DAC DSP Mixing (1) + */ +#define WM8962_DAC_MONOMIX 0x0200 /* DAC_MONOMIX */ +#define WM8962_DAC_MONOMIX_MASK 0x0200 /* DAC_MONOMIX */ +#define WM8962_DAC_MONOMIX_SHIFT 9 /* DAC_MONOMIX */ +#define WM8962_DAC_MONOMIX_WIDTH 1 /* DAC_MONOMIX */ +#define WM8962_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8962_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8962_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8962_ADC_TO_DACR_MASK 0x000C /* ADC_TO_DACR - [3:2] */ +#define WM8962_ADC_TO_DACR_SHIFT 2 /* ADC_TO_DACR - [3:2] */ +#define WM8962_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [3:2] */ + +/* + * R58 (0x3A) - DAC DSP Mixing (2) + */ +#define WM8962_ADCL_DAC_SVOL_MASK 0x00F0 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8962_ADCL_DAC_SVOL_SHIFT 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8962_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8962_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8962_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8962_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ + +/* + * R60 (0x3C) - DC Servo 0 + */ +#define WM8962_INL_DCS_ENA 0x0080 /* INL_DCS_ENA */ +#define WM8962_INL_DCS_ENA_MASK 0x0080 /* INL_DCS_ENA */ +#define WM8962_INL_DCS_ENA_SHIFT 7 /* INL_DCS_ENA */ +#define WM8962_INL_DCS_ENA_WIDTH 1 /* INL_DCS_ENA */ +#define WM8962_INL_DCS_STARTUP 0x0040 /* INL_DCS_STARTUP */ +#define WM8962_INL_DCS_STARTUP_MASK 0x0040 /* INL_DCS_STARTUP */ +#define WM8962_INL_DCS_STARTUP_SHIFT 6 /* INL_DCS_STARTUP */ +#define WM8962_INL_DCS_STARTUP_WIDTH 1 /* INL_DCS_STARTUP */ +#define WM8962_INR_DCS_ENA 0x0008 /* INR_DCS_ENA */ +#define WM8962_INR_DCS_ENA_MASK 0x0008 /* INR_DCS_ENA */ +#define WM8962_INR_DCS_ENA_SHIFT 3 /* INR_DCS_ENA */ +#define WM8962_INR_DCS_ENA_WIDTH 1 /* INR_DCS_ENA */ +#define WM8962_INR_DCS_STARTUP 0x0004 /* INR_DCS_STARTUP */ +#define WM8962_INR_DCS_STARTUP_MASK 0x0004 /* INR_DCS_STARTUP */ +#define WM8962_INR_DCS_STARTUP_SHIFT 2 /* INR_DCS_STARTUP */ +#define WM8962_INR_DCS_STARTUP_WIDTH 1 /* INR_DCS_STARTUP */ + +/* + * R61 (0x3D) - DC Servo 1 + */ +#define WM8962_HP1L_DCS_ENA 0x0080 /* HP1L_DCS_ENA */ +#define WM8962_HP1L_DCS_ENA_MASK 0x0080 /* HP1L_DCS_ENA */ +#define WM8962_HP1L_DCS_ENA_SHIFT 7 /* HP1L_DCS_ENA */ +#define WM8962_HP1L_DCS_ENA_WIDTH 1 /* HP1L_DCS_ENA */ +#define WM8962_HP1L_DCS_STARTUP 0x0040 /* HP1L_DCS_STARTUP */ +#define WM8962_HP1L_DCS_STARTUP_MASK 0x0040 /* HP1L_DCS_STARTUP */ +#define WM8962_HP1L_DCS_STARTUP_SHIFT 6 /* HP1L_DCS_STARTUP */ +#define WM8962_HP1L_DCS_STARTUP_WIDTH 1 /* HP1L_DCS_STARTUP */ +#define WM8962_HP1L_DCS_SYNC 0x0010 /* HP1L_DCS_SYNC */ +#define WM8962_HP1L_DCS_SYNC_MASK 0x0010 /* HP1L_DCS_SYNC */ +#define WM8962_HP1L_DCS_SYNC_SHIFT 4 /* HP1L_DCS_SYNC */ +#define WM8962_HP1L_DCS_SYNC_WIDTH 1 /* HP1L_DCS_SYNC */ +#define WM8962_HP1R_DCS_ENA 0x0008 /* HP1R_DCS_ENA */ +#define WM8962_HP1R_DCS_ENA_MASK 0x0008 /* HP1R_DCS_ENA */ +#define WM8962_HP1R_DCS_ENA_SHIFT 3 /* HP1R_DCS_ENA */ +#define WM8962_HP1R_DCS_ENA_WIDTH 1 /* HP1R_DCS_ENA */ +#define WM8962_HP1R_DCS_STARTUP 0x0004 /* HP1R_DCS_STARTUP */ +#define WM8962_HP1R_DCS_STARTUP_MASK 0x0004 /* HP1R_DCS_STARTUP */ +#define WM8962_HP1R_DCS_STARTUP_SHIFT 2 /* HP1R_DCS_STARTUP */ +#define WM8962_HP1R_DCS_STARTUP_WIDTH 1 /* HP1R_DCS_STARTUP */ +#define WM8962_HP1R_DCS_SYNC 0x0001 /* HP1R_DCS_SYNC */ +#define WM8962_HP1R_DCS_SYNC_MASK 0x0001 /* HP1R_DCS_SYNC */ +#define WM8962_HP1R_DCS_SYNC_SHIFT 0 /* HP1R_DCS_SYNC */ +#define WM8962_HP1R_DCS_SYNC_WIDTH 1 /* HP1R_DCS_SYNC */ + +/* + * R64 (0x40) - DC Servo 4 + */ +#define WM8962_HP1_DCS_SYNC_STEPS_MASK 0x3F80 /* HP1_DCS_SYNC_STEPS - [13:7] */ +#define WM8962_HP1_DCS_SYNC_STEPS_SHIFT 7 /* HP1_DCS_SYNC_STEPS - [13:7] */ +#define WM8962_HP1_DCS_SYNC_STEPS_WIDTH 7 /* HP1_DCS_SYNC_STEPS - [13:7] */ + +/* + * R66 (0x42) - DC Servo 6 + */ +#define WM8962_DCS_STARTUP_DONE_INL 0x0400 /* DCS_STARTUP_DONE_INL */ +#define WM8962_DCS_STARTUP_DONE_INL_MASK 0x0400 /* DCS_STARTUP_DONE_INL */ +#define WM8962_DCS_STARTUP_DONE_INL_SHIFT 10 /* DCS_STARTUP_DONE_INL */ +#define WM8962_DCS_STARTUP_DONE_INL_WIDTH 1 /* DCS_STARTUP_DONE_INL */ +#define WM8962_DCS_STARTUP_DONE_INR 0x0200 /* DCS_STARTUP_DONE_INR */ +#define WM8962_DCS_STARTUP_DONE_INR_MASK 0x0200 /* DCS_STARTUP_DONE_INR */ +#define WM8962_DCS_STARTUP_DONE_INR_SHIFT 9 /* DCS_STARTUP_DONE_INR */ +#define WM8962_DCS_STARTUP_DONE_INR_WIDTH 1 /* DCS_STARTUP_DONE_INR */ +#define WM8962_DCS_STARTUP_DONE_HP1L 0x0100 /* DCS_STARTUP_DONE_HP1L */ +#define WM8962_DCS_STARTUP_DONE_HP1L_MASK 0x0100 /* DCS_STARTUP_DONE_HP1L */ +#define WM8962_DCS_STARTUP_DONE_HP1L_SHIFT 8 /* DCS_STARTUP_DONE_HP1L */ +#define WM8962_DCS_STARTUP_DONE_HP1L_WIDTH 1 /* DCS_STARTUP_DONE_HP1L */ +#define WM8962_DCS_STARTUP_DONE_HP1R 0x0080 /* DCS_STARTUP_DONE_HP1R */ +#define WM8962_DCS_STARTUP_DONE_HP1R_MASK 0x0080 /* DCS_STARTUP_DONE_HP1R */ +#define WM8962_DCS_STARTUP_DONE_HP1R_SHIFT 7 /* DCS_STARTUP_DONE_HP1R */ +#define WM8962_DCS_STARTUP_DONE_HP1R_WIDTH 1 /* DCS_STARTUP_DONE_HP1R */ + +/* + * R68 (0x44) - Analogue PGA Bias + */ +#define WM8962_HP_PGAS_BIAS_MASK 0x0007 /* HP_PGAS_BIAS - [2:0] */ +#define WM8962_HP_PGAS_BIAS_SHIFT 0 /* HP_PGAS_BIAS - [2:0] */ +#define WM8962_HP_PGAS_BIAS_WIDTH 3 /* HP_PGAS_BIAS - [2:0] */ + +/* + * R69 (0x45) - Analogue HP 0 + */ +#define WM8962_HP1L_RMV_SHORT 0x0080 /* HP1L_RMV_SHORT */ +#define WM8962_HP1L_RMV_SHORT_MASK 0x0080 /* HP1L_RMV_SHORT */ +#define WM8962_HP1L_RMV_SHORT_SHIFT 7 /* HP1L_RMV_SHORT */ +#define WM8962_HP1L_RMV_SHORT_WIDTH 1 /* HP1L_RMV_SHORT */ +#define WM8962_HP1L_ENA_OUTP 0x0040 /* HP1L_ENA_OUTP */ +#define WM8962_HP1L_ENA_OUTP_MASK 0x0040 /* HP1L_ENA_OUTP */ +#define WM8962_HP1L_ENA_OUTP_SHIFT 6 /* HP1L_ENA_OUTP */ +#define WM8962_HP1L_ENA_OUTP_WIDTH 1 /* HP1L_ENA_OUTP */ +#define WM8962_HP1L_ENA_DLY 0x0020 /* HP1L_ENA_DLY */ +#define WM8962_HP1L_ENA_DLY_MASK 0x0020 /* HP1L_ENA_DLY */ +#define WM8962_HP1L_ENA_DLY_SHIFT 5 /* HP1L_ENA_DLY */ +#define WM8962_HP1L_ENA_DLY_WIDTH 1 /* HP1L_ENA_DLY */ +#define WM8962_HP1L_ENA 0x0010 /* HP1L_ENA */ +#define WM8962_HP1L_ENA_MASK 0x0010 /* HP1L_ENA */ +#define WM8962_HP1L_ENA_SHIFT 4 /* HP1L_ENA */ +#define WM8962_HP1L_ENA_WIDTH 1 /* HP1L_ENA */ +#define WM8962_HP1R_RMV_SHORT 0x0008 /* HP1R_RMV_SHORT */ +#define WM8962_HP1R_RMV_SHORT_MASK 0x0008 /* HP1R_RMV_SHORT */ +#define WM8962_HP1R_RMV_SHORT_SHIFT 3 /* HP1R_RMV_SHORT */ +#define WM8962_HP1R_RMV_SHORT_WIDTH 1 /* HP1R_RMV_SHORT */ +#define WM8962_HP1R_ENA_OUTP 0x0004 /* HP1R_ENA_OUTP */ +#define WM8962_HP1R_ENA_OUTP_MASK 0x0004 /* HP1R_ENA_OUTP */ +#define WM8962_HP1R_ENA_OUTP_SHIFT 2 /* HP1R_ENA_OUTP */ +#define WM8962_HP1R_ENA_OUTP_WIDTH 1 /* HP1R_ENA_OUTP */ +#define WM8962_HP1R_ENA_DLY 0x0002 /* HP1R_ENA_DLY */ +#define WM8962_HP1R_ENA_DLY_MASK 0x0002 /* HP1R_ENA_DLY */ +#define WM8962_HP1R_ENA_DLY_SHIFT 1 /* HP1R_ENA_DLY */ +#define WM8962_HP1R_ENA_DLY_WIDTH 1 /* HP1R_ENA_DLY */ +#define WM8962_HP1R_ENA 0x0001 /* HP1R_ENA */ +#define WM8962_HP1R_ENA_MASK 0x0001 /* HP1R_ENA */ +#define WM8962_HP1R_ENA_SHIFT 0 /* HP1R_ENA */ +#define WM8962_HP1R_ENA_WIDTH 1 /* HP1R_ENA */ + +/* + * R71 (0x47) - Analogue HP 2 + */ +#define WM8962_HP1L_VOL_MASK 0x01C0 /* HP1L_VOL - [8:6] */ +#define WM8962_HP1L_VOL_SHIFT 6 /* HP1L_VOL - [8:6] */ +#define WM8962_HP1L_VOL_WIDTH 3 /* HP1L_VOL - [8:6] */ +#define WM8962_HP1R_VOL_MASK 0x0038 /* HP1R_VOL - [5:3] */ +#define WM8962_HP1R_VOL_SHIFT 3 /* HP1R_VOL - [5:3] */ +#define WM8962_HP1R_VOL_WIDTH 3 /* HP1R_VOL - [5:3] */ +#define WM8962_HP_BIAS_BOOST_MASK 0x0007 /* HP_BIAS_BOOST - [2:0] */ +#define WM8962_HP_BIAS_BOOST_SHIFT 0 /* HP_BIAS_BOOST - [2:0] */ +#define WM8962_HP_BIAS_BOOST_WIDTH 3 /* HP_BIAS_BOOST - [2:0] */ + +/* + * R72 (0x48) - Charge Pump 1 + */ +#define WM8962_CP_ENA 0x0001 /* CP_ENA */ +#define WM8962_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8962_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8962_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R82 (0x52) - Charge Pump B + */ +#define WM8962_CP_DYN_PWR 0x0001 /* CP_DYN_PWR */ +#define WM8962_CP_DYN_PWR_MASK 0x0001 /* CP_DYN_PWR */ +#define WM8962_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR */ +#define WM8962_CP_DYN_PWR_WIDTH 1 /* CP_DYN_PWR */ + +/* + * R87 (0x57) - Write Sequencer Control 1 + */ +#define WM8962_WSEQ_AUTOSEQ_ENA 0x0080 /* WSEQ_AUTOSEQ_ENA */ +#define WM8962_WSEQ_AUTOSEQ_ENA_MASK 0x0080 /* WSEQ_AUTOSEQ_ENA */ +#define WM8962_WSEQ_AUTOSEQ_ENA_SHIFT 7 /* WSEQ_AUTOSEQ_ENA */ +#define WM8962_WSEQ_AUTOSEQ_ENA_WIDTH 1 /* WSEQ_AUTOSEQ_ENA */ +#define WM8962_WSEQ_ENA 0x0020 /* WSEQ_ENA */ +#define WM8962_WSEQ_ENA_MASK 0x0020 /* WSEQ_ENA */ +#define WM8962_WSEQ_ENA_SHIFT 5 /* WSEQ_ENA */ +#define WM8962_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ + +/* + * R90 (0x5A) - Write Sequencer Control 2 + */ +#define WM8962_WSEQ_ABORT 0x0100 /* WSEQ_ABORT */ +#define WM8962_WSEQ_ABORT_MASK 0x0100 /* WSEQ_ABORT */ +#define WM8962_WSEQ_ABORT_SHIFT 8 /* WSEQ_ABORT */ +#define WM8962_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8962_WSEQ_START 0x0080 /* WSEQ_START */ +#define WM8962_WSEQ_START_MASK 0x0080 /* WSEQ_START */ +#define WM8962_WSEQ_START_SHIFT 7 /* WSEQ_START */ +#define WM8962_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8962_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */ +#define WM8962_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */ +#define WM8962_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */ + +/* + * R93 (0x5D) - Write Sequencer Control 3 + */ +#define WM8962_WSEQ_CURRENT_INDEX_MASK 0x03F8 /* WSEQ_CURRENT_INDEX - [9:3] */ +#define WM8962_WSEQ_CURRENT_INDEX_SHIFT 3 /* WSEQ_CURRENT_INDEX - [9:3] */ +#define WM8962_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [9:3] */ +#define WM8962_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8962_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8962_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8962_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R94 (0x5E) - Control Interface + */ +#define WM8962_SPI_CONTRD 0x0040 /* SPI_CONTRD */ +#define WM8962_SPI_CONTRD_MASK 0x0040 /* SPI_CONTRD */ +#define WM8962_SPI_CONTRD_SHIFT 6 /* SPI_CONTRD */ +#define WM8962_SPI_CONTRD_WIDTH 1 /* SPI_CONTRD */ +#define WM8962_SPI_4WIRE 0x0020 /* SPI_4WIRE */ +#define WM8962_SPI_4WIRE_MASK 0x0020 /* SPI_4WIRE */ +#define WM8962_SPI_4WIRE_SHIFT 5 /* SPI_4WIRE */ +#define WM8962_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */ +#define WM8962_SPI_CFG 0x0010 /* SPI_CFG */ +#define WM8962_SPI_CFG_MASK 0x0010 /* SPI_CFG */ +#define WM8962_SPI_CFG_SHIFT 4 /* SPI_CFG */ +#define WM8962_SPI_CFG_WIDTH 1 /* SPI_CFG */ + +/* + * R99 (0x63) - Mixer Enables + */ +#define WM8962_HPMIXL_ENA 0x0008 /* HPMIXL_ENA */ +#define WM8962_HPMIXL_ENA_MASK 0x0008 /* HPMIXL_ENA */ +#define WM8962_HPMIXL_ENA_SHIFT 3 /* HPMIXL_ENA */ +#define WM8962_HPMIXL_ENA_WIDTH 1 /* HPMIXL_ENA */ +#define WM8962_HPMIXR_ENA 0x0004 /* HPMIXR_ENA */ +#define WM8962_HPMIXR_ENA_MASK 0x0004 /* HPMIXR_ENA */ +#define WM8962_HPMIXR_ENA_SHIFT 2 /* HPMIXR_ENA */ +#define WM8962_HPMIXR_ENA_WIDTH 1 /* HPMIXR_ENA */ +#define WM8962_SPKMIXL_ENA 0x0002 /* SPKMIXL_ENA */ +#define WM8962_SPKMIXL_ENA_MASK 0x0002 /* SPKMIXL_ENA */ +#define WM8962_SPKMIXL_ENA_SHIFT 1 /* SPKMIXL_ENA */ +#define WM8962_SPKMIXL_ENA_WIDTH 1 /* SPKMIXL_ENA */ +#define WM8962_SPKMIXR_ENA 0x0001 /* SPKMIXR_ENA */ +#define WM8962_SPKMIXR_ENA_MASK 0x0001 /* SPKMIXR_ENA */ +#define WM8962_SPKMIXR_ENA_SHIFT 0 /* SPKMIXR_ENA */ +#define WM8962_SPKMIXR_ENA_WIDTH 1 /* SPKMIXR_ENA */ + +/* + * R100 (0x64) - Headphone Mixer (1) + */ +#define WM8962_HPMIXL_TO_HPOUTL_PGA 0x0080 /* HPMIXL_TO_HPOUTL_PGA */ +#define WM8962_HPMIXL_TO_HPOUTL_PGA_MASK 0x0080 /* HPMIXL_TO_HPOUTL_PGA */ +#define WM8962_HPMIXL_TO_HPOUTL_PGA_SHIFT 7 /* HPMIXL_TO_HPOUTL_PGA */ +#define WM8962_HPMIXL_TO_HPOUTL_PGA_WIDTH 1 /* HPMIXL_TO_HPOUTL_PGA */ +#define WM8962_DACL_TO_HPMIXL 0x0020 /* DACL_TO_HPMIXL */ +#define WM8962_DACL_TO_HPMIXL_MASK 0x0020 /* DACL_TO_HPMIXL */ +#define WM8962_DACL_TO_HPMIXL_SHIFT 5 /* DACL_TO_HPMIXL */ +#define WM8962_DACL_TO_HPMIXL_WIDTH 1 /* DACL_TO_HPMIXL */ +#define WM8962_DACR_TO_HPMIXL 0x0010 /* DACR_TO_HPMIXL */ +#define WM8962_DACR_TO_HPMIXL_MASK 0x0010 /* DACR_TO_HPMIXL */ +#define WM8962_DACR_TO_HPMIXL_SHIFT 4 /* DACR_TO_HPMIXL */ +#define WM8962_DACR_TO_HPMIXL_WIDTH 1 /* DACR_TO_HPMIXL */ +#define WM8962_MIXINL_TO_HPMIXL 0x0008 /* MIXINL_TO_HPMIXL */ +#define WM8962_MIXINL_TO_HPMIXL_MASK 0x0008 /* MIXINL_TO_HPMIXL */ +#define WM8962_MIXINL_TO_HPMIXL_SHIFT 3 /* MIXINL_TO_HPMIXL */ +#define WM8962_MIXINL_TO_HPMIXL_WIDTH 1 /* MIXINL_TO_HPMIXL */ +#define WM8962_MIXINR_TO_HPMIXL 0x0004 /* MIXINR_TO_HPMIXL */ +#define WM8962_MIXINR_TO_HPMIXL_MASK 0x0004 /* MIXINR_TO_HPMIXL */ +#define WM8962_MIXINR_TO_HPMIXL_SHIFT 2 /* MIXINR_TO_HPMIXL */ +#define WM8962_MIXINR_TO_HPMIXL_WIDTH 1 /* MIXINR_TO_HPMIXL */ +#define WM8962_IN4L_TO_HPMIXL 0x0002 /* IN4L_TO_HPMIXL */ +#define WM8962_IN4L_TO_HPMIXL_MASK 0x0002 /* IN4L_TO_HPMIXL */ +#define WM8962_IN4L_TO_HPMIXL_SHIFT 1 /* IN4L_TO_HPMIXL */ +#define WM8962_IN4L_TO_HPMIXL_WIDTH 1 /* IN4L_TO_HPMIXL */ +#define WM8962_IN4R_TO_HPMIXL 0x0001 /* IN4R_TO_HPMIXL */ +#define WM8962_IN4R_TO_HPMIXL_MASK 0x0001 /* IN4R_TO_HPMIXL */ +#define WM8962_IN4R_TO_HPMIXL_SHIFT 0 /* IN4R_TO_HPMIXL */ +#define WM8962_IN4R_TO_HPMIXL_WIDTH 1 /* IN4R_TO_HPMIXL */ + +/* + * R101 (0x65) - Headphone Mixer (2) + */ +#define WM8962_HPMIXR_TO_HPOUTR_PGA 0x0080 /* HPMIXR_TO_HPOUTR_PGA */ +#define WM8962_HPMIXR_TO_HPOUTR_PGA_MASK 0x0080 /* HPMIXR_TO_HPOUTR_PGA */ +#define WM8962_HPMIXR_TO_HPOUTR_PGA_SHIFT 7 /* HPMIXR_TO_HPOUTR_PGA */ +#define WM8962_HPMIXR_TO_HPOUTR_PGA_WIDTH 1 /* HPMIXR_TO_HPOUTR_PGA */ +#define WM8962_DACL_TO_HPMIXR 0x0020 /* DACL_TO_HPMIXR */ +#define WM8962_DACL_TO_HPMIXR_MASK 0x0020 /* DACL_TO_HPMIXR */ +#define WM8962_DACL_TO_HPMIXR_SHIFT 5 /* DACL_TO_HPMIXR */ +#define WM8962_DACL_TO_HPMIXR_WIDTH 1 /* DACL_TO_HPMIXR */ +#define WM8962_DACR_TO_HPMIXR 0x0010 /* DACR_TO_HPMIXR */ +#define WM8962_DACR_TO_HPMIXR_MASK 0x0010 /* DACR_TO_HPMIXR */ +#define WM8962_DACR_TO_HPMIXR_SHIFT 4 /* DACR_TO_HPMIXR */ +#define WM8962_DACR_TO_HPMIXR_WIDTH 1 /* DACR_TO_HPMIXR */ +#define WM8962_MIXINL_TO_HPMIXR 0x0008 /* MIXINL_TO_HPMIXR */ +#define WM8962_MIXINL_TO_HPMIXR_MASK 0x0008 /* MIXINL_TO_HPMIXR */ +#define WM8962_MIXINL_TO_HPMIXR_SHIFT 3 /* MIXINL_TO_HPMIXR */ +#define WM8962_MIXINL_TO_HPMIXR_WIDTH 1 /* MIXINL_TO_HPMIXR */ +#define WM8962_MIXINR_TO_HPMIXR 0x0004 /* MIXINR_TO_HPMIXR */ +#define WM8962_MIXINR_TO_HPMIXR_MASK 0x0004 /* MIXINR_TO_HPMIXR */ +#define WM8962_MIXINR_TO_HPMIXR_SHIFT 2 /* MIXINR_TO_HPMIXR */ +#define WM8962_MIXINR_TO_HPMIXR_WIDTH 1 /* MIXINR_TO_HPMIXR */ +#define WM8962_IN4L_TO_HPMIXR 0x0002 /* IN4L_TO_HPMIXR */ +#define WM8962_IN4L_TO_HPMIXR_MASK 0x0002 /* IN4L_TO_HPMIXR */ +#define WM8962_IN4L_TO_HPMIXR_SHIFT 1 /* IN4L_TO_HPMIXR */ +#define WM8962_IN4L_TO_HPMIXR_WIDTH 1 /* IN4L_TO_HPMIXR */ +#define WM8962_IN4R_TO_HPMIXR 0x0001 /* IN4R_TO_HPMIXR */ +#define WM8962_IN4R_TO_HPMIXR_MASK 0x0001 /* IN4R_TO_HPMIXR */ +#define WM8962_IN4R_TO_HPMIXR_SHIFT 0 /* IN4R_TO_HPMIXR */ +#define WM8962_IN4R_TO_HPMIXR_WIDTH 1 /* IN4R_TO_HPMIXR */ + +/* + * R102 (0x66) - Headphone Mixer (3) + */ +#define WM8962_HPMIXL_MUTE 0x0100 /* HPMIXL_MUTE */ +#define WM8962_HPMIXL_MUTE_MASK 0x0100 /* HPMIXL_MUTE */ +#define WM8962_HPMIXL_MUTE_SHIFT 8 /* HPMIXL_MUTE */ +#define WM8962_HPMIXL_MUTE_WIDTH 1 /* HPMIXL_MUTE */ +#define WM8962_MIXINL_HPMIXL_VOL 0x0080 /* MIXINL_HPMIXL_VOL */ +#define WM8962_MIXINL_HPMIXL_VOL_MASK 0x0080 /* MIXINL_HPMIXL_VOL */ +#define WM8962_MIXINL_HPMIXL_VOL_SHIFT 7 /* MIXINL_HPMIXL_VOL */ +#define WM8962_MIXINL_HPMIXL_VOL_WIDTH 1 /* MIXINL_HPMIXL_VOL */ +#define WM8962_MIXINR_HPMIXL_VOL 0x0040 /* MIXINR_HPMIXL_VOL */ +#define WM8962_MIXINR_HPMIXL_VOL_MASK 0x0040 /* MIXINR_HPMIXL_VOL */ +#define WM8962_MIXINR_HPMIXL_VOL_SHIFT 6 /* MIXINR_HPMIXL_VOL */ +#define WM8962_MIXINR_HPMIXL_VOL_WIDTH 1 /* MIXINR_HPMIXL_VOL */ +#define WM8962_IN4L_HPMIXL_VOL_MASK 0x0038 /* IN4L_HPMIXL_VOL - [5:3] */ +#define WM8962_IN4L_HPMIXL_VOL_SHIFT 3 /* IN4L_HPMIXL_VOL - [5:3] */ +#define WM8962_IN4L_HPMIXL_VOL_WIDTH 3 /* IN4L_HPMIXL_VOL - [5:3] */ +#define WM8962_IN4R_HPMIXL_VOL_MASK 0x0007 /* IN4R_HPMIXL_VOL - [2:0] */ +#define WM8962_IN4R_HPMIXL_VOL_SHIFT 0 /* IN4R_HPMIXL_VOL - [2:0] */ +#define WM8962_IN4R_HPMIXL_VOL_WIDTH 3 /* IN4R_HPMIXL_VOL - [2:0] */ + +/* + * R103 (0x67) - Headphone Mixer (4) + */ +#define WM8962_HPMIXR_MUTE 0x0100 /* HPMIXR_MUTE */ +#define WM8962_HPMIXR_MUTE_MASK 0x0100 /* HPMIXR_MUTE */ +#define WM8962_HPMIXR_MUTE_SHIFT 8 /* HPMIXR_MUTE */ +#define WM8962_HPMIXR_MUTE_WIDTH 1 /* HPMIXR_MUTE */ +#define WM8962_MIXINL_HPMIXR_VOL 0x0080 /* MIXINL_HPMIXR_VOL */ +#define WM8962_MIXINL_HPMIXR_VOL_MASK 0x0080 /* MIXINL_HPMIXR_VOL */ +#define WM8962_MIXINL_HPMIXR_VOL_SHIFT 7 /* MIXINL_HPMIXR_VOL */ +#define WM8962_MIXINL_HPMIXR_VOL_WIDTH 1 /* MIXINL_HPMIXR_VOL */ +#define WM8962_MIXINR_HPMIXR_VOL 0x0040 /* MIXINR_HPMIXR_VOL */ +#define WM8962_MIXINR_HPMIXR_VOL_MASK 0x0040 /* MIXINR_HPMIXR_VOL */ +#define WM8962_MIXINR_HPMIXR_VOL_SHIFT 6 /* MIXINR_HPMIXR_VOL */ +#define WM8962_MIXINR_HPMIXR_VOL_WIDTH 1 /* MIXINR_HPMIXR_VOL */ +#define WM8962_IN4L_HPMIXR_VOL_MASK 0x0038 /* IN4L_HPMIXR_VOL - [5:3] */ +#define WM8962_IN4L_HPMIXR_VOL_SHIFT 3 /* IN4L_HPMIXR_VOL - [5:3] */ +#define WM8962_IN4L_HPMIXR_VOL_WIDTH 3 /* IN4L_HPMIXR_VOL - [5:3] */ +#define WM8962_IN4R_HPMIXR_VOL_MASK 0x0007 /* IN4R_HPMIXR_VOL - [2:0] */ +#define WM8962_IN4R_HPMIXR_VOL_SHIFT 0 /* IN4R_HPMIXR_VOL - [2:0] */ +#define WM8962_IN4R_HPMIXR_VOL_WIDTH 3 /* IN4R_HPMIXR_VOL - [2:0] */ + +/* + * R105 (0x69) - Speaker Mixer (1) + */ +#define WM8962_SPKMIXL_TO_SPKOUTL_PGA 0x0080 /* SPKMIXL_TO_SPKOUTL_PGA */ +#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_MASK 0x0080 /* SPKMIXL_TO_SPKOUTL_PGA */ +#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_SHIFT 7 /* SPKMIXL_TO_SPKOUTL_PGA */ +#define WM8962_SPKMIXL_TO_SPKOUTL_PGA_WIDTH 1 /* SPKMIXL_TO_SPKOUTL_PGA */ +#define WM8962_DACL_TO_SPKMIXL 0x0020 /* DACL_TO_SPKMIXL */ +#define WM8962_DACL_TO_SPKMIXL_MASK 0x0020 /* DACL_TO_SPKMIXL */ +#define WM8962_DACL_TO_SPKMIXL_SHIFT 5 /* DACL_TO_SPKMIXL */ +#define WM8962_DACL_TO_SPKMIXL_WIDTH 1 /* DACL_TO_SPKMIXL */ +#define WM8962_DACR_TO_SPKMIXL 0x0010 /* DACR_TO_SPKMIXL */ +#define WM8962_DACR_TO_SPKMIXL_MASK 0x0010 /* DACR_TO_SPKMIXL */ +#define WM8962_DACR_TO_SPKMIXL_SHIFT 4 /* DACR_TO_SPKMIXL */ +#define WM8962_DACR_TO_SPKMIXL_WIDTH 1 /* DACR_TO_SPKMIXL */ +#define WM8962_MIXINL_TO_SPKMIXL 0x0008 /* MIXINL_TO_SPKMIXL */ +#define WM8962_MIXINL_TO_SPKMIXL_MASK 0x0008 /* MIXINL_TO_SPKMIXL */ +#define WM8962_MIXINL_TO_SPKMIXL_SHIFT 3 /* MIXINL_TO_SPKMIXL */ +#define WM8962_MIXINL_TO_SPKMIXL_WIDTH 1 /* MIXINL_TO_SPKMIXL */ +#define WM8962_MIXINR_TO_SPKMIXL 0x0004 /* MIXINR_TO_SPKMIXL */ +#define WM8962_MIXINR_TO_SPKMIXL_MASK 0x0004 /* MIXINR_TO_SPKMIXL */ +#define WM8962_MIXINR_TO_SPKMIXL_SHIFT 2 /* MIXINR_TO_SPKMIXL */ +#define WM8962_MIXINR_TO_SPKMIXL_WIDTH 1 /* MIXINR_TO_SPKMIXL */ +#define WM8962_IN4L_TO_SPKMIXL 0x0002 /* IN4L_TO_SPKMIXL */ +#define WM8962_IN4L_TO_SPKMIXL_MASK 0x0002 /* IN4L_TO_SPKMIXL */ +#define WM8962_IN4L_TO_SPKMIXL_SHIFT 1 /* IN4L_TO_SPKMIXL */ +#define WM8962_IN4L_TO_SPKMIXL_WIDTH 1 /* IN4L_TO_SPKMIXL */ +#define WM8962_IN4R_TO_SPKMIXL 0x0001 /* IN4R_TO_SPKMIXL */ +#define WM8962_IN4R_TO_SPKMIXL_MASK 0x0001 /* IN4R_TO_SPKMIXL */ +#define WM8962_IN4R_TO_SPKMIXL_SHIFT 0 /* IN4R_TO_SPKMIXL */ +#define WM8962_IN4R_TO_SPKMIXL_WIDTH 1 /* IN4R_TO_SPKMIXL */ + +/* + * R106 (0x6A) - Speaker Mixer (2) + */ +#define WM8962_SPKMIXR_TO_SPKOUTR_PGA 0x0080 /* SPKMIXR_TO_SPKOUTR_PGA */ +#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_MASK 0x0080 /* SPKMIXR_TO_SPKOUTR_PGA */ +#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_SHIFT 7 /* SPKMIXR_TO_SPKOUTR_PGA */ +#define WM8962_SPKMIXR_TO_SPKOUTR_PGA_WIDTH 1 /* SPKMIXR_TO_SPKOUTR_PGA */ +#define WM8962_DACL_TO_SPKMIXR 0x0020 /* DACL_TO_SPKMIXR */ +#define WM8962_DACL_TO_SPKMIXR_MASK 0x0020 /* DACL_TO_SPKMIXR */ +#define WM8962_DACL_TO_SPKMIXR_SHIFT 5 /* DACL_TO_SPKMIXR */ +#define WM8962_DACL_TO_SPKMIXR_WIDTH 1 /* DACL_TO_SPKMIXR */ +#define WM8962_DACR_TO_SPKMIXR 0x0010 /* DACR_TO_SPKMIXR */ +#define WM8962_DACR_TO_SPKMIXR_MASK 0x0010 /* DACR_TO_SPKMIXR */ +#define WM8962_DACR_TO_SPKMIXR_SHIFT 4 /* DACR_TO_SPKMIXR */ +#define WM8962_DACR_TO_SPKMIXR_WIDTH 1 /* DACR_TO_SPKMIXR */ +#define WM8962_MIXINL_TO_SPKMIXR 0x0008 /* MIXINL_TO_SPKMIXR */ +#define WM8962_MIXINL_TO_SPKMIXR_MASK 0x0008 /* MIXINL_TO_SPKMIXR */ +#define WM8962_MIXINL_TO_SPKMIXR_SHIFT 3 /* MIXINL_TO_SPKMIXR */ +#define WM8962_MIXINL_TO_SPKMIXR_WIDTH 1 /* MIXINL_TO_SPKMIXR */ +#define WM8962_MIXINR_TO_SPKMIXR 0x0004 /* MIXINR_TO_SPKMIXR */ +#define WM8962_MIXINR_TO_SPKMIXR_MASK 0x0004 /* MIXINR_TO_SPKMIXR */ +#define WM8962_MIXINR_TO_SPKMIXR_SHIFT 2 /* MIXINR_TO_SPKMIXR */ +#define WM8962_MIXINR_TO_SPKMIXR_WIDTH 1 /* MIXINR_TO_SPKMIXR */ +#define WM8962_IN4L_TO_SPKMIXR 0x0002 /* IN4L_TO_SPKMIXR */ +#define WM8962_IN4L_TO_SPKMIXR_MASK 0x0002 /* IN4L_TO_SPKMIXR */ +#define WM8962_IN4L_TO_SPKMIXR_SHIFT 1 /* IN4L_TO_SPKMIXR */ +#define WM8962_IN4L_TO_SPKMIXR_WIDTH 1 /* IN4L_TO_SPKMIXR */ +#define WM8962_IN4R_TO_SPKMIXR 0x0001 /* IN4R_TO_SPKMIXR */ +#define WM8962_IN4R_TO_SPKMIXR_MASK 0x0001 /* IN4R_TO_SPKMIXR */ +#define WM8962_IN4R_TO_SPKMIXR_SHIFT 0 /* IN4R_TO_SPKMIXR */ +#define WM8962_IN4R_TO_SPKMIXR_WIDTH 1 /* IN4R_TO_SPKMIXR */ + +/* + * R107 (0x6B) - Speaker Mixer (3) + */ +#define WM8962_SPKMIXL_MUTE 0x0100 /* SPKMIXL_MUTE */ +#define WM8962_SPKMIXL_MUTE_MASK 0x0100 /* SPKMIXL_MUTE */ +#define WM8962_SPKMIXL_MUTE_SHIFT 8 /* SPKMIXL_MUTE */ +#define WM8962_SPKMIXL_MUTE_WIDTH 1 /* SPKMIXL_MUTE */ +#define WM8962_MIXINL_SPKMIXL_VOL 0x0080 /* MIXINL_SPKMIXL_VOL */ +#define WM8962_MIXINL_SPKMIXL_VOL_MASK 0x0080 /* MIXINL_SPKMIXL_VOL */ +#define WM8962_MIXINL_SPKMIXL_VOL_SHIFT 7 /* MIXINL_SPKMIXL_VOL */ +#define WM8962_MIXINL_SPKMIXL_VOL_WIDTH 1 /* MIXINL_SPKMIXL_VOL */ +#define WM8962_MIXINR_SPKMIXL_VOL 0x0040 /* MIXINR_SPKMIXL_VOL */ +#define WM8962_MIXINR_SPKMIXL_VOL_MASK 0x0040 /* MIXINR_SPKMIXL_VOL */ +#define WM8962_MIXINR_SPKMIXL_VOL_SHIFT 6 /* MIXINR_SPKMIXL_VOL */ +#define WM8962_MIXINR_SPKMIXL_VOL_WIDTH 1 /* MIXINR_SPKMIXL_VOL */ +#define WM8962_IN4L_SPKMIXL_VOL_MASK 0x0038 /* IN4L_SPKMIXL_VOL - [5:3] */ +#define WM8962_IN4L_SPKMIXL_VOL_SHIFT 3 /* IN4L_SPKMIXL_VOL - [5:3] */ +#define WM8962_IN4L_SPKMIXL_VOL_WIDTH 3 /* IN4L_SPKMIXL_VOL - [5:3] */ +#define WM8962_IN4R_SPKMIXL_VOL_MASK 0x0007 /* IN4R_SPKMIXL_VOL - [2:0] */ +#define WM8962_IN4R_SPKMIXL_VOL_SHIFT 0 /* IN4R_SPKMIXL_VOL - [2:0] */ +#define WM8962_IN4R_SPKMIXL_VOL_WIDTH 3 /* IN4R_SPKMIXL_VOL - [2:0] */ + +/* + * R108 (0x6C) - Speaker Mixer (4) + */ +#define WM8962_SPKMIXR_MUTE 0x0100 /* SPKMIXR_MUTE */ +#define WM8962_SPKMIXR_MUTE_MASK 0x0100 /* SPKMIXR_MUTE */ +#define WM8962_SPKMIXR_MUTE_SHIFT 8 /* SPKMIXR_MUTE */ +#define WM8962_SPKMIXR_MUTE_WIDTH 1 /* SPKMIXR_MUTE */ +#define WM8962_MIXINL_SPKMIXR_VOL 0x0080 /* MIXINL_SPKMIXR_VOL */ +#define WM8962_MIXINL_SPKMIXR_VOL_MASK 0x0080 /* MIXINL_SPKMIXR_VOL */ +#define WM8962_MIXINL_SPKMIXR_VOL_SHIFT 7 /* MIXINL_SPKMIXR_VOL */ +#define WM8962_MIXINL_SPKMIXR_VOL_WIDTH 1 /* MIXINL_SPKMIXR_VOL */ +#define WM8962_MIXINR_SPKMIXR_VOL 0x0040 /* MIXINR_SPKMIXR_VOL */ +#define WM8962_MIXINR_SPKMIXR_VOL_MASK 0x0040 /* MIXINR_SPKMIXR_VOL */ +#define WM8962_MIXINR_SPKMIXR_VOL_SHIFT 6 /* MIXINR_SPKMIXR_VOL */ +#define WM8962_MIXINR_SPKMIXR_VOL_WIDTH 1 /* MIXINR_SPKMIXR_VOL */ +#define WM8962_IN4L_SPKMIXR_VOL_MASK 0x0038 /* IN4L_SPKMIXR_VOL - [5:3] */ +#define WM8962_IN4L_SPKMIXR_VOL_SHIFT 3 /* IN4L_SPKMIXR_VOL - [5:3] */ +#define WM8962_IN4L_SPKMIXR_VOL_WIDTH 3 /* IN4L_SPKMIXR_VOL - [5:3] */ +#define WM8962_IN4R_SPKMIXR_VOL_MASK 0x0007 /* IN4R_SPKMIXR_VOL - [2:0] */ +#define WM8962_IN4R_SPKMIXR_VOL_SHIFT 0 /* IN4R_SPKMIXR_VOL - [2:0] */ +#define WM8962_IN4R_SPKMIXR_VOL_WIDTH 3 /* IN4R_SPKMIXR_VOL - [2:0] */ + +/* + * R109 (0x6D) - Speaker Mixer (5) + */ +#define WM8962_DACL_SPKMIXL_VOL 0x0080 /* DACL_SPKMIXL_VOL */ +#define WM8962_DACL_SPKMIXL_VOL_MASK 0x0080 /* DACL_SPKMIXL_VOL */ +#define WM8962_DACL_SPKMIXL_VOL_SHIFT 7 /* DACL_SPKMIXL_VOL */ +#define WM8962_DACL_SPKMIXL_VOL_WIDTH 1 /* DACL_SPKMIXL_VOL */ +#define WM8962_DACR_SPKMIXL_VOL 0x0040 /* DACR_SPKMIXL_VOL */ +#define WM8962_DACR_SPKMIXL_VOL_MASK 0x0040 /* DACR_SPKMIXL_VOL */ +#define WM8962_DACR_SPKMIXL_VOL_SHIFT 6 /* DACR_SPKMIXL_VOL */ +#define WM8962_DACR_SPKMIXL_VOL_WIDTH 1 /* DACR_SPKMIXL_VOL */ +#define WM8962_DACL_SPKMIXR_VOL 0x0020 /* DACL_SPKMIXR_VOL */ +#define WM8962_DACL_SPKMIXR_VOL_MASK 0x0020 /* DACL_SPKMIXR_VOL */ +#define WM8962_DACL_SPKMIXR_VOL_SHIFT 5 /* DACL_SPKMIXR_VOL */ +#define WM8962_DACL_SPKMIXR_VOL_WIDTH 1 /* DACL_SPKMIXR_VOL */ +#define WM8962_DACR_SPKMIXR_VOL 0x0010 /* DACR_SPKMIXR_VOL */ +#define WM8962_DACR_SPKMIXR_VOL_MASK 0x0010 /* DACR_SPKMIXR_VOL */ +#define WM8962_DACR_SPKMIXR_VOL_SHIFT 4 /* DACR_SPKMIXR_VOL */ +#define WM8962_DACR_SPKMIXR_VOL_WIDTH 1 /* DACR_SPKMIXR_VOL */ + +/* + * R110 (0x6E) - Beep Generator (1) + */ +#define WM8962_BEEP_GAIN_MASK 0x00F0 /* BEEP_GAIN - [7:4] */ +#define WM8962_BEEP_GAIN_SHIFT 4 /* BEEP_GAIN - [7:4] */ +#define WM8962_BEEP_GAIN_WIDTH 4 /* BEEP_GAIN - [7:4] */ +#define WM8962_BEEP_RATE_MASK 0x0006 /* BEEP_RATE - [2:1] */ +#define WM8962_BEEP_RATE_SHIFT 1 /* BEEP_RATE - [2:1] */ +#define WM8962_BEEP_RATE_WIDTH 2 /* BEEP_RATE - [2:1] */ +#define WM8962_BEEP_ENA 0x0001 /* BEEP_ENA */ +#define WM8962_BEEP_ENA_MASK 0x0001 /* BEEP_ENA */ +#define WM8962_BEEP_ENA_SHIFT 0 /* BEEP_ENA */ +#define WM8962_BEEP_ENA_WIDTH 1 /* BEEP_ENA */ + +/* + * R115 (0x73) - Oscillator Trim (3) + */ +#define WM8962_OSC_TRIM_XTI_MASK 0x001F /* OSC_TRIM_XTI - [4:0] */ +#define WM8962_OSC_TRIM_XTI_SHIFT 0 /* OSC_TRIM_XTI - [4:0] */ +#define WM8962_OSC_TRIM_XTI_WIDTH 5 /* OSC_TRIM_XTI - [4:0] */ + +/* + * R116 (0x74) - Oscillator Trim (4) + */ +#define WM8962_OSC_TRIM_XTO_MASK 0x001F /* OSC_TRIM_XTO - [4:0] */ +#define WM8962_OSC_TRIM_XTO_SHIFT 0 /* OSC_TRIM_XTO - [4:0] */ +#define WM8962_OSC_TRIM_XTO_WIDTH 5 /* OSC_TRIM_XTO - [4:0] */ + +/* + * R119 (0x77) - Oscillator Trim (7) + */ +#define WM8962_XTO_CAP_SEL_MASK 0x00F0 /* XTO_CAP_SEL - [7:4] */ +#define WM8962_XTO_CAP_SEL_SHIFT 4 /* XTO_CAP_SEL - [7:4] */ +#define WM8962_XTO_CAP_SEL_WIDTH 4 /* XTO_CAP_SEL - [7:4] */ +#define WM8962_XTI_CAP_SEL_MASK 0x000F /* XTI_CAP_SEL - [3:0] */ +#define WM8962_XTI_CAP_SEL_SHIFT 0 /* XTI_CAP_SEL - [3:0] */ +#define WM8962_XTI_CAP_SEL_WIDTH 4 /* XTI_CAP_SEL - [3:0] */ + +/* + * R124 (0x7C) - Analogue Clocking1 + */ +#define WM8962_CLKOUT2_SEL_MASK 0x0060 /* CLKOUT2_SEL - [6:5] */ +#define WM8962_CLKOUT2_SEL_SHIFT 5 /* CLKOUT2_SEL - [6:5] */ +#define WM8962_CLKOUT2_SEL_WIDTH 2 /* CLKOUT2_SEL - [6:5] */ +#define WM8962_CLKOUT3_SEL_MASK 0x0018 /* CLKOUT3_SEL - [4:3] */ +#define WM8962_CLKOUT3_SEL_SHIFT 3 /* CLKOUT3_SEL - [4:3] */ +#define WM8962_CLKOUT3_SEL_WIDTH 2 /* CLKOUT3_SEL - [4:3] */ +#define WM8962_CLKOUT5_SEL 0x0001 /* CLKOUT5_SEL */ +#define WM8962_CLKOUT5_SEL_MASK 0x0001 /* CLKOUT5_SEL */ +#define WM8962_CLKOUT5_SEL_SHIFT 0 /* CLKOUT5_SEL */ +#define WM8962_CLKOUT5_SEL_WIDTH 1 /* CLKOUT5_SEL */ + +/* + * R125 (0x7D) - Analogue Clocking2 + */ +#define WM8962_PLL2_OUTDIV 0x0080 /* PLL2_OUTDIV */ +#define WM8962_PLL2_OUTDIV_MASK 0x0080 /* PLL2_OUTDIV */ +#define WM8962_PLL2_OUTDIV_SHIFT 7 /* PLL2_OUTDIV */ +#define WM8962_PLL2_OUTDIV_WIDTH 1 /* PLL2_OUTDIV */ +#define WM8962_PLL3_OUTDIV 0x0040 /* PLL3_OUTDIV */ +#define WM8962_PLL3_OUTDIV_MASK 0x0040 /* PLL3_OUTDIV */ +#define WM8962_PLL3_OUTDIV_SHIFT 6 /* PLL3_OUTDIV */ +#define WM8962_PLL3_OUTDIV_WIDTH 1 /* PLL3_OUTDIV */ +#define WM8962_PLL_SYSCLK_DIV_MASK 0x0018 /* PLL_SYSCLK_DIV - [4:3] */ +#define WM8962_PLL_SYSCLK_DIV_SHIFT 3 /* PLL_SYSCLK_DIV - [4:3] */ +#define WM8962_PLL_SYSCLK_DIV_WIDTH 2 /* PLL_SYSCLK_DIV - [4:3] */ +#define WM8962_CLKOUT3_DIV 0x0004 /* CLKOUT3_DIV */ +#define WM8962_CLKOUT3_DIV_MASK 0x0004 /* CLKOUT3_DIV */ +#define WM8962_CLKOUT3_DIV_SHIFT 2 /* CLKOUT3_DIV */ +#define WM8962_CLKOUT3_DIV_WIDTH 1 /* CLKOUT3_DIV */ +#define WM8962_CLKOUT2_DIV 0x0002 /* CLKOUT2_DIV */ +#define WM8962_CLKOUT2_DIV_MASK 0x0002 /* CLKOUT2_DIV */ +#define WM8962_CLKOUT2_DIV_SHIFT 1 /* CLKOUT2_DIV */ +#define WM8962_CLKOUT2_DIV_WIDTH 1 /* CLKOUT2_DIV */ +#define WM8962_CLKOUT5_DIV 0x0001 /* CLKOUT5_DIV */ +#define WM8962_CLKOUT5_DIV_MASK 0x0001 /* CLKOUT5_DIV */ +#define WM8962_CLKOUT5_DIV_SHIFT 0 /* CLKOUT5_DIV */ +#define WM8962_CLKOUT5_DIV_WIDTH 1 /* CLKOUT5_DIV */ + +/* + * R126 (0x7E) - Analogue Clocking3 + */ +#define WM8962_CLKOUT2_OE 0x0008 /* CLKOUT2_OE */ +#define WM8962_CLKOUT2_OE_MASK 0x0008 /* CLKOUT2_OE */ +#define WM8962_CLKOUT2_OE_SHIFT 3 /* CLKOUT2_OE */ +#define WM8962_CLKOUT2_OE_WIDTH 1 /* CLKOUT2_OE */ +#define WM8962_CLKOUT3_OE 0x0004 /* CLKOUT3_OE */ +#define WM8962_CLKOUT3_OE_MASK 0x0004 /* CLKOUT3_OE */ +#define WM8962_CLKOUT3_OE_SHIFT 2 /* CLKOUT3_OE */ +#define WM8962_CLKOUT3_OE_WIDTH 1 /* CLKOUT3_OE */ +#define WM8962_CLKOUT5_OE 0x0001 /* CLKOUT5_OE */ +#define WM8962_CLKOUT5_OE_MASK 0x0001 /* CLKOUT5_OE */ +#define WM8962_CLKOUT5_OE_SHIFT 0 /* CLKOUT5_OE */ +#define WM8962_CLKOUT5_OE_WIDTH 1 /* CLKOUT5_OE */ + +/* + * R127 (0x7F) - PLL Software Reset + */ +#define WM8962_SW_RESET_PLL_MASK 0xFFFF /* SW_RESET_PLL - [15:0] */ +#define WM8962_SW_RESET_PLL_SHIFT 0 /* SW_RESET_PLL - [15:0] */ +#define WM8962_SW_RESET_PLL_WIDTH 16 /* SW_RESET_PLL - [15:0] */ + +/* + * R129 (0x81) - PLL2 + */ +#define WM8962_OSC_ENA 0x0080 /* OSC_ENA */ +#define WM8962_OSC_ENA_MASK 0x0080 /* OSC_ENA */ +#define WM8962_OSC_ENA_SHIFT 7 /* OSC_ENA */ +#define WM8962_OSC_ENA_WIDTH 1 /* OSC_ENA */ +#define WM8962_PLL2_ENA 0x0020 /* PLL2_ENA */ +#define WM8962_PLL2_ENA_MASK 0x0020 /* PLL2_ENA */ +#define WM8962_PLL2_ENA_SHIFT 5 /* PLL2_ENA */ +#define WM8962_PLL2_ENA_WIDTH 1 /* PLL2_ENA */ +#define WM8962_PLL3_ENA 0x0010 /* PLL3_ENA */ +#define WM8962_PLL3_ENA_MASK 0x0010 /* PLL3_ENA */ +#define WM8962_PLL3_ENA_SHIFT 4 /* PLL3_ENA */ +#define WM8962_PLL3_ENA_WIDTH 1 /* PLL3_ENA */ + +/* + * R131 (0x83) - PLL 4 + */ +#define WM8962_PLL_CLK_SRC 0x0002 /* PLL_CLK_SRC */ +#define WM8962_PLL_CLK_SRC_MASK 0x0002 /* PLL_CLK_SRC */ +#define WM8962_PLL_CLK_SRC_SHIFT 1 /* PLL_CLK_SRC */ +#define WM8962_PLL_CLK_SRC_WIDTH 1 /* PLL_CLK_SRC */ +#define WM8962_FLL_TO_PLL3 0x0001 /* FLL_TO_PLL3 */ +#define WM8962_FLL_TO_PLL3_MASK 0x0001 /* FLL_TO_PLL3 */ +#define WM8962_FLL_TO_PLL3_SHIFT 0 /* FLL_TO_PLL3 */ +#define WM8962_FLL_TO_PLL3_WIDTH 1 /* FLL_TO_PLL3 */ + +/* + * R136 (0x88) - PLL 9 + */ +#define WM8962_PLL2_FRAC 0x0040 /* PLL2_FRAC */ +#define WM8962_PLL2_FRAC_MASK 0x0040 /* PLL2_FRAC */ +#define WM8962_PLL2_FRAC_SHIFT 6 /* PLL2_FRAC */ +#define WM8962_PLL2_FRAC_WIDTH 1 /* PLL2_FRAC */ +#define WM8962_PLL2_N_MASK 0x001F /* PLL2_N - [4:0] */ +#define WM8962_PLL2_N_SHIFT 0 /* PLL2_N - [4:0] */ +#define WM8962_PLL2_N_WIDTH 5 /* PLL2_N - [4:0] */ + +/* + * R137 (0x89) - PLL 10 + */ +#define WM8962_PLL2_K_MASK 0x00FF /* PLL2_K - [7:0] */ +#define WM8962_PLL2_K_SHIFT 0 /* PLL2_K - [7:0] */ +#define WM8962_PLL2_K_WIDTH 8 /* PLL2_K - [7:0] */ + +/* + * R138 (0x8A) - PLL 11 + */ +#define WM8962_PLL2_K_MASK 0x00FF /* PLL2_K - [7:0] */ +#define WM8962_PLL2_K_SHIFT 0 /* PLL2_K - [7:0] */ +#define WM8962_PLL2_K_WIDTH 8 /* PLL2_K - [7:0] */ + +/* + * R139 (0x8B) - PLL 12 + */ +#define WM8962_PLL2_K_MASK 0x00FF /* PLL2_K - [7:0] */ +#define WM8962_PLL2_K_SHIFT 0 /* PLL2_K - [7:0] */ +#define WM8962_PLL2_K_WIDTH 8 /* PLL2_K - [7:0] */ + +/* + * R140 (0x8C) - PLL 13 + */ +#define WM8962_PLL3_FRAC 0x0040 /* PLL3_FRAC */ +#define WM8962_PLL3_FRAC_MASK 0x0040 /* PLL3_FRAC */ +#define WM8962_PLL3_FRAC_SHIFT 6 /* PLL3_FRAC */ +#define WM8962_PLL3_FRAC_WIDTH 1 /* PLL3_FRAC */ +#define WM8962_PLL3_N_MASK 0x001F /* PLL3_N - [4:0] */ +#define WM8962_PLL3_N_SHIFT 0 /* PLL3_N - [4:0] */ +#define WM8962_PLL3_N_WIDTH 5 /* PLL3_N - [4:0] */ + +/* + * R141 (0x8D) - PLL 14 + */ +#define WM8962_PLL3_K_MASK 0x00FF /* PLL3_K - [7:0] */ +#define WM8962_PLL3_K_SHIFT 0 /* PLL3_K - [7:0] */ +#define WM8962_PLL3_K_WIDTH 8 /* PLL3_K - [7:0] */ + +/* + * R142 (0x8E) - PLL 15 + */ +#define WM8962_PLL3_K_MASK 0x00FF /* PLL3_K - [7:0] */ +#define WM8962_PLL3_K_SHIFT 0 /* PLL3_K - [7:0] */ +#define WM8962_PLL3_K_WIDTH 8 /* PLL3_K - [7:0] */ + +/* + * R143 (0x8F) - PLL 16 + */ +#define WM8962_PLL3_K_MASK 0x00FF /* PLL3_K - [7:0] */ +#define WM8962_PLL3_K_SHIFT 0 /* PLL3_K - [7:0] */ +#define WM8962_PLL3_K_WIDTH 8 /* PLL3_K - [7:0] */ + +/* + * R155 (0x9B) - FLL Control (1) + */ +#define WM8962_FLL_REFCLK_SRC_MASK 0x0060 /* FLL_REFCLK_SRC - [6:5] */ +#define WM8962_FLL_REFCLK_SRC_SHIFT 5 /* FLL_REFCLK_SRC - [6:5] */ +#define WM8962_FLL_REFCLK_SRC_WIDTH 2 /* FLL_REFCLK_SRC - [6:5] */ +#define WM8962_FLL_FRAC 0x0004 /* FLL_FRAC */ +#define WM8962_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */ +#define WM8962_FLL_FRAC_SHIFT 2 /* FLL_FRAC */ +#define WM8962_FLL_FRAC_WIDTH 1 /* FLL_FRAC */ +#define WM8962_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */ +#define WM8962_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */ +#define WM8962_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */ +#define WM8962_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */ +#define WM8962_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM8962_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM8962_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM8962_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R156 (0x9C) - FLL Control (2) + */ +#define WM8962_FLL_OUTDIV_MASK 0x01F8 /* FLL_OUTDIV - [8:3] */ +#define WM8962_FLL_OUTDIV_SHIFT 3 /* FLL_OUTDIV - [8:3] */ +#define WM8962_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [8:3] */ +#define WM8962_FLL_REFCLK_DIV_MASK 0x0003 /* FLL_REFCLK_DIV - [1:0] */ +#define WM8962_FLL_REFCLK_DIV_SHIFT 0 /* FLL_REFCLK_DIV - [1:0] */ +#define WM8962_FLL_REFCLK_DIV_WIDTH 2 /* FLL_REFCLK_DIV - [1:0] */ + +/* + * R157 (0x9D) - FLL Control (3) + */ +#define WM8962_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM8962_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM8962_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R159 (0x9F) - FLL Control (5) + */ +#define WM8962_FLL_FRC_NCO_VAL_MASK 0x007E /* FLL_FRC_NCO_VAL - [6:1] */ +#define WM8962_FLL_FRC_NCO_VAL_SHIFT 1 /* FLL_FRC_NCO_VAL - [6:1] */ +#define WM8962_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [6:1] */ +#define WM8962_FLL_FRC_NCO 0x0001 /* FLL_FRC_NCO */ +#define WM8962_FLL_FRC_NCO_MASK 0x0001 /* FLL_FRC_NCO */ +#define WM8962_FLL_FRC_NCO_SHIFT 0 /* FLL_FRC_NCO */ +#define WM8962_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */ + +/* + * R160 (0xA0) - FLL Control (6) + */ +#define WM8962_FLL_THETA_MASK 0xFFFF /* FLL_THETA - [15:0] */ +#define WM8962_FLL_THETA_SHIFT 0 /* FLL_THETA - [15:0] */ +#define WM8962_FLL_THETA_WIDTH 16 /* FLL_THETA - [15:0] */ + +/* + * R161 (0xA1) - FLL Control (7) + */ +#define WM8962_FLL_LAMBDA_MASK 0xFFFF /* FLL_LAMBDA - [15:0] */ +#define WM8962_FLL_LAMBDA_SHIFT 0 /* FLL_LAMBDA - [15:0] */ +#define WM8962_FLL_LAMBDA_WIDTH 16 /* FLL_LAMBDA - [15:0] */ + +/* + * R162 (0xA2) - FLL Control (8) + */ +#define WM8962_FLL_N_MASK 0x03FF /* FLL_N - [9:0] */ +#define WM8962_FLL_N_SHIFT 0 /* FLL_N - [9:0] */ +#define WM8962_FLL_N_WIDTH 10 /* FLL_N - [9:0] */ + +/* + * R252 (0xFC) - General test 1 + */ +#define WM8962_REG_SYNC 0x0004 /* REG_SYNC */ +#define WM8962_REG_SYNC_MASK 0x0004 /* REG_SYNC */ +#define WM8962_REG_SYNC_SHIFT 2 /* REG_SYNC */ +#define WM8962_REG_SYNC_WIDTH 1 /* REG_SYNC */ +#define WM8962_AUTO_INC 0x0001 /* AUTO_INC */ +#define WM8962_AUTO_INC_MASK 0x0001 /* AUTO_INC */ +#define WM8962_AUTO_INC_SHIFT 0 /* AUTO_INC */ +#define WM8962_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +/* + * R256 (0x100) - DF1 + */ +#define WM8962_DRC_DF1_ENA 0x0008 /* DRC_DF1_ENA */ +#define WM8962_DRC_DF1_ENA_MASK 0x0008 /* DRC_DF1_ENA */ +#define WM8962_DRC_DF1_ENA_SHIFT 3 /* DRC_DF1_ENA */ +#define WM8962_DRC_DF1_ENA_WIDTH 1 /* DRC_DF1_ENA */ +#define WM8962_DF1_SHARED_COEFF 0x0004 /* DF1_SHARED_COEFF */ +#define WM8962_DF1_SHARED_COEFF_MASK 0x0004 /* DF1_SHARED_COEFF */ +#define WM8962_DF1_SHARED_COEFF_SHIFT 2 /* DF1_SHARED_COEFF */ +#define WM8962_DF1_SHARED_COEFF_WIDTH 1 /* DF1_SHARED_COEFF */ +#define WM8962_DF1_SHARED_COEFF_SEL 0x0002 /* DF1_SHARED_COEFF_SEL */ +#define WM8962_DF1_SHARED_COEFF_SEL_MASK 0x0002 /* DF1_SHARED_COEFF_SEL */ +#define WM8962_DF1_SHARED_COEFF_SEL_SHIFT 1 /* DF1_SHARED_COEFF_SEL */ +#define WM8962_DF1_SHARED_COEFF_SEL_WIDTH 1 /* DF1_SHARED_COEFF_SEL */ +#define WM8962_DF1_ENA 0x0001 /* DF1_ENA */ +#define WM8962_DF1_ENA_MASK 0x0001 /* DF1_ENA */ +#define WM8962_DF1_ENA_SHIFT 0 /* DF1_ENA */ +#define WM8962_DF1_ENA_WIDTH 1 /* DF1_ENA */ + +/* + * R257 (0x101) - DF2 + */ +#define WM8962_DF1_COEFF_L0_MASK 0xFFFF /* DF1_COEFF_L0 - [15:0] */ +#define WM8962_DF1_COEFF_L0_SHIFT 0 /* DF1_COEFF_L0 - [15:0] */ +#define WM8962_DF1_COEFF_L0_WIDTH 16 /* DF1_COEFF_L0 - [15:0] */ + +/* + * R258 (0x102) - DF3 + */ +#define WM8962_DF1_COEFF_L1_MASK 0xFFFF /* DF1_COEFF_L1 - [15:0] */ +#define WM8962_DF1_COEFF_L1_SHIFT 0 /* DF1_COEFF_L1 - [15:0] */ +#define WM8962_DF1_COEFF_L1_WIDTH 16 /* DF1_COEFF_L1 - [15:0] */ + +/* + * R259 (0x103) - DF4 + */ +#define WM8962_DF1_COEFF_L2_MASK 0xFFFF /* DF1_COEFF_L2 - [15:0] */ +#define WM8962_DF1_COEFF_L2_SHIFT 0 /* DF1_COEFF_L2 - [15:0] */ +#define WM8962_DF1_COEFF_L2_WIDTH 16 /* DF1_COEFF_L2 - [15:0] */ + +/* + * R260 (0x104) - DF5 + */ +#define WM8962_DF1_COEFF_R0_MASK 0xFFFF /* DF1_COEFF_R0 - [15:0] */ +#define WM8962_DF1_COEFF_R0_SHIFT 0 /* DF1_COEFF_R0 - [15:0] */ +#define WM8962_DF1_COEFF_R0_WIDTH 16 /* DF1_COEFF_R0 - [15:0] */ + +/* + * R261 (0x105) - DF6 + */ +#define WM8962_DF1_COEFF_R1_MASK 0xFFFF /* DF1_COEFF_R1 - [15:0] */ +#define WM8962_DF1_COEFF_R1_SHIFT 0 /* DF1_COEFF_R1 - [15:0] */ +#define WM8962_DF1_COEFF_R1_WIDTH 16 /* DF1_COEFF_R1 - [15:0] */ + +/* + * R262 (0x106) - DF7 + */ +#define WM8962_DF1_COEFF_R2_MASK 0xFFFF /* DF1_COEFF_R2 - [15:0] */ +#define WM8962_DF1_COEFF_R2_SHIFT 0 /* DF1_COEFF_R2 - [15:0] */ +#define WM8962_DF1_COEFF_R2_WIDTH 16 /* DF1_COEFF_R2 - [15:0] */ + +/* + * R264 (0x108) - LHPF1 + */ +#define WM8962_LHPF_MODE 0x0002 /* LHPF_MODE */ +#define WM8962_LHPF_MODE_MASK 0x0002 /* LHPF_MODE */ +#define WM8962_LHPF_MODE_SHIFT 1 /* LHPF_MODE */ +#define WM8962_LHPF_MODE_WIDTH 1 /* LHPF_MODE */ +#define WM8962_LHPF_ENA 0x0001 /* LHPF_ENA */ +#define WM8962_LHPF_ENA_MASK 0x0001 /* LHPF_ENA */ +#define WM8962_LHPF_ENA_SHIFT 0 /* LHPF_ENA */ +#define WM8962_LHPF_ENA_WIDTH 1 /* LHPF_ENA */ + +/* + * R265 (0x109) - LHPF2 + */ +#define WM8962_LHPF_COEFF_MASK 0xFFFF /* LHPF_COEFF - [15:0] */ +#define WM8962_LHPF_COEFF_SHIFT 0 /* LHPF_COEFF - [15:0] */ +#define WM8962_LHPF_COEFF_WIDTH 16 /* LHPF_COEFF - [15:0] */ + +/* + * R268 (0x10C) - THREED1 + */ +#define WM8962_ADC_MONOMIX 0x0040 /* ADC_MONOMIX */ +#define WM8962_ADC_MONOMIX_MASK 0x0040 /* ADC_MONOMIX */ +#define WM8962_ADC_MONOMIX_SHIFT 6 /* ADC_MONOMIX */ +#define WM8962_ADC_MONOMIX_WIDTH 1 /* ADC_MONOMIX */ +#define WM8962_THREED_SIGN_L 0x0020 /* THREED_SIGN_L */ +#define WM8962_THREED_SIGN_L_MASK 0x0020 /* THREED_SIGN_L */ +#define WM8962_THREED_SIGN_L_SHIFT 5 /* THREED_SIGN_L */ +#define WM8962_THREED_SIGN_L_WIDTH 1 /* THREED_SIGN_L */ +#define WM8962_THREED_SIGN_R 0x0010 /* THREED_SIGN_R */ +#define WM8962_THREED_SIGN_R_MASK 0x0010 /* THREED_SIGN_R */ +#define WM8962_THREED_SIGN_R_SHIFT 4 /* THREED_SIGN_R */ +#define WM8962_THREED_SIGN_R_WIDTH 1 /* THREED_SIGN_R */ +#define WM8962_THREED_LHPF_MODE 0x0004 /* THREED_LHPF_MODE */ +#define WM8962_THREED_LHPF_MODE_MASK 0x0004 /* THREED_LHPF_MODE */ +#define WM8962_THREED_LHPF_MODE_SHIFT 2 /* THREED_LHPF_MODE */ +#define WM8962_THREED_LHPF_MODE_WIDTH 1 /* THREED_LHPF_MODE */ +#define WM8962_THREED_LHPF_ENA 0x0002 /* THREED_LHPF_ENA */ +#define WM8962_THREED_LHPF_ENA_MASK 0x0002 /* THREED_LHPF_ENA */ +#define WM8962_THREED_LHPF_ENA_SHIFT 1 /* THREED_LHPF_ENA */ +#define WM8962_THREED_LHPF_ENA_WIDTH 1 /* THREED_LHPF_ENA */ +#define WM8962_THREED_ENA 0x0001 /* THREED_ENA */ +#define WM8962_THREED_ENA_MASK 0x0001 /* THREED_ENA */ +#define WM8962_THREED_ENA_SHIFT 0 /* THREED_ENA */ +#define WM8962_THREED_ENA_WIDTH 1 /* THREED_ENA */ + +/* + * R269 (0x10D) - THREED2 + */ +#define WM8962_THREED_FGAINL_MASK 0xF800 /* THREED_FGAINL - [15:11] */ +#define WM8962_THREED_FGAINL_SHIFT 11 /* THREED_FGAINL - [15:11] */ +#define WM8962_THREED_FGAINL_WIDTH 5 /* THREED_FGAINL - [15:11] */ +#define WM8962_THREED_CGAINL_MASK 0x07C0 /* THREED_CGAINL - [10:6] */ +#define WM8962_THREED_CGAINL_SHIFT 6 /* THREED_CGAINL - [10:6] */ +#define WM8962_THREED_CGAINL_WIDTH 5 /* THREED_CGAINL - [10:6] */ +#define WM8962_THREED_DELAYL_MASK 0x003C /* THREED_DELAYL - [5:2] */ +#define WM8962_THREED_DELAYL_SHIFT 2 /* THREED_DELAYL - [5:2] */ +#define WM8962_THREED_DELAYL_WIDTH 4 /* THREED_DELAYL - [5:2] */ + +/* + * R270 (0x10E) - THREED3 + */ +#define WM8962_THREED_LHPF_COEFF_MASK 0xFFFF /* THREED_LHPF_COEFF - [15:0] */ +#define WM8962_THREED_LHPF_COEFF_SHIFT 0 /* THREED_LHPF_COEFF - [15:0] */ +#define WM8962_THREED_LHPF_COEFF_WIDTH 16 /* THREED_LHPF_COEFF - [15:0] */ + +/* + * R271 (0x10F) - THREED4 + */ +#define WM8962_THREED_FGAINR_MASK 0xF800 /* THREED_FGAINR - [15:11] */ +#define WM8962_THREED_FGAINR_SHIFT 11 /* THREED_FGAINR - [15:11] */ +#define WM8962_THREED_FGAINR_WIDTH 5 /* THREED_FGAINR - [15:11] */ +#define WM8962_THREED_CGAINR_MASK 0x07C0 /* THREED_CGAINR - [10:6] */ +#define WM8962_THREED_CGAINR_SHIFT 6 /* THREED_CGAINR - [10:6] */ +#define WM8962_THREED_CGAINR_WIDTH 5 /* THREED_CGAINR - [10:6] */ +#define WM8962_THREED_DELAYR_MASK 0x003C /* THREED_DELAYR - [5:2] */ +#define WM8962_THREED_DELAYR_SHIFT 2 /* THREED_DELAYR - [5:2] */ +#define WM8962_THREED_DELAYR_WIDTH 4 /* THREED_DELAYR - [5:2] */ + +/* + * R276 (0x114) - DRC 1 + */ +#define WM8962_DRC_SIG_DET_RMS_MASK 0x7C00 /* DRC_SIG_DET_RMS - [14:10] */ +#define WM8962_DRC_SIG_DET_RMS_SHIFT 10 /* DRC_SIG_DET_RMS - [14:10] */ +#define WM8962_DRC_SIG_DET_RMS_WIDTH 5 /* DRC_SIG_DET_RMS - [14:10] */ +#define WM8962_DRC_SIG_DET_PK_MASK 0x0300 /* DRC_SIG_DET_PK - [9:8] */ +#define WM8962_DRC_SIG_DET_PK_SHIFT 8 /* DRC_SIG_DET_PK - [9:8] */ +#define WM8962_DRC_SIG_DET_PK_WIDTH 2 /* DRC_SIG_DET_PK - [9:8] */ +#define WM8962_DRC_NG_ENA 0x0080 /* DRC_NG_ENA */ +#define WM8962_DRC_NG_ENA_MASK 0x0080 /* DRC_NG_ENA */ +#define WM8962_DRC_NG_ENA_SHIFT 7 /* DRC_NG_ENA */ +#define WM8962_DRC_NG_ENA_WIDTH 1 /* DRC_NG_ENA */ +#define WM8962_DRC_SIG_DET_MODE 0x0040 /* DRC_SIG_DET_MODE */ +#define WM8962_DRC_SIG_DET_MODE_MASK 0x0040 /* DRC_SIG_DET_MODE */ +#define WM8962_DRC_SIG_DET_MODE_SHIFT 6 /* DRC_SIG_DET_MODE */ +#define WM8962_DRC_SIG_DET_MODE_WIDTH 1 /* DRC_SIG_DET_MODE */ +#define WM8962_DRC_SIG_DET 0x0020 /* DRC_SIG_DET */ +#define WM8962_DRC_SIG_DET_MASK 0x0020 /* DRC_SIG_DET */ +#define WM8962_DRC_SIG_DET_SHIFT 5 /* DRC_SIG_DET */ +#define WM8962_DRC_SIG_DET_WIDTH 1 /* DRC_SIG_DET */ +#define WM8962_DRC_KNEE2_OP_ENA 0x0010 /* DRC_KNEE2_OP_ENA */ +#define WM8962_DRC_KNEE2_OP_ENA_MASK 0x0010 /* DRC_KNEE2_OP_ENA */ +#define WM8962_DRC_KNEE2_OP_ENA_SHIFT 4 /* DRC_KNEE2_OP_ENA */ +#define WM8962_DRC_KNEE2_OP_ENA_WIDTH 1 /* DRC_KNEE2_OP_ENA */ +#define WM8962_DRC_QR 0x0008 /* DRC_QR */ +#define WM8962_DRC_QR_MASK 0x0008 /* DRC_QR */ +#define WM8962_DRC_QR_SHIFT 3 /* DRC_QR */ +#define WM8962_DRC_QR_WIDTH 1 /* DRC_QR */ +#define WM8962_DRC_ANTICLIP 0x0004 /* DRC_ANTICLIP */ +#define WM8962_DRC_ANTICLIP_MASK 0x0004 /* DRC_ANTICLIP */ +#define WM8962_DRC_ANTICLIP_SHIFT 2 /* DRC_ANTICLIP */ +#define WM8962_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */ +#define WM8962_DRC_MODE 0x0002 /* DRC_MODE */ +#define WM8962_DRC_MODE_MASK 0x0002 /* DRC_MODE */ +#define WM8962_DRC_MODE_SHIFT 1 /* DRC_MODE */ +#define WM8962_DRC_MODE_WIDTH 1 /* DRC_MODE */ +#define WM8962_DRC_ENA 0x0001 /* DRC_ENA */ +#define WM8962_DRC_ENA_MASK 0x0001 /* DRC_ENA */ +#define WM8962_DRC_ENA_SHIFT 0 /* DRC_ENA */ +#define WM8962_DRC_ENA_WIDTH 1 /* DRC_ENA */ + +/* + * R277 (0x115) - DRC 2 + */ +#define WM8962_DRC_ATK_MASK 0x1E00 /* DRC_ATK - [12:9] */ +#define WM8962_DRC_ATK_SHIFT 9 /* DRC_ATK - [12:9] */ +#define WM8962_DRC_ATK_WIDTH 4 /* DRC_ATK - [12:9] */ +#define WM8962_DRC_DCY_MASK 0x01E0 /* DRC_DCY - [8:5] */ +#define WM8962_DRC_DCY_SHIFT 5 /* DRC_DCY - [8:5] */ +#define WM8962_DRC_DCY_WIDTH 4 /* DRC_DCY - [8:5] */ +#define WM8962_DRC_MINGAIN_MASK 0x001C /* DRC_MINGAIN - [4:2] */ +#define WM8962_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [4:2] */ +#define WM8962_DRC_MINGAIN_WIDTH 3 /* DRC_MINGAIN - [4:2] */ +#define WM8962_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8962_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8962_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R278 (0x116) - DRC 3 + */ +#define WM8962_DRC_NG_MINGAIN_MASK 0xF000 /* DRC_NG_MINGAIN - [15:12] */ +#define WM8962_DRC_NG_MINGAIN_SHIFT 12 /* DRC_NG_MINGAIN - [15:12] */ +#define WM8962_DRC_NG_MINGAIN_WIDTH 4 /* DRC_NG_MINGAIN - [15:12] */ +#define WM8962_DRC_QR_THR_MASK 0x0C00 /* DRC_QR_THR - [11:10] */ +#define WM8962_DRC_QR_THR_SHIFT 10 /* DRC_QR_THR - [11:10] */ +#define WM8962_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [11:10] */ +#define WM8962_DRC_QR_DCY_MASK 0x0300 /* DRC_QR_DCY - [9:8] */ +#define WM8962_DRC_QR_DCY_SHIFT 8 /* DRC_QR_DCY - [9:8] */ +#define WM8962_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [9:8] */ +#define WM8962_DRC_NG_EXP_MASK 0x00C0 /* DRC_NG_EXP - [7:6] */ +#define WM8962_DRC_NG_EXP_SHIFT 6 /* DRC_NG_EXP - [7:6] */ +#define WM8962_DRC_NG_EXP_WIDTH 2 /* DRC_NG_EXP - [7:6] */ +#define WM8962_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */ +#define WM8962_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */ +#define WM8962_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */ +#define WM8962_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */ +#define WM8962_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */ +#define WM8962_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */ + +/* + * R279 (0x117) - DRC 4 + */ +#define WM8962_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */ +#define WM8962_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */ +#define WM8962_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */ +#define WM8962_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */ +#define WM8962_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */ +#define WM8962_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */ + +/* + * R280 (0x118) - DRC 5 + */ +#define WM8962_DRC_KNEE2_IP_MASK 0x03E0 /* DRC_KNEE2_IP - [9:5] */ +#define WM8962_DRC_KNEE2_IP_SHIFT 5 /* DRC_KNEE2_IP - [9:5] */ +#define WM8962_DRC_KNEE2_IP_WIDTH 5 /* DRC_KNEE2_IP - [9:5] */ +#define WM8962_DRC_KNEE2_OP_MASK 0x001F /* DRC_KNEE2_OP - [4:0] */ +#define WM8962_DRC_KNEE2_OP_SHIFT 0 /* DRC_KNEE2_OP - [4:0] */ +#define WM8962_DRC_KNEE2_OP_WIDTH 5 /* DRC_KNEE2_OP - [4:0] */ + +/* + * R285 (0x11D) - Tloopback + */ +#define WM8962_TLB_ENA 0x0002 /* TLB_ENA */ +#define WM8962_TLB_ENA_MASK 0x0002 /* TLB_ENA */ +#define WM8962_TLB_ENA_SHIFT 1 /* TLB_ENA */ +#define WM8962_TLB_ENA_WIDTH 1 /* TLB_ENA */ +#define WM8962_TLB_MODE 0x0001 /* TLB_MODE */ +#define WM8962_TLB_MODE_MASK 0x0001 /* TLB_MODE */ +#define WM8962_TLB_MODE_SHIFT 0 /* TLB_MODE */ +#define WM8962_TLB_MODE_WIDTH 1 /* TLB_MODE */ + +/* + * R335 (0x14F) - EQ1 + */ +#define WM8962_EQ_SHARED_COEFF 0x0004 /* EQ_SHARED_COEFF */ +#define WM8962_EQ_SHARED_COEFF_MASK 0x0004 /* EQ_SHARED_COEFF */ +#define WM8962_EQ_SHARED_COEFF_SHIFT 2 /* EQ_SHARED_COEFF */ +#define WM8962_EQ_SHARED_COEFF_WIDTH 1 /* EQ_SHARED_COEFF */ +#define WM8962_EQ_SHARED_COEFF_SEL 0x0002 /* EQ_SHARED_COEFF_SEL */ +#define WM8962_EQ_SHARED_COEFF_SEL_MASK 0x0002 /* EQ_SHARED_COEFF_SEL */ +#define WM8962_EQ_SHARED_COEFF_SEL_SHIFT 1 /* EQ_SHARED_COEFF_SEL */ +#define WM8962_EQ_SHARED_COEFF_SEL_WIDTH 1 /* EQ_SHARED_COEFF_SEL */ +#define WM8962_EQ_ENA 0x0001 /* EQ_ENA */ +#define WM8962_EQ_ENA_MASK 0x0001 /* EQ_ENA */ +#define WM8962_EQ_ENA_SHIFT 0 /* EQ_ENA */ +#define WM8962_EQ_ENA_WIDTH 1 /* EQ_ENA */ + +/* + * R336 (0x150) - EQ2 + */ +#define WM8962_EQL_B1_GAIN_MASK 0xF800 /* EQL_B1_GAIN - [15:11] */ +#define WM8962_EQL_B1_GAIN_SHIFT 11 /* EQL_B1_GAIN - [15:11] */ +#define WM8962_EQL_B1_GAIN_WIDTH 5 /* EQL_B1_GAIN - [15:11] */ +#define WM8962_EQL_B2_GAIN_MASK 0x07C0 /* EQL_B2_GAIN - [10:6] */ +#define WM8962_EQL_B2_GAIN_SHIFT 6 /* EQL_B2_GAIN - [10:6] */ +#define WM8962_EQL_B2_GAIN_WIDTH 5 /* EQL_B2_GAIN - [10:6] */ +#define WM8962_EQL_B3_GAIN_MASK 0x003E /* EQL_B3_GAIN - [5:1] */ +#define WM8962_EQL_B3_GAIN_SHIFT 1 /* EQL_B3_GAIN - [5:1] */ +#define WM8962_EQL_B3_GAIN_WIDTH 5 /* EQL_B3_GAIN - [5:1] */ + +/* + * R337 (0x151) - EQ3 + */ +#define WM8962_EQL_B4_GAIN_MASK 0xF800 /* EQL_B4_GAIN - [15:11] */ +#define WM8962_EQL_B4_GAIN_SHIFT 11 /* EQL_B4_GAIN - [15:11] */ +#define WM8962_EQL_B4_GAIN_WIDTH 5 /* EQL_B4_GAIN - [15:11] */ +#define WM8962_EQL_B5_GAIN_MASK 0x07C0 /* EQL_B5_GAIN - [10:6] */ +#define WM8962_EQL_B5_GAIN_SHIFT 6 /* EQL_B5_GAIN - [10:6] */ +#define WM8962_EQL_B5_GAIN_WIDTH 5 /* EQL_B5_GAIN - [10:6] */ + +/* + * R338 (0x152) - EQ4 + */ +#define WM8962_EQL_B1_A_MASK 0xFFFF /* EQL_B1_A - [15:0] */ +#define WM8962_EQL_B1_A_SHIFT 0 /* EQL_B1_A - [15:0] */ +#define WM8962_EQL_B1_A_WIDTH 16 /* EQL_B1_A - [15:0] */ + +/* + * R339 (0x153) - EQ5 + */ +#define WM8962_EQL_B1_B_MASK 0xFFFF /* EQL_B1_B - [15:0] */ +#define WM8962_EQL_B1_B_SHIFT 0 /* EQL_B1_B - [15:0] */ +#define WM8962_EQL_B1_B_WIDTH 16 /* EQL_B1_B - [15:0] */ + +/* + * R340 (0x154) - EQ6 + */ +#define WM8962_EQL_B1_PG_MASK 0xFFFF /* EQL_B1_PG - [15:0] */ +#define WM8962_EQL_B1_PG_SHIFT 0 /* EQL_B1_PG - [15:0] */ +#define WM8962_EQL_B1_PG_WIDTH 16 /* EQL_B1_PG - [15:0] */ + +/* + * R341 (0x155) - EQ7 + */ +#define WM8962_EQL_B2_A_MASK 0xFFFF /* EQL_B2_A - [15:0] */ +#define WM8962_EQL_B2_A_SHIFT 0 /* EQL_B2_A - [15:0] */ +#define WM8962_EQL_B2_A_WIDTH 16 /* EQL_B2_A - [15:0] */ + +/* + * R342 (0x156) - EQ8 + */ +#define WM8962_EQL_B2_B_MASK 0xFFFF /* EQL_B2_B - [15:0] */ +#define WM8962_EQL_B2_B_SHIFT 0 /* EQL_B2_B - [15:0] */ +#define WM8962_EQL_B2_B_WIDTH 16 /* EQL_B2_B - [15:0] */ + +/* + * R343 (0x157) - EQ9 + */ +#define WM8962_EQL_B2_C_MASK 0xFFFF /* EQL_B2_C - [15:0] */ +#define WM8962_EQL_B2_C_SHIFT 0 /* EQL_B2_C - [15:0] */ +#define WM8962_EQL_B2_C_WIDTH 16 /* EQL_B2_C - [15:0] */ + +/* + * R344 (0x158) - EQ10 + */ +#define WM8962_EQL_B2_PG_MASK 0xFFFF /* EQL_B2_PG - [15:0] */ +#define WM8962_EQL_B2_PG_SHIFT 0 /* EQL_B2_PG - [15:0] */ +#define WM8962_EQL_B2_PG_WIDTH 16 /* EQL_B2_PG - [15:0] */ + +/* + * R345 (0x159) - EQ11 + */ +#define WM8962_EQL_B3_A_MASK 0xFFFF /* EQL_B3_A - [15:0] */ +#define WM8962_EQL_B3_A_SHIFT 0 /* EQL_B3_A - [15:0] */ +#define WM8962_EQL_B3_A_WIDTH 16 /* EQL_B3_A - [15:0] */ + +/* + * R346 (0x15A) - EQ12 + */ +#define WM8962_EQL_B3_B_MASK 0xFFFF /* EQL_B3_B - [15:0] */ +#define WM8962_EQL_B3_B_SHIFT 0 /* EQL_B3_B - [15:0] */ +#define WM8962_EQL_B3_B_WIDTH 16 /* EQL_B3_B - [15:0] */ + +/* + * R347 (0x15B) - EQ13 + */ +#define WM8962_EQL_B3_C_MASK 0xFFFF /* EQL_B3_C - [15:0] */ +#define WM8962_EQL_B3_C_SHIFT 0 /* EQL_B3_C - [15:0] */ +#define WM8962_EQL_B3_C_WIDTH 16 /* EQL_B3_C - [15:0] */ + +/* + * R348 (0x15C) - EQ14 + */ +#define WM8962_EQL_B3_PG_MASK 0xFFFF /* EQL_B3_PG - [15:0] */ +#define WM8962_EQL_B3_PG_SHIFT 0 /* EQL_B3_PG - [15:0] */ +#define WM8962_EQL_B3_PG_WIDTH 16 /* EQL_B3_PG - [15:0] */ + +/* + * R349 (0x15D) - EQ15 + */ +#define WM8962_EQL_B4_A_MASK 0xFFFF /* EQL_B4_A - [15:0] */ +#define WM8962_EQL_B4_A_SHIFT 0 /* EQL_B4_A - [15:0] */ +#define WM8962_EQL_B4_A_WIDTH 16 /* EQL_B4_A - [15:0] */ + +/* + * R350 (0x15E) - EQ16 + */ +#define WM8962_EQL_B4_B_MASK 0xFFFF /* EQL_B4_B - [15:0] */ +#define WM8962_EQL_B4_B_SHIFT 0 /* EQL_B4_B - [15:0] */ +#define WM8962_EQL_B4_B_WIDTH 16 /* EQL_B4_B - [15:0] */ + +/* + * R351 (0x15F) - EQ17 + */ +#define WM8962_EQL_B4_C_MASK 0xFFFF /* EQL_B4_C - [15:0] */ +#define WM8962_EQL_B4_C_SHIFT 0 /* EQL_B4_C - [15:0] */ +#define WM8962_EQL_B4_C_WIDTH 16 /* EQL_B4_C - [15:0] */ + +/* + * R352 (0x160) - EQ18 + */ +#define WM8962_EQL_B4_PG_MASK 0xFFFF /* EQL_B4_PG - [15:0] */ +#define WM8962_EQL_B4_PG_SHIFT 0 /* EQL_B4_PG - [15:0] */ +#define WM8962_EQL_B4_PG_WIDTH 16 /* EQL_B4_PG - [15:0] */ + +/* + * R353 (0x161) - EQ19 + */ +#define WM8962_EQL_B5_A_MASK 0xFFFF /* EQL_B5_A - [15:0] */ +#define WM8962_EQL_B5_A_SHIFT 0 /* EQL_B5_A - [15:0] */ +#define WM8962_EQL_B5_A_WIDTH 16 /* EQL_B5_A - [15:0] */ + +/* + * R354 (0x162) - EQ20 + */ +#define WM8962_EQL_B5_B_MASK 0xFFFF /* EQL_B5_B - [15:0] */ +#define WM8962_EQL_B5_B_SHIFT 0 /* EQL_B5_B - [15:0] */ +#define WM8962_EQL_B5_B_WIDTH 16 /* EQL_B5_B - [15:0] */ + +/* + * R355 (0x163) - EQ21 + */ +#define WM8962_EQL_B5_PG_MASK 0xFFFF /* EQL_B5_PG - [15:0] */ +#define WM8962_EQL_B5_PG_SHIFT 0 /* EQL_B5_PG - [15:0] */ +#define WM8962_EQL_B5_PG_WIDTH 16 /* EQL_B5_PG - [15:0] */ + +/* + * R356 (0x164) - EQ22 + */ +#define WM8962_EQR_B1_GAIN_MASK 0xF800 /* EQR_B1_GAIN - [15:11] */ +#define WM8962_EQR_B1_GAIN_SHIFT 11 /* EQR_B1_GAIN - [15:11] */ +#define WM8962_EQR_B1_GAIN_WIDTH 5 /* EQR_B1_GAIN - [15:11] */ +#define WM8962_EQR_B2_GAIN_MASK 0x07C0 /* EQR_B2_GAIN - [10:6] */ +#define WM8962_EQR_B2_GAIN_SHIFT 6 /* EQR_B2_GAIN - [10:6] */ +#define WM8962_EQR_B2_GAIN_WIDTH 5 /* EQR_B2_GAIN - [10:6] */ +#define WM8962_EQR_B3_GAIN_MASK 0x003E /* EQR_B3_GAIN - [5:1] */ +#define WM8962_EQR_B3_GAIN_SHIFT 1 /* EQR_B3_GAIN - [5:1] */ +#define WM8962_EQR_B3_GAIN_WIDTH 5 /* EQR_B3_GAIN - [5:1] */ + +/* + * R357 (0x165) - EQ23 + */ +#define WM8962_EQR_B4_GAIN_MASK 0xF800 /* EQR_B4_GAIN - [15:11] */ +#define WM8962_EQR_B4_GAIN_SHIFT 11 /* EQR_B4_GAIN - [15:11] */ +#define WM8962_EQR_B4_GAIN_WIDTH 5 /* EQR_B4_GAIN - [15:11] */ +#define WM8962_EQR_B5_GAIN_MASK 0x07C0 /* EQR_B5_GAIN - [10:6] */ +#define WM8962_EQR_B5_GAIN_SHIFT 6 /* EQR_B5_GAIN - [10:6] */ +#define WM8962_EQR_B5_GAIN_WIDTH 5 /* EQR_B5_GAIN - [10:6] */ + +/* + * R358 (0x166) - EQ24 + */ +#define WM8962_EQR_B1_A_MASK 0xFFFF /* EQR_B1_A - [15:0] */ +#define WM8962_EQR_B1_A_SHIFT 0 /* EQR_B1_A - [15:0] */ +#define WM8962_EQR_B1_A_WIDTH 16 /* EQR_B1_A - [15:0] */ + +/* + * R359 (0x167) - EQ25 + */ +#define WM8962_EQR_B1_B_MASK 0xFFFF /* EQR_B1_B - [15:0] */ +#define WM8962_EQR_B1_B_SHIFT 0 /* EQR_B1_B - [15:0] */ +#define WM8962_EQR_B1_B_WIDTH 16 /* EQR_B1_B - [15:0] */ + +/* + * R360 (0x168) - EQ26 + */ +#define WM8962_EQR_B1_PG_MASK 0xFFFF /* EQR_B1_PG - [15:0] */ +#define WM8962_EQR_B1_PG_SHIFT 0 /* EQR_B1_PG - [15:0] */ +#define WM8962_EQR_B1_PG_WIDTH 16 /* EQR_B1_PG - [15:0] */ + +/* + * R361 (0x169) - EQ27 + */ +#define WM8962_EQR_B2_A_MASK 0xFFFF /* EQR_B2_A - [15:0] */ +#define WM8962_EQR_B2_A_SHIFT 0 /* EQR_B2_A - [15:0] */ +#define WM8962_EQR_B2_A_WIDTH 16 /* EQR_B2_A - [15:0] */ + +/* + * R362 (0x16A) - EQ28 + */ +#define WM8962_EQR_B2_B_MASK 0xFFFF /* EQR_B2_B - [15:0] */ +#define WM8962_EQR_B2_B_SHIFT 0 /* EQR_B2_B - [15:0] */ +#define WM8962_EQR_B2_B_WIDTH 16 /* EQR_B2_B - [15:0] */ + +/* + * R363 (0x16B) - EQ29 + */ +#define WM8962_EQR_B2_C_MASK 0xFFFF /* EQR_B2_C - [15:0] */ +#define WM8962_EQR_B2_C_SHIFT 0 /* EQR_B2_C - [15:0] */ +#define WM8962_EQR_B2_C_WIDTH 16 /* EQR_B2_C - [15:0] */ + +/* + * R364 (0x16C) - EQ30 + */ +#define WM8962_EQR_B2_PG_MASK 0xFFFF /* EQR_B2_PG - [15:0] */ +#define WM8962_EQR_B2_PG_SHIFT 0 /* EQR_B2_PG - [15:0] */ +#define WM8962_EQR_B2_PG_WIDTH 16 /* EQR_B2_PG - [15:0] */ + +/* + * R365 (0x16D) - EQ31 + */ +#define WM8962_EQR_B3_A_MASK 0xFFFF /* EQR_B3_A - [15:0] */ +#define WM8962_EQR_B3_A_SHIFT 0 /* EQR_B3_A - [15:0] */ +#define WM8962_EQR_B3_A_WIDTH 16 /* EQR_B3_A - [15:0] */ + +/* + * R366 (0x16E) - EQ32 + */ +#define WM8962_EQR_B3_B_MASK 0xFFFF /* EQR_B3_B - [15:0] */ +#define WM8962_EQR_B3_B_SHIFT 0 /* EQR_B3_B - [15:0] */ +#define WM8962_EQR_B3_B_WIDTH 16 /* EQR_B3_B - [15:0] */ + +/* + * R367 (0x16F) - EQ33 + */ +#define WM8962_EQR_B3_C_MASK 0xFFFF /* EQR_B3_C - [15:0] */ +#define WM8962_EQR_B3_C_SHIFT 0 /* EQR_B3_C - [15:0] */ +#define WM8962_EQR_B3_C_WIDTH 16 /* EQR_B3_C - [15:0] */ + +/* + * R368 (0x170) - EQ34 + */ +#define WM8962_EQR_B3_PG_MASK 0xFFFF /* EQR_B3_PG - [15:0] */ +#define WM8962_EQR_B3_PG_SHIFT 0 /* EQR_B3_PG - [15:0] */ +#define WM8962_EQR_B3_PG_WIDTH 16 /* EQR_B3_PG - [15:0] */ + +/* + * R369 (0x171) - EQ35 + */ +#define WM8962_EQR_B4_A_MASK 0xFFFF /* EQR_B4_A - [15:0] */ +#define WM8962_EQR_B4_A_SHIFT 0 /* EQR_B4_A - [15:0] */ +#define WM8962_EQR_B4_A_WIDTH 16 /* EQR_B4_A - [15:0] */ + +/* + * R370 (0x172) - EQ36 + */ +#define WM8962_EQR_B4_B_MASK 0xFFFF /* EQR_B4_B - [15:0] */ +#define WM8962_EQR_B4_B_SHIFT 0 /* EQR_B4_B - [15:0] */ +#define WM8962_EQR_B4_B_WIDTH 16 /* EQR_B4_B - [15:0] */ + +/* + * R371 (0x173) - EQ37 + */ +#define WM8962_EQR_B4_C_MASK 0xFFFF /* EQR_B4_C - [15:0] */ +#define WM8962_EQR_B4_C_SHIFT 0 /* EQR_B4_C - [15:0] */ +#define WM8962_EQR_B4_C_WIDTH 16 /* EQR_B4_C - [15:0] */ + +/* + * R372 (0x174) - EQ38 + */ +#define WM8962_EQR_B4_PG_MASK 0xFFFF /* EQR_B4_PG - [15:0] */ +#define WM8962_EQR_B4_PG_SHIFT 0 /* EQR_B4_PG - [15:0] */ +#define WM8962_EQR_B4_PG_WIDTH 16 /* EQR_B4_PG - [15:0] */ + +/* + * R373 (0x175) - EQ39 + */ +#define WM8962_EQR_B5_A_MASK 0xFFFF /* EQR_B5_A - [15:0] */ +#define WM8962_EQR_B5_A_SHIFT 0 /* EQR_B5_A - [15:0] */ +#define WM8962_EQR_B5_A_WIDTH 16 /* EQR_B5_A - [15:0] */ + +/* + * R374 (0x176) - EQ40 + */ +#define WM8962_EQR_B5_B_MASK 0xFFFF /* EQR_B5_B - [15:0] */ +#define WM8962_EQR_B5_B_SHIFT 0 /* EQR_B5_B - [15:0] */ +#define WM8962_EQR_B5_B_WIDTH 16 /* EQR_B5_B - [15:0] */ + +/* + * R375 (0x177) - EQ41 + */ +#define WM8962_EQR_B5_PG_MASK 0xFFFF /* EQR_B5_PG - [15:0] */ +#define WM8962_EQR_B5_PG_SHIFT 0 /* EQR_B5_PG - [15:0] */ +#define WM8962_EQR_B5_PG_WIDTH 16 /* EQR_B5_PG - [15:0] */ + +/* + * R513 (0x201) - GPIO 2 + */ +#define WM8962_GP2_POL 0x0400 /* GP2_POL */ +#define WM8962_GP2_POL_MASK 0x0400 /* GP2_POL */ +#define WM8962_GP2_POL_SHIFT 10 /* GP2_POL */ +#define WM8962_GP2_POL_WIDTH 1 /* GP2_POL */ +#define WM8962_GP2_LVL 0x0040 /* GP2_LVL */ +#define WM8962_GP2_LVL_MASK 0x0040 /* GP2_LVL */ +#define WM8962_GP2_LVL_SHIFT 6 /* GP2_LVL */ +#define WM8962_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8962_GP2_FN_MASK 0x001F /* GP2_FN - [4:0] */ +#define WM8962_GP2_FN_SHIFT 0 /* GP2_FN - [4:0] */ +#define WM8962_GP2_FN_WIDTH 5 /* GP2_FN - [4:0] */ + +/* + * R514 (0x202) - GPIO 3 + */ +#define WM8962_GP3_POL 0x0400 /* GP3_POL */ +#define WM8962_GP3_POL_MASK 0x0400 /* GP3_POL */ +#define WM8962_GP3_POL_SHIFT 10 /* GP3_POL */ +#define WM8962_GP3_POL_WIDTH 1 /* GP3_POL */ +#define WM8962_GP3_LVL 0x0040 /* GP3_LVL */ +#define WM8962_GP3_LVL_MASK 0x0040 /* GP3_LVL */ +#define WM8962_GP3_LVL_SHIFT 6 /* GP3_LVL */ +#define WM8962_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8962_GP3_FN_MASK 0x001F /* GP3_FN - [4:0] */ +#define WM8962_GP3_FN_SHIFT 0 /* GP3_FN - [4:0] */ +#define WM8962_GP3_FN_WIDTH 5 /* GP3_FN - [4:0] */ + +/* + * R516 (0x204) - GPIO 5 + */ +#define WM8962_GP5_DIR 0x8000 /* GP5_DIR */ +#define WM8962_GP5_DIR_MASK 0x8000 /* GP5_DIR */ +#define WM8962_GP5_DIR_SHIFT 15 /* GP5_DIR */ +#define WM8962_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8962_GP5_PU 0x4000 /* GP5_PU */ +#define WM8962_GP5_PU_MASK 0x4000 /* GP5_PU */ +#define WM8962_GP5_PU_SHIFT 14 /* GP5_PU */ +#define WM8962_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8962_GP5_PD 0x2000 /* GP5_PD */ +#define WM8962_GP5_PD_MASK 0x2000 /* GP5_PD */ +#define WM8962_GP5_PD_SHIFT 13 /* GP5_PD */ +#define WM8962_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8962_GP5_POL 0x0400 /* GP5_POL */ +#define WM8962_GP5_POL_MASK 0x0400 /* GP5_POL */ +#define WM8962_GP5_POL_SHIFT 10 /* GP5_POL */ +#define WM8962_GP5_POL_WIDTH 1 /* GP5_POL */ +#define WM8962_GP5_OP_CFG 0x0200 /* GP5_OP_CFG */ +#define WM8962_GP5_OP_CFG_MASK 0x0200 /* GP5_OP_CFG */ +#define WM8962_GP5_OP_CFG_SHIFT 9 /* GP5_OP_CFG */ +#define WM8962_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8962_GP5_DB 0x0100 /* GP5_DB */ +#define WM8962_GP5_DB_MASK 0x0100 /* GP5_DB */ +#define WM8962_GP5_DB_SHIFT 8 /* GP5_DB */ +#define WM8962_GP5_DB_WIDTH 1 /* GP5_DB */ +#define WM8962_GP5_LVL 0x0040 /* GP5_LVL */ +#define WM8962_GP5_LVL_MASK 0x0040 /* GP5_LVL */ +#define WM8962_GP5_LVL_SHIFT 6 /* GP5_LVL */ +#define WM8962_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8962_GP5_FN_MASK 0x001F /* GP5_FN - [4:0] */ +#define WM8962_GP5_FN_SHIFT 0 /* GP5_FN - [4:0] */ +#define WM8962_GP5_FN_WIDTH 5 /* GP5_FN - [4:0] */ + +/* + * R517 (0x205) - GPIO 6 + */ +#define WM8962_GP6_DIR 0x8000 /* GP6_DIR */ +#define WM8962_GP6_DIR_MASK 0x8000 /* GP6_DIR */ +#define WM8962_GP6_DIR_SHIFT 15 /* GP6_DIR */ +#define WM8962_GP6_DIR_WIDTH 1 /* GP6_DIR */ +#define WM8962_GP6_PU 0x4000 /* GP6_PU */ +#define WM8962_GP6_PU_MASK 0x4000 /* GP6_PU */ +#define WM8962_GP6_PU_SHIFT 14 /* GP6_PU */ +#define WM8962_GP6_PU_WIDTH 1 /* GP6_PU */ +#define WM8962_GP6_PD 0x2000 /* GP6_PD */ +#define WM8962_GP6_PD_MASK 0x2000 /* GP6_PD */ +#define WM8962_GP6_PD_SHIFT 13 /* GP6_PD */ +#define WM8962_GP6_PD_WIDTH 1 /* GP6_PD */ +#define WM8962_GP6_POL 0x0400 /* GP6_POL */ +#define WM8962_GP6_POL_MASK 0x0400 /* GP6_POL */ +#define WM8962_GP6_POL_SHIFT 10 /* GP6_POL */ +#define WM8962_GP6_POL_WIDTH 1 /* GP6_POL */ +#define WM8962_GP6_OP_CFG 0x0200 /* GP6_OP_CFG */ +#define WM8962_GP6_OP_CFG_MASK 0x0200 /* GP6_OP_CFG */ +#define WM8962_GP6_OP_CFG_SHIFT 9 /* GP6_OP_CFG */ +#define WM8962_GP6_OP_CFG_WIDTH 1 /* GP6_OP_CFG */ +#define WM8962_GP6_DB 0x0100 /* GP6_DB */ +#define WM8962_GP6_DB_MASK 0x0100 /* GP6_DB */ +#define WM8962_GP6_DB_SHIFT 8 /* GP6_DB */ +#define WM8962_GP6_DB_WIDTH 1 /* GP6_DB */ +#define WM8962_GP6_LVL 0x0040 /* GP6_LVL */ +#define WM8962_GP6_LVL_MASK 0x0040 /* GP6_LVL */ +#define WM8962_GP6_LVL_SHIFT 6 /* GP6_LVL */ +#define WM8962_GP6_LVL_WIDTH 1 /* GP6_LVL */ +#define WM8962_GP6_FN_MASK 0x001F /* GP6_FN - [4:0] */ +#define WM8962_GP6_FN_SHIFT 0 /* GP6_FN - [4:0] */ +#define WM8962_GP6_FN_WIDTH 5 /* GP6_FN - [4:0] */ + +/* + * R560 (0x230) - Interrupt Status 1 + */ +#define WM8962_GP6_EINT 0x0020 /* GP6_EINT */ +#define WM8962_GP6_EINT_MASK 0x0020 /* GP6_EINT */ +#define WM8962_GP6_EINT_SHIFT 5 /* GP6_EINT */ +#define WM8962_GP6_EINT_WIDTH 1 /* GP6_EINT */ +#define WM8962_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM8962_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM8962_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM8962_GP5_EINT_WIDTH 1 /* GP5_EINT */ + +/* + * R561 (0x231) - Interrupt Status 2 + */ +#define WM8962_MICSCD_EINT 0x8000 /* MICSCD_EINT */ +#define WM8962_MICSCD_EINT_MASK 0x8000 /* MICSCD_EINT */ +#define WM8962_MICSCD_EINT_SHIFT 15 /* MICSCD_EINT */ +#define WM8962_MICSCD_EINT_WIDTH 1 /* MICSCD_EINT */ +#define WM8962_MICD_EINT 0x4000 /* MICD_EINT */ +#define WM8962_MICD_EINT_MASK 0x4000 /* MICD_EINT */ +#define WM8962_MICD_EINT_SHIFT 14 /* MICD_EINT */ +#define WM8962_MICD_EINT_WIDTH 1 /* MICD_EINT */ +#define WM8962_FIFOS_ERR_EINT 0x2000 /* FIFOS_ERR_EINT */ +#define WM8962_FIFOS_ERR_EINT_MASK 0x2000 /* FIFOS_ERR_EINT */ +#define WM8962_FIFOS_ERR_EINT_SHIFT 13 /* FIFOS_ERR_EINT */ +#define WM8962_FIFOS_ERR_EINT_WIDTH 1 /* FIFOS_ERR_EINT */ +#define WM8962_ALC_LOCK_EINT 0x1000 /* ALC_LOCK_EINT */ +#define WM8962_ALC_LOCK_EINT_MASK 0x1000 /* ALC_LOCK_EINT */ +#define WM8962_ALC_LOCK_EINT_SHIFT 12 /* ALC_LOCK_EINT */ +#define WM8962_ALC_LOCK_EINT_WIDTH 1 /* ALC_LOCK_EINT */ +#define WM8962_ALC_THRESH_EINT 0x0800 /* ALC_THRESH_EINT */ +#define WM8962_ALC_THRESH_EINT_MASK 0x0800 /* ALC_THRESH_EINT */ +#define WM8962_ALC_THRESH_EINT_SHIFT 11 /* ALC_THRESH_EINT */ +#define WM8962_ALC_THRESH_EINT_WIDTH 1 /* ALC_THRESH_EINT */ +#define WM8962_ALC_SAT_EINT 0x0400 /* ALC_SAT_EINT */ +#define WM8962_ALC_SAT_EINT_MASK 0x0400 /* ALC_SAT_EINT */ +#define WM8962_ALC_SAT_EINT_SHIFT 10 /* ALC_SAT_EINT */ +#define WM8962_ALC_SAT_EINT_WIDTH 1 /* ALC_SAT_EINT */ +#define WM8962_ALC_PKOVR_EINT 0x0200 /* ALC_PKOVR_EINT */ +#define WM8962_ALC_PKOVR_EINT_MASK 0x0200 /* ALC_PKOVR_EINT */ +#define WM8962_ALC_PKOVR_EINT_SHIFT 9 /* ALC_PKOVR_EINT */ +#define WM8962_ALC_PKOVR_EINT_WIDTH 1 /* ALC_PKOVR_EINT */ +#define WM8962_ALC_NGATE_EINT 0x0100 /* ALC_NGATE_EINT */ +#define WM8962_ALC_NGATE_EINT_MASK 0x0100 /* ALC_NGATE_EINT */ +#define WM8962_ALC_NGATE_EINT_SHIFT 8 /* ALC_NGATE_EINT */ +#define WM8962_ALC_NGATE_EINT_WIDTH 1 /* ALC_NGATE_EINT */ +#define WM8962_WSEQ_DONE_EINT 0x0080 /* WSEQ_DONE_EINT */ +#define WM8962_WSEQ_DONE_EINT_MASK 0x0080 /* WSEQ_DONE_EINT */ +#define WM8962_WSEQ_DONE_EINT_SHIFT 7 /* WSEQ_DONE_EINT */ +#define WM8962_WSEQ_DONE_EINT_WIDTH 1 /* WSEQ_DONE_EINT */ +#define WM8962_DRC_ACTDET_EINT 0x0040 /* DRC_ACTDET_EINT */ +#define WM8962_DRC_ACTDET_EINT_MASK 0x0040 /* DRC_ACTDET_EINT */ +#define WM8962_DRC_ACTDET_EINT_SHIFT 6 /* DRC_ACTDET_EINT */ +#define WM8962_DRC_ACTDET_EINT_WIDTH 1 /* DRC_ACTDET_EINT */ +#define WM8962_FLL_LOCK_EINT 0x0020 /* FLL_LOCK_EINT */ +#define WM8962_FLL_LOCK_EINT_MASK 0x0020 /* FLL_LOCK_EINT */ +#define WM8962_FLL_LOCK_EINT_SHIFT 5 /* FLL_LOCK_EINT */ +#define WM8962_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */ +#define WM8962_PLL3_LOCK_EINT 0x0008 /* PLL3_LOCK_EINT */ +#define WM8962_PLL3_LOCK_EINT_MASK 0x0008 /* PLL3_LOCK_EINT */ +#define WM8962_PLL3_LOCK_EINT_SHIFT 3 /* PLL3_LOCK_EINT */ +#define WM8962_PLL3_LOCK_EINT_WIDTH 1 /* PLL3_LOCK_EINT */ +#define WM8962_PLL2_LOCK_EINT 0x0004 /* PLL2_LOCK_EINT */ +#define WM8962_PLL2_LOCK_EINT_MASK 0x0004 /* PLL2_LOCK_EINT */ +#define WM8962_PLL2_LOCK_EINT_SHIFT 2 /* PLL2_LOCK_EINT */ +#define WM8962_PLL2_LOCK_EINT_WIDTH 1 /* PLL2_LOCK_EINT */ +#define WM8962_TEMP_SHUT_EINT 0x0001 /* TEMP_SHUT_EINT */ +#define WM8962_TEMP_SHUT_EINT_MASK 0x0001 /* TEMP_SHUT_EINT */ +#define WM8962_TEMP_SHUT_EINT_SHIFT 0 /* TEMP_SHUT_EINT */ +#define WM8962_TEMP_SHUT_EINT_WIDTH 1 /* TEMP_SHUT_EINT */ + +/* + * R568 (0x238) - Interrupt Status 1 Mask + */ +#define WM8962_IM_GP6_EINT 0x0020 /* IM_GP6_EINT */ +#define WM8962_IM_GP6_EINT_MASK 0x0020 /* IM_GP6_EINT */ +#define WM8962_IM_GP6_EINT_SHIFT 5 /* IM_GP6_EINT */ +#define WM8962_IM_GP6_EINT_WIDTH 1 /* IM_GP6_EINT */ +#define WM8962_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM8962_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM8962_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM8962_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ + +/* + * R569 (0x239) - Interrupt Status 2 Mask + */ +#define WM8962_IM_MICSCD_EINT 0x8000 /* IM_MICSCD_EINT */ +#define WM8962_IM_MICSCD_EINT_MASK 0x8000 /* IM_MICSCD_EINT */ +#define WM8962_IM_MICSCD_EINT_SHIFT 15 /* IM_MICSCD_EINT */ +#define WM8962_IM_MICSCD_EINT_WIDTH 1 /* IM_MICSCD_EINT */ +#define WM8962_IM_MICD_EINT 0x4000 /* IM_MICD_EINT */ +#define WM8962_IM_MICD_EINT_MASK 0x4000 /* IM_MICD_EINT */ +#define WM8962_IM_MICD_EINT_SHIFT 14 /* IM_MICD_EINT */ +#define WM8962_IM_MICD_EINT_WIDTH 1 /* IM_MICD_EINT */ +#define WM8962_IM_FIFOS_ERR_EINT 0x2000 /* IM_FIFOS_ERR_EINT */ +#define WM8962_IM_FIFOS_ERR_EINT_MASK 0x2000 /* IM_FIFOS_ERR_EINT */ +#define WM8962_IM_FIFOS_ERR_EINT_SHIFT 13 /* IM_FIFOS_ERR_EINT */ +#define WM8962_IM_FIFOS_ERR_EINT_WIDTH 1 /* IM_FIFOS_ERR_EINT */ +#define WM8962_IM_ALC_LOCK_EINT 0x1000 /* IM_ALC_LOCK_EINT */ +#define WM8962_IM_ALC_LOCK_EINT_MASK 0x1000 /* IM_ALC_LOCK_EINT */ +#define WM8962_IM_ALC_LOCK_EINT_SHIFT 12 /* IM_ALC_LOCK_EINT */ +#define WM8962_IM_ALC_LOCK_EINT_WIDTH 1 /* IM_ALC_LOCK_EINT */ +#define WM8962_IM_ALC_THRESH_EINT 0x0800 /* IM_ALC_THRESH_EINT */ +#define WM8962_IM_ALC_THRESH_EINT_MASK 0x0800 /* IM_ALC_THRESH_EINT */ +#define WM8962_IM_ALC_THRESH_EINT_SHIFT 11 /* IM_ALC_THRESH_EINT */ +#define WM8962_IM_ALC_THRESH_EINT_WIDTH 1 /* IM_ALC_THRESH_EINT */ +#define WM8962_IM_ALC_SAT_EINT 0x0400 /* IM_ALC_SAT_EINT */ +#define WM8962_IM_ALC_SAT_EINT_MASK 0x0400 /* IM_ALC_SAT_EINT */ +#define WM8962_IM_ALC_SAT_EINT_SHIFT 10 /* IM_ALC_SAT_EINT */ +#define WM8962_IM_ALC_SAT_EINT_WIDTH 1 /* IM_ALC_SAT_EINT */ +#define WM8962_IM_ALC_PKOVR_EINT 0x0200 /* IM_ALC_PKOVR_EINT */ +#define WM8962_IM_ALC_PKOVR_EINT_MASK 0x0200 /* IM_ALC_PKOVR_EINT */ +#define WM8962_IM_ALC_PKOVR_EINT_SHIFT 9 /* IM_ALC_PKOVR_EINT */ +#define WM8962_IM_ALC_PKOVR_EINT_WIDTH 1 /* IM_ALC_PKOVR_EINT */ +#define WM8962_IM_ALC_NGATE_EINT 0x0100 /* IM_ALC_NGATE_EINT */ +#define WM8962_IM_ALC_NGATE_EINT_MASK 0x0100 /* IM_ALC_NGATE_EINT */ +#define WM8962_IM_ALC_NGATE_EINT_SHIFT 8 /* IM_ALC_NGATE_EINT */ +#define WM8962_IM_ALC_NGATE_EINT_WIDTH 1 /* IM_ALC_NGATE_EINT */ +#define WM8962_IM_WSEQ_DONE_EINT 0x0080 /* IM_WSEQ_DONE_EINT */ +#define WM8962_IM_WSEQ_DONE_EINT_MASK 0x0080 /* IM_WSEQ_DONE_EINT */ +#define WM8962_IM_WSEQ_DONE_EINT_SHIFT 7 /* IM_WSEQ_DONE_EINT */ +#define WM8962_IM_WSEQ_DONE_EINT_WIDTH 1 /* IM_WSEQ_DONE_EINT */ +#define WM8962_IM_DRC_ACTDET_EINT 0x0040 /* IM_DRC_ACTDET_EINT */ +#define WM8962_IM_DRC_ACTDET_EINT_MASK 0x0040 /* IM_DRC_ACTDET_EINT */ +#define WM8962_IM_DRC_ACTDET_EINT_SHIFT 6 /* IM_DRC_ACTDET_EINT */ +#define WM8962_IM_DRC_ACTDET_EINT_WIDTH 1 /* IM_DRC_ACTDET_EINT */ +#define WM8962_IM_FLL_LOCK_EINT 0x0020 /* IM_FLL_LOCK_EINT */ +#define WM8962_IM_FLL_LOCK_EINT_MASK 0x0020 /* IM_FLL_LOCK_EINT */ +#define WM8962_IM_FLL_LOCK_EINT_SHIFT 5 /* IM_FLL_LOCK_EINT */ +#define WM8962_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */ +#define WM8962_IM_PLL3_LOCK_EINT 0x0008 /* IM_PLL3_LOCK_EINT */ +#define WM8962_IM_PLL3_LOCK_EINT_MASK 0x0008 /* IM_PLL3_LOCK_EINT */ +#define WM8962_IM_PLL3_LOCK_EINT_SHIFT 3 /* IM_PLL3_LOCK_EINT */ +#define WM8962_IM_PLL3_LOCK_EINT_WIDTH 1 /* IM_PLL3_LOCK_EINT */ +#define WM8962_IM_PLL2_LOCK_EINT 0x0004 /* IM_PLL2_LOCK_EINT */ +#define WM8962_IM_PLL2_LOCK_EINT_MASK 0x0004 /* IM_PLL2_LOCK_EINT */ +#define WM8962_IM_PLL2_LOCK_EINT_SHIFT 2 /* IM_PLL2_LOCK_EINT */ +#define WM8962_IM_PLL2_LOCK_EINT_WIDTH 1 /* IM_PLL2_LOCK_EINT */ +#define WM8962_IM_TEMP_SHUT_EINT 0x0001 /* IM_TEMP_SHUT_EINT */ +#define WM8962_IM_TEMP_SHUT_EINT_MASK 0x0001 /* IM_TEMP_SHUT_EINT */ +#define WM8962_IM_TEMP_SHUT_EINT_SHIFT 0 /* IM_TEMP_SHUT_EINT */ +#define WM8962_IM_TEMP_SHUT_EINT_WIDTH 1 /* IM_TEMP_SHUT_EINT */ + +/* + * R576 (0x240) - Interrupt Control + */ +#define WM8962_IRQ_POL 0x0001 /* IRQ_POL */ +#define WM8962_IRQ_POL_MASK 0x0001 /* IRQ_POL */ +#define WM8962_IRQ_POL_SHIFT 0 /* IRQ_POL */ +#define WM8962_IRQ_POL_WIDTH 1 /* IRQ_POL */ + +/* + * R584 (0x248) - IRQ Debounce + */ +#define WM8962_FLL_LOCK_DB 0x0020 /* FLL_LOCK_DB */ +#define WM8962_FLL_LOCK_DB_MASK 0x0020 /* FLL_LOCK_DB */ +#define WM8962_FLL_LOCK_DB_SHIFT 5 /* FLL_LOCK_DB */ +#define WM8962_FLL_LOCK_DB_WIDTH 1 /* FLL_LOCK_DB */ +#define WM8962_PLL3_LOCK_DB 0x0008 /* PLL3_LOCK_DB */ +#define WM8962_PLL3_LOCK_DB_MASK 0x0008 /* PLL3_LOCK_DB */ +#define WM8962_PLL3_LOCK_DB_SHIFT 3 /* PLL3_LOCK_DB */ +#define WM8962_PLL3_LOCK_DB_WIDTH 1 /* PLL3_LOCK_DB */ +#define WM8962_PLL2_LOCK_DB 0x0004 /* PLL2_LOCK_DB */ +#define WM8962_PLL2_LOCK_DB_MASK 0x0004 /* PLL2_LOCK_DB */ +#define WM8962_PLL2_LOCK_DB_SHIFT 2 /* PLL2_LOCK_DB */ +#define WM8962_PLL2_LOCK_DB_WIDTH 1 /* PLL2_LOCK_DB */ +#define WM8962_TEMP_SHUT_DB 0x0001 /* TEMP_SHUT_DB */ +#define WM8962_TEMP_SHUT_DB_MASK 0x0001 /* TEMP_SHUT_DB */ +#define WM8962_TEMP_SHUT_DB_SHIFT 0 /* TEMP_SHUT_DB */ +#define WM8962_TEMP_SHUT_DB_WIDTH 1 /* TEMP_SHUT_DB */ + +/* + * R586 (0x24A) - MICINT Source Pol + */ +#define WM8962_MICSCD_IRQ_POL 0x8000 /* MICSCD_IRQ_POL */ +#define WM8962_MICSCD_IRQ_POL_MASK 0x8000 /* MICSCD_IRQ_POL */ +#define WM8962_MICSCD_IRQ_POL_SHIFT 15 /* MICSCD_IRQ_POL */ +#define WM8962_MICSCD_IRQ_POL_WIDTH 1 /* MICSCD_IRQ_POL */ +#define WM8962_MICD_IRQ_POL 0x4000 /* MICD_IRQ_POL */ +#define WM8962_MICD_IRQ_POL_MASK 0x4000 /* MICD_IRQ_POL */ +#define WM8962_MICD_IRQ_POL_SHIFT 14 /* MICD_IRQ_POL */ +#define WM8962_MICD_IRQ_POL_WIDTH 1 /* MICD_IRQ_POL */ + +/* + * R768 (0x300) - DSP2 Power Management + */ +#define WM8962_DSP2_ENA 0x0001 /* DSP2_ENA */ +#define WM8962_DSP2_ENA_MASK 0x0001 /* DSP2_ENA */ +#define WM8962_DSP2_ENA_SHIFT 0 /* DSP2_ENA */ +#define WM8962_DSP2_ENA_WIDTH 1 /* DSP2_ENA */ + +/* + * R1037 (0x40D) - DSP2_ExecControl + */ +#define WM8962_DSP2_STOPC 0x0020 /* DSP2_STOPC */ +#define WM8962_DSP2_STOPC_MASK 0x0020 /* DSP2_STOPC */ +#define WM8962_DSP2_STOPC_SHIFT 5 /* DSP2_STOPC */ +#define WM8962_DSP2_STOPC_WIDTH 1 /* DSP2_STOPC */ +#define WM8962_DSP2_STOPS 0x0010 /* DSP2_STOPS */ +#define WM8962_DSP2_STOPS_MASK 0x0010 /* DSP2_STOPS */ +#define WM8962_DSP2_STOPS_SHIFT 4 /* DSP2_STOPS */ +#define WM8962_DSP2_STOPS_WIDTH 1 /* DSP2_STOPS */ +#define WM8962_DSP2_STOPI 0x0008 /* DSP2_STOPI */ +#define WM8962_DSP2_STOPI_MASK 0x0008 /* DSP2_STOPI */ +#define WM8962_DSP2_STOPI_SHIFT 3 /* DSP2_STOPI */ +#define WM8962_DSP2_STOPI_WIDTH 1 /* DSP2_STOPI */ +#define WM8962_DSP2_STOP 0x0004 /* DSP2_STOP */ +#define WM8962_DSP2_STOP_MASK 0x0004 /* DSP2_STOP */ +#define WM8962_DSP2_STOP_SHIFT 2 /* DSP2_STOP */ +#define WM8962_DSP2_STOP_WIDTH 1 /* DSP2_STOP */ +#define WM8962_DSP2_RUNR 0x0002 /* DSP2_RUNR */ +#define WM8962_DSP2_RUNR_MASK 0x0002 /* DSP2_RUNR */ +#define WM8962_DSP2_RUNR_SHIFT 1 /* DSP2_RUNR */ +#define WM8962_DSP2_RUNR_WIDTH 1 /* DSP2_RUNR */ +#define WM8962_DSP2_RUN 0x0001 /* DSP2_RUN */ +#define WM8962_DSP2_RUN_MASK 0x0001 /* DSP2_RUN */ +#define WM8962_DSP2_RUN_SHIFT 0 /* DSP2_RUN */ +#define WM8962_DSP2_RUN_WIDTH 1 /* DSP2_RUN */ + +/* + * R8192 (0x2000) - DSP2 Instruction RAM 0 + */ +#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_MASK 0x03FF /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */ +#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_SHIFT 0 /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */ +#define WM8962_DSP2_INSTR_RAM_1024_10_9_0_WIDTH 10 /* DSP2_INSTR_RAM_1024_10_9_0 - [9:0] */ + +/* + * R9216 (0x2400) - DSP2 Address RAM 2 + */ +#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_MASK 0x003F /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */ +#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_SHIFT 0 /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */ +#define WM8962_DSP2_ADDR_RAM_1024_38_37_32_WIDTH 6 /* DSP2_ADDR_RAM_1024_38_37_32 - [5:0] */ + +/* + * R9217 (0x2401) - DSP2 Address RAM 1 + */ +#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_MASK 0xFFFF /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */ +#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_SHIFT 0 /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */ +#define WM8962_DSP2_ADDR_RAM_1024_38_31_16_WIDTH 16 /* DSP2_ADDR_RAM_1024_38_31_16 - [15:0] */ + +/* + * R9218 (0x2402) - DSP2 Address RAM 0 + */ +#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_MASK 0xFFFF /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */ +#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_SHIFT 0 /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */ +#define WM8962_DSP2_ADDR_RAM_1024_38_15_0_WIDTH 16 /* DSP2_ADDR_RAM_1024_38_15_0 - [15:0] */ + +/* + * R12288 (0x3000) - DSP2 Data1 RAM 1 + */ +#define WM8962_DSP2_DATA1_RAM_384_24_23_16_MASK 0x00FF /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */ +#define WM8962_DSP2_DATA1_RAM_384_24_23_16_SHIFT 0 /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */ +#define WM8962_DSP2_DATA1_RAM_384_24_23_16_WIDTH 8 /* DSP2_DATA1_RAM_384_24_23_16 - [7:0] */ + +/* + * R12289 (0x3001) - DSP2 Data1 RAM 0 + */ +#define WM8962_DSP2_DATA1_RAM_384_24_15_0_MASK 0xFFFF /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */ +#define WM8962_DSP2_DATA1_RAM_384_24_15_0_SHIFT 0 /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */ +#define WM8962_DSP2_DATA1_RAM_384_24_15_0_WIDTH 16 /* DSP2_DATA1_RAM_384_24_15_0 - [15:0] */ + +/* + * R13312 (0x3400) - DSP2 Data2 RAM 1 + */ +#define WM8962_DSP2_DATA2_RAM_384_24_23_16_MASK 0x00FF /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */ +#define WM8962_DSP2_DATA2_RAM_384_24_23_16_SHIFT 0 /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */ +#define WM8962_DSP2_DATA2_RAM_384_24_23_16_WIDTH 8 /* DSP2_DATA2_RAM_384_24_23_16 - [7:0] */ + +/* + * R13313 (0x3401) - DSP2 Data2 RAM 0 + */ +#define WM8962_DSP2_DATA2_RAM_384_24_15_0_MASK 0xFFFF /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */ +#define WM8962_DSP2_DATA2_RAM_384_24_15_0_SHIFT 0 /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */ +#define WM8962_DSP2_DATA2_RAM_384_24_15_0_WIDTH 16 /* DSP2_DATA2_RAM_384_24_15_0 - [15:0] */ + +/* + * R14336 (0x3800) - DSP2 Data3 RAM 1 + */ +#define WM8962_DSP2_DATA3_RAM_384_24_23_16_MASK 0x00FF /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */ +#define WM8962_DSP2_DATA3_RAM_384_24_23_16_SHIFT 0 /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */ +#define WM8962_DSP2_DATA3_RAM_384_24_23_16_WIDTH 8 /* DSP2_DATA3_RAM_384_24_23_16 - [7:0] */ + +/* + * R14337 (0x3801) - DSP2 Data3 RAM 0 + */ +#define WM8962_DSP2_DATA3_RAM_384_24_15_0_MASK 0xFFFF /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */ +#define WM8962_DSP2_DATA3_RAM_384_24_15_0_SHIFT 0 /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */ +#define WM8962_DSP2_DATA3_RAM_384_24_15_0_WIDTH 16 /* DSP2_DATA3_RAM_384_24_15_0 - [15:0] */ + +/* + * R15360 (0x3C00) - DSP2 Coeff RAM 0 + */ +#define WM8962_DSP2_CMAP_RAM_384_11_10_0_MASK 0x07FF /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */ +#define WM8962_DSP2_CMAP_RAM_384_11_10_0_SHIFT 0 /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */ +#define WM8962_DSP2_CMAP_RAM_384_11_10_0_WIDTH 11 /* DSP2_CMAP_RAM_384_11_10_0 - [10:0] */ + +/* + * R16384 (0x4000) - RETUNEADC_SHARED_COEFF_1 + */ +#define WM8962_ADC_RETUNE_SCV 0x0080 /* ADC_RETUNE_SCV */ +#define WM8962_ADC_RETUNE_SCV_MASK 0x0080 /* ADC_RETUNE_SCV */ +#define WM8962_ADC_RETUNE_SCV_SHIFT 7 /* ADC_RETUNE_SCV */ +#define WM8962_ADC_RETUNE_SCV_WIDTH 1 /* ADC_RETUNE_SCV */ +#define WM8962_RETUNEADC_SHARED_COEFF_22_16_MASK 0x007F /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */ +#define WM8962_RETUNEADC_SHARED_COEFF_22_16_SHIFT 0 /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */ +#define WM8962_RETUNEADC_SHARED_COEFF_22_16_WIDTH 7 /* RETUNEADC_SHARED_COEFF_22_16 - [6:0] */ + +/* + * R16385 (0x4001) - RETUNEADC_SHARED_COEFF_0 + */ +#define WM8962_RETUNEADC_SHARED_COEFF_15_00_MASK 0xFFFF /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */ +#define WM8962_RETUNEADC_SHARED_COEFF_15_00_SHIFT 0 /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */ +#define WM8962_RETUNEADC_SHARED_COEFF_15_00_WIDTH 16 /* RETUNEADC_SHARED_COEFF_15_00 - [15:0] */ + +/* + * R16386 (0x4002) - RETUNEDAC_SHARED_COEFF_1 + */ +#define WM8962_DAC_RETUNE_SCV 0x0080 /* DAC_RETUNE_SCV */ +#define WM8962_DAC_RETUNE_SCV_MASK 0x0080 /* DAC_RETUNE_SCV */ +#define WM8962_DAC_RETUNE_SCV_SHIFT 7 /* DAC_RETUNE_SCV */ +#define WM8962_DAC_RETUNE_SCV_WIDTH 1 /* DAC_RETUNE_SCV */ +#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_MASK 0x007F /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */ +#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_SHIFT 0 /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */ +#define WM8962_RETUNEDAC_SHARED_COEFF_23_16_WIDTH 7 /* RETUNEDAC_SHARED_COEFF_23_16 - [6:0] */ + +/* + * R16387 (0x4003) - RETUNEDAC_SHARED_COEFF_0 + */ +#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_MASK 0xFFFF /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */ +#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_SHIFT 0 /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */ +#define WM8962_RETUNEDAC_SHARED_COEFF_15_00_WIDTH 16 /* RETUNEDAC_SHARED_COEFF_15_00 - [15:0] */ + +/* + * R16388 (0x4004) - SOUNDSTAGE_ENABLES_1 + */ +#define WM8962_SOUNDSTAGE_ENABLES_23_16_MASK 0x00FF /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */ +#define WM8962_SOUNDSTAGE_ENABLES_23_16_SHIFT 0 /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */ +#define WM8962_SOUNDSTAGE_ENABLES_23_16_WIDTH 8 /* SOUNDSTAGE_ENABLES_23_16 - [7:0] */ + +/* + * R16389 (0x4005) - SOUNDSTAGE_ENABLES_0 + */ +#define WM8962_SOUNDSTAGE_ENABLES_15_06_MASK 0xFFC0 /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */ +#define WM8962_SOUNDSTAGE_ENABLES_15_06_SHIFT 6 /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */ +#define WM8962_SOUNDSTAGE_ENABLES_15_06_WIDTH 10 /* SOUNDSTAGE_ENABLES_15_06 - [15:6] */ +#define WM8962_RTN_ADC_ENA 0x0020 /* RTN_ADC_ENA */ +#define WM8962_RTN_ADC_ENA_MASK 0x0020 /* RTN_ADC_ENA */ +#define WM8962_RTN_ADC_ENA_SHIFT 5 /* RTN_ADC_ENA */ +#define WM8962_RTN_ADC_ENA_WIDTH 1 /* RTN_ADC_ENA */ +#define WM8962_RTN_DAC_ENA 0x0010 /* RTN_DAC_ENA */ +#define WM8962_RTN_DAC_ENA_MASK 0x0010 /* RTN_DAC_ENA */ +#define WM8962_RTN_DAC_ENA_SHIFT 4 /* RTN_DAC_ENA */ +#define WM8962_RTN_DAC_ENA_WIDTH 1 /* RTN_DAC_ENA */ +#define WM8962_HDBASS_ENA 0x0008 /* HDBASS_ENA */ +#define WM8962_HDBASS_ENA_MASK 0x0008 /* HDBASS_ENA */ +#define WM8962_HDBASS_ENA_SHIFT 3 /* HDBASS_ENA */ +#define WM8962_HDBASS_ENA_WIDTH 1 /* HDBASS_ENA */ +#define WM8962_HPF2_ENA 0x0004 /* HPF2_ENA */ +#define WM8962_HPF2_ENA_MASK 0x0004 /* HPF2_ENA */ +#define WM8962_HPF2_ENA_SHIFT 2 /* HPF2_ENA */ +#define WM8962_HPF2_ENA_WIDTH 1 /* HPF2_ENA */ +#define WM8962_HPF1_ENA 0x0002 /* HPF1_ENA */ +#define WM8962_HPF1_ENA_MASK 0x0002 /* HPF1_ENA */ +#define WM8962_HPF1_ENA_SHIFT 1 /* HPF1_ENA */ +#define WM8962_HPF1_ENA_WIDTH 1 /* HPF1_ENA */ +#define WM8962_VSS_ENA 0x0001 /* VSS_ENA */ +#define WM8962_VSS_ENA_MASK 0x0001 /* VSS_ENA */ +#define WM8962_VSS_ENA_SHIFT 0 /* VSS_ENA */ +#define WM8962_VSS_ENA_WIDTH 1 /* VSS_ENA */ + +int wm8962_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack); + +#endif diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c new file mode 100644 index 000000000..a79f7a7ab --- /dev/null +++ b/sound/soc/codecs/wm8971.c @@ -0,0 +1,719 @@ +/* + * wm8971.c -- WM8971 ALSA SoC Audio driver + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8971.h" + +#define WM8971_REG_COUNT 43 + +/* codec private data */ +struct wm8971_priv { + unsigned int sysclk; + struct delayed_work charge_work; + struct regmap *regmap; +}; + +/* + * wm8971 register cache + * We can't read the WM8971 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8971_reg_defaults[] = { + { 0, 0x0097 }, + { 1, 0x0097 }, + { 2, 0x0079 }, + { 3, 0x0079 }, + { 4, 0x0000 }, + { 5, 0x0008 }, + { 6, 0x0000 }, + { 7, 0x000a }, + { 8, 0x0000 }, + { 9, 0x0000 }, + { 10, 0x00ff }, + { 11, 0x00ff }, + { 12, 0x000f }, + { 13, 0x000f }, + { 14, 0x0000 }, + { 15, 0x0000 }, + { 16, 0x0000 }, + { 17, 0x007b }, + { 18, 0x0000 }, + { 19, 0x0032 }, + { 20, 0x0000 }, + { 21, 0x00c3 }, + { 22, 0x00c3 }, + { 23, 0x00c0 }, + { 24, 0x0000 }, + { 25, 0x0000 }, + { 26, 0x0000 }, + { 27, 0x0000 }, + { 28, 0x0000 }, + { 29, 0x0000 }, + { 30, 0x0000 }, + { 31, 0x0000 }, + { 32, 0x0000 }, + { 33, 0x0000 }, + { 34, 0x0050 }, + { 35, 0x0050 }, + { 36, 0x0050 }, + { 37, 0x0050 }, + { 38, 0x0050 }, + { 39, 0x0050 }, + { 40, 0x0079 }, + { 41, 0x0079 }, + { 42, 0x0079 }, +}; + +#define wm8971_reset(c) snd_soc_component_write(c, WM8971_RESET, 0) + +/* WM8971 Controls */ +static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" }; +static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz", + "200Hz @ 48kHz" }; +static const char *wm8971_treble[] = { "8kHz", "4kHz" }; +static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" }; +static const char *wm8971_ng_type[] = { "Constant PGA Gain", + "Mute ADC Output" }; +static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" }; +static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA", + "Differential"}; +static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA", + "Differential"}; +static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"}; +static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"}; +static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; + +static const struct soc_enum wm8971_enum[] = { + SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */ + SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter), + SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble), + SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func), + SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */ + SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase), + SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */ + SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux), + SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel), + SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel), + SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */ + SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux), +}; + +static const struct snd_kcontrol_new wm8971_snd_controls[] = { + SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0), + SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL, + 6, 1, 0), + SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1), + + SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V, + WM8971_ROUT1V, 7, 1, 0), + SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V, + WM8971_ROUT2V, 7, 1, 0), + SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0), + + SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0), + + SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1, + WM8971_LOUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1, + WM8971_ROUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1, + WM8971_MOUTM2, 4, 7, 1), + + SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V, + WM8971_ROUT1V, 0, 127, 0), + SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V, + WM8971_ROUT2V, 0, 127, 0), + + SOC_ENUM("Bass Boost", wm8971_enum[0]), + SOC_ENUM("Bass Filter", wm8971_enum[1]), + SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1), + + SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0), + SOC_ENUM("Treble Cut-off", wm8971_enum[2]), + + SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1), + + SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0), + + SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0), + SOC_ENUM("ALC Capture Function", wm8971_enum[3]), + SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0), + SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0), + SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0), + SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0), + SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0), + SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]), + SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0), + + SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0), + SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0), + + SOC_ENUM("Playback De-emphasis", wm8971_enum[5]), + SOC_ENUM("Playback Function", wm8971_enum[6]), + SOC_ENUM("Playback Phase", wm8971_enum[7]), + + SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0), +}; + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8971_left_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8971_right_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8971_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8971_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[11]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8971_monomux_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[13]); + +static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_left_mixer_controls[0], + ARRAY_SIZE(wm8971_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_right_mixer_controls[0], + ARRAY_SIZE(wm8971_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0, + &wm8971_mono_mixer_controls[0], + ARRAY_SIZE(wm8971_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Mic Bias", WM8971_PWR1, 1, 0, NULL, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0, + &wm8971_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0, + &wm8971_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_right_line_controls), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("MIC"), +}; + +static const struct snd_soc_dapm_route wm8971_dapm_routes[] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line", "LINPUT1"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line", "RINPUT1"}, + {"Right Line Mux", "Mic", "MIC"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line", "LINPUT1"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line", "RINPUT1"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line", "LINPUT1"}, + {"Differential Mux", "Line", "RINPUT1"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int wm8971_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8971_priv *wm8971 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8971->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8971_IFACE, iface); + return 0; +} + +static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8971_priv *wm8971 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8971_IFACE) & 0x1f3; + u16 srate = snd_soc_component_read32(component, WM8971_SRATE) & 0x1c0; + int coeff = get_coeff(wm8971->sysclk, params_rate(params)); + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + snd_soc_component_write(component, WM8971_IFACE, iface); + if (coeff >= 0) + snd_soc_component_write(component, WM8971_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8971_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8971_ADCDAC) & 0xfff7; + + if (mute) + snd_soc_component_write(component, WM8971_ADCDAC, mute_reg | 0x8); + else + snd_soc_component_write(component, WM8971_ADCDAC, mute_reg); + return 0; +} + +static void wm8971_charge_work(struct work_struct *work) +{ + struct wm8971_priv *wm8971 = + container_of(work, struct wm8971_priv, charge_work.work); + + /* Set to 500k */ + regmap_update_bits(wm8971->regmap, WM8971_PWR1, 0x0180, 0x0100); +} + +static int wm8971_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8971_priv *wm8971 = snd_soc_component_get_drvdata(component); + u16 pwr_reg = snd_soc_component_read32(component, WM8971_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + snd_soc_component_write(component, WM8971_PWR1, pwr_reg | 0x00c1); + break; + case SND_SOC_BIAS_PREPARE: + /* Wait until fully charged */ + flush_delayed_work(&wm8971->charge_work); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + snd_soc_component_cache_sync(component); + /* charge output caps - set vmid to 5k for quick power up */ + snd_soc_component_write(component, WM8971_PWR1, pwr_reg | 0x01c0); + queue_delayed_work(system_power_efficient_wq, + &wm8971->charge_work, msecs_to_jiffies(1000)); + } else { + /* mute dac and set vmid to 500k, enable VREF */ + snd_soc_component_write(component, WM8971_PWR1, pwr_reg | 0x0140); + } + + break; + case SND_SOC_BIAS_OFF: + cancel_delayed_work_sync(&wm8971->charge_work); + snd_soc_component_write(component, WM8971_PWR1, 0x0001); + break; + } + return 0; +} + +#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8971_dai_ops = { + .hw_params = wm8971_pcm_hw_params, + .digital_mute = wm8971_mute, + .set_fmt = wm8971_set_dai_fmt, + .set_sysclk = wm8971_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver wm8971_dai = { + .name = "wm8971-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .ops = &wm8971_dai_ops, +}; + +static int wm8971_probe(struct snd_soc_component *component) +{ + struct wm8971_priv *wm8971 = snd_soc_component_get_drvdata(component); + + INIT_DELAYED_WORK(&wm8971->charge_work, wm8971_charge_work); + + wm8971_reset(component); + + /* set the update bits */ + snd_soc_component_update_bits(component, WM8971_LDAC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8971_RDAC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8971_LOUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8971_ROUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8971_LOUT2V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8971_ROUT2V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8971_LINVOL, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8971_RINVOL, 0x0100, 0x0100); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8971 = { + .probe = wm8971_probe, + .set_bias_level = wm8971_set_bias_level, + .controls = wm8971_snd_controls, + .num_controls = ARRAY_SIZE(wm8971_snd_controls), + .dapm_widgets = wm8971_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8971_dapm_widgets), + .dapm_routes = wm8971_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8971_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8971_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8971_MOUTV, + + .reg_defaults = wm8971_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8971_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int wm8971_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8971_priv *wm8971; + int ret; + + wm8971 = devm_kzalloc(&i2c->dev, sizeof(struct wm8971_priv), + GFP_KERNEL); + if (wm8971 == NULL) + return -ENOMEM; + + wm8971->regmap = devm_regmap_init_i2c(i2c, &wm8971_regmap); + if (IS_ERR(wm8971->regmap)) + return PTR_ERR(wm8971->regmap); + + i2c_set_clientdata(i2c, wm8971); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8971, &wm8971_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8971_i2c_id[] = { + { "wm8971", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id); + +static struct i2c_driver wm8971_i2c_driver = { + .driver = { + .name = "wm8971", + }, + .probe = wm8971_i2c_probe, + .id_table = wm8971_i2c_id, +}; + +module_i2c_driver(wm8971_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8971 driver"); +MODULE_AUTHOR("Lab126"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8971.h b/sound/soc/codecs/wm8971.h new file mode 100644 index 000000000..f31c38fdd --- /dev/null +++ b/sound/soc/codecs/wm8971.h @@ -0,0 +1,56 @@ +/* + * wm8971.h -- audio driver for WM8971 + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8971_H +#define _WM8971_H + +#define WM8971_LINVOL 0x00 +#define WM8971_RINVOL 0x01 +#define WM8971_LOUT1V 0x02 +#define WM8971_ROUT1V 0x03 +#define WM8971_ADCDAC 0x05 +#define WM8971_IFACE 0x07 +#define WM8971_SRATE 0x08 +#define WM8971_LDAC 0x0a +#define WM8971_RDAC 0x0b +#define WM8971_BASS 0x0c +#define WM8971_TREBLE 0x0d +#define WM8971_RESET 0x0f +#define WM8971_ALC1 0x11 +#define WM8971_ALC2 0x12 +#define WM8971_ALC3 0x13 +#define WM8971_NGATE 0x14 +#define WM8971_LADC 0x15 +#define WM8971_RADC 0x16 +#define WM8971_ADCTL1 0x17 +#define WM8971_ADCTL2 0x18 +#define WM8971_PWR1 0x19 +#define WM8971_PWR2 0x1a +#define WM8971_ADCTL3 0x1b +#define WM8971_ADCIN 0x1f +#define WM8971_LADCIN 0x20 +#define WM8971_RADCIN 0x21 +#define WM8971_LOUTM1 0x22 +#define WM8971_LOUTM2 0x23 +#define WM8971_ROUTM1 0x24 +#define WM8971_ROUTM2 0x25 +#define WM8971_MOUTM1 0x26 +#define WM8971_MOUTM2 0x27 +#define WM8971_LOUT2V 0x28 +#define WM8971_ROUT2V 0x29 +#define WM8971_MOUTV 0x2A + +#define WM8971_SYSCLK 0 + +#endif diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c new file mode 100644 index 000000000..43edaf8cd --- /dev/null +++ b/sound/soc/codecs/wm8974.c @@ -0,0 +1,743 @@ +/* + * wm8974.c -- WM8974 ALSA Soc Audio driver + * + * Copyright 2006-2009 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8974.h" + +struct wm8974_priv { + unsigned int mclk; + unsigned int fs; +}; + +static const struct reg_default wm8974_reg_defaults[] = { + { 0, 0x0000 }, { 1, 0x0000 }, { 2, 0x0000 }, { 3, 0x0000 }, + { 4, 0x0050 }, { 5, 0x0000 }, { 6, 0x0140 }, { 7, 0x0000 }, + { 8, 0x0000 }, { 9, 0x0000 }, { 10, 0x0000 }, { 11, 0x00ff }, + { 12, 0x0000 }, { 13, 0x0000 }, { 14, 0x0100 }, { 15, 0x00ff }, + { 16, 0x0000 }, { 17, 0x0000 }, { 18, 0x012c }, { 19, 0x002c }, + { 20, 0x002c }, { 21, 0x002c }, { 22, 0x002c }, { 23, 0x0000 }, + { 24, 0x0032 }, { 25, 0x0000 }, { 26, 0x0000 }, { 27, 0x0000 }, + { 28, 0x0000 }, { 29, 0x0000 }, { 30, 0x0000 }, { 31, 0x0000 }, + { 32, 0x0038 }, { 33, 0x000b }, { 34, 0x0032 }, { 35, 0x0000 }, + { 36, 0x0008 }, { 37, 0x000c }, { 38, 0x0093 }, { 39, 0x00e9 }, + { 40, 0x0000 }, { 41, 0x0000 }, { 42, 0x0000 }, { 43, 0x0000 }, + { 44, 0x0003 }, { 45, 0x0010 }, { 46, 0x0000 }, { 47, 0x0000 }, + { 48, 0x0000 }, { 49, 0x0002 }, { 50, 0x0000 }, { 51, 0x0000 }, + { 52, 0x0000 }, { 53, 0x0000 }, { 54, 0x0039 }, { 55, 0x0000 }, + { 56, 0x0000 }, +}; + +#define WM8974_POWER1_BIASEN 0x08 +#define WM8974_POWER1_BUFIOEN 0x04 + +#define wm8974_reset(c) snd_soc_component_write(c, WM8974_RESET, 0) + +static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" }; +static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8974_eqmode[] = {"Capture", "Playback" }; +static const char *wm8974_bw[] = {"Narrow", "Wide" }; +static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" }; +static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" }; +static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" }; +static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" }; +static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" }; +static const char *wm8974_alc[] = {"ALC", "Limiter" }; + +static const struct soc_enum wm8974_enum[] = { + SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */ + SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */ + SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp), + SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode), + + SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1), + SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw), + SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2), + SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw), + + SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3), + SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw), + SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4), + SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw), + + SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5), + SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc), +}; + +static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" }; + +static SOC_ENUM_SINGLE_DECL(wm8974_auxmode, + WM8974_INPUT, 3, wm8974_auxmode_text); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0); + +static const struct snd_kcontrol_new wm8974_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8974_enum[1]), +SOC_ENUM("ADC Companding", wm8974_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8974_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0), + +SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv), + +SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0), + +SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL, 0, 255, 0, digital_tlv), + +SOC_ENUM("Equaliser Function", wm8974_enum[3]), +SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]), +SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1, 0, 24, 1, eq_tlv), + +SOC_ENUM("Equaliser EQ2 Bandwidth", wm8974_enum[5]), +SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]), +SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2, 0, 24, 1, eq_tlv), + +SOC_ENUM("Equaliser EQ3 Bandwidth", wm8974_enum[7]), +SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]), +SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3, 0, 24, 1, eq_tlv), + +SOC_ENUM("Equaliser EQ4 Bandwidth", wm8974_enum[9]), +SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]), +SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4, 0, 24, 1, eq_tlv), + +SOC_ENUM("Equaliser EQ5 Bandwidth", wm8974_enum[11]), +SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]), +SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5, 0, 24, 1, eq_tlv), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8974_enum[13]), +SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0), +SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0, inpga_tlv), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1), +SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0, spk_tlv), + +SOC_ENUM("Aux Mode", wm8974_auxmode), + +SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1), + +/* DAC / ADC oversampling */ +SOC_SINGLE("DAC 128x Oversampling Switch", WM8974_DAC, 8, 1, 0), +SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0), +}; + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 0), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0), +}; + +/* Boost mixer */ +static const struct snd_kcontrol_new wm8974_boost_mixer[] = { +SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0), +}; + +/* Input PGA */ +static const struct snd_kcontrol_new wm8974_inpga[] = { +SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0), +SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0), +SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0), +}; + +/* AUX Input boost vol */ +static const struct snd_kcontrol_new wm8974_aux_boost_controls = +SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0); + +/* Mic Input boost vol */ +static const struct snd_kcontrol_new wm8974_mic_boost_controls = +SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0); + +static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0, + &wm8974_speaker_mixer_controls[0], + ARRAY_SIZE(wm8974_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0, + &wm8974_mono_mixer_controls[0], + ARRAY_SIZE(wm8974_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga, + ARRAY_SIZE(wm8974_inpga)), +SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0, + wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)), + +SND_SOC_DAPM_SUPPLY("Mic Bias", WM8974_POWER1, 4, 0, NULL, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const struct snd_soc_dapm_route wm8974_dapm_routes[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Boost Mixer */ + {"ADC", NULL, "Boost Mixer"}, + {"Boost Mixer", "Aux Switch", "Aux Input"}, + {"Boost Mixer", NULL, "Input PGA"}, + {"Boost Mixer", NULL, "MICP"}, + + /* Input PGA */ + {"Input PGA", "Aux Switch", "Aux Input"}, + {"Input PGA", "MicN Switch", "MICN"}, + {"Input PGA", "MicP Switch", "MICP"}, + + /* Inputs */ + {"Aux Input", NULL, "AUX"}, +}; + +struct pll_ { + unsigned int pre_div:1; + unsigned int n:4; + unsigned int k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(struct pll_ *pll_div, + unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + /* There is a fixed divide by 4 in the output path */ + target *= 4; + + Ndiv = target / source; + if (Ndiv < 6) { + source /= 2; + pll_div->pre_div = 1; + Ndiv = target / source; + } else + pll_div->pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8974 N value %u outwith recommended range!\n", + Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct pll_ pll_div; + u16 reg; + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = snd_soc_component_read32(component, WM8974_CLOCK); + snd_soc_component_write(component, WM8974_CLOCK, reg & 0x0ff); + + /* Turn off PLL */ + reg = snd_soc_component_read32(component, WM8974_POWER1); + snd_soc_component_write(component, WM8974_POWER1, reg & 0x1df); + return 0; + } + + pll_factors(&pll_div, freq_out, freq_in); + + snd_soc_component_write(component, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n); + snd_soc_component_write(component, WM8974_PLLK1, pll_div.k >> 18); + snd_soc_component_write(component, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff); + snd_soc_component_write(component, WM8974_PLLK3, pll_div.k & 0x1ff); + reg = snd_soc_component_read32(component, WM8974_POWER1); + snd_soc_component_write(component, WM8974_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = snd_soc_component_read32(component, WM8974_CLOCK); + snd_soc_component_write(component, WM8974_CLOCK, reg | 0x100); + + return 0; +} + +/* + * Configure WM8974 clock dividers. + */ +static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + switch (div_id) { + case WM8974_OPCLKDIV: + reg = snd_soc_component_read32(component, WM8974_GPIO) & 0x1cf; + snd_soc_component_write(component, WM8974_GPIO, reg | div); + break; + case WM8974_MCLKDIV: + reg = snd_soc_component_read32(component, WM8974_CLOCK) & 0x11f; + snd_soc_component_write(component, WM8974_CLOCK, reg | div); + break; + case WM8974_BCLKDIV: + reg = snd_soc_component_read32(component, WM8974_CLOCK) & 0x1e3; + snd_soc_component_write(component, WM8974_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static unsigned int wm8974_get_mclkdiv(unsigned int f_in, unsigned int f_out, + int *mclkdiv) +{ + unsigned int ratio = 2 * f_in / f_out; + + if (ratio <= 2) { + *mclkdiv = WM8974_MCLKDIV_1; + ratio = 2; + } else if (ratio == 3) { + *mclkdiv = WM8974_MCLKDIV_1_5; + } else if (ratio == 4) { + *mclkdiv = WM8974_MCLKDIV_2; + } else if (ratio <= 6) { + *mclkdiv = WM8974_MCLKDIV_3; + ratio = 6; + } else if (ratio <= 8) { + *mclkdiv = WM8974_MCLKDIV_4; + ratio = 8; + } else if (ratio <= 12) { + *mclkdiv = WM8974_MCLKDIV_6; + ratio = 12; + } else if (ratio <= 16) { + *mclkdiv = WM8974_MCLKDIV_8; + ratio = 16; + } else { + *mclkdiv = WM8974_MCLKDIV_12; + ratio = 24; + } + + return f_out * ratio / 2; +} + +static int wm8974_update_clocks(struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8974_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int fs256; + unsigned int fpll = 0; + unsigned int f; + int mclkdiv; + + if (!priv->mclk || !priv->fs) + return 0; + + fs256 = 256 * priv->fs; + + f = wm8974_get_mclkdiv(priv->mclk, fs256, &mclkdiv); + + if (f != priv->mclk) { + /* The PLL performs best around 90MHz */ + fpll = wm8974_get_mclkdiv(22500000, fs256, &mclkdiv); + } + + wm8974_set_dai_pll(dai, 0, 0, priv->mclk, fpll); + wm8974_set_dai_clkdiv(dai, WM8974_MCLKDIV, mclkdiv); + + return 0; +} + +static int wm8974_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8974_priv *priv = snd_soc_component_get_drvdata(component); + + if (dir != SND_SOC_CLOCK_IN) + return -EINVAL; + + priv->mclk = freq; + + return wm8974_update_clocks(dai); +} + +static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + u16 clk = snd_soc_component_read32(component, WM8974_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8974_IFACE, iface); + snd_soc_component_write(component, WM8974_CLOCK, clk); + return 0; +} + +static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8974_priv *priv = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8974_IFACE) & 0x19f; + u16 adn = snd_soc_component_read32(component, WM8974_ADD) & 0x1f1; + int err; + + priv->fs = params_rate(params); + err = wm8974_update_clocks(dai); + if (err) + return err; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0020; + break; + case 24: + iface |= 0x0040; + break; + case 32: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case 8000: + adn |= 0x5 << 1; + break; + case 11025: + adn |= 0x4 << 1; + break; + case 16000: + adn |= 0x3 << 1; + break; + case 22050: + adn |= 0x2 << 1; + break; + case 32000: + adn |= 0x1 << 1; + break; + case 44100: + case 48000: + break; + } + + snd_soc_component_write(component, WM8974_IFACE, iface); + snd_soc_component_write(component, WM8974_ADD, adn); + return 0; +} + +static int wm8974_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8974_DAC) & 0xffbf; + + if (mute) + snd_soc_component_write(component, WM8974_DAC, mute_reg | 0x40); + else + snd_soc_component_write(component, WM8974_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8974_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + u16 power1 = snd_soc_component_read32(component, WM8974_POWER1) & ~0x3; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + power1 |= 0x1; /* VMID 50k */ + snd_soc_component_write(component, WM8974_POWER1, power1); + break; + + case SND_SOC_BIAS_STANDBY: + power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(dev_get_regmap(component->dev, NULL)); + + /* Initial cap charge at VMID 5k */ + snd_soc_component_write(component, WM8974_POWER1, power1 | 0x3); + mdelay(100); + } + + power1 |= 0x2; /* VMID 500k */ + snd_soc_component_write(component, WM8974_POWER1, power1); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, WM8974_POWER1, 0); + snd_soc_component_write(component, WM8974_POWER2, 0); + snd_soc_component_write(component, WM8974_POWER3, 0); + break; + } + + return 0; +} + +#define WM8974_RATES (SNDRV_PCM_RATE_8000_48000) + +#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8974_ops = { + .hw_params = wm8974_pcm_hw_params, + .digital_mute = wm8974_mute, + .set_fmt = wm8974_set_dai_fmt, + .set_clkdiv = wm8974_set_dai_clkdiv, + .set_pll = wm8974_set_dai_pll, + .set_sysclk = wm8974_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver wm8974_dai = { + .name = "wm8974-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, /* Only 1 channel of data */ + .rates = WM8974_RATES, + .formats = WM8974_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, /* Only 1 channel of data */ + .rates = WM8974_RATES, + .formats = WM8974_FORMATS,}, + .ops = &wm8974_ops, + .symmetric_rates = 1, +}; + +static const struct regmap_config wm8974_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = WM8974_MONOMIX, + .reg_defaults = wm8974_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8974_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +static int wm8974_probe(struct snd_soc_component *component) +{ + int ret = 0; + + ret = wm8974_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8974 = { + .probe = wm8974_probe, + .set_bias_level = wm8974_set_bias_level, + .controls = wm8974_snd_controls, + .num_controls = ARRAY_SIZE(wm8974_snd_controls), + .dapm_widgets = wm8974_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8974_dapm_widgets), + .dapm_routes = wm8974_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8974_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8974_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8974_priv *priv; + struct regmap *regmap; + int ret; + + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(i2c, priv); + + regmap = devm_regmap_init_i2c(i2c, &wm8974_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8974, &wm8974_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8974_i2c_id[] = { + { "wm8974", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id); + +static const struct of_device_id wm8974_of_match[] = { + { .compatible = "wlf,wm8974", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8974_of_match); + +static struct i2c_driver wm8974_i2c_driver = { + .driver = { + .name = "wm8974", + .of_match_table = wm8974_of_match, + }, + .probe = wm8974_i2c_probe, + .id_table = wm8974_i2c_id, +}; + +module_i2c_driver(wm8974_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8974 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8974.h b/sound/soc/codecs/wm8974.h new file mode 100644 index 000000000..3c94e7bb5 --- /dev/null +++ b/sound/soc/codecs/wm8974.h @@ -0,0 +1,86 @@ +/* + * wm8974.h -- WM8974 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8974_H +#define _WM8974_H + +/* WM8974 register space */ + +#define WM8974_RESET 0x0 +#define WM8974_POWER1 0x1 +#define WM8974_POWER2 0x2 +#define WM8974_POWER3 0x3 +#define WM8974_IFACE 0x4 +#define WM8974_COMP 0x5 +#define WM8974_CLOCK 0x6 +#define WM8974_ADD 0x7 +#define WM8974_GPIO 0x8 +#define WM8974_DAC 0xa +#define WM8974_DACVOL 0xb +#define WM8974_ADC 0xe +#define WM8974_ADCVOL 0xf +#define WM8974_EQ1 0x12 +#define WM8974_EQ2 0x13 +#define WM8974_EQ3 0x14 +#define WM8974_EQ4 0x15 +#define WM8974_EQ5 0x16 +#define WM8974_DACLIM1 0x18 +#define WM8974_DACLIM2 0x19 +#define WM8974_NOTCH1 0x1b +#define WM8974_NOTCH2 0x1c +#define WM8974_NOTCH3 0x1d +#define WM8974_NOTCH4 0x1e +#define WM8974_ALC1 0x20 +#define WM8974_ALC2 0x21 +#define WM8974_ALC3 0x22 +#define WM8974_NGATE 0x23 +#define WM8974_PLLN 0x24 +#define WM8974_PLLK1 0x25 +#define WM8974_PLLK2 0x26 +#define WM8974_PLLK3 0x27 +#define WM8974_ATTEN 0x28 +#define WM8974_INPUT 0x2c +#define WM8974_INPPGA 0x2d +#define WM8974_ADCBOOST 0x2f +#define WM8974_OUTPUT 0x31 +#define WM8974_SPKMIX 0x32 +#define WM8974_SPKVOL 0x36 +#define WM8974_MONOMIX 0x38 + +#define WM8974_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8974_OPCLKDIV 0 +#define WM8974_MCLKDIV 1 +#define WM8974_BCLKDIV 2 + +/* PLL Out dividers */ +#define WM8974_OPCLKDIV_1 (0 << 4) +#define WM8974_OPCLKDIV_2 (1 << 4) +#define WM8974_OPCLKDIV_3 (2 << 4) +#define WM8974_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8974_BCLKDIV_1 (0 << 2) +#define WM8974_BCLKDIV_2 (1 << 2) +#define WM8974_BCLKDIV_4 (2 << 2) +#define WM8974_BCLKDIV_8 (3 << 2) +#define WM8974_BCLKDIV_16 (4 << 2) +#define WM8974_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8974_MCLKDIV_1 (0 << 5) +#define WM8974_MCLKDIV_1_5 (1 << 5) +#define WM8974_MCLKDIV_2 (2 << 5) +#define WM8974_MCLKDIV_3 (3 << 5) +#define WM8974_MCLKDIV_4 (4 << 5) +#define WM8974_MCLKDIV_6 (5 << 5) +#define WM8974_MCLKDIV_8 (6 << 5) +#define WM8974_MCLKDIV_12 (7 << 5) + +#endif diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c new file mode 100644 index 000000000..bae4fe89d --- /dev/null +++ b/sound/soc/codecs/wm8978.c @@ -0,0 +1,1087 @@ +/* + * wm8978.c -- WM8978 ALSA SoC Audio Codec driver + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski + * Copyright (C) 2007 Carlos Munoz + * Copyright 2006-2009 Wolfson Microelectronics PLC. + * Based on wm8974 and wm8990 by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8978.h" + +static const struct reg_default wm8978_reg_defaults[] = { + { 1, 0x0000 }, + { 2, 0x0000 }, + { 3, 0x0000 }, + { 4, 0x0050 }, + { 5, 0x0000 }, + { 6, 0x0140 }, + { 7, 0x0000 }, + { 8, 0x0000 }, + { 9, 0x0000 }, + { 10, 0x0000 }, + { 11, 0x00ff }, + { 12, 0x00ff }, + { 13, 0x0000 }, + { 14, 0x0100 }, + { 15, 0x00ff }, + { 16, 0x00ff }, + { 17, 0x0000 }, + { 18, 0x012c }, + { 19, 0x002c }, + { 20, 0x002c }, + { 21, 0x002c }, + { 22, 0x002c }, + { 23, 0x0000 }, + { 24, 0x0032 }, + { 25, 0x0000 }, + { 26, 0x0000 }, + { 27, 0x0000 }, + { 28, 0x0000 }, + { 29, 0x0000 }, + { 30, 0x0000 }, + { 31, 0x0000 }, + { 32, 0x0038 }, + { 33, 0x000b }, + { 34, 0x0032 }, + { 35, 0x0000 }, + { 36, 0x0008 }, + { 37, 0x000c }, + { 38, 0x0093 }, + { 39, 0x00e9 }, + { 40, 0x0000 }, + { 41, 0x0000 }, + { 42, 0x0000 }, + { 43, 0x0000 }, + { 44, 0x0033 }, + { 45, 0x0010 }, + { 46, 0x0010 }, + { 47, 0x0100 }, + { 48, 0x0100 }, + { 49, 0x0002 }, + { 50, 0x0001 }, + { 51, 0x0001 }, + { 52, 0x0039 }, + { 53, 0x0039 }, + { 54, 0x0039 }, + { 55, 0x0039 }, + { 56, 0x0001 }, + { 57, 0x0001 }, +}; + +static bool wm8978_volatile(struct device *dev, unsigned int reg) +{ + return reg == WM8978_RESET; +} + +/* codec private data */ +struct wm8978_priv { + struct regmap *regmap; + unsigned int f_pllout; + unsigned int f_mclk; + unsigned int f_256fs; + unsigned int f_opclk; + int mclk_idx; + enum wm8978_sysclk_src sysclk; +}; + +static const char *wm8978_companding[] = {"Off", "NC", "u-law", "A-law"}; +static const char *wm8978_eqmode[] = {"Capture", "Playback"}; +static const char *wm8978_bw[] = {"Narrow", "Wide"}; +static const char *wm8978_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz"}; +static const char *wm8978_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz"}; +static const char *wm8978_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz"}; +static const char *wm8978_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz"}; +static const char *wm8978_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz"}; +static const char *wm8978_alc3[] = {"ALC", "Limiter"}; +static const char *wm8978_alc1[] = {"Off", "Right", "Left", "Both"}; + +static SOC_ENUM_SINGLE_DECL(adc_compand, WM8978_COMPANDING_CONTROL, 1, + wm8978_companding); +static SOC_ENUM_SINGLE_DECL(dac_compand, WM8978_COMPANDING_CONTROL, 3, + wm8978_companding); +static SOC_ENUM_SINGLE_DECL(eqmode, WM8978_EQ1, 8, wm8978_eqmode); +static SOC_ENUM_SINGLE_DECL(eq1, WM8978_EQ1, 5, wm8978_eq1); +static SOC_ENUM_SINGLE_DECL(eq2bw, WM8978_EQ2, 8, wm8978_bw); +static SOC_ENUM_SINGLE_DECL(eq2, WM8978_EQ2, 5, wm8978_eq2); +static SOC_ENUM_SINGLE_DECL(eq3bw, WM8978_EQ3, 8, wm8978_bw); +static SOC_ENUM_SINGLE_DECL(eq3, WM8978_EQ3, 5, wm8978_eq3); +static SOC_ENUM_SINGLE_DECL(eq4bw, WM8978_EQ4, 8, wm8978_bw); +static SOC_ENUM_SINGLE_DECL(eq4, WM8978_EQ4, 5, wm8978_eq4); +static SOC_ENUM_SINGLE_DECL(eq5, WM8978_EQ5, 5, wm8978_eq5); +static SOC_ENUM_SINGLE_DECL(alc3, WM8978_ALC_CONTROL_3, 8, wm8978_alc3); +static SOC_ENUM_SINGLE_DECL(alc1, WM8978_ALC_CONTROL_1, 7, wm8978_alc1); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(limiter_tlv, 0, 100, 0); + +static const struct snd_kcontrol_new wm8978_snd_controls[] = { + + SOC_SINGLE("Digital Loopback Switch", + WM8978_COMPANDING_CONTROL, 0, 1, 0), + + SOC_ENUM("ADC Companding", adc_compand), + SOC_ENUM("DAC Companding", dac_compand), + + SOC_DOUBLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 1, 0), + + SOC_DOUBLE_R_TLV("PCM Volume", + WM8978_LEFT_DAC_DIGITAL_VOLUME, WM8978_RIGHT_DAC_DIGITAL_VOLUME, + 0, 255, 0, digital_tlv), + + SOC_SINGLE("High Pass Filter Switch", WM8978_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("High Pass Cut Off", WM8978_ADC_CONTROL, 4, 7, 0), + SOC_DOUBLE("ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 1, 0), + + SOC_DOUBLE_R_TLV("ADC Volume", + WM8978_LEFT_ADC_DIGITAL_VOLUME, WM8978_RIGHT_ADC_DIGITAL_VOLUME, + 0, 255, 0, digital_tlv), + + SOC_ENUM("Equaliser Function", eqmode), + SOC_ENUM("EQ1 Cut Off", eq1), + SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1, 0, 24, 1, eq_tlv), + + SOC_ENUM("Equaliser EQ2 Bandwidth", eq2bw), + SOC_ENUM("EQ2 Cut Off", eq2), + SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2, 0, 24, 1, eq_tlv), + + SOC_ENUM("Equaliser EQ3 Bandwidth", eq3bw), + SOC_ENUM("EQ3 Cut Off", eq3), + SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3, 0, 24, 1, eq_tlv), + + SOC_ENUM("Equaliser EQ4 Bandwidth", eq4bw), + SOC_ENUM("EQ4 Cut Off", eq4), + SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4, 0, 24, 1, eq_tlv), + + SOC_ENUM("EQ5 Cut Off", eq5), + SOC_SINGLE_TLV("EQ5 Volume", WM8978_EQ5, 0, 24, 1, eq_tlv), + + SOC_SINGLE("DAC Playback Limiter Switch", + WM8978_DAC_LIMITER_1, 8, 1, 0), + SOC_SINGLE("DAC Playback Limiter Decay", + WM8978_DAC_LIMITER_1, 4, 15, 0), + SOC_SINGLE("DAC Playback Limiter Attack", + WM8978_DAC_LIMITER_1, 0, 15, 0), + + SOC_SINGLE("DAC Playback Limiter Threshold", + WM8978_DAC_LIMITER_2, 4, 7, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Volume", + WM8978_DAC_LIMITER_2, 0, 12, 0, limiter_tlv), + + SOC_ENUM("ALC Enable Switch", alc1), + SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0), + SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0), + + SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 10, 0), + SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0), + + SOC_ENUM("ALC Capture Mode", alc3), + SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 10, 0), + SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 10, 0), + + SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Threshold", + WM8978_NOISE_GATE, 0, 7, 0), + + SOC_DOUBLE_R("Capture PGA ZC Switch", + WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL, + 7, 1, 0), + + /* OUT1 - Headphones */ + SOC_DOUBLE_R("Headphone Playback ZC Switch", + WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 7, 1, 0), + + SOC_DOUBLE_R_TLV("Headphone Playback Volume", + WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, + 0, 63, 0, spk_tlv), + + /* OUT2 - Speakers */ + SOC_DOUBLE_R("Speaker Playback ZC Switch", + WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 7, 1, 0), + + SOC_DOUBLE_R_TLV("Speaker Playback Volume", + WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, + 0, 63, 0, spk_tlv), + + /* OUT3/4 - Line Output */ + SOC_DOUBLE_R("Line Playback Switch", + WM8978_OUT3_MIXER_CONTROL, WM8978_OUT4_MIXER_CONTROL, 6, 1, 1), + + /* Mixer #3: Boost (Input) mixer */ + SOC_DOUBLE_R("PGA Boost (+20dB)", + WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL, + 8, 1, 0), + SOC_DOUBLE_R_TLV("L2/R2 Boost Volume", + WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL, + 4, 7, 0, boost_tlv), + SOC_DOUBLE_R_TLV("Aux Boost Volume", + WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL, + 0, 7, 0, boost_tlv), + + /* Input PGA volume */ + SOC_DOUBLE_R_TLV("Input PGA Volume", + WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL, + 0, 63, 0, inpga_tlv), + + /* Headphone */ + SOC_DOUBLE_R("Headphone Switch", + WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 6, 1, 1), + + /* Speaker */ + SOC_DOUBLE_R("Speaker Switch", + WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1), + + /* DAC / ADC oversampling */ + SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, + 5, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, + 5, 1, 0), +}; + +/* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */ +static const struct snd_kcontrol_new wm8978_left_out_mixer[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_LEFT_MIXER_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_LEFT_MIXER_CONTROL, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_LEFT_MIXER_CONTROL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8978_right_out_mixer[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_RIGHT_MIXER_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 0, 1, 0), +}; + +/* OUT3/OUT4 Mixer not implemented */ + +/* Mixer #2: Input PGA Mute */ +static const struct snd_kcontrol_new wm8978_left_input_mixer[] = { + SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0), +}; +static const struct snd_kcontrol_new wm8978_right_input_mixer[] = { + SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8978_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", + WM8978_POWER_MANAGEMENT_3, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", + WM8978_POWER_MANAGEMENT_3, 1, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", + WM8978_POWER_MANAGEMENT_2, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", + WM8978_POWER_MANAGEMENT_2, 1, 0), + + /* Mixer #1: OUT1,2 */ + SOC_MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_3, + 2, 0, wm8978_left_out_mixer), + SOC_MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_3, + 3, 0, wm8978_right_out_mixer), + + SOC_MIXER_ARRAY("Left Input Mixer", WM8978_POWER_MANAGEMENT_2, + 2, 0, wm8978_left_input_mixer), + SOC_MIXER_ARRAY("Right Input Mixer", WM8978_POWER_MANAGEMENT_2, + 3, 0, wm8978_right_input_mixer), + + SND_SOC_DAPM_PGA("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, + 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, + 5, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL, + 6, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL, + 6, 1, NULL, 0), + + SND_SOC_DAPM_PGA("Left Headphone Out", WM8978_POWER_MANAGEMENT_2, + 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Headphone Out", WM8978_POWER_MANAGEMENT_2, + 8, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Left Speaker Out", WM8978_POWER_MANAGEMENT_3, + 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Speaker Out", WM8978_POWER_MANAGEMENT_3, + 5, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("OUT4 VMID", WM8978_POWER_MANAGEMENT_3, + 8, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8978_POWER_MANAGEMENT_1, 4, 0), + + SND_SOC_DAPM_INPUT("LMICN"), + SND_SOC_DAPM_INPUT("LMICP"), + SND_SOC_DAPM_INPUT("RMICN"), + SND_SOC_DAPM_INPUT("RMICP"), + SND_SOC_DAPM_INPUT("LAUX"), + SND_SOC_DAPM_INPUT("RAUX"), + SND_SOC_DAPM_INPUT("L2"), + SND_SOC_DAPM_INPUT("R2"), + SND_SOC_DAPM_OUTPUT("LHP"), + SND_SOC_DAPM_OUTPUT("RHP"), + SND_SOC_DAPM_OUTPUT("LSPK"), + SND_SOC_DAPM_OUTPUT("RSPK"), +}; + +static const struct snd_soc_dapm_route wm8978_dapm_routes[] = { + /* Output mixer */ + {"Right Output Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right Output Mixer", "Aux Playback Switch", "RAUX"}, + {"Right Output Mixer", "Line Bypass Switch", "Right Boost Mixer"}, + + {"Left Output Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left Output Mixer", "Aux Playback Switch", "LAUX"}, + {"Left Output Mixer", "Line Bypass Switch", "Left Boost Mixer"}, + + /* Outputs */ + {"Right Headphone Out", NULL, "Right Output Mixer"}, + {"RHP", NULL, "Right Headphone Out"}, + + {"Left Headphone Out", NULL, "Left Output Mixer"}, + {"LHP", NULL, "Left Headphone Out"}, + + {"Right Speaker Out", NULL, "Right Output Mixer"}, + {"RSPK", NULL, "Right Speaker Out"}, + + {"Left Speaker Out", NULL, "Left Output Mixer"}, + {"LSPK", NULL, "Left Speaker Out"}, + + /* Boost Mixer */ + {"Right ADC", NULL, "Right Boost Mixer"}, + + {"Right Boost Mixer", NULL, "RAUX"}, + {"Right Boost Mixer", NULL, "Right Capture PGA"}, + {"Right Boost Mixer", NULL, "R2"}, + + {"Left ADC", NULL, "Left Boost Mixer"}, + + {"Left Boost Mixer", NULL, "LAUX"}, + {"Left Boost Mixer", NULL, "Left Capture PGA"}, + {"Left Boost Mixer", NULL, "L2"}, + + /* Input PGA */ + {"Right Capture PGA", NULL, "Right Input Mixer"}, + {"Left Capture PGA", NULL, "Left Input Mixer"}, + + {"Right Input Mixer", "R2 Switch", "R2"}, + {"Right Input Mixer", "MicN Switch", "RMICN"}, + {"Right Input Mixer", "MicP Switch", "RMICP"}, + + {"Left Input Mixer", "L2 Switch", "L2"}, + {"Left Input Mixer", "MicN Switch", "LMICN"}, + {"Left Input Mixer", "MicP Switch", "LMICP"}, +}; + +/* PLL divisors */ +struct wm8978_pll_div { + u32 k; + u8 n; + u8 div2; +}; + +#define FIXED_PLL_SIZE (1 << 24) + +static void pll_factors(struct snd_soc_component *component, + struct wm8978_pll_div *pll_div, unsigned int target, unsigned int source) +{ + u64 k_part; + unsigned int k, n_div, n_mod; + + n_div = target / source; + if (n_div < 6) { + source >>= 1; + pll_div->div2 = 1; + n_div = target / source; + } else { + pll_div->div2 = 0; + } + + if (n_div < 6 || n_div > 12) + dev_warn(component->dev, + "WM8978 N value exceeds recommended range! N = %u\n", + n_div); + + pll_div->n = n_div; + n_mod = target - source * n_div; + k_part = FIXED_PLL_SIZE * (long long)n_mod + source / 2; + + do_div(k_part, source); + + k = k_part & 0xFFFFFFFF; + + pll_div->k = k; +} + +/* MCLK dividers */ +static const int mclk_numerator[] = {1, 3, 2, 3, 4, 6, 8, 12}; +static const int mclk_denominator[] = {1, 2, 1, 1, 1, 1, 1, 1}; + +/* + * find index >= idx, such that, for a given f_out, + * 3 * f_mclk / 4 <= f_PLLOUT < 13 * f_mclk / 4 + * f_out can be f_256fs or f_opclk, currently only used for f_256fs. Can be + * generalised for f_opclk with suitable coefficient arrays, but currently + * the OPCLK divisor is calculated directly, not iteratively. + */ +static int wm8978_enum_mclk(unsigned int f_out, unsigned int f_mclk, + unsigned int *f_pllout) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) { + unsigned int f_pllout_x4 = 4 * f_out * mclk_numerator[i] / + mclk_denominator[i]; + if (3 * f_mclk <= f_pllout_x4 && f_pllout_x4 < 13 * f_mclk) { + *f_pllout = f_pllout_x4 / 4; + return i; + } + } + + return -EINVAL; +} + +/* + * Calculate internal frequencies and dividers, according to Figure 40 + * "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6 + */ +static int wm8978_configure_pll(struct snd_soc_component *component) +{ + struct wm8978_priv *wm8978 = snd_soc_component_get_drvdata(component); + struct wm8978_pll_div pll_div; + unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk, + f_256fs = wm8978->f_256fs; + unsigned int f2; + + if (!f_mclk) + return -EINVAL; + + if (f_opclk) { + unsigned int opclk_div; + /* Cannot set up MCLK divider now, do later */ + wm8978->mclk_idx = -1; + + /* + * The user needs OPCLK. Choose OPCLKDIV to put + * 6 <= R = f2 / f1 < 13, 1 <= OPCLKDIV <= 4. + * f_opclk = f_mclk * prescale * R / 4 / OPCLKDIV, where + * prescale = 1, or prescale = 2. Prescale is calculated inside + * pll_factors(). We have to select f_PLLOUT, such that + * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be + * f_mclk * 3 / 16 <= f_opclk < f_mclk * 13 / 4. + */ + if (16 * f_opclk < 3 * f_mclk || 4 * f_opclk >= 13 * f_mclk) + return -EINVAL; + + if (4 * f_opclk < 3 * f_mclk) + /* Have to use OPCLKDIV */ + opclk_div = (3 * f_mclk / 4 + f_opclk - 1) / f_opclk; + else + opclk_div = 1; + + dev_dbg(component->dev, "%s: OPCLKDIV=%d\n", __func__, opclk_div); + + snd_soc_component_update_bits(component, WM8978_GPIO_CONTROL, 0x30, + (opclk_div - 1) << 4); + + wm8978->f_pllout = f_opclk * opclk_div; + } else if (f_256fs) { + /* + * Not using OPCLK, but PLL is used for the codec, choose R: + * 6 <= R = f2 / f1 < 13, to put 1 <= MCLKDIV <= 12. + * f_256fs = f_mclk * prescale * R / 4 / MCLKDIV, where + * prescale = 1, or prescale = 2. Prescale is calculated inside + * pll_factors(). We have to select f_PLLOUT, such that + * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be + * f_mclk * 3 / 48 <= f_256fs < f_mclk * 13 / 4. This means MCLK + * must be 3.781MHz <= f_MCLK <= 32.768MHz + */ + int idx = wm8978_enum_mclk(f_256fs, f_mclk, &wm8978->f_pllout); + if (idx < 0) + return idx; + + wm8978->mclk_idx = idx; + } else { + return -EINVAL; + } + + f2 = wm8978->f_pllout * 4; + + dev_dbg(component->dev, "%s: f_MCLK=%uHz, f_PLLOUT=%uHz\n", __func__, + wm8978->f_mclk, wm8978->f_pllout); + + pll_factors(component, &pll_div, f2, wm8978->f_mclk); + + dev_dbg(component->dev, "%s: calculated PLL N=0x%x, K=0x%x, div2=%d\n", + __func__, pll_div.n, pll_div.k, pll_div.div2); + + /* Turn PLL off for configuration... */ + snd_soc_component_update_bits(component, WM8978_POWER_MANAGEMENT_1, 0x20, 0); + + snd_soc_component_write(component, WM8978_PLL_N, (pll_div.div2 << 4) | pll_div.n); + snd_soc_component_write(component, WM8978_PLL_K1, pll_div.k >> 18); + snd_soc_component_write(component, WM8978_PLL_K2, (pll_div.k >> 9) & 0x1ff); + snd_soc_component_write(component, WM8978_PLL_K3, pll_div.k & 0x1ff); + + /* ...and on again */ + snd_soc_component_update_bits(component, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20); + + if (f_opclk) + /* Output PLL (OPCLK) to GPIO1 */ + snd_soc_component_update_bits(component, WM8978_GPIO_CONTROL, 7, 4); + + return 0; +} + +/* + * Configure WM8978 clock dividers. + */ +static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8978_priv *wm8978 = snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (div_id) { + case WM8978_OPCLKRATE: + wm8978->f_opclk = div; + + if (wm8978->f_mclk) + /* + * We know the MCLK frequency, the user has requested + * OPCLK, configure the PLL based on that and start it + * and OPCLK immediately. We will configure PLL to match + * user-requested OPCLK frquency as good as possible. + * In fact, it is likely, that matching the sampling + * rate, when it becomes known, is more important, and + * we will not be reconfiguring PLL then, because we + * must not interrupt OPCLK. But it should be fine, + * because typically the user will request OPCLK to run + * at 256fs or 512fs, and for these cases we will also + * find an exact MCLK divider configuration - it will + * be equal to or double the OPCLK divisor. + */ + ret = wm8978_configure_pll(component); + break; + case WM8978_BCLKDIV: + if (div & ~0x1c) + return -EINVAL; + snd_soc_component_update_bits(component, WM8978_CLOCKING, 0x1c, div); + break; + default: + return -EINVAL; + } + + dev_dbg(component->dev, "%s: ID %d, value %u\n", __func__, div_id, div); + + return ret; +} + +/* + * @freq: when .set_pll() us not used, freq is codec MCLK input frequency + */ +static int wm8978_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8978_priv *wm8978 = snd_soc_component_get_drvdata(component); + int ret = 0; + + dev_dbg(component->dev, "%s: ID %d, freq %u\n", __func__, clk_id, freq); + + if (freq) { + wm8978->f_mclk = freq; + + /* Even if MCLK is used for system clock, might have to drive OPCLK */ + if (wm8978->f_opclk) + ret = wm8978_configure_pll(component); + + /* Our sysclk is fixed to 256 * fs, will configure in .hw_params() */ + + if (!ret) + wm8978->sysclk = clk_id; + } + + if (wm8978->sysclk == WM8978_PLL && (!freq || clk_id == WM8978_MCLK)) { + /* Clock CODEC directly from MCLK */ + snd_soc_component_update_bits(component, WM8978_CLOCKING, 0x100, 0); + + /* GPIO1 into default mode as input - before configuring PLL */ + snd_soc_component_update_bits(component, WM8978_GPIO_CONTROL, 7, 0); + + /* Turn off PLL */ + snd_soc_component_update_bits(component, WM8978_POWER_MANAGEMENT_1, 0x20, 0); + wm8978->sysclk = WM8978_MCLK; + wm8978->f_pllout = 0; + wm8978->f_opclk = 0; + } + + return ret; +} + +/* + * Set ADC and Voice DAC format. + */ +static int wm8978_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + /* + * BCLK polarity mask = 0x100, LRC clock polarity mask = 0x80, + * Data Format mask = 0x18: all will be calculated anew + */ + u16 iface = snd_soc_component_read32(component, WM8978_AUDIO_INTERFACE) & ~0x198; + u16 clk = snd_soc_component_read32(component, WM8978_CLOCKING); + + dev_dbg(component->dev, "%s\n", __func__); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + clk &= ~1; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x8; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x18; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x80; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8978_AUDIO_INTERFACE, iface); + snd_soc_component_write(component, WM8978_CLOCKING, clk); + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8978_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8978_priv *wm8978 = snd_soc_component_get_drvdata(component); + /* Word length mask = 0x60 */ + u16 iface_ctl = snd_soc_component_read32(component, WM8978_AUDIO_INTERFACE) & ~0x60; + /* Sampling rate mask = 0xe (for filters) */ + u16 add_ctl = snd_soc_component_read32(component, WM8978_ADDITIONAL_CONTROL) & ~0xe; + u16 clking = snd_soc_component_read32(component, WM8978_CLOCKING); + enum wm8978_sysclk_src current_clk_id = clking & 0x100 ? + WM8978_PLL : WM8978_MCLK; + unsigned int f_sel, diff, diff_best = INT_MAX; + int i, best = 0; + + if (!wm8978->f_mclk) + return -EINVAL; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface_ctl |= 0x20; + break; + case 24: + iface_ctl |= 0x40; + break; + case 32: + iface_ctl |= 0x60; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case 8000: + add_ctl |= 0x5 << 1; + break; + case 11025: + add_ctl |= 0x4 << 1; + break; + case 16000: + add_ctl |= 0x3 << 1; + break; + case 22050: + add_ctl |= 0x2 << 1; + break; + case 32000: + add_ctl |= 0x1 << 1; + break; + case 44100: + case 48000: + break; + } + + /* Sampling rate is known now, can configure the MCLK divider */ + wm8978->f_256fs = params_rate(params) * 256; + + if (wm8978->sysclk == WM8978_MCLK) { + wm8978->mclk_idx = -1; + f_sel = wm8978->f_mclk; + } else { + if (!wm8978->f_opclk) { + /* We only enter here, if OPCLK is not used */ + int ret = wm8978_configure_pll(component); + if (ret < 0) + return ret; + } + f_sel = wm8978->f_pllout; + } + + if (wm8978->mclk_idx < 0) { + /* Either MCLK is used directly, or OPCLK is used */ + if (f_sel < wm8978->f_256fs || f_sel > 12 * wm8978->f_256fs) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) { + diff = abs(wm8978->f_256fs * 3 - + f_sel * 3 * mclk_denominator[i] / mclk_numerator[i]); + + if (diff < diff_best) { + diff_best = diff; + best = i; + } + + if (!diff) + break; + } + } else { + /* OPCLK not used, codec driven by PLL */ + best = wm8978->mclk_idx; + diff = 0; + } + + if (diff) + dev_warn(component->dev, "Imprecise sampling rate: %uHz%s\n", + f_sel * mclk_denominator[best] / mclk_numerator[best] / 256, + wm8978->sysclk == WM8978_MCLK ? + ", consider using PLL" : ""); + + dev_dbg(component->dev, "%s: width %d, rate %u, MCLK divisor #%d\n", __func__, + params_width(params), params_rate(params), best); + + /* MCLK divisor mask = 0xe0 */ + snd_soc_component_update_bits(component, WM8978_CLOCKING, 0xe0, best << 5); + + snd_soc_component_write(component, WM8978_AUDIO_INTERFACE, iface_ctl); + snd_soc_component_write(component, WM8978_ADDITIONAL_CONTROL, add_ctl); + + if (wm8978->sysclk != current_clk_id) { + if (wm8978->sysclk == WM8978_PLL) + /* Run CODEC from PLL instead of MCLK */ + snd_soc_component_update_bits(component, WM8978_CLOCKING, + 0x100, 0x100); + else + /* Clock CODEC directly from MCLK */ + snd_soc_component_update_bits(component, WM8978_CLOCKING, 0x100, 0); + } + + return 0; +} + +static int wm8978_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + dev_dbg(component->dev, "%s: %d\n", __func__, mute); + + if (mute) + snd_soc_component_update_bits(component, WM8978_DAC_CONTROL, 0x40, 0x40); + else + snd_soc_component_update_bits(component, WM8978_DAC_CONTROL, 0x40, 0); + + return 0; +} + +static int wm8978_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + u16 power1 = snd_soc_component_read32(component, WM8978_POWER_MANAGEMENT_1) & ~3; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + power1 |= 1; /* VMID 75k */ + snd_soc_component_write(component, WM8978_POWER_MANAGEMENT_1, power1); + break; + case SND_SOC_BIAS_STANDBY: + /* bit 3: enable bias, bit 2: enable I/O tie off buffer */ + power1 |= 0xc; + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Initial cap charge at VMID 5k */ + snd_soc_component_write(component, WM8978_POWER_MANAGEMENT_1, + power1 | 0x3); + mdelay(100); + } + + power1 |= 0x2; /* VMID 500k */ + snd_soc_component_write(component, WM8978_POWER_MANAGEMENT_1, power1); + break; + case SND_SOC_BIAS_OFF: + /* Preserve PLL - OPCLK may be used by someone */ + snd_soc_component_update_bits(component, WM8978_POWER_MANAGEMENT_1, ~0x20, 0); + snd_soc_component_write(component, WM8978_POWER_MANAGEMENT_2, 0); + snd_soc_component_write(component, WM8978_POWER_MANAGEMENT_3, 0); + break; + } + + dev_dbg(component->dev, "%s: %d, %x\n", __func__, level, power1); + + return 0; +} + +#define WM8978_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8978_dai_ops = { + .hw_params = wm8978_hw_params, + .digital_mute = wm8978_mute, + .set_fmt = wm8978_set_dai_fmt, + .set_clkdiv = wm8978_set_dai_clkdiv, + .set_sysclk = wm8978_set_dai_sysclk, +}; + +/* Also supports 12kHz */ +static struct snd_soc_dai_driver wm8978_dai = { + .name = "wm8978-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8978_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8978_FORMATS, + }, + .ops = &wm8978_dai_ops, + .symmetric_rates = 1, +}; + +static int wm8978_suspend(struct snd_soc_component *component) +{ + struct wm8978_priv *wm8978 = snd_soc_component_get_drvdata(component); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + /* Also switch PLL off */ + snd_soc_component_write(component, WM8978_POWER_MANAGEMENT_1, 0); + + regcache_mark_dirty(wm8978->regmap); + + return 0; +} + +static int wm8978_resume(struct snd_soc_component *component) +{ + struct wm8978_priv *wm8978 = snd_soc_component_get_drvdata(component); + + /* Sync reg_cache with the hardware */ + regcache_sync(wm8978->regmap); + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + if (wm8978->f_pllout) + /* Switch PLL on */ + snd_soc_component_update_bits(component, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20); + + return 0; +} + +/* + * These registers contain an "update" bit - bit 8. This means, for example, + * that one can write new DAC digital volume for both channels, but only when + * the update bit is set, will also the volume be updated - simultaneously for + * both channels. + */ +static const int update_reg[] = { + WM8978_LEFT_DAC_DIGITAL_VOLUME, + WM8978_RIGHT_DAC_DIGITAL_VOLUME, + WM8978_LEFT_ADC_DIGITAL_VOLUME, + WM8978_RIGHT_ADC_DIGITAL_VOLUME, + WM8978_LEFT_INP_PGA_CONTROL, + WM8978_RIGHT_INP_PGA_CONTROL, + WM8978_LOUT1_HP_CONTROL, + WM8978_ROUT1_HP_CONTROL, + WM8978_LOUT2_SPK_CONTROL, + WM8978_ROUT2_SPK_CONTROL, +}; + +static int wm8978_probe(struct snd_soc_component *component) +{ + struct wm8978_priv *wm8978 = snd_soc_component_get_drvdata(component); + int i; + + /* + * Set default system clock to PLL, it is more precise, this is also the + * default hardware setting + */ + wm8978->sysclk = WM8978_PLL; + + /* + * Set the update bit in all registers, that have one. This way all + * writes to those registers will also cause the update bit to be + * written. + */ + for (i = 0; i < ARRAY_SIZE(update_reg); i++) + snd_soc_component_update_bits(component, update_reg[i], 0x100, 0x100); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8978 = { + .probe = wm8978_probe, + .suspend = wm8978_suspend, + .resume = wm8978_resume, + .set_bias_level = wm8978_set_bias_level, + .controls = wm8978_snd_controls, + .num_controls = ARRAY_SIZE(wm8978_snd_controls), + .dapm_widgets = wm8978_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8978_dapm_widgets), + .dapm_routes = wm8978_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8978_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8978_regmap_config = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = WM8978_MAX_REGISTER, + .volatile_reg = wm8978_volatile, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8978_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8978_reg_defaults), +}; + +static int wm8978_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8978_priv *wm8978; + int ret; + + wm8978 = devm_kzalloc(&i2c->dev, sizeof(struct wm8978_priv), + GFP_KERNEL); + if (wm8978 == NULL) + return -ENOMEM; + + wm8978->regmap = devm_regmap_init_i2c(i2c, &wm8978_regmap_config); + if (IS_ERR(wm8978->regmap)) { + ret = PTR_ERR(wm8978->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8978); + + /* Reset the codec */ + ret = regmap_write(wm8978->regmap, WM8978_RESET, 0); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8978, &wm8978_dai, 1); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct i2c_device_id wm8978_i2c_id[] = { + { "wm8978", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id); + +static const struct of_device_id wm8978_of_match[] = { + { .compatible = "wlf,wm8978", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8978_of_match); + +static struct i2c_driver wm8978_i2c_driver = { + .driver = { + .name = "wm8978", + .of_match_table = wm8978_of_match, + }, + .probe = wm8978_i2c_probe, + .id_table = wm8978_i2c_id, +}; + +module_i2c_driver(wm8978_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8978 codec driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8978.h b/sound/soc/codecs/wm8978.h new file mode 100644 index 000000000..0dcf6868d --- /dev/null +++ b/sound/soc/codecs/wm8978.h @@ -0,0 +1,85 @@ +/* + * wm8978.h -- codec driver for WM8978 + * + * Copyright 2009 Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __WM8978_H__ +#define __WM8978_H__ + +/* + * Register values. + */ +#define WM8978_RESET 0x00 +#define WM8978_POWER_MANAGEMENT_1 0x01 +#define WM8978_POWER_MANAGEMENT_2 0x02 +#define WM8978_POWER_MANAGEMENT_3 0x03 +#define WM8978_AUDIO_INTERFACE 0x04 +#define WM8978_COMPANDING_CONTROL 0x05 +#define WM8978_CLOCKING 0x06 +#define WM8978_ADDITIONAL_CONTROL 0x07 +#define WM8978_GPIO_CONTROL 0x08 +#define WM8978_JACK_DETECT_CONTROL_1 0x09 +#define WM8978_DAC_CONTROL 0x0A +#define WM8978_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8978_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8978_JACK_DETECT_CONTROL_2 0x0D +#define WM8978_ADC_CONTROL 0x0E +#define WM8978_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8978_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8978_EQ1 0x12 +#define WM8978_EQ2 0x13 +#define WM8978_EQ3 0x14 +#define WM8978_EQ4 0x15 +#define WM8978_EQ5 0x16 +#define WM8978_DAC_LIMITER_1 0x18 +#define WM8978_DAC_LIMITER_2 0x19 +#define WM8978_NOTCH_FILTER_1 0x1b +#define WM8978_NOTCH_FILTER_2 0x1c +#define WM8978_NOTCH_FILTER_3 0x1d +#define WM8978_NOTCH_FILTER_4 0x1e +#define WM8978_ALC_CONTROL_1 0x20 +#define WM8978_ALC_CONTROL_2 0x21 +#define WM8978_ALC_CONTROL_3 0x22 +#define WM8978_NOISE_GATE 0x23 +#define WM8978_PLL_N 0x24 +#define WM8978_PLL_K1 0x25 +#define WM8978_PLL_K2 0x26 +#define WM8978_PLL_K3 0x27 +#define WM8978_3D_CONTROL 0x29 +#define WM8978_BEEP_CONTROL 0x2b +#define WM8978_INPUT_CONTROL 0x2c +#define WM8978_LEFT_INP_PGA_CONTROL 0x2d +#define WM8978_RIGHT_INP_PGA_CONTROL 0x2e +#define WM8978_LEFT_ADC_BOOST_CONTROL 0x2f +#define WM8978_RIGHT_ADC_BOOST_CONTROL 0x30 +#define WM8978_OUTPUT_CONTROL 0x31 +#define WM8978_LEFT_MIXER_CONTROL 0x32 +#define WM8978_RIGHT_MIXER_CONTROL 0x33 +#define WM8978_LOUT1_HP_CONTROL 0x34 +#define WM8978_ROUT1_HP_CONTROL 0x35 +#define WM8978_LOUT2_SPK_CONTROL 0x36 +#define WM8978_ROUT2_SPK_CONTROL 0x37 +#define WM8978_OUT3_MIXER_CONTROL 0x38 +#define WM8978_OUT4_MIXER_CONTROL 0x39 + +#define WM8978_MAX_REGISTER 0x39 + +#define WM8978_CACHEREGNUM 58 + +/* Clock divider Id's */ +enum wm8978_clk_id { + WM8978_OPCLKRATE, + WM8978_BCLKDIV, +}; + +enum wm8978_sysclk_src { + WM8978_MCLK = 0, + WM8978_PLL, +}; + +#endif /* __WM8978_H__ */ diff --git a/sound/soc/codecs/wm8983.c b/sound/soc/codecs/wm8983.c new file mode 100644 index 000000000..9f35801aa --- /dev/null +++ b/sound/soc/codecs/wm8983.c @@ -0,0 +1,1115 @@ +/* + * wm8983.c -- WM8983 ALSA SoC Audio driver + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8983.h" + +static const struct reg_default wm8983_defaults[] = { + { 0x01, 0x0000 }, /* R1 - Power management 1 */ + { 0x02, 0x0000 }, /* R2 - Power management 2 */ + { 0x03, 0x0000 }, /* R3 - Power management 3 */ + { 0x04, 0x0050 }, /* R4 - Audio Interface */ + { 0x05, 0x0000 }, /* R5 - Companding control */ + { 0x06, 0x0140 }, /* R6 - Clock Gen control */ + { 0x07, 0x0000 }, /* R7 - Additional control */ + { 0x08, 0x0000 }, /* R8 - GPIO Control */ + { 0x09, 0x0000 }, /* R9 - Jack Detect Control 1 */ + { 0x0A, 0x0000 }, /* R10 - DAC Control */ + { 0x0B, 0x00FF }, /* R11 - Left DAC digital Vol */ + { 0x0C, 0x00FF }, /* R12 - Right DAC digital vol */ + { 0x0D, 0x0000 }, /* R13 - Jack Detect Control 2 */ + { 0x0E, 0x0100 }, /* R14 - ADC Control */ + { 0x0F, 0x00FF }, /* R15 - Left ADC Digital Vol */ + { 0x10, 0x00FF }, /* R16 - Right ADC Digital Vol */ + { 0x12, 0x012C }, /* R18 - EQ1 - low shelf */ + { 0x13, 0x002C }, /* R19 - EQ2 - peak 1 */ + { 0x14, 0x002C }, /* R20 - EQ3 - peak 2 */ + { 0x15, 0x002C }, /* R21 - EQ4 - peak 3 */ + { 0x16, 0x002C }, /* R22 - EQ5 - high shelf */ + { 0x18, 0x0032 }, /* R24 - DAC Limiter 1 */ + { 0x19, 0x0000 }, /* R25 - DAC Limiter 2 */ + { 0x1B, 0x0000 }, /* R27 - Notch Filter 1 */ + { 0x1C, 0x0000 }, /* R28 - Notch Filter 2 */ + { 0x1D, 0x0000 }, /* R29 - Notch Filter 3 */ + { 0x1E, 0x0000 }, /* R30 - Notch Filter 4 */ + { 0x20, 0x0038 }, /* R32 - ALC control 1 */ + { 0x21, 0x000B }, /* R33 - ALC control 2 */ + { 0x22, 0x0032 }, /* R34 - ALC control 3 */ + { 0x23, 0x0000 }, /* R35 - Noise Gate */ + { 0x24, 0x0008 }, /* R36 - PLL N */ + { 0x25, 0x000C }, /* R37 - PLL K 1 */ + { 0x26, 0x0093 }, /* R38 - PLL K 2 */ + { 0x27, 0x00E9 }, /* R39 - PLL K 3 */ + { 0x29, 0x0000 }, /* R41 - 3D control */ + { 0x2A, 0x0000 }, /* R42 - OUT4 to ADC */ + { 0x2B, 0x0000 }, /* R43 - Beep control */ + { 0x2C, 0x0033 }, /* R44 - Input ctrl */ + { 0x2D, 0x0010 }, /* R45 - Left INP PGA gain ctrl */ + { 0x2E, 0x0010 }, /* R46 - Right INP PGA gain ctrl */ + { 0x2F, 0x0100 }, /* R47 - Left ADC BOOST ctrl */ + { 0x30, 0x0100 }, /* R48 - Right ADC BOOST ctrl */ + { 0x31, 0x0002 }, /* R49 - Output ctrl */ + { 0x32, 0x0001 }, /* R50 - Left mixer ctrl */ + { 0x33, 0x0001 }, /* R51 - Right mixer ctrl */ + { 0x34, 0x0039 }, /* R52 - LOUT1 (HP) volume ctrl */ + { 0x35, 0x0039 }, /* R53 - ROUT1 (HP) volume ctrl */ + { 0x36, 0x0039 }, /* R54 - LOUT2 (SPK) volume ctrl */ + { 0x37, 0x0039 }, /* R55 - ROUT2 (SPK) volume ctrl */ + { 0x38, 0x0001 }, /* R56 - OUT3 mixer ctrl */ + { 0x39, 0x0001 }, /* R57 - OUT4 (MONO) mix ctrl */ + { 0x3D, 0x0000 }, /* R61 - BIAS CTRL */ +}; + +/* vol/gain update regs */ +static const int vol_update_regs[] = { + WM8983_LEFT_DAC_DIGITAL_VOL, + WM8983_RIGHT_DAC_DIGITAL_VOL, + WM8983_LEFT_ADC_DIGITAL_VOL, + WM8983_RIGHT_ADC_DIGITAL_VOL, + WM8983_LOUT1_HP_VOLUME_CTRL, + WM8983_ROUT1_HP_VOLUME_CTRL, + WM8983_LOUT2_SPK_VOLUME_CTRL, + WM8983_ROUT2_SPK_VOLUME_CTRL, + WM8983_LEFT_INP_PGA_GAIN_CTRL, + WM8983_RIGHT_INP_PGA_GAIN_CTRL +}; + +struct wm8983_priv { + struct regmap *regmap; + u32 sysclk; + u32 bclk; +}; + +static const struct { + int div; + int ratio; +} fs_ratios[] = { + { 10, 128 }, + { 15, 192 }, + { 20, 256 }, + { 30, 384 }, + { 40, 512 }, + { 60, 768 }, + { 80, 1024 }, + { 120, 1536 } +}; + +static const int srates[] = { 48000, 32000, 24000, 16000, 12000, 8000 }; + +static const int bclk_divs[] = { + 1, 2, 4, 8, 16, 32 +}; + +static int eqmode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int eqmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(lim_thresh_tlv, -600, 100, 0); +static const DECLARE_TLV_DB_SCALE(lim_boost_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(alc_min_tlv, -1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(alc_max_tlv, -675, 600, 0); +static const DECLARE_TLV_DB_SCALE(alc_tar_tlv, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(pga_vol_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(boost_tlv, -1200, 300, 1); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(aux_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(pga_boost_tlv, 0, 2000, 0); + +static const char *alc_sel_text[] = { "Off", "Right", "Left", "Stereo" }; +static SOC_ENUM_SINGLE_DECL(alc_sel, WM8983_ALC_CONTROL_1, 7, alc_sel_text); + +static const char *alc_mode_text[] = { "ALC", "Limiter" }; +static SOC_ENUM_SINGLE_DECL(alc_mode, WM8983_ALC_CONTROL_3, 8, alc_mode_text); + +static const char *filter_mode_text[] = { "Audio", "Application" }; +static SOC_ENUM_SINGLE_DECL(filter_mode, WM8983_ADC_CONTROL, 7, + filter_mode_text); + +static const char *eq_bw_text[] = { "Narrow", "Wide" }; +static const char *eqmode_text[] = { "Capture", "Playback" }; +static SOC_ENUM_SINGLE_EXT_DECL(eqmode, eqmode_text); + +static const char *eq1_cutoff_text[] = { + "80Hz", "105Hz", "135Hz", "175Hz" +}; +static SOC_ENUM_SINGLE_DECL(eq1_cutoff, WM8983_EQ1_LOW_SHELF, 5, + eq1_cutoff_text); +static const char *eq2_cutoff_text[] = { + "230Hz", "300Hz", "385Hz", "500Hz" +}; +static SOC_ENUM_SINGLE_DECL(eq2_bw, WM8983_EQ2_PEAK_1, 8, eq_bw_text); +static SOC_ENUM_SINGLE_DECL(eq2_cutoff, WM8983_EQ2_PEAK_1, 5, eq2_cutoff_text); +static const char *eq3_cutoff_text[] = { + "650Hz", "850Hz", "1.1kHz", "1.4kHz" +}; +static SOC_ENUM_SINGLE_DECL(eq3_bw, WM8983_EQ3_PEAK_2, 8, eq_bw_text); +static SOC_ENUM_SINGLE_DECL(eq3_cutoff, WM8983_EQ3_PEAK_2, 5, eq3_cutoff_text); +static const char *eq4_cutoff_text[] = { + "1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" +}; +static SOC_ENUM_SINGLE_DECL(eq4_bw, WM8983_EQ4_PEAK_3, 8, eq_bw_text); +static SOC_ENUM_SINGLE_DECL(eq4_cutoff, WM8983_EQ4_PEAK_3, 5, eq4_cutoff_text); +static const char *eq5_cutoff_text[] = { + "5.3kHz", "6.9kHz", "9kHz", "11.7kHz" +}; +static SOC_ENUM_SINGLE_DECL(eq5_cutoff, WM8983_EQ5_HIGH_SHELF, 5, + eq5_cutoff_text); + +static const char *depth_3d_text[] = { + "Off", + "6.67%", + "13.3%", + "20%", + "26.7%", + "33.3%", + "40%", + "46.6%", + "53.3%", + "60%", + "66.7%", + "73.3%", + "80%", + "86.7%", + "93.3%", + "100%" +}; +static SOC_ENUM_SINGLE_DECL(depth_3d, WM8983_3D_CONTROL, 0, + depth_3d_text); + +static const struct snd_kcontrol_new wm8983_snd_controls[] = { + SOC_SINGLE("Digital Loopback Switch", WM8983_COMPANDING_CONTROL, + 0, 1, 0), + + SOC_ENUM("ALC Capture Function", alc_sel), + SOC_SINGLE_TLV("ALC Capture Max Volume", WM8983_ALC_CONTROL_1, + 3, 7, 0, alc_max_tlv), + SOC_SINGLE_TLV("ALC Capture Min Volume", WM8983_ALC_CONTROL_1, + 0, 7, 0, alc_min_tlv), + SOC_SINGLE_TLV("ALC Capture Target Volume", WM8983_ALC_CONTROL_2, + 0, 15, 0, alc_tar_tlv), + SOC_SINGLE("ALC Capture Attack", WM8983_ALC_CONTROL_3, 0, 10, 0), + SOC_SINGLE("ALC Capture Hold", WM8983_ALC_CONTROL_2, 4, 10, 0), + SOC_SINGLE("ALC Capture Decay", WM8983_ALC_CONTROL_3, 4, 10, 0), + SOC_ENUM("ALC Mode", alc_mode), + SOC_SINGLE("ALC Capture NG Switch", WM8983_NOISE_GATE, + 3, 1, 0), + SOC_SINGLE("ALC Capture NG Threshold", WM8983_NOISE_GATE, + 0, 7, 1), + + SOC_DOUBLE_R_TLV("Capture Volume", WM8983_LEFT_ADC_DIGITAL_VOL, + WM8983_RIGHT_ADC_DIGITAL_VOL, 0, 255, 0, adc_tlv), + SOC_DOUBLE_R("Capture PGA ZC Switch", WM8983_LEFT_INP_PGA_GAIN_CTRL, + WM8983_RIGHT_INP_PGA_GAIN_CTRL, 7, 1, 0), + SOC_DOUBLE_R_TLV("Capture PGA Volume", WM8983_LEFT_INP_PGA_GAIN_CTRL, + WM8983_RIGHT_INP_PGA_GAIN_CTRL, 0, 63, 0, pga_vol_tlv), + + SOC_DOUBLE_R_TLV("Capture PGA Boost Volume", + WM8983_LEFT_ADC_BOOST_CTRL, WM8983_RIGHT_ADC_BOOST_CTRL, + 8, 1, 0, pga_boost_tlv), + + SOC_DOUBLE("ADC Inversion Switch", WM8983_ADC_CONTROL, 0, 1, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8983_ADC_CONTROL, 8, 1, 0), + + SOC_DOUBLE_R_TLV("Playback Volume", WM8983_LEFT_DAC_DIGITAL_VOL, + WM8983_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), + + SOC_SINGLE("DAC Playback Limiter Switch", WM8983_DAC_LIMITER_1, 8, 1, 0), + SOC_SINGLE("DAC Playback Limiter Decay", WM8983_DAC_LIMITER_1, 4, 10, 0), + SOC_SINGLE("DAC Playback Limiter Attack", WM8983_DAC_LIMITER_1, 0, 11, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8983_DAC_LIMITER_2, + 4, 7, 1, lim_thresh_tlv), + SOC_SINGLE_TLV("DAC Playback Limiter Boost Volume", WM8983_DAC_LIMITER_2, + 0, 12, 0, lim_boost_tlv), + SOC_DOUBLE("DAC Inversion Switch", WM8983_DAC_CONTROL, 0, 1, 1, 0), + SOC_SINGLE("DAC Auto Mute Switch", WM8983_DAC_CONTROL, 2, 1, 0), + SOC_SINGLE("DAC 128x Oversampling Switch", WM8983_DAC_CONTROL, 3, 1, 0), + + SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8983_LOUT1_HP_VOLUME_CTRL, + WM8983_ROUT1_HP_VOLUME_CTRL, 0, 63, 0, out_tlv), + SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8983_LOUT1_HP_VOLUME_CTRL, + WM8983_ROUT1_HP_VOLUME_CTRL, 7, 1, 0), + SOC_DOUBLE_R("Headphone Switch", WM8983_LOUT1_HP_VOLUME_CTRL, + WM8983_ROUT1_HP_VOLUME_CTRL, 6, 1, 1), + + SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8983_LOUT2_SPK_VOLUME_CTRL, + WM8983_ROUT2_SPK_VOLUME_CTRL, 0, 63, 0, out_tlv), + SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8983_LOUT2_SPK_VOLUME_CTRL, + WM8983_ROUT2_SPK_VOLUME_CTRL, 7, 1, 0), + SOC_DOUBLE_R("Speaker Switch", WM8983_LOUT2_SPK_VOLUME_CTRL, + WM8983_ROUT2_SPK_VOLUME_CTRL, 6, 1, 1), + + SOC_SINGLE("OUT3 Switch", WM8983_OUT3_MIXER_CTRL, + 6, 1, 1), + + SOC_SINGLE("OUT4 Switch", WM8983_OUT4_MONO_MIX_CTRL, + 6, 1, 1), + + SOC_SINGLE("High Pass Filter Switch", WM8983_ADC_CONTROL, 8, 1, 0), + SOC_ENUM("High Pass Filter Mode", filter_mode), + SOC_SINGLE("High Pass Filter Cutoff", WM8983_ADC_CONTROL, 4, 7, 0), + + SOC_DOUBLE_R_TLV("Aux Bypass Volume", + WM8983_LEFT_MIXER_CTRL, WM8983_RIGHT_MIXER_CTRL, 6, 7, 0, + aux_tlv), + + SOC_DOUBLE_R_TLV("Input PGA Bypass Volume", + WM8983_LEFT_MIXER_CTRL, WM8983_RIGHT_MIXER_CTRL, 2, 7, 0, + bypass_tlv), + + SOC_ENUM_EXT("Equalizer Function", eqmode, eqmode_get, eqmode_put), + SOC_ENUM("EQ1 Cutoff", eq1_cutoff), + SOC_SINGLE_TLV("EQ1 Volume", WM8983_EQ1_LOW_SHELF, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ2 Bandwidth", eq2_bw), + SOC_ENUM("EQ2 Cutoff", eq2_cutoff), + SOC_SINGLE_TLV("EQ2 Volume", WM8983_EQ2_PEAK_1, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ3 Bandwidth", eq3_bw), + SOC_ENUM("EQ3 Cutoff", eq3_cutoff), + SOC_SINGLE_TLV("EQ3 Volume", WM8983_EQ3_PEAK_2, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ4 Bandwidth", eq4_bw), + SOC_ENUM("EQ4 Cutoff", eq4_cutoff), + SOC_SINGLE_TLV("EQ4 Volume", WM8983_EQ4_PEAK_3, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ5 Cutoff", eq5_cutoff), + SOC_SINGLE_TLV("EQ5 Volume", WM8983_EQ5_HIGH_SHELF, 0, 24, 1, eq_tlv), + + SOC_ENUM("3D Depth", depth_3d), +}; + +static const struct snd_kcontrol_new left_out_mixer[] = { + SOC_DAPM_SINGLE("Line Switch", WM8983_LEFT_MIXER_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Switch", WM8983_LEFT_MIXER_CTRL, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Switch", WM8983_LEFT_MIXER_CTRL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_out_mixer[] = { + SOC_DAPM_SINGLE("Line Switch", WM8983_RIGHT_MIXER_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Switch", WM8983_RIGHT_MIXER_CTRL, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Switch", WM8983_RIGHT_MIXER_CTRL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new left_input_mixer[] = { + SOC_DAPM_SINGLE("L2 Switch", WM8983_INPUT_CTRL, 2, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8983_INPUT_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8983_INPUT_CTRL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_input_mixer[] = { + SOC_DAPM_SINGLE("R2 Switch", WM8983_INPUT_CTRL, 6, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8983_INPUT_CTRL, 5, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8983_INPUT_CTRL, 4, 1, 0), +}; + +static const struct snd_kcontrol_new left_boost_mixer[] = { + SOC_DAPM_SINGLE_TLV("L2 Volume", WM8983_LEFT_ADC_BOOST_CTRL, + 4, 7, 0, boost_tlv), + SOC_DAPM_SINGLE_TLV("AUXL Volume", WM8983_LEFT_ADC_BOOST_CTRL, + 0, 7, 0, boost_tlv) +}; + +static const struct snd_kcontrol_new out3_mixer[] = { + SOC_DAPM_SINGLE("LMIX2OUT3 Switch", WM8983_OUT3_MIXER_CTRL, + 1, 1, 0), + SOC_DAPM_SINGLE("LDAC2OUT3 Switch", WM8983_OUT3_MIXER_CTRL, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new out4_mixer[] = { + SOC_DAPM_SINGLE("LMIX2OUT4 Switch", WM8983_OUT4_MONO_MIX_CTRL, + 4, 1, 0), + SOC_DAPM_SINGLE("RMIX2OUT4 Switch", WM8983_OUT4_MONO_MIX_CTRL, + 1, 1, 0), + SOC_DAPM_SINGLE("LDAC2OUT4 Switch", WM8983_OUT4_MONO_MIX_CTRL, + 3, 1, 0), + SOC_DAPM_SINGLE("RDAC2OUT4 Switch", WM8983_OUT4_MONO_MIX_CTRL, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_boost_mixer[] = { + SOC_DAPM_SINGLE_TLV("R2 Volume", WM8983_RIGHT_ADC_BOOST_CTRL, + 4, 7, 0, boost_tlv), + SOC_DAPM_SINGLE_TLV("AUXR Volume", WM8983_RIGHT_ADC_BOOST_CTRL, + 0, 7, 0, boost_tlv) +}; + +static const struct snd_soc_dapm_widget wm8983_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8983_POWER_MANAGEMENT_3, + 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8983_POWER_MANAGEMENT_3, + 1, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8983_POWER_MANAGEMENT_2, + 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8983_POWER_MANAGEMENT_2, + 1, 0), + + SND_SOC_DAPM_MIXER("Left Output Mixer", WM8983_POWER_MANAGEMENT_3, + 2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)), + SND_SOC_DAPM_MIXER("Right Output Mixer", WM8983_POWER_MANAGEMENT_3, + 3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)), + + SND_SOC_DAPM_MIXER("Left Input Mixer", WM8983_POWER_MANAGEMENT_2, + 2, 0, left_input_mixer, ARRAY_SIZE(left_input_mixer)), + SND_SOC_DAPM_MIXER("Right Input Mixer", WM8983_POWER_MANAGEMENT_2, + 3, 0, right_input_mixer, ARRAY_SIZE(right_input_mixer)), + + SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8983_POWER_MANAGEMENT_2, + 4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)), + SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8983_POWER_MANAGEMENT_2, + 5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)), + + SND_SOC_DAPM_MIXER("OUT3 Mixer", WM8983_POWER_MANAGEMENT_1, + 6, 0, out3_mixer, ARRAY_SIZE(out3_mixer)), + + SND_SOC_DAPM_MIXER("OUT4 Mixer", WM8983_POWER_MANAGEMENT_1, + 7, 0, out4_mixer, ARRAY_SIZE(out4_mixer)), + + SND_SOC_DAPM_PGA("Left Capture PGA", WM8983_LEFT_INP_PGA_GAIN_CTRL, + 6, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Capture PGA", WM8983_RIGHT_INP_PGA_GAIN_CTRL, + 6, 1, NULL, 0), + + SND_SOC_DAPM_PGA("Left Headphone Out", WM8983_POWER_MANAGEMENT_2, + 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Headphone Out", WM8983_POWER_MANAGEMENT_2, + 8, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Left Speaker Out", WM8983_POWER_MANAGEMENT_3, + 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Speaker Out", WM8983_POWER_MANAGEMENT_3, + 6, 0, NULL, 0), + + SND_SOC_DAPM_PGA("OUT3 Out", WM8983_POWER_MANAGEMENT_3, + 7, 0, NULL, 0), + + SND_SOC_DAPM_PGA("OUT4 Out", WM8983_POWER_MANAGEMENT_3, + 8, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Mic Bias", WM8983_POWER_MANAGEMENT_1, 4, 0, + NULL, 0), + + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_INPUT("LIP"), + SND_SOC_DAPM_INPUT("RIN"), + SND_SOC_DAPM_INPUT("RIP"), + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), + SND_SOC_DAPM_INPUT("L2"), + SND_SOC_DAPM_INPUT("R2"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("OUT4") +}; + +static const struct snd_soc_dapm_route wm8983_audio_map[] = { + { "OUT3 Mixer", "LMIX2OUT3 Switch", "Left Output Mixer" }, + { "OUT3 Mixer", "LDAC2OUT3 Switch", "Left DAC" }, + + { "OUT3 Out", NULL, "OUT3 Mixer" }, + { "OUT3", NULL, "OUT3 Out" }, + + { "OUT4 Mixer", "LMIX2OUT4 Switch", "Left Output Mixer" }, + { "OUT4 Mixer", "RMIX2OUT4 Switch", "Right Output Mixer" }, + { "OUT4 Mixer", "LDAC2OUT4 Switch", "Left DAC" }, + { "OUT4 Mixer", "RDAC2OUT4 Switch", "Right DAC" }, + + { "OUT4 Out", NULL, "OUT4 Mixer" }, + { "OUT4", NULL, "OUT4 Out" }, + + { "Right Output Mixer", "PCM Switch", "Right DAC" }, + { "Right Output Mixer", "Aux Switch", "AUXR" }, + { "Right Output Mixer", "Line Switch", "Right Boost Mixer" }, + + { "Left Output Mixer", "PCM Switch", "Left DAC" }, + { "Left Output Mixer", "Aux Switch", "AUXL" }, + { "Left Output Mixer", "Line Switch", "Left Boost Mixer" }, + + { "Right Headphone Out", NULL, "Right Output Mixer" }, + { "HPR", NULL, "Right Headphone Out" }, + + { "Left Headphone Out", NULL, "Left Output Mixer" }, + { "HPL", NULL, "Left Headphone Out" }, + + { "Right Speaker Out", NULL, "Right Output Mixer" }, + { "SPKR", NULL, "Right Speaker Out" }, + + { "Left Speaker Out", NULL, "Left Output Mixer" }, + { "SPKL", NULL, "Left Speaker Out" }, + + { "Right ADC", NULL, "Right Boost Mixer" }, + + { "Right Boost Mixer", "AUXR Volume", "AUXR" }, + { "Right Boost Mixer", NULL, "Right Capture PGA" }, + { "Right Boost Mixer", "R2 Volume", "R2" }, + + { "Left ADC", NULL, "Left Boost Mixer" }, + + { "Left Boost Mixer", "AUXL Volume", "AUXL" }, + { "Left Boost Mixer", NULL, "Left Capture PGA" }, + { "Left Boost Mixer", "L2 Volume", "L2" }, + + { "Right Capture PGA", NULL, "Right Input Mixer" }, + { "Left Capture PGA", NULL, "Left Input Mixer" }, + + { "Right Input Mixer", "R2 Switch", "R2" }, + { "Right Input Mixer", "MicN Switch", "RIN" }, + { "Right Input Mixer", "MicP Switch", "RIP" }, + + { "Left Input Mixer", "L2 Switch", "L2" }, + { "Left Input Mixer", "MicN Switch", "LIN" }, + { "Left Input Mixer", "MicP Switch", "LIP" }, +}; + +static int eqmode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg; + + reg = snd_soc_component_read32(component, WM8983_EQ1_LOW_SHELF); + if (reg & WM8983_EQ3DMODE) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + + return 0; +} + +static int eqmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int regpwr2, regpwr3; + unsigned int reg_eq; + + if (ucontrol->value.enumerated.item[0] != 0 + && ucontrol->value.enumerated.item[0] != 1) + return -EINVAL; + + reg_eq = snd_soc_component_read32(component, WM8983_EQ1_LOW_SHELF); + switch ((reg_eq & WM8983_EQ3DMODE) >> WM8983_EQ3DMODE_SHIFT) { + case 0: + if (!ucontrol->value.enumerated.item[0]) + return 0; + break; + case 1: + if (ucontrol->value.enumerated.item[0]) + return 0; + break; + } + + regpwr2 = snd_soc_component_read32(component, WM8983_POWER_MANAGEMENT_2); + regpwr3 = snd_soc_component_read32(component, WM8983_POWER_MANAGEMENT_3); + /* disable the DACs and ADCs */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_2, + WM8983_ADCENR_MASK | WM8983_ADCENL_MASK, 0); + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_3, + WM8983_DACENR_MASK | WM8983_DACENL_MASK, 0); + /* set the desired eqmode */ + snd_soc_component_update_bits(component, WM8983_EQ1_LOW_SHELF, + WM8983_EQ3DMODE_MASK, + ucontrol->value.enumerated.item[0] + << WM8983_EQ3DMODE_SHIFT); + /* restore DAC/ADC configuration */ + snd_soc_component_write(component, WM8983_POWER_MANAGEMENT_2, regpwr2); + snd_soc_component_write(component, WM8983_POWER_MANAGEMENT_3, regpwr3); + return 0; +} + +static bool wm8983_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8983_SOFTWARE_RESET ... WM8983_RIGHT_ADC_DIGITAL_VOL: + case WM8983_EQ1_LOW_SHELF ... WM8983_DAC_LIMITER_2: + case WM8983_NOTCH_FILTER_1 ... WM8983_NOTCH_FILTER_4: + case WM8983_ALC_CONTROL_1 ... WM8983_PLL_K_3: + case WM8983_3D_CONTROL ... WM8983_OUT4_MONO_MIX_CTRL: + case WM8983_BIAS_CTRL: + return true; + default: + return false; + } +} + +static int wm8983_dac_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + return snd_soc_component_update_bits(component, WM8983_DAC_CONTROL, + WM8983_SOFTMUTE_MASK, + !!mute << WM8983_SOFTMUTE_SHIFT); +} + +static int wm8983_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + u16 format, master, bcp, lrp; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + format = 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = 0x1; + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + format = 0x3; + break; + default: + dev_err(dai->dev, "Unknown dai format\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8983_AUDIO_INTERFACE, + WM8983_FMT_MASK, format << WM8983_FMT_SHIFT); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + default: + dev_err(dai->dev, "Unknown master/slave configuration\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8983_CLOCK_GEN_CONTROL, + WM8983_MS_MASK, master << WM8983_MS_SHIFT); + + /* FIXME: We don't currently support DSP A/B modes */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + dev_err(dai->dev, "DSP A/B modes are not supported\n"); + return -EINVAL; + default: + break; + } + + bcp = lrp = 0; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + bcp = lrp = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + bcp = 1; + break; + case SND_SOC_DAIFMT_NB_IF: + lrp = 1; + break; + default: + dev_err(dai->dev, "Unknown polarity configuration\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8983_AUDIO_INTERFACE, + WM8983_LRCP_MASK, lrp << WM8983_LRCP_SHIFT); + snd_soc_component_update_bits(component, WM8983_AUDIO_INTERFACE, + WM8983_BCP_MASK, bcp << WM8983_BCP_SHIFT); + return 0; +} + +static int wm8983_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int i; + struct snd_soc_component *component = dai->component; + struct wm8983_priv *wm8983 = snd_soc_component_get_drvdata(component); + u16 blen, srate_idx; + u32 tmp; + int srate_best; + int ret; + + ret = snd_soc_params_to_bclk(params); + if (ret < 0) { + dev_err(component->dev, "Failed to convert params to bclk: %d\n", ret); + return ret; + } + + wm8983->bclk = ret; + + switch (params_width(params)) { + case 16: + blen = 0x0; + break; + case 20: + blen = 0x1; + break; + case 24: + blen = 0x2; + break; + case 32: + blen = 0x3; + break; + default: + dev_err(dai->dev, "Unsupported word length %u\n", + params_width(params)); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8983_AUDIO_INTERFACE, + WM8983_WL_MASK, blen << WM8983_WL_SHIFT); + + /* + * match to the nearest possible sample rate and rely + * on the array index to configure the SR register + */ + srate_idx = 0; + srate_best = abs(srates[0] - params_rate(params)); + for (i = 1; i < ARRAY_SIZE(srates); ++i) { + if (abs(srates[i] - params_rate(params)) >= srate_best) + continue; + srate_idx = i; + srate_best = abs(srates[i] - params_rate(params)); + } + + dev_dbg(dai->dev, "Selected SRATE = %d\n", srates[srate_idx]); + snd_soc_component_update_bits(component, WM8983_ADDITIONAL_CONTROL, + WM8983_SR_MASK, srate_idx << WM8983_SR_SHIFT); + + dev_dbg(dai->dev, "Target BCLK = %uHz\n", wm8983->bclk); + dev_dbg(dai->dev, "SYSCLK = %uHz\n", wm8983->sysclk); + + for (i = 0; i < ARRAY_SIZE(fs_ratios); ++i) { + if (wm8983->sysclk / params_rate(params) + == fs_ratios[i].ratio) + break; + } + + if (i == ARRAY_SIZE(fs_ratios)) { + dev_err(dai->dev, "Unable to configure MCLK ratio %u/%u\n", + wm8983->sysclk, params_rate(params)); + return -EINVAL; + } + + dev_dbg(dai->dev, "MCLK ratio = %dfs\n", fs_ratios[i].ratio); + snd_soc_component_update_bits(component, WM8983_CLOCK_GEN_CONTROL, + WM8983_MCLKDIV_MASK, i << WM8983_MCLKDIV_SHIFT); + + /* select the appropriate bclk divider */ + tmp = (wm8983->sysclk / fs_ratios[i].div) * 10; + for (i = 0; i < ARRAY_SIZE(bclk_divs); ++i) { + if (wm8983->bclk == tmp / bclk_divs[i]) + break; + } + + if (i == ARRAY_SIZE(bclk_divs)) { + dev_err(dai->dev, "No matching BCLK divider found\n"); + return -EINVAL; + } + + dev_dbg(dai->dev, "BCLK div = %d\n", i); + snd_soc_component_update_bits(component, WM8983_CLOCK_GEN_CONTROL, + WM8983_BCLKDIV_MASK, i << WM8983_BCLKDIV_SHIFT); + + return 0; +} + +struct pll_div { + u32 div2:1; + u32 n:4; + u32 k:24; +}; + +#define FIXED_PLL_SIZE ((1ULL << 24) * 10) +static int pll_factors(struct pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned long int K, Ndiv, Nmod; + + pll_div->div2 = 0; + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } + + if (Ndiv < 6 || Ndiv > 12) { + printk(KERN_ERR "%s: WM8983 N value is not within" + " the recommended range: %lu\n", __func__, Ndiv); + return -EINVAL; + } + pll_div->n = Ndiv; + + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (u64)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xffffffff; + if ((K % 10) >= 5) + K += 5; + K /= 10; + pll_div->k = K; + return 0; +} + +static int wm8983_set_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + int ret; + struct snd_soc_component *component; + struct pll_div pll_div; + + component = dai->component; + if (!freq_in || !freq_out) { + /* disable the PLL */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_PLLEN_MASK, 0); + return 0; + } else { + ret = pll_factors(&pll_div, freq_out * 4 * 2, freq_in); + if (ret) + return ret; + + /* disable the PLL before re-programming it */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_PLLEN_MASK, 0); + + /* set PLLN and PRESCALE */ + snd_soc_component_write(component, WM8983_PLL_N, + (pll_div.div2 << WM8983_PLL_PRESCALE_SHIFT) + | pll_div.n); + /* set PLLK */ + snd_soc_component_write(component, WM8983_PLL_K_3, pll_div.k & 0x1ff); + snd_soc_component_write(component, WM8983_PLL_K_2, (pll_div.k >> 9) & 0x1ff); + snd_soc_component_write(component, WM8983_PLL_K_1, (pll_div.k >> 18)); + /* enable the PLL */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_PLLEN_MASK, WM8983_PLLEN); + } + + return 0; +} + +static int wm8983_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8983_priv *wm8983 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8983_CLKSRC_MCLK: + snd_soc_component_update_bits(component, WM8983_CLOCK_GEN_CONTROL, + WM8983_CLKSEL_MASK, 0); + break; + case WM8983_CLKSRC_PLL: + snd_soc_component_update_bits(component, WM8983_CLOCK_GEN_CONTROL, + WM8983_CLKSEL_MASK, WM8983_CLKSEL); + break; + default: + dev_err(dai->dev, "Unknown clock source: %d\n", clk_id); + return -EINVAL; + } + + wm8983->sysclk = freq; + return 0; +} + +static int wm8983_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8983_priv *wm8983 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + /* VMID at 100k */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_VMIDSEL_MASK, + 1 << WM8983_VMIDSEL_SHIFT); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regcache_sync(wm8983->regmap); + if (ret < 0) { + dev_err(component->dev, "Failed to sync cache: %d\n", ret); + return ret; + } + /* enable anti-pop features */ + snd_soc_component_update_bits(component, WM8983_OUT4_TO_ADC, + WM8983_POBCTRL_MASK | WM8983_DELEN_MASK, + WM8983_POBCTRL | WM8983_DELEN); + /* enable thermal shutdown */ + snd_soc_component_update_bits(component, WM8983_OUTPUT_CTRL, + WM8983_TSDEN_MASK, WM8983_TSDEN); + /* enable BIASEN */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_BIASEN_MASK, WM8983_BIASEN); + /* VMID at 100k */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_VMIDSEL_MASK, + 1 << WM8983_VMIDSEL_SHIFT); + msleep(250); + /* disable anti-pop features */ + snd_soc_component_update_bits(component, WM8983_OUT4_TO_ADC, + WM8983_POBCTRL_MASK | + WM8983_DELEN_MASK, 0); + } + + /* VMID at 500k */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_VMIDSEL_MASK, + 2 << WM8983_VMIDSEL_SHIFT); + break; + case SND_SOC_BIAS_OFF: + /* disable thermal shutdown */ + snd_soc_component_update_bits(component, WM8983_OUTPUT_CTRL, + WM8983_TSDEN_MASK, 0); + /* disable VMIDSEL and BIASEN */ + snd_soc_component_update_bits(component, WM8983_POWER_MANAGEMENT_1, + WM8983_VMIDSEL_MASK | WM8983_BIASEN_MASK, + 0); + /* wait for VMID to discharge */ + msleep(100); + snd_soc_component_write(component, WM8983_POWER_MANAGEMENT_1, 0); + snd_soc_component_write(component, WM8983_POWER_MANAGEMENT_2, 0); + snd_soc_component_write(component, WM8983_POWER_MANAGEMENT_3, 0); + break; + } + + return 0; +} + +static int wm8983_probe(struct snd_soc_component *component) +{ + int ret; + int i; + + ret = snd_soc_component_write(component, WM8983_SOFTWARE_RESET, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + /* set the vol/gain update bits */ + for (i = 0; i < ARRAY_SIZE(vol_update_regs); ++i) + snd_soc_component_update_bits(component, vol_update_regs[i], + 0x100, 0x100); + + /* mute all outputs and set PGAs to minimum gain */ + for (i = WM8983_LOUT1_HP_VOLUME_CTRL; + i <= WM8983_OUT4_MONO_MIX_CTRL; ++i) + snd_soc_component_update_bits(component, i, 0x40, 0x40); + + /* enable soft mute */ + snd_soc_component_update_bits(component, WM8983_DAC_CONTROL, + WM8983_SOFTMUTE_MASK, + WM8983_SOFTMUTE); + + /* enable BIASCUT */ + snd_soc_component_update_bits(component, WM8983_BIAS_CTRL, + WM8983_BIASCUT, WM8983_BIASCUT); + return 0; +} + +static const struct snd_soc_dai_ops wm8983_dai_ops = { + .digital_mute = wm8983_dac_mute, + .hw_params = wm8983_hw_params, + .set_fmt = wm8983_set_fmt, + .set_sysclk = wm8983_set_sysclk, + .set_pll = wm8983_set_pll +}; + +#define WM8983_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm8983_dai = { + .name = "wm8983-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8983_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8983_FORMATS, + }, + .ops = &wm8983_dai_ops, + .symmetric_rates = 1 +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8983 = { + .probe = wm8983_probe, + .set_bias_level = wm8983_set_bias_level, + .controls = wm8983_snd_controls, + .num_controls = ARRAY_SIZE(wm8983_snd_controls), + .dapm_widgets = wm8983_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8983_dapm_widgets), + .dapm_routes = wm8983_audio_map, + .num_dapm_routes = ARRAY_SIZE(wm8983_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8983_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .reg_defaults = wm8983_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8983_defaults), + .cache_type = REGCACHE_RBTREE, + .max_register = WM8983_MAX_REGISTER, + + .writeable_reg = wm8983_writeable, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8983_spi_probe(struct spi_device *spi) +{ + struct wm8983_priv *wm8983; + int ret; + + wm8983 = devm_kzalloc(&spi->dev, sizeof *wm8983, GFP_KERNEL); + if (!wm8983) + return -ENOMEM; + + wm8983->regmap = devm_regmap_init_spi(spi, &wm8983_regmap); + if (IS_ERR(wm8983->regmap)) { + ret = PTR_ERR(wm8983->regmap); + dev_err(&spi->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + spi_set_drvdata(spi, wm8983); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8983, &wm8983_dai, 1); + return ret; +} + +static struct spi_driver wm8983_spi_driver = { + .driver = { + .name = "wm8983", + }, + .probe = wm8983_spi_probe, +}; +#endif + +#if IS_ENABLED(CONFIG_I2C) +static int wm8983_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8983_priv *wm8983; + int ret; + + wm8983 = devm_kzalloc(&i2c->dev, sizeof *wm8983, GFP_KERNEL); + if (!wm8983) + return -ENOMEM; + + wm8983->regmap = devm_regmap_init_i2c(i2c, &wm8983_regmap); + if (IS_ERR(wm8983->regmap)) { + ret = PTR_ERR(wm8983->regmap); + dev_err(&i2c->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8983); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8983, &wm8983_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8983_i2c_id[] = { + { "wm8983", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8983_i2c_id); + +static struct i2c_driver wm8983_i2c_driver = { + .driver = { + .name = "wm8983", + }, + .probe = wm8983_i2c_probe, + .id_table = wm8983_i2c_id +}; +#endif + +static int __init wm8983_modinit(void) +{ + int ret = 0; + +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8983_i2c_driver); + if (ret) { + printk(KERN_ERR "Failed to register wm8983 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8983_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8983 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8983_modinit); + +static void __exit wm8983_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8983_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8983_spi_driver); +#endif +} +module_exit(wm8983_exit); + +MODULE_DESCRIPTION("ASoC WM8983 driver"); +MODULE_AUTHOR("Dimitris Papastamos "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8983.h b/sound/soc/codecs/wm8983.h new file mode 100644 index 000000000..71ee619c2 --- /dev/null +++ b/sound/soc/codecs/wm8983.h @@ -0,0 +1,1029 @@ +/* + * wm8983.h -- WM8983 ALSA SoC Audio driver + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8983_H +#define _WM8983_H + +/* + * Register values. + */ +#define WM8983_SOFTWARE_RESET 0x00 +#define WM8983_POWER_MANAGEMENT_1 0x01 +#define WM8983_POWER_MANAGEMENT_2 0x02 +#define WM8983_POWER_MANAGEMENT_3 0x03 +#define WM8983_AUDIO_INTERFACE 0x04 +#define WM8983_COMPANDING_CONTROL 0x05 +#define WM8983_CLOCK_GEN_CONTROL 0x06 +#define WM8983_ADDITIONAL_CONTROL 0x07 +#define WM8983_GPIO_CONTROL 0x08 +#define WM8983_JACK_DETECT_CONTROL_1 0x09 +#define WM8983_DAC_CONTROL 0x0A +#define WM8983_LEFT_DAC_DIGITAL_VOL 0x0B +#define WM8983_RIGHT_DAC_DIGITAL_VOL 0x0C +#define WM8983_JACK_DETECT_CONTROL_2 0x0D +#define WM8983_ADC_CONTROL 0x0E +#define WM8983_LEFT_ADC_DIGITAL_VOL 0x0F +#define WM8983_RIGHT_ADC_DIGITAL_VOL 0x10 +#define WM8983_EQ1_LOW_SHELF 0x12 +#define WM8983_EQ2_PEAK_1 0x13 +#define WM8983_EQ3_PEAK_2 0x14 +#define WM8983_EQ4_PEAK_3 0x15 +#define WM8983_EQ5_HIGH_SHELF 0x16 +#define WM8983_DAC_LIMITER_1 0x18 +#define WM8983_DAC_LIMITER_2 0x19 +#define WM8983_NOTCH_FILTER_1 0x1B +#define WM8983_NOTCH_FILTER_2 0x1C +#define WM8983_NOTCH_FILTER_3 0x1D +#define WM8983_NOTCH_FILTER_4 0x1E +#define WM8983_ALC_CONTROL_1 0x20 +#define WM8983_ALC_CONTROL_2 0x21 +#define WM8983_ALC_CONTROL_3 0x22 +#define WM8983_NOISE_GATE 0x23 +#define WM8983_PLL_N 0x24 +#define WM8983_PLL_K_1 0x25 +#define WM8983_PLL_K_2 0x26 +#define WM8983_PLL_K_3 0x27 +#define WM8983_3D_CONTROL 0x29 +#define WM8983_OUT4_TO_ADC 0x2A +#define WM8983_BEEP_CONTROL 0x2B +#define WM8983_INPUT_CTRL 0x2C +#define WM8983_LEFT_INP_PGA_GAIN_CTRL 0x2D +#define WM8983_RIGHT_INP_PGA_GAIN_CTRL 0x2E +#define WM8983_LEFT_ADC_BOOST_CTRL 0x2F +#define WM8983_RIGHT_ADC_BOOST_CTRL 0x30 +#define WM8983_OUTPUT_CTRL 0x31 +#define WM8983_LEFT_MIXER_CTRL 0x32 +#define WM8983_RIGHT_MIXER_CTRL 0x33 +#define WM8983_LOUT1_HP_VOLUME_CTRL 0x34 +#define WM8983_ROUT1_HP_VOLUME_CTRL 0x35 +#define WM8983_LOUT2_SPK_VOLUME_CTRL 0x36 +#define WM8983_ROUT2_SPK_VOLUME_CTRL 0x37 +#define WM8983_OUT3_MIXER_CTRL 0x38 +#define WM8983_OUT4_MONO_MIX_CTRL 0x39 +#define WM8983_BIAS_CTRL 0x3D + +#define WM8983_REGISTER_COUNT 59 +#define WM8983_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM8983_SOFTWARE_RESET_MASK 0x01FF /* SOFTWARE_RESET - [8:0] */ +#define WM8983_SOFTWARE_RESET_SHIFT 0 /* SOFTWARE_RESET - [8:0] */ +#define WM8983_SOFTWARE_RESET_WIDTH 9 /* SOFTWARE_RESET - [8:0] */ + +/* + * R1 (0x01) - Power management 1 + */ +#define WM8983_BUFDCOPEN 0x0100 /* BUFDCOPEN */ +#define WM8983_BUFDCOPEN_MASK 0x0100 /* BUFDCOPEN */ +#define WM8983_BUFDCOPEN_SHIFT 8 /* BUFDCOPEN */ +#define WM8983_BUFDCOPEN_WIDTH 1 /* BUFDCOPEN */ +#define WM8983_OUT4MIXEN 0x0080 /* OUT4MIXEN */ +#define WM8983_OUT4MIXEN_MASK 0x0080 /* OUT4MIXEN */ +#define WM8983_OUT4MIXEN_SHIFT 7 /* OUT4MIXEN */ +#define WM8983_OUT4MIXEN_WIDTH 1 /* OUT4MIXEN */ +#define WM8983_OUT3MIXEN 0x0040 /* OUT3MIXEN */ +#define WM8983_OUT3MIXEN_MASK 0x0040 /* OUT3MIXEN */ +#define WM8983_OUT3MIXEN_SHIFT 6 /* OUT3MIXEN */ +#define WM8983_OUT3MIXEN_WIDTH 1 /* OUT3MIXEN */ +#define WM8983_PLLEN 0x0020 /* PLLEN */ +#define WM8983_PLLEN_MASK 0x0020 /* PLLEN */ +#define WM8983_PLLEN_SHIFT 5 /* PLLEN */ +#define WM8983_PLLEN_WIDTH 1 /* PLLEN */ +#define WM8983_MICBEN 0x0010 /* MICBEN */ +#define WM8983_MICBEN_MASK 0x0010 /* MICBEN */ +#define WM8983_MICBEN_SHIFT 4 /* MICBEN */ +#define WM8983_MICBEN_WIDTH 1 /* MICBEN */ +#define WM8983_BIASEN 0x0008 /* BIASEN */ +#define WM8983_BIASEN_MASK 0x0008 /* BIASEN */ +#define WM8983_BIASEN_SHIFT 3 /* BIASEN */ +#define WM8983_BIASEN_WIDTH 1 /* BIASEN */ +#define WM8983_BUFIOEN 0x0004 /* BUFIOEN */ +#define WM8983_BUFIOEN_MASK 0x0004 /* BUFIOEN */ +#define WM8983_BUFIOEN_SHIFT 2 /* BUFIOEN */ +#define WM8983_BUFIOEN_WIDTH 1 /* BUFIOEN */ +#define WM8983_VMIDSEL_MASK 0x0003 /* VMIDSEL - [1:0] */ +#define WM8983_VMIDSEL_SHIFT 0 /* VMIDSEL - [1:0] */ +#define WM8983_VMIDSEL_WIDTH 2 /* VMIDSEL - [1:0] */ + +/* + * R2 (0x02) - Power management 2 + */ +#define WM8983_ROUT1EN 0x0100 /* ROUT1EN */ +#define WM8983_ROUT1EN_MASK 0x0100 /* ROUT1EN */ +#define WM8983_ROUT1EN_SHIFT 8 /* ROUT1EN */ +#define WM8983_ROUT1EN_WIDTH 1 /* ROUT1EN */ +#define WM8983_LOUT1EN 0x0080 /* LOUT1EN */ +#define WM8983_LOUT1EN_MASK 0x0080 /* LOUT1EN */ +#define WM8983_LOUT1EN_SHIFT 7 /* LOUT1EN */ +#define WM8983_LOUT1EN_WIDTH 1 /* LOUT1EN */ +#define WM8983_SLEEP 0x0040 /* SLEEP */ +#define WM8983_SLEEP_MASK 0x0040 /* SLEEP */ +#define WM8983_SLEEP_SHIFT 6 /* SLEEP */ +#define WM8983_SLEEP_WIDTH 1 /* SLEEP */ +#define WM8983_BOOSTENR 0x0020 /* BOOSTENR */ +#define WM8983_BOOSTENR_MASK 0x0020 /* BOOSTENR */ +#define WM8983_BOOSTENR_SHIFT 5 /* BOOSTENR */ +#define WM8983_BOOSTENR_WIDTH 1 /* BOOSTENR */ +#define WM8983_BOOSTENL 0x0010 /* BOOSTENL */ +#define WM8983_BOOSTENL_MASK 0x0010 /* BOOSTENL */ +#define WM8983_BOOSTENL_SHIFT 4 /* BOOSTENL */ +#define WM8983_BOOSTENL_WIDTH 1 /* BOOSTENL */ +#define WM8983_INPGAENR 0x0008 /* INPGAENR */ +#define WM8983_INPGAENR_MASK 0x0008 /* INPGAENR */ +#define WM8983_INPGAENR_SHIFT 3 /* INPGAENR */ +#define WM8983_INPGAENR_WIDTH 1 /* INPGAENR */ +#define WM8983_INPPGAENL 0x0004 /* INPPGAENL */ +#define WM8983_INPPGAENL_MASK 0x0004 /* INPPGAENL */ +#define WM8983_INPPGAENL_SHIFT 2 /* INPPGAENL */ +#define WM8983_INPPGAENL_WIDTH 1 /* INPPGAENL */ +#define WM8983_ADCENR 0x0002 /* ADCENR */ +#define WM8983_ADCENR_MASK 0x0002 /* ADCENR */ +#define WM8983_ADCENR_SHIFT 1 /* ADCENR */ +#define WM8983_ADCENR_WIDTH 1 /* ADCENR */ +#define WM8983_ADCENL 0x0001 /* ADCENL */ +#define WM8983_ADCENL_MASK 0x0001 /* ADCENL */ +#define WM8983_ADCENL_SHIFT 0 /* ADCENL */ +#define WM8983_ADCENL_WIDTH 1 /* ADCENL */ + +/* + * R3 (0x03) - Power management 3 + */ +#define WM8983_OUT4EN 0x0100 /* OUT4EN */ +#define WM8983_OUT4EN_MASK 0x0100 /* OUT4EN */ +#define WM8983_OUT4EN_SHIFT 8 /* OUT4EN */ +#define WM8983_OUT4EN_WIDTH 1 /* OUT4EN */ +#define WM8983_OUT3EN 0x0080 /* OUT3EN */ +#define WM8983_OUT3EN_MASK 0x0080 /* OUT3EN */ +#define WM8983_OUT3EN_SHIFT 7 /* OUT3EN */ +#define WM8983_OUT3EN_WIDTH 1 /* OUT3EN */ +#define WM8983_LOUT2EN 0x0040 /* LOUT2EN */ +#define WM8983_LOUT2EN_MASK 0x0040 /* LOUT2EN */ +#define WM8983_LOUT2EN_SHIFT 6 /* LOUT2EN */ +#define WM8983_LOUT2EN_WIDTH 1 /* LOUT2EN */ +#define WM8983_ROUT2EN 0x0020 /* ROUT2EN */ +#define WM8983_ROUT2EN_MASK 0x0020 /* ROUT2EN */ +#define WM8983_ROUT2EN_SHIFT 5 /* ROUT2EN */ +#define WM8983_ROUT2EN_WIDTH 1 /* ROUT2EN */ +#define WM8983_RMIXEN 0x0008 /* RMIXEN */ +#define WM8983_RMIXEN_MASK 0x0008 /* RMIXEN */ +#define WM8983_RMIXEN_SHIFT 3 /* RMIXEN */ +#define WM8983_RMIXEN_WIDTH 1 /* RMIXEN */ +#define WM8983_LMIXEN 0x0004 /* LMIXEN */ +#define WM8983_LMIXEN_MASK 0x0004 /* LMIXEN */ +#define WM8983_LMIXEN_SHIFT 2 /* LMIXEN */ +#define WM8983_LMIXEN_WIDTH 1 /* LMIXEN */ +#define WM8983_DACENR 0x0002 /* DACENR */ +#define WM8983_DACENR_MASK 0x0002 /* DACENR */ +#define WM8983_DACENR_SHIFT 1 /* DACENR */ +#define WM8983_DACENR_WIDTH 1 /* DACENR */ +#define WM8983_DACENL 0x0001 /* DACENL */ +#define WM8983_DACENL_MASK 0x0001 /* DACENL */ +#define WM8983_DACENL_SHIFT 0 /* DACENL */ +#define WM8983_DACENL_WIDTH 1 /* DACENL */ + +/* + * R4 (0x04) - Audio Interface + */ +#define WM8983_BCP 0x0100 /* BCP */ +#define WM8983_BCP_MASK 0x0100 /* BCP */ +#define WM8983_BCP_SHIFT 8 /* BCP */ +#define WM8983_BCP_WIDTH 1 /* BCP */ +#define WM8983_LRCP 0x0080 /* LRCP */ +#define WM8983_LRCP_MASK 0x0080 /* LRCP */ +#define WM8983_LRCP_SHIFT 7 /* LRCP */ +#define WM8983_LRCP_WIDTH 1 /* LRCP */ +#define WM8983_WL_MASK 0x0060 /* WL - [6:5] */ +#define WM8983_WL_SHIFT 5 /* WL - [6:5] */ +#define WM8983_WL_WIDTH 2 /* WL - [6:5] */ +#define WM8983_FMT_MASK 0x0018 /* FMT - [4:3] */ +#define WM8983_FMT_SHIFT 3 /* FMT - [4:3] */ +#define WM8983_FMT_WIDTH 2 /* FMT - [4:3] */ +#define WM8983_DLRSWAP 0x0004 /* DLRSWAP */ +#define WM8983_DLRSWAP_MASK 0x0004 /* DLRSWAP */ +#define WM8983_DLRSWAP_SHIFT 2 /* DLRSWAP */ +#define WM8983_DLRSWAP_WIDTH 1 /* DLRSWAP */ +#define WM8983_ALRSWAP 0x0002 /* ALRSWAP */ +#define WM8983_ALRSWAP_MASK 0x0002 /* ALRSWAP */ +#define WM8983_ALRSWAP_SHIFT 1 /* ALRSWAP */ +#define WM8983_ALRSWAP_WIDTH 1 /* ALRSWAP */ +#define WM8983_MONO 0x0001 /* MONO */ +#define WM8983_MONO_MASK 0x0001 /* MONO */ +#define WM8983_MONO_SHIFT 0 /* MONO */ +#define WM8983_MONO_WIDTH 1 /* MONO */ + +/* + * R5 (0x05) - Companding control + */ +#define WM8983_WL8 0x0020 /* WL8 */ +#define WM8983_WL8_MASK 0x0020 /* WL8 */ +#define WM8983_WL8_SHIFT 5 /* WL8 */ +#define WM8983_WL8_WIDTH 1 /* WL8 */ +#define WM8983_DAC_COMP_MASK 0x0018 /* DAC_COMP - [4:3] */ +#define WM8983_DAC_COMP_SHIFT 3 /* DAC_COMP - [4:3] */ +#define WM8983_DAC_COMP_WIDTH 2 /* DAC_COMP - [4:3] */ +#define WM8983_ADC_COMP_MASK 0x0006 /* ADC_COMP - [2:1] */ +#define WM8983_ADC_COMP_SHIFT 1 /* ADC_COMP - [2:1] */ +#define WM8983_ADC_COMP_WIDTH 2 /* ADC_COMP - [2:1] */ +#define WM8983_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8983_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8983_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8983_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R6 (0x06) - Clock Gen control + */ +#define WM8983_CLKSEL 0x0100 /* CLKSEL */ +#define WM8983_CLKSEL_MASK 0x0100 /* CLKSEL */ +#define WM8983_CLKSEL_SHIFT 8 /* CLKSEL */ +#define WM8983_CLKSEL_WIDTH 1 /* CLKSEL */ +#define WM8983_MCLKDIV_MASK 0x00E0 /* MCLKDIV - [7:5] */ +#define WM8983_MCLKDIV_SHIFT 5 /* MCLKDIV - [7:5] */ +#define WM8983_MCLKDIV_WIDTH 3 /* MCLKDIV - [7:5] */ +#define WM8983_BCLKDIV_MASK 0x001C /* BCLKDIV - [4:2] */ +#define WM8983_BCLKDIV_SHIFT 2 /* BCLKDIV - [4:2] */ +#define WM8983_BCLKDIV_WIDTH 3 /* BCLKDIV - [4:2] */ +#define WM8983_MS 0x0001 /* MS */ +#define WM8983_MS_MASK 0x0001 /* MS */ +#define WM8983_MS_SHIFT 0 /* MS */ +#define WM8983_MS_WIDTH 1 /* MS */ + +/* + * R7 (0x07) - Additional control + */ +#define WM8983_SR_MASK 0x000E /* SR - [3:1] */ +#define WM8983_SR_SHIFT 1 /* SR - [3:1] */ +#define WM8983_SR_WIDTH 3 /* SR - [3:1] */ +#define WM8983_SLOWCLKEN 0x0001 /* SLOWCLKEN */ +#define WM8983_SLOWCLKEN_MASK 0x0001 /* SLOWCLKEN */ +#define WM8983_SLOWCLKEN_SHIFT 0 /* SLOWCLKEN */ +#define WM8983_SLOWCLKEN_WIDTH 1 /* SLOWCLKEN */ + +/* + * R8 (0x08) - GPIO Control + */ +#define WM8983_OPCLKDIV_MASK 0x0030 /* OPCLKDIV - [5:4] */ +#define WM8983_OPCLKDIV_SHIFT 4 /* OPCLKDIV - [5:4] */ +#define WM8983_OPCLKDIV_WIDTH 2 /* OPCLKDIV - [5:4] */ +#define WM8983_GPIO1POL 0x0008 /* GPIO1POL */ +#define WM8983_GPIO1POL_MASK 0x0008 /* GPIO1POL */ +#define WM8983_GPIO1POL_SHIFT 3 /* GPIO1POL */ +#define WM8983_GPIO1POL_WIDTH 1 /* GPIO1POL */ +#define WM8983_GPIO1SEL_MASK 0x0007 /* GPIO1SEL - [2:0] */ +#define WM8983_GPIO1SEL_SHIFT 0 /* GPIO1SEL - [2:0] */ +#define WM8983_GPIO1SEL_WIDTH 3 /* GPIO1SEL - [2:0] */ + +/* + * R9 (0x09) - Jack Detect Control 1 + */ +#define WM8983_JD_VMID1 0x0100 /* JD_VMID1 */ +#define WM8983_JD_VMID1_MASK 0x0100 /* JD_VMID1 */ +#define WM8983_JD_VMID1_SHIFT 8 /* JD_VMID1 */ +#define WM8983_JD_VMID1_WIDTH 1 /* JD_VMID1 */ +#define WM8983_JD_VMID0 0x0080 /* JD_VMID0 */ +#define WM8983_JD_VMID0_MASK 0x0080 /* JD_VMID0 */ +#define WM8983_JD_VMID0_SHIFT 7 /* JD_VMID0 */ +#define WM8983_JD_VMID0_WIDTH 1 /* JD_VMID0 */ +#define WM8983_JD_EN 0x0040 /* JD_EN */ +#define WM8983_JD_EN_MASK 0x0040 /* JD_EN */ +#define WM8983_JD_EN_SHIFT 6 /* JD_EN */ +#define WM8983_JD_EN_WIDTH 1 /* JD_EN */ +#define WM8983_JD_SEL_MASK 0x0030 /* JD_SEL - [5:4] */ +#define WM8983_JD_SEL_SHIFT 4 /* JD_SEL - [5:4] */ +#define WM8983_JD_SEL_WIDTH 2 /* JD_SEL - [5:4] */ + +/* + * R10 (0x0A) - DAC Control + */ +#define WM8983_SOFTMUTE 0x0040 /* SOFTMUTE */ +#define WM8983_SOFTMUTE_MASK 0x0040 /* SOFTMUTE */ +#define WM8983_SOFTMUTE_SHIFT 6 /* SOFTMUTE */ +#define WM8983_SOFTMUTE_WIDTH 1 /* SOFTMUTE */ +#define WM8983_DACOSR128 0x0008 /* DACOSR128 */ +#define WM8983_DACOSR128_MASK 0x0008 /* DACOSR128 */ +#define WM8983_DACOSR128_SHIFT 3 /* DACOSR128 */ +#define WM8983_DACOSR128_WIDTH 1 /* DACOSR128 */ +#define WM8983_AMUTE 0x0004 /* AMUTE */ +#define WM8983_AMUTE_MASK 0x0004 /* AMUTE */ +#define WM8983_AMUTE_SHIFT 2 /* AMUTE */ +#define WM8983_AMUTE_WIDTH 1 /* AMUTE */ +#define WM8983_DACRPOL 0x0002 /* DACRPOL */ +#define WM8983_DACRPOL_MASK 0x0002 /* DACRPOL */ +#define WM8983_DACRPOL_SHIFT 1 /* DACRPOL */ +#define WM8983_DACRPOL_WIDTH 1 /* DACRPOL */ +#define WM8983_DACLPOL 0x0001 /* DACLPOL */ +#define WM8983_DACLPOL_MASK 0x0001 /* DACLPOL */ +#define WM8983_DACLPOL_SHIFT 0 /* DACLPOL */ +#define WM8983_DACLPOL_WIDTH 1 /* DACLPOL */ + +/* + * R11 (0x0B) - Left DAC digital Vol + */ +#define WM8983_DACVU 0x0100 /* DACVU */ +#define WM8983_DACVU_MASK 0x0100 /* DACVU */ +#define WM8983_DACVU_SHIFT 8 /* DACVU */ +#define WM8983_DACVU_WIDTH 1 /* DACVU */ +#define WM8983_DACLVOL_MASK 0x00FF /* DACLVOL - [7:0] */ +#define WM8983_DACLVOL_SHIFT 0 /* DACLVOL - [7:0] */ +#define WM8983_DACLVOL_WIDTH 8 /* DACLVOL - [7:0] */ + +/* + * R12 (0x0C) - Right DAC digital vol + */ +#define WM8983_DACVU 0x0100 /* DACVU */ +#define WM8983_DACVU_MASK 0x0100 /* DACVU */ +#define WM8983_DACVU_SHIFT 8 /* DACVU */ +#define WM8983_DACVU_WIDTH 1 /* DACVU */ +#define WM8983_DACRVOL_MASK 0x00FF /* DACRVOL - [7:0] */ +#define WM8983_DACRVOL_SHIFT 0 /* DACRVOL - [7:0] */ +#define WM8983_DACRVOL_WIDTH 8 /* DACRVOL - [7:0] */ + +/* + * R13 (0x0D) - Jack Detect Control 2 + */ +#define WM8983_JD_EN1_MASK 0x00F0 /* JD_EN1 - [7:4] */ +#define WM8983_JD_EN1_SHIFT 4 /* JD_EN1 - [7:4] */ +#define WM8983_JD_EN1_WIDTH 4 /* JD_EN1 - [7:4] */ +#define WM8983_JD_EN0_MASK 0x000F /* JD_EN0 - [3:0] */ +#define WM8983_JD_EN0_SHIFT 0 /* JD_EN0 - [3:0] */ +#define WM8983_JD_EN0_WIDTH 4 /* JD_EN0 - [3:0] */ + +/* + * R14 (0x0E) - ADC Control + */ +#define WM8983_HPFEN 0x0100 /* HPFEN */ +#define WM8983_HPFEN_MASK 0x0100 /* HPFEN */ +#define WM8983_HPFEN_SHIFT 8 /* HPFEN */ +#define WM8983_HPFEN_WIDTH 1 /* HPFEN */ +#define WM8983_HPFAPP 0x0080 /* HPFAPP */ +#define WM8983_HPFAPP_MASK 0x0080 /* HPFAPP */ +#define WM8983_HPFAPP_SHIFT 7 /* HPFAPP */ +#define WM8983_HPFAPP_WIDTH 1 /* HPFAPP */ +#define WM8983_HPFCUT_MASK 0x0070 /* HPFCUT - [6:4] */ +#define WM8983_HPFCUT_SHIFT 4 /* HPFCUT - [6:4] */ +#define WM8983_HPFCUT_WIDTH 3 /* HPFCUT - [6:4] */ +#define WM8983_ADCOSR128 0x0008 /* ADCOSR128 */ +#define WM8983_ADCOSR128_MASK 0x0008 /* ADCOSR128 */ +#define WM8983_ADCOSR128_SHIFT 3 /* ADCOSR128 */ +#define WM8983_ADCOSR128_WIDTH 1 /* ADCOSR128 */ +#define WM8983_ADCRPOL 0x0002 /* ADCRPOL */ +#define WM8983_ADCRPOL_MASK 0x0002 /* ADCRPOL */ +#define WM8983_ADCRPOL_SHIFT 1 /* ADCRPOL */ +#define WM8983_ADCRPOL_WIDTH 1 /* ADCRPOL */ +#define WM8983_ADCLPOL 0x0001 /* ADCLPOL */ +#define WM8983_ADCLPOL_MASK 0x0001 /* ADCLPOL */ +#define WM8983_ADCLPOL_SHIFT 0 /* ADCLPOL */ +#define WM8983_ADCLPOL_WIDTH 1 /* ADCLPOL */ + +/* + * R15 (0x0F) - Left ADC Digital Vol + */ +#define WM8983_ADCVU 0x0100 /* ADCVU */ +#define WM8983_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8983_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8983_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8983_ADCLVOL_MASK 0x00FF /* ADCLVOL - [7:0] */ +#define WM8983_ADCLVOL_SHIFT 0 /* ADCLVOL - [7:0] */ +#define WM8983_ADCLVOL_WIDTH 8 /* ADCLVOL - [7:0] */ + +/* + * R16 (0x10) - Right ADC Digital Vol + */ +#define WM8983_ADCVU 0x0100 /* ADCVU */ +#define WM8983_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8983_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8983_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8983_ADCRVOL_MASK 0x00FF /* ADCRVOL - [7:0] */ +#define WM8983_ADCRVOL_SHIFT 0 /* ADCRVOL - [7:0] */ +#define WM8983_ADCRVOL_WIDTH 8 /* ADCRVOL - [7:0] */ + +/* + * R18 (0x12) - EQ1 - low shelf + */ +#define WM8983_EQ3DMODE 0x0100 /* EQ3DMODE */ +#define WM8983_EQ3DMODE_MASK 0x0100 /* EQ3DMODE */ +#define WM8983_EQ3DMODE_SHIFT 8 /* EQ3DMODE */ +#define WM8983_EQ3DMODE_WIDTH 1 /* EQ3DMODE */ +#define WM8983_EQ1C_MASK 0x0060 /* EQ1C - [6:5] */ +#define WM8983_EQ1C_SHIFT 5 /* EQ1C - [6:5] */ +#define WM8983_EQ1C_WIDTH 2 /* EQ1C - [6:5] */ +#define WM8983_EQ1G_MASK 0x001F /* EQ1G - [4:0] */ +#define WM8983_EQ1G_SHIFT 0 /* EQ1G - [4:0] */ +#define WM8983_EQ1G_WIDTH 5 /* EQ1G - [4:0] */ + +/* + * R19 (0x13) - EQ2 - peak 1 + */ +#define WM8983_EQ2BW 0x0100 /* EQ2BW */ +#define WM8983_EQ2BW_MASK 0x0100 /* EQ2BW */ +#define WM8983_EQ2BW_SHIFT 8 /* EQ2BW */ +#define WM8983_EQ2BW_WIDTH 1 /* EQ2BW */ +#define WM8983_EQ2C_MASK 0x0060 /* EQ2C - [6:5] */ +#define WM8983_EQ2C_SHIFT 5 /* EQ2C - [6:5] */ +#define WM8983_EQ2C_WIDTH 2 /* EQ2C - [6:5] */ +#define WM8983_EQ2G_MASK 0x001F /* EQ2G - [4:0] */ +#define WM8983_EQ2G_SHIFT 0 /* EQ2G - [4:0] */ +#define WM8983_EQ2G_WIDTH 5 /* EQ2G - [4:0] */ + +/* + * R20 (0x14) - EQ3 - peak 2 + */ +#define WM8983_EQ3BW 0x0100 /* EQ3BW */ +#define WM8983_EQ3BW_MASK 0x0100 /* EQ3BW */ +#define WM8983_EQ3BW_SHIFT 8 /* EQ3BW */ +#define WM8983_EQ3BW_WIDTH 1 /* EQ3BW */ +#define WM8983_EQ3C_MASK 0x0060 /* EQ3C - [6:5] */ +#define WM8983_EQ3C_SHIFT 5 /* EQ3C - [6:5] */ +#define WM8983_EQ3C_WIDTH 2 /* EQ3C - [6:5] */ +#define WM8983_EQ3G_MASK 0x001F /* EQ3G - [4:0] */ +#define WM8983_EQ3G_SHIFT 0 /* EQ3G - [4:0] */ +#define WM8983_EQ3G_WIDTH 5 /* EQ3G - [4:0] */ + +/* + * R21 (0x15) - EQ4 - peak 3 + */ +#define WM8983_EQ4BW 0x0100 /* EQ4BW */ +#define WM8983_EQ4BW_MASK 0x0100 /* EQ4BW */ +#define WM8983_EQ4BW_SHIFT 8 /* EQ4BW */ +#define WM8983_EQ4BW_WIDTH 1 /* EQ4BW */ +#define WM8983_EQ4C_MASK 0x0060 /* EQ4C - [6:5] */ +#define WM8983_EQ4C_SHIFT 5 /* EQ4C - [6:5] */ +#define WM8983_EQ4C_WIDTH 2 /* EQ4C - [6:5] */ +#define WM8983_EQ4G_MASK 0x001F /* EQ4G - [4:0] */ +#define WM8983_EQ4G_SHIFT 0 /* EQ4G - [4:0] */ +#define WM8983_EQ4G_WIDTH 5 /* EQ4G - [4:0] */ + +/* + * R22 (0x16) - EQ5 - high shelf + */ +#define WM8983_EQ5C_MASK 0x0060 /* EQ5C - [6:5] */ +#define WM8983_EQ5C_SHIFT 5 /* EQ5C - [6:5] */ +#define WM8983_EQ5C_WIDTH 2 /* EQ5C - [6:5] */ +#define WM8983_EQ5G_MASK 0x001F /* EQ5G - [4:0] */ +#define WM8983_EQ5G_SHIFT 0 /* EQ5G - [4:0] */ +#define WM8983_EQ5G_WIDTH 5 /* EQ5G - [4:0] */ + +/* + * R24 (0x18) - DAC Limiter 1 + */ +#define WM8983_LIMEN 0x0100 /* LIMEN */ +#define WM8983_LIMEN_MASK 0x0100 /* LIMEN */ +#define WM8983_LIMEN_SHIFT 8 /* LIMEN */ +#define WM8983_LIMEN_WIDTH 1 /* LIMEN */ +#define WM8983_LIMDCY_MASK 0x00F0 /* LIMDCY - [7:4] */ +#define WM8983_LIMDCY_SHIFT 4 /* LIMDCY - [7:4] */ +#define WM8983_LIMDCY_WIDTH 4 /* LIMDCY - [7:4] */ +#define WM8983_LIMATK_MASK 0x000F /* LIMATK - [3:0] */ +#define WM8983_LIMATK_SHIFT 0 /* LIMATK - [3:0] */ +#define WM8983_LIMATK_WIDTH 4 /* LIMATK - [3:0] */ + +/* + * R25 (0x19) - DAC Limiter 2 + */ +#define WM8983_LIMLVL_MASK 0x0070 /* LIMLVL - [6:4] */ +#define WM8983_LIMLVL_SHIFT 4 /* LIMLVL - [6:4] */ +#define WM8983_LIMLVL_WIDTH 3 /* LIMLVL - [6:4] */ +#define WM8983_LIMBOOST_MASK 0x000F /* LIMBOOST - [3:0] */ +#define WM8983_LIMBOOST_SHIFT 0 /* LIMBOOST - [3:0] */ +#define WM8983_LIMBOOST_WIDTH 4 /* LIMBOOST - [3:0] */ + +/* + * R27 (0x1B) - Notch Filter 1 + */ +#define WM8983_NFU 0x0100 /* NFU */ +#define WM8983_NFU_MASK 0x0100 /* NFU */ +#define WM8983_NFU_SHIFT 8 /* NFU */ +#define WM8983_NFU_WIDTH 1 /* NFU */ +#define WM8983_NFEN 0x0080 /* NFEN */ +#define WM8983_NFEN_MASK 0x0080 /* NFEN */ +#define WM8983_NFEN_SHIFT 7 /* NFEN */ +#define WM8983_NFEN_WIDTH 1 /* NFEN */ +#define WM8983_NFA0_13_7_MASK 0x007F /* NFA0(13:7) - [6:0] */ +#define WM8983_NFA0_13_7_SHIFT 0 /* NFA0(13:7) - [6:0] */ +#define WM8983_NFA0_13_7_WIDTH 7 /* NFA0(13:7) - [6:0] */ + +/* + * R28 (0x1C) - Notch Filter 2 + */ +#define WM8983_NFU 0x0100 /* NFU */ +#define WM8983_NFU_MASK 0x0100 /* NFU */ +#define WM8983_NFU_SHIFT 8 /* NFU */ +#define WM8983_NFU_WIDTH 1 /* NFU */ +#define WM8983_NFA0_6_0_MASK 0x007F /* NFA0(6:0) - [6:0] */ +#define WM8983_NFA0_6_0_SHIFT 0 /* NFA0(6:0) - [6:0] */ +#define WM8983_NFA0_6_0_WIDTH 7 /* NFA0(6:0) - [6:0] */ + +/* + * R29 (0x1D) - Notch Filter 3 + */ +#define WM8983_NFU 0x0100 /* NFU */ +#define WM8983_NFU_MASK 0x0100 /* NFU */ +#define WM8983_NFU_SHIFT 8 /* NFU */ +#define WM8983_NFU_WIDTH 1 /* NFU */ +#define WM8983_NFA1_13_7_MASK 0x007F /* NFA1(13:7) - [6:0] */ +#define WM8983_NFA1_13_7_SHIFT 0 /* NFA1(13:7) - [6:0] */ +#define WM8983_NFA1_13_7_WIDTH 7 /* NFA1(13:7) - [6:0] */ + +/* + * R30 (0x1E) - Notch Filter 4 + */ +#define WM8983_NFU 0x0100 /* NFU */ +#define WM8983_NFU_MASK 0x0100 /* NFU */ +#define WM8983_NFU_SHIFT 8 /* NFU */ +#define WM8983_NFU_WIDTH 1 /* NFU */ +#define WM8983_NFA1_6_0_MASK 0x007F /* NFA1(6:0) - [6:0] */ +#define WM8983_NFA1_6_0_SHIFT 0 /* NFA1(6:0) - [6:0] */ +#define WM8983_NFA1_6_0_WIDTH 7 /* NFA1(6:0) - [6:0] */ + +/* + * R32 (0x20) - ALC control 1 + */ +#define WM8983_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */ +#define WM8983_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */ +#define WM8983_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */ +#define WM8983_ALCMAX_MASK 0x0038 /* ALCMAX - [5:3] */ +#define WM8983_ALCMAX_SHIFT 3 /* ALCMAX - [5:3] */ +#define WM8983_ALCMAX_WIDTH 3 /* ALCMAX - [5:3] */ +#define WM8983_ALCMIN_MASK 0x0007 /* ALCMIN - [2:0] */ +#define WM8983_ALCMIN_SHIFT 0 /* ALCMIN - [2:0] */ +#define WM8983_ALCMIN_WIDTH 3 /* ALCMIN - [2:0] */ + +/* + * R33 (0x21) - ALC control 2 + */ +#define WM8983_ALCHLD_MASK 0x00F0 /* ALCHLD - [7:4] */ +#define WM8983_ALCHLD_SHIFT 4 /* ALCHLD - [7:4] */ +#define WM8983_ALCHLD_WIDTH 4 /* ALCHLD - [7:4] */ +#define WM8983_ALCLVL_MASK 0x000F /* ALCLVL - [3:0] */ +#define WM8983_ALCLVL_SHIFT 0 /* ALCLVL - [3:0] */ +#define WM8983_ALCLVL_WIDTH 4 /* ALCLVL - [3:0] */ + +/* + * R34 (0x22) - ALC control 3 + */ +#define WM8983_ALCMODE 0x0100 /* ALCMODE */ +#define WM8983_ALCMODE_MASK 0x0100 /* ALCMODE */ +#define WM8983_ALCMODE_SHIFT 8 /* ALCMODE */ +#define WM8983_ALCMODE_WIDTH 1 /* ALCMODE */ +#define WM8983_ALCDCY_MASK 0x00F0 /* ALCDCY - [7:4] */ +#define WM8983_ALCDCY_SHIFT 4 /* ALCDCY - [7:4] */ +#define WM8983_ALCDCY_WIDTH 4 /* ALCDCY - [7:4] */ +#define WM8983_ALCATK_MASK 0x000F /* ALCATK - [3:0] */ +#define WM8983_ALCATK_SHIFT 0 /* ALCATK - [3:0] */ +#define WM8983_ALCATK_WIDTH 4 /* ALCATK - [3:0] */ + +/* + * R35 (0x23) - Noise Gate + */ +#define WM8983_NGEN 0x0008 /* NGEN */ +#define WM8983_NGEN_MASK 0x0008 /* NGEN */ +#define WM8983_NGEN_SHIFT 3 /* NGEN */ +#define WM8983_NGEN_WIDTH 1 /* NGEN */ +#define WM8983_NGTH_MASK 0x0007 /* NGTH - [2:0] */ +#define WM8983_NGTH_SHIFT 0 /* NGTH - [2:0] */ +#define WM8983_NGTH_WIDTH 3 /* NGTH - [2:0] */ + +/* + * R36 (0x24) - PLL N + */ +#define WM8983_PLL_PRESCALE 0x0010 /* PLL_PRESCALE */ +#define WM8983_PLL_PRESCALE_MASK 0x0010 /* PLL_PRESCALE */ +#define WM8983_PLL_PRESCALE_SHIFT 4 /* PLL_PRESCALE */ +#define WM8983_PLL_PRESCALE_WIDTH 1 /* PLL_PRESCALE */ +#define WM8983_PLLN_MASK 0x000F /* PLLN - [3:0] */ +#define WM8983_PLLN_SHIFT 0 /* PLLN - [3:0] */ +#define WM8983_PLLN_WIDTH 4 /* PLLN - [3:0] */ + +/* + * R37 (0x25) - PLL K 1 + */ +#define WM8983_PLLK_23_18_MASK 0x003F /* PLLK(23:18) - [5:0] */ +#define WM8983_PLLK_23_18_SHIFT 0 /* PLLK(23:18) - [5:0] */ +#define WM8983_PLLK_23_18_WIDTH 6 /* PLLK(23:18) - [5:0] */ + +/* + * R38 (0x26) - PLL K 2 + */ +#define WM8983_PLLK_17_9_MASK 0x01FF /* PLLK(17:9) - [8:0] */ +#define WM8983_PLLK_17_9_SHIFT 0 /* PLLK(17:9) - [8:0] */ +#define WM8983_PLLK_17_9_WIDTH 9 /* PLLK(17:9) - [8:0] */ + +/* + * R39 (0x27) - PLL K 3 + */ +#define WM8983_PLLK_8_0_MASK 0x01FF /* PLLK(8:0) - [8:0] */ +#define WM8983_PLLK_8_0_SHIFT 0 /* PLLK(8:0) - [8:0] */ +#define WM8983_PLLK_8_0_WIDTH 9 /* PLLK(8:0) - [8:0] */ + +/* + * R41 (0x29) - 3D control + */ +#define WM8983_DEPTH3D_MASK 0x000F /* DEPTH3D - [3:0] */ +#define WM8983_DEPTH3D_SHIFT 0 /* DEPTH3D - [3:0] */ +#define WM8983_DEPTH3D_WIDTH 4 /* DEPTH3D - [3:0] */ + +/* + * R42 (0x2A) - OUT4 to ADC + */ +#define WM8983_OUT4_2ADCVOL_MASK 0x01C0 /* OUT4_2ADCVOL - [8:6] */ +#define WM8983_OUT4_2ADCVOL_SHIFT 6 /* OUT4_2ADCVOL - [8:6] */ +#define WM8983_OUT4_2ADCVOL_WIDTH 3 /* OUT4_2ADCVOL - [8:6] */ +#define WM8983_OUT4_2LNR 0x0020 /* OUT4_2LNR */ +#define WM8983_OUT4_2LNR_MASK 0x0020 /* OUT4_2LNR */ +#define WM8983_OUT4_2LNR_SHIFT 5 /* OUT4_2LNR */ +#define WM8983_OUT4_2LNR_WIDTH 1 /* OUT4_2LNR */ +#define WM8983_POBCTRL 0x0004 /* POBCTRL */ +#define WM8983_POBCTRL_MASK 0x0004 /* POBCTRL */ +#define WM8983_POBCTRL_SHIFT 2 /* POBCTRL */ +#define WM8983_POBCTRL_WIDTH 1 /* POBCTRL */ +#define WM8983_DELEN 0x0002 /* DELEN */ +#define WM8983_DELEN_MASK 0x0002 /* DELEN */ +#define WM8983_DELEN_SHIFT 1 /* DELEN */ +#define WM8983_DELEN_WIDTH 1 /* DELEN */ +#define WM8983_OUT1DEL 0x0001 /* OUT1DEL */ +#define WM8983_OUT1DEL_MASK 0x0001 /* OUT1DEL */ +#define WM8983_OUT1DEL_SHIFT 0 /* OUT1DEL */ +#define WM8983_OUT1DEL_WIDTH 1 /* OUT1DEL */ + +/* + * R43 (0x2B) - Beep control + */ +#define WM8983_BYPL2RMIX 0x0100 /* BYPL2RMIX */ +#define WM8983_BYPL2RMIX_MASK 0x0100 /* BYPL2RMIX */ +#define WM8983_BYPL2RMIX_SHIFT 8 /* BYPL2RMIX */ +#define WM8983_BYPL2RMIX_WIDTH 1 /* BYPL2RMIX */ +#define WM8983_BYPR2LMIX 0x0080 /* BYPR2LMIX */ +#define WM8983_BYPR2LMIX_MASK 0x0080 /* BYPR2LMIX */ +#define WM8983_BYPR2LMIX_SHIFT 7 /* BYPR2LMIX */ +#define WM8983_BYPR2LMIX_WIDTH 1 /* BYPR2LMIX */ +#define WM8983_MUTERPGA2INV 0x0020 /* MUTERPGA2INV */ +#define WM8983_MUTERPGA2INV_MASK 0x0020 /* MUTERPGA2INV */ +#define WM8983_MUTERPGA2INV_SHIFT 5 /* MUTERPGA2INV */ +#define WM8983_MUTERPGA2INV_WIDTH 1 /* MUTERPGA2INV */ +#define WM8983_INVROUT2 0x0010 /* INVROUT2 */ +#define WM8983_INVROUT2_MASK 0x0010 /* INVROUT2 */ +#define WM8983_INVROUT2_SHIFT 4 /* INVROUT2 */ +#define WM8983_INVROUT2_WIDTH 1 /* INVROUT2 */ +#define WM8983_BEEPVOL_MASK 0x000E /* BEEPVOL - [3:1] */ +#define WM8983_BEEPVOL_SHIFT 1 /* BEEPVOL - [3:1] */ +#define WM8983_BEEPVOL_WIDTH 3 /* BEEPVOL - [3:1] */ +#define WM8983_BEEPEN 0x0001 /* BEEPEN */ +#define WM8983_BEEPEN_MASK 0x0001 /* BEEPEN */ +#define WM8983_BEEPEN_SHIFT 0 /* BEEPEN */ +#define WM8983_BEEPEN_WIDTH 1 /* BEEPEN */ + +/* + * R44 (0x2C) - Input ctrl + */ +#define WM8983_MBVSEL 0x0100 /* MBVSEL */ +#define WM8983_MBVSEL_MASK 0x0100 /* MBVSEL */ +#define WM8983_MBVSEL_SHIFT 8 /* MBVSEL */ +#define WM8983_MBVSEL_WIDTH 1 /* MBVSEL */ +#define WM8983_R2_2INPPGA 0x0040 /* R2_2INPPGA */ +#define WM8983_R2_2INPPGA_MASK 0x0040 /* R2_2INPPGA */ +#define WM8983_R2_2INPPGA_SHIFT 6 /* R2_2INPPGA */ +#define WM8983_R2_2INPPGA_WIDTH 1 /* R2_2INPPGA */ +#define WM8983_RIN2INPPGA 0x0020 /* RIN2INPPGA */ +#define WM8983_RIN2INPPGA_MASK 0x0020 /* RIN2INPPGA */ +#define WM8983_RIN2INPPGA_SHIFT 5 /* RIN2INPPGA */ +#define WM8983_RIN2INPPGA_WIDTH 1 /* RIN2INPPGA */ +#define WM8983_RIP2INPPGA 0x0010 /* RIP2INPPGA */ +#define WM8983_RIP2INPPGA_MASK 0x0010 /* RIP2INPPGA */ +#define WM8983_RIP2INPPGA_SHIFT 4 /* RIP2INPPGA */ +#define WM8983_RIP2INPPGA_WIDTH 1 /* RIP2INPPGA */ +#define WM8983_L2_2INPPGA 0x0004 /* L2_2INPPGA */ +#define WM8983_L2_2INPPGA_MASK 0x0004 /* L2_2INPPGA */ +#define WM8983_L2_2INPPGA_SHIFT 2 /* L2_2INPPGA */ +#define WM8983_L2_2INPPGA_WIDTH 1 /* L2_2INPPGA */ +#define WM8983_LIN2INPPGA 0x0002 /* LIN2INPPGA */ +#define WM8983_LIN2INPPGA_MASK 0x0002 /* LIN2INPPGA */ +#define WM8983_LIN2INPPGA_SHIFT 1 /* LIN2INPPGA */ +#define WM8983_LIN2INPPGA_WIDTH 1 /* LIN2INPPGA */ +#define WM8983_LIP2INPPGA 0x0001 /* LIP2INPPGA */ +#define WM8983_LIP2INPPGA_MASK 0x0001 /* LIP2INPPGA */ +#define WM8983_LIP2INPPGA_SHIFT 0 /* LIP2INPPGA */ +#define WM8983_LIP2INPPGA_WIDTH 1 /* LIP2INPPGA */ + +/* + * R45 (0x2D) - Left INP PGA gain ctrl + */ +#define WM8983_INPGAVU 0x0100 /* INPGAVU */ +#define WM8983_INPGAVU_MASK 0x0100 /* INPGAVU */ +#define WM8983_INPGAVU_SHIFT 8 /* INPGAVU */ +#define WM8983_INPGAVU_WIDTH 1 /* INPGAVU */ +#define WM8983_INPPGAZCL 0x0080 /* INPPGAZCL */ +#define WM8983_INPPGAZCL_MASK 0x0080 /* INPPGAZCL */ +#define WM8983_INPPGAZCL_SHIFT 7 /* INPPGAZCL */ +#define WM8983_INPPGAZCL_WIDTH 1 /* INPPGAZCL */ +#define WM8983_INPPGAMUTEL 0x0040 /* INPPGAMUTEL */ +#define WM8983_INPPGAMUTEL_MASK 0x0040 /* INPPGAMUTEL */ +#define WM8983_INPPGAMUTEL_SHIFT 6 /* INPPGAMUTEL */ +#define WM8983_INPPGAMUTEL_WIDTH 1 /* INPPGAMUTEL */ +#define WM8983_INPPGAVOLL_MASK 0x003F /* INPPGAVOLL - [5:0] */ +#define WM8983_INPPGAVOLL_SHIFT 0 /* INPPGAVOLL - [5:0] */ +#define WM8983_INPPGAVOLL_WIDTH 6 /* INPPGAVOLL - [5:0] */ + +/* + * R46 (0x2E) - Right INP PGA gain ctrl + */ +#define WM8983_INPGAVU 0x0100 /* INPGAVU */ +#define WM8983_INPGAVU_MASK 0x0100 /* INPGAVU */ +#define WM8983_INPGAVU_SHIFT 8 /* INPGAVU */ +#define WM8983_INPGAVU_WIDTH 1 /* INPGAVU */ +#define WM8983_INPPGAZCR 0x0080 /* INPPGAZCR */ +#define WM8983_INPPGAZCR_MASK 0x0080 /* INPPGAZCR */ +#define WM8983_INPPGAZCR_SHIFT 7 /* INPPGAZCR */ +#define WM8983_INPPGAZCR_WIDTH 1 /* INPPGAZCR */ +#define WM8983_INPPGAMUTER 0x0040 /* INPPGAMUTER */ +#define WM8983_INPPGAMUTER_MASK 0x0040 /* INPPGAMUTER */ +#define WM8983_INPPGAMUTER_SHIFT 6 /* INPPGAMUTER */ +#define WM8983_INPPGAMUTER_WIDTH 1 /* INPPGAMUTER */ +#define WM8983_INPPGAVOLR_MASK 0x003F /* INPPGAVOLR - [5:0] */ +#define WM8983_INPPGAVOLR_SHIFT 0 /* INPPGAVOLR - [5:0] */ +#define WM8983_INPPGAVOLR_WIDTH 6 /* INPPGAVOLR - [5:0] */ + +/* + * R47 (0x2F) - Left ADC BOOST ctrl + */ +#define WM8983_PGABOOSTL 0x0100 /* PGABOOSTL */ +#define WM8983_PGABOOSTL_MASK 0x0100 /* PGABOOSTL */ +#define WM8983_PGABOOSTL_SHIFT 8 /* PGABOOSTL */ +#define WM8983_PGABOOSTL_WIDTH 1 /* PGABOOSTL */ +#define WM8983_L2_2BOOSTVOL_MASK 0x0070 /* L2_2BOOSTVOL - [6:4] */ +#define WM8983_L2_2BOOSTVOL_SHIFT 4 /* L2_2BOOSTVOL - [6:4] */ +#define WM8983_L2_2BOOSTVOL_WIDTH 3 /* L2_2BOOSTVOL - [6:4] */ +#define WM8983_AUXL2BOOSTVOL_MASK 0x0007 /* AUXL2BOOSTVOL - [2:0] */ +#define WM8983_AUXL2BOOSTVOL_SHIFT 0 /* AUXL2BOOSTVOL - [2:0] */ +#define WM8983_AUXL2BOOSTVOL_WIDTH 3 /* AUXL2BOOSTVOL - [2:0] */ + +/* + * R48 (0x30) - Right ADC BOOST ctrl + */ +#define WM8983_PGABOOSTR 0x0100 /* PGABOOSTR */ +#define WM8983_PGABOOSTR_MASK 0x0100 /* PGABOOSTR */ +#define WM8983_PGABOOSTR_SHIFT 8 /* PGABOOSTR */ +#define WM8983_PGABOOSTR_WIDTH 1 /* PGABOOSTR */ +#define WM8983_R2_2BOOSTVOL_MASK 0x0070 /* R2_2BOOSTVOL - [6:4] */ +#define WM8983_R2_2BOOSTVOL_SHIFT 4 /* R2_2BOOSTVOL - [6:4] */ +#define WM8983_R2_2BOOSTVOL_WIDTH 3 /* R2_2BOOSTVOL - [6:4] */ +#define WM8983_AUXR2BOOSTVOL_MASK 0x0007 /* AUXR2BOOSTVOL - [2:0] */ +#define WM8983_AUXR2BOOSTVOL_SHIFT 0 /* AUXR2BOOSTVOL - [2:0] */ +#define WM8983_AUXR2BOOSTVOL_WIDTH 3 /* AUXR2BOOSTVOL - [2:0] */ + +/* + * R49 (0x31) - Output ctrl + */ +#define WM8983_DACL2RMIX 0x0040 /* DACL2RMIX */ +#define WM8983_DACL2RMIX_MASK 0x0040 /* DACL2RMIX */ +#define WM8983_DACL2RMIX_SHIFT 6 /* DACL2RMIX */ +#define WM8983_DACL2RMIX_WIDTH 1 /* DACL2RMIX */ +#define WM8983_DACR2LMIX 0x0020 /* DACR2LMIX */ +#define WM8983_DACR2LMIX_MASK 0x0020 /* DACR2LMIX */ +#define WM8983_DACR2LMIX_SHIFT 5 /* DACR2LMIX */ +#define WM8983_DACR2LMIX_WIDTH 1 /* DACR2LMIX */ +#define WM8983_OUT4BOOST 0x0010 /* OUT4BOOST */ +#define WM8983_OUT4BOOST_MASK 0x0010 /* OUT4BOOST */ +#define WM8983_OUT4BOOST_SHIFT 4 /* OUT4BOOST */ +#define WM8983_OUT4BOOST_WIDTH 1 /* OUT4BOOST */ +#define WM8983_OUT3BOOST 0x0008 /* OUT3BOOST */ +#define WM8983_OUT3BOOST_MASK 0x0008 /* OUT3BOOST */ +#define WM8983_OUT3BOOST_SHIFT 3 /* OUT3BOOST */ +#define WM8983_OUT3BOOST_WIDTH 1 /* OUT3BOOST */ +#define WM8983_SPKBOOST 0x0004 /* SPKBOOST */ +#define WM8983_SPKBOOST_MASK 0x0004 /* SPKBOOST */ +#define WM8983_SPKBOOST_SHIFT 2 /* SPKBOOST */ +#define WM8983_SPKBOOST_WIDTH 1 /* SPKBOOST */ +#define WM8983_TSDEN 0x0002 /* TSDEN */ +#define WM8983_TSDEN_MASK 0x0002 /* TSDEN */ +#define WM8983_TSDEN_SHIFT 1 /* TSDEN */ +#define WM8983_TSDEN_WIDTH 1 /* TSDEN */ +#define WM8983_VROI 0x0001 /* VROI */ +#define WM8983_VROI_MASK 0x0001 /* VROI */ +#define WM8983_VROI_SHIFT 0 /* VROI */ +#define WM8983_VROI_WIDTH 1 /* VROI */ + +/* + * R50 (0x32) - Left mixer ctrl + */ +#define WM8983_AUXLMIXVOL_MASK 0x01C0 /* AUXLMIXVOL - [8:6] */ +#define WM8983_AUXLMIXVOL_SHIFT 6 /* AUXLMIXVOL - [8:6] */ +#define WM8983_AUXLMIXVOL_WIDTH 3 /* AUXLMIXVOL - [8:6] */ +#define WM8983_AUXL2LMIX 0x0020 /* AUXL2LMIX */ +#define WM8983_AUXL2LMIX_MASK 0x0020 /* AUXL2LMIX */ +#define WM8983_AUXL2LMIX_SHIFT 5 /* AUXL2LMIX */ +#define WM8983_AUXL2LMIX_WIDTH 1 /* AUXL2LMIX */ +#define WM8983_BYPLMIXVOL_MASK 0x001C /* BYPLMIXVOL - [4:2] */ +#define WM8983_BYPLMIXVOL_SHIFT 2 /* BYPLMIXVOL - [4:2] */ +#define WM8983_BYPLMIXVOL_WIDTH 3 /* BYPLMIXVOL - [4:2] */ +#define WM8983_BYPL2LMIX 0x0002 /* BYPL2LMIX */ +#define WM8983_BYPL2LMIX_MASK 0x0002 /* BYPL2LMIX */ +#define WM8983_BYPL2LMIX_SHIFT 1 /* BYPL2LMIX */ +#define WM8983_BYPL2LMIX_WIDTH 1 /* BYPL2LMIX */ +#define WM8983_DACL2LMIX 0x0001 /* DACL2LMIX */ +#define WM8983_DACL2LMIX_MASK 0x0001 /* DACL2LMIX */ +#define WM8983_DACL2LMIX_SHIFT 0 /* DACL2LMIX */ +#define WM8983_DACL2LMIX_WIDTH 1 /* DACL2LMIX */ + +/* + * R51 (0x33) - Right mixer ctrl + */ +#define WM8983_AUXRMIXVOL_MASK 0x01C0 /* AUXRMIXVOL - [8:6] */ +#define WM8983_AUXRMIXVOL_SHIFT 6 /* AUXRMIXVOL - [8:6] */ +#define WM8983_AUXRMIXVOL_WIDTH 3 /* AUXRMIXVOL - [8:6] */ +#define WM8983_AUXR2RMIX 0x0020 /* AUXR2RMIX */ +#define WM8983_AUXR2RMIX_MASK 0x0020 /* AUXR2RMIX */ +#define WM8983_AUXR2RMIX_SHIFT 5 /* AUXR2RMIX */ +#define WM8983_AUXR2RMIX_WIDTH 1 /* AUXR2RMIX */ +#define WM8983_BYPRMIXVOL_MASK 0x001C /* BYPRMIXVOL - [4:2] */ +#define WM8983_BYPRMIXVOL_SHIFT 2 /* BYPRMIXVOL - [4:2] */ +#define WM8983_BYPRMIXVOL_WIDTH 3 /* BYPRMIXVOL - [4:2] */ +#define WM8983_BYPR2RMIX 0x0002 /* BYPR2RMIX */ +#define WM8983_BYPR2RMIX_MASK 0x0002 /* BYPR2RMIX */ +#define WM8983_BYPR2RMIX_SHIFT 1 /* BYPR2RMIX */ +#define WM8983_BYPR2RMIX_WIDTH 1 /* BYPR2RMIX */ +#define WM8983_DACR2RMIX 0x0001 /* DACR2RMIX */ +#define WM8983_DACR2RMIX_MASK 0x0001 /* DACR2RMIX */ +#define WM8983_DACR2RMIX_SHIFT 0 /* DACR2RMIX */ +#define WM8983_DACR2RMIX_WIDTH 1 /* DACR2RMIX */ + +/* + * R52 (0x34) - LOUT1 (HP) volume ctrl + */ +#define WM8983_OUT1VU 0x0100 /* OUT1VU */ +#define WM8983_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8983_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8983_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8983_LOUT1ZC 0x0080 /* LOUT1ZC */ +#define WM8983_LOUT1ZC_MASK 0x0080 /* LOUT1ZC */ +#define WM8983_LOUT1ZC_SHIFT 7 /* LOUT1ZC */ +#define WM8983_LOUT1ZC_WIDTH 1 /* LOUT1ZC */ +#define WM8983_LOUT1MUTE 0x0040 /* LOUT1MUTE */ +#define WM8983_LOUT1MUTE_MASK 0x0040 /* LOUT1MUTE */ +#define WM8983_LOUT1MUTE_SHIFT 6 /* LOUT1MUTE */ +#define WM8983_LOUT1MUTE_WIDTH 1 /* LOUT1MUTE */ +#define WM8983_LOUT1VOL_MASK 0x003F /* LOUT1VOL - [5:0] */ +#define WM8983_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [5:0] */ +#define WM8983_LOUT1VOL_WIDTH 6 /* LOUT1VOL - [5:0] */ + +/* + * R53 (0x35) - ROUT1 (HP) volume ctrl + */ +#define WM8983_OUT1VU 0x0100 /* OUT1VU */ +#define WM8983_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8983_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8983_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8983_ROUT1ZC 0x0080 /* ROUT1ZC */ +#define WM8983_ROUT1ZC_MASK 0x0080 /* ROUT1ZC */ +#define WM8983_ROUT1ZC_SHIFT 7 /* ROUT1ZC */ +#define WM8983_ROUT1ZC_WIDTH 1 /* ROUT1ZC */ +#define WM8983_ROUT1MUTE 0x0040 /* ROUT1MUTE */ +#define WM8983_ROUT1MUTE_MASK 0x0040 /* ROUT1MUTE */ +#define WM8983_ROUT1MUTE_SHIFT 6 /* ROUT1MUTE */ +#define WM8983_ROUT1MUTE_WIDTH 1 /* ROUT1MUTE */ +#define WM8983_ROUT1VOL_MASK 0x003F /* ROUT1VOL - [5:0] */ +#define WM8983_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [5:0] */ +#define WM8983_ROUT1VOL_WIDTH 6 /* ROUT1VOL - [5:0] */ + +/* + * R54 (0x36) - LOUT2 (SPK) volume ctrl + */ +#define WM8983_OUT2VU 0x0100 /* OUT2VU */ +#define WM8983_OUT2VU_MASK 0x0100 /* OUT2VU */ +#define WM8983_OUT2VU_SHIFT 8 /* OUT2VU */ +#define WM8983_OUT2VU_WIDTH 1 /* OUT2VU */ +#define WM8983_LOUT2ZC 0x0080 /* LOUT2ZC */ +#define WM8983_LOUT2ZC_MASK 0x0080 /* LOUT2ZC */ +#define WM8983_LOUT2ZC_SHIFT 7 /* LOUT2ZC */ +#define WM8983_LOUT2ZC_WIDTH 1 /* LOUT2ZC */ +#define WM8983_LOUT2MUTE 0x0040 /* LOUT2MUTE */ +#define WM8983_LOUT2MUTE_MASK 0x0040 /* LOUT2MUTE */ +#define WM8983_LOUT2MUTE_SHIFT 6 /* LOUT2MUTE */ +#define WM8983_LOUT2MUTE_WIDTH 1 /* LOUT2MUTE */ +#define WM8983_LOUT2VOL_MASK 0x003F /* LOUT2VOL - [5:0] */ +#define WM8983_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [5:0] */ +#define WM8983_LOUT2VOL_WIDTH 6 /* LOUT2VOL - [5:0] */ + +/* + * R55 (0x37) - ROUT2 (SPK) volume ctrl + */ +#define WM8983_OUT2VU 0x0100 /* OUT2VU */ +#define WM8983_OUT2VU_MASK 0x0100 /* OUT2VU */ +#define WM8983_OUT2VU_SHIFT 8 /* OUT2VU */ +#define WM8983_OUT2VU_WIDTH 1 /* OUT2VU */ +#define WM8983_ROUT2ZC 0x0080 /* ROUT2ZC */ +#define WM8983_ROUT2ZC_MASK 0x0080 /* ROUT2ZC */ +#define WM8983_ROUT2ZC_SHIFT 7 /* ROUT2ZC */ +#define WM8983_ROUT2ZC_WIDTH 1 /* ROUT2ZC */ +#define WM8983_ROUT2MUTE 0x0040 /* ROUT2MUTE */ +#define WM8983_ROUT2MUTE_MASK 0x0040 /* ROUT2MUTE */ +#define WM8983_ROUT2MUTE_SHIFT 6 /* ROUT2MUTE */ +#define WM8983_ROUT2MUTE_WIDTH 1 /* ROUT2MUTE */ +#define WM8983_ROUT2VOL_MASK 0x003F /* ROUT2VOL - [5:0] */ +#define WM8983_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [5:0] */ +#define WM8983_ROUT2VOL_WIDTH 6 /* ROUT2VOL - [5:0] */ + +/* + * R56 (0x38) - OUT3 mixer ctrl + */ +#define WM8983_OUT3MUTE 0x0040 /* OUT3MUTE */ +#define WM8983_OUT3MUTE_MASK 0x0040 /* OUT3MUTE */ +#define WM8983_OUT3MUTE_SHIFT 6 /* OUT3MUTE */ +#define WM8983_OUT3MUTE_WIDTH 1 /* OUT3MUTE */ +#define WM8983_OUT4_2OUT3 0x0008 /* OUT4_2OUT3 */ +#define WM8983_OUT4_2OUT3_MASK 0x0008 /* OUT4_2OUT3 */ +#define WM8983_OUT4_2OUT3_SHIFT 3 /* OUT4_2OUT3 */ +#define WM8983_OUT4_2OUT3_WIDTH 1 /* OUT4_2OUT3 */ +#define WM8983_BYPL2OUT3 0x0004 /* BYPL2OUT3 */ +#define WM8983_BYPL2OUT3_MASK 0x0004 /* BYPL2OUT3 */ +#define WM8983_BYPL2OUT3_SHIFT 2 /* BYPL2OUT3 */ +#define WM8983_BYPL2OUT3_WIDTH 1 /* BYPL2OUT3 */ +#define WM8983_LMIX2OUT3 0x0002 /* LMIX2OUT3 */ +#define WM8983_LMIX2OUT3_MASK 0x0002 /* LMIX2OUT3 */ +#define WM8983_LMIX2OUT3_SHIFT 1 /* LMIX2OUT3 */ +#define WM8983_LMIX2OUT3_WIDTH 1 /* LMIX2OUT3 */ +#define WM8983_LDAC2OUT3 0x0001 /* LDAC2OUT3 */ +#define WM8983_LDAC2OUT3_MASK 0x0001 /* LDAC2OUT3 */ +#define WM8983_LDAC2OUT3_SHIFT 0 /* LDAC2OUT3 */ +#define WM8983_LDAC2OUT3_WIDTH 1 /* LDAC2OUT3 */ + +/* + * R57 (0x39) - OUT4 (MONO) mix ctrl + */ +#define WM8983_OUT3_2OUT4 0x0080 /* OUT3_2OUT4 */ +#define WM8983_OUT3_2OUT4_MASK 0x0080 /* OUT3_2OUT4 */ +#define WM8983_OUT3_2OUT4_SHIFT 7 /* OUT3_2OUT4 */ +#define WM8983_OUT3_2OUT4_WIDTH 1 /* OUT3_2OUT4 */ +#define WM8983_OUT4MUTE 0x0040 /* OUT4MUTE */ +#define WM8983_OUT4MUTE_MASK 0x0040 /* OUT4MUTE */ +#define WM8983_OUT4MUTE_SHIFT 6 /* OUT4MUTE */ +#define WM8983_OUT4MUTE_WIDTH 1 /* OUT4MUTE */ +#define WM8983_OUT4ATTN 0x0020 /* OUT4ATTN */ +#define WM8983_OUT4ATTN_MASK 0x0020 /* OUT4ATTN */ +#define WM8983_OUT4ATTN_SHIFT 5 /* OUT4ATTN */ +#define WM8983_OUT4ATTN_WIDTH 1 /* OUT4ATTN */ +#define WM8983_LMIX2OUT4 0x0010 /* LMIX2OUT4 */ +#define WM8983_LMIX2OUT4_MASK 0x0010 /* LMIX2OUT4 */ +#define WM8983_LMIX2OUT4_SHIFT 4 /* LMIX2OUT4 */ +#define WM8983_LMIX2OUT4_WIDTH 1 /* LMIX2OUT4 */ +#define WM8983_LDAC2OUT4 0x0008 /* LDAC2OUT4 */ +#define WM8983_LDAC2OUT4_MASK 0x0008 /* LDAC2OUT4 */ +#define WM8983_LDAC2OUT4_SHIFT 3 /* LDAC2OUT4 */ +#define WM8983_LDAC2OUT4_WIDTH 1 /* LDAC2OUT4 */ +#define WM8983_BYPR2OUT4 0x0004 /* BYPR2OUT4 */ +#define WM8983_BYPR2OUT4_MASK 0x0004 /* BYPR2OUT4 */ +#define WM8983_BYPR2OUT4_SHIFT 2 /* BYPR2OUT4 */ +#define WM8983_BYPR2OUT4_WIDTH 1 /* BYPR2OUT4 */ +#define WM8983_RMIX2OUT4 0x0002 /* RMIX2OUT4 */ +#define WM8983_RMIX2OUT4_MASK 0x0002 /* RMIX2OUT4 */ +#define WM8983_RMIX2OUT4_SHIFT 1 /* RMIX2OUT4 */ +#define WM8983_RMIX2OUT4_WIDTH 1 /* RMIX2OUT4 */ +#define WM8983_RDAC2OUT4 0x0001 /* RDAC2OUT4 */ +#define WM8983_RDAC2OUT4_MASK 0x0001 /* RDAC2OUT4 */ +#define WM8983_RDAC2OUT4_SHIFT 0 /* RDAC2OUT4 */ +#define WM8983_RDAC2OUT4_WIDTH 1 /* RDAC2OUT4 */ + +/* + * R61 (0x3D) - BIAS CTRL + */ +#define WM8983_BIASCUT 0x0100 /* BIASCUT */ +#define WM8983_BIASCUT_MASK 0x0100 /* BIASCUT */ +#define WM8983_BIASCUT_SHIFT 8 /* BIASCUT */ +#define WM8983_BIASCUT_WIDTH 1 /* BIASCUT */ +#define WM8983_HALFIPBIAS 0x0080 /* HALFIPBIAS */ +#define WM8983_HALFIPBIAS_MASK 0x0080 /* HALFIPBIAS */ +#define WM8983_HALFIPBIAS_SHIFT 7 /* HALFIPBIAS */ +#define WM8983_HALFIPBIAS_WIDTH 1 /* HALFIPBIAS */ +#define WM8983_VBBIASTST_MASK 0x0060 /* VBBIASTST - [6:5] */ +#define WM8983_VBBIASTST_SHIFT 5 /* VBBIASTST - [6:5] */ +#define WM8983_VBBIASTST_WIDTH 2 /* VBBIASTST - [6:5] */ +#define WM8983_BUFBIAS_MASK 0x0018 /* BUFBIAS - [4:3] */ +#define WM8983_BUFBIAS_SHIFT 3 /* BUFBIAS - [4:3] */ +#define WM8983_BUFBIAS_WIDTH 2 /* BUFBIAS - [4:3] */ +#define WM8983_ADCBIAS_MASK 0x0006 /* ADCBIAS - [2:1] */ +#define WM8983_ADCBIAS_SHIFT 1 /* ADCBIAS - [2:1] */ +#define WM8983_ADCBIAS_WIDTH 2 /* ADCBIAS - [2:1] */ +#define WM8983_HALFOPBIAS 0x0001 /* HALFOPBIAS */ +#define WM8983_HALFOPBIAS_MASK 0x0001 /* HALFOPBIAS */ +#define WM8983_HALFOPBIAS_SHIFT 0 /* HALFOPBIAS */ +#define WM8983_HALFOPBIAS_WIDTH 1 /* HALFOPBIAS */ + +enum clk_src { + WM8983_CLKSRC_MCLK, + WM8983_CLKSRC_PLL +}; + +#endif /* _WM8983_H */ diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c new file mode 100644 index 000000000..18b342ca4 --- /dev/null +++ b/sound/soc/codecs/wm8985.c @@ -0,0 +1,1250 @@ +/* + * wm8985.c -- WM8985 / WM8758 ALSA SoC Audio driver + * + * Copyright 2010 Wolfson Microelectronics plc + * Author: Dimitris Papastamos + * + * WM8758 support: + * Copyright: 2016 Barix AG + * Author: Petr Kulhavy + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * o Add OUT3/OUT4 mixer controls. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8985.h" + +#define WM8985_NUM_SUPPLIES 4 +static const char *wm8985_supply_names[WM8985_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "AVDD1", + "AVDD2" +}; + +enum wm8985_type { + WM8985, + WM8758, +}; + +static const struct reg_default wm8985_reg_defaults[] = { + { 1, 0x0000 }, /* R1 - Power management 1 */ + { 2, 0x0000 }, /* R2 - Power management 2 */ + { 3, 0x0000 }, /* R3 - Power management 3 */ + { 4, 0x0050 }, /* R4 - Audio Interface */ + { 5, 0x0000 }, /* R5 - Companding control */ + { 6, 0x0140 }, /* R6 - Clock Gen control */ + { 7, 0x0000 }, /* R7 - Additional control */ + { 8, 0x0000 }, /* R8 - GPIO Control */ + { 9, 0x0000 }, /* R9 - Jack Detect Control 1 */ + { 10, 0x0000 }, /* R10 - DAC Control */ + { 11, 0x00FF }, /* R11 - Left DAC digital Vol */ + { 12, 0x00FF }, /* R12 - Right DAC digital vol */ + { 13, 0x0000 }, /* R13 - Jack Detect Control 2 */ + { 14, 0x0100 }, /* R14 - ADC Control */ + { 15, 0x00FF }, /* R15 - Left ADC Digital Vol */ + { 16, 0x00FF }, /* R16 - Right ADC Digital Vol */ + { 18, 0x012C }, /* R18 - EQ1 - low shelf */ + { 19, 0x002C }, /* R19 - EQ2 - peak 1 */ + { 20, 0x002C }, /* R20 - EQ3 - peak 2 */ + { 21, 0x002C }, /* R21 - EQ4 - peak 3 */ + { 22, 0x002C }, /* R22 - EQ5 - high shelf */ + { 24, 0x0032 }, /* R24 - DAC Limiter 1 */ + { 25, 0x0000 }, /* R25 - DAC Limiter 2 */ + { 27, 0x0000 }, /* R27 - Notch Filter 1 */ + { 28, 0x0000 }, /* R28 - Notch Filter 2 */ + { 29, 0x0000 }, /* R29 - Notch Filter 3 */ + { 30, 0x0000 }, /* R30 - Notch Filter 4 */ + { 32, 0x0038 }, /* R32 - ALC control 1 */ + { 33, 0x000B }, /* R33 - ALC control 2 */ + { 34, 0x0032 }, /* R34 - ALC control 3 */ + { 35, 0x0000 }, /* R35 - Noise Gate */ + { 36, 0x0008 }, /* R36 - PLL N */ + { 37, 0x000C }, /* R37 - PLL K 1 */ + { 38, 0x0093 }, /* R38 - PLL K 2 */ + { 39, 0x00E9 }, /* R39 - PLL K 3 */ + { 41, 0x0000 }, /* R41 - 3D control */ + { 42, 0x0000 }, /* R42 - OUT4 to ADC */ + { 43, 0x0000 }, /* R43 - Beep control */ + { 44, 0x0033 }, /* R44 - Input ctrl */ + { 45, 0x0010 }, /* R45 - Left INP PGA gain ctrl */ + { 46, 0x0010 }, /* R46 - Right INP PGA gain ctrl */ + { 47, 0x0100 }, /* R47 - Left ADC BOOST ctrl */ + { 48, 0x0100 }, /* R48 - Right ADC BOOST ctrl */ + { 49, 0x0002 }, /* R49 - Output ctrl */ + { 50, 0x0001 }, /* R50 - Left mixer ctrl */ + { 51, 0x0001 }, /* R51 - Right mixer ctrl */ + { 52, 0x0039 }, /* R52 - LOUT1 (HP) volume ctrl */ + { 53, 0x0039 }, /* R53 - ROUT1 (HP) volume ctrl */ + { 54, 0x0039 }, /* R54 - LOUT2 (SPK) volume ctrl */ + { 55, 0x0039 }, /* R55 - ROUT2 (SPK) volume ctrl */ + { 56, 0x0001 }, /* R56 - OUT3 mixer ctrl */ + { 57, 0x0001 }, /* R57 - OUT4 (MONO) mix ctrl */ + { 60, 0x0004 }, /* R60 - OUTPUT ctrl */ + { 61, 0x0000 }, /* R61 - BIAS CTRL */ +}; + +static bool wm8985_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8985_SOFTWARE_RESET: + case WM8985_POWER_MANAGEMENT_1: + case WM8985_POWER_MANAGEMENT_2: + case WM8985_POWER_MANAGEMENT_3: + case WM8985_AUDIO_INTERFACE: + case WM8985_COMPANDING_CONTROL: + case WM8985_CLOCK_GEN_CONTROL: + case WM8985_ADDITIONAL_CONTROL: + case WM8985_GPIO_CONTROL: + case WM8985_JACK_DETECT_CONTROL_1: + case WM8985_DAC_CONTROL: + case WM8985_LEFT_DAC_DIGITAL_VOL: + case WM8985_RIGHT_DAC_DIGITAL_VOL: + case WM8985_JACK_DETECT_CONTROL_2: + case WM8985_ADC_CONTROL: + case WM8985_LEFT_ADC_DIGITAL_VOL: + case WM8985_RIGHT_ADC_DIGITAL_VOL: + case WM8985_EQ1_LOW_SHELF: + case WM8985_EQ2_PEAK_1: + case WM8985_EQ3_PEAK_2: + case WM8985_EQ4_PEAK_3: + case WM8985_EQ5_HIGH_SHELF: + case WM8985_DAC_LIMITER_1: + case WM8985_DAC_LIMITER_2: + case WM8985_NOTCH_FILTER_1: + case WM8985_NOTCH_FILTER_2: + case WM8985_NOTCH_FILTER_3: + case WM8985_NOTCH_FILTER_4: + case WM8985_ALC_CONTROL_1: + case WM8985_ALC_CONTROL_2: + case WM8985_ALC_CONTROL_3: + case WM8985_NOISE_GATE: + case WM8985_PLL_N: + case WM8985_PLL_K_1: + case WM8985_PLL_K_2: + case WM8985_PLL_K_3: + case WM8985_3D_CONTROL: + case WM8985_OUT4_TO_ADC: + case WM8985_BEEP_CONTROL: + case WM8985_INPUT_CTRL: + case WM8985_LEFT_INP_PGA_GAIN_CTRL: + case WM8985_RIGHT_INP_PGA_GAIN_CTRL: + case WM8985_LEFT_ADC_BOOST_CTRL: + case WM8985_RIGHT_ADC_BOOST_CTRL: + case WM8985_OUTPUT_CTRL0: + case WM8985_LEFT_MIXER_CTRL: + case WM8985_RIGHT_MIXER_CTRL: + case WM8985_LOUT1_HP_VOLUME_CTRL: + case WM8985_ROUT1_HP_VOLUME_CTRL: + case WM8985_LOUT2_SPK_VOLUME_CTRL: + case WM8985_ROUT2_SPK_VOLUME_CTRL: + case WM8985_OUT3_MIXER_CTRL: + case WM8985_OUT4_MONO_MIX_CTRL: + case WM8985_OUTPUT_CTRL1: + case WM8985_BIAS_CTRL: + return true; + default: + return false; + } +} + +/* + * latch bit 8 of these registers to ensure instant + * volume updates + */ +static const int volume_update_regs[] = { + WM8985_LEFT_DAC_DIGITAL_VOL, + WM8985_RIGHT_DAC_DIGITAL_VOL, + WM8985_LEFT_ADC_DIGITAL_VOL, + WM8985_RIGHT_ADC_DIGITAL_VOL, + WM8985_LOUT2_SPK_VOLUME_CTRL, + WM8985_ROUT2_SPK_VOLUME_CTRL, + WM8985_LOUT1_HP_VOLUME_CTRL, + WM8985_ROUT1_HP_VOLUME_CTRL, + WM8985_LEFT_INP_PGA_GAIN_CTRL, + WM8985_RIGHT_INP_PGA_GAIN_CTRL +}; + +struct wm8985_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8985_NUM_SUPPLIES]; + enum wm8985_type dev_type; + unsigned int sysclk; + unsigned int bclk; +}; + +static const struct { + int div; + int ratio; +} fs_ratios[] = { + { 10, 128 }, + { 15, 192 }, + { 20, 256 }, + { 30, 384 }, + { 40, 512 }, + { 60, 768 }, + { 80, 1024 }, + { 120, 1536 } +}; + +static const int srates[] = { 48000, 32000, 24000, 16000, 12000, 8000 }; + +static const int bclk_divs[] = { + 1, 2, 4, 8, 16, 32 +}; + +static int eqmode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int eqmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(lim_thresh_tlv, -600, 100, 0); +static const DECLARE_TLV_DB_SCALE(lim_boost_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(alc_min_tlv, -1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(alc_max_tlv, -675, 600, 0); +static const DECLARE_TLV_DB_SCALE(alc_tar_tlv, -2250, 150, 0); +static const DECLARE_TLV_DB_SCALE(pga_vol_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(boost_tlv, -1200, 300, 1); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(aux_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(pga_boost_tlv, 0, 2000, 0); + +static const char *alc_sel_text[] = { "Off", "Right", "Left", "Stereo" }; +static SOC_ENUM_SINGLE_DECL(alc_sel, WM8985_ALC_CONTROL_1, 7, alc_sel_text); + +static const char *alc_mode_text[] = { "ALC", "Limiter" }; +static SOC_ENUM_SINGLE_DECL(alc_mode, WM8985_ALC_CONTROL_3, 8, alc_mode_text); + +static const char *filter_mode_text[] = { "Audio", "Application" }; +static SOC_ENUM_SINGLE_DECL(filter_mode, WM8985_ADC_CONTROL, 7, + filter_mode_text); + +static const char *eq_bw_text[] = { "Narrow", "Wide" }; +static const char *eqmode_text[] = { "Capture", "Playback" }; +static SOC_ENUM_SINGLE_EXT_DECL(eqmode, eqmode_text); + +static const char *eq1_cutoff_text[] = { + "80Hz", "105Hz", "135Hz", "175Hz" +}; +static SOC_ENUM_SINGLE_DECL(eq1_cutoff, WM8985_EQ1_LOW_SHELF, 5, + eq1_cutoff_text); +static const char *eq2_cutoff_text[] = { + "230Hz", "300Hz", "385Hz", "500Hz" +}; +static SOC_ENUM_SINGLE_DECL(eq2_bw, WM8985_EQ2_PEAK_1, 8, eq_bw_text); +static SOC_ENUM_SINGLE_DECL(eq2_cutoff, WM8985_EQ2_PEAK_1, 5, eq2_cutoff_text); +static const char *eq3_cutoff_text[] = { + "650Hz", "850Hz", "1.1kHz", "1.4kHz" +}; +static SOC_ENUM_SINGLE_DECL(eq3_bw, WM8985_EQ3_PEAK_2, 8, eq_bw_text); +static SOC_ENUM_SINGLE_DECL(eq3_cutoff, WM8985_EQ3_PEAK_2, 5, + eq3_cutoff_text); +static const char *eq4_cutoff_text[] = { + "1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" +}; +static SOC_ENUM_SINGLE_DECL(eq4_bw, WM8985_EQ4_PEAK_3, 8, eq_bw_text); +static SOC_ENUM_SINGLE_DECL(eq4_cutoff, WM8985_EQ4_PEAK_3, 5, eq4_cutoff_text); +static const char *eq5_cutoff_text[] = { + "5.3kHz", "6.9kHz", "9kHz", "11.7kHz" +}; +static SOC_ENUM_SINGLE_DECL(eq5_cutoff, WM8985_EQ5_HIGH_SHELF, 5, + eq5_cutoff_text); + +static const char *speaker_mode_text[] = { "Class A/B", "Class D" }; +static SOC_ENUM_SINGLE_DECL(speaker_mode, 0x17, 8, speaker_mode_text); + +static const char *depth_3d_text[] = { + "Off", + "6.67%", + "13.3%", + "20%", + "26.7%", + "33.3%", + "40%", + "46.6%", + "53.3%", + "60%", + "66.7%", + "73.3%", + "80%", + "86.7%", + "93.3%", + "100%" +}; +static SOC_ENUM_SINGLE_DECL(depth_3d, WM8985_3D_CONTROL, 0, depth_3d_text); + +static const struct snd_kcontrol_new wm8985_common_snd_controls[] = { + SOC_SINGLE("Digital Loopback Switch", WM8985_COMPANDING_CONTROL, + 0, 1, 0), + + SOC_ENUM("ALC Capture Function", alc_sel), + SOC_SINGLE_TLV("ALC Capture Max Volume", WM8985_ALC_CONTROL_1, + 3, 7, 0, alc_max_tlv), + SOC_SINGLE_TLV("ALC Capture Min Volume", WM8985_ALC_CONTROL_1, + 0, 7, 0, alc_min_tlv), + SOC_SINGLE_TLV("ALC Capture Target Volume", WM8985_ALC_CONTROL_2, + 0, 15, 0, alc_tar_tlv), + SOC_SINGLE("ALC Capture Attack", WM8985_ALC_CONTROL_3, 0, 10, 0), + SOC_SINGLE("ALC Capture Hold", WM8985_ALC_CONTROL_2, 4, 10, 0), + SOC_SINGLE("ALC Capture Decay", WM8985_ALC_CONTROL_3, 4, 10, 0), + SOC_ENUM("ALC Mode", alc_mode), + SOC_SINGLE("ALC Capture NG Switch", WM8985_NOISE_GATE, + 3, 1, 0), + SOC_SINGLE("ALC Capture NG Threshold", WM8985_NOISE_GATE, + 0, 7, 1), + + SOC_DOUBLE_R_TLV("Capture Volume", WM8985_LEFT_ADC_DIGITAL_VOL, + WM8985_RIGHT_ADC_DIGITAL_VOL, 0, 255, 0, adc_tlv), + SOC_DOUBLE_R("Capture PGA ZC Switch", WM8985_LEFT_INP_PGA_GAIN_CTRL, + WM8985_RIGHT_INP_PGA_GAIN_CTRL, 7, 1, 0), + SOC_DOUBLE_R_TLV("Capture PGA Volume", WM8985_LEFT_INP_PGA_GAIN_CTRL, + WM8985_RIGHT_INP_PGA_GAIN_CTRL, 0, 63, 0, pga_vol_tlv), + + SOC_DOUBLE_R_TLV("Capture PGA Boost Volume", + WM8985_LEFT_ADC_BOOST_CTRL, WM8985_RIGHT_ADC_BOOST_CTRL, + 8, 1, 0, pga_boost_tlv), + + SOC_DOUBLE("ADC Inversion Switch", WM8985_ADC_CONTROL, 0, 1, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 8, 1, 0), + + SOC_DOUBLE_R_TLV("Playback Volume", WM8985_LEFT_DAC_DIGITAL_VOL, + WM8985_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), + + SOC_SINGLE("DAC Playback Limiter Switch", WM8985_DAC_LIMITER_1, 8, 1, 0), + SOC_SINGLE("DAC Playback Limiter Decay", WM8985_DAC_LIMITER_1, 4, 10, 0), + SOC_SINGLE("DAC Playback Limiter Attack", WM8985_DAC_LIMITER_1, 0, 11, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8985_DAC_LIMITER_2, + 4, 7, 1, lim_thresh_tlv), + SOC_SINGLE_TLV("DAC Playback Limiter Boost Volume", WM8985_DAC_LIMITER_2, + 0, 12, 0, lim_boost_tlv), + SOC_DOUBLE("DAC Inversion Switch", WM8985_DAC_CONTROL, 0, 1, 1, 0), + SOC_SINGLE("DAC Auto Mute Switch", WM8985_DAC_CONTROL, 2, 1, 0), + SOC_SINGLE("DAC 128x Oversampling Switch", WM8985_DAC_CONTROL, 3, 1, 0), + + SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8985_LOUT1_HP_VOLUME_CTRL, + WM8985_ROUT1_HP_VOLUME_CTRL, 0, 63, 0, out_tlv), + SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8985_LOUT1_HP_VOLUME_CTRL, + WM8985_ROUT1_HP_VOLUME_CTRL, 7, 1, 0), + SOC_DOUBLE_R("Headphone Switch", WM8985_LOUT1_HP_VOLUME_CTRL, + WM8985_ROUT1_HP_VOLUME_CTRL, 6, 1, 1), + + SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8985_LOUT2_SPK_VOLUME_CTRL, + WM8985_ROUT2_SPK_VOLUME_CTRL, 0, 63, 0, out_tlv), + SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8985_LOUT2_SPK_VOLUME_CTRL, + WM8985_ROUT2_SPK_VOLUME_CTRL, 7, 1, 0), + SOC_DOUBLE_R("Speaker Switch", WM8985_LOUT2_SPK_VOLUME_CTRL, + WM8985_ROUT2_SPK_VOLUME_CTRL, 6, 1, 1), + + SOC_SINGLE("High Pass Filter Switch", WM8985_ADC_CONTROL, 8, 1, 0), + SOC_ENUM("High Pass Filter Mode", filter_mode), + SOC_SINGLE("High Pass Filter Cutoff", WM8985_ADC_CONTROL, 4, 7, 0), + + SOC_DOUBLE_R_TLV("Input PGA Bypass Volume", + WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 2, 7, 0, + bypass_tlv), + + SOC_ENUM_EXT("Equalizer Function", eqmode, eqmode_get, eqmode_put), + SOC_ENUM("EQ1 Cutoff", eq1_cutoff), + SOC_SINGLE_TLV("EQ1 Volume", WM8985_EQ1_LOW_SHELF, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ2 Bandwidth", eq2_bw), + SOC_ENUM("EQ2 Cutoff", eq2_cutoff), + SOC_SINGLE_TLV("EQ2 Volume", WM8985_EQ2_PEAK_1, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ3 Bandwidth", eq3_bw), + SOC_ENUM("EQ3 Cutoff", eq3_cutoff), + SOC_SINGLE_TLV("EQ3 Volume", WM8985_EQ3_PEAK_2, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ4 Bandwidth", eq4_bw), + SOC_ENUM("EQ4 Cutoff", eq4_cutoff), + SOC_SINGLE_TLV("EQ4 Volume", WM8985_EQ4_PEAK_3, 0, 24, 1, eq_tlv), + SOC_ENUM("EQ5 Cutoff", eq5_cutoff), + SOC_SINGLE_TLV("EQ5 Volume", WM8985_EQ5_HIGH_SHELF, 0, 24, 1, eq_tlv), + + SOC_ENUM("3D Depth", depth_3d), +}; + +static const struct snd_kcontrol_new wm8985_specific_snd_controls[] = { + SOC_DOUBLE_R_TLV("Aux Bypass Volume", + WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 6, 7, 0, + aux_tlv), + + SOC_ENUM("Speaker Mode", speaker_mode) +}; + +static const struct snd_kcontrol_new left_out_mixer[] = { + SOC_DAPM_SINGLE("Line Switch", WM8985_LEFT_MIXER_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("PCM Switch", WM8985_LEFT_MIXER_CTRL, 0, 1, 0), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE("Aux Switch", WM8985_LEFT_MIXER_CTRL, 5, 1, 0), +}; + +static const struct snd_kcontrol_new right_out_mixer[] = { + SOC_DAPM_SINGLE("Line Switch", WM8985_RIGHT_MIXER_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("PCM Switch", WM8985_RIGHT_MIXER_CTRL, 0, 1, 0), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE("Aux Switch", WM8985_RIGHT_MIXER_CTRL, 5, 1, 0), +}; + +static const struct snd_kcontrol_new left_input_mixer[] = { + SOC_DAPM_SINGLE("L2 Switch", WM8985_INPUT_CTRL, 2, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8985_INPUT_CTRL, 1, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8985_INPUT_CTRL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_input_mixer[] = { + SOC_DAPM_SINGLE("R2 Switch", WM8985_INPUT_CTRL, 6, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8985_INPUT_CTRL, 5, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8985_INPUT_CTRL, 4, 1, 0), +}; + +static const struct snd_kcontrol_new left_boost_mixer[] = { + SOC_DAPM_SINGLE_TLV("L2 Volume", WM8985_LEFT_ADC_BOOST_CTRL, + 4, 7, 0, boost_tlv), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE_TLV("AUXL Volume", WM8985_LEFT_ADC_BOOST_CTRL, + 0, 7, 0, boost_tlv) +}; + +static const struct snd_kcontrol_new right_boost_mixer[] = { + SOC_DAPM_SINGLE_TLV("R2 Volume", WM8985_RIGHT_ADC_BOOST_CTRL, + 4, 7, 0, boost_tlv), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE_TLV("AUXR Volume", WM8985_RIGHT_ADC_BOOST_CTRL, + 0, 7, 0, boost_tlv) +}; + +static const struct snd_soc_dapm_widget wm8985_common_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8985_POWER_MANAGEMENT_3, + 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8985_POWER_MANAGEMENT_3, + 1, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8985_POWER_MANAGEMENT_2, + 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8985_POWER_MANAGEMENT_2, + 1, 0), + + SND_SOC_DAPM_MIXER("Left Input Mixer", WM8985_POWER_MANAGEMENT_2, + 2, 0, left_input_mixer, ARRAY_SIZE(left_input_mixer)), + SND_SOC_DAPM_MIXER("Right Input Mixer", WM8985_POWER_MANAGEMENT_2, + 3, 0, right_input_mixer, ARRAY_SIZE(right_input_mixer)), + + SND_SOC_DAPM_PGA("Left Capture PGA", WM8985_LEFT_INP_PGA_GAIN_CTRL, + 6, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Capture PGA", WM8985_RIGHT_INP_PGA_GAIN_CTRL, + 6, 1, NULL, 0), + + SND_SOC_DAPM_PGA("Left Headphone Out", WM8985_POWER_MANAGEMENT_2, + 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Headphone Out", WM8985_POWER_MANAGEMENT_2, + 8, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Left Speaker Out", WM8985_POWER_MANAGEMENT_3, + 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Speaker Out", WM8985_POWER_MANAGEMENT_3, + 6, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Mic Bias", WM8985_POWER_MANAGEMENT_1, 4, 0, + NULL, 0), + + SND_SOC_DAPM_INPUT("LIN"), + SND_SOC_DAPM_INPUT("LIP"), + SND_SOC_DAPM_INPUT("RIN"), + SND_SOC_DAPM_INPUT("RIP"), + SND_SOC_DAPM_INPUT("L2"), + SND_SOC_DAPM_INPUT("R2"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR") +}; + +static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, + 2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)), + SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, + 3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)), + + SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)), + SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)), + + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), +}; + +static const struct snd_soc_dapm_widget wm8758_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, + 2, 0, left_out_mixer, + ARRAY_SIZE(left_out_mixer) - 1), + SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, + 3, 0, right_out_mixer, + ARRAY_SIZE(right_out_mixer) - 1), + + SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 4, 0, left_boost_mixer, + ARRAY_SIZE(left_boost_mixer) - 1), + SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 5, 0, right_boost_mixer, + ARRAY_SIZE(right_boost_mixer) - 1), +}; + +static const struct snd_soc_dapm_route wm8985_common_dapm_routes[] = { + { "Right Output Mixer", "PCM Switch", "Right DAC" }, + { "Right Output Mixer", "Line Switch", "Right Boost Mixer" }, + + { "Left Output Mixer", "PCM Switch", "Left DAC" }, + { "Left Output Mixer", "Line Switch", "Left Boost Mixer" }, + + { "Right Headphone Out", NULL, "Right Output Mixer" }, + { "HPR", NULL, "Right Headphone Out" }, + + { "Left Headphone Out", NULL, "Left Output Mixer" }, + { "HPL", NULL, "Left Headphone Out" }, + + { "Right Speaker Out", NULL, "Right Output Mixer" }, + { "SPKR", NULL, "Right Speaker Out" }, + + { "Left Speaker Out", NULL, "Left Output Mixer" }, + { "SPKL", NULL, "Left Speaker Out" }, + + { "Right ADC", NULL, "Right Boost Mixer" }, + + { "Right Boost Mixer", NULL, "Right Capture PGA" }, + { "Right Boost Mixer", "R2 Volume", "R2" }, + + { "Left ADC", NULL, "Left Boost Mixer" }, + + { "Left Boost Mixer", NULL, "Left Capture PGA" }, + { "Left Boost Mixer", "L2 Volume", "L2" }, + + { "Right Capture PGA", NULL, "Right Input Mixer" }, + { "Left Capture PGA", NULL, "Left Input Mixer" }, + + { "Right Input Mixer", "R2 Switch", "R2" }, + { "Right Input Mixer", "MicN Switch", "RIN" }, + { "Right Input Mixer", "MicP Switch", "RIP" }, + + { "Left Input Mixer", "L2 Switch", "L2" }, + { "Left Input Mixer", "MicN Switch", "LIN" }, + { "Left Input Mixer", "MicP Switch", "LIP" }, +}; +static const struct snd_soc_dapm_route wm8985_aux_dapm_routes[] = { + { "Right Output Mixer", "Aux Switch", "AUXR" }, + { "Left Output Mixer", "Aux Switch", "AUXL" }, + + { "Right Boost Mixer", "AUXR Volume", "AUXR" }, + { "Left Boost Mixer", "AUXL Volume", "AUXL" }, +}; + +static int wm8985_add_widgets(struct snd_soc_component *component) +{ + struct wm8985_priv *wm8985 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + switch (wm8985->dev_type) { + case WM8758: + snd_soc_dapm_new_controls(dapm, wm8758_dapm_widgets, + ARRAY_SIZE(wm8758_dapm_widgets)); + break; + + case WM8985: + snd_soc_add_component_controls(component, wm8985_specific_snd_controls, + ARRAY_SIZE(wm8985_specific_snd_controls)); + + snd_soc_dapm_new_controls(dapm, wm8985_dapm_widgets, + ARRAY_SIZE(wm8985_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, wm8985_aux_dapm_routes, + ARRAY_SIZE(wm8985_aux_dapm_routes)); + break; + } + + return 0; +} + +static int eqmode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg; + + reg = snd_soc_component_read32(component, WM8985_EQ1_LOW_SHELF); + if (reg & WM8985_EQ3DMODE) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + + return 0; +} + +static int eqmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int regpwr2, regpwr3; + unsigned int reg_eq; + + if (ucontrol->value.enumerated.item[0] != 0 + && ucontrol->value.enumerated.item[0] != 1) + return -EINVAL; + + reg_eq = snd_soc_component_read32(component, WM8985_EQ1_LOW_SHELF); + switch ((reg_eq & WM8985_EQ3DMODE) >> WM8985_EQ3DMODE_SHIFT) { + case 0: + if (!ucontrol->value.enumerated.item[0]) + return 0; + break; + case 1: + if (ucontrol->value.enumerated.item[0]) + return 0; + break; + } + + regpwr2 = snd_soc_component_read32(component, WM8985_POWER_MANAGEMENT_2); + regpwr3 = snd_soc_component_read32(component, WM8985_POWER_MANAGEMENT_3); + /* disable the DACs and ADCs */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_2, + WM8985_ADCENR_MASK | WM8985_ADCENL_MASK, 0); + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_3, + WM8985_DACENR_MASK | WM8985_DACENL_MASK, 0); + snd_soc_component_update_bits(component, WM8985_ADDITIONAL_CONTROL, + WM8985_M128ENB_MASK, WM8985_M128ENB); + /* set the desired eqmode */ + snd_soc_component_update_bits(component, WM8985_EQ1_LOW_SHELF, + WM8985_EQ3DMODE_MASK, + ucontrol->value.enumerated.item[0] + << WM8985_EQ3DMODE_SHIFT); + /* restore DAC/ADC configuration */ + snd_soc_component_write(component, WM8985_POWER_MANAGEMENT_2, regpwr2); + snd_soc_component_write(component, WM8985_POWER_MANAGEMENT_3, regpwr3); + return 0; +} + +static int wm8985_reset(struct snd_soc_component *component) +{ + return snd_soc_component_write(component, WM8985_SOFTWARE_RESET, 0x0); +} + +static int wm8985_dac_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + + return snd_soc_component_update_bits(component, WM8985_DAC_CONTROL, + WM8985_SOFTMUTE_MASK, + !!mute << WM8985_SOFTMUTE_SHIFT); +} + +static int wm8985_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component; + u16 format, master, bcp, lrp; + + component = dai->component; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + format = 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = 0x1; + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + format = 0x3; + break; + default: + dev_err(dai->dev, "Unknown dai format\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8985_AUDIO_INTERFACE, + WM8985_FMT_MASK, format << WM8985_FMT_SHIFT); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + default: + dev_err(dai->dev, "Unknown master/slave configuration\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8985_CLOCK_GEN_CONTROL, + WM8985_MS_MASK, master << WM8985_MS_SHIFT); + + /* frame inversion is not valid for dsp modes */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + case SND_SOC_DAIFMT_NB_IF: + return -EINVAL; + default: + break; + } + break; + default: + break; + } + + bcp = lrp = 0; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + bcp = lrp = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + bcp = 1; + break; + case SND_SOC_DAIFMT_NB_IF: + lrp = 1; + break; + default: + dev_err(dai->dev, "Unknown polarity configuration\n"); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8985_AUDIO_INTERFACE, + WM8985_LRP_MASK, lrp << WM8985_LRP_SHIFT); + snd_soc_component_update_bits(component, WM8985_AUDIO_INTERFACE, + WM8985_BCP_MASK, bcp << WM8985_BCP_SHIFT); + return 0; +} + +static int wm8985_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int i; + struct snd_soc_component *component; + struct wm8985_priv *wm8985; + u16 blen, srate_idx; + unsigned int tmp; + int srate_best; + + component = dai->component; + wm8985 = snd_soc_component_get_drvdata(component); + + wm8985->bclk = snd_soc_params_to_bclk(params); + if ((int)wm8985->bclk < 0) + return wm8985->bclk; + + switch (params_width(params)) { + case 16: + blen = 0x0; + break; + case 20: + blen = 0x1; + break; + case 24: + blen = 0x2; + break; + case 32: + blen = 0x3; + break; + default: + dev_err(dai->dev, "Unsupported word length %u\n", + params_width(params)); + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8985_AUDIO_INTERFACE, + WM8985_WL_MASK, blen << WM8985_WL_SHIFT); + + /* + * match to the nearest possible sample rate and rely + * on the array index to configure the SR register + */ + srate_idx = 0; + srate_best = abs(srates[0] - params_rate(params)); + for (i = 1; i < ARRAY_SIZE(srates); ++i) { + if (abs(srates[i] - params_rate(params)) >= srate_best) + continue; + srate_idx = i; + srate_best = abs(srates[i] - params_rate(params)); + } + + dev_dbg(dai->dev, "Selected SRATE = %d\n", srates[srate_idx]); + snd_soc_component_update_bits(component, WM8985_ADDITIONAL_CONTROL, + WM8985_SR_MASK, srate_idx << WM8985_SR_SHIFT); + + dev_dbg(dai->dev, "Target BCLK = %uHz\n", wm8985->bclk); + dev_dbg(dai->dev, "SYSCLK = %uHz\n", wm8985->sysclk); + + for (i = 0; i < ARRAY_SIZE(fs_ratios); ++i) { + if (wm8985->sysclk / params_rate(params) + == fs_ratios[i].ratio) + break; + } + + if (i == ARRAY_SIZE(fs_ratios)) { + dev_err(dai->dev, "Unable to configure MCLK ratio %u/%u\n", + wm8985->sysclk, params_rate(params)); + return -EINVAL; + } + + dev_dbg(dai->dev, "MCLK ratio = %dfs\n", fs_ratios[i].ratio); + snd_soc_component_update_bits(component, WM8985_CLOCK_GEN_CONTROL, + WM8985_MCLKDIV_MASK, i << WM8985_MCLKDIV_SHIFT); + + /* select the appropriate bclk divider */ + tmp = (wm8985->sysclk / fs_ratios[i].div) * 10; + for (i = 0; i < ARRAY_SIZE(bclk_divs); ++i) { + if (wm8985->bclk == tmp / bclk_divs[i]) + break; + } + + if (i == ARRAY_SIZE(bclk_divs)) { + dev_err(dai->dev, "No matching BCLK divider found\n"); + return -EINVAL; + } + + dev_dbg(dai->dev, "BCLK div = %d\n", i); + snd_soc_component_update_bits(component, WM8985_CLOCK_GEN_CONTROL, + WM8985_BCLKDIV_MASK, i << WM8985_BCLKDIV_SHIFT); + return 0; +} + +struct pll_div { + u32 div2:1; + u32 n:4; + u32 k:24; +}; + +#define FIXED_PLL_SIZE ((1ULL << 24) * 10) +static int pll_factors(struct pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned long int K, Ndiv, Nmod; + + pll_div->div2 = 0; + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } + + if (Ndiv < 6 || Ndiv > 12) { + printk(KERN_ERR "%s: WM8985 N value is not within" + " the recommended range: %lu\n", __func__, Ndiv); + return -EINVAL; + } + pll_div->n = Ndiv; + + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (u64)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xffffffff; + if ((K % 10) >= 5) + K += 5; + K /= 10; + pll_div->k = K; + + return 0; +} + +static int wm8985_set_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + int ret; + struct snd_soc_component *component; + struct pll_div pll_div; + + component = dai->component; + if (!freq_in || !freq_out) { + /* disable the PLL */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_PLLEN_MASK, 0); + } else { + ret = pll_factors(&pll_div, freq_out * 4 * 2, freq_in); + if (ret) + return ret; + + /* set PLLN and PRESCALE */ + snd_soc_component_write(component, WM8985_PLL_N, + (pll_div.div2 << WM8985_PLL_PRESCALE_SHIFT) + | pll_div.n); + /* set PLLK */ + snd_soc_component_write(component, WM8985_PLL_K_3, pll_div.k & 0x1ff); + snd_soc_component_write(component, WM8985_PLL_K_2, (pll_div.k >> 9) & 0x1ff); + snd_soc_component_write(component, WM8985_PLL_K_1, (pll_div.k >> 18)); + /* set the source of the clock to be the PLL */ + snd_soc_component_update_bits(component, WM8985_CLOCK_GEN_CONTROL, + WM8985_CLKSEL_MASK, WM8985_CLKSEL); + /* enable the PLL */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_PLLEN_MASK, WM8985_PLLEN); + } + return 0; +} + +static int wm8985_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component; + struct wm8985_priv *wm8985; + + component = dai->component; + wm8985 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8985_CLKSRC_MCLK: + snd_soc_component_update_bits(component, WM8985_CLOCK_GEN_CONTROL, + WM8985_CLKSEL_MASK, 0); + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_PLLEN_MASK, 0); + break; + case WM8985_CLKSRC_PLL: + snd_soc_component_update_bits(component, WM8985_CLOCK_GEN_CONTROL, + WM8985_CLKSEL_MASK, WM8985_CLKSEL); + break; + default: + dev_err(dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } + + wm8985->sysclk = freq; + return 0; +} + +static int wm8985_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + int ret; + struct wm8985_priv *wm8985; + + wm8985 = snd_soc_component_get_drvdata(component); + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + /* VMID at 75k */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_VMIDSEL_MASK, + 1 << WM8985_VMIDSEL_SHIFT); + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies), + wm8985->supplies); + if (ret) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + regcache_sync(wm8985->regmap); + + /* enable anti-pop features */ + snd_soc_component_update_bits(component, WM8985_OUT4_TO_ADC, + WM8985_POBCTRL_MASK, + WM8985_POBCTRL); + /* enable thermal shutdown */ + snd_soc_component_update_bits(component, WM8985_OUTPUT_CTRL0, + WM8985_TSDEN_MASK, WM8985_TSDEN); + snd_soc_component_update_bits(component, WM8985_OUTPUT_CTRL0, + WM8985_TSOPCTRL_MASK, + WM8985_TSOPCTRL); + /* enable BIASEN */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_BIASEN_MASK, WM8985_BIASEN); + /* VMID at 75k */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_VMIDSEL_MASK, + 1 << WM8985_VMIDSEL_SHIFT); + msleep(500); + /* disable anti-pop features */ + snd_soc_component_update_bits(component, WM8985_OUT4_TO_ADC, + WM8985_POBCTRL_MASK, 0); + } + /* VMID at 300k */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_VMIDSEL_MASK, + 2 << WM8985_VMIDSEL_SHIFT); + break; + case SND_SOC_BIAS_OFF: + /* disable thermal shutdown */ + snd_soc_component_update_bits(component, WM8985_OUTPUT_CTRL0, + WM8985_TSOPCTRL_MASK, 0); + snd_soc_component_update_bits(component, WM8985_OUTPUT_CTRL0, + WM8985_TSDEN_MASK, 0); + /* disable VMIDSEL and BIASEN */ + snd_soc_component_update_bits(component, WM8985_POWER_MANAGEMENT_1, + WM8985_VMIDSEL_MASK | WM8985_BIASEN_MASK, + 0); + snd_soc_component_write(component, WM8985_POWER_MANAGEMENT_1, 0); + snd_soc_component_write(component, WM8985_POWER_MANAGEMENT_2, 0); + snd_soc_component_write(component, WM8985_POWER_MANAGEMENT_3, 0); + + regcache_mark_dirty(wm8985->regmap); + + regulator_bulk_disable(ARRAY_SIZE(wm8985->supplies), + wm8985->supplies); + break; + } + + return 0; +} + +static int wm8985_probe(struct snd_soc_component *component) +{ + size_t i; + struct wm8985_priv *wm8985; + int ret; + + wm8985 = snd_soc_component_get_drvdata(component); + + for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++) + wm8985->supplies[i].supply = wm8985_supply_names[i]; + + ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8985->supplies), + wm8985->supplies); + if (ret) { + dev_err(component->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies), + wm8985->supplies); + if (ret) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = wm8985_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + goto err_reg_enable; + } + + /* latch volume update bits */ + for (i = 0; i < ARRAY_SIZE(volume_update_regs); ++i) + snd_soc_component_update_bits(component, volume_update_regs[i], + 0x100, 0x100); + /* enable BIASCUT */ + snd_soc_component_update_bits(component, WM8985_BIAS_CTRL, WM8985_BIASCUT, + WM8985_BIASCUT); + + wm8985_add_widgets(component); + + return 0; + +err_reg_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8985->supplies), wm8985->supplies); + return ret; +} + +static const struct snd_soc_dai_ops wm8985_dai_ops = { + .digital_mute = wm8985_dac_mute, + .hw_params = wm8985_hw_params, + .set_fmt = wm8985_set_fmt, + .set_sysclk = wm8985_set_sysclk, + .set_pll = wm8985_set_pll +}; + +#define WM8985_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm8985_dai = { + .name = "wm8985-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8985_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8985_FORMATS, + }, + .ops = &wm8985_dai_ops, + .symmetric_rates = 1 +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8985 = { + .probe = wm8985_probe, + .set_bias_level = wm8985_set_bias_level, + .controls = wm8985_common_snd_controls, + .num_controls = ARRAY_SIZE(wm8985_common_snd_controls), + .dapm_widgets = wm8985_common_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8985_common_dapm_widgets), + .dapm_routes = wm8985_common_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8985_common_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8985_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = WM8985_MAX_REGISTER, + .writeable_reg = wm8985_writeable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8985_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8985_reg_defaults), +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8985_spi_probe(struct spi_device *spi) +{ + struct wm8985_priv *wm8985; + int ret; + + wm8985 = devm_kzalloc(&spi->dev, sizeof *wm8985, GFP_KERNEL); + if (!wm8985) + return -ENOMEM; + + spi_set_drvdata(spi, wm8985); + + wm8985->dev_type = WM8985; + + wm8985->regmap = devm_regmap_init_spi(spi, &wm8985_regmap); + if (IS_ERR(wm8985->regmap)) { + ret = PTR_ERR(wm8985->regmap); + dev_err(&spi->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8985, &wm8985_dai, 1); + return ret; +} + +static struct spi_driver wm8985_spi_driver = { + .driver = { + .name = "wm8985", + }, + .probe = wm8985_spi_probe, +}; +#endif + +#if IS_ENABLED(CONFIG_I2C) +static int wm8985_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8985_priv *wm8985; + int ret; + + wm8985 = devm_kzalloc(&i2c->dev, sizeof *wm8985, GFP_KERNEL); + if (!wm8985) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8985); + + wm8985->dev_type = id->driver_data; + + wm8985->regmap = devm_regmap_init_i2c(i2c, &wm8985_regmap); + if (IS_ERR(wm8985->regmap)) { + ret = PTR_ERR(wm8985->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8985, &wm8985_dai, 1); + return ret; +} + +static const struct i2c_device_id wm8985_i2c_id[] = { + { "wm8985", WM8985 }, + { "wm8758", WM8758 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8985_i2c_id); + +static struct i2c_driver wm8985_i2c_driver = { + .driver = { + .name = "wm8985", + }, + .probe = wm8985_i2c_probe, + .id_table = wm8985_i2c_id +}; +#endif + +static int __init wm8985_modinit(void) +{ + int ret = 0; + +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8985_i2c_driver); + if (ret) { + printk(KERN_ERR "Failed to register wm8985 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8985_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register wm8985 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8985_modinit); + +static void __exit wm8985_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8985_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8985_spi_driver); +#endif +} +module_exit(wm8985_exit); + +MODULE_DESCRIPTION("ASoC WM8985 / WM8758 driver"); +MODULE_AUTHOR("Dimitris Papastamos "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8985.h b/sound/soc/codecs/wm8985.h new file mode 100644 index 000000000..41b1048e3 --- /dev/null +++ b/sound/soc/codecs/wm8985.h @@ -0,0 +1,1083 @@ +/* + * wm8985.h -- WM8985 ASoC driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8985_H +#define _WM8985_H + +#define WM8985_SOFTWARE_RESET 0x00 +#define WM8985_POWER_MANAGEMENT_1 0x01 +#define WM8985_POWER_MANAGEMENT_2 0x02 +#define WM8985_POWER_MANAGEMENT_3 0x03 +#define WM8985_AUDIO_INTERFACE 0x04 +#define WM8985_COMPANDING_CONTROL 0x05 +#define WM8985_CLOCK_GEN_CONTROL 0x06 +#define WM8985_ADDITIONAL_CONTROL 0x07 +#define WM8985_GPIO_CONTROL 0x08 +#define WM8985_JACK_DETECT_CONTROL_1 0x09 +#define WM8985_DAC_CONTROL 0x0A +#define WM8985_LEFT_DAC_DIGITAL_VOL 0x0B +#define WM8985_RIGHT_DAC_DIGITAL_VOL 0x0C +#define WM8985_JACK_DETECT_CONTROL_2 0x0D +#define WM8985_ADC_CONTROL 0x0E +#define WM8985_LEFT_ADC_DIGITAL_VOL 0x0F +#define WM8985_RIGHT_ADC_DIGITAL_VOL 0x10 +#define WM8985_EQ1_LOW_SHELF 0x12 +#define WM8985_EQ2_PEAK_1 0x13 +#define WM8985_EQ3_PEAK_2 0x14 +#define WM8985_EQ4_PEAK_3 0x15 +#define WM8985_EQ5_HIGH_SHELF 0x16 +#define WM8985_DAC_LIMITER_1 0x18 +#define WM8985_DAC_LIMITER_2 0x19 +#define WM8985_NOTCH_FILTER_1 0x1B +#define WM8985_NOTCH_FILTER_2 0x1C +#define WM8985_NOTCH_FILTER_3 0x1D +#define WM8985_NOTCH_FILTER_4 0x1E +#define WM8985_ALC_CONTROL_1 0x20 +#define WM8985_ALC_CONTROL_2 0x21 +#define WM8985_ALC_CONTROL_3 0x22 +#define WM8985_NOISE_GATE 0x23 +#define WM8985_PLL_N 0x24 +#define WM8985_PLL_K_1 0x25 +#define WM8985_PLL_K_2 0x26 +#define WM8985_PLL_K_3 0x27 +#define WM8985_3D_CONTROL 0x29 +#define WM8985_OUT4_TO_ADC 0x2A +#define WM8985_BEEP_CONTROL 0x2B +#define WM8985_INPUT_CTRL 0x2C +#define WM8985_LEFT_INP_PGA_GAIN_CTRL 0x2D +#define WM8985_RIGHT_INP_PGA_GAIN_CTRL 0x2E +#define WM8985_LEFT_ADC_BOOST_CTRL 0x2F +#define WM8985_RIGHT_ADC_BOOST_CTRL 0x30 +#define WM8985_OUTPUT_CTRL0 0x31 +#define WM8985_LEFT_MIXER_CTRL 0x32 +#define WM8985_RIGHT_MIXER_CTRL 0x33 +#define WM8985_LOUT1_HP_VOLUME_CTRL 0x34 +#define WM8985_ROUT1_HP_VOLUME_CTRL 0x35 +#define WM8985_LOUT2_SPK_VOLUME_CTRL 0x36 +#define WM8985_ROUT2_SPK_VOLUME_CTRL 0x37 +#define WM8985_OUT3_MIXER_CTRL 0x38 +#define WM8985_OUT4_MONO_MIX_CTRL 0x39 +#define WM8985_OUTPUT_CTRL1 0x3C +#define WM8985_BIAS_CTRL 0x3D + +#define WM8985_REGISTER_COUNT 59 +#define WM8985_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM8985_SOFTWARE_RESET_MASK 0x01FF /* SOFTWARE_RESET - [8:0] */ +#define WM8985_SOFTWARE_RESET_SHIFT 0 /* SOFTWARE_RESET - [8:0] */ +#define WM8985_SOFTWARE_RESET_WIDTH 9 /* SOFTWARE_RESET - [8:0] */ + +/* + * R1 (0x01) - Power management 1 + */ +#define WM8985_OUT4MIXEN 0x0080 /* OUT4MIXEN */ +#define WM8985_OUT4MIXEN_MASK 0x0080 /* OUT4MIXEN */ +#define WM8985_OUT4MIXEN_SHIFT 7 /* OUT4MIXEN */ +#define WM8985_OUT4MIXEN_WIDTH 1 /* OUT4MIXEN */ +#define WM8985_OUT3MIXEN 0x0040 /* OUT3MIXEN */ +#define WM8985_OUT3MIXEN_MASK 0x0040 /* OUT3MIXEN */ +#define WM8985_OUT3MIXEN_SHIFT 6 /* OUT3MIXEN */ +#define WM8985_OUT3MIXEN_WIDTH 1 /* OUT3MIXEN */ +#define WM8985_PLLEN 0x0020 /* PLLEN */ +#define WM8985_PLLEN_MASK 0x0020 /* PLLEN */ +#define WM8985_PLLEN_SHIFT 5 /* PLLEN */ +#define WM8985_PLLEN_WIDTH 1 /* PLLEN */ +#define WM8985_MICBEN 0x0010 /* MICBEN */ +#define WM8985_MICBEN_MASK 0x0010 /* MICBEN */ +#define WM8985_MICBEN_SHIFT 4 /* MICBEN */ +#define WM8985_MICBEN_WIDTH 1 /* MICBEN */ +#define WM8985_BIASEN 0x0008 /* BIASEN */ +#define WM8985_BIASEN_MASK 0x0008 /* BIASEN */ +#define WM8985_BIASEN_SHIFT 3 /* BIASEN */ +#define WM8985_BIASEN_WIDTH 1 /* BIASEN */ +#define WM8985_BUFIOEN 0x0004 /* BUFIOEN */ +#define WM8985_BUFIOEN_MASK 0x0004 /* BUFIOEN */ +#define WM8985_BUFIOEN_SHIFT 2 /* BUFIOEN */ +#define WM8985_BUFIOEN_WIDTH 1 /* BUFIOEN */ +#define WM8985_VMIDSEL 0x0003 /* VMIDSEL */ +#define WM8985_VMIDSEL_MASK 0x0003 /* VMIDSEL - [1:0] */ +#define WM8985_VMIDSEL_SHIFT 0 /* VMIDSEL - [1:0] */ +#define WM8985_VMIDSEL_WIDTH 2 /* VMIDSEL - [1:0] */ + +/* + * R2 (0x02) - Power management 2 + */ +#define WM8985_ROUT1EN 0x0100 /* ROUT1EN */ +#define WM8985_ROUT1EN_MASK 0x0100 /* ROUT1EN */ +#define WM8985_ROUT1EN_SHIFT 8 /* ROUT1EN */ +#define WM8985_ROUT1EN_WIDTH 1 /* ROUT1EN */ +#define WM8985_LOUT1EN 0x0080 /* LOUT1EN */ +#define WM8985_LOUT1EN_MASK 0x0080 /* LOUT1EN */ +#define WM8985_LOUT1EN_SHIFT 7 /* LOUT1EN */ +#define WM8985_LOUT1EN_WIDTH 1 /* LOUT1EN */ +#define WM8985_SLEEP 0x0040 /* SLEEP */ +#define WM8985_SLEEP_MASK 0x0040 /* SLEEP */ +#define WM8985_SLEEP_SHIFT 6 /* SLEEP */ +#define WM8985_SLEEP_WIDTH 1 /* SLEEP */ +#define WM8985_BOOSTENR 0x0020 /* BOOSTENR */ +#define WM8985_BOOSTENR_MASK 0x0020 /* BOOSTENR */ +#define WM8985_BOOSTENR_SHIFT 5 /* BOOSTENR */ +#define WM8985_BOOSTENR_WIDTH 1 /* BOOSTENR */ +#define WM8985_BOOSTENL 0x0010 /* BOOSTENL */ +#define WM8985_BOOSTENL_MASK 0x0010 /* BOOSTENL */ +#define WM8985_BOOSTENL_SHIFT 4 /* BOOSTENL */ +#define WM8985_BOOSTENL_WIDTH 1 /* BOOSTENL */ +#define WM8985_INPGAENR 0x0008 /* INPGAENR */ +#define WM8985_INPGAENR_MASK 0x0008 /* INPGAENR */ +#define WM8985_INPGAENR_SHIFT 3 /* INPGAENR */ +#define WM8985_INPGAENR_WIDTH 1 /* INPGAENR */ +#define WM8985_INPPGAENL 0x0004 /* INPPGAENL */ +#define WM8985_INPPGAENL_MASK 0x0004 /* INPPGAENL */ +#define WM8985_INPPGAENL_SHIFT 2 /* INPPGAENL */ +#define WM8985_INPPGAENL_WIDTH 1 /* INPPGAENL */ +#define WM8985_ADCENR 0x0002 /* ADCENR */ +#define WM8985_ADCENR_MASK 0x0002 /* ADCENR */ +#define WM8985_ADCENR_SHIFT 1 /* ADCENR */ +#define WM8985_ADCENR_WIDTH 1 /* ADCENR */ +#define WM8985_ADCENL 0x0001 /* ADCENL */ +#define WM8985_ADCENL_MASK 0x0001 /* ADCENL */ +#define WM8985_ADCENL_SHIFT 0 /* ADCENL */ +#define WM8985_ADCENL_WIDTH 1 /* ADCENL */ + +/* + * R3 (0x03) - Power management 3 + */ +#define WM8985_OUT4EN 0x0100 /* OUT4EN */ +#define WM8985_OUT4EN_MASK 0x0100 /* OUT4EN */ +#define WM8985_OUT4EN_SHIFT 8 /* OUT4EN */ +#define WM8985_OUT4EN_WIDTH 1 /* OUT4EN */ +#define WM8985_OUT3EN 0x0080 /* OUT3EN */ +#define WM8985_OUT3EN_MASK 0x0080 /* OUT3EN */ +#define WM8985_OUT3EN_SHIFT 7 /* OUT3EN */ +#define WM8985_OUT3EN_WIDTH 1 /* OUT3EN */ +#define WM8985_ROUT2EN 0x0040 /* ROUT2EN */ +#define WM8985_ROUT2EN_MASK 0x0040 /* ROUT2EN */ +#define WM8985_ROUT2EN_SHIFT 6 /* ROUT2EN */ +#define WM8985_ROUT2EN_WIDTH 1 /* ROUT2EN */ +#define WM8985_LOUT2EN 0x0020 /* LOUT2EN */ +#define WM8985_LOUT2EN_MASK 0x0020 /* LOUT2EN */ +#define WM8985_LOUT2EN_SHIFT 5 /* LOUT2EN */ +#define WM8985_LOUT2EN_WIDTH 1 /* LOUT2EN */ +#define WM8985_RMIXEN 0x0008 /* RMIXEN */ +#define WM8985_RMIXEN_MASK 0x0008 /* RMIXEN */ +#define WM8985_RMIXEN_SHIFT 3 /* RMIXEN */ +#define WM8985_RMIXEN_WIDTH 1 /* RMIXEN */ +#define WM8985_LMIXEN 0x0004 /* LMIXEN */ +#define WM8985_LMIXEN_MASK 0x0004 /* LMIXEN */ +#define WM8985_LMIXEN_SHIFT 2 /* LMIXEN */ +#define WM8985_LMIXEN_WIDTH 1 /* LMIXEN */ +#define WM8985_DACENR 0x0002 /* DACENR */ +#define WM8985_DACENR_MASK 0x0002 /* DACENR */ +#define WM8985_DACENR_SHIFT 1 /* DACENR */ +#define WM8985_DACENR_WIDTH 1 /* DACENR */ +#define WM8985_DACENL 0x0001 /* DACENL */ +#define WM8985_DACENL_MASK 0x0001 /* DACENL */ +#define WM8985_DACENL_SHIFT 0 /* DACENL */ +#define WM8985_DACENL_WIDTH 1 /* DACENL */ + +/* + * R4 (0x04) - Audio Interface + */ +#define WM8985_BCP 0x0100 /* BCP */ +#define WM8985_BCP_MASK 0x0100 /* BCP */ +#define WM8985_BCP_SHIFT 8 /* BCP */ +#define WM8985_BCP_WIDTH 1 /* BCP */ +#define WM8985_LRP 0x0080 /* LRP */ +#define WM8985_LRP_MASK 0x0080 /* LRP */ +#define WM8985_LRP_SHIFT 7 /* LRP */ +#define WM8985_LRP_WIDTH 1 /* LRP */ +#define WM8985_WL_MASK 0x0060 /* WL - [6:5] */ +#define WM8985_WL_SHIFT 5 /* WL - [6:5] */ +#define WM8985_WL_WIDTH 2 /* WL - [6:5] */ +#define WM8985_FMT_MASK 0x0018 /* FMT - [4:3] */ +#define WM8985_FMT_SHIFT 3 /* FMT - [4:3] */ +#define WM8985_FMT_WIDTH 2 /* FMT - [4:3] */ +#define WM8985_DLRSWAP 0x0004 /* DLRSWAP */ +#define WM8985_DLRSWAP_MASK 0x0004 /* DLRSWAP */ +#define WM8985_DLRSWAP_SHIFT 2 /* DLRSWAP */ +#define WM8985_DLRSWAP_WIDTH 1 /* DLRSWAP */ +#define WM8985_ALRSWAP 0x0002 /* ALRSWAP */ +#define WM8985_ALRSWAP_MASK 0x0002 /* ALRSWAP */ +#define WM8985_ALRSWAP_SHIFT 1 /* ALRSWAP */ +#define WM8985_ALRSWAP_WIDTH 1 /* ALRSWAP */ +#define WM8985_MONO 0x0001 /* MONO */ +#define WM8985_MONO_MASK 0x0001 /* MONO */ +#define WM8985_MONO_SHIFT 0 /* MONO */ +#define WM8985_MONO_WIDTH 1 /* MONO */ + +/* + * R5 (0x05) - Companding control + */ +#define WM8985_WL8 0x0020 /* WL8 */ +#define WM8985_WL8_MASK 0x0020 /* WL8 */ +#define WM8985_WL8_SHIFT 5 /* WL8 */ +#define WM8985_WL8_WIDTH 1 /* WL8 */ +#define WM8985_DAC_COMP_MASK 0x0018 /* DAC_COMP - [4:3] */ +#define WM8985_DAC_COMP_SHIFT 3 /* DAC_COMP - [4:3] */ +#define WM8985_DAC_COMP_WIDTH 2 /* DAC_COMP - [4:3] */ +#define WM8985_ADC_COMP_MASK 0x0006 /* ADC_COMP - [2:1] */ +#define WM8985_ADC_COMP_SHIFT 1 /* ADC_COMP - [2:1] */ +#define WM8985_ADC_COMP_WIDTH 2 /* ADC_COMP - [2:1] */ +#define WM8985_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8985_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8985_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8985_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R6 (0x06) - Clock Gen control + */ +#define WM8985_CLKSEL 0x0100 /* CLKSEL */ +#define WM8985_CLKSEL_MASK 0x0100 /* CLKSEL */ +#define WM8985_CLKSEL_SHIFT 8 /* CLKSEL */ +#define WM8985_CLKSEL_WIDTH 1 /* CLKSEL */ +#define WM8985_MCLKDIV_MASK 0x00E0 /* MCLKDIV - [7:5] */ +#define WM8985_MCLKDIV_SHIFT 5 /* MCLKDIV - [7:5] */ +#define WM8985_MCLKDIV_WIDTH 3 /* MCLKDIV - [7:5] */ +#define WM8985_BCLKDIV_MASK 0x001C /* BCLKDIV - [4:2] */ +#define WM8985_BCLKDIV_SHIFT 2 /* BCLKDIV - [4:2] */ +#define WM8985_BCLKDIV_WIDTH 3 /* BCLKDIV - [4:2] */ +#define WM8985_MS 0x0001 /* MS */ +#define WM8985_MS_MASK 0x0001 /* MS */ +#define WM8985_MS_SHIFT 0 /* MS */ +#define WM8985_MS_WIDTH 1 /* MS */ + +/* + * R7 (0x07) - Additional control + */ +#define WM8985_M128ENB 0x0100 /* M128ENB */ +#define WM8985_M128ENB_MASK 0x0100 /* M128ENB */ +#define WM8985_M128ENB_SHIFT 8 /* M128ENB */ +#define WM8985_M128ENB_WIDTH 1 /* M128ENB */ +#define WM8985_DCLKDIV_MASK 0x00F0 /* DCLKDIV - [7:4] */ +#define WM8985_DCLKDIV_SHIFT 4 /* DCLKDIV - [7:4] */ +#define WM8985_DCLKDIV_WIDTH 4 /* DCLKDIV - [7:4] */ +#define WM8985_SR_MASK 0x000E /* SR - [3:1] */ +#define WM8985_SR_SHIFT 1 /* SR - [3:1] */ +#define WM8985_SR_WIDTH 3 /* SR - [3:1] */ +#define WM8985_SLOWCLKEN 0x0001 /* SLOWCLKEN */ +#define WM8985_SLOWCLKEN_MASK 0x0001 /* SLOWCLKEN */ +#define WM8985_SLOWCLKEN_SHIFT 0 /* SLOWCLKEN */ +#define WM8985_SLOWCLKEN_WIDTH 1 /* SLOWCLKEN */ + +/* + * R8 (0x08) - GPIO Control + */ +#define WM8985_GPIO1GP 0x0100 /* GPIO1GP */ +#define WM8985_GPIO1GP_MASK 0x0100 /* GPIO1GP */ +#define WM8985_GPIO1GP_SHIFT 8 /* GPIO1GP */ +#define WM8985_GPIO1GP_WIDTH 1 /* GPIO1GP */ +#define WM8985_GPIO1GPU 0x0080 /* GPIO1GPU */ +#define WM8985_GPIO1GPU_MASK 0x0080 /* GPIO1GPU */ +#define WM8985_GPIO1GPU_SHIFT 7 /* GPIO1GPU */ +#define WM8985_GPIO1GPU_WIDTH 1 /* GPIO1GPU */ +#define WM8985_GPIO1GPD 0x0040 /* GPIO1GPD */ +#define WM8985_GPIO1GPD_MASK 0x0040 /* GPIO1GPD */ +#define WM8985_GPIO1GPD_SHIFT 6 /* GPIO1GPD */ +#define WM8985_GPIO1GPD_WIDTH 1 /* GPIO1GPD */ +#define WM8758_OPCLKDIV_MASK 0x0030 /* OPCLKDIV - [1:0] */ +#define WM8758_OPCLKDIV_SHIFT 4 /* OPCLKDIV - [1:0] */ +#define WM8758_OPCLKDIV_WIDTH 2 /* OPCLKDIV - [1:0] */ +#define WM8985_GPIO1POL 0x0008 /* GPIO1POL */ +#define WM8985_GPIO1POL_MASK 0x0008 /* GPIO1POL */ +#define WM8985_GPIO1POL_SHIFT 3 /* GPIO1POL */ +#define WM8985_GPIO1POL_WIDTH 1 /* GPIO1POL */ +#define WM8985_GPIO1SEL_MASK 0x0007 /* GPIO1SEL - [2:0] */ +#define WM8985_GPIO1SEL_SHIFT 0 /* GPIO1SEL - [2:0] */ +#define WM8985_GPIO1SEL_WIDTH 3 /* GPIO1SEL - [2:0] */ + +/* + * R9 (0x09) - Jack Detect Control 1 + */ +#define WM8758_JD_VMID1_MASK 0x0100 /* JD_VMID1 */ +#define WM8758_JD_VMID1_SHIFT 8 /* JD_VMID1 */ +#define WM8758_JD_VMID1_WIDTH 1 /* JD_VMID1 */ +#define WM8758_JD_VMID0_MASK 0x0080 /* JD_VMID0 */ +#define WM8758_JD_VMID0_SHIFT 7 /* JD_VMID0 */ +#define WM8758_JD_VMID0_WIDTH 1 /* JD_VMID0 */ +#define WM8985_JD_EN 0x0040 /* JD_EN */ +#define WM8985_JD_EN_MASK 0x0040 /* JD_EN */ +#define WM8985_JD_EN_SHIFT 6 /* JD_EN */ +#define WM8985_JD_EN_WIDTH 1 /* JD_EN */ +#define WM8985_JD_SEL_MASK 0x0030 /* JD_SEL - [5:4] */ +#define WM8985_JD_SEL_SHIFT 4 /* JD_SEL - [5:4] */ +#define WM8985_JD_SEL_WIDTH 2 /* JD_SEL - [5:4] */ + +/* + * R10 (0x0A) - DAC Control + */ +#define WM8985_SOFTMUTE 0x0040 /* SOFTMUTE */ +#define WM8985_SOFTMUTE_MASK 0x0040 /* SOFTMUTE */ +#define WM8985_SOFTMUTE_SHIFT 6 /* SOFTMUTE */ +#define WM8985_SOFTMUTE_WIDTH 1 /* SOFTMUTE */ +#define WM8985_DACOSR128 0x0008 /* DACOSR128 */ +#define WM8985_DACOSR128_MASK 0x0008 /* DACOSR128 */ +#define WM8985_DACOSR128_SHIFT 3 /* DACOSR128 */ +#define WM8985_DACOSR128_WIDTH 1 /* DACOSR128 */ +#define WM8985_AMUTE 0x0004 /* AMUTE */ +#define WM8985_AMUTE_MASK 0x0004 /* AMUTE */ +#define WM8985_AMUTE_SHIFT 2 /* AMUTE */ +#define WM8985_AMUTE_WIDTH 1 /* AMUTE */ +#define WM8985_DACPOLR 0x0002 /* DACPOLR */ +#define WM8985_DACPOLR_MASK 0x0002 /* DACPOLR */ +#define WM8985_DACPOLR_SHIFT 1 /* DACPOLR */ +#define WM8985_DACPOLR_WIDTH 1 /* DACPOLR */ +#define WM8985_DACPOLL 0x0001 /* DACPOLL */ +#define WM8985_DACPOLL_MASK 0x0001 /* DACPOLL */ +#define WM8985_DACPOLL_SHIFT 0 /* DACPOLL */ +#define WM8985_DACPOLL_WIDTH 1 /* DACPOLL */ + +/* + * R11 (0x0B) - Left DAC digital Vol + */ +#define WM8985_DACVU 0x0100 /* DACVU */ +#define WM8985_DACVU_MASK 0x0100 /* DACVU */ +#define WM8985_DACVU_SHIFT 8 /* DACVU */ +#define WM8985_DACVU_WIDTH 1 /* DACVU */ +#define WM8985_DACVOLL_MASK 0x00FF /* DACVOLL - [7:0] */ +#define WM8985_DACVOLL_SHIFT 0 /* DACVOLL - [7:0] */ +#define WM8985_DACVOLL_WIDTH 8 /* DACVOLL - [7:0] */ + +/* + * R12 (0x0C) - Right DAC digital vol + */ +#define WM8985_DACVU 0x0100 /* DACVU */ +#define WM8985_DACVU_MASK 0x0100 /* DACVU */ +#define WM8985_DACVU_SHIFT 8 /* DACVU */ +#define WM8985_DACVU_WIDTH 1 /* DACVU */ +#define WM8985_DACVOLR_MASK 0x00FF /* DACVOLR - [7:0] */ +#define WM8985_DACVOLR_SHIFT 0 /* DACVOLR - [7:0] */ +#define WM8985_DACVOLR_WIDTH 8 /* DACVOLR - [7:0] */ + +/* + * R13 (0x0D) - Jack Detect Control 2 + */ +#define WM8985_JD_EN1_MASK 0x00F0 /* JD_EN1 - [7:4] */ +#define WM8985_JD_EN1_SHIFT 4 /* JD_EN1 - [7:4] */ +#define WM8985_JD_EN1_WIDTH 4 /* JD_EN1 - [7:4] */ +#define WM8985_JD_EN0_MASK 0x000F /* JD_EN0 - [3:0] */ +#define WM8985_JD_EN0_SHIFT 0 /* JD_EN0 - [3:0] */ +#define WM8985_JD_EN0_WIDTH 4 /* JD_EN0 - [3:0] */ + +/* + * R14 (0x0E) - ADC Control + */ +#define WM8985_HPFEN 0x0100 /* HPFEN */ +#define WM8985_HPFEN_MASK 0x0100 /* HPFEN */ +#define WM8985_HPFEN_SHIFT 8 /* HPFEN */ +#define WM8985_HPFEN_WIDTH 1 /* HPFEN */ +#define WM8985_HPFAPP 0x0080 /* HPFAPP */ +#define WM8985_HPFAPP_MASK 0x0080 /* HPFAPP */ +#define WM8985_HPFAPP_SHIFT 7 /* HPFAPP */ +#define WM8985_HPFAPP_WIDTH 1 /* HPFAPP */ +#define WM8985_HPFCUT_MASK 0x0070 /* HPFCUT - [6:4] */ +#define WM8985_HPFCUT_SHIFT 4 /* HPFCUT - [6:4] */ +#define WM8985_HPFCUT_WIDTH 3 /* HPFCUT - [6:4] */ +#define WM8985_ADCOSR128 0x0008 /* ADCOSR128 */ +#define WM8985_ADCOSR128_MASK 0x0008 /* ADCOSR128 */ +#define WM8985_ADCOSR128_SHIFT 3 /* ADCOSR128 */ +#define WM8985_ADCOSR128_WIDTH 1 /* ADCOSR128 */ +#define WM8985_ADCRPOL 0x0002 /* ADCRPOL */ +#define WM8985_ADCRPOL_MASK 0x0002 /* ADCRPOL */ +#define WM8985_ADCRPOL_SHIFT 1 /* ADCRPOL */ +#define WM8985_ADCRPOL_WIDTH 1 /* ADCRPOL */ +#define WM8985_ADCLPOL 0x0001 /* ADCLPOL */ +#define WM8985_ADCLPOL_MASK 0x0001 /* ADCLPOL */ +#define WM8985_ADCLPOL_SHIFT 0 /* ADCLPOL */ +#define WM8985_ADCLPOL_WIDTH 1 /* ADCLPOL */ + +/* + * R15 (0x0F) - Left ADC Digital Vol + */ +#define WM8985_ADCVU 0x0100 /* ADCVU */ +#define WM8985_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8985_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8985_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8985_ADCVOLL_MASK 0x00FF /* ADCVOLL - [7:0] */ +#define WM8985_ADCVOLL_SHIFT 0 /* ADCVOLL - [7:0] */ +#define WM8985_ADCVOLL_WIDTH 8 /* ADCVOLL - [7:0] */ + +/* + * R16 (0x10) - Right ADC Digital Vol + */ +#define WM8985_ADCVU 0x0100 /* ADCVU */ +#define WM8985_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8985_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8985_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8985_ADCVOLR_MASK 0x00FF /* ADCVOLR - [7:0] */ +#define WM8985_ADCVOLR_SHIFT 0 /* ADCVOLR - [7:0] */ +#define WM8985_ADCVOLR_WIDTH 8 /* ADCVOLR - [7:0] */ + +/* + * R18 (0x12) - EQ1 - low shelf + */ +#define WM8985_EQ3DMODE 0x0100 /* EQ3DMODE */ +#define WM8985_EQ3DMODE_MASK 0x0100 /* EQ3DMODE */ +#define WM8985_EQ3DMODE_SHIFT 8 /* EQ3DMODE */ +#define WM8985_EQ3DMODE_WIDTH 1 /* EQ3DMODE */ +#define WM8985_EQ1C_MASK 0x0060 /* EQ1C - [6:5] */ +#define WM8985_EQ1C_SHIFT 5 /* EQ1C - [6:5] */ +#define WM8985_EQ1C_WIDTH 2 /* EQ1C - [6:5] */ +#define WM8985_EQ1G_MASK 0x001F /* EQ1G - [4:0] */ +#define WM8985_EQ1G_SHIFT 0 /* EQ1G - [4:0] */ +#define WM8985_EQ1G_WIDTH 5 /* EQ1G - [4:0] */ + +/* + * R19 (0x13) - EQ2 - peak 1 + */ +#define WM8985_EQ2BW 0x0100 /* EQ2BW */ +#define WM8985_EQ2BW_MASK 0x0100 /* EQ2BW */ +#define WM8985_EQ2BW_SHIFT 8 /* EQ2BW */ +#define WM8985_EQ2BW_WIDTH 1 /* EQ2BW */ +#define WM8985_EQ2C_MASK 0x0060 /* EQ2C - [6:5] */ +#define WM8985_EQ2C_SHIFT 5 /* EQ2C - [6:5] */ +#define WM8985_EQ2C_WIDTH 2 /* EQ2C - [6:5] */ +#define WM8985_EQ2G_MASK 0x001F /* EQ2G - [4:0] */ +#define WM8985_EQ2G_SHIFT 0 /* EQ2G - [4:0] */ +#define WM8985_EQ2G_WIDTH 5 /* EQ2G - [4:0] */ + +/* + * R20 (0x14) - EQ3 - peak 2 + */ +#define WM8985_EQ3BW 0x0100 /* EQ3BW */ +#define WM8985_EQ3BW_MASK 0x0100 /* EQ3BW */ +#define WM8985_EQ3BW_SHIFT 8 /* EQ3BW */ +#define WM8985_EQ3BW_WIDTH 1 /* EQ3BW */ +#define WM8985_EQ3C_MASK 0x0060 /* EQ3C - [6:5] */ +#define WM8985_EQ3C_SHIFT 5 /* EQ3C - [6:5] */ +#define WM8985_EQ3C_WIDTH 2 /* EQ3C - [6:5] */ +#define WM8985_EQ3G_MASK 0x001F /* EQ3G - [4:0] */ +#define WM8985_EQ3G_SHIFT 0 /* EQ3G - [4:0] */ +#define WM8985_EQ3G_WIDTH 5 /* EQ3G - [4:0] */ + +/* + * R21 (0x15) - EQ4 - peak 3 + */ +#define WM8985_EQ4BW 0x0100 /* EQ4BW */ +#define WM8985_EQ4BW_MASK 0x0100 /* EQ4BW */ +#define WM8985_EQ4BW_SHIFT 8 /* EQ4BW */ +#define WM8985_EQ4BW_WIDTH 1 /* EQ4BW */ +#define WM8985_EQ4C_MASK 0x0060 /* EQ4C - [6:5] */ +#define WM8985_EQ4C_SHIFT 5 /* EQ4C - [6:5] */ +#define WM8985_EQ4C_WIDTH 2 /* EQ4C - [6:5] */ +#define WM8985_EQ4G_MASK 0x001F /* EQ4G - [4:0] */ +#define WM8985_EQ4G_SHIFT 0 /* EQ4G - [4:0] */ +#define WM8985_EQ4G_WIDTH 5 /* EQ4G - [4:0] */ + +/* + * R22 (0x16) - EQ5 - high shelf + */ +#define WM8985_EQ5C_MASK 0x0060 /* EQ5C - [6:5] */ +#define WM8985_EQ5C_SHIFT 5 /* EQ5C - [6:5] */ +#define WM8985_EQ5C_WIDTH 2 /* EQ5C - [6:5] */ +#define WM8985_EQ5G_MASK 0x001F /* EQ5G - [4:0] */ +#define WM8985_EQ5G_SHIFT 0 /* EQ5G - [4:0] */ +#define WM8985_EQ5G_WIDTH 5 /* EQ5G - [4:0] */ + +/* + * R24 (0x18) - DAC Limiter 1 + */ +#define WM8985_LIMEN 0x0100 /* LIMEN */ +#define WM8985_LIMEN_MASK 0x0100 /* LIMEN */ +#define WM8985_LIMEN_SHIFT 8 /* LIMEN */ +#define WM8985_LIMEN_WIDTH 1 /* LIMEN */ +#define WM8985_LIMDCY_MASK 0x00F0 /* LIMDCY - [7:4] */ +#define WM8985_LIMDCY_SHIFT 4 /* LIMDCY - [7:4] */ +#define WM8985_LIMDCY_WIDTH 4 /* LIMDCY - [7:4] */ +#define WM8985_LIMATK_MASK 0x000F /* LIMATK - [3:0] */ +#define WM8985_LIMATK_SHIFT 0 /* LIMATK - [3:0] */ +#define WM8985_LIMATK_WIDTH 4 /* LIMATK - [3:0] */ + +/* + * R25 (0x19) - DAC Limiter 2 + */ +#define WM8985_LIMLVL_MASK 0x0070 /* LIMLVL - [6:4] */ +#define WM8985_LIMLVL_SHIFT 4 /* LIMLVL - [6:4] */ +#define WM8985_LIMLVL_WIDTH 3 /* LIMLVL - [6:4] */ +#define WM8985_LIMBOOST_MASK 0x000F /* LIMBOOST - [3:0] */ +#define WM8985_LIMBOOST_SHIFT 0 /* LIMBOOST - [3:0] */ +#define WM8985_LIMBOOST_WIDTH 4 /* LIMBOOST - [3:0] */ + +/* + * R27 (0x1B) - Notch Filter 1 + */ +#define WM8985_NFU 0x0100 /* NFU */ +#define WM8985_NFU_MASK 0x0100 /* NFU */ +#define WM8985_NFU_SHIFT 8 /* NFU */ +#define WM8985_NFU_WIDTH 1 /* NFU */ +#define WM8985_NFEN 0x0080 /* NFEN */ +#define WM8985_NFEN_MASK 0x0080 /* NFEN */ +#define WM8985_NFEN_SHIFT 7 /* NFEN */ +#define WM8985_NFEN_WIDTH 1 /* NFEN */ +#define WM8985_NFA0_13_7_MASK 0x007F /* NFA0(13:7) - [6:0] */ +#define WM8985_NFA0_13_7_SHIFT 0 /* NFA0(13:7) - [6:0] */ +#define WM8985_NFA0_13_7_WIDTH 7 /* NFA0(13:7) - [6:0] */ + +/* + * R28 (0x1C) - Notch Filter 2 + */ +#define WM8985_NFU 0x0100 /* NFU */ +#define WM8985_NFU_MASK 0x0100 /* NFU */ +#define WM8985_NFU_SHIFT 8 /* NFU */ +#define WM8985_NFU_WIDTH 1 /* NFU */ +#define WM8985_NFA0_6_0_MASK 0x007F /* NFA0(6:0) - [6:0] */ +#define WM8985_NFA0_6_0_SHIFT 0 /* NFA0(6:0) - [6:0] */ +#define WM8985_NFA0_6_0_WIDTH 7 /* NFA0(6:0) - [6:0] */ + +/* + * R29 (0x1D) - Notch Filter 3 + */ +#define WM8985_NFU 0x0100 /* NFU */ +#define WM8985_NFU_MASK 0x0100 /* NFU */ +#define WM8985_NFU_SHIFT 8 /* NFU */ +#define WM8985_NFU_WIDTH 1 /* NFU */ +#define WM8985_NFA1_13_7_MASK 0x007F /* NFA1(13:7) - [6:0] */ +#define WM8985_NFA1_13_7_SHIFT 0 /* NFA1(13:7) - [6:0] */ +#define WM8985_NFA1_13_7_WIDTH 7 /* NFA1(13:7) - [6:0] */ + +/* + * R30 (0x1E) - Notch Filter 4 + */ +#define WM8985_NFU 0x0100 /* NFU */ +#define WM8985_NFU_MASK 0x0100 /* NFU */ +#define WM8985_NFU_SHIFT 8 /* NFU */ +#define WM8985_NFU_WIDTH 1 /* NFU */ +#define WM8985_NFA1_6_0_MASK 0x007F /* NFA1(6:0) - [6:0] */ +#define WM8985_NFA1_6_0_SHIFT 0 /* NFA1(6:0) - [6:0] */ +#define WM8985_NFA1_6_0_WIDTH 7 /* NFA1(6:0) - [6:0] */ + +/* + * R32 (0x20) - ALC control 1 + */ +#define WM8985_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */ +#define WM8985_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */ +#define WM8985_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */ +#define WM8985_ALCMAX_MASK 0x0038 /* ALCMAX - [5:3] */ +#define WM8985_ALCMAX_SHIFT 3 /* ALCMAX - [5:3] */ +#define WM8985_ALCMAX_WIDTH 3 /* ALCMAX - [5:3] */ +#define WM8985_ALCMIN_MASK 0x0007 /* ALCMIN - [2:0] */ +#define WM8985_ALCMIN_SHIFT 0 /* ALCMIN - [2:0] */ +#define WM8985_ALCMIN_WIDTH 3 /* ALCMIN - [2:0] */ + +/* + * R33 (0x21) - ALC control 2 + */ +#define WM8985_ALCHLD_MASK 0x00F0 /* ALCHLD - [7:4] */ +#define WM8985_ALCHLD_SHIFT 4 /* ALCHLD - [7:4] */ +#define WM8985_ALCHLD_WIDTH 4 /* ALCHLD - [7:4] */ +#define WM8985_ALCLVL_MASK 0x000F /* ALCLVL - [3:0] */ +#define WM8985_ALCLVL_SHIFT 0 /* ALCLVL - [3:0] */ +#define WM8985_ALCLVL_WIDTH 4 /* ALCLVL - [3:0] */ + +/* + * R34 (0x22) - ALC control 3 + */ +#define WM8985_ALCMODE 0x0100 /* ALCMODE */ +#define WM8985_ALCMODE_MASK 0x0100 /* ALCMODE */ +#define WM8985_ALCMODE_SHIFT 8 /* ALCMODE */ +#define WM8985_ALCMODE_WIDTH 1 /* ALCMODE */ +#define WM8985_ALCDCY_MASK 0x00F0 /* ALCDCY - [7:4] */ +#define WM8985_ALCDCY_SHIFT 4 /* ALCDCY - [7:4] */ +#define WM8985_ALCDCY_WIDTH 4 /* ALCDCY - [7:4] */ +#define WM8985_ALCATK_MASK 0x000F /* ALCATK - [3:0] */ +#define WM8985_ALCATK_SHIFT 0 /* ALCATK - [3:0] */ +#define WM8985_ALCATK_WIDTH 4 /* ALCATK - [3:0] */ + +/* + * R35 (0x23) - Noise Gate + */ +#define WM8985_NGEN 0x0008 /* NGEN */ +#define WM8985_NGEN_MASK 0x0008 /* NGEN */ +#define WM8985_NGEN_SHIFT 3 /* NGEN */ +#define WM8985_NGEN_WIDTH 1 /* NGEN */ +#define WM8985_NGTH_MASK 0x0007 /* NGTH - [2:0] */ +#define WM8985_NGTH_SHIFT 0 /* NGTH - [2:0] */ +#define WM8985_NGTH_WIDTH 3 /* NGTH - [2:0] */ + +/* + * R36 (0x24) - PLL N + */ +#define WM8985_PLL_PRESCALE 0x0010 /* PLL_PRESCALE */ +#define WM8985_PLL_PRESCALE_MASK 0x0010 /* PLL_PRESCALE */ +#define WM8985_PLL_PRESCALE_SHIFT 4 /* PLL_PRESCALE */ +#define WM8985_PLL_PRESCALE_WIDTH 1 /* PLL_PRESCALE */ +#define WM8985_PLLN_MASK 0x000F /* PLLN - [3:0] */ +#define WM8985_PLLN_SHIFT 0 /* PLLN - [3:0] */ +#define WM8985_PLLN_WIDTH 4 /* PLLN - [3:0] */ + +/* + * R37 (0x25) - PLL K 1 + */ +#define WM8985_PLLK_23_18_MASK 0x003F /* PLLK(23:18) - [5:0] */ +#define WM8985_PLLK_23_18_SHIFT 0 /* PLLK(23:18) - [5:0] */ +#define WM8985_PLLK_23_18_WIDTH 6 /* PLLK(23:18) - [5:0] */ + +/* + * R38 (0x26) - PLL K 2 + */ +#define WM8985_PLLK_17_9_MASK 0x01FF /* PLLK(17:9) - [8:0] */ +#define WM8985_PLLK_17_9_SHIFT 0 /* PLLK(17:9) - [8:0] */ +#define WM8985_PLLK_17_9_WIDTH 9 /* PLLK(17:9) - [8:0] */ + +/* + * R39 (0x27) - PLL K 3 + */ +#define WM8985_PLLK_8_0_MASK 0x01FF /* PLLK(8:0) - [8:0] */ +#define WM8985_PLLK_8_0_SHIFT 0 /* PLLK(8:0) - [8:0] */ +#define WM8985_PLLK_8_0_WIDTH 9 /* PLLK(8:0) - [8:0] */ + +/* + * R41 (0x29) - 3D control + */ +#define WM8985_DEPTH3D_MASK 0x000F /* DEPTH3D - [3:0] */ +#define WM8985_DEPTH3D_SHIFT 0 /* DEPTH3D - [3:0] */ +#define WM8985_DEPTH3D_WIDTH 4 /* DEPTH3D - [3:0] */ + +/* + * R42 (0x2A) - OUT4 to ADC + */ +#define WM8985_OUT4_2ADCVOL_MASK 0x01C0 /* OUT4_2ADCVOL - [8:6] */ +#define WM8985_OUT4_2ADCVOL_SHIFT 6 /* OUT4_2ADCVOL - [8:6] */ +#define WM8985_OUT4_2ADCVOL_WIDTH 3 /* OUT4_2ADCVOL - [8:6] */ +#define WM8985_OUT4_2LNR 0x0020 /* OUT4_2LNR */ +#define WM8985_OUT4_2LNR_MASK 0x0020 /* OUT4_2LNR */ +#define WM8985_OUT4_2LNR_SHIFT 5 /* OUT4_2LNR */ +#define WM8985_OUT4_2LNR_WIDTH 1 /* OUT4_2LNR */ +#define WM8758_VMIDTOG_MASK 0x0010 /* VMIDTOG */ +#define WM8758_VMIDTOG_SHIFT 4 /* VMIDTOG */ +#define WM8758_VMIDTOG_WIDTH 1 /* VMIDTOG */ +#define WM8758_OUT2DEL_MASK 0x0008 /* OUT2DEL */ +#define WM8758_OUT2DEL_SHIFT 3 /* OUT2DEL */ +#define WM8758_OUT2DEL_WIDTH 1 /* OUT2DEL */ +#define WM8985_POBCTRL 0x0004 /* POBCTRL */ +#define WM8985_POBCTRL_MASK 0x0004 /* POBCTRL */ +#define WM8985_POBCTRL_SHIFT 2 /* POBCTRL */ +#define WM8985_POBCTRL_WIDTH 1 /* POBCTRL */ +#define WM8985_DELEN 0x0002 /* DELEN */ +#define WM8985_DELEN_MASK 0x0002 /* DELEN */ +#define WM8985_DELEN_SHIFT 1 /* DELEN */ +#define WM8985_DELEN_WIDTH 1 /* DELEN */ +#define WM8985_OUT1DEL 0x0001 /* OUT1DEL */ +#define WM8985_OUT1DEL_MASK 0x0001 /* OUT1DEL */ +#define WM8985_OUT1DEL_SHIFT 0 /* OUT1DEL */ +#define WM8985_OUT1DEL_WIDTH 1 /* OUT1DEL */ + +/* + * R43 (0x2B) - Beep control + */ +#define WM8985_BYPL2RMIX 0x0100 /* BYPL2RMIX */ +#define WM8985_BYPL2RMIX_MASK 0x0100 /* BYPL2RMIX */ +#define WM8985_BYPL2RMIX_SHIFT 8 /* BYPL2RMIX */ +#define WM8985_BYPL2RMIX_WIDTH 1 /* BYPL2RMIX */ +#define WM8985_BYPR2LMIX 0x0080 /* BYPR2LMIX */ +#define WM8985_BYPR2LMIX_MASK 0x0080 /* BYPR2LMIX */ +#define WM8985_BYPR2LMIX_SHIFT 7 /* BYPR2LMIX */ +#define WM8985_BYPR2LMIX_WIDTH 1 /* BYPR2LMIX */ +#define WM8985_MUTERPGA2INV 0x0020 /* MUTERPGA2INV */ +#define WM8985_MUTERPGA2INV_MASK 0x0020 /* MUTERPGA2INV */ +#define WM8985_MUTERPGA2INV_SHIFT 5 /* MUTERPGA2INV */ +#define WM8985_MUTERPGA2INV_WIDTH 1 /* MUTERPGA2INV */ +#define WM8985_INVROUT2 0x0010 /* INVROUT2 */ +#define WM8985_INVROUT2_MASK 0x0010 /* INVROUT2 */ +#define WM8985_INVROUT2_SHIFT 4 /* INVROUT2 */ +#define WM8985_INVROUT2_WIDTH 1 /* INVROUT2 */ +#define WM8985_BEEPVOL_MASK 0x000E /* BEEPVOL - [3:1] */ +#define WM8985_BEEPVOL_SHIFT 1 /* BEEPVOL - [3:1] */ +#define WM8985_BEEPVOL_WIDTH 3 /* BEEPVOL - [3:1] */ +#define WM8758_DELEN2_MASK 0x0004 /* DELEN2 */ +#define WM8758_DELEN2_SHIFT 2 /* DELEN2 */ +#define WM8758_DELEN2_WIDTH 1 /* DELEN2 */ +#define WM8985_BEEPEN 0x0001 /* BEEPEN */ +#define WM8985_BEEPEN_MASK 0x0001 /* BEEPEN */ +#define WM8985_BEEPEN_SHIFT 0 /* BEEPEN */ +#define WM8985_BEEPEN_WIDTH 1 /* BEEPEN */ + +/* + * R44 (0x2C) - Input ctrl + */ +#define WM8985_MBVSEL 0x0100 /* MBVSEL */ +#define WM8985_MBVSEL_MASK 0x0100 /* MBVSEL */ +#define WM8985_MBVSEL_SHIFT 8 /* MBVSEL */ +#define WM8985_MBVSEL_WIDTH 1 /* MBVSEL */ +#define WM8985_R2_2INPPGA 0x0040 /* R2_2INPPGA */ +#define WM8985_R2_2INPPGA_MASK 0x0040 /* R2_2INPPGA */ +#define WM8985_R2_2INPPGA_SHIFT 6 /* R2_2INPPGA */ +#define WM8985_R2_2INPPGA_WIDTH 1 /* R2_2INPPGA */ +#define WM8985_RIN2INPPGA 0x0020 /* RIN2INPPGA */ +#define WM8985_RIN2INPPGA_MASK 0x0020 /* RIN2INPPGA */ +#define WM8985_RIN2INPPGA_SHIFT 5 /* RIN2INPPGA */ +#define WM8985_RIN2INPPGA_WIDTH 1 /* RIN2INPPGA */ +#define WM8985_RIP2INPPGA 0x0010 /* RIP2INPPGA */ +#define WM8985_RIP2INPPGA_MASK 0x0010 /* RIP2INPPGA */ +#define WM8985_RIP2INPPGA_SHIFT 4 /* RIP2INPPGA */ +#define WM8985_RIP2INPPGA_WIDTH 1 /* RIP2INPPGA */ +#define WM8985_L2_2INPPGA 0x0004 /* L2_2INPPGA */ +#define WM8985_L2_2INPPGA_MASK 0x0004 /* L2_2INPPGA */ +#define WM8985_L2_2INPPGA_SHIFT 2 /* L2_2INPPGA */ +#define WM8985_L2_2INPPGA_WIDTH 1 /* L2_2INPPGA */ +#define WM8985_LIN2INPPGA 0x0002 /* LIN2INPPGA */ +#define WM8985_LIN2INPPGA_MASK 0x0002 /* LIN2INPPGA */ +#define WM8985_LIN2INPPGA_SHIFT 1 /* LIN2INPPGA */ +#define WM8985_LIN2INPPGA_WIDTH 1 /* LIN2INPPGA */ +#define WM8985_LIP2INPPGA 0x0001 /* LIP2INPPGA */ +#define WM8985_LIP2INPPGA_MASK 0x0001 /* LIP2INPPGA */ +#define WM8985_LIP2INPPGA_SHIFT 0 /* LIP2INPPGA */ +#define WM8985_LIP2INPPGA_WIDTH 1 /* LIP2INPPGA */ + +/* + * R45 (0x2D) - Left INP PGA gain ctrl + */ +#define WM8985_INPGAVU 0x0100 /* INPGAVU */ +#define WM8985_INPGAVU_MASK 0x0100 /* INPGAVU */ +#define WM8985_INPGAVU_SHIFT 8 /* INPGAVU */ +#define WM8985_INPGAVU_WIDTH 1 /* INPGAVU */ +#define WM8985_INPPGAZCL 0x0080 /* INPPGAZCL */ +#define WM8985_INPPGAZCL_MASK 0x0080 /* INPPGAZCL */ +#define WM8985_INPPGAZCL_SHIFT 7 /* INPPGAZCL */ +#define WM8985_INPPGAZCL_WIDTH 1 /* INPPGAZCL */ +#define WM8985_INPPGAMUTEL 0x0040 /* INPPGAMUTEL */ +#define WM8985_INPPGAMUTEL_MASK 0x0040 /* INPPGAMUTEL */ +#define WM8985_INPPGAMUTEL_SHIFT 6 /* INPPGAMUTEL */ +#define WM8985_INPPGAMUTEL_WIDTH 1 /* INPPGAMUTEL */ +#define WM8985_INPPGAVOLL_MASK 0x003F /* INPPGAVOLL - [5:0] */ +#define WM8985_INPPGAVOLL_SHIFT 0 /* INPPGAVOLL - [5:0] */ +#define WM8985_INPPGAVOLL_WIDTH 6 /* INPPGAVOLL - [5:0] */ + +/* + * R46 (0x2E) - Right INP PGA gain ctrl + */ +#define WM8985_INPGAVU 0x0100 /* INPGAVU */ +#define WM8985_INPGAVU_MASK 0x0100 /* INPGAVU */ +#define WM8985_INPGAVU_SHIFT 8 /* INPGAVU */ +#define WM8985_INPGAVU_WIDTH 1 /* INPGAVU */ +#define WM8985_INPPGAZCR 0x0080 /* INPPGAZCR */ +#define WM8985_INPPGAZCR_MASK 0x0080 /* INPPGAZCR */ +#define WM8985_INPPGAZCR_SHIFT 7 /* INPPGAZCR */ +#define WM8985_INPPGAZCR_WIDTH 1 /* INPPGAZCR */ +#define WM8985_INPPGAMUTER 0x0040 /* INPPGAMUTER */ +#define WM8985_INPPGAMUTER_MASK 0x0040 /* INPPGAMUTER */ +#define WM8985_INPPGAMUTER_SHIFT 6 /* INPPGAMUTER */ +#define WM8985_INPPGAMUTER_WIDTH 1 /* INPPGAMUTER */ +#define WM8985_INPPGAVOLR_MASK 0x003F /* INPPGAVOLR - [5:0] */ +#define WM8985_INPPGAVOLR_SHIFT 0 /* INPPGAVOLR - [5:0] */ +#define WM8985_INPPGAVOLR_WIDTH 6 /* INPPGAVOLR - [5:0] */ + +/* + * R47 (0x2F) - Left ADC BOOST ctrl + */ +#define WM8985_PGABOOSTL 0x0100 /* PGABOOSTL */ +#define WM8985_PGABOOSTL_MASK 0x0100 /* PGABOOSTL */ +#define WM8985_PGABOOSTL_SHIFT 8 /* PGABOOSTL */ +#define WM8985_PGABOOSTL_WIDTH 1 /* PGABOOSTL */ +#define WM8985_L2_2BOOSTVOL_MASK 0x0070 /* L2_2BOOSTVOL - [6:4] */ +#define WM8985_L2_2BOOSTVOL_SHIFT 4 /* L2_2BOOSTVOL - [6:4] */ +#define WM8985_L2_2BOOSTVOL_WIDTH 3 /* L2_2BOOSTVOL - [6:4] */ +#define WM8985_AUXL2BOOSTVOL_MASK 0x0007 /* AUXL2BOOSTVOL - [2:0] */ +#define WM8985_AUXL2BOOSTVOL_SHIFT 0 /* AUXL2BOOSTVOL - [2:0] */ +#define WM8985_AUXL2BOOSTVOL_WIDTH 3 /* AUXL2BOOSTVOL - [2:0] */ + +/* + * R48 (0x30) - Right ADC BOOST ctrl + */ +#define WM8985_PGABOOSTR 0x0100 /* PGABOOSTR */ +#define WM8985_PGABOOSTR_MASK 0x0100 /* PGABOOSTR */ +#define WM8985_PGABOOSTR_SHIFT 8 /* PGABOOSTR */ +#define WM8985_PGABOOSTR_WIDTH 1 /* PGABOOSTR */ +#define WM8985_R2_2BOOSTVOL_MASK 0x0070 /* R2_2BOOSTVOL - [6:4] */ +#define WM8985_R2_2BOOSTVOL_SHIFT 4 /* R2_2BOOSTVOL - [6:4] */ +#define WM8985_R2_2BOOSTVOL_WIDTH 3 /* R2_2BOOSTVOL - [6:4] */ +#define WM8985_AUXR2BOOSTVOL_MASK 0x0007 /* AUXR2BOOSTVOL - [2:0] */ +#define WM8985_AUXR2BOOSTVOL_SHIFT 0 /* AUXR2BOOSTVOL - [2:0] */ +#define WM8985_AUXR2BOOSTVOL_WIDTH 3 /* AUXR2BOOSTVOL - [2:0] */ + +/* + * R49 (0x31) - Output ctrl + */ +#define WM8758_HP_COM 0x0100 /* HP_COM */ +#define WM8758_HP_COM_MASK 0x0100 /* HP_COM */ +#define WM8758_HP_COM_SHIFT 8 /* HP_COM */ +#define WM8758_HP_COM_WIDTH 1 /* HP_COM */ +#define WM8758_LINE_COM 0x0080 /* LINE_COM */ +#define WM8758_LINE_COM_MASK 0x0080 /* LINE_COM */ +#define WM8758_LINE_COM_SHIFT 7 /* LINE_COM */ +#define WM8758_LINE_COM_WIDTH 1 /* LINE_COM */ +#define WM8985_DACL2RMIX 0x0040 /* DACL2RMIX */ +#define WM8985_DACL2RMIX_MASK 0x0040 /* DACL2RMIX */ +#define WM8985_DACL2RMIX_SHIFT 6 /* DACL2RMIX */ +#define WM8985_DACL2RMIX_WIDTH 1 /* DACL2RMIX */ +#define WM8985_DACR2LMIX 0x0020 /* DACR2LMIX */ +#define WM8985_DACR2LMIX_MASK 0x0020 /* DACR2LMIX */ +#define WM8985_DACR2LMIX_SHIFT 5 /* DACR2LMIX */ +#define WM8985_DACR2LMIX_WIDTH 1 /* DACR2LMIX */ +#define WM8985_OUT4BOOST 0x0010 /* OUT4BOOST */ +#define WM8985_OUT4BOOST_MASK 0x0010 /* OUT4BOOST */ +#define WM8985_OUT4BOOST_SHIFT 4 /* OUT4BOOST */ +#define WM8985_OUT4BOOST_WIDTH 1 /* OUT4BOOST */ +#define WM8985_OUT3BOOST 0x0008 /* OUT3BOOST */ +#define WM8985_OUT3BOOST_MASK 0x0008 /* OUT3BOOST */ +#define WM8985_OUT3BOOST_SHIFT 3 /* OUT3BOOST */ +#define WM8985_OUT3BOOST_WIDTH 1 /* OUT3BOOST */ +#define WM8758_OUT4ENDEL 0x0010 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_MASK 0x0010 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_SHIFT 4 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_WIDTH 1 /* OUT4ENDEL */ +#define WM8758_OUT3ENDEL 0x0008 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_MASK 0x0008 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_SHIFT 3 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_WIDTH 1 /* OUT3ENDEL */ +#define WM8985_TSOPCTRL 0x0004 /* TSOPCTRL */ +#define WM8985_TSOPCTRL_MASK 0x0004 /* TSOPCTRL */ +#define WM8985_TSOPCTRL_SHIFT 2 /* TSOPCTRL */ +#define WM8985_TSOPCTRL_WIDTH 1 /* TSOPCTRL */ +#define WM8985_TSDEN 0x0002 /* TSDEN */ +#define WM8985_TSDEN_MASK 0x0002 /* TSDEN */ +#define WM8985_TSDEN_SHIFT 1 /* TSDEN */ +#define WM8985_TSDEN_WIDTH 1 /* TSDEN */ +#define WM8985_VROI 0x0001 /* VROI */ +#define WM8985_VROI_MASK 0x0001 /* VROI */ +#define WM8985_VROI_SHIFT 0 /* VROI */ +#define WM8985_VROI_WIDTH 1 /* VROI */ + +/* + * R50 (0x32) - Left mixer ctrl + */ +#define WM8985_AUXLMIXVOL_MASK 0x01C0 /* AUXLMIXVOL - [8:6] */ +#define WM8985_AUXLMIXVOL_SHIFT 6 /* AUXLMIXVOL - [8:6] */ +#define WM8985_AUXLMIXVOL_WIDTH 3 /* AUXLMIXVOL - [8:6] */ +#define WM8985_AUXL2LMIX 0x0020 /* AUXL2LMIX */ +#define WM8985_AUXL2LMIX_MASK 0x0020 /* AUXL2LMIX */ +#define WM8985_AUXL2LMIX_SHIFT 5 /* AUXL2LMIX */ +#define WM8985_AUXL2LMIX_WIDTH 1 /* AUXL2LMIX */ +#define WM8985_BYPLMIXVOL_MASK 0x001C /* BYPLMIXVOL - [4:2] */ +#define WM8985_BYPLMIXVOL_SHIFT 2 /* BYPLMIXVOL - [4:2] */ +#define WM8985_BYPLMIXVOL_WIDTH 3 /* BYPLMIXVOL - [4:2] */ +#define WM8985_BYPL2LMIX 0x0002 /* BYPL2LMIX */ +#define WM8985_BYPL2LMIX_MASK 0x0002 /* BYPL2LMIX */ +#define WM8985_BYPL2LMIX_SHIFT 1 /* BYPL2LMIX */ +#define WM8985_BYPL2LMIX_WIDTH 1 /* BYPL2LMIX */ +#define WM8985_DACL2LMIX 0x0001 /* DACL2LMIX */ +#define WM8985_DACL2LMIX_MASK 0x0001 /* DACL2LMIX */ +#define WM8985_DACL2LMIX_SHIFT 0 /* DACL2LMIX */ +#define WM8985_DACL2LMIX_WIDTH 1 /* DACL2LMIX */ + +/* + * R51 (0x33) - Right mixer ctrl + */ +#define WM8985_AUXRMIXVOL_MASK 0x01C0 /* AUXRMIXVOL - [8:6] */ +#define WM8985_AUXRMIXVOL_SHIFT 6 /* AUXRMIXVOL - [8:6] */ +#define WM8985_AUXRMIXVOL_WIDTH 3 /* AUXRMIXVOL - [8:6] */ +#define WM8985_AUXR2RMIX 0x0020 /* AUXR2RMIX */ +#define WM8985_AUXR2RMIX_MASK 0x0020 /* AUXR2RMIX */ +#define WM8985_AUXR2RMIX_SHIFT 5 /* AUXR2RMIX */ +#define WM8985_AUXR2RMIX_WIDTH 1 /* AUXR2RMIX */ +#define WM8985_BYPRMIXVOL_MASK 0x001C /* BYPRMIXVOL - [4:2] */ +#define WM8985_BYPRMIXVOL_SHIFT 2 /* BYPRMIXVOL - [4:2] */ +#define WM8985_BYPRMIXVOL_WIDTH 3 /* BYPRMIXVOL - [4:2] */ +#define WM8985_BYPR2RMIX 0x0002 /* BYPR2RMIX */ +#define WM8985_BYPR2RMIX_MASK 0x0002 /* BYPR2RMIX */ +#define WM8985_BYPR2RMIX_SHIFT 1 /* BYPR2RMIX */ +#define WM8985_BYPR2RMIX_WIDTH 1 /* BYPR2RMIX */ +#define WM8985_DACR2RMIX 0x0001 /* DACR2RMIX */ +#define WM8985_DACR2RMIX_MASK 0x0001 /* DACR2RMIX */ +#define WM8985_DACR2RMIX_SHIFT 0 /* DACR2RMIX */ +#define WM8985_DACR2RMIX_WIDTH 1 /* DACR2RMIX */ + +/* + * R52 (0x34) - LOUT1 (HP) volume ctrl + */ +#define WM8985_OUT1VU 0x0100 /* OUT1VU */ +#define WM8985_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8985_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8985_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8985_LOUT1ZC 0x0080 /* LOUT1ZC */ +#define WM8985_LOUT1ZC_MASK 0x0080 /* LOUT1ZC */ +#define WM8985_LOUT1ZC_SHIFT 7 /* LOUT1ZC */ +#define WM8985_LOUT1ZC_WIDTH 1 /* LOUT1ZC */ +#define WM8985_LOUT1MUTE 0x0040 /* LOUT1MUTE */ +#define WM8985_LOUT1MUTE_MASK 0x0040 /* LOUT1MUTE */ +#define WM8985_LOUT1MUTE_SHIFT 6 /* LOUT1MUTE */ +#define WM8985_LOUT1MUTE_WIDTH 1 /* LOUT1MUTE */ +#define WM8985_LOUT1VOL_MASK 0x003F /* LOUT1VOL - [5:0] */ +#define WM8985_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [5:0] */ +#define WM8985_LOUT1VOL_WIDTH 6 /* LOUT1VOL - [5:0] */ + +/* + * R53 (0x35) - ROUT1 (HP) volume ctrl + */ +#define WM8985_OUT1VU 0x0100 /* OUT1VU */ +#define WM8985_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8985_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8985_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8985_ROUT1ZC 0x0080 /* ROUT1ZC */ +#define WM8985_ROUT1ZC_MASK 0x0080 /* ROUT1ZC */ +#define WM8985_ROUT1ZC_SHIFT 7 /* ROUT1ZC */ +#define WM8985_ROUT1ZC_WIDTH 1 /* ROUT1ZC */ +#define WM8985_ROUT1MUTE 0x0040 /* ROUT1MUTE */ +#define WM8985_ROUT1MUTE_MASK 0x0040 /* ROUT1MUTE */ +#define WM8985_ROUT1MUTE_SHIFT 6 /* ROUT1MUTE */ +#define WM8985_ROUT1MUTE_WIDTH 1 /* ROUT1MUTE */ +#define WM8985_ROUT1VOL_MASK 0x003F /* ROUT1VOL - [5:0] */ +#define WM8985_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [5:0] */ +#define WM8985_ROUT1VOL_WIDTH 6 /* ROUT1VOL - [5:0] */ + +/* + * R54 (0x36) - LOUT2 (SPK) volume ctrl + */ +#define WM8985_OUT2VU 0x0100 /* OUT2VU */ +#define WM8985_OUT2VU_MASK 0x0100 /* OUT2VU */ +#define WM8985_OUT2VU_SHIFT 8 /* OUT2VU */ +#define WM8985_OUT2VU_WIDTH 1 /* OUT2VU */ +#define WM8985_LOUT2ZC 0x0080 /* LOUT2ZC */ +#define WM8985_LOUT2ZC_MASK 0x0080 /* LOUT2ZC */ +#define WM8985_LOUT2ZC_SHIFT 7 /* LOUT2ZC */ +#define WM8985_LOUT2ZC_WIDTH 1 /* LOUT2ZC */ +#define WM8985_LOUT2MUTE 0x0040 /* LOUT2MUTE */ +#define WM8985_LOUT2MUTE_MASK 0x0040 /* LOUT2MUTE */ +#define WM8985_LOUT2MUTE_SHIFT 6 /* LOUT2MUTE */ +#define WM8985_LOUT2MUTE_WIDTH 1 /* LOUT2MUTE */ +#define WM8985_LOUT2VOL_MASK 0x003F /* LOUT2VOL - [5:0] */ +#define WM8985_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [5:0] */ +#define WM8985_LOUT2VOL_WIDTH 6 /* LOUT2VOL - [5:0] */ + +/* + * R55 (0x37) - ROUT2 (SPK) volume ctrl + */ +#define WM8985_OUT2VU 0x0100 /* OUT2VU */ +#define WM8985_OUT2VU_MASK 0x0100 /* OUT2VU */ +#define WM8985_OUT2VU_SHIFT 8 /* OUT2VU */ +#define WM8985_OUT2VU_WIDTH 1 /* OUT2VU */ +#define WM8985_ROUT2ZC 0x0080 /* ROUT2ZC */ +#define WM8985_ROUT2ZC_MASK 0x0080 /* ROUT2ZC */ +#define WM8985_ROUT2ZC_SHIFT 7 /* ROUT2ZC */ +#define WM8985_ROUT2ZC_WIDTH 1 /* ROUT2ZC */ +#define WM8985_ROUT2MUTE 0x0040 /* ROUT2MUTE */ +#define WM8985_ROUT2MUTE_MASK 0x0040 /* ROUT2MUTE */ +#define WM8985_ROUT2MUTE_SHIFT 6 /* ROUT2MUTE */ +#define WM8985_ROUT2MUTE_WIDTH 1 /* ROUT2MUTE */ +#define WM8985_ROUT2VOL_MASK 0x003F /* ROUT2VOL - [5:0] */ +#define WM8985_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [5:0] */ +#define WM8985_ROUT2VOL_WIDTH 6 /* ROUT2VOL - [5:0] */ + +/* + * R56 (0x38) - OUT3 mixer ctrl + */ +#define WM8985_OUT3MUTE 0x0040 /* OUT3MUTE */ +#define WM8985_OUT3MUTE_MASK 0x0040 /* OUT3MUTE */ +#define WM8985_OUT3MUTE_SHIFT 6 /* OUT3MUTE */ +#define WM8985_OUT3MUTE_WIDTH 1 /* OUT3MUTE */ +#define WM8985_OUT4_2OUT3 0x0008 /* OUT4_2OUT3 */ +#define WM8985_OUT4_2OUT3_MASK 0x0008 /* OUT4_2OUT3 */ +#define WM8985_OUT4_2OUT3_SHIFT 3 /* OUT4_2OUT3 */ +#define WM8985_OUT4_2OUT3_WIDTH 1 /* OUT4_2OUT3 */ +#define WM8985_BYPL2OUT3 0x0004 /* BYPL2OUT3 */ +#define WM8985_BYPL2OUT3_MASK 0x0004 /* BYPL2OUT3 */ +#define WM8985_BYPL2OUT3_SHIFT 2 /* BYPL2OUT3 */ +#define WM8985_BYPL2OUT3_WIDTH 1 /* BYPL2OUT3 */ +#define WM8985_LMIX2OUT3 0x0002 /* LMIX2OUT3 */ +#define WM8985_LMIX2OUT3_MASK 0x0002 /* LMIX2OUT3 */ +#define WM8985_LMIX2OUT3_SHIFT 1 /* LMIX2OUT3 */ +#define WM8985_LMIX2OUT3_WIDTH 1 /* LMIX2OUT3 */ +#define WM8985_LDAC2OUT3 0x0001 /* LDAC2OUT3 */ +#define WM8985_LDAC2OUT3_MASK 0x0001 /* LDAC2OUT3 */ +#define WM8985_LDAC2OUT3_SHIFT 0 /* LDAC2OUT3 */ +#define WM8985_LDAC2OUT3_WIDTH 1 /* LDAC2OUT3 */ + +/* + * R57 (0x39) - OUT4 (MONO) mix ctrl + */ +#define WM8985_OUT3_2OUT4 0x0080 /* OUT3_2OUT4 */ +#define WM8985_OUT3_2OUT4_MASK 0x0080 /* OUT3_2OUT4 */ +#define WM8985_OUT3_2OUT4_SHIFT 7 /* OUT3_2OUT4 */ +#define WM8985_OUT3_2OUT4_WIDTH 1 /* OUT3_2OUT4 */ +#define WM8985_OUT4MUTE 0x0040 /* OUT4MUTE */ +#define WM8985_OUT4MUTE_MASK 0x0040 /* OUT4MUTE */ +#define WM8985_OUT4MUTE_SHIFT 6 /* OUT4MUTE */ +#define WM8985_OUT4MUTE_WIDTH 1 /* OUT4MUTE */ +#define WM8985_OUT4ATTN 0x0020 /* OUT4ATTN */ +#define WM8985_OUT4ATTN_MASK 0x0020 /* OUT4ATTN */ +#define WM8985_OUT4ATTN_SHIFT 5 /* OUT4ATTN */ +#define WM8985_OUT4ATTN_WIDTH 1 /* OUT4ATTN */ +#define WM8985_LMIX2OUT4 0x0010 /* LMIX2OUT4 */ +#define WM8985_LMIX2OUT4_MASK 0x0010 /* LMIX2OUT4 */ +#define WM8985_LMIX2OUT4_SHIFT 4 /* LMIX2OUT4 */ +#define WM8985_LMIX2OUT4_WIDTH 1 /* LMIX2OUT4 */ +#define WM8985_LDAC2OUT4 0x0008 /* LDAC2OUT4 */ +#define WM8985_LDAC2OUT4_MASK 0x0008 /* LDAC2OUT4 */ +#define WM8985_LDAC2OUT4_SHIFT 3 /* LDAC2OUT4 */ +#define WM8985_LDAC2OUT4_WIDTH 1 /* LDAC2OUT4 */ +#define WM8985_BYPR2OUT4 0x0004 /* BYPR2OUT4 */ +#define WM8985_BYPR2OUT4_MASK 0x0004 /* BYPR2OUT4 */ +#define WM8985_BYPR2OUT4_SHIFT 2 /* BYPR2OUT4 */ +#define WM8985_BYPR2OUT4_WIDTH 1 /* BYPR2OUT4 */ +#define WM8985_RMIX2OUT4 0x0002 /* RMIX2OUT4 */ +#define WM8985_RMIX2OUT4_MASK 0x0002 /* RMIX2OUT4 */ +#define WM8985_RMIX2OUT4_SHIFT 1 /* RMIX2OUT4 */ +#define WM8985_RMIX2OUT4_WIDTH 1 /* RMIX2OUT4 */ +#define WM8985_RDAC2OUT4 0x0001 /* RDAC2OUT4 */ +#define WM8985_RDAC2OUT4_MASK 0x0001 /* RDAC2OUT4 */ +#define WM8985_RDAC2OUT4_SHIFT 0 /* RDAC2OUT4 */ +#define WM8985_RDAC2OUT4_WIDTH 1 /* RDAC2OUT4 */ + +/* + * R60 (0x3C) - OUTPUT ctrl + */ +#define WM8985_VIDBUFFTST_MASK 0x01E0 /* VIDBUFFTST - [8:5] */ +#define WM8985_VIDBUFFTST_SHIFT 5 /* VIDBUFFTST - [8:5] */ +#define WM8985_VIDBUFFTST_WIDTH 4 /* VIDBUFFTST - [8:5] */ +#define WM8985_HPTOG 0x0008 /* HPTOG */ +#define WM8985_HPTOG_MASK 0x0008 /* HPTOG */ +#define WM8985_HPTOG_SHIFT 3 /* HPTOG */ +#define WM8985_HPTOG_WIDTH 1 /* HPTOG */ + +/* + * R61 (0x3D) - BIAS CTRL + */ +#define WM8985_BIASCUT 0x0100 /* BIASCUT */ +#define WM8985_BIASCUT_MASK 0x0100 /* BIASCUT */ +#define WM8985_BIASCUT_SHIFT 8 /* BIASCUT */ +#define WM8985_BIASCUT_WIDTH 1 /* BIASCUT */ +#define WM8985_HALFIPBIAS 0x0080 /* HALFIPBIAS */ +#define WM8985_HALFIPBIAS_MASK 0x0080 /* HALFIPBIAS */ +#define WM8985_HALFIPBIAS_SHIFT 7 /* HALFIPBIAS */ +#define WM8985_HALFIPBIAS_WIDTH 1 /* HALFIPBIAS */ +#define WM8758_HALFIPBIAS 0x0040 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_MASK 0x0040 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_SHIFT 6 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_WIDTH 1 /* HALFI_IPGA */ +#define WM8985_VBBIASTST_MASK 0x0060 /* VBBIASTST - [6:5] */ +#define WM8985_VBBIASTST_SHIFT 5 /* VBBIASTST - [6:5] */ +#define WM8985_VBBIASTST_WIDTH 2 /* VBBIASTST - [6:5] */ +#define WM8985_BUFBIAS_MASK 0x0018 /* BUFBIAS - [4:3] */ +#define WM8985_BUFBIAS_SHIFT 3 /* BUFBIAS - [4:3] */ +#define WM8985_BUFBIAS_WIDTH 2 /* BUFBIAS - [4:3] */ +#define WM8985_ADCBIAS_MASK 0x0006 /* ADCBIAS - [2:1] */ +#define WM8985_ADCBIAS_SHIFT 1 /* ADCBIAS - [2:1] */ +#define WM8985_ADCBIAS_WIDTH 2 /* ADCBIAS - [2:1] */ +#define WM8985_HALFOPBIAS 0x0001 /* HALFOPBIAS */ +#define WM8985_HALFOPBIAS_MASK 0x0001 /* HALFOPBIAS */ +#define WM8985_HALFOPBIAS_SHIFT 0 /* HALFOPBIAS */ +#define WM8985_HALFOPBIAS_WIDTH 1 /* HALFOPBIAS */ + +enum clk_src { + WM8985_CLKSRC_MCLK, + WM8985_CLKSRC_PLL +}; + +#define WM8985_PLL 0 + +#endif diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c new file mode 100644 index 000000000..6e52c6a8b --- /dev/null +++ b/sound/soc/codecs/wm8988.c @@ -0,0 +1,952 @@ +/* + * wm8988.c -- WM8988 ALSA SoC audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * Copyright 2005 Openedhand Ltd. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8988.h" + +/* + * wm8988 register cache + * We can't read the WM8988 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8988_reg_defaults[] = { + { 0, 0x0097 }, + { 1, 0x0097 }, + { 2, 0x0079 }, + { 3, 0x0079 }, + { 5, 0x0008 }, + { 7, 0x000a }, + { 8, 0x0000 }, + { 10, 0x00ff }, + { 11, 0x00ff }, + { 12, 0x000f }, + { 13, 0x000f }, + { 16, 0x0000 }, + { 17, 0x007b }, + { 18, 0x0000 }, + { 19, 0x0032 }, + { 20, 0x0000 }, + { 21, 0x00c3 }, + { 22, 0x00c3 }, + { 23, 0x00c0 }, + { 24, 0x0000 }, + { 25, 0x0000 }, + { 26, 0x0000 }, + { 27, 0x0000 }, + { 31, 0x0000 }, + { 32, 0x0000 }, + { 33, 0x0000 }, + { 34, 0x0050 }, + { 35, 0x0050 }, + { 36, 0x0050 }, + { 37, 0x0050 }, + { 40, 0x0079 }, + { 41, 0x0079 }, + { 42, 0x0079 }, +}; + +static bool wm8988_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8988_LINVOL: + case WM8988_RINVOL: + case WM8988_LOUT1V: + case WM8988_ROUT1V: + case WM8988_ADCDAC: + case WM8988_IFACE: + case WM8988_SRATE: + case WM8988_LDAC: + case WM8988_RDAC: + case WM8988_BASS: + case WM8988_TREBLE: + case WM8988_RESET: + case WM8988_3D: + case WM8988_ALC1: + case WM8988_ALC2: + case WM8988_ALC3: + case WM8988_NGATE: + case WM8988_LADC: + case WM8988_RADC: + case WM8988_ADCTL1: + case WM8988_ADCTL2: + case WM8988_PWR1: + case WM8988_PWR2: + case WM8988_ADCTL3: + case WM8988_ADCIN: + case WM8988_LADCIN: + case WM8988_RADCIN: + case WM8988_LOUTM1: + case WM8988_LOUTM2: + case WM8988_ROUTM1: + case WM8988_ROUTM2: + case WM8988_LOUT2V: + case WM8988_ROUT2V: + case WM8988_LPPB: + return true; + default: + return false; + } +} + +/* codec private data */ +struct wm8988_priv { + struct regmap *regmap; + unsigned int sysclk; + const struct snd_pcm_hw_constraint_list *sysclk_constraints; +}; + +#define wm8988_reset(c) snd_soc_component_write(c, WM8988_RESET, 0) + +/* + * WM8988 Controls + */ + +static const char *bass_boost_txt[] = {"Linear Control", "Adaptive Boost"}; +static SOC_ENUM_SINGLE_DECL(bass_boost, + WM8988_BASS, 7, bass_boost_txt); + +static const char *bass_filter_txt[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" }; +static SOC_ENUM_SINGLE_DECL(bass_filter, + WM8988_BASS, 6, bass_filter_txt); + +static const char *treble_txt[] = {"8kHz", "4kHz"}; +static SOC_ENUM_SINGLE_DECL(treble, + WM8988_TREBLE, 6, treble_txt); + +static const char *stereo_3d_lc_txt[] = {"200Hz", "500Hz"}; +static SOC_ENUM_SINGLE_DECL(stereo_3d_lc, + WM8988_3D, 5, stereo_3d_lc_txt); + +static const char *stereo_3d_uc_txt[] = {"2.2kHz", "1.5kHz"}; +static SOC_ENUM_SINGLE_DECL(stereo_3d_uc, + WM8988_3D, 6, stereo_3d_uc_txt); + +static const char *stereo_3d_func_txt[] = {"Capture", "Playback"}; +static SOC_ENUM_SINGLE_DECL(stereo_3d_func, + WM8988_3D, 7, stereo_3d_func_txt); + +static const char *alc_func_txt[] = {"Off", "Right", "Left", "Stereo"}; +static SOC_ENUM_SINGLE_DECL(alc_func, + WM8988_ALC1, 7, alc_func_txt); + +static const char *ng_type_txt[] = {"Constant PGA Gain", + "Mute ADC Output"}; +static SOC_ENUM_SINGLE_DECL(ng_type, + WM8988_NGATE, 1, ng_type_txt); + +static const char *deemph_txt[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static SOC_ENUM_SINGLE_DECL(deemph, + WM8988_ADCDAC, 1, deemph_txt); + +static const char *adcpol_txt[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static SOC_ENUM_SINGLE_DECL(adcpol, + WM8988_ADCDAC, 5, adcpol_txt); + +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); + +static const struct snd_kcontrol_new wm8988_snd_controls[] = { + +SOC_ENUM("Bass Boost", bass_boost), +SOC_ENUM("Bass Filter", bass_filter), +SOC_SINGLE("Bass Volume", WM8988_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8988_TREBLE, 0, 15, 0), +SOC_ENUM("Treble Cut-off", treble), + +SOC_SINGLE("3D Switch", WM8988_3D, 0, 1, 0), +SOC_SINGLE("3D Volume", WM8988_3D, 1, 15, 0), +SOC_ENUM("3D Lower Cut-off", stereo_3d_lc), +SOC_ENUM("3D Upper Cut-off", stereo_3d_uc), +SOC_ENUM("3D Mode", stereo_3d_func), + +SOC_SINGLE("ALC Capture Target Volume", WM8988_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8988_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", alc_func), +SOC_SINGLE("ALC Capture ZC Switch", WM8988_ALC2, 7, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8988_ALC2, 0, 15, 0), +SOC_SINGLE("ALC Capture Decay Time", WM8988_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack Time", WM8988_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8988_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", ng_type), +SOC_SINGLE("ALC Capture NG Switch", WM8988_NGATE, 0, 1, 0), + +SOC_SINGLE("ZC Timeout Switch", WM8988_ADCTL1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Digital Volume", WM8988_LADC, WM8988_RADC, + 0, 255, 0, adc_tlv), +SOC_DOUBLE_R_TLV("Capture Volume", WM8988_LINVOL, WM8988_RINVOL, + 0, 63, 0, pga_tlv), +SOC_DOUBLE_R("Capture ZC Switch", WM8988_LINVOL, WM8988_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8988_LINVOL, WM8988_RINVOL, 7, 1, 1), + +SOC_ENUM("Playback De-emphasis", deemph), + +SOC_ENUM("Capture Polarity", adcpol), +SOC_SINGLE("Playback 6dB Attenuate", WM8988_ADCDAC, 7, 1, 0), +SOC_SINGLE("Capture 6dB Attenuate", WM8988_ADCDAC, 8, 1, 0), + +SOC_DOUBLE_R_TLV("PCM Volume", WM8988_LDAC, WM8988_RDAC, 0, 255, 0, dac_tlv), + +SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", WM8988_LOUTM1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", WM8988_LOUTM2, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", WM8988_ROUTM1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", WM8988_ROUTM2, 4, 7, 1, + bypass_tlv), + +SOC_DOUBLE_R("Output 1 Playback ZC Switch", WM8988_LOUT1V, + WM8988_ROUT1V, 7, 1, 0), +SOC_DOUBLE_R_TLV("Output 1 Playback Volume", WM8988_LOUT1V, WM8988_ROUT1V, + 0, 127, 0, out_tlv), + +SOC_DOUBLE_R("Output 2 Playback ZC Switch", WM8988_LOUT2V, + WM8988_ROUT2V, 7, 1, 0), +SOC_DOUBLE_R_TLV("Output 2 Playback Volume", WM8988_LOUT2V, WM8988_ROUT2V, + 0, 127, 0, out_tlv), + +}; + +/* + * DAPM Controls + */ + +static int wm8988_lrc_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 adctl2 = snd_soc_component_read32(component, WM8988_ADCTL2); + + /* Use the DAC to gate LRC if active, otherwise use ADC */ + if (snd_soc_component_read32(component, WM8988_PWR2) & 0x180) + adctl2 &= ~0x4; + else + adctl2 |= 0x4; + + return snd_soc_component_write(component, WM8988_ADCTL2, adctl2); +} + +static const char *wm8988_line_texts[] = { + "Line 1", "Line 2", "PGA", "Differential"}; + +static const unsigned int wm8988_line_values[] = { + 0, 1, 3, 4}; + +static const struct soc_enum wm8988_lline_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_LOUTM1, 0, 7, + ARRAY_SIZE(wm8988_line_texts), + wm8988_line_texts, + wm8988_line_values); +static const struct snd_kcontrol_new wm8988_left_line_controls = + SOC_DAPM_ENUM("Route", wm8988_lline_enum); + +static const struct soc_enum wm8988_rline_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_ROUTM1, 0, 7, + ARRAY_SIZE(wm8988_line_texts), + wm8988_line_texts, + wm8988_line_values); +static const struct snd_kcontrol_new wm8988_right_line_controls = + SOC_DAPM_ENUM("Route", wm8988_lline_enum); + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8988_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", WM8988_LOUTM1, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_LOUTM1, 7, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", WM8988_LOUTM2, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8988_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", WM8988_ROUTM1, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_ROUTM1, 7, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", WM8988_ROUTM2, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_ROUTM2, 7, 1, 0), +}; + +static const char *wm8988_pga_sel[] = {"Line 1", "Line 2", "Differential"}; +static const unsigned int wm8988_pga_val[] = { 0, 1, 3 }; + +/* Left PGA Mux */ +static const struct soc_enum wm8988_lpga_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_LADCIN, 6, 3, + ARRAY_SIZE(wm8988_pga_sel), + wm8988_pga_sel, + wm8988_pga_val); +static const struct snd_kcontrol_new wm8988_left_pga_controls = + SOC_DAPM_ENUM("Route", wm8988_lpga_enum); + +/* Right PGA Mux */ +static const struct soc_enum wm8988_rpga_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_RADCIN, 6, 3, + ARRAY_SIZE(wm8988_pga_sel), + wm8988_pga_sel, + wm8988_pga_val); +static const struct snd_kcontrol_new wm8988_right_pga_controls = + SOC_DAPM_ENUM("Route", wm8988_rpga_enum); + +/* Differential Mux */ +static const char *wm8988_diff_sel[] = {"Line 1", "Line 2"}; +static SOC_ENUM_SINGLE_DECL(diffmux, + WM8988_ADCIN, 8, wm8988_diff_sel); +static const struct snd_kcontrol_new wm8988_diffmux_controls = + SOC_DAPM_ENUM("Route", diffmux); + +/* Mono ADC Mux */ +static const char *wm8988_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static SOC_ENUM_SINGLE_DECL(monomux, + WM8988_ADCIN, 6, wm8988_mono_mux); +static const struct snd_kcontrol_new wm8988_monomux_controls = + SOC_DAPM_ENUM("Route", monomux); + +static const struct snd_soc_dapm_widget wm8988_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Mic Bias", WM8988_PWR1, 1, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &wm8988_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8988_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8988_monomux_controls), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8988_PWR1, 5, 0, + &wm8988_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8988_PWR1, 4, 0, + &wm8988_right_pga_controls), + + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8988_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8988_right_line_controls), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8988_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8988_PWR1, 3, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8988_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8988_PWR2, 8, 0), + + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8988_left_mixer_controls[0], + ARRAY_SIZE(wm8988_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8988_right_mixer_controls[0], + ARRAY_SIZE(wm8988_right_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8988_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8988_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8988_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8988_PWR2, 6, 0, NULL, 0), + + SND_SOC_DAPM_POST("LRC control", wm8988_lrc_control), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("VREF"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), +}; + +static const struct snd_soc_dapm_route wm8988_dapm_routes[] = { + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left PGA Mux", "Line 1", "LINPUT1" }, + { "Left PGA Mux", "Line 2", "LINPUT2" }, + { "Left PGA Mux", "Differential", "Differential Mux" }, + + { "Right PGA Mux", "Line 1", "RINPUT1" }, + { "Right PGA Mux", "Line 2", "RINPUT2" }, + { "Right PGA Mux", "Differential", "Differential Mux" }, + + { "Differential Mux", "Line 1", "LINPUT1" }, + { "Differential Mux", "Line 1", "RINPUT1" }, + { "Differential Mux", "Line 2", "LINPUT2" }, + { "Differential Mux", "Line 2", "RINPUT2" }, + + { "Left ADC Mux", "Stereo", "Left PGA Mux" }, + { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" }, + { "Left ADC Mux", "Digital Mono", "Left PGA Mux" }, + + { "Right ADC Mux", "Stereo", "Right PGA Mux" }, + { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" }, + { "Right ADC Mux", "Digital Mono", "Right PGA Mux" }, + + { "Left ADC", NULL, "Left ADC Mux" }, + { "Right ADC", NULL, "Right ADC Mux" }, + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left Mixer", "Playback Switch", "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, + { "Left Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Right Mixer", "Left Playback Switch", "Left DAC" }, + { "Right Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Right Mixer", "Playback Switch", "Right DAC" }, + { "Right Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Left Out 1", NULL, "Left Mixer" }, + { "LOUT1", NULL, "Left Out 1" }, + { "Right Out 1", NULL, "Right Mixer" }, + { "ROUT1", NULL, "Right Out 1" }, + + { "Left Out 2", NULL, "Left Mixer" }, + { "LOUT2", NULL, "Left Out 2" }, + { "Right Out 2", NULL, "Right Mixer" }, + { "ROUT2", NULL, "Right Out 2" }, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + + return -EINVAL; +} + +/* The set of rates we can generate from the above for each SYSCLK */ + +static const unsigned int rates_12288[] = { + 8000, 12000, 16000, 24000, 32000, 48000, 96000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_12288 = { + .count = ARRAY_SIZE(rates_12288), + .list = rates_12288, +}; + +static const unsigned int rates_112896[] = { + 8000, 11025, 22050, 44100, +}; + +static const struct snd_pcm_hw_constraint_list constraints_112896 = { + .count = ARRAY_SIZE(rates_112896), + .list = rates_112896, +}; + +static const unsigned int rates_12[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 41100, 48000, + 48000, 88235, 96000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_12 = { + .count = ARRAY_SIZE(rates_12), + .list = rates_12, +}; + +/* + * Note that this should be called from init rather than from hw_params. + */ +static int wm8988_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8988_priv *wm8988 = snd_soc_component_get_drvdata(component); + + switch (freq) { + case 11289600: + case 18432000: + case 22579200: + case 36864000: + wm8988->sysclk_constraints = &constraints_112896; + wm8988->sysclk = freq; + return 0; + + case 12288000: + case 16934400: + case 24576000: + case 33868800: + wm8988->sysclk_constraints = &constraints_12288; + wm8988->sysclk = freq; + return 0; + + case 12000000: + case 24000000: + wm8988->sysclk_constraints = &constraints_12; + wm8988->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8988_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8988_IFACE, iface); + return 0; +} + +static int wm8988_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8988_priv *wm8988 = snd_soc_component_get_drvdata(component); + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8988->sysclk) { + dev_err(component->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + wm8988->sysclk_constraints); + + return 0; +} + +static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8988_priv *wm8988 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read32(component, WM8988_IFACE) & 0x1f3; + u16 srate = snd_soc_component_read32(component, WM8988_SRATE) & 0x180; + int coeff; + + coeff = get_coeff(wm8988->sysclk, params_rate(params)); + if (coeff < 0) { + coeff = get_coeff(wm8988->sysclk / 2, params_rate(params)); + srate |= 0x40; + } + if (coeff < 0) { + dev_err(component->dev, + "Unable to configure sample rate %dHz with %dHz MCLK\n", + params_rate(params), wm8988->sysclk); + return coeff; + } + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + snd_soc_component_write(component, WM8988_IFACE, iface); + if (coeff >= 0) + snd_soc_component_write(component, WM8988_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8988_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 mute_reg = snd_soc_component_read32(component, WM8988_ADCDAC) & 0xfff7; + + if (mute) + snd_soc_component_write(component, WM8988_ADCDAC, mute_reg | 0x8); + else + snd_soc_component_write(component, WM8988_ADCDAC, mute_reg); + return 0; +} + +static int wm8988_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8988_priv *wm8988 = snd_soc_component_get_drvdata(component); + u16 pwr_reg = snd_soc_component_read32(component, WM8988_PWR1) & ~0x1c1; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VREF, VMID=2x50k, digital enabled */ + snd_soc_component_write(component, WM8988_PWR1, pwr_reg | 0x00c0); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8988->regmap); + + /* VREF, VMID=2x5k */ + snd_soc_component_write(component, WM8988_PWR1, pwr_reg | 0x1c1); + + /* Charge caps */ + msleep(100); + } + + /* VREF, VMID=2*500k, digital stopped */ + snd_soc_component_write(component, WM8988_PWR1, pwr_reg | 0x0141); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_write(component, WM8988_PWR1, 0x0000); + break; + } + return 0; +} + +#define WM8988_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8988_ops = { + .startup = wm8988_pcm_startup, + .hw_params = wm8988_pcm_hw_params, + .set_fmt = wm8988_set_dai_fmt, + .set_sysclk = wm8988_set_dai_sysclk, + .digital_mute = wm8988_mute, +}; + +static struct snd_soc_dai_driver wm8988_dai = { + .name = "wm8988-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8988_RATES, + .formats = WM8988_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8988_RATES, + .formats = WM8988_FORMATS, + }, + .ops = &wm8988_ops, + .symmetric_rates = 1, +}; + +static int wm8988_probe(struct snd_soc_component *component) +{ + int ret = 0; + + ret = wm8988_reset(component); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset\n"); + return ret; + } + + /* set the update bits (we always update left then right) */ + snd_soc_component_update_bits(component, WM8988_RADC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8988_RDAC, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8988_ROUT1V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8988_ROUT2V, 0x0100, 0x0100); + snd_soc_component_update_bits(component, WM8988_RINVOL, 0x0100, 0x0100); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8988 = { + .probe = wm8988_probe, + .set_bias_level = wm8988_set_bias_level, + .controls = wm8988_snd_controls, + .num_controls = ARRAY_SIZE(wm8988_snd_controls), + .dapm_widgets = wm8988_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8988_dapm_widgets), + .dapm_routes = wm8988_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8988_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8988_regmap = { + .reg_bits = 7, + .val_bits = 9, + + .max_register = WM8988_LPPB, + .writeable_reg = wm8988_writeable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8988_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8988_reg_defaults), +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8988_spi_probe(struct spi_device *spi) +{ + struct wm8988_priv *wm8988; + int ret; + + wm8988 = devm_kzalloc(&spi->dev, sizeof(struct wm8988_priv), + GFP_KERNEL); + if (wm8988 == NULL) + return -ENOMEM; + + wm8988->regmap = devm_regmap_init_spi(spi, &wm8988_regmap); + if (IS_ERR(wm8988->regmap)) { + ret = PTR_ERR(wm8988->regmap); + dev_err(&spi->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + spi_set_drvdata(spi, wm8988); + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8988, &wm8988_dai, 1); + return ret; +} + +static struct spi_driver wm8988_spi_driver = { + .driver = { + .name = "wm8988", + }, + .probe = wm8988_spi_probe, +}; +#endif /* CONFIG_SPI_MASTER */ + +#if IS_ENABLED(CONFIG_I2C) +static int wm8988_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8988_priv *wm8988; + int ret; + + wm8988 = devm_kzalloc(&i2c->dev, sizeof(struct wm8988_priv), + GFP_KERNEL); + if (wm8988 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8988); + + wm8988->regmap = devm_regmap_init_i2c(i2c, &wm8988_regmap); + if (IS_ERR(wm8988->regmap)) { + ret = PTR_ERR(wm8988->regmap); + dev_err(&i2c->dev, "Failed to init regmap: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8988, &wm8988_dai, 1); + return ret; +} + +static const struct i2c_device_id wm8988_i2c_id[] = { + { "wm8988", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8988_i2c_id); + +static struct i2c_driver wm8988_i2c_driver = { + .driver = { + .name = "wm8988", + }, + .probe = wm8988_i2c_probe, + .id_table = wm8988_i2c_id, +}; +#endif + +static int __init wm8988_modinit(void) +{ + int ret = 0; +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8988_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8988 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8988_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8988 SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(wm8988_modinit); + +static void __exit wm8988_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8988_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8988_spi_driver); +#endif +} +module_exit(wm8988_exit); + + +MODULE_DESCRIPTION("ASoC WM8988 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8988.h b/sound/soc/codecs/wm8988.h new file mode 100644 index 000000000..5c04024e5 --- /dev/null +++ b/sound/soc/codecs/wm8988.h @@ -0,0 +1,57 @@ +/* + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on WM8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _WM8988_H +#define _WM8988_H + +/* WM8988 register space */ + +#define WM8988_LINVOL 0x00 +#define WM8988_RINVOL 0x01 +#define WM8988_LOUT1V 0x02 +#define WM8988_ROUT1V 0x03 +#define WM8988_ADCDAC 0x05 +#define WM8988_IFACE 0x07 +#define WM8988_SRATE 0x08 +#define WM8988_LDAC 0x0a +#define WM8988_RDAC 0x0b +#define WM8988_BASS 0x0c +#define WM8988_TREBLE 0x0d +#define WM8988_RESET 0x0f +#define WM8988_3D 0x10 +#define WM8988_ALC1 0x11 +#define WM8988_ALC2 0x12 +#define WM8988_ALC3 0x13 +#define WM8988_NGATE 0x14 +#define WM8988_LADC 0x15 +#define WM8988_RADC 0x16 +#define WM8988_ADCTL1 0x17 +#define WM8988_ADCTL2 0x18 +#define WM8988_PWR1 0x19 +#define WM8988_PWR2 0x1a +#define WM8988_ADCTL3 0x1b +#define WM8988_ADCIN 0x1f +#define WM8988_LADCIN 0x20 +#define WM8988_RADCIN 0x21 +#define WM8988_LOUTM1 0x22 +#define WM8988_LOUTM2 0x23 +#define WM8988_ROUTM1 0x24 +#define WM8988_ROUTM2 0x25 +#define WM8988_LOUT2V 0x28 +#define WM8988_ROUT2V 0x29 +#define WM8988_LPPB 0x43 +#define WM8988_NUM_REG 0x44 + +#define WM8988_SYSCLK 0 + +#endif diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c new file mode 100644 index 000000000..457bc437c --- /dev/null +++ b/sound/soc/codecs/wm8990.c @@ -0,0 +1,1361 @@ +/* + * wm8990.c -- WM8990 ALSA Soc Audio driver + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8990.h" + +/* codec private data */ +struct wm8990_priv { + struct regmap *regmap; + unsigned int sysclk; + unsigned int pcmclk; +}; + +static bool wm8990_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8990_RESET: + return true; + default: + return false; + } +} + +static const struct reg_default wm8990_reg_defaults[] = { + { 1, 0x0000 }, /* R1 - Power Management (1) */ + { 2, 0x6000 }, /* R2 - Power Management (2) */ + { 3, 0x0000 }, /* R3 - Power Management (3) */ + { 4, 0x4050 }, /* R4 - Audio Interface (1) */ + { 5, 0x4000 }, /* R5 - Audio Interface (2) */ + { 6, 0x01C8 }, /* R6 - Clocking (1) */ + { 7, 0x0000 }, /* R7 - Clocking (2) */ + { 8, 0x0040 }, /* R8 - Audio Interface (3) */ + { 9, 0x0040 }, /* R9 - Audio Interface (4) */ + { 10, 0x0004 }, /* R10 - DAC CTRL */ + { 11, 0x00C0 }, /* R11 - Left DAC Digital Volume */ + { 12, 0x00C0 }, /* R12 - Right DAC Digital Volume */ + { 13, 0x0000 }, /* R13 - Digital Side Tone */ + { 14, 0x0100 }, /* R14 - ADC CTRL */ + { 15, 0x00C0 }, /* R15 - Left ADC Digital Volume */ + { 16, 0x00C0 }, /* R16 - Right ADC Digital Volume */ + + { 18, 0x0000 }, /* R18 - GPIO CTRL 1 */ + { 19, 0x1000 }, /* R19 - GPIO1 & GPIO2 */ + { 20, 0x1010 }, /* R20 - GPIO3 & GPIO4 */ + { 21, 0x1010 }, /* R21 - GPIO5 & GPIO6 */ + { 22, 0x8000 }, /* R22 - GPIOCTRL 2 */ + { 23, 0x0800 }, /* R23 - GPIO_POL */ + { 24, 0x008B }, /* R24 - Left Line Input 1&2 Volume */ + { 25, 0x008B }, /* R25 - Left Line Input 3&4 Volume */ + { 26, 0x008B }, /* R26 - Right Line Input 1&2 Volume */ + { 27, 0x008B }, /* R27 - Right Line Input 3&4 Volume */ + { 28, 0x0000 }, /* R28 - Left Output Volume */ + { 29, 0x0000 }, /* R29 - Right Output Volume */ + { 30, 0x0066 }, /* R30 - Line Outputs Volume */ + { 31, 0x0022 }, /* R31 - Out3/4 Volume */ + { 32, 0x0079 }, /* R32 - Left OPGA Volume */ + { 33, 0x0079 }, /* R33 - Right OPGA Volume */ + { 34, 0x0003 }, /* R34 - Speaker Volume */ + { 35, 0x0003 }, /* R35 - ClassD1 */ + + { 37, 0x0100 }, /* R37 - ClassD3 */ + { 38, 0x0079 }, /* R38 - ClassD4 */ + { 39, 0x0000 }, /* R39 - Input Mixer1 */ + { 40, 0x0000 }, /* R40 - Input Mixer2 */ + { 41, 0x0000 }, /* R41 - Input Mixer3 */ + { 42, 0x0000 }, /* R42 - Input Mixer4 */ + { 43, 0x0000 }, /* R43 - Input Mixer5 */ + { 44, 0x0000 }, /* R44 - Input Mixer6 */ + { 45, 0x0000 }, /* R45 - Output Mixer1 */ + { 46, 0x0000 }, /* R46 - Output Mixer2 */ + { 47, 0x0000 }, /* R47 - Output Mixer3 */ + { 48, 0x0000 }, /* R48 - Output Mixer4 */ + { 49, 0x0000 }, /* R49 - Output Mixer5 */ + { 50, 0x0000 }, /* R50 - Output Mixer6 */ + { 51, 0x0180 }, /* R51 - Out3/4 Mixer */ + { 52, 0x0000 }, /* R52 - Line Mixer1 */ + { 53, 0x0000 }, /* R53 - Line Mixer2 */ + { 54, 0x0000 }, /* R54 - Speaker Mixer */ + { 55, 0x0000 }, /* R55 - Additional Control */ + { 56, 0x0000 }, /* R56 - AntiPOP1 */ + { 57, 0x0000 }, /* R57 - AntiPOP2 */ + { 58, 0x0000 }, /* R58 - MICBIAS */ + + { 60, 0x0008 }, /* R60 - PLL1 */ + { 61, 0x0031 }, /* R61 - PLL2 */ + { 62, 0x0026 }, /* R62 - PLL3 */ +}; + +#define wm8990_reset(c) snd_soc_component_write(c, WM8990_RESET, 0) + +static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 600, 0); + +static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1650, 3000, 0); + +static const DECLARE_TLV_DB_SCALE(out_mix_tlv, 0, -2100, 0); + +static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -7300, 600, 0); + +static const DECLARE_TLV_DB_SCALE(out_omix_tlv, -600, 0, 0); + +static const DECLARE_TLV_DB_SCALE(out_dac_tlv, -7163, 0, 0); + +static const DECLARE_TLV_DB_SCALE(in_adc_tlv, -7163, 1763, 0); + +static const DECLARE_TLV_DB_SCALE(out_sidetone_tlv, -3600, 0, 0); + +static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = snd_soc_component_read32(component, reg); + return snd_soc_component_write(component, reg, val | 0x0100); +} + +#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ + tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, reg, shift, max, invert, \ + snd_soc_get_volsw, wm899x_outpga_put_volsw_vu, tlv_array) + + +static const char *wm8990_digital_sidetone[] = + {"None", "Left ADC", "Right ADC", "Reserved"}; + +static SOC_ENUM_SINGLE_DECL(wm8990_left_digital_sidetone_enum, + WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACL_SHIFT, + wm8990_digital_sidetone); + +static SOC_ENUM_SINGLE_DECL(wm8990_right_digital_sidetone_enum, + WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACR_SHIFT, + wm8990_digital_sidetone); + +static const char *wm8990_adcmode[] = + {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static SOC_ENUM_SINGLE_DECL(wm8990_right_adcmode_enum, + WM8990_ADC_CTRL, + WM8990_ADC_HPF_CUT_SHIFT, + wm8990_adcmode); + +static const struct snd_kcontrol_new wm8990_snd_controls[] = { +/* INMIXL */ +SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), +SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), +/* INMIXR */ +SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), + +/* LOMIX */ +SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), + +/* ROMIX */ +SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv), + +/* LOUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME, + WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0), + +/* ROUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME, + WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0), + +/* LOPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAZC_BIT, 1, 0), + +/* ROPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAZC_BIT, 1, 0), + +SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LONMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOPMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOATTN_BIT, 1, 0), +SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_RONMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROPMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROATTN_BIT, 1, 0), + +SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3MUTE_BIT, 1, 0), +SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3ATTN_BIT, 1, 0), + +SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4MUTE_BIT, 1, 0), +SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4ATTN_BIT, 1, 0), + +SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1, + WM8990_CDMODE_BIT, 1, 0), + +SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME, + WM8990_SPKATTN_SHIFT, WM8990_SPKATTN_MASK, 0), +SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3, + WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0), +SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3, + WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0), +SOC_SINGLE_TLV("Speaker Volume", WM8990_CLASSD4, + WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("Speaker ZC Switch", WM8990_CLASSD4, + WM8990_SPKZC_SHIFT, WM8990_SPKZC_MASK, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8990_LEFT_DAC_DIGITAL_VOLUME, + WM8990_DACL_VOL_SHIFT, + WM8990_DACL_VOL_MASK, + 0, + out_dac_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8990_RIGHT_DAC_DIGITAL_VOLUME, + WM8990_DACR_VOL_SHIFT, + WM8990_DACR_VOL_MASK, + 0, + out_dac_tlv), + +SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum), +SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum), + +SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0, + out_sidetone_tlv), +SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + +SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL, + WM8990_ADC_HPF_ENA_BIT, 1, 0), + +SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8990_LEFT_ADC_DIGITAL_VOLUME, + WM8990_ADCL_VOL_SHIFT, + WM8990_ADCL_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8990_RIGHT_ADC_DIGITAL_VOLUME, + WM8990_ADCR_VOL_SHIFT, + WM8990_ADCR_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LIN12VOL_SHIFT, + WM8990_LIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12ZC_BIT, 1, 0), + +SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LIN34VOL_SHIFT, + WM8990_LIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34ZC_BIT, 1, 0), + +SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RIN12VOL_SHIFT, + WM8990_RIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12ZC_BIT, 1, 0), + +SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RIN34VOL_SHIFT, + WM8990_RIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34ZC_BIT, 1, 0), + +SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34MUTE_BIT, 1, 0), + +}; + +/* + * _DAPM_ Controls + */ + +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u32 reg_shift = kcontrol->private_value & 0xfff; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) : + reg = snd_soc_component_read32(component, WM8990_OUTPUT_MIXER1); + if (reg & WM8990_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8): + reg = snd_soc_component_read32(component, WM8990_OUTPUT_MIXER2); + if (reg & WM8990_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8): + reg = snd_soc_component_read32(component, WM8990_SPEAKER_MIXER); + if (reg & WM8990_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8): + reg = snd_soc_component_read32(component, WM8990_SPEAKER_MIXER); + if (reg & WM8990_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const DECLARE_TLV_DB_SCALE(in_mix_tlv, -1200, 600, 0); + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = { +SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = { +SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = { +SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = { +SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3, + WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4, + WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8990_ainlmux[] = + {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static SOC_ENUM_SINGLE_DECL(wm8990_ainlmux_enum, + WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT, + wm8990_ainlmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8990_ainrmux[] = + {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static SOC_ENUM_SINGLE_DECL(wm8990_ainrmux_enum, + WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT, + wm8990_ainrmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = { +SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT, + WM8990_LR4BVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT, + WM8990_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = { +SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LR12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LL12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1, + WM8990_LDLO_BIT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = { +SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RL12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RR12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2, + WM8990_RDRO_BIT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = { +SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LROPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1, + WM8990_LOPLON_BIT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = { +SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LR12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LL12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALOP_BIT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = { +SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RLOPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2, + WM8990_ROPRON_BIT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = { +SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RR12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGAROP_BIT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = { +SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_LI4O3_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_LPGAO3_BIT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = { +SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_RPGAO4_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_RI4O4_BIT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = { +SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LI2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LB2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_LOPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_LDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_RDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_ROPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RI2SPK_BIT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = { +/* Input Side */ +/* Input Lines */ +SND_SOC_DAPM_INPUT("LIN1"), +SND_SOC_DAPM_INPUT("LIN2"), +SND_SOC_DAPM_INPUT("LIN3"), +SND_SOC_DAPM_INPUT("LIN4/RXN"), +SND_SOC_DAPM_INPUT("RIN3"), +SND_SOC_DAPM_INPUT("RIN4/RXP"), +SND_SOC_DAPM_INPUT("RIN1"), +SND_SOC_DAPM_INPUT("RIN2"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +SND_SOC_DAPM_SUPPLY("INL", WM8990_POWER_MANAGEMENT_2, WM8990_AINL_ENA_BIT, 0, + NULL, 0), +SND_SOC_DAPM_SUPPLY("INR", WM8990_POWER_MANAGEMENT_2, WM8990_AINR_ENA_BIT, 0, + NULL, 0), + +/* DACs */ +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCL_ENA_BIT, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCR_ENA_BIT, 0), + +/* Input PGAs */ +SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT, + 0, &wm8990_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)), +SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT, + 0, &wm8990_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)), +SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT, + 0, &wm8990_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)), +SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT, + 0, &wm8990_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)), + +/* INMIXL */ +SND_SOC_DAPM_MIXER("INMIXL", SND_SOC_NOPM, 0, 0, + &wm8990_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixl_controls)), + +/* AINLMUX */ +SND_SOC_DAPM_MUX("AINLMUX", SND_SOC_NOPM, 0, 0, &wm8990_dapm_ainlmux_controls), + +/* INMIXR */ +SND_SOC_DAPM_MIXER("INMIXR", SND_SOC_NOPM, 0, 0, + &wm8990_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixr_controls)), + +/* AINRMUX */ +SND_SOC_DAPM_MUX("AINRMUX", SND_SOC_NOPM, 0, 0, &wm8990_dapm_ainrmux_controls), + +/* Output Side */ +/* DACs */ +SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACL_ENA_BIT, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACR_ENA_BIT, 0), + +/* LOMIX */ +SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT, + 0, &wm8990_dapm_lomix_controls[0], + ARRAY_SIZE(wm8990_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LONMIX */ +SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0, + &wm8990_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lonmix_controls)), + +/* LOPMIX */ +SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0, + &wm8990_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lopmix_controls)), + +/* OUT3MIX */ +SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0, + &wm8990_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out3mix_controls)), + +/* SPKMIX */ +SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0, + &wm8990_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + +/* OUT4MIX */ +SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0, + &wm8990_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out4mix_controls)), + +/* ROPMIX */ +SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0, + &wm8990_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ropmix_controls)), + +/* RONMIX */ +SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0, + &wm8990_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ronmix_controls)), + +/* ROMIX */ +SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT, + 0, &wm8990_dapm_romix_controls[0], + ARRAY_SIZE(wm8990_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LOUT PGA */ +SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0, + NULL, 0), + +/* ROUT PGA */ +SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0, + NULL, 0), + +/* LOPGA */ +SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0, + NULL, 0), + +/* ROPGA */ +SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0, + NULL, 0), + +/* MICBIAS */ +SND_SOC_DAPM_SUPPLY("MICBIAS", WM8990_POWER_MANAGEMENT_1, + WM8990_MICBIAS_ENA_BIT, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route wm8990_dapm_routes[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + {"AINLMUX", NULL, "INL"}, + {"INMIXL", NULL, "INL"}, + {"AINRMUX", NULL, "INR"}, + {"INMIXR", NULL, "INR"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AINLMUX */ + {"AINLMUX", "INMIXL Mix", "INMIXL"}, + {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AINLMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AINLMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Left ADC", NULL, "AINLMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AINRMUX */ + {"AINRMUX", "INMIXR Mix", "INMIXR"}, + {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AINRMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AINRMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Right ADC", NULL, "AINRMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT3", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +/* PLL divisors */ +struct _pll_div { + u32 div2; + u32 n; + u32 k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 16) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8990 N value outwith recommended range! N = %u\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct _pll_div pll_div; + + if (freq_in && freq_out) { + pll_factors(&pll_div, freq_out * 4, freq_in); + + /* Turn on PLL */ + snd_soc_component_update_bits(component, WM8990_POWER_MANAGEMENT_2, + WM8990_PLL_ENA, WM8990_PLL_ENA); + + /* sysclk comes from PLL */ + snd_soc_component_update_bits(component, WM8990_CLOCKING_2, + WM8990_SYSCLK_SRC, WM8990_SYSCLK_SRC); + + /* set up N , fractional mode and pre-divisor if necessary */ + snd_soc_component_write(component, WM8990_PLL1, pll_div.n | WM8990_SDM | + (pll_div.div2?WM8990_PRESCALE:0)); + snd_soc_component_write(component, WM8990_PLL2, (u8)(pll_div.k>>8)); + snd_soc_component_write(component, WM8990_PLL3, (u8)(pll_div.k & 0xFF)); + } else { + /* Turn off PLL */ + snd_soc_component_update_bits(component, WM8990_POWER_MANAGEMENT_2, + WM8990_PLL_ENA, 0); + } + return 0; +} + +/* + * Clock after PLL and dividers + */ +static int wm8990_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8990_priv *wm8990 = snd_soc_component_get_drvdata(component); + + wm8990->sysclk = freq; + return 0; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 audio1, audio3; + + audio1 = snd_soc_component_read32(component, WM8990_AUDIO_INTERFACE_1); + audio3 = snd_soc_component_read32(component, WM8990_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8990_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8990_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8990_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8990_AIF_TMF_I2S; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8990_AIF_TMF_RIGHTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8990_AIF_TMF_LEFTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8990_AIF_TMF_DSP; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8990_AUDIO_INTERFACE_1, audio1); + snd_soc_component_write(component, WM8990_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + + switch (div_id) { + case WM8990_MCLK_DIV: + snd_soc_component_update_bits(component, WM8990_CLOCKING_2, + WM8990_MCLK_DIV_MASK, div); + break; + case WM8990_DACCLK_DIV: + snd_soc_component_update_bits(component, WM8990_CLOCKING_2, + WM8990_DAC_CLKDIV_MASK, div); + break; + case WM8990_ADCCLK_DIV: + snd_soc_component_update_bits(component, WM8990_CLOCKING_2, + WM8990_ADC_CLKDIV_MASK, div); + break; + case WM8990_BCLK_DIV: + snd_soc_component_update_bits(component, WM8990_CLOCKING_1, + WM8990_BCLK_DIV_MASK, div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8990_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 audio1 = snd_soc_component_read32(component, WM8990_AUDIO_INTERFACE_1); + + audio1 &= ~WM8990_AIF_WL_MASK; + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + audio1 |= WM8990_AIF_WL_20BITS; + break; + case 24: + audio1 |= WM8990_AIF_WL_24BITS; + break; + case 32: + audio1 |= WM8990_AIF_WL_32BITS; + break; + } + + snd_soc_component_write(component, WM8990_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8990_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 val; + + val = snd_soc_component_read32(component, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE; + + if (mute) + snd_soc_component_write(component, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + else + snd_soc_component_write(component, WM8990_DAC_CTRL, val); + + return 0; +} + +static int wm8990_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8990_priv *wm8990 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + snd_soc_component_update_bits(component, WM8990_POWER_MANAGEMENT_1, + WM8990_VMID_MODE_MASK, 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regcache_sync(wm8990->regmap); + if (ret < 0) { + dev_err(component->dev, "Failed to sync cache: %d\n", ret); + return ret; + } + + /* Enable all output discharge bits */ + snd_soc_component_write(component, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + snd_soc_component_write(component, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_VMIDTOG); + + /* Delay to allow output caps to discharge */ + msleep(300); + + /* Disable VMIDTOG */ + snd_soc_component_write(component, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL); + + /* disable all output discharge bits */ + snd_soc_component_write(component, WM8990_ANTIPOP1, 0); + + /* Enable outputs */ + snd_soc_component_write(component, WM8990_POWER_MANAGEMENT_1, 0x1b00); + + msleep(50); + + /* Enable VMID at 2x50k */ + snd_soc_component_write(component, WM8990_POWER_MANAGEMENT_1, 0x1f02); + + msleep(100); + + /* Enable VREF */ + snd_soc_component_write(component, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + msleep(600); + + /* Enable BUFIOEN */ + snd_soc_component_write(component, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* Disable outputs */ + snd_soc_component_write(component, WM8990_POWER_MANAGEMENT_1, 0x3); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8990_ANTIPOP2, WM8990_BUFIOEN); + + /* Enable workaround for ADC clocking issue. */ + snd_soc_component_write(component, WM8990_EXT_ACCESS_ENA, 0x2); + snd_soc_component_write(component, WM8990_EXT_CTL1, 0xa003); + snd_soc_component_write(component, WM8990_EXT_ACCESS_ENA, 0); + } + + /* VMID=2*250k */ + snd_soc_component_update_bits(component, WM8990_POWER_MANAGEMENT_1, + WM8990_VMID_MODE_MASK, 0x4); + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + snd_soc_component_write(component, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_POBCTRL | WM8990_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* mute DAC */ + snd_soc_component_update_bits(component, WM8990_DAC_CTRL, + WM8990_DAC_MUTE, WM8990_DAC_MUTE); + + /* Enable any disabled outputs */ + snd_soc_component_write(component, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + /* Disable VMID */ + snd_soc_component_write(component, WM8990_POWER_MANAGEMENT_1, 0x1f01); + + msleep(300); + + /* Enable all output discharge bits */ + snd_soc_component_write(component, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Disable VREF */ + snd_soc_component_write(component, WM8990_POWER_MANAGEMENT_1, 0x0); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8990_ANTIPOP2, 0x0); + + regcache_mark_dirty(wm8990->regmap); + break; + } + + return 0; +} + +#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* + * The WM8990 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +static const struct snd_soc_dai_ops wm8990_dai_ops = { + .hw_params = wm8990_hw_params, + .digital_mute = wm8990_mute, + .set_fmt = wm8990_set_dai_fmt, + .set_clkdiv = wm8990_set_dai_clkdiv, + .set_pll = wm8990_set_dai_pll, + .set_sysclk = wm8990_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver wm8990_dai = { +/* ADC/DAC on primary */ + .name = "wm8990-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .ops = &wm8990_dai_ops, +}; + +/* + * initialise the WM8990 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8990_probe(struct snd_soc_component *component) +{ + wm8990_reset(component); + + /* charge output caps */ + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + snd_soc_component_update_bits(component, WM8990_AUDIO_INTERFACE_4, + WM8990_ALRCGPIO1, WM8990_ALRCGPIO1); + + snd_soc_component_update_bits(component, WM8990_GPIO1_GPIO2, + WM8990_GPIO1_SEL_MASK, 1); + + snd_soc_component_update_bits(component, WM8990_POWER_MANAGEMENT_2, + WM8990_OPCLK_ENA, WM8990_OPCLK_ENA); + + snd_soc_component_write(component, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + snd_soc_component_write(component, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm8990 = { + .probe = wm8990_probe, + .set_bias_level = wm8990_set_bias_level, + .controls = wm8990_snd_controls, + .num_controls = ARRAY_SIZE(wm8990_snd_controls), + .dapm_widgets = wm8990_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8990_dapm_widgets), + .dapm_routes = wm8990_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8990_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8990_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM8990_PLL3, + .volatile_reg = wm8990_volatile_register, + .reg_defaults = wm8990_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8990_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int wm8990_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8990_priv *wm8990; + int ret; + + wm8990 = devm_kzalloc(&i2c->dev, sizeof(struct wm8990_priv), + GFP_KERNEL); + if (wm8990 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8990); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8990, &wm8990_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8990_i2c_id[] = { + { "wm8990", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id); + +static struct i2c_driver wm8990_i2c_driver = { + .driver = { + .name = "wm8990", + }, + .probe = wm8990_i2c_probe, + .id_table = wm8990_i2c_id, +}; + +module_i2c_driver(wm8990_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8990 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h new file mode 100644 index 000000000..0e9c78040 --- /dev/null +++ b/sound/soc/codecs/wm8990.h @@ -0,0 +1,826 @@ +/* + * wm8990.h -- audio driver for WM8990 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __WM8990REGISTERDEFS_H__ +#define __WM8990REGISTERDEFS_H__ + +/* + * Register values. + */ +#define WM8990_RESET 0x00 +#define WM8990_POWER_MANAGEMENT_1 0x01 +#define WM8990_POWER_MANAGEMENT_2 0x02 +#define WM8990_POWER_MANAGEMENT_3 0x03 +#define WM8990_AUDIO_INTERFACE_1 0x04 +#define WM8990_AUDIO_INTERFACE_2 0x05 +#define WM8990_CLOCKING_1 0x06 +#define WM8990_CLOCKING_2 0x07 +#define WM8990_AUDIO_INTERFACE_3 0x08 +#define WM8990_AUDIO_INTERFACE_4 0x09 +#define WM8990_DAC_CTRL 0x0A +#define WM8990_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8990_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8990_DIGITAL_SIDE_TONE 0x0D +#define WM8990_ADC_CTRL 0x0E +#define WM8990_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8990_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8990_GPIO_CTRL_1 0x12 +#define WM8990_GPIO1_GPIO2 0x13 +#define WM8990_GPIO3_GPIO4 0x14 +#define WM8990_GPIO5_GPIO6 0x15 +#define WM8990_GPIOCTRL_2 0x16 +#define WM8990_GPIO_POL 0x17 +#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8990_LEFT_OUTPUT_VOLUME 0x1C +#define WM8990_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8990_LINE_OUTPUTS_VOLUME 0x1E +#define WM8990_OUT3_4_VOLUME 0x1F +#define WM8990_LEFT_OPGA_VOLUME 0x20 +#define WM8990_RIGHT_OPGA_VOLUME 0x21 +#define WM8990_SPEAKER_VOLUME 0x22 +#define WM8990_CLASSD1 0x23 +#define WM8990_CLASSD3 0x25 +#define WM8990_CLASSD4 0x26 +#define WM8990_INPUT_MIXER1 0x27 +#define WM8990_INPUT_MIXER2 0x28 +#define WM8990_INPUT_MIXER3 0x29 +#define WM8990_INPUT_MIXER4 0x2A +#define WM8990_INPUT_MIXER5 0x2B +#define WM8990_INPUT_MIXER6 0x2C +#define WM8990_OUTPUT_MIXER1 0x2D +#define WM8990_OUTPUT_MIXER2 0x2E +#define WM8990_OUTPUT_MIXER3 0x2F +#define WM8990_OUTPUT_MIXER4 0x30 +#define WM8990_OUTPUT_MIXER5 0x31 +#define WM8990_OUTPUT_MIXER6 0x32 +#define WM8990_OUT3_4_MIXER 0x33 +#define WM8990_LINE_MIXER1 0x34 +#define WM8990_LINE_MIXER2 0x35 +#define WM8990_SPEAKER_MIXER 0x36 +#define WM8990_ADDITIONAL_CONTROL 0x37 +#define WM8990_ANTIPOP1 0x38 +#define WM8990_ANTIPOP2 0x39 +#define WM8990_MICBIAS 0x3A +#define WM8990_PLL1 0x3C +#define WM8990_PLL2 0x3D +#define WM8990_PLL3 0x3E + +#define WM8990_EXT_ACCESS_ENA 0x75 +#define WM8990_EXT_CTL1 0x7a + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Reset + */ +#define WM8990_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8990_SPK_ENA 0x1000 /* SPK_ENA */ +#define WM8990_SPK_ENA_BIT 12 +#define WM8990_OUT3_ENA 0x0800 /* OUT3_ENA */ +#define WM8990_OUT3_ENA_BIT 11 +#define WM8990_OUT4_ENA 0x0400 /* OUT4_ENA */ +#define WM8990_OUT4_ENA_BIT 10 +#define WM8990_LOUT_ENA 0x0200 /* LOUT_ENA */ +#define WM8990_LOUT_ENA_BIT 9 +#define WM8990_ROUT_ENA 0x0100 /* ROUT_ENA */ +#define WM8990_ROUT_ENA_BIT 8 +#define WM8990_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */ +#define WM8990_MICBIAS_ENA_BIT 4 +#define WM8990_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */ +#define WM8990_VREF_ENA 0x0001 /* VREF_ENA */ +#define WM8990_VREF_ENA_BIT 0 + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8990_PLL_ENA 0x8000 /* PLL_ENA */ +#define WM8990_PLL_ENA_BIT 15 +#define WM8990_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8990_TSHUT_ENA_BIT 14 +#define WM8990_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8990_TSHUT_OPDIS_BIT 13 +#define WM8990_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8990_OPCLK_ENA_BIT 11 +#define WM8990_AINL_ENA 0x0200 /* AINL_ENA */ +#define WM8990_AINL_ENA_BIT 9 +#define WM8990_AINR_ENA 0x0100 /* AINR_ENA */ +#define WM8990_AINR_ENA_BIT 8 +#define WM8990_LIN34_ENA 0x0080 /* LIN34_ENA */ +#define WM8990_LIN34_ENA_BIT 7 +#define WM8990_LIN12_ENA 0x0040 /* LIN12_ENA */ +#define WM8990_LIN12_ENA_BIT 6 +#define WM8990_RIN34_ENA 0x0020 /* RIN34_ENA */ +#define WM8990_RIN34_ENA_BIT 5 +#define WM8990_RIN12_ENA 0x0010 /* RIN12_ENA */ +#define WM8990_RIN12_ENA_BIT 4 +#define WM8990_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8990_ADCL_ENA_BIT 1 +#define WM8990_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8990_ADCR_ENA_BIT 0 + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8990_LON_ENA 0x2000 /* LON_ENA */ +#define WM8990_LON_ENA_BIT 13 +#define WM8990_LOP_ENA 0x1000 /* LOP_ENA */ +#define WM8990_LOP_ENA_BIT 12 +#define WM8990_RON_ENA 0x0800 /* RON_ENA */ +#define WM8990_RON_ENA_BIT 11 +#define WM8990_ROP_ENA 0x0400 /* ROP_ENA */ +#define WM8990_ROP_ENA_BIT 10 +#define WM8990_LOPGA_ENA 0x0080 /* LOPGA_ENA */ +#define WM8990_LOPGA_ENA_BIT 7 +#define WM8990_ROPGA_ENA 0x0040 /* ROPGA_ENA */ +#define WM8990_ROPGA_ENA_BIT 6 +#define WM8990_LOMIX_ENA 0x0020 /* LOMIX_ENA */ +#define WM8990_LOMIX_ENA_BIT 5 +#define WM8990_ROMIX_ENA 0x0010 /* ROMIX_ENA */ +#define WM8990_ROMIX_ENA_BIT 4 +#define WM8990_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8990_DACL_ENA_BIT 1 +#define WM8990_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8990_DACR_ENA_BIT 0 + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8990_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8990_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8990_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8990_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8990_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8990_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8990_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8990_AIF_WL_16BITS (0 << 5) +#define WM8990_AIF_WL_20BITS (1 << 5) +#define WM8990_AIF_WL_24BITS (2 << 5) +#define WM8990_AIF_WL_32BITS (3 << 5) +#define WM8990_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8990_AIF_TMF_RIGHTJ (0 << 3) +#define WM8990_AIF_TMF_LEFTJ (1 << 3) +#define WM8990_AIF_TMF_I2S (2 << 3) +#define WM8990_AIF_TMF_DSP (3 << 3) + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8990_DACL_SRC 0x8000 /* DACL_SRC */ +#define WM8990_DACR_SRC 0x4000 /* DACR_SRC */ +#define WM8990_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8990_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8990_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST */ +#define WM8990_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8990_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8990_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8990_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8990_LOOPBACK 0x0001 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking (1) + */ +#define WM8990_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8990_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8990_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */ +#define WM8990_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8990_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8990_BCLK_DIV_1 (0x0 << 1) +#define WM8990_BCLK_DIV_1_5 (0x1 << 1) +#define WM8990_BCLK_DIV_2 (0x2 << 1) +#define WM8990_BCLK_DIV_3 (0x3 << 1) +#define WM8990_BCLK_DIV_4 (0x4 << 1) +#define WM8990_BCLK_DIV_5_5 (0x5 << 1) +#define WM8990_BCLK_DIV_6 (0x6 << 1) +#define WM8990_BCLK_DIV_8 (0x7 << 1) +#define WM8990_BCLK_DIV_11 (0x8 << 1) +#define WM8990_BCLK_DIV_12 (0x9 << 1) +#define WM8990_BCLK_DIV_16 (0xA << 1) +#define WM8990_BCLK_DIV_22 (0xB << 1) +#define WM8990_BCLK_DIV_24 (0xC << 1) +#define WM8990_BCLK_DIV_32 (0xD << 1) +#define WM8990_BCLK_DIV_44 (0xE << 1) +#define WM8990_BCLK_DIV_48 (0xF << 1) + +/* + * R7 (0x07) - Clocking (2) + */ +#define WM8990_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8990_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8990_CLK_FORCE 0x2000 /* CLK_FORCE */ +#define WM8990_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */ +#define WM8990_MCLK_DIV_1 (0 << 11) +#define WM8990_MCLK_DIV_2 (2 << 11) +#define WM8990_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8990_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV */ +#define WM8990_ADC_CLKDIV_1 (0 << 5) +#define WM8990_ADC_CLKDIV_1_5 (1 << 5) +#define WM8990_ADC_CLKDIV_2 (2 << 5) +#define WM8990_ADC_CLKDIV_3 (3 << 5) +#define WM8990_ADC_CLKDIV_4 (4 << 5) +#define WM8990_ADC_CLKDIV_5_5 (5 << 5) +#define WM8990_ADC_CLKDIV_6 (6 << 5) +#define WM8990_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */ +#define WM8990_DAC_CLKDIV_1 (0 << 2) +#define WM8990_DAC_CLKDIV_1_5 (1 << 2) +#define WM8990_DAC_CLKDIV_2 (2 << 2) +#define WM8990_DAC_CLKDIV_3 (3 << 2) +#define WM8990_DAC_CLKDIV_4 (4 << 2) +#define WM8990_DAC_CLKDIV_5_5 (5 << 2) +#define WM8990_DAC_CLKDIV_6 (6 << 2) + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8990_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8990_AIF_MSTR2 0x4000 /* AIF_MSTR2 */ +#define WM8990_AIF_SEL 0x2000 /* AIF_SEL */ +#define WM8990_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */ +#define WM8990_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8990_ALRCGPIO1 0x8000 /* ALRCGPIO1 */ +#define WM8990_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */ +#define WM8990_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8990_DACLRC_DIR 0x0800 /* DACLRC_DIR */ +#define WM8990_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8990_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */ +#define WM8990_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8990_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8990_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8990_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */ +#define WM8990_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */ +#define WM8990_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8990_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8990_DACR_DATINV 0x0001 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8990_DACL_VOL_SHIFT 0 +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8990_DACR_VOL_SHIFT 0 +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8990_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL */ +#define WM8990_ADCL_DAC_SVOL_SHIFT 9 +#define WM8990_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL */ +#define WM8990_ADCR_DAC_SVOL_SHIFT 5 +#define WM8990_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */ +#define WM8990_ADC_TO_DACL_SHIFT 2 +#define WM8990_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */ +#define WM8990_ADC_TO_DACR_SHIFT 0 + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8990_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */ +#define WM8990_ADC_HPF_ENA_BIT 8 +#define WM8990_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */ +#define WM8990_ADC_HPF_CUT_SHIFT 5 +#define WM8990_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8990_ADCL_DATINV_BIT 1 +#define WM8990_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8990_ADCR_DATINV_BIT 0 + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8990_ADCL_VOL_SHIFT 0 + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8990_ADCR_VOL_SHIFT 0 + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8990_IRQ 0x1000 /* IRQ */ +#define WM8990_TEMPOK 0x0800 /* TEMPOK */ +#define WM8990_MICSHRT 0x0400 /* MICSHRT */ +#define WM8990_MICDET 0x0200 /* MICDET */ +#define WM8990_PLL_LCK 0x0100 /* PLL_LCK */ +#define WM8990_GPI8_STATUS 0x0080 /* GPI8_STATUS */ +#define WM8990_GPI7_STATUS 0x0040 /* GPI7_STATUS */ +#define WM8990_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */ +#define WM8990_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */ +#define WM8990_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */ +#define WM8990_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */ +#define WM8990_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */ +#define WM8990_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */ + +/* + * R19 (0x13) - GPIO1 & GPIO2 + */ +#define WM8990_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */ +#define WM8990_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */ +#define WM8990_GPIO2_PU 0x2000 /* GPIO2_PU */ +#define WM8990_GPIO2_PD 0x1000 /* GPIO2_PD */ +#define WM8990_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */ +#define WM8990_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */ +#define WM8990_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */ +#define WM8990_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8990_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8990_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - GPIO3 & GPIO4 + */ +#define WM8990_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */ +#define WM8990_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */ +#define WM8990_GPIO4_PU 0x2000 /* GPIO4_PU */ +#define WM8990_GPIO4_PD 0x1000 /* GPIO4_PD */ +#define WM8990_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */ +#define WM8990_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */ +#define WM8990_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */ +#define WM8990_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8990_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8990_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ + +/* + * R21 (0x15) - GPIO5 & GPIO6 + */ +#define WM8990_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */ +#define WM8990_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */ +#define WM8990_GPIO6_PU 0x2000 /* GPIO6_PU */ +#define WM8990_GPIO6_PD 0x1000 /* GPIO6_PD */ +#define WM8990_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */ +#define WM8990_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */ +#define WM8990_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */ +#define WM8990_GPIO5_PU 0x0020 /* GPIO5_PU */ +#define WM8990_GPIO5_PD 0x0010 /* GPIO5_PD */ +#define WM8990_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8990_RD_3W_ENA 0x8000 /* RD_3W_ENA */ +#define WM8990_MODE_3W4W 0x4000 /* MODE_3W4W */ +#define WM8990_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */ +#define WM8990_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */ +#define WM8990_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */ +#define WM8990_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */ +#define WM8990_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */ +#define WM8990_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */ +#define WM8990_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8990_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */ +#define WM8990_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */ +#define WM8990_GPI7_ENA 0x0001 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8990_IRQ_INV 0x1000 /* IRQ_INV */ +#define WM8990_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8990_MICSHRT_POL 0x0400 /* MICSHRT_POL */ +#define WM8990_MICDET_POL 0x0200 /* MICDET_POL */ +#define WM8990_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */ +#define WM8990_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8990_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8990_GPIO6_POL 0x0020 /* GPIO6_POL */ +#define WM8990_GPIO5_POL 0x0010 /* GPIO5_POL */ +#define WM8990_GPIO4_POL 0x0008 /* GPIO4_POL */ +#define WM8990_GPIO3_POL 0x0004 /* GPIO3_POL */ +#define WM8990_GPIO2_POL 0x0002 /* GPIO2_POL */ +#define WM8990_GPIO1_POL 0x0001 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI12MUTE 0x0080 /* LI12MUTE */ +#define WM8990_LI12MUTE_BIT 7 +#define WM8990_LI12ZC 0x0040 /* LI12ZC */ +#define WM8990_LI12ZC_BIT 6 +#define WM8990_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */ +#define WM8990_LIN12VOL_SHIFT 0 +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI34MUTE 0x0080 /* LI34MUTE */ +#define WM8990_LI34MUTE_BIT 7 +#define WM8990_LI34ZC 0x0040 /* LI34ZC */ +#define WM8990_LI34ZC_BIT 6 +#define WM8990_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */ +#define WM8990_LIN34VOL_SHIFT 0 + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI12MUTE 0x0080 /* RI12MUTE */ +#define WM8990_RI12MUTE_BIT 7 +#define WM8990_RI12ZC 0x0040 /* RI12ZC */ +#define WM8990_RI12ZC_BIT 6 +#define WM8990_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */ +#define WM8990_RIN12VOL_SHIFT 0 + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI34MUTE 0x0080 /* RI34MUTE */ +#define WM8990_RI34MUTE_BIT 7 +#define WM8990_RI34ZC 0x0040 /* RI34ZC */ +#define WM8990_RI34ZC_BIT 6 +#define WM8990_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */ +#define WM8990_RIN34VOL_SHIFT 0 + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOZC 0x0080 /* LOZC */ +#define WM8990_LOZC_BIT 7 +#define WM8990_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8990_LOUTVOL_SHIFT 0 +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROZC 0x0080 /* ROZC */ +#define WM8990_ROZC_BIT 7 +#define WM8990_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8990_ROUTVOL_SHIFT 0 +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8990_LONMUTE 0x0040 /* LONMUTE */ +#define WM8990_LONMUTE_BIT 6 +#define WM8990_LOPMUTE 0x0020 /* LOPMUTE */ +#define WM8990_LOPMUTE_BIT 5 +#define WM8990_LOATTN 0x0010 /* LOATTN */ +#define WM8990_LOATTN_BIT 4 +#define WM8990_RONMUTE 0x0004 /* RONMUTE */ +#define WM8990_RONMUTE_BIT 2 +#define WM8990_ROPMUTE 0x0002 /* ROPMUTE */ +#define WM8990_ROPMUTE_BIT 1 +#define WM8990_ROATTN 0x0001 /* ROATTN */ +#define WM8990_ROATTN_BIT 0 + +/* + * R31 (0x1F) - Out3/4 Volume + */ +#define WM8990_OUT3MUTE 0x0020 /* OUT3MUTE */ +#define WM8990_OUT3MUTE_BIT 5 +#define WM8990_OUT3ATTN 0x0010 /* OUT3ATTN */ +#define WM8990_OUT3ATTN_BIT 4 +#define WM8990_OUT4MUTE 0x0002 /* OUT4MUTE */ +#define WM8990_OUT4MUTE_BIT 1 +#define WM8990_OUT4ATTN 0x0001 /* OUT4ATTN */ +#define WM8990_OUT4ATTN_BIT 0 + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOPGAZC 0x0080 /* LOPGAZC */ +#define WM8990_LOPGAZC_BIT 7 +#define WM8990_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */ +#define WM8990_LOPGAVOL_SHIFT 0 + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROPGAZC 0x0080 /* ROPGAZC */ +#define WM8990_ROPGAZC_BIT 7 +#define WM8990_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */ +#define WM8990_ROPGAVOL_SHIFT 0 +/* + * R34 (0x22) - Speaker Volume + */ +#define WM8990_SPKATTN_MASK 0x0003 /* SPKATTN - [1:0] */ +#define WM8990_SPKATTN_SHIFT 0 + +/* + * R35 (0x23) - ClassD1 + */ +#define WM8990_CDMODE 0x0100 /* CDMODE */ +#define WM8990_CDMODE_BIT 8 + +/* + * R37 (0x25) - ClassD3 + */ +#define WM8990_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */ +#define WM8990_DCGAIN_SHIFT 3 +#define WM8990_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */ +#define WM8990_ACGAIN_SHIFT 0 + +/* + * R38 (0x26) - ClassD4 + */ +#define WM8990_SPKZC_MASK 0x0001 /* SPKZC */ +#define WM8990_SPKZC_SHIFT 7 /* SPKZC */ +#define WM8990_SPKVOL_MASK 0x007F /* SPKVOL - [6:0] */ +#define WM8990_SPKVOL_SHIFT 0 /* SPKVOL - [6:0] */ + +/* + * R39 (0x27) - Input Mixer1 + */ +#define WM8990_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */ +#define WM8990_AINLMODE_SHIFT 2 +#define WM8990_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */ +#define WM8990_AINRMODE_SHIFT 0 + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8990_LMP4 0x0080 /* LMP4 */ +#define WM8990_LMP4_BIT 7 /* LMP4 */ +#define WM8990_LMN3 0x0040 /* LMN3 */ +#define WM8990_LMN3_BIT 6 /* LMN3 */ +#define WM8990_LMP2 0x0020 /* LMP2 */ +#define WM8990_LMP2_BIT 5 /* LMP2 */ +#define WM8990_LMN1 0x0010 /* LMN1 */ +#define WM8990_LMN1_BIT 4 /* LMN1 */ +#define WM8990_RMP4 0x0008 /* RMP4 */ +#define WM8990_RMP4_BIT 3 /* RMP4 */ +#define WM8990_RMN3 0x0004 /* RMN3 */ +#define WM8990_RMN3_BIT 2 /* RMN3 */ +#define WM8990_RMP2 0x0002 /* RMP2 */ +#define WM8990_RMP2_BIT 1 /* RMP2 */ +#define WM8990_RMN1 0x0001 /* RMN1 */ +#define WM8990_RMN1_BIT 0 /* RMN1 */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8990_L34MNB 0x0100 /* L34MNB */ +#define WM8990_L34MNB_BIT 8 +#define WM8990_L34MNBST 0x0080 /* L34MNBST */ +#define WM8990_L34MNBST_BIT 7 +#define WM8990_L12MNB 0x0020 /* L12MNB */ +#define WM8990_L12MNB_BIT 5 +#define WM8990_L12MNBST 0x0010 /* L12MNBST */ +#define WM8990_L12MNBST_BIT 4 +#define WM8990_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */ +#define WM8990_LDBVOL_SHIFT 0 + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8990_R34MNB 0x0100 /* R34MNB */ +#define WM8990_R34MNB_BIT 8 +#define WM8990_R34MNBST 0x0080 /* R34MNBST */ +#define WM8990_R34MNBST_BIT 7 +#define WM8990_R12MNB 0x0020 /* R12MNB */ +#define WM8990_R12MNB_BIT 5 +#define WM8990_R12MNBST 0x0010 /* R12MNBST */ +#define WM8990_R12MNBST_BIT 4 +#define WM8990_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */ +#define WM8990_RDBVOL_SHIFT 0 + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8990_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */ +#define WM8990_LI2BVOL_SHIFT 6 +#define WM8990_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */ +#define WM8990_LR4BVOL_SHIFT 3 +#define WM8990_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */ +#define WM8990_LL4BVOL_SHIFT 0 + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8990_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */ +#define WM8990_RI2BVOL_SHIFT 6 +#define WM8990_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */ +#define WM8990_RL4BVOL_SHIFT 3 +#define WM8990_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */ +#define WM8990_RR4BVOL_SHIFT 0 + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8990_LRBLO 0x0080 /* LRBLO */ +#define WM8990_LRBLO_BIT 7 +#define WM8990_LLBLO 0x0040 /* LLBLO */ +#define WM8990_LLBLO_BIT 6 +#define WM8990_LRI3LO 0x0020 /* LRI3LO */ +#define WM8990_LRI3LO_BIT 5 +#define WM8990_LLI3LO 0x0010 /* LLI3LO */ +#define WM8990_LLI3LO_BIT 4 +#define WM8990_LR12LO 0x0008 /* LR12LO */ +#define WM8990_LR12LO_BIT 3 +#define WM8990_LL12LO 0x0004 /* LL12LO */ +#define WM8990_LL12LO_BIT 2 +#define WM8990_LDLO 0x0001 /* LDLO */ +#define WM8990_LDLO_BIT 0 + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8990_RLBRO 0x0080 /* RLBRO */ +#define WM8990_RLBRO_BIT 7 +#define WM8990_RRBRO 0x0040 /* RRBRO */ +#define WM8990_RRBRO_BIT 6 +#define WM8990_RLI3RO 0x0020 /* RLI3RO */ +#define WM8990_RLI3RO_BIT 5 +#define WM8990_RRI3RO 0x0010 /* RRI3RO */ +#define WM8990_RRI3RO_BIT 4 +#define WM8990_RL12RO 0x0008 /* RL12RO */ +#define WM8990_RL12RO_BIT 3 +#define WM8990_RR12RO 0x0004 /* RR12RO */ +#define WM8990_RR12RO_BIT 2 +#define WM8990_RDRO 0x0001 /* RDRO */ +#define WM8990_RDRO_BIT 0 + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8990_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */ +#define WM8990_LLI3LOVOL_SHIFT 6 +#define WM8990_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */ +#define WM8990_LR12LOVOL_SHIFT 3 +#define WM8990_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */ +#define WM8990_LL12LOVOL_SHIFT 0 + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8990_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */ +#define WM8990_RRI3ROVOL_SHIFT 6 +#define WM8990_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */ +#define WM8990_RL12ROVOL_SHIFT 3 +#define WM8990_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */ +#define WM8990_RR12ROVOL_SHIFT 0 + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8990_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */ +#define WM8990_LRI3LOVOL_SHIFT 6 +#define WM8990_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */ +#define WM8990_LRBLOVOL_SHIFT 3 +#define WM8990_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */ +#define WM8990_LLBLOVOL_SHIFT 0 + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8990_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */ +#define WM8990_RLI3ROVOL_SHIFT 6 +#define WM8990_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */ +#define WM8990_RLBROVOL_SHIFT 3 +#define WM8990_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */ +#define WM8990_RRBROVOL_SHIFT 0 + +/* + * R51 (0x33) - Out3/4 Mixer + */ +#define WM8990_VSEL_MASK 0x0180 /* VSEL - [8:7] */ +#define WM8990_LI4O3 0x0020 /* LI4O3 */ +#define WM8990_LI4O3_BIT 5 +#define WM8990_LPGAO3 0x0010 /* LPGAO3 */ +#define WM8990_LPGAO3_BIT 4 +#define WM8990_RI4O4 0x0002 /* RI4O4 */ +#define WM8990_RI4O4_BIT 1 +#define WM8990_RPGAO4 0x0001 /* RPGAO4 */ +#define WM8990_RPGAO4_BIT 0 +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8990_LLOPGALON 0x0040 /* LLOPGALON */ +#define WM8990_LLOPGALON_BIT 6 +#define WM8990_LROPGALON 0x0020 /* LROPGALON */ +#define WM8990_LROPGALON_BIT 5 +#define WM8990_LOPLON 0x0010 /* LOPLON */ +#define WM8990_LOPLON_BIT 4 +#define WM8990_LR12LOP 0x0004 /* LR12LOP */ +#define WM8990_LR12LOP_BIT 2 +#define WM8990_LL12LOP 0x0002 /* LL12LOP */ +#define WM8990_LL12LOP_BIT 1 +#define WM8990_LLOPGALOP 0x0001 /* LLOPGALOP */ +#define WM8990_LLOPGALOP_BIT 0 +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8990_RROPGARON 0x0040 /* RROPGARON */ +#define WM8990_RROPGARON_BIT 6 +#define WM8990_RLOPGARON 0x0020 /* RLOPGARON */ +#define WM8990_RLOPGARON_BIT 5 +#define WM8990_ROPRON 0x0010 /* ROPRON */ +#define WM8990_ROPRON_BIT 4 +#define WM8990_RL12ROP 0x0004 /* RL12ROP */ +#define WM8990_RL12ROP_BIT 2 +#define WM8990_RR12ROP 0x0002 /* RR12ROP */ +#define WM8990_RR12ROP_BIT 1 +#define WM8990_RROPGAROP 0x0001 /* RROPGAROP */ +#define WM8990_RROPGAROP_BIT 0 + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8990_LB2SPK 0x0080 /* LB2SPK */ +#define WM8990_LB2SPK_BIT 7 +#define WM8990_RB2SPK 0x0040 /* RB2SPK */ +#define WM8990_RB2SPK_BIT 6 +#define WM8990_LI2SPK 0x0020 /* LI2SPK */ +#define WM8990_LI2SPK_BIT 5 +#define WM8990_RI2SPK 0x0010 /* RI2SPK */ +#define WM8990_RI2SPK_BIT 4 +#define WM8990_LOPGASPK 0x0008 /* LOPGASPK */ +#define WM8990_LOPGASPK_BIT 3 +#define WM8990_ROPGASPK 0x0004 /* ROPGASPK */ +#define WM8990_ROPGASPK_BIT 2 +#define WM8990_LDSPK 0x0002 /* LDSPK */ +#define WM8990_LDSPK_BIT 1 +#define WM8990_RDSPK 0x0001 /* RDSPK */ +#define WM8990_RDSPK_BIT 0 + +/* + * R55 (0x37) - Additional Control + */ +#define WM8990_VROI 0x0001 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8990_DIS_LLINE 0x0020 /* DIS_LLINE */ +#define WM8990_DIS_RLINE 0x0010 /* DIS_RLINE */ +#define WM8990_DIS_OUT3 0x0008 /* DIS_OUT3 */ +#define WM8990_DIS_OUT4 0x0004 /* DIS_OUT4 */ +#define WM8990_DIS_LOUT 0x0002 /* DIS_LOUT */ +#define WM8990_DIS_ROUT 0x0001 /* DIS_ROUT */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8990_SOFTST 0x0040 /* SOFTST */ +#define WM8990_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8990_BUFDCOPEN 0x0004 /* BUFDCOPEN */ +#define WM8990_POBCTRL 0x0002 /* POBCTRL */ +#define WM8990_VMIDTOG 0x0001 /* VMIDTOG */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8990_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */ +#define WM8990_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */ +#define WM8990_MCD 0x0004 /* MCD */ +#define WM8990_MBSEL 0x0001 /* MBSEL */ + +/* + * R60 (0x3C) - PLL1 + */ +#define WM8990_SDM 0x0080 /* SDM */ +#define WM8990_PRESCALE 0x0040 /* PRESCALE */ +#define WM8990_PLLN_MASK 0x000F /* PLLN - [3:0] */ + +/* + * R61 (0x3D) - PLL2 + */ +#define WM8990_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */ + +/* + * R62 (0x3E) - PLL3 + */ +#define WM8990_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */ + +#define WM8990_MCLK_DIV 0 +#define WM8990_DACCLK_DIV 1 +#define WM8990_ADCCLK_DIV 2 +#define WM8990_BCLK_DIV 3 + +#endif /* __WM8990REGISTERDEFS_H__ */ +/*------------------------------ END OF FILE ---------------------------------*/ diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c new file mode 100644 index 000000000..76a639df6 --- /dev/null +++ b/sound/soc/codecs/wm8991.c @@ -0,0 +1,1347 @@ +/* + * wm8991.c -- WM8991 ALSA Soc Audio driver + * + * Copyright 2007-2010 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * Graeme.Gregory@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8991.h" + +struct wm8991_priv { + struct regmap *regmap; + unsigned int pcmclk; +}; + +static const struct reg_default wm8991_reg_defaults[] = { + { 1, 0x0000 }, /* R1 - Power Management (1) */ + { 2, 0x6000 }, /* R2 - Power Management (2) */ + { 3, 0x0000 }, /* R3 - Power Management (3) */ + { 4, 0x4050 }, /* R4 - Audio Interface (1) */ + { 5, 0x4000 }, /* R5 - Audio Interface (2) */ + { 6, 0x01C8 }, /* R6 - Clocking (1) */ + { 7, 0x0000 }, /* R7 - Clocking (2) */ + { 8, 0x0040 }, /* R8 - Audio Interface (3) */ + { 9, 0x0040 }, /* R9 - Audio Interface (4) */ + { 10, 0x0004 }, /* R10 - DAC CTRL */ + { 11, 0x00C0 }, /* R11 - Left DAC Digital Volume */ + { 12, 0x00C0 }, /* R12 - Right DAC Digital Volume */ + { 13, 0x0000 }, /* R13 - Digital Side Tone */ + { 14, 0x0100 }, /* R14 - ADC CTRL */ + { 15, 0x00C0 }, /* R15 - Left ADC Digital Volume */ + { 16, 0x00C0 }, /* R16 - Right ADC Digital Volume */ + + { 18, 0x0000 }, /* R18 - GPIO CTRL 1 */ + { 19, 0x1000 }, /* R19 - GPIO1 & GPIO2 */ + { 20, 0x1010 }, /* R20 - GPIO3 & GPIO4 */ + { 21, 0x1010 }, /* R21 - GPIO5 & GPIO6 */ + { 22, 0x8000 }, /* R22 - GPIOCTRL 2 */ + { 23, 0x0800 }, /* R23 - GPIO_POL */ + { 24, 0x008B }, /* R24 - Left Line Input 1&2 Volume */ + { 25, 0x008B }, /* R25 - Left Line Input 3&4 Volume */ + { 26, 0x008B }, /* R26 - Right Line Input 1&2 Volume */ + { 27, 0x008B }, /* R27 - Right Line Input 3&4 Volume */ + { 28, 0x0000 }, /* R28 - Left Output Volume */ + { 29, 0x0000 }, /* R29 - Right Output Volume */ + { 30, 0x0066 }, /* R30 - Line Outputs Volume */ + { 31, 0x0022 }, /* R31 - Out3/4 Volume */ + { 32, 0x0079 }, /* R32 - Left OPGA Volume */ + { 33, 0x0079 }, /* R33 - Right OPGA Volume */ + { 34, 0x0003 }, /* R34 - Speaker Volume */ + { 35, 0x0003 }, /* R35 - ClassD1 */ + + { 37, 0x0100 }, /* R37 - ClassD3 */ + + { 39, 0x0000 }, /* R39 - Input Mixer1 */ + { 40, 0x0000 }, /* R40 - Input Mixer2 */ + { 41, 0x0000 }, /* R41 - Input Mixer3 */ + { 42, 0x0000 }, /* R42 - Input Mixer4 */ + { 43, 0x0000 }, /* R43 - Input Mixer5 */ + { 44, 0x0000 }, /* R44 - Input Mixer6 */ + { 45, 0x0000 }, /* R45 - Output Mixer1 */ + { 46, 0x0000 }, /* R46 - Output Mixer2 */ + { 47, 0x0000 }, /* R47 - Output Mixer3 */ + { 48, 0x0000 }, /* R48 - Output Mixer4 */ + { 49, 0x0000 }, /* R49 - Output Mixer5 */ + { 50, 0x0000 }, /* R50 - Output Mixer6 */ + { 51, 0x0180 }, /* R51 - Out3/4 Mixer */ + { 52, 0x0000 }, /* R52 - Line Mixer1 */ + { 53, 0x0000 }, /* R53 - Line Mixer2 */ + { 54, 0x0000 }, /* R54 - Speaker Mixer */ + { 55, 0x0000 }, /* R55 - Additional Control */ + { 56, 0x0000 }, /* R56 - AntiPOP1 */ + { 57, 0x0000 }, /* R57 - AntiPOP2 */ + { 58, 0x0000 }, /* R58 - MICBIAS */ + + { 60, 0x0008 }, /* R60 - PLL1 */ + { 61, 0x0031 }, /* R61 - PLL2 */ + { 62, 0x0026 }, /* R62 - PLL3 */ +}; + +static bool wm8991_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8991_RESET: + return true; + default: + return false; + } +} + +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(in_pga_tlv, -1650, 150, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(out_mix_tlv, -2100, 300, 0); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(out_pga_tlv, + 0x00, 0x2f, SNDRV_CTL_TLVD_DB_SCALE_ITEM(SNDRV_CTL_TLVD_DB_GAIN_MUTE, 0, 1), + 0x30, 0x7f, SNDRV_CTL_TLVD_DB_SCALE_ITEM(-7300, 100, 0), +); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(out_dac_tlv, + 0x00, 0xbf, SNDRV_CTL_TLVD_DB_SCALE_ITEM(-71625, 375, 1), + 0xc0, 0xff, SNDRV_CTL_TLVD_DB_SCALE_ITEM(0, 0, 0), +); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(in_adc_tlv, + 0x00, 0xef, SNDRV_CTL_TLVD_DB_SCALE_ITEM(-71625, 375, 1), + 0xf0, 0xff, SNDRV_CTL_TLVD_DB_SCALE_ITEM(17625, 0, 0), +); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(out_sidetone_tlv, + 0x00, 0x0c, SNDRV_CTL_TLVD_DB_SCALE_ITEM(-3600, 300, 0), + 0x0d, 0x0f, SNDRV_CTL_TLVD_DB_SCALE_ITEM(0, 0, 0), +); + +static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int reg = kcontrol->private_value & 0xff; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = snd_soc_component_read32(component, reg); + return snd_soc_component_write(component, reg, val | 0x0100); +} + +static const char *wm8991_digital_sidetone[] = +{"None", "Left ADC", "Right ADC", "Reserved"}; + +static SOC_ENUM_SINGLE_DECL(wm8991_left_digital_sidetone_enum, + WM8991_DIGITAL_SIDE_TONE, + WM8991_ADC_TO_DACL_SHIFT, + wm8991_digital_sidetone); + +static SOC_ENUM_SINGLE_DECL(wm8991_right_digital_sidetone_enum, + WM8991_DIGITAL_SIDE_TONE, + WM8991_ADC_TO_DACR_SHIFT, + wm8991_digital_sidetone); + +static const char *wm8991_adcmode[] = +{"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static SOC_ENUM_SINGLE_DECL(wm8991_right_adcmode_enum, + WM8991_ADC_CTRL, + WM8991_ADC_HPF_CUT_SHIFT, + wm8991_adcmode); + +static const struct snd_kcontrol_new wm8991_snd_controls[] = { + /* INMIXL */ + SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0), + SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0), + /* INMIXR */ + SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0), + SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0), + + /* LOMIX */ + SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LLI3LOVOL_SHIFT, WM8991_LLI3LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LR12LOVOL_SHIFT, WM8991_LR12LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LL12LOVOL_SHIFT, WM8991_LL12LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRI3LOVOL_SHIFT, WM8991_LRI3LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + + /* ROMIX */ + SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RRI3ROVOL_SHIFT, WM8991_RRI3ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RL12ROVOL_SHIFT, WM8991_RL12ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RR12ROVOL_SHIFT, WM8991_RR12ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RLI3ROVOL_SHIFT, WM8991_RLI3ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RLBROVOL_SHIFT, WM8991_RLBROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RRBROVOL_SHIFT, WM8991_RRBROVOL_MASK, 1, out_mix_tlv), + + /* LOUT */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8991_LEFT_OUTPUT_VOLUME, + WM8991_LOUTVOL_SHIFT, WM8991_LOUTVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("LOUT ZC", WM8991_LEFT_OUTPUT_VOLUME, WM8991_LOZC_BIT, 1, 0), + + /* ROUT */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8991_RIGHT_OUTPUT_VOLUME, + WM8991_ROUTVOL_SHIFT, WM8991_ROUTVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("ROUT ZC", WM8991_RIGHT_OUTPUT_VOLUME, WM8991_ROZC_BIT, 1, 0), + + /* LOPGA */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8991_LEFT_OPGA_VOLUME, + WM8991_LOPGAVOL_SHIFT, WM8991_LOPGAVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("LOPGA ZC Switch", WM8991_LEFT_OPGA_VOLUME, + WM8991_LOPGAZC_BIT, 1, 0), + + /* ROPGA */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8991_RIGHT_OPGA_VOLUME, + WM8991_ROPGAVOL_SHIFT, WM8991_ROPGAVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("ROPGA ZC Switch", WM8991_RIGHT_OPGA_VOLUME, + WM8991_ROPGAZC_BIT, 1, 0), + + SOC_SINGLE("LON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LONMUTE_BIT, 1, 0), + SOC_SINGLE("LOP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LOPMUTE_BIT, 1, 0), + SOC_SINGLE("LOP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LOATTN_BIT, 1, 0), + SOC_SINGLE("RON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_RONMUTE_BIT, 1, 0), + SOC_SINGLE("ROP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_ROPMUTE_BIT, 1, 0), + SOC_SINGLE("ROP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_ROATTN_BIT, 1, 0), + + SOC_SINGLE("OUT3 Mute Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT3MUTE_BIT, 1, 0), + SOC_SINGLE("OUT3 Attenuation Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT3ATTN_BIT, 1, 0), + + SOC_SINGLE("OUT4 Mute Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT4MUTE_BIT, 1, 0), + SOC_SINGLE("OUT4 Attenuation Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT4ATTN_BIT, 1, 0), + + SOC_SINGLE("Speaker Mode Switch", WM8991_CLASSD1, + WM8991_CDMODE_BIT, 1, 0), + + SOC_SINGLE("Speaker Output Attenuation Volume", WM8991_SPEAKER_VOLUME, + WM8991_SPKVOL_SHIFT, WM8991_SPKVOL_MASK, 0), + SOC_SINGLE("Speaker DC Boost Volume", WM8991_CLASSD3, + WM8991_DCGAIN_SHIFT, WM8991_DCGAIN_MASK, 0), + SOC_SINGLE("Speaker AC Boost Volume", WM8991_CLASSD3, + WM8991_ACGAIN_SHIFT, WM8991_ACGAIN_MASK, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8991_LEFT_DAC_DIGITAL_VOLUME, + WM8991_DACL_VOL_SHIFT, + WM8991_DACL_VOL_MASK, + 0, + out_dac_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8991_RIGHT_DAC_DIGITAL_VOLUME, + WM8991_DACR_VOL_SHIFT, + WM8991_DACR_VOL_MASK, + 0, + out_dac_tlv), + + SOC_ENUM("Left Digital Sidetone", wm8991_left_digital_sidetone_enum), + SOC_ENUM("Right Digital Sidetone", wm8991_right_digital_sidetone_enum), + + SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE, + WM8991_ADCL_DAC_SVOL_SHIFT, WM8991_ADCL_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE, + WM8991_ADCR_DAC_SVOL_SHIFT, WM8991_ADCR_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + + SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8991_ADC_CTRL, + WM8991_ADC_HPF_ENA_BIT, 1, 0), + + SOC_ENUM("ADC HPF Mode", wm8991_right_adcmode_enum), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8991_LEFT_ADC_DIGITAL_VOLUME, + WM8991_ADCL_VOL_SHIFT, + WM8991_ADCL_VOL_MASK, + 0, + in_adc_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8991_RIGHT_ADC_DIGITAL_VOLUME, + WM8991_ADCR_VOL_SHIFT, + WM8991_ADCR_VOL_MASK, + 0, + in_adc_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LIN12VOL_SHIFT, + WM8991_LIN12VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("LIN12 ZC Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LI12ZC_BIT, 1, 0), + + SOC_SINGLE("LIN12 Mute Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LI12MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LIN34VOL_SHIFT, + WM8991_LIN34VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("LIN34 ZC Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LI34ZC_BIT, 1, 0), + + SOC_SINGLE("LIN34 Mute Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LI34MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RIN12VOL_SHIFT, + WM8991_RIN12VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("RIN12 ZC Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RI12ZC_BIT, 1, 0), + + SOC_SINGLE("RIN12 Mute Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RI12MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RIN34VOL_SHIFT, + WM8991_RIN34VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("RIN34 ZC Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RI34ZC_BIT, 1, 0), + + SOC_SINGLE("RIN34 Mute Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RI34MUTE_BIT, 1, 0), +}; + +/* + * _DAPM_ Controls + */ +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u32 reg_shift = kcontrol->private_value & 0xfff; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8991_SPEAKER_MIXER | (WM8991_LDSPK_BIT << 8): + reg = snd_soc_component_read32(component, WM8991_OUTPUT_MIXER1); + if (reg & WM8991_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + + case WM8991_SPEAKER_MIXER | (WM8991_RDSPK_BIT << 8): + reg = snd_soc_component_read32(component, WM8991_OUTPUT_MIXER2); + if (reg & WM8991_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + + case WM8991_OUTPUT_MIXER1 | (WM8991_LDLO_BIT << 8): + reg = snd_soc_component_read32(component, WM8991_SPEAKER_MIXER); + if (reg & WM8991_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + + case WM8991_OUTPUT_MIXER2 | (WM8991_RDRO_BIT << 8): + reg = snd_soc_component_read32(component, WM8991_SPEAKER_MIXER); + if (reg & WM8991_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(in_mix_tlv, -1200, 300, 1); + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8991_dapm_lin12_pga_controls[] = { + SOC_DAPM_SINGLE("LIN1 Switch", WM8991_INPUT_MIXER2, WM8991_LMN1_BIT, 1, 0), + SOC_DAPM_SINGLE("LIN2 Switch", WM8991_INPUT_MIXER2, WM8991_LMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8991_dapm_lin34_pga_controls[] = { + SOC_DAPM_SINGLE("LIN3 Switch", WM8991_INPUT_MIXER2, WM8991_LMN3_BIT, 1, 0), + SOC_DAPM_SINGLE("LIN4 Switch", WM8991_INPUT_MIXER2, WM8991_LMP4_BIT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8991_dapm_rin12_pga_controls[] = { + SOC_DAPM_SINGLE("RIN1 Switch", WM8991_INPUT_MIXER2, WM8991_RMN1_BIT, 1, 0), + SOC_DAPM_SINGLE("RIN2 Switch", WM8991_INPUT_MIXER2, WM8991_RMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8991_dapm_rin34_pga_controls[] = { + SOC_DAPM_SINGLE("RIN3 Switch", WM8991_INPUT_MIXER2, WM8991_RMN3_BIT, 1, 0), + SOC_DAPM_SINGLE("RIN4 Switch", WM8991_INPUT_MIXER2, WM8991_RMP4_BIT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8991_dapm_inmixl_controls[] = { + SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8991_INPUT_MIXER3, + WM8991_LDBVOL_SHIFT, WM8991_LDBVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8991_INPUT_MIXER5, WM8991_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), + SOC_DAPM_SINGLE("LINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + 1, 0), + SOC_DAPM_SINGLE("LINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = { + SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8991_INPUT_MIXER4, + WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), + SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + 1, 0), + SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8991_ainlmux[] = +{"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static SOC_ENUM_SINGLE_DECL(wm8991_ainlmux_enum, + WM8991_INPUT_MIXER1, WM8991_AINLMODE_SHIFT, + wm8991_ainlmux); + +static const struct snd_kcontrol_new wm8991_dapm_ainlmux_controls = + SOC_DAPM_ENUM("Route", wm8991_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8991_ainrmux[] = +{"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static SOC_ENUM_SINGLE_DECL(wm8991_ainrmux_enum, + WM8991_INPUT_MIXER1, WM8991_AINRMODE_SHIFT, + wm8991_ainrmux); + +static const struct snd_kcontrol_new wm8991_dapm_ainrmux_controls = + SOC_DAPM_ENUM("Route", wm8991_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8991_dapm_rxvoice_controls[] = { + SOC_DAPM_SINGLE_TLV("LIN4RXN", WM8991_INPUT_MIXER5, WM8991_LR4BVOL_SHIFT, + WM8991_LR4BVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("RIN4RXP", WM8991_INPUT_MIXER6, WM8991_RL4BVOL_SHIFT, + WM8991_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lomix_controls[] = { + SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LRBLO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LLBLO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LRI3LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LLI3LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LR12LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LL12LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8991_OUTPUT_MIXER1, + WM8991_LDLO_BIT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8991_dapm_romix_controls[] = { + SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RLBRO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RRBRO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RLI3RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RRI3RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RL12RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RR12RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8991_OUTPUT_MIXER2, + WM8991_RDRO_BIT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lonmix_controls[] = { + SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LLOPGALON_BIT, 1, 0), + SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LROPGALON_BIT, 1, 0), + SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8991_LINE_MIXER1, + WM8991_LOPLON_BIT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lopmix_controls[] = { + SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER1, + WM8991_LR12LOP_BIT, 1, 0), + SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER1, + WM8991_LL12LOP_BIT, 1, 0), + SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LLOPGALOP_BIT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8991_dapm_ronmix_controls[] = { + SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RROPGARON_BIT, 1, 0), + SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RLOPGARON_BIT, 1, 0), + SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8991_LINE_MIXER2, + WM8991_ROPRON_BIT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8991_dapm_ropmix_controls[] = { + SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER2, + WM8991_RL12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER2, + WM8991_RR12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RROPGAROP_BIT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8991_dapm_out3mix_controls[] = { + SOC_DAPM_SINGLE("OUT3MIX LIN4RXN Bypass Switch", WM8991_OUT3_4_MIXER, + WM8991_LI4O3_BIT, 1, 0), + SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8991_OUT3_4_MIXER, + WM8991_LPGAO3_BIT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8991_dapm_out4mix_controls[] = { + SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8991_OUT3_4_MIXER, + WM8991_RPGAO4_BIT, 1, 0), + SOC_DAPM_SINGLE("OUT4MIX RIN4RXP Bypass Switch", WM8991_OUT3_4_MIXER, + WM8991_RI4O4_BIT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8991_dapm_spkmix_controls[] = { + SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_LI2SPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_LB2SPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8991_SPEAKER_MIXER, + WM8991_LOPGASPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8991_SPEAKER_MIXER, + WM8991_LDSPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8991_SPEAKER_MIXER, + WM8991_RDSPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8991_SPEAKER_MIXER, + WM8991_ROPGASPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_RL12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_RI2SPK_BIT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8991_dapm_widgets[] = { + /* Input Side */ + /* Input Lines */ + SND_SOC_DAPM_INPUT("LIN1"), + SND_SOC_DAPM_INPUT("LIN2"), + SND_SOC_DAPM_INPUT("LIN3"), + SND_SOC_DAPM_INPUT("LIN4RXN"), + SND_SOC_DAPM_INPUT("RIN3"), + SND_SOC_DAPM_INPUT("RIN4RXP"), + SND_SOC_DAPM_INPUT("RIN1"), + SND_SOC_DAPM_INPUT("RIN2"), + SND_SOC_DAPM_INPUT("Internal ADC Source"), + + SND_SOC_DAPM_SUPPLY("INL", WM8991_POWER_MANAGEMENT_2, + WM8991_AINL_ENA_BIT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("INR", WM8991_POWER_MANAGEMENT_2, + WM8991_AINR_ENA_BIT, 0, NULL, 0), + + /* DACs */ + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8991_POWER_MANAGEMENT_2, + WM8991_ADCL_ENA_BIT, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8991_POWER_MANAGEMENT_2, + WM8991_ADCR_ENA_BIT, 0), + + /* Input PGAs */ + SND_SOC_DAPM_MIXER("LIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN12_ENA_BIT, + 0, &wm8991_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_lin12_pga_controls)), + SND_SOC_DAPM_MIXER("LIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN34_ENA_BIT, + 0, &wm8991_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_lin34_pga_controls)), + SND_SOC_DAPM_MIXER("RIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN12_ENA_BIT, + 0, &wm8991_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_rin12_pga_controls)), + SND_SOC_DAPM_MIXER("RIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN34_ENA_BIT, + 0, &wm8991_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_rin34_pga_controls)), + + /* INMIXL */ + SND_SOC_DAPM_MIXER("INMIXL", SND_SOC_NOPM, 0, 0, + &wm8991_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8991_dapm_inmixl_controls)), + + /* AINLMUX */ + SND_SOC_DAPM_MUX("AINLMUX", SND_SOC_NOPM, 0, 0, + &wm8991_dapm_ainlmux_controls), + + /* INMIXR */ + SND_SOC_DAPM_MIXER("INMIXR", SND_SOC_NOPM, 0, 0, + &wm8991_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8991_dapm_inmixr_controls)), + + /* AINRMUX */ + SND_SOC_DAPM_MUX("AINRMUX", SND_SOC_NOPM, 0, 0, + &wm8991_dapm_ainrmux_controls), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8991_POWER_MANAGEMENT_3, + WM8991_DACL_ENA_BIT, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8991_POWER_MANAGEMENT_3, + WM8991_DACR_ENA_BIT, 0), + + /* LOMIX */ + SND_SOC_DAPM_MIXER_E("LOMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOMIX_ENA_BIT, + 0, &wm8991_dapm_lomix_controls[0], + ARRAY_SIZE(wm8991_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + + /* LONMIX */ + SND_SOC_DAPM_MIXER("LONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LON_ENA_BIT, 0, + &wm8991_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8991_dapm_lonmix_controls)), + + /* LOPMIX */ + SND_SOC_DAPM_MIXER("LOPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOP_ENA_BIT, 0, + &wm8991_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8991_dapm_lopmix_controls)), + + /* OUT3MIX */ + SND_SOC_DAPM_MIXER("OUT3MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT3_ENA_BIT, 0, + &wm8991_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8991_dapm_out3mix_controls)), + + /* SPKMIX */ + SND_SOC_DAPM_MIXER_E("SPKMIX", WM8991_POWER_MANAGEMENT_1, WM8991_SPK_ENA_BIT, 0, + &wm8991_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8991_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + + /* OUT4MIX */ + SND_SOC_DAPM_MIXER("OUT4MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT4_ENA_BIT, 0, + &wm8991_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8991_dapm_out4mix_controls)), + + /* ROPMIX */ + SND_SOC_DAPM_MIXER("ROPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROP_ENA_BIT, 0, + &wm8991_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8991_dapm_ropmix_controls)), + + /* RONMIX */ + SND_SOC_DAPM_MIXER("RONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_RON_ENA_BIT, 0, + &wm8991_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8991_dapm_ronmix_controls)), + + /* ROMIX */ + SND_SOC_DAPM_MIXER_E("ROMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROMIX_ENA_BIT, + 0, &wm8991_dapm_romix_controls[0], + ARRAY_SIZE(wm8991_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + + /* LOUT PGA */ + SND_SOC_DAPM_PGA("LOUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_LOUT_ENA_BIT, 0, + NULL, 0), + + /* ROUT PGA */ + SND_SOC_DAPM_PGA("ROUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_ROUT_ENA_BIT, 0, + NULL, 0), + + /* LOPGA */ + SND_SOC_DAPM_PGA("LOPGA", WM8991_POWER_MANAGEMENT_3, WM8991_LOPGA_ENA_BIT, 0, + NULL, 0), + + /* ROPGA */ + SND_SOC_DAPM_PGA("ROPGA", WM8991_POWER_MANAGEMENT_3, WM8991_ROPGA_ENA_BIT, 0, + NULL, 0), + + /* MICBIAS */ + SND_SOC_DAPM_SUPPLY("MICBIAS", WM8991_POWER_MANAGEMENT_1, + WM8991_MICBIAS_ENA_BIT, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LON"), + SND_SOC_DAPM_OUTPUT("LOP"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("SPKN"), + SND_SOC_DAPM_OUTPUT("SPKP"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("OUT4"), + SND_SOC_DAPM_OUTPUT("ROP"), + SND_SOC_DAPM_OUTPUT("RON"), + SND_SOC_DAPM_OUTPUT("OUT"), + + SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route wm8991_dapm_routes[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + {"INMIXL", NULL, "INL"}, + {"AINLMUX", NULL, "INL"}, + {"INMIXR", NULL, "INR"}, + {"AINRMUX", NULL, "INR"}, + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4RXN"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AINLMUX */ + {"AINLMUX", "INMIXL Mix", "INMIXL"}, + {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AINLMUX", "RXVOICE Mix", "LIN4RXN"}, + {"AINLMUX", "RXVOICE Mix", "RIN4RXP"}, + /* ADC */ + {"Left ADC", NULL, "AINLMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4RXP"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AINRMUX */ + {"AINRMUX", "INMIXR Mix", "INMIXR"}, + {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AINRMUX", "RXVOICE Mix", "LIN4RXN"}, + {"AINRMUX", "RXVOICE Mix", "RIN4RXP"}, + /* ADC */ + {"Right ADC", NULL, "AINRMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4RXN Bypass Switch", "LIN4RXN"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4RXP Bypass Switch", "RIN4RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +/* PLL divisors */ +struct _pll_div { + u32 div2; + u32 n; + u32 k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 16) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8991 N value outwith recommended range! N = %d\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8991_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, int src, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + struct snd_soc_component *component = codec_dai->component; + struct _pll_div pll_div; + + if (freq_in && freq_out) { + pll_factors(&pll_div, freq_out * 4, freq_in); + + /* Turn on PLL */ + reg = snd_soc_component_read32(component, WM8991_POWER_MANAGEMENT_2); + reg |= WM8991_PLL_ENA; + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_2, reg); + + /* sysclk comes from PLL */ + reg = snd_soc_component_read32(component, WM8991_CLOCKING_2); + snd_soc_component_write(component, WM8991_CLOCKING_2, reg | WM8991_SYSCLK_SRC); + + /* set up N , fractional mode and pre-divisor if necessary */ + snd_soc_component_write(component, WM8991_PLL1, pll_div.n | WM8991_SDM | + (pll_div.div2 ? WM8991_PRESCALE : 0)); + snd_soc_component_write(component, WM8991_PLL2, (u8)(pll_div.k>>8)); + snd_soc_component_write(component, WM8991_PLL3, (u8)(pll_div.k & 0xFF)); + } else { + /* Turn on PLL */ + reg = snd_soc_component_read32(component, WM8991_POWER_MANAGEMENT_2); + reg &= ~WM8991_PLL_ENA; + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_2, reg); + } + return 0; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8991_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 audio1, audio3; + + audio1 = snd_soc_component_read32(component, WM8991_AUDIO_INTERFACE_1); + audio3 = snd_soc_component_read32(component, WM8991_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8991_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8991_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8991_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8991_AIF_TMF_I2S; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8991_AIF_TMF_RIGHTJ; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8991_AIF_TMF_LEFTJ; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8991_AIF_TMF_DSP; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8991_AIF_TMF_DSP | WM8991_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8991_AUDIO_INTERFACE_1, audio1); + snd_soc_component_write(component, WM8991_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8991_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + u16 reg; + + switch (div_id) { + case WM8991_MCLK_DIV: + reg = snd_soc_component_read32(component, WM8991_CLOCKING_2) & + ~WM8991_MCLK_DIV_MASK; + snd_soc_component_write(component, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_DACCLK_DIV: + reg = snd_soc_component_read32(component, WM8991_CLOCKING_2) & + ~WM8991_DAC_CLKDIV_MASK; + snd_soc_component_write(component, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_ADCCLK_DIV: + reg = snd_soc_component_read32(component, WM8991_CLOCKING_2) & + ~WM8991_ADC_CLKDIV_MASK; + snd_soc_component_write(component, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_BCLK_DIV: + reg = snd_soc_component_read32(component, WM8991_CLOCKING_1) & + ~WM8991_BCLK_DIV_MASK; + snd_soc_component_write(component, WM8991_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8991_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + u16 audio1 = snd_soc_component_read32(component, WM8991_AUDIO_INTERFACE_1); + + audio1 &= ~WM8991_AIF_WL_MASK; + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + audio1 |= WM8991_AIF_WL_20BITS; + break; + case 24: + audio1 |= WM8991_AIF_WL_24BITS; + break; + case 32: + audio1 |= WM8991_AIF_WL_32BITS; + break; + } + + snd_soc_component_write(component, WM8991_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8991_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + u16 val; + + val = snd_soc_component_read32(component, WM8991_DAC_CTRL) & ~WM8991_DAC_MUTE; + if (mute) + snd_soc_component_write(component, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE); + else + snd_soc_component_write(component, WM8991_DAC_CTRL, val); + return 0; +} + +static int wm8991_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8991_priv *wm8991 = snd_soc_component_get_drvdata(component); + u16 val; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + val = snd_soc_component_read32(component, WM8991_POWER_MANAGEMENT_1) & + ~WM8991_VMID_MODE_MASK; + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, val | 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8991->regmap); + /* Enable all output discharge bits */ + snd_soc_component_write(component, WM8991_ANTIPOP1, WM8991_DIS_LLINE | + WM8991_DIS_RLINE | WM8991_DIS_OUT3 | + WM8991_DIS_OUT4 | WM8991_DIS_LOUT | + WM8991_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + snd_soc_component_write(component, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_VMIDTOG); + + /* Delay to allow output caps to discharge */ + msleep(300); + + /* Disable VMIDTOG */ + snd_soc_component_write(component, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL); + + /* disable all output discharge bits */ + snd_soc_component_write(component, WM8991_ANTIPOP1, 0); + + /* Enable outputs */ + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, 0x1b00); + + msleep(50); + + /* Enable VMID at 2x50k */ + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, 0x1f02); + + msleep(100); + + /* Enable VREF */ + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, 0x1f03); + + msleep(600); + + /* Enable BUFIOEN */ + snd_soc_component_write(component, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_BUFIOEN); + + /* Disable outputs */ + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, 0x3); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8991_ANTIPOP2, WM8991_BUFIOEN); + } + + /* VMID=2*250k */ + val = snd_soc_component_read32(component, WM8991_POWER_MANAGEMENT_1) & + ~WM8991_VMID_MODE_MASK; + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, val | 0x4); + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + snd_soc_component_write(component, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_POBCTRL | WM8991_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_BUFIOEN); + + /* mute DAC */ + val = snd_soc_component_read32(component, WM8991_DAC_CTRL); + snd_soc_component_write(component, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE); + + /* Enable any disabled outputs */ + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, 0x1f03); + + /* Disable VMID */ + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, 0x1f01); + + msleep(300); + + /* Enable all output discharge bits */ + snd_soc_component_write(component, WM8991_ANTIPOP1, WM8991_DIS_LLINE | + WM8991_DIS_RLINE | WM8991_DIS_OUT3 | + WM8991_DIS_OUT4 | WM8991_DIS_LOUT | + WM8991_DIS_ROUT); + + /* Disable VREF */ + snd_soc_component_write(component, WM8991_POWER_MANAGEMENT_1, 0x0); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_component_write(component, WM8991_ANTIPOP2, 0x0); + regcache_mark_dirty(wm8991->regmap); + break; + } + + return 0; +} + +#define WM8991_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm8991_ops = { + .hw_params = wm8991_hw_params, + .digital_mute = wm8991_mute, + .set_fmt = wm8991_set_dai_fmt, + .set_clkdiv = wm8991_set_dai_clkdiv, + .set_pll = wm8991_set_dai_pll +}; + +/* + * The WM8991 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +static struct snd_soc_dai_driver wm8991_dai = { + /* ADC/DAC on primary */ + .name = "wm8991", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8991_FORMATS + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8991_FORMATS + }, + .ops = &wm8991_ops +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8991 = { + .set_bias_level = wm8991_set_bias_level, + .controls = wm8991_snd_controls, + .num_controls = ARRAY_SIZE(wm8991_snd_controls), + .dapm_widgets = wm8991_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8991_dapm_widgets), + .dapm_routes = wm8991_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8991_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8991_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM8991_PLL3, + .volatile_reg = wm8991_volatile, + .reg_defaults = wm8991_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8991_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static int wm8991_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8991_priv *wm8991; + unsigned int val; + int ret; + + wm8991 = devm_kzalloc(&i2c->dev, sizeof(*wm8991), GFP_KERNEL); + if (!wm8991) + return -ENOMEM; + + wm8991->regmap = devm_regmap_init_i2c(i2c, &wm8991_regmap); + if (IS_ERR(wm8991->regmap)) + return PTR_ERR(wm8991->regmap); + + i2c_set_clientdata(i2c, wm8991); + + ret = regmap_read(wm8991->regmap, WM8991_RESET, &val); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read device ID: %d\n", ret); + return ret; + } + if (val != 0x8991) { + dev_err(&i2c->dev, "Device with ID %x is not a WM8991\n", val); + return -EINVAL; + } + + ret = regmap_write(wm8991->regmap, WM8991_RESET, 0); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + regmap_update_bits(wm8991->regmap, WM8991_AUDIO_INTERFACE_4, + WM8991_ALRCGPIO1, WM8991_ALRCGPIO1); + + regmap_update_bits(wm8991->regmap, WM8991_GPIO1_GPIO2, + WM8991_GPIO1_SEL_MASK, 1); + + regmap_update_bits(wm8991->regmap, WM8991_POWER_MANAGEMENT_1, + WM8991_VREF_ENA | WM8991_VMID_MODE_MASK, + WM8991_VREF_ENA | WM8991_VMID_MODE_MASK); + + regmap_update_bits(wm8991->regmap, WM8991_POWER_MANAGEMENT_2, + WM8991_OPCLK_ENA, WM8991_OPCLK_ENA); + + regmap_write(wm8991->regmap, WM8991_DAC_CTRL, 0); + regmap_write(wm8991->regmap, WM8991_LEFT_OUTPUT_VOLUME, + 0x50 | (1<<8)); + regmap_write(wm8991->regmap, WM8991_RIGHT_OUTPUT_VOLUME, + 0x50 | (1<<8)); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8991, &wm8991_dai, 1); + + return ret; +} + +static const struct i2c_device_id wm8991_i2c_id[] = { + { "wm8991", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8991_i2c_id); + +static struct i2c_driver wm8991_i2c_driver = { + .driver = { + .name = "wm8991", + }, + .probe = wm8991_i2c_probe, + .id_table = wm8991_i2c_id, +}; + +module_i2c_driver(wm8991_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8991 driver"); +MODULE_AUTHOR("Graeme Gregory"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8991.h b/sound/soc/codecs/wm8991.h new file mode 100644 index 000000000..08ed38330 --- /dev/null +++ b/sound/soc/codecs/wm8991.h @@ -0,0 +1,819 @@ +/* + * wm8991.h -- audio driver for WM8991 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8991_H +#define _WM8991_H + +/* + * Register values. + */ +#define WM8991_RESET 0x00 +#define WM8991_POWER_MANAGEMENT_1 0x01 +#define WM8991_POWER_MANAGEMENT_2 0x02 +#define WM8991_POWER_MANAGEMENT_3 0x03 +#define WM8991_AUDIO_INTERFACE_1 0x04 +#define WM8991_AUDIO_INTERFACE_2 0x05 +#define WM8991_CLOCKING_1 0x06 +#define WM8991_CLOCKING_2 0x07 +#define WM8991_AUDIO_INTERFACE_3 0x08 +#define WM8991_AUDIO_INTERFACE_4 0x09 +#define WM8991_DAC_CTRL 0x0A +#define WM8991_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8991_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8991_DIGITAL_SIDE_TONE 0x0D +#define WM8991_ADC_CTRL 0x0E +#define WM8991_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8991_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8991_GPIO_CTRL_1 0x12 +#define WM8991_GPIO1_GPIO2 0x13 +#define WM8991_GPIO3_GPIO4 0x14 +#define WM8991_GPIO5_GPIO6 0x15 +#define WM8991_GPIOCTRL_2 0x16 +#define WM8991_GPIO_POL 0x17 +#define WM8991_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8991_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8991_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8991_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8991_LEFT_OUTPUT_VOLUME 0x1C +#define WM8991_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8991_LINE_OUTPUTS_VOLUME 0x1E +#define WM8991_OUT3_4_VOLUME 0x1F +#define WM8991_LEFT_OPGA_VOLUME 0x20 +#define WM8991_RIGHT_OPGA_VOLUME 0x21 +#define WM8991_SPEAKER_VOLUME 0x22 +#define WM8991_CLASSD1 0x23 +#define WM8991_CLASSD3 0x25 +#define WM8991_INPUT_MIXER1 0x27 +#define WM8991_INPUT_MIXER2 0x28 +#define WM8991_INPUT_MIXER3 0x29 +#define WM8991_INPUT_MIXER4 0x2A +#define WM8991_INPUT_MIXER5 0x2B +#define WM8991_INPUT_MIXER6 0x2C +#define WM8991_OUTPUT_MIXER1 0x2D +#define WM8991_OUTPUT_MIXER2 0x2E +#define WM8991_OUTPUT_MIXER3 0x2F +#define WM8991_OUTPUT_MIXER4 0x30 +#define WM8991_OUTPUT_MIXER5 0x31 +#define WM8991_OUTPUT_MIXER6 0x32 +#define WM8991_OUT3_4_MIXER 0x33 +#define WM8991_LINE_MIXER1 0x34 +#define WM8991_LINE_MIXER2 0x35 +#define WM8991_SPEAKER_MIXER 0x36 +#define WM8991_ADDITIONAL_CONTROL 0x37 +#define WM8991_ANTIPOP1 0x38 +#define WM8991_ANTIPOP2 0x39 +#define WM8991_MICBIAS 0x3A +#define WM8991_PLL1 0x3C +#define WM8991_PLL2 0x3D +#define WM8991_PLL3 0x3E + +#define WM8991_REGISTER_COUNT 60 +#define WM8991_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Reset + */ +#define WM8991_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8991_SPK_ENA 0x1000 /* SPK_ENA */ +#define WM8991_SPK_ENA_BIT 12 +#define WM8991_OUT3_ENA 0x0800 /* OUT3_ENA */ +#define WM8991_OUT3_ENA_BIT 11 +#define WM8991_OUT4_ENA 0x0400 /* OUT4_ENA */ +#define WM8991_OUT4_ENA_BIT 10 +#define WM8991_LOUT_ENA 0x0200 /* LOUT_ENA */ +#define WM8991_LOUT_ENA_BIT 9 +#define WM8991_ROUT_ENA 0x0100 /* ROUT_ENA */ +#define WM8991_ROUT_ENA_BIT 8 +#define WM8991_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */ +#define WM8991_MICBIAS_ENA_BIT 4 +#define WM8991_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */ +#define WM8991_VREF_ENA 0x0001 /* VREF_ENA */ +#define WM8991_VREF_ENA_BIT 0 + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8991_PLL_ENA 0x8000 /* PLL_ENA */ +#define WM8991_PLL_ENA_BIT 15 +#define WM8991_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8991_TSHUT_ENA_BIT 14 +#define WM8991_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8991_TSHUT_OPDIS_BIT 13 +#define WM8991_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8991_OPCLK_ENA_BIT 11 +#define WM8991_AINL_ENA 0x0200 /* AINL_ENA */ +#define WM8991_AINL_ENA_BIT 9 +#define WM8991_AINR_ENA 0x0100 /* AINR_ENA */ +#define WM8991_AINR_ENA_BIT 8 +#define WM8991_LIN34_ENA 0x0080 /* LIN34_ENA */ +#define WM8991_LIN34_ENA_BIT 7 +#define WM8991_LIN12_ENA 0x0040 /* LIN12_ENA */ +#define WM8991_LIN12_ENA_BIT 6 +#define WM8991_RIN34_ENA 0x0020 /* RIN34_ENA */ +#define WM8991_RIN34_ENA_BIT 5 +#define WM8991_RIN12_ENA 0x0010 /* RIN12_ENA */ +#define WM8991_RIN12_ENA_BIT 4 +#define WM8991_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8991_ADCL_ENA_BIT 1 +#define WM8991_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8991_ADCR_ENA_BIT 0 + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8991_LON_ENA 0x2000 /* LON_ENA */ +#define WM8991_LON_ENA_BIT 13 +#define WM8991_LOP_ENA 0x1000 /* LOP_ENA */ +#define WM8991_LOP_ENA_BIT 12 +#define WM8991_RON_ENA 0x0800 /* RON_ENA */ +#define WM8991_RON_ENA_BIT 11 +#define WM8991_ROP_ENA 0x0400 /* ROP_ENA */ +#define WM8991_ROP_ENA_BIT 10 +#define WM8991_LOPGA_ENA 0x0080 /* LOPGA_ENA */ +#define WM8991_LOPGA_ENA_BIT 7 +#define WM8991_ROPGA_ENA 0x0040 /* ROPGA_ENA */ +#define WM8991_ROPGA_ENA_BIT 6 +#define WM8991_LOMIX_ENA 0x0020 /* LOMIX_ENA */ +#define WM8991_LOMIX_ENA_BIT 5 +#define WM8991_ROMIX_ENA 0x0010 /* ROMIX_ENA */ +#define WM8991_ROMIX_ENA_BIT 4 +#define WM8991_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8991_DACL_ENA_BIT 1 +#define WM8991_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8991_DACR_ENA_BIT 0 + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8991_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8991_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8991_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8991_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8991_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8991_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8991_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8991_AIF_WL_16BITS (0 << 5) +#define WM8991_AIF_WL_20BITS (1 << 5) +#define WM8991_AIF_WL_24BITS (2 << 5) +#define WM8991_AIF_WL_32BITS (3 << 5) +#define WM8991_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8991_AIF_TMF_RIGHTJ (0 << 3) +#define WM8991_AIF_TMF_LEFTJ (1 << 3) +#define WM8991_AIF_TMF_I2S (2 << 3) +#define WM8991_AIF_TMF_DSP (3 << 3) + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8991_DACL_SRC 0x8000 /* DACL_SRC */ +#define WM8991_DACR_SRC 0x4000 /* DACR_SRC */ +#define WM8991_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8991_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8991_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */ +#define WM8991_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8991_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8991_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8991_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8991_LOOPBACK 0x0001 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking (1) + */ +#define WM8991_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8991_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8991_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */ +#define WM8991_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8991_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8991_BCLK_DIV_1 (0x0 << 1) +#define WM8991_BCLK_DIV_1_5 (0x1 << 1) +#define WM8991_BCLK_DIV_2 (0x2 << 1) +#define WM8991_BCLK_DIV_3 (0x3 << 1) +#define WM8991_BCLK_DIV_4 (0x4 << 1) +#define WM8991_BCLK_DIV_5_5 (0x5 << 1) +#define WM8991_BCLK_DIV_6 (0x6 << 1) +#define WM8991_BCLK_DIV_8 (0x7 << 1) +#define WM8991_BCLK_DIV_11 (0x8 << 1) +#define WM8991_BCLK_DIV_12 (0x9 << 1) +#define WM8991_BCLK_DIV_16 (0xA << 1) +#define WM8991_BCLK_DIV_22 (0xB << 1) +#define WM8991_BCLK_DIV_24 (0xC << 1) +#define WM8991_BCLK_DIV_32 (0xD << 1) +#define WM8991_BCLK_DIV_44 (0xE << 1) +#define WM8991_BCLK_DIV_48 (0xF << 1) + +/* + * R7 (0x07) - Clocking (2) + */ +#define WM8991_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8991_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8991_CLK_FORCE 0x2000 /* CLK_FORCE */ +#define WM8991_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */ +#define WM8991_MCLK_DIV_1 (0 << 11) +#define WM8991_MCLK_DIV_2 ( 2 << 11) +#define WM8991_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8991_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV - [7:5] */ +#define WM8991_ADC_CLKDIV_1 (0 << 5) +#define WM8991_ADC_CLKDIV_1_5 (1 << 5) +#define WM8991_ADC_CLKDIV_2 (2 << 5) +#define WM8991_ADC_CLKDIV_3 (3 << 5) +#define WM8991_ADC_CLKDIV_4 (4 << 5) +#define WM8991_ADC_CLKDIV_5_5 (5 << 5) +#define WM8991_ADC_CLKDIV_6 (6 << 5) +#define WM8991_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */ +#define WM8991_DAC_CLKDIV_1 (0 << 2) +#define WM8991_DAC_CLKDIV_1_5 (1 << 2) +#define WM8991_DAC_CLKDIV_2 (2 << 2) +#define WM8991_DAC_CLKDIV_3 (3 << 2) +#define WM8991_DAC_CLKDIV_4 (4 << 2) +#define WM8991_DAC_CLKDIV_5_5 (5 << 2) +#define WM8991_DAC_CLKDIV_6 (6 << 2) + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8991_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8991_AIF_MSTR2 0x4000 /* AIF_MSTR2 */ +#define WM8991_AIF_SEL 0x2000 /* AIF_SEL */ +#define WM8991_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */ +#define WM8991_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE - [10:0] */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8991_ALRCGPIO1 0x8000 /* ALRCGPIO1 */ +#define WM8991_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */ +#define WM8991_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8991_DACLRC_DIR 0x0800 /* DACLRC_DIR */ +#define WM8991_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE - [10:0] */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8991_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */ +#define WM8991_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8991_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8991_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8991_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */ +#define WM8991_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */ +#define WM8991_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8991_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8991_DACR_DATINV 0x0001 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8991_DAC_VU 0x0100 /* DAC_VU */ +#define WM8991_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8991_DACL_VOL_SHIFT 0 +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8991_DAC_VU 0x0100 /* DAC_VU */ +#define WM8991_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8991_DACR_VOL_SHIFT 0 +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8991_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL - [12:9] */ +#define WM8991_ADCL_DAC_SVOL_SHIFT 9 +#define WM8991_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL - [8:5] */ +#define WM8991_ADCR_DAC_SVOL_SHIFT 5 +#define WM8991_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */ +#define WM8991_ADC_TO_DACL_SHIFT 2 +#define WM8991_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */ +#define WM8991_ADC_TO_DACR_SHIFT 0 + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8991_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */ +#define WM8991_ADC_HPF_ENA_BIT 8 +#define WM8991_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */ +#define WM8991_ADC_HPF_CUT_SHIFT 5 +#define WM8991_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8991_ADCL_DATINV_BIT 1 +#define WM8991_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8991_ADCR_DATINV_BIT 0 + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8991_ADC_VU 0x0100 /* ADC_VU */ +#define WM8991_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8991_ADCL_VOL_SHIFT 0 + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8991_ADC_VU 0x0100 /* ADC_VU */ +#define WM8991_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8991_ADCR_VOL_SHIFT 0 + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8991_IRQ 0x1000 /* IRQ */ +#define WM8991_TEMPOK 0x0800 /* TEMPOK */ +#define WM8991_MICSHRT 0x0400 /* MICSHRT */ +#define WM8991_MICDET 0x0200 /* MICDET */ +#define WM8991_PLL_LCK 0x0100 /* PLL_LCK */ +#define WM8991_GPI8_STATUS 0x0080 /* GPI8_STATUS */ +#define WM8991_GPI7_STATUS 0x0040 /* GPI7_STATUS */ +#define WM8991_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */ +#define WM8991_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */ +#define WM8991_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */ +#define WM8991_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */ +#define WM8991_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */ +#define WM8991_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */ + +/* + * R19 (0x13) - GPIO1 & GPIO2 + */ +#define WM8991_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */ +#define WM8991_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */ +#define WM8991_GPIO2_PU 0x2000 /* GPIO2_PU */ +#define WM8991_GPIO2_PD 0x1000 /* GPIO2_PD */ +#define WM8991_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */ +#define WM8991_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */ +#define WM8991_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */ +#define WM8991_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8991_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8991_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - GPIO3 & GPIO4 + */ +#define WM8991_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */ +#define WM8991_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */ +#define WM8991_GPIO4_PU 0x2000 /* GPIO4_PU */ +#define WM8991_GPIO4_PD 0x1000 /* GPIO4_PD */ +#define WM8991_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */ +#define WM8991_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */ +#define WM8991_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */ +#define WM8991_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8991_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8991_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ + +/* + * R21 (0x15) - GPIO5 & GPIO6 + */ +#define WM8991_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */ +#define WM8991_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */ +#define WM8991_GPIO6_PU 0x2000 /* GPIO6_PU */ +#define WM8991_GPIO6_PD 0x1000 /* GPIO6_PD */ +#define WM8991_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */ +#define WM8991_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */ +#define WM8991_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */ +#define WM8991_GPIO5_PU 0x0020 /* GPIO5_PU */ +#define WM8991_GPIO5_PD 0x0010 /* GPIO5_PD */ +#define WM8991_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8991_RD_3W_ENA 0x8000 /* RD_3W_ENA */ +#define WM8991_MODE_3W4W 0x4000 /* MODE_3W4W */ +#define WM8991_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */ +#define WM8991_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */ +#define WM8991_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */ +#define WM8991_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */ +#define WM8991_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */ +#define WM8991_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */ +#define WM8991_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8991_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */ +#define WM8991_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */ +#define WM8991_GPI7_ENA 0x0001 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8991_IRQ_INV 0x1000 /* IRQ_INV */ +#define WM8991_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8991_MICSHRT_POL 0x0400 /* MICSHRT_POL */ +#define WM8991_MICDET_POL 0x0200 /* MICDET_POL */ +#define WM8991_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */ +#define WM8991_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8991_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8991_GPIO6_POL 0x0020 /* GPIO6_POL */ +#define WM8991_GPIO5_POL 0x0010 /* GPIO5_POL */ +#define WM8991_GPIO4_POL 0x0008 /* GPIO4_POL */ +#define WM8991_GPIO3_POL 0x0004 /* GPIO3_POL */ +#define WM8991_GPIO2_POL 0x0002 /* GPIO2_POL */ +#define WM8991_GPIO1_POL 0x0001 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_LI12MUTE 0x0080 /* LI12MUTE */ +#define WM8991_LI12MUTE_BIT 7 +#define WM8991_LI12ZC 0x0040 /* LI12ZC */ +#define WM8991_LI12ZC_BIT 6 +#define WM8991_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */ +#define WM8991_LIN12VOL_SHIFT 0 +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_LI34MUTE 0x0080 /* LI34MUTE */ +#define WM8991_LI34MUTE_BIT 7 +#define WM8991_LI34ZC 0x0040 /* LI34ZC */ +#define WM8991_LI34ZC_BIT 6 +#define WM8991_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */ +#define WM8991_LIN34VOL_SHIFT 0 + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_RI12MUTE 0x0080 /* RI12MUTE */ +#define WM8991_RI12MUTE_BIT 7 +#define WM8991_RI12ZC 0x0040 /* RI12ZC */ +#define WM8991_RI12ZC_BIT 6 +#define WM8991_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */ +#define WM8991_RIN12VOL_SHIFT 0 + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_RI34MUTE 0x0080 /* RI34MUTE */ +#define WM8991_RI34MUTE_BIT 7 +#define WM8991_RI34ZC 0x0040 /* RI34ZC */ +#define WM8991_RI34ZC_BIT 6 +#define WM8991_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */ +#define WM8991_RIN34VOL_SHIFT 0 + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_LOZC 0x0080 /* LOZC */ +#define WM8991_LOZC_BIT 7 +#define WM8991_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8991_LOUTVOL_SHIFT 0 +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_ROZC 0x0080 /* ROZC */ +#define WM8991_ROZC_BIT 7 +#define WM8991_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8991_ROUTVOL_SHIFT 0 +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8991_LONMUTE 0x0040 /* LONMUTE */ +#define WM8991_LONMUTE_BIT 6 +#define WM8991_LOPMUTE 0x0020 /* LOPMUTE */ +#define WM8991_LOPMUTE_BIT 5 +#define WM8991_LOATTN 0x0010 /* LOATTN */ +#define WM8991_LOATTN_BIT 4 +#define WM8991_RONMUTE 0x0004 /* RONMUTE */ +#define WM8991_RONMUTE_BIT 2 +#define WM8991_ROPMUTE 0x0002 /* ROPMUTE */ +#define WM8991_ROPMUTE_BIT 1 +#define WM8991_ROATTN 0x0001 /* ROATTN */ +#define WM8991_ROATTN_BIT 0 + +/* + * R31 (0x1F) - Out3/4 Volume + */ +#define WM8991_OUT3MUTE 0x0020 /* OUT3MUTE */ +#define WM8991_OUT3MUTE_BIT 5 +#define WM8991_OUT3ATTN 0x0010 /* OUT3ATTN */ +#define WM8991_OUT3ATTN_BIT 4 +#define WM8991_OUT4MUTE 0x0002 /* OUT4MUTE */ +#define WM8991_OUT4MUTE_BIT 1 +#define WM8991_OUT4ATTN 0x0001 /* OUT4ATTN */ +#define WM8991_OUT4ATTN_BIT 0 + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_LOPGAZC 0x0080 /* LOPGAZC */ +#define WM8991_LOPGAZC_BIT 7 +#define WM8991_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */ +#define WM8991_LOPGAVOL_SHIFT 0 + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_ROPGAZC 0x0080 /* ROPGAZC */ +#define WM8991_ROPGAZC_BIT 7 +#define WM8991_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */ +#define WM8991_ROPGAVOL_SHIFT 0 +/* + * R34 (0x22) - Speaker Volume + */ +#define WM8991_SPKVOL_MASK 0x0003 /* SPKVOL - [1:0] */ +#define WM8991_SPKVOL_SHIFT 0 + +/* + * R35 (0x23) - ClassD1 + */ +#define WM8991_CDMODE 0x0100 /* CDMODE */ +#define WM8991_CDMODE_BIT 8 + +/* + * R37 (0x25) - ClassD3 + */ +#define WM8991_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */ +#define WM8991_DCGAIN_SHIFT 3 +#define WM8991_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */ +#define WM8991_ACGAIN_SHIFT 0 +/* + * R39 (0x27) - Input Mixer1 + */ +#define WM8991_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */ +#define WM8991_AINLMODE_SHIFT 2 +#define WM8991_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */ +#define WM8991_AINRMODE_SHIFT 0 + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8991_LMP4 0x0080 /* LMP4 */ +#define WM8991_LMP4_BIT 7 /* LMP4 */ +#define WM8991_LMN3 0x0040 /* LMN3 */ +#define WM8991_LMN3_BIT 6 /* LMN3 */ +#define WM8991_LMP2 0x0020 /* LMP2 */ +#define WM8991_LMP2_BIT 5 /* LMP2 */ +#define WM8991_LMN1 0x0010 /* LMN1 */ +#define WM8991_LMN1_BIT 4 /* LMN1 */ +#define WM8991_RMP4 0x0008 /* RMP4 */ +#define WM8991_RMP4_BIT 3 /* RMP4 */ +#define WM8991_RMN3 0x0004 /* RMN3 */ +#define WM8991_RMN3_BIT 2 /* RMN3 */ +#define WM8991_RMP2 0x0002 /* RMP2 */ +#define WM8991_RMP2_BIT 1 /* RMP2 */ +#define WM8991_RMN1 0x0001 /* RMN1 */ +#define WM8991_RMN1_BIT 0 /* RMN1 */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8991_L34MNB 0x0100 /* L34MNB */ +#define WM8991_L34MNB_BIT 8 +#define WM8991_L34MNBST 0x0080 /* L34MNBST */ +#define WM8991_L34MNBST_BIT 7 +#define WM8991_L12MNB 0x0020 /* L12MNB */ +#define WM8991_L12MNB_BIT 5 +#define WM8991_L12MNBST 0x0010 /* L12MNBST */ +#define WM8991_L12MNBST_BIT 4 +#define WM8991_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */ +#define WM8991_LDBVOL_SHIFT 0 + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8991_R34MNB 0x0100 /* R34MNB */ +#define WM8991_R34MNB_BIT 8 +#define WM8991_R34MNBST 0x0080 /* R34MNBST */ +#define WM8991_R34MNBST_BIT 7 +#define WM8991_R12MNB 0x0020 /* R12MNB */ +#define WM8991_R12MNB_BIT 5 +#define WM8991_R12MNBST 0x0010 /* R12MNBST */ +#define WM8991_R12MNBST_BIT 4 +#define WM8991_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */ +#define WM8991_RDBVOL_SHIFT 0 + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8991_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */ +#define WM8991_LI2BVOL_SHIFT 6 +#define WM8991_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */ +#define WM8991_LR4BVOL_SHIFT 3 +#define WM8991_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */ +#define WM8991_LL4BVOL_SHIFT 0 + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8991_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */ +#define WM8991_RI2BVOL_SHIFT 6 +#define WM8991_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */ +#define WM8991_RL4BVOL_SHIFT 3 +#define WM8991_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */ +#define WM8991_RR4BVOL_SHIFT 0 + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8991_LRBLO 0x0080 /* LRBLO */ +#define WM8991_LRBLO_BIT 7 +#define WM8991_LLBLO 0x0040 /* LLBLO */ +#define WM8991_LLBLO_BIT 6 +#define WM8991_LRI3LO 0x0020 /* LRI3LO */ +#define WM8991_LRI3LO_BIT 5 +#define WM8991_LLI3LO 0x0010 /* LLI3LO */ +#define WM8991_LLI3LO_BIT 4 +#define WM8991_LR12LO 0x0008 /* LR12LO */ +#define WM8991_LR12LO_BIT 3 +#define WM8991_LL12LO 0x0004 /* LL12LO */ +#define WM8991_LL12LO_BIT 2 +#define WM8991_LDLO 0x0001 /* LDLO */ +#define WM8991_LDLO_BIT 0 + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8991_RLBRO 0x0080 /* RLBRO */ +#define WM8991_RLBRO_BIT 7 +#define WM8991_RRBRO 0x0040 /* RRBRO */ +#define WM8991_RRBRO_BIT 6 +#define WM8991_RLI3RO 0x0020 /* RLI3RO */ +#define WM8991_RLI3RO_BIT 5 +#define WM8991_RRI3RO 0x0010 /* RRI3RO */ +#define WM8991_RRI3RO_BIT 4 +#define WM8991_RL12RO 0x0008 /* RL12RO */ +#define WM8991_RL12RO_BIT 3 +#define WM8991_RR12RO 0x0004 /* RR12RO */ +#define WM8991_RR12RO_BIT 2 +#define WM8991_RDRO 0x0001 /* RDRO */ +#define WM8991_RDRO_BIT 0 + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8991_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */ +#define WM8991_LLI3LOVOL_SHIFT 6 +#define WM8991_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */ +#define WM8991_LR12LOVOL_SHIFT 3 +#define WM8991_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */ +#define WM8991_LL12LOVOL_SHIFT 0 + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8991_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */ +#define WM8991_RRI3ROVOL_SHIFT 6 +#define WM8991_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */ +#define WM8991_RL12ROVOL_SHIFT 3 +#define WM8991_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */ +#define WM8991_RR12ROVOL_SHIFT 0 + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8991_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */ +#define WM8991_LRI3LOVOL_SHIFT 6 +#define WM8991_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */ +#define WM8991_LRBLOVOL_SHIFT 3 +#define WM8991_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */ +#define WM8991_LLBLOVOL_SHIFT 0 + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8991_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */ +#define WM8991_RLI3ROVOL_SHIFT 6 +#define WM8991_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */ +#define WM8991_RLBROVOL_SHIFT 3 +#define WM8991_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */ +#define WM8991_RRBROVOL_SHIFT 0 + +/* + * R51 (0x33) - Out3/4 Mixer + */ +#define WM8991_VSEL_MASK 0x0180 /* VSEL - [8:7] */ +#define WM8991_LI4O3 0x0020 /* LI4O3 */ +#define WM8991_LI4O3_BIT 5 +#define WM8991_LPGAO3 0x0010 /* LPGAO3 */ +#define WM8991_LPGAO3_BIT 4 +#define WM8991_RI4O4 0x0002 /* RI4O4 */ +#define WM8991_RI4O4_BIT 1 +#define WM8991_RPGAO4 0x0001 /* RPGAO4 */ +#define WM8991_RPGAO4_BIT 0 +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8991_LLOPGALON 0x0040 /* LLOPGALON */ +#define WM8991_LLOPGALON_BIT 6 +#define WM8991_LROPGALON 0x0020 /* LROPGALON */ +#define WM8991_LROPGALON_BIT 5 +#define WM8991_LOPLON 0x0010 /* LOPLON */ +#define WM8991_LOPLON_BIT 4 +#define WM8991_LR12LOP 0x0004 /* LR12LOP */ +#define WM8991_LR12LOP_BIT 2 +#define WM8991_LL12LOP 0x0002 /* LL12LOP */ +#define WM8991_LL12LOP_BIT 1 +#define WM8991_LLOPGALOP 0x0001 /* LLOPGALOP */ +#define WM8991_LLOPGALOP_BIT 0 +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8991_RROPGARON 0x0040 /* RROPGARON */ +#define WM8991_RROPGARON_BIT 6 +#define WM8991_RLOPGARON 0x0020 /* RLOPGARON */ +#define WM8991_RLOPGARON_BIT 5 +#define WM8991_ROPRON 0x0010 /* ROPRON */ +#define WM8991_ROPRON_BIT 4 +#define WM8991_RL12ROP 0x0004 /* RL12ROP */ +#define WM8991_RL12ROP_BIT 2 +#define WM8991_RR12ROP 0x0002 /* RR12ROP */ +#define WM8991_RR12ROP_BIT 1 +#define WM8991_RROPGAROP 0x0001 /* RROPGAROP */ +#define WM8991_RROPGAROP_BIT 0 + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8991_LB2SPK 0x0080 /* LB2SPK */ +#define WM8991_LB2SPK_BIT 7 +#define WM8991_RB2SPK 0x0040 /* RB2SPK */ +#define WM8991_RB2SPK_BIT 6 +#define WM8991_LI2SPK 0x0020 /* LI2SPK */ +#define WM8991_LI2SPK_BIT 5 +#define WM8991_RI2SPK 0x0010 /* RI2SPK */ +#define WM8991_RI2SPK_BIT 4 +#define WM8991_LOPGASPK 0x0008 /* LOPGASPK */ +#define WM8991_LOPGASPK_BIT 3 +#define WM8991_ROPGASPK 0x0004 /* ROPGASPK */ +#define WM8991_ROPGASPK_BIT 2 +#define WM8991_LDSPK 0x0002 /* LDSPK */ +#define WM8991_LDSPK_BIT 1 +#define WM8991_RDSPK 0x0001 /* RDSPK */ +#define WM8991_RDSPK_BIT 0 + +/* + * R55 (0x37) - Additional Control + */ +#define WM8991_VROI 0x0001 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8991_DIS_LLINE 0x0020 /* DIS_LLINE */ +#define WM8991_DIS_RLINE 0x0010 /* DIS_RLINE */ +#define WM8991_DIS_OUT3 0x0008 /* DIS_OUT3 */ +#define WM8991_DIS_OUT4 0x0004 /* DIS_OUT4 */ +#define WM8991_DIS_LOUT 0x0002 /* DIS_LOUT */ +#define WM8991_DIS_ROUT 0x0001 /* DIS_ROUT */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8991_SOFTST 0x0040 /* SOFTST */ +#define WM8991_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8991_BUFDCOPEN 0x0004 /* BUFDCOPEN */ +#define WM8991_POBCTRL 0x0002 /* POBCTRL */ +#define WM8991_VMIDTOG 0x0001 /* VMIDTOG */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8991_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */ +#define WM8991_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */ +#define WM8991_MCD 0x0004 /* MCD */ +#define WM8991_MBSEL 0x0001 /* MBSEL */ + +/* + * R60 (0x3C) - PLL1 + */ +#define WM8991_SDM 0x0080 /* SDM */ +#define WM8991_PRESCALE 0x0040 /* PRESCALE */ +#define WM8991_PLLN_MASK 0x000F /* PLLN - [3:0] */ + +/* + * R61 (0x3D) - PLL2 + */ +#define WM8991_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */ + +/* + * R62 (0x3E) - PLL3 + */ +#define WM8991_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */ + +#define WM8991_MCLK_DIV 0 +#define WM8991_DACCLK_DIV 1 +#define WM8991_ADCCLK_DIV 2 +#define WM8991_BCLK_DIV 3 + +#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ + tlv_array) \ + SOC_SINGLE_EXT_TLV(xname, reg, shift, max, invert, \ + snd_soc_get_volsw, wm899x_outpga_put_volsw_vu, tlv_array) + +#endif /* _WM8991_H */ diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c new file mode 100644 index 000000000..2c61655c4 --- /dev/null +++ b/sound/soc/codecs/wm8993.c @@ -0,0 +1,1759 @@ +/* + * wm8993.c -- WM8993 ALSA SoC audio driver + * + * Copyright 2009-12 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8993.h" +#include "wm_hubs.h" + +#define WM8993_NUM_SUPPLIES 6 +static const char *wm8993_supply_names[WM8993_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD", +}; + +static const struct reg_default wm8993_reg_defaults[] = { + { 1, 0x0000 }, /* R1 - Power Management (1) */ + { 2, 0x6000 }, /* R2 - Power Management (2) */ + { 3, 0x0000 }, /* R3 - Power Management (3) */ + { 4, 0x4050 }, /* R4 - Audio Interface (1) */ + { 5, 0x4000 }, /* R5 - Audio Interface (2) */ + { 6, 0x01C8 }, /* R6 - Clocking 1 */ + { 7, 0x0000 }, /* R7 - Clocking 2 */ + { 8, 0x0000 }, /* R8 - Audio Interface (3) */ + { 9, 0x0040 }, /* R9 - Audio Interface (4) */ + { 10, 0x0004 }, /* R10 - DAC CTRL */ + { 11, 0x00C0 }, /* R11 - Left DAC Digital Volume */ + { 12, 0x00C0 }, /* R12 - Right DAC Digital Volume */ + { 13, 0x0000 }, /* R13 - Digital Side Tone */ + { 14, 0x0300 }, /* R14 - ADC CTRL */ + { 15, 0x00C0 }, /* R15 - Left ADC Digital Volume */ + { 16, 0x00C0 }, /* R16 - Right ADC Digital Volume */ + { 18, 0x0000 }, /* R18 - GPIO CTRL 1 */ + { 19, 0x0010 }, /* R19 - GPIO1 */ + { 20, 0x0000 }, /* R20 - IRQ_DEBOUNCE */ + { 21, 0x0000 }, /* R21 - Inputs Clamp */ + { 22, 0x8000 }, /* R22 - GPIOCTRL 2 */ + { 23, 0x0800 }, /* R23 - GPIO_POL */ + { 24, 0x008B }, /* R24 - Left Line Input 1&2 Volume */ + { 25, 0x008B }, /* R25 - Left Line Input 3&4 Volume */ + { 26, 0x008B }, /* R26 - Right Line Input 1&2 Volume */ + { 27, 0x008B }, /* R27 - Right Line Input 3&4 Volume */ + { 28, 0x006D }, /* R28 - Left Output Volume */ + { 29, 0x006D }, /* R29 - Right Output Volume */ + { 30, 0x0066 }, /* R30 - Line Outputs Volume */ + { 31, 0x0020 }, /* R31 - HPOUT2 Volume */ + { 32, 0x0079 }, /* R32 - Left OPGA Volume */ + { 33, 0x0079 }, /* R33 - Right OPGA Volume */ + { 34, 0x0003 }, /* R34 - SPKMIXL Attenuation */ + { 35, 0x0003 }, /* R35 - SPKMIXR Attenuation */ + { 36, 0x0011 }, /* R36 - SPKOUT Mixers */ + { 37, 0x0100 }, /* R37 - SPKOUT Boost */ + { 38, 0x0079 }, /* R38 - Speaker Volume Left */ + { 39, 0x0079 }, /* R39 - Speaker Volume Right */ + { 40, 0x0000 }, /* R40 - Input Mixer2 */ + { 41, 0x0000 }, /* R41 - Input Mixer3 */ + { 42, 0x0000 }, /* R42 - Input Mixer4 */ + { 43, 0x0000 }, /* R43 - Input Mixer5 */ + { 44, 0x0000 }, /* R44 - Input Mixer6 */ + { 45, 0x0000 }, /* R45 - Output Mixer1 */ + { 46, 0x0000 }, /* R46 - Output Mixer2 */ + { 47, 0x0000 }, /* R47 - Output Mixer3 */ + { 48, 0x0000 }, /* R48 - Output Mixer4 */ + { 49, 0x0000 }, /* R49 - Output Mixer5 */ + { 50, 0x0000 }, /* R50 - Output Mixer6 */ + { 51, 0x0000 }, /* R51 - HPOUT2 Mixer */ + { 52, 0x0000 }, /* R52 - Line Mixer1 */ + { 53, 0x0000 }, /* R53 - Line Mixer2 */ + { 54, 0x0000 }, /* R54 - Speaker Mixer */ + { 55, 0x0000 }, /* R55 - Additional Control */ + { 56, 0x0000 }, /* R56 - AntiPOP1 */ + { 57, 0x0000 }, /* R57 - AntiPOP2 */ + { 58, 0x0000 }, /* R58 - MICBIAS */ + { 60, 0x0000 }, /* R60 - FLL Control 1 */ + { 61, 0x0000 }, /* R61 - FLL Control 2 */ + { 62, 0x0000 }, /* R62 - FLL Control 3 */ + { 63, 0x2EE0 }, /* R63 - FLL Control 4 */ + { 64, 0x0002 }, /* R64 - FLL Control 5 */ + { 65, 0x2287 }, /* R65 - Clocking 3 */ + { 66, 0x025F }, /* R66 - Clocking 4 */ + { 67, 0x0000 }, /* R67 - MW Slave Control */ + { 69, 0x0002 }, /* R69 - Bus Control 1 */ + { 70, 0x0000 }, /* R70 - Write Sequencer 0 */ + { 71, 0x0000 }, /* R71 - Write Sequencer 1 */ + { 72, 0x0000 }, /* R72 - Write Sequencer 2 */ + { 73, 0x0000 }, /* R73 - Write Sequencer 3 */ + { 74, 0x0000 }, /* R74 - Write Sequencer 4 */ + { 75, 0x0000 }, /* R75 - Write Sequencer 5 */ + { 76, 0x1F25 }, /* R76 - Charge Pump 1 */ + { 81, 0x0000 }, /* R81 - Class W 0 */ + { 85, 0x054A }, /* R85 - DC Servo 1 */ + { 87, 0x0000 }, /* R87 - DC Servo 3 */ + { 96, 0x0100 }, /* R96 - Analogue HP 0 */ + { 98, 0x0000 }, /* R98 - EQ1 */ + { 99, 0x000C }, /* R99 - EQ2 */ + { 100, 0x000C }, /* R100 - EQ3 */ + { 101, 0x000C }, /* R101 - EQ4 */ + { 102, 0x000C }, /* R102 - EQ5 */ + { 103, 0x000C }, /* R103 - EQ6 */ + { 104, 0x0FCA }, /* R104 - EQ7 */ + { 105, 0x0400 }, /* R105 - EQ8 */ + { 106, 0x00D8 }, /* R106 - EQ9 */ + { 107, 0x1EB5 }, /* R107 - EQ10 */ + { 108, 0xF145 }, /* R108 - EQ11 */ + { 109, 0x0B75 }, /* R109 - EQ12 */ + { 110, 0x01C5 }, /* R110 - EQ13 */ + { 111, 0x1C58 }, /* R111 - EQ14 */ + { 112, 0xF373 }, /* R112 - EQ15 */ + { 113, 0x0A54 }, /* R113 - EQ16 */ + { 114, 0x0558 }, /* R114 - EQ17 */ + { 115, 0x168E }, /* R115 - EQ18 */ + { 116, 0xF829 }, /* R116 - EQ19 */ + { 117, 0x07AD }, /* R117 - EQ20 */ + { 118, 0x1103 }, /* R118 - EQ21 */ + { 119, 0x0564 }, /* R119 - EQ22 */ + { 120, 0x0559 }, /* R120 - EQ23 */ + { 121, 0x4000 }, /* R121 - EQ24 */ + { 122, 0x0000 }, /* R122 - Digital Pulls */ + { 123, 0x0F08 }, /* R123 - DRC Control 1 */ + { 124, 0x0000 }, /* R124 - DRC Control 2 */ + { 125, 0x0080 }, /* R125 - DRC Control 3 */ + { 126, 0x0000 }, /* R126 - DRC Control 4 */ +}; + +static struct { + int ratio; + int clk_sys_rate; +} clk_sys_rates[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +static struct { + int rate; + int sample_rate; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 1 }, + { 16000, 2 }, + { 22050, 3 }, + { 24000, 3 }, + { 32000, 4 }, + { 44100, 5 }, + { 48000, 5 }, +}; + +static struct { + int div; /* *10 due to .5s */ + int bclk_div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 55, 5 }, + { 60, 6 }, + { 80, 7 }, + { 110, 8 }, + { 120, 9 }, + { 160, 10 }, + { 220, 11 }, + { 240, 12 }, + { 320, 13 }, + { 440, 14 }, + { 480, 15 }, +}; + +struct wm8993_priv { + struct wm_hubs_data hubs_data; + struct device *dev; + struct regmap *regmap; + struct regulator_bulk_data supplies[WM8993_NUM_SUPPLIES]; + struct wm8993_platform_data pdata; + struct completion fll_lock; + int master; + int sysclk_source; + int tdm_slots; + int tdm_width; + unsigned int mclk_rate; + unsigned int sysclk_rate; + unsigned int fs; + unsigned int bclk; + unsigned int fll_fref; + unsigned int fll_fout; + int fll_src; +}; + +static bool wm8993_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8993_SOFTWARE_RESET: + case WM8993_GPIO_CTRL_1: + case WM8993_DC_SERVO_0: + case WM8993_DC_SERVO_READBACK_0: + case WM8993_DC_SERVO_READBACK_1: + case WM8993_DC_SERVO_READBACK_2: + return true; + default: + return false; + } +} + +static bool wm8993_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8993_SOFTWARE_RESET: + case WM8993_POWER_MANAGEMENT_1: + case WM8993_POWER_MANAGEMENT_2: + case WM8993_POWER_MANAGEMENT_3: + case WM8993_AUDIO_INTERFACE_1: + case WM8993_AUDIO_INTERFACE_2: + case WM8993_CLOCKING_1: + case WM8993_CLOCKING_2: + case WM8993_AUDIO_INTERFACE_3: + case WM8993_AUDIO_INTERFACE_4: + case WM8993_DAC_CTRL: + case WM8993_LEFT_DAC_DIGITAL_VOLUME: + case WM8993_RIGHT_DAC_DIGITAL_VOLUME: + case WM8993_DIGITAL_SIDE_TONE: + case WM8993_ADC_CTRL: + case WM8993_LEFT_ADC_DIGITAL_VOLUME: + case WM8993_RIGHT_ADC_DIGITAL_VOLUME: + case WM8993_GPIO_CTRL_1: + case WM8993_GPIO1: + case WM8993_IRQ_DEBOUNCE: + case WM8993_GPIOCTRL_2: + case WM8993_GPIO_POL: + case WM8993_LEFT_LINE_INPUT_1_2_VOLUME: + case WM8993_LEFT_LINE_INPUT_3_4_VOLUME: + case WM8993_RIGHT_LINE_INPUT_1_2_VOLUME: + case WM8993_RIGHT_LINE_INPUT_3_4_VOLUME: + case WM8993_LEFT_OUTPUT_VOLUME: + case WM8993_RIGHT_OUTPUT_VOLUME: + case WM8993_LINE_OUTPUTS_VOLUME: + case WM8993_HPOUT2_VOLUME: + case WM8993_LEFT_OPGA_VOLUME: + case WM8993_RIGHT_OPGA_VOLUME: + case WM8993_SPKMIXL_ATTENUATION: + case WM8993_SPKMIXR_ATTENUATION: + case WM8993_SPKOUT_MIXERS: + case WM8993_SPKOUT_BOOST: + case WM8993_SPEAKER_VOLUME_LEFT: + case WM8993_SPEAKER_VOLUME_RIGHT: + case WM8993_INPUT_MIXER2: + case WM8993_INPUT_MIXER3: + case WM8993_INPUT_MIXER4: + case WM8993_INPUT_MIXER5: + case WM8993_INPUT_MIXER6: + case WM8993_OUTPUT_MIXER1: + case WM8993_OUTPUT_MIXER2: + case WM8993_OUTPUT_MIXER3: + case WM8993_OUTPUT_MIXER4: + case WM8993_OUTPUT_MIXER5: + case WM8993_OUTPUT_MIXER6: + case WM8993_HPOUT2_MIXER: + case WM8993_LINE_MIXER1: + case WM8993_LINE_MIXER2: + case WM8993_SPEAKER_MIXER: + case WM8993_ADDITIONAL_CONTROL: + case WM8993_ANTIPOP1: + case WM8993_ANTIPOP2: + case WM8993_MICBIAS: + case WM8993_FLL_CONTROL_1: + case WM8993_FLL_CONTROL_2: + case WM8993_FLL_CONTROL_3: + case WM8993_FLL_CONTROL_4: + case WM8993_FLL_CONTROL_5: + case WM8993_CLOCKING_3: + case WM8993_CLOCKING_4: + case WM8993_MW_SLAVE_CONTROL: + case WM8993_BUS_CONTROL_1: + case WM8993_WRITE_SEQUENCER_0: + case WM8993_WRITE_SEQUENCER_1: + case WM8993_WRITE_SEQUENCER_2: + case WM8993_WRITE_SEQUENCER_3: + case WM8993_WRITE_SEQUENCER_4: + case WM8993_WRITE_SEQUENCER_5: + case WM8993_CHARGE_PUMP_1: + case WM8993_CLASS_W_0: + case WM8993_DC_SERVO_0: + case WM8993_DC_SERVO_1: + case WM8993_DC_SERVO_3: + case WM8993_DC_SERVO_READBACK_0: + case WM8993_DC_SERVO_READBACK_1: + case WM8993_DC_SERVO_READBACK_2: + case WM8993_ANALOGUE_HP_0: + case WM8993_EQ1: + case WM8993_EQ2: + case WM8993_EQ3: + case WM8993_EQ4: + case WM8993_EQ5: + case WM8993_EQ6: + case WM8993_EQ7: + case WM8993_EQ8: + case WM8993_EQ9: + case WM8993_EQ10: + case WM8993_EQ11: + case WM8993_EQ12: + case WM8993_EQ13: + case WM8993_EQ14: + case WM8993_EQ15: + case WM8993_EQ16: + case WM8993_EQ17: + case WM8993_EQ18: + case WM8993_EQ19: + case WM8993_EQ20: + case WM8993_EQ21: + case WM8993_EQ22: + case WM8993_EQ23: + case WM8993_EQ24: + case WM8993_DIGITAL_PULLS: + case WM8993_DRC_CONTROL_1: + case WM8993_DRC_CONTROL_2: + case WM8993_DRC_CONTROL_3: + case WM8993_DRC_CONTROL_4: + return true; + default: + return false; + } +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_clk_ref_div; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + fll_div->fll_clk_ref_div = 0; + while ((Fref / div) > 13500000) { + div *= 2; + fll_div->fll_clk_ref_div++; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 0; + target = Fout * 2; + while (target < 90000000) { + div++; + target *= 2; + if (div > 7) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + fll_div->fll_outdiv = div; + + pr_debug("Fvco=%dHz\n", target); + + /* Find an appropriate FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + target /= fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + fll_div->n = Ndiv; + Nmod = target % Fref; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", + fll_div->n, fll_div->k, + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_clk_ref_div); + + return 0; +} + +static int _wm8993_set_fll(struct snd_soc_component *component, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + struct i2c_client *i2c = to_i2c_client(component->dev); + u16 reg1, reg4, reg5; + struct _fll_div fll_div; + unsigned int timeout; + int ret; + + /* Any change? */ + if (Fref == wm8993->fll_fref && Fout == wm8993->fll_fout) + return 0; + + /* Disable the FLL */ + if (Fout == 0) { + dev_dbg(component->dev, "FLL disabled\n"); + wm8993->fll_fref = 0; + wm8993->fll_fout = 0; + + reg1 = snd_soc_component_read32(component, WM8993_FLL_CONTROL_1); + reg1 &= ~WM8993_FLL_ENA; + snd_soc_component_write(component, WM8993_FLL_CONTROL_1, reg1); + + return 0; + } + + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + + reg5 = snd_soc_component_read32(component, WM8993_FLL_CONTROL_5); + reg5 &= ~WM8993_FLL_CLK_SRC_MASK; + + switch (fll_id) { + case WM8993_FLL_MCLK: + break; + + case WM8993_FLL_LRCLK: + reg5 |= 1; + break; + + case WM8993_FLL_BCLK: + reg5 |= 2; + break; + + default: + dev_err(component->dev, "Unknown FLL ID %d\n", fll_id); + return -EINVAL; + } + + /* Any FLL configuration change requires that the FLL be + * disabled first. */ + reg1 = snd_soc_component_read32(component, WM8993_FLL_CONTROL_1); + reg1 &= ~WM8993_FLL_ENA; + snd_soc_component_write(component, WM8993_FLL_CONTROL_1, reg1); + + /* Apply the configuration */ + if (fll_div.k) + reg1 |= WM8993_FLL_FRAC_MASK; + else + reg1 &= ~WM8993_FLL_FRAC_MASK; + snd_soc_component_write(component, WM8993_FLL_CONTROL_1, reg1); + + snd_soc_component_write(component, WM8993_FLL_CONTROL_2, + (fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT)); + snd_soc_component_write(component, WM8993_FLL_CONTROL_3, fll_div.k); + + reg4 = snd_soc_component_read32(component, WM8993_FLL_CONTROL_4); + reg4 &= ~WM8993_FLL_N_MASK; + reg4 |= fll_div.n << WM8993_FLL_N_SHIFT; + snd_soc_component_write(component, WM8993_FLL_CONTROL_4, reg4); + + reg5 &= ~WM8993_FLL_CLK_REF_DIV_MASK; + reg5 |= fll_div.fll_clk_ref_div << WM8993_FLL_CLK_REF_DIV_SHIFT; + snd_soc_component_write(component, WM8993_FLL_CONTROL_5, reg5); + + /* If we've got an interrupt wired up make sure we get it */ + if (i2c->irq) + timeout = msecs_to_jiffies(20); + else if (Fref < 1000000) + timeout = msecs_to_jiffies(3); + else + timeout = msecs_to_jiffies(1); + + try_wait_for_completion(&wm8993->fll_lock); + + /* Enable the FLL */ + snd_soc_component_write(component, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA); + + timeout = wait_for_completion_timeout(&wm8993->fll_lock, timeout); + if (i2c->irq && !timeout) + dev_warn(component->dev, "Timed out waiting for FLL\n"); + + dev_dbg(component->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout); + + wm8993->fll_fref = Fref; + wm8993->fll_fout = Fout; + wm8993->fll_src = source; + + return 0; +} + +static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + return _wm8993_set_fll(dai->component, fll_id, source, Fref, Fout); +} + +static int configure_clock(struct snd_soc_component *component) +{ + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + unsigned int reg; + + /* This should be done on init() for bypass paths */ + switch (wm8993->sysclk_source) { + case WM8993_SYSCLK_MCLK: + dev_dbg(component->dev, "Using %dHz MCLK\n", wm8993->mclk_rate); + + reg = snd_soc_component_read32(component, WM8993_CLOCKING_2); + reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC); + if (wm8993->mclk_rate > 13500000) { + reg |= WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->mclk_rate / 2; + } else { + reg &= ~WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->mclk_rate; + } + snd_soc_component_write(component, WM8993_CLOCKING_2, reg); + break; + + case WM8993_SYSCLK_FLL: + dev_dbg(component->dev, "Using %dHz FLL clock\n", + wm8993->fll_fout); + + reg = snd_soc_component_read32(component, WM8993_CLOCKING_2); + reg |= WM8993_SYSCLK_SRC; + if (wm8993->fll_fout > 13500000) { + reg |= WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->fll_fout / 2; + } else { + reg &= ~WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->fll_fout; + } + snd_soc_component_write(component, WM8993_CLOCKING_2, reg); + break; + + default: + dev_err(component->dev, "System clock not configured\n"); + return -EINVAL; + } + + dev_dbg(component->dev, "CLK_SYS is %dHz\n", wm8993->sysclk_rate); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_SCALE(drc_comp_threash, -4500, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_comp_amp, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0); +static const DECLARE_TLV_DB_RANGE(drc_max_tlv, + 0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0), + 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -1800, 300, 0); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); + +static const char *dac_deemph_text[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", +}; + +static SOC_ENUM_SINGLE_DECL(dac_deemph, + WM8993_DAC_CTRL, 4, dac_deemph_text); + +static const char *adc_hpf_text[] = { + "Hi-Fi", + "Voice 1", + "Voice 2", + "Voice 3", +}; + +static SOC_ENUM_SINGLE_DECL(adc_hpf, + WM8993_ADC_CTRL, 5, adc_hpf_text); + +static const char *drc_path_text[] = { + "ADC", + "DAC" +}; + +static SOC_ENUM_SINGLE_DECL(drc_path, + WM8993_DRC_CONTROL_1, 14, drc_path_text); + +static const char *drc_r0_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "1/16", + "0", +}; + +static SOC_ENUM_SINGLE_DECL(drc_r0, + WM8993_DRC_CONTROL_3, 8, drc_r0_text); + +static const char *drc_r1_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "0", +}; + +static SOC_ENUM_SINGLE_DECL(drc_r1, + WM8993_DRC_CONTROL_4, 13, drc_r1_text); + +static const char *drc_attack_text[] = { + "Reserved", + "181us", + "363us", + "726us", + "1.45ms", + "2.9ms", + "5.8ms", + "11.6ms", + "23.2ms", + "46.4ms", + "92.8ms", + "185.6ms", +}; + +static SOC_ENUM_SINGLE_DECL(drc_attack, + WM8993_DRC_CONTROL_2, 12, drc_attack_text); + +static const char *drc_decay_text[] = { + "186ms", + "372ms", + "743ms", + "1.49s", + "2.97ms", + "5.94ms", + "11.89ms", + "23.78ms", + "47.56ms", +}; + +static SOC_ENUM_SINGLE_DECL(drc_decay, + WM8993_DRC_CONTROL_2, 8, drc_decay_text); + +static const char *drc_ff_text[] = { + "5 samples", + "9 samples", +}; + +static SOC_ENUM_SINGLE_DECL(drc_ff, + WM8993_DRC_CONTROL_3, 7, drc_ff_text); + +static const char *drc_qr_rate_text[] = { + "0.725ms", + "1.45ms", + "5.8ms", +}; + +static SOC_ENUM_SINGLE_DECL(drc_qr_rate, + WM8993_DRC_CONTROL_3, 0, drc_qr_rate_text); + +static const char *drc_smooth_text[] = { + "Low", + "Medium", + "High", +}; + +static SOC_ENUM_SINGLE_DECL(drc_smooth, + WM8993_DRC_CONTROL_1, 4, drc_smooth_text); + +static const struct snd_kcontrol_new wm8993_snd_controls[] = { +SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8993_DIGITAL_SIDE_TONE, + 5, 9, 12, 0, sidetone_tlv), + +SOC_SINGLE("DRC Switch", WM8993_DRC_CONTROL_1, 15, 1, 0), +SOC_ENUM("DRC Path", drc_path), +SOC_SINGLE_TLV("DRC Compressor Threshold Volume", WM8993_DRC_CONTROL_2, + 2, 60, 1, drc_comp_threash), +SOC_SINGLE_TLV("DRC Compressor Amplitude Volume", WM8993_DRC_CONTROL_3, + 11, 30, 1, drc_comp_amp), +SOC_ENUM("DRC R0", drc_r0), +SOC_ENUM("DRC R1", drc_r1), +SOC_SINGLE_TLV("DRC Minimum Volume", WM8993_DRC_CONTROL_1, 2, 3, 1, + drc_min_tlv), +SOC_SINGLE_TLV("DRC Maximum Volume", WM8993_DRC_CONTROL_1, 0, 3, 0, + drc_max_tlv), +SOC_ENUM("DRC Attack Rate", drc_attack), +SOC_ENUM("DRC Decay Rate", drc_decay), +SOC_ENUM("DRC FF Delay", drc_ff), +SOC_SINGLE("DRC Anti-clip Switch", WM8993_DRC_CONTROL_1, 9, 1, 0), +SOC_SINGLE("DRC Quick Release Switch", WM8993_DRC_CONTROL_1, 10, 1, 0), +SOC_SINGLE_TLV("DRC Quick Release Volume", WM8993_DRC_CONTROL_3, 2, 3, 0, + drc_qr_tlv), +SOC_ENUM("DRC Quick Release Rate", drc_qr_rate), +SOC_SINGLE("DRC Smoothing Switch", WM8993_DRC_CONTROL_1, 11, 1, 0), +SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8993_DRC_CONTROL_1, 8, 1, 0), +SOC_ENUM("DRC Smoothing Hysteresis Threshold", drc_smooth), +SOC_SINGLE_TLV("DRC Startup Volume", WM8993_DRC_CONTROL_4, 8, 18, 0, + drc_startup_tlv), + +SOC_SINGLE("EQ Switch", WM8993_EQ1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8993_LEFT_ADC_DIGITAL_VOLUME, + WM8993_RIGHT_ADC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv), +SOC_SINGLE("ADC High Pass Filter Switch", WM8993_ADC_CTRL, 8, 1, 0), +SOC_ENUM("ADC High Pass Filter Mode", adc_hpf), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8993_LEFT_DAC_DIGITAL_VOLUME, + WM8993_RIGHT_DAC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv), +SOC_SINGLE_TLV("Playback Boost Volume", WM8993_AUDIO_INTERFACE_2, 10, 3, 0, + dac_boost_tlv), +SOC_ENUM("DAC Deemphasis", dac_deemph), + +SOC_SINGLE_TLV("SPKL DAC Volume", WM8993_SPKMIXL_ATTENUATION, + 2, 1, 1, wm_hubs_spkmix_tlv), + +SOC_SINGLE_TLV("SPKR DAC Volume", WM8993_SPKMIXR_ATTENUATION, + 2, 1, 1, wm_hubs_spkmix_tlv), +}; + +static const struct snd_kcontrol_new wm8993_eq_controls[] = { +SOC_SINGLE_TLV("EQ1 Volume", WM8993_EQ2, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Volume", WM8993_EQ3, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Volume", WM8993_EQ4, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Volume", WM8993_EQ5, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ5 Volume", WM8993_EQ6, 0, 24, 0, eq_tlv), +}; + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return configure_clock(component); + + case SND_SOC_DAPM_POST_PMD: + break; + } + + return 0; +} + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), +SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0), +}; + +static const char *aif_text[] = { + "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(aifoutl_enum, + WM8993_AUDIO_INTERFACE_1, 15, aif_text); + +static const struct snd_kcontrol_new aifoutl_mux = + SOC_DAPM_ENUM("AIFOUTL Mux", aifoutl_enum); + +static SOC_ENUM_SINGLE_DECL(aifoutr_enum, + WM8993_AUDIO_INTERFACE_1, 14, aif_text); + +static const struct snd_kcontrol_new aifoutr_mux = + SOC_DAPM_ENUM("AIFOUTR Mux", aifoutr_enum); + +static SOC_ENUM_SINGLE_DECL(aifinl_enum, + WM8993_AUDIO_INTERFACE_2, 15, aif_text); + +static const struct snd_kcontrol_new aifinl_mux = + SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum); + +static SOC_ENUM_SINGLE_DECL(aifinr_enum, + WM8993_AUDIO_INTERFACE_2, 14, aif_text); + +static const struct snd_kcontrol_new aifinr_mux = + SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum); + +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(sidetonel_enum, + WM8993_DIGITAL_SIDE_TONE, 2, sidetone_text); + +static const struct snd_kcontrol_new sidetonel_mux = + SOC_DAPM_ENUM("Left Sidetone", sidetonel_enum); + +static SOC_ENUM_SINGLE_DECL(sidetoner_enum, + WM8993_DIGITAL_SIDE_TONE, 0, sidetone_text); + +static const struct snd_kcontrol_new sidetoner_mux = + SOC_DAPM_ENUM("Right Sidetone", sidetoner_enum); + +static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8993_BUS_CONTROL_1, 1, 0, clk_sys_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("TOCLK", WM8993_CLOCKING_1, 14, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8993_CLOCKING_3, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", NULL, WM8993_POWER_MANAGEMENT_2, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, WM8993_POWER_MANAGEMENT_2, 0, 0), + +SND_SOC_DAPM_MUX("AIFOUTL Mux", SND_SOC_NOPM, 0, 0, &aifoutl_mux), +SND_SOC_DAPM_MUX("AIFOUTR Mux", SND_SOC_NOPM, 0, 0, &aifoutr_mux), + +SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux), +SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux), + +SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &sidetonel_mux), +SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &sidetoner_mux), + +SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0), +SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0), + +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux), + +SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), +SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route routes[] = { + { "MICBIAS1", NULL, "VMID" }, + { "MICBIAS2", NULL, "VMID" }, + + { "ADCL", NULL, "CLK_SYS" }, + { "ADCL", NULL, "CLK_DSP" }, + { "ADCR", NULL, "CLK_SYS" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "AIFOUTL Mux", "Left", "ADCL" }, + { "AIFOUTL Mux", "Right", "ADCR" }, + { "AIFOUTR Mux", "Left", "ADCL" }, + { "AIFOUTR Mux", "Right", "ADCR" }, + + { "AIFOUTL", NULL, "AIFOUTL Mux" }, + { "AIFOUTR", NULL, "AIFOUTR Mux" }, + + { "DACL Mux", "Left", "AIFINL" }, + { "DACL Mux", "Right", "AIFINR" }, + { "DACR Mux", "Left", "AIFINL" }, + { "DACR Mux", "Right", "AIFINR" }, + + { "DACL Sidetone", "Left", "ADCL" }, + { "DACL Sidetone", "Right", "ADCR" }, + { "DACR Sidetone", "Left", "ADCL" }, + { "DACR Sidetone", "Right", "ADCR" }, + + { "DACL", NULL, "CLK_SYS" }, + { "DACL", NULL, "CLK_DSP" }, + { "DACL", NULL, "DACL Mux" }, + { "DACL", NULL, "DACL Sidetone" }, + { "DACR", NULL, "CLK_SYS" }, + { "DACR", NULL, "CLK_DSP" }, + { "DACR", NULL, "DACR Mux" }, + { "DACR", NULL, "DACR Sidetone" }, + + { "Left Output Mixer", "DAC Switch", "DACL" }, + + { "Right Output Mixer", "DAC Switch", "DACR" }, + + { "Left Output PGA", NULL, "CLK_SYS" }, + + { "Right Output PGA", NULL, "CLK_SYS" }, + + { "SPKL", "DAC Switch", "DACL" }, + { "SPKL", NULL, "CLK_SYS" }, + + { "SPKR", "DAC Switch", "DACR" }, + { "SPKR", NULL, "CLK_SYS" }, + + { "Left Headphone Mux", "DAC", "DACL" }, + { "Right Headphone Mux", "DAC", "DACR" }, +}; + +static int wm8993_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + int ret; + + wm_hubs_set_bias_level(component, level); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + /* VMID=2*40k */ + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK, 0x2); + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_2, + WM8993_TSHUT_ENA, WM8993_TSHUT_ENA); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); + if (ret != 0) + return ret; + + regcache_cache_only(wm8993->regmap, false); + regcache_sync(wm8993->regmap); + + wm_hubs_vmid_ena(component); + + /* Bring up VMID with fast soft start */ + snd_soc_component_update_bits(component, WM8993_ANTIPOP2, + WM8993_STARTUP_BIAS_ENA | + WM8993_VMID_BUF_ENA | + WM8993_VMID_RAMP_MASK | + WM8993_BIAS_SRC, + WM8993_STARTUP_BIAS_ENA | + WM8993_VMID_BUF_ENA | + WM8993_VMID_RAMP_MASK | + WM8993_BIAS_SRC); + + /* If either line output is single ended we + * need the VMID buffer */ + if (!wm8993->pdata.lineout1_diff || + !wm8993->pdata.lineout2_diff) + snd_soc_component_update_bits(component, WM8993_ANTIPOP1, + WM8993_LINEOUT_VMID_BUF_ENA, + WM8993_LINEOUT_VMID_BUF_ENA); + + /* VMID=2*40k */ + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK | + WM8993_BIAS_ENA, + WM8993_BIAS_ENA | 0x2); + msleep(32); + + /* Switch to normal bias */ + snd_soc_component_update_bits(component, WM8993_ANTIPOP2, + WM8993_BIAS_SRC | + WM8993_STARTUP_BIAS_ENA, 0); + } + + /* VMID=2*240k */ + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK, 0x4); + + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_2, + WM8993_TSHUT_ENA, 0); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, WM8993_ANTIPOP1, + WM8993_LINEOUT_VMID_BUF_ENA, 0); + + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK | WM8993_BIAS_ENA, + 0); + + snd_soc_component_update_bits(component, WM8993_ANTIPOP2, + WM8993_STARTUP_BIAS_ENA | + WM8993_VMID_BUF_ENA | + WM8993_VMID_RAMP_MASK | + WM8993_BIAS_SRC, 0); + + regcache_cache_only(wm8993->regmap, true); + regcache_mark_dirty(wm8993->regmap); + + regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); + break; + } + + return 0; +} + +static int wm8993_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8993_SYSCLK_MCLK: + wm8993->mclk_rate = freq; + /* fall through */ + case WM8993_SYSCLK_FLL: + wm8993->sysclk_source = clk_id; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8993_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + unsigned int aif1 = snd_soc_component_read32(component, WM8993_AUDIO_INTERFACE_1); + unsigned int aif4 = snd_soc_component_read32(component, WM8993_AUDIO_INTERFACE_4); + + aif1 &= ~(WM8993_BCLK_DIR | WM8993_AIF_BCLK_INV | + WM8993_AIF_LRCLK_INV | WM8993_AIF_FMT_MASK); + aif4 &= ~WM8993_LRCLK_DIR; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + wm8993->master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif4 |= WM8993_LRCLK_DIR; + wm8993->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif1 |= WM8993_BCLK_DIR; + wm8993->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 |= WM8993_BCLK_DIR; + aif4 |= WM8993_LRCLK_DIR; + wm8993->master = 1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8993_AIF_LRCLK_INV; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x18; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8993_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8993_AIF_BCLK_INV | WM8993_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8993_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8993_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM8993_AUDIO_INTERFACE_1, aif1); + snd_soc_component_write(component, WM8993_AUDIO_INTERFACE_4, aif4); + + return 0; +} + +static int wm8993_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + int ret, i, best, best_val, cur_val; + unsigned int clocking1, clocking3, aif1, aif4; + + clocking1 = snd_soc_component_read32(component, WM8993_CLOCKING_1); + clocking1 &= ~WM8993_BCLK_DIV_MASK; + + clocking3 = snd_soc_component_read32(component, WM8993_CLOCKING_3); + clocking3 &= ~(WM8993_CLK_SYS_RATE_MASK | WM8993_SAMPLE_RATE_MASK); + + aif1 = snd_soc_component_read32(component, WM8993_AUDIO_INTERFACE_1); + aif1 &= ~WM8993_AIF_WL_MASK; + + aif4 = snd_soc_component_read32(component, WM8993_AUDIO_INTERFACE_4); + aif4 &= ~WM8993_LRCLK_RATE_MASK; + + /* What BCLK do we need? */ + wm8993->fs = params_rate(params); + wm8993->bclk = 2 * wm8993->fs; + if (wm8993->tdm_slots) { + dev_dbg(component->dev, "Configuring for %d %d bit TDM slots\n", + wm8993->tdm_slots, wm8993->tdm_width); + wm8993->bclk *= wm8993->tdm_width * wm8993->tdm_slots; + } else { + switch (params_width(params)) { + case 16: + wm8993->bclk *= 16; + break; + case 20: + wm8993->bclk *= 20; + aif1 |= 0x8; + break; + case 24: + wm8993->bclk *= 24; + aif1 |= 0x10; + break; + case 32: + wm8993->bclk *= 32; + aif1 |= 0x18; + break; + default: + return -EINVAL; + } + } + + dev_dbg(component->dev, "Target BCLK is %dHz\n", wm8993->bclk); + + ret = configure_clock(component); + if (ret != 0) + return ret; + + /* Select nearest CLK_SYS_RATE */ + best = 0; + best_val = abs((wm8993->sysclk_rate / clk_sys_rates[0].ratio) + - wm8993->fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) { + cur_val = abs((wm8993->sysclk_rate / + clk_sys_rates[i].ratio) - wm8993->fs); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(component->dev, "Selected CLK_SYS_RATIO of %d\n", + clk_sys_rates[best].ratio); + clocking3 |= (clk_sys_rates[best].clk_sys_rate + << WM8993_CLK_SYS_RATE_SHIFT); + + /* SAMPLE_RATE */ + best = 0; + best_val = abs(wm8993->fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(wm8993->fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(component->dev, "Selected SAMPLE_RATE of %dHz\n", + sample_rates[best].rate); + clocking3 |= (sample_rates[best].sample_rate + << WM8993_SAMPLE_RATE_SHIFT); + + /* BCLK_DIV */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = ((wm8993->sysclk_rate * 10) / bclk_divs[i].div) + - wm8993->bclk; + if (cur_val < 0) /* Table is sorted */ + break; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + wm8993->bclk = (wm8993->sysclk_rate * 10) / bclk_divs[best].div; + dev_dbg(component->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n", + bclk_divs[best].div, wm8993->bclk); + clocking1 |= bclk_divs[best].bclk_div << WM8993_BCLK_DIV_SHIFT; + + /* LRCLK is a simple fraction of BCLK */ + dev_dbg(component->dev, "LRCLK_RATE is %d\n", wm8993->bclk / wm8993->fs); + aif4 |= wm8993->bclk / wm8993->fs; + + snd_soc_component_write(component, WM8993_CLOCKING_1, clocking1); + snd_soc_component_write(component, WM8993_CLOCKING_3, clocking3); + snd_soc_component_write(component, WM8993_AUDIO_INTERFACE_1, aif1); + snd_soc_component_write(component, WM8993_AUDIO_INTERFACE_4, aif4); + + /* ReTune Mobile? */ + if (wm8993->pdata.num_retune_configs) { + u16 eq1 = snd_soc_component_read32(component, WM8993_EQ1); + struct wm8993_retune_mobile_setting *s; + + best = 0; + best_val = abs(wm8993->pdata.retune_configs[0].rate + - wm8993->fs); + for (i = 0; i < wm8993->pdata.num_retune_configs; i++) { + cur_val = abs(wm8993->pdata.retune_configs[i].rate + - wm8993->fs); + if (cur_val < best_val) { + best_val = cur_val; + best = i; + } + } + s = &wm8993->pdata.retune_configs[best]; + + dev_dbg(component->dev, "ReTune Mobile %s tuned for %dHz\n", + s->name, s->rate); + + /* Disable EQ while we reconfigure */ + snd_soc_component_update_bits(component, WM8993_EQ1, WM8993_EQ_ENA, 0); + + for (i = 1; i < ARRAY_SIZE(s->config); i++) + snd_soc_component_write(component, WM8993_EQ1 + i, s->config[i]); + + snd_soc_component_update_bits(component, WM8993_EQ1, WM8993_EQ_ENA, eq1); + } + + return 0; +} + +static int wm8993_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int reg; + + reg = snd_soc_component_read32(component, WM8993_DAC_CTRL); + + if (mute) + reg |= WM8993_DAC_MUTE; + else + reg &= ~WM8993_DAC_MUTE; + + snd_soc_component_write(component, WM8993_DAC_CTRL, reg); + + return 0; +} + +static int wm8993_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + int aif1 = 0; + int aif2 = 0; + + /* Don't need to validate anything if we're turning off TDM */ + if (slots == 0) { + wm8993->tdm_slots = 0; + goto out; + } + + /* Note that we allow configurations we can't handle ourselves - + * for example, we can generate clocks for slots 2 and up even if + * we can't use those slots ourselves. + */ + aif1 |= WM8993_AIFADC_TDM; + aif2 |= WM8993_AIFDAC_TDM; + + switch (rx_mask) { + case 3: + break; + case 0xc: + aif1 |= WM8993_AIFADC_TDM_CHAN; + break; + default: + return -EINVAL; + } + + + switch (tx_mask) { + case 3: + break; + case 0xc: + aif2 |= WM8993_AIFDAC_TDM_CHAN; + break; + default: + return -EINVAL; + } + +out: + wm8993->tdm_width = slot_width; + wm8993->tdm_slots = slots / 2; + + snd_soc_component_update_bits(component, WM8993_AUDIO_INTERFACE_1, + WM8993_AIFADC_TDM | WM8993_AIFADC_TDM_CHAN, aif1); + snd_soc_component_update_bits(component, WM8993_AUDIO_INTERFACE_2, + WM8993_AIFDAC_TDM | WM8993_AIFDAC_TDM_CHAN, aif2); + + return 0; +} + +static irqreturn_t wm8993_irq(int irq, void *data) +{ + struct wm8993_priv *wm8993 = data; + int mask, val, ret; + + ret = regmap_read(wm8993->regmap, WM8993_GPIO_CTRL_1, &val); + if (ret != 0) { + dev_err(wm8993->dev, "Failed to read interrupt status: %d\n", + ret); + return IRQ_NONE; + } + + ret = regmap_read(wm8993->regmap, WM8993_GPIOCTRL_2, &mask); + if (ret != 0) { + dev_err(wm8993->dev, "Failed to read interrupt mask: %d\n", + ret); + return IRQ_NONE; + } + + /* The IRQ pin status is visible in the register too */ + val &= ~(mask | WM8993_IRQ); + if (!val) + return IRQ_NONE; + + if (val & WM8993_TEMPOK_EINT) + dev_crit(wm8993->dev, "Thermal warning\n"); + + if (val & WM8993_FLL_LOCK_EINT) { + dev_dbg(wm8993->dev, "FLL locked\n"); + complete(&wm8993->fll_lock); + } + + ret = regmap_write(wm8993->regmap, WM8993_GPIO_CTRL_1, val); + if (ret != 0) + dev_err(wm8993->dev, "Failed to ack interrupt: %d\n", ret); + + return IRQ_HANDLED; +} + +static const struct snd_soc_dai_ops wm8993_ops = { + .set_sysclk = wm8993_set_sysclk, + .set_fmt = wm8993_set_dai_fmt, + .hw_params = wm8993_hw_params, + .digital_mute = wm8993_digital_mute, + .set_pll = wm8993_set_fll, + .set_tdm_slot = wm8993_set_tdm_slot, +}; + +#define WM8993_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8993_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm8993_dai = { + .name = "wm8993-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8993_RATES, + .formats = WM8993_FORMATS, + .sig_bits = 24, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8993_RATES, + .formats = WM8993_FORMATS, + .sig_bits = 24, + }, + .ops = &wm8993_ops, + .symmetric_rates = 1, +}; + +static int wm8993_probe(struct snd_soc_component *component) +{ + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + wm8993->hubs_data.hp_startup_mode = 1; + wm8993->hubs_data.dcs_codes_l = -2; + wm8993->hubs_data.dcs_codes_r = -2; + wm8993->hubs_data.series_startup = 1; + + /* Latch volume update bits and default ZC on */ + snd_soc_component_update_bits(component, WM8993_RIGHT_DAC_DIGITAL_VOLUME, + WM8993_DAC_VU, WM8993_DAC_VU); + snd_soc_component_update_bits(component, WM8993_RIGHT_ADC_DIGITAL_VOLUME, + WM8993_ADC_VU, WM8993_ADC_VU); + + /* Manualy manage the HPOUT sequencing for independent stereo + * control. */ + snd_soc_component_update_bits(component, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1_AUTO_PU, 0); + + /* Use automatic clock configuration */ + snd_soc_component_update_bits(component, WM8993_CLOCKING_4, WM8993_SR_MODE, 0); + + wm_hubs_handle_analogue_pdata(component, wm8993->pdata.lineout1_diff, + wm8993->pdata.lineout2_diff, + wm8993->pdata.lineout1fb, + wm8993->pdata.lineout2fb, + wm8993->pdata.jd_scthr, + wm8993->pdata.jd_thr, + wm8993->pdata.micbias1_delay, + wm8993->pdata.micbias2_delay, + wm8993->pdata.micbias1_lvl, + wm8993->pdata.micbias2_lvl); + + snd_soc_add_component_controls(component, wm8993_snd_controls, + ARRAY_SIZE(wm8993_snd_controls)); + if (wm8993->pdata.num_retune_configs != 0) { + dev_dbg(component->dev, "Using ReTune Mobile\n"); + } else { + dev_dbg(component->dev, "No ReTune Mobile, using normal EQ\n"); + snd_soc_add_component_controls(component, wm8993_eq_controls, + ARRAY_SIZE(wm8993_eq_controls)); + } + + snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets, + ARRAY_SIZE(wm8993_dapm_widgets)); + wm_hubs_add_analogue_controls(component); + + snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes)); + wm_hubs_add_analogue_routes(component, wm8993->pdata.lineout1_diff, + wm8993->pdata.lineout2_diff); + + /* If the line outputs are differential then we aren't presenting + * VMID as an output and can disable it. + */ + if (wm8993->pdata.lineout1_diff && wm8993->pdata.lineout2_diff) + dapm->idle_bias_off = 1; + + return 0; + +} + +#ifdef CONFIG_PM +static int wm8993_suspend(struct snd_soc_component *component) +{ + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + int fll_fout = wm8993->fll_fout; + int fll_fref = wm8993->fll_fref; + int ret; + + /* Stop the FLL in an orderly fashion */ + ret = _wm8993_set_fll(component, 0, 0, 0, 0); + if (ret != 0) { + dev_err(component->dev, "Failed to stop FLL\n"); + return ret; + } + + wm8993->fll_fout = fll_fout; + wm8993->fll_fref = fll_fref; + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8993_resume(struct snd_soc_component *component) +{ + struct wm8993_priv *wm8993 = snd_soc_component_get_drvdata(component); + int ret; + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + /* Restart the FLL? */ + if (wm8993->fll_fout) { + int fll_fout = wm8993->fll_fout; + int fll_fref = wm8993->fll_fref; + + wm8993->fll_fref = 0; + wm8993->fll_fout = 0; + + ret = _wm8993_set_fll(component, 0, wm8993->fll_src, + fll_fref, fll_fout); + if (ret != 0) + dev_err(component->dev, "Failed to restart FLL\n"); + } + + return 0; +} +#else +#define wm8993_suspend NULL +#define wm8993_resume NULL +#endif + +/* Tune DC servo configuration */ +static const struct reg_sequence wm8993_regmap_patch[] = { + { 0x44, 3 }, + { 0x56, 3 }, + { 0x44, 0 }, +}; + +static const struct regmap_config wm8993_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM8993_MAX_REGISTER, + .volatile_reg = wm8993_volatile, + .readable_reg = wm8993_readable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm8993_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8993_reg_defaults), +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8993 = { + .probe = wm8993_probe, + .suspend = wm8993_suspend, + .resume = wm8993_resume, + .set_bias_level = wm8993_set_bias_level, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8993_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8993_priv *wm8993; + unsigned int reg; + int ret, i; + + wm8993 = devm_kzalloc(&i2c->dev, sizeof(struct wm8993_priv), + GFP_KERNEL); + if (wm8993 == NULL) + return -ENOMEM; + + wm8993->dev = &i2c->dev; + init_completion(&wm8993->fll_lock); + + wm8993->regmap = devm_regmap_init_i2c(i2c, &wm8993_regmap); + if (IS_ERR(wm8993->regmap)) { + ret = PTR_ERR(wm8993->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, wm8993); + + for (i = 0; i < ARRAY_SIZE(wm8993->supplies); i++) + wm8993->supplies[i].supply = wm8993_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8993->supplies), + wm8993->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + + ret = regmap_read(wm8993->regmap, WM8993_SOFTWARE_RESET, ®); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read chip ID: %d\n", ret); + goto err_enable; + } + + if (reg != 0x8993) { + dev_err(&i2c->dev, "Invalid ID register value %x\n", reg); + ret = -EINVAL; + goto err_enable; + } + + ret = regmap_write(wm8993->regmap, WM8993_SOFTWARE_RESET, 0xffff); + if (ret != 0) + goto err_enable; + + ret = regmap_register_patch(wm8993->regmap, wm8993_regmap_patch, + ARRAY_SIZE(wm8993_regmap_patch)); + if (ret != 0) + dev_warn(wm8993->dev, "Failed to apply regmap patch: %d\n", + ret); + + if (i2c->irq) { + /* Put GPIO1 into interrupt mode (only GPIO1 can output IRQ) */ + ret = regmap_update_bits(wm8993->regmap, WM8993_GPIO1, + WM8993_GPIO1_PD | + WM8993_GPIO1_SEL_MASK, 7); + if (ret != 0) + goto err_enable; + + ret = request_threaded_irq(i2c->irq, NULL, wm8993_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "wm8993", wm8993); + if (ret != 0) + goto err_enable; + + } + + regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies); + + regcache_cache_only(wm8993->regmap, true); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8993, &wm8993_dai, 1); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + goto err_irq; + } + + return 0; + +err_irq: + if (i2c->irq) + free_irq(i2c->irq, wm8993); +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies); + return ret; +} + +static int wm8993_i2c_remove(struct i2c_client *i2c) +{ + struct wm8993_priv *wm8993 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, wm8993); + regulator_bulk_disable(ARRAY_SIZE(wm8993->supplies), wm8993->supplies); + + return 0; +} + +static const struct i2c_device_id wm8993_i2c_id[] = { + { "wm8993", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8993_i2c_id); + +static struct i2c_driver wm8993_i2c_driver = { + .driver = { + .name = "wm8993", + }, + .probe = wm8993_i2c_probe, + .remove = wm8993_i2c_remove, + .id_table = wm8993_i2c_id, +}; + +module_i2c_driver(wm8993_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8993 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8993.h b/sound/soc/codecs/wm8993.h new file mode 100644 index 000000000..91811aa15 --- /dev/null +++ b/sound/soc/codecs/wm8993.h @@ -0,0 +1,2139 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef WM8993_H +#define WM8993_H + +#define WM8993_SYSCLK_MCLK 1 +#define WM8993_SYSCLK_FLL 2 + +#define WM8993_FLL_MCLK 1 +#define WM8993_FLL_BCLK 2 +#define WM8993_FLL_LRCLK 3 + +/* + * Register values. + */ +#define WM8993_SOFTWARE_RESET 0x00 +#define WM8993_POWER_MANAGEMENT_1 0x01 +#define WM8993_POWER_MANAGEMENT_2 0x02 +#define WM8993_POWER_MANAGEMENT_3 0x03 +#define WM8993_AUDIO_INTERFACE_1 0x04 +#define WM8993_AUDIO_INTERFACE_2 0x05 +#define WM8993_CLOCKING_1 0x06 +#define WM8993_CLOCKING_2 0x07 +#define WM8993_AUDIO_INTERFACE_3 0x08 +#define WM8993_AUDIO_INTERFACE_4 0x09 +#define WM8993_DAC_CTRL 0x0A +#define WM8993_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8993_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8993_DIGITAL_SIDE_TONE 0x0D +#define WM8993_ADC_CTRL 0x0E +#define WM8993_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8993_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8993_GPIO_CTRL_1 0x12 +#define WM8993_GPIO1 0x13 +#define WM8993_IRQ_DEBOUNCE 0x14 +#define WM8993_INPUTS_CLAMP_REG 0x15 +#define WM8993_GPIOCTRL_2 0x16 +#define WM8993_GPIO_POL 0x17 +#define WM8993_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8993_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8993_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8993_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8993_LEFT_OUTPUT_VOLUME 0x1C +#define WM8993_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8993_LINE_OUTPUTS_VOLUME 0x1E +#define WM8993_HPOUT2_VOLUME 0x1F +#define WM8993_LEFT_OPGA_VOLUME 0x20 +#define WM8993_RIGHT_OPGA_VOLUME 0x21 +#define WM8993_SPKMIXL_ATTENUATION 0x22 +#define WM8993_SPKMIXR_ATTENUATION 0x23 +#define WM8993_SPKOUT_MIXERS 0x24 +#define WM8993_SPKOUT_BOOST 0x25 +#define WM8993_SPEAKER_VOLUME_LEFT 0x26 +#define WM8993_SPEAKER_VOLUME_RIGHT 0x27 +#define WM8993_INPUT_MIXER2 0x28 +#define WM8993_INPUT_MIXER3 0x29 +#define WM8993_INPUT_MIXER4 0x2A +#define WM8993_INPUT_MIXER5 0x2B +#define WM8993_INPUT_MIXER6 0x2C +#define WM8993_OUTPUT_MIXER1 0x2D +#define WM8993_OUTPUT_MIXER2 0x2E +#define WM8993_OUTPUT_MIXER3 0x2F +#define WM8993_OUTPUT_MIXER4 0x30 +#define WM8993_OUTPUT_MIXER5 0x31 +#define WM8993_OUTPUT_MIXER6 0x32 +#define WM8993_HPOUT2_MIXER 0x33 +#define WM8993_LINE_MIXER1 0x34 +#define WM8993_LINE_MIXER2 0x35 +#define WM8993_SPEAKER_MIXER 0x36 +#define WM8993_ADDITIONAL_CONTROL 0x37 +#define WM8993_ANTIPOP1 0x38 +#define WM8993_ANTIPOP2 0x39 +#define WM8993_MICBIAS 0x3A +#define WM8993_FLL_CONTROL_1 0x3C +#define WM8993_FLL_CONTROL_2 0x3D +#define WM8993_FLL_CONTROL_3 0x3E +#define WM8993_FLL_CONTROL_4 0x3F +#define WM8993_FLL_CONTROL_5 0x40 +#define WM8993_CLOCKING_3 0x41 +#define WM8993_CLOCKING_4 0x42 +#define WM8993_MW_SLAVE_CONTROL 0x43 +#define WM8993_BUS_CONTROL_1 0x45 +#define WM8993_WRITE_SEQUENCER_0 0x46 +#define WM8993_WRITE_SEQUENCER_1 0x47 +#define WM8993_WRITE_SEQUENCER_2 0x48 +#define WM8993_WRITE_SEQUENCER_3 0x49 +#define WM8993_WRITE_SEQUENCER_4 0x4A +#define WM8993_WRITE_SEQUENCER_5 0x4B +#define WM8993_CHARGE_PUMP_1 0x4C +#define WM8993_CLASS_W_0 0x51 +#define WM8993_DC_SERVO_0 0x54 +#define WM8993_DC_SERVO_1 0x55 +#define WM8993_DC_SERVO_3 0x57 +#define WM8993_DC_SERVO_READBACK_0 0x58 +#define WM8993_DC_SERVO_READBACK_1 0x59 +#define WM8993_DC_SERVO_READBACK_2 0x5A +#define WM8993_ANALOGUE_HP_0 0x60 +#define WM8993_EQ1 0x62 +#define WM8993_EQ2 0x63 +#define WM8993_EQ3 0x64 +#define WM8993_EQ4 0x65 +#define WM8993_EQ5 0x66 +#define WM8993_EQ6 0x67 +#define WM8993_EQ7 0x68 +#define WM8993_EQ8 0x69 +#define WM8993_EQ9 0x6A +#define WM8993_EQ10 0x6B +#define WM8993_EQ11 0x6C +#define WM8993_EQ12 0x6D +#define WM8993_EQ13 0x6E +#define WM8993_EQ14 0x6F +#define WM8993_EQ15 0x70 +#define WM8993_EQ16 0x71 +#define WM8993_EQ17 0x72 +#define WM8993_EQ18 0x73 +#define WM8993_EQ19 0x74 +#define WM8993_EQ20 0x75 +#define WM8993_EQ21 0x76 +#define WM8993_EQ22 0x77 +#define WM8993_EQ23 0x78 +#define WM8993_EQ24 0x79 +#define WM8993_DIGITAL_PULLS 0x7A +#define WM8993_DRC_CONTROL_1 0x7B +#define WM8993_DRC_CONTROL_2 0x7C +#define WM8993_DRC_CONTROL_3 0x7D +#define WM8993_DRC_CONTROL_4 0x7E + +#define WM8993_REGISTER_COUNT 0x7F +#define WM8993_MAX_REGISTER 0x7E + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM8993_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM8993_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM8993_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8993_SPKOUTR_ENA 0x2000 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTR_ENA_MASK 0x2000 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTR_ENA_SHIFT 13 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTR_ENA_WIDTH 1 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTL_ENA 0x1000 /* SPKOUTL_ENA */ +#define WM8993_SPKOUTL_ENA_MASK 0x1000 /* SPKOUTL_ENA */ +#define WM8993_SPKOUTL_ENA_SHIFT 12 /* SPKOUTL_ENA */ +#define WM8993_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */ +#define WM8993_HPOUT2_ENA 0x0800 /* HPOUT2_ENA */ +#define WM8993_HPOUT2_ENA_MASK 0x0800 /* HPOUT2_ENA */ +#define WM8993_HPOUT2_ENA_SHIFT 11 /* HPOUT2_ENA */ +#define WM8993_HPOUT2_ENA_WIDTH 1 /* HPOUT2_ENA */ +#define WM8993_HPOUT1L_ENA 0x0200 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1L_ENA_MASK 0x0200 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1L_ENA_SHIFT 9 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1R_ENA 0x0100 /* HPOUT1R_ENA */ +#define WM8993_HPOUT1R_ENA_MASK 0x0100 /* HPOUT1R_ENA */ +#define WM8993_HPOUT1R_ENA_SHIFT 8 /* HPOUT1R_ENA */ +#define WM8993_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */ +#define WM8993_MICB2_ENA 0x0020 /* MICB2_ENA */ +#define WM8993_MICB2_ENA_MASK 0x0020 /* MICB2_ENA */ +#define WM8993_MICB2_ENA_SHIFT 5 /* MICB2_ENA */ +#define WM8993_MICB2_ENA_WIDTH 1 /* MICB2_ENA */ +#define WM8993_MICB1_ENA 0x0010 /* MICB1_ENA */ +#define WM8993_MICB1_ENA_MASK 0x0010 /* MICB1_ENA */ +#define WM8993_MICB1_ENA_SHIFT 4 /* MICB1_ENA */ +#define WM8993_MICB1_ENA_WIDTH 1 /* MICB1_ENA */ +#define WM8993_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */ +#define WM8993_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */ +#define WM8993_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */ +#define WM8993_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM8993_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM8993_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM8993_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8993_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8993_TSHUT_ENA_MASK 0x4000 /* TSHUT_ENA */ +#define WM8993_TSHUT_ENA_SHIFT 14 /* TSHUT_ENA */ +#define WM8993_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM8993_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8993_TSHUT_OPDIS_MASK 0x2000 /* TSHUT_OPDIS */ +#define WM8993_TSHUT_OPDIS_SHIFT 13 /* TSHUT_OPDIS */ +#define WM8993_TSHUT_OPDIS_WIDTH 1 /* TSHUT_OPDIS */ +#define WM8993_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8993_OPCLK_ENA_MASK 0x0800 /* OPCLK_ENA */ +#define WM8993_OPCLK_ENA_SHIFT 11 /* OPCLK_ENA */ +#define WM8993_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */ +#define WM8993_MIXINL_ENA 0x0200 /* MIXINL_ENA */ +#define WM8993_MIXINL_ENA_MASK 0x0200 /* MIXINL_ENA */ +#define WM8993_MIXINL_ENA_SHIFT 9 /* MIXINL_ENA */ +#define WM8993_MIXINL_ENA_WIDTH 1 /* MIXINL_ENA */ +#define WM8993_MIXINR_ENA 0x0100 /* MIXINR_ENA */ +#define WM8993_MIXINR_ENA_MASK 0x0100 /* MIXINR_ENA */ +#define WM8993_MIXINR_ENA_SHIFT 8 /* MIXINR_ENA */ +#define WM8993_MIXINR_ENA_WIDTH 1 /* MIXINR_ENA */ +#define WM8993_IN2L_ENA 0x0080 /* IN2L_ENA */ +#define WM8993_IN2L_ENA_MASK 0x0080 /* IN2L_ENA */ +#define WM8993_IN2L_ENA_SHIFT 7 /* IN2L_ENA */ +#define WM8993_IN2L_ENA_WIDTH 1 /* IN2L_ENA */ +#define WM8993_IN1L_ENA 0x0040 /* IN1L_ENA */ +#define WM8993_IN1L_ENA_MASK 0x0040 /* IN1L_ENA */ +#define WM8993_IN1L_ENA_SHIFT 6 /* IN1L_ENA */ +#define WM8993_IN1L_ENA_WIDTH 1 /* IN1L_ENA */ +#define WM8993_IN2R_ENA 0x0020 /* IN2R_ENA */ +#define WM8993_IN2R_ENA_MASK 0x0020 /* IN2R_ENA */ +#define WM8993_IN2R_ENA_SHIFT 5 /* IN2R_ENA */ +#define WM8993_IN2R_ENA_WIDTH 1 /* IN2R_ENA */ +#define WM8993_IN1R_ENA 0x0010 /* IN1R_ENA */ +#define WM8993_IN1R_ENA_MASK 0x0010 /* IN1R_ENA */ +#define WM8993_IN1R_ENA_SHIFT 4 /* IN1R_ENA */ +#define WM8993_IN1R_ENA_WIDTH 1 /* IN1R_ENA */ +#define WM8993_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8993_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8993_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8993_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8993_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8993_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8993_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8993_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8993_LINEOUT1N_ENA 0x2000 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1N_ENA_MASK 0x2000 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1N_ENA_SHIFT 13 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1N_ENA_WIDTH 1 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1P_ENA 0x1000 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT1P_ENA_MASK 0x1000 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT1P_ENA_SHIFT 12 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT1P_ENA_WIDTH 1 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT2N_ENA 0x0800 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2N_ENA_MASK 0x0800 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2N_ENA_SHIFT 11 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2N_ENA_WIDTH 1 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2P_ENA 0x0400 /* LINEOUT2P_ENA */ +#define WM8993_LINEOUT2P_ENA_MASK 0x0400 /* LINEOUT2P_ENA */ +#define WM8993_LINEOUT2P_ENA_SHIFT 10 /* LINEOUT2P_ENA */ +#define WM8993_LINEOUT2P_ENA_WIDTH 1 /* LINEOUT2P_ENA */ +#define WM8993_SPKRVOL_ENA 0x0200 /* SPKRVOL_ENA */ +#define WM8993_SPKRVOL_ENA_MASK 0x0200 /* SPKRVOL_ENA */ +#define WM8993_SPKRVOL_ENA_SHIFT 9 /* SPKRVOL_ENA */ +#define WM8993_SPKRVOL_ENA_WIDTH 1 /* SPKRVOL_ENA */ +#define WM8993_SPKLVOL_ENA 0x0100 /* SPKLVOL_ENA */ +#define WM8993_SPKLVOL_ENA_MASK 0x0100 /* SPKLVOL_ENA */ +#define WM8993_SPKLVOL_ENA_SHIFT 8 /* SPKLVOL_ENA */ +#define WM8993_SPKLVOL_ENA_WIDTH 1 /* SPKLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA 0x0080 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA_MASK 0x0080 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA_SHIFT 7 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA_WIDTH 1 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA 0x0040 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA_MASK 0x0040 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA_SHIFT 6 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA_WIDTH 1 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTL_ENA 0x0020 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTL_ENA_MASK 0x0020 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTL_ENA_SHIFT 5 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTR_ENA 0x0010 /* MIXOUTR_ENA */ +#define WM8993_MIXOUTR_ENA_MASK 0x0010 /* MIXOUTR_ENA */ +#define WM8993_MIXOUTR_ENA_SHIFT 4 /* MIXOUTR_ENA */ +#define WM8993_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ +#define WM8993_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8993_DACL_ENA_MASK 0x0002 /* DACL_ENA */ +#define WM8993_DACL_ENA_SHIFT 1 /* DACL_ENA */ +#define WM8993_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8993_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8993_DACR_ENA_MASK 0x0001 /* DACR_ENA */ +#define WM8993_DACR_ENA_SHIFT 0 /* DACR_ENA */ +#define WM8993_DACR_ENA_WIDTH 1 /* DACR_ENA */ + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8993_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8993_AIFADCL_SRC_MASK 0x8000 /* AIFADCL_SRC */ +#define WM8993_AIFADCL_SRC_SHIFT 15 /* AIFADCL_SRC */ +#define WM8993_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */ +#define WM8993_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8993_AIFADCR_SRC_MASK 0x4000 /* AIFADCR_SRC */ +#define WM8993_AIFADCR_SRC_SHIFT 14 /* AIFADCR_SRC */ +#define WM8993_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */ +#define WM8993_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_MASK 0x2000 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_SHIFT 13 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8993_AIFADC_TDM_CHAN_MASK 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8993_AIFADC_TDM_CHAN_SHIFT 12 /* AIFADC_TDM_CHAN */ +#define WM8993_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */ +#define WM8993_BCLK_DIR 0x0200 /* BCLK_DIR */ +#define WM8993_BCLK_DIR_MASK 0x0200 /* BCLK_DIR */ +#define WM8993_BCLK_DIR_SHIFT 9 /* BCLK_DIR */ +#define WM8993_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM8993_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8993_AIF_BCLK_INV_MASK 0x0100 /* AIF_BCLK_INV */ +#define WM8993_AIF_BCLK_INV_SHIFT 8 /* AIF_BCLK_INV */ +#define WM8993_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM8993_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8993_AIF_LRCLK_INV_MASK 0x0080 /* AIF_LRCLK_INV */ +#define WM8993_AIF_LRCLK_INV_SHIFT 7 /* AIF_LRCLK_INV */ +#define WM8993_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM8993_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8993_AIF_WL_SHIFT 5 /* AIF_WL - [6:5] */ +#define WM8993_AIF_WL_WIDTH 2 /* AIF_WL - [6:5] */ +#define WM8993_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8993_AIF_FMT_SHIFT 3 /* AIF_FMT - [4:3] */ +#define WM8993_AIF_FMT_WIDTH 2 /* AIF_FMT - [4:3] */ + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8993_AIFDACL_SRC 0x8000 /* AIFDACL_SRC */ +#define WM8993_AIFDACL_SRC_MASK 0x8000 /* AIFDACL_SRC */ +#define WM8993_AIFDACL_SRC_SHIFT 15 /* AIFDACL_SRC */ +#define WM8993_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */ +#define WM8993_AIFDACR_SRC 0x4000 /* AIFDACR_SRC */ +#define WM8993_AIFDACR_SRC_MASK 0x4000 /* AIFDACR_SRC */ +#define WM8993_AIFDACR_SRC_SHIFT 14 /* AIFDACR_SRC */ +#define WM8993_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */ +#define WM8993_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8993_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8993_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */ +#define WM8993_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */ +#define WM8993_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */ +#define WM8993_DAC_BOOST_SHIFT 10 /* DAC_BOOST - [11:10] */ +#define WM8993_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [11:10] */ +#define WM8993_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8993_DAC_COMP_MASK 0x0010 /* DAC_COMP */ +#define WM8993_DAC_COMP_SHIFT 4 /* DAC_COMP */ +#define WM8993_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8993_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8993_DAC_COMPMODE_MASK 0x0008 /* DAC_COMPMODE */ +#define WM8993_DAC_COMPMODE_SHIFT 3 /* DAC_COMPMODE */ +#define WM8993_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ +#define WM8993_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8993_ADC_COMP_MASK 0x0004 /* ADC_COMP */ +#define WM8993_ADC_COMP_SHIFT 2 /* ADC_COMP */ +#define WM8993_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8993_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8993_ADC_COMPMODE_MASK 0x0002 /* ADC_COMPMODE */ +#define WM8993_ADC_COMPMODE_SHIFT 1 /* ADC_COMPMODE */ +#define WM8993_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8993_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8993_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8993_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8993_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking 1 + */ +#define WM8993_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8993_TOCLK_RATE_MASK 0x8000 /* TOCLK_RATE */ +#define WM8993_TOCLK_RATE_SHIFT 15 /* TOCLK_RATE */ +#define WM8993_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */ +#define WM8993_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8993_TOCLK_ENA_MASK 0x4000 /* TOCLK_ENA */ +#define WM8993_TOCLK_ENA_SHIFT 14 /* TOCLK_ENA */ +#define WM8993_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ +#define WM8993_OPCLK_DIV_MASK 0x1E00 /* OPCLK_DIV - [12:9] */ +#define WM8993_OPCLK_DIV_SHIFT 9 /* OPCLK_DIV - [12:9] */ +#define WM8993_OPCLK_DIV_WIDTH 4 /* OPCLK_DIV - [12:9] */ +#define WM8993_DCLK_DIV_MASK 0x01C0 /* DCLK_DIV - [8:6] */ +#define WM8993_DCLK_DIV_SHIFT 6 /* DCLK_DIV - [8:6] */ +#define WM8993_DCLK_DIV_WIDTH 3 /* DCLK_DIV - [8:6] */ +#define WM8993_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8993_BCLK_DIV_SHIFT 1 /* BCLK_DIV - [4:1] */ +#define WM8993_BCLK_DIV_WIDTH 4 /* BCLK_DIV - [4:1] */ + +/* + * R7 (0x07) - Clocking 2 + */ +#define WM8993_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8993_MCLK_SRC_MASK 0x8000 /* MCLK_SRC */ +#define WM8993_MCLK_SRC_SHIFT 15 /* MCLK_SRC */ +#define WM8993_MCLK_SRC_WIDTH 1 /* MCLK_SRC */ +#define WM8993_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8993_SYSCLK_SRC_MASK 0x4000 /* SYSCLK_SRC */ +#define WM8993_SYSCLK_SRC_SHIFT 14 /* SYSCLK_SRC */ +#define WM8993_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */ +#define WM8993_MCLK_DIV 0x1000 /* MCLK_DIV */ +#define WM8993_MCLK_DIV_MASK 0x1000 /* MCLK_DIV */ +#define WM8993_MCLK_DIV_SHIFT 12 /* MCLK_DIV */ +#define WM8993_MCLK_DIV_WIDTH 1 /* MCLK_DIV */ +#define WM8993_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8993_MCLK_INV_MASK 0x0400 /* MCLK_INV */ +#define WM8993_MCLK_INV_SHIFT 10 /* MCLK_INV */ +#define WM8993_MCLK_INV_WIDTH 1 /* MCLK_INV */ +#define WM8993_ADC_DIV_MASK 0x00E0 /* ADC_DIV - [7:5] */ +#define WM8993_ADC_DIV_SHIFT 5 /* ADC_DIV - [7:5] */ +#define WM8993_ADC_DIV_WIDTH 3 /* ADC_DIV - [7:5] */ +#define WM8993_DAC_DIV_MASK 0x001C /* DAC_DIV - [4:2] */ +#define WM8993_DAC_DIV_SHIFT 2 /* DAC_DIV - [4:2] */ +#define WM8993_DAC_DIV_WIDTH 3 /* DAC_DIV - [4:2] */ + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8993_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8993_AIF_MSTR1_MASK 0x8000 /* AIF_MSTR1 */ +#define WM8993_AIF_MSTR1_SHIFT 15 /* AIF_MSTR1 */ +#define WM8993_AIF_MSTR1_WIDTH 1 /* AIF_MSTR1 */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8993_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8993_AIF_TRIS_MASK 0x2000 /* AIF_TRIS */ +#define WM8993_AIF_TRIS_SHIFT 13 /* AIF_TRIS */ +#define WM8993_AIF_TRIS_WIDTH 1 /* AIF_TRIS */ +#define WM8993_LRCLK_DIR 0x0800 /* LRCLK_DIR */ +#define WM8993_LRCLK_DIR_MASK 0x0800 /* LRCLK_DIR */ +#define WM8993_LRCLK_DIR_SHIFT 11 /* LRCLK_DIR */ +#define WM8993_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM8993_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM8993_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM8993_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8993_DAC_OSR128 0x2000 /* DAC_OSR128 */ +#define WM8993_DAC_OSR128_MASK 0x2000 /* DAC_OSR128 */ +#define WM8993_DAC_OSR128_SHIFT 13 /* DAC_OSR128 */ +#define WM8993_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ +#define WM8993_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8993_DAC_MONO_MASK 0x0200 /* DAC_MONO */ +#define WM8993_DAC_MONO_SHIFT 9 /* DAC_MONO */ +#define WM8993_DAC_MONO_WIDTH 1 /* DAC_MONO */ +#define WM8993_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8993_DAC_SB_FILT_MASK 0x0100 /* DAC_SB_FILT */ +#define WM8993_DAC_SB_FILT_SHIFT 8 /* DAC_SB_FILT */ +#define WM8993_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */ +#define WM8993_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8993_DAC_MUTERATE_MASK 0x0080 /* DAC_MUTERATE */ +#define WM8993_DAC_MUTERATE_SHIFT 7 /* DAC_MUTERATE */ +#define WM8993_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8993_DAC_UNMUTE_RAMP 0x0040 /* DAC_UNMUTE_RAMP */ +#define WM8993_DAC_UNMUTE_RAMP_MASK 0x0040 /* DAC_UNMUTE_RAMP */ +#define WM8993_DAC_UNMUTE_RAMP_SHIFT 6 /* DAC_UNMUTE_RAMP */ +#define WM8993_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */ +#define WM8993_DEEMPH_MASK 0x0030 /* DEEMPH - [5:4] */ +#define WM8993_DEEMPH_SHIFT 4 /* DEEMPH - [5:4] */ +#define WM8993_DEEMPH_WIDTH 2 /* DEEMPH - [5:4] */ +#define WM8993_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8993_DAC_MUTE_MASK 0x0004 /* DAC_MUTE */ +#define WM8993_DAC_MUTE_SHIFT 2 /* DAC_MUTE */ +#define WM8993_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8993_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8993_DACL_DATINV_MASK 0x0002 /* DACL_DATINV */ +#define WM8993_DACL_DATINV_SHIFT 1 /* DACL_DATINV */ +#define WM8993_DACL_DATINV_WIDTH 1 /* DACL_DATINV */ +#define WM8993_DACR_DATINV 0x0001 /* DACR_DATINV */ +#define WM8993_DACR_DATINV_MASK 0x0001 /* DACR_DATINV */ +#define WM8993_DACR_DATINV_SHIFT 0 /* DACR_DATINV */ +#define WM8993_DACR_DATINV_WIDTH 1 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8993_DAC_VU 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8993_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8993_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8993_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8993_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8993_DAC_VU 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8993_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8993_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8993_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8993_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8993_ADCL_DAC_SVOL_MASK 0x1E00 /* ADCL_DAC_SVOL - [12:9] */ +#define WM8993_ADCL_DAC_SVOL_SHIFT 9 /* ADCL_DAC_SVOL - [12:9] */ +#define WM8993_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [12:9] */ +#define WM8993_ADCR_DAC_SVOL_MASK 0x01E0 /* ADCR_DAC_SVOL - [8:5] */ +#define WM8993_ADCR_DAC_SVOL_SHIFT 5 /* ADCR_DAC_SVOL - [8:5] */ +#define WM8993_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [8:5] */ +#define WM8993_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8993_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8993_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ +#define WM8993_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */ +#define WM8993_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */ +#define WM8993_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */ + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8993_ADC_OSR128 0x0200 /* ADC_OSR128 */ +#define WM8993_ADC_OSR128_MASK 0x0200 /* ADC_OSR128 */ +#define WM8993_ADC_OSR128_SHIFT 9 /* ADC_OSR128 */ +#define WM8993_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ +#define WM8993_ADC_HPF 0x0100 /* ADC_HPF */ +#define WM8993_ADC_HPF_MASK 0x0100 /* ADC_HPF */ +#define WM8993_ADC_HPF_SHIFT 8 /* ADC_HPF */ +#define WM8993_ADC_HPF_WIDTH 1 /* ADC_HPF */ +#define WM8993_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */ +#define WM8993_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */ +#define WM8993_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */ +#define WM8993_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8993_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */ +#define WM8993_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */ +#define WM8993_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */ +#define WM8993_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8993_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */ +#define WM8993_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */ +#define WM8993_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */ + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8993_ADC_VU 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8993_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8993_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8993_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8993_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8993_ADC_VU 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8993_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8993_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8993_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8993_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8993_JD2_SC_EINT 0x8000 /* JD2_SC_EINT */ +#define WM8993_JD2_SC_EINT_MASK 0x8000 /* JD2_SC_EINT */ +#define WM8993_JD2_SC_EINT_SHIFT 15 /* JD2_SC_EINT */ +#define WM8993_JD2_SC_EINT_WIDTH 1 /* JD2_SC_EINT */ +#define WM8993_JD2_EINT 0x4000 /* JD2_EINT */ +#define WM8993_JD2_EINT_MASK 0x4000 /* JD2_EINT */ +#define WM8993_JD2_EINT_SHIFT 14 /* JD2_EINT */ +#define WM8993_JD2_EINT_WIDTH 1 /* JD2_EINT */ +#define WM8993_WSEQ_EINT 0x2000 /* WSEQ_EINT */ +#define WM8993_WSEQ_EINT_MASK 0x2000 /* WSEQ_EINT */ +#define WM8993_WSEQ_EINT_SHIFT 13 /* WSEQ_EINT */ +#define WM8993_WSEQ_EINT_WIDTH 1 /* WSEQ_EINT */ +#define WM8993_IRQ 0x1000 /* IRQ */ +#define WM8993_IRQ_MASK 0x1000 /* IRQ */ +#define WM8993_IRQ_SHIFT 12 /* IRQ */ +#define WM8993_IRQ_WIDTH 1 /* IRQ */ +#define WM8993_TEMPOK_EINT 0x0800 /* TEMPOK_EINT */ +#define WM8993_TEMPOK_EINT_MASK 0x0800 /* TEMPOK_EINT */ +#define WM8993_TEMPOK_EINT_SHIFT 11 /* TEMPOK_EINT */ +#define WM8993_TEMPOK_EINT_WIDTH 1 /* TEMPOK_EINT */ +#define WM8993_JD1_SC_EINT 0x0400 /* JD1_SC_EINT */ +#define WM8993_JD1_SC_EINT_MASK 0x0400 /* JD1_SC_EINT */ +#define WM8993_JD1_SC_EINT_SHIFT 10 /* JD1_SC_EINT */ +#define WM8993_JD1_SC_EINT_WIDTH 1 /* JD1_SC_EINT */ +#define WM8993_JD1_EINT 0x0200 /* JD1_EINT */ +#define WM8993_JD1_EINT_MASK 0x0200 /* JD1_EINT */ +#define WM8993_JD1_EINT_SHIFT 9 /* JD1_EINT */ +#define WM8993_JD1_EINT_WIDTH 1 /* JD1_EINT */ +#define WM8993_FLL_LOCK_EINT 0x0100 /* FLL_LOCK_EINT */ +#define WM8993_FLL_LOCK_EINT_MASK 0x0100 /* FLL_LOCK_EINT */ +#define WM8993_FLL_LOCK_EINT_SHIFT 8 /* FLL_LOCK_EINT */ +#define WM8993_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */ +#define WM8993_GPI8_EINT 0x0080 /* GPI8_EINT */ +#define WM8993_GPI8_EINT_MASK 0x0080 /* GPI8_EINT */ +#define WM8993_GPI8_EINT_SHIFT 7 /* GPI8_EINT */ +#define WM8993_GPI8_EINT_WIDTH 1 /* GPI8_EINT */ +#define WM8993_GPI7_EINT 0x0040 /* GPI7_EINT */ +#define WM8993_GPI7_EINT_MASK 0x0040 /* GPI7_EINT */ +#define WM8993_GPI7_EINT_SHIFT 6 /* GPI7_EINT */ +#define WM8993_GPI7_EINT_WIDTH 1 /* GPI7_EINT */ +#define WM8993_GPIO1_EINT 0x0001 /* GPIO1_EINT */ +#define WM8993_GPIO1_EINT_MASK 0x0001 /* GPIO1_EINT */ +#define WM8993_GPIO1_EINT_SHIFT 0 /* GPIO1_EINT */ +#define WM8993_GPIO1_EINT_WIDTH 1 /* GPIO1_EINT */ + +/* + * R19 (0x13) - GPIO1 + */ +#define WM8993_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8993_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */ +#define WM8993_GPIO1_PU_SHIFT 5 /* GPIO1_PU */ +#define WM8993_GPIO1_PU_WIDTH 1 /* GPIO1_PU */ +#define WM8993_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8993_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */ +#define WM8993_GPIO1_PD_SHIFT 4 /* GPIO1_PD */ +#define WM8993_GPIO1_PD_WIDTH 1 /* GPIO1_PD */ +#define WM8993_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ +#define WM8993_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */ +#define WM8993_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - IRQ_DEBOUNCE + */ +#define WM8993_JD2_SC_DB 0x8000 /* JD2_SC_DB */ +#define WM8993_JD2_SC_DB_MASK 0x8000 /* JD2_SC_DB */ +#define WM8993_JD2_SC_DB_SHIFT 15 /* JD2_SC_DB */ +#define WM8993_JD2_SC_DB_WIDTH 1 /* JD2_SC_DB */ +#define WM8993_JD2_DB 0x4000 /* JD2_DB */ +#define WM8993_JD2_DB_MASK 0x4000 /* JD2_DB */ +#define WM8993_JD2_DB_SHIFT 14 /* JD2_DB */ +#define WM8993_JD2_DB_WIDTH 1 /* JD2_DB */ +#define WM8993_WSEQ_DB 0x2000 /* WSEQ_DB */ +#define WM8993_WSEQ_DB_MASK 0x2000 /* WSEQ_DB */ +#define WM8993_WSEQ_DB_SHIFT 13 /* WSEQ_DB */ +#define WM8993_WSEQ_DB_WIDTH 1 /* WSEQ_DB */ +#define WM8993_TEMPOK_DB 0x0800 /* TEMPOK_DB */ +#define WM8993_TEMPOK_DB_MASK 0x0800 /* TEMPOK_DB */ +#define WM8993_TEMPOK_DB_SHIFT 11 /* TEMPOK_DB */ +#define WM8993_TEMPOK_DB_WIDTH 1 /* TEMPOK_DB */ +#define WM8993_JD1_SC_DB 0x0400 /* JD1_SC_DB */ +#define WM8993_JD1_SC_DB_MASK 0x0400 /* JD1_SC_DB */ +#define WM8993_JD1_SC_DB_SHIFT 10 /* JD1_SC_DB */ +#define WM8993_JD1_SC_DB_WIDTH 1 /* JD1_SC_DB */ +#define WM8993_JD1_DB 0x0200 /* JD1_DB */ +#define WM8993_JD1_DB_MASK 0x0200 /* JD1_DB */ +#define WM8993_JD1_DB_SHIFT 9 /* JD1_DB */ +#define WM8993_JD1_DB_WIDTH 1 /* JD1_DB */ +#define WM8993_FLL_LOCK_DB 0x0100 /* FLL_LOCK_DB */ +#define WM8993_FLL_LOCK_DB_MASK 0x0100 /* FLL_LOCK_DB */ +#define WM8993_FLL_LOCK_DB_SHIFT 8 /* FLL_LOCK_DB */ +#define WM8993_FLL_LOCK_DB_WIDTH 1 /* FLL_LOCK_DB */ +#define WM8993_GPI8_DB 0x0080 /* GPI8_DB */ +#define WM8993_GPI8_DB_MASK 0x0080 /* GPI8_DB */ +#define WM8993_GPI8_DB_SHIFT 7 /* GPI8_DB */ +#define WM8993_GPI8_DB_WIDTH 1 /* GPI8_DB */ +#define WM8993_GPI7_DB 0x0008 /* GPI7_DB */ +#define WM8993_GPI7_DB_MASK 0x0008 /* GPI7_DB */ +#define WM8993_GPI7_DB_SHIFT 3 /* GPI7_DB */ +#define WM8993_GPI7_DB_WIDTH 1 /* GPI7_DB */ +#define WM8993_GPIO1_DB 0x0001 /* GPIO1_DB */ +#define WM8993_GPIO1_DB_MASK 0x0001 /* GPIO1_DB */ +#define WM8993_GPIO1_DB_SHIFT 0 /* GPIO1_DB */ +#define WM8993_GPIO1_DB_WIDTH 1 /* GPIO1_DB */ + +/* + * R21 (0x15) - Inputs Clamp + */ +#define WM8993_INPUTS_CLAMP 0x0040 /* INPUTS_CLAMP */ +#define WM8993_INPUTS_CLAMP_MASK 0x0040 /* INPUTS_CLAMP */ +#define WM8993_INPUTS_CLAMP_SHIFT 7 /* INPUTS_CLAMP */ +#define WM8993_INPUTS_CLAMP_WIDTH 1 /* INPUTS_CLAMP */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8993_IM_JD2_EINT 0x2000 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_EINT_MASK 0x2000 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_EINT_SHIFT 13 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_EINT_WIDTH 1 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_SC_EINT 0x1000 /* IM_JD2_SC_EINT */ +#define WM8993_IM_JD2_SC_EINT_MASK 0x1000 /* IM_JD2_SC_EINT */ +#define WM8993_IM_JD2_SC_EINT_SHIFT 12 /* IM_JD2_SC_EINT */ +#define WM8993_IM_JD2_SC_EINT_WIDTH 1 /* IM_JD2_SC_EINT */ +#define WM8993_IM_TEMPOK_EINT 0x0800 /* IM_TEMPOK_EINT */ +#define WM8993_IM_TEMPOK_EINT_MASK 0x0800 /* IM_TEMPOK_EINT */ +#define WM8993_IM_TEMPOK_EINT_SHIFT 11 /* IM_TEMPOK_EINT */ +#define WM8993_IM_TEMPOK_EINT_WIDTH 1 /* IM_TEMPOK_EINT */ +#define WM8993_IM_JD1_SC_EINT 0x0400 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_SC_EINT_MASK 0x0400 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_SC_EINT_SHIFT 10 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_SC_EINT_WIDTH 1 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_EINT 0x0200 /* IM_JD1_EINT */ +#define WM8993_IM_JD1_EINT_MASK 0x0200 /* IM_JD1_EINT */ +#define WM8993_IM_JD1_EINT_SHIFT 9 /* IM_JD1_EINT */ +#define WM8993_IM_JD1_EINT_WIDTH 1 /* IM_JD1_EINT */ +#define WM8993_IM_FLL_LOCK_EINT 0x0100 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_FLL_LOCK_EINT_MASK 0x0100 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_FLL_LOCK_EINT_SHIFT 8 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_GPI8_EINT 0x0040 /* IM_GPI8_EINT */ +#define WM8993_IM_GPI8_EINT_MASK 0x0040 /* IM_GPI8_EINT */ +#define WM8993_IM_GPI8_EINT_SHIFT 6 /* IM_GPI8_EINT */ +#define WM8993_IM_GPI8_EINT_WIDTH 1 /* IM_GPI8_EINT */ +#define WM8993_IM_GPIO1_EINT 0x0020 /* IM_GPIO1_EINT */ +#define WM8993_IM_GPIO1_EINT_MASK 0x0020 /* IM_GPIO1_EINT */ +#define WM8993_IM_GPIO1_EINT_SHIFT 5 /* IM_GPIO1_EINT */ +#define WM8993_IM_GPIO1_EINT_WIDTH 1 /* IM_GPIO1_EINT */ +#define WM8993_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8993_GPI8_ENA_MASK 0x0010 /* GPI8_ENA */ +#define WM8993_GPI8_ENA_SHIFT 4 /* GPI8_ENA */ +#define WM8993_GPI8_ENA_WIDTH 1 /* GPI8_ENA */ +#define WM8993_IM_GPI7_EINT 0x0004 /* IM_GPI7_EINT */ +#define WM8993_IM_GPI7_EINT_MASK 0x0004 /* IM_GPI7_EINT */ +#define WM8993_IM_GPI7_EINT_SHIFT 2 /* IM_GPI7_EINT */ +#define WM8993_IM_GPI7_EINT_WIDTH 1 /* IM_GPI7_EINT */ +#define WM8993_IM_WSEQ_EINT 0x0002 /* IM_WSEQ_EINT */ +#define WM8993_IM_WSEQ_EINT_MASK 0x0002 /* IM_WSEQ_EINT */ +#define WM8993_IM_WSEQ_EINT_SHIFT 1 /* IM_WSEQ_EINT */ +#define WM8993_IM_WSEQ_EINT_WIDTH 1 /* IM_WSEQ_EINT */ +#define WM8993_GPI7_ENA 0x0001 /* GPI7_ENA */ +#define WM8993_GPI7_ENA_MASK 0x0001 /* GPI7_ENA */ +#define WM8993_GPI7_ENA_SHIFT 0 /* GPI7_ENA */ +#define WM8993_GPI7_ENA_WIDTH 1 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8993_JD2_SC_POL 0x8000 /* JD2_SC_POL */ +#define WM8993_JD2_SC_POL_MASK 0x8000 /* JD2_SC_POL */ +#define WM8993_JD2_SC_POL_SHIFT 15 /* JD2_SC_POL */ +#define WM8993_JD2_SC_POL_WIDTH 1 /* JD2_SC_POL */ +#define WM8993_JD2_POL 0x4000 /* JD2_POL */ +#define WM8993_JD2_POL_MASK 0x4000 /* JD2_POL */ +#define WM8993_JD2_POL_SHIFT 14 /* JD2_POL */ +#define WM8993_JD2_POL_WIDTH 1 /* JD2_POL */ +#define WM8993_WSEQ_POL 0x2000 /* WSEQ_POL */ +#define WM8993_WSEQ_POL_MASK 0x2000 /* WSEQ_POL */ +#define WM8993_WSEQ_POL_SHIFT 13 /* WSEQ_POL */ +#define WM8993_WSEQ_POL_WIDTH 1 /* WSEQ_POL */ +#define WM8993_IRQ_POL 0x1000 /* IRQ_POL */ +#define WM8993_IRQ_POL_MASK 0x1000 /* IRQ_POL */ +#define WM8993_IRQ_POL_SHIFT 12 /* IRQ_POL */ +#define WM8993_IRQ_POL_WIDTH 1 /* IRQ_POL */ +#define WM8993_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8993_TEMPOK_POL_MASK 0x0800 /* TEMPOK_POL */ +#define WM8993_TEMPOK_POL_SHIFT 11 /* TEMPOK_POL */ +#define WM8993_TEMPOK_POL_WIDTH 1 /* TEMPOK_POL */ +#define WM8993_JD1_SC_POL 0x0400 /* JD1_SC_POL */ +#define WM8993_JD1_SC_POL_MASK 0x0400 /* JD1_SC_POL */ +#define WM8993_JD1_SC_POL_SHIFT 10 /* JD1_SC_POL */ +#define WM8993_JD1_SC_POL_WIDTH 1 /* JD1_SC_POL */ +#define WM8993_JD1_POL 0x0200 /* JD1_POL */ +#define WM8993_JD1_POL_MASK 0x0200 /* JD1_POL */ +#define WM8993_JD1_POL_SHIFT 9 /* JD1_POL */ +#define WM8993_JD1_POL_WIDTH 1 /* JD1_POL */ +#define WM8993_FLL_LOCK_POL 0x0100 /* FLL_LOCK_POL */ +#define WM8993_FLL_LOCK_POL_MASK 0x0100 /* FLL_LOCK_POL */ +#define WM8993_FLL_LOCK_POL_SHIFT 8 /* FLL_LOCK_POL */ +#define WM8993_FLL_LOCK_POL_WIDTH 1 /* FLL_LOCK_POL */ +#define WM8993_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8993_GPI8_POL_MASK 0x0080 /* GPI8_POL */ +#define WM8993_GPI8_POL_SHIFT 7 /* GPI8_POL */ +#define WM8993_GPI8_POL_WIDTH 1 /* GPI8_POL */ +#define WM8993_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8993_GPI7_POL_MASK 0x0040 /* GPI7_POL */ +#define WM8993_GPI7_POL_SHIFT 6 /* GPI7_POL */ +#define WM8993_GPI7_POL_WIDTH 1 /* GPI7_POL */ +#define WM8993_GPIO1_POL 0x0001 /* GPIO1_POL */ +#define WM8993_GPIO1_POL_MASK 0x0001 /* GPIO1_POL */ +#define WM8993_GPIO1_POL_SHIFT 0 /* GPIO1_POL */ +#define WM8993_GPIO1_POL_WIDTH 1 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8993_IN1_VU 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM8993_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8993_IN1L_MUTE 0x0080 /* IN1L_MUTE */ +#define WM8993_IN1L_MUTE_MASK 0x0080 /* IN1L_MUTE */ +#define WM8993_IN1L_MUTE_SHIFT 7 /* IN1L_MUTE */ +#define WM8993_IN1L_MUTE_WIDTH 1 /* IN1L_MUTE */ +#define WM8993_IN1L_ZC 0x0040 /* IN1L_ZC */ +#define WM8993_IN1L_ZC_MASK 0x0040 /* IN1L_ZC */ +#define WM8993_IN1L_ZC_SHIFT 6 /* IN1L_ZC */ +#define WM8993_IN1L_ZC_WIDTH 1 /* IN1L_ZC */ +#define WM8993_IN1L_VOL_MASK 0x001F /* IN1L_VOL - [4:0] */ +#define WM8993_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [4:0] */ +#define WM8993_IN1L_VOL_WIDTH 5 /* IN1L_VOL - [4:0] */ + +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8993_IN2_VU 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM8993_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM8993_IN2L_MUTE 0x0080 /* IN2L_MUTE */ +#define WM8993_IN2L_MUTE_MASK 0x0080 /* IN2L_MUTE */ +#define WM8993_IN2L_MUTE_SHIFT 7 /* IN2L_MUTE */ +#define WM8993_IN2L_MUTE_WIDTH 1 /* IN2L_MUTE */ +#define WM8993_IN2L_ZC 0x0040 /* IN2L_ZC */ +#define WM8993_IN2L_ZC_MASK 0x0040 /* IN2L_ZC */ +#define WM8993_IN2L_ZC_SHIFT 6 /* IN2L_ZC */ +#define WM8993_IN2L_ZC_WIDTH 1 /* IN2L_ZC */ +#define WM8993_IN2L_VOL_MASK 0x001F /* IN2L_VOL - [4:0] */ +#define WM8993_IN2L_VOL_SHIFT 0 /* IN2L_VOL - [4:0] */ +#define WM8993_IN2L_VOL_WIDTH 5 /* IN2L_VOL - [4:0] */ + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8993_IN1_VU 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM8993_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8993_IN1R_MUTE 0x0080 /* IN1R_MUTE */ +#define WM8993_IN1R_MUTE_MASK 0x0080 /* IN1R_MUTE */ +#define WM8993_IN1R_MUTE_SHIFT 7 /* IN1R_MUTE */ +#define WM8993_IN1R_MUTE_WIDTH 1 /* IN1R_MUTE */ +#define WM8993_IN1R_ZC 0x0040 /* IN1R_ZC */ +#define WM8993_IN1R_ZC_MASK 0x0040 /* IN1R_ZC */ +#define WM8993_IN1R_ZC_SHIFT 6 /* IN1R_ZC */ +#define WM8993_IN1R_ZC_WIDTH 1 /* IN1R_ZC */ +#define WM8993_IN1R_VOL_MASK 0x001F /* IN1R_VOL - [4:0] */ +#define WM8993_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [4:0] */ +#define WM8993_IN1R_VOL_WIDTH 5 /* IN1R_VOL - [4:0] */ + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8993_IN2_VU 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM8993_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM8993_IN2R_MUTE 0x0080 /* IN2R_MUTE */ +#define WM8993_IN2R_MUTE_MASK 0x0080 /* IN2R_MUTE */ +#define WM8993_IN2R_MUTE_SHIFT 7 /* IN2R_MUTE */ +#define WM8993_IN2R_MUTE_WIDTH 1 /* IN2R_MUTE */ +#define WM8993_IN2R_ZC 0x0040 /* IN2R_ZC */ +#define WM8993_IN2R_ZC_MASK 0x0040 /* IN2R_ZC */ +#define WM8993_IN2R_ZC_SHIFT 6 /* IN2R_ZC */ +#define WM8993_IN2R_ZC_WIDTH 1 /* IN2R_ZC */ +#define WM8993_IN2R_VOL_MASK 0x001F /* IN2R_VOL - [4:0] */ +#define WM8993_IN2R_VOL_SHIFT 0 /* IN2R_VOL - [4:0] */ +#define WM8993_IN2R_VOL_WIDTH 5 /* IN2R_VOL - [4:0] */ + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8993_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM8993_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_MUTE_N 0x0040 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_MUTE_N_MASK 0x0040 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_MUTE_N_SHIFT 6 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_MUTE_N_WIDTH 1 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_VOL_MASK 0x003F /* HPOUT1L_VOL - [5:0] */ +#define WM8993_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [5:0] */ +#define WM8993_HPOUT1L_VOL_WIDTH 6 /* HPOUT1L_VOL - [5:0] */ + +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8993_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM8993_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_MUTE_N 0x0040 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_MUTE_N_MASK 0x0040 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_MUTE_N_SHIFT 6 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_MUTE_N_WIDTH 1 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_VOL_MASK 0x003F /* HPOUT1R_VOL - [5:0] */ +#define WM8993_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [5:0] */ +#define WM8993_HPOUT1R_VOL_WIDTH 6 /* HPOUT1R_VOL - [5:0] */ + +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8993_LINEOUT1N_MUTE 0x0040 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1N_MUTE_MASK 0x0040 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1N_MUTE_SHIFT 6 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1N_MUTE_WIDTH 1 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1P_MUTE 0x0020 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1P_MUTE_MASK 0x0020 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1P_MUTE_SHIFT 5 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1P_MUTE_WIDTH 1 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1_VOL 0x0010 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT1_VOL_MASK 0x0010 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT1_VOL_SHIFT 4 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT1_VOL_WIDTH 1 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT2N_MUTE 0x0004 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2N_MUTE_MASK 0x0004 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2N_MUTE_SHIFT 2 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2N_MUTE_WIDTH 1 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2P_MUTE 0x0002 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2P_MUTE_MASK 0x0002 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2P_MUTE_SHIFT 1 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2P_MUTE_WIDTH 1 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2_VOL 0x0001 /* LINEOUT2_VOL */ +#define WM8993_LINEOUT2_VOL_MASK 0x0001 /* LINEOUT2_VOL */ +#define WM8993_LINEOUT2_VOL_SHIFT 0 /* LINEOUT2_VOL */ +#define WM8993_LINEOUT2_VOL_WIDTH 1 /* LINEOUT2_VOL */ + +/* + * R31 (0x1F) - HPOUT2 Volume + */ +#define WM8993_HPOUT2_MUTE 0x0020 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_MUTE_MASK 0x0020 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_MUTE_SHIFT 5 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_MUTE_WIDTH 1 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_VOL 0x0010 /* HPOUT2_VOL */ +#define WM8993_HPOUT2_VOL_MASK 0x0010 /* HPOUT2_VOL */ +#define WM8993_HPOUT2_VOL_SHIFT 4 /* HPOUT2_VOL */ +#define WM8993_HPOUT2_VOL_WIDTH 1 /* HPOUT2_VOL */ + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8993_MIXOUT_VU 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_MASK 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_SHIFT 8 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_WIDTH 1 /* MIXOUT_VU */ +#define WM8993_MIXOUTL_ZC 0x0080 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_ZC_MASK 0x0080 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_ZC_SHIFT 7 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_ZC_WIDTH 1 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_MUTE_N 0x0040 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_MUTE_N_MASK 0x0040 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_MUTE_N_SHIFT 6 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_MUTE_N_WIDTH 1 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_VOL_MASK 0x003F /* MIXOUTL_VOL - [5:0] */ +#define WM8993_MIXOUTL_VOL_SHIFT 0 /* MIXOUTL_VOL - [5:0] */ +#define WM8993_MIXOUTL_VOL_WIDTH 6 /* MIXOUTL_VOL - [5:0] */ + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8993_MIXOUT_VU 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_MASK 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_SHIFT 8 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_WIDTH 1 /* MIXOUT_VU */ +#define WM8993_MIXOUTR_ZC 0x0080 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_ZC_MASK 0x0080 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_ZC_SHIFT 7 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_ZC_WIDTH 1 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_MUTE_N 0x0040 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_MUTE_N_MASK 0x0040 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_MUTE_N_SHIFT 6 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_MUTE_N_WIDTH 1 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_VOL_MASK 0x003F /* MIXOUTR_VOL - [5:0] */ +#define WM8993_MIXOUTR_VOL_SHIFT 0 /* MIXOUTR_VOL - [5:0] */ +#define WM8993_MIXOUTR_VOL_WIDTH 6 /* MIXOUTR_VOL - [5:0] */ + +/* + * R34 (0x22) - SPKMIXL Attenuation + */ +#define WM8993_MIXINL_SPKMIXL_VOL 0x0020 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_MIXINL_SPKMIXL_VOL_MASK 0x0020 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_MIXINL_SPKMIXL_VOL_SHIFT 5 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_MIXINL_SPKMIXL_VOL_WIDTH 1 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL 0x0010 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL_MASK 0x0010 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL_SHIFT 4 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL_WIDTH 1 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL 0x0008 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL_MASK 0x0008 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL_SHIFT 3 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL_WIDTH 1 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL 0x0004 /* DACL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL_MASK 0x0004 /* DACL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL_SHIFT 2 /* DACL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL_WIDTH 1 /* DACL_SPKMIXL_VOL */ +#define WM8993_SPKMIXL_VOL_MASK 0x0003 /* SPKMIXL_VOL - [1:0] */ +#define WM8993_SPKMIXL_VOL_SHIFT 0 /* SPKMIXL_VOL - [1:0] */ +#define WM8993_SPKMIXL_VOL_WIDTH 2 /* SPKMIXL_VOL - [1:0] */ + +/* + * R35 (0x23) - SPKMIXR Attenuation + */ +#define WM8993_SPKOUT_CLASSAB_MODE 0x0100 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_SPKOUT_CLASSAB_MODE_MASK 0x0100 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_SPKOUT_CLASSAB_MODE_SHIFT 8 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_SPKOUT_CLASSAB_MODE_WIDTH 1 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_MIXINR_SPKMIXR_VOL 0x0020 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_MIXINR_SPKMIXR_VOL_MASK 0x0020 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_MIXINR_SPKMIXR_VOL_SHIFT 5 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_MIXINR_SPKMIXR_VOL_WIDTH 1 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL 0x0010 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL_MASK 0x0010 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL_SHIFT 4 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL_WIDTH 1 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL 0x0008 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL_MASK 0x0008 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL_SHIFT 3 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL_WIDTH 1 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL 0x0004 /* DACR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL_MASK 0x0004 /* DACR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL_SHIFT 2 /* DACR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL_WIDTH 1 /* DACR_SPKMIXR_VOL */ +#define WM8993_SPKMIXR_VOL_MASK 0x0003 /* SPKMIXR_VOL - [1:0] */ +#define WM8993_SPKMIXR_VOL_SHIFT 0 /* SPKMIXR_VOL - [1:0] */ +#define WM8993_SPKMIXR_VOL_WIDTH 2 /* SPKMIXR_VOL - [1:0] */ + +/* + * R36 (0x24) - SPKOUT Mixers + */ +#define WM8993_VRX_TO_SPKOUTL 0x0020 /* VRX_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTL_MASK 0x0020 /* VRX_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTL_SHIFT 5 /* VRX_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTL_WIDTH 1 /* VRX_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL_MASK 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL_SHIFT 4 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL_WIDTH 1 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL 0x0008 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL_MASK 0x0008 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL_SHIFT 3 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL_WIDTH 1 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTR 0x0004 /* VRX_TO_SPKOUTR */ +#define WM8993_VRX_TO_SPKOUTR_MASK 0x0004 /* VRX_TO_SPKOUTR */ +#define WM8993_VRX_TO_SPKOUTR_SHIFT 2 /* VRX_TO_SPKOUTR */ +#define WM8993_VRX_TO_SPKOUTR_WIDTH 1 /* VRX_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR 0x0002 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR_MASK 0x0002 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR_SHIFT 1 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR_WIDTH 1 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR 0x0001 /* SPKMIXR_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR_MASK 0x0001 /* SPKMIXR_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR_SHIFT 0 /* SPKMIXR_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR_WIDTH 1 /* SPKMIXR_TO_SPKOUTR */ + +/* + * R37 (0x25) - SPKOUT Boost + */ +#define WM8993_SPKOUTL_BOOST_MASK 0x0038 /* SPKOUTL_BOOST - [5:3] */ +#define WM8993_SPKOUTL_BOOST_SHIFT 3 /* SPKOUTL_BOOST - [5:3] */ +#define WM8993_SPKOUTL_BOOST_WIDTH 3 /* SPKOUTL_BOOST - [5:3] */ +#define WM8993_SPKOUTR_BOOST_MASK 0x0007 /* SPKOUTR_BOOST - [2:0] */ +#define WM8993_SPKOUTR_BOOST_SHIFT 0 /* SPKOUTR_BOOST - [2:0] */ +#define WM8993_SPKOUTR_BOOST_WIDTH 3 /* SPKOUTR_BOOST - [2:0] */ + +/* + * R38 (0x26) - Speaker Volume Left + */ +#define WM8993_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM8993_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_MUTE_N 0x0040 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_MUTE_N_MASK 0x0040 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_MUTE_N_SHIFT 6 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_MUTE_N_WIDTH 1 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */ +#define WM8993_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */ +#define WM8993_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */ + +/* + * R39 (0x27) - Speaker Volume Right + */ +#define WM8993_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM8993_SPKOUTR_ZC 0x0080 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_ZC_MASK 0x0080 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_ZC_SHIFT 7 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_ZC_WIDTH 1 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_MUTE_N 0x0040 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_MUTE_N_MASK 0x0040 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_MUTE_N_SHIFT 6 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_MUTE_N_WIDTH 1 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_VOL_MASK 0x003F /* SPKOUTR_VOL - [5:0] */ +#define WM8993_SPKOUTR_VOL_SHIFT 0 /* SPKOUTR_VOL - [5:0] */ +#define WM8993_SPKOUTR_VOL_WIDTH 6 /* SPKOUTR_VOL - [5:0] */ + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8993_IN2LP_TO_IN2L 0x0080 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LP_TO_IN2L_MASK 0x0080 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LP_TO_IN2L_SHIFT 7 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LP_TO_IN2L_WIDTH 1 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L 0x0040 /* IN2LN_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L_MASK 0x0040 /* IN2LN_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L_SHIFT 6 /* IN2LN_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L_WIDTH 1 /* IN2LN_TO_IN2L */ +#define WM8993_IN1LP_TO_IN1L 0x0020 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LP_TO_IN1L_MASK 0x0020 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LP_TO_IN1L_SHIFT 5 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LP_TO_IN1L_WIDTH 1 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L 0x0010 /* IN1LN_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L_MASK 0x0010 /* IN1LN_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L_SHIFT 4 /* IN1LN_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L_WIDTH 1 /* IN1LN_TO_IN1L */ +#define WM8993_IN2RP_TO_IN2R 0x0008 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RP_TO_IN2R_MASK 0x0008 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RP_TO_IN2R_SHIFT 3 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RP_TO_IN2R_WIDTH 1 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R 0x0004 /* IN2RN_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R_MASK 0x0004 /* IN2RN_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R_SHIFT 2 /* IN2RN_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R_WIDTH 1 /* IN2RN_TO_IN2R */ +#define WM8993_IN1RP_TO_IN1R 0x0002 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RP_TO_IN1R_MASK 0x0002 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RP_TO_IN1R_SHIFT 1 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RP_TO_IN1R_WIDTH 1 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R 0x0001 /* IN1RN_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R_MASK 0x0001 /* IN1RN_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R_SHIFT 0 /* IN1RN_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R_WIDTH 1 /* IN1RN_TO_IN1R */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8993_IN2L_TO_MIXINL 0x0100 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_TO_MIXINL_MASK 0x0100 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_TO_MIXINL_SHIFT 8 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_TO_MIXINL_WIDTH 1 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_MIXINL_VOL 0x0080 /* IN2L_MIXINL_VOL */ +#define WM8993_IN2L_MIXINL_VOL_MASK 0x0080 /* IN2L_MIXINL_VOL */ +#define WM8993_IN2L_MIXINL_VOL_SHIFT 7 /* IN2L_MIXINL_VOL */ +#define WM8993_IN2L_MIXINL_VOL_WIDTH 1 /* IN2L_MIXINL_VOL */ +#define WM8993_IN1L_TO_MIXINL 0x0020 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_TO_MIXINL_MASK 0x0020 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_TO_MIXINL_SHIFT 5 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_TO_MIXINL_WIDTH 1 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_MIXINL_VOL 0x0010 /* IN1L_MIXINL_VOL */ +#define WM8993_IN1L_MIXINL_VOL_MASK 0x0010 /* IN1L_MIXINL_VOL */ +#define WM8993_IN1L_MIXINL_VOL_SHIFT 4 /* IN1L_MIXINL_VOL */ +#define WM8993_IN1L_MIXINL_VOL_WIDTH 1 /* IN1L_MIXINL_VOL */ +#define WM8993_MIXOUTL_MIXINL_VOL_MASK 0x0007 /* MIXOUTL_MIXINL_VOL - [2:0] */ +#define WM8993_MIXOUTL_MIXINL_VOL_SHIFT 0 /* MIXOUTL_MIXINL_VOL - [2:0] */ +#define WM8993_MIXOUTL_MIXINL_VOL_WIDTH 3 /* MIXOUTL_MIXINL_VOL - [2:0] */ + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8993_IN2R_TO_MIXINR 0x0100 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_TO_MIXINR_MASK 0x0100 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_TO_MIXINR_SHIFT 8 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_TO_MIXINR_WIDTH 1 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_MIXINR_VOL 0x0080 /* IN2R_MIXINR_VOL */ +#define WM8993_IN2R_MIXINR_VOL_MASK 0x0080 /* IN2R_MIXINR_VOL */ +#define WM8993_IN2R_MIXINR_VOL_SHIFT 7 /* IN2R_MIXINR_VOL */ +#define WM8993_IN2R_MIXINR_VOL_WIDTH 1 /* IN2R_MIXINR_VOL */ +#define WM8993_IN1R_TO_MIXINR 0x0020 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_TO_MIXINR_MASK 0x0020 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_TO_MIXINR_SHIFT 5 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_TO_MIXINR_WIDTH 1 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_MIXINR_VOL 0x0010 /* IN1R_MIXINR_VOL */ +#define WM8993_IN1R_MIXINR_VOL_MASK 0x0010 /* IN1R_MIXINR_VOL */ +#define WM8993_IN1R_MIXINR_VOL_SHIFT 4 /* IN1R_MIXINR_VOL */ +#define WM8993_IN1R_MIXINR_VOL_WIDTH 1 /* IN1R_MIXINR_VOL */ +#define WM8993_MIXOUTR_MIXINR_VOL_MASK 0x0007 /* MIXOUTR_MIXINR_VOL - [2:0] */ +#define WM8993_MIXOUTR_MIXINR_VOL_SHIFT 0 /* MIXOUTR_MIXINR_VOL - [2:0] */ +#define WM8993_MIXOUTR_MIXINR_VOL_WIDTH 3 /* MIXOUTR_MIXINR_VOL - [2:0] */ + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8993_IN1LP_MIXINL_VOL_MASK 0x01C0 /* IN1LP_MIXINL_VOL - [8:6] */ +#define WM8993_IN1LP_MIXINL_VOL_SHIFT 6 /* IN1LP_MIXINL_VOL - [8:6] */ +#define WM8993_IN1LP_MIXINL_VOL_WIDTH 3 /* IN1LP_MIXINL_VOL - [8:6] */ +#define WM8993_VRX_MIXINL_VOL_MASK 0x0007 /* VRX_MIXINL_VOL - [2:0] */ +#define WM8993_VRX_MIXINL_VOL_SHIFT 0 /* VRX_MIXINL_VOL - [2:0] */ +#define WM8993_VRX_MIXINL_VOL_WIDTH 3 /* VRX_MIXINL_VOL - [2:0] */ + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8993_IN1RP_MIXINR_VOL_MASK 0x01C0 /* IN1RP_MIXINR_VOL - [8:6] */ +#define WM8993_IN1RP_MIXINR_VOL_SHIFT 6 /* IN1RP_MIXINR_VOL - [8:6] */ +#define WM8993_IN1RP_MIXINR_VOL_WIDTH 3 /* IN1RP_MIXINR_VOL - [8:6] */ +#define WM8993_VRX_MIXINR_VOL_MASK 0x0007 /* VRX_MIXINR_VOL - [2:0] */ +#define WM8993_VRX_MIXINR_VOL_SHIFT 0 /* VRX_MIXINR_VOL - [2:0] */ +#define WM8993_VRX_MIXINR_VOL_WIDTH 3 /* VRX_MIXINR_VOL - [2:0] */ + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8993_DACL_TO_HPOUT1L 0x0100 /* DACL_TO_HPOUT1L */ +#define WM8993_DACL_TO_HPOUT1L_MASK 0x0100 /* DACL_TO_HPOUT1L */ +#define WM8993_DACL_TO_HPOUT1L_SHIFT 8 /* DACL_TO_HPOUT1L */ +#define WM8993_DACL_TO_HPOUT1L_WIDTH 1 /* DACL_TO_HPOUT1L */ +#define WM8993_MIXINR_TO_MIXOUTL 0x0080 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINR_TO_MIXOUTL_MASK 0x0080 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINR_TO_MIXOUTL_SHIFT 7 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINR_TO_MIXOUTL_WIDTH 1 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL 0x0040 /* MIXINL_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL_MASK 0x0040 /* MIXINL_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL_SHIFT 6 /* MIXINL_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL_WIDTH 1 /* MIXINL_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL 0x0020 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL_MASK 0x0020 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL_SHIFT 5 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL_WIDTH 1 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL 0x0010 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL_MASK 0x0010 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL_SHIFT 4 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL_WIDTH 1 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL 0x0008 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL_MASK 0x0008 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL_SHIFT 3 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL_WIDTH 1 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL 0x0004 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL_MASK 0x0004 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL_SHIFT 2 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL_WIDTH 1 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL 0x0002 /* IN2LP_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL_MASK 0x0002 /* IN2LP_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL_SHIFT 1 /* IN2LP_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL_WIDTH 1 /* IN2LP_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL 0x0001 /* DACL_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL_MASK 0x0001 /* DACL_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL_SHIFT 0 /* DACL_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */ + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8993_DACR_TO_HPOUT1R 0x0100 /* DACR_TO_HPOUT1R */ +#define WM8993_DACR_TO_HPOUT1R_MASK 0x0100 /* DACR_TO_HPOUT1R */ +#define WM8993_DACR_TO_HPOUT1R_SHIFT 8 /* DACR_TO_HPOUT1R */ +#define WM8993_DACR_TO_HPOUT1R_WIDTH 1 /* DACR_TO_HPOUT1R */ +#define WM8993_MIXINL_TO_MIXOUTR 0x0080 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINL_TO_MIXOUTR_MASK 0x0080 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINL_TO_MIXOUTR_SHIFT 7 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINL_TO_MIXOUTR_WIDTH 1 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR 0x0040 /* MIXINR_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR_MASK 0x0040 /* MIXINR_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR_SHIFT 6 /* MIXINR_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR_WIDTH 1 /* MIXINR_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR 0x0020 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR_MASK 0x0020 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR_SHIFT 5 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR_WIDTH 1 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR 0x0010 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR_MASK 0x0010 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR_SHIFT 4 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR_WIDTH 1 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR 0x0008 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR_MASK 0x0008 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR_SHIFT 3 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR_WIDTH 1 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR 0x0004 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR_MASK 0x0004 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR_SHIFT 2 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR_WIDTH 1 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR 0x0002 /* IN2RP_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR_MASK 0x0002 /* IN2RP_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR_SHIFT 1 /* IN2RP_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR_WIDTH 1 /* IN2RP_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR 0x0001 /* DACR_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR_MASK 0x0001 /* DACR_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR_SHIFT 0 /* DACR_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */ + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8993_IN2LP_MIXOUTL_VOL_MASK 0x0E00 /* IN2LP_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2LP_MIXOUTL_VOL_SHIFT 9 /* IN2LP_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2LP_MIXOUTL_VOL_WIDTH 3 /* IN2LP_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2LN_MIXOUTL_VOL_MASK 0x01C0 /* IN2LN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTL_VOL_SHIFT 6 /* IN2LN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTL_VOL_WIDTH 3 /* IN2LN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN1R_MIXOUTL_VOL_MASK 0x0038 /* IN1R_MIXOUTL_VOL - [5:3] */ +#define WM8993_IN1R_MIXOUTL_VOL_SHIFT 3 /* IN1R_MIXOUTL_VOL - [5:3] */ +#define WM8993_IN1R_MIXOUTL_VOL_WIDTH 3 /* IN1R_MIXOUTL_VOL - [5:3] */ +#define WM8993_IN1L_MIXOUTL_VOL_MASK 0x0007 /* IN1L_MIXOUTL_VOL - [2:0] */ +#define WM8993_IN1L_MIXOUTL_VOL_SHIFT 0 /* IN1L_MIXOUTL_VOL - [2:0] */ +#define WM8993_IN1L_MIXOUTL_VOL_WIDTH 3 /* IN1L_MIXOUTL_VOL - [2:0] */ + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8993_IN2RP_MIXOUTR_VOL_MASK 0x0E00 /* IN2RP_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2RP_MIXOUTR_VOL_SHIFT 9 /* IN2RP_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2RP_MIXOUTR_VOL_WIDTH 3 /* IN2RP_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2RN_MIXOUTR_VOL_MASK 0x01C0 /* IN2RN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTR_VOL_SHIFT 6 /* IN2RN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTR_VOL_WIDTH 3 /* IN2RN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN1L_MIXOUTR_VOL_MASK 0x0038 /* IN1L_MIXOUTR_VOL - [5:3] */ +#define WM8993_IN1L_MIXOUTR_VOL_SHIFT 3 /* IN1L_MIXOUTR_VOL - [5:3] */ +#define WM8993_IN1L_MIXOUTR_VOL_WIDTH 3 /* IN1L_MIXOUTR_VOL - [5:3] */ +#define WM8993_IN1R_MIXOUTR_VOL_MASK 0x0007 /* IN1R_MIXOUTR_VOL - [2:0] */ +#define WM8993_IN1R_MIXOUTR_VOL_SHIFT 0 /* IN1R_MIXOUTR_VOL - [2:0] */ +#define WM8993_IN1R_MIXOUTR_VOL_WIDTH 3 /* IN1R_MIXOUTR_VOL - [2:0] */ + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8993_DACL_MIXOUTL_VOL_MASK 0x0E00 /* DACL_MIXOUTL_VOL - [11:9] */ +#define WM8993_DACL_MIXOUTL_VOL_SHIFT 9 /* DACL_MIXOUTL_VOL - [11:9] */ +#define WM8993_DACL_MIXOUTL_VOL_WIDTH 3 /* DACL_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2RN_MIXOUTL_VOL_MASK 0x01C0 /* IN2RN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTL_VOL_SHIFT 6 /* IN2RN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTL_VOL_WIDTH 3 /* IN2RN_MIXOUTL_VOL - [8:6] */ +#define WM8993_MIXINR_MIXOUTL_VOL_MASK 0x0038 /* MIXINR_MIXOUTL_VOL - [5:3] */ +#define WM8993_MIXINR_MIXOUTL_VOL_SHIFT 3 /* MIXINR_MIXOUTL_VOL - [5:3] */ +#define WM8993_MIXINR_MIXOUTL_VOL_WIDTH 3 /* MIXINR_MIXOUTL_VOL - [5:3] */ +#define WM8993_MIXINL_MIXOUTL_VOL_MASK 0x0007 /* MIXINL_MIXOUTL_VOL - [2:0] */ +#define WM8993_MIXINL_MIXOUTL_VOL_SHIFT 0 /* MIXINL_MIXOUTL_VOL - [2:0] */ +#define WM8993_MIXINL_MIXOUTL_VOL_WIDTH 3 /* MIXINL_MIXOUTL_VOL - [2:0] */ + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8993_DACR_MIXOUTR_VOL_MASK 0x0E00 /* DACR_MIXOUTR_VOL - [11:9] */ +#define WM8993_DACR_MIXOUTR_VOL_SHIFT 9 /* DACR_MIXOUTR_VOL - [11:9] */ +#define WM8993_DACR_MIXOUTR_VOL_WIDTH 3 /* DACR_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2LN_MIXOUTR_VOL_MASK 0x01C0 /* IN2LN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTR_VOL_SHIFT 6 /* IN2LN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTR_VOL_WIDTH 3 /* IN2LN_MIXOUTR_VOL - [8:6] */ +#define WM8993_MIXINL_MIXOUTR_VOL_MASK 0x0038 /* MIXINL_MIXOUTR_VOL - [5:3] */ +#define WM8993_MIXINL_MIXOUTR_VOL_SHIFT 3 /* MIXINL_MIXOUTR_VOL - [5:3] */ +#define WM8993_MIXINL_MIXOUTR_VOL_WIDTH 3 /* MIXINL_MIXOUTR_VOL - [5:3] */ +#define WM8993_MIXINR_MIXOUTR_VOL_MASK 0x0007 /* MIXINR_MIXOUTR_VOL - [2:0] */ +#define WM8993_MIXINR_MIXOUTR_VOL_SHIFT 0 /* MIXINR_MIXOUTR_VOL - [2:0] */ +#define WM8993_MIXINR_MIXOUTR_VOL_WIDTH 3 /* MIXINR_MIXOUTR_VOL - [2:0] */ + +/* + * R51 (0x33) - HPOUT2 Mixer + */ +#define WM8993_VRX_TO_HPOUT2 0x0020 /* VRX_TO_HPOUT2 */ +#define WM8993_VRX_TO_HPOUT2_MASK 0x0020 /* VRX_TO_HPOUT2 */ +#define WM8993_VRX_TO_HPOUT2_SHIFT 5 /* VRX_TO_HPOUT2 */ +#define WM8993_VRX_TO_HPOUT2_WIDTH 1 /* VRX_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2 0x0010 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2_MASK 0x0010 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2_SHIFT 4 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2_WIDTH 1 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2 0x0008 /* MIXOUTRVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2_MASK 0x0008 /* MIXOUTRVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2_SHIFT 3 /* MIXOUTRVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2_WIDTH 1 /* MIXOUTRVOL_TO_HPOUT2 */ + +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8993_MIXOUTL_TO_LINEOUT1N 0x0040 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTL_TO_LINEOUT1N_MASK 0x0040 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTL_TO_LINEOUT1N_SHIFT 6 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTL_TO_LINEOUT1N_WIDTH 1 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N 0x0020 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N_MASK 0x0020 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N_SHIFT 5 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N_WIDTH 1 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_LINEOUT1_MODE 0x0010 /* LINEOUT1_MODE */ +#define WM8993_LINEOUT1_MODE_MASK 0x0010 /* LINEOUT1_MODE */ +#define WM8993_LINEOUT1_MODE_SHIFT 4 /* LINEOUT1_MODE */ +#define WM8993_LINEOUT1_MODE_WIDTH 1 /* LINEOUT1_MODE */ +#define WM8993_IN1R_TO_LINEOUT1P 0x0004 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1R_TO_LINEOUT1P_MASK 0x0004 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1R_TO_LINEOUT1P_SHIFT 2 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1R_TO_LINEOUT1P_WIDTH 1 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P 0x0002 /* IN1L_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P_MASK 0x0002 /* IN1L_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P_SHIFT 1 /* IN1L_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P_WIDTH 1 /* IN1L_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P 0x0001 /* MIXOUTL_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P_MASK 0x0001 /* MIXOUTL_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P_SHIFT 0 /* MIXOUTL_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P_WIDTH 1 /* MIXOUTL_TO_LINEOUT1P */ + +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8993_MIXOUTR_TO_LINEOUT2N 0x0040 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTR_TO_LINEOUT2N_MASK 0x0040 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTR_TO_LINEOUT2N_SHIFT 6 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTR_TO_LINEOUT2N_WIDTH 1 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N 0x0020 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N_MASK 0x0020 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N_SHIFT 5 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N_WIDTH 1 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_LINEOUT2_MODE 0x0010 /* LINEOUT2_MODE */ +#define WM8993_LINEOUT2_MODE_MASK 0x0010 /* LINEOUT2_MODE */ +#define WM8993_LINEOUT2_MODE_SHIFT 4 /* LINEOUT2_MODE */ +#define WM8993_LINEOUT2_MODE_WIDTH 1 /* LINEOUT2_MODE */ +#define WM8993_IN1L_TO_LINEOUT2P 0x0004 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1L_TO_LINEOUT2P_MASK 0x0004 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1L_TO_LINEOUT2P_SHIFT 2 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1L_TO_LINEOUT2P_WIDTH 1 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P 0x0002 /* IN1R_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P_MASK 0x0002 /* IN1R_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P_SHIFT 1 /* IN1R_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P_WIDTH 1 /* IN1R_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P 0x0001 /* MIXOUTR_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P_MASK 0x0001 /* MIXOUTR_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P_SHIFT 0 /* MIXOUTR_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P_WIDTH 1 /* MIXOUTR_TO_LINEOUT2P */ + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8993_SPKAB_REF_SEL 0x0100 /* SPKAB_REF_SEL */ +#define WM8993_SPKAB_REF_SEL_MASK 0x0100 /* SPKAB_REF_SEL */ +#define WM8993_SPKAB_REF_SEL_SHIFT 8 /* SPKAB_REF_SEL */ +#define WM8993_SPKAB_REF_SEL_WIDTH 1 /* SPKAB_REF_SEL */ +#define WM8993_MIXINL_TO_SPKMIXL 0x0080 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINL_TO_SPKMIXL_MASK 0x0080 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINL_TO_SPKMIXL_SHIFT 7 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINL_TO_SPKMIXL_WIDTH 1 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINR_TO_SPKMIXR 0x0040 /* MIXINR_TO_SPKMIXR */ +#define WM8993_MIXINR_TO_SPKMIXR_MASK 0x0040 /* MIXINR_TO_SPKMIXR */ +#define WM8993_MIXINR_TO_SPKMIXR_SHIFT 6 /* MIXINR_TO_SPKMIXR */ +#define WM8993_MIXINR_TO_SPKMIXR_WIDTH 1 /* MIXINR_TO_SPKMIXR */ +#define WM8993_IN1LP_TO_SPKMIXL 0x0020 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1LP_TO_SPKMIXL_MASK 0x0020 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1LP_TO_SPKMIXL_SHIFT 5 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1LP_TO_SPKMIXL_WIDTH 1 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1RP_TO_SPKMIXR 0x0010 /* IN1RP_TO_SPKMIXR */ +#define WM8993_IN1RP_TO_SPKMIXR_MASK 0x0010 /* IN1RP_TO_SPKMIXR */ +#define WM8993_IN1RP_TO_SPKMIXR_SHIFT 4 /* IN1RP_TO_SPKMIXR */ +#define WM8993_IN1RP_TO_SPKMIXR_WIDTH 1 /* IN1RP_TO_SPKMIXR */ +#define WM8993_MIXOUTL_TO_SPKMIXL 0x0008 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTL_TO_SPKMIXL_MASK 0x0008 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTL_TO_SPKMIXL_SHIFT 3 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTL_TO_SPKMIXL_WIDTH 1 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTR_TO_SPKMIXR 0x0004 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_MIXOUTR_TO_SPKMIXR_MASK 0x0004 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_MIXOUTR_TO_SPKMIXR_SHIFT 2 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_MIXOUTR_TO_SPKMIXR_WIDTH 1 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_DACL_TO_SPKMIXL 0x0002 /* DACL_TO_SPKMIXL */ +#define WM8993_DACL_TO_SPKMIXL_MASK 0x0002 /* DACL_TO_SPKMIXL */ +#define WM8993_DACL_TO_SPKMIXL_SHIFT 1 /* DACL_TO_SPKMIXL */ +#define WM8993_DACL_TO_SPKMIXL_WIDTH 1 /* DACL_TO_SPKMIXL */ +#define WM8993_DACR_TO_SPKMIXR 0x0001 /* DACR_TO_SPKMIXR */ +#define WM8993_DACR_TO_SPKMIXR_MASK 0x0001 /* DACR_TO_SPKMIXR */ +#define WM8993_DACR_TO_SPKMIXR_SHIFT 0 /* DACR_TO_SPKMIXR */ +#define WM8993_DACR_TO_SPKMIXR_WIDTH 1 /* DACR_TO_SPKMIXR */ + +/* + * R55 (0x37) - Additional Control + */ +#define WM8993_LINEOUT1_FB 0x0080 /* LINEOUT1_FB */ +#define WM8993_LINEOUT1_FB_MASK 0x0080 /* LINEOUT1_FB */ +#define WM8993_LINEOUT1_FB_SHIFT 7 /* LINEOUT1_FB */ +#define WM8993_LINEOUT1_FB_WIDTH 1 /* LINEOUT1_FB */ +#define WM8993_LINEOUT2_FB 0x0040 /* LINEOUT2_FB */ +#define WM8993_LINEOUT2_FB_MASK 0x0040 /* LINEOUT2_FB */ +#define WM8993_LINEOUT2_FB_SHIFT 6 /* LINEOUT2_FB */ +#define WM8993_LINEOUT2_FB_WIDTH 1 /* LINEOUT2_FB */ +#define WM8993_VROI 0x0001 /* VROI */ +#define WM8993_VROI_MASK 0x0001 /* VROI */ +#define WM8993_VROI_SHIFT 0 /* VROI */ +#define WM8993_VROI_WIDTH 1 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8993_LINEOUT_VMID_BUF_ENA 0x0080 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_LINEOUT_VMID_BUF_ENA_MASK 0x0080 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_LINEOUT_VMID_BUF_ENA_SHIFT 7 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_LINEOUT_VMID_BUF_ENA_WIDTH 1 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_HPOUT2_IN_ENA 0x0040 /* HPOUT2_IN_ENA */ +#define WM8993_HPOUT2_IN_ENA_MASK 0x0040 /* HPOUT2_IN_ENA */ +#define WM8993_HPOUT2_IN_ENA_SHIFT 6 /* HPOUT2_IN_ENA */ +#define WM8993_HPOUT2_IN_ENA_WIDTH 1 /* HPOUT2_IN_ENA */ +#define WM8993_LINEOUT1_DISCH 0x0020 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT1_DISCH_MASK 0x0020 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT1_DISCH_SHIFT 5 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT1_DISCH_WIDTH 1 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT2_DISCH 0x0010 /* LINEOUT2_DISCH */ +#define WM8993_LINEOUT2_DISCH_MASK 0x0010 /* LINEOUT2_DISCH */ +#define WM8993_LINEOUT2_DISCH_SHIFT 4 /* LINEOUT2_DISCH */ +#define WM8993_LINEOUT2_DISCH_WIDTH 1 /* LINEOUT2_DISCH */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8993_VMID_RAMP_MASK 0x0060 /* VMID_RAMP - [6:5] */ +#define WM8993_VMID_RAMP_SHIFT 5 /* VMID_RAMP - [6:5] */ +#define WM8993_VMID_RAMP_WIDTH 2 /* VMID_RAMP - [6:5] */ +#define WM8993_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */ +#define WM8993_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */ +#define WM8993_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */ +#define WM8993_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM8993_STARTUP_BIAS_ENA 0x0004 /* STARTUP_BIAS_ENA */ +#define WM8993_STARTUP_BIAS_ENA_MASK 0x0004 /* STARTUP_BIAS_ENA */ +#define WM8993_STARTUP_BIAS_ENA_SHIFT 2 /* STARTUP_BIAS_ENA */ +#define WM8993_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8993_BIAS_SRC 0x0002 /* BIAS_SRC */ +#define WM8993_BIAS_SRC_MASK 0x0002 /* BIAS_SRC */ +#define WM8993_BIAS_SRC_SHIFT 1 /* BIAS_SRC */ +#define WM8993_BIAS_SRC_WIDTH 1 /* BIAS_SRC */ +#define WM8993_VMID_DISCH 0x0001 /* VMID_DISCH */ +#define WM8993_VMID_DISCH_MASK 0x0001 /* VMID_DISCH */ +#define WM8993_VMID_DISCH_SHIFT 0 /* VMID_DISCH */ +#define WM8993_VMID_DISCH_WIDTH 1 /* VMID_DISCH */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8993_JD_SCTHR_MASK 0x00C0 /* JD_SCTHR - [7:6] */ +#define WM8993_JD_SCTHR_SHIFT 6 /* JD_SCTHR - [7:6] */ +#define WM8993_JD_SCTHR_WIDTH 2 /* JD_SCTHR - [7:6] */ +#define WM8993_JD_THR_MASK 0x0030 /* JD_THR - [5:4] */ +#define WM8993_JD_THR_SHIFT 4 /* JD_THR - [5:4] */ +#define WM8993_JD_THR_WIDTH 2 /* JD_THR - [5:4] */ +#define WM8993_JD_ENA 0x0004 /* JD_ENA */ +#define WM8993_JD_ENA_MASK 0x0004 /* JD_ENA */ +#define WM8993_JD_ENA_SHIFT 2 /* JD_ENA */ +#define WM8993_JD_ENA_WIDTH 1 /* JD_ENA */ +#define WM8993_MICB2_LVL 0x0002 /* MICB2_LVL */ +#define WM8993_MICB2_LVL_MASK 0x0002 /* MICB2_LVL */ +#define WM8993_MICB2_LVL_SHIFT 1 /* MICB2_LVL */ +#define WM8993_MICB2_LVL_WIDTH 1 /* MICB2_LVL */ +#define WM8993_MICB1_LVL 0x0001 /* MICB1_LVL */ +#define WM8993_MICB1_LVL_MASK 0x0001 /* MICB1_LVL */ +#define WM8993_MICB1_LVL_SHIFT 0 /* MICB1_LVL */ +#define WM8993_MICB1_LVL_WIDTH 1 /* MICB1_LVL */ + +/* + * R60 (0x3C) - FLL Control 1 + */ +#define WM8993_FLL_FRAC 0x0004 /* FLL_FRAC */ +#define WM8993_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */ +#define WM8993_FLL_FRAC_SHIFT 2 /* FLL_FRAC */ +#define WM8993_FLL_FRAC_WIDTH 1 /* FLL_FRAC */ +#define WM8993_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */ +#define WM8993_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */ +#define WM8993_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */ +#define WM8993_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */ +#define WM8993_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM8993_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM8993_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM8993_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R61 (0x3D) - FLL Control 2 + */ +#define WM8993_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */ +#define WM8993_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */ +#define WM8993_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */ +#define WM8993_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */ +#define WM8993_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */ +#define WM8993_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */ +#define WM8993_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM8993_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM8993_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R62 (0x3E) - FLL Control 3 + */ +#define WM8993_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */ +#define WM8993_FLL_K_SHIFT 0 /* FLL_K - [15:0] */ +#define WM8993_FLL_K_WIDTH 16 /* FLL_K - [15:0] */ + +/* + * R63 (0x3F) - FLL Control 4 + */ +#define WM8993_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */ +#define WM8993_FLL_N_SHIFT 5 /* FLL_N - [14:5] */ +#define WM8993_FLL_N_WIDTH 10 /* FLL_N - [14:5] */ +#define WM8993_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */ +#define WM8993_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */ +#define WM8993_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */ + +/* + * R64 (0x40) - FLL Control 5 + */ +#define WM8993_FLL_FRC_NCO_VAL_MASK 0x1F80 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8993_FLL_FRC_NCO_VAL_SHIFT 7 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8993_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8993_FLL_FRC_NCO 0x0040 /* FLL_FRC_NCO */ +#define WM8993_FLL_FRC_NCO_MASK 0x0040 /* FLL_FRC_NCO */ +#define WM8993_FLL_FRC_NCO_SHIFT 6 /* FLL_FRC_NCO */ +#define WM8993_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */ +#define WM8993_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8993_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8993_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8993_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */ +#define WM8993_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */ +#define WM8993_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */ + +/* + * R65 (0x41) - Clocking 3 + */ +#define WM8993_CLK_DCS_DIV_MASK 0x3C00 /* CLK_DCS_DIV - [13:10] */ +#define WM8993_CLK_DCS_DIV_SHIFT 10 /* CLK_DCS_DIV - [13:10] */ +#define WM8993_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [13:10] */ +#define WM8993_SAMPLE_RATE_MASK 0x0380 /* SAMPLE_RATE - [9:7] */ +#define WM8993_SAMPLE_RATE_SHIFT 7 /* SAMPLE_RATE - [9:7] */ +#define WM8993_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [9:7] */ +#define WM8993_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */ +#define WM8993_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */ +#define WM8993_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */ +#define WM8993_CLK_DSP_ENA 0x0001 /* CLK_DSP_ENA */ +#define WM8993_CLK_DSP_ENA_MASK 0x0001 /* CLK_DSP_ENA */ +#define WM8993_CLK_DSP_ENA_SHIFT 0 /* CLK_DSP_ENA */ +#define WM8993_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ + +/* + * R66 (0x42) - Clocking 4 + */ +#define WM8993_DAC_DIV4 0x0200 /* DAC_DIV4 */ +#define WM8993_DAC_DIV4_MASK 0x0200 /* DAC_DIV4 */ +#define WM8993_DAC_DIV4_SHIFT 9 /* DAC_DIV4 */ +#define WM8993_DAC_DIV4_WIDTH 1 /* DAC_DIV4 */ +#define WM8993_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */ +#define WM8993_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */ +#define WM8993_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */ +#define WM8993_SR_MODE 0x0001 /* SR_MODE */ +#define WM8993_SR_MODE_MASK 0x0001 /* SR_MODE */ +#define WM8993_SR_MODE_SHIFT 0 /* SR_MODE */ +#define WM8993_SR_MODE_WIDTH 1 /* SR_MODE */ + +/* + * R67 (0x43) - MW Slave Control + */ +#define WM8993_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */ +#define WM8993_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */ +#define WM8993_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */ +#define WM8993_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ + +/* + * R69 (0x45) - Bus Control 1 + */ +#define WM8993_CLK_SYS_ENA 0x0002 /* CLK_SYS_ENA */ +#define WM8993_CLK_SYS_ENA_MASK 0x0002 /* CLK_SYS_ENA */ +#define WM8993_CLK_SYS_ENA_SHIFT 1 /* CLK_SYS_ENA */ +#define WM8993_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ + +/* + * R70 (0x46) - Write Sequencer 0 + */ +#define WM8993_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM8993_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM8993_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM8993_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8993_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8993_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8993_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R71 (0x47) - Write Sequencer 1 + */ +#define WM8993_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8993_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8993_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8993_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM8993_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM8993_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM8993_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8993_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8993_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R72 (0x48) - Write Sequencer 2 + */ +#define WM8993_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM8993_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM8993_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM8993_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8993_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM8993_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM8993_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM8993_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8993_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8993_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R73 (0x49) - Write Sequencer 3 + */ +#define WM8993_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8993_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8993_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8993_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8993_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8993_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8993_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8993_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8993_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8993_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8993_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R74 (0x4A) - Write Sequencer 4 + */ +#define WM8993_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8993_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8993_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8993_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R75 (0x4B) - Write Sequencer 5 + */ +#define WM8993_WSEQ_CURRENT_INDEX_MASK 0x003F /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM8993_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM8993_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [5:0] */ + +/* + * R76 (0x4C) - Charge Pump 1 + */ +#define WM8993_CP_ENA 0x8000 /* CP_ENA */ +#define WM8993_CP_ENA_MASK 0x8000 /* CP_ENA */ +#define WM8993_CP_ENA_SHIFT 15 /* CP_ENA */ +#define WM8993_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R81 (0x51) - Class W 0 + */ +#define WM8993_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_V 0x0001 /* CP_DYN_V */ +#define WM8993_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */ +#define WM8993_CP_DYN_V_SHIFT 0 /* CP_DYN_V */ +#define WM8993_CP_DYN_V_WIDTH 1 /* CP_DYN_V */ + +/* + * R84 (0x54) - DC Servo 0 + */ +#define WM8993_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_DAC_WR_1 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_1_MASK 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_1_SHIFT 3 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_0 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_TRIG_DAC_WR_0_MASK 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_TRIG_DAC_WR_0_SHIFT 2 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8993_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8993_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM8993_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R85 (0x55) - DC Servo 1 + */ +#define WM8993_DCS_SERIES_NO_01_MASK 0x0FE0 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM8993_DCS_SERIES_NO_01_SHIFT 5 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM8993_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM8993_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8993_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8993_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R87 (0x57) - DC Servo 3 + */ +#define WM8993_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8993_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8993_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8993_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8993_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8993_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R88 (0x58) - DC Servo Readback 0 + */ +#define WM8993_DCS_DATAPATH_BUSY 0x4000 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_DATAPATH_BUSY_MASK 0x4000 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_DATAPATH_BUSY_SHIFT 14 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_DATAPATH_BUSY_WIDTH 1 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_CHANNEL_MASK 0x3000 /* DCS_CHANNEL - [13:12] */ +#define WM8993_DCS_CHANNEL_SHIFT 12 /* DCS_CHANNEL - [13:12] */ +#define WM8993_DCS_CHANNEL_WIDTH 2 /* DCS_CHANNEL - [13:12] */ +#define WM8993_DCS_CAL_COMPLETE_MASK 0x0300 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM8993_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM8993_DCS_CAL_COMPLETE_WIDTH 2 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM8993_DCS_DAC_WR_COMPLETE_MASK 0x0030 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM8993_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM8993_DCS_DAC_WR_COMPLETE_WIDTH 2 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM8993_DCS_STARTUP_COMPLETE_MASK 0x0003 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM8993_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM8993_DCS_STARTUP_COMPLETE_WIDTH 2 /* DCS_STARTUP_COMPLETE - [1:0] */ + +/* + * R89 (0x59) - DC Servo Readback 1 + */ +#define WM8993_DCS_INTEG_CHAN_1_MASK 0x00FF /* DCS_INTEG_CHAN_1 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_1_SHIFT 0 /* DCS_INTEG_CHAN_1 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_1_WIDTH 8 /* DCS_INTEG_CHAN_1 - [7:0] */ + +/* + * R90 (0x5A) - DC Servo Readback 2 + */ +#define WM8993_DCS_INTEG_CHAN_0_MASK 0x00FF /* DCS_INTEG_CHAN_0 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_0_SHIFT 0 /* DCS_INTEG_CHAN_0 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_0_WIDTH 8 /* DCS_INTEG_CHAN_0 - [7:0] */ + +/* + * R96 (0x60) - Analogue HP 0 + */ +#define WM8993_HPOUT1_AUTO_PU 0x0100 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1_AUTO_PU_MASK 0x0100 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1_AUTO_PU_SHIFT 8 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1_AUTO_PU_WIDTH 1 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */ +#define WM8993_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */ +#define WM8993_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */ +#define WM8993_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */ + +/* + * R98 (0x62) - EQ1 + */ +#define WM8993_EQ_ENA 0x0001 /* EQ_ENA */ +#define WM8993_EQ_ENA_MASK 0x0001 /* EQ_ENA */ +#define WM8993_EQ_ENA_SHIFT 0 /* EQ_ENA */ +#define WM8993_EQ_ENA_WIDTH 1 /* EQ_ENA */ + +/* + * R99 (0x63) - EQ2 + */ +#define WM8993_EQ_B1_GAIN_MASK 0x001F /* EQ_B1_GAIN - [4:0] */ +#define WM8993_EQ_B1_GAIN_SHIFT 0 /* EQ_B1_GAIN - [4:0] */ +#define WM8993_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [4:0] */ + +/* + * R100 (0x64) - EQ3 + */ +#define WM8993_EQ_B2_GAIN_MASK 0x001F /* EQ_B2_GAIN - [4:0] */ +#define WM8993_EQ_B2_GAIN_SHIFT 0 /* EQ_B2_GAIN - [4:0] */ +#define WM8993_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [4:0] */ + +/* + * R101 (0x65) - EQ4 + */ +#define WM8993_EQ_B3_GAIN_MASK 0x001F /* EQ_B3_GAIN - [4:0] */ +#define WM8993_EQ_B3_GAIN_SHIFT 0 /* EQ_B3_GAIN - [4:0] */ +#define WM8993_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [4:0] */ + +/* + * R102 (0x66) - EQ5 + */ +#define WM8993_EQ_B4_GAIN_MASK 0x001F /* EQ_B4_GAIN - [4:0] */ +#define WM8993_EQ_B4_GAIN_SHIFT 0 /* EQ_B4_GAIN - [4:0] */ +#define WM8993_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [4:0] */ + +/* + * R103 (0x67) - EQ6 + */ +#define WM8993_EQ_B5_GAIN_MASK 0x001F /* EQ_B5_GAIN - [4:0] */ +#define WM8993_EQ_B5_GAIN_SHIFT 0 /* EQ_B5_GAIN - [4:0] */ +#define WM8993_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [4:0] */ + +/* + * R104 (0x68) - EQ7 + */ +#define WM8993_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */ +#define WM8993_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */ +#define WM8993_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */ + +/* + * R105 (0x69) - EQ8 + */ +#define WM8993_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */ +#define WM8993_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */ +#define WM8993_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */ + +/* + * R106 (0x6A) - EQ9 + */ +#define WM8993_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */ +#define WM8993_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */ +#define WM8993_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */ + +/* + * R107 (0x6B) - EQ10 + */ +#define WM8993_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */ +#define WM8993_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */ +#define WM8993_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */ + +/* + * R108 (0x6C) - EQ11 + */ +#define WM8993_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */ +#define WM8993_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */ +#define WM8993_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */ + +/* + * R109 (0x6D) - EQ12 + */ +#define WM8993_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */ +#define WM8993_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */ +#define WM8993_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */ + +/* + * R110 (0x6E) - EQ13 + */ +#define WM8993_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */ +#define WM8993_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */ +#define WM8993_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */ + +/* + * R111 (0x6F) - EQ14 + */ +#define WM8993_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */ +#define WM8993_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */ +#define WM8993_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */ + +/* + * R112 (0x70) - EQ15 + */ +#define WM8993_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */ +#define WM8993_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */ +#define WM8993_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */ + +/* + * R113 (0x71) - EQ16 + */ +#define WM8993_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */ +#define WM8993_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */ +#define WM8993_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */ + +/* + * R114 (0x72) - EQ17 + */ +#define WM8993_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */ +#define WM8993_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */ +#define WM8993_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */ + +/* + * R115 (0x73) - EQ18 + */ +#define WM8993_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */ +#define WM8993_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */ +#define WM8993_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */ + +/* + * R116 (0x74) - EQ19 + */ +#define WM8993_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */ +#define WM8993_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */ +#define WM8993_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */ + +/* + * R117 (0x75) - EQ20 + */ +#define WM8993_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */ +#define WM8993_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */ +#define WM8993_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */ + +/* + * R118 (0x76) - EQ21 + */ +#define WM8993_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */ +#define WM8993_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */ +#define WM8993_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */ + +/* + * R119 (0x77) - EQ22 + */ +#define WM8993_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */ +#define WM8993_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */ +#define WM8993_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */ + +/* + * R120 (0x78) - EQ23 + */ +#define WM8993_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */ +#define WM8993_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */ +#define WM8993_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */ + +/* + * R121 (0x79) - EQ24 + */ +#define WM8993_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */ +#define WM8993_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */ +#define WM8993_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */ + +/* + * R122 (0x7A) - Digital Pulls + */ +#define WM8993_MCLK_PU 0x0080 /* MCLK_PU */ +#define WM8993_MCLK_PU_MASK 0x0080 /* MCLK_PU */ +#define WM8993_MCLK_PU_SHIFT 7 /* MCLK_PU */ +#define WM8993_MCLK_PU_WIDTH 1 /* MCLK_PU */ +#define WM8993_MCLK_PD 0x0040 /* MCLK_PD */ +#define WM8993_MCLK_PD_MASK 0x0040 /* MCLK_PD */ +#define WM8993_MCLK_PD_SHIFT 6 /* MCLK_PD */ +#define WM8993_MCLK_PD_WIDTH 1 /* MCLK_PD */ +#define WM8993_DACDAT_PU 0x0020 /* DACDAT_PU */ +#define WM8993_DACDAT_PU_MASK 0x0020 /* DACDAT_PU */ +#define WM8993_DACDAT_PU_SHIFT 5 /* DACDAT_PU */ +#define WM8993_DACDAT_PU_WIDTH 1 /* DACDAT_PU */ +#define WM8993_DACDAT_PD 0x0010 /* DACDAT_PD */ +#define WM8993_DACDAT_PD_MASK 0x0010 /* DACDAT_PD */ +#define WM8993_DACDAT_PD_SHIFT 4 /* DACDAT_PD */ +#define WM8993_DACDAT_PD_WIDTH 1 /* DACDAT_PD */ +#define WM8993_LRCLK_PU 0x0008 /* LRCLK_PU */ +#define WM8993_LRCLK_PU_MASK 0x0008 /* LRCLK_PU */ +#define WM8993_LRCLK_PU_SHIFT 3 /* LRCLK_PU */ +#define WM8993_LRCLK_PU_WIDTH 1 /* LRCLK_PU */ +#define WM8993_LRCLK_PD 0x0004 /* LRCLK_PD */ +#define WM8993_LRCLK_PD_MASK 0x0004 /* LRCLK_PD */ +#define WM8993_LRCLK_PD_SHIFT 2 /* LRCLK_PD */ +#define WM8993_LRCLK_PD_WIDTH 1 /* LRCLK_PD */ +#define WM8993_BCLK_PU 0x0002 /* BCLK_PU */ +#define WM8993_BCLK_PU_MASK 0x0002 /* BCLK_PU */ +#define WM8993_BCLK_PU_SHIFT 1 /* BCLK_PU */ +#define WM8993_BCLK_PU_WIDTH 1 /* BCLK_PU */ +#define WM8993_BCLK_PD 0x0001 /* BCLK_PD */ +#define WM8993_BCLK_PD_MASK 0x0001 /* BCLK_PD */ +#define WM8993_BCLK_PD_SHIFT 0 /* BCLK_PD */ +#define WM8993_BCLK_PD_WIDTH 1 /* BCLK_PD */ + +/* + * R123 (0x7B) - DRC Control 1 + */ +#define WM8993_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM8993_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM8993_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM8993_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM8993_DRC_DAC_PATH 0x4000 /* DRC_DAC_PATH */ +#define WM8993_DRC_DAC_PATH_MASK 0x4000 /* DRC_DAC_PATH */ +#define WM8993_DRC_DAC_PATH_SHIFT 14 /* DRC_DAC_PATH */ +#define WM8993_DRC_DAC_PATH_WIDTH 1 /* DRC_DAC_PATH */ +#define WM8993_DRC_SMOOTH_ENA 0x0800 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_SMOOTH_ENA_MASK 0x0800 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_SMOOTH_ENA_SHIFT 11 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_QR_ENA 0x0400 /* DRC_QR_ENA */ +#define WM8993_DRC_QR_ENA_MASK 0x0400 /* DRC_QR_ENA */ +#define WM8993_DRC_QR_ENA_SHIFT 10 /* DRC_QR_ENA */ +#define WM8993_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */ +#define WM8993_DRC_ANTICLIP_ENA 0x0200 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_ANTICLIP_ENA_MASK 0x0200 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_ANTICLIP_ENA_SHIFT 9 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_HYST_ENA 0x0100 /* DRC_HYST_ENA */ +#define WM8993_DRC_HYST_ENA_MASK 0x0100 /* DRC_HYST_ENA */ +#define WM8993_DRC_HYST_ENA_SHIFT 8 /* DRC_HYST_ENA */ +#define WM8993_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */ +#define WM8993_DRC_THRESH_HYST_MASK 0x0030 /* DRC_THRESH_HYST - [5:4] */ +#define WM8993_DRC_THRESH_HYST_SHIFT 4 /* DRC_THRESH_HYST - [5:4] */ +#define WM8993_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [5:4] */ +#define WM8993_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM8993_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM8993_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM8993_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8993_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8993_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R124 (0x7C) - DRC Control 2 + */ +#define WM8993_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8993_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8993_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8993_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */ +#define WM8993_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */ +#define WM8993_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */ +#define WM8993_DRC_THRESH_COMP_MASK 0x00FC /* DRC_THRESH_COMP - [7:2] */ +#define WM8993_DRC_THRESH_COMP_SHIFT 2 /* DRC_THRESH_COMP - [7:2] */ +#define WM8993_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [7:2] */ + +/* + * R125 (0x7D) - DRC Control 3 + */ +#define WM8993_DRC_AMP_COMP_MASK 0xF800 /* DRC_AMP_COMP - [15:11] */ +#define WM8993_DRC_AMP_COMP_SHIFT 11 /* DRC_AMP_COMP - [15:11] */ +#define WM8993_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [15:11] */ +#define WM8993_DRC_R0_SLOPE_COMP_MASK 0x0700 /* DRC_R0_SLOPE_COMP - [10:8] */ +#define WM8993_DRC_R0_SLOPE_COMP_SHIFT 8 /* DRC_R0_SLOPE_COMP - [10:8] */ +#define WM8993_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [10:8] */ +#define WM8993_DRC_FF_DELAY 0x0080 /* DRC_FF_DELAY */ +#define WM8993_DRC_FF_DELAY_MASK 0x0080 /* DRC_FF_DELAY */ +#define WM8993_DRC_FF_DELAY_SHIFT 7 /* DRC_FF_DELAY */ +#define WM8993_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */ +#define WM8993_DRC_THRESH_QR_MASK 0x000C /* DRC_THRESH_QR - [3:2] */ +#define WM8993_DRC_THRESH_QR_SHIFT 2 /* DRC_THRESH_QR - [3:2] */ +#define WM8993_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [3:2] */ +#define WM8993_DRC_RATE_QR_MASK 0x0003 /* DRC_RATE_QR - [1:0] */ +#define WM8993_DRC_RATE_QR_SHIFT 0 /* DRC_RATE_QR - [1:0] */ +#define WM8993_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [1:0] */ + +/* + * R126 (0x7E) - DRC Control 4 + */ +#define WM8993_DRC_R1_SLOPE_COMP_MASK 0xE000 /* DRC_R1_SLOPE_COMP - [15:13] */ +#define WM8993_DRC_R1_SLOPE_COMP_SHIFT 13 /* DRC_R1_SLOPE_COMP - [15:13] */ +#define WM8993_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [15:13] */ +#define WM8993_DRC_STARTUP_GAIN_MASK 0x1F00 /* DRC_STARTUP_GAIN - [12:8] */ +#define WM8993_DRC_STARTUP_GAIN_SHIFT 8 /* DRC_STARTUP_GAIN - [12:8] */ +#define WM8993_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [12:8] */ + +#endif diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c new file mode 100644 index 000000000..e3e069277 --- /dev/null +++ b/sound/soc/codecs/wm8994.c @@ -0,0 +1,4543 @@ +/* + * wm8994.c -- WM8994 ALSA SoC Audio driver + * + * Copyright 2009-12 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "wm8994.h" +#include "wm_hubs.h" + +#define WM1811_JACKDET_MODE_NONE 0x0000 +#define WM1811_JACKDET_MODE_JACK 0x0100 +#define WM1811_JACKDET_MODE_MIC 0x0080 +#define WM1811_JACKDET_MODE_AUDIO 0x0180 + +#define WM8994_NUM_DRC 3 +#define WM8994_NUM_EQ 3 + +static struct { + unsigned int reg; + unsigned int mask; +} wm8994_vu_bits[] = { + { WM8994_LEFT_LINE_INPUT_1_2_VOLUME, WM8994_IN1_VU }, + { WM8994_RIGHT_LINE_INPUT_1_2_VOLUME, WM8994_IN1_VU }, + { WM8994_LEFT_LINE_INPUT_3_4_VOLUME, WM8994_IN2_VU }, + { WM8994_RIGHT_LINE_INPUT_3_4_VOLUME, WM8994_IN2_VU }, + { WM8994_SPEAKER_VOLUME_LEFT, WM8994_SPKOUT_VU }, + { WM8994_SPEAKER_VOLUME_RIGHT, WM8994_SPKOUT_VU }, + { WM8994_LEFT_OUTPUT_VOLUME, WM8994_HPOUT1_VU }, + { WM8994_RIGHT_OUTPUT_VOLUME, WM8994_HPOUT1_VU }, + { WM8994_LEFT_OPGA_VOLUME, WM8994_MIXOUT_VU }, + { WM8994_RIGHT_OPGA_VOLUME, WM8994_MIXOUT_VU }, + + { WM8994_AIF1_DAC1_LEFT_VOLUME, WM8994_AIF1DAC1_VU }, + { WM8994_AIF1_DAC1_RIGHT_VOLUME, WM8994_AIF1DAC1_VU }, + { WM8994_AIF1_DAC2_LEFT_VOLUME, WM8994_AIF1DAC2_VU }, + { WM8994_AIF1_DAC2_RIGHT_VOLUME, WM8994_AIF1DAC2_VU }, + { WM8994_AIF2_DAC_LEFT_VOLUME, WM8994_AIF2DAC_VU }, + { WM8994_AIF2_DAC_RIGHT_VOLUME, WM8994_AIF2DAC_VU }, + { WM8994_AIF1_ADC1_LEFT_VOLUME, WM8994_AIF1ADC1_VU }, + { WM8994_AIF1_ADC1_RIGHT_VOLUME, WM8994_AIF1ADC1_VU }, + { WM8994_AIF1_ADC2_LEFT_VOLUME, WM8994_AIF1ADC2_VU }, + { WM8994_AIF1_ADC2_RIGHT_VOLUME, WM8994_AIF1ADC2_VU }, + { WM8994_AIF2_ADC_LEFT_VOLUME, WM8994_AIF2ADC_VU }, + { WM8994_AIF2_ADC_RIGHT_VOLUME, WM8994_AIF1ADC2_VU }, + { WM8994_DAC1_LEFT_VOLUME, WM8994_DAC1_VU }, + { WM8994_DAC1_RIGHT_VOLUME, WM8994_DAC1_VU }, + { WM8994_DAC2_LEFT_VOLUME, WM8994_DAC2_VU }, + { WM8994_DAC2_RIGHT_VOLUME, WM8994_DAC2_VU }, +}; + +static int wm8994_drc_base[] = { + WM8994_AIF1_DRC1_1, + WM8994_AIF1_DRC2_1, + WM8994_AIF2_DRC_1, +}; + +static int wm8994_retune_mobile_base[] = { + WM8994_AIF1_DAC1_EQ_GAINS_1, + WM8994_AIF1_DAC2_EQ_GAINS_1, + WM8994_AIF2_EQ_GAINS_1, +}; + +static const struct wm8958_micd_rate micdet_rates[] = { + { 32768, true, 1, 4 }, + { 32768, false, 1, 1 }, + { 44100 * 256, true, 7, 10 }, + { 44100 * 256, false, 7, 10 }, +}; + +static const struct wm8958_micd_rate jackdet_rates[] = { + { 32768, true, 0, 1 }, + { 32768, false, 0, 1 }, + { 44100 * 256, true, 10, 10 }, + { 44100 * 256, false, 7, 8 }, +}; + +static void wm8958_micd_set_rate(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int best, i, sysclk, val; + bool idle; + const struct wm8958_micd_rate *rates; + int num_rates; + + idle = !wm8994->jack_mic; + + sysclk = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (sysclk & WM8994_SYSCLK_SRC) + sysclk = wm8994->aifclk[1]; + else + sysclk = wm8994->aifclk[0]; + + if (control->pdata.micd_rates) { + rates = control->pdata.micd_rates; + num_rates = control->pdata.num_micd_rates; + } else if (wm8994->jackdet) { + rates = jackdet_rates; + num_rates = ARRAY_SIZE(jackdet_rates); + } else { + rates = micdet_rates; + num_rates = ARRAY_SIZE(micdet_rates); + } + + best = 0; + for (i = 0; i < num_rates; i++) { + if (rates[i].idle != idle) + continue; + if (abs(rates[i].sysclk - sysclk) < + abs(rates[best].sysclk - sysclk)) + best = i; + else if (rates[best].idle != idle) + best = i; + } + + val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT + | rates[best].rate << WM8958_MICD_RATE_SHIFT; + + dev_dbg(component->dev, "MICD rate %d,%d for %dHz %s\n", + rates[best].start, rates[best].rate, sysclk, + idle ? "idle" : "active"); + + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_BIAS_STARTTIME_MASK | + WM8958_MICD_RATE_MASK, val); +} + +static int configure_aif_clock(struct snd_soc_component *component, int aif) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int rate; + int reg1 = 0; + int offset; + + if (aif) + offset = 4; + else + offset = 0; + + switch (wm8994->sysclk[aif]) { + case WM8994_SYSCLK_MCLK1: + rate = wm8994->mclk[0]; + break; + + case WM8994_SYSCLK_MCLK2: + reg1 |= 0x8; + rate = wm8994->mclk[1]; + break; + + case WM8994_SYSCLK_FLL1: + reg1 |= 0x10; + rate = wm8994->fll[0].out; + break; + + case WM8994_SYSCLK_FLL2: + reg1 |= 0x18; + rate = wm8994->fll[1].out; + break; + + default: + return -EINVAL; + } + + if (rate >= 13500000) { + rate /= 2; + reg1 |= WM8994_AIF1CLK_DIV; + + dev_dbg(component->dev, "Dividing AIF%d clock to %dHz\n", + aif + 1, rate); + } + + wm8994->aifclk[aif] = rate; + + snd_soc_component_update_bits(component, WM8994_AIF1_CLOCKING_1 + offset, + WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV, + reg1); + + return 0; +} + +static int configure_clock(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int change, new; + + /* Bring up the AIF clocks first */ + configure_aif_clock(component, 0); + configure_aif_clock(component, 1); + + /* Then switch CLK_SYS over to the higher of them; a change + * can only happen as a result of a clocking change which can + * only be made outside of DAPM so we can safely redo the + * clocking. + */ + + /* If they're equal it doesn't matter which is used */ + if (wm8994->aifclk[0] == wm8994->aifclk[1]) { + wm8958_micd_set_rate(component); + return 0; + } + + if (wm8994->aifclk[0] < wm8994->aifclk[1]) + new = WM8994_SYSCLK_SRC; + else + new = 0; + + change = snd_soc_component_update_bits(component, WM8994_CLOCKING_1, + WM8994_SYSCLK_SRC, new); + if (change) + snd_soc_dapm_sync(dapm); + + wm8958_micd_set_rate(component); + + return 0; +} + +static int check_clk_sys(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + int reg = snd_soc_component_read32(component, WM8994_CLOCKING_1); + const char *clk; + + /* Check what we're currently using for CLK_SYS */ + if (reg & WM8994_SYSCLK_SRC) + clk = "AIF2CLK"; + else + clk = "AIF1CLK"; + + return strcmp(source->name, clk) == 0; +} + +static const char *sidetone_hpf_text[] = { + "2.7kHz", "1.35kHz", "675Hz", "370Hz", "180Hz", "90Hz", "45Hz" +}; + +static SOC_ENUM_SINGLE_DECL(sidetone_hpf, + WM8994_SIDETONE, 7, sidetone_hpf_text); + +static const char *adc_hpf_text[] = { + "HiFi", "Voice 1", "Voice 2", "Voice 3" +}; + +static SOC_ENUM_SINGLE_DECL(aif1adc1_hpf, + WM8994_AIF1_ADC1_FILTERS, 13, adc_hpf_text); + +static SOC_ENUM_SINGLE_DECL(aif1adc2_hpf, + WM8994_AIF1_ADC2_FILTERS, 13, adc_hpf_text); + +static SOC_ENUM_SINGLE_DECL(aif2adc_hpf, + WM8994_AIF2_ADC_FILTERS, 13, adc_hpf_text); + +static const DECLARE_TLV_DB_SCALE(aif_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_SCALE(wm8994_3d_tlv, -1600, 183, 0); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); +static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0); + +#define WM8994_DRC_SWITCH(xname, reg, shift) \ + SOC_SINGLE_EXT(xname, reg, shift, 1, 0, \ + snd_soc_get_volsw, wm8994_put_drc_sw) + +static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int mask, ret; + + /* Can't enable both ADC and DAC paths simultaneously */ + if (mc->shift == WM8994_AIF1DAC1_DRC_ENA_SHIFT) + mask = WM8994_AIF1ADC1L_DRC_ENA_MASK | + WM8994_AIF1ADC1R_DRC_ENA_MASK; + else + mask = WM8994_AIF1DAC1_DRC_ENA_MASK; + + ret = snd_soc_component_read32(component, mc->reg); + if (ret < 0) + return ret; + if (ret & mask) + return -EINVAL; + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + +static void wm8994_set_drc(struct snd_soc_component *component, int drc) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + int base = wm8994_drc_base[drc]; + int cfg = wm8994->drc_cfg[drc]; + int save, i; + + /* Save any enables; the configuration should clear them. */ + save = snd_soc_component_read32(component, base); + save &= WM8994_AIF1DAC1_DRC_ENA | WM8994_AIF1ADC1L_DRC_ENA | + WM8994_AIF1ADC1R_DRC_ENA; + + for (i = 0; i < WM8994_DRC_REGS; i++) + snd_soc_component_update_bits(component, base + i, 0xffff, + pdata->drc_cfgs[cfg].regs[i]); + + snd_soc_component_update_bits(component, base, WM8994_AIF1DAC1_DRC_ENA | + WM8994_AIF1ADC1L_DRC_ENA | + WM8994_AIF1ADC1R_DRC_ENA, save); +} + +/* Icky as hell but saves code duplication */ +static int wm8994_get_drc(const char *name) +{ + if (strcmp(name, "AIF1DRC1 Mode") == 0) + return 0; + if (strcmp(name, "AIF1DRC2 Mode") == 0) + return 1; + if (strcmp(name, "AIF2DRC Mode") == 0) + return 2; + return -EINVAL; +} + +static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + int drc = wm8994_get_drc(kcontrol->id.name); + int value = ucontrol->value.enumerated.item[0]; + + if (drc < 0) + return drc; + + if (value >= pdata->num_drc_cfgs) + return -EINVAL; + + wm8994->drc_cfg[drc] = value; + + wm8994_set_drc(component, drc); + + return 0; +} + +static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int drc = wm8994_get_drc(kcontrol->id.name); + + if (drc < 0) + return drc; + ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc]; + + return 0; +} + +static void wm8994_set_retune_mobile(struct snd_soc_component *component, int block) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + int base = wm8994_retune_mobile_base[block]; + int iface, best, best_val, save, i, cfg; + + if (!pdata || !wm8994->num_retune_mobile_texts) + return; + + switch (block) { + case 0: + case 1: + iface = 0; + break; + case 2: + iface = 1; + break; + default: + return; + } + + /* Find the version of the currently selected configuration + * with the nearest sample rate. */ + cfg = wm8994->retune_mobile_cfg[block]; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8994->retune_mobile_texts[cfg]) == 0 && + abs(pdata->retune_mobile_cfgs[i].rate + - wm8994->dac_rates[iface]) < best_val) { + best = i; + best_val = abs(pdata->retune_mobile_cfgs[i].rate + - wm8994->dac_rates[iface]); + } + } + + dev_dbg(component->dev, "ReTune Mobile %d %s/%dHz for %dHz sample rate\n", + block, + pdata->retune_mobile_cfgs[best].name, + pdata->retune_mobile_cfgs[best].rate, + wm8994->dac_rates[iface]); + + /* The EQ will be disabled while reconfiguring it, remember the + * current configuration. + */ + save = snd_soc_component_read32(component, base); + save &= WM8994_AIF1DAC1_EQ_ENA; + + for (i = 0; i < WM8994_EQ_REGS; i++) + snd_soc_component_update_bits(component, base + i, 0xffff, + pdata->retune_mobile_cfgs[best].regs[i]); + + snd_soc_component_update_bits(component, base, WM8994_AIF1DAC1_EQ_ENA, save); +} + +/* Icky as hell but saves code duplication */ +static int wm8994_get_retune_mobile_block(const char *name) +{ + if (strcmp(name, "AIF1.1 EQ Mode") == 0) + return 0; + if (strcmp(name, "AIF1.2 EQ Mode") == 0) + return 1; + if (strcmp(name, "AIF2 EQ Mode") == 0) + return 2; + return -EINVAL; +} + +static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + int block = wm8994_get_retune_mobile_block(kcontrol->id.name); + int value = ucontrol->value.enumerated.item[0]; + + if (block < 0) + return block; + + if (value >= pdata->num_retune_mobile_cfgs) + return -EINVAL; + + wm8994->retune_mobile_cfg[block] = value; + + wm8994_set_retune_mobile(component, block); + + return 0; +} + +static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int block = wm8994_get_retune_mobile_block(kcontrol->id.name); + + if (block < 0) + return block; + + ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block]; + + return 0; +} + +static const char *aif_chan_src_text[] = { + "Left", "Right" +}; + +static SOC_ENUM_SINGLE_DECL(aif1adcl_src, + WM8994_AIF1_CONTROL_1, 15, aif_chan_src_text); + +static SOC_ENUM_SINGLE_DECL(aif1adcr_src, + WM8994_AIF1_CONTROL_1, 14, aif_chan_src_text); + +static SOC_ENUM_SINGLE_DECL(aif2adcl_src, + WM8994_AIF2_CONTROL_1, 15, aif_chan_src_text); + +static SOC_ENUM_SINGLE_DECL(aif2adcr_src, + WM8994_AIF2_CONTROL_1, 14, aif_chan_src_text); + +static SOC_ENUM_SINGLE_DECL(aif1dacl_src, + WM8994_AIF1_CONTROL_2, 15, aif_chan_src_text); + +static SOC_ENUM_SINGLE_DECL(aif1dacr_src, + WM8994_AIF1_CONTROL_2, 14, aif_chan_src_text); + +static SOC_ENUM_SINGLE_DECL(aif2dacl_src, + WM8994_AIF2_CONTROL_2, 15, aif_chan_src_text); + +static SOC_ENUM_SINGLE_DECL(aif2dacr_src, + WM8994_AIF2_CONTROL_2, 14, aif_chan_src_text); + +static const char *osr_text[] = { + "Low Power", "High Performance", +}; + +static SOC_ENUM_SINGLE_DECL(dac_osr, + WM8994_OVERSAMPLING, 0, osr_text); + +static SOC_ENUM_SINGLE_DECL(adc_osr, + WM8994_OVERSAMPLING, 1, osr_text); + +static const struct snd_kcontrol_new wm8994_common_snd_controls[] = { +SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8994_AIF1_ADC1_LEFT_VOLUME, + WM8994_AIF1_ADC1_RIGHT_VOLUME, + 1, 119, 0, digital_tlv), +SOC_DOUBLE_R_TLV("AIF2ADC Volume", WM8994_AIF2_ADC_LEFT_VOLUME, + WM8994_AIF2_ADC_RIGHT_VOLUME, + 1, 119, 0, digital_tlv), + +SOC_ENUM("AIF1ADCL Source", aif1adcl_src), +SOC_ENUM("AIF1ADCR Source", aif1adcr_src), +SOC_ENUM("AIF2ADCL Source", aif2adcl_src), +SOC_ENUM("AIF2ADCR Source", aif2adcr_src), + +SOC_ENUM("AIF1DACL Source", aif1dacl_src), +SOC_ENUM("AIF1DACR Source", aif1dacr_src), +SOC_ENUM("AIF2DACL Source", aif2dacl_src), +SOC_ENUM("AIF2DACR Source", aif2dacr_src), + +SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8994_AIF1_DAC1_LEFT_VOLUME, + WM8994_AIF1_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R_TLV("AIF2DAC Volume", WM8994_AIF2_DAC_LEFT_VOLUME, + WM8994_AIF2_DAC_RIGHT_VOLUME, 1, 96, 0, digital_tlv), + +SOC_SINGLE_TLV("AIF1 Boost Volume", WM8994_AIF1_CONTROL_2, 10, 3, 0, aif_tlv), +SOC_SINGLE_TLV("AIF2 Boost Volume", WM8994_AIF2_CONTROL_2, 10, 3, 0, aif_tlv), + +SOC_SINGLE("AIF1DAC1 EQ Switch", WM8994_AIF1_DAC1_EQ_GAINS_1, 0, 1, 0), +SOC_SINGLE("AIF2 EQ Switch", WM8994_AIF2_EQ_GAINS_1, 0, 1, 0), + +WM8994_DRC_SWITCH("AIF1DAC1 DRC Switch", WM8994_AIF1_DRC1_1, 2), +WM8994_DRC_SWITCH("AIF1ADC1L DRC Switch", WM8994_AIF1_DRC1_1, 1), +WM8994_DRC_SWITCH("AIF1ADC1R DRC Switch", WM8994_AIF1_DRC1_1, 0), + +WM8994_DRC_SWITCH("AIF2DAC DRC Switch", WM8994_AIF2_DRC_1, 2), +WM8994_DRC_SWITCH("AIF2ADCL DRC Switch", WM8994_AIF2_DRC_1, 1), +WM8994_DRC_SWITCH("AIF2ADCR DRC Switch", WM8994_AIF2_DRC_1, 0), + +SOC_SINGLE_TLV("DAC1 Right Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES, + 5, 12, 0, st_tlv), +SOC_SINGLE_TLV("DAC1 Left Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES, + 0, 12, 0, st_tlv), +SOC_SINGLE_TLV("DAC2 Right Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES, + 5, 12, 0, st_tlv), +SOC_SINGLE_TLV("DAC2 Left Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES, + 0, 12, 0, st_tlv), +SOC_ENUM("Sidetone HPF Mux", sidetone_hpf), +SOC_SINGLE("Sidetone HPF Switch", WM8994_SIDETONE, 6, 1, 0), + +SOC_ENUM("AIF1ADC1 HPF Mode", aif1adc1_hpf), +SOC_DOUBLE("AIF1ADC1 HPF Switch", WM8994_AIF1_ADC1_FILTERS, 12, 11, 1, 0), + +SOC_ENUM("AIF2ADC HPF Mode", aif2adc_hpf), +SOC_DOUBLE("AIF2ADC HPF Switch", WM8994_AIF2_ADC_FILTERS, 12, 11, 1, 0), + +SOC_ENUM("ADC OSR", adc_osr), +SOC_ENUM("DAC OSR", dac_osr), + +SOC_DOUBLE_R_TLV("DAC1 Volume", WM8994_DAC1_LEFT_VOLUME, + WM8994_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R("DAC1 Switch", WM8994_DAC1_LEFT_VOLUME, + WM8994_DAC1_RIGHT_VOLUME, 9, 1, 1), + +SOC_DOUBLE_R_TLV("DAC2 Volume", WM8994_DAC2_LEFT_VOLUME, + WM8994_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R("DAC2 Switch", WM8994_DAC2_LEFT_VOLUME, + WM8994_DAC2_RIGHT_VOLUME, 9, 1, 1), + +SOC_SINGLE_TLV("SPKL DAC2 Volume", WM8994_SPKMIXL_ATTENUATION, + 6, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKL DAC1 Volume", WM8994_SPKMIXL_ATTENUATION, + 2, 1, 1, wm_hubs_spkmix_tlv), + +SOC_SINGLE_TLV("SPKR DAC2 Volume", WM8994_SPKMIXR_ATTENUATION, + 6, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKR DAC1 Volume", WM8994_SPKMIXR_ATTENUATION, + 2, 1, 1, wm_hubs_spkmix_tlv), + +SOC_SINGLE_TLV("AIF1DAC1 3D Stereo Volume", WM8994_AIF1_DAC1_FILTERS_2, + 10, 15, 0, wm8994_3d_tlv), +SOC_SINGLE("AIF1DAC1 3D Stereo Switch", WM8994_AIF1_DAC1_FILTERS_2, + 8, 1, 0), +SOC_SINGLE_TLV("AIF1DAC2 3D Stereo Volume", WM8994_AIF1_DAC2_FILTERS_2, + 10, 15, 0, wm8994_3d_tlv), +SOC_SINGLE("AIF1DAC2 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2, + 8, 1, 0), +SOC_SINGLE_TLV("AIF2DAC 3D Stereo Volume", WM8994_AIF2_DAC_FILTERS_2, + 10, 15, 0, wm8994_3d_tlv), +SOC_SINGLE("AIF2DAC 3D Stereo Switch", WM8994_AIF2_DAC_FILTERS_2, + 8, 1, 0), +}; + +/* Controls not available on WM1811 */ +static const struct snd_kcontrol_new wm8994_snd_controls[] = { +SOC_DOUBLE_R_TLV("AIF1ADC2 Volume", WM8994_AIF1_ADC2_LEFT_VOLUME, + WM8994_AIF1_ADC2_RIGHT_VOLUME, + 1, 119, 0, digital_tlv), +SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8994_AIF1_DAC2_LEFT_VOLUME, + WM8994_AIF1_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv), + +SOC_SINGLE("AIF1DAC2 EQ Switch", WM8994_AIF1_DAC2_EQ_GAINS_1, 0, 1, 0), + +WM8994_DRC_SWITCH("AIF1DAC2 DRC Switch", WM8994_AIF1_DRC2_1, 2), +WM8994_DRC_SWITCH("AIF1ADC2L DRC Switch", WM8994_AIF1_DRC2_1, 1), +WM8994_DRC_SWITCH("AIF1ADC2R DRC Switch", WM8994_AIF1_DRC2_1, 0), + +SOC_ENUM("AIF1ADC2 HPF Mode", aif1adc2_hpf), +SOC_DOUBLE("AIF1ADC2 HPF Switch", WM8994_AIF1_ADC2_FILTERS, 12, 11, 1, 0), +}; + +static const struct snd_kcontrol_new wm8994_eq_controls[] = { +SOC_SINGLE_TLV("AIF1DAC1 EQ1 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ2 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ3 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ4 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ5 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 6, 31, 0, + eq_tlv), + +SOC_SINGLE_TLV("AIF1DAC2 EQ1 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ2 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ3 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ4 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ5 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 6, 31, 0, + eq_tlv), + +SOC_SINGLE_TLV("AIF2 EQ1 Volume", WM8994_AIF2_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ2 Volume", WM8994_AIF2_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ3 Volume", WM8994_AIF2_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ4 Volume", WM8994_AIF2_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0, + eq_tlv), +}; + +static const struct snd_kcontrol_new wm8994_drc_controls[] = { +SND_SOC_BYTES_MASK("AIF1.1 DRC", WM8994_AIF1_DRC1_1, 5, + WM8994_AIF1DAC1_DRC_ENA | WM8994_AIF1ADC1L_DRC_ENA | + WM8994_AIF1ADC1R_DRC_ENA), +SND_SOC_BYTES_MASK("AIF1.2 DRC", WM8994_AIF1_DRC2_1, 5, + WM8994_AIF1DAC2_DRC_ENA | WM8994_AIF1ADC2L_DRC_ENA | + WM8994_AIF1ADC2R_DRC_ENA), +SND_SOC_BYTES_MASK("AIF2 DRC", WM8994_AIF2_DRC_1, 5, + WM8994_AIF2DAC_DRC_ENA | WM8994_AIF2ADCL_DRC_ENA | + WM8994_AIF2ADCR_DRC_ENA), +}; + +static const char *wm8958_ng_text[] = { + "30ms", "125ms", "250ms", "500ms", +}; + +static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac1_ng_hold, + WM8958_AIF1_DAC1_NOISE_GATE, + WM8958_AIF1DAC1_NG_THR_SHIFT, + wm8958_ng_text); + +static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac2_ng_hold, + WM8958_AIF1_DAC2_NOISE_GATE, + WM8958_AIF1DAC2_NG_THR_SHIFT, + wm8958_ng_text); + +static SOC_ENUM_SINGLE_DECL(wm8958_aif2dac_ng_hold, + WM8958_AIF2_DAC_NOISE_GATE, + WM8958_AIF2DAC_NG_THR_SHIFT, + wm8958_ng_text); + +static const struct snd_kcontrol_new wm8958_snd_controls[] = { +SOC_SINGLE_TLV("AIF3 Boost Volume", WM8958_AIF3_CONTROL_2, 10, 3, 0, aif_tlv), + +SOC_SINGLE("AIF1DAC1 Noise Gate Switch", WM8958_AIF1_DAC1_NOISE_GATE, + WM8958_AIF1DAC1_NG_ENA_SHIFT, 1, 0), +SOC_ENUM("AIF1DAC1 Noise Gate Hold Time", wm8958_aif1dac1_ng_hold), +SOC_SINGLE_TLV("AIF1DAC1 Noise Gate Threshold Volume", + WM8958_AIF1_DAC1_NOISE_GATE, WM8958_AIF1DAC1_NG_THR_SHIFT, + 7, 1, ng_tlv), + +SOC_SINGLE("AIF1DAC2 Noise Gate Switch", WM8958_AIF1_DAC2_NOISE_GATE, + WM8958_AIF1DAC2_NG_ENA_SHIFT, 1, 0), +SOC_ENUM("AIF1DAC2 Noise Gate Hold Time", wm8958_aif1dac2_ng_hold), +SOC_SINGLE_TLV("AIF1DAC2 Noise Gate Threshold Volume", + WM8958_AIF1_DAC2_NOISE_GATE, WM8958_AIF1DAC2_NG_THR_SHIFT, + 7, 1, ng_tlv), + +SOC_SINGLE("AIF2DAC Noise Gate Switch", WM8958_AIF2_DAC_NOISE_GATE, + WM8958_AIF2DAC_NG_ENA_SHIFT, 1, 0), +SOC_ENUM("AIF2DAC Noise Gate Hold Time", wm8958_aif2dac_ng_hold), +SOC_SINGLE_TLV("AIF2DAC Noise Gate Threshold Volume", + WM8958_AIF2_DAC_NOISE_GATE, WM8958_AIF2DAC_NG_THR_SHIFT, + 7, 1, ng_tlv), +}; + +static const struct snd_kcontrol_new wm1811_snd_controls[] = { +SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0, + mixin_boost_tlv), +SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0, + mixin_boost_tlv), +}; + +/* We run all mode setting through a function to enforce audio mode */ +static void wm1811_jackdet_set_mode(struct snd_soc_component *component, u16 mode) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (!wm8994->jackdet || !wm8994->micdet[0].jack) + return; + + if (wm8994->active_refcount) + mode = WM1811_JACKDET_MODE_AUDIO; + + if (mode == wm8994->jackdet_mode) + return; + + wm8994->jackdet_mode = mode; + + /* Always use audio mode to detect while the system is active */ + if (mode != WM1811_JACKDET_MODE_NONE) + mode = WM1811_JACKDET_MODE_AUDIO; + + snd_soc_component_update_bits(component, WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, mode); +} + +static void active_reference(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + mutex_lock(&wm8994->accdet_lock); + + wm8994->active_refcount++; + + dev_dbg(component->dev, "Active refcount incremented, now %d\n", + wm8994->active_refcount); + + /* If we're using jack detection go into audio mode */ + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_AUDIO); + + mutex_unlock(&wm8994->accdet_lock); +} + +static void active_dereference(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + u16 mode; + + mutex_lock(&wm8994->accdet_lock); + + wm8994->active_refcount--; + + dev_dbg(component->dev, "Active refcount decremented, now %d\n", + wm8994->active_refcount); + + if (wm8994->active_refcount == 0) { + /* Go into appropriate detection only mode */ + if (wm8994->jack_mic || wm8994->mic_detecting) + mode = WM1811_JACKDET_MODE_MIC; + else + mode = WM1811_JACKDET_MODE_JACK; + + wm1811_jackdet_set_mode(component, mode); + } + + mutex_unlock(&wm8994->accdet_lock); +} + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return configure_clock(component); + + case SND_SOC_DAPM_POST_PMU: + /* + * JACKDET won't run until we start the clock and it + * only reports deltas, make sure we notify the state + * up the stack on startup. Use a *very* generous + * timeout for paranoia, there's no urgency and we + * don't want false reports. + */ + if (wm8994->jackdet && !wm8994->clk_has_run) { + queue_delayed_work(system_power_efficient_wq, + &wm8994->jackdet_bootstrap, + msecs_to_jiffies(1000)); + wm8994->clk_has_run = true; + } + break; + + case SND_SOC_DAPM_POST_PMD: + configure_clock(component); + break; + } + + return 0; +} + +static void vmid_reference(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + pm_runtime_get_sync(component->dev); + + wm8994->vmid_refcount++; + + dev_dbg(component->dev, "Referencing VMID, refcount is now %d\n", + wm8994->vmid_refcount); + + if (wm8994->vmid_refcount == 1) { + snd_soc_component_update_bits(component, WM8994_ANTIPOP_1, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH, 0); + + wm_hubs_vmid_ena(component); + + switch (wm8994->vmid_mode) { + default: + WARN_ON(NULL == "Invalid VMID mode"); + /* fall through */ + case WM8994_VMID_NORMAL: + /* Startup bias, VMID ramp & buffer */ + snd_soc_component_update_bits(component, WM8994_ANTIPOP_2, + WM8994_BIAS_SRC | + WM8994_VMID_DISCH | + WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + WM8994_VMID_RAMP_MASK, + WM8994_BIAS_SRC | + WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + (0x2 << WM8994_VMID_RAMP_SHIFT)); + + /* Main bias enable, VMID=2x40k */ + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_1, + WM8994_BIAS_ENA | + WM8994_VMID_SEL_MASK, + WM8994_BIAS_ENA | 0x2); + + msleep(300); + + snd_soc_component_update_bits(component, WM8994_ANTIPOP_2, + WM8994_VMID_RAMP_MASK | + WM8994_BIAS_SRC, + 0); + break; + + case WM8994_VMID_FORCE: + /* Startup bias, slow VMID ramp & buffer */ + snd_soc_component_update_bits(component, WM8994_ANTIPOP_2, + WM8994_BIAS_SRC | + WM8994_VMID_DISCH | + WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + WM8994_VMID_RAMP_MASK, + WM8994_BIAS_SRC | + WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + (0x2 << WM8994_VMID_RAMP_SHIFT)); + + /* Main bias enable, VMID=2x40k */ + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_1, + WM8994_BIAS_ENA | + WM8994_VMID_SEL_MASK, + WM8994_BIAS_ENA | 0x2); + + msleep(400); + + snd_soc_component_update_bits(component, WM8994_ANTIPOP_2, + WM8994_VMID_RAMP_MASK | + WM8994_BIAS_SRC, + 0); + break; + } + } +} + +static void vmid_dereference(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + wm8994->vmid_refcount--; + + dev_dbg(component->dev, "Dereferencing VMID, refcount is now %d\n", + wm8994->vmid_refcount); + + if (wm8994->vmid_refcount == 0) { + if (wm8994->hubs.lineout1_se) + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_3, + WM8994_LINEOUT1N_ENA | + WM8994_LINEOUT1P_ENA, + WM8994_LINEOUT1N_ENA | + WM8994_LINEOUT1P_ENA); + + if (wm8994->hubs.lineout2_se) + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_3, + WM8994_LINEOUT2N_ENA | + WM8994_LINEOUT2P_ENA, + WM8994_LINEOUT2N_ENA | + WM8994_LINEOUT2P_ENA); + + /* Start discharging VMID */ + snd_soc_component_update_bits(component, WM8994_ANTIPOP_2, + WM8994_BIAS_SRC | + WM8994_VMID_DISCH, + WM8994_BIAS_SRC | + WM8994_VMID_DISCH); + + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_1, + WM8994_VMID_SEL_MASK, 0); + + msleep(400); + + /* Active discharge */ + snd_soc_component_update_bits(component, WM8994_ANTIPOP_1, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH); + + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_3, + WM8994_LINEOUT1N_ENA | + WM8994_LINEOUT1P_ENA | + WM8994_LINEOUT2N_ENA | + WM8994_LINEOUT2P_ENA, 0); + + /* Switch off startup biases */ + snd_soc_component_update_bits(component, WM8994_ANTIPOP_2, + WM8994_BIAS_SRC | + WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + WM8994_VMID_RAMP_MASK, 0); + + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_1, + WM8994_VMID_SEL_MASK, 0); + } + + pm_runtime_put(component->dev); +} + +static int vmid_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + vmid_reference(component); + break; + + case SND_SOC_DAPM_POST_PMD: + vmid_dereference(component); + break; + } + + return 0; +} + +static bool wm8994_check_class_w_digital(struct snd_soc_component *component) +{ + int source = 0; /* GCC flow analysis can't track enable */ + int reg, reg_r; + + /* We also need the same AIF source for L/R and only one path */ + reg = snd_soc_component_read32(component, WM8994_DAC1_LEFT_MIXER_ROUTING); + switch (reg) { + case WM8994_AIF2DACL_TO_DAC1L: + dev_vdbg(component->dev, "Class W source AIF2DAC\n"); + source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT; + break; + case WM8994_AIF1DAC2L_TO_DAC1L: + dev_vdbg(component->dev, "Class W source AIF1DAC2\n"); + source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT; + break; + case WM8994_AIF1DAC1L_TO_DAC1L: + dev_vdbg(component->dev, "Class W source AIF1DAC1\n"); + source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT; + break; + default: + dev_vdbg(component->dev, "DAC mixer setting: %x\n", reg); + return false; + } + + reg_r = snd_soc_component_read32(component, WM8994_DAC1_RIGHT_MIXER_ROUTING); + if (reg_r != reg) { + dev_vdbg(component->dev, "Left and right DAC mixers different\n"); + return false; + } + + /* Set the source up */ + snd_soc_component_update_bits(component, WM8994_CLASS_W_1, + WM8994_CP_DYN_SRC_SEL_MASK, source); + + return true; +} + +static int aif1clk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int mask = WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC1R_ENA; + int i; + int dac; + int adc; + int val; + + switch (control->type) { + case WM8994: + case WM8958: + mask |= WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA; + break; + default: + break; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Don't enable timeslot 2 if not in use */ + if (wm8994->channels[0] <= 2) + mask &= ~(WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA); + + val = snd_soc_component_read32(component, WM8994_AIF1_CONTROL_1); + if ((val & WM8994_AIF1ADCL_SRC) && + (val & WM8994_AIF1ADCR_SRC)) + adc = WM8994_AIF1ADC1R_ENA | WM8994_AIF1ADC2R_ENA; + else if (!(val & WM8994_AIF1ADCL_SRC) && + !(val & WM8994_AIF1ADCR_SRC)) + adc = WM8994_AIF1ADC1L_ENA | WM8994_AIF1ADC2L_ENA; + else + adc = WM8994_AIF1ADC1R_ENA | WM8994_AIF1ADC2R_ENA | + WM8994_AIF1ADC1L_ENA | WM8994_AIF1ADC2L_ENA; + + val = snd_soc_component_read32(component, WM8994_AIF1_CONTROL_2); + if ((val & WM8994_AIF1DACL_SRC) && + (val & WM8994_AIF1DACR_SRC)) + dac = WM8994_AIF1DAC1R_ENA | WM8994_AIF1DAC2R_ENA; + else if (!(val & WM8994_AIF1DACL_SRC) && + !(val & WM8994_AIF1DACR_SRC)) + dac = WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC2L_ENA; + else + dac = WM8994_AIF1DAC1R_ENA | WM8994_AIF1DAC2R_ENA | + WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC2L_ENA; + + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_4, + mask, adc); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_5, + mask, dac); + snd_soc_component_update_bits(component, WM8994_CLOCKING_1, + WM8994_AIF1DSPCLK_ENA | + WM8994_SYSDSPCLK_ENA, + WM8994_AIF1DSPCLK_ENA | + WM8994_SYSDSPCLK_ENA); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_4, mask, + WM8994_AIF1ADC1R_ENA | + WM8994_AIF1ADC1L_ENA | + WM8994_AIF1ADC2R_ENA | + WM8994_AIF1ADC2L_ENA); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_5, mask, + WM8994_AIF1DAC1R_ENA | + WM8994_AIF1DAC1L_ENA | + WM8994_AIF1DAC2R_ENA | + WM8994_AIF1DAC2L_ENA); + break; + + case SND_SOC_DAPM_POST_PMU: + for (i = 0; i < ARRAY_SIZE(wm8994_vu_bits); i++) + snd_soc_component_write(component, wm8994_vu_bits[i].reg, + snd_soc_component_read32(component, + wm8994_vu_bits[i].reg)); + break; + + case SND_SOC_DAPM_PRE_PMD: + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_5, + mask, 0); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_4, + mask, 0); + + val = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (val & WM8994_AIF2DSPCLK_ENA) + val = WM8994_SYSDSPCLK_ENA; + else + val = 0; + snd_soc_component_update_bits(component, WM8994_CLOCKING_1, + WM8994_SYSDSPCLK_ENA | + WM8994_AIF1DSPCLK_ENA, val); + break; + } + + return 0; +} + +static int aif2clk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int i; + int dac; + int adc; + int val; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + val = snd_soc_component_read32(component, WM8994_AIF2_CONTROL_1); + if ((val & WM8994_AIF2ADCL_SRC) && + (val & WM8994_AIF2ADCR_SRC)) + adc = WM8994_AIF2ADCR_ENA; + else if (!(val & WM8994_AIF2ADCL_SRC) && + !(val & WM8994_AIF2ADCR_SRC)) + adc = WM8994_AIF2ADCL_ENA; + else + adc = WM8994_AIF2ADCL_ENA | WM8994_AIF2ADCR_ENA; + + + val = snd_soc_component_read32(component, WM8994_AIF2_CONTROL_2); + if ((val & WM8994_AIF2DACL_SRC) && + (val & WM8994_AIF2DACR_SRC)) + dac = WM8994_AIF2DACR_ENA; + else if (!(val & WM8994_AIF2DACL_SRC) && + !(val & WM8994_AIF2DACR_SRC)) + dac = WM8994_AIF2DACL_ENA; + else + dac = WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA; + + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_4, + WM8994_AIF2ADCL_ENA | + WM8994_AIF2ADCR_ENA, adc); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_5, + WM8994_AIF2DACL_ENA | + WM8994_AIF2DACR_ENA, dac); + snd_soc_component_update_bits(component, WM8994_CLOCKING_1, + WM8994_AIF2DSPCLK_ENA | + WM8994_SYSDSPCLK_ENA, + WM8994_AIF2DSPCLK_ENA | + WM8994_SYSDSPCLK_ENA); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_4, + WM8994_AIF2ADCL_ENA | + WM8994_AIF2ADCR_ENA, + WM8994_AIF2ADCL_ENA | + WM8994_AIF2ADCR_ENA); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_5, + WM8994_AIF2DACL_ENA | + WM8994_AIF2DACR_ENA, + WM8994_AIF2DACL_ENA | + WM8994_AIF2DACR_ENA); + break; + + case SND_SOC_DAPM_POST_PMU: + for (i = 0; i < ARRAY_SIZE(wm8994_vu_bits); i++) + snd_soc_component_write(component, wm8994_vu_bits[i].reg, + snd_soc_component_read32(component, + wm8994_vu_bits[i].reg)); + break; + + case SND_SOC_DAPM_PRE_PMD: + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_5, + WM8994_AIF2DACL_ENA | + WM8994_AIF2DACR_ENA, 0); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_4, + WM8994_AIF2ADCL_ENA | + WM8994_AIF2ADCR_ENA, 0); + + val = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (val & WM8994_AIF1DSPCLK_ENA) + val = WM8994_SYSDSPCLK_ENA; + else + val = 0; + snd_soc_component_update_bits(component, WM8994_CLOCKING_1, + WM8994_SYSDSPCLK_ENA | + WM8994_AIF2DSPCLK_ENA, val); + break; + } + + return 0; +} + +static int aif1clk_late_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wm8994->aif1clk_enable = 1; + break; + case SND_SOC_DAPM_POST_PMD: + wm8994->aif1clk_disable = 1; + break; + } + + return 0; +} + +static int aif2clk_late_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wm8994->aif2clk_enable = 1; + break; + case SND_SOC_DAPM_POST_PMD: + wm8994->aif2clk_disable = 1; + break; + } + + return 0; +} + +static int late_enable_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (wm8994->aif1clk_enable) { + aif1clk_ev(w, kcontrol, SND_SOC_DAPM_PRE_PMU); + snd_soc_component_update_bits(component, WM8994_AIF1_CLOCKING_1, + WM8994_AIF1CLK_ENA_MASK, + WM8994_AIF1CLK_ENA); + aif1clk_ev(w, kcontrol, SND_SOC_DAPM_POST_PMU); + wm8994->aif1clk_enable = 0; + } + if (wm8994->aif2clk_enable) { + aif2clk_ev(w, kcontrol, SND_SOC_DAPM_PRE_PMU); + snd_soc_component_update_bits(component, WM8994_AIF2_CLOCKING_1, + WM8994_AIF2CLK_ENA_MASK, + WM8994_AIF2CLK_ENA); + aif2clk_ev(w, kcontrol, SND_SOC_DAPM_POST_PMU); + wm8994->aif2clk_enable = 0; + } + break; + } + + /* We may also have postponed startup of DSP, handle that. */ + wm8958_aif_ev(w, kcontrol, event); + + return 0; +} + +static int late_disable_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMD: + if (wm8994->aif1clk_disable) { + aif1clk_ev(w, kcontrol, SND_SOC_DAPM_PRE_PMD); + snd_soc_component_update_bits(component, WM8994_AIF1_CLOCKING_1, + WM8994_AIF1CLK_ENA_MASK, 0); + aif1clk_ev(w, kcontrol, SND_SOC_DAPM_POST_PMD); + wm8994->aif1clk_disable = 0; + } + if (wm8994->aif2clk_disable) { + aif2clk_ev(w, kcontrol, SND_SOC_DAPM_PRE_PMD); + snd_soc_component_update_bits(component, WM8994_AIF2_CLOCKING_1, + WM8994_AIF2CLK_ENA_MASK, 0); + aif2clk_ev(w, kcontrol, SND_SOC_DAPM_POST_PMD); + wm8994->aif2clk_disable = 0; + } + break; + } + + return 0; +} + +static int adc_mux_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + late_enable_ev(w, kcontrol, event); + return 0; +} + +static int micbias_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + late_enable_ev(w, kcontrol, event); + return 0; +} + +static int dac_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int mask = 1 << w->shift; + + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_5, + mask, mask); + return 0; +} + +static const char *adc_mux_text[] = { + "ADC", + "DMIC", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(adc_enum, adc_mux_text); + +static const struct snd_kcontrol_new adcl_mux = + SOC_DAPM_ENUM("ADCL Mux", adc_enum); + +static const struct snd_kcontrol_new adcr_mux = + SOC_DAPM_ENUM("ADCR Mux", adc_enum); + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0), +SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0), +SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 8, 1, 0), +SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1RP Switch", WM8994_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 0, 1, 0), +}; + +/* Debugging; dump chip status after DAPM transitions */ +static int post_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + dev_dbg(component->dev, "SRC status: %x\n", + snd_soc_component_read32(component, + WM8994_RATE_STATUS)); + return 0; +} + +static const struct snd_kcontrol_new aif1adc1l_mix[] = { +SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc1r_mix[] = { +SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc2l_mix[] = { +SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc2r_mix[] = { +SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif2dac2l_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 2, 1, 0), +SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif2dac2r_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 2, 1, 0), +SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +#define WM8994_CLASS_W_SWITCH(xname, reg, shift, max, invert) \ + SOC_SINGLE_EXT(xname, reg, shift, max, invert, \ + snd_soc_dapm_get_volsw, wm8994_put_class_w) + +static int wm8994_put_class_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + int ret; + + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + wm_hubs_update_class_w(component); + + return ret; +} + +static const struct snd_kcontrol_new dac1l_mix[] = { +WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 5, 1, 0), +WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 4, 1, 0), +WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 2, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 1, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new dac1r_mix[] = { +WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 5, 1, 0), +WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 4, 1, 0), +WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 2, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 1, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const char *sidetone_text[] = { + "ADC/DMIC1", "DMIC2", +}; + +static SOC_ENUM_SINGLE_DECL(sidetone1_enum, + WM8994_SIDETONE, 0, sidetone_text); + +static const struct snd_kcontrol_new sidetone1_mux = + SOC_DAPM_ENUM("Left Sidetone Mux", sidetone1_enum); + +static SOC_ENUM_SINGLE_DECL(sidetone2_enum, + WM8994_SIDETONE, 1, sidetone_text); + +static const struct snd_kcontrol_new sidetone2_mux = + SOC_DAPM_ENUM("Right Sidetone Mux", sidetone2_enum); + +static const char *aif1dac_text[] = { + "AIF1DACDAT", "AIF3DACDAT", +}; + +static const char *loopback_text[] = { + "None", "ADCDAT", +}; + +static SOC_ENUM_SINGLE_DECL(aif1_loopback_enum, + WM8994_AIF1_CONTROL_2, + WM8994_AIF1_LOOPBACK_SHIFT, + loopback_text); + +static const struct snd_kcontrol_new aif1_loopback = + SOC_DAPM_ENUM("AIF1 Loopback", aif1_loopback_enum); + +static SOC_ENUM_SINGLE_DECL(aif2_loopback_enum, + WM8994_AIF2_CONTROL_2, + WM8994_AIF2_LOOPBACK_SHIFT, + loopback_text); + +static const struct snd_kcontrol_new aif2_loopback = + SOC_DAPM_ENUM("AIF2 Loopback", aif2_loopback_enum); + +static SOC_ENUM_SINGLE_DECL(aif1dac_enum, + WM8994_POWER_MANAGEMENT_6, 0, aif1dac_text); + +static const struct snd_kcontrol_new aif1dac_mux = + SOC_DAPM_ENUM("AIF1DAC Mux", aif1dac_enum); + +static const char *aif2dac_text[] = { + "AIF2DACDAT", "AIF3DACDAT", +}; + +static SOC_ENUM_SINGLE_DECL(aif2dac_enum, + WM8994_POWER_MANAGEMENT_6, 1, aif2dac_text); + +static const struct snd_kcontrol_new aif2dac_mux = + SOC_DAPM_ENUM("AIF2DAC Mux", aif2dac_enum); + +static const char *aif2adc_text[] = { + "AIF2ADCDAT", "AIF3DACDAT", +}; + +static SOC_ENUM_SINGLE_DECL(aif2adc_enum, + WM8994_POWER_MANAGEMENT_6, 2, aif2adc_text); + +static const struct snd_kcontrol_new aif2adc_mux = + SOC_DAPM_ENUM("AIF2ADC Mux", aif2adc_enum); + +static const char *aif3adc_text[] = { + "AIF1ADCDAT", "AIF2ADCDAT", "AIF2DACDAT", "Mono PCM", +}; + +static SOC_ENUM_SINGLE_DECL(wm8994_aif3adc_enum, + WM8994_POWER_MANAGEMENT_6, 3, aif3adc_text); + +static const struct snd_kcontrol_new wm8994_aif3adc_mux = + SOC_DAPM_ENUM("AIF3ADC Mux", wm8994_aif3adc_enum); + +static SOC_ENUM_SINGLE_DECL(wm8958_aif3adc_enum, + WM8994_POWER_MANAGEMENT_6, 3, aif3adc_text); + +static const struct snd_kcontrol_new wm8958_aif3adc_mux = + SOC_DAPM_ENUM("AIF3ADC Mux", wm8958_aif3adc_enum); + +static const char *mono_pcm_out_text[] = { + "None", "AIF2ADCL", "AIF2ADCR", +}; + +static SOC_ENUM_SINGLE_DECL(mono_pcm_out_enum, + WM8994_POWER_MANAGEMENT_6, 9, mono_pcm_out_text); + +static const struct snd_kcontrol_new mono_pcm_out_mux = + SOC_DAPM_ENUM("Mono PCM Out Mux", mono_pcm_out_enum); + +static const char *aif2dac_src_text[] = { + "AIF2", "AIF3", +}; + +/* Note that these two control shouldn't be simultaneously switched to AIF3 */ +static SOC_ENUM_SINGLE_DECL(aif2dacl_src_enum, + WM8994_POWER_MANAGEMENT_6, 7, aif2dac_src_text); + +static const struct snd_kcontrol_new aif2dacl_src_mux = + SOC_DAPM_ENUM("AIF2DACL Mux", aif2dacl_src_enum); + +static SOC_ENUM_SINGLE_DECL(aif2dacr_src_enum, + WM8994_POWER_MANAGEMENT_6, 8, aif2dac_src_text); + +static const struct snd_kcontrol_new aif2dacr_src_mux = + SOC_DAPM_ENUM("AIF2DACR Mux", aif2dacr_src_enum); + +static const struct snd_soc_dapm_widget wm8994_lateclk_revd_widgets[] = { +SND_SOC_DAPM_SUPPLY("AIF1CLK", SND_SOC_NOPM, 0, 0, aif1clk_late_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("AIF2CLK", SND_SOC_NOPM, 0, 0, aif2clk_late_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA_E("Late DAC1L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Late DAC1R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), + +SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer), + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer), + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), + +SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev) +}; + +static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = { +SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, aif1clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, aif2clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux), +}; + +static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = { +SND_SOC_DAPM_DAC_E("DAC2L", NULL, SND_SOC_NOPM, 3, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_DAC_E("DAC2R", NULL, SND_SOC_NOPM, 2, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_DAC_E("DAC1L", NULL, SND_SOC_NOPM, 1, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_DAC_E("DAC1R", NULL, SND_SOC_NOPM, 0, 0, + dac_ev, SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_widget wm8994_dac_widgets[] = { +SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0), +SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0), +SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0), +SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0), +}; + +static const struct snd_soc_dapm_widget wm8994_adc_revd_widgets[] = { +SND_SOC_DAPM_MUX_E("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux, + adc_mux_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MUX_E("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux, + adc_mux_ev, SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_widget wm8994_adc_widgets[] = { +SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux), +SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux), +}; + +static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("DMIC1DAT"), +SND_SOC_DAPM_INPUT("DMIC2DAT"), +SND_SOC_DAPM_INPUT("Clock"), + +SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev, + SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, vmid_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_SUPPLY("DSP1CLK", SND_SOC_NOPM, 3, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("DSP2CLK", SND_SOC_NOPM, 2, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("DSPINTCLK", SND_SOC_NOPM, 1, 0, NULL, 0), + +SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", NULL, + 0, SND_SOC_NOPM, 9, 0), +SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", NULL, + 0, SND_SOC_NOPM, 8, 0), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC1L", NULL, 0, + SND_SOC_NOPM, 9, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC1R", NULL, 0, + SND_SOC_NOPM, 8, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", NULL, + 0, SND_SOC_NOPM, 11, 0), +SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", NULL, + 0, SND_SOC_NOPM, 10, 0), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC2L", NULL, 0, + SND_SOC_NOPM, 11, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC2R", NULL, 0, + SND_SOC_NOPM, 10, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0, + aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)), +SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0, + aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)), + +SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0, + aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)), +SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0, + aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)), + +SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0, + aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)), +SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0, + aif2dac2r_mix, ARRAY_SIZE(aif2dac2r_mix)), + +SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &sidetone1_mux), +SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &sidetone2_mux), + +SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0, + dac1l_mix, ARRAY_SIZE(dac1l_mix)), +SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0, + dac1r_mix, ARRAY_SIZE(dac1r_mix)), + +SND_SOC_DAPM_AIF_OUT("AIF2ADCL", NULL, 0, + SND_SOC_NOPM, 13, 0), +SND_SOC_DAPM_AIF_OUT("AIF2ADCR", NULL, 0, + SND_SOC_NOPM, 12, 0), +SND_SOC_DAPM_AIF_IN_E("AIF2DACL", NULL, 0, + SND_SOC_NOPM, 13, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_AIF_IN_E("AIF2DACR", NULL, 0, + SND_SOC_NOPM, 12, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_AIF_IN("AIF1DACDAT", NULL, 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIF2DACDAT", NULL, 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIF1ADCDAT", NULL, 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", NULL, 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("AIF1DAC Mux", SND_SOC_NOPM, 0, 0, &aif1dac_mux), +SND_SOC_DAPM_MUX("AIF2DAC Mux", SND_SOC_NOPM, 0, 0, &aif2dac_mux), +SND_SOC_DAPM_MUX("AIF2ADC Mux", SND_SOC_NOPM, 0, 0, &aif2adc_mux), + +SND_SOC_DAPM_AIF_IN("AIF3DACDAT", NULL, 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIF3ADCDAT", NULL, 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_SUPPLY("TOCLK", WM8994_CLOCKING_1, 4, 0, NULL, 0), + +SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8994_POWER_MANAGEMENT_4, 5, 0), +SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8994_POWER_MANAGEMENT_4, 4, 0), +SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8994_POWER_MANAGEMENT_4, 3, 0), +SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0), + +/* Power is done with the muxes since the ADC power also controls the + * downsampling chain, the chip will automatically manage the analogue + * specific portions. + */ +SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("AIF1 Loopback", SND_SOC_NOPM, 0, 0, &aif1_loopback), +SND_SOC_DAPM_MUX("AIF2 Loopback", SND_SOC_NOPM, 0, 0, &aif2_loopback), + +SND_SOC_DAPM_POST("Debug log", post_ev), +}; + +static const struct snd_soc_dapm_widget wm8994_specific_dapm_widgets[] = { +SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8994_aif3adc_mux), +}; + +static const struct snd_soc_dapm_widget wm8958_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("AIF3", WM8994_POWER_MANAGEMENT_6, 5, 1, NULL, 0), +SND_SOC_DAPM_MUX("Mono PCM Out Mux", SND_SOC_NOPM, 0, 0, &mono_pcm_out_mux), +SND_SOC_DAPM_MUX("AIF2DACL Mux", SND_SOC_NOPM, 0, 0, &aif2dacl_src_mux), +SND_SOC_DAPM_MUX("AIF2DACR Mux", SND_SOC_NOPM, 0, 0, &aif2dacr_src_mux), +SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8958_aif3adc_mux), +}; + +static const struct snd_soc_dapm_route intercon[] = { + { "CLK_SYS", NULL, "AIF1CLK", check_clk_sys }, + { "CLK_SYS", NULL, "AIF2CLK", check_clk_sys }, + + { "DSP1CLK", NULL, "CLK_SYS" }, + { "DSP2CLK", NULL, "CLK_SYS" }, + { "DSPINTCLK", NULL, "CLK_SYS" }, + + { "AIF1ADC1L", NULL, "AIF1CLK" }, + { "AIF1ADC1L", NULL, "DSP1CLK" }, + { "AIF1ADC1R", NULL, "AIF1CLK" }, + { "AIF1ADC1R", NULL, "DSP1CLK" }, + { "AIF1ADC1R", NULL, "DSPINTCLK" }, + + { "AIF1DAC1L", NULL, "AIF1CLK" }, + { "AIF1DAC1L", NULL, "DSP1CLK" }, + { "AIF1DAC1R", NULL, "AIF1CLK" }, + { "AIF1DAC1R", NULL, "DSP1CLK" }, + { "AIF1DAC1R", NULL, "DSPINTCLK" }, + + { "AIF1ADC2L", NULL, "AIF1CLK" }, + { "AIF1ADC2L", NULL, "DSP1CLK" }, + { "AIF1ADC2R", NULL, "AIF1CLK" }, + { "AIF1ADC2R", NULL, "DSP1CLK" }, + { "AIF1ADC2R", NULL, "DSPINTCLK" }, + + { "AIF1DAC2L", NULL, "AIF1CLK" }, + { "AIF1DAC2L", NULL, "DSP1CLK" }, + { "AIF1DAC2R", NULL, "AIF1CLK" }, + { "AIF1DAC2R", NULL, "DSP1CLK" }, + { "AIF1DAC2R", NULL, "DSPINTCLK" }, + + { "AIF2ADCL", NULL, "AIF2CLK" }, + { "AIF2ADCL", NULL, "DSP2CLK" }, + { "AIF2ADCR", NULL, "AIF2CLK" }, + { "AIF2ADCR", NULL, "DSP2CLK" }, + { "AIF2ADCR", NULL, "DSPINTCLK" }, + + { "AIF2DACL", NULL, "AIF2CLK" }, + { "AIF2DACL", NULL, "DSP2CLK" }, + { "AIF2DACR", NULL, "AIF2CLK" }, + { "AIF2DACR", NULL, "DSP2CLK" }, + { "AIF2DACR", NULL, "DSPINTCLK" }, + + { "DMIC1L", NULL, "DMIC1DAT" }, + { "DMIC1L", NULL, "CLK_SYS" }, + { "DMIC1R", NULL, "DMIC1DAT" }, + { "DMIC1R", NULL, "CLK_SYS" }, + { "DMIC2L", NULL, "DMIC2DAT" }, + { "DMIC2L", NULL, "CLK_SYS" }, + { "DMIC2R", NULL, "DMIC2DAT" }, + { "DMIC2R", NULL, "CLK_SYS" }, + + { "ADCL", NULL, "AIF1CLK" }, + { "ADCL", NULL, "DSP1CLK" }, + { "ADCL", NULL, "DSPINTCLK" }, + + { "ADCR", NULL, "AIF1CLK" }, + { "ADCR", NULL, "DSP1CLK" }, + { "ADCR", NULL, "DSPINTCLK" }, + + { "ADCL Mux", "ADC", "ADCL" }, + { "ADCL Mux", "DMIC", "DMIC1L" }, + { "ADCR Mux", "ADC", "ADCR" }, + { "ADCR Mux", "DMIC", "DMIC1R" }, + + { "DAC1L", NULL, "AIF1CLK" }, + { "DAC1L", NULL, "DSP1CLK" }, + { "DAC1L", NULL, "DSPINTCLK" }, + + { "DAC1R", NULL, "AIF1CLK" }, + { "DAC1R", NULL, "DSP1CLK" }, + { "DAC1R", NULL, "DSPINTCLK" }, + + { "DAC2L", NULL, "AIF2CLK" }, + { "DAC2L", NULL, "DSP2CLK" }, + { "DAC2L", NULL, "DSPINTCLK" }, + + { "DAC2R", NULL, "AIF2DACR" }, + { "DAC2R", NULL, "AIF2CLK" }, + { "DAC2R", NULL, "DSP2CLK" }, + { "DAC2R", NULL, "DSPINTCLK" }, + + { "TOCLK", NULL, "CLK_SYS" }, + + { "AIF1DACDAT", NULL, "AIF1 Playback" }, + { "AIF2DACDAT", NULL, "AIF2 Playback" }, + { "AIF3DACDAT", NULL, "AIF3 Playback" }, + + { "AIF1 Capture", NULL, "AIF1ADCDAT" }, + { "AIF2 Capture", NULL, "AIF2ADCDAT" }, + { "AIF3 Capture", NULL, "AIF3ADCDAT" }, + + /* AIF1 outputs */ + { "AIF1ADC1L", NULL, "AIF1ADC1L Mixer" }, + { "AIF1ADC1L Mixer", "ADC/DMIC Switch", "ADCL Mux" }, + { "AIF1ADC1L Mixer", "AIF2 Switch", "AIF2DACL" }, + + { "AIF1ADC1R", NULL, "AIF1ADC1R Mixer" }, + { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" }, + { "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" }, + + { "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" }, + { "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" }, + { "AIF1ADC2L Mixer", "AIF2 Switch", "AIF2DACL" }, + + { "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" }, + { "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" }, + { "AIF1ADC2R Mixer", "AIF2 Switch", "AIF2DACR" }, + + /* Pin level routing for AIF3 */ + { "AIF1DAC1L", NULL, "AIF1DAC Mux" }, + { "AIF1DAC1R", NULL, "AIF1DAC Mux" }, + { "AIF1DAC2L", NULL, "AIF1DAC Mux" }, + { "AIF1DAC2R", NULL, "AIF1DAC Mux" }, + + { "AIF1DAC Mux", "AIF1DACDAT", "AIF1 Loopback" }, + { "AIF1DAC Mux", "AIF3DACDAT", "AIF3DACDAT" }, + { "AIF2DAC Mux", "AIF2DACDAT", "AIF2 Loopback" }, + { "AIF2DAC Mux", "AIF3DACDAT", "AIF3DACDAT" }, + { "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCL" }, + { "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCR" }, + { "AIF2ADC Mux", "AIF3DACDAT", "AIF3ADCDAT" }, + + /* DAC1 inputs */ + { "DAC1L Mixer", "AIF2 Switch", "AIF2DACL" }, + { "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, + { "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, + { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + { "DAC1R Mixer", "AIF2 Switch", "AIF2DACR" }, + { "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, + { "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, + { "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + /* DAC2/AIF2 outputs */ + { "AIF2ADCL", NULL, "AIF2DAC2L Mixer" }, + { "AIF2DAC2L Mixer", "AIF2 Switch", "AIF2DACL" }, + { "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, + { "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, + { "AIF2DAC2L Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "AIF2DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + { "AIF2ADCR", NULL, "AIF2DAC2R Mixer" }, + { "AIF2DAC2R Mixer", "AIF2 Switch", "AIF2DACR" }, + { "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, + { "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, + { "AIF2DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "AIF2DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + { "AIF1ADCDAT", NULL, "AIF1ADC1L" }, + { "AIF1ADCDAT", NULL, "AIF1ADC1R" }, + { "AIF1ADCDAT", NULL, "AIF1ADC2L" }, + { "AIF1ADCDAT", NULL, "AIF1ADC2R" }, + + { "AIF2ADCDAT", NULL, "AIF2ADC Mux" }, + + /* AIF3 output */ + { "AIF3ADC Mux", "AIF1ADCDAT", "AIF1ADC1L" }, + { "AIF3ADC Mux", "AIF1ADCDAT", "AIF1ADC1R" }, + { "AIF3ADC Mux", "AIF1ADCDAT", "AIF1ADC2L" }, + { "AIF3ADC Mux", "AIF1ADCDAT", "AIF1ADC2R" }, + { "AIF3ADC Mux", "AIF2ADCDAT", "AIF2ADCL" }, + { "AIF3ADC Mux", "AIF2ADCDAT", "AIF2ADCR" }, + { "AIF3ADC Mux", "AIF2DACDAT", "AIF2DACL" }, + { "AIF3ADC Mux", "AIF2DACDAT", "AIF2DACR" }, + + { "AIF3ADCDAT", NULL, "AIF3ADC Mux" }, + + /* Loopback */ + { "AIF1 Loopback", "ADCDAT", "AIF1ADCDAT" }, + { "AIF1 Loopback", "None", "AIF1DACDAT" }, + { "AIF2 Loopback", "ADCDAT", "AIF2ADCDAT" }, + { "AIF2 Loopback", "None", "AIF2DACDAT" }, + + /* Sidetone */ + { "Left Sidetone", "ADC/DMIC1", "ADCL Mux" }, + { "Left Sidetone", "DMIC2", "DMIC2L" }, + { "Right Sidetone", "ADC/DMIC1", "ADCR Mux" }, + { "Right Sidetone", "DMIC2", "DMIC2R" }, + + /* Output stages */ + { "Left Output Mixer", "DAC Switch", "DAC1L" }, + { "Right Output Mixer", "DAC Switch", "DAC1R" }, + + { "SPKL", "DAC1 Switch", "DAC1L" }, + { "SPKL", "DAC2 Switch", "DAC2L" }, + + { "SPKR", "DAC1 Switch", "DAC1R" }, + { "SPKR", "DAC2 Switch", "DAC2R" }, + + { "Left Headphone Mux", "DAC", "DAC1L" }, + { "Right Headphone Mux", "DAC", "DAC1R" }, +}; + +static const struct snd_soc_dapm_route wm8994_lateclk_revd_intercon[] = { + { "DAC1L", NULL, "Late DAC1L Enable PGA" }, + { "Late DAC1L Enable PGA", NULL, "DAC1L Mixer" }, + { "DAC1R", NULL, "Late DAC1R Enable PGA" }, + { "Late DAC1R Enable PGA", NULL, "DAC1R Mixer" }, + { "DAC2L", NULL, "Late DAC2L Enable PGA" }, + { "Late DAC2L Enable PGA", NULL, "AIF2DAC2L Mixer" }, + { "DAC2R", NULL, "Late DAC2R Enable PGA" }, + { "Late DAC2R Enable PGA", NULL, "AIF2DAC2R Mixer" } +}; + +static const struct snd_soc_dapm_route wm8994_lateclk_intercon[] = { + { "DAC1L", NULL, "DAC1L Mixer" }, + { "DAC1R", NULL, "DAC1R Mixer" }, + { "DAC2L", NULL, "AIF2DAC2L Mixer" }, + { "DAC2R", NULL, "AIF2DAC2R Mixer" }, +}; + +static const struct snd_soc_dapm_route wm8994_revd_intercon[] = { + { "AIF1DACDAT", NULL, "AIF2DACDAT" }, + { "AIF2DACDAT", NULL, "AIF1DACDAT" }, + { "AIF1ADCDAT", NULL, "AIF2ADCDAT" }, + { "AIF2ADCDAT", NULL, "AIF1ADCDAT" }, + { "MICBIAS1", NULL, "CLK_SYS" }, + { "MICBIAS1", NULL, "MICBIAS Supply" }, + { "MICBIAS2", NULL, "CLK_SYS" }, + { "MICBIAS2", NULL, "MICBIAS Supply" }, +}; + +static const struct snd_soc_dapm_route wm8994_intercon[] = { + { "AIF2DACL", NULL, "AIF2DAC Mux" }, + { "AIF2DACR", NULL, "AIF2DAC Mux" }, + { "MICBIAS1", NULL, "VMID" }, + { "MICBIAS2", NULL, "VMID" }, +}; + +static const struct snd_soc_dapm_route wm8958_intercon[] = { + { "AIF2DACL", NULL, "AIF2DACL Mux" }, + { "AIF2DACR", NULL, "AIF2DACR Mux" }, + + { "AIF2DACL Mux", "AIF2", "AIF2DAC Mux" }, + { "AIF2DACL Mux", "AIF3", "AIF3DACDAT" }, + { "AIF2DACR Mux", "AIF2", "AIF2DAC Mux" }, + { "AIF2DACR Mux", "AIF3", "AIF3DACDAT" }, + + { "AIF3DACDAT", NULL, "AIF3" }, + { "AIF3ADCDAT", NULL, "AIF3" }, + + { "Mono PCM Out Mux", "AIF2ADCL", "AIF2ADCL" }, + { "Mono PCM Out Mux", "AIF2ADCR", "AIF2ADCR" }, + + { "AIF3ADC Mux", "Mono PCM", "Mono PCM Out Mux" }, +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +struct fll_div { + u16 outdiv; + u16 n; + u16 k; + u16 lambda; + u16 clk_ref_div; + u16 fll_fratio; +}; + +static int wm8994_get_fll_config(struct wm8994 *control, struct fll_div *fll, + int freq_in, int freq_out) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, gcd_fll; + + pr_debug("FLL input=%dHz, output=%dHz\n", freq_in, freq_out); + + /* Scale the input frequency down to <= 13.5MHz */ + fll->clk_ref_div = 0; + while (freq_in > 13500000) { + fll->clk_ref_div++; + freq_in /= 2; + + if (fll->clk_ref_div > 3) + return -EINVAL; + } + pr_debug("CLK_REF_DIV=%d, Fref=%dHz\n", fll->clk_ref_div, freq_in); + + /* Scale the output to give 90MHz<=Fvco<=100MHz */ + fll->outdiv = 3; + while (freq_out * (fll->outdiv + 1) < 90000000) { + fll->outdiv++; + if (fll->outdiv > 63) + return -EINVAL; + } + freq_out *= fll->outdiv + 1; + pr_debug("OUTDIV=%d, Fvco=%dHz\n", fll->outdiv, freq_out); + + if (freq_in > 1000000) { + fll->fll_fratio = 0; + } else if (freq_in > 256000) { + fll->fll_fratio = 1; + freq_in *= 2; + } else if (freq_in > 128000) { + fll->fll_fratio = 2; + freq_in *= 4; + } else if (freq_in > 64000) { + fll->fll_fratio = 3; + freq_in *= 8; + } else { + fll->fll_fratio = 4; + freq_in *= 16; + } + pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in); + + /* Now, calculate N.K */ + Ndiv = freq_out / freq_in; + + fll->n = Ndiv; + Nmod = freq_out % freq_in; + pr_debug("Nmod=%d\n", Nmod); + + switch (control->type) { + case WM8994: + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, freq_in); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll->k = K / 10; + fll->lambda = 0; + + pr_debug("N=%x K=%x\n", fll->n, fll->k); + break; + + default: + gcd_fll = gcd(freq_out, freq_in); + + fll->k = (freq_out - (freq_in * fll->n)) / gcd_fll; + fll->lambda = freq_in / gcd_fll; + + } + + return 0; +} + +static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, + unsigned int freq_in, unsigned int freq_out) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int reg_offset, ret; + struct fll_div fll; + u16 reg, clk1, aif_reg, aif_src; + unsigned long timeout; + bool was_enabled; + + switch (id) { + case WM8994_FLL1: + reg_offset = 0; + id = 0; + aif_src = 0x10; + break; + case WM8994_FLL2: + reg_offset = 0x20; + id = 1; + aif_src = 0x18; + break; + default: + return -EINVAL; + } + + reg = snd_soc_component_read32(component, WM8994_FLL1_CONTROL_1 + reg_offset); + was_enabled = reg & WM8994_FLL1_ENA; + + switch (src) { + case 0: + /* Allow no source specification when stopping */ + if (freq_out) + return -EINVAL; + src = wm8994->fll[id].src; + break; + case WM8994_FLL_SRC_MCLK1: + case WM8994_FLL_SRC_MCLK2: + case WM8994_FLL_SRC_LRCLK: + case WM8994_FLL_SRC_BCLK: + break; + case WM8994_FLL_SRC_INTERNAL: + freq_in = 12000000; + freq_out = 12000000; + break; + default: + return -EINVAL; + } + + /* Are we changing anything? */ + if (wm8994->fll[id].src == src && + wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out) + return 0; + + /* If we're stopping the FLL redo the old config - no + * registers will actually be written but we avoid GCC flow + * analysis bugs spewing warnings. + */ + if (freq_out) + ret = wm8994_get_fll_config(control, &fll, freq_in, freq_out); + else + ret = wm8994_get_fll_config(control, &fll, wm8994->fll[id].in, + wm8994->fll[id].out); + if (ret < 0) + return ret; + + /* Make sure that we're not providing SYSCLK right now */ + clk1 = snd_soc_component_read32(component, WM8994_CLOCKING_1); + if (clk1 & WM8994_SYSCLK_SRC) + aif_reg = WM8994_AIF2_CLOCKING_1; + else + aif_reg = WM8994_AIF1_CLOCKING_1; + reg = snd_soc_component_read32(component, aif_reg); + + if ((reg & WM8994_AIF1CLK_ENA) && + (reg & WM8994_AIF1CLK_SRC_MASK) == aif_src) { + dev_err(component->dev, "FLL%d is currently providing SYSCLK\n", + id + 1); + return -EBUSY; + } + + /* We always need to disable the FLL while reconfiguring */ + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_1 + reg_offset, + WM8994_FLL1_ENA, 0); + + if (wm8994->fll_byp && src == WM8994_FLL_SRC_BCLK && + freq_in == freq_out && freq_out) { + dev_dbg(component->dev, "Bypassing FLL%d\n", id + 1); + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_5 + reg_offset, + WM8958_FLL1_BYP, WM8958_FLL1_BYP); + goto out; + } + + reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) | + (fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT); + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_2 + reg_offset, + WM8994_FLL1_OUTDIV_MASK | + WM8994_FLL1_FRATIO_MASK, reg); + + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_3 + reg_offset, + WM8994_FLL1_K_MASK, fll.k); + + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_4 + reg_offset, + WM8994_FLL1_N_MASK, + fll.n << WM8994_FLL1_N_SHIFT); + + if (fll.lambda) { + snd_soc_component_update_bits(component, WM8958_FLL1_EFS_1 + reg_offset, + WM8958_FLL1_LAMBDA_MASK, + fll.lambda); + snd_soc_component_update_bits(component, WM8958_FLL1_EFS_2 + reg_offset, + WM8958_FLL1_EFS_ENA, WM8958_FLL1_EFS_ENA); + } else { + snd_soc_component_update_bits(component, WM8958_FLL1_EFS_2 + reg_offset, + WM8958_FLL1_EFS_ENA, 0); + } + + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_5 + reg_offset, + WM8994_FLL1_FRC_NCO | WM8958_FLL1_BYP | + WM8994_FLL1_REFCLK_DIV_MASK | + WM8994_FLL1_REFCLK_SRC_MASK, + ((src == WM8994_FLL_SRC_INTERNAL) + << WM8994_FLL1_FRC_NCO_SHIFT) | + (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) | + (src - 1)); + + /* Clear any pending completion from a previous failure */ + try_wait_for_completion(&wm8994->fll_locked[id]); + + /* Enable (with fractional mode if required) */ + if (freq_out) { + /* Enable VMID if we need it */ + if (!was_enabled) { + active_reference(component); + + switch (control->type) { + case WM8994: + vmid_reference(component); + break; + case WM8958: + if (control->revision < 1) + vmid_reference(component); + break; + default: + break; + } + } + + reg = WM8994_FLL1_ENA; + + if (fll.k) + reg |= WM8994_FLL1_FRAC; + if (src == WM8994_FLL_SRC_INTERNAL) + reg |= WM8994_FLL1_OSC_ENA; + + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_1 + reg_offset, + WM8994_FLL1_ENA | WM8994_FLL1_OSC_ENA | + WM8994_FLL1_FRAC, reg); + + if (wm8994->fll_locked_irq) { + timeout = wait_for_completion_timeout(&wm8994->fll_locked[id], + msecs_to_jiffies(10)); + if (timeout == 0) + dev_warn(component->dev, + "Timed out waiting for FLL lock\n"); + } else { + msleep(5); + } + } else { + if (was_enabled) { + switch (control->type) { + case WM8994: + vmid_dereference(component); + break; + case WM8958: + if (control->revision < 1) + vmid_dereference(component); + break; + default: + break; + } + + active_dereference(component); + } + } + +out: + wm8994->fll[id].in = freq_in; + wm8994->fll[id].out = freq_out; + wm8994->fll[id].src = src; + + configure_clock(component); + + /* + * If SYSCLK will be less than 50kHz adjust AIFnCLK dividers + * for detection. + */ + if (max(wm8994->aifclk[0], wm8994->aifclk[1]) < 50000) { + dev_dbg(component->dev, "Configuring AIFs for 128fs\n"); + + wm8994->aifdiv[0] = snd_soc_component_read32(component, WM8994_AIF1_RATE) + & WM8994_AIF1CLK_RATE_MASK; + wm8994->aifdiv[1] = snd_soc_component_read32(component, WM8994_AIF2_RATE) + & WM8994_AIF1CLK_RATE_MASK; + + snd_soc_component_update_bits(component, WM8994_AIF1_RATE, + WM8994_AIF1CLK_RATE_MASK, 0x1); + snd_soc_component_update_bits(component, WM8994_AIF2_RATE, + WM8994_AIF2CLK_RATE_MASK, 0x1); + } else if (wm8994->aifdiv[0]) { + snd_soc_component_update_bits(component, WM8994_AIF1_RATE, + WM8994_AIF1CLK_RATE_MASK, + wm8994->aifdiv[0]); + snd_soc_component_update_bits(component, WM8994_AIF2_RATE, + WM8994_AIF2CLK_RATE_MASK, + wm8994->aifdiv[1]); + + wm8994->aifdiv[0] = 0; + wm8994->aifdiv[1] = 0; + } + + return 0; +} + +static irqreturn_t wm8994_fll_locked_irq(int irq, void *data) +{ + struct completion *completion = data; + + complete(completion); + + return IRQ_HANDLED; +} + +static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 }; + +static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, + unsigned int freq_in, unsigned int freq_out) +{ + return _wm8994_set_fll(dai->component, id, src, freq_in, freq_out); +} + +static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int i; + + switch (dai->id) { + case 1: + case 2: + break; + + default: + /* AIF3 shares clocking with AIF1/2 */ + return -EINVAL; + } + + switch (clk_id) { + case WM8994_SYSCLK_MCLK1: + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK1; + wm8994->mclk[0] = freq; + dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n", + dai->id, freq); + break; + + case WM8994_SYSCLK_MCLK2: + /* TODO: Set GPIO AF */ + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK2; + wm8994->mclk[1] = freq; + dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n", + dai->id, freq); + break; + + case WM8994_SYSCLK_FLL1: + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL1; + dev_dbg(dai->dev, "AIF%d using FLL1\n", dai->id); + break; + + case WM8994_SYSCLK_FLL2: + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL2; + dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id); + break; + + case WM8994_SYSCLK_OPCLK: + /* Special case - a division (times 10) is given and + * no effect on main clocking. + */ + if (freq) { + for (i = 0; i < ARRAY_SIZE(opclk_divs); i++) + if (opclk_divs[i] == freq) + break; + if (i == ARRAY_SIZE(opclk_divs)) + return -EINVAL; + snd_soc_component_update_bits(component, WM8994_CLOCKING_2, + WM8994_OPCLK_DIV_MASK, i); + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, WM8994_OPCLK_ENA); + } else { + snd_soc_component_update_bits(component, WM8994_POWER_MANAGEMENT_2, + WM8994_OPCLK_ENA, 0); + } + break; + + default: + return -EINVAL; + } + + configure_clock(component); + + /* + * If SYSCLK will be less than 50kHz adjust AIFnCLK dividers + * for detection. + */ + if (max(wm8994->aifclk[0], wm8994->aifclk[1]) < 50000) { + dev_dbg(component->dev, "Configuring AIFs for 128fs\n"); + + wm8994->aifdiv[0] = snd_soc_component_read32(component, WM8994_AIF1_RATE) + & WM8994_AIF1CLK_RATE_MASK; + wm8994->aifdiv[1] = snd_soc_component_read32(component, WM8994_AIF2_RATE) + & WM8994_AIF1CLK_RATE_MASK; + + snd_soc_component_update_bits(component, WM8994_AIF1_RATE, + WM8994_AIF1CLK_RATE_MASK, 0x1); + snd_soc_component_update_bits(component, WM8994_AIF2_RATE, + WM8994_AIF2CLK_RATE_MASK, 0x1); + } else if (wm8994->aifdiv[0]) { + snd_soc_component_update_bits(component, WM8994_AIF1_RATE, + WM8994_AIF1CLK_RATE_MASK, + wm8994->aifdiv[0]); + snd_soc_component_update_bits(component, WM8994_AIF2_RATE, + WM8994_AIF2CLK_RATE_MASK, + wm8994->aifdiv[1]); + + wm8994->aifdiv[0] = 0; + wm8994->aifdiv[1] = 0; + } + + return 0; +} + +static int wm8994_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + + wm_hubs_set_bias_level(component, level); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* MICBIAS into regulating mode */ + switch (control->type) { + case WM8958: + case WM1811: + snd_soc_component_update_bits(component, WM8958_MICBIAS1, + WM8958_MICB1_MODE, 0); + snd_soc_component_update_bits(component, WM8958_MICBIAS2, + WM8958_MICB2_MODE, 0); + break; + default: + break; + } + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) + active_reference(component); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + switch (control->type) { + case WM8958: + if (control->revision == 0) { + /* Optimise performance for rev A */ + snd_soc_component_update_bits(component, + WM8958_CHARGE_PUMP_2, + WM8958_CP_DISCH, + WM8958_CP_DISCH); + } + break; + + default: + break; + } + + /* Discharge LINEOUT1 & 2 */ + snd_soc_component_update_bits(component, WM8994_ANTIPOP_1, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH); + } + + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_PREPARE) + active_dereference(component); + + /* MICBIAS into bypass mode on newer devices */ + switch (control->type) { + case WM8958: + case WM1811: + snd_soc_component_update_bits(component, WM8958_MICBIAS1, + WM8958_MICB1_MODE, + WM8958_MICB1_MODE); + snd_soc_component_update_bits(component, WM8958_MICBIAS2, + WM8958_MICB2_MODE, + WM8958_MICB2_MODE); + break; + default: + break; + } + break; + + case SND_SOC_BIAS_OFF: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_STANDBY) + wm8994->cur_fw = NULL; + break; + } + + return 0; +} + +int wm8994_vmid_mode(struct snd_soc_component *component, enum wm8994_vmid_mode mode) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + switch (mode) { + case WM8994_VMID_NORMAL: + snd_soc_dapm_mutex_lock(dapm); + + if (wm8994->hubs.lineout1_se) { + snd_soc_dapm_disable_pin_unlocked(dapm, + "LINEOUT1N Driver"); + snd_soc_dapm_disable_pin_unlocked(dapm, + "LINEOUT1P Driver"); + } + if (wm8994->hubs.lineout2_se) { + snd_soc_dapm_disable_pin_unlocked(dapm, + "LINEOUT2N Driver"); + snd_soc_dapm_disable_pin_unlocked(dapm, + "LINEOUT2P Driver"); + } + + /* Do the sync with the old mode to allow it to clean up */ + snd_soc_dapm_sync_unlocked(dapm); + wm8994->vmid_mode = mode; + + snd_soc_dapm_mutex_unlock(dapm); + break; + + case WM8994_VMID_FORCE: + snd_soc_dapm_mutex_lock(dapm); + + if (wm8994->hubs.lineout1_se) { + snd_soc_dapm_force_enable_pin_unlocked(dapm, + "LINEOUT1N Driver"); + snd_soc_dapm_force_enable_pin_unlocked(dapm, + "LINEOUT1P Driver"); + } + if (wm8994->hubs.lineout2_se) { + snd_soc_dapm_force_enable_pin_unlocked(dapm, + "LINEOUT2N Driver"); + snd_soc_dapm_force_enable_pin_unlocked(dapm, + "LINEOUT2P Driver"); + } + + wm8994->vmid_mode = mode; + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int ms_reg; + int aif1_reg; + int dac_reg; + int adc_reg; + int ms = 0; + int aif1 = 0; + int lrclk = 0; + + switch (dai->id) { + case 1: + ms_reg = WM8994_AIF1_MASTER_SLAVE; + aif1_reg = WM8994_AIF1_CONTROL_1; + dac_reg = WM8994_AIF1DAC_LRCLK; + adc_reg = WM8994_AIF1ADC_LRCLK; + break; + case 2: + ms_reg = WM8994_AIF2_MASTER_SLAVE; + aif1_reg = WM8994_AIF2_CONTROL_1; + dac_reg = WM8994_AIF1DAC_LRCLK; + adc_reg = WM8994_AIF1ADC_LRCLK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ms = WM8994_AIF1_MSTR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8994_AIF1_LRCLK_INV; + lrclk |= WM8958_AIF1_LRCLK_INV; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x18; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8994_AIF1_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV; + lrclk |= WM8958_AIF1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8994_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8994_AIF1_LRCLK_INV; + lrclk |= WM8958_AIF1_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + /* The AIF2 format configuration needs to be mirrored to AIF3 + * on WM8958 if it's in use so just do it all the time. */ + switch (control->type) { + case WM1811: + case WM8958: + if (dai->id == 2) + snd_soc_component_update_bits(component, WM8958_AIF3_CONTROL_1, + WM8994_AIF1_LRCLK_INV | + WM8958_AIF3_FMT_MASK, aif1); + break; + + default: + break; + } + + snd_soc_component_update_bits(component, aif1_reg, + WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV | + WM8994_AIF1_FMT_MASK, + aif1); + snd_soc_component_update_bits(component, ms_reg, WM8994_AIF1_MSTR, + ms); + snd_soc_component_update_bits(component, dac_reg, + WM8958_AIF1_LRCLK_INV, lrclk); + snd_soc_component_update_bits(component, adc_reg, + WM8958_AIF1_LRCLK_INV, lrclk); + + return 0; +} + +static struct { + int val, rate; +} srs[] = { + { 0, 8000 }, + { 1, 11025 }, + { 2, 12000 }, + { 3, 16000 }, + { 4, 22050 }, + { 5, 24000 }, + { 6, 32000 }, + { 7, 44100 }, + { 8, 48000 }, + { 9, 88200 }, + { 10, 96000 }, +}; + +static int fs_ratios[] = { + 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536 +}; + +static int bclk_divs[] = { + 10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480, + 640, 880, 960, 1280, 1760, 1920 +}; + +static int wm8994_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + int aif1_reg; + int aif2_reg; + int bclk_reg; + int lrclk_reg; + int rate_reg; + int aif1 = 0; + int aif2 = 0; + int bclk = 0; + int lrclk = 0; + int rate_val = 0; + int id = dai->id - 1; + + int i, cur_val, best_val, bclk_rate, best; + + switch (dai->id) { + case 1: + aif1_reg = WM8994_AIF1_CONTROL_1; + aif2_reg = WM8994_AIF1_CONTROL_2; + bclk_reg = WM8994_AIF1_BCLK; + rate_reg = WM8994_AIF1_RATE; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + wm8994->lrclk_shared[0]) { + lrclk_reg = WM8994_AIF1DAC_LRCLK; + } else { + lrclk_reg = WM8994_AIF1ADC_LRCLK; + dev_dbg(component->dev, "AIF1 using split LRCLK\n"); + } + break; + case 2: + aif1_reg = WM8994_AIF2_CONTROL_1; + aif2_reg = WM8994_AIF2_CONTROL_2; + bclk_reg = WM8994_AIF2_BCLK; + rate_reg = WM8994_AIF2_RATE; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + wm8994->lrclk_shared[1]) { + lrclk_reg = WM8994_AIF2DAC_LRCLK; + } else { + lrclk_reg = WM8994_AIF2ADC_LRCLK; + dev_dbg(component->dev, "AIF2 using split LRCLK\n"); + } + break; + default: + return -EINVAL; + } + + bclk_rate = params_rate(params); + switch (params_width(params)) { + case 16: + bclk_rate *= 16; + break; + case 20: + bclk_rate *= 20; + aif1 |= 0x20; + break; + case 24: + bclk_rate *= 24; + aif1 |= 0x40; + break; + case 32: + bclk_rate *= 32; + aif1 |= 0x60; + break; + default: + return -EINVAL; + } + + wm8994->channels[id] = params_channels(params); + if (pdata->max_channels_clocked[id] && + wm8994->channels[id] > pdata->max_channels_clocked[id]) { + dev_dbg(dai->dev, "Constraining channels to %d from %d\n", + pdata->max_channels_clocked[id], wm8994->channels[id]); + wm8994->channels[id] = pdata->max_channels_clocked[id]; + } + + switch (wm8994->channels[id]) { + case 1: + case 2: + bclk_rate *= 2; + break; + default: + bclk_rate *= 4; + break; + } + + /* Try to find an appropriate sample rate; look for an exact match. */ + for (i = 0; i < ARRAY_SIZE(srs); i++) + if (srs[i].rate == params_rate(params)) + break; + if (i == ARRAY_SIZE(srs)) + return -EINVAL; + rate_val |= srs[i].val << WM8994_AIF1_SR_SHIFT; + + dev_dbg(dai->dev, "Sample rate is %dHz\n", srs[i].rate); + dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n", + dai->id, wm8994->aifclk[id], bclk_rate); + + if (wm8994->channels[id] == 1 && + (snd_soc_component_read32(component, aif1_reg) & 0x18) == 0x18) + aif2 |= WM8994_AIF1_MONO; + + if (wm8994->aifclk[id] == 0) { + dev_err(dai->dev, "AIF%dCLK not configured\n", dai->id); + return -EINVAL; + } + + /* AIFCLK/fs ratio; look for a close match in either direction */ + best = 0; + best_val = abs((fs_ratios[0] * params_rate(params)) + - wm8994->aifclk[id]); + for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) { + cur_val = abs((fs_ratios[i] * params_rate(params)) + - wm8994->aifclk[id]); + if (cur_val >= best_val) + continue; + best = i; + best_val = cur_val; + } + dev_dbg(dai->dev, "Selected AIF%dCLK/fs = %d\n", + dai->id, fs_ratios[best]); + rate_val |= best; + + /* We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + best = 0; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = (wm8994->aifclk[id] * 10 / bclk_divs[i]) - bclk_rate; + if (cur_val < 0) /* BCLK table is sorted */ + break; + best = i; + } + bclk_rate = wm8994->aifclk[id] * 10 / bclk_divs[best]; + dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n", + bclk_divs[best], bclk_rate); + bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT; + + lrclk = bclk_rate / params_rate(params); + if (!lrclk) { + dev_err(dai->dev, "Unable to generate LRCLK from %dHz BCLK\n", + bclk_rate); + return -EINVAL; + } + dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n", + lrclk, bclk_rate / lrclk); + + snd_soc_component_update_bits(component, aif1_reg, WM8994_AIF1_WL_MASK, aif1); + snd_soc_component_update_bits(component, aif2_reg, WM8994_AIF1_MONO, aif2); + snd_soc_component_update_bits(component, bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk); + snd_soc_component_update_bits(component, lrclk_reg, WM8994_AIF1DAC_RATE_MASK, + lrclk); + snd_soc_component_update_bits(component, rate_reg, WM8994_AIF1_SR_MASK | + WM8994_AIF1CLK_RATE_MASK, rate_val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->id) { + case 1: + wm8994->dac_rates[0] = params_rate(params); + wm8994_set_retune_mobile(component, 0); + wm8994_set_retune_mobile(component, 1); + break; + case 2: + wm8994->dac_rates[1] = params_rate(params); + wm8994_set_retune_mobile(component, 2); + break; + } + } + + return 0; +} + +static int wm8994_aif3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int aif1_reg; + int aif1 = 0; + + switch (dai->id) { + case 3: + switch (control->type) { + case WM1811: + case WM8958: + aif1_reg = WM8958_AIF3_CONTROL_1; + break; + default: + return 0; + } + break; + default: + return 0; + } + + switch (params_width(params)) { + case 16: + break; + case 20: + aif1 |= 0x20; + break; + case 24: + aif1 |= 0x40; + break; + case 32: + aif1 |= 0x60; + break; + default: + return -EINVAL; + } + + return snd_soc_component_update_bits(component, aif1_reg, WM8994_AIF1_WL_MASK, aif1); +} + +static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + int mute_reg; + int reg; + + switch (codec_dai->id) { + case 1: + mute_reg = WM8994_AIF1_DAC1_FILTERS_1; + break; + case 2: + mute_reg = WM8994_AIF2_DAC_FILTERS_1; + break; + default: + return -EINVAL; + } + + if (mute) + reg = WM8994_AIF1DAC1_MUTE; + else + reg = 0; + + snd_soc_component_update_bits(component, mute_reg, WM8994_AIF1DAC1_MUTE, reg); + + return 0; +} + +static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate) +{ + struct snd_soc_component *component = codec_dai->component; + int reg, val, mask; + + switch (codec_dai->id) { + case 1: + reg = WM8994_AIF1_MASTER_SLAVE; + mask = WM8994_AIF1_TRI; + break; + case 2: + reg = WM8994_AIF2_MASTER_SLAVE; + mask = WM8994_AIF2_TRI; + break; + default: + return -EINVAL; + } + + if (tristate) + val = mask; + else + val = 0; + + return snd_soc_component_update_bits(component, reg, mask, val); +} + +static int wm8994_aif2_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + + /* Disable the pulls on the AIF if we're using it to save power. */ + snd_soc_component_update_bits(component, WM8994_GPIO_3, + WM8994_GPN_PU | WM8994_GPN_PD, 0); + snd_soc_component_update_bits(component, WM8994_GPIO_4, + WM8994_GPN_PU | WM8994_GPN_PD, 0); + snd_soc_component_update_bits(component, WM8994_GPIO_5, + WM8994_GPN_PU | WM8994_GPN_PD, 0); + + return 0; +} + +#define WM8994_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = { + .set_sysclk = wm8994_set_dai_sysclk, + .set_fmt = wm8994_set_dai_fmt, + .hw_params = wm8994_hw_params, + .digital_mute = wm8994_aif_mute, + .set_pll = wm8994_set_fll, + .set_tristate = wm8994_set_tristate, +}; + +static const struct snd_soc_dai_ops wm8994_aif2_dai_ops = { + .set_sysclk = wm8994_set_dai_sysclk, + .set_fmt = wm8994_set_dai_fmt, + .hw_params = wm8994_hw_params, + .digital_mute = wm8994_aif_mute, + .set_pll = wm8994_set_fll, + .set_tristate = wm8994_set_tristate, +}; + +static const struct snd_soc_dai_ops wm8994_aif3_dai_ops = { + .hw_params = wm8994_aif3_hw_params, +}; + +static struct snd_soc_dai_driver wm8994_dai[] = { + { + .name = "wm8994-aif1", + .id = 1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + .sig_bits = 24, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + .sig_bits = 24, + }, + .ops = &wm8994_aif1_dai_ops, + }, + { + .name = "wm8994-aif2", + .id = 2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + .sig_bits = 24, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + .sig_bits = 24, + }, + .probe = wm8994_aif2_probe, + .ops = &wm8994_aif2_dai_ops, + }, + { + .name = "wm8994-aif3", + .id = 3, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + .sig_bits = 24, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + .sig_bits = 24, + }, + .ops = &wm8994_aif3_dai_ops, + } +}; + +#ifdef CONFIG_PM +static int wm8994_component_suspend(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { + memcpy(&wm8994->fll_suspend[i], &wm8994->fll[i], + sizeof(struct wm8994_fll_config)); + ret = _wm8994_set_fll(component, i + 1, 0, 0, 0); + if (ret < 0) + dev_warn(component->dev, "Failed to stop FLL%d: %d\n", + i + 1, ret); + } + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8994_component_resume(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { + if (!wm8994->fll_suspend[i].out) + continue; + + ret = _wm8994_set_fll(component, i + 1, + wm8994->fll_suspend[i].src, + wm8994->fll_suspend[i].in, + wm8994->fll_suspend[i].out); + if (ret < 0) + dev_warn(component->dev, "Failed to restore FLL%d: %d\n", + i + 1, ret); + } + + return 0; +} +#else +#define wm8994_component_suspend NULL +#define wm8994_component_resume NULL +#endif + +static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994) +{ + struct snd_soc_component *component = wm8994->hubs.component; + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT("AIF1.1 EQ Mode", + wm8994->retune_mobile_enum, + wm8994_get_retune_mobile_enum, + wm8994_put_retune_mobile_enum), + SOC_ENUM_EXT("AIF1.2 EQ Mode", + wm8994->retune_mobile_enum, + wm8994_get_retune_mobile_enum, + wm8994_put_retune_mobile_enum), + SOC_ENUM_EXT("AIF2 EQ Mode", + wm8994->retune_mobile_enum, + wm8994_get_retune_mobile_enum, + wm8994_put_retune_mobile_enum), + }; + int ret, i, j; + const char **t; + + /* We need an array of texts for the enum API but the number + * of texts is likely to be less than the number of + * configurations due to the sample rate dependency of the + * configurations. */ + wm8994->num_retune_mobile_texts = 0; + wm8994->retune_mobile_texts = NULL; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + for (j = 0; j < wm8994->num_retune_mobile_texts; j++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8994->retune_mobile_texts[j]) == 0) + break; + } + + if (j != wm8994->num_retune_mobile_texts) + continue; + + /* Expand the array... */ + t = krealloc(wm8994->retune_mobile_texts, + sizeof(char *) * + (wm8994->num_retune_mobile_texts + 1), + GFP_KERNEL); + if (t == NULL) + continue; + + /* ...store the new entry... */ + t[wm8994->num_retune_mobile_texts] = + pdata->retune_mobile_cfgs[i].name; + + /* ...and remember the new version. */ + wm8994->num_retune_mobile_texts++; + wm8994->retune_mobile_texts = t; + } + + dev_dbg(component->dev, "Allocated %d unique ReTune Mobile names\n", + wm8994->num_retune_mobile_texts); + + wm8994->retune_mobile_enum.items = wm8994->num_retune_mobile_texts; + wm8994->retune_mobile_enum.texts = wm8994->retune_mobile_texts; + + ret = snd_soc_add_component_controls(wm8994->hubs.component, controls, + ARRAY_SIZE(controls)); + if (ret != 0) + dev_err(wm8994->hubs.component->dev, + "Failed to add ReTune Mobile controls: %d\n", ret); +} + +static void wm8994_handle_pdata(struct wm8994_priv *wm8994) +{ + struct snd_soc_component *component = wm8994->hubs.component; + struct wm8994 *control = wm8994->wm8994; + struct wm8994_pdata *pdata = &control->pdata; + int ret, i; + + if (!pdata) + return; + + wm_hubs_handle_analogue_pdata(component, pdata->lineout1_diff, + pdata->lineout2_diff, + pdata->lineout1fb, + pdata->lineout2fb, + pdata->jd_scthr, + pdata->jd_thr, + pdata->micb1_delay, + pdata->micb2_delay, + pdata->micbias1_lvl, + pdata->micbias2_lvl); + + dev_dbg(component->dev, "%d DRC configurations\n", pdata->num_drc_cfgs); + + if (pdata->num_drc_cfgs) { + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT("AIF1DRC1 Mode", wm8994->drc_enum, + wm8994_get_drc_enum, wm8994_put_drc_enum), + SOC_ENUM_EXT("AIF1DRC2 Mode", wm8994->drc_enum, + wm8994_get_drc_enum, wm8994_put_drc_enum), + SOC_ENUM_EXT("AIF2DRC Mode", wm8994->drc_enum, + wm8994_get_drc_enum, wm8994_put_drc_enum), + }; + + /* We need an array of texts for the enum API */ + wm8994->drc_texts = devm_kcalloc(wm8994->hubs.component->dev, + pdata->num_drc_cfgs, sizeof(char *), GFP_KERNEL); + if (!wm8994->drc_texts) + return; + + for (i = 0; i < pdata->num_drc_cfgs; i++) + wm8994->drc_texts[i] = pdata->drc_cfgs[i].name; + + wm8994->drc_enum.items = pdata->num_drc_cfgs; + wm8994->drc_enum.texts = wm8994->drc_texts; + + ret = snd_soc_add_component_controls(wm8994->hubs.component, controls, + ARRAY_SIZE(controls)); + for (i = 0; i < WM8994_NUM_DRC; i++) + wm8994_set_drc(component, i); + } else { + ret = snd_soc_add_component_controls(wm8994->hubs.component, + wm8994_drc_controls, + ARRAY_SIZE(wm8994_drc_controls)); + } + + if (ret != 0) + dev_err(wm8994->hubs.component->dev, + "Failed to add DRC mode controls: %d\n", ret); + + + dev_dbg(component->dev, "%d ReTune Mobile configurations\n", + pdata->num_retune_mobile_cfgs); + + if (pdata->num_retune_mobile_cfgs) + wm8994_handle_retune_mobile_pdata(wm8994); + else + snd_soc_add_component_controls(wm8994->hubs.component, wm8994_eq_controls, + ARRAY_SIZE(wm8994_eq_controls)); + + for (i = 0; i < ARRAY_SIZE(pdata->micbias); i++) { + if (pdata->micbias[i]) { + snd_soc_component_write(component, WM8958_MICBIAS1 + i, + pdata->micbias[i] & 0xffff); + } + } +} + +/** + * wm8994_mic_detect - Enable microphone detection via the WM8994 IRQ + * + * @component: WM8994 component + * @jack: jack to report detection events on + * @micbias: microphone bias to detect on + * + * Enable microphone detection via IRQ on the WM8994. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for WM8994 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * Configuration of detection levels is available via the micbias1_lvl + * and micbias2_lvl platform data members. + */ +int wm8994_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack, + int micbias) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994_micdet *micdet; + struct wm8994 *control = wm8994->wm8994; + int reg, ret; + + if (control->type != WM8994) { + dev_warn(component->dev, "Not a WM8994\n"); + return -EINVAL; + } + + pm_runtime_get_sync(component->dev); + + switch (micbias) { + case 1: + micdet = &wm8994->micdet[0]; + if (jack) + ret = snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + else + ret = snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + break; + case 2: + micdet = &wm8994->micdet[1]; + if (jack) + ret = snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + else + ret = snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + break; + default: + dev_warn(component->dev, "Invalid MICBIAS %d\n", micbias); + return -EINVAL; + } + + if (ret != 0) + dev_warn(component->dev, "Failed to configure MICBIAS%d: %d\n", + micbias, ret); + + dev_dbg(component->dev, "Configuring microphone detection on %d %p\n", + micbias, jack); + + /* Store the configuration */ + micdet->jack = jack; + micdet->detecting = true; + + /* If either of the jacks is set up then enable detection */ + if (wm8994->micdet[0].jack || wm8994->micdet[1].jack) + reg = WM8994_MICD_ENA; + else + reg = 0; + + snd_soc_component_update_bits(component, WM8994_MICBIAS, WM8994_MICD_ENA, reg); + + /* enable MICDET and MICSHRT deboune */ + snd_soc_component_update_bits(component, WM8994_IRQ_DEBOUNCE, + WM8994_MIC1_DET_DB_MASK | WM8994_MIC1_SHRT_DB_MASK | + WM8994_MIC2_DET_DB_MASK | WM8994_MIC2_SHRT_DB_MASK, + WM8994_MIC1_DET_DB | WM8994_MIC1_SHRT_DB); + + snd_soc_dapm_sync(dapm); + + pm_runtime_put(component->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8994_mic_detect); + +static void wm8994_mic_work(struct work_struct *work) +{ + struct wm8994_priv *priv = container_of(work, + struct wm8994_priv, + mic_work.work); + struct regmap *regmap = priv->wm8994->regmap; + struct device *dev = priv->wm8994->dev; + unsigned int reg; + int ret; + int report; + + pm_runtime_get_sync(dev); + + ret = regmap_read(regmap, WM8994_INTERRUPT_RAW_STATUS_2, ®); + if (ret < 0) { + dev_err(dev, "Failed to read microphone status: %d\n", + ret); + pm_runtime_put(dev); + return; + } + + dev_dbg(dev, "Microphone status: %x\n", reg); + + report = 0; + if (reg & WM8994_MIC1_DET_STS) { + if (priv->micdet[0].detecting) + report = SND_JACK_HEADSET; + } + if (reg & WM8994_MIC1_SHRT_STS) { + if (priv->micdet[0].detecting) + report = SND_JACK_HEADPHONE; + else + report |= SND_JACK_BTN_0; + } + if (report) + priv->micdet[0].detecting = false; + else + priv->micdet[0].detecting = true; + + snd_soc_jack_report(priv->micdet[0].jack, report, + SND_JACK_HEADSET | SND_JACK_BTN_0); + + report = 0; + if (reg & WM8994_MIC2_DET_STS) { + if (priv->micdet[1].detecting) + report = SND_JACK_HEADSET; + } + if (reg & WM8994_MIC2_SHRT_STS) { + if (priv->micdet[1].detecting) + report = SND_JACK_HEADPHONE; + else + report |= SND_JACK_BTN_0; + } + if (report) + priv->micdet[1].detecting = false; + else + priv->micdet[1].detecting = true; + + snd_soc_jack_report(priv->micdet[1].jack, report, + SND_JACK_HEADSET | SND_JACK_BTN_0); + + pm_runtime_put(dev); +} + +static irqreturn_t wm8994_mic_irq(int irq, void *data) +{ + struct wm8994_priv *priv = data; + struct snd_soc_component *component = priv->hubs.component; + +#ifndef CONFIG_SND_SOC_WM8994_MODULE + trace_snd_soc_jack_irq(dev_name(component->dev)); +#endif + + pm_wakeup_event(component->dev, 300); + + queue_delayed_work(system_power_efficient_wq, + &priv->mic_work, msecs_to_jiffies(250)); + + return IRQ_HANDLED; +} + +/* Should be called with accdet_lock held */ +static void wm1811_micd_stop(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (!wm8994->jackdet) + return; + + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, WM8958_MICD_ENA, 0); + + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_JACK); + + if (wm8994->wm8994->pdata.jd_ext_cap) + snd_soc_dapm_disable_pin(dapm, "MICBIAS2"); +} + +static void wm8958_button_det(struct snd_soc_component *component, u16 status) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + int report; + + report = 0; + if (status & 0x4) + report |= SND_JACK_BTN_0; + + if (status & 0x8) + report |= SND_JACK_BTN_1; + + if (status & 0x10) + report |= SND_JACK_BTN_2; + + if (status & 0x20) + report |= SND_JACK_BTN_3; + + if (status & 0x40) + report |= SND_JACK_BTN_4; + + if (status & 0x80) + report |= SND_JACK_BTN_5; + + snd_soc_jack_report(wm8994->micdet[0].jack, report, + wm8994->btn_mask); +} + +static void wm8958_open_circuit_work(struct work_struct *work) +{ + struct wm8994_priv *wm8994 = container_of(work, + struct wm8994_priv, + open_circuit_work.work); + struct device *dev = wm8994->wm8994->dev; + + mutex_lock(&wm8994->accdet_lock); + + wm1811_micd_stop(wm8994->hubs.component); + + dev_dbg(dev, "Reporting open circuit\n"); + + wm8994->jack_mic = false; + wm8994->mic_detecting = true; + + wm8958_micd_set_rate(wm8994->hubs.component); + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); + + mutex_unlock(&wm8994->accdet_lock); +} + +static void wm8958_mic_id(void *data, u16 status) +{ + struct snd_soc_component *component = data; + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + /* Either nothing present or just starting detection */ + if (!(status & WM8958_MICD_STS)) { + /* If nothing present then clear our statuses */ + dev_dbg(component->dev, "Detected open circuit\n"); + + queue_delayed_work(system_power_efficient_wq, + &wm8994->open_circuit_work, + msecs_to_jiffies(2500)); + return; + } + + /* If the measurement is showing a high impedence we've got a + * microphone. + */ + if (status & 0x600) { + dev_dbg(component->dev, "Detected microphone\n"); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + wm8958_micd_set_rate(component); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET, + SND_JACK_HEADSET); + } + + + if (status & 0xfc) { + dev_dbg(component->dev, "Detected headphone\n"); + wm8994->mic_detecting = false; + + wm8958_micd_set_rate(component); + + /* If we have jackdet that will detect removal */ + wm1811_micd_stop(component); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADPHONE, + SND_JACK_HEADSET); + } +} + +/* Deferred mic detection to allow for extra settling time */ +static void wm1811_mic_work(struct work_struct *work) +{ + struct wm8994_priv *wm8994 = container_of(work, struct wm8994_priv, + mic_work.work); + struct wm8994 *control = wm8994->wm8994; + struct snd_soc_component *component = wm8994->hubs.component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + pm_runtime_get_sync(component->dev); + + /* If required for an external cap force MICBIAS on */ + if (control->pdata.jd_ext_cap) { + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS2"); + snd_soc_dapm_sync(dapm); + } + + mutex_lock(&wm8994->accdet_lock); + + dev_dbg(component->dev, "Starting mic detection\n"); + + /* Use a user-supplied callback if we have one */ + if (wm8994->micd_cb) { + wm8994->micd_cb(wm8994->micd_cb_data); + } else { + /* + * Start off measument of microphone impedence to find out + * what's actually there. + */ + wm8994->mic_detecting = true; + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_MIC); + + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } + + mutex_unlock(&wm8994->accdet_lock); + + pm_runtime_put(component->dev); +} + +static irqreturn_t wm1811_jackdet_irq(int irq, void *data) +{ + struct wm8994_priv *wm8994 = data; + struct wm8994 *control = wm8994->wm8994; + struct snd_soc_component *component = wm8994->hubs.component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int reg, delay; + bool present; + + pm_runtime_get_sync(component->dev); + + cancel_delayed_work_sync(&wm8994->mic_complete_work); + + mutex_lock(&wm8994->accdet_lock); + + reg = snd_soc_component_read32(component, WM1811_JACKDET_CTRL); + if (reg < 0) { + dev_err(component->dev, "Failed to read jack status: %d\n", reg); + mutex_unlock(&wm8994->accdet_lock); + pm_runtime_put(component->dev); + return IRQ_NONE; + } + + dev_dbg(component->dev, "JACKDET %x\n", reg); + + present = reg & WM1811_JACKDET_LVL; + + if (present) { + dev_dbg(component->dev, "Jack detected\n"); + + wm8958_micd_set_rate(component); + + snd_soc_component_update_bits(component, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, 0); + + /* Disable debounce while inserted */ + snd_soc_component_update_bits(component, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, 0); + + delay = control->pdata.micdet_delay; + queue_delayed_work(system_power_efficient_wq, + &wm8994->mic_work, + msecs_to_jiffies(delay)); + } else { + dev_dbg(component->dev, "Jack not detected\n"); + + cancel_delayed_work_sync(&wm8994->mic_work); + + snd_soc_component_update_bits(component, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, WM8958_MICB2_DISCH); + + /* Enable debounce while removed */ + snd_soc_component_update_bits(component, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, WM1811_JACKDET_DB); + + wm8994->mic_detecting = false; + wm8994->jack_mic = false; + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_JACK); + } + + mutex_unlock(&wm8994->accdet_lock); + + /* Turn off MICBIAS if it was on for an external cap */ + if (control->pdata.jd_ext_cap && !present) + snd_soc_dapm_disable_pin(dapm, "MICBIAS2"); + + if (present) + snd_soc_jack_report(wm8994->micdet[0].jack, + SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); + else + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + + /* Since we only report deltas force an update, ensures we + * avoid bootstrapping issues with the core. */ + snd_soc_jack_report(wm8994->micdet[0].jack, 0, 0); + + pm_runtime_put(component->dev); + return IRQ_HANDLED; +} + +static void wm1811_jackdet_bootstrap(struct work_struct *work) +{ + struct wm8994_priv *wm8994 = container_of(work, + struct wm8994_priv, + jackdet_bootstrap.work); + wm1811_jackdet_irq(0, wm8994); +} + +/** + * wm8958_mic_detect - Enable microphone detection via the WM8958 IRQ + * + * @component: WM8958 component + * @jack: jack to report detection events on + * + * Enable microphone detection functionality for the WM8958. By + * default simple detection which supports the detection of up to 6 + * buttons plus video and microphone functionality is supported. + * + * The WM8958 has an advanced jack detection facility which is able to + * support complex accessory detection, especially when used in + * conjunction with external circuitry. In order to provide maximum + * flexiblity a callback is provided which allows a completely custom + * detection algorithm. + */ +int wm8958_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack, + wm1811_micdet_cb det_cb, void *det_cb_data, + wm1811_mic_id_cb id_cb, void *id_cb_data) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + u16 micd_lvl_sel; + + switch (control->type) { + case WM1811: + case WM8958: + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(component->dev); + + if (jack) { + snd_soc_dapm_force_enable_pin(dapm, "CLK_SYS"); + snd_soc_dapm_sync(dapm); + + wm8994->micdet[0].jack = jack; + + if (det_cb) { + wm8994->micd_cb = det_cb; + wm8994->micd_cb_data = det_cb_data; + } else { + wm8994->mic_detecting = true; + wm8994->jack_mic = false; + } + + if (id_cb) { + wm8994->mic_id_cb = id_cb; + wm8994->mic_id_cb_data = id_cb_data; + } else { + wm8994->mic_id_cb = wm8958_mic_id; + wm8994->mic_id_cb_data = component; + } + + wm8958_micd_set_rate(component); + + /* Detect microphones and short circuits by default */ + if (control->pdata.micd_lvl_sel) + micd_lvl_sel = control->pdata.micd_lvl_sel; + else + micd_lvl_sel = 0x41; + + wm8994->btn_mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | + SND_JACK_BTN_4 | SND_JACK_BTN_5; + + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_2, + WM8958_MICD_LVL_SEL_MASK, micd_lvl_sel); + + WARN_ON(snd_soc_component_get_bias_level(component) > SND_SOC_BIAS_STANDBY); + + /* + * If we can use jack detection start off with that, + * otherwise jump straight to microphone detection. + */ + if (wm8994->jackdet) { + /* Disable debounce for the initial detect */ + snd_soc_component_update_bits(component, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, 0); + + snd_soc_component_update_bits(component, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, + WM8958_MICB2_DISCH); + snd_soc_component_update_bits(component, WM8994_LDO_1, + WM8994_LDO1_DISCH, 0); + wm1811_jackdet_set_mode(component, + WM1811_JACKDET_MODE_JACK); + } else { + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } + + } else { + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_NONE); + snd_soc_dapm_disable_pin(dapm, "CLK_SYS"); + snd_soc_dapm_sync(dapm); + } + + pm_runtime_put(component->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8958_mic_detect); + +static void wm8958_mic_work(struct work_struct *work) +{ + struct wm8994_priv *wm8994 = container_of(work, + struct wm8994_priv, + mic_complete_work.work); + struct snd_soc_component *component = wm8994->hubs.component; + + pm_runtime_get_sync(component->dev); + + mutex_lock(&wm8994->accdet_lock); + + wm8994->mic_id_cb(wm8994->mic_id_cb_data, wm8994->mic_status); + + mutex_unlock(&wm8994->accdet_lock); + + pm_runtime_put(component->dev); +} + +static irqreturn_t wm8958_mic_irq(int irq, void *data) +{ + struct wm8994_priv *wm8994 = data; + struct snd_soc_component *component = wm8994->hubs.component; + int reg, count, ret, id_delay; + + /* + * Jack detection may have detected a removal simulataneously + * with an update of the MICDET status; if so it will have + * stopped detection and we can ignore this interrupt. + */ + if (!(snd_soc_component_read32(component, WM8958_MIC_DETECT_1) & WM8958_MICD_ENA)) + return IRQ_HANDLED; + + cancel_delayed_work_sync(&wm8994->mic_complete_work); + cancel_delayed_work_sync(&wm8994->open_circuit_work); + + pm_runtime_get_sync(component->dev); + + /* We may occasionally read a detection without an impedence + * range being provided - if that happens loop again. + */ + count = 10; + do { + reg = snd_soc_component_read32(component, WM8958_MIC_DETECT_3); + if (reg < 0) { + dev_err(component->dev, + "Failed to read mic detect status: %d\n", + reg); + pm_runtime_put(component->dev); + return IRQ_NONE; + } + + if (!(reg & WM8958_MICD_VALID)) { + dev_dbg(component->dev, "Mic detect data not valid\n"); + goto out; + } + + if (!(reg & WM8958_MICD_STS) || (reg & WM8958_MICD_LVL_MASK)) + break; + + msleep(1); + } while (count--); + + if (count == 0) + dev_warn(component->dev, "No impedance range reported for jack\n"); + +#ifndef CONFIG_SND_SOC_WM8994_MODULE + trace_snd_soc_jack_irq(dev_name(component->dev)); +#endif + + /* Avoid a transient report when the accessory is being removed */ + if (wm8994->jackdet) { + ret = snd_soc_component_read32(component, WM1811_JACKDET_CTRL); + if (ret < 0) { + dev_err(component->dev, "Failed to read jack status: %d\n", + ret); + } else if (!(ret & WM1811_JACKDET_LVL)) { + dev_dbg(component->dev, "Ignoring removed jack\n"); + goto out; + } + } else if (!(reg & WM8958_MICD_STS)) { + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + wm8994->mic_detecting = true; + goto out; + } + + wm8994->mic_status = reg; + id_delay = wm8994->wm8994->pdata.mic_id_delay; + + if (wm8994->mic_detecting) + queue_delayed_work(system_power_efficient_wq, + &wm8994->mic_complete_work, + msecs_to_jiffies(id_delay)); + else + wm8958_button_det(component, reg); + +out: + pm_runtime_put(component->dev); + return IRQ_HANDLED; +} + +static irqreturn_t wm8994_fifo_error(int irq, void *data) +{ + struct snd_soc_component *component = data; + + dev_err(component->dev, "FIFO error\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t wm8994_temp_warn(int irq, void *data) +{ + struct snd_soc_component *component = data; + + dev_err(component->dev, "Thermal warning\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t wm8994_temp_shut(int irq, void *data) +{ + struct snd_soc_component *component = data; + + dev_crit(component->dev, "Thermal shutdown\n"); + + return IRQ_HANDLED; +} + +static int wm8994_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8994 *control = dev_get_drvdata(component->dev->parent); + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + unsigned int reg; + int ret, i; + + snd_soc_component_init_regmap(component, control->regmap); + + wm8994->hubs.component = component; + + mutex_init(&wm8994->accdet_lock); + INIT_DELAYED_WORK(&wm8994->jackdet_bootstrap, + wm1811_jackdet_bootstrap); + INIT_DELAYED_WORK(&wm8994->open_circuit_work, + wm8958_open_circuit_work); + + switch (control->type) { + case WM8994: + INIT_DELAYED_WORK(&wm8994->mic_work, wm8994_mic_work); + break; + case WM1811: + INIT_DELAYED_WORK(&wm8994->mic_work, wm1811_mic_work); + break; + default: + break; + } + + INIT_DELAYED_WORK(&wm8994->mic_complete_work, wm8958_mic_work); + + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) + init_completion(&wm8994->fll_locked[i]); + + wm8994->micdet_irq = control->pdata.micdet_irq; + + /* By default use idle_bias_off, will override for WM8994 */ + dapm->idle_bias_off = 1; + + /* Set revision-specific configuration */ + switch (control->type) { + case WM8994: + /* Single ended line outputs should have VMID on. */ + if (!control->pdata.lineout1_diff || + !control->pdata.lineout2_diff) + dapm->idle_bias_off = 0; + + switch (control->revision) { + case 2: + case 3: + wm8994->hubs.dcs_codes_l = -5; + wm8994->hubs.dcs_codes_r = -5; + wm8994->hubs.hp_startup_mode = 1; + wm8994->hubs.dcs_readback_mode = 1; + wm8994->hubs.series_startup = 1; + break; + default: + wm8994->hubs.dcs_readback_mode = 2; + break; + } + wm8994->hubs.micd_scthr = true; + break; + + case WM8958: + wm8994->hubs.dcs_readback_mode = 1; + wm8994->hubs.hp_startup_mode = 1; + wm8994->hubs.micd_scthr = true; + + switch (control->revision) { + case 0: + break; + default: + wm8994->fll_byp = true; + break; + } + break; + + case WM1811: + wm8994->hubs.dcs_readback_mode = 2; + wm8994->hubs.no_series_update = 1; + wm8994->hubs.hp_startup_mode = 1; + wm8994->hubs.no_cache_dac_hp_direct = true; + wm8994->fll_byp = true; + + wm8994->hubs.dcs_codes_l = -9; + wm8994->hubs.dcs_codes_r = -7; + + snd_soc_component_update_bits(component, WM8994_ANALOGUE_HP_1, + WM1811_HPOUT1_ATTN, WM1811_HPOUT1_ATTN); + break; + + default: + break; + } + + wm8994_request_irq(wm8994->wm8994, WM8994_IRQ_FIFOS_ERR, + wm8994_fifo_error, "FIFO error", component); + wm8994_request_irq(wm8994->wm8994, WM8994_IRQ_TEMP_WARN, + wm8994_temp_warn, "Thermal warning", component); + wm8994_request_irq(wm8994->wm8994, WM8994_IRQ_TEMP_SHUT, + wm8994_temp_shut, "Thermal shutdown", component); + + switch (control->type) { + case WM8994: + if (wm8994->micdet_irq) + ret = request_threaded_irq(wm8994->micdet_irq, NULL, + wm8994_mic_irq, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "Mic1 detect", + wm8994); + else + ret = wm8994_request_irq(wm8994->wm8994, + WM8994_IRQ_MIC1_DET, + wm8994_mic_irq, "Mic 1 detect", + wm8994); + + if (ret != 0) + dev_warn(component->dev, + "Failed to request Mic1 detect IRQ: %d\n", + ret); + + + ret = wm8994_request_irq(wm8994->wm8994, + WM8994_IRQ_MIC1_SHRT, + wm8994_mic_irq, "Mic 1 short", + wm8994); + if (ret != 0) + dev_warn(component->dev, + "Failed to request Mic1 short IRQ: %d\n", + ret); + + ret = wm8994_request_irq(wm8994->wm8994, + WM8994_IRQ_MIC2_DET, + wm8994_mic_irq, "Mic 2 detect", + wm8994); + if (ret != 0) + dev_warn(component->dev, + "Failed to request Mic2 detect IRQ: %d\n", + ret); + + ret = wm8994_request_irq(wm8994->wm8994, + WM8994_IRQ_MIC2_SHRT, + wm8994_mic_irq, "Mic 2 short", + wm8994); + if (ret != 0) + dev_warn(component->dev, + "Failed to request Mic2 short IRQ: %d\n", + ret); + break; + + case WM8958: + case WM1811: + if (wm8994->micdet_irq) { + ret = request_threaded_irq(wm8994->micdet_irq, NULL, + wm8958_mic_irq, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "Mic detect", + wm8994); + if (ret != 0) + dev_warn(component->dev, + "Failed to request Mic detect IRQ: %d\n", + ret); + } else { + wm8994_request_irq(wm8994->wm8994, WM8994_IRQ_MIC1_DET, + wm8958_mic_irq, "Mic detect", + wm8994); + } + } + + switch (control->type) { + case WM1811: + if (control->cust_id > 1 || control->revision > 1) { + ret = wm8994_request_irq(wm8994->wm8994, + WM8994_IRQ_GPIO(6), + wm1811_jackdet_irq, "JACKDET", + wm8994); + if (ret == 0) + wm8994->jackdet = true; + } + break; + default: + break; + } + + wm8994->fll_locked_irq = true; + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) { + ret = wm8994_request_irq(wm8994->wm8994, + WM8994_IRQ_FLL1_LOCK + i, + wm8994_fll_locked_irq, "FLL lock", + &wm8994->fll_locked[i]); + if (ret != 0) + wm8994->fll_locked_irq = false; + } + + /* Make sure we can read from the GPIOs if they're inputs */ + pm_runtime_get_sync(component->dev); + + /* Remember if AIFnLRCLK is configured as a GPIO. This should be + * configured on init - if a system wants to do this dynamically + * at runtime we can deal with that then. + */ + ret = regmap_read(control->regmap, WM8994_GPIO_1, ®); + if (ret < 0) { + dev_err(component->dev, "Failed to read GPIO1 state: %d\n", ret); + goto err_irq; + } + if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { + wm8994->lrclk_shared[0] = 1; + wm8994_dai[0].symmetric_rates = 1; + } else { + wm8994->lrclk_shared[0] = 0; + } + + ret = regmap_read(control->regmap, WM8994_GPIO_6, ®); + if (ret < 0) { + dev_err(component->dev, "Failed to read GPIO6 state: %d\n", ret); + goto err_irq; + } + if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { + wm8994->lrclk_shared[1] = 1; + wm8994_dai[1].symmetric_rates = 1; + } else { + wm8994->lrclk_shared[1] = 0; + } + + pm_runtime_put(component->dev); + + /* Latch volume update bits */ + for (i = 0; i < ARRAY_SIZE(wm8994_vu_bits); i++) + snd_soc_component_update_bits(component, wm8994_vu_bits[i].reg, + wm8994_vu_bits[i].mask, + wm8994_vu_bits[i].mask); + + /* Set the low bit of the 3D stereo depth so TLV matches */ + snd_soc_component_update_bits(component, WM8994_AIF1_DAC1_FILTERS_2, + 1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT, + 1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT); + snd_soc_component_update_bits(component, WM8994_AIF1_DAC2_FILTERS_2, + 1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT, + 1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT); + snd_soc_component_update_bits(component, WM8994_AIF2_DAC_FILTERS_2, + 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT, + 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT); + + /* Unconditionally enable AIF1 ADC TDM mode on chips which can + * use this; it only affects behaviour on idle TDM clock + * cycles. */ + switch (control->type) { + case WM8994: + case WM8958: + snd_soc_component_update_bits(component, WM8994_AIF1_CONTROL_1, + WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM); + break; + default: + break; + } + + /* Put MICBIAS into bypass mode by default on newer devices */ + switch (control->type) { + case WM8958: + case WM1811: + snd_soc_component_update_bits(component, WM8958_MICBIAS1, + WM8958_MICB1_MODE, WM8958_MICB1_MODE); + snd_soc_component_update_bits(component, WM8958_MICBIAS2, + WM8958_MICB2_MODE, WM8958_MICB2_MODE); + break; + default: + break; + } + + wm8994->hubs.check_class_w_digital = wm8994_check_class_w_digital; + wm_hubs_update_class_w(component); + + wm8994_handle_pdata(wm8994); + + wm_hubs_add_analogue_controls(component); + snd_soc_add_component_controls(component, wm8994_common_snd_controls, + ARRAY_SIZE(wm8994_common_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8994_dapm_widgets, + ARRAY_SIZE(wm8994_dapm_widgets)); + + switch (control->type) { + case WM8994: + snd_soc_add_component_controls(component, wm8994_snd_controls, + ARRAY_SIZE(wm8994_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets, + ARRAY_SIZE(wm8994_specific_dapm_widgets)); + if (control->revision < 4) { + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets, + ARRAY_SIZE(wm8994_lateclk_revd_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets, + ARRAY_SIZE(wm8994_adc_revd_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets, + ARRAY_SIZE(wm8994_dac_revd_widgets)); + } else { + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, + ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, + ARRAY_SIZE(wm8994_dac_widgets)); + } + break; + case WM8958: + snd_soc_add_component_controls(component, wm8994_snd_controls, + ARRAY_SIZE(wm8994_snd_controls)); + snd_soc_add_component_controls(component, wm8958_snd_controls, + ARRAY_SIZE(wm8958_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets, + ARRAY_SIZE(wm8958_dapm_widgets)); + if (control->revision < 1) { + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets, + ARRAY_SIZE(wm8994_lateclk_revd_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets, + ARRAY_SIZE(wm8994_adc_revd_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets, + ARRAY_SIZE(wm8994_dac_revd_widgets)); + } else { + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, + ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, + ARRAY_SIZE(wm8994_dac_widgets)); + } + break; + + case WM1811: + snd_soc_add_component_controls(component, wm8958_snd_controls, + ARRAY_SIZE(wm8958_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets, + ARRAY_SIZE(wm8958_dapm_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, + ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, + ARRAY_SIZE(wm8994_dac_widgets)); + break; + } + + wm_hubs_add_analogue_routes(component, 0, 0); + ret = wm8994_request_irq(wm8994->wm8994, WM8994_IRQ_DCS_DONE, + wm_hubs_dcs_done, "DC servo done", + &wm8994->hubs); + if (ret == 0) + wm8994->hubs.dcs_done_irq = true; + snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); + + switch (control->type) { + case WM8994: + snd_soc_dapm_add_routes(dapm, wm8994_intercon, + ARRAY_SIZE(wm8994_intercon)); + + if (control->revision < 4) { + snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon, + ARRAY_SIZE(wm8994_revd_intercon)); + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon, + ARRAY_SIZE(wm8994_lateclk_revd_intercon)); + } else { + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon, + ARRAY_SIZE(wm8994_lateclk_intercon)); + } + break; + case WM8958: + if (control->revision < 1) { + snd_soc_dapm_add_routes(dapm, wm8994_intercon, + ARRAY_SIZE(wm8994_intercon)); + snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon, + ARRAY_SIZE(wm8994_revd_intercon)); + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon, + ARRAY_SIZE(wm8994_lateclk_revd_intercon)); + } else { + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon, + ARRAY_SIZE(wm8994_lateclk_intercon)); + snd_soc_dapm_add_routes(dapm, wm8958_intercon, + ARRAY_SIZE(wm8958_intercon)); + } + + wm8958_dsp2_init(component); + break; + case WM1811: + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon, + ARRAY_SIZE(wm8994_lateclk_intercon)); + snd_soc_dapm_add_routes(dapm, wm8958_intercon, + ARRAY_SIZE(wm8958_intercon)); + break; + } + + return 0; + +err_irq: + if (wm8994->jackdet) + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_GPIO(6), wm8994); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_MIC2_SHRT, wm8994); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_MIC2_DET, wm8994); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_MIC1_SHRT, wm8994); + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_FLL1_LOCK + i, + &wm8994->fll_locked[i]); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_DCS_DONE, + &wm8994->hubs); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_FIFOS_ERR, component); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_TEMP_SHUT, component); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_TEMP_WARN, component); + + return ret; +} + +static void wm8994_component_remove(struct snd_soc_component *component) +{ + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + struct wm8994 *control = wm8994->wm8994; + int i; + + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_FLL1_LOCK + i, + &wm8994->fll_locked[i]); + + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_DCS_DONE, + &wm8994->hubs); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_FIFOS_ERR, component); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_TEMP_SHUT, component); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_TEMP_WARN, component); + + if (wm8994->jackdet) + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_GPIO(6), wm8994); + + switch (control->type) { + case WM8994: + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_MIC2_DET, + wm8994); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_MIC1_SHRT, + wm8994); + wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_MIC1_DET, + wm8994); + break; + + case WM1811: + case WM8958: + if (wm8994->micdet_irq) + free_irq(wm8994->micdet_irq, wm8994); + break; + } + release_firmware(wm8994->mbc); + release_firmware(wm8994->mbc_vss); + release_firmware(wm8994->enh_eq); + kfree(wm8994->retune_mobile_texts); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8994 = { + .probe = wm8994_component_probe, + .remove = wm8994_component_remove, + .suspend = wm8994_component_suspend, + .resume = wm8994_component_resume, + .set_bias_level = wm8994_set_bias_level, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8994_probe(struct platform_device *pdev) +{ + struct wm8994_priv *wm8994; + + wm8994 = devm_kzalloc(&pdev->dev, sizeof(struct wm8994_priv), + GFP_KERNEL); + if (wm8994 == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, wm8994); + + mutex_init(&wm8994->fw_lock); + + wm8994->wm8994 = dev_get_drvdata(pdev->dev.parent); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + + return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_wm8994, + wm8994_dai, ARRAY_SIZE(wm8994_dai)); +} + +static int wm8994_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int wm8994_suspend(struct device *dev) +{ + struct wm8994_priv *wm8994 = dev_get_drvdata(dev); + + /* Drop down to power saving mode when system is suspended */ + if (wm8994->jackdet && !wm8994->active_refcount) + regmap_update_bits(wm8994->wm8994->regmap, WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + wm8994->jackdet_mode); + + return 0; +} + +static int wm8994_resume(struct device *dev) +{ + struct wm8994_priv *wm8994 = dev_get_drvdata(dev); + + if (wm8994->jackdet && wm8994->jackdet_mode) + regmap_update_bits(wm8994->wm8994->regmap, WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + + return 0; +} +#endif + +static const struct dev_pm_ops wm8994_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(wm8994_suspend, wm8994_resume) +}; + +static struct platform_driver wm8994_codec_driver = { + .driver = { + .name = "wm8994-codec", + .pm = &wm8994_pm_ops, + }, + .probe = wm8994_probe, + .remove = wm8994_remove, +}; + +module_platform_driver(wm8994_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8994 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8994-codec"); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h new file mode 100644 index 000000000..a72efb0e6 --- /dev/null +++ b/sound/soc/codecs/wm8994.h @@ -0,0 +1,168 @@ +/* + * wm8994.h -- WM8994 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8994_H +#define _WM8994_H + +#include +#include +#include +#include +#include + +#include "wm_hubs.h" + +/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */ +#define WM8994_SYSCLK_MCLK1 1 +#define WM8994_SYSCLK_MCLK2 2 +#define WM8994_SYSCLK_FLL1 3 +#define WM8994_SYSCLK_FLL2 4 + +/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */ +#define WM8994_SYSCLK_OPCLK 5 + +#define WM8994_FLL1 1 +#define WM8994_FLL2 2 + +#define WM8994_FLL_SRC_MCLK1 1 +#define WM8994_FLL_SRC_MCLK2 2 +#define WM8994_FLL_SRC_LRCLK 3 +#define WM8994_FLL_SRC_BCLK 4 +#define WM8994_FLL_SRC_INTERNAL 5 + +enum wm8994_vmid_mode { + WM8994_VMID_NORMAL, + WM8994_VMID_FORCE, +}; + +typedef void (*wm1811_micdet_cb)(void *data); +typedef void (*wm1811_mic_id_cb)(void *data, u16 status); + +int wm8994_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack, + int micbias); +int wm8958_mic_detect(struct snd_soc_component *component, struct snd_soc_jack *jack, + wm1811_micdet_cb cb, void *det_cb_data, + wm1811_mic_id_cb id_cb, void *id_cb_data); + +int wm8994_vmid_mode(struct snd_soc_component *component, enum wm8994_vmid_mode mode); + +int wm8958_aif_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +void wm8958_dsp2_init(struct snd_soc_component *component); + +struct wm8994_micdet { + struct snd_soc_jack *jack; + bool detecting; +}; + +/* codec private data */ +struct wm8994_fll_config { + int src; + int in; + int out; +}; + +#define WM8994_NUM_DRC 3 +#define WM8994_NUM_EQ 3 + +struct wm8994; + +struct wm8994_priv { + struct wm_hubs_data hubs; + struct wm8994 *wm8994; + int sysclk[2]; + int sysclk_rate[2]; + int mclk[2]; + int aifclk[2]; + int aifdiv[2]; + int channels[2]; + struct wm8994_fll_config fll[2], fll_suspend[2]; + struct completion fll_locked[2]; + bool fll_locked_irq; + bool fll_byp; + bool clk_has_run; + + int vmid_refcount; + int active_refcount; + enum wm8994_vmid_mode vmid_mode; + + int dac_rates[2]; + int lrclk_shared[2]; + + int mbc_ena[3]; + int hpf1_ena[3]; + int hpf2_ena[3]; + int vss_ena[3]; + int enh_eq_ena[3]; + + /* Platform dependant DRC configuration */ + const char **drc_texts; + int drc_cfg[WM8994_NUM_DRC]; + struct soc_enum drc_enum; + + /* Platform dependant ReTune mobile configuration */ + int num_retune_mobile_texts; + const char **retune_mobile_texts; + int retune_mobile_cfg[WM8994_NUM_EQ]; + struct soc_enum retune_mobile_enum; + + /* Platform dependant MBC configuration */ + int mbc_cfg; + const char **mbc_texts; + struct soc_enum mbc_enum; + + /* Platform dependant VSS configuration */ + int vss_cfg; + const char **vss_texts; + struct soc_enum vss_enum; + + /* Platform dependant VSS HPF configuration */ + int vss_hpf_cfg; + const char **vss_hpf_texts; + struct soc_enum vss_hpf_enum; + + /* Platform dependant enhanced EQ configuration */ + int enh_eq_cfg; + const char **enh_eq_texts; + struct soc_enum enh_eq_enum; + + struct mutex accdet_lock; + struct wm8994_micdet micdet[2]; + struct delayed_work mic_work; + struct delayed_work open_circuit_work; + struct delayed_work mic_complete_work; + u16 mic_status; + bool mic_detecting; + bool jack_mic; + int btn_mask; + bool jackdet; + int jackdet_mode; + struct delayed_work jackdet_bootstrap; + + int micdet_irq; + wm1811_micdet_cb micd_cb; + void *micd_cb_data; + wm1811_mic_id_cb mic_id_cb; + void *mic_id_cb_data; + + unsigned int aif1clk_enable:1; + unsigned int aif2clk_enable:1; + + unsigned int aif1clk_disable:1; + unsigned int aif2clk_disable:1; + + struct mutex fw_lock; + int dsp_active; + const struct firmware *cur_fw; + const struct firmware *mbc; + const struct firmware *mbc_vss; + const struct firmware *enh_eq; +}; + +#endif diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c new file mode 100644 index 000000000..68c99fe37 --- /dev/null +++ b/sound/soc/codecs/wm8995.c @@ -0,0 +1,2331 @@ +/* + * wm8995.c -- WM8995 ALSA SoC Audio driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * Based on wm8994.c and wm_hubs.c by Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8995.h" + +#define WM8995_NUM_SUPPLIES 8 +static const char *wm8995_supply_names[WM8995_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD1", + "DBVDD2", + "DBVDD3", + "AVDD1", + "AVDD2", + "CPVDD", + "MICVDD" +}; + +static const struct reg_default wm8995_reg_defaults[] = { + { 0, 0x8995 }, + { 5, 0x0100 }, + { 16, 0x000b }, + { 17, 0x000b }, + { 24, 0x02c0 }, + { 25, 0x02c0 }, + { 26, 0x02c0 }, + { 27, 0x02c0 }, + { 28, 0x000f }, + { 32, 0x0005 }, + { 33, 0x0005 }, + { 40, 0x0003 }, + { 41, 0x0013 }, + { 48, 0x0004 }, + { 56, 0x09f8 }, + { 64, 0x1f25 }, + { 69, 0x0004 }, + { 82, 0xaaaa }, + { 84, 0x2a2a }, + { 146, 0x0060 }, + { 256, 0x0002 }, + { 257, 0x8004 }, + { 520, 0x0010 }, + { 528, 0x0083 }, + { 529, 0x0083 }, + { 548, 0x0c80 }, + { 580, 0x0c80 }, + { 768, 0x4050 }, + { 769, 0x4000 }, + { 771, 0x0040 }, + { 772, 0x0040 }, + { 773, 0x0040 }, + { 774, 0x0004 }, + { 775, 0x0100 }, + { 784, 0x4050 }, + { 785, 0x4000 }, + { 787, 0x0040 }, + { 788, 0x0040 }, + { 789, 0x0040 }, + { 1024, 0x00c0 }, + { 1025, 0x00c0 }, + { 1026, 0x00c0 }, + { 1027, 0x00c0 }, + { 1028, 0x00c0 }, + { 1029, 0x00c0 }, + { 1030, 0x00c0 }, + { 1031, 0x00c0 }, + { 1056, 0x0200 }, + { 1057, 0x0010 }, + { 1058, 0x0200 }, + { 1059, 0x0010 }, + { 1088, 0x0098 }, + { 1089, 0x0845 }, + { 1104, 0x0098 }, + { 1105, 0x0845 }, + { 1152, 0x6318 }, + { 1153, 0x6300 }, + { 1154, 0x0fca }, + { 1155, 0x0400 }, + { 1156, 0x00d8 }, + { 1157, 0x1eb5 }, + { 1158, 0xf145 }, + { 1159, 0x0b75 }, + { 1160, 0x01c5 }, + { 1161, 0x1c58 }, + { 1162, 0xf373 }, + { 1163, 0x0a54 }, + { 1164, 0x0558 }, + { 1165, 0x168e }, + { 1166, 0xf829 }, + { 1167, 0x07ad }, + { 1168, 0x1103 }, + { 1169, 0x0564 }, + { 1170, 0x0559 }, + { 1171, 0x4000 }, + { 1184, 0x6318 }, + { 1185, 0x6300 }, + { 1186, 0x0fca }, + { 1187, 0x0400 }, + { 1188, 0x00d8 }, + { 1189, 0x1eb5 }, + { 1190, 0xf145 }, + { 1191, 0x0b75 }, + { 1192, 0x01c5 }, + { 1193, 0x1c58 }, + { 1194, 0xf373 }, + { 1195, 0x0a54 }, + { 1196, 0x0558 }, + { 1197, 0x168e }, + { 1198, 0xf829 }, + { 1199, 0x07ad }, + { 1200, 0x1103 }, + { 1201, 0x0564 }, + { 1202, 0x0559 }, + { 1203, 0x4000 }, + { 1280, 0x00c0 }, + { 1281, 0x00c0 }, + { 1282, 0x00c0 }, + { 1283, 0x00c0 }, + { 1312, 0x0200 }, + { 1313, 0x0010 }, + { 1344, 0x0098 }, + { 1345, 0x0845 }, + { 1408, 0x6318 }, + { 1409, 0x6300 }, + { 1410, 0x0fca }, + { 1411, 0x0400 }, + { 1412, 0x00d8 }, + { 1413, 0x1eb5 }, + { 1414, 0xf145 }, + { 1415, 0x0b75 }, + { 1416, 0x01c5 }, + { 1417, 0x1c58 }, + { 1418, 0xf373 }, + { 1419, 0x0a54 }, + { 1420, 0x0558 }, + { 1421, 0x168e }, + { 1422, 0xf829 }, + { 1423, 0x07ad }, + { 1424, 0x1103 }, + { 1425, 0x0564 }, + { 1426, 0x0559 }, + { 1427, 0x4000 }, + { 1568, 0x0002 }, + { 1792, 0xa100 }, + { 1793, 0xa101 }, + { 1794, 0xa101 }, + { 1795, 0xa101 }, + { 1796, 0xa101 }, + { 1797, 0xa101 }, + { 1798, 0xa101 }, + { 1799, 0xa101 }, + { 1800, 0xa101 }, + { 1801, 0xa101 }, + { 1802, 0xa101 }, + { 1803, 0xa101 }, + { 1804, 0xa101 }, + { 1805, 0xa101 }, + { 1825, 0x0055 }, + { 1848, 0x3fff }, + { 1849, 0x1fff }, + { 2049, 0x0001 }, + { 2050, 0x0069 }, + { 2056, 0x0002 }, + { 2057, 0x0003 }, + { 2058, 0x0069 }, + { 12288, 0x0001 }, + { 12289, 0x0001 }, + { 12291, 0x0006 }, + { 12292, 0x0040 }, + { 12293, 0x0001 }, + { 12294, 0x000f }, + { 12295, 0x0006 }, + { 12296, 0x0001 }, + { 12297, 0x0003 }, + { 12298, 0x0104 }, + { 12300, 0x0060 }, + { 12301, 0x0011 }, + { 12302, 0x0401 }, + { 12304, 0x0050 }, + { 12305, 0x0003 }, + { 12306, 0x0100 }, + { 12308, 0x0051 }, + { 12309, 0x0003 }, + { 12310, 0x0104 }, + { 12311, 0x000a }, + { 12312, 0x0060 }, + { 12313, 0x003b }, + { 12314, 0x0502 }, + { 12315, 0x0100 }, + { 12316, 0x2fff }, + { 12320, 0x2fff }, + { 12324, 0x2fff }, + { 12328, 0x2fff }, + { 12332, 0x2fff }, + { 12336, 0x2fff }, + { 12340, 0x2fff }, + { 12344, 0x2fff }, + { 12348, 0x2fff }, + { 12352, 0x0001 }, + { 12353, 0x0001 }, + { 12355, 0x0006 }, + { 12356, 0x0040 }, + { 12357, 0x0001 }, + { 12358, 0x000f }, + { 12359, 0x0006 }, + { 12360, 0x0001 }, + { 12361, 0x0003 }, + { 12362, 0x0104 }, + { 12364, 0x0060 }, + { 12365, 0x0011 }, + { 12366, 0x0401 }, + { 12368, 0x0050 }, + { 12369, 0x0003 }, + { 12370, 0x0100 }, + { 12372, 0x0060 }, + { 12373, 0x003b }, + { 12374, 0x0502 }, + { 12375, 0x0100 }, + { 12376, 0x2fff }, + { 12380, 0x2fff }, + { 12384, 0x2fff }, + { 12388, 0x2fff }, + { 12392, 0x2fff }, + { 12396, 0x2fff }, + { 12400, 0x2fff }, + { 12404, 0x2fff }, + { 12408, 0x2fff }, + { 12412, 0x2fff }, + { 12416, 0x0001 }, + { 12417, 0x0001 }, + { 12419, 0x0006 }, + { 12420, 0x0040 }, + { 12421, 0x0001 }, + { 12422, 0x000f }, + { 12423, 0x0006 }, + { 12424, 0x0001 }, + { 12425, 0x0003 }, + { 12426, 0x0106 }, + { 12428, 0x0061 }, + { 12429, 0x0011 }, + { 12430, 0x0401 }, + { 12432, 0x0050 }, + { 12433, 0x0003 }, + { 12434, 0x0102 }, + { 12436, 0x0051 }, + { 12437, 0x0003 }, + { 12438, 0x0106 }, + { 12439, 0x000a }, + { 12440, 0x0061 }, + { 12441, 0x003b }, + { 12442, 0x0502 }, + { 12443, 0x0100 }, + { 12444, 0x2fff }, + { 12448, 0x2fff }, + { 12452, 0x2fff }, + { 12456, 0x2fff }, + { 12460, 0x2fff }, + { 12464, 0x2fff }, + { 12468, 0x2fff }, + { 12472, 0x2fff }, + { 12476, 0x2fff }, + { 12480, 0x0001 }, + { 12481, 0x0001 }, + { 12483, 0x0006 }, + { 12484, 0x0040 }, + { 12485, 0x0001 }, + { 12486, 0x000f }, + { 12487, 0x0006 }, + { 12488, 0x0001 }, + { 12489, 0x0003 }, + { 12490, 0x0106 }, + { 12492, 0x0061 }, + { 12493, 0x0011 }, + { 12494, 0x0401 }, + { 12496, 0x0050 }, + { 12497, 0x0003 }, + { 12498, 0x0102 }, + { 12500, 0x0061 }, + { 12501, 0x003b }, + { 12502, 0x0502 }, + { 12503, 0x0100 }, + { 12504, 0x2fff }, + { 12508, 0x2fff }, + { 12512, 0x2fff }, + { 12516, 0x2fff }, + { 12520, 0x2fff }, + { 12524, 0x2fff }, + { 12528, 0x2fff }, + { 12532, 0x2fff }, + { 12536, 0x2fff }, + { 12540, 0x2fff }, + { 12544, 0x0060 }, + { 12546, 0x0601 }, + { 12548, 0x0050 }, + { 12550, 0x0100 }, + { 12552, 0x0001 }, + { 12554, 0x0104 }, + { 12555, 0x0100 }, + { 12556, 0x2fff }, + { 12560, 0x2fff }, + { 12564, 0x2fff }, + { 12568, 0x2fff }, + { 12572, 0x2fff }, + { 12576, 0x2fff }, + { 12580, 0x2fff }, + { 12584, 0x2fff }, + { 12588, 0x2fff }, + { 12592, 0x2fff }, + { 12596, 0x2fff }, + { 12600, 0x2fff }, + { 12604, 0x2fff }, + { 12608, 0x0061 }, + { 12610, 0x0601 }, + { 12612, 0x0050 }, + { 12614, 0x0102 }, + { 12616, 0x0001 }, + { 12618, 0x0106 }, + { 12619, 0x0100 }, + { 12620, 0x2fff }, + { 12624, 0x2fff }, + { 12628, 0x2fff }, + { 12632, 0x2fff }, + { 12636, 0x2fff }, + { 12640, 0x2fff }, + { 12644, 0x2fff }, + { 12648, 0x2fff }, + { 12652, 0x2fff }, + { 12656, 0x2fff }, + { 12660, 0x2fff }, + { 12664, 0x2fff }, + { 12668, 0x2fff }, + { 12672, 0x0060 }, + { 12674, 0x0601 }, + { 12676, 0x0061 }, + { 12678, 0x0601 }, + { 12680, 0x0050 }, + { 12682, 0x0300 }, + { 12684, 0x0001 }, + { 12686, 0x0304 }, + { 12688, 0x0040 }, + { 12690, 0x000f }, + { 12692, 0x0001 }, + { 12695, 0x0100 }, +}; + +struct fll_config { + int src; + int in; + int out; +}; + +struct wm8995_priv { + struct regmap *regmap; + int sysclk[2]; + int mclk[2]; + int aifclk[2]; + struct fll_config fll[2], fll_suspend[2]; + struct regulator_bulk_data supplies[WM8995_NUM_SUPPLIES]; + struct notifier_block disable_nb[WM8995_NUM_SUPPLIES]; + struct snd_soc_component *component; +}; + +/* + * We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define WM8995_REGULATOR_EVENT(n) \ +static int wm8995_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct wm8995_priv *wm8995 = container_of(nb, struct wm8995_priv, \ + disable_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + regcache_mark_dirty(wm8995->regmap); \ + } \ + return 0; \ +} + +WM8995_REGULATOR_EVENT(0) +WM8995_REGULATOR_EVENT(1) +WM8995_REGULATOR_EVENT(2) +WM8995_REGULATOR_EVENT(3) +WM8995_REGULATOR_EVENT(4) +WM8995_REGULATOR_EVENT(5) +WM8995_REGULATOR_EVENT(6) +WM8995_REGULATOR_EVENT(7) + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(in1lr_pga_tlv, -1650, 150, 0); +static const DECLARE_TLV_DB_SCALE(in1l_boost_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 150, 0); + +static const char *in1l_text[] = { + "Differential", "Single-ended IN1LN", "Single-ended IN1LP" +}; + +static SOC_ENUM_SINGLE_DECL(in1l_enum, WM8995_LEFT_LINE_INPUT_CONTROL, + 2, in1l_text); + +static const char *in1r_text[] = { + "Differential", "Single-ended IN1RN", "Single-ended IN1RP" +}; + +static SOC_ENUM_SINGLE_DECL(in1r_enum, WM8995_LEFT_LINE_INPUT_CONTROL, + 0, in1r_text); + +static const char *dmic_src_text[] = { + "DMICDAT1", "DMICDAT2", "DMICDAT3" +}; + +static SOC_ENUM_SINGLE_DECL(dmic_src1_enum, WM8995_POWER_MANAGEMENT_5, + 8, dmic_src_text); +static SOC_ENUM_SINGLE_DECL(dmic_src2_enum, WM8995_POWER_MANAGEMENT_5, + 6, dmic_src_text); + +static const struct snd_kcontrol_new wm8995_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC1 Volume", WM8995_DAC1_LEFT_VOLUME, + WM8995_DAC1_RIGHT_VOLUME, 0, 96, 0, digital_tlv), + SOC_DOUBLE_R("DAC1 Switch", WM8995_DAC1_LEFT_VOLUME, + WM8995_DAC1_RIGHT_VOLUME, 9, 1, 1), + + SOC_DOUBLE_R_TLV("DAC2 Volume", WM8995_DAC2_LEFT_VOLUME, + WM8995_DAC2_RIGHT_VOLUME, 0, 96, 0, digital_tlv), + SOC_DOUBLE_R("DAC2 Switch", WM8995_DAC2_LEFT_VOLUME, + WM8995_DAC2_RIGHT_VOLUME, 9, 1, 1), + + SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8995_AIF1_DAC1_LEFT_VOLUME, + WM8995_AIF1_DAC1_RIGHT_VOLUME, 0, 96, 0, digital_tlv), + SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8995_AIF1_DAC2_LEFT_VOLUME, + WM8995_AIF1_DAC2_RIGHT_VOLUME, 0, 96, 0, digital_tlv), + SOC_DOUBLE_R_TLV("AIF2DAC Volume", WM8995_AIF2_DAC_LEFT_VOLUME, + WM8995_AIF2_DAC_RIGHT_VOLUME, 0, 96, 0, digital_tlv), + + SOC_DOUBLE_R_TLV("IN1LR Volume", WM8995_LEFT_LINE_INPUT_1_VOLUME, + WM8995_RIGHT_LINE_INPUT_1_VOLUME, 0, 31, 0, in1lr_pga_tlv), + + SOC_SINGLE_TLV("IN1L Boost", WM8995_LEFT_LINE_INPUT_CONTROL, + 4, 3, 0, in1l_boost_tlv), + + SOC_ENUM("IN1L Mode", in1l_enum), + SOC_ENUM("IN1R Mode", in1r_enum), + + SOC_ENUM("DMIC1 SRC", dmic_src1_enum), + SOC_ENUM("DMIC2 SRC", dmic_src2_enum), + + SOC_DOUBLE_TLV("DAC1 Sidetone Volume", WM8995_DAC1_MIXER_VOLUMES, 0, 5, + 24, 0, sidetone_tlv), + SOC_DOUBLE_TLV("DAC2 Sidetone Volume", WM8995_DAC2_MIXER_VOLUMES, 0, 5, + 24, 0, sidetone_tlv), + + SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8995_AIF1_ADC1_LEFT_VOLUME, + WM8995_AIF1_ADC1_RIGHT_VOLUME, 0, 96, 0, digital_tlv), + SOC_DOUBLE_R_TLV("AIF1ADC2 Volume", WM8995_AIF1_ADC2_LEFT_VOLUME, + WM8995_AIF1_ADC2_RIGHT_VOLUME, 0, 96, 0, digital_tlv), + SOC_DOUBLE_R_TLV("AIF2ADC Volume", WM8995_AIF2_ADC_LEFT_VOLUME, + WM8995_AIF2_ADC_RIGHT_VOLUME, 0, 96, 0, digital_tlv) +}; + +static void wm8995_update_class_w(struct snd_soc_component *component) +{ + int enable = 1; + int source = 0; /* GCC flow analysis can't track enable */ + int reg, reg_r; + + /* We also need the same setting for L/R and only one path */ + reg = snd_soc_component_read32(component, WM8995_DAC1_LEFT_MIXER_ROUTING); + switch (reg) { + case WM8995_AIF2DACL_TO_DAC1L: + dev_dbg(component->dev, "Class W source AIF2DAC\n"); + source = 2 << WM8995_CP_DYN_SRC_SEL_SHIFT; + break; + case WM8995_AIF1DAC2L_TO_DAC1L: + dev_dbg(component->dev, "Class W source AIF1DAC2\n"); + source = 1 << WM8995_CP_DYN_SRC_SEL_SHIFT; + break; + case WM8995_AIF1DAC1L_TO_DAC1L: + dev_dbg(component->dev, "Class W source AIF1DAC1\n"); + source = 0 << WM8995_CP_DYN_SRC_SEL_SHIFT; + break; + default: + dev_dbg(component->dev, "DAC mixer setting: %x\n", reg); + enable = 0; + break; + } + + reg_r = snd_soc_component_read32(component, WM8995_DAC1_RIGHT_MIXER_ROUTING); + if (reg_r != reg) { + dev_dbg(component->dev, "Left and right DAC mixers different\n"); + enable = 0; + } + + if (enable) { + dev_dbg(component->dev, "Class W enabled\n"); + snd_soc_component_update_bits(component, WM8995_CLASS_W_1, + WM8995_CP_DYN_PWR_MASK | + WM8995_CP_DYN_SRC_SEL_MASK, + source | WM8995_CP_DYN_PWR); + } else { + dev_dbg(component->dev, "Class W disabled\n"); + snd_soc_component_update_bits(component, WM8995_CLASS_W_1, + WM8995_CP_DYN_PWR_MASK, 0); + } +} + +static int check_clk_sys(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(source->dapm); + unsigned int reg; + const char *clk; + + reg = snd_soc_component_read32(component, WM8995_CLOCKING_1); + /* Check what we're currently using for CLK_SYS */ + if (reg & WM8995_SYSCLK_SRC) + clk = "AIF2CLK"; + else + clk = "AIF1CLK"; + return !strcmp(source->name, clk); +} + +static int wm8995_put_class_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + int ret; + + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + wm8995_update_class_w(component); + return ret; +} + +static int hp_supply_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable the headphone amp */ + snd_soc_component_update_bits(component, WM8995_POWER_MANAGEMENT_1, + WM8995_HPOUT1L_ENA_MASK | + WM8995_HPOUT1R_ENA_MASK, + WM8995_HPOUT1L_ENA | + WM8995_HPOUT1R_ENA); + + /* Enable the second stage */ + snd_soc_component_update_bits(component, WM8995_ANALOGUE_HP_1, + WM8995_HPOUT1L_DLY_MASK | + WM8995_HPOUT1R_DLY_MASK, + WM8995_HPOUT1L_DLY | + WM8995_HPOUT1R_DLY); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, WM8995_CHARGE_PUMP_1, + WM8995_CP_ENA_MASK, 0); + break; + } + + return 0; +} + +static void dc_servo_cmd(struct snd_soc_component *component, + unsigned int reg, unsigned int val, unsigned int mask) +{ + int timeout = 10; + + dev_dbg(component->dev, "%s: reg = %#x, val = %#x, mask = %#x\n", + __func__, reg, val, mask); + + snd_soc_component_write(component, reg, val); + while (timeout--) { + msleep(10); + val = snd_soc_component_read32(component, WM8995_DC_SERVO_READBACK_0); + if ((val & mask) == mask) + return; + } + + dev_err(component->dev, "Timed out waiting for DC Servo\n"); +} + +static int hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int reg; + + reg = snd_soc_component_read32(component, WM8995_ANALOGUE_HP_1); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, WM8995_CHARGE_PUMP_1, + WM8995_CP_ENA_MASK, WM8995_CP_ENA); + + msleep(5); + + snd_soc_component_update_bits(component, WM8995_POWER_MANAGEMENT_1, + WM8995_HPOUT1L_ENA_MASK | + WM8995_HPOUT1R_ENA_MASK, + WM8995_HPOUT1L_ENA | WM8995_HPOUT1R_ENA); + + udelay(20); + + reg |= WM8995_HPOUT1L_DLY | WM8995_HPOUT1R_DLY; + snd_soc_component_write(component, WM8995_ANALOGUE_HP_1, reg); + + snd_soc_component_write(component, WM8995_DC_SERVO_1, WM8995_DCS_ENA_CHAN_0 | + WM8995_DCS_ENA_CHAN_1); + + dc_servo_cmd(component, WM8995_DC_SERVO_2, + WM8995_DCS_TRIG_STARTUP_0 | + WM8995_DCS_TRIG_STARTUP_1, + WM8995_DCS_TRIG_DAC_WR_0 | + WM8995_DCS_TRIG_DAC_WR_1); + + reg |= WM8995_HPOUT1R_OUTP | WM8995_HPOUT1R_RMV_SHORT | + WM8995_HPOUT1L_OUTP | WM8995_HPOUT1L_RMV_SHORT; + snd_soc_component_write(component, WM8995_ANALOGUE_HP_1, reg); + + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, WM8995_ANALOGUE_HP_1, + WM8995_HPOUT1L_OUTP_MASK | + WM8995_HPOUT1R_OUTP_MASK | + WM8995_HPOUT1L_RMV_SHORT_MASK | + WM8995_HPOUT1R_RMV_SHORT_MASK, 0); + + snd_soc_component_update_bits(component, WM8995_ANALOGUE_HP_1, + WM8995_HPOUT1L_DLY_MASK | + WM8995_HPOUT1R_DLY_MASK, 0); + + snd_soc_component_write(component, WM8995_DC_SERVO_1, 0); + + snd_soc_component_update_bits(component, WM8995_POWER_MANAGEMENT_1, + WM8995_HPOUT1L_ENA_MASK | + WM8995_HPOUT1R_ENA_MASK, + 0); + break; + } + + return 0; +} + +static int configure_aif_clock(struct snd_soc_component *component, int aif) +{ + struct wm8995_priv *wm8995; + int rate; + int reg1 = 0; + int offset; + + wm8995 = snd_soc_component_get_drvdata(component); + + if (aif) + offset = 4; + else + offset = 0; + + switch (wm8995->sysclk[aif]) { + case WM8995_SYSCLK_MCLK1: + rate = wm8995->mclk[0]; + break; + case WM8995_SYSCLK_MCLK2: + reg1 |= 0x8; + rate = wm8995->mclk[1]; + break; + case WM8995_SYSCLK_FLL1: + reg1 |= 0x10; + rate = wm8995->fll[0].out; + break; + case WM8995_SYSCLK_FLL2: + reg1 |= 0x18; + rate = wm8995->fll[1].out; + break; + default: + return -EINVAL; + } + + if (rate >= 13500000) { + rate /= 2; + reg1 |= WM8995_AIF1CLK_DIV; + + dev_dbg(component->dev, "Dividing AIF%d clock to %dHz\n", + aif + 1, rate); + } + + wm8995->aifclk[aif] = rate; + + snd_soc_component_update_bits(component, WM8995_AIF1_CLOCKING_1 + offset, + WM8995_AIF1CLK_SRC_MASK | WM8995_AIF1CLK_DIV_MASK, + reg1); + return 0; +} + +static int configure_clock(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8995_priv *wm8995; + int change, new; + + wm8995 = snd_soc_component_get_drvdata(component); + + /* Bring up the AIF clocks first */ + configure_aif_clock(component, 0); + configure_aif_clock(component, 1); + + /* + * Then switch CLK_SYS over to the higher of them; a change + * can only happen as a result of a clocking change which can + * only be made outside of DAPM so we can safely redo the + * clocking. + */ + + /* If they're equal it doesn't matter which is used */ + if (wm8995->aifclk[0] == wm8995->aifclk[1]) + return 0; + + if (wm8995->aifclk[0] < wm8995->aifclk[1]) + new = WM8995_SYSCLK_SRC; + else + new = 0; + + change = snd_soc_component_update_bits(component, WM8995_CLOCKING_1, + WM8995_SYSCLK_SRC_MASK, new); + if (!change) + return 0; + + snd_soc_dapm_sync(dapm); + + return 0; +} + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return configure_clock(component); + + case SND_SOC_DAPM_POST_PMD: + configure_clock(component); + break; + } + + return 0; +} + +static const char *sidetone_text[] = { + "ADC/DMIC1", "DMIC2", +}; + +static SOC_ENUM_SINGLE_DECL(sidetone1_enum, WM8995_SIDETONE, 0, sidetone_text); + +static const struct snd_kcontrol_new sidetone1_mux = + SOC_DAPM_ENUM("Left Sidetone Mux", sidetone1_enum); + +static SOC_ENUM_SINGLE_DECL(sidetone2_enum, WM8995_SIDETONE, 1, sidetone_text); + +static const struct snd_kcontrol_new sidetone2_mux = + SOC_DAPM_ENUM("Right Sidetone Mux", sidetone2_enum); + +static const struct snd_kcontrol_new aif1adc1l_mix[] = { + SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8995_AIF1_ADC1_LEFT_MIXER_ROUTING, + 1, 1, 0), + SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC1_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc1r_mix[] = { + SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8995_AIF1_ADC1_RIGHT_MIXER_ROUTING, + 1, 1, 0), + SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC1_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc2l_mix[] = { + SOC_DAPM_SINGLE("DMIC Switch", WM8995_AIF1_ADC2_LEFT_MIXER_ROUTING, + 1, 1, 0), + SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC2_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc2r_mix[] = { + SOC_DAPM_SINGLE("DMIC Switch", WM8995_AIF1_ADC2_RIGHT_MIXER_ROUTING, + 1, 1, 0), + SOC_DAPM_SINGLE("AIF2 Switch", WM8995_AIF1_ADC2_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new dac1l_mix[] = { + WM8995_CLASS_W_SWITCH("Right Sidetone Switch", WM8995_DAC1_LEFT_MIXER_ROUTING, + 5, 1, 0), + WM8995_CLASS_W_SWITCH("Left Sidetone Switch", WM8995_DAC1_LEFT_MIXER_ROUTING, + 4, 1, 0), + WM8995_CLASS_W_SWITCH("AIF2 Switch", WM8995_DAC1_LEFT_MIXER_ROUTING, + 2, 1, 0), + WM8995_CLASS_W_SWITCH("AIF1.2 Switch", WM8995_DAC1_LEFT_MIXER_ROUTING, + 1, 1, 0), + WM8995_CLASS_W_SWITCH("AIF1.1 Switch", WM8995_DAC1_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new dac1r_mix[] = { + WM8995_CLASS_W_SWITCH("Right Sidetone Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING, + 5, 1, 0), + WM8995_CLASS_W_SWITCH("Left Sidetone Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING, + 4, 1, 0), + WM8995_CLASS_W_SWITCH("AIF2 Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING, + 2, 1, 0), + WM8995_CLASS_W_SWITCH("AIF1.2 Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING, + 1, 1, 0), + WM8995_CLASS_W_SWITCH("AIF1.1 Switch", WM8995_DAC1_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif2dac2l_mix[] = { + SOC_DAPM_SINGLE("Right Sidetone Switch", WM8995_DAC2_LEFT_MIXER_ROUTING, + 5, 1, 0), + SOC_DAPM_SINGLE("Left Sidetone Switch", WM8995_DAC2_LEFT_MIXER_ROUTING, + 4, 1, 0), + SOC_DAPM_SINGLE("AIF2 Switch", WM8995_DAC2_LEFT_MIXER_ROUTING, + 2, 1, 0), + SOC_DAPM_SINGLE("AIF1.2 Switch", WM8995_DAC2_LEFT_MIXER_ROUTING, + 1, 1, 0), + SOC_DAPM_SINGLE("AIF1.1 Switch", WM8995_DAC2_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif2dac2r_mix[] = { + SOC_DAPM_SINGLE("Right Sidetone Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING, + 5, 1, 0), + SOC_DAPM_SINGLE("Left Sidetone Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING, + 4, 1, 0), + SOC_DAPM_SINGLE("AIF2 Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING, + 2, 1, 0), + SOC_DAPM_SINGLE("AIF1.2 Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING, + 1, 1, 0), + SOC_DAPM_SINGLE("AIF1.1 Switch", WM8995_DAC2_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new in1l_pga = + SOC_DAPM_SINGLE("IN1L Switch", WM8995_POWER_MANAGEMENT_2, 5, 1, 0); + +static const struct snd_kcontrol_new in1r_pga = + SOC_DAPM_SINGLE("IN1R Switch", WM8995_POWER_MANAGEMENT_2, 4, 1, 0); + +static const char *adc_mux_text[] = { + "ADC", + "DMIC", +}; + +static SOC_ENUM_SINGLE_VIRT_DECL(adc_enum, adc_mux_text); + +static const struct snd_kcontrol_new adcl_mux = + SOC_DAPM_ENUM("ADCL Mux", adc_enum); + +static const struct snd_kcontrol_new adcr_mux = + SOC_DAPM_ENUM("ADCR Mux", adc_enum); + +static const char *spk_src_text[] = { + "DAC1L", "DAC1R", "DAC2L", "DAC2R" +}; + +static SOC_ENUM_SINGLE_DECL(spk1l_src_enum, WM8995_LEFT_PDM_SPEAKER_1, + 0, spk_src_text); +static SOC_ENUM_SINGLE_DECL(spk1r_src_enum, WM8995_RIGHT_PDM_SPEAKER_1, + 0, spk_src_text); +static SOC_ENUM_SINGLE_DECL(spk2l_src_enum, WM8995_LEFT_PDM_SPEAKER_2, + 0, spk_src_text); +static SOC_ENUM_SINGLE_DECL(spk2r_src_enum, WM8995_RIGHT_PDM_SPEAKER_2, + 0, spk_src_text); + +static const struct snd_kcontrol_new spk1l_mux = + SOC_DAPM_ENUM("SPK1L SRC", spk1l_src_enum); +static const struct snd_kcontrol_new spk1r_mux = + SOC_DAPM_ENUM("SPK1R SRC", spk1r_src_enum); +static const struct snd_kcontrol_new spk2l_mux = + SOC_DAPM_ENUM("SPK2L SRC", spk2l_src_enum); +static const struct snd_kcontrol_new spk2r_mux = + SOC_DAPM_ENUM("SPK2R SRC", spk2r_src_enum); + +static const struct snd_soc_dapm_widget wm8995_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("DMIC1DAT"), + SND_SOC_DAPM_INPUT("DMIC2DAT"), + + SND_SOC_DAPM_INPUT("IN1L"), + SND_SOC_DAPM_INPUT("IN1R"), + + SND_SOC_DAPM_MIXER("IN1L PGA", SND_SOC_NOPM, 0, 0, + &in1l_pga, 1), + SND_SOC_DAPM_MIXER("IN1R PGA", SND_SOC_NOPM, 0, 0, + &in1r_pga, 1), + + SND_SOC_DAPM_SUPPLY("MICBIAS1", WM8995_POWER_MANAGEMENT_1, 8, 0, + NULL, 0), + SND_SOC_DAPM_SUPPLY("MICBIAS2", WM8995_POWER_MANAGEMENT_1, 9, 0, + NULL, 0), + + SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8995_AIF1_CLOCKING_1, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8995_AIF2_CLOCKING_1, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DSP1CLK", WM8995_CLOCKING_1, 3, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("DSP2CLK", WM8995_CLOCKING_1, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SYSDSPCLK", WM8995_CLOCKING_1, 1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", "AIF1 Capture", 0, + WM8995_POWER_MANAGEMENT_3, 9, 0), + SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", "AIF1 Capture", 0, + WM8995_POWER_MANAGEMENT_3, 8, 0), + SND_SOC_DAPM_AIF_OUT("AIF1ADCDAT", "AIF1 Capture", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", "AIF1 Capture", + 0, WM8995_POWER_MANAGEMENT_3, 11, 0), + SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", "AIF1 Capture", + 0, WM8995_POWER_MANAGEMENT_3, 10, 0), + + SND_SOC_DAPM_MUX("ADCL Mux", SND_SOC_NOPM, 1, 0, &adcl_mux), + SND_SOC_DAPM_MUX("ADCR Mux", SND_SOC_NOPM, 0, 0, &adcr_mux), + + SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8995_POWER_MANAGEMENT_3, 5, 0), + SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8995_POWER_MANAGEMENT_3, 4, 0), + SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8995_POWER_MANAGEMENT_3, 3, 0), + SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8995_POWER_MANAGEMENT_3, 2, 0), + + SND_SOC_DAPM_ADC("ADCL", NULL, WM8995_POWER_MANAGEMENT_3, 1, 0), + SND_SOC_DAPM_ADC("ADCR", NULL, WM8995_POWER_MANAGEMENT_3, 0, 0), + + SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0, + aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)), + SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0, + aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)), + SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0, + aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)), + SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0, + aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)), + + SND_SOC_DAPM_AIF_IN("AIF1DAC1L", NULL, 0, WM8995_POWER_MANAGEMENT_4, + 9, 0), + SND_SOC_DAPM_AIF_IN("AIF1DAC1R", NULL, 0, WM8995_POWER_MANAGEMENT_4, + 8, 0), + SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, + 0, 0), + + SND_SOC_DAPM_AIF_IN("AIF1DAC2L", NULL, 0, WM8995_POWER_MANAGEMENT_4, + 11, 0), + SND_SOC_DAPM_AIF_IN("AIF1DAC2R", NULL, 0, WM8995_POWER_MANAGEMENT_4, + 10, 0), + + SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0, + aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)), + SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0, + aif2dac2r_mix, ARRAY_SIZE(aif2dac2r_mix)), + + SND_SOC_DAPM_DAC("DAC2L", NULL, WM8995_POWER_MANAGEMENT_4, 3, 0), + SND_SOC_DAPM_DAC("DAC2R", NULL, WM8995_POWER_MANAGEMENT_4, 2, 0), + SND_SOC_DAPM_DAC("DAC1L", NULL, WM8995_POWER_MANAGEMENT_4, 1, 0), + SND_SOC_DAPM_DAC("DAC1R", NULL, WM8995_POWER_MANAGEMENT_4, 0, 0), + + SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0, dac1l_mix, + ARRAY_SIZE(dac1l_mix)), + SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0, dac1r_mix, + ARRAY_SIZE(dac1r_mix)), + + SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &sidetone1_mux), + SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &sidetone2_mux), + + SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, + hp_supply_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("SPK1L Driver", WM8995_LEFT_PDM_SPEAKER_1, + 4, 0, &spk1l_mux), + SND_SOC_DAPM_MUX("SPK1R Driver", WM8995_RIGHT_PDM_SPEAKER_1, + 4, 0, &spk1r_mux), + SND_SOC_DAPM_MUX("SPK2L Driver", WM8995_LEFT_PDM_SPEAKER_2, + 4, 0, &spk2l_mux), + SND_SOC_DAPM_MUX("SPK2R Driver", WM8995_RIGHT_PDM_SPEAKER_2, + 4, 0, &spk2r_mux), + + SND_SOC_DAPM_SUPPLY("LDO2", WM8995_POWER_MANAGEMENT_2, 1, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("HP1L"), + SND_SOC_DAPM_OUTPUT("HP1R"), + SND_SOC_DAPM_OUTPUT("SPK1L"), + SND_SOC_DAPM_OUTPUT("SPK1R"), + SND_SOC_DAPM_OUTPUT("SPK2L"), + SND_SOC_DAPM_OUTPUT("SPK2R") +}; + +static const struct snd_soc_dapm_route wm8995_intercon[] = { + { "CLK_SYS", NULL, "AIF1CLK", check_clk_sys }, + { "CLK_SYS", NULL, "AIF2CLK", check_clk_sys }, + + { "DSP1CLK", NULL, "CLK_SYS" }, + { "DSP2CLK", NULL, "CLK_SYS" }, + { "SYSDSPCLK", NULL, "CLK_SYS" }, + + { "AIF1ADC1L", NULL, "AIF1CLK" }, + { "AIF1ADC1L", NULL, "DSP1CLK" }, + { "AIF1ADC1R", NULL, "AIF1CLK" }, + { "AIF1ADC1R", NULL, "DSP1CLK" }, + { "AIF1ADC1R", NULL, "SYSDSPCLK" }, + + { "AIF1ADC2L", NULL, "AIF1CLK" }, + { "AIF1ADC2L", NULL, "DSP1CLK" }, + { "AIF1ADC2R", NULL, "AIF1CLK" }, + { "AIF1ADC2R", NULL, "DSP1CLK" }, + { "AIF1ADC2R", NULL, "SYSDSPCLK" }, + + { "DMIC1L", NULL, "DMIC1DAT" }, + { "DMIC1L", NULL, "CLK_SYS" }, + { "DMIC1R", NULL, "DMIC1DAT" }, + { "DMIC1R", NULL, "CLK_SYS" }, + { "DMIC2L", NULL, "DMIC2DAT" }, + { "DMIC2L", NULL, "CLK_SYS" }, + { "DMIC2R", NULL, "DMIC2DAT" }, + { "DMIC2R", NULL, "CLK_SYS" }, + + { "ADCL", NULL, "AIF1CLK" }, + { "ADCL", NULL, "DSP1CLK" }, + { "ADCL", NULL, "SYSDSPCLK" }, + + { "ADCR", NULL, "AIF1CLK" }, + { "ADCR", NULL, "DSP1CLK" }, + { "ADCR", NULL, "SYSDSPCLK" }, + + { "IN1L PGA", "IN1L Switch", "IN1L" }, + { "IN1R PGA", "IN1R Switch", "IN1R" }, + { "IN1L PGA", NULL, "LDO2" }, + { "IN1R PGA", NULL, "LDO2" }, + + { "ADCL", NULL, "IN1L PGA" }, + { "ADCR", NULL, "IN1R PGA" }, + + { "ADCL Mux", "ADC", "ADCL" }, + { "ADCL Mux", "DMIC", "DMIC1L" }, + { "ADCR Mux", "ADC", "ADCR" }, + { "ADCR Mux", "DMIC", "DMIC1R" }, + + /* AIF1 outputs */ + { "AIF1ADC1L", NULL, "AIF1ADC1L Mixer" }, + { "AIF1ADC1L Mixer", "ADC/DMIC Switch", "ADCL Mux" }, + + { "AIF1ADC1R", NULL, "AIF1ADC1R Mixer" }, + { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" }, + + { "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" }, + { "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" }, + + { "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" }, + { "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" }, + + /* Sidetone */ + { "Left Sidetone", "ADC/DMIC1", "AIF1ADC1L" }, + { "Left Sidetone", "DMIC2", "AIF1ADC2L" }, + { "Right Sidetone", "ADC/DMIC1", "AIF1ADC1R" }, + { "Right Sidetone", "DMIC2", "AIF1ADC2R" }, + + { "AIF1DAC1L", NULL, "AIF1CLK" }, + { "AIF1DAC1L", NULL, "DSP1CLK" }, + { "AIF1DAC1R", NULL, "AIF1CLK" }, + { "AIF1DAC1R", NULL, "DSP1CLK" }, + { "AIF1DAC1R", NULL, "SYSDSPCLK" }, + + { "AIF1DAC2L", NULL, "AIF1CLK" }, + { "AIF1DAC2L", NULL, "DSP1CLK" }, + { "AIF1DAC2R", NULL, "AIF1CLK" }, + { "AIF1DAC2R", NULL, "DSP1CLK" }, + { "AIF1DAC2R", NULL, "SYSDSPCLK" }, + + { "DAC1L", NULL, "AIF1CLK" }, + { "DAC1L", NULL, "DSP1CLK" }, + { "DAC1L", NULL, "SYSDSPCLK" }, + + { "DAC1R", NULL, "AIF1CLK" }, + { "DAC1R", NULL, "DSP1CLK" }, + { "DAC1R", NULL, "SYSDSPCLK" }, + + { "AIF1DAC1L", NULL, "AIF1DACDAT" }, + { "AIF1DAC1R", NULL, "AIF1DACDAT" }, + { "AIF1DAC2L", NULL, "AIF1DACDAT" }, + { "AIF1DAC2R", NULL, "AIF1DACDAT" }, + + /* DAC1 inputs */ + { "DAC1L", NULL, "DAC1L Mixer" }, + { "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, + { "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, + { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + { "DAC1R", NULL, "DAC1R Mixer" }, + { "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, + { "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, + { "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + /* DAC2/AIF2 outputs */ + { "DAC2L", NULL, "AIF2DAC2L Mixer" }, + { "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, + { "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, + + { "DAC2R", NULL, "AIF2DAC2R Mixer" }, + { "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, + { "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, + + /* Output stages */ + { "Headphone PGA", NULL, "DAC1L" }, + { "Headphone PGA", NULL, "DAC1R" }, + + { "Headphone PGA", NULL, "DAC2L" }, + { "Headphone PGA", NULL, "DAC2R" }, + + { "Headphone PGA", NULL, "Headphone Supply" }, + { "Headphone PGA", NULL, "CLK_SYS" }, + { "Headphone PGA", NULL, "LDO2" }, + + { "HP1L", NULL, "Headphone PGA" }, + { "HP1R", NULL, "Headphone PGA" }, + + { "SPK1L Driver", "DAC1L", "DAC1L" }, + { "SPK1L Driver", "DAC1R", "DAC1R" }, + { "SPK1L Driver", "DAC2L", "DAC2L" }, + { "SPK1L Driver", "DAC2R", "DAC2R" }, + { "SPK1L Driver", NULL, "CLK_SYS" }, + + { "SPK1R Driver", "DAC1L", "DAC1L" }, + { "SPK1R Driver", "DAC1R", "DAC1R" }, + { "SPK1R Driver", "DAC2L", "DAC2L" }, + { "SPK1R Driver", "DAC2R", "DAC2R" }, + { "SPK1R Driver", NULL, "CLK_SYS" }, + + { "SPK2L Driver", "DAC1L", "DAC1L" }, + { "SPK2L Driver", "DAC1R", "DAC1R" }, + { "SPK2L Driver", "DAC2L", "DAC2L" }, + { "SPK2L Driver", "DAC2R", "DAC2R" }, + { "SPK2L Driver", NULL, "CLK_SYS" }, + + { "SPK2R Driver", "DAC1L", "DAC1L" }, + { "SPK2R Driver", "DAC1R", "DAC1R" }, + { "SPK2R Driver", "DAC2L", "DAC2L" }, + { "SPK2R Driver", "DAC2R", "DAC2R" }, + { "SPK2R Driver", NULL, "CLK_SYS" }, + + { "SPK1L", NULL, "SPK1L Driver" }, + { "SPK1R", NULL, "SPK1R Driver" }, + { "SPK2L", NULL, "SPK2L Driver" }, + { "SPK2R", NULL, "SPK2R Driver" } +}; + +static bool wm8995_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8995_SOFTWARE_RESET: + case WM8995_POWER_MANAGEMENT_1: + case WM8995_POWER_MANAGEMENT_2: + case WM8995_POWER_MANAGEMENT_3: + case WM8995_POWER_MANAGEMENT_4: + case WM8995_POWER_MANAGEMENT_5: + case WM8995_LEFT_LINE_INPUT_1_VOLUME: + case WM8995_RIGHT_LINE_INPUT_1_VOLUME: + case WM8995_LEFT_LINE_INPUT_CONTROL: + case WM8995_DAC1_LEFT_VOLUME: + case WM8995_DAC1_RIGHT_VOLUME: + case WM8995_DAC2_LEFT_VOLUME: + case WM8995_DAC2_RIGHT_VOLUME: + case WM8995_OUTPUT_VOLUME_ZC_1: + case WM8995_MICBIAS_1: + case WM8995_MICBIAS_2: + case WM8995_LDO_1: + case WM8995_LDO_2: + case WM8995_ACCESSORY_DETECT_MODE1: + case WM8995_ACCESSORY_DETECT_MODE2: + case WM8995_HEADPHONE_DETECT1: + case WM8995_HEADPHONE_DETECT2: + case WM8995_MIC_DETECT_1: + case WM8995_MIC_DETECT_2: + case WM8995_CHARGE_PUMP_1: + case WM8995_CLASS_W_1: + case WM8995_DC_SERVO_1: + case WM8995_DC_SERVO_2: + case WM8995_DC_SERVO_3: + case WM8995_DC_SERVO_5: + case WM8995_DC_SERVO_6: + case WM8995_DC_SERVO_7: + case WM8995_DC_SERVO_READBACK_0: + case WM8995_ANALOGUE_HP_1: + case WM8995_ANALOGUE_HP_2: + case WM8995_CHIP_REVISION: + case WM8995_CONTROL_INTERFACE_1: + case WM8995_CONTROL_INTERFACE_2: + case WM8995_WRITE_SEQUENCER_CTRL_1: + case WM8995_WRITE_SEQUENCER_CTRL_2: + case WM8995_AIF1_CLOCKING_1: + case WM8995_AIF1_CLOCKING_2: + case WM8995_AIF2_CLOCKING_1: + case WM8995_AIF2_CLOCKING_2: + case WM8995_CLOCKING_1: + case WM8995_CLOCKING_2: + case WM8995_AIF1_RATE: + case WM8995_AIF2_RATE: + case WM8995_RATE_STATUS: + case WM8995_FLL1_CONTROL_1: + case WM8995_FLL1_CONTROL_2: + case WM8995_FLL1_CONTROL_3: + case WM8995_FLL1_CONTROL_4: + case WM8995_FLL1_CONTROL_5: + case WM8995_FLL2_CONTROL_1: + case WM8995_FLL2_CONTROL_2: + case WM8995_FLL2_CONTROL_3: + case WM8995_FLL2_CONTROL_4: + case WM8995_FLL2_CONTROL_5: + case WM8995_AIF1_CONTROL_1: + case WM8995_AIF1_CONTROL_2: + case WM8995_AIF1_MASTER_SLAVE: + case WM8995_AIF1_BCLK: + case WM8995_AIF1ADC_LRCLK: + case WM8995_AIF1DAC_LRCLK: + case WM8995_AIF1DAC_DATA: + case WM8995_AIF1ADC_DATA: + case WM8995_AIF2_CONTROL_1: + case WM8995_AIF2_CONTROL_2: + case WM8995_AIF2_MASTER_SLAVE: + case WM8995_AIF2_BCLK: + case WM8995_AIF2ADC_LRCLK: + case WM8995_AIF2DAC_LRCLK: + case WM8995_AIF2DAC_DATA: + case WM8995_AIF2ADC_DATA: + case WM8995_AIF1_ADC1_LEFT_VOLUME: + case WM8995_AIF1_ADC1_RIGHT_VOLUME: + case WM8995_AIF1_DAC1_LEFT_VOLUME: + case WM8995_AIF1_DAC1_RIGHT_VOLUME: + case WM8995_AIF1_ADC2_LEFT_VOLUME: + case WM8995_AIF1_ADC2_RIGHT_VOLUME: + case WM8995_AIF1_DAC2_LEFT_VOLUME: + case WM8995_AIF1_DAC2_RIGHT_VOLUME: + case WM8995_AIF1_ADC1_FILTERS: + case WM8995_AIF1_ADC2_FILTERS: + case WM8995_AIF1_DAC1_FILTERS_1: + case WM8995_AIF1_DAC1_FILTERS_2: + case WM8995_AIF1_DAC2_FILTERS_1: + case WM8995_AIF1_DAC2_FILTERS_2: + case WM8995_AIF1_DRC1_1: + case WM8995_AIF1_DRC1_2: + case WM8995_AIF1_DRC1_3: + case WM8995_AIF1_DRC1_4: + case WM8995_AIF1_DRC1_5: + case WM8995_AIF1_DRC2_1: + case WM8995_AIF1_DRC2_2: + case WM8995_AIF1_DRC2_3: + case WM8995_AIF1_DRC2_4: + case WM8995_AIF1_DRC2_5: + case WM8995_AIF1_DAC1_EQ_GAINS_1: + case WM8995_AIF1_DAC1_EQ_GAINS_2: + case WM8995_AIF1_DAC1_EQ_BAND_1_A: + case WM8995_AIF1_DAC1_EQ_BAND_1_B: + case WM8995_AIF1_DAC1_EQ_BAND_1_PG: + case WM8995_AIF1_DAC1_EQ_BAND_2_A: + case WM8995_AIF1_DAC1_EQ_BAND_2_B: + case WM8995_AIF1_DAC1_EQ_BAND_2_C: + case WM8995_AIF1_DAC1_EQ_BAND_2_PG: + case WM8995_AIF1_DAC1_EQ_BAND_3_A: + case WM8995_AIF1_DAC1_EQ_BAND_3_B: + case WM8995_AIF1_DAC1_EQ_BAND_3_C: + case WM8995_AIF1_DAC1_EQ_BAND_3_PG: + case WM8995_AIF1_DAC1_EQ_BAND_4_A: + case WM8995_AIF1_DAC1_EQ_BAND_4_B: + case WM8995_AIF1_DAC1_EQ_BAND_4_C: + case WM8995_AIF1_DAC1_EQ_BAND_4_PG: + case WM8995_AIF1_DAC1_EQ_BAND_5_A: + case WM8995_AIF1_DAC1_EQ_BAND_5_B: + case WM8995_AIF1_DAC1_EQ_BAND_5_PG: + case WM8995_AIF1_DAC2_EQ_GAINS_1: + case WM8995_AIF1_DAC2_EQ_GAINS_2: + case WM8995_AIF1_DAC2_EQ_BAND_1_A: + case WM8995_AIF1_DAC2_EQ_BAND_1_B: + case WM8995_AIF1_DAC2_EQ_BAND_1_PG: + case WM8995_AIF1_DAC2_EQ_BAND_2_A: + case WM8995_AIF1_DAC2_EQ_BAND_2_B: + case WM8995_AIF1_DAC2_EQ_BAND_2_C: + case WM8995_AIF1_DAC2_EQ_BAND_2_PG: + case WM8995_AIF1_DAC2_EQ_BAND_3_A: + case WM8995_AIF1_DAC2_EQ_BAND_3_B: + case WM8995_AIF1_DAC2_EQ_BAND_3_C: + case WM8995_AIF1_DAC2_EQ_BAND_3_PG: + case WM8995_AIF1_DAC2_EQ_BAND_4_A: + case WM8995_AIF1_DAC2_EQ_BAND_4_B: + case WM8995_AIF1_DAC2_EQ_BAND_4_C: + case WM8995_AIF1_DAC2_EQ_BAND_4_PG: + case WM8995_AIF1_DAC2_EQ_BAND_5_A: + case WM8995_AIF1_DAC2_EQ_BAND_5_B: + case WM8995_AIF1_DAC2_EQ_BAND_5_PG: + case WM8995_AIF2_ADC_LEFT_VOLUME: + case WM8995_AIF2_ADC_RIGHT_VOLUME: + case WM8995_AIF2_DAC_LEFT_VOLUME: + case WM8995_AIF2_DAC_RIGHT_VOLUME: + case WM8995_AIF2_ADC_FILTERS: + case WM8995_AIF2_DAC_FILTERS_1: + case WM8995_AIF2_DAC_FILTERS_2: + case WM8995_AIF2_DRC_1: + case WM8995_AIF2_DRC_2: + case WM8995_AIF2_DRC_3: + case WM8995_AIF2_DRC_4: + case WM8995_AIF2_DRC_5: + case WM8995_AIF2_EQ_GAINS_1: + case WM8995_AIF2_EQ_GAINS_2: + case WM8995_AIF2_EQ_BAND_1_A: + case WM8995_AIF2_EQ_BAND_1_B: + case WM8995_AIF2_EQ_BAND_1_PG: + case WM8995_AIF2_EQ_BAND_2_A: + case WM8995_AIF2_EQ_BAND_2_B: + case WM8995_AIF2_EQ_BAND_2_C: + case WM8995_AIF2_EQ_BAND_2_PG: + case WM8995_AIF2_EQ_BAND_3_A: + case WM8995_AIF2_EQ_BAND_3_B: + case WM8995_AIF2_EQ_BAND_3_C: + case WM8995_AIF2_EQ_BAND_3_PG: + case WM8995_AIF2_EQ_BAND_4_A: + case WM8995_AIF2_EQ_BAND_4_B: + case WM8995_AIF2_EQ_BAND_4_C: + case WM8995_AIF2_EQ_BAND_4_PG: + case WM8995_AIF2_EQ_BAND_5_A: + case WM8995_AIF2_EQ_BAND_5_B: + case WM8995_AIF2_EQ_BAND_5_PG: + case WM8995_DAC1_MIXER_VOLUMES: + case WM8995_DAC1_LEFT_MIXER_ROUTING: + case WM8995_DAC1_RIGHT_MIXER_ROUTING: + case WM8995_DAC2_MIXER_VOLUMES: + case WM8995_DAC2_LEFT_MIXER_ROUTING: + case WM8995_DAC2_RIGHT_MIXER_ROUTING: + case WM8995_AIF1_ADC1_LEFT_MIXER_ROUTING: + case WM8995_AIF1_ADC1_RIGHT_MIXER_ROUTING: + case WM8995_AIF1_ADC2_LEFT_MIXER_ROUTING: + case WM8995_AIF1_ADC2_RIGHT_MIXER_ROUTING: + case WM8995_DAC_SOFTMUTE: + case WM8995_OVERSAMPLING: + case WM8995_SIDETONE: + case WM8995_GPIO_1: + case WM8995_GPIO_2: + case WM8995_GPIO_3: + case WM8995_GPIO_4: + case WM8995_GPIO_5: + case WM8995_GPIO_6: + case WM8995_GPIO_7: + case WM8995_GPIO_8: + case WM8995_GPIO_9: + case WM8995_GPIO_10: + case WM8995_GPIO_11: + case WM8995_GPIO_12: + case WM8995_GPIO_13: + case WM8995_GPIO_14: + case WM8995_PULL_CONTROL_1: + case WM8995_PULL_CONTROL_2: + case WM8995_INTERRUPT_STATUS_1: + case WM8995_INTERRUPT_STATUS_2: + case WM8995_INTERRUPT_RAW_STATUS_2: + case WM8995_INTERRUPT_STATUS_1_MASK: + case WM8995_INTERRUPT_STATUS_2_MASK: + case WM8995_INTERRUPT_CONTROL: + case WM8995_LEFT_PDM_SPEAKER_1: + case WM8995_RIGHT_PDM_SPEAKER_1: + case WM8995_PDM_SPEAKER_1_MUTE_SEQUENCE: + case WM8995_LEFT_PDM_SPEAKER_2: + case WM8995_RIGHT_PDM_SPEAKER_2: + case WM8995_PDM_SPEAKER_2_MUTE_SEQUENCE: + return true; + default: + return false; + } +} + +static bool wm8995_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8995_SOFTWARE_RESET: + case WM8995_DC_SERVO_READBACK_0: + case WM8995_INTERRUPT_STATUS_1: + case WM8995_INTERRUPT_STATUS_2: + case WM8995_INTERRUPT_CONTROL: + case WM8995_ACCESSORY_DETECT_MODE1: + case WM8995_ACCESSORY_DETECT_MODE2: + case WM8995_HEADPHONE_DETECT1: + case WM8995_HEADPHONE_DETECT2: + case WM8995_RATE_STATUS: + return true; + default: + return false; + } +} + +static int wm8995_aif_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_component *component = dai->component; + int mute_reg; + + switch (dai->id) { + case 0: + mute_reg = WM8995_AIF1_DAC1_FILTERS_1; + break; + case 1: + mute_reg = WM8995_AIF2_DAC_FILTERS_1; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, mute_reg, WM8995_AIF1DAC1_MUTE_MASK, + !!mute << WM8995_AIF1DAC1_MUTE_SHIFT); + return 0; +} + +static int wm8995_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component; + int master; + int aif; + + component = dai->component; + + master = 0; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + master = WM8995_AIF1_MSTR; + break; + default: + dev_err(dai->dev, "Unknown master/slave configuration\n"); + return -EINVAL; + } + + aif = 0; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8995_AIF1_LRCLK_INV; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif |= (0x3 << WM8995_AIF1_FMT_SHIFT); + break; + case SND_SOC_DAIFMT_I2S: + aif |= (0x2 << WM8995_AIF1_FMT_SHIFT); + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif |= (0x1 << WM8995_AIF1_FMT_SHIFT); + break; + default: + dev_err(dai->dev, "Unknown dai format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8995_AIF1_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8995_AIF1_BCLK_INV | WM8995_AIF1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8995_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8995_AIF1_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, WM8995_AIF1_CONTROL_1, + WM8995_AIF1_BCLK_INV_MASK | + WM8995_AIF1_LRCLK_INV_MASK | + WM8995_AIF1_FMT_MASK, aif); + snd_soc_component_update_bits(component, WM8995_AIF1_MASTER_SLAVE, + WM8995_AIF1_MSTR_MASK, master); + return 0; +} + +static const int srs[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, + 48000, 88200, 96000 +}; + +static const int fs_ratios[] = { + -1 /* reserved */, + 128, 192, 256, 384, 512, 768, 1024, 1408, 1536 +}; + +static const int bclk_divs[] = { + 10, 15, 20, 30, 40, 55, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480 +}; + +static int wm8995_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component; + struct wm8995_priv *wm8995; + int aif1_reg; + int bclk_reg; + int lrclk_reg; + int rate_reg; + int bclk_rate; + int aif1; + int lrclk, bclk; + int i, rate_val, best, best_val, cur_val; + + component = dai->component; + wm8995 = snd_soc_component_get_drvdata(component); + + switch (dai->id) { + case 0: + aif1_reg = WM8995_AIF1_CONTROL_1; + bclk_reg = WM8995_AIF1_BCLK; + rate_reg = WM8995_AIF1_RATE; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK /* || + wm8995->lrclk_shared[0] */) { + lrclk_reg = WM8995_AIF1DAC_LRCLK; + } else { + lrclk_reg = WM8995_AIF1ADC_LRCLK; + dev_dbg(component->dev, "AIF1 using split LRCLK\n"); + } + break; + case 1: + aif1_reg = WM8995_AIF2_CONTROL_1; + bclk_reg = WM8995_AIF2_BCLK; + rate_reg = WM8995_AIF2_RATE; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK /* || + wm8995->lrclk_shared[1] */) { + lrclk_reg = WM8995_AIF2DAC_LRCLK; + } else { + lrclk_reg = WM8995_AIF2ADC_LRCLK; + dev_dbg(component->dev, "AIF2 using split LRCLK\n"); + } + break; + default: + return -EINVAL; + } + + bclk_rate = snd_soc_params_to_bclk(params); + if (bclk_rate < 0) + return bclk_rate; + + aif1 = 0; + switch (params_width(params)) { + case 16: + break; + case 20: + aif1 |= (0x1 << WM8995_AIF1_WL_SHIFT); + break; + case 24: + aif1 |= (0x2 << WM8995_AIF1_WL_SHIFT); + break; + case 32: + aif1 |= (0x3 << WM8995_AIF1_WL_SHIFT); + break; + default: + dev_err(dai->dev, "Unsupported word length %u\n", + params_width(params)); + return -EINVAL; + } + + /* try to find a suitable sample rate */ + for (i = 0; i < ARRAY_SIZE(srs); ++i) + if (srs[i] == params_rate(params)) + break; + if (i == ARRAY_SIZE(srs)) { + dev_err(dai->dev, "Sample rate %d is not supported\n", + params_rate(params)); + return -EINVAL; + } + rate_val = i << WM8995_AIF1_SR_SHIFT; + + dev_dbg(dai->dev, "Sample rate is %dHz\n", srs[i]); + dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n", + dai->id + 1, wm8995->aifclk[dai->id], bclk_rate); + + /* AIFCLK/fs ratio; look for a close match in either direction */ + best = 1; + best_val = abs((fs_ratios[1] * params_rate(params)) + - wm8995->aifclk[dai->id]); + for (i = 2; i < ARRAY_SIZE(fs_ratios); i++) { + cur_val = abs((fs_ratios[i] * params_rate(params)) + - wm8995->aifclk[dai->id]); + if (cur_val >= best_val) + continue; + best = i; + best_val = cur_val; + } + rate_val |= best; + + dev_dbg(dai->dev, "Selected AIF%dCLK/fs = %d\n", + dai->id + 1, fs_ratios[best]); + + /* + * We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + best = 0; + bclk = 0; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = (wm8995->aifclk[dai->id] * 10 / bclk_divs[i]) - bclk_rate; + if (cur_val < 0) /* BCLK table is sorted */ + break; + best = i; + } + bclk |= best << WM8995_AIF1_BCLK_DIV_SHIFT; + + bclk_rate = wm8995->aifclk[dai->id] * 10 / bclk_divs[best]; + dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n", + bclk_divs[best], bclk_rate); + + lrclk = bclk_rate / params_rate(params); + dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n", + lrclk, bclk_rate / lrclk); + + snd_soc_component_update_bits(component, aif1_reg, + WM8995_AIF1_WL_MASK, aif1); + snd_soc_component_update_bits(component, bclk_reg, + WM8995_AIF1_BCLK_DIV_MASK, bclk); + snd_soc_component_update_bits(component, lrclk_reg, + WM8995_AIF1DAC_RATE_MASK, lrclk); + snd_soc_component_update_bits(component, rate_reg, + WM8995_AIF1_SR_MASK | + WM8995_AIF1CLK_RATE_MASK, rate_val); + return 0; +} + +static int wm8995_set_tristate(struct snd_soc_dai *codec_dai, int tristate) +{ + struct snd_soc_component *component = codec_dai->component; + int reg, val, mask; + + switch (codec_dai->id) { + case 0: + reg = WM8995_AIF1_MASTER_SLAVE; + mask = WM8995_AIF1_TRI; + break; + case 1: + reg = WM8995_AIF2_MASTER_SLAVE; + mask = WM8995_AIF2_TRI; + break; + case 2: + reg = WM8995_POWER_MANAGEMENT_5; + mask = WM8995_AIF3_TRI; + break; + default: + return -EINVAL; + } + + if (tristate) + val = mask; + else + val = 0; + + return snd_soc_component_update_bits(component, reg, mask, val); +} + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +struct fll_div { + u16 outdiv; + u16 n; + u16 k; + u16 clk_ref_div; + u16 fll_fratio; +}; + +static int wm8995_get_fll_config(struct fll_div *fll, + int freq_in, int freq_out) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("FLL input=%dHz, output=%dHz\n", freq_in, freq_out); + + /* Scale the input frequency down to <= 13.5MHz */ + fll->clk_ref_div = 0; + while (freq_in > 13500000) { + fll->clk_ref_div++; + freq_in /= 2; + + if (fll->clk_ref_div > 3) + return -EINVAL; + } + pr_debug("CLK_REF_DIV=%d, Fref=%dHz\n", fll->clk_ref_div, freq_in); + + /* Scale the output to give 90MHz<=Fvco<=100MHz */ + fll->outdiv = 3; + while (freq_out * (fll->outdiv + 1) < 90000000) { + fll->outdiv++; + if (fll->outdiv > 63) + return -EINVAL; + } + freq_out *= fll->outdiv + 1; + pr_debug("OUTDIV=%d, Fvco=%dHz\n", fll->outdiv, freq_out); + + if (freq_in > 1000000) { + fll->fll_fratio = 0; + } else if (freq_in > 256000) { + fll->fll_fratio = 1; + freq_in *= 2; + } else if (freq_in > 128000) { + fll->fll_fratio = 2; + freq_in *= 4; + } else if (freq_in > 64000) { + fll->fll_fratio = 3; + freq_in *= 8; + } else { + fll->fll_fratio = 4; + freq_in *= 16; + } + pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in); + + /* Now, calculate N.K */ + Ndiv = freq_out / freq_in; + + fll->n = Ndiv; + Nmod = freq_out % freq_in; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, freq_in); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll->k = K / 10; + + pr_debug("N=%x K=%x\n", fll->n, fll->k); + + return 0; +} + +static int wm8995_set_fll(struct snd_soc_dai *dai, int id, + int src, unsigned int freq_in, + unsigned int freq_out) +{ + struct snd_soc_component *component; + struct wm8995_priv *wm8995; + int reg_offset, ret; + struct fll_div fll; + u16 reg, aif1, aif2; + + component = dai->component; + wm8995 = snd_soc_component_get_drvdata(component); + + aif1 = snd_soc_component_read32(component, WM8995_AIF1_CLOCKING_1) + & WM8995_AIF1CLK_ENA; + + aif2 = snd_soc_component_read32(component, WM8995_AIF2_CLOCKING_1) + & WM8995_AIF2CLK_ENA; + + switch (id) { + case WM8995_FLL1: + reg_offset = 0; + id = 0; + break; + case WM8995_FLL2: + reg_offset = 0x20; + id = 1; + break; + default: + return -EINVAL; + } + + switch (src) { + case 0: + /* Allow no source specification when stopping */ + if (freq_out) + return -EINVAL; + break; + case WM8995_FLL_SRC_MCLK1: + case WM8995_FLL_SRC_MCLK2: + case WM8995_FLL_SRC_LRCLK: + case WM8995_FLL_SRC_BCLK: + break; + default: + return -EINVAL; + } + + /* Are we changing anything? */ + if (wm8995->fll[id].src == src && + wm8995->fll[id].in == freq_in && wm8995->fll[id].out == freq_out) + return 0; + + /* If we're stopping the FLL redo the old config - no + * registers will actually be written but we avoid GCC flow + * analysis bugs spewing warnings. + */ + if (freq_out) + ret = wm8995_get_fll_config(&fll, freq_in, freq_out); + else + ret = wm8995_get_fll_config(&fll, wm8995->fll[id].in, + wm8995->fll[id].out); + if (ret < 0) + return ret; + + /* Gate the AIF clocks while we reclock */ + snd_soc_component_update_bits(component, WM8995_AIF1_CLOCKING_1, + WM8995_AIF1CLK_ENA_MASK, 0); + snd_soc_component_update_bits(component, WM8995_AIF2_CLOCKING_1, + WM8995_AIF2CLK_ENA_MASK, 0); + + /* We always need to disable the FLL while reconfiguring */ + snd_soc_component_update_bits(component, WM8995_FLL1_CONTROL_1 + reg_offset, + WM8995_FLL1_ENA_MASK, 0); + + reg = (fll.outdiv << WM8995_FLL1_OUTDIV_SHIFT) | + (fll.fll_fratio << WM8995_FLL1_FRATIO_SHIFT); + snd_soc_component_update_bits(component, WM8995_FLL1_CONTROL_2 + reg_offset, + WM8995_FLL1_OUTDIV_MASK | + WM8995_FLL1_FRATIO_MASK, reg); + + snd_soc_component_write(component, WM8995_FLL1_CONTROL_3 + reg_offset, fll.k); + + snd_soc_component_update_bits(component, WM8995_FLL1_CONTROL_4 + reg_offset, + WM8995_FLL1_N_MASK, + fll.n << WM8995_FLL1_N_SHIFT); + + snd_soc_component_update_bits(component, WM8995_FLL1_CONTROL_5 + reg_offset, + WM8995_FLL1_REFCLK_DIV_MASK | + WM8995_FLL1_REFCLK_SRC_MASK, + (fll.clk_ref_div << WM8995_FLL1_REFCLK_DIV_SHIFT) | + (src - 1)); + + if (freq_out) + snd_soc_component_update_bits(component, WM8995_FLL1_CONTROL_1 + reg_offset, + WM8995_FLL1_ENA_MASK, WM8995_FLL1_ENA); + + wm8995->fll[id].in = freq_in; + wm8995->fll[id].out = freq_out; + wm8995->fll[id].src = src; + + /* Enable any gated AIF clocks */ + snd_soc_component_update_bits(component, WM8995_AIF1_CLOCKING_1, + WM8995_AIF1CLK_ENA_MASK, aif1); + snd_soc_component_update_bits(component, WM8995_AIF2_CLOCKING_1, + WM8995_AIF2CLK_ENA_MASK, aif2); + + configure_clock(component); + + return 0; +} + +static int wm8995_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component; + struct wm8995_priv *wm8995; + + component = dai->component; + wm8995 = snd_soc_component_get_drvdata(component); + + switch (dai->id) { + case 0: + case 1: + break; + default: + /* AIF3 shares clocking with AIF1/2 */ + return -EINVAL; + } + + switch (clk_id) { + case WM8995_SYSCLK_MCLK1: + wm8995->sysclk[dai->id] = WM8995_SYSCLK_MCLK1; + wm8995->mclk[0] = freq; + dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n", + dai->id + 1, freq); + break; + case WM8995_SYSCLK_MCLK2: + wm8995->sysclk[dai->id] = WM8995_SYSCLK_MCLK2; + wm8995->mclk[1] = freq; + dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n", + dai->id + 1, freq); + break; + case WM8995_SYSCLK_FLL1: + wm8995->sysclk[dai->id] = WM8995_SYSCLK_FLL1; + dev_dbg(dai->dev, "AIF%d using FLL1\n", dai->id + 1); + break; + case WM8995_SYSCLK_FLL2: + wm8995->sysclk[dai->id] = WM8995_SYSCLK_FLL2; + dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id + 1); + break; + case WM8995_SYSCLK_OPCLK: + default: + dev_err(dai->dev, "Unknown clock source %d\n", clk_id); + return -EINVAL; + } + + configure_clock(component); + + return 0; +} + +static int wm8995_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8995_priv *wm8995; + int ret; + + wm8995 = snd_soc_component_get_drvdata(component); + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) + return ret; + + ret = regcache_sync(wm8995->regmap); + if (ret) { + dev_err(component->dev, + "Failed to sync cache: %d\n", ret); + return ret; + } + + snd_soc_component_update_bits(component, WM8995_POWER_MANAGEMENT_1, + WM8995_BG_ENA_MASK, WM8995_BG_ENA); + } + break; + case SND_SOC_BIAS_OFF: + snd_soc_component_update_bits(component, WM8995_POWER_MANAGEMENT_1, + WM8995_BG_ENA_MASK, 0); + regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + break; + } + + return 0; +} + +static void wm8995_remove(struct snd_soc_component *component) +{ + struct wm8995_priv *wm8995; + int i; + + wm8995 = snd_soc_component_get_drvdata(component); + + for (i = 0; i < ARRAY_SIZE(wm8995->supplies); ++i) + regulator_unregister_notifier(wm8995->supplies[i].consumer, + &wm8995->disable_nb[i]); + + regulator_bulk_free(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); +} + +static int wm8995_probe(struct snd_soc_component *component) +{ + struct wm8995_priv *wm8995; + int i; + int ret; + + wm8995 = snd_soc_component_get_drvdata(component); + wm8995->component = component; + + for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) + wm8995->supplies[i].supply = wm8995_supply_names[i]; + + ret = regulator_bulk_get(component->dev, ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) { + dev_err(component->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8995->disable_nb[0].notifier_call = wm8995_regulator_event_0; + wm8995->disable_nb[1].notifier_call = wm8995_regulator_event_1; + wm8995->disable_nb[2].notifier_call = wm8995_regulator_event_2; + wm8995->disable_nb[3].notifier_call = wm8995_regulator_event_3; + wm8995->disable_nb[4].notifier_call = wm8995_regulator_event_4; + wm8995->disable_nb[5].notifier_call = wm8995_regulator_event_5; + wm8995->disable_nb[6].notifier_call = wm8995_regulator_event_6; + wm8995->disable_nb[7].notifier_call = wm8995_regulator_event_7; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) { + ret = regulator_register_notifier(wm8995->supplies[i].consumer, + &wm8995->disable_nb[i]); + if (ret) { + dev_err(component->dev, + "Failed to register regulator notifier: %d\n", + ret); + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) { + dev_err(component->dev, "Failed to enable supplies: %d\n", ret); + goto err_reg_get; + } + + ret = snd_soc_component_read32(component, WM8995_SOFTWARE_RESET); + if (ret < 0) { + dev_err(component->dev, "Failed to read device ID: %d\n", ret); + goto err_reg_enable; + } + + if (ret != 0x8995) { + dev_err(component->dev, "Invalid device ID: %#x\n", ret); + ret = -EINVAL; + goto err_reg_enable; + } + + ret = snd_soc_component_write(component, WM8995_SOFTWARE_RESET, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to issue reset: %d\n", ret); + goto err_reg_enable; + } + + /* Latch volume updates (right only; we always do left then right). */ + snd_soc_component_update_bits(component, WM8995_AIF1_DAC1_RIGHT_VOLUME, + WM8995_AIF1DAC1_VU_MASK, WM8995_AIF1DAC1_VU); + snd_soc_component_update_bits(component, WM8995_AIF1_DAC2_RIGHT_VOLUME, + WM8995_AIF1DAC2_VU_MASK, WM8995_AIF1DAC2_VU); + snd_soc_component_update_bits(component, WM8995_AIF2_DAC_RIGHT_VOLUME, + WM8995_AIF2DAC_VU_MASK, WM8995_AIF2DAC_VU); + snd_soc_component_update_bits(component, WM8995_AIF1_ADC1_RIGHT_VOLUME, + WM8995_AIF1ADC1_VU_MASK, WM8995_AIF1ADC1_VU); + snd_soc_component_update_bits(component, WM8995_AIF1_ADC2_RIGHT_VOLUME, + WM8995_AIF1ADC2_VU_MASK, WM8995_AIF1ADC2_VU); + snd_soc_component_update_bits(component, WM8995_AIF2_ADC_RIGHT_VOLUME, + WM8995_AIF2ADC_VU_MASK, WM8995_AIF1ADC2_VU); + snd_soc_component_update_bits(component, WM8995_DAC1_RIGHT_VOLUME, + WM8995_DAC1_VU_MASK, WM8995_DAC1_VU); + snd_soc_component_update_bits(component, WM8995_DAC2_RIGHT_VOLUME, + WM8995_DAC2_VU_MASK, WM8995_DAC2_VU); + snd_soc_component_update_bits(component, WM8995_RIGHT_LINE_INPUT_1_VOLUME, + WM8995_IN1_VU_MASK, WM8995_IN1_VU); + + wm8995_update_class_w(component); + + return 0; + +err_reg_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); +err_reg_get: + regulator_bulk_free(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); + return ret; +} + +#define WM8995_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8995_aif1_dai_ops = { + .set_sysclk = wm8995_set_dai_sysclk, + .set_fmt = wm8995_set_dai_fmt, + .hw_params = wm8995_hw_params, + .digital_mute = wm8995_aif_mute, + .set_pll = wm8995_set_fll, + .set_tristate = wm8995_set_tristate, +}; + +static const struct snd_soc_dai_ops wm8995_aif2_dai_ops = { + .set_sysclk = wm8995_set_dai_sysclk, + .set_fmt = wm8995_set_dai_fmt, + .hw_params = wm8995_hw_params, + .digital_mute = wm8995_aif_mute, + .set_pll = wm8995_set_fll, + .set_tristate = wm8995_set_tristate, +}; + +static const struct snd_soc_dai_ops wm8995_aif3_dai_ops = { + .set_tristate = wm8995_set_tristate, +}; + +static struct snd_soc_dai_driver wm8995_dai[] = { + { + .name = "wm8995-aif1", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8995_FORMATS + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8995_FORMATS + }, + .ops = &wm8995_aif1_dai_ops + }, + { + .name = "wm8995-aif2", + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8995_FORMATS + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8995_FORMATS + }, + .ops = &wm8995_aif2_dai_ops + }, + { + .name = "wm8995-aif3", + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8995_FORMATS + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8995_FORMATS + }, + .ops = &wm8995_aif3_dai_ops + } +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8995 = { + .probe = wm8995_probe, + .remove = wm8995_remove, + .set_bias_level = wm8995_set_bias_level, + .controls = wm8995_snd_controls, + .num_controls = ARRAY_SIZE(wm8995_snd_controls), + .dapm_widgets = wm8995_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8995_dapm_widgets), + .dapm_routes = wm8995_intercon, + .num_dapm_routes = ARRAY_SIZE(wm8995_intercon), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm8995_regmap = { + .reg_bits = 16, + .val_bits = 16, + + .max_register = WM8995_MAX_REGISTER, + .reg_defaults = wm8995_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8995_reg_defaults), + .volatile_reg = wm8995_volatile, + .readable_reg = wm8995_readable, + .cache_type = REGCACHE_RBTREE, +}; + +#if defined(CONFIG_SPI_MASTER) +static int wm8995_spi_probe(struct spi_device *spi) +{ + struct wm8995_priv *wm8995; + int ret; + + wm8995 = devm_kzalloc(&spi->dev, sizeof(*wm8995), GFP_KERNEL); + if (!wm8995) + return -ENOMEM; + + spi_set_drvdata(spi, wm8995); + + wm8995->regmap = devm_regmap_init_spi(spi, &wm8995_regmap); + if (IS_ERR(wm8995->regmap)) { + ret = PTR_ERR(wm8995->regmap); + dev_err(&spi->dev, "Failed to register regmap: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm8995, wm8995_dai, + ARRAY_SIZE(wm8995_dai)); + return ret; +} + +static struct spi_driver wm8995_spi_driver = { + .driver = { + .name = "wm8995", + }, + .probe = wm8995_spi_probe, +}; +#endif + +#if IS_ENABLED(CONFIG_I2C) +static int wm8995_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8995_priv *wm8995; + int ret; + + wm8995 = devm_kzalloc(&i2c->dev, sizeof(*wm8995), GFP_KERNEL); + if (!wm8995) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8995); + + wm8995->regmap = devm_regmap_init_i2c(i2c, &wm8995_regmap); + if (IS_ERR(wm8995->regmap)) { + ret = PTR_ERR(wm8995->regmap); + dev_err(&i2c->dev, "Failed to register regmap: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8995, wm8995_dai, + ARRAY_SIZE(wm8995_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id wm8995_i2c_id[] = { + {"wm8995", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, wm8995_i2c_id); + +static struct i2c_driver wm8995_i2c_driver = { + .driver = { + .name = "wm8995", + }, + .probe = wm8995_i2c_probe, + .id_table = wm8995_i2c_id +}; +#endif + +static int __init wm8995_modinit(void) +{ + int ret = 0; + +#if IS_ENABLED(CONFIG_I2C) + ret = i2c_add_driver(&wm8995_i2c_driver); + if (ret) { + printk(KERN_ERR "Failed to register wm8995 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8995_spi_driver); + if (ret) { + printk(KERN_ERR "Failed to register wm8995 SPI driver: %d\n", + ret); + } +#endif + return ret; +} + +module_init(wm8995_modinit); + +static void __exit wm8995_exit(void) +{ +#if IS_ENABLED(CONFIG_I2C) + i2c_del_driver(&wm8995_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8995_spi_driver); +#endif +} + +module_exit(wm8995_exit); + +MODULE_DESCRIPTION("ASoC WM8995 driver"); +MODULE_AUTHOR("Dimitris Papastamos "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8995.h b/sound/soc/codecs/wm8995.h new file mode 100644 index 000000000..508ad27fe --- /dev/null +++ b/sound/soc/codecs/wm8995.h @@ -0,0 +1,4266 @@ +/* + * wm8995.h -- WM8995 ALSA SoC Audio driver + * + * Copyright 2010 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8995_H +#define _WM8995_H + +#include + +/* + * Register values. + */ +#define WM8995_SOFTWARE_RESET 0x00 +#define WM8995_POWER_MANAGEMENT_1 0x01 +#define WM8995_POWER_MANAGEMENT_2 0x02 +#define WM8995_POWER_MANAGEMENT_3 0x03 +#define WM8995_POWER_MANAGEMENT_4 0x04 +#define WM8995_POWER_MANAGEMENT_5 0x05 +#define WM8995_LEFT_LINE_INPUT_1_VOLUME 0x10 +#define WM8995_RIGHT_LINE_INPUT_1_VOLUME 0x11 +#define WM8995_LEFT_LINE_INPUT_CONTROL 0x12 +#define WM8995_DAC1_LEFT_VOLUME 0x18 +#define WM8995_DAC1_RIGHT_VOLUME 0x19 +#define WM8995_DAC2_LEFT_VOLUME 0x1A +#define WM8995_DAC2_RIGHT_VOLUME 0x1B +#define WM8995_OUTPUT_VOLUME_ZC_1 0x1C +#define WM8995_MICBIAS_1 0x20 +#define WM8995_MICBIAS_2 0x21 +#define WM8995_LDO_1 0x28 +#define WM8995_LDO_2 0x29 +#define WM8995_ACCESSORY_DETECT_MODE1 0x30 +#define WM8995_ACCESSORY_DETECT_MODE2 0x31 +#define WM8995_HEADPHONE_DETECT1 0x34 +#define WM8995_HEADPHONE_DETECT2 0x35 +#define WM8995_MIC_DETECT_1 0x38 +#define WM8995_MIC_DETECT_2 0x39 +#define WM8995_CHARGE_PUMP_1 0x40 +#define WM8995_CLASS_W_1 0x45 +#define WM8995_DC_SERVO_1 0x50 +#define WM8995_DC_SERVO_2 0x51 +#define WM8995_DC_SERVO_3 0x52 +#define WM8995_DC_SERVO_5 0x54 +#define WM8995_DC_SERVO_6 0x55 +#define WM8995_DC_SERVO_7 0x56 +#define WM8995_DC_SERVO_READBACK_0 0x57 +#define WM8995_ANALOGUE_HP_1 0x60 +#define WM8995_ANALOGUE_HP_2 0x61 +#define WM8995_CHIP_REVISION 0x100 +#define WM8995_CONTROL_INTERFACE_1 0x101 +#define WM8995_CONTROL_INTERFACE_2 0x102 +#define WM8995_WRITE_SEQUENCER_CTRL_1 0x110 +#define WM8995_WRITE_SEQUENCER_CTRL_2 0x111 +#define WM8995_AIF1_CLOCKING_1 0x200 +#define WM8995_AIF1_CLOCKING_2 0x201 +#define WM8995_AIF2_CLOCKING_1 0x204 +#define WM8995_AIF2_CLOCKING_2 0x205 +#define WM8995_CLOCKING_1 0x208 +#define WM8995_CLOCKING_2 0x209 +#define WM8995_AIF1_RATE 0x210 +#define WM8995_AIF2_RATE 0x211 +#define WM8995_RATE_STATUS 0x212 +#define WM8995_FLL1_CONTROL_1 0x220 +#define WM8995_FLL1_CONTROL_2 0x221 +#define WM8995_FLL1_CONTROL_3 0x222 +#define WM8995_FLL1_CONTROL_4 0x223 +#define WM8995_FLL1_CONTROL_5 0x224 +#define WM8995_FLL2_CONTROL_1 0x240 +#define WM8995_FLL2_CONTROL_2 0x241 +#define WM8995_FLL2_CONTROL_3 0x242 +#define WM8995_FLL2_CONTROL_4 0x243 +#define WM8995_FLL2_CONTROL_5 0x244 +#define WM8995_AIF1_CONTROL_1 0x300 +#define WM8995_AIF1_CONTROL_2 0x301 +#define WM8995_AIF1_MASTER_SLAVE 0x302 +#define WM8995_AIF1_BCLK 0x303 +#define WM8995_AIF1ADC_LRCLK 0x304 +#define WM8995_AIF1DAC_LRCLK 0x305 +#define WM8995_AIF1DAC_DATA 0x306 +#define WM8995_AIF1ADC_DATA 0x307 +#define WM8995_AIF2_CONTROL_1 0x310 +#define WM8995_AIF2_CONTROL_2 0x311 +#define WM8995_AIF2_MASTER_SLAVE 0x312 +#define WM8995_AIF2_BCLK 0x313 +#define WM8995_AIF2ADC_LRCLK 0x314 +#define WM8995_AIF2DAC_LRCLK 0x315 +#define WM8995_AIF2DAC_DATA 0x316 +#define WM8995_AIF2ADC_DATA 0x317 +#define WM8995_AIF1_ADC1_LEFT_VOLUME 0x400 +#define WM8995_AIF1_ADC1_RIGHT_VOLUME 0x401 +#define WM8995_AIF1_DAC1_LEFT_VOLUME 0x402 +#define WM8995_AIF1_DAC1_RIGHT_VOLUME 0x403 +#define WM8995_AIF1_ADC2_LEFT_VOLUME 0x404 +#define WM8995_AIF1_ADC2_RIGHT_VOLUME 0x405 +#define WM8995_AIF1_DAC2_LEFT_VOLUME 0x406 +#define WM8995_AIF1_DAC2_RIGHT_VOLUME 0x407 +#define WM8995_AIF1_ADC1_FILTERS 0x410 +#define WM8995_AIF1_ADC2_FILTERS 0x411 +#define WM8995_AIF1_DAC1_FILTERS_1 0x420 +#define WM8995_AIF1_DAC1_FILTERS_2 0x421 +#define WM8995_AIF1_DAC2_FILTERS_1 0x422 +#define WM8995_AIF1_DAC2_FILTERS_2 0x423 +#define WM8995_AIF1_DRC1_1 0x440 +#define WM8995_AIF1_DRC1_2 0x441 +#define WM8995_AIF1_DRC1_3 0x442 +#define WM8995_AIF1_DRC1_4 0x443 +#define WM8995_AIF1_DRC1_5 0x444 +#define WM8995_AIF1_DRC2_1 0x450 +#define WM8995_AIF1_DRC2_2 0x451 +#define WM8995_AIF1_DRC2_3 0x452 +#define WM8995_AIF1_DRC2_4 0x453 +#define WM8995_AIF1_DRC2_5 0x454 +#define WM8995_AIF1_DAC1_EQ_GAINS_1 0x480 +#define WM8995_AIF1_DAC1_EQ_GAINS_2 0x481 +#define WM8995_AIF1_DAC1_EQ_BAND_1_A 0x482 +#define WM8995_AIF1_DAC1_EQ_BAND_1_B 0x483 +#define WM8995_AIF1_DAC1_EQ_BAND_1_PG 0x484 +#define WM8995_AIF1_DAC1_EQ_BAND_2_A 0x485 +#define WM8995_AIF1_DAC1_EQ_BAND_2_B 0x486 +#define WM8995_AIF1_DAC1_EQ_BAND_2_C 0x487 +#define WM8995_AIF1_DAC1_EQ_BAND_2_PG 0x488 +#define WM8995_AIF1_DAC1_EQ_BAND_3_A 0x489 +#define WM8995_AIF1_DAC1_EQ_BAND_3_B 0x48A +#define WM8995_AIF1_DAC1_EQ_BAND_3_C 0x48B +#define WM8995_AIF1_DAC1_EQ_BAND_3_PG 0x48C +#define WM8995_AIF1_DAC1_EQ_BAND_4_A 0x48D +#define WM8995_AIF1_DAC1_EQ_BAND_4_B 0x48E +#define WM8995_AIF1_DAC1_EQ_BAND_4_C 0x48F +#define WM8995_AIF1_DAC1_EQ_BAND_4_PG 0x490 +#define WM8995_AIF1_DAC1_EQ_BAND_5_A 0x491 +#define WM8995_AIF1_DAC1_EQ_BAND_5_B 0x492 +#define WM8995_AIF1_DAC1_EQ_BAND_5_PG 0x493 +#define WM8995_AIF1_DAC2_EQ_GAINS_1 0x4A0 +#define WM8995_AIF1_DAC2_EQ_GAINS_2 0x4A1 +#define WM8995_AIF1_DAC2_EQ_BAND_1_A 0x4A2 +#define WM8995_AIF1_DAC2_EQ_BAND_1_B 0x4A3 +#define WM8995_AIF1_DAC2_EQ_BAND_1_PG 0x4A4 +#define WM8995_AIF1_DAC2_EQ_BAND_2_A 0x4A5 +#define WM8995_AIF1_DAC2_EQ_BAND_2_B 0x4A6 +#define WM8995_AIF1_DAC2_EQ_BAND_2_C 0x4A7 +#define WM8995_AIF1_DAC2_EQ_BAND_2_PG 0x4A8 +#define WM8995_AIF1_DAC2_EQ_BAND_3_A 0x4A9 +#define WM8995_AIF1_DAC2_EQ_BAND_3_B 0x4AA +#define WM8995_AIF1_DAC2_EQ_BAND_3_C 0x4AB +#define WM8995_AIF1_DAC2_EQ_BAND_3_PG 0x4AC +#define WM8995_AIF1_DAC2_EQ_BAND_4_A 0x4AD +#define WM8995_AIF1_DAC2_EQ_BAND_4_B 0x4AE +#define WM8995_AIF1_DAC2_EQ_BAND_4_C 0x4AF +#define WM8995_AIF1_DAC2_EQ_BAND_4_PG 0x4B0 +#define WM8995_AIF1_DAC2_EQ_BAND_5_A 0x4B1 +#define WM8995_AIF1_DAC2_EQ_BAND_5_B 0x4B2 +#define WM8995_AIF1_DAC2_EQ_BAND_5_PG 0x4B3 +#define WM8995_AIF2_ADC_LEFT_VOLUME 0x500 +#define WM8995_AIF2_ADC_RIGHT_VOLUME 0x501 +#define WM8995_AIF2_DAC_LEFT_VOLUME 0x502 +#define WM8995_AIF2_DAC_RIGHT_VOLUME 0x503 +#define WM8995_AIF2_ADC_FILTERS 0x510 +#define WM8995_AIF2_DAC_FILTERS_1 0x520 +#define WM8995_AIF2_DAC_FILTERS_2 0x521 +#define WM8995_AIF2_DRC_1 0x540 +#define WM8995_AIF2_DRC_2 0x541 +#define WM8995_AIF2_DRC_3 0x542 +#define WM8995_AIF2_DRC_4 0x543 +#define WM8995_AIF2_DRC_5 0x544 +#define WM8995_AIF2_EQ_GAINS_1 0x580 +#define WM8995_AIF2_EQ_GAINS_2 0x581 +#define WM8995_AIF2_EQ_BAND_1_A 0x582 +#define WM8995_AIF2_EQ_BAND_1_B 0x583 +#define WM8995_AIF2_EQ_BAND_1_PG 0x584 +#define WM8995_AIF2_EQ_BAND_2_A 0x585 +#define WM8995_AIF2_EQ_BAND_2_B 0x586 +#define WM8995_AIF2_EQ_BAND_2_C 0x587 +#define WM8995_AIF2_EQ_BAND_2_PG 0x588 +#define WM8995_AIF2_EQ_BAND_3_A 0x589 +#define WM8995_AIF2_EQ_BAND_3_B 0x58A +#define WM8995_AIF2_EQ_BAND_3_C 0x58B +#define WM8995_AIF2_EQ_BAND_3_PG 0x58C +#define WM8995_AIF2_EQ_BAND_4_A 0x58D +#define WM8995_AIF2_EQ_BAND_4_B 0x58E +#define WM8995_AIF2_EQ_BAND_4_C 0x58F +#define WM8995_AIF2_EQ_BAND_4_PG 0x590 +#define WM8995_AIF2_EQ_BAND_5_A 0x591 +#define WM8995_AIF2_EQ_BAND_5_B 0x592 +#define WM8995_AIF2_EQ_BAND_5_PG 0x593 +#define WM8995_DAC1_MIXER_VOLUMES 0x600 +#define WM8995_DAC1_LEFT_MIXER_ROUTING 0x601 +#define WM8995_DAC1_RIGHT_MIXER_ROUTING 0x602 +#define WM8995_DAC2_MIXER_VOLUMES 0x603 +#define WM8995_DAC2_LEFT_MIXER_ROUTING 0x604 +#define WM8995_DAC2_RIGHT_MIXER_ROUTING 0x605 +#define WM8995_AIF1_ADC1_LEFT_MIXER_ROUTING 0x606 +#define WM8995_AIF1_ADC1_RIGHT_MIXER_ROUTING 0x607 +#define WM8995_AIF1_ADC2_LEFT_MIXER_ROUTING 0x608 +#define WM8995_AIF1_ADC2_RIGHT_MIXER_ROUTING 0x609 +#define WM8995_DAC_SOFTMUTE 0x610 +#define WM8995_OVERSAMPLING 0x620 +#define WM8995_SIDETONE 0x621 +#define WM8995_GPIO_1 0x700 +#define WM8995_GPIO_2 0x701 +#define WM8995_GPIO_3 0x702 +#define WM8995_GPIO_4 0x703 +#define WM8995_GPIO_5 0x704 +#define WM8995_GPIO_6 0x705 +#define WM8995_GPIO_7 0x706 +#define WM8995_GPIO_8 0x707 +#define WM8995_GPIO_9 0x708 +#define WM8995_GPIO_10 0x709 +#define WM8995_GPIO_11 0x70A +#define WM8995_GPIO_12 0x70B +#define WM8995_GPIO_13 0x70C +#define WM8995_GPIO_14 0x70D +#define WM8995_PULL_CONTROL_1 0x720 +#define WM8995_PULL_CONTROL_2 0x721 +#define WM8995_INTERRUPT_STATUS_1 0x730 +#define WM8995_INTERRUPT_STATUS_2 0x731 +#define WM8995_INTERRUPT_RAW_STATUS_2 0x732 +#define WM8995_INTERRUPT_STATUS_1_MASK 0x738 +#define WM8995_INTERRUPT_STATUS_2_MASK 0x739 +#define WM8995_INTERRUPT_CONTROL 0x740 +#define WM8995_LEFT_PDM_SPEAKER_1 0x800 +#define WM8995_RIGHT_PDM_SPEAKER_1 0x801 +#define WM8995_PDM_SPEAKER_1_MUTE_SEQUENCE 0x802 +#define WM8995_LEFT_PDM_SPEAKER_2 0x808 +#define WM8995_RIGHT_PDM_SPEAKER_2 0x809 +#define WM8995_PDM_SPEAKER_2_MUTE_SEQUENCE 0x80A +#define WM8995_WRITE_SEQUENCER_0 0x3000 +#define WM8995_WRITE_SEQUENCER_1 0x3001 +#define WM8995_WRITE_SEQUENCER_2 0x3002 +#define WM8995_WRITE_SEQUENCER_3 0x3003 +#define WM8995_WRITE_SEQUENCER_4 0x3004 +#define WM8995_WRITE_SEQUENCER_5 0x3005 +#define WM8995_WRITE_SEQUENCER_6 0x3006 +#define WM8995_WRITE_SEQUENCER_7 0x3007 +#define WM8995_WRITE_SEQUENCER_8 0x3008 +#define WM8995_WRITE_SEQUENCER_9 0x3009 +#define WM8995_WRITE_SEQUENCER_10 0x300A +#define WM8995_WRITE_SEQUENCER_11 0x300B +#define WM8995_WRITE_SEQUENCER_12 0x300C +#define WM8995_WRITE_SEQUENCER_13 0x300D +#define WM8995_WRITE_SEQUENCER_14 0x300E +#define WM8995_WRITE_SEQUENCER_15 0x300F +#define WM8995_WRITE_SEQUENCER_16 0x3010 +#define WM8995_WRITE_SEQUENCER_17 0x3011 +#define WM8995_WRITE_SEQUENCER_18 0x3012 +#define WM8995_WRITE_SEQUENCER_19 0x3013 +#define WM8995_WRITE_SEQUENCER_20 0x3014 +#define WM8995_WRITE_SEQUENCER_21 0x3015 +#define WM8995_WRITE_SEQUENCER_22 0x3016 +#define WM8995_WRITE_SEQUENCER_23 0x3017 +#define WM8995_WRITE_SEQUENCER_24 0x3018 +#define WM8995_WRITE_SEQUENCER_25 0x3019 +#define WM8995_WRITE_SEQUENCER_26 0x301A +#define WM8995_WRITE_SEQUENCER_27 0x301B +#define WM8995_WRITE_SEQUENCER_28 0x301C +#define WM8995_WRITE_SEQUENCER_29 0x301D +#define WM8995_WRITE_SEQUENCER_30 0x301E +#define WM8995_WRITE_SEQUENCER_31 0x301F +#define WM8995_WRITE_SEQUENCER_32 0x3020 +#define WM8995_WRITE_SEQUENCER_33 0x3021 +#define WM8995_WRITE_SEQUENCER_34 0x3022 +#define WM8995_WRITE_SEQUENCER_35 0x3023 +#define WM8995_WRITE_SEQUENCER_36 0x3024 +#define WM8995_WRITE_SEQUENCER_37 0x3025 +#define WM8995_WRITE_SEQUENCER_38 0x3026 +#define WM8995_WRITE_SEQUENCER_39 0x3027 +#define WM8995_WRITE_SEQUENCER_40 0x3028 +#define WM8995_WRITE_SEQUENCER_41 0x3029 +#define WM8995_WRITE_SEQUENCER_42 0x302A +#define WM8995_WRITE_SEQUENCER_43 0x302B +#define WM8995_WRITE_SEQUENCER_44 0x302C +#define WM8995_WRITE_SEQUENCER_45 0x302D +#define WM8995_WRITE_SEQUENCER_46 0x302E +#define WM8995_WRITE_SEQUENCER_47 0x302F +#define WM8995_WRITE_SEQUENCER_48 0x3030 +#define WM8995_WRITE_SEQUENCER_49 0x3031 +#define WM8995_WRITE_SEQUENCER_50 0x3032 +#define WM8995_WRITE_SEQUENCER_51 0x3033 +#define WM8995_WRITE_SEQUENCER_52 0x3034 +#define WM8995_WRITE_SEQUENCER_53 0x3035 +#define WM8995_WRITE_SEQUENCER_54 0x3036 +#define WM8995_WRITE_SEQUENCER_55 0x3037 +#define WM8995_WRITE_SEQUENCER_56 0x3038 +#define WM8995_WRITE_SEQUENCER_57 0x3039 +#define WM8995_WRITE_SEQUENCER_58 0x303A +#define WM8995_WRITE_SEQUENCER_59 0x303B +#define WM8995_WRITE_SEQUENCER_60 0x303C +#define WM8995_WRITE_SEQUENCER_61 0x303D +#define WM8995_WRITE_SEQUENCER_62 0x303E +#define WM8995_WRITE_SEQUENCER_63 0x303F +#define WM8995_WRITE_SEQUENCER_64 0x3040 +#define WM8995_WRITE_SEQUENCER_65 0x3041 +#define WM8995_WRITE_SEQUENCER_66 0x3042 +#define WM8995_WRITE_SEQUENCER_67 0x3043 +#define WM8995_WRITE_SEQUENCER_68 0x3044 +#define WM8995_WRITE_SEQUENCER_69 0x3045 +#define WM8995_WRITE_SEQUENCER_70 0x3046 +#define WM8995_WRITE_SEQUENCER_71 0x3047 +#define WM8995_WRITE_SEQUENCER_72 0x3048 +#define WM8995_WRITE_SEQUENCER_73 0x3049 +#define WM8995_WRITE_SEQUENCER_74 0x304A +#define WM8995_WRITE_SEQUENCER_75 0x304B +#define WM8995_WRITE_SEQUENCER_76 0x304C +#define WM8995_WRITE_SEQUENCER_77 0x304D +#define WM8995_WRITE_SEQUENCER_78 0x304E +#define WM8995_WRITE_SEQUENCER_79 0x304F +#define WM8995_WRITE_SEQUENCER_80 0x3050 +#define WM8995_WRITE_SEQUENCER_81 0x3051 +#define WM8995_WRITE_SEQUENCER_82 0x3052 +#define WM8995_WRITE_SEQUENCER_83 0x3053 +#define WM8995_WRITE_SEQUENCER_84 0x3054 +#define WM8995_WRITE_SEQUENCER_85 0x3055 +#define WM8995_WRITE_SEQUENCER_86 0x3056 +#define WM8995_WRITE_SEQUENCER_87 0x3057 +#define WM8995_WRITE_SEQUENCER_88 0x3058 +#define WM8995_WRITE_SEQUENCER_89 0x3059 +#define WM8995_WRITE_SEQUENCER_90 0x305A +#define WM8995_WRITE_SEQUENCER_91 0x305B +#define WM8995_WRITE_SEQUENCER_92 0x305C +#define WM8995_WRITE_SEQUENCER_93 0x305D +#define WM8995_WRITE_SEQUENCER_94 0x305E +#define WM8995_WRITE_SEQUENCER_95 0x305F +#define WM8995_WRITE_SEQUENCER_96 0x3060 +#define WM8995_WRITE_SEQUENCER_97 0x3061 +#define WM8995_WRITE_SEQUENCER_98 0x3062 +#define WM8995_WRITE_SEQUENCER_99 0x3063 +#define WM8995_WRITE_SEQUENCER_100 0x3064 +#define WM8995_WRITE_SEQUENCER_101 0x3065 +#define WM8995_WRITE_SEQUENCER_102 0x3066 +#define WM8995_WRITE_SEQUENCER_103 0x3067 +#define WM8995_WRITE_SEQUENCER_104 0x3068 +#define WM8995_WRITE_SEQUENCER_105 0x3069 +#define WM8995_WRITE_SEQUENCER_106 0x306A +#define WM8995_WRITE_SEQUENCER_107 0x306B +#define WM8995_WRITE_SEQUENCER_108 0x306C +#define WM8995_WRITE_SEQUENCER_109 0x306D +#define WM8995_WRITE_SEQUENCER_110 0x306E +#define WM8995_WRITE_SEQUENCER_111 0x306F +#define WM8995_WRITE_SEQUENCER_112 0x3070 +#define WM8995_WRITE_SEQUENCER_113 0x3071 +#define WM8995_WRITE_SEQUENCER_114 0x3072 +#define WM8995_WRITE_SEQUENCER_115 0x3073 +#define WM8995_WRITE_SEQUENCER_116 0x3074 +#define WM8995_WRITE_SEQUENCER_117 0x3075 +#define WM8995_WRITE_SEQUENCER_118 0x3076 +#define WM8995_WRITE_SEQUENCER_119 0x3077 +#define WM8995_WRITE_SEQUENCER_120 0x3078 +#define WM8995_WRITE_SEQUENCER_121 0x3079 +#define WM8995_WRITE_SEQUENCER_122 0x307A +#define WM8995_WRITE_SEQUENCER_123 0x307B +#define WM8995_WRITE_SEQUENCER_124 0x307C +#define WM8995_WRITE_SEQUENCER_125 0x307D +#define WM8995_WRITE_SEQUENCER_126 0x307E +#define WM8995_WRITE_SEQUENCER_127 0x307F +#define WM8995_WRITE_SEQUENCER_128 0x3080 +#define WM8995_WRITE_SEQUENCER_129 0x3081 +#define WM8995_WRITE_SEQUENCER_130 0x3082 +#define WM8995_WRITE_SEQUENCER_131 0x3083 +#define WM8995_WRITE_SEQUENCER_132 0x3084 +#define WM8995_WRITE_SEQUENCER_133 0x3085 +#define WM8995_WRITE_SEQUENCER_134 0x3086 +#define WM8995_WRITE_SEQUENCER_135 0x3087 +#define WM8995_WRITE_SEQUENCER_136 0x3088 +#define WM8995_WRITE_SEQUENCER_137 0x3089 +#define WM8995_WRITE_SEQUENCER_138 0x308A +#define WM8995_WRITE_SEQUENCER_139 0x308B +#define WM8995_WRITE_SEQUENCER_140 0x308C +#define WM8995_WRITE_SEQUENCER_141 0x308D +#define WM8995_WRITE_SEQUENCER_142 0x308E +#define WM8995_WRITE_SEQUENCER_143 0x308F +#define WM8995_WRITE_SEQUENCER_144 0x3090 +#define WM8995_WRITE_SEQUENCER_145 0x3091 +#define WM8995_WRITE_SEQUENCER_146 0x3092 +#define WM8995_WRITE_SEQUENCER_147 0x3093 +#define WM8995_WRITE_SEQUENCER_148 0x3094 +#define WM8995_WRITE_SEQUENCER_149 0x3095 +#define WM8995_WRITE_SEQUENCER_150 0x3096 +#define WM8995_WRITE_SEQUENCER_151 0x3097 +#define WM8995_WRITE_SEQUENCER_152 0x3098 +#define WM8995_WRITE_SEQUENCER_153 0x3099 +#define WM8995_WRITE_SEQUENCER_154 0x309A +#define WM8995_WRITE_SEQUENCER_155 0x309B +#define WM8995_WRITE_SEQUENCER_156 0x309C +#define WM8995_WRITE_SEQUENCER_157 0x309D +#define WM8995_WRITE_SEQUENCER_158 0x309E +#define WM8995_WRITE_SEQUENCER_159 0x309F +#define WM8995_WRITE_SEQUENCER_160 0x30A0 +#define WM8995_WRITE_SEQUENCER_161 0x30A1 +#define WM8995_WRITE_SEQUENCER_162 0x30A2 +#define WM8995_WRITE_SEQUENCER_163 0x30A3 +#define WM8995_WRITE_SEQUENCER_164 0x30A4 +#define WM8995_WRITE_SEQUENCER_165 0x30A5 +#define WM8995_WRITE_SEQUENCER_166 0x30A6 +#define WM8995_WRITE_SEQUENCER_167 0x30A7 +#define WM8995_WRITE_SEQUENCER_168 0x30A8 +#define WM8995_WRITE_SEQUENCER_169 0x30A9 +#define WM8995_WRITE_SEQUENCER_170 0x30AA +#define WM8995_WRITE_SEQUENCER_171 0x30AB +#define WM8995_WRITE_SEQUENCER_172 0x30AC +#define WM8995_WRITE_SEQUENCER_173 0x30AD +#define WM8995_WRITE_SEQUENCER_174 0x30AE +#define WM8995_WRITE_SEQUENCER_175 0x30AF +#define WM8995_WRITE_SEQUENCER_176 0x30B0 +#define WM8995_WRITE_SEQUENCER_177 0x30B1 +#define WM8995_WRITE_SEQUENCER_178 0x30B2 +#define WM8995_WRITE_SEQUENCER_179 0x30B3 +#define WM8995_WRITE_SEQUENCER_180 0x30B4 +#define WM8995_WRITE_SEQUENCER_181 0x30B5 +#define WM8995_WRITE_SEQUENCER_182 0x30B6 +#define WM8995_WRITE_SEQUENCER_183 0x30B7 +#define WM8995_WRITE_SEQUENCER_184 0x30B8 +#define WM8995_WRITE_SEQUENCER_185 0x30B9 +#define WM8995_WRITE_SEQUENCER_186 0x30BA +#define WM8995_WRITE_SEQUENCER_187 0x30BB +#define WM8995_WRITE_SEQUENCER_188 0x30BC +#define WM8995_WRITE_SEQUENCER_189 0x30BD +#define WM8995_WRITE_SEQUENCER_190 0x30BE +#define WM8995_WRITE_SEQUENCER_191 0x30BF +#define WM8995_WRITE_SEQUENCER_192 0x30C0 +#define WM8995_WRITE_SEQUENCER_193 0x30C1 +#define WM8995_WRITE_SEQUENCER_194 0x30C2 +#define WM8995_WRITE_SEQUENCER_195 0x30C3 +#define WM8995_WRITE_SEQUENCER_196 0x30C4 +#define WM8995_WRITE_SEQUENCER_197 0x30C5 +#define WM8995_WRITE_SEQUENCER_198 0x30C6 +#define WM8995_WRITE_SEQUENCER_199 0x30C7 +#define WM8995_WRITE_SEQUENCER_200 0x30C8 +#define WM8995_WRITE_SEQUENCER_201 0x30C9 +#define WM8995_WRITE_SEQUENCER_202 0x30CA +#define WM8995_WRITE_SEQUENCER_203 0x30CB +#define WM8995_WRITE_SEQUENCER_204 0x30CC +#define WM8995_WRITE_SEQUENCER_205 0x30CD +#define WM8995_WRITE_SEQUENCER_206 0x30CE +#define WM8995_WRITE_SEQUENCER_207 0x30CF +#define WM8995_WRITE_SEQUENCER_208 0x30D0 +#define WM8995_WRITE_SEQUENCER_209 0x30D1 +#define WM8995_WRITE_SEQUENCER_210 0x30D2 +#define WM8995_WRITE_SEQUENCER_211 0x30D3 +#define WM8995_WRITE_SEQUENCER_212 0x30D4 +#define WM8995_WRITE_SEQUENCER_213 0x30D5 +#define WM8995_WRITE_SEQUENCER_214 0x30D6 +#define WM8995_WRITE_SEQUENCER_215 0x30D7 +#define WM8995_WRITE_SEQUENCER_216 0x30D8 +#define WM8995_WRITE_SEQUENCER_217 0x30D9 +#define WM8995_WRITE_SEQUENCER_218 0x30DA +#define WM8995_WRITE_SEQUENCER_219 0x30DB +#define WM8995_WRITE_SEQUENCER_220 0x30DC +#define WM8995_WRITE_SEQUENCER_221 0x30DD +#define WM8995_WRITE_SEQUENCER_222 0x30DE +#define WM8995_WRITE_SEQUENCER_223 0x30DF +#define WM8995_WRITE_SEQUENCER_224 0x30E0 +#define WM8995_WRITE_SEQUENCER_225 0x30E1 +#define WM8995_WRITE_SEQUENCER_226 0x30E2 +#define WM8995_WRITE_SEQUENCER_227 0x30E3 +#define WM8995_WRITE_SEQUENCER_228 0x30E4 +#define WM8995_WRITE_SEQUENCER_229 0x30E5 +#define WM8995_WRITE_SEQUENCER_230 0x30E6 +#define WM8995_WRITE_SEQUENCER_231 0x30E7 +#define WM8995_WRITE_SEQUENCER_232 0x30E8 +#define WM8995_WRITE_SEQUENCER_233 0x30E9 +#define WM8995_WRITE_SEQUENCER_234 0x30EA +#define WM8995_WRITE_SEQUENCER_235 0x30EB +#define WM8995_WRITE_SEQUENCER_236 0x30EC +#define WM8995_WRITE_SEQUENCER_237 0x30ED +#define WM8995_WRITE_SEQUENCER_238 0x30EE +#define WM8995_WRITE_SEQUENCER_239 0x30EF +#define WM8995_WRITE_SEQUENCER_240 0x30F0 +#define WM8995_WRITE_SEQUENCER_241 0x30F1 +#define WM8995_WRITE_SEQUENCER_242 0x30F2 +#define WM8995_WRITE_SEQUENCER_243 0x30F3 +#define WM8995_WRITE_SEQUENCER_244 0x30F4 +#define WM8995_WRITE_SEQUENCER_245 0x30F5 +#define WM8995_WRITE_SEQUENCER_246 0x30F6 +#define WM8995_WRITE_SEQUENCER_247 0x30F7 +#define WM8995_WRITE_SEQUENCER_248 0x30F8 +#define WM8995_WRITE_SEQUENCER_249 0x30F9 +#define WM8995_WRITE_SEQUENCER_250 0x30FA +#define WM8995_WRITE_SEQUENCER_251 0x30FB +#define WM8995_WRITE_SEQUENCER_252 0x30FC +#define WM8995_WRITE_SEQUENCER_253 0x30FD +#define WM8995_WRITE_SEQUENCER_254 0x30FE +#define WM8995_WRITE_SEQUENCER_255 0x30FF +#define WM8995_WRITE_SEQUENCER_256 0x3100 +#define WM8995_WRITE_SEQUENCER_257 0x3101 +#define WM8995_WRITE_SEQUENCER_258 0x3102 +#define WM8995_WRITE_SEQUENCER_259 0x3103 +#define WM8995_WRITE_SEQUENCER_260 0x3104 +#define WM8995_WRITE_SEQUENCER_261 0x3105 +#define WM8995_WRITE_SEQUENCER_262 0x3106 +#define WM8995_WRITE_SEQUENCER_263 0x3107 +#define WM8995_WRITE_SEQUENCER_264 0x3108 +#define WM8995_WRITE_SEQUENCER_265 0x3109 +#define WM8995_WRITE_SEQUENCER_266 0x310A +#define WM8995_WRITE_SEQUENCER_267 0x310B +#define WM8995_WRITE_SEQUENCER_268 0x310C +#define WM8995_WRITE_SEQUENCER_269 0x310D +#define WM8995_WRITE_SEQUENCER_270 0x310E +#define WM8995_WRITE_SEQUENCER_271 0x310F +#define WM8995_WRITE_SEQUENCER_272 0x3110 +#define WM8995_WRITE_SEQUENCER_273 0x3111 +#define WM8995_WRITE_SEQUENCER_274 0x3112 +#define WM8995_WRITE_SEQUENCER_275 0x3113 +#define WM8995_WRITE_SEQUENCER_276 0x3114 +#define WM8995_WRITE_SEQUENCER_277 0x3115 +#define WM8995_WRITE_SEQUENCER_278 0x3116 +#define WM8995_WRITE_SEQUENCER_279 0x3117 +#define WM8995_WRITE_SEQUENCER_280 0x3118 +#define WM8995_WRITE_SEQUENCER_281 0x3119 +#define WM8995_WRITE_SEQUENCER_282 0x311A +#define WM8995_WRITE_SEQUENCER_283 0x311B +#define WM8995_WRITE_SEQUENCER_284 0x311C +#define WM8995_WRITE_SEQUENCER_285 0x311D +#define WM8995_WRITE_SEQUENCER_286 0x311E +#define WM8995_WRITE_SEQUENCER_287 0x311F +#define WM8995_WRITE_SEQUENCER_288 0x3120 +#define WM8995_WRITE_SEQUENCER_289 0x3121 +#define WM8995_WRITE_SEQUENCER_290 0x3122 +#define WM8995_WRITE_SEQUENCER_291 0x3123 +#define WM8995_WRITE_SEQUENCER_292 0x3124 +#define WM8995_WRITE_SEQUENCER_293 0x3125 +#define WM8995_WRITE_SEQUENCER_294 0x3126 +#define WM8995_WRITE_SEQUENCER_295 0x3127 +#define WM8995_WRITE_SEQUENCER_296 0x3128 +#define WM8995_WRITE_SEQUENCER_297 0x3129 +#define WM8995_WRITE_SEQUENCER_298 0x312A +#define WM8995_WRITE_SEQUENCER_299 0x312B +#define WM8995_WRITE_SEQUENCER_300 0x312C +#define WM8995_WRITE_SEQUENCER_301 0x312D +#define WM8995_WRITE_SEQUENCER_302 0x312E +#define WM8995_WRITE_SEQUENCER_303 0x312F +#define WM8995_WRITE_SEQUENCER_304 0x3130 +#define WM8995_WRITE_SEQUENCER_305 0x3131 +#define WM8995_WRITE_SEQUENCER_306 0x3132 +#define WM8995_WRITE_SEQUENCER_307 0x3133 +#define WM8995_WRITE_SEQUENCER_308 0x3134 +#define WM8995_WRITE_SEQUENCER_309 0x3135 +#define WM8995_WRITE_SEQUENCER_310 0x3136 +#define WM8995_WRITE_SEQUENCER_311 0x3137 +#define WM8995_WRITE_SEQUENCER_312 0x3138 +#define WM8995_WRITE_SEQUENCER_313 0x3139 +#define WM8995_WRITE_SEQUENCER_314 0x313A +#define WM8995_WRITE_SEQUENCER_315 0x313B +#define WM8995_WRITE_SEQUENCER_316 0x313C +#define WM8995_WRITE_SEQUENCER_317 0x313D +#define WM8995_WRITE_SEQUENCER_318 0x313E +#define WM8995_WRITE_SEQUENCER_319 0x313F +#define WM8995_WRITE_SEQUENCER_320 0x3140 +#define WM8995_WRITE_SEQUENCER_321 0x3141 +#define WM8995_WRITE_SEQUENCER_322 0x3142 +#define WM8995_WRITE_SEQUENCER_323 0x3143 +#define WM8995_WRITE_SEQUENCER_324 0x3144 +#define WM8995_WRITE_SEQUENCER_325 0x3145 +#define WM8995_WRITE_SEQUENCER_326 0x3146 +#define WM8995_WRITE_SEQUENCER_327 0x3147 +#define WM8995_WRITE_SEQUENCER_328 0x3148 +#define WM8995_WRITE_SEQUENCER_329 0x3149 +#define WM8995_WRITE_SEQUENCER_330 0x314A +#define WM8995_WRITE_SEQUENCER_331 0x314B +#define WM8995_WRITE_SEQUENCER_332 0x314C +#define WM8995_WRITE_SEQUENCER_333 0x314D +#define WM8995_WRITE_SEQUENCER_334 0x314E +#define WM8995_WRITE_SEQUENCER_335 0x314F +#define WM8995_WRITE_SEQUENCER_336 0x3150 +#define WM8995_WRITE_SEQUENCER_337 0x3151 +#define WM8995_WRITE_SEQUENCER_338 0x3152 +#define WM8995_WRITE_SEQUENCER_339 0x3153 +#define WM8995_WRITE_SEQUENCER_340 0x3154 +#define WM8995_WRITE_SEQUENCER_341 0x3155 +#define WM8995_WRITE_SEQUENCER_342 0x3156 +#define WM8995_WRITE_SEQUENCER_343 0x3157 +#define WM8995_WRITE_SEQUENCER_344 0x3158 +#define WM8995_WRITE_SEQUENCER_345 0x3159 +#define WM8995_WRITE_SEQUENCER_346 0x315A +#define WM8995_WRITE_SEQUENCER_347 0x315B +#define WM8995_WRITE_SEQUENCER_348 0x315C +#define WM8995_WRITE_SEQUENCER_349 0x315D +#define WM8995_WRITE_SEQUENCER_350 0x315E +#define WM8995_WRITE_SEQUENCER_351 0x315F +#define WM8995_WRITE_SEQUENCER_352 0x3160 +#define WM8995_WRITE_SEQUENCER_353 0x3161 +#define WM8995_WRITE_SEQUENCER_354 0x3162 +#define WM8995_WRITE_SEQUENCER_355 0x3163 +#define WM8995_WRITE_SEQUENCER_356 0x3164 +#define WM8995_WRITE_SEQUENCER_357 0x3165 +#define WM8995_WRITE_SEQUENCER_358 0x3166 +#define WM8995_WRITE_SEQUENCER_359 0x3167 +#define WM8995_WRITE_SEQUENCER_360 0x3168 +#define WM8995_WRITE_SEQUENCER_361 0x3169 +#define WM8995_WRITE_SEQUENCER_362 0x316A +#define WM8995_WRITE_SEQUENCER_363 0x316B +#define WM8995_WRITE_SEQUENCER_364 0x316C +#define WM8995_WRITE_SEQUENCER_365 0x316D +#define WM8995_WRITE_SEQUENCER_366 0x316E +#define WM8995_WRITE_SEQUENCER_367 0x316F +#define WM8995_WRITE_SEQUENCER_368 0x3170 +#define WM8995_WRITE_SEQUENCER_369 0x3171 +#define WM8995_WRITE_SEQUENCER_370 0x3172 +#define WM8995_WRITE_SEQUENCER_371 0x3173 +#define WM8995_WRITE_SEQUENCER_372 0x3174 +#define WM8995_WRITE_SEQUENCER_373 0x3175 +#define WM8995_WRITE_SEQUENCER_374 0x3176 +#define WM8995_WRITE_SEQUENCER_375 0x3177 +#define WM8995_WRITE_SEQUENCER_376 0x3178 +#define WM8995_WRITE_SEQUENCER_377 0x3179 +#define WM8995_WRITE_SEQUENCER_378 0x317A +#define WM8995_WRITE_SEQUENCER_379 0x317B +#define WM8995_WRITE_SEQUENCER_380 0x317C +#define WM8995_WRITE_SEQUENCER_381 0x317D +#define WM8995_WRITE_SEQUENCER_382 0x317E +#define WM8995_WRITE_SEQUENCER_383 0x317F +#define WM8995_WRITE_SEQUENCER_384 0x3180 +#define WM8995_WRITE_SEQUENCER_385 0x3181 +#define WM8995_WRITE_SEQUENCER_386 0x3182 +#define WM8995_WRITE_SEQUENCER_387 0x3183 +#define WM8995_WRITE_SEQUENCER_388 0x3184 +#define WM8995_WRITE_SEQUENCER_389 0x3185 +#define WM8995_WRITE_SEQUENCER_390 0x3186 +#define WM8995_WRITE_SEQUENCER_391 0x3187 +#define WM8995_WRITE_SEQUENCER_392 0x3188 +#define WM8995_WRITE_SEQUENCER_393 0x3189 +#define WM8995_WRITE_SEQUENCER_394 0x318A +#define WM8995_WRITE_SEQUENCER_395 0x318B +#define WM8995_WRITE_SEQUENCER_396 0x318C +#define WM8995_WRITE_SEQUENCER_397 0x318D +#define WM8995_WRITE_SEQUENCER_398 0x318E +#define WM8995_WRITE_SEQUENCER_399 0x318F +#define WM8995_WRITE_SEQUENCER_400 0x3190 +#define WM8995_WRITE_SEQUENCER_401 0x3191 +#define WM8995_WRITE_SEQUENCER_402 0x3192 +#define WM8995_WRITE_SEQUENCER_403 0x3193 +#define WM8995_WRITE_SEQUENCER_404 0x3194 +#define WM8995_WRITE_SEQUENCER_405 0x3195 +#define WM8995_WRITE_SEQUENCER_406 0x3196 +#define WM8995_WRITE_SEQUENCER_407 0x3197 +#define WM8995_WRITE_SEQUENCER_408 0x3198 +#define WM8995_WRITE_SEQUENCER_409 0x3199 +#define WM8995_WRITE_SEQUENCER_410 0x319A +#define WM8995_WRITE_SEQUENCER_411 0x319B +#define WM8995_WRITE_SEQUENCER_412 0x319C +#define WM8995_WRITE_SEQUENCER_413 0x319D +#define WM8995_WRITE_SEQUENCER_414 0x319E +#define WM8995_WRITE_SEQUENCER_415 0x319F +#define WM8995_WRITE_SEQUENCER_416 0x31A0 +#define WM8995_WRITE_SEQUENCER_417 0x31A1 +#define WM8995_WRITE_SEQUENCER_418 0x31A2 +#define WM8995_WRITE_SEQUENCER_419 0x31A3 +#define WM8995_WRITE_SEQUENCER_420 0x31A4 +#define WM8995_WRITE_SEQUENCER_421 0x31A5 +#define WM8995_WRITE_SEQUENCER_422 0x31A6 +#define WM8995_WRITE_SEQUENCER_423 0x31A7 +#define WM8995_WRITE_SEQUENCER_424 0x31A8 +#define WM8995_WRITE_SEQUENCER_425 0x31A9 +#define WM8995_WRITE_SEQUENCER_426 0x31AA +#define WM8995_WRITE_SEQUENCER_427 0x31AB +#define WM8995_WRITE_SEQUENCER_428 0x31AC +#define WM8995_WRITE_SEQUENCER_429 0x31AD +#define WM8995_WRITE_SEQUENCER_430 0x31AE +#define WM8995_WRITE_SEQUENCER_431 0x31AF +#define WM8995_WRITE_SEQUENCER_432 0x31B0 +#define WM8995_WRITE_SEQUENCER_433 0x31B1 +#define WM8995_WRITE_SEQUENCER_434 0x31B2 +#define WM8995_WRITE_SEQUENCER_435 0x31B3 +#define WM8995_WRITE_SEQUENCER_436 0x31B4 +#define WM8995_WRITE_SEQUENCER_437 0x31B5 +#define WM8995_WRITE_SEQUENCER_438 0x31B6 +#define WM8995_WRITE_SEQUENCER_439 0x31B7 +#define WM8995_WRITE_SEQUENCER_440 0x31B8 +#define WM8995_WRITE_SEQUENCER_441 0x31B9 +#define WM8995_WRITE_SEQUENCER_442 0x31BA +#define WM8995_WRITE_SEQUENCER_443 0x31BB +#define WM8995_WRITE_SEQUENCER_444 0x31BC +#define WM8995_WRITE_SEQUENCER_445 0x31BD +#define WM8995_WRITE_SEQUENCER_446 0x31BE +#define WM8995_WRITE_SEQUENCER_447 0x31BF +#define WM8995_WRITE_SEQUENCER_448 0x31C0 +#define WM8995_WRITE_SEQUENCER_449 0x31C1 +#define WM8995_WRITE_SEQUENCER_450 0x31C2 +#define WM8995_WRITE_SEQUENCER_451 0x31C3 +#define WM8995_WRITE_SEQUENCER_452 0x31C4 +#define WM8995_WRITE_SEQUENCER_453 0x31C5 +#define WM8995_WRITE_SEQUENCER_454 0x31C6 +#define WM8995_WRITE_SEQUENCER_455 0x31C7 +#define WM8995_WRITE_SEQUENCER_456 0x31C8 +#define WM8995_WRITE_SEQUENCER_457 0x31C9 +#define WM8995_WRITE_SEQUENCER_458 0x31CA +#define WM8995_WRITE_SEQUENCER_459 0x31CB +#define WM8995_WRITE_SEQUENCER_460 0x31CC +#define WM8995_WRITE_SEQUENCER_461 0x31CD +#define WM8995_WRITE_SEQUENCER_462 0x31CE +#define WM8995_WRITE_SEQUENCER_463 0x31CF +#define WM8995_WRITE_SEQUENCER_464 0x31D0 +#define WM8995_WRITE_SEQUENCER_465 0x31D1 +#define WM8995_WRITE_SEQUENCER_466 0x31D2 +#define WM8995_WRITE_SEQUENCER_467 0x31D3 +#define WM8995_WRITE_SEQUENCER_468 0x31D4 +#define WM8995_WRITE_SEQUENCER_469 0x31D5 +#define WM8995_WRITE_SEQUENCER_470 0x31D6 +#define WM8995_WRITE_SEQUENCER_471 0x31D7 +#define WM8995_WRITE_SEQUENCER_472 0x31D8 +#define WM8995_WRITE_SEQUENCER_473 0x31D9 +#define WM8995_WRITE_SEQUENCER_474 0x31DA +#define WM8995_WRITE_SEQUENCER_475 0x31DB +#define WM8995_WRITE_SEQUENCER_476 0x31DC +#define WM8995_WRITE_SEQUENCER_477 0x31DD +#define WM8995_WRITE_SEQUENCER_478 0x31DE +#define WM8995_WRITE_SEQUENCER_479 0x31DF +#define WM8995_WRITE_SEQUENCER_480 0x31E0 +#define WM8995_WRITE_SEQUENCER_481 0x31E1 +#define WM8995_WRITE_SEQUENCER_482 0x31E2 +#define WM8995_WRITE_SEQUENCER_483 0x31E3 +#define WM8995_WRITE_SEQUENCER_484 0x31E4 +#define WM8995_WRITE_SEQUENCER_485 0x31E5 +#define WM8995_WRITE_SEQUENCER_486 0x31E6 +#define WM8995_WRITE_SEQUENCER_487 0x31E7 +#define WM8995_WRITE_SEQUENCER_488 0x31E8 +#define WM8995_WRITE_SEQUENCER_489 0x31E9 +#define WM8995_WRITE_SEQUENCER_490 0x31EA +#define WM8995_WRITE_SEQUENCER_491 0x31EB +#define WM8995_WRITE_SEQUENCER_492 0x31EC +#define WM8995_WRITE_SEQUENCER_493 0x31ED +#define WM8995_WRITE_SEQUENCER_494 0x31EE +#define WM8995_WRITE_SEQUENCER_495 0x31EF +#define WM8995_WRITE_SEQUENCER_496 0x31F0 +#define WM8995_WRITE_SEQUENCER_497 0x31F1 +#define WM8995_WRITE_SEQUENCER_498 0x31F2 +#define WM8995_WRITE_SEQUENCER_499 0x31F3 +#define WM8995_WRITE_SEQUENCER_500 0x31F4 +#define WM8995_WRITE_SEQUENCER_501 0x31F5 +#define WM8995_WRITE_SEQUENCER_502 0x31F6 +#define WM8995_WRITE_SEQUENCER_503 0x31F7 +#define WM8995_WRITE_SEQUENCER_504 0x31F8 +#define WM8995_WRITE_SEQUENCER_505 0x31F9 +#define WM8995_WRITE_SEQUENCER_506 0x31FA +#define WM8995_WRITE_SEQUENCER_507 0x31FB +#define WM8995_WRITE_SEQUENCER_508 0x31FC +#define WM8995_WRITE_SEQUENCER_509 0x31FD +#define WM8995_WRITE_SEQUENCER_510 0x31FE +#define WM8995_WRITE_SEQUENCER_511 0x31FF + +#define WM8995_REGISTER_COUNT 725 +#define WM8995_MAX_REGISTER 0x31FF + +#define WM8995_MAX_CACHED_REGISTER WM8995_MAX_REGISTER + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM8995_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM8995_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM8995_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8995_MICB2_ENA 0x0200 /* MICB2_ENA */ +#define WM8995_MICB2_ENA_MASK 0x0200 /* MICB2_ENA */ +#define WM8995_MICB2_ENA_SHIFT 9 /* MICB2_ENA */ +#define WM8995_MICB2_ENA_WIDTH 1 /* MICB2_ENA */ +#define WM8995_MICB1_ENA 0x0100 /* MICB1_ENA */ +#define WM8995_MICB1_ENA_MASK 0x0100 /* MICB1_ENA */ +#define WM8995_MICB1_ENA_SHIFT 8 /* MICB1_ENA */ +#define WM8995_MICB1_ENA_WIDTH 1 /* MICB1_ENA */ +#define WM8995_HPOUT2L_ENA 0x0080 /* HPOUT2L_ENA */ +#define WM8995_HPOUT2L_ENA_MASK 0x0080 /* HPOUT2L_ENA */ +#define WM8995_HPOUT2L_ENA_SHIFT 7 /* HPOUT2L_ENA */ +#define WM8995_HPOUT2L_ENA_WIDTH 1 /* HPOUT2L_ENA */ +#define WM8995_HPOUT2R_ENA 0x0040 /* HPOUT2R_ENA */ +#define WM8995_HPOUT2R_ENA_MASK 0x0040 /* HPOUT2R_ENA */ +#define WM8995_HPOUT2R_ENA_SHIFT 6 /* HPOUT2R_ENA */ +#define WM8995_HPOUT2R_ENA_WIDTH 1 /* HPOUT2R_ENA */ +#define WM8995_HPOUT1L_ENA 0x0020 /* HPOUT1L_ENA */ +#define WM8995_HPOUT1L_ENA_MASK 0x0020 /* HPOUT1L_ENA */ +#define WM8995_HPOUT1L_ENA_SHIFT 5 /* HPOUT1L_ENA */ +#define WM8995_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */ +#define WM8995_HPOUT1R_ENA 0x0010 /* HPOUT1R_ENA */ +#define WM8995_HPOUT1R_ENA_MASK 0x0010 /* HPOUT1R_ENA */ +#define WM8995_HPOUT1R_ENA_SHIFT 4 /* HPOUT1R_ENA */ +#define WM8995_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */ +#define WM8995_BG_ENA 0x0001 /* BG_ENA */ +#define WM8995_BG_ENA_MASK 0x0001 /* BG_ENA */ +#define WM8995_BG_ENA_SHIFT 0 /* BG_ENA */ +#define WM8995_BG_ENA_WIDTH 1 /* BG_ENA */ + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8995_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8995_OPCLK_ENA_MASK 0x0800 /* OPCLK_ENA */ +#define WM8995_OPCLK_ENA_SHIFT 11 /* OPCLK_ENA */ +#define WM8995_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */ +#define WM8995_IN1L_ENA 0x0020 /* IN1L_ENA */ +#define WM8995_IN1L_ENA_MASK 0x0020 /* IN1L_ENA */ +#define WM8995_IN1L_ENA_SHIFT 5 /* IN1L_ENA */ +#define WM8995_IN1L_ENA_WIDTH 1 /* IN1L_ENA */ +#define WM8995_IN1R_ENA 0x0010 /* IN1R_ENA */ +#define WM8995_IN1R_ENA_MASK 0x0010 /* IN1R_ENA */ +#define WM8995_IN1R_ENA_SHIFT 4 /* IN1R_ENA */ +#define WM8995_IN1R_ENA_WIDTH 1 /* IN1R_ENA */ +#define WM8995_LDO2_ENA 0x0002 /* LDO2_ENA */ +#define WM8995_LDO2_ENA_MASK 0x0002 /* LDO2_ENA */ +#define WM8995_LDO2_ENA_SHIFT 1 /* LDO2_ENA */ +#define WM8995_LDO2_ENA_WIDTH 1 /* LDO2_ENA */ + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8995_AIF2ADCL_ENA 0x2000 /* AIF2ADCL_ENA */ +#define WM8995_AIF2ADCL_ENA_MASK 0x2000 /* AIF2ADCL_ENA */ +#define WM8995_AIF2ADCL_ENA_SHIFT 13 /* AIF2ADCL_ENA */ +#define WM8995_AIF2ADCL_ENA_WIDTH 1 /* AIF2ADCL_ENA */ +#define WM8995_AIF2ADCR_ENA 0x1000 /* AIF2ADCR_ENA */ +#define WM8995_AIF2ADCR_ENA_MASK 0x1000 /* AIF2ADCR_ENA */ +#define WM8995_AIF2ADCR_ENA_SHIFT 12 /* AIF2ADCR_ENA */ +#define WM8995_AIF2ADCR_ENA_WIDTH 1 /* AIF2ADCR_ENA */ +#define WM8995_AIF1ADC2L_ENA 0x0800 /* AIF1ADC2L_ENA */ +#define WM8995_AIF1ADC2L_ENA_MASK 0x0800 /* AIF1ADC2L_ENA */ +#define WM8995_AIF1ADC2L_ENA_SHIFT 11 /* AIF1ADC2L_ENA */ +#define WM8995_AIF1ADC2L_ENA_WIDTH 1 /* AIF1ADC2L_ENA */ +#define WM8995_AIF1ADC2R_ENA 0x0400 /* AIF1ADC2R_ENA */ +#define WM8995_AIF1ADC2R_ENA_MASK 0x0400 /* AIF1ADC2R_ENA */ +#define WM8995_AIF1ADC2R_ENA_SHIFT 10 /* AIF1ADC2R_ENA */ +#define WM8995_AIF1ADC2R_ENA_WIDTH 1 /* AIF1ADC2R_ENA */ +#define WM8995_AIF1ADC1L_ENA 0x0200 /* AIF1ADC1L_ENA */ +#define WM8995_AIF1ADC1L_ENA_MASK 0x0200 /* AIF1ADC1L_ENA */ +#define WM8995_AIF1ADC1L_ENA_SHIFT 9 /* AIF1ADC1L_ENA */ +#define WM8995_AIF1ADC1L_ENA_WIDTH 1 /* AIF1ADC1L_ENA */ +#define WM8995_AIF1ADC1R_ENA 0x0100 /* AIF1ADC1R_ENA */ +#define WM8995_AIF1ADC1R_ENA_MASK 0x0100 /* AIF1ADC1R_ENA */ +#define WM8995_AIF1ADC1R_ENA_SHIFT 8 /* AIF1ADC1R_ENA */ +#define WM8995_AIF1ADC1R_ENA_WIDTH 1 /* AIF1ADC1R_ENA */ +#define WM8995_DMIC3L_ENA 0x0080 /* DMIC3L_ENA */ +#define WM8995_DMIC3L_ENA_MASK 0x0080 /* DMIC3L_ENA */ +#define WM8995_DMIC3L_ENA_SHIFT 7 /* DMIC3L_ENA */ +#define WM8995_DMIC3L_ENA_WIDTH 1 /* DMIC3L_ENA */ +#define WM8995_DMIC3R_ENA 0x0040 /* DMIC3R_ENA */ +#define WM8995_DMIC3R_ENA_MASK 0x0040 /* DMIC3R_ENA */ +#define WM8995_DMIC3R_ENA_SHIFT 6 /* DMIC3R_ENA */ +#define WM8995_DMIC3R_ENA_WIDTH 1 /* DMIC3R_ENA */ +#define WM8995_DMIC2L_ENA 0x0020 /* DMIC2L_ENA */ +#define WM8995_DMIC2L_ENA_MASK 0x0020 /* DMIC2L_ENA */ +#define WM8995_DMIC2L_ENA_SHIFT 5 /* DMIC2L_ENA */ +#define WM8995_DMIC2L_ENA_WIDTH 1 /* DMIC2L_ENA */ +#define WM8995_DMIC2R_ENA 0x0010 /* DMIC2R_ENA */ +#define WM8995_DMIC2R_ENA_MASK 0x0010 /* DMIC2R_ENA */ +#define WM8995_DMIC2R_ENA_SHIFT 4 /* DMIC2R_ENA */ +#define WM8995_DMIC2R_ENA_WIDTH 1 /* DMIC2R_ENA */ +#define WM8995_DMIC1L_ENA 0x0008 /* DMIC1L_ENA */ +#define WM8995_DMIC1L_ENA_MASK 0x0008 /* DMIC1L_ENA */ +#define WM8995_DMIC1L_ENA_SHIFT 3 /* DMIC1L_ENA */ +#define WM8995_DMIC1L_ENA_WIDTH 1 /* DMIC1L_ENA */ +#define WM8995_DMIC1R_ENA 0x0004 /* DMIC1R_ENA */ +#define WM8995_DMIC1R_ENA_MASK 0x0004 /* DMIC1R_ENA */ +#define WM8995_DMIC1R_ENA_SHIFT 2 /* DMIC1R_ENA */ +#define WM8995_DMIC1R_ENA_WIDTH 1 /* DMIC1R_ENA */ +#define WM8995_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8995_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8995_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8995_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8995_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8995_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8995_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8995_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R4 (0x04) - Power Management (4) + */ +#define WM8995_AIF2DACL_ENA 0x2000 /* AIF2DACL_ENA */ +#define WM8995_AIF2DACL_ENA_MASK 0x2000 /* AIF2DACL_ENA */ +#define WM8995_AIF2DACL_ENA_SHIFT 13 /* AIF2DACL_ENA */ +#define WM8995_AIF2DACL_ENA_WIDTH 1 /* AIF2DACL_ENA */ +#define WM8995_AIF2DACR_ENA 0x1000 /* AIF2DACR_ENA */ +#define WM8995_AIF2DACR_ENA_MASK 0x1000 /* AIF2DACR_ENA */ +#define WM8995_AIF2DACR_ENA_SHIFT 12 /* AIF2DACR_ENA */ +#define WM8995_AIF2DACR_ENA_WIDTH 1 /* AIF2DACR_ENA */ +#define WM8995_AIF1DAC2L_ENA 0x0800 /* AIF1DAC2L_ENA */ +#define WM8995_AIF1DAC2L_ENA_MASK 0x0800 /* AIF1DAC2L_ENA */ +#define WM8995_AIF1DAC2L_ENA_SHIFT 11 /* AIF1DAC2L_ENA */ +#define WM8995_AIF1DAC2L_ENA_WIDTH 1 /* AIF1DAC2L_ENA */ +#define WM8995_AIF1DAC2R_ENA 0x0400 /* AIF1DAC2R_ENA */ +#define WM8995_AIF1DAC2R_ENA_MASK 0x0400 /* AIF1DAC2R_ENA */ +#define WM8995_AIF1DAC2R_ENA_SHIFT 10 /* AIF1DAC2R_ENA */ +#define WM8995_AIF1DAC2R_ENA_WIDTH 1 /* AIF1DAC2R_ENA */ +#define WM8995_AIF1DAC1L_ENA 0x0200 /* AIF1DAC1L_ENA */ +#define WM8995_AIF1DAC1L_ENA_MASK 0x0200 /* AIF1DAC1L_ENA */ +#define WM8995_AIF1DAC1L_ENA_SHIFT 9 /* AIF1DAC1L_ENA */ +#define WM8995_AIF1DAC1L_ENA_WIDTH 1 /* AIF1DAC1L_ENA */ +#define WM8995_AIF1DAC1R_ENA 0x0100 /* AIF1DAC1R_ENA */ +#define WM8995_AIF1DAC1R_ENA_MASK 0x0100 /* AIF1DAC1R_ENA */ +#define WM8995_AIF1DAC1R_ENA_SHIFT 8 /* AIF1DAC1R_ENA */ +#define WM8995_AIF1DAC1R_ENA_WIDTH 1 /* AIF1DAC1R_ENA */ +#define WM8995_DAC2L_ENA 0x0008 /* DAC2L_ENA */ +#define WM8995_DAC2L_ENA_MASK 0x0008 /* DAC2L_ENA */ +#define WM8995_DAC2L_ENA_SHIFT 3 /* DAC2L_ENA */ +#define WM8995_DAC2L_ENA_WIDTH 1 /* DAC2L_ENA */ +#define WM8995_DAC2R_ENA 0x0004 /* DAC2R_ENA */ +#define WM8995_DAC2R_ENA_MASK 0x0004 /* DAC2R_ENA */ +#define WM8995_DAC2R_ENA_SHIFT 2 /* DAC2R_ENA */ +#define WM8995_DAC2R_ENA_WIDTH 1 /* DAC2R_ENA */ +#define WM8995_DAC1L_ENA 0x0002 /* DAC1L_ENA */ +#define WM8995_DAC1L_ENA_MASK 0x0002 /* DAC1L_ENA */ +#define WM8995_DAC1L_ENA_SHIFT 1 /* DAC1L_ENA */ +#define WM8995_DAC1L_ENA_WIDTH 1 /* DAC1L_ENA */ +#define WM8995_DAC1R_ENA 0x0001 /* DAC1R_ENA */ +#define WM8995_DAC1R_ENA_MASK 0x0001 /* DAC1R_ENA */ +#define WM8995_DAC1R_ENA_SHIFT 0 /* DAC1R_ENA */ +#define WM8995_DAC1R_ENA_WIDTH 1 /* DAC1R_ENA */ + +/* + * R5 (0x05) - Power Management (5) + */ +#define WM8995_DMIC_SRC2_MASK 0x0300 /* DMIC_SRC2 - [9:8] */ +#define WM8995_DMIC_SRC2_SHIFT 8 /* DMIC_SRC2 - [9:8] */ +#define WM8995_DMIC_SRC2_WIDTH 2 /* DMIC_SRC2 - [9:8] */ +#define WM8995_DMIC_SRC1_MASK 0x00C0 /* DMIC_SRC1 - [7:6] */ +#define WM8995_DMIC_SRC1_SHIFT 6 /* DMIC_SRC1 - [7:6] */ +#define WM8995_DMIC_SRC1_WIDTH 2 /* DMIC_SRC1 - [7:6] */ +#define WM8995_AIF3_TRI 0x0020 /* AIF3_TRI */ +#define WM8995_AIF3_TRI_MASK 0x0020 /* AIF3_TRI */ +#define WM8995_AIF3_TRI_SHIFT 5 /* AIF3_TRI */ +#define WM8995_AIF3_TRI_WIDTH 1 /* AIF3_TRI */ +#define WM8995_AIF3_ADCDAT_SRC_MASK 0x0018 /* AIF3_ADCDAT_SRC - [4:3] */ +#define WM8995_AIF3_ADCDAT_SRC_SHIFT 3 /* AIF3_ADCDAT_SRC - [4:3] */ +#define WM8995_AIF3_ADCDAT_SRC_WIDTH 2 /* AIF3_ADCDAT_SRC - [4:3] */ +#define WM8995_AIF2_ADCDAT_SRC 0x0004 /* AIF2_ADCDAT_SRC */ +#define WM8995_AIF2_ADCDAT_SRC_MASK 0x0004 /* AIF2_ADCDAT_SRC */ +#define WM8995_AIF2_ADCDAT_SRC_SHIFT 2 /* AIF2_ADCDAT_SRC */ +#define WM8995_AIF2_ADCDAT_SRC_WIDTH 1 /* AIF2_ADCDAT_SRC */ +#define WM8995_AIF2_DACDAT_SRC 0x0002 /* AIF2_DACDAT_SRC */ +#define WM8995_AIF2_DACDAT_SRC_MASK 0x0002 /* AIF2_DACDAT_SRC */ +#define WM8995_AIF2_DACDAT_SRC_SHIFT 1 /* AIF2_DACDAT_SRC */ +#define WM8995_AIF2_DACDAT_SRC_WIDTH 1 /* AIF2_DACDAT_SRC */ +#define WM8995_AIF1_DACDAT_SRC 0x0001 /* AIF1_DACDAT_SRC */ +#define WM8995_AIF1_DACDAT_SRC_MASK 0x0001 /* AIF1_DACDAT_SRC */ +#define WM8995_AIF1_DACDAT_SRC_SHIFT 0 /* AIF1_DACDAT_SRC */ +#define WM8995_AIF1_DACDAT_SRC_WIDTH 1 /* AIF1_DACDAT_SRC */ + +/* + * R16 (0x10) - Left Line Input 1 Volume + */ +#define WM8995_IN1_VU 0x0080 /* IN1_VU */ +#define WM8995_IN1_VU_MASK 0x0080 /* IN1_VU */ +#define WM8995_IN1_VU_SHIFT 7 /* IN1_VU */ +#define WM8995_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8995_IN1L_ZC 0x0020 /* IN1L_ZC */ +#define WM8995_IN1L_ZC_MASK 0x0020 /* IN1L_ZC */ +#define WM8995_IN1L_ZC_SHIFT 5 /* IN1L_ZC */ +#define WM8995_IN1L_ZC_WIDTH 1 /* IN1L_ZC */ +#define WM8995_IN1L_VOL_MASK 0x001F /* IN1L_VOL - [4:0] */ +#define WM8995_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [4:0] */ +#define WM8995_IN1L_VOL_WIDTH 5 /* IN1L_VOL - [4:0] */ + +/* + * R17 (0x11) - Right Line Input 1 Volume + */ +#define WM8995_IN1_VU 0x0080 /* IN1_VU */ +#define WM8995_IN1_VU_MASK 0x0080 /* IN1_VU */ +#define WM8995_IN1_VU_SHIFT 7 /* IN1_VU */ +#define WM8995_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8995_IN1R_ZC 0x0020 /* IN1R_ZC */ +#define WM8995_IN1R_ZC_MASK 0x0020 /* IN1R_ZC */ +#define WM8995_IN1R_ZC_SHIFT 5 /* IN1R_ZC */ +#define WM8995_IN1R_ZC_WIDTH 1 /* IN1R_ZC */ +#define WM8995_IN1R_VOL_MASK 0x001F /* IN1R_VOL - [4:0] */ +#define WM8995_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [4:0] */ +#define WM8995_IN1R_VOL_WIDTH 5 /* IN1R_VOL - [4:0] */ + +/* + * R18 (0x12) - Left Line Input Control + */ +#define WM8995_IN1L_BOOST_MASK 0x0030 /* IN1L_BOOST - [5:4] */ +#define WM8995_IN1L_BOOST_SHIFT 4 /* IN1L_BOOST - [5:4] */ +#define WM8995_IN1L_BOOST_WIDTH 2 /* IN1L_BOOST - [5:4] */ +#define WM8995_IN1L_MODE_MASK 0x000C /* IN1L_MODE - [3:2] */ +#define WM8995_IN1L_MODE_SHIFT 2 /* IN1L_MODE - [3:2] */ +#define WM8995_IN1L_MODE_WIDTH 2 /* IN1L_MODE - [3:2] */ +#define WM8995_IN1R_MODE_MASK 0x0003 /* IN1R_MODE - [1:0] */ +#define WM8995_IN1R_MODE_SHIFT 0 /* IN1R_MODE - [1:0] */ +#define WM8995_IN1R_MODE_WIDTH 2 /* IN1R_MODE - [1:0] */ + +/* + * R24 (0x18) - DAC1 Left Volume + */ +#define WM8995_DAC1L_MUTE 0x0200 /* DAC1L_MUTE */ +#define WM8995_DAC1L_MUTE_MASK 0x0200 /* DAC1L_MUTE */ +#define WM8995_DAC1L_MUTE_SHIFT 9 /* DAC1L_MUTE */ +#define WM8995_DAC1L_MUTE_WIDTH 1 /* DAC1L_MUTE */ +#define WM8995_DAC1_VU 0x0100 /* DAC1_VU */ +#define WM8995_DAC1_VU_MASK 0x0100 /* DAC1_VU */ +#define WM8995_DAC1_VU_SHIFT 8 /* DAC1_VU */ +#define WM8995_DAC1_VU_WIDTH 1 /* DAC1_VU */ +#define WM8995_DAC1L_VOL_MASK 0x00FF /* DAC1L_VOL - [7:0] */ +#define WM8995_DAC1L_VOL_SHIFT 0 /* DAC1L_VOL - [7:0] */ +#define WM8995_DAC1L_VOL_WIDTH 8 /* DAC1L_VOL - [7:0] */ + +/* + * R25 (0x19) - DAC1 Right Volume + */ +#define WM8995_DAC1R_MUTE 0x0200 /* DAC1R_MUTE */ +#define WM8995_DAC1R_MUTE_MASK 0x0200 /* DAC1R_MUTE */ +#define WM8995_DAC1R_MUTE_SHIFT 9 /* DAC1R_MUTE */ +#define WM8995_DAC1R_MUTE_WIDTH 1 /* DAC1R_MUTE */ +#define WM8995_DAC1_VU 0x0100 /* DAC1_VU */ +#define WM8995_DAC1_VU_MASK 0x0100 /* DAC1_VU */ +#define WM8995_DAC1_VU_SHIFT 8 /* DAC1_VU */ +#define WM8995_DAC1_VU_WIDTH 1 /* DAC1_VU */ +#define WM8995_DAC1R_VOL_MASK 0x00FF /* DAC1R_VOL - [7:0] */ +#define WM8995_DAC1R_VOL_SHIFT 0 /* DAC1R_VOL - [7:0] */ +#define WM8995_DAC1R_VOL_WIDTH 8 /* DAC1R_VOL - [7:0] */ + +/* + * R26 (0x1A) - DAC2 Left Volume + */ +#define WM8995_DAC2L_MUTE 0x0200 /* DAC2L_MUTE */ +#define WM8995_DAC2L_MUTE_MASK 0x0200 /* DAC2L_MUTE */ +#define WM8995_DAC2L_MUTE_SHIFT 9 /* DAC2L_MUTE */ +#define WM8995_DAC2L_MUTE_WIDTH 1 /* DAC2L_MUTE */ +#define WM8995_DAC2_VU 0x0100 /* DAC2_VU */ +#define WM8995_DAC2_VU_MASK 0x0100 /* DAC2_VU */ +#define WM8995_DAC2_VU_SHIFT 8 /* DAC2_VU */ +#define WM8995_DAC2_VU_WIDTH 1 /* DAC2_VU */ +#define WM8995_DAC2L_VOL_MASK 0x00FF /* DAC2L_VOL - [7:0] */ +#define WM8995_DAC2L_VOL_SHIFT 0 /* DAC2L_VOL - [7:0] */ +#define WM8995_DAC2L_VOL_WIDTH 8 /* DAC2L_VOL - [7:0] */ + +/* + * R27 (0x1B) - DAC2 Right Volume + */ +#define WM8995_DAC2R_MUTE 0x0200 /* DAC2R_MUTE */ +#define WM8995_DAC2R_MUTE_MASK 0x0200 /* DAC2R_MUTE */ +#define WM8995_DAC2R_MUTE_SHIFT 9 /* DAC2R_MUTE */ +#define WM8995_DAC2R_MUTE_WIDTH 1 /* DAC2R_MUTE */ +#define WM8995_DAC2_VU 0x0100 /* DAC2_VU */ +#define WM8995_DAC2_VU_MASK 0x0100 /* DAC2_VU */ +#define WM8995_DAC2_VU_SHIFT 8 /* DAC2_VU */ +#define WM8995_DAC2_VU_WIDTH 1 /* DAC2_VU */ +#define WM8995_DAC2R_VOL_MASK 0x00FF /* DAC2R_VOL - [7:0] */ +#define WM8995_DAC2R_VOL_SHIFT 0 /* DAC2R_VOL - [7:0] */ +#define WM8995_DAC2R_VOL_WIDTH 8 /* DAC2R_VOL - [7:0] */ + +/* + * R28 (0x1C) - Output Volume ZC (1) + */ +#define WM8995_HPOUT2L_ZC 0x0008 /* HPOUT2L_ZC */ +#define WM8995_HPOUT2L_ZC_MASK 0x0008 /* HPOUT2L_ZC */ +#define WM8995_HPOUT2L_ZC_SHIFT 3 /* HPOUT2L_ZC */ +#define WM8995_HPOUT2L_ZC_WIDTH 1 /* HPOUT2L_ZC */ +#define WM8995_HPOUT2R_ZC 0x0004 /* HPOUT2R_ZC */ +#define WM8995_HPOUT2R_ZC_MASK 0x0004 /* HPOUT2R_ZC */ +#define WM8995_HPOUT2R_ZC_SHIFT 2 /* HPOUT2R_ZC */ +#define WM8995_HPOUT2R_ZC_WIDTH 1 /* HPOUT2R_ZC */ +#define WM8995_HPOUT1L_ZC 0x0002 /* HPOUT1L_ZC */ +#define WM8995_HPOUT1L_ZC_MASK 0x0002 /* HPOUT1L_ZC */ +#define WM8995_HPOUT1L_ZC_SHIFT 1 /* HPOUT1L_ZC */ +#define WM8995_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */ +#define WM8995_HPOUT1R_ZC 0x0001 /* HPOUT1R_ZC */ +#define WM8995_HPOUT1R_ZC_MASK 0x0001 /* HPOUT1R_ZC */ +#define WM8995_HPOUT1R_ZC_SHIFT 0 /* HPOUT1R_ZC */ +#define WM8995_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */ + +/* + * R32 (0x20) - MICBIAS (1) + */ +#define WM8995_MICB1_MODE 0x0008 /* MICB1_MODE */ +#define WM8995_MICB1_MODE_MASK 0x0008 /* MICB1_MODE */ +#define WM8995_MICB1_MODE_SHIFT 3 /* MICB1_MODE */ +#define WM8995_MICB1_MODE_WIDTH 1 /* MICB1_MODE */ +#define WM8995_MICB1_LVL_MASK 0x0006 /* MICB1_LVL - [2:1] */ +#define WM8995_MICB1_LVL_SHIFT 1 /* MICB1_LVL - [2:1] */ +#define WM8995_MICB1_LVL_WIDTH 2 /* MICB1_LVL - [2:1] */ +#define WM8995_MICB1_DISCH 0x0001 /* MICB1_DISCH */ +#define WM8995_MICB1_DISCH_MASK 0x0001 /* MICB1_DISCH */ +#define WM8995_MICB1_DISCH_SHIFT 0 /* MICB1_DISCH */ +#define WM8995_MICB1_DISCH_WIDTH 1 /* MICB1_DISCH */ + +/* + * R33 (0x21) - MICBIAS (2) + */ +#define WM8995_MICB2_MODE 0x0008 /* MICB2_MODE */ +#define WM8995_MICB2_MODE_MASK 0x0008 /* MICB2_MODE */ +#define WM8995_MICB2_MODE_SHIFT 3 /* MICB2_MODE */ +#define WM8995_MICB2_MODE_WIDTH 1 /* MICB2_MODE */ +#define WM8995_MICB2_LVL_MASK 0x0006 /* MICB2_LVL - [2:1] */ +#define WM8995_MICB2_LVL_SHIFT 1 /* MICB2_LVL - [2:1] */ +#define WM8995_MICB2_LVL_WIDTH 2 /* MICB2_LVL - [2:1] */ +#define WM8995_MICB2_DISCH 0x0001 /* MICB2_DISCH */ +#define WM8995_MICB2_DISCH_MASK 0x0001 /* MICB2_DISCH */ +#define WM8995_MICB2_DISCH_SHIFT 0 /* MICB2_DISCH */ +#define WM8995_MICB2_DISCH_WIDTH 1 /* MICB2_DISCH */ + +/* + * R40 (0x28) - LDO 1 + */ +#define WM8995_LDO1_MODE 0x0020 /* LDO1_MODE */ +#define WM8995_LDO1_MODE_MASK 0x0020 /* LDO1_MODE */ +#define WM8995_LDO1_MODE_SHIFT 5 /* LDO1_MODE */ +#define WM8995_LDO1_MODE_WIDTH 1 /* LDO1_MODE */ +#define WM8995_LDO1_VSEL_MASK 0x0006 /* LDO1_VSEL - [2:1] */ +#define WM8995_LDO1_VSEL_SHIFT 1 /* LDO1_VSEL - [2:1] */ +#define WM8995_LDO1_VSEL_WIDTH 2 /* LDO1_VSEL - [2:1] */ +#define WM8995_LDO1_DISCH 0x0001 /* LDO1_DISCH */ +#define WM8995_LDO1_DISCH_MASK 0x0001 /* LDO1_DISCH */ +#define WM8995_LDO1_DISCH_SHIFT 0 /* LDO1_DISCH */ +#define WM8995_LDO1_DISCH_WIDTH 1 /* LDO1_DISCH */ + +/* + * R41 (0x29) - LDO 2 + */ +#define WM8995_LDO2_MODE 0x0020 /* LDO2_MODE */ +#define WM8995_LDO2_MODE_MASK 0x0020 /* LDO2_MODE */ +#define WM8995_LDO2_MODE_SHIFT 5 /* LDO2_MODE */ +#define WM8995_LDO2_MODE_WIDTH 1 /* LDO2_MODE */ +#define WM8995_LDO2_VSEL_MASK 0x001E /* LDO2_VSEL - [4:1] */ +#define WM8995_LDO2_VSEL_SHIFT 1 /* LDO2_VSEL - [4:1] */ +#define WM8995_LDO2_VSEL_WIDTH 4 /* LDO2_VSEL - [4:1] */ +#define WM8995_LDO2_DISCH 0x0001 /* LDO2_DISCH */ +#define WM8995_LDO2_DISCH_MASK 0x0001 /* LDO2_DISCH */ +#define WM8995_LDO2_DISCH_SHIFT 0 /* LDO2_DISCH */ +#define WM8995_LDO2_DISCH_WIDTH 1 /* LDO2_DISCH */ + +/* + * R48 (0x30) - Accessory Detect Mode1 + */ +#define WM8995_JD_MODE_MASK 0x0003 /* JD_MODE - [1:0] */ +#define WM8995_JD_MODE_SHIFT 0 /* JD_MODE - [1:0] */ +#define WM8995_JD_MODE_WIDTH 2 /* JD_MODE - [1:0] */ + +/* + * R49 (0x31) - Accessory Detect Mode2 + */ +#define WM8995_VID_ENA 0x0001 /* VID_ENA */ +#define WM8995_VID_ENA_MASK 0x0001 /* VID_ENA */ +#define WM8995_VID_ENA_SHIFT 0 /* VID_ENA */ +#define WM8995_VID_ENA_WIDTH 1 /* VID_ENA */ + +/* + * R52 (0x34) - Headphone Detect1 + */ +#define WM8995_HP_RAMPRATE 0x0002 /* HP_RAMPRATE */ +#define WM8995_HP_RAMPRATE_MASK 0x0002 /* HP_RAMPRATE */ +#define WM8995_HP_RAMPRATE_SHIFT 1 /* HP_RAMPRATE */ +#define WM8995_HP_RAMPRATE_WIDTH 1 /* HP_RAMPRATE */ +#define WM8995_HP_POLL 0x0001 /* HP_POLL */ +#define WM8995_HP_POLL_MASK 0x0001 /* HP_POLL */ +#define WM8995_HP_POLL_SHIFT 0 /* HP_POLL */ +#define WM8995_HP_POLL_WIDTH 1 /* HP_POLL */ + +/* + * R53 (0x35) - Headphone Detect2 + */ +#define WM8995_HP_DONE 0x0080 /* HP_DONE */ +#define WM8995_HP_DONE_MASK 0x0080 /* HP_DONE */ +#define WM8995_HP_DONE_SHIFT 7 /* HP_DONE */ +#define WM8995_HP_DONE_WIDTH 1 /* HP_DONE */ +#define WM8995_HP_LVL_MASK 0x007F /* HP_LVL - [6:0] */ +#define WM8995_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */ +#define WM8995_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */ + +/* + * R56 (0x38) - Mic Detect (1) + */ +#define WM8995_MICD_RATE_MASK 0x7800 /* MICD_RATE - [14:11] */ +#define WM8995_MICD_RATE_SHIFT 11 /* MICD_RATE - [14:11] */ +#define WM8995_MICD_RATE_WIDTH 4 /* MICD_RATE - [14:11] */ +#define WM8995_MICD_LVL_SEL_MASK 0x01F8 /* MICD_LVL_SEL - [8:3] */ +#define WM8995_MICD_LVL_SEL_SHIFT 3 /* MICD_LVL_SEL - [8:3] */ +#define WM8995_MICD_LVL_SEL_WIDTH 6 /* MICD_LVL_SEL - [8:3] */ +#define WM8995_MICD_DBTIME 0x0002 /* MICD_DBTIME */ +#define WM8995_MICD_DBTIME_MASK 0x0002 /* MICD_DBTIME */ +#define WM8995_MICD_DBTIME_SHIFT 1 /* MICD_DBTIME */ +#define WM8995_MICD_DBTIME_WIDTH 1 /* MICD_DBTIME */ +#define WM8995_MICD_ENA 0x0001 /* MICD_ENA */ +#define WM8995_MICD_ENA_MASK 0x0001 /* MICD_ENA */ +#define WM8995_MICD_ENA_SHIFT 0 /* MICD_ENA */ +#define WM8995_MICD_ENA_WIDTH 1 /* MICD_ENA */ + +/* + * R57 (0x39) - Mic Detect (2) + */ +#define WM8995_MICD_LVL_MASK 0x01FC /* MICD_LVL - [8:2] */ +#define WM8995_MICD_LVL_SHIFT 2 /* MICD_LVL - [8:2] */ +#define WM8995_MICD_LVL_WIDTH 7 /* MICD_LVL - [8:2] */ +#define WM8995_MICD_VALID 0x0002 /* MICD_VALID */ +#define WM8995_MICD_VALID_MASK 0x0002 /* MICD_VALID */ +#define WM8995_MICD_VALID_SHIFT 1 /* MICD_VALID */ +#define WM8995_MICD_VALID_WIDTH 1 /* MICD_VALID */ +#define WM8995_MICD_STS 0x0001 /* MICD_STS */ +#define WM8995_MICD_STS_MASK 0x0001 /* MICD_STS */ +#define WM8995_MICD_STS_SHIFT 0 /* MICD_STS */ +#define WM8995_MICD_STS_WIDTH 1 /* MICD_STS */ + +/* + * R64 (0x40) - Charge Pump (1) + */ +#define WM8995_CP_ENA 0x8000 /* CP_ENA */ +#define WM8995_CP_ENA_MASK 0x8000 /* CP_ENA */ +#define WM8995_CP_ENA_SHIFT 15 /* CP_ENA */ +#define WM8995_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R69 (0x45) - Class W (1) + */ +#define WM8995_CP_DYN_SRC_SEL_MASK 0x0300 /* CP_DYN_SRC_SEL - [9:8] */ +#define WM8995_CP_DYN_SRC_SEL_SHIFT 8 /* CP_DYN_SRC_SEL - [9:8] */ +#define WM8995_CP_DYN_SRC_SEL_WIDTH 2 /* CP_DYN_SRC_SEL - [9:8] */ +#define WM8995_CP_DYN_PWR 0x0001 /* CP_DYN_PWR */ +#define WM8995_CP_DYN_PWR_MASK 0x0001 /* CP_DYN_PWR */ +#define WM8995_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR */ +#define WM8995_CP_DYN_PWR_WIDTH 1 /* CP_DYN_PWR */ + +/* + * R80 (0x50) - DC Servo (1) + */ +#define WM8995_DCS_ENA_CHAN_3 0x0008 /* DCS_ENA_CHAN_3 */ +#define WM8995_DCS_ENA_CHAN_3_MASK 0x0008 /* DCS_ENA_CHAN_3 */ +#define WM8995_DCS_ENA_CHAN_3_SHIFT 3 /* DCS_ENA_CHAN_3 */ +#define WM8995_DCS_ENA_CHAN_3_WIDTH 1 /* DCS_ENA_CHAN_3 */ +#define WM8995_DCS_ENA_CHAN_2 0x0004 /* DCS_ENA_CHAN_2 */ +#define WM8995_DCS_ENA_CHAN_2_MASK 0x0004 /* DCS_ENA_CHAN_2 */ +#define WM8995_DCS_ENA_CHAN_2_SHIFT 2 /* DCS_ENA_CHAN_2 */ +#define WM8995_DCS_ENA_CHAN_2_WIDTH 1 /* DCS_ENA_CHAN_2 */ +#define WM8995_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8995_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8995_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM8995_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM8995_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8995_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8995_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM8995_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R81 (0x51) - DC Servo (2) + */ +#define WM8995_DCS_TRIG_SINGLE_3 0x8000 /* DCS_TRIG_SINGLE_3 */ +#define WM8995_DCS_TRIG_SINGLE_3_MASK 0x8000 /* DCS_TRIG_SINGLE_3 */ +#define WM8995_DCS_TRIG_SINGLE_3_SHIFT 15 /* DCS_TRIG_SINGLE_3 */ +#define WM8995_DCS_TRIG_SINGLE_3_WIDTH 1 /* DCS_TRIG_SINGLE_3 */ +#define WM8995_DCS_TRIG_SINGLE_2 0x4000 /* DCS_TRIG_SINGLE_2 */ +#define WM8995_DCS_TRIG_SINGLE_2_MASK 0x4000 /* DCS_TRIG_SINGLE_2 */ +#define WM8995_DCS_TRIG_SINGLE_2_SHIFT 14 /* DCS_TRIG_SINGLE_2 */ +#define WM8995_DCS_TRIG_SINGLE_2_WIDTH 1 /* DCS_TRIG_SINGLE_2 */ +#define WM8995_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8995_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8995_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM8995_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM8995_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8995_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8995_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM8995_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM8995_DCS_TRIG_SERIES_3 0x0800 /* DCS_TRIG_SERIES_3 */ +#define WM8995_DCS_TRIG_SERIES_3_MASK 0x0800 /* DCS_TRIG_SERIES_3 */ +#define WM8995_DCS_TRIG_SERIES_3_SHIFT 11 /* DCS_TRIG_SERIES_3 */ +#define WM8995_DCS_TRIG_SERIES_3_WIDTH 1 /* DCS_TRIG_SERIES_3 */ +#define WM8995_DCS_TRIG_SERIES_2 0x0400 /* DCS_TRIG_SERIES_2 */ +#define WM8995_DCS_TRIG_SERIES_2_MASK 0x0400 /* DCS_TRIG_SERIES_2 */ +#define WM8995_DCS_TRIG_SERIES_2_SHIFT 10 /* DCS_TRIG_SERIES_2 */ +#define WM8995_DCS_TRIG_SERIES_2_WIDTH 1 /* DCS_TRIG_SERIES_2 */ +#define WM8995_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8995_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8995_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM8995_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM8995_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8995_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8995_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM8995_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM8995_DCS_TRIG_STARTUP_3 0x0080 /* DCS_TRIG_STARTUP_3 */ +#define WM8995_DCS_TRIG_STARTUP_3_MASK 0x0080 /* DCS_TRIG_STARTUP_3 */ +#define WM8995_DCS_TRIG_STARTUP_3_SHIFT 7 /* DCS_TRIG_STARTUP_3 */ +#define WM8995_DCS_TRIG_STARTUP_3_WIDTH 1 /* DCS_TRIG_STARTUP_3 */ +#define WM8995_DCS_TRIG_STARTUP_2 0x0040 /* DCS_TRIG_STARTUP_2 */ +#define WM8995_DCS_TRIG_STARTUP_2_MASK 0x0040 /* DCS_TRIG_STARTUP_2 */ +#define WM8995_DCS_TRIG_STARTUP_2_SHIFT 6 /* DCS_TRIG_STARTUP_2 */ +#define WM8995_DCS_TRIG_STARTUP_2_WIDTH 1 /* DCS_TRIG_STARTUP_2 */ +#define WM8995_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8995_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8995_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM8995_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM8995_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8995_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8995_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM8995_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM8995_DCS_TRIG_DAC_WR_3 0x0008 /* DCS_TRIG_DAC_WR_3 */ +#define WM8995_DCS_TRIG_DAC_WR_3_MASK 0x0008 /* DCS_TRIG_DAC_WR_3 */ +#define WM8995_DCS_TRIG_DAC_WR_3_SHIFT 3 /* DCS_TRIG_DAC_WR_3 */ +#define WM8995_DCS_TRIG_DAC_WR_3_WIDTH 1 /* DCS_TRIG_DAC_WR_3 */ +#define WM8995_DCS_TRIG_DAC_WR_2 0x0004 /* DCS_TRIG_DAC_WR_2 */ +#define WM8995_DCS_TRIG_DAC_WR_2_MASK 0x0004 /* DCS_TRIG_DAC_WR_2 */ +#define WM8995_DCS_TRIG_DAC_WR_2_SHIFT 2 /* DCS_TRIG_DAC_WR_2 */ +#define WM8995_DCS_TRIG_DAC_WR_2_WIDTH 1 /* DCS_TRIG_DAC_WR_2 */ +#define WM8995_DCS_TRIG_DAC_WR_1 0x0002 /* DCS_TRIG_DAC_WR_1 */ +#define WM8995_DCS_TRIG_DAC_WR_1_MASK 0x0002 /* DCS_TRIG_DAC_WR_1 */ +#define WM8995_DCS_TRIG_DAC_WR_1_SHIFT 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8995_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8995_DCS_TRIG_DAC_WR_0 0x0001 /* DCS_TRIG_DAC_WR_0 */ +#define WM8995_DCS_TRIG_DAC_WR_0_MASK 0x0001 /* DCS_TRIG_DAC_WR_0 */ +#define WM8995_DCS_TRIG_DAC_WR_0_SHIFT 0 /* DCS_TRIG_DAC_WR_0 */ +#define WM8995_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ + +/* + * R82 (0x52) - DC Servo (3) + */ +#define WM8995_DCS_TIMER_PERIOD_23_MASK 0x0F00 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8995_DCS_TIMER_PERIOD_23_SHIFT 8 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8995_DCS_TIMER_PERIOD_23_WIDTH 4 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8995_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8995_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8995_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R84 (0x54) - DC Servo (5) + */ +#define WM8995_DCS_SERIES_NO_23_MASK 0x7F00 /* DCS_SERIES_NO_23 - [14:8] */ +#define WM8995_DCS_SERIES_NO_23_SHIFT 8 /* DCS_SERIES_NO_23 - [14:8] */ +#define WM8995_DCS_SERIES_NO_23_WIDTH 7 /* DCS_SERIES_NO_23 - [14:8] */ +#define WM8995_DCS_SERIES_NO_01_MASK 0x007F /* DCS_SERIES_NO_01 - [6:0] */ +#define WM8995_DCS_SERIES_NO_01_SHIFT 0 /* DCS_SERIES_NO_01 - [6:0] */ +#define WM8995_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [6:0] */ + +/* + * R85 (0x55) - DC Servo (6) + */ +#define WM8995_DCS_DAC_WR_VAL_3_MASK 0xFF00 /* DCS_DAC_WR_VAL_3 - [15:8] */ +#define WM8995_DCS_DAC_WR_VAL_3_SHIFT 8 /* DCS_DAC_WR_VAL_3 - [15:8] */ +#define WM8995_DCS_DAC_WR_VAL_3_WIDTH 8 /* DCS_DAC_WR_VAL_3 - [15:8] */ +#define WM8995_DCS_DAC_WR_VAL_2_MASK 0x00FF /* DCS_DAC_WR_VAL_2 - [7:0] */ +#define WM8995_DCS_DAC_WR_VAL_2_SHIFT 0 /* DCS_DAC_WR_VAL_2 - [7:0] */ +#define WM8995_DCS_DAC_WR_VAL_2_WIDTH 8 /* DCS_DAC_WR_VAL_2 - [7:0] */ + +/* + * R86 (0x56) - DC Servo (7) + */ +#define WM8995_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8995_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8995_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8995_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8995_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8995_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R87 (0x57) - DC Servo Readback 0 + */ +#define WM8995_DCS_CAL_COMPLETE_MASK 0x0F00 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8995_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8995_DCS_CAL_COMPLETE_WIDTH 4 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8995_DCS_DAC_WR_COMPLETE_MASK 0x00F0 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8995_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8995_DCS_DAC_WR_COMPLETE_WIDTH 4 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8995_DCS_STARTUP_COMPLETE_MASK 0x000F /* DCS_STARTUP_COMPLETE - [3:0] */ +#define WM8995_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [3:0] */ +#define WM8995_DCS_STARTUP_COMPLETE_WIDTH 4 /* DCS_STARTUP_COMPLETE - [3:0] */ + +/* + * R96 (0x60) - Analogue HP (1) + */ +#define WM8995_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8995_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8995_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ +#define WM8995_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */ +#define WM8995_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */ +#define WM8995_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */ +#define WM8995_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */ +#define WM8995_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */ +#define WM8995_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */ +#define WM8995_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */ +#define WM8995_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */ +#define WM8995_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */ +#define WM8995_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8995_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8995_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */ +#define WM8995_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */ +#define WM8995_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */ +#define WM8995_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */ +#define WM8995_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */ +#define WM8995_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */ +#define WM8995_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */ +#define WM8995_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */ +#define WM8995_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */ +#define WM8995_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */ + +/* + * R97 (0x61) - Analogue HP (2) + */ +#define WM8995_HPOUT2L_RMV_SHORT 0x0080 /* HPOUT2L_RMV_SHORT */ +#define WM8995_HPOUT2L_RMV_SHORT_MASK 0x0080 /* HPOUT2L_RMV_SHORT */ +#define WM8995_HPOUT2L_RMV_SHORT_SHIFT 7 /* HPOUT2L_RMV_SHORT */ +#define WM8995_HPOUT2L_RMV_SHORT_WIDTH 1 /* HPOUT2L_RMV_SHORT */ +#define WM8995_HPOUT2L_OUTP 0x0040 /* HPOUT2L_OUTP */ +#define WM8995_HPOUT2L_OUTP_MASK 0x0040 /* HPOUT2L_OUTP */ +#define WM8995_HPOUT2L_OUTP_SHIFT 6 /* HPOUT2L_OUTP */ +#define WM8995_HPOUT2L_OUTP_WIDTH 1 /* HPOUT2L_OUTP */ +#define WM8995_HPOUT2L_DLY 0x0020 /* HPOUT2L_DLY */ +#define WM8995_HPOUT2L_DLY_MASK 0x0020 /* HPOUT2L_DLY */ +#define WM8995_HPOUT2L_DLY_SHIFT 5 /* HPOUT2L_DLY */ +#define WM8995_HPOUT2L_DLY_WIDTH 1 /* HPOUT2L_DLY */ +#define WM8995_HPOUT2R_RMV_SHORT 0x0008 /* HPOUT2R_RMV_SHORT */ +#define WM8995_HPOUT2R_RMV_SHORT_MASK 0x0008 /* HPOUT2R_RMV_SHORT */ +#define WM8995_HPOUT2R_RMV_SHORT_SHIFT 3 /* HPOUT2R_RMV_SHORT */ +#define WM8995_HPOUT2R_RMV_SHORT_WIDTH 1 /* HPOUT2R_RMV_SHORT */ +#define WM8995_HPOUT2R_OUTP 0x0004 /* HPOUT2R_OUTP */ +#define WM8995_HPOUT2R_OUTP_MASK 0x0004 /* HPOUT2R_OUTP */ +#define WM8995_HPOUT2R_OUTP_SHIFT 2 /* HPOUT2R_OUTP */ +#define WM8995_HPOUT2R_OUTP_WIDTH 1 /* HPOUT2R_OUTP */ +#define WM8995_HPOUT2R_DLY 0x0002 /* HPOUT2R_DLY */ +#define WM8995_HPOUT2R_DLY_MASK 0x0002 /* HPOUT2R_DLY */ +#define WM8995_HPOUT2R_DLY_SHIFT 1 /* HPOUT2R_DLY */ +#define WM8995_HPOUT2R_DLY_WIDTH 1 /* HPOUT2R_DLY */ + +/* + * R256 (0x100) - Chip Revision + */ +#define WM8995_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */ +#define WM8995_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */ +#define WM8995_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */ + +/* + * R257 (0x101) - Control Interface (1) + */ +#define WM8995_REG_SYNC 0x8000 /* REG_SYNC */ +#define WM8995_REG_SYNC_MASK 0x8000 /* REG_SYNC */ +#define WM8995_REG_SYNC_SHIFT 15 /* REG_SYNC */ +#define WM8995_REG_SYNC_WIDTH 1 /* REG_SYNC */ +#define WM8995_SPI_CONTRD 0x0040 /* SPI_CONTRD */ +#define WM8995_SPI_CONTRD_MASK 0x0040 /* SPI_CONTRD */ +#define WM8995_SPI_CONTRD_SHIFT 6 /* SPI_CONTRD */ +#define WM8995_SPI_CONTRD_WIDTH 1 /* SPI_CONTRD */ +#define WM8995_SPI_4WIRE 0x0020 /* SPI_4WIRE */ +#define WM8995_SPI_4WIRE_MASK 0x0020 /* SPI_4WIRE */ +#define WM8995_SPI_4WIRE_SHIFT 5 /* SPI_4WIRE */ +#define WM8995_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */ +#define WM8995_SPI_CFG 0x0010 /* SPI_CFG */ +#define WM8995_SPI_CFG_MASK 0x0010 /* SPI_CFG */ +#define WM8995_SPI_CFG_SHIFT 4 /* SPI_CFG */ +#define WM8995_SPI_CFG_WIDTH 1 /* SPI_CFG */ +#define WM8995_AUTO_INC 0x0004 /* AUTO_INC */ +#define WM8995_AUTO_INC_MASK 0x0004 /* AUTO_INC */ +#define WM8995_AUTO_INC_SHIFT 2 /* AUTO_INC */ +#define WM8995_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +/* + * R258 (0x102) - Control Interface (2) + */ +#define WM8995_CTRL_IF_SRC 0x0001 /* CTRL_IF_SRC */ +#define WM8995_CTRL_IF_SRC_MASK 0x0001 /* CTRL_IF_SRC */ +#define WM8995_CTRL_IF_SRC_SHIFT 0 /* CTRL_IF_SRC */ +#define WM8995_CTRL_IF_SRC_WIDTH 1 /* CTRL_IF_SRC */ + +/* + * R272 (0x110) - Write Sequencer Ctrl (1) + */ +#define WM8995_WSEQ_ENA 0x8000 /* WSEQ_ENA */ +#define WM8995_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */ +#define WM8995_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */ +#define WM8995_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8995_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8995_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8995_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8995_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8995_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8995_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8995_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8995_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8995_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */ +#define WM8995_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */ +#define WM8995_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */ + +/* + * R273 (0x111) - Write Sequencer Ctrl (2) + */ +#define WM8995_WSEQ_BUSY 0x0100 /* WSEQ_BUSY */ +#define WM8995_WSEQ_BUSY_MASK 0x0100 /* WSEQ_BUSY */ +#define WM8995_WSEQ_BUSY_SHIFT 8 /* WSEQ_BUSY */ +#define WM8995_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ +#define WM8995_WSEQ_CURRENT_INDEX_MASK 0x007F /* WSEQ_CURRENT_INDEX - [6:0] */ +#define WM8995_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [6:0] */ +#define WM8995_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [6:0] */ + +/* + * R512 (0x200) - AIF1 Clocking (1) + */ +#define WM8995_AIF1CLK_SRC_MASK 0x0018 /* AIF1CLK_SRC - [4:3] */ +#define WM8995_AIF1CLK_SRC_SHIFT 3 /* AIF1CLK_SRC - [4:3] */ +#define WM8995_AIF1CLK_SRC_WIDTH 2 /* AIF1CLK_SRC - [4:3] */ +#define WM8995_AIF1CLK_INV 0x0004 /* AIF1CLK_INV */ +#define WM8995_AIF1CLK_INV_MASK 0x0004 /* AIF1CLK_INV */ +#define WM8995_AIF1CLK_INV_SHIFT 2 /* AIF1CLK_INV */ +#define WM8995_AIF1CLK_INV_WIDTH 1 /* AIF1CLK_INV */ +#define WM8995_AIF1CLK_DIV 0x0002 /* AIF1CLK_DIV */ +#define WM8995_AIF1CLK_DIV_MASK 0x0002 /* AIF1CLK_DIV */ +#define WM8995_AIF1CLK_DIV_SHIFT 1 /* AIF1CLK_DIV */ +#define WM8995_AIF1CLK_DIV_WIDTH 1 /* AIF1CLK_DIV */ +#define WM8995_AIF1CLK_ENA 0x0001 /* AIF1CLK_ENA */ +#define WM8995_AIF1CLK_ENA_MASK 0x0001 /* AIF1CLK_ENA */ +#define WM8995_AIF1CLK_ENA_SHIFT 0 /* AIF1CLK_ENA */ +#define WM8995_AIF1CLK_ENA_WIDTH 1 /* AIF1CLK_ENA */ + +/* + * R513 (0x201) - AIF1 Clocking (2) + */ +#define WM8995_AIF1DAC_DIV_MASK 0x0038 /* AIF1DAC_DIV - [5:3] */ +#define WM8995_AIF1DAC_DIV_SHIFT 3 /* AIF1DAC_DIV - [5:3] */ +#define WM8995_AIF1DAC_DIV_WIDTH 3 /* AIF1DAC_DIV - [5:3] */ +#define WM8995_AIF1ADC_DIV_MASK 0x0007 /* AIF1ADC_DIV - [2:0] */ +#define WM8995_AIF1ADC_DIV_SHIFT 0 /* AIF1ADC_DIV - [2:0] */ +#define WM8995_AIF1ADC_DIV_WIDTH 3 /* AIF1ADC_DIV - [2:0] */ + +/* + * R516 (0x204) - AIF2 Clocking (1) + */ +#define WM8995_AIF2CLK_SRC_MASK 0x0018 /* AIF2CLK_SRC - [4:3] */ +#define WM8995_AIF2CLK_SRC_SHIFT 3 /* AIF2CLK_SRC - [4:3] */ +#define WM8995_AIF2CLK_SRC_WIDTH 2 /* AIF2CLK_SRC - [4:3] */ +#define WM8995_AIF2CLK_INV 0x0004 /* AIF2CLK_INV */ +#define WM8995_AIF2CLK_INV_MASK 0x0004 /* AIF2CLK_INV */ +#define WM8995_AIF2CLK_INV_SHIFT 2 /* AIF2CLK_INV */ +#define WM8995_AIF2CLK_INV_WIDTH 1 /* AIF2CLK_INV */ +#define WM8995_AIF2CLK_DIV 0x0002 /* AIF2CLK_DIV */ +#define WM8995_AIF2CLK_DIV_MASK 0x0002 /* AIF2CLK_DIV */ +#define WM8995_AIF2CLK_DIV_SHIFT 1 /* AIF2CLK_DIV */ +#define WM8995_AIF2CLK_DIV_WIDTH 1 /* AIF2CLK_DIV */ +#define WM8995_AIF2CLK_ENA 0x0001 /* AIF2CLK_ENA */ +#define WM8995_AIF2CLK_ENA_MASK 0x0001 /* AIF2CLK_ENA */ +#define WM8995_AIF2CLK_ENA_SHIFT 0 /* AIF2CLK_ENA */ +#define WM8995_AIF2CLK_ENA_WIDTH 1 /* AIF2CLK_ENA */ + +/* + * R517 (0x205) - AIF2 Clocking (2) + */ +#define WM8995_AIF2DAC_DIV_MASK 0x0038 /* AIF2DAC_DIV - [5:3] */ +#define WM8995_AIF2DAC_DIV_SHIFT 3 /* AIF2DAC_DIV - [5:3] */ +#define WM8995_AIF2DAC_DIV_WIDTH 3 /* AIF2DAC_DIV - [5:3] */ +#define WM8995_AIF2ADC_DIV_MASK 0x0007 /* AIF2ADC_DIV - [2:0] */ +#define WM8995_AIF2ADC_DIV_SHIFT 0 /* AIF2ADC_DIV - [2:0] */ +#define WM8995_AIF2ADC_DIV_WIDTH 3 /* AIF2ADC_DIV - [2:0] */ + +/* + * R520 (0x208) - Clocking (1) + */ +#define WM8995_LFCLK_ENA 0x0020 /* LFCLK_ENA */ +#define WM8995_LFCLK_ENA_MASK 0x0020 /* LFCLK_ENA */ +#define WM8995_LFCLK_ENA_SHIFT 5 /* LFCLK_ENA */ +#define WM8995_LFCLK_ENA_WIDTH 1 /* LFCLK_ENA */ +#define WM8995_TOCLK_ENA 0x0010 /* TOCLK_ENA */ +#define WM8995_TOCLK_ENA_MASK 0x0010 /* TOCLK_ENA */ +#define WM8995_TOCLK_ENA_SHIFT 4 /* TOCLK_ENA */ +#define WM8995_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ +#define WM8995_AIF1DSPCLK_ENA 0x0008 /* AIF1DSPCLK_ENA */ +#define WM8995_AIF1DSPCLK_ENA_MASK 0x0008 /* AIF1DSPCLK_ENA */ +#define WM8995_AIF1DSPCLK_ENA_SHIFT 3 /* AIF1DSPCLK_ENA */ +#define WM8995_AIF1DSPCLK_ENA_WIDTH 1 /* AIF1DSPCLK_ENA */ +#define WM8995_AIF2DSPCLK_ENA 0x0004 /* AIF2DSPCLK_ENA */ +#define WM8995_AIF2DSPCLK_ENA_MASK 0x0004 /* AIF2DSPCLK_ENA */ +#define WM8995_AIF2DSPCLK_ENA_SHIFT 2 /* AIF2DSPCLK_ENA */ +#define WM8995_AIF2DSPCLK_ENA_WIDTH 1 /* AIF2DSPCLK_ENA */ +#define WM8995_SYSDSPCLK_ENA 0x0002 /* SYSDSPCLK_ENA */ +#define WM8995_SYSDSPCLK_ENA_MASK 0x0002 /* SYSDSPCLK_ENA */ +#define WM8995_SYSDSPCLK_ENA_SHIFT 1 /* SYSDSPCLK_ENA */ +#define WM8995_SYSDSPCLK_ENA_WIDTH 1 /* SYSDSPCLK_ENA */ +#define WM8995_SYSCLK_SRC 0x0001 /* SYSCLK_SRC */ +#define WM8995_SYSCLK_SRC_MASK 0x0001 /* SYSCLK_SRC */ +#define WM8995_SYSCLK_SRC_SHIFT 0 /* SYSCLK_SRC */ +#define WM8995_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */ + +/* + * R521 (0x209) - Clocking (2) + */ +#define WM8995_TOCLK_DIV_MASK 0x0700 /* TOCLK_DIV - [10:8] */ +#define WM8995_TOCLK_DIV_SHIFT 8 /* TOCLK_DIV - [10:8] */ +#define WM8995_TOCLK_DIV_WIDTH 3 /* TOCLK_DIV - [10:8] */ +#define WM8995_DBCLK_DIV_MASK 0x00F0 /* DBCLK_DIV - [7:4] */ +#define WM8995_DBCLK_DIV_SHIFT 4 /* DBCLK_DIV - [7:4] */ +#define WM8995_DBCLK_DIV_WIDTH 4 /* DBCLK_DIV - [7:4] */ +#define WM8995_OPCLK_DIV_MASK 0x0007 /* OPCLK_DIV - [2:0] */ +#define WM8995_OPCLK_DIV_SHIFT 0 /* OPCLK_DIV - [2:0] */ +#define WM8995_OPCLK_DIV_WIDTH 3 /* OPCLK_DIV - [2:0] */ + +/* + * R528 (0x210) - AIF1 Rate + */ +#define WM8995_AIF1_SR_MASK 0x00F0 /* AIF1_SR - [7:4] */ +#define WM8995_AIF1_SR_SHIFT 4 /* AIF1_SR - [7:4] */ +#define WM8995_AIF1_SR_WIDTH 4 /* AIF1_SR - [7:4] */ +#define WM8995_AIF1CLK_RATE_MASK 0x000F /* AIF1CLK_RATE - [3:0] */ +#define WM8995_AIF1CLK_RATE_SHIFT 0 /* AIF1CLK_RATE - [3:0] */ +#define WM8995_AIF1CLK_RATE_WIDTH 4 /* AIF1CLK_RATE - [3:0] */ + +/* + * R529 (0x211) - AIF2 Rate + */ +#define WM8995_AIF2_SR_MASK 0x00F0 /* AIF2_SR - [7:4] */ +#define WM8995_AIF2_SR_SHIFT 4 /* AIF2_SR - [7:4] */ +#define WM8995_AIF2_SR_WIDTH 4 /* AIF2_SR - [7:4] */ +#define WM8995_AIF2CLK_RATE_MASK 0x000F /* AIF2CLK_RATE - [3:0] */ +#define WM8995_AIF2CLK_RATE_SHIFT 0 /* AIF2CLK_RATE - [3:0] */ +#define WM8995_AIF2CLK_RATE_WIDTH 4 /* AIF2CLK_RATE - [3:0] */ + +/* + * R530 (0x212) - Rate Status + */ +#define WM8995_SR_ERROR_MASK 0x000F /* SR_ERROR - [3:0] */ +#define WM8995_SR_ERROR_SHIFT 0 /* SR_ERROR - [3:0] */ +#define WM8995_SR_ERROR_WIDTH 4 /* SR_ERROR - [3:0] */ + +/* + * R544 (0x220) - FLL1 Control (1) + */ +#define WM8995_FLL1_OSC_ENA 0x0002 /* FLL1_OSC_ENA */ +#define WM8995_FLL1_OSC_ENA_MASK 0x0002 /* FLL1_OSC_ENA */ +#define WM8995_FLL1_OSC_ENA_SHIFT 1 /* FLL1_OSC_ENA */ +#define WM8995_FLL1_OSC_ENA_WIDTH 1 /* FLL1_OSC_ENA */ +#define WM8995_FLL1_ENA 0x0001 /* FLL1_ENA */ +#define WM8995_FLL1_ENA_MASK 0x0001 /* FLL1_ENA */ +#define WM8995_FLL1_ENA_SHIFT 0 /* FLL1_ENA */ +#define WM8995_FLL1_ENA_WIDTH 1 /* FLL1_ENA */ + +/* + * R545 (0x221) - FLL1 Control (2) + */ +#define WM8995_FLL1_OUTDIV_MASK 0x3F00 /* FLL1_OUTDIV - [13:8] */ +#define WM8995_FLL1_OUTDIV_SHIFT 8 /* FLL1_OUTDIV - [13:8] */ +#define WM8995_FLL1_OUTDIV_WIDTH 6 /* FLL1_OUTDIV - [13:8] */ +#define WM8995_FLL1_CTRL_RATE_MASK 0x0070 /* FLL1_CTRL_RATE - [6:4] */ +#define WM8995_FLL1_CTRL_RATE_SHIFT 4 /* FLL1_CTRL_RATE - [6:4] */ +#define WM8995_FLL1_CTRL_RATE_WIDTH 3 /* FLL1_CTRL_RATE - [6:4] */ +#define WM8995_FLL1_FRATIO_MASK 0x0007 /* FLL1_FRATIO - [2:0] */ +#define WM8995_FLL1_FRATIO_SHIFT 0 /* FLL1_FRATIO - [2:0] */ +#define WM8995_FLL1_FRATIO_WIDTH 3 /* FLL1_FRATIO - [2:0] */ + +/* + * R546 (0x222) - FLL1 Control (3) + */ +#define WM8995_FLL1_K_MASK 0xFFFF /* FLL1_K - [15:0] */ +#define WM8995_FLL1_K_SHIFT 0 /* FLL1_K - [15:0] */ +#define WM8995_FLL1_K_WIDTH 16 /* FLL1_K - [15:0] */ + +/* + * R547 (0x223) - FLL1 Control (4) + */ +#define WM8995_FLL1_N_MASK 0x7FE0 /* FLL1_N - [14:5] */ +#define WM8995_FLL1_N_SHIFT 5 /* FLL1_N - [14:5] */ +#define WM8995_FLL1_N_WIDTH 10 /* FLL1_N - [14:5] */ +#define WM8995_FLL1_LOOP_GAIN_MASK 0x000F /* FLL1_LOOP_GAIN - [3:0] */ +#define WM8995_FLL1_LOOP_GAIN_SHIFT 0 /* FLL1_LOOP_GAIN - [3:0] */ +#define WM8995_FLL1_LOOP_GAIN_WIDTH 4 /* FLL1_LOOP_GAIN - [3:0] */ + +/* + * R548 (0x224) - FLL1 Control (5) + */ +#define WM8995_FLL1_FRC_NCO_VAL_MASK 0x1F80 /* FLL1_FRC_NCO_VAL - [12:7] */ +#define WM8995_FLL1_FRC_NCO_VAL_SHIFT 7 /* FLL1_FRC_NCO_VAL - [12:7] */ +#define WM8995_FLL1_FRC_NCO_VAL_WIDTH 6 /* FLL1_FRC_NCO_VAL - [12:7] */ +#define WM8995_FLL1_FRC_NCO 0x0040 /* FLL1_FRC_NCO */ +#define WM8995_FLL1_FRC_NCO_MASK 0x0040 /* FLL1_FRC_NCO */ +#define WM8995_FLL1_FRC_NCO_SHIFT 6 /* FLL1_FRC_NCO */ +#define WM8995_FLL1_FRC_NCO_WIDTH 1 /* FLL1_FRC_NCO */ +#define WM8995_FLL1_REFCLK_DIV_MASK 0x0018 /* FLL1_REFCLK_DIV - [4:3] */ +#define WM8995_FLL1_REFCLK_DIV_SHIFT 3 /* FLL1_REFCLK_DIV - [4:3] */ +#define WM8995_FLL1_REFCLK_DIV_WIDTH 2 /* FLL1_REFCLK_DIV - [4:3] */ +#define WM8995_FLL1_REFCLK_SRC_MASK 0x0003 /* FLL1_REFCLK_SRC - [1:0] */ +#define WM8995_FLL1_REFCLK_SRC_SHIFT 0 /* FLL1_REFCLK_SRC - [1:0] */ +#define WM8995_FLL1_REFCLK_SRC_WIDTH 2 /* FLL1_REFCLK_SRC - [1:0] */ + +/* + * R576 (0x240) - FLL2 Control (1) + */ +#define WM8995_FLL2_OSC_ENA 0x0002 /* FLL2_OSC_ENA */ +#define WM8995_FLL2_OSC_ENA_MASK 0x0002 /* FLL2_OSC_ENA */ +#define WM8995_FLL2_OSC_ENA_SHIFT 1 /* FLL2_OSC_ENA */ +#define WM8995_FLL2_OSC_ENA_WIDTH 1 /* FLL2_OSC_ENA */ +#define WM8995_FLL2_ENA 0x0001 /* FLL2_ENA */ +#define WM8995_FLL2_ENA_MASK 0x0001 /* FLL2_ENA */ +#define WM8995_FLL2_ENA_SHIFT 0 /* FLL2_ENA */ +#define WM8995_FLL2_ENA_WIDTH 1 /* FLL2_ENA */ + +/* + * R577 (0x241) - FLL2 Control (2) + */ +#define WM8995_FLL2_OUTDIV_MASK 0x3F00 /* FLL2_OUTDIV - [13:8] */ +#define WM8995_FLL2_OUTDIV_SHIFT 8 /* FLL2_OUTDIV - [13:8] */ +#define WM8995_FLL2_OUTDIV_WIDTH 6 /* FLL2_OUTDIV - [13:8] */ +#define WM8995_FLL2_CTRL_RATE_MASK 0x0070 /* FLL2_CTRL_RATE - [6:4] */ +#define WM8995_FLL2_CTRL_RATE_SHIFT 4 /* FLL2_CTRL_RATE - [6:4] */ +#define WM8995_FLL2_CTRL_RATE_WIDTH 3 /* FLL2_CTRL_RATE - [6:4] */ +#define WM8995_FLL2_FRATIO_MASK 0x0007 /* FLL2_FRATIO - [2:0] */ +#define WM8995_FLL2_FRATIO_SHIFT 0 /* FLL2_FRATIO - [2:0] */ +#define WM8995_FLL2_FRATIO_WIDTH 3 /* FLL2_FRATIO - [2:0] */ + +/* + * R578 (0x242) - FLL2 Control (3) + */ +#define WM8995_FLL2_K_MASK 0xFFFF /* FLL2_K - [15:0] */ +#define WM8995_FLL2_K_SHIFT 0 /* FLL2_K - [15:0] */ +#define WM8995_FLL2_K_WIDTH 16 /* FLL2_K - [15:0] */ + +/* + * R579 (0x243) - FLL2 Control (4) + */ +#define WM8995_FLL2_N_MASK 0x7FE0 /* FLL2_N - [14:5] */ +#define WM8995_FLL2_N_SHIFT 5 /* FLL2_N - [14:5] */ +#define WM8995_FLL2_N_WIDTH 10 /* FLL2_N - [14:5] */ +#define WM8995_FLL2_LOOP_GAIN_MASK 0x000F /* FLL2_LOOP_GAIN - [3:0] */ +#define WM8995_FLL2_LOOP_GAIN_SHIFT 0 /* FLL2_LOOP_GAIN - [3:0] */ +#define WM8995_FLL2_LOOP_GAIN_WIDTH 4 /* FLL2_LOOP_GAIN - [3:0] */ + +/* + * R580 (0x244) - FLL2 Control (5) + */ +#define WM8995_FLL2_FRC_NCO_VAL_MASK 0x1F80 /* FLL2_FRC_NCO_VAL - [12:7] */ +#define WM8995_FLL2_FRC_NCO_VAL_SHIFT 7 /* FLL2_FRC_NCO_VAL - [12:7] */ +#define WM8995_FLL2_FRC_NCO_VAL_WIDTH 6 /* FLL2_FRC_NCO_VAL - [12:7] */ +#define WM8995_FLL2_FRC_NCO 0x0040 /* FLL2_FRC_NCO */ +#define WM8995_FLL2_FRC_NCO_MASK 0x0040 /* FLL2_FRC_NCO */ +#define WM8995_FLL2_FRC_NCO_SHIFT 6 /* FLL2_FRC_NCO */ +#define WM8995_FLL2_FRC_NCO_WIDTH 1 /* FLL2_FRC_NCO */ +#define WM8995_FLL2_REFCLK_DIV_MASK 0x0018 /* FLL2_REFCLK_DIV - [4:3] */ +#define WM8995_FLL2_REFCLK_DIV_SHIFT 3 /* FLL2_REFCLK_DIV - [4:3] */ +#define WM8995_FLL2_REFCLK_DIV_WIDTH 2 /* FLL2_REFCLK_DIV - [4:3] */ +#define WM8995_FLL2_REFCLK_SRC_MASK 0x0003 /* FLL2_REFCLK_SRC - [1:0] */ +#define WM8995_FLL2_REFCLK_SRC_SHIFT 0 /* FLL2_REFCLK_SRC - [1:0] */ +#define WM8995_FLL2_REFCLK_SRC_WIDTH 2 /* FLL2_REFCLK_SRC - [1:0] */ + +/* + * R768 (0x300) - AIF1 Control (1) + */ +#define WM8995_AIF1ADCL_SRC 0x8000 /* AIF1ADCL_SRC */ +#define WM8995_AIF1ADCL_SRC_MASK 0x8000 /* AIF1ADCL_SRC */ +#define WM8995_AIF1ADCL_SRC_SHIFT 15 /* AIF1ADCL_SRC */ +#define WM8995_AIF1ADCL_SRC_WIDTH 1 /* AIF1ADCL_SRC */ +#define WM8995_AIF1ADCR_SRC 0x4000 /* AIF1ADCR_SRC */ +#define WM8995_AIF1ADCR_SRC_MASK 0x4000 /* AIF1ADCR_SRC */ +#define WM8995_AIF1ADCR_SRC_SHIFT 14 /* AIF1ADCR_SRC */ +#define WM8995_AIF1ADCR_SRC_WIDTH 1 /* AIF1ADCR_SRC */ +#define WM8995_AIF1ADC_TDM 0x2000 /* AIF1ADC_TDM */ +#define WM8995_AIF1ADC_TDM_MASK 0x2000 /* AIF1ADC_TDM */ +#define WM8995_AIF1ADC_TDM_SHIFT 13 /* AIF1ADC_TDM */ +#define WM8995_AIF1ADC_TDM_WIDTH 1 /* AIF1ADC_TDM */ +#define WM8995_AIF1_BCLK_INV 0x0100 /* AIF1_BCLK_INV */ +#define WM8995_AIF1_BCLK_INV_MASK 0x0100 /* AIF1_BCLK_INV */ +#define WM8995_AIF1_BCLK_INV_SHIFT 8 /* AIF1_BCLK_INV */ +#define WM8995_AIF1_BCLK_INV_WIDTH 1 /* AIF1_BCLK_INV */ +#define WM8995_AIF1_LRCLK_INV 0x0080 /* AIF1_LRCLK_INV */ +#define WM8995_AIF1_LRCLK_INV_MASK 0x0080 /* AIF1_LRCLK_INV */ +#define WM8995_AIF1_LRCLK_INV_SHIFT 7 /* AIF1_LRCLK_INV */ +#define WM8995_AIF1_LRCLK_INV_WIDTH 1 /* AIF1_LRCLK_INV */ +#define WM8995_AIF1_WL_MASK 0x0060 /* AIF1_WL - [6:5] */ +#define WM8995_AIF1_WL_SHIFT 5 /* AIF1_WL - [6:5] */ +#define WM8995_AIF1_WL_WIDTH 2 /* AIF1_WL - [6:5] */ +#define WM8995_AIF1_FMT_MASK 0x0018 /* AIF1_FMT - [4:3] */ +#define WM8995_AIF1_FMT_SHIFT 3 /* AIF1_FMT - [4:3] */ +#define WM8995_AIF1_FMT_WIDTH 2 /* AIF1_FMT - [4:3] */ + +/* + * R769 (0x301) - AIF1 Control (2) + */ +#define WM8995_AIF1DACL_SRC 0x8000 /* AIF1DACL_SRC */ +#define WM8995_AIF1DACL_SRC_MASK 0x8000 /* AIF1DACL_SRC */ +#define WM8995_AIF1DACL_SRC_SHIFT 15 /* AIF1DACL_SRC */ +#define WM8995_AIF1DACL_SRC_WIDTH 1 /* AIF1DACL_SRC */ +#define WM8995_AIF1DACR_SRC 0x4000 /* AIF1DACR_SRC */ +#define WM8995_AIF1DACR_SRC_MASK 0x4000 /* AIF1DACR_SRC */ +#define WM8995_AIF1DACR_SRC_SHIFT 14 /* AIF1DACR_SRC */ +#define WM8995_AIF1DACR_SRC_WIDTH 1 /* AIF1DACR_SRC */ +#define WM8995_AIF1DAC_BOOST_MASK 0x0C00 /* AIF1DAC_BOOST - [11:10] */ +#define WM8995_AIF1DAC_BOOST_SHIFT 10 /* AIF1DAC_BOOST - [11:10] */ +#define WM8995_AIF1DAC_BOOST_WIDTH 2 /* AIF1DAC_BOOST - [11:10] */ +#define WM8995_AIF1DAC_COMP 0x0010 /* AIF1DAC_COMP */ +#define WM8995_AIF1DAC_COMP_MASK 0x0010 /* AIF1DAC_COMP */ +#define WM8995_AIF1DAC_COMP_SHIFT 4 /* AIF1DAC_COMP */ +#define WM8995_AIF1DAC_COMP_WIDTH 1 /* AIF1DAC_COMP */ +#define WM8995_AIF1DAC_COMPMODE 0x0008 /* AIF1DAC_COMPMODE */ +#define WM8995_AIF1DAC_COMPMODE_MASK 0x0008 /* AIF1DAC_COMPMODE */ +#define WM8995_AIF1DAC_COMPMODE_SHIFT 3 /* AIF1DAC_COMPMODE */ +#define WM8995_AIF1DAC_COMPMODE_WIDTH 1 /* AIF1DAC_COMPMODE */ +#define WM8995_AIF1ADC_COMP 0x0004 /* AIF1ADC_COMP */ +#define WM8995_AIF1ADC_COMP_MASK 0x0004 /* AIF1ADC_COMP */ +#define WM8995_AIF1ADC_COMP_SHIFT 2 /* AIF1ADC_COMP */ +#define WM8995_AIF1ADC_COMP_WIDTH 1 /* AIF1ADC_COMP */ +#define WM8995_AIF1ADC_COMPMODE 0x0002 /* AIF1ADC_COMPMODE */ +#define WM8995_AIF1ADC_COMPMODE_MASK 0x0002 /* AIF1ADC_COMPMODE */ +#define WM8995_AIF1ADC_COMPMODE_SHIFT 1 /* AIF1ADC_COMPMODE */ +#define WM8995_AIF1ADC_COMPMODE_WIDTH 1 /* AIF1ADC_COMPMODE */ +#define WM8995_AIF1_LOOPBACK 0x0001 /* AIF1_LOOPBACK */ +#define WM8995_AIF1_LOOPBACK_MASK 0x0001 /* AIF1_LOOPBACK */ +#define WM8995_AIF1_LOOPBACK_SHIFT 0 /* AIF1_LOOPBACK */ +#define WM8995_AIF1_LOOPBACK_WIDTH 1 /* AIF1_LOOPBACK */ + +/* + * R770 (0x302) - AIF1 Master/Slave + */ +#define WM8995_AIF1_TRI 0x8000 /* AIF1_TRI */ +#define WM8995_AIF1_TRI_MASK 0x8000 /* AIF1_TRI */ +#define WM8995_AIF1_TRI_SHIFT 15 /* AIF1_TRI */ +#define WM8995_AIF1_TRI_WIDTH 1 /* AIF1_TRI */ +#define WM8995_AIF1_MSTR 0x4000 /* AIF1_MSTR */ +#define WM8995_AIF1_MSTR_MASK 0x4000 /* AIF1_MSTR */ +#define WM8995_AIF1_MSTR_SHIFT 14 /* AIF1_MSTR */ +#define WM8995_AIF1_MSTR_WIDTH 1 /* AIF1_MSTR */ +#define WM8995_AIF1_CLK_FRC 0x2000 /* AIF1_CLK_FRC */ +#define WM8995_AIF1_CLK_FRC_MASK 0x2000 /* AIF1_CLK_FRC */ +#define WM8995_AIF1_CLK_FRC_SHIFT 13 /* AIF1_CLK_FRC */ +#define WM8995_AIF1_CLK_FRC_WIDTH 1 /* AIF1_CLK_FRC */ +#define WM8995_AIF1_LRCLK_FRC 0x1000 /* AIF1_LRCLK_FRC */ +#define WM8995_AIF1_LRCLK_FRC_MASK 0x1000 /* AIF1_LRCLK_FRC */ +#define WM8995_AIF1_LRCLK_FRC_SHIFT 12 /* AIF1_LRCLK_FRC */ +#define WM8995_AIF1_LRCLK_FRC_WIDTH 1 /* AIF1_LRCLK_FRC */ + +/* + * R771 (0x303) - AIF1 BCLK + */ +#define WM8995_AIF1_BCLK_DIV_MASK 0x00F0 /* AIF1_BCLK_DIV - [7:4] */ +#define WM8995_AIF1_BCLK_DIV_SHIFT 4 /* AIF1_BCLK_DIV - [7:4] */ +#define WM8995_AIF1_BCLK_DIV_WIDTH 4 /* AIF1_BCLK_DIV - [7:4] */ + +/* + * R772 (0x304) - AIF1ADC LRCLK + */ +#define WM8995_AIF1ADC_LRCLK_DIR 0x0800 /* AIF1ADC_LRCLK_DIR */ +#define WM8995_AIF1ADC_LRCLK_DIR_MASK 0x0800 /* AIF1ADC_LRCLK_DIR */ +#define WM8995_AIF1ADC_LRCLK_DIR_SHIFT 11 /* AIF1ADC_LRCLK_DIR */ +#define WM8995_AIF1ADC_LRCLK_DIR_WIDTH 1 /* AIF1ADC_LRCLK_DIR */ +#define WM8995_AIF1ADC_RATE_MASK 0x07FF /* AIF1ADC_RATE - [10:0] */ +#define WM8995_AIF1ADC_RATE_SHIFT 0 /* AIF1ADC_RATE - [10:0] */ +#define WM8995_AIF1ADC_RATE_WIDTH 11 /* AIF1ADC_RATE - [10:0] */ + +/* + * R773 (0x305) - AIF1DAC LRCLK + */ +#define WM8995_AIF1DAC_LRCLK_DIR 0x0800 /* AIF1DAC_LRCLK_DIR */ +#define WM8995_AIF1DAC_LRCLK_DIR_MASK 0x0800 /* AIF1DAC_LRCLK_DIR */ +#define WM8995_AIF1DAC_LRCLK_DIR_SHIFT 11 /* AIF1DAC_LRCLK_DIR */ +#define WM8995_AIF1DAC_LRCLK_DIR_WIDTH 1 /* AIF1DAC_LRCLK_DIR */ +#define WM8995_AIF1DAC_RATE_MASK 0x07FF /* AIF1DAC_RATE - [10:0] */ +#define WM8995_AIF1DAC_RATE_SHIFT 0 /* AIF1DAC_RATE - [10:0] */ +#define WM8995_AIF1DAC_RATE_WIDTH 11 /* AIF1DAC_RATE - [10:0] */ + +/* + * R774 (0x306) - AIF1DAC Data + */ +#define WM8995_AIF1DACL_DAT_INV 0x0002 /* AIF1DACL_DAT_INV */ +#define WM8995_AIF1DACL_DAT_INV_MASK 0x0002 /* AIF1DACL_DAT_INV */ +#define WM8995_AIF1DACL_DAT_INV_SHIFT 1 /* AIF1DACL_DAT_INV */ +#define WM8995_AIF1DACL_DAT_INV_WIDTH 1 /* AIF1DACL_DAT_INV */ +#define WM8995_AIF1DACR_DAT_INV 0x0001 /* AIF1DACR_DAT_INV */ +#define WM8995_AIF1DACR_DAT_INV_MASK 0x0001 /* AIF1DACR_DAT_INV */ +#define WM8995_AIF1DACR_DAT_INV_SHIFT 0 /* AIF1DACR_DAT_INV */ +#define WM8995_AIF1DACR_DAT_INV_WIDTH 1 /* AIF1DACR_DAT_INV */ + +/* + * R775 (0x307) - AIF1ADC Data + */ +#define WM8995_AIF1ADCL_DAT_INV 0x0002 /* AIF1ADCL_DAT_INV */ +#define WM8995_AIF1ADCL_DAT_INV_MASK 0x0002 /* AIF1ADCL_DAT_INV */ +#define WM8995_AIF1ADCL_DAT_INV_SHIFT 1 /* AIF1ADCL_DAT_INV */ +#define WM8995_AIF1ADCL_DAT_INV_WIDTH 1 /* AIF1ADCL_DAT_INV */ +#define WM8995_AIF1ADCR_DAT_INV 0x0001 /* AIF1ADCR_DAT_INV */ +#define WM8995_AIF1ADCR_DAT_INV_MASK 0x0001 /* AIF1ADCR_DAT_INV */ +#define WM8995_AIF1ADCR_DAT_INV_SHIFT 0 /* AIF1ADCR_DAT_INV */ +#define WM8995_AIF1ADCR_DAT_INV_WIDTH 1 /* AIF1ADCR_DAT_INV */ + +/* + * R784 (0x310) - AIF2 Control (1) + */ +#define WM8995_AIF2ADCL_SRC 0x8000 /* AIF2ADCL_SRC */ +#define WM8995_AIF2ADCL_SRC_MASK 0x8000 /* AIF2ADCL_SRC */ +#define WM8995_AIF2ADCL_SRC_SHIFT 15 /* AIF2ADCL_SRC */ +#define WM8995_AIF2ADCL_SRC_WIDTH 1 /* AIF2ADCL_SRC */ +#define WM8995_AIF2ADCR_SRC 0x4000 /* AIF2ADCR_SRC */ +#define WM8995_AIF2ADCR_SRC_MASK 0x4000 /* AIF2ADCR_SRC */ +#define WM8995_AIF2ADCR_SRC_SHIFT 14 /* AIF2ADCR_SRC */ +#define WM8995_AIF2ADCR_SRC_WIDTH 1 /* AIF2ADCR_SRC */ +#define WM8995_AIF2ADC_TDM 0x2000 /* AIF2ADC_TDM */ +#define WM8995_AIF2ADC_TDM_MASK 0x2000 /* AIF2ADC_TDM */ +#define WM8995_AIF2ADC_TDM_SHIFT 13 /* AIF2ADC_TDM */ +#define WM8995_AIF2ADC_TDM_WIDTH 1 /* AIF2ADC_TDM */ +#define WM8995_AIF2ADC_TDM_CHAN 0x1000 /* AIF2ADC_TDM_CHAN */ +#define WM8995_AIF2ADC_TDM_CHAN_MASK 0x1000 /* AIF2ADC_TDM_CHAN */ +#define WM8995_AIF2ADC_TDM_CHAN_SHIFT 12 /* AIF2ADC_TDM_CHAN */ +#define WM8995_AIF2ADC_TDM_CHAN_WIDTH 1 /* AIF2ADC_TDM_CHAN */ +#define WM8995_AIF2_BCLK_INV 0x0100 /* AIF2_BCLK_INV */ +#define WM8995_AIF2_BCLK_INV_MASK 0x0100 /* AIF2_BCLK_INV */ +#define WM8995_AIF2_BCLK_INV_SHIFT 8 /* AIF2_BCLK_INV */ +#define WM8995_AIF2_BCLK_INV_WIDTH 1 /* AIF2_BCLK_INV */ +#define WM8995_AIF2_LRCLK_INV 0x0080 /* AIF2_LRCLK_INV */ +#define WM8995_AIF2_LRCLK_INV_MASK 0x0080 /* AIF2_LRCLK_INV */ +#define WM8995_AIF2_LRCLK_INV_SHIFT 7 /* AIF2_LRCLK_INV */ +#define WM8995_AIF2_LRCLK_INV_WIDTH 1 /* AIF2_LRCLK_INV */ +#define WM8995_AIF2_WL_MASK 0x0060 /* AIF2_WL - [6:5] */ +#define WM8995_AIF2_WL_SHIFT 5 /* AIF2_WL - [6:5] */ +#define WM8995_AIF2_WL_WIDTH 2 /* AIF2_WL - [6:5] */ +#define WM8995_AIF2_FMT_MASK 0x0018 /* AIF2_FMT - [4:3] */ +#define WM8995_AIF2_FMT_SHIFT 3 /* AIF2_FMT - [4:3] */ +#define WM8995_AIF2_FMT_WIDTH 2 /* AIF2_FMT - [4:3] */ + +/* + * R785 (0x311) - AIF2 Control (2) + */ +#define WM8995_AIF2DACL_SRC 0x8000 /* AIF2DACL_SRC */ +#define WM8995_AIF2DACL_SRC_MASK 0x8000 /* AIF2DACL_SRC */ +#define WM8995_AIF2DACL_SRC_SHIFT 15 /* AIF2DACL_SRC */ +#define WM8995_AIF2DACL_SRC_WIDTH 1 /* AIF2DACL_SRC */ +#define WM8995_AIF2DACR_SRC 0x4000 /* AIF2DACR_SRC */ +#define WM8995_AIF2DACR_SRC_MASK 0x4000 /* AIF2DACR_SRC */ +#define WM8995_AIF2DACR_SRC_SHIFT 14 /* AIF2DACR_SRC */ +#define WM8995_AIF2DACR_SRC_WIDTH 1 /* AIF2DACR_SRC */ +#define WM8995_AIF2DAC_TDM 0x2000 /* AIF2DAC_TDM */ +#define WM8995_AIF2DAC_TDM_MASK 0x2000 /* AIF2DAC_TDM */ +#define WM8995_AIF2DAC_TDM_SHIFT 13 /* AIF2DAC_TDM */ +#define WM8995_AIF2DAC_TDM_WIDTH 1 /* AIF2DAC_TDM */ +#define WM8995_AIF2DAC_TDM_CHAN 0x1000 /* AIF2DAC_TDM_CHAN */ +#define WM8995_AIF2DAC_TDM_CHAN_MASK 0x1000 /* AIF2DAC_TDM_CHAN */ +#define WM8995_AIF2DAC_TDM_CHAN_SHIFT 12 /* AIF2DAC_TDM_CHAN */ +#define WM8995_AIF2DAC_TDM_CHAN_WIDTH 1 /* AIF2DAC_TDM_CHAN */ +#define WM8995_AIF2DAC_BOOST_MASK 0x0C00 /* AIF2DAC_BOOST - [11:10] */ +#define WM8995_AIF2DAC_BOOST_SHIFT 10 /* AIF2DAC_BOOST - [11:10] */ +#define WM8995_AIF2DAC_BOOST_WIDTH 2 /* AIF2DAC_BOOST - [11:10] */ +#define WM8995_AIF2DAC_COMP 0x0010 /* AIF2DAC_COMP */ +#define WM8995_AIF2DAC_COMP_MASK 0x0010 /* AIF2DAC_COMP */ +#define WM8995_AIF2DAC_COMP_SHIFT 4 /* AIF2DAC_COMP */ +#define WM8995_AIF2DAC_COMP_WIDTH 1 /* AIF2DAC_COMP */ +#define WM8995_AIF2DAC_COMPMODE 0x0008 /* AIF2DAC_COMPMODE */ +#define WM8995_AIF2DAC_COMPMODE_MASK 0x0008 /* AIF2DAC_COMPMODE */ +#define WM8995_AIF2DAC_COMPMODE_SHIFT 3 /* AIF2DAC_COMPMODE */ +#define WM8995_AIF2DAC_COMPMODE_WIDTH 1 /* AIF2DAC_COMPMODE */ +#define WM8995_AIF2ADC_COMP 0x0004 /* AIF2ADC_COMP */ +#define WM8995_AIF2ADC_COMP_MASK 0x0004 /* AIF2ADC_COMP */ +#define WM8995_AIF2ADC_COMP_SHIFT 2 /* AIF2ADC_COMP */ +#define WM8995_AIF2ADC_COMP_WIDTH 1 /* AIF2ADC_COMP */ +#define WM8995_AIF2ADC_COMPMODE 0x0002 /* AIF2ADC_COMPMODE */ +#define WM8995_AIF2ADC_COMPMODE_MASK 0x0002 /* AIF2ADC_COMPMODE */ +#define WM8995_AIF2ADC_COMPMODE_SHIFT 1 /* AIF2ADC_COMPMODE */ +#define WM8995_AIF2ADC_COMPMODE_WIDTH 1 /* AIF2ADC_COMPMODE */ +#define WM8995_AIF2_LOOPBACK 0x0001 /* AIF2_LOOPBACK */ +#define WM8995_AIF2_LOOPBACK_MASK 0x0001 /* AIF2_LOOPBACK */ +#define WM8995_AIF2_LOOPBACK_SHIFT 0 /* AIF2_LOOPBACK */ +#define WM8995_AIF2_LOOPBACK_WIDTH 1 /* AIF2_LOOPBACK */ + +/* + * R786 (0x312) - AIF2 Master/Slave + */ +#define WM8995_AIF2_TRI 0x8000 /* AIF2_TRI */ +#define WM8995_AIF2_TRI_MASK 0x8000 /* AIF2_TRI */ +#define WM8995_AIF2_TRI_SHIFT 15 /* AIF2_TRI */ +#define WM8995_AIF2_TRI_WIDTH 1 /* AIF2_TRI */ +#define WM8995_AIF2_MSTR 0x4000 /* AIF2_MSTR */ +#define WM8995_AIF2_MSTR_MASK 0x4000 /* AIF2_MSTR */ +#define WM8995_AIF2_MSTR_SHIFT 14 /* AIF2_MSTR */ +#define WM8995_AIF2_MSTR_WIDTH 1 /* AIF2_MSTR */ +#define WM8995_AIF2_CLK_FRC 0x2000 /* AIF2_CLK_FRC */ +#define WM8995_AIF2_CLK_FRC_MASK 0x2000 /* AIF2_CLK_FRC */ +#define WM8995_AIF2_CLK_FRC_SHIFT 13 /* AIF2_CLK_FRC */ +#define WM8995_AIF2_CLK_FRC_WIDTH 1 /* AIF2_CLK_FRC */ +#define WM8995_AIF2_LRCLK_FRC 0x1000 /* AIF2_LRCLK_FRC */ +#define WM8995_AIF2_LRCLK_FRC_MASK 0x1000 /* AIF2_LRCLK_FRC */ +#define WM8995_AIF2_LRCLK_FRC_SHIFT 12 /* AIF2_LRCLK_FRC */ +#define WM8995_AIF2_LRCLK_FRC_WIDTH 1 /* AIF2_LRCLK_FRC */ + +/* + * R787 (0x313) - AIF2 BCLK + */ +#define WM8995_AIF2_BCLK_DIV_MASK 0x00F0 /* AIF2_BCLK_DIV - [7:4] */ +#define WM8995_AIF2_BCLK_DIV_SHIFT 4 /* AIF2_BCLK_DIV - [7:4] */ +#define WM8995_AIF2_BCLK_DIV_WIDTH 4 /* AIF2_BCLK_DIV - [7:4] */ + +/* + * R788 (0x314) - AIF2ADC LRCLK + */ +#define WM8995_AIF2ADC_LRCLK_DIR 0x0800 /* AIF2ADC_LRCLK_DIR */ +#define WM8995_AIF2ADC_LRCLK_DIR_MASK 0x0800 /* AIF2ADC_LRCLK_DIR */ +#define WM8995_AIF2ADC_LRCLK_DIR_SHIFT 11 /* AIF2ADC_LRCLK_DIR */ +#define WM8995_AIF2ADC_LRCLK_DIR_WIDTH 1 /* AIF2ADC_LRCLK_DIR */ +#define WM8995_AIF2ADC_RATE_MASK 0x07FF /* AIF2ADC_RATE - [10:0] */ +#define WM8995_AIF2ADC_RATE_SHIFT 0 /* AIF2ADC_RATE - [10:0] */ +#define WM8995_AIF2ADC_RATE_WIDTH 11 /* AIF2ADC_RATE - [10:0] */ + +/* + * R789 (0x315) - AIF2DAC LRCLK + */ +#define WM8995_AIF2DAC_LRCLK_DIR 0x0800 /* AIF2DAC_LRCLK_DIR */ +#define WM8995_AIF2DAC_LRCLK_DIR_MASK 0x0800 /* AIF2DAC_LRCLK_DIR */ +#define WM8995_AIF2DAC_LRCLK_DIR_SHIFT 11 /* AIF2DAC_LRCLK_DIR */ +#define WM8995_AIF2DAC_LRCLK_DIR_WIDTH 1 /* AIF2DAC_LRCLK_DIR */ +#define WM8995_AIF2DAC_RATE_MASK 0x07FF /* AIF2DAC_RATE - [10:0] */ +#define WM8995_AIF2DAC_RATE_SHIFT 0 /* AIF2DAC_RATE - [10:0] */ +#define WM8995_AIF2DAC_RATE_WIDTH 11 /* AIF2DAC_RATE - [10:0] */ + +/* + * R790 (0x316) - AIF2DAC Data + */ +#define WM8995_AIF2DACL_DAT_INV 0x0002 /* AIF2DACL_DAT_INV */ +#define WM8995_AIF2DACL_DAT_INV_MASK 0x0002 /* AIF2DACL_DAT_INV */ +#define WM8995_AIF2DACL_DAT_INV_SHIFT 1 /* AIF2DACL_DAT_INV */ +#define WM8995_AIF2DACL_DAT_INV_WIDTH 1 /* AIF2DACL_DAT_INV */ +#define WM8995_AIF2DACR_DAT_INV 0x0001 /* AIF2DACR_DAT_INV */ +#define WM8995_AIF2DACR_DAT_INV_MASK 0x0001 /* AIF2DACR_DAT_INV */ +#define WM8995_AIF2DACR_DAT_INV_SHIFT 0 /* AIF2DACR_DAT_INV */ +#define WM8995_AIF2DACR_DAT_INV_WIDTH 1 /* AIF2DACR_DAT_INV */ + +/* + * R791 (0x317) - AIF2ADC Data + */ +#define WM8995_AIF2ADCL_DAT_INV 0x0002 /* AIF2ADCL_DAT_INV */ +#define WM8995_AIF2ADCL_DAT_INV_MASK 0x0002 /* AIF2ADCL_DAT_INV */ +#define WM8995_AIF2ADCL_DAT_INV_SHIFT 1 /* AIF2ADCL_DAT_INV */ +#define WM8995_AIF2ADCL_DAT_INV_WIDTH 1 /* AIF2ADCL_DAT_INV */ +#define WM8995_AIF2ADCR_DAT_INV 0x0001 /* AIF2ADCR_DAT_INV */ +#define WM8995_AIF2ADCR_DAT_INV_MASK 0x0001 /* AIF2ADCR_DAT_INV */ +#define WM8995_AIF2ADCR_DAT_INV_SHIFT 0 /* AIF2ADCR_DAT_INV */ +#define WM8995_AIF2ADCR_DAT_INV_WIDTH 1 /* AIF2ADCR_DAT_INV */ + +/* + * R1024 (0x400) - AIF1 ADC1 Left Volume + */ +#define WM8995_AIF1ADC1_VU 0x0100 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1_VU_MASK 0x0100 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1_VU_SHIFT 8 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1_VU_WIDTH 1 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1L_VOL_MASK 0x00FF /* AIF1ADC1L_VOL - [7:0] */ +#define WM8995_AIF1ADC1L_VOL_SHIFT 0 /* AIF1ADC1L_VOL - [7:0] */ +#define WM8995_AIF1ADC1L_VOL_WIDTH 8 /* AIF1ADC1L_VOL - [7:0] */ + +/* + * R1025 (0x401) - AIF1 ADC1 Right Volume + */ +#define WM8995_AIF1ADC1_VU 0x0100 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1_VU_MASK 0x0100 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1_VU_SHIFT 8 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1_VU_WIDTH 1 /* AIF1ADC1_VU */ +#define WM8995_AIF1ADC1R_VOL_MASK 0x00FF /* AIF1ADC1R_VOL - [7:0] */ +#define WM8995_AIF1ADC1R_VOL_SHIFT 0 /* AIF1ADC1R_VOL - [7:0] */ +#define WM8995_AIF1ADC1R_VOL_WIDTH 8 /* AIF1ADC1R_VOL - [7:0] */ + +/* + * R1026 (0x402) - AIF1 DAC1 Left Volume + */ +#define WM8995_AIF1DAC1_VU 0x0100 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1_VU_MASK 0x0100 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1_VU_SHIFT 8 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1_VU_WIDTH 1 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1L_VOL_MASK 0x00FF /* AIF1DAC1L_VOL - [7:0] */ +#define WM8995_AIF1DAC1L_VOL_SHIFT 0 /* AIF1DAC1L_VOL - [7:0] */ +#define WM8995_AIF1DAC1L_VOL_WIDTH 8 /* AIF1DAC1L_VOL - [7:0] */ + +/* + * R1027 (0x403) - AIF1 DAC1 Right Volume + */ +#define WM8995_AIF1DAC1_VU 0x0100 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1_VU_MASK 0x0100 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1_VU_SHIFT 8 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1_VU_WIDTH 1 /* AIF1DAC1_VU */ +#define WM8995_AIF1DAC1R_VOL_MASK 0x00FF /* AIF1DAC1R_VOL - [7:0] */ +#define WM8995_AIF1DAC1R_VOL_SHIFT 0 /* AIF1DAC1R_VOL - [7:0] */ +#define WM8995_AIF1DAC1R_VOL_WIDTH 8 /* AIF1DAC1R_VOL - [7:0] */ + +/* + * R1028 (0x404) - AIF1 ADC2 Left Volume + */ +#define WM8995_AIF1ADC2_VU 0x0100 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2_VU_MASK 0x0100 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2_VU_SHIFT 8 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2_VU_WIDTH 1 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2L_VOL_MASK 0x00FF /* AIF1ADC2L_VOL - [7:0] */ +#define WM8995_AIF1ADC2L_VOL_SHIFT 0 /* AIF1ADC2L_VOL - [7:0] */ +#define WM8995_AIF1ADC2L_VOL_WIDTH 8 /* AIF1ADC2L_VOL - [7:0] */ + +/* + * R1029 (0x405) - AIF1 ADC2 Right Volume + */ +#define WM8995_AIF1ADC2_VU 0x0100 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2_VU_MASK 0x0100 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2_VU_SHIFT 8 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2_VU_WIDTH 1 /* AIF1ADC2_VU */ +#define WM8995_AIF1ADC2R_VOL_MASK 0x00FF /* AIF1ADC2R_VOL - [7:0] */ +#define WM8995_AIF1ADC2R_VOL_SHIFT 0 /* AIF1ADC2R_VOL - [7:0] */ +#define WM8995_AIF1ADC2R_VOL_WIDTH 8 /* AIF1ADC2R_VOL - [7:0] */ + +/* + * R1030 (0x406) - AIF1 DAC2 Left Volume + */ +#define WM8995_AIF1DAC2_VU 0x0100 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2_VU_MASK 0x0100 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2_VU_SHIFT 8 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2_VU_WIDTH 1 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2L_VOL_MASK 0x00FF /* AIF1DAC2L_VOL - [7:0] */ +#define WM8995_AIF1DAC2L_VOL_SHIFT 0 /* AIF1DAC2L_VOL - [7:0] */ +#define WM8995_AIF1DAC2L_VOL_WIDTH 8 /* AIF1DAC2L_VOL - [7:0] */ + +/* + * R1031 (0x407) - AIF1 DAC2 Right Volume + */ +#define WM8995_AIF1DAC2_VU 0x0100 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2_VU_MASK 0x0100 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2_VU_SHIFT 8 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2_VU_WIDTH 1 /* AIF1DAC2_VU */ +#define WM8995_AIF1DAC2R_VOL_MASK 0x00FF /* AIF1DAC2R_VOL - [7:0] */ +#define WM8995_AIF1DAC2R_VOL_SHIFT 0 /* AIF1DAC2R_VOL - [7:0] */ +#define WM8995_AIF1DAC2R_VOL_WIDTH 8 /* AIF1DAC2R_VOL - [7:0] */ + +/* + * R1040 (0x410) - AIF1 ADC1 Filters + */ +#define WM8995_AIF1ADC_4FS 0x8000 /* AIF1ADC_4FS */ +#define WM8995_AIF1ADC_4FS_MASK 0x8000 /* AIF1ADC_4FS */ +#define WM8995_AIF1ADC_4FS_SHIFT 15 /* AIF1ADC_4FS */ +#define WM8995_AIF1ADC_4FS_WIDTH 1 /* AIF1ADC_4FS */ +#define WM8995_AIF1ADC1L_HPF 0x1000 /* AIF1ADC1L_HPF */ +#define WM8995_AIF1ADC1L_HPF_MASK 0x1000 /* AIF1ADC1L_HPF */ +#define WM8995_AIF1ADC1L_HPF_SHIFT 12 /* AIF1ADC1L_HPF */ +#define WM8995_AIF1ADC1L_HPF_WIDTH 1 /* AIF1ADC1L_HPF */ +#define WM8995_AIF1ADC1R_HPF 0x0800 /* AIF1ADC1R_HPF */ +#define WM8995_AIF1ADC1R_HPF_MASK 0x0800 /* AIF1ADC1R_HPF */ +#define WM8995_AIF1ADC1R_HPF_SHIFT 11 /* AIF1ADC1R_HPF */ +#define WM8995_AIF1ADC1R_HPF_WIDTH 1 /* AIF1ADC1R_HPF */ +#define WM8995_AIF1ADC1_HPF_MODE 0x0008 /* AIF1ADC1_HPF_MODE */ +#define WM8995_AIF1ADC1_HPF_MODE_MASK 0x0008 /* AIF1ADC1_HPF_MODE */ +#define WM8995_AIF1ADC1_HPF_MODE_SHIFT 3 /* AIF1ADC1_HPF_MODE */ +#define WM8995_AIF1ADC1_HPF_MODE_WIDTH 1 /* AIF1ADC1_HPF_MODE */ +#define WM8995_AIF1ADC1_HPF_CUT_MASK 0x0007 /* AIF1ADC1_HPF_CUT - [2:0] */ +#define WM8995_AIF1ADC1_HPF_CUT_SHIFT 0 /* AIF1ADC1_HPF_CUT - [2:0] */ +#define WM8995_AIF1ADC1_HPF_CUT_WIDTH 3 /* AIF1ADC1_HPF_CUT - [2:0] */ + +/* + * R1041 (0x411) - AIF1 ADC2 Filters + */ +#define WM8995_AIF1ADC2L_HPF 0x1000 /* AIF1ADC2L_HPF */ +#define WM8995_AIF1ADC2L_HPF_MASK 0x1000 /* AIF1ADC2L_HPF */ +#define WM8995_AIF1ADC2L_HPF_SHIFT 12 /* AIF1ADC2L_HPF */ +#define WM8995_AIF1ADC2L_HPF_WIDTH 1 /* AIF1ADC2L_HPF */ +#define WM8995_AIF1ADC2R_HPF 0x0800 /* AIF1ADC2R_HPF */ +#define WM8995_AIF1ADC2R_HPF_MASK 0x0800 /* AIF1ADC2R_HPF */ +#define WM8995_AIF1ADC2R_HPF_SHIFT 11 /* AIF1ADC2R_HPF */ +#define WM8995_AIF1ADC2R_HPF_WIDTH 1 /* AIF1ADC2R_HPF */ +#define WM8995_AIF1ADC2_HPF_MODE 0x0008 /* AIF1ADC2_HPF_MODE */ +#define WM8995_AIF1ADC2_HPF_MODE_MASK 0x0008 /* AIF1ADC2_HPF_MODE */ +#define WM8995_AIF1ADC2_HPF_MODE_SHIFT 3 /* AIF1ADC2_HPF_MODE */ +#define WM8995_AIF1ADC2_HPF_MODE_WIDTH 1 /* AIF1ADC2_HPF_MODE */ +#define WM8995_AIF1ADC2_HPF_CUT_MASK 0x0007 /* AIF1ADC2_HPF_CUT - [2:0] */ +#define WM8995_AIF1ADC2_HPF_CUT_SHIFT 0 /* AIF1ADC2_HPF_CUT - [2:0] */ +#define WM8995_AIF1ADC2_HPF_CUT_WIDTH 3 /* AIF1ADC2_HPF_CUT - [2:0] */ + +/* + * R1056 (0x420) - AIF1 DAC1 Filters (1) + */ +#define WM8995_AIF1DAC1_MUTE 0x0200 /* AIF1DAC1_MUTE */ +#define WM8995_AIF1DAC1_MUTE_MASK 0x0200 /* AIF1DAC1_MUTE */ +#define WM8995_AIF1DAC1_MUTE_SHIFT 9 /* AIF1DAC1_MUTE */ +#define WM8995_AIF1DAC1_MUTE_WIDTH 1 /* AIF1DAC1_MUTE */ +#define WM8995_AIF1DAC1_MONO 0x0080 /* AIF1DAC1_MONO */ +#define WM8995_AIF1DAC1_MONO_MASK 0x0080 /* AIF1DAC1_MONO */ +#define WM8995_AIF1DAC1_MONO_SHIFT 7 /* AIF1DAC1_MONO */ +#define WM8995_AIF1DAC1_MONO_WIDTH 1 /* AIF1DAC1_MONO */ +#define WM8995_AIF1DAC1_MUTERATE 0x0020 /* AIF1DAC1_MUTERATE */ +#define WM8995_AIF1DAC1_MUTERATE_MASK 0x0020 /* AIF1DAC1_MUTERATE */ +#define WM8995_AIF1DAC1_MUTERATE_SHIFT 5 /* AIF1DAC1_MUTERATE */ +#define WM8995_AIF1DAC1_MUTERATE_WIDTH 1 /* AIF1DAC1_MUTERATE */ +#define WM8995_AIF1DAC1_UNMUTE_RAMP 0x0010 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8995_AIF1DAC1_UNMUTE_RAMP_MASK 0x0010 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8995_AIF1DAC1_UNMUTE_RAMP_SHIFT 4 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8995_AIF1DAC1_UNMUTE_RAMP_WIDTH 1 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8995_AIF1DAC1_DEEMP_MASK 0x0006 /* AIF1DAC1_DEEMP - [2:1] */ +#define WM8995_AIF1DAC1_DEEMP_SHIFT 1 /* AIF1DAC1_DEEMP - [2:1] */ +#define WM8995_AIF1DAC1_DEEMP_WIDTH 2 /* AIF1DAC1_DEEMP - [2:1] */ + +/* + * R1057 (0x421) - AIF1 DAC1 Filters (2) + */ +#define WM8995_AIF1DAC1_3D_GAIN_MASK 0x3E00 /* AIF1DAC1_3D_GAIN - [13:9] */ +#define WM8995_AIF1DAC1_3D_GAIN_SHIFT 9 /* AIF1DAC1_3D_GAIN - [13:9] */ +#define WM8995_AIF1DAC1_3D_GAIN_WIDTH 5 /* AIF1DAC1_3D_GAIN - [13:9] */ +#define WM8995_AIF1DAC1_3D_ENA 0x0100 /* AIF1DAC1_3D_ENA */ +#define WM8995_AIF1DAC1_3D_ENA_MASK 0x0100 /* AIF1DAC1_3D_ENA */ +#define WM8995_AIF1DAC1_3D_ENA_SHIFT 8 /* AIF1DAC1_3D_ENA */ +#define WM8995_AIF1DAC1_3D_ENA_WIDTH 1 /* AIF1DAC1_3D_ENA */ + +/* + * R1058 (0x422) - AIF1 DAC2 Filters (1) + */ +#define WM8995_AIF1DAC2_MUTE 0x0200 /* AIF1DAC2_MUTE */ +#define WM8995_AIF1DAC2_MUTE_MASK 0x0200 /* AIF1DAC2_MUTE */ +#define WM8995_AIF1DAC2_MUTE_SHIFT 9 /* AIF1DAC2_MUTE */ +#define WM8995_AIF1DAC2_MUTE_WIDTH 1 /* AIF1DAC2_MUTE */ +#define WM8995_AIF1DAC2_MONO 0x0080 /* AIF1DAC2_MONO */ +#define WM8995_AIF1DAC2_MONO_MASK 0x0080 /* AIF1DAC2_MONO */ +#define WM8995_AIF1DAC2_MONO_SHIFT 7 /* AIF1DAC2_MONO */ +#define WM8995_AIF1DAC2_MONO_WIDTH 1 /* AIF1DAC2_MONO */ +#define WM8995_AIF1DAC2_MUTERATE 0x0020 /* AIF1DAC2_MUTERATE */ +#define WM8995_AIF1DAC2_MUTERATE_MASK 0x0020 /* AIF1DAC2_MUTERATE */ +#define WM8995_AIF1DAC2_MUTERATE_SHIFT 5 /* AIF1DAC2_MUTERATE */ +#define WM8995_AIF1DAC2_MUTERATE_WIDTH 1 /* AIF1DAC2_MUTERATE */ +#define WM8995_AIF1DAC2_UNMUTE_RAMP 0x0010 /* AIF1DAC2_UNMUTE_RAMP */ +#define WM8995_AIF1DAC2_UNMUTE_RAMP_MASK 0x0010 /* AIF1DAC2_UNMUTE_RAMP */ +#define WM8995_AIF1DAC2_UNMUTE_RAMP_SHIFT 4 /* AIF1DAC2_UNMUTE_RAMP */ +#define WM8995_AIF1DAC2_UNMUTE_RAMP_WIDTH 1 /* AIF1DAC2_UNMUTE_RAMP */ +#define WM8995_AIF1DAC2_DEEMP_MASK 0x0006 /* AIF1DAC2_DEEMP - [2:1] */ +#define WM8995_AIF1DAC2_DEEMP_SHIFT 1 /* AIF1DAC2_DEEMP - [2:1] */ +#define WM8995_AIF1DAC2_DEEMP_WIDTH 2 /* AIF1DAC2_DEEMP - [2:1] */ + +/* + * R1059 (0x423) - AIF1 DAC2 Filters (2) + */ +#define WM8995_AIF1DAC2_3D_GAIN_MASK 0x3E00 /* AIF1DAC2_3D_GAIN - [13:9] */ +#define WM8995_AIF1DAC2_3D_GAIN_SHIFT 9 /* AIF1DAC2_3D_GAIN - [13:9] */ +#define WM8995_AIF1DAC2_3D_GAIN_WIDTH 5 /* AIF1DAC2_3D_GAIN - [13:9] */ +#define WM8995_AIF1DAC2_3D_ENA 0x0100 /* AIF1DAC2_3D_ENA */ +#define WM8995_AIF1DAC2_3D_ENA_MASK 0x0100 /* AIF1DAC2_3D_ENA */ +#define WM8995_AIF1DAC2_3D_ENA_SHIFT 8 /* AIF1DAC2_3D_ENA */ +#define WM8995_AIF1DAC2_3D_ENA_WIDTH 1 /* AIF1DAC2_3D_ENA */ + +/* + * R1088 (0x440) - AIF1 DRC1 (1) + */ +#define WM8995_AIF1DRC1_SIG_DET_RMS_MASK 0xF800 /* AIF1DRC1_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF1DRC1_SIG_DET_RMS_SHIFT 11 /* AIF1DRC1_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF1DRC1_SIG_DET_RMS_WIDTH 5 /* AIF1DRC1_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF1DRC1_SIG_DET_PK_MASK 0x0600 /* AIF1DRC1_SIG_DET_PK - [10:9] */ +#define WM8995_AIF1DRC1_SIG_DET_PK_SHIFT 9 /* AIF1DRC1_SIG_DET_PK - [10:9] */ +#define WM8995_AIF1DRC1_SIG_DET_PK_WIDTH 2 /* AIF1DRC1_SIG_DET_PK - [10:9] */ +#define WM8995_AIF1DRC1_NG_ENA 0x0100 /* AIF1DRC1_NG_ENA */ +#define WM8995_AIF1DRC1_NG_ENA_MASK 0x0100 /* AIF1DRC1_NG_ENA */ +#define WM8995_AIF1DRC1_NG_ENA_SHIFT 8 /* AIF1DRC1_NG_ENA */ +#define WM8995_AIF1DRC1_NG_ENA_WIDTH 1 /* AIF1DRC1_NG_ENA */ +#define WM8995_AIF1DRC1_SIG_DET_MODE 0x0080 /* AIF1DRC1_SIG_DET_MODE */ +#define WM8995_AIF1DRC1_SIG_DET_MODE_MASK 0x0080 /* AIF1DRC1_SIG_DET_MODE */ +#define WM8995_AIF1DRC1_SIG_DET_MODE_SHIFT 7 /* AIF1DRC1_SIG_DET_MODE */ +#define WM8995_AIF1DRC1_SIG_DET_MODE_WIDTH 1 /* AIF1DRC1_SIG_DET_MODE */ +#define WM8995_AIF1DRC1_SIG_DET 0x0040 /* AIF1DRC1_SIG_DET */ +#define WM8995_AIF1DRC1_SIG_DET_MASK 0x0040 /* AIF1DRC1_SIG_DET */ +#define WM8995_AIF1DRC1_SIG_DET_SHIFT 6 /* AIF1DRC1_SIG_DET */ +#define WM8995_AIF1DRC1_SIG_DET_WIDTH 1 /* AIF1DRC1_SIG_DET */ +#define WM8995_AIF1DRC1_KNEE2_OP_ENA 0x0020 /* AIF1DRC1_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC1_KNEE2_OP_ENA_MASK 0x0020 /* AIF1DRC1_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC1_KNEE2_OP_ENA_SHIFT 5 /* AIF1DRC1_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC1_KNEE2_OP_ENA_WIDTH 1 /* AIF1DRC1_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC1_QR 0x0010 /* AIF1DRC1_QR */ +#define WM8995_AIF1DRC1_QR_MASK 0x0010 /* AIF1DRC1_QR */ +#define WM8995_AIF1DRC1_QR_SHIFT 4 /* AIF1DRC1_QR */ +#define WM8995_AIF1DRC1_QR_WIDTH 1 /* AIF1DRC1_QR */ +#define WM8995_AIF1DRC1_ANTICLIP 0x0008 /* AIF1DRC1_ANTICLIP */ +#define WM8995_AIF1DRC1_ANTICLIP_MASK 0x0008 /* AIF1DRC1_ANTICLIP */ +#define WM8995_AIF1DRC1_ANTICLIP_SHIFT 3 /* AIF1DRC1_ANTICLIP */ +#define WM8995_AIF1DRC1_ANTICLIP_WIDTH 1 /* AIF1DRC1_ANTICLIP */ +#define WM8995_AIF1DAC1_DRC_ENA 0x0004 /* AIF1DAC1_DRC_ENA */ +#define WM8995_AIF1DAC1_DRC_ENA_MASK 0x0004 /* AIF1DAC1_DRC_ENA */ +#define WM8995_AIF1DAC1_DRC_ENA_SHIFT 2 /* AIF1DAC1_DRC_ENA */ +#define WM8995_AIF1DAC1_DRC_ENA_WIDTH 1 /* AIF1DAC1_DRC_ENA */ +#define WM8995_AIF1ADC1L_DRC_ENA 0x0002 /* AIF1ADC1L_DRC_ENA */ +#define WM8995_AIF1ADC1L_DRC_ENA_MASK 0x0002 /* AIF1ADC1L_DRC_ENA */ +#define WM8995_AIF1ADC1L_DRC_ENA_SHIFT 1 /* AIF1ADC1L_DRC_ENA */ +#define WM8995_AIF1ADC1L_DRC_ENA_WIDTH 1 /* AIF1ADC1L_DRC_ENA */ +#define WM8995_AIF1ADC1R_DRC_ENA 0x0001 /* AIF1ADC1R_DRC_ENA */ +#define WM8995_AIF1ADC1R_DRC_ENA_MASK 0x0001 /* AIF1ADC1R_DRC_ENA */ +#define WM8995_AIF1ADC1R_DRC_ENA_SHIFT 0 /* AIF1ADC1R_DRC_ENA */ +#define WM8995_AIF1ADC1R_DRC_ENA_WIDTH 1 /* AIF1ADC1R_DRC_ENA */ + +/* + * R1089 (0x441) - AIF1 DRC1 (2) + */ +#define WM8995_AIF1DRC1_ATK_MASK 0x1E00 /* AIF1DRC1_ATK - [12:9] */ +#define WM8995_AIF1DRC1_ATK_SHIFT 9 /* AIF1DRC1_ATK - [12:9] */ +#define WM8995_AIF1DRC1_ATK_WIDTH 4 /* AIF1DRC1_ATK - [12:9] */ +#define WM8995_AIF1DRC1_DCY_MASK 0x01E0 /* AIF1DRC1_DCY - [8:5] */ +#define WM8995_AIF1DRC1_DCY_SHIFT 5 /* AIF1DRC1_DCY - [8:5] */ +#define WM8995_AIF1DRC1_DCY_WIDTH 4 /* AIF1DRC1_DCY - [8:5] */ +#define WM8995_AIF1DRC1_MINGAIN_MASK 0x001C /* AIF1DRC1_MINGAIN - [4:2] */ +#define WM8995_AIF1DRC1_MINGAIN_SHIFT 2 /* AIF1DRC1_MINGAIN - [4:2] */ +#define WM8995_AIF1DRC1_MINGAIN_WIDTH 3 /* AIF1DRC1_MINGAIN - [4:2] */ +#define WM8995_AIF1DRC1_MAXGAIN_MASK 0x0003 /* AIF1DRC1_MAXGAIN - [1:0] */ +#define WM8995_AIF1DRC1_MAXGAIN_SHIFT 0 /* AIF1DRC1_MAXGAIN - [1:0] */ +#define WM8995_AIF1DRC1_MAXGAIN_WIDTH 2 /* AIF1DRC1_MAXGAIN - [1:0] */ + +/* + * R1090 (0x442) - AIF1 DRC1 (3) + */ +#define WM8995_AIF1DRC1_NG_MINGAIN_MASK 0xF000 /* AIF1DRC1_NG_MINGAIN - [15:12] */ +#define WM8995_AIF1DRC1_NG_MINGAIN_SHIFT 12 /* AIF1DRC1_NG_MINGAIN - [15:12] */ +#define WM8995_AIF1DRC1_NG_MINGAIN_WIDTH 4 /* AIF1DRC1_NG_MINGAIN - [15:12] */ +#define WM8995_AIF1DRC1_NG_EXP_MASK 0x0C00 /* AIF1DRC1_NG_EXP - [11:10] */ +#define WM8995_AIF1DRC1_NG_EXP_SHIFT 10 /* AIF1DRC1_NG_EXP - [11:10] */ +#define WM8995_AIF1DRC1_NG_EXP_WIDTH 2 /* AIF1DRC1_NG_EXP - [11:10] */ +#define WM8995_AIF1DRC1_QR_THR_MASK 0x0300 /* AIF1DRC1_QR_THR - [9:8] */ +#define WM8995_AIF1DRC1_QR_THR_SHIFT 8 /* AIF1DRC1_QR_THR - [9:8] */ +#define WM8995_AIF1DRC1_QR_THR_WIDTH 2 /* AIF1DRC1_QR_THR - [9:8] */ +#define WM8995_AIF1DRC1_QR_DCY_MASK 0x00C0 /* AIF1DRC1_QR_DCY - [7:6] */ +#define WM8995_AIF1DRC1_QR_DCY_SHIFT 6 /* AIF1DRC1_QR_DCY - [7:6] */ +#define WM8995_AIF1DRC1_QR_DCY_WIDTH 2 /* AIF1DRC1_QR_DCY - [7:6] */ +#define WM8995_AIF1DRC1_HI_COMP_MASK 0x0038 /* AIF1DRC1_HI_COMP - [5:3] */ +#define WM8995_AIF1DRC1_HI_COMP_SHIFT 3 /* AIF1DRC1_HI_COMP - [5:3] */ +#define WM8995_AIF1DRC1_HI_COMP_WIDTH 3 /* AIF1DRC1_HI_COMP - [5:3] */ +#define WM8995_AIF1DRC1_LO_COMP_MASK 0x0007 /* AIF1DRC1_LO_COMP - [2:0] */ +#define WM8995_AIF1DRC1_LO_COMP_SHIFT 0 /* AIF1DRC1_LO_COMP - [2:0] */ +#define WM8995_AIF1DRC1_LO_COMP_WIDTH 3 /* AIF1DRC1_LO_COMP - [2:0] */ + +/* + * R1091 (0x443) - AIF1 DRC1 (4) + */ +#define WM8995_AIF1DRC1_KNEE_IP_MASK 0x07E0 /* AIF1DRC1_KNEE_IP - [10:5] */ +#define WM8995_AIF1DRC1_KNEE_IP_SHIFT 5 /* AIF1DRC1_KNEE_IP - [10:5] */ +#define WM8995_AIF1DRC1_KNEE_IP_WIDTH 6 /* AIF1DRC1_KNEE_IP - [10:5] */ +#define WM8995_AIF1DRC1_KNEE_OP_MASK 0x001F /* AIF1DRC1_KNEE_OP - [4:0] */ +#define WM8995_AIF1DRC1_KNEE_OP_SHIFT 0 /* AIF1DRC1_KNEE_OP - [4:0] */ +#define WM8995_AIF1DRC1_KNEE_OP_WIDTH 5 /* AIF1DRC1_KNEE_OP - [4:0] */ + +/* + * R1092 (0x444) - AIF1 DRC1 (5) + */ +#define WM8995_AIF1DRC1_KNEE2_IP_MASK 0x03E0 /* AIF1DRC1_KNEE2_IP - [9:5] */ +#define WM8995_AIF1DRC1_KNEE2_IP_SHIFT 5 /* AIF1DRC1_KNEE2_IP - [9:5] */ +#define WM8995_AIF1DRC1_KNEE2_IP_WIDTH 5 /* AIF1DRC1_KNEE2_IP - [9:5] */ +#define WM8995_AIF1DRC1_KNEE2_OP_MASK 0x001F /* AIF1DRC1_KNEE2_OP - [4:0] */ +#define WM8995_AIF1DRC1_KNEE2_OP_SHIFT 0 /* AIF1DRC1_KNEE2_OP - [4:0] */ +#define WM8995_AIF1DRC1_KNEE2_OP_WIDTH 5 /* AIF1DRC1_KNEE2_OP - [4:0] */ + +/* + * R1104 (0x450) - AIF1 DRC2 (1) + */ +#define WM8995_AIF1DRC2_SIG_DET_RMS_MASK 0xF800 /* AIF1DRC2_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF1DRC2_SIG_DET_RMS_SHIFT 11 /* AIF1DRC2_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF1DRC2_SIG_DET_RMS_WIDTH 5 /* AIF1DRC2_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF1DRC2_SIG_DET_PK_MASK 0x0600 /* AIF1DRC2_SIG_DET_PK - [10:9] */ +#define WM8995_AIF1DRC2_SIG_DET_PK_SHIFT 9 /* AIF1DRC2_SIG_DET_PK - [10:9] */ +#define WM8995_AIF1DRC2_SIG_DET_PK_WIDTH 2 /* AIF1DRC2_SIG_DET_PK - [10:9] */ +#define WM8995_AIF1DRC2_NG_ENA 0x0100 /* AIF1DRC2_NG_ENA */ +#define WM8995_AIF1DRC2_NG_ENA_MASK 0x0100 /* AIF1DRC2_NG_ENA */ +#define WM8995_AIF1DRC2_NG_ENA_SHIFT 8 /* AIF1DRC2_NG_ENA */ +#define WM8995_AIF1DRC2_NG_ENA_WIDTH 1 /* AIF1DRC2_NG_ENA */ +#define WM8995_AIF1DRC2_SIG_DET_MODE 0x0080 /* AIF1DRC2_SIG_DET_MODE */ +#define WM8995_AIF1DRC2_SIG_DET_MODE_MASK 0x0080 /* AIF1DRC2_SIG_DET_MODE */ +#define WM8995_AIF1DRC2_SIG_DET_MODE_SHIFT 7 /* AIF1DRC2_SIG_DET_MODE */ +#define WM8995_AIF1DRC2_SIG_DET_MODE_WIDTH 1 /* AIF1DRC2_SIG_DET_MODE */ +#define WM8995_AIF1DRC2_SIG_DET 0x0040 /* AIF1DRC2_SIG_DET */ +#define WM8995_AIF1DRC2_SIG_DET_MASK 0x0040 /* AIF1DRC2_SIG_DET */ +#define WM8995_AIF1DRC2_SIG_DET_SHIFT 6 /* AIF1DRC2_SIG_DET */ +#define WM8995_AIF1DRC2_SIG_DET_WIDTH 1 /* AIF1DRC2_SIG_DET */ +#define WM8995_AIF1DRC2_KNEE2_OP_ENA 0x0020 /* AIF1DRC2_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC2_KNEE2_OP_ENA_MASK 0x0020 /* AIF1DRC2_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC2_KNEE2_OP_ENA_SHIFT 5 /* AIF1DRC2_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC2_KNEE2_OP_ENA_WIDTH 1 /* AIF1DRC2_KNEE2_OP_ENA */ +#define WM8995_AIF1DRC2_QR 0x0010 /* AIF1DRC2_QR */ +#define WM8995_AIF1DRC2_QR_MASK 0x0010 /* AIF1DRC2_QR */ +#define WM8995_AIF1DRC2_QR_SHIFT 4 /* AIF1DRC2_QR */ +#define WM8995_AIF1DRC2_QR_WIDTH 1 /* AIF1DRC2_QR */ +#define WM8995_AIF1DRC2_ANTICLIP 0x0008 /* AIF1DRC2_ANTICLIP */ +#define WM8995_AIF1DRC2_ANTICLIP_MASK 0x0008 /* AIF1DRC2_ANTICLIP */ +#define WM8995_AIF1DRC2_ANTICLIP_SHIFT 3 /* AIF1DRC2_ANTICLIP */ +#define WM8995_AIF1DRC2_ANTICLIP_WIDTH 1 /* AIF1DRC2_ANTICLIP */ +#define WM8995_AIF1DAC2_DRC_ENA 0x0004 /* AIF1DAC2_DRC_ENA */ +#define WM8995_AIF1DAC2_DRC_ENA_MASK 0x0004 /* AIF1DAC2_DRC_ENA */ +#define WM8995_AIF1DAC2_DRC_ENA_SHIFT 2 /* AIF1DAC2_DRC_ENA */ +#define WM8995_AIF1DAC2_DRC_ENA_WIDTH 1 /* AIF1DAC2_DRC_ENA */ +#define WM8995_AIF1ADC2L_DRC_ENA 0x0002 /* AIF1ADC2L_DRC_ENA */ +#define WM8995_AIF1ADC2L_DRC_ENA_MASK 0x0002 /* AIF1ADC2L_DRC_ENA */ +#define WM8995_AIF1ADC2L_DRC_ENA_SHIFT 1 /* AIF1ADC2L_DRC_ENA */ +#define WM8995_AIF1ADC2L_DRC_ENA_WIDTH 1 /* AIF1ADC2L_DRC_ENA */ +#define WM8995_AIF1ADC2R_DRC_ENA 0x0001 /* AIF1ADC2R_DRC_ENA */ +#define WM8995_AIF1ADC2R_DRC_ENA_MASK 0x0001 /* AIF1ADC2R_DRC_ENA */ +#define WM8995_AIF1ADC2R_DRC_ENA_SHIFT 0 /* AIF1ADC2R_DRC_ENA */ +#define WM8995_AIF1ADC2R_DRC_ENA_WIDTH 1 /* AIF1ADC2R_DRC_ENA */ + +/* + * R1105 (0x451) - AIF1 DRC2 (2) + */ +#define WM8995_AIF1DRC2_ATK_MASK 0x1E00 /* AIF1DRC2_ATK - [12:9] */ +#define WM8995_AIF1DRC2_ATK_SHIFT 9 /* AIF1DRC2_ATK - [12:9] */ +#define WM8995_AIF1DRC2_ATK_WIDTH 4 /* AIF1DRC2_ATK - [12:9] */ +#define WM8995_AIF1DRC2_DCY_MASK 0x01E0 /* AIF1DRC2_DCY - [8:5] */ +#define WM8995_AIF1DRC2_DCY_SHIFT 5 /* AIF1DRC2_DCY - [8:5] */ +#define WM8995_AIF1DRC2_DCY_WIDTH 4 /* AIF1DRC2_DCY - [8:5] */ +#define WM8995_AIF1DRC2_MINGAIN_MASK 0x001C /* AIF1DRC2_MINGAIN - [4:2] */ +#define WM8995_AIF1DRC2_MINGAIN_SHIFT 2 /* AIF1DRC2_MINGAIN - [4:2] */ +#define WM8995_AIF1DRC2_MINGAIN_WIDTH 3 /* AIF1DRC2_MINGAIN - [4:2] */ +#define WM8995_AIF1DRC2_MAXGAIN_MASK 0x0003 /* AIF1DRC2_MAXGAIN - [1:0] */ +#define WM8995_AIF1DRC2_MAXGAIN_SHIFT 0 /* AIF1DRC2_MAXGAIN - [1:0] */ +#define WM8995_AIF1DRC2_MAXGAIN_WIDTH 2 /* AIF1DRC2_MAXGAIN - [1:0] */ + +/* + * R1106 (0x452) - AIF1 DRC2 (3) + */ +#define WM8995_AIF1DRC2_NG_MINGAIN_MASK 0xF000 /* AIF1DRC2_NG_MINGAIN - [15:12] */ +#define WM8995_AIF1DRC2_NG_MINGAIN_SHIFT 12 /* AIF1DRC2_NG_MINGAIN - [15:12] */ +#define WM8995_AIF1DRC2_NG_MINGAIN_WIDTH 4 /* AIF1DRC2_NG_MINGAIN - [15:12] */ +#define WM8995_AIF1DRC2_NG_EXP_MASK 0x0C00 /* AIF1DRC2_NG_EXP - [11:10] */ +#define WM8995_AIF1DRC2_NG_EXP_SHIFT 10 /* AIF1DRC2_NG_EXP - [11:10] */ +#define WM8995_AIF1DRC2_NG_EXP_WIDTH 2 /* AIF1DRC2_NG_EXP - [11:10] */ +#define WM8995_AIF1DRC2_QR_THR_MASK 0x0300 /* AIF1DRC2_QR_THR - [9:8] */ +#define WM8995_AIF1DRC2_QR_THR_SHIFT 8 /* AIF1DRC2_QR_THR - [9:8] */ +#define WM8995_AIF1DRC2_QR_THR_WIDTH 2 /* AIF1DRC2_QR_THR - [9:8] */ +#define WM8995_AIF1DRC2_QR_DCY_MASK 0x00C0 /* AIF1DRC2_QR_DCY - [7:6] */ +#define WM8995_AIF1DRC2_QR_DCY_SHIFT 6 /* AIF1DRC2_QR_DCY - [7:6] */ +#define WM8995_AIF1DRC2_QR_DCY_WIDTH 2 /* AIF1DRC2_QR_DCY - [7:6] */ +#define WM8995_AIF1DRC2_HI_COMP_MASK 0x0038 /* AIF1DRC2_HI_COMP - [5:3] */ +#define WM8995_AIF1DRC2_HI_COMP_SHIFT 3 /* AIF1DRC2_HI_COMP - [5:3] */ +#define WM8995_AIF1DRC2_HI_COMP_WIDTH 3 /* AIF1DRC2_HI_COMP - [5:3] */ +#define WM8995_AIF1DRC2_LO_COMP_MASK 0x0007 /* AIF1DRC2_LO_COMP - [2:0] */ +#define WM8995_AIF1DRC2_LO_COMP_SHIFT 0 /* AIF1DRC2_LO_COMP - [2:0] */ +#define WM8995_AIF1DRC2_LO_COMP_WIDTH 3 /* AIF1DRC2_LO_COMP - [2:0] */ + +/* + * R1107 (0x453) - AIF1 DRC2 (4) + */ +#define WM8995_AIF1DRC2_KNEE_IP_MASK 0x07E0 /* AIF1DRC2_KNEE_IP - [10:5] */ +#define WM8995_AIF1DRC2_KNEE_IP_SHIFT 5 /* AIF1DRC2_KNEE_IP - [10:5] */ +#define WM8995_AIF1DRC2_KNEE_IP_WIDTH 6 /* AIF1DRC2_KNEE_IP - [10:5] */ +#define WM8995_AIF1DRC2_KNEE_OP_MASK 0x001F /* AIF1DRC2_KNEE_OP - [4:0] */ +#define WM8995_AIF1DRC2_KNEE_OP_SHIFT 0 /* AIF1DRC2_KNEE_OP - [4:0] */ +#define WM8995_AIF1DRC2_KNEE_OP_WIDTH 5 /* AIF1DRC2_KNEE_OP - [4:0] */ + +/* + * R1108 (0x454) - AIF1 DRC2 (5) + */ +#define WM8995_AIF1DRC2_KNEE2_IP_MASK 0x03E0 /* AIF1DRC2_KNEE2_IP - [9:5] */ +#define WM8995_AIF1DRC2_KNEE2_IP_SHIFT 5 /* AIF1DRC2_KNEE2_IP - [9:5] */ +#define WM8995_AIF1DRC2_KNEE2_IP_WIDTH 5 /* AIF1DRC2_KNEE2_IP - [9:5] */ +#define WM8995_AIF1DRC2_KNEE2_OP_MASK 0x001F /* AIF1DRC2_KNEE2_OP - [4:0] */ +#define WM8995_AIF1DRC2_KNEE2_OP_SHIFT 0 /* AIF1DRC2_KNEE2_OP - [4:0] */ +#define WM8995_AIF1DRC2_KNEE2_OP_WIDTH 5 /* AIF1DRC2_KNEE2_OP - [4:0] */ + +/* + * R1152 (0x480) - AIF1 DAC1 EQ Gains (1) + */ +#define WM8995_AIF1DAC1_EQ_B1_GAIN_MASK 0xF800 /* AIF1DAC1_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF1DAC1_EQ_B1_GAIN_SHIFT 11 /* AIF1DAC1_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF1DAC1_EQ_B1_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF1DAC1_EQ_B2_GAIN_MASK 0x07C0 /* AIF1DAC1_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF1DAC1_EQ_B2_GAIN_SHIFT 6 /* AIF1DAC1_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF1DAC1_EQ_B2_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF1DAC1_EQ_B3_GAIN_MASK 0x003E /* AIF1DAC1_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF1DAC1_EQ_B3_GAIN_SHIFT 1 /* AIF1DAC1_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF1DAC1_EQ_B3_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF1DAC1_EQ_ENA 0x0001 /* AIF1DAC1_EQ_ENA */ +#define WM8995_AIF1DAC1_EQ_ENA_MASK 0x0001 /* AIF1DAC1_EQ_ENA */ +#define WM8995_AIF1DAC1_EQ_ENA_SHIFT 0 /* AIF1DAC1_EQ_ENA */ +#define WM8995_AIF1DAC1_EQ_ENA_WIDTH 1 /* AIF1DAC1_EQ_ENA */ + +/* + * R1153 (0x481) - AIF1 DAC1 EQ Gains (2) + */ +#define WM8995_AIF1DAC1_EQ_B4_GAIN_MASK 0xF800 /* AIF1DAC1_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF1DAC1_EQ_B4_GAIN_SHIFT 11 /* AIF1DAC1_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF1DAC1_EQ_B4_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF1DAC1_EQ_B5_GAIN_MASK 0x07C0 /* AIF1DAC1_EQ_B5_GAIN - [10:6] */ +#define WM8995_AIF1DAC1_EQ_B5_GAIN_SHIFT 6 /* AIF1DAC1_EQ_B5_GAIN - [10:6] */ +#define WM8995_AIF1DAC1_EQ_B5_GAIN_WIDTH 5 /* AIF1DAC1_EQ_B5_GAIN - [10:6] */ + +/* + * R1154 (0x482) - AIF1 DAC1 EQ Band 1 A + */ +#define WM8995_AIF1DAC1_EQ_B1_A_MASK 0xFFFF /* AIF1DAC1_EQ_B1_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B1_A_SHIFT 0 /* AIF1DAC1_EQ_B1_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B1_A_WIDTH 16 /* AIF1DAC1_EQ_B1_A - [15:0] */ + +/* + * R1155 (0x483) - AIF1 DAC1 EQ Band 1 B + */ +#define WM8995_AIF1DAC1_EQ_B1_B_MASK 0xFFFF /* AIF1DAC1_EQ_B1_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B1_B_SHIFT 0 /* AIF1DAC1_EQ_B1_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B1_B_WIDTH 16 /* AIF1DAC1_EQ_B1_B - [15:0] */ + +/* + * R1156 (0x484) - AIF1 DAC1 EQ Band 1 PG + */ +#define WM8995_AIF1DAC1_EQ_B1_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B1_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B1_PG_SHIFT 0 /* AIF1DAC1_EQ_B1_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B1_PG_WIDTH 16 /* AIF1DAC1_EQ_B1_PG - [15:0] */ + +/* + * R1157 (0x485) - AIF1 DAC1 EQ Band 2 A + */ +#define WM8995_AIF1DAC1_EQ_B2_A_MASK 0xFFFF /* AIF1DAC1_EQ_B2_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_A_SHIFT 0 /* AIF1DAC1_EQ_B2_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_A_WIDTH 16 /* AIF1DAC1_EQ_B2_A - [15:0] */ + +/* + * R1158 (0x486) - AIF1 DAC1 EQ Band 2 B + */ +#define WM8995_AIF1DAC1_EQ_B2_B_MASK 0xFFFF /* AIF1DAC1_EQ_B2_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_B_SHIFT 0 /* AIF1DAC1_EQ_B2_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_B_WIDTH 16 /* AIF1DAC1_EQ_B2_B - [15:0] */ + +/* + * R1159 (0x487) - AIF1 DAC1 EQ Band 2 C + */ +#define WM8995_AIF1DAC1_EQ_B2_C_MASK 0xFFFF /* AIF1DAC1_EQ_B2_C - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_C_SHIFT 0 /* AIF1DAC1_EQ_B2_C - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_C_WIDTH 16 /* AIF1DAC1_EQ_B2_C - [15:0] */ + +/* + * R1160 (0x488) - AIF1 DAC1 EQ Band 2 PG + */ +#define WM8995_AIF1DAC1_EQ_B2_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B2_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_PG_SHIFT 0 /* AIF1DAC1_EQ_B2_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B2_PG_WIDTH 16 /* AIF1DAC1_EQ_B2_PG - [15:0] */ + +/* + * R1161 (0x489) - AIF1 DAC1 EQ Band 3 A + */ +#define WM8995_AIF1DAC1_EQ_B3_A_MASK 0xFFFF /* AIF1DAC1_EQ_B3_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_A_SHIFT 0 /* AIF1DAC1_EQ_B3_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_A_WIDTH 16 /* AIF1DAC1_EQ_B3_A - [15:0] */ + +/* + * R1162 (0x48A) - AIF1 DAC1 EQ Band 3 B + */ +#define WM8995_AIF1DAC1_EQ_B3_B_MASK 0xFFFF /* AIF1DAC1_EQ_B3_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_B_SHIFT 0 /* AIF1DAC1_EQ_B3_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_B_WIDTH 16 /* AIF1DAC1_EQ_B3_B - [15:0] */ + +/* + * R1163 (0x48B) - AIF1 DAC1 EQ Band 3 C + */ +#define WM8995_AIF1DAC1_EQ_B3_C_MASK 0xFFFF /* AIF1DAC1_EQ_B3_C - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_C_SHIFT 0 /* AIF1DAC1_EQ_B3_C - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_C_WIDTH 16 /* AIF1DAC1_EQ_B3_C - [15:0] */ + +/* + * R1164 (0x48C) - AIF1 DAC1 EQ Band 3 PG + */ +#define WM8995_AIF1DAC1_EQ_B3_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B3_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_PG_SHIFT 0 /* AIF1DAC1_EQ_B3_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B3_PG_WIDTH 16 /* AIF1DAC1_EQ_B3_PG - [15:0] */ + +/* + * R1165 (0x48D) - AIF1 DAC1 EQ Band 4 A + */ +#define WM8995_AIF1DAC1_EQ_B4_A_MASK 0xFFFF /* AIF1DAC1_EQ_B4_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_A_SHIFT 0 /* AIF1DAC1_EQ_B4_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_A_WIDTH 16 /* AIF1DAC1_EQ_B4_A - [15:0] */ + +/* + * R1166 (0x48E) - AIF1 DAC1 EQ Band 4 B + */ +#define WM8995_AIF1DAC1_EQ_B4_B_MASK 0xFFFF /* AIF1DAC1_EQ_B4_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_B_SHIFT 0 /* AIF1DAC1_EQ_B4_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_B_WIDTH 16 /* AIF1DAC1_EQ_B4_B - [15:0] */ + +/* + * R1167 (0x48F) - AIF1 DAC1 EQ Band 4 C + */ +#define WM8995_AIF1DAC1_EQ_B4_C_MASK 0xFFFF /* AIF1DAC1_EQ_B4_C - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_C_SHIFT 0 /* AIF1DAC1_EQ_B4_C - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_C_WIDTH 16 /* AIF1DAC1_EQ_B4_C - [15:0] */ + +/* + * R1168 (0x490) - AIF1 DAC1 EQ Band 4 PG + */ +#define WM8995_AIF1DAC1_EQ_B4_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B4_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_PG_SHIFT 0 /* AIF1DAC1_EQ_B4_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B4_PG_WIDTH 16 /* AIF1DAC1_EQ_B4_PG - [15:0] */ + +/* + * R1169 (0x491) - AIF1 DAC1 EQ Band 5 A + */ +#define WM8995_AIF1DAC1_EQ_B5_A_MASK 0xFFFF /* AIF1DAC1_EQ_B5_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B5_A_SHIFT 0 /* AIF1DAC1_EQ_B5_A - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B5_A_WIDTH 16 /* AIF1DAC1_EQ_B5_A - [15:0] */ + +/* + * R1170 (0x492) - AIF1 DAC1 EQ Band 5 B + */ +#define WM8995_AIF1DAC1_EQ_B5_B_MASK 0xFFFF /* AIF1DAC1_EQ_B5_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B5_B_SHIFT 0 /* AIF1DAC1_EQ_B5_B - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B5_B_WIDTH 16 /* AIF1DAC1_EQ_B5_B - [15:0] */ + +/* + * R1171 (0x493) - AIF1 DAC1 EQ Band 5 PG + */ +#define WM8995_AIF1DAC1_EQ_B5_PG_MASK 0xFFFF /* AIF1DAC1_EQ_B5_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B5_PG_SHIFT 0 /* AIF1DAC1_EQ_B5_PG - [15:0] */ +#define WM8995_AIF1DAC1_EQ_B5_PG_WIDTH 16 /* AIF1DAC1_EQ_B5_PG - [15:0] */ + +/* + * R1184 (0x4A0) - AIF1 DAC2 EQ Gains (1) + */ +#define WM8995_AIF1DAC2_EQ_B1_GAIN_MASK 0xF800 /* AIF1DAC2_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF1DAC2_EQ_B1_GAIN_SHIFT 11 /* AIF1DAC2_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF1DAC2_EQ_B1_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF1DAC2_EQ_B2_GAIN_MASK 0x07C0 /* AIF1DAC2_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF1DAC2_EQ_B2_GAIN_SHIFT 6 /* AIF1DAC2_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF1DAC2_EQ_B2_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF1DAC2_EQ_B3_GAIN_MASK 0x003E /* AIF1DAC2_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF1DAC2_EQ_B3_GAIN_SHIFT 1 /* AIF1DAC2_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF1DAC2_EQ_B3_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF1DAC2_EQ_ENA 0x0001 /* AIF1DAC2_EQ_ENA */ +#define WM8995_AIF1DAC2_EQ_ENA_MASK 0x0001 /* AIF1DAC2_EQ_ENA */ +#define WM8995_AIF1DAC2_EQ_ENA_SHIFT 0 /* AIF1DAC2_EQ_ENA */ +#define WM8995_AIF1DAC2_EQ_ENA_WIDTH 1 /* AIF1DAC2_EQ_ENA */ + +/* + * R1185 (0x4A1) - AIF1 DAC2 EQ Gains (2) + */ +#define WM8995_AIF1DAC2_EQ_B4_GAIN_MASK 0xF800 /* AIF1DAC2_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF1DAC2_EQ_B4_GAIN_SHIFT 11 /* AIF1DAC2_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF1DAC2_EQ_B4_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF1DAC2_EQ_B5_GAIN_MASK 0x07C0 /* AIF1DAC2_EQ_B5_GAIN - [10:6] */ +#define WM8995_AIF1DAC2_EQ_B5_GAIN_SHIFT 6 /* AIF1DAC2_EQ_B5_GAIN - [10:6] */ +#define WM8995_AIF1DAC2_EQ_B5_GAIN_WIDTH 5 /* AIF1DAC2_EQ_B5_GAIN - [10:6] */ + +/* + * R1186 (0x4A2) - AIF1 DAC2 EQ Band 1 A + */ +#define WM8995_AIF1DAC2_EQ_B1_A_MASK 0xFFFF /* AIF1DAC2_EQ_B1_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B1_A_SHIFT 0 /* AIF1DAC2_EQ_B1_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B1_A_WIDTH 16 /* AIF1DAC2_EQ_B1_A - [15:0] */ + +/* + * R1187 (0x4A3) - AIF1 DAC2 EQ Band 1 B + */ +#define WM8995_AIF1DAC2_EQ_B1_B_MASK 0xFFFF /* AIF1DAC2_EQ_B1_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B1_B_SHIFT 0 /* AIF1DAC2_EQ_B1_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B1_B_WIDTH 16 /* AIF1DAC2_EQ_B1_B - [15:0] */ + +/* + * R1188 (0x4A4) - AIF1 DAC2 EQ Band 1 PG + */ +#define WM8995_AIF1DAC2_EQ_B1_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B1_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B1_PG_SHIFT 0 /* AIF1DAC2_EQ_B1_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B1_PG_WIDTH 16 /* AIF1DAC2_EQ_B1_PG - [15:0] */ + +/* + * R1189 (0x4A5) - AIF1 DAC2 EQ Band 2 A + */ +#define WM8995_AIF1DAC2_EQ_B2_A_MASK 0xFFFF /* AIF1DAC2_EQ_B2_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_A_SHIFT 0 /* AIF1DAC2_EQ_B2_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_A_WIDTH 16 /* AIF1DAC2_EQ_B2_A - [15:0] */ + +/* + * R1190 (0x4A6) - AIF1 DAC2 EQ Band 2 B + */ +#define WM8995_AIF1DAC2_EQ_B2_B_MASK 0xFFFF /* AIF1DAC2_EQ_B2_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_B_SHIFT 0 /* AIF1DAC2_EQ_B2_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_B_WIDTH 16 /* AIF1DAC2_EQ_B2_B - [15:0] */ + +/* + * R1191 (0x4A7) - AIF1 DAC2 EQ Band 2 C + */ +#define WM8995_AIF1DAC2_EQ_B2_C_MASK 0xFFFF /* AIF1DAC2_EQ_B2_C - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_C_SHIFT 0 /* AIF1DAC2_EQ_B2_C - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_C_WIDTH 16 /* AIF1DAC2_EQ_B2_C - [15:0] */ + +/* + * R1192 (0x4A8) - AIF1 DAC2 EQ Band 2 PG + */ +#define WM8995_AIF1DAC2_EQ_B2_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B2_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_PG_SHIFT 0 /* AIF1DAC2_EQ_B2_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B2_PG_WIDTH 16 /* AIF1DAC2_EQ_B2_PG - [15:0] */ + +/* + * R1193 (0x4A9) - AIF1 DAC2 EQ Band 3 A + */ +#define WM8995_AIF1DAC2_EQ_B3_A_MASK 0xFFFF /* AIF1DAC2_EQ_B3_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_A_SHIFT 0 /* AIF1DAC2_EQ_B3_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_A_WIDTH 16 /* AIF1DAC2_EQ_B3_A - [15:0] */ + +/* + * R1194 (0x4AA) - AIF1 DAC2 EQ Band 3 B + */ +#define WM8995_AIF1DAC2_EQ_B3_B_MASK 0xFFFF /* AIF1DAC2_EQ_B3_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_B_SHIFT 0 /* AIF1DAC2_EQ_B3_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_B_WIDTH 16 /* AIF1DAC2_EQ_B3_B - [15:0] */ + +/* + * R1195 (0x4AB) - AIF1 DAC2 EQ Band 3 C + */ +#define WM8995_AIF1DAC2_EQ_B3_C_MASK 0xFFFF /* AIF1DAC2_EQ_B3_C - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_C_SHIFT 0 /* AIF1DAC2_EQ_B3_C - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_C_WIDTH 16 /* AIF1DAC2_EQ_B3_C - [15:0] */ + +/* + * R1196 (0x4AC) - AIF1 DAC2 EQ Band 3 PG + */ +#define WM8995_AIF1DAC2_EQ_B3_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B3_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_PG_SHIFT 0 /* AIF1DAC2_EQ_B3_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B3_PG_WIDTH 16 /* AIF1DAC2_EQ_B3_PG - [15:0] */ + +/* + * R1197 (0x4AD) - AIF1 DAC2 EQ Band 4 A + */ +#define WM8995_AIF1DAC2_EQ_B4_A_MASK 0xFFFF /* AIF1DAC2_EQ_B4_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_A_SHIFT 0 /* AIF1DAC2_EQ_B4_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_A_WIDTH 16 /* AIF1DAC2_EQ_B4_A - [15:0] */ + +/* + * R1198 (0x4AE) - AIF1 DAC2 EQ Band 4 B + */ +#define WM8995_AIF1DAC2_EQ_B4_B_MASK 0xFFFF /* AIF1DAC2_EQ_B4_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_B_SHIFT 0 /* AIF1DAC2_EQ_B4_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_B_WIDTH 16 /* AIF1DAC2_EQ_B4_B - [15:0] */ + +/* + * R1199 (0x4AF) - AIF1 DAC2 EQ Band 4 C + */ +#define WM8995_AIF1DAC2_EQ_B4_C_MASK 0xFFFF /* AIF1DAC2_EQ_B4_C - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_C_SHIFT 0 /* AIF1DAC2_EQ_B4_C - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_C_WIDTH 16 /* AIF1DAC2_EQ_B4_C - [15:0] */ + +/* + * R1200 (0x4B0) - AIF1 DAC2 EQ Band 4 PG + */ +#define WM8995_AIF1DAC2_EQ_B4_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B4_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_PG_SHIFT 0 /* AIF1DAC2_EQ_B4_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B4_PG_WIDTH 16 /* AIF1DAC2_EQ_B4_PG - [15:0] */ + +/* + * R1201 (0x4B1) - AIF1 DAC2 EQ Band 5 A + */ +#define WM8995_AIF1DAC2_EQ_B5_A_MASK 0xFFFF /* AIF1DAC2_EQ_B5_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B5_A_SHIFT 0 /* AIF1DAC2_EQ_B5_A - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B5_A_WIDTH 16 /* AIF1DAC2_EQ_B5_A - [15:0] */ + +/* + * R1202 (0x4B2) - AIF1 DAC2 EQ Band 5 B + */ +#define WM8995_AIF1DAC2_EQ_B5_B_MASK 0xFFFF /* AIF1DAC2_EQ_B5_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B5_B_SHIFT 0 /* AIF1DAC2_EQ_B5_B - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B5_B_WIDTH 16 /* AIF1DAC2_EQ_B5_B - [15:0] */ + +/* + * R1203 (0x4B3) - AIF1 DAC2 EQ Band 5 PG + */ +#define WM8995_AIF1DAC2_EQ_B5_PG_MASK 0xFFFF /* AIF1DAC2_EQ_B5_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B5_PG_SHIFT 0 /* AIF1DAC2_EQ_B5_PG - [15:0] */ +#define WM8995_AIF1DAC2_EQ_B5_PG_WIDTH 16 /* AIF1DAC2_EQ_B5_PG - [15:0] */ + +/* + * R1280 (0x500) - AIF2 ADC Left Volume + */ +#define WM8995_AIF2ADC_VU 0x0100 /* AIF2ADC_VU */ +#define WM8995_AIF2ADC_VU_MASK 0x0100 /* AIF2ADC_VU */ +#define WM8995_AIF2ADC_VU_SHIFT 8 /* AIF2ADC_VU */ +#define WM8995_AIF2ADC_VU_WIDTH 1 /* AIF2ADC_VU */ +#define WM8995_AIF2ADCL_VOL_MASK 0x00FF /* AIF2ADCL_VOL - [7:0] */ +#define WM8995_AIF2ADCL_VOL_SHIFT 0 /* AIF2ADCL_VOL - [7:0] */ +#define WM8995_AIF2ADCL_VOL_WIDTH 8 /* AIF2ADCL_VOL - [7:0] */ + +/* + * R1281 (0x501) - AIF2 ADC Right Volume + */ +#define WM8995_AIF2ADC_VU 0x0100 /* AIF2ADC_VU */ +#define WM8995_AIF2ADC_VU_MASK 0x0100 /* AIF2ADC_VU */ +#define WM8995_AIF2ADC_VU_SHIFT 8 /* AIF2ADC_VU */ +#define WM8995_AIF2ADC_VU_WIDTH 1 /* AIF2ADC_VU */ +#define WM8995_AIF2ADCR_VOL_MASK 0x00FF /* AIF2ADCR_VOL - [7:0] */ +#define WM8995_AIF2ADCR_VOL_SHIFT 0 /* AIF2ADCR_VOL - [7:0] */ +#define WM8995_AIF2ADCR_VOL_WIDTH 8 /* AIF2ADCR_VOL - [7:0] */ + +/* + * R1282 (0x502) - AIF2 DAC Left Volume + */ +#define WM8995_AIF2DAC_VU 0x0100 /* AIF2DAC_VU */ +#define WM8995_AIF2DAC_VU_MASK 0x0100 /* AIF2DAC_VU */ +#define WM8995_AIF2DAC_VU_SHIFT 8 /* AIF2DAC_VU */ +#define WM8995_AIF2DAC_VU_WIDTH 1 /* AIF2DAC_VU */ +#define WM8995_AIF2DACL_VOL_MASK 0x00FF /* AIF2DACL_VOL - [7:0] */ +#define WM8995_AIF2DACL_VOL_SHIFT 0 /* AIF2DACL_VOL - [7:0] */ +#define WM8995_AIF2DACL_VOL_WIDTH 8 /* AIF2DACL_VOL - [7:0] */ + +/* + * R1283 (0x503) - AIF2 DAC Right Volume + */ +#define WM8995_AIF2DAC_VU 0x0100 /* AIF2DAC_VU */ +#define WM8995_AIF2DAC_VU_MASK 0x0100 /* AIF2DAC_VU */ +#define WM8995_AIF2DAC_VU_SHIFT 8 /* AIF2DAC_VU */ +#define WM8995_AIF2DAC_VU_WIDTH 1 /* AIF2DAC_VU */ +#define WM8995_AIF2DACR_VOL_MASK 0x00FF /* AIF2DACR_VOL - [7:0] */ +#define WM8995_AIF2DACR_VOL_SHIFT 0 /* AIF2DACR_VOL - [7:0] */ +#define WM8995_AIF2DACR_VOL_WIDTH 8 /* AIF2DACR_VOL - [7:0] */ + +/* + * R1296 (0x510) - AIF2 ADC Filters + */ +#define WM8995_AIF2ADC_4FS 0x8000 /* AIF2ADC_4FS */ +#define WM8995_AIF2ADC_4FS_MASK 0x8000 /* AIF2ADC_4FS */ +#define WM8995_AIF2ADC_4FS_SHIFT 15 /* AIF2ADC_4FS */ +#define WM8995_AIF2ADC_4FS_WIDTH 1 /* AIF2ADC_4FS */ +#define WM8995_AIF2ADCL_HPF 0x1000 /* AIF2ADCL_HPF */ +#define WM8995_AIF2ADCL_HPF_MASK 0x1000 /* AIF2ADCL_HPF */ +#define WM8995_AIF2ADCL_HPF_SHIFT 12 /* AIF2ADCL_HPF */ +#define WM8995_AIF2ADCL_HPF_WIDTH 1 /* AIF2ADCL_HPF */ +#define WM8995_AIF2ADCR_HPF 0x0800 /* AIF2ADCR_HPF */ +#define WM8995_AIF2ADCR_HPF_MASK 0x0800 /* AIF2ADCR_HPF */ +#define WM8995_AIF2ADCR_HPF_SHIFT 11 /* AIF2ADCR_HPF */ +#define WM8995_AIF2ADCR_HPF_WIDTH 1 /* AIF2ADCR_HPF */ +#define WM8995_AIF2ADC_HPF_MODE 0x0008 /* AIF2ADC_HPF_MODE */ +#define WM8995_AIF2ADC_HPF_MODE_MASK 0x0008 /* AIF2ADC_HPF_MODE */ +#define WM8995_AIF2ADC_HPF_MODE_SHIFT 3 /* AIF2ADC_HPF_MODE */ +#define WM8995_AIF2ADC_HPF_MODE_WIDTH 1 /* AIF2ADC_HPF_MODE */ +#define WM8995_AIF2ADC_HPF_CUT_MASK 0x0007 /* AIF2ADC_HPF_CUT - [2:0] */ +#define WM8995_AIF2ADC_HPF_CUT_SHIFT 0 /* AIF2ADC_HPF_CUT - [2:0] */ +#define WM8995_AIF2ADC_HPF_CUT_WIDTH 3 /* AIF2ADC_HPF_CUT - [2:0] */ + +/* + * R1312 (0x520) - AIF2 DAC Filters (1) + */ +#define WM8995_AIF2DAC_MUTE 0x0200 /* AIF2DAC_MUTE */ +#define WM8995_AIF2DAC_MUTE_MASK 0x0200 /* AIF2DAC_MUTE */ +#define WM8995_AIF2DAC_MUTE_SHIFT 9 /* AIF2DAC_MUTE */ +#define WM8995_AIF2DAC_MUTE_WIDTH 1 /* AIF2DAC_MUTE */ +#define WM8995_AIF2DAC_MONO 0x0080 /* AIF2DAC_MONO */ +#define WM8995_AIF2DAC_MONO_MASK 0x0080 /* AIF2DAC_MONO */ +#define WM8995_AIF2DAC_MONO_SHIFT 7 /* AIF2DAC_MONO */ +#define WM8995_AIF2DAC_MONO_WIDTH 1 /* AIF2DAC_MONO */ +#define WM8995_AIF2DAC_MUTERATE 0x0020 /* AIF2DAC_MUTERATE */ +#define WM8995_AIF2DAC_MUTERATE_MASK 0x0020 /* AIF2DAC_MUTERATE */ +#define WM8995_AIF2DAC_MUTERATE_SHIFT 5 /* AIF2DAC_MUTERATE */ +#define WM8995_AIF2DAC_MUTERATE_WIDTH 1 /* AIF2DAC_MUTERATE */ +#define WM8995_AIF2DAC_UNMUTE_RAMP 0x0010 /* AIF2DAC_UNMUTE_RAMP */ +#define WM8995_AIF2DAC_UNMUTE_RAMP_MASK 0x0010 /* AIF2DAC_UNMUTE_RAMP */ +#define WM8995_AIF2DAC_UNMUTE_RAMP_SHIFT 4 /* AIF2DAC_UNMUTE_RAMP */ +#define WM8995_AIF2DAC_UNMUTE_RAMP_WIDTH 1 /* AIF2DAC_UNMUTE_RAMP */ +#define WM8995_AIF2DAC_DEEMP_MASK 0x0006 /* AIF2DAC_DEEMP - [2:1] */ +#define WM8995_AIF2DAC_DEEMP_SHIFT 1 /* AIF2DAC_DEEMP - [2:1] */ +#define WM8995_AIF2DAC_DEEMP_WIDTH 2 /* AIF2DAC_DEEMP - [2:1] */ + +/* + * R1313 (0x521) - AIF2 DAC Filters (2) + */ +#define WM8995_AIF2DAC_3D_GAIN_MASK 0x3E00 /* AIF2DAC_3D_GAIN - [13:9] */ +#define WM8995_AIF2DAC_3D_GAIN_SHIFT 9 /* AIF2DAC_3D_GAIN - [13:9] */ +#define WM8995_AIF2DAC_3D_GAIN_WIDTH 5 /* AIF2DAC_3D_GAIN - [13:9] */ +#define WM8995_AIF2DAC_3D_ENA 0x0100 /* AIF2DAC_3D_ENA */ +#define WM8995_AIF2DAC_3D_ENA_MASK 0x0100 /* AIF2DAC_3D_ENA */ +#define WM8995_AIF2DAC_3D_ENA_SHIFT 8 /* AIF2DAC_3D_ENA */ +#define WM8995_AIF2DAC_3D_ENA_WIDTH 1 /* AIF2DAC_3D_ENA */ + +/* + * R1344 (0x540) - AIF2 DRC (1) + */ +#define WM8995_AIF2DRC_SIG_DET_RMS_MASK 0xF800 /* AIF2DRC_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF2DRC_SIG_DET_RMS_SHIFT 11 /* AIF2DRC_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF2DRC_SIG_DET_RMS_WIDTH 5 /* AIF2DRC_SIG_DET_RMS - [15:11] */ +#define WM8995_AIF2DRC_SIG_DET_PK_MASK 0x0600 /* AIF2DRC_SIG_DET_PK - [10:9] */ +#define WM8995_AIF2DRC_SIG_DET_PK_SHIFT 9 /* AIF2DRC_SIG_DET_PK - [10:9] */ +#define WM8995_AIF2DRC_SIG_DET_PK_WIDTH 2 /* AIF2DRC_SIG_DET_PK - [10:9] */ +#define WM8995_AIF2DRC_NG_ENA 0x0100 /* AIF2DRC_NG_ENA */ +#define WM8995_AIF2DRC_NG_ENA_MASK 0x0100 /* AIF2DRC_NG_ENA */ +#define WM8995_AIF2DRC_NG_ENA_SHIFT 8 /* AIF2DRC_NG_ENA */ +#define WM8995_AIF2DRC_NG_ENA_WIDTH 1 /* AIF2DRC_NG_ENA */ +#define WM8995_AIF2DRC_SIG_DET_MODE 0x0080 /* AIF2DRC_SIG_DET_MODE */ +#define WM8995_AIF2DRC_SIG_DET_MODE_MASK 0x0080 /* AIF2DRC_SIG_DET_MODE */ +#define WM8995_AIF2DRC_SIG_DET_MODE_SHIFT 7 /* AIF2DRC_SIG_DET_MODE */ +#define WM8995_AIF2DRC_SIG_DET_MODE_WIDTH 1 /* AIF2DRC_SIG_DET_MODE */ +#define WM8995_AIF2DRC_SIG_DET 0x0040 /* AIF2DRC_SIG_DET */ +#define WM8995_AIF2DRC_SIG_DET_MASK 0x0040 /* AIF2DRC_SIG_DET */ +#define WM8995_AIF2DRC_SIG_DET_SHIFT 6 /* AIF2DRC_SIG_DET */ +#define WM8995_AIF2DRC_SIG_DET_WIDTH 1 /* AIF2DRC_SIG_DET */ +#define WM8995_AIF2DRC_KNEE2_OP_ENA 0x0020 /* AIF2DRC_KNEE2_OP_ENA */ +#define WM8995_AIF2DRC_KNEE2_OP_ENA_MASK 0x0020 /* AIF2DRC_KNEE2_OP_ENA */ +#define WM8995_AIF2DRC_KNEE2_OP_ENA_SHIFT 5 /* AIF2DRC_KNEE2_OP_ENA */ +#define WM8995_AIF2DRC_KNEE2_OP_ENA_WIDTH 1 /* AIF2DRC_KNEE2_OP_ENA */ +#define WM8995_AIF2DRC_QR 0x0010 /* AIF2DRC_QR */ +#define WM8995_AIF2DRC_QR_MASK 0x0010 /* AIF2DRC_QR */ +#define WM8995_AIF2DRC_QR_SHIFT 4 /* AIF2DRC_QR */ +#define WM8995_AIF2DRC_QR_WIDTH 1 /* AIF2DRC_QR */ +#define WM8995_AIF2DRC_ANTICLIP 0x0008 /* AIF2DRC_ANTICLIP */ +#define WM8995_AIF2DRC_ANTICLIP_MASK 0x0008 /* AIF2DRC_ANTICLIP */ +#define WM8995_AIF2DRC_ANTICLIP_SHIFT 3 /* AIF2DRC_ANTICLIP */ +#define WM8995_AIF2DRC_ANTICLIP_WIDTH 1 /* AIF2DRC_ANTICLIP */ +#define WM8995_AIF2DAC_DRC_ENA 0x0004 /* AIF2DAC_DRC_ENA */ +#define WM8995_AIF2DAC_DRC_ENA_MASK 0x0004 /* AIF2DAC_DRC_ENA */ +#define WM8995_AIF2DAC_DRC_ENA_SHIFT 2 /* AIF2DAC_DRC_ENA */ +#define WM8995_AIF2DAC_DRC_ENA_WIDTH 1 /* AIF2DAC_DRC_ENA */ +#define WM8995_AIF2ADCL_DRC_ENA 0x0002 /* AIF2ADCL_DRC_ENA */ +#define WM8995_AIF2ADCL_DRC_ENA_MASK 0x0002 /* AIF2ADCL_DRC_ENA */ +#define WM8995_AIF2ADCL_DRC_ENA_SHIFT 1 /* AIF2ADCL_DRC_ENA */ +#define WM8995_AIF2ADCL_DRC_ENA_WIDTH 1 /* AIF2ADCL_DRC_ENA */ +#define WM8995_AIF2ADCR_DRC_ENA 0x0001 /* AIF2ADCR_DRC_ENA */ +#define WM8995_AIF2ADCR_DRC_ENA_MASK 0x0001 /* AIF2ADCR_DRC_ENA */ +#define WM8995_AIF2ADCR_DRC_ENA_SHIFT 0 /* AIF2ADCR_DRC_ENA */ +#define WM8995_AIF2ADCR_DRC_ENA_WIDTH 1 /* AIF2ADCR_DRC_ENA */ + +/* + * R1345 (0x541) - AIF2 DRC (2) + */ +#define WM8995_AIF2DRC_ATK_MASK 0x1E00 /* AIF2DRC_ATK - [12:9] */ +#define WM8995_AIF2DRC_ATK_SHIFT 9 /* AIF2DRC_ATK - [12:9] */ +#define WM8995_AIF2DRC_ATK_WIDTH 4 /* AIF2DRC_ATK - [12:9] */ +#define WM8995_AIF2DRC_DCY_MASK 0x01E0 /* AIF2DRC_DCY - [8:5] */ +#define WM8995_AIF2DRC_DCY_SHIFT 5 /* AIF2DRC_DCY - [8:5] */ +#define WM8995_AIF2DRC_DCY_WIDTH 4 /* AIF2DRC_DCY - [8:5] */ +#define WM8995_AIF2DRC_MINGAIN_MASK 0x001C /* AIF2DRC_MINGAIN - [4:2] */ +#define WM8995_AIF2DRC_MINGAIN_SHIFT 2 /* AIF2DRC_MINGAIN - [4:2] */ +#define WM8995_AIF2DRC_MINGAIN_WIDTH 3 /* AIF2DRC_MINGAIN - [4:2] */ +#define WM8995_AIF2DRC_MAXGAIN_MASK 0x0003 /* AIF2DRC_MAXGAIN - [1:0] */ +#define WM8995_AIF2DRC_MAXGAIN_SHIFT 0 /* AIF2DRC_MAXGAIN - [1:0] */ +#define WM8995_AIF2DRC_MAXGAIN_WIDTH 2 /* AIF2DRC_MAXGAIN - [1:0] */ + +/* + * R1346 (0x542) - AIF2 DRC (3) + */ +#define WM8995_AIF2DRC_NG_MINGAIN_MASK 0xF000 /* AIF2DRC_NG_MINGAIN - [15:12] */ +#define WM8995_AIF2DRC_NG_MINGAIN_SHIFT 12 /* AIF2DRC_NG_MINGAIN - [15:12] */ +#define WM8995_AIF2DRC_NG_MINGAIN_WIDTH 4 /* AIF2DRC_NG_MINGAIN - [15:12] */ +#define WM8995_AIF2DRC_NG_EXP_MASK 0x0C00 /* AIF2DRC_NG_EXP - [11:10] */ +#define WM8995_AIF2DRC_NG_EXP_SHIFT 10 /* AIF2DRC_NG_EXP - [11:10] */ +#define WM8995_AIF2DRC_NG_EXP_WIDTH 2 /* AIF2DRC_NG_EXP - [11:10] */ +#define WM8995_AIF2DRC_QR_THR_MASK 0x0300 /* AIF2DRC_QR_THR - [9:8] */ +#define WM8995_AIF2DRC_QR_THR_SHIFT 8 /* AIF2DRC_QR_THR - [9:8] */ +#define WM8995_AIF2DRC_QR_THR_WIDTH 2 /* AIF2DRC_QR_THR - [9:8] */ +#define WM8995_AIF2DRC_QR_DCY_MASK 0x00C0 /* AIF2DRC_QR_DCY - [7:6] */ +#define WM8995_AIF2DRC_QR_DCY_SHIFT 6 /* AIF2DRC_QR_DCY - [7:6] */ +#define WM8995_AIF2DRC_QR_DCY_WIDTH 2 /* AIF2DRC_QR_DCY - [7:6] */ +#define WM8995_AIF2DRC_HI_COMP_MASK 0x0038 /* AIF2DRC_HI_COMP - [5:3] */ +#define WM8995_AIF2DRC_HI_COMP_SHIFT 3 /* AIF2DRC_HI_COMP - [5:3] */ +#define WM8995_AIF2DRC_HI_COMP_WIDTH 3 /* AIF2DRC_HI_COMP - [5:3] */ +#define WM8995_AIF2DRC_LO_COMP_MASK 0x0007 /* AIF2DRC_LO_COMP - [2:0] */ +#define WM8995_AIF2DRC_LO_COMP_SHIFT 0 /* AIF2DRC_LO_COMP - [2:0] */ +#define WM8995_AIF2DRC_LO_COMP_WIDTH 3 /* AIF2DRC_LO_COMP - [2:0] */ + +/* + * R1347 (0x543) - AIF2 DRC (4) + */ +#define WM8995_AIF2DRC_KNEE_IP_MASK 0x07E0 /* AIF2DRC_KNEE_IP - [10:5] */ +#define WM8995_AIF2DRC_KNEE_IP_SHIFT 5 /* AIF2DRC_KNEE_IP - [10:5] */ +#define WM8995_AIF2DRC_KNEE_IP_WIDTH 6 /* AIF2DRC_KNEE_IP - [10:5] */ +#define WM8995_AIF2DRC_KNEE_OP_MASK 0x001F /* AIF2DRC_KNEE_OP - [4:0] */ +#define WM8995_AIF2DRC_KNEE_OP_SHIFT 0 /* AIF2DRC_KNEE_OP - [4:0] */ +#define WM8995_AIF2DRC_KNEE_OP_WIDTH 5 /* AIF2DRC_KNEE_OP - [4:0] */ + +/* + * R1348 (0x544) - AIF2 DRC (5) + */ +#define WM8995_AIF2DRC_KNEE2_IP_MASK 0x03E0 /* AIF2DRC_KNEE2_IP - [9:5] */ +#define WM8995_AIF2DRC_KNEE2_IP_SHIFT 5 /* AIF2DRC_KNEE2_IP - [9:5] */ +#define WM8995_AIF2DRC_KNEE2_IP_WIDTH 5 /* AIF2DRC_KNEE2_IP - [9:5] */ +#define WM8995_AIF2DRC_KNEE2_OP_MASK 0x001F /* AIF2DRC_KNEE2_OP - [4:0] */ +#define WM8995_AIF2DRC_KNEE2_OP_SHIFT 0 /* AIF2DRC_KNEE2_OP - [4:0] */ +#define WM8995_AIF2DRC_KNEE2_OP_WIDTH 5 /* AIF2DRC_KNEE2_OP - [4:0] */ + +/* + * R1408 (0x580) - AIF2 EQ Gains (1) + */ +#define WM8995_AIF2DAC_EQ_B1_GAIN_MASK 0xF800 /* AIF2DAC_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF2DAC_EQ_B1_GAIN_SHIFT 11 /* AIF2DAC_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF2DAC_EQ_B1_GAIN_WIDTH 5 /* AIF2DAC_EQ_B1_GAIN - [15:11] */ +#define WM8995_AIF2DAC_EQ_B2_GAIN_MASK 0x07C0 /* AIF2DAC_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF2DAC_EQ_B2_GAIN_SHIFT 6 /* AIF2DAC_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF2DAC_EQ_B2_GAIN_WIDTH 5 /* AIF2DAC_EQ_B2_GAIN - [10:6] */ +#define WM8995_AIF2DAC_EQ_B3_GAIN_MASK 0x003E /* AIF2DAC_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF2DAC_EQ_B3_GAIN_SHIFT 1 /* AIF2DAC_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF2DAC_EQ_B3_GAIN_WIDTH 5 /* AIF2DAC_EQ_B3_GAIN - [5:1] */ +#define WM8995_AIF2DAC_EQ_ENA 0x0001 /* AIF2DAC_EQ_ENA */ +#define WM8995_AIF2DAC_EQ_ENA_MASK 0x0001 /* AIF2DAC_EQ_ENA */ +#define WM8995_AIF2DAC_EQ_ENA_SHIFT 0 /* AIF2DAC_EQ_ENA */ +#define WM8995_AIF2DAC_EQ_ENA_WIDTH 1 /* AIF2DAC_EQ_ENA */ + +/* + * R1409 (0x581) - AIF2 EQ Gains (2) + */ +#define WM8995_AIF2DAC_EQ_B4_GAIN_MASK 0xF800 /* AIF2DAC_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF2DAC_EQ_B4_GAIN_SHIFT 11 /* AIF2DAC_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF2DAC_EQ_B4_GAIN_WIDTH 5 /* AIF2DAC_EQ_B4_GAIN - [15:11] */ +#define WM8995_AIF2DAC_EQ_B5_GAIN_MASK 0x07C0 /* AIF2DAC_EQ_B5_GAIN - [10:6] */ +#define WM8995_AIF2DAC_EQ_B5_GAIN_SHIFT 6 /* AIF2DAC_EQ_B5_GAIN - [10:6] */ +#define WM8995_AIF2DAC_EQ_B5_GAIN_WIDTH 5 /* AIF2DAC_EQ_B5_GAIN - [10:6] */ + +/* + * R1410 (0x582) - AIF2 EQ Band 1 A + */ +#define WM8995_AIF2DAC_EQ_B1_A_MASK 0xFFFF /* AIF2DAC_EQ_B1_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B1_A_SHIFT 0 /* AIF2DAC_EQ_B1_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B1_A_WIDTH 16 /* AIF2DAC_EQ_B1_A - [15:0] */ + +/* + * R1411 (0x583) - AIF2 EQ Band 1 B + */ +#define WM8995_AIF2DAC_EQ_B1_B_MASK 0xFFFF /* AIF2DAC_EQ_B1_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B1_B_SHIFT 0 /* AIF2DAC_EQ_B1_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B1_B_WIDTH 16 /* AIF2DAC_EQ_B1_B - [15:0] */ + +/* + * R1412 (0x584) - AIF2 EQ Band 1 PG + */ +#define WM8995_AIF2DAC_EQ_B1_PG_MASK 0xFFFF /* AIF2DAC_EQ_B1_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B1_PG_SHIFT 0 /* AIF2DAC_EQ_B1_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B1_PG_WIDTH 16 /* AIF2DAC_EQ_B1_PG - [15:0] */ + +/* + * R1413 (0x585) - AIF2 EQ Band 2 A + */ +#define WM8995_AIF2DAC_EQ_B2_A_MASK 0xFFFF /* AIF2DAC_EQ_B2_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_A_SHIFT 0 /* AIF2DAC_EQ_B2_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_A_WIDTH 16 /* AIF2DAC_EQ_B2_A - [15:0] */ + +/* + * R1414 (0x586) - AIF2 EQ Band 2 B + */ +#define WM8995_AIF2DAC_EQ_B2_B_MASK 0xFFFF /* AIF2DAC_EQ_B2_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_B_SHIFT 0 /* AIF2DAC_EQ_B2_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_B_WIDTH 16 /* AIF2DAC_EQ_B2_B - [15:0] */ + +/* + * R1415 (0x587) - AIF2 EQ Band 2 C + */ +#define WM8995_AIF2DAC_EQ_B2_C_MASK 0xFFFF /* AIF2DAC_EQ_B2_C - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_C_SHIFT 0 /* AIF2DAC_EQ_B2_C - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_C_WIDTH 16 /* AIF2DAC_EQ_B2_C - [15:0] */ + +/* + * R1416 (0x588) - AIF2 EQ Band 2 PG + */ +#define WM8995_AIF2DAC_EQ_B2_PG_MASK 0xFFFF /* AIF2DAC_EQ_B2_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_PG_SHIFT 0 /* AIF2DAC_EQ_B2_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B2_PG_WIDTH 16 /* AIF2DAC_EQ_B2_PG - [15:0] */ + +/* + * R1417 (0x589) - AIF2 EQ Band 3 A + */ +#define WM8995_AIF2DAC_EQ_B3_A_MASK 0xFFFF /* AIF2DAC_EQ_B3_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_A_SHIFT 0 /* AIF2DAC_EQ_B3_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_A_WIDTH 16 /* AIF2DAC_EQ_B3_A - [15:0] */ + +/* + * R1418 (0x58A) - AIF2 EQ Band 3 B + */ +#define WM8995_AIF2DAC_EQ_B3_B_MASK 0xFFFF /* AIF2DAC_EQ_B3_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_B_SHIFT 0 /* AIF2DAC_EQ_B3_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_B_WIDTH 16 /* AIF2DAC_EQ_B3_B - [15:0] */ + +/* + * R1419 (0x58B) - AIF2 EQ Band 3 C + */ +#define WM8995_AIF2DAC_EQ_B3_C_MASK 0xFFFF /* AIF2DAC_EQ_B3_C - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_C_SHIFT 0 /* AIF2DAC_EQ_B3_C - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_C_WIDTH 16 /* AIF2DAC_EQ_B3_C - [15:0] */ + +/* + * R1420 (0x58C) - AIF2 EQ Band 3 PG + */ +#define WM8995_AIF2DAC_EQ_B3_PG_MASK 0xFFFF /* AIF2DAC_EQ_B3_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_PG_SHIFT 0 /* AIF2DAC_EQ_B3_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B3_PG_WIDTH 16 /* AIF2DAC_EQ_B3_PG - [15:0] */ + +/* + * R1421 (0x58D) - AIF2 EQ Band 4 A + */ +#define WM8995_AIF2DAC_EQ_B4_A_MASK 0xFFFF /* AIF2DAC_EQ_B4_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_A_SHIFT 0 /* AIF2DAC_EQ_B4_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_A_WIDTH 16 /* AIF2DAC_EQ_B4_A - [15:0] */ + +/* + * R1422 (0x58E) - AIF2 EQ Band 4 B + */ +#define WM8995_AIF2DAC_EQ_B4_B_MASK 0xFFFF /* AIF2DAC_EQ_B4_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_B_SHIFT 0 /* AIF2DAC_EQ_B4_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_B_WIDTH 16 /* AIF2DAC_EQ_B4_B - [15:0] */ + +/* + * R1423 (0x58F) - AIF2 EQ Band 4 C + */ +#define WM8995_AIF2DAC_EQ_B4_C_MASK 0xFFFF /* AIF2DAC_EQ_B4_C - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_C_SHIFT 0 /* AIF2DAC_EQ_B4_C - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_C_WIDTH 16 /* AIF2DAC_EQ_B4_C - [15:0] */ + +/* + * R1424 (0x590) - AIF2 EQ Band 4 PG + */ +#define WM8995_AIF2DAC_EQ_B4_PG_MASK 0xFFFF /* AIF2DAC_EQ_B4_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_PG_SHIFT 0 /* AIF2DAC_EQ_B4_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B4_PG_WIDTH 16 /* AIF2DAC_EQ_B4_PG - [15:0] */ + +/* + * R1425 (0x591) - AIF2 EQ Band 5 A + */ +#define WM8995_AIF2DAC_EQ_B5_A_MASK 0xFFFF /* AIF2DAC_EQ_B5_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B5_A_SHIFT 0 /* AIF2DAC_EQ_B5_A - [15:0] */ +#define WM8995_AIF2DAC_EQ_B5_A_WIDTH 16 /* AIF2DAC_EQ_B5_A - [15:0] */ + +/* + * R1426 (0x592) - AIF2 EQ Band 5 B + */ +#define WM8995_AIF2DAC_EQ_B5_B_MASK 0xFFFF /* AIF2DAC_EQ_B5_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B5_B_SHIFT 0 /* AIF2DAC_EQ_B5_B - [15:0] */ +#define WM8995_AIF2DAC_EQ_B5_B_WIDTH 16 /* AIF2DAC_EQ_B5_B - [15:0] */ + +/* + * R1427 (0x593) - AIF2 EQ Band 5 PG + */ +#define WM8995_AIF2DAC_EQ_B5_PG_MASK 0xFFFF /* AIF2DAC_EQ_B5_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B5_PG_SHIFT 0 /* AIF2DAC_EQ_B5_PG - [15:0] */ +#define WM8995_AIF2DAC_EQ_B5_PG_WIDTH 16 /* AIF2DAC_EQ_B5_PG - [15:0] */ + +/* + * R1536 (0x600) - DAC1 Mixer Volumes + */ +#define WM8995_ADCR_DAC1_VOL_MASK 0x03E0 /* ADCR_DAC1_VOL - [9:5] */ +#define WM8995_ADCR_DAC1_VOL_SHIFT 5 /* ADCR_DAC1_VOL - [9:5] */ +#define WM8995_ADCR_DAC1_VOL_WIDTH 5 /* ADCR_DAC1_VOL - [9:5] */ +#define WM8995_ADCL_DAC1_VOL_MASK 0x001F /* ADCL_DAC1_VOL - [4:0] */ +#define WM8995_ADCL_DAC1_VOL_SHIFT 0 /* ADCL_DAC1_VOL - [4:0] */ +#define WM8995_ADCL_DAC1_VOL_WIDTH 5 /* ADCL_DAC1_VOL - [4:0] */ + +/* + * R1537 (0x601) - DAC1 Left Mixer Routing + */ +#define WM8995_ADCR_TO_DAC1L 0x0020 /* ADCR_TO_DAC1L */ +#define WM8995_ADCR_TO_DAC1L_MASK 0x0020 /* ADCR_TO_DAC1L */ +#define WM8995_ADCR_TO_DAC1L_SHIFT 5 /* ADCR_TO_DAC1L */ +#define WM8995_ADCR_TO_DAC1L_WIDTH 1 /* ADCR_TO_DAC1L */ +#define WM8995_ADCL_TO_DAC1L 0x0010 /* ADCL_TO_DAC1L */ +#define WM8995_ADCL_TO_DAC1L_MASK 0x0010 /* ADCL_TO_DAC1L */ +#define WM8995_ADCL_TO_DAC1L_SHIFT 4 /* ADCL_TO_DAC1L */ +#define WM8995_ADCL_TO_DAC1L_WIDTH 1 /* ADCL_TO_DAC1L */ +#define WM8995_AIF2DACL_TO_DAC1L 0x0004 /* AIF2DACL_TO_DAC1L */ +#define WM8995_AIF2DACL_TO_DAC1L_MASK 0x0004 /* AIF2DACL_TO_DAC1L */ +#define WM8995_AIF2DACL_TO_DAC1L_SHIFT 2 /* AIF2DACL_TO_DAC1L */ +#define WM8995_AIF2DACL_TO_DAC1L_WIDTH 1 /* AIF2DACL_TO_DAC1L */ +#define WM8995_AIF1DAC2L_TO_DAC1L 0x0002 /* AIF1DAC2L_TO_DAC1L */ +#define WM8995_AIF1DAC2L_TO_DAC1L_MASK 0x0002 /* AIF1DAC2L_TO_DAC1L */ +#define WM8995_AIF1DAC2L_TO_DAC1L_SHIFT 1 /* AIF1DAC2L_TO_DAC1L */ +#define WM8995_AIF1DAC2L_TO_DAC1L_WIDTH 1 /* AIF1DAC2L_TO_DAC1L */ +#define WM8995_AIF1DAC1L_TO_DAC1L 0x0001 /* AIF1DAC1L_TO_DAC1L */ +#define WM8995_AIF1DAC1L_TO_DAC1L_MASK 0x0001 /* AIF1DAC1L_TO_DAC1L */ +#define WM8995_AIF1DAC1L_TO_DAC1L_SHIFT 0 /* AIF1DAC1L_TO_DAC1L */ +#define WM8995_AIF1DAC1L_TO_DAC1L_WIDTH 1 /* AIF1DAC1L_TO_DAC1L */ + +/* + * R1538 (0x602) - DAC1 Right Mixer Routing + */ +#define WM8995_ADCR_TO_DAC1R 0x0020 /* ADCR_TO_DAC1R */ +#define WM8995_ADCR_TO_DAC1R_MASK 0x0020 /* ADCR_TO_DAC1R */ +#define WM8995_ADCR_TO_DAC1R_SHIFT 5 /* ADCR_TO_DAC1R */ +#define WM8995_ADCR_TO_DAC1R_WIDTH 1 /* ADCR_TO_DAC1R */ +#define WM8995_ADCL_TO_DAC1R 0x0010 /* ADCL_TO_DAC1R */ +#define WM8995_ADCL_TO_DAC1R_MASK 0x0010 /* ADCL_TO_DAC1R */ +#define WM8995_ADCL_TO_DAC1R_SHIFT 4 /* ADCL_TO_DAC1R */ +#define WM8995_ADCL_TO_DAC1R_WIDTH 1 /* ADCL_TO_DAC1R */ +#define WM8995_AIF2DACR_TO_DAC1R 0x0004 /* AIF2DACR_TO_DAC1R */ +#define WM8995_AIF2DACR_TO_DAC1R_MASK 0x0004 /* AIF2DACR_TO_DAC1R */ +#define WM8995_AIF2DACR_TO_DAC1R_SHIFT 2 /* AIF2DACR_TO_DAC1R */ +#define WM8995_AIF2DACR_TO_DAC1R_WIDTH 1 /* AIF2DACR_TO_DAC1R */ +#define WM8995_AIF1DAC2R_TO_DAC1R 0x0002 /* AIF1DAC2R_TO_DAC1R */ +#define WM8995_AIF1DAC2R_TO_DAC1R_MASK 0x0002 /* AIF1DAC2R_TO_DAC1R */ +#define WM8995_AIF1DAC2R_TO_DAC1R_SHIFT 1 /* AIF1DAC2R_TO_DAC1R */ +#define WM8995_AIF1DAC2R_TO_DAC1R_WIDTH 1 /* AIF1DAC2R_TO_DAC1R */ +#define WM8995_AIF1DAC1R_TO_DAC1R 0x0001 /* AIF1DAC1R_TO_DAC1R */ +#define WM8995_AIF1DAC1R_TO_DAC1R_MASK 0x0001 /* AIF1DAC1R_TO_DAC1R */ +#define WM8995_AIF1DAC1R_TO_DAC1R_SHIFT 0 /* AIF1DAC1R_TO_DAC1R */ +#define WM8995_AIF1DAC1R_TO_DAC1R_WIDTH 1 /* AIF1DAC1R_TO_DAC1R */ + +/* + * R1539 (0x603) - DAC2 Mixer Volumes + */ +#define WM8995_ADCR_DAC2_VOL_MASK 0x03E0 /* ADCR_DAC2_VOL - [9:5] */ +#define WM8995_ADCR_DAC2_VOL_SHIFT 5 /* ADCR_DAC2_VOL - [9:5] */ +#define WM8995_ADCR_DAC2_VOL_WIDTH 5 /* ADCR_DAC2_VOL - [9:5] */ +#define WM8995_ADCL_DAC2_VOL_MASK 0x001F /* ADCL_DAC2_VOL - [4:0] */ +#define WM8995_ADCL_DAC2_VOL_SHIFT 0 /* ADCL_DAC2_VOL - [4:0] */ +#define WM8995_ADCL_DAC2_VOL_WIDTH 5 /* ADCL_DAC2_VOL - [4:0] */ + +/* + * R1540 (0x604) - DAC2 Left Mixer Routing + */ +#define WM8995_ADCR_TO_DAC2L 0x0020 /* ADCR_TO_DAC2L */ +#define WM8995_ADCR_TO_DAC2L_MASK 0x0020 /* ADCR_TO_DAC2L */ +#define WM8995_ADCR_TO_DAC2L_SHIFT 5 /* ADCR_TO_DAC2L */ +#define WM8995_ADCR_TO_DAC2L_WIDTH 1 /* ADCR_TO_DAC2L */ +#define WM8995_ADCL_TO_DAC2L 0x0010 /* ADCL_TO_DAC2L */ +#define WM8995_ADCL_TO_DAC2L_MASK 0x0010 /* ADCL_TO_DAC2L */ +#define WM8995_ADCL_TO_DAC2L_SHIFT 4 /* ADCL_TO_DAC2L */ +#define WM8995_ADCL_TO_DAC2L_WIDTH 1 /* ADCL_TO_DAC2L */ +#define WM8995_AIF2DACL_TO_DAC2L 0x0004 /* AIF2DACL_TO_DAC2L */ +#define WM8995_AIF2DACL_TO_DAC2L_MASK 0x0004 /* AIF2DACL_TO_DAC2L */ +#define WM8995_AIF2DACL_TO_DAC2L_SHIFT 2 /* AIF2DACL_TO_DAC2L */ +#define WM8995_AIF2DACL_TO_DAC2L_WIDTH 1 /* AIF2DACL_TO_DAC2L */ +#define WM8995_AIF1DAC2L_TO_DAC2L 0x0002 /* AIF1DAC2L_TO_DAC2L */ +#define WM8995_AIF1DAC2L_TO_DAC2L_MASK 0x0002 /* AIF1DAC2L_TO_DAC2L */ +#define WM8995_AIF1DAC2L_TO_DAC2L_SHIFT 1 /* AIF1DAC2L_TO_DAC2L */ +#define WM8995_AIF1DAC2L_TO_DAC2L_WIDTH 1 /* AIF1DAC2L_TO_DAC2L */ +#define WM8995_AIF1DAC1L_TO_DAC2L 0x0001 /* AIF1DAC1L_TO_DAC2L */ +#define WM8995_AIF1DAC1L_TO_DAC2L_MASK 0x0001 /* AIF1DAC1L_TO_DAC2L */ +#define WM8995_AIF1DAC1L_TO_DAC2L_SHIFT 0 /* AIF1DAC1L_TO_DAC2L */ +#define WM8995_AIF1DAC1L_TO_DAC2L_WIDTH 1 /* AIF1DAC1L_TO_DAC2L */ + +/* + * R1541 (0x605) - DAC2 Right Mixer Routing + */ +#define WM8995_ADCR_TO_DAC2R 0x0020 /* ADCR_TO_DAC2R */ +#define WM8995_ADCR_TO_DAC2R_MASK 0x0020 /* ADCR_TO_DAC2R */ +#define WM8995_ADCR_TO_DAC2R_SHIFT 5 /* ADCR_TO_DAC2R */ +#define WM8995_ADCR_TO_DAC2R_WIDTH 1 /* ADCR_TO_DAC2R */ +#define WM8995_ADCL_TO_DAC2R 0x0010 /* ADCL_TO_DAC2R */ +#define WM8995_ADCL_TO_DAC2R_MASK 0x0010 /* ADCL_TO_DAC2R */ +#define WM8995_ADCL_TO_DAC2R_SHIFT 4 /* ADCL_TO_DAC2R */ +#define WM8995_ADCL_TO_DAC2R_WIDTH 1 /* ADCL_TO_DAC2R */ +#define WM8995_AIF2DACR_TO_DAC2R 0x0004 /* AIF2DACR_TO_DAC2R */ +#define WM8995_AIF2DACR_TO_DAC2R_MASK 0x0004 /* AIF2DACR_TO_DAC2R */ +#define WM8995_AIF2DACR_TO_DAC2R_SHIFT 2 /* AIF2DACR_TO_DAC2R */ +#define WM8995_AIF2DACR_TO_DAC2R_WIDTH 1 /* AIF2DACR_TO_DAC2R */ +#define WM8995_AIF1DAC2R_TO_DAC2R 0x0002 /* AIF1DAC2R_TO_DAC2R */ +#define WM8995_AIF1DAC2R_TO_DAC2R_MASK 0x0002 /* AIF1DAC2R_TO_DAC2R */ +#define WM8995_AIF1DAC2R_TO_DAC2R_SHIFT 1 /* AIF1DAC2R_TO_DAC2R */ +#define WM8995_AIF1DAC2R_TO_DAC2R_WIDTH 1 /* AIF1DAC2R_TO_DAC2R */ +#define WM8995_AIF1DAC1R_TO_DAC2R 0x0001 /* AIF1DAC1R_TO_DAC2R */ +#define WM8995_AIF1DAC1R_TO_DAC2R_MASK 0x0001 /* AIF1DAC1R_TO_DAC2R */ +#define WM8995_AIF1DAC1R_TO_DAC2R_SHIFT 0 /* AIF1DAC1R_TO_DAC2R */ +#define WM8995_AIF1DAC1R_TO_DAC2R_WIDTH 1 /* AIF1DAC1R_TO_DAC2R */ + +/* + * R1542 (0x606) - AIF1 ADC1 Left Mixer Routing + */ +#define WM8995_ADC1L_TO_AIF1ADC1L 0x0002 /* ADC1L_TO_AIF1ADC1L */ +#define WM8995_ADC1L_TO_AIF1ADC1L_MASK 0x0002 /* ADC1L_TO_AIF1ADC1L */ +#define WM8995_ADC1L_TO_AIF1ADC1L_SHIFT 1 /* ADC1L_TO_AIF1ADC1L */ +#define WM8995_ADC1L_TO_AIF1ADC1L_WIDTH 1 /* ADC1L_TO_AIF1ADC1L */ +#define WM8995_AIF2DACL_TO_AIF1ADC1L 0x0001 /* AIF2DACL_TO_AIF1ADC1L */ +#define WM8995_AIF2DACL_TO_AIF1ADC1L_MASK 0x0001 /* AIF2DACL_TO_AIF1ADC1L */ +#define WM8995_AIF2DACL_TO_AIF1ADC1L_SHIFT 0 /* AIF2DACL_TO_AIF1ADC1L */ +#define WM8995_AIF2DACL_TO_AIF1ADC1L_WIDTH 1 /* AIF2DACL_TO_AIF1ADC1L */ + +/* + * R1543 (0x607) - AIF1 ADC1 Right Mixer Routing + */ +#define WM8995_ADC1R_TO_AIF1ADC1R 0x0002 /* ADC1R_TO_AIF1ADC1R */ +#define WM8995_ADC1R_TO_AIF1ADC1R_MASK 0x0002 /* ADC1R_TO_AIF1ADC1R */ +#define WM8995_ADC1R_TO_AIF1ADC1R_SHIFT 1 /* ADC1R_TO_AIF1ADC1R */ +#define WM8995_ADC1R_TO_AIF1ADC1R_WIDTH 1 /* ADC1R_TO_AIF1ADC1R */ +#define WM8995_AIF2DACR_TO_AIF1ADC1R 0x0001 /* AIF2DACR_TO_AIF1ADC1R */ +#define WM8995_AIF2DACR_TO_AIF1ADC1R_MASK 0x0001 /* AIF2DACR_TO_AIF1ADC1R */ +#define WM8995_AIF2DACR_TO_AIF1ADC1R_SHIFT 0 /* AIF2DACR_TO_AIF1ADC1R */ +#define WM8995_AIF2DACR_TO_AIF1ADC1R_WIDTH 1 /* AIF2DACR_TO_AIF1ADC1R */ + +/* + * R1544 (0x608) - AIF1 ADC2 Left Mixer Routing + */ +#define WM8995_ADC2L_TO_AIF1ADC2L 0x0002 /* ADC2L_TO_AIF1ADC2L */ +#define WM8995_ADC2L_TO_AIF1ADC2L_MASK 0x0002 /* ADC2L_TO_AIF1ADC2L */ +#define WM8995_ADC2L_TO_AIF1ADC2L_SHIFT 1 /* ADC2L_TO_AIF1ADC2L */ +#define WM8995_ADC2L_TO_AIF1ADC2L_WIDTH 1 /* ADC2L_TO_AIF1ADC2L */ +#define WM8995_AIF2DACL_TO_AIF1ADC2L 0x0001 /* AIF2DACL_TO_AIF1ADC2L */ +#define WM8995_AIF2DACL_TO_AIF1ADC2L_MASK 0x0001 /* AIF2DACL_TO_AIF1ADC2L */ +#define WM8995_AIF2DACL_TO_AIF1ADC2L_SHIFT 0 /* AIF2DACL_TO_AIF1ADC2L */ +#define WM8995_AIF2DACL_TO_AIF1ADC2L_WIDTH 1 /* AIF2DACL_TO_AIF1ADC2L */ + +/* + * R1545 (0x609) - AIF1 ADC2 Right mixer Routing + */ +#define WM8995_ADC2R_TO_AIF1ADC2R 0x0002 /* ADC2R_TO_AIF1ADC2R */ +#define WM8995_ADC2R_TO_AIF1ADC2R_MASK 0x0002 /* ADC2R_TO_AIF1ADC2R */ +#define WM8995_ADC2R_TO_AIF1ADC2R_SHIFT 1 /* ADC2R_TO_AIF1ADC2R */ +#define WM8995_ADC2R_TO_AIF1ADC2R_WIDTH 1 /* ADC2R_TO_AIF1ADC2R */ +#define WM8995_AIF2DACR_TO_AIF1ADC2R 0x0001 /* AIF2DACR_TO_AIF1ADC2R */ +#define WM8995_AIF2DACR_TO_AIF1ADC2R_MASK 0x0001 /* AIF2DACR_TO_AIF1ADC2R */ +#define WM8995_AIF2DACR_TO_AIF1ADC2R_SHIFT 0 /* AIF2DACR_TO_AIF1ADC2R */ +#define WM8995_AIF2DACR_TO_AIF1ADC2R_WIDTH 1 /* AIF2DACR_TO_AIF1ADC2R */ + +/* + * R1552 (0x610) - DAC Softmute + */ +#define WM8995_DAC_SOFTMUTEMODE 0x0002 /* DAC_SOFTMUTEMODE */ +#define WM8995_DAC_SOFTMUTEMODE_MASK 0x0002 /* DAC_SOFTMUTEMODE */ +#define WM8995_DAC_SOFTMUTEMODE_SHIFT 1 /* DAC_SOFTMUTEMODE */ +#define WM8995_DAC_SOFTMUTEMODE_WIDTH 1 /* DAC_SOFTMUTEMODE */ +#define WM8995_DAC_MUTERATE 0x0001 /* DAC_MUTERATE */ +#define WM8995_DAC_MUTERATE_MASK 0x0001 /* DAC_MUTERATE */ +#define WM8995_DAC_MUTERATE_SHIFT 0 /* DAC_MUTERATE */ +#define WM8995_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ + +/* + * R1568 (0x620) - Oversampling + */ +#define WM8995_ADC_OSR128 0x0002 /* ADC_OSR128 */ +#define WM8995_ADC_OSR128_MASK 0x0002 /* ADC_OSR128 */ +#define WM8995_ADC_OSR128_SHIFT 1 /* ADC_OSR128 */ +#define WM8995_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ +#define WM8995_DAC_OSR128 0x0001 /* DAC_OSR128 */ +#define WM8995_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */ +#define WM8995_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */ +#define WM8995_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ + +/* + * R1569 (0x621) - Sidetone + */ +#define WM8995_ST_LPF 0x1000 /* ST_LPF */ +#define WM8995_ST_LPF_MASK 0x1000 /* ST_LPF */ +#define WM8995_ST_LPF_SHIFT 12 /* ST_LPF */ +#define WM8995_ST_LPF_WIDTH 1 /* ST_LPF */ +#define WM8995_ST_HPF_CUT_MASK 0x0380 /* ST_HPF_CUT - [9:7] */ +#define WM8995_ST_HPF_CUT_SHIFT 7 /* ST_HPF_CUT - [9:7] */ +#define WM8995_ST_HPF_CUT_WIDTH 3 /* ST_HPF_CUT - [9:7] */ +#define WM8995_ST_HPF 0x0040 /* ST_HPF */ +#define WM8995_ST_HPF_MASK 0x0040 /* ST_HPF */ +#define WM8995_ST_HPF_SHIFT 6 /* ST_HPF */ +#define WM8995_ST_HPF_WIDTH 1 /* ST_HPF */ +#define WM8995_STR_SEL 0x0002 /* STR_SEL */ +#define WM8995_STR_SEL_MASK 0x0002 /* STR_SEL */ +#define WM8995_STR_SEL_SHIFT 1 /* STR_SEL */ +#define WM8995_STR_SEL_WIDTH 1 /* STR_SEL */ +#define WM8995_STL_SEL 0x0001 /* STL_SEL */ +#define WM8995_STL_SEL_MASK 0x0001 /* STL_SEL */ +#define WM8995_STL_SEL_SHIFT 0 /* STL_SEL */ +#define WM8995_STL_SEL_WIDTH 1 /* STL_SEL */ + +/* + * R1792 (0x700) - GPIO 1 + */ +#define WM8995_GP1_DIR 0x8000 /* GP1_DIR */ +#define WM8995_GP1_DIR_MASK 0x8000 /* GP1_DIR */ +#define WM8995_GP1_DIR_SHIFT 15 /* GP1_DIR */ +#define WM8995_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM8995_GP1_PU 0x4000 /* GP1_PU */ +#define WM8995_GP1_PU_MASK 0x4000 /* GP1_PU */ +#define WM8995_GP1_PU_SHIFT 14 /* GP1_PU */ +#define WM8995_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM8995_GP1_PD 0x2000 /* GP1_PD */ +#define WM8995_GP1_PD_MASK 0x2000 /* GP1_PD */ +#define WM8995_GP1_PD_SHIFT 13 /* GP1_PD */ +#define WM8995_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM8995_GP1_POL 0x0400 /* GP1_POL */ +#define WM8995_GP1_POL_MASK 0x0400 /* GP1_POL */ +#define WM8995_GP1_POL_SHIFT 10 /* GP1_POL */ +#define WM8995_GP1_POL_WIDTH 1 /* GP1_POL */ +#define WM8995_GP1_OP_CFG 0x0200 /* GP1_OP_CFG */ +#define WM8995_GP1_OP_CFG_MASK 0x0200 /* GP1_OP_CFG */ +#define WM8995_GP1_OP_CFG_SHIFT 9 /* GP1_OP_CFG */ +#define WM8995_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM8995_GP1_DB 0x0100 /* GP1_DB */ +#define WM8995_GP1_DB_MASK 0x0100 /* GP1_DB */ +#define WM8995_GP1_DB_SHIFT 8 /* GP1_DB */ +#define WM8995_GP1_DB_WIDTH 1 /* GP1_DB */ +#define WM8995_GP1_LVL 0x0040 /* GP1_LVL */ +#define WM8995_GP1_LVL_MASK 0x0040 /* GP1_LVL */ +#define WM8995_GP1_LVL_SHIFT 6 /* GP1_LVL */ +#define WM8995_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM8995_GP1_FN_MASK 0x001F /* GP1_FN - [4:0] */ +#define WM8995_GP1_FN_SHIFT 0 /* GP1_FN - [4:0] */ +#define WM8995_GP1_FN_WIDTH 5 /* GP1_FN - [4:0] */ + +/* + * R1793 (0x701) - GPIO 2 + */ +#define WM8995_GP2_DIR 0x8000 /* GP2_DIR */ +#define WM8995_GP2_DIR_MASK 0x8000 /* GP2_DIR */ +#define WM8995_GP2_DIR_SHIFT 15 /* GP2_DIR */ +#define WM8995_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM8995_GP2_PU 0x4000 /* GP2_PU */ +#define WM8995_GP2_PU_MASK 0x4000 /* GP2_PU */ +#define WM8995_GP2_PU_SHIFT 14 /* GP2_PU */ +#define WM8995_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM8995_GP2_PD 0x2000 /* GP2_PD */ +#define WM8995_GP2_PD_MASK 0x2000 /* GP2_PD */ +#define WM8995_GP2_PD_SHIFT 13 /* GP2_PD */ +#define WM8995_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM8995_GP2_POL 0x0400 /* GP2_POL */ +#define WM8995_GP2_POL_MASK 0x0400 /* GP2_POL */ +#define WM8995_GP2_POL_SHIFT 10 /* GP2_POL */ +#define WM8995_GP2_POL_WIDTH 1 /* GP2_POL */ +#define WM8995_GP2_OP_CFG 0x0200 /* GP2_OP_CFG */ +#define WM8995_GP2_OP_CFG_MASK 0x0200 /* GP2_OP_CFG */ +#define WM8995_GP2_OP_CFG_SHIFT 9 /* GP2_OP_CFG */ +#define WM8995_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM8995_GP2_DB 0x0100 /* GP2_DB */ +#define WM8995_GP2_DB_MASK 0x0100 /* GP2_DB */ +#define WM8995_GP2_DB_SHIFT 8 /* GP2_DB */ +#define WM8995_GP2_DB_WIDTH 1 /* GP2_DB */ +#define WM8995_GP2_LVL 0x0040 /* GP2_LVL */ +#define WM8995_GP2_LVL_MASK 0x0040 /* GP2_LVL */ +#define WM8995_GP2_LVL_SHIFT 6 /* GP2_LVL */ +#define WM8995_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8995_GP2_FN_MASK 0x001F /* GP2_FN - [4:0] */ +#define WM8995_GP2_FN_SHIFT 0 /* GP2_FN - [4:0] */ +#define WM8995_GP2_FN_WIDTH 5 /* GP2_FN - [4:0] */ + +/* + * R1794 (0x702) - GPIO 3 + */ +#define WM8995_GP3_DIR 0x8000 /* GP3_DIR */ +#define WM8995_GP3_DIR_MASK 0x8000 /* GP3_DIR */ +#define WM8995_GP3_DIR_SHIFT 15 /* GP3_DIR */ +#define WM8995_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM8995_GP3_PU 0x4000 /* GP3_PU */ +#define WM8995_GP3_PU_MASK 0x4000 /* GP3_PU */ +#define WM8995_GP3_PU_SHIFT 14 /* GP3_PU */ +#define WM8995_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM8995_GP3_PD 0x2000 /* GP3_PD */ +#define WM8995_GP3_PD_MASK 0x2000 /* GP3_PD */ +#define WM8995_GP3_PD_SHIFT 13 /* GP3_PD */ +#define WM8995_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM8995_GP3_POL 0x0400 /* GP3_POL */ +#define WM8995_GP3_POL_MASK 0x0400 /* GP3_POL */ +#define WM8995_GP3_POL_SHIFT 10 /* GP3_POL */ +#define WM8995_GP3_POL_WIDTH 1 /* GP3_POL */ +#define WM8995_GP3_OP_CFG 0x0200 /* GP3_OP_CFG */ +#define WM8995_GP3_OP_CFG_MASK 0x0200 /* GP3_OP_CFG */ +#define WM8995_GP3_OP_CFG_SHIFT 9 /* GP3_OP_CFG */ +#define WM8995_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM8995_GP3_DB 0x0100 /* GP3_DB */ +#define WM8995_GP3_DB_MASK 0x0100 /* GP3_DB */ +#define WM8995_GP3_DB_SHIFT 8 /* GP3_DB */ +#define WM8995_GP3_DB_WIDTH 1 /* GP3_DB */ +#define WM8995_GP3_LVL 0x0040 /* GP3_LVL */ +#define WM8995_GP3_LVL_MASK 0x0040 /* GP3_LVL */ +#define WM8995_GP3_LVL_SHIFT 6 /* GP3_LVL */ +#define WM8995_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8995_GP3_FN_MASK 0x001F /* GP3_FN - [4:0] */ +#define WM8995_GP3_FN_SHIFT 0 /* GP3_FN - [4:0] */ +#define WM8995_GP3_FN_WIDTH 5 /* GP3_FN - [4:0] */ + +/* + * R1795 (0x703) - GPIO 4 + */ +#define WM8995_GP4_DIR 0x8000 /* GP4_DIR */ +#define WM8995_GP4_DIR_MASK 0x8000 /* GP4_DIR */ +#define WM8995_GP4_DIR_SHIFT 15 /* GP4_DIR */ +#define WM8995_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM8995_GP4_PU 0x4000 /* GP4_PU */ +#define WM8995_GP4_PU_MASK 0x4000 /* GP4_PU */ +#define WM8995_GP4_PU_SHIFT 14 /* GP4_PU */ +#define WM8995_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM8995_GP4_PD 0x2000 /* GP4_PD */ +#define WM8995_GP4_PD_MASK 0x2000 /* GP4_PD */ +#define WM8995_GP4_PD_SHIFT 13 /* GP4_PD */ +#define WM8995_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM8995_GP4_POL 0x0400 /* GP4_POL */ +#define WM8995_GP4_POL_MASK 0x0400 /* GP4_POL */ +#define WM8995_GP4_POL_SHIFT 10 /* GP4_POL */ +#define WM8995_GP4_POL_WIDTH 1 /* GP4_POL */ +#define WM8995_GP4_OP_CFG 0x0200 /* GP4_OP_CFG */ +#define WM8995_GP4_OP_CFG_MASK 0x0200 /* GP4_OP_CFG */ +#define WM8995_GP4_OP_CFG_SHIFT 9 /* GP4_OP_CFG */ +#define WM8995_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM8995_GP4_DB 0x0100 /* GP4_DB */ +#define WM8995_GP4_DB_MASK 0x0100 /* GP4_DB */ +#define WM8995_GP4_DB_SHIFT 8 /* GP4_DB */ +#define WM8995_GP4_DB_WIDTH 1 /* GP4_DB */ +#define WM8995_GP4_LVL 0x0040 /* GP4_LVL */ +#define WM8995_GP4_LVL_MASK 0x0040 /* GP4_LVL */ +#define WM8995_GP4_LVL_SHIFT 6 /* GP4_LVL */ +#define WM8995_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM8995_GP4_FN_MASK 0x001F /* GP4_FN - [4:0] */ +#define WM8995_GP4_FN_SHIFT 0 /* GP4_FN - [4:0] */ +#define WM8995_GP4_FN_WIDTH 5 /* GP4_FN - [4:0] */ + +/* + * R1796 (0x704) - GPIO 5 + */ +#define WM8995_GP5_DIR 0x8000 /* GP5_DIR */ +#define WM8995_GP5_DIR_MASK 0x8000 /* GP5_DIR */ +#define WM8995_GP5_DIR_SHIFT 15 /* GP5_DIR */ +#define WM8995_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8995_GP5_PU 0x4000 /* GP5_PU */ +#define WM8995_GP5_PU_MASK 0x4000 /* GP5_PU */ +#define WM8995_GP5_PU_SHIFT 14 /* GP5_PU */ +#define WM8995_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8995_GP5_PD 0x2000 /* GP5_PD */ +#define WM8995_GP5_PD_MASK 0x2000 /* GP5_PD */ +#define WM8995_GP5_PD_SHIFT 13 /* GP5_PD */ +#define WM8995_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8995_GP5_POL 0x0400 /* GP5_POL */ +#define WM8995_GP5_POL_MASK 0x0400 /* GP5_POL */ +#define WM8995_GP5_POL_SHIFT 10 /* GP5_POL */ +#define WM8995_GP5_POL_WIDTH 1 /* GP5_POL */ +#define WM8995_GP5_OP_CFG 0x0200 /* GP5_OP_CFG */ +#define WM8995_GP5_OP_CFG_MASK 0x0200 /* GP5_OP_CFG */ +#define WM8995_GP5_OP_CFG_SHIFT 9 /* GP5_OP_CFG */ +#define WM8995_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8995_GP5_DB 0x0100 /* GP5_DB */ +#define WM8995_GP5_DB_MASK 0x0100 /* GP5_DB */ +#define WM8995_GP5_DB_SHIFT 8 /* GP5_DB */ +#define WM8995_GP5_DB_WIDTH 1 /* GP5_DB */ +#define WM8995_GP5_LVL 0x0040 /* GP5_LVL */ +#define WM8995_GP5_LVL_MASK 0x0040 /* GP5_LVL */ +#define WM8995_GP5_LVL_SHIFT 6 /* GP5_LVL */ +#define WM8995_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8995_GP5_FN_MASK 0x001F /* GP5_FN - [4:0] */ +#define WM8995_GP5_FN_SHIFT 0 /* GP5_FN - [4:0] */ +#define WM8995_GP5_FN_WIDTH 5 /* GP5_FN - [4:0] */ + +/* + * R1797 (0x705) - GPIO 6 + */ +#define WM8995_GP6_DIR 0x8000 /* GP6_DIR */ +#define WM8995_GP6_DIR_MASK 0x8000 /* GP6_DIR */ +#define WM8995_GP6_DIR_SHIFT 15 /* GP6_DIR */ +#define WM8995_GP6_DIR_WIDTH 1 /* GP6_DIR */ +#define WM8995_GP6_PU 0x4000 /* GP6_PU */ +#define WM8995_GP6_PU_MASK 0x4000 /* GP6_PU */ +#define WM8995_GP6_PU_SHIFT 14 /* GP6_PU */ +#define WM8995_GP6_PU_WIDTH 1 /* GP6_PU */ +#define WM8995_GP6_PD 0x2000 /* GP6_PD */ +#define WM8995_GP6_PD_MASK 0x2000 /* GP6_PD */ +#define WM8995_GP6_PD_SHIFT 13 /* GP6_PD */ +#define WM8995_GP6_PD_WIDTH 1 /* GP6_PD */ +#define WM8995_GP6_POL 0x0400 /* GP6_POL */ +#define WM8995_GP6_POL_MASK 0x0400 /* GP6_POL */ +#define WM8995_GP6_POL_SHIFT 10 /* GP6_POL */ +#define WM8995_GP6_POL_WIDTH 1 /* GP6_POL */ +#define WM8995_GP6_OP_CFG 0x0200 /* GP6_OP_CFG */ +#define WM8995_GP6_OP_CFG_MASK 0x0200 /* GP6_OP_CFG */ +#define WM8995_GP6_OP_CFG_SHIFT 9 /* GP6_OP_CFG */ +#define WM8995_GP6_OP_CFG_WIDTH 1 /* GP6_OP_CFG */ +#define WM8995_GP6_DB 0x0100 /* GP6_DB */ +#define WM8995_GP6_DB_MASK 0x0100 /* GP6_DB */ +#define WM8995_GP6_DB_SHIFT 8 /* GP6_DB */ +#define WM8995_GP6_DB_WIDTH 1 /* GP6_DB */ +#define WM8995_GP6_LVL 0x0040 /* GP6_LVL */ +#define WM8995_GP6_LVL_MASK 0x0040 /* GP6_LVL */ +#define WM8995_GP6_LVL_SHIFT 6 /* GP6_LVL */ +#define WM8995_GP6_LVL_WIDTH 1 /* GP6_LVL */ +#define WM8995_GP6_FN_MASK 0x001F /* GP6_FN - [4:0] */ +#define WM8995_GP6_FN_SHIFT 0 /* GP6_FN - [4:0] */ +#define WM8995_GP6_FN_WIDTH 5 /* GP6_FN - [4:0] */ + +/* + * R1798 (0x706) - GPIO 7 + */ +#define WM8995_GP7_DIR 0x8000 /* GP7_DIR */ +#define WM8995_GP7_DIR_MASK 0x8000 /* GP7_DIR */ +#define WM8995_GP7_DIR_SHIFT 15 /* GP7_DIR */ +#define WM8995_GP7_DIR_WIDTH 1 /* GP7_DIR */ +#define WM8995_GP7_PU 0x4000 /* GP7_PU */ +#define WM8995_GP7_PU_MASK 0x4000 /* GP7_PU */ +#define WM8995_GP7_PU_SHIFT 14 /* GP7_PU */ +#define WM8995_GP7_PU_WIDTH 1 /* GP7_PU */ +#define WM8995_GP7_PD 0x2000 /* GP7_PD */ +#define WM8995_GP7_PD_MASK 0x2000 /* GP7_PD */ +#define WM8995_GP7_PD_SHIFT 13 /* GP7_PD */ +#define WM8995_GP7_PD_WIDTH 1 /* GP7_PD */ +#define WM8995_GP7_POL 0x0400 /* GP7_POL */ +#define WM8995_GP7_POL_MASK 0x0400 /* GP7_POL */ +#define WM8995_GP7_POL_SHIFT 10 /* GP7_POL */ +#define WM8995_GP7_POL_WIDTH 1 /* GP7_POL */ +#define WM8995_GP7_OP_CFG 0x0200 /* GP7_OP_CFG */ +#define WM8995_GP7_OP_CFG_MASK 0x0200 /* GP7_OP_CFG */ +#define WM8995_GP7_OP_CFG_SHIFT 9 /* GP7_OP_CFG */ +#define WM8995_GP7_OP_CFG_WIDTH 1 /* GP7_OP_CFG */ +#define WM8995_GP7_DB 0x0100 /* GP7_DB */ +#define WM8995_GP7_DB_MASK 0x0100 /* GP7_DB */ +#define WM8995_GP7_DB_SHIFT 8 /* GP7_DB */ +#define WM8995_GP7_DB_WIDTH 1 /* GP7_DB */ +#define WM8995_GP7_LVL 0x0040 /* GP7_LVL */ +#define WM8995_GP7_LVL_MASK 0x0040 /* GP7_LVL */ +#define WM8995_GP7_LVL_SHIFT 6 /* GP7_LVL */ +#define WM8995_GP7_LVL_WIDTH 1 /* GP7_LVL */ +#define WM8995_GP7_FN_MASK 0x001F /* GP7_FN - [4:0] */ +#define WM8995_GP7_FN_SHIFT 0 /* GP7_FN - [4:0] */ +#define WM8995_GP7_FN_WIDTH 5 /* GP7_FN - [4:0] */ + +/* + * R1799 (0x707) - GPIO 8 + */ +#define WM8995_GP8_DIR 0x8000 /* GP8_DIR */ +#define WM8995_GP8_DIR_MASK 0x8000 /* GP8_DIR */ +#define WM8995_GP8_DIR_SHIFT 15 /* GP8_DIR */ +#define WM8995_GP8_DIR_WIDTH 1 /* GP8_DIR */ +#define WM8995_GP8_PU 0x4000 /* GP8_PU */ +#define WM8995_GP8_PU_MASK 0x4000 /* GP8_PU */ +#define WM8995_GP8_PU_SHIFT 14 /* GP8_PU */ +#define WM8995_GP8_PU_WIDTH 1 /* GP8_PU */ +#define WM8995_GP8_PD 0x2000 /* GP8_PD */ +#define WM8995_GP8_PD_MASK 0x2000 /* GP8_PD */ +#define WM8995_GP8_PD_SHIFT 13 /* GP8_PD */ +#define WM8995_GP8_PD_WIDTH 1 /* GP8_PD */ +#define WM8995_GP8_POL 0x0400 /* GP8_POL */ +#define WM8995_GP8_POL_MASK 0x0400 /* GP8_POL */ +#define WM8995_GP8_POL_SHIFT 10 /* GP8_POL */ +#define WM8995_GP8_POL_WIDTH 1 /* GP8_POL */ +#define WM8995_GP8_OP_CFG 0x0200 /* GP8_OP_CFG */ +#define WM8995_GP8_OP_CFG_MASK 0x0200 /* GP8_OP_CFG */ +#define WM8995_GP8_OP_CFG_SHIFT 9 /* GP8_OP_CFG */ +#define WM8995_GP8_OP_CFG_WIDTH 1 /* GP8_OP_CFG */ +#define WM8995_GP8_DB 0x0100 /* GP8_DB */ +#define WM8995_GP8_DB_MASK 0x0100 /* GP8_DB */ +#define WM8995_GP8_DB_SHIFT 8 /* GP8_DB */ +#define WM8995_GP8_DB_WIDTH 1 /* GP8_DB */ +#define WM8995_GP8_LVL 0x0040 /* GP8_LVL */ +#define WM8995_GP8_LVL_MASK 0x0040 /* GP8_LVL */ +#define WM8995_GP8_LVL_SHIFT 6 /* GP8_LVL */ +#define WM8995_GP8_LVL_WIDTH 1 /* GP8_LVL */ +#define WM8995_GP8_FN_MASK 0x001F /* GP8_FN - [4:0] */ +#define WM8995_GP8_FN_SHIFT 0 /* GP8_FN - [4:0] */ +#define WM8995_GP8_FN_WIDTH 5 /* GP8_FN - [4:0] */ + +/* + * R1800 (0x708) - GPIO 9 + */ +#define WM8995_GP9_DIR 0x8000 /* GP9_DIR */ +#define WM8995_GP9_DIR_MASK 0x8000 /* GP9_DIR */ +#define WM8995_GP9_DIR_SHIFT 15 /* GP9_DIR */ +#define WM8995_GP9_DIR_WIDTH 1 /* GP9_DIR */ +#define WM8995_GP9_PU 0x4000 /* GP9_PU */ +#define WM8995_GP9_PU_MASK 0x4000 /* GP9_PU */ +#define WM8995_GP9_PU_SHIFT 14 /* GP9_PU */ +#define WM8995_GP9_PU_WIDTH 1 /* GP9_PU */ +#define WM8995_GP9_PD 0x2000 /* GP9_PD */ +#define WM8995_GP9_PD_MASK 0x2000 /* GP9_PD */ +#define WM8995_GP9_PD_SHIFT 13 /* GP9_PD */ +#define WM8995_GP9_PD_WIDTH 1 /* GP9_PD */ +#define WM8995_GP9_POL 0x0400 /* GP9_POL */ +#define WM8995_GP9_POL_MASK 0x0400 /* GP9_POL */ +#define WM8995_GP9_POL_SHIFT 10 /* GP9_POL */ +#define WM8995_GP9_POL_WIDTH 1 /* GP9_POL */ +#define WM8995_GP9_OP_CFG 0x0200 /* GP9_OP_CFG */ +#define WM8995_GP9_OP_CFG_MASK 0x0200 /* GP9_OP_CFG */ +#define WM8995_GP9_OP_CFG_SHIFT 9 /* GP9_OP_CFG */ +#define WM8995_GP9_OP_CFG_WIDTH 1 /* GP9_OP_CFG */ +#define WM8995_GP9_DB 0x0100 /* GP9_DB */ +#define WM8995_GP9_DB_MASK 0x0100 /* GP9_DB */ +#define WM8995_GP9_DB_SHIFT 8 /* GP9_DB */ +#define WM8995_GP9_DB_WIDTH 1 /* GP9_DB */ +#define WM8995_GP9_LVL 0x0040 /* GP9_LVL */ +#define WM8995_GP9_LVL_MASK 0x0040 /* GP9_LVL */ +#define WM8995_GP9_LVL_SHIFT 6 /* GP9_LVL */ +#define WM8995_GP9_LVL_WIDTH 1 /* GP9_LVL */ +#define WM8995_GP9_FN_MASK 0x001F /* GP9_FN - [4:0] */ +#define WM8995_GP9_FN_SHIFT 0 /* GP9_FN - [4:0] */ +#define WM8995_GP9_FN_WIDTH 5 /* GP9_FN - [4:0] */ + +/* + * R1801 (0x709) - GPIO 10 + */ +#define WM8995_GP10_DIR 0x8000 /* GP10_DIR */ +#define WM8995_GP10_DIR_MASK 0x8000 /* GP10_DIR */ +#define WM8995_GP10_DIR_SHIFT 15 /* GP10_DIR */ +#define WM8995_GP10_DIR_WIDTH 1 /* GP10_DIR */ +#define WM8995_GP10_PU 0x4000 /* GP10_PU */ +#define WM8995_GP10_PU_MASK 0x4000 /* GP10_PU */ +#define WM8995_GP10_PU_SHIFT 14 /* GP10_PU */ +#define WM8995_GP10_PU_WIDTH 1 /* GP10_PU */ +#define WM8995_GP10_PD 0x2000 /* GP10_PD */ +#define WM8995_GP10_PD_MASK 0x2000 /* GP10_PD */ +#define WM8995_GP10_PD_SHIFT 13 /* GP10_PD */ +#define WM8995_GP10_PD_WIDTH 1 /* GP10_PD */ +#define WM8995_GP10_POL 0x0400 /* GP10_POL */ +#define WM8995_GP10_POL_MASK 0x0400 /* GP10_POL */ +#define WM8995_GP10_POL_SHIFT 10 /* GP10_POL */ +#define WM8995_GP10_POL_WIDTH 1 /* GP10_POL */ +#define WM8995_GP10_OP_CFG 0x0200 /* GP10_OP_CFG */ +#define WM8995_GP10_OP_CFG_MASK 0x0200 /* GP10_OP_CFG */ +#define WM8995_GP10_OP_CFG_SHIFT 9 /* GP10_OP_CFG */ +#define WM8995_GP10_OP_CFG_WIDTH 1 /* GP10_OP_CFG */ +#define WM8995_GP10_DB 0x0100 /* GP10_DB */ +#define WM8995_GP10_DB_MASK 0x0100 /* GP10_DB */ +#define WM8995_GP10_DB_SHIFT 8 /* GP10_DB */ +#define WM8995_GP10_DB_WIDTH 1 /* GP10_DB */ +#define WM8995_GP10_LVL 0x0040 /* GP10_LVL */ +#define WM8995_GP10_LVL_MASK 0x0040 /* GP10_LVL */ +#define WM8995_GP10_LVL_SHIFT 6 /* GP10_LVL */ +#define WM8995_GP10_LVL_WIDTH 1 /* GP10_LVL */ +#define WM8995_GP10_FN_MASK 0x001F /* GP10_FN - [4:0] */ +#define WM8995_GP10_FN_SHIFT 0 /* GP10_FN - [4:0] */ +#define WM8995_GP10_FN_WIDTH 5 /* GP10_FN - [4:0] */ + +/* + * R1802 (0x70A) - GPIO 11 + */ +#define WM8995_GP11_DIR 0x8000 /* GP11_DIR */ +#define WM8995_GP11_DIR_MASK 0x8000 /* GP11_DIR */ +#define WM8995_GP11_DIR_SHIFT 15 /* GP11_DIR */ +#define WM8995_GP11_DIR_WIDTH 1 /* GP11_DIR */ +#define WM8995_GP11_PU 0x4000 /* GP11_PU */ +#define WM8995_GP11_PU_MASK 0x4000 /* GP11_PU */ +#define WM8995_GP11_PU_SHIFT 14 /* GP11_PU */ +#define WM8995_GP11_PU_WIDTH 1 /* GP11_PU */ +#define WM8995_GP11_PD 0x2000 /* GP11_PD */ +#define WM8995_GP11_PD_MASK 0x2000 /* GP11_PD */ +#define WM8995_GP11_PD_SHIFT 13 /* GP11_PD */ +#define WM8995_GP11_PD_WIDTH 1 /* GP11_PD */ +#define WM8995_GP11_POL 0x0400 /* GP11_POL */ +#define WM8995_GP11_POL_MASK 0x0400 /* GP11_POL */ +#define WM8995_GP11_POL_SHIFT 10 /* GP11_POL */ +#define WM8995_GP11_POL_WIDTH 1 /* GP11_POL */ +#define WM8995_GP11_OP_CFG 0x0200 /* GP11_OP_CFG */ +#define WM8995_GP11_OP_CFG_MASK 0x0200 /* GP11_OP_CFG */ +#define WM8995_GP11_OP_CFG_SHIFT 9 /* GP11_OP_CFG */ +#define WM8995_GP11_OP_CFG_WIDTH 1 /* GP11_OP_CFG */ +#define WM8995_GP11_DB 0x0100 /* GP11_DB */ +#define WM8995_GP11_DB_MASK 0x0100 /* GP11_DB */ +#define WM8995_GP11_DB_SHIFT 8 /* GP11_DB */ +#define WM8995_GP11_DB_WIDTH 1 /* GP11_DB */ +#define WM8995_GP11_LVL 0x0040 /* GP11_LVL */ +#define WM8995_GP11_LVL_MASK 0x0040 /* GP11_LVL */ +#define WM8995_GP11_LVL_SHIFT 6 /* GP11_LVL */ +#define WM8995_GP11_LVL_WIDTH 1 /* GP11_LVL */ +#define WM8995_GP11_FN_MASK 0x001F /* GP11_FN - [4:0] */ +#define WM8995_GP11_FN_SHIFT 0 /* GP11_FN - [4:0] */ +#define WM8995_GP11_FN_WIDTH 5 /* GP11_FN - [4:0] */ + +/* + * R1803 (0x70B) - GPIO 12 + */ +#define WM8995_GP12_DIR 0x8000 /* GP12_DIR */ +#define WM8995_GP12_DIR_MASK 0x8000 /* GP12_DIR */ +#define WM8995_GP12_DIR_SHIFT 15 /* GP12_DIR */ +#define WM8995_GP12_DIR_WIDTH 1 /* GP12_DIR */ +#define WM8995_GP12_PU 0x4000 /* GP12_PU */ +#define WM8995_GP12_PU_MASK 0x4000 /* GP12_PU */ +#define WM8995_GP12_PU_SHIFT 14 /* GP12_PU */ +#define WM8995_GP12_PU_WIDTH 1 /* GP12_PU */ +#define WM8995_GP12_PD 0x2000 /* GP12_PD */ +#define WM8995_GP12_PD_MASK 0x2000 /* GP12_PD */ +#define WM8995_GP12_PD_SHIFT 13 /* GP12_PD */ +#define WM8995_GP12_PD_WIDTH 1 /* GP12_PD */ +#define WM8995_GP12_POL 0x0400 /* GP12_POL */ +#define WM8995_GP12_POL_MASK 0x0400 /* GP12_POL */ +#define WM8995_GP12_POL_SHIFT 10 /* GP12_POL */ +#define WM8995_GP12_POL_WIDTH 1 /* GP12_POL */ +#define WM8995_GP12_OP_CFG 0x0200 /* GP12_OP_CFG */ +#define WM8995_GP12_OP_CFG_MASK 0x0200 /* GP12_OP_CFG */ +#define WM8995_GP12_OP_CFG_SHIFT 9 /* GP12_OP_CFG */ +#define WM8995_GP12_OP_CFG_WIDTH 1 /* GP12_OP_CFG */ +#define WM8995_GP12_DB 0x0100 /* GP12_DB */ +#define WM8995_GP12_DB_MASK 0x0100 /* GP12_DB */ +#define WM8995_GP12_DB_SHIFT 8 /* GP12_DB */ +#define WM8995_GP12_DB_WIDTH 1 /* GP12_DB */ +#define WM8995_GP12_LVL 0x0040 /* GP12_LVL */ +#define WM8995_GP12_LVL_MASK 0x0040 /* GP12_LVL */ +#define WM8995_GP12_LVL_SHIFT 6 /* GP12_LVL */ +#define WM8995_GP12_LVL_WIDTH 1 /* GP12_LVL */ +#define WM8995_GP12_FN_MASK 0x001F /* GP12_FN - [4:0] */ +#define WM8995_GP12_FN_SHIFT 0 /* GP12_FN - [4:0] */ +#define WM8995_GP12_FN_WIDTH 5 /* GP12_FN - [4:0] */ + +/* + * R1804 (0x70C) - GPIO 13 + */ +#define WM8995_GP13_DIR 0x8000 /* GP13_DIR */ +#define WM8995_GP13_DIR_MASK 0x8000 /* GP13_DIR */ +#define WM8995_GP13_DIR_SHIFT 15 /* GP13_DIR */ +#define WM8995_GP13_DIR_WIDTH 1 /* GP13_DIR */ +#define WM8995_GP13_PU 0x4000 /* GP13_PU */ +#define WM8995_GP13_PU_MASK 0x4000 /* GP13_PU */ +#define WM8995_GP13_PU_SHIFT 14 /* GP13_PU */ +#define WM8995_GP13_PU_WIDTH 1 /* GP13_PU */ +#define WM8995_GP13_PD 0x2000 /* GP13_PD */ +#define WM8995_GP13_PD_MASK 0x2000 /* GP13_PD */ +#define WM8995_GP13_PD_SHIFT 13 /* GP13_PD */ +#define WM8995_GP13_PD_WIDTH 1 /* GP13_PD */ +#define WM8995_GP13_POL 0x0400 /* GP13_POL */ +#define WM8995_GP13_POL_MASK 0x0400 /* GP13_POL */ +#define WM8995_GP13_POL_SHIFT 10 /* GP13_POL */ +#define WM8995_GP13_POL_WIDTH 1 /* GP13_POL */ +#define WM8995_GP13_OP_CFG 0x0200 /* GP13_OP_CFG */ +#define WM8995_GP13_OP_CFG_MASK 0x0200 /* GP13_OP_CFG */ +#define WM8995_GP13_OP_CFG_SHIFT 9 /* GP13_OP_CFG */ +#define WM8995_GP13_OP_CFG_WIDTH 1 /* GP13_OP_CFG */ +#define WM8995_GP13_DB 0x0100 /* GP13_DB */ +#define WM8995_GP13_DB_MASK 0x0100 /* GP13_DB */ +#define WM8995_GP13_DB_SHIFT 8 /* GP13_DB */ +#define WM8995_GP13_DB_WIDTH 1 /* GP13_DB */ +#define WM8995_GP13_LVL 0x0040 /* GP13_LVL */ +#define WM8995_GP13_LVL_MASK 0x0040 /* GP13_LVL */ +#define WM8995_GP13_LVL_SHIFT 6 /* GP13_LVL */ +#define WM8995_GP13_LVL_WIDTH 1 /* GP13_LVL */ +#define WM8995_GP13_FN_MASK 0x001F /* GP13_FN - [4:0] */ +#define WM8995_GP13_FN_SHIFT 0 /* GP13_FN - [4:0] */ +#define WM8995_GP13_FN_WIDTH 5 /* GP13_FN - [4:0] */ + +/* + * R1805 (0x70D) - GPIO 14 + */ +#define WM8995_GP14_DIR 0x8000 /* GP14_DIR */ +#define WM8995_GP14_DIR_MASK 0x8000 /* GP14_DIR */ +#define WM8995_GP14_DIR_SHIFT 15 /* GP14_DIR */ +#define WM8995_GP14_DIR_WIDTH 1 /* GP14_DIR */ +#define WM8995_GP14_PU 0x4000 /* GP14_PU */ +#define WM8995_GP14_PU_MASK 0x4000 /* GP14_PU */ +#define WM8995_GP14_PU_SHIFT 14 /* GP14_PU */ +#define WM8995_GP14_PU_WIDTH 1 /* GP14_PU */ +#define WM8995_GP14_PD 0x2000 /* GP14_PD */ +#define WM8995_GP14_PD_MASK 0x2000 /* GP14_PD */ +#define WM8995_GP14_PD_SHIFT 13 /* GP14_PD */ +#define WM8995_GP14_PD_WIDTH 1 /* GP14_PD */ +#define WM8995_GP14_POL 0x0400 /* GP14_POL */ +#define WM8995_GP14_POL_MASK 0x0400 /* GP14_POL */ +#define WM8995_GP14_POL_SHIFT 10 /* GP14_POL */ +#define WM8995_GP14_POL_WIDTH 1 /* GP14_POL */ +#define WM8995_GP14_OP_CFG 0x0200 /* GP14_OP_CFG */ +#define WM8995_GP14_OP_CFG_MASK 0x0200 /* GP14_OP_CFG */ +#define WM8995_GP14_OP_CFG_SHIFT 9 /* GP14_OP_CFG */ +#define WM8995_GP14_OP_CFG_WIDTH 1 /* GP14_OP_CFG */ +#define WM8995_GP14_DB 0x0100 /* GP14_DB */ +#define WM8995_GP14_DB_MASK 0x0100 /* GP14_DB */ +#define WM8995_GP14_DB_SHIFT 8 /* GP14_DB */ +#define WM8995_GP14_DB_WIDTH 1 /* GP14_DB */ +#define WM8995_GP14_LVL 0x0040 /* GP14_LVL */ +#define WM8995_GP14_LVL_MASK 0x0040 /* GP14_LVL */ +#define WM8995_GP14_LVL_SHIFT 6 /* GP14_LVL */ +#define WM8995_GP14_LVL_WIDTH 1 /* GP14_LVL */ +#define WM8995_GP14_FN_MASK 0x001F /* GP14_FN - [4:0] */ +#define WM8995_GP14_FN_SHIFT 0 /* GP14_FN - [4:0] */ +#define WM8995_GP14_FN_WIDTH 5 /* GP14_FN - [4:0] */ + +/* + * R1824 (0x720) - Pull Control (1) + */ +#define WM8995_DMICDAT3_PD 0x4000 /* DMICDAT3_PD */ +#define WM8995_DMICDAT3_PD_MASK 0x4000 /* DMICDAT3_PD */ +#define WM8995_DMICDAT3_PD_SHIFT 14 /* DMICDAT3_PD */ +#define WM8995_DMICDAT3_PD_WIDTH 1 /* DMICDAT3_PD */ +#define WM8995_DMICDAT2_PD 0x1000 /* DMICDAT2_PD */ +#define WM8995_DMICDAT2_PD_MASK 0x1000 /* DMICDAT2_PD */ +#define WM8995_DMICDAT2_PD_SHIFT 12 /* DMICDAT2_PD */ +#define WM8995_DMICDAT2_PD_WIDTH 1 /* DMICDAT2_PD */ +#define WM8995_DMICDAT1_PD 0x0400 /* DMICDAT1_PD */ +#define WM8995_DMICDAT1_PD_MASK 0x0400 /* DMICDAT1_PD */ +#define WM8995_DMICDAT1_PD_SHIFT 10 /* DMICDAT1_PD */ +#define WM8995_DMICDAT1_PD_WIDTH 1 /* DMICDAT1_PD */ +#define WM8995_MCLK2_PU 0x0200 /* MCLK2_PU */ +#define WM8995_MCLK2_PU_MASK 0x0200 /* MCLK2_PU */ +#define WM8995_MCLK2_PU_SHIFT 9 /* MCLK2_PU */ +#define WM8995_MCLK2_PU_WIDTH 1 /* MCLK2_PU */ +#define WM8995_MCLK2_PD 0x0100 /* MCLK2_PD */ +#define WM8995_MCLK2_PD_MASK 0x0100 /* MCLK2_PD */ +#define WM8995_MCLK2_PD_SHIFT 8 /* MCLK2_PD */ +#define WM8995_MCLK2_PD_WIDTH 1 /* MCLK2_PD */ +#define WM8995_MCLK1_PU 0x0080 /* MCLK1_PU */ +#define WM8995_MCLK1_PU_MASK 0x0080 /* MCLK1_PU */ +#define WM8995_MCLK1_PU_SHIFT 7 /* MCLK1_PU */ +#define WM8995_MCLK1_PU_WIDTH 1 /* MCLK1_PU */ +#define WM8995_MCLK1_PD 0x0040 /* MCLK1_PD */ +#define WM8995_MCLK1_PD_MASK 0x0040 /* MCLK1_PD */ +#define WM8995_MCLK1_PD_SHIFT 6 /* MCLK1_PD */ +#define WM8995_MCLK1_PD_WIDTH 1 /* MCLK1_PD */ +#define WM8995_DACDAT1_PU 0x0020 /* DACDAT1_PU */ +#define WM8995_DACDAT1_PU_MASK 0x0020 /* DACDAT1_PU */ +#define WM8995_DACDAT1_PU_SHIFT 5 /* DACDAT1_PU */ +#define WM8995_DACDAT1_PU_WIDTH 1 /* DACDAT1_PU */ +#define WM8995_DACDAT1_PD 0x0010 /* DACDAT1_PD */ +#define WM8995_DACDAT1_PD_MASK 0x0010 /* DACDAT1_PD */ +#define WM8995_DACDAT1_PD_SHIFT 4 /* DACDAT1_PD */ +#define WM8995_DACDAT1_PD_WIDTH 1 /* DACDAT1_PD */ +#define WM8995_DACLRCLK1_PU 0x0008 /* DACLRCLK1_PU */ +#define WM8995_DACLRCLK1_PU_MASK 0x0008 /* DACLRCLK1_PU */ +#define WM8995_DACLRCLK1_PU_SHIFT 3 /* DACLRCLK1_PU */ +#define WM8995_DACLRCLK1_PU_WIDTH 1 /* DACLRCLK1_PU */ +#define WM8995_DACLRCLK1_PD 0x0004 /* DACLRCLK1_PD */ +#define WM8995_DACLRCLK1_PD_MASK 0x0004 /* DACLRCLK1_PD */ +#define WM8995_DACLRCLK1_PD_SHIFT 2 /* DACLRCLK1_PD */ +#define WM8995_DACLRCLK1_PD_WIDTH 1 /* DACLRCLK1_PD */ +#define WM8995_BCLK1_PU 0x0002 /* BCLK1_PU */ +#define WM8995_BCLK1_PU_MASK 0x0002 /* BCLK1_PU */ +#define WM8995_BCLK1_PU_SHIFT 1 /* BCLK1_PU */ +#define WM8995_BCLK1_PU_WIDTH 1 /* BCLK1_PU */ +#define WM8995_BCLK1_PD 0x0001 /* BCLK1_PD */ +#define WM8995_BCLK1_PD_MASK 0x0001 /* BCLK1_PD */ +#define WM8995_BCLK1_PD_SHIFT 0 /* BCLK1_PD */ +#define WM8995_BCLK1_PD_WIDTH 1 /* BCLK1_PD */ + +/* + * R1825 (0x721) - Pull Control (2) + */ +#define WM8995_LDO1ENA_PD 0x0010 /* LDO1ENA_PD */ +#define WM8995_LDO1ENA_PD_MASK 0x0010 /* LDO1ENA_PD */ +#define WM8995_LDO1ENA_PD_SHIFT 4 /* LDO1ENA_PD */ +#define WM8995_LDO1ENA_PD_WIDTH 1 /* LDO1ENA_PD */ +#define WM8995_MODE_PD 0x0004 /* MODE_PD */ +#define WM8995_MODE_PD_MASK 0x0004 /* MODE_PD */ +#define WM8995_MODE_PD_SHIFT 2 /* MODE_PD */ +#define WM8995_MODE_PD_WIDTH 1 /* MODE_PD */ +#define WM8995_CSNADDR_PD 0x0001 /* CSNADDR_PD */ +#define WM8995_CSNADDR_PD_MASK 0x0001 /* CSNADDR_PD */ +#define WM8995_CSNADDR_PD_SHIFT 0 /* CSNADDR_PD */ +#define WM8995_CSNADDR_PD_WIDTH 1 /* CSNADDR_PD */ + +/* + * R1840 (0x730) - Interrupt Status 1 + */ +#define WM8995_GP14_EINT 0x2000 /* GP14_EINT */ +#define WM8995_GP14_EINT_MASK 0x2000 /* GP14_EINT */ +#define WM8995_GP14_EINT_SHIFT 13 /* GP14_EINT */ +#define WM8995_GP14_EINT_WIDTH 1 /* GP14_EINT */ +#define WM8995_GP13_EINT 0x1000 /* GP13_EINT */ +#define WM8995_GP13_EINT_MASK 0x1000 /* GP13_EINT */ +#define WM8995_GP13_EINT_SHIFT 12 /* GP13_EINT */ +#define WM8995_GP13_EINT_WIDTH 1 /* GP13_EINT */ +#define WM8995_GP12_EINT 0x0800 /* GP12_EINT */ +#define WM8995_GP12_EINT_MASK 0x0800 /* GP12_EINT */ +#define WM8995_GP12_EINT_SHIFT 11 /* GP12_EINT */ +#define WM8995_GP12_EINT_WIDTH 1 /* GP12_EINT */ +#define WM8995_GP11_EINT 0x0400 /* GP11_EINT */ +#define WM8995_GP11_EINT_MASK 0x0400 /* GP11_EINT */ +#define WM8995_GP11_EINT_SHIFT 10 /* GP11_EINT */ +#define WM8995_GP11_EINT_WIDTH 1 /* GP11_EINT */ +#define WM8995_GP10_EINT 0x0200 /* GP10_EINT */ +#define WM8995_GP10_EINT_MASK 0x0200 /* GP10_EINT */ +#define WM8995_GP10_EINT_SHIFT 9 /* GP10_EINT */ +#define WM8995_GP10_EINT_WIDTH 1 /* GP10_EINT */ +#define WM8995_GP9_EINT 0x0100 /* GP9_EINT */ +#define WM8995_GP9_EINT_MASK 0x0100 /* GP9_EINT */ +#define WM8995_GP9_EINT_SHIFT 8 /* GP9_EINT */ +#define WM8995_GP9_EINT_WIDTH 1 /* GP9_EINT */ +#define WM8995_GP8_EINT 0x0080 /* GP8_EINT */ +#define WM8995_GP8_EINT_MASK 0x0080 /* GP8_EINT */ +#define WM8995_GP8_EINT_SHIFT 7 /* GP8_EINT */ +#define WM8995_GP8_EINT_WIDTH 1 /* GP8_EINT */ +#define WM8995_GP7_EINT 0x0040 /* GP7_EINT */ +#define WM8995_GP7_EINT_MASK 0x0040 /* GP7_EINT */ +#define WM8995_GP7_EINT_SHIFT 6 /* GP7_EINT */ +#define WM8995_GP7_EINT_WIDTH 1 /* GP7_EINT */ +#define WM8995_GP6_EINT 0x0020 /* GP6_EINT */ +#define WM8995_GP6_EINT_MASK 0x0020 /* GP6_EINT */ +#define WM8995_GP6_EINT_SHIFT 5 /* GP6_EINT */ +#define WM8995_GP6_EINT_WIDTH 1 /* GP6_EINT */ +#define WM8995_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM8995_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM8995_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM8995_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM8995_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM8995_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM8995_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM8995_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM8995_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM8995_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM8995_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM8995_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM8995_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM8995_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM8995_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM8995_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM8995_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM8995_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM8995_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM8995_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R1841 (0x731) - Interrupt Status 2 + */ +#define WM8995_DCS_DONE_23_EINT 0x1000 /* DCS_DONE_23_EINT */ +#define WM8995_DCS_DONE_23_EINT_MASK 0x1000 /* DCS_DONE_23_EINT */ +#define WM8995_DCS_DONE_23_EINT_SHIFT 12 /* DCS_DONE_23_EINT */ +#define WM8995_DCS_DONE_23_EINT_WIDTH 1 /* DCS_DONE_23_EINT */ +#define WM8995_DCS_DONE_01_EINT 0x0800 /* DCS_DONE_01_EINT */ +#define WM8995_DCS_DONE_01_EINT_MASK 0x0800 /* DCS_DONE_01_EINT */ +#define WM8995_DCS_DONE_01_EINT_SHIFT 11 /* DCS_DONE_01_EINT */ +#define WM8995_DCS_DONE_01_EINT_WIDTH 1 /* DCS_DONE_01_EINT */ +#define WM8995_WSEQ_DONE_EINT 0x0400 /* WSEQ_DONE_EINT */ +#define WM8995_WSEQ_DONE_EINT_MASK 0x0400 /* WSEQ_DONE_EINT */ +#define WM8995_WSEQ_DONE_EINT_SHIFT 10 /* WSEQ_DONE_EINT */ +#define WM8995_WSEQ_DONE_EINT_WIDTH 1 /* WSEQ_DONE_EINT */ +#define WM8995_FIFOS_ERR_EINT 0x0200 /* FIFOS_ERR_EINT */ +#define WM8995_FIFOS_ERR_EINT_MASK 0x0200 /* FIFOS_ERR_EINT */ +#define WM8995_FIFOS_ERR_EINT_SHIFT 9 /* FIFOS_ERR_EINT */ +#define WM8995_FIFOS_ERR_EINT_WIDTH 1 /* FIFOS_ERR_EINT */ +#define WM8995_AIF2DRC_SIG_DET_EINT 0x0100 /* AIF2DRC_SIG_DET_EINT */ +#define WM8995_AIF2DRC_SIG_DET_EINT_MASK 0x0100 /* AIF2DRC_SIG_DET_EINT */ +#define WM8995_AIF2DRC_SIG_DET_EINT_SHIFT 8 /* AIF2DRC_SIG_DET_EINT */ +#define WM8995_AIF2DRC_SIG_DET_EINT_WIDTH 1 /* AIF2DRC_SIG_DET_EINT */ +#define WM8995_AIF1DRC2_SIG_DET_EINT 0x0080 /* AIF1DRC2_SIG_DET_EINT */ +#define WM8995_AIF1DRC2_SIG_DET_EINT_MASK 0x0080 /* AIF1DRC2_SIG_DET_EINT */ +#define WM8995_AIF1DRC2_SIG_DET_EINT_SHIFT 7 /* AIF1DRC2_SIG_DET_EINT */ +#define WM8995_AIF1DRC2_SIG_DET_EINT_WIDTH 1 /* AIF1DRC2_SIG_DET_EINT */ +#define WM8995_AIF1DRC1_SIG_DET_EINT 0x0040 /* AIF1DRC1_SIG_DET_EINT */ +#define WM8995_AIF1DRC1_SIG_DET_EINT_MASK 0x0040 /* AIF1DRC1_SIG_DET_EINT */ +#define WM8995_AIF1DRC1_SIG_DET_EINT_SHIFT 6 /* AIF1DRC1_SIG_DET_EINT */ +#define WM8995_AIF1DRC1_SIG_DET_EINT_WIDTH 1 /* AIF1DRC1_SIG_DET_EINT */ +#define WM8995_SRC2_LOCK_EINT 0x0020 /* SRC2_LOCK_EINT */ +#define WM8995_SRC2_LOCK_EINT_MASK 0x0020 /* SRC2_LOCK_EINT */ +#define WM8995_SRC2_LOCK_EINT_SHIFT 5 /* SRC2_LOCK_EINT */ +#define WM8995_SRC2_LOCK_EINT_WIDTH 1 /* SRC2_LOCK_EINT */ +#define WM8995_SRC1_LOCK_EINT 0x0010 /* SRC1_LOCK_EINT */ +#define WM8995_SRC1_LOCK_EINT_MASK 0x0010 /* SRC1_LOCK_EINT */ +#define WM8995_SRC1_LOCK_EINT_SHIFT 4 /* SRC1_LOCK_EINT */ +#define WM8995_SRC1_LOCK_EINT_WIDTH 1 /* SRC1_LOCK_EINT */ +#define WM8995_FLL2_LOCK_EINT 0x0008 /* FLL2_LOCK_EINT */ +#define WM8995_FLL2_LOCK_EINT_MASK 0x0008 /* FLL2_LOCK_EINT */ +#define WM8995_FLL2_LOCK_EINT_SHIFT 3 /* FLL2_LOCK_EINT */ +#define WM8995_FLL2_LOCK_EINT_WIDTH 1 /* FLL2_LOCK_EINT */ +#define WM8995_FLL1_LOCK_EINT 0x0004 /* FLL1_LOCK_EINT */ +#define WM8995_FLL1_LOCK_EINT_MASK 0x0004 /* FLL1_LOCK_EINT */ +#define WM8995_FLL1_LOCK_EINT_SHIFT 2 /* FLL1_LOCK_EINT */ +#define WM8995_FLL1_LOCK_EINT_WIDTH 1 /* FLL1_LOCK_EINT */ +#define WM8995_HP_DONE_EINT 0x0002 /* HP_DONE_EINT */ +#define WM8995_HP_DONE_EINT_MASK 0x0002 /* HP_DONE_EINT */ +#define WM8995_HP_DONE_EINT_SHIFT 1 /* HP_DONE_EINT */ +#define WM8995_HP_DONE_EINT_WIDTH 1 /* HP_DONE_EINT */ +#define WM8995_MICD_EINT 0x0001 /* MICD_EINT */ +#define WM8995_MICD_EINT_MASK 0x0001 /* MICD_EINT */ +#define WM8995_MICD_EINT_SHIFT 0 /* MICD_EINT */ +#define WM8995_MICD_EINT_WIDTH 1 /* MICD_EINT */ + +/* + * R1842 (0x732) - Interrupt Raw Status 2 + */ +#define WM8995_DCS_DONE_23_STS 0x1000 /* DCS_DONE_23_STS */ +#define WM8995_DCS_DONE_23_STS_MASK 0x1000 /* DCS_DONE_23_STS */ +#define WM8995_DCS_DONE_23_STS_SHIFT 12 /* DCS_DONE_23_STS */ +#define WM8995_DCS_DONE_23_STS_WIDTH 1 /* DCS_DONE_23_STS */ +#define WM8995_DCS_DONE_01_STS 0x0800 /* DCS_DONE_01_STS */ +#define WM8995_DCS_DONE_01_STS_MASK 0x0800 /* DCS_DONE_01_STS */ +#define WM8995_DCS_DONE_01_STS_SHIFT 11 /* DCS_DONE_01_STS */ +#define WM8995_DCS_DONE_01_STS_WIDTH 1 /* DCS_DONE_01_STS */ +#define WM8995_WSEQ_DONE_STS 0x0400 /* WSEQ_DONE_STS */ +#define WM8995_WSEQ_DONE_STS_MASK 0x0400 /* WSEQ_DONE_STS */ +#define WM8995_WSEQ_DONE_STS_SHIFT 10 /* WSEQ_DONE_STS */ +#define WM8995_WSEQ_DONE_STS_WIDTH 1 /* WSEQ_DONE_STS */ +#define WM8995_FIFOS_ERR_STS 0x0200 /* FIFOS_ERR_STS */ +#define WM8995_FIFOS_ERR_STS_MASK 0x0200 /* FIFOS_ERR_STS */ +#define WM8995_FIFOS_ERR_STS_SHIFT 9 /* FIFOS_ERR_STS */ +#define WM8995_FIFOS_ERR_STS_WIDTH 1 /* FIFOS_ERR_STS */ +#define WM8995_AIF2DRC_SIG_DET_STS 0x0100 /* AIF2DRC_SIG_DET_STS */ +#define WM8995_AIF2DRC_SIG_DET_STS_MASK 0x0100 /* AIF2DRC_SIG_DET_STS */ +#define WM8995_AIF2DRC_SIG_DET_STS_SHIFT 8 /* AIF2DRC_SIG_DET_STS */ +#define WM8995_AIF2DRC_SIG_DET_STS_WIDTH 1 /* AIF2DRC_SIG_DET_STS */ +#define WM8995_AIF1DRC2_SIG_DET_STS 0x0080 /* AIF1DRC2_SIG_DET_STS */ +#define WM8995_AIF1DRC2_SIG_DET_STS_MASK 0x0080 /* AIF1DRC2_SIG_DET_STS */ +#define WM8995_AIF1DRC2_SIG_DET_STS_SHIFT 7 /* AIF1DRC2_SIG_DET_STS */ +#define WM8995_AIF1DRC2_SIG_DET_STS_WIDTH 1 /* AIF1DRC2_SIG_DET_STS */ +#define WM8995_AIF1DRC1_SIG_DET_STS 0x0040 /* AIF1DRC1_SIG_DET_STS */ +#define WM8995_AIF1DRC1_SIG_DET_STS_MASK 0x0040 /* AIF1DRC1_SIG_DET_STS */ +#define WM8995_AIF1DRC1_SIG_DET_STS_SHIFT 6 /* AIF1DRC1_SIG_DET_STS */ +#define WM8995_AIF1DRC1_SIG_DET_STS_WIDTH 1 /* AIF1DRC1_SIG_DET_STS */ +#define WM8995_SRC2_LOCK_STS 0x0020 /* SRC2_LOCK_STS */ +#define WM8995_SRC2_LOCK_STS_MASK 0x0020 /* SRC2_LOCK_STS */ +#define WM8995_SRC2_LOCK_STS_SHIFT 5 /* SRC2_LOCK_STS */ +#define WM8995_SRC2_LOCK_STS_WIDTH 1 /* SRC2_LOCK_STS */ +#define WM8995_SRC1_LOCK_STS 0x0010 /* SRC1_LOCK_STS */ +#define WM8995_SRC1_LOCK_STS_MASK 0x0010 /* SRC1_LOCK_STS */ +#define WM8995_SRC1_LOCK_STS_SHIFT 4 /* SRC1_LOCK_STS */ +#define WM8995_SRC1_LOCK_STS_WIDTH 1 /* SRC1_LOCK_STS */ +#define WM8995_FLL2_LOCK_STS 0x0008 /* FLL2_LOCK_STS */ +#define WM8995_FLL2_LOCK_STS_MASK 0x0008 /* FLL2_LOCK_STS */ +#define WM8995_FLL2_LOCK_STS_SHIFT 3 /* FLL2_LOCK_STS */ +#define WM8995_FLL2_LOCK_STS_WIDTH 1 /* FLL2_LOCK_STS */ +#define WM8995_FLL1_LOCK_STS 0x0004 /* FLL1_LOCK_STS */ +#define WM8995_FLL1_LOCK_STS_MASK 0x0004 /* FLL1_LOCK_STS */ +#define WM8995_FLL1_LOCK_STS_SHIFT 2 /* FLL1_LOCK_STS */ +#define WM8995_FLL1_LOCK_STS_WIDTH 1 /* FLL1_LOCK_STS */ + +/* + * R1848 (0x738) - Interrupt Status 1 Mask + */ +#define WM8995_IM_GP14_EINT 0x2000 /* IM_GP14_EINT */ +#define WM8995_IM_GP14_EINT_MASK 0x2000 /* IM_GP14_EINT */ +#define WM8995_IM_GP14_EINT_SHIFT 13 /* IM_GP14_EINT */ +#define WM8995_IM_GP14_EINT_WIDTH 1 /* IM_GP14_EINT */ +#define WM8995_IM_GP13_EINT 0x1000 /* IM_GP13_EINT */ +#define WM8995_IM_GP13_EINT_MASK 0x1000 /* IM_GP13_EINT */ +#define WM8995_IM_GP13_EINT_SHIFT 12 /* IM_GP13_EINT */ +#define WM8995_IM_GP13_EINT_WIDTH 1 /* IM_GP13_EINT */ +#define WM8995_IM_GP12_EINT 0x0800 /* IM_GP12_EINT */ +#define WM8995_IM_GP12_EINT_MASK 0x0800 /* IM_GP12_EINT */ +#define WM8995_IM_GP12_EINT_SHIFT 11 /* IM_GP12_EINT */ +#define WM8995_IM_GP12_EINT_WIDTH 1 /* IM_GP12_EINT */ +#define WM8995_IM_GP11_EINT 0x0400 /* IM_GP11_EINT */ +#define WM8995_IM_GP11_EINT_MASK 0x0400 /* IM_GP11_EINT */ +#define WM8995_IM_GP11_EINT_SHIFT 10 /* IM_GP11_EINT */ +#define WM8995_IM_GP11_EINT_WIDTH 1 /* IM_GP11_EINT */ +#define WM8995_IM_GP10_EINT 0x0200 /* IM_GP10_EINT */ +#define WM8995_IM_GP10_EINT_MASK 0x0200 /* IM_GP10_EINT */ +#define WM8995_IM_GP10_EINT_SHIFT 9 /* IM_GP10_EINT */ +#define WM8995_IM_GP10_EINT_WIDTH 1 /* IM_GP10_EINT */ +#define WM8995_IM_GP9_EINT 0x0100 /* IM_GP9_EINT */ +#define WM8995_IM_GP9_EINT_MASK 0x0100 /* IM_GP9_EINT */ +#define WM8995_IM_GP9_EINT_SHIFT 8 /* IM_GP9_EINT */ +#define WM8995_IM_GP9_EINT_WIDTH 1 /* IM_GP9_EINT */ +#define WM8995_IM_GP8_EINT 0x0080 /* IM_GP8_EINT */ +#define WM8995_IM_GP8_EINT_MASK 0x0080 /* IM_GP8_EINT */ +#define WM8995_IM_GP8_EINT_SHIFT 7 /* IM_GP8_EINT */ +#define WM8995_IM_GP8_EINT_WIDTH 1 /* IM_GP8_EINT */ +#define WM8995_IM_GP7_EINT 0x0040 /* IM_GP7_EINT */ +#define WM8995_IM_GP7_EINT_MASK 0x0040 /* IM_GP7_EINT */ +#define WM8995_IM_GP7_EINT_SHIFT 6 /* IM_GP7_EINT */ +#define WM8995_IM_GP7_EINT_WIDTH 1 /* IM_GP7_EINT */ +#define WM8995_IM_GP6_EINT 0x0020 /* IM_GP6_EINT */ +#define WM8995_IM_GP6_EINT_MASK 0x0020 /* IM_GP6_EINT */ +#define WM8995_IM_GP6_EINT_SHIFT 5 /* IM_GP6_EINT */ +#define WM8995_IM_GP6_EINT_WIDTH 1 /* IM_GP6_EINT */ +#define WM8995_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM8995_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM8995_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM8995_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM8995_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM8995_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM8995_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM8995_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM8995_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM8995_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM8995_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM8995_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM8995_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM8995_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM8995_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM8995_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM8995_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM8995_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM8995_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM8995_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R1849 (0x739) - Interrupt Status 2 Mask + */ +#define WM8995_IM_DCS_DONE_23_EINT 0x1000 /* IM_DCS_DONE_23_EINT */ +#define WM8995_IM_DCS_DONE_23_EINT_MASK 0x1000 /* IM_DCS_DONE_23_EINT */ +#define WM8995_IM_DCS_DONE_23_EINT_SHIFT 12 /* IM_DCS_DONE_23_EINT */ +#define WM8995_IM_DCS_DONE_23_EINT_WIDTH 1 /* IM_DCS_DONE_23_EINT */ +#define WM8995_IM_DCS_DONE_01_EINT 0x0800 /* IM_DCS_DONE_01_EINT */ +#define WM8995_IM_DCS_DONE_01_EINT_MASK 0x0800 /* IM_DCS_DONE_01_EINT */ +#define WM8995_IM_DCS_DONE_01_EINT_SHIFT 11 /* IM_DCS_DONE_01_EINT */ +#define WM8995_IM_DCS_DONE_01_EINT_WIDTH 1 /* IM_DCS_DONE_01_EINT */ +#define WM8995_IM_WSEQ_DONE_EINT 0x0400 /* IM_WSEQ_DONE_EINT */ +#define WM8995_IM_WSEQ_DONE_EINT_MASK 0x0400 /* IM_WSEQ_DONE_EINT */ +#define WM8995_IM_WSEQ_DONE_EINT_SHIFT 10 /* IM_WSEQ_DONE_EINT */ +#define WM8995_IM_WSEQ_DONE_EINT_WIDTH 1 /* IM_WSEQ_DONE_EINT */ +#define WM8995_IM_FIFOS_ERR_EINT 0x0200 /* IM_FIFOS_ERR_EINT */ +#define WM8995_IM_FIFOS_ERR_EINT_MASK 0x0200 /* IM_FIFOS_ERR_EINT */ +#define WM8995_IM_FIFOS_ERR_EINT_SHIFT 9 /* IM_FIFOS_ERR_EINT */ +#define WM8995_IM_FIFOS_ERR_EINT_WIDTH 1 /* IM_FIFOS_ERR_EINT */ +#define WM8995_IM_AIF2DRC_SIG_DET_EINT 0x0100 /* IM_AIF2DRC_SIG_DET_EINT */ +#define WM8995_IM_AIF2DRC_SIG_DET_EINT_MASK 0x0100 /* IM_AIF2DRC_SIG_DET_EINT */ +#define WM8995_IM_AIF2DRC_SIG_DET_EINT_SHIFT 8 /* IM_AIF2DRC_SIG_DET_EINT */ +#define WM8995_IM_AIF2DRC_SIG_DET_EINT_WIDTH 1 /* IM_AIF2DRC_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC2_SIG_DET_EINT 0x0080 /* IM_AIF1DRC2_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC2_SIG_DET_EINT_MASK 0x0080 /* IM_AIF1DRC2_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC2_SIG_DET_EINT_SHIFT 7 /* IM_AIF1DRC2_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC2_SIG_DET_EINT_WIDTH 1 /* IM_AIF1DRC2_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC1_SIG_DET_EINT 0x0040 /* IM_AIF1DRC1_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC1_SIG_DET_EINT_MASK 0x0040 /* IM_AIF1DRC1_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC1_SIG_DET_EINT_SHIFT 6 /* IM_AIF1DRC1_SIG_DET_EINT */ +#define WM8995_IM_AIF1DRC1_SIG_DET_EINT_WIDTH 1 /* IM_AIF1DRC1_SIG_DET_EINT */ +#define WM8995_IM_SRC2_LOCK_EINT 0x0020 /* IM_SRC2_LOCK_EINT */ +#define WM8995_IM_SRC2_LOCK_EINT_MASK 0x0020 /* IM_SRC2_LOCK_EINT */ +#define WM8995_IM_SRC2_LOCK_EINT_SHIFT 5 /* IM_SRC2_LOCK_EINT */ +#define WM8995_IM_SRC2_LOCK_EINT_WIDTH 1 /* IM_SRC2_LOCK_EINT */ +#define WM8995_IM_SRC1_LOCK_EINT 0x0010 /* IM_SRC1_LOCK_EINT */ +#define WM8995_IM_SRC1_LOCK_EINT_MASK 0x0010 /* IM_SRC1_LOCK_EINT */ +#define WM8995_IM_SRC1_LOCK_EINT_SHIFT 4 /* IM_SRC1_LOCK_EINT */ +#define WM8995_IM_SRC1_LOCK_EINT_WIDTH 1 /* IM_SRC1_LOCK_EINT */ +#define WM8995_IM_FLL2_LOCK_EINT 0x0008 /* IM_FLL2_LOCK_EINT */ +#define WM8995_IM_FLL2_LOCK_EINT_MASK 0x0008 /* IM_FLL2_LOCK_EINT */ +#define WM8995_IM_FLL2_LOCK_EINT_SHIFT 3 /* IM_FLL2_LOCK_EINT */ +#define WM8995_IM_FLL2_LOCK_EINT_WIDTH 1 /* IM_FLL2_LOCK_EINT */ +#define WM8995_IM_FLL1_LOCK_EINT 0x0004 /* IM_FLL1_LOCK_EINT */ +#define WM8995_IM_FLL1_LOCK_EINT_MASK 0x0004 /* IM_FLL1_LOCK_EINT */ +#define WM8995_IM_FLL1_LOCK_EINT_SHIFT 2 /* IM_FLL1_LOCK_EINT */ +#define WM8995_IM_FLL1_LOCK_EINT_WIDTH 1 /* IM_FLL1_LOCK_EINT */ +#define WM8995_IM_HP_DONE_EINT 0x0002 /* IM_HP_DONE_EINT */ +#define WM8995_IM_HP_DONE_EINT_MASK 0x0002 /* IM_HP_DONE_EINT */ +#define WM8995_IM_HP_DONE_EINT_SHIFT 1 /* IM_HP_DONE_EINT */ +#define WM8995_IM_HP_DONE_EINT_WIDTH 1 /* IM_HP_DONE_EINT */ +#define WM8995_IM_MICD_EINT 0x0001 /* IM_MICD_EINT */ +#define WM8995_IM_MICD_EINT_MASK 0x0001 /* IM_MICD_EINT */ +#define WM8995_IM_MICD_EINT_SHIFT 0 /* IM_MICD_EINT */ +#define WM8995_IM_MICD_EINT_WIDTH 1 /* IM_MICD_EINT */ + +/* + * R1856 (0x740) - Interrupt Control + */ +#define WM8995_IM_IRQ 0x0001 /* IM_IRQ */ +#define WM8995_IM_IRQ_MASK 0x0001 /* IM_IRQ */ +#define WM8995_IM_IRQ_SHIFT 0 /* IM_IRQ */ +#define WM8995_IM_IRQ_WIDTH 1 /* IM_IRQ */ + +/* + * R2048 (0x800) - Left PDM Speaker 1 + */ +#define WM8995_SPK1L_ENA 0x0010 /* SPK1L_ENA */ +#define WM8995_SPK1L_ENA_MASK 0x0010 /* SPK1L_ENA */ +#define WM8995_SPK1L_ENA_SHIFT 4 /* SPK1L_ENA */ +#define WM8995_SPK1L_ENA_WIDTH 1 /* SPK1L_ENA */ +#define WM8995_SPK1L_MUTE 0x0008 /* SPK1L_MUTE */ +#define WM8995_SPK1L_MUTE_MASK 0x0008 /* SPK1L_MUTE */ +#define WM8995_SPK1L_MUTE_SHIFT 3 /* SPK1L_MUTE */ +#define WM8995_SPK1L_MUTE_WIDTH 1 /* SPK1L_MUTE */ +#define WM8995_SPK1L_MUTE_ZC 0x0004 /* SPK1L_MUTE_ZC */ +#define WM8995_SPK1L_MUTE_ZC_MASK 0x0004 /* SPK1L_MUTE_ZC */ +#define WM8995_SPK1L_MUTE_ZC_SHIFT 2 /* SPK1L_MUTE_ZC */ +#define WM8995_SPK1L_MUTE_ZC_WIDTH 1 /* SPK1L_MUTE_ZC */ +#define WM8995_SPK1L_SRC_MASK 0x0003 /* SPK1L_SRC - [1:0] */ +#define WM8995_SPK1L_SRC_SHIFT 0 /* SPK1L_SRC - [1:0] */ +#define WM8995_SPK1L_SRC_WIDTH 2 /* SPK1L_SRC - [1:0] */ + +/* + * R2049 (0x801) - Right PDM Speaker 1 + */ +#define WM8995_SPK1R_ENA 0x0010 /* SPK1R_ENA */ +#define WM8995_SPK1R_ENA_MASK 0x0010 /* SPK1R_ENA */ +#define WM8995_SPK1R_ENA_SHIFT 4 /* SPK1R_ENA */ +#define WM8995_SPK1R_ENA_WIDTH 1 /* SPK1R_ENA */ +#define WM8995_SPK1R_MUTE 0x0008 /* SPK1R_MUTE */ +#define WM8995_SPK1R_MUTE_MASK 0x0008 /* SPK1R_MUTE */ +#define WM8995_SPK1R_MUTE_SHIFT 3 /* SPK1R_MUTE */ +#define WM8995_SPK1R_MUTE_WIDTH 1 /* SPK1R_MUTE */ +#define WM8995_SPK1R_MUTE_ZC 0x0004 /* SPK1R_MUTE_ZC */ +#define WM8995_SPK1R_MUTE_ZC_MASK 0x0004 /* SPK1R_MUTE_ZC */ +#define WM8995_SPK1R_MUTE_ZC_SHIFT 2 /* SPK1R_MUTE_ZC */ +#define WM8995_SPK1R_MUTE_ZC_WIDTH 1 /* SPK1R_MUTE_ZC */ +#define WM8995_SPK1R_SRC_MASK 0x0003 /* SPK1R_SRC - [1:0] */ +#define WM8995_SPK1R_SRC_SHIFT 0 /* SPK1R_SRC - [1:0] */ +#define WM8995_SPK1R_SRC_WIDTH 2 /* SPK1R_SRC - [1:0] */ + +/* + * R2050 (0x802) - PDM Speaker 1 Mute Sequence + */ +#define WM8995_SPK1_MUTE_SEQ1_MASK 0x00FF /* SPK1_MUTE_SEQ1 - [7:0] */ +#define WM8995_SPK1_MUTE_SEQ1_SHIFT 0 /* SPK1_MUTE_SEQ1 - [7:0] */ +#define WM8995_SPK1_MUTE_SEQ1_WIDTH 8 /* SPK1_MUTE_SEQ1 - [7:0] */ + +/* + * R2056 (0x808) - Left PDM Speaker 2 + */ +#define WM8995_SPK2L_ENA 0x0010 /* SPK2L_ENA */ +#define WM8995_SPK2L_ENA_MASK 0x0010 /* SPK2L_ENA */ +#define WM8995_SPK2L_ENA_SHIFT 4 /* SPK2L_ENA */ +#define WM8995_SPK2L_ENA_WIDTH 1 /* SPK2L_ENA */ +#define WM8995_SPK2L_MUTE 0x0008 /* SPK2L_MUTE */ +#define WM8995_SPK2L_MUTE_MASK 0x0008 /* SPK2L_MUTE */ +#define WM8995_SPK2L_MUTE_SHIFT 3 /* SPK2L_MUTE */ +#define WM8995_SPK2L_MUTE_WIDTH 1 /* SPK2L_MUTE */ +#define WM8995_SPK2L_MUTE_ZC 0x0004 /* SPK2L_MUTE_ZC */ +#define WM8995_SPK2L_MUTE_ZC_MASK 0x0004 /* SPK2L_MUTE_ZC */ +#define WM8995_SPK2L_MUTE_ZC_SHIFT 2 /* SPK2L_MUTE_ZC */ +#define WM8995_SPK2L_MUTE_ZC_WIDTH 1 /* SPK2L_MUTE_ZC */ +#define WM8995_SPK2L_SRC_MASK 0x0003 /* SPK2L_SRC - [1:0] */ +#define WM8995_SPK2L_SRC_SHIFT 0 /* SPK2L_SRC - [1:0] */ +#define WM8995_SPK2L_SRC_WIDTH 2 /* SPK2L_SRC - [1:0] */ + +/* + * R2057 (0x809) - Right PDM Speaker 2 + */ +#define WM8995_SPK2R_ENA 0x0010 /* SPK2R_ENA */ +#define WM8995_SPK2R_ENA_MASK 0x0010 /* SPK2R_ENA */ +#define WM8995_SPK2R_ENA_SHIFT 4 /* SPK2R_ENA */ +#define WM8995_SPK2R_ENA_WIDTH 1 /* SPK2R_ENA */ +#define WM8995_SPK2R_MUTE 0x0008 /* SPK2R_MUTE */ +#define WM8995_SPK2R_MUTE_MASK 0x0008 /* SPK2R_MUTE */ +#define WM8995_SPK2R_MUTE_SHIFT 3 /* SPK2R_MUTE */ +#define WM8995_SPK2R_MUTE_WIDTH 1 /* SPK2R_MUTE */ +#define WM8995_SPK2R_MUTE_ZC 0x0004 /* SPK2R_MUTE_ZC */ +#define WM8995_SPK2R_MUTE_ZC_MASK 0x0004 /* SPK2R_MUTE_ZC */ +#define WM8995_SPK2R_MUTE_ZC_SHIFT 2 /* SPK2R_MUTE_ZC */ +#define WM8995_SPK2R_MUTE_ZC_WIDTH 1 /* SPK2R_MUTE_ZC */ +#define WM8995_SPK2R_SRC_MASK 0x0003 /* SPK2R_SRC - [1:0] */ +#define WM8995_SPK2R_SRC_SHIFT 0 /* SPK2R_SRC - [1:0] */ +#define WM8995_SPK2R_SRC_WIDTH 2 /* SPK2R_SRC - [1:0] */ + +/* + * R2058 (0x80A) - PDM Speaker 2 Mute Sequence + */ +#define WM8995_SPK2_MUTE_SEQ1_MASK 0x00FF /* SPK2_MUTE_SEQ1 - [7:0] */ +#define WM8995_SPK2_MUTE_SEQ1_SHIFT 0 /* SPK2_MUTE_SEQ1 - [7:0] */ +#define WM8995_SPK2_MUTE_SEQ1_WIDTH 8 /* SPK2_MUTE_SEQ1 - [7:0] */ + +#define WM8995_CLASS_W_SWITCH(xname, reg, shift, max, invert) \ + SOC_SINGLE_EXT(xname, reg, shift, max, invert, \ + snd_soc_dapm_get_volsw, wm8995_put_class_w) + +struct wm8995_reg_access { + u16 read; + u16 write; + u16 vol; +}; + +/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */ +enum clk_src { + WM8995_SYSCLK_MCLK1 = 1, + WM8995_SYSCLK_MCLK2, + WM8995_SYSCLK_FLL1, + WM8995_SYSCLK_FLL2, + WM8995_SYSCLK_OPCLK +}; + +#define WM8995_FLL1 1 +#define WM8995_FLL2 2 + +#define WM8995_FLL_SRC_MCLK1 1 +#define WM8995_FLL_SRC_MCLK2 2 +#define WM8995_FLL_SRC_LRCLK 3 +#define WM8995_FLL_SRC_BCLK 4 + +#endif /* _WM8995_H */ diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c new file mode 100644 index 000000000..91711f895 --- /dev/null +++ b/sound/soc/codecs/wm8996.c @@ -0,0 +1,3107 @@ +/* + * wm8996.c - WM8996 audio codec interface + * + * Copyright 2011-2 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "wm8996.h" + +#define WM8996_AIFS 2 + +#define HPOUT1L 1 +#define HPOUT1R 2 +#define HPOUT2L 4 +#define HPOUT2R 8 + +#define WM8996_NUM_SUPPLIES 3 +static const char *wm8996_supply_names[WM8996_NUM_SUPPLIES] = { + "DBVDD", + "AVDD1", + "AVDD2", +}; + +struct wm8996_priv { + struct device *dev; + struct regmap *regmap; + struct snd_soc_component *component; + + int ldo1ena; + + int sysclk; + int sysclk_src; + + int fll_src; + int fll_fref; + int fll_fout; + + struct completion fll_lock; + + u16 dcs_pending; + struct completion dcs_done; + + u16 hpout_ena; + u16 hpout_pending; + + struct regulator_bulk_data supplies[WM8996_NUM_SUPPLIES]; + struct notifier_block disable_nb[WM8996_NUM_SUPPLIES]; + int bg_ena; + + struct wm8996_pdata pdata; + + int rx_rate[WM8996_AIFS]; + int bclk_rate[WM8996_AIFS]; + + /* Platform dependant ReTune mobile configuration */ + int num_retune_mobile_texts; + const char **retune_mobile_texts; + int retune_mobile_cfg[2]; + struct soc_enum retune_mobile_enum; + + struct snd_soc_jack *jack; + bool detecting; + bool jack_mic; + int jack_flips; + wm8996_polarity_fn polarity_cb; + +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif +}; + +/* We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define WM8996_REGULATOR_EVENT(n) \ +static int wm8996_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct wm8996_priv *wm8996 = container_of(nb, struct wm8996_priv, \ + disable_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + regcache_mark_dirty(wm8996->regmap); \ + } \ + return 0; \ +} + +WM8996_REGULATOR_EVENT(0) +WM8996_REGULATOR_EVENT(1) +WM8996_REGULATOR_EVENT(2) + +static const struct reg_default wm8996_reg[] = { + { WM8996_POWER_MANAGEMENT_1, 0x0 }, + { WM8996_POWER_MANAGEMENT_2, 0x0 }, + { WM8996_POWER_MANAGEMENT_3, 0x0 }, + { WM8996_POWER_MANAGEMENT_4, 0x0 }, + { WM8996_POWER_MANAGEMENT_5, 0x0 }, + { WM8996_POWER_MANAGEMENT_6, 0x0 }, + { WM8996_POWER_MANAGEMENT_7, 0x10 }, + { WM8996_POWER_MANAGEMENT_8, 0x0 }, + { WM8996_LEFT_LINE_INPUT_VOLUME, 0x0 }, + { WM8996_RIGHT_LINE_INPUT_VOLUME, 0x0 }, + { WM8996_LINE_INPUT_CONTROL, 0x0 }, + { WM8996_DAC1_HPOUT1_VOLUME, 0x88 }, + { WM8996_DAC2_HPOUT2_VOLUME, 0x88 }, + { WM8996_DAC1_LEFT_VOLUME, 0x2c0 }, + { WM8996_DAC1_RIGHT_VOLUME, 0x2c0 }, + { WM8996_DAC2_LEFT_VOLUME, 0x2c0 }, + { WM8996_DAC2_RIGHT_VOLUME, 0x2c0 }, + { WM8996_OUTPUT1_LEFT_VOLUME, 0x80 }, + { WM8996_OUTPUT1_RIGHT_VOLUME, 0x80 }, + { WM8996_OUTPUT2_LEFT_VOLUME, 0x80 }, + { WM8996_OUTPUT2_RIGHT_VOLUME, 0x80 }, + { WM8996_MICBIAS_1, 0x39 }, + { WM8996_MICBIAS_2, 0x39 }, + { WM8996_LDO_1, 0x3 }, + { WM8996_LDO_2, 0x13 }, + { WM8996_ACCESSORY_DETECT_MODE_1, 0x4 }, + { WM8996_ACCESSORY_DETECT_MODE_2, 0x0 }, + { WM8996_HEADPHONE_DETECT_1, 0x20 }, + { WM8996_HEADPHONE_DETECT_2, 0x0 }, + { WM8996_MIC_DETECT_1, 0x7600 }, + { WM8996_MIC_DETECT_2, 0xbf }, + { WM8996_CHARGE_PUMP_1, 0x1f25 }, + { WM8996_CHARGE_PUMP_2, 0xab19 }, + { WM8996_DC_SERVO_1, 0x0 }, + { WM8996_DC_SERVO_3, 0x0 }, + { WM8996_DC_SERVO_5, 0x2a2a }, + { WM8996_DC_SERVO_6, 0x0 }, + { WM8996_DC_SERVO_7, 0x0 }, + { WM8996_ANALOGUE_HP_1, 0x0 }, + { WM8996_ANALOGUE_HP_2, 0x0 }, + { WM8996_CONTROL_INTERFACE_1, 0x8004 }, + { WM8996_WRITE_SEQUENCER_CTRL_1, 0x0 }, + { WM8996_WRITE_SEQUENCER_CTRL_2, 0x0 }, + { WM8996_AIF_CLOCKING_1, 0x0 }, + { WM8996_AIF_CLOCKING_2, 0x0 }, + { WM8996_CLOCKING_1, 0x10 }, + { WM8996_CLOCKING_2, 0x0 }, + { WM8996_AIF_RATE, 0x83 }, + { WM8996_FLL_CONTROL_1, 0x0 }, + { WM8996_FLL_CONTROL_2, 0x0 }, + { WM8996_FLL_CONTROL_3, 0x0 }, + { WM8996_FLL_CONTROL_4, 0x5dc0 }, + { WM8996_FLL_CONTROL_5, 0xc84 }, + { WM8996_FLL_EFS_1, 0x0 }, + { WM8996_FLL_EFS_2, 0x2 }, + { WM8996_AIF1_CONTROL, 0x0 }, + { WM8996_AIF1_BCLK, 0x0 }, + { WM8996_AIF1_TX_LRCLK_1, 0x80 }, + { WM8996_AIF1_TX_LRCLK_2, 0x8 }, + { WM8996_AIF1_RX_LRCLK_1, 0x80 }, + { WM8996_AIF1_RX_LRCLK_2, 0x0 }, + { WM8996_AIF1TX_DATA_CONFIGURATION_1, 0x1818 }, + { WM8996_AIF1TX_DATA_CONFIGURATION_2, 0 }, + { WM8996_AIF1RX_DATA_CONFIGURATION, 0x1818 }, + { WM8996_AIF1TX_CHANNEL_0_CONFIGURATION, 0x0 }, + { WM8996_AIF1TX_CHANNEL_1_CONFIGURATION, 0x0 }, + { WM8996_AIF1TX_CHANNEL_2_CONFIGURATION, 0x0 }, + { WM8996_AIF1TX_CHANNEL_3_CONFIGURATION, 0x0 }, + { WM8996_AIF1TX_CHANNEL_4_CONFIGURATION, 0x0 }, + { WM8996_AIF1TX_CHANNEL_5_CONFIGURATION, 0x0 }, + { WM8996_AIF1RX_CHANNEL_0_CONFIGURATION, 0x0 }, + { WM8996_AIF1RX_CHANNEL_1_CONFIGURATION, 0x0 }, + { WM8996_AIF1RX_CHANNEL_2_CONFIGURATION, 0x0 }, + { WM8996_AIF1RX_CHANNEL_3_CONFIGURATION, 0x0 }, + { WM8996_AIF1RX_CHANNEL_4_CONFIGURATION, 0x0 }, + { WM8996_AIF1RX_CHANNEL_5_CONFIGURATION, 0x0 }, + { WM8996_AIF1RX_MONO_CONFIGURATION, 0x0 }, + { WM8996_AIF1TX_TEST, 0x7 }, + { WM8996_AIF2_CONTROL, 0x0 }, + { WM8996_AIF2_BCLK, 0x0 }, + { WM8996_AIF2_TX_LRCLK_1, 0x80 }, + { WM8996_AIF2_TX_LRCLK_2, 0x8 }, + { WM8996_AIF2_RX_LRCLK_1, 0x80 }, + { WM8996_AIF2_RX_LRCLK_2, 0x0 }, + { WM8996_AIF2TX_DATA_CONFIGURATION_1, 0x1818 }, + { WM8996_AIF2RX_DATA_CONFIGURATION, 0x1818 }, + { WM8996_AIF2RX_DATA_CONFIGURATION, 0x0 }, + { WM8996_AIF2TX_CHANNEL_0_CONFIGURATION, 0x0 }, + { WM8996_AIF2TX_CHANNEL_1_CONFIGURATION, 0x0 }, + { WM8996_AIF2RX_CHANNEL_0_CONFIGURATION, 0x0 }, + { WM8996_AIF2RX_CHANNEL_1_CONFIGURATION, 0x0 }, + { WM8996_AIF2RX_MONO_CONFIGURATION, 0x0 }, + { WM8996_AIF2TX_TEST, 0x1 }, + { WM8996_DSP1_TX_LEFT_VOLUME, 0xc0 }, + { WM8996_DSP1_TX_RIGHT_VOLUME, 0xc0 }, + { WM8996_DSP1_RX_LEFT_VOLUME, 0xc0 }, + { WM8996_DSP1_RX_RIGHT_VOLUME, 0xc0 }, + { WM8996_DSP1_TX_FILTERS, 0x2000 }, + { WM8996_DSP1_RX_FILTERS_1, 0x200 }, + { WM8996_DSP1_RX_FILTERS_2, 0x10 }, + { WM8996_DSP1_DRC_1, 0x98 }, + { WM8996_DSP1_DRC_2, 0x845 }, + { WM8996_DSP1_RX_EQ_GAINS_1, 0x6318 }, + { WM8996_DSP1_RX_EQ_GAINS_2, 0x6300 }, + { WM8996_DSP1_RX_EQ_BAND_1_A, 0xfca }, + { WM8996_DSP1_RX_EQ_BAND_1_B, 0x400 }, + { WM8996_DSP1_RX_EQ_BAND_1_PG, 0xd8 }, + { WM8996_DSP1_RX_EQ_BAND_2_A, 0x1eb5 }, + { WM8996_DSP1_RX_EQ_BAND_2_B, 0xf145 }, + { WM8996_DSP1_RX_EQ_BAND_2_C, 0xb75 }, + { WM8996_DSP1_RX_EQ_BAND_2_PG, 0x1c5 }, + { WM8996_DSP1_RX_EQ_BAND_3_A, 0x1c58 }, + { WM8996_DSP1_RX_EQ_BAND_3_B, 0xf373 }, + { WM8996_DSP1_RX_EQ_BAND_3_C, 0xa54 }, + { WM8996_DSP1_RX_EQ_BAND_3_PG, 0x558 }, + { WM8996_DSP1_RX_EQ_BAND_4_A, 0x168e }, + { WM8996_DSP1_RX_EQ_BAND_4_B, 0xf829 }, + { WM8996_DSP1_RX_EQ_BAND_4_C, 0x7ad }, + { WM8996_DSP1_RX_EQ_BAND_4_PG, 0x1103 }, + { WM8996_DSP1_RX_EQ_BAND_5_A, 0x564 }, + { WM8996_DSP1_RX_EQ_BAND_5_B, 0x559 }, + { WM8996_DSP1_RX_EQ_BAND_5_PG, 0x4000 }, + { WM8996_DSP2_TX_LEFT_VOLUME, 0xc0 }, + { WM8996_DSP2_TX_RIGHT_VOLUME, 0xc0 }, + { WM8996_DSP2_RX_LEFT_VOLUME, 0xc0 }, + { WM8996_DSP2_RX_RIGHT_VOLUME, 0xc0 }, + { WM8996_DSP2_TX_FILTERS, 0x2000 }, + { WM8996_DSP2_RX_FILTERS_1, 0x200 }, + { WM8996_DSP2_RX_FILTERS_2, 0x10 }, + { WM8996_DSP2_DRC_1, 0x98 }, + { WM8996_DSP2_DRC_2, 0x845 }, + { WM8996_DSP2_RX_EQ_GAINS_1, 0x6318 }, + { WM8996_DSP2_RX_EQ_GAINS_2, 0x6300 }, + { WM8996_DSP2_RX_EQ_BAND_1_A, 0xfca }, + { WM8996_DSP2_RX_EQ_BAND_1_B, 0x400 }, + { WM8996_DSP2_RX_EQ_BAND_1_PG, 0xd8 }, + { WM8996_DSP2_RX_EQ_BAND_2_A, 0x1eb5 }, + { WM8996_DSP2_RX_EQ_BAND_2_B, 0xf145 }, + { WM8996_DSP2_RX_EQ_BAND_2_C, 0xb75 }, + { WM8996_DSP2_RX_EQ_BAND_2_PG, 0x1c5 }, + { WM8996_DSP2_RX_EQ_BAND_3_A, 0x1c58 }, + { WM8996_DSP2_RX_EQ_BAND_3_B, 0xf373 }, + { WM8996_DSP2_RX_EQ_BAND_3_C, 0xa54 }, + { WM8996_DSP2_RX_EQ_BAND_3_PG, 0x558 }, + { WM8996_DSP2_RX_EQ_BAND_4_A, 0x168e }, + { WM8996_DSP2_RX_EQ_BAND_4_B, 0xf829 }, + { WM8996_DSP2_RX_EQ_BAND_4_C, 0x7ad }, + { WM8996_DSP2_RX_EQ_BAND_4_PG, 0x1103 }, + { WM8996_DSP2_RX_EQ_BAND_5_A, 0x564 }, + { WM8996_DSP2_RX_EQ_BAND_5_B, 0x559 }, + { WM8996_DSP2_RX_EQ_BAND_5_PG, 0x4000 }, + { WM8996_DAC1_MIXER_VOLUMES, 0x0 }, + { WM8996_DAC1_LEFT_MIXER_ROUTING, 0x0 }, + { WM8996_DAC1_RIGHT_MIXER_ROUTING, 0x0 }, + { WM8996_DAC2_MIXER_VOLUMES, 0x0 }, + { WM8996_DAC2_LEFT_MIXER_ROUTING, 0x0 }, + { WM8996_DAC2_RIGHT_MIXER_ROUTING, 0x0 }, + { WM8996_DSP1_TX_LEFT_MIXER_ROUTING, 0x0 }, + { WM8996_DSP1_TX_RIGHT_MIXER_ROUTING, 0x0 }, + { WM8996_DSP2_TX_LEFT_MIXER_ROUTING, 0x0 }, + { WM8996_DSP2_TX_RIGHT_MIXER_ROUTING, 0x0 }, + { WM8996_DSP_TX_MIXER_SELECT, 0x0 }, + { WM8996_DAC_SOFTMUTE, 0x0 }, + { WM8996_OVERSAMPLING, 0xd }, + { WM8996_SIDETONE, 0x1040 }, + { WM8996_GPIO_1, 0xa101 }, + { WM8996_GPIO_2, 0xa101 }, + { WM8996_GPIO_3, 0xa101 }, + { WM8996_GPIO_4, 0xa101 }, + { WM8996_GPIO_5, 0xa101 }, + { WM8996_PULL_CONTROL_1, 0x0 }, + { WM8996_PULL_CONTROL_2, 0x140 }, + { WM8996_INTERRUPT_STATUS_1_MASK, 0x1f }, + { WM8996_INTERRUPT_STATUS_2_MASK, 0x1ecf }, + { WM8996_LEFT_PDM_SPEAKER, 0x0 }, + { WM8996_RIGHT_PDM_SPEAKER, 0x1 }, + { WM8996_PDM_SPEAKER_MUTE_SEQUENCE, 0x69 }, + { WM8996_PDM_SPEAKER_VOLUME, 0x66 }, +}; + +static const DECLARE_TLV_DB_SCALE(inpga_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 150, 0); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(out_digital_tlv, -1200, 150, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -900, 75, 0); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -900, 150, 0); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(threedstereo_tlv, -1600, 183, 1); + +static const char *sidetone_hpf_text[] = { + "2.9kHz", "1.5kHz", "735Hz", "403Hz", "196Hz", "98Hz", "49Hz" +}; + +static SOC_ENUM_SINGLE_DECL(sidetone_hpf, + WM8996_SIDETONE, 7, sidetone_hpf_text); + +static const char *hpf_mode_text[] = { + "HiFi", "Custom", "Voice" +}; + +static SOC_ENUM_SINGLE_DECL(dsp1tx_hpf_mode, + WM8996_DSP1_TX_FILTERS, 3, hpf_mode_text); + +static SOC_ENUM_SINGLE_DECL(dsp2tx_hpf_mode, + WM8996_DSP2_TX_FILTERS, 3, hpf_mode_text); + +static const char *hpf_cutoff_text[] = { + "50Hz", "75Hz", "100Hz", "150Hz", "200Hz", "300Hz", "400Hz" +}; + +static SOC_ENUM_SINGLE_DECL(dsp1tx_hpf_cutoff, + WM8996_DSP1_TX_FILTERS, 0, hpf_cutoff_text); + +static SOC_ENUM_SINGLE_DECL(dsp2tx_hpf_cutoff, + WM8996_DSP2_TX_FILTERS, 0, hpf_cutoff_text); + +static void wm8996_set_retune_mobile(struct snd_soc_component *component, int block) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + struct wm8996_pdata *pdata = &wm8996->pdata; + int base, best, best_val, save, i, cfg, iface; + + if (!wm8996->num_retune_mobile_texts) + return; + + switch (block) { + case 0: + base = WM8996_DSP1_RX_EQ_GAINS_1; + if (snd_soc_component_read32(component, WM8996_POWER_MANAGEMENT_8) & + WM8996_DSP1RX_SRC) + iface = 1; + else + iface = 0; + break; + case 1: + base = WM8996_DSP1_RX_EQ_GAINS_2; + if (snd_soc_component_read32(component, WM8996_POWER_MANAGEMENT_8) & + WM8996_DSP2RX_SRC) + iface = 1; + else + iface = 0; + break; + default: + return; + } + + /* Find the version of the currently selected configuration + * with the nearest sample rate. */ + cfg = wm8996->retune_mobile_cfg[block]; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8996->retune_mobile_texts[cfg]) == 0 && + abs(pdata->retune_mobile_cfgs[i].rate + - wm8996->rx_rate[iface]) < best_val) { + best = i; + best_val = abs(pdata->retune_mobile_cfgs[i].rate + - wm8996->rx_rate[iface]); + } + } + + dev_dbg(component->dev, "ReTune Mobile %d %s/%dHz for %dHz sample rate\n", + block, + pdata->retune_mobile_cfgs[best].name, + pdata->retune_mobile_cfgs[best].rate, + wm8996->rx_rate[iface]); + + /* The EQ will be disabled while reconfiguring it, remember the + * current configuration. + */ + save = snd_soc_component_read32(component, base); + save &= WM8996_DSP1RX_EQ_ENA; + + for (i = 0; i < ARRAY_SIZE(pdata->retune_mobile_cfgs[best].regs); i++) + snd_soc_component_update_bits(component, base + i, 0xffff, + pdata->retune_mobile_cfgs[best].regs[i]); + + snd_soc_component_update_bits(component, base, WM8996_DSP1RX_EQ_ENA, save); +} + +/* Icky as hell but saves code duplication */ +static int wm8996_get_retune_mobile_block(const char *name) +{ + if (strcmp(name, "DSP1 EQ Mode") == 0) + return 0; + if (strcmp(name, "DSP2 EQ Mode") == 0) + return 1; + return -EINVAL; +} + +static int wm8996_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + struct wm8996_pdata *pdata = &wm8996->pdata; + int block = wm8996_get_retune_mobile_block(kcontrol->id.name); + int value = ucontrol->value.enumerated.item[0]; + + if (block < 0) + return block; + + if (value >= pdata->num_retune_mobile_cfgs) + return -EINVAL; + + wm8996->retune_mobile_cfg[block] = value; + + wm8996_set_retune_mobile(component, block); + + return 0; +} + +static int wm8996_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int block = wm8996_get_retune_mobile_block(kcontrol->id.name); + + if (block < 0) + return block; + ucontrol->value.enumerated.item[0] = wm8996->retune_mobile_cfg[block]; + + return 0; +} + +static const struct snd_kcontrol_new wm8996_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", WM8996_LEFT_LINE_INPUT_VOLUME, + WM8996_RIGHT_LINE_INPUT_VOLUME, 0, 31, 0, inpga_tlv), +SOC_DOUBLE_R("Capture ZC Switch", WM8996_LEFT_LINE_INPUT_VOLUME, + WM8996_RIGHT_LINE_INPUT_VOLUME, 5, 1, 0), + +SOC_DOUBLE_TLV("DAC1 Sidetone Volume", WM8996_DAC1_MIXER_VOLUMES, + 0, 5, 24, 0, sidetone_tlv), +SOC_DOUBLE_TLV("DAC2 Sidetone Volume", WM8996_DAC2_MIXER_VOLUMES, + 0, 5, 24, 0, sidetone_tlv), +SOC_SINGLE("Sidetone LPF Switch", WM8996_SIDETONE, 12, 1, 0), +SOC_ENUM("Sidetone HPF Cut-off", sidetone_hpf), +SOC_SINGLE("Sidetone HPF Switch", WM8996_SIDETONE, 6, 1, 0), + +SOC_DOUBLE_R_TLV("DSP1 Capture Volume", WM8996_DSP1_TX_LEFT_VOLUME, + WM8996_DSP1_TX_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R_TLV("DSP2 Capture Volume", WM8996_DSP2_TX_LEFT_VOLUME, + WM8996_DSP2_TX_RIGHT_VOLUME, 1, 96, 0, digital_tlv), + +SOC_SINGLE("DSP1 Capture Notch Filter Switch", WM8996_DSP1_TX_FILTERS, + 13, 1, 0), +SOC_DOUBLE("DSP1 Capture HPF Switch", WM8996_DSP1_TX_FILTERS, 12, 11, 1, 0), +SOC_ENUM("DSP1 Capture HPF Mode", dsp1tx_hpf_mode), +SOC_ENUM("DSP1 Capture HPF Cutoff", dsp1tx_hpf_cutoff), + +SOC_SINGLE("DSP2 Capture Notch Filter Switch", WM8996_DSP2_TX_FILTERS, + 13, 1, 0), +SOC_DOUBLE("DSP2 Capture HPF Switch", WM8996_DSP2_TX_FILTERS, 12, 11, 1, 0), +SOC_ENUM("DSP2 Capture HPF Mode", dsp2tx_hpf_mode), +SOC_ENUM("DSP2 Capture HPF Cutoff", dsp2tx_hpf_cutoff), + +SOC_DOUBLE_R_TLV("DSP1 Playback Volume", WM8996_DSP1_RX_LEFT_VOLUME, + WM8996_DSP1_RX_RIGHT_VOLUME, 1, 112, 0, digital_tlv), +SOC_SINGLE("DSP1 Playback Switch", WM8996_DSP1_RX_FILTERS_1, 9, 1, 1), + +SOC_DOUBLE_R_TLV("DSP2 Playback Volume", WM8996_DSP2_RX_LEFT_VOLUME, + WM8996_DSP2_RX_RIGHT_VOLUME, 1, 112, 0, digital_tlv), +SOC_SINGLE("DSP2 Playback Switch", WM8996_DSP2_RX_FILTERS_1, 9, 1, 1), + +SOC_DOUBLE_R_TLV("DAC1 Volume", WM8996_DAC1_LEFT_VOLUME, + WM8996_DAC1_RIGHT_VOLUME, 1, 112, 0, digital_tlv), +SOC_DOUBLE_R("DAC1 Switch", WM8996_DAC1_LEFT_VOLUME, + WM8996_DAC1_RIGHT_VOLUME, 9, 1, 1), + +SOC_DOUBLE_R_TLV("DAC2 Volume", WM8996_DAC2_LEFT_VOLUME, + WM8996_DAC2_RIGHT_VOLUME, 1, 112, 0, digital_tlv), +SOC_DOUBLE_R("DAC2 Switch", WM8996_DAC2_LEFT_VOLUME, + WM8996_DAC2_RIGHT_VOLUME, 9, 1, 1), + +SOC_SINGLE("Speaker High Performance Switch", WM8996_OVERSAMPLING, 3, 1, 0), +SOC_SINGLE("DMIC High Performance Switch", WM8996_OVERSAMPLING, 2, 1, 0), +SOC_SINGLE("ADC High Performance Switch", WM8996_OVERSAMPLING, 1, 1, 0), +SOC_SINGLE("DAC High Performance Switch", WM8996_OVERSAMPLING, 0, 1, 0), + +SOC_SINGLE("DAC Soft Mute Switch", WM8996_DAC_SOFTMUTE, 1, 1, 0), +SOC_SINGLE("DAC Slow Soft Mute Switch", WM8996_DAC_SOFTMUTE, 0, 1, 0), + +SOC_SINGLE("DSP1 3D Stereo Switch", WM8996_DSP1_RX_FILTERS_2, 8, 1, 0), +SOC_SINGLE("DSP2 3D Stereo Switch", WM8996_DSP2_RX_FILTERS_2, 8, 1, 0), + +SOC_SINGLE_TLV("DSP1 3D Stereo Volume", WM8996_DSP1_RX_FILTERS_2, 10, 15, + 0, threedstereo_tlv), +SOC_SINGLE_TLV("DSP2 3D Stereo Volume", WM8996_DSP2_RX_FILTERS_2, 10, 15, + 0, threedstereo_tlv), + +SOC_DOUBLE_TLV("Digital Output 1 Volume", WM8996_DAC1_HPOUT1_VOLUME, 0, 4, + 8, 0, out_digital_tlv), +SOC_DOUBLE_TLV("Digital Output 2 Volume", WM8996_DAC2_HPOUT2_VOLUME, 0, 4, + 8, 0, out_digital_tlv), + +SOC_DOUBLE_R_TLV("Output 1 Volume", WM8996_OUTPUT1_LEFT_VOLUME, + WM8996_OUTPUT1_RIGHT_VOLUME, 0, 12, 0, out_tlv), +SOC_DOUBLE_R("Output 1 ZC Switch", WM8996_OUTPUT1_LEFT_VOLUME, + WM8996_OUTPUT1_RIGHT_VOLUME, 7, 1, 0), + +SOC_DOUBLE_R_TLV("Output 2 Volume", WM8996_OUTPUT2_LEFT_VOLUME, + WM8996_OUTPUT2_RIGHT_VOLUME, 0, 12, 0, out_tlv), +SOC_DOUBLE_R("Output 2 ZC Switch", WM8996_OUTPUT2_LEFT_VOLUME, + WM8996_OUTPUT2_RIGHT_VOLUME, 7, 1, 0), + +SOC_DOUBLE_TLV("Speaker Volume", WM8996_PDM_SPEAKER_VOLUME, 0, 4, 8, 0, + spk_tlv), +SOC_DOUBLE_R("Speaker Switch", WM8996_LEFT_PDM_SPEAKER, + WM8996_RIGHT_PDM_SPEAKER, 3, 1, 1), +SOC_DOUBLE_R("Speaker ZC Switch", WM8996_LEFT_PDM_SPEAKER, + WM8996_RIGHT_PDM_SPEAKER, 2, 1, 0), + +SOC_SINGLE("DSP1 EQ Switch", WM8996_DSP1_RX_EQ_GAINS_1, 0, 1, 0), +SOC_SINGLE("DSP2 EQ Switch", WM8996_DSP2_RX_EQ_GAINS_1, 0, 1, 0), + +SOC_SINGLE("DSP1 DRC TXL Switch", WM8996_DSP1_DRC_1, 0, 1, 0), +SOC_SINGLE("DSP1 DRC TXR Switch", WM8996_DSP1_DRC_1, 1, 1, 0), +SOC_SINGLE("DSP1 DRC RX Switch", WM8996_DSP1_DRC_1, 2, 1, 0), +SND_SOC_BYTES_MASK("DSP1 DRC", WM8996_DSP1_DRC_1, 5, + WM8996_DSP1RX_DRC_ENA | WM8996_DSP1TXL_DRC_ENA | + WM8996_DSP1TXR_DRC_ENA), + +SOC_SINGLE("DSP2 DRC TXL Switch", WM8996_DSP2_DRC_1, 0, 1, 0), +SOC_SINGLE("DSP2 DRC TXR Switch", WM8996_DSP2_DRC_1, 1, 1, 0), +SOC_SINGLE("DSP2 DRC RX Switch", WM8996_DSP2_DRC_1, 2, 1, 0), +SND_SOC_BYTES_MASK("DSP2 DRC", WM8996_DSP2_DRC_1, 5, + WM8996_DSP2RX_DRC_ENA | WM8996_DSP2TXL_DRC_ENA | + WM8996_DSP2TXR_DRC_ENA), +}; + +static const struct snd_kcontrol_new wm8996_eq_controls[] = { +SOC_SINGLE_TLV("DSP1 EQ B1 Volume", WM8996_DSP1_RX_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP1 EQ B2 Volume", WM8996_DSP1_RX_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP1 EQ B3 Volume", WM8996_DSP1_RX_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP1 EQ B4 Volume", WM8996_DSP1_RX_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP1 EQ B5 Volume", WM8996_DSP1_RX_EQ_GAINS_2, 6, 31, 0, + eq_tlv), + +SOC_SINGLE_TLV("DSP2 EQ B1 Volume", WM8996_DSP2_RX_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP2 EQ B2 Volume", WM8996_DSP2_RX_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP2 EQ B3 Volume", WM8996_DSP2_RX_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP2 EQ B4 Volume", WM8996_DSP2_RX_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("DSP2 EQ B5 Volume", WM8996_DSP2_RX_EQ_GAINS_2, 6, 31, 0, + eq_tlv), +}; + +static void wm8996_bg_enable(struct snd_soc_component *component) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + + wm8996->bg_ena++; + if (wm8996->bg_ena == 1) { + snd_soc_component_update_bits(component, WM8996_POWER_MANAGEMENT_1, + WM8996_BG_ENA, WM8996_BG_ENA); + msleep(2); + } +} + +static void wm8996_bg_disable(struct snd_soc_component *component) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + + wm8996->bg_ena--; + if (!wm8996->bg_ena) + snd_soc_component_update_bits(component, WM8996_POWER_MANAGEMENT_1, + WM8996_BG_ENA, 0); +} + +static int bg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wm8996_bg_enable(component); + break; + case SND_SOC_DAPM_POST_PMD: + wm8996_bg_disable(component); + break; + default: + WARN(1, "Invalid event %d\n", event); + ret = -EINVAL; + } + + return ret; +} + +static int cp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(5); + break; + default: + WARN(1, "Invalid event %d\n", event); + } + + return 0; +} + +static int rmv_short_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + + /* Record which outputs we enabled */ + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + wm8996->hpout_pending &= ~w->shift; + break; + case SND_SOC_DAPM_PRE_PMU: + wm8996->hpout_pending |= w->shift; + break; + default: + WARN(1, "Invalid event %d\n", event); + return -EINVAL; + } + + return 0; +} + +static void wait_for_dc_servo(struct snd_soc_component *component, u16 mask) +{ + struct i2c_client *i2c = to_i2c_client(component->dev); + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int ret; + unsigned long timeout = 200; + + snd_soc_component_write(component, WM8996_DC_SERVO_2, mask); + + /* Use the interrupt if possible */ + do { + if (i2c->irq) { + timeout = wait_for_completion_timeout(&wm8996->dcs_done, + msecs_to_jiffies(200)); + if (timeout == 0) + dev_err(component->dev, "DC servo timed out\n"); + + } else { + msleep(1); + timeout--; + } + + ret = snd_soc_component_read32(component, WM8996_DC_SERVO_2); + dev_dbg(component->dev, "DC servo state: %x\n", ret); + } while (timeout && ret & mask); + + if (timeout == 0) + dev_err(component->dev, "DC servo timed out for %x\n", mask); + else + dev_dbg(component->dev, "DC servo complete for %x\n", mask); +} + +static void wm8996_seq_notifier(struct snd_soc_component *component, + enum snd_soc_dapm_type event, int subseq) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + u16 val, mask; + + /* Complete any pending DC servo starts */ + if (wm8996->dcs_pending) { + dev_dbg(component->dev, "Starting DC servo for %x\n", + wm8996->dcs_pending); + + /* Trigger a startup sequence */ + wait_for_dc_servo(component, wm8996->dcs_pending + << WM8996_DCS_TRIG_STARTUP_0_SHIFT); + + wm8996->dcs_pending = 0; + } + + if (wm8996->hpout_pending != wm8996->hpout_ena) { + dev_dbg(component->dev, "Applying RMV_SHORTs %x->%x\n", + wm8996->hpout_ena, wm8996->hpout_pending); + + val = 0; + mask = 0; + if (wm8996->hpout_pending & HPOUT1L) { + val |= WM8996_HPOUT1L_RMV_SHORT | WM8996_HPOUT1L_OUTP; + mask |= WM8996_HPOUT1L_RMV_SHORT | WM8996_HPOUT1L_OUTP; + } else { + mask |= WM8996_HPOUT1L_RMV_SHORT | + WM8996_HPOUT1L_OUTP | + WM8996_HPOUT1L_DLY; + } + + if (wm8996->hpout_pending & HPOUT1R) { + val |= WM8996_HPOUT1R_RMV_SHORT | WM8996_HPOUT1R_OUTP; + mask |= WM8996_HPOUT1R_RMV_SHORT | WM8996_HPOUT1R_OUTP; + } else { + mask |= WM8996_HPOUT1R_RMV_SHORT | + WM8996_HPOUT1R_OUTP | + WM8996_HPOUT1R_DLY; + } + + snd_soc_component_update_bits(component, WM8996_ANALOGUE_HP_1, mask, val); + + val = 0; + mask = 0; + if (wm8996->hpout_pending & HPOUT2L) { + val |= WM8996_HPOUT2L_RMV_SHORT | WM8996_HPOUT2L_OUTP; + mask |= WM8996_HPOUT2L_RMV_SHORT | WM8996_HPOUT2L_OUTP; + } else { + mask |= WM8996_HPOUT2L_RMV_SHORT | + WM8996_HPOUT2L_OUTP | + WM8996_HPOUT2L_DLY; + } + + if (wm8996->hpout_pending & HPOUT2R) { + val |= WM8996_HPOUT2R_RMV_SHORT | WM8996_HPOUT2R_OUTP; + mask |= WM8996_HPOUT2R_RMV_SHORT | WM8996_HPOUT2R_OUTP; + } else { + mask |= WM8996_HPOUT2R_RMV_SHORT | + WM8996_HPOUT2R_OUTP | + WM8996_HPOUT2R_DLY; + } + + snd_soc_component_update_bits(component, WM8996_ANALOGUE_HP_2, mask, val); + + wm8996->hpout_ena = wm8996->hpout_pending; + } +} + +static int dcs_start(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + wm8996->dcs_pending |= 1 << w->shift; + break; + default: + WARN(1, "Invalid event %d\n", event); + return -EINVAL; + } + + return 0; +} + +static const char *sidetone_text[] = { + "IN1", "IN2", +}; + +static SOC_ENUM_SINGLE_DECL(left_sidetone_enum, + WM8996_SIDETONE, 0, sidetone_text); + +static const struct snd_kcontrol_new left_sidetone = + SOC_DAPM_ENUM("Left Sidetone", left_sidetone_enum); + +static SOC_ENUM_SINGLE_DECL(right_sidetone_enum, + WM8996_SIDETONE, 1, sidetone_text); + +static const struct snd_kcontrol_new right_sidetone = + SOC_DAPM_ENUM("Right Sidetone", right_sidetone_enum); + +static const char *spk_text[] = { + "DAC1L", "DAC1R", "DAC2L", "DAC2R" +}; + +static SOC_ENUM_SINGLE_DECL(spkl_enum, + WM8996_LEFT_PDM_SPEAKER, 0, spk_text); + +static const struct snd_kcontrol_new spkl_mux = + SOC_DAPM_ENUM("SPKL", spkl_enum); + +static SOC_ENUM_SINGLE_DECL(spkr_enum, + WM8996_RIGHT_PDM_SPEAKER, 0, spk_text); + +static const struct snd_kcontrol_new spkr_mux = + SOC_DAPM_ENUM("SPKR", spkr_enum); + +static const char *dsp1rx_text[] = { + "AIF1", "AIF2" +}; + +static SOC_ENUM_SINGLE_DECL(dsp1rx_enum, + WM8996_POWER_MANAGEMENT_8, 0, dsp1rx_text); + +static const struct snd_kcontrol_new dsp1rx = + SOC_DAPM_ENUM("DSP1RX", dsp1rx_enum); + +static const char *dsp2rx_text[] = { + "AIF2", "AIF1" +}; + +static SOC_ENUM_SINGLE_DECL(dsp2rx_enum, + WM8996_POWER_MANAGEMENT_8, 4, dsp2rx_text); + +static const struct snd_kcontrol_new dsp2rx = + SOC_DAPM_ENUM("DSP2RX", dsp2rx_enum); + +static const char *aif2tx_text[] = { + "DSP2", "DSP1", "AIF1" +}; + +static SOC_ENUM_SINGLE_DECL(aif2tx_enum, + WM8996_POWER_MANAGEMENT_8, 6, aif2tx_text); + +static const struct snd_kcontrol_new aif2tx = + SOC_DAPM_ENUM("AIF2TX", aif2tx_enum); + +static const char *inmux_text[] = { + "ADC", "DMIC1", "DMIC2" +}; + +static SOC_ENUM_SINGLE_DECL(in1_enum, + WM8996_POWER_MANAGEMENT_7, 0, inmux_text); + +static const struct snd_kcontrol_new in1_mux = + SOC_DAPM_ENUM("IN1 Mux", in1_enum); + +static SOC_ENUM_SINGLE_DECL(in2_enum, + WM8996_POWER_MANAGEMENT_7, 4, inmux_text); + +static const struct snd_kcontrol_new in2_mux = + SOC_DAPM_ENUM("IN2 Mux", in2_enum); + +static const struct snd_kcontrol_new dac2r_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8996_DAC2_RIGHT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8996_DAC2_RIGHT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("DSP2 Switch", WM8996_DAC2_RIGHT_MIXER_ROUTING, 1, 1, 0), +SOC_DAPM_SINGLE("DSP1 Switch", WM8996_DAC2_RIGHT_MIXER_ROUTING, 0, 1, 0), +}; + +static const struct snd_kcontrol_new dac2l_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8996_DAC2_LEFT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8996_DAC2_LEFT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("DSP2 Switch", WM8996_DAC2_LEFT_MIXER_ROUTING, 1, 1, 0), +SOC_DAPM_SINGLE("DSP1 Switch", WM8996_DAC2_LEFT_MIXER_ROUTING, 0, 1, 0), +}; + +static const struct snd_kcontrol_new dac1r_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8996_DAC1_RIGHT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8996_DAC1_RIGHT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("DSP2 Switch", WM8996_DAC1_RIGHT_MIXER_ROUTING, 1, 1, 0), +SOC_DAPM_SINGLE("DSP1 Switch", WM8996_DAC1_RIGHT_MIXER_ROUTING, 0, 1, 0), +}; + +static const struct snd_kcontrol_new dac1l_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8996_DAC1_LEFT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8996_DAC1_LEFT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("DSP2 Switch", WM8996_DAC1_LEFT_MIXER_ROUTING, 1, 1, 0), +SOC_DAPM_SINGLE("DSP1 Switch", WM8996_DAC1_LEFT_MIXER_ROUTING, 0, 1, 0), +}; + +static const struct snd_kcontrol_new dsp1txl[] = { +SOC_DAPM_SINGLE("IN1 Switch", WM8996_DSP1_TX_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8996_DSP1_TX_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new dsp1txr[] = { +SOC_DAPM_SINGLE("IN1 Switch", WM8996_DSP1_TX_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8996_DSP1_TX_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new dsp2txl[] = { +SOC_DAPM_SINGLE("IN1 Switch", WM8996_DSP2_TX_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8996_DSP2_TX_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new dsp2txr[] = { +SOC_DAPM_SINGLE("IN1 Switch", WM8996_DSP2_TX_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8996_DSP2_TX_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + + +static const struct snd_soc_dapm_widget wm8996_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1LN"), +SND_SOC_DAPM_INPUT("IN1LP"), +SND_SOC_DAPM_INPUT("IN1RN"), +SND_SOC_DAPM_INPUT("IN1RP"), + +SND_SOC_DAPM_INPUT("IN2LN"), +SND_SOC_DAPM_INPUT("IN2LP"), +SND_SOC_DAPM_INPUT("IN2RN"), +SND_SOC_DAPM_INPUT("IN2RP"), + +SND_SOC_DAPM_INPUT("DMIC1DAT"), +SND_SOC_DAPM_INPUT("DMIC2DAT"), + +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_SUPPLY_S("SYSCLK", 1, WM8996_AIF_CLOCKING_1, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY_S("SYSDSPCLK", 2, WM8996_CLOCKING_1, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY_S("AIFCLK", 2, WM8996_CLOCKING_1, 2, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY_S("Charge Pump", 2, WM8996_CHARGE_PUMP_1, 15, 0, cp_event, + SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_SUPPLY("Bandgap", SND_SOC_NOPM, 0, 0, bg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("LDO2", WM8996_POWER_MANAGEMENT_2, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICB1 Audio", WM8996_MICBIAS_1, 4, 1, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICB2 Audio", WM8996_MICBIAS_2, 4, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("MICB2", WM8996_POWER_MANAGEMENT_1, 9, 0), +SND_SOC_DAPM_MICBIAS("MICB1", WM8996_POWER_MANAGEMENT_1, 8, 0), + +SND_SOC_DAPM_PGA("IN1L PGA", WM8996_POWER_MANAGEMENT_2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN1R PGA", WM8996_POWER_MANAGEMENT_2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MUX("IN1L Mux", WM8996_POWER_MANAGEMENT_7, 2, 0, &in1_mux), +SND_SOC_DAPM_MUX("IN1R Mux", WM8996_POWER_MANAGEMENT_7, 3, 0, &in1_mux), +SND_SOC_DAPM_MUX("IN2L Mux", WM8996_POWER_MANAGEMENT_7, 6, 0, &in2_mux), +SND_SOC_DAPM_MUX("IN2R Mux", WM8996_POWER_MANAGEMENT_7, 7, 0, &in2_mux), + +SND_SOC_DAPM_SUPPLY("DMIC2", WM8996_POWER_MANAGEMENT_7, 9, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("DMIC1", WM8996_POWER_MANAGEMENT_7, 8, 0, NULL, 0), + +SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8996_POWER_MANAGEMENT_3, 5, 0), +SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8996_POWER_MANAGEMENT_3, 4, 0), +SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8996_POWER_MANAGEMENT_3, 3, 0), +SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8996_POWER_MANAGEMENT_3, 2, 0), + +SND_SOC_DAPM_ADC("ADCL", NULL, WM8996_POWER_MANAGEMENT_3, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, WM8996_POWER_MANAGEMENT_3, 0, 0), + +SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &left_sidetone), +SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &right_sidetone), + +SND_SOC_DAPM_AIF_IN("DSP2RXL", NULL, 0, WM8996_POWER_MANAGEMENT_3, 11, 0), +SND_SOC_DAPM_AIF_IN("DSP2RXR", NULL, 1, WM8996_POWER_MANAGEMENT_3, 10, 0), +SND_SOC_DAPM_AIF_IN("DSP1RXL", NULL, 0, WM8996_POWER_MANAGEMENT_3, 9, 0), +SND_SOC_DAPM_AIF_IN("DSP1RXR", NULL, 1, WM8996_POWER_MANAGEMENT_3, 8, 0), + +SND_SOC_DAPM_MIXER("DSP2TXL", WM8996_POWER_MANAGEMENT_5, 11, 0, + dsp2txl, ARRAY_SIZE(dsp2txl)), +SND_SOC_DAPM_MIXER("DSP2TXR", WM8996_POWER_MANAGEMENT_5, 10, 0, + dsp2txr, ARRAY_SIZE(dsp2txr)), +SND_SOC_DAPM_MIXER("DSP1TXL", WM8996_POWER_MANAGEMENT_5, 9, 0, + dsp1txl, ARRAY_SIZE(dsp1txl)), +SND_SOC_DAPM_MIXER("DSP1TXR", WM8996_POWER_MANAGEMENT_5, 8, 0, + dsp1txr, ARRAY_SIZE(dsp1txr)), + +SND_SOC_DAPM_MIXER("DAC2L Mixer", SND_SOC_NOPM, 0, 0, + dac2l_mix, ARRAY_SIZE(dac2l_mix)), +SND_SOC_DAPM_MIXER("DAC2R Mixer", SND_SOC_NOPM, 0, 0, + dac2r_mix, ARRAY_SIZE(dac2r_mix)), +SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0, + dac1l_mix, ARRAY_SIZE(dac1l_mix)), +SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0, + dac1r_mix, ARRAY_SIZE(dac1r_mix)), + +SND_SOC_DAPM_DAC("DAC2L", NULL, WM8996_POWER_MANAGEMENT_5, 3, 0), +SND_SOC_DAPM_DAC("DAC2R", NULL, WM8996_POWER_MANAGEMENT_5, 2, 0), +SND_SOC_DAPM_DAC("DAC1L", NULL, WM8996_POWER_MANAGEMENT_5, 1, 0), +SND_SOC_DAPM_DAC("DAC1R", NULL, WM8996_POWER_MANAGEMENT_5, 0, 0), + +SND_SOC_DAPM_AIF_IN("AIF2RX1", NULL, 0, WM8996_POWER_MANAGEMENT_4, 9, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX0", NULL, 1, WM8996_POWER_MANAGEMENT_4, 8, 0), + +SND_SOC_DAPM_AIF_OUT("AIF2TX1", NULL, 0, WM8996_POWER_MANAGEMENT_6, 9, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX0", NULL, 1, WM8996_POWER_MANAGEMENT_6, 8, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX5", NULL, 5, WM8996_POWER_MANAGEMENT_4, 5, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", NULL, 4, WM8996_POWER_MANAGEMENT_4, 4, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", NULL, 3, WM8996_POWER_MANAGEMENT_4, 3, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", NULL, 2, WM8996_POWER_MANAGEMENT_4, 2, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX1", NULL, 1, WM8996_POWER_MANAGEMENT_4, 1, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX0", NULL, 0, WM8996_POWER_MANAGEMENT_4, 0, 0), + +SND_SOC_DAPM_AIF_OUT("AIF1TX5", NULL, 5, WM8996_POWER_MANAGEMENT_6, 5, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", NULL, 4, WM8996_POWER_MANAGEMENT_6, 4, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", NULL, 3, WM8996_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", NULL, 2, WM8996_POWER_MANAGEMENT_6, 2, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX1", NULL, 1, WM8996_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX0", NULL, 0, WM8996_POWER_MANAGEMENT_6, 0, 0), + +/* We route as stereo pairs so define some dummy widgets to squash + * things down for now. RXA = 0,1, RXB = 2,3 and so on */ +SND_SOC_DAPM_PGA("AIF1RXA", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("AIF1RXB", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("AIF1RXC", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("AIF2RX", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("DSP2TX", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_MUX("DSP1RX", SND_SOC_NOPM, 0, 0, &dsp1rx), +SND_SOC_DAPM_MUX("DSP2RX", SND_SOC_NOPM, 0, 0, &dsp2rx), +SND_SOC_DAPM_MUX("AIF2TX", SND_SOC_NOPM, 0, 0, &aif2tx), + +SND_SOC_DAPM_MUX("SPKL", SND_SOC_NOPM, 0, 0, &spkl_mux), +SND_SOC_DAPM_MUX("SPKR", SND_SOC_NOPM, 0, 0, &spkr_mux), +SND_SOC_DAPM_PGA("SPKL PGA", WM8996_LEFT_PDM_SPEAKER, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("SPKR PGA", WM8996_RIGHT_PDM_SPEAKER, 4, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("HPOUT2L PGA", 0, WM8996_POWER_MANAGEMENT_1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT2L_DLY", 1, WM8996_ANALOGUE_HP_2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT2L_DCS", 2, WM8996_DC_SERVO_1, 2, 0, dcs_start, + SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_S("HPOUT2L_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT2L, 0, + rmv_short_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_PGA_S("HPOUT2R PGA", 0, WM8996_POWER_MANAGEMENT_1, 6, 0,NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT2R_DLY", 1, WM8996_ANALOGUE_HP_2, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT2R_DCS", 2, WM8996_DC_SERVO_1, 3, 0, dcs_start, + SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_S("HPOUT2R_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT2R, 0, + rmv_short_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_PGA_S("HPOUT1L PGA", 0, WM8996_POWER_MANAGEMENT_1, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT1L_DLY", 1, WM8996_ANALOGUE_HP_1, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT1L_DCS", 2, WM8996_DC_SERVO_1, 0, 0, dcs_start, + SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_S("HPOUT1L_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT1L, 0, + rmv_short_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_PGA_S("HPOUT1R PGA", 0, WM8996_POWER_MANAGEMENT_1, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT1R_DLY", 1, WM8996_ANALOGUE_HP_1, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPOUT1R_DCS", 2, WM8996_DC_SERVO_1, 1, 0, dcs_start, + SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_S("HPOUT1R_RMV_SHORT", 3, SND_SOC_NOPM, HPOUT1R, 0, + rmv_short_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("HPOUT2L"), +SND_SOC_DAPM_OUTPUT("HPOUT2R"), +SND_SOC_DAPM_OUTPUT("SPKDAT"), +}; + +static const struct snd_soc_dapm_route wm8996_dapm_routes[] = { + { "AIFCLK", NULL, "SYSCLK" }, + { "SYSDSPCLK", NULL, "SYSCLK" }, + { "Charge Pump", NULL, "SYSCLK" }, + { "Charge Pump", NULL, "CPVDD" }, + + { "MICB1", NULL, "LDO2" }, + { "MICB1", NULL, "MICB1 Audio" }, + { "MICB1", NULL, "Bandgap" }, + { "MICB2", NULL, "LDO2" }, + { "MICB2", NULL, "MICB2 Audio" }, + { "MICB2", NULL, "Bandgap" }, + + { "AIF1RX0", NULL, "AIF1 Playback" }, + { "AIF1RX1", NULL, "AIF1 Playback" }, + { "AIF1RX2", NULL, "AIF1 Playback" }, + { "AIF1RX3", NULL, "AIF1 Playback" }, + { "AIF1RX4", NULL, "AIF1 Playback" }, + { "AIF1RX5", NULL, "AIF1 Playback" }, + + { "AIF2RX0", NULL, "AIF2 Playback" }, + { "AIF2RX1", NULL, "AIF2 Playback" }, + + { "AIF1 Capture", NULL, "AIF1TX0" }, + { "AIF1 Capture", NULL, "AIF1TX1" }, + { "AIF1 Capture", NULL, "AIF1TX2" }, + { "AIF1 Capture", NULL, "AIF1TX3" }, + { "AIF1 Capture", NULL, "AIF1TX4" }, + { "AIF1 Capture", NULL, "AIF1TX5" }, + + { "AIF2 Capture", NULL, "AIF2TX0" }, + { "AIF2 Capture", NULL, "AIF2TX1" }, + + { "IN1L PGA", NULL, "IN2LN" }, + { "IN1L PGA", NULL, "IN2LP" }, + { "IN1L PGA", NULL, "IN1LN" }, + { "IN1L PGA", NULL, "IN1LP" }, + { "IN1L PGA", NULL, "Bandgap" }, + + { "IN1R PGA", NULL, "IN2RN" }, + { "IN1R PGA", NULL, "IN2RP" }, + { "IN1R PGA", NULL, "IN1RN" }, + { "IN1R PGA", NULL, "IN1RP" }, + { "IN1R PGA", NULL, "Bandgap" }, + + { "ADCL", NULL, "IN1L PGA" }, + + { "ADCR", NULL, "IN1R PGA" }, + + { "DMIC1L", NULL, "DMIC1DAT" }, + { "DMIC1R", NULL, "DMIC1DAT" }, + { "DMIC2L", NULL, "DMIC2DAT" }, + { "DMIC2R", NULL, "DMIC2DAT" }, + + { "DMIC2L", NULL, "DMIC2" }, + { "DMIC2R", NULL, "DMIC2" }, + { "DMIC1L", NULL, "DMIC1" }, + { "DMIC1R", NULL, "DMIC1" }, + + { "IN1L Mux", "ADC", "ADCL" }, + { "IN1L Mux", "DMIC1", "DMIC1L" }, + { "IN1L Mux", "DMIC2", "DMIC2L" }, + + { "IN1R Mux", "ADC", "ADCR" }, + { "IN1R Mux", "DMIC1", "DMIC1R" }, + { "IN1R Mux", "DMIC2", "DMIC2R" }, + + { "IN2L Mux", "ADC", "ADCL" }, + { "IN2L Mux", "DMIC1", "DMIC1L" }, + { "IN2L Mux", "DMIC2", "DMIC2L" }, + + { "IN2R Mux", "ADC", "ADCR" }, + { "IN2R Mux", "DMIC1", "DMIC1R" }, + { "IN2R Mux", "DMIC2", "DMIC2R" }, + + { "Left Sidetone", "IN1", "IN1L Mux" }, + { "Left Sidetone", "IN2", "IN2L Mux" }, + + { "Right Sidetone", "IN1", "IN1R Mux" }, + { "Right Sidetone", "IN2", "IN2R Mux" }, + + { "DSP1TXL", "IN1 Switch", "IN1L Mux" }, + { "DSP1TXR", "IN1 Switch", "IN1R Mux" }, + + { "DSP2TXL", "IN1 Switch", "IN2L Mux" }, + { "DSP2TXR", "IN1 Switch", "IN2R Mux" }, + + { "AIF1TX0", NULL, "DSP1TXL" }, + { "AIF1TX1", NULL, "DSP1TXR" }, + { "AIF1TX2", NULL, "DSP2TXL" }, + { "AIF1TX3", NULL, "DSP2TXR" }, + { "AIF1TX4", NULL, "AIF2RX0" }, + { "AIF1TX5", NULL, "AIF2RX1" }, + + { "AIF1RX0", NULL, "AIFCLK" }, + { "AIF1RX1", NULL, "AIFCLK" }, + { "AIF1RX2", NULL, "AIFCLK" }, + { "AIF1RX3", NULL, "AIFCLK" }, + { "AIF1RX4", NULL, "AIFCLK" }, + { "AIF1RX5", NULL, "AIFCLK" }, + + { "AIF2RX0", NULL, "AIFCLK" }, + { "AIF2RX1", NULL, "AIFCLK" }, + + { "AIF1TX0", NULL, "AIFCLK" }, + { "AIF1TX1", NULL, "AIFCLK" }, + { "AIF1TX2", NULL, "AIFCLK" }, + { "AIF1TX3", NULL, "AIFCLK" }, + { "AIF1TX4", NULL, "AIFCLK" }, + { "AIF1TX5", NULL, "AIFCLK" }, + + { "AIF2TX0", NULL, "AIFCLK" }, + { "AIF2TX1", NULL, "AIFCLK" }, + + { "DSP1RXL", NULL, "SYSDSPCLK" }, + { "DSP1RXR", NULL, "SYSDSPCLK" }, + { "DSP2RXL", NULL, "SYSDSPCLK" }, + { "DSP2RXR", NULL, "SYSDSPCLK" }, + { "DSP1TXL", NULL, "SYSDSPCLK" }, + { "DSP1TXR", NULL, "SYSDSPCLK" }, + { "DSP2TXL", NULL, "SYSDSPCLK" }, + { "DSP2TXR", NULL, "SYSDSPCLK" }, + + { "AIF1RXA", NULL, "AIF1RX0" }, + { "AIF1RXA", NULL, "AIF1RX1" }, + { "AIF1RXB", NULL, "AIF1RX2" }, + { "AIF1RXB", NULL, "AIF1RX3" }, + { "AIF1RXC", NULL, "AIF1RX4" }, + { "AIF1RXC", NULL, "AIF1RX5" }, + + { "AIF2RX", NULL, "AIF2RX0" }, + { "AIF2RX", NULL, "AIF2RX1" }, + + { "AIF2TX", "DSP2", "DSP2TX" }, + { "AIF2TX", "DSP1", "DSP1RX" }, + { "AIF2TX", "AIF1", "AIF1RXC" }, + + { "DSP1RXL", NULL, "DSP1RX" }, + { "DSP1RXR", NULL, "DSP1RX" }, + { "DSP2RXL", NULL, "DSP2RX" }, + { "DSP2RXR", NULL, "DSP2RX" }, + + { "DSP2TX", NULL, "DSP2TXL" }, + { "DSP2TX", NULL, "DSP2TXR" }, + + { "DSP1RX", "AIF1", "AIF1RXA" }, + { "DSP1RX", "AIF2", "AIF2RX" }, + + { "DSP2RX", "AIF1", "AIF1RXB" }, + { "DSP2RX", "AIF2", "AIF2RX" }, + + { "DAC2L Mixer", "DSP2 Switch", "DSP2RXL" }, + { "DAC2L Mixer", "DSP1 Switch", "DSP1RXL" }, + { "DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" }, + { "DAC2L Mixer", "Left Sidetone Switch", "Left Sidetone" }, + + { "DAC2R Mixer", "DSP2 Switch", "DSP2RXR" }, + { "DAC2R Mixer", "DSP1 Switch", "DSP1RXR" }, + { "DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + { "DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" }, + + { "DAC1L Mixer", "DSP2 Switch", "DSP2RXL" }, + { "DAC1L Mixer", "DSP1 Switch", "DSP1RXL" }, + { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" }, + { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" }, + + { "DAC1R Mixer", "DSP2 Switch", "DSP2RXR" }, + { "DAC1R Mixer", "DSP1 Switch", "DSP1RXR" }, + { "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + { "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" }, + + { "DAC1L", NULL, "DAC1L Mixer" }, + { "DAC1R", NULL, "DAC1R Mixer" }, + { "DAC2L", NULL, "DAC2L Mixer" }, + { "DAC2R", NULL, "DAC2R Mixer" }, + + { "HPOUT2L PGA", NULL, "Charge Pump" }, + { "HPOUT2L PGA", NULL, "Bandgap" }, + { "HPOUT2L PGA", NULL, "DAC2L" }, + { "HPOUT2L_DLY", NULL, "HPOUT2L PGA" }, + { "HPOUT2L_DCS", NULL, "HPOUT2L_DLY" }, + { "HPOUT2L_RMV_SHORT", NULL, "HPOUT2L_DCS" }, + + { "HPOUT2R PGA", NULL, "Charge Pump" }, + { "HPOUT2R PGA", NULL, "Bandgap" }, + { "HPOUT2R PGA", NULL, "DAC2R" }, + { "HPOUT2R_DLY", NULL, "HPOUT2R PGA" }, + { "HPOUT2R_DCS", NULL, "HPOUT2R_DLY" }, + { "HPOUT2R_RMV_SHORT", NULL, "HPOUT2R_DCS" }, + + { "HPOUT1L PGA", NULL, "Charge Pump" }, + { "HPOUT1L PGA", NULL, "Bandgap" }, + { "HPOUT1L PGA", NULL, "DAC1L" }, + { "HPOUT1L_DLY", NULL, "HPOUT1L PGA" }, + { "HPOUT1L_DCS", NULL, "HPOUT1L_DLY" }, + { "HPOUT1L_RMV_SHORT", NULL, "HPOUT1L_DCS" }, + + { "HPOUT1R PGA", NULL, "Charge Pump" }, + { "HPOUT1R PGA", NULL, "Bandgap" }, + { "HPOUT1R PGA", NULL, "DAC1R" }, + { "HPOUT1R_DLY", NULL, "HPOUT1R PGA" }, + { "HPOUT1R_DCS", NULL, "HPOUT1R_DLY" }, + { "HPOUT1R_RMV_SHORT", NULL, "HPOUT1R_DCS" }, + + { "HPOUT2L", NULL, "HPOUT2L_RMV_SHORT" }, + { "HPOUT2R", NULL, "HPOUT2R_RMV_SHORT" }, + { "HPOUT1L", NULL, "HPOUT1L_RMV_SHORT" }, + { "HPOUT1R", NULL, "HPOUT1R_RMV_SHORT" }, + + { "SPKL", "DAC1L", "DAC1L" }, + { "SPKL", "DAC1R", "DAC1R" }, + { "SPKL", "DAC2L", "DAC2L" }, + { "SPKL", "DAC2R", "DAC2R" }, + + { "SPKR", "DAC1L", "DAC1L" }, + { "SPKR", "DAC1R", "DAC1R" }, + { "SPKR", "DAC2L", "DAC2L" }, + { "SPKR", "DAC2R", "DAC2R" }, + + { "SPKL PGA", NULL, "SPKL" }, + { "SPKR PGA", NULL, "SPKR" }, + + { "SPKDAT", NULL, "SPKL PGA" }, + { "SPKDAT", NULL, "SPKR PGA" }, +}; + +static bool wm8996_readable_register(struct device *dev, unsigned int reg) +{ + /* Due to the sparseness of the register map the compiler + * output from an explicit switch statement ends up being much + * more efficient than a table. + */ + switch (reg) { + case WM8996_SOFTWARE_RESET: + case WM8996_POWER_MANAGEMENT_1: + case WM8996_POWER_MANAGEMENT_2: + case WM8996_POWER_MANAGEMENT_3: + case WM8996_POWER_MANAGEMENT_4: + case WM8996_POWER_MANAGEMENT_5: + case WM8996_POWER_MANAGEMENT_6: + case WM8996_POWER_MANAGEMENT_7: + case WM8996_POWER_MANAGEMENT_8: + case WM8996_LEFT_LINE_INPUT_VOLUME: + case WM8996_RIGHT_LINE_INPUT_VOLUME: + case WM8996_LINE_INPUT_CONTROL: + case WM8996_DAC1_HPOUT1_VOLUME: + case WM8996_DAC2_HPOUT2_VOLUME: + case WM8996_DAC1_LEFT_VOLUME: + case WM8996_DAC1_RIGHT_VOLUME: + case WM8996_DAC2_LEFT_VOLUME: + case WM8996_DAC2_RIGHT_VOLUME: + case WM8996_OUTPUT1_LEFT_VOLUME: + case WM8996_OUTPUT1_RIGHT_VOLUME: + case WM8996_OUTPUT2_LEFT_VOLUME: + case WM8996_OUTPUT2_RIGHT_VOLUME: + case WM8996_MICBIAS_1: + case WM8996_MICBIAS_2: + case WM8996_LDO_1: + case WM8996_LDO_2: + case WM8996_ACCESSORY_DETECT_MODE_1: + case WM8996_ACCESSORY_DETECT_MODE_2: + case WM8996_HEADPHONE_DETECT_1: + case WM8996_HEADPHONE_DETECT_2: + case WM8996_MIC_DETECT_1: + case WM8996_MIC_DETECT_2: + case WM8996_MIC_DETECT_3: + case WM8996_CHARGE_PUMP_1: + case WM8996_CHARGE_PUMP_2: + case WM8996_DC_SERVO_1: + case WM8996_DC_SERVO_2: + case WM8996_DC_SERVO_3: + case WM8996_DC_SERVO_5: + case WM8996_DC_SERVO_6: + case WM8996_DC_SERVO_7: + case WM8996_DC_SERVO_READBACK_0: + case WM8996_ANALOGUE_HP_1: + case WM8996_ANALOGUE_HP_2: + case WM8996_CHIP_REVISION: + case WM8996_CONTROL_INTERFACE_1: + case WM8996_WRITE_SEQUENCER_CTRL_1: + case WM8996_WRITE_SEQUENCER_CTRL_2: + case WM8996_AIF_CLOCKING_1: + case WM8996_AIF_CLOCKING_2: + case WM8996_CLOCKING_1: + case WM8996_CLOCKING_2: + case WM8996_AIF_RATE: + case WM8996_FLL_CONTROL_1: + case WM8996_FLL_CONTROL_2: + case WM8996_FLL_CONTROL_3: + case WM8996_FLL_CONTROL_4: + case WM8996_FLL_CONTROL_5: + case WM8996_FLL_CONTROL_6: + case WM8996_FLL_EFS_1: + case WM8996_FLL_EFS_2: + case WM8996_AIF1_CONTROL: + case WM8996_AIF1_BCLK: + case WM8996_AIF1_TX_LRCLK_1: + case WM8996_AIF1_TX_LRCLK_2: + case WM8996_AIF1_RX_LRCLK_1: + case WM8996_AIF1_RX_LRCLK_2: + case WM8996_AIF1TX_DATA_CONFIGURATION_1: + case WM8996_AIF1TX_DATA_CONFIGURATION_2: + case WM8996_AIF1RX_DATA_CONFIGURATION: + case WM8996_AIF1TX_CHANNEL_0_CONFIGURATION: + case WM8996_AIF1TX_CHANNEL_1_CONFIGURATION: + case WM8996_AIF1TX_CHANNEL_2_CONFIGURATION: + case WM8996_AIF1TX_CHANNEL_3_CONFIGURATION: + case WM8996_AIF1TX_CHANNEL_4_CONFIGURATION: + case WM8996_AIF1TX_CHANNEL_5_CONFIGURATION: + case WM8996_AIF1RX_CHANNEL_0_CONFIGURATION: + case WM8996_AIF1RX_CHANNEL_1_CONFIGURATION: + case WM8996_AIF1RX_CHANNEL_2_CONFIGURATION: + case WM8996_AIF1RX_CHANNEL_3_CONFIGURATION: + case WM8996_AIF1RX_CHANNEL_4_CONFIGURATION: + case WM8996_AIF1RX_CHANNEL_5_CONFIGURATION: + case WM8996_AIF1RX_MONO_CONFIGURATION: + case WM8996_AIF1TX_TEST: + case WM8996_AIF2_CONTROL: + case WM8996_AIF2_BCLK: + case WM8996_AIF2_TX_LRCLK_1: + case WM8996_AIF2_TX_LRCLK_2: + case WM8996_AIF2_RX_LRCLK_1: + case WM8996_AIF2_RX_LRCLK_2: + case WM8996_AIF2TX_DATA_CONFIGURATION_1: + case WM8996_AIF2TX_DATA_CONFIGURATION_2: + case WM8996_AIF2RX_DATA_CONFIGURATION: + case WM8996_AIF2TX_CHANNEL_0_CONFIGURATION: + case WM8996_AIF2TX_CHANNEL_1_CONFIGURATION: + case WM8996_AIF2RX_CHANNEL_0_CONFIGURATION: + case WM8996_AIF2RX_CHANNEL_1_CONFIGURATION: + case WM8996_AIF2RX_MONO_CONFIGURATION: + case WM8996_AIF2TX_TEST: + case WM8996_DSP1_TX_LEFT_VOLUME: + case WM8996_DSP1_TX_RIGHT_VOLUME: + case WM8996_DSP1_RX_LEFT_VOLUME: + case WM8996_DSP1_RX_RIGHT_VOLUME: + case WM8996_DSP1_TX_FILTERS: + case WM8996_DSP1_RX_FILTERS_1: + case WM8996_DSP1_RX_FILTERS_2: + case WM8996_DSP1_DRC_1: + case WM8996_DSP1_DRC_2: + case WM8996_DSP1_DRC_3: + case WM8996_DSP1_DRC_4: + case WM8996_DSP1_DRC_5: + case WM8996_DSP1_RX_EQ_GAINS_1: + case WM8996_DSP1_RX_EQ_GAINS_2: + case WM8996_DSP1_RX_EQ_BAND_1_A: + case WM8996_DSP1_RX_EQ_BAND_1_B: + case WM8996_DSP1_RX_EQ_BAND_1_PG: + case WM8996_DSP1_RX_EQ_BAND_2_A: + case WM8996_DSP1_RX_EQ_BAND_2_B: + case WM8996_DSP1_RX_EQ_BAND_2_C: + case WM8996_DSP1_RX_EQ_BAND_2_PG: + case WM8996_DSP1_RX_EQ_BAND_3_A: + case WM8996_DSP1_RX_EQ_BAND_3_B: + case WM8996_DSP1_RX_EQ_BAND_3_C: + case WM8996_DSP1_RX_EQ_BAND_3_PG: + case WM8996_DSP1_RX_EQ_BAND_4_A: + case WM8996_DSP1_RX_EQ_BAND_4_B: + case WM8996_DSP1_RX_EQ_BAND_4_C: + case WM8996_DSP1_RX_EQ_BAND_4_PG: + case WM8996_DSP1_RX_EQ_BAND_5_A: + case WM8996_DSP1_RX_EQ_BAND_5_B: + case WM8996_DSP1_RX_EQ_BAND_5_PG: + case WM8996_DSP2_TX_LEFT_VOLUME: + case WM8996_DSP2_TX_RIGHT_VOLUME: + case WM8996_DSP2_RX_LEFT_VOLUME: + case WM8996_DSP2_RX_RIGHT_VOLUME: + case WM8996_DSP2_TX_FILTERS: + case WM8996_DSP2_RX_FILTERS_1: + case WM8996_DSP2_RX_FILTERS_2: + case WM8996_DSP2_DRC_1: + case WM8996_DSP2_DRC_2: + case WM8996_DSP2_DRC_3: + case WM8996_DSP2_DRC_4: + case WM8996_DSP2_DRC_5: + case WM8996_DSP2_RX_EQ_GAINS_1: + case WM8996_DSP2_RX_EQ_GAINS_2: + case WM8996_DSP2_RX_EQ_BAND_1_A: + case WM8996_DSP2_RX_EQ_BAND_1_B: + case WM8996_DSP2_RX_EQ_BAND_1_PG: + case WM8996_DSP2_RX_EQ_BAND_2_A: + case WM8996_DSP2_RX_EQ_BAND_2_B: + case WM8996_DSP2_RX_EQ_BAND_2_C: + case WM8996_DSP2_RX_EQ_BAND_2_PG: + case WM8996_DSP2_RX_EQ_BAND_3_A: + case WM8996_DSP2_RX_EQ_BAND_3_B: + case WM8996_DSP2_RX_EQ_BAND_3_C: + case WM8996_DSP2_RX_EQ_BAND_3_PG: + case WM8996_DSP2_RX_EQ_BAND_4_A: + case WM8996_DSP2_RX_EQ_BAND_4_B: + case WM8996_DSP2_RX_EQ_BAND_4_C: + case WM8996_DSP2_RX_EQ_BAND_4_PG: + case WM8996_DSP2_RX_EQ_BAND_5_A: + case WM8996_DSP2_RX_EQ_BAND_5_B: + case WM8996_DSP2_RX_EQ_BAND_5_PG: + case WM8996_DAC1_MIXER_VOLUMES: + case WM8996_DAC1_LEFT_MIXER_ROUTING: + case WM8996_DAC1_RIGHT_MIXER_ROUTING: + case WM8996_DAC2_MIXER_VOLUMES: + case WM8996_DAC2_LEFT_MIXER_ROUTING: + case WM8996_DAC2_RIGHT_MIXER_ROUTING: + case WM8996_DSP1_TX_LEFT_MIXER_ROUTING: + case WM8996_DSP1_TX_RIGHT_MIXER_ROUTING: + case WM8996_DSP2_TX_LEFT_MIXER_ROUTING: + case WM8996_DSP2_TX_RIGHT_MIXER_ROUTING: + case WM8996_DSP_TX_MIXER_SELECT: + case WM8996_DAC_SOFTMUTE: + case WM8996_OVERSAMPLING: + case WM8996_SIDETONE: + case WM8996_GPIO_1: + case WM8996_GPIO_2: + case WM8996_GPIO_3: + case WM8996_GPIO_4: + case WM8996_GPIO_5: + case WM8996_PULL_CONTROL_1: + case WM8996_PULL_CONTROL_2: + case WM8996_INTERRUPT_STATUS_1: + case WM8996_INTERRUPT_STATUS_2: + case WM8996_INTERRUPT_RAW_STATUS_2: + case WM8996_INTERRUPT_STATUS_1_MASK: + case WM8996_INTERRUPT_STATUS_2_MASK: + case WM8996_INTERRUPT_CONTROL: + case WM8996_LEFT_PDM_SPEAKER: + case WM8996_RIGHT_PDM_SPEAKER: + case WM8996_PDM_SPEAKER_MUTE_SEQUENCE: + case WM8996_PDM_SPEAKER_VOLUME: + return true; + default: + return false; + } +} + +static bool wm8996_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8996_SOFTWARE_RESET: + case WM8996_CHIP_REVISION: + case WM8996_LDO_1: + case WM8996_LDO_2: + case WM8996_INTERRUPT_STATUS_1: + case WM8996_INTERRUPT_STATUS_2: + case WM8996_INTERRUPT_RAW_STATUS_2: + case WM8996_DC_SERVO_READBACK_0: + case WM8996_DC_SERVO_2: + case WM8996_DC_SERVO_6: + case WM8996_DC_SERVO_7: + case WM8996_FLL_CONTROL_6: + case WM8996_MIC_DETECT_3: + case WM8996_HEADPHONE_DETECT_1: + case WM8996_HEADPHONE_DETECT_2: + return true; + default: + return false; + } +} + +static const int bclk_divs[] = { + 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96 +}; + +static void wm8996_update_bclk(struct snd_soc_component *component) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int aif, best, cur_val, bclk_rate, bclk_reg, i; + + /* Don't bother if we're in a low frequency idle mode that + * can't support audio. + */ + if (wm8996->sysclk < 64000) + return; + + for (aif = 0; aif < WM8996_AIFS; aif++) { + switch (aif) { + case 0: + bclk_reg = WM8996_AIF1_BCLK; + break; + case 1: + bclk_reg = WM8996_AIF2_BCLK; + break; + } + + bclk_rate = wm8996->bclk_rate[aif]; + + /* Pick a divisor for BCLK as close as we can get to ideal */ + best = 0; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = (wm8996->sysclk / bclk_divs[i]) - bclk_rate; + if (cur_val < 0) /* BCLK table is sorted */ + break; + best = i; + } + bclk_rate = wm8996->sysclk / bclk_divs[best]; + dev_dbg(component->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n", + bclk_divs[best], bclk_rate); + + snd_soc_component_update_bits(component, bclk_reg, + WM8996_AIF1_BCLK_DIV_MASK, best); + } +} + +static int wm8996_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + /* Put the MICBIASes into regulating mode */ + snd_soc_component_update_bits(component, WM8996_MICBIAS_1, + WM8996_MICB1_MODE, 0); + snd_soc_component_update_bits(component, WM8996_MICBIAS_2, + WM8996_MICB2_MODE, 0); + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8996->supplies), + wm8996->supplies); + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + if (wm8996->pdata.ldo_ena >= 0) { + gpio_set_value_cansleep(wm8996->pdata.ldo_ena, + 1); + msleep(5); + } + + regcache_cache_only(wm8996->regmap, false); + regcache_sync(wm8996->regmap); + } + + /* Bypass the MICBIASes for lowest power */ + snd_soc_component_update_bits(component, WM8996_MICBIAS_1, + WM8996_MICB1_MODE, WM8996_MICB1_MODE); + snd_soc_component_update_bits(component, WM8996_MICBIAS_2, + WM8996_MICB2_MODE, WM8996_MICB2_MODE); + break; + + case SND_SOC_BIAS_OFF: + regcache_cache_only(wm8996->regmap, true); + if (wm8996->pdata.ldo_ena >= 0) { + gpio_set_value_cansleep(wm8996->pdata.ldo_ena, 0); + regcache_cache_only(wm8996->regmap, true); + } + regulator_bulk_disable(ARRAY_SIZE(wm8996->supplies), + wm8996->supplies); + break; + } + + return 0; +} + +static int wm8996_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + int aifctrl = 0; + int bclk = 0; + int lrclk_tx = 0; + int lrclk_rx = 0; + int aifctrl_reg, bclk_reg, lrclk_tx_reg, lrclk_rx_reg; + + switch (dai->id) { + case 0: + aifctrl_reg = WM8996_AIF1_CONTROL; + bclk_reg = WM8996_AIF1_BCLK; + lrclk_tx_reg = WM8996_AIF1_TX_LRCLK_2; + lrclk_rx_reg = WM8996_AIF1_RX_LRCLK_2; + break; + case 1: + aifctrl_reg = WM8996_AIF2_CONTROL; + bclk_reg = WM8996_AIF2_BCLK; + lrclk_tx_reg = WM8996_AIF2_TX_LRCLK_2; + lrclk_rx_reg = WM8996_AIF2_RX_LRCLK_2; + break; + default: + WARN(1, "Invalid dai id %d\n", dai->id); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + bclk |= WM8996_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + lrclk_tx |= WM8996_AIF1TX_LRCLK_INV; + lrclk_rx |= WM8996_AIF1RX_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + bclk |= WM8996_AIF1_BCLK_INV; + lrclk_tx |= WM8996_AIF1TX_LRCLK_INV; + lrclk_rx |= WM8996_AIF1RX_LRCLK_INV; + break; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + lrclk_tx |= WM8996_AIF1TX_LRCLK_MSTR; + lrclk_rx |= WM8996_AIF1RX_LRCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + bclk |= WM8996_AIF1_BCLK_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + bclk |= WM8996_AIF1_BCLK_MSTR; + lrclk_tx |= WM8996_AIF1TX_LRCLK_MSTR; + lrclk_rx |= WM8996_AIF1RX_LRCLK_MSTR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + break; + case SND_SOC_DAIFMT_DSP_B: + aifctrl |= 1; + break; + case SND_SOC_DAIFMT_I2S: + aifctrl |= 2; + break; + case SND_SOC_DAIFMT_LEFT_J: + aifctrl |= 3; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, aifctrl_reg, WM8996_AIF1_FMT_MASK, aifctrl); + snd_soc_component_update_bits(component, bclk_reg, + WM8996_AIF1_BCLK_INV | WM8996_AIF1_BCLK_MSTR, + bclk); + snd_soc_component_update_bits(component, lrclk_tx_reg, + WM8996_AIF1TX_LRCLK_INV | + WM8996_AIF1TX_LRCLK_MSTR, + lrclk_tx); + snd_soc_component_update_bits(component, lrclk_rx_reg, + WM8996_AIF1RX_LRCLK_INV | + WM8996_AIF1RX_LRCLK_MSTR, + lrclk_rx); + + return 0; +} + +static const int dsp_divs[] = { + 48000, 32000, 16000, 8000 +}; + +static int wm8996_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int bits, i, bclk_rate, best; + int aifdata = 0; + int lrclk = 0; + int dsp = 0; + int aifdata_reg, lrclk_reg, dsp_shift; + + switch (dai->id) { + case 0: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + (snd_soc_component_read32(component, WM8996_GPIO_1)) & WM8996_GP1_FN_MASK) { + aifdata_reg = WM8996_AIF1RX_DATA_CONFIGURATION; + lrclk_reg = WM8996_AIF1_RX_LRCLK_1; + } else { + aifdata_reg = WM8996_AIF1TX_DATA_CONFIGURATION_1; + lrclk_reg = WM8996_AIF1_TX_LRCLK_1; + } + dsp_shift = 0; + break; + case 1: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + (snd_soc_component_read32(component, WM8996_GPIO_2)) & WM8996_GP2_FN_MASK) { + aifdata_reg = WM8996_AIF2RX_DATA_CONFIGURATION; + lrclk_reg = WM8996_AIF2_RX_LRCLK_1; + } else { + aifdata_reg = WM8996_AIF2TX_DATA_CONFIGURATION_1; + lrclk_reg = WM8996_AIF2_TX_LRCLK_1; + } + dsp_shift = WM8996_DSP2_DIV_SHIFT; + break; + default: + WARN(1, "Invalid dai id %d\n", dai->id); + return -EINVAL; + } + + bclk_rate = snd_soc_params_to_bclk(params); + if (bclk_rate < 0) { + dev_err(component->dev, "Unsupported BCLK rate: %d\n", bclk_rate); + return bclk_rate; + } + + wm8996->bclk_rate[dai->id] = bclk_rate; + wm8996->rx_rate[dai->id] = params_rate(params); + + /* Needs looking at for TDM */ + bits = params_width(params); + if (bits < 0) + return bits; + aifdata |= (bits << WM8996_AIF1TX_WL_SHIFT) | bits; + + best = 0; + for (i = 0; i < ARRAY_SIZE(dsp_divs); i++) { + if (abs(dsp_divs[i] - params_rate(params)) < + abs(dsp_divs[best] - params_rate(params))) + best = i; + } + dsp |= i << dsp_shift; + + wm8996_update_bclk(component); + + lrclk = bclk_rate / params_rate(params); + dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n", + lrclk, bclk_rate / lrclk); + + snd_soc_component_update_bits(component, aifdata_reg, + WM8996_AIF1TX_WL_MASK | + WM8996_AIF1TX_SLOT_LEN_MASK, + aifdata); + snd_soc_component_update_bits(component, lrclk_reg, WM8996_AIF1RX_RATE_MASK, + lrclk); + snd_soc_component_update_bits(component, WM8996_AIF_CLOCKING_2, + WM8996_DSP1_DIV_MASK << dsp_shift, dsp); + + return 0; +} + +static int wm8996_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int lfclk = 0; + int ratediv = 0; + int sync = WM8996_REG_SYNC; + int src; + int old; + + if (freq == wm8996->sysclk && clk_id == wm8996->sysclk_src) + return 0; + + /* Disable SYSCLK while we reconfigure */ + old = snd_soc_component_read32(component, WM8996_AIF_CLOCKING_1) & WM8996_SYSCLK_ENA; + snd_soc_component_update_bits(component, WM8996_AIF_CLOCKING_1, + WM8996_SYSCLK_ENA, 0); + + switch (clk_id) { + case WM8996_SYSCLK_MCLK1: + wm8996->sysclk = freq; + src = 0; + break; + case WM8996_SYSCLK_MCLK2: + wm8996->sysclk = freq; + src = 1; + break; + case WM8996_SYSCLK_FLL: + wm8996->sysclk = freq; + src = 2; + break; + default: + dev_err(component->dev, "Unsupported clock source %d\n", clk_id); + return -EINVAL; + } + + switch (wm8996->sysclk) { + case 5644800: + case 6144000: + snd_soc_component_update_bits(component, WM8996_AIF_RATE, + WM8996_SYSCLK_RATE, 0); + break; + case 22579200: + case 24576000: + ratediv = WM8996_SYSCLK_DIV; + wm8996->sysclk /= 2; + /* fall through */ + case 11289600: + case 12288000: + snd_soc_component_update_bits(component, WM8996_AIF_RATE, + WM8996_SYSCLK_RATE, WM8996_SYSCLK_RATE); + break; + case 32000: + case 32768: + lfclk = WM8996_LFCLK_ENA; + sync = 0; + break; + default: + dev_warn(component->dev, "Unsupported clock rate %dHz\n", + wm8996->sysclk); + return -EINVAL; + } + + wm8996_update_bclk(component); + + snd_soc_component_update_bits(component, WM8996_AIF_CLOCKING_1, + WM8996_SYSCLK_SRC_MASK | WM8996_SYSCLK_DIV_MASK, + src << WM8996_SYSCLK_SRC_SHIFT | ratediv); + snd_soc_component_update_bits(component, WM8996_CLOCKING_1, WM8996_LFCLK_ENA, lfclk); + snd_soc_component_update_bits(component, WM8996_CONTROL_INTERFACE_1, + WM8996_REG_SYNC, sync); + snd_soc_component_update_bits(component, WM8996_AIF_CLOCKING_1, + WM8996_SYSCLK_ENA, old); + + wm8996->sysclk_src = clk_id; + + return 0; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_refclk_div; + u16 fll_loop_gain; + u16 fll_ref_freq; + u16 n; + u16 theta; + u16 lambda; +}; + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + unsigned int target; + unsigned int div; + unsigned int fratio, gcd_fll; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + fll_div->fll_refclk_div = 0; + while ((Fref / div) > 13500000) { + div *= 2; + fll_div->fll_refclk_div++; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("FLL Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + if (Fref >= 3000000) + fll_div->fll_loop_gain = 5; + else + fll_div->fll_loop_gain = 0; + + if (Fref >= 48000) + fll_div->fll_ref_freq = 0; + else + fll_div->fll_ref_freq = 1; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 2; + while (Fout * div < 90000000) { + div++; + if (div > 64) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + target = Fout * div; + fll_div->fll_outdiv = div - 1; + + pr_debug("FLL Fvco=%dHz\n", target); + + /* Find an appropraite FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + fratio = fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + fll_div->n = target / (fratio * Fref); + + if (target % Fref == 0) { + fll_div->theta = 0; + fll_div->lambda = 0; + } else { + gcd_fll = gcd(target, fratio * Fref); + + fll_div->theta = (target - (fll_div->n * fratio * Fref)) + / gcd_fll; + fll_div->lambda = (fratio * Fref) / gcd_fll; + } + + pr_debug("FLL N=%x THETA=%x LAMBDA=%x\n", + fll_div->n, fll_div->theta, fll_div->lambda); + pr_debug("FLL_FRATIO=%x FLL_OUTDIV=%x FLL_REFCLK_DIV=%x\n", + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_refclk_div); + + return 0; +} + +static int wm8996_set_fll(struct snd_soc_component *component, int fll_id, int source, + unsigned int Fref, unsigned int Fout) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + struct i2c_client *i2c = to_i2c_client(component->dev); + struct _fll_div fll_div; + unsigned long timeout, time_left; + int ret, reg, retry; + + /* Any change? */ + if (source == wm8996->fll_src && Fref == wm8996->fll_fref && + Fout == wm8996->fll_fout) + return 0; + + if (Fout == 0) { + dev_dbg(component->dev, "FLL disabled\n"); + + wm8996->fll_fref = 0; + wm8996->fll_fout = 0; + + snd_soc_component_update_bits(component, WM8996_FLL_CONTROL_1, + WM8996_FLL_ENA, 0); + + wm8996_bg_disable(component); + + return 0; + } + + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + + switch (source) { + case WM8996_FLL_MCLK1: + reg = 0; + break; + case WM8996_FLL_MCLK2: + reg = 1; + break; + case WM8996_FLL_DACLRCLK1: + reg = 2; + break; + case WM8996_FLL_BCLK1: + reg = 3; + break; + default: + dev_err(component->dev, "Unknown FLL source %d\n", ret); + return -EINVAL; + } + + reg |= fll_div.fll_refclk_div << WM8996_FLL_REFCLK_DIV_SHIFT; + reg |= fll_div.fll_ref_freq << WM8996_FLL_REF_FREQ_SHIFT; + + snd_soc_component_update_bits(component, WM8996_FLL_CONTROL_5, + WM8996_FLL_REFCLK_DIV_MASK | WM8996_FLL_REF_FREQ | + WM8996_FLL_REFCLK_SRC_MASK, reg); + + reg = 0; + if (fll_div.theta || fll_div.lambda) + reg |= WM8996_FLL_EFS_ENA | (3 << WM8996_FLL_LFSR_SEL_SHIFT); + else + reg |= 1 << WM8996_FLL_LFSR_SEL_SHIFT; + snd_soc_component_write(component, WM8996_FLL_EFS_2, reg); + + snd_soc_component_update_bits(component, WM8996_FLL_CONTROL_2, + WM8996_FLL_OUTDIV_MASK | + WM8996_FLL_FRATIO_MASK, + (fll_div.fll_outdiv << WM8996_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio)); + + snd_soc_component_write(component, WM8996_FLL_CONTROL_3, fll_div.theta); + + snd_soc_component_update_bits(component, WM8996_FLL_CONTROL_4, + WM8996_FLL_N_MASK | WM8996_FLL_LOOP_GAIN_MASK, + (fll_div.n << WM8996_FLL_N_SHIFT) | + fll_div.fll_loop_gain); + + snd_soc_component_write(component, WM8996_FLL_EFS_1, fll_div.lambda); + + /* Enable the bandgap if it's not already enabled */ + ret = snd_soc_component_read32(component, WM8996_FLL_CONTROL_1); + if (!(ret & WM8996_FLL_ENA)) + wm8996_bg_enable(component); + + /* Clear any pending completions (eg, from failed startups) */ + try_wait_for_completion(&wm8996->fll_lock); + + snd_soc_component_update_bits(component, WM8996_FLL_CONTROL_1, + WM8996_FLL_ENA, WM8996_FLL_ENA); + + /* The FLL supports live reconfiguration - kick that in case we were + * already enabled. + */ + snd_soc_component_write(component, WM8996_FLL_CONTROL_6, WM8996_FLL_SWITCH_CLK); + + /* Wait for the FLL to lock, using the interrupt if possible */ + if (Fref > 1000000) + timeout = usecs_to_jiffies(300); + else + timeout = msecs_to_jiffies(2); + + /* Allow substantially longer if we've actually got the IRQ, poll + * at a slightly higher rate if we don't. + */ + if (i2c->irq) + timeout *= 10; + else + /* ensure timeout of atleast 1 jiffies */ + timeout = timeout/2 ? : 1; + + for (retry = 0; retry < 10; retry++) { + time_left = wait_for_completion_timeout(&wm8996->fll_lock, + timeout); + if (time_left != 0) { + WARN_ON(!i2c->irq); + ret = 1; + break; + } + + ret = snd_soc_component_read32(component, WM8996_INTERRUPT_RAW_STATUS_2); + if (ret & WM8996_FLL_LOCK_STS) + break; + } + if (retry == 10) { + dev_err(component->dev, "Timed out waiting for FLL\n"); + ret = -ETIMEDOUT; + } + + dev_dbg(component->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout); + + wm8996->fll_fref = Fref; + wm8996->fll_fout = Fout; + wm8996->fll_src = source; + + return ret; +} + +#ifdef CONFIG_GPIOLIB +static void wm8996_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8996_priv *wm8996 = gpiochip_get_data(chip); + + regmap_update_bits(wm8996->regmap, WM8996_GPIO_1 + offset, + WM8996_GP1_LVL, !!value << WM8996_GP1_LVL_SHIFT); +} + +static int wm8996_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8996_priv *wm8996 = gpiochip_get_data(chip); + int val; + + val = (1 << WM8996_GP1_FN_SHIFT) | (!!value << WM8996_GP1_LVL_SHIFT); + + return regmap_update_bits(wm8996->regmap, WM8996_GPIO_1 + offset, + WM8996_GP1_FN_MASK | WM8996_GP1_DIR | + WM8996_GP1_LVL, val); +} + +static int wm8996_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8996_priv *wm8996 = gpiochip_get_data(chip); + unsigned int reg; + int ret; + + ret = regmap_read(wm8996->regmap, WM8996_GPIO_1 + offset, ®); + if (ret < 0) + return ret; + + return (reg & WM8996_GP1_LVL) != 0; +} + +static int wm8996_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8996_priv *wm8996 = gpiochip_get_data(chip); + + return regmap_update_bits(wm8996->regmap, WM8996_GPIO_1 + offset, + WM8996_GP1_FN_MASK | WM8996_GP1_DIR, + (1 << WM8996_GP1_FN_SHIFT) | + (1 << WM8996_GP1_DIR_SHIFT)); +} + +static const struct gpio_chip wm8996_template_chip = { + .label = "wm8996", + .owner = THIS_MODULE, + .direction_output = wm8996_gpio_direction_out, + .set = wm8996_gpio_set, + .direction_input = wm8996_gpio_direction_in, + .get = wm8996_gpio_get, + .can_sleep = 1, +}; + +static void wm8996_init_gpio(struct wm8996_priv *wm8996) +{ + int ret; + + wm8996->gpio_chip = wm8996_template_chip; + wm8996->gpio_chip.ngpio = 5; + wm8996->gpio_chip.parent = wm8996->dev; + + if (wm8996->pdata.gpio_base) + wm8996->gpio_chip.base = wm8996->pdata.gpio_base; + else + wm8996->gpio_chip.base = -1; + + ret = gpiochip_add_data(&wm8996->gpio_chip, wm8996); + if (ret != 0) + dev_err(wm8996->dev, "Failed to add GPIOs: %d\n", ret); +} + +static void wm8996_free_gpio(struct wm8996_priv *wm8996) +{ + gpiochip_remove(&wm8996->gpio_chip); +} +#else +static void wm8996_init_gpio(struct wm8996_priv *wm8996) +{ +} + +static void wm8996_free_gpio(struct wm8996_priv *wm8996) +{ +} +#endif + +/** + * wm8996_detect - Enable default WM8996 jack detection + * + * The WM8996 has advanced accessory detection support for headsets. + * This function provides a default implementation which integrates + * the majority of this functionality with minimal user configuration. + * + * This will detect headset, headphone and short circuit button and + * will also detect inverted microphone ground connections and update + * the polarity of the connections. + */ +int wm8996_detect(struct snd_soc_component *component, struct snd_soc_jack *jack, + wm8996_polarity_fn polarity_cb) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + wm8996->jack = jack; + wm8996->detecting = true; + wm8996->polarity_cb = polarity_cb; + wm8996->jack_flips = 0; + + if (wm8996->polarity_cb) + wm8996->polarity_cb(component, 0); + + /* Clear discarge to avoid noise during detection */ + snd_soc_component_update_bits(component, WM8996_MICBIAS_1, + WM8996_MICB1_DISCH, 0); + snd_soc_component_update_bits(component, WM8996_MICBIAS_2, + WM8996_MICB2_DISCH, 0); + + /* LDO2 powers the microphones, SYSCLK clocks detection */ + snd_soc_dapm_mutex_lock(dapm); + + snd_soc_dapm_force_enable_pin_unlocked(dapm, "LDO2"); + snd_soc_dapm_force_enable_pin_unlocked(dapm, "SYSCLK"); + + snd_soc_dapm_mutex_unlock(dapm); + + /* We start off just enabling microphone detection - even a + * plain headphone will trigger detection. + */ + snd_soc_component_update_bits(component, WM8996_MIC_DETECT_1, + WM8996_MICD_ENA, WM8996_MICD_ENA); + + /* Slowest detection rate, gives debounce for initial detection */ + snd_soc_component_update_bits(component, WM8996_MIC_DETECT_1, + WM8996_MICD_RATE_MASK, + WM8996_MICD_RATE_MASK); + + /* Enable interrupts and we're off */ + snd_soc_component_update_bits(component, WM8996_INTERRUPT_STATUS_2_MASK, + WM8996_IM_MICD_EINT | WM8996_HP_DONE_EINT, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8996_detect); + +static void wm8996_hpdet_irq(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int val, reg, report; + + /* Assume headphone in error conditions; we need to report + * something or we stall our state machine. + */ + report = SND_JACK_HEADPHONE; + + reg = snd_soc_component_read32(component, WM8996_HEADPHONE_DETECT_2); + if (reg < 0) { + dev_err(component->dev, "Failed to read HPDET status\n"); + goto out; + } + + if (!(reg & WM8996_HP_DONE)) { + dev_err(component->dev, "Got HPDET IRQ but HPDET is busy\n"); + goto out; + } + + val = reg & WM8996_HP_LVL_MASK; + + dev_dbg(component->dev, "HPDET measured %d ohms\n", val); + + /* If we've got high enough impedence then report as line, + * otherwise assume headphone. + */ + if (val >= 126) + report = SND_JACK_LINEOUT; + else + report = SND_JACK_HEADPHONE; + +out: + if (wm8996->jack_mic) + report |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(wm8996->jack, report, + SND_JACK_LINEOUT | SND_JACK_HEADSET); + + wm8996->detecting = false; + + /* If the output isn't running re-clamp it */ + if (!(snd_soc_component_read32(component, WM8996_POWER_MANAGEMENT_1) & + (WM8996_HPOUT1L_ENA | WM8996_HPOUT1R_RMV_SHORT))) + snd_soc_component_update_bits(component, WM8996_ANALOGUE_HP_1, + WM8996_HPOUT1L_RMV_SHORT | + WM8996_HPOUT1R_RMV_SHORT, 0); + + /* Go back to looking at the microphone */ + snd_soc_component_update_bits(component, WM8996_ACCESSORY_DETECT_MODE_1, + WM8996_JD_MODE_MASK, 0); + snd_soc_component_update_bits(component, WM8996_MIC_DETECT_1, WM8996_MICD_ENA, + WM8996_MICD_ENA); + + snd_soc_dapm_disable_pin(dapm, "Bandgap"); + snd_soc_dapm_sync(dapm); +} + +static void wm8996_hpdet_start(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + /* Unclamp the output, we can't measure while we're shorting it */ + snd_soc_component_update_bits(component, WM8996_ANALOGUE_HP_1, + WM8996_HPOUT1L_RMV_SHORT | + WM8996_HPOUT1R_RMV_SHORT, + WM8996_HPOUT1L_RMV_SHORT | + WM8996_HPOUT1R_RMV_SHORT); + + /* We need bandgap for HPDET */ + snd_soc_dapm_force_enable_pin(dapm, "Bandgap"); + snd_soc_dapm_sync(dapm); + + /* Go into headphone detect left mode */ + snd_soc_component_update_bits(component, WM8996_MIC_DETECT_1, WM8996_MICD_ENA, 0); + snd_soc_component_update_bits(component, WM8996_ACCESSORY_DETECT_MODE_1, + WM8996_JD_MODE_MASK, 1); + + /* Trigger a measurement */ + snd_soc_component_update_bits(component, WM8996_HEADPHONE_DETECT_1, + WM8996_HP_POLL, WM8996_HP_POLL); +} + +static void wm8996_report_headphone(struct snd_soc_component *component) +{ + dev_dbg(component->dev, "Headphone detected\n"); + wm8996_hpdet_start(component); + + /* Increase the detection rate a bit for responsiveness. */ + snd_soc_component_update_bits(component, WM8996_MIC_DETECT_1, + WM8996_MICD_RATE_MASK | + WM8996_MICD_BIAS_STARTTIME_MASK, + 7 << WM8996_MICD_RATE_SHIFT | + 7 << WM8996_MICD_BIAS_STARTTIME_SHIFT); +} + +static void wm8996_micd(struct snd_soc_component *component) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int val, reg; + + val = snd_soc_component_read32(component, WM8996_MIC_DETECT_3); + + dev_dbg(component->dev, "Microphone event: %x\n", val); + + if (!(val & WM8996_MICD_VALID)) { + dev_warn(component->dev, "Microphone detection state invalid\n"); + return; + } + + /* No accessory, reset everything and report removal */ + if (!(val & WM8996_MICD_STS)) { + dev_dbg(component->dev, "Jack removal detected\n"); + wm8996->jack_mic = false; + wm8996->detecting = true; + wm8996->jack_flips = 0; + snd_soc_jack_report(wm8996->jack, 0, + SND_JACK_LINEOUT | SND_JACK_HEADSET | + SND_JACK_BTN_0); + + snd_soc_component_update_bits(component, WM8996_MIC_DETECT_1, + WM8996_MICD_RATE_MASK | + WM8996_MICD_BIAS_STARTTIME_MASK, + WM8996_MICD_RATE_MASK | + 9 << WM8996_MICD_BIAS_STARTTIME_SHIFT); + return; + } + + /* If the measurement is very high we've got a microphone, + * either we just detected one or if we already reported then + * we've got a button release event. + */ + if (val & 0x400) { + if (wm8996->detecting) { + dev_dbg(component->dev, "Microphone detected\n"); + wm8996->jack_mic = true; + wm8996_hpdet_start(component); + + /* Increase poll rate to give better responsiveness + * for buttons */ + snd_soc_component_update_bits(component, WM8996_MIC_DETECT_1, + WM8996_MICD_RATE_MASK | + WM8996_MICD_BIAS_STARTTIME_MASK, + 5 << WM8996_MICD_RATE_SHIFT | + 7 << WM8996_MICD_BIAS_STARTTIME_SHIFT); + } else { + dev_dbg(component->dev, "Mic button up\n"); + snd_soc_jack_report(wm8996->jack, 0, SND_JACK_BTN_0); + } + + return; + } + + /* If we detected a lower impedence during initial startup + * then we probably have the wrong polarity, flip it. Don't + * do this for the lowest impedences to speed up detection of + * plain headphones. If both polarities report a low + * impedence then give up and report headphones. + */ + if (wm8996->detecting && (val & 0x3f0)) { + wm8996->jack_flips++; + + if (wm8996->jack_flips > 1) { + wm8996_report_headphone(component); + return; + } + + reg = snd_soc_component_read32(component, WM8996_ACCESSORY_DETECT_MODE_2); + reg ^= WM8996_HPOUT1FB_SRC | WM8996_MICD_SRC | + WM8996_MICD_BIAS_SRC; + snd_soc_component_update_bits(component, WM8996_ACCESSORY_DETECT_MODE_2, + WM8996_HPOUT1FB_SRC | WM8996_MICD_SRC | + WM8996_MICD_BIAS_SRC, reg); + + if (wm8996->polarity_cb) + wm8996->polarity_cb(component, + (reg & WM8996_MICD_SRC) != 0); + + dev_dbg(component->dev, "Set microphone polarity to %d\n", + (reg & WM8996_MICD_SRC) != 0); + + return; + } + + /* Don't distinguish between buttons, just report any low + * impedence as BTN_0. + */ + if (val & 0x3fc) { + if (wm8996->jack_mic) { + dev_dbg(component->dev, "Mic button detected\n"); + snd_soc_jack_report(wm8996->jack, SND_JACK_BTN_0, + SND_JACK_BTN_0); + } else if (wm8996->detecting) { + wm8996_report_headphone(component); + } + } +} + +static irqreturn_t wm8996_irq(int irq, void *data) +{ + struct snd_soc_component *component = data; + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + int irq_val; + + irq_val = snd_soc_component_read32(component, WM8996_INTERRUPT_STATUS_2); + if (irq_val < 0) { + dev_err(component->dev, "Failed to read IRQ status: %d\n", + irq_val); + return IRQ_NONE; + } + irq_val &= ~snd_soc_component_read32(component, WM8996_INTERRUPT_STATUS_2_MASK); + + if (!irq_val) + return IRQ_NONE; + + snd_soc_component_write(component, WM8996_INTERRUPT_STATUS_2, irq_val); + + if (irq_val & (WM8996_DCS_DONE_01_EINT | WM8996_DCS_DONE_23_EINT)) { + dev_dbg(component->dev, "DC servo IRQ\n"); + complete(&wm8996->dcs_done); + } + + if (irq_val & WM8996_FIFOS_ERR_EINT) + dev_err(component->dev, "Digital core FIFO error\n"); + + if (irq_val & WM8996_FLL_LOCK_EINT) { + dev_dbg(component->dev, "FLL locked\n"); + complete(&wm8996->fll_lock); + } + + if (irq_val & WM8996_MICD_EINT) + wm8996_micd(component); + + if (irq_val & WM8996_HP_DONE_EINT) + wm8996_hpdet_irq(component); + + return IRQ_HANDLED; +} + +static irqreturn_t wm8996_edge_irq(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + irqreturn_t val; + + do { + val = wm8996_irq(irq, data); + if (val != IRQ_NONE) + ret = val; + } while (val != IRQ_NONE); + + return ret; +} + +static void wm8996_retune_mobile_pdata(struct snd_soc_component *component) +{ + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + struct wm8996_pdata *pdata = &wm8996->pdata; + + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT("DSP1 EQ Mode", + wm8996->retune_mobile_enum, + wm8996_get_retune_mobile_enum, + wm8996_put_retune_mobile_enum), + SOC_ENUM_EXT("DSP2 EQ Mode", + wm8996->retune_mobile_enum, + wm8996_get_retune_mobile_enum, + wm8996_put_retune_mobile_enum), + }; + int ret, i, j; + const char **t; + + /* We need an array of texts for the enum API but the number + * of texts is likely to be less than the number of + * configurations due to the sample rate dependency of the + * configurations. */ + wm8996->num_retune_mobile_texts = 0; + wm8996->retune_mobile_texts = NULL; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + for (j = 0; j < wm8996->num_retune_mobile_texts; j++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8996->retune_mobile_texts[j]) == 0) + break; + } + + if (j != wm8996->num_retune_mobile_texts) + continue; + + /* Expand the array... */ + t = krealloc(wm8996->retune_mobile_texts, + sizeof(char *) * + (wm8996->num_retune_mobile_texts + 1), + GFP_KERNEL); + if (t == NULL) + continue; + + /* ...store the new entry... */ + t[wm8996->num_retune_mobile_texts] = + pdata->retune_mobile_cfgs[i].name; + + /* ...and remember the new version. */ + wm8996->num_retune_mobile_texts++; + wm8996->retune_mobile_texts = t; + } + + dev_dbg(component->dev, "Allocated %d unique ReTune Mobile names\n", + wm8996->num_retune_mobile_texts); + + wm8996->retune_mobile_enum.items = wm8996->num_retune_mobile_texts; + wm8996->retune_mobile_enum.texts = wm8996->retune_mobile_texts; + + ret = snd_soc_add_component_controls(component, controls, ARRAY_SIZE(controls)); + if (ret != 0) + dev_err(component->dev, + "Failed to add ReTune Mobile controls: %d\n", ret); +} + +static const struct regmap_config wm8996_regmap = { + .reg_bits = 16, + .val_bits = 16, + + .max_register = WM8996_MAX_REGISTER, + .reg_defaults = wm8996_reg, + .num_reg_defaults = ARRAY_SIZE(wm8996_reg), + .volatile_reg = wm8996_volatile_register, + .readable_reg = wm8996_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int wm8996_probe(struct snd_soc_component *component) +{ + int ret; + struct wm8996_priv *wm8996 = snd_soc_component_get_drvdata(component); + struct i2c_client *i2c = to_i2c_client(component->dev); + int irq_flags; + + wm8996->component = component; + + init_completion(&wm8996->dcs_done); + init_completion(&wm8996->fll_lock); + + if (wm8996->pdata.num_retune_mobile_cfgs) + wm8996_retune_mobile_pdata(component); + else + snd_soc_add_component_controls(component, wm8996_eq_controls, + ARRAY_SIZE(wm8996_eq_controls)); + + if (i2c->irq) { + if (wm8996->pdata.irq_flags) + irq_flags = wm8996->pdata.irq_flags; + else + irq_flags = IRQF_TRIGGER_LOW; + + irq_flags |= IRQF_ONESHOT; + + if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) + ret = request_threaded_irq(i2c->irq, NULL, + wm8996_edge_irq, + irq_flags, "wm8996", component); + else + ret = request_threaded_irq(i2c->irq, NULL, wm8996_irq, + irq_flags, "wm8996", component); + + if (ret == 0) { + /* Unmask the interrupt */ + snd_soc_component_update_bits(component, WM8996_INTERRUPT_CONTROL, + WM8996_IM_IRQ, 0); + + /* Enable error reporting and DC servo status */ + snd_soc_component_update_bits(component, + WM8996_INTERRUPT_STATUS_2_MASK, + WM8996_IM_DCS_DONE_23_EINT | + WM8996_IM_DCS_DONE_01_EINT | + WM8996_IM_FLL_LOCK_EINT | + WM8996_IM_FIFOS_ERR_EINT, + 0); + } else { + dev_err(component->dev, "Failed to request IRQ: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static void wm8996_remove(struct snd_soc_component *component) +{ + struct i2c_client *i2c = to_i2c_client(component->dev); + + snd_soc_component_update_bits(component, WM8996_INTERRUPT_CONTROL, + WM8996_IM_IRQ, WM8996_IM_IRQ); + + if (i2c->irq) + free_irq(i2c->irq, component); +} + +static const struct snd_soc_component_driver soc_component_dev_wm8996 = { + .probe = wm8996_probe, + .remove = wm8996_remove, + .set_bias_level = wm8996_set_bias_level, + .seq_notifier = wm8996_seq_notifier, + .controls = wm8996_snd_controls, + .num_controls = ARRAY_SIZE(wm8996_snd_controls), + .dapm_widgets = wm8996_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8996_dapm_widgets), + .dapm_routes = wm8996_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8996_dapm_routes), + .set_pll = wm8996_set_fll, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, + +}; + +#define WM8996_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) +#define WM8996_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8996_dai_ops = { + .set_fmt = wm8996_set_fmt, + .hw_params = wm8996_hw_params, + .set_sysclk = wm8996_set_sysclk, +}; + +static struct snd_soc_dai_driver wm8996_dai[] = { + { + .name = "wm8996-aif1", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = WM8996_RATES, + .formats = WM8996_FORMATS, + .sig_bits = 24, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 6, + .rates = WM8996_RATES, + .formats = WM8996_FORMATS, + .sig_bits = 24, + }, + .ops = &wm8996_dai_ops, + }, + { + .name = "wm8996-aif2", + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8996_RATES, + .formats = WM8996_FORMATS, + .sig_bits = 24, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8996_RATES, + .formats = WM8996_FORMATS, + .sig_bits = 24, + }, + .ops = &wm8996_dai_ops, + }, +}; + +static int wm8996_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8996_priv *wm8996; + int ret, i; + unsigned int reg; + + wm8996 = devm_kzalloc(&i2c->dev, sizeof(struct wm8996_priv), + GFP_KERNEL); + if (wm8996 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm8996); + wm8996->dev = &i2c->dev; + + if (dev_get_platdata(&i2c->dev)) + memcpy(&wm8996->pdata, dev_get_platdata(&i2c->dev), + sizeof(wm8996->pdata)); + + if (wm8996->pdata.ldo_ena > 0) { + ret = gpio_request_one(wm8996->pdata.ldo_ena, + GPIOF_OUT_INIT_LOW, "WM8996 ENA"); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request GPIO %d: %d\n", + wm8996->pdata.ldo_ena, ret); + goto err; + } + } + + for (i = 0; i < ARRAY_SIZE(wm8996->supplies); i++) + wm8996->supplies[i].supply = wm8996_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8996->supplies), + wm8996->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + goto err_gpio; + } + + wm8996->disable_nb[0].notifier_call = wm8996_regulator_event_0; + wm8996->disable_nb[1].notifier_call = wm8996_regulator_event_1; + wm8996->disable_nb[2].notifier_call = wm8996_regulator_event_2; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8996->supplies); i++) { + ret = regulator_register_notifier(wm8996->supplies[i].consumer, + &wm8996->disable_nb[i]); + if (ret != 0) { + dev_err(&i2c->dev, + "Failed to register regulator notifier: %d\n", + ret); + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8996->supplies), + wm8996->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + goto err_gpio; + } + + if (wm8996->pdata.ldo_ena > 0) { + gpio_set_value_cansleep(wm8996->pdata.ldo_ena, 1); + msleep(5); + } + + wm8996->regmap = devm_regmap_init_i2c(i2c, &wm8996_regmap); + if (IS_ERR(wm8996->regmap)) { + ret = PTR_ERR(wm8996->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + goto err_enable; + } + + ret = regmap_read(wm8996->regmap, WM8996_SOFTWARE_RESET, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read ID register: %d\n", ret); + goto err_regmap; + } + if (reg != 0x8915) { + dev_err(&i2c->dev, "Device is not a WM8996, ID %x\n", reg); + ret = -EINVAL; + goto err_regmap; + } + + ret = regmap_read(wm8996->regmap, WM8996_CHIP_REVISION, ®); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to read device revision: %d\n", + ret); + goto err_regmap; + } + + dev_info(&i2c->dev, "revision %c\n", + (reg & WM8996_CHIP_REV_MASK) + 'A'); + + if (wm8996->pdata.ldo_ena > 0) { + gpio_set_value_cansleep(wm8996->pdata.ldo_ena, 0); + regcache_cache_only(wm8996->regmap, true); + } else { + ret = regmap_write(wm8996->regmap, WM8996_SOFTWARE_RESET, + 0x8915); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret); + goto err_regmap; + } + } + + regulator_bulk_disable(ARRAY_SIZE(wm8996->supplies), wm8996->supplies); + + /* Apply platform data settings */ + regmap_update_bits(wm8996->regmap, WM8996_LINE_INPUT_CONTROL, + WM8996_INL_MODE_MASK | WM8996_INR_MODE_MASK, + wm8996->pdata.inl_mode << WM8996_INL_MODE_SHIFT | + wm8996->pdata.inr_mode); + + for (i = 0; i < ARRAY_SIZE(wm8996->pdata.gpio_default); i++) { + if (!wm8996->pdata.gpio_default[i]) + continue; + + regmap_write(wm8996->regmap, WM8996_GPIO_1 + i, + wm8996->pdata.gpio_default[i] & 0xffff); + } + + if (wm8996->pdata.spkmute_seq) + regmap_update_bits(wm8996->regmap, + WM8996_PDM_SPEAKER_MUTE_SEQUENCE, + WM8996_SPK_MUTE_ENDIAN | + WM8996_SPK_MUTE_SEQ1_MASK, + wm8996->pdata.spkmute_seq); + + regmap_update_bits(wm8996->regmap, WM8996_ACCESSORY_DETECT_MODE_2, + WM8996_MICD_BIAS_SRC | WM8996_HPOUT1FB_SRC | + WM8996_MICD_SRC, wm8996->pdata.micdet_def); + + /* Latch volume update bits */ + regmap_update_bits(wm8996->regmap, WM8996_LEFT_LINE_INPUT_VOLUME, + WM8996_IN1_VU, WM8996_IN1_VU); + regmap_update_bits(wm8996->regmap, WM8996_RIGHT_LINE_INPUT_VOLUME, + WM8996_IN1_VU, WM8996_IN1_VU); + + regmap_update_bits(wm8996->regmap, WM8996_DAC1_LEFT_VOLUME, + WM8996_DAC1_VU, WM8996_DAC1_VU); + regmap_update_bits(wm8996->regmap, WM8996_DAC1_RIGHT_VOLUME, + WM8996_DAC1_VU, WM8996_DAC1_VU); + regmap_update_bits(wm8996->regmap, WM8996_DAC2_LEFT_VOLUME, + WM8996_DAC2_VU, WM8996_DAC2_VU); + regmap_update_bits(wm8996->regmap, WM8996_DAC2_RIGHT_VOLUME, + WM8996_DAC2_VU, WM8996_DAC2_VU); + + regmap_update_bits(wm8996->regmap, WM8996_OUTPUT1_LEFT_VOLUME, + WM8996_DAC1_VU, WM8996_DAC1_VU); + regmap_update_bits(wm8996->regmap, WM8996_OUTPUT1_RIGHT_VOLUME, + WM8996_DAC1_VU, WM8996_DAC1_VU); + regmap_update_bits(wm8996->regmap, WM8996_OUTPUT2_LEFT_VOLUME, + WM8996_DAC2_VU, WM8996_DAC2_VU); + regmap_update_bits(wm8996->regmap, WM8996_OUTPUT2_RIGHT_VOLUME, + WM8996_DAC2_VU, WM8996_DAC2_VU); + + regmap_update_bits(wm8996->regmap, WM8996_DSP1_TX_LEFT_VOLUME, + WM8996_DSP1TX_VU, WM8996_DSP1TX_VU); + regmap_update_bits(wm8996->regmap, WM8996_DSP1_TX_RIGHT_VOLUME, + WM8996_DSP1TX_VU, WM8996_DSP1TX_VU); + regmap_update_bits(wm8996->regmap, WM8996_DSP2_TX_LEFT_VOLUME, + WM8996_DSP2TX_VU, WM8996_DSP2TX_VU); + regmap_update_bits(wm8996->regmap, WM8996_DSP2_TX_RIGHT_VOLUME, + WM8996_DSP2TX_VU, WM8996_DSP2TX_VU); + + regmap_update_bits(wm8996->regmap, WM8996_DSP1_RX_LEFT_VOLUME, + WM8996_DSP1RX_VU, WM8996_DSP1RX_VU); + regmap_update_bits(wm8996->regmap, WM8996_DSP1_RX_RIGHT_VOLUME, + WM8996_DSP1RX_VU, WM8996_DSP1RX_VU); + regmap_update_bits(wm8996->regmap, WM8996_DSP2_RX_LEFT_VOLUME, + WM8996_DSP2RX_VU, WM8996_DSP2RX_VU); + regmap_update_bits(wm8996->regmap, WM8996_DSP2_RX_RIGHT_VOLUME, + WM8996_DSP2RX_VU, WM8996_DSP2RX_VU); + + /* No support currently for the underclocked TDM modes and + * pick a default TDM layout with each channel pair working with + * slots 0 and 1. */ + regmap_update_bits(wm8996->regmap, + WM8996_AIF1RX_CHANNEL_0_CONFIGURATION, + WM8996_AIF1RX_CHAN0_SLOTS_MASK | + WM8996_AIF1RX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1RX_CHAN0_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1RX_CHANNEL_1_CONFIGURATION, + WM8996_AIF1RX_CHAN1_SLOTS_MASK | + WM8996_AIF1RX_CHAN1_START_SLOT_MASK, + 1 << WM8996_AIF1RX_CHAN1_SLOTS_SHIFT | 1); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1RX_CHANNEL_2_CONFIGURATION, + WM8996_AIF1RX_CHAN2_SLOTS_MASK | + WM8996_AIF1RX_CHAN2_START_SLOT_MASK, + 1 << WM8996_AIF1RX_CHAN2_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1RX_CHANNEL_3_CONFIGURATION, + WM8996_AIF1RX_CHAN3_SLOTS_MASK | + WM8996_AIF1RX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1RX_CHAN3_SLOTS_SHIFT | 1); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1RX_CHANNEL_4_CONFIGURATION, + WM8996_AIF1RX_CHAN4_SLOTS_MASK | + WM8996_AIF1RX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1RX_CHAN4_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1RX_CHANNEL_5_CONFIGURATION, + WM8996_AIF1RX_CHAN5_SLOTS_MASK | + WM8996_AIF1RX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1RX_CHAN5_SLOTS_SHIFT | 1); + + regmap_update_bits(wm8996->regmap, + WM8996_AIF2RX_CHANNEL_0_CONFIGURATION, + WM8996_AIF2RX_CHAN0_SLOTS_MASK | + WM8996_AIF2RX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF2RX_CHAN0_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF2RX_CHANNEL_1_CONFIGURATION, + WM8996_AIF2RX_CHAN1_SLOTS_MASK | + WM8996_AIF2RX_CHAN1_START_SLOT_MASK, + 1 << WM8996_AIF2RX_CHAN1_SLOTS_SHIFT | 1); + + regmap_update_bits(wm8996->regmap, + WM8996_AIF1TX_CHANNEL_0_CONFIGURATION, + WM8996_AIF1TX_CHAN0_SLOTS_MASK | + WM8996_AIF1TX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1TX_CHAN0_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1TX_CHANNEL_1_CONFIGURATION, + WM8996_AIF1TX_CHAN1_SLOTS_MASK | + WM8996_AIF1TX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1TX_CHAN1_SLOTS_SHIFT | 1); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1TX_CHANNEL_2_CONFIGURATION, + WM8996_AIF1TX_CHAN2_SLOTS_MASK | + WM8996_AIF1TX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1TX_CHAN2_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1TX_CHANNEL_3_CONFIGURATION, + WM8996_AIF1TX_CHAN3_SLOTS_MASK | + WM8996_AIF1TX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1TX_CHAN3_SLOTS_SHIFT | 1); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1TX_CHANNEL_4_CONFIGURATION, + WM8996_AIF1TX_CHAN4_SLOTS_MASK | + WM8996_AIF1TX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1TX_CHAN4_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1TX_CHANNEL_5_CONFIGURATION, + WM8996_AIF1TX_CHAN5_SLOTS_MASK | + WM8996_AIF1TX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF1TX_CHAN5_SLOTS_SHIFT | 1); + + regmap_update_bits(wm8996->regmap, + WM8996_AIF2TX_CHANNEL_0_CONFIGURATION, + WM8996_AIF2TX_CHAN0_SLOTS_MASK | + WM8996_AIF2TX_CHAN0_START_SLOT_MASK, + 1 << WM8996_AIF2TX_CHAN0_SLOTS_SHIFT | 0); + regmap_update_bits(wm8996->regmap, + WM8996_AIF1TX_CHANNEL_1_CONFIGURATION, + WM8996_AIF2TX_CHAN1_SLOTS_MASK | + WM8996_AIF2TX_CHAN1_START_SLOT_MASK, + 1 << WM8996_AIF1TX_CHAN1_SLOTS_SHIFT | 1); + + /* If the TX LRCLK pins are not in LRCLK mode configure the + * AIFs to source their clocks from the RX LRCLKs. + */ + ret = regmap_read(wm8996->regmap, WM8996_GPIO_1, ®); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read GPIO1: %d\n", ret); + goto err_regmap; + } + + if (reg & WM8996_GP1_FN_MASK) + regmap_update_bits(wm8996->regmap, WM8996_AIF1_TX_LRCLK_2, + WM8996_AIF1TX_LRCLK_MODE, + WM8996_AIF1TX_LRCLK_MODE); + + ret = regmap_read(wm8996->regmap, WM8996_GPIO_2, ®); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read GPIO2: %d\n", ret); + goto err_regmap; + } + + if (reg & WM8996_GP2_FN_MASK) + regmap_update_bits(wm8996->regmap, WM8996_AIF2_TX_LRCLK_2, + WM8996_AIF2TX_LRCLK_MODE, + WM8996_AIF2TX_LRCLK_MODE); + + wm8996_init_gpio(wm8996); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8996, wm8996_dai, + ARRAY_SIZE(wm8996_dai)); + if (ret < 0) + goto err_gpiolib; + + return ret; + +err_gpiolib: + wm8996_free_gpio(wm8996); +err_regmap: +err_enable: + if (wm8996->pdata.ldo_ena > 0) + gpio_set_value_cansleep(wm8996->pdata.ldo_ena, 0); + regulator_bulk_disable(ARRAY_SIZE(wm8996->supplies), wm8996->supplies); +err_gpio: + if (wm8996->pdata.ldo_ena > 0) + gpio_free(wm8996->pdata.ldo_ena); +err: + + return ret; +} + +static int wm8996_i2c_remove(struct i2c_client *client) +{ + struct wm8996_priv *wm8996 = i2c_get_clientdata(client); + int i; + + wm8996_free_gpio(wm8996); + if (wm8996->pdata.ldo_ena > 0) { + gpio_set_value_cansleep(wm8996->pdata.ldo_ena, 0); + gpio_free(wm8996->pdata.ldo_ena); + } + for (i = 0; i < ARRAY_SIZE(wm8996->supplies); i++) + regulator_unregister_notifier(wm8996->supplies[i].consumer, + &wm8996->disable_nb[i]); + + return 0; +} + +static const struct i2c_device_id wm8996_i2c_id[] = { + { "wm8996", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8996_i2c_id); + +static struct i2c_driver wm8996_i2c_driver = { + .driver = { + .name = "wm8996", + }, + .probe = wm8996_i2c_probe, + .remove = wm8996_i2c_remove, + .id_table = wm8996_i2c_id, +}; + +module_i2c_driver(wm8996_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8996 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8996.h b/sound/soc/codecs/wm8996.h new file mode 100644 index 000000000..b38769958 --- /dev/null +++ b/sound/soc/codecs/wm8996.h @@ -0,0 +1,3721 @@ +/* + * wm8996.h - WM8996 audio codec interface + * + * Copyright 2011 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8996_H +#define _WM8996_H + +#define WM8996_SYSCLK_MCLK1 1 +#define WM8996_SYSCLK_MCLK2 2 +#define WM8996_SYSCLK_FLL 3 + +#define WM8996_FLL_MCLK1 1 +#define WM8996_FLL_MCLK2 2 +#define WM8996_FLL_DACLRCLK1 3 +#define WM8996_FLL_BCLK1 4 + +typedef void (*wm8996_polarity_fn)(struct snd_soc_component *component, int polarity); + +int wm8996_detect(struct snd_soc_component *component, struct snd_soc_jack *jack, + wm8996_polarity_fn polarity_cb); + +/* + * Register values. + */ +#define WM8996_SOFTWARE_RESET 0x00 +#define WM8996_POWER_MANAGEMENT_1 0x01 +#define WM8996_POWER_MANAGEMENT_2 0x02 +#define WM8996_POWER_MANAGEMENT_3 0x03 +#define WM8996_POWER_MANAGEMENT_4 0x04 +#define WM8996_POWER_MANAGEMENT_5 0x05 +#define WM8996_POWER_MANAGEMENT_6 0x06 +#define WM8996_POWER_MANAGEMENT_7 0x07 +#define WM8996_POWER_MANAGEMENT_8 0x08 +#define WM8996_LEFT_LINE_INPUT_VOLUME 0x10 +#define WM8996_RIGHT_LINE_INPUT_VOLUME 0x11 +#define WM8996_LINE_INPUT_CONTROL 0x12 +#define WM8996_DAC1_HPOUT1_VOLUME 0x15 +#define WM8996_DAC2_HPOUT2_VOLUME 0x16 +#define WM8996_DAC1_LEFT_VOLUME 0x18 +#define WM8996_DAC1_RIGHT_VOLUME 0x19 +#define WM8996_DAC2_LEFT_VOLUME 0x1A +#define WM8996_DAC2_RIGHT_VOLUME 0x1B +#define WM8996_OUTPUT1_LEFT_VOLUME 0x1C +#define WM8996_OUTPUT1_RIGHT_VOLUME 0x1D +#define WM8996_OUTPUT2_LEFT_VOLUME 0x1E +#define WM8996_OUTPUT2_RIGHT_VOLUME 0x1F +#define WM8996_MICBIAS_1 0x20 +#define WM8996_MICBIAS_2 0x21 +#define WM8996_LDO_1 0x28 +#define WM8996_LDO_2 0x29 +#define WM8996_ACCESSORY_DETECT_MODE_1 0x30 +#define WM8996_ACCESSORY_DETECT_MODE_2 0x31 +#define WM8996_HEADPHONE_DETECT_1 0x34 +#define WM8996_HEADPHONE_DETECT_2 0x35 +#define WM8996_MIC_DETECT_1 0x38 +#define WM8996_MIC_DETECT_2 0x39 +#define WM8996_MIC_DETECT_3 0x3A +#define WM8996_CHARGE_PUMP_1 0x40 +#define WM8996_CHARGE_PUMP_2 0x41 +#define WM8996_DC_SERVO_1 0x50 +#define WM8996_DC_SERVO_2 0x51 +#define WM8996_DC_SERVO_3 0x52 +#define WM8996_DC_SERVO_5 0x54 +#define WM8996_DC_SERVO_6 0x55 +#define WM8996_DC_SERVO_7 0x56 +#define WM8996_DC_SERVO_READBACK_0 0x57 +#define WM8996_ANALOGUE_HP_1 0x60 +#define WM8996_ANALOGUE_HP_2 0x61 +#define WM8996_CHIP_REVISION 0x100 +#define WM8996_CONTROL_INTERFACE_1 0x101 +#define WM8996_WRITE_SEQUENCER_CTRL_1 0x110 +#define WM8996_WRITE_SEQUENCER_CTRL_2 0x111 +#define WM8996_AIF_CLOCKING_1 0x200 +#define WM8996_AIF_CLOCKING_2 0x201 +#define WM8996_CLOCKING_1 0x208 +#define WM8996_CLOCKING_2 0x209 +#define WM8996_AIF_RATE 0x210 +#define WM8996_FLL_CONTROL_1 0x220 +#define WM8996_FLL_CONTROL_2 0x221 +#define WM8996_FLL_CONTROL_3 0x222 +#define WM8996_FLL_CONTROL_4 0x223 +#define WM8996_FLL_CONTROL_5 0x224 +#define WM8996_FLL_CONTROL_6 0x225 +#define WM8996_FLL_EFS_1 0x226 +#define WM8996_FLL_EFS_2 0x227 +#define WM8996_AIF1_CONTROL 0x300 +#define WM8996_AIF1_BCLK 0x301 +#define WM8996_AIF1_TX_LRCLK_1 0x302 +#define WM8996_AIF1_TX_LRCLK_2 0x303 +#define WM8996_AIF1_RX_LRCLK_1 0x304 +#define WM8996_AIF1_RX_LRCLK_2 0x305 +#define WM8996_AIF1TX_DATA_CONFIGURATION_1 0x306 +#define WM8996_AIF1TX_DATA_CONFIGURATION_2 0x307 +#define WM8996_AIF1RX_DATA_CONFIGURATION 0x308 +#define WM8996_AIF1TX_CHANNEL_0_CONFIGURATION 0x309 +#define WM8996_AIF1TX_CHANNEL_1_CONFIGURATION 0x30A +#define WM8996_AIF1TX_CHANNEL_2_CONFIGURATION 0x30B +#define WM8996_AIF1TX_CHANNEL_3_CONFIGURATION 0x30C +#define WM8996_AIF1TX_CHANNEL_4_CONFIGURATION 0x30D +#define WM8996_AIF1TX_CHANNEL_5_CONFIGURATION 0x30E +#define WM8996_AIF1RX_CHANNEL_0_CONFIGURATION 0x30F +#define WM8996_AIF1RX_CHANNEL_1_CONFIGURATION 0x310 +#define WM8996_AIF1RX_CHANNEL_2_CONFIGURATION 0x311 +#define WM8996_AIF1RX_CHANNEL_3_CONFIGURATION 0x312 +#define WM8996_AIF1RX_CHANNEL_4_CONFIGURATION 0x313 +#define WM8996_AIF1RX_CHANNEL_5_CONFIGURATION 0x314 +#define WM8996_AIF1RX_MONO_CONFIGURATION 0x315 +#define WM8996_AIF1TX_TEST 0x31A +#define WM8996_AIF2_CONTROL 0x320 +#define WM8996_AIF2_BCLK 0x321 +#define WM8996_AIF2_TX_LRCLK_1 0x322 +#define WM8996_AIF2_TX_LRCLK_2 0x323 +#define WM8996_AIF2_RX_LRCLK_1 0x324 +#define WM8996_AIF2_RX_LRCLK_2 0x325 +#define WM8996_AIF2TX_DATA_CONFIGURATION_1 0x326 +#define WM8996_AIF2TX_DATA_CONFIGURATION_2 0x327 +#define WM8996_AIF2RX_DATA_CONFIGURATION 0x328 +#define WM8996_AIF2TX_CHANNEL_0_CONFIGURATION 0x329 +#define WM8996_AIF2TX_CHANNEL_1_CONFIGURATION 0x32A +#define WM8996_AIF2RX_CHANNEL_0_CONFIGURATION 0x32B +#define WM8996_AIF2RX_CHANNEL_1_CONFIGURATION 0x32C +#define WM8996_AIF2RX_MONO_CONFIGURATION 0x32D +#define WM8996_AIF2TX_TEST 0x32F +#define WM8996_DSP1_TX_LEFT_VOLUME 0x400 +#define WM8996_DSP1_TX_RIGHT_VOLUME 0x401 +#define WM8996_DSP1_RX_LEFT_VOLUME 0x402 +#define WM8996_DSP1_RX_RIGHT_VOLUME 0x403 +#define WM8996_DSP1_TX_FILTERS 0x410 +#define WM8996_DSP1_RX_FILTERS_1 0x420 +#define WM8996_DSP1_RX_FILTERS_2 0x421 +#define WM8996_DSP1_DRC_1 0x440 +#define WM8996_DSP1_DRC_2 0x441 +#define WM8996_DSP1_DRC_3 0x442 +#define WM8996_DSP1_DRC_4 0x443 +#define WM8996_DSP1_DRC_5 0x444 +#define WM8996_DSP1_RX_EQ_GAINS_1 0x480 +#define WM8996_DSP1_RX_EQ_GAINS_2 0x481 +#define WM8996_DSP1_RX_EQ_BAND_1_A 0x482 +#define WM8996_DSP1_RX_EQ_BAND_1_B 0x483 +#define WM8996_DSP1_RX_EQ_BAND_1_PG 0x484 +#define WM8996_DSP1_RX_EQ_BAND_2_A 0x485 +#define WM8996_DSP1_RX_EQ_BAND_2_B 0x486 +#define WM8996_DSP1_RX_EQ_BAND_2_C 0x487 +#define WM8996_DSP1_RX_EQ_BAND_2_PG 0x488 +#define WM8996_DSP1_RX_EQ_BAND_3_A 0x489 +#define WM8996_DSP1_RX_EQ_BAND_3_B 0x48A +#define WM8996_DSP1_RX_EQ_BAND_3_C 0x48B +#define WM8996_DSP1_RX_EQ_BAND_3_PG 0x48C +#define WM8996_DSP1_RX_EQ_BAND_4_A 0x48D +#define WM8996_DSP1_RX_EQ_BAND_4_B 0x48E +#define WM8996_DSP1_RX_EQ_BAND_4_C 0x48F +#define WM8996_DSP1_RX_EQ_BAND_4_PG 0x490 +#define WM8996_DSP1_RX_EQ_BAND_5_A 0x491 +#define WM8996_DSP1_RX_EQ_BAND_5_B 0x492 +#define WM8996_DSP1_RX_EQ_BAND_5_PG 0x493 +#define WM8996_DSP2_TX_LEFT_VOLUME 0x500 +#define WM8996_DSP2_TX_RIGHT_VOLUME 0x501 +#define WM8996_DSP2_RX_LEFT_VOLUME 0x502 +#define WM8996_DSP2_RX_RIGHT_VOLUME 0x503 +#define WM8996_DSP2_TX_FILTERS 0x510 +#define WM8996_DSP2_RX_FILTERS_1 0x520 +#define WM8996_DSP2_RX_FILTERS_2 0x521 +#define WM8996_DSP2_DRC_1 0x540 +#define WM8996_DSP2_DRC_2 0x541 +#define WM8996_DSP2_DRC_3 0x542 +#define WM8996_DSP2_DRC_4 0x543 +#define WM8996_DSP2_DRC_5 0x544 +#define WM8996_DSP2_RX_EQ_GAINS_1 0x580 +#define WM8996_DSP2_RX_EQ_GAINS_2 0x581 +#define WM8996_DSP2_RX_EQ_BAND_1_A 0x582 +#define WM8996_DSP2_RX_EQ_BAND_1_B 0x583 +#define WM8996_DSP2_RX_EQ_BAND_1_PG 0x584 +#define WM8996_DSP2_RX_EQ_BAND_2_A 0x585 +#define WM8996_DSP2_RX_EQ_BAND_2_B 0x586 +#define WM8996_DSP2_RX_EQ_BAND_2_C 0x587 +#define WM8996_DSP2_RX_EQ_BAND_2_PG 0x588 +#define WM8996_DSP2_RX_EQ_BAND_3_A 0x589 +#define WM8996_DSP2_RX_EQ_BAND_3_B 0x58A +#define WM8996_DSP2_RX_EQ_BAND_3_C 0x58B +#define WM8996_DSP2_RX_EQ_BAND_3_PG 0x58C +#define WM8996_DSP2_RX_EQ_BAND_4_A 0x58D +#define WM8996_DSP2_RX_EQ_BAND_4_B 0x58E +#define WM8996_DSP2_RX_EQ_BAND_4_C 0x58F +#define WM8996_DSP2_RX_EQ_BAND_4_PG 0x590 +#define WM8996_DSP2_RX_EQ_BAND_5_A 0x591 +#define WM8996_DSP2_RX_EQ_BAND_5_B 0x592 +#define WM8996_DSP2_RX_EQ_BAND_5_PG 0x593 +#define WM8996_DAC1_MIXER_VOLUMES 0x600 +#define WM8996_DAC1_LEFT_MIXER_ROUTING 0x601 +#define WM8996_DAC1_RIGHT_MIXER_ROUTING 0x602 +#define WM8996_DAC2_MIXER_VOLUMES 0x603 +#define WM8996_DAC2_LEFT_MIXER_ROUTING 0x604 +#define WM8996_DAC2_RIGHT_MIXER_ROUTING 0x605 +#define WM8996_DSP1_TX_LEFT_MIXER_ROUTING 0x606 +#define WM8996_DSP1_TX_RIGHT_MIXER_ROUTING 0x607 +#define WM8996_DSP2_TX_LEFT_MIXER_ROUTING 0x608 +#define WM8996_DSP2_TX_RIGHT_MIXER_ROUTING 0x609 +#define WM8996_DSP_TX_MIXER_SELECT 0x60A +#define WM8996_DAC_SOFTMUTE 0x610 +#define WM8996_OVERSAMPLING 0x620 +#define WM8996_SIDETONE 0x621 +#define WM8996_GPIO_1 0x700 +#define WM8996_GPIO_2 0x701 +#define WM8996_GPIO_3 0x702 +#define WM8996_GPIO_4 0x703 +#define WM8996_GPIO_5 0x704 +#define WM8996_PULL_CONTROL_1 0x720 +#define WM8996_PULL_CONTROL_2 0x721 +#define WM8996_INTERRUPT_STATUS_1 0x730 +#define WM8996_INTERRUPT_STATUS_2 0x731 +#define WM8996_INTERRUPT_RAW_STATUS_2 0x732 +#define WM8996_INTERRUPT_STATUS_1_MASK 0x738 +#define WM8996_INTERRUPT_STATUS_2_MASK 0x739 +#define WM8996_INTERRUPT_CONTROL 0x740 +#define WM8996_LEFT_PDM_SPEAKER 0x800 +#define WM8996_RIGHT_PDM_SPEAKER 0x801 +#define WM8996_PDM_SPEAKER_MUTE_SEQUENCE 0x802 +#define WM8996_PDM_SPEAKER_VOLUME 0x803 +#define WM8996_WRITE_SEQUENCER_0 0x3000 +#define WM8996_WRITE_SEQUENCER_1 0x3001 +#define WM8996_WRITE_SEQUENCER_2 0x3002 +#define WM8996_WRITE_SEQUENCER_3 0x3003 +#define WM8996_WRITE_SEQUENCER_4 0x3004 +#define WM8996_WRITE_SEQUENCER_5 0x3005 +#define WM8996_WRITE_SEQUENCER_6 0x3006 +#define WM8996_WRITE_SEQUENCER_7 0x3007 +#define WM8996_WRITE_SEQUENCER_8 0x3008 +#define WM8996_WRITE_SEQUENCER_9 0x3009 +#define WM8996_WRITE_SEQUENCER_10 0x300A +#define WM8996_WRITE_SEQUENCER_11 0x300B +#define WM8996_WRITE_SEQUENCER_12 0x300C +#define WM8996_WRITE_SEQUENCER_13 0x300D +#define WM8996_WRITE_SEQUENCER_14 0x300E +#define WM8996_WRITE_SEQUENCER_15 0x300F +#define WM8996_WRITE_SEQUENCER_16 0x3010 +#define WM8996_WRITE_SEQUENCER_17 0x3011 +#define WM8996_WRITE_SEQUENCER_18 0x3012 +#define WM8996_WRITE_SEQUENCER_19 0x3013 +#define WM8996_WRITE_SEQUENCER_20 0x3014 +#define WM8996_WRITE_SEQUENCER_21 0x3015 +#define WM8996_WRITE_SEQUENCER_22 0x3016 +#define WM8996_WRITE_SEQUENCER_23 0x3017 +#define WM8996_WRITE_SEQUENCER_24 0x3018 +#define WM8996_WRITE_SEQUENCER_25 0x3019 +#define WM8996_WRITE_SEQUENCER_26 0x301A +#define WM8996_WRITE_SEQUENCER_27 0x301B +#define WM8996_WRITE_SEQUENCER_28 0x301C +#define WM8996_WRITE_SEQUENCER_29 0x301D +#define WM8996_WRITE_SEQUENCER_30 0x301E +#define WM8996_WRITE_SEQUENCER_31 0x301F +#define WM8996_WRITE_SEQUENCER_32 0x3020 +#define WM8996_WRITE_SEQUENCER_33 0x3021 +#define WM8996_WRITE_SEQUENCER_34 0x3022 +#define WM8996_WRITE_SEQUENCER_35 0x3023 +#define WM8996_WRITE_SEQUENCER_36 0x3024 +#define WM8996_WRITE_SEQUENCER_37 0x3025 +#define WM8996_WRITE_SEQUENCER_38 0x3026 +#define WM8996_WRITE_SEQUENCER_39 0x3027 +#define WM8996_WRITE_SEQUENCER_40 0x3028 +#define WM8996_WRITE_SEQUENCER_41 0x3029 +#define WM8996_WRITE_SEQUENCER_42 0x302A +#define WM8996_WRITE_SEQUENCER_43 0x302B +#define WM8996_WRITE_SEQUENCER_44 0x302C +#define WM8996_WRITE_SEQUENCER_45 0x302D +#define WM8996_WRITE_SEQUENCER_46 0x302E +#define WM8996_WRITE_SEQUENCER_47 0x302F +#define WM8996_WRITE_SEQUENCER_48 0x3030 +#define WM8996_WRITE_SEQUENCER_49 0x3031 +#define WM8996_WRITE_SEQUENCER_50 0x3032 +#define WM8996_WRITE_SEQUENCER_51 0x3033 +#define WM8996_WRITE_SEQUENCER_52 0x3034 +#define WM8996_WRITE_SEQUENCER_53 0x3035 +#define WM8996_WRITE_SEQUENCER_54 0x3036 +#define WM8996_WRITE_SEQUENCER_55 0x3037 +#define WM8996_WRITE_SEQUENCER_56 0x3038 +#define WM8996_WRITE_SEQUENCER_57 0x3039 +#define WM8996_WRITE_SEQUENCER_58 0x303A +#define WM8996_WRITE_SEQUENCER_59 0x303B +#define WM8996_WRITE_SEQUENCER_60 0x303C +#define WM8996_WRITE_SEQUENCER_61 0x303D +#define WM8996_WRITE_SEQUENCER_62 0x303E +#define WM8996_WRITE_SEQUENCER_63 0x303F +#define WM8996_WRITE_SEQUENCER_64 0x3040 +#define WM8996_WRITE_SEQUENCER_65 0x3041 +#define WM8996_WRITE_SEQUENCER_66 0x3042 +#define WM8996_WRITE_SEQUENCER_67 0x3043 +#define WM8996_WRITE_SEQUENCER_68 0x3044 +#define WM8996_WRITE_SEQUENCER_69 0x3045 +#define WM8996_WRITE_SEQUENCER_70 0x3046 +#define WM8996_WRITE_SEQUENCER_71 0x3047 +#define WM8996_WRITE_SEQUENCER_72 0x3048 +#define WM8996_WRITE_SEQUENCER_73 0x3049 +#define WM8996_WRITE_SEQUENCER_74 0x304A +#define WM8996_WRITE_SEQUENCER_75 0x304B +#define WM8996_WRITE_SEQUENCER_76 0x304C +#define WM8996_WRITE_SEQUENCER_77 0x304D +#define WM8996_WRITE_SEQUENCER_78 0x304E +#define WM8996_WRITE_SEQUENCER_79 0x304F +#define WM8996_WRITE_SEQUENCER_80 0x3050 +#define WM8996_WRITE_SEQUENCER_81 0x3051 +#define WM8996_WRITE_SEQUENCER_82 0x3052 +#define WM8996_WRITE_SEQUENCER_83 0x3053 +#define WM8996_WRITE_SEQUENCER_84 0x3054 +#define WM8996_WRITE_SEQUENCER_85 0x3055 +#define WM8996_WRITE_SEQUENCER_86 0x3056 +#define WM8996_WRITE_SEQUENCER_87 0x3057 +#define WM8996_WRITE_SEQUENCER_88 0x3058 +#define WM8996_WRITE_SEQUENCER_89 0x3059 +#define WM8996_WRITE_SEQUENCER_90 0x305A +#define WM8996_WRITE_SEQUENCER_91 0x305B +#define WM8996_WRITE_SEQUENCER_92 0x305C +#define WM8996_WRITE_SEQUENCER_93 0x305D +#define WM8996_WRITE_SEQUENCER_94 0x305E +#define WM8996_WRITE_SEQUENCER_95 0x305F +#define WM8996_WRITE_SEQUENCER_96 0x3060 +#define WM8996_WRITE_SEQUENCER_97 0x3061 +#define WM8996_WRITE_SEQUENCER_98 0x3062 +#define WM8996_WRITE_SEQUENCER_99 0x3063 +#define WM8996_WRITE_SEQUENCER_100 0x3064 +#define WM8996_WRITE_SEQUENCER_101 0x3065 +#define WM8996_WRITE_SEQUENCER_102 0x3066 +#define WM8996_WRITE_SEQUENCER_103 0x3067 +#define WM8996_WRITE_SEQUENCER_104 0x3068 +#define WM8996_WRITE_SEQUENCER_105 0x3069 +#define WM8996_WRITE_SEQUENCER_106 0x306A +#define WM8996_WRITE_SEQUENCER_107 0x306B +#define WM8996_WRITE_SEQUENCER_108 0x306C +#define WM8996_WRITE_SEQUENCER_109 0x306D +#define WM8996_WRITE_SEQUENCER_110 0x306E +#define WM8996_WRITE_SEQUENCER_111 0x306F +#define WM8996_WRITE_SEQUENCER_112 0x3070 +#define WM8996_WRITE_SEQUENCER_113 0x3071 +#define WM8996_WRITE_SEQUENCER_114 0x3072 +#define WM8996_WRITE_SEQUENCER_115 0x3073 +#define WM8996_WRITE_SEQUENCER_116 0x3074 +#define WM8996_WRITE_SEQUENCER_117 0x3075 +#define WM8996_WRITE_SEQUENCER_118 0x3076 +#define WM8996_WRITE_SEQUENCER_119 0x3077 +#define WM8996_WRITE_SEQUENCER_120 0x3078 +#define WM8996_WRITE_SEQUENCER_121 0x3079 +#define WM8996_WRITE_SEQUENCER_122 0x307A +#define WM8996_WRITE_SEQUENCER_123 0x307B +#define WM8996_WRITE_SEQUENCER_124 0x307C +#define WM8996_WRITE_SEQUENCER_125 0x307D +#define WM8996_WRITE_SEQUENCER_126 0x307E +#define WM8996_WRITE_SEQUENCER_127 0x307F +#define WM8996_WRITE_SEQUENCER_128 0x3080 +#define WM8996_WRITE_SEQUENCER_129 0x3081 +#define WM8996_WRITE_SEQUENCER_130 0x3082 +#define WM8996_WRITE_SEQUENCER_131 0x3083 +#define WM8996_WRITE_SEQUENCER_132 0x3084 +#define WM8996_WRITE_SEQUENCER_133 0x3085 +#define WM8996_WRITE_SEQUENCER_134 0x3086 +#define WM8996_WRITE_SEQUENCER_135 0x3087 +#define WM8996_WRITE_SEQUENCER_136 0x3088 +#define WM8996_WRITE_SEQUENCER_137 0x3089 +#define WM8996_WRITE_SEQUENCER_138 0x308A +#define WM8996_WRITE_SEQUENCER_139 0x308B +#define WM8996_WRITE_SEQUENCER_140 0x308C +#define WM8996_WRITE_SEQUENCER_141 0x308D +#define WM8996_WRITE_SEQUENCER_142 0x308E +#define WM8996_WRITE_SEQUENCER_143 0x308F +#define WM8996_WRITE_SEQUENCER_144 0x3090 +#define WM8996_WRITE_SEQUENCER_145 0x3091 +#define WM8996_WRITE_SEQUENCER_146 0x3092 +#define WM8996_WRITE_SEQUENCER_147 0x3093 +#define WM8996_WRITE_SEQUENCER_148 0x3094 +#define WM8996_WRITE_SEQUENCER_149 0x3095 +#define WM8996_WRITE_SEQUENCER_150 0x3096 +#define WM8996_WRITE_SEQUENCER_151 0x3097 +#define WM8996_WRITE_SEQUENCER_152 0x3098 +#define WM8996_WRITE_SEQUENCER_153 0x3099 +#define WM8996_WRITE_SEQUENCER_154 0x309A +#define WM8996_WRITE_SEQUENCER_155 0x309B +#define WM8996_WRITE_SEQUENCER_156 0x309C +#define WM8996_WRITE_SEQUENCER_157 0x309D +#define WM8996_WRITE_SEQUENCER_158 0x309E +#define WM8996_WRITE_SEQUENCER_159 0x309F +#define WM8996_WRITE_SEQUENCER_160 0x30A0 +#define WM8996_WRITE_SEQUENCER_161 0x30A1 +#define WM8996_WRITE_SEQUENCER_162 0x30A2 +#define WM8996_WRITE_SEQUENCER_163 0x30A3 +#define WM8996_WRITE_SEQUENCER_164 0x30A4 +#define WM8996_WRITE_SEQUENCER_165 0x30A5 +#define WM8996_WRITE_SEQUENCER_166 0x30A6 +#define WM8996_WRITE_SEQUENCER_167 0x30A7 +#define WM8996_WRITE_SEQUENCER_168 0x30A8 +#define WM8996_WRITE_SEQUENCER_169 0x30A9 +#define WM8996_WRITE_SEQUENCER_170 0x30AA +#define WM8996_WRITE_SEQUENCER_171 0x30AB +#define WM8996_WRITE_SEQUENCER_172 0x30AC +#define WM8996_WRITE_SEQUENCER_173 0x30AD +#define WM8996_WRITE_SEQUENCER_174 0x30AE +#define WM8996_WRITE_SEQUENCER_175 0x30AF +#define WM8996_WRITE_SEQUENCER_176 0x30B0 +#define WM8996_WRITE_SEQUENCER_177 0x30B1 +#define WM8996_WRITE_SEQUENCER_178 0x30B2 +#define WM8996_WRITE_SEQUENCER_179 0x30B3 +#define WM8996_WRITE_SEQUENCER_180 0x30B4 +#define WM8996_WRITE_SEQUENCER_181 0x30B5 +#define WM8996_WRITE_SEQUENCER_182 0x30B6 +#define WM8996_WRITE_SEQUENCER_183 0x30B7 +#define WM8996_WRITE_SEQUENCER_184 0x30B8 +#define WM8996_WRITE_SEQUENCER_185 0x30B9 +#define WM8996_WRITE_SEQUENCER_186 0x30BA +#define WM8996_WRITE_SEQUENCER_187 0x30BB +#define WM8996_WRITE_SEQUENCER_188 0x30BC +#define WM8996_WRITE_SEQUENCER_189 0x30BD +#define WM8996_WRITE_SEQUENCER_190 0x30BE +#define WM8996_WRITE_SEQUENCER_191 0x30BF +#define WM8996_WRITE_SEQUENCER_192 0x30C0 +#define WM8996_WRITE_SEQUENCER_193 0x30C1 +#define WM8996_WRITE_SEQUENCER_194 0x30C2 +#define WM8996_WRITE_SEQUENCER_195 0x30C3 +#define WM8996_WRITE_SEQUENCER_196 0x30C4 +#define WM8996_WRITE_SEQUENCER_197 0x30C5 +#define WM8996_WRITE_SEQUENCER_198 0x30C6 +#define WM8996_WRITE_SEQUENCER_199 0x30C7 +#define WM8996_WRITE_SEQUENCER_200 0x30C8 +#define WM8996_WRITE_SEQUENCER_201 0x30C9 +#define WM8996_WRITE_SEQUENCER_202 0x30CA +#define WM8996_WRITE_SEQUENCER_203 0x30CB +#define WM8996_WRITE_SEQUENCER_204 0x30CC +#define WM8996_WRITE_SEQUENCER_205 0x30CD +#define WM8996_WRITE_SEQUENCER_206 0x30CE +#define WM8996_WRITE_SEQUENCER_207 0x30CF +#define WM8996_WRITE_SEQUENCER_208 0x30D0 +#define WM8996_WRITE_SEQUENCER_209 0x30D1 +#define WM8996_WRITE_SEQUENCER_210 0x30D2 +#define WM8996_WRITE_SEQUENCER_211 0x30D3 +#define WM8996_WRITE_SEQUENCER_212 0x30D4 +#define WM8996_WRITE_SEQUENCER_213 0x30D5 +#define WM8996_WRITE_SEQUENCER_214 0x30D6 +#define WM8996_WRITE_SEQUENCER_215 0x30D7 +#define WM8996_WRITE_SEQUENCER_216 0x30D8 +#define WM8996_WRITE_SEQUENCER_217 0x30D9 +#define WM8996_WRITE_SEQUENCER_218 0x30DA +#define WM8996_WRITE_SEQUENCER_219 0x30DB +#define WM8996_WRITE_SEQUENCER_220 0x30DC +#define WM8996_WRITE_SEQUENCER_221 0x30DD +#define WM8996_WRITE_SEQUENCER_222 0x30DE +#define WM8996_WRITE_SEQUENCER_223 0x30DF +#define WM8996_WRITE_SEQUENCER_224 0x30E0 +#define WM8996_WRITE_SEQUENCER_225 0x30E1 +#define WM8996_WRITE_SEQUENCER_226 0x30E2 +#define WM8996_WRITE_SEQUENCER_227 0x30E3 +#define WM8996_WRITE_SEQUENCER_228 0x30E4 +#define WM8996_WRITE_SEQUENCER_229 0x30E5 +#define WM8996_WRITE_SEQUENCER_230 0x30E6 +#define WM8996_WRITE_SEQUENCER_231 0x30E7 +#define WM8996_WRITE_SEQUENCER_232 0x30E8 +#define WM8996_WRITE_SEQUENCER_233 0x30E9 +#define WM8996_WRITE_SEQUENCER_234 0x30EA +#define WM8996_WRITE_SEQUENCER_235 0x30EB +#define WM8996_WRITE_SEQUENCER_236 0x30EC +#define WM8996_WRITE_SEQUENCER_237 0x30ED +#define WM8996_WRITE_SEQUENCER_238 0x30EE +#define WM8996_WRITE_SEQUENCER_239 0x30EF +#define WM8996_WRITE_SEQUENCER_240 0x30F0 +#define WM8996_WRITE_SEQUENCER_241 0x30F1 +#define WM8996_WRITE_SEQUENCER_242 0x30F2 +#define WM8996_WRITE_SEQUENCER_243 0x30F3 +#define WM8996_WRITE_SEQUENCER_244 0x30F4 +#define WM8996_WRITE_SEQUENCER_245 0x30F5 +#define WM8996_WRITE_SEQUENCER_246 0x30F6 +#define WM8996_WRITE_SEQUENCER_247 0x30F7 +#define WM8996_WRITE_SEQUENCER_248 0x30F8 +#define WM8996_WRITE_SEQUENCER_249 0x30F9 +#define WM8996_WRITE_SEQUENCER_250 0x30FA +#define WM8996_WRITE_SEQUENCER_251 0x30FB +#define WM8996_WRITE_SEQUENCER_252 0x30FC +#define WM8996_WRITE_SEQUENCER_253 0x30FD +#define WM8996_WRITE_SEQUENCER_254 0x30FE +#define WM8996_WRITE_SEQUENCER_255 0x30FF +#define WM8996_WRITE_SEQUENCER_256 0x3100 +#define WM8996_WRITE_SEQUENCER_257 0x3101 +#define WM8996_WRITE_SEQUENCER_258 0x3102 +#define WM8996_WRITE_SEQUENCER_259 0x3103 +#define WM8996_WRITE_SEQUENCER_260 0x3104 +#define WM8996_WRITE_SEQUENCER_261 0x3105 +#define WM8996_WRITE_SEQUENCER_262 0x3106 +#define WM8996_WRITE_SEQUENCER_263 0x3107 +#define WM8996_WRITE_SEQUENCER_264 0x3108 +#define WM8996_WRITE_SEQUENCER_265 0x3109 +#define WM8996_WRITE_SEQUENCER_266 0x310A +#define WM8996_WRITE_SEQUENCER_267 0x310B +#define WM8996_WRITE_SEQUENCER_268 0x310C +#define WM8996_WRITE_SEQUENCER_269 0x310D +#define WM8996_WRITE_SEQUENCER_270 0x310E +#define WM8996_WRITE_SEQUENCER_271 0x310F +#define WM8996_WRITE_SEQUENCER_272 0x3110 +#define WM8996_WRITE_SEQUENCER_273 0x3111 +#define WM8996_WRITE_SEQUENCER_274 0x3112 +#define WM8996_WRITE_SEQUENCER_275 0x3113 +#define WM8996_WRITE_SEQUENCER_276 0x3114 +#define WM8996_WRITE_SEQUENCER_277 0x3115 +#define WM8996_WRITE_SEQUENCER_278 0x3116 +#define WM8996_WRITE_SEQUENCER_279 0x3117 +#define WM8996_WRITE_SEQUENCER_280 0x3118 +#define WM8996_WRITE_SEQUENCER_281 0x3119 +#define WM8996_WRITE_SEQUENCER_282 0x311A +#define WM8996_WRITE_SEQUENCER_283 0x311B +#define WM8996_WRITE_SEQUENCER_284 0x311C +#define WM8996_WRITE_SEQUENCER_285 0x311D +#define WM8996_WRITE_SEQUENCER_286 0x311E +#define WM8996_WRITE_SEQUENCER_287 0x311F +#define WM8996_WRITE_SEQUENCER_288 0x3120 +#define WM8996_WRITE_SEQUENCER_289 0x3121 +#define WM8996_WRITE_SEQUENCER_290 0x3122 +#define WM8996_WRITE_SEQUENCER_291 0x3123 +#define WM8996_WRITE_SEQUENCER_292 0x3124 +#define WM8996_WRITE_SEQUENCER_293 0x3125 +#define WM8996_WRITE_SEQUENCER_294 0x3126 +#define WM8996_WRITE_SEQUENCER_295 0x3127 +#define WM8996_WRITE_SEQUENCER_296 0x3128 +#define WM8996_WRITE_SEQUENCER_297 0x3129 +#define WM8996_WRITE_SEQUENCER_298 0x312A +#define WM8996_WRITE_SEQUENCER_299 0x312B +#define WM8996_WRITE_SEQUENCER_300 0x312C +#define WM8996_WRITE_SEQUENCER_301 0x312D +#define WM8996_WRITE_SEQUENCER_302 0x312E +#define WM8996_WRITE_SEQUENCER_303 0x312F +#define WM8996_WRITE_SEQUENCER_304 0x3130 +#define WM8996_WRITE_SEQUENCER_305 0x3131 +#define WM8996_WRITE_SEQUENCER_306 0x3132 +#define WM8996_WRITE_SEQUENCER_307 0x3133 +#define WM8996_WRITE_SEQUENCER_308 0x3134 +#define WM8996_WRITE_SEQUENCER_309 0x3135 +#define WM8996_WRITE_SEQUENCER_310 0x3136 +#define WM8996_WRITE_SEQUENCER_311 0x3137 +#define WM8996_WRITE_SEQUENCER_312 0x3138 +#define WM8996_WRITE_SEQUENCER_313 0x3139 +#define WM8996_WRITE_SEQUENCER_314 0x313A +#define WM8996_WRITE_SEQUENCER_315 0x313B +#define WM8996_WRITE_SEQUENCER_316 0x313C +#define WM8996_WRITE_SEQUENCER_317 0x313D +#define WM8996_WRITE_SEQUENCER_318 0x313E +#define WM8996_WRITE_SEQUENCER_319 0x313F +#define WM8996_WRITE_SEQUENCER_320 0x3140 +#define WM8996_WRITE_SEQUENCER_321 0x3141 +#define WM8996_WRITE_SEQUENCER_322 0x3142 +#define WM8996_WRITE_SEQUENCER_323 0x3143 +#define WM8996_WRITE_SEQUENCER_324 0x3144 +#define WM8996_WRITE_SEQUENCER_325 0x3145 +#define WM8996_WRITE_SEQUENCER_326 0x3146 +#define WM8996_WRITE_SEQUENCER_327 0x3147 +#define WM8996_WRITE_SEQUENCER_328 0x3148 +#define WM8996_WRITE_SEQUENCER_329 0x3149 +#define WM8996_WRITE_SEQUENCER_330 0x314A +#define WM8996_WRITE_SEQUENCER_331 0x314B +#define WM8996_WRITE_SEQUENCER_332 0x314C +#define WM8996_WRITE_SEQUENCER_333 0x314D +#define WM8996_WRITE_SEQUENCER_334 0x314E +#define WM8996_WRITE_SEQUENCER_335 0x314F +#define WM8996_WRITE_SEQUENCER_336 0x3150 +#define WM8996_WRITE_SEQUENCER_337 0x3151 +#define WM8996_WRITE_SEQUENCER_338 0x3152 +#define WM8996_WRITE_SEQUENCER_339 0x3153 +#define WM8996_WRITE_SEQUENCER_340 0x3154 +#define WM8996_WRITE_SEQUENCER_341 0x3155 +#define WM8996_WRITE_SEQUENCER_342 0x3156 +#define WM8996_WRITE_SEQUENCER_343 0x3157 +#define WM8996_WRITE_SEQUENCER_344 0x3158 +#define WM8996_WRITE_SEQUENCER_345 0x3159 +#define WM8996_WRITE_SEQUENCER_346 0x315A +#define WM8996_WRITE_SEQUENCER_347 0x315B +#define WM8996_WRITE_SEQUENCER_348 0x315C +#define WM8996_WRITE_SEQUENCER_349 0x315D +#define WM8996_WRITE_SEQUENCER_350 0x315E +#define WM8996_WRITE_SEQUENCER_351 0x315F +#define WM8996_WRITE_SEQUENCER_352 0x3160 +#define WM8996_WRITE_SEQUENCER_353 0x3161 +#define WM8996_WRITE_SEQUENCER_354 0x3162 +#define WM8996_WRITE_SEQUENCER_355 0x3163 +#define WM8996_WRITE_SEQUENCER_356 0x3164 +#define WM8996_WRITE_SEQUENCER_357 0x3165 +#define WM8996_WRITE_SEQUENCER_358 0x3166 +#define WM8996_WRITE_SEQUENCER_359 0x3167 +#define WM8996_WRITE_SEQUENCER_360 0x3168 +#define WM8996_WRITE_SEQUENCER_361 0x3169 +#define WM8996_WRITE_SEQUENCER_362 0x316A +#define WM8996_WRITE_SEQUENCER_363 0x316B +#define WM8996_WRITE_SEQUENCER_364 0x316C +#define WM8996_WRITE_SEQUENCER_365 0x316D +#define WM8996_WRITE_SEQUENCER_366 0x316E +#define WM8996_WRITE_SEQUENCER_367 0x316F +#define WM8996_WRITE_SEQUENCER_368 0x3170 +#define WM8996_WRITE_SEQUENCER_369 0x3171 +#define WM8996_WRITE_SEQUENCER_370 0x3172 +#define WM8996_WRITE_SEQUENCER_371 0x3173 +#define WM8996_WRITE_SEQUENCER_372 0x3174 +#define WM8996_WRITE_SEQUENCER_373 0x3175 +#define WM8996_WRITE_SEQUENCER_374 0x3176 +#define WM8996_WRITE_SEQUENCER_375 0x3177 +#define WM8996_WRITE_SEQUENCER_376 0x3178 +#define WM8996_WRITE_SEQUENCER_377 0x3179 +#define WM8996_WRITE_SEQUENCER_378 0x317A +#define WM8996_WRITE_SEQUENCER_379 0x317B +#define WM8996_WRITE_SEQUENCER_380 0x317C +#define WM8996_WRITE_SEQUENCER_381 0x317D +#define WM8996_WRITE_SEQUENCER_382 0x317E +#define WM8996_WRITE_SEQUENCER_383 0x317F +#define WM8996_WRITE_SEQUENCER_384 0x3180 +#define WM8996_WRITE_SEQUENCER_385 0x3181 +#define WM8996_WRITE_SEQUENCER_386 0x3182 +#define WM8996_WRITE_SEQUENCER_387 0x3183 +#define WM8996_WRITE_SEQUENCER_388 0x3184 +#define WM8996_WRITE_SEQUENCER_389 0x3185 +#define WM8996_WRITE_SEQUENCER_390 0x3186 +#define WM8996_WRITE_SEQUENCER_391 0x3187 +#define WM8996_WRITE_SEQUENCER_392 0x3188 +#define WM8996_WRITE_SEQUENCER_393 0x3189 +#define WM8996_WRITE_SEQUENCER_394 0x318A +#define WM8996_WRITE_SEQUENCER_395 0x318B +#define WM8996_WRITE_SEQUENCER_396 0x318C +#define WM8996_WRITE_SEQUENCER_397 0x318D +#define WM8996_WRITE_SEQUENCER_398 0x318E +#define WM8996_WRITE_SEQUENCER_399 0x318F +#define WM8996_WRITE_SEQUENCER_400 0x3190 +#define WM8996_WRITE_SEQUENCER_401 0x3191 +#define WM8996_WRITE_SEQUENCER_402 0x3192 +#define WM8996_WRITE_SEQUENCER_403 0x3193 +#define WM8996_WRITE_SEQUENCER_404 0x3194 +#define WM8996_WRITE_SEQUENCER_405 0x3195 +#define WM8996_WRITE_SEQUENCER_406 0x3196 +#define WM8996_WRITE_SEQUENCER_407 0x3197 +#define WM8996_WRITE_SEQUENCER_408 0x3198 +#define WM8996_WRITE_SEQUENCER_409 0x3199 +#define WM8996_WRITE_SEQUENCER_410 0x319A +#define WM8996_WRITE_SEQUENCER_411 0x319B +#define WM8996_WRITE_SEQUENCER_412 0x319C +#define WM8996_WRITE_SEQUENCER_413 0x319D +#define WM8996_WRITE_SEQUENCER_414 0x319E +#define WM8996_WRITE_SEQUENCER_415 0x319F +#define WM8996_WRITE_SEQUENCER_416 0x31A0 +#define WM8996_WRITE_SEQUENCER_417 0x31A1 +#define WM8996_WRITE_SEQUENCER_418 0x31A2 +#define WM8996_WRITE_SEQUENCER_419 0x31A3 +#define WM8996_WRITE_SEQUENCER_420 0x31A4 +#define WM8996_WRITE_SEQUENCER_421 0x31A5 +#define WM8996_WRITE_SEQUENCER_422 0x31A6 +#define WM8996_WRITE_SEQUENCER_423 0x31A7 +#define WM8996_WRITE_SEQUENCER_424 0x31A8 +#define WM8996_WRITE_SEQUENCER_425 0x31A9 +#define WM8996_WRITE_SEQUENCER_426 0x31AA +#define WM8996_WRITE_SEQUENCER_427 0x31AB +#define WM8996_WRITE_SEQUENCER_428 0x31AC +#define WM8996_WRITE_SEQUENCER_429 0x31AD +#define WM8996_WRITE_SEQUENCER_430 0x31AE +#define WM8996_WRITE_SEQUENCER_431 0x31AF +#define WM8996_WRITE_SEQUENCER_432 0x31B0 +#define WM8996_WRITE_SEQUENCER_433 0x31B1 +#define WM8996_WRITE_SEQUENCER_434 0x31B2 +#define WM8996_WRITE_SEQUENCER_435 0x31B3 +#define WM8996_WRITE_SEQUENCER_436 0x31B4 +#define WM8996_WRITE_SEQUENCER_437 0x31B5 +#define WM8996_WRITE_SEQUENCER_438 0x31B6 +#define WM8996_WRITE_SEQUENCER_439 0x31B7 +#define WM8996_WRITE_SEQUENCER_440 0x31B8 +#define WM8996_WRITE_SEQUENCER_441 0x31B9 +#define WM8996_WRITE_SEQUENCER_442 0x31BA +#define WM8996_WRITE_SEQUENCER_443 0x31BB +#define WM8996_WRITE_SEQUENCER_444 0x31BC +#define WM8996_WRITE_SEQUENCER_445 0x31BD +#define WM8996_WRITE_SEQUENCER_446 0x31BE +#define WM8996_WRITE_SEQUENCER_447 0x31BF +#define WM8996_WRITE_SEQUENCER_448 0x31C0 +#define WM8996_WRITE_SEQUENCER_449 0x31C1 +#define WM8996_WRITE_SEQUENCER_450 0x31C2 +#define WM8996_WRITE_SEQUENCER_451 0x31C3 +#define WM8996_WRITE_SEQUENCER_452 0x31C4 +#define WM8996_WRITE_SEQUENCER_453 0x31C5 +#define WM8996_WRITE_SEQUENCER_454 0x31C6 +#define WM8996_WRITE_SEQUENCER_455 0x31C7 +#define WM8996_WRITE_SEQUENCER_456 0x31C8 +#define WM8996_WRITE_SEQUENCER_457 0x31C9 +#define WM8996_WRITE_SEQUENCER_458 0x31CA +#define WM8996_WRITE_SEQUENCER_459 0x31CB +#define WM8996_WRITE_SEQUENCER_460 0x31CC +#define WM8996_WRITE_SEQUENCER_461 0x31CD +#define WM8996_WRITE_SEQUENCER_462 0x31CE +#define WM8996_WRITE_SEQUENCER_463 0x31CF +#define WM8996_WRITE_SEQUENCER_464 0x31D0 +#define WM8996_WRITE_SEQUENCER_465 0x31D1 +#define WM8996_WRITE_SEQUENCER_466 0x31D2 +#define WM8996_WRITE_SEQUENCER_467 0x31D3 +#define WM8996_WRITE_SEQUENCER_468 0x31D4 +#define WM8996_WRITE_SEQUENCER_469 0x31D5 +#define WM8996_WRITE_SEQUENCER_470 0x31D6 +#define WM8996_WRITE_SEQUENCER_471 0x31D7 +#define WM8996_WRITE_SEQUENCER_472 0x31D8 +#define WM8996_WRITE_SEQUENCER_473 0x31D9 +#define WM8996_WRITE_SEQUENCER_474 0x31DA +#define WM8996_WRITE_SEQUENCER_475 0x31DB +#define WM8996_WRITE_SEQUENCER_476 0x31DC +#define WM8996_WRITE_SEQUENCER_477 0x31DD +#define WM8996_WRITE_SEQUENCER_478 0x31DE +#define WM8996_WRITE_SEQUENCER_479 0x31DF +#define WM8996_WRITE_SEQUENCER_480 0x31E0 +#define WM8996_WRITE_SEQUENCER_481 0x31E1 +#define WM8996_WRITE_SEQUENCER_482 0x31E2 +#define WM8996_WRITE_SEQUENCER_483 0x31E3 +#define WM8996_WRITE_SEQUENCER_484 0x31E4 +#define WM8996_WRITE_SEQUENCER_485 0x31E5 +#define WM8996_WRITE_SEQUENCER_486 0x31E6 +#define WM8996_WRITE_SEQUENCER_487 0x31E7 +#define WM8996_WRITE_SEQUENCER_488 0x31E8 +#define WM8996_WRITE_SEQUENCER_489 0x31E9 +#define WM8996_WRITE_SEQUENCER_490 0x31EA +#define WM8996_WRITE_SEQUENCER_491 0x31EB +#define WM8996_WRITE_SEQUENCER_492 0x31EC +#define WM8996_WRITE_SEQUENCER_493 0x31ED +#define WM8996_WRITE_SEQUENCER_494 0x31EE +#define WM8996_WRITE_SEQUENCER_495 0x31EF +#define WM8996_WRITE_SEQUENCER_496 0x31F0 +#define WM8996_WRITE_SEQUENCER_497 0x31F1 +#define WM8996_WRITE_SEQUENCER_498 0x31F2 +#define WM8996_WRITE_SEQUENCER_499 0x31F3 +#define WM8996_WRITE_SEQUENCER_500 0x31F4 +#define WM8996_WRITE_SEQUENCER_501 0x31F5 +#define WM8996_WRITE_SEQUENCER_502 0x31F6 +#define WM8996_WRITE_SEQUENCER_503 0x31F7 +#define WM8996_WRITE_SEQUENCER_504 0x31F8 +#define WM8996_WRITE_SEQUENCER_505 0x31F9 +#define WM8996_WRITE_SEQUENCER_506 0x31FA +#define WM8996_WRITE_SEQUENCER_507 0x31FB +#define WM8996_WRITE_SEQUENCER_508 0x31FC +#define WM8996_WRITE_SEQUENCER_509 0x31FD +#define WM8996_WRITE_SEQUENCER_510 0x31FE +#define WM8996_WRITE_SEQUENCER_511 0x31FF + +#define WM8996_REGISTER_COUNT 706 +#define WM8996_MAX_REGISTER 0x31FF + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM8996_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM8996_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM8996_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8996_MICB2_ENA 0x0200 /* MICB2_ENA */ +#define WM8996_MICB2_ENA_MASK 0x0200 /* MICB2_ENA */ +#define WM8996_MICB2_ENA_SHIFT 9 /* MICB2_ENA */ +#define WM8996_MICB2_ENA_WIDTH 1 /* MICB2_ENA */ +#define WM8996_MICB1_ENA 0x0100 /* MICB1_ENA */ +#define WM8996_MICB1_ENA_MASK 0x0100 /* MICB1_ENA */ +#define WM8996_MICB1_ENA_SHIFT 8 /* MICB1_ENA */ +#define WM8996_MICB1_ENA_WIDTH 1 /* MICB1_ENA */ +#define WM8996_HPOUT2L_ENA 0x0080 /* HPOUT2L_ENA */ +#define WM8996_HPOUT2L_ENA_MASK 0x0080 /* HPOUT2L_ENA */ +#define WM8996_HPOUT2L_ENA_SHIFT 7 /* HPOUT2L_ENA */ +#define WM8996_HPOUT2L_ENA_WIDTH 1 /* HPOUT2L_ENA */ +#define WM8996_HPOUT2R_ENA 0x0040 /* HPOUT2R_ENA */ +#define WM8996_HPOUT2R_ENA_MASK 0x0040 /* HPOUT2R_ENA */ +#define WM8996_HPOUT2R_ENA_SHIFT 6 /* HPOUT2R_ENA */ +#define WM8996_HPOUT2R_ENA_WIDTH 1 /* HPOUT2R_ENA */ +#define WM8996_HPOUT1L_ENA 0x0020 /* HPOUT1L_ENA */ +#define WM8996_HPOUT1L_ENA_MASK 0x0020 /* HPOUT1L_ENA */ +#define WM8996_HPOUT1L_ENA_SHIFT 5 /* HPOUT1L_ENA */ +#define WM8996_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */ +#define WM8996_HPOUT1R_ENA 0x0010 /* HPOUT1R_ENA */ +#define WM8996_HPOUT1R_ENA_MASK 0x0010 /* HPOUT1R_ENA */ +#define WM8996_HPOUT1R_ENA_SHIFT 4 /* HPOUT1R_ENA */ +#define WM8996_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */ +#define WM8996_BG_ENA 0x0001 /* BG_ENA */ +#define WM8996_BG_ENA_MASK 0x0001 /* BG_ENA */ +#define WM8996_BG_ENA_SHIFT 0 /* BG_ENA */ +#define WM8996_BG_ENA_WIDTH 1 /* BG_ENA */ + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8996_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8996_OPCLK_ENA_MASK 0x0800 /* OPCLK_ENA */ +#define WM8996_OPCLK_ENA_SHIFT 11 /* OPCLK_ENA */ +#define WM8996_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */ +#define WM8996_INL_ENA 0x0020 /* INL_ENA */ +#define WM8996_INL_ENA_MASK 0x0020 /* INL_ENA */ +#define WM8996_INL_ENA_SHIFT 5 /* INL_ENA */ +#define WM8996_INL_ENA_WIDTH 1 /* INL_ENA */ +#define WM8996_INR_ENA 0x0010 /* INR_ENA */ +#define WM8996_INR_ENA_MASK 0x0010 /* INR_ENA */ +#define WM8996_INR_ENA_SHIFT 4 /* INR_ENA */ +#define WM8996_INR_ENA_WIDTH 1 /* INR_ENA */ +#define WM8996_LDO2_ENA 0x0002 /* LDO2_ENA */ +#define WM8996_LDO2_ENA_MASK 0x0002 /* LDO2_ENA */ +#define WM8996_LDO2_ENA_SHIFT 1 /* LDO2_ENA */ +#define WM8996_LDO2_ENA_WIDTH 1 /* LDO2_ENA */ + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8996_DSP2RXL_ENA 0x0800 /* DSP2RXL_ENA */ +#define WM8996_DSP2RXL_ENA_MASK 0x0800 /* DSP2RXL_ENA */ +#define WM8996_DSP2RXL_ENA_SHIFT 11 /* DSP2RXL_ENA */ +#define WM8996_DSP2RXL_ENA_WIDTH 1 /* DSP2RXL_ENA */ +#define WM8996_DSP2RXR_ENA 0x0400 /* DSP2RXR_ENA */ +#define WM8996_DSP2RXR_ENA_MASK 0x0400 /* DSP2RXR_ENA */ +#define WM8996_DSP2RXR_ENA_SHIFT 10 /* DSP2RXR_ENA */ +#define WM8996_DSP2RXR_ENA_WIDTH 1 /* DSP2RXR_ENA */ +#define WM8996_DSP1RXL_ENA 0x0200 /* DSP1RXL_ENA */ +#define WM8996_DSP1RXL_ENA_MASK 0x0200 /* DSP1RXL_ENA */ +#define WM8996_DSP1RXL_ENA_SHIFT 9 /* DSP1RXL_ENA */ +#define WM8996_DSP1RXL_ENA_WIDTH 1 /* DSP1RXL_ENA */ +#define WM8996_DSP1RXR_ENA 0x0100 /* DSP1RXR_ENA */ +#define WM8996_DSP1RXR_ENA_MASK 0x0100 /* DSP1RXR_ENA */ +#define WM8996_DSP1RXR_ENA_SHIFT 8 /* DSP1RXR_ENA */ +#define WM8996_DSP1RXR_ENA_WIDTH 1 /* DSP1RXR_ENA */ +#define WM8996_DMIC2L_ENA 0x0020 /* DMIC2L_ENA */ +#define WM8996_DMIC2L_ENA_MASK 0x0020 /* DMIC2L_ENA */ +#define WM8996_DMIC2L_ENA_SHIFT 5 /* DMIC2L_ENA */ +#define WM8996_DMIC2L_ENA_WIDTH 1 /* DMIC2L_ENA */ +#define WM8996_DMIC2R_ENA 0x0010 /* DMIC2R_ENA */ +#define WM8996_DMIC2R_ENA_MASK 0x0010 /* DMIC2R_ENA */ +#define WM8996_DMIC2R_ENA_SHIFT 4 /* DMIC2R_ENA */ +#define WM8996_DMIC2R_ENA_WIDTH 1 /* DMIC2R_ENA */ +#define WM8996_DMIC1L_ENA 0x0008 /* DMIC1L_ENA */ +#define WM8996_DMIC1L_ENA_MASK 0x0008 /* DMIC1L_ENA */ +#define WM8996_DMIC1L_ENA_SHIFT 3 /* DMIC1L_ENA */ +#define WM8996_DMIC1L_ENA_WIDTH 1 /* DMIC1L_ENA */ +#define WM8996_DMIC1R_ENA 0x0004 /* DMIC1R_ENA */ +#define WM8996_DMIC1R_ENA_MASK 0x0004 /* DMIC1R_ENA */ +#define WM8996_DMIC1R_ENA_SHIFT 2 /* DMIC1R_ENA */ +#define WM8996_DMIC1R_ENA_WIDTH 1 /* DMIC1R_ENA */ +#define WM8996_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8996_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8996_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8996_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8996_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8996_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8996_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8996_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R4 (0x04) - Power Management (4) + */ +#define WM8996_AIF2RX_CHAN1_ENA 0x0200 /* AIF2RX_CHAN1_ENA */ +#define WM8996_AIF2RX_CHAN1_ENA_MASK 0x0200 /* AIF2RX_CHAN1_ENA */ +#define WM8996_AIF2RX_CHAN1_ENA_SHIFT 9 /* AIF2RX_CHAN1_ENA */ +#define WM8996_AIF2RX_CHAN1_ENA_WIDTH 1 /* AIF2RX_CHAN1_ENA */ +#define WM8996_AIF2RX_CHAN0_ENA 0x0100 /* AIF2RX_CHAN0_ENA */ +#define WM8996_AIF2RX_CHAN0_ENA_MASK 0x0100 /* AIF2RX_CHAN0_ENA */ +#define WM8996_AIF2RX_CHAN0_ENA_SHIFT 8 /* AIF2RX_CHAN0_ENA */ +#define WM8996_AIF2RX_CHAN0_ENA_WIDTH 1 /* AIF2RX_CHAN0_ENA */ +#define WM8996_AIF1RX_CHAN5_ENA 0x0020 /* AIF1RX_CHAN5_ENA */ +#define WM8996_AIF1RX_CHAN5_ENA_MASK 0x0020 /* AIF1RX_CHAN5_ENA */ +#define WM8996_AIF1RX_CHAN5_ENA_SHIFT 5 /* AIF1RX_CHAN5_ENA */ +#define WM8996_AIF1RX_CHAN5_ENA_WIDTH 1 /* AIF1RX_CHAN5_ENA */ +#define WM8996_AIF1RX_CHAN4_ENA 0x0010 /* AIF1RX_CHAN4_ENA */ +#define WM8996_AIF1RX_CHAN4_ENA_MASK 0x0010 /* AIF1RX_CHAN4_ENA */ +#define WM8996_AIF1RX_CHAN4_ENA_SHIFT 4 /* AIF1RX_CHAN4_ENA */ +#define WM8996_AIF1RX_CHAN4_ENA_WIDTH 1 /* AIF1RX_CHAN4_ENA */ +#define WM8996_AIF1RX_CHAN3_ENA 0x0008 /* AIF1RX_CHAN3_ENA */ +#define WM8996_AIF1RX_CHAN3_ENA_MASK 0x0008 /* AIF1RX_CHAN3_ENA */ +#define WM8996_AIF1RX_CHAN3_ENA_SHIFT 3 /* AIF1RX_CHAN3_ENA */ +#define WM8996_AIF1RX_CHAN3_ENA_WIDTH 1 /* AIF1RX_CHAN3_ENA */ +#define WM8996_AIF1RX_CHAN2_ENA 0x0004 /* AIF1RX_CHAN2_ENA */ +#define WM8996_AIF1RX_CHAN2_ENA_MASK 0x0004 /* AIF1RX_CHAN2_ENA */ +#define WM8996_AIF1RX_CHAN2_ENA_SHIFT 2 /* AIF1RX_CHAN2_ENA */ +#define WM8996_AIF1RX_CHAN2_ENA_WIDTH 1 /* AIF1RX_CHAN2_ENA */ +#define WM8996_AIF1RX_CHAN1_ENA 0x0002 /* AIF1RX_CHAN1_ENA */ +#define WM8996_AIF1RX_CHAN1_ENA_MASK 0x0002 /* AIF1RX_CHAN1_ENA */ +#define WM8996_AIF1RX_CHAN1_ENA_SHIFT 1 /* AIF1RX_CHAN1_ENA */ +#define WM8996_AIF1RX_CHAN1_ENA_WIDTH 1 /* AIF1RX_CHAN1_ENA */ +#define WM8996_AIF1RX_CHAN0_ENA 0x0001 /* AIF1RX_CHAN0_ENA */ +#define WM8996_AIF1RX_CHAN0_ENA_MASK 0x0001 /* AIF1RX_CHAN0_ENA */ +#define WM8996_AIF1RX_CHAN0_ENA_SHIFT 0 /* AIF1RX_CHAN0_ENA */ +#define WM8996_AIF1RX_CHAN0_ENA_WIDTH 1 /* AIF1RX_CHAN0_ENA */ + +/* + * R5 (0x05) - Power Management (5) + */ +#define WM8996_DSP2TXL_ENA 0x0800 /* DSP2TXL_ENA */ +#define WM8996_DSP2TXL_ENA_MASK 0x0800 /* DSP2TXL_ENA */ +#define WM8996_DSP2TXL_ENA_SHIFT 11 /* DSP2TXL_ENA */ +#define WM8996_DSP2TXL_ENA_WIDTH 1 /* DSP2TXL_ENA */ +#define WM8996_DSP2TXR_ENA 0x0400 /* DSP2TXR_ENA */ +#define WM8996_DSP2TXR_ENA_MASK 0x0400 /* DSP2TXR_ENA */ +#define WM8996_DSP2TXR_ENA_SHIFT 10 /* DSP2TXR_ENA */ +#define WM8996_DSP2TXR_ENA_WIDTH 1 /* DSP2TXR_ENA */ +#define WM8996_DSP1TXL_ENA 0x0200 /* DSP1TXL_ENA */ +#define WM8996_DSP1TXL_ENA_MASK 0x0200 /* DSP1TXL_ENA */ +#define WM8996_DSP1TXL_ENA_SHIFT 9 /* DSP1TXL_ENA */ +#define WM8996_DSP1TXL_ENA_WIDTH 1 /* DSP1TXL_ENA */ +#define WM8996_DSP1TXR_ENA 0x0100 /* DSP1TXR_ENA */ +#define WM8996_DSP1TXR_ENA_MASK 0x0100 /* DSP1TXR_ENA */ +#define WM8996_DSP1TXR_ENA_SHIFT 8 /* DSP1TXR_ENA */ +#define WM8996_DSP1TXR_ENA_WIDTH 1 /* DSP1TXR_ENA */ +#define WM8996_DAC2L_ENA 0x0008 /* DAC2L_ENA */ +#define WM8996_DAC2L_ENA_MASK 0x0008 /* DAC2L_ENA */ +#define WM8996_DAC2L_ENA_SHIFT 3 /* DAC2L_ENA */ +#define WM8996_DAC2L_ENA_WIDTH 1 /* DAC2L_ENA */ +#define WM8996_DAC2R_ENA 0x0004 /* DAC2R_ENA */ +#define WM8996_DAC2R_ENA_MASK 0x0004 /* DAC2R_ENA */ +#define WM8996_DAC2R_ENA_SHIFT 2 /* DAC2R_ENA */ +#define WM8996_DAC2R_ENA_WIDTH 1 /* DAC2R_ENA */ +#define WM8996_DAC1L_ENA 0x0002 /* DAC1L_ENA */ +#define WM8996_DAC1L_ENA_MASK 0x0002 /* DAC1L_ENA */ +#define WM8996_DAC1L_ENA_SHIFT 1 /* DAC1L_ENA */ +#define WM8996_DAC1L_ENA_WIDTH 1 /* DAC1L_ENA */ +#define WM8996_DAC1R_ENA 0x0001 /* DAC1R_ENA */ +#define WM8996_DAC1R_ENA_MASK 0x0001 /* DAC1R_ENA */ +#define WM8996_DAC1R_ENA_SHIFT 0 /* DAC1R_ENA */ +#define WM8996_DAC1R_ENA_WIDTH 1 /* DAC1R_ENA */ + +/* + * R6 (0x06) - Power Management (6) + */ +#define WM8996_AIF2TX_CHAN1_ENA 0x0200 /* AIF2TX_CHAN1_ENA */ +#define WM8996_AIF2TX_CHAN1_ENA_MASK 0x0200 /* AIF2TX_CHAN1_ENA */ +#define WM8996_AIF2TX_CHAN1_ENA_SHIFT 9 /* AIF2TX_CHAN1_ENA */ +#define WM8996_AIF2TX_CHAN1_ENA_WIDTH 1 /* AIF2TX_CHAN1_ENA */ +#define WM8996_AIF2TX_CHAN0_ENA 0x0100 /* AIF2TX_CHAN0_ENA */ +#define WM8996_AIF2TX_CHAN0_ENA_MASK 0x0100 /* AIF2TX_CHAN0_ENA */ +#define WM8996_AIF2TX_CHAN0_ENA_SHIFT 8 /* AIF2TX_CHAN0_ENA */ +#define WM8996_AIF2TX_CHAN0_ENA_WIDTH 1 /* AIF2TX_CHAN0_ENA */ +#define WM8996_AIF1TX_CHAN5_ENA 0x0020 /* AIF1TX_CHAN5_ENA */ +#define WM8996_AIF1TX_CHAN5_ENA_MASK 0x0020 /* AIF1TX_CHAN5_ENA */ +#define WM8996_AIF1TX_CHAN5_ENA_SHIFT 5 /* AIF1TX_CHAN5_ENA */ +#define WM8996_AIF1TX_CHAN5_ENA_WIDTH 1 /* AIF1TX_CHAN5_ENA */ +#define WM8996_AIF1TX_CHAN4_ENA 0x0010 /* AIF1TX_CHAN4_ENA */ +#define WM8996_AIF1TX_CHAN4_ENA_MASK 0x0010 /* AIF1TX_CHAN4_ENA */ +#define WM8996_AIF1TX_CHAN4_ENA_SHIFT 4 /* AIF1TX_CHAN4_ENA */ +#define WM8996_AIF1TX_CHAN4_ENA_WIDTH 1 /* AIF1TX_CHAN4_ENA */ +#define WM8996_AIF1TX_CHAN3_ENA 0x0008 /* AIF1TX_CHAN3_ENA */ +#define WM8996_AIF1TX_CHAN3_ENA_MASK 0x0008 /* AIF1TX_CHAN3_ENA */ +#define WM8996_AIF1TX_CHAN3_ENA_SHIFT 3 /* AIF1TX_CHAN3_ENA */ +#define WM8996_AIF1TX_CHAN3_ENA_WIDTH 1 /* AIF1TX_CHAN3_ENA */ +#define WM8996_AIF1TX_CHAN2_ENA 0x0004 /* AIF1TX_CHAN2_ENA */ +#define WM8996_AIF1TX_CHAN2_ENA_MASK 0x0004 /* AIF1TX_CHAN2_ENA */ +#define WM8996_AIF1TX_CHAN2_ENA_SHIFT 2 /* AIF1TX_CHAN2_ENA */ +#define WM8996_AIF1TX_CHAN2_ENA_WIDTH 1 /* AIF1TX_CHAN2_ENA */ +#define WM8996_AIF1TX_CHAN1_ENA 0x0002 /* AIF1TX_CHAN1_ENA */ +#define WM8996_AIF1TX_CHAN1_ENA_MASK 0x0002 /* AIF1TX_CHAN1_ENA */ +#define WM8996_AIF1TX_CHAN1_ENA_SHIFT 1 /* AIF1TX_CHAN1_ENA */ +#define WM8996_AIF1TX_CHAN1_ENA_WIDTH 1 /* AIF1TX_CHAN1_ENA */ +#define WM8996_AIF1TX_CHAN0_ENA 0x0001 /* AIF1TX_CHAN0_ENA */ +#define WM8996_AIF1TX_CHAN0_ENA_MASK 0x0001 /* AIF1TX_CHAN0_ENA */ +#define WM8996_AIF1TX_CHAN0_ENA_SHIFT 0 /* AIF1TX_CHAN0_ENA */ +#define WM8996_AIF1TX_CHAN0_ENA_WIDTH 1 /* AIF1TX_CHAN0_ENA */ + +/* + * R7 (0x07) - Power Management (7) + */ +#define WM8996_DMIC2_FN 0x0200 /* DMIC2_FN */ +#define WM8996_DMIC2_FN_MASK 0x0200 /* DMIC2_FN */ +#define WM8996_DMIC2_FN_SHIFT 9 /* DMIC2_FN */ +#define WM8996_DMIC2_FN_WIDTH 1 /* DMIC2_FN */ +#define WM8996_DMIC1_FN 0x0100 /* DMIC1_FN */ +#define WM8996_DMIC1_FN_MASK 0x0100 /* DMIC1_FN */ +#define WM8996_DMIC1_FN_SHIFT 8 /* DMIC1_FN */ +#define WM8996_DMIC1_FN_WIDTH 1 /* DMIC1_FN */ +#define WM8996_ADC_DMIC_DSP2R_ENA 0x0080 /* ADC_DMIC_DSP2R_ENA */ +#define WM8996_ADC_DMIC_DSP2R_ENA_MASK 0x0080 /* ADC_DMIC_DSP2R_ENA */ +#define WM8996_ADC_DMIC_DSP2R_ENA_SHIFT 7 /* ADC_DMIC_DSP2R_ENA */ +#define WM8996_ADC_DMIC_DSP2R_ENA_WIDTH 1 /* ADC_DMIC_DSP2R_ENA */ +#define WM8996_ADC_DMIC_DSP2L_ENA 0x0040 /* ADC_DMIC_DSP2L_ENA */ +#define WM8996_ADC_DMIC_DSP2L_ENA_MASK 0x0040 /* ADC_DMIC_DSP2L_ENA */ +#define WM8996_ADC_DMIC_DSP2L_ENA_SHIFT 6 /* ADC_DMIC_DSP2L_ENA */ +#define WM8996_ADC_DMIC_DSP2L_ENA_WIDTH 1 /* ADC_DMIC_DSP2L_ENA */ +#define WM8996_ADC_DMIC_SRC2_MASK 0x0030 /* ADC_DMIC_SRC2 - [5:4] */ +#define WM8996_ADC_DMIC_SRC2_SHIFT 4 /* ADC_DMIC_SRC2 - [5:4] */ +#define WM8996_ADC_DMIC_SRC2_WIDTH 2 /* ADC_DMIC_SRC2 - [5:4] */ +#define WM8996_ADC_DMIC_DSP1R_ENA 0x0008 /* ADC_DMIC_DSP1R_ENA */ +#define WM8996_ADC_DMIC_DSP1R_ENA_MASK 0x0008 /* ADC_DMIC_DSP1R_ENA */ +#define WM8996_ADC_DMIC_DSP1R_ENA_SHIFT 3 /* ADC_DMIC_DSP1R_ENA */ +#define WM8996_ADC_DMIC_DSP1R_ENA_WIDTH 1 /* ADC_DMIC_DSP1R_ENA */ +#define WM8996_ADC_DMIC_DSP1L_ENA 0x0004 /* ADC_DMIC_DSP1L_ENA */ +#define WM8996_ADC_DMIC_DSP1L_ENA_MASK 0x0004 /* ADC_DMIC_DSP1L_ENA */ +#define WM8996_ADC_DMIC_DSP1L_ENA_SHIFT 2 /* ADC_DMIC_DSP1L_ENA */ +#define WM8996_ADC_DMIC_DSP1L_ENA_WIDTH 1 /* ADC_DMIC_DSP1L_ENA */ +#define WM8996_ADC_DMIC_SRC1_MASK 0x0003 /* ADC_DMIC_SRC1 - [1:0] */ +#define WM8996_ADC_DMIC_SRC1_SHIFT 0 /* ADC_DMIC_SRC1 - [1:0] */ +#define WM8996_ADC_DMIC_SRC1_WIDTH 2 /* ADC_DMIC_SRC1 - [1:0] */ + +/* + * R8 (0x08) - Power Management (8) + */ +#define WM8996_AIF2TX_SRC_MASK 0x00C0 /* AIF2TX_SRC - [7:6] */ +#define WM8996_AIF2TX_SRC_SHIFT 6 /* AIF2TX_SRC - [7:6] */ +#define WM8996_AIF2TX_SRC_WIDTH 2 /* AIF2TX_SRC - [7:6] */ +#define WM8996_DSP2RX_SRC 0x0010 /* DSP2RX_SRC */ +#define WM8996_DSP2RX_SRC_MASK 0x0010 /* DSP2RX_SRC */ +#define WM8996_DSP2RX_SRC_SHIFT 4 /* DSP2RX_SRC */ +#define WM8996_DSP2RX_SRC_WIDTH 1 /* DSP2RX_SRC */ +#define WM8996_DSP1RX_SRC 0x0001 /* DSP1RX_SRC */ +#define WM8996_DSP1RX_SRC_MASK 0x0001 /* DSP1RX_SRC */ +#define WM8996_DSP1RX_SRC_SHIFT 0 /* DSP1RX_SRC */ +#define WM8996_DSP1RX_SRC_WIDTH 1 /* DSP1RX_SRC */ + +/* + * R16 (0x10) - Left Line Input Volume + */ +#define WM8996_IN1_VU 0x0080 /* IN1_VU */ +#define WM8996_IN1_VU_MASK 0x0080 /* IN1_VU */ +#define WM8996_IN1_VU_SHIFT 7 /* IN1_VU */ +#define WM8996_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8996_IN1L_ZC 0x0020 /* IN1L_ZC */ +#define WM8996_IN1L_ZC_MASK 0x0020 /* IN1L_ZC */ +#define WM8996_IN1L_ZC_SHIFT 5 /* IN1L_ZC */ +#define WM8996_IN1L_ZC_WIDTH 1 /* IN1L_ZC */ +#define WM8996_IN1L_VOL_MASK 0x001F /* IN1L_VOL - [4:0] */ +#define WM8996_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [4:0] */ +#define WM8996_IN1L_VOL_WIDTH 5 /* IN1L_VOL - [4:0] */ + +/* + * R17 (0x11) - Right Line Input Volume + */ +#define WM8996_IN1_VU 0x0080 /* IN1_VU */ +#define WM8996_IN1_VU_MASK 0x0080 /* IN1_VU */ +#define WM8996_IN1_VU_SHIFT 7 /* IN1_VU */ +#define WM8996_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8996_IN1R_ZC 0x0020 /* IN1R_ZC */ +#define WM8996_IN1R_ZC_MASK 0x0020 /* IN1R_ZC */ +#define WM8996_IN1R_ZC_SHIFT 5 /* IN1R_ZC */ +#define WM8996_IN1R_ZC_WIDTH 1 /* IN1R_ZC */ +#define WM8996_IN1R_VOL_MASK 0x001F /* IN1R_VOL - [4:0] */ +#define WM8996_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [4:0] */ +#define WM8996_IN1R_VOL_WIDTH 5 /* IN1R_VOL - [4:0] */ + +/* + * R18 (0x12) - Line Input Control + */ +#define WM8996_INL_MODE_MASK 0x000C /* INL_MODE - [3:2] */ +#define WM8996_INL_MODE_SHIFT 2 /* INL_MODE - [3:2] */ +#define WM8996_INL_MODE_WIDTH 2 /* INL_MODE - [3:2] */ +#define WM8996_INR_MODE_MASK 0x0003 /* INR_MODE - [1:0] */ +#define WM8996_INR_MODE_SHIFT 0 /* INR_MODE - [1:0] */ +#define WM8996_INR_MODE_WIDTH 2 /* INR_MODE - [1:0] */ + +/* + * R21 (0x15) - DAC1 HPOUT1 Volume + */ +#define WM8996_DAC1R_HPOUT1R_VOL_MASK 0x00F0 /* DAC1R_HPOUT1R_VOL - [7:4] */ +#define WM8996_DAC1R_HPOUT1R_VOL_SHIFT 4 /* DAC1R_HPOUT1R_VOL - [7:4] */ +#define WM8996_DAC1R_HPOUT1R_VOL_WIDTH 4 /* DAC1R_HPOUT1R_VOL - [7:4] */ +#define WM8996_DAC1L_HPOUT1L_VOL_MASK 0x000F /* DAC1L_HPOUT1L_VOL - [3:0] */ +#define WM8996_DAC1L_HPOUT1L_VOL_SHIFT 0 /* DAC1L_HPOUT1L_VOL - [3:0] */ +#define WM8996_DAC1L_HPOUT1L_VOL_WIDTH 4 /* DAC1L_HPOUT1L_VOL - [3:0] */ + +/* + * R22 (0x16) - DAC2 HPOUT2 Volume + */ +#define WM8996_DAC2R_HPOUT2R_VOL_MASK 0x00F0 /* DAC2R_HPOUT2R_VOL - [7:4] */ +#define WM8996_DAC2R_HPOUT2R_VOL_SHIFT 4 /* DAC2R_HPOUT2R_VOL - [7:4] */ +#define WM8996_DAC2R_HPOUT2R_VOL_WIDTH 4 /* DAC2R_HPOUT2R_VOL - [7:4] */ +#define WM8996_DAC2L_HPOUT2L_VOL_MASK 0x000F /* DAC2L_HPOUT2L_VOL - [3:0] */ +#define WM8996_DAC2L_HPOUT2L_VOL_SHIFT 0 /* DAC2L_HPOUT2L_VOL - [3:0] */ +#define WM8996_DAC2L_HPOUT2L_VOL_WIDTH 4 /* DAC2L_HPOUT2L_VOL - [3:0] */ + +/* + * R24 (0x18) - DAC1 Left Volume + */ +#define WM8996_DAC1L_MUTE 0x0200 /* DAC1L_MUTE */ +#define WM8996_DAC1L_MUTE_MASK 0x0200 /* DAC1L_MUTE */ +#define WM8996_DAC1L_MUTE_SHIFT 9 /* DAC1L_MUTE */ +#define WM8996_DAC1L_MUTE_WIDTH 1 /* DAC1L_MUTE */ +#define WM8996_DAC1_VU 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_MASK 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_SHIFT 8 /* DAC1_VU */ +#define WM8996_DAC1_VU_WIDTH 1 /* DAC1_VU */ +#define WM8996_DAC1L_VOL_MASK 0x00FF /* DAC1L_VOL - [7:0] */ +#define WM8996_DAC1L_VOL_SHIFT 0 /* DAC1L_VOL - [7:0] */ +#define WM8996_DAC1L_VOL_WIDTH 8 /* DAC1L_VOL - [7:0] */ + +/* + * R25 (0x19) - DAC1 Right Volume + */ +#define WM8996_DAC1R_MUTE 0x0200 /* DAC1R_MUTE */ +#define WM8996_DAC1R_MUTE_MASK 0x0200 /* DAC1R_MUTE */ +#define WM8996_DAC1R_MUTE_SHIFT 9 /* DAC1R_MUTE */ +#define WM8996_DAC1R_MUTE_WIDTH 1 /* DAC1R_MUTE */ +#define WM8996_DAC1_VU 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_MASK 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_SHIFT 8 /* DAC1_VU */ +#define WM8996_DAC1_VU_WIDTH 1 /* DAC1_VU */ +#define WM8996_DAC1R_VOL_MASK 0x00FF /* DAC1R_VOL - [7:0] */ +#define WM8996_DAC1R_VOL_SHIFT 0 /* DAC1R_VOL - [7:0] */ +#define WM8996_DAC1R_VOL_WIDTH 8 /* DAC1R_VOL - [7:0] */ + +/* + * R26 (0x1A) - DAC2 Left Volume + */ +#define WM8996_DAC2L_MUTE 0x0200 /* DAC2L_MUTE */ +#define WM8996_DAC2L_MUTE_MASK 0x0200 /* DAC2L_MUTE */ +#define WM8996_DAC2L_MUTE_SHIFT 9 /* DAC2L_MUTE */ +#define WM8996_DAC2L_MUTE_WIDTH 1 /* DAC2L_MUTE */ +#define WM8996_DAC2_VU 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_MASK 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_SHIFT 8 /* DAC2_VU */ +#define WM8996_DAC2_VU_WIDTH 1 /* DAC2_VU */ +#define WM8996_DAC2L_VOL_MASK 0x00FF /* DAC2L_VOL - [7:0] */ +#define WM8996_DAC2L_VOL_SHIFT 0 /* DAC2L_VOL - [7:0] */ +#define WM8996_DAC2L_VOL_WIDTH 8 /* DAC2L_VOL - [7:0] */ + +/* + * R27 (0x1B) - DAC2 Right Volume + */ +#define WM8996_DAC2R_MUTE 0x0200 /* DAC2R_MUTE */ +#define WM8996_DAC2R_MUTE_MASK 0x0200 /* DAC2R_MUTE */ +#define WM8996_DAC2R_MUTE_SHIFT 9 /* DAC2R_MUTE */ +#define WM8996_DAC2R_MUTE_WIDTH 1 /* DAC2R_MUTE */ +#define WM8996_DAC2_VU 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_MASK 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_SHIFT 8 /* DAC2_VU */ +#define WM8996_DAC2_VU_WIDTH 1 /* DAC2_VU */ +#define WM8996_DAC2R_VOL_MASK 0x00FF /* DAC2R_VOL - [7:0] */ +#define WM8996_DAC2R_VOL_SHIFT 0 /* DAC2R_VOL - [7:0] */ +#define WM8996_DAC2R_VOL_WIDTH 8 /* DAC2R_VOL - [7:0] */ + +/* + * R28 (0x1C) - Output1 Left Volume + */ +#define WM8996_DAC1_VU 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_MASK 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_SHIFT 8 /* DAC1_VU */ +#define WM8996_DAC1_VU_WIDTH 1 /* DAC1_VU */ +#define WM8996_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */ +#define WM8996_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */ +#define WM8996_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */ +#define WM8996_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */ +#define WM8996_HPOUT1L_VOL_MASK 0x000F /* HPOUT1L_VOL - [3:0] */ +#define WM8996_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [3:0] */ +#define WM8996_HPOUT1L_VOL_WIDTH 4 /* HPOUT1L_VOL - [3:0] */ + +/* + * R29 (0x1D) - Output1 Right Volume + */ +#define WM8996_DAC1_VU 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_MASK 0x0100 /* DAC1_VU */ +#define WM8996_DAC1_VU_SHIFT 8 /* DAC1_VU */ +#define WM8996_DAC1_VU_WIDTH 1 /* DAC1_VU */ +#define WM8996_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */ +#define WM8996_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */ +#define WM8996_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */ +#define WM8996_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */ +#define WM8996_HPOUT1R_VOL_MASK 0x000F /* HPOUT1R_VOL - [3:0] */ +#define WM8996_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [3:0] */ +#define WM8996_HPOUT1R_VOL_WIDTH 4 /* HPOUT1R_VOL - [3:0] */ + +/* + * R30 (0x1E) - Output2 Left Volume + */ +#define WM8996_DAC2_VU 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_MASK 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_SHIFT 8 /* DAC2_VU */ +#define WM8996_DAC2_VU_WIDTH 1 /* DAC2_VU */ +#define WM8996_HPOUT2L_ZC 0x0080 /* HPOUT2L_ZC */ +#define WM8996_HPOUT2L_ZC_MASK 0x0080 /* HPOUT2L_ZC */ +#define WM8996_HPOUT2L_ZC_SHIFT 7 /* HPOUT2L_ZC */ +#define WM8996_HPOUT2L_ZC_WIDTH 1 /* HPOUT2L_ZC */ +#define WM8996_HPOUT2L_VOL_MASK 0x000F /* HPOUT2L_VOL - [3:0] */ +#define WM8996_HPOUT2L_VOL_SHIFT 0 /* HPOUT2L_VOL - [3:0] */ +#define WM8996_HPOUT2L_VOL_WIDTH 4 /* HPOUT2L_VOL - [3:0] */ + +/* + * R31 (0x1F) - Output2 Right Volume + */ +#define WM8996_DAC2_VU 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_MASK 0x0100 /* DAC2_VU */ +#define WM8996_DAC2_VU_SHIFT 8 /* DAC2_VU */ +#define WM8996_DAC2_VU_WIDTH 1 /* DAC2_VU */ +#define WM8996_HPOUT2R_ZC 0x0080 /* HPOUT2R_ZC */ +#define WM8996_HPOUT2R_ZC_MASK 0x0080 /* HPOUT2R_ZC */ +#define WM8996_HPOUT2R_ZC_SHIFT 7 /* HPOUT2R_ZC */ +#define WM8996_HPOUT2R_ZC_WIDTH 1 /* HPOUT2R_ZC */ +#define WM8996_HPOUT2R_VOL_MASK 0x000F /* HPOUT2R_VOL - [3:0] */ +#define WM8996_HPOUT2R_VOL_SHIFT 0 /* HPOUT2R_VOL - [3:0] */ +#define WM8996_HPOUT2R_VOL_WIDTH 4 /* HPOUT2R_VOL - [3:0] */ + +/* + * R32 (0x20) - MICBIAS (1) + */ +#define WM8996_MICB1_RATE 0x0020 /* MICB1_RATE */ +#define WM8996_MICB1_RATE_MASK 0x0020 /* MICB1_RATE */ +#define WM8996_MICB1_RATE_SHIFT 5 /* MICB1_RATE */ +#define WM8996_MICB1_RATE_WIDTH 1 /* MICB1_RATE */ +#define WM8996_MICB1_MODE 0x0010 /* MICB1_MODE */ +#define WM8996_MICB1_MODE_MASK 0x0010 /* MICB1_MODE */ +#define WM8996_MICB1_MODE_SHIFT 4 /* MICB1_MODE */ +#define WM8996_MICB1_MODE_WIDTH 1 /* MICB1_MODE */ +#define WM8996_MICB1_LVL_MASK 0x000E /* MICB1_LVL - [3:1] */ +#define WM8996_MICB1_LVL_SHIFT 1 /* MICB1_LVL - [3:1] */ +#define WM8996_MICB1_LVL_WIDTH 3 /* MICB1_LVL - [3:1] */ +#define WM8996_MICB1_DISCH 0x0001 /* MICB1_DISCH */ +#define WM8996_MICB1_DISCH_MASK 0x0001 /* MICB1_DISCH */ +#define WM8996_MICB1_DISCH_SHIFT 0 /* MICB1_DISCH */ +#define WM8996_MICB1_DISCH_WIDTH 1 /* MICB1_DISCH */ + +/* + * R33 (0x21) - MICBIAS (2) + */ +#define WM8996_MICB2_RATE 0x0020 /* MICB2_RATE */ +#define WM8996_MICB2_RATE_MASK 0x0020 /* MICB2_RATE */ +#define WM8996_MICB2_RATE_SHIFT 5 /* MICB2_RATE */ +#define WM8996_MICB2_RATE_WIDTH 1 /* MICB2_RATE */ +#define WM8996_MICB2_MODE 0x0010 /* MICB2_MODE */ +#define WM8996_MICB2_MODE_MASK 0x0010 /* MICB2_MODE */ +#define WM8996_MICB2_MODE_SHIFT 4 /* MICB2_MODE */ +#define WM8996_MICB2_MODE_WIDTH 1 /* MICB2_MODE */ +#define WM8996_MICB2_LVL_MASK 0x000E /* MICB2_LVL - [3:1] */ +#define WM8996_MICB2_LVL_SHIFT 1 /* MICB2_LVL - [3:1] */ +#define WM8996_MICB2_LVL_WIDTH 3 /* MICB2_LVL - [3:1] */ +#define WM8996_MICB2_DISCH 0x0001 /* MICB2_DISCH */ +#define WM8996_MICB2_DISCH_MASK 0x0001 /* MICB2_DISCH */ +#define WM8996_MICB2_DISCH_SHIFT 0 /* MICB2_DISCH */ +#define WM8996_MICB2_DISCH_WIDTH 1 /* MICB2_DISCH */ + +/* + * R40 (0x28) - LDO 1 + */ +#define WM8996_LDO1_MODE 0x0020 /* LDO1_MODE */ +#define WM8996_LDO1_MODE_MASK 0x0020 /* LDO1_MODE */ +#define WM8996_LDO1_MODE_SHIFT 5 /* LDO1_MODE */ +#define WM8996_LDO1_MODE_WIDTH 1 /* LDO1_MODE */ +#define WM8996_LDO1_VSEL_MASK 0x0006 /* LDO1_VSEL - [2:1] */ +#define WM8996_LDO1_VSEL_SHIFT 1 /* LDO1_VSEL - [2:1] */ +#define WM8996_LDO1_VSEL_WIDTH 2 /* LDO1_VSEL - [2:1] */ +#define WM8996_LDO1_DISCH 0x0001 /* LDO1_DISCH */ +#define WM8996_LDO1_DISCH_MASK 0x0001 /* LDO1_DISCH */ +#define WM8996_LDO1_DISCH_SHIFT 0 /* LDO1_DISCH */ +#define WM8996_LDO1_DISCH_WIDTH 1 /* LDO1_DISCH */ + +/* + * R41 (0x29) - LDO 2 + */ +#define WM8996_LDO2_MODE 0x0020 /* LDO2_MODE */ +#define WM8996_LDO2_MODE_MASK 0x0020 /* LDO2_MODE */ +#define WM8996_LDO2_MODE_SHIFT 5 /* LDO2_MODE */ +#define WM8996_LDO2_MODE_WIDTH 1 /* LDO2_MODE */ +#define WM8996_LDO2_VSEL_MASK 0x001E /* LDO2_VSEL - [4:1] */ +#define WM8996_LDO2_VSEL_SHIFT 1 /* LDO2_VSEL - [4:1] */ +#define WM8996_LDO2_VSEL_WIDTH 4 /* LDO2_VSEL - [4:1] */ +#define WM8996_LDO2_DISCH 0x0001 /* LDO2_DISCH */ +#define WM8996_LDO2_DISCH_MASK 0x0001 /* LDO2_DISCH */ +#define WM8996_LDO2_DISCH_SHIFT 0 /* LDO2_DISCH */ +#define WM8996_LDO2_DISCH_WIDTH 1 /* LDO2_DISCH */ + +/* + * R48 (0x30) - Accessory Detect Mode 1 + */ +#define WM8996_JD_MODE_MASK 0x0003 /* JD_MODE - [1:0] */ +#define WM8996_JD_MODE_SHIFT 0 /* JD_MODE - [1:0] */ +#define WM8996_JD_MODE_WIDTH 2 /* JD_MODE - [1:0] */ + +/* + * R49 (0x31) - Accessory Detect Mode 2 + */ +#define WM8996_HPOUT1FB_SRC 0x0004 /* HPOUT1FB_SRC */ +#define WM8996_HPOUT1FB_SRC_MASK 0x0004 /* HPOUT1FB_SRC */ +#define WM8996_HPOUT1FB_SRC_SHIFT 2 /* HPOUT1FB_SRC */ +#define WM8996_HPOUT1FB_SRC_WIDTH 1 /* HPOUT1FB_SRC */ +#define WM8996_MICD_SRC 0x0002 /* MICD_SRC */ +#define WM8996_MICD_SRC_MASK 0x0002 /* MICD_SRC */ +#define WM8996_MICD_SRC_SHIFT 1 /* MICD_SRC */ +#define WM8996_MICD_SRC_WIDTH 1 /* MICD_SRC */ +#define WM8996_MICD_BIAS_SRC 0x0001 /* MICD_BIAS_SRC */ +#define WM8996_MICD_BIAS_SRC_MASK 0x0001 /* MICD_BIAS_SRC */ +#define WM8996_MICD_BIAS_SRC_SHIFT 0 /* MICD_BIAS_SRC */ +#define WM8996_MICD_BIAS_SRC_WIDTH 1 /* MICD_BIAS_SRC */ + +/* + * R52 (0x34) - Headphone Detect 1 + */ +#define WM8996_HP_HOLDTIME_MASK 0x00E0 /* HP_HOLDTIME - [7:5] */ +#define WM8996_HP_HOLDTIME_SHIFT 5 /* HP_HOLDTIME - [7:5] */ +#define WM8996_HP_HOLDTIME_WIDTH 3 /* HP_HOLDTIME - [7:5] */ +#define WM8996_HP_CLK_DIV_MASK 0x0018 /* HP_CLK_DIV - [4:3] */ +#define WM8996_HP_CLK_DIV_SHIFT 3 /* HP_CLK_DIV - [4:3] */ +#define WM8996_HP_CLK_DIV_WIDTH 2 /* HP_CLK_DIV - [4:3] */ +#define WM8996_HP_STEP_SIZE 0x0002 /* HP_STEP_SIZE */ +#define WM8996_HP_STEP_SIZE_MASK 0x0002 /* HP_STEP_SIZE */ +#define WM8996_HP_STEP_SIZE_SHIFT 1 /* HP_STEP_SIZE */ +#define WM8996_HP_STEP_SIZE_WIDTH 1 /* HP_STEP_SIZE */ +#define WM8996_HP_POLL 0x0001 /* HP_POLL */ +#define WM8996_HP_POLL_MASK 0x0001 /* HP_POLL */ +#define WM8996_HP_POLL_SHIFT 0 /* HP_POLL */ +#define WM8996_HP_POLL_WIDTH 1 /* HP_POLL */ + +/* + * R53 (0x35) - Headphone Detect 2 + */ +#define WM8996_HP_DONE 0x0080 /* HP_DONE */ +#define WM8996_HP_DONE_MASK 0x0080 /* HP_DONE */ +#define WM8996_HP_DONE_SHIFT 7 /* HP_DONE */ +#define WM8996_HP_DONE_WIDTH 1 /* HP_DONE */ +#define WM8996_HP_LVL_MASK 0x007F /* HP_LVL - [6:0] */ +#define WM8996_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */ +#define WM8996_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */ + +/* + * R56 (0x38) - Mic Detect 1 + */ +#define WM8996_MICD_BIAS_STARTTIME_MASK 0xF000 /* MICD_BIAS_STARTTIME - [15:12] */ +#define WM8996_MICD_BIAS_STARTTIME_SHIFT 12 /* MICD_BIAS_STARTTIME - [15:12] */ +#define WM8996_MICD_BIAS_STARTTIME_WIDTH 4 /* MICD_BIAS_STARTTIME - [15:12] */ +#define WM8996_MICD_RATE_MASK 0x0F00 /* MICD_RATE - [11:8] */ +#define WM8996_MICD_RATE_SHIFT 8 /* MICD_RATE - [11:8] */ +#define WM8996_MICD_RATE_WIDTH 4 /* MICD_RATE - [11:8] */ +#define WM8996_MICD_DBTIME 0x0002 /* MICD_DBTIME */ +#define WM8996_MICD_DBTIME_MASK 0x0002 /* MICD_DBTIME */ +#define WM8996_MICD_DBTIME_SHIFT 1 /* MICD_DBTIME */ +#define WM8996_MICD_DBTIME_WIDTH 1 /* MICD_DBTIME */ +#define WM8996_MICD_ENA 0x0001 /* MICD_ENA */ +#define WM8996_MICD_ENA_MASK 0x0001 /* MICD_ENA */ +#define WM8996_MICD_ENA_SHIFT 0 /* MICD_ENA */ +#define WM8996_MICD_ENA_WIDTH 1 /* MICD_ENA */ + +/* + * R57 (0x39) - Mic Detect 2 + */ +#define WM8996_MICD_LVL_SEL_MASK 0x00FF /* MICD_LVL_SEL - [7:0] */ +#define WM8996_MICD_LVL_SEL_SHIFT 0 /* MICD_LVL_SEL - [7:0] */ +#define WM8996_MICD_LVL_SEL_WIDTH 8 /* MICD_LVL_SEL - [7:0] */ + +/* + * R58 (0x3A) - Mic Detect 3 + */ +#define WM8996_MICD_LVL_MASK 0x07FC /* MICD_LVL - [10:2] */ +#define WM8996_MICD_LVL_SHIFT 2 /* MICD_LVL - [10:2] */ +#define WM8996_MICD_LVL_WIDTH 9 /* MICD_LVL - [10:2] */ +#define WM8996_MICD_VALID 0x0002 /* MICD_VALID */ +#define WM8996_MICD_VALID_MASK 0x0002 /* MICD_VALID */ +#define WM8996_MICD_VALID_SHIFT 1 /* MICD_VALID */ +#define WM8996_MICD_VALID_WIDTH 1 /* MICD_VALID */ +#define WM8996_MICD_STS 0x0001 /* MICD_STS */ +#define WM8996_MICD_STS_MASK 0x0001 /* MICD_STS */ +#define WM8996_MICD_STS_SHIFT 0 /* MICD_STS */ +#define WM8996_MICD_STS_WIDTH 1 /* MICD_STS */ + +/* + * R64 (0x40) - Charge Pump (1) + */ +#define WM8996_CP_ENA 0x8000 /* CP_ENA */ +#define WM8996_CP_ENA_MASK 0x8000 /* CP_ENA */ +#define WM8996_CP_ENA_SHIFT 15 /* CP_ENA */ +#define WM8996_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R65 (0x41) - Charge Pump (2) + */ +#define WM8996_CP_DISCH 0x8000 /* CP_DISCH */ +#define WM8996_CP_DISCH_MASK 0x8000 /* CP_DISCH */ +#define WM8996_CP_DISCH_SHIFT 15 /* CP_DISCH */ +#define WM8996_CP_DISCH_WIDTH 1 /* CP_DISCH */ + +/* + * R80 (0x50) - DC Servo (1) + */ +#define WM8996_DCS_ENA_CHAN_3 0x0008 /* DCS_ENA_CHAN_3 */ +#define WM8996_DCS_ENA_CHAN_3_MASK 0x0008 /* DCS_ENA_CHAN_3 */ +#define WM8996_DCS_ENA_CHAN_3_SHIFT 3 /* DCS_ENA_CHAN_3 */ +#define WM8996_DCS_ENA_CHAN_3_WIDTH 1 /* DCS_ENA_CHAN_3 */ +#define WM8996_DCS_ENA_CHAN_2 0x0004 /* DCS_ENA_CHAN_2 */ +#define WM8996_DCS_ENA_CHAN_2_MASK 0x0004 /* DCS_ENA_CHAN_2 */ +#define WM8996_DCS_ENA_CHAN_2_SHIFT 2 /* DCS_ENA_CHAN_2 */ +#define WM8996_DCS_ENA_CHAN_2_WIDTH 1 /* DCS_ENA_CHAN_2 */ +#define WM8996_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8996_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8996_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM8996_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM8996_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8996_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8996_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM8996_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R81 (0x51) - DC Servo (2) + */ +#define WM8996_DCS_TRIG_SINGLE_3 0x8000 /* DCS_TRIG_SINGLE_3 */ +#define WM8996_DCS_TRIG_SINGLE_3_MASK 0x8000 /* DCS_TRIG_SINGLE_3 */ +#define WM8996_DCS_TRIG_SINGLE_3_SHIFT 15 /* DCS_TRIG_SINGLE_3 */ +#define WM8996_DCS_TRIG_SINGLE_3_WIDTH 1 /* DCS_TRIG_SINGLE_3 */ +#define WM8996_DCS_TRIG_SINGLE_2 0x4000 /* DCS_TRIG_SINGLE_2 */ +#define WM8996_DCS_TRIG_SINGLE_2_MASK 0x4000 /* DCS_TRIG_SINGLE_2 */ +#define WM8996_DCS_TRIG_SINGLE_2_SHIFT 14 /* DCS_TRIG_SINGLE_2 */ +#define WM8996_DCS_TRIG_SINGLE_2_WIDTH 1 /* DCS_TRIG_SINGLE_2 */ +#define WM8996_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8996_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8996_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM8996_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM8996_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8996_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8996_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM8996_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM8996_DCS_TRIG_SERIES_3 0x0800 /* DCS_TRIG_SERIES_3 */ +#define WM8996_DCS_TRIG_SERIES_3_MASK 0x0800 /* DCS_TRIG_SERIES_3 */ +#define WM8996_DCS_TRIG_SERIES_3_SHIFT 11 /* DCS_TRIG_SERIES_3 */ +#define WM8996_DCS_TRIG_SERIES_3_WIDTH 1 /* DCS_TRIG_SERIES_3 */ +#define WM8996_DCS_TRIG_SERIES_2 0x0400 /* DCS_TRIG_SERIES_2 */ +#define WM8996_DCS_TRIG_SERIES_2_MASK 0x0400 /* DCS_TRIG_SERIES_2 */ +#define WM8996_DCS_TRIG_SERIES_2_SHIFT 10 /* DCS_TRIG_SERIES_2 */ +#define WM8996_DCS_TRIG_SERIES_2_WIDTH 1 /* DCS_TRIG_SERIES_2 */ +#define WM8996_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8996_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8996_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM8996_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM8996_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8996_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8996_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM8996_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM8996_DCS_TRIG_STARTUP_3 0x0080 /* DCS_TRIG_STARTUP_3 */ +#define WM8996_DCS_TRIG_STARTUP_3_MASK 0x0080 /* DCS_TRIG_STARTUP_3 */ +#define WM8996_DCS_TRIG_STARTUP_3_SHIFT 7 /* DCS_TRIG_STARTUP_3 */ +#define WM8996_DCS_TRIG_STARTUP_3_WIDTH 1 /* DCS_TRIG_STARTUP_3 */ +#define WM8996_DCS_TRIG_STARTUP_2 0x0040 /* DCS_TRIG_STARTUP_2 */ +#define WM8996_DCS_TRIG_STARTUP_2_MASK 0x0040 /* DCS_TRIG_STARTUP_2 */ +#define WM8996_DCS_TRIG_STARTUP_2_SHIFT 6 /* DCS_TRIG_STARTUP_2 */ +#define WM8996_DCS_TRIG_STARTUP_2_WIDTH 1 /* DCS_TRIG_STARTUP_2 */ +#define WM8996_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8996_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8996_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM8996_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM8996_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8996_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8996_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM8996_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM8996_DCS_TRIG_DAC_WR_3 0x0008 /* DCS_TRIG_DAC_WR_3 */ +#define WM8996_DCS_TRIG_DAC_WR_3_MASK 0x0008 /* DCS_TRIG_DAC_WR_3 */ +#define WM8996_DCS_TRIG_DAC_WR_3_SHIFT 3 /* DCS_TRIG_DAC_WR_3 */ +#define WM8996_DCS_TRIG_DAC_WR_3_WIDTH 1 /* DCS_TRIG_DAC_WR_3 */ +#define WM8996_DCS_TRIG_DAC_WR_2 0x0004 /* DCS_TRIG_DAC_WR_2 */ +#define WM8996_DCS_TRIG_DAC_WR_2_MASK 0x0004 /* DCS_TRIG_DAC_WR_2 */ +#define WM8996_DCS_TRIG_DAC_WR_2_SHIFT 2 /* DCS_TRIG_DAC_WR_2 */ +#define WM8996_DCS_TRIG_DAC_WR_2_WIDTH 1 /* DCS_TRIG_DAC_WR_2 */ +#define WM8996_DCS_TRIG_DAC_WR_1 0x0002 /* DCS_TRIG_DAC_WR_1 */ +#define WM8996_DCS_TRIG_DAC_WR_1_MASK 0x0002 /* DCS_TRIG_DAC_WR_1 */ +#define WM8996_DCS_TRIG_DAC_WR_1_SHIFT 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8996_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8996_DCS_TRIG_DAC_WR_0 0x0001 /* DCS_TRIG_DAC_WR_0 */ +#define WM8996_DCS_TRIG_DAC_WR_0_MASK 0x0001 /* DCS_TRIG_DAC_WR_0 */ +#define WM8996_DCS_TRIG_DAC_WR_0_SHIFT 0 /* DCS_TRIG_DAC_WR_0 */ +#define WM8996_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ + +/* + * R82 (0x52) - DC Servo (3) + */ +#define WM8996_DCS_TIMER_PERIOD_23_MASK 0x0F00 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8996_DCS_TIMER_PERIOD_23_SHIFT 8 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8996_DCS_TIMER_PERIOD_23_WIDTH 4 /* DCS_TIMER_PERIOD_23 - [11:8] */ +#define WM8996_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8996_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8996_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R84 (0x54) - DC Servo (5) + */ +#define WM8996_DCS_SERIES_NO_23_MASK 0x7F00 /* DCS_SERIES_NO_23 - [14:8] */ +#define WM8996_DCS_SERIES_NO_23_SHIFT 8 /* DCS_SERIES_NO_23 - [14:8] */ +#define WM8996_DCS_SERIES_NO_23_WIDTH 7 /* DCS_SERIES_NO_23 - [14:8] */ +#define WM8996_DCS_SERIES_NO_01_MASK 0x007F /* DCS_SERIES_NO_01 - [6:0] */ +#define WM8996_DCS_SERIES_NO_01_SHIFT 0 /* DCS_SERIES_NO_01 - [6:0] */ +#define WM8996_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [6:0] */ + +/* + * R85 (0x55) - DC Servo (6) + */ +#define WM8996_DCS_DAC_WR_VAL_3_MASK 0xFF00 /* DCS_DAC_WR_VAL_3 - [15:8] */ +#define WM8996_DCS_DAC_WR_VAL_3_SHIFT 8 /* DCS_DAC_WR_VAL_3 - [15:8] */ +#define WM8996_DCS_DAC_WR_VAL_3_WIDTH 8 /* DCS_DAC_WR_VAL_3 - [15:8] */ +#define WM8996_DCS_DAC_WR_VAL_2_MASK 0x00FF /* DCS_DAC_WR_VAL_2 - [7:0] */ +#define WM8996_DCS_DAC_WR_VAL_2_SHIFT 0 /* DCS_DAC_WR_VAL_2 - [7:0] */ +#define WM8996_DCS_DAC_WR_VAL_2_WIDTH 8 /* DCS_DAC_WR_VAL_2 - [7:0] */ + +/* + * R86 (0x56) - DC Servo (7) + */ +#define WM8996_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8996_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8996_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8996_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8996_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8996_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R87 (0x57) - DC Servo Readback 0 + */ +#define WM8996_DCS_CAL_COMPLETE_MASK 0x0F00 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8996_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8996_DCS_CAL_COMPLETE_WIDTH 4 /* DCS_CAL_COMPLETE - [11:8] */ +#define WM8996_DCS_DAC_WR_COMPLETE_MASK 0x00F0 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8996_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8996_DCS_DAC_WR_COMPLETE_WIDTH 4 /* DCS_DAC_WR_COMPLETE - [7:4] */ +#define WM8996_DCS_STARTUP_COMPLETE_MASK 0x000F /* DCS_STARTUP_COMPLETE - [3:0] */ +#define WM8996_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [3:0] */ +#define WM8996_DCS_STARTUP_COMPLETE_WIDTH 4 /* DCS_STARTUP_COMPLETE - [3:0] */ + +/* + * R96 (0x60) - Analogue HP (1) + */ +#define WM8996_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8996_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8996_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ +#define WM8996_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */ +#define WM8996_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */ +#define WM8996_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */ +#define WM8996_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */ +#define WM8996_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */ +#define WM8996_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */ +#define WM8996_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */ +#define WM8996_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */ +#define WM8996_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */ +#define WM8996_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8996_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8996_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */ +#define WM8996_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */ +#define WM8996_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */ +#define WM8996_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */ +#define WM8996_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */ +#define WM8996_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */ +#define WM8996_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */ +#define WM8996_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */ +#define WM8996_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */ +#define WM8996_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */ + +/* + * R97 (0x61) - Analogue HP (2) + */ +#define WM8996_HPOUT2L_RMV_SHORT 0x0080 /* HPOUT2L_RMV_SHORT */ +#define WM8996_HPOUT2L_RMV_SHORT_MASK 0x0080 /* HPOUT2L_RMV_SHORT */ +#define WM8996_HPOUT2L_RMV_SHORT_SHIFT 7 /* HPOUT2L_RMV_SHORT */ +#define WM8996_HPOUT2L_RMV_SHORT_WIDTH 1 /* HPOUT2L_RMV_SHORT */ +#define WM8996_HPOUT2L_OUTP 0x0040 /* HPOUT2L_OUTP */ +#define WM8996_HPOUT2L_OUTP_MASK 0x0040 /* HPOUT2L_OUTP */ +#define WM8996_HPOUT2L_OUTP_SHIFT 6 /* HPOUT2L_OUTP */ +#define WM8996_HPOUT2L_OUTP_WIDTH 1 /* HPOUT2L_OUTP */ +#define WM8996_HPOUT2L_DLY 0x0020 /* HPOUT2L_DLY */ +#define WM8996_HPOUT2L_DLY_MASK 0x0020 /* HPOUT2L_DLY */ +#define WM8996_HPOUT2L_DLY_SHIFT 5 /* HPOUT2L_DLY */ +#define WM8996_HPOUT2L_DLY_WIDTH 1 /* HPOUT2L_DLY */ +#define WM8996_HPOUT2R_RMV_SHORT 0x0008 /* HPOUT2R_RMV_SHORT */ +#define WM8996_HPOUT2R_RMV_SHORT_MASK 0x0008 /* HPOUT2R_RMV_SHORT */ +#define WM8996_HPOUT2R_RMV_SHORT_SHIFT 3 /* HPOUT2R_RMV_SHORT */ +#define WM8996_HPOUT2R_RMV_SHORT_WIDTH 1 /* HPOUT2R_RMV_SHORT */ +#define WM8996_HPOUT2R_OUTP 0x0004 /* HPOUT2R_OUTP */ +#define WM8996_HPOUT2R_OUTP_MASK 0x0004 /* HPOUT2R_OUTP */ +#define WM8996_HPOUT2R_OUTP_SHIFT 2 /* HPOUT2R_OUTP */ +#define WM8996_HPOUT2R_OUTP_WIDTH 1 /* HPOUT2R_OUTP */ +#define WM8996_HPOUT2R_DLY 0x0002 /* HPOUT2R_DLY */ +#define WM8996_HPOUT2R_DLY_MASK 0x0002 /* HPOUT2R_DLY */ +#define WM8996_HPOUT2R_DLY_SHIFT 1 /* HPOUT2R_DLY */ +#define WM8996_HPOUT2R_DLY_WIDTH 1 /* HPOUT2R_DLY */ + +/* + * R256 (0x100) - Chip Revision + */ +#define WM8996_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */ +#define WM8996_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */ +#define WM8996_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */ + +/* + * R257 (0x101) - Control Interface (1) + */ +#define WM8996_REG_SYNC 0x8000 /* REG_SYNC */ +#define WM8996_REG_SYNC_MASK 0x8000 /* REG_SYNC */ +#define WM8996_REG_SYNC_SHIFT 15 /* REG_SYNC */ +#define WM8996_REG_SYNC_WIDTH 1 /* REG_SYNC */ +#define WM8996_AUTO_INC 0x0004 /* AUTO_INC */ +#define WM8996_AUTO_INC_MASK 0x0004 /* AUTO_INC */ +#define WM8996_AUTO_INC_SHIFT 2 /* AUTO_INC */ +#define WM8996_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +/* + * R272 (0x110) - Write Sequencer Ctrl (1) + */ +#define WM8996_WSEQ_ENA 0x8000 /* WSEQ_ENA */ +#define WM8996_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */ +#define WM8996_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */ +#define WM8996_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8996_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8996_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8996_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8996_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8996_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8996_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8996_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8996_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8996_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */ +#define WM8996_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */ +#define WM8996_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */ + +/* + * R273 (0x111) - Write Sequencer Ctrl (2) + */ +#define WM8996_WSEQ_BUSY 0x0100 /* WSEQ_BUSY */ +#define WM8996_WSEQ_BUSY_MASK 0x0100 /* WSEQ_BUSY */ +#define WM8996_WSEQ_BUSY_SHIFT 8 /* WSEQ_BUSY */ +#define WM8996_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ +#define WM8996_WSEQ_CURRENT_INDEX_MASK 0x007F /* WSEQ_CURRENT_INDEX - [6:0] */ +#define WM8996_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [6:0] */ +#define WM8996_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [6:0] */ + +/* + * R512 (0x200) - AIF Clocking (1) + */ +#define WM8996_SYSCLK_SRC_MASK 0x0018 /* SYSCLK_SRC - [4:3] */ +#define WM8996_SYSCLK_SRC_SHIFT 3 /* SYSCLK_SRC - [4:3] */ +#define WM8996_SYSCLK_SRC_WIDTH 2 /* SYSCLK_SRC - [4:3] */ +#define WM8996_SYSCLK_INV 0x0004 /* SYSCLK_INV */ +#define WM8996_SYSCLK_INV_MASK 0x0004 /* SYSCLK_INV */ +#define WM8996_SYSCLK_INV_SHIFT 2 /* SYSCLK_INV */ +#define WM8996_SYSCLK_INV_WIDTH 1 /* SYSCLK_INV */ +#define WM8996_SYSCLK_DIV 0x0002 /* SYSCLK_DIV */ +#define WM8996_SYSCLK_DIV_MASK 0x0002 /* SYSCLK_DIV */ +#define WM8996_SYSCLK_DIV_SHIFT 1 /* SYSCLK_DIV */ +#define WM8996_SYSCLK_DIV_WIDTH 1 /* SYSCLK_DIV */ +#define WM8996_SYSCLK_ENA 0x0001 /* SYSCLK_ENA */ +#define WM8996_SYSCLK_ENA_MASK 0x0001 /* SYSCLK_ENA */ +#define WM8996_SYSCLK_ENA_SHIFT 0 /* SYSCLK_ENA */ +#define WM8996_SYSCLK_ENA_WIDTH 1 /* SYSCLK_ENA */ + +/* + * R513 (0x201) - AIF Clocking (2) + */ +#define WM8996_DSP2_DIV_MASK 0x0018 /* DSP2_DIV - [4:3] */ +#define WM8996_DSP2_DIV_SHIFT 3 /* DSP2_DIV - [4:3] */ +#define WM8996_DSP2_DIV_WIDTH 2 /* DSP2_DIV - [4:3] */ +#define WM8996_DSP1_DIV_MASK 0x0003 /* DSP1_DIV - [1:0] */ +#define WM8996_DSP1_DIV_SHIFT 0 /* DSP1_DIV - [1:0] */ +#define WM8996_DSP1_DIV_WIDTH 2 /* DSP1_DIV - [1:0] */ + +/* + * R520 (0x208) - Clocking (1) + */ +#define WM8996_LFCLK_ENA 0x0020 /* LFCLK_ENA */ +#define WM8996_LFCLK_ENA_MASK 0x0020 /* LFCLK_ENA */ +#define WM8996_LFCLK_ENA_SHIFT 5 /* LFCLK_ENA */ +#define WM8996_LFCLK_ENA_WIDTH 1 /* LFCLK_ENA */ +#define WM8996_TOCLK_ENA 0x0010 /* TOCLK_ENA */ +#define WM8996_TOCLK_ENA_MASK 0x0010 /* TOCLK_ENA */ +#define WM8996_TOCLK_ENA_SHIFT 4 /* TOCLK_ENA */ +#define WM8996_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ +#define WM8996_AIFCLK_ENA 0x0004 /* AIFCLK_ENA */ +#define WM8996_AIFCLK_ENA_MASK 0x0004 /* AIFCLK_ENA */ +#define WM8996_AIFCLK_ENA_SHIFT 2 /* AIFCLK_ENA */ +#define WM8996_AIFCLK_ENA_WIDTH 1 /* AIFCLK_ENA */ +#define WM8996_SYSDSPCLK_ENA 0x0002 /* SYSDSPCLK_ENA */ +#define WM8996_SYSDSPCLK_ENA_MASK 0x0002 /* SYSDSPCLK_ENA */ +#define WM8996_SYSDSPCLK_ENA_SHIFT 1 /* SYSDSPCLK_ENA */ +#define WM8996_SYSDSPCLK_ENA_WIDTH 1 /* SYSDSPCLK_ENA */ + +/* + * R521 (0x209) - Clocking (2) + */ +#define WM8996_TOCLK_DIV_MASK 0x0700 /* TOCLK_DIV - [10:8] */ +#define WM8996_TOCLK_DIV_SHIFT 8 /* TOCLK_DIV - [10:8] */ +#define WM8996_TOCLK_DIV_WIDTH 3 /* TOCLK_DIV - [10:8] */ +#define WM8996_DBCLK_DIV_MASK 0x00F0 /* DBCLK_DIV - [7:4] */ +#define WM8996_DBCLK_DIV_SHIFT 4 /* DBCLK_DIV - [7:4] */ +#define WM8996_DBCLK_DIV_WIDTH 4 /* DBCLK_DIV - [7:4] */ +#define WM8996_OPCLK_DIV_MASK 0x0007 /* OPCLK_DIV - [2:0] */ +#define WM8996_OPCLK_DIV_SHIFT 0 /* OPCLK_DIV - [2:0] */ +#define WM8996_OPCLK_DIV_WIDTH 3 /* OPCLK_DIV - [2:0] */ + +/* + * R528 (0x210) - AIF Rate + */ +#define WM8996_SYSCLK_RATE 0x0001 /* SYSCLK_RATE */ +#define WM8996_SYSCLK_RATE_MASK 0x0001 /* SYSCLK_RATE */ +#define WM8996_SYSCLK_RATE_SHIFT 0 /* SYSCLK_RATE */ +#define WM8996_SYSCLK_RATE_WIDTH 1 /* SYSCLK_RATE */ + +/* + * R544 (0x220) - FLL Control (1) + */ +#define WM8996_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */ +#define WM8996_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */ +#define WM8996_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */ +#define WM8996_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */ +#define WM8996_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM8996_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM8996_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM8996_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R545 (0x221) - FLL Control (2) + */ +#define WM8996_FLL_OUTDIV_MASK 0x3F00 /* FLL_OUTDIV - [13:8] */ +#define WM8996_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [13:8] */ +#define WM8996_FLL_OUTDIV_WIDTH 6 /* FLL_OUTDIV - [13:8] */ +#define WM8996_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM8996_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM8996_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R546 (0x222) - FLL Control (3) + */ +#define WM8996_FLL_THETA_MASK 0xFFFF /* FLL_THETA - [15:0] */ +#define WM8996_FLL_THETA_SHIFT 0 /* FLL_THETA - [15:0] */ +#define WM8996_FLL_THETA_WIDTH 16 /* FLL_THETA - [15:0] */ + +/* + * R547 (0x223) - FLL Control (4) + */ +#define WM8996_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */ +#define WM8996_FLL_N_SHIFT 5 /* FLL_N - [14:5] */ +#define WM8996_FLL_N_WIDTH 10 /* FLL_N - [14:5] */ +#define WM8996_FLL_LOOP_GAIN_MASK 0x000F /* FLL_LOOP_GAIN - [3:0] */ +#define WM8996_FLL_LOOP_GAIN_SHIFT 0 /* FLL_LOOP_GAIN - [3:0] */ +#define WM8996_FLL_LOOP_GAIN_WIDTH 4 /* FLL_LOOP_GAIN - [3:0] */ + +/* + * R548 (0x224) - FLL Control (5) + */ +#define WM8996_FLL_FRC_NCO_VAL_MASK 0x1F80 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8996_FLL_FRC_NCO_VAL_SHIFT 7 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8996_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8996_FLL_FRC_NCO 0x0040 /* FLL_FRC_NCO */ +#define WM8996_FLL_FRC_NCO_MASK 0x0040 /* FLL_FRC_NCO */ +#define WM8996_FLL_FRC_NCO_SHIFT 6 /* FLL_FRC_NCO */ +#define WM8996_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */ +#define WM8996_FLL_REFCLK_DIV_MASK 0x0018 /* FLL_REFCLK_DIV - [4:3] */ +#define WM8996_FLL_REFCLK_DIV_SHIFT 3 /* FLL_REFCLK_DIV - [4:3] */ +#define WM8996_FLL_REFCLK_DIV_WIDTH 2 /* FLL_REFCLK_DIV - [4:3] */ +#define WM8996_FLL_REF_FREQ 0x0004 /* FLL_REF_FREQ */ +#define WM8996_FLL_REF_FREQ_MASK 0x0004 /* FLL_REF_FREQ */ +#define WM8996_FLL_REF_FREQ_SHIFT 2 /* FLL_REF_FREQ */ +#define WM8996_FLL_REF_FREQ_WIDTH 1 /* FLL_REF_FREQ */ +#define WM8996_FLL_REFCLK_SRC_MASK 0x0003 /* FLL_REFCLK_SRC - [1:0] */ +#define WM8996_FLL_REFCLK_SRC_SHIFT 0 /* FLL_REFCLK_SRC - [1:0] */ +#define WM8996_FLL_REFCLK_SRC_WIDTH 2 /* FLL_REFCLK_SRC - [1:0] */ + +/* + * R549 (0x225) - FLL Control (6) + */ +#define WM8996_FLL_REFCLK_SRC_STS_MASK 0x000C /* FLL_REFCLK_SRC_STS - [3:2] */ +#define WM8996_FLL_REFCLK_SRC_STS_SHIFT 2 /* FLL_REFCLK_SRC_STS - [3:2] */ +#define WM8996_FLL_REFCLK_SRC_STS_WIDTH 2 /* FLL_REFCLK_SRC_STS - [3:2] */ +#define WM8996_FLL_SWITCH_CLK 0x0001 /* FLL_SWITCH_CLK */ +#define WM8996_FLL_SWITCH_CLK_MASK 0x0001 /* FLL_SWITCH_CLK */ +#define WM8996_FLL_SWITCH_CLK_SHIFT 0 /* FLL_SWITCH_CLK */ +#define WM8996_FLL_SWITCH_CLK_WIDTH 1 /* FLL_SWITCH_CLK */ + +/* + * R550 (0x226) - FLL EFS 1 + */ +#define WM8996_FLL_LAMBDA_MASK 0xFFFF /* FLL_LAMBDA - [15:0] */ +#define WM8996_FLL_LAMBDA_SHIFT 0 /* FLL_LAMBDA - [15:0] */ +#define WM8996_FLL_LAMBDA_WIDTH 16 /* FLL_LAMBDA - [15:0] */ + +/* + * R551 (0x227) - FLL EFS 2 + */ +#define WM8996_FLL_LFSR_SEL_MASK 0x0006 /* FLL_LFSR_SEL - [2:1] */ +#define WM8996_FLL_LFSR_SEL_SHIFT 1 /* FLL_LFSR_SEL - [2:1] */ +#define WM8996_FLL_LFSR_SEL_WIDTH 2 /* FLL_LFSR_SEL - [2:1] */ +#define WM8996_FLL_EFS_ENA 0x0001 /* FLL_EFS_ENA */ +#define WM8996_FLL_EFS_ENA_MASK 0x0001 /* FLL_EFS_ENA */ +#define WM8996_FLL_EFS_ENA_SHIFT 0 /* FLL_EFS_ENA */ +#define WM8996_FLL_EFS_ENA_WIDTH 1 /* FLL_EFS_ENA */ + +/* + * R768 (0x300) - AIF1 Control + */ +#define WM8996_AIF1_TRI 0x0004 /* AIF1_TRI */ +#define WM8996_AIF1_TRI_MASK 0x0004 /* AIF1_TRI */ +#define WM8996_AIF1_TRI_SHIFT 2 /* AIF1_TRI */ +#define WM8996_AIF1_TRI_WIDTH 1 /* AIF1_TRI */ +#define WM8996_AIF1_FMT_MASK 0x0003 /* AIF1_FMT - [1:0] */ +#define WM8996_AIF1_FMT_SHIFT 0 /* AIF1_FMT - [1:0] */ +#define WM8996_AIF1_FMT_WIDTH 2 /* AIF1_FMT - [1:0] */ + +/* + * R769 (0x301) - AIF1 BCLK + */ +#define WM8996_AIF1_BCLK_INV 0x0400 /* AIF1_BCLK_INV */ +#define WM8996_AIF1_BCLK_INV_MASK 0x0400 /* AIF1_BCLK_INV */ +#define WM8996_AIF1_BCLK_INV_SHIFT 10 /* AIF1_BCLK_INV */ +#define WM8996_AIF1_BCLK_INV_WIDTH 1 /* AIF1_BCLK_INV */ +#define WM8996_AIF1_BCLK_FRC 0x0200 /* AIF1_BCLK_FRC */ +#define WM8996_AIF1_BCLK_FRC_MASK 0x0200 /* AIF1_BCLK_FRC */ +#define WM8996_AIF1_BCLK_FRC_SHIFT 9 /* AIF1_BCLK_FRC */ +#define WM8996_AIF1_BCLK_FRC_WIDTH 1 /* AIF1_BCLK_FRC */ +#define WM8996_AIF1_BCLK_MSTR 0x0100 /* AIF1_BCLK_MSTR */ +#define WM8996_AIF1_BCLK_MSTR_MASK 0x0100 /* AIF1_BCLK_MSTR */ +#define WM8996_AIF1_BCLK_MSTR_SHIFT 8 /* AIF1_BCLK_MSTR */ +#define WM8996_AIF1_BCLK_MSTR_WIDTH 1 /* AIF1_BCLK_MSTR */ +#define WM8996_AIF1_BCLK_DIV_MASK 0x000F /* AIF1_BCLK_DIV - [3:0] */ +#define WM8996_AIF1_BCLK_DIV_SHIFT 0 /* AIF1_BCLK_DIV - [3:0] */ +#define WM8996_AIF1_BCLK_DIV_WIDTH 4 /* AIF1_BCLK_DIV - [3:0] */ + +/* + * R770 (0x302) - AIF1 TX LRCLK(1) + */ +#define WM8996_AIF1TX_RATE_MASK 0x07FF /* AIF1TX_RATE - [10:0] */ +#define WM8996_AIF1TX_RATE_SHIFT 0 /* AIF1TX_RATE - [10:0] */ +#define WM8996_AIF1TX_RATE_WIDTH 11 /* AIF1TX_RATE - [10:0] */ + +/* + * R771 (0x303) - AIF1 TX LRCLK(2) + */ +#define WM8996_AIF1TX_LRCLK_MODE 0x0008 /* AIF1TX_LRCLK_MODE */ +#define WM8996_AIF1TX_LRCLK_MODE_MASK 0x0008 /* AIF1TX_LRCLK_MODE */ +#define WM8996_AIF1TX_LRCLK_MODE_SHIFT 3 /* AIF1TX_LRCLK_MODE */ +#define WM8996_AIF1TX_LRCLK_MODE_WIDTH 1 /* AIF1TX_LRCLK_MODE */ +#define WM8996_AIF1TX_LRCLK_INV 0x0004 /* AIF1TX_LRCLK_INV */ +#define WM8996_AIF1TX_LRCLK_INV_MASK 0x0004 /* AIF1TX_LRCLK_INV */ +#define WM8996_AIF1TX_LRCLK_INV_SHIFT 2 /* AIF1TX_LRCLK_INV */ +#define WM8996_AIF1TX_LRCLK_INV_WIDTH 1 /* AIF1TX_LRCLK_INV */ +#define WM8996_AIF1TX_LRCLK_FRC 0x0002 /* AIF1TX_LRCLK_FRC */ +#define WM8996_AIF1TX_LRCLK_FRC_MASK 0x0002 /* AIF1TX_LRCLK_FRC */ +#define WM8996_AIF1TX_LRCLK_FRC_SHIFT 1 /* AIF1TX_LRCLK_FRC */ +#define WM8996_AIF1TX_LRCLK_FRC_WIDTH 1 /* AIF1TX_LRCLK_FRC */ +#define WM8996_AIF1TX_LRCLK_MSTR 0x0001 /* AIF1TX_LRCLK_MSTR */ +#define WM8996_AIF1TX_LRCLK_MSTR_MASK 0x0001 /* AIF1TX_LRCLK_MSTR */ +#define WM8996_AIF1TX_LRCLK_MSTR_SHIFT 0 /* AIF1TX_LRCLK_MSTR */ +#define WM8996_AIF1TX_LRCLK_MSTR_WIDTH 1 /* AIF1TX_LRCLK_MSTR */ + +/* + * R772 (0x304) - AIF1 RX LRCLK(1) + */ +#define WM8996_AIF1RX_RATE_MASK 0x07FF /* AIF1RX_RATE - [10:0] */ +#define WM8996_AIF1RX_RATE_SHIFT 0 /* AIF1RX_RATE - [10:0] */ +#define WM8996_AIF1RX_RATE_WIDTH 11 /* AIF1RX_RATE - [10:0] */ + +/* + * R773 (0x305) - AIF1 RX LRCLK(2) + */ +#define WM8996_AIF1RX_LRCLK_INV 0x0004 /* AIF1RX_LRCLK_INV */ +#define WM8996_AIF1RX_LRCLK_INV_MASK 0x0004 /* AIF1RX_LRCLK_INV */ +#define WM8996_AIF1RX_LRCLK_INV_SHIFT 2 /* AIF1RX_LRCLK_INV */ +#define WM8996_AIF1RX_LRCLK_INV_WIDTH 1 /* AIF1RX_LRCLK_INV */ +#define WM8996_AIF1RX_LRCLK_FRC 0x0002 /* AIF1RX_LRCLK_FRC */ +#define WM8996_AIF1RX_LRCLK_FRC_MASK 0x0002 /* AIF1RX_LRCLK_FRC */ +#define WM8996_AIF1RX_LRCLK_FRC_SHIFT 1 /* AIF1RX_LRCLK_FRC */ +#define WM8996_AIF1RX_LRCLK_FRC_WIDTH 1 /* AIF1RX_LRCLK_FRC */ +#define WM8996_AIF1RX_LRCLK_MSTR 0x0001 /* AIF1RX_LRCLK_MSTR */ +#define WM8996_AIF1RX_LRCLK_MSTR_MASK 0x0001 /* AIF1RX_LRCLK_MSTR */ +#define WM8996_AIF1RX_LRCLK_MSTR_SHIFT 0 /* AIF1RX_LRCLK_MSTR */ +#define WM8996_AIF1RX_LRCLK_MSTR_WIDTH 1 /* AIF1RX_LRCLK_MSTR */ + +/* + * R774 (0x306) - AIF1TX Data Configuration (1) + */ +#define WM8996_AIF1TX_WL_MASK 0xFF00 /* AIF1TX_WL - [15:8] */ +#define WM8996_AIF1TX_WL_SHIFT 8 /* AIF1TX_WL - [15:8] */ +#define WM8996_AIF1TX_WL_WIDTH 8 /* AIF1TX_WL - [15:8] */ +#define WM8996_AIF1TX_SLOT_LEN_MASK 0x00FF /* AIF1TX_SLOT_LEN - [7:0] */ +#define WM8996_AIF1TX_SLOT_LEN_SHIFT 0 /* AIF1TX_SLOT_LEN - [7:0] */ +#define WM8996_AIF1TX_SLOT_LEN_WIDTH 8 /* AIF1TX_SLOT_LEN - [7:0] */ + +/* + * R775 (0x307) - AIF1TX Data Configuration (2) + */ +#define WM8996_AIF1TX_DAT_TRI 0x0001 /* AIF1TX_DAT_TRI */ +#define WM8996_AIF1TX_DAT_TRI_MASK 0x0001 /* AIF1TX_DAT_TRI */ +#define WM8996_AIF1TX_DAT_TRI_SHIFT 0 /* AIF1TX_DAT_TRI */ +#define WM8996_AIF1TX_DAT_TRI_WIDTH 1 /* AIF1TX_DAT_TRI */ + +/* + * R776 (0x308) - AIF1RX Data Configuration + */ +#define WM8996_AIF1RX_WL_MASK 0xFF00 /* AIF1RX_WL - [15:8] */ +#define WM8996_AIF1RX_WL_SHIFT 8 /* AIF1RX_WL - [15:8] */ +#define WM8996_AIF1RX_WL_WIDTH 8 /* AIF1RX_WL - [15:8] */ +#define WM8996_AIF1RX_SLOT_LEN_MASK 0x00FF /* AIF1RX_SLOT_LEN - [7:0] */ +#define WM8996_AIF1RX_SLOT_LEN_SHIFT 0 /* AIF1RX_SLOT_LEN - [7:0] */ +#define WM8996_AIF1RX_SLOT_LEN_WIDTH 8 /* AIF1RX_SLOT_LEN - [7:0] */ + +/* + * R777 (0x309) - AIF1TX Channel 0 Configuration + */ +#define WM8996_AIF1TX_CHAN0_DAT_INV 0x8000 /* AIF1TX_CHAN0_DAT_INV */ +#define WM8996_AIF1TX_CHAN0_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN0_DAT_INV */ +#define WM8996_AIF1TX_CHAN0_DAT_INV_SHIFT 15 /* AIF1TX_CHAN0_DAT_INV */ +#define WM8996_AIF1TX_CHAN0_DAT_INV_WIDTH 1 /* AIF1TX_CHAN0_DAT_INV */ +#define WM8996_AIF1TX_CHAN0_SPACING_MASK 0x7E00 /* AIF1TX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN0_SPACING_SHIFT 9 /* AIF1TX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN0_SPACING_WIDTH 6 /* AIF1TX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN0_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN0_SLOTS_SHIFT 6 /* AIF1TX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN0_SLOTS_WIDTH 3 /* AIF1TX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN0_START_SLOT_MASK 0x003F /* AIF1TX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN0_START_SLOT_SHIFT 0 /* AIF1TX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN0_START_SLOT_WIDTH 6 /* AIF1TX_CHAN0_START_SLOT - [5:0] */ + +/* + * R778 (0x30A) - AIF1TX Channel 1 Configuration + */ +#define WM8996_AIF1TX_CHAN1_DAT_INV 0x8000 /* AIF1TX_CHAN1_DAT_INV */ +#define WM8996_AIF1TX_CHAN1_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN1_DAT_INV */ +#define WM8996_AIF1TX_CHAN1_DAT_INV_SHIFT 15 /* AIF1TX_CHAN1_DAT_INV */ +#define WM8996_AIF1TX_CHAN1_DAT_INV_WIDTH 1 /* AIF1TX_CHAN1_DAT_INV */ +#define WM8996_AIF1TX_CHAN1_SPACING_MASK 0x7E00 /* AIF1TX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN1_SPACING_SHIFT 9 /* AIF1TX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN1_SPACING_WIDTH 6 /* AIF1TX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN1_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN1_SLOTS_SHIFT 6 /* AIF1TX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN1_SLOTS_WIDTH 3 /* AIF1TX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN1_START_SLOT_MASK 0x003F /* AIF1TX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN1_START_SLOT_SHIFT 0 /* AIF1TX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN1_START_SLOT_WIDTH 6 /* AIF1TX_CHAN1_START_SLOT - [5:0] */ + +/* + * R779 (0x30B) - AIF1TX Channel 2 Configuration + */ +#define WM8996_AIF1TX_CHAN2_DAT_INV 0x8000 /* AIF1TX_CHAN2_DAT_INV */ +#define WM8996_AIF1TX_CHAN2_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN2_DAT_INV */ +#define WM8996_AIF1TX_CHAN2_DAT_INV_SHIFT 15 /* AIF1TX_CHAN2_DAT_INV */ +#define WM8996_AIF1TX_CHAN2_DAT_INV_WIDTH 1 /* AIF1TX_CHAN2_DAT_INV */ +#define WM8996_AIF1TX_CHAN2_SPACING_MASK 0x7E00 /* AIF1TX_CHAN2_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN2_SPACING_SHIFT 9 /* AIF1TX_CHAN2_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN2_SPACING_WIDTH 6 /* AIF1TX_CHAN2_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN2_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN2_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN2_SLOTS_SHIFT 6 /* AIF1TX_CHAN2_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN2_SLOTS_WIDTH 3 /* AIF1TX_CHAN2_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN2_START_SLOT_MASK 0x003F /* AIF1TX_CHAN2_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN2_START_SLOT_SHIFT 0 /* AIF1TX_CHAN2_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN2_START_SLOT_WIDTH 6 /* AIF1TX_CHAN2_START_SLOT - [5:0] */ + +/* + * R780 (0x30C) - AIF1TX Channel 3 Configuration + */ +#define WM8996_AIF1TX_CHAN3_DAT_INV 0x8000 /* AIF1TX_CHAN3_DAT_INV */ +#define WM8996_AIF1TX_CHAN3_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN3_DAT_INV */ +#define WM8996_AIF1TX_CHAN3_DAT_INV_SHIFT 15 /* AIF1TX_CHAN3_DAT_INV */ +#define WM8996_AIF1TX_CHAN3_DAT_INV_WIDTH 1 /* AIF1TX_CHAN3_DAT_INV */ +#define WM8996_AIF1TX_CHAN3_SPACING_MASK 0x7E00 /* AIF1TX_CHAN3_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN3_SPACING_SHIFT 9 /* AIF1TX_CHAN3_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN3_SPACING_WIDTH 6 /* AIF1TX_CHAN3_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN3_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN3_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN3_SLOTS_SHIFT 6 /* AIF1TX_CHAN3_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN3_SLOTS_WIDTH 3 /* AIF1TX_CHAN3_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN3_START_SLOT_MASK 0x003F /* AIF1TX_CHAN3_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN3_START_SLOT_SHIFT 0 /* AIF1TX_CHAN3_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN3_START_SLOT_WIDTH 6 /* AIF1TX_CHAN3_START_SLOT - [5:0] */ + +/* + * R781 (0x30D) - AIF1TX Channel 4 Configuration + */ +#define WM8996_AIF1TX_CHAN4_DAT_INV 0x8000 /* AIF1TX_CHAN4_DAT_INV */ +#define WM8996_AIF1TX_CHAN4_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN4_DAT_INV */ +#define WM8996_AIF1TX_CHAN4_DAT_INV_SHIFT 15 /* AIF1TX_CHAN4_DAT_INV */ +#define WM8996_AIF1TX_CHAN4_DAT_INV_WIDTH 1 /* AIF1TX_CHAN4_DAT_INV */ +#define WM8996_AIF1TX_CHAN4_SPACING_MASK 0x7E00 /* AIF1TX_CHAN4_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN4_SPACING_SHIFT 9 /* AIF1TX_CHAN4_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN4_SPACING_WIDTH 6 /* AIF1TX_CHAN4_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN4_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN4_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN4_SLOTS_SHIFT 6 /* AIF1TX_CHAN4_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN4_SLOTS_WIDTH 3 /* AIF1TX_CHAN4_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN4_START_SLOT_MASK 0x003F /* AIF1TX_CHAN4_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN4_START_SLOT_SHIFT 0 /* AIF1TX_CHAN4_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN4_START_SLOT_WIDTH 6 /* AIF1TX_CHAN4_START_SLOT - [5:0] */ + +/* + * R782 (0x30E) - AIF1TX Channel 5 Configuration + */ +#define WM8996_AIF1TX_CHAN5_DAT_INV 0x8000 /* AIF1TX_CHAN5_DAT_INV */ +#define WM8996_AIF1TX_CHAN5_DAT_INV_MASK 0x8000 /* AIF1TX_CHAN5_DAT_INV */ +#define WM8996_AIF1TX_CHAN5_DAT_INV_SHIFT 15 /* AIF1TX_CHAN5_DAT_INV */ +#define WM8996_AIF1TX_CHAN5_DAT_INV_WIDTH 1 /* AIF1TX_CHAN5_DAT_INV */ +#define WM8996_AIF1TX_CHAN5_SPACING_MASK 0x7E00 /* AIF1TX_CHAN5_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN5_SPACING_SHIFT 9 /* AIF1TX_CHAN5_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN5_SPACING_WIDTH 6 /* AIF1TX_CHAN5_SPACING - [14:9] */ +#define WM8996_AIF1TX_CHAN5_SLOTS_MASK 0x01C0 /* AIF1TX_CHAN5_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN5_SLOTS_SHIFT 6 /* AIF1TX_CHAN5_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN5_SLOTS_WIDTH 3 /* AIF1TX_CHAN5_SLOTS - [8:6] */ +#define WM8996_AIF1TX_CHAN5_START_SLOT_MASK 0x003F /* AIF1TX_CHAN5_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN5_START_SLOT_SHIFT 0 /* AIF1TX_CHAN5_START_SLOT - [5:0] */ +#define WM8996_AIF1TX_CHAN5_START_SLOT_WIDTH 6 /* AIF1TX_CHAN5_START_SLOT - [5:0] */ + +/* + * R783 (0x30F) - AIF1RX Channel 0 Configuration + */ +#define WM8996_AIF1RX_CHAN0_DAT_INV 0x8000 /* AIF1RX_CHAN0_DAT_INV */ +#define WM8996_AIF1RX_CHAN0_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN0_DAT_INV */ +#define WM8996_AIF1RX_CHAN0_DAT_INV_SHIFT 15 /* AIF1RX_CHAN0_DAT_INV */ +#define WM8996_AIF1RX_CHAN0_DAT_INV_WIDTH 1 /* AIF1RX_CHAN0_DAT_INV */ +#define WM8996_AIF1RX_CHAN0_SPACING_MASK 0x7E00 /* AIF1RX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN0_SPACING_SHIFT 9 /* AIF1RX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN0_SPACING_WIDTH 6 /* AIF1RX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN0_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN0_SLOTS_SHIFT 6 /* AIF1RX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN0_SLOTS_WIDTH 3 /* AIF1RX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN0_START_SLOT_MASK 0x003F /* AIF1RX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN0_START_SLOT_SHIFT 0 /* AIF1RX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN0_START_SLOT_WIDTH 6 /* AIF1RX_CHAN0_START_SLOT - [5:0] */ + +/* + * R784 (0x310) - AIF1RX Channel 1 Configuration + */ +#define WM8996_AIF1RX_CHAN1_DAT_INV 0x8000 /* AIF1RX_CHAN1_DAT_INV */ +#define WM8996_AIF1RX_CHAN1_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN1_DAT_INV */ +#define WM8996_AIF1RX_CHAN1_DAT_INV_SHIFT 15 /* AIF1RX_CHAN1_DAT_INV */ +#define WM8996_AIF1RX_CHAN1_DAT_INV_WIDTH 1 /* AIF1RX_CHAN1_DAT_INV */ +#define WM8996_AIF1RX_CHAN1_SPACING_MASK 0x7E00 /* AIF1RX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN1_SPACING_SHIFT 9 /* AIF1RX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN1_SPACING_WIDTH 6 /* AIF1RX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN1_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN1_SLOTS_SHIFT 6 /* AIF1RX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN1_SLOTS_WIDTH 3 /* AIF1RX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN1_START_SLOT_MASK 0x003F /* AIF1RX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN1_START_SLOT_SHIFT 0 /* AIF1RX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN1_START_SLOT_WIDTH 6 /* AIF1RX_CHAN1_START_SLOT - [5:0] */ + +/* + * R785 (0x311) - AIF1RX Channel 2 Configuration + */ +#define WM8996_AIF1RX_CHAN2_DAT_INV 0x8000 /* AIF1RX_CHAN2_DAT_INV */ +#define WM8996_AIF1RX_CHAN2_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN2_DAT_INV */ +#define WM8996_AIF1RX_CHAN2_DAT_INV_SHIFT 15 /* AIF1RX_CHAN2_DAT_INV */ +#define WM8996_AIF1RX_CHAN2_DAT_INV_WIDTH 1 /* AIF1RX_CHAN2_DAT_INV */ +#define WM8996_AIF1RX_CHAN2_SPACING_MASK 0x7E00 /* AIF1RX_CHAN2_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN2_SPACING_SHIFT 9 /* AIF1RX_CHAN2_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN2_SPACING_WIDTH 6 /* AIF1RX_CHAN2_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN2_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN2_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN2_SLOTS_SHIFT 6 /* AIF1RX_CHAN2_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN2_SLOTS_WIDTH 3 /* AIF1RX_CHAN2_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN2_START_SLOT_MASK 0x003F /* AIF1RX_CHAN2_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN2_START_SLOT_SHIFT 0 /* AIF1RX_CHAN2_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN2_START_SLOT_WIDTH 6 /* AIF1RX_CHAN2_START_SLOT - [5:0] */ + +/* + * R786 (0x312) - AIF1RX Channel 3 Configuration + */ +#define WM8996_AIF1RX_CHAN3_DAT_INV 0x8000 /* AIF1RX_CHAN3_DAT_INV */ +#define WM8996_AIF1RX_CHAN3_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN3_DAT_INV */ +#define WM8996_AIF1RX_CHAN3_DAT_INV_SHIFT 15 /* AIF1RX_CHAN3_DAT_INV */ +#define WM8996_AIF1RX_CHAN3_DAT_INV_WIDTH 1 /* AIF1RX_CHAN3_DAT_INV */ +#define WM8996_AIF1RX_CHAN3_SPACING_MASK 0x7E00 /* AIF1RX_CHAN3_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN3_SPACING_SHIFT 9 /* AIF1RX_CHAN3_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN3_SPACING_WIDTH 6 /* AIF1RX_CHAN3_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN3_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN3_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN3_SLOTS_SHIFT 6 /* AIF1RX_CHAN3_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN3_SLOTS_WIDTH 3 /* AIF1RX_CHAN3_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN3_START_SLOT_MASK 0x003F /* AIF1RX_CHAN3_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN3_START_SLOT_SHIFT 0 /* AIF1RX_CHAN3_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN3_START_SLOT_WIDTH 6 /* AIF1RX_CHAN3_START_SLOT - [5:0] */ + +/* + * R787 (0x313) - AIF1RX Channel 4 Configuration + */ +#define WM8996_AIF1RX_CHAN4_DAT_INV 0x8000 /* AIF1RX_CHAN4_DAT_INV */ +#define WM8996_AIF1RX_CHAN4_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN4_DAT_INV */ +#define WM8996_AIF1RX_CHAN4_DAT_INV_SHIFT 15 /* AIF1RX_CHAN4_DAT_INV */ +#define WM8996_AIF1RX_CHAN4_DAT_INV_WIDTH 1 /* AIF1RX_CHAN4_DAT_INV */ +#define WM8996_AIF1RX_CHAN4_SPACING_MASK 0x7E00 /* AIF1RX_CHAN4_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN4_SPACING_SHIFT 9 /* AIF1RX_CHAN4_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN4_SPACING_WIDTH 6 /* AIF1RX_CHAN4_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN4_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN4_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN4_SLOTS_SHIFT 6 /* AIF1RX_CHAN4_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN4_SLOTS_WIDTH 3 /* AIF1RX_CHAN4_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN4_START_SLOT_MASK 0x003F /* AIF1RX_CHAN4_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN4_START_SLOT_SHIFT 0 /* AIF1RX_CHAN4_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN4_START_SLOT_WIDTH 6 /* AIF1RX_CHAN4_START_SLOT - [5:0] */ + +/* + * R788 (0x314) - AIF1RX Channel 5 Configuration + */ +#define WM8996_AIF1RX_CHAN5_DAT_INV 0x8000 /* AIF1RX_CHAN5_DAT_INV */ +#define WM8996_AIF1RX_CHAN5_DAT_INV_MASK 0x8000 /* AIF1RX_CHAN5_DAT_INV */ +#define WM8996_AIF1RX_CHAN5_DAT_INV_SHIFT 15 /* AIF1RX_CHAN5_DAT_INV */ +#define WM8996_AIF1RX_CHAN5_DAT_INV_WIDTH 1 /* AIF1RX_CHAN5_DAT_INV */ +#define WM8996_AIF1RX_CHAN5_SPACING_MASK 0x7E00 /* AIF1RX_CHAN5_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN5_SPACING_SHIFT 9 /* AIF1RX_CHAN5_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN5_SPACING_WIDTH 6 /* AIF1RX_CHAN5_SPACING - [14:9] */ +#define WM8996_AIF1RX_CHAN5_SLOTS_MASK 0x01C0 /* AIF1RX_CHAN5_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN5_SLOTS_SHIFT 6 /* AIF1RX_CHAN5_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN5_SLOTS_WIDTH 3 /* AIF1RX_CHAN5_SLOTS - [8:6] */ +#define WM8996_AIF1RX_CHAN5_START_SLOT_MASK 0x003F /* AIF1RX_CHAN5_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN5_START_SLOT_SHIFT 0 /* AIF1RX_CHAN5_START_SLOT - [5:0] */ +#define WM8996_AIF1RX_CHAN5_START_SLOT_WIDTH 6 /* AIF1RX_CHAN5_START_SLOT - [5:0] */ + +/* + * R789 (0x315) - AIF1RX Mono Configuration + */ +#define WM8996_AIF1RX_CHAN4_MONO_MODE 0x0004 /* AIF1RX_CHAN4_MONO_MODE */ +#define WM8996_AIF1RX_CHAN4_MONO_MODE_MASK 0x0004 /* AIF1RX_CHAN4_MONO_MODE */ +#define WM8996_AIF1RX_CHAN4_MONO_MODE_SHIFT 2 /* AIF1RX_CHAN4_MONO_MODE */ +#define WM8996_AIF1RX_CHAN4_MONO_MODE_WIDTH 1 /* AIF1RX_CHAN4_MONO_MODE */ +#define WM8996_AIF1RX_CHAN2_MONO_MODE 0x0002 /* AIF1RX_CHAN2_MONO_MODE */ +#define WM8996_AIF1RX_CHAN2_MONO_MODE_MASK 0x0002 /* AIF1RX_CHAN2_MONO_MODE */ +#define WM8996_AIF1RX_CHAN2_MONO_MODE_SHIFT 1 /* AIF1RX_CHAN2_MONO_MODE */ +#define WM8996_AIF1RX_CHAN2_MONO_MODE_WIDTH 1 /* AIF1RX_CHAN2_MONO_MODE */ +#define WM8996_AIF1RX_CHAN0_MONO_MODE 0x0001 /* AIF1RX_CHAN0_MONO_MODE */ +#define WM8996_AIF1RX_CHAN0_MONO_MODE_MASK 0x0001 /* AIF1RX_CHAN0_MONO_MODE */ +#define WM8996_AIF1RX_CHAN0_MONO_MODE_SHIFT 0 /* AIF1RX_CHAN0_MONO_MODE */ +#define WM8996_AIF1RX_CHAN0_MONO_MODE_WIDTH 1 /* AIF1RX_CHAN0_MONO_MODE */ + +/* + * R794 (0x31A) - AIF1TX Test + */ +#define WM8996_AIF1TX45_DITHER_ENA 0x0004 /* AIF1TX45_DITHER_ENA */ +#define WM8996_AIF1TX45_DITHER_ENA_MASK 0x0004 /* AIF1TX45_DITHER_ENA */ +#define WM8996_AIF1TX45_DITHER_ENA_SHIFT 2 /* AIF1TX45_DITHER_ENA */ +#define WM8996_AIF1TX45_DITHER_ENA_WIDTH 1 /* AIF1TX45_DITHER_ENA */ +#define WM8996_AIF1TX23_DITHER_ENA 0x0002 /* AIF1TX23_DITHER_ENA */ +#define WM8996_AIF1TX23_DITHER_ENA_MASK 0x0002 /* AIF1TX23_DITHER_ENA */ +#define WM8996_AIF1TX23_DITHER_ENA_SHIFT 1 /* AIF1TX23_DITHER_ENA */ +#define WM8996_AIF1TX23_DITHER_ENA_WIDTH 1 /* AIF1TX23_DITHER_ENA */ +#define WM8996_AIF1TX01_DITHER_ENA 0x0001 /* AIF1TX01_DITHER_ENA */ +#define WM8996_AIF1TX01_DITHER_ENA_MASK 0x0001 /* AIF1TX01_DITHER_ENA */ +#define WM8996_AIF1TX01_DITHER_ENA_SHIFT 0 /* AIF1TX01_DITHER_ENA */ +#define WM8996_AIF1TX01_DITHER_ENA_WIDTH 1 /* AIF1TX01_DITHER_ENA */ + +/* + * R800 (0x320) - AIF2 Control + */ +#define WM8996_AIF2_TRI 0x0004 /* AIF2_TRI */ +#define WM8996_AIF2_TRI_MASK 0x0004 /* AIF2_TRI */ +#define WM8996_AIF2_TRI_SHIFT 2 /* AIF2_TRI */ +#define WM8996_AIF2_TRI_WIDTH 1 /* AIF2_TRI */ +#define WM8996_AIF2_FMT_MASK 0x0003 /* AIF2_FMT - [1:0] */ +#define WM8996_AIF2_FMT_SHIFT 0 /* AIF2_FMT - [1:0] */ +#define WM8996_AIF2_FMT_WIDTH 2 /* AIF2_FMT - [1:0] */ + +/* + * R801 (0x321) - AIF2 BCLK + */ +#define WM8996_AIF2_BCLK_INV 0x0400 /* AIF2_BCLK_INV */ +#define WM8996_AIF2_BCLK_INV_MASK 0x0400 /* AIF2_BCLK_INV */ +#define WM8996_AIF2_BCLK_INV_SHIFT 10 /* AIF2_BCLK_INV */ +#define WM8996_AIF2_BCLK_INV_WIDTH 1 /* AIF2_BCLK_INV */ +#define WM8996_AIF2_BCLK_FRC 0x0200 /* AIF2_BCLK_FRC */ +#define WM8996_AIF2_BCLK_FRC_MASK 0x0200 /* AIF2_BCLK_FRC */ +#define WM8996_AIF2_BCLK_FRC_SHIFT 9 /* AIF2_BCLK_FRC */ +#define WM8996_AIF2_BCLK_FRC_WIDTH 1 /* AIF2_BCLK_FRC */ +#define WM8996_AIF2_BCLK_MSTR 0x0100 /* AIF2_BCLK_MSTR */ +#define WM8996_AIF2_BCLK_MSTR_MASK 0x0100 /* AIF2_BCLK_MSTR */ +#define WM8996_AIF2_BCLK_MSTR_SHIFT 8 /* AIF2_BCLK_MSTR */ +#define WM8996_AIF2_BCLK_MSTR_WIDTH 1 /* AIF2_BCLK_MSTR */ +#define WM8996_AIF2_BCLK_DIV_MASK 0x000F /* AIF2_BCLK_DIV - [3:0] */ +#define WM8996_AIF2_BCLK_DIV_SHIFT 0 /* AIF2_BCLK_DIV - [3:0] */ +#define WM8996_AIF2_BCLK_DIV_WIDTH 4 /* AIF2_BCLK_DIV - [3:0] */ + +/* + * R802 (0x322) - AIF2 TX LRCLK(1) + */ +#define WM8996_AIF2TX_RATE_MASK 0x07FF /* AIF2TX_RATE - [10:0] */ +#define WM8996_AIF2TX_RATE_SHIFT 0 /* AIF2TX_RATE - [10:0] */ +#define WM8996_AIF2TX_RATE_WIDTH 11 /* AIF2TX_RATE - [10:0] */ + +/* + * R803 (0x323) - AIF2 TX LRCLK(2) + */ +#define WM8996_AIF2TX_LRCLK_MODE 0x0008 /* AIF2TX_LRCLK_MODE */ +#define WM8996_AIF2TX_LRCLK_MODE_MASK 0x0008 /* AIF2TX_LRCLK_MODE */ +#define WM8996_AIF2TX_LRCLK_MODE_SHIFT 3 /* AIF2TX_LRCLK_MODE */ +#define WM8996_AIF2TX_LRCLK_MODE_WIDTH 1 /* AIF2TX_LRCLK_MODE */ +#define WM8996_AIF2TX_LRCLK_INV 0x0004 /* AIF2TX_LRCLK_INV */ +#define WM8996_AIF2TX_LRCLK_INV_MASK 0x0004 /* AIF2TX_LRCLK_INV */ +#define WM8996_AIF2TX_LRCLK_INV_SHIFT 2 /* AIF2TX_LRCLK_INV */ +#define WM8996_AIF2TX_LRCLK_INV_WIDTH 1 /* AIF2TX_LRCLK_INV */ +#define WM8996_AIF2TX_LRCLK_FRC 0x0002 /* AIF2TX_LRCLK_FRC */ +#define WM8996_AIF2TX_LRCLK_FRC_MASK 0x0002 /* AIF2TX_LRCLK_FRC */ +#define WM8996_AIF2TX_LRCLK_FRC_SHIFT 1 /* AIF2TX_LRCLK_FRC */ +#define WM8996_AIF2TX_LRCLK_FRC_WIDTH 1 /* AIF2TX_LRCLK_FRC */ +#define WM8996_AIF2TX_LRCLK_MSTR 0x0001 /* AIF2TX_LRCLK_MSTR */ +#define WM8996_AIF2TX_LRCLK_MSTR_MASK 0x0001 /* AIF2TX_LRCLK_MSTR */ +#define WM8996_AIF2TX_LRCLK_MSTR_SHIFT 0 /* AIF2TX_LRCLK_MSTR */ +#define WM8996_AIF2TX_LRCLK_MSTR_WIDTH 1 /* AIF2TX_LRCLK_MSTR */ + +/* + * R804 (0x324) - AIF2 RX LRCLK(1) + */ +#define WM8996_AIF2RX_RATE_MASK 0x07FF /* AIF2RX_RATE - [10:0] */ +#define WM8996_AIF2RX_RATE_SHIFT 0 /* AIF2RX_RATE - [10:0] */ +#define WM8996_AIF2RX_RATE_WIDTH 11 /* AIF2RX_RATE - [10:0] */ + +/* + * R805 (0x325) - AIF2 RX LRCLK(2) + */ +#define WM8996_AIF2RX_LRCLK_INV 0x0004 /* AIF2RX_LRCLK_INV */ +#define WM8996_AIF2RX_LRCLK_INV_MASK 0x0004 /* AIF2RX_LRCLK_INV */ +#define WM8996_AIF2RX_LRCLK_INV_SHIFT 2 /* AIF2RX_LRCLK_INV */ +#define WM8996_AIF2RX_LRCLK_INV_WIDTH 1 /* AIF2RX_LRCLK_INV */ +#define WM8996_AIF2RX_LRCLK_FRC 0x0002 /* AIF2RX_LRCLK_FRC */ +#define WM8996_AIF2RX_LRCLK_FRC_MASK 0x0002 /* AIF2RX_LRCLK_FRC */ +#define WM8996_AIF2RX_LRCLK_FRC_SHIFT 1 /* AIF2RX_LRCLK_FRC */ +#define WM8996_AIF2RX_LRCLK_FRC_WIDTH 1 /* AIF2RX_LRCLK_FRC */ +#define WM8996_AIF2RX_LRCLK_MSTR 0x0001 /* AIF2RX_LRCLK_MSTR */ +#define WM8996_AIF2RX_LRCLK_MSTR_MASK 0x0001 /* AIF2RX_LRCLK_MSTR */ +#define WM8996_AIF2RX_LRCLK_MSTR_SHIFT 0 /* AIF2RX_LRCLK_MSTR */ +#define WM8996_AIF2RX_LRCLK_MSTR_WIDTH 1 /* AIF2RX_LRCLK_MSTR */ + +/* + * R806 (0x326) - AIF2TX Data Configuration (1) + */ +#define WM8996_AIF2TX_WL_MASK 0xFF00 /* AIF2TX_WL - [15:8] */ +#define WM8996_AIF2TX_WL_SHIFT 8 /* AIF2TX_WL - [15:8] */ +#define WM8996_AIF2TX_WL_WIDTH 8 /* AIF2TX_WL - [15:8] */ +#define WM8996_AIF2TX_SLOT_LEN_MASK 0x00FF /* AIF2TX_SLOT_LEN - [7:0] */ +#define WM8996_AIF2TX_SLOT_LEN_SHIFT 0 /* AIF2TX_SLOT_LEN - [7:0] */ +#define WM8996_AIF2TX_SLOT_LEN_WIDTH 8 /* AIF2TX_SLOT_LEN - [7:0] */ + +/* + * R807 (0x327) - AIF2TX Data Configuration (2) + */ +#define WM8996_AIF2TX_DAT_TRI 0x0001 /* AIF2TX_DAT_TRI */ +#define WM8996_AIF2TX_DAT_TRI_MASK 0x0001 /* AIF2TX_DAT_TRI */ +#define WM8996_AIF2TX_DAT_TRI_SHIFT 0 /* AIF2TX_DAT_TRI */ +#define WM8996_AIF2TX_DAT_TRI_WIDTH 1 /* AIF2TX_DAT_TRI */ + +/* + * R808 (0x328) - AIF2RX Data Configuration + */ +#define WM8996_AIF2RX_WL_MASK 0xFF00 /* AIF2RX_WL - [15:8] */ +#define WM8996_AIF2RX_WL_SHIFT 8 /* AIF2RX_WL - [15:8] */ +#define WM8996_AIF2RX_WL_WIDTH 8 /* AIF2RX_WL - [15:8] */ +#define WM8996_AIF2RX_SLOT_LEN_MASK 0x00FF /* AIF2RX_SLOT_LEN - [7:0] */ +#define WM8996_AIF2RX_SLOT_LEN_SHIFT 0 /* AIF2RX_SLOT_LEN - [7:0] */ +#define WM8996_AIF2RX_SLOT_LEN_WIDTH 8 /* AIF2RX_SLOT_LEN - [7:0] */ + +/* + * R809 (0x329) - AIF2TX Channel 0 Configuration + */ +#define WM8996_AIF2TX_CHAN0_DAT_INV 0x8000 /* AIF2TX_CHAN0_DAT_INV */ +#define WM8996_AIF2TX_CHAN0_DAT_INV_MASK 0x8000 /* AIF2TX_CHAN0_DAT_INV */ +#define WM8996_AIF2TX_CHAN0_DAT_INV_SHIFT 15 /* AIF2TX_CHAN0_DAT_INV */ +#define WM8996_AIF2TX_CHAN0_DAT_INV_WIDTH 1 /* AIF2TX_CHAN0_DAT_INV */ +#define WM8996_AIF2TX_CHAN0_SPACING_MASK 0x7E00 /* AIF2TX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF2TX_CHAN0_SPACING_SHIFT 9 /* AIF2TX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF2TX_CHAN0_SPACING_WIDTH 6 /* AIF2TX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF2TX_CHAN0_SLOTS_MASK 0x01C0 /* AIF2TX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF2TX_CHAN0_SLOTS_SHIFT 6 /* AIF2TX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF2TX_CHAN0_SLOTS_WIDTH 3 /* AIF2TX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF2TX_CHAN0_START_SLOT_MASK 0x003F /* AIF2TX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF2TX_CHAN0_START_SLOT_SHIFT 0 /* AIF2TX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF2TX_CHAN0_START_SLOT_WIDTH 6 /* AIF2TX_CHAN0_START_SLOT - [5:0] */ + +/* + * R810 (0x32A) - AIF2TX Channel 1 Configuration + */ +#define WM8996_AIF2TX_CHAN1_DAT_INV 0x8000 /* AIF2TX_CHAN1_DAT_INV */ +#define WM8996_AIF2TX_CHAN1_DAT_INV_MASK 0x8000 /* AIF2TX_CHAN1_DAT_INV */ +#define WM8996_AIF2TX_CHAN1_DAT_INV_SHIFT 15 /* AIF2TX_CHAN1_DAT_INV */ +#define WM8996_AIF2TX_CHAN1_DAT_INV_WIDTH 1 /* AIF2TX_CHAN1_DAT_INV */ +#define WM8996_AIF2TX_CHAN1_SPACING_MASK 0x7E00 /* AIF2TX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF2TX_CHAN1_SPACING_SHIFT 9 /* AIF2TX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF2TX_CHAN1_SPACING_WIDTH 6 /* AIF2TX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF2TX_CHAN1_SLOTS_MASK 0x01C0 /* AIF2TX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF2TX_CHAN1_SLOTS_SHIFT 6 /* AIF2TX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF2TX_CHAN1_SLOTS_WIDTH 3 /* AIF2TX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF2TX_CHAN1_START_SLOT_MASK 0x003F /* AIF2TX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF2TX_CHAN1_START_SLOT_SHIFT 0 /* AIF2TX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF2TX_CHAN1_START_SLOT_WIDTH 6 /* AIF2TX_CHAN1_START_SLOT - [5:0] */ + +/* + * R811 (0x32B) - AIF2RX Channel 0 Configuration + */ +#define WM8996_AIF2RX_CHAN0_DAT_INV 0x8000 /* AIF2RX_CHAN0_DAT_INV */ +#define WM8996_AIF2RX_CHAN0_DAT_INV_MASK 0x8000 /* AIF2RX_CHAN0_DAT_INV */ +#define WM8996_AIF2RX_CHAN0_DAT_INV_SHIFT 15 /* AIF2RX_CHAN0_DAT_INV */ +#define WM8996_AIF2RX_CHAN0_DAT_INV_WIDTH 1 /* AIF2RX_CHAN0_DAT_INV */ +#define WM8996_AIF2RX_CHAN0_SPACING_MASK 0x7E00 /* AIF2RX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF2RX_CHAN0_SPACING_SHIFT 9 /* AIF2RX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF2RX_CHAN0_SPACING_WIDTH 6 /* AIF2RX_CHAN0_SPACING - [14:9] */ +#define WM8996_AIF2RX_CHAN0_SLOTS_MASK 0x01C0 /* AIF2RX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF2RX_CHAN0_SLOTS_SHIFT 6 /* AIF2RX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF2RX_CHAN0_SLOTS_WIDTH 3 /* AIF2RX_CHAN0_SLOTS - [8:6] */ +#define WM8996_AIF2RX_CHAN0_START_SLOT_MASK 0x003F /* AIF2RX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF2RX_CHAN0_START_SLOT_SHIFT 0 /* AIF2RX_CHAN0_START_SLOT - [5:0] */ +#define WM8996_AIF2RX_CHAN0_START_SLOT_WIDTH 6 /* AIF2RX_CHAN0_START_SLOT - [5:0] */ + +/* + * R812 (0x32C) - AIF2RX Channel 1 Configuration + */ +#define WM8996_AIF2RX_CHAN1_DAT_INV 0x8000 /* AIF2RX_CHAN1_DAT_INV */ +#define WM8996_AIF2RX_CHAN1_DAT_INV_MASK 0x8000 /* AIF2RX_CHAN1_DAT_INV */ +#define WM8996_AIF2RX_CHAN1_DAT_INV_SHIFT 15 /* AIF2RX_CHAN1_DAT_INV */ +#define WM8996_AIF2RX_CHAN1_DAT_INV_WIDTH 1 /* AIF2RX_CHAN1_DAT_INV */ +#define WM8996_AIF2RX_CHAN1_SPACING_MASK 0x7E00 /* AIF2RX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF2RX_CHAN1_SPACING_SHIFT 9 /* AIF2RX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF2RX_CHAN1_SPACING_WIDTH 6 /* AIF2RX_CHAN1_SPACING - [14:9] */ +#define WM8996_AIF2RX_CHAN1_SLOTS_MASK 0x01C0 /* AIF2RX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF2RX_CHAN1_SLOTS_SHIFT 6 /* AIF2RX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF2RX_CHAN1_SLOTS_WIDTH 3 /* AIF2RX_CHAN1_SLOTS - [8:6] */ +#define WM8996_AIF2RX_CHAN1_START_SLOT_MASK 0x003F /* AIF2RX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF2RX_CHAN1_START_SLOT_SHIFT 0 /* AIF2RX_CHAN1_START_SLOT - [5:0] */ +#define WM8996_AIF2RX_CHAN1_START_SLOT_WIDTH 6 /* AIF2RX_CHAN1_START_SLOT - [5:0] */ + +/* + * R813 (0x32D) - AIF2RX Mono Configuration + */ +#define WM8996_AIF2RX_CHAN0_MONO_MODE 0x0001 /* AIF2RX_CHAN0_MONO_MODE */ +#define WM8996_AIF2RX_CHAN0_MONO_MODE_MASK 0x0001 /* AIF2RX_CHAN0_MONO_MODE */ +#define WM8996_AIF2RX_CHAN0_MONO_MODE_SHIFT 0 /* AIF2RX_CHAN0_MONO_MODE */ +#define WM8996_AIF2RX_CHAN0_MONO_MODE_WIDTH 1 /* AIF2RX_CHAN0_MONO_MODE */ + +/* + * R815 (0x32F) - AIF2TX Test + */ +#define WM8996_AIF2TX_DITHER_ENA 0x0001 /* AIF2TX_DITHER_ENA */ +#define WM8996_AIF2TX_DITHER_ENA_MASK 0x0001 /* AIF2TX_DITHER_ENA */ +#define WM8996_AIF2TX_DITHER_ENA_SHIFT 0 /* AIF2TX_DITHER_ENA */ +#define WM8996_AIF2TX_DITHER_ENA_WIDTH 1 /* AIF2TX_DITHER_ENA */ + +/* + * R1024 (0x400) - DSP1 TX Left Volume + */ +#define WM8996_DSP1TX_VU 0x0100 /* DSP1TX_VU */ +#define WM8996_DSP1TX_VU_MASK 0x0100 /* DSP1TX_VU */ +#define WM8996_DSP1TX_VU_SHIFT 8 /* DSP1TX_VU */ +#define WM8996_DSP1TX_VU_WIDTH 1 /* DSP1TX_VU */ +#define WM8996_DSP1TXL_VOL_MASK 0x00FF /* DSP1TXL_VOL - [7:0] */ +#define WM8996_DSP1TXL_VOL_SHIFT 0 /* DSP1TXL_VOL - [7:0] */ +#define WM8996_DSP1TXL_VOL_WIDTH 8 /* DSP1TXL_VOL - [7:0] */ + +/* + * R1025 (0x401) - DSP1 TX Right Volume + */ +#define WM8996_DSP1TX_VU 0x0100 /* DSP1TX_VU */ +#define WM8996_DSP1TX_VU_MASK 0x0100 /* DSP1TX_VU */ +#define WM8996_DSP1TX_VU_SHIFT 8 /* DSP1TX_VU */ +#define WM8996_DSP1TX_VU_WIDTH 1 /* DSP1TX_VU */ +#define WM8996_DSP1TXR_VOL_MASK 0x00FF /* DSP1TXR_VOL - [7:0] */ +#define WM8996_DSP1TXR_VOL_SHIFT 0 /* DSP1TXR_VOL - [7:0] */ +#define WM8996_DSP1TXR_VOL_WIDTH 8 /* DSP1TXR_VOL - [7:0] */ + +/* + * R1026 (0x402) - DSP1 RX Left Volume + */ +#define WM8996_DSP1RX_VU 0x0100 /* DSP1RX_VU */ +#define WM8996_DSP1RX_VU_MASK 0x0100 /* DSP1RX_VU */ +#define WM8996_DSP1RX_VU_SHIFT 8 /* DSP1RX_VU */ +#define WM8996_DSP1RX_VU_WIDTH 1 /* DSP1RX_VU */ +#define WM8996_DSP1RXL_VOL_MASK 0x00FF /* DSP1RXL_VOL - [7:0] */ +#define WM8996_DSP1RXL_VOL_SHIFT 0 /* DSP1RXL_VOL - [7:0] */ +#define WM8996_DSP1RXL_VOL_WIDTH 8 /* DSP1RXL_VOL - [7:0] */ + +/* + * R1027 (0x403) - DSP1 RX Right Volume + */ +#define WM8996_DSP1RX_VU 0x0100 /* DSP1RX_VU */ +#define WM8996_DSP1RX_VU_MASK 0x0100 /* DSP1RX_VU */ +#define WM8996_DSP1RX_VU_SHIFT 8 /* DSP1RX_VU */ +#define WM8996_DSP1RX_VU_WIDTH 1 /* DSP1RX_VU */ +#define WM8996_DSP1RXR_VOL_MASK 0x00FF /* DSP1RXR_VOL - [7:0] */ +#define WM8996_DSP1RXR_VOL_SHIFT 0 /* DSP1RXR_VOL - [7:0] */ +#define WM8996_DSP1RXR_VOL_WIDTH 8 /* DSP1RXR_VOL - [7:0] */ + +/* + * R1040 (0x410) - DSP1 TX Filters + */ +#define WM8996_DSP1TX_NF 0x2000 /* DSP1TX_NF */ +#define WM8996_DSP1TX_NF_MASK 0x2000 /* DSP1TX_NF */ +#define WM8996_DSP1TX_NF_SHIFT 13 /* DSP1TX_NF */ +#define WM8996_DSP1TX_NF_WIDTH 1 /* DSP1TX_NF */ +#define WM8996_DSP1TXL_HPF 0x1000 /* DSP1TXL_HPF */ +#define WM8996_DSP1TXL_HPF_MASK 0x1000 /* DSP1TXL_HPF */ +#define WM8996_DSP1TXL_HPF_SHIFT 12 /* DSP1TXL_HPF */ +#define WM8996_DSP1TXL_HPF_WIDTH 1 /* DSP1TXL_HPF */ +#define WM8996_DSP1TXR_HPF 0x0800 /* DSP1TXR_HPF */ +#define WM8996_DSP1TXR_HPF_MASK 0x0800 /* DSP1TXR_HPF */ +#define WM8996_DSP1TXR_HPF_SHIFT 11 /* DSP1TXR_HPF */ +#define WM8996_DSP1TXR_HPF_WIDTH 1 /* DSP1TXR_HPF */ +#define WM8996_DSP1TX_HPF_MODE_MASK 0x0018 /* DSP1TX_HPF_MODE - [4:3] */ +#define WM8996_DSP1TX_HPF_MODE_SHIFT 3 /* DSP1TX_HPF_MODE - [4:3] */ +#define WM8996_DSP1TX_HPF_MODE_WIDTH 2 /* DSP1TX_HPF_MODE - [4:3] */ +#define WM8996_DSP1TX_HPF_CUT_MASK 0x0007 /* DSP1TX_HPF_CUT - [2:0] */ +#define WM8996_DSP1TX_HPF_CUT_SHIFT 0 /* DSP1TX_HPF_CUT - [2:0] */ +#define WM8996_DSP1TX_HPF_CUT_WIDTH 3 /* DSP1TX_HPF_CUT - [2:0] */ + +/* + * R1056 (0x420) - DSP1 RX Filters (1) + */ +#define WM8996_DSP1RX_MUTE 0x0200 /* DSP1RX_MUTE */ +#define WM8996_DSP1RX_MUTE_MASK 0x0200 /* DSP1RX_MUTE */ +#define WM8996_DSP1RX_MUTE_SHIFT 9 /* DSP1RX_MUTE */ +#define WM8996_DSP1RX_MUTE_WIDTH 1 /* DSP1RX_MUTE */ +#define WM8996_DSP1RX_MONO 0x0080 /* DSP1RX_MONO */ +#define WM8996_DSP1RX_MONO_MASK 0x0080 /* DSP1RX_MONO */ +#define WM8996_DSP1RX_MONO_SHIFT 7 /* DSP1RX_MONO */ +#define WM8996_DSP1RX_MONO_WIDTH 1 /* DSP1RX_MONO */ +#define WM8996_DSP1RX_MUTERATE 0x0020 /* DSP1RX_MUTERATE */ +#define WM8996_DSP1RX_MUTERATE_MASK 0x0020 /* DSP1RX_MUTERATE */ +#define WM8996_DSP1RX_MUTERATE_SHIFT 5 /* DSP1RX_MUTERATE */ +#define WM8996_DSP1RX_MUTERATE_WIDTH 1 /* DSP1RX_MUTERATE */ +#define WM8996_DSP1RX_UNMUTE_RAMP 0x0010 /* DSP1RX_UNMUTE_RAMP */ +#define WM8996_DSP1RX_UNMUTE_RAMP_MASK 0x0010 /* DSP1RX_UNMUTE_RAMP */ +#define WM8996_DSP1RX_UNMUTE_RAMP_SHIFT 4 /* DSP1RX_UNMUTE_RAMP */ +#define WM8996_DSP1RX_UNMUTE_RAMP_WIDTH 1 /* DSP1RX_UNMUTE_RAMP */ + +/* + * R1057 (0x421) - DSP1 RX Filters (2) + */ +#define WM8996_DSP1RX_3D_GAIN_MASK 0x3E00 /* DSP1RX_3D_GAIN - [13:9] */ +#define WM8996_DSP1RX_3D_GAIN_SHIFT 9 /* DSP1RX_3D_GAIN - [13:9] */ +#define WM8996_DSP1RX_3D_GAIN_WIDTH 5 /* DSP1RX_3D_GAIN - [13:9] */ +#define WM8996_DSP1RX_3D_ENA 0x0100 /* DSP1RX_3D_ENA */ +#define WM8996_DSP1RX_3D_ENA_MASK 0x0100 /* DSP1RX_3D_ENA */ +#define WM8996_DSP1RX_3D_ENA_SHIFT 8 /* DSP1RX_3D_ENA */ +#define WM8996_DSP1RX_3D_ENA_WIDTH 1 /* DSP1RX_3D_ENA */ + +/* + * R1088 (0x440) - DSP1 DRC (1) + */ +#define WM8996_DSP1DRC_SIG_DET_RMS_MASK 0xF800 /* DSP1DRC_SIG_DET_RMS - [15:11] */ +#define WM8996_DSP1DRC_SIG_DET_RMS_SHIFT 11 /* DSP1DRC_SIG_DET_RMS - [15:11] */ +#define WM8996_DSP1DRC_SIG_DET_RMS_WIDTH 5 /* DSP1DRC_SIG_DET_RMS - [15:11] */ +#define WM8996_DSP1DRC_SIG_DET_PK_MASK 0x0600 /* DSP1DRC_SIG_DET_PK - [10:9] */ +#define WM8996_DSP1DRC_SIG_DET_PK_SHIFT 9 /* DSP1DRC_SIG_DET_PK - [10:9] */ +#define WM8996_DSP1DRC_SIG_DET_PK_WIDTH 2 /* DSP1DRC_SIG_DET_PK - [10:9] */ +#define WM8996_DSP1DRC_NG_ENA 0x0100 /* DSP1DRC_NG_ENA */ +#define WM8996_DSP1DRC_NG_ENA_MASK 0x0100 /* DSP1DRC_NG_ENA */ +#define WM8996_DSP1DRC_NG_ENA_SHIFT 8 /* DSP1DRC_NG_ENA */ +#define WM8996_DSP1DRC_NG_ENA_WIDTH 1 /* DSP1DRC_NG_ENA */ +#define WM8996_DSP1DRC_SIG_DET_MODE 0x0080 /* DSP1DRC_SIG_DET_MODE */ +#define WM8996_DSP1DRC_SIG_DET_MODE_MASK 0x0080 /* DSP1DRC_SIG_DET_MODE */ +#define WM8996_DSP1DRC_SIG_DET_MODE_SHIFT 7 /* DSP1DRC_SIG_DET_MODE */ +#define WM8996_DSP1DRC_SIG_DET_MODE_WIDTH 1 /* DSP1DRC_SIG_DET_MODE */ +#define WM8996_DSP1DRC_SIG_DET 0x0040 /* DSP1DRC_SIG_DET */ +#define WM8996_DSP1DRC_SIG_DET_MASK 0x0040 /* DSP1DRC_SIG_DET */ +#define WM8996_DSP1DRC_SIG_DET_SHIFT 6 /* DSP1DRC_SIG_DET */ +#define WM8996_DSP1DRC_SIG_DET_WIDTH 1 /* DSP1DRC_SIG_DET */ +#define WM8996_DSP1DRC_KNEE2_OP_ENA 0x0020 /* DSP1DRC_KNEE2_OP_ENA */ +#define WM8996_DSP1DRC_KNEE2_OP_ENA_MASK 0x0020 /* DSP1DRC_KNEE2_OP_ENA */ +#define WM8996_DSP1DRC_KNEE2_OP_ENA_SHIFT 5 /* DSP1DRC_KNEE2_OP_ENA */ +#define WM8996_DSP1DRC_KNEE2_OP_ENA_WIDTH 1 /* DSP1DRC_KNEE2_OP_ENA */ +#define WM8996_DSP1DRC_QR 0x0010 /* DSP1DRC_QR */ +#define WM8996_DSP1DRC_QR_MASK 0x0010 /* DSP1DRC_QR */ +#define WM8996_DSP1DRC_QR_SHIFT 4 /* DSP1DRC_QR */ +#define WM8996_DSP1DRC_QR_WIDTH 1 /* DSP1DRC_QR */ +#define WM8996_DSP1DRC_ANTICLIP 0x0008 /* DSP1DRC_ANTICLIP */ +#define WM8996_DSP1DRC_ANTICLIP_MASK 0x0008 /* DSP1DRC_ANTICLIP */ +#define WM8996_DSP1DRC_ANTICLIP_SHIFT 3 /* DSP1DRC_ANTICLIP */ +#define WM8996_DSP1DRC_ANTICLIP_WIDTH 1 /* DSP1DRC_ANTICLIP */ +#define WM8996_DSP1RX_DRC_ENA 0x0004 /* DSP1RX_DRC_ENA */ +#define WM8996_DSP1RX_DRC_ENA_MASK 0x0004 /* DSP1RX_DRC_ENA */ +#define WM8996_DSP1RX_DRC_ENA_SHIFT 2 /* DSP1RX_DRC_ENA */ +#define WM8996_DSP1RX_DRC_ENA_WIDTH 1 /* DSP1RX_DRC_ENA */ +#define WM8996_DSP1TXL_DRC_ENA 0x0002 /* DSP1TXL_DRC_ENA */ +#define WM8996_DSP1TXL_DRC_ENA_MASK 0x0002 /* DSP1TXL_DRC_ENA */ +#define WM8996_DSP1TXL_DRC_ENA_SHIFT 1 /* DSP1TXL_DRC_ENA */ +#define WM8996_DSP1TXL_DRC_ENA_WIDTH 1 /* DSP1TXL_DRC_ENA */ +#define WM8996_DSP1TXR_DRC_ENA 0x0001 /* DSP1TXR_DRC_ENA */ +#define WM8996_DSP1TXR_DRC_ENA_MASK 0x0001 /* DSP1TXR_DRC_ENA */ +#define WM8996_DSP1TXR_DRC_ENA_SHIFT 0 /* DSP1TXR_DRC_ENA */ +#define WM8996_DSP1TXR_DRC_ENA_WIDTH 1 /* DSP1TXR_DRC_ENA */ + +/* + * R1089 (0x441) - DSP1 DRC (2) + */ +#define WM8996_DSP1DRC_ATK_MASK 0x1E00 /* DSP1DRC_ATK - [12:9] */ +#define WM8996_DSP1DRC_ATK_SHIFT 9 /* DSP1DRC_ATK - [12:9] */ +#define WM8996_DSP1DRC_ATK_WIDTH 4 /* DSP1DRC_ATK - [12:9] */ +#define WM8996_DSP1DRC_DCY_MASK 0x01E0 /* DSP1DRC_DCY - [8:5] */ +#define WM8996_DSP1DRC_DCY_SHIFT 5 /* DSP1DRC_DCY - [8:5] */ +#define WM8996_DSP1DRC_DCY_WIDTH 4 /* DSP1DRC_DCY - [8:5] */ +#define WM8996_DSP1DRC_MINGAIN_MASK 0x001C /* DSP1DRC_MINGAIN - [4:2] */ +#define WM8996_DSP1DRC_MINGAIN_SHIFT 2 /* DSP1DRC_MINGAIN - [4:2] */ +#define WM8996_DSP1DRC_MINGAIN_WIDTH 3 /* DSP1DRC_MINGAIN - [4:2] */ +#define WM8996_DSP1DRC_MAXGAIN_MASK 0x0003 /* DSP1DRC_MAXGAIN - [1:0] */ +#define WM8996_DSP1DRC_MAXGAIN_SHIFT 0 /* DSP1DRC_MAXGAIN - [1:0] */ +#define WM8996_DSP1DRC_MAXGAIN_WIDTH 2 /* DSP1DRC_MAXGAIN - [1:0] */ + +/* + * R1090 (0x442) - DSP1 DRC (3) + */ +#define WM8996_DSP1DRC_NG_MINGAIN_MASK 0xF000 /* DSP1DRC_NG_MINGAIN - [15:12] */ +#define WM8996_DSP1DRC_NG_MINGAIN_SHIFT 12 /* DSP1DRC_NG_MINGAIN - [15:12] */ +#define WM8996_DSP1DRC_NG_MINGAIN_WIDTH 4 /* DSP1DRC_NG_MINGAIN - [15:12] */ +#define WM8996_DSP1DRC_NG_EXP_MASK 0x0C00 /* DSP1DRC_NG_EXP - [11:10] */ +#define WM8996_DSP1DRC_NG_EXP_SHIFT 10 /* DSP1DRC_NG_EXP - [11:10] */ +#define WM8996_DSP1DRC_NG_EXP_WIDTH 2 /* DSP1DRC_NG_EXP - [11:10] */ +#define WM8996_DSP1DRC_QR_THR_MASK 0x0300 /* DSP1DRC_QR_THR - [9:8] */ +#define WM8996_DSP1DRC_QR_THR_SHIFT 8 /* DSP1DRC_QR_THR - [9:8] */ +#define WM8996_DSP1DRC_QR_THR_WIDTH 2 /* DSP1DRC_QR_THR - [9:8] */ +#define WM8996_DSP1DRC_QR_DCY_MASK 0x00C0 /* DSP1DRC_QR_DCY - [7:6] */ +#define WM8996_DSP1DRC_QR_DCY_SHIFT 6 /* DSP1DRC_QR_DCY - [7:6] */ +#define WM8996_DSP1DRC_QR_DCY_WIDTH 2 /* DSP1DRC_QR_DCY - [7:6] */ +#define WM8996_DSP1DRC_HI_COMP_MASK 0x0038 /* DSP1DRC_HI_COMP - [5:3] */ +#define WM8996_DSP1DRC_HI_COMP_SHIFT 3 /* DSP1DRC_HI_COMP - [5:3] */ +#define WM8996_DSP1DRC_HI_COMP_WIDTH 3 /* DSP1DRC_HI_COMP - [5:3] */ +#define WM8996_DSP1DRC_LO_COMP_MASK 0x0007 /* DSP1DRC_LO_COMP - [2:0] */ +#define WM8996_DSP1DRC_LO_COMP_SHIFT 0 /* DSP1DRC_LO_COMP - [2:0] */ +#define WM8996_DSP1DRC_LO_COMP_WIDTH 3 /* DSP1DRC_LO_COMP - [2:0] */ + +/* + * R1091 (0x443) - DSP1 DRC (4) + */ +#define WM8996_DSP1DRC_KNEE_IP_MASK 0x07E0 /* DSP1DRC_KNEE_IP - [10:5] */ +#define WM8996_DSP1DRC_KNEE_IP_SHIFT 5 /* DSP1DRC_KNEE_IP - [10:5] */ +#define WM8996_DSP1DRC_KNEE_IP_WIDTH 6 /* DSP1DRC_KNEE_IP - [10:5] */ +#define WM8996_DSP1DRC_KNEE_OP_MASK 0x001F /* DSP1DRC_KNEE_OP - [4:0] */ +#define WM8996_DSP1DRC_KNEE_OP_SHIFT 0 /* DSP1DRC_KNEE_OP - [4:0] */ +#define WM8996_DSP1DRC_KNEE_OP_WIDTH 5 /* DSP1DRC_KNEE_OP - [4:0] */ + +/* + * R1092 (0x444) - DSP1 DRC (5) + */ +#define WM8996_DSP1DRC_KNEE2_IP_MASK 0x03E0 /* DSP1DRC_KNEE2_IP - [9:5] */ +#define WM8996_DSP1DRC_KNEE2_IP_SHIFT 5 /* DSP1DRC_KNEE2_IP - [9:5] */ +#define WM8996_DSP1DRC_KNEE2_IP_WIDTH 5 /* DSP1DRC_KNEE2_IP - [9:5] */ +#define WM8996_DSP1DRC_KNEE2_OP_MASK 0x001F /* DSP1DRC_KNEE2_OP - [4:0] */ +#define WM8996_DSP1DRC_KNEE2_OP_SHIFT 0 /* DSP1DRC_KNEE2_OP - [4:0] */ +#define WM8996_DSP1DRC_KNEE2_OP_WIDTH 5 /* DSP1DRC_KNEE2_OP - [4:0] */ + +/* + * R1152 (0x480) - DSP1 RX EQ Gains (1) + */ +#define WM8996_DSP1RX_EQ_B1_GAIN_MASK 0xF800 /* DSP1RX_EQ_B1_GAIN - [15:11] */ +#define WM8996_DSP1RX_EQ_B1_GAIN_SHIFT 11 /* DSP1RX_EQ_B1_GAIN - [15:11] */ +#define WM8996_DSP1RX_EQ_B1_GAIN_WIDTH 5 /* DSP1RX_EQ_B1_GAIN - [15:11] */ +#define WM8996_DSP1RX_EQ_B2_GAIN_MASK 0x07C0 /* DSP1RX_EQ_B2_GAIN - [10:6] */ +#define WM8996_DSP1RX_EQ_B2_GAIN_SHIFT 6 /* DSP1RX_EQ_B2_GAIN - [10:6] */ +#define WM8996_DSP1RX_EQ_B2_GAIN_WIDTH 5 /* DSP1RX_EQ_B2_GAIN - [10:6] */ +#define WM8996_DSP1RX_EQ_B3_GAIN_MASK 0x003E /* DSP1RX_EQ_B3_GAIN - [5:1] */ +#define WM8996_DSP1RX_EQ_B3_GAIN_SHIFT 1 /* DSP1RX_EQ_B3_GAIN - [5:1] */ +#define WM8996_DSP1RX_EQ_B3_GAIN_WIDTH 5 /* DSP1RX_EQ_B3_GAIN - [5:1] */ +#define WM8996_DSP1RX_EQ_ENA 0x0001 /* DSP1RX_EQ_ENA */ +#define WM8996_DSP1RX_EQ_ENA_MASK 0x0001 /* DSP1RX_EQ_ENA */ +#define WM8996_DSP1RX_EQ_ENA_SHIFT 0 /* DSP1RX_EQ_ENA */ +#define WM8996_DSP1RX_EQ_ENA_WIDTH 1 /* DSP1RX_EQ_ENA */ + +/* + * R1153 (0x481) - DSP1 RX EQ Gains (2) + */ +#define WM8996_DSP1RX_EQ_B4_GAIN_MASK 0xF800 /* DSP1RX_EQ_B4_GAIN - [15:11] */ +#define WM8996_DSP1RX_EQ_B4_GAIN_SHIFT 11 /* DSP1RX_EQ_B4_GAIN - [15:11] */ +#define WM8996_DSP1RX_EQ_B4_GAIN_WIDTH 5 /* DSP1RX_EQ_B4_GAIN - [15:11] */ +#define WM8996_DSP1RX_EQ_B5_GAIN_MASK 0x07C0 /* DSP1RX_EQ_B5_GAIN - [10:6] */ +#define WM8996_DSP1RX_EQ_B5_GAIN_SHIFT 6 /* DSP1RX_EQ_B5_GAIN - [10:6] */ +#define WM8996_DSP1RX_EQ_B5_GAIN_WIDTH 5 /* DSP1RX_EQ_B5_GAIN - [10:6] */ + +/* + * R1154 (0x482) - DSP1 RX EQ Band 1 A + */ +#define WM8996_DSP1RX_EQ_B1_A_MASK 0xFFFF /* DSP1RX_EQ_B1_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B1_A_SHIFT 0 /* DSP1RX_EQ_B1_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B1_A_WIDTH 16 /* DSP1RX_EQ_B1_A - [15:0] */ + +/* + * R1155 (0x483) - DSP1 RX EQ Band 1 B + */ +#define WM8996_DSP1RX_EQ_B1_B_MASK 0xFFFF /* DSP1RX_EQ_B1_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B1_B_SHIFT 0 /* DSP1RX_EQ_B1_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B1_B_WIDTH 16 /* DSP1RX_EQ_B1_B - [15:0] */ + +/* + * R1156 (0x484) - DSP1 RX EQ Band 1 PG + */ +#define WM8996_DSP1RX_EQ_B1_PG_MASK 0xFFFF /* DSP1RX_EQ_B1_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B1_PG_SHIFT 0 /* DSP1RX_EQ_B1_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B1_PG_WIDTH 16 /* DSP1RX_EQ_B1_PG - [15:0] */ + +/* + * R1157 (0x485) - DSP1 RX EQ Band 2 A + */ +#define WM8996_DSP1RX_EQ_B2_A_MASK 0xFFFF /* DSP1RX_EQ_B2_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_A_SHIFT 0 /* DSP1RX_EQ_B2_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_A_WIDTH 16 /* DSP1RX_EQ_B2_A - [15:0] */ + +/* + * R1158 (0x486) - DSP1 RX EQ Band 2 B + */ +#define WM8996_DSP1RX_EQ_B2_B_MASK 0xFFFF /* DSP1RX_EQ_B2_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_B_SHIFT 0 /* DSP1RX_EQ_B2_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_B_WIDTH 16 /* DSP1RX_EQ_B2_B - [15:0] */ + +/* + * R1159 (0x487) - DSP1 RX EQ Band 2 C + */ +#define WM8996_DSP1RX_EQ_B2_C_MASK 0xFFFF /* DSP1RX_EQ_B2_C - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_C_SHIFT 0 /* DSP1RX_EQ_B2_C - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_C_WIDTH 16 /* DSP1RX_EQ_B2_C - [15:0] */ + +/* + * R1160 (0x488) - DSP1 RX EQ Band 2 PG + */ +#define WM8996_DSP1RX_EQ_B2_PG_MASK 0xFFFF /* DSP1RX_EQ_B2_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_PG_SHIFT 0 /* DSP1RX_EQ_B2_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B2_PG_WIDTH 16 /* DSP1RX_EQ_B2_PG - [15:0] */ + +/* + * R1161 (0x489) - DSP1 RX EQ Band 3 A + */ +#define WM8996_DSP1RX_EQ_B3_A_MASK 0xFFFF /* DSP1RX_EQ_B3_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_A_SHIFT 0 /* DSP1RX_EQ_B3_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_A_WIDTH 16 /* DSP1RX_EQ_B3_A - [15:0] */ + +/* + * R1162 (0x48A) - DSP1 RX EQ Band 3 B + */ +#define WM8996_DSP1RX_EQ_B3_B_MASK 0xFFFF /* DSP1RX_EQ_B3_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_B_SHIFT 0 /* DSP1RX_EQ_B3_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_B_WIDTH 16 /* DSP1RX_EQ_B3_B - [15:0] */ + +/* + * R1163 (0x48B) - DSP1 RX EQ Band 3 C + */ +#define WM8996_DSP1RX_EQ_B3_C_MASK 0xFFFF /* DSP1RX_EQ_B3_C - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_C_SHIFT 0 /* DSP1RX_EQ_B3_C - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_C_WIDTH 16 /* DSP1RX_EQ_B3_C - [15:0] */ + +/* + * R1164 (0x48C) - DSP1 RX EQ Band 3 PG + */ +#define WM8996_DSP1RX_EQ_B3_PG_MASK 0xFFFF /* DSP1RX_EQ_B3_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_PG_SHIFT 0 /* DSP1RX_EQ_B3_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B3_PG_WIDTH 16 /* DSP1RX_EQ_B3_PG - [15:0] */ + +/* + * R1165 (0x48D) - DSP1 RX EQ Band 4 A + */ +#define WM8996_DSP1RX_EQ_B4_A_MASK 0xFFFF /* DSP1RX_EQ_B4_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_A_SHIFT 0 /* DSP1RX_EQ_B4_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_A_WIDTH 16 /* DSP1RX_EQ_B4_A - [15:0] */ + +/* + * R1166 (0x48E) - DSP1 RX EQ Band 4 B + */ +#define WM8996_DSP1RX_EQ_B4_B_MASK 0xFFFF /* DSP1RX_EQ_B4_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_B_SHIFT 0 /* DSP1RX_EQ_B4_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_B_WIDTH 16 /* DSP1RX_EQ_B4_B - [15:0] */ + +/* + * R1167 (0x48F) - DSP1 RX EQ Band 4 C + */ +#define WM8996_DSP1RX_EQ_B4_C_MASK 0xFFFF /* DSP1RX_EQ_B4_C - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_C_SHIFT 0 /* DSP1RX_EQ_B4_C - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_C_WIDTH 16 /* DSP1RX_EQ_B4_C - [15:0] */ + +/* + * R1168 (0x490) - DSP1 RX EQ Band 4 PG + */ +#define WM8996_DSP1RX_EQ_B4_PG_MASK 0xFFFF /* DSP1RX_EQ_B4_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_PG_SHIFT 0 /* DSP1RX_EQ_B4_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B4_PG_WIDTH 16 /* DSP1RX_EQ_B4_PG - [15:0] */ + +/* + * R1169 (0x491) - DSP1 RX EQ Band 5 A + */ +#define WM8996_DSP1RX_EQ_B5_A_MASK 0xFFFF /* DSP1RX_EQ_B5_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B5_A_SHIFT 0 /* DSP1RX_EQ_B5_A - [15:0] */ +#define WM8996_DSP1RX_EQ_B5_A_WIDTH 16 /* DSP1RX_EQ_B5_A - [15:0] */ + +/* + * R1170 (0x492) - DSP1 RX EQ Band 5 B + */ +#define WM8996_DSP1RX_EQ_B5_B_MASK 0xFFFF /* DSP1RX_EQ_B5_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B5_B_SHIFT 0 /* DSP1RX_EQ_B5_B - [15:0] */ +#define WM8996_DSP1RX_EQ_B5_B_WIDTH 16 /* DSP1RX_EQ_B5_B - [15:0] */ + +/* + * R1171 (0x493) - DSP1 RX EQ Band 5 PG + */ +#define WM8996_DSP1RX_EQ_B5_PG_MASK 0xFFFF /* DSP1RX_EQ_B5_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B5_PG_SHIFT 0 /* DSP1RX_EQ_B5_PG - [15:0] */ +#define WM8996_DSP1RX_EQ_B5_PG_WIDTH 16 /* DSP1RX_EQ_B5_PG - [15:0] */ + +/* + * R1280 (0x500) - DSP2 TX Left Volume + */ +#define WM8996_DSP2TX_VU 0x0100 /* DSP2TX_VU */ +#define WM8996_DSP2TX_VU_MASK 0x0100 /* DSP2TX_VU */ +#define WM8996_DSP2TX_VU_SHIFT 8 /* DSP2TX_VU */ +#define WM8996_DSP2TX_VU_WIDTH 1 /* DSP2TX_VU */ +#define WM8996_DSP2TXL_VOL_MASK 0x00FF /* DSP2TXL_VOL - [7:0] */ +#define WM8996_DSP2TXL_VOL_SHIFT 0 /* DSP2TXL_VOL - [7:0] */ +#define WM8996_DSP2TXL_VOL_WIDTH 8 /* DSP2TXL_VOL - [7:0] */ + +/* + * R1281 (0x501) - DSP2 TX Right Volume + */ +#define WM8996_DSP2TX_VU 0x0100 /* DSP2TX_VU */ +#define WM8996_DSP2TX_VU_MASK 0x0100 /* DSP2TX_VU */ +#define WM8996_DSP2TX_VU_SHIFT 8 /* DSP2TX_VU */ +#define WM8996_DSP2TX_VU_WIDTH 1 /* DSP2TX_VU */ +#define WM8996_DSP2TXR_VOL_MASK 0x00FF /* DSP2TXR_VOL - [7:0] */ +#define WM8996_DSP2TXR_VOL_SHIFT 0 /* DSP2TXR_VOL - [7:0] */ +#define WM8996_DSP2TXR_VOL_WIDTH 8 /* DSP2TXR_VOL - [7:0] */ + +/* + * R1282 (0x502) - DSP2 RX Left Volume + */ +#define WM8996_DSP2RX_VU 0x0100 /* DSP2RX_VU */ +#define WM8996_DSP2RX_VU_MASK 0x0100 /* DSP2RX_VU */ +#define WM8996_DSP2RX_VU_SHIFT 8 /* DSP2RX_VU */ +#define WM8996_DSP2RX_VU_WIDTH 1 /* DSP2RX_VU */ +#define WM8996_DSP2RXL_VOL_MASK 0x00FF /* DSP2RXL_VOL - [7:0] */ +#define WM8996_DSP2RXL_VOL_SHIFT 0 /* DSP2RXL_VOL - [7:0] */ +#define WM8996_DSP2RXL_VOL_WIDTH 8 /* DSP2RXL_VOL - [7:0] */ + +/* + * R1283 (0x503) - DSP2 RX Right Volume + */ +#define WM8996_DSP2RX_VU 0x0100 /* DSP2RX_VU */ +#define WM8996_DSP2RX_VU_MASK 0x0100 /* DSP2RX_VU */ +#define WM8996_DSP2RX_VU_SHIFT 8 /* DSP2RX_VU */ +#define WM8996_DSP2RX_VU_WIDTH 1 /* DSP2RX_VU */ +#define WM8996_DSP2RXR_VOL_MASK 0x00FF /* DSP2RXR_VOL - [7:0] */ +#define WM8996_DSP2RXR_VOL_SHIFT 0 /* DSP2RXR_VOL - [7:0] */ +#define WM8996_DSP2RXR_VOL_WIDTH 8 /* DSP2RXR_VOL - [7:0] */ + +/* + * R1296 (0x510) - DSP2 TX Filters + */ +#define WM8996_DSP2TX_NF 0x2000 /* DSP2TX_NF */ +#define WM8996_DSP2TX_NF_MASK 0x2000 /* DSP2TX_NF */ +#define WM8996_DSP2TX_NF_SHIFT 13 /* DSP2TX_NF */ +#define WM8996_DSP2TX_NF_WIDTH 1 /* DSP2TX_NF */ +#define WM8996_DSP2TXL_HPF 0x1000 /* DSP2TXL_HPF */ +#define WM8996_DSP2TXL_HPF_MASK 0x1000 /* DSP2TXL_HPF */ +#define WM8996_DSP2TXL_HPF_SHIFT 12 /* DSP2TXL_HPF */ +#define WM8996_DSP2TXL_HPF_WIDTH 1 /* DSP2TXL_HPF */ +#define WM8996_DSP2TXR_HPF 0x0800 /* DSP2TXR_HPF */ +#define WM8996_DSP2TXR_HPF_MASK 0x0800 /* DSP2TXR_HPF */ +#define WM8996_DSP2TXR_HPF_SHIFT 11 /* DSP2TXR_HPF */ +#define WM8996_DSP2TXR_HPF_WIDTH 1 /* DSP2TXR_HPF */ +#define WM8996_DSP2TX_HPF_MODE_MASK 0x0018 /* DSP2TX_HPF_MODE - [4:3] */ +#define WM8996_DSP2TX_HPF_MODE_SHIFT 3 /* DSP2TX_HPF_MODE - [4:3] */ +#define WM8996_DSP2TX_HPF_MODE_WIDTH 2 /* DSP2TX_HPF_MODE - [4:3] */ +#define WM8996_DSP2TX_HPF_CUT_MASK 0x0007 /* DSP2TX_HPF_CUT - [2:0] */ +#define WM8996_DSP2TX_HPF_CUT_SHIFT 0 /* DSP2TX_HPF_CUT - [2:0] */ +#define WM8996_DSP2TX_HPF_CUT_WIDTH 3 /* DSP2TX_HPF_CUT - [2:0] */ + +/* + * R1312 (0x520) - DSP2 RX Filters (1) + */ +#define WM8996_DSP2RX_MUTE 0x0200 /* DSP2RX_MUTE */ +#define WM8996_DSP2RX_MUTE_MASK 0x0200 /* DSP2RX_MUTE */ +#define WM8996_DSP2RX_MUTE_SHIFT 9 /* DSP2RX_MUTE */ +#define WM8996_DSP2RX_MUTE_WIDTH 1 /* DSP2RX_MUTE */ +#define WM8996_DSP2RX_MONO 0x0080 /* DSP2RX_MONO */ +#define WM8996_DSP2RX_MONO_MASK 0x0080 /* DSP2RX_MONO */ +#define WM8996_DSP2RX_MONO_SHIFT 7 /* DSP2RX_MONO */ +#define WM8996_DSP2RX_MONO_WIDTH 1 /* DSP2RX_MONO */ +#define WM8996_DSP2RX_MUTERATE 0x0020 /* DSP2RX_MUTERATE */ +#define WM8996_DSP2RX_MUTERATE_MASK 0x0020 /* DSP2RX_MUTERATE */ +#define WM8996_DSP2RX_MUTERATE_SHIFT 5 /* DSP2RX_MUTERATE */ +#define WM8996_DSP2RX_MUTERATE_WIDTH 1 /* DSP2RX_MUTERATE */ +#define WM8996_DSP2RX_UNMUTE_RAMP 0x0010 /* DSP2RX_UNMUTE_RAMP */ +#define WM8996_DSP2RX_UNMUTE_RAMP_MASK 0x0010 /* DSP2RX_UNMUTE_RAMP */ +#define WM8996_DSP2RX_UNMUTE_RAMP_SHIFT 4 /* DSP2RX_UNMUTE_RAMP */ +#define WM8996_DSP2RX_UNMUTE_RAMP_WIDTH 1 /* DSP2RX_UNMUTE_RAMP */ + +/* + * R1313 (0x521) - DSP2 RX Filters (2) + */ +#define WM8996_DSP2RX_3D_GAIN_MASK 0x3E00 /* DSP2RX_3D_GAIN - [13:9] */ +#define WM8996_DSP2RX_3D_GAIN_SHIFT 9 /* DSP2RX_3D_GAIN - [13:9] */ +#define WM8996_DSP2RX_3D_GAIN_WIDTH 5 /* DSP2RX_3D_GAIN - [13:9] */ +#define WM8996_DSP2RX_3D_ENA 0x0100 /* DSP2RX_3D_ENA */ +#define WM8996_DSP2RX_3D_ENA_MASK 0x0100 /* DSP2RX_3D_ENA */ +#define WM8996_DSP2RX_3D_ENA_SHIFT 8 /* DSP2RX_3D_ENA */ +#define WM8996_DSP2RX_3D_ENA_WIDTH 1 /* DSP2RX_3D_ENA */ + +/* + * R1344 (0x540) - DSP2 DRC (1) + */ +#define WM8996_DSP2DRC_SIG_DET_RMS_MASK 0xF800 /* DSP2DRC_SIG_DET_RMS - [15:11] */ +#define WM8996_DSP2DRC_SIG_DET_RMS_SHIFT 11 /* DSP2DRC_SIG_DET_RMS - [15:11] */ +#define WM8996_DSP2DRC_SIG_DET_RMS_WIDTH 5 /* DSP2DRC_SIG_DET_RMS - [15:11] */ +#define WM8996_DSP2DRC_SIG_DET_PK_MASK 0x0600 /* DSP2DRC_SIG_DET_PK - [10:9] */ +#define WM8996_DSP2DRC_SIG_DET_PK_SHIFT 9 /* DSP2DRC_SIG_DET_PK - [10:9] */ +#define WM8996_DSP2DRC_SIG_DET_PK_WIDTH 2 /* DSP2DRC_SIG_DET_PK - [10:9] */ +#define WM8996_DSP2DRC_NG_ENA 0x0100 /* DSP2DRC_NG_ENA */ +#define WM8996_DSP2DRC_NG_ENA_MASK 0x0100 /* DSP2DRC_NG_ENA */ +#define WM8996_DSP2DRC_NG_ENA_SHIFT 8 /* DSP2DRC_NG_ENA */ +#define WM8996_DSP2DRC_NG_ENA_WIDTH 1 /* DSP2DRC_NG_ENA */ +#define WM8996_DSP2DRC_SIG_DET_MODE 0x0080 /* DSP2DRC_SIG_DET_MODE */ +#define WM8996_DSP2DRC_SIG_DET_MODE_MASK 0x0080 /* DSP2DRC_SIG_DET_MODE */ +#define WM8996_DSP2DRC_SIG_DET_MODE_SHIFT 7 /* DSP2DRC_SIG_DET_MODE */ +#define WM8996_DSP2DRC_SIG_DET_MODE_WIDTH 1 /* DSP2DRC_SIG_DET_MODE */ +#define WM8996_DSP2DRC_SIG_DET 0x0040 /* DSP2DRC_SIG_DET */ +#define WM8996_DSP2DRC_SIG_DET_MASK 0x0040 /* DSP2DRC_SIG_DET */ +#define WM8996_DSP2DRC_SIG_DET_SHIFT 6 /* DSP2DRC_SIG_DET */ +#define WM8996_DSP2DRC_SIG_DET_WIDTH 1 /* DSP2DRC_SIG_DET */ +#define WM8996_DSP2DRC_KNEE2_OP_ENA 0x0020 /* DSP2DRC_KNEE2_OP_ENA */ +#define WM8996_DSP2DRC_KNEE2_OP_ENA_MASK 0x0020 /* DSP2DRC_KNEE2_OP_ENA */ +#define WM8996_DSP2DRC_KNEE2_OP_ENA_SHIFT 5 /* DSP2DRC_KNEE2_OP_ENA */ +#define WM8996_DSP2DRC_KNEE2_OP_ENA_WIDTH 1 /* DSP2DRC_KNEE2_OP_ENA */ +#define WM8996_DSP2DRC_QR 0x0010 /* DSP2DRC_QR */ +#define WM8996_DSP2DRC_QR_MASK 0x0010 /* DSP2DRC_QR */ +#define WM8996_DSP2DRC_QR_SHIFT 4 /* DSP2DRC_QR */ +#define WM8996_DSP2DRC_QR_WIDTH 1 /* DSP2DRC_QR */ +#define WM8996_DSP2DRC_ANTICLIP 0x0008 /* DSP2DRC_ANTICLIP */ +#define WM8996_DSP2DRC_ANTICLIP_MASK 0x0008 /* DSP2DRC_ANTICLIP */ +#define WM8996_DSP2DRC_ANTICLIP_SHIFT 3 /* DSP2DRC_ANTICLIP */ +#define WM8996_DSP2DRC_ANTICLIP_WIDTH 1 /* DSP2DRC_ANTICLIP */ +#define WM8996_DSP2RX_DRC_ENA 0x0004 /* DSP2RX_DRC_ENA */ +#define WM8996_DSP2RX_DRC_ENA_MASK 0x0004 /* DSP2RX_DRC_ENA */ +#define WM8996_DSP2RX_DRC_ENA_SHIFT 2 /* DSP2RX_DRC_ENA */ +#define WM8996_DSP2RX_DRC_ENA_WIDTH 1 /* DSP2RX_DRC_ENA */ +#define WM8996_DSP2TXL_DRC_ENA 0x0002 /* DSP2TXL_DRC_ENA */ +#define WM8996_DSP2TXL_DRC_ENA_MASK 0x0002 /* DSP2TXL_DRC_ENA */ +#define WM8996_DSP2TXL_DRC_ENA_SHIFT 1 /* DSP2TXL_DRC_ENA */ +#define WM8996_DSP2TXL_DRC_ENA_WIDTH 1 /* DSP2TXL_DRC_ENA */ +#define WM8996_DSP2TXR_DRC_ENA 0x0001 /* DSP2TXR_DRC_ENA */ +#define WM8996_DSP2TXR_DRC_ENA_MASK 0x0001 /* DSP2TXR_DRC_ENA */ +#define WM8996_DSP2TXR_DRC_ENA_SHIFT 0 /* DSP2TXR_DRC_ENA */ +#define WM8996_DSP2TXR_DRC_ENA_WIDTH 1 /* DSP2TXR_DRC_ENA */ + +/* + * R1345 (0x541) - DSP2 DRC (2) + */ +#define WM8996_DSP2DRC_ATK_MASK 0x1E00 /* DSP2DRC_ATK - [12:9] */ +#define WM8996_DSP2DRC_ATK_SHIFT 9 /* DSP2DRC_ATK - [12:9] */ +#define WM8996_DSP2DRC_ATK_WIDTH 4 /* DSP2DRC_ATK - [12:9] */ +#define WM8996_DSP2DRC_DCY_MASK 0x01E0 /* DSP2DRC_DCY - [8:5] */ +#define WM8996_DSP2DRC_DCY_SHIFT 5 /* DSP2DRC_DCY - [8:5] */ +#define WM8996_DSP2DRC_DCY_WIDTH 4 /* DSP2DRC_DCY - [8:5] */ +#define WM8996_DSP2DRC_MINGAIN_MASK 0x001C /* DSP2DRC_MINGAIN - [4:2] */ +#define WM8996_DSP2DRC_MINGAIN_SHIFT 2 /* DSP2DRC_MINGAIN - [4:2] */ +#define WM8996_DSP2DRC_MINGAIN_WIDTH 3 /* DSP2DRC_MINGAIN - [4:2] */ +#define WM8996_DSP2DRC_MAXGAIN_MASK 0x0003 /* DSP2DRC_MAXGAIN - [1:0] */ +#define WM8996_DSP2DRC_MAXGAIN_SHIFT 0 /* DSP2DRC_MAXGAIN - [1:0] */ +#define WM8996_DSP2DRC_MAXGAIN_WIDTH 2 /* DSP2DRC_MAXGAIN - [1:0] */ + +/* + * R1346 (0x542) - DSP2 DRC (3) + */ +#define WM8996_DSP2DRC_NG_MINGAIN_MASK 0xF000 /* DSP2DRC_NG_MINGAIN - [15:12] */ +#define WM8996_DSP2DRC_NG_MINGAIN_SHIFT 12 /* DSP2DRC_NG_MINGAIN - [15:12] */ +#define WM8996_DSP2DRC_NG_MINGAIN_WIDTH 4 /* DSP2DRC_NG_MINGAIN - [15:12] */ +#define WM8996_DSP2DRC_NG_EXP_MASK 0x0C00 /* DSP2DRC_NG_EXP - [11:10] */ +#define WM8996_DSP2DRC_NG_EXP_SHIFT 10 /* DSP2DRC_NG_EXP - [11:10] */ +#define WM8996_DSP2DRC_NG_EXP_WIDTH 2 /* DSP2DRC_NG_EXP - [11:10] */ +#define WM8996_DSP2DRC_QR_THR_MASK 0x0300 /* DSP2DRC_QR_THR - [9:8] */ +#define WM8996_DSP2DRC_QR_THR_SHIFT 8 /* DSP2DRC_QR_THR - [9:8] */ +#define WM8996_DSP2DRC_QR_THR_WIDTH 2 /* DSP2DRC_QR_THR - [9:8] */ +#define WM8996_DSP2DRC_QR_DCY_MASK 0x00C0 /* DSP2DRC_QR_DCY - [7:6] */ +#define WM8996_DSP2DRC_QR_DCY_SHIFT 6 /* DSP2DRC_QR_DCY - [7:6] */ +#define WM8996_DSP2DRC_QR_DCY_WIDTH 2 /* DSP2DRC_QR_DCY - [7:6] */ +#define WM8996_DSP2DRC_HI_COMP_MASK 0x0038 /* DSP2DRC_HI_COMP - [5:3] */ +#define WM8996_DSP2DRC_HI_COMP_SHIFT 3 /* DSP2DRC_HI_COMP - [5:3] */ +#define WM8996_DSP2DRC_HI_COMP_WIDTH 3 /* DSP2DRC_HI_COMP - [5:3] */ +#define WM8996_DSP2DRC_LO_COMP_MASK 0x0007 /* DSP2DRC_LO_COMP - [2:0] */ +#define WM8996_DSP2DRC_LO_COMP_SHIFT 0 /* DSP2DRC_LO_COMP - [2:0] */ +#define WM8996_DSP2DRC_LO_COMP_WIDTH 3 /* DSP2DRC_LO_COMP - [2:0] */ + +/* + * R1347 (0x543) - DSP2 DRC (4) + */ +#define WM8996_DSP2DRC_KNEE_IP_MASK 0x07E0 /* DSP2DRC_KNEE_IP - [10:5] */ +#define WM8996_DSP2DRC_KNEE_IP_SHIFT 5 /* DSP2DRC_KNEE_IP - [10:5] */ +#define WM8996_DSP2DRC_KNEE_IP_WIDTH 6 /* DSP2DRC_KNEE_IP - [10:5] */ +#define WM8996_DSP2DRC_KNEE_OP_MASK 0x001F /* DSP2DRC_KNEE_OP - [4:0] */ +#define WM8996_DSP2DRC_KNEE_OP_SHIFT 0 /* DSP2DRC_KNEE_OP - [4:0] */ +#define WM8996_DSP2DRC_KNEE_OP_WIDTH 5 /* DSP2DRC_KNEE_OP - [4:0] */ + +/* + * R1348 (0x544) - DSP2 DRC (5) + */ +#define WM8996_DSP2DRC_KNEE2_IP_MASK 0x03E0 /* DSP2DRC_KNEE2_IP - [9:5] */ +#define WM8996_DSP2DRC_KNEE2_IP_SHIFT 5 /* DSP2DRC_KNEE2_IP - [9:5] */ +#define WM8996_DSP2DRC_KNEE2_IP_WIDTH 5 /* DSP2DRC_KNEE2_IP - [9:5] */ +#define WM8996_DSP2DRC_KNEE2_OP_MASK 0x001F /* DSP2DRC_KNEE2_OP - [4:0] */ +#define WM8996_DSP2DRC_KNEE2_OP_SHIFT 0 /* DSP2DRC_KNEE2_OP - [4:0] */ +#define WM8996_DSP2DRC_KNEE2_OP_WIDTH 5 /* DSP2DRC_KNEE2_OP - [4:0] */ + +/* + * R1408 (0x580) - DSP2 RX EQ Gains (1) + */ +#define WM8996_DSP2RX_EQ_B1_GAIN_MASK 0xF800 /* DSP2RX_EQ_B1_GAIN - [15:11] */ +#define WM8996_DSP2RX_EQ_B1_GAIN_SHIFT 11 /* DSP2RX_EQ_B1_GAIN - [15:11] */ +#define WM8996_DSP2RX_EQ_B1_GAIN_WIDTH 5 /* DSP2RX_EQ_B1_GAIN - [15:11] */ +#define WM8996_DSP2RX_EQ_B2_GAIN_MASK 0x07C0 /* DSP2RX_EQ_B2_GAIN - [10:6] */ +#define WM8996_DSP2RX_EQ_B2_GAIN_SHIFT 6 /* DSP2RX_EQ_B2_GAIN - [10:6] */ +#define WM8996_DSP2RX_EQ_B2_GAIN_WIDTH 5 /* DSP2RX_EQ_B2_GAIN - [10:6] */ +#define WM8996_DSP2RX_EQ_B3_GAIN_MASK 0x003E /* DSP2RX_EQ_B3_GAIN - [5:1] */ +#define WM8996_DSP2RX_EQ_B3_GAIN_SHIFT 1 /* DSP2RX_EQ_B3_GAIN - [5:1] */ +#define WM8996_DSP2RX_EQ_B3_GAIN_WIDTH 5 /* DSP2RX_EQ_B3_GAIN - [5:1] */ +#define WM8996_DSP2RX_EQ_ENA 0x0001 /* DSP2RX_EQ_ENA */ +#define WM8996_DSP2RX_EQ_ENA_MASK 0x0001 /* DSP2RX_EQ_ENA */ +#define WM8996_DSP2RX_EQ_ENA_SHIFT 0 /* DSP2RX_EQ_ENA */ +#define WM8996_DSP2RX_EQ_ENA_WIDTH 1 /* DSP2RX_EQ_ENA */ + +/* + * R1409 (0x581) - DSP2 RX EQ Gains (2) + */ +#define WM8996_DSP2RX_EQ_B4_GAIN_MASK 0xF800 /* DSP2RX_EQ_B4_GAIN - [15:11] */ +#define WM8996_DSP2RX_EQ_B4_GAIN_SHIFT 11 /* DSP2RX_EQ_B4_GAIN - [15:11] */ +#define WM8996_DSP2RX_EQ_B4_GAIN_WIDTH 5 /* DSP2RX_EQ_B4_GAIN - [15:11] */ +#define WM8996_DSP2RX_EQ_B5_GAIN_MASK 0x07C0 /* DSP2RX_EQ_B5_GAIN - [10:6] */ +#define WM8996_DSP2RX_EQ_B5_GAIN_SHIFT 6 /* DSP2RX_EQ_B5_GAIN - [10:6] */ +#define WM8996_DSP2RX_EQ_B5_GAIN_WIDTH 5 /* DSP2RX_EQ_B5_GAIN - [10:6] */ + +/* + * R1410 (0x582) - DSP2 RX EQ Band 1 A + */ +#define WM8996_DSP2RX_EQ_B1_A_MASK 0xFFFF /* DSP2RX_EQ_B1_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B1_A_SHIFT 0 /* DSP2RX_EQ_B1_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B1_A_WIDTH 16 /* DSP2RX_EQ_B1_A - [15:0] */ + +/* + * R1411 (0x583) - DSP2 RX EQ Band 1 B + */ +#define WM8996_DSP2RX_EQ_B1_B_MASK 0xFFFF /* DSP2RX_EQ_B1_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B1_B_SHIFT 0 /* DSP2RX_EQ_B1_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B1_B_WIDTH 16 /* DSP2RX_EQ_B1_B - [15:0] */ + +/* + * R1412 (0x584) - DSP2 RX EQ Band 1 PG + */ +#define WM8996_DSP2RX_EQ_B1_PG_MASK 0xFFFF /* DSP2RX_EQ_B1_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B1_PG_SHIFT 0 /* DSP2RX_EQ_B1_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B1_PG_WIDTH 16 /* DSP2RX_EQ_B1_PG - [15:0] */ + +/* + * R1413 (0x585) - DSP2 RX EQ Band 2 A + */ +#define WM8996_DSP2RX_EQ_B2_A_MASK 0xFFFF /* DSP2RX_EQ_B2_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_A_SHIFT 0 /* DSP2RX_EQ_B2_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_A_WIDTH 16 /* DSP2RX_EQ_B2_A - [15:0] */ + +/* + * R1414 (0x586) - DSP2 RX EQ Band 2 B + */ +#define WM8996_DSP2RX_EQ_B2_B_MASK 0xFFFF /* DSP2RX_EQ_B2_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_B_SHIFT 0 /* DSP2RX_EQ_B2_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_B_WIDTH 16 /* DSP2RX_EQ_B2_B - [15:0] */ + +/* + * R1415 (0x587) - DSP2 RX EQ Band 2 C + */ +#define WM8996_DSP2RX_EQ_B2_C_MASK 0xFFFF /* DSP2RX_EQ_B2_C - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_C_SHIFT 0 /* DSP2RX_EQ_B2_C - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_C_WIDTH 16 /* DSP2RX_EQ_B2_C - [15:0] */ + +/* + * R1416 (0x588) - DSP2 RX EQ Band 2 PG + */ +#define WM8996_DSP2RX_EQ_B2_PG_MASK 0xFFFF /* DSP2RX_EQ_B2_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_PG_SHIFT 0 /* DSP2RX_EQ_B2_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B2_PG_WIDTH 16 /* DSP2RX_EQ_B2_PG - [15:0] */ + +/* + * R1417 (0x589) - DSP2 RX EQ Band 3 A + */ +#define WM8996_DSP2RX_EQ_B3_A_MASK 0xFFFF /* DSP2RX_EQ_B3_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_A_SHIFT 0 /* DSP2RX_EQ_B3_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_A_WIDTH 16 /* DSP2RX_EQ_B3_A - [15:0] */ + +/* + * R1418 (0x58A) - DSP2 RX EQ Band 3 B + */ +#define WM8996_DSP2RX_EQ_B3_B_MASK 0xFFFF /* DSP2RX_EQ_B3_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_B_SHIFT 0 /* DSP2RX_EQ_B3_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_B_WIDTH 16 /* DSP2RX_EQ_B3_B - [15:0] */ + +/* + * R1419 (0x58B) - DSP2 RX EQ Band 3 C + */ +#define WM8996_DSP2RX_EQ_B3_C_MASK 0xFFFF /* DSP2RX_EQ_B3_C - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_C_SHIFT 0 /* DSP2RX_EQ_B3_C - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_C_WIDTH 16 /* DSP2RX_EQ_B3_C - [15:0] */ + +/* + * R1420 (0x58C) - DSP2 RX EQ Band 3 PG + */ +#define WM8996_DSP2RX_EQ_B3_PG_MASK 0xFFFF /* DSP2RX_EQ_B3_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_PG_SHIFT 0 /* DSP2RX_EQ_B3_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B3_PG_WIDTH 16 /* DSP2RX_EQ_B3_PG - [15:0] */ + +/* + * R1421 (0x58D) - DSP2 RX EQ Band 4 A + */ +#define WM8996_DSP2RX_EQ_B4_A_MASK 0xFFFF /* DSP2RX_EQ_B4_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_A_SHIFT 0 /* DSP2RX_EQ_B4_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_A_WIDTH 16 /* DSP2RX_EQ_B4_A - [15:0] */ + +/* + * R1422 (0x58E) - DSP2 RX EQ Band 4 B + */ +#define WM8996_DSP2RX_EQ_B4_B_MASK 0xFFFF /* DSP2RX_EQ_B4_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_B_SHIFT 0 /* DSP2RX_EQ_B4_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_B_WIDTH 16 /* DSP2RX_EQ_B4_B - [15:0] */ + +/* + * R1423 (0x58F) - DSP2 RX EQ Band 4 C + */ +#define WM8996_DSP2RX_EQ_B4_C_MASK 0xFFFF /* DSP2RX_EQ_B4_C - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_C_SHIFT 0 /* DSP2RX_EQ_B4_C - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_C_WIDTH 16 /* DSP2RX_EQ_B4_C - [15:0] */ + +/* + * R1424 (0x590) - DSP2 RX EQ Band 4 PG + */ +#define WM8996_DSP2RX_EQ_B4_PG_MASK 0xFFFF /* DSP2RX_EQ_B4_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_PG_SHIFT 0 /* DSP2RX_EQ_B4_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B4_PG_WIDTH 16 /* DSP2RX_EQ_B4_PG - [15:0] */ + +/* + * R1425 (0x591) - DSP2 RX EQ Band 5 A + */ +#define WM8996_DSP2RX_EQ_B5_A_MASK 0xFFFF /* DSP2RX_EQ_B5_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B5_A_SHIFT 0 /* DSP2RX_EQ_B5_A - [15:0] */ +#define WM8996_DSP2RX_EQ_B5_A_WIDTH 16 /* DSP2RX_EQ_B5_A - [15:0] */ + +/* + * R1426 (0x592) - DSP2 RX EQ Band 5 B + */ +#define WM8996_DSP2RX_EQ_B5_B_MASK 0xFFFF /* DSP2RX_EQ_B5_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B5_B_SHIFT 0 /* DSP2RX_EQ_B5_B - [15:0] */ +#define WM8996_DSP2RX_EQ_B5_B_WIDTH 16 /* DSP2RX_EQ_B5_B - [15:0] */ + +/* + * R1427 (0x593) - DSP2 RX EQ Band 5 PG + */ +#define WM8996_DSP2RX_EQ_B5_PG_MASK 0xFFFF /* DSP2RX_EQ_B5_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B5_PG_SHIFT 0 /* DSP2RX_EQ_B5_PG - [15:0] */ +#define WM8996_DSP2RX_EQ_B5_PG_WIDTH 16 /* DSP2RX_EQ_B5_PG - [15:0] */ + +/* + * R1536 (0x600) - DAC1 Mixer Volumes + */ +#define WM8996_ADCR_DAC1_VOL_MASK 0x03E0 /* ADCR_DAC1_VOL - [9:5] */ +#define WM8996_ADCR_DAC1_VOL_SHIFT 5 /* ADCR_DAC1_VOL - [9:5] */ +#define WM8996_ADCR_DAC1_VOL_WIDTH 5 /* ADCR_DAC1_VOL - [9:5] */ +#define WM8996_ADCL_DAC1_VOL_MASK 0x001F /* ADCL_DAC1_VOL - [4:0] */ +#define WM8996_ADCL_DAC1_VOL_SHIFT 0 /* ADCL_DAC1_VOL - [4:0] */ +#define WM8996_ADCL_DAC1_VOL_WIDTH 5 /* ADCL_DAC1_VOL - [4:0] */ + +/* + * R1537 (0x601) - DAC1 Left Mixer Routing + */ +#define WM8996_ADCR_TO_DAC1L 0x0020 /* ADCR_TO_DAC1L */ +#define WM8996_ADCR_TO_DAC1L_MASK 0x0020 /* ADCR_TO_DAC1L */ +#define WM8996_ADCR_TO_DAC1L_SHIFT 5 /* ADCR_TO_DAC1L */ +#define WM8996_ADCR_TO_DAC1L_WIDTH 1 /* ADCR_TO_DAC1L */ +#define WM8996_ADCL_TO_DAC1L 0x0010 /* ADCL_TO_DAC1L */ +#define WM8996_ADCL_TO_DAC1L_MASK 0x0010 /* ADCL_TO_DAC1L */ +#define WM8996_ADCL_TO_DAC1L_SHIFT 4 /* ADCL_TO_DAC1L */ +#define WM8996_ADCL_TO_DAC1L_WIDTH 1 /* ADCL_TO_DAC1L */ +#define WM8996_DSP2RXL_TO_DAC1L 0x0002 /* DSP2RXL_TO_DAC1L */ +#define WM8996_DSP2RXL_TO_DAC1L_MASK 0x0002 /* DSP2RXL_TO_DAC1L */ +#define WM8996_DSP2RXL_TO_DAC1L_SHIFT 1 /* DSP2RXL_TO_DAC1L */ +#define WM8996_DSP2RXL_TO_DAC1L_WIDTH 1 /* DSP2RXL_TO_DAC1L */ +#define WM8996_DSP1RXL_TO_DAC1L 0x0001 /* DSP1RXL_TO_DAC1L */ +#define WM8996_DSP1RXL_TO_DAC1L_MASK 0x0001 /* DSP1RXL_TO_DAC1L */ +#define WM8996_DSP1RXL_TO_DAC1L_SHIFT 0 /* DSP1RXL_TO_DAC1L */ +#define WM8996_DSP1RXL_TO_DAC1L_WIDTH 1 /* DSP1RXL_TO_DAC1L */ + +/* + * R1538 (0x602) - DAC1 Right Mixer Routing + */ +#define WM8996_ADCR_TO_DAC1R 0x0020 /* ADCR_TO_DAC1R */ +#define WM8996_ADCR_TO_DAC1R_MASK 0x0020 /* ADCR_TO_DAC1R */ +#define WM8996_ADCR_TO_DAC1R_SHIFT 5 /* ADCR_TO_DAC1R */ +#define WM8996_ADCR_TO_DAC1R_WIDTH 1 /* ADCR_TO_DAC1R */ +#define WM8996_ADCL_TO_DAC1R 0x0010 /* ADCL_TO_DAC1R */ +#define WM8996_ADCL_TO_DAC1R_MASK 0x0010 /* ADCL_TO_DAC1R */ +#define WM8996_ADCL_TO_DAC1R_SHIFT 4 /* ADCL_TO_DAC1R */ +#define WM8996_ADCL_TO_DAC1R_WIDTH 1 /* ADCL_TO_DAC1R */ +#define WM8996_DSP2RXR_TO_DAC1R 0x0002 /* DSP2RXR_TO_DAC1R */ +#define WM8996_DSP2RXR_TO_DAC1R_MASK 0x0002 /* DSP2RXR_TO_DAC1R */ +#define WM8996_DSP2RXR_TO_DAC1R_SHIFT 1 /* DSP2RXR_TO_DAC1R */ +#define WM8996_DSP2RXR_TO_DAC1R_WIDTH 1 /* DSP2RXR_TO_DAC1R */ +#define WM8996_DSP1RXR_TO_DAC1R 0x0001 /* DSP1RXR_TO_DAC1R */ +#define WM8996_DSP1RXR_TO_DAC1R_MASK 0x0001 /* DSP1RXR_TO_DAC1R */ +#define WM8996_DSP1RXR_TO_DAC1R_SHIFT 0 /* DSP1RXR_TO_DAC1R */ +#define WM8996_DSP1RXR_TO_DAC1R_WIDTH 1 /* DSP1RXR_TO_DAC1R */ + +/* + * R1539 (0x603) - DAC2 Mixer Volumes + */ +#define WM8996_ADCR_DAC2_VOL_MASK 0x03E0 /* ADCR_DAC2_VOL - [9:5] */ +#define WM8996_ADCR_DAC2_VOL_SHIFT 5 /* ADCR_DAC2_VOL - [9:5] */ +#define WM8996_ADCR_DAC2_VOL_WIDTH 5 /* ADCR_DAC2_VOL - [9:5] */ +#define WM8996_ADCL_DAC2_VOL_MASK 0x001F /* ADCL_DAC2_VOL - [4:0] */ +#define WM8996_ADCL_DAC2_VOL_SHIFT 0 /* ADCL_DAC2_VOL - [4:0] */ +#define WM8996_ADCL_DAC2_VOL_WIDTH 5 /* ADCL_DAC2_VOL - [4:0] */ + +/* + * R1540 (0x604) - DAC2 Left Mixer Routing + */ +#define WM8996_ADCR_TO_DAC2L 0x0020 /* ADCR_TO_DAC2L */ +#define WM8996_ADCR_TO_DAC2L_MASK 0x0020 /* ADCR_TO_DAC2L */ +#define WM8996_ADCR_TO_DAC2L_SHIFT 5 /* ADCR_TO_DAC2L */ +#define WM8996_ADCR_TO_DAC2L_WIDTH 1 /* ADCR_TO_DAC2L */ +#define WM8996_ADCL_TO_DAC2L 0x0010 /* ADCL_TO_DAC2L */ +#define WM8996_ADCL_TO_DAC2L_MASK 0x0010 /* ADCL_TO_DAC2L */ +#define WM8996_ADCL_TO_DAC2L_SHIFT 4 /* ADCL_TO_DAC2L */ +#define WM8996_ADCL_TO_DAC2L_WIDTH 1 /* ADCL_TO_DAC2L */ +#define WM8996_DSP2RXL_TO_DAC2L 0x0002 /* DSP2RXL_TO_DAC2L */ +#define WM8996_DSP2RXL_TO_DAC2L_MASK 0x0002 /* DSP2RXL_TO_DAC2L */ +#define WM8996_DSP2RXL_TO_DAC2L_SHIFT 1 /* DSP2RXL_TO_DAC2L */ +#define WM8996_DSP2RXL_TO_DAC2L_WIDTH 1 /* DSP2RXL_TO_DAC2L */ +#define WM8996_DSP1RXL_TO_DAC2L 0x0001 /* DSP1RXL_TO_DAC2L */ +#define WM8996_DSP1RXL_TO_DAC2L_MASK 0x0001 /* DSP1RXL_TO_DAC2L */ +#define WM8996_DSP1RXL_TO_DAC2L_SHIFT 0 /* DSP1RXL_TO_DAC2L */ +#define WM8996_DSP1RXL_TO_DAC2L_WIDTH 1 /* DSP1RXL_TO_DAC2L */ + +/* + * R1541 (0x605) - DAC2 Right Mixer Routing + */ +#define WM8996_ADCR_TO_DAC2R 0x0020 /* ADCR_TO_DAC2R */ +#define WM8996_ADCR_TO_DAC2R_MASK 0x0020 /* ADCR_TO_DAC2R */ +#define WM8996_ADCR_TO_DAC2R_SHIFT 5 /* ADCR_TO_DAC2R */ +#define WM8996_ADCR_TO_DAC2R_WIDTH 1 /* ADCR_TO_DAC2R */ +#define WM8996_ADCL_TO_DAC2R 0x0010 /* ADCL_TO_DAC2R */ +#define WM8996_ADCL_TO_DAC2R_MASK 0x0010 /* ADCL_TO_DAC2R */ +#define WM8996_ADCL_TO_DAC2R_SHIFT 4 /* ADCL_TO_DAC2R */ +#define WM8996_ADCL_TO_DAC2R_WIDTH 1 /* ADCL_TO_DAC2R */ +#define WM8996_DSP2RXR_TO_DAC2R 0x0002 /* DSP2RXR_TO_DAC2R */ +#define WM8996_DSP2RXR_TO_DAC2R_MASK 0x0002 /* DSP2RXR_TO_DAC2R */ +#define WM8996_DSP2RXR_TO_DAC2R_SHIFT 1 /* DSP2RXR_TO_DAC2R */ +#define WM8996_DSP2RXR_TO_DAC2R_WIDTH 1 /* DSP2RXR_TO_DAC2R */ +#define WM8996_DSP1RXR_TO_DAC2R 0x0001 /* DSP1RXR_TO_DAC2R */ +#define WM8996_DSP1RXR_TO_DAC2R_MASK 0x0001 /* DSP1RXR_TO_DAC2R */ +#define WM8996_DSP1RXR_TO_DAC2R_SHIFT 0 /* DSP1RXR_TO_DAC2R */ +#define WM8996_DSP1RXR_TO_DAC2R_WIDTH 1 /* DSP1RXR_TO_DAC2R */ + +/* + * R1542 (0x606) - DSP1 TX Left Mixer Routing + */ +#define WM8996_ADC1L_TO_DSP1TXL 0x0002 /* ADC1L_TO_DSP1TXL */ +#define WM8996_ADC1L_TO_DSP1TXL_MASK 0x0002 /* ADC1L_TO_DSP1TXL */ +#define WM8996_ADC1L_TO_DSP1TXL_SHIFT 1 /* ADC1L_TO_DSP1TXL */ +#define WM8996_ADC1L_TO_DSP1TXL_WIDTH 1 /* ADC1L_TO_DSP1TXL */ +#define WM8996_DACL_TO_DSP1TXL 0x0001 /* DACL_TO_DSP1TXL */ +#define WM8996_DACL_TO_DSP1TXL_MASK 0x0001 /* DACL_TO_DSP1TXL */ +#define WM8996_DACL_TO_DSP1TXL_SHIFT 0 /* DACL_TO_DSP1TXL */ +#define WM8996_DACL_TO_DSP1TXL_WIDTH 1 /* DACL_TO_DSP1TXL */ + +/* + * R1543 (0x607) - DSP1 TX Right Mixer Routing + */ +#define WM8996_ADC1R_TO_DSP1TXR 0x0002 /* ADC1R_TO_DSP1TXR */ +#define WM8996_ADC1R_TO_DSP1TXR_MASK 0x0002 /* ADC1R_TO_DSP1TXR */ +#define WM8996_ADC1R_TO_DSP1TXR_SHIFT 1 /* ADC1R_TO_DSP1TXR */ +#define WM8996_ADC1R_TO_DSP1TXR_WIDTH 1 /* ADC1R_TO_DSP1TXR */ +#define WM8996_DACR_TO_DSP1TXR 0x0001 /* DACR_TO_DSP1TXR */ +#define WM8996_DACR_TO_DSP1TXR_MASK 0x0001 /* DACR_TO_DSP1TXR */ +#define WM8996_DACR_TO_DSP1TXR_SHIFT 0 /* DACR_TO_DSP1TXR */ +#define WM8996_DACR_TO_DSP1TXR_WIDTH 1 /* DACR_TO_DSP1TXR */ + +/* + * R1544 (0x608) - DSP2 TX Left Mixer Routing + */ +#define WM8996_ADC2L_TO_DSP2TXL 0x0002 /* ADC2L_TO_DSP2TXL */ +#define WM8996_ADC2L_TO_DSP2TXL_MASK 0x0002 /* ADC2L_TO_DSP2TXL */ +#define WM8996_ADC2L_TO_DSP2TXL_SHIFT 1 /* ADC2L_TO_DSP2TXL */ +#define WM8996_ADC2L_TO_DSP2TXL_WIDTH 1 /* ADC2L_TO_DSP2TXL */ +#define WM8996_DACL_TO_DSP2TXL 0x0001 /* DACL_TO_DSP2TXL */ +#define WM8996_DACL_TO_DSP2TXL_MASK 0x0001 /* DACL_TO_DSP2TXL */ +#define WM8996_DACL_TO_DSP2TXL_SHIFT 0 /* DACL_TO_DSP2TXL */ +#define WM8996_DACL_TO_DSP2TXL_WIDTH 1 /* DACL_TO_DSP2TXL */ + +/* + * R1545 (0x609) - DSP2 TX Right Mixer Routing + */ +#define WM8996_ADC2R_TO_DSP2TXR 0x0002 /* ADC2R_TO_DSP2TXR */ +#define WM8996_ADC2R_TO_DSP2TXR_MASK 0x0002 /* ADC2R_TO_DSP2TXR */ +#define WM8996_ADC2R_TO_DSP2TXR_SHIFT 1 /* ADC2R_TO_DSP2TXR */ +#define WM8996_ADC2R_TO_DSP2TXR_WIDTH 1 /* ADC2R_TO_DSP2TXR */ +#define WM8996_DACR_TO_DSP2TXR 0x0001 /* DACR_TO_DSP2TXR */ +#define WM8996_DACR_TO_DSP2TXR_MASK 0x0001 /* DACR_TO_DSP2TXR */ +#define WM8996_DACR_TO_DSP2TXR_SHIFT 0 /* DACR_TO_DSP2TXR */ +#define WM8996_DACR_TO_DSP2TXR_WIDTH 1 /* DACR_TO_DSP2TXR */ + +/* + * R1546 (0x60A) - DSP TX Mixer Select + */ +#define WM8996_DAC_TO_DSPTX_SRC 0x0001 /* DAC_TO_DSPTX_SRC */ +#define WM8996_DAC_TO_DSPTX_SRC_MASK 0x0001 /* DAC_TO_DSPTX_SRC */ +#define WM8996_DAC_TO_DSPTX_SRC_SHIFT 0 /* DAC_TO_DSPTX_SRC */ +#define WM8996_DAC_TO_DSPTX_SRC_WIDTH 1 /* DAC_TO_DSPTX_SRC */ + +/* + * R1552 (0x610) - DAC Softmute + */ +#define WM8996_DAC_SOFTMUTEMODE 0x0002 /* DAC_SOFTMUTEMODE */ +#define WM8996_DAC_SOFTMUTEMODE_MASK 0x0002 /* DAC_SOFTMUTEMODE */ +#define WM8996_DAC_SOFTMUTEMODE_SHIFT 1 /* DAC_SOFTMUTEMODE */ +#define WM8996_DAC_SOFTMUTEMODE_WIDTH 1 /* DAC_SOFTMUTEMODE */ +#define WM8996_DAC_MUTERATE 0x0001 /* DAC_MUTERATE */ +#define WM8996_DAC_MUTERATE_MASK 0x0001 /* DAC_MUTERATE */ +#define WM8996_DAC_MUTERATE_SHIFT 0 /* DAC_MUTERATE */ +#define WM8996_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ + +/* + * R1568 (0x620) - Oversampling + */ +#define WM8996_SPK_OSR128 0x0008 /* SPK_OSR128 */ +#define WM8996_SPK_OSR128_MASK 0x0008 /* SPK_OSR128 */ +#define WM8996_SPK_OSR128_SHIFT 3 /* SPK_OSR128 */ +#define WM8996_SPK_OSR128_WIDTH 1 /* SPK_OSR128 */ +#define WM8996_DMIC_OSR64 0x0004 /* DMIC_OSR64 */ +#define WM8996_DMIC_OSR64_MASK 0x0004 /* DMIC_OSR64 */ +#define WM8996_DMIC_OSR64_SHIFT 2 /* DMIC_OSR64 */ +#define WM8996_DMIC_OSR64_WIDTH 1 /* DMIC_OSR64 */ +#define WM8996_ADC_OSR128 0x0002 /* ADC_OSR128 */ +#define WM8996_ADC_OSR128_MASK 0x0002 /* ADC_OSR128 */ +#define WM8996_ADC_OSR128_SHIFT 1 /* ADC_OSR128 */ +#define WM8996_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ +#define WM8996_DAC_OSR128 0x0001 /* DAC_OSR128 */ +#define WM8996_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */ +#define WM8996_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */ +#define WM8996_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ + +/* + * R1569 (0x621) - Sidetone + */ +#define WM8996_ST_LPF 0x1000 /* ST_LPF */ +#define WM8996_ST_LPF_MASK 0x1000 /* ST_LPF */ +#define WM8996_ST_LPF_SHIFT 12 /* ST_LPF */ +#define WM8996_ST_LPF_WIDTH 1 /* ST_LPF */ +#define WM8996_ST_HPF_CUT_MASK 0x0380 /* ST_HPF_CUT - [9:7] */ +#define WM8996_ST_HPF_CUT_SHIFT 7 /* ST_HPF_CUT - [9:7] */ +#define WM8996_ST_HPF_CUT_WIDTH 3 /* ST_HPF_CUT - [9:7] */ +#define WM8996_ST_HPF 0x0040 /* ST_HPF */ +#define WM8996_ST_HPF_MASK 0x0040 /* ST_HPF */ +#define WM8996_ST_HPF_SHIFT 6 /* ST_HPF */ +#define WM8996_ST_HPF_WIDTH 1 /* ST_HPF */ +#define WM8996_STR_SEL 0x0002 /* STR_SEL */ +#define WM8996_STR_SEL_MASK 0x0002 /* STR_SEL */ +#define WM8996_STR_SEL_SHIFT 1 /* STR_SEL */ +#define WM8996_STR_SEL_WIDTH 1 /* STR_SEL */ +#define WM8996_STL_SEL 0x0001 /* STL_SEL */ +#define WM8996_STL_SEL_MASK 0x0001 /* STL_SEL */ +#define WM8996_STL_SEL_SHIFT 0 /* STL_SEL */ +#define WM8996_STL_SEL_WIDTH 1 /* STL_SEL */ + +/* + * R1792 (0x700) - GPIO 1 + */ +#define WM8996_GP1_DIR 0x8000 /* GP1_DIR */ +#define WM8996_GP1_DIR_MASK 0x8000 /* GP1_DIR */ +#define WM8996_GP1_DIR_SHIFT 15 /* GP1_DIR */ +#define WM8996_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM8996_GP1_PU 0x4000 /* GP1_PU */ +#define WM8996_GP1_PU_MASK 0x4000 /* GP1_PU */ +#define WM8996_GP1_PU_SHIFT 14 /* GP1_PU */ +#define WM8996_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM8996_GP1_PD 0x2000 /* GP1_PD */ +#define WM8996_GP1_PD_MASK 0x2000 /* GP1_PD */ +#define WM8996_GP1_PD_SHIFT 13 /* GP1_PD */ +#define WM8996_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM8996_GP1_POL 0x0400 /* GP1_POL */ +#define WM8996_GP1_POL_MASK 0x0400 /* GP1_POL */ +#define WM8996_GP1_POL_SHIFT 10 /* GP1_POL */ +#define WM8996_GP1_POL_WIDTH 1 /* GP1_POL */ +#define WM8996_GP1_OP_CFG 0x0200 /* GP1_OP_CFG */ +#define WM8996_GP1_OP_CFG_MASK 0x0200 /* GP1_OP_CFG */ +#define WM8996_GP1_OP_CFG_SHIFT 9 /* GP1_OP_CFG */ +#define WM8996_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM8996_GP1_DB 0x0100 /* GP1_DB */ +#define WM8996_GP1_DB_MASK 0x0100 /* GP1_DB */ +#define WM8996_GP1_DB_SHIFT 8 /* GP1_DB */ +#define WM8996_GP1_DB_WIDTH 1 /* GP1_DB */ +#define WM8996_GP1_LVL 0x0040 /* GP1_LVL */ +#define WM8996_GP1_LVL_MASK 0x0040 /* GP1_LVL */ +#define WM8996_GP1_LVL_SHIFT 6 /* GP1_LVL */ +#define WM8996_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM8996_GP1_FN_MASK 0x000F /* GP1_FN - [3:0] */ +#define WM8996_GP1_FN_SHIFT 0 /* GP1_FN - [3:0] */ +#define WM8996_GP1_FN_WIDTH 4 /* GP1_FN - [3:0] */ + +/* + * R1793 (0x701) - GPIO 2 + */ +#define WM8996_GP2_DIR 0x8000 /* GP2_DIR */ +#define WM8996_GP2_DIR_MASK 0x8000 /* GP2_DIR */ +#define WM8996_GP2_DIR_SHIFT 15 /* GP2_DIR */ +#define WM8996_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM8996_GP2_PU 0x4000 /* GP2_PU */ +#define WM8996_GP2_PU_MASK 0x4000 /* GP2_PU */ +#define WM8996_GP2_PU_SHIFT 14 /* GP2_PU */ +#define WM8996_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM8996_GP2_PD 0x2000 /* GP2_PD */ +#define WM8996_GP2_PD_MASK 0x2000 /* GP2_PD */ +#define WM8996_GP2_PD_SHIFT 13 /* GP2_PD */ +#define WM8996_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM8996_GP2_POL 0x0400 /* GP2_POL */ +#define WM8996_GP2_POL_MASK 0x0400 /* GP2_POL */ +#define WM8996_GP2_POL_SHIFT 10 /* GP2_POL */ +#define WM8996_GP2_POL_WIDTH 1 /* GP2_POL */ +#define WM8996_GP2_OP_CFG 0x0200 /* GP2_OP_CFG */ +#define WM8996_GP2_OP_CFG_MASK 0x0200 /* GP2_OP_CFG */ +#define WM8996_GP2_OP_CFG_SHIFT 9 /* GP2_OP_CFG */ +#define WM8996_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM8996_GP2_DB 0x0100 /* GP2_DB */ +#define WM8996_GP2_DB_MASK 0x0100 /* GP2_DB */ +#define WM8996_GP2_DB_SHIFT 8 /* GP2_DB */ +#define WM8996_GP2_DB_WIDTH 1 /* GP2_DB */ +#define WM8996_GP2_LVL 0x0040 /* GP2_LVL */ +#define WM8996_GP2_LVL_MASK 0x0040 /* GP2_LVL */ +#define WM8996_GP2_LVL_SHIFT 6 /* GP2_LVL */ +#define WM8996_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8996_GP2_FN_MASK 0x000F /* GP2_FN - [3:0] */ +#define WM8996_GP2_FN_SHIFT 0 /* GP2_FN - [3:0] */ +#define WM8996_GP2_FN_WIDTH 4 /* GP2_FN - [3:0] */ + +/* + * R1794 (0x702) - GPIO 3 + */ +#define WM8996_GP3_DIR 0x8000 /* GP3_DIR */ +#define WM8996_GP3_DIR_MASK 0x8000 /* GP3_DIR */ +#define WM8996_GP3_DIR_SHIFT 15 /* GP3_DIR */ +#define WM8996_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM8996_GP3_PU 0x4000 /* GP3_PU */ +#define WM8996_GP3_PU_MASK 0x4000 /* GP3_PU */ +#define WM8996_GP3_PU_SHIFT 14 /* GP3_PU */ +#define WM8996_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM8996_GP3_PD 0x2000 /* GP3_PD */ +#define WM8996_GP3_PD_MASK 0x2000 /* GP3_PD */ +#define WM8996_GP3_PD_SHIFT 13 /* GP3_PD */ +#define WM8996_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM8996_GP3_POL 0x0400 /* GP3_POL */ +#define WM8996_GP3_POL_MASK 0x0400 /* GP3_POL */ +#define WM8996_GP3_POL_SHIFT 10 /* GP3_POL */ +#define WM8996_GP3_POL_WIDTH 1 /* GP3_POL */ +#define WM8996_GP3_OP_CFG 0x0200 /* GP3_OP_CFG */ +#define WM8996_GP3_OP_CFG_MASK 0x0200 /* GP3_OP_CFG */ +#define WM8996_GP3_OP_CFG_SHIFT 9 /* GP3_OP_CFG */ +#define WM8996_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM8996_GP3_DB 0x0100 /* GP3_DB */ +#define WM8996_GP3_DB_MASK 0x0100 /* GP3_DB */ +#define WM8996_GP3_DB_SHIFT 8 /* GP3_DB */ +#define WM8996_GP3_DB_WIDTH 1 /* GP3_DB */ +#define WM8996_GP3_LVL 0x0040 /* GP3_LVL */ +#define WM8996_GP3_LVL_MASK 0x0040 /* GP3_LVL */ +#define WM8996_GP3_LVL_SHIFT 6 /* GP3_LVL */ +#define WM8996_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8996_GP3_FN_MASK 0x000F /* GP3_FN - [3:0] */ +#define WM8996_GP3_FN_SHIFT 0 /* GP3_FN - [3:0] */ +#define WM8996_GP3_FN_WIDTH 4 /* GP3_FN - [3:0] */ + +/* + * R1795 (0x703) - GPIO 4 + */ +#define WM8996_GP4_DIR 0x8000 /* GP4_DIR */ +#define WM8996_GP4_DIR_MASK 0x8000 /* GP4_DIR */ +#define WM8996_GP4_DIR_SHIFT 15 /* GP4_DIR */ +#define WM8996_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM8996_GP4_PU 0x4000 /* GP4_PU */ +#define WM8996_GP4_PU_MASK 0x4000 /* GP4_PU */ +#define WM8996_GP4_PU_SHIFT 14 /* GP4_PU */ +#define WM8996_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM8996_GP4_PD 0x2000 /* GP4_PD */ +#define WM8996_GP4_PD_MASK 0x2000 /* GP4_PD */ +#define WM8996_GP4_PD_SHIFT 13 /* GP4_PD */ +#define WM8996_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM8996_GP4_POL 0x0400 /* GP4_POL */ +#define WM8996_GP4_POL_MASK 0x0400 /* GP4_POL */ +#define WM8996_GP4_POL_SHIFT 10 /* GP4_POL */ +#define WM8996_GP4_POL_WIDTH 1 /* GP4_POL */ +#define WM8996_GP4_OP_CFG 0x0200 /* GP4_OP_CFG */ +#define WM8996_GP4_OP_CFG_MASK 0x0200 /* GP4_OP_CFG */ +#define WM8996_GP4_OP_CFG_SHIFT 9 /* GP4_OP_CFG */ +#define WM8996_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM8996_GP4_DB 0x0100 /* GP4_DB */ +#define WM8996_GP4_DB_MASK 0x0100 /* GP4_DB */ +#define WM8996_GP4_DB_SHIFT 8 /* GP4_DB */ +#define WM8996_GP4_DB_WIDTH 1 /* GP4_DB */ +#define WM8996_GP4_LVL 0x0040 /* GP4_LVL */ +#define WM8996_GP4_LVL_MASK 0x0040 /* GP4_LVL */ +#define WM8996_GP4_LVL_SHIFT 6 /* GP4_LVL */ +#define WM8996_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM8996_GP4_FN_MASK 0x000F /* GP4_FN - [3:0] */ +#define WM8996_GP4_FN_SHIFT 0 /* GP4_FN - [3:0] */ +#define WM8996_GP4_FN_WIDTH 4 /* GP4_FN - [3:0] */ + +/* + * R1796 (0x704) - GPIO 5 + */ +#define WM8996_GP5_DIR 0x8000 /* GP5_DIR */ +#define WM8996_GP5_DIR_MASK 0x8000 /* GP5_DIR */ +#define WM8996_GP5_DIR_SHIFT 15 /* GP5_DIR */ +#define WM8996_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8996_GP5_PU 0x4000 /* GP5_PU */ +#define WM8996_GP5_PU_MASK 0x4000 /* GP5_PU */ +#define WM8996_GP5_PU_SHIFT 14 /* GP5_PU */ +#define WM8996_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8996_GP5_PD 0x2000 /* GP5_PD */ +#define WM8996_GP5_PD_MASK 0x2000 /* GP5_PD */ +#define WM8996_GP5_PD_SHIFT 13 /* GP5_PD */ +#define WM8996_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8996_GP5_POL 0x0400 /* GP5_POL */ +#define WM8996_GP5_POL_MASK 0x0400 /* GP5_POL */ +#define WM8996_GP5_POL_SHIFT 10 /* GP5_POL */ +#define WM8996_GP5_POL_WIDTH 1 /* GP5_POL */ +#define WM8996_GP5_OP_CFG 0x0200 /* GP5_OP_CFG */ +#define WM8996_GP5_OP_CFG_MASK 0x0200 /* GP5_OP_CFG */ +#define WM8996_GP5_OP_CFG_SHIFT 9 /* GP5_OP_CFG */ +#define WM8996_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8996_GP5_DB 0x0100 /* GP5_DB */ +#define WM8996_GP5_DB_MASK 0x0100 /* GP5_DB */ +#define WM8996_GP5_DB_SHIFT 8 /* GP5_DB */ +#define WM8996_GP5_DB_WIDTH 1 /* GP5_DB */ +#define WM8996_GP5_LVL 0x0040 /* GP5_LVL */ +#define WM8996_GP5_LVL_MASK 0x0040 /* GP5_LVL */ +#define WM8996_GP5_LVL_SHIFT 6 /* GP5_LVL */ +#define WM8996_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8996_GP5_FN_MASK 0x000F /* GP5_FN - [3:0] */ +#define WM8996_GP5_FN_SHIFT 0 /* GP5_FN - [3:0] */ +#define WM8996_GP5_FN_WIDTH 4 /* GP5_FN - [3:0] */ + +/* + * R1824 (0x720) - Pull Control (1) + */ +#define WM8996_DMICDAT2_PD 0x1000 /* DMICDAT2_PD */ +#define WM8996_DMICDAT2_PD_MASK 0x1000 /* DMICDAT2_PD */ +#define WM8996_DMICDAT2_PD_SHIFT 12 /* DMICDAT2_PD */ +#define WM8996_DMICDAT2_PD_WIDTH 1 /* DMICDAT2_PD */ +#define WM8996_DMICDAT1_PD 0x0400 /* DMICDAT1_PD */ +#define WM8996_DMICDAT1_PD_MASK 0x0400 /* DMICDAT1_PD */ +#define WM8996_DMICDAT1_PD_SHIFT 10 /* DMICDAT1_PD */ +#define WM8996_DMICDAT1_PD_WIDTH 1 /* DMICDAT1_PD */ +#define WM8996_MCLK2_PU 0x0200 /* MCLK2_PU */ +#define WM8996_MCLK2_PU_MASK 0x0200 /* MCLK2_PU */ +#define WM8996_MCLK2_PU_SHIFT 9 /* MCLK2_PU */ +#define WM8996_MCLK2_PU_WIDTH 1 /* MCLK2_PU */ +#define WM8996_MCLK2_PD 0x0100 /* MCLK2_PD */ +#define WM8996_MCLK2_PD_MASK 0x0100 /* MCLK2_PD */ +#define WM8996_MCLK2_PD_SHIFT 8 /* MCLK2_PD */ +#define WM8996_MCLK2_PD_WIDTH 1 /* MCLK2_PD */ +#define WM8996_MCLK1_PU 0x0080 /* MCLK1_PU */ +#define WM8996_MCLK1_PU_MASK 0x0080 /* MCLK1_PU */ +#define WM8996_MCLK1_PU_SHIFT 7 /* MCLK1_PU */ +#define WM8996_MCLK1_PU_WIDTH 1 /* MCLK1_PU */ +#define WM8996_MCLK1_PD 0x0040 /* MCLK1_PD */ +#define WM8996_MCLK1_PD_MASK 0x0040 /* MCLK1_PD */ +#define WM8996_MCLK1_PD_SHIFT 6 /* MCLK1_PD */ +#define WM8996_MCLK1_PD_WIDTH 1 /* MCLK1_PD */ +#define WM8996_DACDAT1_PU 0x0020 /* DACDAT1_PU */ +#define WM8996_DACDAT1_PU_MASK 0x0020 /* DACDAT1_PU */ +#define WM8996_DACDAT1_PU_SHIFT 5 /* DACDAT1_PU */ +#define WM8996_DACDAT1_PU_WIDTH 1 /* DACDAT1_PU */ +#define WM8996_DACDAT1_PD 0x0010 /* DACDAT1_PD */ +#define WM8996_DACDAT1_PD_MASK 0x0010 /* DACDAT1_PD */ +#define WM8996_DACDAT1_PD_SHIFT 4 /* DACDAT1_PD */ +#define WM8996_DACDAT1_PD_WIDTH 1 /* DACDAT1_PD */ +#define WM8996_DACLRCLK1_PU 0x0008 /* DACLRCLK1_PU */ +#define WM8996_DACLRCLK1_PU_MASK 0x0008 /* DACLRCLK1_PU */ +#define WM8996_DACLRCLK1_PU_SHIFT 3 /* DACLRCLK1_PU */ +#define WM8996_DACLRCLK1_PU_WIDTH 1 /* DACLRCLK1_PU */ +#define WM8996_DACLRCLK1_PD 0x0004 /* DACLRCLK1_PD */ +#define WM8996_DACLRCLK1_PD_MASK 0x0004 /* DACLRCLK1_PD */ +#define WM8996_DACLRCLK1_PD_SHIFT 2 /* DACLRCLK1_PD */ +#define WM8996_DACLRCLK1_PD_WIDTH 1 /* DACLRCLK1_PD */ +#define WM8996_BCLK1_PU 0x0002 /* BCLK1_PU */ +#define WM8996_BCLK1_PU_MASK 0x0002 /* BCLK1_PU */ +#define WM8996_BCLK1_PU_SHIFT 1 /* BCLK1_PU */ +#define WM8996_BCLK1_PU_WIDTH 1 /* BCLK1_PU */ +#define WM8996_BCLK1_PD 0x0001 /* BCLK1_PD */ +#define WM8996_BCLK1_PD_MASK 0x0001 /* BCLK1_PD */ +#define WM8996_BCLK1_PD_SHIFT 0 /* BCLK1_PD */ +#define WM8996_BCLK1_PD_WIDTH 1 /* BCLK1_PD */ + +/* + * R1825 (0x721) - Pull Control (2) + */ +#define WM8996_LDO1ENA_PD 0x0100 /* LDO1ENA_PD */ +#define WM8996_LDO1ENA_PD_MASK 0x0100 /* LDO1ENA_PD */ +#define WM8996_LDO1ENA_PD_SHIFT 8 /* LDO1ENA_PD */ +#define WM8996_LDO1ENA_PD_WIDTH 1 /* LDO1ENA_PD */ +#define WM8996_ADDR_PD 0x0040 /* ADDR_PD */ +#define WM8996_ADDR_PD_MASK 0x0040 /* ADDR_PD */ +#define WM8996_ADDR_PD_SHIFT 6 /* ADDR_PD */ +#define WM8996_ADDR_PD_WIDTH 1 /* ADDR_PD */ +#define WM8996_DACDAT2_PU 0x0020 /* DACDAT2_PU */ +#define WM8996_DACDAT2_PU_MASK 0x0020 /* DACDAT2_PU */ +#define WM8996_DACDAT2_PU_SHIFT 5 /* DACDAT2_PU */ +#define WM8996_DACDAT2_PU_WIDTH 1 /* DACDAT2_PU */ +#define WM8996_DACDAT2_PD 0x0010 /* DACDAT2_PD */ +#define WM8996_DACDAT2_PD_MASK 0x0010 /* DACDAT2_PD */ +#define WM8996_DACDAT2_PD_SHIFT 4 /* DACDAT2_PD */ +#define WM8996_DACDAT2_PD_WIDTH 1 /* DACDAT2_PD */ +#define WM8996_DACLRCLK2_PU 0x0008 /* DACLRCLK2_PU */ +#define WM8996_DACLRCLK2_PU_MASK 0x0008 /* DACLRCLK2_PU */ +#define WM8996_DACLRCLK2_PU_SHIFT 3 /* DACLRCLK2_PU */ +#define WM8996_DACLRCLK2_PU_WIDTH 1 /* DACLRCLK2_PU */ +#define WM8996_DACLRCLK2_PD 0x0004 /* DACLRCLK2_PD */ +#define WM8996_DACLRCLK2_PD_MASK 0x0004 /* DACLRCLK2_PD */ +#define WM8996_DACLRCLK2_PD_SHIFT 2 /* DACLRCLK2_PD */ +#define WM8996_DACLRCLK2_PD_WIDTH 1 /* DACLRCLK2_PD */ +#define WM8996_BCLK2_PU 0x0002 /* BCLK2_PU */ +#define WM8996_BCLK2_PU_MASK 0x0002 /* BCLK2_PU */ +#define WM8996_BCLK2_PU_SHIFT 1 /* BCLK2_PU */ +#define WM8996_BCLK2_PU_WIDTH 1 /* BCLK2_PU */ +#define WM8996_BCLK2_PD 0x0001 /* BCLK2_PD */ +#define WM8996_BCLK2_PD_MASK 0x0001 /* BCLK2_PD */ +#define WM8996_BCLK2_PD_SHIFT 0 /* BCLK2_PD */ +#define WM8996_BCLK2_PD_WIDTH 1 /* BCLK2_PD */ + +/* + * R1840 (0x730) - Interrupt Status 1 + */ +#define WM8996_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM8996_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM8996_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM8996_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM8996_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM8996_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM8996_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM8996_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM8996_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM8996_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM8996_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM8996_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM8996_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM8996_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM8996_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM8996_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM8996_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM8996_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM8996_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM8996_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R1841 (0x731) - Interrupt Status 2 + */ +#define WM8996_DCS_DONE_23_EINT 0x1000 /* DCS_DONE_23_EINT */ +#define WM8996_DCS_DONE_23_EINT_MASK 0x1000 /* DCS_DONE_23_EINT */ +#define WM8996_DCS_DONE_23_EINT_SHIFT 12 /* DCS_DONE_23_EINT */ +#define WM8996_DCS_DONE_23_EINT_WIDTH 1 /* DCS_DONE_23_EINT */ +#define WM8996_DCS_DONE_01_EINT 0x0800 /* DCS_DONE_01_EINT */ +#define WM8996_DCS_DONE_01_EINT_MASK 0x0800 /* DCS_DONE_01_EINT */ +#define WM8996_DCS_DONE_01_EINT_SHIFT 11 /* DCS_DONE_01_EINT */ +#define WM8996_DCS_DONE_01_EINT_WIDTH 1 /* DCS_DONE_01_EINT */ +#define WM8996_WSEQ_DONE_EINT 0x0400 /* WSEQ_DONE_EINT */ +#define WM8996_WSEQ_DONE_EINT_MASK 0x0400 /* WSEQ_DONE_EINT */ +#define WM8996_WSEQ_DONE_EINT_SHIFT 10 /* WSEQ_DONE_EINT */ +#define WM8996_WSEQ_DONE_EINT_WIDTH 1 /* WSEQ_DONE_EINT */ +#define WM8996_FIFOS_ERR_EINT 0x0200 /* FIFOS_ERR_EINT */ +#define WM8996_FIFOS_ERR_EINT_MASK 0x0200 /* FIFOS_ERR_EINT */ +#define WM8996_FIFOS_ERR_EINT_SHIFT 9 /* FIFOS_ERR_EINT */ +#define WM8996_FIFOS_ERR_EINT_WIDTH 1 /* FIFOS_ERR_EINT */ +#define WM8996_DSP2DRC_SIG_DET_EINT 0x0080 /* DSP2DRC_SIG_DET_EINT */ +#define WM8996_DSP2DRC_SIG_DET_EINT_MASK 0x0080 /* DSP2DRC_SIG_DET_EINT */ +#define WM8996_DSP2DRC_SIG_DET_EINT_SHIFT 7 /* DSP2DRC_SIG_DET_EINT */ +#define WM8996_DSP2DRC_SIG_DET_EINT_WIDTH 1 /* DSP2DRC_SIG_DET_EINT */ +#define WM8996_DSP1DRC_SIG_DET_EINT 0x0040 /* DSP1DRC_SIG_DET_EINT */ +#define WM8996_DSP1DRC_SIG_DET_EINT_MASK 0x0040 /* DSP1DRC_SIG_DET_EINT */ +#define WM8996_DSP1DRC_SIG_DET_EINT_SHIFT 6 /* DSP1DRC_SIG_DET_EINT */ +#define WM8996_DSP1DRC_SIG_DET_EINT_WIDTH 1 /* DSP1DRC_SIG_DET_EINT */ +#define WM8996_FLL_SW_CLK_DONE_EINT 0x0008 /* FLL_SW_CLK_DONE_EINT */ +#define WM8996_FLL_SW_CLK_DONE_EINT_MASK 0x0008 /* FLL_SW_CLK_DONE_EINT */ +#define WM8996_FLL_SW_CLK_DONE_EINT_SHIFT 3 /* FLL_SW_CLK_DONE_EINT */ +#define WM8996_FLL_SW_CLK_DONE_EINT_WIDTH 1 /* FLL_SW_CLK_DONE_EINT */ +#define WM8996_FLL_LOCK_EINT 0x0004 /* FLL_LOCK_EINT */ +#define WM8996_FLL_LOCK_EINT_MASK 0x0004 /* FLL_LOCK_EINT */ +#define WM8996_FLL_LOCK_EINT_SHIFT 2 /* FLL_LOCK_EINT */ +#define WM8996_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */ +#define WM8996_HP_DONE_EINT 0x0002 /* HP_DONE_EINT */ +#define WM8996_HP_DONE_EINT_MASK 0x0002 /* HP_DONE_EINT */ +#define WM8996_HP_DONE_EINT_SHIFT 1 /* HP_DONE_EINT */ +#define WM8996_HP_DONE_EINT_WIDTH 1 /* HP_DONE_EINT */ +#define WM8996_MICD_EINT 0x0001 /* MICD_EINT */ +#define WM8996_MICD_EINT_MASK 0x0001 /* MICD_EINT */ +#define WM8996_MICD_EINT_SHIFT 0 /* MICD_EINT */ +#define WM8996_MICD_EINT_WIDTH 1 /* MICD_EINT */ + +/* + * R1842 (0x732) - Interrupt Raw Status 2 + */ +#define WM8996_DCS_DONE_23_STS 0x1000 /* DCS_DONE_23_STS */ +#define WM8996_DCS_DONE_23_STS_MASK 0x1000 /* DCS_DONE_23_STS */ +#define WM8996_DCS_DONE_23_STS_SHIFT 12 /* DCS_DONE_23_STS */ +#define WM8996_DCS_DONE_23_STS_WIDTH 1 /* DCS_DONE_23_STS */ +#define WM8996_DCS_DONE_01_STS 0x0800 /* DCS_DONE_01_STS */ +#define WM8996_DCS_DONE_01_STS_MASK 0x0800 /* DCS_DONE_01_STS */ +#define WM8996_DCS_DONE_01_STS_SHIFT 11 /* DCS_DONE_01_STS */ +#define WM8996_DCS_DONE_01_STS_WIDTH 1 /* DCS_DONE_01_STS */ +#define WM8996_WSEQ_DONE_STS 0x0400 /* WSEQ_DONE_STS */ +#define WM8996_WSEQ_DONE_STS_MASK 0x0400 /* WSEQ_DONE_STS */ +#define WM8996_WSEQ_DONE_STS_SHIFT 10 /* WSEQ_DONE_STS */ +#define WM8996_WSEQ_DONE_STS_WIDTH 1 /* WSEQ_DONE_STS */ +#define WM8996_FIFOS_ERR_STS 0x0200 /* FIFOS_ERR_STS */ +#define WM8996_FIFOS_ERR_STS_MASK 0x0200 /* FIFOS_ERR_STS */ +#define WM8996_FIFOS_ERR_STS_SHIFT 9 /* FIFOS_ERR_STS */ +#define WM8996_FIFOS_ERR_STS_WIDTH 1 /* FIFOS_ERR_STS */ +#define WM8996_DSP2DRC_SIG_DET_STS 0x0080 /* DSP2DRC_SIG_DET_STS */ +#define WM8996_DSP2DRC_SIG_DET_STS_MASK 0x0080 /* DSP2DRC_SIG_DET_STS */ +#define WM8996_DSP2DRC_SIG_DET_STS_SHIFT 7 /* DSP2DRC_SIG_DET_STS */ +#define WM8996_DSP2DRC_SIG_DET_STS_WIDTH 1 /* DSP2DRC_SIG_DET_STS */ +#define WM8996_DSP1DRC_SIG_DET_STS 0x0040 /* DSP1DRC_SIG_DET_STS */ +#define WM8996_DSP1DRC_SIG_DET_STS_MASK 0x0040 /* DSP1DRC_SIG_DET_STS */ +#define WM8996_DSP1DRC_SIG_DET_STS_SHIFT 6 /* DSP1DRC_SIG_DET_STS */ +#define WM8996_DSP1DRC_SIG_DET_STS_WIDTH 1 /* DSP1DRC_SIG_DET_STS */ +#define WM8996_FLL_LOCK_STS 0x0004 /* FLL_LOCK_STS */ +#define WM8996_FLL_LOCK_STS_MASK 0x0004 /* FLL_LOCK_STS */ +#define WM8996_FLL_LOCK_STS_SHIFT 2 /* FLL_LOCK_STS */ +#define WM8996_FLL_LOCK_STS_WIDTH 1 /* FLL_LOCK_STS */ + +/* + * R1848 (0x738) - Interrupt Status 1 Mask + */ +#define WM8996_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM8996_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM8996_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM8996_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM8996_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM8996_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM8996_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM8996_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM8996_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM8996_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM8996_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM8996_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM8996_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM8996_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM8996_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM8996_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM8996_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM8996_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM8996_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM8996_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R1849 (0x739) - Interrupt Status 2 Mask + */ +#define WM8996_IM_DCS_DONE_23_EINT 0x1000 /* IM_DCS_DONE_23_EINT */ +#define WM8996_IM_DCS_DONE_23_EINT_MASK 0x1000 /* IM_DCS_DONE_23_EINT */ +#define WM8996_IM_DCS_DONE_23_EINT_SHIFT 12 /* IM_DCS_DONE_23_EINT */ +#define WM8996_IM_DCS_DONE_23_EINT_WIDTH 1 /* IM_DCS_DONE_23_EINT */ +#define WM8996_IM_DCS_DONE_01_EINT 0x0800 /* IM_DCS_DONE_01_EINT */ +#define WM8996_IM_DCS_DONE_01_EINT_MASK 0x0800 /* IM_DCS_DONE_01_EINT */ +#define WM8996_IM_DCS_DONE_01_EINT_SHIFT 11 /* IM_DCS_DONE_01_EINT */ +#define WM8996_IM_DCS_DONE_01_EINT_WIDTH 1 /* IM_DCS_DONE_01_EINT */ +#define WM8996_IM_WSEQ_DONE_EINT 0x0400 /* IM_WSEQ_DONE_EINT */ +#define WM8996_IM_WSEQ_DONE_EINT_MASK 0x0400 /* IM_WSEQ_DONE_EINT */ +#define WM8996_IM_WSEQ_DONE_EINT_SHIFT 10 /* IM_WSEQ_DONE_EINT */ +#define WM8996_IM_WSEQ_DONE_EINT_WIDTH 1 /* IM_WSEQ_DONE_EINT */ +#define WM8996_IM_FIFOS_ERR_EINT 0x0200 /* IM_FIFOS_ERR_EINT */ +#define WM8996_IM_FIFOS_ERR_EINT_MASK 0x0200 /* IM_FIFOS_ERR_EINT */ +#define WM8996_IM_FIFOS_ERR_EINT_SHIFT 9 /* IM_FIFOS_ERR_EINT */ +#define WM8996_IM_FIFOS_ERR_EINT_WIDTH 1 /* IM_FIFOS_ERR_EINT */ +#define WM8996_IM_DSP2DRC_SIG_DET_EINT 0x0080 /* IM_DSP2DRC_SIG_DET_EINT */ +#define WM8996_IM_DSP2DRC_SIG_DET_EINT_MASK 0x0080 /* IM_DSP2DRC_SIG_DET_EINT */ +#define WM8996_IM_DSP2DRC_SIG_DET_EINT_SHIFT 7 /* IM_DSP2DRC_SIG_DET_EINT */ +#define WM8996_IM_DSP2DRC_SIG_DET_EINT_WIDTH 1 /* IM_DSP2DRC_SIG_DET_EINT */ +#define WM8996_IM_DSP1DRC_SIG_DET_EINT 0x0040 /* IM_DSP1DRC_SIG_DET_EINT */ +#define WM8996_IM_DSP1DRC_SIG_DET_EINT_MASK 0x0040 /* IM_DSP1DRC_SIG_DET_EINT */ +#define WM8996_IM_DSP1DRC_SIG_DET_EINT_SHIFT 6 /* IM_DSP1DRC_SIG_DET_EINT */ +#define WM8996_IM_DSP1DRC_SIG_DET_EINT_WIDTH 1 /* IM_DSP1DRC_SIG_DET_EINT */ +#define WM8996_IM_FLL_SW_CLK_DONE_EINT 0x0008 /* IM_FLL_SW_CLK_DONE_EINT */ +#define WM8996_IM_FLL_SW_CLK_DONE_EINT_MASK 0x0008 /* IM_FLL_SW_CLK_DONE_EINT */ +#define WM8996_IM_FLL_SW_CLK_DONE_EINT_SHIFT 3 /* IM_FLL_SW_CLK_DONE_EINT */ +#define WM8996_IM_FLL_SW_CLK_DONE_EINT_WIDTH 1 /* IM_FLL_SW_CLK_DONE_EINT */ +#define WM8996_IM_FLL_LOCK_EINT 0x0004 /* IM_FLL_LOCK_EINT */ +#define WM8996_IM_FLL_LOCK_EINT_MASK 0x0004 /* IM_FLL_LOCK_EINT */ +#define WM8996_IM_FLL_LOCK_EINT_SHIFT 2 /* IM_FLL_LOCK_EINT */ +#define WM8996_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */ +#define WM8996_IM_HP_DONE_EINT 0x0002 /* IM_HP_DONE_EINT */ +#define WM8996_IM_HP_DONE_EINT_MASK 0x0002 /* IM_HP_DONE_EINT */ +#define WM8996_IM_HP_DONE_EINT_SHIFT 1 /* IM_HP_DONE_EINT */ +#define WM8996_IM_HP_DONE_EINT_WIDTH 1 /* IM_HP_DONE_EINT */ +#define WM8996_IM_MICD_EINT 0x0001 /* IM_MICD_EINT */ +#define WM8996_IM_MICD_EINT_MASK 0x0001 /* IM_MICD_EINT */ +#define WM8996_IM_MICD_EINT_SHIFT 0 /* IM_MICD_EINT */ +#define WM8996_IM_MICD_EINT_WIDTH 1 /* IM_MICD_EINT */ + +/* + * R1856 (0x740) - Interrupt Control + */ +#define WM8996_IM_IRQ 0x0001 /* IM_IRQ */ +#define WM8996_IM_IRQ_MASK 0x0001 /* IM_IRQ */ +#define WM8996_IM_IRQ_SHIFT 0 /* IM_IRQ */ +#define WM8996_IM_IRQ_WIDTH 1 /* IM_IRQ */ + +/* + * R2048 (0x800) - Left PDM Speaker + */ +#define WM8996_SPKL_ENA 0x0010 /* SPKL_ENA */ +#define WM8996_SPKL_ENA_MASK 0x0010 /* SPKL_ENA */ +#define WM8996_SPKL_ENA_SHIFT 4 /* SPKL_ENA */ +#define WM8996_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ +#define WM8996_SPKL_MUTE 0x0008 /* SPKL_MUTE */ +#define WM8996_SPKL_MUTE_MASK 0x0008 /* SPKL_MUTE */ +#define WM8996_SPKL_MUTE_SHIFT 3 /* SPKL_MUTE */ +#define WM8996_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */ +#define WM8996_SPKL_MUTE_ZC 0x0004 /* SPKL_MUTE_ZC */ +#define WM8996_SPKL_MUTE_ZC_MASK 0x0004 /* SPKL_MUTE_ZC */ +#define WM8996_SPKL_MUTE_ZC_SHIFT 2 /* SPKL_MUTE_ZC */ +#define WM8996_SPKL_MUTE_ZC_WIDTH 1 /* SPKL_MUTE_ZC */ +#define WM8996_SPKL_SRC_MASK 0x0003 /* SPKL_SRC - [1:0] */ +#define WM8996_SPKL_SRC_SHIFT 0 /* SPKL_SRC - [1:0] */ +#define WM8996_SPKL_SRC_WIDTH 2 /* SPKL_SRC - [1:0] */ + +/* + * R2049 (0x801) - Right PDM Speaker + */ +#define WM8996_SPKR_ENA 0x0010 /* SPKR_ENA */ +#define WM8996_SPKR_ENA_MASK 0x0010 /* SPKR_ENA */ +#define WM8996_SPKR_ENA_SHIFT 4 /* SPKR_ENA */ +#define WM8996_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ +#define WM8996_SPKR_MUTE 0x0008 /* SPKR_MUTE */ +#define WM8996_SPKR_MUTE_MASK 0x0008 /* SPKR_MUTE */ +#define WM8996_SPKR_MUTE_SHIFT 3 /* SPKR_MUTE */ +#define WM8996_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */ +#define WM8996_SPKR_MUTE_ZC 0x0004 /* SPKR_MUTE_ZC */ +#define WM8996_SPKR_MUTE_ZC_MASK 0x0004 /* SPKR_MUTE_ZC */ +#define WM8996_SPKR_MUTE_ZC_SHIFT 2 /* SPKR_MUTE_ZC */ +#define WM8996_SPKR_MUTE_ZC_WIDTH 1 /* SPKR_MUTE_ZC */ +#define WM8996_SPKR_SRC_MASK 0x0003 /* SPKR_SRC - [1:0] */ +#define WM8996_SPKR_SRC_SHIFT 0 /* SPKR_SRC - [1:0] */ +#define WM8996_SPKR_SRC_WIDTH 2 /* SPKR_SRC - [1:0] */ + +/* + * R2050 (0x802) - PDM Speaker Mute Sequence + */ +#define WM8996_SPK_MUTE_ENDIAN 0x0100 /* SPK_MUTE_ENDIAN */ +#define WM8996_SPK_MUTE_ENDIAN_MASK 0x0100 /* SPK_MUTE_ENDIAN */ +#define WM8996_SPK_MUTE_ENDIAN_SHIFT 8 /* SPK_MUTE_ENDIAN */ +#define WM8996_SPK_MUTE_ENDIAN_WIDTH 1 /* SPK_MUTE_ENDIAN */ +#define WM8996_SPK_MUTE_SEQ1_MASK 0x00FF /* SPK_MUTE_SEQ1 - [7:0] */ +#define WM8996_SPK_MUTE_SEQ1_SHIFT 0 /* SPK_MUTE_SEQ1 - [7:0] */ +#define WM8996_SPK_MUTE_SEQ1_WIDTH 8 /* SPK_MUTE_SEQ1 - [7:0] */ + +/* + * R2051 (0x803) - PDM Speaker Volume + */ +#define WM8996_SPKR_VOL_MASK 0x00F0 /* SPKR_VOL - [7:4] */ +#define WM8996_SPKR_VOL_SHIFT 4 /* SPKR_VOL - [7:4] */ +#define WM8996_SPKR_VOL_WIDTH 4 /* SPKR_VOL - [7:4] */ +#define WM8996_SPKL_VOL_MASK 0x000F /* SPKL_VOL - [3:0] */ +#define WM8996_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [3:0] */ +#define WM8996_SPKL_VOL_WIDTH 4 /* SPKL_VOL - [3:0] */ + +#endif diff --git a/sound/soc/codecs/wm8997.c b/sound/soc/codecs/wm8997.c new file mode 100644 index 000000000..bb6a95be8 --- /dev/null +++ b/sound/soc/codecs/wm8997.c @@ -0,0 +1,1216 @@ +/* + * wm8997.c -- WM8997 ALSA SoC Audio driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Charles Keepax + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arizona.h" +#include "wm8997.h" + +struct wm8997_priv { + struct arizona_priv core; + struct arizona_fll fll[2]; +}; + +static DECLARE_TLV_DB_SCALE(ana_tlv, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); +static DECLARE_TLV_DB_SCALE(noise_tlv, -13200, 600, 0); +static DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); + +static const struct reg_default wm8997_sysclk_reva_patch[] = { + { 0x301D, 0x7B15 }, + { 0x301B, 0x0050 }, + { 0x305D, 0x7B17 }, + { 0x305B, 0x0050 }, + { 0x3001, 0x08FE }, + { 0x3003, 0x00F4 }, + { 0x3041, 0x08FF }, + { 0x3043, 0x0005 }, + { 0x3020, 0x0225 }, + { 0x3021, 0x0A00 }, + { 0x3022, 0xE24D }, + { 0x3023, 0x0800 }, + { 0x3024, 0xE24D }, + { 0x3025, 0xF000 }, + { 0x3060, 0x0226 }, + { 0x3061, 0x0A00 }, + { 0x3062, 0xE252 }, + { 0x3063, 0x0800 }, + { 0x3064, 0xE252 }, + { 0x3065, 0xF000 }, + { 0x3116, 0x022B }, + { 0x3117, 0xFA00 }, + { 0x3110, 0x246C }, + { 0x3111, 0x0A03 }, + { 0x3112, 0x246E }, + { 0x3113, 0x0A03 }, + { 0x3114, 0x2470 }, + { 0x3115, 0x0A03 }, + { 0x3126, 0x246C }, + { 0x3127, 0x0A02 }, + { 0x3128, 0x246E }, + { 0x3129, 0x0A02 }, + { 0x312A, 0x2470 }, + { 0x312B, 0xFA02 }, + { 0x3125, 0x0800 }, +}; + +static int wm8997_sysclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct regmap *regmap = arizona->regmap; + const struct reg_default *patch = NULL; + int i, patch_size; + + switch (arizona->rev) { + case 0: + patch = wm8997_sysclk_reva_patch; + patch_size = ARRAY_SIZE(wm8997_sysclk_reva_patch); + break; + default: + break; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (patch) + for (i = 0; i < patch_size; i++) + regmap_write_async(regmap, patch[i].reg, + patch[i].def); + break; + case SND_SOC_DAPM_PRE_PMD: + break; + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_POST_PMD: + return arizona_clk_ev(w, kcontrol, event); + default: + return 0; + } + + return arizona_dvfs_sysclk_ev(w, kcontrol, event); +} + +static const char * const wm8997_osr_text[] = { + "Low power", "Normal", "High performance", +}; + +static const unsigned int wm8997_osr_val[] = { + 0x0, 0x3, 0x5, +}; + +static const struct soc_enum wm8997_hpout_osr[] = { + SOC_VALUE_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_1L, + ARIZONA_OUT1_OSR_SHIFT, 0x7, + ARRAY_SIZE(wm8997_osr_text), + wm8997_osr_text, wm8997_osr_val), + SOC_VALUE_ENUM_SINGLE(ARIZONA_OUTPUT_PATH_CONFIG_3L, + ARIZONA_OUT3_OSR_SHIFT, 0x7, + ARRAY_SIZE(wm8997_osr_text), + wm8997_osr_text, wm8997_osr_val), +}; + +#define WM8997_NG_SRC(name, base) \ + SOC_SINGLE(name " NG HPOUT1L Switch", base, 0, 1, 0), \ + SOC_SINGLE(name " NG HPOUT1R Switch", base, 1, 1, 0), \ + SOC_SINGLE(name " NG EPOUT Switch", base, 4, 1, 0), \ + SOC_SINGLE(name " NG SPKOUT Switch", base, 6, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT1L Switch", base, 8, 1, 0), \ + SOC_SINGLE(name " NG SPKDAT1R Switch", base, 9, 1, 0) + +static const struct snd_kcontrol_new wm8997_snd_controls[] = { +SOC_SINGLE("IN1 High Performance Switch", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1_OSR_SHIFT, 1, 0), +SOC_SINGLE("IN2 High Performance Switch", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2_OSR_SHIFT, 1, 0), + +SOC_SINGLE_RANGE_TLV("IN1L Volume", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN1R Volume", ARIZONA_IN1R_CONTROL, + ARIZONA_IN1R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN2L Volume", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN2R Volume", ARIZONA_IN2R_CONTROL, + ARIZONA_IN2R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), + +SOC_SINGLE_TLV("IN1L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1L, + ARIZONA_IN1L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN1R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1R, + ARIZONA_IN1R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2L, + ARIZONA_IN2L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2R, + ARIZONA_IN2R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), + +SOC_ENUM("Input Ramp Up", arizona_in_vi_ramp), +SOC_ENUM("Input Ramp Down", arizona_in_vd_ramp), + +ARIZONA_MIXER_CONTROLS("EQ1", ARIZONA_EQ1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ2", ARIZONA_EQ2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ3", ARIZONA_EQ3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EQ4", ARIZONA_EQ4MIX_INPUT_1_SOURCE), + +ARIZONA_EQ_CONTROL("EQ1 Coefficients", ARIZONA_EQ1_2), +SOC_SINGLE_TLV("EQ1 B1 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B2 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B3 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B4 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B5 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ2 Coefficients", ARIZONA_EQ2_2), +SOC_SINGLE_TLV("EQ2 B1 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B2 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B3 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B4 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B5 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ3 Coefficients", ARIZONA_EQ3_2), +SOC_SINGLE_TLV("EQ3 B1 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B2 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B3 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B4 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B5 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_EQ_CONTROL("EQ4 Coefficients", ARIZONA_EQ4_2), +SOC_SINGLE_TLV("EQ4 B1 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B2 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B3 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B4 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B5 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_MIXER_CONTROLS("DRC1L", ARIZONA_DRC1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("DRC1R", ARIZONA_DRC1RMIX_INPUT_1_SOURCE), + +SND_SOC_BYTES_MASK("DRC1", ARIZONA_DRC1_CTRL1, 5, + ARIZONA_DRC1R_ENA | ARIZONA_DRC1L_ENA), + +ARIZONA_MIXER_CONTROLS("LHPF1", ARIZONA_HPLP1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF2", ARIZONA_HPLP2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF3", ARIZONA_HPLP3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF4", ARIZONA_HPLP4MIX_INPUT_1_SOURCE), + +SOC_ENUM("LHPF1 Mode", arizona_lhpf1_mode), +SOC_ENUM("LHPF2 Mode", arizona_lhpf2_mode), +SOC_ENUM("LHPF3 Mode", arizona_lhpf3_mode), +SOC_ENUM("LHPF4 Mode", arizona_lhpf4_mode), + +ARIZONA_LHPF_CONTROL("LHPF1 Coefficients", ARIZONA_HPLPF1_2), +ARIZONA_LHPF_CONTROL("LHPF2 Coefficients", ARIZONA_HPLPF2_2), +ARIZONA_LHPF_CONTROL("LHPF3 Coefficients", ARIZONA_HPLPF3_2), +ARIZONA_LHPF_CONTROL("LHPF4 Coefficients", ARIZONA_HPLPF4_2), + +SOC_ENUM("ISRC1 FSL", arizona_isrc_fsl[0]), +SOC_ENUM("ISRC2 FSL", arizona_isrc_fsl[1]), + +ARIZONA_MIXER_CONTROLS("Mic", ARIZONA_MICMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("Noise", ARIZONA_NOISEMIX_INPUT_1_SOURCE), + +SOC_SINGLE_TLV("Noise Generator Volume", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_GAIN_SHIFT, 0x16, 0, noise_tlv), + +ARIZONA_MIXER_CONTROLS("HPOUT1L", ARIZONA_OUT1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUT1R", ARIZONA_OUT1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EPOUT", ARIZONA_OUT3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUT", ARIZONA_OUT4LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT1L", ARIZONA_OUT5LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDAT1R", ARIZONA_OUT5RMIX_INPUT_1_SOURCE), + +SOC_SINGLE("Speaker High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_4L, + ARIZONA_OUT4_OSR_SHIFT, 1, 0), +SOC_SINGLE("SPKDAT1 High Performance Switch", ARIZONA_OUTPUT_PATH_CONFIG_5L, + ARIZONA_OUT5_OSR_SHIFT, 1, 0), + +SOC_DOUBLE_R("HPOUT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_MUTE_SHIFT, 1, 1), +SOC_SINGLE("EPOUT Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_OUT3L_MUTE_SHIFT, 1, 1), +SOC_SINGLE("Speaker Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_OUT4L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKDAT1 Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_MUTE_SHIFT, 1, 1), + +SOC_DOUBLE_R_TLV("HPOUT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("EPOUT Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_OUT3L_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("Speaker Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_OUT4L_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("SPKDAT1 Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_VOL_SHIFT, + 0xbf, 0, digital_tlv), + +SOC_ENUM("HPOUT1 OSR", wm8997_hpout_osr[0]), +SOC_ENUM("EPOUT OSR", wm8997_hpout_osr[1]), + +SOC_ENUM("Output Ramp Up", arizona_out_vi_ramp), +SOC_ENUM("Output Ramp Down", arizona_out_vd_ramp), + +SOC_DOUBLE("SPKDAT1 Switch", ARIZONA_PDM_SPK1_CTRL_1, ARIZONA_SPK1L_MUTE_SHIFT, + ARIZONA_SPK1R_MUTE_SHIFT, 1, 1), + +SOC_SINGLE("Noise Gate Switch", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_ENA_SHIFT, 1, 0), +SOC_SINGLE_TLV("Noise Gate Threshold Volume", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_THR_SHIFT, 7, 1, ng_tlv), +SOC_ENUM("Noise Gate Hold", arizona_ng_hold), + +WM8997_NG_SRC("HPOUT1L", ARIZONA_NOISE_GATE_SELECT_1L), +WM8997_NG_SRC("HPOUT1R", ARIZONA_NOISE_GATE_SELECT_1R), +WM8997_NG_SRC("EPOUT", ARIZONA_NOISE_GATE_SELECT_3L), +WM8997_NG_SRC("SPKOUT", ARIZONA_NOISE_GATE_SELECT_4L), +WM8997_NG_SRC("SPKDAT1L", ARIZONA_NOISE_GATE_SELECT_5L), +WM8997_NG_SRC("SPKDAT1R", ARIZONA_NOISE_GATE_SELECT_5R), + +ARIZONA_MIXER_CONTROLS("AIF1TX1", ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX2", ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX3", ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX4", ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX5", ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX6", ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX7", ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX8", ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF2TX1", ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX2", ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("SLIMTX1", ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX2", ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX3", ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX4", ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX5", ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX6", ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX7", ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SLIMTX8", ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE), +}; + +ARIZONA_MIXER_ENUMS(EQ1, ARIZONA_EQ1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ2, ARIZONA_EQ2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ3, ARIZONA_EQ3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(EQ4, ARIZONA_EQ4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(DRC1L, ARIZONA_DRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(DRC1R, ARIZONA_DRC1RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(LHPF1, ARIZONA_HPLP1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF2, ARIZONA_HPLP2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF3, ARIZONA_HPLP3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF4, ARIZONA_HPLP4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(Mic, ARIZONA_MICMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(Noise, ARIZONA_NOISEMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(PWM1, ARIZONA_PWM1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(PWM2, ARIZONA_PWM2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(OUT1L, ARIZONA_OUT1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT1R, ARIZONA_OUT1RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT3, ARIZONA_OUT3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUT, ARIZONA_OUT4LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT1L, ARIZONA_OUT5LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDAT1R, ARIZONA_OUT5RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF1TX1, ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX2, ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX3, ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX4, ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX5, ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX6, ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX7, ARIZONA_AIF1TX7MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX8, ARIZONA_AIF1TX8MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF2TX1, ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX2, ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(SLIMTX1, ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX2, ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX3, ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX4, ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX5, ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX6, ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX7, ARIZONA_SLIMTX7MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SLIMTX8, ARIZONA_SLIMTX8MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1INT1, ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT2, ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1DEC1, ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC2, ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2INT1, ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT2, ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2DEC1, ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC2, ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE); + +static const char *wm8997_aec_loopback_texts[] = { + "HPOUT1L", "HPOUT1R", "EPOUT", "SPKOUT", "SPKDAT1L", "SPKDAT1R", +}; + +static const unsigned int wm8997_aec_loopback_values[] = { + 0, 1, 4, 6, 8, 9, +}; + +static const struct soc_enum wm8997_aec_loopback = + SOC_VALUE_ENUM_SINGLE(ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_SRC_SHIFT, 0xf, + ARRAY_SIZE(wm8997_aec_loopback_texts), + wm8997_aec_loopback_texts, + wm8997_aec_loopback_values); + +static const struct snd_kcontrol_new wm8997_aec_loopback_mux = + SOC_DAPM_ENUM("AEC Loopback", wm8997_aec_loopback); + +static const struct snd_soc_dapm_widget wm8997_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, ARIZONA_SYSCLK_ENA_SHIFT, + 0, wm8997_sysclk_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1, + ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK, + ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK, + ARIZONA_OPCLK_ASYNC_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD2", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, SND_SOC_DAPM_REGULATOR_BYPASS), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDD", 0, 0), + +SND_SOC_DAPM_SIGGEN("TONE"), +SND_SOC_DAPM_SIGGEN("NOISE"), +SND_SOC_DAPM_SIGGEN("HAPTICS"), + +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), + +SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN1R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_SUPPLY("MICBIAS1", ARIZONA_MIC_BIAS_CTRL_1, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", ARIZONA_MIC_BIAS_CTRL_2, + ARIZONA_MICB2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS3", ARIZONA_MIC_BIAS_CTRL_3, + ARIZONA_MICB3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Noise Generator", ARIZONA_COMFORT_NOISE_GENERATOR, + ARIZONA_NOISE_GEN_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Tone Generator 1", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("Tone Generator 2", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE2_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Mic Mute Mixer", ARIZONA_MIC_NOISE_MIX_CONTROL_1, + ARIZONA_MICMUTE_MIX_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("EQ1", ARIZONA_EQ1_1, ARIZONA_EQ1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ2", ARIZONA_EQ2_1, ARIZONA_EQ2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ3", ARIZONA_EQ3_1, ARIZONA_EQ3_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ4", ARIZONA_EQ4_1, ARIZONA_EQ4_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("DRC1L", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC1R", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1R_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", ARIZONA_HPLPF1_1, ARIZONA_LHPF1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", ARIZONA_HPLPF2_1, ARIZONA_LHPF2_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF3", ARIZONA_HPLPF3_1, ARIZONA_LHPF3_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF4", ARIZONA_HPLPF4_1, ARIZONA_LHPF4_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("PWM1 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM1_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_PGA("PWM2 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM2_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1INT1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1DEC1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2INT1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2DEC1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_AIF_OUT("AIF1TX1", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX5", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX6", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX7", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX8", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX1", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX5", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX6", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX7", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX8", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF2TX1", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX2", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF2RX1", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX2", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("SLIMTX1", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX2", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX3", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX4", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX5", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX6", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX7", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX8", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("SLIMRX1", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX2", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX3", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX4", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX5", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX6", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX6_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX7", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX7_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX8", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX8_ENA_SHIFT, 0), + +SND_SOC_DAPM_MUX("AEC Loopback", ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_ENA_SHIFT, 0, &wm8997_aec_loopback_mux), + +SND_SOC_DAPM_PGA_E("OUT1L", SND_SOC_NOPM, + ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT1R", SND_SOC_NOPM, + ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT3L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + +ARIZONA_MIXER_WIDGETS(EQ1, "EQ1"), +ARIZONA_MIXER_WIDGETS(EQ2, "EQ2"), +ARIZONA_MIXER_WIDGETS(EQ3, "EQ3"), +ARIZONA_MIXER_WIDGETS(EQ4, "EQ4"), + +ARIZONA_MIXER_WIDGETS(DRC1L, "DRC1L"), +ARIZONA_MIXER_WIDGETS(DRC1R, "DRC1R"), + +ARIZONA_MIXER_WIDGETS(LHPF1, "LHPF1"), +ARIZONA_MIXER_WIDGETS(LHPF2, "LHPF2"), +ARIZONA_MIXER_WIDGETS(LHPF3, "LHPF3"), +ARIZONA_MIXER_WIDGETS(LHPF4, "LHPF4"), + +ARIZONA_MIXER_WIDGETS(Mic, "Mic"), +ARIZONA_MIXER_WIDGETS(Noise, "Noise"), + +ARIZONA_MIXER_WIDGETS(PWM1, "PWM1"), +ARIZONA_MIXER_WIDGETS(PWM2, "PWM2"), + +ARIZONA_MIXER_WIDGETS(OUT1L, "HPOUT1L"), +ARIZONA_MIXER_WIDGETS(OUT1R, "HPOUT1R"), +ARIZONA_MIXER_WIDGETS(OUT3, "EPOUT"), +ARIZONA_MIXER_WIDGETS(SPKOUT, "SPKOUT"), +ARIZONA_MIXER_WIDGETS(SPKDAT1L, "SPKDAT1L"), +ARIZONA_MIXER_WIDGETS(SPKDAT1R, "SPKDAT1R"), + +ARIZONA_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"), +ARIZONA_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"), +ARIZONA_MIXER_WIDGETS(AIF1TX3, "AIF1TX3"), +ARIZONA_MIXER_WIDGETS(AIF1TX4, "AIF1TX4"), +ARIZONA_MIXER_WIDGETS(AIF1TX5, "AIF1TX5"), +ARIZONA_MIXER_WIDGETS(AIF1TX6, "AIF1TX6"), +ARIZONA_MIXER_WIDGETS(AIF1TX7, "AIF1TX7"), +ARIZONA_MIXER_WIDGETS(AIF1TX8, "AIF1TX8"), + +ARIZONA_MIXER_WIDGETS(AIF2TX1, "AIF2TX1"), +ARIZONA_MIXER_WIDGETS(AIF2TX2, "AIF2TX2"), + +ARIZONA_MIXER_WIDGETS(SLIMTX1, "SLIMTX1"), +ARIZONA_MIXER_WIDGETS(SLIMTX2, "SLIMTX2"), +ARIZONA_MIXER_WIDGETS(SLIMTX3, "SLIMTX3"), +ARIZONA_MIXER_WIDGETS(SLIMTX4, "SLIMTX4"), +ARIZONA_MIXER_WIDGETS(SLIMTX5, "SLIMTX5"), +ARIZONA_MIXER_WIDGETS(SLIMTX6, "SLIMTX6"), +ARIZONA_MIXER_WIDGETS(SLIMTX7, "SLIMTX7"), +ARIZONA_MIXER_WIDGETS(SLIMTX8, "SLIMTX8"), + +ARIZONA_MUX_WIDGETS(ISRC1DEC1, "ISRC1DEC1"), +ARIZONA_MUX_WIDGETS(ISRC1DEC2, "ISRC1DEC2"), + +ARIZONA_MUX_WIDGETS(ISRC1INT1, "ISRC1INT1"), +ARIZONA_MUX_WIDGETS(ISRC1INT2, "ISRC1INT2"), + +ARIZONA_MUX_WIDGETS(ISRC2DEC1, "ISRC2DEC1"), +ARIZONA_MUX_WIDGETS(ISRC2DEC2, "ISRC2DEC2"), + +ARIZONA_MUX_WIDGETS(ISRC2INT1, "ISRC2INT1"), +ARIZONA_MUX_WIDGETS(ISRC2INT2, "ISRC2INT2"), + +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("EPOUTN"), +SND_SOC_DAPM_OUTPUT("EPOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKDAT1L"), +SND_SOC_DAPM_OUTPUT("SPKDAT1R"), + +SND_SOC_DAPM_OUTPUT("MICSUPP"), +}; + +#define ARIZONA_MIXER_INPUT_ROUTES(name) \ + { name, "Noise Generator", "Noise Generator" }, \ + { name, "Tone Generator 1", "Tone Generator 1" }, \ + { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "Haptics", "HAPTICS" }, \ + { name, "AEC", "AEC Loopback" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2L PGA" }, \ + { name, "IN2R", "IN2R PGA" }, \ + { name, "Mic Mute Mixer", "Mic Mute Mixer" }, \ + { name, "AIF1RX1", "AIF1RX1" }, \ + { name, "AIF1RX2", "AIF1RX2" }, \ + { name, "AIF1RX3", "AIF1RX3" }, \ + { name, "AIF1RX4", "AIF1RX4" }, \ + { name, "AIF1RX5", "AIF1RX5" }, \ + { name, "AIF1RX6", "AIF1RX6" }, \ + { name, "AIF1RX7", "AIF1RX7" }, \ + { name, "AIF1RX8", "AIF1RX8" }, \ + { name, "AIF2RX1", "AIF2RX1" }, \ + { name, "AIF2RX2", "AIF2RX2" }, \ + { name, "SLIMRX1", "SLIMRX1" }, \ + { name, "SLIMRX2", "SLIMRX2" }, \ + { name, "SLIMRX3", "SLIMRX3" }, \ + { name, "SLIMRX4", "SLIMRX4" }, \ + { name, "SLIMRX5", "SLIMRX5" }, \ + { name, "SLIMRX6", "SLIMRX6" }, \ + { name, "SLIMRX7", "SLIMRX7" }, \ + { name, "SLIMRX8", "SLIMRX8" }, \ + { name, "EQ1", "EQ1" }, \ + { name, "EQ2", "EQ2" }, \ + { name, "EQ3", "EQ3" }, \ + { name, "EQ4", "EQ4" }, \ + { name, "DRC1L", "DRC1L" }, \ + { name, "DRC1R", "DRC1R" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" }, \ + { name, "LHPF3", "LHPF3" }, \ + { name, "LHPF4", "LHPF4" }, \ + { name, "ISRC1DEC1", "ISRC1DEC1" }, \ + { name, "ISRC1DEC2", "ISRC1DEC2" }, \ + { name, "ISRC1INT1", "ISRC1INT1" }, \ + { name, "ISRC1INT2", "ISRC1INT2" }, \ + { name, "ISRC2DEC1", "ISRC2DEC1" }, \ + { name, "ISRC2DEC2", "ISRC2DEC2" }, \ + { name, "ISRC2INT1", "ISRC2INT1" }, \ + { name, "ISRC2INT2", "ISRC2INT2" } + +static const struct snd_soc_dapm_route wm8997_dapm_routes[] = { + { "AIF2 Capture", NULL, "DBVDD2" }, + { "AIF2 Playback", NULL, "DBVDD2" }, + + { "OUT1L", NULL, "CPVDD" }, + { "OUT1R", NULL, "CPVDD" }, + { "OUT3L", NULL, "CPVDD" }, + + { "OUT4L", NULL, "SPKVDD" }, + + { "OUT1L", NULL, "SYSCLK" }, + { "OUT1R", NULL, "SYSCLK" }, + { "OUT3L", NULL, "SYSCLK" }, + { "OUT4L", NULL, "SYSCLK" }, + + { "IN1L", NULL, "SYSCLK" }, + { "IN1R", NULL, "SYSCLK" }, + { "IN2L", NULL, "SYSCLK" }, + { "IN2R", NULL, "SYSCLK" }, + + { "MICBIAS1", NULL, "MICVDD" }, + { "MICBIAS2", NULL, "MICVDD" }, + { "MICBIAS3", NULL, "MICVDD" }, + + { "Noise Generator", NULL, "SYSCLK" }, + { "Tone Generator 1", NULL, "SYSCLK" }, + { "Tone Generator 2", NULL, "SYSCLK" }, + + { "Noise Generator", NULL, "NOISE" }, + { "Tone Generator 1", NULL, "TONE" }, + { "Tone Generator 2", NULL, "TONE" }, + + { "AIF1 Capture", NULL, "AIF1TX1" }, + { "AIF1 Capture", NULL, "AIF1TX2" }, + { "AIF1 Capture", NULL, "AIF1TX3" }, + { "AIF1 Capture", NULL, "AIF1TX4" }, + { "AIF1 Capture", NULL, "AIF1TX5" }, + { "AIF1 Capture", NULL, "AIF1TX6" }, + { "AIF1 Capture", NULL, "AIF1TX7" }, + { "AIF1 Capture", NULL, "AIF1TX8" }, + + { "AIF1RX1", NULL, "AIF1 Playback" }, + { "AIF1RX2", NULL, "AIF1 Playback" }, + { "AIF1RX3", NULL, "AIF1 Playback" }, + { "AIF1RX4", NULL, "AIF1 Playback" }, + { "AIF1RX5", NULL, "AIF1 Playback" }, + { "AIF1RX6", NULL, "AIF1 Playback" }, + { "AIF1RX7", NULL, "AIF1 Playback" }, + { "AIF1RX8", NULL, "AIF1 Playback" }, + + { "AIF2 Capture", NULL, "AIF2TX1" }, + { "AIF2 Capture", NULL, "AIF2TX2" }, + + { "AIF2RX1", NULL, "AIF2 Playback" }, + { "AIF2RX2", NULL, "AIF2 Playback" }, + + { "Slim1 Capture", NULL, "SLIMTX1" }, + { "Slim1 Capture", NULL, "SLIMTX2" }, + { "Slim1 Capture", NULL, "SLIMTX3" }, + { "Slim1 Capture", NULL, "SLIMTX4" }, + + { "SLIMRX1", NULL, "Slim1 Playback" }, + { "SLIMRX2", NULL, "Slim1 Playback" }, + { "SLIMRX3", NULL, "Slim1 Playback" }, + { "SLIMRX4", NULL, "Slim1 Playback" }, + + { "Slim2 Capture", NULL, "SLIMTX5" }, + { "Slim2 Capture", NULL, "SLIMTX6" }, + + { "SLIMRX5", NULL, "Slim2 Playback" }, + { "SLIMRX6", NULL, "Slim2 Playback" }, + + { "Slim3 Capture", NULL, "SLIMTX7" }, + { "Slim3 Capture", NULL, "SLIMTX8" }, + + { "SLIMRX7", NULL, "Slim3 Playback" }, + { "SLIMRX8", NULL, "Slim3 Playback" }, + + { "AIF1 Playback", NULL, "SYSCLK" }, + { "AIF2 Playback", NULL, "SYSCLK" }, + { "Slim1 Playback", NULL, "SYSCLK" }, + { "Slim2 Playback", NULL, "SYSCLK" }, + { "Slim3 Playback", NULL, "SYSCLK" }, + + { "AIF1 Capture", NULL, "SYSCLK" }, + { "AIF2 Capture", NULL, "SYSCLK" }, + { "Slim1 Capture", NULL, "SYSCLK" }, + { "Slim2 Capture", NULL, "SYSCLK" }, + { "Slim3 Capture", NULL, "SYSCLK" }, + + { "IN1L PGA", NULL, "IN1L" }, + { "IN1R PGA", NULL, "IN1R" }, + + { "IN2L PGA", NULL, "IN2L" }, + { "IN2R PGA", NULL, "IN2R" }, + + ARIZONA_MIXER_ROUTES("OUT1L", "HPOUT1L"), + ARIZONA_MIXER_ROUTES("OUT1R", "HPOUT1R"), + ARIZONA_MIXER_ROUTES("OUT3L", "EPOUT"), + + ARIZONA_MIXER_ROUTES("OUT4L", "SPKOUT"), + ARIZONA_MIXER_ROUTES("OUT5L", "SPKDAT1L"), + ARIZONA_MIXER_ROUTES("OUT5R", "SPKDAT1R"), + + ARIZONA_MIXER_ROUTES("PWM1 Driver", "PWM1"), + ARIZONA_MIXER_ROUTES("PWM2 Driver", "PWM2"), + + ARIZONA_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), + ARIZONA_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), + ARIZONA_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), + ARIZONA_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), + ARIZONA_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), + ARIZONA_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), + ARIZONA_MIXER_ROUTES("AIF1TX7", "AIF1TX7"), + ARIZONA_MIXER_ROUTES("AIF1TX8", "AIF1TX8"), + + ARIZONA_MIXER_ROUTES("AIF2TX1", "AIF2TX1"), + ARIZONA_MIXER_ROUTES("AIF2TX2", "AIF2TX2"), + + ARIZONA_MIXER_ROUTES("SLIMTX1", "SLIMTX1"), + ARIZONA_MIXER_ROUTES("SLIMTX2", "SLIMTX2"), + ARIZONA_MIXER_ROUTES("SLIMTX3", "SLIMTX3"), + ARIZONA_MIXER_ROUTES("SLIMTX4", "SLIMTX4"), + ARIZONA_MIXER_ROUTES("SLIMTX5", "SLIMTX5"), + ARIZONA_MIXER_ROUTES("SLIMTX6", "SLIMTX6"), + ARIZONA_MIXER_ROUTES("SLIMTX7", "SLIMTX7"), + ARIZONA_MIXER_ROUTES("SLIMTX8", "SLIMTX8"), + + ARIZONA_MIXER_ROUTES("EQ1", "EQ1"), + ARIZONA_MIXER_ROUTES("EQ2", "EQ2"), + ARIZONA_MIXER_ROUTES("EQ3", "EQ3"), + ARIZONA_MIXER_ROUTES("EQ4", "EQ4"), + + ARIZONA_MIXER_ROUTES("DRC1L", "DRC1L"), + ARIZONA_MIXER_ROUTES("DRC1R", "DRC1R"), + + ARIZONA_MIXER_ROUTES("LHPF1", "LHPF1"), + ARIZONA_MIXER_ROUTES("LHPF2", "LHPF2"), + ARIZONA_MIXER_ROUTES("LHPF3", "LHPF3"), + ARIZONA_MIXER_ROUTES("LHPF4", "LHPF4"), + + ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Noise"), + ARIZONA_MIXER_ROUTES("Mic Mute Mixer", "Mic"), + + ARIZONA_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), + ARIZONA_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), + + ARIZONA_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), + ARIZONA_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), + + ARIZONA_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), + ARIZONA_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), + + ARIZONA_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), + ARIZONA_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), + + { "AEC Loopback", "HPOUT1L", "OUT1L" }, + { "AEC Loopback", "HPOUT1R", "OUT1R" }, + { "HPOUT1L", NULL, "OUT1L" }, + { "HPOUT1R", NULL, "OUT1R" }, + + { "AEC Loopback", "EPOUT", "OUT3L" }, + { "EPOUTN", NULL, "OUT3L" }, + { "EPOUTP", NULL, "OUT3L" }, + + { "AEC Loopback", "SPKOUT", "OUT4L" }, + { "SPKOUTN", NULL, "OUT4L" }, + { "SPKOUTP", NULL, "OUT4L" }, + + { "AEC Loopback", "SPKDAT1L", "OUT5L" }, + { "AEC Loopback", "SPKDAT1R", "OUT5R" }, + { "SPKDAT1L", NULL, "OUT5L" }, + { "SPKDAT1R", NULL, "OUT5R" }, + + { "MICSUPP", NULL, "SYSCLK" }, +}; + +static int wm8997_set_fll(struct snd_soc_component *component, int fll_id, + int source, unsigned int Fref, unsigned int Fout) +{ + struct wm8997_priv *wm8997 = snd_soc_component_get_drvdata(component); + + switch (fll_id) { + case WM8997_FLL1: + return arizona_set_fll(&wm8997->fll[0], source, Fref, Fout); + case WM8997_FLL2: + return arizona_set_fll(&wm8997->fll[1], source, Fref, Fout); + case WM8997_FLL1_REFCLK: + return arizona_set_fll_refclk(&wm8997->fll[0], source, Fref, + Fout); + case WM8997_FLL2_REFCLK: + return arizona_set_fll_refclk(&wm8997->fll[1], source, Fref, + Fout); + default: + return -EINVAL; + } +} + +#define WM8997_RATES SNDRV_PCM_RATE_KNOT + +#define WM8997_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm8997_dai[] = { + { + .name = "wm8997-aif1", + .id = 1, + .base = ARIZONA_AIF1_BCLK_CTRL, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 8, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 8, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm8997-aif2", + .id = 2, + .base = ARIZONA_AIF2_BCLK_CTRL, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm8997-slim1", + .id = 3, + .playback = { + .stream_name = "Slim1 Playback", + .channels_min = 1, + .channels_max = 4, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .capture = { + .stream_name = "Slim1 Capture", + .channels_min = 1, + .channels_max = 4, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm8997-slim2", + .id = 4, + .playback = { + .stream_name = "Slim2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .capture = { + .stream_name = "Slim2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm8997-slim3", + .id = 5, + .playback = { + .stream_name = "Slim3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .capture = { + .stream_name = "Slim3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8997_RATES, + .formats = WM8997_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, +}; + +static int wm8997_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8997_priv *priv = snd_soc_component_get_drvdata(component); + struct arizona *arizona = priv->core.arizona; + int ret; + + snd_soc_component_init_regmap(component, arizona->regmap); + + ret = arizona_init_spk(component); + if (ret < 0) + return ret; + + snd_soc_component_disable_pin(component, "HAPTICS"); + + priv->core.arizona->dapm = dapm; + + return 0; +} + +static void wm8997_component_remove(struct snd_soc_component *component) +{ + struct wm8997_priv *priv = snd_soc_component_get_drvdata(component); + + priv->core.arizona->dapm = NULL; +} + +#define WM8997_DIG_VU 0x0200 + +static unsigned int wm8997_digital_vu[] = { + ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, + ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8997 = { + .probe = wm8997_component_probe, + .remove = wm8997_component_remove, + .set_sysclk = arizona_set_sysclk, + .set_pll = wm8997_set_fll, + .controls = wm8997_snd_controls, + .num_controls = ARRAY_SIZE(wm8997_snd_controls), + .dapm_widgets = wm8997_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8997_dapm_widgets), + .dapm_routes = wm8997_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8997_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8997_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + struct wm8997_priv *wm8997; + int i, ret; + + wm8997 = devm_kzalloc(&pdev->dev, sizeof(struct wm8997_priv), + GFP_KERNEL); + if (wm8997 == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, wm8997); + + if (IS_ENABLED(CONFIG_OF)) { + if (!dev_get_platdata(arizona->dev)) { + ret = arizona_of_get_audio_pdata(arizona); + if (ret < 0) + return ret; + } + } + + wm8997->core.arizona = arizona; + wm8997->core.num_inputs = 4; + + arizona_init_dvfs(&wm8997->core); + + for (i = 0; i < ARRAY_SIZE(wm8997->fll); i++) + wm8997->fll[i].vco_mult = 1; + + arizona_init_fll(arizona, 1, ARIZONA_FLL1_CONTROL_1 - 1, + ARIZONA_IRQ_FLL1_LOCK, ARIZONA_IRQ_FLL1_CLOCK_OK, + &wm8997->fll[0]); + arizona_init_fll(arizona, 2, ARIZONA_FLL2_CONTROL_1 - 1, + ARIZONA_IRQ_FLL2_LOCK, ARIZONA_IRQ_FLL2_CLOCK_OK, + &wm8997->fll[1]); + + /* SR2 fixed at 8kHz, SR3 fixed at 16kHz */ + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_2, + ARIZONA_SAMPLE_RATE_2_MASK, 0x11); + regmap_update_bits(arizona->regmap, ARIZONA_SAMPLE_RATE_3, + ARIZONA_SAMPLE_RATE_3_MASK, 0x12); + + for (i = 0; i < ARRAY_SIZE(wm8997_dai); i++) + arizona_init_dai(&wm8997->core, i); + + /* Latch volume update bits */ + for (i = 0; i < ARRAY_SIZE(wm8997_digital_vu); i++) + regmap_update_bits(arizona->regmap, wm8997_digital_vu[i], + WM8997_DIG_VU, WM8997_DIG_VU); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + + arizona_init_common(arizona); + + ret = arizona_init_vol_limit(arizona); + if (ret < 0) + return ret; + ret = arizona_init_spk_irqs(arizona); + if (ret < 0) + return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm8997, + wm8997_dai, + ARRAY_SIZE(wm8997_dai)); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register component: %d\n", ret); + goto err_spk_irqs; + } + + return ret; + +err_spk_irqs: + arizona_free_spk_irqs(arizona); + + return ret; +} + +static int wm8997_remove(struct platform_device *pdev) +{ + struct wm8997_priv *wm8997 = platform_get_drvdata(pdev); + struct arizona *arizona = wm8997->core.arizona; + + pm_runtime_disable(&pdev->dev); + + arizona_free_spk_irqs(arizona); + + return 0; +} + +static struct platform_driver wm8997_codec_driver = { + .driver = { + .name = "wm8997-codec", + }, + .probe = wm8997_probe, + .remove = wm8997_remove, +}; + +module_platform_driver(wm8997_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8997 driver"); +MODULE_AUTHOR("Charles Keepax "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8997-codec"); diff --git a/sound/soc/codecs/wm8997.h b/sound/soc/codecs/wm8997.h new file mode 100644 index 000000000..5e91c6a7d --- /dev/null +++ b/sound/soc/codecs/wm8997.h @@ -0,0 +1,23 @@ +/* + * wm8997.h -- WM8997 ALSA SoC Audio driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8997_H +#define _WM8997_H + +#include "arizona.h" + +#define WM8997_FLL1 1 +#define WM8997_FLL2 2 +#define WM8997_FLL1_REFCLK 3 +#define WM8997_FLL2_REFCLK 4 + +#endif diff --git a/sound/soc/codecs/wm8998.c b/sound/soc/codecs/wm8998.c new file mode 100644 index 000000000..17dc5780a --- /dev/null +++ b/sound/soc/codecs/wm8998.c @@ -0,0 +1,1427 @@ +/* + * wm8998.c -- ALSA SoC Audio driver for WM8998 codecs + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Author: Richard Fitzgerald + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arizona.h" +#include "wm8998.h" + +struct wm8998_priv { + struct arizona_priv core; + struct arizona_fll fll[2]; +}; + +static int wm8998_asrc_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int val; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + val = snd_soc_component_read32(component, ARIZONA_ASRC_RATE1); + val &= ARIZONA_ASRC_RATE1_MASK; + val >>= ARIZONA_ASRC_RATE1_SHIFT; + + switch (val) { + case 0: + case 1: + case 2: + val = snd_soc_component_read32(component, + ARIZONA_SAMPLE_RATE_1 + val); + if (val >= 0x11) { + dev_warn(component->dev, + "Unsupported ASRC rate1 (%s)\n", + arizona_sample_rate_val_to_name(val)); + return -EINVAL; + } + break; + default: + dev_err(component->dev, + "Illegal ASRC rate1 selector (0x%x)\n", + val); + return -EINVAL; + } + + val = snd_soc_component_read32(component, ARIZONA_ASRC_RATE2); + val &= ARIZONA_ASRC_RATE2_MASK; + val >>= ARIZONA_ASRC_RATE2_SHIFT; + + switch (val) { + case 8: + case 9: + val -= 0x8; + val = snd_soc_component_read32(component, + ARIZONA_ASYNC_SAMPLE_RATE_1 + val); + if (val >= 0x11) { + dev_warn(component->dev, + "Unsupported ASRC rate2 (%s)\n", + arizona_sample_rate_val_to_name(val)); + return -EINVAL; + } + break; + default: + dev_err(component->dev, + "Illegal ASRC rate2 selector (0x%x)\n", + val); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8998_inmux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct wm8998_priv *wm8998 = snd_soc_component_get_drvdata(component); + struct arizona *arizona = wm8998->core.arizona; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int mode_reg, mode_index; + unsigned int mux, inmode, src_val, mode_val; + + mux = ucontrol->value.enumerated.item[0]; + if (mux > 1) + return -EINVAL; + + switch (e->reg) { + case ARIZONA_ADC_DIGITAL_VOLUME_2L: + mode_reg = ARIZONA_IN2L_CONTROL; + mode_index = 1 + (2 * mux); + break; + default: + mode_reg = ARIZONA_IN1L_CONTROL; + mode_index = (2 * mux); + break; + } + + inmode = arizona->pdata.inmode[mode_index]; + if (inmode & ARIZONA_INMODE_DMIC) + mode_val = 1 << ARIZONA_IN1_MODE_SHIFT; + else + mode_val = 0; + + src_val = mux << ARIZONA_IN1L_SRC_SHIFT; + if (inmode & ARIZONA_INMODE_SE) + src_val |= 1 << ARIZONA_IN1L_SRC_SE_SHIFT; + + snd_soc_component_update_bits(component, mode_reg, + ARIZONA_IN1_MODE_MASK, mode_val); + + snd_soc_component_update_bits(component, e->reg, + ARIZONA_IN1L_SRC_MASK | + ARIZONA_IN1L_SRC_SE_MASK, + src_val); + + return snd_soc_dapm_mux_update_power(dapm, kcontrol, + ucontrol->value.enumerated.item[0], + e, NULL); +} + +static const char * const wm8998_inmux_texts[] = { + "A", + "B", +}; + +static SOC_ENUM_SINGLE_DECL(wm8998_in1muxl_enum, + ARIZONA_ADC_DIGITAL_VOLUME_1L, + ARIZONA_IN1L_SRC_SHIFT, + wm8998_inmux_texts); + +static SOC_ENUM_SINGLE_DECL(wm8998_in1muxr_enum, + ARIZONA_ADC_DIGITAL_VOLUME_1R, + ARIZONA_IN1R_SRC_SHIFT, + wm8998_inmux_texts); + +static SOC_ENUM_SINGLE_DECL(wm8998_in2mux_enum, + ARIZONA_ADC_DIGITAL_VOLUME_2L, + ARIZONA_IN2L_SRC_SHIFT, + wm8998_inmux_texts); + +static const struct snd_kcontrol_new wm8998_in1mux[2] = { + SOC_DAPM_ENUM_EXT("IN1L Mux", wm8998_in1muxl_enum, + snd_soc_dapm_get_enum_double, wm8998_inmux_put), + SOC_DAPM_ENUM_EXT("IN1R Mux", wm8998_in1muxr_enum, + snd_soc_dapm_get_enum_double, wm8998_inmux_put), +}; + +static const struct snd_kcontrol_new wm8998_in2mux = + SOC_DAPM_ENUM_EXT("IN2 Mux", wm8998_in2mux_enum, + snd_soc_dapm_get_enum_double, wm8998_inmux_put); + +static DECLARE_TLV_DB_SCALE(ana_tlv, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); +static DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); + +#define WM8998_NG_SRC(name, base) \ + SOC_SINGLE(name " NG HPOUTL Switch", base, 0, 1, 0), \ + SOC_SINGLE(name " NG HPOUTR Switch", base, 1, 1, 0), \ + SOC_SINGLE(name " NG LINEOUTL Switch", base, 2, 1, 0), \ + SOC_SINGLE(name " NG LINEOUTR Switch", base, 3, 1, 0), \ + SOC_SINGLE(name " NG EPOUT Switch", base, 4, 1, 0), \ + SOC_SINGLE(name " NG SPKOUTL Switch", base, 6, 1, 0), \ + SOC_SINGLE(name " NG SPKOUTR Switch", base, 7, 1, 0) + +static const struct snd_kcontrol_new wm8998_snd_controls[] = { +SOC_ENUM("IN1 OSR", arizona_in_dmic_osr[0]), +SOC_ENUM("IN2 OSR", arizona_in_dmic_osr[1]), + +SOC_SINGLE_RANGE_TLV("IN1L Volume", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN1R Volume", ARIZONA_IN1R_CONTROL, + ARIZONA_IN1R_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), +SOC_SINGLE_RANGE_TLV("IN2 Volume", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2L_PGA_VOL_SHIFT, 0x40, 0x5f, 0, ana_tlv), + +SOC_ENUM("IN HPF Cutoff Frequency", arizona_in_hpf_cut_enum), + +SOC_SINGLE("IN1L HPF Switch", ARIZONA_IN1L_CONTROL, + ARIZONA_IN1L_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN1R HPF Switch", ARIZONA_IN1R_CONTROL, + ARIZONA_IN1R_HPF_SHIFT, 1, 0), +SOC_SINGLE("IN2 HPF Switch", ARIZONA_IN2L_CONTROL, + ARIZONA_IN2L_HPF_SHIFT, 1, 0), + +SOC_SINGLE_TLV("IN1L Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1L, + ARIZONA_IN1L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN1R Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_1R, + ARIZONA_IN1R_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("IN2 Digital Volume", ARIZONA_ADC_DIGITAL_VOLUME_2L, + ARIZONA_IN2L_DIG_VOL_SHIFT, 0xbf, 0, digital_tlv), + +SOC_ENUM("Input Ramp Up", arizona_in_vi_ramp), +SOC_ENUM("Input Ramp Down", arizona_in_vd_ramp), + +ARIZONA_GAINMUX_CONTROLS("EQ1", ARIZONA_EQ1MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("EQ2", ARIZONA_EQ2MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("EQ3", ARIZONA_EQ3MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("EQ4", ARIZONA_EQ4MIX_INPUT_1_SOURCE), + +SND_SOC_BYTES("EQ1 Coefficients", ARIZONA_EQ1_3, 19), +SOC_SINGLE("EQ1 Mode Switch", ARIZONA_EQ1_2, ARIZONA_EQ1_B1_MODE_SHIFT, 1, 0), +SOC_SINGLE_TLV("EQ1 B1 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B2 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B3 Volume", ARIZONA_EQ1_1, ARIZONA_EQ1_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B4 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ1 B5 Volume", ARIZONA_EQ1_2, ARIZONA_EQ1_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +SND_SOC_BYTES("EQ2 Coefficients", ARIZONA_EQ2_3, 19), +SOC_SINGLE("EQ2 Mode Switch", ARIZONA_EQ2_2, ARIZONA_EQ2_B1_MODE_SHIFT, 1, 0), +SOC_SINGLE_TLV("EQ2 B1 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B2 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B3 Volume", ARIZONA_EQ2_1, ARIZONA_EQ2_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B4 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 B5 Volume", ARIZONA_EQ2_2, ARIZONA_EQ2_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +SND_SOC_BYTES("EQ3 Coefficients", ARIZONA_EQ3_3, 19), +SOC_SINGLE("EQ3 Mode Switch", ARIZONA_EQ3_2, ARIZONA_EQ3_B1_MODE_SHIFT, 1, 0), +SOC_SINGLE_TLV("EQ3 B1 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B2 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B3 Volume", ARIZONA_EQ3_1, ARIZONA_EQ3_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B4 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 B5 Volume", ARIZONA_EQ3_2, ARIZONA_EQ3_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +SND_SOC_BYTES("EQ4 Coefficients", ARIZONA_EQ4_3, 19), +SOC_SINGLE("EQ4 Mode Switch", ARIZONA_EQ4_2, ARIZONA_EQ4_B1_MODE_SHIFT, 1, 0), +SOC_SINGLE_TLV("EQ4 B1 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B1_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B2 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B2_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B3 Volume", ARIZONA_EQ4_1, ARIZONA_EQ4_B3_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B4 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B4_GAIN_SHIFT, + 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 B5 Volume", ARIZONA_EQ4_2, ARIZONA_EQ4_B5_GAIN_SHIFT, + 24, 0, eq_tlv), + +ARIZONA_GAINMUX_CONTROLS("DRC1L", ARIZONA_DRC1LMIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("DRC1R", ARIZONA_DRC1RMIX_INPUT_1_SOURCE), + +SND_SOC_BYTES_MASK("DRC1", ARIZONA_DRC1_CTRL1, 5, + ARIZONA_DRC1R_ENA | ARIZONA_DRC1L_ENA), + +ARIZONA_MIXER_CONTROLS("LHPF1", ARIZONA_HPLP1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF2", ARIZONA_HPLP2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF3", ARIZONA_HPLP3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LHPF4", ARIZONA_HPLP4MIX_INPUT_1_SOURCE), + +SND_SOC_BYTES("LHPF1 Coefficients", ARIZONA_HPLPF1_2, 1), +SND_SOC_BYTES("LHPF2 Coefficients", ARIZONA_HPLPF2_2, 1), +SND_SOC_BYTES("LHPF3 Coefficients", ARIZONA_HPLPF3_2, 1), +SND_SOC_BYTES("LHPF4 Coefficients", ARIZONA_HPLPF4_2, 1), + +SOC_ENUM("LHPF1 Mode", arizona_lhpf1_mode), +SOC_ENUM("LHPF2 Mode", arizona_lhpf2_mode), +SOC_ENUM("LHPF3 Mode", arizona_lhpf3_mode), +SOC_ENUM("LHPF4 Mode", arizona_lhpf4_mode), + +SOC_ENUM("ISRC1 FSL", arizona_isrc_fsl[0]), +SOC_ENUM("ISRC2 FSL", arizona_isrc_fsl[1]), +SOC_ENUM("ISRC1 FSH", arizona_isrc_fsh[0]), +SOC_ENUM("ISRC2 FSH", arizona_isrc_fsh[1]), +SOC_ENUM("ASRC RATE 1", arizona_asrc_rate1), + +ARIZONA_MIXER_CONTROLS("HPOUTL", ARIZONA_OUT1LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("HPOUTR", ARIZONA_OUT1RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LINEOUTL", ARIZONA_OUT2LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("LINEOUTR", ARIZONA_OUT2RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("EPOUT", ARIZONA_OUT3LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUTL", ARIZONA_OUT4LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKOUTR", ARIZONA_OUT4RMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDATL", ARIZONA_OUT5LMIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("SPKDATR", ARIZONA_OUT5RMIX_INPUT_1_SOURCE), + +SOC_DOUBLE_R("HPOUT Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("LINEOUT Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_MUTE_SHIFT, 1, 1), +SOC_SINGLE("EPOUT Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_OUT3L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("Speaker Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_MUTE_SHIFT, 1, 1), +SOC_DOUBLE_R("SPKDAT Digital Switch", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_MUTE_SHIFT, 1, 1), + +SOC_DOUBLE_R_TLV("HPOUT Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, ARIZONA_OUT1L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("LINEOUT Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, ARIZONA_OUT2L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_SINGLE_TLV("EPOUT Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_OUT3L_VOL_SHIFT, 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("Speaker Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, ARIZONA_OUT4L_VOL_SHIFT, + 0xbf, 0, digital_tlv), +SOC_DOUBLE_R_TLV("SPKDAT Digital Volume", ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, ARIZONA_OUT5L_VOL_SHIFT, + 0xbf, 0, digital_tlv), + +SOC_DOUBLE("SPKDAT Switch", ARIZONA_PDM_SPK1_CTRL_1, ARIZONA_SPK1L_MUTE_SHIFT, + ARIZONA_SPK1R_MUTE_SHIFT, 1, 1), + +SOC_ENUM("Output Ramp Up", arizona_out_vi_ramp), +SOC_ENUM("Output Ramp Down", arizona_out_vd_ramp), + +SOC_SINGLE("Noise Gate Switch", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_ENA_SHIFT, 1, 0), +SOC_SINGLE_TLV("Noise Gate Threshold Volume", ARIZONA_NOISE_GATE_CONTROL, + ARIZONA_NGATE_THR_SHIFT, 7, 1, ng_tlv), +SOC_ENUM("Noise Gate Hold", arizona_ng_hold), + +WM8998_NG_SRC("HPOUTL", ARIZONA_NOISE_GATE_SELECT_1L), +WM8998_NG_SRC("HPOUTR", ARIZONA_NOISE_GATE_SELECT_1R), +WM8998_NG_SRC("LINEOUTL", ARIZONA_NOISE_GATE_SELECT_2L), +WM8998_NG_SRC("LINEOUTR", ARIZONA_NOISE_GATE_SELECT_2R), +WM8998_NG_SRC("EPOUT", ARIZONA_NOISE_GATE_SELECT_3L), +WM8998_NG_SRC("SPKOUTL", ARIZONA_NOISE_GATE_SELECT_4L), +WM8998_NG_SRC("SPKOUTR", ARIZONA_NOISE_GATE_SELECT_4R), +WM8998_NG_SRC("SPKDATL", ARIZONA_NOISE_GATE_SELECT_5L), +WM8998_NG_SRC("SPKDATR", ARIZONA_NOISE_GATE_SELECT_5R), + +ARIZONA_MIXER_CONTROLS("AIF1TX1", ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX2", ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX3", ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX4", ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX5", ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF1TX6", ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF2TX1", ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX2", ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX3", ARIZONA_AIF2TX3MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX4", ARIZONA_AIF2TX4MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX5", ARIZONA_AIF2TX5MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF2TX6", ARIZONA_AIF2TX6MIX_INPUT_1_SOURCE), + +ARIZONA_MIXER_CONTROLS("AIF3TX1", ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE), +ARIZONA_MIXER_CONTROLS("AIF3TX2", ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE), + +ARIZONA_GAINMUX_CONTROLS("SLIMTX1", ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("SLIMTX2", ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("SLIMTX3", ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("SLIMTX4", ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("SLIMTX5", ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("SLIMTX6", ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE), + +ARIZONA_GAINMUX_CONTROLS("SPDIFTX1", ARIZONA_SPDIFTX1MIX_INPUT_1_SOURCE), +ARIZONA_GAINMUX_CONTROLS("SPDIFTX2", ARIZONA_SPDIFTX2MIX_INPUT_1_SOURCE), +}; + +ARIZONA_MUX_ENUMS(EQ1, ARIZONA_EQ1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(EQ2, ARIZONA_EQ2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(EQ3, ARIZONA_EQ3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(EQ4, ARIZONA_EQ4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(DRC1L, ARIZONA_DRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(DRC1R, ARIZONA_DRC1RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(LHPF1, ARIZONA_HPLP1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF2, ARIZONA_HPLP2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF3, ARIZONA_HPLP3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(LHPF4, ARIZONA_HPLP4MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(PWM1, ARIZONA_PWM1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(PWM2, ARIZONA_PWM2MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(OUT1L, ARIZONA_OUT1LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT1R, ARIZONA_OUT1RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT2L, ARIZONA_OUT2LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT2R, ARIZONA_OUT2RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(OUT3, ARIZONA_OUT3LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUTL, ARIZONA_OUT4LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKOUTR, ARIZONA_OUT4RMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDATL, ARIZONA_OUT5LMIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(SPKDATR, ARIZONA_OUT5RMIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF1TX1, ARIZONA_AIF1TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX2, ARIZONA_AIF1TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX3, ARIZONA_AIF1TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX4, ARIZONA_AIF1TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX5, ARIZONA_AIF1TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF1TX6, ARIZONA_AIF1TX6MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF2TX1, ARIZONA_AIF2TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX2, ARIZONA_AIF2TX2MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX3, ARIZONA_AIF2TX3MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX4, ARIZONA_AIF2TX4MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX5, ARIZONA_AIF2TX5MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF2TX6, ARIZONA_AIF2TX6MIX_INPUT_1_SOURCE); + +ARIZONA_MIXER_ENUMS(AIF3TX1, ARIZONA_AIF3TX1MIX_INPUT_1_SOURCE); +ARIZONA_MIXER_ENUMS(AIF3TX2, ARIZONA_AIF3TX2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(SLIMTX1, ARIZONA_SLIMTX1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(SLIMTX2, ARIZONA_SLIMTX2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(SLIMTX3, ARIZONA_SLIMTX3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(SLIMTX4, ARIZONA_SLIMTX4MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(SLIMTX5, ARIZONA_SLIMTX5MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(SLIMTX6, ARIZONA_SLIMTX6MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(SPD1TX1, ARIZONA_SPDIFTX1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(SPD1TX2, ARIZONA_SPDIFTX2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ASRC1L, ARIZONA_ASRC1LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC1R, ARIZONA_ASRC1RMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2L, ARIZONA_ASRC2LMIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ASRC2R, ARIZONA_ASRC2RMIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1INT1, ARIZONA_ISRC1INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT2, ARIZONA_ISRC1INT2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT3, ARIZONA_ISRC1INT3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1INT4, ARIZONA_ISRC1INT4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC1DEC1, ARIZONA_ISRC1DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC2, ARIZONA_ISRC1DEC2MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC3, ARIZONA_ISRC1DEC3MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC1DEC4, ARIZONA_ISRC1DEC4MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2INT1, ARIZONA_ISRC2INT1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2INT2, ARIZONA_ISRC2INT2MIX_INPUT_1_SOURCE); + +ARIZONA_MUX_ENUMS(ISRC2DEC1, ARIZONA_ISRC2DEC1MIX_INPUT_1_SOURCE); +ARIZONA_MUX_ENUMS(ISRC2DEC2, ARIZONA_ISRC2DEC2MIX_INPUT_1_SOURCE); + +static const char * const wm8998_aec_loopback_texts[] = { + "HPOUTL", "HPOUTR", "LINEOUTL", "LINEOUTR", "EPOUT", + "SPKOUTL", "SPKOUTR", "SPKDATL", "SPKDATR", +}; + +static const unsigned int wm8998_aec_loopback_values[] = { + 0, 1, 2, 3, 4, 6, 7, 8, 9, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(wm8998_aec1_loopback, + ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_SRC_SHIFT, 0xf, + wm8998_aec_loopback_texts, + wm8998_aec_loopback_values); + +static SOC_VALUE_ENUM_SINGLE_DECL(wm8998_aec2_loopback, + ARIZONA_DAC_AEC_CONTROL_2, + ARIZONA_AEC_LOOPBACK_SRC_SHIFT, 0xf, + wm8998_aec_loopback_texts, + wm8998_aec_loopback_values); + +static const struct snd_kcontrol_new wm8998_aec_loopback_mux[] = { + SOC_DAPM_ENUM("AEC1 Loopback", wm8998_aec1_loopback), + SOC_DAPM_ENUM("AEC2 Loopback", wm8998_aec2_loopback), +}; + +static const struct snd_soc_dapm_widget wm8998_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("SYSCLK", ARIZONA_SYSTEM_CLOCK_1, + ARIZONA_SYSCLK_ENA_SHIFT, 0, arizona_clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("ASYNCCLK", ARIZONA_ASYNC_CLOCK_1, + ARIZONA_ASYNC_CLK_ENA_SHIFT, 0, arizona_clk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("OPCLK", ARIZONA_OUTPUT_SYSTEM_CLOCK, + ARIZONA_OPCLK_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("ASYNCOPCLK", ARIZONA_OUTPUT_ASYNC_CLOCK, + ARIZONA_OPCLK_ASYNC_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD2", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DBVDD3", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("MICVDD", 0, SND_SOC_DAPM_REGULATOR_BYPASS), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDL", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("SPKVDDR", 0, 0), + +SND_SOC_DAPM_SIGGEN("TONE"), +SND_SOC_DAPM_SIGGEN("HAPTICS"), + +SND_SOC_DAPM_INPUT("IN1AL"), +SND_SOC_DAPM_INPUT("IN1AR"), +SND_SOC_DAPM_INPUT("IN1BL"), +SND_SOC_DAPM_INPUT("IN1BR"), +SND_SOC_DAPM_INPUT("IN2A"), +SND_SOC_DAPM_INPUT("IN2B"), + +SND_SOC_DAPM_MUX("IN1L Mux", SND_SOC_NOPM, 0, 0, &wm8998_in1mux[0]), +SND_SOC_DAPM_MUX("IN1R Mux", SND_SOC_NOPM, 0, 0, &wm8998_in1mux[1]), +SND_SOC_DAPM_MUX("IN2 Mux", SND_SOC_NOPM, 0, 0, &wm8998_in2mux), + +SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), + +SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN1R PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1R_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("IN2 PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN2L_ENA_SHIFT, + 0, NULL, 0, arizona_in_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_SUPPLY("MICBIAS1", ARIZONA_MIC_BIAS_CTRL_1, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS2", ARIZONA_MIC_BIAS_CTRL_2, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS3", ARIZONA_MIC_BIAS_CTRL_3, + ARIZONA_MICB1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Tone Generator 1", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("Tone Generator 2", ARIZONA_TONE_GENERATOR_1, + ARIZONA_TONE2_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("EQ1", ARIZONA_EQ1_1, ARIZONA_EQ1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ2", ARIZONA_EQ2_1, ARIZONA_EQ2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ3", ARIZONA_EQ3_1, ARIZONA_EQ3_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("EQ4", ARIZONA_EQ4_1, ARIZONA_EQ4_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("DRC1L", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1L_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("DRC1R", ARIZONA_DRC1_CTRL1, ARIZONA_DRC1R_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("LHPF1", ARIZONA_HPLPF1_1, ARIZONA_LHPF1_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF2", ARIZONA_HPLPF2_1, ARIZONA_LHPF2_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF3", ARIZONA_HPLPF3_1, ARIZONA_LHPF3_ENA_SHIFT, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LHPF4", ARIZONA_HPLPF4_1, ARIZONA_LHPF4_ENA_SHIFT, 0, + NULL, 0), + +SND_SOC_DAPM_PGA("PWM1 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM1_ENA_SHIFT, + 0, NULL, 0), +SND_SOC_DAPM_PGA("PWM2 Driver", ARIZONA_PWM_DRIVE_1, ARIZONA_PWM2_ENA_SHIFT, + 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("ASRC1L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1L_ENA_SHIFT, 0, + NULL, 0, wm8998_asrc_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("ASRC1R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC1R_ENA_SHIFT, 0, + NULL, 0, wm8998_asrc_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("ASRC2L", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2L_ENA_SHIFT, 0, + NULL, 0, wm8998_asrc_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("ASRC2R", ARIZONA_ASRC_ENABLE, ARIZONA_ASRC2R_ENA_SHIFT, 0, + NULL, 0, wm8998_asrc_ev, SND_SOC_DAPM_PRE_PMU), + +SND_SOC_DAPM_PGA("ISRC1INT1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT3", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1INT4", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_INT3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC1DEC1", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC2", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC1_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC3", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC2_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC1DEC4", ARIZONA_ISRC_1_CTRL_3, + ARIZONA_ISRC1_DEC3_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2INT1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2INT2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_INT1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_PGA("ISRC2DEC1", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC0_ENA_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("ISRC2DEC2", ARIZONA_ISRC_2_CTRL_3, + ARIZONA_ISRC2_DEC1_ENA_SHIFT, 0, NULL, 0), + +SND_SOC_DAPM_MUX("AEC1 Loopback", ARIZONA_DAC_AEC_CONTROL_1, + ARIZONA_AEC_LOOPBACK_ENA_SHIFT, 0, + &wm8998_aec_loopback_mux[0]), + +SND_SOC_DAPM_MUX("AEC2 Loopback", ARIZONA_DAC_AEC_CONTROL_2, + ARIZONA_AEC_LOOPBACK_ENA_SHIFT, 0, + &wm8998_aec_loopback_mux[1]), + +SND_SOC_DAPM_AIF_OUT("AIF1TX1", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX2", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX3", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX4", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX5", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF1TX6", NULL, 0, + ARIZONA_AIF1_TX_ENABLES, ARIZONA_AIF1TX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF1RX1", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX2", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX3", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX4", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX5", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF1RX6", NULL, 0, + ARIZONA_AIF1_RX_ENABLES, ARIZONA_AIF1RX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF2TX1", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX2", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX3", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX4", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX5", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF2TX6", NULL, 0, + ARIZONA_AIF2_TX_ENABLES, ARIZONA_AIF2TX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF2RX1", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX2", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX3", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX4", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX5", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF2RX6", NULL, 0, + ARIZONA_AIF2_RX_ENABLES, ARIZONA_AIF2RX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("SLIMRX1", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX2", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX3", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("SLIMRX4", NULL, 0, + ARIZONA_SLIMBUS_RX_CHANNEL_ENABLE, + ARIZONA_SLIMRX4_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("SLIMTX1", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX2", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX2_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX3", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX3_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX4", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX4_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX5", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX5_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("SLIMTX6", NULL, 0, + ARIZONA_SLIMBUS_TX_CHANNEL_ENABLE, + ARIZONA_SLIMTX6_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIF3TX1", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_OUT("AIF3TX2", NULL, 0, + ARIZONA_AIF3_TX_ENABLES, ARIZONA_AIF3TX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_AIF_IN("AIF3RX1", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX1_ENA_SHIFT, 0), +SND_SOC_DAPM_AIF_IN("AIF3RX2", NULL, 0, + ARIZONA_AIF3_RX_ENABLES, ARIZONA_AIF3RX2_ENA_SHIFT, 0), + +SND_SOC_DAPM_PGA_E("OUT1L", SND_SOC_NOPM, + ARIZONA_OUT1L_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT1R", SND_SOC_NOPM, + ARIZONA_OUT1R_ENA_SHIFT, 0, NULL, 0, arizona_hp_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT2L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT2R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT2R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT3", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT3L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5L", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5L_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_PGA_E("OUT5R", ARIZONA_OUTPUT_ENABLES_1, + ARIZONA_OUT5R_ENA_SHIFT, 0, NULL, 0, arizona_out_ev, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_PGA("SPD1TX1", ARIZONA_SPD1_TX_CONTROL, + ARIZONA_SPD1_VAL1_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_PGA("SPD1TX2", ARIZONA_SPD1_TX_CONTROL, + ARIZONA_SPD1_VAL2_SHIFT, 0, NULL, 0), +SND_SOC_DAPM_OUT_DRV("SPD1", ARIZONA_SPD1_TX_CONTROL, + ARIZONA_SPD1_ENA_SHIFT, 0, NULL, 0), + +ARIZONA_MUX_WIDGETS(EQ1, "EQ1"), +ARIZONA_MUX_WIDGETS(EQ2, "EQ2"), +ARIZONA_MUX_WIDGETS(EQ3, "EQ3"), +ARIZONA_MUX_WIDGETS(EQ4, "EQ4"), + +ARIZONA_MUX_WIDGETS(DRC1L, "DRC1L"), +ARIZONA_MUX_WIDGETS(DRC1R, "DRC1R"), + +ARIZONA_MIXER_WIDGETS(LHPF1, "LHPF1"), +ARIZONA_MIXER_WIDGETS(LHPF2, "LHPF2"), +ARIZONA_MIXER_WIDGETS(LHPF3, "LHPF3"), +ARIZONA_MIXER_WIDGETS(LHPF4, "LHPF4"), + +ARIZONA_MIXER_WIDGETS(PWM1, "PWM1"), +ARIZONA_MIXER_WIDGETS(PWM2, "PWM2"), + +ARIZONA_MIXER_WIDGETS(OUT1L, "HPOUTL"), +ARIZONA_MIXER_WIDGETS(OUT1R, "HPOUTR"), +ARIZONA_MIXER_WIDGETS(OUT2L, "LINEOUTL"), +ARIZONA_MIXER_WIDGETS(OUT2R, "LINEOUTR"), +ARIZONA_MIXER_WIDGETS(OUT3, "EPOUT"), +ARIZONA_MIXER_WIDGETS(SPKOUTL, "SPKOUTL"), +ARIZONA_MIXER_WIDGETS(SPKOUTR, "SPKOUTR"), +ARIZONA_MIXER_WIDGETS(SPKDATL, "SPKDATL"), +ARIZONA_MIXER_WIDGETS(SPKDATR, "SPKDATR"), + +ARIZONA_MIXER_WIDGETS(AIF1TX1, "AIF1TX1"), +ARIZONA_MIXER_WIDGETS(AIF1TX2, "AIF1TX2"), +ARIZONA_MIXER_WIDGETS(AIF1TX3, "AIF1TX3"), +ARIZONA_MIXER_WIDGETS(AIF1TX4, "AIF1TX4"), +ARIZONA_MIXER_WIDGETS(AIF1TX5, "AIF1TX5"), +ARIZONA_MIXER_WIDGETS(AIF1TX6, "AIF1TX6"), + +ARIZONA_MIXER_WIDGETS(AIF2TX1, "AIF2TX1"), +ARIZONA_MIXER_WIDGETS(AIF2TX2, "AIF2TX2"), +ARIZONA_MIXER_WIDGETS(AIF2TX3, "AIF2TX3"), +ARIZONA_MIXER_WIDGETS(AIF2TX4, "AIF2TX4"), +ARIZONA_MIXER_WIDGETS(AIF2TX5, "AIF2TX5"), +ARIZONA_MIXER_WIDGETS(AIF2TX6, "AIF2TX6"), + +ARIZONA_MIXER_WIDGETS(AIF3TX1, "AIF3TX1"), +ARIZONA_MIXER_WIDGETS(AIF3TX2, "AIF3TX2"), + +ARIZONA_MUX_WIDGETS(SLIMTX1, "SLIMTX1"), +ARIZONA_MUX_WIDGETS(SLIMTX2, "SLIMTX2"), +ARIZONA_MUX_WIDGETS(SLIMTX3, "SLIMTX3"), +ARIZONA_MUX_WIDGETS(SLIMTX4, "SLIMTX4"), +ARIZONA_MUX_WIDGETS(SLIMTX5, "SLIMTX5"), +ARIZONA_MUX_WIDGETS(SLIMTX6, "SLIMTX6"), + +ARIZONA_MUX_WIDGETS(SPD1TX1, "SPDIFTX1"), +ARIZONA_MUX_WIDGETS(SPD1TX2, "SPDIFTX2"), + +ARIZONA_MUX_WIDGETS(ASRC1L, "ASRC1L"), +ARIZONA_MUX_WIDGETS(ASRC1R, "ASRC1R"), +ARIZONA_MUX_WIDGETS(ASRC2L, "ASRC2L"), +ARIZONA_MUX_WIDGETS(ASRC2R, "ASRC2R"), + +ARIZONA_MUX_WIDGETS(ISRC1DEC1, "ISRC1DEC1"), +ARIZONA_MUX_WIDGETS(ISRC1DEC2, "ISRC1DEC2"), +ARIZONA_MUX_WIDGETS(ISRC1DEC3, "ISRC1DEC3"), +ARIZONA_MUX_WIDGETS(ISRC1DEC4, "ISRC1DEC4"), + +ARIZONA_MUX_WIDGETS(ISRC1INT1, "ISRC1INT1"), +ARIZONA_MUX_WIDGETS(ISRC1INT2, "ISRC1INT2"), +ARIZONA_MUX_WIDGETS(ISRC1INT3, "ISRC1INT3"), +ARIZONA_MUX_WIDGETS(ISRC1INT4, "ISRC1INT4"), + +ARIZONA_MUX_WIDGETS(ISRC2DEC1, "ISRC2DEC1"), +ARIZONA_MUX_WIDGETS(ISRC2DEC2, "ISRC2DEC2"), + +ARIZONA_MUX_WIDGETS(ISRC2INT1, "ISRC2INT1"), +ARIZONA_MUX_WIDGETS(ISRC2INT2, "ISRC2INT2"), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LINEOUTL"), +SND_SOC_DAPM_OUTPUT("LINEOUTR"), +SND_SOC_DAPM_OUTPUT("EPOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTLN"), +SND_SOC_DAPM_OUTPUT("SPKOUTLP"), +SND_SOC_DAPM_OUTPUT("SPKOUTRN"), +SND_SOC_DAPM_OUTPUT("SPKOUTRP"), +SND_SOC_DAPM_OUTPUT("SPKDATL"), +SND_SOC_DAPM_OUTPUT("SPKDATR"), +SND_SOC_DAPM_OUTPUT("SPDIF"), + +SND_SOC_DAPM_OUTPUT("MICSUPP"), +}; + +#define ARIZONA_MIXER_INPUT_ROUTES(name) \ + { name, "Tone Generator 1", "Tone Generator 1" }, \ + { name, "Tone Generator 2", "Tone Generator 2" }, \ + { name, "Haptics", "HAPTICS" }, \ + { name, "AEC", "AEC1 Loopback" }, \ + { name, "AEC2", "AEC2 Loopback" }, \ + { name, "IN1L", "IN1L PGA" }, \ + { name, "IN1R", "IN1R PGA" }, \ + { name, "IN2L", "IN2 PGA" }, \ + { name, "AIF1RX1", "AIF1RX1" }, \ + { name, "AIF1RX2", "AIF1RX2" }, \ + { name, "AIF1RX3", "AIF1RX3" }, \ + { name, "AIF1RX4", "AIF1RX4" }, \ + { name, "AIF1RX5", "AIF1RX5" }, \ + { name, "AIF1RX6", "AIF1RX6" }, \ + { name, "AIF2RX1", "AIF2RX1" }, \ + { name, "AIF2RX2", "AIF2RX2" }, \ + { name, "AIF2RX3", "AIF2RX3" }, \ + { name, "AIF2RX4", "AIF2RX4" }, \ + { name, "AIF2RX5", "AIF2RX5" }, \ + { name, "AIF2RX6", "AIF2RX6" }, \ + { name, "AIF3RX1", "AIF3RX1" }, \ + { name, "AIF3RX2", "AIF3RX2" }, \ + { name, "SLIMRX1", "SLIMRX1" }, \ + { name, "SLIMRX2", "SLIMRX2" }, \ + { name, "SLIMRX3", "SLIMRX3" }, \ + { name, "SLIMRX4", "SLIMRX4" }, \ + { name, "EQ1", "EQ1" }, \ + { name, "EQ2", "EQ2" }, \ + { name, "EQ3", "EQ3" }, \ + { name, "EQ4", "EQ4" }, \ + { name, "DRC1L", "DRC1L" }, \ + { name, "DRC1R", "DRC1R" }, \ + { name, "LHPF1", "LHPF1" }, \ + { name, "LHPF2", "LHPF2" }, \ + { name, "LHPF3", "LHPF3" }, \ + { name, "LHPF4", "LHPF4" }, \ + { name, "ASRC1L", "ASRC1L" }, \ + { name, "ASRC1R", "ASRC1R" }, \ + { name, "ASRC2L", "ASRC2L" }, \ + { name, "ASRC2R", "ASRC2R" }, \ + { name, "ISRC1DEC1", "ISRC1DEC1" }, \ + { name, "ISRC1DEC2", "ISRC1DEC2" }, \ + { name, "ISRC1DEC3", "ISRC1DEC3" }, \ + { name, "ISRC1DEC4", "ISRC1DEC4" }, \ + { name, "ISRC1INT1", "ISRC1INT1" }, \ + { name, "ISRC1INT2", "ISRC1INT2" }, \ + { name, "ISRC1INT3", "ISRC1INT3" }, \ + { name, "ISRC1INT4", "ISRC1INT4" }, \ + { name, "ISRC2DEC1", "ISRC2DEC1" }, \ + { name, "ISRC2DEC2", "ISRC2DEC2" }, \ + { name, "ISRC2INT1", "ISRC2INT1" }, \ + { name, "ISRC2INT2", "ISRC2INT2" } + +static const struct snd_soc_dapm_route wm8998_dapm_routes[] = { + { "AIF2 Capture", NULL, "DBVDD2" }, + { "AIF2 Playback", NULL, "DBVDD2" }, + + { "AIF3 Capture", NULL, "DBVDD3" }, + { "AIF3 Playback", NULL, "DBVDD3" }, + + { "OUT1L", NULL, "CPVDD" }, + { "OUT1R", NULL, "CPVDD" }, + { "OUT2L", NULL, "CPVDD" }, + { "OUT2R", NULL, "CPVDD" }, + { "OUT3", NULL, "CPVDD" }, + + { "OUT4L", NULL, "SPKVDDL" }, + { "OUT4R", NULL, "SPKVDDR" }, + + { "OUT1L", NULL, "SYSCLK" }, + { "OUT1R", NULL, "SYSCLK" }, + { "OUT2L", NULL, "SYSCLK" }, + { "OUT2R", NULL, "SYSCLK" }, + { "OUT3", NULL, "SYSCLK" }, + { "OUT4L", NULL, "SYSCLK" }, + { "OUT4R", NULL, "SYSCLK" }, + { "OUT5L", NULL, "SYSCLK" }, + { "OUT5R", NULL, "SYSCLK" }, + + { "IN1AL", NULL, "SYSCLK" }, + { "IN1AR", NULL, "SYSCLK" }, + { "IN1BL", NULL, "SYSCLK" }, + { "IN1BR", NULL, "SYSCLK" }, + { "IN2A", NULL, "SYSCLK" }, + { "IN2B", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "SYSCLK" }, + { "ASRC1R", NULL, "SYSCLK" }, + { "ASRC2L", NULL, "SYSCLK" }, + { "ASRC2R", NULL, "SYSCLK" }, + + { "ASRC1L", NULL, "ASYNCCLK" }, + { "ASRC1R", NULL, "ASYNCCLK" }, + { "ASRC2L", NULL, "ASYNCCLK" }, + { "ASRC2R", NULL, "ASYNCCLK" }, + + { "SPD1", NULL, "SYSCLK" }, + { "SPD1", NULL, "SPD1TX1" }, + { "SPD1", NULL, "SPD1TX2" }, + + { "MICBIAS1", NULL, "MICVDD" }, + { "MICBIAS2", NULL, "MICVDD" }, + { "MICBIAS3", NULL, "MICVDD" }, + + { "Tone Generator 1", NULL, "SYSCLK" }, + { "Tone Generator 2", NULL, "SYSCLK" }, + + { "Tone Generator 1", NULL, "TONE" }, + { "Tone Generator 2", NULL, "TONE" }, + + { "AIF1 Capture", NULL, "AIF1TX1" }, + { "AIF1 Capture", NULL, "AIF1TX2" }, + { "AIF1 Capture", NULL, "AIF1TX3" }, + { "AIF1 Capture", NULL, "AIF1TX4" }, + { "AIF1 Capture", NULL, "AIF1TX5" }, + { "AIF1 Capture", NULL, "AIF1TX6" }, + + { "AIF1RX1", NULL, "AIF1 Playback" }, + { "AIF1RX2", NULL, "AIF1 Playback" }, + { "AIF1RX3", NULL, "AIF1 Playback" }, + { "AIF1RX4", NULL, "AIF1 Playback" }, + { "AIF1RX5", NULL, "AIF1 Playback" }, + { "AIF1RX6", NULL, "AIF1 Playback" }, + + { "AIF2 Capture", NULL, "AIF2TX1" }, + { "AIF2 Capture", NULL, "AIF2TX2" }, + { "AIF2 Capture", NULL, "AIF2TX3" }, + { "AIF2 Capture", NULL, "AIF2TX4" }, + { "AIF2 Capture", NULL, "AIF2TX5" }, + { "AIF2 Capture", NULL, "AIF2TX6" }, + + { "AIF2RX1", NULL, "AIF2 Playback" }, + { "AIF2RX2", NULL, "AIF2 Playback" }, + { "AIF2RX3", NULL, "AIF2 Playback" }, + { "AIF2RX4", NULL, "AIF2 Playback" }, + { "AIF2RX5", NULL, "AIF2 Playback" }, + { "AIF2RX6", NULL, "AIF2 Playback" }, + + { "AIF3 Capture", NULL, "AIF3TX1" }, + { "AIF3 Capture", NULL, "AIF3TX2" }, + + { "AIF3RX1", NULL, "AIF3 Playback" }, + { "AIF3RX2", NULL, "AIF3 Playback" }, + + { "Slim1 Capture", NULL, "SLIMTX1" }, + { "Slim1 Capture", NULL, "SLIMTX2" }, + { "Slim1 Capture", NULL, "SLIMTX3" }, + { "Slim1 Capture", NULL, "SLIMTX4" }, + + { "Slim2 Capture", NULL, "SLIMTX5" }, + { "Slim2 Capture", NULL, "SLIMTX6" }, + + { "SLIMRX1", NULL, "Slim1 Playback" }, + { "SLIMRX2", NULL, "Slim1 Playback" }, + + { "SLIMRX3", NULL, "Slim2 Playback" }, + { "SLIMRX4", NULL, "Slim2 Playback" }, + + { "AIF1 Playback", NULL, "SYSCLK" }, + { "AIF2 Playback", NULL, "SYSCLK" }, + { "AIF3 Playback", NULL, "SYSCLK" }, + { "Slim1 Playback", NULL, "SYSCLK" }, + { "Slim2 Playback", NULL, "SYSCLK" }, + + { "AIF1 Capture", NULL, "SYSCLK" }, + { "AIF2 Capture", NULL, "SYSCLK" }, + { "AIF3 Capture", NULL, "SYSCLK" }, + { "Slim1 Capture", NULL, "SYSCLK" }, + { "Slim2 Capture", NULL, "SYSCLK" }, + + { "IN1L Mux", "A", "IN1AL" }, + { "IN1R Mux", "A", "IN1AR" }, + { "IN1L Mux", "B", "IN1BL" }, + { "IN1R Mux", "B", "IN1BR" }, + + { "IN2 Mux", "A", "IN2A" }, + { "IN2 Mux", "B", "IN2B" }, + + { "IN1L PGA", NULL, "IN1L Mux" }, + { "IN1R PGA", NULL, "IN1R Mux" }, + { "IN2 PGA", NULL, "IN2 Mux" }, + + ARIZONA_MIXER_ROUTES("OUT1L", "HPOUTL"), + ARIZONA_MIXER_ROUTES("OUT1R", "HPOUTR"), + ARIZONA_MIXER_ROUTES("OUT2L", "LINEOUTL"), + ARIZONA_MIXER_ROUTES("OUT2R", "LINEOUTR"), + ARIZONA_MIXER_ROUTES("OUT3", "EPOUT"), + + ARIZONA_MIXER_ROUTES("OUT4L", "SPKOUTL"), + ARIZONA_MIXER_ROUTES("OUT4R", "SPKOUTR"), + ARIZONA_MIXER_ROUTES("OUT5L", "SPKDATL"), + ARIZONA_MIXER_ROUTES("OUT5R", "SPKDATR"), + + ARIZONA_MIXER_ROUTES("PWM1 Driver", "PWM1"), + ARIZONA_MIXER_ROUTES("PWM2 Driver", "PWM2"), + + ARIZONA_MIXER_ROUTES("AIF1TX1", "AIF1TX1"), + ARIZONA_MIXER_ROUTES("AIF1TX2", "AIF1TX2"), + ARIZONA_MIXER_ROUTES("AIF1TX3", "AIF1TX3"), + ARIZONA_MIXER_ROUTES("AIF1TX4", "AIF1TX4"), + ARIZONA_MIXER_ROUTES("AIF1TX5", "AIF1TX5"), + ARIZONA_MIXER_ROUTES("AIF1TX6", "AIF1TX6"), + + ARIZONA_MIXER_ROUTES("AIF2TX1", "AIF2TX1"), + ARIZONA_MIXER_ROUTES("AIF2TX2", "AIF2TX2"), + ARIZONA_MIXER_ROUTES("AIF2TX3", "AIF2TX3"), + ARIZONA_MIXER_ROUTES("AIF2TX4", "AIF2TX4"), + ARIZONA_MIXER_ROUTES("AIF2TX5", "AIF2TX5"), + ARIZONA_MIXER_ROUTES("AIF2TX6", "AIF2TX6"), + + ARIZONA_MIXER_ROUTES("AIF3TX1", "AIF3TX1"), + ARIZONA_MIXER_ROUTES("AIF3TX2", "AIF3TX2"), + + ARIZONA_MUX_ROUTES("SLIMTX1", "SLIMTX1"), + ARIZONA_MUX_ROUTES("SLIMTX2", "SLIMTX2"), + ARIZONA_MUX_ROUTES("SLIMTX3", "SLIMTX3"), + ARIZONA_MUX_ROUTES("SLIMTX4", "SLIMTX4"), + ARIZONA_MUX_ROUTES("SLIMTX5", "SLIMTX5"), + ARIZONA_MUX_ROUTES("SLIMTX6", "SLIMTX6"), + + ARIZONA_MUX_ROUTES("SPD1TX1", "SPDIFTX1"), + ARIZONA_MUX_ROUTES("SPD1TX2", "SPDIFTX2"), + + ARIZONA_MUX_ROUTES("EQ1", "EQ1"), + ARIZONA_MUX_ROUTES("EQ2", "EQ2"), + ARIZONA_MUX_ROUTES("EQ3", "EQ3"), + ARIZONA_MUX_ROUTES("EQ4", "EQ4"), + + ARIZONA_MUX_ROUTES("DRC1L", "DRC1L"), + ARIZONA_MUX_ROUTES("DRC1R", "DRC1R"), + + ARIZONA_MIXER_ROUTES("LHPF1", "LHPF1"), + ARIZONA_MIXER_ROUTES("LHPF2", "LHPF2"), + ARIZONA_MIXER_ROUTES("LHPF3", "LHPF3"), + ARIZONA_MIXER_ROUTES("LHPF4", "LHPF4"), + + ARIZONA_MUX_ROUTES("ASRC1L", "ASRC1L"), + ARIZONA_MUX_ROUTES("ASRC1R", "ASRC1R"), + ARIZONA_MUX_ROUTES("ASRC2L", "ASRC2L"), + ARIZONA_MUX_ROUTES("ASRC2R", "ASRC2R"), + + ARIZONA_MUX_ROUTES("ISRC1INT1", "ISRC1INT1"), + ARIZONA_MUX_ROUTES("ISRC1INT2", "ISRC1INT2"), + ARIZONA_MUX_ROUTES("ISRC1INT3", "ISRC1INT3"), + ARIZONA_MUX_ROUTES("ISRC1INT4", "ISRC1INT4"), + + ARIZONA_MUX_ROUTES("ISRC1DEC1", "ISRC1DEC1"), + ARIZONA_MUX_ROUTES("ISRC1DEC2", "ISRC1DEC2"), + ARIZONA_MUX_ROUTES("ISRC1DEC3", "ISRC1DEC3"), + ARIZONA_MUX_ROUTES("ISRC1DEC4", "ISRC1DEC4"), + + ARIZONA_MUX_ROUTES("ISRC2INT1", "ISRC2INT1"), + ARIZONA_MUX_ROUTES("ISRC2INT2", "ISRC2INT2"), + + ARIZONA_MUX_ROUTES("ISRC2DEC1", "ISRC2DEC1"), + ARIZONA_MUX_ROUTES("ISRC2DEC2", "ISRC2DEC2"), + + { "AEC1 Loopback", "HPOUTL", "OUT1L" }, + { "AEC1 Loopback", "HPOUTR", "OUT1R" }, + { "AEC2 Loopback", "HPOUTL", "OUT1L" }, + { "AEC2 Loopback", "HPOUTR", "OUT1R" }, + { "HPOUTL", NULL, "OUT1L" }, + { "HPOUTR", NULL, "OUT1R" }, + + { "AEC1 Loopback", "LINEOUTL", "OUT2L" }, + { "AEC1 Loopback", "LINEOUTR", "OUT2R" }, + { "AEC2 Loopback", "LINEOUTL", "OUT2L" }, + { "AEC2 Loopback", "LINEOUTR", "OUT2R" }, + { "LINEOUTL", NULL, "OUT2L" }, + { "LINEOUTR", NULL, "OUT2R" }, + + { "AEC1 Loopback", "EPOUT", "OUT3" }, + { "AEC2 Loopback", "EPOUT", "OUT3" }, + { "EPOUT", NULL, "OUT3" }, + + { "AEC1 Loopback", "SPKOUTL", "OUT4L" }, + { "AEC2 Loopback", "SPKOUTL", "OUT4L" }, + { "SPKOUTLN", NULL, "OUT4L" }, + { "SPKOUTLP", NULL, "OUT4L" }, + + { "AEC1 Loopback", "SPKOUTR", "OUT4R" }, + { "AEC2 Loopback", "SPKOUTR", "OUT4R" }, + { "SPKOUTRN", NULL, "OUT4R" }, + { "SPKOUTRP", NULL, "OUT4R" }, + + { "SPDIF", NULL, "SPD1" }, + + { "AEC1 Loopback", "SPKDATL", "OUT5L" }, + { "AEC1 Loopback", "SPKDATR", "OUT5R" }, + { "AEC2 Loopback", "SPKDATL", "OUT5L" }, + { "AEC2 Loopback", "SPKDATR", "OUT5R" }, + { "SPKDATL", NULL, "OUT5L" }, + { "SPKDATR", NULL, "OUT5R" }, + + { "MICSUPP", NULL, "SYSCLK" }, + + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "DRC1L" }, + { "DRC1 Signal Activity", NULL, "DRC1R" }, +}; + +#define WM8998_RATES SNDRV_PCM_RATE_KNOT + +#define WM8998_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver wm8998_dai[] = { + { + .name = "wm8998-aif1", + .id = 1, + .base = ARIZONA_AIF1_BCLK_CTRL, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 6, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm8998-aif2", + .id = 2, + .base = ARIZONA_AIF2_BCLK_CTRL, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 6, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm8998-aif3", + .id = 3, + .base = ARIZONA_AIF3_BCLK_CTRL, + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .ops = &arizona_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "wm8998-slim1", + .id = 4, + .playback = { + .stream_name = "Slim1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .capture = { + .stream_name = "Slim1 Capture", + .channels_min = 1, + .channels_max = 4, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, + { + .name = "wm8998-slim2", + .id = 5, + .playback = { + .stream_name = "Slim2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .capture = { + .stream_name = "Slim2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8998_RATES, + .formats = WM8998_FORMATS, + }, + .ops = &arizona_simple_dai_ops, + }, +}; + +static int wm8998_set_fll(struct snd_soc_component *component, int fll_id, + int source, unsigned int Fref, unsigned int Fout) +{ + struct wm8998_priv *wm8998 = snd_soc_component_get_drvdata(component); + + switch (fll_id) { + case WM8998_FLL1: + return arizona_set_fll(&wm8998->fll[0], source, Fref, Fout); + case WM8998_FLL2: + return arizona_set_fll(&wm8998->fll[1], source, Fref, Fout); + case WM8998_FLL1_REFCLK: + return arizona_set_fll_refclk(&wm8998->fll[0], source, Fref, + Fout); + case WM8998_FLL2_REFCLK: + return arizona_set_fll_refclk(&wm8998->fll[1], source, Fref, + Fout); + default: + return -EINVAL; + } +} + +static int wm8998_component_probe(struct snd_soc_component *component) +{ + struct wm8998_priv *priv = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct arizona *arizona = priv->core.arizona; + int ret; + + arizona->dapm = dapm; + snd_soc_component_init_regmap(component, arizona->regmap); + + ret = arizona_init_spk(component); + if (ret < 0) + return ret; + + arizona_init_gpio(component); + + snd_soc_component_disable_pin(component, "HAPTICS"); + + return 0; +} + +static void wm8998_component_remove(struct snd_soc_component *component) +{ + struct wm8998_priv *priv = snd_soc_component_get_drvdata(component); + + priv->core.arizona->dapm = NULL; +} + +#define WM8998_DIG_VU 0x0200 + +static unsigned int wm8998_digital_vu[] = { + ARIZONA_DAC_DIGITAL_VOLUME_1L, + ARIZONA_DAC_DIGITAL_VOLUME_1R, + ARIZONA_DAC_DIGITAL_VOLUME_2L, + ARIZONA_DAC_DIGITAL_VOLUME_2R, + ARIZONA_DAC_DIGITAL_VOLUME_3L, + ARIZONA_DAC_DIGITAL_VOLUME_4L, + ARIZONA_DAC_DIGITAL_VOLUME_4R, + ARIZONA_DAC_DIGITAL_VOLUME_5L, + ARIZONA_DAC_DIGITAL_VOLUME_5R, +}; + +static const struct snd_soc_component_driver soc_component_dev_wm8998 = { + .probe = wm8998_component_probe, + .remove = wm8998_component_remove, + .set_sysclk = arizona_set_sysclk, + .set_pll = wm8998_set_fll, + .controls = wm8998_snd_controls, + .num_controls = ARRAY_SIZE(wm8998_snd_controls), + .dapm_widgets = wm8998_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8998_dapm_widgets), + .dapm_routes = wm8998_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8998_dapm_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm8998_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + struct wm8998_priv *wm8998; + int i, ret; + + wm8998 = devm_kzalloc(&pdev->dev, sizeof(struct wm8998_priv), + GFP_KERNEL); + if (!wm8998) + return -ENOMEM; + platform_set_drvdata(pdev, wm8998); + + if (IS_ENABLED(CONFIG_OF)) { + if (!dev_get_platdata(arizona->dev)) { + ret = arizona_of_get_audio_pdata(arizona); + if (ret < 0) + return ret; + } + } + + wm8998->core.arizona = arizona; + wm8998->core.num_inputs = 3; /* IN1L, IN1R, IN2 */ + + for (i = 0; i < ARRAY_SIZE(wm8998->fll); i++) + wm8998->fll[i].vco_mult = 1; + + arizona_init_fll(arizona, 1, ARIZONA_FLL1_CONTROL_1 - 1, + ARIZONA_IRQ_FLL1_LOCK, ARIZONA_IRQ_FLL1_CLOCK_OK, + &wm8998->fll[0]); + arizona_init_fll(arizona, 2, ARIZONA_FLL2_CONTROL_1 - 1, + ARIZONA_IRQ_FLL2_LOCK, ARIZONA_IRQ_FLL2_CLOCK_OK, + &wm8998->fll[1]); + + for (i = 0; i < ARRAY_SIZE(wm8998_dai); i++) + arizona_init_dai(&wm8998->core, i); + + /* Latch volume update bits */ + for (i = 0; i < ARRAY_SIZE(wm8998_digital_vu); i++) + regmap_update_bits(arizona->regmap, wm8998_digital_vu[i], + WM8998_DIG_VU, WM8998_DIG_VU); + + pm_runtime_enable(&pdev->dev); + pm_runtime_idle(&pdev->dev); + + arizona_init_common(arizona); + + ret = arizona_init_spk_irqs(arizona); + if (ret < 0) + goto err_pm_disable; + + ret = devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm8998, + wm8998_dai, + ARRAY_SIZE(wm8998_dai)); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register component: %d\n", ret); + goto err_spk_irqs; + } + + return ret; + +err_spk_irqs: + arizona_free_spk_irqs(arizona); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int wm8998_remove(struct platform_device *pdev) +{ + struct wm8998_priv *wm8998 = platform_get_drvdata(pdev); + struct arizona *arizona = wm8998->core.arizona; + + pm_runtime_disable(&pdev->dev); + + arizona_free_spk_irqs(arizona); + + return 0; +} + +static struct platform_driver wm8998_codec_driver = { + .driver = { + .name = "wm8998-codec", + }, + .probe = wm8998_probe, + .remove = wm8998_remove, +}; + +module_platform_driver(wm8998_codec_driver); + +MODULE_DESCRIPTION("ASoC WM8998 driver"); +MODULE_AUTHOR("Richard Fitzgerald "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:wm8998-codec"); diff --git a/sound/soc/codecs/wm8998.h b/sound/soc/codecs/wm8998.h new file mode 100644 index 000000000..1e8647252 --- /dev/null +++ b/sound/soc/codecs/wm8998.h @@ -0,0 +1,23 @@ +/* + * wm8998.h -- ALSA SoC Audio driver for WM8998 codecs + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Author: Richard Fitzgerald + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8998_H +#define _WM8998_H + +#include "arizona.h" + +#define WM8998_FLL1 1 +#define WM8998_FLL2 2 +#define WM8998_FLL1_REFCLK 3 +#define WM8998_FLL2_REFCLK 4 + +#endif diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c new file mode 100644 index 000000000..399255d1f --- /dev/null +++ b/sound/soc/codecs/wm9081.c @@ -0,0 +1,1388 @@ +/* + * wm9081.c -- WM9081 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * Copyright 2009-12 Wolfson Microelectronics plc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "wm9081.h" + +static const struct reg_default wm9081_reg[] = { + { 2, 0x00B9 }, /* R2 - Analogue Lineout */ + { 3, 0x00B9 }, /* R3 - Analogue Speaker PGA */ + { 4, 0x0001 }, /* R4 - VMID Control */ + { 5, 0x0068 }, /* R5 - Bias Control 1 */ + { 7, 0x0000 }, /* R7 - Analogue Mixer */ + { 8, 0x0000 }, /* R8 - Anti Pop Control */ + { 9, 0x01DB }, /* R9 - Analogue Speaker 1 */ + { 10, 0x0018 }, /* R10 - Analogue Speaker 2 */ + { 11, 0x0180 }, /* R11 - Power Management */ + { 12, 0x0000 }, /* R12 - Clock Control 1 */ + { 13, 0x0038 }, /* R13 - Clock Control 2 */ + { 14, 0x4000 }, /* R14 - Clock Control 3 */ + { 16, 0x0000 }, /* R16 - FLL Control 1 */ + { 17, 0x0200 }, /* R17 - FLL Control 2 */ + { 18, 0x0000 }, /* R18 - FLL Control 3 */ + { 19, 0x0204 }, /* R19 - FLL Control 4 */ + { 20, 0x0000 }, /* R20 - FLL Control 5 */ + { 22, 0x0000 }, /* R22 - Audio Interface 1 */ + { 23, 0x0002 }, /* R23 - Audio Interface 2 */ + { 24, 0x0008 }, /* R24 - Audio Interface 3 */ + { 25, 0x0022 }, /* R25 - Audio Interface 4 */ + { 27, 0x0006 }, /* R27 - Interrupt Status Mask */ + { 28, 0x0000 }, /* R28 - Interrupt Polarity */ + { 29, 0x0000 }, /* R29 - Interrupt Control */ + { 30, 0x00C0 }, /* R30 - DAC Digital 1 */ + { 31, 0x0008 }, /* R31 - DAC Digital 2 */ + { 32, 0x09AF }, /* R32 - DRC 1 */ + { 33, 0x4201 }, /* R33 - DRC 2 */ + { 34, 0x0000 }, /* R34 - DRC 3 */ + { 35, 0x0000 }, /* R35 - DRC 4 */ + { 38, 0x0000 }, /* R38 - Write Sequencer 1 */ + { 39, 0x0000 }, /* R39 - Write Sequencer 2 */ + { 40, 0x0002 }, /* R40 - MW Slave 1 */ + { 42, 0x0000 }, /* R42 - EQ 1 */ + { 43, 0x0000 }, /* R43 - EQ 2 */ + { 44, 0x0FCA }, /* R44 - EQ 3 */ + { 45, 0x0400 }, /* R45 - EQ 4 */ + { 46, 0x00B8 }, /* R46 - EQ 5 */ + { 47, 0x1EB5 }, /* R47 - EQ 6 */ + { 48, 0xF145 }, /* R48 - EQ 7 */ + { 49, 0x0B75 }, /* R49 - EQ 8 */ + { 50, 0x01C5 }, /* R50 - EQ 9 */ + { 51, 0x169E }, /* R51 - EQ 10 */ + { 52, 0xF829 }, /* R52 - EQ 11 */ + { 53, 0x07AD }, /* R53 - EQ 12 */ + { 54, 0x1103 }, /* R54 - EQ 13 */ + { 55, 0x1C58 }, /* R55 - EQ 14 */ + { 56, 0xF373 }, /* R56 - EQ 15 */ + { 57, 0x0A54 }, /* R57 - EQ 16 */ + { 58, 0x0558 }, /* R58 - EQ 17 */ + { 59, 0x0564 }, /* R59 - EQ 18 */ + { 60, 0x0559 }, /* R60 - EQ 19 */ + { 61, 0x4000 }, /* R61 - EQ 20 */ +}; + +static struct { + int ratio; + int clk_sys_rate; +} clk_sys_rates[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +static struct { + int rate; + int sample_rate; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 88200, 9 }, + { 96000, 10 }, +}; + +static struct { + int div; /* *10 due to .5s */ + int bclk_div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 55, 6 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 110, 10 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 15 }, + { 250, 16 }, + { 300, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + +struct wm9081_priv { + struct regmap *regmap; + int sysclk_source; + int mclk_rate; + int sysclk_rate; + int fs; + int bclk; + int master; + int fll_fref; + int fll_fout; + int tdm_width; + struct wm9081_pdata pdata; +}; + +static bool wm9081_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM9081_SOFTWARE_RESET: + case WM9081_INTERRUPT_STATUS: + return true; + default: + return false; + } +} + +static bool wm9081_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM9081_SOFTWARE_RESET: + case WM9081_ANALOGUE_LINEOUT: + case WM9081_ANALOGUE_SPEAKER_PGA: + case WM9081_VMID_CONTROL: + case WM9081_BIAS_CONTROL_1: + case WM9081_ANALOGUE_MIXER: + case WM9081_ANTI_POP_CONTROL: + case WM9081_ANALOGUE_SPEAKER_1: + case WM9081_ANALOGUE_SPEAKER_2: + case WM9081_POWER_MANAGEMENT: + case WM9081_CLOCK_CONTROL_1: + case WM9081_CLOCK_CONTROL_2: + case WM9081_CLOCK_CONTROL_3: + case WM9081_FLL_CONTROL_1: + case WM9081_FLL_CONTROL_2: + case WM9081_FLL_CONTROL_3: + case WM9081_FLL_CONTROL_4: + case WM9081_FLL_CONTROL_5: + case WM9081_AUDIO_INTERFACE_1: + case WM9081_AUDIO_INTERFACE_2: + case WM9081_AUDIO_INTERFACE_3: + case WM9081_AUDIO_INTERFACE_4: + case WM9081_INTERRUPT_STATUS: + case WM9081_INTERRUPT_STATUS_MASK: + case WM9081_INTERRUPT_POLARITY: + case WM9081_INTERRUPT_CONTROL: + case WM9081_DAC_DIGITAL_1: + case WM9081_DAC_DIGITAL_2: + case WM9081_DRC_1: + case WM9081_DRC_2: + case WM9081_DRC_3: + case WM9081_DRC_4: + case WM9081_WRITE_SEQUENCER_1: + case WM9081_WRITE_SEQUENCER_2: + case WM9081_MW_SLAVE_1: + case WM9081_EQ_1: + case WM9081_EQ_2: + case WM9081_EQ_3: + case WM9081_EQ_4: + case WM9081_EQ_5: + case WM9081_EQ_6: + case WM9081_EQ_7: + case WM9081_EQ_8: + case WM9081_EQ_9: + case WM9081_EQ_10: + case WM9081_EQ_11: + case WM9081_EQ_12: + case WM9081_EQ_13: + case WM9081_EQ_14: + case WM9081_EQ_15: + case WM9081_EQ_16: + case WM9081_EQ_17: + case WM9081_EQ_18: + case WM9081_EQ_19: + case WM9081_EQ_20: + return true; + default: + return false; + } +} + +static int wm9081_reset(struct regmap *map) +{ + return regmap_write(map, WM9081_SOFTWARE_RESET, 0x9081); +} + +static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_out_tlv, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0); +static const DECLARE_TLV_DB_RANGE(drc_max_tlv, + 0, 0, TLV_DB_SCALE_ITEM(1200, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -300, 50, 0); + +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(in_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); + +static const char *drc_high_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "1/16", + "0", +}; + +static SOC_ENUM_SINGLE_DECL(drc_high, WM9081_DRC_3, 3, drc_high_text); + +static const char *drc_low_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "0", +}; + +static SOC_ENUM_SINGLE_DECL(drc_low, WM9081_DRC_3, 0, drc_low_text); + +static const char *drc_atk_text[] = { + "181us", + "181us", + "363us", + "726us", + "1.45ms", + "2.9ms", + "5.8ms", + "11.6ms", + "23.2ms", + "46.4ms", + "92.8ms", + "185.6ms", +}; + +static SOC_ENUM_SINGLE_DECL(drc_atk, WM9081_DRC_2, 12, drc_atk_text); + +static const char *drc_dcy_text[] = { + "186ms", + "372ms", + "743ms", + "1.49s", + "2.97s", + "5.94s", + "11.89s", + "23.78s", + "47.56s", +}; + +static SOC_ENUM_SINGLE_DECL(drc_dcy, WM9081_DRC_2, 8, drc_dcy_text); + +static const char *drc_qr_dcy_text[] = { + "0.725ms", + "1.45ms", + "5.8ms", +}; + +static SOC_ENUM_SINGLE_DECL(drc_qr_dcy, WM9081_DRC_2, 4, drc_qr_dcy_text); + +static const char *dac_deemph_text[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", +}; + +static SOC_ENUM_SINGLE_DECL(dac_deemph, WM9081_DAC_DIGITAL_2, 1, + dac_deemph_text); + +static const char *speaker_mode_text[] = { + "Class D", + "Class AB", +}; + +static SOC_ENUM_SINGLE_DECL(speaker_mode, WM9081_ANALOGUE_SPEAKER_2, 6, + speaker_mode_text); + +static int speaker_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg; + + reg = snd_soc_component_read32(component, WM9081_ANALOGUE_SPEAKER_2); + if (reg & WM9081_SPK_MODE) + ucontrol->value.enumerated.item[0] = 1; + else + ucontrol->value.enumerated.item[0] = 0; + + return 0; +} + +/* + * Stop any attempts to change speaker mode while the speaker is enabled. + * + * We also have some special anti-pop controls dependent on speaker + * mode which must be changed along with the mode. + */ +static int speaker_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int reg_pwr = snd_soc_component_read32(component, WM9081_POWER_MANAGEMENT); + unsigned int reg2 = snd_soc_component_read32(component, WM9081_ANALOGUE_SPEAKER_2); + + /* Are we changing anything? */ + if (ucontrol->value.enumerated.item[0] == + ((reg2 & WM9081_SPK_MODE) != 0)) + return 0; + + /* Don't try to change modes while enabled */ + if (reg_pwr & WM9081_SPK_ENA) + return -EINVAL; + + if (ucontrol->value.enumerated.item[0]) { + /* Class AB */ + reg2 &= ~(WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL); + reg2 |= WM9081_SPK_MODE; + } else { + /* Class D */ + reg2 |= WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL; + reg2 &= ~WM9081_SPK_MODE; + } + + snd_soc_component_write(component, WM9081_ANALOGUE_SPEAKER_2, reg2); + + return 0; +} + +static const struct snd_kcontrol_new wm9081_snd_controls[] = { +SOC_SINGLE_TLV("IN1 Volume", WM9081_ANALOGUE_MIXER, 1, 1, 1, in_tlv), +SOC_SINGLE_TLV("IN2 Volume", WM9081_ANALOGUE_MIXER, 3, 1, 1, in_tlv), + +SOC_SINGLE_TLV("Playback Volume", WM9081_DAC_DIGITAL_1, 1, 96, 0, dac_tlv), + +SOC_SINGLE("LINEOUT Switch", WM9081_ANALOGUE_LINEOUT, 7, 1, 1), +SOC_SINGLE("LINEOUT ZC Switch", WM9081_ANALOGUE_LINEOUT, 6, 1, 0), +SOC_SINGLE_TLV("LINEOUT Volume", WM9081_ANALOGUE_LINEOUT, 0, 63, 0, out_tlv), + +SOC_SINGLE("DRC Switch", WM9081_DRC_1, 15, 1, 0), +SOC_ENUM("DRC High Slope", drc_high), +SOC_ENUM("DRC Low Slope", drc_low), +SOC_SINGLE_TLV("DRC Input Volume", WM9081_DRC_4, 5, 60, 1, drc_in_tlv), +SOC_SINGLE_TLV("DRC Output Volume", WM9081_DRC_4, 0, 30, 1, drc_out_tlv), +SOC_SINGLE_TLV("DRC Minimum Volume", WM9081_DRC_2, 2, 3, 1, drc_min_tlv), +SOC_SINGLE_TLV("DRC Maximum Volume", WM9081_DRC_2, 0, 3, 0, drc_max_tlv), +SOC_ENUM("DRC Attack", drc_atk), +SOC_ENUM("DRC Decay", drc_dcy), +SOC_SINGLE("DRC Quick Release Switch", WM9081_DRC_1, 2, 1, 0), +SOC_SINGLE_TLV("DRC Quick Release Volume", WM9081_DRC_2, 6, 3, 0, drc_qr_tlv), +SOC_ENUM("DRC Quick Release Decay", drc_qr_dcy), +SOC_SINGLE_TLV("DRC Startup Volume", WM9081_DRC_1, 6, 18, 0, drc_startup_tlv), + +SOC_SINGLE("EQ Switch", WM9081_EQ_1, 0, 1, 0), + +SOC_SINGLE("Speaker DC Volume", WM9081_ANALOGUE_SPEAKER_1, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM9081_ANALOGUE_SPEAKER_1, 0, 5, 0), +SOC_SINGLE("Speaker Switch", WM9081_ANALOGUE_SPEAKER_PGA, 7, 1, 1), +SOC_SINGLE("Speaker ZC Switch", WM9081_ANALOGUE_SPEAKER_PGA, 6, 1, 0), +SOC_SINGLE_TLV("Speaker Volume", WM9081_ANALOGUE_SPEAKER_PGA, 0, 63, 0, + out_tlv), +SOC_ENUM("DAC Deemphasis", dac_deemph), +SOC_ENUM_EXT("Speaker Mode", speaker_mode, speaker_mode_get, speaker_mode_put), +}; + +static const struct snd_kcontrol_new wm9081_eq_controls[] = { +SOC_SINGLE_TLV("EQ1 Volume", WM9081_EQ_1, 11, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Volume", WM9081_EQ_1, 6, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Volume", WM9081_EQ_1, 1, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Volume", WM9081_EQ_2, 11, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ5 Volume", WM9081_EQ_2, 6, 24, 0, eq_tlv), +}; + +static const struct snd_kcontrol_new mixer[] = { +SOC_DAPM_SINGLE("IN1 Switch", WM9081_ANALOGUE_MIXER, 0, 1, 0), +SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0), +}; + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_clk_ref_div; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + while ((Fref / div) > 13500000) { + div *= 2; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + fll_div->fll_clk_ref_div = div / 2; + + pr_debug("Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 0; + target = Fout * 2; + while (target < 90000000) { + div++; + target *= 2; + if (div > 7) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + fll_div->fll_outdiv = div; + + pr_debug("Fvco=%dHz\n", target); + + /* Find an appropriate FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + target /= fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + fll_div->n = Ndiv; + Nmod = target % Fref; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", + fll_div->n, fll_div->k, + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_clk_ref_div); + + return 0; +} + +static int wm9081_set_fll(struct snd_soc_component *component, int fll_id, + unsigned int Fref, unsigned int Fout) +{ + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + u16 reg1, reg4, reg5; + struct _fll_div fll_div; + int ret; + int clk_sys_reg; + + /* Any change? */ + if (Fref == wm9081->fll_fref && Fout == wm9081->fll_fout) + return 0; + + /* Disable the FLL */ + if (Fout == 0) { + dev_dbg(component->dev, "FLL disabled\n"); + wm9081->fll_fref = 0; + wm9081->fll_fout = 0; + + return 0; + } + + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + + reg5 = snd_soc_component_read32(component, WM9081_FLL_CONTROL_5); + reg5 &= ~WM9081_FLL_CLK_SRC_MASK; + + switch (fll_id) { + case WM9081_SYSCLK_FLL_MCLK: + reg5 |= 0x1; + break; + + default: + dev_err(component->dev, "Unknown FLL ID %d\n", fll_id); + return -EINVAL; + } + + /* Disable CLK_SYS while we reconfigure */ + clk_sys_reg = snd_soc_component_read32(component, WM9081_CLOCK_CONTROL_3); + if (clk_sys_reg & WM9081_CLK_SYS_ENA) + snd_soc_component_write(component, WM9081_CLOCK_CONTROL_3, + clk_sys_reg & ~WM9081_CLK_SYS_ENA); + + /* Any FLL configuration change requires that the FLL be + * disabled first. */ + reg1 = snd_soc_component_read32(component, WM9081_FLL_CONTROL_1); + reg1 &= ~WM9081_FLL_ENA; + snd_soc_component_write(component, WM9081_FLL_CONTROL_1, reg1); + + /* Apply the configuration */ + if (fll_div.k) + reg1 |= WM9081_FLL_FRAC_MASK; + else + reg1 &= ~WM9081_FLL_FRAC_MASK; + snd_soc_component_write(component, WM9081_FLL_CONTROL_1, reg1); + + snd_soc_component_write(component, WM9081_FLL_CONTROL_2, + (fll_div.fll_outdiv << WM9081_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio << WM9081_FLL_FRATIO_SHIFT)); + snd_soc_component_write(component, WM9081_FLL_CONTROL_3, fll_div.k); + + reg4 = snd_soc_component_read32(component, WM9081_FLL_CONTROL_4); + reg4 &= ~WM9081_FLL_N_MASK; + reg4 |= fll_div.n << WM9081_FLL_N_SHIFT; + snd_soc_component_write(component, WM9081_FLL_CONTROL_4, reg4); + + reg5 &= ~WM9081_FLL_CLK_REF_DIV_MASK; + reg5 |= fll_div.fll_clk_ref_div << WM9081_FLL_CLK_REF_DIV_SHIFT; + snd_soc_component_write(component, WM9081_FLL_CONTROL_5, reg5); + + /* Set gain to the recommended value */ + snd_soc_component_update_bits(component, WM9081_FLL_CONTROL_4, + WM9081_FLL_GAIN_MASK, 0); + + /* Enable the FLL */ + snd_soc_component_write(component, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA); + + /* Then bring CLK_SYS up again if it was disabled */ + if (clk_sys_reg & WM9081_CLK_SYS_ENA) + snd_soc_component_write(component, WM9081_CLOCK_CONTROL_3, clk_sys_reg); + + dev_dbg(component->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout); + + wm9081->fll_fref = Fref; + wm9081->fll_fout = Fout; + + return 0; +} + +static int configure_clock(struct snd_soc_component *component) +{ + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + int new_sysclk, i, target; + unsigned int reg; + int ret = 0; + int mclkdiv = 0; + int fll = 0; + + switch (wm9081->sysclk_source) { + case WM9081_SYSCLK_MCLK: + if (wm9081->mclk_rate > 12225000) { + mclkdiv = 1; + wm9081->sysclk_rate = wm9081->mclk_rate / 2; + } else { + wm9081->sysclk_rate = wm9081->mclk_rate; + } + wm9081_set_fll(component, WM9081_SYSCLK_FLL_MCLK, 0, 0); + break; + + case WM9081_SYSCLK_FLL_MCLK: + /* If we have a sample rate calculate a CLK_SYS that + * gives us a suitable DAC configuration, plus BCLK. + * Ideally we would check to see if we can clock + * directly from MCLK and only use the FLL if this is + * not the case, though care must be taken with free + * running mode. + */ + if (wm9081->master && wm9081->bclk) { + /* Make sure we can generate CLK_SYS and BCLK + * and that we've got 3MHz for optimal + * performance. */ + for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { + target = wm9081->fs * clk_sys_rates[i].ratio; + new_sysclk = target; + if (target >= wm9081->bclk && + target > 3000000) + break; + } + + if (i == ARRAY_SIZE(clk_sys_rates)) + return -EINVAL; + + } else if (wm9081->fs) { + for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { + new_sysclk = clk_sys_rates[i].ratio + * wm9081->fs; + if (new_sysclk > 3000000) + break; + } + + if (i == ARRAY_SIZE(clk_sys_rates)) + return -EINVAL; + + } else { + new_sysclk = 12288000; + } + + ret = wm9081_set_fll(component, WM9081_SYSCLK_FLL_MCLK, + wm9081->mclk_rate, new_sysclk); + if (ret == 0) { + wm9081->sysclk_rate = new_sysclk; + + /* Switch SYSCLK over to FLL */ + fll = 1; + } else { + wm9081->sysclk_rate = wm9081->mclk_rate; + } + break; + + default: + return -EINVAL; + } + + reg = snd_soc_component_read32(component, WM9081_CLOCK_CONTROL_1); + if (mclkdiv) + reg |= WM9081_MCLKDIV2; + else + reg &= ~WM9081_MCLKDIV2; + snd_soc_component_write(component, WM9081_CLOCK_CONTROL_1, reg); + + reg = snd_soc_component_read32(component, WM9081_CLOCK_CONTROL_3); + if (fll) + reg |= WM9081_CLK_SRC_SEL; + else + reg &= ~WM9081_CLK_SRC_SEL; + snd_soc_component_write(component, WM9081_CLOCK_CONTROL_3, reg); + + dev_dbg(component->dev, "CLK_SYS is %dHz\n", wm9081->sysclk_rate); + + return ret; +} + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + + /* This should be done on init() for bypass paths */ + switch (wm9081->sysclk_source) { + case WM9081_SYSCLK_MCLK: + dev_dbg(component->dev, "Using %dHz MCLK\n", wm9081->mclk_rate); + break; + case WM9081_SYSCLK_FLL_MCLK: + dev_dbg(component->dev, "Using %dHz MCLK with FLL\n", + wm9081->mclk_rate); + break; + default: + dev_err(component->dev, "System clock not configured\n"); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + configure_clock(component); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Disable the FLL if it's running */ + wm9081_set_fll(component, 0, 0, 0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget wm9081_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1"), +SND_SOC_DAPM_INPUT("IN2"), + +SND_SOC_DAPM_DAC("DAC", NULL, WM9081_POWER_MANAGEMENT, 0, 0), + +SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, + mixer, ARRAY_SIZE(mixer)), + +SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0), +SND_SOC_DAPM_OUT_DRV("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("LINEOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), + +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM9081_CLOCK_CONTROL_3, 0, 0, clk_sys_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM9081_CLOCK_CONTROL_3, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TSENSE", WM9081_POWER_MANAGEMENT, 7, 0, NULL, 0), +}; + + +static const struct snd_soc_dapm_route wm9081_audio_paths[] = { + { "DAC", NULL, "CLK_SYS" }, + { "DAC", NULL, "CLK_DSP" }, + { "DAC", NULL, "AIF" }, + + { "Mixer", "IN1 Switch", "IN1" }, + { "Mixer", "IN2 Switch", "IN2" }, + { "Mixer", "Playback Switch", "DAC" }, + + { "LINEOUT PGA", NULL, "Mixer" }, + { "LINEOUT PGA", NULL, "TOCLK" }, + { "LINEOUT PGA", NULL, "CLK_SYS" }, + + { "LINEOUT", NULL, "LINEOUT PGA" }, + + { "Speaker PGA", NULL, "Mixer" }, + { "Speaker PGA", NULL, "TOCLK" }, + { "Speaker PGA", NULL, "CLK_SYS" }, + + { "Speaker", NULL, "Speaker PGA" }, + { "Speaker", NULL, "TSENSE" }, + + { "SPKN", NULL, "Speaker" }, + { "SPKP", NULL, "Speaker" }, +}; + +static int wm9081_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*40k */ + snd_soc_component_update_bits(component, WM9081_VMID_CONTROL, + WM9081_VMID_SEL_MASK, 0x2); + + /* Normal bias current */ + snd_soc_component_update_bits(component, WM9081_BIAS_CONTROL_1, + WM9081_STBY_BIAS_ENA, 0); + break; + + case SND_SOC_BIAS_STANDBY: + /* Initial cold start */ + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + regcache_cache_only(wm9081->regmap, false); + regcache_sync(wm9081->regmap); + + /* Disable LINEOUT discharge */ + snd_soc_component_update_bits(component, WM9081_ANTI_POP_CONTROL, + WM9081_LINEOUT_DISCH, 0); + + /* Select startup bias source */ + snd_soc_component_update_bits(component, WM9081_BIAS_CONTROL_1, + WM9081_BIAS_SRC | WM9081_BIAS_ENA, + WM9081_BIAS_SRC | WM9081_BIAS_ENA); + + /* VMID 2*4k; Soft VMID ramp enable */ + snd_soc_component_update_bits(component, WM9081_VMID_CONTROL, + WM9081_VMID_RAMP | + WM9081_VMID_SEL_MASK, + WM9081_VMID_RAMP | 0x6); + + mdelay(100); + + /* Normal bias enable & soft start off */ + snd_soc_component_update_bits(component, WM9081_VMID_CONTROL, + WM9081_VMID_RAMP, 0); + + /* Standard bias source */ + snd_soc_component_update_bits(component, WM9081_BIAS_CONTROL_1, + WM9081_BIAS_SRC, 0); + } + + /* VMID 2*240k */ + snd_soc_component_update_bits(component, WM9081_VMID_CONTROL, + WM9081_VMID_SEL_MASK, 0x04); + + /* Standby bias current on */ + snd_soc_component_update_bits(component, WM9081_BIAS_CONTROL_1, + WM9081_STBY_BIAS_ENA, + WM9081_STBY_BIAS_ENA); + break; + + case SND_SOC_BIAS_OFF: + /* Startup bias source and disable bias */ + snd_soc_component_update_bits(component, WM9081_BIAS_CONTROL_1, + WM9081_BIAS_SRC | WM9081_BIAS_ENA, + WM9081_BIAS_SRC); + + /* Disable VMID with soft ramping */ + snd_soc_component_update_bits(component, WM9081_VMID_CONTROL, + WM9081_VMID_RAMP | WM9081_VMID_SEL_MASK, + WM9081_VMID_RAMP); + + /* Actively discharge LINEOUT */ + snd_soc_component_update_bits(component, WM9081_ANTI_POP_CONTROL, + WM9081_LINEOUT_DISCH, + WM9081_LINEOUT_DISCH); + + regcache_cache_only(wm9081->regmap, true); + break; + } + + return 0; +} + +static int wm9081_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + unsigned int aif2 = snd_soc_component_read32(component, WM9081_AUDIO_INTERFACE_2); + + aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV | + WM9081_BCLK_DIR | WM9081_LRCLK_DIR | WM9081_AIF_FMT_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + wm9081->master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif2 |= WM9081_LRCLK_DIR; + wm9081->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif2 |= WM9081_BCLK_DIR; + wm9081->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif2 |= WM9081_LRCLK_DIR | WM9081_BCLK_DIR; + wm9081->master = 1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif2 |= WM9081_AIF_LRCLK_INV; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + aif2 |= 0x3; + break; + case SND_SOC_DAIFMT_I2S: + aif2 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif2 |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif2 |= WM9081_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif2 |= WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif2 |= WM9081_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif2 |= WM9081_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM9081_AUDIO_INTERFACE_2, aif2); + + return 0; +} + +static int wm9081_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + int ret, i, best, best_val, cur_val; + unsigned int clk_ctrl2, aif1, aif2, aif3, aif4; + + clk_ctrl2 = snd_soc_component_read32(component, WM9081_CLOCK_CONTROL_2); + clk_ctrl2 &= ~(WM9081_CLK_SYS_RATE_MASK | WM9081_SAMPLE_RATE_MASK); + + aif1 = snd_soc_component_read32(component, WM9081_AUDIO_INTERFACE_1); + + aif2 = snd_soc_component_read32(component, WM9081_AUDIO_INTERFACE_2); + aif2 &= ~WM9081_AIF_WL_MASK; + + aif3 = snd_soc_component_read32(component, WM9081_AUDIO_INTERFACE_3); + aif3 &= ~WM9081_BCLK_DIV_MASK; + + aif4 = snd_soc_component_read32(component, WM9081_AUDIO_INTERFACE_4); + aif4 &= ~WM9081_LRCLK_RATE_MASK; + + wm9081->fs = params_rate(params); + + if (wm9081->tdm_width) { + /* If TDM is set up then that fixes our BCLK. */ + int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >> + WM9081_AIFDAC_TDM_MODE_SHIFT) + 1; + + wm9081->bclk = wm9081->fs * wm9081->tdm_width * slots; + } else { + /* Otherwise work out a BCLK from the sample size */ + wm9081->bclk = 2 * wm9081->fs; + + switch (params_width(params)) { + case 16: + wm9081->bclk *= 16; + break; + case 20: + wm9081->bclk *= 20; + aif2 |= 0x4; + break; + case 24: + wm9081->bclk *= 24; + aif2 |= 0x8; + break; + case 32: + wm9081->bclk *= 32; + aif2 |= 0xc; + break; + default: + return -EINVAL; + } + } + + dev_dbg(component->dev, "Target BCLK is %dHz\n", wm9081->bclk); + + ret = configure_clock(component); + if (ret != 0) + return ret; + + /* Select nearest CLK_SYS_RATE */ + best = 0; + best_val = abs((wm9081->sysclk_rate / clk_sys_rates[0].ratio) + - wm9081->fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) { + cur_val = abs((wm9081->sysclk_rate / + clk_sys_rates[i].ratio) - wm9081->fs); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(component->dev, "Selected CLK_SYS_RATIO of %d\n", + clk_sys_rates[best].ratio); + clk_ctrl2 |= (clk_sys_rates[best].clk_sys_rate + << WM9081_CLK_SYS_RATE_SHIFT); + + /* SAMPLE_RATE */ + best = 0; + best_val = abs(wm9081->fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(wm9081->fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(component->dev, "Selected SAMPLE_RATE of %dHz\n", + sample_rates[best].rate); + clk_ctrl2 |= (sample_rates[best].sample_rate + << WM9081_SAMPLE_RATE_SHIFT); + + /* BCLK_DIV */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = ((wm9081->sysclk_rate * 10) / bclk_divs[i].div) + - wm9081->bclk; + if (cur_val < 0) /* Table is sorted */ + break; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + wm9081->bclk = (wm9081->sysclk_rate * 10) / bclk_divs[best].div; + dev_dbg(component->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n", + bclk_divs[best].div, wm9081->bclk); + aif3 |= bclk_divs[best].bclk_div; + + /* LRCLK is a simple fraction of BCLK */ + dev_dbg(component->dev, "LRCLK_RATE is %d\n", wm9081->bclk / wm9081->fs); + aif4 |= wm9081->bclk / wm9081->fs; + + /* Apply a ReTune Mobile configuration if it's in use */ + if (wm9081->pdata.num_retune_configs) { + struct wm9081_pdata *pdata = &wm9081->pdata; + struct wm9081_retune_mobile_setting *s; + int eq1; + + best = 0; + best_val = abs(pdata->retune_configs[0].rate - wm9081->fs); + for (i = 0; i < pdata->num_retune_configs; i++) { + cur_val = abs(pdata->retune_configs[i].rate - + wm9081->fs); + if (cur_val < best_val) { + best_val = cur_val; + best = i; + } + } + s = &pdata->retune_configs[best]; + + dev_dbg(component->dev, "ReTune Mobile %s tuned for %dHz\n", + s->name, s->rate); + + /* If the EQ is enabled then disable it while we write out */ + eq1 = snd_soc_component_read32(component, WM9081_EQ_1) & WM9081_EQ_ENA; + if (eq1 & WM9081_EQ_ENA) + snd_soc_component_write(component, WM9081_EQ_1, 0); + + /* Write out the other values */ + for (i = 1; i < ARRAY_SIZE(s->config); i++) + snd_soc_component_write(component, WM9081_EQ_1 + i, s->config[i]); + + eq1 |= (s->config[0] & ~WM9081_EQ_ENA); + snd_soc_component_write(component, WM9081_EQ_1, eq1); + } + + snd_soc_component_write(component, WM9081_CLOCK_CONTROL_2, clk_ctrl2); + snd_soc_component_write(component, WM9081_AUDIO_INTERFACE_2, aif2); + snd_soc_component_write(component, WM9081_AUDIO_INTERFACE_3, aif3); + snd_soc_component_write(component, WM9081_AUDIO_INTERFACE_4, aif4); + + return 0; +} + +static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_component *component = codec_dai->component; + unsigned int reg; + + reg = snd_soc_component_read32(component, WM9081_DAC_DIGITAL_2); + + if (mute) + reg |= WM9081_DAC_MUTE; + else + reg &= ~WM9081_DAC_MUTE; + + snd_soc_component_write(component, WM9081_DAC_DIGITAL_2, reg); + + return 0; +} + +static int wm9081_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM9081_SYSCLK_MCLK: + case WM9081_SYSCLK_FLL_MCLK: + wm9081->sysclk_source = clk_id; + wm9081->mclk_rate = freq; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + unsigned int aif1 = snd_soc_component_read32(component, WM9081_AUDIO_INTERFACE_1); + + aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK); + + if (slots < 0 || slots > 4) + return -EINVAL; + + wm9081->tdm_width = slot_width; + + if (slots == 0) + slots = 1; + + aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT; + + switch (rx_mask) { + case 1: + break; + case 2: + aif1 |= 0x10; + break; + case 4: + aif1 |= 0x20; + break; + case 8: + aif1 |= 0x30; + break; + default: + return -EINVAL; + } + + snd_soc_component_write(component, WM9081_AUDIO_INTERFACE_1, aif1); + + return 0; +} + +#define WM9081_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM9081_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm9081_dai_ops = { + .hw_params = wm9081_hw_params, + .set_fmt = wm9081_set_dai_fmt, + .digital_mute = wm9081_digital_mute, + .set_tdm_slot = wm9081_set_tdm_slot, +}; + +/* We report two channels because the CODEC processes a stereo signal, even + * though it is only capable of handling a mono output. + */ +static struct snd_soc_dai_driver wm9081_dai = { + .name = "wm9081-hifi", + .playback = { + .stream_name = "AIF", + .channels_min = 1, + .channels_max = 2, + .rates = WM9081_RATES, + .formats = WM9081_FORMATS, + }, + .ops = &wm9081_dai_ops, +}; + +static int wm9081_probe(struct snd_soc_component *component) +{ + struct wm9081_priv *wm9081 = snd_soc_component_get_drvdata(component); + + /* Enable zero cross by default */ + snd_soc_component_update_bits(component, WM9081_ANALOGUE_LINEOUT, + WM9081_LINEOUTZC, WM9081_LINEOUTZC); + snd_soc_component_update_bits(component, WM9081_ANALOGUE_SPEAKER_PGA, + WM9081_SPKPGAZC, WM9081_SPKPGAZC); + + if (!wm9081->pdata.num_retune_configs) { + dev_dbg(component->dev, + "No ReTune Mobile data, using normal EQ\n"); + snd_soc_add_component_controls(component, wm9081_eq_controls, + ARRAY_SIZE(wm9081_eq_controls)); + } + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm9081 = { + .probe = wm9081_probe, + .set_sysclk = wm9081_set_sysclk, + .set_bias_level = wm9081_set_bias_level, + .controls = wm9081_snd_controls, + .num_controls = ARRAY_SIZE(wm9081_snd_controls), + .dapm_widgets = wm9081_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm9081_dapm_widgets), + .dapm_routes = wm9081_audio_paths, + .num_dapm_routes = ARRAY_SIZE(wm9081_audio_paths), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm9081_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM9081_MAX_REGISTER, + .reg_defaults = wm9081_reg, + .num_reg_defaults = ARRAY_SIZE(wm9081_reg), + .volatile_reg = wm9081_volatile_register, + .readable_reg = wm9081_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int wm9081_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm9081_priv *wm9081; + unsigned int reg; + int ret; + + wm9081 = devm_kzalloc(&i2c->dev, sizeof(struct wm9081_priv), + GFP_KERNEL); + if (wm9081 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, wm9081); + + wm9081->regmap = devm_regmap_init_i2c(i2c, &wm9081_regmap); + if (IS_ERR(wm9081->regmap)) { + ret = PTR_ERR(wm9081->regmap); + dev_err(&i2c->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + ret = regmap_read(wm9081->regmap, WM9081_SOFTWARE_RESET, ®); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to read chip ID: %d\n", ret); + return ret; + } + if (reg != 0x9081) { + dev_err(&i2c->dev, "Device is not a WM9081: ID=0x%x\n", reg); + return -EINVAL; + } + + ret = wm9081_reset(wm9081->regmap); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + return ret; + } + + if (dev_get_platdata(&i2c->dev)) + memcpy(&wm9081->pdata, dev_get_platdata(&i2c->dev), + sizeof(wm9081->pdata)); + + reg = 0; + if (wm9081->pdata.irq_high) + reg |= WM9081_IRQ_POL; + if (!wm9081->pdata.irq_cmos) + reg |= WM9081_IRQ_OP_CTRL; + regmap_update_bits(wm9081->regmap, WM9081_INTERRUPT_CONTROL, + WM9081_IRQ_POL | WM9081_IRQ_OP_CTRL, reg); + + regcache_cache_only(wm9081->regmap, true); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm9081, &wm9081_dai, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int wm9081_i2c_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wm9081_i2c_id[] = { + { "wm9081", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id); + +static struct i2c_driver wm9081_i2c_driver = { + .driver = { + .name = "wm9081", + }, + .probe = wm9081_i2c_probe, + .remove = wm9081_i2c_remove, + .id_table = wm9081_i2c_id, +}; + +module_i2c_driver(wm9081_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM9081 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9081.h b/sound/soc/codecs/wm9081.h new file mode 100644 index 000000000..871cccb06 --- /dev/null +++ b/sound/soc/codecs/wm9081.h @@ -0,0 +1,784 @@ +#ifndef WM9081_H +#define WM9081_H + +/* + * wm9081.c -- WM9081 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * Copyright 2009 Wolfson Microelectronics plc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +/* + * SYSCLK sources + */ +#define WM9081_SYSCLK_MCLK 1 /* Use MCLK without FLL */ +#define WM9081_SYSCLK_FLL_MCLK 2 /* Use MCLK, enabling FLL if required */ + +/* + * Register values. + */ +#define WM9081_SOFTWARE_RESET 0x00 +#define WM9081_ANALOGUE_LINEOUT 0x02 +#define WM9081_ANALOGUE_SPEAKER_PGA 0x03 +#define WM9081_VMID_CONTROL 0x04 +#define WM9081_BIAS_CONTROL_1 0x05 +#define WM9081_ANALOGUE_MIXER 0x07 +#define WM9081_ANTI_POP_CONTROL 0x08 +#define WM9081_ANALOGUE_SPEAKER_1 0x09 +#define WM9081_ANALOGUE_SPEAKER_2 0x0A +#define WM9081_POWER_MANAGEMENT 0x0B +#define WM9081_CLOCK_CONTROL_1 0x0C +#define WM9081_CLOCK_CONTROL_2 0x0D +#define WM9081_CLOCK_CONTROL_3 0x0E +#define WM9081_FLL_CONTROL_1 0x10 +#define WM9081_FLL_CONTROL_2 0x11 +#define WM9081_FLL_CONTROL_3 0x12 +#define WM9081_FLL_CONTROL_4 0x13 +#define WM9081_FLL_CONTROL_5 0x14 +#define WM9081_AUDIO_INTERFACE_1 0x16 +#define WM9081_AUDIO_INTERFACE_2 0x17 +#define WM9081_AUDIO_INTERFACE_3 0x18 +#define WM9081_AUDIO_INTERFACE_4 0x19 +#define WM9081_INTERRUPT_STATUS 0x1A +#define WM9081_INTERRUPT_STATUS_MASK 0x1B +#define WM9081_INTERRUPT_POLARITY 0x1C +#define WM9081_INTERRUPT_CONTROL 0x1D +#define WM9081_DAC_DIGITAL_1 0x1E +#define WM9081_DAC_DIGITAL_2 0x1F +#define WM9081_DRC_1 0x20 +#define WM9081_DRC_2 0x21 +#define WM9081_DRC_3 0x22 +#define WM9081_DRC_4 0x23 +#define WM9081_WRITE_SEQUENCER_1 0x26 +#define WM9081_WRITE_SEQUENCER_2 0x27 +#define WM9081_MW_SLAVE_1 0x28 +#define WM9081_EQ_1 0x2A +#define WM9081_EQ_2 0x2B +#define WM9081_EQ_3 0x2C +#define WM9081_EQ_4 0x2D +#define WM9081_EQ_5 0x2E +#define WM9081_EQ_6 0x2F +#define WM9081_EQ_7 0x30 +#define WM9081_EQ_8 0x31 +#define WM9081_EQ_9 0x32 +#define WM9081_EQ_10 0x33 +#define WM9081_EQ_11 0x34 +#define WM9081_EQ_12 0x35 +#define WM9081_EQ_13 0x36 +#define WM9081_EQ_14 0x37 +#define WM9081_EQ_15 0x38 +#define WM9081_EQ_16 0x39 +#define WM9081_EQ_17 0x3A +#define WM9081_EQ_18 0x3B +#define WM9081_EQ_19 0x3C +#define WM9081_EQ_20 0x3D + +#define WM9081_REGISTER_COUNT 55 +#define WM9081_MAX_REGISTER 0x3D + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM9081_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM9081_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM9081_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R2 (0x02) - Analogue Lineout + */ +#define WM9081_LINEOUT_MUTE 0x0080 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_MASK 0x0080 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_SHIFT 7 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_WIDTH 1 /* LINEOUT_MUTE */ +#define WM9081_LINEOUTZC 0x0040 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_MASK 0x0040 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_SHIFT 6 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_WIDTH 1 /* LINEOUTZC */ +#define WM9081_LINEOUT_VOL_MASK 0x003F /* LINEOUT_VOL - [5:0] */ +#define WM9081_LINEOUT_VOL_SHIFT 0 /* LINEOUT_VOL - [5:0] */ +#define WM9081_LINEOUT_VOL_WIDTH 6 /* LINEOUT_VOL - [5:0] */ + +/* + * R3 (0x03) - Analogue Speaker PGA + */ +#define WM9081_SPKPGA_MUTE 0x0080 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_MASK 0x0080 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_SHIFT 7 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_WIDTH 1 /* SPKPGA_MUTE */ +#define WM9081_SPKPGAZC 0x0040 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_MASK 0x0040 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_SHIFT 6 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_WIDTH 1 /* SPKPGAZC */ +#define WM9081_SPKPGA_VOL_MASK 0x003F /* SPKPGA_VOL - [5:0] */ +#define WM9081_SPKPGA_VOL_SHIFT 0 /* SPKPGA_VOL - [5:0] */ +#define WM9081_SPKPGA_VOL_WIDTH 6 /* SPKPGA_VOL - [5:0] */ + +/* + * R4 (0x04) - VMID Control + */ +#define WM9081_VMID_BUF_ENA 0x0020 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_MASK 0x0020 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_SHIFT 5 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM9081_VMID_RAMP 0x0008 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_MASK 0x0008 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_SHIFT 3 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_WIDTH 1 /* VMID_RAMP */ +#define WM9081_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_FAST_ST 0x0001 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_MASK 0x0001 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_SHIFT 0 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_WIDTH 1 /* VMID_FAST_ST */ + +/* + * R5 (0x05) - Bias Control 1 + */ +#define WM9081_BIAS_SRC 0x0040 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_MASK 0x0040 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_SHIFT 6 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_WIDTH 1 /* BIAS_SRC */ +#define WM9081_STBY_BIAS_LVL 0x0020 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_MASK 0x0020 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_SHIFT 5 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_WIDTH 1 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_ENA 0x0010 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_MASK 0x0010 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_SHIFT 4 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_WIDTH 1 /* STBY_BIAS_ENA */ +#define WM9081_BIAS_LVL_MASK 0x000C /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_LVL_SHIFT 2 /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_LVL_WIDTH 2 /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_ENA 0x0002 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_MASK 0x0002 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_SHIFT 1 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA 0x0001 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_MASK 0x0001 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_SHIFT 0 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ + +/* + * R7 (0x07) - Analogue Mixer + */ +#define WM9081_DAC_SEL 0x0010 /* DAC_SEL */ +#define WM9081_DAC_SEL_MASK 0x0010 /* DAC_SEL */ +#define WM9081_DAC_SEL_SHIFT 4 /* DAC_SEL */ +#define WM9081_DAC_SEL_WIDTH 1 /* DAC_SEL */ +#define WM9081_IN2_VOL 0x0008 /* IN2_VOL */ +#define WM9081_IN2_VOL_MASK 0x0008 /* IN2_VOL */ +#define WM9081_IN2_VOL_SHIFT 3 /* IN2_VOL */ +#define WM9081_IN2_VOL_WIDTH 1 /* IN2_VOL */ +#define WM9081_IN2_ENA 0x0004 /* IN2_ENA */ +#define WM9081_IN2_ENA_MASK 0x0004 /* IN2_ENA */ +#define WM9081_IN2_ENA_SHIFT 2 /* IN2_ENA */ +#define WM9081_IN2_ENA_WIDTH 1 /* IN2_ENA */ +#define WM9081_IN1_VOL 0x0002 /* IN1_VOL */ +#define WM9081_IN1_VOL_MASK 0x0002 /* IN1_VOL */ +#define WM9081_IN1_VOL_SHIFT 1 /* IN1_VOL */ +#define WM9081_IN1_VOL_WIDTH 1 /* IN1_VOL */ +#define WM9081_IN1_ENA 0x0001 /* IN1_ENA */ +#define WM9081_IN1_ENA_MASK 0x0001 /* IN1_ENA */ +#define WM9081_IN1_ENA_SHIFT 0 /* IN1_ENA */ +#define WM9081_IN1_ENA_WIDTH 1 /* IN1_ENA */ + +/* + * R8 (0x08) - Anti Pop Control + */ +#define WM9081_LINEOUT_DISCH 0x0004 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_MASK 0x0004 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_SHIFT 2 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_WIDTH 1 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_VROI 0x0002 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_MASK 0x0002 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_SHIFT 1 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_WIDTH 1 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_CLAMP 0x0001 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_MASK 0x0001 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_SHIFT 0 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_WIDTH 1 /* LINEOUT_CLAMP */ + +/* + * R9 (0x09) - Analogue Speaker 1 + */ +#define WM9081_SPK_DCGAIN_MASK 0x0038 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_DCGAIN_SHIFT 3 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_DCGAIN_WIDTH 3 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_ACGAIN_MASK 0x0007 /* SPK_ACGAIN - [2:0] */ +#define WM9081_SPK_ACGAIN_SHIFT 0 /* SPK_ACGAIN - [2:0] */ +#define WM9081_SPK_ACGAIN_WIDTH 3 /* SPK_ACGAIN - [2:0] */ + +/* + * R10 (0x0A) - Analogue Speaker 2 + */ +#define WM9081_SPK_MODE 0x0040 /* SPK_MODE */ +#define WM9081_SPK_MODE_MASK 0x0040 /* SPK_MODE */ +#define WM9081_SPK_MODE_SHIFT 6 /* SPK_MODE */ +#define WM9081_SPK_MODE_WIDTH 1 /* SPK_MODE */ +#define WM9081_SPK_INV_MUTE 0x0010 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_MASK 0x0010 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_SHIFT 4 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_WIDTH 1 /* SPK_INV_MUTE */ +#define WM9081_OUT_SPK_CTRL 0x0008 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_MASK 0x0008 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_SHIFT 3 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_WIDTH 1 /* OUT_SPK_CTRL */ + +/* + * R11 (0x0B) - Power Management + */ +#define WM9081_TSHUT_ENA 0x0100 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_MASK 0x0100 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_SHIFT 8 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM9081_TSENSE_ENA 0x0080 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_MASK 0x0080 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_SHIFT 7 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_WIDTH 1 /* TSENSE_ENA */ +#define WM9081_TEMP_SHUT 0x0040 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_MASK 0x0040 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_SHIFT 6 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */ +#define WM9081_LINEOUT_ENA 0x0010 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_MASK 0x0010 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_SHIFT 4 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_WIDTH 1 /* LINEOUT_ENA */ +#define WM9081_SPKPGA_ENA 0x0004 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_MASK 0x0004 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_SHIFT 2 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_WIDTH 1 /* SPKPGA_ENA */ +#define WM9081_SPK_ENA 0x0002 /* SPK_ENA */ +#define WM9081_SPK_ENA_MASK 0x0002 /* SPK_ENA */ +#define WM9081_SPK_ENA_SHIFT 1 /* SPK_ENA */ +#define WM9081_SPK_ENA_WIDTH 1 /* SPK_ENA */ +#define WM9081_DAC_ENA 0x0001 /* DAC_ENA */ +#define WM9081_DAC_ENA_MASK 0x0001 /* DAC_ENA */ +#define WM9081_DAC_ENA_SHIFT 0 /* DAC_ENA */ +#define WM9081_DAC_ENA_WIDTH 1 /* DAC_ENA */ + +/* + * R12 (0x0C) - Clock Control 1 + */ +#define WM9081_CLK_OP_DIV_MASK 0x1C00 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_OP_DIV_SHIFT 10 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_OP_DIV_WIDTH 3 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_TO_DIV_MASK 0x0300 /* CLK_TO_DIV - [9:8] */ +#define WM9081_CLK_TO_DIV_SHIFT 8 /* CLK_TO_DIV - [9:8] */ +#define WM9081_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [9:8] */ +#define WM9081_MCLKDIV2 0x0080 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_MASK 0x0080 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_SHIFT 7 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ + +/* + * R13 (0x0D) - Clock Control 2 + */ +#define WM9081_CLK_SYS_RATE_MASK 0x00F0 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_CLK_SYS_RATE_SHIFT 4 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */ +#define WM9081_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */ +#define WM9081_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */ + +/* + * R14 (0x0E) - Clock Control 3 + */ +#define WM9081_CLK_SRC_SEL 0x2000 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_MASK 0x2000 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_SHIFT 13 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_WIDTH 1 /* CLK_SRC_SEL */ +#define WM9081_CLK_OP_ENA 0x0020 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_MASK 0x0020 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_SHIFT 5 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_WIDTH 1 /* CLK_OP_ENA */ +#define WM9081_CLK_TO_ENA 0x0004 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_MASK 0x0004 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_SHIFT 2 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_WIDTH 1 /* CLK_TO_ENA */ +#define WM9081_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM9081_CLK_SYS_ENA 0x0001 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_MASK 0x0001 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_SHIFT 0 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ + +/* + * R16 (0x10) - FLL Control 1 + */ +#define WM9081_FLL_HOLD 0x0008 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_MASK 0x0008 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_SHIFT 3 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_WIDTH 1 /* FLL_HOLD */ +#define WM9081_FLL_FRAC 0x0004 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_SHIFT 2 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_WIDTH 1 /* FLL_FRAC */ +#define WM9081_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM9081_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM9081_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM9081_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R17 (0x11) - FLL Control 2 + */ +#define WM9081_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM9081_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM9081_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R18 (0x12) - FLL Control 3 + */ +#define WM9081_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */ +#define WM9081_FLL_K_SHIFT 0 /* FLL_K - [15:0] */ +#define WM9081_FLL_K_WIDTH 16 /* FLL_K - [15:0] */ + +/* + * R19 (0x13) - FLL Control 4 + */ +#define WM9081_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */ +#define WM9081_FLL_N_SHIFT 5 /* FLL_N - [14:5] */ +#define WM9081_FLL_N_WIDTH 10 /* FLL_N - [14:5] */ +#define WM9081_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */ +#define WM9081_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */ +#define WM9081_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */ + +/* + * R20 (0x14) - FLL Control 5 + */ +#define WM9081_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */ +#define WM9081_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */ +#define WM9081_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */ + +/* + * R22 (0x16) - Audio Interface 1 + */ +#define WM9081_AIFDAC_CHAN 0x0040 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_MASK 0x0040 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_SHIFT 6 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_WIDTH 1 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_TDM_SLOT_MASK 0x0030 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_SLOT_SHIFT 4 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_SLOT_WIDTH 2 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_MODE_MASK 0x000C /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_AIFDAC_TDM_MODE_SHIFT 2 /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_AIFDAC_TDM_MODE_WIDTH 2 /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM9081_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM9081_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM9081_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM9081_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R23 (0x17) - Audio Interface 2 + */ +#define WM9081_AIF_TRIS 0x0200 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_MASK 0x0200 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_SHIFT 9 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_WIDTH 1 /* AIF_TRIS */ +#define WM9081_DAC_DAT_INV 0x0100 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_MASK 0x0100 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_SHIFT 8 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_WIDTH 1 /* DAC_DAT_INV */ +#define WM9081_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM9081_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM9081_LRCLK_DIR 0x0020 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_MASK 0x0020 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_SHIFT 5 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM9081_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM9081_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM9081_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM9081_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM9081_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM9081_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM9081_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R24 (0x18) - Audio Interface 3 + */ +#define WM9081_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM9081_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM9081_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R25 (0x19) - Audio Interface 4 + */ +#define WM9081_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM9081_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM9081_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R26 (0x1A) - Interrupt Status + */ +#define WM9081_WSEQ_BUSY_EINT 0x0004 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_MASK 0x0004 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_SHIFT 2 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM9081_TSHUT_EINT 0x0001 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_MASK 0x0001 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_SHIFT 0 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_WIDTH 1 /* TSHUT_EINT */ + +/* + * R27 (0x1B) - Interrupt Status Mask + */ +#define WM9081_IM_WSEQ_BUSY_EINT 0x0004 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_MASK 0x0004 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_SHIFT 2 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_TSHUT_EINT 0x0001 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_MASK 0x0001 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_SHIFT 0 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_WIDTH 1 /* IM_TSHUT_EINT */ + +/* + * R28 (0x1C) - Interrupt Polarity + */ +#define WM9081_TSHUT_INV 0x0001 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_MASK 0x0001 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_SHIFT 0 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_WIDTH 1 /* TSHUT_INV */ + +/* + * R29 (0x1D) - Interrupt Control + */ +#define WM9081_IRQ_POL 0x8000 /* IRQ_POL */ +#define WM9081_IRQ_POL_MASK 0x8000 /* IRQ_POL */ +#define WM9081_IRQ_POL_SHIFT 15 /* IRQ_POL */ +#define WM9081_IRQ_POL_WIDTH 1 /* IRQ_POL */ +#define WM9081_IRQ_OP_CTRL 0x0001 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_MASK 0x0001 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_SHIFT 0 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_WIDTH 1 /* IRQ_OP_CTRL */ + +/* + * R30 (0x1E) - DAC Digital 1 + */ +#define WM9081_DAC_VOL_MASK 0x00FF /* DAC_VOL - [7:0] */ +#define WM9081_DAC_VOL_SHIFT 0 /* DAC_VOL - [7:0] */ +#define WM9081_DAC_VOL_WIDTH 8 /* DAC_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital 2 + */ +#define WM9081_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM9081_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM9081_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM9081_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R32 (0x20) - DRC 1 + */ +#define WM9081_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM9081_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM9081_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM9081_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM9081_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_FF_DLY 0x0020 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_MASK 0x0020 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_SHIFT 5 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_WIDTH 1 /* DRC_FF_DLY */ +#define WM9081_DRC_QR 0x0004 /* DRC_QR */ +#define WM9081_DRC_QR_MASK 0x0004 /* DRC_QR */ +#define WM9081_DRC_QR_SHIFT 2 /* DRC_QR */ +#define WM9081_DRC_QR_WIDTH 1 /* DRC_QR */ +#define WM9081_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */ + +/* + * R33 (0x21) - DRC 2 + */ +#define WM9081_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM9081_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM9081_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R34 (0x22) - DRC 3 + */ +#define WM9081_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */ +#define WM9081_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */ +#define WM9081_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */ + +/* + * R35 (0x23) - DRC 4 + */ +#define WM9081_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */ +#define WM9081_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */ +#define WM9081_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */ + +/* + * R38 (0x26) - Write Sequencer 1 + */ +#define WM9081_WSEQ_ENA 0x8000 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM9081_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM9081_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM9081_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM9081_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM9081_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM9081_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */ +#define WM9081_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */ +#define WM9081_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */ + +/* + * R39 (0x27) - Write Sequencer 2 + */ +#define WM9081_WSEQ_CURRENT_INDEX_MASK 0x07F0 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R40 (0x28) - MW Slave 1 + */ +#define WM9081_SPI_CFG 0x0020 /* SPI_CFG */ +#define WM9081_SPI_CFG_MASK 0x0020 /* SPI_CFG */ +#define WM9081_SPI_CFG_SHIFT 5 /* SPI_CFG */ +#define WM9081_SPI_CFG_WIDTH 1 /* SPI_CFG */ +#define WM9081_SPI_4WIRE 0x0010 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_MASK 0x0010 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_SHIFT 4 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */ +#define WM9081_ARA_ENA 0x0008 /* ARA_ENA */ +#define WM9081_ARA_ENA_MASK 0x0008 /* ARA_ENA */ +#define WM9081_ARA_ENA_SHIFT 3 /* ARA_ENA */ +#define WM9081_ARA_ENA_WIDTH 1 /* ARA_ENA */ +#define WM9081_AUTO_INC 0x0002 /* AUTO_INC */ +#define WM9081_AUTO_INC_MASK 0x0002 /* AUTO_INC */ +#define WM9081_AUTO_INC_SHIFT 1 /* AUTO_INC */ +#define WM9081_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +/* + * R42 (0x2A) - EQ 1 + */ +#define WM9081_EQ_B1_GAIN_MASK 0xF800 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B1_GAIN_SHIFT 11 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B2_GAIN_MASK 0x07C0 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B2_GAIN_SHIFT 6 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B4_GAIN_MASK 0x003E /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_B4_GAIN_SHIFT 1 /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_ENA 0x0001 /* EQ_ENA */ +#define WM9081_EQ_ENA_MASK 0x0001 /* EQ_ENA */ +#define WM9081_EQ_ENA_SHIFT 0 /* EQ_ENA */ +#define WM9081_EQ_ENA_WIDTH 1 /* EQ_ENA */ + +/* + * R43 (0x2B) - EQ 2 + */ +#define WM9081_EQ_B3_GAIN_MASK 0xF800 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B3_GAIN_SHIFT 11 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B5_GAIN_MASK 0x07C0 /* EQ_B5_GAIN - [10:6] */ +#define WM9081_EQ_B5_GAIN_SHIFT 6 /* EQ_B5_GAIN - [10:6] */ +#define WM9081_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [10:6] */ + +/* + * R44 (0x2C) - EQ 3 + */ +#define WM9081_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */ +#define WM9081_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */ +#define WM9081_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */ + +/* + * R45 (0x2D) - EQ 4 + */ +#define WM9081_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */ +#define WM9081_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */ +#define WM9081_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */ + +/* + * R46 (0x2E) - EQ 5 + */ +#define WM9081_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */ +#define WM9081_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */ +#define WM9081_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */ + +/* + * R47 (0x2F) - EQ 6 + */ +#define WM9081_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */ +#define WM9081_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */ +#define WM9081_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */ + +/* + * R48 (0x30) - EQ 7 + */ +#define WM9081_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */ +#define WM9081_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */ +#define WM9081_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */ + +/* + * R49 (0x31) - EQ 8 + */ +#define WM9081_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */ +#define WM9081_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */ +#define WM9081_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */ + +/* + * R50 (0x32) - EQ 9 + */ +#define WM9081_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */ +#define WM9081_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */ +#define WM9081_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */ + +/* + * R51 (0x33) - EQ 10 + */ +#define WM9081_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */ +#define WM9081_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */ +#define WM9081_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */ + +/* + * R52 (0x34) - EQ 11 + */ +#define WM9081_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */ +#define WM9081_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */ +#define WM9081_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */ + +/* + * R53 (0x35) - EQ 12 + */ +#define WM9081_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */ +#define WM9081_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */ +#define WM9081_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */ + +/* + * R54 (0x36) - EQ 13 + */ +#define WM9081_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */ +#define WM9081_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */ +#define WM9081_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */ + +/* + * R55 (0x37) - EQ 14 + */ +#define WM9081_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */ +#define WM9081_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */ +#define WM9081_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */ + +/* + * R56 (0x38) - EQ 15 + */ +#define WM9081_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */ +#define WM9081_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */ +#define WM9081_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */ + +/* + * R57 (0x39) - EQ 16 + */ +#define WM9081_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */ +#define WM9081_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */ +#define WM9081_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */ + +/* + * R58 (0x3A) - EQ 17 + */ +#define WM9081_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */ +#define WM9081_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */ +#define WM9081_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */ + +/* + * R59 (0x3B) - EQ 18 + */ +#define WM9081_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */ +#define WM9081_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */ +#define WM9081_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */ + +/* + * R60 (0x3C) - EQ 19 + */ +#define WM9081_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */ +#define WM9081_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */ +#define WM9081_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */ + +/* + * R61 (0x3D) - EQ 20 + */ +#define WM9081_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */ +#define WM9081_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */ +#define WM9081_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */ + + +#endif diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c new file mode 100644 index 000000000..a9f1a0360 --- /dev/null +++ b/sound/soc/codecs/wm9090.c @@ -0,0 +1,643 @@ +/* + * ALSA SoC WM9090 driver + * + * Copyright 2009-12 Wolfson Microelectronics + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm9090.h" + +static const struct reg_default wm9090_reg_defaults[] = { + { 1, 0x0006 }, /* R1 - Power Management (1) */ + { 2, 0x6000 }, /* R2 - Power Management (2) */ + { 3, 0x0000 }, /* R3 - Power Management (3) */ + { 6, 0x01C0 }, /* R6 - Clocking 1 */ + { 22, 0x0003 }, /* R22 - IN1 Line Control */ + { 23, 0x0003 }, /* R23 - IN2 Line Control */ + { 24, 0x0083 }, /* R24 - IN1 Line Input A Volume */ + { 25, 0x0083 }, /* R25 - IN1 Line Input B Volume */ + { 26, 0x0083 }, /* R26 - IN2 Line Input A Volume */ + { 27, 0x0083 }, /* R27 - IN2 Line Input B Volume */ + { 28, 0x002D }, /* R28 - Left Output Volume */ + { 29, 0x002D }, /* R29 - Right Output Volume */ + { 34, 0x0100 }, /* R34 - SPKMIXL Attenuation */ + { 35, 0x0010 }, /* R36 - SPKOUT Mixers */ + { 37, 0x0140 }, /* R37 - ClassD3 */ + { 38, 0x0039 }, /* R38 - Speaker Volume Left */ + { 45, 0x0000 }, /* R45 - Output Mixer1 */ + { 46, 0x0000 }, /* R46 - Output Mixer2 */ + { 47, 0x0100 }, /* R47 - Output Mixer3 */ + { 48, 0x0100 }, /* R48 - Output Mixer4 */ + { 54, 0x0000 }, /* R54 - Speaker Mixer */ + { 57, 0x000D }, /* R57 - AntiPOP2 */ + { 70, 0x0000 }, /* R70 - Write Sequencer 0 */ + { 71, 0x0000 }, /* R71 - Write Sequencer 1 */ + { 72, 0x0000 }, /* R72 - Write Sequencer 2 */ + { 73, 0x0000 }, /* R73 - Write Sequencer 3 */ + { 74, 0x0000 }, /* R74 - Write Sequencer 4 */ + { 75, 0x0000 }, /* R75 - Write Sequencer 5 */ + { 76, 0x1F25 }, /* R76 - Charge Pump 1 */ + { 85, 0x054A }, /* R85 - DC Servo 1 */ + { 87, 0x0000 }, /* R87 - DC Servo 3 */ + { 96, 0x0100 }, /* R96 - Analogue HP 0 */ + { 98, 0x8640 }, /* R98 - AGC Control 0 */ + { 99, 0xC000 }, /* R99 - AGC Control 1 */ + { 100, 0x0200 }, /* R100 - AGC Control 2 */ +}; + +/* This struct is used to save the context */ +struct wm9090_priv { + struct wm9090_platform_data pdata; + struct regmap *regmap; +}; + +static bool wm9090_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM9090_SOFTWARE_RESET: + case WM9090_DC_SERVO_0: + case WM9090_DC_SERVO_READBACK_0: + case WM9090_DC_SERVO_READBACK_1: + case WM9090_DC_SERVO_READBACK_2: + return true; + + default: + return false; + } +} + +static bool wm9090_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM9090_SOFTWARE_RESET: + case WM9090_POWER_MANAGEMENT_1: + case WM9090_POWER_MANAGEMENT_2: + case WM9090_POWER_MANAGEMENT_3: + case WM9090_CLOCKING_1: + case WM9090_IN1_LINE_CONTROL: + case WM9090_IN2_LINE_CONTROL: + case WM9090_IN1_LINE_INPUT_A_VOLUME: + case WM9090_IN1_LINE_INPUT_B_VOLUME: + case WM9090_IN2_LINE_INPUT_A_VOLUME: + case WM9090_IN2_LINE_INPUT_B_VOLUME: + case WM9090_LEFT_OUTPUT_VOLUME: + case WM9090_RIGHT_OUTPUT_VOLUME: + case WM9090_SPKMIXL_ATTENUATION: + case WM9090_SPKOUT_MIXERS: + case WM9090_CLASSD3: + case WM9090_SPEAKER_VOLUME_LEFT: + case WM9090_OUTPUT_MIXER1: + case WM9090_OUTPUT_MIXER2: + case WM9090_OUTPUT_MIXER3: + case WM9090_OUTPUT_MIXER4: + case WM9090_SPEAKER_MIXER: + case WM9090_ANTIPOP2: + case WM9090_WRITE_SEQUENCER_0: + case WM9090_WRITE_SEQUENCER_1: + case WM9090_WRITE_SEQUENCER_2: + case WM9090_WRITE_SEQUENCER_3: + case WM9090_WRITE_SEQUENCER_4: + case WM9090_WRITE_SEQUENCER_5: + case WM9090_CHARGE_PUMP_1: + case WM9090_DC_SERVO_0: + case WM9090_DC_SERVO_1: + case WM9090_DC_SERVO_3: + case WM9090_DC_SERVO_READBACK_0: + case WM9090_DC_SERVO_READBACK_1: + case WM9090_DC_SERVO_READBACK_2: + case WM9090_ANALOGUE_HP_0: + case WM9090_AGC_CONTROL_0: + case WM9090_AGC_CONTROL_1: + case WM9090_AGC_CONTROL_2: + return true; + + default: + return false; + } +} + +static void wait_for_dc_servo(struct snd_soc_component *component) +{ + unsigned int reg; + int count = 0; + + dev_dbg(component->dev, "Waiting for DC servo...\n"); + do { + count++; + msleep(1); + reg = snd_soc_component_read32(component, WM9090_DC_SERVO_READBACK_0); + dev_dbg(component->dev, "DC servo status: %x\n", reg); + } while ((reg & WM9090_DCS_CAL_COMPLETE_MASK) + != WM9090_DCS_CAL_COMPLETE_MASK && count < 1000); + + if ((reg & WM9090_DCS_CAL_COMPLETE_MASK) + != WM9090_DCS_CAL_COMPLETE_MASK) + dev_err(component->dev, "Timed out waiting for DC Servo\n"); +} + +static const DECLARE_TLV_DB_RANGE(in_tlv, + 0, 0, TLV_DB_SCALE_ITEM(-600, 0, 0), + 1, 3, TLV_DB_SCALE_ITEM(-350, 350, 0), + 4, 6, TLV_DB_SCALE_ITEM(600, 600, 0) +); +static const DECLARE_TLV_DB_RANGE(mix_tlv, + 0, 2, TLV_DB_SCALE_ITEM(-1200, 300, 0), + 3, 3, TLV_DB_SCALE_ITEM(0, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_RANGE(spkboost_tlv, + 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), + 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0) +); + +static const struct snd_kcontrol_new wm9090_controls[] = { +SOC_SINGLE_TLV("IN1A Volume", WM9090_IN1_LINE_INPUT_A_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN1A Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1A ZC Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("IN2A Volume", WM9090_IN2_LINE_INPUT_A_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN2A Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2A ZC Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 6, 1, 0), + +SOC_SINGLE("MIXOUTL Switch", WM9090_OUTPUT_MIXER3, 8, 1, 1), +SOC_SINGLE_TLV("MIXOUTL IN1A Volume", WM9090_OUTPUT_MIXER3, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN2A Volume", WM9090_OUTPUT_MIXER3, 2, 3, 1, + mix_tlv), + +SOC_SINGLE("MIXOUTR Switch", WM9090_OUTPUT_MIXER4, 8, 1, 1), +SOC_SINGLE_TLV("MIXOUTR IN1A Volume", WM9090_OUTPUT_MIXER4, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN2A Volume", WM9090_OUTPUT_MIXER4, 2, 3, 1, + mix_tlv), + +SOC_SINGLE("SPKMIX Switch", WM9090_SPKMIXL_ATTENUATION, 8, 1, 1), +SOC_SINGLE_TLV("SPKMIX IN1A Volume", WM9090_SPKMIXL_ATTENUATION, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("SPKMIX IN2A Volume", WM9090_SPKMIXL_ATTENUATION, 2, 3, 1, + mix_tlv), + +SOC_DOUBLE_R_TLV("Headphone Volume", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 0, 63, 0, out_tlv), +SOC_DOUBLE_R("Headphone Switch", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 6, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("Speaker Volume", WM9090_SPEAKER_VOLUME_LEFT, 0, 63, 0, + out_tlv), +SOC_SINGLE("Speaker Switch", WM9090_SPEAKER_VOLUME_LEFT, 6, 1, 1), +SOC_SINGLE("Speaker ZC Switch", WM9090_SPEAKER_VOLUME_LEFT, 7, 1, 0), +SOC_SINGLE_TLV("Speaker Boost Volume", WM9090_CLASSD3, 3, 7, 0, spkboost_tlv), +}; + +static const struct snd_kcontrol_new wm9090_in1_se_controls[] = { +SOC_SINGLE_TLV("IN1B Volume", WM9090_IN1_LINE_INPUT_B_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN1B Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1B ZC Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("SPKMIX IN1B Volume", WM9090_SPKMIXL_ATTENUATION, 4, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN1B Volume", WM9090_OUTPUT_MIXER3, 4, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN1B Volume", WM9090_OUTPUT_MIXER4, 4, 3, 1, + mix_tlv), +}; + +static const struct snd_kcontrol_new wm9090_in2_se_controls[] = { +SOC_SINGLE_TLV("IN2B Volume", WM9090_IN2_LINE_INPUT_B_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN2B Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2B ZC Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("SPKMIX IN2B Volume", WM9090_SPKMIXL_ATTENUATION, 0, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN2B Volume", WM9090_OUTPUT_MIXER3, 0, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN2B Volume", WM9090_OUTPUT_MIXER4, 0, 3, 1, + mix_tlv), +}; + +static int hp_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int reg = snd_soc_component_read32(component, WM9090_ANALOGUE_HP_0); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, WM9090_CHARGE_PUMP_1, + WM9090_CP_ENA, WM9090_CP_ENA); + + msleep(5); + + snd_soc_component_update_bits(component, WM9090_POWER_MANAGEMENT_1, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA); + + reg |= WM9090_HPOUT1L_DLY | WM9090_HPOUT1R_DLY; + snd_soc_component_write(component, WM9090_ANALOGUE_HP_0, reg); + + /* Start the DC servo. We don't currently use the + * ability to save the state since we don't have full + * control of the analogue paths and they can change + * DC offsets; see the WM8904 driver for an example of + * doing so. + */ + snd_soc_component_write(component, WM9090_DC_SERVO_0, + WM9090_DCS_ENA_CHAN_0 | + WM9090_DCS_ENA_CHAN_1 | + WM9090_DCS_TRIG_STARTUP_1 | + WM9090_DCS_TRIG_STARTUP_0); + wait_for_dc_servo(component); + + reg |= WM9090_HPOUT1R_OUTP | WM9090_HPOUT1R_RMV_SHORT | + WM9090_HPOUT1L_OUTP | WM9090_HPOUT1L_RMV_SHORT; + snd_soc_component_write(component, WM9090_ANALOGUE_HP_0, reg); + break; + + case SND_SOC_DAPM_PRE_PMD: + reg &= ~(WM9090_HPOUT1L_RMV_SHORT | + WM9090_HPOUT1L_DLY | + WM9090_HPOUT1L_OUTP | + WM9090_HPOUT1R_RMV_SHORT | + WM9090_HPOUT1R_DLY | + WM9090_HPOUT1R_OUTP); + + snd_soc_component_write(component, WM9090_ANALOGUE_HP_0, reg); + + snd_soc_component_write(component, WM9090_DC_SERVO_0, 0); + + snd_soc_component_update_bits(component, WM9090_POWER_MANAGEMENT_1, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA, + 0); + + snd_soc_component_update_bits(component, WM9090_CHARGE_PUMP_1, + WM9090_CP_ENA, 0); + break; + } + + return 0; +} + +static const struct snd_kcontrol_new spkmix[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_SPEAKER_MIXER, 0, 1, 0), +}; + +static const struct snd_kcontrol_new spkout[] = { +SOC_DAPM_SINGLE("Mixer Switch", WM9090_SPKOUT_MIXERS, 4, 1, 0), +}; + +static const struct snd_kcontrol_new mixoutl[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER1, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mixoutr[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER2, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER2, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm9090_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1+"), +SND_SOC_DAPM_INPUT("IN1-"), +SND_SOC_DAPM_INPUT("IN2+"), +SND_SOC_DAPM_INPUT("IN2-"), + +SND_SOC_DAPM_SUPPLY("OSC", WM9090_POWER_MANAGEMENT_1, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("IN1A PGA", WM9090_POWER_MANAGEMENT_2, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN1B PGA", WM9090_POWER_MANAGEMENT_2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN2A PGA", WM9090_POWER_MANAGEMENT_2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN2B PGA", WM9090_POWER_MANAGEMENT_2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("SPKMIX", WM9090_POWER_MANAGEMENT_3, 3, 0, + spkmix, ARRAY_SIZE(spkmix)), +SND_SOC_DAPM_MIXER("MIXOUTL", WM9090_POWER_MANAGEMENT_3, 5, 0, + mixoutl, ARRAY_SIZE(mixoutl)), +SND_SOC_DAPM_MIXER("MIXOUTR", WM9090_POWER_MANAGEMENT_3, 4, 0, + mixoutr, ARRAY_SIZE(mixoutr)), + +SND_SOC_DAPM_PGA_E("HP PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + hp_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_PGA("SPKPGA", WM9090_POWER_MANAGEMENT_3, 8, 0, NULL, 0), +SND_SOC_DAPM_MIXER("SPKOUT", WM9090_POWER_MANAGEMENT_1, 12, 0, + spkout, ARRAY_SIZE(spkout)), + +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("Speaker"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + { "IN1A PGA", NULL, "IN1+" }, + { "IN2A PGA", NULL, "IN2+" }, + + { "SPKMIX", "IN1A Switch", "IN1A PGA" }, + { "SPKMIX", "IN2A Switch", "IN2A PGA" }, + + { "MIXOUTL", "IN1A Switch", "IN1A PGA" }, + { "MIXOUTL", "IN2A Switch", "IN2A PGA" }, + + { "MIXOUTR", "IN1A Switch", "IN1A PGA" }, + { "MIXOUTR", "IN2A Switch", "IN2A PGA" }, + + { "HP PGA", NULL, "OSC" }, + { "HP PGA", NULL, "MIXOUTL" }, + { "HP PGA", NULL, "MIXOUTR" }, + + { "HPL", NULL, "HP PGA" }, + { "HPR", NULL, "HP PGA" }, + + { "SPKPGA", NULL, "OSC" }, + { "SPKPGA", NULL, "SPKMIX" }, + + { "SPKOUT", "Mixer Switch", "SPKPGA" }, + + { "Speaker", NULL, "SPKOUT" }, +}; + +static const struct snd_soc_dapm_route audio_map_in1_se[] = { + { "IN1B PGA", NULL, "IN1-" }, + + { "SPKMIX", "IN1B Switch", "IN1B PGA" }, + { "MIXOUTL", "IN1B Switch", "IN1B PGA" }, + { "MIXOUTR", "IN1B Switch", "IN1B PGA" }, +}; + +static const struct snd_soc_dapm_route audio_map_in1_diff[] = { + { "IN1A PGA", NULL, "IN1-" }, +}; + +static const struct snd_soc_dapm_route audio_map_in2_se[] = { + { "IN2B PGA", NULL, "IN2-" }, + + { "SPKMIX", "IN2B Switch", "IN2B PGA" }, + { "MIXOUTL", "IN2B Switch", "IN2B PGA" }, + { "MIXOUTR", "IN2B Switch", "IN2B PGA" }, +}; + +static const struct snd_soc_dapm_route audio_map_in2_diff[] = { + { "IN2A PGA", NULL, "IN2-" }, +}; + +static int wm9090_add_controls(struct snd_soc_component *component) +{ + struct wm9090_priv *wm9090 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int i; + + snd_soc_dapm_new_controls(dapm, wm9090_dapm_widgets, + ARRAY_SIZE(wm9090_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_add_component_controls(component, wm9090_controls, + ARRAY_SIZE(wm9090_controls)); + + if (wm9090->pdata.lin1_diff) { + snd_soc_dapm_add_routes(dapm, audio_map_in1_diff, + ARRAY_SIZE(audio_map_in1_diff)); + } else { + snd_soc_dapm_add_routes(dapm, audio_map_in1_se, + ARRAY_SIZE(audio_map_in1_se)); + snd_soc_add_component_controls(component, wm9090_in1_se_controls, + ARRAY_SIZE(wm9090_in1_se_controls)); + } + + if (wm9090->pdata.lin2_diff) { + snd_soc_dapm_add_routes(dapm, audio_map_in2_diff, + ARRAY_SIZE(audio_map_in2_diff)); + } else { + snd_soc_dapm_add_routes(dapm, audio_map_in2_se, + ARRAY_SIZE(audio_map_in2_se)); + snd_soc_add_component_controls(component, wm9090_in2_se_controls, + ARRAY_SIZE(wm9090_in2_se_controls)); + } + + if (wm9090->pdata.agc_ena) { + for (i = 0; i < ARRAY_SIZE(wm9090->pdata.agc); i++) + snd_soc_component_write(component, WM9090_AGC_CONTROL_0 + i, + wm9090->pdata.agc[i]); + snd_soc_component_update_bits(component, WM9090_POWER_MANAGEMENT_3, + WM9090_AGC_ENA, WM9090_AGC_ENA); + } else { + snd_soc_component_update_bits(component, WM9090_POWER_MANAGEMENT_3, + WM9090_AGC_ENA, 0); + } + + return 0; + +} + +/* + * The machine driver should call this from their set_bias_level; if there + * isn't one then this can just be set as the set_bias_level function. + */ +static int wm9090_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm9090_priv *wm9090 = snd_soc_component_get_drvdata(component); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + snd_soc_component_update_bits(component, WM9090_ANTIPOP2, WM9090_VMID_ENA, + WM9090_VMID_ENA); + snd_soc_component_update_bits(component, WM9090_POWER_MANAGEMENT_1, + WM9090_BIAS_ENA | + WM9090_VMID_RES_MASK, + WM9090_BIAS_ENA | + 1 << WM9090_VMID_RES_SHIFT); + msleep(1); /* Probably an overestimate */ + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { + /* Restore the register cache */ + regcache_sync(wm9090->regmap); + } + + /* We keep VMID off during standby since the combination of + * ground referenced outputs and class D speaker mean that + * latency is not an issue. + */ + snd_soc_component_update_bits(component, WM9090_POWER_MANAGEMENT_1, + WM9090_BIAS_ENA | WM9090_VMID_RES_MASK, 0); + snd_soc_component_update_bits(component, WM9090_ANTIPOP2, + WM9090_VMID_ENA, 0); + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static int wm9090_probe(struct snd_soc_component *component) +{ + /* Configure some defaults; they will be written out when we + * bring the bias up. + */ + snd_soc_component_update_bits(component, WM9090_IN1_LINE_INPUT_A_VOLUME, + WM9090_IN1_VU | WM9090_IN1A_ZC, + WM9090_IN1_VU | WM9090_IN1A_ZC); + snd_soc_component_update_bits(component, WM9090_IN1_LINE_INPUT_B_VOLUME, + WM9090_IN1_VU | WM9090_IN1B_ZC, + WM9090_IN1_VU | WM9090_IN1B_ZC); + snd_soc_component_update_bits(component, WM9090_IN2_LINE_INPUT_A_VOLUME, + WM9090_IN2_VU | WM9090_IN2A_ZC, + WM9090_IN2_VU | WM9090_IN2A_ZC); + snd_soc_component_update_bits(component, WM9090_IN2_LINE_INPUT_B_VOLUME, + WM9090_IN2_VU | WM9090_IN2B_ZC, + WM9090_IN2_VU | WM9090_IN2B_ZC); + snd_soc_component_update_bits(component, WM9090_SPEAKER_VOLUME_LEFT, + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC, + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC); + snd_soc_component_update_bits(component, WM9090_LEFT_OUTPUT_VOLUME, + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC, + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC); + snd_soc_component_update_bits(component, WM9090_RIGHT_OUTPUT_VOLUME, + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC, + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC); + + snd_soc_component_update_bits(component, WM9090_CLOCKING_1, + WM9090_TOCLK_ENA, WM9090_TOCLK_ENA); + + wm9090_add_controls(component); + + return 0; +} + +static const struct snd_soc_component_driver soc_component_dev_wm9090 = { + .probe = wm9090_probe, + .set_bias_level = wm9090_set_bias_level, + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config wm9090_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = WM9090_MAX_REGISTER, + .volatile_reg = wm9090_volatile, + .readable_reg = wm9090_readable, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = wm9090_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm9090_reg_defaults), +}; + + +static int wm9090_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm9090_priv *wm9090; + unsigned int reg; + int ret; + + wm9090 = devm_kzalloc(&i2c->dev, sizeof(*wm9090), GFP_KERNEL); + if (!wm9090) + return -ENOMEM; + + wm9090->regmap = devm_regmap_init_i2c(i2c, &wm9090_regmap); + if (IS_ERR(wm9090->regmap)) { + ret = PTR_ERR(wm9090->regmap); + dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + ret = regmap_read(wm9090->regmap, WM9090_SOFTWARE_RESET, ®); + if (ret < 0) + return ret; + + if (reg != 0x9093) { + dev_err(&i2c->dev, "Device is not a WM9090, ID=%x\n", reg); + return -ENODEV; + } + + ret = regmap_write(wm9090->regmap, WM9090_SOFTWARE_RESET, 0); + if (ret < 0) + return ret; + + if (i2c->dev.platform_data) + memcpy(&wm9090->pdata, i2c->dev.platform_data, + sizeof(wm9090->pdata)); + + i2c_set_clientdata(i2c, wm9090); + + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm9090, NULL, 0); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct i2c_device_id wm9090_id[] = { + { "wm9090", 0 }, + { "wm9093", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm9090_id); + +static struct i2c_driver wm9090_i2c_driver = { + .driver = { + .name = "wm9090", + }, + .probe = wm9090_i2c_probe, + .id_table = wm9090_id, +}; + +module_i2c_driver(wm9090_i2c_driver); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("WM9090 ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9090.h b/sound/soc/codecs/wm9090.h new file mode 100644 index 000000000..29b9d9fc7 --- /dev/null +++ b/sound/soc/codecs/wm9090.h @@ -0,0 +1,713 @@ +/* + * ALSA SoC WM9090 driver + * + * Copyright 2009 Wolfson Microelectronics + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __WM9090_H +#define __WM9090_H + +/* + * Register values. + */ +#define WM9090_SOFTWARE_RESET 0x00 +#define WM9090_POWER_MANAGEMENT_1 0x01 +#define WM9090_POWER_MANAGEMENT_2 0x02 +#define WM9090_POWER_MANAGEMENT_3 0x03 +#define WM9090_CLOCKING_1 0x06 +#define WM9090_IN1_LINE_CONTROL 0x16 +#define WM9090_IN2_LINE_CONTROL 0x17 +#define WM9090_IN1_LINE_INPUT_A_VOLUME 0x18 +#define WM9090_IN1_LINE_INPUT_B_VOLUME 0x19 +#define WM9090_IN2_LINE_INPUT_A_VOLUME 0x1A +#define WM9090_IN2_LINE_INPUT_B_VOLUME 0x1B +#define WM9090_LEFT_OUTPUT_VOLUME 0x1C +#define WM9090_RIGHT_OUTPUT_VOLUME 0x1D +#define WM9090_SPKMIXL_ATTENUATION 0x22 +#define WM9090_SPKOUT_MIXERS 0x24 +#define WM9090_CLASSD3 0x25 +#define WM9090_SPEAKER_VOLUME_LEFT 0x26 +#define WM9090_OUTPUT_MIXER1 0x2D +#define WM9090_OUTPUT_MIXER2 0x2E +#define WM9090_OUTPUT_MIXER3 0x2F +#define WM9090_OUTPUT_MIXER4 0x30 +#define WM9090_SPEAKER_MIXER 0x36 +#define WM9090_ANTIPOP2 0x39 +#define WM9090_WRITE_SEQUENCER_0 0x46 +#define WM9090_WRITE_SEQUENCER_1 0x47 +#define WM9090_WRITE_SEQUENCER_2 0x48 +#define WM9090_WRITE_SEQUENCER_3 0x49 +#define WM9090_WRITE_SEQUENCER_4 0x4A +#define WM9090_WRITE_SEQUENCER_5 0x4B +#define WM9090_CHARGE_PUMP_1 0x4C +#define WM9090_DC_SERVO_0 0x54 +#define WM9090_DC_SERVO_1 0x55 +#define WM9090_DC_SERVO_3 0x57 +#define WM9090_DC_SERVO_READBACK_0 0x58 +#define WM9090_DC_SERVO_READBACK_1 0x59 +#define WM9090_DC_SERVO_READBACK_2 0x5A +#define WM9090_ANALOGUE_HP_0 0x60 +#define WM9090_AGC_CONTROL_0 0x62 +#define WM9090_AGC_CONTROL_1 0x63 +#define WM9090_AGC_CONTROL_2 0x64 + +#define WM9090_REGISTER_COUNT 40 +#define WM9090_MAX_REGISTER 0x64 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM9090_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM9090_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM9090_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM9090_SPKOUTL_ENA 0x1000 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_MASK 0x1000 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_SHIFT 12 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */ +#define WM9090_HPOUT1L_ENA 0x0200 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_MASK 0x0200 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_SHIFT 9 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1R_ENA 0x0100 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_MASK 0x0100 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_SHIFT 8 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */ +#define WM9090_OSC_ENA 0x0008 /* OSC_ENA */ +#define WM9090_OSC_ENA_MASK 0x0008 /* OSC_ENA */ +#define WM9090_OSC_ENA_SHIFT 3 /* OSC_ENA */ +#define WM9090_OSC_ENA_WIDTH 1 /* OSC_ENA */ +#define WM9090_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM9090_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM9090_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM9090_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM9090_TSHUT 0x8000 /* TSHUT */ +#define WM9090_TSHUT_MASK 0x8000 /* TSHUT */ +#define WM9090_TSHUT_SHIFT 15 /* TSHUT */ +#define WM9090_TSHUT_WIDTH 1 /* TSHUT */ +#define WM9090_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_MASK 0x4000 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_SHIFT 14 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM9090_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_MASK 0x2000 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_SHIFT 13 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_WIDTH 1 /* TSHUT_OPDIS */ +#define WM9090_IN1A_ENA 0x0080 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_MASK 0x0080 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_SHIFT 7 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_WIDTH 1 /* IN1A_ENA */ +#define WM9090_IN1B_ENA 0x0040 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_MASK 0x0040 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_SHIFT 6 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_WIDTH 1 /* IN1B_ENA */ +#define WM9090_IN2A_ENA 0x0020 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_MASK 0x0020 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_SHIFT 5 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_WIDTH 1 /* IN2A_ENA */ +#define WM9090_IN2B_ENA 0x0010 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_MASK 0x0010 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_SHIFT 4 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_WIDTH 1 /* IN2B_ENA */ + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM9090_AGC_ENA 0x4000 /* AGC_ENA */ +#define WM9090_AGC_ENA_MASK 0x4000 /* AGC_ENA */ +#define WM9090_AGC_ENA_SHIFT 14 /* AGC_ENA */ +#define WM9090_AGC_ENA_WIDTH 1 /* AGC_ENA */ +#define WM9090_SPKLVOL_ENA 0x0100 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_MASK 0x0100 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_SHIFT 8 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_WIDTH 1 /* SPKLVOL_ENA */ +#define WM9090_MIXOUTL_ENA 0x0020 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_MASK 0x0020 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_SHIFT 5 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTR_ENA 0x0010 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_MASK 0x0010 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_SHIFT 4 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ +#define WM9090_SPKMIX_ENA 0x0008 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_MASK 0x0008 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_SHIFT 3 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_WIDTH 1 /* SPKMIX_ENA */ + +/* + * R6 (0x06) - Clocking 1 + */ +#define WM9090_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_MASK 0x8000 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_SHIFT 15 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */ +#define WM9090_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_MASK 0x4000 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_SHIFT 14 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ + +/* + * R22 (0x16) - IN1 Line Control + */ +#define WM9090_IN1_DIFF 0x0002 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_MASK 0x0002 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_SHIFT 1 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_WIDTH 1 /* IN1_DIFF */ +#define WM9090_IN1_CLAMP 0x0001 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_MASK 0x0001 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_SHIFT 0 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_WIDTH 1 /* IN1_CLAMP */ + +/* + * R23 (0x17) - IN2 Line Control + */ +#define WM9090_IN2_DIFF 0x0002 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_MASK 0x0002 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_SHIFT 1 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_WIDTH 1 /* IN2_DIFF */ +#define WM9090_IN2_CLAMP 0x0001 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_MASK 0x0001 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_SHIFT 0 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_WIDTH 1 /* IN2_CLAMP */ + +/* + * R24 (0x18) - IN1 Line Input A Volume + */ +#define WM9090_IN1_VU 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM9090_IN1A_MUTE 0x0080 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_MASK 0x0080 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_SHIFT 7 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_WIDTH 1 /* IN1A_MUTE */ +#define WM9090_IN1A_ZC 0x0040 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_MASK 0x0040 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_SHIFT 6 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_WIDTH 1 /* IN1A_ZC */ +#define WM9090_IN1A_VOL_MASK 0x0007 /* IN1A_VOL - [2:0] */ +#define WM9090_IN1A_VOL_SHIFT 0 /* IN1A_VOL - [2:0] */ +#define WM9090_IN1A_VOL_WIDTH 3 /* IN1A_VOL - [2:0] */ + +/* + * R25 (0x19) - IN1 Line Input B Volume + */ +#define WM9090_IN1_VU 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM9090_IN1B_MUTE 0x0080 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_MASK 0x0080 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_SHIFT 7 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_WIDTH 1 /* IN1B_MUTE */ +#define WM9090_IN1B_ZC 0x0040 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_MASK 0x0040 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_SHIFT 6 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_WIDTH 1 /* IN1B_ZC */ +#define WM9090_IN1B_VOL_MASK 0x0007 /* IN1B_VOL - [2:0] */ +#define WM9090_IN1B_VOL_SHIFT 0 /* IN1B_VOL - [2:0] */ +#define WM9090_IN1B_VOL_WIDTH 3 /* IN1B_VOL - [2:0] */ + +/* + * R26 (0x1A) - IN2 Line Input A Volume + */ +#define WM9090_IN2_VU 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM9090_IN2A_MUTE 0x0080 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_MASK 0x0080 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_SHIFT 7 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_WIDTH 1 /* IN2A_MUTE */ +#define WM9090_IN2A_ZC 0x0040 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_MASK 0x0040 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_SHIFT 6 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_WIDTH 1 /* IN2A_ZC */ +#define WM9090_IN2A_VOL_MASK 0x0007 /* IN2A_VOL - [2:0] */ +#define WM9090_IN2A_VOL_SHIFT 0 /* IN2A_VOL - [2:0] */ +#define WM9090_IN2A_VOL_WIDTH 3 /* IN2A_VOL - [2:0] */ + +/* + * R27 (0x1B) - IN2 Line Input B Volume + */ +#define WM9090_IN2_VU 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM9090_IN2B_MUTE 0x0080 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_MASK 0x0080 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_SHIFT 7 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_WIDTH 1 /* IN2B_MUTE */ +#define WM9090_IN2B_ZC 0x0040 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_MASK 0x0040 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_SHIFT 6 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_WIDTH 1 /* IN2B_ZC */ +#define WM9090_IN2B_VOL_MASK 0x0007 /* IN2B_VOL - [2:0] */ +#define WM9090_IN2B_VOL_SHIFT 0 /* IN2B_VOL - [2:0] */ +#define WM9090_IN2B_VOL_WIDTH 3 /* IN2B_VOL - [2:0] */ + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM9090_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_MUTE 0x0040 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_MASK 0x0040 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_SHIFT 6 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_WIDTH 1 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_VOL_MASK 0x003F /* HPOUT1L_VOL - [5:0] */ +#define WM9090_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [5:0] */ +#define WM9090_HPOUT1L_VOL_WIDTH 6 /* HPOUT1L_VOL - [5:0] */ + +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM9090_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_MUTE 0x0040 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_MASK 0x0040 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_SHIFT 6 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_WIDTH 1 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_VOL_MASK 0x003F /* HPOUT1R_VOL - [5:0] */ +#define WM9090_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [5:0] */ +#define WM9090_HPOUT1R_VOL_WIDTH 6 /* HPOUT1R_VOL - [5:0] */ + +/* + * R34 (0x22) - SPKMIXL Attenuation + */ +#define WM9090_SPKMIX_MUTE 0x0100 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_MASK 0x0100 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_SHIFT 8 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_WIDTH 1 /* SPKMIX_MUTE */ +#define WM9090_IN1A_SPKMIX_VOL_MASK 0x00C0 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1A_SPKMIX_VOL_SHIFT 6 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1A_SPKMIX_VOL_WIDTH 2 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1B_SPKMIX_VOL_MASK 0x0030 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN1B_SPKMIX_VOL_SHIFT 4 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN1B_SPKMIX_VOL_WIDTH 2 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN2A_SPKMIX_VOL_MASK 0x000C /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2A_SPKMIX_VOL_SHIFT 2 /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2A_SPKMIX_VOL_WIDTH 2 /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2B_SPKMIX_VOL_MASK 0x0003 /* IN2B_SPKMIX_VOL - [1:0] */ +#define WM9090_IN2B_SPKMIX_VOL_SHIFT 0 /* IN2B_SPKMIX_VOL - [1:0] */ +#define WM9090_IN2B_SPKMIX_VOL_WIDTH 2 /* IN2B_SPKMIX_VOL - [1:0] */ + +/* + * R36 (0x24) - SPKOUT Mixers + */ +#define WM9090_SPKMIXL_TO_SPKOUTL 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_MASK 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_SHIFT 4 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_WIDTH 1 /* SPKMIXL_TO_SPKOUTL */ + +/* + * R37 (0x25) - ClassD3 + */ +#define WM9090_SPKOUTL_BOOST_MASK 0x0038 /* SPKOUTL_BOOST - [5:3] */ +#define WM9090_SPKOUTL_BOOST_SHIFT 3 /* SPKOUTL_BOOST - [5:3] */ +#define WM9090_SPKOUTL_BOOST_WIDTH 3 /* SPKOUTL_BOOST - [5:3] */ + +/* + * R38 (0x26) - Speaker Volume Left + */ +#define WM9090_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM9090_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_MUTE 0x0040 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_MASK 0x0040 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_SHIFT 6 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_WIDTH 1 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */ +#define WM9090_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */ +#define WM9090_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */ + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM9090_IN1A_TO_MIXOUTL 0x0040 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_MASK 0x0040 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_SHIFT 6 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_WIDTH 1 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL 0x0004 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_MASK 0x0004 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_SHIFT 2 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_WIDTH 1 /* IN2A_TO_MIXOUTL */ + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM9090_IN1A_TO_MIXOUTR 0x0040 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_MASK 0x0040 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_SHIFT 6 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_WIDTH 1 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR 0x0010 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_MASK 0x0010 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_SHIFT 4 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_WIDTH 1 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR 0x0004 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_MASK 0x0004 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_SHIFT 2 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_WIDTH 1 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR 0x0001 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_MASK 0x0001 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_SHIFT 0 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_WIDTH 1 /* IN2B_TO_MIXOUTR */ + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM9090_MIXOUTL_MUTE 0x0100 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_MASK 0x0100 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_SHIFT 8 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_WIDTH 1 /* MIXOUTL_MUTE */ +#define WM9090_IN1A_MIXOUTL_VOL_MASK 0x00C0 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTL_VOL_SHIFT 6 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTL_VOL_WIDTH 2 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN2A_MIXOUTL_VOL_MASK 0x000C /* IN2A_MIXOUTL_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTL_VOL_SHIFT 2 /* IN2A_MIXOUTL_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTL_VOL_WIDTH 2 /* IN2A_MIXOUTL_VOL - [3:2] */ + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM9090_MIXOUTR_MUTE 0x0100 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_MASK 0x0100 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_SHIFT 8 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_WIDTH 1 /* MIXOUTR_MUTE */ +#define WM9090_IN1A_MIXOUTR_VOL_MASK 0x00C0 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTR_VOL_SHIFT 6 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTR_VOL_WIDTH 2 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1B_MIXOUTR_VOL_MASK 0x0030 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN1B_MIXOUTR_VOL_SHIFT 4 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN1B_MIXOUTR_VOL_WIDTH 2 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN2A_MIXOUTR_VOL_MASK 0x000C /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTR_VOL_SHIFT 2 /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTR_VOL_WIDTH 2 /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2B_MIXOUTR_VOL_MASK 0x0003 /* IN2B_MIXOUTR_VOL - [1:0] */ +#define WM9090_IN2B_MIXOUTR_VOL_SHIFT 0 /* IN2B_MIXOUTR_VOL - [1:0] */ +#define WM9090_IN2B_MIXOUTR_VOL_WIDTH 2 /* IN2B_MIXOUTR_VOL - [1:0] */ + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM9090_IN1A_TO_SPKMIX 0x0040 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_MASK 0x0040 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_SHIFT 6 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_WIDTH 1 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX 0x0010 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_MASK 0x0010 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_SHIFT 4 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_WIDTH 1 /* IN1B_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX 0x0004 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_MASK 0x0004 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_SHIFT 2 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_WIDTH 1 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX 0x0001 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_MASK 0x0001 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_SHIFT 0 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_WIDTH 1 /* IN2B_TO_SPKMIX */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM9090_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM9090_VMID_ENA 0x0001 /* VMID_ENA */ +#define WM9090_VMID_ENA_MASK 0x0001 /* VMID_ENA */ +#define WM9090_VMID_ENA_SHIFT 0 /* VMID_ENA */ +#define WM9090_VMID_ENA_WIDTH 1 /* VMID_ENA */ + +/* + * R70 (0x46) - Write Sequencer 0 + */ +#define WM9090_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM9090_WSEQ_WRITE_INDEX_MASK 0x000F /* WSEQ_WRITE_INDEX - [3:0] */ +#define WM9090_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [3:0] */ +#define WM9090_WSEQ_WRITE_INDEX_WIDTH 4 /* WSEQ_WRITE_INDEX - [3:0] */ + +/* + * R71 (0x47) - Write Sequencer 1 + */ +#define WM9090_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM9090_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM9090_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R72 (0x48) - Write Sequencer 2 + */ +#define WM9090_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM9090_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM9090_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM9090_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R73 (0x49) - Write Sequencer 3 + */ +#define WM9090_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM9090_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM9090_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM9090_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM9090_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM9090_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM9090_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM9090_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R74 (0x4A) - Write Sequencer 4 + */ +#define WM9090_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R75 (0x4B) - Write Sequencer 5 + */ +#define WM9090_WSEQ_CURRENT_INDEX_MASK 0x003F /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM9090_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM9090_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [5:0] */ + +/* + * R76 (0x4C) - Charge Pump 1 + */ +#define WM9090_CP_ENA 0x8000 /* CP_ENA */ +#define WM9090_CP_ENA_MASK 0x8000 /* CP_ENA */ +#define WM9090_CP_ENA_SHIFT 15 /* CP_ENA */ +#define WM9090_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R84 (0x54) - DC Servo 0 + */ +#define WM9090_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_DAC_WR_1 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_MASK 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_SHIFT 3 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_0 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_MASK 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_SHIFT 2 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R85 (0x55) - DC Servo 1 + */ +#define WM9090_DCS_SERIES_NO_01_MASK 0x0FE0 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_SERIES_NO_01_SHIFT 5 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM9090_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM9090_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R87 (0x57) - DC Servo 3 + */ +#define WM9090_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R88 (0x58) - DC Servo Readback 0 + */ +#define WM9090_DCS_CAL_COMPLETE_MASK 0x0300 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_CAL_COMPLETE_WIDTH 2 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_DAC_WR_COMPLETE_MASK 0x0030 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_DAC_WR_COMPLETE_WIDTH 2 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_STARTUP_COMPLETE_MASK 0x0003 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM9090_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM9090_DCS_STARTUP_COMPLETE_WIDTH 2 /* DCS_STARTUP_COMPLETE - [1:0] */ + +/* + * R89 (0x59) - DC Servo Readback 1 + */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_1_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_SHIFT 0 /* DCS_DAC_WR_VAL_1_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_WIDTH 8 /* DCS_DAC_WR_VAL_1_RD - [7:0] */ + +/* + * R90 (0x5A) - DC Servo Readback 2 + */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_0_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_SHIFT 0 /* DCS_DAC_WR_VAL_0_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_WIDTH 8 /* DCS_DAC_WR_VAL_0_RD - [7:0] */ + +/* + * R96 (0x60) - Analogue HP 0 + */ +#define WM9090_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */ + +/* + * R98 (0x62) - AGC Control 0 + */ +#define WM9090_AGC_CLIP_ENA 0x8000 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_MASK 0x8000 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_SHIFT 15 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_WIDTH 1 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_THR_MASK 0x0F00 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_THR_SHIFT 8 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_THR_WIDTH 4 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_ATK_MASK 0x0070 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_ATK_SHIFT 4 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_ATK_WIDTH 3 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_DCY_MASK 0x0007 /* AGC_CLIP_DCY - [2:0] */ +#define WM9090_AGC_CLIP_DCY_SHIFT 0 /* AGC_CLIP_DCY - [2:0] */ +#define WM9090_AGC_CLIP_DCY_WIDTH 3 /* AGC_CLIP_DCY - [2:0] */ + +/* + * R99 (0x63) - AGC Control 1 + */ +#define WM9090_AGC_PWR_ENA 0x8000 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_MASK 0x8000 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_SHIFT 15 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_WIDTH 1 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_AVG 0x1000 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_MASK 0x1000 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_SHIFT 12 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_WIDTH 1 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_THR_MASK 0x0F00 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_THR_SHIFT 8 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_THR_WIDTH 4 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_ATK_MASK 0x0070 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_ATK_SHIFT 4 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_ATK_WIDTH 3 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_DCY_MASK 0x0007 /* AGC_PWR_DCY - [2:0] */ +#define WM9090_AGC_PWR_DCY_SHIFT 0 /* AGC_PWR_DCY - [2:0] */ +#define WM9090_AGC_PWR_DCY_WIDTH 3 /* AGC_PWR_DCY - [2:0] */ + +/* + * R100 (0x64) - AGC Control 2 + */ +#define WM9090_AGC_RAMP 0x0100 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_MASK 0x0100 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_SHIFT 8 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_WIDTH 1 /* AGC_RAMP */ +#define WM9090_AGC_MINGAIN_MASK 0x003F /* AGC_MINGAIN - [5:0] */ +#define WM9090_AGC_MINGAIN_SHIFT 0 /* AGC_MINGAIN - [5:0] */ +#define WM9090_AGC_MINGAIN_WIDTH 6 /* AGC_MINGAIN - [5:0] */ + +#endif diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c new file mode 100644 index 000000000..54c306707 --- /dev/null +++ b/sound/soc/codecs/wm9705.c @@ -0,0 +1,405 @@ +/* + * wm9705.c -- ALSA Soc WM9705 codec support + * + * Copyright 2008 Ian Molton + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; Version 2 of the License only. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WM9705_VENDOR_ID 0x574d4c05 +#define WM9705_VENDOR_ID_MASK 0xffffffff + +struct wm9705_priv { + struct snd_ac97 *ac97; + struct wm97xx_platform_data *mfd_pdata; +}; + +static const struct reg_default wm9705_reg_defaults[] = { + { 0x02, 0x8000 }, + { 0x04, 0x8000 }, + { 0x06, 0x8000 }, + { 0x0a, 0x8000 }, + { 0x0c, 0x8008 }, + { 0x0e, 0x8008 }, + { 0x10, 0x8808 }, + { 0x12, 0x8808 }, + { 0x14, 0x8808 }, + { 0x16, 0x8808 }, + { 0x18, 0x8808 }, + { 0x1a, 0x0000 }, + { 0x1c, 0x8000 }, + { 0x20, 0x0000 }, + { 0x22, 0x0000 }, + { 0x26, 0x000f }, + { 0x28, 0x0605 }, + { 0x2a, 0x0000 }, + { 0x2c, 0xbb80 }, + { 0x32, 0xbb80 }, + { 0x34, 0x2000 }, + { 0x5a, 0x0000 }, + { 0x5c, 0x0000 }, + { 0x72, 0x0808 }, + { 0x74, 0x0000 }, + { 0x76, 0x0006 }, + { 0x78, 0x0000 }, + { 0x7a, 0x0000 }, +}; + +static const struct regmap_config wm9705_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x7e, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = regmap_ac97_default_volatile, + + .reg_defaults = wm9705_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm9705_reg_defaults), +}; + +static const struct snd_kcontrol_new wm9705_snd_ac97_controls[] = { + SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1), + SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1), + SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), + SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), + SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), + SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + SOC_SINGLE("PCBeep Playback Volume", AC97_PC_BEEP, 1, 15, 1), + SOC_SINGLE("Phone Playback Volume", AC97_PHONE, 0, 31, 1), + SOC_DOUBLE("Line Playback Volume", AC97_LINE, 8, 0, 31, 1), + SOC_DOUBLE("CD Playback Volume", AC97_CD, 8, 0, 31, 1), + SOC_SINGLE("Mic Playback Volume", AC97_MIC, 0, 31, 1), + SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 6, 1, 0), + SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0), + SOC_SINGLE("Capture Switch", AC97_REC_GAIN, 15, 1, 1), +}; + +static const char *wm9705_mic[] = {"Mic 1", "Mic 2"}; +static const char *wm9705_rec_sel[] = {"Mic", "CD", "NC", "NC", + "Line", "Stereo Mix", "Mono Mix", "Phone"}; + +static SOC_ENUM_SINGLE_DECL(wm9705_enum_mic, + AC97_GENERAL_PURPOSE, 8, wm9705_mic); +static SOC_ENUM_SINGLE_DECL(wm9705_enum_rec_l, + AC97_REC_SEL, 8, wm9705_rec_sel); +static SOC_ENUM_SINGLE_DECL(wm9705_enum_rec_r, + AC97_REC_SEL, 0, wm9705_rec_sel); + +/* Headphone Mixer */ +static const struct snd_kcontrol_new wm9705_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Playback Switch", AC97_PC_BEEP, 15, 1, 1), + SOC_DAPM_SINGLE("CD Playback Switch", AC97_CD, 15, 1, 1), + SOC_DAPM_SINGLE("Mic Playback Switch", AC97_MIC, 15, 1, 1), + SOC_DAPM_SINGLE("Phone Playback Switch", AC97_PHONE, 15, 1, 1), + SOC_DAPM_SINGLE("Line Playback Switch", AC97_LINE, 15, 1, 1), +}; + +/* Mic source */ +static const struct snd_kcontrol_new wm9705_mic_src_controls = + SOC_DAPM_ENUM("Route", wm9705_enum_mic); + +/* Capture source */ +static const struct snd_kcontrol_new wm9705_capture_selectl_controls = + SOC_DAPM_ENUM("Route", wm9705_enum_rec_l); +static const struct snd_kcontrol_new wm9705_capture_selectr_controls = + SOC_DAPM_ENUM("Route", wm9705_enum_rec_r); + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget wm9705_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Mic Source", SND_SOC_NOPM, 0, 0, + &wm9705_mic_src_controls), + SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0, + &wm9705_capture_selectl_controls), + SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0, + &wm9705_capture_selectr_controls), + SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MIXER_NAMED_CTL("HP Mixer", SND_SOC_NOPM, 0, 0, + &wm9705_hp_mixer_controls[0], + ARRAY_SIZE(wm9705_hp_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_PGA("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line out PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Phone PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("PCBEEP PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("CD PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ADC PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_INPUT("PHONE"), + SND_SOC_DAPM_INPUT("LINEINL"), + SND_SOC_DAPM_INPUT("LINEINR"), + SND_SOC_DAPM_INPUT("CDINL"), + SND_SOC_DAPM_INPUT("CDINR"), + SND_SOC_DAPM_INPUT("PCBEEP"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), +}; + +/* Audio map + * WM9705 has no switches to disable the route from the inputs to the HP mixer + * so in order to prevent active inputs from forcing the audio outputs to be + * constantly enabled, we use the mutes on those inputs to simulate such + * controls. + */ +static const struct snd_soc_dapm_route wm9705_audio_map[] = { + /* HP mixer */ + {"HP Mixer", "PCBeep Playback Switch", "PCBEEP PGA"}, + {"HP Mixer", "CD Playback Switch", "CD PGA"}, + {"HP Mixer", "Mic Playback Switch", "Mic PGA"}, + {"HP Mixer", "Phone Playback Switch", "Phone PGA"}, + {"HP Mixer", "Line Playback Switch", "Line PGA"}, + {"HP Mixer", NULL, "Left DAC"}, + {"HP Mixer", NULL, "Right DAC"}, + + /* mono mixer */ + {"Mono Mixer", NULL, "HP Mixer"}, + + /* outputs */ + {"Headphone PGA", NULL, "HP Mixer"}, + {"HPOUTL", NULL, "Headphone PGA"}, + {"HPOUTR", NULL, "Headphone PGA"}, + {"Line out PGA", NULL, "HP Mixer"}, + {"LOUT", NULL, "Line out PGA"}, + {"ROUT", NULL, "Line out PGA"}, + {"Mono PGA", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono PGA"}, + + /* inputs */ + {"CD PGA", NULL, "CDINL"}, + {"CD PGA", NULL, "CDINR"}, + {"Line PGA", NULL, "LINEINL"}, + {"Line PGA", NULL, "LINEINR"}, + {"Phone PGA", NULL, "PHONE"}, + {"Mic Source", "Mic 1", "MIC1"}, + {"Mic Source", "Mic 2", "MIC2"}, + {"Mic PGA", NULL, "Mic Source"}, + {"PCBEEP PGA", NULL, "PCBEEP"}, + + /* Left capture selector */ + {"Left Capture Source", "Mic", "Mic Source"}, + {"Left Capture Source", "CD", "CDINL"}, + {"Left Capture Source", "Line", "LINEINL"}, + {"Left Capture Source", "Stereo Mix", "HP Mixer"}, + {"Left Capture Source", "Mono Mix", "HP Mixer"}, + {"Left Capture Source", "Phone", "PHONE"}, + + /* Right capture source */ + {"Right Capture Source", "Mic", "Mic Source"}, + {"Right Capture Source", "CD", "CDINR"}, + {"Right Capture Source", "Line", "LINEINR"}, + {"Right Capture Source", "Stereo Mix", "HP Mixer"}, + {"Right Capture Source", "Mono Mix", "HP Mixer"}, + {"Right Capture Source", "Phone", "PHONE"}, + + {"ADC PGA", NULL, "Left Capture Source"}, + {"ADC PGA", NULL, "Right Capture Source"}, + + /* ADC's */ + {"Left ADC", NULL, "ADC PGA"}, + {"Right ADC", NULL, "ADC PGA"}, +}; + +static int ac97_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int reg; + + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x1, 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return snd_soc_component_write(component, reg, substream->runtime->rate); +} + +#define WM9705_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +static const struct snd_soc_dai_ops wm9705_dai_ops = { + .prepare = ac97_prepare, +}; + +static struct snd_soc_dai_driver wm9705_dai[] = { + { + .name = "wm9705-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9705_AC97_RATES, + .formats = SND_SOC_STD_AC97_FMTS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9705_AC97_RATES, + .formats = SND_SOC_STD_AC97_FMTS, + }, + .ops = &wm9705_dai_ops, + }, + { + .name = "wm9705-aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9705_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + } +}; + +#ifdef CONFIG_PM +static int wm9705_soc_suspend(struct snd_soc_component *component) +{ + regcache_cache_bypass(component->regmap, true); + snd_soc_component_write(component, AC97_POWERDOWN, 0xffff); + regcache_cache_bypass(component->regmap, false); + + return 0; +} + +static int wm9705_soc_resume(struct snd_soc_component *component) +{ + struct wm9705_priv *wm9705 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_ac97_reset(wm9705->ac97, true, WM9705_VENDOR_ID, + WM9705_VENDOR_ID_MASK); + if (ret < 0) + return ret; + + snd_soc_component_cache_sync(component); + + return 0; +} +#else +#define wm9705_soc_suspend NULL +#define wm9705_soc_resume NULL +#endif + +static int wm9705_soc_probe(struct snd_soc_component *component) +{ + struct wm9705_priv *wm9705 = snd_soc_component_get_drvdata(component); + struct regmap *regmap; + + if (wm9705->mfd_pdata) { + wm9705->ac97 = wm9705->mfd_pdata->ac97; + regmap = wm9705->mfd_pdata->regmap; + } else if (IS_ENABLED(CONFIG_SND_SOC_AC97_BUS)) { + wm9705->ac97 = snd_soc_new_ac97_component(component, WM9705_VENDOR_ID, + WM9705_VENDOR_ID_MASK); + if (IS_ERR(wm9705->ac97)) { + dev_err(component->dev, "Failed to register AC97 codec\n"); + return PTR_ERR(wm9705->ac97); + } + + regmap = regmap_init_ac97(wm9705->ac97, &wm9705_regmap_config); + if (IS_ERR(regmap)) { + snd_soc_free_ac97_component(wm9705->ac97); + return PTR_ERR(regmap); + } + } else { + return -ENXIO; + } + + snd_soc_component_set_drvdata(component, wm9705->ac97); + snd_soc_component_init_regmap(component, regmap); + + return 0; +} + +static void wm9705_soc_remove(struct snd_soc_component *component) +{ + struct wm9705_priv *wm9705 = snd_soc_component_get_drvdata(component); + + if (IS_ENABLED(CONFIG_SND_SOC_AC97_BUS) && !wm9705->mfd_pdata) { + snd_soc_component_exit_regmap(component); + snd_soc_free_ac97_component(wm9705->ac97); + } +} + +static const struct snd_soc_component_driver soc_component_dev_wm9705 = { + .probe = wm9705_soc_probe, + .remove = wm9705_soc_remove, + .suspend = wm9705_soc_suspend, + .resume = wm9705_soc_resume, + .controls = wm9705_snd_ac97_controls, + .num_controls = ARRAY_SIZE(wm9705_snd_ac97_controls), + .dapm_widgets = wm9705_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm9705_dapm_widgets), + .dapm_routes = wm9705_audio_map, + .num_dapm_routes = ARRAY_SIZE(wm9705_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm9705_probe(struct platform_device *pdev) +{ + struct wm9705_priv *wm9705; + + wm9705 = devm_kzalloc(&pdev->dev, sizeof(*wm9705), GFP_KERNEL); + if (wm9705 == NULL) + return -ENOMEM; + + wm9705->mfd_pdata = dev_get_platdata(&pdev->dev); + platform_set_drvdata(pdev, wm9705); + + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm9705, wm9705_dai, ARRAY_SIZE(wm9705_dai)); +} + +static struct platform_driver wm9705_codec_driver = { + .driver = { + .name = "wm9705-codec", + }, + + .probe = wm9705_probe, +}; + +module_platform_driver(wm9705_codec_driver); + +MODULE_DESCRIPTION("ASoC WM9705 driver"); +MODULE_AUTHOR("Ian Molton"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c new file mode 100644 index 000000000..01949eaba --- /dev/null +++ b/sound/soc/codecs/wm9712.c @@ -0,0 +1,731 @@ +/* + * wm9712.c -- ALSA Soc WM9712 codec support + * + * Copyright 2006-12 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WM9712_VENDOR_ID 0x574d4c12 +#define WM9712_VENDOR_ID_MASK 0xffffffff + +struct wm9712_priv { + struct snd_ac97 *ac97; + unsigned int hp_mixer[2]; + struct mutex lock; + struct wm97xx_platform_data *mfd_pdata; +}; + +static const struct reg_default wm9712_reg_defaults[] = { + { 0x02, 0x8000 }, + { 0x04, 0x8000 }, + { 0x06, 0x8000 }, + { 0x08, 0x0f0f }, + { 0x0a, 0xaaa0 }, + { 0x0c, 0xc008 }, + { 0x0e, 0x6808 }, + { 0x10, 0xe808 }, + { 0x12, 0xaaa0 }, + { 0x14, 0xad00 }, + { 0x16, 0x8000 }, + { 0x18, 0xe808 }, + { 0x1a, 0x3000 }, + { 0x1c, 0x8000 }, + { 0x20, 0x0000 }, + { 0x22, 0x0000 }, + { 0x26, 0x000f }, + { 0x28, 0x0605 }, + { 0x2a, 0x0410 }, + { 0x2c, 0xbb80 }, + { 0x2e, 0xbb80 }, + { 0x32, 0xbb80 }, + { 0x34, 0x2000 }, + { 0x4c, 0xf83e }, + { 0x4e, 0xffff }, + { 0x50, 0x0000 }, + { 0x52, 0x0000 }, + { 0x56, 0xf83e }, + { 0x58, 0x0008 }, + { 0x5c, 0x0000 }, + { 0x60, 0xb032 }, + { 0x62, 0x3e00 }, + { 0x64, 0x0000 }, + { 0x76, 0x0006 }, + { 0x78, 0x0001 }, + { 0x7a, 0x0000 }, +}; + +static bool wm9712_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_REC_GAIN: + return true; + default: + return regmap_ac97_default_volatile(dev, reg); + } +} + +static const struct regmap_config wm9712_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x7e, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm9712_volatile_reg, + + .reg_defaults = wm9712_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm9712_reg_defaults), +}; + +#define HPL_MIXER 0x0 +#define HPR_MIXER 0x1 + +static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"}; +static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"}; +static const char *wm9712_out3_src[] = {"Left", "VREF", "Left + Right", + "Mono"}; +static const char *wm9712_spk_src[] = {"Speaker Mix", "Headphone Mix"}; +static const char *wm9712_rec_adc[] = {"Stereo", "Left", "Right", "Mute"}; +static const char *wm9712_base[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm9712_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; +static const char *wm9712_mic[] = {"Mic 1", "Differential", "Mic 2", + "Stereo"}; +static const char *wm9712_rec_sel[] = {"Mic", "NC", "NC", "Speaker Mixer", + "Line", "Headphone Mixer", "Phone Mixer", "Phone"}; +static const char *wm9712_ng_type[] = {"Constant Gain", "Mute"}; +static const char *wm9712_diff_sel[] = {"Mic", "Line"}; + +static const DECLARE_TLV_DB_SCALE(main_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(boost_tlv, 0, 2000, 0); + +static const struct soc_enum wm9712_enum[] = { +SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9712_alc_select), +SOC_ENUM_SINGLE(AC97_VIDEO, 12, 4, wm9712_alc_mux), +SOC_ENUM_SINGLE(AC97_AUX, 9, 4, wm9712_out3_src), +SOC_ENUM_SINGLE(AC97_AUX, 8, 2, wm9712_spk_src), +SOC_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9712_rec_adc), +SOC_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9712_base), +SOC_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9712_rec_gain), +SOC_ENUM_SINGLE(AC97_MIC, 5, 4, wm9712_mic), +SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9712_rec_sel), +SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9712_rec_sel), +SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9712_ng_type), +SOC_ENUM_SINGLE(0x5c, 8, 2, wm9712_diff_sel), +}; + +static const struct snd_kcontrol_new wm9712_snd_ac97_controls[] = { +SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1), +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), +SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), + +SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0), +SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0), +SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0), +SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + +SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0), +SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +SOC_ENUM("ALC Function", wm9712_enum[0]), +SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), +SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1), +SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +SOC_ENUM("ALC NG Type", wm9712_enum[10]), +SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1), + +SOC_SINGLE("Mic Headphone Volume", AC97_VIDEO, 12, 7, 1), +SOC_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1), + +SOC_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1), +SOC_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 1), +SOC_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1), + +SOC_SINGLE("PCBeep Bypass Headphone Volume", AC97_PC_BEEP, 12, 7, 1), +SOC_SINGLE("PCBeep Bypass Speaker Volume", AC97_PC_BEEP, 8, 7, 1), +SOC_SINGLE("PCBeep Bypass Phone Volume", AC97_PC_BEEP, 4, 7, 1), + +SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1), +SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1), +SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1), + +SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1), +SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1), + +SOC_SINGLE_TLV("Capture Boost Switch", AC97_REC_SEL, 14, 1, 0, boost_tlv), +SOC_SINGLE_TLV("Capture to Phone Boost Switch", AC97_REC_SEL, 11, 1, 1, + boost_tlv), + +SOC_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1), +SOC_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1), +SOC_SINGLE("3D Playback Volume", AC97_3D_CONTROL, 0, 15, 0), + +SOC_ENUM("Bass Control", wm9712_enum[5]), +SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1), +SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1), +SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0), +SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1), +SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1), + +SOC_SINGLE("Capture Switch", AC97_REC_GAIN, 15, 1, 1), +SOC_ENUM("Capture Volume Steps", wm9712_enum[6]), +SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 0), +SOC_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0), + +SOC_SINGLE_TLV("Mic 1 Volume", AC97_MIC, 8, 31, 1, main_tlv), +SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv), +SOC_SINGLE_TLV("Mic Boost Volume", AC97_MIC, 7, 1, 0, boost_tlv), +}; + +static const unsigned int wm9712_mixer_mute_regs[] = { + AC97_VIDEO, + AC97_PCM, + AC97_LINE, + AC97_PHONE, + AC97_CD, + AC97_PC_BEEP, +}; + +/* We have to create a fake left and right HP mixers because + * the codec only has a single control that is shared by both channels. + * This makes it impossible to determine the audio path. + */ +static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct wm9712_priv *wm9712 = snd_soc_component_get_drvdata(component); + unsigned int val = ucontrol->value.integer.value[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mixer, mask, shift, old; + struct snd_soc_dapm_update update = {}; + bool change; + + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; + mask = 1 << shift; + + mutex_lock(&wm9712->lock); + old = wm9712->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9712->hp_mixer[mixer] |= mask; + else + wm9712->hp_mixer[mixer] &= ~mask; + + change = old != wm9712->hp_mixer[mixer]; + if (change) { + update.kcontrol = kcontrol; + update.reg = wm9712_mixer_mute_regs[shift]; + update.mask = 0x8000; + if ((wm9712->hp_mixer[0] & mask) || + (wm9712->hp_mixer[1] & mask)) + update.val = 0x0; + else + update.val = 0x8000; + + snd_soc_dapm_mixer_update_power(dapm, kcontrol, val, + &update); + } + + mutex_unlock(&wm9712->lock); + + return change; +} + +static int wm9712_hp_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct wm9712_priv *wm9712 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int shift, mixer; + + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; + + ucontrol->value.integer.value[0] = + (wm9712->hp_mixer[mixer] >> shift) & 1; + + return 0; +} + +#define WM9712_HP_MIXER_CTRL(xname, xmixer, xshift) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = wm9712_hp_mixer_get, .put = wm9712_hp_mixer_put, \ + .private_value = SOC_SINGLE_VALUE(SND_SOC_NOPM, \ + (xmixer << 8) | xshift, 1, 0, 0) \ +} + +/* Left Headphone Mixers */ +static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = { + WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPL_MIXER, 5), + WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 4), + WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPL_MIXER, 3), + WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPL_MIXER, 2), + WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 1), + WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPL_MIXER, 0), +}; + +/* Right Headphone Mixers */ +static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = { + WM9712_HP_MIXER_CTRL("PCBeep Bypass Switch", HPR_MIXER, 5), + WM9712_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 4), + WM9712_HP_MIXER_CTRL("Phone Bypass Switch", HPR_MIXER, 3), + WM9712_HP_MIXER_CTRL("Line Bypass Switch", HPR_MIXER, 2), + WM9712_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 1), + WM9712_HP_MIXER_CTRL("Mic Sidetone Switch", HPR_MIXER, 0), +}; + +/* Speaker Mixer */ +static const struct snd_kcontrol_new wm9712_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 11, 1, 1), + SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 11, 1, 1), + SOC_DAPM_SINGLE("Phone Bypass Switch", AC97_PHONE, 14, 1, 1), + SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 14, 1, 1), + SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 14, 1, 1), +}; + +/* Phone Mixer */ +static const struct snd_kcontrol_new wm9712_phone_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 7, 1, 1), + SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 7, 1, 1), + SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 13, 1, 1), + SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 13, 1, 1), + SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_MIC, 14, 1, 1), + SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_MIC, 13, 1, 1), +}; + +/* ALC headphone mux */ +static const struct snd_kcontrol_new wm9712_alc_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[1]); + +/* out 3 mux */ +static const struct snd_kcontrol_new wm9712_out3_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[2]); + +/* spk mux */ +static const struct snd_kcontrol_new wm9712_spk_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[3]); + +/* Capture to Phone mux */ +static const struct snd_kcontrol_new wm9712_capture_phone_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[4]); + +/* Capture left select */ +static const struct snd_kcontrol_new wm9712_capture_selectl_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[8]); + +/* Capture right select */ +static const struct snd_kcontrol_new wm9712_capture_selectr_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[9]); + +/* Mic select */ +static const struct snd_kcontrol_new wm9712_mic_src_controls = +SOC_DAPM_ENUM("Mic Source Select", wm9712_enum[7]); + +/* diff select */ +static const struct snd_kcontrol_new wm9712_diff_sel_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[11]); + +static const struct snd_soc_dapm_widget wm9712_dapm_widgets[] = { +SND_SOC_DAPM_MUX("ALC Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm9712_alc_mux_controls), +SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, + &wm9712_out3_mux_controls), +SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0, + &wm9712_spk_mux_controls), +SND_SOC_DAPM_MUX("Capture Phone Mux", SND_SOC_NOPM, 0, 0, + &wm9712_capture_phone_mux_controls), +SND_SOC_DAPM_MUX("Left Capture Select", SND_SOC_NOPM, 0, 0, + &wm9712_capture_selectl_controls), +SND_SOC_DAPM_MUX("Right Capture Select", SND_SOC_NOPM, 0, 0, + &wm9712_capture_selectr_controls), +SND_SOC_DAPM_MUX("Left Mic Select Source", SND_SOC_NOPM, 0, 0, + &wm9712_mic_src_controls), +SND_SOC_DAPM_MUX("Right Mic Select Source", SND_SOC_NOPM, 0, 0, + &wm9712_mic_src_controls), +SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0, + &wm9712_diff_sel_controls), +SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_INT_PAGING, 9, 1, + &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls)), +SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_INT_PAGING, 8, 1, + &wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls)), +SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1, + &wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1, + &wm9712_speaker_mixer_controls[0], + ARRAY_SIZE(wm9712_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_INT_PAGING, 14, 1), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_INT_PAGING, 13, 1), +SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_INT_PAGING, 12, 1), +SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_INT_PAGING, 11, 1), +SND_SOC_DAPM_PGA("Headphone PGA", AC97_INT_PAGING, 4, 1, NULL, 0), +SND_SOC_DAPM_PGA("Speaker PGA", AC97_INT_PAGING, 3, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 3 PGA", AC97_INT_PAGING, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Line PGA", AC97_INT_PAGING, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("Phone PGA", AC97_INT_PAGING, 1, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic PGA", AC97_INT_PAGING, 0, 1, NULL, 0), +SND_SOC_DAPM_PGA("Differential Mic", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_INT_PAGING, 10, 1), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LOUT2"), +SND_SOC_DAPM_OUTPUT("ROUT2"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_INPUT("LINEINL"), +SND_SOC_DAPM_INPUT("LINEINR"), +SND_SOC_DAPM_INPUT("PHONE"), +SND_SOC_DAPM_INPUT("PCBEEP"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2"), +}; + +static const struct snd_soc_dapm_route wm9712_audio_map[] = { + /* virtual mixer - mixes left & right channels for spk and mono */ + {"AC97 Mixer", NULL, "Left DAC"}, + {"AC97 Mixer", NULL, "Right DAC"}, + + /* Left HP mixer */ + {"Left HP Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Left HP Mixer", "Phone Bypass Switch", "Phone PGA"}, + {"Left HP Mixer", "Line Bypass Switch", "Line PGA"}, + {"Left HP Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left HP Mixer", "Mic Sidetone Switch", "Mic PGA"}, + {"Left HP Mixer", NULL, "ALC Sidetone Mux"}, + + /* Right HP mixer */ + {"Right HP Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Right HP Mixer", "Phone Bypass Switch", "Phone PGA"}, + {"Right HP Mixer", "Line Bypass Switch", "Line PGA"}, + {"Right HP Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right HP Mixer", "Mic Sidetone Switch", "Mic PGA"}, + {"Right HP Mixer", NULL, "ALC Sidetone Mux"}, + + /* speaker mixer */ + {"Speaker Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Speaker Mixer", "Line Bypass Switch", "Line PGA"}, + {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Speaker Mixer", "Phone Bypass Switch", "Phone PGA"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"}, + + /* Phone mixer */ + {"Phone Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Phone Mixer", "Line Bypass Switch", "Line PGA"}, + {"Phone Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Phone Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Phone Mixer", "Mic 1 Sidetone Switch", "Mic PGA"}, + {"Phone Mixer", "Mic 2 Sidetone Switch", "Mic PGA"}, + + /* inputs */ + {"Line PGA", NULL, "LINEINL"}, + {"Line PGA", NULL, "LINEINR"}, + {"Phone PGA", NULL, "PHONE"}, + {"Mic PGA", NULL, "MIC1"}, + {"Mic PGA", NULL, "MIC2"}, + + /* microphones */ + {"Differential Mic", NULL, "MIC1"}, + {"Differential Mic", NULL, "MIC2"}, + {"Left Mic Select Source", "Mic 1", "MIC1"}, + {"Left Mic Select Source", "Mic 2", "MIC2"}, + {"Left Mic Select Source", "Stereo", "MIC1"}, + {"Left Mic Select Source", "Differential", "Differential Mic"}, + {"Right Mic Select Source", "Mic 1", "MIC1"}, + {"Right Mic Select Source", "Mic 2", "MIC2"}, + {"Right Mic Select Source", "Stereo", "MIC2"}, + {"Right Mic Select Source", "Differential", "Differential Mic"}, + + /* left capture selector */ + {"Left Capture Select", "Mic", "MIC1"}, + {"Left Capture Select", "Speaker Mixer", "Speaker Mixer"}, + {"Left Capture Select", "Line", "LINEINL"}, + {"Left Capture Select", "Headphone Mixer", "Left HP Mixer"}, + {"Left Capture Select", "Phone Mixer", "Phone Mixer"}, + {"Left Capture Select", "Phone", "PHONE"}, + + /* right capture selector */ + {"Right Capture Select", "Mic", "MIC2"}, + {"Right Capture Select", "Speaker Mixer", "Speaker Mixer"}, + {"Right Capture Select", "Line", "LINEINR"}, + {"Right Capture Select", "Headphone Mixer", "Right HP Mixer"}, + {"Right Capture Select", "Phone Mixer", "Phone Mixer"}, + {"Right Capture Select", "Phone", "PHONE"}, + + /* ALC Sidetone */ + {"ALC Sidetone Mux", "Stereo", "Left Capture Select"}, + {"ALC Sidetone Mux", "Stereo", "Right Capture Select"}, + {"ALC Sidetone Mux", "Left", "Left Capture Select"}, + {"ALC Sidetone Mux", "Right", "Right Capture Select"}, + + /* ADC's */ + {"Left ADC", NULL, "Left Capture Select"}, + {"Right ADC", NULL, "Right Capture Select"}, + + /* outputs */ + {"MONOOUT", NULL, "Phone Mixer"}, + {"HPOUTL", NULL, "Headphone PGA"}, + {"Headphone PGA", NULL, "Left HP Mixer"}, + {"HPOUTR", NULL, "Headphone PGA"}, + {"Headphone PGA", NULL, "Right HP Mixer"}, + + /* mono mixer */ + {"Mono Mixer", NULL, "Left HP Mixer"}, + {"Mono Mixer", NULL, "Right HP Mixer"}, + + /* Out3 Mux */ + {"Out3 Mux", "Left", "Left HP Mixer"}, + {"Out3 Mux", "Mono", "Phone Mixer"}, + {"Out3 Mux", "Left + Right", "Mono Mixer"}, + {"Out 3 PGA", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3 PGA"}, + + /* speaker Mux */ + {"Speaker Mux", "Speaker Mix", "Speaker Mixer"}, + {"Speaker Mux", "Headphone Mix", "Mono Mixer"}, + {"Speaker PGA", NULL, "Speaker Mux"}, + {"LOUT2", NULL, "Speaker PGA"}, + {"ROUT2", NULL, "Speaker PGA"}, +}; + +static int ac97_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int reg; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x1, 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return snd_soc_component_write(component, reg, runtime->rate); +} + +static int ac97_aux_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x1, 0x1); + snd_soc_component_update_bits(component, AC97_PCI_SID, 0x8000, 0x8000); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + + return snd_soc_component_write(component, AC97_PCM_SURR_DAC_RATE, runtime->rate); +} + +#define WM9712_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) + +static const struct snd_soc_dai_ops wm9712_dai_ops_hifi = { + .prepare = ac97_prepare, +}; + +static const struct snd_soc_dai_ops wm9712_dai_ops_aux = { + .prepare = ac97_aux_prepare, +}; + +static struct snd_soc_dai_driver wm9712_dai[] = { +{ + .name = "wm9712-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9712_AC97_RATES, + .formats = SND_SOC_STD_AC97_FMTS,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9712_AC97_RATES, + .formats = SND_SOC_STD_AC97_FMTS,}, + .ops = &wm9712_dai_ops_hifi, +}, +{ + .name = "wm9712-aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9712_AC97_RATES, + .formats = SND_SOC_STD_AC97_FMTS,}, + .ops = &wm9712_dai_ops_aux, +} +}; + +static int wm9712_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_component_write(component, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: + /* disable everything including AC link */ + snd_soc_component_write(component, AC97_EXTENDED_MSTATUS, 0xffff); + snd_soc_component_write(component, AC97_POWERDOWN, 0xffff); + break; + } + return 0; +} + +static int wm9712_soc_resume(struct snd_soc_component *component) +{ + struct wm9712_priv *wm9712 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_ac97_reset(wm9712->ac97, true, WM9712_VENDOR_ID, + WM9712_VENDOR_ID_MASK); + if (ret < 0) + return ret; + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + if (ret == 0) + snd_soc_component_cache_sync(component); + + return ret; +} + +static int wm9712_soc_probe(struct snd_soc_component *component) +{ + struct wm9712_priv *wm9712 = snd_soc_component_get_drvdata(component); + struct regmap *regmap; + + if (wm9712->mfd_pdata) { + wm9712->ac97 = wm9712->mfd_pdata->ac97; + regmap = wm9712->mfd_pdata->regmap; + } else if (IS_ENABLED(CONFIG_SND_SOC_AC97_BUS)) { + int ret; + + wm9712->ac97 = snd_soc_new_ac97_component(component, WM9712_VENDOR_ID, + WM9712_VENDOR_ID_MASK); + if (IS_ERR(wm9712->ac97)) { + ret = PTR_ERR(wm9712->ac97); + dev_err(component->dev, + "Failed to register AC97 codec: %d\n", ret); + return ret; + } + + regmap = regmap_init_ac97(wm9712->ac97, &wm9712_regmap_config); + if (IS_ERR(regmap)) { + snd_soc_free_ac97_component(wm9712->ac97); + return PTR_ERR(regmap); + } + } else { + return -ENXIO; + } + + snd_soc_component_init_regmap(component, regmap); + + /* set alc mux to none */ + snd_soc_component_update_bits(component, AC97_VIDEO, 0x3000, 0x3000); + + return 0; +} + +static void wm9712_soc_remove(struct snd_soc_component *component) +{ + struct wm9712_priv *wm9712 = snd_soc_component_get_drvdata(component); + + if (IS_ENABLED(CONFIG_SND_SOC_AC97_BUS) && !wm9712->mfd_pdata) { + snd_soc_component_exit_regmap(component); + snd_soc_free_ac97_component(wm9712->ac97); + } +} + +static const struct snd_soc_component_driver soc_component_dev_wm9712 = { + .probe = wm9712_soc_probe, + .remove = wm9712_soc_remove, + .resume = wm9712_soc_resume, + .set_bias_level = wm9712_set_bias_level, + .controls = wm9712_snd_ac97_controls, + .num_controls = ARRAY_SIZE(wm9712_snd_ac97_controls), + .dapm_widgets = wm9712_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm9712_dapm_widgets), + .dapm_routes = wm9712_audio_map, + .num_dapm_routes = ARRAY_SIZE(wm9712_audio_map), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm9712_probe(struct platform_device *pdev) +{ + struct wm9712_priv *wm9712; + + wm9712 = devm_kzalloc(&pdev->dev, sizeof(*wm9712), GFP_KERNEL); + if (wm9712 == NULL) + return -ENOMEM; + + mutex_init(&wm9712->lock); + + wm9712->mfd_pdata = dev_get_platdata(&pdev->dev); + platform_set_drvdata(pdev, wm9712); + + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm9712, wm9712_dai, ARRAY_SIZE(wm9712_dai)); +} + +static struct platform_driver wm9712_component_driver = { + .driver = { + .name = "wm9712-codec", + }, + + .probe = wm9712_probe, +}; + +module_platform_driver(wm9712_component_driver); + +MODULE_DESCRIPTION("ASoC WM9711/WM9712 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c new file mode 100644 index 000000000..5a2fdf4f6 --- /dev/null +++ b/sound/soc/codecs/wm9713.c @@ -0,0 +1,1296 @@ +/* + * wm9713.c -- ALSA Soc WM9713 codec support + * + * Copyright 2006-10 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Features:- + * + * o Support for AC97 Codec, Voice DAC and Aux DAC + * o Support for DAPM + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm9713.h" + +#define WM9713_VENDOR_ID 0x574d4c13 +#define WM9713_VENDOR_ID_MASK 0xffffffff + +struct wm9713_priv { + struct snd_ac97 *ac97; + u32 pll_in; /* PLL input frequency */ + unsigned int hp_mixer[2]; + struct mutex lock; + struct wm97xx_platform_data *mfd_pdata; +}; + +#define HPL_MIXER 0 +#define HPR_MIXER 1 + +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", "Speaker", + "Mono Out", "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", "Inv"}; +static const char *wm9713_spk_pga[] = + {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid", + "Speaker Vmid", "Inv Vmid"}; +static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone", + "Headphone Vmid"}; +static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"}; +static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"}; +static const char *wm9713_dac_inv[] = + {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone", + "Headphone Mono", "NC", "Vmid"}; +static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"}; +static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"}; +static const char *wm9713_micb_select[] = {"MPB", "MPA"}; + +static const struct soc_enum wm9713_enum[] = { +SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/ +SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */ +SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */ +SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */ +SOC_ENUM_SINGLE_VIRT(2, wm9713_micb_select), /* mic selection 19 */ +}; + +static const DECLARE_TLV_DB_SCALE(out_tlv, -4650, 150, 0); +static const DECLARE_TLV_DB_SCALE(main_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(misc_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_RANGE(mic_tlv, + 0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0), + 3, 3, TLV_DB_SCALE_ITEM(3000, 0, 0) +); + +static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = { +SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, out_tlv), +SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1), +SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1, + out_tlv), +SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1), +SOC_DOUBLE_TLV("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1, main_tlv), +SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1, main_tlv), +SOC_SINGLE_TLV("Mic 1 Volume", AC97_MIC, 8, 31, 1, main_tlv), +SOC_SINGLE_TLV("Mic 2 Volume", AC97_MIC, 0, 31, 1, main_tlv), +SOC_SINGLE_TLV("Mic 1 Preamp Volume", AC97_3D_CONTROL, 10, 3, 0, mic_tlv), +SOC_SINGLE_TLV("Mic 2 Preamp Volume", AC97_3D_CONTROL, 12, 3, 0, mic_tlv), + +SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0), +SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1), + +SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1), +SOC_ENUM("Capture Volume Steps", wm9713_enum[5]), +SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0), +SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0), + +SOC_SINGLE_TLV("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1, misc_tlv), +SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0), +SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0), + +SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0), +SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +SOC_ENUM("ALC Function", wm9713_enum[6]), +SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), +SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0), +SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +SOC_ENUM("ALC NG Type", wm9713_enum[17]), +SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0), + +SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0), +SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0), + +SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1), +SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0), +SOC_SINGLE_TLV("Out4 Playback Volume", AC97_MASTER_MONO, 8, 31, 1, out_tlv), + +SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1), +SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0), +SOC_SINGLE_TLV("Out3 Playback Volume", AC97_MASTER_MONO, 0, 31, 1, out_tlv), + +SOC_SINGLE_TLV("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1, main_tlv), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1), +SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0), +SOC_SINGLE_TLV("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1, out_tlv), + +SOC_SINGLE_TLV("Headphone Mixer Beep Playback Volume", AC97_AUX, 12, 7, 1, + misc_tlv), +SOC_SINGLE_TLV("Speaker Mixer Beep Playback Volume", AC97_AUX, 8, 7, 1, + misc_tlv), +SOC_SINGLE_TLV("Mono Mixer Beep Playback Volume", AC97_AUX, 4, 7, 1, misc_tlv), + +SOC_SINGLE_TLV("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1, + misc_tlv), +SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1), +SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1), + +SOC_SINGLE_TLV("Headphone Mixer Aux Playback Volume", AC97_REC_SEL, 12, 7, 1, + misc_tlv), + +SOC_SINGLE_TLV("Speaker Mixer Voice Playback Volume", AC97_PCM, 8, 7, 1, + misc_tlv), +SOC_SINGLE_TLV("Speaker Mixer Aux Playback Volume", AC97_REC_SEL, 8, 7, 1, + misc_tlv), + +SOC_SINGLE_TLV("Mono Mixer Voice Playback Volume", AC97_PCM, 4, 7, 1, + misc_tlv), +SOC_SINGLE_TLV("Mono Mixer Aux Playback Volume", AC97_REC_SEL, 4, 7, 1, + misc_tlv), + +SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1), +SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1), + +SOC_ENUM("Bass Control", wm9713_enum[16]), +SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1), +SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1), +SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0), +SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1), +SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1), + +SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0), +SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), +SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), +}; + +static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (WARN_ON(event != SND_SOC_DAPM_PRE_PMD)) + return -EINVAL; + + /* Gracefully shut down the voice interface. */ + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0f00, 0x0200); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0f00, 0x0f00); + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0x1000, 0x1000); + + return 0; +} + +static const unsigned int wm9713_mixer_mute_regs[] = { + AC97_PC_BEEP, + AC97_MASTER_TONE, + AC97_PHONE, + AC97_REC_SEL, + AC97_PCM, + AC97_AUX, +}; + +/* We have to create a fake left and right HP mixers because + * the codec only has a single control that is shared by both channels. + * This makes it impossible to determine the audio path using the current + * register map, thus we add a new (virtual) register to help determine the + * audio route within the device. + */ +static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct wm9713_priv *wm9713 = snd_soc_component_get_drvdata(component); + unsigned int val = ucontrol->value.integer.value[0]; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mixer, mask, shift, old; + struct snd_soc_dapm_update update = {}; + bool change; + + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; + mask = (1 << shift); + + mutex_lock(&wm9713->lock); + old = wm9713->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9713->hp_mixer[mixer] |= mask; + else + wm9713->hp_mixer[mixer] &= ~mask; + + change = old != wm9713->hp_mixer[mixer]; + if (change) { + update.kcontrol = kcontrol; + update.reg = wm9713_mixer_mute_regs[shift]; + update.mask = 0x8000; + if ((wm9713->hp_mixer[0] & mask) || + (wm9713->hp_mixer[1] & mask)) + update.val = 0x0; + else + update.val = 0x8000; + + snd_soc_dapm_mixer_update_power(dapm, kcontrol, val, + &update); + } + + mutex_unlock(&wm9713->lock); + + return change; +} + +static int wm9713_hp_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct wm9713_priv *wm9713 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mixer, shift; + + mixer = mc->shift >> 8; + shift = mc->shift & 0xff; + + ucontrol->value.integer.value[0] = + (wm9713->hp_mixer[mixer] >> shift) & 1; + + return 0; +} + +#define WM9713_HP_MIXER_CTRL(xname, xmixer, xshift) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = wm9713_hp_mixer_get, .put = wm9713_hp_mixer_put, \ + .private_value = SOC_DOUBLE_VALUE(SND_SOC_NOPM, \ + xshift, xmixer, 1, 0, 0) \ +} + +/* Left Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = { +WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPL_MIXER, 5), +WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPL_MIXER, 4), +WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPL_MIXER, 3), +WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPL_MIXER, 2), +WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPL_MIXER, 1), +WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPL_MIXER, 0), +}; + +/* Right Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = { +WM9713_HP_MIXER_CTRL("Beep Playback Switch", HPR_MIXER, 5), +WM9713_HP_MIXER_CTRL("Voice Playback Switch", HPR_MIXER, 4), +WM9713_HP_MIXER_CTRL("Aux Playback Switch", HPR_MIXER, 3), +WM9713_HP_MIXER_CTRL("PCM Playback Switch", HPR_MIXER, 2), +WM9713_HP_MIXER_CTRL("MonoIn Playback Switch", HPR_MIXER, 1), +WM9713_HP_MIXER_CTRL("Bypass Playback Switch", HPR_MIXER, 0), +}; + +/* headphone capture mux */ +static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[1]); + +/* headphone mic mux */ +static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[0]); + +/* Speaker Mixer */ +static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 11, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Beep Playback Switch", AC97_AUX, 7, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1), +SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1), +SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1), +}; + +/* mono mic mux */ +static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[2]); + +/* mono output mux */ +static const struct snd_kcontrol_new wm9713_mono_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[7]); + +/* speaker left output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[8]); + +/* speaker right output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[9]); + +/* headphone left output mux */ +static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[10]); + +/* headphone right output mux */ +static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[11]); + +/* Out3 mux */ +static const struct snd_kcontrol_new wm9713_out3_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[12]); + +/* Out4 mux */ +static const struct snd_kcontrol_new wm9713_out4_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[13]); + +/* DAC inv mux 1 */ +static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[14]); + +/* DAC inv mux 2 */ +static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[15]); + +/* Capture source left */ +static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[3]); + +/* Capture source right */ +static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[4]); + +/* mic source */ +static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[18]); + +/* mic source B virtual control */ +static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[19]); + +static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = { +SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_rec_mux_controls), +SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_mic_mux_controls), +SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mic_mux_controls), +SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mux_controls), +SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkl_mux_controls), +SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkr_mux_controls), +SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpl_out_mux_controls), +SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpr_out_mux_controls), +SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out3_mux_controls), +SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out4_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv1_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv2_mux_controls), +SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcl_mux_controls), +SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcr_mux_controls), +SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0, + &wm9713_mic_sel_mux_controls), +SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0, + &wm9713_micb_sel_mux_controls), +SND_SOC_DAPM_MIXER("Left HP Mixer", AC97_EXTENDED_MID, 3, 1, + &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls)), +SND_SOC_DAPM_MIXER("Right HP Mixer", AC97_EXTENDED_MID, 2, 1, + &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1, + &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1, + &wm9713_speaker_mixer_controls[0], + ARRAY_SIZE(wm9713_speaker_mixer_controls)), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1), +SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_DAC_E("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1, + wm9713_voice_shutdown, SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), +SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0), +SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1), +SND_SOC_DAPM_OUTPUT("MONO"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("SPKL"), +SND_SOC_DAPM_OUTPUT("SPKR"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_INPUT("LINEL"), +SND_SOC_DAPM_INPUT("LINER"), +SND_SOC_DAPM_INPUT("MONOIN"), +SND_SOC_DAPM_INPUT("PCBEEP"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2A"), +SND_SOC_DAPM_INPUT("MIC2B"), +SND_SOC_DAPM_VMID("VMID"), +}; + +static const struct snd_soc_dapm_route wm9713_audio_map[] = { + /* left HP mixer */ + {"Left HP Mixer", "Beep Playback Switch", "PCBEEP"}, + {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"}, + {"Left HP Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Left HP Mixer", NULL, "Capture Headphone Mux"}, + + /* right HP mixer */ + {"Right HP Mixer", "Beep Playback Switch", "PCBEEP"}, + {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"}, + {"Right HP Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Right HP Mixer", NULL, "Capture Headphone Mux"}, + + /* virtual mixer - mixes left & right channels for spk and mono */ + {"AC97 Mixer", NULL, "Left DAC"}, + {"AC97 Mixer", NULL, "Right DAC"}, + {"Line Mixer", NULL, "Right Line In"}, + {"Line Mixer", NULL, "Left Line In"}, + {"HP Mixer", NULL, "Left HP Mixer"}, + {"HP Mixer", NULL, "Right HP Mixer"}, + {"Capture Mixer", NULL, "Left Capture Source"}, + {"Capture Mixer", NULL, "Right Capture Source"}, + + /* speaker mixer */ + {"Speaker Mixer", "Beep Playback Switch", "PCBEEP"}, + {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"}, + + /* mono mixer */ + {"Mono Mixer", "Beep Playback Switch", "PCBEEP"}, + {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"}, + {"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"}, + {"Mono Mixer", NULL, "Capture Mono Mux"}, + + /* DAC inv mux 1 */ + {"DAC Inv Mux 1", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"}, + + /* DAC inv mux 2 */ + {"DAC Inv Mux 2", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"}, + + /* headphone left mux */ + {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"}, + + /* headphone right mux */ + {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"}, + + /* speaker left mux */ + {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"}, + {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* speaker right mux */ + {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"}, + {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"}, + + /* mono mux */ + {"Mono Out Mux", "Mono", "Mono Mixer"}, + {"Mono Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* out 3 mux */ + {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"}, + + /* out 4 mux */ + {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"}, + + /* output pga */ + {"HPL", NULL, "Left Headphone"}, + {"Left Headphone", NULL, "Left Headphone Out Mux"}, + {"HPR", NULL, "Right Headphone"}, + {"Right Headphone", NULL, "Right Headphone Out Mux"}, + {"OUT3", NULL, "Out 3"}, + {"Out 3", NULL, "Out 3 Mux"}, + {"OUT4", NULL, "Out 4"}, + {"Out 4", NULL, "Out 4 Mux"}, + {"SPKL", NULL, "Left Speaker"}, + {"Left Speaker", NULL, "Left Speaker Out Mux"}, + {"SPKR", NULL, "Right Speaker"}, + {"Right Speaker", NULL, "Right Speaker Out Mux"}, + {"MONO", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono Out Mux"}, + + /* input pga */ + {"Left Line In", NULL, "LINEL"}, + {"Right Line In", NULL, "LINER"}, + {"Mono In", NULL, "MONOIN"}, + {"Mic A PGA", NULL, "Mic A Pre Amp"}, + {"Mic B PGA", NULL, "Mic B Pre Amp"}, + + /* left capture select */ + {"Left Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Left Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Left Capture Source", "Line", "LINEL"}, + {"Left Capture Source", "Mono In", "MONOIN"}, + {"Left Capture Source", "Headphone", "Left HP Mixer"}, + {"Left Capture Source", "Speaker", "Speaker Mixer"}, + {"Left Capture Source", "Mono Out", "Mono Mixer"}, + + /* right capture select */ + {"Right Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Right Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Right Capture Source", "Line", "LINER"}, + {"Right Capture Source", "Mono In", "MONOIN"}, + {"Right Capture Source", "Headphone", "Right HP Mixer"}, + {"Right Capture Source", "Speaker", "Speaker Mixer"}, + {"Right Capture Source", "Mono Out", "Mono Mixer"}, + + /* left ADC */ + {"Left ADC", NULL, "Left Capture Source"}, + {"Left Voice ADC", NULL, "Left ADC"}, + {"Left HiFi ADC", NULL, "Left ADC"}, + + /* right ADC */ + {"Right ADC", NULL, "Right Capture Source"}, + {"Right Voice ADC", NULL, "Right ADC"}, + {"Right HiFi ADC", NULL, "Right ADC"}, + + /* mic */ + {"Mic A Pre Amp", NULL, "Mic A Source"}, + {"Mic A Source", "Mic 1", "MIC1"}, + {"Mic A Source", "Mic 2 A", "MIC2A"}, + {"Mic A Source", "Mic 2 B", "Mic B Source"}, + {"Mic B Pre Amp", "MPB", "Mic B Source"}, + {"Mic B Source", NULL, "MIC2B"}, + + /* headphone capture */ + {"Capture Headphone Mux", "Stereo", "Capture Mixer"}, + {"Capture Headphone Mux", "Left", "Left Capture Source"}, + {"Capture Headphone Mux", "Right", "Right Capture Source"}, + + /* mono capture */ + {"Capture Mono Mux", "Stereo", "Capture Mixer"}, + {"Capture Mono Mux", "Left", "Left Capture Source"}, + {"Capture Mono Mux", "Right", "Right Capture Source"}, +}; + +static bool wm9713_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_RESET ... AC97_PCM_SURR_DAC_RATE: + case AC97_PCM_LR_ADC_RATE: + case AC97_CENTER_LFE_MASTER: + case AC97_SPDIF ... AC97_LINE1_LEVEL: + case AC97_GPIO_CFG ... 0x5c: + case AC97_CODEC_CLASS_REV ... AC97_PCI_SID: + case 0x74 ... AC97_VENDOR_ID2: + return true; + default: + return false; + } +} + +static bool wm9713_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return false; + default: + return wm9713_readable_reg(dev, reg); + } +} + +static const struct reg_default wm9713_reg_defaults[] = { + { 0x02, 0x8080 }, /* Speaker Output Volume */ + { 0x04, 0x8080 }, /* Headphone Output Volume */ + { 0x06, 0x8080 }, /* Out3/OUT4 Volume */ + { 0x08, 0xc880 }, /* Mono Volume */ + { 0x0a, 0xe808 }, /* LINEIN Volume */ + { 0x0c, 0xe808 }, /* DAC PGA Volume */ + { 0x0e, 0x0808 }, /* MIC PGA Volume */ + { 0x10, 0x00da }, /* MIC Routing Control */ + { 0x12, 0x8000 }, /* Record PGA Volume */ + { 0x14, 0xd600 }, /* Record Routing */ + { 0x16, 0xaaa0 }, /* PCBEEP Volume */ + { 0x18, 0xaaa0 }, /* VxDAC Volume */ + { 0x1a, 0xaaa0 }, /* AUXDAC Volume */ + { 0x1c, 0x0000 }, /* Output PGA Mux */ + { 0x1e, 0x0000 }, /* DAC 3D control */ + { 0x20, 0x0f0f }, /* DAC Tone Control*/ + { 0x22, 0x0040 }, /* MIC Input Select & Bias */ + { 0x24, 0x0000 }, /* Output Volume Mapping & Jack */ + { 0x26, 0x7f00 }, /* Powerdown Ctrl/Stat*/ + { 0x28, 0x0405 }, /* Extended Audio ID */ + { 0x2a, 0x0410 }, /* Extended Audio Start/Ctrl */ + { 0x2c, 0xbb80 }, /* Audio DACs Sample Rate */ + { 0x2e, 0xbb80 }, /* AUXDAC Sample Rate */ + { 0x32, 0xbb80 }, /* Audio ADCs Sample Rate */ + { 0x36, 0x4523 }, /* PCM codec control */ + { 0x3a, 0x2000 }, /* SPDIF control */ + { 0x3c, 0xfdff }, /* Powerdown 1 */ + { 0x3e, 0xffff }, /* Powerdown 2 */ + { 0x40, 0x0000 }, /* General Purpose */ + { 0x42, 0x0000 }, /* Fast Power-Up Control */ + { 0x44, 0x0080 }, /* MCLK/PLL Control */ + { 0x46, 0x0000 }, /* MCLK/PLL Control */ + { 0x4c, 0xfffe }, /* GPIO Pin Configuration */ + { 0x4e, 0xffff }, /* GPIO Pin Polarity / Type */ + { 0x50, 0x0000 }, /* GPIO Pin Sticky */ + { 0x52, 0x0000 }, /* GPIO Pin Wake-Up */ + /* GPIO Pin Status */ + { 0x56, 0xfffe }, /* GPIO Pin Sharing */ + { 0x58, 0x4000 }, /* GPIO PullUp/PullDown */ + { 0x5a, 0x0000 }, /* Additional Functions 1 */ + { 0x5c, 0x0000 }, /* Additional Functions 2 */ + { 0x60, 0xb032 }, /* ALC Control */ + { 0x62, 0x3e00 }, /* ALC / Noise Gate Control */ + { 0x64, 0x0000 }, /* AUXDAC input control */ + { 0x74, 0x0000 }, /* Digitiser Reg 1 */ + { 0x76, 0x0006 }, /* Digitiser Reg 2 */ + { 0x78, 0x0001 }, /* Digitiser Reg 3 */ + { 0x7a, 0x0000 }, /* Digitiser Read Back */ +}; + +static const struct regmap_config wm9713_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x7e, + .cache_type = REGCACHE_RBTREE, + + .reg_defaults = wm9713_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm9713_reg_defaults), + .volatile_reg = regmap_ac97_default_volatile, + .readable_reg = wm9713_readable_reg, + .writeable_reg = wm9713_writeable_reg, +}; + +/* PLL divisors */ +struct _pll_div { + u32 divsel:1; + u32 divctl:1; + u32 lf:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the PLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 22) * 10) + +static void pll_factors(struct snd_soc_component *component, + struct _pll_div *pll_div, unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + + /* The the PLL output is always 98.304MHz. */ + target = 98304000; + + /* If the input frequency is over 14.4MHz then scale it down. */ + if (source > 14400000) { + source >>= 1; + pll_div->divsel = 1; + + if (source > 14400000) { + source >>= 1; + pll_div->divctl = 1; + } else + pll_div->divctl = 0; + + } else { + pll_div->divsel = 0; + pll_div->divctl = 0; + } + + /* Low frequency sources require an additional divide in the + * loop. + */ + if (source < 8192000) { + pll_div->lf = 1; + target >>= 2; + } else + pll_div->lf = 0; + + Ndiv = target / source; + if ((Ndiv < 5) || (Ndiv > 12)) + dev_warn(component->dev, + "WM9713 PLL N value %u out of recommended range!\n", + Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +/** + * Please note that changing the PLL input frequency may require + * resynchronisation with the AC97 controller. + */ +static int wm9713_set_pll(struct snd_soc_component *component, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm9713_priv *wm9713 = snd_soc_component_get_drvdata(component); + u16 reg, reg2; + struct _pll_div pll_div; + + /* turn PLL off ? */ + if (freq_in == 0) { + /* disable PLL power and select ext source */ + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0080, 0x0080); + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0x0200, 0x0200); + wm9713->pll_in = 0; + return 0; + } + + pll_factors(component, &pll_div, freq_in); + + if (pll_div.k == 0) { + reg = (pll_div.n << 12) | (pll_div.lf << 11) | + (pll_div.divsel << 9) | (pll_div.divctl << 8); + snd_soc_component_write(component, AC97_LINE1_LEVEL, reg); + } else { + /* write the fractional k to the reg 0x46 pages */ + reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) | + (pll_div.divsel << 9) | (pll_div.divctl << 8); + + /* K [21:20] */ + reg = reg2 | (0x5 << 4) | (pll_div.k >> 20); + snd_soc_component_write(component, AC97_LINE1_LEVEL, reg); + + /* K [19:16] */ + reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf); + snd_soc_component_write(component, AC97_LINE1_LEVEL, reg); + + /* K [15:12] */ + reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf); + snd_soc_component_write(component, AC97_LINE1_LEVEL, reg); + + /* K [11:8] */ + reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf); + snd_soc_component_write(component, AC97_LINE1_LEVEL, reg); + + /* K [7:4] */ + reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf); + snd_soc_component_write(component, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */ + snd_soc_component_write(component, AC97_LINE1_LEVEL, reg); + } + + /* turn PLL on and select as source */ + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0x0200, 0x0000); + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0080, 0x0000); + wm9713->pll_in = freq_in; + + /* wait 10ms AC97 link frames for the link to stabilise */ + schedule_timeout_interruptible(msecs_to_jiffies(10)); + return 0; +} + +static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + return wm9713_set_pll(component, pll_id, freq_in, freq_out); +} + +/* + * Tristate the PCM DAI lines, tristate can be disabled by calling + * wm9713_set_dai_fmt() + */ +static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai, + int tristate) +{ + struct snd_soc_component *component = codec_dai->component; + + if (tristate) + snd_soc_component_update_bits(component, AC97_CENTER_LFE_MASTER, + 0x6000, 0x0000); + + return 0; +} + +/* + * Configure WM9713 clock dividers. + * Voice DAC needs 256 FS + */ +static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_component *component = codec_dai->component; + + switch (div_id) { + case WM9713_PCMCLK_DIV: + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0f00, div); + break; + case WM9713_CLKA_MULT: + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0002, div); + break; + case WM9713_CLKB_MULT: + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0004, div); + break; + case WM9713_HIFI_DIV: + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x7000, div); + break; + case WM9713_PCMBCLK_DIV: + snd_soc_component_update_bits(component, AC97_CENTER_LFE_MASTER, 0x0e00, div); + break; + case WM9713_PCMCLK_PLL_DIV: + snd_soc_component_update_bits(component, AC97_LINE1_LEVEL, + 0x007f, div | 0x60); + break; + case WM9713_HIFI_PLL_DIV: + snd_soc_component_update_bits(component, AC97_LINE1_LEVEL, + 0x007f, div | 0x70); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + u16 gpio = snd_soc_component_read32(component, AC97_GPIO_CFG) & 0xffc5; + u16 reg = 0x8000; + + /* clock masters */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + reg |= 0x4000; + gpio |= 0x0010; + break; + case SND_SOC_DAIFMT_CBM_CFS: + reg |= 0x6000; + gpio |= 0x0018; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg |= 0x2000; + gpio |= 0x001a; + break; + case SND_SOC_DAIFMT_CBS_CFM: + gpio |= 0x0012; + break; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + reg |= 0x00c0; + break; + case SND_SOC_DAIFMT_IB_NF: + reg |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + reg |= 0x0040; + break; + } + + /* DAI format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + reg |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + reg |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + reg |= 0x0043; + break; + } + + snd_soc_component_write(component, AC97_GPIO_CFG, gpio); + snd_soc_component_write(component, AC97_CENTER_LFE_MASTER, reg); + return 0; +} + +static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + + /* enable PCM interface in master mode */ + switch (params_width(params)) { + case 16: + break; + case 20: + snd_soc_component_update_bits(component, AC97_CENTER_LFE_MASTER, + 0x000c, 0x0004); + break; + case 24: + snd_soc_component_update_bits(component, AC97_CENTER_LFE_MASTER, + 0x000c, 0x0008); + break; + case 32: + snd_soc_component_update_bits(component, AC97_CENTER_LFE_MASTER, + 0x000c, 0x000c); + break; + } + return 0; +} + +static int ac97_hifi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + int reg; + + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x0001, 0x0001); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return snd_soc_component_write(component, reg, runtime->rate); +} + +static int ac97_aux_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x0001, 0x0001); + snd_soc_component_update_bits(component, AC97_PCI_SID, 0x8000, 0x8000); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + + return snd_soc_component_write(component, AC97_PCM_SURR_DAC_RATE, runtime->rate); +} + +#define WM9713_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops wm9713_dai_ops_hifi = { + .prepare = ac97_hifi_prepare, + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, +}; + +static const struct snd_soc_dai_ops wm9713_dai_ops_aux = { + .prepare = ac97_aux_prepare, + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, +}; + +static const struct snd_soc_dai_ops wm9713_dai_ops_voice = { + .hw_params = wm9713_pcm_hw_params, + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, + .set_fmt = wm9713_set_dai_fmt, + .set_tristate = wm9713_set_dai_tristate, +}; + +static struct snd_soc_dai_driver wm9713_dai[] = { +{ + .name = "wm9713-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SND_SOC_STD_AC97_FMTS,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SND_SOC_STD_AC97_FMTS,}, + .ops = &wm9713_dai_ops_hifi, + }, + { + .name = "wm9713-aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_RATES, + .formats = SND_SOC_STD_AC97_FMTS,}, + .ops = &wm9713_dai_ops_aux, + }, + { + .name = "wm9713-voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_PCM_RATES, + .formats = WM9713_PCM_FORMATS,}, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_PCM_RATES, + .formats = WM9713_PCM_FORMATS,}, + .ops = &wm9713_dai_ops_voice, + .symmetric_rates = 1, + }, +}; + +static int wm9713_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + /* enable thermal shutdown */ + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0xe400, 0x0000); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* enable master bias and vmid */ + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0xc400, 0x0000); + snd_soc_component_write(component, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: + /* disable everything including AC link */ + snd_soc_component_write(component, AC97_EXTENDED_MID, 0xffff); + snd_soc_component_write(component, AC97_EXTENDED_MSTATUS, 0xffff); + snd_soc_component_write(component, AC97_POWERDOWN, 0xffff); + break; + } + return 0; +} + +static int wm9713_soc_suspend(struct snd_soc_component *component) +{ + /* Disable everything except touchpanel - that will be handled + * by the touch driver and left disabled if touch is not in + * use. */ + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0x7fff, + 0x7fff); + snd_soc_component_write(component, AC97_EXTENDED_MSTATUS, 0xffff); + snd_soc_component_write(component, AC97_POWERDOWN, 0x6f00); + snd_soc_component_write(component, AC97_POWERDOWN, 0xffff); + + return 0; +} + +static int wm9713_soc_resume(struct snd_soc_component *component) +{ + struct wm9713_priv *wm9713 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_ac97_reset(wm9713->ac97, true, WM9713_VENDOR_ID, + WM9713_VENDOR_ID_MASK); + if (ret < 0) + return ret; + + snd_soc_component_force_bias_level(component, SND_SOC_BIAS_STANDBY); + + /* do we need to re-start the PLL ? */ + if (wm9713->pll_in) + wm9713_set_pll(component, 0, wm9713->pll_in, 0); + + /* only synchronise the codec if warm reset failed */ + if (ret == 0) { + regcache_mark_dirty(component->regmap); + snd_soc_component_cache_sync(component); + } + + return ret; +} + +static int wm9713_soc_probe(struct snd_soc_component *component) +{ + struct wm9713_priv *wm9713 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = NULL; + + if (wm9713->mfd_pdata) { + wm9713->ac97 = wm9713->mfd_pdata->ac97; + regmap = wm9713->mfd_pdata->regmap; + } else if (IS_ENABLED(CONFIG_SND_SOC_AC97_BUS)) { + wm9713->ac97 = snd_soc_new_ac97_component(component, WM9713_VENDOR_ID, + WM9713_VENDOR_ID_MASK); + if (IS_ERR(wm9713->ac97)) + return PTR_ERR(wm9713->ac97); + regmap = regmap_init_ac97(wm9713->ac97, &wm9713_regmap_config); + if (IS_ERR(regmap)) { + snd_soc_free_ac97_component(wm9713->ac97); + return PTR_ERR(regmap); + } + } else { + return -ENXIO; + } + + snd_soc_component_init_regmap(component, regmap); + + /* unmute the adc - move to kcontrol */ + snd_soc_component_update_bits(component, AC97_CD, 0x7fff, 0x0000); + + return 0; +} + +static void wm9713_soc_remove(struct snd_soc_component *component) +{ + struct wm9713_priv *wm9713 = snd_soc_component_get_drvdata(component); + + if (IS_ENABLED(CONFIG_SND_SOC_AC97_BUS) && !wm9713->mfd_pdata) { + snd_soc_component_exit_regmap(component); + snd_soc_free_ac97_component(wm9713->ac97); + } +} + +static const struct snd_soc_component_driver soc_component_dev_wm9713 = { + .probe = wm9713_soc_probe, + .remove = wm9713_soc_remove, + .suspend = wm9713_soc_suspend, + .resume = wm9713_soc_resume, + .set_bias_level = wm9713_set_bias_level, + .controls = wm9713_snd_ac97_controls, + .num_controls = ARRAY_SIZE(wm9713_snd_ac97_controls), + .dapm_widgets = wm9713_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm9713_dapm_widgets), + .dapm_routes = wm9713_audio_map, + .num_dapm_routes = ARRAY_SIZE(wm9713_audio_map), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int wm9713_probe(struct platform_device *pdev) +{ + struct wm9713_priv *wm9713; + + wm9713 = devm_kzalloc(&pdev->dev, sizeof(*wm9713), GFP_KERNEL); + if (wm9713 == NULL) + return -ENOMEM; + + mutex_init(&wm9713->lock); + + wm9713->mfd_pdata = dev_get_platdata(&pdev->dev); + platform_set_drvdata(pdev, wm9713); + + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai)); +} + +static struct platform_driver wm9713_codec_driver = { + .driver = { + .name = "wm9713-codec", + }, + + .probe = wm9713_probe, +}; + +module_platform_driver(wm9713_codec_driver); + +MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h new file mode 100644 index 000000000..f0800dcca --- /dev/null +++ b/sound/soc/codecs/wm9713.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * wm9713.h -- WM9713 Soc Audio driver + */ + +#ifndef _WM9713_H +#define _WM9713_H + +/* clock inputs */ +#define WM9713_CLKA_PIN 0 +#define WM9713_CLKB_PIN 1 + +/* clock divider ID's */ +#define WM9713_PCMCLK_DIV 0 +#define WM9713_CLKA_MULT 1 +#define WM9713_CLKB_MULT 2 +#define WM9713_HIFI_DIV 3 +#define WM9713_PCMBCLK_DIV 4 +#define WM9713_PCMCLK_PLL_DIV 5 +#define WM9713_HIFI_PLL_DIV 6 + +/* Calculate the appropriate bit mask for the external PCM clock divider */ +#define WM9713_PCMDIV(x) ((x - 1) << 8) + +/* Calculate the appropriate bit mask for the external HiFi clock divider */ +#define WM9713_HIFIDIV(x) ((x - 1) << 12) + +/* MCLK clock mulitipliers */ +#define WM9713_CLKA_X1 (0 << 1) +#define WM9713_CLKA_X2 (1 << 1) +#define WM9713_CLKB_X1 (0 << 2) +#define WM9713_CLKB_X2 (1 << 2) + +/* MCLK clock MUX */ +#define WM9713_CLK_MUX_A (0 << 0) +#define WM9713_CLK_MUX_B (1 << 0) + +/* Voice DAI BCLK divider */ +#define WM9713_PCMBCLK_DIV_1 (0 << 9) +#define WM9713_PCMBCLK_DIV_2 (1 << 9) +#define WM9713_PCMBCLK_DIV_4 (2 << 9) +#define WM9713_PCMBCLK_DIV_8 (3 << 9) +#define WM9713_PCMBCLK_DIV_16 (4 << 9) + +#endif diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c new file mode 100644 index 000000000..c5b0b56d9 --- /dev/null +++ b/sound/soc/codecs/wm_adsp.c @@ -0,0 +1,3880 @@ +/* + * wm_adsp.c -- Wolfson ADSP support + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm_adsp.h" + +#define adsp_crit(_dsp, fmt, ...) \ + dev_crit(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#define adsp_err(_dsp, fmt, ...) \ + dev_err(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#define adsp_warn(_dsp, fmt, ...) \ + dev_warn(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#define adsp_info(_dsp, fmt, ...) \ + dev_info(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) +#define adsp_dbg(_dsp, fmt, ...) \ + dev_dbg(_dsp->dev, "%s: " fmt, _dsp->name, ##__VA_ARGS__) + +#define ADSP1_CONTROL_1 0x00 +#define ADSP1_CONTROL_2 0x02 +#define ADSP1_CONTROL_3 0x03 +#define ADSP1_CONTROL_4 0x04 +#define ADSP1_CONTROL_5 0x06 +#define ADSP1_CONTROL_6 0x07 +#define ADSP1_CONTROL_7 0x08 +#define ADSP1_CONTROL_8 0x09 +#define ADSP1_CONTROL_9 0x0A +#define ADSP1_CONTROL_10 0x0B +#define ADSP1_CONTROL_11 0x0C +#define ADSP1_CONTROL_12 0x0D +#define ADSP1_CONTROL_13 0x0F +#define ADSP1_CONTROL_14 0x10 +#define ADSP1_CONTROL_15 0x11 +#define ADSP1_CONTROL_16 0x12 +#define ADSP1_CONTROL_17 0x13 +#define ADSP1_CONTROL_18 0x14 +#define ADSP1_CONTROL_19 0x16 +#define ADSP1_CONTROL_20 0x17 +#define ADSP1_CONTROL_21 0x18 +#define ADSP1_CONTROL_22 0x1A +#define ADSP1_CONTROL_23 0x1B +#define ADSP1_CONTROL_24 0x1C +#define ADSP1_CONTROL_25 0x1E +#define ADSP1_CONTROL_26 0x20 +#define ADSP1_CONTROL_27 0x21 +#define ADSP1_CONTROL_28 0x22 +#define ADSP1_CONTROL_29 0x23 +#define ADSP1_CONTROL_30 0x24 +#define ADSP1_CONTROL_31 0x26 + +/* + * ADSP1 Control 19 + */ +#define ADSP1_WDMA_BUFFER_LENGTH_MASK 0x00FF /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define ADSP1_WDMA_BUFFER_LENGTH_SHIFT 0 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ +#define ADSP1_WDMA_BUFFER_LENGTH_WIDTH 8 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */ + + +/* + * ADSP1 Control 30 + */ +#define ADSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */ +#define ADSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ +#define ADSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ +#define ADSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ +#define ADSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ +#define ADSP1_START 0x0001 /* DSP1_START */ +#define ADSP1_START_MASK 0x0001 /* DSP1_START */ +#define ADSP1_START_SHIFT 0 /* DSP1_START */ +#define ADSP1_START_WIDTH 1 /* DSP1_START */ + +/* + * ADSP1 Control 31 + */ +#define ADSP1_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */ +#define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ +#define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +#define ADSP2_CONTROL 0x0 +#define ADSP2_CLOCKING 0x1 +#define ADSP2V2_CLOCKING 0x2 +#define ADSP2_STATUS1 0x4 +#define ADSP2_WDMA_CONFIG_1 0x30 +#define ADSP2_WDMA_CONFIG_2 0x31 +#define ADSP2V2_WDMA_CONFIG_2 0x32 +#define ADSP2_RDMA_CONFIG_1 0x34 + +#define ADSP2_SCRATCH0 0x40 +#define ADSP2_SCRATCH1 0x41 +#define ADSP2_SCRATCH2 0x42 +#define ADSP2_SCRATCH3 0x43 + +#define ADSP2V2_SCRATCH0_1 0x40 +#define ADSP2V2_SCRATCH2_3 0x42 + +/* + * ADSP2 Control + */ + +#define ADSP2_MEM_ENA 0x0010 /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_MASK 0x0010 /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_SHIFT 4 /* DSP1_MEM_ENA */ +#define ADSP2_MEM_ENA_WIDTH 1 /* DSP1_MEM_ENA */ +#define ADSP2_SYS_ENA 0x0004 /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */ +#define ADSP2_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */ +#define ADSP2_CORE_ENA 0x0002 /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */ +#define ADSP2_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */ +#define ADSP2_START 0x0001 /* DSP1_START */ +#define ADSP2_START_MASK 0x0001 /* DSP1_START */ +#define ADSP2_START_SHIFT 0 /* DSP1_START */ +#define ADSP2_START_WIDTH 1 /* DSP1_START */ + +/* + * ADSP2 clocking + */ +#define ADSP2_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */ +#define ADSP2_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */ +#define ADSP2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +/* + * ADSP2V2 clocking + */ +#define ADSP2V2_CLK_SEL_MASK 0x70000 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_SHIFT 16 /* CLK_SEL_ENA */ +#define ADSP2V2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */ + +#define ADSP2V2_RATE_MASK 0x7800 /* DSP_RATE */ +#define ADSP2V2_RATE_SHIFT 11 /* DSP_RATE */ +#define ADSP2V2_RATE_WIDTH 4 /* DSP_RATE */ + +/* + * ADSP2 Status 1 + */ +#define ADSP2_RAM_RDY 0x0001 +#define ADSP2_RAM_RDY_MASK 0x0001 +#define ADSP2_RAM_RDY_SHIFT 0 +#define ADSP2_RAM_RDY_WIDTH 1 + +/* + * ADSP2 Lock support + */ +#define ADSP2_LOCK_CODE_0 0x5555 +#define ADSP2_LOCK_CODE_1 0xAAAA + +#define ADSP2_WATCHDOG 0x0A +#define ADSP2_BUS_ERR_ADDR 0x52 +#define ADSP2_REGION_LOCK_STATUS 0x64 +#define ADSP2_LOCK_REGION_1_LOCK_REGION_0 0x66 +#define ADSP2_LOCK_REGION_3_LOCK_REGION_2 0x68 +#define ADSP2_LOCK_REGION_5_LOCK_REGION_4 0x6A +#define ADSP2_LOCK_REGION_7_LOCK_REGION_6 0x6C +#define ADSP2_LOCK_REGION_9_LOCK_REGION_8 0x6E +#define ADSP2_LOCK_REGION_CTRL 0x7A +#define ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR 0x7C + +#define ADSP2_REGION_LOCK_ERR_MASK 0x8000 +#define ADSP2_SLAVE_ERR_MASK 0x4000 +#define ADSP2_WDT_TIMEOUT_STS_MASK 0x2000 +#define ADSP2_CTRL_ERR_PAUSE_ENA 0x0002 +#define ADSP2_CTRL_ERR_EINT 0x0001 + +#define ADSP2_BUS_ERR_ADDR_MASK 0x00FFFFFF +#define ADSP2_XMEM_ERR_ADDR_MASK 0x0000FFFF +#define ADSP2_PMEM_ERR_ADDR_MASK 0x7FFF0000 +#define ADSP2_PMEM_ERR_ADDR_SHIFT 16 +#define ADSP2_WDT_ENA_MASK 0xFFFFFFFD + +#define ADSP2_LOCK_REGION_SHIFT 16 + +#define ADSP_MAX_STD_CTRL_SIZE 512 + +#define WM_ADSP_ACKED_CTL_TIMEOUT_MS 100 +#define WM_ADSP_ACKED_CTL_N_QUICKPOLLS 10 +#define WM_ADSP_ACKED_CTL_MIN_VALUE 0 +#define WM_ADSP_ACKED_CTL_MAX_VALUE 0xFFFFFF + +/* + * Event control messages + */ +#define WM_ADSP_FW_EVENT_SHUTDOWN 0x000001 + +struct wm_adsp_buf { + struct list_head list; + void *buf; +}; + +static struct wm_adsp_buf *wm_adsp_buf_alloc(const void *src, size_t len, + struct list_head *list) +{ + struct wm_adsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL); + + if (buf == NULL) + return NULL; + + buf->buf = vmalloc(len); + if (!buf->buf) { + kfree(buf); + return NULL; + } + memcpy(buf->buf, src, len); + + if (list) + list_add_tail(&buf->list, list); + + return buf; +} + +static void wm_adsp_buf_free(struct list_head *list) +{ + while (!list_empty(list)) { + struct wm_adsp_buf *buf = list_first_entry(list, + struct wm_adsp_buf, + list); + list_del(&buf->list); + vfree(buf->buf); + kfree(buf); + } +} + +#define WM_ADSP_FW_MBC_VSS 0 +#define WM_ADSP_FW_HIFI 1 +#define WM_ADSP_FW_TX 2 +#define WM_ADSP_FW_TX_SPK 3 +#define WM_ADSP_FW_RX 4 +#define WM_ADSP_FW_RX_ANC 5 +#define WM_ADSP_FW_CTRL 6 +#define WM_ADSP_FW_ASR 7 +#define WM_ADSP_FW_TRACE 8 +#define WM_ADSP_FW_SPK_PROT 9 +#define WM_ADSP_FW_MISC 10 + +#define WM_ADSP_NUM_FW 11 + +static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = { + [WM_ADSP_FW_MBC_VSS] = "MBC/VSS", + [WM_ADSP_FW_HIFI] = "MasterHiFi", + [WM_ADSP_FW_TX] = "Tx", + [WM_ADSP_FW_TX_SPK] = "Tx Speaker", + [WM_ADSP_FW_RX] = "Rx", + [WM_ADSP_FW_RX_ANC] = "Rx ANC", + [WM_ADSP_FW_CTRL] = "Voice Ctrl", + [WM_ADSP_FW_ASR] = "ASR Assist", + [WM_ADSP_FW_TRACE] = "Dbg Trace", + [WM_ADSP_FW_SPK_PROT] = "Protection", + [WM_ADSP_FW_MISC] = "Misc", +}; + +struct wm_adsp_system_config_xm_hdr { + __be32 sys_enable; + __be32 fw_id; + __be32 fw_rev; + __be32 boot_status; + __be32 watchdog; + __be32 dma_buffer_size; + __be32 rdma[6]; + __be32 wdma[8]; + __be32 build_job_name[3]; + __be32 build_job_number; +}; + +struct wm_adsp_alg_xm_struct { + __be32 magic; + __be32 smoothing; + __be32 threshold; + __be32 host_buf_ptr; + __be32 start_seq; + __be32 high_water_mark; + __be32 low_water_mark; + __be64 smoothed_power; +}; + +struct wm_adsp_buffer { + __be32 X_buf_base; /* XM base addr of first X area */ + __be32 X_buf_size; /* Size of 1st X area in words */ + __be32 X_buf_base2; /* XM base addr of 2nd X area */ + __be32 X_buf_brk; /* Total X size in words */ + __be32 Y_buf_base; /* YM base addr of Y area */ + __be32 wrap; /* Total size X and Y in words */ + __be32 high_water_mark; /* Point at which IRQ is asserted */ + __be32 irq_count; /* bits 1-31 count IRQ assertions */ + __be32 irq_ack; /* acked IRQ count, bit 0 enables IRQ */ + __be32 next_write_index; /* word index of next write */ + __be32 next_read_index; /* word index of next read */ + __be32 error; /* error if any */ + __be32 oldest_block_index; /* word index of oldest surviving */ + __be32 requested_rewind; /* how many blocks rewind was done */ + __be32 reserved_space; /* internal */ + __be32 min_free; /* min free space since stream start */ + __be32 blocks_written[2]; /* total blocks written (64 bit) */ + __be32 words_written[2]; /* total words written (64 bit) */ +}; + +struct wm_adsp_compr; + +struct wm_adsp_compr_buf { + struct wm_adsp *dsp; + struct wm_adsp_compr *compr; + + struct wm_adsp_buffer_region *regions; + u32 host_buf_ptr; + + u32 error; + u32 irq_count; + int read_index; + int avail; +}; + +struct wm_adsp_compr { + struct wm_adsp *dsp; + struct wm_adsp_compr_buf *buf; + + struct snd_compr_stream *stream; + struct snd_compressed_buffer size; + + u32 *raw_buf; + unsigned int copied_total; + + unsigned int sample_rate; +}; + +#define WM_ADSP_DATA_WORD_SIZE 3 + +#define WM_ADSP_MIN_FRAGMENTS 1 +#define WM_ADSP_MAX_FRAGMENTS 256 +#define WM_ADSP_MIN_FRAGMENT_SIZE (64 * WM_ADSP_DATA_WORD_SIZE) +#define WM_ADSP_MAX_FRAGMENT_SIZE (4096 * WM_ADSP_DATA_WORD_SIZE) + +#define WM_ADSP_ALG_XM_STRUCT_MAGIC 0x49aec7 + +#define HOST_BUFFER_FIELD(field) \ + (offsetof(struct wm_adsp_buffer, field) / sizeof(__be32)) + +#define ALG_XM_FIELD(field) \ + (offsetof(struct wm_adsp_alg_xm_struct, field) / sizeof(__be32)) + +static int wm_adsp_buffer_init(struct wm_adsp *dsp); +static int wm_adsp_buffer_free(struct wm_adsp *dsp); + +struct wm_adsp_buffer_region { + unsigned int offset; + unsigned int cumulative_size; + unsigned int mem_type; + unsigned int base_addr; +}; + +struct wm_adsp_buffer_region_def { + unsigned int mem_type; + unsigned int base_offset; + unsigned int size_offset; +}; + +static const struct wm_adsp_buffer_region_def default_regions[] = { + { + .mem_type = WMFW_ADSP2_XM, + .base_offset = HOST_BUFFER_FIELD(X_buf_base), + .size_offset = HOST_BUFFER_FIELD(X_buf_size), + }, + { + .mem_type = WMFW_ADSP2_XM, + .base_offset = HOST_BUFFER_FIELD(X_buf_base2), + .size_offset = HOST_BUFFER_FIELD(X_buf_brk), + }, + { + .mem_type = WMFW_ADSP2_YM, + .base_offset = HOST_BUFFER_FIELD(Y_buf_base), + .size_offset = HOST_BUFFER_FIELD(wrap), + }, +}; + +struct wm_adsp_fw_caps { + u32 id; + struct snd_codec_desc desc; + int num_regions; + const struct wm_adsp_buffer_region_def *region_defs; +}; + +static const struct wm_adsp_fw_caps ctrl_caps[] = { + { + .id = SND_AUDIOCODEC_BESPOKE, + .desc = { + .max_ch = 8, + .sample_rates = { 16000 }, + .num_sample_rates = 1, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .num_regions = ARRAY_SIZE(default_regions), + .region_defs = default_regions, + }, +}; + +static const struct wm_adsp_fw_caps trace_caps[] = { + { + .id = SND_AUDIOCODEC_BESPOKE, + .desc = { + .max_ch = 8, + .sample_rates = { + 4000, 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 64000, 88200, + 96000, 176400, 192000 + }, + .num_sample_rates = 15, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .num_regions = ARRAY_SIZE(default_regions), + .region_defs = default_regions, + }, +}; + +static const struct { + const char *file; + int compr_direction; + int num_caps; + const struct wm_adsp_fw_caps *caps; + bool voice_trigger; +} wm_adsp_fw[WM_ADSP_NUM_FW] = { + [WM_ADSP_FW_MBC_VSS] = { .file = "mbc-vss" }, + [WM_ADSP_FW_HIFI] = { .file = "hifi" }, + [WM_ADSP_FW_TX] = { .file = "tx" }, + [WM_ADSP_FW_TX_SPK] = { .file = "tx-spk" }, + [WM_ADSP_FW_RX] = { .file = "rx" }, + [WM_ADSP_FW_RX_ANC] = { .file = "rx-anc" }, + [WM_ADSP_FW_CTRL] = { + .file = "ctrl", + .compr_direction = SND_COMPRESS_CAPTURE, + .num_caps = ARRAY_SIZE(ctrl_caps), + .caps = ctrl_caps, + .voice_trigger = true, + }, + [WM_ADSP_FW_ASR] = { .file = "asr" }, + [WM_ADSP_FW_TRACE] = { + .file = "trace", + .compr_direction = SND_COMPRESS_CAPTURE, + .num_caps = ARRAY_SIZE(trace_caps), + .caps = trace_caps, + }, + [WM_ADSP_FW_SPK_PROT] = { .file = "spk-prot" }, + [WM_ADSP_FW_MISC] = { .file = "misc" }, +}; + +struct wm_coeff_ctl_ops { + int (*xget)(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + int (*xput)(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +}; + +struct wm_coeff_ctl { + const char *name; + const char *fw_name; + struct wm_adsp_alg_region alg_region; + struct wm_coeff_ctl_ops ops; + struct wm_adsp *dsp; + unsigned int enabled:1; + struct list_head list; + void *cache; + unsigned int offset; + size_t len; + unsigned int set:1; + struct soc_bytes_ext bytes_ext; + unsigned int flags; + unsigned int type; +}; + +static const char *wm_adsp_mem_region_name(unsigned int type) +{ + switch (type) { + case WMFW_ADSP1_PM: + return "PM"; + case WMFW_ADSP1_DM: + return "DM"; + case WMFW_ADSP2_XM: + return "XM"; + case WMFW_ADSP2_YM: + return "YM"; + case WMFW_ADSP1_ZM: + return "ZM"; + default: + return NULL; + } +} + +#ifdef CONFIG_DEBUG_FS +static void wm_adsp_debugfs_save_wmfwname(struct wm_adsp *dsp, const char *s) +{ + char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); + + kfree(dsp->wmfw_file_name); + dsp->wmfw_file_name = tmp; +} + +static void wm_adsp_debugfs_save_binname(struct wm_adsp *dsp, const char *s) +{ + char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); + + kfree(dsp->bin_file_name); + dsp->bin_file_name = tmp; +} + +static void wm_adsp_debugfs_clear(struct wm_adsp *dsp) +{ + kfree(dsp->wmfw_file_name); + kfree(dsp->bin_file_name); + dsp->wmfw_file_name = NULL; + dsp->bin_file_name = NULL; +} + +static ssize_t wm_adsp_debugfs_wmfw_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wm_adsp *dsp = file->private_data; + ssize_t ret; + + mutex_lock(&dsp->pwr_lock); + + if (!dsp->wmfw_file_name || !dsp->booted) + ret = 0; + else + ret = simple_read_from_buffer(user_buf, count, ppos, + dsp->wmfw_file_name, + strlen(dsp->wmfw_file_name)); + + mutex_unlock(&dsp->pwr_lock); + return ret; +} + +static ssize_t wm_adsp_debugfs_bin_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wm_adsp *dsp = file->private_data; + ssize_t ret; + + mutex_lock(&dsp->pwr_lock); + + if (!dsp->bin_file_name || !dsp->booted) + ret = 0; + else + ret = simple_read_from_buffer(user_buf, count, ppos, + dsp->bin_file_name, + strlen(dsp->bin_file_name)); + + mutex_unlock(&dsp->pwr_lock); + return ret; +} + +static const struct { + const char *name; + const struct file_operations fops; +} wm_adsp_debugfs_fops[] = { + { + .name = "wmfw_file_name", + .fops = { + .open = simple_open, + .read = wm_adsp_debugfs_wmfw_read, + }, + }, + { + .name = "bin_file_name", + .fops = { + .open = simple_open, + .read = wm_adsp_debugfs_bin_read, + }, + }, +}; + +static void wm_adsp2_init_debugfs(struct wm_adsp *dsp, + struct snd_soc_component *component) +{ + struct dentry *root = NULL; + int i; + + if (!component->debugfs_root) { + adsp_err(dsp, "No codec debugfs root\n"); + goto err; + } + + root = debugfs_create_dir(dsp->name, component->debugfs_root); + + if (!root) + goto err; + + if (!debugfs_create_bool("booted", 0444, root, &dsp->booted)) + goto err; + + if (!debugfs_create_bool("running", 0444, root, &dsp->running)) + goto err; + + if (!debugfs_create_x32("fw_id", 0444, root, &dsp->fw_id)) + goto err; + + if (!debugfs_create_x32("fw_version", 0444, root, &dsp->fw_id_version)) + goto err; + + for (i = 0; i < ARRAY_SIZE(wm_adsp_debugfs_fops); ++i) { + if (!debugfs_create_file(wm_adsp_debugfs_fops[i].name, + 0444, root, dsp, + &wm_adsp_debugfs_fops[i].fops)) + goto err; + } + + dsp->debugfs_root = root; + return; + +err: + debugfs_remove_recursive(root); + adsp_err(dsp, "Failed to create debugfs\n"); +} + +static void wm_adsp2_cleanup_debugfs(struct wm_adsp *dsp) +{ + wm_adsp_debugfs_clear(dsp); + debugfs_remove_recursive(dsp->debugfs_root); +} +#else +static inline void wm_adsp2_init_debugfs(struct wm_adsp *dsp, + struct snd_soc_component *component) +{ +} + +static inline void wm_adsp2_cleanup_debugfs(struct wm_adsp *dsp) +{ +} + +static inline void wm_adsp_debugfs_save_wmfwname(struct wm_adsp *dsp, + const char *s) +{ +} + +static inline void wm_adsp_debugfs_save_binname(struct wm_adsp *dsp, + const char *s) +{ +} + +static inline void wm_adsp_debugfs_clear(struct wm_adsp *dsp) +{ +} +#endif + +int wm_adsp_fw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dsp[e->shift_l].fw; + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp_fw_get); + +int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); + int ret = 1; + + if (ucontrol->value.enumerated.item[0] == dsp[e->shift_l].fw) + return 0; + + if (ucontrol->value.enumerated.item[0] >= WM_ADSP_NUM_FW) + return -EINVAL; + + mutex_lock(&dsp[e->shift_l].pwr_lock); + + if (dsp[e->shift_l].booted || dsp[e->shift_l].compr) + ret = -EBUSY; + else + dsp[e->shift_l].fw = ucontrol->value.enumerated.item[0]; + + mutex_unlock(&dsp[e->shift_l].pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp_fw_put); + +const struct soc_enum wm_adsp_fw_enum[] = { + SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 1, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 2, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 3, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 4, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 5, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), + SOC_ENUM_SINGLE(0, 6, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text), +}; +EXPORT_SYMBOL_GPL(wm_adsp_fw_enum); + +static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp, + int type) +{ + int i; + + for (i = 0; i < dsp->num_mems; i++) + if (dsp->mem[i].type == type) + return &dsp->mem[i]; + + return NULL; +} + +static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *mem, + unsigned int offset) +{ + if (WARN_ON(!mem)) + return offset; + switch (mem->type) { + case WMFW_ADSP1_PM: + return mem->base + (offset * 3); + case WMFW_ADSP1_DM: + return mem->base + (offset * 2); + case WMFW_ADSP2_XM: + return mem->base + (offset * 2); + case WMFW_ADSP2_YM: + return mem->base + (offset * 2); + case WMFW_ADSP1_ZM: + return mem->base + (offset * 2); + default: + WARN(1, "Unknown memory region type"); + return offset; + } +} + +static void wm_adsp2_show_fw_status(struct wm_adsp *dsp) +{ + unsigned int scratch[4]; + unsigned int addr = dsp->base + ADSP2_SCRATCH0; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(scratch); ++i) { + ret = regmap_read(dsp->regmap, addr + i, &scratch[i]); + if (ret) { + adsp_err(dsp, "Failed to read SCRATCH%u: %d\n", i, ret); + return; + } + } + + adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + scratch[0], scratch[1], scratch[2], scratch[3]); +} + +static void wm_adsp2v2_show_fw_status(struct wm_adsp *dsp) +{ + unsigned int scratch[2]; + int ret; + + ret = regmap_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH0_1, + &scratch[0]); + if (ret) { + adsp_err(dsp, "Failed to read SCRATCH0_1: %d\n", ret); + return; + } + + ret = regmap_read(dsp->regmap, dsp->base + ADSP2V2_SCRATCH2_3, + &scratch[1]); + if (ret) { + adsp_err(dsp, "Failed to read SCRATCH2_3: %d\n", ret); + return; + } + + adsp_dbg(dsp, "FW SCRATCH 0:0x%x 1:0x%x 2:0x%x 3:0x%x\n", + scratch[0] & 0xFFFF, + scratch[0] >> 16, + scratch[1] & 0xFFFF, + scratch[1] >> 16); +} + +static inline struct wm_coeff_ctl *bytes_ext_to_ctl(struct soc_bytes_ext *ext) +{ + return container_of(ext, struct wm_coeff_ctl, bytes_ext); +} + +static int wm_coeff_base_reg(struct wm_coeff_ctl *ctl, unsigned int *reg) +{ + const struct wm_adsp_alg_region *alg_region = &ctl->alg_region; + struct wm_adsp *dsp = ctl->dsp; + const struct wm_adsp_region *mem; + + mem = wm_adsp_find_region(dsp, alg_region->type); + if (!mem) { + adsp_err(dsp, "No base for region %x\n", + alg_region->type); + return -EINVAL; + } + + *reg = wm_adsp_region_to_reg(mem, ctl->alg_region.base + ctl->offset); + + return 0; +} + +static int wm_coeff_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + + switch (ctl->type) { + case WMFW_CTL_TYPE_ACKED: + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = WM_ADSP_ACKED_CTL_MIN_VALUE; + uinfo->value.integer.max = WM_ADSP_ACKED_CTL_MAX_VALUE; + uinfo->value.integer.step = 1; + uinfo->count = 1; + break; + default: + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = ctl->len; + break; + } + + return 0; +} + +static int wm_coeff_write_acked_control(struct wm_coeff_ctl *ctl, + unsigned int event_id) +{ + struct wm_adsp *dsp = ctl->dsp; + u32 val = cpu_to_be32(event_id); + unsigned int reg; + int i, ret; + + ret = wm_coeff_base_reg(ctl, ®); + if (ret) + return ret; + + adsp_dbg(dsp, "Sending 0x%x to acked control alg 0x%x %s:0x%x\n", + event_id, ctl->alg_region.alg, + wm_adsp_mem_region_name(ctl->alg_region.type), ctl->offset); + + ret = regmap_raw_write(dsp->regmap, reg, &val, sizeof(val)); + if (ret) { + adsp_err(dsp, "Failed to write %x: %d\n", reg, ret); + return ret; + } + + /* + * Poll for ack, we initially poll at ~1ms intervals for firmwares + * that respond quickly, then go to ~10ms polls. A firmware is unlikely + * to ack instantly so we do the first 1ms delay before reading the + * control to avoid a pointless bus transaction + */ + for (i = 0; i < WM_ADSP_ACKED_CTL_TIMEOUT_MS;) { + switch (i) { + case 0 ... WM_ADSP_ACKED_CTL_N_QUICKPOLLS - 1: + usleep_range(1000, 2000); + i++; + break; + default: + usleep_range(10000, 20000); + i += 10; + break; + } + + ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); + if (ret) { + adsp_err(dsp, "Failed to read %x: %d\n", reg, ret); + return ret; + } + + if (val == 0) { + adsp_dbg(dsp, "Acked control ACKED at poll %u\n", i); + return 0; + } + } + + adsp_warn(dsp, "Acked control @0x%x alg:0x%x %s:0x%x timed out\n", + reg, ctl->alg_region.alg, + wm_adsp_mem_region_name(ctl->alg_region.type), + ctl->offset); + + return -ETIMEDOUT; +} + +static int wm_coeff_write_control(struct wm_coeff_ctl *ctl, + const void *buf, size_t len) +{ + struct wm_adsp *dsp = ctl->dsp; + void *scratch; + int ret; + unsigned int reg; + + ret = wm_coeff_base_reg(ctl, ®); + if (ret) + return ret; + + scratch = kmemdup(buf, len, GFP_KERNEL | GFP_DMA); + if (!scratch) + return -ENOMEM; + + ret = regmap_raw_write(dsp->regmap, reg, scratch, + len); + if (ret) { + adsp_err(dsp, "Failed to write %zu bytes to %x: %d\n", + len, reg, ret); + kfree(scratch); + return ret; + } + adsp_dbg(dsp, "Wrote %zu bytes to %x\n", len, reg); + + kfree(scratch); + + return 0; +} + +static int wm_coeff_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + char *p = ucontrol->value.bytes.data; + int ret = 0; + + mutex_lock(&ctl->dsp->pwr_lock); + + if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) + ret = -EPERM; + else + memcpy(ctl->cache, p, ctl->len); + + ctl->set = 1; + if (ctl->enabled && ctl->dsp->running) + ret = wm_coeff_write_control(ctl, p, ctl->len); + + mutex_unlock(&ctl->dsp->pwr_lock); + + return ret; +} + +static int wm_coeff_tlv_put(struct snd_kcontrol *kctl, + const unsigned int __user *bytes, unsigned int size) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + int ret = 0; + + mutex_lock(&ctl->dsp->pwr_lock); + + if (copy_from_user(ctl->cache, bytes, size)) { + ret = -EFAULT; + } else { + ctl->set = 1; + if (ctl->enabled && ctl->dsp->running) + ret = wm_coeff_write_control(ctl, ctl->cache, size); + else if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) + ret = -EPERM; + } + + mutex_unlock(&ctl->dsp->pwr_lock); + + return ret; +} + +static int wm_coeff_put_acked(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + unsigned int val = ucontrol->value.integer.value[0]; + int ret; + + if (val == 0) + return 0; /* 0 means no event */ + + mutex_lock(&ctl->dsp->pwr_lock); + + if (ctl->enabled && ctl->dsp->running) + ret = wm_coeff_write_acked_control(ctl, val); + else + ret = -EPERM; + + mutex_unlock(&ctl->dsp->pwr_lock); + + return ret; +} + +static int wm_coeff_read_control(struct wm_coeff_ctl *ctl, + void *buf, size_t len) +{ + struct wm_adsp *dsp = ctl->dsp; + void *scratch; + int ret; + unsigned int reg; + + ret = wm_coeff_base_reg(ctl, ®); + if (ret) + return ret; + + scratch = kmalloc(len, GFP_KERNEL | GFP_DMA); + if (!scratch) + return -ENOMEM; + + ret = regmap_raw_read(dsp->regmap, reg, scratch, len); + if (ret) { + adsp_err(dsp, "Failed to read %zu bytes from %x: %d\n", + len, reg, ret); + kfree(scratch); + return ret; + } + adsp_dbg(dsp, "Read %zu bytes from %x\n", len, reg); + + memcpy(buf, scratch, len); + kfree(scratch); + + return 0; +} + +static int wm_coeff_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + char *p = ucontrol->value.bytes.data; + int ret = 0; + + mutex_lock(&ctl->dsp->pwr_lock); + + if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) { + if (ctl->enabled && ctl->dsp->running) + ret = wm_coeff_read_control(ctl, p, ctl->len); + else + ret = -EPERM; + } else { + if (!ctl->flags && ctl->enabled && ctl->dsp->running) + ret = wm_coeff_read_control(ctl, ctl->cache, ctl->len); + + memcpy(p, ctl->cache, ctl->len); + } + + mutex_unlock(&ctl->dsp->pwr_lock); + + return ret; +} + +static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, + unsigned int __user *bytes, unsigned int size) +{ + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *)kctl->private_value; + struct wm_coeff_ctl *ctl = bytes_ext_to_ctl(bytes_ext); + int ret = 0; + + mutex_lock(&ctl->dsp->pwr_lock); + + if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) { + if (ctl->enabled && ctl->dsp->running) + ret = wm_coeff_read_control(ctl, ctl->cache, size); + else + ret = -EPERM; + } else { + if (!ctl->flags && ctl->enabled && ctl->dsp->running) + ret = wm_coeff_read_control(ctl, ctl->cache, size); + } + + if (!ret && copy_to_user(bytes, ctl->cache, size)) + ret = -EFAULT; + + mutex_unlock(&ctl->dsp->pwr_lock); + + return ret; +} + +static int wm_coeff_get_acked(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + /* + * Although it's not useful to read an acked control, we must satisfy + * user-side assumptions that all controls are readable and that a + * write of the same value should be filtered out (it's valid to send + * the same event number again to the firmware). We therefore return 0, + * meaning "no event" so valid event numbers will always be a change + */ + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +struct wmfw_ctl_work { + struct wm_adsp *dsp; + struct wm_coeff_ctl *ctl; + struct work_struct work; +}; + +static unsigned int wmfw_convert_flags(unsigned int in, unsigned int len) +{ + unsigned int out, rd, wr, vol; + + if (len > ADSP_MAX_STD_CTRL_SIZE) { + rd = SNDRV_CTL_ELEM_ACCESS_TLV_READ; + wr = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE; + vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + out = SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } else { + rd = SNDRV_CTL_ELEM_ACCESS_READ; + wr = SNDRV_CTL_ELEM_ACCESS_WRITE; + vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + out = 0; + } + + if (in) { + out |= rd; + if (in & WMFW_CTL_FLAG_WRITEABLE) + out |= wr; + if (in & WMFW_CTL_FLAG_VOLATILE) + out |= vol; + } else { + out |= rd | wr | vol; + } + + return out; +} + +static int wmfw_add_ctl(struct wm_adsp *dsp, struct wm_coeff_ctl *ctl) +{ + struct snd_kcontrol_new *kcontrol; + int ret; + + if (!ctl || !ctl->name) + return -EINVAL; + + kcontrol = kzalloc(sizeof(*kcontrol), GFP_KERNEL); + if (!kcontrol) + return -ENOMEM; + + kcontrol->name = ctl->name; + kcontrol->info = wm_coeff_info; + kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kcontrol->tlv.c = snd_soc_bytes_tlv_callback; + kcontrol->private_value = (unsigned long)&ctl->bytes_ext; + kcontrol->access = wmfw_convert_flags(ctl->flags, ctl->len); + + switch (ctl->type) { + case WMFW_CTL_TYPE_ACKED: + kcontrol->get = wm_coeff_get_acked; + kcontrol->put = wm_coeff_put_acked; + break; + default: + if (kcontrol->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + ctl->bytes_ext.max = ctl->len; + ctl->bytes_ext.get = wm_coeff_tlv_get; + ctl->bytes_ext.put = wm_coeff_tlv_put; + } else { + kcontrol->get = wm_coeff_get; + kcontrol->put = wm_coeff_put; + } + break; + } + + ret = snd_soc_add_component_controls(dsp->component, kcontrol, 1); + if (ret < 0) + goto err_kcontrol; + + kfree(kcontrol); + + return 0; + +err_kcontrol: + kfree(kcontrol); + return ret; +} + +static int wm_coeff_init_control_caches(struct wm_adsp *dsp) +{ + struct wm_coeff_ctl *ctl; + int ret; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (!ctl->enabled || ctl->set) + continue; + if (ctl->flags & WMFW_CTL_FLAG_VOLATILE) + continue; + + /* + * For readable controls populate the cache from the DSP memory. + * For non-readable controls the cache was zero-filled when + * created so we don't need to do anything. + */ + if (!ctl->flags || (ctl->flags & WMFW_CTL_FLAG_READABLE)) { + ret = wm_coeff_read_control(ctl, ctl->cache, ctl->len); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int wm_coeff_sync_controls(struct wm_adsp *dsp) +{ + struct wm_coeff_ctl *ctl; + int ret; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (!ctl->enabled) + continue; + if (ctl->set && !(ctl->flags & WMFW_CTL_FLAG_VOLATILE)) { + ret = wm_coeff_write_control(ctl, ctl->cache, ctl->len); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static void wm_adsp_signal_event_controls(struct wm_adsp *dsp, + unsigned int event) +{ + struct wm_coeff_ctl *ctl; + int ret; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (ctl->type != WMFW_CTL_TYPE_HOSTEVENT) + continue; + + if (!ctl->enabled) + continue; + + ret = wm_coeff_write_acked_control(ctl, event); + if (ret) + adsp_warn(dsp, + "Failed to send 0x%x event to alg 0x%x (%d)\n", + event, ctl->alg_region.alg, ret); + } +} + +static void wm_adsp_ctl_work(struct work_struct *work) +{ + struct wmfw_ctl_work *ctl_work = container_of(work, + struct wmfw_ctl_work, + work); + + wmfw_add_ctl(ctl_work->dsp, ctl_work->ctl); + kfree(ctl_work); +} + +static void wm_adsp_free_ctl_blk(struct wm_coeff_ctl *ctl) +{ + kfree(ctl->cache); + kfree(ctl->name); + kfree(ctl); +} + +static int wm_adsp_create_control(struct wm_adsp *dsp, + const struct wm_adsp_alg_region *alg_region, + unsigned int offset, unsigned int len, + const char *subname, unsigned int subname_len, + unsigned int flags, unsigned int type) +{ + struct wm_coeff_ctl *ctl; + struct wmfw_ctl_work *ctl_work; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const char *region_name; + int ret; + + region_name = wm_adsp_mem_region_name(alg_region->type); + if (!region_name) { + adsp_err(dsp, "Unknown region type: %d\n", alg_region->type); + return -EINVAL; + } + + switch (dsp->fw_ver) { + case 0: + case 1: + snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %x", + dsp->name, region_name, alg_region->alg); + break; + default: + ret = snprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "%s%c %.12s %x", dsp->name, *region_name, + wm_adsp_fw_text[dsp->fw], alg_region->alg); + + /* Truncate the subname from the start if it is too long */ + if (subname) { + int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; + int skip = 0; + + if (dsp->component->name_prefix) + avail -= strlen(dsp->component->name_prefix) + 1; + + if (subname_len > avail) + skip = subname_len - avail; + + snprintf(name + ret, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, " %.*s", + subname_len - skip, subname + skip); + } + break; + } + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (!strcmp(ctl->name, name)) { + if (!ctl->enabled) + ctl->enabled = 1; + return 0; + } + } + + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return -ENOMEM; + ctl->fw_name = wm_adsp_fw_text[dsp->fw]; + ctl->alg_region = *alg_region; + ctl->name = kmemdup(name, strlen(name) + 1, GFP_KERNEL); + if (!ctl->name) { + ret = -ENOMEM; + goto err_ctl; + } + ctl->enabled = 1; + ctl->set = 0; + ctl->ops.xget = wm_coeff_get; + ctl->ops.xput = wm_coeff_put; + ctl->dsp = dsp; + + ctl->flags = flags; + ctl->type = type; + ctl->offset = offset; + ctl->len = len; + ctl->cache = kzalloc(ctl->len, GFP_KERNEL); + if (!ctl->cache) { + ret = -ENOMEM; + goto err_ctl_name; + } + + list_add(&ctl->list, &dsp->ctl_list); + + if (flags & WMFW_CTL_FLAG_SYS) + return 0; + + ctl_work = kzalloc(sizeof(*ctl_work), GFP_KERNEL); + if (!ctl_work) { + ret = -ENOMEM; + goto err_list_del; + } + + ctl_work->dsp = dsp; + ctl_work->ctl = ctl; + INIT_WORK(&ctl_work->work, wm_adsp_ctl_work); + schedule_work(&ctl_work->work); + + return 0; + +err_list_del: + list_del(&ctl->list); + kfree(ctl->cache); +err_ctl_name: + kfree(ctl->name); +err_ctl: + kfree(ctl); + + return ret; +} + +struct wm_coeff_parsed_alg { + int id; + const u8 *name; + int name_len; + int ncoeff; +}; + +struct wm_coeff_parsed_coeff { + int offset; + int mem_type; + const u8 *name; + int name_len; + int ctl_type; + int flags; + int len; +}; + +static int wm_coeff_parse_string(int bytes, const u8 **pos, const u8 **str) +{ + int length; + + switch (bytes) { + case 1: + length = **pos; + break; + case 2: + length = le16_to_cpu(*((__le16 *)*pos)); + break; + default: + return 0; + } + + if (str) + *str = *pos + bytes; + + *pos += ((length + bytes) + 3) & ~0x03; + + return length; +} + +static int wm_coeff_parse_int(int bytes, const u8 **pos) +{ + int val = 0; + + switch (bytes) { + case 2: + val = le16_to_cpu(*((__le16 *)*pos)); + break; + case 4: + val = le32_to_cpu(*((__le32 *)*pos)); + break; + default: + break; + } + + *pos += bytes; + + return val; +} + +static inline void wm_coeff_parse_alg(struct wm_adsp *dsp, const u8 **data, + struct wm_coeff_parsed_alg *blk) +{ + const struct wmfw_adsp_alg_data *raw; + + switch (dsp->fw_ver) { + case 0: + case 1: + raw = (const struct wmfw_adsp_alg_data *)*data; + *data = raw->data; + + blk->id = le32_to_cpu(raw->id); + blk->name = raw->name; + blk->name_len = strlen(raw->name); + blk->ncoeff = le32_to_cpu(raw->ncoeff); + break; + default: + blk->id = wm_coeff_parse_int(sizeof(raw->id), data); + blk->name_len = wm_coeff_parse_string(sizeof(u8), data, + &blk->name); + wm_coeff_parse_string(sizeof(u16), data, NULL); + blk->ncoeff = wm_coeff_parse_int(sizeof(raw->ncoeff), data); + break; + } + + adsp_dbg(dsp, "Algorithm ID: %#x\n", blk->id); + adsp_dbg(dsp, "Algorithm name: %.*s\n", blk->name_len, blk->name); + adsp_dbg(dsp, "# of coefficient descriptors: %#x\n", blk->ncoeff); +} + +static inline void wm_coeff_parse_coeff(struct wm_adsp *dsp, const u8 **data, + struct wm_coeff_parsed_coeff *blk) +{ + const struct wmfw_adsp_coeff_data *raw; + const u8 *tmp; + int length; + + switch (dsp->fw_ver) { + case 0: + case 1: + raw = (const struct wmfw_adsp_coeff_data *)*data; + *data = *data + sizeof(raw->hdr) + le32_to_cpu(raw->hdr.size); + + blk->offset = le16_to_cpu(raw->hdr.offset); + blk->mem_type = le16_to_cpu(raw->hdr.type); + blk->name = raw->name; + blk->name_len = strlen(raw->name); + blk->ctl_type = le16_to_cpu(raw->ctl_type); + blk->flags = le16_to_cpu(raw->flags); + blk->len = le32_to_cpu(raw->len); + break; + default: + tmp = *data; + blk->offset = wm_coeff_parse_int(sizeof(raw->hdr.offset), &tmp); + blk->mem_type = wm_coeff_parse_int(sizeof(raw->hdr.type), &tmp); + length = wm_coeff_parse_int(sizeof(raw->hdr.size), &tmp); + blk->name_len = wm_coeff_parse_string(sizeof(u8), &tmp, + &blk->name); + wm_coeff_parse_string(sizeof(u8), &tmp, NULL); + wm_coeff_parse_string(sizeof(u16), &tmp, NULL); + blk->ctl_type = wm_coeff_parse_int(sizeof(raw->ctl_type), &tmp); + blk->flags = wm_coeff_parse_int(sizeof(raw->flags), &tmp); + blk->len = wm_coeff_parse_int(sizeof(raw->len), &tmp); + + *data = *data + sizeof(raw->hdr) + length; + break; + } + + adsp_dbg(dsp, "\tCoefficient type: %#x\n", blk->mem_type); + adsp_dbg(dsp, "\tCoefficient offset: %#x\n", blk->offset); + adsp_dbg(dsp, "\tCoefficient name: %.*s\n", blk->name_len, blk->name); + adsp_dbg(dsp, "\tCoefficient flags: %#x\n", blk->flags); + adsp_dbg(dsp, "\tALSA control type: %#x\n", blk->ctl_type); + adsp_dbg(dsp, "\tALSA control len: %#x\n", blk->len); +} + +static int wm_adsp_check_coeff_flags(struct wm_adsp *dsp, + const struct wm_coeff_parsed_coeff *coeff_blk, + unsigned int f_required, + unsigned int f_illegal) +{ + if ((coeff_blk->flags & f_illegal) || + ((coeff_blk->flags & f_required) != f_required)) { + adsp_err(dsp, "Illegal flags 0x%x for control type 0x%x\n", + coeff_blk->flags, coeff_blk->ctl_type); + return -EINVAL; + } + + return 0; +} + +static int wm_adsp_parse_coeff(struct wm_adsp *dsp, + const struct wmfw_region *region) +{ + struct wm_adsp_alg_region alg_region = {}; + struct wm_coeff_parsed_alg alg_blk; + struct wm_coeff_parsed_coeff coeff_blk; + const u8 *data = region->data; + int i, ret; + + wm_coeff_parse_alg(dsp, &data, &alg_blk); + for (i = 0; i < alg_blk.ncoeff; i++) { + wm_coeff_parse_coeff(dsp, &data, &coeff_blk); + + switch (coeff_blk.ctl_type) { + case SNDRV_CTL_ELEM_TYPE_BYTES: + break; + case WMFW_CTL_TYPE_ACKED: + if (coeff_blk.flags & WMFW_CTL_FLAG_SYS) + continue; /* ignore */ + + ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk, + WMFW_CTL_FLAG_VOLATILE | + WMFW_CTL_FLAG_WRITEABLE | + WMFW_CTL_FLAG_READABLE, + 0); + if (ret) + return -EINVAL; + break; + case WMFW_CTL_TYPE_HOSTEVENT: + ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk, + WMFW_CTL_FLAG_SYS | + WMFW_CTL_FLAG_VOLATILE | + WMFW_CTL_FLAG_WRITEABLE | + WMFW_CTL_FLAG_READABLE, + 0); + if (ret) + return -EINVAL; + break; + case WMFW_CTL_TYPE_HOST_BUFFER: + ret = wm_adsp_check_coeff_flags(dsp, &coeff_blk, + WMFW_CTL_FLAG_SYS | + WMFW_CTL_FLAG_VOLATILE | + WMFW_CTL_FLAG_READABLE, + 0); + if (ret) + return -EINVAL; + break; + default: + adsp_err(dsp, "Unknown control type: %d\n", + coeff_blk.ctl_type); + return -EINVAL; + } + + alg_region.type = coeff_blk.mem_type; + alg_region.alg = alg_blk.id; + + ret = wm_adsp_create_control(dsp, &alg_region, + coeff_blk.offset, + coeff_blk.len, + coeff_blk.name, + coeff_blk.name_len, + coeff_blk.flags, + coeff_blk.ctl_type); + if (ret < 0) + adsp_err(dsp, "Failed to create control: %.*s, %d\n", + coeff_blk.name_len, coeff_blk.name, ret); + } + + return 0; +} + +static int wm_adsp_load(struct wm_adsp *dsp) +{ + LIST_HEAD(buf_list); + const struct firmware *firmware; + struct regmap *regmap = dsp->regmap; + unsigned int pos = 0; + const struct wmfw_header *header; + const struct wmfw_adsp1_sizes *adsp1_sizes; + const struct wmfw_adsp2_sizes *adsp2_sizes; + const struct wmfw_footer *footer; + const struct wmfw_region *region; + const struct wm_adsp_region *mem; + const char *region_name; + char *file, *text = NULL; + struct wm_adsp_buf *buf; + unsigned int reg; + int regions = 0; + int ret, offset, type, sizes; + + file = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (file == NULL) + return -ENOMEM; + + snprintf(file, PAGE_SIZE, "%s-%s-%s.wmfw", dsp->part, dsp->fwf_name, + wm_adsp_fw[dsp->fw].file); + file[PAGE_SIZE - 1] = '\0'; + + ret = request_firmware(&firmware, file, dsp->dev); + if (ret != 0) { + adsp_err(dsp, "Failed to request '%s'\n", file); + goto out; + } + ret = -EINVAL; + + pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); + if (pos >= firmware->size) { + adsp_err(dsp, "%s: file too short, %zu bytes\n", + file, firmware->size); + goto out_fw; + } + + header = (void *)&firmware->data[0]; + + if (memcmp(&header->magic[0], "WMFW", 4) != 0) { + adsp_err(dsp, "%s: invalid magic\n", file); + goto out_fw; + } + + switch (header->ver) { + case 0: + adsp_warn(dsp, "%s: Depreciated file format %d\n", + file, header->ver); + break; + case 1: + case 2: + break; + default: + adsp_err(dsp, "%s: unknown file format %d\n", + file, header->ver); + goto out_fw; + } + + adsp_info(dsp, "Firmware version: %d\n", header->ver); + dsp->fw_ver = header->ver; + + if (header->core != dsp->type) { + adsp_err(dsp, "%s: invalid core %d != %d\n", + file, header->core, dsp->type); + goto out_fw; + } + + switch (dsp->type) { + case WMFW_ADSP1: + pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer); + adsp1_sizes = (void *)&(header[1]); + footer = (void *)&(adsp1_sizes[1]); + sizes = sizeof(*adsp1_sizes); + + adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n", + file, le32_to_cpu(adsp1_sizes->dm), + le32_to_cpu(adsp1_sizes->pm), + le32_to_cpu(adsp1_sizes->zm)); + break; + + case WMFW_ADSP2: + pos = sizeof(*header) + sizeof(*adsp2_sizes) + sizeof(*footer); + adsp2_sizes = (void *)&(header[1]); + footer = (void *)&(adsp2_sizes[1]); + sizes = sizeof(*adsp2_sizes); + + adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n", + file, le32_to_cpu(adsp2_sizes->xm), + le32_to_cpu(adsp2_sizes->ym), + le32_to_cpu(adsp2_sizes->pm), + le32_to_cpu(adsp2_sizes->zm)); + break; + + default: + WARN(1, "Unknown DSP type"); + goto out_fw; + } + + if (le32_to_cpu(header->len) != sizeof(*header) + + sizes + sizeof(*footer)) { + adsp_err(dsp, "%s: unexpected header length %d\n", + file, le32_to_cpu(header->len)); + goto out_fw; + } + + adsp_dbg(dsp, "%s: timestamp %llu\n", file, + le64_to_cpu(footer->timestamp)); + + while (pos < firmware->size && + sizeof(*region) < firmware->size - pos) { + region = (void *)&(firmware->data[pos]); + region_name = "Unknown"; + reg = 0; + text = NULL; + offset = le32_to_cpu(region->offset) & 0xffffff; + type = be32_to_cpu(region->type) & 0xff; + mem = wm_adsp_find_region(dsp, type); + + switch (type) { + case WMFW_NAME_TEXT: + region_name = "Firmware name"; + text = kzalloc(le32_to_cpu(region->len) + 1, + GFP_KERNEL); + break; + case WMFW_ALGORITHM_DATA: + region_name = "Algorithm"; + ret = wm_adsp_parse_coeff(dsp, region); + if (ret != 0) + goto out_fw; + break; + case WMFW_INFO_TEXT: + region_name = "Information"; + text = kzalloc(le32_to_cpu(region->len) + 1, + GFP_KERNEL); + break; + case WMFW_ABSOLUTE: + region_name = "Absolute"; + reg = offset; + break; + case WMFW_ADSP1_PM: + case WMFW_ADSP1_DM: + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + case WMFW_ADSP1_ZM: + region_name = wm_adsp_mem_region_name(type); + reg = wm_adsp_region_to_reg(mem, offset); + break; + default: + adsp_warn(dsp, + "%s.%d: Unknown region type %x at %d(%x)\n", + file, regions, type, pos, pos); + break; + } + + adsp_dbg(dsp, "%s.%d: %d bytes at %d in %s\n", file, + regions, le32_to_cpu(region->len), offset, + region_name); + + if (le32_to_cpu(region->len) > + firmware->size - pos - sizeof(*region)) { + adsp_err(dsp, + "%s.%d: %s region len %d bytes exceeds file length %zu\n", + file, regions, region_name, + le32_to_cpu(region->len), firmware->size); + ret = -EINVAL; + goto out_fw; + } + + if (text) { + memcpy(text, region->data, le32_to_cpu(region->len)); + adsp_info(dsp, "%s: %s\n", file, text); + kfree(text); + text = NULL; + } + + if (reg) { + buf = wm_adsp_buf_alloc(region->data, + le32_to_cpu(region->len), + &buf_list); + if (!buf) { + adsp_err(dsp, "Out of memory\n"); + ret = -ENOMEM; + goto out_fw; + } + + ret = regmap_raw_write_async(regmap, reg, buf->buf, + le32_to_cpu(region->len)); + if (ret != 0) { + adsp_err(dsp, + "%s.%d: Failed to write %d bytes at %d in %s: %d\n", + file, regions, + le32_to_cpu(region->len), offset, + region_name, ret); + goto out_fw; + } + } + + pos += le32_to_cpu(region->len) + sizeof(*region); + regions++; + } + + ret = regmap_async_complete(regmap); + if (ret != 0) { + adsp_err(dsp, "Failed to complete async write: %d\n", ret); + goto out_fw; + } + + if (pos > firmware->size) + adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", + file, regions, pos - firmware->size); + + wm_adsp_debugfs_save_wmfwname(dsp, file); + +out_fw: + regmap_async_complete(regmap); + wm_adsp_buf_free(&buf_list); + release_firmware(firmware); + kfree(text); +out: + kfree(file); + + return ret; +} + +static void wm_adsp_ctl_fixup_base(struct wm_adsp *dsp, + const struct wm_adsp_alg_region *alg_region) +{ + struct wm_coeff_ctl *ctl; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (ctl->fw_name == wm_adsp_fw_text[dsp->fw] && + alg_region->alg == ctl->alg_region.alg && + alg_region->type == ctl->alg_region.type) { + ctl->alg_region.base = alg_region->base; + } + } +} + +static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs, + const struct wm_adsp_region *mem, + unsigned int pos, unsigned int len) +{ + void *alg; + unsigned int reg; + int ret; + __be32 val; + + if (n_algs == 0) { + adsp_err(dsp, "No algorithms\n"); + return ERR_PTR(-EINVAL); + } + + if (n_algs > 1024) { + adsp_err(dsp, "Algorithm count %zx excessive\n", n_algs); + return ERR_PTR(-EINVAL); + } + + /* Read the terminator first to validate the length */ + reg = wm_adsp_region_to_reg(mem, pos + len); + + ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); + if (ret != 0) { + adsp_err(dsp, "Failed to read algorithm list end: %d\n", + ret); + return ERR_PTR(ret); + } + + if (be32_to_cpu(val) != 0xbedead) + adsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbedead\n", + reg, be32_to_cpu(val)); + + /* Convert length from DSP words to bytes */ + len *= sizeof(u32); + + alg = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!alg) + return ERR_PTR(-ENOMEM); + + reg = wm_adsp_region_to_reg(mem, pos); + + ret = regmap_raw_read(dsp->regmap, reg, alg, len); + if (ret != 0) { + adsp_err(dsp, "Failed to read algorithm list: %d\n", ret); + kfree(alg); + return ERR_PTR(ret); + } + + return alg; +} + +static struct wm_adsp_alg_region * + wm_adsp_find_alg_region(struct wm_adsp *dsp, int type, unsigned int id) +{ + struct wm_adsp_alg_region *alg_region; + + list_for_each_entry(alg_region, &dsp->alg_regions, list) { + if (id == alg_region->alg && type == alg_region->type) + return alg_region; + } + + return NULL; +} + +static struct wm_adsp_alg_region *wm_adsp_create_region(struct wm_adsp *dsp, + int type, __be32 id, + __be32 base) +{ + struct wm_adsp_alg_region *alg_region; + + alg_region = kzalloc(sizeof(*alg_region), GFP_KERNEL); + if (!alg_region) + return ERR_PTR(-ENOMEM); + + alg_region->type = type; + alg_region->alg = be32_to_cpu(id); + alg_region->base = be32_to_cpu(base); + + list_add_tail(&alg_region->list, &dsp->alg_regions); + + if (dsp->fw_ver > 0) + wm_adsp_ctl_fixup_base(dsp, alg_region); + + return alg_region; +} + +static void wm_adsp_free_alg_regions(struct wm_adsp *dsp) +{ + struct wm_adsp_alg_region *alg_region; + + while (!list_empty(&dsp->alg_regions)) { + alg_region = list_first_entry(&dsp->alg_regions, + struct wm_adsp_alg_region, + list); + list_del(&alg_region->list); + kfree(alg_region); + } +} + +static int wm_adsp1_setup_algs(struct wm_adsp *dsp) +{ + struct wmfw_adsp1_id_hdr adsp1_id; + struct wmfw_adsp1_alg_hdr *adsp1_alg; + struct wm_adsp_alg_region *alg_region; + const struct wm_adsp_region *mem; + unsigned int pos, len; + size_t n_algs; + int i, ret; + + mem = wm_adsp_find_region(dsp, WMFW_ADSP1_DM); + if (WARN_ON(!mem)) + return -EINVAL; + + ret = regmap_raw_read(dsp->regmap, mem->base, &adsp1_id, + sizeof(adsp1_id)); + if (ret != 0) { + adsp_err(dsp, "Failed to read algorithm info: %d\n", + ret); + return ret; + } + + n_algs = be32_to_cpu(adsp1_id.n_algs); + dsp->fw_id = be32_to_cpu(adsp1_id.fw.id); + adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n", + dsp->fw_id, + (be32_to_cpu(adsp1_id.fw.ver) & 0xff0000) >> 16, + (be32_to_cpu(adsp1_id.fw.ver) & 0xff00) >> 8, + be32_to_cpu(adsp1_id.fw.ver) & 0xff, + n_algs); + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_ZM, + adsp1_id.fw.id, adsp1_id.zm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_DM, + adsp1_id.fw.id, adsp1_id.dm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + /* Calculate offset and length in DSP words */ + pos = sizeof(adsp1_id) / sizeof(u32); + len = (sizeof(*adsp1_alg) * n_algs) / sizeof(u32); + + adsp1_alg = wm_adsp_read_algs(dsp, n_algs, mem, pos, len); + if (IS_ERR(adsp1_alg)) + return PTR_ERR(adsp1_alg); + + for (i = 0; i < n_algs; i++) { + adsp_info(dsp, "%d: ID %x v%d.%d.%d DM@%x ZM@%x\n", + i, be32_to_cpu(adsp1_alg[i].alg.id), + (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff0000) >> 16, + (be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff00) >> 8, + be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff, + be32_to_cpu(adsp1_alg[i].dm), + be32_to_cpu(adsp1_alg[i].zm)); + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_DM, + adsp1_alg[i].alg.id, + adsp1_alg[i].dm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp1_alg[i + 1].dm); + len -= be32_to_cpu(adsp1_alg[i].dm); + len *= 4; + wm_adsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + SNDRV_CTL_ELEM_TYPE_BYTES); + } else { + adsp_warn(dsp, "Missing length info for region DM with ID %x\n", + be32_to_cpu(adsp1_alg[i].alg.id)); + } + } + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP1_ZM, + adsp1_alg[i].alg.id, + adsp1_alg[i].zm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp1_alg[i + 1].zm); + len -= be32_to_cpu(adsp1_alg[i].zm); + len *= 4; + wm_adsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + SNDRV_CTL_ELEM_TYPE_BYTES); + } else { + adsp_warn(dsp, "Missing length info for region ZM with ID %x\n", + be32_to_cpu(adsp1_alg[i].alg.id)); + } + } + } + +out: + kfree(adsp1_alg); + return ret; +} + +static int wm_adsp2_setup_algs(struct wm_adsp *dsp) +{ + struct wmfw_adsp2_id_hdr adsp2_id; + struct wmfw_adsp2_alg_hdr *adsp2_alg; + struct wm_adsp_alg_region *alg_region; + const struct wm_adsp_region *mem; + unsigned int pos, len; + size_t n_algs; + int i, ret; + + mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM); + if (WARN_ON(!mem)) + return -EINVAL; + + ret = regmap_raw_read(dsp->regmap, mem->base, &adsp2_id, + sizeof(adsp2_id)); + if (ret != 0) { + adsp_err(dsp, "Failed to read algorithm info: %d\n", + ret); + return ret; + } + + n_algs = be32_to_cpu(adsp2_id.n_algs); + dsp->fw_id = be32_to_cpu(adsp2_id.fw.id); + dsp->fw_id_version = be32_to_cpu(adsp2_id.fw.ver); + adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n", + dsp->fw_id, + (dsp->fw_id_version & 0xff0000) >> 16, + (dsp->fw_id_version & 0xff00) >> 8, + dsp->fw_id_version & 0xff, + n_algs); + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_XM, + adsp2_id.fw.id, adsp2_id.xm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_YM, + adsp2_id.fw.id, adsp2_id.ym); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_ZM, + adsp2_id.fw.id, adsp2_id.zm); + if (IS_ERR(alg_region)) + return PTR_ERR(alg_region); + + /* Calculate offset and length in DSP words */ + pos = sizeof(adsp2_id) / sizeof(u32); + len = (sizeof(*adsp2_alg) * n_algs) / sizeof(u32); + + adsp2_alg = wm_adsp_read_algs(dsp, n_algs, mem, pos, len); + if (IS_ERR(adsp2_alg)) + return PTR_ERR(adsp2_alg); + + for (i = 0; i < n_algs; i++) { + adsp_info(dsp, + "%d: ID %x v%d.%d.%d XM@%x YM@%x ZM@%x\n", + i, be32_to_cpu(adsp2_alg[i].alg.id), + (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff0000) >> 16, + (be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff00) >> 8, + be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff, + be32_to_cpu(adsp2_alg[i].xm), + be32_to_cpu(adsp2_alg[i].ym), + be32_to_cpu(adsp2_alg[i].zm)); + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_XM, + adsp2_alg[i].alg.id, + adsp2_alg[i].xm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp2_alg[i + 1].xm); + len -= be32_to_cpu(adsp2_alg[i].xm); + len *= 4; + wm_adsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + SNDRV_CTL_ELEM_TYPE_BYTES); + } else { + adsp_warn(dsp, "Missing length info for region XM with ID %x\n", + be32_to_cpu(adsp2_alg[i].alg.id)); + } + } + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_YM, + adsp2_alg[i].alg.id, + adsp2_alg[i].ym); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp2_alg[i + 1].ym); + len -= be32_to_cpu(adsp2_alg[i].ym); + len *= 4; + wm_adsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + SNDRV_CTL_ELEM_TYPE_BYTES); + } else { + adsp_warn(dsp, "Missing length info for region YM with ID %x\n", + be32_to_cpu(adsp2_alg[i].alg.id)); + } + } + + alg_region = wm_adsp_create_region(dsp, WMFW_ADSP2_ZM, + adsp2_alg[i].alg.id, + adsp2_alg[i].zm); + if (IS_ERR(alg_region)) { + ret = PTR_ERR(alg_region); + goto out; + } + if (dsp->fw_ver == 0) { + if (i + 1 < n_algs) { + len = be32_to_cpu(adsp2_alg[i + 1].zm); + len -= be32_to_cpu(adsp2_alg[i].zm); + len *= 4; + wm_adsp_create_control(dsp, alg_region, 0, + len, NULL, 0, 0, + SNDRV_CTL_ELEM_TYPE_BYTES); + } else { + adsp_warn(dsp, "Missing length info for region ZM with ID %x\n", + be32_to_cpu(adsp2_alg[i].alg.id)); + } + } + } + +out: + kfree(adsp2_alg); + return ret; +} + +static int wm_adsp_load_coeff(struct wm_adsp *dsp) +{ + LIST_HEAD(buf_list); + struct regmap *regmap = dsp->regmap; + struct wmfw_coeff_hdr *hdr; + struct wmfw_coeff_item *blk; + const struct firmware *firmware; + const struct wm_adsp_region *mem; + struct wm_adsp_alg_region *alg_region; + const char *region_name; + int ret, pos, blocks, type, offset, reg; + char *file; + struct wm_adsp_buf *buf; + + file = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (file == NULL) + return -ENOMEM; + + snprintf(file, PAGE_SIZE, "%s-%s-%s.bin", dsp->part, dsp->fwf_name, + wm_adsp_fw[dsp->fw].file); + file[PAGE_SIZE - 1] = '\0'; + + ret = request_firmware(&firmware, file, dsp->dev); + if (ret != 0) { + adsp_warn(dsp, "Failed to request '%s'\n", file); + ret = 0; + goto out; + } + ret = -EINVAL; + + if (sizeof(*hdr) >= firmware->size) { + adsp_err(dsp, "%s: file too short, %zu bytes\n", + file, firmware->size); + goto out_fw; + } + + hdr = (void *)&firmware->data[0]; + if (memcmp(hdr->magic, "WMDR", 4) != 0) { + adsp_err(dsp, "%s: invalid magic\n", file); + goto out_fw; + } + + switch (be32_to_cpu(hdr->rev) & 0xff) { + case 1: + break; + default: + adsp_err(dsp, "%s: Unsupported coefficient file format %d\n", + file, be32_to_cpu(hdr->rev) & 0xff); + ret = -EINVAL; + goto out_fw; + } + + adsp_dbg(dsp, "%s: v%d.%d.%d\n", file, + (le32_to_cpu(hdr->ver) >> 16) & 0xff, + (le32_to_cpu(hdr->ver) >> 8) & 0xff, + le32_to_cpu(hdr->ver) & 0xff); + + pos = le32_to_cpu(hdr->len); + + blocks = 0; + while (pos < firmware->size && + sizeof(*blk) < firmware->size - pos) { + blk = (void *)(&firmware->data[pos]); + + type = le16_to_cpu(blk->type); + offset = le16_to_cpu(blk->offset); + + adsp_dbg(dsp, "%s.%d: %x v%d.%d.%d\n", + file, blocks, le32_to_cpu(blk->id), + (le32_to_cpu(blk->ver) >> 16) & 0xff, + (le32_to_cpu(blk->ver) >> 8) & 0xff, + le32_to_cpu(blk->ver) & 0xff); + adsp_dbg(dsp, "%s.%d: %d bytes at 0x%x in %x\n", + file, blocks, le32_to_cpu(blk->len), offset, type); + + reg = 0; + region_name = "Unknown"; + switch (type) { + case (WMFW_NAME_TEXT << 8): + case (WMFW_INFO_TEXT << 8): + break; + case (WMFW_ABSOLUTE << 8): + /* + * Old files may use this for global + * coefficients. + */ + if (le32_to_cpu(blk->id) == dsp->fw_id && + offset == 0) { + region_name = "global coefficients"; + mem = wm_adsp_find_region(dsp, type); + if (!mem) { + adsp_err(dsp, "No ZM\n"); + break; + } + reg = wm_adsp_region_to_reg(mem, 0); + + } else { + region_name = "register"; + reg = offset; + } + break; + + case WMFW_ADSP1_DM: + case WMFW_ADSP1_ZM: + case WMFW_ADSP2_XM: + case WMFW_ADSP2_YM: + adsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n", + file, blocks, le32_to_cpu(blk->len), + type, le32_to_cpu(blk->id)); + + mem = wm_adsp_find_region(dsp, type); + if (!mem) { + adsp_err(dsp, "No base for region %x\n", type); + break; + } + + alg_region = wm_adsp_find_alg_region(dsp, type, + le32_to_cpu(blk->id)); + if (alg_region) { + reg = alg_region->base; + reg = wm_adsp_region_to_reg(mem, reg); + reg += offset; + } else { + adsp_err(dsp, "No %x for algorithm %x\n", + type, le32_to_cpu(blk->id)); + } + break; + + default: + adsp_err(dsp, "%s.%d: Unknown region type %x at %d\n", + file, blocks, type, pos); + break; + } + + if (reg) { + if (le32_to_cpu(blk->len) > + firmware->size - pos - sizeof(*blk)) { + adsp_err(dsp, + "%s.%d: %s region len %d bytes exceeds file length %zu\n", + file, blocks, region_name, + le32_to_cpu(blk->len), + firmware->size); + ret = -EINVAL; + goto out_fw; + } + + buf = wm_adsp_buf_alloc(blk->data, + le32_to_cpu(blk->len), + &buf_list); + if (!buf) { + adsp_err(dsp, "Out of memory\n"); + ret = -ENOMEM; + goto out_fw; + } + + adsp_dbg(dsp, "%s.%d: Writing %d bytes at %x\n", + file, blocks, le32_to_cpu(blk->len), + reg); + ret = regmap_raw_write_async(regmap, reg, buf->buf, + le32_to_cpu(blk->len)); + if (ret != 0) { + adsp_err(dsp, + "%s.%d: Failed to write to %x in %s: %d\n", + file, blocks, reg, region_name, ret); + } + } + + pos += (le32_to_cpu(blk->len) + sizeof(*blk) + 3) & ~0x03; + blocks++; + } + + ret = regmap_async_complete(regmap); + if (ret != 0) + adsp_err(dsp, "Failed to complete async write: %d\n", ret); + + if (pos > firmware->size) + adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n", + file, blocks, pos - firmware->size); + + wm_adsp_debugfs_save_binname(dsp, file); + +out_fw: + regmap_async_complete(regmap); + release_firmware(firmware); + wm_adsp_buf_free(&buf_list); +out: + kfree(file); + return ret; +} + +static int wm_adsp_create_name(struct wm_adsp *dsp) +{ + char *p; + + if (!dsp->name) { + dsp->name = devm_kasprintf(dsp->dev, GFP_KERNEL, "DSP%d", + dsp->num); + if (!dsp->name) + return -ENOMEM; + } + + if (!dsp->fwf_name) { + p = devm_kstrdup(dsp->dev, dsp->name, GFP_KERNEL); + if (!p) + return -ENOMEM; + + dsp->fwf_name = p; + for (; *p != 0; ++p) + *p = tolower(*p); + } + + return 0; +} + +int wm_adsp1_init(struct wm_adsp *dsp) +{ + int ret; + + ret = wm_adsp_create_name(dsp); + if (ret) + return ret; + + INIT_LIST_HEAD(&dsp->alg_regions); + + mutex_init(&dsp->pwr_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp1_init); + +int wm_adsp1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); + struct wm_adsp *dsp = &dsps[w->shift]; + struct wm_coeff_ctl *ctl; + int ret; + unsigned int val; + + dsp->component = component; + + mutex_lock(&dsp->pwr_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_SYS_ENA, ADSP1_SYS_ENA); + + /* + * For simplicity set the DSP clock rate to be the + * SYSCLK rate rather than making it configurable. + */ + if (dsp->sysclk_reg) { + ret = regmap_read(dsp->regmap, dsp->sysclk_reg, &val); + if (ret != 0) { + adsp_err(dsp, "Failed to read SYSCLK state: %d\n", + ret); + goto err_mutex; + } + + val = (val & dsp->sysclk_mask) >> dsp->sysclk_shift; + + ret = regmap_update_bits(dsp->regmap, + dsp->base + ADSP1_CONTROL_31, + ADSP1_CLK_SEL_MASK, val); + if (ret != 0) { + adsp_err(dsp, "Failed to set clock rate: %d\n", + ret); + goto err_mutex; + } + } + + ret = wm_adsp_load(dsp); + if (ret != 0) + goto err_ena; + + ret = wm_adsp1_setup_algs(dsp); + if (ret != 0) + goto err_ena; + + ret = wm_adsp_load_coeff(dsp); + if (ret != 0) + goto err_ena; + + /* Initialize caches for enabled and unset controls */ + ret = wm_coeff_init_control_caches(dsp); + if (ret != 0) + goto err_ena; + + /* Sync set controls */ + ret = wm_coeff_sync_controls(dsp); + if (ret != 0) + goto err_ena; + + dsp->booted = true; + + /* Start the core running */ + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_CORE_ENA | ADSP1_START, + ADSP1_CORE_ENA | ADSP1_START); + + dsp->running = true; + break; + + case SND_SOC_DAPM_PRE_PMD: + dsp->running = false; + dsp->booted = false; + + /* Halt the core */ + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_CORE_ENA | ADSP1_START, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_19, + ADSP1_WDMA_BUFFER_LENGTH_MASK, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_SYS_ENA, 0); + + list_for_each_entry(ctl, &dsp->ctl_list, list) + ctl->enabled = 0; + + + wm_adsp_free_alg_regions(dsp); + break; + + default: + break; + } + + mutex_unlock(&dsp->pwr_lock); + + return 0; + +err_ena: + regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30, + ADSP1_SYS_ENA, 0); +err_mutex: + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp1_event); + +static int wm_adsp2_ena(struct wm_adsp *dsp) +{ + unsigned int val; + int ret, count; + + switch (dsp->rev) { + case 0: + ret = regmap_update_bits_async(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, ADSP2_SYS_ENA); + if (ret != 0) + return ret; + break; + default: + break; + } + + /* Wait for the RAM to start, should be near instantaneous */ + for (count = 0; count < 10; ++count) { + ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1, &val); + if (ret != 0) + return ret; + + if (val & ADSP2_RAM_RDY) + break; + + usleep_range(250, 500); + } + + if (!(val & ADSP2_RAM_RDY)) { + adsp_err(dsp, "Failed to start DSP RAM\n"); + return -EBUSY; + } + + adsp_dbg(dsp, "RAM ready after %d polls\n", count); + + return 0; +} + +static void wm_adsp2_boot_work(struct work_struct *work) +{ + struct wm_adsp *dsp = container_of(work, + struct wm_adsp, + boot_work); + int ret; + + mutex_lock(&dsp->pwr_lock); + + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, ADSP2_MEM_ENA); + if (ret != 0) + goto err_mutex; + + ret = wm_adsp2_ena(dsp); + if (ret != 0) + goto err_mem; + + ret = wm_adsp_load(dsp); + if (ret != 0) + goto err_ena; + + ret = wm_adsp2_setup_algs(dsp); + if (ret != 0) + goto err_ena; + + ret = wm_adsp_load_coeff(dsp); + if (ret != 0) + goto err_ena; + + /* Initialize caches for enabled and unset controls */ + ret = wm_coeff_init_control_caches(dsp); + if (ret != 0) + goto err_ena; + + switch (dsp->rev) { + case 0: + /* Turn DSP back off until we are ready to run */ + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); + if (ret != 0) + goto err_ena; + break; + default: + break; + } + + dsp->booted = true; + + mutex_unlock(&dsp->pwr_lock); + + return; + +err_ena: + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); +err_mem: + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); +err_mutex: + mutex_unlock(&dsp->pwr_lock); +} + +static void wm_adsp2_set_dspclk(struct wm_adsp *dsp, unsigned int freq) +{ + int ret; + + switch (dsp->rev) { + case 0: + ret = regmap_update_bits_async(dsp->regmap, + dsp->base + ADSP2_CLOCKING, + ADSP2_CLK_SEL_MASK, + freq << ADSP2_CLK_SEL_SHIFT); + if (ret) { + adsp_err(dsp, "Failed to set clock rate: %d\n", ret); + return; + } + break; + default: + /* clock is handled by parent codec driver */ + break; + } +} + +int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct wm_adsp *dsp = &dsps[mc->shift - 1]; + + ucontrol->value.integer.value[0] = dsp->preloaded; + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_preloader_get); + +int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct wm_adsp *dsp = &dsps[mc->shift - 1]; + char preload[32]; + + snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->name); + + dsp->preloaded = ucontrol->value.integer.value[0]; + + if (ucontrol->value.integer.value[0]) + snd_soc_component_force_enable_pin(component, preload); + else + snd_soc_component_disable_pin(component, preload); + + snd_soc_dapm_sync(dapm); + + flush_work(&dsp->boot_work); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_preloader_put); + +static void wm_adsp_stop_watchdog(struct wm_adsp *dsp) +{ + switch (dsp->rev) { + case 0: + case 1: + return; + default: + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_WATCHDOG, + ADSP2_WDT_ENA_MASK, 0); + } +} + +int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event, + unsigned int freq) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); + struct wm_adsp *dsp = &dsps[w->shift]; + struct wm_coeff_ctl *ctl; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + wm_adsp2_set_dspclk(dsp, freq); + queue_work(system_unbound_wq, &dsp->boot_work); + break; + case SND_SOC_DAPM_PRE_PMD: + mutex_lock(&dsp->pwr_lock); + + wm_adsp_debugfs_clear(dsp); + + dsp->fw_id = 0; + dsp->fw_id_version = 0; + + dsp->booted = false; + + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); + + list_for_each_entry(ctl, &dsp->ctl_list, list) + ctl->enabled = 0; + + wm_adsp_free_alg_regions(dsp); + + mutex_unlock(&dsp->pwr_lock); + + adsp_dbg(dsp, "Shutdown complete\n"); + break; + default: + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_early_event); + +int wm_adsp2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_adsp *dsps = snd_soc_component_get_drvdata(component); + struct wm_adsp *dsp = &dsps[w->shift]; + int ret; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + flush_work(&dsp->boot_work); + + mutex_lock(&dsp->pwr_lock); + + if (!dsp->booted) { + ret = -EIO; + goto err; + } + + ret = wm_adsp2_ena(dsp); + if (ret != 0) + goto err; + + /* Sync set controls */ + ret = wm_coeff_sync_controls(dsp); + if (ret != 0) + goto err; + + wm_adsp2_lock(dsp, dsp->lock_regions); + + ret = regmap_update_bits(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, + ADSP2_CORE_ENA | ADSP2_START); + if (ret != 0) + goto err; + + if (wm_adsp_fw[dsp->fw].num_caps != 0) { + ret = wm_adsp_buffer_init(dsp); + if (ret < 0) + goto err; + } + + dsp->running = true; + + mutex_unlock(&dsp->pwr_lock); + + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Tell the firmware to cleanup */ + wm_adsp_signal_event_controls(dsp, WM_ADSP_FW_EVENT_SHUTDOWN); + + wm_adsp_stop_watchdog(dsp); + + /* Log firmware state, it can be useful for analysis */ + switch (dsp->rev) { + case 0: + wm_adsp2_show_fw_status(dsp); + break; + default: + wm_adsp2v2_show_fw_status(dsp); + break; + } + + mutex_lock(&dsp->pwr_lock); + + dsp->running = false; + + regmap_update_bits(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_CORE_ENA | ADSP2_START, 0); + + /* Make sure DMAs are quiesced */ + switch (dsp->rev) { + case 0: + regmap_write(dsp->regmap, + dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_2, 0); + + regmap_update_bits(dsp->regmap, + dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); + break; + default: + regmap_write(dsp->regmap, + dsp->base + ADSP2_RDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2_WDMA_CONFIG_1, 0); + regmap_write(dsp->regmap, + dsp->base + ADSP2V2_WDMA_CONFIG_2, 0); + break; + } + + if (wm_adsp_fw[dsp->fw].num_caps != 0) + wm_adsp_buffer_free(dsp); + + mutex_unlock(&dsp->pwr_lock); + + adsp_dbg(dsp, "Execution stopped\n"); + break; + + default: + break; + } + + return 0; +err: + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0); + mutex_unlock(&dsp->pwr_lock); + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp2_event); + +int wm_adsp2_component_probe(struct wm_adsp *dsp, struct snd_soc_component *component) +{ + char preload[32]; + + snprintf(preload, ARRAY_SIZE(preload), "%s Preload", dsp->name); + snd_soc_component_disable_pin(component, preload); + + wm_adsp2_init_debugfs(dsp, component); + + dsp->component = component; + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_component_probe); + +int wm_adsp2_component_remove(struct wm_adsp *dsp, struct snd_soc_component *component) +{ + wm_adsp2_cleanup_debugfs(dsp); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_component_remove); + +int wm_adsp2_init(struct wm_adsp *dsp) +{ + int ret; + + ret = wm_adsp_create_name(dsp); + if (ret) + return ret; + + switch (dsp->rev) { + case 0: + /* + * Disable the DSP memory by default when in reset for a small + * power saving. + */ + ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_MEM_ENA, 0); + if (ret) { + adsp_err(dsp, + "Failed to clear memory retention: %d\n", ret); + return ret; + } + break; + default: + break; + } + + INIT_LIST_HEAD(&dsp->alg_regions); + INIT_LIST_HEAD(&dsp->ctl_list); + INIT_WORK(&dsp->boot_work, wm_adsp2_boot_work); + + mutex_init(&dsp->pwr_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_init); + +void wm_adsp2_remove(struct wm_adsp *dsp) +{ + struct wm_coeff_ctl *ctl; + + while (!list_empty(&dsp->ctl_list)) { + ctl = list_first_entry(&dsp->ctl_list, struct wm_coeff_ctl, + list); + list_del(&ctl->list); + wm_adsp_free_ctl_blk(ctl); + } +} +EXPORT_SYMBOL_GPL(wm_adsp2_remove); + +static inline int wm_adsp_compr_attached(struct wm_adsp_compr *compr) +{ + return compr->buf != NULL; +} + +static int wm_adsp_compr_attach(struct wm_adsp_compr *compr) +{ + /* + * Note this will be more complex once each DSP can support multiple + * streams + */ + if (!compr->dsp->buffer) + return -EINVAL; + + compr->buf = compr->dsp->buffer; + compr->buf->compr = compr; + + return 0; +} + +static void wm_adsp_compr_detach(struct wm_adsp_compr *compr) +{ + if (!compr) + return; + + /* Wake the poll so it can see buffer is no longer attached */ + if (compr->stream) + snd_compr_fragment_elapsed(compr->stream); + + if (wm_adsp_compr_attached(compr)) { + compr->buf->compr = NULL; + compr->buf = NULL; + } +} + +int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) +{ + struct wm_adsp_compr *compr; + int ret = 0; + + mutex_lock(&dsp->pwr_lock); + + if (wm_adsp_fw[dsp->fw].num_caps == 0) { + adsp_err(dsp, "Firmware does not support compressed API\n"); + ret = -ENXIO; + goto out; + } + + if (wm_adsp_fw[dsp->fw].compr_direction != stream->direction) { + adsp_err(dsp, "Firmware does not support stream direction\n"); + ret = -EINVAL; + goto out; + } + + if (dsp->compr) { + /* It is expect this limitation will be removed in future */ + adsp_err(dsp, "Only a single stream supported per DSP\n"); + ret = -EBUSY; + goto out; + } + + compr = kzalloc(sizeof(*compr), GFP_KERNEL); + if (!compr) { + ret = -ENOMEM; + goto out; + } + + compr->dsp = dsp; + compr->stream = stream; + + dsp->compr = compr; + + stream->runtime->private_data = compr; + +out: + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_open); + +int wm_adsp_compr_free(struct snd_compr_stream *stream) +{ + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + + mutex_lock(&dsp->pwr_lock); + + wm_adsp_compr_detach(compr); + dsp->compr = NULL; + + kfree(compr->raw_buf); + kfree(compr); + + mutex_unlock(&dsp->pwr_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_free); + +static int wm_adsp_compr_check_params(struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + const struct wm_adsp_fw_caps *caps; + const struct snd_codec_desc *desc; + int i, j; + + if (params->buffer.fragment_size < WM_ADSP_MIN_FRAGMENT_SIZE || + params->buffer.fragment_size > WM_ADSP_MAX_FRAGMENT_SIZE || + params->buffer.fragments < WM_ADSP_MIN_FRAGMENTS || + params->buffer.fragments > WM_ADSP_MAX_FRAGMENTS || + params->buffer.fragment_size % WM_ADSP_DATA_WORD_SIZE) { + adsp_err(dsp, "Invalid buffer fragsize=%d fragments=%d\n", + params->buffer.fragment_size, + params->buffer.fragments); + + return -EINVAL; + } + + for (i = 0; i < wm_adsp_fw[dsp->fw].num_caps; i++) { + caps = &wm_adsp_fw[dsp->fw].caps[i]; + desc = &caps->desc; + + if (caps->id != params->codec.id) + continue; + + if (stream->direction == SND_COMPRESS_PLAYBACK) { + if (desc->max_ch < params->codec.ch_out) + continue; + } else { + if (desc->max_ch < params->codec.ch_in) + continue; + } + + if (!(desc->formats & (1 << params->codec.format))) + continue; + + for (j = 0; j < desc->num_sample_rates; ++j) + if (desc->sample_rates[j] == params->codec.sample_rate) + return 0; + } + + adsp_err(dsp, "Invalid params id=%u ch=%u,%u rate=%u fmt=%u\n", + params->codec.id, params->codec.ch_in, params->codec.ch_out, + params->codec.sample_rate, params->codec.format); + return -EINVAL; +} + +static inline unsigned int wm_adsp_compr_frag_words(struct wm_adsp_compr *compr) +{ + return compr->size.fragment_size / WM_ADSP_DATA_WORD_SIZE; +} + +int wm_adsp_compr_set_params(struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + struct wm_adsp_compr *compr = stream->runtime->private_data; + unsigned int size; + int ret; + + ret = wm_adsp_compr_check_params(stream, params); + if (ret) + return ret; + + compr->size = params->buffer; + + adsp_dbg(compr->dsp, "fragment_size=%d fragments=%d\n", + compr->size.fragment_size, compr->size.fragments); + + size = wm_adsp_compr_frag_words(compr) * sizeof(*compr->raw_buf); + compr->raw_buf = kmalloc(size, GFP_DMA | GFP_KERNEL); + if (!compr->raw_buf) + return -ENOMEM; + + compr->sample_rate = params->codec.sample_rate; + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_set_params); + +int wm_adsp_compr_get_caps(struct snd_compr_stream *stream, + struct snd_compr_caps *caps) +{ + struct wm_adsp_compr *compr = stream->runtime->private_data; + int fw = compr->dsp->fw; + int i; + + if (wm_adsp_fw[fw].caps) { + for (i = 0; i < wm_adsp_fw[fw].num_caps; i++) + caps->codecs[i] = wm_adsp_fw[fw].caps[i].id; + + caps->num_codecs = i; + caps->direction = wm_adsp_fw[fw].compr_direction; + + caps->min_fragment_size = WM_ADSP_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = WM_ADSP_MAX_FRAGMENT_SIZE; + caps->min_fragments = WM_ADSP_MIN_FRAGMENTS; + caps->max_fragments = WM_ADSP_MAX_FRAGMENTS; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_get_caps); + +static int wm_adsp_read_data_block(struct wm_adsp *dsp, int mem_type, + unsigned int mem_addr, + unsigned int num_words, u32 *data) +{ + struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type); + unsigned int i, reg; + int ret; + + if (!mem) + return -EINVAL; + + reg = wm_adsp_region_to_reg(mem, mem_addr); + + ret = regmap_raw_read(dsp->regmap, reg, data, + sizeof(*data) * num_words); + if (ret < 0) + return ret; + + for (i = 0; i < num_words; ++i) + data[i] = be32_to_cpu(data[i]) & 0x00ffffffu; + + return 0; +} + +static inline int wm_adsp_read_data_word(struct wm_adsp *dsp, int mem_type, + unsigned int mem_addr, u32 *data) +{ + return wm_adsp_read_data_block(dsp, mem_type, mem_addr, 1, data); +} + +static int wm_adsp_write_data_word(struct wm_adsp *dsp, int mem_type, + unsigned int mem_addr, u32 data) +{ + struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type); + unsigned int reg; + + if (!mem) + return -EINVAL; + + reg = wm_adsp_region_to_reg(mem, mem_addr); + + data = cpu_to_be32(data & 0x00ffffffu); + + return regmap_raw_write(dsp->regmap, reg, &data, sizeof(data)); +} + +static inline int wm_adsp_buffer_read(struct wm_adsp_compr_buf *buf, + unsigned int field_offset, u32 *data) +{ + return wm_adsp_read_data_word(buf->dsp, WMFW_ADSP2_XM, + buf->host_buf_ptr + field_offset, data); +} + +static inline int wm_adsp_buffer_write(struct wm_adsp_compr_buf *buf, + unsigned int field_offset, u32 data) +{ + return wm_adsp_write_data_word(buf->dsp, WMFW_ADSP2_XM, + buf->host_buf_ptr + field_offset, data); +} + +static int wm_adsp_legacy_host_buf_addr(struct wm_adsp_compr_buf *buf) +{ + struct wm_adsp_alg_region *alg_region; + struct wm_adsp *dsp = buf->dsp; + u32 xmalg, addr, magic; + int i, ret; + + alg_region = wm_adsp_find_alg_region(dsp, WMFW_ADSP2_XM, dsp->fw_id); + xmalg = sizeof(struct wm_adsp_system_config_xm_hdr) / sizeof(__be32); + + addr = alg_region->base + xmalg + ALG_XM_FIELD(magic); + ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, &magic); + if (ret < 0) + return ret; + + if (magic != WM_ADSP_ALG_XM_STRUCT_MAGIC) + return -EINVAL; + + addr = alg_region->base + xmalg + ALG_XM_FIELD(host_buf_ptr); + for (i = 0; i < 5; ++i) { + ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, + &buf->host_buf_ptr); + if (ret < 0) + return ret; + + if (buf->host_buf_ptr) + break; + + usleep_range(1000, 2000); + } + + if (!buf->host_buf_ptr) + return -EIO; + + adsp_dbg(dsp, "host_buf_ptr=%x\n", buf->host_buf_ptr); + + return 0; +} + +static struct wm_coeff_ctl * +wm_adsp_find_host_buffer_ctrl(struct wm_adsp_compr_buf *buf) +{ + struct wm_adsp *dsp = buf->dsp; + struct wm_coeff_ctl *ctl; + + list_for_each_entry(ctl, &dsp->ctl_list, list) { + if (ctl->type != WMFW_CTL_TYPE_HOST_BUFFER) + continue; + + if (!ctl->enabled) + continue; + + return ctl; + } + + return NULL; +} + +static int wm_adsp_buffer_locate(struct wm_adsp_compr_buf *buf) +{ + struct wm_adsp *dsp = buf->dsp; + struct wm_coeff_ctl *ctl; + unsigned int reg; + u32 val; + int i, ret; + + ctl = wm_adsp_find_host_buffer_ctrl(buf); + if (!ctl) + return wm_adsp_legacy_host_buf_addr(buf); + + ret = wm_coeff_base_reg(ctl, ®); + if (ret) + return ret; + + for (i = 0; i < 5; ++i) { + ret = regmap_raw_read(dsp->regmap, reg, &val, sizeof(val)); + if (ret < 0) + return ret; + + if (val) + break; + + usleep_range(1000, 2000); + } + + if (!val) + return -EIO; + + buf->host_buf_ptr = be32_to_cpu(val); + adsp_dbg(dsp, "host_buf_ptr=%x\n", buf->host_buf_ptr); + + return 0; +} + +static int wm_adsp_buffer_populate(struct wm_adsp_compr_buf *buf) +{ + const struct wm_adsp_fw_caps *caps = wm_adsp_fw[buf->dsp->fw].caps; + struct wm_adsp_buffer_region *region; + u32 offset = 0; + int i, ret; + + for (i = 0; i < caps->num_regions; ++i) { + region = &buf->regions[i]; + + region->offset = offset; + region->mem_type = caps->region_defs[i].mem_type; + + ret = wm_adsp_buffer_read(buf, caps->region_defs[i].base_offset, + ®ion->base_addr); + if (ret < 0) + return ret; + + ret = wm_adsp_buffer_read(buf, caps->region_defs[i].size_offset, + &offset); + if (ret < 0) + return ret; + + region->cumulative_size = offset; + + adsp_dbg(buf->dsp, + "region=%d type=%d base=%04x off=%04x size=%04x\n", + i, region->mem_type, region->base_addr, + region->offset, region->cumulative_size); + } + + return 0; +} + +static void wm_adsp_buffer_clear(struct wm_adsp_compr_buf *buf) +{ + buf->irq_count = 0xFFFFFFFF; + buf->read_index = -1; + buf->avail = 0; +} + +static int wm_adsp_buffer_init(struct wm_adsp *dsp) +{ + struct wm_adsp_compr_buf *buf; + int ret; + + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf->dsp = dsp; + + wm_adsp_buffer_clear(buf); + + ret = wm_adsp_buffer_locate(buf); + if (ret < 0) { + adsp_err(dsp, "Failed to acquire host buffer: %d\n", ret); + goto err_buffer; + } + + buf->regions = kcalloc(wm_adsp_fw[dsp->fw].caps->num_regions, + sizeof(*buf->regions), GFP_KERNEL); + if (!buf->regions) { + ret = -ENOMEM; + goto err_buffer; + } + + ret = wm_adsp_buffer_populate(buf); + if (ret < 0) { + adsp_err(dsp, "Failed to populate host buffer: %d\n", ret); + goto err_regions; + } + + dsp->buffer = buf; + + return 0; + +err_regions: + kfree(buf->regions); +err_buffer: + kfree(buf); + return ret; +} + +static int wm_adsp_buffer_free(struct wm_adsp *dsp) +{ + if (dsp->buffer) { + wm_adsp_compr_detach(dsp->buffer->compr); + + kfree(dsp->buffer->regions); + kfree(dsp->buffer); + + dsp->buffer = NULL; + } + + return 0; +} + +int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd) +{ + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + int ret = 0; + + adsp_dbg(dsp, "Trigger: %d\n", cmd); + + mutex_lock(&dsp->pwr_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!wm_adsp_compr_attached(compr)) { + ret = wm_adsp_compr_attach(compr); + if (ret < 0) { + adsp_err(dsp, "Failed to link buffer and stream: %d\n", + ret); + break; + } + } + + /* Trigger the IRQ at one fragment of data */ + ret = wm_adsp_buffer_write(compr->buf, + HOST_BUFFER_FIELD(high_water_mark), + wm_adsp_compr_frag_words(compr)); + if (ret < 0) { + adsp_err(dsp, "Failed to set high water mark: %d\n", + ret); + break; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (wm_adsp_compr_attached(compr)) + wm_adsp_buffer_clear(compr->buf); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_trigger); + +static inline int wm_adsp_buffer_size(struct wm_adsp_compr_buf *buf) +{ + int last_region = wm_adsp_fw[buf->dsp->fw].caps->num_regions - 1; + + return buf->regions[last_region].cumulative_size; +} + +static int wm_adsp_buffer_update_avail(struct wm_adsp_compr_buf *buf) +{ + u32 next_read_index, next_write_index; + int write_index, read_index, avail; + int ret; + + /* Only sync read index if we haven't already read a valid index */ + if (buf->read_index < 0) { + ret = wm_adsp_buffer_read(buf, + HOST_BUFFER_FIELD(next_read_index), + &next_read_index); + if (ret < 0) + return ret; + + read_index = sign_extend32(next_read_index, 23); + + if (read_index < 0) { + adsp_dbg(buf->dsp, "Avail check on unstarted stream\n"); + return 0; + } + + buf->read_index = read_index; + } + + ret = wm_adsp_buffer_read(buf, HOST_BUFFER_FIELD(next_write_index), + &next_write_index); + if (ret < 0) + return ret; + + write_index = sign_extend32(next_write_index, 23); + + avail = write_index - buf->read_index; + if (avail < 0) + avail += wm_adsp_buffer_size(buf); + + adsp_dbg(buf->dsp, "readindex=0x%x, writeindex=0x%x, avail=%d\n", + buf->read_index, write_index, avail * WM_ADSP_DATA_WORD_SIZE); + + buf->avail = avail; + + return 0; +} + +static int wm_adsp_buffer_get_error(struct wm_adsp_compr_buf *buf) +{ + int ret; + + ret = wm_adsp_buffer_read(buf, HOST_BUFFER_FIELD(error), &buf->error); + if (ret < 0) { + adsp_err(buf->dsp, "Failed to check buffer error: %d\n", ret); + return ret; + } + if (buf->error != 0) { + adsp_err(buf->dsp, "Buffer error occurred: %d\n", buf->error); + return -EIO; + } + + return 0; +} + +int wm_adsp_compr_handle_irq(struct wm_adsp *dsp) +{ + struct wm_adsp_compr_buf *buf; + struct wm_adsp_compr *compr; + int ret = 0; + + mutex_lock(&dsp->pwr_lock); + + buf = dsp->buffer; + compr = dsp->compr; + + if (!buf) { + ret = -ENODEV; + goto out; + } + + adsp_dbg(dsp, "Handling buffer IRQ\n"); + + ret = wm_adsp_buffer_get_error(buf); + if (ret < 0) + goto out_notify; /* Wake poll to report error */ + + ret = wm_adsp_buffer_read(buf, HOST_BUFFER_FIELD(irq_count), + &buf->irq_count); + if (ret < 0) { + adsp_err(dsp, "Failed to get irq_count: %d\n", ret); + goto out; + } + + ret = wm_adsp_buffer_update_avail(buf); + if (ret < 0) { + adsp_err(dsp, "Error reading avail: %d\n", ret); + goto out; + } + + if (wm_adsp_fw[dsp->fw].voice_trigger && buf->irq_count == 2) + ret = WM_ADSP_COMPR_VOICE_TRIGGER; + +out_notify: + if (compr && compr->stream) + snd_compr_fragment_elapsed(compr->stream); + +out: + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_handle_irq); + +static int wm_adsp_buffer_reenable_irq(struct wm_adsp_compr_buf *buf) +{ + if (buf->irq_count & 0x01) + return 0; + + adsp_dbg(buf->dsp, "Enable IRQ(0x%x) for next fragment\n", + buf->irq_count); + + buf->irq_count |= 0x01; + + return wm_adsp_buffer_write(buf, HOST_BUFFER_FIELD(irq_ack), + buf->irq_count); +} + +int wm_adsp_compr_pointer(struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + struct wm_adsp_compr_buf *buf; + int ret = 0; + + adsp_dbg(dsp, "Pointer request\n"); + + mutex_lock(&dsp->pwr_lock); + + buf = compr->buf; + + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); + ret = -EIO; + goto out; + } + + if (buf->avail < wm_adsp_compr_frag_words(compr)) { + ret = wm_adsp_buffer_update_avail(buf); + if (ret < 0) { + adsp_err(dsp, "Error reading avail: %d\n", ret); + goto out; + } + + /* + * If we really have less than 1 fragment available tell the + * DSP to inform us once a whole fragment is available. + */ + if (buf->avail < wm_adsp_compr_frag_words(compr)) { + ret = wm_adsp_buffer_get_error(buf); + if (ret < 0) { + if (compr->buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); + goto out; + } + + ret = wm_adsp_buffer_reenable_irq(buf); + if (ret < 0) { + adsp_err(dsp, + "Failed to re-enable buffer IRQ: %d\n", + ret); + goto out; + } + } + } + + tstamp->copied_total = compr->copied_total; + tstamp->copied_total += buf->avail * WM_ADSP_DATA_WORD_SIZE; + tstamp->sampling_rate = compr->sample_rate; + +out: + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_pointer); + +static int wm_adsp_buffer_capture_block(struct wm_adsp_compr *compr, int target) +{ + struct wm_adsp_compr_buf *buf = compr->buf; + u8 *pack_in = (u8 *)compr->raw_buf; + u8 *pack_out = (u8 *)compr->raw_buf; + unsigned int adsp_addr; + int mem_type, nwords, max_read; + int i, j, ret; + + /* Calculate read parameters */ + for (i = 0; i < wm_adsp_fw[buf->dsp->fw].caps->num_regions; ++i) + if (buf->read_index < buf->regions[i].cumulative_size) + break; + + if (i == wm_adsp_fw[buf->dsp->fw].caps->num_regions) + return -EINVAL; + + mem_type = buf->regions[i].mem_type; + adsp_addr = buf->regions[i].base_addr + + (buf->read_index - buf->regions[i].offset); + + max_read = wm_adsp_compr_frag_words(compr); + nwords = buf->regions[i].cumulative_size - buf->read_index; + + if (nwords > target) + nwords = target; + if (nwords > buf->avail) + nwords = buf->avail; + if (nwords > max_read) + nwords = max_read; + if (!nwords) + return 0; + + /* Read data from DSP */ + ret = wm_adsp_read_data_block(buf->dsp, mem_type, adsp_addr, + nwords, compr->raw_buf); + if (ret < 0) + return ret; + + /* Remove the padding bytes from the data read from the DSP */ + for (i = 0; i < nwords; i++) { + for (j = 0; j < WM_ADSP_DATA_WORD_SIZE; j++) + *pack_out++ = *pack_in++; + + pack_in += sizeof(*(compr->raw_buf)) - WM_ADSP_DATA_WORD_SIZE; + } + + /* update read index to account for words read */ + buf->read_index += nwords; + if (buf->read_index == wm_adsp_buffer_size(buf)) + buf->read_index = 0; + + ret = wm_adsp_buffer_write(buf, HOST_BUFFER_FIELD(next_read_index), + buf->read_index); + if (ret < 0) + return ret; + + /* update avail to account for words read */ + buf->avail -= nwords; + + return nwords; +} + +static int wm_adsp_compr_read(struct wm_adsp_compr *compr, + char __user *buf, size_t count) +{ + struct wm_adsp *dsp = compr->dsp; + int ntotal = 0; + int nwords, nbytes; + + adsp_dbg(dsp, "Requested read of %zu bytes\n", count); + + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(compr->stream, SNDRV_PCM_STATE_XRUN); + return -EIO; + } + + count /= WM_ADSP_DATA_WORD_SIZE; + + do { + nwords = wm_adsp_buffer_capture_block(compr, count); + if (nwords < 0) { + adsp_err(dsp, "Failed to capture block: %d\n", nwords); + return nwords; + } + + nbytes = nwords * WM_ADSP_DATA_WORD_SIZE; + + adsp_dbg(dsp, "Read %d bytes\n", nbytes); + + if (copy_to_user(buf + ntotal, compr->raw_buf, nbytes)) { + adsp_err(dsp, "Failed to copy data to user: %d, %d\n", + ntotal, nbytes); + return -EFAULT; + } + + count -= nwords; + ntotal += nbytes; + } while (nwords > 0 && count > 0); + + compr->copied_total += ntotal; + + return ntotal; +} + +int wm_adsp_compr_copy(struct snd_compr_stream *stream, char __user *buf, + size_t count) +{ + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + int ret; + + mutex_lock(&dsp->pwr_lock); + + if (stream->direction == SND_COMPRESS_CAPTURE) + ret = wm_adsp_compr_read(compr, buf, count); + else + ret = -ENOTSUPP; + + mutex_unlock(&dsp->pwr_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); + +int wm_adsp2_lock(struct wm_adsp *dsp, unsigned int lock_regions) +{ + struct regmap *regmap = dsp->regmap; + unsigned int code0, code1, lock_reg; + + if (!(lock_regions & WM_ADSP2_REGION_ALL)) + return 0; + + lock_regions &= WM_ADSP2_REGION_ALL; + lock_reg = dsp->base + ADSP2_LOCK_REGION_1_LOCK_REGION_0; + + while (lock_regions) { + code0 = code1 = 0; + if (lock_regions & BIT(0)) { + code0 = ADSP2_LOCK_CODE_0; + code1 = ADSP2_LOCK_CODE_1; + } + if (lock_regions & BIT(1)) { + code0 |= ADSP2_LOCK_CODE_0 << ADSP2_LOCK_REGION_SHIFT; + code1 |= ADSP2_LOCK_CODE_1 << ADSP2_LOCK_REGION_SHIFT; + } + regmap_write(regmap, lock_reg, code0); + regmap_write(regmap, lock_reg, code1); + lock_regions >>= 2; + lock_reg += 2; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm_adsp2_lock); + +irqreturn_t wm_adsp2_bus_error(struct wm_adsp *dsp) +{ + unsigned int val; + struct regmap *regmap = dsp->regmap; + int ret = 0; + + mutex_lock(&dsp->pwr_lock); + + ret = regmap_read(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, &val); + if (ret) { + adsp_err(dsp, + "Failed to read Region Lock Ctrl register: %d\n", ret); + goto error; + } + + if (val & ADSP2_WDT_TIMEOUT_STS_MASK) { + adsp_err(dsp, "watchdog timeout error\n"); + wm_adsp_stop_watchdog(dsp); + } + + if (val & (ADSP2_SLAVE_ERR_MASK | ADSP2_REGION_LOCK_ERR_MASK)) { + if (val & ADSP2_SLAVE_ERR_MASK) + adsp_err(dsp, "bus error: slave error\n"); + else + adsp_err(dsp, "bus error: region lock error\n"); + + ret = regmap_read(regmap, dsp->base + ADSP2_BUS_ERR_ADDR, &val); + if (ret) { + adsp_err(dsp, + "Failed to read Bus Err Addr register: %d\n", + ret); + goto error; + } + + adsp_err(dsp, "bus error address = 0x%x\n", + val & ADSP2_BUS_ERR_ADDR_MASK); + + ret = regmap_read(regmap, + dsp->base + ADSP2_PMEM_ERR_ADDR_XMEM_ERR_ADDR, + &val); + if (ret) { + adsp_err(dsp, + "Failed to read Pmem Xmem Err Addr register: %d\n", + ret); + goto error; + } + + adsp_err(dsp, "xmem error address = 0x%x\n", + val & ADSP2_XMEM_ERR_ADDR_MASK); + adsp_err(dsp, "pmem error address = 0x%x\n", + (val & ADSP2_PMEM_ERR_ADDR_MASK) >> + ADSP2_PMEM_ERR_ADDR_SHIFT); + } + + regmap_update_bits(regmap, dsp->base + ADSP2_LOCK_REGION_CTRL, + ADSP2_CTRL_ERR_EINT, ADSP2_CTRL_ERR_EINT); + +error: + mutex_unlock(&dsp->pwr_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_adsp2_bus_error); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h new file mode 100644 index 000000000..4b8778b0b --- /dev/null +++ b/sound/soc/codecs/wm_adsp.h @@ -0,0 +1,171 @@ +/* + * wm_adsp.h -- Wolfson ADSP support + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __WM_ADSP_H +#define __WM_ADSP_H + +#include +#include +#include + +#include "wmfw.h" + +/* Return values for wm_adsp_compr_handle_irq */ +#define WM_ADSP_COMPR_OK 0 +#define WM_ADSP_COMPR_VOICE_TRIGGER 1 + +#define WM_ADSP2_REGION_0 BIT(0) +#define WM_ADSP2_REGION_1 BIT(1) +#define WM_ADSP2_REGION_2 BIT(2) +#define WM_ADSP2_REGION_3 BIT(3) +#define WM_ADSP2_REGION_4 BIT(4) +#define WM_ADSP2_REGION_5 BIT(5) +#define WM_ADSP2_REGION_6 BIT(6) +#define WM_ADSP2_REGION_7 BIT(7) +#define WM_ADSP2_REGION_8 BIT(8) +#define WM_ADSP2_REGION_9 BIT(9) +#define WM_ADSP2_REGION_1_9 (WM_ADSP2_REGION_1 | \ + WM_ADSP2_REGION_2 | WM_ADSP2_REGION_3 | \ + WM_ADSP2_REGION_4 | WM_ADSP2_REGION_5 | \ + WM_ADSP2_REGION_6 | WM_ADSP2_REGION_7 | \ + WM_ADSP2_REGION_8 | WM_ADSP2_REGION_9) +#define WM_ADSP2_REGION_ALL (WM_ADSP2_REGION_0 | WM_ADSP2_REGION_1_9) + +struct wm_adsp_region { + int type; + unsigned int base; +}; + +struct wm_adsp_alg_region { + struct list_head list; + unsigned int alg; + int type; + unsigned int base; +}; + +struct wm_adsp_compr; +struct wm_adsp_compr_buf; + +struct wm_adsp { + const char *part; + const char *name; + const char *fwf_name; + int rev; + int num; + int type; + struct device *dev; + struct regmap *regmap; + struct snd_soc_component *component; + + unsigned int base; + unsigned int sysclk_reg; + unsigned int sysclk_mask; + unsigned int sysclk_shift; + + struct list_head alg_regions; + + unsigned int fw_id; + unsigned int fw_id_version; + + const struct wm_adsp_region *mem; + int num_mems; + + int fw; + int fw_ver; + + bool preloaded; + bool booted; + bool running; + + struct list_head ctl_list; + + struct work_struct boot_work; + + struct wm_adsp_compr *compr; + struct wm_adsp_compr_buf *buffer; + + struct mutex pwr_lock; + + unsigned int lock_regions; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; + char *wmfw_file_name; + char *bin_file_name; +#endif + +}; + +#define WM_ADSP1(wname, num) \ + SND_SOC_DAPM_PGA_E(wname, SND_SOC_NOPM, num, 0, NULL, 0, \ + wm_adsp1_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +#define WM_ADSP2_PRELOAD_SWITCH(wname, num) \ + SOC_SINGLE_EXT(wname " Preload Switch", SND_SOC_NOPM, num, 1, 0, \ + wm_adsp2_preloader_get, wm_adsp2_preloader_put) + +#define WM_ADSP2(wname, num, event_fn) \ + SND_SOC_DAPM_SPK(wname " Preload", NULL), \ +{ .id = snd_soc_dapm_supply, .name = wname " Preloader", \ + .reg = SND_SOC_NOPM, .shift = num, .event = event_fn, \ + .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD, \ + .subseq = 100, /* Ensure we run after SYSCLK supply widget */ }, \ +{ .id = snd_soc_dapm_out_drv, .name = wname, \ + .reg = SND_SOC_NOPM, .shift = num, .event = wm_adsp2_event, \ + .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD } + +#define WM_ADSP_FW_CONTROL(dspname, num) \ + SOC_ENUM_EXT(dspname " Firmware", wm_adsp_fw_enum[num], \ + wm_adsp_fw_get, wm_adsp_fw_put) + +extern const struct soc_enum wm_adsp_fw_enum[]; + +int wm_adsp1_init(struct wm_adsp *dsp); +int wm_adsp2_init(struct wm_adsp *dsp); +void wm_adsp2_remove(struct wm_adsp *dsp); +int wm_adsp2_component_probe(struct wm_adsp *dsp, struct snd_soc_component *component); +int wm_adsp2_component_remove(struct wm_adsp *dsp, struct snd_soc_component *component); +int wm_adsp1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +int wm_adsp2_early_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event, + unsigned int freq); + +int wm_adsp2_lock(struct wm_adsp *adsp, unsigned int regions); +irqreturn_t wm_adsp2_bus_error(struct wm_adsp *adsp); + +int wm_adsp2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +int wm_adsp2_preloader_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int wm_adsp_fw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream); +int wm_adsp_compr_free(struct snd_compr_stream *stream); +int wm_adsp_compr_set_params(struct snd_compr_stream *stream, + struct snd_compr_params *params); +int wm_adsp_compr_get_caps(struct snd_compr_stream *stream, + struct snd_compr_caps *caps); +int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd); +int wm_adsp_compr_handle_irq(struct wm_adsp *dsp); +int wm_adsp_compr_pointer(struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp); +int wm_adsp_compr_copy(struct snd_compr_stream *stream, + char __user *buf, size_t count); + +#endif diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c new file mode 100644 index 000000000..da7fa6f54 --- /dev/null +++ b/sound/soc/codecs/wm_hubs.c @@ -0,0 +1,1313 @@ +/* + * wm_hubs.c -- WM8993/4 common code + * + * Copyright 2009-12 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8993.h" +#include "wm_hubs.h" + +const DECLARE_TLV_DB_SCALE(wm_hubs_spkmix_tlv, -300, 300, 0); +EXPORT_SYMBOL_GPL(wm_hubs_spkmix_tlv); + +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0); +static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0); +static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1); +static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_RANGE(spkboost_tlv, + 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), + 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0) +); +static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0); + +static const char *speaker_ref_text[] = { + "SPKVDD/2", + "VMID", +}; + +static SOC_ENUM_SINGLE_DECL(speaker_ref, + WM8993_SPEAKER_MIXER, 8, speaker_ref_text); + +static const char *speaker_mode_text[] = { + "Class D", + "Class AB", +}; + +static SOC_ENUM_SINGLE_DECL(speaker_mode, + WM8993_SPKMIXR_ATTENUATION, 8, speaker_mode_text); + +static void wait_for_dc_servo(struct snd_soc_component *component, unsigned int op) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + unsigned int reg; + int count = 0; + int timeout; + unsigned int val; + + val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1; + + /* Trigger the command */ + snd_soc_component_write(component, WM8993_DC_SERVO_0, val); + + dev_dbg(component->dev, "Waiting for DC servo...\n"); + + if (hubs->dcs_done_irq) + timeout = 4; + else + timeout = 400; + + do { + count++; + + if (hubs->dcs_done_irq) + wait_for_completion_timeout(&hubs->dcs_done, + msecs_to_jiffies(250)); + else + msleep(1); + + reg = snd_soc_component_read32(component, WM8993_DC_SERVO_0); + dev_dbg(component->dev, "DC servo: %x\n", reg); + } while (reg & op && count < timeout); + + if (reg & op) + dev_err(component->dev, "Timed out waiting for DC Servo %x\n", + op); +} + +irqreturn_t wm_hubs_dcs_done(int irq, void *data) +{ + struct wm_hubs_data *hubs = data; + + complete(&hubs->dcs_done); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_hubs_dcs_done); + +static bool wm_hubs_dac_hp_direct(struct snd_soc_component *component) +{ + int reg; + + /* If we're going via the mixer we'll need to do additional checks */ + reg = snd_soc_component_read32(component, WM8993_OUTPUT_MIXER1); + if (!(reg & WM8993_DACL_TO_HPOUT1L)) { + if (reg & ~WM8993_DACL_TO_MIXOUTL) { + dev_vdbg(component->dev, "Analogue paths connected: %x\n", + reg & ~WM8993_DACL_TO_HPOUT1L); + return false; + } else { + dev_vdbg(component->dev, "HPL connected to mixer\n"); + } + } else { + dev_vdbg(component->dev, "HPL connected to DAC\n"); + } + + reg = snd_soc_component_read32(component, WM8993_OUTPUT_MIXER2); + if (!(reg & WM8993_DACR_TO_HPOUT1R)) { + if (reg & ~WM8993_DACR_TO_MIXOUTR) { + dev_vdbg(component->dev, "Analogue paths connected: %x\n", + reg & ~WM8993_DACR_TO_HPOUT1R); + return false; + } else { + dev_vdbg(component->dev, "HPR connected to mixer\n"); + } + } else { + dev_vdbg(component->dev, "HPR connected to DAC\n"); + } + + return true; +} + +struct wm_hubs_dcs_cache { + struct list_head list; + unsigned int left; + unsigned int right; + u16 dcs_cfg; +}; + +static bool wm_hubs_dcs_cache_get(struct snd_soc_component *component, + struct wm_hubs_dcs_cache **entry) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + struct wm_hubs_dcs_cache *cache; + unsigned int left, right; + + left = snd_soc_component_read32(component, WM8993_LEFT_OUTPUT_VOLUME); + left &= WM8993_HPOUT1L_VOL_MASK; + + right = snd_soc_component_read32(component, WM8993_RIGHT_OUTPUT_VOLUME); + right &= WM8993_HPOUT1R_VOL_MASK; + + list_for_each_entry(cache, &hubs->dcs_cache, list) { + if (cache->left != left || cache->right != right) + continue; + + *entry = cache; + return true; + } + + return false; +} + +static void wm_hubs_dcs_cache_set(struct snd_soc_component *component, u16 dcs_cfg) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + struct wm_hubs_dcs_cache *cache; + + if (hubs->no_cache_dac_hp_direct) + return; + + cache = devm_kzalloc(component->dev, sizeof(*cache), GFP_KERNEL); + if (!cache) + return; + + cache->left = snd_soc_component_read32(component, WM8993_LEFT_OUTPUT_VOLUME); + cache->left &= WM8993_HPOUT1L_VOL_MASK; + + cache->right = snd_soc_component_read32(component, WM8993_RIGHT_OUTPUT_VOLUME); + cache->right &= WM8993_HPOUT1R_VOL_MASK; + + cache->dcs_cfg = dcs_cfg; + + list_add_tail(&cache->list, &hubs->dcs_cache); +} + +static int wm_hubs_read_dc_servo(struct snd_soc_component *component, + u16 *reg_l, u16 *reg_r) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + u16 dcs_reg, reg; + int ret = 0; + + switch (hubs->dcs_readback_mode) { + case 2: + dcs_reg = WM8994_DC_SERVO_4E; + break; + case 1: + dcs_reg = WM8994_DC_SERVO_READBACK; + break; + default: + dcs_reg = WM8993_DC_SERVO_3; + break; + } + + /* Different chips in the family support different readback + * methods. + */ + switch (hubs->dcs_readback_mode) { + case 0: + *reg_l = snd_soc_component_read32(component, WM8993_DC_SERVO_READBACK_1) + & WM8993_DCS_INTEG_CHAN_0_MASK; + *reg_r = snd_soc_component_read32(component, WM8993_DC_SERVO_READBACK_2) + & WM8993_DCS_INTEG_CHAN_1_MASK; + break; + case 2: + case 1: + reg = snd_soc_component_read32(component, dcs_reg); + *reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK) + >> WM8993_DCS_DAC_WR_VAL_1_SHIFT; + *reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK; + break; + default: + WARN(1, "Unknown DCS readback method\n"); + ret = -1; + } + return ret; +} + +/* + * Startup calibration of the DC servo + */ +static void enable_dc_servo(struct snd_soc_component *component) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + struct wm_hubs_dcs_cache *cache; + s8 offset; + u16 reg_l, reg_r, dcs_cfg, dcs_reg; + + switch (hubs->dcs_readback_mode) { + case 2: + dcs_reg = WM8994_DC_SERVO_4E; + break; + default: + dcs_reg = WM8993_DC_SERVO_3; + break; + } + + /* If we're using a digital only path and have a previously + * callibrated DC servo offset stored then use that. */ + if (wm_hubs_dac_hp_direct(component) && + wm_hubs_dcs_cache_get(component, &cache)) { + dev_dbg(component->dev, "Using cached DCS offset %x for %d,%d\n", + cache->dcs_cfg, cache->left, cache->right); + snd_soc_component_write(component, dcs_reg, cache->dcs_cfg); + wait_for_dc_servo(component, + WM8993_DCS_TRIG_DAC_WR_0 | + WM8993_DCS_TRIG_DAC_WR_1); + return; + } + + if (hubs->series_startup) { + /* Set for 32 series updates */ + snd_soc_component_update_bits(component, WM8993_DC_SERVO_1, + WM8993_DCS_SERIES_NO_01_MASK, + 32 << WM8993_DCS_SERIES_NO_01_SHIFT); + wait_for_dc_servo(component, + WM8993_DCS_TRIG_SERIES_0 | + WM8993_DCS_TRIG_SERIES_1); + } else { + wait_for_dc_servo(component, + WM8993_DCS_TRIG_STARTUP_0 | + WM8993_DCS_TRIG_STARTUP_1); + } + + if (wm_hubs_read_dc_servo(component, ®_l, ®_r) < 0) + return; + + dev_dbg(component->dev, "DCS input: %x %x\n", reg_l, reg_r); + + /* Apply correction to DC servo result */ + if (hubs->dcs_codes_l || hubs->dcs_codes_r) { + dev_dbg(component->dev, + "Applying %d/%d code DC servo correction\n", + hubs->dcs_codes_l, hubs->dcs_codes_r); + + /* HPOUT1R */ + offset = (s8)reg_r; + dev_dbg(component->dev, "DCS right %d->%d\n", offset, + offset + hubs->dcs_codes_r); + offset += hubs->dcs_codes_r; + dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT; + + /* HPOUT1L */ + offset = (s8)reg_l; + dev_dbg(component->dev, "DCS left %d->%d\n", offset, + offset + hubs->dcs_codes_l); + offset += hubs->dcs_codes_l; + dcs_cfg |= (u8)offset; + + dev_dbg(component->dev, "DCS result: %x\n", dcs_cfg); + + /* Do it */ + snd_soc_component_write(component, dcs_reg, dcs_cfg); + wait_for_dc_servo(component, + WM8993_DCS_TRIG_DAC_WR_0 | + WM8993_DCS_TRIG_DAC_WR_1); + } else { + dcs_cfg = reg_r << WM8993_DCS_DAC_WR_VAL_1_SHIFT; + dcs_cfg |= reg_l; + } + + /* Save the callibrated offset if we're in class W mode and + * therefore don't have any analogue signal mixed in. */ + if (wm_hubs_dac_hp_direct(component)) + wm_hubs_dcs_cache_set(component, dcs_cfg); +} + +/* + * Update the DC servo calibration on gain changes + */ +static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + /* If we're applying an offset correction then updating the + * callibration would be likely to introduce further offsets. */ + if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update) + return ret; + + /* Only need to do this if the outputs are active */ + if (snd_soc_component_read32(component, WM8993_POWER_MANAGEMENT_1) + & (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA)) + snd_soc_component_update_bits(component, + WM8993_DC_SERVO_0, + WM8993_DCS_TRIG_SINGLE_0 | + WM8993_DCS_TRIG_SINGLE_1, + WM8993_DCS_TRIG_SINGLE_0 | + WM8993_DCS_TRIG_SINGLE_1); + + return ret; +} + +static const struct snd_kcontrol_new analogue_snd_controls[] = { +SOC_SINGLE_TLV("IN1L Volume", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN1L Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1L ZC Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("IN1R Volume", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN1R Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1R ZC Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 6, 1, 0), + + +SOC_SINGLE_TLV("IN2L Volume", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN2L Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2L ZC Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("IN2R Volume", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN2R Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2R ZC Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8993_INPUT_MIXER3, 7, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINL IN1L Volume", WM8993_INPUT_MIXER3, 4, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINL Output Record Volume", WM8993_INPUT_MIXER3, 0, 7, 0, + inmix_tlv), +SOC_SINGLE_TLV("MIXINL IN1LP Volume", WM8993_INPUT_MIXER5, 6, 7, 0, inmix_tlv), +SOC_SINGLE_TLV("MIXINL Direct Voice Volume", WM8993_INPUT_MIXER5, 0, 6, 0, + inmix_tlv), + +SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8993_INPUT_MIXER4, 7, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINR IN1R Volume", WM8993_INPUT_MIXER4, 4, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINR Output Record Volume", WM8993_INPUT_MIXER4, 0, 7, 0, + inmix_tlv), +SOC_SINGLE_TLV("MIXINR IN1RP Volume", WM8993_INPUT_MIXER6, 6, 7, 0, inmix_tlv), +SOC_SINGLE_TLV("MIXINR Direct Voice Volume", WM8993_INPUT_MIXER6, 0, 6, 0, + inmix_tlv), + +SOC_SINGLE_TLV("Left Output Mixer IN2RN Volume", WM8993_OUTPUT_MIXER5, 6, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN2LN Volume", WM8993_OUTPUT_MIXER3, 6, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN2LP Volume", WM8993_OUTPUT_MIXER3, 9, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN1L Volume", WM8993_OUTPUT_MIXER3, 0, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN1R Volume", WM8993_OUTPUT_MIXER3, 3, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer Right Input Volume", + WM8993_OUTPUT_MIXER5, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer Left Input Volume", + WM8993_OUTPUT_MIXER5, 0, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer DAC Volume", WM8993_OUTPUT_MIXER5, 9, 7, 1, + outmix_tlv), + +SOC_SINGLE_TLV("Right Output Mixer IN2LN Volume", + WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN2RN Volume", + WM8993_OUTPUT_MIXER4, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN1L Volume", + WM8993_OUTPUT_MIXER4, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN1R Volume", + WM8993_OUTPUT_MIXER4, 0, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume", + WM8993_OUTPUT_MIXER4, 9, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer Left Input Volume", + WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer Right Input Volume", + WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer DAC Volume", + WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv), + +SOC_DOUBLE_R_TLV("Output Volume", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 0, 63, 0, outpga_tlv), +SOC_DOUBLE_R("Output Switch", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 6, 1, 0), +SOC_DOUBLE_R("Output ZC Switch", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 7, 1, 0), + +SOC_SINGLE("Earpiece Switch", WM8993_HPOUT2_VOLUME, 5, 1, 1), +SOC_SINGLE_TLV("Earpiece Volume", WM8993_HPOUT2_VOLUME, 4, 1, 1, earpiece_tlv), + +SOC_SINGLE_TLV("SPKL Input Volume", WM8993_SPKMIXL_ATTENUATION, + 5, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKL IN1LP Volume", WM8993_SPKMIXL_ATTENUATION, + 4, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKL Output Volume", WM8993_SPKMIXL_ATTENUATION, + 3, 1, 1, wm_hubs_spkmix_tlv), + +SOC_SINGLE_TLV("SPKR Input Volume", WM8993_SPKMIXR_ATTENUATION, + 5, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKR IN1RP Volume", WM8993_SPKMIXR_ATTENUATION, + 4, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKR Output Volume", WM8993_SPKMIXR_ATTENUATION, + 3, 1, 1, wm_hubs_spkmix_tlv), + +SOC_DOUBLE_R_TLV("Speaker Mixer Volume", + WM8993_SPKMIXL_ATTENUATION, WM8993_SPKMIXR_ATTENUATION, + 0, 3, 1, spkmixout_tlv), +SOC_DOUBLE_R_TLV("Speaker Volume", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 0, 63, 0, outpga_tlv), +SOC_DOUBLE_R("Speaker Switch", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R("Speaker ZC Switch", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 7, 1, 0), +SOC_DOUBLE_TLV("Speaker Boost Volume", WM8993_SPKOUT_BOOST, 3, 0, 7, 0, + spkboost_tlv), +SOC_ENUM("Speaker Reference", speaker_ref), +SOC_ENUM("Speaker Mode", speaker_mode), + +SOC_DOUBLE_R_EXT_TLV("Headphone Volume", + WM8993_LEFT_OUTPUT_VOLUME, WM8993_RIGHT_OUTPUT_VOLUME, + 0, 63, 0, snd_soc_get_volsw, wm8993_put_dc_servo, + outpga_tlv), + +SOC_DOUBLE_R("Headphone Switch", WM8993_LEFT_OUTPUT_VOLUME, + WM8993_RIGHT_OUTPUT_VOLUME, 6, 1, 0), +SOC_DOUBLE_R("Headphone ZC Switch", WM8993_LEFT_OUTPUT_VOLUME, + WM8993_RIGHT_OUTPUT_VOLUME, 7, 1, 0), + +SOC_SINGLE("LINEOUT1N Switch", WM8993_LINE_OUTPUTS_VOLUME, 6, 1, 1), +SOC_SINGLE("LINEOUT1P Switch", WM8993_LINE_OUTPUTS_VOLUME, 5, 1, 1), +SOC_SINGLE_TLV("LINEOUT1 Volume", WM8993_LINE_OUTPUTS_VOLUME, 4, 1, 1, + line_tlv), + +SOC_SINGLE("LINEOUT2N Switch", WM8993_LINE_OUTPUTS_VOLUME, 2, 1, 1), +SOC_SINGLE("LINEOUT2P Switch", WM8993_LINE_OUTPUTS_VOLUME, 1, 1, 1), +SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1, + line_tlv), +}; + +static int hp_supply_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + switch (hubs->hp_startup_mode) { + case 0: + break; + case 1: + /* Enable the headphone amp */ + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | + WM8993_HPOUT1R_ENA, + WM8993_HPOUT1L_ENA | + WM8993_HPOUT1R_ENA); + + /* Enable the second stage */ + snd_soc_component_update_bits(component, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY, + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY); + break; + default: + dev_err(component->dev, "Unknown HP startup mode %d\n", + hubs->hp_startup_mode); + break; + } + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, WM8993_CHARGE_PUMP_1, + WM8993_CP_ENA, 0); + break; + } + + return 0; +} + +static int hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int reg = snd_soc_component_read32(component, WM8993_ANALOGUE_HP_0); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_component_update_bits(component, WM8993_CHARGE_PUMP_1, + WM8993_CP_ENA, WM8993_CP_ENA); + + msleep(5); + + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA); + + reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY; + snd_soc_component_write(component, WM8993_ANALOGUE_HP_0, reg); + + snd_soc_component_update_bits(component, WM8993_DC_SERVO_1, + WM8993_DCS_TIMER_PERIOD_01_MASK, 0); + + enable_dc_servo(component); + + reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT | + WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT; + snd_soc_component_write(component, WM8993_ANALOGUE_HP_0, reg); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1L_OUTP | + WM8993_HPOUT1R_OUTP | + WM8993_HPOUT1L_RMV_SHORT | + WM8993_HPOUT1R_RMV_SHORT, 0); + + snd_soc_component_update_bits(component, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY, 0); + + snd_soc_component_write(component, WM8993_DC_SERVO_0, 0); + + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, + 0); + break; + } + + return 0; +} + +static int earpiece_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + u16 reg = snd_soc_component_read32(component, WM8993_ANTIPOP1) & ~WM8993_HPOUT2_IN_ENA; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + reg |= WM8993_HPOUT2_IN_ENA; + snd_soc_component_write(component, WM8993_ANTIPOP1, reg); + udelay(50); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_component_write(component, WM8993_ANTIPOP1, reg); + break; + + default: + WARN(1, "Invalid event %d\n", event); + break; + } + + return 0; +} + +static int lineout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + bool *flag; + + switch (w->shift) { + case WM8993_LINEOUT1N_ENA_SHIFT: + flag = &hubs->lineout1n_ena; + break; + case WM8993_LINEOUT1P_ENA_SHIFT: + flag = &hubs->lineout1p_ena; + break; + case WM8993_LINEOUT2N_ENA_SHIFT: + flag = &hubs->lineout2n_ena; + break; + case WM8993_LINEOUT2P_ENA_SHIFT: + flag = &hubs->lineout2p_ena; + break; + default: + WARN(1, "Unknown line output"); + return -EINVAL; + } + + *flag = SND_SOC_DAPM_EVENT_ON(event); + + return 0; +} + +static int micbias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + + switch (w->shift) { + case WM8993_MICB1_ENA_SHIFT: + if (hubs->micb1_delay) + msleep(hubs->micb1_delay); + break; + case WM8993_MICB2_ENA_SHIFT: + if (hubs->micb2_delay) + msleep(hubs->micb2_delay); + break; + default: + return -EINVAL; + } + + return 0; +} + +void wm_hubs_update_class_w(struct snd_soc_component *component) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + int enable = WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ; + + if (!wm_hubs_dac_hp_direct(component)) + enable = false; + + if (hubs->check_class_w_digital && !hubs->check_class_w_digital(component)) + enable = false; + + dev_vdbg(component->dev, "Class W %s\n", enable ? "enabled" : "disabled"); + + snd_soc_component_update_bits(component, WM8993_CLASS_W_0, + WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ, enable); + + snd_soc_component_write(component, WM8993_LEFT_OUTPUT_VOLUME, + snd_soc_component_read32(component, WM8993_LEFT_OUTPUT_VOLUME)); + snd_soc_component_write(component, WM8993_RIGHT_OUTPUT_VOLUME, + snd_soc_component_read32(component, WM8993_RIGHT_OUTPUT_VOLUME)); +} +EXPORT_SYMBOL_GPL(wm_hubs_update_class_w); + +#define WM_HUBS_SINGLE_W(xname, reg, shift, max, invert) \ + SOC_SINGLE_EXT(xname, reg, shift, max, invert, \ + snd_soc_dapm_get_volsw, class_w_put_volsw) + +static int class_w_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + int ret; + + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + wm_hubs_update_class_w(component); + + return ret; +} + +#define WM_HUBS_ENUM_W(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = snd_soc_dapm_get_enum_double, \ + .put = class_w_put_double, \ + .private_value = (unsigned long)&xenum } + +static int class_w_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol); + int ret; + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + wm_hubs_update_class_w(component); + + return ret; +} + +static const char *hp_mux_text[] = { + "Mixer", + "DAC", +}; + +static SOC_ENUM_SINGLE_DECL(hpl_enum, + WM8993_OUTPUT_MIXER1, 8, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpl_mux = + WM_HUBS_ENUM_W("Left Headphone Mux", hpl_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpl_mux); + +static SOC_ENUM_SINGLE_DECL(hpr_enum, + WM8993_OUTPUT_MIXER2, 8, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpr_mux = + WM_HUBS_ENUM_W("Right Headphone Mux", hpr_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpr_mux); + +static const struct snd_kcontrol_new in1l_pga[] = { +SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0), +SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0), +}; + +static const struct snd_kcontrol_new in1r_pga[] = { +SOC_DAPM_SINGLE("IN1RP Switch", WM8993_INPUT_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("IN1RN Switch", WM8993_INPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new in2l_pga[] = { +SOC_DAPM_SINGLE("IN2LP Switch", WM8993_INPUT_MIXER2, 7, 1, 0), +SOC_DAPM_SINGLE("IN2LN Switch", WM8993_INPUT_MIXER2, 6, 1, 0), +}; + +static const struct snd_kcontrol_new in2r_pga[] = { +SOC_DAPM_SINGLE("IN2RP Switch", WM8993_INPUT_MIXER2, 3, 1, 0), +SOC_DAPM_SINGLE("IN2RN Switch", WM8993_INPUT_MIXER2, 2, 1, 0), +}; + +static const struct snd_kcontrol_new mixinl[] = { +SOC_DAPM_SINGLE("IN2L Switch", WM8993_INPUT_MIXER3, 8, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_INPUT_MIXER3, 5, 1, 0), +}; + +static const struct snd_kcontrol_new mixinr[] = { +SOC_DAPM_SINGLE("IN2R Switch", WM8993_INPUT_MIXER4, 8, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0), +}; + +static const struct snd_kcontrol_new left_output_mixer[] = { +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), +WM_HUBS_SINGLE_W("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), +WM_HUBS_SINGLE_W("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new earpiece_mixer[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_HPOUT2_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Left Output Switch", WM8993_HPOUT2_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_HPOUT2_MIXER, 3, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_boost[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 5, 1, 0), +SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 4, 1, 0), +SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 3, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_boost[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 2, 1, 0), +SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 1, 1, 0), +SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line1_mix[] = { +SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER1, 1, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line1n_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER1, 5, 1, 0), +}; + +static const struct snd_kcontrol_new line1p_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line2_mix[] = { +SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line2n_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER2, 5, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 6, 1, 0), +}; + +static const struct snd_kcontrol_new line2p_mix[] = { +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget analogue_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1LN"), +SND_SOC_DAPM_INPUT("IN1LP"), +SND_SOC_DAPM_INPUT("IN2LN"), +SND_SOC_DAPM_INPUT("IN2LP:VXRN"), +SND_SOC_DAPM_INPUT("IN1RN"), +SND_SOC_DAPM_INPUT("IN1RP"), +SND_SOC_DAPM_INPUT("IN2RN"), +SND_SOC_DAPM_INPUT("IN2RP:VXRP"), + +SND_SOC_DAPM_SUPPLY("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0, + micbias_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_SUPPLY("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0, + micbias_event, SND_SOC_DAPM_POST_PMU), + +SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0, + in1l_pga, ARRAY_SIZE(in1l_pga)), +SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0, + in1r_pga, ARRAY_SIZE(in1r_pga)), + +SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0, + in2l_pga, ARRAY_SIZE(in2l_pga)), +SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0, + in2r_pga, ARRAY_SIZE(in2r_pga)), + +SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0, + mixinl, ARRAY_SIZE(mixinl)), +SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0, + mixinr, ARRAY_SIZE(mixinr)), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + +SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("Headphone Supply", SND_SOC_NOPM, 0, 0, hp_supply_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_OUT_DRV_E("Headphone PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, + earpiece_mixer, ARRAY_SIZE(earpiece_mixer)), +SND_SOC_DAPM_PGA_E("Earpiece Driver", WM8993_POWER_MANAGEMENT_1, 11, 0, + NULL, 0, earpiece_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_MIXER("SPKL Boost", SND_SOC_NOPM, 0, 0, + left_speaker_boost, ARRAY_SIZE(left_speaker_boost)), +SND_SOC_DAPM_MIXER("SPKR Boost", SND_SOC_NOPM, 0, 0, + right_speaker_boost, ARRAY_SIZE(right_speaker_boost)), + +SND_SOC_DAPM_SUPPLY("TSHUT", WM8993_POWER_MANAGEMENT_2, 14, 0, NULL, 0), +SND_SOC_DAPM_OUT_DRV("SPKL Driver", WM8993_POWER_MANAGEMENT_1, 12, 0, + NULL, 0), +SND_SOC_DAPM_OUT_DRV("SPKR Driver", WM8993_POWER_MANAGEMENT_1, 13, 0, + NULL, 0), + +SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0, + line1_mix, ARRAY_SIZE(line1_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0, + line2_mix, ARRAY_SIZE(line2_mix)), + +SND_SOC_DAPM_MIXER("LINEOUT1N Mixer", SND_SOC_NOPM, 0, 0, + line1n_mix, ARRAY_SIZE(line1n_mix)), +SND_SOC_DAPM_MIXER("LINEOUT1P Mixer", SND_SOC_NOPM, 0, 0, + line1p_mix, ARRAY_SIZE(line1p_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2N Mixer", SND_SOC_NOPM, 0, 0, + line2n_mix, ARRAY_SIZE(line2n_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2P Mixer", SND_SOC_NOPM, 0, 0, + line2p_mix, ARRAY_SIZE(line2p_mix)), + +SND_SOC_DAPM_OUT_DRV_E("LINEOUT1N Driver", WM8993_POWER_MANAGEMENT_3, 13, 0, + NULL, 0, lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_OUT_DRV_E("LINEOUT1P Driver", WM8993_POWER_MANAGEMENT_3, 12, 0, + NULL, 0, lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_OUT_DRV_E("LINEOUT2N Driver", WM8993_POWER_MANAGEMENT_3, 11, 0, + NULL, 0, lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_OUT_DRV_E("LINEOUT2P Driver", WM8993_POWER_MANAGEMENT_3, 10, 0, + NULL, 0, lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("SPKOUTLP"), +SND_SOC_DAPM_OUTPUT("SPKOUTLN"), +SND_SOC_DAPM_OUTPUT("SPKOUTRP"), +SND_SOC_DAPM_OUTPUT("SPKOUTRN"), +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("HPOUT2P"), +SND_SOC_DAPM_OUTPUT("HPOUT2N"), +SND_SOC_DAPM_OUTPUT("LINEOUT1P"), +SND_SOC_DAPM_OUTPUT("LINEOUT1N"), +SND_SOC_DAPM_OUTPUT("LINEOUT2P"), +SND_SOC_DAPM_OUTPUT("LINEOUT2N"), +}; + +static const struct snd_soc_dapm_route analogue_routes[] = { + { "MICBIAS1", NULL, "CLK_SYS" }, + { "MICBIAS2", NULL, "CLK_SYS" }, + + { "IN1L PGA", "IN1LP Switch", "IN1LP" }, + { "IN1L PGA", "IN1LN Switch", "IN1LN" }, + + { "IN1L PGA", NULL, "VMID" }, + { "IN1R PGA", NULL, "VMID" }, + { "IN2L PGA", NULL, "VMID" }, + { "IN2R PGA", NULL, "VMID" }, + + { "IN1R PGA", "IN1RP Switch", "IN1RP" }, + { "IN1R PGA", "IN1RN Switch", "IN1RN" }, + + { "IN2L PGA", "IN2LP Switch", "IN2LP:VXRN" }, + { "IN2L PGA", "IN2LN Switch", "IN2LN" }, + + { "IN2R PGA", "IN2RP Switch", "IN2RP:VXRP" }, + { "IN2R PGA", "IN2RN Switch", "IN2RN" }, + + { "Direct Voice", NULL, "IN2LP:VXRN" }, + { "Direct Voice", NULL, "IN2RP:VXRP" }, + + { "MIXINL", "IN1L Switch", "IN1L PGA" }, + { "MIXINL", "IN2L Switch", "IN2L PGA" }, + { "MIXINL", NULL, "Direct Voice" }, + { "MIXINL", NULL, "IN1LP" }, + { "MIXINL", NULL, "Left Output Mixer" }, + { "MIXINL", NULL, "VMID" }, + + { "MIXINR", "IN1R Switch", "IN1R PGA" }, + { "MIXINR", "IN2R Switch", "IN2R PGA" }, + { "MIXINR", NULL, "Direct Voice" }, + { "MIXINR", NULL, "IN1RP" }, + { "MIXINR", NULL, "Right Output Mixer" }, + { "MIXINR", NULL, "VMID" }, + + { "ADCL", NULL, "MIXINL" }, + { "ADCR", NULL, "MIXINR" }, + + { "Left Output Mixer", "Left Input Switch", "MIXINL" }, + { "Left Output Mixer", "Right Input Switch", "MIXINR" }, + { "Left Output Mixer", "IN2RN Switch", "IN2RN" }, + { "Left Output Mixer", "IN2LN Switch", "IN2LN" }, + { "Left Output Mixer", "IN2LP Switch", "IN2LP:VXRN" }, + { "Left Output Mixer", "IN1L Switch", "IN1L PGA" }, + { "Left Output Mixer", "IN1R Switch", "IN1R PGA" }, + + { "Right Output Mixer", "Left Input Switch", "MIXINL" }, + { "Right Output Mixer", "Right Input Switch", "MIXINR" }, + { "Right Output Mixer", "IN2LN Switch", "IN2LN" }, + { "Right Output Mixer", "IN2RN Switch", "IN2RN" }, + { "Right Output Mixer", "IN2RP Switch", "IN2RP:VXRP" }, + { "Right Output Mixer", "IN1L Switch", "IN1L PGA" }, + { "Right Output Mixer", "IN1R Switch", "IN1R PGA" }, + + { "Left Output PGA", NULL, "Left Output Mixer" }, + { "Left Output PGA", NULL, "TOCLK" }, + + { "Right Output PGA", NULL, "Right Output Mixer" }, + { "Right Output PGA", NULL, "TOCLK" }, + + { "Earpiece Mixer", "Direct Voice Switch", "Direct Voice" }, + { "Earpiece Mixer", "Left Output Switch", "Left Output PGA" }, + { "Earpiece Mixer", "Right Output Switch", "Right Output PGA" }, + + { "Earpiece Driver", NULL, "VMID" }, + { "Earpiece Driver", NULL, "Earpiece Mixer" }, + { "HPOUT2N", NULL, "Earpiece Driver" }, + { "HPOUT2P", NULL, "Earpiece Driver" }, + + { "SPKL", "Input Switch", "MIXINL" }, + { "SPKL", "IN1LP Switch", "IN1LP" }, + { "SPKL", "Output Switch", "Left Output PGA" }, + { "SPKL", NULL, "TOCLK" }, + + { "SPKR", "Input Switch", "MIXINR" }, + { "SPKR", "IN1RP Switch", "IN1RP" }, + { "SPKR", "Output Switch", "Right Output PGA" }, + { "SPKR", NULL, "TOCLK" }, + + { "SPKL Boost", "Direct Voice Switch", "Direct Voice" }, + { "SPKL Boost", "SPKL Switch", "SPKL" }, + { "SPKL Boost", "SPKR Switch", "SPKR" }, + + { "SPKR Boost", "Direct Voice Switch", "Direct Voice" }, + { "SPKR Boost", "SPKR Switch", "SPKR" }, + { "SPKR Boost", "SPKL Switch", "SPKL" }, + + { "SPKL Driver", NULL, "VMID" }, + { "SPKL Driver", NULL, "SPKL Boost" }, + { "SPKL Driver", NULL, "CLK_SYS" }, + { "SPKL Driver", NULL, "TSHUT" }, + + { "SPKR Driver", NULL, "VMID" }, + { "SPKR Driver", NULL, "SPKR Boost" }, + { "SPKR Driver", NULL, "CLK_SYS" }, + { "SPKR Driver", NULL, "TSHUT" }, + + { "SPKOUTLP", NULL, "SPKL Driver" }, + { "SPKOUTLN", NULL, "SPKL Driver" }, + { "SPKOUTRP", NULL, "SPKR Driver" }, + { "SPKOUTRN", NULL, "SPKR Driver" }, + + { "Left Headphone Mux", "Mixer", "Left Output PGA" }, + { "Right Headphone Mux", "Mixer", "Right Output PGA" }, + + { "Headphone PGA", NULL, "Left Headphone Mux" }, + { "Headphone PGA", NULL, "Right Headphone Mux" }, + { "Headphone PGA", NULL, "VMID" }, + { "Headphone PGA", NULL, "CLK_SYS" }, + { "Headphone PGA", NULL, "Headphone Supply" }, + + { "HPOUT1L", NULL, "Headphone PGA" }, + { "HPOUT1R", NULL, "Headphone PGA" }, + + { "LINEOUT1N Driver", NULL, "VMID" }, + { "LINEOUT1P Driver", NULL, "VMID" }, + { "LINEOUT2N Driver", NULL, "VMID" }, + { "LINEOUT2P Driver", NULL, "VMID" }, + + { "LINEOUT1N", NULL, "LINEOUT1N Driver" }, + { "LINEOUT1P", NULL, "LINEOUT1P Driver" }, + { "LINEOUT2N", NULL, "LINEOUT2N Driver" }, + { "LINEOUT2P", NULL, "LINEOUT2P Driver" }, +}; + +static const struct snd_soc_dapm_route lineout1_diff_routes[] = { + { "LINEOUT1 Mixer", "IN1L Switch", "IN1L PGA" }, + { "LINEOUT1 Mixer", "IN1R Switch", "IN1R PGA" }, + { "LINEOUT1 Mixer", "Output Switch", "Left Output PGA" }, + + { "LINEOUT1N Driver", NULL, "LINEOUT1 Mixer" }, + { "LINEOUT1P Driver", NULL, "LINEOUT1 Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout1_se_routes[] = { + { "LINEOUT1N Mixer", "Left Output Switch", "Left Output PGA" }, + { "LINEOUT1N Mixer", "Right Output Switch", "Right Output PGA" }, + + { "LINEOUT1P Mixer", "Left Output Switch", "Left Output PGA" }, + + { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" }, + { "LINEOUT1P Driver", NULL, "LINEOUT1P Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout2_diff_routes[] = { + { "LINEOUT2 Mixer", "IN1L Switch", "IN1L PGA" }, + { "LINEOUT2 Mixer", "IN1R Switch", "IN1R PGA" }, + { "LINEOUT2 Mixer", "Output Switch", "Right Output PGA" }, + + { "LINEOUT2N Driver", NULL, "LINEOUT2 Mixer" }, + { "LINEOUT2P Driver", NULL, "LINEOUT2 Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout2_se_routes[] = { + { "LINEOUT2N Mixer", "Left Output Switch", "Left Output PGA" }, + { "LINEOUT2N Mixer", "Right Output Switch", "Right Output PGA" }, + + { "LINEOUT2P Mixer", "Right Output Switch", "Right Output PGA" }, + + { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" }, + { "LINEOUT2P Driver", NULL, "LINEOUT2P Mixer" }, +}; + +int wm_hubs_add_analogue_controls(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + /* Latch volume update bits & default ZC on */ + snd_soc_component_update_bits(component, WM8993_LEFT_LINE_INPUT_1_2_VOLUME, + WM8993_IN1_VU, WM8993_IN1_VU); + snd_soc_component_update_bits(component, WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8993_IN1_VU, WM8993_IN1_VU); + snd_soc_component_update_bits(component, WM8993_LEFT_LINE_INPUT_3_4_VOLUME, + WM8993_IN2_VU, WM8993_IN2_VU); + snd_soc_component_update_bits(component, WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8993_IN2_VU, WM8993_IN2_VU); + + snd_soc_component_update_bits(component, WM8993_SPEAKER_VOLUME_LEFT, + WM8993_SPKOUT_VU, WM8993_SPKOUT_VU); + snd_soc_component_update_bits(component, WM8993_SPEAKER_VOLUME_RIGHT, + WM8993_SPKOUT_VU, WM8993_SPKOUT_VU); + + snd_soc_component_update_bits(component, WM8993_LEFT_OUTPUT_VOLUME, + WM8993_HPOUT1_VU | WM8993_HPOUT1L_ZC, + WM8993_HPOUT1_VU | WM8993_HPOUT1L_ZC); + snd_soc_component_update_bits(component, WM8993_RIGHT_OUTPUT_VOLUME, + WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC, + WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC); + + snd_soc_component_update_bits(component, WM8993_LEFT_OPGA_VOLUME, + WM8993_MIXOUTL_ZC | WM8993_MIXOUT_VU, + WM8993_MIXOUTL_ZC | WM8993_MIXOUT_VU); + snd_soc_component_update_bits(component, WM8993_RIGHT_OPGA_VOLUME, + WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU, + WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU); + + snd_soc_add_component_controls(component, analogue_snd_controls, + ARRAY_SIZE(analogue_snd_controls)); + + snd_soc_dapm_new_controls(dapm, analogue_dapm_widgets, + ARRAY_SIZE(analogue_dapm_widgets)); + return 0; +} +EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls); + +int wm_hubs_add_analogue_routes(struct snd_soc_component *component, + int lineout1_diff, int lineout2_diff) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + hubs->component = component; + + INIT_LIST_HEAD(&hubs->dcs_cache); + init_completion(&hubs->dcs_done); + + snd_soc_dapm_add_routes(dapm, analogue_routes, + ARRAY_SIZE(analogue_routes)); + + if (lineout1_diff) + snd_soc_dapm_add_routes(dapm, + lineout1_diff_routes, + ARRAY_SIZE(lineout1_diff_routes)); + else + snd_soc_dapm_add_routes(dapm, + lineout1_se_routes, + ARRAY_SIZE(lineout1_se_routes)); + + if (lineout2_diff) + snd_soc_dapm_add_routes(dapm, + lineout2_diff_routes, + ARRAY_SIZE(lineout2_diff_routes)); + else + snd_soc_dapm_add_routes(dapm, + lineout2_se_routes, + ARRAY_SIZE(lineout2_se_routes)); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_routes); + +int wm_hubs_handle_analogue_pdata(struct snd_soc_component *component, + int lineout1_diff, int lineout2_diff, + int lineout1fb, int lineout2fb, + int jd_scthr, int jd_thr, + int micbias1_delay, int micbias2_delay, + int micbias1_lvl, int micbias2_lvl) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + + hubs->lineout1_se = !lineout1_diff; + hubs->lineout2_se = !lineout2_diff; + hubs->micb1_delay = micbias1_delay; + hubs->micb2_delay = micbias2_delay; + + if (!lineout1_diff) + snd_soc_component_update_bits(component, WM8993_LINE_MIXER1, + WM8993_LINEOUT1_MODE, + WM8993_LINEOUT1_MODE); + if (!lineout2_diff) + snd_soc_component_update_bits(component, WM8993_LINE_MIXER2, + WM8993_LINEOUT2_MODE, + WM8993_LINEOUT2_MODE); + + if (!lineout1_diff && !lineout2_diff) + snd_soc_component_update_bits(component, WM8993_ANTIPOP1, + WM8993_LINEOUT_VMID_BUF_ENA, + WM8993_LINEOUT_VMID_BUF_ENA); + + if (lineout1fb) + snd_soc_component_update_bits(component, WM8993_ADDITIONAL_CONTROL, + WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB); + + if (lineout2fb) + snd_soc_component_update_bits(component, WM8993_ADDITIONAL_CONTROL, + WM8993_LINEOUT2_FB, WM8993_LINEOUT2_FB); + + if (!hubs->micd_scthr) + return 0; + + snd_soc_component_update_bits(component, WM8993_MICBIAS, + WM8993_JD_SCTHR_MASK | WM8993_JD_THR_MASK | + WM8993_MICB1_LVL | WM8993_MICB2_LVL, + jd_scthr << WM8993_JD_SCTHR_SHIFT | + jd_thr << WM8993_JD_THR_SHIFT | + micbias1_lvl | + micbias2_lvl << WM8993_MICB2_LVL_SHIFT); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_hubs_handle_analogue_pdata); + +void wm_hubs_vmid_ena(struct snd_soc_component *component) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + int val = 0; + + if (hubs->lineout1_se) + val |= WM8993_LINEOUT1N_ENA | WM8993_LINEOUT1P_ENA; + + if (hubs->lineout2_se) + val |= WM8993_LINEOUT2N_ENA | WM8993_LINEOUT2P_ENA; + + /* Enable the line outputs while we power up */ + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_3, val, val); +} +EXPORT_SYMBOL_GPL(wm_hubs_vmid_ena); + +void wm_hubs_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct wm_hubs_data *hubs = snd_soc_component_get_drvdata(component); + int mask, val; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + /* Clamp the inputs to VMID while we ramp to charge caps */ + snd_soc_component_update_bits(component, WM8993_INPUTS_CLAMP_REG, + WM8993_INPUTS_CLAMP, WM8993_INPUTS_CLAMP); + break; + + case SND_SOC_BIAS_ON: + /* Turn off any unneeded single ended outputs */ + val = 0; + mask = 0; + + if (hubs->lineout1_se) + mask |= WM8993_LINEOUT1N_ENA | WM8993_LINEOUT1P_ENA; + + if (hubs->lineout2_se) + mask |= WM8993_LINEOUT2N_ENA | WM8993_LINEOUT2P_ENA; + + if (hubs->lineout1_se && hubs->lineout1n_ena) + val |= WM8993_LINEOUT1N_ENA; + + if (hubs->lineout1_se && hubs->lineout1p_ena) + val |= WM8993_LINEOUT1P_ENA; + + if (hubs->lineout2_se && hubs->lineout2n_ena) + val |= WM8993_LINEOUT2N_ENA; + + if (hubs->lineout2_se && hubs->lineout2p_ena) + val |= WM8993_LINEOUT2P_ENA; + + snd_soc_component_update_bits(component, WM8993_POWER_MANAGEMENT_3, + mask, val); + + /* Remove the input clamps */ + snd_soc_component_update_bits(component, WM8993_INPUTS_CLAMP_REG, + WM8993_INPUTS_CLAMP, 0); + break; + + default: + break; + } +} +EXPORT_SYMBOL_GPL(wm_hubs_set_bias_level); + +MODULE_DESCRIPTION("Shared support for Wolfson hubs products"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h new file mode 100644 index 000000000..1433d73e0 --- /dev/null +++ b/sound/soc/codecs/wm_hubs.h @@ -0,0 +1,75 @@ +/* + * wm_hubs.h -- WM899x common code + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM_HUBS_H +#define _WM_HUBS_H + +#include +#include +#include +#include + +struct snd_soc_component; + +extern const unsigned int wm_hubs_spkmix_tlv[]; + +/* This *must* be the first element of the codec->private_data struct */ +struct wm_hubs_data { + int dcs_codes_l; + int dcs_codes_r; + int dcs_readback_mode; + int hp_startup_mode; + int series_startup; + int no_series_update; + bool micd_scthr; + + bool no_cache_dac_hp_direct; + struct list_head dcs_cache; + bool (*check_class_w_digital)(struct snd_soc_component *); + + int micb1_delay; + int micb2_delay; + + bool lineout1_se; + bool lineout1n_ena; + bool lineout1p_ena; + + bool lineout2_se; + bool lineout2n_ena; + bool lineout2p_ena; + + bool dcs_done_irq; + struct completion dcs_done; + + struct snd_soc_component *component; +}; + +extern int wm_hubs_add_analogue_controls(struct snd_soc_component *); +extern int wm_hubs_add_analogue_routes(struct snd_soc_component *, int, int); +extern int wm_hubs_handle_analogue_pdata(struct snd_soc_component *, + int lineout1_diff, int lineout2_diff, + int lineout1fb, int lineout2fb, + int jd_scthr, int jd_thr, + int micbias1_dly, int micbias2_dly, + int micbias1_lvl, int micbias2_lvl); + +extern irqreturn_t wm_hubs_dcs_done(int irq, void *data); +extern void wm_hubs_vmid_ena(struct snd_soc_component *component); +extern void wm_hubs_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level); +extern void wm_hubs_update_class_w(struct snd_soc_component *component); + +extern const struct snd_kcontrol_new wm_hubs_hpl_mux; +extern const struct snd_kcontrol_new wm_hubs_hpr_mux; + +#endif diff --git a/sound/soc/codecs/wmfw.h b/sound/soc/codecs/wmfw.h new file mode 100644 index 000000000..0c3f50acb --- /dev/null +++ b/sound/soc/codecs/wmfw.h @@ -0,0 +1,172 @@ +/* + * wmfw.h - Wolfson firmware format information + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __WMFW_H +#define __WMFW_H + +#include + +#define WMFW_MAX_ALG_NAME 256 +#define WMFW_MAX_ALG_DESCR_NAME 256 + +#define WMFW_MAX_COEFF_NAME 256 +#define WMFW_MAX_COEFF_DESCR_NAME 256 + +#define WMFW_CTL_FLAG_SYS 0x8000 +#define WMFW_CTL_FLAG_VOLATILE 0x0004 +#define WMFW_CTL_FLAG_WRITEABLE 0x0002 +#define WMFW_CTL_FLAG_READABLE 0x0001 + +/* Non-ALSA coefficient types start at 0x1000 */ +#define WMFW_CTL_TYPE_ACKED 0x1000 /* acked control */ +#define WMFW_CTL_TYPE_HOSTEVENT 0x1001 /* event control */ +#define WMFW_CTL_TYPE_HOST_BUFFER 0x1002 /* host buffer pointer */ + +struct wmfw_header { + char magic[4]; + __le32 len; + __le16 rev; + u8 core; + u8 ver; +} __packed; + +struct wmfw_footer { + __le64 timestamp; + __le32 checksum; +} __packed; + +struct wmfw_adsp1_sizes { + __le32 dm; + __le32 pm; + __le32 zm; +} __packed; + +struct wmfw_adsp2_sizes { + __le32 xm; + __le32 ym; + __le32 pm; + __le32 zm; +} __packed; + +struct wmfw_region { + union { + __be32 type; + __le32 offset; + }; + __le32 len; + u8 data[]; +} __packed; + +struct wmfw_id_hdr { + __be32 core_id; + __be32 core_rev; + __be32 id; + __be32 ver; +} __packed; + +struct wmfw_adsp1_id_hdr { + struct wmfw_id_hdr fw; + __be32 zm; + __be32 dm; + __be32 n_algs; +} __packed; + +struct wmfw_adsp2_id_hdr { + struct wmfw_id_hdr fw; + __be32 zm; + __be32 xm; + __be32 ym; + __be32 n_algs; +} __packed; + +struct wmfw_alg_hdr { + __be32 id; + __be32 ver; +} __packed; + +struct wmfw_adsp1_alg_hdr { + struct wmfw_alg_hdr alg; + __be32 zm; + __be32 dm; +} __packed; + +struct wmfw_adsp2_alg_hdr { + struct wmfw_alg_hdr alg; + __be32 zm; + __be32 xm; + __be32 ym; +} __packed; + +struct wmfw_adsp_alg_data { + __le32 id; + u8 name[WMFW_MAX_ALG_NAME]; + u8 descr[WMFW_MAX_ALG_DESCR_NAME]; + __le32 ncoeff; + u8 data[]; +} __packed; + +struct wmfw_adsp_coeff_data { + struct { + __le16 offset; + __le16 type; + __le32 size; + } hdr; + u8 name[WMFW_MAX_COEFF_NAME]; + u8 descr[WMFW_MAX_COEFF_DESCR_NAME]; + __le16 ctl_type; + __le16 flags; + __le32 len; + u8 data[]; +} __packed; + +struct wmfw_coeff_hdr { + u8 magic[4]; + __le32 len; + union { + __be32 rev; + __le32 ver; + }; + union { + __be32 core; + __le32 core_ver; + }; + u8 data[]; +} __packed; + +struct wmfw_coeff_item { + __le16 offset; + __le16 type; + __le32 id; + __le32 ver; + __le32 sr; + __le32 len; + u8 data[]; +} __packed; + +#define WMFW_ADSP1 1 +#define WMFW_ADSP2 2 + +#define WMFW_ABSOLUTE 0xf0 +#define WMFW_ALGORITHM_DATA 0xf2 +#define WMFW_NAME_TEXT 0xfe +#define WMFW_INFO_TEXT 0xff + +#define WMFW_ADSP1_PM 2 +#define WMFW_ADSP1_DM 3 +#define WMFW_ADSP1_ZM 4 + +#define WMFW_ADSP2_PM 2 +#define WMFW_ADSP2_ZM 4 +#define WMFW_ADSP2_XM 5 +#define WMFW_ADSP2_YM 6 + +#endif diff --git a/sound/soc/codecs/zx_aud96p22.c b/sound/soc/codecs/zx_aud96p22.c new file mode 100644 index 000000000..7a2d6eaf1 --- /dev/null +++ b/sound/soc/codecs/zx_aud96p22.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + * + * Author: Baoyou Xie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AUD96P22_RESET 0x00 +#define RST_DAC_DPZ BIT(0) +#define RST_ADC_DPZ BIT(1) +#define AUD96P22_I2S1_CONFIG_0 0x03 +#define I2S1_MS_MODE BIT(3) +#define I2S1_MODE_MASK 0x7 +#define I2S1_MODE_RIGHT_J 0x0 +#define I2S1_MODE_I2S 0x1 +#define I2S1_MODE_LEFT_J 0x2 +#define AUD96P22_PD_0 0x15 +#define AUD96P22_PD_1 0x16 +#define AUD96P22_PD_3 0x18 +#define AUD96P22_PD_4 0x19 +#define AUD96P22_MUTE_0 0x1d +#define AUD96P22_MUTE_2 0x1f +#define AUD96P22_MUTE_4 0x21 +#define AUD96P22_RECVOL_0 0x24 +#define AUD96P22_RECVOL_1 0x25 +#define AUD96P22_PGA1VOL_0 0x26 +#define AUD96P22_PGA1VOL_1 0x27 +#define AUD96P22_LMVOL_0 0x34 +#define AUD96P22_LMVOL_1 0x35 +#define AUD96P22_HS1VOL_0 0x38 +#define AUD96P22_HS1VOL_1 0x39 +#define AUD96P22_PGA1SEL_0 0x47 +#define AUD96P22_PGA1SEL_1 0x48 +#define AUD96P22_LDR1SEL_0 0x59 +#define AUD96P22_LDR1SEL_1 0x60 +#define AUD96P22_LDR2SEL_0 0x5d +#define AUD96P22_REG_MAX 0xfb + +struct aud96p22_priv { + struct regmap *regmap; +}; + +static int aud96p22_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aud96p22_priv *priv = snd_soc_component_get_drvdata(component); + struct regmap *regmap = priv->regmap; + + if (event != SND_SOC_DAPM_POST_PMU) + return -EINVAL; + + /* Assert/de-assert the bit to reset ADC data path */ + regmap_update_bits(regmap, AUD96P22_RESET, RST_ADC_DPZ, 0); + regmap_update_bits(regmap, AUD96P22_RESET, RST_ADC_DPZ, RST_ADC_DPZ); + + return 0; +} + +static int aud96p22_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aud96p22_priv *priv = snd_soc_component_get_drvdata(component); + struct regmap *regmap = priv->regmap; + + if (event != SND_SOC_DAPM_POST_PMU) + return -EINVAL; + + /* Assert/de-assert the bit to reset DAC data path */ + regmap_update_bits(regmap, AUD96P22_RESET, RST_DAC_DPZ, 0); + regmap_update_bits(regmap, AUD96P22_RESET, RST_DAC_DPZ, RST_DAC_DPZ); + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(lm_tlv, -11550, 50, 0); +static const DECLARE_TLV_DB_SCALE(hs_tlv, -3900, 300, 0); +static const DECLARE_TLV_DB_SCALE(rec_tlv, -9550, 50, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1800, 100, 0); + +static const struct snd_kcontrol_new aud96p22_snd_controls[] = { + /* Volume control */ + SOC_DOUBLE_R_TLV("Master Playback Volume", AUD96P22_LMVOL_0, + AUD96P22_LMVOL_1, 0, 0xff, 0, lm_tlv), + SOC_DOUBLE_R_TLV("Headphone Volume", AUD96P22_HS1VOL_0, + AUD96P22_HS1VOL_1, 0, 0xf, 0, hs_tlv), + SOC_DOUBLE_R_TLV("Master Capture Volume", AUD96P22_RECVOL_0, + AUD96P22_RECVOL_1, 0, 0xff, 0, rec_tlv), + SOC_DOUBLE_R_TLV("Analogue Capture Volume", AUD96P22_PGA1VOL_0, + AUD96P22_PGA1VOL_1, 0, 0x37, 0, pga_tlv), + + /* Mute control */ + SOC_DOUBLE("Master Playback Switch", AUD96P22_MUTE_2, 0, 1, 1, 1), + SOC_DOUBLE("Headphone Switch", AUD96P22_MUTE_2, 4, 5, 1, 1), + SOC_DOUBLE("Line Out Switch", AUD96P22_MUTE_4, 0, 1, 1, 1), + SOC_DOUBLE("Speaker Switch", AUD96P22_MUTE_4, 2, 3, 1, 1), + SOC_DOUBLE("Master Capture Switch", AUD96P22_MUTE_0, 0, 1, 1, 1), + SOC_DOUBLE("Analogue Capture Switch", AUD96P22_MUTE_0, 2, 3, 1, 1), +}; + +/* Input mux kcontrols */ +static const unsigned int ain_mux_values[] = { + 0, 1, 3, 4, 5, +}; + +static const char * const ainl_mux_texts[] = { + "AINL1 differential", + "AINL1 single-ended", + "AINL3 single-ended", + "AINL2 differential", + "AINL2 single-ended", +}; + +static const char * const ainr_mux_texts[] = { + "AINR1 differential", + "AINR1 single-ended", + "AINR3 single-ended", + "AINR2 differential", + "AINR2 single-ended", +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(ainl_mux_enum, AUD96P22_PGA1SEL_0, + 0, 0x7, ainl_mux_texts, ain_mux_values); +static SOC_VALUE_ENUM_SINGLE_DECL(ainr_mux_enum, AUD96P22_PGA1SEL_1, + 0, 0x7, ainr_mux_texts, ain_mux_values); + +static const struct snd_kcontrol_new ainl_mux_kcontrol = + SOC_DAPM_ENUM("AINL Mux", ainl_mux_enum); +static const struct snd_kcontrol_new ainr_mux_kcontrol = + SOC_DAPM_ENUM("AINR Mux", ainr_mux_enum); + +/* Output mixer kcontrols */ +static const struct snd_kcontrol_new ld1_left_kcontrols[] = { + SOC_DAPM_SINGLE("DACL LD1L Switch", AUD96P22_LDR1SEL_0, 0, 1, 0), + SOC_DAPM_SINGLE("AINL LD1L Switch", AUD96P22_LDR1SEL_0, 1, 1, 0), + SOC_DAPM_SINGLE("AINR LD1L Switch", AUD96P22_LDR1SEL_0, 2, 1, 0), +}; + +static const struct snd_kcontrol_new ld1_right_kcontrols[] = { + SOC_DAPM_SINGLE("DACR LD1R Switch", AUD96P22_LDR1SEL_1, 8, 1, 0), + SOC_DAPM_SINGLE("AINR LD1R Switch", AUD96P22_LDR1SEL_1, 9, 1, 0), + SOC_DAPM_SINGLE("AINL LD1R Switch", AUD96P22_LDR1SEL_1, 10, 1, 0), +}; + +static const struct snd_kcontrol_new ld2_kcontrols[] = { + SOC_DAPM_SINGLE("DACL LD2 Switch", AUD96P22_LDR2SEL_0, 0, 1, 0), + SOC_DAPM_SINGLE("AINL LD2 Switch", AUD96P22_LDR2SEL_0, 1, 1, 0), + SOC_DAPM_SINGLE("DACR LD2 Switch", AUD96P22_LDR2SEL_0, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget aud96p22_dapm_widgets[] = { + /* Overall power bit */ + SND_SOC_DAPM_SUPPLY("POWER", AUD96P22_PD_0, 0, 0, NULL, 0), + + /* Input pins */ + SND_SOC_DAPM_INPUT("AINL1P"), + SND_SOC_DAPM_INPUT("AINL2P"), + SND_SOC_DAPM_INPUT("AINL3"), + SND_SOC_DAPM_INPUT("AINL1N"), + SND_SOC_DAPM_INPUT("AINL2N"), + SND_SOC_DAPM_INPUT("AINR2N"), + SND_SOC_DAPM_INPUT("AINR1N"), + SND_SOC_DAPM_INPUT("AINR3"), + SND_SOC_DAPM_INPUT("AINR2P"), + SND_SOC_DAPM_INPUT("AINR1P"), + + /* Input muxes */ + SND_SOC_DAPM_MUX("AINLMUX", AUD96P22_PD_1, 2, 0, &ainl_mux_kcontrol), + SND_SOC_DAPM_MUX("AINRMUX", AUD96P22_PD_1, 3, 0, &ainr_mux_kcontrol), + + /* ADCs */ + SND_SOC_DAPM_ADC_E("ADCL", "Capture Left", AUD96P22_PD_1, 0, 0, + aud96p22_adc_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_ADC_E("ADCR", "Capture Right", AUD96P22_PD_1, 1, 0, + aud96p22_adc_event, SND_SOC_DAPM_POST_PMU), + + /* DACs */ + SND_SOC_DAPM_DAC_E("DACL", "Playback Left", AUD96P22_PD_3, 0, 0, + aud96p22_dac_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_DAC_E("DACR", "Playback Right", AUD96P22_PD_3, 1, 0, + aud96p22_dac_event, SND_SOC_DAPM_POST_PMU), + + /* Output mixers */ + SND_SOC_DAPM_MIXER("LD1L", AUD96P22_PD_3, 6, 0, ld1_left_kcontrols, + ARRAY_SIZE(ld1_left_kcontrols)), + SND_SOC_DAPM_MIXER("LD1R", AUD96P22_PD_3, 7, 0, ld1_right_kcontrols, + ARRAY_SIZE(ld1_right_kcontrols)), + SND_SOC_DAPM_MIXER("LD2", AUD96P22_PD_4, 2, 0, ld2_kcontrols, + ARRAY_SIZE(ld2_kcontrols)), + + /* Headset power switch */ + SND_SOC_DAPM_SUPPLY("HS1L", AUD96P22_PD_3, 4, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HS1R", AUD96P22_PD_3, 5, 0, NULL, 0), + + /* Output pins */ + SND_SOC_DAPM_OUTPUT("HSOUTL"), + SND_SOC_DAPM_OUTPUT("LINEOUTL"), + SND_SOC_DAPM_OUTPUT("LINEOUTMP"), + SND_SOC_DAPM_OUTPUT("LINEOUTMN"), + SND_SOC_DAPM_OUTPUT("LINEOUTR"), + SND_SOC_DAPM_OUTPUT("HSOUTR"), +}; + +static const struct snd_soc_dapm_route aud96p22_dapm_routes[] = { + { "AINLMUX", "AINL1 differential", "AINL1N" }, + { "AINLMUX", "AINL1 single-ended", "AINL1P" }, + { "AINLMUX", "AINL3 single-ended", "AINL3" }, + { "AINLMUX", "AINL2 differential", "AINL2N" }, + { "AINLMUX", "AINL2 single-ended", "AINL2P" }, + + { "AINRMUX", "AINR1 differential", "AINR1N" }, + { "AINRMUX", "AINR1 single-ended", "AINR1P" }, + { "AINRMUX", "AINR3 single-ended", "AINR3" }, + { "AINRMUX", "AINR2 differential", "AINR2N" }, + { "AINRMUX", "AINR2 single-ended", "AINR2P" }, + + { "ADCL", NULL, "AINLMUX" }, + { "ADCR", NULL, "AINRMUX" }, + + { "ADCL", NULL, "POWER" }, + { "ADCR", NULL, "POWER" }, + { "DACL", NULL, "POWER" }, + { "DACR", NULL, "POWER" }, + + { "LD1L", "DACL LD1L Switch", "DACL" }, + { "LD1L", "AINL LD1L Switch", "AINLMUX" }, + { "LD1L", "AINR LD1L Switch", "AINRMUX" }, + + { "LD1R", "DACR LD1R Switch", "DACR" }, + { "LD1R", "AINR LD1R Switch", "AINRMUX" }, + { "LD1R", "AINL LD1R Switch", "AINLMUX" }, + + { "LD2", "DACL LD2 Switch", "DACL" }, + { "LD2", "AINL LD2 Switch", "AINLMUX" }, + { "LD2", "DACR LD2 Switch", "DACR" }, + + { "HSOUTL", NULL, "LD1L" }, + { "HSOUTR", NULL, "LD1R" }, + { "HSOUTL", NULL, "HS1L" }, + { "HSOUTR", NULL, "HS1R" }, + + { "LINEOUTL", NULL, "LD1L" }, + { "LINEOUTR", NULL, "LD1R" }, + + { "LINEOUTMP", NULL, "LD2" }, + { "LINEOUTMN", NULL, "LD2" }, +}; + +static const struct snd_soc_component_driver aud96p22_driver = { + .controls = aud96p22_snd_controls, + .num_controls = ARRAY_SIZE(aud96p22_snd_controls), + .dapm_widgets = aud96p22_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aud96p22_dapm_widgets), + .dapm_routes = aud96p22_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(aud96p22_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int aud96p22_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct aud96p22_priv *priv = snd_soc_component_get_drvdata(dai->component); + struct regmap *regmap = priv->regmap; + unsigned int val; + + /* Master/slave mode */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + val = I2S1_MS_MODE; + break; + default: + return -EINVAL; + } + + regmap_update_bits(regmap, AUD96P22_I2S1_CONFIG_0, I2S1_MS_MODE, val); + + /* Audio format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S1_MODE_RIGHT_J; + break; + case SND_SOC_DAIFMT_I2S: + val = I2S1_MODE_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S1_MODE_LEFT_J; + break; + default: + return -EINVAL; + } + + regmap_update_bits(regmap, AUD96P22_I2S1_CONFIG_0, I2S1_MODE_MASK, val); + + return 0; +} + +static const struct snd_soc_dai_ops aud96p22_dai_ops = { + .set_fmt = aud96p22_set_fmt, +}; + +#define AUD96P22_RATES SNDRV_PCM_RATE_8000_192000 +#define AUD96P22_FORMATS (\ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver aud96p22_dai = { + .name = "aud96p22-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AUD96P22_RATES, + .formats = AUD96P22_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AUD96P22_RATES, + .formats = AUD96P22_FORMATS, + }, + .ops = &aud96p22_dai_ops, +}; + +static const struct regmap_config aud96p22_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AUD96P22_REG_MAX, + .cache_type = REGCACHE_RBTREE, +}; + +static int aud96p22_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct aud96p22_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(i2c, &aud96p22_regmap); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(dev, "failed to init i2c regmap: %d\n", ret); + return ret; + } + + i2c_set_clientdata(i2c, priv); + + ret = devm_snd_soc_register_component(dev, &aud96p22_driver, &aud96p22_dai, 1); + if (ret) { + dev_err(dev, "failed to register component: %d\n", ret); + return ret; + } + + return 0; +} + +static int aud96p22_i2c_remove(struct i2c_client *i2c) +{ + return 0; +} + +static const struct of_device_id aud96p22_dt_ids[] = { + { .compatible = "zte,zx-aud96p22", }, + { } +}; +MODULE_DEVICE_TABLE(of, aud96p22_dt_ids); + +static struct i2c_driver aud96p22_i2c_driver = { + .driver = { + .name = "zx_aud96p22", + .of_match_table = aud96p22_dt_ids, + }, + .probe = aud96p22_i2c_probe, + .remove = aud96p22_i2c_remove, +}; +module_i2c_driver(aud96p22_i2c_driver); + +MODULE_DESCRIPTION("ZTE ASoC AUD96P22 CODEC driver"); +MODULE_AUTHOR("Baoyou Xie "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig new file mode 100644 index 000000000..778faff28 --- /dev/null +++ b/sound/soc/davinci/Kconfig @@ -0,0 +1,106 @@ +config SND_DAVINCI_SOC + tristate + depends on ARCH_DAVINCI + select SND_EDMA_SOC + +config SND_EDMA_SOC + tristate "SoC Audio for Texas Instruments chips using eDMA" + depends on TI_EDMA + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M here if you want audio support for TI SoC which uses eDMA. + The following line of SoCs are supported by this platform driver: + - daVinci devices + - AM335x + - AM437x/AM438x + - DRA7xx family + +config SND_DAVINCI_SOC_I2S + tristate "DaVinci Multichannel Buffered Serial Port (McBSP) support" + depends on SND_EDMA_SOC + help + Say Y or M here if you want to have support for McBSP IP found in + Texas Instruments DaVinci DA850 SoCs. + +config SND_DAVINCI_SOC_MCASP + tristate "Multichannel Audio Serial Port (McASP) support" + depends on SND_SDMA_SOC || SND_EDMA_SOC + help + Say Y or M here if you want to have support for McASP IP found in + various Texas Instruments SoCs like: + - daVinci devices + - Sitara line of SoCs (AM335x, AM438x, etc) + - DRA7x devices + +config SND_DAVINCI_SOC_VCIF + tristate + +config SND_DAVINCI_SOC_GENERIC_EVM + tristate + select SND_SOC_TLV320AIC3X + select SND_DAVINCI_SOC_MCASP + +config SND_AM33XX_SOC_EVM + tristate "SoC Audio for the AM33XX chip based boards" + depends on SND_EDMA_SOC && SOC_AM33XX && I2C + select SND_DAVINCI_SOC_GENERIC_EVM + help + Say Y or M if you want to add support for SoC audio on AM33XX + boards using McASP and TLV320AIC3X codec. For example AM335X-EVM, + AM335X-EVMSK, and BeagelBone with AudioCape boards have this + setup. + +config SND_DAVINCI_SOC_EVM + tristate "SoC Audio support for DaVinci DM6446, DM355 or DM365 EVM" + depends on SND_EDMA_SOC && I2C + depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM + select SND_DAVINCI_SOC_GENERIC_EVM + help + Say Y if you want to add support for SoC audio on TI + DaVinci DM6446, DM355 or DM365 EVM platforms. + +choice + prompt "DM365 codec select" + depends on SND_DAVINCI_SOC_EVM + depends on MACH_DAVINCI_DM365_EVM + +config SND_DM365_AIC3X_CODEC + tristate "Audio Codec - AIC3101" + help + Say Y if you want to add support for AIC3101 audio codec + +config SND_DM365_VOICE_CODEC + tristate "Voice Codec - CQ93VC" + select MFD_DAVINCI_VOICECODEC + select SND_DAVINCI_SOC_VCIF + select SND_SOC_CQ0093VC + help + Say Y if you want to add support for SoC On-chip voice codec +endchoice + +config SND_DM6467_SOC_EVM + tristate "SoC Audio support for DaVinci DM6467 EVM" + depends on SND_EDMA_SOC && MACH_DAVINCI_DM6467_EVM && I2C + select SND_DAVINCI_SOC_GENERIC_EVM + select SND_SOC_SPDIF + + help + Say Y if you want to add support for SoC audio on TI + +config SND_DA830_SOC_EVM + tristate "SoC Audio support for DA830/OMAP-L137 EVM" + depends on SND_EDMA_SOC && MACH_DAVINCI_DA830_EVM && I2C + select SND_DAVINCI_SOC_GENERIC_EVM + + help + Say Y if you want to add support for SoC audio on TI + DA830/OMAP-L137 EVM + +config SND_DA850_SOC_EVM + tristate "SoC Audio support for DA850/OMAP-L138 EVM" + depends on SND_EDMA_SOC && MACH_DAVINCI_DA850_EVM && I2C + select SND_DAVINCI_SOC_GENERIC_EVM + help + Say Y if you want to add support for SoC audio on TI + DA850/OMAP-L138 EVM + diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile new file mode 100644 index 000000000..23c6592eb --- /dev/null +++ b/sound/soc/davinci/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# DAVINCI Platform Support +snd-soc-edma-objs := edma-pcm.o +snd-soc-davinci-i2s-objs := davinci-i2s.o +snd-soc-davinci-mcasp-objs:= davinci-mcasp.o +snd-soc-davinci-vcif-objs:= davinci-vcif.o + +obj-$(CONFIG_SND_EDMA_SOC) += snd-soc-edma.o +obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o +obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o +obj-$(CONFIG_SND_DAVINCI_SOC_VCIF) += snd-soc-davinci-vcif.o + +# Generic DAVINCI/AM33xx Machine Support +snd-soc-evm-objs := davinci-evm.o + +obj-$(CONFIG_SND_DAVINCI_SOC_GENERIC_EVM) += snd-soc-evm.o diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c new file mode 100644 index 000000000..7a369e0f2 --- /dev/null +++ b/sound/soc/davinci/davinci-evm.c @@ -0,0 +1,511 @@ +/* + * ASoC driver for TI DAVINCI EVM platform + * + * Author: Vladimir Barinov, + * Copyright: (C) 2007 MontaVista Software, Inc., + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct snd_soc_card_drvdata_davinci { + struct clk *mclk; + unsigned sysclk; +}; + +static int evm_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *soc_card = rtd->card; + struct snd_soc_card_drvdata_davinci *drvdata = + snd_soc_card_get_drvdata(soc_card); + + if (drvdata->mclk) + return clk_prepare_enable(drvdata->mclk); + + return 0; +} + +static void evm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *soc_card = rtd->card; + struct snd_soc_card_drvdata_davinci *drvdata = + snd_soc_card_get_drvdata(soc_card); + + if (drvdata->mclk) + clk_disable_unprepare(drvdata->mclk); +} + +static int evm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_card *soc_card = rtd->card; + int ret = 0; + unsigned sysclk = ((struct snd_soc_card_drvdata_davinci *) + snd_soc_card_get_drvdata(soc_card))->sysclk; + + /* set the codec system clock */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set the CPU system clock */ + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, sysclk, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops evm_ops = { + .startup = evm_startup, + .shutdown = evm_shutdown, + .hw_params = evm_hw_params, +}; + +/* davinci-evm machine dapm widgets */ +static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +/* davinci-evm machine audio_mapnections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + /* Headphone connected to HPLOUT, HPROUT */ + {"Headphone Jack", NULL, "HPLOUT"}, + {"Headphone Jack", NULL, "HPROUT"}, + + /* Line Out connected to LLOUT, RLOUT */ + {"Line Out", NULL, "LLOUT"}, + {"Line Out", NULL, "RLOUT"}, + + /* Mic connected to (MIC3L | MIC3R) */ + {"MIC3L", NULL, "Mic Bias"}, + {"MIC3R", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic Jack"}, + + /* Line In connected to (LINE1L | LINE2L), (LINE1R | LINE2R) */ + {"LINE1L", NULL, "Line In"}, + {"LINE2L", NULL, "Line In"}, + {"LINE1R", NULL, "Line In"}, + {"LINE2R", NULL, "Line In"}, +}; + +/* Logic for a aic3x as connected on a davinci-evm */ +static int evm_aic3x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct device_node *np = card->dev->of_node; + int ret; + + /* Add davinci-evm specific widgets */ + snd_soc_dapm_new_controls(&card->dapm, aic3x_dapm_widgets, + ARRAY_SIZE(aic3x_dapm_widgets)); + + if (np) { + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); + if (ret) + return ret; + } else { + /* Set up davinci-evm specific audio path audio_map */ + snd_soc_dapm_add_routes(&card->dapm, audio_map, + ARRAY_SIZE(audio_map)); + } + + /* not connected */ + snd_soc_dapm_nc_pin(&card->dapm, "MONO_LOUT"); + snd_soc_dapm_nc_pin(&card->dapm, "HPLCOM"); + snd_soc_dapm_nc_pin(&card->dapm, "HPRCOM"); + + return 0; +} + +/* davinci-evm digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link dm6446_evm_dai = { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai_name = "davinci-mcbsp", + .codec_dai_name = "tlv320aic3x-hifi", + .codec_name = "tlv320aic3x-codec.1-001b", + .platform_name = "davinci-mcbsp", + .init = evm_aic3x_init, + .ops = &evm_ops, + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, +}; + +static struct snd_soc_dai_link dm355_evm_dai = { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai_name = "davinci-mcbsp.1", + .codec_dai_name = "tlv320aic3x-hifi", + .codec_name = "tlv320aic3x-codec.1-001b", + .platform_name = "davinci-mcbsp.1", + .init = evm_aic3x_init, + .ops = &evm_ops, + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, +}; + +static struct snd_soc_dai_link dm365_evm_dai = { +#ifdef CONFIG_SND_DM365_AIC3X_CODEC + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai_name = "davinci-mcbsp", + .codec_dai_name = "tlv320aic3x-hifi", + .codec_name = "tlv320aic3x-codec.1-0018", + .platform_name = "davinci-mcbsp", + .init = evm_aic3x_init, + .ops = &evm_ops, + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, +#elif defined(CONFIG_SND_DM365_VOICE_CODEC) + .name = "Voice Codec - CQ93VC", + .stream_name = "CQ93", + .cpu_dai_name = "davinci-vcif", + .codec_dai_name = "cq93vc-hifi", + .codec_name = "cq93vc-codec", + .platform_name = "davinci-vcif", +#endif +}; + +static struct snd_soc_dai_link dm6467_evm_dai[] = { + { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai_name= "davinci-mcasp.0", + .codec_dai_name = "tlv320aic3x-hifi", + .platform_name = "davinci-mcasp.0", + .codec_name = "tlv320aic3x-codec.0-001a", + .init = evm_aic3x_init, + .ops = &evm_ops, + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, + }, + { + .name = "McASP", + .stream_name = "spdif", + .cpu_dai_name= "davinci-mcasp.1", + .codec_dai_name = "dit-hifi", + .codec_name = "spdif_dit", + .platform_name = "davinci-mcasp.1", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, + }, +}; + +static struct snd_soc_dai_link da830_evm_dai = { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai_name = "davinci-mcasp.1", + .codec_dai_name = "tlv320aic3x-hifi", + .codec_name = "tlv320aic3x-codec.1-0018", + .platform_name = "davinci-mcasp.1", + .init = evm_aic3x_init, + .ops = &evm_ops, + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, +}; + +static struct snd_soc_dai_link da850_evm_dai = { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai_name= "davinci-mcasp.0", + .codec_dai_name = "tlv320aic3x-hifi", + .codec_name = "tlv320aic3x-codec.1-0018", + .platform_name = "davinci-mcasp.0", + .init = evm_aic3x_init, + .ops = &evm_ops, + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, +}; + +/* davinci dm6446 evm audio machine driver */ +/* + * ASP0 in DM6446 EVM is clocked by U55, as configured by + * board-dm644x-evm.c using GPIOs from U18. There are six + * options; here we "know" we use a 48 KHz sample rate. + */ +static struct snd_soc_card_drvdata_davinci dm6446_snd_soc_card_drvdata = { + .sysclk = 12288000, +}; + +static struct snd_soc_card dm6446_snd_soc_card_evm = { + .name = "DaVinci DM6446 EVM", + .owner = THIS_MODULE, + .dai_link = &dm6446_evm_dai, + .num_links = 1, + .drvdata = &dm6446_snd_soc_card_drvdata, +}; + +/* davinci dm355 evm audio machine driver */ +/* ASP1 on DM355 EVM is clocked by an external oscillator */ +static struct snd_soc_card_drvdata_davinci dm355_snd_soc_card_drvdata = { + .sysclk = 27000000, +}; + +static struct snd_soc_card dm355_snd_soc_card_evm = { + .name = "DaVinci DM355 EVM", + .owner = THIS_MODULE, + .dai_link = &dm355_evm_dai, + .num_links = 1, + .drvdata = &dm355_snd_soc_card_drvdata, +}; + +/* davinci dm365 evm audio machine driver */ +static struct snd_soc_card_drvdata_davinci dm365_snd_soc_card_drvdata = { + .sysclk = 27000000, +}; + +static struct snd_soc_card dm365_snd_soc_card_evm = { + .name = "DaVinci DM365 EVM", + .owner = THIS_MODULE, + .dai_link = &dm365_evm_dai, + .num_links = 1, + .drvdata = &dm365_snd_soc_card_drvdata, +}; + +/* davinci dm6467 evm audio machine driver */ +static struct snd_soc_card_drvdata_davinci dm6467_snd_soc_card_drvdata = { + .sysclk = 27000000, +}; + +static struct snd_soc_card dm6467_snd_soc_card_evm = { + .name = "DaVinci DM6467 EVM", + .owner = THIS_MODULE, + .dai_link = dm6467_evm_dai, + .num_links = ARRAY_SIZE(dm6467_evm_dai), + .drvdata = &dm6467_snd_soc_card_drvdata, +}; + +static struct snd_soc_card_drvdata_davinci da830_snd_soc_card_drvdata = { + .sysclk = 24576000, +}; + +static struct snd_soc_card da830_snd_soc_card = { + .name = "DA830/OMAP-L137 EVM", + .owner = THIS_MODULE, + .dai_link = &da830_evm_dai, + .num_links = 1, + .drvdata = &da830_snd_soc_card_drvdata, +}; + +static struct snd_soc_card_drvdata_davinci da850_snd_soc_card_drvdata = { + .sysclk = 24576000, +}; + +static struct snd_soc_card da850_snd_soc_card = { + .name = "DA850/OMAP-L138 EVM", + .owner = THIS_MODULE, + .dai_link = &da850_evm_dai, + .num_links = 1, + .drvdata = &da850_snd_soc_card_drvdata, +}; + +#if defined(CONFIG_OF) + +/* + * The struct is used as place holder. It will be completely + * filled with data from dt node. + */ +static struct snd_soc_dai_link evm_dai_tlv320aic3x = { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .codec_dai_name = "tlv320aic3x-hifi", + .ops = &evm_ops, + .init = evm_aic3x_init, + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_NF, +}; + +static const struct of_device_id davinci_evm_dt_ids[] = { + { + .compatible = "ti,da830-evm-audio", + .data = (void *) &evm_dai_tlv320aic3x, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, davinci_evm_dt_ids); + +/* davinci evm audio machine driver */ +static struct snd_soc_card evm_soc_card = { + .owner = THIS_MODULE, + .num_links = 1, +}; + +static int davinci_evm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + struct snd_soc_dai_link *dai; + struct snd_soc_card_drvdata_davinci *drvdata = NULL; + struct clk *mclk; + int ret = 0; + + match = of_match_device(of_match_ptr(davinci_evm_dt_ids), &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + return -ENODEV; + } + + dai = (struct snd_soc_dai_link *) match->data; + + evm_soc_card.dai_link = dai; + + dai->codec_of_node = of_parse_phandle(np, "ti,audio-codec", 0); + if (!dai->codec_of_node) + return -EINVAL; + + dai->cpu_of_node = of_parse_phandle(np, "ti,mcasp-controller", 0); + if (!dai->cpu_of_node) + return -EINVAL; + + dai->platform_of_node = dai->cpu_of_node; + + evm_soc_card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&evm_soc_card, "ti,model"); + if (ret) + return ret; + + mclk = devm_clk_get(&pdev->dev, "mclk"); + if (PTR_ERR(mclk) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (IS_ERR(mclk)) { + dev_dbg(&pdev->dev, "mclk not found.\n"); + mclk = NULL; + } + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->mclk = mclk; + + ret = of_property_read_u32(np, "ti,codec-clock-rate", &drvdata->sysclk); + + if (ret < 0) { + if (!drvdata->mclk) { + dev_err(&pdev->dev, + "No clock or clock rate defined.\n"); + return -EINVAL; + } + drvdata->sysclk = clk_get_rate(drvdata->mclk); + } else if (drvdata->mclk) { + unsigned int requestd_rate = drvdata->sysclk; + clk_set_rate(drvdata->mclk, drvdata->sysclk); + drvdata->sysclk = clk_get_rate(drvdata->mclk); + if (drvdata->sysclk != requestd_rate) + dev_warn(&pdev->dev, + "Could not get requested rate %u using %u.\n", + requestd_rate, drvdata->sysclk); + } + + snd_soc_card_set_drvdata(&evm_soc_card, drvdata); + ret = devm_snd_soc_register_card(&pdev->dev, &evm_soc_card); + + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + + return ret; +} + +static struct platform_driver davinci_evm_driver = { + .probe = davinci_evm_probe, + .driver = { + .name = "davinci_evm", + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(davinci_evm_dt_ids), + }, +}; +#endif + +static struct platform_device *evm_snd_device; + +static int __init evm_init(void) +{ + struct snd_soc_card *evm_snd_dev_data; + int index; + int ret; + + /* + * If dtb is there, the devices will be created dynamically. + * Only register platfrom driver structure. + */ +#if defined(CONFIG_OF) + if (of_have_populated_dt()) + return platform_driver_register(&davinci_evm_driver); +#endif + + if (machine_is_davinci_evm()) { + evm_snd_dev_data = &dm6446_snd_soc_card_evm; + index = 0; + } else if (machine_is_davinci_dm355_evm()) { + evm_snd_dev_data = &dm355_snd_soc_card_evm; + index = 1; + } else if (machine_is_davinci_dm365_evm()) { + evm_snd_dev_data = &dm365_snd_soc_card_evm; + index = 0; + } else if (machine_is_davinci_dm6467_evm()) { + evm_snd_dev_data = &dm6467_snd_soc_card_evm; + index = 0; + } else if (machine_is_davinci_da830_evm()) { + evm_snd_dev_data = &da830_snd_soc_card; + index = 1; + } else if (machine_is_davinci_da850_evm()) { + evm_snd_dev_data = &da850_snd_soc_card; + index = 0; + } else + return -EINVAL; + + evm_snd_device = platform_device_alloc("soc-audio", index); + if (!evm_snd_device) + return -ENOMEM; + + platform_set_drvdata(evm_snd_device, evm_snd_dev_data); + ret = platform_device_add(evm_snd_device); + if (ret) + platform_device_put(evm_snd_device); + + return ret; +} + +static void __exit evm_exit(void) +{ +#if defined(CONFIG_OF) + if (of_have_populated_dt()) { + platform_driver_unregister(&davinci_evm_driver); + return; + } +#endif + + platform_device_unregister(evm_snd_device); +} + +module_init(evm_init); +module_exit(evm_exit); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("TI DAVINCI EVM ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c new file mode 100644 index 000000000..205841e46 --- /dev/null +++ b/sound/soc/davinci/davinci-i2s.c @@ -0,0 +1,785 @@ +/* + * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor + * + * Author: Vladimir Barinov, + * Copyright: (C) 2007 MontaVista Software, Inc., + * + * DT support (c) 2016 Petr Kulhavy, Barix AG + * based on davinci-mcasp.c DT support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * on DA850 implement HW FIFOs instead of DMA into DXR and DRR registers + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "edma-pcm.h" +#include "davinci-i2s.h" + +#define DRV_NAME "davinci-i2s" + +/* + * NOTE: terminology here is confusing. + * + * - This driver supports the "Audio Serial Port" (ASP), + * found on dm6446, dm355, and other DaVinci chips. + * + * - But it labels it a "Multi-channel Buffered Serial Port" + * (McBSP) as on older chips like the dm642 ... which was + * backward-compatible, possibly explaining that confusion. + * + * - OMAP chips have a controller called McBSP, which is + * incompatible with the DaVinci flavor of McBSP. + * + * - Newer DaVinci chips have a controller called McASP, + * incompatible with ASP and with either McBSP. + * + * In short: this uses ASP to implement I2S, not McBSP. + * And it won't be the only DaVinci implemention of I2S. + */ +#define DAVINCI_MCBSP_DRR_REG 0x00 +#define DAVINCI_MCBSP_DXR_REG 0x04 +#define DAVINCI_MCBSP_SPCR_REG 0x08 +#define DAVINCI_MCBSP_RCR_REG 0x0c +#define DAVINCI_MCBSP_XCR_REG 0x10 +#define DAVINCI_MCBSP_SRGR_REG 0x14 +#define DAVINCI_MCBSP_PCR_REG 0x24 + +#define DAVINCI_MCBSP_SPCR_RRST (1 << 0) +#define DAVINCI_MCBSP_SPCR_RINTM(v) ((v) << 4) +#define DAVINCI_MCBSP_SPCR_XRST (1 << 16) +#define DAVINCI_MCBSP_SPCR_XINTM(v) ((v) << 20) +#define DAVINCI_MCBSP_SPCR_GRST (1 << 22) +#define DAVINCI_MCBSP_SPCR_FRST (1 << 23) +#define DAVINCI_MCBSP_SPCR_FREE (1 << 25) + +#define DAVINCI_MCBSP_RCR_RWDLEN1(v) ((v) << 5) +#define DAVINCI_MCBSP_RCR_RFRLEN1(v) ((v) << 8) +#define DAVINCI_MCBSP_RCR_RDATDLY(v) ((v) << 16) +#define DAVINCI_MCBSP_RCR_RFIG (1 << 18) +#define DAVINCI_MCBSP_RCR_RWDLEN2(v) ((v) << 21) +#define DAVINCI_MCBSP_RCR_RFRLEN2(v) ((v) << 24) +#define DAVINCI_MCBSP_RCR_RPHASE BIT(31) + +#define DAVINCI_MCBSP_XCR_XWDLEN1(v) ((v) << 5) +#define DAVINCI_MCBSP_XCR_XFRLEN1(v) ((v) << 8) +#define DAVINCI_MCBSP_XCR_XDATDLY(v) ((v) << 16) +#define DAVINCI_MCBSP_XCR_XFIG (1 << 18) +#define DAVINCI_MCBSP_XCR_XWDLEN2(v) ((v) << 21) +#define DAVINCI_MCBSP_XCR_XFRLEN2(v) ((v) << 24) +#define DAVINCI_MCBSP_XCR_XPHASE BIT(31) + +#define DAVINCI_MCBSP_SRGR_FWID(v) ((v) << 8) +#define DAVINCI_MCBSP_SRGR_FPER(v) ((v) << 16) +#define DAVINCI_MCBSP_SRGR_FSGM (1 << 28) +#define DAVINCI_MCBSP_SRGR_CLKSM BIT(29) + +#define DAVINCI_MCBSP_PCR_CLKRP (1 << 0) +#define DAVINCI_MCBSP_PCR_CLKXP (1 << 1) +#define DAVINCI_MCBSP_PCR_FSRP (1 << 2) +#define DAVINCI_MCBSP_PCR_FSXP (1 << 3) +#define DAVINCI_MCBSP_PCR_SCLKME (1 << 7) +#define DAVINCI_MCBSP_PCR_CLKRM (1 << 8) +#define DAVINCI_MCBSP_PCR_CLKXM (1 << 9) +#define DAVINCI_MCBSP_PCR_FSRM (1 << 10) +#define DAVINCI_MCBSP_PCR_FSXM (1 << 11) + +enum { + DAVINCI_MCBSP_WORD_8 = 0, + DAVINCI_MCBSP_WORD_12, + DAVINCI_MCBSP_WORD_16, + DAVINCI_MCBSP_WORD_20, + DAVINCI_MCBSP_WORD_24, + DAVINCI_MCBSP_WORD_32, +}; + +static const unsigned char data_type[SNDRV_PCM_FORMAT_S32_LE + 1] = { + [SNDRV_PCM_FORMAT_S8] = 1, + [SNDRV_PCM_FORMAT_S16_LE] = 2, + [SNDRV_PCM_FORMAT_S32_LE] = 4, +}; + +static const unsigned char asp_word_length[SNDRV_PCM_FORMAT_S32_LE + 1] = { + [SNDRV_PCM_FORMAT_S8] = DAVINCI_MCBSP_WORD_8, + [SNDRV_PCM_FORMAT_S16_LE] = DAVINCI_MCBSP_WORD_16, + [SNDRV_PCM_FORMAT_S32_LE] = DAVINCI_MCBSP_WORD_32, +}; + +static const unsigned char double_fmt[SNDRV_PCM_FORMAT_S32_LE + 1] = { + [SNDRV_PCM_FORMAT_S8] = SNDRV_PCM_FORMAT_S16_LE, + [SNDRV_PCM_FORMAT_S16_LE] = SNDRV_PCM_FORMAT_S32_LE, +}; + +struct davinci_mcbsp_dev { + struct device *dev; + struct snd_dmaengine_dai_dma_data dma_data[2]; + int dma_request[2]; + void __iomem *base; +#define MOD_DSP_A 0 +#define MOD_DSP_B 1 + int mode; + u32 pcr; + struct clk *clk; + /* + * Combining both channels into 1 element will at least double the + * amount of time between servicing the dma channel, increase + * effiency, and reduce the chance of overrun/underrun. But, + * it will result in the left & right channels being swapped. + * + * If relabeling the left and right channels is not possible, + * you may want to let the codec know to swap them back. + * + * It may allow x10 the amount of time to service dma requests, + * if the codec is master and is using an unnecessarily fast bit clock + * (ie. tlvaic23b), independent of the sample rate. So, having an + * entire frame at once means it can be serviced at the sample rate + * instead of the bit clock rate. + * + * In the now unlikely case that an underrun still + * occurs, both the left and right samples will be repeated + * so that no pops are heard, and the left and right channels + * won't end up being swapped because of the underrun. + */ + unsigned enable_channel_combine:1; + + unsigned int fmt; + int clk_div; + int clk_input_pin; + bool i2s_accurate_sck; +}; + +static inline void davinci_mcbsp_write_reg(struct davinci_mcbsp_dev *dev, + int reg, u32 val) +{ + __raw_writel(val, dev->base + reg); +} + +static inline u32 davinci_mcbsp_read_reg(struct davinci_mcbsp_dev *dev, int reg) +{ + return __raw_readl(dev->base + reg); +} + +static void toggle_clock(struct davinci_mcbsp_dev *dev, int playback) +{ + u32 m = playback ? DAVINCI_MCBSP_PCR_CLKXP : DAVINCI_MCBSP_PCR_CLKRP; + /* The clock needs to toggle to complete reset. + * So, fake it by toggling the clk polarity. + */ + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr ^ m); + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr); +} + +static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u32 spcr; + u32 mask = playback ? DAVINCI_MCBSP_SPCR_XRST : DAVINCI_MCBSP_SPCR_RRST; + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + if (spcr & mask) { + /* start off disabled */ + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, + spcr & ~mask); + toggle_clock(dev, playback); + } + if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM | + DAVINCI_MCBSP_PCR_CLKXM | DAVINCI_MCBSP_PCR_CLKRM)) { + /* Start the sample generator */ + spcr |= DAVINCI_MCBSP_SPCR_GRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + } + + if (playback) { + /* Stop the DMA to avoid data loss */ + /* while the transmitter is out of reset to handle XSYNCERR */ + if (component->driver->ops->trigger) { + int ret = component->driver->ops->trigger(substream, + SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + printk(KERN_DEBUG "Playback DMA stop failed\n"); + } + + /* Enable the transmitter */ + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr |= DAVINCI_MCBSP_SPCR_XRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + + /* wait for any unexpected frame sync error to occur */ + udelay(100); + + /* Disable the transmitter to clear any outstanding XSYNCERR */ + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr &= ~DAVINCI_MCBSP_SPCR_XRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + toggle_clock(dev, playback); + + /* Restart the DMA */ + if (component->driver->ops->trigger) { + int ret = component->driver->ops->trigger(substream, + SNDRV_PCM_TRIGGER_START); + if (ret < 0) + printk(KERN_DEBUG "Playback DMA start failed\n"); + } + } + + /* Enable transmitter or receiver */ + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr |= mask; + + if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM)) { + /* Start frame sync */ + spcr |= DAVINCI_MCBSP_SPCR_FRST; + } + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); +} + +static void davinci_mcbsp_stop(struct davinci_mcbsp_dev *dev, int playback) +{ + u32 spcr; + + /* Reset transmitter/receiver and sample rate/frame sync generators */ + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr &= ~(DAVINCI_MCBSP_SPCR_GRST | DAVINCI_MCBSP_SPCR_FRST); + spcr &= playback ? ~DAVINCI_MCBSP_SPCR_XRST : ~DAVINCI_MCBSP_SPCR_RRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + toggle_clock(dev, playback); +} + +#define DEFAULT_BITPERSAMPLE 16 + +static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + unsigned int pcr; + unsigned int srgr; + bool inv_fs = false; + /* Attention srgr is updated by hw_params! */ + srgr = DAVINCI_MCBSP_SRGR_FSGM | + DAVINCI_MCBSP_SRGR_FPER(DEFAULT_BITPERSAMPLE * 2 - 1) | + DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1); + + dev->fmt = fmt; + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* cpu is master */ + pcr = DAVINCI_MCBSP_PCR_FSXM | + DAVINCI_MCBSP_PCR_FSRM | + DAVINCI_MCBSP_PCR_CLKXM | + DAVINCI_MCBSP_PCR_CLKRM; + break; + case SND_SOC_DAIFMT_CBM_CFS: + pcr = DAVINCI_MCBSP_PCR_FSRM | DAVINCI_MCBSP_PCR_FSXM; + /* + * Selection of the clock input pin that is the + * input for the Sample Rate Generator. + * McBSP FSR and FSX are driven by the Sample Rate + * Generator. + */ + switch (dev->clk_input_pin) { + case MCBSP_CLKS: + pcr |= DAVINCI_MCBSP_PCR_CLKXM | + DAVINCI_MCBSP_PCR_CLKRM; + break; + case MCBSP_CLKR: + pcr |= DAVINCI_MCBSP_PCR_SCLKME; + break; + default: + dev_err(dev->dev, "bad clk_input_pin\n"); + return -EINVAL; + } + + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master */ + pcr = 0; + break; + default: + printk(KERN_ERR "%s:bad master\n", __func__); + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* Davinci doesn't support TRUE I2S, but some codecs will have + * the left and right channels contiguous. This allows + * dsp_a mode to be used with an inverted normal frame clk. + * If your codec is master and does not have contiguous + * channels, then you will have sound on only one channel. + * Try using a different mode, or codec as slave. + * + * The TLV320AIC33 is an example of a codec where this works. + * It has a variable bit clock frequency allowing it to have + * valid data on every bit clock. + * + * The TLV320AIC23 is an example of a codec where this does not + * work. It has a fixed bit clock frequency with progressively + * more empty bit clock slots between channels as the sample + * rate is lowered. + */ + inv_fs = true; + /* fall through */ + case SND_SOC_DAIFMT_DSP_A: + dev->mode = MOD_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + dev->mode = MOD_DSP_B; + break; + default: + printk(KERN_ERR "%s:bad format\n", __func__); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* CLKRP Receive clock polarity, + * 1 - sampled on rising edge of CLKR + * valid on rising edge + * CLKXP Transmit clock polarity, + * 1 - clocked on falling edge of CLKX + * valid on rising edge + * FSRP Receive frame sync pol, 0 - active high + * FSXP Transmit frame sync pol, 0 - active high + */ + pcr |= (DAVINCI_MCBSP_PCR_CLKXP | DAVINCI_MCBSP_PCR_CLKRP); + break; + case SND_SOC_DAIFMT_IB_IF: + /* CLKRP Receive clock polarity, + * 0 - sampled on falling edge of CLKR + * valid on falling edge + * CLKXP Transmit clock polarity, + * 0 - clocked on rising edge of CLKX + * valid on falling edge + * FSRP Receive frame sync pol, 1 - active low + * FSXP Transmit frame sync pol, 1 - active low + */ + pcr |= (DAVINCI_MCBSP_PCR_FSXP | DAVINCI_MCBSP_PCR_FSRP); + break; + case SND_SOC_DAIFMT_NB_IF: + /* CLKRP Receive clock polarity, + * 1 - sampled on rising edge of CLKR + * valid on rising edge + * CLKXP Transmit clock polarity, + * 1 - clocked on falling edge of CLKX + * valid on rising edge + * FSRP Receive frame sync pol, 1 - active low + * FSXP Transmit frame sync pol, 1 - active low + */ + pcr |= (DAVINCI_MCBSP_PCR_CLKXP | DAVINCI_MCBSP_PCR_CLKRP | + DAVINCI_MCBSP_PCR_FSXP | DAVINCI_MCBSP_PCR_FSRP); + break; + case SND_SOC_DAIFMT_IB_NF: + /* CLKRP Receive clock polarity, + * 0 - sampled on falling edge of CLKR + * valid on falling edge + * CLKXP Transmit clock polarity, + * 0 - clocked on rising edge of CLKX + * valid on falling edge + * FSRP Receive frame sync pol, 0 - active high + * FSXP Transmit frame sync pol, 0 - active high + */ + break; + default: + return -EINVAL; + } + if (inv_fs == true) + pcr ^= (DAVINCI_MCBSP_PCR_FSXP | DAVINCI_MCBSP_PCR_FSRP); + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr); + dev->pcr = pcr; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, pcr); + return 0; +} + +static int davinci_i2s_dai_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + + if (div_id != DAVINCI_MCBSP_CLKGDV) + return -ENODEV; + + dev->clk_div = div; + return 0; +} + +static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai); + struct snd_interval *i = NULL; + int mcbsp_word_length, master; + unsigned int rcr, xcr, srgr, clk_div, freq, framesize; + u32 spcr; + snd_pcm_format_t fmt; + unsigned element_cnt = 1; + + /* general line settings */ + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + } else { + spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + } + + master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK; + fmt = params_format(params); + mcbsp_word_length = asp_word_length[fmt]; + + switch (master) { + case SND_SOC_DAIFMT_CBS_CFS: + freq = clk_get_rate(dev->clk); + srgr = DAVINCI_MCBSP_SRGR_FSGM | + DAVINCI_MCBSP_SRGR_CLKSM; + srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length * + 8 - 1); + if (dev->i2s_accurate_sck) { + clk_div = 256; + do { + framesize = (freq / (--clk_div)) / + params->rate_num * + params->rate_den; + } while (((framesize < 33) || (framesize > 4095)) && + (clk_div)); + clk_div--; + srgr |= DAVINCI_MCBSP_SRGR_FPER(framesize - 1); + } else { + /* symmetric waveforms */ + clk_div = freq / (mcbsp_word_length * 16) / + params->rate_num * params->rate_den; + srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length * + 16 - 1); + } + clk_div &= 0xFF; + srgr |= clk_div; + break; + case SND_SOC_DAIFMT_CBM_CFS: + srgr = DAVINCI_MCBSP_SRGR_FSGM; + clk_div = dev->clk_div - 1; + srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length * 8 - 1); + srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length * 16 - 1); + clk_div &= 0xFF; + srgr |= clk_div; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Clock and frame sync given from external sources */ + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + srgr = DAVINCI_MCBSP_SRGR_FSGM; + srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1); + pr_debug("%s - %d FWID set: re-read srgr = %X\n", + __func__, __LINE__, snd_interval_value(i) - 1); + + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS); + srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1); + break; + default: + return -EINVAL; + } + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr); + + rcr = DAVINCI_MCBSP_RCR_RFIG; + xcr = DAVINCI_MCBSP_XCR_XFIG; + if (dev->mode == MOD_DSP_B) { + rcr |= DAVINCI_MCBSP_RCR_RDATDLY(0); + xcr |= DAVINCI_MCBSP_XCR_XDATDLY(0); + } else { + rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1); + xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1); + } + /* Determine xfer data type */ + fmt = params_format(params); + if ((fmt > SNDRV_PCM_FORMAT_S32_LE) || !data_type[fmt]) { + printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n"); + return -EINVAL; + } + + if (params_channels(params) == 2) { + element_cnt = 2; + if (double_fmt[fmt] && dev->enable_channel_combine) { + element_cnt = 1; + fmt = double_fmt[fmt]; + } + switch (master) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(0); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(0); + rcr |= DAVINCI_MCBSP_RCR_RPHASE; + xcr |= DAVINCI_MCBSP_XCR_XPHASE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(element_cnt - 1); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(element_cnt - 1); + break; + default: + return -EINVAL; + } + } + mcbsp_word_length = asp_word_length[fmt]; + + switch (master) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(0); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(0); + break; + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1); + break; + default: + return -EINVAL; + } + + rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) | + DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length); + xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) | + DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr); + else + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr); + + pr_debug("%s - %d srgr=%X\n", __func__, __LINE__, srgr); + pr_debug("%s - %d xcr=%X\n", __func__, __LINE__, xcr); + pr_debug("%s - %d rcr=%X\n", __func__, __LINE__, rcr); + return 0; +} + +static int davinci_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai); + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + davinci_mcbsp_stop(dev, playback); + return 0; +} + +static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + davinci_mcbsp_start(dev, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + davinci_mcbsp_stop(dev, playback); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static void davinci_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai); + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + davinci_mcbsp_stop(dev, playback); +} + +#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000 + +static const struct snd_soc_dai_ops davinci_i2s_dai_ops = { + .shutdown = davinci_i2s_shutdown, + .prepare = davinci_i2s_prepare, + .trigger = davinci_i2s_trigger, + .hw_params = davinci_i2s_hw_params, + .set_fmt = davinci_i2s_set_dai_fmt, + .set_clkdiv = davinci_i2s_dai_set_clkdiv, + +}; + +static int davinci_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct davinci_mcbsp_dev *dev = snd_soc_dai_get_drvdata(dai); + + dai->playback_dma_data = &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + dai->capture_dma_data = &dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]; + + return 0; +} + +static struct snd_soc_dai_driver davinci_i2s_dai = { + .probe = davinci_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = DAVINCI_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = DAVINCI_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &davinci_i2s_dai_ops, + +}; + +static const struct snd_soc_component_driver davinci_i2s_component = { + .name = DRV_NAME, +}; + +static int davinci_i2s_probe(struct platform_device *pdev) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + struct davinci_mcbsp_dev *dev; + struct resource *mem, *res; + void __iomem *io_base; + int *dma; + int ret; + + mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); + if (!mem) { + dev_warn(&pdev->dev, + "\"mpu\" mem resource not found, using index 0\n"); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + } + + io_base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + dev = devm_kzalloc(&pdev->dev, sizeof(struct davinci_mcbsp_dev), + GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->base = io_base; + + /* setup DMA, first TX, then RX */ + dma_data = &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + dma_data->addr = (dma_addr_t)(mem->start + DAVINCI_MCBSP_DXR_REG); + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (res) { + dma = &dev->dma_request[SNDRV_PCM_STREAM_PLAYBACK]; + *dma = res->start; + dma_data->filter_data = dma; + } else if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + dma_data->filter_data = "tx"; + } else { + dev_err(&pdev->dev, "Missing DMA tx resource\n"); + return -ENODEV; + } + + dma_data = &dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]; + dma_data->addr = (dma_addr_t)(mem->start + DAVINCI_MCBSP_DRR_REG); + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (res) { + dma = &dev->dma_request[SNDRV_PCM_STREAM_CAPTURE]; + *dma = res->start; + dma_data->filter_data = dma; + } else if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + dma_data->filter_data = "rx"; + } else { + dev_err(&pdev->dev, "Missing DMA rx resource\n"); + return -ENODEV; + } + + dev->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) + return -ENODEV; + ret = clk_enable(dev->clk); + if (ret) + goto err_put_clk; + + dev->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, dev); + + ret = snd_soc_register_component(&pdev->dev, &davinci_i2s_component, + &davinci_i2s_dai, 1); + if (ret != 0) + goto err_release_clk; + + ret = edma_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "register PCM failed: %d\n", ret); + goto err_unregister_component; + } + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_release_clk: + clk_disable(dev->clk); +err_put_clk: + clk_put(dev->clk); + return ret; +} + +static int davinci_i2s_remove(struct platform_device *pdev) +{ + struct davinci_mcbsp_dev *dev = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + + clk_disable(dev->clk); + clk_put(dev->clk); + dev->clk = NULL; + + return 0; +} + +static const struct of_device_id davinci_i2s_match[] = { + { .compatible = "ti,da850-mcbsp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, davinci_i2s_match); + +static struct platform_driver davinci_mcbsp_driver = { + .probe = davinci_i2s_probe, + .remove = davinci_i2s_remove, + .driver = { + .name = "davinci-mcbsp", + .of_match_table = of_match_ptr(davinci_i2s_match), + }, +}; + +module_platform_driver(davinci_mcbsp_driver); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("TI DAVINCI I2S (McBSP) SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/davinci-i2s.h b/sound/soc/davinci/davinci-i2s.h new file mode 100644 index 000000000..48dac3e25 --- /dev/null +++ b/sound/soc/davinci/davinci-i2s.h @@ -0,0 +1,20 @@ +/* + * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor + * + * Author: Vladimir Barinov, + * Copyright: (C) 2007 MontaVista Software, Inc., + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _DAVINCI_I2S_H +#define _DAVINCI_I2S_H + +/* McBSP dividers */ +enum davinci_mcbsp_div { + DAVINCI_MCBSP_CLKGDV, /* Sample rate generator divider */ +}; + +#endif diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c new file mode 100644 index 000000000..1203de8aa --- /dev/null +++ b/sound/soc/davinci/davinci-mcasp.c @@ -0,0 +1,2122 @@ +/* + * ALSA SoC McASP Audio Layer for TI DAVINCI processor + * + * Multi-channel Audio Serial Port Driver + * + * Author: Nirmal Pandey , + * Suresh Rajashekara + * Steve Chen + * + * Copyright: (C) 2009 MontaVista Software, Inc., + * Copyright: (C) 2009 Texas Instruments, India + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "edma-pcm.h" +#include "../omap/sdma-pcm.h" +#include "davinci-mcasp.h" + +#define MCASP_MAX_AFIFO_DEPTH 64 + +#ifdef CONFIG_PM +static u32 context_regs[] = { + DAVINCI_MCASP_TXFMCTL_REG, + DAVINCI_MCASP_RXFMCTL_REG, + DAVINCI_MCASP_TXFMT_REG, + DAVINCI_MCASP_RXFMT_REG, + DAVINCI_MCASP_ACLKXCTL_REG, + DAVINCI_MCASP_ACLKRCTL_REG, + DAVINCI_MCASP_AHCLKXCTL_REG, + DAVINCI_MCASP_AHCLKRCTL_REG, + DAVINCI_MCASP_PDIR_REG, + DAVINCI_MCASP_RXMASK_REG, + DAVINCI_MCASP_TXMASK_REG, + DAVINCI_MCASP_RXTDM_REG, + DAVINCI_MCASP_TXTDM_REG, +}; + +struct davinci_mcasp_context { + u32 config_regs[ARRAY_SIZE(context_regs)]; + u32 afifo_regs[2]; /* for read/write fifo control registers */ + u32 *xrsr_regs; /* for serializer configuration */ + bool pm_state; +}; +#endif + +struct davinci_mcasp_ruledata { + struct davinci_mcasp *mcasp; + int serializers; +}; + +struct davinci_mcasp { + struct snd_dmaengine_dai_dma_data dma_data[2]; + void __iomem *base; + u32 fifo_base; + struct device *dev; + struct snd_pcm_substream *substreams[2]; + unsigned int dai_fmt; + + /* McASP specific data */ + int tdm_slots; + u32 tdm_mask[2]; + int slot_width; + u8 op_mode; + u8 num_serializer; + u8 *serial_dir; + u8 version; + u8 bclk_div; + int streams; + u32 irq_request[2]; + int dma_request[2]; + + int sysclk_freq; + bool bclk_master; + + /* McASP FIFO related */ + u8 txnumevt; + u8 rxnumevt; + + bool dat_port; + + /* Used for comstraint setting on the second stream */ + u32 channels; + +#ifdef CONFIG_PM_SLEEP + struct davinci_mcasp_context context; +#endif + + struct davinci_mcasp_ruledata ruledata[2]; + struct snd_pcm_hw_constraint_list chconstr[2]; +}; + +static inline void mcasp_set_bits(struct davinci_mcasp *mcasp, u32 offset, + u32 val) +{ + void __iomem *reg = mcasp->base + offset; + __raw_writel(__raw_readl(reg) | val, reg); +} + +static inline void mcasp_clr_bits(struct davinci_mcasp *mcasp, u32 offset, + u32 val) +{ + void __iomem *reg = mcasp->base + offset; + __raw_writel((__raw_readl(reg) & ~(val)), reg); +} + +static inline void mcasp_mod_bits(struct davinci_mcasp *mcasp, u32 offset, + u32 val, u32 mask) +{ + void __iomem *reg = mcasp->base + offset; + __raw_writel((__raw_readl(reg) & ~mask) | val, reg); +} + +static inline void mcasp_set_reg(struct davinci_mcasp *mcasp, u32 offset, + u32 val) +{ + __raw_writel(val, mcasp->base + offset); +} + +static inline u32 mcasp_get_reg(struct davinci_mcasp *mcasp, u32 offset) +{ + return (u32)__raw_readl(mcasp->base + offset); +} + +static void mcasp_set_ctl_reg(struct davinci_mcasp *mcasp, u32 ctl_reg, u32 val) +{ + int i = 0; + + mcasp_set_bits(mcasp, ctl_reg, val); + + /* programming GBLCTL needs to read back from GBLCTL and verfiy */ + /* loop count is to avoid the lock-up */ + for (i = 0; i < 1000; i++) { + if ((mcasp_get_reg(mcasp, ctl_reg) & val) == val) + break; + } + + if (i == 1000 && ((mcasp_get_reg(mcasp, ctl_reg) & val) != val)) + printk(KERN_ERR "GBLCTL write error\n"); +} + +static bool mcasp_is_synchronous(struct davinci_mcasp *mcasp) +{ + u32 rxfmctl = mcasp_get_reg(mcasp, DAVINCI_MCASP_RXFMCTL_REG); + u32 aclkxctl = mcasp_get_reg(mcasp, DAVINCI_MCASP_ACLKXCTL_REG); + + return !(aclkxctl & TX_ASYNC) && rxfmctl & AFSRE; +} + +static void mcasp_start_rx(struct davinci_mcasp *mcasp) +{ + if (mcasp->rxnumevt) { /* enable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + mcasp_set_bits(mcasp, reg, FIFO_ENABLE); + } + + /* Start clocks */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXHCLKRST); + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXCLKRST); + /* + * When ASYNC == 0 the transmit and receive sections operate + * synchronously from the transmit clock and frame sync. We need to make + * sure that the TX signlas are enabled when starting reception. + */ + if (mcasp_is_synchronous(mcasp)) { + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); + } + + /* Activate serializer(s) */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXSERCLR); + /* Release RX state machine */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXSMRST); + /* Release Frame Sync generator */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXFSRST); + if (mcasp_is_synchronous(mcasp)) + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXFSRST); + + /* enable receive IRQs */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_EVTCTLR_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]); +} + +static void mcasp_start_tx(struct davinci_mcasp *mcasp) +{ + u32 cnt; + + if (mcasp->txnumevt) { /* enable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + mcasp_set_bits(mcasp, reg, FIFO_ENABLE); + } + + /* Start clocks */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); + /* Activate serializer(s) */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR); + + /* wait for XDATA to be cleared */ + cnt = 0; + while ((mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) & XRDATA) && + (cnt < 100000)) + cnt++; + + /* Release TX state machine */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSMRST); + /* Release Frame Sync generator */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXFSRST); + + /* enable transmit IRQs */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_EVTCTLX_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]); +} + +static void davinci_mcasp_start(struct davinci_mcasp *mcasp, int stream) +{ + mcasp->streams++; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + mcasp_start_tx(mcasp); + else + mcasp_start_rx(mcasp); +} + +static void mcasp_stop_rx(struct davinci_mcasp *mcasp) +{ + /* disable IRQ sources */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_EVTCTLR_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]); + + /* + * In synchronous mode stop the TX clocks if no other stream is + * running + */ + if (mcasp_is_synchronous(mcasp) && !mcasp->streams) + mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, 0); + + mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, 0); + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF); + + if (mcasp->rxnumevt) { /* disable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + } +} + +static void mcasp_stop_tx(struct davinci_mcasp *mcasp) +{ + u32 val = 0; + + /* disable IRQ sources */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_EVTCTLX_REG, + mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]); + + /* + * In synchronous mode keep TX clocks running if the capture stream is + * still running. + */ + if (mcasp_is_synchronous(mcasp) && mcasp->streams) + val = TXHCLKRST | TXCLKRST | TXFSRST; + + mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, val); + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); + + if (mcasp->txnumevt) { /* disable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + } +} + +static void davinci_mcasp_stop(struct davinci_mcasp *mcasp, int stream) +{ + mcasp->streams--; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + mcasp_stop_tx(mcasp); + else + mcasp_stop_rx(mcasp); +} + +static irqreturn_t davinci_mcasp_tx_irq_handler(int irq, void *data) +{ + struct davinci_mcasp *mcasp = (struct davinci_mcasp *)data; + struct snd_pcm_substream *substream; + u32 irq_mask = mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]; + u32 handled_mask = 0; + u32 stat; + + stat = mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG); + if (stat & XUNDRN & irq_mask) { + dev_warn(mcasp->dev, "Transmit buffer underflow\n"); + handled_mask |= XUNDRN; + + substream = mcasp->substreams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream) + snd_pcm_stop_xrun(substream); + } + + if (!handled_mask) + dev_warn(mcasp->dev, "unhandled tx event. txstat: 0x%08x\n", + stat); + + if (stat & XRERR) + handled_mask |= XRERR; + + /* Ack the handled event only */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, handled_mask); + + return IRQ_RETVAL(handled_mask); +} + +static irqreturn_t davinci_mcasp_rx_irq_handler(int irq, void *data) +{ + struct davinci_mcasp *mcasp = (struct davinci_mcasp *)data; + struct snd_pcm_substream *substream; + u32 irq_mask = mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]; + u32 handled_mask = 0; + u32 stat; + + stat = mcasp_get_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG); + if (stat & ROVRN & irq_mask) { + dev_warn(mcasp->dev, "Receive buffer overflow\n"); + handled_mask |= ROVRN; + + substream = mcasp->substreams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream) + snd_pcm_stop_xrun(substream); + } + + if (!handled_mask) + dev_warn(mcasp->dev, "unhandled rx event. rxstat: 0x%08x\n", + stat); + + if (stat & XRERR) + handled_mask |= XRERR; + + /* Ack the handled event only */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, handled_mask); + + return IRQ_RETVAL(handled_mask); +} + +static irqreturn_t davinci_mcasp_common_irq_handler(int irq, void *data) +{ + struct davinci_mcasp *mcasp = (struct davinci_mcasp *)data; + irqreturn_t ret = IRQ_NONE; + + if (mcasp->substreams[SNDRV_PCM_STREAM_PLAYBACK]) + ret = davinci_mcasp_tx_irq_handler(irq, data); + + if (mcasp->substreams[SNDRV_PCM_STREAM_CAPTURE]) + ret |= davinci_mcasp_rx_irq_handler(irq, data); + + return ret; +} + +static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + u32 data_delay; + bool fs_pol_rising; + bool inv_fs = false; + + if (!fmt) + return 0; + + pm_runtime_get_sync(mcasp->dev); + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* 1st data bit occur one ACLK cycle after the frame sync */ + data_delay = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_AC97: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* No delay after FS */ + data_delay = 0; + break; + case SND_SOC_DAIFMT_I2S: + /* configure a full-word SYNC pulse (LRCLK) */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* 1st data bit occur one ACLK cycle after the frame sync */ + data_delay = 1; + /* FS need to be inverted */ + inv_fs = true; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* configure a full-word SYNC pulse (LRCLK) */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* No delay after FS */ + data_delay = 0; + break; + default: + ret = -EINVAL; + goto out; + } + + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, FSXDLY(data_delay), + FSXDLY(3)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, FSRDLY(data_delay), + FSRDLY(3)); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* codec is clock and frame slave */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, ACLKX | ACLKR); + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AFSX | AFSR); + mcasp->bclk_master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFM: + /* codec is clock slave and frame master */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, ACLKX | ACLKR); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AFSX | AFSR); + mcasp->bclk_master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + /* codec is clock master and frame slave */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, ACLKX | ACLKR); + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AFSX | AFSR); + mcasp->bclk_master = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is clock and frame master */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, + ACLKX | AFSX | ACLKR | AHCLKR | AFSR); + mcasp->bclk_master = 0; + break; + default: + ret = -EINVAL; + goto out; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = true; + break; + case SND_SOC_DAIFMT_NB_IF: + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = false; + break; + case SND_SOC_DAIFMT_IB_IF: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = false; + break; + case SND_SOC_DAIFMT_NB_NF: + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = true; + break; + default: + ret = -EINVAL; + goto out; + } + + if (inv_fs) + fs_pol_rising = !fs_pol_rising; + + if (fs_pol_rising) { + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + } else { + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + } + + mcasp->dai_fmt = fmt; +out: + pm_runtime_put(mcasp->dev); + return ret; +} + +static int __davinci_mcasp_set_clkdiv(struct davinci_mcasp *mcasp, int div_id, + int div, bool explicit) +{ + pm_runtime_get_sync(mcasp->dev); + switch (div_id) { + case MCASP_CLKDIV_AUXCLK: /* MCLK divider */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXDIV(div - 1), AHCLKXDIV_MASK); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRDIV(div - 1), AHCLKRDIV_MASK); + break; + + case MCASP_CLKDIV_BCLK: /* BCLK divider */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, + ACLKXDIV(div - 1), ACLKXDIV_MASK); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, + ACLKRDIV(div - 1), ACLKRDIV_MASK); + if (explicit) + mcasp->bclk_div = div; + break; + + case MCASP_CLKDIV_BCLK_FS_RATIO: + /* + * BCLK/LRCLK ratio descries how many bit-clock cycles + * fit into one frame. The clock ratio is given for a + * full period of data (for I2S format both left and + * right channels), so it has to be divided by number + * of tdm-slots (for I2S - divided by 2). + * Instead of storing this ratio, we calculate a new + * tdm_slot width by dividing the the ratio by the + * number of configured tdm slots. + */ + mcasp->slot_width = div / mcasp->tdm_slots; + if (div % mcasp->tdm_slots) + dev_warn(mcasp->dev, + "%s(): BCLK/LRCLK %d is not divisible by %d tdm slots", + __func__, div, mcasp->tdm_slots); + break; + + default: + return -EINVAL; + } + + pm_runtime_put(mcasp->dev); + return 0; +} + +static int davinci_mcasp_set_clkdiv(struct snd_soc_dai *dai, int div_id, + int div) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + + return __davinci_mcasp_set_clkdiv(mcasp, div_id, div, 1); +} + +static int davinci_mcasp_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + + pm_runtime_get_sync(mcasp->dev); + if (dir == SND_SOC_CLOCK_OUT) { + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AHCLKX); + } else { + mcasp_clr_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AHCLKX); + } + + mcasp->sysclk_freq = freq; + + pm_runtime_put(mcasp->dev); + return 0; +} + +/* All serializers must have equal number of channels */ +static int davinci_mcasp_ch_constraint(struct davinci_mcasp *mcasp, int stream, + int serializers) +{ + struct snd_pcm_hw_constraint_list *cl = &mcasp->chconstr[stream]; + unsigned int *list = (unsigned int *) cl->list; + int slots = mcasp->tdm_slots; + int i, count = 0; + + if (mcasp->tdm_mask[stream]) + slots = hweight32(mcasp->tdm_mask[stream]); + + for (i = 1; i <= slots; i++) + list[count++] = i; + + for (i = 2; i <= serializers; i++) + list[count++] = i*slots; + + cl->count = count; + + return 0; +} + +static int davinci_mcasp_set_ch_constraints(struct davinci_mcasp *mcasp) +{ + int rx_serializers = 0, tx_serializers = 0, ret, i; + + for (i = 0; i < mcasp->num_serializer; i++) + if (mcasp->serial_dir[i] == TX_MODE) + tx_serializers++; + else if (mcasp->serial_dir[i] == RX_MODE) + rx_serializers++; + + ret = davinci_mcasp_ch_constraint(mcasp, SNDRV_PCM_STREAM_PLAYBACK, + tx_serializers); + if (ret) + return ret; + + ret = davinci_mcasp_ch_constraint(mcasp, SNDRV_PCM_STREAM_CAPTURE, + rx_serializers); + + return ret; +} + + +static int davinci_mcasp_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + + dev_dbg(mcasp->dev, + "%s() tx_mask 0x%08x rx_mask 0x%08x slots %d width %d\n", + __func__, tx_mask, rx_mask, slots, slot_width); + + if (tx_mask >= (1<= (1<dev, + "Bad tdm mask tx: 0x%08x rx: 0x%08x slots %d\n", + tx_mask, rx_mask, slots); + return -EINVAL; + } + + if (slot_width && + (slot_width < 8 || slot_width > 32 || slot_width % 4 != 0)) { + dev_err(mcasp->dev, "%s: Unsupported slot_width %d\n", + __func__, slot_width); + return -EINVAL; + } + + mcasp->tdm_slots = slots; + mcasp->tdm_mask[SNDRV_PCM_STREAM_PLAYBACK] = tx_mask; + mcasp->tdm_mask[SNDRV_PCM_STREAM_CAPTURE] = rx_mask; + mcasp->slot_width = slot_width; + + return davinci_mcasp_set_ch_constraints(mcasp); +} + +static int davinci_config_channel_size(struct davinci_mcasp *mcasp, + int sample_width) +{ + u32 fmt; + u32 tx_rotate = (sample_width / 4) & 0x7; + u32 mask = (1ULL << sample_width) - 1; + u32 slot_width = sample_width; + + /* + * For captured data we should not rotate, inversion and masking is + * enoguh to get the data to the right position: + * Format data from bus after reverse (XRBUF) + * S16_LE: |LSB|MSB|xxx|xxx| |xxx|xxx|MSB|LSB| + * S24_3LE: |LSB|DAT|MSB|xxx| |xxx|MSB|DAT|LSB| + * S24_LE: |LSB|DAT|MSB|xxx| |xxx|MSB|DAT|LSB| + * S32_LE: |LSB|DAT|DAT|MSB| |MSB|DAT|DAT|LSB| + */ + u32 rx_rotate = 0; + + /* + * Setting the tdm slot width either with set_clkdiv() or + * set_tdm_slot() allows us to for example send 32 bits per + * channel to the codec, while only 16 of them carry audio + * payload. + */ + if (mcasp->slot_width) { + /* + * When we have more bclk then it is needed for the + * data, we need to use the rotation to move the + * received samples to have correct alignment. + */ + slot_width = mcasp->slot_width; + rx_rotate = (slot_width - sample_width) / 4; + } + + /* mapping of the XSSZ bit-field as described in the datasheet */ + fmt = (slot_width >> 1) - 1; + + if (mcasp->op_mode != DAVINCI_MCASP_DIT_MODE) { + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXSSZ(fmt), + RXSSZ(0x0F)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXSSZ(fmt), + TXSSZ(0x0F)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXROT(tx_rotate), + TXROT(7)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXROT(rx_rotate), + RXROT(7)); + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXMASK_REG, mask); + } + + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXMASK_REG, mask); + + return 0; +} + +static int mcasp_common_hw_param(struct davinci_mcasp *mcasp, int stream, + int period_words, int channels) +{ + struct snd_dmaengine_dai_dma_data *dma_data = &mcasp->dma_data[stream]; + int i; + u8 tx_ser = 0; + u8 rx_ser = 0; + u8 slots = mcasp->tdm_slots; + u8 max_active_serializers = (channels + slots - 1) / slots; + int active_serializers, numevt; + u32 reg; + /* Default configuration */ + if (mcasp->version < MCASP_VERSION_3) + mcasp_set_bits(mcasp, DAVINCI_MCASP_PWREMUMGT_REG, MCASP_SOFT); + + /* All PINS as McASP */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_PFUNC_REG, 0x00000000); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS); + } else { + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_REVTCTL_REG, RXDATADMADIS); + } + + for (i = 0; i < mcasp->num_serializer; i++) { + mcasp_set_bits(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), + mcasp->serial_dir[i]); + if (mcasp->serial_dir[i] == TX_MODE && + tx_ser < max_active_serializers) { + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AXR(i)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), + DISMOD_LOW, DISMOD_MASK); + tx_ser++; + } else if (mcasp->serial_dir[i] == RX_MODE && + rx_ser < max_active_serializers) { + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AXR(i)); + rx_ser++; + } else if (mcasp->serial_dir[i] == INACTIVE_MODE) { + mcasp_mod_bits(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), + SRMOD_INACTIVE, SRMOD_MASK); + } + } + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + active_serializers = tx_ser; + numevt = mcasp->txnumevt; + reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + } else { + active_serializers = rx_ser; + numevt = mcasp->rxnumevt; + reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + } + + if (active_serializers < max_active_serializers) { + dev_warn(mcasp->dev, "stream has more channels (%d) than are " + "enabled in mcasp (%d)\n", channels, + active_serializers * slots); + return -EINVAL; + } + + /* AFIFO is not in use */ + if (!numevt) { + /* Configure the burst size for platform drivers */ + if (active_serializers > 1) { + /* + * If more than one serializers are in use we have one + * DMA request to provide data for all serializers. + * For example if three serializers are enabled the DMA + * need to transfer three words per DMA request. + */ + dma_data->maxburst = active_serializers; + } else { + dma_data->maxburst = 0; + } + return 0; + } + + if (period_words % active_serializers) { + dev_err(mcasp->dev, "Invalid combination of period words and " + "active serializers: %d, %d\n", period_words, + active_serializers); + return -EINVAL; + } + + /* + * Calculate the optimal AFIFO depth for platform side: + * The number of words for numevt need to be in steps of active + * serializers. + */ + numevt = (numevt / active_serializers) * active_serializers; + + while (period_words % numevt && numevt > 0) + numevt -= active_serializers; + if (numevt <= 0) + numevt = active_serializers; + + mcasp_mod_bits(mcasp, reg, active_serializers, NUMDMA_MASK); + mcasp_mod_bits(mcasp, reg, NUMEVT(numevt), NUMEVT_MASK); + + /* Configure the burst size for platform drivers */ + if (numevt == 1) + numevt = 0; + dma_data->maxburst = numevt; + + return 0; +} + +static int mcasp_i2s_hw_param(struct davinci_mcasp *mcasp, int stream, + int channels) +{ + int i, active_slots; + int total_slots; + int active_serializers; + u32 mask = 0; + u32 busel = 0; + + total_slots = mcasp->tdm_slots; + + /* + * If more than one serializer is needed, then use them with + * all the specified tdm_slots. Otherwise, one serializer can + * cope with the transaction using just as many slots as there + * are channels in the stream. + */ + if (mcasp->tdm_mask[stream]) { + active_slots = hweight32(mcasp->tdm_mask[stream]); + active_serializers = (channels + active_slots - 1) / + active_slots; + if (active_serializers == 1) + active_slots = channels; + for (i = 0; i < total_slots; i++) { + if ((1 << i) & mcasp->tdm_mask[stream]) { + mask |= (1 << i); + if (--active_slots <= 0) + break; + } + } + } else { + active_serializers = (channels + total_slots - 1) / total_slots; + if (active_serializers == 1) + active_slots = channels; + else + active_slots = total_slots; + + for (i = 0; i < active_slots; i++) + mask |= (1 << i); + } + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC); + + if (!mcasp->dat_port) + busel = TXSEL; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXTDM_REG, mask); + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, busel | TXORD); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, + FSXMOD(total_slots), FSXMOD(0x1FF)); + } else if (stream == SNDRV_PCM_STREAM_CAPTURE) { + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXTDM_REG, mask); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, busel | RXORD); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, + FSRMOD(total_slots), FSRMOD(0x1FF)); + /* + * If McASP is set to be TX/RX synchronous and the playback is + * not running already we need to configure the TX slots in + * order to have correct FSX on the bus + */ + if (mcasp_is_synchronous(mcasp) && !mcasp->channels) + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, + FSXMOD(total_slots), FSXMOD(0x1FF)); + } + + return 0; +} + +/* S/PDIF */ +static int mcasp_dit_hw_param(struct davinci_mcasp *mcasp, + unsigned int rate) +{ + u32 cs_value = 0; + u8 *cs_bytes = (u8*) &cs_value; + + /* Set the TX format : 24 bit right rotation, 32 bit slot, Pad 0 + and LSB first */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXROT(6) | TXSSZ(15)); + + /* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE | FSXMOD(0x180)); + + /* Set the TX tdm : for all the slots */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXTDM_REG, 0xFFFFFFFF); + + /* Set the TX clock controls : div = 1 and internal */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE | TX_ASYNC); + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS); + + /* Only 44100 and 48000 are valid, both have the same setting */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXDIV(3)); + + /* Enable the DIT */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXDITCTL_REG, DITEN); + + /* Set S/PDIF channel status bits */ + cs_bytes[0] = IEC958_AES0_CON_NOT_COPYRIGHT; + cs_bytes[1] = IEC958_AES1_CON_PCM_CODER; + + switch (rate) { + case 22050: + cs_bytes[3] |= IEC958_AES3_CON_FS_22050; + break; + case 24000: + cs_bytes[3] |= IEC958_AES3_CON_FS_24000; + break; + case 32000: + cs_bytes[3] |= IEC958_AES3_CON_FS_32000; + break; + case 44100: + cs_bytes[3] |= IEC958_AES3_CON_FS_44100; + break; + case 48000: + cs_bytes[3] |= IEC958_AES3_CON_FS_48000; + break; + case 88200: + cs_bytes[3] |= IEC958_AES3_CON_FS_88200; + break; + case 96000: + cs_bytes[3] |= IEC958_AES3_CON_FS_96000; + break; + case 176400: + cs_bytes[3] |= IEC958_AES3_CON_FS_176400; + break; + case 192000: + cs_bytes[3] |= IEC958_AES3_CON_FS_192000; + break; + default: + printk(KERN_WARNING "unsupported sampling rate: %d\n", rate); + return -EINVAL; + } + + mcasp_set_reg(mcasp, DAVINCI_MCASP_DITCSRA_REG, cs_value); + mcasp_set_reg(mcasp, DAVINCI_MCASP_DITCSRB_REG, cs_value); + + return 0; +} + +static int davinci_mcasp_calc_clk_div(struct davinci_mcasp *mcasp, + unsigned int bclk_freq, bool set) +{ + int error_ppm; + unsigned int sysclk_freq = mcasp->sysclk_freq; + u32 reg = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG); + int div = sysclk_freq / bclk_freq; + int rem = sysclk_freq % bclk_freq; + int aux_div = 1; + + if (div > (ACLKXDIV_MASK + 1)) { + if (reg & AHCLKXE) { + aux_div = div / (ACLKXDIV_MASK + 1); + if (div % (ACLKXDIV_MASK + 1)) + aux_div++; + + sysclk_freq /= aux_div; + div = sysclk_freq / bclk_freq; + rem = sysclk_freq % bclk_freq; + } else if (set) { + dev_warn(mcasp->dev, "Too fast reference clock (%u)\n", + sysclk_freq); + } + } + + if (rem != 0) { + if (div == 0 || + ((sysclk_freq / div) - bclk_freq) > + (bclk_freq - (sysclk_freq / (div+1)))) { + div++; + rem = rem - bclk_freq; + } + } + error_ppm = (div*1000000 + (int)div64_long(1000000LL*rem, + (int)bclk_freq)) / div - 1000000; + + if (set) { + if (error_ppm) + dev_info(mcasp->dev, "Sample-rate is off by %d PPM\n", + error_ppm); + + __davinci_mcasp_set_clkdiv(mcasp, MCASP_CLKDIV_BCLK, div, 0); + if (reg & AHCLKXE) + __davinci_mcasp_set_clkdiv(mcasp, MCASP_CLKDIV_AUXCLK, + aux_div, 0); + } + + return error_ppm; +} + +static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + int word_length; + int channels = params_channels(params); + int period_size = params_period_size(params); + int ret; + + ret = davinci_mcasp_set_dai_fmt(cpu_dai, mcasp->dai_fmt); + if (ret) + return ret; + + /* + * If mcasp is BCLK master, and a BCLK divider was not provided by + * the machine driver, we need to calculate the ratio. + */ + if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { + int slots = mcasp->tdm_slots; + int rate = params_rate(params); + int sbits = params_width(params); + + if (mcasp->slot_width) + sbits = mcasp->slot_width; + + davinci_mcasp_calc_clk_div(mcasp, rate * sbits * slots, true); + } + + ret = mcasp_common_hw_param(mcasp, substream->stream, + period_size * channels, channels); + if (ret) + return ret; + + if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) + ret = mcasp_dit_hw_param(mcasp, params_rate(params)); + else + ret = mcasp_i2s_hw_param(mcasp, substream->stream, + channels); + + if (ret) + return ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + word_length = 8; + break; + + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + word_length = 16; + break; + + case SNDRV_PCM_FORMAT_U24_3LE: + case SNDRV_PCM_FORMAT_S24_3LE: + word_length = 24; + break; + + case SNDRV_PCM_FORMAT_U24_LE: + case SNDRV_PCM_FORMAT_S24_LE: + word_length = 24; + break; + + case SNDRV_PCM_FORMAT_U32_LE: + case SNDRV_PCM_FORMAT_S32_LE: + word_length = 32; + break; + + default: + printk(KERN_WARNING "davinci-mcasp: unsupported PCM format"); + return -EINVAL; + } + + davinci_config_channel_size(mcasp, word_length); + + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) + mcasp->channels = channels; + + return 0; +} + +static int davinci_mcasp_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + davinci_mcasp_start(mcasp, substream->stream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + davinci_mcasp_stop(mcasp, substream->stream); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int davinci_mcasp_hw_rule_slot_width(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct davinci_mcasp_ruledata *rd = rule->private; + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_mask nfmt; + int i, slot_width; + + snd_mask_none(&nfmt); + slot_width = rd->mcasp->slot_width; + + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + if (snd_mask_test(fmt, i)) { + if (snd_pcm_format_width(i) <= slot_width) { + snd_mask_set(&nfmt, i); + } + } + } + + return snd_mask_refine(fmt, &nfmt); +} + +static const unsigned int davinci_mcasp_dai_rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, + 88200, 96000, 176400, 192000, +}; + +#define DAVINCI_MAX_RATE_ERROR_PPM 1000 + +static int davinci_mcasp_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct davinci_mcasp_ruledata *rd = rule->private; + struct snd_interval *ri = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + int sbits = params_width(params); + int slots = rd->mcasp->tdm_slots; + struct snd_interval range; + int i; + + if (rd->mcasp->slot_width) + sbits = rd->mcasp->slot_width; + + snd_interval_any(&range); + range.empty = 1; + + for (i = 0; i < ARRAY_SIZE(davinci_mcasp_dai_rates); i++) { + if (snd_interval_test(ri, davinci_mcasp_dai_rates[i])) { + uint bclk_freq = sbits*slots* + davinci_mcasp_dai_rates[i]; + int ppm; + + ppm = davinci_mcasp_calc_clk_div(rd->mcasp, bclk_freq, + false); + if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) { + if (range.empty) { + range.min = davinci_mcasp_dai_rates[i]; + range.empty = 0; + } + range.max = davinci_mcasp_dai_rates[i]; + } + } + } + + dev_dbg(rd->mcasp->dev, + "Frequencies %d-%d -> %d-%d for %d sbits and %d tdm slots\n", + ri->min, ri->max, range.min, range.max, sbits, slots); + + return snd_interval_refine(hw_param_interval(params, rule->var), + &range); +} + +static int davinci_mcasp_hw_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct davinci_mcasp_ruledata *rd = rule->private; + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_mask nfmt; + int rate = params_rate(params); + int slots = rd->mcasp->tdm_slots; + int i, count = 0; + + snd_mask_none(&nfmt); + + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + if (snd_mask_test(fmt, i)) { + uint sbits = snd_pcm_format_width(i); + int ppm; + + if (rd->mcasp->slot_width) + sbits = rd->mcasp->slot_width; + + ppm = davinci_mcasp_calc_clk_div(rd->mcasp, + sbits * slots * rate, + false); + if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) { + snd_mask_set(&nfmt, i); + count++; + } + } + } + dev_dbg(rd->mcasp->dev, + "%d possible sample format for %d Hz and %d tdm slots\n", + count, rate, slots); + + return snd_mask_refine(fmt, &nfmt); +} + +static int davinci_mcasp_hw_rule_min_periodsize( + struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *period_size = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + struct snd_interval frames; + + snd_interval_any(&frames); + frames.min = 64; + frames.integer = 1; + + return snd_interval_refine(period_size, &frames); +} + +static int davinci_mcasp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + struct davinci_mcasp_ruledata *ruledata = + &mcasp->ruledata[substream->stream]; + u32 max_channels = 0; + int i, dir, ret; + int tdm_slots = mcasp->tdm_slots; + + /* Do not allow more then one stream per direction */ + if (mcasp->substreams[substream->stream]) + return -EBUSY; + + mcasp->substreams[substream->stream] = substream; + + if (mcasp->tdm_mask[substream->stream]) + tdm_slots = hweight32(mcasp->tdm_mask[substream->stream]); + + if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) + return 0; + + /* + * Limit the maximum allowed channels for the first stream: + * number of serializers for the direction * tdm slots per serializer + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = TX_MODE; + else + dir = RX_MODE; + + for (i = 0; i < mcasp->num_serializer; i++) { + if (mcasp->serial_dir[i] == dir) + max_channels++; + } + ruledata->serializers = max_channels; + ruledata->mcasp = mcasp; + max_channels *= tdm_slots; + /* + * If the already active stream has less channels than the calculated + * limnit based on the seirializers * tdm_slots, we need to use that as + * a constraint for the second stream. + * Otherwise (first stream or less allowed channels) we use the + * calculated constraint. + */ + if (mcasp->channels && mcasp->channels < max_channels) + max_channels = mcasp->channels; + /* + * But we can always allow channels upto the amount of + * the available tdm_slots. + */ + if (max_channels < tdm_slots) + max_channels = tdm_slots; + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 0, max_channels); + + snd_pcm_hw_constraint_list(substream->runtime, + 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &mcasp->chconstr[substream->stream]); + + if (mcasp->slot_width) { + /* Only allow formats require <= slot_width bits on the bus */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + davinci_mcasp_hw_rule_slot_width, + ruledata, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) + return ret; + } + + /* + * If we rely on implicit BCLK divider setting we should + * set constraints based on what we can provide. + */ + if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + davinci_mcasp_hw_rule_rate, + ruledata, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) + return ret; + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + davinci_mcasp_hw_rule_format, + ruledata, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (ret) + return ret; + } + + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + davinci_mcasp_hw_rule_min_periodsize, NULL, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + + return 0; +} + +static void davinci_mcasp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + + mcasp->substreams[substream->stream] = NULL; + + if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) + return; + + if (!cpu_dai->active) + mcasp->channels = 0; +} + +static const struct snd_soc_dai_ops davinci_mcasp_dai_ops = { + .startup = davinci_mcasp_startup, + .shutdown = davinci_mcasp_shutdown, + .trigger = davinci_mcasp_trigger, + .hw_params = davinci_mcasp_hw_params, + .set_fmt = davinci_mcasp_set_dai_fmt, + .set_clkdiv = davinci_mcasp_set_clkdiv, + .set_sysclk = davinci_mcasp_set_sysclk, + .set_tdm_slot = davinci_mcasp_set_tdm_slot, +}; + +static int davinci_mcasp_dai_probe(struct snd_soc_dai *dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + + dai->playback_dma_data = &mcasp->dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + dai->capture_dma_data = &mcasp->dma_data[SNDRV_PCM_STREAM_CAPTURE]; + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int davinci_mcasp_suspend(struct snd_soc_dai *dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + struct davinci_mcasp_context *context = &mcasp->context; + u32 reg; + int i; + + context->pm_state = pm_runtime_active(mcasp->dev); + if (!context->pm_state) + pm_runtime_get_sync(mcasp->dev); + + for (i = 0; i < ARRAY_SIZE(context_regs); i++) + context->config_regs[i] = mcasp_get_reg(mcasp, context_regs[i]); + + if (mcasp->txnumevt) { + reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + context->afifo_regs[0] = mcasp_get_reg(mcasp, reg); + } + if (mcasp->rxnumevt) { + reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + context->afifo_regs[1] = mcasp_get_reg(mcasp, reg); + } + + for (i = 0; i < mcasp->num_serializer; i++) + context->xrsr_regs[i] = mcasp_get_reg(mcasp, + DAVINCI_MCASP_XRSRCTL_REG(i)); + + pm_runtime_put_sync(mcasp->dev); + + return 0; +} + +static int davinci_mcasp_resume(struct snd_soc_dai *dai) +{ + struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + struct davinci_mcasp_context *context = &mcasp->context; + u32 reg; + int i; + + pm_runtime_get_sync(mcasp->dev); + + for (i = 0; i < ARRAY_SIZE(context_regs); i++) + mcasp_set_reg(mcasp, context_regs[i], context->config_regs[i]); + + if (mcasp->txnumevt) { + reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + mcasp_set_reg(mcasp, reg, context->afifo_regs[0]); + } + if (mcasp->rxnumevt) { + reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + mcasp_set_reg(mcasp, reg, context->afifo_regs[1]); + } + + for (i = 0; i < mcasp->num_serializer; i++) + mcasp_set_reg(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), + context->xrsr_regs[i]); + + if (!context->pm_state) + pm_runtime_put_sync(mcasp->dev); + + return 0; +} +#else +#define davinci_mcasp_suspend NULL +#define davinci_mcasp_resume NULL +#endif + +#define DAVINCI_MCASP_RATES SNDRV_PCM_RATE_8000_192000 + +#define DAVINCI_MCASP_PCM_FMTS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_U32_LE) + +static struct snd_soc_dai_driver davinci_mcasp_dai[] = { + { + .name = "davinci-mcasp.0", + .probe = davinci_mcasp_dai_probe, + .suspend = davinci_mcasp_suspend, + .resume = davinci_mcasp_resume, + .playback = { + .channels_min = 1, + .channels_max = 32 * 16, + .rates = DAVINCI_MCASP_RATES, + .formats = DAVINCI_MCASP_PCM_FMTS, + }, + .capture = { + .channels_min = 1, + .channels_max = 32 * 16, + .rates = DAVINCI_MCASP_RATES, + .formats = DAVINCI_MCASP_PCM_FMTS, + }, + .ops = &davinci_mcasp_dai_ops, + + .symmetric_samplebits = 1, + .symmetric_rates = 1, + }, + { + .name = "davinci-mcasp.1", + .probe = davinci_mcasp_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 384, + .rates = DAVINCI_MCASP_RATES, + .formats = DAVINCI_MCASP_PCM_FMTS, + }, + .ops = &davinci_mcasp_dai_ops, + }, + +}; + +static const struct snd_soc_component_driver davinci_mcasp_component = { + .name = "davinci-mcasp", +}; + +/* Some HW specific values and defaults. The rest is filled in from DT. */ +static struct davinci_mcasp_pdata dm646x_mcasp_pdata = { + .tx_dma_offset = 0x400, + .rx_dma_offset = 0x400, + .version = MCASP_VERSION_1, +}; + +static struct davinci_mcasp_pdata da830_mcasp_pdata = { + .tx_dma_offset = 0x2000, + .rx_dma_offset = 0x2000, + .version = MCASP_VERSION_2, +}; + +static struct davinci_mcasp_pdata am33xx_mcasp_pdata = { + .tx_dma_offset = 0, + .rx_dma_offset = 0, + .version = MCASP_VERSION_3, +}; + +static struct davinci_mcasp_pdata dra7_mcasp_pdata = { + /* The CFG port offset will be calculated if it is needed */ + .tx_dma_offset = 0, + .rx_dma_offset = 0, + .version = MCASP_VERSION_4, +}; + +static const struct of_device_id mcasp_dt_ids[] = { + { + .compatible = "ti,dm646x-mcasp-audio", + .data = &dm646x_mcasp_pdata, + }, + { + .compatible = "ti,da830-mcasp-audio", + .data = &da830_mcasp_pdata, + }, + { + .compatible = "ti,am33xx-mcasp-audio", + .data = &am33xx_mcasp_pdata, + }, + { + .compatible = "ti,dra7-mcasp-audio", + .data = &dra7_mcasp_pdata, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mcasp_dt_ids); + +static int mcasp_reparent_fck(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct clk *gfclk, *parent_clk; + const char *parent_name; + int ret; + + if (!node) + return 0; + + parent_name = of_get_property(node, "fck_parent", NULL); + if (!parent_name) + return 0; + + dev_warn(&pdev->dev, "Update the bindings to use assigned-clocks!\n"); + + gfclk = clk_get(&pdev->dev, "fck"); + if (IS_ERR(gfclk)) { + dev_err(&pdev->dev, "failed to get fck\n"); + return PTR_ERR(gfclk); + } + + parent_clk = clk_get(NULL, parent_name); + if (IS_ERR(parent_clk)) { + dev_err(&pdev->dev, "failed to get parent clock\n"); + ret = PTR_ERR(parent_clk); + goto err1; + } + + ret = clk_set_parent(gfclk, parent_clk); + if (ret) { + dev_err(&pdev->dev, "failed to reparent fck\n"); + goto err2; + } + +err2: + clk_put(parent_clk); +err1: + clk_put(gfclk); + return ret; +} + +static struct davinci_mcasp_pdata *davinci_mcasp_set_pdata_from_of( + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct davinci_mcasp_pdata *pdata = NULL; + const struct of_device_id *match = + of_match_device(mcasp_dt_ids, &pdev->dev); + struct of_phandle_args dma_spec; + + const u32 *of_serial_dir32; + u32 val; + int i, ret = 0; + + if (pdev->dev.platform_data) { + pdata = pdev->dev.platform_data; + return pdata; + } else if (match) { + pdata = devm_kmemdup(&pdev->dev, match->data, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + return pdata; + } + } else { + /* control shouldn't reach here. something is wrong */ + ret = -EINVAL; + goto nodata; + } + + ret = of_property_read_u32(np, "op-mode", &val); + if (ret >= 0) + pdata->op_mode = val; + + ret = of_property_read_u32(np, "tdm-slots", &val); + if (ret >= 0) { + if (val < 2 || val > 32) { + dev_err(&pdev->dev, + "tdm-slots must be in rage [2-32]\n"); + ret = -EINVAL; + goto nodata; + } + + pdata->tdm_slots = val; + } + + of_serial_dir32 = of_get_property(np, "serial-dir", &val); + val /= sizeof(u32); + if (of_serial_dir32) { + u8 *of_serial_dir = devm_kzalloc(&pdev->dev, + (sizeof(*of_serial_dir) * val), + GFP_KERNEL); + if (!of_serial_dir) { + ret = -ENOMEM; + goto nodata; + } + + for (i = 0; i < val; i++) + of_serial_dir[i] = be32_to_cpup(&of_serial_dir32[i]); + + pdata->num_serializer = val; + pdata->serial_dir = of_serial_dir; + } + + ret = of_property_match_string(np, "dma-names", "tx"); + if (ret < 0) + goto nodata; + + ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells", ret, + &dma_spec); + if (ret < 0) + goto nodata; + + pdata->tx_dma_channel = dma_spec.args[0]; + + /* RX is not valid in DIT mode */ + if (pdata->op_mode != DAVINCI_MCASP_DIT_MODE) { + ret = of_property_match_string(np, "dma-names", "rx"); + if (ret < 0) + goto nodata; + + ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells", ret, + &dma_spec); + if (ret < 0) + goto nodata; + + pdata->rx_dma_channel = dma_spec.args[0]; + } + + ret = of_property_read_u32(np, "tx-num-evt", &val); + if (ret >= 0) + pdata->txnumevt = val; + + ret = of_property_read_u32(np, "rx-num-evt", &val); + if (ret >= 0) + pdata->rxnumevt = val; + + ret = of_property_read_u32(np, "sram-size-playback", &val); + if (ret >= 0) + pdata->sram_size_playback = val; + + ret = of_property_read_u32(np, "sram-size-capture", &val); + if (ret >= 0) + pdata->sram_size_capture = val; + + return pdata; + +nodata: + if (ret < 0) { + dev_err(&pdev->dev, "Error populating platform data, err %d\n", + ret); + pdata = NULL; + } + return pdata; +} + +enum { + PCM_EDMA, + PCM_SDMA, +}; +static const char *sdma_prefix = "ti,omap"; + +static int davinci_mcasp_get_dma_type(struct davinci_mcasp *mcasp) +{ + struct dma_chan *chan; + const char *tmp; + int ret = PCM_EDMA; + + if (!mcasp->dev->of_node) + return PCM_EDMA; + + tmp = mcasp->dma_data[SNDRV_PCM_STREAM_PLAYBACK].filter_data; + chan = dma_request_slave_channel_reason(mcasp->dev, tmp); + if (IS_ERR(chan)) { + if (PTR_ERR(chan) != -EPROBE_DEFER) + dev_err(mcasp->dev, + "Can't verify DMA configuration (%ld)\n", + PTR_ERR(chan)); + return PTR_ERR(chan); + } + if (WARN_ON(!chan->device || !chan->device->dev)) { + dma_release_channel(chan); + return -EINVAL; + } + + if (chan->device->dev->of_node) + ret = of_property_read_string(chan->device->dev->of_node, + "compatible", &tmp); + else + dev_dbg(mcasp->dev, "DMA controller has no of-node\n"); + + dma_release_channel(chan); + if (ret) + return ret; + + dev_dbg(mcasp->dev, "DMA controller compatible = \"%s\"\n", tmp); + if (!strncmp(tmp, sdma_prefix, strlen(sdma_prefix))) + return PCM_SDMA; + + return PCM_EDMA; +} + +static u32 davinci_mcasp_txdma_offset(struct davinci_mcasp_pdata *pdata) +{ + int i; + u32 offset = 0; + + if (pdata->version != MCASP_VERSION_4) + return pdata->tx_dma_offset; + + for (i = 0; i < pdata->num_serializer; i++) { + if (pdata->serial_dir[i] == TX_MODE) { + if (!offset) { + offset = DAVINCI_MCASP_TXBUF_REG(i); + } else { + pr_err("%s: Only one serializer allowed!\n", + __func__); + break; + } + } + } + + return offset; +} + +static u32 davinci_mcasp_rxdma_offset(struct davinci_mcasp_pdata *pdata) +{ + int i; + u32 offset = 0; + + if (pdata->version != MCASP_VERSION_4) + return pdata->rx_dma_offset; + + for (i = 0; i < pdata->num_serializer; i++) { + if (pdata->serial_dir[i] == RX_MODE) { + if (!offset) { + offset = DAVINCI_MCASP_RXBUF_REG(i); + } else { + pr_err("%s: Only one serializer allowed!\n", + __func__); + break; + } + } + } + + return offset; +} + +static int davinci_mcasp_probe(struct platform_device *pdev) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + struct resource *mem, *res, *dat; + struct davinci_mcasp_pdata *pdata; + struct davinci_mcasp *mcasp; + char *irq_name; + int *dma; + int irq; + int ret; + + if (!pdev->dev.platform_data && !pdev->dev.of_node) { + dev_err(&pdev->dev, "No platform data supplied\n"); + return -EINVAL; + } + + mcasp = devm_kzalloc(&pdev->dev, sizeof(struct davinci_mcasp), + GFP_KERNEL); + if (!mcasp) + return -ENOMEM; + + pdata = davinci_mcasp_set_pdata_from_of(pdev); + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); + if (!mem) { + dev_warn(mcasp->dev, + "\"mpu\" mem resource not found, using index 0\n"); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + } + + mcasp->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(mcasp->base)) + return PTR_ERR(mcasp->base); + + pm_runtime_enable(&pdev->dev); + + mcasp->op_mode = pdata->op_mode; + /* sanity check for tdm slots parameter */ + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) { + if (pdata->tdm_slots < 2) { + dev_err(&pdev->dev, "invalid tdm slots: %d\n", + pdata->tdm_slots); + mcasp->tdm_slots = 2; + } else if (pdata->tdm_slots > 32) { + dev_err(&pdev->dev, "invalid tdm slots: %d\n", + pdata->tdm_slots); + mcasp->tdm_slots = 32; + } else { + mcasp->tdm_slots = pdata->tdm_slots; + } + } + + mcasp->num_serializer = pdata->num_serializer; +#ifdef CONFIG_PM_SLEEP + mcasp->context.xrsr_regs = devm_kcalloc(&pdev->dev, + mcasp->num_serializer, sizeof(u32), + GFP_KERNEL); + if (!mcasp->context.xrsr_regs) { + ret = -ENOMEM; + goto err; + } +#endif + mcasp->serial_dir = pdata->serial_dir; + mcasp->version = pdata->version; + mcasp->txnumevt = pdata->txnumevt; + mcasp->rxnumevt = pdata->rxnumevt; + + mcasp->dev = &pdev->dev; + + irq = platform_get_irq_byname(pdev, "common"); + if (irq >= 0) { + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_common", + dev_name(&pdev->dev)); + if (!irq_name) { + ret = -ENOMEM; + goto err; + } + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + davinci_mcasp_common_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + irq_name, mcasp); + if (ret) { + dev_err(&pdev->dev, "common IRQ request failed\n"); + goto err; + } + + mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK] = XUNDRN; + mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE] = ROVRN; + } + + irq = platform_get_irq_byname(pdev, "rx"); + if (irq >= 0) { + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_rx", + dev_name(&pdev->dev)); + if (!irq_name) { + ret = -ENOMEM; + goto err; + } + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + davinci_mcasp_rx_irq_handler, + IRQF_ONESHOT, irq_name, mcasp); + if (ret) { + dev_err(&pdev->dev, "RX IRQ request failed\n"); + goto err; + } + + mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE] = ROVRN; + } + + irq = platform_get_irq_byname(pdev, "tx"); + if (irq >= 0) { + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_tx", + dev_name(&pdev->dev)); + if (!irq_name) { + ret = -ENOMEM; + goto err; + } + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + davinci_mcasp_tx_irq_handler, + IRQF_ONESHOT, irq_name, mcasp); + if (ret) { + dev_err(&pdev->dev, "TX IRQ request failed\n"); + goto err; + } + + mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK] = XUNDRN; + } + + dat = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat"); + if (dat) + mcasp->dat_port = true; + + dma_data = &mcasp->dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + if (dat) + dma_data->addr = dat->start; + else + dma_data->addr = mem->start + davinci_mcasp_txdma_offset(pdata); + + dma = &mcasp->dma_request[SNDRV_PCM_STREAM_PLAYBACK]; + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (res) + *dma = res->start; + else + *dma = pdata->tx_dma_channel; + + /* dmaengine filter data for DT and non-DT boot */ + if (pdev->dev.of_node) + dma_data->filter_data = "tx"; + else + dma_data->filter_data = dma; + + /* RX is not valid in DIT mode */ + if (mcasp->op_mode != DAVINCI_MCASP_DIT_MODE) { + dma_data = &mcasp->dma_data[SNDRV_PCM_STREAM_CAPTURE]; + if (dat) + dma_data->addr = dat->start; + else + dma_data->addr = + mem->start + davinci_mcasp_rxdma_offset(pdata); + + dma = &mcasp->dma_request[SNDRV_PCM_STREAM_CAPTURE]; + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (res) + *dma = res->start; + else + *dma = pdata->rx_dma_channel; + + /* dmaengine filter data for DT and non-DT boot */ + if (pdev->dev.of_node) + dma_data->filter_data = "rx"; + else + dma_data->filter_data = dma; + } + + if (mcasp->version < MCASP_VERSION_3) { + mcasp->fifo_base = DAVINCI_MCASP_V2_AFIFO_BASE; + /* dma_params->dma_addr is pointing to the data port address */ + mcasp->dat_port = true; + } else { + mcasp->fifo_base = DAVINCI_MCASP_V3_AFIFO_BASE; + } + + /* Allocate memory for long enough list for all possible + * scenarios. Maximum number tdm slots is 32 and there cannot + * be more serializers than given in the configuration. The + * serializer directions could be taken into account, but it + * would make code much more complex and save only couple of + * bytes. + */ + mcasp->chconstr[SNDRV_PCM_STREAM_PLAYBACK].list = + devm_kcalloc(mcasp->dev, + 32 + mcasp->num_serializer - 1, + sizeof(unsigned int), + GFP_KERNEL); + + mcasp->chconstr[SNDRV_PCM_STREAM_CAPTURE].list = + devm_kcalloc(mcasp->dev, + 32 + mcasp->num_serializer - 1, + sizeof(unsigned int), + GFP_KERNEL); + + if (!mcasp->chconstr[SNDRV_PCM_STREAM_PLAYBACK].list || + !mcasp->chconstr[SNDRV_PCM_STREAM_CAPTURE].list) { + ret = -ENOMEM; + goto err; + } + + ret = davinci_mcasp_set_ch_constraints(mcasp); + if (ret) + goto err; + + dev_set_drvdata(&pdev->dev, mcasp); + + mcasp_reparent_fck(pdev); + + ret = devm_snd_soc_register_component(&pdev->dev, + &davinci_mcasp_component, + &davinci_mcasp_dai[pdata->op_mode], 1); + + if (ret != 0) + goto err; + + ret = davinci_mcasp_get_dma_type(mcasp); + switch (ret) { + case PCM_EDMA: +#if IS_BUILTIN(CONFIG_SND_EDMA_SOC) || \ + (IS_MODULE(CONFIG_SND_DAVINCI_SOC_MCASP) && \ + IS_MODULE(CONFIG_SND_EDMA_SOC)) + ret = edma_pcm_platform_register(&pdev->dev); +#else + dev_err(&pdev->dev, "Missing SND_EDMA_SOC\n"); + ret = -EINVAL; + goto err; +#endif + break; + case PCM_SDMA: +#if IS_BUILTIN(CONFIG_SND_SDMA_SOC) || \ + (IS_MODULE(CONFIG_SND_DAVINCI_SOC_MCASP) && \ + IS_MODULE(CONFIG_SND_SDMA_SOC)) + ret = sdma_pcm_platform_register(&pdev->dev, NULL, NULL); +#else + dev_err(&pdev->dev, "Missing SND_SDMA_SOC\n"); + ret = -EINVAL; + goto err; +#endif + break; + default: + dev_err(&pdev->dev, "No DMA controller found (%d)\n", ret); + case -EPROBE_DEFER: + goto err; + break; + } + + if (ret) { + dev_err(&pdev->dev, "register PCM failed: %d\n", ret); + goto err; + } + + return 0; + +err: + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int davinci_mcasp_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver davinci_mcasp_driver = { + .probe = davinci_mcasp_probe, + .remove = davinci_mcasp_remove, + .driver = { + .name = "davinci-mcasp", + .of_match_table = mcasp_dt_ids, + }, +}; + +module_platform_driver(davinci_mcasp_driver); + +MODULE_AUTHOR("Steve Chen"); +MODULE_DESCRIPTION("TI DAVINCI McASP SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/davinci-mcasp.h b/sound/soc/davinci/davinci-mcasp.h new file mode 100644 index 000000000..afddc8010 --- /dev/null +++ b/sound/soc/davinci/davinci-mcasp.h @@ -0,0 +1,314 @@ +/* + * ALSA SoC McASP Audio Layer for TI DAVINCI processor + * + * MCASP related definitions + * + * Author: Nirmal Pandey , + * Suresh Rajashekara + * Steve Chen + * + * Copyright: (C) 2009 MontaVista Software, Inc., + * Copyright: (C) 2009 Texas Instruments, India + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef DAVINCI_MCASP_H +#define DAVINCI_MCASP_H + +/* + * McASP register definitions + */ +#define DAVINCI_MCASP_PID_REG 0x00 +#define DAVINCI_MCASP_PWREMUMGT_REG 0x04 + +#define DAVINCI_MCASP_PFUNC_REG 0x10 +#define DAVINCI_MCASP_PDIR_REG 0x14 +#define DAVINCI_MCASP_PDOUT_REG 0x18 +#define DAVINCI_MCASP_PDSET_REG 0x1c + +#define DAVINCI_MCASP_PDCLR_REG 0x20 + +#define DAVINCI_MCASP_TLGC_REG 0x30 +#define DAVINCI_MCASP_TLMR_REG 0x34 + +#define DAVINCI_MCASP_GBLCTL_REG 0x44 +#define DAVINCI_MCASP_AMUTE_REG 0x48 +#define DAVINCI_MCASP_LBCTL_REG 0x4c + +#define DAVINCI_MCASP_TXDITCTL_REG 0x50 + +#define DAVINCI_MCASP_GBLCTLR_REG 0x60 +#define DAVINCI_MCASP_RXMASK_REG 0x64 +#define DAVINCI_MCASP_RXFMT_REG 0x68 +#define DAVINCI_MCASP_RXFMCTL_REG 0x6c + +#define DAVINCI_MCASP_ACLKRCTL_REG 0x70 +#define DAVINCI_MCASP_AHCLKRCTL_REG 0x74 +#define DAVINCI_MCASP_RXTDM_REG 0x78 +#define DAVINCI_MCASP_EVTCTLR_REG 0x7c + +#define DAVINCI_MCASP_RXSTAT_REG 0x80 +#define DAVINCI_MCASP_RXTDMSLOT_REG 0x84 +#define DAVINCI_MCASP_RXCLKCHK_REG 0x88 +#define DAVINCI_MCASP_REVTCTL_REG 0x8c + +#define DAVINCI_MCASP_GBLCTLX_REG 0xa0 +#define DAVINCI_MCASP_TXMASK_REG 0xa4 +#define DAVINCI_MCASP_TXFMT_REG 0xa8 +#define DAVINCI_MCASP_TXFMCTL_REG 0xac + +#define DAVINCI_MCASP_ACLKXCTL_REG 0xb0 +#define DAVINCI_MCASP_AHCLKXCTL_REG 0xb4 +#define DAVINCI_MCASP_TXTDM_REG 0xb8 +#define DAVINCI_MCASP_EVTCTLX_REG 0xbc + +#define DAVINCI_MCASP_TXSTAT_REG 0xc0 +#define DAVINCI_MCASP_TXTDMSLOT_REG 0xc4 +#define DAVINCI_MCASP_TXCLKCHK_REG 0xc8 +#define DAVINCI_MCASP_XEVTCTL_REG 0xcc + +/* Left(even TDM Slot) Channel Status Register File */ +#define DAVINCI_MCASP_DITCSRA_REG 0x100 +/* Right(odd TDM slot) Channel Status Register File */ +#define DAVINCI_MCASP_DITCSRB_REG 0x118 +/* Left(even TDM slot) User Data Register File */ +#define DAVINCI_MCASP_DITUDRA_REG 0x130 +/* Right(odd TDM Slot) User Data Register File */ +#define DAVINCI_MCASP_DITUDRB_REG 0x148 + +/* Serializer n Control Register */ +#define DAVINCI_MCASP_XRSRCTL_BASE_REG 0x180 +#define DAVINCI_MCASP_XRSRCTL_REG(n) (DAVINCI_MCASP_XRSRCTL_BASE_REG + \ + (n << 2)) + +/* Transmit Buffer for Serializer n */ +#define DAVINCI_MCASP_TXBUF_REG(n) (0x200 + (n << 2)) +/* Receive Buffer for Serializer n */ +#define DAVINCI_MCASP_RXBUF_REG(n) (0x280 + (n << 2)) + +/* McASP FIFO Registers */ +#define DAVINCI_MCASP_V2_AFIFO_BASE (0x1010) +#define DAVINCI_MCASP_V3_AFIFO_BASE (0x1000) + +/* FIFO register offsets from AFIFO base */ +#define MCASP_WFIFOCTL_OFFSET (0x0) +#define MCASP_WFIFOSTS_OFFSET (0x4) +#define MCASP_RFIFOCTL_OFFSET (0x8) +#define MCASP_RFIFOSTS_OFFSET (0xc) + +/* + * DAVINCI_MCASP_PWREMUMGT_REG - Power Down and Emulation Management + * Register Bits + */ +#define MCASP_FREE BIT(0) +#define MCASP_SOFT BIT(1) + +/* + * DAVINCI_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits + */ +#define AXR(n) (1< + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "edma-pcm.h" +#include "davinci-i2s.h" + +#define MOD_REG_BIT(val, mask, set) do { \ + if (set) { \ + val |= mask; \ + } else { \ + val &= ~mask; \ + } \ +} while (0) + +struct davinci_vcif_dev { + struct davinci_vc *davinci_vc; + struct snd_dmaengine_dai_dma_data dma_data[2]; + int dma_request[2]; +}; + +static void davinci_vcif_start(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_vcif_dev *davinci_vcif_dev = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + u32 w; + + /* Start the sample generator and enable transmitter/receiver */ + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 0); + else + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 0); + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); +} + +static void davinci_vcif_stop(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_vcif_dev *davinci_vcif_dev = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + u32 w; + + /* Reset transmitter/receiver and sample rate/frame sync generators */ + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 1); + else + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 1); + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); +} + +static int davinci_vcif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct davinci_vcif_dev *davinci_vcif_dev = snd_soc_dai_get_drvdata(dai); + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + u32 w; + + /* Restart the codec before setup */ + davinci_vcif_stop(substream); + davinci_vcif_start(substream); + + /* General line settings */ + writel(DAVINCI_VC_CTRL_MASK, davinci_vc->base + DAVINCI_VC_CTRL); + + writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTCLR); + + writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTEN); + + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + + /* Determine xfer data type */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_BITS_8 | + DAVINCI_VC_CTRL_WD_UNSIGNED, 1); + break; + case SNDRV_PCM_FORMAT_S8: + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_WD_BITS_8, 1); + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_UNSIGNED, 0); + break; + case SNDRV_PCM_FORMAT_S16_LE: + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_BITS_8 | + DAVINCI_VC_CTRL_WD_UNSIGNED, 0); + break; + default: + printk(KERN_WARNING "davinci-vcif: unsupported PCM format"); + return -EINVAL; + } + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); + + return 0; +} + +static int davinci_vcif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + davinci_vcif_start(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + davinci_vcif_stop(substream); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +#define DAVINCI_VCIF_RATES SNDRV_PCM_RATE_8000_48000 + +static const struct snd_soc_dai_ops davinci_vcif_dai_ops = { + .trigger = davinci_vcif_trigger, + .hw_params = davinci_vcif_hw_params, +}; + +static int davinci_vcif_dai_probe(struct snd_soc_dai *dai) +{ + struct davinci_vcif_dev *dev = snd_soc_dai_get_drvdata(dai); + + dai->playback_dma_data = &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + dai->capture_dma_data = &dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]; + + return 0; +} + +static struct snd_soc_dai_driver davinci_vcif_dai = { + .probe = davinci_vcif_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = DAVINCI_VCIF_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = DAVINCI_VCIF_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &davinci_vcif_dai_ops, + +}; + +static const struct snd_soc_component_driver davinci_vcif_component = { + .name = "davinci-vcif", +}; + +static int davinci_vcif_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = pdev->dev.platform_data; + struct davinci_vcif_dev *davinci_vcif_dev; + int ret; + + davinci_vcif_dev = devm_kzalloc(&pdev->dev, + sizeof(struct davinci_vcif_dev), + GFP_KERNEL); + if (!davinci_vcif_dev) + return -ENOMEM; + + /* DMA tx params */ + davinci_vcif_dev->davinci_vc = davinci_vc; + davinci_vcif_dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].filter_data = + &davinci_vc->davinci_vcif.dma_tx_channel; + davinci_vcif_dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr = + davinci_vc->davinci_vcif.dma_tx_addr; + + /* DMA rx params */ + davinci_vcif_dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].filter_data = + &davinci_vc->davinci_vcif.dma_rx_channel; + davinci_vcif_dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr = + davinci_vc->davinci_vcif.dma_rx_addr; + + dev_set_drvdata(&pdev->dev, davinci_vcif_dev); + + ret = devm_snd_soc_register_component(&pdev->dev, + &davinci_vcif_component, + &davinci_vcif_dai, 1); + if (ret != 0) { + dev_err(&pdev->dev, "could not register dai\n"); + return ret; + } + + ret = edma_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "register PCM failed: %d\n", ret); + return ret; + } + + return 0; +} + +static struct platform_driver davinci_vcif_driver = { + .probe = davinci_vcif_probe, + .driver = { + .name = "davinci-vcif", + }, +}; + +module_platform_driver(davinci_vcif_driver); + +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC Voice Codec Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/edma-pcm.c b/sound/soc/davinci/edma-pcm.c new file mode 100644 index 000000000..59e588abe --- /dev/null +++ b/sound/soc/davinci/edma-pcm.c @@ -0,0 +1,59 @@ +/* + * edma-pcm.c - eDMA PCM driver using dmaengine for AM3xxx, AM4xxx + * + * Copyright (C) 2014 Texas Instruments, Inc. + * + * Author: Peter Ujfalusi + * + * Based on: sound/soc/tegra/tegra_pcm.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "edma-pcm.h" + +static const struct snd_pcm_hardware edma_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_INTERLEAVED, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 64 * 1024, + .periods_min = 2, + .periods_max = 19, /* Limit by edma dmaengine driver */ +}; + +static const struct snd_dmaengine_pcm_config edma_dmaengine_pcm_config = { + .pcm_hardware = &edma_pcm_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .compat_filter_fn = edma_filter_fn, + .prealloc_buffer_size = 128 * 1024, +}; + +int edma_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, &edma_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(edma_pcm_platform_register); + +MODULE_AUTHOR("Peter Ujfalusi "); +MODULE_DESCRIPTION("eDMA PCM ASoC platform driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/edma-pcm.h b/sound/soc/davinci/edma-pcm.h new file mode 100644 index 000000000..b09577448 --- /dev/null +++ b/sound/soc/davinci/edma-pcm.h @@ -0,0 +1,32 @@ +/* + * edma-pcm.h - eDMA PCM driver using dmaengine for AM3xxx, AM4xxx + * + * Copyright (C) 2014 Texas Instruments, Inc. + * + * Author: Peter Ujfalusi + * + * Based on: sound/soc/tegra/tegra_pcm.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __EDMA_PCM_H__ +#define __EDMA_PCM_H__ + +#if IS_ENABLED(CONFIG_SND_EDMA_SOC) +int edma_pcm_platform_register(struct device *dev); +#else +static inline int edma_pcm_platform_register(struct device *dev) +{ + return 0; +} +#endif /* CONFIG_SND_EDMA_SOC */ + +#endif /* __EDMA_PCM_H__ */ diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig new file mode 100644 index 000000000..aa0c6ec4d --- /dev/null +++ b/sound/soc/dwc/Kconfig @@ -0,0 +1,19 @@ +config SND_DESIGNWARE_I2S + tristate "Synopsys I2S Device Driver" + depends on CLKDEV_LOOKUP + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2S driver for + Synopsys designware I2S device. The device supports up to + a maximum of 8 channels each for play and record. + +config SND_DESIGNWARE_PCM + bool "PCM PIO extension for I2S driver" + depends on SND_DESIGNWARE_I2S + help + Say Y or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + + This functionality is specially suited for I2S devices that don't have + DMA support. + diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile new file mode 100644 index 000000000..3e24c0ff9 --- /dev/null +++ b/sound/soc/dwc/Makefile @@ -0,0 +1,5 @@ +# SYNOPSYS Platform Support +obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o + +designware_i2s-y := dwc-i2s.o +designware_i2s-$(CONFIG_SND_DESIGNWARE_PCM) += dwc-pcm.o diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c new file mode 100644 index 000000000..65112b9d8 --- /dev/null +++ b/sound/soc/dwc/dwc-i2s.c @@ -0,0 +1,761 @@ +/* + * ALSA SoC Synopsys I2S Audio Layer + * + * sound/soc/dwc/designware_i2s.c + * + * Copyright (C) 2010 ST Microelectronics + * Rajeev Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "local.h" + +static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val) +{ + writel(val, io_base + reg); +} + +static inline u32 i2s_read_reg(void __iomem *io_base, int reg) +{ + return readl(io_base + reg); +} + +static inline void i2s_disable_channels(struct dw_i2s_dev *dev, u32 stream) +{ + u32 i = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->i2s_base, TER(i), 0); + } else { + for (i = 0; i < 4; i++) + i2s_write_reg(dev->i2s_base, RER(i), 0); + } +} + +static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream) +{ + u32 i = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < 4; i++) + i2s_read_reg(dev->i2s_base, TOR(i)); + } else { + for (i = 0; i < 4; i++) + i2s_read_reg(dev->i2s_base, ROR(i)); + } +} + +static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream, + int chan_nr) +{ + u32 i, irq; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); + } + } else { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); + } + } +} + +static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream, + int chan_nr) +{ + u32 i, irq; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30); + } + } else { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03); + } + } +} + +static irqreturn_t i2s_irq_handler(int irq, void *dev_id) +{ + struct dw_i2s_dev *dev = dev_id; + bool irq_valid = false; + u32 isr[4]; + int i; + + for (i = 0; i < 4; i++) + isr[i] = i2s_read_reg(dev->i2s_base, ISR(i)); + + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK); + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE); + + for (i = 0; i < 4; i++) { + /* + * Check if TX fifo is empty. If empty fill FIFO with samples + * NOTE: Only two channels supported + */ + if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) { + dw_pcm_push_tx(dev); + irq_valid = true; + } + + /* + * Data available. Retrieve samples from FIFO + * NOTE: Only two channels supported + */ + if ((isr[i] & ISR_RXDA) && (i == 0) && dev->use_pio) { + dw_pcm_pop_rx(dev); + irq_valid = true; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_TXFO) { + dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i); + irq_valid = true; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_RXFO) { + dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i); + irq_valid = true; + } + } + + if (irq_valid) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static void i2s_start(struct dw_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + struct i2s_clk_config_data *config = &dev->config; + + i2s_write_reg(dev->i2s_base, IER, 1); + i2s_enable_irqs(dev, substream->stream, config->chan_nr); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 1); + else + i2s_write_reg(dev->i2s_base, IRER, 1); + + i2s_write_reg(dev->i2s_base, CER, 1); +} + +static void i2s_stop(struct dw_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + + i2s_clear_irqs(dev, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 0); + else + i2s_write_reg(dev->i2s_base, IRER, 0); + + i2s_disable_irqs(dev, substream->stream, 8); + + if (!dev->active) { + i2s_write_reg(dev->i2s_base, CER, 0); + i2s_write_reg(dev->i2s_base, IER, 0); + } +} + +static int dw_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + union dw_i2s_snd_dma_data *dma_data = NULL; + + if (!(dev->capability & DWC_I2S_RECORD) && + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)) + return -EINVAL; + + if (!(dev->capability & DWC_I2S_PLAY) && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &dev->play_dma_data; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dma_data = &dev->capture_dma_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data); + + return 0; +} + +static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) +{ + u32 ch_reg; + struct i2s_clk_config_data *config = &dev->config; + + + i2s_disable_channels(dev, stream); + + for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_base, TCR(ch_reg), + dev->xfer_resolution); + i2s_write_reg(dev->i2s_base, TFCR(ch_reg), + dev->fifo_th - 1); + i2s_write_reg(dev->i2s_base, TER(ch_reg), 1); + } else { + i2s_write_reg(dev->i2s_base, RCR(ch_reg), + dev->xfer_resolution); + i2s_write_reg(dev->i2s_base, RFCR(ch_reg), + dev->fifo_th - 1); + i2s_write_reg(dev->i2s_base, RER(ch_reg), 1); + } + + } +} + +static int dw_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + struct i2s_clk_config_data *config = &dev->config; + int ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + config->data_width = 16; + dev->ccr = 0x00; + dev->xfer_resolution = 0x02; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + config->data_width = 24; + dev->ccr = 0x08; + dev->xfer_resolution = 0x04; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + config->data_width = 32; + dev->ccr = 0x10; + dev->xfer_resolution = 0x05; + break; + + default: + dev_err(dev->dev, "designware-i2s: unsupported PCM fmt"); + return -EINVAL; + } + + config->chan_nr = params_channels(params); + + switch (config->chan_nr) { + case EIGHT_CHANNEL_SUPPORT: + case SIX_CHANNEL_SUPPORT: + case FOUR_CHANNEL_SUPPORT: + case TWO_CHANNEL_SUPPORT: + break; + default: + dev_err(dev->dev, "channel not supported\n"); + return -EINVAL; + } + + dw_i2s_config(dev, substream->stream); + + i2s_write_reg(dev->i2s_base, CCR, dev->ccr); + + config->sample_rate = params_rate(params); + + if (dev->capability & DW_I2S_MASTER) { + if (dev->i2s_clk_cfg) { + ret = dev->i2s_clk_cfg(config); + if (ret < 0) { + dev_err(dev->dev, "runtime audio clk config fail\n"); + return ret; + } + } else { + u32 bitclk = config->sample_rate * + config->data_width * 2; + + ret = clk_set_rate(dev->clk, bitclk); + if (ret) { + dev_err(dev->dev, "Can't set I2S clock rate: %d\n", + ret); + return ret; + } + } + } + return 0; +} + +static void dw_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static int dw_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, TXFFR, 1); + else + i2s_write_reg(dev->i2s_base, RXFFR, 1); + + return 0; +} + +static int dw_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev->active++; + i2s_start(dev, substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + i2s_stop(dev, substream); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int dw_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + if (dev->capability & DW_I2S_SLAVE) + ret = 0; + else + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBS_CFS: + if (dev->capability & DW_I2S_MASTER) + ret = 0; + else + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + dev_dbg(dev->dev, "dwc : Invalid master/slave format\n"); + ret = -EINVAL; + break; + } + return ret; +} + +static const struct snd_soc_dai_ops dw_i2s_dai_ops = { + .startup = dw_i2s_startup, + .shutdown = dw_i2s_shutdown, + .hw_params = dw_i2s_hw_params, + .prepare = dw_i2s_prepare, + .trigger = dw_i2s_trigger, + .set_fmt = dw_i2s_set_fmt, +}; + +static const struct snd_soc_component_driver dw_i2s_component = { + .name = "dw-i2s", +}; + +#ifdef CONFIG_PM +static int dw_i2s_runtime_suspend(struct device *dev) +{ + struct dw_i2s_dev *dw_dev = dev_get_drvdata(dev); + + if (dw_dev->capability & DW_I2S_MASTER) + clk_disable(dw_dev->clk); + return 0; +} + +static int dw_i2s_runtime_resume(struct device *dev) +{ + struct dw_i2s_dev *dw_dev = dev_get_drvdata(dev); + + if (dw_dev->capability & DW_I2S_MASTER) + clk_enable(dw_dev->clk); + return 0; +} + +static int dw_i2s_suspend(struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (dev->capability & DW_I2S_MASTER) + clk_disable(dev->clk); + return 0; +} + +static int dw_i2s_resume(struct snd_soc_dai *dai) +{ + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (dev->capability & DW_I2S_MASTER) + clk_enable(dev->clk); + + if (dai->playback_active) + dw_i2s_config(dev, SNDRV_PCM_STREAM_PLAYBACK); + if (dai->capture_active) + dw_i2s_config(dev, SNDRV_PCM_STREAM_CAPTURE); + return 0; +} + +#else +#define dw_i2s_suspend NULL +#define dw_i2s_resume NULL +#endif + +/* + * The following tables allow a direct lookup of various parameters + * defined in the I2S block's configuration in terms of sound system + * parameters. Each table is sized to the number of entries possible + * according to the number of configuration bits describing an I2S + * block parameter. + */ + +/* Maximum bit resolution of a channel - not uniformly spaced */ +static const u32 fifo_width[COMP_MAX_WORDSIZE] = { + 12, 16, 20, 24, 32, 0, 0, 0 +}; + +/* Width of (DMA) bus */ +static const u32 bus_widths[COMP_MAX_DATA_WIDTH] = { + DMA_SLAVE_BUSWIDTH_1_BYTE, + DMA_SLAVE_BUSWIDTH_2_BYTES, + DMA_SLAVE_BUSWIDTH_4_BYTES, + DMA_SLAVE_BUSWIDTH_UNDEFINED +}; + +/* PCM format to support channel resolution */ +static const u32 formats[COMP_MAX_WORDSIZE] = { + SNDRV_PCM_FMTBIT_S16_LE, + SNDRV_PCM_FMTBIT_S16_LE, + SNDRV_PCM_FMTBIT_S24_LE, + SNDRV_PCM_FMTBIT_S24_LE, + SNDRV_PCM_FMTBIT_S32_LE, + 0, + 0, + 0 +}; + +static int dw_configure_dai(struct dw_i2s_dev *dev, + struct snd_soc_dai_driver *dw_i2s_dai, + unsigned int rates) +{ + /* + * Read component parameter registers to extract + * the I2S block's configuration. + */ + u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1); + u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2); + u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); + u32 idx; + + if (dev->capability & DWC_I2S_RECORD && + dev->quirks & DW_I2S_QUIRK_COMP_PARAM1) + comp1 = comp1 & ~BIT(5); + + if (dev->capability & DWC_I2S_PLAY && + dev->quirks & DW_I2S_QUIRK_COMP_PARAM1) + comp1 = comp1 & ~BIT(6); + + if (COMP1_TX_ENABLED(comp1)) { + dev_dbg(dev->dev, " designware: play supported\n"); + idx = COMP1_TX_WORDSIZE_0(comp1); + if (WARN_ON(idx >= ARRAY_SIZE(formats))) + return -EINVAL; + if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE) + idx = 1; + dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM; + dw_i2s_dai->playback.channels_max = + 1 << (COMP1_TX_CHANNELS(comp1) + 1); + dw_i2s_dai->playback.formats = formats[idx]; + dw_i2s_dai->playback.rates = rates; + } + + if (COMP1_RX_ENABLED(comp1)) { + dev_dbg(dev->dev, "designware: record supported\n"); + idx = COMP2_RX_WORDSIZE_0(comp2); + if (WARN_ON(idx >= ARRAY_SIZE(formats))) + return -EINVAL; + if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE) + idx = 1; + dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM; + dw_i2s_dai->capture.channels_max = + 1 << (COMP1_RX_CHANNELS(comp1) + 1); + dw_i2s_dai->capture.formats = formats[idx]; + dw_i2s_dai->capture.rates = rates; + } + + if (COMP1_MODE_EN(comp1)) { + dev_dbg(dev->dev, "designware: i2s master mode supported\n"); + dev->capability |= DW_I2S_MASTER; + } else { + dev_dbg(dev->dev, "designware: i2s slave mode supported\n"); + dev->capability |= DW_I2S_SLAVE; + } + + dev->fifo_th = fifo_depth / 2; + return 0; +} + +static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev, + struct snd_soc_dai_driver *dw_i2s_dai, + struct resource *res, + const struct i2s_platform_data *pdata) +{ + u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1); + u32 idx = COMP1_APB_DATA_WIDTH(comp1); + int ret; + + if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) + return -EINVAL; + + ret = dw_configure_dai(dev, dw_i2s_dai, pdata->snd_rates); + if (ret < 0) + return ret; + + if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE) + idx = 1; + /* Set DMA slaves info */ + dev->play_dma_data.pd.data = pdata->play_dma_data; + dev->capture_dma_data.pd.data = pdata->capture_dma_data; + dev->play_dma_data.pd.addr = res->start + I2S_TXDMA; + dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA; + dev->play_dma_data.pd.max_burst = 16; + dev->capture_dma_data.pd.max_burst = 16; + dev->play_dma_data.pd.addr_width = bus_widths[idx]; + dev->capture_dma_data.pd.addr_width = bus_widths[idx]; + dev->play_dma_data.pd.filter = pdata->filter; + dev->capture_dma_data.pd.filter = pdata->filter; + + return 0; +} + +static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, + struct snd_soc_dai_driver *dw_i2s_dai, + struct resource *res) +{ + u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1); + u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2); + u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); + u32 idx = COMP1_APB_DATA_WIDTH(comp1); + u32 idx2; + int ret; + + if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) + return -EINVAL; + + ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000); + if (ret < 0) + return ret; + + if (COMP1_TX_ENABLED(comp1)) { + idx2 = COMP1_TX_WORDSIZE_0(comp1); + + dev->capability |= DWC_I2S_PLAY; + dev->play_dma_data.dt.addr = res->start + I2S_TXDMA; + dev->play_dma_data.dt.addr_width = bus_widths[idx]; + dev->play_dma_data.dt.fifo_size = fifo_depth * + (fifo_width[idx2]) >> 8; + dev->play_dma_data.dt.maxburst = 16; + } + if (COMP1_RX_ENABLED(comp1)) { + idx2 = COMP2_RX_WORDSIZE_0(comp2); + + dev->capability |= DWC_I2S_RECORD; + dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA; + dev->capture_dma_data.dt.addr_width = bus_widths[idx]; + dev->capture_dma_data.dt.fifo_size = fifo_depth * + (fifo_width[idx2] >> 8); + dev->capture_dma_data.dt.maxburst = 16; + } + + return 0; + +} + +static int dw_i2s_probe(struct platform_device *pdev) +{ + const struct i2s_platform_data *pdata = pdev->dev.platform_data; + struct dw_i2s_dev *dev; + struct resource *res; + int ret, irq; + struct snd_soc_dai_driver *dw_i2s_dai; + const char *clk_id; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL); + if (!dw_i2s_dai) + return -ENOMEM; + + dw_i2s_dai->ops = &dw_i2s_dai_ops; + dw_i2s_dai->suspend = dw_i2s_suspend; + dw_i2s_dai->resume = dw_i2s_resume; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->i2s_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->i2s_base)) + return PTR_ERR(dev->i2s_base); + + dev->dev = &pdev->dev; + + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + } + + dev->i2s_reg_comp1 = I2S_COMP_PARAM_1; + dev->i2s_reg_comp2 = I2S_COMP_PARAM_2; + if (pdata) { + dev->capability = pdata->cap; + clk_id = NULL; + dev->quirks = pdata->quirks; + if (dev->quirks & DW_I2S_QUIRK_COMP_REG_OFFSET) { + dev->i2s_reg_comp1 = pdata->i2s_reg_comp1; + dev->i2s_reg_comp2 = pdata->i2s_reg_comp2; + } + ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata); + } else { + clk_id = "i2sclk"; + ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res); + } + if (ret < 0) + return ret; + + if (dev->capability & DW_I2S_MASTER) { + if (pdata) { + dev->i2s_clk_cfg = pdata->i2s_clk_cfg; + if (!dev->i2s_clk_cfg) { + dev_err(&pdev->dev, "no clock configure method\n"); + return -ENODEV; + } + } + dev->clk = devm_clk_get(&pdev->dev, clk_id); + + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + ret = clk_prepare_enable(dev->clk); + if (ret < 0) + return ret; + } + + dev_set_drvdata(&pdev->dev, dev); + ret = devm_snd_soc_register_component(&pdev->dev, &dw_i2s_component, + dw_i2s_dai, 1); + if (ret != 0) { + dev_err(&pdev->dev, "not able to register dai\n"); + goto err_clk_disable; + } + + if (!pdata) { + if (irq >= 0) { + ret = dw_pcm_register(pdev); + dev->use_pio = true; + } else { + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + dev->use_pio = false; + } + + if (ret) { + dev_err(&pdev->dev, "could not register pcm: %d\n", + ret); + goto err_clk_disable; + } + } + + pm_runtime_enable(&pdev->dev); + return 0; + +err_clk_disable: + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk); + return ret; +} + +static int dw_i2s_remove(struct platform_device *pdev) +{ + struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev); + + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk); + + pm_runtime_disable(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id dw_i2s_of_match[] = { + { .compatible = "snps,designware-i2s", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dw_i2s_of_match); +#endif + +static const struct dev_pm_ops dwc_pm_ops = { + SET_RUNTIME_PM_OPS(dw_i2s_runtime_suspend, dw_i2s_runtime_resume, NULL) +}; + +static struct platform_driver dw_i2s_driver = { + .probe = dw_i2s_probe, + .remove = dw_i2s_remove, + .driver = { + .name = "designware-i2s", + .of_match_table = of_match_ptr(dw_i2s_of_match), + .pm = &dwc_pm_ops, + }, +}; + +module_platform_driver(dw_i2s_driver); + +MODULE_AUTHOR("Rajeev Kumar "); +MODULE_DESCRIPTION("DESIGNWARE I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:designware_i2s"); diff --git a/sound/soc/dwc/dwc-pcm.c b/sound/soc/dwc/dwc-pcm.c new file mode 100644 index 000000000..2cc963202 --- /dev/null +++ b/sound/soc/dwc/dwc-pcm.c @@ -0,0 +1,282 @@ +/* + * ALSA SoC Synopsys PIO PCM for I2S driver + * + * sound/soc/dwc/designware_pcm.c + * + * Copyright (C) 2016 Synopsys + * Jose Abreu + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include "local.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +#define dw_pcm_tx_fn(sample_bits) \ +static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \ + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \ + bool *period_elapsed) \ +{ \ + const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ + unsigned int period_pos = tx_ptr % runtime->period_size; \ + int i; \ +\ + for (i = 0; i < dev->fifo_th; i++) { \ + iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \ + iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \ + period_pos++; \ + if (++tx_ptr >= runtime->buffer_size) \ + tx_ptr = 0; \ + } \ + *period_elapsed = period_pos >= runtime->period_size; \ + return tx_ptr; \ +} + +#define dw_pcm_rx_fn(sample_bits) \ +static unsigned int dw_pcm_rx_##sample_bits(struct dw_i2s_dev *dev, \ + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \ + bool *period_elapsed) \ +{ \ + u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ + unsigned int period_pos = rx_ptr % runtime->period_size; \ + int i; \ +\ + for (i = 0; i < dev->fifo_th; i++) { \ + p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \ + p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \ + period_pos++; \ + if (++rx_ptr >= runtime->buffer_size) \ + rx_ptr = 0; \ + } \ + *period_elapsed = period_pos >= runtime->period_size; \ + return rx_ptr; \ +} + +dw_pcm_tx_fn(16); +dw_pcm_tx_fn(32); +dw_pcm_rx_fn(16); +dw_pcm_rx_fn(32); + +#undef dw_pcm_tx_fn +#undef dw_pcm_rx_fn + +static const struct snd_pcm_hardware dw_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .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, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 16, +}; + +static void dw_pcm_transfer(struct dw_i2s_dev *dev, bool push) +{ + struct snd_pcm_substream *substream; + bool active, period_elapsed; + + rcu_read_lock(); + if (push) + substream = rcu_dereference(dev->tx_substream); + else + substream = rcu_dereference(dev->rx_substream); + active = substream && snd_pcm_running(substream); + if (active) { + unsigned int ptr; + unsigned int new_ptr; + + if (push) { + ptr = READ_ONCE(dev->tx_ptr); + new_ptr = dev->tx_fn(dev, substream->runtime, ptr, + &period_elapsed); + cmpxchg(&dev->tx_ptr, ptr, new_ptr); + } else { + ptr = READ_ONCE(dev->rx_ptr); + new_ptr = dev->rx_fn(dev, substream->runtime, ptr, + &period_elapsed); + cmpxchg(&dev->rx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); + } + rcu_read_unlock(); +} + +void dw_pcm_push_tx(struct dw_i2s_dev *dev) +{ + dw_pcm_transfer(dev, true); +} + +void dw_pcm_pop_rx(struct dw_i2s_dev *dev) +{ + dw_pcm_transfer(dev, false); +} + +static int dw_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + +static int dw_pcm_close(struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int dw_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + int ret; + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dev->tx_fn = dw_pcm_tx_16; + dev->rx_fn = dw_pcm_rx_16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + dev->tx_fn = dw_pcm_tx_32; + dev->rx_fn = dw_pcm_rx_32; + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + else + return 0; +} + +static int dw_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + } else { + WRITE_ONCE(dev->rx_ptr, 0); + rcu_assign_pointer(dev->rx_substream, substream); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rcu_assign_pointer(dev->tx_substream, NULL); + else + rcu_assign_pointer(dev->rx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = READ_ONCE(dev->tx_ptr); + else + pos = READ_ONCE(dev->rx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int dw_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + size_t size = dw_pcm_hardware.buffer_bytes_max; + + return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), size, size); +} + +static void dw_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static const struct snd_pcm_ops dw_pcm_ops = { + .open = dw_pcm_open, + .close = dw_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dw_pcm_hw_params, + .hw_free = dw_pcm_hw_free, + .trigger = dw_pcm_trigger, + .pointer = dw_pcm_pointer, +}; + +static const struct snd_soc_component_driver dw_pcm_component = { + .pcm_new = dw_pcm_new, + .pcm_free = dw_pcm_free, + .ops = &dw_pcm_ops, +}; + +int dw_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component, + NULL, 0); +} diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h new file mode 100644 index 000000000..91dc70a82 --- /dev/null +++ b/sound/soc/dwc/local.h @@ -0,0 +1,135 @@ +/* + * Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com) + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __DESIGNWARE_LOCAL_H +#define __DESIGNWARE_LOCAL_H + +#include +#include +#include +#include +#include +#include + +/* common register for all channel */ +#define IER 0x000 +#define IRER 0x004 +#define ITER 0x008 +#define CER 0x00C +#define CCR 0x010 +#define RXFFR 0x014 +#define TXFFR 0x018 + +/* Interrupt status register fields */ +#define ISR_TXFO BIT(5) +#define ISR_TXFE BIT(4) +#define ISR_RXFO BIT(1) +#define ISR_RXDA BIT(0) + +/* I2STxRxRegisters for all channels */ +#define LRBR_LTHR(x) (0x40 * x + 0x020) +#define RRBR_RTHR(x) (0x40 * x + 0x024) +#define RER(x) (0x40 * x + 0x028) +#define TER(x) (0x40 * x + 0x02C) +#define RCR(x) (0x40 * x + 0x030) +#define TCR(x) (0x40 * x + 0x034) +#define ISR(x) (0x40 * x + 0x038) +#define IMR(x) (0x40 * x + 0x03C) +#define ROR(x) (0x40 * x + 0x040) +#define TOR(x) (0x40 * x + 0x044) +#define RFCR(x) (0x40 * x + 0x048) +#define TFCR(x) (0x40 * x + 0x04C) +#define RFF(x) (0x40 * x + 0x050) +#define TFF(x) (0x40 * x + 0x054) + +/* I2SCOMPRegisters */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +/* + * Component parameter register fields - define the I2S block's + * configuration. + */ +#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25) +#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22) +#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19) +#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16) +#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9) +#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7) +#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6) +#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5) +#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4) +#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2) +#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0) + +#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10) +#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7) +#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3) +#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0) + +/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ +#define COMP_MAX_WORDSIZE (1 << 3) +#define COMP_MAX_DATA_WIDTH (1 << 2) + +#define MAX_CHANNEL_NUM 8 +#define MIN_CHANNEL_NUM 2 + +union dw_i2s_snd_dma_data { + struct i2s_dma_data pd; + struct snd_dmaengine_dai_dma_data dt; +}; + +struct dw_i2s_dev { + void __iomem *i2s_base; + struct clk *clk; + int active; + unsigned int capability; + unsigned int quirks; + unsigned int i2s_reg_comp1; + unsigned int i2s_reg_comp2; + struct device *dev; + u32 ccr; + u32 xfer_resolution; + u32 fifo_th; + + /* data related to DMA transfers b/w i2s and DMAC */ + union dw_i2s_snd_dma_data play_dma_data; + union dw_i2s_snd_dma_data capture_dma_data; + struct i2s_clk_config_data config; + int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); + + /* data related to PIO transfers */ + bool use_pio; + struct snd_pcm_substream __rcu *tx_substream; + struct snd_pcm_substream __rcu *rx_substream; + unsigned int (*tx_fn)(struct dw_i2s_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed); + unsigned int (*rx_fn)(struct dw_i2s_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, + bool *period_elapsed); + unsigned int tx_ptr; + unsigned int rx_ptr; +}; + +#if IS_ENABLED(CONFIG_SND_DESIGNWARE_PCM) +void dw_pcm_push_tx(struct dw_i2s_dev *dev); +void dw_pcm_pop_rx(struct dw_i2s_dev *dev); +int dw_pcm_register(struct platform_device *pdev); +#else +void dw_pcm_push_tx(struct dw_i2s_dev *dev) { } +void dw_pcm_pop_rx(struct dw_i2s_dev *dev) { } +int dw_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig new file mode 100644 index 000000000..f721cd4e3 --- /dev/null +++ b/sound/soc/fsl/Kconfig @@ -0,0 +1,293 @@ +menu "SoC Audio for Freescale CPUs" + +comment "Common SoC Audio options for Freescale CPUs:" + +config SND_SOC_FSL_ASRC + tristate "Asynchronous Sample Rate Converter (ASRC) module support" + depends on HAS_DMA + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Asynchronous Sample Rate Converter (ASRC) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_SAI + tristate "Synchronous Audio Interface (SAI) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Synchronous Audio Interface (SAI) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_SSI + tristate "Synchronous Serial Interface module (SSI) support" + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) + select REGMAP_MMIO + help + Say Y if you want to add Synchronous Serial Interface (SSI) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_SPDIF + tristate "Sony/Philips Digital Interface (S/PDIF) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) + select BITREVERSE + help + Say Y if you want to add Sony/Philips Digital Interface (SPDIF) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_ESAI + tristate "Enhanced Serial Audio Interface (ESAI) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + help + Say Y if you want to add Enhanced Synchronous Audio Interface + (ESAI) support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_UTILS + tristate + +config SND_SOC_IMX_PCM_DMA + tristate + select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_SOC_IMX_AUDMUX + tristate "Digital Audio Mux module support" + help + Say Y if you want to add Digital Audio Mux (AUDMUX) support + for the ARM i.MX CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_POWERPC_SOC + tristate "SoC Audio for Freescale PowerPC CPUs" + depends on FSL_SOC || PPC_MPC52xx + help + Say Y or M if you want to add support for codecs attached to + the PowerPC CPUs. + +config SND_IMX_SOC + tristate "SoC Audio for Freescale i.MX CPUs" + depends on ARCH_MXC || COMPILE_TEST + help + Say Y or M if you want to add support for codecs attached to + the i.MX CPUs. + +if SND_POWERPC_SOC + +config SND_MPC52xx_DMA + tristate + +config SND_SOC_POWERPC_DMA + tristate + +comment "SoC Audio support for Freescale PPC boards:" + +config SND_SOC_MPC8610_HPCD + tristate "ALSA SoC support for the Freescale MPC8610 HPCD board" + # I2C is necessary for the CS4270 driver + depends on MPC8610_HPCD && I2C + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA + select SND_SOC_CS4270 + select SND_SOC_CS4270_VD33_ERRATA + default y if MPC8610_HPCD + help + Say Y if you want to enable audio on the Freescale MPC8610 HPCD. + +config SND_SOC_P1022_DS + tristate "ALSA SoC support for the Freescale P1022 DS board" + # I2C is necessary for the WM8776 driver + depends on P1022_DS && I2C + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA + select SND_SOC_WM8776 + default y if P1022_DS + help + Say Y if you want to enable audio on the Freescale P1022 DS board. + This will also include the Wolfson Microelectronics WM8776 codec + driver. + +config SND_SOC_P1022_RDK + tristate "ALSA SoC support for the Freescale / iVeia P1022 RDK board" + # I2C is necessary for the WM8960 driver + depends on P1022_RDK && I2C + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA + select SND_SOC_WM8960 + default y if P1022_RDK + help + Say Y if you want to enable audio on the Freescale / iVeia + P1022 RDK board. This will also include the Wolfson + Microelectronics WM8960 codec driver. + +config SND_SOC_MPC5200_I2S + tristate "Freescale MPC5200 PSC in I2S mode driver" + depends on PPC_MPC52xx && PPC_BESTCOMM + select SND_MPC52xx_DMA + select PPC_BESTCOMM_GEN_BD + help + Say Y here to support the MPC5200 PSCs in I2S mode. + +config SND_SOC_MPC5200_AC97 + tristate "Freescale MPC5200 PSC in AC97 mode driver" + depends on PPC_MPC52xx && PPC_BESTCOMM + select SND_SOC_AC97_BUS + select SND_MPC52xx_DMA + select PPC_BESTCOMM_GEN_BD + help + Say Y here to support the MPC5200 PSCs in AC97 mode. + +config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE + select SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 + baseboard. + +config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA + select SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. + +endif # SND_POWERPC_SOC + +config SND_SOC_IMX_PCM_FIQ + tristate + default y if SND_SOC_IMX_SSI=y && (SND_SOC_FSL_SSI=m || SND_SOC_FSL_SPDIF=m) && (MXC_TZIC || MXC_AVIC) + select FIQ + +if SND_IMX_SOC + +config SND_SOC_IMX_SSI + tristate + select SND_SOC_FSL_UTILS + +comment "SoC Audio support for Freescale i.MX boards:" + +config SND_MXC_SOC_WM1133_EV1 + tristate "Audio on the i.MX31ADS with WM1133-EV1 fitted" + depends on MACH_MX31ADS_WM1133_EV1 + select SND_SOC_WM8350 + select SND_SOC_IMX_PCM_FIQ + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Enable support for audio on the i.MX31ADS with the WM1133-EV1 + PMIC board with WM8835x fitted. + +config SND_SOC_MX27VIS_AIC32X4 + tristate "SoC audio support for Visstrim M10 boards" + depends on MACH_IMX27_VISSTRIM_M10 && I2C + select SND_SOC_TLV320AIC32X4 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Say Y if you want to add support for SoC audio on Visstrim SM10 + board with TLV320AIC32X4 codec. + +config SND_SOC_PHYCORE_AC97 + tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" + depends on MACH_PCM043 || MACH_PCA100 + select SND_SOC_AC97_BUS + select SND_SOC_WM9712 + select SND_SOC_IMX_PCM_FIQ + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Say Y if you want to add support for SoC audio on Phytec phyCORE + and phyCARD boards in AC97 mode + +config SND_SOC_EUKREA_TLV320 + tristate "Eukrea TLV320" + depends on ARCH_MXC && !ARM64 && I2C + select SND_SOC_TLV320AIC23_I2C + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + select SND_SOC_FSL_SSI + select SND_SOC_IMX_PCM_DMA + help + Enable I2S based access to the TLV320AIC23B codec attached + to the SSI interface + +config SND_SOC_IMX_ES8328 + tristate "SoC Audio support for i.MX boards with the ES8328 codec" + depends on OF && (I2C || SPI) + select SND_SOC_ES8328_I2C if I2C + select SND_SOC_ES8328_SPI if SPI_MASTER + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + help + Say Y if you want to add support for the ES8328 audio codec connected + via SSI/I2S over either SPI or I2C. + +config SND_SOC_IMX_SGTL5000 + tristate "SoC Audio support for i.MX boards with sgtl5000" + depends on OF && I2C + select SND_SOC_SGTL5000 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + help + Say Y if you want to add support for SoC audio on an i.MX board with + a sgtl5000 codec. + +config SND_SOC_IMX_SPDIF + tristate "SoC Audio support for i.MX boards with S/PDIF" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SPDIF + help + SoC Audio support for i.MX boards with S/PDIF + Say Y if you want to add support for SoC audio on an i.MX board with + a S/DPDIF. + +config SND_SOC_IMX_MC13783 + tristate "SoC Audio support for I.MX boards with mc13783" + depends on MFD_MC13XXX && ARM + select SND_SOC_IMX_SSI + select SND_SOC_IMX_AUDMUX + select SND_SOC_MC13783 + select SND_SOC_IMX_PCM_DMA + +config SND_SOC_FSL_ASOC_CARD + tristate "Generic ASoC Sound Card with ASRC support" + depends on OF && I2C + # enforce SND_SOC_FSL_ASOC_CARD=m if SND_AC97_CODEC=m: + depends on SND_AC97_CODEC || SND_AC97_CODEC=n + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_ESAI + select SND_SOC_FSL_SAI + select SND_SOC_FSL_SSI + help + ALSA SoC Audio support with ASRC feature for Freescale SoCs that have + ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888, + CS4271, CS4272 and SGTL5000. + Say Y if you want to add support for Freescale Generic ASoC Sound Card. + +endif # SND_IMX_SOC + +endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile new file mode 100644 index 000000000..de94fa057 --- /dev/null +++ b/sound/soc/fsl/Makefile @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0 +# MPC8610 HPCD Machine Support +snd-soc-mpc8610-hpcd-objs := mpc8610_hpcd.o +obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o + +# P1022 DS Machine Support +snd-soc-p1022-ds-objs := p1022_ds.o +obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o + +# P1022 RDK Machine Support +snd-soc-p1022-rdk-objs := p1022_rdk.o +obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o + +# Freescale SSI/DMA/SAI/SPDIF Support +snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o +snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o +snd-soc-fsl-sai-objs := fsl_sai.o +snd-soc-fsl-ssi-y := fsl_ssi.o +snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o +snd-soc-fsl-spdif-objs := fsl_spdif.o +snd-soc-fsl-esai-objs := fsl_esai.o +snd-soc-fsl-utils-objs := fsl_utils.o +snd-soc-fsl-dma-objs := fsl_dma.o +obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o +obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o +obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o +obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o +obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o +obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o +obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o +obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o + +# MPC5200 Platform Support +obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o +obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o +obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o + +# MPC5200 Machine Support +obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o +obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o + +# i.MX Platform Support +snd-soc-imx-ssi-objs := imx-ssi.o +snd-soc-imx-audmux-objs := imx-audmux.o +obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o +obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o + +obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o +obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o + +# i.MX Machine Support +snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o +snd-soc-phycore-ac97-objs := phycore-ac97.o +snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o +snd-soc-wm1133-ev1-objs := wm1133-ev1.o +snd-soc-imx-es8328-objs := imx-es8328.o +snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-spdif-objs := imx-spdif.o +snd-soc-imx-mc13783-objs := imx-mc13783.o + +obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o +obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o +obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o +obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o +obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o +obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o +obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c new file mode 100644 index 000000000..667f4215d --- /dev/null +++ b/sound/soc/fsl/efika-audio-fabric.c @@ -0,0 +1,89 @@ +/* + * Efika driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mpc5200_dma.h" + +#define DRV_NAME "efika-audio-fabric" + +static struct snd_soc_dai_link efika_fabric_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 Analog", + .codec_dai_name = "stac9766-hifi-analog", + .cpu_dai_name = "mpc5200-psc-ac97.0", + .platform_name = "mpc5200-pcm-audio", + .codec_name = "stac9766-codec", +}, +{ + .name = "AC97", + .stream_name = "AC97 IEC958", + .codec_dai_name = "stac9766-hifi-IEC958", + .cpu_dai_name = "mpc5200-psc-ac97.1", + .platform_name = "mpc5200-pcm-audio", + .codec_name = "stac9766-codec", +}, +}; + +static struct snd_soc_card card = { + .name = "Efika", + .owner = THIS_MODULE, + .dai_link = efika_fabric_dai, + .num_links = ARRAY_SIZE(efika_fabric_dai), +}; + +static __init int efika_fabric_init(void) +{ + struct platform_device *pdev; + int rc; + + if (!of_machine_is_compatible("bplan,efika")) + return -ENODEV; + + pdev = platform_device_alloc("soc-audio", 1); + if (!pdev) { + pr_err("efika_fabric_init: platform_device_alloc() failed\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, &card); + + rc = platform_device_add(pdev); + if (rc) { + pr_err("efika_fabric_init: platform_device_add() failed\n"); + platform_device_put(pdev); + return -ENODEV; + } + return 0; +} + +module_init(efika_fabric_init); + + +MODULE_AUTHOR("Jon Smirl "); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c new file mode 100644 index 000000000..30a3d68b5 --- /dev/null +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -0,0 +1,234 @@ +/* + * eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode + * + * Copyright 2010 Eric Bénard, Eukréa Electromatique + * + * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c + * which is Copyright 2009 Simtec Electronics + * and on sound/soc/imx/phycore-ac97.c which is + * Copyright 2009 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/tlv320aic23.h" +#include "imx-ssi.h" +#include "imx-audmux.h" + +#define CODEC_CLOCK 12000000 + +static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + CODEC_CLOCK, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(cpu_dai->dev, + "Failed to set the codec sysclk.\n"); + return ret; + } + + snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 0); + + ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + /* fsl_ssi lacks the set_sysclk ops */ + if (ret && ret != -EINVAL) { + dev_err(cpu_dai->dev, + "Can't set the IMX_SSP_SYS_CLK CPU system clock.\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops eukrea_tlv320_snd_ops = { + .hw_params = eukrea_tlv320_hw_params, +}; + +static struct snd_soc_dai_link eukrea_tlv320_dai = { + .name = "tlv320aic23", + .stream_name = "TLV320AIC23", + .codec_dai_name = "tlv320aic23-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &eukrea_tlv320_snd_ops, +}; + +static struct snd_soc_card eukrea_tlv320 = { + .owner = THIS_MODULE, + .dai_link = &eukrea_tlv320_dai, + .num_links = 1, +}; + +static int eukrea_tlv320_probe(struct platform_device *pdev) +{ + int ret; + int int_port = 0, ext_port; + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np = NULL, *codec_np = NULL; + + eukrea_tlv320.dev = &pdev->dev; + if (np) { + ret = snd_soc_of_parse_card_name(&eukrea_tlv320, + "eukrea,model"); + if (ret) { + dev_err(&pdev->dev, + "eukrea,model node missing or invalid.\n"); + goto err; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, + "ssi-controller", 0); + if (!ssi_np) { + dev_err(&pdev->dev, + "ssi-controller missing or invalid.\n"); + ret = -ENODEV; + goto err; + } + + codec_np = of_parse_phandle(ssi_np, "codec-handle", 0); + if (codec_np) + eukrea_tlv320_dai.codec_of_node = codec_np; + else + dev_err(&pdev->dev, "codec-handle node missing or invalid.\n"); + + ret = of_property_read_u32(np, "fsl,mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, + "fsl,mux-int-port node missing or invalid.\n"); + goto err; + } + ret = of_property_read_u32(np, "fsl,mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, + "fsl,mux-ext-port node missing or invalid.\n"); + goto err; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + + eukrea_tlv320_dai.cpu_of_node = ssi_np; + eukrea_tlv320_dai.platform_of_node = ssi_np; + } else { + eukrea_tlv320_dai.cpu_dai_name = "imx-ssi.0"; + eukrea_tlv320_dai.platform_name = "imx-ssi.0"; + eukrea_tlv320_dai.codec_name = "tlv320aic23-codec.0-001a"; + eukrea_tlv320.name = "cpuimx-audio"; + } + + if (machine_is_eukrea_cpuimx27() || + of_find_compatible_node(NULL, NULL, "fsl,imx21-audmux")) { + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_TCLKDIR | + IMX_AUDMUX_V1_PCR_RFSDIR | + IMX_AUDMUX_V1_PCR_RCLKDIR | + IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) + ); + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) + ); + } else if (machine_is_eukrea_cpuimx25sd() || + machine_is_eukrea_cpuimx35sd() || + machine_is_eukrea_cpuimx51sd() || + of_find_compatible_node(NULL, NULL, "fsl,imx31-audmux")) { + if (!np) + ext_port = machine_is_eukrea_cpuimx25sd() ? + 4 : 3; + + imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port), + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port) + ); + imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port) + ); + } else { + if (np) { + /* The eukrea,asoc-tlv320 driver was explicitly + * requested (through the device tree). + */ + dev_err(&pdev->dev, + "Missing or invalid audmux DT node.\n"); + return -ENODEV; + } else { + /* Return happy. + * We might run on a totally different machine. + */ + return 0; + } + } + + ret = snd_soc_register_card(&eukrea_tlv320); +err: + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + of_node_put(ssi_np); + + return ret; +} + +static int eukrea_tlv320_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&eukrea_tlv320); + + return 0; +} + +static const struct of_device_id imx_tlv320_dt_ids[] = { + { .compatible = "eukrea,asoc-tlv320"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_tlv320_dt_ids); + +static struct platform_driver eukrea_tlv320_driver = { + .driver = { + .name = "eukrea_tlv320", + .of_match_table = imx_tlv320_dt_ids, + }, + .probe = eukrea_tlv320_probe, + .remove = eukrea_tlv320_remove, +}; + +module_platform_driver(eukrea_tlv320_driver); + +MODULE_AUTHOR("Eric Bénard "); +MODULE_DESCRIPTION("CPUIMX ALSA SoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:eukrea_tlv320"); diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c new file mode 100644 index 000000000..600d9be97 --- /dev/null +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale Generic ASoC Sound Card driver with ASRC +// +// Copyright (C) 2014 Freescale Semiconductor, Inc. +// +// Author: Nicolin Chen + +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_SND_AC97_CODEC) +#include +#endif +#include +#include + +#include "fsl_esai.h" +#include "fsl_sai.h" +#include "imx-audmux.h" + +#include "../codecs/sgtl5000.h" +#include "../codecs/wm8962.h" +#include "../codecs/wm8960.h" + +#define CS427x_SYSCLK_MCLK 0 + +#define RX 0 +#define TX 1 + +/* Default DAI format without Master and Slave flag */ +#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF) + +/** + * CODEC private data + * + * @mclk_freq: Clock rate of MCLK + * @mclk_id: MCLK (or main clock) id for set_sysclk() + * @fll_id: FLL (or secordary clock) id for set_sysclk() + * @pll_id: PLL id for set_pll() + */ +struct codec_priv { + unsigned long mclk_freq; + u32 mclk_id; + u32 fll_id; + u32 pll_id; +}; + +/** + * CPU private data + * + * @sysclk_freq[2]: SYSCLK rates for set_sysclk() + * @sysclk_dir[2]: SYSCLK directions for set_sysclk() + * @sysclk_id[2]: SYSCLK ids for set_sysclk() + * @slot_width: Slot width of each frame + * + * Note: [1] for tx and [0] for rx + */ +struct cpu_priv { + unsigned long sysclk_freq[2]; + u32 sysclk_dir[2]; + u32 sysclk_id[2]; + u32 slot_width; +}; + +/** + * Freescale Generic ASOC card private data + * + * @dai_link[3]: DAI link structure including normal one and DPCM link + * @pdev: platform device pointer + * @codec_priv: CODEC private data + * @cpu_priv: CPU private data + * @card: ASoC card structure + * @sample_rate: Current sample rate + * @sample_format: Current sample format + * @asrc_rate: ASRC sample rate used by Back-Ends + * @asrc_format: ASRC sample format used by Back-Ends + * @dai_fmt: DAI format between CPU and CODEC + * @name: Card name + */ + +struct fsl_asoc_card_priv { + struct snd_soc_dai_link dai_link[3]; + struct platform_device *pdev; + struct codec_priv codec_priv; + struct cpu_priv cpu_priv; + struct snd_soc_card card; + u32 sample_rate; + snd_pcm_format_t sample_format; + u32 asrc_rate; + snd_pcm_format_t asrc_format; + u32 dai_fmt; + char name[32]; +}; + +/** + * This dapm route map exsits for DPCM link only. + * The other routes shall go through Device Tree. + * + * Note: keep all ASRC routes in the second half + * to drop them easily for non-ASRC cases. + */ +static const struct snd_soc_dapm_route audio_map[] = { + /* 1st half -- Normal DAPM routes */ + {"Playback", NULL, "CPU-Playback"}, + {"CPU-Capture", NULL, "Capture"}, + /* 2nd half -- ASRC DAPM routes */ + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, +}; + +static const struct snd_soc_dapm_route audio_map_ac97[] = { + /* 1st half -- Normal DAPM routes */ + {"Playback", NULL, "AC97 Playback"}, + {"AC97 Capture", NULL, "Capture"}, + /* 2nd half -- ASRC DAPM routes */ + {"AC97 Playback", NULL, "ASRC-Playback"}, + {"ASRC-Capture", NULL, "AC97 Capture"}, +}; + +/* Add all possible widgets into here without being redundant */ +static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), +}; + +static bool fsl_asoc_card_is_ac97(struct fsl_asoc_card_priv *priv) +{ + return priv->dai_fmt == SND_SOC_DAIFMT_AC97; +} + +static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct cpu_priv *cpu_priv = &priv->cpu_priv; + struct device *dev = rtd->card->dev; + int ret; + + priv->sample_rate = params_rate(params); + priv->sample_format = params_format(params); + + /* + * If codec-dai is DAI Master and all configurations are already in the + * set_bias_level(), bypass the remaining settings in hw_params(). + * Note: (dai_fmt & CBM_CFM) includes CBM_CFM and CBM_CFS. + */ + if ((priv->card.set_bias_level && + priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) || + fsl_asoc_card_is_ac97(priv)) + return 0; + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, cpu_priv->sysclk_id[tx], + cpu_priv->sysclk_freq[tx], + cpu_priv->sysclk_dir[tx]); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set sysclk for cpu dai\n"); + return ret; + } + + if (cpu_priv->slot_width) { + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, + cpu_priv->slot_width); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set TDM slot for cpu dai\n"); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_ops fsl_asoc_card_ops = { + .hw_params = fsl_asoc_card_hw_params, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_interval *rate; + struct snd_mask *mask; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set_format(mask, priv->asrc_format); + + return 0; +} + +static struct snd_soc_dai_link fsl_asoc_card_dai[] = { + /* Default ASoC DAI Link*/ + { + .name = "HiFi", + .stream_name = "HiFi", + .ops = &fsl_asoc_card_ops, + }, + /* DPCM Link between Front-End and Back-End (Optional) */ + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dpcm_playback = 1, + .dpcm_capture = 1, + .dynamic = 1, + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .platform_name = "snd-soc-dummy", + .be_hw_params_fixup = be_hw_params_fixup, + .ops = &fsl_asoc_card_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, +}; + +static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct codec_priv *codec_priv = &priv->codec_priv; + struct device *dev = card->dev; + unsigned int pll_out; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + + if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE) + pll_out = priv->sample_rate * 384; + else + pll_out = priv->sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, + codec_priv->mclk_id, + codec_priv->mclk_freq, pll_out); + if (ret) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->fll_id, + pll_out, SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + + ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, + codec_priv->mclk_freq, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to switch away from FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0); + if (ret) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + break; + + default: + break; + } + + return 0; +} + +static int fsl_asoc_card_audmux_init(struct device_node *np, + struct fsl_asoc_card_priv *priv) +{ + struct device *dev = &priv->pdev->dev; + u32 int_ptcr = 0, ext_ptcr = 0; + int int_port, ext_port; + int ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(dev, "mux-int-port missing or invalid\n"); + return ret; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the AUDMUX API expects it starts at 0. + */ + int_port--; + ext_port--; + + /* + * Use asynchronous mode (6 wires) for all cases except AC97. + * If only 4 wires are needed, just set SSI into + * synchronous mode and enable 4 PADs in IOMUX. + */ + switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR; + ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + break; + default: + if (!fsl_asoc_card_is_ac97(priv)) + return -EINVAL; + } + + if (fsl_asoc_card_is_ac97(priv)) { + int_ptcr = IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + ext_ptcr = IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR; + } + + /* Asynchronous mode can not be set along with RCLKDIR */ + if (!fsl_asoc_card_is_ac97(priv)) { + unsigned int pdcr = + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port); + + ret = imx_audmux_v2_configure_port(int_port, 0, + pdcr); + if (ret) { + dev_err(dev, "audmux internal port setup failed\n"); + return ret; + } + } + + ret = imx_audmux_v2_configure_port(int_port, int_ptcr, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(dev, "audmux internal port setup failed\n"); + return ret; + } + + if (!fsl_asoc_card_is_ac97(priv)) { + unsigned int pdcr = + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port); + + ret = imx_audmux_v2_configure_port(ext_port, 0, + pdcr); + if (ret) { + dev_err(dev, "audmux external port setup failed\n"); + return ret; + } + } + + ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(dev, "audmux external port setup failed\n"); + return ret; + } + + return 0; +} + +static int fsl_asoc_card_late_probe(struct snd_soc_card *card) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct codec_priv *codec_priv = &priv->codec_priv; + struct device *dev = card->dev; + int ret; + + if (fsl_asoc_card_is_ac97(priv)) { +#if IS_ENABLED(CONFIG_SND_AC97_CODEC) + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component); + + /* + * Use slots 3/4 for S/PDIF so SSI won't try to enable + * other slots and send some samples there + * due to SLOTREQ bits for S/PDIF received from codec + */ + snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS, + AC97_EA_SPSA_SLOT_MASK, AC97_EA_SPSA_3_4); +#endif + + return 0; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, + codec_priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set sysclk in %s\n", __func__); + return ret; + } + + return 0; +} + +static int fsl_asoc_card_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np, *asrc_np; + struct device_node *np = pdev->dev.of_node; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct fsl_asoc_card_priv *priv; + struct i2c_client *codec_dev; + const char *codec_dai_name; + u32 width; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + /* Give a chance to old DT binding */ + if (!cpu_np) + cpu_np = of_parse_phandle(np, "ssi-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "CPU phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find CPU DAI device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (codec_np) + codec_dev = of_find_i2c_device_by_node(codec_np); + else + codec_dev = NULL; + + asrc_np = of_parse_phandle(np, "audio-asrc", 0); + if (asrc_np) + asrc_pdev = of_find_device_by_node(asrc_np); + + /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ + if (codec_dev) { + struct clk *codec_clk = clk_get(&codec_dev->dev, NULL); + + if (!IS_ERR(codec_clk)) { + priv->codec_priv.mclk_freq = clk_get_rate(codec_clk); + clk_put(codec_clk); + } + } + + /* Default sample rate and format, will be updated in hw_params() */ + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; + + /* Assign a default DAI format, and allow each card to overwrite it */ + priv->dai_fmt = DAI_FMT_BASE; + + /* Diversify the card configurations */ + if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { + codec_dai_name = "cs42888"; + priv->card.set_bias_level = NULL; + priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq; + priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq; + priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; + priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; + priv->cpu_priv.slot_width = 32; + priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { + codec_dai_name = "cs4271-hifi"; + priv->codec_priv.mclk_id = CS427x_SYSCLK_MCLK; + priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { + codec_dai_name = "sgtl5000"; + priv->codec_priv.mclk_id = SGTL5000_SYSCLK; + priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { + codec_dai_name = "wm8962"; + priv->card.set_bias_level = fsl_asoc_card_set_bias_level; + priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK; + priv->codec_priv.fll_id = WM8962_SYSCLK_FLL; + priv->codec_priv.pll_id = WM8962_FLL; + priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { + codec_dai_name = "wm8960-hifi"; + priv->card.set_bias_level = fsl_asoc_card_set_bias_level; + priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO; + priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO; + priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { + codec_dai_name = "ac97-hifi"; + priv->card.set_bias_level = NULL; + priv->dai_fmt = SND_SOC_DAIFMT_AC97; + } else { + dev_err(&pdev->dev, "unknown Device Tree compatible\n"); + ret = -EINVAL; + goto asrc_fail; + } + + if (!fsl_asoc_card_is_ac97(priv) && !codec_dev) { + dev_err(&pdev->dev, "failed to find codec device\n"); + ret = -EINVAL; + goto asrc_fail; + } + + /* Common settings for corresponding Freescale CPU DAI driver */ + if (strstr(cpu_np->name, "ssi")) { + /* Only SSI needs to configure AUDMUX */ + ret = fsl_asoc_card_audmux_init(np, priv); + if (ret) { + dev_err(&pdev->dev, "failed to init audmux\n"); + goto asrc_fail; + } + } else if (strstr(cpu_np->name, "esai")) { + priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; + priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; + } else if (strstr(cpu_np->name, "sai")) { + priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; + priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; + } + + snprintf(priv->name, sizeof(priv->name), "%s-audio", + fsl_asoc_card_is_ac97(priv) ? "ac97" : + codec_dev->name); + + /* Initialize sound card */ + priv->pdev = pdev; + priv->card.dev = &pdev->dev; + priv->card.name = priv->name; + priv->card.dai_link = priv->dai_link; + priv->card.dapm_routes = fsl_asoc_card_is_ac97(priv) ? + audio_map_ac97 : audio_map; + priv->card.late_probe = fsl_asoc_card_late_probe; + priv->card.num_dapm_routes = ARRAY_SIZE(audio_map); + priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); + + /* Drop the second half of DAPM routes -- ASRC */ + if (!asrc_pdev) + priv->card.num_dapm_routes /= 2; + + memcpy(priv->dai_link, fsl_asoc_card_dai, + sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link)); + + ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + goto asrc_fail; + } + + /* Normal DAI Link */ + priv->dai_link[0].cpu_of_node = cpu_np; + priv->dai_link[0].codec_dai_name = codec_dai_name; + + if (!fsl_asoc_card_is_ac97(priv)) + priv->dai_link[0].codec_of_node = codec_np; + else { + u32 idx; + + ret = of_property_read_u32(cpu_np, "cell-index", &idx); + if (ret) { + dev_err(&pdev->dev, + "cannot get CPU index property\n"); + goto asrc_fail; + } + + priv->dai_link[0].codec_name = + devm_kasprintf(&pdev->dev, GFP_KERNEL, + "ac97-codec.%u", + (unsigned int)idx); + if (!priv->dai_link[0].codec_name) { + ret = -ENOMEM; + goto asrc_fail; + } + } + + priv->dai_link[0].platform_of_node = cpu_np; + priv->dai_link[0].dai_fmt = priv->dai_fmt; + priv->card.num_links = 1; + + if (asrc_pdev) { + /* DPCM DAI Links only if ASRC exsits */ + priv->dai_link[1].cpu_of_node = asrc_np; + priv->dai_link[1].platform_of_node = asrc_np; + priv->dai_link[2].codec_dai_name = codec_dai_name; + priv->dai_link[2].codec_of_node = codec_np; + priv->dai_link[2].codec_name = + priv->dai_link[0].codec_name; + priv->dai_link[2].cpu_of_node = cpu_np; + priv->dai_link[2].dai_fmt = priv->dai_fmt; + priv->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto asrc_fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto asrc_fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + /* Finish card registering */ + platform_set_drvdata(pdev, priv); + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + +asrc_fail: + of_node_put(asrc_np); + of_node_put(codec_np); + put_device(&cpu_pdev->dev); +fail: + of_node_put(cpu_np); + + return ret; +} + +static const struct of_device_id fsl_asoc_card_dt_ids[] = { + { .compatible = "fsl,imx-audio-ac97", }, + { .compatible = "fsl,imx-audio-cs42888", }, + { .compatible = "fsl,imx-audio-cs427x", }, + { .compatible = "fsl,imx-audio-sgtl5000", }, + { .compatible = "fsl,imx-audio-wm8962", }, + { .compatible = "fsl,imx-audio-wm8960", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); + +static struct platform_driver fsl_asoc_card_driver = { + .probe = fsl_asoc_card_probe, + .driver = { + .name = "fsl-asoc-card", + .pm = &snd_soc_pm_ops, + .of_match_table = fsl_asoc_card_dt_ids, + }, +}; +module_platform_driver(fsl_asoc_card_driver); + +MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC"); +MODULE_AUTHOR("Nicolin Chen "); +MODULE_ALIAS("platform:fsl-asoc-card"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c new file mode 100644 index 000000000..09e03b884 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc.c @@ -0,0 +1,1043 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver +// +// Copyright (C) 2014 Freescale Semiconductor, Inc. +// +// Author: Nicolin Chen + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_asrc.h" + +#define IDEAL_RATIO_DECIMAL_DEPTH 26 + +#define pair_err(fmt, ...) \ + dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + +#define pair_dbg(fmt, ...) \ + dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + +/* Sample rates are aligned with that defined in pcm.h file */ +static const u8 process_option[][12][2] = { + /* 8kHz 11.025kHz 16kHz 22.05kHz 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 5512Hz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 8kHz */ + {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 11025Hz */ + {{1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 16kHz */ + {{1, 2}, {1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 22050Hz */ + {{1, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, /* 32kHz */ + {{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 44.1kHz */ + {{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 48kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, /* 64kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 88.2kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 96kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 176kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 192kHz */ +}; + +/* Corresponding to process_option */ +static int supported_input_rate[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, + 96000, 176400, 192000, +}; + +static int supported_asrc_rate[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000, +}; + +/** + * The following tables map the relationship between asrc_inclk/asrc_outclk in + * fsl_asrc.h and the registers of ASRCSR + */ +static unsigned char input_clk_map_imx35[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +static unsigned char output_clk_map_imx35[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +/* i.MX53 uses the same map for input and output */ +static unsigned char input_clk_map_imx53[] = { +/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ + 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd, +}; + +static unsigned char output_clk_map_imx53[] = { +/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ + 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd, +}; + +static unsigned char *clk_map[2]; + +/** + * Request ASRC pair + * + * It assigns pair by the order of A->C->B because allocation of pair B, + * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A + * while pair A and pair C are comparatively independent. + */ +static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) +{ + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct device *dev = &asrc_priv->pdev->dev; + unsigned long lock_flags; + int i, ret = 0; + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc_priv->pair[i] != NULL) + continue; + + index = i; + + if (i != ASRC_PAIR_B) + break; + } + + if (index == ASRC_INVALID_PAIR) { + dev_err(dev, "all pairs are busy now\n"); + ret = -EBUSY; + } else if (asrc_priv->channel_avail < channels) { + dev_err(dev, "can't afford required channels: %d\n", channels); + ret = -EINVAL; + } else { + asrc_priv->channel_avail -= channels; + asrc_priv->pair[index] = pair; + pair->channels = channels; + pair->index = index; + } + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + + return ret; +} + +/** + * Release ASRC pair + * + * It clears the resource from asrc_priv and releases the occupied channels. + */ +static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + asrc_priv->channel_avail += pair->channels; + asrc_priv->pair[index] = NULL; + pair->error = 0; + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); +} + +/** + * Configure input and output thresholds + */ +static void fsl_asrc_set_watermarks(struct fsl_asrc_pair *pair, u32 in, u32 out) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), + ASRMCRi_EXTTHRSHi_MASK | + ASRMCRi_INFIFO_THRESHOLD_MASK | + ASRMCRi_OUTFIFO_THRESHOLD_MASK, + ASRMCRi_EXTTHRSHi | + ASRMCRi_INFIFO_THRESHOLD(in) | + ASRMCRi_OUTFIFO_THRESHOLD(out)); +} + +/** + * Calculate the total divisor between asrck clock rate and sample rate + * + * It follows the formula clk_rate = samplerate * (2 ^ prescaler) * divider + */ +static u32 fsl_asrc_cal_asrck_divisor(struct fsl_asrc_pair *pair, u32 div) +{ + u32 ps; + + /* Calculate the divisors: prescaler [2^0, 2^7], divder [1, 8] */ + for (ps = 0; div > 8; ps++) + div >>= 1; + + return ((div - 1) << ASRCDRi_AxCPi_WIDTH) | ps; +} + +/** + * Calculate and set the ratio for Ideal Ratio mode only + * + * The ratio is a 32-bit fixed point value with 26 fractional bits. + */ +static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair, + int inrate, int outrate) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + unsigned long ratio; + int i; + + if (!outrate) { + pair_err("output rate should not be zero\n"); + return -EINVAL; + } + + /* Calculate the intergal part of the ratio */ + ratio = (inrate / outrate) << IDEAL_RATIO_DECIMAL_DEPTH; + + /* ... and then the 26 depth decimal part */ + inrate %= outrate; + + for (i = 1; i <= IDEAL_RATIO_DECIMAL_DEPTH; i++) { + inrate <<= 1; + + if (inrate < outrate) + continue; + + ratio |= 1 << (IDEAL_RATIO_DECIMAL_DEPTH - i); + inrate -= outrate; + + if (!inrate) + break; + } + + regmap_write(asrc_priv->regmap, REG_ASRIDRL(index), ratio); + regmap_write(asrc_priv->regmap, REG_ASRIDRH(index), ratio >> 24); + + return 0; +} + +/** + * Configure the assigned ASRC pair + * + * It configures those ASRC registers according to a configuration instance + * of struct asrc_config which includes in/output sample rate, width, channel + * and clock settings. + */ +static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) +{ + struct asrc_config *config = pair->config; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + u32 inrate, outrate, indiv, outdiv; + u32 clk_index[2], div[2]; + int in, out, channels; + struct clk *clk; + bool ideal; + + if (!config) { + pair_err("invalid pair config\n"); + return -EINVAL; + } + + /* Validate channels */ + if (config->channel_num < 1 || config->channel_num > 10) { + pair_err("does not support %d channels\n", config->channel_num); + return -EINVAL; + } + + /* Validate output width */ + if (config->output_word_width == ASRC_WIDTH_8_BIT) { + pair_err("does not support 8bit width output\n"); + return -EINVAL; + } + + inrate = config->input_sample_rate; + outrate = config->output_sample_rate; + ideal = config->inclk == INCLK_NONE; + + /* Validate input and output sample rates */ + for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++) + if (inrate == supported_input_rate[in]) + break; + + if (in == ARRAY_SIZE(supported_input_rate)) { + pair_err("unsupported input sample rate: %dHz\n", inrate); + return -EINVAL; + } + + for (out = 0; out < ARRAY_SIZE(supported_asrc_rate); out++) + if (outrate == supported_asrc_rate[out]) + break; + + if (out == ARRAY_SIZE(supported_asrc_rate)) { + pair_err("unsupported output sample rate: %dHz\n", outrate); + return -EINVAL; + } + + if ((outrate >= 8000 && outrate <= 30000) && + (outrate > 24 * inrate || inrate > 8 * outrate)) { + pair_err("exceed supported ratio range [1/24, 8] for \ + inrate/outrate: %d/%d\n", inrate, outrate); + return -EINVAL; + } + + /* Validate input and output clock sources */ + clk_index[IN] = clk_map[IN][config->inclk]; + clk_index[OUT] = clk_map[OUT][config->outclk]; + + /* We only have output clock for ideal ratio mode */ + clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]]; + + div[IN] = clk_get_rate(clk) / inrate; + if (div[IN] == 0) { + pair_err("failed to support input sample rate %dHz by asrck_%x\n", + inrate, clk_index[ideal ? OUT : IN]); + return -EINVAL; + } + + clk = asrc_priv->asrck_clk[clk_index[OUT]]; + + /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */ + if (ideal) + div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE; + else + div[OUT] = clk_get_rate(clk) / outrate; + + if (div[OUT] == 0) { + pair_err("failed to support output sample rate %dHz by asrck_%x\n", + outrate, clk_index[OUT]); + return -EINVAL; + } + + /* Set the channel number */ + channels = config->channel_num; + + if (asrc_priv->channel_bits < 4) + channels /= 2; + + /* Update channels for current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCNCR, + ASRCNCR_ANCi_MASK(index, asrc_priv->channel_bits), + ASRCNCR_ANCi(index, channels, asrc_priv->channel_bits)); + + /* Default setting: Automatic selection for processing mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index)); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_USRi_MASK(index), 0); + + /* Set the input and output clock sources */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCSR, + ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), + ASRCSR_AICS(index, clk_index[IN]) | + ASRCSR_AOCS(index, clk_index[OUT])); + + /* Calculate the input clock divisors */ + indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]); + outdiv = fsl_asrc_cal_asrck_divisor(pair, div[OUT]); + + /* Suppose indiv and outdiv includes prescaler, so add its MASK too */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCDR(index), + ASRCDRi_AOCPi_MASK(index) | ASRCDRi_AICPi_MASK(index) | + ASRCDRi_AOCDi_MASK(index) | ASRCDRi_AICDi_MASK(index), + ASRCDRi_AOCP(index, outdiv) | ASRCDRi_AICP(index, indiv)); + + /* Implement word_width configurations */ + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index), + ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK, + ASRMCR1i_OW16(config->output_word_width) | + ASRMCR1i_IWD(config->input_word_width)); + + /* Enable BUFFER STALL */ + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), + ASRMCRi_BUFSTALLi_MASK, ASRMCRi_BUFSTALLi); + + /* Set default thresholds for input and output FIFO */ + fsl_asrc_set_watermarks(pair, ASRC_INPUTFIFO_THRESHOLD, + ASRC_INPUTFIFO_THRESHOLD); + + /* Configure the following only for Ideal Ratio mode */ + if (!ideal) + return 0; + + /* Clear ASTSx bit to use Ideal Ratio mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ATSi_MASK(index), 0); + + /* Enable Ideal Ratio mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index), + ASRCTR_IDR(index) | ASRCTR_USR(index)); + + /* Apply configurations for pre- and post-processing */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, + ASRCFG_PREMODi_MASK(index) | ASRCFG_POSTMODi_MASK(index), + ASRCFG_PREMOD(index, process_option[in][out][0]) | + ASRCFG_POSTMOD(index, process_option[in][out][1])); + + return fsl_asrc_set_ideal_ratio(pair, inrate, outrate); +} + +/** + * Start the assigned ASRC pair + * + * It enables the assigned pair and makes it stopped at the stall level. + */ +static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + int reg, retry = 10, i; + + /* Enable the current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), ASRCTR_ASRCE(index)); + + /* Wait for status of initialization */ + do { + udelay(5); + regmap_read(asrc_priv->regmap, REG_ASRCFG, ®); + reg &= ASRCFG_INIRQi_MASK(index); + } while (!reg && --retry); + + /* Make the input fifo to ASRC STALL level */ + regmap_read(asrc_priv->regmap, REG_ASRCNCR, ®); + for (i = 0; i < pair->channels * 4; i++) + regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0); + + /* Enable overload interrupt */ + regmap_write(asrc_priv->regmap, REG_ASRIER, ASRIER_AOLIE); +} + +/** + * Stop the assigned ASRC pair + */ +static void fsl_asrc_stop_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + /* Stop the current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); +} + +/** + * Get DMA channel according to the pair and direction. + */ +struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + char name[4]; + + sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a'); + + return dma_request_slave_channel(&asrc_priv->pdev->dev, name); +} +EXPORT_SYMBOL_GPL(fsl_asrc_get_dma_channel); + +static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); + int width = params_width(params); + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + struct asrc_config config; + int word_width, ret; + + ret = fsl_asrc_request_pair(channels, pair); + if (ret) { + dev_err(dai->dev, "fail to request asrc pair\n"); + return ret; + } + + pair->config = &config; + + if (width == 16) + width = ASRC_WIDTH_16_BIT; + else + width = ASRC_WIDTH_24_BIT; + + if (asrc_priv->asrc_width == 16) + word_width = ASRC_WIDTH_16_BIT; + else + word_width = ASRC_WIDTH_24_BIT; + + config.pair = pair->index; + config.channel_num = channels; + config.inclk = INCLK_NONE; + config.outclk = OUTCLK_ASRCK1_CLK; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + config.input_word_width = width; + config.output_word_width = word_width; + config.input_sample_rate = rate; + config.output_sample_rate = asrc_priv->asrc_rate; + } else { + config.input_word_width = word_width; + config.output_word_width = width; + config.input_sample_rate = asrc_priv->asrc_rate; + config.output_sample_rate = rate; + } + + ret = fsl_asrc_config_pair(pair); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } + + return 0; +} + +static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + if (pair) + fsl_asrc_release_pair(pair); + + return 0; +} + +static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fsl_asrc_start_pair(pair); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fsl_asrc_stop_pair(pair); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops fsl_asrc_dai_ops = { + .hw_params = fsl_asrc_dai_hw_params, + .hw_free = fsl_asrc_dai_hw_free, + .trigger = fsl_asrc_dai_trigger, +}; + +static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &asrc_priv->dma_params_tx, + &asrc_priv->dma_params_rx); + + return 0; +} + +#define FSL_ASRC_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE) + +static struct snd_soc_dai_driver fsl_asrc_dai = { + .probe = fsl_asrc_dai_probe, + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 10, + .rates = FSL_ASRC_RATES, + .formats = FSL_ASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 10, + .rates = FSL_ASRC_RATES, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, +}; + +static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRCTR: + case REG_ASRIER: + case REG_ASRCNCR: + case REG_ASRCFG: + case REG_ASRCSR: + case REG_ASRCDR1: + case REG_ASRCDR2: + case REG_ASRSTR: + case REG_ASRPM1: + case REG_ASRPM2: + case REG_ASRPM3: + case REG_ASRPM4: + case REG_ASRPM5: + case REG_ASRTFR1: + case REG_ASRCCR: + case REG_ASRDOA: + case REG_ASRDOB: + case REG_ASRDOC: + case REG_ASRIDRHA: + case REG_ASRIDRLA: + case REG_ASRIDRHB: + case REG_ASRIDRLB: + case REG_ASRIDRHC: + case REG_ASRIDRLC: + case REG_ASR76K: + case REG_ASR56K: + case REG_ASRMCRA: + case REG_ASRFSTA: + case REG_ASRMCRB: + case REG_ASRFSTB: + case REG_ASRMCRC: + case REG_ASRFSTC: + case REG_ASRMCR1A: + case REG_ASRMCR1B: + case REG_ASRMCR1C: + return true; + default: + return false; + } +} + +static bool fsl_asrc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRSTR: + case REG_ASRDIA: + case REG_ASRDIB: + case REG_ASRDIC: + case REG_ASRDOA: + case REG_ASRDOB: + case REG_ASRDOC: + case REG_ASRFSTA: + case REG_ASRFSTB: + case REG_ASRFSTC: + case REG_ASRCFG: + return true; + default: + return false; + } +} + +static bool fsl_asrc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRCTR: + case REG_ASRIER: + case REG_ASRCNCR: + case REG_ASRCFG: + case REG_ASRCSR: + case REG_ASRCDR1: + case REG_ASRCDR2: + case REG_ASRSTR: + case REG_ASRPM1: + case REG_ASRPM2: + case REG_ASRPM3: + case REG_ASRPM4: + case REG_ASRPM5: + case REG_ASRTFR1: + case REG_ASRCCR: + case REG_ASRDIA: + case REG_ASRDIB: + case REG_ASRDIC: + case REG_ASRIDRHA: + case REG_ASRIDRLA: + case REG_ASRIDRHB: + case REG_ASRIDRLB: + case REG_ASRIDRHC: + case REG_ASRIDRLC: + case REG_ASR76K: + case REG_ASR56K: + case REG_ASRMCRA: + case REG_ASRMCRB: + case REG_ASRMCRC: + case REG_ASRMCR1A: + case REG_ASRMCR1B: + case REG_ASRMCR1C: + return true; + default: + return false; + } +} + +static struct reg_default fsl_asrc_reg[] = { + { REG_ASRCTR, 0x0000 }, { REG_ASRIER, 0x0000 }, + { REG_ASRCNCR, 0x0000 }, { REG_ASRCFG, 0x0000 }, + { REG_ASRCSR, 0x0000 }, { REG_ASRCDR1, 0x0000 }, + { REG_ASRCDR2, 0x0000 }, { REG_ASRSTR, 0x0000 }, + { REG_ASRRA, 0x0000 }, { REG_ASRRB, 0x0000 }, + { REG_ASRRC, 0x0000 }, { REG_ASRPM1, 0x0000 }, + { REG_ASRPM2, 0x0000 }, { REG_ASRPM3, 0x0000 }, + { REG_ASRPM4, 0x0000 }, { REG_ASRPM5, 0x0000 }, + { REG_ASRTFR1, 0x0000 }, { REG_ASRCCR, 0x0000 }, + { REG_ASRDIA, 0x0000 }, { REG_ASRDOA, 0x0000 }, + { REG_ASRDIB, 0x0000 }, { REG_ASRDOB, 0x0000 }, + { REG_ASRDIC, 0x0000 }, { REG_ASRDOC, 0x0000 }, + { REG_ASRIDRHA, 0x0000 }, { REG_ASRIDRLA, 0x0000 }, + { REG_ASRIDRHB, 0x0000 }, { REG_ASRIDRLB, 0x0000 }, + { REG_ASRIDRHC, 0x0000 }, { REG_ASRIDRLC, 0x0000 }, + { REG_ASR76K, 0x0A47 }, { REG_ASR56K, 0x0DF3 }, + { REG_ASRMCRA, 0x0000 }, { REG_ASRFSTA, 0x0000 }, + { REG_ASRMCRB, 0x0000 }, { REG_ASRFSTB, 0x0000 }, + { REG_ASRMCRC, 0x0000 }, { REG_ASRFSTC, 0x0000 }, + { REG_ASRMCR1A, 0x0000 }, { REG_ASRMCR1B, 0x0000 }, + { REG_ASRMCR1C, 0x0000 }, +}; + +static const struct regmap_config fsl_asrc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_ASRMCR1C, + .reg_defaults = fsl_asrc_reg, + .num_reg_defaults = ARRAY_SIZE(fsl_asrc_reg), + .readable_reg = fsl_asrc_readable_reg, + .volatile_reg = fsl_asrc_volatile_reg, + .writeable_reg = fsl_asrc_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +/** + * Initialize ASRC registers with a default configurations + */ +static int fsl_asrc_init(struct fsl_asrc *asrc_priv) +{ + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ + regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN); + + /* Disable interrupt by default */ + regmap_write(asrc_priv->regmap, REG_ASRIER, 0x0); + + /* Apply recommended settings for parameters from Reference Manual */ + regmap_write(asrc_priv->regmap, REG_ASRPM1, 0x7fffff); + regmap_write(asrc_priv->regmap, REG_ASRPM2, 0x255555); + regmap_write(asrc_priv->regmap, REG_ASRPM3, 0xff7280); + regmap_write(asrc_priv->regmap, REG_ASRPM4, 0xff7280); + regmap_write(asrc_priv->regmap, REG_ASRPM5, 0xff7280); + + /* Base address for task queue FIFO. Set to 0x7C */ + regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1, + ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc)); + + /* Set the processing clock for 76KHz to 133M */ + regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6); + + /* Set the processing clock for 56KHz to 133M */ + return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947); +} + +/** + * Interrupt handler for ASRC + */ +static irqreturn_t fsl_asrc_isr(int irq, void *dev_id) +{ + struct fsl_asrc *asrc_priv = (struct fsl_asrc *)dev_id; + struct device *dev = &asrc_priv->pdev->dev; + enum asrc_pair_index index; + u32 status; + + regmap_read(asrc_priv->regmap, REG_ASRSTR, &status); + + /* Clean overload error */ + regmap_write(asrc_priv->regmap, REG_ASRSTR, ASRSTR_AOLE); + + /* + * We here use dev_dbg() for all exceptions because ASRC itself does + * not care if FIFO overflowed or underrun while a warning in the + * interrupt would result a ridged conversion. + */ + for (index = ASRC_PAIR_A; index < ASRC_PAIR_MAX_NUM; index++) { + if (!asrc_priv->pair[index]) + continue; + + if (status & ASRSTR_ATQOL) { + asrc_priv->pair[index]->error |= ASRC_TASK_Q_OVERLOAD; + dev_dbg(dev, "ASRC Task Queue FIFO overload\n"); + } + + if (status & ASRSTR_AOOL(index)) { + asrc_priv->pair[index]->error |= ASRC_OUTPUT_TASK_OVERLOAD; + pair_dbg("Output Task Overload\n"); + } + + if (status & ASRSTR_AIOL(index)) { + asrc_priv->pair[index]->error |= ASRC_INPUT_TASK_OVERLOAD; + pair_dbg("Input Task Overload\n"); + } + + if (status & ASRSTR_AODO(index)) { + asrc_priv->pair[index]->error |= ASRC_OUTPUT_BUFFER_OVERFLOW; + pair_dbg("Output Data Buffer has overflowed\n"); + } + + if (status & ASRSTR_AIDU(index)) { + asrc_priv->pair[index]->error |= ASRC_INPUT_BUFFER_UNDERRUN; + pair_dbg("Input Data Buffer has underflowed\n"); + } + } + + return IRQ_HANDLED; +} + +static int fsl_asrc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_asrc *asrc_priv; + struct resource *res; + void __iomem *regs; + int irq, ret, i; + char tmp[16]; + + asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL); + if (!asrc_priv) + return -ENOMEM; + + asrc_priv->pdev = pdev; + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + asrc_priv->paddr = res->start; + + asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs, + &fsl_asrc_regmap_config); + if (IS_ERR(asrc_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap\n"); + return PTR_ERR(asrc_priv->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0, + dev_name(&pdev->dev), asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret); + return ret; + } + + asrc_priv->mem_clk = devm_clk_get(&pdev->dev, "mem"); + if (IS_ERR(asrc_priv->mem_clk)) { + dev_err(&pdev->dev, "failed to get mem clock\n"); + return PTR_ERR(asrc_priv->mem_clk); + } + + asrc_priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(asrc_priv->ipg_clk)) { + dev_err(&pdev->dev, "failed to get ipg clock\n"); + return PTR_ERR(asrc_priv->ipg_clk); + } + + asrc_priv->spba_clk = devm_clk_get(&pdev->dev, "spba"); + if (IS_ERR(asrc_priv->spba_clk)) + dev_warn(&pdev->dev, "failed to get spba clock\n"); + + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) { + sprintf(tmp, "asrck_%x", i); + asrc_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(asrc_priv->asrck_clk[i])) { + dev_err(&pdev->dev, "failed to get %s clock\n", tmp); + return PTR_ERR(asrc_priv->asrck_clk[i]); + } + } + + if (of_device_is_compatible(np, "fsl,imx35-asrc")) { + asrc_priv->channel_bits = 3; + clk_map[IN] = input_clk_map_imx35; + clk_map[OUT] = output_clk_map_imx35; + } else { + asrc_priv->channel_bits = 4; + clk_map[IN] = input_clk_map_imx53; + clk_map[OUT] = output_clk_map_imx53; + } + + ret = fsl_asrc_init(asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to init asrc %d\n", ret); + return ret; + } + + asrc_priv->channel_avail = 10; + + ret = of_property_read_u32(np, "fsl,asrc-rate", + &asrc_priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + return ret; + } + + ret = of_property_read_u32(np, "fsl,asrc-width", + &asrc_priv->asrc_width); + if (ret) { + dev_err(&pdev->dev, "failed to get output width\n"); + return ret; + } + + if (asrc_priv->asrc_width != 16 && asrc_priv->asrc_width != 24) { + dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n"); + asrc_priv->asrc_width = 24; + } + + platform_set_drvdata(pdev, asrc_priv); + pm_runtime_enable(&pdev->dev); + spin_lock_init(&asrc_priv->lock); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, + &fsl_asrc_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_asrc_runtime_resume(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int i, ret; + + ret = clk_prepare_enable(asrc_priv->mem_clk); + if (ret) + return ret; + ret = clk_prepare_enable(asrc_priv->ipg_clk); + if (ret) + goto disable_mem_clk; + if (!IS_ERR(asrc_priv->spba_clk)) { + ret = clk_prepare_enable(asrc_priv->spba_clk); + if (ret) + goto disable_ipg_clk; + } + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) { + ret = clk_prepare_enable(asrc_priv->asrck_clk[i]); + if (ret) + goto disable_asrck_clk; + } + + return 0; + +disable_asrck_clk: + for (i--; i >= 0; i--) + clk_disable_unprepare(asrc_priv->asrck_clk[i]); + if (!IS_ERR(asrc_priv->spba_clk)) + clk_disable_unprepare(asrc_priv->spba_clk); +disable_ipg_clk: + clk_disable_unprepare(asrc_priv->ipg_clk); +disable_mem_clk: + clk_disable_unprepare(asrc_priv->mem_clk); + return ret; +} + +static int fsl_asrc_runtime_suspend(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int i; + + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) + clk_disable_unprepare(asrc_priv->asrck_clk[i]); + if (!IS_ERR(asrc_priv->spba_clk)) + clk_disable_unprepare(asrc_priv->spba_clk); + clk_disable_unprepare(asrc_priv->ipg_clk); + clk_disable_unprepare(asrc_priv->mem_clk); + + return 0; +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +static int fsl_asrc_suspend(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + + regmap_read(asrc_priv->regmap, REG_ASRCFG, + &asrc_priv->regcache_cfg); + + regcache_cache_only(asrc_priv->regmap, true); + regcache_mark_dirty(asrc_priv->regmap); + + return 0; +} + +static int fsl_asrc_resume(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + u32 asrctr; + + /* Stop all pairs provisionally */ + regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, 0); + + /* Restore all registers */ + regcache_cache_only(asrc_priv->regmap, false); + regcache_sync(asrc_priv->regmap); + + regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, + ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK | + ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg); + + /* Restart enabled pairs */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, asrctr); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_asrc_pm = { + SET_RUNTIME_PM_OPS(fsl_asrc_runtime_suspend, fsl_asrc_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_asrc_suspend, fsl_asrc_resume) +}; + +static const struct of_device_id fsl_asrc_ids[] = { + { .compatible = "fsl,imx35-asrc", }, + { .compatible = "fsl,imx53-asrc", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_asrc_ids); + +static struct platform_driver fsl_asrc_driver = { + .probe = fsl_asrc_probe, + .driver = { + .name = "fsl-asrc", + .of_match_table = fsl_asrc_ids, + .pm = &fsl_asrc_pm, + }, +}; +module_platform_driver(fsl_asrc_driver); + +MODULE_DESCRIPTION("Freescale ASRC ASoC driver"); +MODULE_AUTHOR("Nicolin Chen "); +MODULE_ALIAS("platform:fsl-asrc"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h new file mode 100644 index 000000000..c60075112 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc.h @@ -0,0 +1,465 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fsl_asrc.h - Freescale ASRC ALSA SoC header file + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen + */ + +#ifndef _FSL_ASRC_H +#define _FSL_ASRC_H + +#define IN 0 +#define OUT 1 + +#define ASRC_DMA_BUFFER_NUM 2 +#define ASRC_INPUTFIFO_THRESHOLD 32 +#define ASRC_OUTPUTFIFO_THRESHOLD 32 +#define ASRC_FIFO_THRESHOLD_MIN 0 +#define ASRC_FIFO_THRESHOLD_MAX 63 +#define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) +#define ASRC_MAX_BUFFER_SIZE (1024 * 48) +#define ASRC_OUTPUT_LAST_SAMPLE 8 + +#define IDEAL_RATIO_RATE 1000000 + +#define REG_ASRCTR 0x00 +#define REG_ASRIER 0x04 +#define REG_ASRCNCR 0x0C +#define REG_ASRCFG 0x10 +#define REG_ASRCSR 0x14 + +#define REG_ASRCDR1 0x18 +#define REG_ASRCDR2 0x1C +#define REG_ASRCDR(i) ((i < 2) ? REG_ASRCDR1 : REG_ASRCDR2) + +#define REG_ASRSTR 0x20 +#define REG_ASRRA 0x24 +#define REG_ASRRB 0x28 +#define REG_ASRRC 0x2C +#define REG_ASRPM1 0x40 +#define REG_ASRPM2 0x44 +#define REG_ASRPM3 0x48 +#define REG_ASRPM4 0x4C +#define REG_ASRPM5 0x50 +#define REG_ASRTFR1 0x54 +#define REG_ASRCCR 0x5C + +#define REG_ASRDIA 0x60 +#define REG_ASRDOA 0x64 +#define REG_ASRDIB 0x68 +#define REG_ASRDOB 0x6C +#define REG_ASRDIC 0x70 +#define REG_ASRDOC 0x74 +#define REG_ASRDI(i) (REG_ASRDIA + (i << 3)) +#define REG_ASRDO(i) (REG_ASRDOA + (i << 3)) +#define REG_ASRDx(x, i) ((x) == IN ? REG_ASRDI(i) : REG_ASRDO(i)) + +#define REG_ASRIDRHA 0x80 +#define REG_ASRIDRLA 0x84 +#define REG_ASRIDRHB 0x88 +#define REG_ASRIDRLB 0x8C +#define REG_ASRIDRHC 0x90 +#define REG_ASRIDRLC 0x94 +#define REG_ASRIDRH(i) (REG_ASRIDRHA + (i << 3)) +#define REG_ASRIDRL(i) (REG_ASRIDRLA + (i << 3)) + +#define REG_ASR76K 0x98 +#define REG_ASR56K 0x9C + +#define REG_ASRMCRA 0xA0 +#define REG_ASRFSTA 0xA4 +#define REG_ASRMCRB 0xA8 +#define REG_ASRFSTB 0xAC +#define REG_ASRMCRC 0xB0 +#define REG_ASRFSTC 0xB4 +#define REG_ASRMCR(i) (REG_ASRMCRA + (i << 3)) +#define REG_ASRFST(i) (REG_ASRFSTA + (i << 3)) + +#define REG_ASRMCR1A 0xC0 +#define REG_ASRMCR1B 0xC4 +#define REG_ASRMCR1C 0xC8 +#define REG_ASRMCR1(i) (REG_ASRMCR1A + (i << 2)) + + +/* REG0 0x00 REG_ASRCTR */ +#define ASRCTR_ATSi_SHIFT(i) (20 + i) +#define ASRCTR_ATSi_MASK(i) (1 << ASRCTR_ATSi_SHIFT(i)) +#define ASRCTR_ATS(i) (1 << ASRCTR_ATSi_SHIFT(i)) +#define ASRCTR_USRi_SHIFT(i) (14 + (i << 1)) +#define ASRCTR_USRi_MASK(i) (1 << ASRCTR_USRi_SHIFT(i)) +#define ASRCTR_USR(i) (1 << ASRCTR_USRi_SHIFT(i)) +#define ASRCTR_IDRi_SHIFT(i) (13 + (i << 1)) +#define ASRCTR_IDRi_MASK(i) (1 << ASRCTR_IDRi_SHIFT(i)) +#define ASRCTR_IDR(i) (1 << ASRCTR_IDRi_SHIFT(i)) +#define ASRCTR_SRST_SHIFT 4 +#define ASRCTR_SRST_MASK (1 << ASRCTR_SRST_SHIFT) +#define ASRCTR_SRST (1 << ASRCTR_SRST_SHIFT) +#define ASRCTR_ASRCEi_SHIFT(i) (1 + i) +#define ASRCTR_ASRCEi_MASK(i) (1 << ASRCTR_ASRCEi_SHIFT(i)) +#define ASRCTR_ASRCE(i) (1 << ASRCTR_ASRCEi_SHIFT(i)) +#define ASRCTR_ASRCEi_ALL_MASK (0x7 << ASRCTR_ASRCEi_SHIFT(0)) +#define ASRCTR_ASRCEN_SHIFT 0 +#define ASRCTR_ASRCEN_MASK (1 << ASRCTR_ASRCEN_SHIFT) +#define ASRCTR_ASRCEN (1 << ASRCTR_ASRCEN_SHIFT) + +/* REG1 0x04 REG_ASRIER */ +#define ASRIER_AFPWE_SHIFT 7 +#define ASRIER_AFPWE_MASK (1 << ASRIER_AFPWE_SHIFT) +#define ASRIER_AFPWE (1 << ASRIER_AFPWE_SHIFT) +#define ASRIER_AOLIE_SHIFT 6 +#define ASRIER_AOLIE_MASK (1 << ASRIER_AOLIE_SHIFT) +#define ASRIER_AOLIE (1 << ASRIER_AOLIE_SHIFT) +#define ASRIER_ADOEi_SHIFT(i) (3 + i) +#define ASRIER_ADOEi_MASK(i) (1 << ASRIER_ADOEi_SHIFT(i)) +#define ASRIER_ADOE(i) (1 << ASRIER_ADOEi_SHIFT(i)) +#define ASRIER_ADIEi_SHIFT(i) (0 + i) +#define ASRIER_ADIEi_MASK(i) (1 << ASRIER_ADIEi_SHIFT(i)) +#define ASRIER_ADIE(i) (1 << ASRIER_ADIEi_SHIFT(i)) + +/* REG2 0x0C REG_ASRCNCR */ +#define ASRCNCR_ANCi_SHIFT(i, b) (b * i) +#define ASRCNCR_ANCi_MASK(i, b) (((1 << b) - 1) << ASRCNCR_ANCi_SHIFT(i, b)) +#define ASRCNCR_ANCi(i, v, b) ((v << ASRCNCR_ANCi_SHIFT(i, b)) & ASRCNCR_ANCi_MASK(i, b)) + +/* REG3 0x10 REG_ASRCFG */ +#define ASRCFG_INIRQi_SHIFT(i) (21 + i) +#define ASRCFG_INIRQi_MASK(i) (1 << ASRCFG_INIRQi_SHIFT(i)) +#define ASRCFG_INIRQi (1 << ASRCFG_INIRQi_SHIFT(i)) +#define ASRCFG_NDPRi_SHIFT(i) (18 + i) +#define ASRCFG_NDPRi_MASK(i) (1 << ASRCFG_NDPRi_SHIFT(i)) +#define ASRCFG_NDPRi_ALL_SHIFT 18 +#define ASRCFG_NDPRi_ALL_MASK (7 << ASRCFG_NDPRi_ALL_SHIFT) +#define ASRCFG_NDPRi (1 << ASRCFG_NDPRi_SHIFT(i)) +#define ASRCFG_POSTMODi_SHIFT(i) (8 + (i << 2)) +#define ASRCFG_POSTMODi_WIDTH 2 +#define ASRCFG_POSTMODi_MASK(i) (((1 << ASRCFG_POSTMODi_WIDTH) - 1) << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_ALL_MASK (ASRCFG_POSTMODi_MASK(0) | ASRCFG_POSTMODi_MASK(1) | ASRCFG_POSTMODi_MASK(2)) +#define ASRCFG_POSTMOD(i, v) ((v) << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_UP(i) (0 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_DCON(i) (1 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_DOWN(i) (2 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_PREMODi_SHIFT(i) (6 + (i << 2)) +#define ASRCFG_PREMODi_WIDTH 2 +#define ASRCFG_PREMODi_MASK(i) (((1 << ASRCFG_PREMODi_WIDTH) - 1) << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_ALL_MASK (ASRCFG_PREMODi_MASK(0) | ASRCFG_PREMODi_MASK(1) | ASRCFG_PREMODi_MASK(2)) +#define ASRCFG_PREMOD(i, v) ((v) << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_UP(i) (0 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_DCON(i) (1 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_DOWN(i) (2 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_BYPASS(i) (3 << ASRCFG_PREMODi_SHIFT(i)) + +/* REG4 0x14 REG_ASRCSR */ +#define ASRCSR_AxCSi_WIDTH 4 +#define ASRCSR_AxCSi_MASK ((1 << ASRCSR_AxCSi_WIDTH) - 1) +#define ASRCSR_AOCSi_SHIFT(i) (12 + (i << 2)) +#define ASRCSR_AOCSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AOCSi_SHIFT(i)) +#define ASRCSR_AOCS(i, v) ((v) << ASRCSR_AOCSi_SHIFT(i)) +#define ASRCSR_AICSi_SHIFT(i) (i << 2) +#define ASRCSR_AICSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AICSi_SHIFT(i)) +#define ASRCSR_AICS(i, v) ((v) << ASRCSR_AICSi_SHIFT(i)) + +/* REG5&6 0x18 & 0x1C REG_ASRCDR1 & ASRCDR2 */ +#define ASRCDRi_AxCPi_WIDTH 3 +#define ASRCDRi_AICPi_SHIFT(i) (0 + (i % 2) * 6) +#define ASRCDRi_AICPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICPi_SHIFT(i)) +#define ASRCDRi_AICP(i, v) ((v) << ASRCDRi_AICPi_SHIFT(i)) +#define ASRCDRi_AICDi_SHIFT(i) (3 + (i % 2) * 6) +#define ASRCDRi_AICDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICDi_SHIFT(i)) +#define ASRCDRi_AICD(i, v) ((v) << ASRCDRi_AICDi_SHIFT(i)) +#define ASRCDRi_AOCPi_SHIFT(i) ((i < 2) ? 12 + i * 6 : 6) +#define ASRCDRi_AOCPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCPi_SHIFT(i)) +#define ASRCDRi_AOCP(i, v) ((v) << ASRCDRi_AOCPi_SHIFT(i)) +#define ASRCDRi_AOCDi_SHIFT(i) ((i < 2) ? 15 + i * 6 : 9) +#define ASRCDRi_AOCDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCDi_SHIFT(i)) +#define ASRCDRi_AOCD(i, v) ((v) << ASRCDRi_AOCDi_SHIFT(i)) + +/* REG7 0x20 REG_ASRSTR */ +#define ASRSTR_DSLCNT_SHIFT 21 +#define ASRSTR_DSLCNT_MASK (1 << ASRSTR_DSLCNT_SHIFT) +#define ASRSTR_DSLCNT (1 << ASRSTR_DSLCNT_SHIFT) +#define ASRSTR_ATQOL_SHIFT 20 +#define ASRSTR_ATQOL_MASK (1 << ASRSTR_ATQOL_SHIFT) +#define ASRSTR_ATQOL (1 << ASRSTR_ATQOL_SHIFT) +#define ASRSTR_AOOLi_SHIFT(i) (17 + i) +#define ASRSTR_AOOLi_MASK(i) (1 << ASRSTR_AOOLi_SHIFT(i)) +#define ASRSTR_AOOL(i) (1 << ASRSTR_AOOLi_SHIFT(i)) +#define ASRSTR_AIOLi_SHIFT(i) (14 + i) +#define ASRSTR_AIOLi_MASK(i) (1 << ASRSTR_AIOLi_SHIFT(i)) +#define ASRSTR_AIOL(i) (1 << ASRSTR_AIOLi_SHIFT(i)) +#define ASRSTR_AODOi_SHIFT(i) (11 + i) +#define ASRSTR_AODOi_MASK(i) (1 << ASRSTR_AODOi_SHIFT(i)) +#define ASRSTR_AODO(i) (1 << ASRSTR_AODOi_SHIFT(i)) +#define ASRSTR_AIDUi_SHIFT(i) (8 + i) +#define ASRSTR_AIDUi_MASK(i) (1 << ASRSTR_AIDUi_SHIFT(i)) +#define ASRSTR_AIDU(i) (1 << ASRSTR_AIDUi_SHIFT(i)) +#define ASRSTR_FPWT_SHIFT 7 +#define ASRSTR_FPWT_MASK (1 << ASRSTR_FPWT_SHIFT) +#define ASRSTR_FPWT (1 << ASRSTR_FPWT_SHIFT) +#define ASRSTR_AOLE_SHIFT 6 +#define ASRSTR_AOLE_MASK (1 << ASRSTR_AOLE_SHIFT) +#define ASRSTR_AOLE (1 << ASRSTR_AOLE_SHIFT) +#define ASRSTR_AODEi_SHIFT(i) (3 + i) +#define ASRSTR_AODFi_MASK(i) (1 << ASRSTR_AODEi_SHIFT(i)) +#define ASRSTR_AODF(i) (1 << ASRSTR_AODEi_SHIFT(i)) +#define ASRSTR_AIDEi_SHIFT(i) (0 + i) +#define ASRSTR_AIDEi_MASK(i) (1 << ASRSTR_AIDEi_SHIFT(i)) +#define ASRSTR_AIDE(i) (1 << ASRSTR_AIDEi_SHIFT(i)) + +/* REG10 0x54 REG_ASRTFR1 */ +#define ASRTFR1_TF_BASE_WIDTH 7 +#define ASRTFR1_TF_BASE_SHIFT 6 +#define ASRTFR1_TF_BASE_MASK (((1 << ASRTFR1_TF_BASE_WIDTH) - 1) << ASRTFR1_TF_BASE_SHIFT) +#define ASRTFR1_TF_BASE(i) ((i) << ASRTFR1_TF_BASE_SHIFT) + +/* + * REG22 0xA0 REG_ASRMCRA + * REG24 0xA8 REG_ASRMCRB + * REG26 0xB0 REG_ASRMCRC + */ +#define ASRMCRi_ZEROBUFi_SHIFT 23 +#define ASRMCRi_ZEROBUFi_MASK (1 << ASRMCRi_ZEROBUFi_SHIFT) +#define ASRMCRi_ZEROBUFi (1 << ASRMCRi_ZEROBUFi_SHIFT) +#define ASRMCRi_EXTTHRSHi_SHIFT 22 +#define ASRMCRi_EXTTHRSHi_MASK (1 << ASRMCRi_EXTTHRSHi_SHIFT) +#define ASRMCRi_EXTTHRSHi (1 << ASRMCRi_EXTTHRSHi_SHIFT) +#define ASRMCRi_BUFSTALLi_SHIFT 21 +#define ASRMCRi_BUFSTALLi_MASK (1 << ASRMCRi_BUFSTALLi_SHIFT) +#define ASRMCRi_BUFSTALLi (1 << ASRMCRi_BUFSTALLi_SHIFT) +#define ASRMCRi_BYPASSPOLYi_SHIFT 20 +#define ASRMCRi_BYPASSPOLYi_MASK (1 << ASRMCRi_BYPASSPOLYi_SHIFT) +#define ASRMCRi_BYPASSPOLYi (1 << ASRMCRi_BYPASSPOLYi_SHIFT) +#define ASRMCRi_OUTFIFO_THRESHOLD_WIDTH 6 +#define ASRMCRi_OUTFIFO_THRESHOLD_SHIFT 12 +#define ASRMCRi_OUTFIFO_THRESHOLD_MASK (((1 << ASRMCRi_OUTFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) +#define ASRMCRi_OUTFIFO_THRESHOLD(v) (((v) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) & ASRMCRi_OUTFIFO_THRESHOLD_MASK) +#define ASRMCRi_RSYNIFi_SHIFT 11 +#define ASRMCRi_RSYNIFi_MASK (1 << ASRMCRi_RSYNIFi_SHIFT) +#define ASRMCRi_RSYNIFi (1 << ASRMCRi_RSYNIFi_SHIFT) +#define ASRMCRi_RSYNOFi_SHIFT 10 +#define ASRMCRi_RSYNOFi_MASK (1 << ASRMCRi_RSYNOFi_SHIFT) +#define ASRMCRi_RSYNOFi (1 << ASRMCRi_RSYNOFi_SHIFT) +#define ASRMCRi_INFIFO_THRESHOLD_WIDTH 6 +#define ASRMCRi_INFIFO_THRESHOLD_SHIFT 0 +#define ASRMCRi_INFIFO_THRESHOLD_MASK (((1 << ASRMCRi_INFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) +#define ASRMCRi_INFIFO_THRESHOLD(v) (((v) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) & ASRMCRi_INFIFO_THRESHOLD_MASK) + +/* + * REG23 0xA4 REG_ASRFSTA + * REG25 0xAC REG_ASRFSTB + * REG27 0xB4 REG_ASRFSTC + */ +#define ASRFSTi_OAFi_SHIFT 23 +#define ASRFSTi_OAFi_MASK (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_OAFi (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_OUTPUT_FIFO_WIDTH 7 +#define ASRFSTi_OUTPUT_FIFO_SHIFT 12 +#define ASRFSTi_OUTPUT_FIFO_MASK (((1 << ASRFSTi_OUTPUT_FIFO_WIDTH) - 1) << ASRFSTi_OUTPUT_FIFO_SHIFT) +#define ASRFSTi_IAEi_SHIFT 11 +#define ASRFSTi_IAEi_MASK (1 << ASRFSTi_IAEi_SHIFT) +#define ASRFSTi_IAEi (1 << ASRFSTi_IAEi_SHIFT) +#define ASRFSTi_INPUT_FIFO_WIDTH 7 +#define ASRFSTi_INPUT_FIFO_SHIFT 0 +#define ASRFSTi_INPUT_FIFO_MASK ((1 << ASRFSTi_INPUT_FIFO_WIDTH) - 1) + +/* REG28 0xC0 & 0xC4 & 0xC8 REG_ASRMCR1i */ +#define ASRMCR1i_IWD_WIDTH 3 +#define ASRMCR1i_IWD_SHIFT 9 +#define ASRMCR1i_IWD_MASK (((1 << ASRMCR1i_IWD_WIDTH) - 1) << ASRMCR1i_IWD_SHIFT) +#define ASRMCR1i_IWD(v) ((v) << ASRMCR1i_IWD_SHIFT) +#define ASRMCR1i_IMSB_SHIFT 8 +#define ASRMCR1i_IMSB_MASK (1 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_IMSB_MSB (1 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_IMSB_LSB (0 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_OMSB_SHIFT 2 +#define ASRMCR1i_OMSB_MASK (1 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OMSB_MSB (1 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OMSB_LSB (0 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OSGN_SHIFT 1 +#define ASRMCR1i_OSGN_MASK (1 << ASRMCR1i_OSGN_SHIFT) +#define ASRMCR1i_OSGN (1 << ASRMCR1i_OSGN_SHIFT) +#define ASRMCR1i_OW16_SHIFT 0 +#define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT) +#define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT) + + +enum asrc_pair_index { + ASRC_INVALID_PAIR = -1, + ASRC_PAIR_A = 0, + ASRC_PAIR_B = 1, + ASRC_PAIR_C = 2, +}; + +#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) + +enum asrc_inclk { + INCLK_NONE = 0x03, + INCLK_ESAI_RX = 0x00, + INCLK_SSI1_RX = 0x01, + INCLK_SSI2_RX = 0x02, + INCLK_SSI3_RX = 0x07, + INCLK_SPDIF_RX = 0x04, + INCLK_MLB_CLK = 0x05, + INCLK_PAD = 0x06, + INCLK_ESAI_TX = 0x08, + INCLK_SSI1_TX = 0x09, + INCLK_SSI2_TX = 0x0a, + INCLK_SSI3_TX = 0x0b, + INCLK_SPDIF_TX = 0x0c, + INCLK_ASRCK1_CLK = 0x0f, +}; + +enum asrc_outclk { + OUTCLK_NONE = 0x03, + OUTCLK_ESAI_TX = 0x00, + OUTCLK_SSI1_TX = 0x01, + OUTCLK_SSI2_TX = 0x02, + OUTCLK_SSI3_TX = 0x07, + OUTCLK_SPDIF_TX = 0x04, + OUTCLK_MLB_CLK = 0x05, + OUTCLK_PAD = 0x06, + OUTCLK_ESAI_RX = 0x08, + OUTCLK_SSI1_RX = 0x09, + OUTCLK_SSI2_RX = 0x0a, + OUTCLK_SSI3_RX = 0x0b, + OUTCLK_SPDIF_RX = 0x0c, + OUTCLK_ASRCK1_CLK = 0x0f, +}; + +#define ASRC_CLK_MAX_NUM 16 + +enum asrc_word_width { + ASRC_WIDTH_24_BIT = 0, + ASRC_WIDTH_16_BIT = 1, + ASRC_WIDTH_8_BIT = 2, +}; + +struct asrc_config { + enum asrc_pair_index pair; + unsigned int channel_num; + unsigned int buffer_num; + unsigned int dma_buffer_size; + unsigned int input_sample_rate; + unsigned int output_sample_rate; + enum asrc_word_width input_word_width; + enum asrc_word_width output_word_width; + enum asrc_inclk inclk; + enum asrc_outclk outclk; +}; + +struct asrc_req { + unsigned int chn_num; + enum asrc_pair_index index; +}; + +struct asrc_querybuf { + unsigned int buffer_index; + unsigned int input_length; + unsigned int output_length; + unsigned long input_offset; + unsigned long output_offset; +}; + +struct asrc_convert_buffer { + void *input_buffer_vaddr; + void *output_buffer_vaddr; + unsigned int input_buffer_length; + unsigned int output_buffer_length; +}; + +struct asrc_status_flags { + enum asrc_pair_index index; + unsigned int overload_error; +}; + +enum asrc_error_status { + ASRC_TASK_Q_OVERLOAD = 0x01, + ASRC_OUTPUT_TASK_OVERLOAD = 0x02, + ASRC_INPUT_TASK_OVERLOAD = 0x04, + ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, + ASRC_INPUT_BUFFER_UNDERRUN = 0x10, +}; + +struct dma_block { + dma_addr_t dma_paddr; + void *dma_vaddr; + unsigned int length; +}; + +/** + * fsl_asrc_pair: ASRC Pair private data + * + * @asrc_priv: pointer to its parent module + * @config: configuration profile + * @error: error record + * @index: pair index (ASRC_PAIR_A, ASRC_PAIR_B, ASRC_PAIR_C) + * @channels: occupied channel number + * @desc: input and output dma descriptors + * @dma_chan: inputer and output DMA channels + * @dma_data: private dma data + * @pos: hardware pointer position + * @private: pair private area + */ +struct fsl_asrc_pair { + struct fsl_asrc *asrc_priv; + struct asrc_config *config; + unsigned int error; + + enum asrc_pair_index index; + unsigned int channels; + + struct dma_async_tx_descriptor *desc[2]; + struct dma_chan *dma_chan[2]; + struct imx_dma_data dma_data; + unsigned int pos; + + void *private; +}; + +/** + * fsl_asrc_pair: ASRC private data + * + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @pdev: platform device pointer + * @regmap: regmap handler + * @paddr: physical address to the base address of registers + * @mem_clk: clock source to access register + * @ipg_clk: clock source to drive peripheral + * @spba_clk: SPBA clock (optional, depending on SoC design) + * @asrck_clk: clock sources to driver ASRC internal logic + * @lock: spin lock for resource protection + * @pair: pair pointers + * @channel_bits: width of ASRCNCR register for each pair + * @channel_avail: non-occupied channel numbers + * @asrc_rate: default sample rate for ASoC Back-Ends + * @asrc_width: default sample width for ASoC Back-Ends + * @regcache_cfg: store register value of REG_ASRCFG + */ +struct fsl_asrc { + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct platform_device *pdev; + struct regmap *regmap; + unsigned long paddr; + struct clk *mem_clk; + struct clk *ipg_clk; + struct clk *spba_clk; + struct clk *asrck_clk[ASRC_CLK_MAX_NUM]; + spinlock_t lock; + + struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM]; + unsigned int channel_bits; + unsigned int channel_avail; + + int asrc_rate; + int asrc_width; + + u32 regcache_cfg; +}; + +#define DRV_NAME "fsl-asrc-dai" +extern struct snd_soc_component_driver fsl_asrc_component; +struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir); +#endif /* _FSL_ASRC_H */ diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c new file mode 100644 index 000000000..b9ac44898 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale ASRC ALSA SoC Platform (DMA) driver +// +// Copyright (C) 2014 Freescale Semiconductor, Inc. +// +// Author: Nicolin Chen + +#include +#include +#include +#include +#include + +#include "fsl_asrc.h" + +#define FSL_ASRC_DMABUF_SIZE (256 * 1024) + +static const struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65535, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool filter(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static void fsl_asrc_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + pair->pos += snd_pcm_lib_period_bytes(substream); + if (pair->pos >= snd_pcm_lib_buffer_bytes(substream)) + pair->pos = 0; + + snd_pcm_period_elapsed(substream); +} + +static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) +{ + u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + unsigned long flags = DMA_CTRL_ACK; + + /* Prepare and submit Front-End DMA channel */ + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + pair->pos = 0; + pair->desc[!dir] = dmaengine_prep_dma_cyclic( + pair->dma_chan[!dir], runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags); + if (!pair->desc[!dir]) { + dev_err(dev, "failed to prepare slave DMA for Front-End\n"); + return -ENOMEM; + } + + pair->desc[!dir]->callback = fsl_asrc_dma_complete; + pair->desc[!dir]->callback_param = substream; + + dmaengine_submit(pair->desc[!dir]); + + /* Prepare and submit Back-End DMA channel */ + pair->desc[dir] = dmaengine_prep_dma_cyclic( + pair->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0); + if (!pair->desc[dir]) { + dev_err(dev, "failed to prepare slave DMA for Back-End\n"); + return -ENOMEM; + } + + dmaengine_submit(pair->desc[dir]); + + return 0; +} + +static int fsl_asrc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_asrc_dma_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(pair->dma_chan[IN]); + dma_async_issue_pending(pair->dma_chan[OUT]); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(pair->dma_chan[OUT]); + dmaengine_terminate_all(pair->dma_chan[IN]); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL; + struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct dma_slave_config config_fe, config_be; + enum asrc_pair_index index = pair->index; + struct device *dev = component->dev; + int stream = substream->stream; + struct imx_dma_data *tmp_data; + struct snd_soc_dpcm *dpcm; + struct dma_chan *tmp_chan; + struct device *dev_be; + u8 dir = tx ? OUT : IN; + dma_cap_mask_t mask; + int ret; + + /* Fetch the Back-End dma_data from DPCM */ + list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *substream_be; + struct snd_soc_dai *dai = be->cpu_dai; + + if (dpcm->fe != rtd) + continue; + + substream_be = snd_soc_dpcm_get_substream(be, stream); + dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be); + dev_be = dai->dev; + break; + } + + if (!dma_params_be) { + dev_err(dev, "failed to get the substream of Back-End\n"); + return -EINVAL; + } + + /* Override dma_data of the Front-End and config its dmaengine */ + dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_params_fe->addr = asrc_priv->paddr + REG_ASRDx(!dir, index); + dma_params_fe->maxburst = dma_params_be->maxburst; + + pair->dma_chan[!dir] = fsl_asrc_get_dma_channel(pair, !dir); + if (!pair->dma_chan[!dir]) { + dev_err(dev, "failed to request DMA channel\n"); + return -EINVAL; + } + + memset(&config_fe, 0, sizeof(config_fe)); + ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe); + if (ret) { + dev_err(dev, "failed to prepare DMA config for Front-End\n"); + return ret; + } + + ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe); + if (ret) { + dev_err(dev, "failed to config DMA channel for Front-End\n"); + return ret; + } + + /* Request and config DMA channel for Back-End */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_CYCLIC, mask); + + /* Get DMA request of Back-End */ + tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); + tmp_data = tmp_chan->private; + pair->dma_data.dma_request = tmp_data->dma_request; + dma_release_channel(tmp_chan); + + /* Get DMA request of Front-End */ + tmp_chan = fsl_asrc_get_dma_channel(pair, dir); + tmp_data = tmp_chan->private; + pair->dma_data.dma_request2 = tmp_data->dma_request; + pair->dma_data.peripheral_type = tmp_data->peripheral_type; + pair->dma_data.priority = tmp_data->priority; + dma_release_channel(tmp_chan); + + pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data); + if (!pair->dma_chan[dir]) { + dev_err(dev, "failed to request DMA channel for Back-End\n"); + return -EINVAL; + } + + if (asrc_priv->asrc_width == 16) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + + config_be.direction = DMA_DEV_TO_DEV; + config_be.src_addr_width = buswidth; + config_be.src_maxburst = dma_params_be->maxburst; + config_be.dst_addr_width = buswidth; + config_be.dst_maxburst = dma_params_be->maxburst; + + if (tx) { + config_be.src_addr = asrc_priv->paddr + REG_ASRDO(index); + config_be.dst_addr = dma_params_be->addr; + } else { + config_be.dst_addr = asrc_priv->paddr + REG_ASRDI(index); + config_be.src_addr = dma_params_be->addr; + } + + ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be); + if (ret) { + dev_err(dev, "failed to config DMA channel for Back-End\n"); + dma_release_channel(pair->dma_chan[dir]); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int fsl_asrc_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = NULL; + pair->dma_chan[OUT] = NULL; + + return 0; +} + +static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + struct fsl_asrc_pair *pair; + + pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); + if (!pair) + return -ENOMEM; + + pair->asrc_priv = asrc_priv; + + runtime->private_data = pair; + + snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + return 0; +} + +static int fsl_asrc_dma_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct fsl_asrc *asrc_priv; + + if (!pair) + return 0; + + asrc_priv = pair->asrc_priv; + + if (asrc_priv->pair[pair->index] == pair) + asrc_priv->pair[pair->index] = NULL; + + kfree(pair); + + return 0; +} + +static snd_pcm_uframes_t fsl_asrc_dma_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + return bytes_to_frames(substream->runtime, pair->pos); +} + +static const struct snd_pcm_ops fsl_asrc_dma_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_asrc_dma_hw_params, + .hw_free = fsl_asrc_dma_hw_free, + .trigger = fsl_asrc_dma_trigger, + .open = fsl_asrc_dma_startup, + .close = fsl_asrc_dma_shutdown, + .pointer = fsl_asrc_dma_pcm_pointer, +}; + +static int fsl_asrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + int ret, i; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(card->dev, "failed to set DMA mask\n"); + return ret; + } + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + FSL_ASRC_DMABUF_SIZE, &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to allocate DMA buffer\n"); + goto err; + } + } + + return 0; + +err: + if (--i == 0 && pcm->streams[i].substream) + snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer); + + return ret; +} + +static void fsl_asrc_dma_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } +} + +struct snd_soc_component_driver fsl_asrc_component = { + .name = DRV_NAME, + .ops = &fsl_asrc_dma_pcm_ops, + .pcm_new = fsl_asrc_dma_pcm_new, + .pcm_free = fsl_asrc_dma_pcm_free, +}; +EXPORT_SYMBOL_GPL(fsl_asrc_component); diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c new file mode 100644 index 000000000..78871de35 --- /dev/null +++ b/sound/soc/fsl/fsl_dma.c @@ -0,0 +1,981 @@ +/* + * Freescale DMA ALSA SoC PCM driver + * + * Author: Timur Tabi + * + * Copyright 2007-2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * This driver implements ASoC support for the Elo DMA controller, which is + * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms, + * the PCM driver is what handles the DMA buffer. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "fsl_dma.h" +#include "fsl_ssi.h" /* For the offset of stx0 and srx0 */ + +#define DRV_NAME "fsl_dma" + +/* + * The formats that the DMA controller supports, which is anything + * that is 8, 16, or 32 bits. + */ +#define FSLDMA_PCM_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 | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_U24_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_S32_BE | \ + SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_U32_BE) +struct dma_object { + struct snd_soc_component_driver dai; + dma_addr_t ssi_stx_phys; + dma_addr_t ssi_srx_phys; + unsigned int ssi_fifo_depth; + struct ccsr_dma_channel __iomem *channel; + unsigned int irq; + bool assigned; +}; + +/* + * The number of DMA links to use. Two is the bare minimum, but if you + * have really small links you might need more. + */ +#define NUM_DMA_LINKS 2 + +/** fsl_dma_private: p-substream DMA data + * + * Each substream has a 1-to-1 association with a DMA channel. + * + * The link[] array is first because it needs to be aligned on a 32-byte + * boundary, so putting it first will ensure alignment without padding the + * structure. + * + * @link[]: array of link descriptors + * @dma_channel: pointer to the DMA channel's registers + * @irq: IRQ for this DMA channel + * @substream: pointer to the substream object, needed by the ISR + * @ssi_sxx_phys: bus address of the STX or SRX register to use + * @ld_buf_phys: physical address of the LD buffer + * @current_link: index into link[] of the link currently being processed + * @dma_buf_phys: physical address of the DMA buffer + * @dma_buf_next: physical address of the next period to process + * @dma_buf_end: physical address of the byte after the end of the DMA + * @buffer period_size: the size of a single period + * @num_periods: the number of periods in the DMA buffer + */ +struct fsl_dma_private { + struct fsl_dma_link_descriptor link[NUM_DMA_LINKS]; + struct ccsr_dma_channel __iomem *dma_channel; + unsigned int irq; + struct snd_pcm_substream *substream; + dma_addr_t ssi_sxx_phys; + unsigned int ssi_fifo_depth; + dma_addr_t ld_buf_phys; + unsigned int current_link; + dma_addr_t dma_buf_phys; + dma_addr_t dma_buf_next; + dma_addr_t dma_buf_end; + size_t period_size; + unsigned int num_periods; +}; + +/** + * fsl_dma_hardare: define characteristics of the PCM hardware. + * + * The PCM hardware is the Freescale DMA controller. This structure defines + * the capabilities of that hardware. + * + * Since the sampling rate and data format are not controlled by the DMA + * controller, we specify no limits for those values. The only exception is + * period_bytes_min, which is set to a reasonably low value to prevent the + * DMA controller from generating too many interrupts per second. + * + * Since each link descriptor has a 32-bit byte count field, we set + * period_bytes_max to the largest 32-bit number. We also have no maximum + * number of periods. + * + * Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a + * limitation in the SSI driver requires the sample rates for playback and + * capture to be the same. + */ +static const struct snd_pcm_hardware fsl_dma_hardware = { + + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_PAUSE, + .formats = FSLDMA_PCM_FORMATS, + .period_bytes_min = 512, /* A reasonable limit */ + .period_bytes_max = (u32) -1, + .periods_min = NUM_DMA_LINKS, + .periods_max = (unsigned int) -1, + .buffer_bytes_max = 128 * 1024, /* A reasonable limit */ +}; + +/** + * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted + * + * This function should be called by the ISR whenever the DMA controller + * halts data transfer. + */ +static void fsl_dma_abort_stream(struct snd_pcm_substream *substream) +{ + snd_pcm_stop_xrun(substream); +} + +/** + * fsl_dma_update_pointers - update LD pointers to point to the next period + * + * As each period is completed, this function changes the the link + * descriptor pointers for that period to point to the next period. + */ +static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private) +{ + struct fsl_dma_link_descriptor *link = + &dma_private->link[dma_private->current_link]; + + /* Update our link descriptors to point to the next period. On a 36-bit + * system, we also need to update the ESAD bits. We also set (keep) the + * snoop bits. See the comments in fsl_dma_hw_params() about snooping. + */ + if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + link->source_addr = cpu_to_be32(dma_private->dma_buf_next); +#ifdef CONFIG_PHYS_64BIT + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(dma_private->dma_buf_next)); +#endif + } else { + link->dest_addr = cpu_to_be32(dma_private->dma_buf_next); +#ifdef CONFIG_PHYS_64BIT + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(dma_private->dma_buf_next)); +#endif + } + + /* Update our variables for next time */ + dma_private->dma_buf_next += dma_private->period_size; + + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + dma_private->dma_buf_next = dma_private->dma_buf_phys; + + if (++dma_private->current_link >= NUM_DMA_LINKS) + dma_private->current_link = 0; +} + +/** + * fsl_dma_isr: interrupt handler for the DMA controller + * + * @irq: IRQ of the DMA channel + * @dev_id: pointer to the dma_private structure for this DMA channel + */ +static irqreturn_t fsl_dma_isr(int irq, void *dev_id) +{ + struct fsl_dma_private *dma_private = dev_id; + struct snd_pcm_substream *substream = dma_private->substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + irqreturn_t ret = IRQ_NONE; + u32 sr, sr2 = 0; + + /* We got an interrupt, so read the status register to see what we + were interrupted for. + */ + sr = in_be32(&dma_channel->sr); + + if (sr & CCSR_DMA_SR_TE) { + dev_err(dev, "dma transmit error\n"); + fsl_dma_abort_stream(substream); + sr2 |= CCSR_DMA_SR_TE; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_CH) + ret = IRQ_HANDLED; + + if (sr & CCSR_DMA_SR_PE) { + dev_err(dev, "dma programming error\n"); + fsl_dma_abort_stream(substream); + sr2 |= CCSR_DMA_SR_PE; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_EOLNI) { + sr2 |= CCSR_DMA_SR_EOLNI; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_CB) + ret = IRQ_HANDLED; + + if (sr & CCSR_DMA_SR_EOSI) { + /* Tell ALSA we completed a period. */ + snd_pcm_period_elapsed(substream); + + /* + * Update our link descriptors to point to the next period. We + * only need to do this if the number of periods is not equal to + * the number of links. + */ + if (dma_private->num_periods != NUM_DMA_LINKS) + fsl_dma_update_pointers(dma_private); + + sr2 |= CCSR_DMA_SR_EOSI; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_EOLSI) { + sr2 |= CCSR_DMA_SR_EOLSI; + ret = IRQ_HANDLED; + } + + /* Clear the bits that we set */ + if (sr2) + out_be32(&dma_channel->sr, sr2); + + return ret; +} + +/** + * fsl_dma_new: initialize this PCM driver. + * + * This function is called when the codec driver calls snd_soc_new_pcms(), + * once for each .dai_link in the machine driver's snd_soc_card + * structure. + * + * snd_dma_alloc_pages() is just a front-end to dma_alloc_coherent(), which + * (currently) always allocates the DMA buffer in lowmem, even if GFP_HIGHMEM + * is specified. Therefore, any DMA buffers we allocate will always be in low + * memory, but we support for 36-bit physical addresses anyway. + * + * Regardless of where the memory is actually allocated, since the device can + * technically DMA to any 36-bit address, we do need to set the DMA mask to 36. + */ +static int fsl_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(36)); + if (ret) + return ret; + + /* Some codecs have separate DAIs for playback and capture, so we + * should allocate a DMA buffer only for the streams that are valid. + */ + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + if (ret) { + dev_err(card->dev, "can't alloc playback dma buffer\n"); + return ret; + } + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer); + if (ret) { + dev_err(card->dev, "can't alloc capture dma buffer\n"); + snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + return ret; + } + } + + return 0; +} + +/** + * fsl_dma_open: open a new substream. + * + * Each substream has its own DMA buffer. + * + * ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link + * descriptors that ping-pong from one period to the next. For example, if + * there are six periods and two link descriptors, this is how they look + * before playback starts: + * + * The last link descriptor + * ____________ points back to the first + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * | | + * V V + * _________________________________________ + * | | | | | | | The DMA buffer is + * | | | | | | | divided into 6 parts + * |______|______|______|______|______|______| + * + * and here's how they look after the first period is finished playing: + * + * ____________ + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * |______________ + * | | + * V V + * _________________________________________ + * | | | | | | | + * | | | | | | | + * |______|______|______|______|______|______| + * + * The first link descriptor now points to the third period. The DMA + * controller is currently playing the second period. When it finishes, it + * will jump back to the first descriptor and play the third period. + * + * There are four reasons we do this: + * + * 1. The only way to get the DMA controller to automatically restart the + * transfer when it gets to the end of the buffer is to use chaining + * mode. Basic direct mode doesn't offer that feature. + * 2. We need to receive an interrupt at the end of every period. The DMA + * controller can generate an interrupt at the end of every link transfer + * (aka segment). Making each period into a DMA segment will give us the + * interrupts we need. + * 3. By creating only two link descriptors, regardless of the number of + * periods, we do not need to reallocate the link descriptors if the + * number of periods changes. + * 4. All of the audio data is still stored in a single, contiguous DMA + * buffer, which is what ALSA expects. We're just dividing it into + * contiguous parts, and creating a link descriptor for each one. + */ +static int fsl_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct dma_object *dma = + container_of(component->driver, struct dma_object, dai); + struct fsl_dma_private *dma_private; + struct ccsr_dma_channel __iomem *dma_channel; + dma_addr_t ld_buf_phys; + u64 temp_link; /* Pointer to next link descriptor */ + u32 mr; + unsigned int channel; + int ret = 0; + unsigned int i; + + /* + * Reject any DMA buffer whose size is not a multiple of the period + * size. We need to make sure that the DMA buffer can be evenly divided + * into periods. + */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "invalid buffer size\n"); + return ret; + } + + channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + if (dma->assigned) { + dev_err(dev, "dma channel already assigned\n"); + return -EBUSY; + } + + dma_private = dma_alloc_coherent(dev, sizeof(struct fsl_dma_private), + &ld_buf_phys, GFP_KERNEL); + if (!dma_private) { + dev_err(dev, "can't allocate dma private data\n"); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_private->ssi_sxx_phys = dma->ssi_stx_phys; + else + dma_private->ssi_sxx_phys = dma->ssi_srx_phys; + + dma_private->ssi_fifo_depth = dma->ssi_fifo_depth; + dma_private->dma_channel = dma->channel; + dma_private->irq = dma->irq; + dma_private->substream = substream; + dma_private->ld_buf_phys = ld_buf_phys; + dma_private->dma_buf_phys = substream->dma_buffer.addr; + + ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "fsldma-audio", + dma_private); + if (ret) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + dma_private->irq, ret); + dma_free_coherent(dev, sizeof(struct fsl_dma_private), + dma_private, dma_private->ld_buf_phys); + return ret; + } + + dma->assigned = true; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware); + runtime->private_data = dma_private; + + /* Program the fixed DMA controller parameters */ + + dma_channel = dma_private->dma_channel; + + temp_link = dma_private->ld_buf_phys + + sizeof(struct fsl_dma_link_descriptor); + + for (i = 0; i < NUM_DMA_LINKS; i++) { + dma_private->link[i].next = cpu_to_be64(temp_link); + + temp_link += sizeof(struct fsl_dma_link_descriptor); + } + /* The last link descriptor points to the first */ + dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys); + + /* Tell the DMA controller where the first link descriptor is */ + out_be32(&dma_channel->clndar, + CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys)); + out_be32(&dma_channel->eclndar, + CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys)); + + /* The manual says the BCR must be clear before enabling EMP */ + out_be32(&dma_channel->bcr, 0); + + /* + * Program the mode register for interrupts, external master control, + * and source/destination hold. Also clear the Channel Abort bit. + */ + mr = in_be32(&dma_channel->mr) & + ~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE); + + /* + * We want External Master Start and External Master Pause enabled, + * because the SSI is controlling the DMA controller. We want the DMA + * controller to be set up in advance, and then we signal only the SSI + * to start transferring. + * + * We want End-Of-Segment Interrupts enabled, because this will generate + * an interrupt at the end of each segment (each link descriptor + * represents one segment). Each DMA segment is the same thing as an + * ALSA period, so this is how we get an interrupt at the end of every + * period. + * + * We want Error Interrupt enabled, so that we can get an error if + * the DMA controller is mis-programmed somehow. + */ + mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN | + CCSR_DMA_MR_EMS_EN; + + /* For playback, we want the destination address to be held. For + capture, set the source address to be held. */ + mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE; + + out_be32(&dma_channel->mr, mr); + + return 0; +} + +/** + * fsl_dma_hw_params: continue initializing the DMA links + * + * This function obtains hardware parameters about the opened stream and + * programs the DMA controller accordingly. + * + * One drawback of big-endian is that when copying integers of different + * sizes to a fixed-sized register, the address to which the integer must be + * copied is dependent on the size of the integer. + * + * For example, if P is the address of a 32-bit register, and X is a 32-bit + * integer, then X should be copied to address P. However, if X is a 16-bit + * integer, then it should be copied to P+2. If X is an 8-bit register, + * then it should be copied to P+3. + * + * So for playback of 8-bit samples, the DMA controller must transfer single + * bytes from the DMA buffer to the last byte of the STX0 register, i.e. + * offset by 3 bytes. For 16-bit samples, the offset is two bytes. + * + * For 24-bit samples, the offset is 1 byte. However, the DMA controller + * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4, + * and 8 bytes at a time). So we do not support packed 24-bit samples. + * 24-bit data must be padded to 32 bits. + */ +static int fsl_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + + /* Number of bits per sample */ + unsigned int sample_bits = + snd_pcm_format_physical_width(params_format(hw_params)); + + /* Number of bytes per frame */ + unsigned int sample_bytes = sample_bits / 8; + + /* Bus address of SSI STX register */ + dma_addr_t ssi_sxx_phys = dma_private->ssi_sxx_phys; + + /* Size of the DMA buffer, in bytes */ + size_t buffer_size = params_buffer_bytes(hw_params); + + /* Number of bytes per period */ + size_t period_size = params_period_bytes(hw_params); + + /* Pointer to next period */ + dma_addr_t temp_addr = substream->dma_buffer.addr; + + /* Pointer to DMA controller */ + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + + u32 mr; /* DMA Mode Register */ + + unsigned int i; + + /* Initialize our DMA tracking variables */ + dma_private->period_size = period_size; + dma_private->num_periods = params_periods(hw_params); + dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size; + dma_private->dma_buf_next = dma_private->dma_buf_phys + + (NUM_DMA_LINKS * period_size); + + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + /* This happens if the number of periods == NUM_DMA_LINKS */ + dma_private->dma_buf_next = dma_private->dma_buf_phys; + + mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK | + CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK); + + /* Due to a quirk of the SSI's STX register, the target address + * for the DMA operations depends on the sample size. So we calculate + * that offset here. While we're at it, also tell the DMA controller + * how much data to transfer per sample. + */ + switch (sample_bits) { + case 8: + mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1; + ssi_sxx_phys += 3; + break; + case 16: + mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2; + ssi_sxx_phys += 2; + break; + case 32: + mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4; + break; + default: + /* We should never get here */ + dev_err(dev, "unsupported sample size %u\n", sample_bits); + return -EINVAL; + } + + /* + * BWC determines how many bytes are sent/received before the DMA + * controller checks the SSI to see if it needs to stop. BWC should + * always be a multiple of the frame size, so that we always transmit + * whole frames. Each frame occupies two slots in the FIFO. The + * parameter for CCSR_DMA_MR_BWC() is rounded down the next power of two + * (MR[BWC] can only represent even powers of two). + * + * To simplify the process, we set BWC to the largest value that is + * less than or equal to the FIFO watermark. For playback, this ensures + * that we transfer the maximum amount without overrunning the FIFO. + * For capture, this ensures that we transfer the maximum amount without + * underrunning the FIFO. + * + * f = SSI FIFO depth + * w = SSI watermark value (which equals f - 2) + * b = DMA bandwidth count (in bytes) + * s = sample size (in bytes, which equals frame_size * 2) + * + * For playback, we never transmit more than the transmit FIFO + * watermark, otherwise we might write more data than the FIFO can hold. + * The watermark is equal to the FIFO depth minus two. + * + * For capture, two equations must hold: + * w > f - (b / s) + * w >= b / s + * + * So, b > 2 * s, but b must also be <= s * w. To simplify, we set + * b = s * w, which is equal to + * (dma_private->ssi_fifo_depth - 2) * sample_bytes. + */ + mr |= CCSR_DMA_MR_BWC((dma_private->ssi_fifo_depth - 2) * sample_bytes); + + out_be32(&dma_channel->mr, mr); + + for (i = 0; i < NUM_DMA_LINKS; i++) { + struct fsl_dma_link_descriptor *link = &dma_private->link[i]; + + link->count = cpu_to_be32(period_size); + + /* The snoop bit tells the DMA controller whether it should tell + * the ECM to snoop during a read or write to an address. For + * audio, we use DMA to transfer data between memory and an I/O + * device (the SSI's STX0 or SRX0 register). Snooping is only + * needed if there is a cache, so we need to snoop memory + * addresses only. For playback, that means we snoop the source + * but not the destination. For capture, we snoop the + * destination but not the source. + * + * Note that failing to snoop properly is unlikely to cause + * cache incoherency if the period size is larger than the + * size of L1 cache. This is because filling in one period will + * flush out the data for the previous period. So if you + * increased period_bytes_min to a large enough size, you might + * get more performance by not snooping, and you'll still be + * okay. You'll need to update fsl_dma_update_pointers() also. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + link->source_addr = cpu_to_be32(temp_addr); + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(temp_addr)); + + link->dest_addr = cpu_to_be32(ssi_sxx_phys); + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP | + upper_32_bits(ssi_sxx_phys)); + } else { + link->source_addr = cpu_to_be32(ssi_sxx_phys); + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP | + upper_32_bits(ssi_sxx_phys)); + + link->dest_addr = cpu_to_be32(temp_addr); + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(temp_addr)); + } + + temp_addr += period_size; + } + + return 0; +} + +/** + * fsl_dma_pointer: determine the current position of the DMA transfer + * + * This function is called by ALSA when ALSA wants to know where in the + * stream buffer the hardware currently is. + * + * For playback, the SAR register contains the physical address of the most + * recent DMA transfer. For capture, the value is in the DAR register. + * + * The base address of the buffer is stored in the source_addr field of the + * first link descriptor. + */ +static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + dma_addr_t position; + snd_pcm_uframes_t frames; + + /* Obtain the current DMA pointer, but don't read the ESAD bits if we + * only have 32-bit DMA addresses. This function is typically called + * in interrupt context, so we need to optimize it. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + position = in_be32(&dma_channel->sar); +#ifdef CONFIG_PHYS_64BIT + position |= (u64)(in_be32(&dma_channel->satr) & + CCSR_DMA_ATR_ESAD_MASK) << 32; +#endif + } else { + position = in_be32(&dma_channel->dar); +#ifdef CONFIG_PHYS_64BIT + position |= (u64)(in_be32(&dma_channel->datr) & + CCSR_DMA_ATR_ESAD_MASK) << 32; +#endif + } + + /* + * When capture is started, the SSI immediately starts to fill its FIFO. + * This means that the DMA controller is not started until the FIFO is + * full. However, ALSA calls this function before that happens, when + * MR.DAR is still zero. In this case, just return zero to indicate + * that nothing has been received yet. + */ + if (!position) + return 0; + + if ((position < dma_private->dma_buf_phys) || + (position > dma_private->dma_buf_end)) { + dev_err(dev, "dma pointer is out of range, halting stream\n"); + return SNDRV_PCM_POS_XRUN; + } + + frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys); + + /* + * If the current address is just past the end of the buffer, wrap it + * around. + */ + if (frames == runtime->buffer_size) + frames = 0; + + return frames; +} + +/** + * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params() + * + * Release the resources allocated in fsl_dma_hw_params() and de-program the + * registers. + * + * This function can be called multiple times. + */ +static int fsl_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + + if (dma_private) { + struct ccsr_dma_channel __iomem *dma_channel; + + dma_channel = dma_private->dma_channel; + + /* Stop the DMA */ + out_be32(&dma_channel->mr, CCSR_DMA_MR_CA); + out_be32(&dma_channel->mr, 0); + + /* Reset all the other registers */ + out_be32(&dma_channel->sr, -1); + out_be32(&dma_channel->clndar, 0); + out_be32(&dma_channel->eclndar, 0); + out_be32(&dma_channel->satr, 0); + out_be32(&dma_channel->sar, 0); + out_be32(&dma_channel->datr, 0); + out_be32(&dma_channel->dar, 0); + out_be32(&dma_channel->bcr, 0); + out_be32(&dma_channel->nlndar, 0); + out_be32(&dma_channel->enlndar, 0); + } + + return 0; +} + +/** + * fsl_dma_close: close the stream. + */ +static int fsl_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct device *dev = component->dev; + struct dma_object *dma = + container_of(component->driver, struct dma_object, dai); + + if (dma_private) { + if (dma_private->irq) + free_irq(dma_private->irq, dma_private); + + /* Deallocate the fsl_dma_private structure */ + dma_free_coherent(dev, sizeof(struct fsl_dma_private), + dma_private, dma_private->ld_buf_phys); + substream->runtime->private_data = NULL; + } + + dma->assigned = false; + + return 0; +} + +/* + * Remove this PCM driver. + */ +static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +/** + * find_ssi_node -- returns the SSI node that points to its DMA channel node + * + * Although this DMA driver attempts to operate independently of the other + * devices, it still needs to determine some information about the SSI device + * that it's working with. Unfortunately, the device tree does not contain + * a pointer from the DMA channel node to the SSI node -- the pointer goes the + * other way. So we need to scan the device tree for SSI nodes until we find + * the one that points to the given DMA channel node. It's ugly, but at least + * it's contained in this one function. + */ +static struct device_node *find_ssi_node(struct device_node *dma_channel_np) +{ + struct device_node *ssi_np, *np; + + for_each_compatible_node(ssi_np, NULL, "fsl,mpc8610-ssi") { + /* Check each DMA phandle to see if it points to us. We + * assume that device_node pointers are a valid comparison. + */ + np = of_parse_phandle(ssi_np, "fsl,playback-dma", 0); + of_node_put(np); + if (np == dma_channel_np) + return ssi_np; + + np = of_parse_phandle(ssi_np, "fsl,capture-dma", 0); + of_node_put(np); + if (np == dma_channel_np) + return ssi_np; + } + + return NULL; +} + +static const struct snd_pcm_ops fsl_dma_ops = { + .open = fsl_dma_open, + .close = fsl_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_dma_hw_params, + .hw_free = fsl_dma_hw_free, + .pointer = fsl_dma_pointer, +}; + +static int fsl_soc_dma_probe(struct platform_device *pdev) +{ + struct dma_object *dma; + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np; + struct resource res; + const uint32_t *iprop; + int ret; + + /* Find the SSI node that points to us. */ + ssi_np = find_ssi_node(np); + if (!ssi_np) { + dev_err(&pdev->dev, "cannot find parent SSI node\n"); + return -ENODEV; + } + + ret = of_address_to_resource(ssi_np, 0, &res); + if (ret) { + dev_err(&pdev->dev, "could not determine resources for %pOF\n", + ssi_np); + of_node_put(ssi_np); + return ret; + } + + dma = kzalloc(sizeof(*dma), GFP_KERNEL); + if (!dma) { + of_node_put(ssi_np); + return -ENOMEM; + } + + dma->dai.name = DRV_NAME; + dma->dai.ops = &fsl_dma_ops; + dma->dai.pcm_new = fsl_dma_new; + dma->dai.pcm_free = fsl_dma_free_dma_buffers; + + /* Store the SSI-specific information that we need */ + dma->ssi_stx_phys = res.start + REG_SSI_STX0; + dma->ssi_srx_phys = res.start + REG_SSI_SRX0; + + iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); + if (iprop) + dma->ssi_fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + dma->ssi_fifo_depth = 8; + + of_node_put(ssi_np); + + ret = devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "could not register platform\n"); + kfree(dma); + return ret; + } + + dma->channel = of_iomap(np, 0); + dma->irq = irq_of_parse_and_map(np, 0); + + dev_set_drvdata(&pdev->dev, dma); + + return 0; +} + +static int fsl_soc_dma_remove(struct platform_device *pdev) +{ + struct dma_object *dma = dev_get_drvdata(&pdev->dev); + + iounmap(dma->channel); + irq_dispose_mapping(dma->irq); + kfree(dma); + + return 0; +} + +static const struct of_device_id fsl_soc_dma_ids[] = { + { .compatible = "fsl,ssi-dma-channel", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_soc_dma_ids); + +static struct platform_driver fsl_soc_dma_driver = { + .driver = { + .name = "fsl-pcm-audio", + .of_match_table = fsl_soc_dma_ids, + }, + .probe = fsl_soc_dma_probe, + .remove = fsl_soc_dma_remove, +}; + +module_platform_driver(fsl_soc_dma_driver); + +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h new file mode 100644 index 000000000..78fee97e8 --- /dev/null +++ b/sound/soc/fsl/fsl_dma.h @@ -0,0 +1,129 @@ +/* + * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MPC8610_PCM_H +#define _MPC8610_PCM_H + +struct ccsr_dma { + u8 res0[0x100]; + struct ccsr_dma_channel { + __be32 mr; /* Mode register */ + __be32 sr; /* Status register */ + __be32 eclndar; /* Current link descriptor extended addr reg */ + __be32 clndar; /* Current link descriptor address register */ + __be32 satr; /* Source attributes register */ + __be32 sar; /* Source address register */ + __be32 datr; /* Destination attributes register */ + __be32 dar; /* Destination address register */ + __be32 bcr; /* Byte count register */ + __be32 enlndar; /* Next link descriptor extended address reg */ + __be32 nlndar; /* Next link descriptor address register */ + u8 res1[4]; + __be32 eclsdar; /* Current list descriptor extended addr reg */ + __be32 clsdar; /* Current list descriptor address register */ + __be32 enlsdar; /* Next list descriptor extended address reg */ + __be32 nlsdar; /* Next list descriptor address register */ + __be32 ssr; /* Source stride register */ + __be32 dsr; /* Destination stride register */ + u8 res2[0x38]; + } channel[4]; + __be32 dgsr; +}; + +#define CCSR_DMA_MR_BWC_DISABLED 0x0F000000 +#define CCSR_DMA_MR_BWC_SHIFT 24 +#define CCSR_DMA_MR_BWC_MASK 0x0F000000 +#define CCSR_DMA_MR_BWC(x) \ + ((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK) +#define CCSR_DMA_MR_EMP_EN 0x00200000 +#define CCSR_DMA_MR_EMS_EN 0x00040000 +#define CCSR_DMA_MR_DAHTS_MASK 0x00030000 +#define CCSR_DMA_MR_DAHTS_1 0x00000000 +#define CCSR_DMA_MR_DAHTS_2 0x00010000 +#define CCSR_DMA_MR_DAHTS_4 0x00020000 +#define CCSR_DMA_MR_DAHTS_8 0x00030000 +#define CCSR_DMA_MR_SAHTS_MASK 0x0000C000 +#define CCSR_DMA_MR_SAHTS_1 0x00000000 +#define CCSR_DMA_MR_SAHTS_2 0x00004000 +#define CCSR_DMA_MR_SAHTS_4 0x00008000 +#define CCSR_DMA_MR_SAHTS_8 0x0000C000 +#define CCSR_DMA_MR_DAHE 0x00002000 +#define CCSR_DMA_MR_SAHE 0x00001000 +#define CCSR_DMA_MR_SRW 0x00000400 +#define CCSR_DMA_MR_EOSIE 0x00000200 +#define CCSR_DMA_MR_EOLNIE 0x00000100 +#define CCSR_DMA_MR_EOLSIE 0x00000080 +#define CCSR_DMA_MR_EIE 0x00000040 +#define CCSR_DMA_MR_XFE 0x00000020 +#define CCSR_DMA_MR_CDSM_SWSM 0x00000010 +#define CCSR_DMA_MR_CA 0x00000008 +#define CCSR_DMA_MR_CTM 0x00000004 +#define CCSR_DMA_MR_CC 0x00000002 +#define CCSR_DMA_MR_CS 0x00000001 + +#define CCSR_DMA_SR_TE 0x00000080 +#define CCSR_DMA_SR_CH 0x00000020 +#define CCSR_DMA_SR_PE 0x00000010 +#define CCSR_DMA_SR_EOLNI 0x00000008 +#define CCSR_DMA_SR_CB 0x00000004 +#define CCSR_DMA_SR_EOSI 0x00000002 +#define CCSR_DMA_SR_EOLSI 0x00000001 + +/* ECLNDAR takes bits 32-36 of the CLNDAR register */ +static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) +{ + return (x >> 32) & 0xf; +} + +#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE) +#define CCSR_DMA_CLNDAR_EOSIE 0x00000008 + +/* SATR and DATR, combined */ +#define CCSR_DMA_ATR_PBATMU 0x20000000 +#define CCSR_DMA_ATR_TFLOWLVL_0 0x00000000 +#define CCSR_DMA_ATR_TFLOWLVL_1 0x06000000 +#define CCSR_DMA_ATR_TFLOWLVL_2 0x08000000 +#define CCSR_DMA_ATR_TFLOWLVL_3 0x0C000000 +#define CCSR_DMA_ATR_PCIORDER 0x02000000 +#define CCSR_DMA_ATR_SME 0x01000000 +#define CCSR_DMA_ATR_NOSNOOP 0x00040000 +#define CCSR_DMA_ATR_SNOOP 0x00050000 +#define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +/** + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List + * Descriptor. Each object must be aligned on a 32-byte boundary. Each + * list descriptor points to a linked-list of link Descriptors. + */ +struct fsl_dma_list_descriptor { + __be64 next; /* Address of next list descriptor */ + __be64 first_link; /* Address of first link descriptor */ + __be32 source; /* Source stride */ + __be32 dest; /* Destination stride */ + u8 res[8]; /* Reserved */ +} __attribute__ ((aligned(32), packed)); + +/** + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor + * must be aligned on a 32-byte boundary. + */ +struct fsl_dma_link_descriptor { + __be32 source_attr; /* Programmed into SATR register */ + __be32 source_addr; /* Programmed into SAR register */ + __be32 dest_attr; /* Programmed into DATR register */ + __be32 dest_addr; /* Programmed into DAR register */ + __be64 next; /* Address of next link descriptor */ + __be32 count; /* Byte count */ + u8 res[4]; /* Reserved */ +} __attribute__ ((aligned(32), packed)); + +#endif diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c new file mode 100644 index 000000000..baa76337c --- /dev/null +++ b/sound/soc/fsl/fsl_esai.c @@ -0,0 +1,1013 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale ESAI ALSA SoC Digital Audio Interface (DAI) driver +// +// Copyright (C) 2014 Freescale Semiconductor, Inc. + +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_esai.h" +#include "imx-pcm.h" + +#define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/** + * fsl_esai: ESAI private data + * + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @pdev: platform device pointer + * @regmap: regmap handler + * @coreclk: clock source to access register + * @extalclk: esai clock source to derive HCK, SCK and FS + * @fsysclk: system clock source to derive HCK, SCK and FS + * @spbaclk: SPBA clock (optional, depending on SoC design) + * @fifo_depth: depth of tx/rx FIFO + * @slot_width: width of each DAI slot + * @slots: number of slots + * @hck_rate: clock rate of desired HCKx clock + * @sck_rate: clock rate of desired SCKx clock + * @hck_dir: the direction of HCKx pads + * @sck_div: if using PSR/PM dividers for SCKx clock + * @slave_mode: if fully using DAI slave mode + * @synchronous: if using tx/rx synchronous mode + * @name: driver name + */ +struct fsl_esai { + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct platform_device *pdev; + struct regmap *regmap; + struct clk *coreclk; + struct clk *extalclk; + struct clk *fsysclk; + struct clk *spbaclk; + u32 fifo_depth; + u32 slot_width; + u32 slots; + u32 tx_mask; + u32 rx_mask; + u32 hck_rate[2]; + u32 sck_rate[2]; + bool hck_dir[2]; + bool sck_div[2]; + bool slave_mode; + bool synchronous; + char name[32]; +}; + +static irqreturn_t esai_isr(int irq, void *devid) +{ + struct fsl_esai *esai_priv = (struct fsl_esai *)devid; + struct platform_device *pdev = esai_priv->pdev; + u32 esr; + + regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr); + + if (esr & ESAI_ESR_TINIT_MASK) + dev_dbg(&pdev->dev, "isr: Transmission Initialized\n"); + + if (esr & ESAI_ESR_RFF_MASK) + dev_warn(&pdev->dev, "isr: Receiving overrun\n"); + + if (esr & ESAI_ESR_TFE_MASK) + dev_warn(&pdev->dev, "isr: Transmission underrun\n"); + + if (esr & ESAI_ESR_TLS_MASK) + dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n"); + + if (esr & ESAI_ESR_TDE_MASK) + dev_dbg(&pdev->dev, "isr: Transmission data exception\n"); + + if (esr & ESAI_ESR_TED_MASK) + dev_dbg(&pdev->dev, "isr: Transmitting even slots\n"); + + if (esr & ESAI_ESR_TD_MASK) + dev_dbg(&pdev->dev, "isr: Transmitting data\n"); + + if (esr & ESAI_ESR_RLS_MASK) + dev_dbg(&pdev->dev, "isr: Just received the last slot\n"); + + if (esr & ESAI_ESR_RDE_MASK) + dev_dbg(&pdev->dev, "isr: Receiving data exception\n"); + + if (esr & ESAI_ESR_RED_MASK) + dev_dbg(&pdev->dev, "isr: Receiving even slots\n"); + + if (esr & ESAI_ESR_RD_MASK) + dev_dbg(&pdev->dev, "isr: Receiving data\n"); + + return IRQ_HANDLED; +} + +/** + * This function is used to calculate the divisors of psr, pm, fp and it is + * supposed to be called in set_dai_sysclk() and set_bclk(). + * + * @ratio: desired overall ratio for the paticipating dividers + * @usefp: for HCK setting, there is no need to set fp divider + * @fp: bypass other dividers by setting fp directly if fp != 0 + * @tx: current setting is for playback or capture + */ +static int fsl_esai_divisor_cal(struct snd_soc_dai *dai, bool tx, u32 ratio, + bool usefp, u32 fp) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + u32 psr, pm = 999, maxfp, prod, sub, savesub, i, j; + + maxfp = usefp ? 16 : 1; + + if (usefp && fp) + goto out_fp; + + if (ratio > 2 * 8 * 256 * maxfp || ratio < 2) { + dev_err(dai->dev, "the ratio is out of range (2 ~ %d)\n", + 2 * 8 * 256 * maxfp); + return -EINVAL; + } else if (ratio % 2) { + dev_err(dai->dev, "the raio must be even if using upper divider\n"); + return -EINVAL; + } + + ratio /= 2; + + psr = ratio <= 256 * maxfp ? ESAI_xCCR_xPSR_BYPASS : ESAI_xCCR_xPSR_DIV8; + + /* Do not loop-search if PM (1 ~ 256) alone can serve the ratio */ + if (ratio <= 256) { + pm = ratio; + fp = 1; + goto out; + } + + /* Set the max fluctuation -- 0.1% of the max devisor */ + savesub = (psr ? 1 : 8) * 256 * maxfp / 1000; + + /* Find the best value for PM */ + for (i = 1; i <= 256; i++) { + for (j = 1; j <= maxfp; j++) { + /* PSR (1 or 8) * PM (1 ~ 256) * FP (1 ~ 16) */ + prod = (psr ? 1 : 8) * i * j; + + if (prod == ratio) + sub = 0; + else if (prod / ratio == 1) + sub = prod - ratio; + else if (ratio / prod == 1) + sub = ratio - prod; + else + continue; + + /* Calculate the fraction */ + sub = sub * 1000 / ratio; + if (sub < savesub) { + savesub = sub; + pm = i; + fp = j; + } + + /* We are lucky */ + if (savesub == 0) + goto out; + } + } + + if (pm == 999) { + dev_err(dai->dev, "failed to calculate proper divisors\n"); + return -EINVAL; + } + +out: + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), + ESAI_xCCR_xPSR_MASK | ESAI_xCCR_xPM_MASK, + psr | ESAI_xCCR_xPM(pm)); + +out_fp: + /* Bypass fp if not being required */ + if (maxfp <= 1) + return 0; + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), + ESAI_xCCR_xFP_MASK, ESAI_xCCR_xFP(fp)); + + return 0; +} + +/** + * This function mainly configures the clock frequency of MCLK (HCKT/HCKR) + * + * @Parameters: + * clk_id: The clock source of HCKT/HCKR + * (Input from outside; output from inside, FSYS or EXTAL) + * freq: The required clock rate of HCKT/HCKR + * dir: The clock direction of HCKT/HCKR + * + * Note: If the direction is input, we do not care about clk_id. + */ +static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + struct clk *clksrc = esai_priv->extalclk; + bool tx = clk_id <= ESAI_HCKT_EXTAL; + bool in = dir == SND_SOC_CLOCK_IN; + u32 ratio, ecr = 0; + unsigned long clk_rate; + int ret; + + if (freq == 0) { + dev_err(dai->dev, "%sput freq of HCK%c should not be 0Hz\n", + in ? "in" : "out", tx ? 'T' : 'R'); + return -EINVAL; + } + + /* Bypass divider settings if the requirement doesn't change */ + if (freq == esai_priv->hck_rate[tx] && dir == esai_priv->hck_dir[tx]) + return 0; + + /* sck_div can be only bypassed if ETO/ERO=0 and SNC_SOC_CLOCK_OUT */ + esai_priv->sck_div[tx] = true; + + /* Set the direction of HCKT/HCKR pins */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), + ESAI_xCCR_xHCKD, in ? 0 : ESAI_xCCR_xHCKD); + + if (in) + goto out; + + switch (clk_id) { + case ESAI_HCKT_FSYS: + case ESAI_HCKR_FSYS: + clksrc = esai_priv->fsysclk; + break; + case ESAI_HCKT_EXTAL: + ecr |= ESAI_ECR_ETI; + break; + case ESAI_HCKR_EXTAL: + ecr |= ESAI_ECR_ERI; + break; + default: + return -EINVAL; + } + + if (IS_ERR(clksrc)) { + dev_err(dai->dev, "no assigned %s clock\n", + clk_id % 2 ? "extal" : "fsys"); + return PTR_ERR(clksrc); + } + clk_rate = clk_get_rate(clksrc); + + ratio = clk_rate / freq; + if (ratio * freq > clk_rate) + ret = ratio * freq - clk_rate; + else if (ratio * freq < clk_rate) + ret = clk_rate - ratio * freq; + else + ret = 0; + + /* Block if clock source can not be divided into the required rate */ + if (ret != 0 && clk_rate / ret < 1000) { + dev_err(dai->dev, "failed to derive required HCK%c rate\n", + tx ? 'T' : 'R'); + return -EINVAL; + } + + /* Only EXTAL source can be output directly without using PSR and PM */ + if (ratio == 1 && clksrc == esai_priv->extalclk) { + /* Bypass all the dividers if not being needed */ + ecr |= tx ? ESAI_ECR_ETO : ESAI_ECR_ERO; + goto out; + } else if (ratio < 2) { + /* The ratio should be no less than 2 if using other sources */ + dev_err(dai->dev, "failed to derive required HCK%c rate\n", + tx ? 'T' : 'R'); + return -EINVAL; + } + + ret = fsl_esai_divisor_cal(dai, tx, ratio, false, 0); + if (ret) + return ret; + + esai_priv->sck_div[tx] = false; + +out: + esai_priv->hck_dir[tx] = dir; + esai_priv->hck_rate[tx] = freq; + + regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR, + tx ? ESAI_ECR_ETI | ESAI_ECR_ETO : + ESAI_ECR_ERI | ESAI_ECR_ERO, ecr); + + return 0; +} + +/** + * This function configures the related dividers according to the bclk rate + */ +static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + u32 hck_rate = esai_priv->hck_rate[tx]; + u32 sub, ratio = hck_rate / freq; + int ret; + + /* Don't apply for fully slave mode or unchanged bclk */ + if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq) + return 0; + + if (ratio * freq > hck_rate) + sub = ratio * freq - hck_rate; + else if (ratio * freq < hck_rate) + sub = hck_rate - ratio * freq; + else + sub = 0; + + /* Block if clock source can not be divided into the required rate */ + if (sub != 0 && hck_rate / sub < 1000) { + dev_err(dai->dev, "failed to derive required SCK%c rate\n", + tx ? 'T' : 'R'); + return -EINVAL; + } + + /* The ratio should be contented by FP alone if bypassing PM and PSR */ + if (!esai_priv->sck_div[tx] && (ratio > 16 || ratio == 0)) { + dev_err(dai->dev, "the ratio is out of range (1 ~ 16)\n"); + return -EINVAL; + } + + ret = fsl_esai_divisor_cal(dai, tx, ratio, true, + esai_priv->sck_div[tx] ? 0 : ratio); + if (ret) + return ret; + + /* Save current bclk rate */ + esai_priv->sck_rate[tx] = freq; + + return 0; +} + +static int fsl_esai_set_dai_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, + ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots)); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, + ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots)); + + esai_priv->slot_width = slot_width; + esai_priv->slots = slots; + esai_priv->tx_mask = tx_mask; + esai_priv->rx_mask = rx_mask; + + return 0; +} + +static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + u32 xcr = 0, xccr = 0, mask; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* Data on rising edge of bclk, frame low, 1clk before data */ + xcr |= ESAI_xCR_xFSR; + xccr |= ESAI_xCCR_xFSP | ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* Data on rising edge of bclk, frame high */ + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_RIGHT_J: + /* Data on rising edge of bclk, frame high, right aligned */ + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + xcr |= ESAI_xCR_xWA; + break; + case SND_SOC_DAIFMT_DSP_A: + /* Data on rising edge of bclk, frame high, 1clk before data */ + xcr |= ESAI_xCR_xFSL | ESAI_xCR_xFSR; + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_DSP_B: + /* Data on rising edge of bclk, frame high */ + xcr |= ESAI_xCR_xFSL; + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + xccr ^= ESAI_xCCR_xFSP; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP; + break; + default: + return -EINVAL; + } + + esai_priv->slave_mode = false; + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + esai_priv->slave_mode = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + xccr |= ESAI_xCCR_xCKD; + break; + case SND_SOC_DAIFMT_CBM_CFS: + xccr |= ESAI_xCCR_xFSD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + break; + default: + return -EINVAL; + } + + mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR | ESAI_xCR_xWA; + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr); + + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr); + + return 0; +} + +static int fsl_esai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + int ret; + + /* + * Some platforms might use the same bit to gate all three or two of + * clocks, so keep all clocks open/close at the same time for safety + */ + ret = clk_prepare_enable(esai_priv->coreclk); + if (ret) + return ret; + if (!IS_ERR(esai_priv->spbaclk)) { + ret = clk_prepare_enable(esai_priv->spbaclk); + if (ret) + goto err_spbaclk; + } + if (!IS_ERR(esai_priv->extalclk)) { + ret = clk_prepare_enable(esai_priv->extalclk); + if (ret) + goto err_extalck; + } + if (!IS_ERR(esai_priv->fsysclk)) { + ret = clk_prepare_enable(esai_priv->fsysclk); + if (ret) + goto err_fsysclk; + } + + if (!dai->active) { + /* Set synchronous mode */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_SAICR, + ESAI_SAICR_SYNC, esai_priv->synchronous ? + ESAI_SAICR_SYNC : 0); + + /* Set slots count */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, + ESAI_xCCR_xDC_MASK, + ESAI_xCCR_xDC(esai_priv->slots)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, + ESAI_xCCR_xDC_MASK, + ESAI_xCCR_xDC(esai_priv->slots)); + } + + return 0; + +err_fsysclk: + if (!IS_ERR(esai_priv->extalclk)) + clk_disable_unprepare(esai_priv->extalclk); +err_extalck: + if (!IS_ERR(esai_priv->spbaclk)) + clk_disable_unprepare(esai_priv->spbaclk); +err_spbaclk: + clk_disable_unprepare(esai_priv->coreclk); + + return ret; +} + +static int fsl_esai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 width = params_width(params); + u32 channels = params_channels(params); + u32 pins = DIV_ROUND_UP(channels, esai_priv->slots); + u32 slot_width = width; + u32 bclk, mask, val; + int ret; + + /* Override slot_width if being specifically set */ + if (esai_priv->slot_width) + slot_width = esai_priv->slot_width; + + bclk = params_rate(params) * slot_width * esai_priv->slots; + + ret = fsl_esai_set_bclk(dai, tx, bclk); + if (ret) + return ret; + + /* Use Normal mode to support monaural audio */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ? + ESAI_xCR_xMOD_NETWORK : 0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR); + + mask = ESAI_xFCR_xFR_MASK | ESAI_xFCR_xWA_MASK | ESAI_xFCR_xFWM_MASK | + (tx ? ESAI_xFCR_TE_MASK | ESAI_xFCR_TIEN : ESAI_xFCR_RE_MASK); + val = ESAI_xFCR_xWA(width) | ESAI_xFCR_xFWM(esai_priv->fifo_depth) | + (tx ? ESAI_xFCR_TE(pins) | ESAI_xFCR_TIEN : ESAI_xFCR_RE(pins)); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val); + + mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0); + val = ESAI_xCR_xSWS(slot_width, width) | (tx ? ESAI_xCR_PADC : 0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); + + /* Remove ESAI personal reset by configuring ESAI_PCRC and ESAI_PRRC */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, + ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, + ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO)); + return 0; +} + +static void fsl_esai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + + if (!IS_ERR(esai_priv->fsysclk)) + clk_disable_unprepare(esai_priv->fsysclk); + if (!IS_ERR(esai_priv->extalclk)) + clk_disable_unprepare(esai_priv->extalclk); + if (!IS_ERR(esai_priv->spbaclk)) + clk_disable_unprepare(esai_priv->spbaclk); + clk_disable_unprepare(esai_priv->coreclk); +} + +static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 i, channels = substream->runtime->channels; + u32 pins = DIV_ROUND_UP(channels, esai_priv->slots); + u32 mask; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFEN_MASK, ESAI_xFCR_xFEN); + + /* Write initial words reqiured by ESAI as normal procedure */ + for (i = 0; tx && i < channels; i++) + regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0); + + /* + * When set the TE/RE in the end of enablement flow, there + * will be channel swap issue for multi data line case. + * In order to workaround this issue, we switch the bit + * enablement sequence to below sequence + * 1) clear the xSMB & xSMA: which is done in probe and + * stop state. + * 2) set TE/RE + * 3) set xSMB + * 4) set xSMA: xSMA is the last one in this flow, which + * will trigger esai to start. + */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, + tx ? ESAI_xCR_TE(pins) : ESAI_xCR_RE(pins)); + mask = tx ? esai_priv->tx_mask : esai_priv->rx_mask; + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx), + ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(mask)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx), + ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(mask)); + + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx), + ESAI_xSMA_xS_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx), + ESAI_xSMB_xS_MASK, 0); + + /* Disable and reset FIFO */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops fsl_esai_dai_ops = { + .startup = fsl_esai_startup, + .shutdown = fsl_esai_shutdown, + .trigger = fsl_esai_trigger, + .hw_params = fsl_esai_hw_params, + .set_sysclk = fsl_esai_set_dai_sysclk, + .set_fmt = fsl_esai_set_dai_fmt, + .set_tdm_slot = fsl_esai_set_dai_tdm_slot, +}; + +static int fsl_esai_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &esai_priv->dma_params_tx, + &esai_priv->dma_params_rx); + + return 0; +} + +static struct snd_soc_dai_driver fsl_esai_dai = { + .probe = fsl_esai_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 12, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = FSL_ESAI_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = FSL_ESAI_FORMATS, + }, + .ops = &fsl_esai_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_esai_component = { + .name = "fsl-esai", +}; + +static const struct reg_default fsl_esai_reg_defaults[] = { + {REG_ESAI_ETDR, 0x00000000}, + {REG_ESAI_ECR, 0x00000000}, + {REG_ESAI_TFCR, 0x00000000}, + {REG_ESAI_RFCR, 0x00000000}, + {REG_ESAI_TX0, 0x00000000}, + {REG_ESAI_TX1, 0x00000000}, + {REG_ESAI_TX2, 0x00000000}, + {REG_ESAI_TX3, 0x00000000}, + {REG_ESAI_TX4, 0x00000000}, + {REG_ESAI_TX5, 0x00000000}, + {REG_ESAI_TSR, 0x00000000}, + {REG_ESAI_SAICR, 0x00000000}, + {REG_ESAI_TCR, 0x00000000}, + {REG_ESAI_TCCR, 0x00000000}, + {REG_ESAI_RCR, 0x00000000}, + {REG_ESAI_RCCR, 0x00000000}, + {REG_ESAI_TSMA, 0x0000ffff}, + {REG_ESAI_TSMB, 0x0000ffff}, + {REG_ESAI_RSMA, 0x0000ffff}, + {REG_ESAI_RSMB, 0x0000ffff}, + {REG_ESAI_PRRC, 0x00000000}, + {REG_ESAI_PCRC, 0x00000000}, +}; + +static bool fsl_esai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ESAI_ERDR: + case REG_ESAI_ECR: + case REG_ESAI_ESR: + case REG_ESAI_TFCR: + case REG_ESAI_TFSR: + case REG_ESAI_RFCR: + case REG_ESAI_RFSR: + case REG_ESAI_RX0: + case REG_ESAI_RX1: + case REG_ESAI_RX2: + case REG_ESAI_RX3: + case REG_ESAI_SAISR: + case REG_ESAI_SAICR: + case REG_ESAI_TCR: + case REG_ESAI_TCCR: + case REG_ESAI_RCR: + case REG_ESAI_RCCR: + case REG_ESAI_TSMA: + case REG_ESAI_TSMB: + case REG_ESAI_RSMA: + case REG_ESAI_RSMB: + case REG_ESAI_PRRC: + case REG_ESAI_PCRC: + return true; + default: + return false; + } +} + +static bool fsl_esai_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ESAI_ERDR: + case REG_ESAI_ESR: + case REG_ESAI_TFSR: + case REG_ESAI_RFSR: + case REG_ESAI_RX0: + case REG_ESAI_RX1: + case REG_ESAI_RX2: + case REG_ESAI_RX3: + case REG_ESAI_SAISR: + return true; + default: + return false; + } +} + +static bool fsl_esai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ESAI_ETDR: + case REG_ESAI_ECR: + case REG_ESAI_TFCR: + case REG_ESAI_RFCR: + case REG_ESAI_TX0: + case REG_ESAI_TX1: + case REG_ESAI_TX2: + case REG_ESAI_TX3: + case REG_ESAI_TX4: + case REG_ESAI_TX5: + case REG_ESAI_TSR: + case REG_ESAI_SAICR: + case REG_ESAI_TCR: + case REG_ESAI_TCCR: + case REG_ESAI_RCR: + case REG_ESAI_RCCR: + case REG_ESAI_TSMA: + case REG_ESAI_TSMB: + case REG_ESAI_RSMA: + case REG_ESAI_RSMB: + case REG_ESAI_PRRC: + case REG_ESAI_PCRC: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_esai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_ESAI_PCRC, + .reg_defaults = fsl_esai_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_esai_reg_defaults), + .readable_reg = fsl_esai_readable_reg, + .volatile_reg = fsl_esai_volatile_reg, + .writeable_reg = fsl_esai_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int fsl_esai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_esai *esai_priv; + struct resource *res; + const __be32 *iprop; + void __iomem *regs; + int irq, ret; + + esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL); + if (!esai_priv) + return -ENOMEM; + + esai_priv->pdev = pdev; + strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1); + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + esai_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "core", regs, &fsl_esai_regmap_config); + if (IS_ERR(esai_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap: %ld\n", + PTR_ERR(esai_priv->regmap)); + return PTR_ERR(esai_priv->regmap); + } + + esai_priv->coreclk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(esai_priv->coreclk)) { + dev_err(&pdev->dev, "failed to get core clock: %ld\n", + PTR_ERR(esai_priv->coreclk)); + return PTR_ERR(esai_priv->coreclk); + } + + esai_priv->extalclk = devm_clk_get(&pdev->dev, "extal"); + if (IS_ERR(esai_priv->extalclk)) + dev_warn(&pdev->dev, "failed to get extal clock: %ld\n", + PTR_ERR(esai_priv->extalclk)); + + esai_priv->fsysclk = devm_clk_get(&pdev->dev, "fsys"); + if (IS_ERR(esai_priv->fsysclk)) + dev_warn(&pdev->dev, "failed to get fsys clock: %ld\n", + PTR_ERR(esai_priv->fsysclk)); + + esai_priv->spbaclk = devm_clk_get(&pdev->dev, "spba"); + if (IS_ERR(esai_priv->spbaclk)) + dev_warn(&pdev->dev, "failed to get spba clock: %ld\n", + PTR_ERR(esai_priv->spbaclk)); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0, + esai_priv->name, esai_priv); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u\n", irq); + return ret; + } + + /* Set a default slot number */ + esai_priv->slots = 2; + + /* Set a default master/slave state */ + esai_priv->slave_mode = true; + + /* Determine the FIFO depth */ + iprop = of_get_property(np, "fsl,fifo-depth", NULL); + if (iprop) + esai_priv->fifo_depth = be32_to_cpup(iprop); + else + esai_priv->fifo_depth = 64; + + esai_priv->dma_params_tx.maxburst = 16; + esai_priv->dma_params_rx.maxburst = 16; + esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR; + esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR; + + esai_priv->synchronous = + of_property_read_bool(np, "fsl,esai-synchronous"); + + /* Implement full symmetry for synchronous mode */ + if (esai_priv->synchronous) { + fsl_esai_dai.symmetric_rates = 1; + fsl_esai_dai.symmetric_channels = 1; + fsl_esai_dai.symmetric_samplebits = 1; + } + + dev_set_drvdata(&pdev->dev, esai_priv); + + /* Reset ESAI unit */ + ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ERST); + if (ret) { + dev_err(&pdev->dev, "failed to reset ESAI: %d\n", ret); + return ret; + } + + /* + * We need to enable ESAI so as to access some of its registers. + * Otherwise, we would fail to dump regmap from user space. + */ + ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ESAIEN); + if (ret) { + dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret); + return ret; + } + + esai_priv->tx_mask = 0xFFFFFFFF; + esai_priv->rx_mask = 0xFFFFFFFF; + + /* Clear the TSMA, TSMB, RSMA, RSMB */ + regmap_write(esai_priv->regmap, REG_ESAI_TSMA, 0); + regmap_write(esai_priv->regmap, REG_ESAI_TSMB, 0); + regmap_write(esai_priv->regmap, REG_ESAI_RSMA, 0); + regmap_write(esai_priv->regmap, REG_ESAI_RSMB, 0); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_esai_component, + &fsl_esai_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); + return ret; + } + + ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + + return ret; +} + +static const struct of_device_id fsl_esai_dt_ids[] = { + { .compatible = "fsl,imx35-esai", }, + { .compatible = "fsl,vf610-esai", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); + +#ifdef CONFIG_PM_SLEEP +static int fsl_esai_suspend(struct device *dev) +{ + struct fsl_esai *esai = dev_get_drvdata(dev); + + regcache_cache_only(esai->regmap, true); + regcache_mark_dirty(esai->regmap); + + return 0; +} + +static int fsl_esai_resume(struct device *dev) +{ + struct fsl_esai *esai = dev_get_drvdata(dev); + int ret; + + regcache_cache_only(esai->regmap, false); + + /* FIFO reset for safety */ + regmap_update_bits(esai->regmap, REG_ESAI_TFCR, + ESAI_xFCR_xFR, ESAI_xFCR_xFR); + regmap_update_bits(esai->regmap, REG_ESAI_RFCR, + ESAI_xFCR_xFR, ESAI_xFCR_xFR); + + ret = regcache_sync(esai->regmap); + if (ret) + return ret; + + /* FIFO reset done */ + regmap_update_bits(esai->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR, 0); + regmap_update_bits(esai->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR, 0); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_esai_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fsl_esai_suspend, fsl_esai_resume) +}; + +static struct platform_driver fsl_esai_driver = { + .probe = fsl_esai_probe, + .driver = { + .name = "fsl-esai-dai", + .pm = &fsl_esai_pm_ops, + .of_match_table = fsl_esai_dt_ids, + }, +}; + +module_platform_driver(fsl_esai_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale ESAI CPU DAI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:fsl-esai-dai"); diff --git a/sound/soc/fsl/fsl_esai.h b/sound/soc/fsl/fsl_esai.h new file mode 100644 index 000000000..f873588d9 --- /dev/null +++ b/sound/soc/fsl/fsl_esai.h @@ -0,0 +1,351 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fsl_esai.h - ALSA ESAI interface for the Freescale i.MX SoC + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen + */ + +#ifndef _FSL_ESAI_DAI_H +#define _FSL_ESAI_DAI_H + +/* ESAI Register Map */ +#define REG_ESAI_ETDR 0x00 +#define REG_ESAI_ERDR 0x04 +#define REG_ESAI_ECR 0x08 +#define REG_ESAI_ESR 0x0C +#define REG_ESAI_TFCR 0x10 +#define REG_ESAI_TFSR 0x14 +#define REG_ESAI_RFCR 0x18 +#define REG_ESAI_RFSR 0x1C +#define REG_ESAI_xFCR(tx) (tx ? REG_ESAI_TFCR : REG_ESAI_RFCR) +#define REG_ESAI_xFSR(tx) (tx ? REG_ESAI_TFSR : REG_ESAI_RFSR) +#define REG_ESAI_TX0 0x80 +#define REG_ESAI_TX1 0x84 +#define REG_ESAI_TX2 0x88 +#define REG_ESAI_TX3 0x8C +#define REG_ESAI_TX4 0x90 +#define REG_ESAI_TX5 0x94 +#define REG_ESAI_TSR 0x98 +#define REG_ESAI_RX0 0xA0 +#define REG_ESAI_RX1 0xA4 +#define REG_ESAI_RX2 0xA8 +#define REG_ESAI_RX3 0xAC +#define REG_ESAI_SAISR 0xCC +#define REG_ESAI_SAICR 0xD0 +#define REG_ESAI_TCR 0xD4 +#define REG_ESAI_TCCR 0xD8 +#define REG_ESAI_RCR 0xDC +#define REG_ESAI_RCCR 0xE0 +#define REG_ESAI_xCR(tx) (tx ? REG_ESAI_TCR : REG_ESAI_RCR) +#define REG_ESAI_xCCR(tx) (tx ? REG_ESAI_TCCR : REG_ESAI_RCCR) +#define REG_ESAI_TSMA 0xE4 +#define REG_ESAI_TSMB 0xE8 +#define REG_ESAI_RSMA 0xEC +#define REG_ESAI_RSMB 0xF0 +#define REG_ESAI_xSMA(tx) (tx ? REG_ESAI_TSMA : REG_ESAI_RSMA) +#define REG_ESAI_xSMB(tx) (tx ? REG_ESAI_TSMB : REG_ESAI_RSMB) +#define REG_ESAI_PRRC 0xF8 +#define REG_ESAI_PCRC 0xFC + +/* ESAI Control Register -- REG_ESAI_ECR 0x8 */ +#define ESAI_ECR_ETI_SHIFT 19 +#define ESAI_ECR_ETI_MASK (1 << ESAI_ECR_ETI_SHIFT) +#define ESAI_ECR_ETI (1 << ESAI_ECR_ETI_SHIFT) +#define ESAI_ECR_ETO_SHIFT 18 +#define ESAI_ECR_ETO_MASK (1 << ESAI_ECR_ETO_SHIFT) +#define ESAI_ECR_ETO (1 << ESAI_ECR_ETO_SHIFT) +#define ESAI_ECR_ERI_SHIFT 17 +#define ESAI_ECR_ERI_MASK (1 << ESAI_ECR_ERI_SHIFT) +#define ESAI_ECR_ERI (1 << ESAI_ECR_ERI_SHIFT) +#define ESAI_ECR_ERO_SHIFT 16 +#define ESAI_ECR_ERO_MASK (1 << ESAI_ECR_ERO_SHIFT) +#define ESAI_ECR_ERO (1 << ESAI_ECR_ERO_SHIFT) +#define ESAI_ECR_ERST_SHIFT 1 +#define ESAI_ECR_ERST_MASK (1 << ESAI_ECR_ERST_SHIFT) +#define ESAI_ECR_ERST (1 << ESAI_ECR_ERST_SHIFT) +#define ESAI_ECR_ESAIEN_SHIFT 0 +#define ESAI_ECR_ESAIEN_MASK (1 << ESAI_ECR_ESAIEN_SHIFT) +#define ESAI_ECR_ESAIEN (1 << ESAI_ECR_ESAIEN_SHIFT) + +/* ESAI Status Register -- REG_ESAI_ESR 0xC */ +#define ESAI_ESR_TINIT_SHIFT 10 +#define ESAI_ESR_TINIT_MASK (1 << ESAI_ESR_TINIT_SHIFT) +#define ESAI_ESR_TINIT (1 << ESAI_ESR_TINIT_SHIFT) +#define ESAI_ESR_RFF_SHIFT 9 +#define ESAI_ESR_RFF_MASK (1 << ESAI_ESR_RFF_SHIFT) +#define ESAI_ESR_RFF (1 << ESAI_ESR_RFF_SHIFT) +#define ESAI_ESR_TFE_SHIFT 8 +#define ESAI_ESR_TFE_MASK (1 << ESAI_ESR_TFE_SHIFT) +#define ESAI_ESR_TFE (1 << ESAI_ESR_TFE_SHIFT) +#define ESAI_ESR_TLS_SHIFT 7 +#define ESAI_ESR_TLS_MASK (1 << ESAI_ESR_TLS_SHIFT) +#define ESAI_ESR_TLS (1 << ESAI_ESR_TLS_SHIFT) +#define ESAI_ESR_TDE_SHIFT 6 +#define ESAI_ESR_TDE_MASK (1 << ESAI_ESR_TDE_SHIFT) +#define ESAI_ESR_TDE (1 << ESAI_ESR_TDE_SHIFT) +#define ESAI_ESR_TED_SHIFT 5 +#define ESAI_ESR_TED_MASK (1 << ESAI_ESR_TED_SHIFT) +#define ESAI_ESR_TED (1 << ESAI_ESR_TED_SHIFT) +#define ESAI_ESR_TD_SHIFT 4 +#define ESAI_ESR_TD_MASK (1 << ESAI_ESR_TD_SHIFT) +#define ESAI_ESR_TD (1 << ESAI_ESR_TD_SHIFT) +#define ESAI_ESR_RLS_SHIFT 3 +#define ESAI_ESR_RLS_MASK (1 << ESAI_ESR_RLS_SHIFT) +#define ESAI_ESR_RLS (1 << ESAI_ESR_RLS_SHIFT) +#define ESAI_ESR_RDE_SHIFT 2 +#define ESAI_ESR_RDE_MASK (1 << ESAI_ESR_RDE_SHIFT) +#define ESAI_ESR_RDE (1 << ESAI_ESR_RDE_SHIFT) +#define ESAI_ESR_RED_SHIFT 1 +#define ESAI_ESR_RED_MASK (1 << ESAI_ESR_RED_SHIFT) +#define ESAI_ESR_RED (1 << ESAI_ESR_RED_SHIFT) +#define ESAI_ESR_RD_SHIFT 0 +#define ESAI_ESR_RD_MASK (1 << ESAI_ESR_RD_SHIFT) +#define ESAI_ESR_RD (1 << ESAI_ESR_RD_SHIFT) + +/* + * Transmit FIFO Configuration Register -- REG_ESAI_TFCR 0x10 + * Receive FIFO Configuration Register -- REG_ESAI_RFCR 0x18 + */ +#define ESAI_xFCR_TIEN_SHIFT 19 +#define ESAI_xFCR_TIEN_MASK (1 << ESAI_xFCR_TIEN_SHIFT) +#define ESAI_xFCR_TIEN (1 << ESAI_xFCR_TIEN_SHIFT) +#define ESAI_xFCR_REXT_SHIFT 19 +#define ESAI_xFCR_REXT_MASK (1 << ESAI_xFCR_REXT_SHIFT) +#define ESAI_xFCR_REXT (1 << ESAI_xFCR_REXT_SHIFT) +#define ESAI_xFCR_xWA_SHIFT 16 +#define ESAI_xFCR_xWA_WIDTH 3 +#define ESAI_xFCR_xWA_MASK (((1 << ESAI_xFCR_xWA_WIDTH) - 1) << ESAI_xFCR_xWA_SHIFT) +#define ESAI_xFCR_xWA(v) (((8 - ((v) >> 2)) << ESAI_xFCR_xWA_SHIFT) & ESAI_xFCR_xWA_MASK) +#define ESAI_xFCR_xFWM_SHIFT 8 +#define ESAI_xFCR_xFWM_WIDTH 8 +#define ESAI_xFCR_xFWM_MASK (((1 << ESAI_xFCR_xFWM_WIDTH) - 1) << ESAI_xFCR_xFWM_SHIFT) +#define ESAI_xFCR_xFWM(v) ((((v) - 1) << ESAI_xFCR_xFWM_SHIFT) & ESAI_xFCR_xFWM_MASK) +#define ESAI_xFCR_xE_SHIFT 2 +#define ESAI_xFCR_TE_WIDTH 6 +#define ESAI_xFCR_RE_WIDTH 4 +#define ESAI_xFCR_TE_MASK (((1 << ESAI_xFCR_TE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT) +#define ESAI_xFCR_RE_MASK (((1 << ESAI_xFCR_RE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT) +#define ESAI_xFCR_TE(x) ((ESAI_xFCR_TE_MASK >> (ESAI_xFCR_TE_WIDTH - x)) & ESAI_xFCR_TE_MASK) +#define ESAI_xFCR_RE(x) ((ESAI_xFCR_RE_MASK >> (ESAI_xFCR_RE_WIDTH - x)) & ESAI_xFCR_RE_MASK) +#define ESAI_xFCR_xFR_SHIFT 1 +#define ESAI_xFCR_xFR_MASK (1 << ESAI_xFCR_xFR_SHIFT) +#define ESAI_xFCR_xFR (1 << ESAI_xFCR_xFR_SHIFT) +#define ESAI_xFCR_xFEN_SHIFT 0 +#define ESAI_xFCR_xFEN_MASK (1 << ESAI_xFCR_xFEN_SHIFT) +#define ESAI_xFCR_xFEN (1 << ESAI_xFCR_xFEN_SHIFT) + +/* + * Transmit FIFO Status Register -- REG_ESAI_TFSR 0x14 + * Receive FIFO Status Register --REG_ESAI_RFSR 0x1C + */ +#define ESAI_xFSR_NTFO_SHIFT 12 +#define ESAI_xFSR_NRFI_SHIFT 12 +#define ESAI_xFSR_NTFI_SHIFT 8 +#define ESAI_xFSR_NRFO_SHIFT 8 +#define ESAI_xFSR_NTFx_WIDTH 3 +#define ESAI_xFSR_NRFx_WIDTH 2 +#define ESAI_xFSR_NTFO_MASK (((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFO_SHIFT) +#define ESAI_xFSR_NTFI_MASK (((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFI_SHIFT) +#define ESAI_xFSR_NRFO_MASK (((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFO_SHIFT) +#define ESAI_xFSR_NRFI_MASK (((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFI_SHIFT) +#define ESAI_xFSR_xFCNT_SHIFT 0 +#define ESAI_xFSR_xFCNT_WIDTH 8 +#define ESAI_xFSR_xFCNT_MASK (((1 << ESAI_xFSR_xFCNT_WIDTH) - 1) << ESAI_xFSR_xFCNT_SHIFT) + +/* ESAI Transmit Slot Register -- REG_ESAI_TSR 0x98 */ +#define ESAI_TSR_SHIFT 0 +#define ESAI_TSR_WIDTH 24 +#define ESAI_TSR_MASK (((1 << ESAI_TSR_WIDTH) - 1) << ESAI_TSR_SHIFT) + +/* Serial Audio Interface Status Register -- REG_ESAI_SAISR 0xCC */ +#define ESAI_SAISR_TODFE_SHIFT 17 +#define ESAI_SAISR_TODFE_MASK (1 << ESAI_SAISR_TODFE_SHIFT) +#define ESAI_SAISR_TODFE (1 << ESAI_SAISR_TODFE_SHIFT) +#define ESAI_SAISR_TEDE_SHIFT 16 +#define ESAI_SAISR_TEDE_MASK (1 << ESAI_SAISR_TEDE_SHIFT) +#define ESAI_SAISR_TEDE (1 << ESAI_SAISR_TEDE_SHIFT) +#define ESAI_SAISR_TDE_SHIFT 15 +#define ESAI_SAISR_TDE_MASK (1 << ESAI_SAISR_TDE_SHIFT) +#define ESAI_SAISR_TDE (1 << ESAI_SAISR_TDE_SHIFT) +#define ESAI_SAISR_TUE_SHIFT 14 +#define ESAI_SAISR_TUE_MASK (1 << ESAI_SAISR_TUE_SHIFT) +#define ESAI_SAISR_TUE (1 << ESAI_SAISR_TUE_SHIFT) +#define ESAI_SAISR_TFS_SHIFT 13 +#define ESAI_SAISR_TFS_MASK (1 << ESAI_SAISR_TFS_SHIFT) +#define ESAI_SAISR_TFS (1 << ESAI_SAISR_TFS_SHIFT) +#define ESAI_SAISR_RODF_SHIFT 10 +#define ESAI_SAISR_RODF_MASK (1 << ESAI_SAISR_RODF_SHIFT) +#define ESAI_SAISR_RODF (1 << ESAI_SAISR_RODF_SHIFT) +#define ESAI_SAISR_REDF_SHIFT 9 +#define ESAI_SAISR_REDF_MASK (1 << ESAI_SAISR_REDF_SHIFT) +#define ESAI_SAISR_REDF (1 << ESAI_SAISR_REDF_SHIFT) +#define ESAI_SAISR_RDF_SHIFT 8 +#define ESAI_SAISR_RDF_MASK (1 << ESAI_SAISR_RDF_SHIFT) +#define ESAI_SAISR_RDF (1 << ESAI_SAISR_RDF_SHIFT) +#define ESAI_SAISR_ROE_SHIFT 7 +#define ESAI_SAISR_ROE_MASK (1 << ESAI_SAISR_ROE_SHIFT) +#define ESAI_SAISR_ROE (1 << ESAI_SAISR_ROE_SHIFT) +#define ESAI_SAISR_RFS_SHIFT 6 +#define ESAI_SAISR_RFS_MASK (1 << ESAI_SAISR_RFS_SHIFT) +#define ESAI_SAISR_RFS (1 << ESAI_SAISR_RFS_SHIFT) +#define ESAI_SAISR_IF2_SHIFT 2 +#define ESAI_SAISR_IF2_MASK (1 << ESAI_SAISR_IF2_SHIFT) +#define ESAI_SAISR_IF2 (1 << ESAI_SAISR_IF2_SHIFT) +#define ESAI_SAISR_IF1_SHIFT 1 +#define ESAI_SAISR_IF1_MASK (1 << ESAI_SAISR_IF1_SHIFT) +#define ESAI_SAISR_IF1 (1 << ESAI_SAISR_IF1_SHIFT) +#define ESAI_SAISR_IF0_SHIFT 0 +#define ESAI_SAISR_IF0_MASK (1 << ESAI_SAISR_IF0_SHIFT) +#define ESAI_SAISR_IF0 (1 << ESAI_SAISR_IF0_SHIFT) + +/* Serial Audio Interface Control Register -- REG_ESAI_SAICR 0xD0 */ +#define ESAI_SAICR_ALC_SHIFT 8 +#define ESAI_SAICR_ALC_MASK (1 << ESAI_SAICR_ALC_SHIFT) +#define ESAI_SAICR_ALC (1 << ESAI_SAICR_ALC_SHIFT) +#define ESAI_SAICR_TEBE_SHIFT 7 +#define ESAI_SAICR_TEBE_MASK (1 << ESAI_SAICR_TEBE_SHIFT) +#define ESAI_SAICR_TEBE (1 << ESAI_SAICR_TEBE_SHIFT) +#define ESAI_SAICR_SYNC_SHIFT 6 +#define ESAI_SAICR_SYNC_MASK (1 << ESAI_SAICR_SYNC_SHIFT) +#define ESAI_SAICR_SYNC (1 << ESAI_SAICR_SYNC_SHIFT) +#define ESAI_SAICR_OF2_SHIFT 2 +#define ESAI_SAICR_OF2_MASK (1 << ESAI_SAICR_OF2_SHIFT) +#define ESAI_SAICR_OF2 (1 << ESAI_SAICR_OF2_SHIFT) +#define ESAI_SAICR_OF1_SHIFT 1 +#define ESAI_SAICR_OF1_MASK (1 << ESAI_SAICR_OF1_SHIFT) +#define ESAI_SAICR_OF1 (1 << ESAI_SAICR_OF1_SHIFT) +#define ESAI_SAICR_OF0_SHIFT 0 +#define ESAI_SAICR_OF0_MASK (1 << ESAI_SAICR_OF0_SHIFT) +#define ESAI_SAICR_OF0 (1 << ESAI_SAICR_OF0_SHIFT) + +/* + * Transmit Control Register -- REG_ESAI_TCR 0xD4 + * Receive Control Register -- REG_ESAI_RCR 0xDC + */ +#define ESAI_xCR_xLIE_SHIFT 23 +#define ESAI_xCR_xLIE_MASK (1 << ESAI_xCR_xLIE_SHIFT) +#define ESAI_xCR_xLIE (1 << ESAI_xCR_xLIE_SHIFT) +#define ESAI_xCR_xIE_SHIFT 22 +#define ESAI_xCR_xIE_MASK (1 << ESAI_xCR_xIE_SHIFT) +#define ESAI_xCR_xIE (1 << ESAI_xCR_xIE_SHIFT) +#define ESAI_xCR_xEDIE_SHIFT 21 +#define ESAI_xCR_xEDIE_MASK (1 << ESAI_xCR_xEDIE_SHIFT) +#define ESAI_xCR_xEDIE (1 << ESAI_xCR_xEDIE_SHIFT) +#define ESAI_xCR_xEIE_SHIFT 20 +#define ESAI_xCR_xEIE_MASK (1 << ESAI_xCR_xEIE_SHIFT) +#define ESAI_xCR_xEIE (1 << ESAI_xCR_xEIE_SHIFT) +#define ESAI_xCR_xPR_SHIFT 19 +#define ESAI_xCR_xPR_MASK (1 << ESAI_xCR_xPR_SHIFT) +#define ESAI_xCR_xPR (1 << ESAI_xCR_xPR_SHIFT) +#define ESAI_xCR_PADC_SHIFT 17 +#define ESAI_xCR_PADC_MASK (1 << ESAI_xCR_PADC_SHIFT) +#define ESAI_xCR_PADC (1 << ESAI_xCR_PADC_SHIFT) +#define ESAI_xCR_xFSR_SHIFT 16 +#define ESAI_xCR_xFSR_MASK (1 << ESAI_xCR_xFSR_SHIFT) +#define ESAI_xCR_xFSR (1 << ESAI_xCR_xFSR_SHIFT) +#define ESAI_xCR_xFSL_SHIFT 15 +#define ESAI_xCR_xFSL_MASK (1 << ESAI_xCR_xFSL_SHIFT) +#define ESAI_xCR_xFSL (1 << ESAI_xCR_xFSL_SHIFT) +#define ESAI_xCR_xSWS_SHIFT 10 +#define ESAI_xCR_xSWS_WIDTH 5 +#define ESAI_xCR_xSWS_MASK (((1 << ESAI_xCR_xSWS_WIDTH) - 1) << ESAI_xCR_xSWS_SHIFT) +#define ESAI_xCR_xSWS(s, w) ((w < 24 ? (s - w + ((w - 8) >> 2)) : (s < 32 ? 0x1e : 0x1f)) << ESAI_xCR_xSWS_SHIFT) +#define ESAI_xCR_xMOD_SHIFT 8 +#define ESAI_xCR_xMOD_WIDTH 2 +#define ESAI_xCR_xMOD_MASK (((1 << ESAI_xCR_xMOD_WIDTH) - 1) << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xMOD_ONDEMAND (0x1 << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xMOD_NETWORK (0x1 << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xMOD_AC97 (0x3 << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xWA_SHIFT 7 +#define ESAI_xCR_xWA_MASK (1 << ESAI_xCR_xWA_SHIFT) +#define ESAI_xCR_xWA (1 << ESAI_xCR_xWA_SHIFT) +#define ESAI_xCR_xSHFD_SHIFT 6 +#define ESAI_xCR_xSHFD_MASK (1 << ESAI_xCR_xSHFD_SHIFT) +#define ESAI_xCR_xSHFD (1 << ESAI_xCR_xSHFD_SHIFT) +#define ESAI_xCR_xE_SHIFT 0 +#define ESAI_xCR_TE_WIDTH 6 +#define ESAI_xCR_RE_WIDTH 4 +#define ESAI_xCR_TE_MASK (((1 << ESAI_xCR_TE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT) +#define ESAI_xCR_RE_MASK (((1 << ESAI_xCR_RE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT) +#define ESAI_xCR_TE(x) ((ESAI_xCR_TE_MASK >> (ESAI_xCR_TE_WIDTH - x)) & ESAI_xCR_TE_MASK) +#define ESAI_xCR_RE(x) ((ESAI_xCR_RE_MASK >> (ESAI_xCR_RE_WIDTH - x)) & ESAI_xCR_RE_MASK) + +/* + * Transmit Clock Control Register -- REG_ESAI_TCCR 0xD8 + * Receive Clock Control Register -- REG_ESAI_RCCR 0xE0 + */ +#define ESAI_xCCR_xHCKD_SHIFT 23 +#define ESAI_xCCR_xHCKD_MASK (1 << ESAI_xCCR_xHCKD_SHIFT) +#define ESAI_xCCR_xHCKD (1 << ESAI_xCCR_xHCKD_SHIFT) +#define ESAI_xCCR_xFSD_SHIFT 22 +#define ESAI_xCCR_xFSD_MASK (1 << ESAI_xCCR_xFSD_SHIFT) +#define ESAI_xCCR_xFSD (1 << ESAI_xCCR_xFSD_SHIFT) +#define ESAI_xCCR_xCKD_SHIFT 21 +#define ESAI_xCCR_xCKD_MASK (1 << ESAI_xCCR_xCKD_SHIFT) +#define ESAI_xCCR_xCKD (1 << ESAI_xCCR_xCKD_SHIFT) +#define ESAI_xCCR_xHCKP_SHIFT 20 +#define ESAI_xCCR_xHCKP_MASK (1 << ESAI_xCCR_xHCKP_SHIFT) +#define ESAI_xCCR_xHCKP (1 << ESAI_xCCR_xHCKP_SHIFT) +#define ESAI_xCCR_xFSP_SHIFT 19 +#define ESAI_xCCR_xFSP_MASK (1 << ESAI_xCCR_xFSP_SHIFT) +#define ESAI_xCCR_xFSP (1 << ESAI_xCCR_xFSP_SHIFT) +#define ESAI_xCCR_xCKP_SHIFT 18 +#define ESAI_xCCR_xCKP_MASK (1 << ESAI_xCCR_xCKP_SHIFT) +#define ESAI_xCCR_xCKP (1 << ESAI_xCCR_xCKP_SHIFT) +#define ESAI_xCCR_xFP_SHIFT 14 +#define ESAI_xCCR_xFP_WIDTH 4 +#define ESAI_xCCR_xFP_MASK (((1 << ESAI_xCCR_xFP_WIDTH) - 1) << ESAI_xCCR_xFP_SHIFT) +#define ESAI_xCCR_xFP(v) ((((v) - 1) << ESAI_xCCR_xFP_SHIFT) & ESAI_xCCR_xFP_MASK) +#define ESAI_xCCR_xDC_SHIFT 9 +#define ESAI_xCCR_xDC_WIDTH 5 +#define ESAI_xCCR_xDC_MASK (((1 << ESAI_xCCR_xDC_WIDTH) - 1) << ESAI_xCCR_xDC_SHIFT) +#define ESAI_xCCR_xDC(v) ((((v) - 1) << ESAI_xCCR_xDC_SHIFT) & ESAI_xCCR_xDC_MASK) +#define ESAI_xCCR_xPSR_SHIFT 8 +#define ESAI_xCCR_xPSR_MASK (1 << ESAI_xCCR_xPSR_SHIFT) +#define ESAI_xCCR_xPSR_BYPASS (1 << ESAI_xCCR_xPSR_SHIFT) +#define ESAI_xCCR_xPSR_DIV8 (0 << ESAI_xCCR_xPSR_SHIFT) +#define ESAI_xCCR_xPM_SHIFT 0 +#define ESAI_xCCR_xPM_WIDTH 8 +#define ESAI_xCCR_xPM_MASK (((1 << ESAI_xCCR_xPM_WIDTH) - 1) << ESAI_xCCR_xPM_SHIFT) +#define ESAI_xCCR_xPM(v) ((((v) - 1) << ESAI_xCCR_xPM_SHIFT) & ESAI_xCCR_xPM_MASK) + +/* Transmit Slot Mask Register A/B -- REG_ESAI_TSMA/B 0xE4 ~ 0xF0 */ +#define ESAI_xSMA_xS_SHIFT 0 +#define ESAI_xSMA_xS_WIDTH 16 +#define ESAI_xSMA_xS_MASK (((1 << ESAI_xSMA_xS_WIDTH) - 1) << ESAI_xSMA_xS_SHIFT) +#define ESAI_xSMA_xS(v) ((v) & ESAI_xSMA_xS_MASK) +#define ESAI_xSMB_xS_SHIFT 0 +#define ESAI_xSMB_xS_WIDTH 16 +#define ESAI_xSMB_xS_MASK (((1 << ESAI_xSMB_xS_WIDTH) - 1) << ESAI_xSMB_xS_SHIFT) +#define ESAI_xSMB_xS(v) (((v) >> ESAI_xSMA_xS_WIDTH) & ESAI_xSMB_xS_MASK) + +/* Port C Direction Register -- REG_ESAI_PRRC 0xF8 */ +#define ESAI_PRRC_PDC_SHIFT 0 +#define ESAI_PRRC_PDC_WIDTH 12 +#define ESAI_PRRC_PDC_MASK (((1 << ESAI_PRRC_PDC_WIDTH) - 1) << ESAI_PRRC_PDC_SHIFT) +#define ESAI_PRRC_PDC(v) ((v) & ESAI_PRRC_PDC_MASK) + +/* Port C Control Register -- REG_ESAI_PCRC 0xFC */ +#define ESAI_PCRC_PC_SHIFT 0 +#define ESAI_PCRC_PC_WIDTH 12 +#define ESAI_PCRC_PC_MASK (((1 << ESAI_PCRC_PC_WIDTH) - 1) << ESAI_PCRC_PC_SHIFT) +#define ESAI_PCRC_PC(v) ((v) & ESAI_PCRC_PC_MASK) + +#define ESAI_GPIO 0xfff + +/* ESAI clock source */ +#define ESAI_HCKT_FSYS 0 +#define ESAI_HCKT_EXTAL 1 +#define ESAI_HCKR_FSYS 2 +#define ESAI_HCKR_EXTAL 3 + +/* ESAI clock divider */ +#define ESAI_TX_DIV_PSR 0 +#define ESAI_TX_DIV_PM 1 +#define ESAI_TX_DIV_FP 2 +#define ESAI_RX_DIV_PSR 3 +#define ESAI_RX_DIV_PM 4 +#define ESAI_RX_DIV_FP 5 +#endif /* _FSL_ESAI_DAI_H */ diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c new file mode 100644 index 000000000..bfc5b21d0 --- /dev/null +++ b/sound/soc/fsl/fsl_sai.c @@ -0,0 +1,965 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Freescale ALSA SoC Digital Audio Interface (SAI) driver. +// +// Copyright 2012-2015 Freescale Semiconductor, Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_sai.h" +#include "imx-pcm.h" + +#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\ + FSL_SAI_CSR_FEIE) + +static const unsigned int fsl_sai_rates[] = { + 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 64000, + 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = { + .count = ARRAY_SIZE(fsl_sai_rates), + .list = fsl_sai_rates, +}; + +static irqreturn_t fsl_sai_isr(int irq, void *devid) +{ + struct fsl_sai *sai = (struct fsl_sai *)devid; + struct device *dev = &sai->pdev->dev; + u32 flags, xcsr, mask; + bool irq_none = true; + + /* + * Both IRQ status bits and IRQ mask bits are in the xCSR but + * different shifts. And we here create a mask only for those + * IRQs that we activated. + */ + mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT; + + /* Tx IRQ */ + regmap_read(sai->regmap, FSL_SAI_TCSR, &xcsr); + flags = xcsr & mask; + + if (flags) + irq_none = false; + else + goto irq_rx; + + if (flags & FSL_SAI_CSR_WSF) + dev_dbg(dev, "isr: Start of Tx word detected\n"); + + if (flags & FSL_SAI_CSR_SEF) + dev_warn(dev, "isr: Tx Frame sync error detected\n"); + + if (flags & FSL_SAI_CSR_FEF) { + dev_warn(dev, "isr: Transmit underrun detected\n"); + /* FIFO reset for safety */ + xcsr |= FSL_SAI_CSR_FR; + } + + if (flags & FSL_SAI_CSR_FWF) + dev_dbg(dev, "isr: Enabled transmit FIFO is empty\n"); + + if (flags & FSL_SAI_CSR_FRF) + dev_dbg(dev, "isr: Transmit FIFO watermark has been reached\n"); + + flags &= FSL_SAI_CSR_xF_W_MASK; + xcsr &= ~FSL_SAI_CSR_xF_MASK; + + if (flags) + regmap_write(sai->regmap, FSL_SAI_TCSR, flags | xcsr); + +irq_rx: + /* Rx IRQ */ + regmap_read(sai->regmap, FSL_SAI_RCSR, &xcsr); + flags = xcsr & mask; + + if (flags) + irq_none = false; + else + goto out; + + if (flags & FSL_SAI_CSR_WSF) + dev_dbg(dev, "isr: Start of Rx word detected\n"); + + if (flags & FSL_SAI_CSR_SEF) + dev_warn(dev, "isr: Rx Frame sync error detected\n"); + + if (flags & FSL_SAI_CSR_FEF) { + dev_warn(dev, "isr: Receive overflow detected\n"); + /* FIFO reset for safety */ + xcsr |= FSL_SAI_CSR_FR; + } + + if (flags & FSL_SAI_CSR_FWF) + dev_dbg(dev, "isr: Enabled receive FIFO is full\n"); + + if (flags & FSL_SAI_CSR_FRF) + dev_dbg(dev, "isr: Receive FIFO watermark has been reached\n"); + + flags &= FSL_SAI_CSR_xF_W_MASK; + xcsr &= ~FSL_SAI_CSR_xF_MASK; + + if (flags) + regmap_write(sai->regmap, FSL_SAI_RCSR, flags | xcsr); + +out: + if (irq_none) + return IRQ_NONE; + else + return IRQ_HANDLED; +} + +static int fsl_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + + sai->slots = slots; + sai->slot_width = slot_width; + + return 0; +} + +static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int fsl_dir) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = fsl_dir == FSL_FMT_TRANSMITTER; + u32 val_cr2 = 0; + + switch (clk_id) { + case FSL_SAI_CLK_BUS: + val_cr2 |= FSL_SAI_CR2_MSEL_BUS; + break; + case FSL_SAI_CLK_MAST1: + val_cr2 |= FSL_SAI_CR2_MSEL_MCLK1; + break; + case FSL_SAI_CLK_MAST2: + val_cr2 |= FSL_SAI_CR2_MSEL_MCLK2; + break; + case FSL_SAI_CLK_MAST3: + val_cr2 |= FSL_SAI_CR2_MSEL_MCLK3; + break; + default: + return -EINVAL; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx), + FSL_SAI_CR2_MSEL_MASK, val_cr2); + + return 0; +} + +static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + int ret; + + if (dir == SND_SOC_CLOCK_IN) + return 0; + + ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, + FSL_FMT_TRANSMITTER); + if (ret) { + dev_err(cpu_dai->dev, "Cannot set tx sysclk: %d\n", ret); + return ret; + } + + ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, + FSL_FMT_RECEIVER); + if (ret) + dev_err(cpu_dai->dev, "Cannot set rx sysclk: %d\n", ret); + + return ret; +} + +static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, + unsigned int fmt, int fsl_dir) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = fsl_dir == FSL_FMT_TRANSMITTER; + u32 val_cr2 = 0, val_cr4 = 0; + + if (!sai->is_lsb_first) + val_cr4 |= FSL_SAI_CR4_MF; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* + * Frame low, 1clk before data, one word length for frame sync, + * frame sync starts one serial clock cycle earlier, + * that is, together with the last bit of the previous + * data word. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + val_cr4 |= FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* + * Frame high, one word length for frame sync, + * frame sync asserts with the first bit of the frame. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + break; + case SND_SOC_DAIFMT_DSP_A: + /* + * Frame high, 1clk before data, one bit for frame sync, + * frame sync starts one serial clock cycle earlier, + * that is, together with the last bit of the previous + * data word. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + val_cr4 |= FSL_SAI_CR4_FSE; + sai->is_dsp_mode = true; + break; + case SND_SOC_DAIFMT_DSP_B: + /* + * Frame high, one bit for frame sync, + * frame sync asserts with the first bit of the frame. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + sai->is_dsp_mode = true; + break; + case SND_SOC_DAIFMT_RIGHT_J: + /* To be done */ + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + val_cr2 ^= FSL_SAI_CR2_BCP; + val_cr4 ^= FSL_SAI_CR4_FSP; + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + val_cr2 ^= FSL_SAI_CR2_BCP; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + val_cr4 ^= FSL_SAI_CR4_FSP; + break; + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + break; + default: + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val_cr2 |= FSL_SAI_CR2_BCD_MSTR; + val_cr4 |= FSL_SAI_CR4_FSD_MSTR; + sai->is_slave_mode = false; + break; + case SND_SOC_DAIFMT_CBM_CFM: + sai->is_slave_mode = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + val_cr2 |= FSL_SAI_CR2_BCD_MSTR; + sai->is_slave_mode = false; + break; + case SND_SOC_DAIFMT_CBM_CFS: + val_cr4 |= FSL_SAI_CR4_FSD_MSTR; + sai->is_slave_mode = true; + break; + default: + return -EINVAL; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx), + FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2); + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx), + FSL_SAI_CR4_MF | FSL_SAI_CR4_FSE | + FSL_SAI_CR4_FSP | FSL_SAI_CR4_FSD_MSTR, val_cr4); + + return 0; +} + +static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + int ret; + + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER); + if (ret) { + dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret); + return ret; + } + + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER); + if (ret) + dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret); + + return ret; +} + +static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); + unsigned long clk_rate; + u32 savediv = 0, ratio, savesub = freq; + u32 id; + int ret = 0; + + /* Don't apply to slave mode */ + if (sai->is_slave_mode) + return 0; + + for (id = 0; id < FSL_SAI_MCLK_MAX; id++) { + clk_rate = clk_get_rate(sai->mclk_clk[id]); + if (!clk_rate) + continue; + + ratio = clk_rate / freq; + + ret = clk_rate - ratio * freq; + + /* + * Drop the source that can not be + * divided into the required rate. + */ + if (ret != 0 && clk_rate / ret < 1000) + continue; + + dev_dbg(dai->dev, + "ratio %d for freq %dHz based on clock %ldHz\n", + ratio, freq, clk_rate); + + if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512) + ratio /= 2; + else + continue; + + if (ret < savesub) { + savediv = ratio; + sai->mclk_id[tx] = id; + savesub = ret; + } + + if (ret == 0) + break; + } + + if (savediv == 0) { + dev_err(dai->dev, "failed to derive required %cx rate: %d\n", + tx ? 'T' : 'R', freq); + return -EINVAL; + } + + /* + * 1) For Asynchronous mode, we must set RCR2 register for capture, and + * set TCR2 register for playback. + * 2) For Tx sync with Rx clock, we must set RCR2 register for playback + * and capture. + * 3) For Rx sync with Tx clock, we must set TCR2 register for playback + * and capture. + * 4) For Tx and Rx are both Synchronous with another SAI, we just + * ignore it. + */ + if ((sai->synchronous[TX] && !sai->synchronous[RX]) || + (!tx && !sai->synchronous[RX])) { + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, + FSL_SAI_CR2_MSEL_MASK, + FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, + FSL_SAI_CR2_DIV_MASK, savediv - 1); + } else if ((sai->synchronous[RX] && !sai->synchronous[TX]) || + (tx && !sai->synchronous[TX])) { + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, + FSL_SAI_CR2_MSEL_MASK, + FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, + FSL_SAI_CR2_DIV_MASK, savediv - 1); + } + + dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n", + sai->mclk_id[tx], savediv, savesub); + + return 0; +} + +static int fsl_sai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int channels = params_channels(params); + u32 word_width = params_width(params); + u32 val_cr4 = 0, val_cr5 = 0; + u32 slots = (channels == 1) ? 2 : channels; + u32 slot_width = word_width; + int ret; + + if (sai->slots) + slots = sai->slots; + + if (sai->slot_width) + slot_width = sai->slot_width; + + if (!sai->is_slave_mode) { + ret = fsl_sai_set_bclk(cpu_dai, tx, + slots * slot_width * params_rate(params)); + if (ret) + return ret; + + /* Do not enable the clock if it is already enabled */ + if (!(sai->mclk_streams & BIT(substream->stream))) { + ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[tx]]); + if (ret) + return ret; + + sai->mclk_streams |= BIT(substream->stream); + } + } + + if (!sai->is_dsp_mode) + val_cr4 |= FSL_SAI_CR4_SYWD(slot_width); + + val_cr5 |= FSL_SAI_CR5_WNW(slot_width); + val_cr5 |= FSL_SAI_CR5_W0W(slot_width); + + if (sai->is_lsb_first) + val_cr5 |= FSL_SAI_CR5_FBT(0); + else + val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1); + + val_cr4 |= FSL_SAI_CR4_FRSZ(slots); + + /* + * For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will + * generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4), + * RCR5(TCR5) and RMR(TMR) for playback(capture), or there will be sync + * error. + */ + + if (!sai->is_slave_mode) { + if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) { + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + val_cr4); + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, + FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | + FSL_SAI_CR5_FBT_MASK, val_cr5); + regmap_write(sai->regmap, FSL_SAI_TMR, + ~0UL - ((1 << channels) - 1)); + } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) { + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + val_cr4); + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, + FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | + FSL_SAI_CR5_FBT_MASK, val_cr5); + regmap_write(sai->regmap, FSL_SAI_RMR, + ~0UL - ((1 << channels) - 1)); + } + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + val_cr4); + regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx), + FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | + FSL_SAI_CR5_FBT_MASK, val_cr5); + regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1)); + + return 0; +} + +static int fsl_sai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + if (!sai->is_slave_mode && + sai->mclk_streams & BIT(substream->stream)) { + clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]); + sai->mclk_streams &= ~BIT(substream->stream); + } + + return 0; +} + + +static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 xcsr, count = 100; + + /* + * Asynchronous mode: Clear SYNC for both Tx and Rx. + * Rx sync with Tx clocks: Clear SYNC for Tx, set it for Rx. + * Tx sync with Rx clocks: Clear SYNC for Rx, set it for Tx. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC, + sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0); + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC, + sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0); + + /* + * It is recommended that the transmitter is the last enabled + * and the first disabled. + */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE); + + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_FRDE, 0); + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_xIE_MASK, 0); + + /* Check if the opposite FRDE is also disabled */ + regmap_read(sai->regmap, FSL_SAI_xCSR(!tx), &xcsr); + if (!(xcsr & FSL_SAI_CSR_FRDE)) { + /* Disable both directions and reset their FIFOs */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_TERE, 0); + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_TERE, 0); + + /* TERE will remain set till the end of current frame */ + do { + udelay(10); + regmap_read(sai->regmap, FSL_SAI_xCSR(tx), &xcsr); + } while (--count && xcsr & FSL_SAI_CSR_TERE); + + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); + + /* + * For sai master mode, after several open/close sai, + * there will be no frame clock, and can't recover + * anymore. Add software reset to fix this issue. + * This is a hardware bug, and will be fix in the + * next sai version. + */ + if (!sai->is_slave_mode) { + /* Software Reset for both Tx and Rx */ + regmap_write(sai->regmap, + FSL_SAI_TCSR, FSL_SAI_CSR_SR); + regmap_write(sai->regmap, + FSL_SAI_RCSR, FSL_SAI_CSR_SR); + /* Clear SR bit to finish the reset */ + regmap_write(sai->regmap, FSL_SAI_TCSR, 0); + regmap_write(sai->regmap, FSL_SAI_RCSR, 0); + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = &sai->pdev->dev; + int ret; + + ret = clk_prepare_enable(sai->bus_clk); + if (ret) { + dev_err(dev, "failed to enable bus clock: %d\n", ret); + return ret; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, + FSL_SAI_CR3_TRCE); + + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &fsl_sai_rate_constraints); + + return ret; +} + +static void fsl_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, 0); + + clk_disable_unprepare(sai->bus_clk); +} + +static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { + .set_sysclk = fsl_sai_set_dai_sysclk, + .set_fmt = fsl_sai_set_dai_fmt, + .set_tdm_slot = fsl_sai_set_dai_tdm_slot, + .hw_params = fsl_sai_hw_params, + .hw_free = fsl_sai_hw_free, + .trigger = fsl_sai_trigger, + .startup = fsl_sai_startup, + .shutdown = fsl_sai_shutdown, +}; + +static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + + /* Software Reset for both Tx and Rx */ + regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR); + regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR); + /* Clear SR bit to finish the reset */ + regmap_write(sai->regmap, FSL_SAI_TCSR, 0); + regmap_write(sai->regmap, FSL_SAI_RCSR, 0); + + regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK, + FSL_SAI_MAXBURST_TX * 2); + regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK, + FSL_SAI_MAXBURST_RX - 1); + + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, + &sai->dma_params_rx); + + snd_soc_dai_set_drvdata(cpu_dai, sai); + + return 0; +} + +static struct snd_soc_dai_driver fsl_sai_dai = { + .probe = fsl_sai_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_SAI_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_SAI_FORMATS, + }, + .ops = &fsl_sai_pcm_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_component = { + .name = "fsl-sai", +}; + +static struct reg_default fsl_sai_reg_defaults[] = { + {FSL_SAI_TCR1, 0}, + {FSL_SAI_TCR2, 0}, + {FSL_SAI_TCR3, 0}, + {FSL_SAI_TCR4, 0}, + {FSL_SAI_TCR5, 0}, + {FSL_SAI_TDR, 0}, + {FSL_SAI_TMR, 0}, + {FSL_SAI_RCR1, 0}, + {FSL_SAI_RCR2, 0}, + {FSL_SAI_RCR3, 0}, + {FSL_SAI_RCR4, 0}, + {FSL_SAI_RCR5, 0}, + {FSL_SAI_RMR, 0}, +}; + +static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TFR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RDR: + case FSL_SAI_RFR: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_RCSR: + case FSL_SAI_TFR: + case FSL_SAI_RFR: + case FSL_SAI_RDR: + return true; + default: + return false; + } +} + +static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TDR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_sai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_RMR, + .reg_defaults = fsl_sai_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_sai_reg_defaults), + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int fsl_sai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_sai *sai; + struct regmap *gpr; + struct resource *res; + void __iomem *base; + char tmp[8]; + int irq, ret, i; + int index; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + sai->pdev = pdev; + + if (of_device_is_compatible(np, "fsl,imx6sx-sai") || + of_device_is_compatible(np, "fsl,imx6ul-sai")) + sai->sai_on_imx = true; + + sai->is_lsb_first = of_property_read_bool(np, "lsb-first"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "bus", base, &fsl_sai_regmap_config); + + /* Compatible with old DTB cases */ + if (IS_ERR(sai->regmap)) + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "sai", base, &fsl_sai_regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(sai->regmap); + } + + /* No error out for old DTB cases but only mark the clock NULL */ + sai->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(sai->bus_clk)) { + dev_err(&pdev->dev, "failed to get bus clock: %ld\n", + PTR_ERR(sai->bus_clk)); + sai->bus_clk = NULL; + } + + sai->mclk_clk[0] = sai->bus_clk; + for (i = 1; i < FSL_SAI_MCLK_MAX; i++) { + sprintf(tmp, "mclk%d", i); + sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(sai->mclk_clk[i])) { + dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", + i + 1, PTR_ERR(sai->mclk_clk[i])); + sai->mclk_clk[i] = NULL; + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u\n", irq); + return ret; + } + + /* Sync Tx with Rx as default by following old DT binding */ + sai->synchronous[RX] = true; + sai->synchronous[TX] = false; + fsl_sai_dai.symmetric_rates = 1; + fsl_sai_dai.symmetric_channels = 1; + fsl_sai_dai.symmetric_samplebits = 1; + + if (of_find_property(np, "fsl,sai-synchronous-rx", NULL) && + of_find_property(np, "fsl,sai-asynchronous", NULL)) { + /* error out if both synchronous and asynchronous are present */ + dev_err(&pdev->dev, "invalid binding for synchronous mode\n"); + return -EINVAL; + } + + if (of_find_property(np, "fsl,sai-synchronous-rx", NULL)) { + /* Sync Rx with Tx */ + sai->synchronous[RX] = false; + sai->synchronous[TX] = true; + } else if (of_find_property(np, "fsl,sai-asynchronous", NULL)) { + /* Discard all settings for asynchronous mode */ + sai->synchronous[RX] = false; + sai->synchronous[TX] = false; + fsl_sai_dai.symmetric_rates = 0; + fsl_sai_dai.symmetric_channels = 0; + fsl_sai_dai.symmetric_samplebits = 0; + } + + if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) && + of_device_is_compatible(np, "fsl,imx6ul-sai")) { + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr"); + if (IS_ERR(gpr)) { + dev_err(&pdev->dev, "cannot find iomuxc registers\n"); + return PTR_ERR(gpr); + } + + index = of_alias_get_id(np, "sai"); + if (index < 0) + return index; + + regmap_update_bits(gpr, IOMUXC_GPR1, MCLK_DIR(index), + MCLK_DIR(index)); + } + + sai->dma_params_rx.addr = res->start + FSL_SAI_RDR; + sai->dma_params_tx.addr = res->start + FSL_SAI_TDR; + sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX; + sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX; + + platform_set_drvdata(pdev, sai); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, + &fsl_sai_dai, 1); + if (ret) + return ret; + + if (sai->sai_on_imx) + return imx_pcm_dma_init(pdev, IMX_SAI_DMABUF_SIZE); + else + return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +} + +static const struct of_device_id fsl_sai_ids[] = { + { .compatible = "fsl,vf610-sai", }, + { .compatible = "fsl,imx6sx-sai", }, + { .compatible = "fsl,imx6ul-sai", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_sai_ids); + +#ifdef CONFIG_PM_SLEEP +static int fsl_sai_suspend(struct device *dev) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + + regcache_cache_only(sai->regmap, true); + regcache_mark_dirty(sai->regmap); + + return 0; +} + +static int fsl_sai_resume(struct device *dev) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + + regcache_cache_only(sai->regmap, false); + regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR); + regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR); + usleep_range(1000, 2000); + regmap_write(sai->regmap, FSL_SAI_TCSR, 0); + regmap_write(sai->regmap, FSL_SAI_RCSR, 0); + return regcache_sync(sai->regmap); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_sai_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fsl_sai_suspend, fsl_sai_resume) +}; + +static struct platform_driver fsl_sai_driver = { + .probe = fsl_sai_probe, + .driver = { + .name = "fsl-sai", + .pm = &fsl_sai_pm_ops, + .of_match_table = fsl_sai_ids, + }, +}; +module_platform_driver(fsl_sai_driver); + +MODULE_DESCRIPTION("Freescale Soc SAI Interface"); +MODULE_AUTHOR("Xiubo Li, "); +MODULE_ALIAS("platform:fsl-sai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h new file mode 100644 index 000000000..24cb156bf --- /dev/null +++ b/sound/soc/fsl/fsl_sai.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2012-2013 Freescale Semiconductor, Inc. + */ + +#ifndef __FSL_SAI_H +#define __FSL_SAI_H + +#include + +#define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +/* SAI Register Map Register */ +#define FSL_SAI_TCSR 0x00 /* SAI Transmit Control */ +#define FSL_SAI_TCR1 0x04 /* SAI Transmit Configuration 1 */ +#define FSL_SAI_TCR2 0x08 /* SAI Transmit Configuration 2 */ +#define FSL_SAI_TCR3 0x0c /* SAI Transmit Configuration 3 */ +#define FSL_SAI_TCR4 0x10 /* SAI Transmit Configuration 4 */ +#define FSL_SAI_TCR5 0x14 /* SAI Transmit Configuration 5 */ +#define FSL_SAI_TDR 0x20 /* SAI Transmit Data */ +#define FSL_SAI_TFR 0x40 /* SAI Transmit FIFO */ +#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */ +#define FSL_SAI_RCSR 0x80 /* SAI Receive Control */ +#define FSL_SAI_RCR1 0x84 /* SAI Receive Configuration 1 */ +#define FSL_SAI_RCR2 0x88 /* SAI Receive Configuration 2 */ +#define FSL_SAI_RCR3 0x8c /* SAI Receive Configuration 3 */ +#define FSL_SAI_RCR4 0x90 /* SAI Receive Configuration 4 */ +#define FSL_SAI_RCR5 0x94 /* SAI Receive Configuration 5 */ +#define FSL_SAI_RDR 0xa0 /* SAI Receive Data */ +#define FSL_SAI_RFR 0xc0 /* SAI Receive FIFO */ +#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */ + +#define FSL_SAI_xCSR(tx) (tx ? FSL_SAI_TCSR : FSL_SAI_RCSR) +#define FSL_SAI_xCR1(tx) (tx ? FSL_SAI_TCR1 : FSL_SAI_RCR1) +#define FSL_SAI_xCR2(tx) (tx ? FSL_SAI_TCR2 : FSL_SAI_RCR2) +#define FSL_SAI_xCR3(tx) (tx ? FSL_SAI_TCR3 : FSL_SAI_RCR3) +#define FSL_SAI_xCR4(tx) (tx ? FSL_SAI_TCR4 : FSL_SAI_RCR4) +#define FSL_SAI_xCR5(tx) (tx ? FSL_SAI_TCR5 : FSL_SAI_RCR5) +#define FSL_SAI_xDR(tx) (tx ? FSL_SAI_TDR : FSL_SAI_RDR) +#define FSL_SAI_xFR(tx) (tx ? FSL_SAI_TFR : FSL_SAI_RFR) +#define FSL_SAI_xMR(tx) (tx ? FSL_SAI_TMR : FSL_SAI_RMR) + +/* SAI Transmit/Receive Control Register */ +#define FSL_SAI_CSR_TERE BIT(31) +#define FSL_SAI_CSR_FR BIT(25) +#define FSL_SAI_CSR_SR BIT(24) +#define FSL_SAI_CSR_xF_SHIFT 16 +#define FSL_SAI_CSR_xF_W_SHIFT 18 +#define FSL_SAI_CSR_xF_MASK (0x1f << FSL_SAI_CSR_xF_SHIFT) +#define FSL_SAI_CSR_xF_W_MASK (0x7 << FSL_SAI_CSR_xF_W_SHIFT) +#define FSL_SAI_CSR_WSF BIT(20) +#define FSL_SAI_CSR_SEF BIT(19) +#define FSL_SAI_CSR_FEF BIT(18) +#define FSL_SAI_CSR_FWF BIT(17) +#define FSL_SAI_CSR_FRF BIT(16) +#define FSL_SAI_CSR_xIE_SHIFT 8 +#define FSL_SAI_CSR_xIE_MASK (0x1f << FSL_SAI_CSR_xIE_SHIFT) +#define FSL_SAI_CSR_WSIE BIT(12) +#define FSL_SAI_CSR_SEIE BIT(11) +#define FSL_SAI_CSR_FEIE BIT(10) +#define FSL_SAI_CSR_FWIE BIT(9) +#define FSL_SAI_CSR_FRIE BIT(8) +#define FSL_SAI_CSR_FRDE BIT(0) + +/* SAI Transmit and Receive Configuration 1 Register */ +#define FSL_SAI_CR1_RFW_MASK 0x1f + +/* SAI Transmit and Receive Configuration 2 Register */ +#define FSL_SAI_CR2_SYNC BIT(30) +#define FSL_SAI_CR2_MSEL_MASK (0x3 << 26) +#define FSL_SAI_CR2_MSEL_BUS 0 +#define FSL_SAI_CR2_MSEL_MCLK1 BIT(26) +#define FSL_SAI_CR2_MSEL_MCLK2 BIT(27) +#define FSL_SAI_CR2_MSEL_MCLK3 (BIT(26) | BIT(27)) +#define FSL_SAI_CR2_MSEL(ID) ((ID) << 26) +#define FSL_SAI_CR2_BCP BIT(25) +#define FSL_SAI_CR2_BCD_MSTR BIT(24) +#define FSL_SAI_CR2_DIV_MASK 0xff + +/* SAI Transmit and Receive Configuration 3 Register */ +#define FSL_SAI_CR3_TRCE BIT(16) +#define FSL_SAI_CR3_WDFL(x) (x) +#define FSL_SAI_CR3_WDFL_MASK 0x1f + +/* SAI Transmit and Receive Configuration 4 Register */ +#define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16) +#define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16) +#define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8) +#define FSL_SAI_CR4_SYWD_MASK (0x1f << 8) +#define FSL_SAI_CR4_MF BIT(4) +#define FSL_SAI_CR4_FSE BIT(3) +#define FSL_SAI_CR4_FSP BIT(1) +#define FSL_SAI_CR4_FSD_MSTR BIT(0) + +/* SAI Transmit and Receive Configuration 5 Register */ +#define FSL_SAI_CR5_WNW(x) (((x) - 1) << 24) +#define FSL_SAI_CR5_WNW_MASK (0x1f << 24) +#define FSL_SAI_CR5_W0W(x) (((x) - 1) << 16) +#define FSL_SAI_CR5_W0W_MASK (0x1f << 16) +#define FSL_SAI_CR5_FBT(x) ((x) << 8) +#define FSL_SAI_CR5_FBT_MASK (0x1f << 8) + +/* SAI type */ +#define FSL_SAI_DMA BIT(0) +#define FSL_SAI_USE_AC97 BIT(1) +#define FSL_SAI_NET BIT(2) +#define FSL_SAI_TRA_SYN BIT(3) +#define FSL_SAI_REC_SYN BIT(4) +#define FSL_SAI_USE_I2S_SLAVE BIT(5) + +#define FSL_FMT_TRANSMITTER 0 +#define FSL_FMT_RECEIVER 1 + +/* SAI clock sources */ +#define FSL_SAI_CLK_BUS 0 +#define FSL_SAI_CLK_MAST1 1 +#define FSL_SAI_CLK_MAST2 2 +#define FSL_SAI_CLK_MAST3 3 + +#define FSL_SAI_MCLK_MAX 4 + +/* SAI data transfer numbers per DMA request */ +#define FSL_SAI_MAXBURST_TX 6 +#define FSL_SAI_MAXBURST_RX 6 + +struct fsl_sai { + struct platform_device *pdev; + struct regmap *regmap; + struct clk *bus_clk; + struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; + + bool is_slave_mode; + bool is_lsb_first; + bool is_dsp_mode; + bool sai_on_imx; + bool synchronous[2]; + + unsigned int mclk_id[2]; + unsigned int mclk_streams; + unsigned int slots; + unsigned int slot_width; + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +#define TX 1 +#define RX 0 + +#endif /* __FSL_SAI_H */ diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c new file mode 100644 index 000000000..740b90df4 --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.c @@ -0,0 +1,1382 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver +// +// Copyright (C) 2013 Freescale Semiconductor, Inc. +// +// Based on stmp3xxx_spdif_dai.c +// Vladimir Barinov +// Copyright 2008 SigmaTel, Inc +// Copyright 2008 Embedded Alley Solutions, Inc + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "fsl_spdif.h" +#include "imx-pcm.h" + +#define FSL_SPDIF_TXFIFO_WML 0x8 +#define FSL_SPDIF_RXFIFO_WML 0x8 + +#define INTR_FOR_PLAYBACK (INT_TXFIFO_RESYNC) +#define INTR_FOR_CAPTURE (INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL |\ + INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |\ + INT_UQ_SYNC | INT_UQ_ERR | INT_RXFIFO_RESYNC |\ + INT_LOSS_LOCK | INT_DPLL_LOCKED) + +#define SIE_INTR_FOR(tx) (tx ? INTR_FOR_PLAYBACK : INTR_FOR_CAPTURE) + +/* Index list for the values that has if (DPLL Locked) condition */ +static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb }; +#define SRPC_NODPLL_START1 0x5 +#define SRPC_NODPLL_START2 0xc + +#define DEFAULT_RXCLK_SRC 1 + +/* + * SPDIF control structure + * Defines channel status, subcode and Q sub + */ +struct spdif_mixer_control { + /* spinlock to access control data */ + spinlock_t ctl_lock; + + /* IEC958 channel tx status bit */ + unsigned char ch_status[4]; + + /* User bits */ + unsigned char subcode[2 * SPDIF_UBITS_SIZE]; + + /* Q subcode part of user bits */ + unsigned char qsub[2 * SPDIF_QSUB_SIZE]; + + /* Buffer offset for U/Q */ + u32 upos; + u32 qpos; + + /* Ready buffer index of the two buffers */ + u32 ready_buf; +}; + +/** + * fsl_spdif_priv: Freescale SPDIF private data + * + * @fsl_spdif_control: SPDIF control data + * @cpu_dai_drv: cpu dai driver + * @pdev: platform device pointer + * @regmap: regmap handler + * @dpll_locked: dpll lock flag + * @txrate: the best rates for playback + * @txclk_df: STC_TXCLK_DF dividers value for playback + * @sysclk_df: STC_SYSCLK_DF dividers value for playback + * @txclk_src: STC_TXCLK_SRC values for playback + * @rxclk_src: SRPC_CLKSRC_SEL values for capture + * @txclk: tx clock sources for playback + * @rxclk: rx clock sources for capture + * @coreclk: core clock for register access via DMA + * @sysclk: system clock for rx clock rate measurement + * @spbaclk: SPBA clock (optional, depending on SoC design) + * @dma_params_tx: DMA parameters for transmit channel + * @dma_params_rx: DMA parameters for receive channel + */ +struct fsl_spdif_priv { + struct spdif_mixer_control fsl_spdif_control; + struct snd_soc_dai_driver cpu_dai_drv; + struct platform_device *pdev; + struct regmap *regmap; + bool dpll_locked; + u32 txrate[SPDIF_TXRATE_MAX]; + u8 txclk_df[SPDIF_TXRATE_MAX]; + u8 sysclk_df[SPDIF_TXRATE_MAX]; + u8 txclk_src[SPDIF_TXRATE_MAX]; + u8 rxclk_src; + struct clk *txclk[SPDIF_TXRATE_MAX]; + struct clk *rxclk; + struct clk *coreclk; + struct clk *sysclk; + struct clk *spbaclk; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_dai_dma_data dma_params_rx; + /* regcache for SRPC */ + u32 regcache_srpc; +}; + +/* DPLL locked and lock loss interrupt handler */ +static void spdif_irq_dpll_lock(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 locked; + + regmap_read(regmap, REG_SPDIF_SRPC, &locked); + locked &= SRPC_DPLL_LOCKED; + + dev_dbg(&pdev->dev, "isr: Rx dpll %s \n", + locked ? "locked" : "loss lock"); + + spdif_priv->dpll_locked = locked ? true : false; +} + +/* Receiver found illegal symbol interrupt handler */ +static void spdif_irq_sym_error(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + + dev_dbg(&pdev->dev, "isr: receiver found illegal symbol\n"); + + /* Clear illegal symbol if DPLL unlocked since no audio stream */ + if (!spdif_priv->dpll_locked) + regmap_update_bits(regmap, REG_SPDIF_SIE, INT_SYM_ERR, 0); +} + +/* U/Q Channel receive register full */ +static void spdif_irq_uqrx_full(struct fsl_spdif_priv *spdif_priv, char name) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 *pos, size, val, reg; + + switch (name) { + case 'U': + pos = &ctrl->upos; + size = SPDIF_UBITS_SIZE; + reg = REG_SPDIF_SRU; + break; + case 'Q': + pos = &ctrl->qpos; + size = SPDIF_QSUB_SIZE; + reg = REG_SPDIF_SRQ; + break; + default: + dev_err(&pdev->dev, "unsupported channel name\n"); + return; + } + + dev_dbg(&pdev->dev, "isr: %c Channel receive register full\n", name); + + if (*pos >= size * 2) { + *pos = 0; + } else if (unlikely((*pos % size) + 3 > size)) { + dev_err(&pdev->dev, "User bit receive buffer overflow\n"); + return; + } + + regmap_read(regmap, reg, &val); + ctrl->subcode[*pos++] = val >> 16; + ctrl->subcode[*pos++] = val >> 8; + ctrl->subcode[*pos++] = val; +} + +/* U/Q Channel sync found */ +static void spdif_irq_uq_sync(struct fsl_spdif_priv *spdif_priv) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct platform_device *pdev = spdif_priv->pdev; + + dev_dbg(&pdev->dev, "isr: U/Q Channel sync found\n"); + + /* U/Q buffer reset */ + if (ctrl->qpos == 0) + return; + + /* Set ready to this buffer */ + ctrl->ready_buf = (ctrl->qpos - 1) / SPDIF_QSUB_SIZE + 1; +} + +/* U/Q Channel framing error */ +static void spdif_irq_uq_err(struct fsl_spdif_priv *spdif_priv) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 val; + + dev_dbg(&pdev->dev, "isr: U/Q Channel framing error\n"); + + /* Read U/Q data to clear the irq and do buffer reset */ + regmap_read(regmap, REG_SPDIF_SRU, &val); + regmap_read(regmap, REG_SPDIF_SRQ, &val); + + /* Drop this U/Q buffer */ + ctrl->ready_buf = 0; + ctrl->upos = 0; + ctrl->qpos = 0; +} + +/* Get spdif interrupt status and clear the interrupt */ +static u32 spdif_intr_status_clear(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + u32 val, val2; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + regmap_read(regmap, REG_SPDIF_SIE, &val2); + + regmap_write(regmap, REG_SPDIF_SIC, val & val2); + + return val; +} + +static irqreturn_t spdif_isr(int irq, void *devid) +{ + struct fsl_spdif_priv *spdif_priv = (struct fsl_spdif_priv *)devid; + struct platform_device *pdev = spdif_priv->pdev; + u32 sis; + + sis = spdif_intr_status_clear(spdif_priv); + + if (sis & INT_DPLL_LOCKED) + spdif_irq_dpll_lock(spdif_priv); + + if (sis & INT_TXFIFO_UNOV) + dev_dbg(&pdev->dev, "isr: Tx FIFO under/overrun\n"); + + if (sis & INT_TXFIFO_RESYNC) + dev_dbg(&pdev->dev, "isr: Tx FIFO resync\n"); + + if (sis & INT_CNEW) + dev_dbg(&pdev->dev, "isr: cstatus new\n"); + + if (sis & INT_VAL_NOGOOD) + dev_dbg(&pdev->dev, "isr: validity flag no good\n"); + + if (sis & INT_SYM_ERR) + spdif_irq_sym_error(spdif_priv); + + if (sis & INT_BIT_ERR) + dev_dbg(&pdev->dev, "isr: receiver found parity bit error\n"); + + if (sis & INT_URX_FUL) + spdif_irq_uqrx_full(spdif_priv, 'U'); + + if (sis & INT_URX_OV) + dev_dbg(&pdev->dev, "isr: U Channel receive register overrun\n"); + + if (sis & INT_QRX_FUL) + spdif_irq_uqrx_full(spdif_priv, 'Q'); + + if (sis & INT_QRX_OV) + dev_dbg(&pdev->dev, "isr: Q Channel receive register overrun\n"); + + if (sis & INT_UQ_SYNC) + spdif_irq_uq_sync(spdif_priv); + + if (sis & INT_UQ_ERR) + spdif_irq_uq_err(spdif_priv); + + if (sis & INT_RXFIFO_UNOV) + dev_dbg(&pdev->dev, "isr: Rx FIFO under/overrun\n"); + + if (sis & INT_RXFIFO_RESYNC) + dev_dbg(&pdev->dev, "isr: Rx FIFO resync\n"); + + if (sis & INT_LOSS_LOCK) + spdif_irq_dpll_lock(spdif_priv); + + /* FIXME: Write Tx FIFO to clear TxEm */ + if (sis & INT_TX_EM) + dev_dbg(&pdev->dev, "isr: Tx FIFO empty\n"); + + /* FIXME: Read Rx FIFO to clear RxFIFOFul */ + if (sis & INT_RXFIFO_FUL) + dev_dbg(&pdev->dev, "isr: Rx FIFO full\n"); + + return IRQ_HANDLED; +} + +static int spdif_softreset(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + u32 val, cycle = 1000; + + regcache_cache_bypass(regmap, true); + + regmap_write(regmap, REG_SPDIF_SCR, SCR_SOFT_RESET); + + /* + * RESET bit would be cleared after finishing its reset procedure, + * which typically lasts 8 cycles. 1000 cycles will keep it safe. + */ + do { + regmap_read(regmap, REG_SPDIF_SCR, &val); + } while ((val & SCR_SOFT_RESET) && cycle--); + + regcache_cache_bypass(regmap, false); + regcache_mark_dirty(regmap); + regcache_sync(regmap); + + if (cycle) + return 0; + else + return -EBUSY; +} + +static void spdif_set_cstatus(struct spdif_mixer_control *ctrl, + u8 mask, u8 cstatus) +{ + ctrl->ch_status[3] &= ~mask; + ctrl->ch_status[3] |= cstatus & mask; +} + +static void spdif_write_channel_status(struct fsl_spdif_priv *spdif_priv) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 ch_status; + + ch_status = (bitrev8(ctrl->ch_status[0]) << 16) | + (bitrev8(ctrl->ch_status[1]) << 8) | + bitrev8(ctrl->ch_status[2]); + regmap_write(regmap, REG_SPDIF_STCSCH, ch_status); + + dev_dbg(&pdev->dev, "STCSCH: 0x%06x\n", ch_status); + + ch_status = bitrev8(ctrl->ch_status[3]) << 16; + regmap_write(regmap, REG_SPDIF_STCSCL, ch_status); + + dev_dbg(&pdev->dev, "STCSCL: 0x%06x\n", ch_status); +} + +/* Set SPDIF PhaseConfig register for rx clock */ +static int spdif_set_rx_clksrc(struct fsl_spdif_priv *spdif_priv, + enum spdif_gainsel gainsel, int dpll_locked) +{ + struct regmap *regmap = spdif_priv->regmap; + u8 clksrc = spdif_priv->rxclk_src; + + if (clksrc >= SRPC_CLKSRC_MAX || gainsel >= GAINSEL_MULTI_MAX) + return -EINVAL; + + regmap_update_bits(regmap, REG_SPDIF_SRPC, + SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK, + SRPC_CLKSRC_SEL_SET(clksrc) | SRPC_GAINSEL_SET(gainsel)); + + return 0; +} + +static int spdif_set_sample_rate(struct snd_pcm_substream *substream, + int sample_rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + unsigned long csfs = 0; + u32 stc, mask, rate; + u8 clk, txclk_df, sysclk_df; + int ret; + + switch (sample_rate) { + case 32000: + rate = SPDIF_TXRATE_32000; + csfs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + rate = SPDIF_TXRATE_44100; + csfs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + rate = SPDIF_TXRATE_48000; + csfs = IEC958_AES3_CON_FS_48000; + break; + case 96000: + rate = SPDIF_TXRATE_96000; + csfs = IEC958_AES3_CON_FS_96000; + break; + case 192000: + rate = SPDIF_TXRATE_192000; + csfs = IEC958_AES3_CON_FS_192000; + break; + default: + dev_err(&pdev->dev, "unsupported sample rate %d\n", sample_rate); + return -EINVAL; + } + + clk = spdif_priv->txclk_src[rate]; + if (clk >= STC_TXCLK_SRC_MAX) { + dev_err(&pdev->dev, "tx clock source is out of range\n"); + return -EINVAL; + } + + txclk_df = spdif_priv->txclk_df[rate]; + if (txclk_df == 0) { + dev_err(&pdev->dev, "the txclk_df can't be zero\n"); + return -EINVAL; + } + + sysclk_df = spdif_priv->sysclk_df[rate]; + + /* Don't mess up the clocks from other modules */ + if (clk != STC_TXCLK_SPDIF_ROOT) + goto clk_set_bypass; + + /* The S/PDIF block needs a clock of 64 * fs * txclk_df */ + ret = clk_set_rate(spdif_priv->txclk[rate], + 64 * sample_rate * txclk_df); + if (ret) { + dev_err(&pdev->dev, "failed to set tx clock rate\n"); + return ret; + } + +clk_set_bypass: + dev_dbg(&pdev->dev, "expected clock rate = %d\n", + (64 * sample_rate * txclk_df * sysclk_df)); + dev_dbg(&pdev->dev, "actual clock rate = %ld\n", + clk_get_rate(spdif_priv->txclk[rate])); + + /* set fs field in consumer channel status */ + spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs); + + /* select clock source and divisor */ + stc = STC_TXCLK_ALL_EN | STC_TXCLK_SRC_SET(clk) | + STC_TXCLK_DF(txclk_df) | STC_SYSCLK_DF(sysclk_df); + mask = STC_TXCLK_ALL_EN_MASK | STC_TXCLK_SRC_MASK | + STC_TXCLK_DF_MASK | STC_SYSCLK_DF_MASK; + regmap_update_bits(regmap, REG_SPDIF_STC, mask, stc); + + dev_dbg(&pdev->dev, "set sample rate to %dHz for %dHz playback\n", + spdif_priv->txrate[rate], sample_rate); + + return 0; +} + +static int fsl_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct platform_device *pdev = spdif_priv->pdev; + struct regmap *regmap = spdif_priv->regmap; + u32 scr, mask; + int i; + int ret; + + /* Reset module and interrupts only for first initialization */ + if (!cpu_dai->active) { + ret = clk_prepare_enable(spdif_priv->coreclk); + if (ret) { + dev_err(&pdev->dev, "failed to enable core clock\n"); + return ret; + } + + if (!IS_ERR(spdif_priv->spbaclk)) { + ret = clk_prepare_enable(spdif_priv->spbaclk); + if (ret) { + dev_err(&pdev->dev, "failed to enable spba clock\n"); + goto err_spbaclk; + } + } + + ret = spdif_softreset(spdif_priv); + if (ret) { + dev_err(&pdev->dev, "failed to soft reset\n"); + goto err; + } + + /* Disable all the interrupts */ + regmap_update_bits(regmap, REG_SPDIF_SIE, 0xffffff, 0); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + scr = SCR_TXFIFO_AUTOSYNC | SCR_TXFIFO_CTRL_NORMAL | + SCR_TXSEL_NORMAL | SCR_USRC_SEL_CHIP | + SCR_TXFIFO_FSEL_IF8; + mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | + SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | + SCR_TXFIFO_FSEL_MASK; + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { + ret = clk_prepare_enable(spdif_priv->txclk[i]); + if (ret) + goto disable_txclk; + } + } else { + scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; + ret = clk_prepare_enable(spdif_priv->rxclk); + if (ret) + goto err; + } + regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); + + /* Power up SPDIF module */ + regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0); + + return 0; + +disable_txclk: + for (i--; i >= 0; i--) + clk_disable_unprepare(spdif_priv->txclk[i]); +err: + if (!IS_ERR(spdif_priv->spbaclk)) + clk_disable_unprepare(spdif_priv->spbaclk); +err_spbaclk: + clk_disable_unprepare(spdif_priv->coreclk); + + return ret; +} + +static void fsl_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 scr, mask, i; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + scr = 0; + mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | + SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | + SCR_TXFIFO_FSEL_MASK; + for (i = 0; i < SPDIF_TXRATE_MAX; i++) + clk_disable_unprepare(spdif_priv->txclk[i]); + } else { + scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; + clk_disable_unprepare(spdif_priv->rxclk); + } + regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); + + /* Power down SPDIF module only if tx&rx are both inactive */ + if (!cpu_dai->active) { + spdif_intr_status_clear(spdif_priv); + regmap_update_bits(regmap, REG_SPDIF_SCR, + SCR_LOW_POWER, SCR_LOW_POWER); + if (!IS_ERR(spdif_priv->spbaclk)) + clk_disable_unprepare(spdif_priv->spbaclk); + clk_disable_unprepare(spdif_priv->coreclk); + } +} + +static int fsl_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct platform_device *pdev = spdif_priv->pdev; + u32 sample_rate = params_rate(params); + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = spdif_set_sample_rate(substream, sample_rate); + if (ret) { + dev_err(&pdev->dev, "%s: set sample rate failed: %d\n", + __func__, sample_rate); + return ret; + } + spdif_set_cstatus(ctrl, IEC958_AES3_CON_CLOCK, + IEC958_AES3_CON_CLOCK_1000PPM); + spdif_write_channel_status(spdif_priv); + } else { + /* Setup rx clock source */ + ret = spdif_set_rx_clksrc(spdif_priv, SPDIF_DEFAULT_GAINSEL, 1); + } + + return ret; +} + +static int fsl_spdif_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 intr = SIE_INTR_FOR(tx); + u32 dmaen = SCR_DMA_xX_EN(tx); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(regmap, REG_SPDIF_SIE, intr, intr); + regmap_update_bits(regmap, REG_SPDIF_SCR, dmaen, dmaen); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(regmap, REG_SPDIF_SCR, dmaen, 0); + regmap_update_bits(regmap, REG_SPDIF_SIE, intr, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops fsl_spdif_dai_ops = { + .startup = fsl_spdif_startup, + .hw_params = fsl_spdif_hw_params, + .trigger = fsl_spdif_trigger, + .shutdown = fsl_spdif_shutdown, +}; + + +/* + * FSL SPDIF IEC958 controller(mixer) functions + * + * Channel status get/put control + * User bit value get/put control + * Valid bit value get control + * DPLL lock status get control + * User bit sync mode selection control + */ + +static int fsl_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 fsl_spdif_pb_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + + uvalue->value.iec958.status[0] = ctrl->ch_status[0]; + uvalue->value.iec958.status[1] = ctrl->ch_status[1]; + uvalue->value.iec958.status[2] = ctrl->ch_status[2]; + uvalue->value.iec958.status[3] = ctrl->ch_status[3]; + + return 0; +} + +static int fsl_spdif_pb_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + + ctrl->ch_status[0] = uvalue->value.iec958.status[0]; + ctrl->ch_status[1] = uvalue->value.iec958.status[1]; + ctrl->ch_status[2] = uvalue->value.iec958.status[2]; + ctrl->ch_status[3] = uvalue->value.iec958.status[3]; + + spdif_write_channel_status(spdif_priv); + + return 0; +} + +/* Get channel status from SPDIF_RX_CCHAN register */ +static int fsl_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 cstatus, val; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + if (!(val & INT_CNEW)) + return -EAGAIN; + + regmap_read(regmap, REG_SPDIF_SRCSH, &cstatus); + ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[2] = cstatus & 0xFF; + + regmap_read(regmap, REG_SPDIF_SRCSL, &cstatus); + ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[5] = cstatus & 0xFF; + + /* Clear intr */ + regmap_write(regmap, REG_SPDIF_SIC, INT_CNEW); + + return 0; +} + +/* + * Get User bits (subcode) from chip value which readed out + * in UChannel register. + */ +static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + unsigned long flags; + int ret = -EAGAIN; + + spin_lock_irqsave(&ctrl->ctl_lock, flags); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + ret = 0; + } + spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; +} + +/* Q-subcode information. The byte size is SPDIF_UBITS_SIZE/8 */ +static int fsl_spdif_qinfo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = SPDIF_QSUB_SIZE; + + return 0; +} + +/* Get Q subcode from chip value which readed out in QChannel register */ +static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + unsigned long flags; + int ret = -EAGAIN; + + spin_lock_irqsave(&ctrl->ctl_lock, flags); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + ret = 0; + } + spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; +} + +/* Valid bit information */ +static int fsl_spdif_vbit_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; +} + +/* Get valid good bit from interrupt status register */ +static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0; + regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD); + + return 0; +} + +/* DPLL lock information */ +static int fsl_spdif_rxrate_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 = 16000; + uinfo->value.integer.max = 96000; + + return 0; +} + +static u32 gainsel_multi[GAINSEL_MULTI_MAX] = { + 24, 16, 12, 8, 6, 4, 3, +}; + +/* Get RX data clock rate given the SPDIF bus_clk */ +static int spdif_get_rxclk_rate(struct fsl_spdif_priv *spdif_priv, + enum spdif_gainsel gainsel) +{ + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u64 tmpval64, busclk_freq = 0; + u32 freqmeas, phaseconf; + u8 clksrc; + + regmap_read(regmap, REG_SPDIF_SRFM, &freqmeas); + regmap_read(regmap, REG_SPDIF_SRPC, &phaseconf); + + clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0xf; + + /* Get bus clock from system */ + if (srpc_dpll_locked[clksrc] && (phaseconf & SRPC_DPLL_LOCKED)) + busclk_freq = clk_get_rate(spdif_priv->sysclk); + + /* FreqMeas_CLK = (BUS_CLK * FreqMeas) / 2 ^ 10 / GAINSEL / 128 */ + tmpval64 = (u64) busclk_freq * freqmeas; + do_div(tmpval64, gainsel_multi[gainsel] * 1024); + do_div(tmpval64, 128 * 1024); + + dev_dbg(&pdev->dev, "FreqMeas: %d\n", freqmeas); + dev_dbg(&pdev->dev, "BusclkFreq: %lld\n", busclk_freq); + dev_dbg(&pdev->dev, "RxRate: %lld\n", tmpval64); + + return (int)tmpval64; +} + +/* + * Get DPLL lock or not info from stable interrupt status register. + * User application must use this control to get locked, + * then can do next PCM operation + */ +static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + int rate = 0; + + if (spdif_priv->dpll_locked) + rate = spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL); + + ucontrol->value.integer.value[0] = rate; + + return 0; +} + +/* User bit sync mode info */ +static int fsl_spdif_usync_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; +} + +/* + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int fsl_spdif_usync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SRCD, &val); + ucontrol->value.integer.value[0] = (val & SRCD_CD_USER) != 0; + + return 0; +} + +/* + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int fsl_spdif_usync_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET; + + regmap_update_bits(regmap, REG_SPDIF_SRCD, SRCD_CD_USER, val); + + return 0; +} + +/* FSL SPDIF IEC958 controller defines */ +static struct snd_kcontrol_new fsl_spdif_ctrls[] = { + /* Status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_info, + .get = fsl_spdif_pb_get, + .put = fsl_spdif_pb_put, + }, + { + .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 = fsl_spdif_info, + .get = fsl_spdif_capture_get, + }, + /* User bits controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_info, + .get = fsl_spdif_subcode_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_qinfo, + .get = fsl_spdif_qget, + }, + /* Valid bit error controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_vbit_info, + .get = fsl_spdif_vbit_get, + }, + /* DPLL lock info get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "RX Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_rxrate_info, + .get = fsl_spdif_rxrate_get, + }, + /* User bit sync mode set/get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 USyncMode CDText", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_usync_info, + .get = fsl_spdif_usync_get, + .put = fsl_spdif_usync_put, + }, +}; + +static int fsl_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_spdif_priv *spdif_private = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &spdif_private->dma_params_tx, + &spdif_private->dma_params_rx); + + snd_soc_add_dai_controls(dai, fsl_spdif_ctrls, ARRAY_SIZE(fsl_spdif_ctrls)); + + return 0; +} + +static struct snd_soc_dai_driver fsl_spdif_dai = { + .probe = &fsl_spdif_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_SPDIF_RATES_PLAYBACK, + .formats = FSL_SPDIF_FORMATS_PLAYBACK, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_SPDIF_RATES_CAPTURE, + .formats = FSL_SPDIF_FORMATS_CAPTURE, + }, + .ops = &fsl_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_spdif_component = { + .name = "fsl-spdif", +}; + +/* FSL SPDIF REGMAP */ +static const struct reg_default fsl_spdif_reg_defaults[] = { + {REG_SPDIF_SCR, 0x00000400}, + {REG_SPDIF_SRCD, 0x00000000}, + {REG_SPDIF_SIE, 0x00000000}, + {REG_SPDIF_STL, 0x00000000}, + {REG_SPDIF_STR, 0x00000000}, + {REG_SPDIF_STCSCH, 0x00000000}, + {REG_SPDIF_STCSCL, 0x00000000}, + {REG_SPDIF_STC, 0x00020f00}, +}; + +static bool fsl_spdif_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SPDIF_SCR: + case REG_SPDIF_SRCD: + case REG_SPDIF_SRPC: + case REG_SPDIF_SIE: + case REG_SPDIF_SIS: + case REG_SPDIF_SRL: + case REG_SPDIF_SRR: + case REG_SPDIF_SRCSH: + case REG_SPDIF_SRCSL: + case REG_SPDIF_SRU: + case REG_SPDIF_SRQ: + case REG_SPDIF_STCSCH: + case REG_SPDIF_STCSCL: + case REG_SPDIF_SRFM: + case REG_SPDIF_STC: + return true; + default: + return false; + } +} + +static bool fsl_spdif_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SPDIF_SRPC: + case REG_SPDIF_SIS: + case REG_SPDIF_SRL: + case REG_SPDIF_SRR: + case REG_SPDIF_SRCSH: + case REG_SPDIF_SRCSL: + case REG_SPDIF_SRU: + case REG_SPDIF_SRQ: + case REG_SPDIF_SRFM: + return true; + default: + return false; + } +} + +static bool fsl_spdif_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SPDIF_SCR: + case REG_SPDIF_SRCD: + case REG_SPDIF_SRPC: + case REG_SPDIF_SIE: + case REG_SPDIF_SIC: + case REG_SPDIF_STL: + case REG_SPDIF_STR: + case REG_SPDIF_STCSCH: + case REG_SPDIF_STCSCL: + case REG_SPDIF_STC: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_SPDIF_STC, + .reg_defaults = fsl_spdif_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_spdif_reg_defaults), + .readable_reg = fsl_spdif_readable_reg, + .volatile_reg = fsl_spdif_volatile_reg, + .writeable_reg = fsl_spdif_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, + struct clk *clk, u64 savesub, + enum spdif_txrate index, bool round) +{ + static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); + u64 rate_ideal, rate_actual, sub; + u32 sysclk_dfmin, sysclk_dfmax; + u32 txclk_df, sysclk_df, arate; + + /* The sysclk has an extra divisor [2, 512] */ + sysclk_dfmin = is_sysclk ? 2 : 1; + sysclk_dfmax = is_sysclk ? 512 : 1; + + for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { + for (txclk_df = 1; txclk_df <= 128; txclk_df++) { + rate_ideal = rate[index] * txclk_df * 64ULL; + if (round) + rate_actual = clk_round_rate(clk, rate_ideal); + else + rate_actual = clk_get_rate(clk); + + arate = rate_actual / 64; + arate /= txclk_df * sysclk_df; + + if (arate == rate[index]) { + /* We are lucky */ + savesub = 0; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + goto out; + } else if (arate / rate[index] == 1) { + /* A little bigger than expect */ + sub = (u64)(arate - rate[index]) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } else if (rate[index] / arate == 1) { + /* A little smaller than expect */ + sub = (u64)(rate[index] - arate) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } + } + } + +out: + return savesub; +} + +static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, + enum spdif_txrate index) +{ + static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + struct platform_device *pdev = spdif_priv->pdev; + struct device *dev = &pdev->dev; + u64 savesub = 100000, ret; + struct clk *clk; + char tmp[16]; + int i; + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + sprintf(tmp, "rxtx%d", i); + clk = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(clk); + } + if (!clk_get_rate(clk)) + continue; + + ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, + i == STC_TXCLK_SPDIF_ROOT); + if (savesub == ret) + continue; + + savesub = ret; + spdif_priv->txclk[index] = clk; + spdif_priv->txclk_src[index] = i; + + /* To quick catch a divisor, we allow a 0.1% deviation */ + if (savesub < 100) + break; + } + + dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", + spdif_priv->txclk_src[index], rate[index]); + dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", + spdif_priv->txclk_df[index], rate[index]); + if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk)) + dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", + spdif_priv->sysclk_df[index], rate[index]); + dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", + rate[index], spdif_priv->txrate[index]); + + return 0; +} + +static int fsl_spdif_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_spdif_priv *spdif_priv; + struct spdif_mixer_control *ctrl; + struct resource *res; + void __iomem *regs; + int irq, ret, i; + + if (!np) + return -ENODEV; + + spdif_priv = devm_kzalloc(&pdev->dev, sizeof(*spdif_priv), GFP_KERNEL); + if (!spdif_priv) + return -ENOMEM; + + spdif_priv->pdev = pdev; + + /* Initialize this copy of the CPU DAI driver structure */ + memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai)); + spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev); + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + spdif_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "core", regs, &fsl_spdif_regmap_config); + if (IS_ERR(spdif_priv->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(spdif_priv->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, + dev_name(&pdev->dev), spdif_priv); + if (ret) { + dev_err(&pdev->dev, "could not claim irq %u\n", irq); + return ret; + } + + /* Get system clock for rx clock rate calculation */ + spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5"); + if (IS_ERR(spdif_priv->sysclk)) { + dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n"); + return PTR_ERR(spdif_priv->sysclk); + } + + /* Get core clock for data register access via DMA */ + spdif_priv->coreclk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(spdif_priv->coreclk)) { + dev_err(&pdev->dev, "no core clock in devicetree\n"); + return PTR_ERR(spdif_priv->coreclk); + } + + spdif_priv->spbaclk = devm_clk_get(&pdev->dev, "spba"); + if (IS_ERR(spdif_priv->spbaclk)) + dev_warn(&pdev->dev, "no spba clock in devicetree\n"); + + /* Select clock source for rx/tx clock */ + spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1"); + if (IS_ERR(spdif_priv->rxclk)) { + dev_err(&pdev->dev, "no rxtx1 clock in devicetree\n"); + return PTR_ERR(spdif_priv->rxclk); + } + spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC; + + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { + ret = fsl_spdif_probe_txclk(spdif_priv, i); + if (ret) + return ret; + } + + /* Initial spinlock for control data */ + ctrl = &spdif_priv->fsl_spdif_control; + spin_lock_init(&ctrl->ctl_lock); + + /* Init tx channel status default value */ + ctrl->ch_status[0] = IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS_5015; + ctrl->ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID; + ctrl->ch_status[2] = 0x00; + ctrl->ch_status[3] = IEC958_AES3_CON_FS_44100 | + IEC958_AES3_CON_CLOCK_1000PPM; + + spdif_priv->dpll_locked = false; + + spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML; + spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML; + spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL; + spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL; + + /* Register with ASoC */ + dev_set_drvdata(&pdev->dev, spdif_priv); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_spdif_component, + &spdif_priv->cpu_dai_drv, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); + return ret; + } + + ret = imx_pcm_dma_init(pdev, IMX_SPDIF_DMABUF_SIZE); + if (ret) + dev_err(&pdev->dev, "imx_pcm_dma_init failed: %d\n", ret); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int fsl_spdif_suspend(struct device *dev) +{ + struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev); + + regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC, + &spdif_priv->regcache_srpc); + + regcache_cache_only(spdif_priv->regmap, true); + regcache_mark_dirty(spdif_priv->regmap); + + return 0; +} + +static int fsl_spdif_resume(struct device *dev) +{ + struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev); + + regcache_cache_only(spdif_priv->regmap, false); + + regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC, + SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK, + spdif_priv->regcache_srpc); + + return regcache_sync(spdif_priv->regmap); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_spdif_pm = { + SET_SYSTEM_SLEEP_PM_OPS(fsl_spdif_suspend, fsl_spdif_resume) +}; + +static const struct of_device_id fsl_spdif_dt_ids[] = { + { .compatible = "fsl,imx35-spdif", }, + { .compatible = "fsl,vf610-spdif", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); + +static struct platform_driver fsl_spdif_driver = { + .driver = { + .name = "fsl-spdif-dai", + .of_match_table = fsl_spdif_dt_ids, + .pm = &fsl_spdif_pm, + }, + .probe = fsl_spdif_probe, +}; + +module_platform_driver(fsl_spdif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale S/PDIF CPU DAI Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:fsl-spdif-dai"); diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h new file mode 100644 index 000000000..7666dabac --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fsl_spdif.h - ALSA S/PDIF interface for the Freescale i.MX SoC + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen + * + * Based on fsl_ssi.h + * Author: Timur Tabi + * Copyright 2007-2008 Freescale Semiconductor, Inc. + */ + +#ifndef _FSL_SPDIF_DAI_H +#define _FSL_SPDIF_DAI_H + +/* S/PDIF Register Map */ +#define REG_SPDIF_SCR 0x0 /* SPDIF Configuration Register */ +#define REG_SPDIF_SRCD 0x4 /* CDText Control Register */ +#define REG_SPDIF_SRPC 0x8 /* PhaseConfig Register */ +#define REG_SPDIF_SIE 0xc /* InterruptEn Register */ +#define REG_SPDIF_SIS 0x10 /* InterruptStat Register */ +#define REG_SPDIF_SIC 0x10 /* InterruptClear Register */ +#define REG_SPDIF_SRL 0x14 /* SPDIFRxLeft Register */ +#define REG_SPDIF_SRR 0x18 /* SPDIFRxRight Register */ +#define REG_SPDIF_SRCSH 0x1c /* SPDIFRxCChannel_h Register */ +#define REG_SPDIF_SRCSL 0x20 /* SPDIFRxCChannel_l Register */ +#define REG_SPDIF_SRU 0x24 /* UchannelRx Register */ +#define REG_SPDIF_SRQ 0x28 /* QchannelRx Register */ +#define REG_SPDIF_STL 0x2C /* SPDIFTxLeft Register */ +#define REG_SPDIF_STR 0x30 /* SPDIFTxRight Register */ +#define REG_SPDIF_STCSCH 0x34 /* SPDIFTxCChannelCons_h Register */ +#define REG_SPDIF_STCSCL 0x38 /* SPDIFTxCChannelCons_l Register */ +#define REG_SPDIF_SRFM 0x44 /* FreqMeas Register */ +#define REG_SPDIF_STC 0x50 /* SPDIFTxClk Register */ + + +/* SPDIF Configuration register */ +#define SCR_RXFIFO_CTL_OFFSET 23 +#define SCR_RXFIFO_CTL_MASK (1 << SCR_RXFIFO_CTL_OFFSET) +#define SCR_RXFIFO_CTL_ZERO (1 << SCR_RXFIFO_CTL_OFFSET) +#define SCR_RXFIFO_OFF_OFFSET 22 +#define SCR_RXFIFO_OFF_MASK (1 << SCR_RXFIFO_OFF_OFFSET) +#define SCR_RXFIFO_OFF (1 << SCR_RXFIFO_OFF_OFFSET) +#define SCR_RXFIFO_RST_OFFSET 21 +#define SCR_RXFIFO_RST_MASK (1 << SCR_RXFIFO_RST_OFFSET) +#define SCR_RXFIFO_RST (1 << SCR_RXFIFO_RST_OFFSET) +#define SCR_RXFIFO_FSEL_OFFSET 19 +#define SCR_RXFIFO_FSEL_MASK (0x3 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF0 (0x0 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF4 (0x1 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF8 (0x2 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF12 (0x3 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_AUTOSYNC_OFFSET 18 +#define SCR_RXFIFO_AUTOSYNC_MASK (1 << SCR_RXFIFO_AUTOSYNC_OFFSET) +#define SCR_RXFIFO_AUTOSYNC (1 << SCR_RXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_AUTOSYNC_OFFSET 17 +#define SCR_TXFIFO_AUTOSYNC_MASK (1 << SCR_TXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_AUTOSYNC (1 << SCR_TXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_FSEL_OFFSET 15 +#define SCR_TXFIFO_FSEL_MASK (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF0 (0x0 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_LOW_POWER (1 << 13) +#define SCR_SOFT_RESET (1 << 12) +#define SCR_TXFIFO_CTRL_OFFSET 10 +#define SCR_TXFIFO_CTRL_MASK (0x3 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_ZERO (0x0 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_NORMAL (0x1 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_ONESAMPLE (0x2 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_DMA_RX_EN_OFFSET 9 +#define SCR_DMA_RX_EN_MASK (1 << SCR_DMA_RX_EN_OFFSET) +#define SCR_DMA_RX_EN (1 << SCR_DMA_RX_EN_OFFSET) +#define SCR_DMA_TX_EN_OFFSET 8 +#define SCR_DMA_TX_EN_MASK (1 << SCR_DMA_TX_EN_OFFSET) +#define SCR_DMA_TX_EN (1 << SCR_DMA_TX_EN_OFFSET) +#define SCR_VAL_OFFSET 5 +#define SCR_VAL_MASK (1 << SCR_VAL_OFFSET) +#define SCR_VAL_CLEAR (1 << SCR_VAL_OFFSET) +#define SCR_TXSEL_OFFSET 2 +#define SCR_TXSEL_MASK (0x7 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_OFF (0 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_RX (1 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_NORMAL (0x5 << SCR_TXSEL_OFFSET) +#define SCR_USRC_SEL_OFFSET 0x0 +#define SCR_USRC_SEL_MASK (0x3 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_NONE (0x0 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_RECV (0x1 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_CHIP (0x3 << SCR_USRC_SEL_OFFSET) + +#define SCR_DMA_xX_EN(tx) (tx ? SCR_DMA_TX_EN : SCR_DMA_RX_EN) + +/* SPDIF CDText control */ +#define SRCD_CD_USER_OFFSET 1 +#define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET) + +/* SPDIF Phase Configuration register */ +#define SRPC_DPLL_LOCKED (1 << 6) +#define SRPC_CLKSRC_SEL_OFFSET 7 +#define SRPC_CLKSRC_SEL_MASK (0xf << SRPC_CLKSRC_SEL_OFFSET) +#define SRPC_CLKSRC_SEL_SET(x) ((x << SRPC_CLKSRC_SEL_OFFSET) & SRPC_CLKSRC_SEL_MASK) +#define SRPC_CLKSRC_SEL_LOCKED_OFFSET1 5 +#define SRPC_CLKSRC_SEL_LOCKED_OFFSET2 2 +#define SRPC_GAINSEL_OFFSET 3 +#define SRPC_GAINSEL_MASK (0x7 << SRPC_GAINSEL_OFFSET) +#define SRPC_GAINSEL_SET(x) ((x << SRPC_GAINSEL_OFFSET) & SRPC_GAINSEL_MASK) + +#define SRPC_CLKSRC_MAX 16 + +enum spdif_gainsel { + GAINSEL_MULTI_24 = 0, + GAINSEL_MULTI_16, + GAINSEL_MULTI_12, + GAINSEL_MULTI_8, + GAINSEL_MULTI_6, + GAINSEL_MULTI_4, + GAINSEL_MULTI_3, +}; +#define GAINSEL_MULTI_MAX (GAINSEL_MULTI_3 + 1) +#define SPDIF_DEFAULT_GAINSEL GAINSEL_MULTI_8 + +/* SPDIF interrupt mask define */ +#define INT_DPLL_LOCKED (1 << 20) +#define INT_TXFIFO_UNOV (1 << 19) +#define INT_TXFIFO_RESYNC (1 << 18) +#define INT_CNEW (1 << 17) +#define INT_VAL_NOGOOD (1 << 16) +#define INT_SYM_ERR (1 << 15) +#define INT_BIT_ERR (1 << 14) +#define INT_URX_FUL (1 << 10) +#define INT_URX_OV (1 << 9) +#define INT_QRX_FUL (1 << 8) +#define INT_QRX_OV (1 << 7) +#define INT_UQ_SYNC (1 << 6) +#define INT_UQ_ERR (1 << 5) +#define INT_RXFIFO_UNOV (1 << 4) +#define INT_RXFIFO_RESYNC (1 << 3) +#define INT_LOSS_LOCK (1 << 2) +#define INT_TX_EM (1 << 1) +#define INT_RXFIFO_FUL (1 << 0) + +/* SPDIF Clock register */ +#define STC_SYSCLK_DF_OFFSET 11 +#define STC_SYSCLK_DF_MASK (0x1ff << STC_SYSCLK_DF_OFFSET) +#define STC_SYSCLK_DF(x) ((((x) - 1) << STC_SYSCLK_DF_OFFSET) & STC_SYSCLK_DF_MASK) +#define STC_TXCLK_SRC_OFFSET 8 +#define STC_TXCLK_SRC_MASK (0x7 << STC_TXCLK_SRC_OFFSET) +#define STC_TXCLK_SRC_SET(x) ((x << STC_TXCLK_SRC_OFFSET) & STC_TXCLK_SRC_MASK) +#define STC_TXCLK_ALL_EN_OFFSET 7 +#define STC_TXCLK_ALL_EN_MASK (1 << STC_TXCLK_ALL_EN_OFFSET) +#define STC_TXCLK_ALL_EN (1 << STC_TXCLK_ALL_EN_OFFSET) +#define STC_TXCLK_DF_OFFSET 0 +#define STC_TXCLK_DF_MASK (0x7ff << STC_TXCLK_DF_OFFSET) +#define STC_TXCLK_DF(x) ((((x) - 1) << STC_TXCLK_DF_OFFSET) & STC_TXCLK_DF_MASK) +#define STC_TXCLK_SRC_MAX 8 + +#define STC_TXCLK_SPDIF_ROOT 1 + +/* SPDIF tx rate */ +enum spdif_txrate { + SPDIF_TXRATE_32000 = 0, + SPDIF_TXRATE_44100, + SPDIF_TXRATE_48000, + SPDIF_TXRATE_96000, + SPDIF_TXRATE_192000, +}; +#define SPDIF_TXRATE_MAX (SPDIF_TXRATE_192000 + 1) + + +#define SPDIF_CSTATUS_BYTE 6 +#define SPDIF_UBITS_SIZE 96 +#define SPDIF_QSUB_SIZE (SPDIF_UBITS_SIZE / 8) + + +#define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + +#define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_96000) + +#define FSL_SPDIF_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +#define FSL_SPDIF_FORMATS_CAPTURE (SNDRV_PCM_FMTBIT_S24_LE) + +#endif /* _FSL_SPDIF_DAI_H */ diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c new file mode 100644 index 000000000..d6a1573b4 --- /dev/null +++ b/sound/soc/fsl/fsl_ssi.c @@ -0,0 +1,1712 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver +// +// Author: Timur Tabi +// +// Copyright 2007-2010 Freescale Semiconductor, Inc. +// +// Some notes why imx-pcm-fiq is used instead of DMA on some boards: +// +// The i.MX SSI core has some nasty limitations in AC97 mode. While most +// sane processor vendors have a FIFO per AC97 slot, the i.MX has only +// one FIFO which combines all valid receive slots. We cannot even select +// which slots we want to receive. The WM9712 with which this driver +// was developed with always sends GPIO status data in slot 12 which +// we receive in our (PCM-) data stream. The only chance we have is to +// manually skip this data in the FIQ handler. With sampling rates different +// from 48000Hz not every frame has valid receive data, so the ratio +// between pcm data and GPIO status data changes. Our FIQ handler is not +// able to handle this, hence this driver only works with 48000Hz sampling +// rate. +// Reading and writing AC97 registers is another challenge. The core +// provides us status bits when the read register is updated with *another* +// value. When we read the same register two times (and the register still +// contains the same value) these status bits are not set. We work +// around this by not polling these bits but only wait a fixed delay. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "fsl_ssi.h" +#include "imx-pcm.h" + +/* Define RX and TX to index ssi->regvals array; Can be 0 or 1 only */ +#define RX 0 +#define TX 1 + +/** + * FSLSSI_I2S_FORMATS: audio formats supported by the SSI + * + * The SSI has a limitation in that the samples must be in the same byte + * order as the host CPU. This is because when multiple bytes are written + * to the STX register, the bytes and bits must be written in the same + * order. The STX is a shift register, so all the bits need to be aligned + * (bit-endianness must match byte-endianness). Processors typically write + * the bits within a byte in the same order that the bytes of a word are + * written in. So if the host CPU is big-endian, then only big-endian + * samples will be written to STX properly. + */ +#ifdef __BIG_ENDIAN +#define FSLSSI_I2S_FORMATS \ + (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_BE) +#else +#define FSLSSI_I2S_FORMATS \ + (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) +#endif + +/* + * In AC97 mode, TXDIR bit is forced to 0 and TFDIR bit is forced to 1: + * - SSI inputs external bit clock and outputs frame sync clock -- CBM_CFS + * - Also have NB_NF to mark these two clocks will not be inverted + */ +#define FSLSSI_AC97_DAIFMT \ + (SND_SOC_DAIFMT_AC97 | \ + SND_SOC_DAIFMT_CBM_CFS | \ + SND_SOC_DAIFMT_NB_NF) + +#define FSLSSI_SIER_DBG_RX_FLAGS \ + (SSI_SIER_RFF0_EN | \ + SSI_SIER_RLS_EN | \ + SSI_SIER_RFS_EN | \ + SSI_SIER_ROE0_EN | \ + SSI_SIER_RFRC_EN) +#define FSLSSI_SIER_DBG_TX_FLAGS \ + (SSI_SIER_TFE0_EN | \ + SSI_SIER_TLS_EN | \ + SSI_SIER_TFS_EN | \ + SSI_SIER_TUE0_EN | \ + SSI_SIER_TFRC_EN) + +enum fsl_ssi_type { + FSL_SSI_MCP8610, + FSL_SSI_MX21, + FSL_SSI_MX35, + FSL_SSI_MX51, +}; + +struct fsl_ssi_regvals { + u32 sier; + u32 srcr; + u32 stcr; + u32 scr; +}; + +static bool fsl_ssi_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SSI_SACCEN: + case REG_SSI_SACCDIS: + return false; + default: + return true; + } +} + +static bool fsl_ssi_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SSI_STX0: + case REG_SSI_STX1: + case REG_SSI_SRX0: + case REG_SSI_SRX1: + case REG_SSI_SISR: + case REG_SSI_SFCSR: + case REG_SSI_SACNT: + case REG_SSI_SACADD: + case REG_SSI_SACDAT: + case REG_SSI_SATAG: + case REG_SSI_SACCST: + case REG_SSI_SOR: + return true; + default: + return false; + } +} + +static bool fsl_ssi_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SSI_SRX0: + case REG_SSI_SRX1: + case REG_SSI_SISR: + case REG_SSI_SACADD: + case REG_SSI_SACDAT: + case REG_SSI_SATAG: + return true; + default: + return false; + } +} + +static bool fsl_ssi_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SSI_SRX0: + case REG_SSI_SRX1: + case REG_SSI_SACCST: + return false; + default: + return true; + } +} + +static const struct regmap_config fsl_ssi_regconfig = { + .max_register = REG_SSI_SACCDIS, + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .val_format_endian = REGMAP_ENDIAN_NATIVE, + .num_reg_defaults_raw = REG_SSI_SACCDIS / sizeof(uint32_t) + 1, + .readable_reg = fsl_ssi_readable_reg, + .volatile_reg = fsl_ssi_volatile_reg, + .precious_reg = fsl_ssi_precious_reg, + .writeable_reg = fsl_ssi_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +struct fsl_ssi_soc_data { + bool imx; + bool imx21regs; /* imx21-class SSI - no SACC{ST,EN,DIS} regs */ + bool offline_config; + u32 sisr_write_mask; +}; + +/** + * fsl_ssi: per-SSI private data + * + * @regs: Pointer to the regmap registers + * @irq: IRQ of this SSI + * @cpu_dai_drv: CPU DAI driver for this device + * + * @dai_fmt: DAI configuration this device is currently used with + * @streams: Mask of current active streams: BIT(TX) and BIT(RX) + * @i2s_net: I2S and Network mode configurations of SCR register + * (this is the initial settings based on the DAI format) + * @synchronous: Use synchronous mode - both of TX and RX use STCK and SFCK + * @use_dma: DMA is used or FIQ with stream filter + * @use_dual_fifo: DMA with support for dual FIFO mode + * @has_ipg_clk_name: If "ipg" is in the clock name list of device tree + * @fifo_depth: Depth of the SSI FIFOs + * @slot_width: Width of each DAI slot + * @slots: Number of slots + * @regvals: Specific RX/TX register settings + * + * @clk: Clock source to access register + * @baudclk: Clock source to generate bit and frame-sync clocks + * @baudclk_streams: Active streams that are using baudclk + * + * @regcache_sfcsr: Cache sfcsr register value during suspend and resume + * @regcache_sacnt: Cache sacnt register value during suspend and resume + * + * @dma_params_tx: DMA transmit parameters + * @dma_params_rx: DMA receive parameters + * @ssi_phys: physical address of the SSI registers + * + * @fiq_params: FIQ stream filtering parameters + * + * @card_pdev: Platform_device pointer to register a sound card for PowerPC or + * to register a CODEC platform device for AC97 + * @card_name: Platform_device name to register a sound card for PowerPC or + * to register a CODEC platform device for AC97 + * @card_idx: The index of SSI to register a sound card for PowerPC or + * to register a CODEC platform device for AC97 + * + * @dbg_stats: Debugging statistics + * + * @soc: SoC specific data + * @dev: Pointer to &pdev->dev + * + * @fifo_watermark: The FIFO watermark setting. Notifies DMA when there are + * @fifo_watermark or fewer words in TX fifo or + * @fifo_watermark or more empty words in RX fifo. + * @dma_maxburst: Max number of words to transfer in one go. So far, + * this is always the same as fifo_watermark. + * + * @ac97_reg_lock: Mutex lock to serialize AC97 register access operations + */ +struct fsl_ssi { + struct regmap *regs; + int irq; + struct snd_soc_dai_driver cpu_dai_drv; + + unsigned int dai_fmt; + u8 streams; + u8 i2s_net; + bool synchronous; + bool use_dma; + bool use_dual_fifo; + bool has_ipg_clk_name; + unsigned int fifo_depth; + unsigned int slot_width; + unsigned int slots; + struct fsl_ssi_regvals regvals[2]; + + struct clk *clk; + struct clk *baudclk; + unsigned int baudclk_streams; + + u32 regcache_sfcsr; + u32 regcache_sacnt; + + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_dai_dma_data dma_params_rx; + dma_addr_t ssi_phys; + + struct imx_pcm_fiq_params fiq_params; + + struct platform_device *card_pdev; + char card_name[32]; + u32 card_idx; + + struct fsl_ssi_dbg dbg_stats; + + const struct fsl_ssi_soc_data *soc; + struct device *dev; + + u32 fifo_watermark; + u32 dma_maxburst; + + struct mutex ac97_reg_lock; +}; + +/* + * SoC specific data + * + * Notes: + * 1) SSI in earlier SoCS has critical bits in control registers that + * cannot be changed after SSI starts running -- a software reset + * (set SSIEN to 0) is required to change their values. So adding + * an offline_config flag for these SoCs. + * 2) SDMA is available since imx35. However, imx35 does not support + * DMA bits changing when SSI is running, so set offline_config. + * 3) imx51 and later versions support register configurations when + * SSI is running (SSIEN); For these versions, DMA needs to be + * configured before SSI sends DMA request to avoid an undefined + * DMA request on the SDMA side. + */ + +static struct fsl_ssi_soc_data fsl_ssi_mpc8610 = { + .imx = false, + .offline_config = true, + .sisr_write_mask = SSI_SISR_RFRC | SSI_SISR_TFRC | + SSI_SISR_ROE0 | SSI_SISR_ROE1 | + SSI_SISR_TUE0 | SSI_SISR_TUE1, +}; + +static struct fsl_ssi_soc_data fsl_ssi_imx21 = { + .imx = true, + .imx21regs = true, + .offline_config = true, + .sisr_write_mask = 0, +}; + +static struct fsl_ssi_soc_data fsl_ssi_imx35 = { + .imx = true, + .offline_config = true, + .sisr_write_mask = SSI_SISR_RFRC | SSI_SISR_TFRC | + SSI_SISR_ROE0 | SSI_SISR_ROE1 | + SSI_SISR_TUE0 | SSI_SISR_TUE1, +}; + +static struct fsl_ssi_soc_data fsl_ssi_imx51 = { + .imx = true, + .offline_config = false, + .sisr_write_mask = SSI_SISR_ROE0 | SSI_SISR_ROE1 | + SSI_SISR_TUE0 | SSI_SISR_TUE1, +}; + +static const struct of_device_id fsl_ssi_ids[] = { + { .compatible = "fsl,mpc8610-ssi", .data = &fsl_ssi_mpc8610 }, + { .compatible = "fsl,imx51-ssi", .data = &fsl_ssi_imx51 }, + { .compatible = "fsl,imx35-ssi", .data = &fsl_ssi_imx35 }, + { .compatible = "fsl,imx21-ssi", .data = &fsl_ssi_imx21 }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_ssi_ids); + +static bool fsl_ssi_is_ac97(struct fsl_ssi *ssi) +{ + return (ssi->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_AC97; +} + +static bool fsl_ssi_is_i2s_master(struct fsl_ssi *ssi) +{ + return (ssi->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBS_CFS; +} + +static bool fsl_ssi_is_i2s_cbm_cfs(struct fsl_ssi *ssi) +{ + return (ssi->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBM_CFS; +} + +/** + * Interrupt handler to gather states + */ +static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) +{ + struct fsl_ssi *ssi = dev_id; + struct regmap *regs = ssi->regs; + u32 sisr, sisr2; + + regmap_read(regs, REG_SSI_SISR, &sisr); + + sisr2 = sisr & ssi->soc->sisr_write_mask; + /* Clear the bits that we set */ + if (sisr2) + regmap_write(regs, REG_SSI_SISR, sisr2); + + fsl_ssi_dbg_isr(&ssi->dbg_stats, sisr); + + return IRQ_HANDLED; +} + +/** + * Set SCR, SIER, STCR and SRCR registers with cached values in regvals + * + * Notes: + * 1) For offline_config SoCs, enable all necessary bits of both streams + * when 1st stream starts, even if the opposite stream will not start + * 2) It also clears FIFO before setting regvals; SOR is safe to set online + */ +static void fsl_ssi_config_enable(struct fsl_ssi *ssi, bool tx) +{ + struct fsl_ssi_regvals *vals = ssi->regvals; + int dir = tx ? TX : RX; + u32 sier, srcr, stcr; + + /* Clear dirty data in the FIFO; It also prevents channel slipping */ + regmap_update_bits(ssi->regs, REG_SSI_SOR, + SSI_SOR_xX_CLR(tx), SSI_SOR_xX_CLR(tx)); + + /* + * On offline_config SoCs, SxCR and SIER are already configured when + * the previous stream started. So skip all SxCR and SIER settings + * to prevent online reconfigurations, then jump to set SCR directly + */ + if (ssi->soc->offline_config && ssi->streams) + goto enable_scr; + + if (ssi->soc->offline_config) { + /* + * Online reconfiguration not supported, so enable all bits for + * both streams at once to avoid necessity of reconfigurations + */ + srcr = vals[RX].srcr | vals[TX].srcr; + stcr = vals[RX].stcr | vals[TX].stcr; + sier = vals[RX].sier | vals[TX].sier; + } else { + /* Otherwise, only set bits for the current stream */ + srcr = vals[dir].srcr; + stcr = vals[dir].stcr; + sier = vals[dir].sier; + } + + /* Configure SRCR, STCR and SIER at once */ + regmap_update_bits(ssi->regs, REG_SSI_SRCR, srcr, srcr); + regmap_update_bits(ssi->regs, REG_SSI_STCR, stcr, stcr); + regmap_update_bits(ssi->regs, REG_SSI_SIER, sier, sier); + +enable_scr: + /* + * Start DMA before setting TE to avoid FIFO underrun + * which may cause a channel slip or a channel swap + * + * TODO: FIQ cases might also need this upon testing + */ + if (ssi->use_dma && tx) { + int try = 100; + u32 sfcsr; + + /* Enable SSI first to send TX DMA request */ + regmap_update_bits(ssi->regs, REG_SSI_SCR, + SSI_SCR_SSIEN, SSI_SCR_SSIEN); + + /* Busy wait until TX FIFO not empty -- DMA working */ + do { + regmap_read(ssi->regs, REG_SSI_SFCSR, &sfcsr); + if (SSI_SFCSR_TFCNT0(sfcsr)) + break; + } while (--try); + + /* FIFO still empty -- something might be wrong */ + if (!SSI_SFCSR_TFCNT0(sfcsr)) + dev_warn(ssi->dev, "Timeout waiting TX FIFO filling\n"); + } + /* Enable all remaining bits in SCR */ + regmap_update_bits(ssi->regs, REG_SSI_SCR, + vals[dir].scr, vals[dir].scr); + + /* Log the enabled stream to the mask */ + ssi->streams |= BIT(dir); +} + +/** + * Exclude bits that are used by the opposite stream + * + * When both streams are active, disabling some bits for the current stream + * might break the other stream if these bits are used by it. + * + * @vals : regvals of the current stream + * @avals: regvals of the opposite stream + * @aactive: active state of the opposite stream + * + * 1) XOR vals and avals to get the differences if the other stream is active; + * Otherwise, return current vals if the other stream is not active + * 2) AND the result of 1) with the current vals + */ +#define _ssi_xor_shared_bits(vals, avals, aactive) \ + ((vals) ^ ((avals) * (aactive))) + +#define ssi_excl_shared_bits(vals, avals, aactive) \ + ((vals) & _ssi_xor_shared_bits(vals, avals, aactive)) + +/** + * Unset SCR, SIER, STCR and SRCR registers with cached values in regvals + * + * Notes: + * 1) For offline_config SoCs, to avoid online reconfigurations, disable all + * bits of both streams at once when the last stream is abort to end + * 2) It also clears FIFO after unsetting regvals; SOR is safe to set online + */ +static void fsl_ssi_config_disable(struct fsl_ssi *ssi, bool tx) +{ + struct fsl_ssi_regvals *vals, *avals; + u32 sier, srcr, stcr, scr; + int adir = tx ? RX : TX; + int dir = tx ? TX : RX; + bool aactive; + + /* Check if the opposite stream is active */ + aactive = ssi->streams & BIT(adir); + + vals = &ssi->regvals[dir]; + + /* Get regvals of the opposite stream to keep opposite stream safe */ + avals = &ssi->regvals[adir]; + + /* + * To keep the other stream safe, exclude shared bits between + * both streams, and get safe bits to disable current stream + */ + scr = ssi_excl_shared_bits(vals->scr, avals->scr, aactive); + + /* Disable safe bits of SCR register for the current stream */ + regmap_update_bits(ssi->regs, REG_SSI_SCR, scr, 0); + + /* Log the disabled stream to the mask */ + ssi->streams &= ~BIT(dir); + + /* + * On offline_config SoCs, if the other stream is active, skip + * SxCR and SIER settings to prevent online reconfigurations + */ + if (ssi->soc->offline_config && aactive) + goto fifo_clear; + + if (ssi->soc->offline_config) { + /* Now there is only current stream active, disable all bits */ + srcr = vals->srcr | avals->srcr; + stcr = vals->stcr | avals->stcr; + sier = vals->sier | avals->sier; + } else { + /* + * To keep the other stream safe, exclude shared bits between + * both streams, and get safe bits to disable current stream + */ + sier = ssi_excl_shared_bits(vals->sier, avals->sier, aactive); + srcr = ssi_excl_shared_bits(vals->srcr, avals->srcr, aactive); + stcr = ssi_excl_shared_bits(vals->stcr, avals->stcr, aactive); + } + + /* Clear configurations of SRCR, STCR and SIER at once */ + regmap_update_bits(ssi->regs, REG_SSI_SRCR, srcr, 0); + regmap_update_bits(ssi->regs, REG_SSI_STCR, stcr, 0); + regmap_update_bits(ssi->regs, REG_SSI_SIER, sier, 0); + +fifo_clear: + /* Clear remaining data in the FIFO */ + regmap_update_bits(ssi->regs, REG_SSI_SOR, + SSI_SOR_xX_CLR(tx), SSI_SOR_xX_CLR(tx)); +} + +static void fsl_ssi_tx_ac97_saccst_setup(struct fsl_ssi *ssi) +{ + struct regmap *regs = ssi->regs; + + /* no SACC{ST,EN,DIS} regs on imx21-class SSI */ + if (!ssi->soc->imx21regs) { + /* Disable all channel slots */ + regmap_write(regs, REG_SSI_SACCDIS, 0xff); + /* Enable slots 3 & 4 -- PCM Playback Left & Right channels */ + regmap_write(regs, REG_SSI_SACCEN, 0x300); + } +} + +/** + * Cache critical bits of SIER, SRCR, STCR and SCR to later set them safely + */ +static void fsl_ssi_setup_regvals(struct fsl_ssi *ssi) +{ + struct fsl_ssi_regvals *vals = ssi->regvals; + + vals[RX].sier = SSI_SIER_RFF0_EN | FSLSSI_SIER_DBG_RX_FLAGS; + vals[RX].srcr = SSI_SRCR_RFEN0; + vals[RX].scr = SSI_SCR_SSIEN | SSI_SCR_RE; + vals[TX].sier = SSI_SIER_TFE0_EN | FSLSSI_SIER_DBG_TX_FLAGS; + vals[TX].stcr = SSI_STCR_TFEN0; + vals[TX].scr = SSI_SCR_SSIEN | SSI_SCR_TE; + + /* AC97 has already enabled SSIEN, RE and TE, so ignore them */ + if (fsl_ssi_is_ac97(ssi)) + vals[RX].scr = vals[TX].scr = 0; + + if (ssi->use_dual_fifo) { + vals[RX].srcr |= SSI_SRCR_RFEN1; + vals[TX].stcr |= SSI_STCR_TFEN1; + } + + if (ssi->use_dma) { + vals[RX].sier |= SSI_SIER_RDMAE; + vals[TX].sier |= SSI_SIER_TDMAE; + } else { + vals[RX].sier |= SSI_SIER_RIE; + vals[TX].sier |= SSI_SIER_TIE; + } +} + +static void fsl_ssi_setup_ac97(struct fsl_ssi *ssi) +{ + struct regmap *regs = ssi->regs; + + /* Setup the clock control register */ + regmap_write(regs, REG_SSI_STCCR, SSI_SxCCR_WL(17) | SSI_SxCCR_DC(13)); + regmap_write(regs, REG_SSI_SRCCR, SSI_SxCCR_WL(17) | SSI_SxCCR_DC(13)); + + /* Enable AC97 mode and startup the SSI */ + regmap_write(regs, REG_SSI_SACNT, SSI_SACNT_AC97EN | SSI_SACNT_FV); + + /* AC97 has to communicate with codec before starting a stream */ + regmap_update_bits(regs, REG_SSI_SCR, + SSI_SCR_SSIEN | SSI_SCR_TE | SSI_SCR_RE, + SSI_SCR_SSIEN | SSI_SCR_TE | SSI_SCR_RE); + + regmap_write(regs, REG_SSI_SOR, SSI_SOR_WAIT(3)); +} + +static int fsl_ssi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai); + int ret; + + ret = clk_prepare_enable(ssi->clk); + if (ret) + return ret; + + /* + * When using dual fifo mode, it is safer to ensure an even period + * size. If appearing to an odd number while DMA always starts its + * task from fifo0, fifo1 would be neglected at the end of each + * period. But SSI would still access fifo1 with an invalid data. + */ + if (ssi->use_dual_fifo) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2); + + return 0; +} + +static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + clk_disable_unprepare(ssi->clk); +} + +/** + * Configure Digital Audio Interface bit clock + * + * Note: This function can be only called when using SSI as DAI master + * + * Quick instruction for parameters: + * freq: Output BCLK frequency = samplerate * slots * slot_width + * (In 2-channel I2S Master mode, slot_width is fixed 32) + */ +static int fsl_ssi_set_bclk(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + struct snd_pcm_hw_params *hw_params) +{ + bool tx2, tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai); + struct regmap *regs = ssi->regs; + u32 pm = 999, div2, psr, stccr, mask, afreq, factor, i; + unsigned long clkrate, baudrate, tmprate; + unsigned int channels = params_channels(hw_params); + unsigned int slot_width = params_width(hw_params); + unsigned int slots = 2; + u64 sub, savesub = 100000; + unsigned int freq; + bool baudclk_is_used; + int ret; + + /* Override slots and slot_width if being specifically set... */ + if (ssi->slots) + slots = ssi->slots; + if (ssi->slot_width) + slot_width = ssi->slot_width; + + /* ...but force 32 bits for stereo audio using I2S Master Mode */ + if (channels == 2 && + (ssi->i2s_net & SSI_SCR_I2S_MODE_MASK) == SSI_SCR_I2S_MODE_MASTER) + slot_width = 32; + + /* Generate bit clock based on the slot number and slot width */ + freq = slots * slot_width * params_rate(hw_params); + + /* Don't apply it to any non-baudclk circumstance */ + if (IS_ERR(ssi->baudclk)) + return -EINVAL; + + /* + * Hardware limitation: The bclk rate must be + * never greater than 1/5 IPG clock rate + */ + if (freq * 5 > clk_get_rate(ssi->clk)) { + dev_err(dai->dev, "bitclk > ipgclk / 5\n"); + return -EINVAL; + } + + baudclk_is_used = ssi->baudclk_streams & ~(BIT(substream->stream)); + + /* It should be already enough to divide clock by setting pm alone */ + psr = 0; + div2 = 0; + + factor = (div2 + 1) * (7 * psr + 1) * 2; + + for (i = 0; i < 255; i++) { + tmprate = freq * factor * (i + 1); + + if (baudclk_is_used) + clkrate = clk_get_rate(ssi->baudclk); + else + clkrate = clk_round_rate(ssi->baudclk, tmprate); + + clkrate /= factor; + afreq = clkrate / (i + 1); + + if (freq == afreq) + sub = 0; + else if (freq / afreq == 1) + sub = freq - afreq; + else if (afreq / freq == 1) + sub = afreq - freq; + else + continue; + + /* Calculate the fraction */ + sub *= 100000; + do_div(sub, freq); + + if (sub < savesub && !(i == 0 && psr == 0 && div2 == 0)) { + baudrate = tmprate; + savesub = sub; + pm = i; + } + + /* We are lucky */ + if (savesub == 0) + break; + } + + /* No proper pm found if it is still remaining the initial value */ + if (pm == 999) { + dev_err(dai->dev, "failed to handle the required sysclk\n"); + return -EINVAL; + } + + stccr = SSI_SxCCR_PM(pm + 1) | (div2 ? SSI_SxCCR_DIV2 : 0) | + (psr ? SSI_SxCCR_PSR : 0); + mask = SSI_SxCCR_PM_MASK | SSI_SxCCR_DIV2 | SSI_SxCCR_PSR; + + /* STCCR is used for RX in synchronous mode */ + tx2 = tx || ssi->synchronous; + regmap_update_bits(regs, REG_SSI_SxCCR(tx2), mask, stccr); + + if (!baudclk_is_used) { + ret = clk_set_rate(ssi->baudclk, baudrate); + if (ret) { + dev_err(dai->dev, "failed to set baudclk rate\n"); + return -EINVAL; + } + } + + return 0; +} + +/** + * Configure SSI based on PCM hardware parameters + * + * Notes: + * 1) SxCCR.WL bits are critical bits that require SSI to be temporarily + * disabled on offline_config SoCs. Even for online configurable SoCs + * running in synchronous mode (both TX and RX use STCCR), it is not + * safe to re-configure them when both two streams start running. + * 2) SxCCR.PM, SxCCR.DIV2 and SxCCR.PSR bits will be configured in the + * fsl_ssi_set_bclk() if SSI is the DAI clock master. + */ +static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, + struct snd_soc_dai *dai) +{ + bool tx2, tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai); + struct regmap *regs = ssi->regs; + unsigned int channels = params_channels(hw_params); + unsigned int sample_size = params_width(hw_params); + u32 wl = SSI_SxCCR_WL(sample_size); + int ret; + + if (fsl_ssi_is_i2s_master(ssi)) { + ret = fsl_ssi_set_bclk(substream, dai, hw_params); + if (ret) + return ret; + + /* Do not enable the clock if it is already enabled */ + if (!(ssi->baudclk_streams & BIT(substream->stream))) { + ret = clk_prepare_enable(ssi->baudclk); + if (ret) + return ret; + + ssi->baudclk_streams |= BIT(substream->stream); + } + } + + /* + * SSI is properly configured if it is enabled and running in + * the synchronous mode; Note that AC97 mode is an exception + * that should set separate configurations for STCCR and SRCCR + * despite running in the synchronous mode. + */ + if (ssi->streams && ssi->synchronous) + return 0; + + if (!fsl_ssi_is_ac97(ssi)) { + /* + * Keep the ssi->i2s_net intact while having a local variable + * to override settings for special use cases. Otherwise, the + * ssi->i2s_net will lose the settings for regular use cases. + */ + u8 i2s_net = ssi->i2s_net; + + /* Normal + Network mode to send 16-bit data in 32-bit frames */ + if (fsl_ssi_is_i2s_cbm_cfs(ssi) && sample_size == 16) + i2s_net = SSI_SCR_I2S_MODE_NORMAL | SSI_SCR_NET; + + /* Use Normal mode to send mono data at 1st slot of 2 slots */ + if (channels == 1) + i2s_net = SSI_SCR_I2S_MODE_NORMAL; + + regmap_update_bits(regs, REG_SSI_SCR, + SSI_SCR_I2S_NET_MASK, i2s_net); + } + + /* In synchronous mode, the SSI uses STCCR for capture */ + tx2 = tx || ssi->synchronous; + regmap_update_bits(regs, REG_SSI_SxCCR(tx2), SSI_SxCCR_WL_MASK, wl); + + return 0; +} + +static int fsl_ssi_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + if (fsl_ssi_is_i2s_master(ssi) && + ssi->baudclk_streams & BIT(substream->stream)) { + clk_disable_unprepare(ssi->baudclk); + ssi->baudclk_streams &= ~BIT(substream->stream); + } + + return 0; +} + +static int _fsl_ssi_set_dai_fmt(struct fsl_ssi *ssi, unsigned int fmt) +{ + u32 strcr = 0, scr = 0, stcr, srcr, mask; + unsigned int slots; + + ssi->dai_fmt = fmt; + + /* Synchronize frame sync clock for TE to avoid data slipping */ + scr |= SSI_SCR_SYNC_TX_FS; + + /* Set to default shifting settings: LSB_ALIGNED */ + strcr |= SSI_STCR_TXBIT0; + + /* Use Network mode as default */ + ssi->i2s_net = SSI_SCR_NET; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + if (IS_ERR(ssi->baudclk)) { + dev_err(ssi->dev, + "missing baudclk for master mode\n"); + return -EINVAL; + } + /* fall through */ + case SND_SOC_DAIFMT_CBM_CFS: + ssi->i2s_net |= SSI_SCR_I2S_MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + ssi->i2s_net |= SSI_SCR_I2S_MODE_SLAVE; + break; + default: + return -EINVAL; + } + + slots = ssi->slots ? : 2; + regmap_update_bits(ssi->regs, REG_SSI_STCCR, + SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots)); + regmap_update_bits(ssi->regs, REG_SSI_SRCCR, + SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots)); + + /* Data on rising edge of bclk, frame low, 1clk before data */ + strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP | SSI_STCR_TEFS; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* Data on rising edge of bclk, frame high */ + strcr |= SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_DSP_A: + /* Data on rising edge of bclk, frame high, 1clk before data */ + strcr |= SSI_STCR_TFSL | SSI_STCR_TSCKP | SSI_STCR_TEFS; + break; + case SND_SOC_DAIFMT_DSP_B: + /* Data on rising edge of bclk, frame high */ + strcr |= SSI_STCR_TFSL | SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_AC97: + /* Data on falling edge of bclk, frame high, 1clk before data */ + strcr |= SSI_STCR_TEFS; + break; + default: + return -EINVAL; + } + + scr |= ssi->i2s_net; + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + strcr ^= SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + strcr ^= SSI_STCR_TFSI; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + strcr ^= SSI_STCR_TSCKP; + strcr ^= SSI_STCR_TFSI; + break; + default: + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Output bit and frame sync clocks */ + strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR; + scr |= SSI_SCR_SYS_CLK_EN; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* Input bit or frame sync clocks */ + break; + case SND_SOC_DAIFMT_CBM_CFS: + /* Input bit clock but output frame sync clock */ + strcr |= SSI_STCR_TFDIR; + break; + default: + return -EINVAL; + } + + stcr = strcr; + srcr = strcr; + + /* Set SYN mode and clear RXDIR bit when using SYN or AC97 mode */ + if (ssi->synchronous || fsl_ssi_is_ac97(ssi)) { + srcr &= ~SSI_SRCR_RXDIR; + scr |= SSI_SCR_SYN; + } + + mask = SSI_STCR_TFDIR | SSI_STCR_TXDIR | SSI_STCR_TSCKP | + SSI_STCR_TFSL | SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0; + + regmap_update_bits(ssi->regs, REG_SSI_STCR, mask, stcr); + regmap_update_bits(ssi->regs, REG_SSI_SRCR, mask, srcr); + + mask = SSI_SCR_SYNC_TX_FS | SSI_SCR_I2S_MODE_MASK | + SSI_SCR_SYS_CLK_EN | SSI_SCR_SYN; + regmap_update_bits(ssi->regs, REG_SSI_SCR, mask, scr); + + return 0; +} + +/** + * Configure Digital Audio Interface (DAI) Format + */ +static int fsl_ssi_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai); + + /* AC97 configured DAIFMT earlier in the probe() */ + if (fsl_ssi_is_ac97(ssi)) + return 0; + + return _fsl_ssi_set_dai_fmt(ssi, fmt); +} + +/** + * Set TDM slot number and slot width + */ +static int fsl_ssi_set_dai_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai); + struct regmap *regs = ssi->regs; + u32 val; + + /* The word length should be 8, 10, 12, 16, 18, 20, 22 or 24 */ + if (slot_width & 1 || slot_width < 8 || slot_width > 24) { + dev_err(dai->dev, "invalid slot width: %d\n", slot_width); + return -EINVAL; + } + + /* The slot number should be >= 2 if using Network mode or I2S mode */ + if (ssi->i2s_net && slots < 2) { + dev_err(dai->dev, "slot number should be >= 2 in I2S or NET\n"); + return -EINVAL; + } + + regmap_update_bits(regs, REG_SSI_STCCR, + SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots)); + regmap_update_bits(regs, REG_SSI_SRCCR, + SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots)); + + /* Save the SCR register value */ + regmap_read(regs, REG_SSI_SCR, &val); + /* Temporarily enable SSI to allow SxMSKs to be configurable */ + regmap_update_bits(regs, REG_SSI_SCR, SSI_SCR_SSIEN, SSI_SCR_SSIEN); + + regmap_write(regs, REG_SSI_STMSK, ~tx_mask); + regmap_write(regs, REG_SSI_SRMSK, ~rx_mask); + + /* Restore the value of SSIEN bit */ + regmap_update_bits(regs, REG_SSI_SCR, SSI_SCR_SSIEN, val); + + ssi->slot_width = slot_width; + ssi->slots = slots; + + return 0; +} + +/** + * Start or stop SSI and corresponding DMA transaction. + * + * The DMA channel is in external master start and pause mode, which + * means the SSI completely controls the flow of data. + */ +static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * SACCST might be modified via AC Link by a CODEC if it sends + * extra bits in their SLOTREQ requests, which'll accidentally + * send valid data to slots other than normal playback slots. + * + * To be safe, configure SACCST right before TX starts. + */ + if (tx && fsl_ssi_is_ac97(ssi)) + fsl_ssi_tx_ac97_saccst_setup(ssi); + fsl_ssi_config_enable(ssi, tx); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fsl_ssi_config_disable(ssi, tx); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_ssi_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai); + + if (ssi->soc->imx && ssi->use_dma) + snd_soc_dai_init_dma_data(dai, &ssi->dma_params_tx, + &ssi->dma_params_rx); + + return 0; +} + +static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { + .startup = fsl_ssi_startup, + .shutdown = fsl_ssi_shutdown, + .hw_params = fsl_ssi_hw_params, + .hw_free = fsl_ssi_hw_free, + .set_fmt = fsl_ssi_set_dai_fmt, + .set_tdm_slot = fsl_ssi_set_dai_tdm_slot, + .trigger = fsl_ssi_trigger, +}; + +static struct snd_soc_dai_driver fsl_ssi_dai_template = { + .probe = fsl_ssi_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 32, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = FSLSSI_I2S_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 32, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = FSLSSI_I2S_FORMATS, + }, + .ops = &fsl_ssi_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_ssi_component = { + .name = "fsl-ssi", +}; + +static struct snd_soc_dai_driver fsl_ssi_ac97_dai = { + .bus_control = true, + .symmetric_channels = 1, + .probe = fsl_ssi_dai_probe, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S20, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + /* 16-bit capture is broken (errata ERR003778) */ + .formats = SNDRV_PCM_FMTBIT_S20, + }, + .ops = &fsl_ssi_dai_ops, +}; + +static struct fsl_ssi *fsl_ac97_data; + +static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct regmap *regs = fsl_ac97_data->regs; + unsigned int lreg; + unsigned int lval; + int ret; + + if (reg > 0x7f) + return; + + mutex_lock(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 write clk_prepare_enable failed: %d\n", + ret); + goto ret_unlock; + } + + lreg = reg << 12; + regmap_write(regs, REG_SSI_SACADD, lreg); + + lval = val << 4; + regmap_write(regs, REG_SSI_SACDAT, lval); + + regmap_update_bits(regs, REG_SSI_SACNT, + SSI_SACNT_RDWR_MASK, SSI_SACNT_WR); + udelay(100); + + clk_disable_unprepare(fsl_ac97_data->clk); + +ret_unlock: + mutex_unlock(&fsl_ac97_data->ac97_reg_lock); +} + +static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct regmap *regs = fsl_ac97_data->regs; + unsigned short val = 0; + u32 reg_val; + unsigned int lreg; + int ret; + + mutex_lock(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 read clk_prepare_enable failed: %d\n", ret); + goto ret_unlock; + } + + lreg = (reg & 0x7f) << 12; + regmap_write(regs, REG_SSI_SACADD, lreg); + regmap_update_bits(regs, REG_SSI_SACNT, + SSI_SACNT_RDWR_MASK, SSI_SACNT_RD); + + udelay(100); + + regmap_read(regs, REG_SSI_SACDAT, ®_val); + val = (reg_val >> 4) & 0xffff; + + clk_disable_unprepare(fsl_ac97_data->clk); + +ret_unlock: + mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + return val; +} + +static struct snd_ac97_bus_ops fsl_ssi_ac97_ops = { + .read = fsl_ssi_ac97_read, + .write = fsl_ssi_ac97_write, +}; + +/** + * Initialize SSI registers + */ +static int fsl_ssi_hw_init(struct fsl_ssi *ssi) +{ + u32 wm = ssi->fifo_watermark; + + /* Initialize regvals */ + fsl_ssi_setup_regvals(ssi); + + /* Set watermarks */ + regmap_write(ssi->regs, REG_SSI_SFCSR, + SSI_SFCSR_TFWM0(wm) | SSI_SFCSR_RFWM0(wm) | + SSI_SFCSR_TFWM1(wm) | SSI_SFCSR_RFWM1(wm)); + + /* Enable Dual FIFO mode */ + if (ssi->use_dual_fifo) + regmap_update_bits(ssi->regs, REG_SSI_SCR, + SSI_SCR_TCH_EN, SSI_SCR_TCH_EN); + + /* AC97 should start earlier to communicate with CODECs */ + if (fsl_ssi_is_ac97(ssi)) { + _fsl_ssi_set_dai_fmt(ssi, ssi->dai_fmt); + fsl_ssi_setup_ac97(ssi); + } + + return 0; +} + +/** + * Clear SSI registers + */ +static void fsl_ssi_hw_clean(struct fsl_ssi *ssi) +{ + /* Disable registers for AC97 */ + if (fsl_ssi_is_ac97(ssi)) { + /* Disable TE and RE bits first */ + regmap_update_bits(ssi->regs, REG_SSI_SCR, + SSI_SCR_TE | SSI_SCR_RE, 0); + /* Disable AC97 mode */ + regmap_write(ssi->regs, REG_SSI_SACNT, 0); + /* Unset WAIT bits */ + regmap_write(ssi->regs, REG_SSI_SOR, 0); + /* Disable SSI -- software reset */ + regmap_update_bits(ssi->regs, REG_SSI_SCR, SSI_SCR_SSIEN, 0); + } +} +/** + * Make every character in a string lower-case + */ +static void make_lowercase(char *s) +{ + if (!s) + return; + for (; *s; s++) + *s = tolower(*s); +} + +static int fsl_ssi_imx_probe(struct platform_device *pdev, + struct fsl_ssi *ssi, void __iomem *iomem) +{ + struct device *dev = &pdev->dev; + int ret; + + /* Backward compatible for a DT without ipg clock name assigned */ + if (ssi->has_ipg_clk_name) + ssi->clk = devm_clk_get(dev, "ipg"); + else + ssi->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ssi->clk)) { + ret = PTR_ERR(ssi->clk); + dev_err(dev, "failed to get clock: %d\n", ret); + return ret; + } + + /* Enable the clock since regmap will not handle it in this case */ + if (!ssi->has_ipg_clk_name) { + ret = clk_prepare_enable(ssi->clk); + if (ret) { + dev_err(dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + } + + /* Do not error out for slave cases that live without a baud clock */ + ssi->baudclk = devm_clk_get(dev, "baud"); + if (IS_ERR(ssi->baudclk)) + dev_dbg(dev, "failed to get baud clock: %ld\n", + PTR_ERR(ssi->baudclk)); + + ssi->dma_params_tx.maxburst = ssi->dma_maxburst; + ssi->dma_params_rx.maxburst = ssi->dma_maxburst; + ssi->dma_params_tx.addr = ssi->ssi_phys + REG_SSI_STX0; + ssi->dma_params_rx.addr = ssi->ssi_phys + REG_SSI_SRX0; + + /* Use even numbers to avoid channel swap due to SDMA script design */ + if (ssi->use_dual_fifo) { + ssi->dma_params_tx.maxburst &= ~0x1; + ssi->dma_params_rx.maxburst &= ~0x1; + } + + if (!ssi->use_dma) { + /* + * Some boards use an incompatible codec. Use imx-fiq-pcm-audio + * to get it working, as DMA is not possible in this situation. + */ + ssi->fiq_params.irq = ssi->irq; + ssi->fiq_params.base = iomem; + ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx; + ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx; + + ret = imx_pcm_fiq_init(pdev, &ssi->fiq_params); + if (ret) + goto error_pcm; + } else { + ret = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE); + if (ret) + goto error_pcm; + } + + return 0; + +error_pcm: + if (!ssi->has_ipg_clk_name) + clk_disable_unprepare(ssi->clk); + + return ret; +} + +static void fsl_ssi_imx_clean(struct platform_device *pdev, struct fsl_ssi *ssi) +{ + if (!ssi->use_dma) + imx_pcm_fiq_exit(pdev); + if (!ssi->has_ipg_clk_name) + clk_disable_unprepare(ssi->clk); +} + +static int fsl_ssi_probe_from_dt(struct fsl_ssi *ssi) +{ + struct device *dev = ssi->dev; + struct device_node *np = dev->of_node; + const struct of_device_id *of_id; + const char *p, *sprop; + const __be32 *iprop; + u32 dmas[4]; + int ret; + + of_id = of_match_device(fsl_ssi_ids, dev); + if (!of_id || !of_id->data) + return -EINVAL; + + ssi->soc = of_id->data; + + ret = of_property_match_string(np, "clock-names", "ipg"); + /* Get error code if not found */ + ssi->has_ipg_clk_name = ret >= 0; + + /* Check if being used in AC97 mode */ + sprop = of_get_property(np, "fsl,mode", NULL); + if (sprop && !strcmp(sprop, "ac97-slave")) { + ssi->dai_fmt = FSLSSI_AC97_DAIFMT; + + ret = of_property_read_u32(np, "cell-index", &ssi->card_idx); + if (ret) { + dev_err(dev, "failed to get SSI index property\n"); + return -EINVAL; + } + strcpy(ssi->card_name, "ac97-codec"); + } else if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) { + /* + * In synchronous mode, STCK and STFS ports are used by RX + * as well. So the software should limit the sample rates, + * sample bits and channels to be symmetric. + * + * This is exclusive with FSLSSI_AC97_FORMATS as AC97 runs + * in the SSI synchronous mode however it does not have to + * limit symmetric sample rates and sample bits. + */ + ssi->synchronous = true; + } + + /* Select DMA or FIQ */ + ssi->use_dma = !of_property_read_bool(np, "fsl,fiq-stream-filter"); + + /* Fetch FIFO depth; Set to 8 for older DT without this property */ + iprop = of_get_property(np, "fsl,fifo-depth", NULL); + if (iprop) + ssi->fifo_depth = be32_to_cpup(iprop); + else + ssi->fifo_depth = 8; + + /* Use dual FIFO mode depending on the support from SDMA script */ + ret = of_property_read_u32_array(np, "dmas", dmas, 4); + if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_SSI_DUAL) + ssi->use_dual_fifo = true; + + /* + * Backward compatible for older bindings by manually triggering the + * machine driver's probe(). Use /compatible property, including the + * address of CPU DAI driver structure, as the name of machine driver + * + * If card_name is set by AC97 earlier, bypass here since it uses a + * different name to register the device. + */ + if (!ssi->card_name[0] && of_get_property(np, "codec-handle", NULL)) { + struct device_node *root = of_find_node_by_path("/"); + + sprop = of_get_property(root, "compatible", NULL); + of_node_put(root); + /* Strip "fsl," in the compatible name if applicable */ + p = strrchr(sprop, ','); + if (p) + sprop = p + 1; + snprintf(ssi->card_name, sizeof(ssi->card_name), + "snd-soc-%s", sprop); + make_lowercase(ssi->card_name); + ssi->card_idx = 0; + } + + return 0; +} + +static int fsl_ssi_probe(struct platform_device *pdev) +{ + struct regmap_config regconfig = fsl_ssi_regconfig; + struct device *dev = &pdev->dev; + struct fsl_ssi *ssi; + struct resource *res; + void __iomem *iomem; + int ret = 0; + + ssi = devm_kzalloc(dev, sizeof(*ssi), GFP_KERNEL); + if (!ssi) + return -ENOMEM; + + ssi->dev = dev; + + /* Probe from DT */ + ret = fsl_ssi_probe_from_dt(ssi); + if (ret) + return ret; + + if (fsl_ssi_is_ac97(ssi)) { + memcpy(&ssi->cpu_dai_drv, &fsl_ssi_ac97_dai, + sizeof(fsl_ssi_ac97_dai)); + fsl_ac97_data = ssi; + } else { + memcpy(&ssi->cpu_dai_drv, &fsl_ssi_dai_template, + sizeof(fsl_ssi_dai_template)); + } + ssi->cpu_dai_drv.name = dev_name(dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iomem = devm_ioremap_resource(dev, res); + if (IS_ERR(iomem)) + return PTR_ERR(iomem); + ssi->ssi_phys = res->start; + + if (ssi->soc->imx21regs) { + /* No SACC{ST,EN,DIS} regs in imx21-class SSI */ + regconfig.max_register = REG_SSI_SRMSK; + regconfig.num_reg_defaults_raw = + REG_SSI_SRMSK / sizeof(uint32_t) + 1; + } + + if (ssi->has_ipg_clk_name) + ssi->regs = devm_regmap_init_mmio_clk(dev, "ipg", iomem, + ®config); + else + ssi->regs = devm_regmap_init_mmio(dev, iomem, ®config); + if (IS_ERR(ssi->regs)) { + dev_err(dev, "failed to init register map\n"); + return PTR_ERR(ssi->regs); + } + + ssi->irq = platform_get_irq(pdev, 0); + if (ssi->irq < 0) { + dev_err(dev, "no irq for node %s\n", pdev->name); + return ssi->irq; + } + + /* Set software limitations for synchronous mode except AC97 */ + if (ssi->synchronous && !fsl_ssi_is_ac97(ssi)) { + ssi->cpu_dai_drv.symmetric_rates = 1; + ssi->cpu_dai_drv.symmetric_channels = 1; + ssi->cpu_dai_drv.symmetric_samplebits = 1; + } + + /* + * Configure TX and RX DMA watermarks -- when to send a DMA request + * + * Values should be tested to avoid FIFO under/over run. Set maxburst + * to fifo_watermark to maxiumize DMA transaction to reduce overhead. + */ + switch (ssi->fifo_depth) { + case 15: + /* + * Set to 8 as a balanced configuration -- When TX FIFO has 8 + * empty slots, send a DMA request to fill these 8 slots. The + * remaining 7 slots should be able to allow DMA to finish the + * transaction before TX FIFO underruns; Same applies to RX. + * + * Tested with cases running at 48kHz @ 16 bits x 16 channels + */ + ssi->fifo_watermark = 8; + ssi->dma_maxburst = 8; + break; + case 8: + default: + /* Safely use old watermark configurations for older chips */ + ssi->fifo_watermark = ssi->fifo_depth - 2; + ssi->dma_maxburst = ssi->fifo_depth - 2; + break; + } + + dev_set_drvdata(dev, ssi); + + if (ssi->soc->imx) { + ret = fsl_ssi_imx_probe(pdev, ssi, iomem); + if (ret) + return ret; + } + + if (fsl_ssi_is_ac97(ssi)) { + mutex_init(&ssi->ac97_reg_lock); + ret = snd_soc_set_ac97_ops_of_reset(&fsl_ssi_ac97_ops, pdev); + if (ret) { + dev_err(dev, "failed to set AC'97 ops\n"); + goto error_ac97_ops; + } + } + + ret = devm_snd_soc_register_component(dev, &fsl_ssi_component, + &ssi->cpu_dai_drv, 1); + if (ret) { + dev_err(dev, "failed to register DAI: %d\n", ret); + goto error_asoc_register; + } + + if (ssi->use_dma) { + ret = devm_request_irq(dev, ssi->irq, fsl_ssi_isr, 0, + dev_name(dev), ssi); + if (ret < 0) { + dev_err(dev, "failed to claim irq %u\n", ssi->irq); + goto error_asoc_register; + } + } + + ret = fsl_ssi_debugfs_create(&ssi->dbg_stats, dev); + if (ret) + goto error_asoc_register; + + /* Initially configures SSI registers */ + fsl_ssi_hw_init(ssi); + + /* Register a platform device for older bindings or AC97 */ + if (ssi->card_name[0]) { + struct device *parent = dev; + /* + * Do not set SSI dev as the parent of AC97 CODEC device since + * it does not have a DT node. Otherwise ASoC core will assume + * CODEC has the same DT node as the SSI, so it may bypass the + * dai_probe() of SSI and then cause NULL DMA data pointers. + */ + if (fsl_ssi_is_ac97(ssi)) + parent = NULL; + + ssi->card_pdev = platform_device_register_data(parent, + ssi->card_name, ssi->card_idx, NULL, 0); + if (IS_ERR(ssi->card_pdev)) { + ret = PTR_ERR(ssi->card_pdev); + dev_err(dev, "failed to register %s: %d\n", + ssi->card_name, ret); + goto error_sound_card; + } + } + + return 0; + +error_sound_card: + fsl_ssi_debugfs_remove(&ssi->dbg_stats); +error_asoc_register: + if (fsl_ssi_is_ac97(ssi)) + snd_soc_set_ac97_ops(NULL); +error_ac97_ops: + if (fsl_ssi_is_ac97(ssi)) + mutex_destroy(&ssi->ac97_reg_lock); + + if (ssi->soc->imx) + fsl_ssi_imx_clean(pdev, ssi); + + return ret; +} + +static int fsl_ssi_remove(struct platform_device *pdev) +{ + struct fsl_ssi *ssi = dev_get_drvdata(&pdev->dev); + + fsl_ssi_debugfs_remove(&ssi->dbg_stats); + + if (ssi->card_pdev) + platform_device_unregister(ssi->card_pdev); + + /* Clean up SSI registers */ + fsl_ssi_hw_clean(ssi); + + if (ssi->soc->imx) + fsl_ssi_imx_clean(pdev, ssi); + + if (fsl_ssi_is_ac97(ssi)) { + snd_soc_set_ac97_ops(NULL); + mutex_destroy(&ssi->ac97_reg_lock); + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int fsl_ssi_suspend(struct device *dev) +{ + struct fsl_ssi *ssi = dev_get_drvdata(dev); + struct regmap *regs = ssi->regs; + + regmap_read(regs, REG_SSI_SFCSR, &ssi->regcache_sfcsr); + regmap_read(regs, REG_SSI_SACNT, &ssi->regcache_sacnt); + + regcache_cache_only(regs, true); + regcache_mark_dirty(regs); + + return 0; +} + +static int fsl_ssi_resume(struct device *dev) +{ + struct fsl_ssi *ssi = dev_get_drvdata(dev); + struct regmap *regs = ssi->regs; + + regcache_cache_only(regs, false); + + regmap_update_bits(regs, REG_SSI_SFCSR, + SSI_SFCSR_RFWM1_MASK | SSI_SFCSR_TFWM1_MASK | + SSI_SFCSR_RFWM0_MASK | SSI_SFCSR_TFWM0_MASK, + ssi->regcache_sfcsr); + regmap_write(regs, REG_SSI_SACNT, ssi->regcache_sacnt); + + return regcache_sync(regs); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_ssi_pm = { + SET_SYSTEM_SLEEP_PM_OPS(fsl_ssi_suspend, fsl_ssi_resume) +}; + +static struct platform_driver fsl_ssi_driver = { + .driver = { + .name = "fsl-ssi-dai", + .of_match_table = fsl_ssi_ids, + .pm = &fsl_ssi_pm, + }, + .probe = fsl_ssi_probe, + .remove = fsl_ssi_remove, +}; + +module_platform_driver(fsl_ssi_driver); + +MODULE_ALIAS("platform:fsl-ssi-dai"); +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h new file mode 100644 index 000000000..0bdda608d --- /dev/null +++ b/sound/soc/fsl/fsl_ssi.h @@ -0,0 +1,326 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 and i.MX SoC + * + * Author: Timur Tabi + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. + */ + +#ifndef _MPC8610_I2S_H +#define _MPC8610_I2S_H + +/* -- SSI Register Map -- */ + +/* SSI Transmit Data Register 0 */ +#define REG_SSI_STX0 0x00 +/* SSI Transmit Data Register 1 */ +#define REG_SSI_STX1 0x04 +/* SSI Receive Data Register 0 */ +#define REG_SSI_SRX0 0x08 +/* SSI Receive Data Register 1 */ +#define REG_SSI_SRX1 0x0c +/* SSI Control Register */ +#define REG_SSI_SCR 0x10 +/* SSI Interrupt Status Register */ +#define REG_SSI_SISR 0x14 +/* SSI Interrupt Enable Register */ +#define REG_SSI_SIER 0x18 +/* SSI Transmit Configuration Register */ +#define REG_SSI_STCR 0x1c +/* SSI Receive Configuration Register */ +#define REG_SSI_SRCR 0x20 +#define REG_SSI_SxCR(tx) ((tx) ? REG_SSI_STCR : REG_SSI_SRCR) +/* SSI Transmit Clock Control Register */ +#define REG_SSI_STCCR 0x24 +/* SSI Receive Clock Control Register */ +#define REG_SSI_SRCCR 0x28 +#define REG_SSI_SxCCR(tx) ((tx) ? REG_SSI_STCCR : REG_SSI_SRCCR) +/* SSI FIFO Control/Status Register */ +#define REG_SSI_SFCSR 0x2c +/* + * SSI Test Register (Intended for debugging purposes only) + * + * Note: STR is not documented in recent IMX datasheet, but + * is described in IMX51 reference manual at section 56.3.3.14 + */ +#define REG_SSI_STR 0x30 +/* + * SSI Option Register (Intended for internal use only) + * + * Note: SOR is not documented in recent IMX datasheet, but + * is described in IMX51 reference manual at section 56.3.3.15 + */ +#define REG_SSI_SOR 0x34 +/* SSI AC97 Control Register */ +#define REG_SSI_SACNT 0x38 +/* SSI AC97 Command Address Register */ +#define REG_SSI_SACADD 0x3c +/* SSI AC97 Command Data Register */ +#define REG_SSI_SACDAT 0x40 +/* SSI AC97 Tag Register */ +#define REG_SSI_SATAG 0x44 +/* SSI Transmit Time Slot Mask Register */ +#define REG_SSI_STMSK 0x48 +/* SSI Receive Time Slot Mask Register */ +#define REG_SSI_SRMSK 0x4c +#define REG_SSI_SxMSK(tx) ((tx) ? REG_SSI_STMSK : REG_SSI_SRMSK) +/* + * SSI AC97 Channel Status Register + * + * The status could be changed by: + * 1) Writing a '1' bit at some position in SACCEN sets relevant bit in SACCST + * 2) Writing a '1' bit at some position in SACCDIS unsets the relevant bit + * 3) Receivng a '1' in SLOTREQ bit from external CODEC via AC Link + */ +#define REG_SSI_SACCST 0x50 +/* SSI AC97 Channel Enable Register -- Set bits in SACCST */ +#define REG_SSI_SACCEN 0x54 +/* SSI AC97 Channel Disable Register -- Clear bits in SACCST */ +#define REG_SSI_SACCDIS 0x58 + +/* -- SSI Register Field Maps -- */ + +/* SSI Control Register -- REG_SSI_SCR 0x10 */ +#define SSI_SCR_SYNC_TX_FS 0x00001000 +#define SSI_SCR_RFR_CLK_DIS 0x00000800 +#define SSI_SCR_TFR_CLK_DIS 0x00000400 +#define SSI_SCR_TCH_EN 0x00000100 +#define SSI_SCR_SYS_CLK_EN 0x00000080 +#define SSI_SCR_I2S_MODE_MASK 0x00000060 +#define SSI_SCR_I2S_MODE_NORMAL 0x00000000 +#define SSI_SCR_I2S_MODE_MASTER 0x00000020 +#define SSI_SCR_I2S_MODE_SLAVE 0x00000040 +#define SSI_SCR_SYN 0x00000010 +#define SSI_SCR_NET 0x00000008 +#define SSI_SCR_I2S_NET_MASK (SSI_SCR_NET | SSI_SCR_I2S_MODE_MASK) +#define SSI_SCR_RE 0x00000004 +#define SSI_SCR_TE 0x00000002 +#define SSI_SCR_SSIEN 0x00000001 + +/* SSI Interrupt Status Register -- REG_SSI_SISR 0x14 */ +#define SSI_SISR_RFRC 0x01000000 +#define SSI_SISR_TFRC 0x00800000 +#define SSI_SISR_CMDAU 0x00040000 +#define SSI_SISR_CMDDU 0x00020000 +#define SSI_SISR_RXT 0x00010000 +#define SSI_SISR_RDR1 0x00008000 +#define SSI_SISR_RDR0 0x00004000 +#define SSI_SISR_TDE1 0x00002000 +#define SSI_SISR_TDE0 0x00001000 +#define SSI_SISR_ROE1 0x00000800 +#define SSI_SISR_ROE0 0x00000400 +#define SSI_SISR_TUE1 0x00000200 +#define SSI_SISR_TUE0 0x00000100 +#define SSI_SISR_TFS 0x00000080 +#define SSI_SISR_RFS 0x00000040 +#define SSI_SISR_TLS 0x00000020 +#define SSI_SISR_RLS 0x00000010 +#define SSI_SISR_RFF1 0x00000008 +#define SSI_SISR_RFF0 0x00000004 +#define SSI_SISR_TFE1 0x00000002 +#define SSI_SISR_TFE0 0x00000001 + +/* SSI Interrupt Enable Register -- REG_SSI_SIER 0x18 */ +#define SSI_SIER_RFRC_EN 0x01000000 +#define SSI_SIER_TFRC_EN 0x00800000 +#define SSI_SIER_RDMAE 0x00400000 +#define SSI_SIER_RIE 0x00200000 +#define SSI_SIER_TDMAE 0x00100000 +#define SSI_SIER_TIE 0x00080000 +#define SSI_SIER_CMDAU_EN 0x00040000 +#define SSI_SIER_CMDDU_EN 0x00020000 +#define SSI_SIER_RXT_EN 0x00010000 +#define SSI_SIER_RDR1_EN 0x00008000 +#define SSI_SIER_RDR0_EN 0x00004000 +#define SSI_SIER_TDE1_EN 0x00002000 +#define SSI_SIER_TDE0_EN 0x00001000 +#define SSI_SIER_ROE1_EN 0x00000800 +#define SSI_SIER_ROE0_EN 0x00000400 +#define SSI_SIER_TUE1_EN 0x00000200 +#define SSI_SIER_TUE0_EN 0x00000100 +#define SSI_SIER_TFS_EN 0x00000080 +#define SSI_SIER_RFS_EN 0x00000040 +#define SSI_SIER_TLS_EN 0x00000020 +#define SSI_SIER_RLS_EN 0x00000010 +#define SSI_SIER_RFF1_EN 0x00000008 +#define SSI_SIER_RFF0_EN 0x00000004 +#define SSI_SIER_TFE1_EN 0x00000002 +#define SSI_SIER_TFE0_EN 0x00000001 + +/* SSI Transmit Configuration Register -- REG_SSI_STCR 0x1C */ +#define SSI_STCR_TXBIT0 0x00000200 +#define SSI_STCR_TFEN1 0x00000100 +#define SSI_STCR_TFEN0 0x00000080 +#define SSI_STCR_TFDIR 0x00000040 +#define SSI_STCR_TXDIR 0x00000020 +#define SSI_STCR_TSHFD 0x00000010 +#define SSI_STCR_TSCKP 0x00000008 +#define SSI_STCR_TFSI 0x00000004 +#define SSI_STCR_TFSL 0x00000002 +#define SSI_STCR_TEFS 0x00000001 + +/* SSI Receive Configuration Register -- REG_SSI_SRCR 0x20 */ +#define SSI_SRCR_RXEXT 0x00000400 +#define SSI_SRCR_RXBIT0 0x00000200 +#define SSI_SRCR_RFEN1 0x00000100 +#define SSI_SRCR_RFEN0 0x00000080 +#define SSI_SRCR_RFDIR 0x00000040 +#define SSI_SRCR_RXDIR 0x00000020 +#define SSI_SRCR_RSHFD 0x00000010 +#define SSI_SRCR_RSCKP 0x00000008 +#define SSI_SRCR_RFSI 0x00000004 +#define SSI_SRCR_RFSL 0x00000002 +#define SSI_SRCR_REFS 0x00000001 + +/* + * SSI Transmit Clock Control Register -- REG_SSI_STCCR 0x24 + * SSI Receive Clock Control Register -- REG_SSI_SRCCR 0x28 + */ +#define SSI_SxCCR_DIV2_SHIFT 18 +#define SSI_SxCCR_DIV2 0x00040000 +#define SSI_SxCCR_PSR_SHIFT 17 +#define SSI_SxCCR_PSR 0x00020000 +#define SSI_SxCCR_WL_SHIFT 13 +#define SSI_SxCCR_WL_MASK 0x0001E000 +#define SSI_SxCCR_WL(x) \ + (((((x) / 2) - 1) << SSI_SxCCR_WL_SHIFT) & SSI_SxCCR_WL_MASK) +#define SSI_SxCCR_DC_SHIFT 8 +#define SSI_SxCCR_DC_MASK 0x00001F00 +#define SSI_SxCCR_DC(x) \ + ((((x) - 1) << SSI_SxCCR_DC_SHIFT) & SSI_SxCCR_DC_MASK) +#define SSI_SxCCR_PM_SHIFT 0 +#define SSI_SxCCR_PM_MASK 0x000000FF +#define SSI_SxCCR_PM(x) \ + ((((x) - 1) << SSI_SxCCR_PM_SHIFT) & SSI_SxCCR_PM_MASK) + +/* + * SSI FIFO Control/Status Register -- REG_SSI_SFCSR 0x2c + * + * Tx or Rx FIFO Counter -- SSI_SFCSR_xFCNTy Read-Only + * Tx or Rx FIFO Watermarks -- SSI_SFCSR_xFWMy Read/Write + */ +#define SSI_SFCSR_RFCNT1_SHIFT 28 +#define SSI_SFCSR_RFCNT1_MASK 0xF0000000 +#define SSI_SFCSR_RFCNT1(x) \ + (((x) & SSI_SFCSR_RFCNT1_MASK) >> SSI_SFCSR_RFCNT1_SHIFT) +#define SSI_SFCSR_TFCNT1_SHIFT 24 +#define SSI_SFCSR_TFCNT1_MASK 0x0F000000 +#define SSI_SFCSR_TFCNT1(x) \ + (((x) & SSI_SFCSR_TFCNT1_MASK) >> SSI_SFCSR_TFCNT1_SHIFT) +#define SSI_SFCSR_RFWM1_SHIFT 20 +#define SSI_SFCSR_RFWM1_MASK 0x00F00000 +#define SSI_SFCSR_RFWM1(x) \ + (((x) << SSI_SFCSR_RFWM1_SHIFT) & SSI_SFCSR_RFWM1_MASK) +#define SSI_SFCSR_TFWM1_SHIFT 16 +#define SSI_SFCSR_TFWM1_MASK 0x000F0000 +#define SSI_SFCSR_TFWM1(x) \ + (((x) << SSI_SFCSR_TFWM1_SHIFT) & SSI_SFCSR_TFWM1_MASK) +#define SSI_SFCSR_RFCNT0_SHIFT 12 +#define SSI_SFCSR_RFCNT0_MASK 0x0000F000 +#define SSI_SFCSR_RFCNT0(x) \ + (((x) & SSI_SFCSR_RFCNT0_MASK) >> SSI_SFCSR_RFCNT0_SHIFT) +#define SSI_SFCSR_TFCNT0_SHIFT 8 +#define SSI_SFCSR_TFCNT0_MASK 0x00000F00 +#define SSI_SFCSR_TFCNT0(x) \ + (((x) & SSI_SFCSR_TFCNT0_MASK) >> SSI_SFCSR_TFCNT0_SHIFT) +#define SSI_SFCSR_RFWM0_SHIFT 4 +#define SSI_SFCSR_RFWM0_MASK 0x000000F0 +#define SSI_SFCSR_RFWM0(x) \ + (((x) << SSI_SFCSR_RFWM0_SHIFT) & SSI_SFCSR_RFWM0_MASK) +#define SSI_SFCSR_TFWM0_SHIFT 0 +#define SSI_SFCSR_TFWM0_MASK 0x0000000F +#define SSI_SFCSR_TFWM0(x) \ + (((x) << SSI_SFCSR_TFWM0_SHIFT) & SSI_SFCSR_TFWM0_MASK) + +/* SSI Test Register -- REG_SSI_STR 0x30 */ +#define SSI_STR_TEST 0x00008000 +#define SSI_STR_RCK2TCK 0x00004000 +#define SSI_STR_RFS2TFS 0x00002000 +#define SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F) +#define SSI_STR_TXD2RXD 0x00000080 +#define SSI_STR_TCK2RCK 0x00000040 +#define SSI_STR_TFS2RFS 0x00000020 +#define SSI_STR_TXSTATE(x) ((x) & 0x1F) + +/* SSI Option Register -- REG_SSI_SOR 0x34 */ +#define SSI_SOR_CLKOFF 0x00000040 +#define SSI_SOR_RX_CLR 0x00000020 +#define SSI_SOR_TX_CLR 0x00000010 +#define SSI_SOR_xX_CLR(tx) ((tx) ? SSI_SOR_TX_CLR : SSI_SOR_RX_CLR) +#define SSI_SOR_INIT 0x00000008 +#define SSI_SOR_WAIT_SHIFT 1 +#define SSI_SOR_WAIT_MASK 0x00000006 +#define SSI_SOR_WAIT(x) (((x) & 3) << SSI_SOR_WAIT_SHIFT) +#define SSI_SOR_SYNRST 0x00000001 + +/* SSI AC97 Control Register -- REG_SSI_SACNT 0x38 */ +#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) +#define SSI_SACNT_WR 0x00000010 +#define SSI_SACNT_RD 0x00000008 +#define SSI_SACNT_RDWR_MASK 0x00000018 +#define SSI_SACNT_TIF 0x00000004 +#define SSI_SACNT_FV 0x00000002 +#define SSI_SACNT_AC97EN 0x00000001 + + +struct device; + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +struct fsl_ssi_dbg { + struct dentry *dbg_dir; + struct dentry *dbg_stats; + + struct { + unsigned int rfrc; + unsigned int tfrc; + unsigned int cmdau; + unsigned int cmddu; + unsigned int rxt; + unsigned int rdr1; + unsigned int rdr0; + unsigned int tde1; + unsigned int tde0; + unsigned int roe1; + unsigned int roe0; + unsigned int tue1; + unsigned int tue0; + unsigned int tfs; + unsigned int rfs; + unsigned int tls; + unsigned int rls; + unsigned int rff1; + unsigned int rff0; + unsigned int tfe1; + unsigned int tfe0; + } stats; +}; + +void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *ssi_dbg, u32 sisr); + +int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev); + +void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg); + +#else + +struct fsl_ssi_dbg { +}; + +static inline void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *stats, u32 sisr) +{ +} + +static inline int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, + struct device *dev) +{ + return 0; +} + +static inline void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg) +{ +} +#endif /* ! IS_ENABLED(CONFIG_DEBUG_FS) */ + +#endif diff --git a/sound/soc/fsl/fsl_ssi_dbg.c b/sound/soc/fsl/fsl_ssi_dbg.c new file mode 100644 index 000000000..1255dfe19 --- /dev/null +++ b/sound/soc/fsl/fsl_ssi_dbg.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale SSI ALSA SoC Digital Audio Interface (DAI) debugging functions +// +// Copyright 2014 Markus Pargmann , Pengutronix +// +// Split from fsl_ssi.c + +#include +#include +#include + +#include "fsl_ssi.h" + +void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *dbg, u32 sisr) +{ + if (sisr & SSI_SISR_RFRC) + dbg->stats.rfrc++; + + if (sisr & SSI_SISR_TFRC) + dbg->stats.tfrc++; + + if (sisr & SSI_SISR_CMDAU) + dbg->stats.cmdau++; + + if (sisr & SSI_SISR_CMDDU) + dbg->stats.cmddu++; + + if (sisr & SSI_SISR_RXT) + dbg->stats.rxt++; + + if (sisr & SSI_SISR_RDR1) + dbg->stats.rdr1++; + + if (sisr & SSI_SISR_RDR0) + dbg->stats.rdr0++; + + if (sisr & SSI_SISR_TDE1) + dbg->stats.tde1++; + + if (sisr & SSI_SISR_TDE0) + dbg->stats.tde0++; + + if (sisr & SSI_SISR_ROE1) + dbg->stats.roe1++; + + if (sisr & SSI_SISR_ROE0) + dbg->stats.roe0++; + + if (sisr & SSI_SISR_TUE1) + dbg->stats.tue1++; + + if (sisr & SSI_SISR_TUE0) + dbg->stats.tue0++; + + if (sisr & SSI_SISR_TFS) + dbg->stats.tfs++; + + if (sisr & SSI_SISR_RFS) + dbg->stats.rfs++; + + if (sisr & SSI_SISR_TLS) + dbg->stats.tls++; + + if (sisr & SSI_SISR_RLS) + dbg->stats.rls++; + + if (sisr & SSI_SISR_RFF1) + dbg->stats.rff1++; + + if (sisr & SSI_SISR_RFF0) + dbg->stats.rff0++; + + if (sisr & SSI_SISR_TFE1) + dbg->stats.tfe1++; + + if (sisr & SSI_SISR_TFE0) + dbg->stats.tfe0++; +} + +/** + * Show the statistics of a flag only if its interrupt is enabled + * + * Compilers will optimize it to a no-op if the interrupt is disabled + */ +#define SIER_SHOW(flag, name) \ + do { \ + if (SSI_SIER_##flag) \ + seq_printf(s, #name "=%u\n", ssi_dbg->stats.name); \ + } while (0) + + +/** + * Display the statistics for the current SSI device + * + * To avoid confusion, only show those counts that are enabled + */ +static int fsl_ssi_stats_show(struct seq_file *s, void *unused) +{ + struct fsl_ssi_dbg *ssi_dbg = s->private; + + SIER_SHOW(RFRC_EN, rfrc); + SIER_SHOW(TFRC_EN, tfrc); + SIER_SHOW(CMDAU_EN, cmdau); + SIER_SHOW(CMDDU_EN, cmddu); + SIER_SHOW(RXT_EN, rxt); + SIER_SHOW(RDR1_EN, rdr1); + SIER_SHOW(RDR0_EN, rdr0); + SIER_SHOW(TDE1_EN, tde1); + SIER_SHOW(TDE0_EN, tde0); + SIER_SHOW(ROE1_EN, roe1); + SIER_SHOW(ROE0_EN, roe0); + SIER_SHOW(TUE1_EN, tue1); + SIER_SHOW(TUE0_EN, tue0); + SIER_SHOW(TFS_EN, tfs); + SIER_SHOW(RFS_EN, rfs); + SIER_SHOW(TLS_EN, tls); + SIER_SHOW(RLS_EN, rls); + SIER_SHOW(RFF1_EN, rff1); + SIER_SHOW(RFF0_EN, rff0); + SIER_SHOW(TFE1_EN, tfe1); + SIER_SHOW(TFE0_EN, tfe0); + + return 0; +} + +static int fsl_ssi_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, fsl_ssi_stats_show, inode->i_private); +} + +static const struct file_operations fsl_ssi_stats_ops = { + .open = fsl_ssi_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev) +{ + ssi_dbg->dbg_dir = debugfs_create_dir(dev_name(dev), NULL); + if (!ssi_dbg->dbg_dir) + return -ENOMEM; + + ssi_dbg->dbg_stats = debugfs_create_file("stats", 0444, + ssi_dbg->dbg_dir, ssi_dbg, + &fsl_ssi_stats_ops); + if (!ssi_dbg->dbg_stats) { + debugfs_remove(ssi_dbg->dbg_dir); + return -ENOMEM; + } + + return 0; +} + +void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg) +{ + debugfs_remove(ssi_dbg->dbg_stats); + debugfs_remove(ssi_dbg->dbg_dir); +} diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c new file mode 100644 index 000000000..cca33ab70 --- /dev/null +++ b/sound/soc/fsl/fsl_utils.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Freescale ALSA SoC Machine driver utility +// +// Author: Timur Tabi +// +// Copyright 2010 Freescale Semiconductor, Inc. + +#include +#include +#include + +#include "fsl_utils.h" + +/** + * fsl_asoc_get_dma_channel - determine the dma channel for a SSI node + * + * @ssi_np: pointer to the SSI device tree node + * @name: name of the phandle pointing to the dma channel + * @dai: ASoC DAI link pointer to be filled with platform_name + * @dma_channel_id: dma channel id to be returned + * @dma_id: dma id to be returned + * + * This function determines the dma and channel id for given SSI node. It + * also discovers the platform_name for the ASoC DAI link. + */ +int fsl_asoc_get_dma_channel(struct device_node *ssi_np, + const char *name, + struct snd_soc_dai_link *dai, + unsigned int *dma_channel_id, + unsigned int *dma_id) +{ + struct resource res; + struct device_node *dma_channel_np, *dma_np; + const __be32 *iprop; + int ret; + + dma_channel_np = of_parse_phandle(ssi_np, name, 0); + if (!dma_channel_np) + return -EINVAL; + + if (!of_device_is_compatible(dma_channel_np, "fsl,ssi-dma-channel")) { + of_node_put(dma_channel_np); + return -EINVAL; + } + + /* Determine the dev_name for the device_node. This code mimics the + * behavior of of_device_make_bus_id(). We need this because ASoC uses + * the dev_name() of the device to match the platform (DMA) device with + * the CPU (SSI) device. It's all ugly and hackish, but it works (for + * now). + * + * dai->platform name should already point to an allocated buffer. + */ + ret = of_address_to_resource(dma_channel_np, 0, &res); + if (ret) { + of_node_put(dma_channel_np); + return ret; + } + snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s", + (unsigned long long) res.start, dma_channel_np->name); + + iprop = of_get_property(dma_channel_np, "cell-index", NULL); + if (!iprop) { + of_node_put(dma_channel_np); + return -EINVAL; + } + *dma_channel_id = be32_to_cpup(iprop); + + dma_np = of_get_parent(dma_channel_np); + iprop = of_get_property(dma_np, "cell-index", NULL); + if (!iprop) { + of_node_put(dma_np); + of_node_put(dma_channel_np); + return -EINVAL; + } + *dma_id = be32_to_cpup(iprop); + + of_node_put(dma_np); + of_node_put(dma_channel_np); + + return 0; +} +EXPORT_SYMBOL(fsl_asoc_get_dma_channel); + +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Freescale ASoC utility code"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_utils.h b/sound/soc/fsl/fsl_utils.h new file mode 100644 index 000000000..c5dc2a14b --- /dev/null +++ b/sound/soc/fsl/fsl_utils.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Freescale ALSA SoC Machine driver utility + * + * Author: Timur Tabi + * + * Copyright 2010 Freescale Semiconductor, Inc. + */ + +#ifndef _FSL_UTILS_H +#define _FSL_UTILS_H + +#define DAI_NAME_SIZE 32 + +struct snd_soc_dai_link; +struct device_node; + +int fsl_asoc_get_dma_channel(struct device_node *ssi_np, const char *name, + struct snd_soc_dai_link *dai, + unsigned int *dma_channel_id, + unsigned int *dma_id); +#endif /* _FSL_UTILS_H */ diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c new file mode 100644 index 000000000..99e07b01a --- /dev/null +++ b/sound/soc/fsl/imx-audmux.c @@ -0,0 +1,378 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * Copyright 2009 Pengutronix, Sascha Hauer + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-audmux.h" + +#define DRIVER_NAME "imx-audmux" + +static struct clk *audmux_clk; +static void __iomem *audmux_base; + +#define IMX_AUDMUX_V2_PTCR(x) ((x) * 8) +#define IMX_AUDMUX_V2_PDCR(x) ((x) * 8 + 4) + +#ifdef CONFIG_DEBUG_FS +static struct dentry *audmux_debugfs_root; + +/* There is an annoying discontinuity in the SSI numbering with regard + * to the Linux number of the devices */ +static const char *audmux_port_string(int port) +{ + switch (port) { + case MX31_AUDMUX_PORT1_SSI0: + return "imx-ssi.0"; + case MX31_AUDMUX_PORT2_SSI1: + return "imx-ssi.1"; + case MX31_AUDMUX_PORT3_SSI_PINS_3: + return "SSI3"; + case MX31_AUDMUX_PORT4_SSI_PINS_4: + return "SSI4"; + case MX31_AUDMUX_PORT5_SSI_PINS_5: + return "SSI5"; + case MX31_AUDMUX_PORT6_SSI_PINS_6: + return "SSI6"; + default: + return "UNKNOWN"; + } +} + +static ssize_t audmux_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + char *buf; + uintptr_t port = (uintptr_t)file->private_data; + u32 pdcr, ptcr; + + if (audmux_clk) { + ret = clk_prepare_enable(audmux_clk); + if (ret) + return ret; + } + + ptcr = readl(audmux_base + IMX_AUDMUX_V2_PTCR(port)); + pdcr = readl(audmux_base + IMX_AUDMUX_V2_PDCR(port)); + + if (audmux_clk) + clk_disable_unprepare(audmux_clk); + + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, PAGE_SIZE, "PDCR: %08x\nPTCR: %08x\n", + pdcr, ptcr); + + if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "TxFS output from %s, ", + audmux_port_string((ptcr >> 27) & 0x7)); + else + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "TxFS input, "); + + if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "TxClk output from %s", + audmux_port_string((ptcr >> 22) & 0x7)); + else + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "TxClk input"); + + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n"); + + if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) { + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "Port is symmetric"); + } else { + if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "RxFS output from %s, ", + audmux_port_string((ptcr >> 17) & 0x7)); + else + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "RxFS input, "); + + if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "RxClk output from %s", + audmux_port_string((ptcr >> 12) & 0x7)); + else + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "RxClk input"); + } + + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "\nData received from %s\n", + audmux_port_string((pdcr >> 13) & 0x7)); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + + return ret; +} + +static const struct file_operations audmux_debugfs_fops = { + .open = simple_open, + .read = audmux_read_file, + .llseek = default_llseek, +}; + +static void audmux_debugfs_init(void) +{ + uintptr_t i; + char buf[20]; + + audmux_debugfs_root = debugfs_create_dir("audmux", NULL); + if (!audmux_debugfs_root) { + pr_warning("Failed to create AUDMUX debugfs root\n"); + return; + } + + for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) { + snprintf(buf, sizeof(buf), "ssi%lu", i); + if (!debugfs_create_file(buf, 0444, audmux_debugfs_root, + (void *)i, &audmux_debugfs_fops)) + pr_warning("Failed to create AUDMUX port %lu debugfs file\n", + i); + } +} + +static void audmux_debugfs_remove(void) +{ + debugfs_remove_recursive(audmux_debugfs_root); +} +#else +static inline void audmux_debugfs_init(void) +{ +} + +static inline void audmux_debugfs_remove(void) +{ +} +#endif + +static enum imx_audmux_type { + IMX21_AUDMUX, + IMX31_AUDMUX, +} audmux_type; + +static const struct platform_device_id imx_audmux_ids[] = { + { + .name = "imx21-audmux", + .driver_data = IMX21_AUDMUX, + }, { + .name = "imx31-audmux", + .driver_data = IMX31_AUDMUX, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, imx_audmux_ids); + +static const struct of_device_id imx_audmux_dt_ids[] = { + { .compatible = "fsl,imx21-audmux", .data = &imx_audmux_ids[0], }, + { .compatible = "fsl,imx31-audmux", .data = &imx_audmux_ids[1], }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_audmux_dt_ids); + +static const uint8_t port_mapping[] = { + 0x0, 0x4, 0x8, 0x10, 0x14, 0x1c, +}; + +int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr) +{ + if (audmux_type != IMX21_AUDMUX) + return -EINVAL; + + if (!audmux_base) + return -ENOSYS; + + if (port >= ARRAY_SIZE(port_mapping)) + return -EINVAL; + + writel(pcr, audmux_base + port_mapping[port]); + + return 0; +} +EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port); + +int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr, + unsigned int pdcr) +{ + int ret; + + if (audmux_type != IMX31_AUDMUX) + return -EINVAL; + + if (!audmux_base) + return -ENOSYS; + + if (audmux_clk) { + ret = clk_prepare_enable(audmux_clk); + if (ret) + return ret; + } + + writel(ptcr, audmux_base + IMX_AUDMUX_V2_PTCR(port)); + writel(pdcr, audmux_base + IMX_AUDMUX_V2_PDCR(port)); + + if (audmux_clk) + clk_disable_unprepare(audmux_clk); + + return 0; +} +EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port); + +static int imx_audmux_parse_dt_defaults(struct platform_device *pdev, + struct device_node *of_node) +{ + struct device_node *child; + + for_each_available_child_of_node(of_node, child) { + unsigned int port; + unsigned int ptcr = 0; + unsigned int pdcr = 0; + unsigned int pcr = 0; + unsigned int val; + int ret; + int i = 0; + + ret = of_property_read_u32(child, "fsl,audmux-port", &port); + if (ret) { + dev_warn(&pdev->dev, "Failed to get fsl,audmux-port of child node \"%pOF\"\n", + child); + continue; + } + if (!of_property_read_bool(child, "fsl,port-config")) { + dev_warn(&pdev->dev, "child node \"%pOF\" does not have property fsl,port-config\n", + child); + continue; + } + + for (i = 0; (ret = of_property_read_u32_index(child, + "fsl,port-config", i, &val)) == 0; + ++i) { + if (audmux_type == IMX31_AUDMUX) { + if (i % 2) + pdcr |= val; + else + ptcr |= val; + } else { + pcr |= val; + } + } + + if (ret != -EOVERFLOW) { + dev_err(&pdev->dev, "Failed to read u32 at index %d of child %pOF\n", + i, child); + continue; + } + + if (audmux_type == IMX31_AUDMUX) { + if (i % 2) { + dev_err(&pdev->dev, "One pdcr value is missing in child node %pOF\n", + child); + continue; + } + imx_audmux_v2_configure_port(port, ptcr, pdcr); + } else { + imx_audmux_v1_configure_port(port, pcr); + } + } + + return 0; +} + +static int imx_audmux_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct of_device_id *of_id = + of_match_device(imx_audmux_dt_ids, &pdev->dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + audmux_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(audmux_base)) + return PTR_ERR(audmux_base); + + audmux_clk = devm_clk_get(&pdev->dev, "audmux"); + if (IS_ERR(audmux_clk)) { + dev_dbg(&pdev->dev, "cannot get clock: %ld\n", + PTR_ERR(audmux_clk)); + audmux_clk = NULL; + } + + if (of_id) + pdev->id_entry = of_id->data; + audmux_type = pdev->id_entry->driver_data; + if (audmux_type == IMX31_AUDMUX) + audmux_debugfs_init(); + + if (of_id) + imx_audmux_parse_dt_defaults(pdev, pdev->dev.of_node); + + return 0; +} + +static int imx_audmux_remove(struct platform_device *pdev) +{ + if (audmux_type == IMX31_AUDMUX) + audmux_debugfs_remove(); + + return 0; +} + +static struct platform_driver imx_audmux_driver = { + .probe = imx_audmux_probe, + .remove = imx_audmux_remove, + .id_table = imx_audmux_ids, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx_audmux_dt_ids, + } +}; + +static int __init imx_audmux_init(void) +{ + return platform_driver_register(&imx_audmux_driver); +} +subsys_initcall(imx_audmux_init); + +static void __exit imx_audmux_exit(void) +{ + platform_driver_unregister(&imx_audmux_driver); +} +module_exit(imx_audmux_exit); + +MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver"); +MODULE_AUTHOR("Sascha Hauer "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/sound/soc/fsl/imx-audmux.h b/sound/soc/fsl/imx-audmux.h new file mode 100644 index 000000000..f75b4d3ae --- /dev/null +++ b/sound/soc/fsl/imx-audmux.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __IMX_AUDMUX_H +#define __IMX_AUDMUX_H + +#include + +int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr); + +int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr, + unsigned int pdcr); + +#endif /* __IMX_AUDMUX_H */ diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c new file mode 100644 index 000000000..735693274 --- /dev/null +++ b/sound/soc/fsl/imx-es8328.c @@ -0,0 +1,231 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 +#define MUX_PORT_MAX 7 + +struct imx_es8328_data { + struct device *dev; + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + int jack_gpio; +}; + +static struct snd_soc_jack_gpio headset_jack_gpios[] = { + { + .gpio = -1, + .name = "headset-gpio", + .report = SND_JACK_HEADSET, + .invert = 0, + .debounce_time = 200, + }, +}; + +static struct snd_soc_jack headset_jack; + +static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct imx_es8328_data *data = container_of(rtd->card, + struct imx_es8328_data, card); + int ret = 0; + + /* Headphone jack detection */ + if (gpio_is_valid(data->jack_gpio)) { + ret = snd_soc_card_jack_new(rtd->card, "Headphone", + SND_JACK_HEADPHONE | SND_JACK_BTN_0, + &headset_jack, NULL, 0); + if (ret) + return ret; + + headset_jack_gpios[0].gpio = data->jack_gpio; + ret = snd_soc_jack_add_gpios(&headset_jack, + ARRAY_SIZE(headset_jack_gpios), + headset_jack_gpios); + } + + return ret; +} + +static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0), +}; + +static int imx_es8328_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np = NULL, *codec_np = NULL; + struct platform_device *ssi_pdev; + struct imx_es8328_data *data; + u32 int_port, ext_port; + int ret; + struct device *dev = &pdev->dev; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(dev, "mux-int-port missing or invalid\n"); + goto fail; + } + if (int_port > MUX_PORT_MAX || int_port == 0) { + dev_err(dev, "mux-int-port: hardware only has %d mux ports\n", + MUX_PORT_MAX); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(dev, "mux-ext-port missing or invalid\n"); + goto fail; + } + if (ext_port > MUX_PORT_MAX || ext_port == 0) { + dev_err(dev, "mux-ext-port: hardware only has %d mux ports\n", + MUX_PORT_MAX); + ret = -EINVAL; + goto fail; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(dev, "audmux internal port setup failed\n"); + return ret; + } + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(dev, "audmux external port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!ssi_np || !codec_np) { + dev_err(dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->dev = dev; + + data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0); + + data->dai.name = "hifi"; + data->dai.stream_name = "hifi"; + data->dai.codec_dai_name = "es8328-hifi-analog"; + data->dai.codec_of_node = codec_np; + data->dai.cpu_of_node = ssi_np; + data->dai.platform_of_node = ssi_np; + data->dai.init = &imx_es8328_dai_init; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + data->card.dev = dev; + data->card.dapm_widgets = imx_es8328_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets); + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(dev, "Unable to parse card name\n"); + goto fail; + } + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) { + dev_err(dev, "Unable to parse routing: %d\n", ret); + goto fail; + } + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + data->card.dai_link = &data->dai; + + ret = snd_soc_register_card(&data->card); + if (ret) { + dev_err(dev, "Unable to register: %d\n", ret); + goto fail; + } + + platform_set_drvdata(pdev, data); +fail: + of_node_put(ssi_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_es8328_remove(struct platform_device *pdev) +{ + struct imx_es8328_data *data = platform_get_drvdata(pdev); + + snd_soc_unregister_card(&data->card); + + return 0; +} + +static const struct of_device_id imx_es8328_dt_ids[] = { + { .compatible = "fsl,imx-audio-es8328", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids); + +static struct platform_driver imx_es8328_driver = { + .driver = { + .name = "imx-es8328", + .of_match_table = imx_es8328_dt_ids, + }, + .probe = imx_es8328_probe, + .remove = imx_es8328_remove, +}; +module_platform_driver(imx_es8328_driver); + +MODULE_AUTHOR("Sean Cross "); +MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-audio-es8328"); diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c new file mode 100644 index 000000000..9d19b808f --- /dev/null +++ b/sound/soc/fsl/imx-mc13783.c @@ -0,0 +1,168 @@ +/* + * imx-mc13783.c -- SoC audio for imx based boards with mc13783 codec + * + * Copyright 2012 Philippe Retornaz, + * + * Heavly based on phycore-mc13783: + * Copyright 2009 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/mc13783.h" +#include "imx-ssi.h" +#include "imx-audmux.h" + +#define FMT_SSI (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static int imx_mc13783_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x3, 0x3, 4, 16); + if (ret) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, MC13783_CLK_CLIA, 26000000, 0); + if (ret) + return ret; + + return snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 16); +} + +static const struct snd_soc_ops imx_mc13783_hifi_ops = { + .hw_params = imx_mc13783_hifi_hw_params, +}; + +static struct snd_soc_dai_link imx_mc13783_dai_mc13783[] = { + { + .name = "MC13783", + .stream_name = "Sound", + .codec_dai_name = "mc13783-hifi", + .codec_name = "mc13783-codec", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-ssi.0", + .ops = &imx_mc13783_hifi_ops, + .symmetric_rates = 1, + .dai_fmt = FMT_SSI, + }, +}; + +static const struct snd_soc_dapm_widget imx_mc13783_widget[] = { + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route imx_mc13783_routes[] = { + {"Speaker", NULL, "LSP"}, + {"Headphone", NULL, "HSL"}, + {"Headphone", NULL, "HSR"}, + + {"MC1LIN", NULL, "MC1 Bias"}, + {"MC2IN", NULL, "MC2 Bias"}, + {"MC1 Bias", NULL, "Mic"}, + {"MC2 Bias", NULL, "Mic"}, +}; + +static struct snd_soc_card imx_mc13783 = { + .name = "imx_mc13783", + .owner = THIS_MODULE, + .dai_link = imx_mc13783_dai_mc13783, + .num_links = ARRAY_SIZE(imx_mc13783_dai_mc13783), + .dapm_widgets = imx_mc13783_widget, + .num_dapm_widgets = ARRAY_SIZE(imx_mc13783_widget), + .dapm_routes = imx_mc13783_routes, + .num_dapm_routes = ARRAY_SIZE(imx_mc13783_routes), +}; + +static int imx_mc13783_probe(struct platform_device *pdev) +{ + int ret; + + imx_mc13783.dev = &pdev->dev; + + ret = snd_soc_register_card(&imx_mc13783); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + if (machine_is_mx31_3ds() || machine_is_mx31moboard()) { + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0) | + IMX_AUDMUX_V2_PDCR_MODE(1) | + IMX_AUDMUX_V2_PDCR_INMMASK(0xfc)); + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4), + IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT4_SSI_PINS_4)); + } else if (machine_is_mx27_3ds()) { + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_TCLKDIR | + IMX_AUDMUX_V1_PCR_RFSDIR | + IMX_AUDMUX_V1_PCR_RCLKDIR | + IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) + ); + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) + ); + } + + return ret; +} + +static int imx_mc13783_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&imx_mc13783); + + return 0; +} + +static struct platform_driver imx_mc13783_audio_driver = { + .driver = { + .name = "imx_mc13783", + }, + .probe = imx_mc13783_probe, + .remove = imx_mc13783_remove +}; + +module_platform_driver(imx_mc13783_audio_driver); + +MODULE_AUTHOR("Sascha Hauer "); +MODULE_AUTHOR("Philippe Retornaz + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "imx-pcm.h" + +static bool filter(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = { + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .compat_filter_fn = filter, +}; + +int imx_pcm_dma_init(struct platform_device *pdev, size_t size) +{ + struct snd_dmaengine_pcm_config *config; + + config = devm_kzalloc(&pdev->dev, + sizeof(struct snd_dmaengine_pcm_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + *config = imx_dmaengine_pcm_config; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, + config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(imx_pcm_dma_init); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c new file mode 100644 index 000000000..0578f3486 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-fiq.c @@ -0,0 +1,391 @@ +/* + * imx-pcm-fiq.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "imx-ssi.h" +#include "imx-pcm.h" + +struct imx_pcm_runtime_data { + unsigned int period; + int periods; + unsigned long offset; + struct hrtimer hrt; + int poll_time_ns; + struct snd_pcm_substream *substream; + atomic_t playing; + atomic_t capturing; +}; + +static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) +{ + struct imx_pcm_runtime_data *iprtd = + container_of(hrt, struct imx_pcm_runtime_data, hrt); + struct snd_pcm_substream *substream = iprtd->substream; + struct pt_regs regs; + + if (!atomic_read(&iprtd->playing) && !atomic_read(&iprtd->capturing)) + return HRTIMER_NORESTART; + + get_fiq_regs(®s); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + iprtd->offset = regs.ARM_r8 & 0xffff; + else + iprtd->offset = regs.ARM_r9 & 0xffff; + + snd_pcm_period_elapsed(substream); + + hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns)); + + return HRTIMER_RESTART; +} + +static struct fiq_handler fh = { + .name = DRV_NAME, +}; + +static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + iprtd->periods = params_periods(params); + iprtd->period = params_period_bytes(params); + iprtd->offset = 0; + iprtd->poll_time_ns = 1000000000 / params_rate(params) * + params_period_size(params); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + struct pt_regs regs; + + get_fiq_regs(®s); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16; + else + regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16; + + set_fiq_regs(®s); + + return 0; +} + +static int imx_pcm_fiq; + +static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&iprtd->playing, 1); + else + atomic_set(&iprtd->capturing, 1); + hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns), + HRTIMER_MODE_REL); + enable_fiq(imx_pcm_fiq); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&iprtd->playing, 0); + else + atomic_set(&iprtd->capturing, 0); + if (!atomic_read(&iprtd->playing) && + !atomic_read(&iprtd->capturing)) + disable_fiq(imx_pcm_fiq); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + return bytes_to_frames(substream->runtime, iprtd->offset); +} + +static const struct snd_pcm_hardware snd_imx_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 16 * 1024, + .periods_min = 4, + .periods_max = 255, + .fifo_size = 0, +}; + +static int snd_imx_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd; + int ret; + + iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); + if (iprtd == NULL) + return -ENOMEM; + runtime->private_data = iprtd; + + iprtd->substream = substream; + + atomic_set(&iprtd->playing, 0); + atomic_set(&iprtd->capturing, 0); + hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + iprtd->hrt.function = snd_hrtimer_callback; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(iprtd); + return ret; + } + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + return 0; +} + +static int snd_imx_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + hrtimer_cancel(&iprtd->hrt); + + kfree(iprtd); + + return 0; +} + +static int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); + + pr_debug("%s: ret: %d %p %pad 0x%08zx\n", __func__, ret, + runtime->dma_area, + &runtime->dma_addr, + runtime->dma_bytes); + return ret; +} + +static const struct snd_pcm_ops imx_pcm_ops = { + .open = snd_imx_open, + .close = snd_imx_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_imx_pcm_hw_params, + .prepare = snd_imx_pcm_prepare, + .trigger = snd_imx_pcm_trigger, + .pointer = snd_imx_pcm_pointer, + .mmap = snd_imx_pcm_mmap, +}; + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = IMX_SSI_DMABUF_SIZE; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_wc(pcm->card->dev, size, &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + + return 0; +} + +static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +static int ssi_irq; + +static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_pcm_substream *substream; + int ret; + + ret = imx_pcm_new(rtd); + if (ret) + return ret; + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + struct snd_dma_buffer *buf = &substream->dma_buffer; + + imx_ssi_fiq_tx_buffer = (unsigned long)buf->area; + } + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + struct snd_dma_buffer *buf = &substream->dma_buffer; + + imx_ssi_fiq_rx_buffer = (unsigned long)buf->area; + } + + set_fiq_handler(&imx_ssi_fiq_start, + &imx_ssi_fiq_end - &imx_ssi_fiq_start); + + return 0; +} + +static void imx_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_wc(pcm->card->dev, buf->bytes, buf->area, buf->addr); + buf->area = NULL; + } +} + +static void imx_pcm_fiq_free(struct snd_pcm *pcm) +{ + mxc_set_irq_fiq(ssi_irq, 0); + release_fiq(&fh); + imx_pcm_free(pcm); +} + +static const struct snd_soc_component_driver imx_soc_component_fiq = { + .ops = &imx_pcm_ops, + .pcm_new = imx_pcm_fiq_new, + .pcm_free = imx_pcm_fiq_free, +}; + +int imx_pcm_fiq_init(struct platform_device *pdev, + struct imx_pcm_fiq_params *params) +{ + int ret; + + ret = claim_fiq(&fh); + if (ret) { + dev_err(&pdev->dev, "failed to claim fiq: %d", ret); + return ret; + } + + mxc_set_irq_fiq(params->irq, 1); + ssi_irq = params->irq; + + imx_pcm_fiq = params->irq; + + imx_ssi_fiq_base = (unsigned long)params->base; + + params->dma_params_tx->maxburst = 4; + params->dma_params_rx->maxburst = 6; + + ret = devm_snd_soc_register_component(&pdev->dev, &imx_soc_component_fiq, + NULL, 0); + if (ret) + goto failed_register; + + return 0; + +failed_register: + mxc_set_irq_fiq(ssi_irq, 0); + release_fiq(&fh); + + return ret; +} +EXPORT_SYMBOL_GPL(imx_pcm_fiq_init); + +void imx_pcm_fiq_exit(struct platform_device *pdev) +{ +} +EXPORT_SYMBOL_GPL(imx_pcm_fiq_exit); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h new file mode 100644 index 000000000..133c4470a --- /dev/null +++ b/sound/soc/fsl/imx-pcm.h @@ -0,0 +1,71 @@ +/* + * Copyright 2009 Sascha Hauer + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _IMX_PCM_H +#define _IMX_PCM_H + +#include + +/* + * Do not change this as the FIQ handler depends on this size + */ +#define IMX_SSI_DMABUF_SIZE (64 * 1024) + +#define IMX_DEFAULT_DMABUF_SIZE (64 * 1024) +#define IMX_SAI_DMABUF_SIZE (64 * 1024) +#define IMX_SPDIF_DMABUF_SIZE (64 * 1024) +#define IMX_ESAI_DMABUF_SIZE (256 * 1024) + +static inline void +imx_pcm_dma_params_init_data(struct imx_dma_data *dma_data, + int dma, enum sdma_peripheral_type peripheral_type) +{ + dma_data->dma_request = dma; + dma_data->priority = DMA_PRIO_HIGH; + dma_data->peripheral_type = peripheral_type; +} + +struct imx_pcm_fiq_params { + int irq; + void __iomem *base; + + /* Pointer to original ssi driver to setup tx rx sizes */ + struct snd_dmaengine_dai_dma_data *dma_params_rx; + struct snd_dmaengine_dai_dma_data *dma_params_tx; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA) +int imx_pcm_dma_init(struct platform_device *pdev, size_t size); +#else +static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size) +{ + return -ENODEV; +} +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ) +int imx_pcm_fiq_init(struct platform_device *pdev, + struct imx_pcm_fiq_params *params); +void imx_pcm_fiq_exit(struct platform_device *pdev); +#else +static inline int imx_pcm_fiq_init(struct platform_device *pdev, + struct imx_pcm_fiq_params *params) +{ + return -ENODEV; +} + +static inline void imx_pcm_fiq_exit(struct platform_device *pdev) +{ +} +#endif + +#endif /* _IMX_PCM_H */ diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c new file mode 100644 index 000000000..4bd8da3a5 --- /dev/null +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright 2012 Freescale Semiconductor, Inc. +// Copyright 2012 Linaro Ltd. + +#include +#include +#include +#include +#include +#include + +#include "../codecs/sgtl5000.h" +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 + +struct imx_sgtl5000_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *codec_clk; + unsigned int clk_frequency; +}; + +static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret; + + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, SGTL5000_SYSCLK, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Line Out Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static int imx_sgtl5000_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np, *codec_np; + struct platform_device *ssi_pdev; + struct i2c_client *codec_dev; + struct imx_sgtl5000_data *data = NULL; + int int_port, ext_port; + int ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + return ret; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(&pdev->dev, "audmux internal port setup failed\n"); + return ret; + } + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(&pdev->dev, "audmux external port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!ssi_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + put_device(&ssi_pdev->dev); + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + goto fail; + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + data->dai.name = "HiFi"; + data->dai.stream_name = "HiFi"; + data->dai.codec_dai_name = "sgtl5000"; + data->dai.codec_of_node = codec_np; + data->dai.cpu_of_node = ssi_np; + data->dai.platform_of_node = ssi_np; + data->dai.init = &imx_sgtl5000_dai_init; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + data->card.dai_link = &data->dai; + data->card.dapm_widgets = imx_sgtl5000_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets); + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + of_node_put(ssi_np); + of_node_put(codec_np); + + return 0; + +fail: + if (data && !IS_ERR(data->codec_clk)) + clk_put(data->codec_clk); + of_node_put(ssi_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_sgtl5000_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card); + + clk_put(data->codec_clk); + + return 0; +} + +static const struct of_device_id imx_sgtl5000_dt_ids[] = { + { .compatible = "fsl,imx-audio-sgtl5000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids); + +static struct platform_driver imx_sgtl5000_driver = { + .driver = { + .name = "imx-sgtl5000", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_sgtl5000_dt_ids, + }, + .probe = imx_sgtl5000_probe, + .remove = imx_sgtl5000_remove, +}; +module_platform_driver(imx_sgtl5000_driver); + +MODULE_AUTHOR("Shawn Guo "); +MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-sgtl5000"); diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c new file mode 100644 index 000000000..fb896b2c9 --- /dev/null +++ b/sound/soc/fsl/imx-spdif.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include + +struct imx_spdif_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; +}; + +static int imx_spdif_audio_probe(struct platform_device *pdev) +{ + struct device_node *spdif_np, *np = pdev->dev.of_node; + struct imx_spdif_data *data; + int ret = 0; + + spdif_np = of_parse_phandle(np, "spdif-controller", 0); + if (!spdif_np) { + dev_err(&pdev->dev, "failed to find spdif-controller\n"); + ret = -EINVAL; + goto end; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto end; + } + + data->dai.name = "S/PDIF PCM"; + data->dai.stream_name = "S/PDIF PCM"; + data->dai.codec_dai_name = "snd-soc-dummy-dai"; + data->dai.codec_name = "snd-soc-dummy"; + data->dai.cpu_of_node = spdif_np; + data->dai.platform_of_node = spdif_np; + data->dai.playback_only = true; + data->dai.capture_only = true; + + if (of_property_read_bool(np, "spdif-out")) + data->dai.capture_only = false; + + if (of_property_read_bool(np, "spdif-in")) + data->dai.playback_only = false; + + if (data->dai.playback_only && data->dai.capture_only) { + dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n"); + goto end; + } + + data->card.dev = &pdev->dev; + data->card.dai_link = &data->dai; + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto end; + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret); + goto end; + } + +end: + of_node_put(spdif_np); + + return ret; +} + +static const struct of_device_id imx_spdif_dt_ids[] = { + { .compatible = "fsl,imx-audio-spdif", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_spdif_dt_ids); + +static struct platform_driver imx_spdif_driver = { + .driver = { + .name = "imx-spdif", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_spdif_dt_ids, + }, + .probe = imx_spdif_audio_probe, +}; + +module_platform_driver(imx_spdif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX S/PDIF machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-spdif"); diff --git a/sound/soc/fsl/imx-ssi.c b/sound/soc/fsl/imx-ssi.c new file mode 100644 index 000000000..06790615e --- /dev/null +++ b/sound/soc/fsl/imx-ssi.c @@ -0,0 +1,661 @@ +/* + * imx-ssi.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * + * The i.MX SSI core has some nasty limitations in AC97 mode. While most + * sane processor vendors have a FIFO per AC97 slot, the i.MX has only + * one FIFO which combines all valid receive slots. We cannot even select + * which slots we want to receive. The WM9712 with which this driver + * was developed with always sends GPIO status data in slot 12 which + * we receive in our (PCM-) data stream. The only chance we have is to + * manually skip this data in the FIQ handler. With sampling rates different + * from 48000Hz not every frame has valid receive data, so the ratio + * between pcm data and GPIO status data changes. Our FIQ handler is not + * able to handle this, hence this driver only works with 48000Hz sampling + * rate. + * Reading and writing AC97 registers is another challenge. The core + * provides us status bits when the read register is updated with *another* + * value. When we read the same register two times (and the register still + * contains the same value) these status bits are not set. We work + * around this by not polling these bits but only wait a fixed delay. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "imx-ssi.h" +#include "fsl_utils.h" + +#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV) + +/* + * SSI Network Mode or TDM slots configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); + u32 sccr; + + sccr = readl(ssi->base + SSI_STCCR); + sccr &= ~SSI_STCCR_DC_MASK; + sccr |= SSI_STCCR_DC(slots - 1); + writel(sccr, ssi->base + SSI_STCCR); + + sccr = readl(ssi->base + SSI_SRCCR); + sccr &= ~SSI_STCCR_DC_MASK; + sccr |= SSI_STCCR_DC(slots - 1); + writel(sccr, ssi->base + SSI_SRCCR); + + writel(~tx_mask, ssi->base + SSI_STMSK); + writel(~rx_mask, ssi->base + SSI_SRMSK); + + return 0; +} + +/* + * SSI DAI format configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); + u32 strcr = 0, scr; + + scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET); + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data on rising edge of bclk, frame low 1clk before data */ + strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSI | + SSI_STCR_TEFS; + scr |= SSI_SCR_NET; + if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) { + scr &= ~SSI_I2S_MODE_MASK; + scr |= SSI_SCR_I2S_MODE_SLAVE; + } + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data on rising edge of bclk, frame high with data */ + strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_DSP_B: + /* data on rising edge of bclk, frame high with data */ + strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSL; + break; + case SND_SOC_DAIFMT_DSP_A: + /* data on rising edge of bclk, frame high 1clk before data */ + strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSL | + SSI_STCR_TEFS; + break; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + strcr ^= SSI_STCR_TSCKP | SSI_STCR_TFSI; + break; + case SND_SOC_DAIFMT_IB_NF: + strcr ^= SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + strcr ^= SSI_STCR_TFSI; + break; + case SND_SOC_DAIFMT_NB_NF: + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + /* Master mode not implemented, needs handling of clocks. */ + return -EINVAL; + } + + strcr |= SSI_STCR_TFEN0; + + if (ssi->flags & IMX_SSI_NET) + scr |= SSI_SCR_NET; + if (ssi->flags & IMX_SSI_SYN) + scr |= SSI_SCR_SYN; + + writel(strcr, ssi->base + SSI_STCR); + writel(strcr, ssi->base + SSI_SRCR); + writel(scr, ssi->base + SSI_SCR); + + return 0; +} + +/* + * SSI system clock configuration. + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); + u32 scr; + + scr = readl(ssi->base + SSI_SCR); + + switch (clk_id) { + case IMX_SSP_SYS_CLK: + if (dir == SND_SOC_CLOCK_OUT) + scr |= SSI_SCR_SYS_CLK_EN; + else + scr &= ~SSI_SCR_SYS_CLK_EN; + break; + default: + return -EINVAL; + } + + writel(scr, ssi->base + SSI_SCR); + + return 0; +} + +/* + * SSI Clock dividers + * Should only be called when port is inactive (i.e. SSIEN = 0). + */ +static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); + u32 stccr, srccr; + + stccr = readl(ssi->base + SSI_STCCR); + srccr = readl(ssi->base + SSI_SRCCR); + + switch (div_id) { + case IMX_SSI_TX_DIV_2: + stccr &= ~SSI_STCCR_DIV2; + stccr |= div; + break; + case IMX_SSI_TX_DIV_PSR: + stccr &= ~SSI_STCCR_PSR; + stccr |= div; + break; + case IMX_SSI_TX_DIV_PM: + stccr &= ~0xff; + stccr |= SSI_STCCR_PM(div); + break; + case IMX_SSI_RX_DIV_2: + stccr &= ~SSI_STCCR_DIV2; + stccr |= div; + break; + case IMX_SSI_RX_DIV_PSR: + stccr &= ~SSI_STCCR_PSR; + stccr |= div; + break; + case IMX_SSI_RX_DIV_PM: + stccr &= ~0xff; + stccr |= SSI_STCCR_PM(div); + break; + default: + return -EINVAL; + } + + writel(stccr, ssi->base + SSI_STCCR); + writel(srccr, ssi->base + SSI_SRCCR); + + return 0; +} + +/* + * Should only be called when port is inactive (i.e. SSIEN = 0), + * although can be called multiple times by upper layers. + */ +static int imx_ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg, sccr; + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = SSI_STCCR; + else + reg = SSI_SRCCR; + + if (ssi->flags & IMX_SSI_SYN) + reg = SSI_STCCR; + + sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK; + + /* DAI data (word) size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + sccr |= SSI_SRCCR_WL(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + sccr |= SSI_SRCCR_WL(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + sccr |= SSI_SRCCR_WL(24); + break; + } + + writel(sccr, ssi->base + reg); + + return 0; +} + +static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai); + unsigned int sier_bits, sier; + unsigned int scr; + + scr = readl(ssi->base + SSI_SCR); + sier = readl(ssi->base + SSI_SIER); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (ssi->flags & IMX_SSI_DMA) + sier_bits = SSI_SIER_TDMAE; + else + sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN; + } else { + if (ssi->flags & IMX_SSI_DMA) + sier_bits = SSI_SIER_RDMAE; + else + sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr |= SSI_SCR_TE; + else + scr |= SSI_SCR_RE; + sier |= sier_bits; + + scr |= SSI_SCR_SSIEN; + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr &= ~SSI_SCR_TE; + else + scr &= ~SSI_SCR_RE; + sier &= ~sier_bits; + + if (!(scr & (SSI_SCR_TE | SSI_SCR_RE))) + scr &= ~SSI_SCR_SSIEN; + + break; + default: + return -EINVAL; + } + + if (!(ssi->flags & IMX_SSI_USE_AC97)) + /* rx/tx are always enabled to access ac97 registers */ + writel(scr, ssi->base + SSI_SCR); + + writel(sier, ssi->base + SSI_SIER); + + return 0; +} + +static const struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = { + .hw_params = imx_ssi_hw_params, + .set_fmt = imx_ssi_set_dai_fmt, + .set_clkdiv = imx_ssi_set_dai_clkdiv, + .set_sysclk = imx_ssi_set_dai_sysclk, + .set_tdm_slot = imx_ssi_set_dai_tdm_slot, + .trigger = imx_ssi_trigger, +}; + +static int imx_ssi_dai_probe(struct snd_soc_dai *dai) +{ + struct imx_ssi *ssi = dev_get_drvdata(dai->dev); + uint32_t val; + + snd_soc_dai_set_drvdata(dai, ssi); + + val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.maxburst) | + SSI_SFCSR_RFWM0(ssi->dma_params_rx.maxburst); + writel(val, ssi->base + SSI_SFCSR); + + /* Tx/Rx config */ + dai->playback_dma_data = &ssi->dma_params_tx; + dai->capture_dma_data = &ssi->dma_params_rx; + + return 0; +} + +static struct snd_soc_dai_driver imx_ssi_dai = { + .probe = imx_ssi_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &imx_ssi_pcm_dai_ops, +}; + +static struct snd_soc_dai_driver imx_ac97_dai = { + .probe = imx_ssi_dai_probe, + .bus_control = true, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &imx_ssi_pcm_dai_ops, +}; + +static const struct snd_soc_component_driver imx_component = { + .name = DRV_NAME, +}; + +static void setup_channel_to_ac97(struct imx_ssi *imx_ssi) +{ + void __iomem *base = imx_ssi->base; + + writel(0x0, base + SSI_SCR); + writel(0x0, base + SSI_STCR); + writel(0x0, base + SSI_SRCR); + + writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR); + + writel(SSI_SFCSR_RFWM0(8) | + SSI_SFCSR_TFWM0(8) | + SSI_SFCSR_RFWM1(8) | + SSI_SFCSR_TFWM1(8), base + SSI_SFCSR); + + writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR); + writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR); + + writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR); + writel(SSI_SOR_WAIT(3), base + SSI_SOR); + + writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN | + SSI_SCR_TE | SSI_SCR_RE, + base + SSI_SCR); + + writel(SSI_SACNT_DEFAULT, base + SSI_SACNT); + writel(0xff, base + SSI_SACCDIS); + writel(0x300, base + SSI_SACCEN); +} + +static struct imx_ssi *ac97_ssi; + +static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + void __iomem *base = imx_ssi->base; + unsigned int lreg; + unsigned int lval; + + if (reg > 0x7f) + return; + + pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); + + lreg = reg << 12; + writel(lreg, base + SSI_SACADD); + + lval = val << 4; + writel(lval , base + SSI_SACDAT); + + writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT); + udelay(100); +} + +static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + void __iomem *base = imx_ssi->base; + + unsigned short val = -1; + unsigned int lreg; + + lreg = (reg & 0x7f) << 12 ; + writel(lreg, base + SSI_SACADD); + writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT); + + udelay(100); + + val = (readl(base + SSI_SACDAT) >> 4) & 0xffff; + + pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); + + return val; +} + +static void imx_ssi_ac97_reset(struct snd_ac97 *ac97) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + + if (imx_ssi->ac97_reset) + imx_ssi->ac97_reset(ac97); + /* First read sometimes fails, do a dummy read */ + imx_ssi_ac97_read(ac97, 0); +} + +static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct imx_ssi *imx_ssi = ac97_ssi; + + if (imx_ssi->ac97_warm_reset) + imx_ssi->ac97_warm_reset(ac97); + + /* First read sometimes fails, do a dummy read */ + imx_ssi_ac97_read(ac97, 0); +} + +static struct snd_ac97_bus_ops imx_ssi_ac97_ops = { + .read = imx_ssi_ac97_read, + .write = imx_ssi_ac97_write, + .reset = imx_ssi_ac97_reset, + .warm_reset = imx_ssi_ac97_warm_reset +}; + +static int imx_ssi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imx_ssi *ssi; + struct imx_ssi_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + struct snd_soc_dai_driver *dai; + + ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL); + if (!ssi) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, ssi); + + if (pdata) { + ssi->ac97_reset = pdata->ac97_reset; + ssi->ac97_warm_reset = pdata->ac97_warm_reset; + ssi->flags = pdata->flags; + } + + ssi->irq = platform_get_irq(pdev, 0); + if (ssi->irq < 0) { + dev_err(&pdev->dev, "Failed to get IRQ: %d\n", ssi->irq); + return ssi->irq; + } + + ssi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ssi->clk)) { + ret = PTR_ERR(ssi->clk); + dev_err(&pdev->dev, "Cannot get the clock: %d\n", + ret); + goto failed_clk; + } + ret = clk_prepare_enable(ssi->clk); + if (ret) + goto failed_clk; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ssi->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ssi->base)) { + ret = PTR_ERR(ssi->base); + goto failed_register; + } + + if (ssi->flags & IMX_SSI_USE_AC97) { + if (ac97_ssi) { + dev_err(&pdev->dev, "AC'97 SSI already registered\n"); + ret = -EBUSY; + goto failed_register; + } + ac97_ssi = ssi; + setup_channel_to_ac97(ssi); + dai = &imx_ac97_dai; + } else + dai = &imx_ssi_dai; + + writel(0x0, ssi->base + SSI_SIER); + + ssi->dma_params_rx.addr = res->start + SSI_SRX0; + ssi->dma_params_tx.addr = res->start + SSI_STX0; + + ssi->dma_params_tx.maxburst = 6; + ssi->dma_params_rx.maxburst = 4; + + ssi->dma_params_tx.filter_data = &ssi->filter_data_tx; + ssi->dma_params_rx.filter_data = &ssi->filter_data_rx; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); + if (res) { + imx_pcm_dma_params_init_data(&ssi->filter_data_tx, res->start, + IMX_DMATYPE_SSI); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0"); + if (res) { + imx_pcm_dma_params_init_data(&ssi->filter_data_rx, res->start, + IMX_DMATYPE_SSI); + } + + platform_set_drvdata(pdev, ssi); + + ret = snd_soc_set_ac97_ops(&imx_ssi_ac97_ops); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret); + goto failed_register; + } + + ret = snd_soc_register_component(&pdev->dev, &imx_component, + dai, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + goto failed_register; + } + + ssi->fiq_params.irq = ssi->irq; + ssi->fiq_params.base = ssi->base; + ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx; + ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx; + + ssi->fiq_init = imx_pcm_fiq_init(pdev, &ssi->fiq_params); + ssi->dma_init = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE); + + if (ssi->fiq_init && ssi->dma_init) { + ret = ssi->fiq_init; + goto failed_pcm; + } + + return 0; + +failed_pcm: + snd_soc_unregister_component(&pdev->dev); +failed_register: + clk_disable_unprepare(ssi->clk); +failed_clk: + snd_soc_set_ac97_ops(NULL); + + return ret; +} + +static int imx_ssi_remove(struct platform_device *pdev) +{ + struct imx_ssi *ssi = platform_get_drvdata(pdev); + + if (!ssi->fiq_init) + imx_pcm_fiq_exit(pdev); + + snd_soc_unregister_component(&pdev->dev); + + if (ssi->flags & IMX_SSI_USE_AC97) + ac97_ssi = NULL; + + clk_disable_unprepare(ssi->clk); + snd_soc_set_ac97_ops(NULL); + + return 0; +} + +static struct platform_driver imx_ssi_driver = { + .probe = imx_ssi_probe, + .remove = imx_ssi_remove, + + .driver = { + .name = "imx-ssi", + }, +}; + +module_platform_driver(imx_ssi_driver); + +/* Module information */ +MODULE_AUTHOR("Sascha Hauer, "); +MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ssi"); diff --git a/sound/soc/fsl/imx-ssi.h b/sound/soc/fsl/imx-ssi.h new file mode 100644 index 000000000..be6562365 --- /dev/null +++ b/sound/soc/fsl/imx-ssi.h @@ -0,0 +1,218 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _IMX_SSI_H +#define _IMX_SSI_H + +#define SSI_STX0 0x00 +#define SSI_STX1 0x04 +#define SSI_SRX0 0x08 +#define SSI_SRX1 0x0c + +#define SSI_SCR 0x10 +#define SSI_SCR_CLK_IST (1 << 9) +#define SSI_SCR_CLK_IST_SHIFT 9 +#define SSI_SCR_TCH_EN (1 << 8) +#define SSI_SCR_SYS_CLK_EN (1 << 7) +#define SSI_SCR_I2S_MODE_NORM (0 << 5) +#define SSI_SCR_I2S_MODE_MSTR (1 << 5) +#define SSI_SCR_I2S_MODE_SLAVE (2 << 5) +#define SSI_I2S_MODE_MASK (3 << 5) +#define SSI_SCR_SYN (1 << 4) +#define SSI_SCR_NET (1 << 3) +#define SSI_SCR_RE (1 << 2) +#define SSI_SCR_TE (1 << 1) +#define SSI_SCR_SSIEN (1 << 0) + +#define SSI_SISR 0x14 +#define SSI_SISR_MASK ((1 << 19) - 1) +#define SSI_SISR_CMDAU (1 << 18) +#define SSI_SISR_CMDDU (1 << 17) +#define SSI_SISR_RXT (1 << 16) +#define SSI_SISR_RDR1 (1 << 15) +#define SSI_SISR_RDR0 (1 << 14) +#define SSI_SISR_TDE1 (1 << 13) +#define SSI_SISR_TDE0 (1 << 12) +#define SSI_SISR_ROE1 (1 << 11) +#define SSI_SISR_ROE0 (1 << 10) +#define SSI_SISR_TUE1 (1 << 9) +#define SSI_SISR_TUE0 (1 << 8) +#define SSI_SISR_TFS (1 << 7) +#define SSI_SISR_RFS (1 << 6) +#define SSI_SISR_TLS (1 << 5) +#define SSI_SISR_RLS (1 << 4) +#define SSI_SISR_RFF1 (1 << 3) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SISR_TFE1 (1 << 1) +#define SSI_SISR_TFE0 (1 << 0) + +#define SSI_SIER 0x18 +#define SSI_SIER_RDMAE (1 << 22) +#define SSI_SIER_RIE (1 << 21) +#define SSI_SIER_TDMAE (1 << 20) +#define SSI_SIER_TIE (1 << 19) +#define SSI_SIER_CMDAU_EN (1 << 18) +#define SSI_SIER_CMDDU_EN (1 << 17) +#define SSI_SIER_RXT_EN (1 << 16) +#define SSI_SIER_RDR1_EN (1 << 15) +#define SSI_SIER_RDR0_EN (1 << 14) +#define SSI_SIER_TDE1_EN (1 << 13) +#define SSI_SIER_TDE0_EN (1 << 12) +#define SSI_SIER_ROE1_EN (1 << 11) +#define SSI_SIER_ROE0_EN (1 << 10) +#define SSI_SIER_TUE1_EN (1 << 9) +#define SSI_SIER_TUE0_EN (1 << 8) +#define SSI_SIER_TFS_EN (1 << 7) +#define SSI_SIER_RFS_EN (1 << 6) +#define SSI_SIER_TLS_EN (1 << 5) +#define SSI_SIER_RLS_EN (1 << 4) +#define SSI_SIER_RFF1_EN (1 << 3) +#define SSI_SIER_RFF0_EN (1 << 2) +#define SSI_SIER_TFE1_EN (1 << 1) +#define SSI_SIER_TFE0_EN (1 << 0) + +#define SSI_STCR 0x1c +#define SSI_STCR_TXBIT0 (1 << 9) +#define SSI_STCR_TFEN1 (1 << 8) +#define SSI_STCR_TFEN0 (1 << 7) +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_STCR_TFDIR (1 << 6) +#define SSI_STCR_TXDIR (1 << 5) +#define SSI_STCR_TSHFD (1 << 4) +#define SSI_STCR_TSCKP (1 << 3) +#define SSI_STCR_TFSI (1 << 2) +#define SSI_STCR_TFSL (1 << 1) +#define SSI_STCR_TEFS (1 << 0) + +#define SSI_SRCR 0x20 +#define SSI_SRCR_RXBIT0 (1 << 9) +#define SSI_SRCR_RFEN1 (1 << 8) +#define SSI_SRCR_RFEN0 (1 << 7) +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_SRCR_RFDIR (1 << 6) +#define SSI_SRCR_RXDIR (1 << 5) +#define SSI_SRCR_RSHFD (1 << 4) +#define SSI_SRCR_RSCKP (1 << 3) +#define SSI_SRCR_RFSI (1 << 2) +#define SSI_SRCR_RFSL (1 << 1) +#define SSI_SRCR_REFS (1 << 0) + +#define SSI_SRCCR 0x28 +#define SSI_SRCCR_DIV2 (1 << 18) +#define SSI_SRCCR_PSR (1 << 17) +#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0) +#define SSI_SRCCR_WL_MASK (0xf << 13) +#define SSI_SRCCR_DC_MASK (0x1f << 8) +#define SSI_SRCCR_PM_MASK (0xff << 0) + +#define SSI_STCCR 0x24 +#define SSI_STCCR_DIV2 (1 << 18) +#define SSI_STCCR_PSR (1 << 17) +#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13) +#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8) +#define SSI_STCCR_PM(x) (((x) & 0xff) << 0) +#define SSI_STCCR_WL_MASK (0xf << 13) +#define SSI_STCCR_DC_MASK (0x1f << 8) +#define SSI_STCCR_PM_MASK (0xff << 0) + +#define SSI_SFCSR 0x2c +#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28) +#define SSI_RX_FIFO_1_COUNT_SHIFT 28 +#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24) +#define SSI_TX_FIFO_1_COUNT_SHIFT 24 +#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20) +#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16) +#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12) +#define SSI_RX_FIFO_0_COUNT_SHIFT 12 +#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8) +#define SSI_TX_FIFO_0_COUNT_SHIFT 8 +#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4) +#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0) +#define SSI_SFCSR_RFWM0_MASK (0xf << 4) +#define SSI_SFCSR_TFWM0_MASK (0xf << 0) + +#define SSI_STR 0x30 +#define SSI_STR_TEST (1 << 15) +#define SSI_STR_RCK2TCK (1 << 14) +#define SSI_STR_RFS2TFS (1 << 13) +#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8) +#define SSI_STR_TXD2RXD (1 << 7) +#define SSI_STR_TCK2RCK (1 << 6) +#define SSI_STR_TFS2RFS (1 << 5) +#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0) + +#define SSI_SOR 0x34 +#define SSI_SOR_CLKOFF (1 << 6) +#define SSI_SOR_RX_CLR (1 << 5) +#define SSI_SOR_TX_CLR (1 << 4) +#define SSI_SOR_INIT (1 << 3) +#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1) +#define SSI_SOR_WAIT_MASK (0x3 << 1) +#define SSI_SOR_SYNRST (1 << 0) + +#define SSI_SACNT 0x38 +#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) +#define SSI_SACNT_WR (1 << 4) +#define SSI_SACNT_RD (1 << 3) +#define SSI_SACNT_TIF (1 << 2) +#define SSI_SACNT_FV (1 << 1) +#define SSI_SACNT_AC97EN (1 << 0) + +#define SSI_SACADD 0x3c +#define SSI_SACDAT 0x40 +#define SSI_SATAG 0x44 +#define SSI_STMSK 0x48 +#define SSI_SRMSK 0x4c +#define SSI_SACCST 0x50 +#define SSI_SACCEN 0x54 +#define SSI_SACCDIS 0x58 + +/* SSI clock sources */ +#define IMX_SSP_SYS_CLK 0 + +/* SSI audio dividers */ +#define IMX_SSI_TX_DIV_2 0 +#define IMX_SSI_TX_DIV_PSR 1 +#define IMX_SSI_TX_DIV_PM 2 +#define IMX_SSI_RX_DIV_2 3 +#define IMX_SSI_RX_DIV_PSR 4 +#define IMX_SSI_RX_DIV_PM 5 + +#define DRV_NAME "imx-ssi" + +#include +#include +#include +#include "imx-pcm.h" + +struct imx_ssi { + struct platform_device *ac97_dev; + + struct snd_soc_dai *imx_ac97; + struct clk *clk; + void __iomem *base; + int irq; + int fiq_enable; + unsigned int offset; + + unsigned int flags; + + void (*ac97_reset) (struct snd_ac97 *ac97); + void (*ac97_warm_reset)(struct snd_ac97 *ac97); + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct imx_dma_data filter_data_tx; + struct imx_dma_data filter_data_rx; + struct imx_pcm_fiq_params fiq_params; + + int fiq_init; + int dma_init; +}; + +#endif /* _IMX_SSI_H */ diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c new file mode 100644 index 000000000..c1a4544eb --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.c @@ -0,0 +1,514 @@ +/* + * Freescale MPC5200 PSC DMA + * ALSA SoC Platform driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "mpc5200_dma.h" + +#define DRV_NAME "mpc5200_dma" + +/* + * Interrupt handlers + */ +static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma) +{ + struct psc_dma *psc_dma = _psc_dma; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + + /* Playback underrun error */ + if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_dma->stats.underrun_count++; + + /* Capture overrun error */ + if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) + psc_dma->stats.overrun_count++; + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + + return IRQ_HANDLED; +} + +/** + * psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer + * @s: pointer to stream private data structure + * + * Enqueues another audio period buffer into the bestcomm queue. + * + * Note: The routine must only be called when there is space available in + * the queue. Otherwise the enqueue will fail and the audio ring buffer + * will get out of sync + */ +static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s) +{ + struct bcom_bd *bd; + + /* Prepare and enqueue the next buffer descriptor */ + bd = bcom_prepare_next_buffer(s->bcom_task); + bd->status = s->period_bytes; + bd->data[0] = s->runtime->dma_addr + (s->period_next * s->period_bytes); + bcom_submit_next_buffer(s->bcom_task, NULL); + + /* Update for next period */ + s->period_next = (s->period_next + 1) % s->runtime->periods; +} + +/* Bestcomm DMA irq handler */ +static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) +{ + struct psc_dma_stream *s = _psc_dma_stream; + + spin_lock(&s->psc_dma->lock); + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + s->period_current = (s->period_current+1) % s->runtime->periods; + s->period_count++; + + psc_dma_bcom_enqueue_next_buffer(s); + } + spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +static int psc_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +/** + * psc_dma_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + */ +static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; + unsigned long flags; + int i; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(psc_dma->dev, "START: stream=%i fbits=%u ps=%u #p=%u\n", + substream->pstr->stream, runtime->frame_bits, + (int)runtime->period_size, runtime->periods); + s->period_bytes = frames_to_bytes(runtime, + runtime->period_size); + s->period_next = 0; + s->period_current = 0; + s->active = 1; + s->period_count = 0; + s->runtime = runtime; + + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ + spin_lock_irqsave(&psc_dma->lock, flags); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + + for (i = 0; i < runtime->periods; i++) + if (!bcom_queue_full(s->bcom_task)) + psc_dma_bcom_enqueue_next_buffer(s); + + bcom_enable(s->bcom_task); + spin_unlock_irqrestore(&psc_dma->lock, flags); + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(psc_dma->dev, "STOP: stream=%i periods_count=%i\n", + substream->pstr->stream, s->period_count); + s->active = 0; + + spin_lock_irqsave(&psc_dma->lock, flags); + bcom_disable(s->bcom_task); + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + spin_unlock_irqrestore(&psc_dma->lock, flags); + + break; + + default: + dev_dbg(psc_dma->dev, "unhandled trigger: stream=%i cmd=%i\n", + substream->pstr->stream, cmd); + return -EINVAL; + } + + /* Update interrupt enable settings */ + imr = 0; + if (psc_dma->playback.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_dma->capture.active) + imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, psc_dma->imr | imr); + + return 0; +} + + +/* --------------------------------------------------------------------- + * The PSC DMA 'ASoC platform' driver + * + * Can be referenced by an 'ASoC machine' driver + * This driver only deals with the audio bus; it doesn't have any + * interaction with the attached codec + */ + +static const struct snd_pcm_hardware psc_dma_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + .period_bytes_max = 1024 * 1024, + .period_bytes_min = 32, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 512, +}; + +static int psc_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct psc_dma_stream *s; + int rc; + + dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware); + + rc = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (rc < 0) { + dev_err(substream->pcm->card->dev, "invalid buffer size\n"); + return rc; + } + + s->stream = substream; + return 0; +} + +static int psc_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct psc_dma_stream *s; + + dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + if (!psc_dma->playback.active && + !psc_dma->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ + } + s->stream = NULL; + return 0; +} + +static snd_pcm_uframes_t +psc_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct psc_dma_stream *s; + dma_addr_t count; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + count = s->period_current * s->period_bytes; + + return bytes_to_frames(substream->runtime, count); +} + +static int +psc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static const struct snd_pcm_ops psc_dma_ops = { + .open = psc_dma_open, + .close = psc_dma_close, + .hw_free = psc_dma_hw_free, + .ioctl = snd_pcm_lib_ioctl, + .pointer = psc_dma_pointer, + .trigger = psc_dma_trigger, + .hw_params = psc_dma_hw_params, +}; + +static int psc_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_pcm *pcm = rtd->pcm; + size_t size = psc_dma_hardware.buffer_bytes_max; + int rc; + + dev_dbg(component->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n", + card, dai, pcm); + + rc = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (rc) + return rc; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + if (rc) + goto playback_alloc_err; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer); + if (rc) + goto capture_alloc_err; + } + + return 0; + + capture_alloc_err: + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + + playback_alloc_err: + dev_err(card->dev, "Cannot allocate buffer(s)\n"); + + return -ENOMEM; +} + +static void psc_dma_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_pcm_substream *substream; + int stream; + + dev_dbg(component->dev, "psc_dma_free(pcm=%p)\n", pcm); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static const struct snd_soc_component_driver mpc5200_audio_dma_component = { + .name = DRV_NAME, + .ops = &psc_dma_ops, + .pcm_new = &psc_dma_new, + .pcm_free = &psc_dma_free, +}; + +int mpc5200_audio_dma_create(struct platform_device *op) +{ + phys_addr_t fifo; + struct psc_dma *psc_dma; + struct resource res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; + int ret; + + /* Fetch the registers and IRQ of the PSC */ + irq = irq_of_parse_and_map(op->dev.of_node, 0); + if (of_address_to_resource(op->dev.of_node, 0, &res)) { + dev_err(&op->dev, "Missing reg property\n"); + return -ENODEV; + } + regs = ioremap(res.start, resource_size(&res)); + if (!regs) { + dev_err(&op->dev, "Could not map registers\n"); + return -ENODEV; + } + + /* Allocate and initialize the driver private data */ + psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL); + if (!psc_dma) { + ret = -ENOMEM; + goto out_unmap; + } + + /* Get the PSC ID */ + prop = of_get_property(op->dev.of_node, "cell-index", &size); + if (!prop || size < sizeof *prop) { + ret = -ENODEV; + goto out_free; + } + + spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); + psc_dma->id = be32_to_cpu(*prop); + psc_dma->irq = irq; + psc_dma->psc_regs = regs; + psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs; + psc_dma->dev = &op->dev; + psc_dma->playback.psc_dma = psc_dma; + psc_dma->capture.psc_dma = psc_dma; + snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id); + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = + bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo); + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + ret = -ENODEV; + goto out_free; + } + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + /* reset receiver */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX); + /* reset transmitter */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX); + /* reset error */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT); + /* reset mode */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1); + + /* Set up mode register; + * First write: RxRdy (FIFO Alarm) generates rx FIFO irq + * Second write: register Normal mode for non loopback + */ + out_8(&psc_dma->psc_regs->mode, 0); + out_8(&psc_dma->psc_regs->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&psc_dma->fifo_regs->rfalarm, 0x100); + out_8(&psc_dma->fifo_regs->rfcntl, 0x4); + out_be16(&psc_dma->fifo_regs->tfalarm, 0x100); + out_8(&psc_dma->fifo_regs->tfcntl, 0x7); + + /* Lookup the IRQ numbers */ + psc_dma->playback.irq = + bcom_get_task_irq(psc_dma->playback.bcom_task); + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + + rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); + rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); + rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); + if (rc) { + ret = -ENODEV; + goto out_irq; + } + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); + + /* Tell the ASoC OF helpers about it */ + return devm_snd_soc_register_component(&op->dev, + &mpc5200_audio_dma_component, NULL, 0); +out_irq: + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, &psc_dma->capture); + free_irq(psc_dma->playback.irq, &psc_dma->playback); +out_free: + kfree(psc_dma); +out_unmap: + iounmap(regs); + return ret; +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +int mpc5200_audio_dma_destroy(struct platform_device *op) +{ + struct psc_dma *psc_dma = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n"); + + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + + /* Release irqs */ + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, &psc_dma->capture); + free_irq(psc_dma->playback.irq, &psc_dma->playback); + + iounmap(psc_dma->psc_regs); + kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_destroy); + +MODULE_AUTHOR("Grant Likely "); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h new file mode 100644 index 000000000..d7ee33b5b --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Freescale MPC5200 Audio DMA driver + */ + +#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__ +#define __SOUND_SOC_FSL_MPC5200_DMA_H__ + +#define PSC_STREAM_NAME_LEN 32 + +/** + * psc_ac97_stream - Data specific to a single stream (playback or capture) + * @active: flag indicating if the stream is active + * @psc_dma: pointer back to parent psc_dma data structure + * @bcom_task: bestcomm task structure + * @irq: irq number for bestcomm task + * @period_end: physical address of end of DMA region + * @period_next_pt: physical address of next DMA buffer to enqueue + * @period_bytes: size of DMA period in bytes + * @ac97_slot_bits: Enable bits for turning on the correct AC97 slot + */ +struct psc_dma_stream { + struct snd_pcm_runtime *runtime; + int active; + struct psc_dma *psc_dma; + struct bcom_task *bcom_task; + int irq; + struct snd_pcm_substream *stream; + int period_next; + int period_current; + int period_bytes; + int period_count; + + /* AC97 state */ + u32 ac97_slot_bits; +}; + +/** + * psc_dma - Private driver data + * @name: short name for this device ("PSC0", "PSC1", etc) + * @psc_regs: pointer to the PSC's registers + * @fifo_regs: pointer to the PSC's FIFO registers + * @irq: IRQ of this PSC + * @dev: struct device pointer + * @dai: the CPU DAI for this device + * @sicr: Base value used in serial interface control register; mode is ORed + * with this value. + * @playback: Playback stream context data + * @capture: Capture stream context data + */ +struct psc_dma { + char name[32]; + struct mpc52xx_psc __iomem *psc_regs; + struct mpc52xx_psc_fifo __iomem *fifo_regs; + unsigned int irq; + struct device *dev; + spinlock_t lock; + struct mutex mutex; + u32 sicr; + uint sysclk; + int imr; + int id; + unsigned int slots; + + /* per-stream data */ + struct psc_dma_stream playback; + struct psc_dma_stream capture; + + /* Statistics */ + struct { + unsigned long overrun_count; + unsigned long underrun_count; + } stats; +}; + +/* Utility for retrieving psc_dma_stream structure from a substream */ +static inline struct psc_dma_stream * +to_psc_dma_stream(struct snd_pcm_substream *substream, struct psc_dma *psc_dma) +{ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + return &psc_dma->capture; + return &psc_dma->playback; +} + +int mpc5200_audio_dma_create(struct platform_device *op); +int mpc5200_audio_dma_destroy(struct platform_device *op); + +#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c new file mode 100644 index 000000000..07ee355ee --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -0,0 +1,350 @@ +/* + * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip. + * + * Copyright (C) 2009 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "mpc5200_dma.h" + +#define DRV_NAME "mpc5200-psc-ac97" + +/* ALSA only supports a single AC97 device so static is recommend here */ +static struct psc_dma *psc_dma; + +static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + int status; + unsigned int val; + + mutex_lock(&psc_dma->mutex); + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); + mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + + /* Force clear the data valid bit */ + in_be32(&psc_dma->psc_regs->ac97_data); + + /* Send the read */ + out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24)); + + /* Wait for the answer */ + status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_DATA_VAL), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); + mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); + mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + + mutex_unlock(&psc_dma->mutex); + return (unsigned short) val; +} + +static void psc_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int status; + + mutex_lock(&psc_dma->mutex); + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); + goto out; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); + + out: + mutex_unlock(&psc_dma->mutex); +} + +static void psc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + mutex_lock(&psc_dma->mutex); + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); + + mutex_unlock(&psc_dma->mutex); +} + +static void psc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + mutex_lock(&psc_dma->mutex); + dev_dbg(psc_dma->dev, "cold reset\n"); + + mpc5200_psc_ac97_gpio_reset(psc_dma->id); + + /* Notify the PSC that a reset has occurred */ + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); + + /* Re-enable RX and TX */ + out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + + mutex_unlock(&psc_dma->mutex); + + usleep_range(1000, 2000); + psc_ac97_warm_reset(ac97); +} + +static struct snd_ac97_bus_ops psc_ac97_ops = { + .read = psc_ac97_read, + .write = psc_ac97_write, + .reset = psc_ac97_cold_reset, + .warm_reset = psc_ac97_warm_reset, +}; + +static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i channels=%i" + " rate=%i format=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params), + params_channels(params), params_rate(params), + params_format(params)); + + /* Determine the set of enable bits to turn on */ + s->ac97_slot_bits = (params_channels(params) == 1) ? 0x100 : 0x300; + if (substream->pstr->stream != SNDRV_PCM_STREAM_CAPTURE) + s->ac97_slot_bits <<= 16; + return 0; +} + +static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + + dev_dbg(psc_dma->dev, "%s(substream=%p)\n", __func__, substream); + + if (params_channels(params) == 1) + out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000); + else + out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000); + + return 0; +} + +static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(dai); + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(psc_dma->dev, "AC97 START: stream=%i\n", + substream->pstr->stream); + + /* Set the slot enable bits */ + psc_dma->slots |= s->ac97_slot_bits; + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + break; + + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(psc_dma->dev, "AC97 STOP: stream=%i\n", + substream->pstr->stream); + + /* Clear the slot enable bits */ + psc_dma->slots &= ~(s->ac97_slot_bits); + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + break; + } + return 0; +} + +static int psc_ac97_probe(struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + /* Go */ + out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + return 0; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_ac97_dai_template: template CPU Digital Audio Interface + */ +static const struct snd_soc_dai_ops psc_ac97_analog_ops = { + .hw_params = psc_ac97_hw_analog_params, + .trigger = psc_ac97_trigger, +}; + +static const struct snd_soc_dai_ops psc_ac97_digital_ops = { + .hw_params = psc_ac97_hw_digital_params, +}; + +static struct snd_soc_dai_driver psc_ac97_dai[] = { +{ + .name = "mpc5200-psc-ac97.0", + .bus_control = true, + .probe = psc_ac97_probe, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .ops = &psc_ac97_analog_ops, +}, +{ + .name = "mpc5200-psc-ac97.1", + .bus_control = true, + .playback = { + .stream_name = "AC97 SPDIF", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE, + }, + .ops = &psc_ac97_digital_ops, +} }; + +static const struct snd_soc_component_driver psc_ac97_component = { + .name = DRV_NAME, +}; + + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int psc_ac97_of_probe(struct platform_device *op) +{ + int rc; + struct mpc52xx_psc __iomem *regs; + + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + rc = snd_soc_set_ac97_ops(&psc_ac97_ops); + if (rc != 0) { + dev_err(&op->dev, "Failed to set AC'97 ops: %d\n", rc); + return rc; + } + + rc = snd_soc_register_component(&op->dev, &psc_ac97_component, + psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); + return rc; + } + + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; + + psc_dma->imr = 0; + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + + /* Configure the serial interface mode to AC97 */ + psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97; + out_be32(®s->sicr, psc_dma->sicr); + + /* No slots active */ + out_be32(®s->ac97_slots, 0x00000000); + + return 0; +} + +static int psc_ac97_of_remove(struct platform_device *op) +{ + mpc5200_audio_dma_destroy(op); + snd_soc_unregister_component(&op->dev); + snd_soc_set_ac97_ops(NULL); + return 0; +} + +/* Match table for of_platform binding */ +static const struct of_device_id psc_ac97_match[] = { + { .compatible = "fsl,mpc5200-psc-ac97", }, + { .compatible = "fsl,mpc5200b-psc-ac97", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_ac97_match); + +static struct platform_driver psc_ac97_driver = { + .probe = psc_ac97_of_probe, + .remove = psc_ac97_of_remove, + .driver = { + .name = "mpc5200-psc-ac97", + .of_match_table = psc_ac97_match, + }, +}; + +module_platform_driver(psc_ac97_driver); + +MODULE_AUTHOR("Jon Smirl "); +MODULE_DESCRIPTION("mpc5200 AC97 module"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c new file mode 100644 index 000000000..d8232943c --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -0,0 +1,241 @@ +/* + * Freescale MPC5200 PSC in I2S mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker + */ + +#include +#include +#include + +#include +#include +#include + +#include + +#include "mpc5200_dma.h" + +/** + * PSC_I2S_RATES: sample rates supported by the I2S + * + * This driver currently only supports the PSC running in I2S slave mode, + * which means the codec determines the sample rate. Therefore, we tell + * ALSA that we support all rates and let the codec driver decide what rates + * are really supported. + */ +#define PSC_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS + +/** + * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode + */ +#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + u32 mode; + + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params)); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mode = MPC52xx_PSC_SICR_SIM_CODEC_8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_16; + break; + case SNDRV_PCM_FORMAT_S24_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_24; + break; + case SNDRV_PCM_FORMAT_S32_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_32; + break; + default: + dev_dbg(psc_dma->dev, "invalid format\n"); + return -EINVAL; + } + out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode); + + return 0; +} + +/** + * psc_i2s_set_sysclk: set the clock frequency and direction + * + * This function is called by the machine driver to tell us what the clock + * frequency and direction are. + * + * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN), + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) + */ +static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", + cpu_dai, dir); + return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; +} + +/** + * psc_i2s_set_fmt: set the serial format. + * + * This function is called by the machine driver to tell us what serial + * format to use. + * + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * + * @format: one of SND_SOC_DAIFMT_xxx + */ +static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", + cpu_dai, format); + return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_i2s_dai_template: template CPU Digital Audio Interface + */ +static const struct snd_soc_dai_ops psc_i2s_dai_ops = { + .hw_params = psc_i2s_hw_params, + .set_sysclk = psc_i2s_set_sysclk, + .set_fmt = psc_i2s_set_fmt, +}; + +static struct snd_soc_dai_driver psc_i2s_dai[] = {{ + .name = "mpc5200-psc-i2s.0", + .playback = { + .stream_name = "I2S Playback", + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .capture = { + .stream_name = "I2S Capture", + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .ops = &psc_i2s_dai_ops, +} }; + +static const struct snd_soc_component_driver psc_i2s_component = { + .name = "mpc5200-i2s", +}; + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int psc_i2s_of_probe(struct platform_device *op) +{ + int rc; + struct psc_dma *psc_dma; + struct mpc52xx_psc __iomem *regs; + + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + rc = snd_soc_register_component(&op->dev, &psc_i2s_component, + psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); + return rc; + } + + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; + + /* Configure the serial interface mode; defaulting to CODEC8 mode */ + psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + MPC52xx_PSC_SICR_CLKPOL; + out_be32(&psc_dma->psc_regs->sicr, + psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); + + /* Check for the codec handle. If it is not present then we + * are done */ + if (!of_get_property(op->dev.of_node, "codec-handle", NULL)) + return 0; + + /* Due to errata in the dma mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) + ; + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) + ; + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + + /* Go */ + out_8(&psc_dma->psc_regs->command, + MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + + return 0; + +} + +static int psc_i2s_of_remove(struct platform_device *op) +{ + mpc5200_audio_dma_destroy(op); + snd_soc_unregister_component(&op->dev); + return 0; +} + +/* Match table for of_platform binding */ +static const struct of_device_id psc_i2s_match[] = { + { .compatible = "fsl,mpc5200-psc-i2s", }, + { .compatible = "fsl,mpc5200b-psc-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_i2s_match); + +static struct platform_driver psc_i2s_driver = { + .probe = psc_i2s_of_probe, + .remove = psc_i2s_of_remove, + .driver = { + .name = "mpc5200-psc-i2s", + .of_match_table = psc_i2s_match, + }, +}; + +module_platform_driver(psc_i2s_driver); + +MODULE_AUTHOR("Grant Likely "); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c new file mode 100644 index 000000000..a639b52c1 --- /dev/null +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -0,0 +1,432 @@ +/** + * Freescale MPC8610HPCD ALSA SoC Machine driver + * + * Author: Timur Tabi + * + * Copyright 2007-2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_dma.h" +#include "fsl_ssi.h" +#include "fsl_utils.h" + +/* There's only one global utilities register */ +static phys_addr_t guts_phys; + +/** + * mpc8610_hpcd_data: machine-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * MPC8610 HPCD. Some of the data is taken from the device tree. + */ +struct mpc8610_hpcd_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ + unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ +}; + +/** + * mpc8610_hpcd_machine_probe: initialize the board + * + * This function is used to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card) +{ + struct mpc8610_hpcd_data *machine_data = + container_of(card, struct mpc8610_hpcd_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Program the signal routing between the SSI and the DMA */ + guts_set_dmacr(guts, machine_data->dma_id[0], + machine_data->dma_channel_id[0], + CCSR_GUTS_DMACR_DEV_SSI); + guts_set_dmacr(guts, machine_data->dma_id[1], + machine_data->dma_channel_id[1], + CCSR_GUTS_DMACR_DEV_SSI); + + guts_set_pmuxcr_dma(guts, machine_data->dma_id[0], + machine_data->dma_channel_id[0], 0); + guts_set_pmuxcr_dma(guts, machine_data->dma_id[1], + machine_data->dma_channel_id[1], 0); + + switch (machine_data->ssi_id) { + case 0: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI); + break; + case 1: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI); + break; + } + + iounmap(guts); + + return 0; +} + +/** + * mpc8610_hpcd_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mpc8610_hpcd_data *machine_data = + container_of(rtd->card, struct mpc8610_hpcd_data, card); + struct device *dev = rtd->card->dev; + int ret = 0; + + /* Tell the codec driver what the serial protocol is. */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format); + if (ret < 0) { + dev_err(dev, "could not set codec driver audio format\n"); + return ret; + } + + /* + * Tell the codec driver what the MCLK frequency is, and whether it's + * a slave or master. + */ + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, + machine_data->clk_frequency, + machine_data->codec_clk_direction); + if (ret < 0) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +/** + * mpc8610_hpcd_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card) +{ + struct mpc8610_hpcd_data *machine_data = + container_of(card, struct mpc8610_hpcd_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Restore the signal routing */ + + guts_set_dmacr(guts, machine_data->dma_id[0], + machine_data->dma_channel_id[0], 0); + guts_set_dmacr(guts, machine_data->dma_id[1], + machine_data->dma_channel_id[1], 0); + + switch (machine_data->ssi_id) { + case 0: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA); + break; + case 1: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA); + break; + } + + iounmap(guts); + + return 0; +} + +/** + * mpc8610_hpcd_ops: ASoC machine driver operations + */ +static const struct snd_soc_ops mpc8610_hpcd_ops = { + .startup = mpc8610_hpcd_startup, +}; + +/** + * mpc8610_hpcd_probe: platform probe function for the machine driver + * + * Although this is a machine driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + */ +static int mpc8610_hpcd_probe(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = to_platform_device(dev); + struct device_node *np = ssi_pdev->dev.of_node; + struct device_node *codec_np = NULL; + struct mpc8610_hpcd_data *machine_data; + int ret = -ENODEV; + const char *sprop; + const u32 *iprop; + + /* Find the codec node for this SSI. */ + codec_np = of_parse_phandle(np, "codec-handle", 0); + if (!codec_np) { + dev_err(dev, "invalid codec node\n"); + return -EINVAL; + } + + machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); + if (!machine_data) { + ret = -ENOMEM; + goto error_alloc; + } + + machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); + machine_data->dai[0].ops = &mpc8610_hpcd_ops; + + /* ASoC core can match codec with device node */ + machine_data->dai[0].codec_of_node = codec_np; + + /* The DAI name from the codec (snd_soc_dai_driver.name) */ + machine_data->dai[0].codec_dai_name = "cs4270-hifi"; + + /* We register two DAIs per SSI, one for playback and the other for + * capture. Currently, we only support codecs that have one DAI for + * both playback and capture. + */ + memcpy(&machine_data->dai[1], &machine_data->dai[0], + sizeof(struct snd_soc_dai_link)); + + /* Get the device ID */ + iprop = of_get_property(np, "cell-index", NULL); + if (!iprop) { + dev_err(&pdev->dev, "cell-index property not found\n"); + ret = -EINVAL; + goto error; + } + machine_data->ssi_id = be32_to_cpup(iprop); + + /* Get the serial format and clock direction. */ + sprop = of_get_property(np, "fsl,mode", NULL); + if (!sprop) { + dev_err(&pdev->dev, "fsl,mode property not found\n"); + ret = -EINVAL; + goto error; + } + + if (strcasecmp(sprop, "i2s-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&pdev->dev, "codec bus-frequency " + "property is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + machine_data->clk_frequency = be32_to_cpup(iprop); + } else if (strcasecmp(sprop, "i2s-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "lj-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "lj-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "rj-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "rj-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "ac97-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "ac97-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else { + dev_err(&pdev->dev, + "unrecognized fsl,mode property '%s'\n", sprop); + ret = -EINVAL; + goto error; + } + + if (!machine_data->clk_frequency) { + dev_err(&pdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Find the playback DMA channel to use. */ + machine_data->dai[0].platform_name = machine_data->platform_name[0]; + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", + &machine_data->dai[0], + &machine_data->dma_channel_id[0], + &machine_data->dma_id[0]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); + goto error; + } + + /* Find the capture DMA channel to use. */ + machine_data->dai[1].platform_name = machine_data->platform_name[1]; + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", + &machine_data->dai[1], + &machine_data->dma_channel_id[1], + &machine_data->dma_id[1]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); + goto error; + } + + /* Initialize our DAI data structure. */ + machine_data->dai[0].stream_name = "playback"; + machine_data->dai[1].stream_name = "capture"; + machine_data->dai[0].name = machine_data->dai[0].stream_name; + machine_data->dai[1].name = machine_data->dai[1].stream_name; + + machine_data->card.probe = mpc8610_hpcd_machine_probe; + machine_data->card.remove = mpc8610_hpcd_machine_remove; + machine_data->card.name = pdev->name; /* The platform driver name */ + machine_data->card.owner = THIS_MODULE; + machine_data->card.dev = &pdev->dev; + machine_data->card.num_links = 2; + machine_data->card.dai_link = machine_data->dai; + + /* Register with ASoC */ + ret = snd_soc_register_card(&machine_data->card); + if (ret) { + dev_err(&pdev->dev, "could not register card\n"); + goto error; + } + + of_node_put(codec_np); + + return 0; + +error: + kfree(machine_data); +error_alloc: + of_node_put(codec_np); + return ret; +} + +/** + * mpc8610_hpcd_remove: remove the platform device + * + * This function is called when the platform device is removed. + */ +static int mpc8610_hpcd_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct mpc8610_hpcd_data *machine_data = + container_of(card, struct mpc8610_hpcd_data, card); + + snd_soc_unregister_card(card); + kfree(machine_data); + + return 0; +} + +static struct platform_driver mpc8610_hpcd_driver = { + .probe = mpc8610_hpcd_probe, + .remove = mpc8610_hpcd_remove, + .driver = { + /* The name must match 'compatible' property in the device tree, + * in lowercase letters. + */ + .name = "snd-soc-mpc8610hpcd", + }, +}; + +/** + * mpc8610_hpcd_init: machine driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init mpc8610_hpcd_init(void) +{ + struct device_node *guts_np; + struct resource res; + + pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n"); + + /* Get the physical address of the global utilities registers */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts"); + if (of_address_to_resource(guts_np, 0, &res)) { + pr_err("mpc8610-hpcd: missing/invalid global utilities node\n"); + return -EINVAL; + } + guts_phys = res.start; + + return platform_driver_register(&mpc8610_hpcd_driver); +} + +/** + * mpc8610_hpcd_exit: machine driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit mpc8610_hpcd_exit(void) +{ + platform_driver_unregister(&mpc8610_hpcd_driver); +} + +module_init(mpc8610_hpcd_init); +module_exit(mpc8610_hpcd_exit); + +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c new file mode 100644 index 000000000..d7ec3d200 --- /dev/null +++ b/sound/soc/fsl/mx27vis-aic32x4.c @@ -0,0 +1,234 @@ +/* + * mx27vis-aic32x4.c + * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/tlv320aic32x4.h" +#include "imx-ssi.h" +#include "imx-audmux.h" + +#define MX27VIS_AMP_GAIN 0 +#define MX27VIS_AMP_MUTE 1 + +static int mx27vis_amp_gain; +static int mx27vis_amp_mute; +static int mx27vis_amp_gain0_gpio; +static int mx27vis_amp_gain1_gpio; +static int mx27vis_amp_mutel_gpio; +static int mx27vis_amp_muter_gpio; + +static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + 25000000, SND_SOC_CLOCK_OUT); + if (ret) { + pr_err("%s: failed setting codec sysclk\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + if (ret) { + pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops mx27vis_aic32x4_snd_ops = { + .hw_params = mx27vis_aic32x4_hw_params, +}; + +static int mx27vis_amp_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int value = ucontrol->value.integer.value[0]; + unsigned int reg = mc->reg; + int max = mc->max; + + if (value > max) + return -EINVAL; + + switch (reg) { + case MX27VIS_AMP_GAIN: + gpio_set_value(mx27vis_amp_gain0_gpio, value & 1); + gpio_set_value(mx27vis_amp_gain1_gpio, value >> 1); + mx27vis_amp_gain = value; + break; + case MX27VIS_AMP_MUTE: + gpio_set_value(mx27vis_amp_mutel_gpio, value & 1); + gpio_set_value(mx27vis_amp_muter_gpio, value >> 1); + mx27vis_amp_mute = value; + break; + } + return 0; +} + +static int mx27vis_amp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + + switch (reg) { + case MX27VIS_AMP_GAIN: + ucontrol->value.integer.value[0] = mx27vis_amp_gain; + break; + case MX27VIS_AMP_MUTE: + ucontrol->value.integer.value[0] = mx27vis_amp_mute; + break; + } + return 0; +} + +/* From 6dB to 24dB in steps of 6dB */ +static const DECLARE_TLV_DB_SCALE(mx27vis_amp_tlv, 600, 600, 0); + +static const struct snd_kcontrol_new mx27vis_aic32x4_controls[] = { + SOC_DAPM_PIN_SWITCH("External Mic"), + SOC_SINGLE_EXT_TLV("LO Ext Boost", MX27VIS_AMP_GAIN, 0, 3, 0, + mx27vis_amp_get, mx27vis_amp_set, mx27vis_amp_tlv), + SOC_DOUBLE_EXT("LO Ext Mute Switch", MX27VIS_AMP_MUTE, 0, 1, 1, 0, + mx27vis_amp_get, mx27vis_amp_set), +}; + +static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + SND_SOC_DAPM_MIC("External Mic", NULL), +}; + +static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + {"Mic Bias", NULL, "External Mic"}, + {"IN1_R", NULL, "Mic Bias"}, + {"IN2_R", NULL, "Mic Bias"}, + {"IN3_R", NULL, "Mic Bias"}, + {"IN1_L", NULL, "Mic Bias"}, + {"IN2_L", NULL, "Mic Bias"}, + {"IN3_L", NULL, "Mic Bias"}, +}; + +static struct snd_soc_dai_link mx27vis_aic32x4_dai = { + .name = "tlv320aic32x4", + .stream_name = "TLV320AIC32X4", + .codec_dai_name = "tlv320aic32x4-hifi", + .platform_name = "imx-ssi.0", + .codec_name = "tlv320aic32x4.0-0018", + .cpu_dai_name = "imx-ssi.0", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &mx27vis_aic32x4_snd_ops, +}; + +static struct snd_soc_card mx27vis_aic32x4 = { + .name = "visstrim_m10-audio", + .owner = THIS_MODULE, + .dai_link = &mx27vis_aic32x4_dai, + .num_links = 1, + .controls = mx27vis_aic32x4_controls, + .num_controls = ARRAY_SIZE(mx27vis_aic32x4_controls), + .dapm_widgets = aic32x4_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aic32x4_dapm_widgets), + .dapm_routes = aic32x4_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(aic32x4_dapm_routes), +}; + +static int mx27vis_aic32x4_probe(struct platform_device *pdev) +{ + struct snd_mx27vis_platform_data *pdata = pdev->dev.platform_data; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "No platform data supplied\n"); + return -EINVAL; + } + + mx27vis_amp_gain0_gpio = pdata->amp_gain0_gpio; + mx27vis_amp_gain1_gpio = pdata->amp_gain1_gpio; + mx27vis_amp_mutel_gpio = pdata->amp_mutel_gpio; + mx27vis_amp_muter_gpio = pdata->amp_muter_gpio; + + mx27vis_aic32x4.dev = &pdev->dev; + ret = snd_soc_register_card(&mx27vis_aic32x4); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + /* Connect SSI0 as clock slave to SSI1 external pins */ + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_TCLKDIR | + IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) + ); + imx_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) + ); + + return ret; +} + +static int mx27vis_aic32x4_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&mx27vis_aic32x4); + + return 0; +} + +static struct platform_driver mx27vis_aic32x4_audio_driver = { + .driver = { + .name = "mx27vis", + }, + .probe = mx27vis_aic32x4_probe, + .remove = mx27vis_aic32x4_remove, +}; + +module_platform_driver(mx27vis_aic32x4_audio_driver); + +MODULE_AUTHOR("Javier Martin "); +MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mx27vis"); diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c new file mode 100644 index 000000000..41c623c55 --- /dev/null +++ b/sound/soc/fsl/p1022_ds.c @@ -0,0 +1,441 @@ +/** + * Freescale P1022DS ALSA SoC Machine driver + * + * Author: Timur Tabi + * + * Copyright 2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_dma.h" +#include "fsl_ssi.h" +#include "fsl_utils.h" + +/* P1022-specific PMUXCR and DMUXCR bit definitions */ + +#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 + +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 + +#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ +#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ + +/* + * Set the DMACR register in the GUTS + * + * The DMACR register determines the source of initiated transfers for each + * channel on each DMA controller. Rather than have a bunch of repetitive + * macros for the bit patterns, we just have a function that calculates + * them. + * + * guts: Pointer to GUTS structure + * co: The DMA controller (0 or 1) + * ch: The channel on the DMA controller (0, 1, 2, or 3) + * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) + */ +static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, + unsigned int co, unsigned int ch, unsigned int device) +{ + unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); + + clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); +} + +/* There's only one global utilities register */ +static phys_addr_t guts_phys; + +/** + * machine_data: machine-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * P1022 DS. Some of the data is taken from the device tree. + */ +struct machine_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ + unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ +}; + +/** + * p1022_ds_machine_probe: initialize the board + * + * This function is used to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int p1022_ds_machine_probe(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Enable SSI Tx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, + CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); + + /* Enable SSI Rx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, + CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); + + /* Enable DMA Channel for SSI */ + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], + CCSR_GUTS_DMUXCR_SSI); + + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], + CCSR_GUTS_DMUXCR_SSI); + + iounmap(guts); + + return 0; +} + +/** + * p1022_ds_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int p1022_ds_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct machine_data *mdata = + container_of(rtd->card, struct machine_data, card); + struct device *dev = rtd->card->dev; + int ret = 0; + + /* Tell the codec driver what the serial protocol is. */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); + if (ret < 0) { + dev_err(dev, "could not set codec driver audio format\n"); + return ret; + } + + /* + * Tell the codec driver what the MCLK frequency is, and whether it's + * a slave or master. + */ + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency, + mdata->codec_clk_direction); + if (ret < 0) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +/** + * p1022_ds_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +static int p1022_ds_machine_remove(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Restore the signal routing */ + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); + + iounmap(guts); + + return 0; +} + +/** + * p1022_ds_ops: ASoC machine driver operations + */ +static const struct snd_soc_ops p1022_ds_ops = { + .startup = p1022_ds_startup, +}; + +/** + * p1022_ds_probe: platform probe function for the machine driver + * + * Although this is a machine driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + */ +static int p1022_ds_probe(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = to_platform_device(dev); + struct device_node *np = ssi_pdev->dev.of_node; + struct device_node *codec_np = NULL; + struct machine_data *mdata; + int ret = -ENODEV; + const char *sprop; + const u32 *iprop; + + /* Find the codec node for this SSI. */ + codec_np = of_parse_phandle(np, "codec-handle", 0); + if (!codec_np) { + dev_err(dev, "could not find codec node\n"); + return -EINVAL; + } + + mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); + if (!mdata) { + ret = -ENOMEM; + goto error_put; + } + + mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); + mdata->dai[0].ops = &p1022_ds_ops; + + /* ASoC core can match codec with device node */ + mdata->dai[0].codec_of_node = codec_np; + + /* We register two DAIs per SSI, one for playback and the other for + * capture. We support codecs that have separate DAIs for both playback + * and capture. + */ + memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); + + /* The DAI names from the codec (snd_soc_dai_driver.name) */ + mdata->dai[0].codec_dai_name = "wm8776-hifi-playback"; + mdata->dai[1].codec_dai_name = "wm8776-hifi-capture"; + + /* Get the device ID */ + iprop = of_get_property(np, "cell-index", NULL); + if (!iprop) { + dev_err(&pdev->dev, "cell-index property not found\n"); + ret = -EINVAL; + goto error; + } + mdata->ssi_id = be32_to_cpup(iprop); + + /* Get the serial format and clock direction. */ + sprop = of_get_property(np, "fsl,mode", NULL); + if (!sprop) { + dev_err(&pdev->dev, "fsl,mode property not found\n"); + ret = -EINVAL; + goto error; + } + + if (strcasecmp(sprop, "i2s-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&pdev->dev, "codec bus-frequency " + "property is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + mdata->clk_frequency = be32_to_cpup(iprop); + } else if (strcasecmp(sprop, "i2s-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "lj-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "lj-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "rj-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "rj-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "ac97-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "ac97-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else { + dev_err(&pdev->dev, + "unrecognized fsl,mode property '%s'\n", sprop); + ret = -EINVAL; + goto error; + } + + if (!mdata->clk_frequency) { + dev_err(&pdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Find the playback DMA channel to use. */ + mdata->dai[0].platform_name = mdata->platform_name[0]; + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], + &mdata->dma_channel_id[0], + &mdata->dma_id[0]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); + goto error; + } + + /* Find the capture DMA channel to use. */ + mdata->dai[1].platform_name = mdata->platform_name[1]; + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], + &mdata->dma_channel_id[1], + &mdata->dma_id[1]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); + goto error; + } + + /* Initialize our DAI data structure. */ + mdata->dai[0].stream_name = "playback"; + mdata->dai[1].stream_name = "capture"; + mdata->dai[0].name = mdata->dai[0].stream_name; + mdata->dai[1].name = mdata->dai[1].stream_name; + + mdata->card.probe = p1022_ds_machine_probe; + mdata->card.remove = p1022_ds_machine_remove; + mdata->card.name = pdev->name; /* The platform driver name */ + mdata->card.owner = THIS_MODULE; + mdata->card.dev = &pdev->dev; + mdata->card.num_links = 2; + mdata->card.dai_link = mdata->dai; + + /* Register with ASoC */ + ret = snd_soc_register_card(&mdata->card); + if (ret) { + dev_err(&pdev->dev, "could not register card\n"); + goto error; + } + + of_node_put(codec_np); + + return 0; + +error: + kfree(mdata); +error_put: + of_node_put(codec_np); + return ret; +} + +/** + * p1022_ds_remove: remove the platform device + * + * This function is called when the platform device is removed. + */ +static int p1022_ds_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct machine_data *mdata = + container_of(card, struct machine_data, card); + + snd_soc_unregister_card(card); + kfree(mdata); + + return 0; +} + +static struct platform_driver p1022_ds_driver = { + .probe = p1022_ds_probe, + .remove = p1022_ds_remove, + .driver = { + /* + * The name must match 'compatible' property in the device tree, + * in lowercase letters. + */ + .name = "snd-soc-p1022ds", + }, +}; + +/** + * p1022_ds_init: machine driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init p1022_ds_init(void) +{ + struct device_node *guts_np; + struct resource res; + + /* Get the physical address of the global utilities registers */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); + if (of_address_to_resource(guts_np, 0, &res)) { + pr_err("snd-soc-p1022ds: missing/invalid global utils node\n"); + of_node_put(guts_np); + return -EINVAL; + } + guts_phys = res.start; + of_node_put(guts_np); + + return platform_driver_register(&p1022_ds_driver); +} + +/** + * p1022_ds_exit: machine driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit p1022_ds_exit(void) +{ + platform_driver_unregister(&p1022_ds_driver); +} + +module_init(p1022_ds_init); +module_exit(p1022_ds_exit); + +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c new file mode 100644 index 000000000..4afbdd610 --- /dev/null +++ b/sound/soc/fsl/p1022_rdk.c @@ -0,0 +1,391 @@ +/** + * Freescale P1022RDK ALSA SoC Machine driver + * + * Author: Timur Tabi + * + * Copyright 2012 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * Note: in order for audio to work correctly, the output controls need + * to be enabled, because they control the clock. So for playback, for + * example: + * + * amixer sset 'Left Output Mixer PCM' on + * amixer sset 'Right Output Mixer PCM' on + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_dma.h" +#include "fsl_ssi.h" +#include "fsl_utils.h" + +/* P1022-specific PMUXCR and DMUXCR bit definitions */ + +#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 + +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 + +#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ +#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ + +/* + * Set the DMACR register in the GUTS + * + * The DMACR register determines the source of initiated transfers for each + * channel on each DMA controller. Rather than have a bunch of repetitive + * macros for the bit patterns, we just have a function that calculates + * them. + * + * guts: Pointer to GUTS structure + * co: The DMA controller (0 or 1) + * ch: The channel on the DMA controller (0, 1, 2, or 3) + * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) + */ +static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, + unsigned int co, unsigned int ch, unsigned int device) +{ + unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); + + clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); +} + +/* There's only one global utilities register */ +static phys_addr_t guts_phys; + +/** + * machine_data: machine-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * P1022 RDK. Some of the data is taken from the device tree. + */ +struct machine_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ +}; + +/** + * p1022_rdk_machine_probe: initialize the board + * + * This function is used to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int p1022_rdk_machine_probe(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Enable SSI Tx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, + CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); + + /* Enable SSI Rx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, + CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); + + /* Enable DMA Channel for SSI */ + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], + CCSR_GUTS_DMUXCR_SSI); + + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], + CCSR_GUTS_DMUXCR_SSI); + + iounmap(guts); + + return 0; +} + +/** + * p1022_rdk_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int p1022_rdk_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct machine_data *mdata = + container_of(rtd->card, struct machine_data, card); + struct device *dev = rtd->card->dev; + int ret = 0; + + /* Tell the codec driver what the serial protocol is. */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); + if (ret < 0) { + dev_err(dev, "could not set codec driver audio format (ret=%i)\n", + ret); + return ret; + } + + ret = snd_soc_dai_set_pll(rtd->codec_dai, 0, 0, mdata->clk_frequency, + mdata->clk_frequency); + if (ret < 0) { + dev_err(dev, "could not set codec PLL frequency (ret=%i)\n", + ret); + return ret; + } + + return 0; +} + +/** + * p1022_rdk_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +static int p1022_rdk_machine_remove(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Restore the signal routing */ + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); + + iounmap(guts); + + return 0; +} + +/** + * p1022_rdk_ops: ASoC machine driver operations + */ +static const struct snd_soc_ops p1022_rdk_ops = { + .startup = p1022_rdk_startup, +}; + +/** + * p1022_rdk_probe: platform probe function for the machine driver + * + * Although this is a machine driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + */ +static int p1022_rdk_probe(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = to_platform_device(dev); + struct device_node *np = ssi_pdev->dev.of_node; + struct device_node *codec_np = NULL; + struct machine_data *mdata; + const u32 *iprop; + int ret; + + /* Find the codec node for this SSI. */ + codec_np = of_parse_phandle(np, "codec-handle", 0); + if (!codec_np) { + dev_err(dev, "could not find codec node\n"); + return -EINVAL; + } + + mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); + if (!mdata) { + ret = -ENOMEM; + goto error_put; + } + + mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); + mdata->dai[0].ops = &p1022_rdk_ops; + + /* ASoC core can match codec with device node */ + mdata->dai[0].codec_of_node = codec_np; + + /* + * We register two DAIs per SSI, one for playback and the other for + * capture. We support codecs that have separate DAIs for both playback + * and capture. + */ + memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); + + /* The DAI names from the codec (snd_soc_dai_driver.name) */ + mdata->dai[0].codec_dai_name = "wm8960-hifi"; + mdata->dai[1].codec_dai_name = mdata->dai[0].codec_dai_name; + + /* + * Configure the SSI for I2S slave mode. Older device trees have + * an fsl,mode property, but we ignore that since there's really + * only one way to configure the SSI. + */ + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* + * In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&pdev->dev, "codec bus-frequency property is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + mdata->clk_frequency = be32_to_cpup(iprop); + + if (!mdata->clk_frequency) { + dev_err(&pdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Find the playback DMA channel to use. */ + mdata->dai[0].platform_name = mdata->platform_name[0]; + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], + &mdata->dma_channel_id[0], + &mdata->dma_id[0]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid playback DMA phandle (ret=%i)\n", + ret); + goto error; + } + + /* Find the capture DMA channel to use. */ + mdata->dai[1].platform_name = mdata->platform_name[1]; + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], + &mdata->dma_channel_id[1], + &mdata->dma_id[1]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid capture DMA phandle (ret=%i)\n", + ret); + goto error; + } + + /* Initialize our DAI data structure. */ + mdata->dai[0].stream_name = "playback"; + mdata->dai[1].stream_name = "capture"; + mdata->dai[0].name = mdata->dai[0].stream_name; + mdata->dai[1].name = mdata->dai[1].stream_name; + + mdata->card.probe = p1022_rdk_machine_probe; + mdata->card.remove = p1022_rdk_machine_remove; + mdata->card.name = pdev->name; /* The platform driver name */ + mdata->card.owner = THIS_MODULE; + mdata->card.dev = &pdev->dev; + mdata->card.num_links = 2; + mdata->card.dai_link = mdata->dai; + + /* Register with ASoC */ + ret = snd_soc_register_card(&mdata->card); + if (ret) { + dev_err(&pdev->dev, "could not register card (ret=%i)\n", ret); + goto error; + } + + return 0; + +error: + kfree(mdata); +error_put: + of_node_put(codec_np); + return ret; +} + +/** + * p1022_rdk_remove: remove the platform device + * + * This function is called when the platform device is removed. + */ +static int p1022_rdk_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct machine_data *mdata = + container_of(card, struct machine_data, card); + + snd_soc_unregister_card(card); + kfree(mdata); + + return 0; +} + +static struct platform_driver p1022_rdk_driver = { + .probe = p1022_rdk_probe, + .remove = p1022_rdk_remove, + .driver = { + /* + * The name must match 'compatible' property in the device tree, + * in lowercase letters. + */ + .name = "snd-soc-p1022rdk", + }, +}; + +/** + * p1022_rdk_init: machine driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init p1022_rdk_init(void) +{ + struct device_node *guts_np; + struct resource res; + + /* Get the physical address of the global utilities registers */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); + if (of_address_to_resource(guts_np, 0, &res)) { + pr_err("snd-soc-p1022rdk: missing/invalid global utils node\n"); + of_node_put(guts_np); + return -EINVAL; + } + guts_phys = res.start; + of_node_put(guts_np); + + return platform_driver_register(&p1022_rdk_driver); +} + +/** + * p1022_rdk_exit: machine driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit p1022_rdk_exit(void) +{ + platform_driver_unregister(&p1022_rdk_driver); +} + +late_initcall(p1022_rdk_init); +module_exit(p1022_rdk_exit); + +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Freescale / iVeia P1022 RDK ALSA SoC machine driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c new file mode 100644 index 000000000..72d454899 --- /dev/null +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -0,0 +1,142 @@ +/* + * Phytec pcm030 driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include + +#include + +#include "mpc5200_dma.h" + +#define DRV_NAME "pcm030-audio-fabric" + +struct pcm030_audio_data { + struct snd_soc_card *card; + struct platform_device *codec_device; +}; + +static struct snd_soc_dai_link pcm030_fabric_dai[] = { +{ + .name = "AC97.0", + .stream_name = "AC97 Analog", + .codec_dai_name = "wm9712-hifi", + .cpu_dai_name = "mpc5200-psc-ac97.0", + .codec_name = "wm9712-codec", +}, +{ + .name = "AC97.1", + .stream_name = "AC97 IEC958", + .codec_dai_name = "wm9712-aux", + .cpu_dai_name = "mpc5200-psc-ac97.1", + .codec_name = "wm9712-codec", +}, +}; + +static struct snd_soc_card pcm030_card = { + .name = "pcm030", + .owner = THIS_MODULE, + .dai_link = pcm030_fabric_dai, + .num_links = ARRAY_SIZE(pcm030_fabric_dai), +}; + +static int pcm030_fabric_probe(struct platform_device *op) +{ + struct device_node *np = op->dev.of_node; + struct device_node *platform_np; + struct snd_soc_card *card = &pcm030_card; + struct pcm030_audio_data *pdata; + int ret; + int i; + + if (!of_machine_is_compatible("phytec,pcm030")) + return -ENODEV; + + pdata = devm_kzalloc(&op->dev, sizeof(struct pcm030_audio_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + card->dev = &op->dev; + + pdata->card = card; + + platform_np = of_parse_phandle(np, "asoc-platform", 0); + if (!platform_np) { + dev_err(&op->dev, "ac97 not registered\n"); + return -ENODEV; + } + + for (i = 0; i < card->num_links; i++) + card->dai_link[i].platform_of_node = platform_np; + + ret = request_module("snd-soc-wm9712"); + if (ret) + dev_err(&op->dev, "request_module returned: %d\n", ret); + + pdata->codec_device = platform_device_alloc("wm9712-codec", -1); + if (!pdata->codec_device) + dev_err(&op->dev, "platform_device_alloc() failed\n"); + + ret = platform_device_add(pdata->codec_device); + if (ret) { + dev_err(&op->dev, "platform_device_add() failed: %d\n", ret); + platform_device_put(pdata->codec_device); + } + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&op->dev, "snd_soc_register_card() failed: %d\n", ret); + platform_device_del(pdata->codec_device); + platform_device_put(pdata->codec_device); + } + + platform_set_drvdata(op, pdata); + return ret; + +} + +static int pcm030_fabric_remove(struct platform_device *op) +{ + struct pcm030_audio_data *pdata = platform_get_drvdata(op); + int ret; + + ret = snd_soc_unregister_card(pdata->card); + platform_device_unregister(pdata->codec_device); + + return ret; +} + +static const struct of_device_id pcm030_audio_match[] = { + { .compatible = "phytec,pcm030-audio-fabric", }, + {} +}; +MODULE_DEVICE_TABLE(of, pcm030_audio_match); + +static struct platform_driver pcm030_fabric_driver = { + .probe = pcm030_fabric_probe, + .remove = pcm030_fabric_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = pcm030_audio_match, + }, +}; + +module_platform_driver(pcm030_fabric_driver); + + +MODULE_AUTHOR("Jon Smirl "); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c new file mode 100644 index 000000000..66fb6c461 --- /dev/null +++ b/sound/soc/fsl/phycore-ac97.c @@ -0,0 +1,125 @@ +/* + * phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode + * + * Copyright 2009 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-audmux.h" + +static struct snd_soc_card imx_phycore; + +static const struct snd_soc_ops imx_phycore_hifi_ops = { +}; + +static struct snd_soc_dai_link imx_phycore_dai_ac97[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "wm9712-hifi", + .codec_name = "wm9712-codec", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-ssi.0", + .ops = &imx_phycore_hifi_ops, + }, +}; + +static struct snd_soc_card imx_phycore = { + .name = "PhyCORE-ac97-audio", + .owner = THIS_MODULE, + .dai_link = imx_phycore_dai_ac97, + .num_links = ARRAY_SIZE(imx_phycore_dai_ac97), +}; + +static struct platform_device *imx_phycore_snd_ac97_device; +static struct platform_device *imx_phycore_snd_device; + +static int __init imx_phycore_init(void) +{ + int ret; + + if (machine_is_pca100()) { + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V1_PCR_TFCSEL(3) | + IMX_AUDMUX_V1_PCR_TCLKDIR | /* clock is output */ + IMX_AUDMUX_V1_PCR_RXDSEL(3)); + imx_audmux_v1_configure_port(3, + IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V1_PCR_TFCSEL(0) | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_RXDSEL(0)); + } else if (machine_is_pcm043()) { + imx_audmux_v2_configure_port(3, + IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V2_PTCR_TFSEL(0) | + IMX_AUDMUX_V2_PTCR_TFSDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(0)); + imx_audmux_v2_configure_port(0, + IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V2_PTCR_TCSEL(3) | + IMX_AUDMUX_V2_PTCR_TCLKDIR, /* clock is output */ + IMX_AUDMUX_V2_PDCR_RXDSEL(3)); + } else { + /* return happy. We might run on a totally different machine */ + return 0; + } + + imx_phycore_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!imx_phycore_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(imx_phycore_snd_ac97_device, &imx_phycore); + ret = platform_device_add(imx_phycore_snd_ac97_device); + if (ret) + goto fail1; + + imx_phycore_snd_device = platform_device_alloc("wm9712-codec", -1); + if (!imx_phycore_snd_device) { + ret = -ENOMEM; + goto fail2; + } + ret = platform_device_add(imx_phycore_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + goto fail3; + } + + return 0; + +fail3: + platform_device_put(imx_phycore_snd_device); +fail2: + platform_device_del(imx_phycore_snd_ac97_device); +fail1: + platform_device_put(imx_phycore_snd_ac97_device); + return ret; +} + +static void __exit imx_phycore_exit(void) +{ + platform_device_unregister(imx_phycore_snd_device); + platform_device_unregister(imx_phycore_snd_ac97_device); +} + +late_initcall(imx_phycore_init); +module_exit(imx_phycore_exit); + +MODULE_AUTHOR("Sascha Hauer "); +MODULE_DESCRIPTION("PhyCORE ALSA SoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c new file mode 100644 index 000000000..2f80b21b2 --- /dev/null +++ b/sound/soc/fsl/wm1133-ev1.c @@ -0,0 +1,292 @@ +/* + * wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS + * + * Copyright (c) 2010 Wolfson Microelectronics plc + * Author: Mark Brown + * + * Based on an earlier driver for the same hardware by Liam Girdwood. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-ssi.h" +#include "../codecs/wm8350.h" +#include "imx-audmux.h" + +/* There is a silicon mic on the board optionally connected via a solder pad + * SP1. Define this to enable it. + */ +#undef USE_SIMIC + +struct _wm8350_audio { + unsigned int channels; + snd_pcm_format_t format; + unsigned int rate; + unsigned int sysclk; + unsigned int bclkdiv; + unsigned int clkdiv; + unsigned int lr_rate; +}; + +/* in order of power consumption per rate (lowest first) */ +static const struct _wm8350_audio wm8350_audio[] = { + /* 16bit mono modes */ + {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1, + WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,}, + + /* 16 bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000, + WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000, + WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000, + WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600, + WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600, + WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + + /* 24bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, +}; + +static int wm1133_ev1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i, found = 0; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int channels = params_channels(params); + + /* find the correct audio parameters */ + for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) { + if (rate == wm8350_audio[i].rate && + format == wm8350_audio[i].format && + channels == wm8350_audio[i].channels) { + found = 1; + break; + } + } + if (!found) + return -EINVAL; + + /* codec FLL input is 14.75 MHz from MCLK */ + snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk); + + /* TODO: The SSI driver should figure this out for us */ + switch (channels) { + case 2: + snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 0); + break; + case 1: + snd_soc_dai_set_tdm_slot(cpu_dai, 0x1, 0x1, 1, 0); + break; + default: + return -EINVAL; + } + + /* set MCLK as the codec system clock for DAC and ADC */ + snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK, + wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN); + + /* set codec BCLK division for sample rate */ + snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV, + wm8350_audio[i].bclkdiv); + + /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate); + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate); + + /* now configure DAC and ADC clocks */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv); + + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv); + + return 0; +} + +static const struct snd_soc_ops wm1133_ev1_ops = { + .hw_params = wm1133_ev1_hw_params, +}; + +static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = { +#ifdef USE_SIMIC + SND_SOC_DAPM_MIC("SiMIC", NULL), +#endif + SND_SOC_DAPM_MIC("Mic1 Jack", NULL), + SND_SOC_DAPM_MIC("Mic2 Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +/* imx32ads soc_card audio map */ +static const struct snd_soc_dapm_route wm1133_ev1_map[] = { + +#ifdef USE_SIMIC + /* SiMIC --> IN1LN (with automatic bias) via SP1 */ + { "IN1LN", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "SiMIC" }, +#endif + + /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */ + { "IN1LN", NULL, "Mic Bias" }, + { "IN1LP", NULL, "Mic1 Jack" }, + { "Mic Bias", NULL, "Mic1 Jack" }, + + /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */ + { "IN1RN", NULL, "Mic Bias" }, + { "IN1RP", NULL, "Mic2 Jack" }, + { "Mic Bias", NULL, "Mic2 Jack" }, + + /* Line in Jack --> AUX (L+R) */ + { "IN3R", NULL, "Line In Jack" }, + { "IN3L", NULL, "Line In Jack" }, + + /* Out1 --> Headphone Jack */ + { "Headphone Jack", NULL, "OUT1R" }, + { "Headphone Jack", NULL, "OUT1L" }, + + /* Out1 --> Line Out Jack */ + { "Line Out Jack", NULL, "OUT2R" }, + { "Line Out Jack", NULL, "OUT2L" }, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE }, +}; + +static struct snd_soc_jack mic_jack; + +static struct snd_soc_jack_pin mic_jack_pins[] = { + { .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE }, + { .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE }, +}; + +static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + + /* Headphone jack detection */ + snd_soc_card_jack_new(rtd->card, "Headphone", SND_JACK_HEADPHONE, + &hp_jack, hp_jack_pins, ARRAY_SIZE(hp_jack_pins)); + wm8350_hp_jack_detect(component, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE); + + /* Microphone jack detection */ + snd_soc_card_jack_new(rtd->card, "Microphone", + SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack, + mic_jack_pins, ARRAY_SIZE(mic_jack_pins)); + wm8350_mic_jack_detect(component, &mic_jack, SND_JACK_MICROPHONE, + SND_JACK_BTN_0); + + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); + + return 0; +} + + +static struct snd_soc_dai_link wm1133_ev1_dai = { + .name = "WM1133-EV1", + .stream_name = "Audio", + .cpu_dai_name = "imx-ssi.0", + .codec_dai_name = "wm8350-hifi", + .platform_name = "imx-ssi.0", + .codec_name = "wm8350-codec.0-0x1a", + .init = wm1133_ev1_init, + .ops = &wm1133_ev1_ops, + .symmetric_rates = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +static struct snd_soc_card wm1133_ev1 = { + .name = "WM1133-EV1", + .owner = THIS_MODULE, + .dai_link = &wm1133_ev1_dai, + .num_links = 1, + + .dapm_widgets = wm1133_ev1_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm1133_ev1_widgets), + .dapm_routes = wm1133_ev1_map, + .num_dapm_routes = ARRAY_SIZE(wm1133_ev1_map), +}; + +static struct platform_device *wm1133_ev1_snd_device; + +static int __init wm1133_ev1_audio_init(void) +{ + int ret; + unsigned int ptcr, pdcr; + + /* SSI0 mastered by port 5 */ + ptcr = IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr); + + ptcr = IMX_AUDMUX_V2_PTCR_SYN; + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0); + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr); + + wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1); + if (!wm1133_ev1_snd_device) + return -ENOMEM; + + platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1); + ret = platform_device_add(wm1133_ev1_snd_device); + + if (ret) + platform_device_put(wm1133_ev1_snd_device); + + return ret; +} +module_init(wm1133_ev1_audio_init); + +static void __exit wm1133_ev1_audio_exit(void) +{ + platform_device_unregister(wm1133_ev1_snd_device); +} +module_exit(wm1133_ev1_audio_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig new file mode 100644 index 000000000..c954be0a0 --- /dev/null +++ b/sound/soc/generic/Kconfig @@ -0,0 +1,33 @@ +config SND_SIMPLE_CARD_UTILS + tristate + +config SND_SIMPLE_CARD + tristate "ASoC Simple sound card support" + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple sound card support + +config SND_SIMPLE_SCU_CARD + tristate "ASoC Simple SCU sound card support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple SCU sound card support. + It supports DPCM of multi CPU single Codec system. + +config SND_AUDIO_GRAPH_CARD + tristate "ASoC Audio Graph sound card support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple simple sound card support + with OF-graph DT bindings. + +config SND_AUDIO_GRAPH_SCU_CARD + tristate "ASoC Audio Graph SCU sound card support" + depends on OF + select SND_SIMPLE_CARD_UTILS + help + This option enables generic simple SCU sound card support + with OF-graph DT bindings. + It supports DPCM of multi CPU single Codec ststem. diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile new file mode 100644 index 000000000..9dec293a4 --- /dev/null +++ b/sound/soc/generic/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-simple-card-utils-objs := simple-card-utils.o +snd-soc-simple-card-objs := simple-card.o +snd-soc-simple-scu-card-objs := simple-scu-card.o +snd-soc-audio-graph-card-objs := audio-graph-card.o +snd-soc-audio-graph-scu-card-objs := audio-graph-scu-card.o + +obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o +obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o +obj-$(CONFIG_SND_SIMPLE_SCU_CARD) += snd-soc-simple-scu-card.o +obj-$(CONFIG_SND_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o +obj-$(CONFIG_SND_AUDIO_GRAPH_SCU_CARD) += snd-soc-audio-graph-scu-card.o diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c new file mode 100644 index 000000000..2094d2c89 --- /dev/null +++ b/sound/soc/generic/audio-graph-card.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ASoC audio graph sound card support +// +// Copyright (C) 2016 Renesas Solutions Corp. +// Kuninori Morimoto +// +// based on ${LINUX}/sound/soc/generic/simple-card.c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct graph_card_data { + struct snd_soc_card snd_card; + struct graph_dai_props { + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; + unsigned int mclk_fs; + } *dai_props; + unsigned int mclk_fs; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; + struct snd_soc_dai_link *dai_link; + struct gpio_desc *pa_gpio; +}; + +static int asoc_graph_card_outdrv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct graph_card_data *priv = snd_soc_card_get_drvdata(dapm->card); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + gpiod_set_value_cansleep(priv->pa_gpio, 1); + break; + case SND_SOC_DAPM_PRE_PMD: + gpiod_set_value_cansleep(priv->pa_gpio, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dapm_widget asoc_graph_card_dapm_widgets[] = { + SND_SOC_DAPM_OUT_DRV_E("Amplifier", SND_SOC_NOPM, + 0, 0, NULL, 0, asoc_graph_card_outdrv_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +}; + +#define graph_priv_to_card(priv) (&(priv)->snd_card) +#define graph_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev) +#define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i)) + +static int asoc_graph_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + int ret; + + ret = asoc_simple_card_clk_enable(&dai_props->cpu_dai); + if (ret) + return ret; + + ret = asoc_simple_card_clk_enable(&dai_props->codec_dai); + if (ret) + asoc_simple_card_clk_disable(&dai_props->cpu_dai); + + return ret; +} + +static void asoc_graph_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + + asoc_simple_card_clk_disable(&dai_props->cpu_dai); + + asoc_simple_card_clk_disable(&dai_props->codec_dai); +} + +static int asoc_graph_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); + unsigned int mclk, mclk_fs = 0; + int ret = 0; + + if (priv->mclk_fs) + mclk_fs = priv->mclk_fs; + else if (dai_props->mclk_fs) + mclk_fs = dai_props->mclk_fs; + + if (mclk_fs) { + mclk = params_rate(params) * mclk_fs; + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + goto err; + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + goto err; + } + return 0; +err: + return ret; +} + +static const struct snd_soc_ops asoc_graph_card_ops = { + .startup = asoc_graph_card_startup, + .shutdown = asoc_graph_card_shutdown, + .hw_params = asoc_graph_card_hw_params, +}; + +static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; + struct graph_dai_props *dai_props = + graph_priv_to_props(priv, rtd->num); + int ret; + + ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai); + if (ret < 0) + return ret; + + return 0; +} + +static int asoc_graph_card_dai_link_of(struct device_node *cpu_port, + struct graph_card_data *priv, + int idx) +{ + struct device *dev = graph_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx); + struct graph_dai_props *dai_props = graph_priv_to_props(priv, idx); + struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; + struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; + struct device_node *cpu_ep = of_get_next_child(cpu_port, NULL); + struct device_node *codec_ep = of_graph_get_remote_endpoint(cpu_ep); + struct device_node *rcpu_ep = of_graph_get_remote_endpoint(codec_ep); + int ret; + + if (rcpu_ep != cpu_ep) { + dev_err(dev, "remote-endpoint mismatch (%s/%s/%s)\n", + cpu_ep->name, codec_ep->name, rcpu_ep->name); + ret = -EINVAL; + goto dai_link_of_err; + } + + ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, + NULL, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + + of_property_read_u32(rcpu_ep, "mclk-fs", &dai_props->mclk_fs); + + ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_of_parse_tdm(cpu_ep, cpu_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_of_parse_tdm(codec_ep, codec_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_canonicalize_dailink(dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codec_dai_name); + if (ret < 0) + goto dai_link_of_err; + + dai_link->ops = &asoc_graph_card_ops; + dai_link->init = asoc_graph_card_dai_init; + + asoc_simple_card_canonicalize_cpu(dai_link, + of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1); + +dai_link_of_err: + of_node_put(cpu_ep); + of_node_put(rcpu_ep); + of_node_put(codec_ep); + + return ret; +} + +static int asoc_graph_card_parse_of(struct graph_card_data *priv) +{ + struct of_phandle_iterator it; + struct device *dev = graph_priv_to_dev(priv); + struct snd_soc_card *card = graph_priv_to_card(priv); + struct device_node *node = dev->of_node; + int rc, idx = 0; + int ret; + + ret = asoc_simple_card_of_parse_widgets(card, NULL); + if (ret < 0) + return ret; + + ret = asoc_simple_card_of_parse_routing(card, NULL, 1); + if (ret < 0) + return ret; + + /* Factor to mclk, used in hw_params() */ + of_property_read_u32(node, "mclk-fs", &priv->mclk_fs); + + of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { + ret = asoc_graph_card_dai_link_of(it.node, priv, idx++); + if (ret < 0) { + of_node_put(it.node); + + return ret; + } + } + + return asoc_simple_card_parse_card_name(card, NULL); +} + +static int asoc_graph_get_dais_count(struct device *dev) +{ + struct of_phandle_iterator it; + struct device_node *node = dev->of_node; + int count = 0; + int rc; + + of_for_each_phandle(&it, rc, node, "dais", NULL, 0) + count++; + + return count; +} + +static int asoc_graph_soc_card_probe(struct snd_soc_card *card) +{ + struct graph_card_data *priv = snd_soc_card_get_drvdata(card); + int ret; + + ret = asoc_simple_card_init_hp(card, &priv->hp_jack, NULL); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_mic(card, &priv->mic_jack, NULL); + if (ret < 0) + return ret; + + return 0; +} + +static int asoc_graph_card_probe(struct platform_device *pdev) +{ + struct graph_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct graph_dai_props *dai_props; + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + int num, ret; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + num = asoc_graph_get_dais_count(dev); + if (num == 0) + return -EINVAL; + + dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL); + dai_link = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL); + if (!dai_props || !dai_link) + return -ENOMEM; + + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); + if (IS_ERR(priv->pa_gpio)) { + ret = PTR_ERR(priv->pa_gpio); + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); + return ret; + } + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + + /* Init snd_soc_card */ + card = graph_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = dai_link; + card->num_links = num; + card->dapm_widgets = asoc_graph_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(asoc_graph_card_dapm_widgets); + card->probe = asoc_graph_soc_card_probe; + + ret = asoc_graph_card_parse_of(priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) + goto err; + + return 0; +err: + asoc_simple_card_clean_reference(card); + + return ret; +} + +static int asoc_graph_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + return asoc_simple_card_clean_reference(card); +} + +static const struct of_device_id asoc_graph_of_match[] = { + { .compatible = "audio-graph-card", }, + {}, +}; +MODULE_DEVICE_TABLE(of, asoc_graph_of_match); + +static struct platform_driver asoc_graph_card = { + .driver = { + .name = "asoc-audio-graph-card", + .pm = &snd_soc_pm_ops, + .of_match_table = asoc_graph_of_match, + }, + .probe = asoc_graph_card_probe, + .remove = asoc_graph_card_remove, +}; +module_platform_driver(asoc_graph_card); + +MODULE_ALIAS("platform:asoc-audio-graph-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Audio Graph Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/sound/soc/generic/audio-graph-scu-card.c b/sound/soc/generic/audio-graph-scu-card.c new file mode 100644 index 000000000..92882e392 --- /dev/null +++ b/sound/soc/generic/audio-graph-scu-card.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ASoC audio graph SCU sound card support +// +// Copyright (C) 2017 Renesas Solutions Corp. +// Kuninori Morimoto +// +// based on +// ${LINUX}/sound/soc/generic/simple-scu-card.c +// ${LINUX}/sound/soc/generic/audio-graph-card.c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct graph_card_data { + struct snd_soc_card snd_card; + struct snd_soc_codec_conf codec_conf; + struct asoc_simple_dai *dai_props; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_card_data adata; +}; + +#define graph_priv_to_card(priv) (&(priv)->snd_card) +#define graph_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev) +#define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i)) + +static int asoc_graph_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct asoc_simple_dai *dai_props = graph_priv_to_props(priv, rtd->num); + + return asoc_simple_card_clk_enable(dai_props); +} + +static void asoc_graph_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct asoc_simple_dai *dai_props = graph_priv_to_props(priv, rtd->num); + + asoc_simple_card_clk_disable(dai_props); +} + +static const struct snd_soc_ops asoc_graph_card_ops = { + .startup = asoc_graph_card_startup, + .shutdown = asoc_graph_card_shutdown, +}; + +static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dai_props; + int num = rtd->num; + + dai_link = graph_priv_to_link(priv, num); + dai_props = graph_priv_to_props(priv, num); + dai = dai_link->dynamic ? + rtd->cpu_dai : + rtd->codec_dai; + + return asoc_simple_card_init_dai(dai, dai_props); +} + +static int asoc_graph_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + + asoc_simple_card_convert_fixup(&priv->adata, params); + + return 0; +} + +static int asoc_graph_card_dai_link_of(struct device_node *ep, + struct graph_card_data *priv, + unsigned int daifmt, + int idx, int is_fe) +{ + struct device *dev = graph_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx); + struct asoc_simple_dai *dai_props = graph_priv_to_props(priv, idx); + struct snd_soc_card *card = graph_priv_to_card(priv); + int ret; + + if (is_fe) { + /* BE is dummy */ + dai_link->codec_of_node = NULL; + dai_link->codec_dai_name = "snd-soc-dummy-dai"; + dai_link->codec_name = "snd-soc-dummy"; + + /* FE settings */ + dai_link->dynamic = 1; + dai_link->dpcm_merged_format = 1; + + ret = asoc_simple_card_parse_graph_cpu(ep, dai_link); + if (ret) + return ret; + + ret = asoc_simple_card_parse_clk_cpu(dev, ep, dai_link, dai_props); + if (ret < 0) + return ret; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); + if (ret < 0) + return ret; + + /* card->num_links includes Codec */ + asoc_simple_card_canonicalize_cpu(dai_link, + of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1); + } else { + /* FE is dummy */ + dai_link->cpu_of_node = NULL; + dai_link->cpu_dai_name = "snd-soc-dummy-dai"; + dai_link->cpu_name = "snd-soc-dummy"; + + /* BE settings */ + dai_link->no_pcm = 1; + dai_link->be_hw_params_fixup = asoc_graph_card_be_hw_params_fixup; + + ret = asoc_simple_card_parse_graph_codec(ep, dai_link); + if (ret < 0) + return ret; + + ret = asoc_simple_card_parse_clk_codec(dev, ep, dai_link, dai_props); + if (ret < 0) + return ret; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "be.%s", + dai_link->codec_dai_name); + if (ret < 0) + return ret; + + snd_soc_of_parse_audio_prefix(card, + &priv->codec_conf, + dai_link->codec_of_node, + "prefix"); + } + + ret = asoc_simple_card_of_parse_tdm(ep, dai_props); + if (ret) + return ret; + + ret = asoc_simple_card_canonicalize_dailink(dai_link); + if (ret < 0) + return ret; + + dai_link->dai_fmt = daifmt; + dai_link->dpcm_playback = 1; + dai_link->dpcm_capture = 1; + dai_link->ops = &asoc_graph_card_ops; + dai_link->init = asoc_graph_card_dai_init; + + return 0; +} + +static int asoc_graph_card_parse_of(struct graph_card_data *priv) +{ + struct of_phandle_iterator it; + struct device *dev = graph_priv_to_dev(priv); + struct snd_soc_card *card = graph_priv_to_card(priv); + struct device_node *node = dev->of_node; + struct device_node *cpu_port; + struct device_node *cpu_ep; + struct device_node *codec_ep; + struct device_node *rcpu_ep; + struct device_node *codec_port; + struct device_node *codec_port_old; + unsigned int daifmt = 0; + int dai_idx, ret; + int rc, codec; + + if (!node) + return -EINVAL; + + /* + * we need to consider "widgets", "mclk-fs" around here + * see simple-card + */ + + ret = asoc_simple_card_of_parse_routing(card, NULL, 0); + if (ret < 0) + return ret; + + asoc_simple_card_parse_convert(dev, NULL, &priv->adata); + + /* + * it supports multi CPU, single CODEC only here + * see asoc_graph_get_dais_count + */ + + /* find 1st codec */ + of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { + cpu_port = it.node; + cpu_ep = of_get_next_child(cpu_port, NULL); + codec_ep = of_graph_get_remote_endpoint(cpu_ep); + rcpu_ep = of_graph_get_remote_endpoint(codec_ep); + + of_node_put(cpu_ep); + of_node_put(codec_ep); + of_node_put(rcpu_ep); + + if (!codec_ep) + continue; + + if (rcpu_ep != cpu_ep) { + dev_err(dev, "remote-endpoint missmatch (%s/%s/%s)\n", + cpu_ep->name, codec_ep->name, rcpu_ep->name); + ret = -EINVAL; + goto parse_of_err; + } + + ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, + NULL, &daifmt); + if (ret < 0) { + of_node_put(cpu_port); + goto parse_of_err; + } + } + + dai_idx = 0; + codec_port_old = NULL; + for (codec = 0; codec < 2; codec++) { + /* + * To listup valid sounds continuously, + * detect all CPU-dummy first, and + * detect all dummy-Codec second + */ + of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { + cpu_port = it.node; + cpu_ep = of_get_next_child(cpu_port, NULL); + codec_ep = of_graph_get_remote_endpoint(cpu_ep); + codec_port = of_graph_get_port_parent(codec_ep); + + of_node_put(cpu_ep); + of_node_put(codec_ep); + of_node_put(codec_port); + + if (codec) { + if (!codec_port) + continue; + + if (codec_port_old == codec_port) + continue; + + codec_port_old = codec_port; + + /* Back-End (= Codec) */ + ret = asoc_graph_card_dai_link_of(codec_ep, priv, daifmt, dai_idx++, 0); + if (ret < 0) { + of_node_put(cpu_port); + goto parse_of_err; + } + } else { + /* Front-End (= CPU) */ + ret = asoc_graph_card_dai_link_of(cpu_ep, priv, daifmt, dai_idx++, 1); + if (ret < 0) { + of_node_put(cpu_port); + goto parse_of_err; + } + } + } + } + + ret = asoc_simple_card_parse_card_name(card, NULL); + if (ret) + goto parse_of_err; + + ret = 0; + +parse_of_err: + return ret; +} + +static int asoc_graph_get_dais_count(struct device *dev) +{ + struct of_phandle_iterator it; + struct device_node *node = dev->of_node; + struct device_node *cpu_port; + struct device_node *cpu_ep; + struct device_node *codec_ep; + struct device_node *codec_port; + struct device_node *codec_port_old; + int count = 0; + int rc; + + codec_port_old = NULL; + of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { + cpu_port = it.node; + cpu_ep = of_get_next_child(cpu_port, NULL); + codec_ep = of_graph_get_remote_endpoint(cpu_ep); + codec_port = of_graph_get_port_parent(codec_ep); + + of_node_put(cpu_ep); + of_node_put(codec_ep); + of_node_put(codec_port); + + if (cpu_ep) + count++; + + if (!codec_port) + continue; + + if (codec_port_old == codec_port) + continue; + + count++; + codec_port_old = codec_port; + } + + return count; +} + +static int asoc_graph_card_probe(struct platform_device *pdev) +{ + struct graph_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dai_props; + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + int num, ret; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + num = asoc_graph_get_dais_count(dev); + if (num == 0) + return -EINVAL; + + dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL); + dai_link = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL); + if (!dai_props || !dai_link) + return -ENOMEM; + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + + /* Init snd_soc_card */ + card = graph_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = priv->dai_link; + card->num_links = num; + card->codec_conf = &priv->codec_conf; + card->num_configs = 1; + + ret = asoc_graph_card_parse_of(priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) + goto err; + + return 0; +err: + asoc_simple_card_clean_reference(card); + + return ret; +} + +static int asoc_graph_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + return asoc_simple_card_clean_reference(card); +} + +static const struct of_device_id asoc_graph_of_match[] = { + { .compatible = "audio-graph-scu-card", }, + {}, +}; +MODULE_DEVICE_TABLE(of, asoc_graph_of_match); + +static struct platform_driver asoc_graph_card = { + .driver = { + .name = "asoc-audio-graph-scu-card", + .pm = &snd_soc_pm_ops, + .of_match_table = asoc_graph_of_match, + }, + .probe = asoc_graph_card_probe, + .remove = asoc_graph_card_remove, +}; +module_platform_driver(asoc_graph_card); + +MODULE_ALIAS("platform:asoc-audio-graph-scu-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Audio Graph SCU Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c new file mode 100644 index 000000000..d3f3f0fec --- /dev/null +++ b/sound/soc/generic/simple-card-utils.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// simple-card-utils.c +// +// Copyright (c) 2016 Kuninori Morimoto + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void asoc_simple_card_convert_fixup(struct asoc_simple_card_data *data, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + if (data->convert_rate) + rate->min = + rate->max = data->convert_rate; + + if (data->convert_channels) + channels->min = + channels->max = data->convert_channels; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_convert_fixup); + +void asoc_simple_card_parse_convert(struct device *dev, char *prefix, + struct asoc_simple_card_data *data) +{ + struct device_node *np = dev->of_node; + char prop[128]; + + if (!prefix) + prefix = ""; + + /* sampling rate convert */ + snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-rate"); + of_property_read_u32(np, prop, &data->convert_rate); + + /* channels transfer */ + snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-channels"); + of_property_read_u32(np, prop, &data->convert_channels); + + dev_dbg(dev, "convert_rate %d\n", data->convert_rate); + dev_dbg(dev, "convert_channels %d\n", data->convert_channels); +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_convert); + +int asoc_simple_card_parse_daifmt(struct device *dev, + struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt) +{ + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + unsigned int daifmt; + + daifmt = snd_soc_of_parse_daifmt(node, prefix, + &bitclkmaster, &framemaster); + daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + if (!bitclkmaster && !framemaster) { + /* + * No dai-link level and master setting was not found from + * sound node level, revert back to legacy DT parsing and + * take the settings from codec node. + */ + dev_dbg(dev, "Revert to legacy daifmt parsing\n"); + + daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | + (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); + } else { + if (codec == bitclkmaster) + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; + else + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; + } + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + *retfmt = daifmt; + + dev_dbg(dev, "format : %04x\n", daifmt); + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_daifmt); + +int asoc_simple_card_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...) +{ + va_list ap; + char *name = NULL; + int ret = -ENOMEM; + + va_start(ap, fmt); + name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap); + va_end(ap); + + if (name) { + ret = 0; + + dai_link->name = name; + dai_link->stream_name = name; + + dev_dbg(dev, "name : %s\n", name); + } + + return ret; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name); + +int asoc_simple_card_parse_card_name(struct snd_soc_card *card, + char *prefix) +{ + int ret; + + if (!prefix) + prefix = ""; + + /* Parse the card name from DT */ + ret = snd_soc_of_parse_card_name(card, "label"); + if (ret < 0 || !card->name) { + char prop[128]; + + snprintf(prop, sizeof(prop), "%sname", prefix); + ret = snd_soc_of_parse_card_name(card, prop); + if (ret < 0) + return ret; + } + + if (!card->name && card->dai_link) + card->name = card->dai_link->name; + + dev_dbg(card->dev, "Card Name: %s\n", card->name ? card->name : ""); + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_card_name); + +static void asoc_simple_card_clk_register(struct asoc_simple_dai *dai, + struct clk *clk) +{ + dai->clk = clk; +} + +int asoc_simple_card_clk_enable(struct asoc_simple_dai *dai) +{ + return clk_prepare_enable(dai->clk); +} +EXPORT_SYMBOL_GPL(asoc_simple_card_clk_enable); + +void asoc_simple_card_clk_disable(struct asoc_simple_dai *dai) +{ + clk_disable_unprepare(dai->clk); +} +EXPORT_SYMBOL_GPL(asoc_simple_card_clk_disable); + +int asoc_simple_card_parse_clk(struct device *dev, + struct device_node *node, + struct device_node *dai_of_node, + struct asoc_simple_dai *simple_dai, + const char *name) +{ + struct clk *clk; + u32 val; + + /* + * Parse dai->sysclk come from "clocks = <&xxx>" + * (if system has common clock) + * or "system-clock-frequency = " + * or device's module clock. + */ + clk = devm_get_clk_from_child(dev, node, NULL); + if (!IS_ERR(clk)) { + simple_dai->sysclk = clk_get_rate(clk); + + asoc_simple_card_clk_register(simple_dai, clk); + } else if (!of_property_read_u32(node, "system-clock-frequency", &val)) { + simple_dai->sysclk = val; + } else { + clk = devm_get_clk_from_child(dev, dai_of_node, NULL); + if (!IS_ERR(clk)) + simple_dai->sysclk = clk_get_rate(clk); + } + + if (of_property_read_bool(node, "system-clock-direction-out")) + simple_dai->clk_direction = SND_SOC_CLOCK_OUT; + + dev_dbg(dev, "%s : sysclk = %d, direction %d\n", name, + simple_dai->sysclk, simple_dai->clk_direction); + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_clk); + +int asoc_simple_card_parse_dai(struct device_node *node, + struct device_node **dai_of_node, + const char **dai_name, + const char *list_name, + const char *cells_name, + int *is_single_link) +{ + struct of_phandle_args args; + int ret; + + if (!node) + return 0; + + /* + * Get node via "sound-dai = <&phandle port>" + * it will be used as xxx_of_node on soc_bind_dai_link() + */ + ret = of_parse_phandle_with_args(node, list_name, cells_name, 0, &args); + if (ret) + return ret; + + /* Get dai->name */ + if (dai_name) { + ret = snd_soc_of_get_dai_name(node, dai_name); + if (ret < 0) + return ret; + } + + *dai_of_node = args.np; + + if (is_single_link) + *is_single_link = !args.args_count; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_dai); + +static int asoc_simple_card_get_dai_id(struct device_node *ep) +{ + struct device_node *node; + struct device_node *endpoint; + int i, id; + int ret; + + ret = snd_soc_get_dai_id(ep); + if (ret != -ENOTSUPP) + return ret; + + node = of_graph_get_port_parent(ep); + + /* + * Non HDMI sound case, counting port/endpoint on its DT + * is enough. Let's count it. + */ + i = 0; + id = -1; + for_each_endpoint_of_node(node, endpoint) { + if (endpoint == ep) + id = i; + i++; + } + + of_node_put(node); + + if (id < 0) + return -ENODEV; + + return id; +} + +int asoc_simple_card_parse_graph_dai(struct device_node *ep, + struct device_node **dai_of_node, + const char **dai_name) +{ + struct device_node *node; + struct of_phandle_args args; + int ret; + + if (!ep) + return 0; + if (!dai_name) + return 0; + + node = of_graph_get_port_parent(ep); + + /* Get dai->name */ + args.np = node; + args.args[0] = asoc_simple_card_get_dai_id(ep); + args.args_count = (of_graph_get_endpoint_count(node) > 1); + + ret = snd_soc_get_dai_name(&args, dai_name); + if (ret < 0) + return ret; + + *dai_of_node = node; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_graph_dai); + +int asoc_simple_card_init_dai(struct snd_soc_dai *dai, + struct asoc_simple_dai *simple_dai) +{ + int ret; + + if (simple_dai->sysclk) { + ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk, + simple_dai->clk_direction); + if (ret && ret != -ENOTSUPP) { + dev_err(dai->dev, "simple-card: set_sysclk error\n"); + return ret; + } + } + + if (simple_dai->slots) { + ret = snd_soc_dai_set_tdm_slot(dai, + simple_dai->tx_slot_mask, + simple_dai->rx_slot_mask, + simple_dai->slots, + simple_dai->slot_width); + if (ret && ret != -ENOTSUPP) { + dev_err(dai->dev, "simple-card: set_tdm_slot error\n"); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_init_dai); + +int asoc_simple_card_canonicalize_dailink(struct snd_soc_dai_link *dai_link) +{ + /* Assumes platform == cpu */ + if (!dai_link->platform_of_node) + dai_link->platform_of_node = dai_link->cpu_of_node; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_dailink); + +void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link, + int is_single_links) +{ + /* + * In soc_bind_dai_link() will check cpu name after + * of_node matching if dai_link has cpu_dai_name. + * but, it will never match if name was created by + * fmt_single_name() remove cpu_dai_name if cpu_args + * was 0. See: + * fmt_single_name() + * fmt_multiple_name() + */ + if (is_single_links) + dai_link->cpu_dai_name = NULL; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_canonicalize_cpu); + +int asoc_simple_card_clean_reference(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + int num_links; + + for (num_links = 0, dai_link = card->dai_link; + num_links < card->num_links; + num_links++, dai_link++) { + of_node_put(dai_link->cpu_of_node); + of_node_put(dai_link->codec_of_node); + } + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_clean_reference); + +int asoc_simple_card_of_parse_routing(struct snd_soc_card *card, + char *prefix, + int optional) +{ + struct device_node *node = card->dev->of_node; + char prop[128]; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); + + if (!of_property_read_bool(node, prop)) { + if (optional) + return 0; + return -EINVAL; + } + + return snd_soc_of_parse_audio_routing(card, prop); +} +EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_routing); + +int asoc_simple_card_of_parse_widgets(struct snd_soc_card *card, + char *prefix) +{ + struct device_node *node = card->dev->of_node; + char prop[128]; + + if (!prefix) + prefix = ""; + + snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); + + if (of_property_read_bool(node, prop)) + return snd_soc_of_parse_audio_simple_widgets(card, prop); + + /* no widgets is not error */ + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_of_parse_widgets); + +int asoc_simple_card_init_jack(struct snd_soc_card *card, + struct asoc_simple_jack *sjack, + int is_hp, char *prefix) +{ + struct device *dev = card->dev; + enum of_gpio_flags flags; + char prop[128]; + char *pin_name; + char *gpio_name; + int mask; + int det; + + if (!prefix) + prefix = ""; + + sjack->gpio.gpio = -ENOENT; + + if (is_hp) { + snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix); + pin_name = "Headphones"; + gpio_name = "Headphone detection"; + mask = SND_JACK_HEADPHONE; + } else { + snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix); + pin_name = "Mic Jack"; + gpio_name = "Mic detection"; + mask = SND_JACK_MICROPHONE; + } + + det = of_get_named_gpio_flags(dev->of_node, prop, 0, &flags); + if (det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (gpio_is_valid(det)) { + sjack->pin.pin = pin_name; + sjack->pin.mask = mask; + + sjack->gpio.name = gpio_name; + sjack->gpio.report = mask; + sjack->gpio.gpio = det; + sjack->gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW); + sjack->gpio.debounce_time = 150; + + snd_soc_card_jack_new(card, pin_name, mask, + &sjack->jack, + &sjack->pin, 1); + + snd_soc_jack_add_gpios(&sjack->jack, 1, + &sjack->gpio); + } + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_init_jack); + +/* Module information */ +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_DESCRIPTION("ALSA SoC Simple Card Utils"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c new file mode 100644 index 000000000..64bf3560c --- /dev/null +++ b/sound/soc/generic/simple-card.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ASoC simple sound card support +// +// Copyright (C) 2012 Renesas Solutions Corp. +// Kuninori Morimoto + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct simple_card_data { + struct snd_soc_card snd_card; + struct simple_dai_props { + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; + unsigned int mclk_fs; + } *dai_props; + unsigned int mclk_fs; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; + struct snd_soc_dai_link *dai_link; +}; + +#define simple_priv_to_card(priv) (&(priv)->snd_card) +#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) + +#define DAI "sound-dai" +#define CELL "#sound-dai-cells" +#define PREFIX "simple-audio-card," + +static int asoc_simple_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + int ret; + + ret = asoc_simple_card_clk_enable(&dai_props->cpu_dai); + if (ret) + return ret; + + ret = asoc_simple_card_clk_enable(&dai_props->codec_dai); + if (ret) + asoc_simple_card_clk_disable(&dai_props->cpu_dai); + + return ret; +} + +static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + + asoc_simple_card_clk_disable(&dai_props->cpu_dai); + + asoc_simple_card_clk_disable(&dai_props->codec_dai); +} + +static int asoc_simple_set_clk_rate(struct asoc_simple_dai *simple_dai, + unsigned long rate) +{ + if (!simple_dai->clk) + return 0; + + if (clk_get_rate(simple_dai->clk) == rate) + return 0; + + return clk_set_rate(simple_dai->clk, rate); +} + +static int asoc_simple_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + unsigned int mclk, mclk_fs = 0; + int ret = 0; + + if (priv->mclk_fs) + mclk_fs = priv->mclk_fs; + else if (dai_props->mclk_fs) + mclk_fs = dai_props->mclk_fs; + + if (mclk_fs) { + mclk = params_rate(params) * mclk_fs; + + ret = asoc_simple_set_clk_rate(&dai_props->codec_dai, mclk); + if (ret < 0) + return ret; + + ret = asoc_simple_set_clk_rate(&dai_props->cpu_dai, mclk); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + goto err; + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + goto err; + } + return 0; +err: + return ret; +} + +static const struct snd_soc_ops asoc_simple_card_ops = { + .startup = asoc_simple_card_startup, + .shutdown = asoc_simple_card_shutdown, + .hw_params = asoc_simple_card_hw_params, +}; + +static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec = rtd->codec_dai; + struct snd_soc_dai *cpu = rtd->cpu_dai; + struct simple_dai_props *dai_props = + simple_priv_to_props(priv, rtd->num); + int ret; + + ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai); + if (ret < 0) + return ret; + + return 0; +} + +static int asoc_simple_card_dai_link_of(struct device_node *node, + struct simple_card_data *priv, + int idx, + bool is_top_level_node) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); + struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx); + struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; + struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; + struct device_node *cpu = NULL; + struct device_node *plat = NULL; + struct device_node *codec = NULL; + char prop[128]; + char *prefix = ""; + int ret, single_cpu; + + /* For single DAI link & old style of DT node */ + if (is_top_level_node) + prefix = PREFIX; + + snprintf(prop, sizeof(prop), "%scpu", prefix); + cpu = of_get_child_by_name(node, prop); + + if (!cpu) { + ret = -EINVAL; + dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); + goto dai_link_of_err; + } + + snprintf(prop, sizeof(prop), "%splat", prefix); + plat = of_get_child_by_name(node, prop); + + snprintf(prop, sizeof(prop), "%scodec", prefix); + codec = of_get_child_by_name(node, prop); + + if (!codec) { + ret = -EINVAL; + dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); + goto dai_link_of_err; + } + + ret = asoc_simple_card_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + + of_property_read_u32(node, "mclk-fs", &dai_props->mclk_fs); + + ret = asoc_simple_card_parse_cpu(cpu, dai_link, + DAI, CELL, &single_cpu); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_codec(codec, dai_link, DAI, CELL); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_platform(plat, dai_link, DAI, CELL); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_of_parse_tdm(cpu, cpu_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_of_parse_tdm(codec, codec_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_parse_clk_codec(dev, codec, dai_link, codec_dai); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_canonicalize_dailink(dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codec_dai_name); + if (ret < 0) + goto dai_link_of_err; + + dai_link->ops = &asoc_simple_card_ops; + dai_link->init = asoc_simple_card_dai_init; + + asoc_simple_card_canonicalize_cpu(dai_link, single_cpu); + +dai_link_of_err: + of_node_put(cpu); + of_node_put(codec); + + return ret; +} + +static int asoc_simple_card_parse_aux_devs(struct device_node *node, + struct simple_card_data *priv) +{ + struct device *dev = simple_priv_to_dev(priv); + struct device_node *aux_node; + struct snd_soc_card *card = simple_priv_to_card(priv); + int i, n, len; + + if (!of_find_property(node, PREFIX "aux-devs", &len)) + return 0; /* Ok to have no aux-devs */ + + n = len / sizeof(__be32); + if (n <= 0) + return -EINVAL; + + card->aux_dev = devm_kcalloc(dev, + n, sizeof(*card->aux_dev), GFP_KERNEL); + if (!card->aux_dev) + return -ENOMEM; + + for (i = 0; i < n; i++) { + aux_node = of_parse_phandle(node, PREFIX "aux-devs", i); + if (!aux_node) + return -EINVAL; + card->aux_dev[i].codec_of_node = aux_node; + } + + card->num_aux_devs = n; + return 0; +} + +static int asoc_simple_card_parse_of(struct simple_card_data *priv) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_card *card = simple_priv_to_card(priv); + struct device_node *dai_link; + struct device_node *node = dev->of_node; + int ret; + + if (!node) + return -EINVAL; + + dai_link = of_get_child_by_name(node, PREFIX "dai-link"); + + ret = asoc_simple_card_of_parse_widgets(card, PREFIX); + if (ret < 0) + goto card_parse_end; + + ret = asoc_simple_card_of_parse_routing(card, PREFIX, 1); + if (ret < 0) + goto card_parse_end; + + /* Factor to mclk, used in hw_params() */ + of_property_read_u32(node, PREFIX "mclk-fs", &priv->mclk_fs); + + /* Single/Muti DAI link(s) & New style of DT node */ + if (dai_link) { + struct device_node *np = NULL; + int i = 0; + + for_each_child_of_node(node, np) { + dev_dbg(dev, "\tlink %d:\n", i); + ret = asoc_simple_card_dai_link_of(np, priv, + i, false); + if (ret < 0) { + of_node_put(np); + goto card_parse_end; + } + i++; + } + } else { + /* For single DAI link & old style of DT node */ + ret = asoc_simple_card_dai_link_of(node, priv, 0, true); + if (ret < 0) + goto card_parse_end; + } + + ret = asoc_simple_card_parse_card_name(card, PREFIX); + if (ret < 0) + goto card_parse_end; + + ret = asoc_simple_card_parse_aux_devs(node, priv); + +card_parse_end: + of_node_put(dai_link); + + return ret; +} + +static int asoc_simple_soc_card_probe(struct snd_soc_card *card) +{ + struct simple_card_data *priv = snd_soc_card_get_drvdata(card); + int ret; + + ret = asoc_simple_card_init_hp(card, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_mic(card, &priv->mic_jack, PREFIX); + if (ret < 0) + return ret; + + return 0; +} + +static int asoc_simple_card_probe(struct platform_device *pdev) +{ + struct simple_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct simple_dai_props *dai_props; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct snd_soc_card *card; + int num, ret; + + /* Get the number of DAI links */ + if (np && of_get_child_by_name(np, PREFIX "dai-link")) + num = of_get_child_count(np); + else + num = 1; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL); + dai_link = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL); + if (!dai_props || !dai_link) + return -ENOMEM; + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + + /* Init snd_soc_card */ + card = simple_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = priv->dai_link; + card->num_links = num; + card->probe = asoc_simple_soc_card_probe; + + if (np && of_device_is_available(np)) { + + ret = asoc_simple_card_parse_of(priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + + } else { + struct asoc_simple_card_info *cinfo; + + cinfo = dev->platform_data; + if (!cinfo) { + dev_err(dev, "no info for asoc-simple-card\n"); + return -EINVAL; + } + + if (!cinfo->name || + !cinfo->codec_dai.name || + !cinfo->codec || + !cinfo->platform || + !cinfo->cpu_dai.name) { + dev_err(dev, "insufficient asoc_simple_card_info settings\n"); + return -EINVAL; + } + + card->name = (cinfo->card) ? cinfo->card : cinfo->name; + dai_link->name = cinfo->name; + dai_link->stream_name = cinfo->name; + dai_link->platform_name = cinfo->platform; + dai_link->codec_name = cinfo->codec; + dai_link->cpu_dai_name = cinfo->cpu_dai.name; + dai_link->codec_dai_name = cinfo->codec_dai.name; + dai_link->dai_fmt = cinfo->daifmt; + dai_link->init = asoc_simple_card_dai_init; + memcpy(&priv->dai_props->cpu_dai, &cinfo->cpu_dai, + sizeof(priv->dai_props->cpu_dai)); + memcpy(&priv->dai_props->codec_dai, &cinfo->codec_dai, + sizeof(priv->dai_props->codec_dai)); + } + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) + goto err; + + return 0; +err: + asoc_simple_card_clean_reference(card); + + return ret; +} + +static int asoc_simple_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + return asoc_simple_card_clean_reference(card); +} + +static const struct of_device_id asoc_simple_of_match[] = { + { .compatible = "simple-audio-card", }, + {}, +}; +MODULE_DEVICE_TABLE(of, asoc_simple_of_match); + +static struct platform_driver asoc_simple_card = { + .driver = { + .name = "asoc-simple-card", + .pm = &snd_soc_pm_ops, + .of_match_table = asoc_simple_of_match, + }, + .probe = asoc_simple_card_probe, + .remove = asoc_simple_card_remove, +}; + +module_platform_driver(asoc_simple_card); + +MODULE_ALIAS("platform:asoc-simple-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Simple Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/sound/soc/generic/simple-scu-card.c b/sound/soc/generic/simple-scu-card.c new file mode 100644 index 000000000..16a83bc51 --- /dev/null +++ b/sound/soc/generic/simple-scu-card.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ASoC simple SCU sound card support +// +// Copyright (C) 2015 Renesas Solutions Corp. +// Kuninori Morimoto +// +// based on ${LINUX}/sound/soc/generic/simple-card.c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct simple_card_data { + struct snd_soc_card snd_card; + struct snd_soc_codec_conf codec_conf; + struct asoc_simple_dai *dai_props; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_card_data adata; +}; + +#define simple_priv_to_card(priv) (&(priv)->snd_card) +#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) +#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) +#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) + +#define DAI "sound-dai" +#define CELL "#sound-dai-cells" +#define PREFIX "simple-audio-card," + +static int asoc_simple_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct asoc_simple_dai *dai_props = + simple_priv_to_props(priv, rtd->num); + + return asoc_simple_card_clk_enable(dai_props); +} + +static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct asoc_simple_dai *dai_props = + simple_priv_to_props(priv, rtd->num); + + asoc_simple_card_clk_disable(dai_props); +} + +static const struct snd_soc_ops asoc_simple_card_ops = { + .startup = asoc_simple_card_startup, + .shutdown = asoc_simple_card_shutdown, +}; + +static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dai_props; + int num = rtd->num; + + dai_link = simple_priv_to_link(priv, num); + dai_props = simple_priv_to_props(priv, num); + dai = dai_link->dynamic ? + rtd->cpu_dai : + rtd->codec_dai; + + return asoc_simple_card_init_dai(dai, dai_props); +} + +static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + + asoc_simple_card_convert_fixup(&priv->adata, params); + + return 0; +} + +static int asoc_simple_card_dai_link_of(struct device_node *np, + struct simple_card_data *priv, + unsigned int daifmt, + int idx, bool is_fe) +{ + struct device *dev = simple_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); + struct asoc_simple_dai *dai_props = simple_priv_to_props(priv, idx); + struct snd_soc_card *card = simple_priv_to_card(priv); + int ret; + + if (is_fe) { + int is_single_links = 0; + + /* BE is dummy */ + dai_link->codec_of_node = NULL; + dai_link->codec_dai_name = "snd-soc-dummy-dai"; + dai_link->codec_name = "snd-soc-dummy"; + + /* FE settings */ + dai_link->dynamic = 1; + dai_link->dpcm_merged_format = 1; + + ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL, + &is_single_links); + if (ret) + return ret; + + ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, dai_props); + if (ret < 0) + return ret; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); + if (ret < 0) + return ret; + + asoc_simple_card_canonicalize_cpu(dai_link, is_single_links); + } else { + /* FE is dummy */ + dai_link->cpu_of_node = NULL; + dai_link->cpu_dai_name = "snd-soc-dummy-dai"; + dai_link->cpu_name = "snd-soc-dummy"; + + /* BE settings */ + dai_link->no_pcm = 1; + dai_link->be_hw_params_fixup = asoc_simple_card_be_hw_params_fixup; + + ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL); + if (ret < 0) + return ret; + + ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, dai_props); + if (ret < 0) + return ret; + + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "be.%s", + dai_link->codec_dai_name); + if (ret < 0) + return ret; + + snd_soc_of_parse_audio_prefix(card, + &priv->codec_conf, + dai_link->codec_of_node, + PREFIX "prefix"); + } + + ret = asoc_simple_card_of_parse_tdm(np, dai_props); + if (ret) + return ret; + + ret = asoc_simple_card_canonicalize_dailink(dai_link); + if (ret < 0) + return ret; + + dai_link->dai_fmt = daifmt; + dai_link->dpcm_playback = 1; + dai_link->dpcm_capture = 1; + dai_link->ops = &asoc_simple_card_ops; + dai_link->init = asoc_simple_card_dai_init; + + return 0; +} + +static int asoc_simple_card_parse_of(struct simple_card_data *priv) + +{ + struct device *dev = simple_priv_to_dev(priv); + struct device_node *np; + struct snd_soc_card *card = simple_priv_to_card(priv); + struct device_node *node = dev->of_node; + unsigned int daifmt = 0; + bool is_fe; + int ret, i; + + if (!node) + return -EINVAL; + + ret = asoc_simple_card_of_parse_widgets(card, PREFIX); + if (ret < 0) + return ret; + + ret = asoc_simple_card_of_parse_routing(card, PREFIX, 0); + if (ret < 0) + return ret; + + asoc_simple_card_parse_convert(dev, PREFIX, &priv->adata); + + /* find 1st codec */ + np = of_get_child_by_name(node, PREFIX "codec"); + if (!np) + return -ENODEV; + + ret = asoc_simple_card_parse_daifmt(dev, node, np, PREFIX, &daifmt); + if (ret < 0) + return ret; + + i = 0; + for_each_child_of_node(node, np) { + is_fe = false; + if (strcmp(np->name, PREFIX "cpu") == 0) + is_fe = true; + + ret = asoc_simple_card_dai_link_of(np, priv, daifmt, i, is_fe); + if (ret < 0) + return ret; + i++; + } + + ret = asoc_simple_card_parse_card_name(card, PREFIX); + if (ret < 0) + return ret; + + return 0; +} + +static int asoc_simple_card_probe(struct platform_device *pdev) +{ + struct simple_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct asoc_simple_dai *dai_props; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + int num, ret; + + /* Allocate the private data */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + num = of_get_child_count(np); + + dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL); + dai_link = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL); + if (!dai_props || !dai_link) + return -ENOMEM; + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + + /* Init snd_soc_card */ + card = simple_priv_to_card(priv); + card->owner = THIS_MODULE; + card->dev = dev; + card->dai_link = priv->dai_link; + card->num_links = num; + card->codec_conf = &priv->codec_conf; + card->num_configs = 1; + + ret = asoc_simple_card_parse_of(priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) + goto err; + + return 0; +err: + asoc_simple_card_clean_reference(card); + + return ret; +} + +static int asoc_simple_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + return asoc_simple_card_clean_reference(card); +} + +static const struct of_device_id asoc_simple_of_match[] = { + { .compatible = "renesas,rsrc-card", }, + { .compatible = "simple-scu-audio-card", }, + {}, +}; +MODULE_DEVICE_TABLE(of, asoc_simple_of_match); + +static struct platform_driver asoc_simple_card = { + .driver = { + .name = "simple-scu-audio-card", + .pm = &snd_soc_pm_ops, + .of_match_table = asoc_simple_of_match, + }, + .probe = asoc_simple_card_probe, + .remove = asoc_simple_card_remove, +}; + +module_platform_driver(asoc_simple_card); + +MODULE_ALIAS("platform:asoc-simple-scu-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC Simple SCU Sound Card"); +MODULE_AUTHOR("Kuninori Morimoto "); diff --git a/sound/soc/hisilicon/Kconfig b/sound/soc/hisilicon/Kconfig new file mode 100644 index 000000000..4356d5a1d --- /dev/null +++ b/sound/soc/hisilicon/Kconfig @@ -0,0 +1,5 @@ +config SND_I2S_HI6210_I2S + tristate "Hisilicon I2S controller" + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Hisilicon I2S diff --git a/sound/soc/hisilicon/Makefile b/sound/soc/hisilicon/Makefile new file mode 100644 index 000000000..e8095e2af --- /dev/null +++ b/sound/soc/hisilicon/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SND_I2S_HI6210_I2S) += hi6210-i2s.o diff --git a/sound/soc/hisilicon/hi6210-i2s.c b/sound/soc/hisilicon/hi6210-i2s.c new file mode 100644 index 000000000..864718ef8 --- /dev/null +++ b/sound/soc/hisilicon/hi6210-i2s.c @@ -0,0 +1,620 @@ +/* + * linux/sound/soc/m8m/hi6210_i2s.c - I2S IP driver + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * 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. + * + * This driver only deals with S2 interface (BT) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hi6210-i2s.h" + +struct hi6210_i2s { + struct device *dev; + struct reset_control *rc; + struct clk *clk[8]; + int clocks; + struct snd_soc_dai_driver dai; + void __iomem *base; + struct regmap *sysctrl; + phys_addr_t base_phys; + struct snd_dmaengine_dai_dma_data dma_data[2]; + int clk_rate; + spinlock_t lock; + int rate; + int format; + u8 bits; + u8 channels; + u8 id; + u8 channel_length; + u8 use; + u32 master:1; + u32 status:1; +}; + +#define SC_PERIPH_CLKEN1 0x210 +#define SC_PERIPH_CLKDIS1 0x214 + +#define SC_PERIPH_CLKEN3 0x230 +#define SC_PERIPH_CLKDIS3 0x234 + +#define SC_PERIPH_CLKEN12 0x270 +#define SC_PERIPH_CLKDIS12 0x274 + +#define SC_PERIPH_RSTEN1 0x310 +#define SC_PERIPH_RSTDIS1 0x314 +#define SC_PERIPH_RSTSTAT1 0x318 + +#define SC_PERIPH_RSTEN2 0x320 +#define SC_PERIPH_RSTDIS2 0x324 +#define SC_PERIPH_RSTSTAT2 0x328 + +#define SOC_PMCTRL_BBPPLLALIAS 0x48 + +enum { + CLK_DACODEC, + CLK_I2S_BASE, +}; + +static inline void hi6210_write_reg(struct hi6210_i2s *i2s, int reg, u32 val) +{ + writel(val, i2s->base + reg); +} + +static inline u32 hi6210_read_reg(struct hi6210_i2s *i2s, int reg) +{ + return readl(i2s->base + reg); +} + +static int hi6210_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + int ret, n; + u32 val; + + /* deassert reset on ABB */ + regmap_read(i2s->sysctrl, SC_PERIPH_RSTSTAT2, &val); + if (val & BIT(4)) + regmap_write(i2s->sysctrl, SC_PERIPH_RSTDIS2, BIT(4)); + + for (n = 0; n < i2s->clocks; n++) { + ret = clk_prepare_enable(i2s->clk[n]); + if (ret) + goto err_unprepare_clk; + } + + ret = clk_set_rate(i2s->clk[CLK_I2S_BASE], 49152000); + if (ret) { + dev_err(i2s->dev, "%s: setting 49.152MHz base rate failed %d\n", + __func__, ret); + goto err_unprepare_clk; + } + + /* enable clock before frequency division */ + regmap_write(i2s->sysctrl, SC_PERIPH_CLKEN12, BIT(9)); + + /* enable codec working clock / == "codec bus clock" */ + regmap_write(i2s->sysctrl, SC_PERIPH_CLKEN1, BIT(5)); + + /* deassert reset on codec / interface clock / working clock */ + regmap_write(i2s->sysctrl, SC_PERIPH_RSTEN1, BIT(5)); + regmap_write(i2s->sysctrl, SC_PERIPH_RSTDIS1, BIT(5)); + + /* not interested in i2s irqs */ + val = hi6210_read_reg(i2s, HII2S_CODEC_IRQ_MASK); + val |= 0x3f; + hi6210_write_reg(i2s, HII2S_CODEC_IRQ_MASK, val); + + + /* reset the stereo downlink fifo */ + val = hi6210_read_reg(i2s, HII2S_APB_AFIFO_CFG_1); + val |= (BIT(5) | BIT(4)); + hi6210_write_reg(i2s, HII2S_APB_AFIFO_CFG_1, val); + + val = hi6210_read_reg(i2s, HII2S_APB_AFIFO_CFG_1); + val &= ~(BIT(5) | BIT(4)); + hi6210_write_reg(i2s, HII2S_APB_AFIFO_CFG_1, val); + + + val = hi6210_read_reg(i2s, HII2S_SW_RST_N); + val &= ~(HII2S_SW_RST_N__ST_DL_WORDLEN_MASK << + HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT); + val |= (HII2S_BITS_16 << HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT); + hi6210_write_reg(i2s, HII2S_SW_RST_N, val); + + val = hi6210_read_reg(i2s, HII2S_MISC_CFG); + /* mux 11/12 = APB not i2s */ + val &= ~HII2S_MISC_CFG__ST_DL_TEST_SEL; + /* BT R ch 0 = mixer op of DACR ch */ + val &= ~HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL; + val &= ~HII2S_MISC_CFG__S2_DOUT_TEST_SEL; + + val |= HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL; + /* BT L ch = 1 = mux 7 = "mixer output of DACL */ + val |= HII2S_MISC_CFG__S2_DOUT_TEST_SEL; + hi6210_write_reg(i2s, HII2S_MISC_CFG, val); + + val = hi6210_read_reg(i2s, HII2S_SW_RST_N); + val |= HII2S_SW_RST_N__SW_RST_N; + hi6210_write_reg(i2s, HII2S_SW_RST_N, val); + + return 0; + +err_unprepare_clk: + while (n--) + clk_disable_unprepare(i2s->clk[n]); + return ret; +} + +static void hi6210_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + int n; + + for (n = 0; n < i2s->clocks; n++) + clk_disable_unprepare(i2s->clk[n]); + + regmap_write(i2s->sysctrl, SC_PERIPH_RSTEN1, BIT(5)); +} + +static void hi6210_i2s_txctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 val; + + spin_lock(&i2s->lock); + if (on) { + /* enable S2 TX */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_IF_TX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } else { + /* disable S2 TX */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_IF_TX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } + spin_unlock(&i2s->lock); +} + +static void hi6210_i2s_rxctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 val; + + spin_lock(&i2s->lock); + if (on) { + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_IF_RX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } else { + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_IF_RX_EN; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + } + spin_unlock(&i2s->lock); +} + +static int hi6210_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + /* + * We don't actually set the hardware until the hw_params + * call, but we need to validate the user input here. + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + break; + default: + return -EINVAL; + } + + i2s->format = fmt; + i2s->master = (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBS_CFS; + + return 0; +} + +static int hi6210_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct hi6210_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 bits = 0, rate = 0, signed_data = 0, fmt = 0; + u32 val; + struct snd_dmaengine_dai_dma_data *dma_data; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U16_LE: + signed_data = HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + /* fallthru */ + case SNDRV_PCM_FORMAT_S16_LE: + bits = HII2S_BITS_16; + break; + case SNDRV_PCM_FORMAT_U24_LE: + signed_data = HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + /* fallthru */ + case SNDRV_PCM_FORMAT_S24_LE: + bits = HII2S_BITS_24; + break; + default: + dev_err(cpu_dai->dev, "Bad format\n"); + return -EINVAL; + } + + + switch (params_rate(params)) { + case 8000: + rate = HII2S_FS_RATE_8KHZ; + break; + case 16000: + rate = HII2S_FS_RATE_16KHZ; + break; + case 32000: + rate = HII2S_FS_RATE_32KHZ; + break; + case 48000: + rate = HII2S_FS_RATE_48KHZ; + break; + case 96000: + rate = HII2S_FS_RATE_96KHZ; + break; + case 192000: + rate = HII2S_FS_RATE_192KHZ; + break; + default: + dev_err(cpu_dai->dev, "Bad rate: %d\n", params_rate(params)); + return -EINVAL; + } + + if (!(params_channels(params))) { + dev_err(cpu_dai->dev, "Bad channels\n"); + return -EINVAL; + } + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + + switch (bits) { + case HII2S_BITS_24: + i2s->bits = 32; + dma_data->addr_width = 3; + break; + default: + i2s->bits = 16; + dma_data->addr_width = 2; + break; + } + i2s->rate = params_rate(params); + i2s->channels = params_channels(params); + i2s->channel_length = i2s->channels * i2s->bits; + + val = hi6210_read_reg(i2s, HII2S_ST_DL_FIFO_TH_CFG); + val &= ~((HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT) | + (HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_MASK << + HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT)); + val |= ((16 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT) | + (30 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT) | + (16 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT) | + (30 << HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT)); + hi6210_write_reg(i2s, HII2S_ST_DL_FIFO_TH_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_IF_CLK_EN_CFG); + val |= (BIT(19) | BIT(18) | BIT(17) | + HII2S_IF_CLK_EN_CFG__S2_IF_CLK_EN | + HII2S_IF_CLK_EN_CFG__S2_OL_MIXER_EN | + HII2S_IF_CLK_EN_CFG__S2_OL_SRC_EN | + HII2S_IF_CLK_EN_CFG__ST_DL_R_EN | + HII2S_IF_CLK_EN_CFG__ST_DL_L_EN); + hi6210_write_reg(i2s, HII2S_IF_CLK_EN_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_DIG_FILTER_CLK_EN_CFG); + val &= ~(HII2S_DIG_FILTER_CLK_EN_CFG__DACR_SDM_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACR_HBF2I_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACR_AGC_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_SDM_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_HBF2I_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_AGC_EN); + val |= (HII2S_DIG_FILTER_CLK_EN_CFG__DACR_MIXER_EN | + HII2S_DIG_FILTER_CLK_EN_CFG__DACL_MIXER_EN); + hi6210_write_reg(i2s, HII2S_DIG_FILTER_CLK_EN_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_DIG_FILTER_MODULE_CFG); + val &= ~(HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN2_MUTE | + HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN2_MUTE); + hi6210_write_reg(i2s, HII2S_DIG_FILTER_MODULE_CFG, val); + + val = hi6210_read_reg(i2s, HII2S_MUX_TOP_MODULE_CFG); + val &= ~(HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN1_MUTE | + HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN2_MUTE | + HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN1_MUTE | + HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN2_MUTE); + hi6210_write_reg(i2s, HII2S_MUX_TOP_MODULE_CFG, val); + + + switch (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = false; + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_MST_SLV; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = true; + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_MST_SLV; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + default: + WARN_ONCE(1, "Invalid i2s->fmt MASTER_MASK. This shouldn't happen\n"); + return -EINVAL; + } + + switch (i2s->format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fmt = HII2S_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + fmt = HII2S_FORMAT_LEFT_JUST; + break; + case SND_SOC_DAIFMT_RIGHT_J: + fmt = HII2S_FORMAT_RIGHT_JUST; + break; + default: + WARN_ONCE(1, "Invalid i2s->fmt FORMAT_MASK. This shouldn't happen\n"); + return -EINVAL; + } + + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~(HII2S_I2S_CFG__S2_FUNC_MODE_MASK << + HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT); + val |= fmt << HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + + + val = hi6210_read_reg(i2s, HII2S_CLK_SEL); + val &= ~(HII2S_CLK_SEL__I2S_BT_FM_SEL | /* BT gets the I2S */ + HII2S_CLK_SEL__EXT_12_288MHZ_SEL); + hi6210_write_reg(i2s, HII2S_CLK_SEL, val); + + dma_data->maxburst = 2; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data->addr = i2s->base_phys + HII2S_ST_DL_CHANNEL; + else + dma_data->addr = i2s->base_phys + HII2S_STEREO_UPLINK_CHANNEL; + + switch (i2s->channels) { + case 1: + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val |= HII2S_I2S_CFG__S2_FRAME_MODE; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + default: + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_FRAME_MODE; + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + break; + } + + /* clear loopback, set signed type and word length */ + val = hi6210_read_reg(i2s, HII2S_I2S_CFG); + val &= ~HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT; + val &= ~(HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_MASK << + HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT); + val &= ~(HII2S_I2S_CFG__S2_DIRECT_LOOP_MASK << + HII2S_I2S_CFG__S2_DIRECT_LOOP_SHIFT); + val |= signed_data; + val |= (bits << HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT); + hi6210_write_reg(i2s, HII2S_I2S_CFG, val); + + + if (!i2s->master) + return 0; + + /* set DAC and related units to correct rate */ + val = hi6210_read_reg(i2s, HII2S_FS_CFG); + val &= ~(HII2S_FS_CFG__FS_S2_MASK << HII2S_FS_CFG__FS_S2_SHIFT); + val &= ~(HII2S_FS_CFG__FS_DACLR_MASK << HII2S_FS_CFG__FS_DACLR_SHIFT); + val &= ~(HII2S_FS_CFG__FS_ST_DL_R_MASK << + HII2S_FS_CFG__FS_ST_DL_R_SHIFT); + val &= ~(HII2S_FS_CFG__FS_ST_DL_L_MASK << + HII2S_FS_CFG__FS_ST_DL_L_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_S2_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_DACLR_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_ST_DL_R_SHIFT); + val |= (rate << HII2S_FS_CFG__FS_ST_DL_L_SHIFT); + hi6210_write_reg(i2s, HII2S_FS_CFG, val); + + return 0; +} + +static int hi6210_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hi6210_i2s_rxctrl(cpu_dai, 1); + else + hi6210_i2s_txctrl(cpu_dai, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hi6210_i2s_rxctrl(cpu_dai, 0); + else + hi6210_i2s_txctrl(cpu_dai, 0); + break; + default: + dev_err(cpu_dai->dev, "unknown cmd\n"); + return -EINVAL; + } + return 0; +} + +static int hi6210_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct hi6210_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, + &i2s->dma_data[SNDRV_PCM_STREAM_PLAYBACK], + &i2s->dma_data[SNDRV_PCM_STREAM_CAPTURE]); + + return 0; +} + + +static const struct snd_soc_dai_ops hi6210_i2s_dai_ops = { + .trigger = hi6210_i2s_trigger, + .hw_params = hi6210_i2s_hw_params, + .set_fmt = hi6210_i2s_set_fmt, + .startup = hi6210_i2s_startup, + .shutdown = hi6210_i2s_shutdown, +}; + +static const struct snd_soc_dai_driver hi6210_i2s_dai_init = { + .probe = hi6210_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .ops = &hi6210_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver hi6210_i2s_i2s_comp = { + .name = "hi6210_i2s-i2s", +}; + +static int hi6210_i2s_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct hi6210_i2s *i2s; + struct resource *res; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->dev = dev; + spin_lock_init(&i2s->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->base = devm_ioremap_resource(dev, res); + if (IS_ERR(i2s->base)) + return PTR_ERR(i2s->base); + + i2s->base_phys = (phys_addr_t)res->start; + i2s->dai = hi6210_i2s_dai_init; + + dev_set_drvdata(&pdev->dev, i2s); + + i2s->sysctrl = syscon_regmap_lookup_by_phandle(node, + "hisilicon,sysctrl-syscon"); + if (IS_ERR(i2s->sysctrl)) + return PTR_ERR(i2s->sysctrl); + + i2s->clk[CLK_DACODEC] = devm_clk_get(&pdev->dev, "dacodec"); + if (IS_ERR_OR_NULL(i2s->clk[CLK_DACODEC])) + return PTR_ERR(i2s->clk[CLK_DACODEC]); + i2s->clocks++; + + i2s->clk[CLK_I2S_BASE] = devm_clk_get(&pdev->dev, "i2s-base"); + if (IS_ERR_OR_NULL(i2s->clk[CLK_I2S_BASE])) + return PTR_ERR(i2s->clk[CLK_I2S_BASE]); + i2s->clocks++; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, &hi6210_i2s_i2s_comp, + &i2s->dai, 1); + return ret; +} + +static const struct of_device_id hi6210_i2s_dt_ids[] = { + { .compatible = "hisilicon,hi6210-i2s" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, hi6210_i2s_dt_ids); + +static struct platform_driver hi6210_i2s_driver = { + .probe = hi6210_i2s_probe, + .driver = { + .name = "hi6210_i2s", + .of_match_table = hi6210_i2s_dt_ids, + }, +}; + +module_platform_driver(hi6210_i2s_driver); + +MODULE_DESCRIPTION("Hisilicon HI6210 I2S driver"); +MODULE_AUTHOR("Andy Green "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/hisilicon/hi6210-i2s.h b/sound/soc/hisilicon/hi6210-i2s.h new file mode 100644 index 000000000..85cecc493 --- /dev/null +++ b/sound/soc/hisilicon/hi6210-i2s.h @@ -0,0 +1,276 @@ +/* + * linux/sound/soc/hisilicon/hi6210-i2s.h + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * 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, see . + * + * Note at least on 6220, S2 == BT, S1 == Digital FM Radio IF + */ + +#ifndef _HI6210_I2S_H +#define _HI6210_I2S_H + +#define HII2S_SW_RST_N 0 + +#define HII2S_SW_RST_N__STEREO_UPLINK_WORDLEN_SHIFT 28 +#define HII2S_SW_RST_N__STEREO_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__THIRDMD_UPLINK_WORDLEN_SHIFT 26 +#define HII2S_SW_RST_N__THIRDMD_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__VOICE_UPLINK_WORDLEN_SHIFT 24 +#define HII2S_SW_RST_N__VOICE_UPLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__ST_DL_WORDLEN_SHIFT 20 +#define HII2S_SW_RST_N__ST_DL_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__THIRDMD_DLINK_WORDLEN_SHIFT 18 +#define HII2S_SW_RST_N__THIRDMD_DLINK_WORDLEN_MASK 3 +#define HII2S_SW_RST_N__VOICE_DLINK_WORDLEN_SHIFT 16 +#define HII2S_SW_RST_N__VOICE_DLINK_WORDLEN_MASK 3 + +#define HII2S_SW_RST_N__SW_RST_N BIT(0) + +enum hi6210_bits { + HII2S_BITS_16, + HII2S_BITS_18, + HII2S_BITS_20, + HII2S_BITS_24, +}; + + +#define HII2S_IF_CLK_EN_CFG 4 + +#define HII2S_IF_CLK_EN_CFG__THIRDMD_UPLINK_EN BIT(25) +#define HII2S_IF_CLK_EN_CFG__THIRDMD_DLINK_EN BIT(24) +#define HII2S_IF_CLK_EN_CFG__S3_IF_CLK_EN BIT(20) +#define HII2S_IF_CLK_EN_CFG__S2_IF_CLK_EN BIT(16) +#define HII2S_IF_CLK_EN_CFG__S2_OL_MIXER_EN BIT(15) +#define HII2S_IF_CLK_EN_CFG__S2_OL_SRC_EN BIT(14) +#define HII2S_IF_CLK_EN_CFG__S2_IR_PGA_EN BIT(13) +#define HII2S_IF_CLK_EN_CFG__S2_IL_PGA_EN BIT(12) +#define HII2S_IF_CLK_EN_CFG__S1_IR_PGA_EN BIT(10) +#define HII2S_IF_CLK_EN_CFG__S1_IL_PGA_EN BIT(9) +#define HII2S_IF_CLK_EN_CFG__S1_IF_CLK_EN BIT(8) +#define HII2S_IF_CLK_EN_CFG__VOICE_DLINK_SRC_EN BIT(7) +#define HII2S_IF_CLK_EN_CFG__VOICE_DLINK_EN BIT(6) +#define HII2S_IF_CLK_EN_CFG__ST_DL_R_EN BIT(5) +#define HII2S_IF_CLK_EN_CFG__ST_DL_L_EN BIT(4) +#define HII2S_IF_CLK_EN_CFG__VOICE_UPLINK_R_EN BIT(3) +#define HII2S_IF_CLK_EN_CFG__VOICE_UPLINK_L_EN BIT(2) +#define HII2S_IF_CLK_EN_CFG__STEREO_UPLINK_R_EN BIT(1) +#define HII2S_IF_CLK_EN_CFG__STEREO_UPLINK_L_EN BIT(0) + +#define HII2S_DIG_FILTER_CLK_EN_CFG 8 +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_SDM_EN BIT(30) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_HBF2I_EN BIT(28) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_MIXER_EN BIT(25) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACR_AGC_EN BIT(24) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_SDM_EN BIT(22) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_HBF2I_EN BIT(20) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_MIXER_EN BIT(17) +#define HII2S_DIG_FILTER_CLK_EN_CFG__DACL_AGC_EN BIT(16) + +#define HII2S_FS_CFG 0xc + +#define HII2S_FS_CFG__FS_S2_SHIFT 28 +#define HII2S_FS_CFG__FS_S2_MASK 7 +#define HII2S_FS_CFG__FS_S1_SHIFT 24 +#define HII2S_FS_CFG__FS_S1_MASK 7 +#define HII2S_FS_CFG__FS_ADCLR_SHIFT 20 +#define HII2S_FS_CFG__FS_ADCLR_MASK 7 +#define HII2S_FS_CFG__FS_DACLR_SHIFT 16 +#define HII2S_FS_CFG__FS_DACLR_MASK 7 +#define HII2S_FS_CFG__FS_ST_DL_R_SHIFT 8 +#define HII2S_FS_CFG__FS_ST_DL_R_MASK 7 +#define HII2S_FS_CFG__FS_ST_DL_L_SHIFT 4 +#define HII2S_FS_CFG__FS_ST_DL_L_MASK 7 +#define HII2S_FS_CFG__FS_VOICE_DLINK_SHIFT 0 +#define HII2S_FS_CFG__FS_VOICE_DLINK_MASK 7 + +enum hi6210_i2s_rates { + HII2S_FS_RATE_8KHZ = 0, + HII2S_FS_RATE_16KHZ = 1, + HII2S_FS_RATE_32KHZ = 2, + HII2S_FS_RATE_48KHZ = 4, + HII2S_FS_RATE_96KHZ = 5, + HII2S_FS_RATE_192KHZ = 6, +}; + +#define HII2S_I2S_CFG 0x10 + +#define HII2S_I2S_CFG__S2_IF_TX_EN BIT(31) +#define HII2S_I2S_CFG__S2_IF_RX_EN BIT(30) +#define HII2S_I2S_CFG__S2_FRAME_MODE BIT(29) +#define HII2S_I2S_CFG__S2_MST_SLV BIT(28) +#define HII2S_I2S_CFG__S2_LRCK_MODE BIT(27) +#define HII2S_I2S_CFG__S2_CHNNL_MODE BIT(26) +#define HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_SHIFT 24 +#define HII2S_I2S_CFG__S2_CODEC_IO_WORDLENGTH_MASK 3 +#define HII2S_I2S_CFG__S2_DIRECT_LOOP_SHIFT 22 +#define HII2S_I2S_CFG__S2_DIRECT_LOOP_MASK 3 +#define HII2S_I2S_CFG__S2_TX_CLK_SEL BIT(21) +#define HII2S_I2S_CFG__S2_RX_CLK_SEL BIT(20) +#define HII2S_I2S_CFG__S2_CODEC_DATA_FORMAT BIT(19) +#define HII2S_I2S_CFG__S2_FUNC_MODE_SHIFT 16 +#define HII2S_I2S_CFG__S2_FUNC_MODE_MASK 7 +#define HII2S_I2S_CFG__S1_IF_TX_EN BIT(15) +#define HII2S_I2S_CFG__S1_IF_RX_EN BIT(14) +#define HII2S_I2S_CFG__S1_FRAME_MODE BIT(13) +#define HII2S_I2S_CFG__S1_MST_SLV BIT(12) +#define HII2S_I2S_CFG__S1_LRCK_MODE BIT(11) +#define HII2S_I2S_CFG__S1_CHNNL_MODE BIT(10) +#define HII2S_I2S_CFG__S1_CODEC_IO_WORDLENGTH_SHIFT 8 +#define HII2S_I2S_CFG__S1_CODEC_IO_WORDLENGTH_MASK 3 +#define HII2S_I2S_CFG__S1_DIRECT_LOOP_SHIFT 6 +#define HII2S_I2S_CFG__S1_DIRECT_LOOP_MASK 3 +#define HII2S_I2S_CFG__S1_TX_CLK_SEL BIT(5) +#define HII2S_I2S_CFG__S1_RX_CLK_SEL BIT(4) +#define HII2S_I2S_CFG__S1_CODEC_DATA_FORMAT BIT(3) +#define HII2S_I2S_CFG__S1_FUNC_MODE_SHIFT 0 +#define HII2S_I2S_CFG__S1_FUNC_MODE_MASK 7 + +enum hi6210_i2s_formats { + HII2S_FORMAT_I2S, + HII2S_FORMAT_PCM_STD, + HII2S_FORMAT_PCM_USER, + HII2S_FORMAT_LEFT_JUST, + HII2S_FORMAT_RIGHT_JUST, +}; + +#define HII2S_DIG_FILTER_MODULE_CFG 0x14 + +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_GAIN_SHIFT 28 +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_GAIN_MASK 3 +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN4_MUTE BIT(27) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN3_MUTE BIT(26) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN2_MUTE BIT(25) +#define HII2S_DIG_FILTER_MODULE_CFG__DACR_MIXER_IN1_MUTE BIT(24) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_GAIN_SHIFT 20 +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_GAIN_MASK 3 +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN4_MUTE BIT(19) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN3_MUTE BIT(18) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN2_MUTE BIT(17) +#define HII2S_DIG_FILTER_MODULE_CFG__DACL_MIXER_IN1_MUTE BIT(16) +#define HII2S_DIG_FILTER_MODULE_CFG__SW_DACR_SDM_DITHER BIT(9) +#define HII2S_DIG_FILTER_MODULE_CFG__SW_DACL_SDM_DITHER BIT(8) +#define HII2S_DIG_FILTER_MODULE_CFG__LM_CODEC_DAC2ADC_SHIFT 4 +#define HII2S_DIG_FILTER_MODULE_CFG__LM_CODEC_DAC2ADC_MASK 7 +#define HII2S_DIG_FILTER_MODULE_CFG__RM_CODEC_DAC2ADC_SHIFT 0 +#define HII2S_DIG_FILTER_MODULE_CFG__RM_CODEC_DAC2ADC_MASK 7 + +enum hi6210_gains { + HII2S_GAIN_100PC, + HII2S_GAIN_50PC, + HII2S_GAIN_25PC, +}; + +#define HII2S_MUX_TOP_MODULE_CFG 0x18 + +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_GAIN_SHIFT 14 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_GAIN_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN2_MUTE BIT(13) +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_MIXER_IN1_MUTE BIT(12) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_GAIN_SHIFT 10 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_GAIN_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN2_MUTE BIT(9) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_MIXER_IN1_MUTE BIT(8) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_RDY BIT(6) +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_MODE_SHIFT 4 +#define HII2S_MUX_TOP_MODULE_CFG__S2_OL_SRC_MODE_MASK 3 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_RDY BIT(3) +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_MODE_SHIFT 0 +#define HII2S_MUX_TOP_MODULE_CFG__VOICE_DLINK_SRC_MODE_MASK 7 + +enum hi6210_s2_src_mode { + HII2S_S2_SRC_MODE_3, + HII2S_S2_SRC_MODE_12, + HII2S_S2_SRC_MODE_6, + HII2S_S2_SRC_MODE_2, +}; + +enum hi6210_voice_dlink_src_mode { + HII2S_VOICE_DL_SRC_MODE_12 = 1, + HII2S_VOICE_DL_SRC_MODE_6, + HII2S_VOICE_DL_SRC_MODE_2, + HII2S_VOICE_DL_SRC_MODE_3, +}; + +#define HII2S_ADC_PGA_CFG 0x1c +#define HII2S_S1_INPUT_PGA_CFG 0x20 +#define HII2S_S2_INPUT_PGA_CFG 0x24 +#define HII2S_ST_DL_PGA_CFG 0x28 +#define HII2S_VOICE_SIDETONE_DLINK_PGA_CFG 0x2c +#define HII2S_APB_AFIFO_CFG_1 0x30 +#define HII2S_APB_AFIFO_CFG_2 0x34 +#define HII2S_ST_DL_FIFO_TH_CFG 0x38 + +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_SHIFT 24 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AEMPTY_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_SHIFT 16 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_R_AFULL_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_SHIFT 8 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AEMPTY_MASK 0x1f +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_SHIFT 0 +#define HII2S_ST_DL_FIFO_TH_CFG__ST_DL_L_AFULL_MASK 0x1f + +#define HII2S_STEREO_UPLINK_FIFO_TH_CFG 0x3c +#define HII2S_VOICE_UPLINK_FIFO_TH_CFG 0x40 +#define HII2S_CODEC_IRQ_MASK 0x44 +#define HII2S_CODEC_IRQ 0x48 +#define HII2S_DACL_AGC_CFG_1 0x4c +#define HII2S_DACL_AGC_CFG_2 0x50 +#define HII2S_DACR_AGC_CFG_1 0x54 +#define HII2S_DACR_AGC_CFG_2 0x58 +#define HII2S_DMIC_SIF_CFG 0x5c +#define HII2S_MISC_CFG 0x60 + +#define HII2S_MISC_CFG__THIRDMD_DLINK_TEST_SEL BIT(17) +#define HII2S_MISC_CFG__THIRDMD_DLINK_DIN_SEL BIT(16) +#define HII2S_MISC_CFG__S3_DOUT_RIGHT_SEL BIT(14) +#define HII2S_MISC_CFG__S3_DOUT_LEFT_SEL BIT(13) +#define HII2S_MISC_CFG__S3_DIN_TEST_SEL BIT(12) +#define HII2S_MISC_CFG__VOICE_DLINK_SRC_UP_DOUT_VLD_SEL BIT(8) +#define HII2S_MISC_CFG__VOICE_DLINK_TEST_SEL BIT(7) +#define HII2S_MISC_CFG__VOICE_DLINK_DIN_SEL BIT(6) +#define HII2S_MISC_CFG__ST_DL_TEST_SEL BIT(4) +#define HII2S_MISC_CFG__S2_DOUT_RIGHT_SEL BIT(3) +#define HII2S_MISC_CFG__S2_DOUT_TEST_SEL BIT(2) +#define HII2S_MISC_CFG__S1_DOUT_TEST_SEL BIT(1) +#define HII2S_MISC_CFG__S2_DOUT_LEFT_SEL BIT(0) + +#define HII2S_S2_SRC_CFG 0x64 +#define HII2S_MEM_CFG 0x68 +#define HII2S_THIRDMD_PCM_PGA_CFG 0x6c +#define HII2S_THIRD_MODEM_FIFO_TH 0x70 +#define HII2S_S3_ANTI_FREQ_JITTER_TX_INC_CNT 0x74 +#define HII2S_S3_ANTI_FREQ_JITTER_TX_DEC_CNT 0x78 +#define HII2S_S3_ANTI_FREQ_JITTER_RX_INC_CNT 0x7c +#define HII2S_S3_ANTI_FREQ_JITTER_RX_DEC_CNT 0x80 +#define HII2S_ANTI_FREQ_JITTER_EN 0x84 +#define HII2S_CLK_SEL 0x88 + +/* 0 = BT owns the i2s */ +#define HII2S_CLK_SEL__I2S_BT_FM_SEL BIT(0) +/* 0 = internal source, 1 = ext */ +#define HII2S_CLK_SEL__EXT_12_288MHZ_SEL BIT(1) + + +#define HII2S_THIRDMD_DLINK_CHANNEL 0xe8 +#define HII2S_THIRDMD_ULINK_CHANNEL 0xec +#define HII2S_VOICE_DLINK_CHANNEL 0xf0 + +/* shovel data in here for playback */ +#define HII2S_ST_DL_CHANNEL 0xf4 +#define HII2S_STEREO_UPLINK_CHANNEL 0xf8 +#define HII2S_VOICE_UPLINK_CHANNEL 0xfc + +#endif/* _HI6210_I2S_H */ diff --git a/sound/soc/img/Kconfig b/sound/soc/img/Kconfig new file mode 100644 index 000000000..857a9510e --- /dev/null +++ b/sound/soc/img/Kconfig @@ -0,0 +1,52 @@ +config SND_SOC_IMG + bool "Audio support for Imagination Technologies designs" + help + Audio support for Imagination Technologies audio hardware + +config SND_SOC_IMG_I2S_IN + tristate "Imagination I2S Input Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2S in driver for + Imagination Technologies I2S in device. + +config SND_SOC_IMG_I2S_OUT + tristate "Imagination I2S Output Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2S out driver for + Imagination Technologies I2S out device. + +config SND_SOC_IMG_PARALLEL_OUT + tristate "Imagination Parallel Output Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for parallel out driver for + Imagination Technologies parallel out device. + +config SND_SOC_IMG_SPDIF_IN + tristate "Imagination SPDIF Input Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for SPDIF input driver for + Imagination Technologies SPDIF input device. + +config SND_SOC_IMG_SPDIF_OUT + tristate "Imagination SPDIF Output Device Driver" + depends on SND_SOC_IMG + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for SPDIF out driver for + Imagination Technologies SPDIF out device. + + +config SND_SOC_IMG_PISTACHIO_INTERNAL_DAC + tristate "Support for Pistachio SoC Internal DAC Driver" + depends on SND_SOC_IMG + help + Say Y or M if you want to add support for Pistachio internal DAC + driver for Imagination Technologies Pistachio internal DAC device. diff --git a/sound/soc/img/Makefile b/sound/soc/img/Makefile new file mode 100644 index 000000000..3e7b0fd4f --- /dev/null +++ b/sound/soc/img/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_SND_SOC_IMG_I2S_IN) += img-i2s-in.o +obj-$(CONFIG_SND_SOC_IMG_I2S_OUT) += img-i2s-out.o +obj-$(CONFIG_SND_SOC_IMG_PARALLEL_OUT) += img-parallel-out.o +obj-$(CONFIG_SND_SOC_IMG_SPDIF_IN) += img-spdif-in.o +obj-$(CONFIG_SND_SOC_IMG_SPDIF_OUT) += img-spdif-out.o + +obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_INTERNAL_DAC) += pistachio-internal-dac.o diff --git a/sound/soc/img/img-i2s-in.c b/sound/soc/img/img-i2s-in.c new file mode 100644 index 000000000..7e48c740b --- /dev/null +++ b/sound/soc/img/img-i2s-in.c @@ -0,0 +1,625 @@ +/* + * IMG I2S input controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_I2S_IN_RX_FIFO 0x0 + +#define IMG_I2S_IN_CTL 0x4 +#define IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK 0xfffffffc +#define IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT 2 +#define IMG_I2S_IN_CTL_16PACK_MASK BIT(1) +#define IMG_I2S_IN_CTL_ME_MASK BIT(0) + +#define IMG_I2S_IN_CH_CTL 0x4 +#define IMG_I2S_IN_CH_CTL_CCDEL_MASK 0x38000 +#define IMG_I2S_IN_CH_CTL_CCDEL_SHIFT 15 +#define IMG_I2S_IN_CH_CTL_FEN_MASK BIT(14) +#define IMG_I2S_IN_CH_CTL_FMODE_MASK BIT(13) +#define IMG_I2S_IN_CH_CTL_16PACK_MASK BIT(12) +#define IMG_I2S_IN_CH_CTL_JUST_MASK BIT(10) +#define IMG_I2S_IN_CH_CTL_PACKH_MASK BIT(9) +#define IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK BIT(8) +#define IMG_I2S_IN_CH_CTL_BLKP_MASK BIT(7) +#define IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK BIT(6) +#define IMG_I2S_IN_CH_CTL_LRD_MASK BIT(3) +#define IMG_I2S_IN_CH_CTL_FW_MASK BIT(2) +#define IMG_I2S_IN_CH_CTL_SW_MASK BIT(1) +#define IMG_I2S_IN_CH_CTL_ME_MASK BIT(0) + +#define IMG_I2S_IN_CH_STRIDE 0x20 + +struct img_i2s_in { + void __iomem *base; + struct clk *clk_sys; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + unsigned int max_i2s_chan; + void __iomem *channel_base; + unsigned int active_channels; + struct snd_soc_dai_driver dai_driver; + u32 suspend_ctl; + u32 *suspend_ch_ctl; +}; + +static int img_i2s_in_runtime_suspend(struct device *dev) +{ + struct img_i2s_in *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(i2s->clk_sys); + + return 0; +} + +static int img_i2s_in_runtime_resume(struct device *dev) +{ + struct img_i2s_in *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->clk_sys); + if (ret) { + dev_err(dev, "Unable to enable sys clock\n"); + return ret; + } + + return 0; +} + +static inline void img_i2s_in_writel(struct img_i2s_in *i2s, u32 val, u32 reg) +{ + writel(val, i2s->base + reg); +} + +static inline u32 img_i2s_in_readl(struct img_i2s_in *i2s, u32 reg) +{ + return readl(i2s->base + reg); +} + +static inline void img_i2s_in_ch_writel(struct img_i2s_in *i2s, u32 chan, + u32 val, u32 reg) +{ + writel(val, i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg); +} + +static inline u32 img_i2s_in_ch_readl(struct img_i2s_in *i2s, u32 chan, + u32 reg) +{ + return readl(i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg); +} + +static inline void img_i2s_in_ch_disable(struct img_i2s_in *i2s, u32 chan) +{ + u32 reg; + + reg = img_i2s_in_ch_readl(i2s, chan, IMG_I2S_IN_CH_CTL); + reg &= ~IMG_I2S_IN_CH_CTL_ME_MASK; + img_i2s_in_ch_writel(i2s, chan, reg, IMG_I2S_IN_CH_CTL); +} + +static inline void img_i2s_in_ch_enable(struct img_i2s_in *i2s, u32 chan) +{ + u32 reg; + + reg = img_i2s_in_ch_readl(i2s, chan, IMG_I2S_IN_CH_CTL); + reg |= IMG_I2S_IN_CH_CTL_ME_MASK; + img_i2s_in_ch_writel(i2s, chan, reg, IMG_I2S_IN_CH_CTL); +} + +static inline void img_i2s_in_disable(struct img_i2s_in *i2s) +{ + u32 reg; + + reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + reg &= ~IMG_I2S_IN_CTL_ME_MASK; + img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL); +} + +static inline void img_i2s_in_enable(struct img_i2s_in *i2s) +{ + u32 reg; + + reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + reg |= IMG_I2S_IN_CTL_ME_MASK; + img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL); +} + +static inline void img_i2s_in_flush(struct img_i2s_in *i2s) +{ + int i; + u32 reg; + + for (i = 0; i < i2s->active_channels; i++) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + reg |= IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + reg &= ~IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } +} + +static int img_i2s_in_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + img_i2s_in_enable(i2s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + img_i2s_in_disable(i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int img_i2s_in_check_rate(struct img_i2s_in *i2s, + unsigned int sample_rate, unsigned int frame_size, + unsigned int *bclk_filter_enable, + unsigned int *bclk_filter_value) +{ + unsigned int bclk_freq, cur_freq; + + bclk_freq = sample_rate * frame_size; + + cur_freq = clk_get_rate(i2s->clk_sys); + + if (cur_freq >= bclk_freq * 8) { + *bclk_filter_enable = 1; + *bclk_filter_value = 0; + } else if (cur_freq >= bclk_freq * 7) { + *bclk_filter_enable = 1; + *bclk_filter_value = 1; + } else if (cur_freq >= bclk_freq * 6) { + *bclk_filter_enable = 0; + *bclk_filter_value = 0; + } else { + dev_err(i2s->dev, + "Sys clock rate %u insufficient for sample rate %u\n", + cur_freq, sample_rate); + return -EINVAL; + } + + return 0; +} + +static int img_i2s_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int rate, channels, i2s_channels, frame_size; + unsigned int bclk_filter_enable, bclk_filter_value; + int i, ret = 0; + u32 reg, control_mask, chan_control_mask; + u32 control_set = 0, chan_control_set = 0; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + i2s_channels = channels / 2; + + switch (format) { + case SNDRV_PCM_FORMAT_S32_LE: + frame_size = 64; + chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_PACKH_MASK; + break; + case SNDRV_PCM_FORMAT_S24_LE: + frame_size = 64; + chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK; + break; + case SNDRV_PCM_FORMAT_S16_LE: + frame_size = 32; + control_set |= IMG_I2S_IN_CTL_16PACK_MASK; + chan_control_set |= IMG_I2S_IN_CH_CTL_16PACK_MASK; + break; + default: + return -EINVAL; + } + + if ((channels < 2) || + (channels > (i2s->max_i2s_chan * 2)) || + (channels % 2)) + return -EINVAL; + + control_set |= ((i2s_channels - 1) << IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT); + + ret = img_i2s_in_check_rate(i2s, rate, frame_size, + &bclk_filter_enable, &bclk_filter_value); + if (ret < 0) + return ret; + + if (bclk_filter_enable) + chan_control_set |= IMG_I2S_IN_CH_CTL_FEN_MASK; + + if (bclk_filter_value) + chan_control_set |= IMG_I2S_IN_CH_CTL_FMODE_MASK; + + control_mask = IMG_I2S_IN_CTL_16PACK_MASK | + IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK; + + chan_control_mask = IMG_I2S_IN_CH_CTL_16PACK_MASK | + IMG_I2S_IN_CH_CTL_FEN_MASK | + IMG_I2S_IN_CH_CTL_FMODE_MASK | + IMG_I2S_IN_CH_CTL_SW_MASK | + IMG_I2S_IN_CH_CTL_FW_MASK | + IMG_I2S_IN_CH_CTL_PACKH_MASK; + + reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + reg = (reg & ~control_mask) | control_set; + img_i2s_in_writel(i2s, reg, IMG_I2S_IN_CTL); + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_in_ch_disable(i2s, i); + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + reg = (reg & ~chan_control_mask) | chan_control_set; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } + + i2s->active_channels = i2s_channels; + + img_i2s_in_flush(i2s); + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_in_ch_enable(i2s, i); + + return 0; +} + +static int img_i2s_in_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + int i, ret; + u32 chan_control_mask, lrd_set = 0, blkp_set = 0, chan_control_set = 0; + u32 reg; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_NF: + lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK; + blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK; + break; + case SND_SOC_DAIFMT_IB_IF: + blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + chan_control_set |= IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + chan_control_mask = IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK; + + ret = pm_runtime_get_sync(i2s->dev); + if (ret < 0) { + pm_runtime_put_noidle(i2s->dev); + return ret; + } + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_in_ch_disable(i2s, i); + + /* + * BLKP and LRD must be set during separate register writes + */ + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + reg = (reg & ~chan_control_mask) | chan_control_set; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + reg = (reg & ~IMG_I2S_IN_CH_CTL_BLKP_MASK) | blkp_set; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + reg = (reg & ~IMG_I2S_IN_CH_CTL_LRD_MASK) | lrd_set; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_in_ch_enable(i2s, i); + + pm_runtime_put(i2s->dev); + + return 0; +} + +static const struct snd_soc_dai_ops img_i2s_in_dai_ops = { + .trigger = img_i2s_in_trigger, + .hw_params = img_i2s_in_hw_params, + .set_fmt = img_i2s_in_set_fmt +}; + +static int img_i2s_in_dai_probe(struct snd_soc_dai *dai) +{ + struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, NULL, &i2s->dma_data); + + return 0; +} + +static const struct snd_soc_component_driver img_i2s_in_component = { + .name = "img-i2s-in" +}; + +static int img_i2s_in_dma_prepare_slave_config(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params, struct dma_slave_config *sc) +{ + unsigned int i2s_channels = params_channels(params) / 2; + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + int ret; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, st); + + ret = snd_hwparams_to_dma_slave_config(st, params, sc); + if (ret) + return ret; + + sc->src_addr = dma_data->addr; + sc->src_addr_width = dma_data->addr_width; + sc->src_maxburst = 4 * i2s_channels; + + return 0; +} + +static const struct snd_dmaengine_pcm_config img_i2s_in_dma_config = { + .prepare_slave_config = img_i2s_in_dma_prepare_slave_config +}; + +static int img_i2s_in_probe(struct platform_device *pdev) +{ + struct img_i2s_in *i2s; + struct resource *res; + void __iomem *base; + int ret, i; + struct reset_control *rst; + unsigned int max_i2s_chan_pow_2; + struct device *dev = &pdev->dev; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + platform_set_drvdata(pdev, i2s); + + i2s->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s->base = base; + + if (of_property_read_u32(pdev->dev.of_node, "img,i2s-channels", + &i2s->max_i2s_chan)) { + dev_err(dev, "No img,i2s-channels property\n"); + return -EINVAL; + } + + max_i2s_chan_pow_2 = 1 << get_count_order(i2s->max_i2s_chan); + + i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20); + + i2s->clk_sys = devm_clk_get(dev, "sys"); + if (IS_ERR(i2s->clk_sys)) { + if (PTR_ERR(i2s->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(i2s->clk_sys); + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_i2s_in_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) + goto err_suspend; + + i2s->active_channels = 1; + i2s->dma_data.addr = res->start + IMG_I2S_IN_RX_FIFO; + i2s->dma_data.addr_width = 4; + + i2s->dai_driver.probe = img_i2s_in_dai_probe; + i2s->dai_driver.capture.channels_min = 2; + i2s->dai_driver.capture.channels_max = i2s->max_i2s_chan * 2; + i2s->dai_driver.capture.rates = SNDRV_PCM_RATE_8000_192000; + i2s->dai_driver.capture.formats = SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE; + i2s->dai_driver.ops = &img_i2s_in_dai_ops; + + rst = devm_reset_control_get_exclusive(dev, "rst"); + if (IS_ERR(rst)) { + if (PTR_ERR(rst) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + pm_runtime_put(&pdev->dev); + goto err_suspend; + } + + dev_dbg(dev, "No top level reset found\n"); + + img_i2s_in_disable(i2s); + + for (i = 0; i < i2s->max_i2s_chan; i++) + img_i2s_in_ch_disable(i2s, i); + } else { + reset_control_assert(rst); + reset_control_deassert(rst); + } + + img_i2s_in_writel(i2s, 0, IMG_I2S_IN_CTL); + + for (i = 0; i < i2s->max_i2s_chan; i++) + img_i2s_in_ch_writel(i2s, i, + (4 << IMG_I2S_IN_CH_CTL_CCDEL_SHIFT) | + IMG_I2S_IN_CH_CTL_JUST_MASK | + IMG_I2S_IN_CH_CTL_FW_MASK, IMG_I2S_IN_CH_CTL); + + pm_runtime_put(&pdev->dev); + + i2s->suspend_ch_ctl = devm_kcalloc(dev, + i2s->max_i2s_chan, sizeof(*i2s->suspend_ch_ctl), GFP_KERNEL); + if (!i2s->suspend_ch_ctl) { + ret = -ENOMEM; + goto err_suspend; + } + + ret = devm_snd_soc_register_component(dev, &img_i2s_in_component, + &i2s->dai_driver, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(dev, &img_i2s_in_dma_config, 0); + if (ret) + goto err_suspend; + + return 0; + +err_suspend: + if (!pm_runtime_enabled(&pdev->dev)) + img_i2s_in_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int img_i2s_in_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_i2s_in_runtime_suspend(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int img_i2s_in_suspend(struct device *dev) +{ + struct img_i2s_in *i2s = dev_get_drvdata(dev); + int i, ret; + u32 reg; + + if (pm_runtime_status_suspended(dev)) { + ret = img_i2s_in_runtime_resume(dev); + if (ret) + return ret; + } + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = img_i2s_in_ch_readl(i2s, i, IMG_I2S_IN_CH_CTL); + i2s->suspend_ch_ctl[i] = reg; + } + + i2s->suspend_ctl = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); + + img_i2s_in_runtime_suspend(dev); + + return 0; +} + +static int img_i2s_in_resume(struct device *dev) +{ + struct img_i2s_in *i2s = dev_get_drvdata(dev); + int i, ret; + u32 reg; + + ret = img_i2s_in_runtime_resume(dev); + if (ret) + return ret; + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = i2s->suspend_ch_ctl[i]; + img_i2s_in_ch_writel(i2s, i, reg, IMG_I2S_IN_CH_CTL); + } + + img_i2s_in_writel(i2s, i2s->suspend_ctl, IMG_I2S_IN_CTL); + + if (pm_runtime_status_suspended(dev)) + img_i2s_in_runtime_suspend(dev); + + return 0; +} +#endif + +static const struct of_device_id img_i2s_in_of_match[] = { + { .compatible = "img,i2s-in" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_i2s_in_of_match); + +static const struct dev_pm_ops img_i2s_in_pm_ops = { + SET_RUNTIME_PM_OPS(img_i2s_in_runtime_suspend, + img_i2s_in_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(img_i2s_in_suspend, img_i2s_in_resume) +}; + +static struct platform_driver img_i2s_in_driver = { + .driver = { + .name = "img-i2s-in", + .of_match_table = img_i2s_in_of_match, + .pm = &img_i2s_in_pm_ops + }, + .probe = img_i2s_in_probe, + .remove = img_i2s_in_dev_remove +}; +module_platform_driver(img_i2s_in_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG I2S Input Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-i2s-out.c b/sound/soc/img/img-i2s-out.c new file mode 100644 index 000000000..798ab5795 --- /dev/null +++ b/sound/soc/img/img-i2s-out.c @@ -0,0 +1,631 @@ +/* + * IMG I2S output controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_I2S_OUT_TX_FIFO 0x0 + +#define IMG_I2S_OUT_CTL 0x4 +#define IMG_I2S_OUT_CTL_DATA_EN_MASK BIT(24) +#define IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK 0xffe000 +#define IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT 13 +#define IMG_I2S_OUT_CTL_FRM_SIZE_MASK BIT(8) +#define IMG_I2S_OUT_CTL_MASTER_MASK BIT(6) +#define IMG_I2S_OUT_CTL_CLK_MASK BIT(5) +#define IMG_I2S_OUT_CTL_CLK_EN_MASK BIT(4) +#define IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK BIT(3) +#define IMG_I2S_OUT_CTL_BCLK_POL_MASK BIT(2) +#define IMG_I2S_OUT_CTL_ME_MASK BIT(0) + +#define IMG_I2S_OUT_CH_CTL 0x4 +#define IMG_I2S_OUT_CHAN_CTL_CH_MASK BIT(11) +#define IMG_I2S_OUT_CHAN_CTL_LT_MASK BIT(10) +#define IMG_I2S_OUT_CHAN_CTL_FMT_MASK 0xf0 +#define IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT 4 +#define IMG_I2S_OUT_CHAN_CTL_JUST_MASK BIT(3) +#define IMG_I2S_OUT_CHAN_CTL_CLKT_MASK BIT(1) +#define IMG_I2S_OUT_CHAN_CTL_ME_MASK BIT(0) + +#define IMG_I2S_OUT_CH_STRIDE 0x20 + +struct img_i2s_out { + void __iomem *base; + struct clk *clk_sys; + struct clk *clk_ref; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + unsigned int max_i2s_chan; + void __iomem *channel_base; + bool force_clk_active; + unsigned int active_channels; + struct reset_control *rst; + struct snd_soc_dai_driver dai_driver; + u32 suspend_ctl; + u32 *suspend_ch_ctl; +}; + +static int img_i2s_out_runtime_suspend(struct device *dev) +{ + struct img_i2s_out *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(i2s->clk_ref); + clk_disable_unprepare(i2s->clk_sys); + + return 0; +} + +static int img_i2s_out_runtime_resume(struct device *dev) +{ + struct img_i2s_out *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->clk_sys); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(i2s->clk_ref); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + clk_disable_unprepare(i2s->clk_sys); + return ret; + } + + return 0; +} + +static inline void img_i2s_out_writel(struct img_i2s_out *i2s, u32 val, + u32 reg) +{ + writel(val, i2s->base + reg); +} + +static inline u32 img_i2s_out_readl(struct img_i2s_out *i2s, u32 reg) +{ + return readl(i2s->base + reg); +} + +static inline void img_i2s_out_ch_writel(struct img_i2s_out *i2s, + u32 chan, u32 val, u32 reg) +{ + writel(val, i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg); +} + +static inline u32 img_i2s_out_ch_readl(struct img_i2s_out *i2s, u32 chan, + u32 reg) +{ + return readl(i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg); +} + +static inline void img_i2s_out_ch_disable(struct img_i2s_out *i2s, u32 chan) +{ + u32 reg; + + reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL); + reg &= ~IMG_I2S_OUT_CHAN_CTL_ME_MASK; + img_i2s_out_ch_writel(i2s, chan, reg, IMG_I2S_OUT_CH_CTL); +} + +static inline void img_i2s_out_ch_enable(struct img_i2s_out *i2s, u32 chan) +{ + u32 reg; + + reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL); + reg |= IMG_I2S_OUT_CHAN_CTL_ME_MASK; + img_i2s_out_ch_writel(i2s, chan, reg, IMG_I2S_OUT_CH_CTL); +} + +static inline void img_i2s_out_disable(struct img_i2s_out *i2s) +{ + u32 reg; + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg &= ~IMG_I2S_OUT_CTL_ME_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); +} + +static inline void img_i2s_out_enable(struct img_i2s_out *i2s) +{ + u32 reg; + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg |= IMG_I2S_OUT_CTL_ME_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); +} + +static void img_i2s_out_reset(struct img_i2s_out *i2s) +{ + int i; + u32 core_ctl, chan_ctl; + + core_ctl = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL) & + ~IMG_I2S_OUT_CTL_ME_MASK & + ~IMG_I2S_OUT_CTL_DATA_EN_MASK; + + if (!i2s->force_clk_active) + core_ctl &= ~IMG_I2S_OUT_CTL_CLK_EN_MASK; + + chan_ctl = img_i2s_out_ch_readl(i2s, 0, IMG_I2S_OUT_CH_CTL) & + ~IMG_I2S_OUT_CHAN_CTL_ME_MASK; + + reset_control_assert(i2s->rst); + reset_control_deassert(i2s->rst); + + for (i = 0; i < i2s->max_i2s_chan; i++) + img_i2s_out_ch_writel(i2s, i, chan_ctl, IMG_I2S_OUT_CH_CTL); + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_out_ch_enable(i2s, i); + + img_i2s_out_writel(i2s, core_ctl, IMG_I2S_OUT_CTL); + img_i2s_out_enable(i2s); +} + +static int img_i2s_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + u32 reg; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + if (!i2s->force_clk_active) + reg |= IMG_I2S_OUT_CTL_CLK_EN_MASK; + reg |= IMG_I2S_OUT_CTL_DATA_EN_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + img_i2s_out_reset(i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int img_i2s_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int channels, i2s_channels; + long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate; + int i; + u32 reg, control_mask, control_set = 0; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + i2s_channels = channels / 2; + + if (format != SNDRV_PCM_FORMAT_S32_LE) + return -EINVAL; + + if ((channels < 2) || + (channels > (i2s->max_i2s_chan * 2)) || + (channels % 2)) + return -EINVAL; + + pre_div_a = clk_round_rate(i2s->clk_ref, rate * 256); + if (pre_div_a < 0) + return pre_div_a; + pre_div_b = clk_round_rate(i2s->clk_ref, rate * 384); + if (pre_div_b < 0) + return pre_div_b; + + diff_a = abs((pre_div_a / 256) - rate); + diff_b = abs((pre_div_b / 384) - rate); + + /* If diffs are equal, use lower clock rate */ + if (diff_a > diff_b) + clk_set_rate(i2s->clk_ref, pre_div_b); + else + clk_set_rate(i2s->clk_ref, pre_div_a); + + /* + * Another driver (eg alsa machine driver) may have rejected the above + * change. Get the current rate and set the register bit according to + * the new minimum diff + */ + clk_rate = clk_get_rate(i2s->clk_ref); + + diff_a = abs((clk_rate / 256) - rate); + diff_b = abs((clk_rate / 384) - rate); + + if (diff_a > diff_b) + control_set |= IMG_I2S_OUT_CTL_CLK_MASK; + + control_set |= ((i2s_channels - 1) << + IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT) & + IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK; + + control_mask = IMG_I2S_OUT_CTL_CLK_MASK | + IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK; + + img_i2s_out_disable(i2s); + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg = (reg & ~control_mask) | control_set; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + + for (i = 0; i < i2s_channels; i++) + img_i2s_out_ch_enable(i2s, i); + + for (; i < i2s->max_i2s_chan; i++) + img_i2s_out_ch_disable(i2s, i); + + img_i2s_out_enable(i2s); + + i2s->active_channels = i2s_channels; + + return 0; +} + +static int img_i2s_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + int i, ret; + bool force_clk_active; + u32 chan_control_mask, control_mask, chan_control_set = 0; + u32 reg, control_set = 0; + + force_clk_active = ((fmt & SND_SOC_DAIFMT_CLOCK_MASK) == + SND_SOC_DAIFMT_CONT); + + if (force_clk_active) + control_set |= IMG_I2S_OUT_CTL_CLK_EN_MASK; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + control_set |= IMG_I2S_OUT_CTL_MASTER_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK; + break; + case SND_SOC_DAIFMT_NB_IF: + control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK; + control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + chan_control_set |= IMG_I2S_OUT_CHAN_CTL_CLKT_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + control_mask = IMG_I2S_OUT_CTL_CLK_EN_MASK | + IMG_I2S_OUT_CTL_MASTER_MASK | + IMG_I2S_OUT_CTL_BCLK_POL_MASK | + IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; + + chan_control_mask = IMG_I2S_OUT_CHAN_CTL_CLKT_MASK; + + ret = pm_runtime_get_sync(i2s->dev); + if (ret < 0) { + pm_runtime_put_noidle(i2s->dev); + return ret; + } + + img_i2s_out_disable(i2s); + + reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + reg = (reg & ~control_mask) | control_set; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_out_ch_disable(i2s, i); + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = img_i2s_out_ch_readl(i2s, i, IMG_I2S_OUT_CH_CTL); + reg = (reg & ~chan_control_mask) | chan_control_set; + img_i2s_out_ch_writel(i2s, i, reg, IMG_I2S_OUT_CH_CTL); + } + + for (i = 0; i < i2s->active_channels; i++) + img_i2s_out_ch_enable(i2s, i); + + img_i2s_out_enable(i2s); + pm_runtime_put(i2s->dev); + + i2s->force_clk_active = force_clk_active; + + return 0; +} + +static const struct snd_soc_dai_ops img_i2s_out_dai_ops = { + .trigger = img_i2s_out_trigger, + .hw_params = img_i2s_out_hw_params, + .set_fmt = img_i2s_out_set_fmt +}; + +static int img_i2s_out_dai_probe(struct snd_soc_dai *dai) +{ + struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &i2s->dma_data, NULL); + + return 0; +} + +static const struct snd_soc_component_driver img_i2s_out_component = { + .name = "img-i2s-out" +}; + +static int img_i2s_out_dma_prepare_slave_config(struct snd_pcm_substream *st, + struct snd_pcm_hw_params *params, struct dma_slave_config *sc) +{ + unsigned int i2s_channels = params_channels(params) / 2; + struct snd_soc_pcm_runtime *rtd = st->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + int ret; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, st); + + ret = snd_hwparams_to_dma_slave_config(st, params, sc); + if (ret) + return ret; + + sc->dst_addr = dma_data->addr; + sc->dst_addr_width = dma_data->addr_width; + sc->dst_maxburst = 4 * i2s_channels; + + return 0; +} + +static const struct snd_dmaengine_pcm_config img_i2s_out_dma_config = { + .prepare_slave_config = img_i2s_out_dma_prepare_slave_config +}; + +static int img_i2s_out_probe(struct platform_device *pdev) +{ + struct img_i2s_out *i2s; + struct resource *res; + void __iomem *base; + int i, ret; + unsigned int max_i2s_chan_pow_2; + u32 reg; + struct device *dev = &pdev->dev; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + platform_set_drvdata(pdev, i2s); + + i2s->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s->base = base; + + if (of_property_read_u32(pdev->dev.of_node, "img,i2s-channels", + &i2s->max_i2s_chan)) { + dev_err(&pdev->dev, "No img,i2s-channels property\n"); + return -EINVAL; + } + + max_i2s_chan_pow_2 = 1 << get_count_order(i2s->max_i2s_chan); + + i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20); + + i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, "rst"); + if (IS_ERR(i2s->rst)) { + if (PTR_ERR(i2s->rst) != -EPROBE_DEFER) + dev_err(&pdev->dev, "No top level reset found\n"); + return PTR_ERR(i2s->rst); + } + + i2s->clk_sys = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(i2s->clk_sys)) { + if (PTR_ERR(i2s->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(i2s->clk_sys); + } + + i2s->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(i2s->clk_ref)) { + if (PTR_ERR(i2s->clk_ref) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'ref'\n"); + return PTR_ERR(i2s->clk_ref); + } + + i2s->suspend_ch_ctl = devm_kcalloc(dev, + i2s->max_i2s_chan, sizeof(*i2s->suspend_ch_ctl), GFP_KERNEL); + if (!i2s->suspend_ch_ctl) + return -ENOMEM; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_i2s_out_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + pm_runtime_put_noidle(&pdev->dev); + goto err_suspend; + } + + reg = IMG_I2S_OUT_CTL_FRM_SIZE_MASK; + img_i2s_out_writel(i2s, reg, IMG_I2S_OUT_CTL); + + reg = IMG_I2S_OUT_CHAN_CTL_JUST_MASK | + IMG_I2S_OUT_CHAN_CTL_LT_MASK | + IMG_I2S_OUT_CHAN_CTL_CH_MASK | + (8 << IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT); + + for (i = 0; i < i2s->max_i2s_chan; i++) + img_i2s_out_ch_writel(i2s, i, reg, IMG_I2S_OUT_CH_CTL); + + img_i2s_out_reset(i2s); + pm_runtime_put(&pdev->dev); + + i2s->active_channels = 1; + i2s->dma_data.addr = res->start + IMG_I2S_OUT_TX_FIFO; + i2s->dma_data.addr_width = 4; + i2s->dma_data.maxburst = 4; + + i2s->dai_driver.probe = img_i2s_out_dai_probe; + i2s->dai_driver.playback.channels_min = 2; + i2s->dai_driver.playback.channels_max = i2s->max_i2s_chan * 2; + i2s->dai_driver.playback.rates = SNDRV_PCM_RATE_8000_192000; + i2s->dai_driver.playback.formats = SNDRV_PCM_FMTBIT_S32_LE; + i2s->dai_driver.ops = &img_i2s_out_dai_ops; + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_i2s_out_component, &i2s->dai_driver, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &img_i2s_out_dma_config, 0); + if (ret) + goto err_suspend; + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + img_i2s_out_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int img_i2s_out_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_i2s_out_runtime_suspend(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int img_i2s_out_suspend(struct device *dev) +{ + struct img_i2s_out *i2s = dev_get_drvdata(dev); + int i, ret; + u32 reg; + + if (pm_runtime_status_suspended(dev)) { + ret = img_i2s_out_runtime_resume(dev); + if (ret) + return ret; + } + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = img_i2s_out_ch_readl(i2s, i, IMG_I2S_OUT_CH_CTL); + i2s->suspend_ch_ctl[i] = reg; + } + + i2s->suspend_ctl = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); + + img_i2s_out_runtime_suspend(dev); + + return 0; +} + +static int img_i2s_out_resume(struct device *dev) +{ + struct img_i2s_out *i2s = dev_get_drvdata(dev); + int i, ret; + u32 reg; + + ret = img_i2s_out_runtime_resume(dev); + if (ret) + return ret; + + for (i = 0; i < i2s->max_i2s_chan; i++) { + reg = i2s->suspend_ch_ctl[i]; + img_i2s_out_ch_writel(i2s, i, reg, IMG_I2S_OUT_CH_CTL); + } + + img_i2s_out_writel(i2s, i2s->suspend_ctl, IMG_I2S_OUT_CTL); + + if (pm_runtime_status_suspended(dev)) + img_i2s_out_runtime_suspend(dev); + + return 0; +} +#endif + +static const struct of_device_id img_i2s_out_of_match[] = { + { .compatible = "img,i2s-out" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_i2s_out_of_match); + +static const struct dev_pm_ops img_i2s_out_pm_ops = { + SET_RUNTIME_PM_OPS(img_i2s_out_runtime_suspend, + img_i2s_out_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(img_i2s_out_suspend, img_i2s_out_resume) +}; + +static struct platform_driver img_i2s_out_driver = { + .driver = { + .name = "img-i2s-out", + .of_match_table = img_i2s_out_of_match, + .pm = &img_i2s_out_pm_ops + }, + .probe = img_i2s_out_probe, + .remove = img_i2s_out_dev_remove +}; +module_platform_driver(img_i2s_out_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG I2S Output Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-parallel-out.c b/sound/soc/img/img-parallel-out.c new file mode 100644 index 000000000..f56752662 --- /dev/null +++ b/sound/soc/img/img-parallel-out.c @@ -0,0 +1,333 @@ +/* + * IMG parallel output controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_PRL_OUT_TX_FIFO 0 + +#define IMG_PRL_OUT_CTL 0x4 +#define IMG_PRL_OUT_CTL_CH_MASK BIT(4) +#define IMG_PRL_OUT_CTL_PACKH_MASK BIT(3) +#define IMG_PRL_OUT_CTL_EDGE_MASK BIT(2) +#define IMG_PRL_OUT_CTL_ME_MASK BIT(1) +#define IMG_PRL_OUT_CTL_SRST_MASK BIT(0) + +struct img_prl_out { + void __iomem *base; + struct clk *clk_sys; + struct clk *clk_ref; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + struct reset_control *rst; +}; + +static int img_prl_out_suspend(struct device *dev) +{ + struct img_prl_out *prl = dev_get_drvdata(dev); + + clk_disable_unprepare(prl->clk_ref); + + return 0; +} + +static int img_prl_out_resume(struct device *dev) +{ + struct img_prl_out *prl = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(prl->clk_ref); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + return 0; +} + +static inline void img_prl_out_writel(struct img_prl_out *prl, + u32 val, u32 reg) +{ + writel(val, prl->base + reg); +} + +static inline u32 img_prl_out_readl(struct img_prl_out *prl, u32 reg) +{ + return readl(prl->base + reg); +} + +static void img_prl_out_reset(struct img_prl_out *prl) +{ + u32 ctl; + + ctl = img_prl_out_readl(prl, IMG_PRL_OUT_CTL) & + ~IMG_PRL_OUT_CTL_ME_MASK; + + reset_control_assert(prl->rst); + reset_control_deassert(prl->rst); + + img_prl_out_writel(prl, ctl, IMG_PRL_OUT_CTL); +} + +static int img_prl_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + u32 reg; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); + reg |= IMG_PRL_OUT_CTL_ME_MASK; + img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + img_prl_out_reset(prl); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int img_prl_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + unsigned int rate, channels; + u32 reg, control_set = 0; + + rate = params_rate(params); + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S32_LE: + control_set |= IMG_PRL_OUT_CTL_PACKH_MASK; + break; + case SNDRV_PCM_FORMAT_S24_LE: + break; + default: + return -EINVAL; + } + + if (channels != 2) + return -EINVAL; + + clk_set_rate(prl->clk_ref, rate * 256); + + reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); + reg = (reg & ~IMG_PRL_OUT_CTL_PACKH_MASK) | control_set; + img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL); + + return 0; +} + +static int img_prl_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + u32 reg, control_set = 0; + int ret; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + control_set |= IMG_PRL_OUT_CTL_EDGE_MASK; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_get_sync(prl->dev); + if (ret < 0) { + pm_runtime_put_noidle(prl->dev); + return ret; + } + + reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); + reg = (reg & ~IMG_PRL_OUT_CTL_EDGE_MASK) | control_set; + img_prl_out_writel(prl, reg, IMG_PRL_OUT_CTL); + pm_runtime_put(prl->dev); + + return 0; +} + +static const struct snd_soc_dai_ops img_prl_out_dai_ops = { + .trigger = img_prl_out_trigger, + .hw_params = img_prl_out_hw_params, + .set_fmt = img_prl_out_set_fmt +}; + +static int img_prl_out_dai_probe(struct snd_soc_dai *dai) +{ + struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &prl->dma_data, NULL); + + return 0; +} + +static struct snd_soc_dai_driver img_prl_out_dai = { + .probe = img_prl_out_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE + }, + .ops = &img_prl_out_dai_ops +}; + +static const struct snd_soc_component_driver img_prl_out_component = { + .name = "img-prl-out" +}; + +static int img_prl_out_probe(struct platform_device *pdev) +{ + struct img_prl_out *prl; + struct resource *res; + void __iomem *base; + int ret; + struct device *dev = &pdev->dev; + + prl = devm_kzalloc(&pdev->dev, sizeof(*prl), GFP_KERNEL); + if (!prl) + return -ENOMEM; + + platform_set_drvdata(pdev, prl); + + prl->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + prl->base = base; + + prl->rst = devm_reset_control_get_exclusive(&pdev->dev, "rst"); + if (IS_ERR(prl->rst)) { + if (PTR_ERR(prl->rst) != -EPROBE_DEFER) + dev_err(&pdev->dev, "No top level reset found\n"); + return PTR_ERR(prl->rst); + } + + prl->clk_sys = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(prl->clk_sys)) { + if (PTR_ERR(prl->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(prl->clk_sys); + } + + prl->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(prl->clk_ref)) { + if (PTR_ERR(prl->clk_ref) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'ref'\n"); + return PTR_ERR(prl->clk_ref); + } + + ret = clk_prepare_enable(prl->clk_sys); + if (ret) + return ret; + + img_prl_out_writel(prl, IMG_PRL_OUT_CTL_EDGE_MASK, IMG_PRL_OUT_CTL); + img_prl_out_reset(prl); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_prl_out_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + prl->dma_data.addr = res->start + IMG_PRL_OUT_TX_FIFO; + prl->dma_data.addr_width = 4; + prl->dma_data.maxburst = 4; + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_prl_out_component, + &img_prl_out_dai, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_suspend; + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + img_prl_out_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(prl->clk_sys); + + return ret; +} + +static int img_prl_out_dev_remove(struct platform_device *pdev) +{ + struct img_prl_out *prl = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_prl_out_suspend(&pdev->dev); + + clk_disable_unprepare(prl->clk_sys); + + return 0; +} + +static const struct of_device_id img_prl_out_of_match[] = { + { .compatible = "img,parallel-out" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_prl_out_of_match); + +static const struct dev_pm_ops img_prl_out_pm_ops = { + SET_RUNTIME_PM_OPS(img_prl_out_suspend, + img_prl_out_resume, NULL) +}; + +static struct platform_driver img_prl_out_driver = { + .driver = { + .name = "img-parallel-out", + .of_match_table = img_prl_out_of_match, + .pm = &img_prl_out_pm_ops + }, + .probe = img_prl_out_probe, + .remove = img_prl_out_dev_remove +}; +module_platform_driver(img_prl_out_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG Parallel Output Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-spdif-in.c b/sound/soc/img/img-spdif-in.c new file mode 100644 index 000000000..cedd40c8d --- /dev/null +++ b/sound/soc/img/img-spdif-in.c @@ -0,0 +1,894 @@ +/* + * IMG SPDIF input controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_SPDIF_IN_RX_FIFO_OFFSET 0 + +#define IMG_SPDIF_IN_CTL 0x4 +#define IMG_SPDIF_IN_CTL_LOCKLO_MASK 0xff +#define IMG_SPDIF_IN_CTL_LOCKLO_SHIFT 0 +#define IMG_SPDIF_IN_CTL_LOCKHI_MASK 0xff00 +#define IMG_SPDIF_IN_CTL_LOCKHI_SHIFT 8 +#define IMG_SPDIF_IN_CTL_TRK_MASK 0xff0000 +#define IMG_SPDIF_IN_CTL_TRK_SHIFT 16 +#define IMG_SPDIF_IN_CTL_SRD_MASK 0x70000000 +#define IMG_SPDIF_IN_CTL_SRD_SHIFT 28 +#define IMG_SPDIF_IN_CTL_SRT_MASK BIT(31) + +#define IMG_SPDIF_IN_STATUS 0x8 +#define IMG_SPDIF_IN_STATUS_SAM_MASK 0x7000 +#define IMG_SPDIF_IN_STATUS_SAM_SHIFT 12 +#define IMG_SPDIF_IN_STATUS_LOCK_MASK BIT(15) +#define IMG_SPDIF_IN_STATUS_LOCK_SHIFT 15 + +#define IMG_SPDIF_IN_CLKGEN 0x1c +#define IMG_SPDIF_IN_CLKGEN_NOM_MASK 0x3ff +#define IMG_SPDIF_IN_CLKGEN_NOM_SHIFT 0 +#define IMG_SPDIF_IN_CLKGEN_HLD_MASK 0x3ff0000 +#define IMG_SPDIF_IN_CLKGEN_HLD_SHIFT 16 + +#define IMG_SPDIF_IN_CSL 0x20 + +#define IMG_SPDIF_IN_CSH 0x24 +#define IMG_SPDIF_IN_CSH_MASK 0xff +#define IMG_SPDIF_IN_CSH_SHIFT 0 + +#define IMG_SPDIF_IN_SOFT_RESET 0x28 +#define IMG_SPDIF_IN_SOFT_RESET_MASK BIT(0) + +#define IMG_SPDIF_IN_ACLKGEN_START 0x2c +#define IMG_SPDIF_IN_ACLKGEN_NOM_MASK 0x3ff +#define IMG_SPDIF_IN_ACLKGEN_NOM_SHIFT 0 +#define IMG_SPDIF_IN_ACLKGEN_HLD_MASK 0xffc00 +#define IMG_SPDIF_IN_ACLKGEN_HLD_SHIFT 10 +#define IMG_SPDIF_IN_ACLKGEN_TRK_MASK 0xff00000 +#define IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT 20 + +#define IMG_SPDIF_IN_NUM_ACLKGEN 4 + +struct img_spdif_in { + spinlock_t lock; + void __iomem *base; + struct clk *clk_sys; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + unsigned int trk; + bool multi_freq; + int lock_acquire; + int lock_release; + unsigned int single_freq; + unsigned int multi_freqs[IMG_SPDIF_IN_NUM_ACLKGEN]; + bool active; + u32 suspend_clkgen; + u32 suspend_ctl; + + /* Write-only registers */ + unsigned int aclkgen_regs[IMG_SPDIF_IN_NUM_ACLKGEN]; +}; + +static int img_spdif_in_runtime_suspend(struct device *dev) +{ + struct img_spdif_in *spdif = dev_get_drvdata(dev); + + clk_disable_unprepare(spdif->clk_sys); + + return 0; +} + +static int img_spdif_in_runtime_resume(struct device *dev) +{ + struct img_spdif_in *spdif = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spdif->clk_sys); + if (ret) { + dev_err(dev, "Unable to enable sys clock\n"); + return ret; + } + + return 0; +} + +static inline void img_spdif_in_writel(struct img_spdif_in *spdif, + u32 val, u32 reg) +{ + writel(val, spdif->base + reg); +} + +static inline u32 img_spdif_in_readl(struct img_spdif_in *spdif, u32 reg) +{ + return readl(spdif->base + reg); +} + +static inline void img_spdif_in_aclkgen_writel(struct img_spdif_in *spdif, + u32 index) +{ + img_spdif_in_writel(spdif, spdif->aclkgen_regs[index], + IMG_SPDIF_IN_ACLKGEN_START + (index * 0x4)); +} + +static int img_spdif_in_check_max_rate(struct img_spdif_in *spdif, + unsigned int sample_rate, unsigned long *actual_freq) +{ + unsigned long min_freq, freq_t; + + /* Clock rate must be at least 24x the bit rate */ + min_freq = sample_rate * 2 * 32 * 24; + + freq_t = clk_get_rate(spdif->clk_sys); + + if (freq_t < min_freq) + return -EINVAL; + + *actual_freq = freq_t; + + return 0; +} + +static int img_spdif_in_do_clkgen_calc(unsigned int rate, unsigned int *pnom, + unsigned int *phld, unsigned long clk_rate) +{ + unsigned int ori, nom, hld; + + /* + * Calculate oversampling ratio, nominal phase increment and hold + * increment for the given rate / frequency + */ + + if (!rate) + return -EINVAL; + + ori = clk_rate / (rate * 64); + + if (!ori) + return -EINVAL; + + nom = (4096 / ori) + 1; + do + hld = 4096 - (--nom * (ori - 1)); + while (hld < 120); + + *pnom = nom; + *phld = hld; + + return 0; +} + +static int img_spdif_in_do_clkgen_single(struct img_spdif_in *spdif, + unsigned int rate) +{ + unsigned int nom, hld; + unsigned long flags, clk_rate; + int ret = 0; + u32 reg; + + ret = img_spdif_in_check_max_rate(spdif, rate, &clk_rate); + if (ret) + return ret; + + ret = img_spdif_in_do_clkgen_calc(rate, &nom, &hld, clk_rate); + if (ret) + return ret; + + reg = (nom << IMG_SPDIF_IN_CLKGEN_NOM_SHIFT) & + IMG_SPDIF_IN_CLKGEN_NOM_MASK; + reg |= (hld << IMG_SPDIF_IN_CLKGEN_HLD_SHIFT) & + IMG_SPDIF_IN_CLKGEN_HLD_MASK; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CLKGEN); + + spdif->single_freq = rate; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_do_clkgen_multi(struct img_spdif_in *spdif, + unsigned int multi_freqs[]) +{ + unsigned int nom, hld, rate, max_rate = 0; + unsigned long flags, clk_rate; + int i, ret = 0; + u32 reg, trk_reg, temp_regs[IMG_SPDIF_IN_NUM_ACLKGEN]; + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) + if (multi_freqs[i] > max_rate) + max_rate = multi_freqs[i]; + + ret = img_spdif_in_check_max_rate(spdif, max_rate, &clk_rate); + if (ret) + return ret; + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) { + rate = multi_freqs[i]; + + ret = img_spdif_in_do_clkgen_calc(rate, &nom, &hld, clk_rate); + if (ret) + return ret; + + reg = (nom << IMG_SPDIF_IN_ACLKGEN_NOM_SHIFT) & + IMG_SPDIF_IN_ACLKGEN_NOM_MASK; + reg |= (hld << IMG_SPDIF_IN_ACLKGEN_HLD_SHIFT) & + IMG_SPDIF_IN_ACLKGEN_HLD_MASK; + temp_regs[i] = reg; + } + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + trk_reg = spdif->trk << IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT; + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) { + spdif->aclkgen_regs[i] = temp_regs[i] | trk_reg; + img_spdif_in_aclkgen_writel(spdif, i); + } + + spdif->multi_freq = true; + spdif->multi_freqs[0] = multi_freqs[0]; + spdif->multi_freqs[1] = multi_freqs[1]; + spdif->multi_freqs[2] = multi_freqs[2]; + spdif->multi_freqs[3] = multi_freqs[3]; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_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 img_spdif_in_get_status_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; + ucontrol->value.iec958.status[4] = 0xff; + + return 0; +} + +static int img_spdif_in_get_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CSL); + ucontrol->value.iec958.status[0] = reg & 0xff; + ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff; + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CSH); + ucontrol->value.iec958.status[4] = (reg & IMG_SPDIF_IN_CSH_MASK) + >> IMG_SPDIF_IN_CSH_SHIFT; + + return 0; +} + +static int img_spdif_in_info_multi_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = IMG_SPDIF_IN_NUM_ACLKGEN; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LONG_MAX; + + return 0; +} + +static int img_spdif_in_get_multi_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + + spin_lock_irqsave(&spdif->lock, flags); + if (spdif->multi_freq) { + ucontrol->value.integer.value[0] = spdif->multi_freqs[0]; + ucontrol->value.integer.value[1] = spdif->multi_freqs[1]; + ucontrol->value.integer.value[2] = spdif->multi_freqs[2]; + ucontrol->value.integer.value[3] = spdif->multi_freqs[3]; + } else { + ucontrol->value.integer.value[0] = 0; + ucontrol->value.integer.value[1] = 0; + ucontrol->value.integer.value[2] = 0; + ucontrol->value.integer.value[3] = 0; + } + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_set_multi_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned int multi_freqs[IMG_SPDIF_IN_NUM_ACLKGEN]; + bool multi_freq; + unsigned long flags; + + if ((ucontrol->value.integer.value[0] == 0) && + (ucontrol->value.integer.value[1] == 0) && + (ucontrol->value.integer.value[2] == 0) && + (ucontrol->value.integer.value[3] == 0)) { + multi_freq = false; + } else { + multi_freqs[0] = ucontrol->value.integer.value[0]; + multi_freqs[1] = ucontrol->value.integer.value[1]; + multi_freqs[2] = ucontrol->value.integer.value[2]; + multi_freqs[3] = ucontrol->value.integer.value[3]; + multi_freq = true; + } + + if (multi_freq) + return img_spdif_in_do_clkgen_multi(spdif, multi_freqs); + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->multi_freq = false; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_info_lock_freq(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 = LONG_MAX; + + return 0; +} + +static int img_spdif_in_get_lock_freq(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uc) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + int i; + unsigned long flags; + + spin_lock_irqsave(&spdif->lock, flags); + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_STATUS); + if (reg & IMG_SPDIF_IN_STATUS_LOCK_MASK) { + if (spdif->multi_freq) { + i = ((reg & IMG_SPDIF_IN_STATUS_SAM_MASK) >> + IMG_SPDIF_IN_STATUS_SAM_SHIFT) - 1; + uc->value.integer.value[0] = spdif->multi_freqs[i]; + } else { + uc->value.integer.value[0] = spdif->single_freq; + } + } else { + uc->value.integer.value[0] = 0; + } + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_info_trk(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 img_spdif_in_get_trk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = spdif->trk; + + return 0; +} + +static int img_spdif_in_set_trk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + int i; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->trk = ucontrol->value.integer.value[0]; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_TRK_MASK; + reg |= spdif->trk << IMG_SPDIF_IN_CTL_TRK_SHIFT; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) { + spdif->aclkgen_regs[i] = (spdif->aclkgen_regs[i] & + ~IMG_SPDIF_IN_ACLKGEN_TRK_MASK) | + (spdif->trk << IMG_SPDIF_IN_ACLKGEN_TRK_SHIFT); + + img_spdif_in_aclkgen_writel(spdif, i); + } + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_info_lock(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -128; + uinfo->value.integer.max = 127; + + return 0; +} + +static int img_spdif_in_get_lock_acquire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = spdif->lock_acquire; + + return 0; +} + +static int img_spdif_in_set_lock_acquire(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->lock_acquire = ucontrol->value.integer.value[0]; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_LOCKHI_MASK; + reg |= (spdif->lock_acquire << IMG_SPDIF_IN_CTL_LOCKHI_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKHI_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_in_get_lock_release(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = spdif->lock_release; + + return 0; +} + +static int img_spdif_in_set_lock_release(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + if (spdif->active) { + spin_unlock_irqrestore(&spdif->lock, flags); + return -EBUSY; + } + + spdif->lock_release = ucontrol->value.integer.value[0]; + + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_LOCKLO_MASK; + reg |= (spdif->lock_release << IMG_SPDIF_IN_CTL_LOCKLO_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKLO_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static struct snd_kcontrol_new img_spdif_in_controls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), + .info = img_spdif_in_iec958_info, + .get = img_spdif_in_get_status_mask + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .info = img_spdif_in_iec958_info, + .get = img_spdif_in_get_status + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Multi Frequency Acquire", + .info = img_spdif_in_info_multi_freq, + .get = img_spdif_in_get_multi_freq, + .put = img_spdif_in_set_multi_freq + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock Frequency", + .info = img_spdif_in_info_lock_freq, + .get = img_spdif_in_get_lock_freq + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock TRK", + .info = img_spdif_in_info_trk, + .get = img_spdif_in_get_trk, + .put = img_spdif_in_set_trk + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock Acquire Threshold", + .info = img_spdif_in_info_lock, + .get = img_spdif_in_get_lock_acquire, + .put = img_spdif_in_set_lock_acquire + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "SPDIF In Lock Release Threshold", + .info = img_spdif_in_info_lock, + .get = img_spdif_in_get_lock_release, + .put = img_spdif_in_set_lock_release + } +}; + +static int img_spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + unsigned long flags; + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai); + int ret = 0; + u32 reg; + + spin_lock_irqsave(&spdif->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + if (spdif->multi_freq) + reg &= ~IMG_SPDIF_IN_CTL_SRD_MASK; + else + reg |= (1UL << IMG_SPDIF_IN_CTL_SRD_SHIFT); + reg |= IMG_SPDIF_IN_CTL_SRT_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + spdif->active = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + reg = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + reg &= ~IMG_SPDIF_IN_CTL_SRT_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + spdif->active = false; + break; + default: + ret = -EINVAL; + } + + spin_unlock_irqrestore(&spdif->lock, flags); + + return ret; +} + +static int img_spdif_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int rate, channels; + snd_pcm_format_t format; + + rate = params_rate(params); + channels = params_channels(params); + format = params_format(params); + + if (format != SNDRV_PCM_FORMAT_S32_LE) + return -EINVAL; + + if (channels != 2) + return -EINVAL; + + return img_spdif_in_do_clkgen_single(spdif, rate); +} + +static const struct snd_soc_dai_ops img_spdif_in_dai_ops = { + .trigger = img_spdif_in_trigger, + .hw_params = img_spdif_in_hw_params +}; + +static int img_spdif_in_dai_probe(struct snd_soc_dai *dai) +{ + struct img_spdif_in *spdif = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, NULL, &spdif->dma_data); + + snd_soc_add_dai_controls(dai, img_spdif_in_controls, + ARRAY_SIZE(img_spdif_in_controls)); + + return 0; +} + +static struct snd_soc_dai_driver img_spdif_in_dai = { + .probe = img_spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &img_spdif_in_dai_ops +}; + +static const struct snd_soc_component_driver img_spdif_in_component = { + .name = "img-spdif-in" +}; + +static int img_spdif_in_probe(struct platform_device *pdev) +{ + struct img_spdif_in *spdif; + struct resource *res; + void __iomem *base; + int ret; + struct reset_control *rst; + u32 reg; + struct device *dev = &pdev->dev; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + spdif->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->base = base; + + spdif->clk_sys = devm_clk_get(dev, "sys"); + if (IS_ERR(spdif->clk_sys)) { + if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(spdif->clk_sys); + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_spdif_in_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) + goto err_suspend; + + rst = devm_reset_control_get_exclusive(&pdev->dev, "rst"); + if (IS_ERR(rst)) { + if (PTR_ERR(rst) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err_pm_put; + } + dev_dbg(dev, "No top level reset found\n"); + img_spdif_in_writel(spdif, IMG_SPDIF_IN_SOFT_RESET_MASK, + IMG_SPDIF_IN_SOFT_RESET); + img_spdif_in_writel(spdif, 0, IMG_SPDIF_IN_SOFT_RESET); + } else { + reset_control_assert(rst); + reset_control_deassert(rst); + } + + spin_lock_init(&spdif->lock); + + spdif->dma_data.addr = res->start + IMG_SPDIF_IN_RX_FIFO_OFFSET; + spdif->dma_data.addr_width = 4; + spdif->dma_data.maxburst = 4; + spdif->trk = 0x80; + spdif->lock_acquire = 4; + spdif->lock_release = -128; + + reg = (spdif->lock_acquire << IMG_SPDIF_IN_CTL_LOCKHI_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKHI_MASK; + reg |= (spdif->lock_release << IMG_SPDIF_IN_CTL_LOCKLO_SHIFT) & + IMG_SPDIF_IN_CTL_LOCKLO_MASK; + reg |= (spdif->trk << IMG_SPDIF_IN_CTL_TRK_SHIFT) & + IMG_SPDIF_IN_CTL_TRK_MASK; + img_spdif_in_writel(spdif, reg, IMG_SPDIF_IN_CTL); + + pm_runtime_put(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_spdif_in_component, &img_spdif_in_dai, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_suspend; + + return 0; + +err_pm_put: + pm_runtime_put(&pdev->dev); +err_suspend: + if (!pm_runtime_enabled(&pdev->dev)) + img_spdif_in_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int img_spdif_in_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_spdif_in_runtime_suspend(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int img_spdif_in_suspend(struct device *dev) +{ + struct img_spdif_in *spdif = dev_get_drvdata(dev); + int ret; + + if (pm_runtime_status_suspended(dev)) { + ret = img_spdif_in_runtime_resume(dev); + if (ret) + return ret; + } + + spdif->suspend_clkgen = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CLKGEN); + spdif->suspend_ctl = img_spdif_in_readl(spdif, IMG_SPDIF_IN_CTL); + + img_spdif_in_runtime_suspend(dev); + + return 0; +} + +static int img_spdif_in_resume(struct device *dev) +{ + struct img_spdif_in *spdif = dev_get_drvdata(dev); + int i, ret; + + ret = img_spdif_in_runtime_resume(dev); + if (ret) + return ret; + + for (i = 0; i < IMG_SPDIF_IN_NUM_ACLKGEN; i++) + img_spdif_in_aclkgen_writel(spdif, i); + + img_spdif_in_writel(spdif, spdif->suspend_clkgen, IMG_SPDIF_IN_CLKGEN); + img_spdif_in_writel(spdif, spdif->suspend_ctl, IMG_SPDIF_IN_CTL); + + if (pm_runtime_status_suspended(dev)) + img_spdif_in_runtime_suspend(dev); + + return 0; +} +#endif + +static const struct of_device_id img_spdif_in_of_match[] = { + { .compatible = "img,spdif-in" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_spdif_in_of_match); + +static const struct dev_pm_ops img_spdif_in_pm_ops = { + SET_RUNTIME_PM_OPS(img_spdif_in_runtime_suspend, + img_spdif_in_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(img_spdif_in_suspend, img_spdif_in_resume) +}; + +static struct platform_driver img_spdif_in_driver = { + .driver = { + .name = "img-spdif-in", + .of_match_table = img_spdif_in_of_match, + .pm = &img_spdif_in_pm_ops + }, + .probe = img_spdif_in_probe, + .remove = img_spdif_in_dev_remove +}; +module_platform_driver(img_spdif_in_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG SPDIF Input driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/img-spdif-out.c b/sound/soc/img/img-spdif-out.c new file mode 100644 index 000000000..934ed3df2 --- /dev/null +++ b/sound/soc/img/img-spdif-out.c @@ -0,0 +1,488 @@ +/* + * IMG SPDIF output controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define IMG_SPDIF_OUT_TX_FIFO 0x0 + +#define IMG_SPDIF_OUT_CTL 0x4 +#define IMG_SPDIF_OUT_CTL_FS_MASK BIT(4) +#define IMG_SPDIF_OUT_CTL_CLK_MASK BIT(2) +#define IMG_SPDIF_OUT_CTL_SRT_MASK BIT(0) + +#define IMG_SPDIF_OUT_CSL 0x14 + +#define IMG_SPDIF_OUT_CSH_UV 0x18 +#define IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT 0 +#define IMG_SPDIF_OUT_CSH_UV_CSH_MASK 0xff + +struct img_spdif_out { + spinlock_t lock; + void __iomem *base; + struct clk *clk_sys; + struct clk *clk_ref; + struct snd_dmaengine_dai_dma_data dma_data; + struct device *dev; + struct reset_control *rst; + u32 suspend_ctl; + u32 suspend_csl; + u32 suspend_csh; +}; + +static int img_spdif_out_runtime_suspend(struct device *dev) +{ + struct img_spdif_out *spdif = dev_get_drvdata(dev); + + clk_disable_unprepare(spdif->clk_ref); + clk_disable_unprepare(spdif->clk_sys); + + return 0; +} + +static int img_spdif_out_runtime_resume(struct device *dev) +{ + struct img_spdif_out *spdif = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spdif->clk_sys); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(spdif->clk_ref); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + clk_disable_unprepare(spdif->clk_sys); + return ret; + } + + return 0; +} + +static inline void img_spdif_out_writel(struct img_spdif_out *spdif, u32 val, + u32 reg) +{ + writel(val, spdif->base + reg); +} + +static inline u32 img_spdif_out_readl(struct img_spdif_out *spdif, u32 reg) +{ + return readl(spdif->base + reg); +} + +static void img_spdif_out_reset(struct img_spdif_out *spdif) +{ + u32 ctl, status_low, status_high; + + ctl = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL) & + ~IMG_SPDIF_OUT_CTL_SRT_MASK; + status_low = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL); + status_high = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); + + reset_control_assert(spdif->rst); + reset_control_deassert(spdif->rst); + + img_spdif_out_writel(spdif, ctl, IMG_SPDIF_OUT_CTL); + img_spdif_out_writel(spdif, status_low, IMG_SPDIF_OUT_CSL); + img_spdif_out_writel(spdif, status_high, IMG_SPDIF_OUT_CSH_UV); +} + +static int img_spdif_out_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 img_spdif_out_get_status_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; + ucontrol->value.iec958.status[4] = 0xff; + + return 0; +} + +static int img_spdif_out_get_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + unsigned long flags; + + spin_lock_irqsave(&spdif->lock, flags); + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL); + ucontrol->value.iec958.status[0] = reg & 0xff; + ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff; + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); + ucontrol->value.iec958.status[4] = + (reg & IMG_SPDIF_OUT_CSH_UV_CSH_MASK) >> + IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT; + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static int img_spdif_out_set_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + unsigned long flags; + + reg = ((u32)ucontrol->value.iec958.status[3] << 24); + reg |= ((u32)ucontrol->value.iec958.status[2] << 16); + reg |= ((u32)ucontrol->value.iec958.status[1] << 8); + reg |= (u32)ucontrol->value.iec958.status[0]; + + spin_lock_irqsave(&spdif->lock, flags); + + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSL); + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); + reg &= ~IMG_SPDIF_OUT_CSH_UV_CSH_MASK; + reg |= (u32)ucontrol->value.iec958.status[4] << + IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT; + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSH_UV); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +} + +static struct snd_kcontrol_new img_spdif_out_controls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = img_spdif_out_info, + .get = img_spdif_out_get_status_mask + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = img_spdif_out_info, + .get = img_spdif_out_get_status, + .put = img_spdif_out_set_status + } +}; + +static int img_spdif_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); + u32 reg; + unsigned long flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL); + reg |= IMG_SPDIF_OUT_CTL_SRT_MASK; + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&spdif->lock, flags); + img_spdif_out_reset(spdif); + spin_unlock_irqrestore(&spdif->lock, flags); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int img_spdif_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int channels; + long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate; + u32 reg; + snd_pcm_format_t format; + + rate = params_rate(params); + format = params_format(params); + channels = params_channels(params); + + dev_dbg(spdif->dev, "hw_params rate %ld channels %u format %u\n", + rate, channels, format); + + if (format != SNDRV_PCM_FORMAT_S32_LE) + return -EINVAL; + + if (channels != 2) + return -EINVAL; + + pre_div_a = clk_round_rate(spdif->clk_ref, rate * 256); + if (pre_div_a < 0) + return pre_div_a; + pre_div_b = clk_round_rate(spdif->clk_ref, rate * 384); + if (pre_div_b < 0) + return pre_div_b; + + diff_a = abs((pre_div_a / 256) - rate); + diff_b = abs((pre_div_b / 384) - rate); + + /* If diffs are equal, use lower clock rate */ + if (diff_a > diff_b) + clk_set_rate(spdif->clk_ref, pre_div_b); + else + clk_set_rate(spdif->clk_ref, pre_div_a); + + /* + * Another driver (eg machine driver) may have rejected the above + * change. Get the current rate and set the register bit according to + * the new min diff + */ + clk_rate = clk_get_rate(spdif->clk_ref); + + diff_a = abs((clk_rate / 256) - rate); + diff_b = abs((clk_rate / 384) - rate); + + reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL); + if (diff_a <= diff_b) + reg &= ~IMG_SPDIF_OUT_CTL_CLK_MASK; + else + reg |= IMG_SPDIF_OUT_CTL_CLK_MASK; + img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL); + + return 0; +} + +static const struct snd_soc_dai_ops img_spdif_out_dai_ops = { + .trigger = img_spdif_out_trigger, + .hw_params = img_spdif_out_hw_params +}; + +static int img_spdif_out_dai_probe(struct snd_soc_dai *dai) +{ + struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL); + + snd_soc_add_dai_controls(dai, img_spdif_out_controls, + ARRAY_SIZE(img_spdif_out_controls)); + + return 0; +} + +static struct snd_soc_dai_driver img_spdif_out_dai = { + .probe = img_spdif_out_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE + }, + .ops = &img_spdif_out_dai_ops +}; + +static const struct snd_soc_component_driver img_spdif_out_component = { + .name = "img-spdif-out" +}; + +static int img_spdif_out_probe(struct platform_device *pdev) +{ + struct img_spdif_out *spdif; + struct resource *res; + void __iomem *base; + int ret; + struct device *dev = &pdev->dev; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + spdif->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->base = base; + + spdif->rst = devm_reset_control_get_exclusive(&pdev->dev, "rst"); + if (IS_ERR(spdif->rst)) { + if (PTR_ERR(spdif->rst) != -EPROBE_DEFER) + dev_err(&pdev->dev, "No top level reset found\n"); + return PTR_ERR(spdif->rst); + } + + spdif->clk_sys = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(spdif->clk_sys)) { + if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'sys'\n"); + return PTR_ERR(spdif->clk_sys); + } + + spdif->clk_ref = devm_clk_get(&pdev->dev, "ref"); + if (IS_ERR(spdif->clk_ref)) { + if (PTR_ERR(spdif->clk_ref) != -EPROBE_DEFER) + dev_err(dev, "Failed to acquire clock 'ref'\n"); + return PTR_ERR(spdif->clk_ref); + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = img_spdif_out_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) + goto err_suspend; + + img_spdif_out_writel(spdif, IMG_SPDIF_OUT_CTL_FS_MASK, + IMG_SPDIF_OUT_CTL); + + img_spdif_out_reset(spdif); + pm_runtime_put(&pdev->dev); + + spin_lock_init(&spdif->lock); + + spdif->dma_data.addr = res->start + IMG_SPDIF_OUT_TX_FIFO; + spdif->dma_data.addr_width = 4; + spdif->dma_data.maxburst = 4; + + ret = devm_snd_soc_register_component(&pdev->dev, + &img_spdif_out_component, + &img_spdif_out_dai, 1); + if (ret) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_suspend; + + dev_dbg(&pdev->dev, "Probe successful\n"); + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + img_spdif_out_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int img_spdif_out_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + img_spdif_out_runtime_suspend(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int img_spdif_out_suspend(struct device *dev) +{ + struct img_spdif_out *spdif = dev_get_drvdata(dev); + int ret; + + if (pm_runtime_status_suspended(dev)) { + ret = img_spdif_out_runtime_resume(dev); + if (ret) + return ret; + } + + spdif->suspend_ctl = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL); + spdif->suspend_csl = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL); + spdif->suspend_csh = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); + + img_spdif_out_runtime_suspend(dev); + + return 0; +} + +static int img_spdif_out_resume(struct device *dev) +{ + struct img_spdif_out *spdif = dev_get_drvdata(dev); + int ret; + + ret = img_spdif_out_runtime_resume(dev); + if (ret) + return ret; + + img_spdif_out_writel(spdif, spdif->suspend_ctl, IMG_SPDIF_OUT_CTL); + img_spdif_out_writel(spdif, spdif->suspend_csl, IMG_SPDIF_OUT_CSL); + img_spdif_out_writel(spdif, spdif->suspend_csh, IMG_SPDIF_OUT_CSH_UV); + + if (pm_runtime_status_suspended(dev)) + img_spdif_out_runtime_suspend(dev); + + return 0; +} +#endif +static const struct of_device_id img_spdif_out_of_match[] = { + { .compatible = "img,spdif-out" }, + {} +}; +MODULE_DEVICE_TABLE(of, img_spdif_out_of_match); + +static const struct dev_pm_ops img_spdif_out_pm_ops = { + SET_RUNTIME_PM_OPS(img_spdif_out_runtime_suspend, + img_spdif_out_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(img_spdif_out_suspend, img_spdif_out_resume) +}; + +static struct platform_driver img_spdif_out_driver = { + .driver = { + .name = "img-spdif-out", + .of_match_table = img_spdif_out_of_match, + .pm = &img_spdif_out_pm_ops + }, + .probe = img_spdif_out_probe, + .remove = img_spdif_out_dev_remove +}; +module_platform_driver(img_spdif_out_driver); + +MODULE_AUTHOR("Damien Horsley "); +MODULE_DESCRIPTION("IMG SPDIF Output driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/img/pistachio-internal-dac.c b/sound/soc/img/pistachio-internal-dac.c new file mode 100644 index 000000000..915b894f9 --- /dev/null +++ b/sound/soc/img/pistachio-internal-dac.c @@ -0,0 +1,289 @@ +/* + * Pistachio internal dac driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define PISTACHIO_INTERNAL_DAC_CTRL 0x40 +#define PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK 0x2 +#define PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK 0x1 + +#define PISTACHIO_INTERNAL_DAC_SRST 0x44 +#define PISTACHIO_INTERNAL_DAC_SRST_MASK 0x1 + +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL 0x48 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT 0 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK 0xFFF +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK 0x1000 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT 13 +#define PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK 0x1FE000 + +#define PISTACHIO_INTERNAL_DAC_PWR 0x1 +#define PISTACHIO_INTERNAL_DAC_PWR_MASK 0x1 + +#define PISTACHIO_INTERNAL_DAC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +/* codec private data */ +struct pistachio_internal_dac { + struct regmap *regmap; + struct regulator *supply; + bool mute; +}; + +static const struct snd_kcontrol_new pistachio_internal_dac_snd_controls[] = { + SOC_SINGLE("Playback Switch", PISTACHIO_INTERNAL_DAC_CTRL, 2, 1, 1) +}; + +static const struct snd_soc_dapm_widget pistachio_internal_dac_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), +}; + +static const struct snd_soc_dapm_route pistachio_internal_dac_routes[] = { + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, +}; + +static void pistachio_internal_dac_reg_writel(struct regmap *top_regs, + u32 val, u32 reg) +{ + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_MASK, + reg << PISTACHIO_INTERNAL_DAC_GTI_CTRL_ADDR_SHIFT); + + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_MASK, + val << PISTACHIO_INTERNAL_DAC_GTI_CTRL_WDATA_SHIFT); + + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK); + + regmap_update_bits(top_regs, PISTACHIO_INTERNAL_DAC_GTI_CTRL, + PISTACHIO_INTERNAL_DAC_GTI_CTRL_WE_MASK, 0); +} + +static void pistachio_internal_dac_pwr_off(struct pistachio_internal_dac *dac) +{ + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL, + PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK, + PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK); + + pistachio_internal_dac_reg_writel(dac->regmap, 0, + PISTACHIO_INTERNAL_DAC_PWR); +} + +static void pistachio_internal_dac_pwr_on(struct pistachio_internal_dac *dac) +{ + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST, + PISTACHIO_INTERNAL_DAC_SRST_MASK, + PISTACHIO_INTERNAL_DAC_SRST_MASK); + + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_SRST, + PISTACHIO_INTERNAL_DAC_SRST_MASK, 0); + + pistachio_internal_dac_reg_writel(dac->regmap, + PISTACHIO_INTERNAL_DAC_PWR_MASK, + PISTACHIO_INTERNAL_DAC_PWR); + + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL, + PISTACHIO_INTERNAL_DAC_CTRL_PWRDN_MASK, 0); +} + +static struct snd_soc_dai_driver pistachio_internal_dac_dais[] = { + { + .name = "pistachio_internal_dac", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = PISTACHIO_INTERNAL_DAC_FORMATS, + } + }, +}; + +static int pistachio_internal_dac_codec_probe(struct snd_soc_component *component) +{ + struct pistachio_internal_dac *dac = snd_soc_component_get_drvdata(component); + + snd_soc_component_init_regmap(component, dac->regmap); + + return 0; +} + +static const struct snd_soc_component_driver pistachio_internal_dac_driver = { + .probe = pistachio_internal_dac_codec_probe, + .controls = pistachio_internal_dac_snd_controls, + .num_controls = ARRAY_SIZE(pistachio_internal_dac_snd_controls), + .dapm_widgets = pistachio_internal_dac_widgets, + .num_dapm_widgets = ARRAY_SIZE(pistachio_internal_dac_widgets), + .dapm_routes = pistachio_internal_dac_routes, + .num_dapm_routes = ARRAY_SIZE(pistachio_internal_dac_routes), + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int pistachio_internal_dac_probe(struct platform_device *pdev) +{ + struct pistachio_internal_dac *dac; + int ret, voltage; + struct device *dev = &pdev->dev; + u32 reg; + + dac = devm_kzalloc(dev, sizeof(*dac), GFP_KERNEL); + + if (!dac) + return -ENOMEM; + + platform_set_drvdata(pdev, dac); + + dac->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "img,cr-top"); + if (IS_ERR(dac->regmap)) + return PTR_ERR(dac->regmap); + + dac->supply = devm_regulator_get(dev, "VDD"); + if (IS_ERR(dac->supply)) { + ret = PTR_ERR(dac->supply); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to acquire supply 'VDD-supply': %d\n", ret); + return ret; + } + + ret = regulator_enable(dac->supply); + if (ret) { + dev_err(dev, "failed to enable supply: %d\n", ret); + return ret; + } + + voltage = regulator_get_voltage(dac->supply); + + switch (voltage) { + case 1800000: + reg = 0; + break; + case 3300000: + reg = PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK; + break; + default: + dev_err(dev, "invalid voltage: %d\n", voltage); + ret = -EINVAL; + goto err_regulator; + } + + regmap_update_bits(dac->regmap, PISTACHIO_INTERNAL_DAC_CTRL, + PISTACHIO_INTERNAL_DAC_CTRL_PWR_SEL_MASK, reg); + + pistachio_internal_dac_pwr_off(dac); + pistachio_internal_dac_pwr_on(dac); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = devm_snd_soc_register_component(dev, + &pistachio_internal_dac_driver, + pistachio_internal_dac_dais, + ARRAY_SIZE(pistachio_internal_dac_dais)); + if (ret) { + dev_err(dev, "failed to register component: %d\n", ret); + goto err_pwr; + } + + return 0; + +err_pwr: + pm_runtime_disable(&pdev->dev); + pistachio_internal_dac_pwr_off(dac); +err_regulator: + regulator_disable(dac->supply); + + return ret; +} + +static int pistachio_internal_dac_remove(struct platform_device *pdev) +{ + struct pistachio_internal_dac *dac = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + pistachio_internal_dac_pwr_off(dac); + regulator_disable(dac->supply); + + return 0; +} + +#ifdef CONFIG_PM +static int pistachio_internal_dac_rt_resume(struct device *dev) +{ + struct pistachio_internal_dac *dac = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(dac->supply); + if (ret) { + dev_err(dev, "failed to enable supply: %d\n", ret); + return ret; + } + + pistachio_internal_dac_pwr_on(dac); + + return 0; +} + +static int pistachio_internal_dac_rt_suspend(struct device *dev) +{ + struct pistachio_internal_dac *dac = dev_get_drvdata(dev); + + pistachio_internal_dac_pwr_off(dac); + + regulator_disable(dac->supply); + + return 0; +} +#endif + +static const struct dev_pm_ops pistachio_internal_dac_pm_ops = { + SET_RUNTIME_PM_OPS(pistachio_internal_dac_rt_suspend, + pistachio_internal_dac_rt_resume, NULL) +}; + +static const struct of_device_id pistachio_internal_dac_of_match[] = { + { .compatible = "img,pistachio-internal-dac" }, + {} +}; +MODULE_DEVICE_TABLE(of, pistachio_internal_dac_of_match); + +static struct platform_driver pistachio_internal_dac_plat_driver = { + .driver = { + .name = "img-pistachio-internal-dac", + .of_match_table = pistachio_internal_dac_of_match, + .pm = &pistachio_internal_dac_pm_ops + }, + .probe = pistachio_internal_dac_probe, + .remove = pistachio_internal_dac_remove +}; +module_platform_driver(pistachio_internal_dac_plat_driver); + +MODULE_DESCRIPTION("Pistachio Internal DAC driver"); +MODULE_AUTHOR("Damien Horsley "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig new file mode 100644 index 000000000..0caa1f4eb --- /dev/null +++ b/sound/soc/intel/Kconfig @@ -0,0 +1,129 @@ +config SND_SOC_INTEL_SST_TOPLEVEL + bool "Intel ASoC SST drivers" + default y + depends on X86 || COMPILE_TEST + select SND_SOC_INTEL_MACH + help + Intel ASoC SST Platform Drivers. If you have a Intel machine that + has an audio controller with a DSP and I2S or DMIC port, then + enable this option by saying Y + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about Intel SST drivers. + +if SND_SOC_INTEL_SST_TOPLEVEL + +config SND_SST_IPC + tristate + # This option controls the IPC core for HiFi2 platforms + +config SND_SST_IPC_PCI + tristate + select SND_SST_IPC + # This option controls the PCI-based IPC for HiFi2 platforms + # (Medfield, Merrifield). + +config SND_SST_IPC_ACPI + tristate + select SND_SST_IPC + # This option controls the ACPI-based IPC for HiFi2 platforms + # (Baytrail, Cherrytrail) + +config SND_SOC_INTEL_SST_ACPI + tristate + # This option controls ACPI-based probing on + # Haswell/Broadwell/Baytrail legacy and will be set + # when these platforms are enabled + +config SND_SOC_INTEL_SST + tristate + +config SND_SOC_INTEL_SST_FIRMWARE + tristate + select DW_DMAC_CORE + # This option controls firmware download on + # Haswell/Broadwell/Baytrail legacy and will be set + # when these platforms are enabled + +config SND_SOC_INTEL_HASWELL + tristate "Haswell/Broadwell Platforms" + depends on SND_DMA_SGBUF + depends on DMADEVICES && ACPI + select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_ACPI + select SND_SOC_INTEL_SST_FIRMWARE + select SND_SOC_ACPI_INTEL_MATCH + help + If you have a Intel Haswell or Broadwell platform connected to + an I2S codec, then enable this option by saying Y or m. This is + typically used for Chromebooks. This is a recommended option. + +config SND_SOC_INTEL_BAYTRAIL + tristate "Baytrail (legacy) Platforms" + depends on DMADEVICES && ACPI && SND_SST_ATOM_HIFI2_PLATFORM=n + select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_ACPI + select SND_SOC_INTEL_SST_FIRMWARE + select SND_SOC_ACPI_INTEL_MATCH + help + If you have a Intel Baytrail platform connected to an I2S codec, + then enable this option by saying Y or m. This was typically used + for Baytrail Chromebooks but this option is now deprecated and is + not recommended, use SND_SST_ATOM_HIFI2_PLATFORM instead. + +config SND_SST_ATOM_HIFI2_PLATFORM + tristate + select SND_SOC_COMPRESS + +config SND_SST_ATOM_HIFI2_PLATFORM_PCI + tristate "PCI HiFi2 (Merrifield) Platforms" + depends on X86 && PCI + select SND_SST_IPC_PCI + select SND_SST_ATOM_HIFI2_PLATFORM + help + If you have a Intel Merrifield/Edison platform, then + enable this option by saying Y or m. Distros will typically not + enable this option: while Merrifield/Edison can run a mainline + kernel with limited functionality it will require a firmware file + which is not in the standard firmware tree + +config SND_SST_ATOM_HIFI2_PLATFORM_ACPI + tristate "ACPI HiFi2 (Baytrail, Cherrytrail) Platforms" + default ACPI + depends on X86 && ACPI + select SND_SST_IPC_ACPI + select SND_SST_ATOM_HIFI2_PLATFORM + select SND_SOC_ACPI_INTEL_MATCH + select IOSF_MBI + help + If you have a Intel Baytrail or Cherrytrail platform with an I2S + codec, then enable this option by saying Y or m. This is a + recommended option + +config SND_SOC_INTEL_SKYLAKE_SSP_CLK + tristate + +config SND_SOC_INTEL_SKYLAKE + tristate "SKL/BXT/KBL/GLK/CNL... Platforms" + depends on PCI && ACPI + select SND_HDA_EXT_CORE + select SND_HDA_DSP_LOADER + select SND_SOC_TOPOLOGY + select SND_SOC_INTEL_SST + select SND_SOC_ACPI_INTEL_MATCH + help + If you have a Intel Skylake/Broxton/ApolloLake/KabyLake/ + GeminiLake or CannonLake platform with the DSP enabled in the BIOS + then enable this option by saying Y or m. + +config SND_SOC_ACPI_INTEL_MATCH + tristate + select SND_SOC_ACPI if ACPI + # this option controls the compilation of ACPI matching tables and + # helpers and is not meant to be selected by the user. + +endif ## SND_SOC_INTEL_SST_TOPLEVEL + +# ASoC codec drivers +source "sound/soc/intel/boards/Kconfig" diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile new file mode 100644 index 000000000..8160520fd --- /dev/null +++ b/sound/soc/intel/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# Core support +obj-$(CONFIG_SND_SOC) += common/ + +# Platform Support +obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += haswell/ +obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += baytrail/ +obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += atom/ +obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += skylake/ + +# Machine support +obj-$(CONFIG_SND_SOC) += boards/ diff --git a/sound/soc/intel/atom/Makefile b/sound/soc/intel/atom/Makefile new file mode 100644 index 000000000..1dc60471b --- /dev/null +++ b/sound/soc/intel/atom/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-sst-atom-hifi2-platform-objs := sst-mfld-platform-pcm.o \ + sst-mfld-platform-compress.o \ + sst-atom-controls.o + +obj-$(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM) += snd-soc-sst-atom-hifi2-platform.o + +# DSP driver +obj-$(CONFIG_SND_SST_IPC) += sst/ diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c new file mode 100644 index 000000000..a1d7f93a0 --- /dev/null +++ b/sound/soc/intel/atom/sst-atom-controls.c @@ -0,0 +1,1564 @@ + /* + * sst-atom-controls.c - Intel MID Platform driver DPCM ALSA controls for Mrfld + * + * Copyright (C) 2013-14 Intel Corp + * Author: Omair Mohammed Abdullah + * Vinod Koul + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * In the dpcm driver modelling when a particular FE/BE/Mixer/Pipe is active + * we forward the settings and parameters, rest we keep the values in + * driver and forward when DAPM enables them + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include "sst-mfld-platform.h" +#include "sst-atom-controls.h" + +static int sst_fill_byte_control(struct sst_data *drv, + u8 ipc_msg, u8 block, + u8 task_id, u8 pipe_id, + u16 len, void *cmd_data) +{ + struct snd_sst_bytes_v2 *byte_data = drv->byte_stream; + + byte_data->type = SST_CMD_BYTES_SET; + byte_data->ipc_msg = ipc_msg; + byte_data->block = block; + byte_data->task_id = task_id; + byte_data->pipe_id = pipe_id; + + if (len > SST_MAX_BIN_BYTES - sizeof(*byte_data)) { + dev_err(&drv->pdev->dev, "command length too big (%u)", len); + return -EINVAL; + } + byte_data->len = len; + memcpy(byte_data->bytes, cmd_data, len); + print_hex_dump_bytes("writing to lpe: ", DUMP_PREFIX_OFFSET, + byte_data, len + sizeof(*byte_data)); + return 0; +} + +static int sst_fill_and_send_cmd_unlocked(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) +{ + int ret = 0; + + ret = sst_fill_byte_control(drv, ipc_msg, + block, task_id, pipe_id, len, cmd_data); + if (ret < 0) + return ret; + return sst->ops->send_byte_stream(sst->dev, drv->byte_stream); +} + +/** + * sst_fill_and_send_cmd - generate the IPC message and send it to the FW + * @ipc_msg: type of IPC (CMD, SET_PARAMS, GET_PARAMS) + * @cmd_data: the IPC payload + */ +static int sst_fill_and_send_cmd(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) +{ + int ret; + + mutex_lock(&drv->lock); + ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, + task_id, pipe_id, cmd_data, len); + mutex_unlock(&drv->lock); + + return ret; +} + +/** + * tx map value is a bitfield where each bit represents a FW channel + * + * 3 2 1 0 # 0 = codec0, 1 = codec1 + * RLRLRLRL # 3, 4 = reserved + * + * e.g. slot 0 rx map = 00001100b -> data from slot 0 goes into codec_in1 L,R + */ +static u8 sst_ssp_tx_map[SST_MAX_TDM_SLOTS] = { + 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default rx map */ +}; + +/** + * rx map value is a bitfield where each bit represents a slot + * + * 76543210 # 0 = slot 0, 1 = slot 1 + * + * e.g. codec1_0 tx map = 00000101b -> data from codec_out1_0 goes into slot 0, 2 + */ +static u8 sst_ssp_rx_map[SST_MAX_TDM_SLOTS] = { + 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, /* default tx map */ +}; + +/** + * NOTE: this is invoked with lock held + */ +static int sst_send_slot_map(struct sst_data *drv) +{ + struct sst_param_sba_ssp_slot_map cmd; + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.command_id = SBA_SET_SSP_SLOT_MAP; + cmd.header.length = sizeof(struct sst_param_sba_ssp_slot_map) + - sizeof(struct sst_dsp_header); + + cmd.param_id = SBA_SET_SSP_SLOT_MAP; + cmd.param_len = sizeof(cmd.rx_slot_map) + sizeof(cmd.tx_slot_map) + + sizeof(cmd.ssp_index); + cmd.ssp_index = SSP_CODEC; + + memcpy(cmd.rx_slot_map, &sst_ssp_tx_map[0], sizeof(cmd.rx_slot_map)); + memcpy(cmd.tx_slot_map, &sst_ssp_rx_map[0], sizeof(cmd.tx_slot_map)); + + return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +static int sst_slot_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_enum *e = (struct sst_enum *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = e->max; + + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; + strcpy(uinfo->value.enumerated.name, + e->texts[uinfo->value.enumerated.item]); + + return 0; +} + +/** + * sst_slot_get - get the status of the interleaver/deinterleaver control + * + * Searches the map where the control status is stored, and gets the + * channel/slot which is currently set for this enumerated control. Since it is + * an enumerated control, there is only one possible value. + */ +static int sst_slot_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sst_enum *e = (void *)kcontrol->private_value; + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + unsigned int ctl_no = e->reg; + unsigned int is_tx = e->tx; + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + + mutex_lock(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) + if (map[mux - 1] & val) + break; + + ucontrol->value.enumerated.item[0] = mux; + mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", + e->texts[mux], mux ? map[mux - 1] : -1); + return 0; +} + +/* sst_check_and_send_slot_map - helper for checking power state and sending + * slot map cmd + * + * called with lock held + */ +static int sst_check_and_send_slot_map(struct sst_data *drv, struct snd_kcontrol *kcontrol) +{ + struct sst_enum *e = (void *)kcontrol->private_value; + int ret = 0; + + if (e->w && e->w->power) + ret = sst_send_slot_map(drv); + else if (!e->w) + dev_err(&drv->pdev->dev, "Slot control: %s doesn't have DAPM widget!!!\n", + kcontrol->id.name); + return ret; +} + +/** + * sst_slot_put - set the status of interleaver/deinterleaver control + * + * (de)interleaver controls are defined in opposite sense to be user-friendly + * + * Instead of the enum value being the value written to the register, it is the + * register address; and the kcontrol number (register num) is the value written + * to the register. This is so that there can be only one value for each + * slot/channel since there is only one control for each slot/channel. + * + * This means that whenever an enum is set, we need to clear the bit + * for that kcontrol_no for all the interleaver OR deinterleaver registers + */ +static int sst_slot_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_enum *e = (void *)kcontrol->private_value; + int i, ret = 0; + unsigned int ctl_no = e->reg; + unsigned int is_tx = e->tx; + unsigned int slot_channel_no; + unsigned int val, mux; + u8 *map; + + map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + + val = 1 << ctl_no; + mux = ucontrol->value.enumerated.item[0]; + if (mux > e->max - 1) + return -EINVAL; + + mutex_lock(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; + + if (mux == 0) { + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + + mutex_unlock(&drv->lock); + return ret; + } + + /* offset by one to take "None" into account */ + slot_channel_no = mux - 1; + map[slot_channel_no] |= val; + + dev_dbg(c->dev, "%s %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", + e->texts[mux], map[slot_channel_no]); + + ret = sst_check_and_send_slot_map(drv, kcontrol); + + mutex_unlock(&drv->lock); + return ret; +} + +static int sst_send_algo_cmd(struct sst_data *drv, + struct sst_algo_control *bc) +{ + int len, ret = 0; + struct sst_cmd_set_params *cmd; + + /*bc->max includes sizeof algos + length field*/ + len = sizeof(cmd->dst) + sizeof(cmd->command_id) + bc->max; + + cmd = kzalloc(len, GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + SST_FILL_DESTINATION(2, cmd->dst, bc->pipe_id, bc->module_id); + cmd->command_id = bc->cmd_id; + memcpy(cmd->params, bc->params, bc->max); + + ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, bc->task_id, 0, cmd, len); + kfree(cmd); + return ret; +} + +/** + * sst_find_and_send_pipe_algo - send all the algo parameters for a pipe + * + * The algos which are in each pipeline are sent to the firmware one by one + * + * Called with lock held + */ +static int sst_find_and_send_pipe_algo(struct sst_data *drv, + const char *pipe, struct sst_ids *ids) +{ + int ret = 0; + struct sst_algo_control *bc; + struct sst_module *algo = NULL; + + dev_dbg(&drv->pdev->dev, "Enter: widget=%s\n", pipe); + + list_for_each_entry(algo, &ids->algo_list, node) { + bc = (void *)algo->kctl->private_value; + + dev_dbg(&drv->pdev->dev, "Found algo control name=%s pipe=%s\n", + algo->kctl->id.name, pipe); + ret = sst_send_algo_cmd(drv, bc); + if (ret) + return ret; + } + return ret; +} + +static int sst_algo_bytes_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = bc->max; + + return 0; +} + +static int sst_algo_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sst_algo_control *bc = (void *)kcontrol->private_value; + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(ucontrol->value.bytes.data, bc->params, bc->max); + break; + default: + dev_err(component->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; + + } + return 0; +} + +static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); + mutex_lock(&drv->lock); + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(bc->params, ucontrol->value.bytes.data, bc->max); + break; + default: + mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; + } + /*if pipe is enabled, need to send the algo params from here*/ + if (bc->w && bc->w->power) + ret = sst_send_algo_cmd(drv, bc); + mutex_unlock(&drv->lock); + + return ret; +} + +static int sst_gain_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = mc->stereo ? 2 : 1; + uinfo->value.integer.min = mc->min; + uinfo->value.integer.max = mc->max; + + return 0; +} + +/** + * sst_send_gain_cmd - send the gain algorithm IPC to the FW + * @gv: the stored value of gain (also contains rampduration) + * @mute: flag that indicates whether this was called from the + * digital_mute callback or directly. If called from the + * digital_mute callback, module will be muted/unmuted based on this + * flag. The flag is always 0 if called directly. + * + * Called with sst_data.lock held + * + * The user-set gain value is sent only if the user-controllable 'mute' control + * is OFF (indicated by gv->mute). Otherwise, the mute value (MIN value) is + * sent. + */ +static int sst_send_gain_cmd(struct sst_data *drv, struct sst_gain_value *gv, + u16 task_id, u16 loc_id, u16 module_id, int mute) +{ + struct sst_cmd_set_gain_dual cmd; + + dev_dbg(&drv->pdev->dev, "Enter\n"); + + cmd.header.command_id = MMX_SET_GAIN; + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.gain_cell_num = 1; + + if (mute || gv->mute) { + cmd.cell_gains[0].cell_gain_left = SST_GAIN_MIN_VALUE; + cmd.cell_gains[0].cell_gain_right = SST_GAIN_MIN_VALUE; + } else { + cmd.cell_gains[0].cell_gain_left = gv->l_gain; + cmd.cell_gains[0].cell_gain_right = gv->r_gain; + } + + SST_FILL_DESTINATION(2, cmd.cell_gains[0].dest, + loc_id, module_id); + cmd.cell_gains[0].gain_time_constant = gv->ramp_duration; + + cmd.header.length = sizeof(struct sst_cmd_set_gain_dual) + - sizeof(struct sst_dsp_header); + + /* we are with lock held, so call the unlocked api to send */ + return sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_SET_PARAMS, + SST_FLAG_BLOCKED, task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +static int sst_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + + switch (mc->type) { + case SST_GAIN_TLV: + ucontrol->value.integer.value[0] = gv->l_gain; + ucontrol->value.integer.value[1] = gv->r_gain; + break; + + case SST_GAIN_MUTE: + ucontrol->value.integer.value[0] = gv->mute ? 0 : 1; + break; + + case SST_GAIN_RAMP_DURATION: + ucontrol->value.integer.value[0] = gv->ramp_duration; + break; + + default: + dev_err(component->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; + } + + return 0; +} + +static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + + mutex_lock(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: + gv->l_gain = ucontrol->value.integer.value[0]; + gv->r_gain = ucontrol->value.integer.value[1]; + dev_dbg(cmpnt->dev, "%s: Volume %d, %d\n", + mc->pname, gv->l_gain, gv->r_gain); + break; + + case SST_GAIN_MUTE: + gv->mute = !ucontrol->value.integer.value[0]; + dev_dbg(cmpnt->dev, "%s: Mute %d\n", mc->pname, gv->mute); + break; + + case SST_GAIN_RAMP_DURATION: + gv->ramp_duration = ucontrol->value.integer.value[0]; + dev_dbg(cmpnt->dev, "%s: Ramp Delay%d\n", + mc->pname, gv->ramp_duration); + break; + + default: + mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; + } + + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); + mutex_unlock(&drv->lock); + + return ret; +} + +static int sst_set_pipe_gain(struct sst_ids *ids, + struct sst_data *drv, int mute); + +static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + mutex_lock(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); + mutex_unlock(&drv->lock); + + return 0; +} + +static int sst_generic_modules_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + return sst_send_pipe_module_params(w, k); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(sst_gain_tlv_common, SST_GAIN_MIN_VALUE * 10, 10, 0); + +/* Look up table to convert MIXER SW bit regs to SWM inputs */ +static const uint swm_mixer_input_ids[SST_SWM_INPUT_COUNT] = { + [SST_IP_MODEM] = SST_SWM_IN_MODEM, + [SST_IP_CODEC0] = SST_SWM_IN_CODEC0, + [SST_IP_CODEC1] = SST_SWM_IN_CODEC1, + [SST_IP_LOOP0] = SST_SWM_IN_SPROT_LOOP, + [SST_IP_LOOP1] = SST_SWM_IN_MEDIA_LOOP1, + [SST_IP_LOOP2] = SST_SWM_IN_MEDIA_LOOP2, + [SST_IP_PCM0] = SST_SWM_IN_PCM0, + [SST_IP_PCM1] = SST_SWM_IN_PCM1, + [SST_IP_MEDIA0] = SST_SWM_IN_MEDIA0, + [SST_IP_MEDIA1] = SST_SWM_IN_MEDIA1, + [SST_IP_MEDIA2] = SST_SWM_IN_MEDIA2, + [SST_IP_MEDIA3] = SST_SWM_IN_MEDIA3, +}; + +/** + * fill_swm_input - fill in the SWM input ids given the register + * + * The register value is a bit-field inicated which mixer inputs are ON. Use the + * lookup table to get the input-id and fill it in the structure. + */ +static int fill_swm_input(struct snd_soc_component *cmpnt, + struct swm_input_ids *swm_input, unsigned int reg) +{ + uint i, is_set, nb_inputs = 0; + u16 input_loc_id; + + dev_dbg(cmpnt->dev, "reg: %#x\n", reg); + for (i = 0; i < SST_SWM_INPUT_COUNT; i++) { + is_set = reg & BIT(i); + if (!is_set) + continue; + + input_loc_id = swm_mixer_input_ids[i]; + SST_FILL_DESTINATION(2, swm_input->input_id, + input_loc_id, SST_DEFAULT_MODULE_ID); + nb_inputs++; + swm_input++; + dev_dbg(cmpnt->dev, "input id: %#x, nb_inputs: %d\n", + input_loc_id, nb_inputs); + + if (nb_inputs == SST_CMD_SWM_MAX_INPUTS) { + dev_warn(cmpnt->dev, "SET_SWM cmd max inputs reached"); + break; + } + } + return nb_inputs; +} + + +/** + * called with lock held + */ +static int sst_set_pipe_gain(struct sst_ids *ids, + struct sst_data *drv, int mute) +{ + int ret = 0; + struct sst_gain_mixer_control *mc; + struct sst_gain_value *gv; + struct sst_module *gain = NULL; + + list_for_each_entry(gain, &ids->gain_list, node) { + struct snd_kcontrol *kctl = gain->kctl; + + dev_dbg(&drv->pdev->dev, "control name=%s\n", kctl->id.name); + mc = (void *)kctl->private_value; + gv = mc->gain_val; + + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, mute); + if (ret) + return ret; + } + return ret; +} + +static int sst_swm_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct sst_cmd_set_swm cmd; + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(cmpnt); + struct sst_ids *ids = w->priv; + bool set_mixer = false; + struct soc_mixer_control *mc; + int val = 0; + int i = 0; + + dev_dbg(cmpnt->dev, "widget = %s\n", w->name); + /* + * Identify which mixer input is on and send the bitmap of the + * inputs as an IPC to the DSP. + */ + for (i = 0; i < w->num_kcontrols; i++) { + if (dapm_kcontrol_get_value(w->kcontrols[i])) { + mc = (struct soc_mixer_control *)(w->kcontrols[i])->private_value; + val |= 1 << mc->shift; + } + } + dev_dbg(cmpnt->dev, "val = %#x\n", val); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + case SND_SOC_DAPM_POST_PMD: + set_mixer = true; + break; + case SND_SOC_DAPM_POST_REG: + if (w->power) + set_mixer = true; + break; + default: + set_mixer = false; + } + + if (set_mixer == false) + return 0; + + if (SND_SOC_DAPM_EVENT_ON(event) || + event == SND_SOC_DAPM_POST_REG) + cmd.switch_state = SST_SWM_ON; + else + cmd.switch_state = SST_SWM_OFF; + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + /* MMX_SET_SWM == SBA_SET_SWM */ + cmd.header.command_id = SBA_SET_SWM; + + SST_FILL_DESTINATION(2, cmd.output_id, + ids->location_id, SST_DEFAULT_MODULE_ID); + cmd.nb_inputs = fill_swm_input(cmpnt, &cmd.input[0], val); + cmd.header.length = offsetof(struct sst_cmd_set_swm, input) + - sizeof(struct sst_dsp_header) + + (cmd.nb_inputs * sizeof(cmd.input[0])); + + return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + ids->task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); +} + +/* SBA mixers - 16 inputs */ +#define SST_SBA_DECLARE_MIX_CONTROLS(kctl_name) \ + static const struct snd_kcontrol_new kctl_name[] = { \ + SOC_DAPM_SINGLE("modem_in Switch", SND_SOC_NOPM, SST_IP_MODEM, 1, 0), \ + SOC_DAPM_SINGLE("codec_in0 Switch", SND_SOC_NOPM, SST_IP_CODEC0, 1, 0), \ + SOC_DAPM_SINGLE("codec_in1 Switch", SND_SOC_NOPM, SST_IP_CODEC1, 1, 0), \ + SOC_DAPM_SINGLE("sprot_loop_in Switch", SND_SOC_NOPM, SST_IP_LOOP0, 1, 0), \ + SOC_DAPM_SINGLE("media_loop1_in Switch", SND_SOC_NOPM, SST_IP_LOOP1, 1, 0), \ + SOC_DAPM_SINGLE("media_loop2_in Switch", SND_SOC_NOPM, SST_IP_LOOP2, 1, 0), \ + SOC_DAPM_SINGLE("pcm0_in Switch", SND_SOC_NOPM, SST_IP_PCM0, 1, 0), \ + SOC_DAPM_SINGLE("pcm1_in Switch", SND_SOC_NOPM, SST_IP_PCM1, 1, 0), \ + } + +#define SST_SBA_MIXER_GRAPH_MAP(mix_name) \ + { mix_name, "modem_in Switch", "modem_in" }, \ + { mix_name, "codec_in0 Switch", "codec_in0" }, \ + { mix_name, "codec_in1 Switch", "codec_in1" }, \ + { mix_name, "sprot_loop_in Switch", "sprot_loop_in" }, \ + { mix_name, "media_loop1_in Switch", "media_loop1_in" }, \ + { mix_name, "media_loop2_in Switch", "media_loop2_in" }, \ + { mix_name, "pcm0_in Switch", "pcm0_in" }, \ + { mix_name, "pcm1_in Switch", "pcm1_in" } + +#define SST_MMX_DECLARE_MIX_CONTROLS(kctl_name) \ + static const struct snd_kcontrol_new kctl_name[] = { \ + SOC_DAPM_SINGLE("media0_in Switch", SND_SOC_NOPM, SST_IP_MEDIA0, 1, 0), \ + SOC_DAPM_SINGLE("media1_in Switch", SND_SOC_NOPM, SST_IP_MEDIA1, 1, 0), \ + SOC_DAPM_SINGLE("media2_in Switch", SND_SOC_NOPM, SST_IP_MEDIA2, 1, 0), \ + SOC_DAPM_SINGLE("media3_in Switch", SND_SOC_NOPM, SST_IP_MEDIA3, 1, 0), \ + } + +SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media0_controls); +SST_MMX_DECLARE_MIX_CONTROLS(sst_mix_media1_controls); + +/* 18 SBA mixers */ +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm1_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_pcm2_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_sprot_l0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l1_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_media_l2_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_voip_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec0_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_codec1_controls); +SST_SBA_DECLARE_MIX_CONTROLS(sst_mix_modem_controls); + +/* + * sst_handle_vb_timer - Start/Stop the DSP scheduler + * + * The DSP expects first cmd to be SBA_VB_START, so at first startup send + * that. + * DSP expects last cmd to be SBA_VB_IDLE, so at last shutdown send that. + * + * Do refcount internally so that we send command only at first start + * and last end. Since SST driver does its own ref count, invoke sst's + * power ops always! + */ +int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) +{ + int ret = 0; + struct sst_cmd_generic cmd; + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + static int timer_usage; + + if (enable) + cmd.header.command_id = SBA_VB_START; + else + cmd.header.command_id = SBA_IDLE; + dev_dbg(dai->dev, "enable=%u, usage=%d\n", enable, timer_usage); + + SST_FILL_DEFAULT_DESTINATION(cmd.header.dst); + cmd.header.length = 0; + + if (enable) { + ret = sst->ops->power(sst->dev, true); + if (ret < 0) + return ret; + } + + mutex_lock(&drv->lock); + if (enable) + timer_usage++; + else + timer_usage--; + + /* + * Send the command only if this call is the first enable or last + * disable + */ + if ((enable && (timer_usage == 1)) || + (!enable && (timer_usage == 0))) { + ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, + SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret && enable) { + timer_usage--; + enable = false; + } + } + mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); + return ret; +} + +int sst_fill_ssp_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct sst_data *ctx = snd_soc_dai_get_drvdata(dai); + + ctx->ssp_cmd.nb_slots = slots; + ctx->ssp_cmd.active_tx_slot_map = tx_mask; + ctx->ssp_cmd.active_rx_slot_map = rx_mask; + ctx->ssp_cmd.nb_bits_per_slots = slot_width; + + return 0; +} + +static int sst_get_frame_sync_polarity(struct snd_soc_dai *dai, + unsigned int fmt) +{ + int format; + + format = fmt & SND_SOC_DAIFMT_INV_MASK; + dev_dbg(dai->dev, "Enter:%s, format=%x\n", __func__, format); + + switch (format) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_IB_NF: + return SSP_FS_ACTIVE_HIGH; + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + return SSP_FS_ACTIVE_LOW; + default: + dev_err(dai->dev, "Invalid frame sync polarity %d\n", format); + } + + return -EINVAL; +} + +static int sst_get_ssp_mode(struct snd_soc_dai *dai, unsigned int fmt) +{ + int format; + + format = (fmt & SND_SOC_DAIFMT_MASTER_MASK); + dev_dbg(dai->dev, "Enter:%s, format=%x\n", __func__, format); + + switch (format) { + case SND_SOC_DAIFMT_CBS_CFS: + return SSP_MODE_MASTER; + case SND_SOC_DAIFMT_CBM_CFM: + return SSP_MODE_SLAVE; + default: + dev_err(dai->dev, "Invalid ssp protocol: %d\n", format); + } + + return -EINVAL; +} + + +int sst_fill_ssp_config(struct snd_soc_dai *dai, unsigned int fmt) +{ + unsigned int mode; + int fs_polarity; + struct sst_data *ctx = snd_soc_dai_get_drvdata(dai); + + mode = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + switch (mode) { + case SND_SOC_DAIFMT_DSP_B: + ctx->ssp_cmd.ssp_protocol = SSP_MODE_PCM; + ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NETWORK << 1); + ctx->ssp_cmd.start_delay = 0; + ctx->ssp_cmd.data_polarity = 1; + ctx->ssp_cmd.frame_sync_width = 1; + break; + + case SND_SOC_DAIFMT_DSP_A: + ctx->ssp_cmd.ssp_protocol = SSP_MODE_PCM; + ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NETWORK << 1); + ctx->ssp_cmd.start_delay = 1; + ctx->ssp_cmd.data_polarity = 1; + ctx->ssp_cmd.frame_sync_width = 1; + break; + + case SND_SOC_DAIFMT_I2S: + ctx->ssp_cmd.ssp_protocol = SSP_MODE_I2S; + ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NORMAL << 1); + ctx->ssp_cmd.start_delay = 1; + ctx->ssp_cmd.data_polarity = 0; + ctx->ssp_cmd.frame_sync_width = ctx->ssp_cmd.nb_bits_per_slots; + break; + + case SND_SOC_DAIFMT_LEFT_J: + ctx->ssp_cmd.ssp_protocol = SSP_MODE_I2S; + ctx->ssp_cmd.mode = sst_get_ssp_mode(dai, fmt) | (SSP_PCM_MODE_NORMAL << 1); + ctx->ssp_cmd.start_delay = 0; + ctx->ssp_cmd.data_polarity = 0; + ctx->ssp_cmd.frame_sync_width = ctx->ssp_cmd.nb_bits_per_slots; + break; + + default: + dev_dbg(dai->dev, "using default ssp configs\n"); + } + + fs_polarity = sst_get_frame_sync_polarity(dai, fmt); + if (fs_polarity < 0) + return fs_polarity; + + ctx->ssp_cmd.frame_sync_polarity = fs_polarity; + + return 0; +} + +/** + * sst_ssp_config - contains SSP configuration for media UC + * this can be overwritten by set_dai_xxx APIs + */ +static const struct sst_ssp_config sst_ssp_configs = { + .ssp_id = SSP_CODEC, + .bits_per_slot = 24, + .slots = 4, + .ssp_mode = SSP_MODE_MASTER, + .pcm_mode = SSP_PCM_MODE_NETWORK, + .duplex = SSP_DUPLEX, + .ssp_protocol = SSP_MODE_PCM, + .fs_width = 1, + .fs_frequency = SSP_FS_48_KHZ, + .active_slot_map = 0xF, + .start_delay = 0, + .frame_sync_polarity = SSP_FS_ACTIVE_HIGH, + .data_polarity = 1, +}; + +void sst_fill_ssp_defaults(struct snd_soc_dai *dai) +{ + const struct sst_ssp_config *config; + struct sst_data *ctx = snd_soc_dai_get_drvdata(dai); + + config = &sst_ssp_configs; + + ctx->ssp_cmd.selection = config->ssp_id; + ctx->ssp_cmd.nb_bits_per_slots = config->bits_per_slot; + ctx->ssp_cmd.nb_slots = config->slots; + ctx->ssp_cmd.mode = config->ssp_mode | (config->pcm_mode << 1); + ctx->ssp_cmd.duplex = config->duplex; + ctx->ssp_cmd.active_tx_slot_map = config->active_slot_map; + ctx->ssp_cmd.active_rx_slot_map = config->active_slot_map; + ctx->ssp_cmd.frame_sync_frequency = config->fs_frequency; + ctx->ssp_cmd.frame_sync_polarity = config->frame_sync_polarity; + ctx->ssp_cmd.data_polarity = config->data_polarity; + ctx->ssp_cmd.frame_sync_width = config->fs_width; + ctx->ssp_cmd.ssp_protocol = config->ssp_protocol; + ctx->ssp_cmd.start_delay = config->start_delay; + ctx->ssp_cmd.reserved1 = ctx->ssp_cmd.reserved2 = 0xFF; +} + +int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable) +{ + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + int ssp_id; + + dev_dbg(dai->dev, "Enter: enable=%d port_name=%s\n", enable, id); + + if (strcmp(id, "ssp0-port") == 0) + ssp_id = SSP_MODEM; + else if (strcmp(id, "ssp2-port") == 0) + ssp_id = SSP_CODEC; + else { + dev_dbg(dai->dev, "port %s is not supported\n", id); + return -1; + } + + SST_FILL_DEFAULT_DESTINATION(drv->ssp_cmd.header.dst); + drv->ssp_cmd.header.command_id = SBA_HW_SET_SSP; + drv->ssp_cmd.header.length = sizeof(struct sst_cmd_sba_hw_set_ssp) + - sizeof(struct sst_dsp_header); + + drv->ssp_cmd.selection = ssp_id; + dev_dbg(dai->dev, "ssp_id: %u\n", ssp_id); + + if (enable) + drv->ssp_cmd.switch_state = SST_SWITCH_ON; + else + drv->ssp_cmd.switch_state = SST_SWITCH_OFF; + + return sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + SST_TASK_SBA, 0, &drv->ssp_cmd, + sizeof(drv->ssp_cmd.header) + drv->ssp_cmd.header.length); +} + +static int sst_set_be_modules(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + + dev_dbg(c->dev, "Enter: widget=%s\n", w->name); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + mutex_lock(&drv->lock); + ret = sst_send_slot_map(drv); + mutex_unlock(&drv->lock); + if (ret) + return ret; + ret = sst_send_pipe_module_params(w, k); + } + return ret; +} + +static int sst_set_media_path(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct sst_cmd_set_media_path cmd; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + dev_dbg(c->dev, "widget=%s\n", w->name); + dev_dbg(c->dev, "task=%u, location=%#x\n", + ids->task_id, ids->location_id); + + if (SND_SOC_DAPM_EVENT_ON(event)) + cmd.switch_state = SST_PATH_ON; + else + cmd.switch_state = SST_PATH_OFF; + + SST_FILL_DESTINATION(2, cmd.header.dst, + ids->location_id, SST_DEFAULT_MODULE_ID); + + /* MMX_SET_MEDIA_PATH == SBA_SET_MEDIA_PATH */ + cmd.header.command_id = MMX_SET_MEDIA_PATH; + cmd.header.length = sizeof(struct sst_cmd_set_media_path) + - sizeof(struct sst_dsp_header); + + ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + ids->task_id, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret) + return ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = sst_send_pipe_module_params(w, k); + return ret; +} + +static int sst_set_media_loop(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct sst_cmd_sba_set_media_loop_map cmd; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + + dev_dbg(c->dev, "Enter:widget=%s\n", w->name); + if (SND_SOC_DAPM_EVENT_ON(event)) + cmd.switch_state = SST_SWITCH_ON; + else + cmd.switch_state = SST_SWITCH_OFF; + + SST_FILL_DESTINATION(2, cmd.header.dst, + ids->location_id, SST_DEFAULT_MODULE_ID); + + cmd.header.command_id = SBA_SET_MEDIA_LOOP_MAP; + cmd.header.length = sizeof(struct sst_cmd_sba_set_media_loop_map) + - sizeof(struct sst_dsp_header); + cmd.param.part.cfg.rate = 2; /* 48khz */ + + cmd.param.part.cfg.format = ids->format; /* stereo/Mono */ + cmd.param.part.cfg.s_length = 1; /* 24bit left justified */ + cmd.map = 0; /* Algo sequence: Gain - DRP - FIR - IIR */ + + ret = sst_fill_and_send_cmd(drv, SST_IPC_IA_CMD, SST_FLAG_BLOCKED, + SST_TASK_SBA, 0, &cmd, + sizeof(cmd.header) + cmd.header.length); + if (ret) + return ret; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = sst_send_pipe_module_params(w, k); + return ret; +} + +static const struct snd_soc_dapm_widget sst_dapm_widgets[] = { + SST_AIF_IN("modem_in", sst_set_be_modules), + SST_AIF_IN("codec_in0", sst_set_be_modules), + SST_AIF_IN("codec_in1", sst_set_be_modules), + SST_AIF_OUT("modem_out", sst_set_be_modules), + SST_AIF_OUT("codec_out0", sst_set_be_modules), + SST_AIF_OUT("codec_out1", sst_set_be_modules), + + /* Media Paths */ + /* MediaX IN paths are set via ALLOC, so no SET_MEDIA_PATH command */ + SST_PATH_INPUT("media0_in", SST_TASK_MMX, SST_SWM_IN_MEDIA0, sst_generic_modules_event), + SST_PATH_INPUT("media1_in", SST_TASK_MMX, SST_SWM_IN_MEDIA1, NULL), + SST_PATH_INPUT("media2_in", SST_TASK_MMX, SST_SWM_IN_MEDIA2, sst_set_media_path), + SST_PATH_INPUT("media3_in", SST_TASK_MMX, SST_SWM_IN_MEDIA3, NULL), + SST_PATH_OUTPUT("media0_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA0, sst_set_media_path), + SST_PATH_OUTPUT("media1_out", SST_TASK_MMX, SST_SWM_OUT_MEDIA1, sst_set_media_path), + + /* SBA PCM Paths */ + SST_PATH_INPUT("pcm0_in", SST_TASK_SBA, SST_SWM_IN_PCM0, sst_set_media_path), + SST_PATH_INPUT("pcm1_in", SST_TASK_SBA, SST_SWM_IN_PCM1, sst_set_media_path), + SST_PATH_OUTPUT("pcm0_out", SST_TASK_SBA, SST_SWM_OUT_PCM0, sst_set_media_path), + SST_PATH_OUTPUT("pcm1_out", SST_TASK_SBA, SST_SWM_OUT_PCM1, sst_set_media_path), + SST_PATH_OUTPUT("pcm2_out", SST_TASK_SBA, SST_SWM_OUT_PCM2, sst_set_media_path), + + /* SBA Loops */ + SST_PATH_INPUT("sprot_loop_in", SST_TASK_SBA, SST_SWM_IN_SPROT_LOOP, NULL), + SST_PATH_INPUT("media_loop1_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP1, NULL), + SST_PATH_INPUT("media_loop2_in", SST_TASK_SBA, SST_SWM_IN_MEDIA_LOOP2, NULL), + SST_PATH_MEDIA_LOOP_OUTPUT("sprot_loop_out", SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, SST_FMT_STEREO, sst_set_media_loop), + SST_PATH_MEDIA_LOOP_OUTPUT("media_loop1_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, SST_FMT_STEREO, sst_set_media_loop), + SST_PATH_MEDIA_LOOP_OUTPUT("media_loop2_out", SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, SST_FMT_STEREO, sst_set_media_loop), + + /* Media Mixers */ + SST_SWM_MIXER("media0_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA0, + sst_mix_media0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media1_out mix 0", SND_SOC_NOPM, SST_TASK_MMX, SST_SWM_OUT_MEDIA1, + sst_mix_media1_controls, sst_swm_mixer_event), + + /* SBA PCM mixers */ + SST_SWM_MIXER("pcm0_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM0, + sst_mix_pcm0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("pcm1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM1, + sst_mix_pcm1_controls, sst_swm_mixer_event), + SST_SWM_MIXER("pcm2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_PCM2, + sst_mix_pcm2_controls, sst_swm_mixer_event), + + /* SBA Loop mixers */ + SST_SWM_MIXER("sprot_loop_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_SPROT_LOOP, + sst_mix_sprot_l0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media_loop1_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP1, + sst_mix_media_l1_controls, sst_swm_mixer_event), + SST_SWM_MIXER("media_loop2_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MEDIA_LOOP2, + sst_mix_media_l2_controls, sst_swm_mixer_event), + + /* SBA Backend mixers */ + SST_SWM_MIXER("codec_out0 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC0, + sst_mix_codec0_controls, sst_swm_mixer_event), + SST_SWM_MIXER("codec_out1 mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_CODEC1, + sst_mix_codec1_controls, sst_swm_mixer_event), + SST_SWM_MIXER("modem_out mix 0", SND_SOC_NOPM, SST_TASK_SBA, SST_SWM_OUT_MODEM, + sst_mix_modem_controls, sst_swm_mixer_event), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"media0_in", NULL, "Compress Playback"}, + {"media1_in", NULL, "Headset Playback"}, + {"media2_in", NULL, "pcm0_out"}, + {"media3_in", NULL, "Deepbuffer Playback"}, + + {"media0_out mix 0", "media0_in Switch", "media0_in"}, + {"media0_out mix 0", "media1_in Switch", "media1_in"}, + {"media0_out mix 0", "media2_in Switch", "media2_in"}, + {"media0_out mix 0", "media3_in Switch", "media3_in"}, + {"media1_out mix 0", "media0_in Switch", "media0_in"}, + {"media1_out mix 0", "media1_in Switch", "media1_in"}, + {"media1_out mix 0", "media2_in Switch", "media2_in"}, + {"media1_out mix 0", "media3_in Switch", "media3_in"}, + + {"media0_out", NULL, "media0_out mix 0"}, + {"media1_out", NULL, "media1_out mix 0"}, + {"pcm0_in", NULL, "media0_out"}, + {"pcm1_in", NULL, "media1_out"}, + + {"Headset Capture", NULL, "pcm1_out"}, + {"Headset Capture", NULL, "pcm2_out"}, + {"pcm0_out", NULL, "pcm0_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm0_out mix 0"), + {"pcm1_out", NULL, "pcm1_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm1_out mix 0"), + {"pcm2_out", NULL, "pcm2_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("pcm2_out mix 0"), + + {"media_loop1_in", NULL, "media_loop1_out"}, + {"media_loop1_out", NULL, "media_loop1_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("media_loop1_out mix 0"), + {"media_loop2_in", NULL, "media_loop2_out"}, + {"media_loop2_out", NULL, "media_loop2_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("media_loop2_out mix 0"), + {"sprot_loop_in", NULL, "sprot_loop_out"}, + {"sprot_loop_out", NULL, "sprot_loop_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("sprot_loop_out mix 0"), + + {"codec_out0", NULL, "codec_out0 mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("codec_out0 mix 0"), + {"codec_out1", NULL, "codec_out1 mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("codec_out1 mix 0"), + {"modem_out", NULL, "modem_out mix 0"}, + SST_SBA_MIXER_GRAPH_MAP("modem_out mix 0"), + + +}; +static const char * const slot_names[] = { + "none", + "slot 0", "slot 1", "slot 2", "slot 3", + "slot 4", "slot 5", "slot 6", "slot 7", /* not supported by FW */ +}; + +static const char * const channel_names[] = { + "none", + "codec_out0_0", "codec_out0_1", "codec_out1_0", "codec_out1_1", + "codec_out2_0", "codec_out2_1", "codec_out3_0", "codec_out3_1", /* not supported by FW */ +}; + +#define SST_INTERLEAVER(xpname, slot_name, slotno) \ + SST_SSP_SLOT_CTL(xpname, "tx interleaver", slot_name, slotno, true, \ + channel_names, sst_slot_get, sst_slot_put) + +#define SST_DEINTERLEAVER(xpname, channel_name, channel_no) \ + SST_SSP_SLOT_CTL(xpname, "rx deinterleaver", channel_name, channel_no, false, \ + slot_names, sst_slot_get, sst_slot_put) + +static const struct snd_kcontrol_new sst_slot_controls[] = { + SST_INTERLEAVER("codec_out", "slot 0", 0), + SST_INTERLEAVER("codec_out", "slot 1", 1), + SST_INTERLEAVER("codec_out", "slot 2", 2), + SST_INTERLEAVER("codec_out", "slot 3", 3), + SST_DEINTERLEAVER("codec_in", "codec_in0_0", 0), + SST_DEINTERLEAVER("codec_in", "codec_in0_1", 1), + SST_DEINTERLEAVER("codec_in", "codec_in1_0", 2), + SST_DEINTERLEAVER("codec_in", "codec_in1_1", 3), +}; + +/* Gain helper with min/max set */ +#define SST_GAIN(name, path_id, task_id, instance, gain_var) \ + SST_GAIN_KCONTROLS(name, "Gain", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ + SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ + sst_gain_get, sst_gain_put, \ + SST_MODULE_ID_GAIN_CELL, path_id, instance, task_id, \ + sst_gain_tlv_common, gain_var) + +#define SST_VOLUME(name, path_id, task_id, instance, gain_var) \ + SST_GAIN_KCONTROLS(name, "Volume", SST_GAIN_MIN_VALUE, SST_GAIN_MAX_VALUE, \ + SST_GAIN_TC_MIN, SST_GAIN_TC_MAX, \ + sst_gain_get, sst_gain_put, \ + SST_MODULE_ID_VOLUME, path_id, instance, task_id, \ + sst_gain_tlv_common, gain_var) + +static struct sst_gain_value sst_gains[]; + +static const struct snd_kcontrol_new sst_gain_controls[] = { + SST_GAIN("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[0]), + SST_GAIN("media1_in", SST_PATH_INDEX_MEDIA1_IN, SST_TASK_MMX, 0, &sst_gains[1]), + SST_GAIN("media2_in", SST_PATH_INDEX_MEDIA2_IN, SST_TASK_MMX, 0, &sst_gains[2]), + SST_GAIN("media3_in", SST_PATH_INDEX_MEDIA3_IN, SST_TASK_MMX, 0, &sst_gains[3]), + + SST_GAIN("pcm0_in", SST_PATH_INDEX_PCM0_IN, SST_TASK_SBA, 0, &sst_gains[4]), + SST_GAIN("pcm1_in", SST_PATH_INDEX_PCM1_IN, SST_TASK_SBA, 0, &sst_gains[5]), + SST_GAIN("pcm1_out", SST_PATH_INDEX_PCM1_OUT, SST_TASK_SBA, 0, &sst_gains[6]), + SST_GAIN("pcm2_out", SST_PATH_INDEX_PCM2_OUT, SST_TASK_SBA, 0, &sst_gains[7]), + + SST_GAIN("codec_in0", SST_PATH_INDEX_CODEC_IN0, SST_TASK_SBA, 0, &sst_gains[8]), + SST_GAIN("codec_in1", SST_PATH_INDEX_CODEC_IN1, SST_TASK_SBA, 0, &sst_gains[9]), + SST_GAIN("codec_out0", SST_PATH_INDEX_CODEC_OUT0, SST_TASK_SBA, 0, &sst_gains[10]), + SST_GAIN("codec_out1", SST_PATH_INDEX_CODEC_OUT1, SST_TASK_SBA, 0, &sst_gains[11]), + SST_GAIN("media_loop1_out", SST_PATH_INDEX_MEDIA_LOOP1_OUT, SST_TASK_SBA, 0, &sst_gains[12]), + SST_GAIN("media_loop2_out", SST_PATH_INDEX_MEDIA_LOOP2_OUT, SST_TASK_SBA, 0, &sst_gains[13]), + SST_GAIN("sprot_loop_out", SST_PATH_INDEX_SPROT_LOOP_OUT, SST_TASK_SBA, 0, &sst_gains[14]), + SST_VOLUME("media0_in", SST_PATH_INDEX_MEDIA0_IN, SST_TASK_MMX, 0, &sst_gains[15]), + SST_GAIN("modem_in", SST_PATH_INDEX_MODEM_IN, SST_TASK_SBA, 0, &sst_gains[16]), + SST_GAIN("modem_out", SST_PATH_INDEX_MODEM_OUT, SST_TASK_SBA, 0, &sst_gains[17]), + +}; + +#define SST_GAIN_NUM_CONTROLS 3 +/* the SST_GAIN macro above will create three alsa controls for each + * instance invoked, gain, mute and ramp duration, which use the same gain + * cell sst_gain to keep track of data + * To calculate number of gain cell instances we need to device by 3 in + * below caulcation for gain cell memory. + * This gets rid of static number and issues while adding new controls + */ +static struct sst_gain_value sst_gains[ARRAY_SIZE(sst_gain_controls)/SST_GAIN_NUM_CONTROLS]; + +static const struct snd_kcontrol_new sst_algo_controls[] = { + SST_ALGO_KCONTROL_BYTES("media_loop1_out", "fir", 272, SST_MODULE_ID_FIR_24, + SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), + SST_ALGO_KCONTROL_BYTES("media_loop1_out", "iir", 300, SST_MODULE_ID_IIR_24, + SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + SST_ALGO_KCONTROL_BYTES("media_loop1_out", "mdrp", 286, SST_MODULE_ID_MDRP, + SST_PATH_INDEX_MEDIA_LOOP1_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP), + SST_ALGO_KCONTROL_BYTES("media_loop2_out", "fir", 272, SST_MODULE_ID_FIR_24, + SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_FIR), + SST_ALGO_KCONTROL_BYTES("media_loop2_out", "iir", 300, SST_MODULE_ID_IIR_24, + SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + SST_ALGO_KCONTROL_BYTES("media_loop2_out", "mdrp", 286, SST_MODULE_ID_MDRP, + SST_PATH_INDEX_MEDIA_LOOP2_OUT, 0, SST_TASK_SBA, SBA_SET_MDRP), + SST_ALGO_KCONTROL_BYTES("sprot_loop_out", "lpro", 192, SST_MODULE_ID_SPROT, + SST_PATH_INDEX_SPROT_LOOP_OUT, 0, SST_TASK_SBA, SBA_VB_LPRO), + SST_ALGO_KCONTROL_BYTES("codec_in0", "dcr", 52, SST_MODULE_ID_FILT_DCR, + SST_PATH_INDEX_CODEC_IN0, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + SST_ALGO_KCONTROL_BYTES("codec_in1", "dcr", 52, SST_MODULE_ID_FILT_DCR, + SST_PATH_INDEX_CODEC_IN1, 0, SST_TASK_SBA, SBA_VB_SET_IIR), + +}; + +static int sst_algo_control_init(struct device *dev) +{ + int i = 0; + struct sst_algo_control *bc; + /*allocate space to cache the algo parameters in the driver*/ + for (i = 0; i < ARRAY_SIZE(sst_algo_controls); i++) { + bc = (struct sst_algo_control *)sst_algo_controls[i].private_value; + bc->params = devm_kzalloc(dev, bc->max, GFP_KERNEL); + if (bc->params == NULL) + return -ENOMEM; + } + return 0; +} + +static bool is_sst_dapm_widget(struct snd_soc_dapm_widget *w) +{ + switch (w->id) { + case snd_soc_dapm_pga: + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + case snd_soc_dapm_input: + case snd_soc_dapm_output: + case snd_soc_dapm_mixer: + return true; + default: + return false; + } +} + +/** + * sst_send_pipe_gains - send gains for the front-end DAIs + * + * The gains in the pipes connected to the front-ends are muted/unmuted + * automatically via the digital_mute() DAPM callback. This function sends the + * gains for the front-end pipes. + */ +int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute) +{ + struct sst_data *drv = snd_soc_dai_get_drvdata(dai); + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_path *p = NULL; + + dev_dbg(dai->dev, "enter, dai-name=%s dir=%d\n", dai->name, stream); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev_dbg(dai->dev, "Stream name=%s\n", + dai->playback_widget->name); + w = dai->playback_widget; + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (p->connected && !p->connected(w, p->sink)) + continue; + + if (p->connect && p->sink->power && + is_sst_dapm_widget(p->sink)) { + struct sst_ids *ids = p->sink->priv; + + dev_dbg(dai->dev, "send gains for widget=%s\n", + p->sink->name); + mutex_lock(&drv->lock); + sst_set_pipe_gain(ids, drv, mute); + mutex_unlock(&drv->lock); + } + } + } else { + dev_dbg(dai->dev, "Stream name=%s\n", + dai->capture_widget->name); + w = dai->capture_widget; + snd_soc_dapm_widget_for_each_source_path(w, p) { + if (p->connected && !p->connected(w, p->source)) + continue; + + if (p->connect && p->source->power && + is_sst_dapm_widget(p->source)) { + struct sst_ids *ids = p->source->priv; + + dev_dbg(dai->dev, "send gain for widget=%s\n", + p->source->name); + mutex_lock(&drv->lock); + sst_set_pipe_gain(ids, drv, mute); + mutex_unlock(&drv->lock); + } + } + } + return 0; +} + +/** + * sst_fill_module_list - populate the list of modules/gains for a pipe + * + * + * Fills the widget pointer in the kcontrol private data, and also fills the + * kcontrol pointer in the widget private data. + * + * Widget pointer is used to send the algo/gain in the .put() handler if the + * widget is powerd on. + * + * Kcontrol pointer is used to send the algo/gain in the widget power ON/OFF + * event handler. Each widget (pipe) has multiple algos stored in the algo_list. + */ +static int sst_fill_module_list(struct snd_kcontrol *kctl, + struct snd_soc_dapm_widget *w, int type) +{ + struct sst_module *module = NULL; + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct sst_ids *ids = w->priv; + int ret = 0; + + module = devm_kzalloc(c->dev, sizeof(*module), GFP_KERNEL); + if (!module) + return -ENOMEM; + + if (type == SST_MODULE_GAIN) { + struct sst_gain_mixer_control *mc = (void *)kctl->private_value; + + mc->w = w; + module->kctl = kctl; + list_add_tail(&module->node, &ids->gain_list); + } else if (type == SST_MODULE_ALGO) { + struct sst_algo_control *bc = (void *)kctl->private_value; + + bc->w = w; + module->kctl = kctl; + list_add_tail(&module->node, &ids->algo_list); + } else { + dev_err(c->dev, "invoked for unknown type %d module %s", + type, kctl->id.name); + ret = -EINVAL; + } + + return ret; +} + +/** + * sst_fill_widget_module_info - fill list of gains/algos for the pipe + * @widget: pipe modelled as a DAPM widget + * + * Fill the list of gains/algos for the widget by looking at all the card + * controls and comparing the name of the widget with the first part of control + * name. First part of control name contains the pipe name (widget name). + */ +static int sst_fill_widget_module_info(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component) +{ + struct snd_kcontrol *kctl; + int index, ret = 0; + struct snd_card *card = component->card->snd_card; + char *idx; + + down_read(&card->controls_rwsem); + + list_for_each_entry(kctl, &card->controls, list) { + idx = strchr(kctl->id.name, ' '); + if (idx == NULL) + continue; + index = idx - (char*)kctl->id.name; + if (strncmp(kctl->id.name, w->name, index)) + continue; + + if (strstr(kctl->id.name, "Volume")) + ret = sst_fill_module_list(kctl, w, SST_MODULE_GAIN); + + else if (strstr(kctl->id.name, "params")) + ret = sst_fill_module_list(kctl, w, SST_MODULE_ALGO); + + else if (strstr(kctl->id.name, "Switch") && + strstr(kctl->id.name, "Gain")) { + struct sst_gain_mixer_control *mc = + (void *)kctl->private_value; + + mc->w = w; + + } else if (strstr(kctl->id.name, "interleaver")) { + struct sst_enum *e = (void *)kctl->private_value; + + e->w = w; + + } else if (strstr(kctl->id.name, "deinterleaver")) { + struct sst_enum *e = (void *)kctl->private_value; + + e->w = w; + } + + if (ret < 0) { + up_read(&card->controls_rwsem); + return ret; + } + } + + up_read(&card->controls_rwsem); + return 0; +} + +/** + * sst_fill_linked_widgets - fill the parent pointer for the linked widget + */ +static void sst_fill_linked_widgets(struct snd_soc_component *component, + struct sst_ids *ids) +{ + struct snd_soc_dapm_widget *w; + unsigned int len = strlen(ids->parent_wname); + + list_for_each_entry(w, &component->card->widgets, list) { + if (!strncmp(ids->parent_wname, w->name, len)) { + ids->parent_w = w; + break; + } + } +} + +/** + * sst_map_modules_to_pipe - fill algo/gains list for all pipes + */ +static int sst_map_modules_to_pipe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_widget *w; + int ret = 0; + + list_for_each_entry(w, &component->card->widgets, list) { + if (is_sst_dapm_widget(w) && (w->priv)) { + struct sst_ids *ids = w->priv; + + dev_dbg(component->dev, "widget type=%d name=%s\n", + w->id, w->name); + INIT_LIST_HEAD(&ids->algo_list); + INIT_LIST_HEAD(&ids->gain_list); + ret = sst_fill_widget_module_info(w, component); + + if (ret < 0) + return ret; + + /* fill linked widgets */ + if (ids->parent_wname != NULL) + sst_fill_linked_widgets(component, ids); + } + } + return 0; +} + +int sst_dsp_init_v2_dpcm(struct snd_soc_component *component) +{ + int i, ret = 0; + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct sst_data *drv = snd_soc_component_get_drvdata(component); + unsigned int gains = ARRAY_SIZE(sst_gain_controls)/3; + + drv->byte_stream = devm_kzalloc(component->dev, + SST_MAX_BIN_BYTES, GFP_KERNEL); + if (!drv->byte_stream) + return -ENOMEM; + + snd_soc_dapm_new_controls(dapm, sst_dapm_widgets, + ARRAY_SIZE(sst_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, intercon, + ARRAY_SIZE(intercon)); + snd_soc_dapm_new_widgets(dapm->card); + + for (i = 0; i < gains; i++) { + sst_gains[i].mute = SST_GAIN_MUTE_DEFAULT; + sst_gains[i].l_gain = SST_GAIN_VOLUME_DEFAULT; + sst_gains[i].r_gain = SST_GAIN_VOLUME_DEFAULT; + sst_gains[i].ramp_duration = SST_GAIN_RAMP_DURATION_DEFAULT; + } + + ret = snd_soc_add_component_controls(component, sst_gain_controls, + ARRAY_SIZE(sst_gain_controls)); + if (ret) + return ret; + + /* Initialize algo control params */ + ret = sst_algo_control_init(component->dev); + if (ret) + return ret; + ret = snd_soc_add_component_controls(component, sst_algo_controls, + ARRAY_SIZE(sst_algo_controls)); + if (ret) + return ret; + + ret = snd_soc_add_component_controls(component, sst_slot_controls, + ARRAY_SIZE(sst_slot_controls)); + if (ret) + return ret; + + ret = sst_map_modules_to_pipe(component); + + return ret; +} diff --git a/sound/soc/intel/atom/sst-atom-controls.h b/sound/soc/intel/atom/sst-atom-controls.h new file mode 100644 index 000000000..351d81469 --- /dev/null +++ b/sound/soc/intel/atom/sst-atom-controls.h @@ -0,0 +1,884 @@ +/* + * sst-atom-controls.h - Intel MID Platform driver header file + * + * Copyright (C) 2013-14 Intel Corp + * Author: Ramesh Babu + * Omair M Abdullah + * Samreen Nilofer + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef __SST_ATOM_CONTROLS_H__ +#define __SST_ATOM_CONTROLS_H__ + +#include +#include + +enum { + MERR_DPCM_AUDIO = 0, + MERR_DPCM_DEEP_BUFFER, + MERR_DPCM_COMPR, +}; + +/* define a bit for each mixer input */ +#define SST_MIX_IP(x) (x) + +#define SST_IP_MODEM SST_MIX_IP(0) +#define SST_IP_BT SST_MIX_IP(1) +#define SST_IP_CODEC0 SST_MIX_IP(2) +#define SST_IP_CODEC1 SST_MIX_IP(3) +#define SST_IP_LOOP0 SST_MIX_IP(4) +#define SST_IP_LOOP1 SST_MIX_IP(5) +#define SST_IP_LOOP2 SST_MIX_IP(6) +#define SST_IP_PROBE SST_MIX_IP(7) +#define SST_IP_VOIP SST_MIX_IP(12) +#define SST_IP_PCM0 SST_MIX_IP(13) +#define SST_IP_PCM1 SST_MIX_IP(14) +#define SST_IP_MEDIA0 SST_MIX_IP(17) +#define SST_IP_MEDIA1 SST_MIX_IP(18) +#define SST_IP_MEDIA2 SST_MIX_IP(19) +#define SST_IP_MEDIA3 SST_MIX_IP(20) + +#define SST_IP_LAST SST_IP_MEDIA3 + +#define SST_SWM_INPUT_COUNT (SST_IP_LAST + 1) +#define SST_CMD_SWM_MAX_INPUTS 6 + +#define SST_PATH_ID_SHIFT 8 +#define SST_DEFAULT_LOCATION_ID 0xFFFF +#define SST_DEFAULT_CELL_NBR 0xFF +#define SST_DEFAULT_MODULE_ID 0xFFFF + +/* + * Audio DSP Path Ids. Specified by the audio DSP FW + */ +enum sst_path_index { + SST_PATH_INDEX_MODEM_OUT = (0x00 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_CODEC_OUT0 = (0x02 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_CODEC_OUT1 = (0x03 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_SPROT_LOOP_OUT = (0x04 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP1_OUT = (0x05 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP2_OUT = (0x06 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_VOIP_OUT = (0x0C << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM0_OUT = (0x0D << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM1_OUT = (0x0E << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM2_OUT = (0x0F << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_MEDIA0_OUT = (0x12 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA1_OUT = (0x13 << SST_PATH_ID_SHIFT), + + + /* Start of input paths */ + SST_PATH_INDEX_MODEM_IN = (0x80 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_CODEC_IN0 = (0x82 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_CODEC_IN1 = (0x83 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_SPROT_LOOP_IN = (0x84 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP1_IN = (0x85 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA_LOOP2_IN = (0x86 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_VOIP_IN = (0x8C << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_PCM0_IN = (0x8D << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_PCM1_IN = (0x8E << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_MEDIA0_IN = (0x8F << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA1_IN = (0x90 << SST_PATH_ID_SHIFT), + SST_PATH_INDEX_MEDIA2_IN = (0x91 << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_MEDIA3_IN = (0x9C << SST_PATH_ID_SHIFT), + + SST_PATH_INDEX_RESERVED = (0xFF << SST_PATH_ID_SHIFT), +}; + +/* + * path IDs + */ +enum sst_swm_inputs { + SST_SWM_IN_MODEM = (SST_PATH_INDEX_MODEM_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_CODEC0 = (SST_PATH_INDEX_CODEC_IN0 | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_CODEC1 = (SST_PATH_INDEX_CODEC_IN1 | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_VOIP = (SST_PATH_INDEX_VOIP_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_PCM0 = (SST_PATH_INDEX_PCM0_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_PCM1 = (SST_PATH_INDEX_PCM1_IN | SST_DEFAULT_CELL_NBR), + SST_SWM_IN_MEDIA0 = (SST_PATH_INDEX_MEDIA0_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_MEDIA1 = (SST_PATH_INDEX_MEDIA1_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_MEDIA2 = (SST_PATH_INDEX_MEDIA2_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_MEDIA3 = (SST_PATH_INDEX_MEDIA3_IN | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_IN_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR) +}; + +/* + * path IDs + */ +enum sst_swm_outputs { + SST_SWM_OUT_MODEM = (SST_PATH_INDEX_MODEM_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_CODEC0 = (SST_PATH_INDEX_CODEC_OUT0 | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_CODEC1 = (SST_PATH_INDEX_CODEC_OUT1 | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_SPROT_LOOP = (SST_PATH_INDEX_SPROT_LOOP_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_MEDIA_LOOP1 = (SST_PATH_INDEX_MEDIA_LOOP1_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_MEDIA_LOOP2 = (SST_PATH_INDEX_MEDIA_LOOP2_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_VOIP = (SST_PATH_INDEX_VOIP_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_PCM0 = (SST_PATH_INDEX_PCM0_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_PCM1 = (SST_PATH_INDEX_PCM1_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_PCM2 = (SST_PATH_INDEX_PCM2_OUT | SST_DEFAULT_CELL_NBR), + SST_SWM_OUT_MEDIA0 = (SST_PATH_INDEX_MEDIA0_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_OUT_MEDIA1 = (SST_PATH_INDEX_MEDIA1_OUT | SST_DEFAULT_CELL_NBR), /* Part of Media Mixer */ + SST_SWM_OUT_END = (SST_PATH_INDEX_RESERVED | SST_DEFAULT_CELL_NBR), +}; + +enum sst_ipc_msg { + SST_IPC_IA_CMD = 1, + SST_IPC_IA_SET_PARAMS, + SST_IPC_IA_GET_PARAMS, +}; + +enum sst_cmd_type { + SST_CMD_BYTES_SET = 1, + SST_CMD_BYTES_GET = 2, +}; + +enum sst_task { + SST_TASK_SBA = 1, + SST_TASK_MMX = 3, +}; + +enum sst_type { + SST_TYPE_CMD = 1, + SST_TYPE_PARAMS, +}; + +enum sst_flag { + SST_FLAG_BLOCKED = 1, + SST_FLAG_NONBLOCK, +}; + +/* + * Enumeration for indexing the gain cells in VB_SET_GAIN DSP command + */ +enum sst_gain_index { + /* GAIN IDs for SB task start here */ + SST_GAIN_INDEX_CODEC_OUT0, + SST_GAIN_INDEX_CODEC_OUT1, + SST_GAIN_INDEX_CODEC_IN0, + SST_GAIN_INDEX_CODEC_IN1, + + SST_GAIN_INDEX_SPROT_LOOP_OUT, + SST_GAIN_INDEX_MEDIA_LOOP1_OUT, + SST_GAIN_INDEX_MEDIA_LOOP2_OUT, + + SST_GAIN_INDEX_PCM0_IN_LEFT, + SST_GAIN_INDEX_PCM0_IN_RIGHT, + + SST_GAIN_INDEX_PCM1_OUT_LEFT, + SST_GAIN_INDEX_PCM1_OUT_RIGHT, + SST_GAIN_INDEX_PCM1_IN_LEFT, + SST_GAIN_INDEX_PCM1_IN_RIGHT, + SST_GAIN_INDEX_PCM2_OUT_LEFT, + + SST_GAIN_INDEX_PCM2_OUT_RIGHT, + SST_GAIN_INDEX_VOIP_OUT, + SST_GAIN_INDEX_VOIP_IN, + + /* Gain IDs for MMX task start here */ + SST_GAIN_INDEX_MEDIA0_IN_LEFT, + SST_GAIN_INDEX_MEDIA0_IN_RIGHT, + SST_GAIN_INDEX_MEDIA1_IN_LEFT, + SST_GAIN_INDEX_MEDIA1_IN_RIGHT, + + SST_GAIN_INDEX_MEDIA2_IN_LEFT, + SST_GAIN_INDEX_MEDIA2_IN_RIGHT, + + SST_GAIN_INDEX_GAIN_END +}; + +/* + * Audio DSP module IDs specified by FW spec + * TODO: Update with all modules + */ +enum sst_module_id { + SST_MODULE_ID_PCM = 0x0001, + SST_MODULE_ID_MP3 = 0x0002, + SST_MODULE_ID_MP24 = 0x0003, + SST_MODULE_ID_AAC = 0x0004, + SST_MODULE_ID_AACP = 0x0005, + SST_MODULE_ID_EAACP = 0x0006, + SST_MODULE_ID_WMA9 = 0x0007, + SST_MODULE_ID_WMA10 = 0x0008, + SST_MODULE_ID_WMA10P = 0x0009, + SST_MODULE_ID_RA = 0x000A, + SST_MODULE_ID_DDAC3 = 0x000B, + SST_MODULE_ID_TRUE_HD = 0x000C, + SST_MODULE_ID_HD_PLUS = 0x000D, + + SST_MODULE_ID_SRC = 0x0064, + SST_MODULE_ID_DOWNMIX = 0x0066, + SST_MODULE_ID_GAIN_CELL = 0x0067, + SST_MODULE_ID_SPROT = 0x006D, + SST_MODULE_ID_BASS_BOOST = 0x006E, + SST_MODULE_ID_STEREO_WDNG = 0x006F, + SST_MODULE_ID_AV_REMOVAL = 0x0070, + SST_MODULE_ID_MIC_EQ = 0x0071, + SST_MODULE_ID_SPL = 0x0072, + SST_MODULE_ID_ALGO_VTSV = 0x0073, + SST_MODULE_ID_NR = 0x0076, + SST_MODULE_ID_BWX = 0x0077, + SST_MODULE_ID_DRP = 0x0078, + SST_MODULE_ID_MDRP = 0x0079, + + SST_MODULE_ID_ANA = 0x007A, + SST_MODULE_ID_AEC = 0x007B, + SST_MODULE_ID_NR_SNS = 0x007C, + SST_MODULE_ID_SER = 0x007D, + SST_MODULE_ID_AGC = 0x007E, + + SST_MODULE_ID_CNI = 0x007F, + SST_MODULE_ID_CONTEXT_ALGO_AWARE = 0x0080, + SST_MODULE_ID_FIR_24 = 0x0081, + SST_MODULE_ID_IIR_24 = 0x0082, + + SST_MODULE_ID_ASRC = 0x0083, + SST_MODULE_ID_TONE_GEN = 0x0084, + SST_MODULE_ID_BMF = 0x0086, + SST_MODULE_ID_EDL = 0x0087, + SST_MODULE_ID_GLC = 0x0088, + + SST_MODULE_ID_FIR_16 = 0x0089, + SST_MODULE_ID_IIR_16 = 0x008A, + SST_MODULE_ID_DNR = 0x008B, + + SST_MODULE_ID_VIRTUALIZER = 0x008C, + SST_MODULE_ID_VISUALIZATION = 0x008D, + SST_MODULE_ID_LOUDNESS_OPTIMIZER = 0x008E, + SST_MODULE_ID_REVERBERATION = 0x008F, + + SST_MODULE_ID_CNI_TX = 0x0090, + SST_MODULE_ID_REF_LINE = 0x0091, + SST_MODULE_ID_VOLUME = 0x0092, + SST_MODULE_ID_FILT_DCR = 0x0094, + SST_MODULE_ID_SLV = 0x009A, + SST_MODULE_ID_NLF = 0x009B, + SST_MODULE_ID_TNR = 0x009C, + SST_MODULE_ID_WNR = 0x009D, + + SST_MODULE_ID_LOG = 0xFF00, + + SST_MODULE_ID_TASK = 0xFFFF, +}; + +enum sst_cmd { + SBA_IDLE = 14, + SBA_VB_SET_SPEECH_PATH = 26, + MMX_SET_GAIN = 33, + SBA_VB_SET_GAIN = 33, + FBA_VB_RX_CNI = 35, + MMX_SET_GAIN_TIMECONST = 36, + SBA_VB_SET_TIMECONST = 36, + SBA_VB_START = 85, + SBA_SET_SWM = 114, + SBA_SET_MDRP = 116, + SBA_HW_SET_SSP = 117, + SBA_SET_MEDIA_LOOP_MAP = 118, + SBA_SET_MEDIA_PATH = 119, + MMX_SET_MEDIA_PATH = 119, + SBA_VB_LPRO = 126, + SBA_VB_SET_FIR = 128, + SBA_VB_SET_IIR = 129, + SBA_SET_SSP_SLOT_MAP = 130, +}; + +enum sst_dsp_switch { + SST_SWITCH_OFF = 0, + SST_SWITCH_ON = 3, +}; + +enum sst_path_switch { + SST_PATH_OFF = 0, + SST_PATH_ON = 1, +}; + +enum sst_swm_state { + SST_SWM_OFF = 0, + SST_SWM_ON = 3, +}; + +#define SST_FILL_LOCATION_IDS(dst, cell_idx, pipe_id) do { \ + dst.location_id.p.cell_nbr_idx = (cell_idx); \ + dst.location_id.p.path_id = (pipe_id); \ + } while (0) +#define SST_FILL_LOCATION_ID(dst, loc_id) (\ + dst.location_id.f = (loc_id)) +#define SST_FILL_MODULE_ID(dst, mod_id) (\ + dst.module_id = (mod_id)) + +#define SST_FILL_DESTINATION1(dst, id) do { \ + SST_FILL_LOCATION_ID(dst, (id) & 0xFFFF); \ + SST_FILL_MODULE_ID(dst, ((id) & 0xFFFF0000) >> 16); \ + } while (0) +#define SST_FILL_DESTINATION2(dst, loc_id, mod_id) do { \ + SST_FILL_LOCATION_ID(dst, loc_id); \ + SST_FILL_MODULE_ID(dst, mod_id); \ + } while (0) +#define SST_FILL_DESTINATION3(dst, cell_idx, path_id, mod_id) do { \ + SST_FILL_LOCATION_IDS(dst, cell_idx, path_id); \ + SST_FILL_MODULE_ID(dst, mod_id); \ + } while (0) + +#define SST_FILL_DESTINATION(level, dst, ...) \ + SST_FILL_DESTINATION##level(dst, __VA_ARGS__) +#define SST_FILL_DEFAULT_DESTINATION(dst) \ + SST_FILL_DESTINATION(2, dst, SST_DEFAULT_LOCATION_ID, SST_DEFAULT_MODULE_ID) + +struct sst_destination_id { + union sst_location_id { + struct { + u8 cell_nbr_idx; /* module index */ + u8 path_id; /* pipe_id */ + } __packed p; /* part */ + u16 f; /* full */ + } __packed location_id; + u16 module_id; +} __packed; +struct sst_dsp_header { + struct sst_destination_id dst; + u16 command_id; + u16 length; +} __packed; + +/* + * + * Common Commands + * + */ +struct sst_cmd_generic { + struct sst_dsp_header header; +} __packed; + +struct swm_input_ids { + struct sst_destination_id input_id; +} __packed; + +struct sst_cmd_set_swm { + struct sst_dsp_header header; + struct sst_destination_id output_id; + u16 switch_state; + u16 nb_inputs; + struct swm_input_ids input[SST_CMD_SWM_MAX_INPUTS]; +} __packed; + +struct sst_cmd_set_media_path { + struct sst_dsp_header header; + u16 switch_state; +} __packed; + +struct pcm_cfg { + u8 s_length:2; + u8 rate:3; + u8 format:3; +} __packed; + +struct sst_cmd_set_speech_path { + struct sst_dsp_header header; + u16 switch_state; + struct { + u16 rsvd:8; + struct pcm_cfg cfg; + } config; +} __packed; + +struct gain_cell { + struct sst_destination_id dest; + s16 cell_gain_left; + s16 cell_gain_right; + u16 gain_time_constant; +} __packed; + +#define NUM_GAIN_CELLS 1 +struct sst_cmd_set_gain_dual { + struct sst_dsp_header header; + u16 gain_cell_num; + struct gain_cell cell_gains[NUM_GAIN_CELLS]; +} __packed; +struct sst_cmd_set_params { + struct sst_destination_id dst; + u16 command_id; + char params[0]; +} __packed; + + +struct sst_cmd_sba_vb_start { + struct sst_dsp_header header; +} __packed; + +union sba_media_loop_params { + struct { + u16 rsvd:8; + struct pcm_cfg cfg; + } part; + u16 full; +} __packed; + +struct sst_cmd_sba_set_media_loop_map { + struct sst_dsp_header header; + u16 switch_state; + union sba_media_loop_params param; + u16 map; +} __packed; + +struct sst_cmd_tone_stop { + struct sst_dsp_header header; + u16 switch_state; +} __packed; + +enum sst_ssp_mode { + SSP_MODE_MASTER = 0, + SSP_MODE_SLAVE = 1, +}; + +enum sst_ssp_pcm_mode { + SSP_PCM_MODE_NORMAL = 0, + SSP_PCM_MODE_NETWORK = 1, +}; + +enum sst_ssp_duplex { + SSP_DUPLEX = 0, + SSP_RX = 1, + SSP_TX = 2, +}; + +enum sst_ssp_fs_frequency { + SSP_FS_8_KHZ = 0, + SSP_FS_16_KHZ = 1, + SSP_FS_44_1_KHZ = 2, + SSP_FS_48_KHZ = 3, +}; + +enum sst_ssp_fs_polarity { + SSP_FS_ACTIVE_LOW = 0, + SSP_FS_ACTIVE_HIGH = 1, +}; + +enum sst_ssp_protocol { + SSP_MODE_PCM = 0, + SSP_MODE_I2S = 1, +}; + +enum sst_ssp_port_id { + SSP_MODEM = 0, + SSP_BT = 1, + SSP_FM = 2, + SSP_CODEC = 3, +}; + +struct sst_cmd_sba_hw_set_ssp { + struct sst_dsp_header header; + u16 selection; /* 0:SSP0(def), 1:SSP1, 2:SSP2 */ + + u16 switch_state; + + u16 nb_bits_per_slots:6; /* 0-32 bits, 24 (def) */ + u16 nb_slots:4; /* 0-8: slots per frame */ + u16 mode:3; /* 0:Master, 1: Slave */ + u16 duplex:3; + + u16 active_tx_slot_map:8; /* Bit map, 0:off, 1:on */ + u16 reserved1:8; + + u16 active_rx_slot_map:8; /* Bit map 0: Off, 1:On */ + u16 reserved2:8; + + u16 frame_sync_frequency; + + u16 frame_sync_polarity:8; + u16 data_polarity:8; + + u16 frame_sync_width; /* 1 to N clocks */ + u16 ssp_protocol:8; + u16 start_delay:8; /* Start delay in terms of clock ticks */ +} __packed; + +#define SST_MAX_TDM_SLOTS 8 + +struct sst_param_sba_ssp_slot_map { + struct sst_dsp_header header; + + u16 param_id; + u16 param_len; + u16 ssp_index; + + u8 rx_slot_map[SST_MAX_TDM_SLOTS]; + u8 tx_slot_map[SST_MAX_TDM_SLOTS]; +} __packed; + +enum { + SST_PROBE_EXTRACTOR = 0, + SST_PROBE_INJECTOR = 1, +}; + +/**** widget defines *****/ + +#define SST_MODULE_GAIN 1 +#define SST_MODULE_ALGO 2 + +#define SST_FMT_MONO 0 +#define SST_FMT_STEREO 3 + +/* physical SSP numbers */ +enum { + SST_SSP0 = 0, + SST_SSP1, + SST_SSP2, + SST_SSP_LAST = SST_SSP2, +}; + +#define SST_NUM_SSPS (SST_SSP_LAST + 1) /* physical SSPs */ +#define SST_MAX_SSP_MUX 2 /* single SSP muxed between pipes */ +#define SST_MAX_SSP_DOMAINS 2 /* domains present in each pipe */ + +struct sst_module { + struct snd_kcontrol *kctl; + struct list_head node; +}; + +struct sst_ssp_config { + u8 ssp_id; + u8 bits_per_slot; + u8 slots; + u8 ssp_mode; + u8 pcm_mode; + u8 duplex; + u8 ssp_protocol; + u8 fs_frequency; + u8 active_slot_map; + u8 start_delay; + u16 fs_width; + u8 frame_sync_polarity; + u8 data_polarity; +}; + +struct sst_ssp_cfg { + const u8 ssp_number; + const int *mux_shift; + const int (*domain_shift)[SST_MAX_SSP_MUX]; + const struct sst_ssp_config (*ssp_config)[SST_MAX_SSP_MUX][SST_MAX_SSP_DOMAINS]; +}; + +struct sst_ids { + u16 location_id; + u16 module_id; + u8 task_id; + u8 format; + u8 reg; + const char *parent_wname; + struct snd_soc_dapm_widget *parent_w; + struct list_head algo_list; + struct list_head gain_list; + const struct sst_pcm_format *pcm_fmt; +}; + + +#define SST_AIF_IN(wname, wevent) \ +{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_AIF_OUT(wname, wevent) \ +{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_INPUT(wname, wevent) \ +{ .id = snd_soc_dapm_input, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_OUTPUT(wname, wevent) \ +{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .task_id = 0, .location_id = 0 } \ +} + +#define SST_DAPM_OUTPUT(wname, wloc_id, wtask_id, wformat, wevent) \ +{ .id = snd_soc_dapm_output, .name = wname, .sname = NULL, \ + .reg = SND_SOC_NOPM, .shift = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \ + .priv = (void *)&(struct sst_ids) { .location_id = wloc_id, .task_id = wtask_id,\ + .pcm_fmt = wformat, } \ +} + +#define SST_PATH(wname, wtask, wloc_id, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, } \ +} + +#define SST_LINKED_PATH(wname, wtask, wloc_id, linked_wname, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .on_val = 1, .off_val = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .parent_wname = linked_wname} \ +} + +#define SST_PATH_MEDIA_LOOP(wname, wtask, wloc_id, wformat, wevent, wflags) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = NULL, .num_kcontrols = 0, \ + .event = wevent, .event_flags = wflags, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .format = wformat,} \ +} + +/* output is triggered before input */ +#define SST_PATH_INPUT(name, task_id, loc_id, event) \ + SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +#define SST_PATH_LINKED_INPUT(name, task_id, loc_id, linked_wname, event) \ + SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD) + +#define SST_PATH_OUTPUT(name, task_id, loc_id, event) \ + SST_PATH(name, task_id, loc_id, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + +#define SST_PATH_LINKED_OUTPUT(name, task_id, loc_id, linked_wname, event) \ + SST_LINKED_PATH(name, task_id, loc_id, linked_wname, event, \ + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + +#define SST_PATH_MEDIA_LOOP_OUTPUT(name, task_id, loc_id, format, event) \ + SST_PATH_MEDIA_LOOP(name, task_id, loc_id, format, event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD) + + +#define SST_SWM_MIXER(wname, wreg, wtask, wloc_id, wcontrols, wevent) \ +{ .id = snd_soc_dapm_mixer, .name = wname, .reg = SND_SOC_NOPM, .shift = 0, \ + .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols),\ + .event = wevent, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | \ + SND_SOC_DAPM_POST_REG, \ + .priv = (void *)&(struct sst_ids) { .task_id = wtask, .location_id = wloc_id, \ + .reg = wreg } \ +} + +enum sst_gain_kcontrol_type { + SST_GAIN_TLV, + SST_GAIN_MUTE, + SST_GAIN_RAMP_DURATION, +}; + +struct sst_gain_mixer_control { + bool stereo; + enum sst_gain_kcontrol_type type; + struct sst_gain_value *gain_val; + int max; + int min; + u16 instance_id; + u16 module_id; + u16 pipe_id; + u16 task_id; + char pname[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_soc_dapm_widget *w; +}; + +struct sst_gain_value { + u16 ramp_duration; + s16 l_gain; + s16 r_gain; + bool mute; +}; +#define SST_GAIN_VOLUME_DEFAULT (-1440) +#define SST_GAIN_RAMP_DURATION_DEFAULT 5 /* timeconstant */ +#define SST_GAIN_MUTE_DEFAULT true + +#define SST_GAIN_KCONTROL_TLV(xname, xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, tlv_array, xgain_val, \ + xmin, xmax, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = sst_gain_ctl_info,\ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = true, .max = xmax, .min = xmin, .type = SST_GAIN_TLV, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} + +#define SST_GAIN_KCONTROL_INT(xname, xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, xtype, xgain_val, \ + xmin, xmax, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sst_gain_ctl_info, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = false, .max = xmax, .min = xmin, .type = xtype, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} + +#define SST_GAIN_KCONTROL_BOOL(xname, xhandler_get, xhandler_put,\ + xmod, xpipe, xinstance, xtask, xgain_val, xpname) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_bool_ext, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long)&(struct sst_gain_mixer_control) \ + { .stereo = false, .type = SST_GAIN_MUTE, \ + .module_id = xmod, .pipe_id = xpipe, .task_id = xtask,\ + .instance_id = xinstance, .gain_val = xgain_val, .pname = xpname} +#define SST_CONTROL_NAME(xpname, xmname, xinstance, xtype) \ + xpname " " xmname " " #xinstance " " xtype + +#define SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, xtype, xsubmodule) \ + xpname " " xmname " " #xinstance " " xtype " " xsubmodule + +/* + * 3 Controls for each Gain module + * e.g. - pcm0_in Gain 0 Volume + * - pcm0_in Gain 0 Ramp Delay + * - pcm0_in Gain 0 Switch + */ +#define SST_GAIN_KCONTROLS(xpname, xmname, xmin_gain, xmax_gain, xmin_tc, xmax_tc, \ + xhandler_get, xhandler_put, \ + xmod, xpipe, xinstance, xtask, tlv_array, xgain_val) \ + { SST_GAIN_KCONTROL_INT(SST_CONTROL_NAME(xpname, xmname, xinstance, "Ramp Delay"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, SST_GAIN_RAMP_DURATION, \ + xgain_val, xmin_tc, xmax_tc, xpname) }, \ + { SST_GAIN_KCONTROL_BOOL(SST_CONTROL_NAME(xpname, xmname, xinstance, "Switch"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, \ + xgain_val, xpname) } ,\ + { SST_GAIN_KCONTROL_TLV(SST_CONTROL_NAME(xpname, xmname, xinstance, "Volume"), \ + xhandler_get, xhandler_put, xmod, xpipe, xinstance, xtask, tlv_array, \ + xgain_val, xmin_gain, xmax_gain, xpname) } + +#define SST_GAIN_TC_MIN 5 +#define SST_GAIN_TC_MAX 5000 +#define SST_GAIN_MIN_VALUE -1440 /* in 0.1 DB units */ +#define SST_GAIN_MAX_VALUE 360 + +enum sst_algo_kcontrol_type { + SST_ALGO_PARAMS, + SST_ALGO_BYPASS, +}; + +struct sst_algo_control { + enum sst_algo_kcontrol_type type; + int max; + u16 module_id; + u16 pipe_id; + u16 task_id; + u16 cmd_id; + bool bypass; + unsigned char *params; + struct snd_soc_dapm_widget *w; +}; + +/* size of the control = size of params + size of length field */ +#define SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, xmod, xtask, xcmd) \ + (struct sst_algo_control){ \ + .max = xcount + sizeof(u16), .type = xtype, .module_id = xmod, \ + .pipe_id = xpipe, .task_id = xtask, .cmd_id = xcmd, \ + } + +#define SST_ALGO_KCONTROL(xname, xcount, xmod, xpipe, \ + xtask, xcmd, xtype, xinfo, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = xinfo, .get = xget, .put = xput, \ + .private_value = (unsigned long)& \ + SST_ALGO_CTL_VALUE(xcount, xtype, xpipe, \ + xmod, xtask, xcmd), \ +} + +#define SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, \ + xpipe, xinstance, xtask, xcmd) \ + SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "params"), \ + xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \ + sst_algo_bytes_ctl_info, \ + sst_algo_control_get, sst_algo_control_set) + +#define SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask) \ + SST_ALGO_KCONTROL(SST_CONTROL_NAME(xpname, xmname, xinstance, "bypass"), \ + 0, xmod, xpipe, xtask, 0, SST_ALGO_BYPASS, \ + snd_soc_info_bool_ext, \ + sst_algo_control_get, sst_algo_control_set) + +#define SST_ALGO_BYPASS_PARAMS(xpname, xmname, xcount, xmod, xpipe, \ + xinstance, xtask, xcmd) \ + SST_ALGO_KCONTROL_BOOL(xpname, xmname, xmod, xpipe, xinstance, xtask), \ + SST_ALGO_KCONTROL_BYTES(xpname, xmname, xcount, xmod, xpipe, xinstance, xtask, xcmd) + +#define SST_COMBO_ALGO_KCONTROL_BYTES(xpname, xmname, xsubmod, xcount, xmod, \ + xpipe, xinstance, xtask, xcmd) \ + SST_ALGO_KCONTROL(SST_COMBO_CONTROL_NAME(xpname, xmname, xinstance, "params", \ + xsubmod), \ + xcount, xmod, xpipe, xtask, xcmd, SST_ALGO_PARAMS, \ + sst_algo_bytes_ctl_info, \ + sst_algo_control_get, sst_algo_control_set) + + +struct sst_enum { + bool tx; + unsigned short reg; + unsigned int max; + const char * const *texts; + struct snd_soc_dapm_widget *w; +}; + +/* only 4 slots/channels supported atm */ +#define SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts) \ + (struct sst_enum){ .reg = s_ch_no, .tx = is_tx, .max = 4+1, .texts = xtexts, } + +#define SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name) \ + xpname " " xmname " " s_ch_name + +#define SST_SSP_SLOT_CTL(xpname, xmname, s_ch_name, s_ch_no, is_tx, xtexts, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = SST_SLOT_CTL_NAME(xpname, xmname, s_ch_name), \ + .info = sst_slot_enum_info, \ + .get = xget, .put = xput, \ + .private_value = (unsigned long)&SST_SSP_SLOT_ENUM(s_ch_no, is_tx, xtexts), \ +} + +#define SST_MUX_CTL_NAME(xpname, xinstance) \ + xpname " " #xinstance + +#define SST_SSP_MUX_ENUM(xreg, xshift, xtexts) \ + (struct soc_enum) SOC_ENUM_DOUBLE(xreg, xshift, xshift, ARRAY_SIZE(xtexts), xtexts) + +#define SST_SSP_MUX_CTL(xpname, xinstance, xreg, xshift, xtexts) \ + SOC_DAPM_ENUM(SST_MUX_CTL_NAME(xpname, xinstance), \ + SST_SSP_MUX_ENUM(xreg, xshift, xtexts)) + +int sst_fill_ssp_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width); +int sst_fill_ssp_config(struct snd_soc_dai *dai, unsigned int fmt); +void sst_fill_ssp_defaults(struct snd_soc_dai *dai); + +#endif diff --git a/sound/soc/intel/atom/sst-mfld-dsp.h b/sound/soc/intel/atom/sst-mfld-dsp.h new file mode 100644 index 000000000..425726315 --- /dev/null +++ b/sound/soc/intel/atom/sst-mfld-dsp.h @@ -0,0 +1,533 @@ +#ifndef __SST_MFLD_DSP_H__ +#define __SST_MFLD_DSP_H__ +/* + * sst_mfld_dsp.h - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define SST_MAX_BIN_BYTES 1024 + +#define MAX_DBG_RW_BYTES 80 +#define MAX_NUM_SCATTER_BUFFERS 8 +#define MAX_LOOP_BACK_DWORDS 8 +/* IPC base address and mailbox, timestamp offsets */ +#define SST_MAILBOX_SIZE 0x0400 +#define SST_MAILBOX_SEND 0x0000 +#define SST_TIME_STAMP 0x1800 +#define SST_TIME_STAMP_MRFLD 0x800 +#define SST_RESERVED_OFFSET 0x1A00 +#define SST_SCU_LPE_MAILBOX 0x1000 +#define SST_LPE_SCU_MAILBOX 0x1400 +#define SST_SCU_LPE_LOG_BUF (SST_SCU_LPE_MAILBOX+16) +#define PROCESS_MSG 0x80 + +/* Message ID's for IPC messages */ +/* Bits B7: SST or IA/SC ; B6-B4: Msg Category; B3-B0: Msg Type */ + +/* I2L Firmware/Codec Download msgs */ +#define IPC_IA_PREP_LIB_DNLD 0x01 +#define IPC_IA_LIB_DNLD_CMPLT 0x02 +#define IPC_IA_GET_FW_VERSION 0x04 +#define IPC_IA_GET_FW_BUILD_INF 0x05 +#define IPC_IA_GET_FW_INFO 0x06 +#define IPC_IA_GET_FW_CTXT 0x07 +#define IPC_IA_SET_FW_CTXT 0x08 +#define IPC_IA_PREPARE_SHUTDOWN 0x31 +/* I2L Codec Config/control msgs */ +#define IPC_PREP_D3 0x10 +#define IPC_IA_SET_CODEC_PARAMS 0x10 +#define IPC_IA_GET_CODEC_PARAMS 0x11 +#define IPC_IA_SET_PPP_PARAMS 0x12 +#define IPC_IA_GET_PPP_PARAMS 0x13 +#define IPC_SST_PERIOD_ELAPSED_MRFLD 0xA +#define IPC_IA_ALG_PARAMS 0x1A +#define IPC_IA_TUNING_PARAMS 0x1B +#define IPC_IA_SET_RUNTIME_PARAMS 0x1C +#define IPC_IA_SET_PARAMS 0x1 +#define IPC_IA_GET_PARAMS 0x2 + +#define IPC_EFFECTS_CREATE 0xE +#define IPC_EFFECTS_DESTROY 0xF + +/* I2L Stream config/control msgs */ +#define IPC_IA_ALLOC_STREAM_MRFLD 0x2 +#define IPC_IA_ALLOC_STREAM 0x20 /* Allocate a stream ID */ +#define IPC_IA_FREE_STREAM_MRFLD 0x03 +#define IPC_IA_FREE_STREAM 0x21 /* Free the stream ID */ +#define IPC_IA_SET_STREAM_PARAMS 0x22 +#define IPC_IA_SET_STREAM_PARAMS_MRFLD 0x12 +#define IPC_IA_GET_STREAM_PARAMS 0x23 +#define IPC_IA_PAUSE_STREAM 0x24 +#define IPC_IA_PAUSE_STREAM_MRFLD 0x4 +#define IPC_IA_RESUME_STREAM 0x25 +#define IPC_IA_RESUME_STREAM_MRFLD 0x5 +#define IPC_IA_DROP_STREAM 0x26 +#define IPC_IA_DROP_STREAM_MRFLD 0x07 +#define IPC_IA_DRAIN_STREAM 0x27 /* Short msg with str_id */ +#define IPC_IA_DRAIN_STREAM_MRFLD 0x8 +#define IPC_IA_CONTROL_ROUTING 0x29 +#define IPC_IA_VTSV_UPDATE_MODULES 0x20 +#define IPC_IA_VTSV_DETECTED 0x21 + +#define IPC_IA_START_STREAM_MRFLD 0X06 +#define IPC_IA_START_STREAM 0x30 /* Short msg with str_id */ + +#define IPC_IA_SET_GAIN_MRFLD 0x21 +/* Debug msgs */ +#define IPC_IA_DBG_MEM_READ 0x40 +#define IPC_IA_DBG_MEM_WRITE 0x41 +#define IPC_IA_DBG_LOOP_BACK 0x42 +#define IPC_IA_DBG_LOG_ENABLE 0x45 +#define IPC_IA_DBG_SET_PROBE_PARAMS 0x47 + +/* L2I Firmware/Codec Download msgs */ +#define IPC_IA_FW_INIT_CMPLT 0x81 +#define IPC_IA_FW_INIT_CMPLT_MRFLD 0x01 +#define IPC_IA_FW_ASYNC_ERR_MRFLD 0x11 + +/* L2I Codec Config/control msgs */ +#define IPC_SST_FRAGMENT_ELPASED 0x90 /* Request IA more data */ + +#define IPC_SST_BUF_UNDER_RUN 0x92 /* PB Under run and stopped */ +#define IPC_SST_BUF_OVER_RUN 0x93 /* CAP Under run and stopped */ +#define IPC_SST_DRAIN_END 0x94 /* PB Drain complete and stopped */ +#define IPC_SST_CHNGE_SSP_PARAMS 0x95 /* PB SSP parameters changed */ +#define IPC_SST_STREAM_PROCESS_FATAL_ERR 0x96/* error in processing a stream */ +#define IPC_SST_PERIOD_ELAPSED 0x97 /* period elapsed */ + +#define IPC_SST_ERROR_EVENT 0x99 /* Buffer over run occurred */ +/* L2S messages */ +#define IPC_SC_DDR_LINK_UP 0xC0 +#define IPC_SC_DDR_LINK_DOWN 0xC1 +#define IPC_SC_SET_LPECLK_REQ 0xC2 +#define IPC_SC_SSP_BIT_BANG 0xC3 + +/* L2I Error reporting msgs */ +#define IPC_IA_MEM_ALLOC_FAIL 0xE0 +#define IPC_IA_PROC_ERR 0xE1 /* error in processing a + stream can be used by playback and + capture modules */ + +/* L2I Debug msgs */ +#define IPC_IA_PRINT_STRING 0xF0 + +/* Buffer under-run */ +#define IPC_IA_BUF_UNDER_RUN_MRFLD 0x0B + +/* Mrfld specific defines: + * For asynchronous messages(INIT_CMPLT, PERIOD_ELAPSED, ASYNC_ERROR) + * received from FW, the format is: + * - IPC High: pvt_id is set to zero. Always short message. + * - msg_id is in lower 16-bits of IPC low payload. + * - pipe_id is in higher 16-bits of IPC low payload for period_elapsed. + * - error id is in higher 16-bits of IPC low payload for async errors. + */ +#define SST_ASYNC_DRV_ID 0 + +/* Command Response or Acknowledge message to any IPC message will have + * same message ID and stream ID information which is sent. + * There is no specific Ack message ID. The data field is used as response + * meaning. + */ +enum ackData { + IPC_ACK_SUCCESS = 0, + IPC_ACK_FAILURE, +}; + +enum ipc_ia_msg_id { + IPC_CMD = 1, /*!< Task Control message ID */ + IPC_SET_PARAMS = 2,/*!< Task Set param message ID */ + IPC_GET_PARAMS = 3, /*!< Task Get param message ID */ + IPC_INVALID = 0xFF, /*! + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sst-mfld-platform.h" + +/* compress stream operations */ +static void sst_compr_fragment_elapsed(void *arg) +{ + struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; + + pr_debug("fragment elapsed by driver\n"); + if (cstream) + snd_compr_fragment_elapsed(cstream); +} + +static void sst_drain_notify(void *arg) +{ + struct snd_compr_stream *cstream = (struct snd_compr_stream *)arg; + + pr_debug("drain notify by driver\n"); + if (cstream) + snd_compr_drain_notify(cstream); +} + +static int sst_platform_compr_open(struct snd_compr_stream *cstream) +{ + + int ret_val = 0; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sst_runtime_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ + if (!sst || !try_module_get(sst->dev->driver->owner)) { + pr_err("no device available to run\n"); + ret_val = -ENODEV; + goto out_ops; + } + stream->compr_ops = sst->compr_ops; + stream->id = 0; + + /* Turn on LPE */ + sst->compr_ops->power(sst->dev, true); + + sst_set_stream_status(stream, SST_PLATFORM_INIT); + runtime->private_data = stream; + return 0; +out_ops: + kfree(stream); + return ret_val; +} + +static int sst_platform_compr_free(struct snd_compr_stream *cstream) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + stream = cstream->runtime->private_data; + /* Turn off LPE */ + sst->compr_ops->power(sst->dev, false); + + /*need to check*/ + str_id = stream->id; + if (str_id) + ret_val = stream->compr_ops->close(sst->dev, str_id); + module_put(sst->dev->driver->owner); + kfree(stream); + pr_debug("%s: %d\n", __func__, ret_val); + return 0; +} + +static int sst_platform_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct sst_runtime_stream *stream; + int retval; + struct snd_sst_params str_params; + struct sst_compress_cb cb; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_data *ctx = snd_soc_component_get_drvdata(component); + + stream = cstream->runtime->private_data; + /* construct fw structure for this*/ + memset(&str_params, 0, sizeof(str_params)); + + /* fill the device type and stream id to pass to SST driver */ + retval = sst_fill_stream_params(cstream, ctx, &str_params, true); + pr_debug("compr_set_params: fill stream params ret_val = 0x%x\n", retval); + if (retval < 0) + return retval; + + switch (params->codec.id) { + case SND_AUDIOCODEC_MP3: { + str_params.codec = SST_CODEC_TYPE_MP3; + str_params.sparams.uc.mp3_params.num_chan = params->codec.ch_in; + str_params.sparams.uc.mp3_params.pcm_wd_sz = 16; + break; + } + + case SND_AUDIOCODEC_AAC: { + str_params.codec = SST_CODEC_TYPE_AAC; + str_params.sparams.uc.aac_params.num_chan = params->codec.ch_in; + str_params.sparams.uc.aac_params.pcm_wd_sz = 16; + if (params->codec.format == SND_AUDIOSTREAMFORMAT_MP4ADTS) + str_params.sparams.uc.aac_params.bs_format = + AAC_BIT_STREAM_ADTS; + else if (params->codec.format == SND_AUDIOSTREAMFORMAT_RAW) + str_params.sparams.uc.aac_params.bs_format = + AAC_BIT_STREAM_RAW; + else { + pr_err("Undefined format%d\n", params->codec.format); + return -EINVAL; + } + str_params.sparams.uc.aac_params.externalsr = + params->codec.sample_rate; + break; + } + + default: + pr_err("codec not supported, id =%d\n", params->codec.id); + return -EINVAL; + } + + str_params.aparams.ring_buf_info[0].addr = + virt_to_phys(cstream->runtime->buffer); + str_params.aparams.ring_buf_info[0].size = + cstream->runtime->buffer_size; + str_params.aparams.sg_count = 1; + str_params.aparams.frag_size = cstream->runtime->fragment_size; + + cb.param = cstream; + cb.compr_cb = sst_compr_fragment_elapsed; + cb.drain_cb_param = cstream; + cb.drain_notify = sst_drain_notify; + + retval = stream->compr_ops->open(sst->dev, &str_params, &cb); + if (retval < 0) { + pr_err("stream allocation failed %d\n", retval); + return retval; + } + + stream->id = retval; + return 0; +} + +static int sst_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd) +{ + struct sst_runtime_stream *stream = cstream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (stream->compr_ops->stream_start) + return stream->compr_ops->stream_start(sst->dev, stream->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (stream->compr_ops->stream_drop) + return stream->compr_ops->stream_drop(sst->dev, stream->id); + break; + case SND_COMPR_TRIGGER_DRAIN: + if (stream->compr_ops->stream_drain) + return stream->compr_ops->stream_drain(sst->dev, stream->id); + break; + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + if (stream->compr_ops->stream_partial_drain) + return stream->compr_ops->stream_partial_drain(sst->dev, stream->id); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (stream->compr_ops->stream_pause) + return stream->compr_ops->stream_pause(sst->dev, stream->id); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (stream->compr_ops->stream_pause_release) + return stream->compr_ops->stream_pause_release(sst->dev, stream->id); + break; + } + return -EINVAL; +} + +static int sst_platform_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct sst_runtime_stream *stream; + + stream = cstream->runtime->private_data; + stream->compr_ops->tstamp(sst->dev, stream->id, tstamp); + tstamp->byte_offset = tstamp->copied_total % + (u32)cstream->runtime->buffer_size; + pr_debug("calc bytes offset/copied bytes as %d\n", tstamp->byte_offset); + return 0; +} + +static int sst_platform_compr_ack(struct snd_compr_stream *cstream, + size_t bytes) +{ + struct sst_runtime_stream *stream; + + stream = cstream->runtime->private_data; + stream->compr_ops->ack(sst->dev, stream->id, (unsigned long)bytes); + stream->bytes_written += bytes; + + return 0; +} + +static int sst_platform_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + struct sst_runtime_stream *stream = + cstream->runtime->private_data; + + return stream->compr_ops->get_caps(caps); +} + +static int sst_platform_compr_get_codec_caps(struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + struct sst_runtime_stream *stream = + cstream->runtime->private_data; + + return stream->compr_ops->get_codec_caps(codec); +} + +static int sst_platform_compr_set_metadata(struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) +{ + struct sst_runtime_stream *stream = + cstream->runtime->private_data; + + return stream->compr_ops->set_metadata(sst->dev, stream->id, metadata); +} + +const struct snd_compr_ops sst_platform_compr_ops = { + + .open = sst_platform_compr_open, + .free = sst_platform_compr_free, + .set_params = sst_platform_compr_set_params, + .set_metadata = sst_platform_compr_set_metadata, + .trigger = sst_platform_compr_trigger, + .pointer = sst_platform_compr_pointer, + .ack = sst_platform_compr_ack, + .get_caps = sst_platform_compr_get_caps, + .get_codec_caps = sst_platform_compr_get_codec_caps, +}; diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c new file mode 100644 index 000000000..682ee41ec --- /dev/null +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -0,0 +1,843 @@ +/* + * sst_mfld_platform.c - Intel MID Platform driver + * + * Copyright (C) 2010-2014 Intel Corp + * Author: Vinod Koul + * Author: Harsha Priya + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sst-mfld-platform.h" +#include "sst-atom-controls.h" + +struct sst_device *sst; +static DEFINE_MUTEX(sst_lock); + +int sst_register_dsp(struct sst_device *dev) +{ + if (WARN_ON(!dev)) + return -EINVAL; + if (!try_module_get(dev->dev->driver->owner)) + return -ENODEV; + mutex_lock(&sst_lock); + if (sst) { + dev_err(dev->dev, "we already have a device %s\n", sst->name); + module_put(dev->dev->driver->owner); + mutex_unlock(&sst_lock); + return -EEXIST; + } + dev_dbg(dev->dev, "registering device %s\n", dev->name); + sst = dev; + mutex_unlock(&sst_lock); + return 0; +} +EXPORT_SYMBOL_GPL(sst_register_dsp); + +int sst_unregister_dsp(struct sst_device *dev) +{ + if (WARN_ON(!dev)) + return -EINVAL; + if (dev != sst) + return -EINVAL; + + mutex_lock(&sst_lock); + + if (!sst) { + mutex_unlock(&sst_lock); + return -EIO; + } + + module_put(sst->dev->driver->owner); + dev_dbg(dev->dev, "unreg %s\n", sst->name); + sst = NULL; + mutex_unlock(&sst_lock); + return 0; +} +EXPORT_SYMBOL_GPL(sst_unregister_dsp); + +static const struct snd_pcm_hardware sst_platform_pcm_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP| + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_SYNC_START), + .buffer_bytes_max = SST_MAX_BUFFER, + .period_bytes_min = SST_MIN_PERIOD_BYTES, + .period_bytes_max = SST_MAX_PERIOD_BYTES, + .periods_min = SST_MIN_PERIODS, + .periods_max = SST_MAX_PERIODS, + .fifo_size = SST_FIFO_SIZE, +}; + +static struct sst_dev_stream_map dpcm_strm_map[] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, /* Reserved, not in use */ + {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA1_IN, SST_TASK_ID_MEDIA, 0}, + {MERR_DPCM_COMPR, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA0_IN, SST_TASK_ID_MEDIA, 0}, + {MERR_DPCM_AUDIO, 0, SNDRV_PCM_STREAM_CAPTURE, PIPE_PCM1_OUT, SST_TASK_ID_MEDIA, 0}, + {MERR_DPCM_DEEP_BUFFER, 0, SNDRV_PCM_STREAM_PLAYBACK, PIPE_MEDIA3_IN, SST_TASK_ID_MEDIA, 0}, +}; + +static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) +{ + + return sst_send_pipe_gains(dai, stream, mute); +} + +/* helper functions */ +void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) +{ + unsigned long flags; + spin_lock_irqsave(&stream->status_lock, flags); + stream->stream_status = state; + spin_unlock_irqrestore(&stream->status_lock, flags); +} + +static inline int sst_get_stream_status(struct sst_runtime_stream *stream) +{ + int state; + unsigned long flags; + + spin_lock_irqsave(&stream->status_lock, flags); + state = stream->stream_status; + spin_unlock_irqrestore(&stream->status_lock, flags); + return state; +} + +static void sst_fill_alloc_params(struct snd_pcm_substream *substream, + struct snd_sst_alloc_params_ext *alloc_param) +{ + unsigned int channels; + snd_pcm_uframes_t period_size; + ssize_t periodbytes; + ssize_t buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + u32 buffer_addr = virt_to_phys(substream->runtime->dma_area); + + channels = substream->runtime->channels; + period_size = substream->runtime->period_size; + periodbytes = samples_to_bytes(substream->runtime, period_size); + alloc_param->ring_buf_info[0].addr = buffer_addr; + alloc_param->ring_buf_info[0].size = buffer_bytes; + alloc_param->sg_count = 1; + alloc_param->reserved = 0; + alloc_param->frag_size = periodbytes * channels; + +} +static void sst_fill_pcm_params(struct snd_pcm_substream *substream, + struct snd_sst_stream_params *param) +{ + param->uc.pcm_params.num_chan = (u8) substream->runtime->channels; + param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits; + param->uc.pcm_params.sfreq = substream->runtime->rate; + + /* PCM stream via ALSA interface */ + param->uc.pcm_params.use_offload_path = 0; + param->uc.pcm_params.reserved2 = 0; + memset(param->uc.pcm_params.channel_map, 0, sizeof(u8)); + +} + +static int sst_get_stream_mapping(int dev, int sdev, int dir, + struct sst_dev_stream_map *map, int size) +{ + int i; + + if (map == NULL) + return -EINVAL; + + + /* index 0 is not used in stream map */ + for (i = 1; i < size; i++) { + if ((map[i].dev_num == dev) && (map[i].direction == dir)) + return i; + } + return 0; +} + +int sst_fill_stream_params(void *substream, + const struct sst_data *ctx, struct snd_sst_params *str_params, bool is_compress) +{ + int map_size; + int index; + struct sst_dev_stream_map *map; + struct snd_pcm_substream *pstream = NULL; + struct snd_compr_stream *cstream = NULL; + + map = ctx->pdata->pdev_strm_map; + map_size = ctx->pdata->strm_map_size; + + if (is_compress == true) + cstream = (struct snd_compr_stream *)substream; + else + pstream = (struct snd_pcm_substream *)substream; + + str_params->stream_type = SST_STREAM_TYPE_MUSIC; + + /* For pcm streams */ + if (pstream) { + index = sst_get_stream_mapping(pstream->pcm->device, + pstream->number, pstream->stream, + map, map_size); + if (index <= 0) + return -EINVAL; + + str_params->stream_id = index; + str_params->device_type = map[index].device_id; + str_params->task = map[index].task_id; + + str_params->ops = (u8)pstream->stream; + } + + if (cstream) { + index = sst_get_stream_mapping(cstream->device->device, + 0, cstream->direction, + map, map_size); + if (index <= 0) + return -EINVAL; + str_params->stream_id = index; + str_params->device_type = map[index].device_id; + str_params->task = map[index].task_id; + + str_params->ops = (u8)cstream->direction; + } + return 0; +} + +static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + struct snd_sst_stream_params param = {{{0,},},}; + struct snd_sst_params str_params = {0}; + struct snd_sst_alloc_params_ext alloc_params = {0}; + int ret_val = 0; + struct sst_data *ctx = snd_soc_dai_get_drvdata(dai); + + /* set codec params and inform SST driver the same */ + sst_fill_pcm_params(substream, ¶m); + sst_fill_alloc_params(substream, &alloc_params); + str_params.sparams = param; + str_params.aparams = alloc_params; + str_params.codec = SST_CODEC_TYPE_PCM; + + /* fill the device type and stream id to pass to SST driver */ + ret_val = sst_fill_stream_params(substream, ctx, &str_params, false); + if (ret_val < 0) + return ret_val; + + stream->stream_info.str_id = str_params.stream_id; + + ret_val = stream->ops->open(sst->dev, &str_params); + if (ret_val <= 0) + return ret_val; + + + return ret_val; +} + +static void sst_period_elapsed(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct sst_runtime_stream *stream; + int status; + + if (!substream || !substream->runtime) + return; + stream = substream->runtime->private_data; + if (!stream) + return; + status = sst_get_stream_status(stream); + if (status != SST_PLATFORM_RUNNING) + return; + snd_pcm_period_elapsed(substream); +} + +static int sst_platform_init_stream(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret_val; + + dev_dbg(rtd->dev, "setting buffer ptr param\n"); + sst_set_stream_status(stream, SST_PLATFORM_INIT); + stream->stream_info.period_elapsed = sst_period_elapsed; + stream->stream_info.arg = substream; + stream->stream_info.buffer_ptr = 0; + stream->stream_info.sfreq = substream->runtime->rate; + ret_val = stream->ops->stream_init(sst->dev, &stream->stream_info); + if (ret_val) + dev_err(rtd->dev, "control_set ret error %d\n", ret_val); + return ret_val; + +} + +static int power_up_sst(struct sst_runtime_stream *stream) +{ + return stream->ops->power(sst->dev, true); +} + +static void power_down_sst(struct sst_runtime_stream *stream) +{ + stream->ops->power(sst->dev, false); +} + +static int sst_media_open(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret_val = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sst_runtime_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ + mutex_lock(&sst_lock); + if (!sst || + !try_module_get(sst->dev->driver->owner)) { + dev_err(dai->dev, "no device available to run\n"); + ret_val = -ENODEV; + goto out_ops; + } + stream->ops = sst->ops; + mutex_unlock(&sst_lock); + + stream->stream_info.str_id = 0; + + stream->stream_info.arg = substream; + /* allocate memory for SST API set */ + runtime->private_data = stream; + + ret_val = power_up_sst(stream); + if (ret_val < 0) + goto out_power_up; + + /* Make sure, that the period size is always even */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +out_ops: + mutex_unlock(&sst_lock); +out_power_up: + kfree(stream); + return ret_val; +} + +static void sst_media_close(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sst_runtime_stream *stream; + int str_id; + + stream = substream->runtime->private_data; + power_down_sst(stream); + + str_id = stream->stream_info.str_id; + if (str_id) + stream->ops->close(sst->dev, str_id); + module_put(sst->dev->driver->owner); + kfree(stream); +} + +static int sst_media_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + if (stream->stream_info.str_id) { + ret_val = stream->ops->stream_drop(sst->dev, str_id); + return ret_val; + } + + ret_val = sst_platform_alloc_stream(substream, dai); + if (ret_val <= 0) + return ret_val; + snprintf(substream->pcm->id, sizeof(substream->pcm->id), + "%d", stream->stream_info.str_id); + + ret_val = sst_platform_init_stream(substream); + if (ret_val) + return ret_val; + substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; + return ret_val; +} + +static int sst_media_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret; + + ret = + snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(params)); + if (ret) + return ret; + memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); + return 0; +} + +static int sst_media_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int sst_enable_ssp(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + + if (!dai->active) { + ret = sst_handle_vb_timer(dai, true); + sst_fill_ssp_defaults(dai); + } + return ret; +} + +static int sst_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret = 0; + + if (dai->active == 1) + ret = send_ssp_cmd(dai, dai->name, 1); + return ret; +} + +static int sst_set_format(struct snd_soc_dai *dai, unsigned int fmt) +{ + int ret = 0; + + if (!dai->active) + return 0; + + ret = sst_fill_ssp_config(dai, fmt); + if (ret < 0) + dev_err(dai->dev, "sst_set_format failed..\n"); + + return ret; +} + +static int sst_platform_set_ssp_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) { + int ret = 0; + + if (!dai->active) + return ret; + + ret = sst_fill_ssp_slot(dai, tx_mask, rx_mask, slots, slot_width); + if (ret < 0) + dev_err(dai->dev, "sst_fill_ssp_slot failed..%d\n", ret); + + return ret; +} + +static void sst_disable_ssp(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (!dai->active) { + send_ssp_cmd(dai, dai->name, 0); + sst_handle_vb_timer(dai, false); + } +} + +static const struct snd_soc_dai_ops sst_media_dai_ops = { + .startup = sst_media_open, + .shutdown = sst_media_close, + .prepare = sst_media_prepare, + .hw_params = sst_media_hw_params, + .hw_free = sst_media_hw_free, + .mute_stream = sst_media_digital_mute, +}; + +static const struct snd_soc_dai_ops sst_compr_dai_ops = { + .mute_stream = sst_media_digital_mute, +}; + +static const struct snd_soc_dai_ops sst_be_dai_ops = { + .startup = sst_enable_ssp, + .hw_params = sst_be_hw_params, + .set_fmt = sst_set_format, + .set_tdm_slot = sst_platform_set_ssp_slot, + .shutdown = sst_disable_ssp, +}; + +static struct snd_soc_dai_driver sst_platform_dai[] = { +{ + .name = "media-cpu-dai", + .ops = &sst_media_dai_ops, + .playback = { + .stream_name = "Headset Playback", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Headset Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "deepbuffer-cpu-dai", + .ops = &sst_media_dai_ops, + .playback = { + .stream_name = "Deepbuffer Playback", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_44100|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "compress-cpu-dai", + .compress_new = snd_soc_new_compress, + .ops = &sst_compr_dai_ops, + .playback = { + .stream_name = "Compress Playback", + .channels_min = 1, + }, +}, +/* BE CPU Dais */ +{ + .name = "ssp0-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp0 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp0 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "ssp1-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp1 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp1 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "ssp2-port", + .ops = &sst_be_dai_ops, + .playback = { + .stream_name = "ssp2 Tx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp2 Rx", + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static int sst_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (substream->pcm->internal) + return 0; + + runtime = substream->runtime; + runtime->hw = sst_platform_pcm_hw; + return 0; +} + +static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + int ret_val = 0, str_id; + struct sst_runtime_stream *stream; + int status; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->dev, "sst_platform_pcm_trigger called\n"); + if (substream->pcm->internal) + return 0; + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(rtd->dev, "sst: Trigger Start\n"); + status = SST_PLATFORM_RUNNING; + stream->stream_info.arg = substream; + ret_val = stream->ops->stream_start(sst->dev, str_id); + break; + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(rtd->dev, "sst: in stop\n"); + status = SST_PLATFORM_DROPPED; + ret_val = stream->ops->stream_drop(sst->dev, str_id); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + dev_dbg(rtd->dev, "sst: in pause\n"); + status = SST_PLATFORM_PAUSED; + ret_val = stream->ops->stream_pause(sst->dev, str_id); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + dev_dbg(rtd->dev, "sst: in pause release\n"); + status = SST_PLATFORM_RUNNING; + ret_val = stream->ops->stream_pause_release(sst->dev, str_id); + break; + default: + return -EINVAL; + } + + if (!ret_val) + sst_set_stream_status(stream, status); + + return ret_val; +} + + +static snd_pcm_uframes_t sst_platform_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val, status; + struct pcm_stream_info *str_info; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + stream = substream->runtime->private_data; + status = sst_get_stream_status(stream); + if (status == SST_PLATFORM_INIT) + return 0; + str_info = &stream->stream_info; + ret_val = stream->ops->stream_read_tstamp(sst->dev, str_info); + if (ret_val) { + dev_err(rtd->dev, "sst: error code = %d\n", ret_val); + return ret_val; + } + substream->runtime->delay = str_info->pcm_delay; + return str_info->buffer_ptr; +} + +static const struct snd_pcm_ops sst_platform_ops = { + .open = sst_platform_open, + .ioctl = snd_pcm_lib_ioctl, + .trigger = sst_platform_pcm_trigger, + .pointer = sst_platform_pcm_pointer, +}; + +static int sst_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_pcm *pcm = rtd->pcm; + int retval = 0; + + if (dai->driver->playback.channels_min || + dai->driver->capture.channels_min) { + retval = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_DMA), + SST_MIN_BUFFER, SST_MAX_BUFFER); + if (retval) { + dev_err(rtd->dev, "dma buffer allocation failure\n"); + return retval; + } + } + return retval; +} + +static int sst_soc_probe(struct snd_soc_component *component) +{ + struct sst_data *drv = dev_get_drvdata(component->dev); + + drv->soc_card = component->card; + return sst_dsp_init_v2_dpcm(component); +} + +static void sst_soc_remove(struct snd_soc_component *component) +{ + struct sst_data *drv = dev_get_drvdata(component->dev); + + drv->soc_card = NULL; +} + +static const struct snd_soc_component_driver sst_soc_platform_drv = { + .name = DRV_NAME, + .probe = sst_soc_probe, + .remove = sst_soc_remove, + .ops = &sst_platform_ops, + .compr_ops = &sst_platform_compr_ops, + .pcm_new = sst_pcm_new, +}; + +static int sst_platform_probe(struct platform_device *pdev) +{ + struct sst_data *drv; + int ret; + struct sst_platform_data *pdata; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (drv == NULL) { + return -ENOMEM; + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) { + return -ENOMEM; + } + + pdata->pdev_strm_map = dpcm_strm_map; + pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map); + drv->pdata = pdata; + drv->pdev = pdev; + mutex_init(&drv->lock); + dev_set_drvdata(&pdev->dev, drv); + + ret = devm_snd_soc_register_component(&pdev->dev, &sst_soc_platform_drv, + sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); + if (ret) + dev_err(&pdev->dev, "registering cpu dais failed\n"); + + return ret; +} + +static int sst_platform_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "sst_platform_remove success\n"); + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int sst_soc_prepare(struct device *dev) +{ + struct sst_data *drv = dev_get_drvdata(dev); + struct snd_soc_pcm_runtime *rtd; + + if (!drv->soc_card) + return 0; + + /* suspend all pcms first */ + snd_soc_suspend(drv->soc_card->dev); + snd_soc_poweroff(drv->soc_card->dev); + + /* set the SSPs to idle */ + list_for_each_entry(rtd, &drv->soc_card->rtd_list, list) { + struct snd_soc_dai *dai = rtd->cpu_dai; + + if (dai->active) { + send_ssp_cmd(dai, dai->name, 0); + sst_handle_vb_timer(dai, false); + } + } + + return 0; +} + +static void sst_soc_complete(struct device *dev) +{ + struct sst_data *drv = dev_get_drvdata(dev); + struct snd_soc_pcm_runtime *rtd; + + if (!drv->soc_card) + return; + + /* restart SSPs */ + list_for_each_entry(rtd, &drv->soc_card->rtd_list, list) { + struct snd_soc_dai *dai = rtd->cpu_dai; + + if (dai->active) { + sst_handle_vb_timer(dai, true); + send_ssp_cmd(dai, dai->name, 1); + } + } + snd_soc_resume(drv->soc_card->dev); +} + +#else + +#define sst_soc_prepare NULL +#define sst_soc_complete NULL + +#endif + + +static const struct dev_pm_ops sst_platform_pm = { + .prepare = sst_soc_prepare, + .complete = sst_soc_complete, +}; + +static struct platform_driver sst_platform_driver = { + .driver = { + .name = "sst-mfld-platform", + .pm = &sst_platform_pm, + }, + .probe = sst_platform_probe, + .remove = sst_platform_remove, +}; + +module_platform_driver(sst_platform_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver"); +MODULE_AUTHOR("Vinod Koul "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sst-atom-hifi2-platform"); +MODULE_ALIAS("platform:sst-mfld-platform"); diff --git a/sound/soc/intel/atom/sst-mfld-platform.h b/sound/soc/intel/atom/sst-mfld-platform.h new file mode 100644 index 000000000..5f729df57 --- /dev/null +++ b/sound/soc/intel/atom/sst-mfld-platform.h @@ -0,0 +1,186 @@ +/* + * sst_mfld_platform.h - Intel MID Platform driver header file + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul + * Author: Harsha Priya + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __SST_PLATFORMDRV_H__ +#define __SST_PLATFORMDRV_H__ + +#include "sst-mfld-dsp.h" +#include "sst-atom-controls.h" + +extern struct sst_device *sst; +extern const struct snd_compr_ops sst_platform_compr_ops; + +#define DRV_NAME "sst" + +#define SST_MONO 1 +#define SST_STEREO 2 +#define SST_MAX_CAP 5 + +#define SST_MAX_BUFFER (800*1024) +#define SST_MIN_BUFFER (800*1024) +#define SST_MIN_PERIOD_BYTES 32 +#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER +#define SST_MIN_PERIODS 2 +#define SST_MAX_PERIODS (1024*2) +#define SST_FIFO_SIZE 0 + +struct pcm_stream_info { + int str_id; + void *arg; + void (*period_elapsed) (void *arg); + unsigned long long buffer_ptr; + unsigned long long pcm_delay; + int sfreq; +}; + +enum sst_drv_status { + SST_PLATFORM_INIT = 1, + SST_PLATFORM_STARTED, + SST_PLATFORM_RUNNING, + SST_PLATFORM_PAUSED, + SST_PLATFORM_DROPPED, +}; + +enum sst_stream_ops { + STREAM_OPS_PLAYBACK = 0, + STREAM_OPS_CAPTURE, +}; + +enum sst_audio_device_type { + SND_SST_DEVICE_HEADSET = 1, + SND_SST_DEVICE_IHF, + SND_SST_DEVICE_VIBRA, + SND_SST_DEVICE_HAPTIC, + SND_SST_DEVICE_CAPTURE, + SND_SST_DEVICE_COMPRESS, +}; + +/* PCM Parameters */ +struct sst_pcm_params { + u16 codec; /* codec type */ + u8 num_chan; /* 1=Mono, 2=Stereo */ + u8 pcm_wd_sz; /* 16/24 - bit*/ + u32 reserved; /* Bitrate in bits per second */ + u32 sfreq; /* Sampling rate in Hz */ + u32 ring_buffer_size; + u32 period_count; /* period elapsed in samples*/ + u32 ring_buffer_addr; +}; + +struct sst_stream_params { + u32 result; + u32 stream_id; + u8 codec; + u8 ops; + u8 stream_type; + u8 device_type; + struct sst_pcm_params sparams; +}; + +struct sst_compress_cb { + void *param; + void (*compr_cb)(void *param); + void *drain_cb_param; + void (*drain_notify)(void *param); +}; + +struct compress_sst_ops { + const char *name; + int (*open)(struct device *dev, + struct snd_sst_params *str_params, struct sst_compress_cb *cb); + int (*stream_start)(struct device *dev, unsigned int str_id); + int (*stream_drop)(struct device *dev, unsigned int str_id); + int (*stream_drain)(struct device *dev, unsigned int str_id); + int (*stream_partial_drain)(struct device *dev, unsigned int str_id); + int (*stream_pause)(struct device *dev, unsigned int str_id); + int (*stream_pause_release)(struct device *dev, unsigned int str_id); + + int (*tstamp)(struct device *dev, unsigned int str_id, + struct snd_compr_tstamp *tstamp); + int (*ack)(struct device *dev, unsigned int str_id, + unsigned long bytes); + int (*close)(struct device *dev, unsigned int str_id); + int (*get_caps)(struct snd_compr_caps *caps); + int (*get_codec_caps)(struct snd_compr_codec_caps *codec); + int (*set_metadata)(struct device *dev, unsigned int str_id, + struct snd_compr_metadata *mdata); + int (*power)(struct device *dev, bool state); +}; + +struct sst_ops { + int (*open)(struct device *dev, struct snd_sst_params *str_param); + int (*stream_init)(struct device *dev, struct pcm_stream_info *str_info); + int (*stream_start)(struct device *dev, int str_id); + int (*stream_drop)(struct device *dev, int str_id); + int (*stream_pause)(struct device *dev, int str_id); + int (*stream_pause_release)(struct device *dev, int str_id); + int (*stream_read_tstamp)(struct device *dev, struct pcm_stream_info *str_info); + int (*send_byte_stream)(struct device *dev, struct snd_sst_bytes_v2 *bytes); + int (*close)(struct device *dev, unsigned int str_id); + int (*power)(struct device *dev, bool state); +}; + +struct sst_runtime_stream { + int stream_status; + unsigned int id; + size_t bytes_written; + struct pcm_stream_info stream_info; + struct sst_ops *ops; + struct compress_sst_ops *compr_ops; + spinlock_t status_lock; +}; + +struct sst_device { + char *name; + struct device *dev; + struct sst_ops *ops; + struct platform_device *pdev; + struct compress_sst_ops *compr_ops; +}; + +struct sst_data; + +int sst_dsp_init_v2_dpcm(struct snd_soc_component *component); +int sst_send_pipe_gains(struct snd_soc_dai *dai, int stream, int mute); +int send_ssp_cmd(struct snd_soc_dai *dai, const char *id, bool enable); +int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable); + +void sst_set_stream_status(struct sst_runtime_stream *stream, int state); +int sst_fill_stream_params(void *substream, const struct sst_data *ctx, + struct snd_sst_params *str_params, bool is_compress); + +struct sst_algo_int_control_v2 { + struct soc_mixer_control mc; + u16 module_id; /* module identifieer */ + u16 pipe_id; /* location info: pipe_id + instance_id */ + u16 instance_id; + unsigned int value; /* Value received is stored here */ +}; +struct sst_data { + struct platform_device *pdev; + struct sst_platform_data *pdata; + struct snd_sst_bytes_v2 *byte_stream; + struct mutex lock; + struct snd_soc_card *soc_card; + struct sst_cmd_sba_hw_set_ssp ssp_cmd; +}; +int sst_register_dsp(struct sst_device *sst); +int sst_unregister_dsp(struct sst_device *sst); +#endif diff --git a/sound/soc/intel/atom/sst/Makefile b/sound/soc/intel/atom/sst/Makefile new file mode 100644 index 000000000..795d1cf8f --- /dev/null +++ b/sound/soc/intel/atom/sst/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-intel-sst-core-objs := sst.o sst_ipc.o sst_stream.o sst_drv_interface.o sst_loader.o sst_pvt.o +snd-intel-sst-pci-objs += sst_pci.o +snd-intel-sst-acpi-objs += sst_acpi.o + +obj-$(CONFIG_SND_SST_IPC) += snd-intel-sst-core.o +obj-$(CONFIG_SND_SST_IPC_PCI) += snd-intel-sst-pci.o +obj-$(CONFIG_SND_SST_IPC_ACPI) += snd-intel-sst-acpi.o diff --git a/sound/soc/intel/atom/sst/sst.c b/sound/soc/intel/atom/sst/sst.c new file mode 100644 index 000000000..0962bc9ad --- /dev/null +++ b/sound/soc/intel/atom/sst/sst.c @@ -0,0 +1,584 @@ +/* + * sst.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +MODULE_AUTHOR("Vinod Koul "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver"); +MODULE_LICENSE("GPL v2"); + +static inline bool sst_is_process_reply(u32 msg_id) +{ + return ((msg_id & PROCESS_MSG) ? true : false); +} + +static inline bool sst_validate_mailbox_size(unsigned int size) +{ + return ((size <= SST_MAILBOX_SIZE) ? true : false); +} + +static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) +{ + union interrupt_reg_mrfld isr; + union ipc_header_mrfld header; + union sst_imr_reg_mrfld imr; + struct ipc_post *msg = NULL; + unsigned int size = 0; + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + irqreturn_t retval = IRQ_HANDLED; + + /* Interrupt arrived, check src */ + isr.full = sst_shim_read64(drv->shim, SST_ISRX); + + if (isr.part.done_interrupt) { + /* Clear done bit */ + spin_lock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, + drv->ipc_reg.ipcx); + header.p.header_high.part.done = 0; + sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full); + + /* write 1 to clear status register */; + isr.part.done_interrupt = 1; + sst_shim_write64(drv->shim, SST_ISRX, isr.full); + spin_unlock(&drv->ipc_spin_lock); + + /* we can send more messages to DSP so trigger work */ + queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq); + retval = IRQ_HANDLED; + } + + if (isr.part.busy_interrupt) { + /* message from dsp so copy that */ + spin_lock(&drv->ipc_spin_lock); + imr.full = sst_shim_read64(drv->shim, SST_IMRX); + imr.part.busy_interrupt = 1; + sst_shim_write64(drv->shim, SST_IMRX, imr.full); + spin_unlock(&drv->ipc_spin_lock); + header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd); + + if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) { + drv->ops->clear_interrupt(drv); + return IRQ_HANDLED; + } + + if (header.p.header_high.part.large) { + size = header.p.header_low_payload; + if (sst_validate_mailbox_size(size)) { + memcpy_fromio(msg->mailbox_data, + drv->mailbox + drv->mailbox_recv_offset, size); + } else { + dev_err(drv->dev, + "Mailbox not copied, payload size is: %u\n", size); + header.p.header_low_payload = 0; + } + } + + msg->mrfld_header = header; + msg->is_process_reply = + sst_is_process_reply(header.p.header_high.part.msg_id); + spin_lock(&drv->rx_msg_lock); + list_add_tail(&msg->node, &drv->rx_list); + spin_unlock(&drv->rx_msg_lock); + drv->ops->clear_interrupt(drv); + retval = IRQ_WAKE_THREAD; + } + return retval; +} + +static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) +{ + struct intel_sst_drv *drv = (struct intel_sst_drv *) context; + struct ipc_post *__msg, *msg = NULL; + unsigned long irq_flags; + + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + if (list_empty(&drv->rx_list)) { + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + return IRQ_HANDLED; + } + + list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) { + list_del(&msg->node); + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + if (msg->is_process_reply) + drv->ops->process_message(msg); + else + drv->ops->process_reply(drv, msg); + + if (msg->is_large) + kfree(msg->mailbox_data); + kfree(msg); + spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); + } + spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); + return IRQ_HANDLED; +} + +static int sst_save_dsp_context_v2(struct intel_sst_drv *sst) +{ + int ret = 0; + + ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD, + IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL, + true, true, false, true); + + if (ret < 0) { + dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret); + return -EIO; + } + + return 0; +} + + +static struct intel_sst_ops mrfld_ops = { + .interrupt = intel_sst_interrupt_mrfld, + .irq_thread = intel_sst_irq_thread_mrfld, + .clear_interrupt = intel_sst_clear_intr_mrfld, + .start = sst_start_mrfld, + .reset = intel_sst_reset_dsp_mrfld, + .post_message = sst_post_message_mrfld, + .process_reply = sst_process_reply_mrfld, + .save_dsp_context = sst_save_dsp_context_v2, + .alloc_stream = sst_alloc_stream_mrfld, + .post_download = sst_post_download_mrfld, +}; + +int sst_driver_ops(struct intel_sst_drv *sst) +{ + + switch (sst->dev_id) { + case SST_MRFLD_PCI_ID: + case SST_BYT_ACPI_ID: + case SST_CHV_ACPI_ID: + sst->tstamp = SST_TIME_STAMP_MRFLD; + sst->ops = &mrfld_ops; + return 0; + + default: + dev_err(sst->dev, + "SST Driver capabilities missing for dev_id: %x", + sst->dev_id); + return -EINVAL; + }; +} + +void sst_process_pending_msg(struct work_struct *work) +{ + struct intel_sst_drv *ctx = container_of(work, + struct intel_sst_drv, ipc_post_msg_wq); + + ctx->ops->post_message(ctx, NULL, false); +} + +static int sst_workqueue_init(struct intel_sst_drv *ctx) +{ + INIT_LIST_HEAD(&ctx->memcpy_list); + INIT_LIST_HEAD(&ctx->rx_list); + INIT_LIST_HEAD(&ctx->ipc_dispatch_list); + INIT_LIST_HEAD(&ctx->block_list); + INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg); + init_waitqueue_head(&ctx->wait_queue); + + ctx->post_msg_wq = + create_singlethread_workqueue("sst_post_msg_wq"); + if (!ctx->post_msg_wq) + return -EBUSY; + return 0; +} + +static void sst_init_locks(struct intel_sst_drv *ctx) +{ + mutex_init(&ctx->sst_lock); + spin_lock_init(&ctx->rx_msg_lock); + spin_lock_init(&ctx->ipc_spin_lock); + spin_lock_init(&ctx->block_lock); +} + +int sst_alloc_drv_context(struct intel_sst_drv **ctx, + struct device *dev, unsigned int dev_id) +{ + *ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL); + if (!(*ctx)) + return -ENOMEM; + + (*ctx)->dev = dev; + (*ctx)->dev_id = dev_id; + + return 0; +} +EXPORT_SYMBOL_GPL(sst_alloc_drv_context); + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->fw_version.type == 0 && ctx->fw_version.major == 0 && + ctx->fw_version.minor == 0 && ctx->fw_version.build == 0) + return sprintf(buf, "FW not yet loaded\n"); + else + return sprintf(buf, "v%02x.%02x.%02x.%02x\n", + ctx->fw_version.type, ctx->fw_version.major, + ctx->fw_version.minor, ctx->fw_version.build); + +} + +static DEVICE_ATTR_RO(firmware_version); + +static const struct attribute *sst_fw_version_attrs[] = { + &dev_attr_firmware_version.attr, + NULL, +}; + +static const struct attribute_group sst_fw_version_attr_group = { + .attrs = (struct attribute **)sst_fw_version_attrs, +}; + +int sst_context_init(struct intel_sst_drv *ctx) +{ + int ret = 0, i; + + if (!ctx->pdata) + return -EINVAL; + + if (!ctx->pdata->probe_data) + return -EINVAL; + + memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info)); + + ret = sst_driver_ops(ctx); + if (ret != 0) + return -EINVAL; + + sst_init_locks(ctx); + sst_set_fw_state_locked(ctx, SST_RESET); + + /* pvt_id 0 reserved for async messages */ + ctx->pvt_id = 1; + ctx->stream_cnt = 0; + ctx->fw_in_mem = NULL; + /* we use memcpy, so set to 0 */ + ctx->use_dma = 0; + ctx->use_lli = 0; + + if (sst_workqueue_init(ctx)) + return -EINVAL; + + ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off; + ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset; + ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset; + + dev_info(ctx->dev, "Got drv data max stream %d\n", + ctx->info.max_streams); + + for (i = 1; i <= ctx->info.max_streams; i++) { + struct stream_info *stream = &ctx->streams[i]; + + memset(stream, 0, sizeof(*stream)); + stream->pipe_id = PIPE_RSVD; + mutex_init(&stream->lock); + } + + /* Register the ISR */ + ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt, + ctx->ops->irq_thread, 0, SST_DRV_NAME, + ctx); + if (ret) + goto do_free_mem; + + dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num); + + /* default intr are unmasked so set this as masked */ + sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038); + + ctx->qos = devm_kzalloc(ctx->dev, + sizeof(struct pm_qos_request), GFP_KERNEL); + if (!ctx->qos) { + ret = -ENOMEM; + goto do_free_mem; + } + pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name); + ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name, + ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb); + if (ret) { + dev_err(ctx->dev, "Firmware download failed:%d\n", ret); + goto do_free_mem; + } + + ret = sysfs_create_group(&ctx->dev->kobj, + &sst_fw_version_attr_group); + if (ret) { + dev_err(ctx->dev, + "Unable to create sysfs\n"); + goto err_sysfs; + } + + sst_register(ctx->dev); + return 0; +err_sysfs: + sysfs_remove_group(&ctx->dev->kobj, &sst_fw_version_attr_group); + +do_free_mem: + destroy_workqueue(ctx->post_msg_wq); + return ret; +} +EXPORT_SYMBOL_GPL(sst_context_init); + +void sst_context_cleanup(struct intel_sst_drv *ctx) +{ + pm_runtime_get_noresume(ctx->dev); + pm_runtime_disable(ctx->dev); + sst_unregister(ctx->dev); + sst_set_fw_state_locked(ctx, SST_SHUTDOWN); + sysfs_remove_group(&ctx->dev->kobj, &sst_fw_version_attr_group); + flush_scheduled_work(); + destroy_workqueue(ctx->post_msg_wq); + pm_qos_remove_request(ctx->qos); + kfree(ctx->fw_sg_list.src); + kfree(ctx->fw_sg_list.dst); + ctx->fw_sg_list.list_len = 0; + kfree(ctx->fw_in_mem); + ctx->fw_in_mem = NULL; + sst_memcpy_free_resources(ctx); + ctx = NULL; +} +EXPORT_SYMBOL_GPL(sst_context_cleanup); + +void sst_configure_runtime_pm(struct intel_sst_drv *ctx) +{ + pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY); + pm_runtime_use_autosuspend(ctx->dev); + /* + * For acpi devices, the actual physical device state is + * initially active. So change the state to active before + * enabling the pm + */ + + if (!acpi_disabled) + pm_runtime_set_active(ctx->dev); + + pm_runtime_enable(ctx->dev); + + if (acpi_disabled) + pm_runtime_set_active(ctx->dev); + else + pm_runtime_put_noidle(ctx->dev); +} +EXPORT_SYMBOL_GPL(sst_configure_runtime_pm); + +static int intel_sst_runtime_suspend(struct device *dev) +{ + int ret = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state == SST_RESET) { + dev_dbg(dev, "LPE is already in RESET state, No action\n"); + return 0; + } + /* save fw context */ + if (ctx->ops->save_dsp_context(ctx)) + return -EBUSY; + + /* Move the SST state to Reset */ + sst_set_fw_state_locked(ctx, SST_RESET); + + synchronize_irq(ctx->irq_num); + flush_workqueue(ctx->post_msg_wq); + + ctx->ops->reset(ctx); + + return ret; +} + +static int intel_sst_suspend(struct device *dev) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + struct sst_fw_save *fw_save; + int i, ret = 0; + + /* check first if we are already in SW reset */ + if (ctx->sst_state == SST_RESET) + return 0; + + /* + * check if any stream is active and running + * they should already by suspend by soc_suspend + */ + for (i = 1; i <= ctx->info.max_streams; i++) { + struct stream_info *stream = &ctx->streams[i]; + + if (stream->status == STREAM_RUNNING) { + dev_err(dev, "stream %d is running, can't suspend, abort\n", i); + return -EBUSY; + } + + if (ctx->pdata->streams_lost_on_suspend) { + stream->resume_status = stream->status; + stream->resume_prev = stream->prev; + if (stream->status != STREAM_UN_INIT) + sst_free_stream(ctx, i); + } + } + synchronize_irq(ctx->irq_num); + flush_workqueue(ctx->post_msg_wq); + + /* Move the SST state to Reset */ + sst_set_fw_state_locked(ctx, SST_RESET); + + /* tell DSP we are suspending */ + if (ctx->ops->save_dsp_context(ctx)) + return -EBUSY; + + /* save the memories */ + fw_save = kzalloc(sizeof(*fw_save), GFP_KERNEL); + if (!fw_save) + return -ENOMEM; + fw_save->iram = kvzalloc(ctx->iram_end - ctx->iram_base, GFP_KERNEL); + if (!fw_save->iram) { + ret = -ENOMEM; + goto iram; + } + fw_save->dram = kvzalloc(ctx->dram_end - ctx->dram_base, GFP_KERNEL); + if (!fw_save->dram) { + ret = -ENOMEM; + goto dram; + } + fw_save->sram = kvzalloc(SST_MAILBOX_SIZE, GFP_KERNEL); + if (!fw_save->sram) { + ret = -ENOMEM; + goto sram; + } + + fw_save->ddr = kvzalloc(ctx->ddr_end - ctx->ddr_base, GFP_KERNEL); + if (!fw_save->ddr) { + ret = -ENOMEM; + goto ddr; + } + + memcpy32_fromio(fw_save->iram, ctx->iram, ctx->iram_end - ctx->iram_base); + memcpy32_fromio(fw_save->dram, ctx->dram, ctx->dram_end - ctx->dram_base); + memcpy32_fromio(fw_save->sram, ctx->mailbox, SST_MAILBOX_SIZE); + memcpy32_fromio(fw_save->ddr, ctx->ddr, ctx->ddr_end - ctx->ddr_base); + + ctx->fw_save = fw_save; + ctx->ops->reset(ctx); + return 0; +ddr: + kvfree(fw_save->sram); +sram: + kvfree(fw_save->dram); +dram: + kvfree(fw_save->iram); +iram: + kfree(fw_save); + return ret; +} + +static int intel_sst_resume(struct device *dev) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + struct sst_fw_save *fw_save = ctx->fw_save; + struct sst_block *block; + int i, ret = 0; + + if (!fw_save) + return 0; + + sst_set_fw_state_locked(ctx, SST_FW_LOADING); + + /* we have to restore the memory saved */ + ctx->ops->reset(ctx); + + ctx->fw_save = NULL; + + memcpy32_toio(ctx->iram, fw_save->iram, ctx->iram_end - ctx->iram_base); + memcpy32_toio(ctx->dram, fw_save->dram, ctx->dram_end - ctx->dram_base); + memcpy32_toio(ctx->mailbox, fw_save->sram, SST_MAILBOX_SIZE); + memcpy32_toio(ctx->ddr, fw_save->ddr, ctx->ddr_end - ctx->ddr_base); + + kvfree(fw_save->sram); + kvfree(fw_save->dram); + kvfree(fw_save->iram); + kvfree(fw_save->ddr); + kfree(fw_save); + + block = sst_create_block(ctx, 0, FW_DWNL_ID); + if (block == NULL) + return -ENOMEM; + + + /* start and wait for ack */ + ctx->ops->start(ctx); + ret = sst_wait_timeout(ctx, block); + if (ret) { + dev_err(ctx->dev, "fw download failed %d\n", ret); + /* FW download failed due to timeout */ + ret = -EBUSY; + + } else { + sst_set_fw_state_locked(ctx, SST_FW_RUNNING); + } + + if (ctx->pdata->streams_lost_on_suspend) { + for (i = 1; i <= ctx->info.max_streams; i++) { + struct stream_info *stream = &ctx->streams[i]; + + if (stream->resume_status != STREAM_UN_INIT) { + dev_dbg(ctx->dev, "Re-allocing stream %d status %d prev %d\n", + i, stream->resume_status, + stream->resume_prev); + sst_realloc_stream(ctx, i); + stream->status = stream->resume_status; + stream->prev = stream->resume_prev; + } + } + } + + sst_free_block(ctx, block); + return ret; +} + +const struct dev_pm_ops intel_sst_pm = { + .suspend = intel_sst_suspend, + .resume = intel_sst_resume, + .runtime_suspend = intel_sst_runtime_suspend, +}; +EXPORT_SYMBOL_GPL(intel_sst_pm); diff --git a/sound/soc/intel/atom/sst/sst.h b/sound/soc/intel/atom/sst/sst.h new file mode 100644 index 000000000..b2a705dc9 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst.h @@ -0,0 +1,534 @@ +/* + * sst.h - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Common private declarations for SST + */ +#ifndef __SST_H__ +#define __SST_H__ + +#include + +/* driver names */ +#define SST_DRV_NAME "intel_sst_driver" +#define SST_MRFLD_PCI_ID 0x119A +#define SST_BYT_ACPI_ID 0x80860F28 +#define SST_CHV_ACPI_ID 0x808622A8 + +#define SST_SUSPEND_DELAY 2000 +#define FW_CONTEXT_MEM (64*1024) +#define SST_ICCM_BOUNDARY 4 +#define SST_CONFIG_SSP_SIGN 0x7ffe8001 + +#define MRFLD_FW_VIRTUAL_BASE 0xC0000000 +#define MRFLD_FW_DDR_BASE_OFFSET 0x0 +#define MRFLD_FW_FEATURE_BASE_OFFSET 0x4 +#define MRFLD_FW_BSS_RESET_BIT 0 + +extern const struct dev_pm_ops intel_sst_pm; +enum sst_states { + SST_FW_LOADING = 1, + SST_FW_RUNNING, + SST_RESET, + SST_SHUTDOWN, +}; + +enum sst_algo_ops { + SST_SET_ALGO = 0, + SST_GET_ALGO = 1, +}; + +#define SST_BLOCK_TIMEOUT 1000 + +#define FW_SIGNATURE_SIZE 4 +#define FW_NAME_SIZE 32 + +/* stream states */ +enum sst_stream_states { + STREAM_UN_INIT = 0, /* Freed/Not used stream */ + STREAM_RUNNING = 1, /* Running */ + STREAM_PAUSED = 2, /* Paused stream */ + STREAM_INIT = 3, /* stream init, waiting for data */ +}; + +enum sst_ram_type { + SST_IRAM = 1, + SST_DRAM = 2, + SST_DDR = 5, + SST_CUSTOM_INFO = 7, /* consists of FW binary information */ +}; + +/* SST shim registers to structure mapping */ +union interrupt_reg { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_pisr_reg { + struct { + u32 pssp0:1; + u32 pssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:26; + } part; + u32 full; +}; + +union sst_pimr_reg { + struct { + u32 ssp0:1; + u32 ssp1:1; + u32 rsvd0:3; + u32 dmac:1; + u32 rsvd1:10; + u32 ssp0_sc:1; + u32 ssp1_sc:1; + u32 rsvd2:3; + u32 dmac_sc:1; + u32 rsvd3:10; + } part; + u32 full; +}; + +union config_status_reg_mrfld { + struct { + u64 lpe_reset:1; + u64 lpe_reset_vector:1; + u64 runstall:1; + u64 pwaitmode:1; + u64 clk_sel:3; + u64 rsvd2:1; + u64 sst_clk:3; + u64 xt_snoop:1; + u64 rsvd3:4; + u64 clk_sel1:6; + u64 clk_enable:3; + u64 rsvd4:6; + u64 slim0baseclk:1; + u64 rsvd:32; + } part; + u64 full; +}; + +union interrupt_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +union sst_imr_reg_mrfld { + struct { + u64 done_interrupt:1; + u64 busy_interrupt:1; + u64 rsvd:62; + } part; + u64 full; +}; + +/** + * struct sst_block - This structure is used to block a user/fw data call to another + * fw/user call + * + * @condition: condition for blocking check + * @ret_code: ret code when block is released + * @data: data ptr + * @size: size of data + * @on: block condition + * @msg_id: msg_id = msgid in mfld/ctp, mrfld = NULL + * @drv_id: str_id in mfld/ctp, = drv_id in mrfld + * @node: list head node + */ +struct sst_block { + bool condition; + int ret_code; + void *data; + u32 size; + bool on; + u32 msg_id; + u32 drv_id; + struct list_head node; +}; + +/** + * struct stream_info - structure that holds the stream information + * + * @status : stream current state + * @prev : stream prev state + * @resume_status : stream current state to restore on resume + * @resume_prev : stream prev state to restore on resume + * @lock : stream mutex for protecting state + * @alloc_param : parameters used for stream (re-)allocation + * @pcm_substream : PCM substream + * @period_elapsed : PCM period elapsed callback + * @sfreq : stream sampling freq + * @cumm_bytes : cummulative bytes decoded + */ +struct stream_info { + unsigned int status; + unsigned int prev; + unsigned int resume_status; + unsigned int resume_prev; + struct mutex lock; + struct snd_sst_alloc_mrfld alloc_param; + + void *pcm_substream; + void (*period_elapsed)(void *pcm_substream); + + unsigned int sfreq; + u32 cumm_bytes; + + void *compr_cb_param; + void (*compr_cb)(void *compr_cb_param); + + void *drain_cb_param; + void (*drain_notify)(void *drain_cb_param); + + unsigned int num_ch; + unsigned int pipe_id; + unsigned int task_id; +}; + +#define SST_FW_SIGN "$SST" +#define SST_FW_LIB_SIGN "$LIB" + +/** + * struct sst_fw_header - FW file headers + * + * @signature : FW signature + * @file_size: size of fw image + * @modules : # of modules + * @file_format : version of header format + * @reserved : reserved fields + */ +struct sst_fw_header { + unsigned char signature[FW_SIGNATURE_SIZE]; + u32 file_size; + u32 modules; + u32 file_format; + u32 reserved[4]; +}; + +/** + * struct fw_module_header - module header in FW + * + * @signature: module signature + * @mod_size: size of module + * @blocks: block count + * @type: block type + * @entry_point: module netry point + */ +struct fw_module_header { + unsigned char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; + u32 type; + u32 entry_point; +}; + +/** + * struct fw_block_info - block header for FW + * + * @type: block ram type I/D + * @size: size of block + * @ram_offset: offset in ram + */ +struct fw_block_info { + enum sst_ram_type type; + u32 size; + u32 ram_offset; + u32 rsvd; +}; + +struct sst_runtime_param { + struct snd_sst_runtime_params param; +}; + +struct sst_sg_list { + struct scatterlist *src; + struct scatterlist *dst; + int list_len; + unsigned int sg_idx; +}; + +struct sst_memcpy_list { + struct list_head memcpylist; + void *dstn; + const void *src; + u32 size; + bool is_io; +}; + +/*Firmware Module Information*/ +enum sst_lib_dwnld_status { + SST_LIB_NOT_FOUND = 0, + SST_LIB_FOUND, + SST_LIB_DOWNLOADED, +}; + +struct sst_module_info { + const char *name; /*Library name*/ + u32 id; /*Module ID*/ + u32 entry_pt; /*Module entry point*/ + u8 status; /*module status*/ + u8 rsvd1; + u16 rsvd2; +}; + +/* + * Structure for managing the Library Region(1.5MB) + * in DDR in Merrifield + */ +struct sst_mem_mgr { + phys_addr_t current_base; + int avail; + unsigned int count; +}; + +struct sst_ipc_reg { + int ipcx; + int ipcd; +}; + +struct sst_fw_save { + void *iram; /* allocated via kvmalloc() */ + void *dram; /* allocated via kvmalloc() */ + void *sram; /* allocated via kvmalloc() */ + void *ddr; /* allocated via kvmalloc() */ +}; + +/** + * struct intel_sst_drv - driver ops + * + * @sst_state : current sst device state + * @dev_id : device identifier, pci_id for pci devices and acpi_id for acpi + * devices + * @shim : SST shim pointer + * @mailbox : SST mailbox pointer + * @iram : SST IRAM pointer + * @dram : SST DRAM pointer + * @pdata : SST info passed as a part of pci platform data + * @shim_phy_add : SST shim phy addr + * @ipc_dispatch_list : ipc messages dispatched + * @rx_list : to copy the process_reply/process_msg from DSP + * @ipc_post_msg_wq : wq to post IPC messages context + * @mad_ops : MAD driver operations registered + * @mad_wq : MAD driver wq + * @post_msg_wq : wq to post IPC messages + * @streams : sst stream contexts + * @list_lock : sst driver list lock (deprecated) + * @ipc_spin_lock : spin lock to handle audio shim access and ipc queue + * @block_lock : spin lock to add block to block_list and assign pvt_id + * @rx_msg_lock : spin lock to handle the rx messages from the DSP + * @scard_ops : sst card ops + * @pci : sst pci device struture + * @dev : pointer to current device struct + * @sst_lock : sst device lock + * @pvt_id : sst private id + * @stream_cnt : total sst active stream count + * @pb_streams : total active pb streams + * @cp_streams : total active cp streams + * @audio_start : audio status + * @qos : PM Qos struct + * firmware_name : Firmware / Library name + */ +struct intel_sst_drv { + int sst_state; + int irq_num; + unsigned int dev_id; + void __iomem *ddr; + void __iomem *shim; + void __iomem *mailbox; + void __iomem *iram; + void __iomem *dram; + unsigned int mailbox_add; + unsigned int iram_base; + unsigned int dram_base; + unsigned int shim_phy_add; + unsigned int iram_end; + unsigned int dram_end; + unsigned int ddr_end; + unsigned int ddr_base; + unsigned int mailbox_recv_offset; + struct list_head block_list; + struct list_head ipc_dispatch_list; + struct sst_platform_info *pdata; + struct list_head rx_list; + struct work_struct ipc_post_msg_wq; + wait_queue_head_t wait_queue; + struct workqueue_struct *post_msg_wq; + unsigned int tstamp; + /* str_id 0 is not used */ + struct stream_info streams[MAX_NUM_STREAMS+1]; + spinlock_t ipc_spin_lock; + spinlock_t block_lock; + spinlock_t rx_msg_lock; + struct pci_dev *pci; + struct device *dev; + volatile long unsigned pvt_id; + struct mutex sst_lock; + unsigned int stream_cnt; + unsigned int csr_value; + void *fw_in_mem; + struct sst_sg_list fw_sg_list, library_list; + struct intel_sst_ops *ops; + struct sst_info info; + struct pm_qos_request *qos; + unsigned int use_dma; + unsigned int use_lli; + atomic_t fw_clear_context; + bool lib_dwnld_reqd; + struct list_head memcpy_list; + struct sst_ipc_reg ipc_reg; + struct sst_mem_mgr lib_mem_mgr; + /* + * Holder for firmware name. Due to async call it needs to be + * persistent till worker thread gets called + */ + char firmware_name[FW_NAME_SIZE]; + + struct snd_sst_fw_version fw_version; + struct sst_fw_save *fw_save; +}; + +/* misc definitions */ +#define FW_DWNL_ID 0x01 + +struct intel_sst_ops { + irqreturn_t (*interrupt)(int, void *); + irqreturn_t (*irq_thread)(int, void *); + void (*clear_interrupt)(struct intel_sst_drv *ctx); + int (*start)(struct intel_sst_drv *ctx); + int (*reset)(struct intel_sst_drv *ctx); + void (*process_reply)(struct intel_sst_drv *ctx, struct ipc_post *msg); + int (*post_message)(struct intel_sst_drv *ctx, + struct ipc_post *msg, bool sync); + void (*process_message)(struct ipc_post *msg); + void (*set_bypass)(bool set); + int (*save_dsp_context)(struct intel_sst_drv *sst); + void (*restore_dsp_context)(void); + int (*alloc_stream)(struct intel_sst_drv *ctx, void *params); + void (*post_download)(struct intel_sst_drv *sst); +}; + +int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); +int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int id); +int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id); +int sst_send_byte_stream_mrfld(struct intel_sst_drv *ctx, + struct snd_sst_bytes_v2 *sbytes); +int sst_set_stream_param(int str_id, struct snd_sst_params *str_param); +int sst_set_metadata(int str_id, char *params); +int sst_get_stream(struct intel_sst_drv *sst_drv_ctx, + struct snd_sst_params *str_param); +int sst_get_stream_allocated(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld); +int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, + int str_id, bool partial_drain); +int sst_post_message_mrfld(struct intel_sst_drv *ctx, + struct ipc_post *msg, bool sync); +void sst_process_reply_mrfld(struct intel_sst_drv *ctx, struct ipc_post *msg); +int sst_start_mrfld(struct intel_sst_drv *ctx); +int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *ctx); +void intel_sst_clear_intr_mrfld(struct intel_sst_drv *ctx); + +int sst_load_fw(struct intel_sst_drv *ctx); +int sst_load_library(struct snd_sst_lib_download *lib, u8 ops); +void sst_post_download_mrfld(struct intel_sst_drv *ctx); +int sst_get_block_stream(struct intel_sst_drv *sst_drv_ctx); +void sst_memcpy_free_resources(struct intel_sst_drv *ctx); + +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block); +int sst_create_ipc_msg(struct ipc_post **arg, bool large); +int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id); +void sst_clean_stream(struct stream_info *stream); +int intel_sst_register_compress(struct intel_sst_drv *sst); +int intel_sst_remove_compress(struct intel_sst_drv *sst); +void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id); +int sst_send_sync_msg(int ipc, int str_id); +int sst_get_num_channel(struct snd_sst_params *str_param); +int sst_get_sfreq(struct snd_sst_params *str_param); +int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params); +void sst_restore_fw_context(void); +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id); +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id); +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed); +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size); +int sst_request_firmware_async(struct intel_sst_drv *ctx); +int sst_driver_ops(struct intel_sst_drv *sst); +struct sst_platform_info *sst_get_acpi_driver_data(const char *hid); +void sst_firmware_load_cb(const struct firmware *fw, void *context); +int sst_prepare_and_post_msg(struct intel_sst_drv *sst, + int task_id, int ipc_msg, int cmd_id, int pipe_id, + size_t mbox_data_len, const void *mbox_data, void **data, + bool large, bool fill_dsp, bool sync, bool response); + +void sst_process_pending_msg(struct work_struct *work); +int sst_assign_pvt_id(struct intel_sst_drv *sst_drv_ctx); +int sst_validate_strid(struct intel_sst_drv *sst_drv_ctx, int str_id); +struct stream_info *get_stream_info(struct intel_sst_drv *sst_drv_ctx, + int str_id); +int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, + u32 pipe_id); +u32 relocate_imr_addr_mrfld(u32 base_addr); +void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, + struct ipc_post *msg); +int sst_pm_runtime_put(struct intel_sst_drv *sst_drv); +int sst_shim_write(void __iomem *addr, int offset, int value); +u32 sst_shim_read(void __iomem *addr, int offset); +u64 sst_reg_read64(void __iomem *addr, int offset); +int sst_shim_write64(void __iomem *addr, int offset, u64 value); +u64 sst_shim_read64(void __iomem *addr, int offset); +void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state); +void sst_fill_header_mrfld(union ipc_header_mrfld *header, + int msg, int task_id, int large, int drv_id); +void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, + int pipe_id, int len); + +int sst_register(struct device *); +int sst_unregister(struct device *); + +int sst_alloc_drv_context(struct intel_sst_drv **ctx, + struct device *dev, unsigned int dev_id); +int sst_context_init(struct intel_sst_drv *ctx); +void sst_context_cleanup(struct intel_sst_drv *ctx); +void sst_configure_runtime_pm(struct intel_sst_drv *ctx); +void memcpy32_toio(void __iomem *dst, const void *src, int count); +void memcpy32_fromio(void *dst, const void __iomem *src, int count); + +#endif diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c new file mode 100644 index 000000000..c90b04cc0 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -0,0 +1,423 @@ +/* + * sst_acpi.c - SST (LPE) driver init file for ACPI enumeration. + * + * Copyright (c) 2013, Intel Corporation. + * + * Authors: Ramesh Babu K V + * Authors: Omair Mohammed Abdullah + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "../../common/sst-dsp.h" +#include "sst.h" + +/* LPE viewpoint addresses */ +#define SST_BYT_IRAM_PHY_START 0xff2c0000 +#define SST_BYT_IRAM_PHY_END 0xff2d4000 +#define SST_BYT_DRAM_PHY_START 0xff300000 +#define SST_BYT_DRAM_PHY_END 0xff320000 +#define SST_BYT_IMR_VIRT_START 0xc0000000 /* virtual addr in LPE */ +#define SST_BYT_IMR_VIRT_END 0xc01fffff +#define SST_BYT_SHIM_PHY_ADDR 0xff340000 +#define SST_BYT_MBOX_PHY_ADDR 0xff344000 +#define SST_BYT_DMA0_PHY_ADDR 0xff298000 +#define SST_BYT_DMA1_PHY_ADDR 0xff29c000 +#define SST_BYT_SSP0_PHY_ADDR 0xff2a0000 +#define SST_BYT_SSP2_PHY_ADDR 0xff2a2000 + +#define BYT_FW_MOD_TABLE_OFFSET 0x80000 +#define BYT_FW_MOD_TABLE_SIZE 0x100 +#define BYT_FW_MOD_OFFSET (BYT_FW_MOD_TABLE_OFFSET + BYT_FW_MOD_TABLE_SIZE) + +static const struct sst_info byt_fwparse_info = { + .use_elf = false, + .max_streams = 25, + .iram_start = SST_BYT_IRAM_PHY_START, + .iram_end = SST_BYT_IRAM_PHY_END, + .iram_use = true, + .dram_start = SST_BYT_DRAM_PHY_START, + .dram_end = SST_BYT_DRAM_PHY_END, + .dram_use = true, + .imr_start = SST_BYT_IMR_VIRT_START, + .imr_end = SST_BYT_IMR_VIRT_END, + .imr_use = true, + .mailbox_start = SST_BYT_MBOX_PHY_ADDR, + .num_probes = 0, + .lpe_viewpt_rqd = true, +}; + +static const struct sst_ipc_info byt_ipc_info = { + .ipc_offset = 0, + .mbox_recv_off = 0x400, +}; + +static const struct sst_lib_dnld_info byt_lib_dnld_info = { + .mod_base = SST_BYT_IMR_VIRT_START, + .mod_end = SST_BYT_IMR_VIRT_END, + .mod_table_offset = BYT_FW_MOD_TABLE_OFFSET, + .mod_table_size = BYT_FW_MOD_TABLE_SIZE, + .mod_ddr_dnld = false, +}; + +static const struct sst_res_info byt_rvp_res_info = { + .shim_offset = 0x140000, + .shim_size = 0x000100, + .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR, + .ssp0_offset = 0xa0000, + .ssp0_size = 0x1000, + .dma0_offset = 0x98000, + .dma0_size = 0x4000, + .dma1_offset = 0x9c000, + .dma1_size = 0x4000, + .iram_offset = 0x0c0000, + .iram_size = 0x14000, + .dram_offset = 0x100000, + .dram_size = 0x28000, + .mbox_offset = 0x144000, + .mbox_size = 0x1000, + .acpi_lpe_res_index = 0, + .acpi_ddr_index = 2, + .acpi_ipc_irq_index = 5, +}; + +/* BYTCR has different BIOS from BYT */ +static const struct sst_res_info bytcr_res_info = { + .shim_offset = 0x140000, + .shim_size = 0x000100, + .shim_phy_addr = SST_BYT_SHIM_PHY_ADDR, + .ssp0_offset = 0xa0000, + .ssp0_size = 0x1000, + .dma0_offset = 0x98000, + .dma0_size = 0x4000, + .dma1_offset = 0x9c000, + .dma1_size = 0x4000, + .iram_offset = 0x0c0000, + .iram_size = 0x14000, + .dram_offset = 0x100000, + .dram_size = 0x28000, + .mbox_offset = 0x144000, + .mbox_size = 0x1000, + .acpi_lpe_res_index = 0, + .acpi_ddr_index = 2, + .acpi_ipc_irq_index = 0 +}; + +static struct sst_platform_info byt_rvp_platform_data = { + .probe_data = &byt_fwparse_info, + .ipc_info = &byt_ipc_info, + .lib_info = &byt_lib_dnld_info, + .res_info = &byt_rvp_res_info, + .platform = "sst-mfld-platform", + .streams_lost_on_suspend = true, +}; + +/* Cherryview (Cherrytrail and Braswell) uses same mrfld dpcm fw as Baytrail, + * so pdata is same as Baytrail, minus the streams_lost_on_suspend quirk. + */ +static struct sst_platform_info chv_platform_data = { + .probe_data = &byt_fwparse_info, + .ipc_info = &byt_ipc_info, + .lib_info = &byt_lib_dnld_info, + .res_info = &byt_rvp_res_info, + .platform = "sst-mfld-platform", +}; + +static int sst_platform_get_resources(struct intel_sst_drv *ctx) +{ + struct resource *rsrc; + struct platform_device *pdev = to_platform_device(ctx->dev); + + /* All ACPI resource request here */ + /* Get Shim addr */ + rsrc = platform_get_resource(pdev, IORESOURCE_MEM, + ctx->pdata->res_info->acpi_lpe_res_index); + if (!rsrc) { + dev_err(ctx->dev, "Invalid SHIM base from IFWI\n"); + return -EIO; + } + dev_info(ctx->dev, "LPE base: %#x size:%#x", (unsigned int) rsrc->start, + (unsigned int)resource_size(rsrc)); + + ctx->iram_base = rsrc->start + ctx->pdata->res_info->iram_offset; + ctx->iram_end = ctx->iram_base + ctx->pdata->res_info->iram_size - 1; + dev_info(ctx->dev, "IRAM base: %#x", ctx->iram_base); + ctx->iram = devm_ioremap_nocache(ctx->dev, ctx->iram_base, + ctx->pdata->res_info->iram_size); + if (!ctx->iram) { + dev_err(ctx->dev, "unable to map IRAM\n"); + return -EIO; + } + + ctx->dram_base = rsrc->start + ctx->pdata->res_info->dram_offset; + ctx->dram_end = ctx->dram_base + ctx->pdata->res_info->dram_size - 1; + dev_info(ctx->dev, "DRAM base: %#x", ctx->dram_base); + ctx->dram = devm_ioremap_nocache(ctx->dev, ctx->dram_base, + ctx->pdata->res_info->dram_size); + if (!ctx->dram) { + dev_err(ctx->dev, "unable to map DRAM\n"); + return -EIO; + } + + ctx->shim_phy_add = rsrc->start + ctx->pdata->res_info->shim_offset; + dev_info(ctx->dev, "SHIM base: %#x", ctx->shim_phy_add); + ctx->shim = devm_ioremap_nocache(ctx->dev, ctx->shim_phy_add, + ctx->pdata->res_info->shim_size); + if (!ctx->shim) { + dev_err(ctx->dev, "unable to map SHIM\n"); + return -EIO; + } + + /* reassign physical address to LPE viewpoint address */ + ctx->shim_phy_add = ctx->pdata->res_info->shim_phy_addr; + + /* Get mailbox addr */ + ctx->mailbox_add = rsrc->start + ctx->pdata->res_info->mbox_offset; + dev_info(ctx->dev, "Mailbox base: %#x", ctx->mailbox_add); + ctx->mailbox = devm_ioremap_nocache(ctx->dev, ctx->mailbox_add, + ctx->pdata->res_info->mbox_size); + if (!ctx->mailbox) { + dev_err(ctx->dev, "unable to map mailbox\n"); + return -EIO; + } + + /* reassign physical address to LPE viewpoint address */ + ctx->mailbox_add = ctx->info.mailbox_start; + + rsrc = platform_get_resource(pdev, IORESOURCE_MEM, + ctx->pdata->res_info->acpi_ddr_index); + if (!rsrc) { + dev_err(ctx->dev, "Invalid DDR base from IFWI\n"); + return -EIO; + } + ctx->ddr_base = rsrc->start; + ctx->ddr_end = rsrc->end; + dev_info(ctx->dev, "DDR base: %#x", ctx->ddr_base); + ctx->ddr = devm_ioremap_nocache(ctx->dev, ctx->ddr_base, + resource_size(rsrc)); + if (!ctx->ddr) { + dev_err(ctx->dev, "unable to map DDR\n"); + return -EIO; + } + + /* Find the IRQ */ + ctx->irq_num = platform_get_irq(pdev, + ctx->pdata->res_info->acpi_ipc_irq_index); + if (ctx->irq_num <= 0) + return ctx->irq_num < 0 ? ctx->irq_num : -EIO; + + return 0; +} + +static int is_byt(void) +{ + bool status = false; + static const struct x86_cpu_id cpu_ids[] = { + { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ + {} + }; + if (x86_match_cpu(cpu_ids)) + status = true; + return status; +} + +static int is_byt_cr(struct device *dev, bool *bytcr) +{ + int status = 0; + + if (IS_ENABLED(CONFIG_IOSF_MBI)) { + u32 bios_status; + + if (!is_byt() || !iosf_mbi_available()) { + /* bail silently */ + return status; + } + + status = iosf_mbi_read(BT_MBI_UNIT_PMC, /* 0x04 PUNIT */ + MBI_REG_READ, /* 0x10 */ + 0x006, /* BIOS_CONFIG */ + &bios_status); + + if (status) { + dev_err(dev, "could not read PUNIT BIOS_CONFIG\n"); + } else { + /* bits 26:27 mirror PMIC options */ + bios_status = (bios_status >> 26) & 3; + + if ((bios_status == 1) || (bios_status == 3)) + *bytcr = true; + else + dev_info(dev, "BYT-CR not detected\n"); + } + } else { + dev_info(dev, "IOSF_MBI not enabled, no BYT-CR detection\n"); + } + return status; +} + + +static int sst_acpi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0; + struct intel_sst_drv *ctx; + const struct acpi_device_id *id; + struct snd_soc_acpi_mach *mach; + struct platform_device *mdev; + struct platform_device *plat_dev; + struct sst_platform_info *pdata; + unsigned int dev_id; + bool bytcr = false; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return -ENODEV; + dev_dbg(dev, "for %s\n", id->id); + + mach = (struct snd_soc_acpi_mach *)id->driver_data; + mach = snd_soc_acpi_find_machine(mach); + if (mach == NULL) { + dev_err(dev, "No matching machine driver found\n"); + return -ENODEV; + } + + if (is_byt()) + mach->pdata = &byt_rvp_platform_data; + else + mach->pdata = &chv_platform_data; + pdata = mach->pdata; + + ret = kstrtouint(id->id, 16, &dev_id); + if (ret < 0) { + dev_err(dev, "Unique device id conversion error: %d\n", ret); + return ret; + } + + dev_dbg(dev, "ACPI device id: %x\n", dev_id); + + ret = sst_alloc_drv_context(&ctx, dev, dev_id); + if (ret < 0) + return ret; + + ret = is_byt_cr(dev, &bytcr); + if (!((ret < 0) || (bytcr == false))) { + dev_info(dev, "Detected Baytrail-CR platform\n"); + + /* override resource info */ + byt_rvp_platform_data.res_info = &bytcr_res_info; + } + + plat_dev = platform_device_register_data(dev, pdata->platform, -1, + NULL, 0); + if (IS_ERR(plat_dev)) { + dev_err(dev, "Failed to create machine device: %s\n", + pdata->platform); + return PTR_ERR(plat_dev); + } + + /* + * Create platform device for sst machine driver, + * pass machine info as pdata + */ + mdev = platform_device_register_data(dev, mach->drv_name, -1, + (const void *)mach, sizeof(*mach)); + if (IS_ERR(mdev)) { + dev_err(dev, "Failed to create machine device: %s\n", + mach->drv_name); + return PTR_ERR(mdev); + } + + /* Fill sst platform data */ + ctx->pdata = pdata; + strcpy(ctx->firmware_name, mach->fw_filename); + + ret = sst_platform_get_resources(ctx); + if (ret) + return ret; + + ret = sst_context_init(ctx); + if (ret < 0) + return ret; + + sst_configure_runtime_pm(ctx); + platform_set_drvdata(pdev, ctx); + return ret; +} + +/** +* intel_sst_remove - remove function +* +* @pdev: platform device structure +* +* This function is called by OS when a device is unloaded +* This frees the interrupt etc +*/ +static int sst_acpi_remove(struct platform_device *pdev) +{ + struct intel_sst_drv *ctx; + + ctx = platform_get_drvdata(pdev); + sst_context_cleanup(ctx); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct acpi_device_id sst_acpi_ids[] = { + { "80860F28", (unsigned long)&snd_soc_acpi_intel_baytrail_machines}, + { "808622A8", (unsigned long)&snd_soc_acpi_intel_cherrytrail_machines}, + { }, +}; + +MODULE_DEVICE_TABLE(acpi, sst_acpi_ids); + +static struct platform_driver sst_acpi_driver = { + .driver = { + .name = "intel_sst_acpi", + .acpi_match_table = ACPI_PTR(sst_acpi_ids), + .pm = &intel_sst_pm, + }, + .probe = sst_acpi_probe, + .remove = sst_acpi_remove, +}; + +module_platform_driver(sst_acpi_driver); + +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine ACPI Driver"); +MODULE_AUTHOR("Ramesh Babu K V"); +MODULE_AUTHOR("Omair Mohammed Abdullah"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/atom/sst/sst_drv_interface.c b/sound/soc/intel/atom/sst/sst_drv_interface.c new file mode 100644 index 000000000..5455d6e0a --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_drv_interface.c @@ -0,0 +1,728 @@ +/* + * sst_drv_interface.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + + + +#define NUM_CODEC 2 +#define MIN_FRAGMENT 2 +#define MAX_FRAGMENT 4 +#define MIN_FRAGMENT_SIZE (50 * 1024) +#define MAX_FRAGMENT_SIZE (1024 * 1024) +#define SST_GET_BYTES_PER_SAMPLE(pcm_wd_sz) (((pcm_wd_sz + 15) >> 4) << 1) +#ifdef CONFIG_PM +#define GET_USAGE_COUNT(dev) (atomic_read(&dev->power.usage_count)) +#else +#define GET_USAGE_COUNT(dev) 1 +#endif + +int free_stream_context(struct intel_sst_drv *ctx, unsigned int str_id) +{ + struct stream_info *stream; + int ret = 0; + + stream = get_stream_info(ctx, str_id); + if (stream) { + /* str_id is valid, so stream is alloacted */ + ret = sst_free_stream(ctx, str_id); + if (ret) + sst_clean_stream(&ctx->streams[str_id]); + return ret; + } else { + dev_err(ctx->dev, "we tried to free stream context %d which was freed!!!\n", str_id); + } + return ret; +} + +int sst_get_stream_allocated(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param, + struct snd_sst_lib_download **lib_dnld) +{ + int retval; + + retval = ctx->ops->alloc_stream(ctx, str_param); + if (retval > 0) + dev_dbg(ctx->dev, "Stream allocated %d\n", retval); + return retval; + +} + +/* + * sst_get_sfreq - this function returns the frequency of the stream + * + * @str_param : stream params + */ +int sst_get_sfreq(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.sfreq; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.externalsr; + case SST_CODEC_TYPE_MP3: + return 0; + default: + return -EINVAL; + } +} + +/* + * sst_get_num_channel - get number of channels for the stream + * + * @str_param : stream params + */ +int sst_get_num_channel(struct snd_sst_params *str_param) +{ + switch (str_param->codec) { + case SST_CODEC_TYPE_PCM: + return str_param->sparams.uc.pcm_params.num_chan; + case SST_CODEC_TYPE_MP3: + return str_param->sparams.uc.mp3_params.num_chan; + case SST_CODEC_TYPE_AAC: + return str_param->sparams.uc.aac_params.num_chan; + default: + return -EINVAL; + } +} + +/* + * sst_get_stream - this function prepares for stream allocation + * + * @str_param : stream param + */ +int sst_get_stream(struct intel_sst_drv *ctx, + struct snd_sst_params *str_param) +{ + int retval; + struct stream_info *str_info; + + /* stream is not allocated, we are allocating */ + retval = ctx->ops->alloc_stream(ctx, str_param); + if (retval <= 0) { + return -EIO; + } + /* store sampling freq */ + str_info = &ctx->streams[retval]; + str_info->sfreq = sst_get_sfreq(str_param); + + return retval; +} + +static int sst_power_control(struct device *dev, bool state) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + int ret = 0; + int usage_count = 0; + + if (state == true) { + ret = pm_runtime_get_sync(dev); + usage_count = GET_USAGE_COUNT(dev); + dev_dbg(ctx->dev, "Enable: pm usage count: %d\n", usage_count); + if (ret < 0) { + pm_runtime_put_sync(dev); + dev_err(ctx->dev, "Runtime get failed with err: %d\n", ret); + return ret; + } + if ((ctx->sst_state == SST_RESET) && (usage_count == 1)) { + ret = sst_load_fw(ctx); + if (ret) { + dev_err(dev, "FW download fail %d\n", ret); + sst_set_fw_state_locked(ctx, SST_RESET); + ret = sst_pm_runtime_put(ctx); + } + } + } else { + usage_count = GET_USAGE_COUNT(dev); + dev_dbg(ctx->dev, "Disable: pm usage count: %d\n", usage_count); + return sst_pm_runtime_put(ctx); + } + return ret; +} + +/* + * sst_open_pcm_stream - Open PCM interface + * + * @str_param: parameters of pcm stream + * + * This function is called by MID sound card driver to open + * a new pcm interface + */ +static int sst_open_pcm_stream(struct device *dev, + struct snd_sst_params *str_param) +{ + int retval; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (!str_param) + return -EINVAL; + + retval = sst_get_stream(ctx, str_param); + if (retval > 0) + ctx->stream_cnt++; + else + dev_err(ctx->dev, "sst_get_stream returned err %d\n", retval); + + return retval; +} + +static int sst_cdev_open(struct device *dev, + struct snd_sst_params *str_params, struct sst_compress_cb *cb) +{ + int str_id, retval; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + retval = pm_runtime_get_sync(ctx->dev); + if (retval < 0) { + pm_runtime_put_sync(ctx->dev); + return retval; + } + + str_id = sst_get_stream(ctx, str_params); + if (str_id > 0) { + dev_dbg(dev, "stream allocated in sst_cdev_open %d\n", str_id); + stream = &ctx->streams[str_id]; + stream->compr_cb = cb->compr_cb; + stream->compr_cb_param = cb->param; + stream->drain_notify = cb->drain_notify; + stream->drain_cb_param = cb->drain_cb_param; + } else { + dev_err(dev, "stream encountered error during alloc %d\n", str_id); + str_id = -EINVAL; + sst_pm_runtime_put(ctx); + } + return str_id; +} + +static int sst_cdev_close(struct device *dev, unsigned int str_id) +{ + int retval; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) { + dev_err(dev, "stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + retval = sst_free_stream(ctx, str_id); + stream->compr_cb_param = NULL; + stream->compr_cb = NULL; + + if (retval) + dev_err(dev, "free stream returned err %d\n", retval); + + dev_dbg(dev, "End\n"); + return retval; +} + +static int sst_cdev_ack(struct device *dev, unsigned int str_id, + unsigned long bytes) +{ + struct stream_info *stream; + struct snd_sst_tstamp fw_tstamp = {0,}; + int offset; + void __iomem *addr; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + /* update bytes sent */ + stream->cumm_bytes += bytes; + dev_dbg(dev, "bytes copied %d inc by %ld\n", stream->cumm_bytes, bytes); + + addr = ((void __iomem *)(ctx->mailbox + ctx->tstamp)) + + (str_id * sizeof(fw_tstamp)); + + memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp)); + + fw_tstamp.bytes_copied = stream->cumm_bytes; + dev_dbg(dev, "bytes sent to fw %llu inc by %ld\n", + fw_tstamp.bytes_copied, bytes); + + offset = offsetof(struct snd_sst_tstamp, bytes_copied); + sst_shim_write(addr, offset, fw_tstamp.bytes_copied); + return 0; +} + +static int sst_cdev_set_metadata(struct device *dev, + unsigned int str_id, struct snd_compr_metadata *metadata) +{ + int retval = 0; + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + dev_dbg(dev, "set metadata for stream %d\n", str_id); + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + + dev_dbg(dev, "pipe id = %d\n", str_info->pipe_id); + retval = sst_prepare_and_post_msg(ctx, str_info->task_id, IPC_CMD, + IPC_IA_SET_STREAM_PARAMS_MRFLD, str_info->pipe_id, + sizeof(*metadata), metadata, NULL, + true, true, true, false); + + return retval; +} + +static int sst_cdev_stream_pause(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_pause_stream(ctx, str_id); +} + +static int sst_cdev_stream_pause_release(struct device *dev, + unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_resume_stream(ctx, str_id); +} + +static int sst_cdev_stream_start(struct device *dev, unsigned int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + return sst_start_stream(ctx, str_id); +} + +static int sst_cdev_stream_drop(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drop_stream(ctx, str_id); +} + +static int sst_cdev_stream_drain(struct device *dev, unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drain_stream(ctx, str_id, false); +} + +static int sst_cdev_stream_partial_drain(struct device *dev, + unsigned int str_id) +{ + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + return sst_drain_stream(ctx, str_id, true); +} + +static int sst_cdev_tstamp(struct device *dev, unsigned int str_id, + struct snd_compr_tstamp *tstamp) +{ + struct snd_sst_tstamp fw_tstamp = {0,}; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + void __iomem *addr; + + addr = (void __iomem *)(ctx->mailbox + ctx->tstamp) + + (str_id * sizeof(fw_tstamp)); + + memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp)); + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + dev_dbg(dev, "rb_counter %llu in bytes\n", fw_tstamp.ring_buffer_counter); + + tstamp->copied_total = fw_tstamp.ring_buffer_counter; + tstamp->pcm_frames = fw_tstamp.frames_decoded; + tstamp->pcm_io_frames = div_u64(fw_tstamp.hardware_counter, + (u64)stream->num_ch * SST_GET_BYTES_PER_SAMPLE(24)); + tstamp->sampling_rate = fw_tstamp.sampling_frequency; + + dev_dbg(dev, "PCM = %u\n", tstamp->pcm_io_frames); + dev_dbg(dev, "Ptr Query on strid = %d copied_total %d, decodec %d\n", + str_id, tstamp->copied_total, tstamp->pcm_frames); + dev_dbg(dev, "rendered %d\n", tstamp->pcm_io_frames); + + return 0; +} + +static int sst_cdev_caps(struct snd_compr_caps *caps) +{ + caps->num_codecs = NUM_CODEC; + caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ + caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ + caps->min_fragments = MIN_FRAGMENT; + caps->max_fragments = MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + return 0; +} + +static const struct snd_compr_codec_caps caps_mp3 = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + +static const struct snd_compr_codec_caps caps_aac = { + .num_descriptors = 2, + .descriptor[1].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[1].bit_rate[0] = 320, + .descriptor[1].bit_rate[1] = 192, + .descriptor[1].num_bitrates = 2, + .descriptor[1].profiles = 0, + .descriptor[1].modes = 0, + .descriptor[1].formats = + (SND_AUDIOSTREAMFORMAT_MP4ADTS | + SND_AUDIOSTREAMFORMAT_RAW), +}; + +static int sst_cdev_codec_caps(struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_MP3) + *codec = caps_mp3; + else if (codec->codec == SND_AUDIOCODEC_AAC) + *codec = caps_aac; + else + return -EINVAL; + + return 0; +} + +void sst_cdev_fragment_elapsed(struct intel_sst_drv *ctx, int str_id) +{ + struct stream_info *stream; + + dev_dbg(ctx->dev, "fragment elapsed from firmware for str_id %d\n", + str_id); + stream = &ctx->streams[str_id]; + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); +} + +/* + * sst_close_pcm_stream - Close PCM interface + * + * @str_id: stream id to be closed + * + * This function is called by MID sound card driver to close + * an existing pcm interface + */ +static int sst_close_pcm_stream(struct device *dev, unsigned int str_id) +{ + struct stream_info *stream; + int retval = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + stream = get_stream_info(ctx, str_id); + if (!stream) { + dev_err(ctx->dev, "stream info is NULL for str %d!!!\n", str_id); + return -EINVAL; + } + + retval = free_stream_context(ctx, str_id); + stream->pcm_substream = NULL; + stream->status = STREAM_UN_INIT; + stream->period_elapsed = NULL; + ctx->stream_cnt--; + + if (retval) + dev_err(ctx->dev, "free stream returned err %d\n", retval); + + dev_dbg(ctx->dev, "Exit\n"); + return 0; +} + +static inline int sst_calc_tstamp(struct intel_sst_drv *ctx, + struct pcm_stream_info *info, + struct snd_pcm_substream *substream, + struct snd_sst_tstamp *fw_tstamp) +{ + size_t delay_bytes, delay_frames; + size_t buffer_sz; + u32 pointer_bytes, pointer_samples; + + dev_dbg(ctx->dev, "mrfld ring_buffer_counter %llu in bytes\n", + fw_tstamp->ring_buffer_counter); + dev_dbg(ctx->dev, "mrfld hardware_counter %llu in bytes\n", + fw_tstamp->hardware_counter); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay_bytes = (size_t) (fw_tstamp->ring_buffer_counter - + fw_tstamp->hardware_counter); + else + delay_bytes = (size_t) (fw_tstamp->hardware_counter - + fw_tstamp->ring_buffer_counter); + delay_frames = bytes_to_frames(substream->runtime, delay_bytes); + buffer_sz = snd_pcm_lib_buffer_bytes(substream); + div_u64_rem(fw_tstamp->ring_buffer_counter, buffer_sz, &pointer_bytes); + pointer_samples = bytes_to_samples(substream->runtime, pointer_bytes); + + dev_dbg(ctx->dev, "pcm delay %zu in bytes\n", delay_bytes); + + info->buffer_ptr = pointer_samples / substream->runtime->channels; + + info->pcm_delay = delay_frames; + dev_dbg(ctx->dev, "buffer ptr %llu pcm_delay rep: %llu\n", + info->buffer_ptr, info->pcm_delay); + return 0; +} + +static int sst_read_timestamp(struct device *dev, struct pcm_stream_info *info) +{ + struct stream_info *stream; + struct snd_pcm_substream *substream; + struct snd_sst_tstamp fw_tstamp; + unsigned int str_id; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + void __iomem *addr; + + str_id = info->str_id; + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + if (!stream->pcm_substream) + return -EINVAL; + substream = stream->pcm_substream; + + addr = (void __iomem *)(ctx->mailbox + ctx->tstamp) + + (str_id * sizeof(fw_tstamp)); + + memcpy_fromio(&fw_tstamp, addr, sizeof(fw_tstamp)); + + return sst_calc_tstamp(ctx, info, substream, &fw_tstamp); +} + +static int sst_stream_start(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = str_info->status; + str_info->status = STREAM_RUNNING; + sst_start_stream(ctx, str_id); + + return 0; +} + +static int sst_stream_drop(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + return sst_drop_stream(ctx, str_id); +} + +static int sst_stream_pause(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + + return sst_pause_stream(ctx, str_id); +} + +static int sst_stream_resume(struct device *dev, int str_id) +{ + struct stream_info *str_info; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + str_info = get_stream_info(ctx, str_id); + if (!str_info) + return -EINVAL; + return sst_resume_stream(ctx, str_id); +} + +static int sst_stream_init(struct device *dev, struct pcm_stream_info *str_info) +{ + int str_id = 0; + struct stream_info *stream; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + str_id = str_info->str_id; + + if (ctx->sst_state != SST_FW_RUNNING) + return 0; + + stream = get_stream_info(ctx, str_id); + if (!stream) + return -EINVAL; + + dev_dbg(ctx->dev, "setting the period ptrs\n"); + stream->pcm_substream = str_info->arg; + stream->period_elapsed = str_info->period_elapsed; + stream->sfreq = str_info->sfreq; + stream->prev = stream->status; + stream->status = STREAM_INIT; + dev_dbg(ctx->dev, + "pcm_substream %p, period_elapsed %p, sfreq %d, status %d\n", + stream->pcm_substream, stream->period_elapsed, + stream->sfreq, stream->status); + + return 0; +} + +/* + * sst_set_byte_stream - Set generic params + * + * @cmd: control cmd to be set + * @arg: command argument + * + * This function is called by MID sound card driver to configure + * SST runtime params. + */ +static int sst_send_byte_stream(struct device *dev, + struct snd_sst_bytes_v2 *bytes) +{ + int ret_val = 0; + struct intel_sst_drv *ctx = dev_get_drvdata(dev); + + if (NULL == bytes) + return -EINVAL; + ret_val = pm_runtime_get_sync(ctx->dev); + if (ret_val < 0) { + pm_runtime_put_sync(ctx->dev); + return ret_val; + } + + ret_val = sst_send_byte_stream_mrfld(ctx, bytes); + sst_pm_runtime_put(ctx); + + return ret_val; +} + +static struct sst_ops pcm_ops = { + .open = sst_open_pcm_stream, + .stream_init = sst_stream_init, + .stream_start = sst_stream_start, + .stream_drop = sst_stream_drop, + .stream_pause = sst_stream_pause, + .stream_pause_release = sst_stream_resume, + .stream_read_tstamp = sst_read_timestamp, + .send_byte_stream = sst_send_byte_stream, + .close = sst_close_pcm_stream, + .power = sst_power_control, +}; + +static struct compress_sst_ops compr_ops = { + .open = sst_cdev_open, + .close = sst_cdev_close, + .stream_pause = sst_cdev_stream_pause, + .stream_pause_release = sst_cdev_stream_pause_release, + .stream_start = sst_cdev_stream_start, + .stream_drop = sst_cdev_stream_drop, + .stream_drain = sst_cdev_stream_drain, + .stream_partial_drain = sst_cdev_stream_partial_drain, + .tstamp = sst_cdev_tstamp, + .ack = sst_cdev_ack, + .get_caps = sst_cdev_caps, + .get_codec_caps = sst_cdev_codec_caps, + .set_metadata = sst_cdev_set_metadata, + .power = sst_power_control, +}; + +static struct sst_device sst_dsp_device = { + .name = "Intel(R) SST LPE", + .dev = NULL, + .ops = &pcm_ops, + .compr_ops = &compr_ops, +}; + +/* + * sst_register - function to register DSP + * + * This functions registers DSP with the platform driver + */ +int sst_register(struct device *dev) +{ + int ret_val; + + sst_dsp_device.dev = dev; + ret_val = sst_register_dsp(&sst_dsp_device); + if (ret_val) + dev_err(dev, "Unable to register DSP with platform driver\n"); + + return ret_val; +} + +int sst_unregister(struct device *dev) +{ + return sst_unregister_dsp(&sst_dsp_device); +} diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c new file mode 100644 index 000000000..20b01e02e --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_ipc.c @@ -0,0 +1,384 @@ +/* + * sst_ipc.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corporation + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +struct sst_block *sst_create_block(struct intel_sst_drv *ctx, + u32 msg_id, u32 drv_id) +{ + struct sst_block *msg = NULL; + + dev_dbg(ctx->dev, "Enter\n"); + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + return NULL; + msg->condition = false; + msg->on = true; + msg->msg_id = msg_id; + msg->drv_id = drv_id; + spin_lock_bh(&ctx->block_lock); + list_add_tail(&msg->node, &ctx->block_list); + spin_unlock_bh(&ctx->block_lock); + + return msg; +} + +/* + * while handling the interrupts, we need to check for message status and + * then if we are blocking for a message + * + * here we are unblocking the blocked ones, this is based on id we have + * passed and search that for block threads. + * We will not find block in two cases + * a) when its small message and block in not there, so silently ignore + * them + * b) when we are actually not able to find the block (bug perhaps) + * + * Since we have bit of small messages we can spam kernel log with err + * print on above so need to keep as debug prints which should be enabled + * via dynamic debug while debugging IPC issues + */ +int sst_wake_up_block(struct intel_sst_drv *ctx, int result, + u32 drv_id, u32 ipc, void *data, u32 size) +{ + struct sst_block *block = NULL; + + dev_dbg(ctx->dev, "Enter\n"); + + spin_lock_bh(&ctx->block_lock); + list_for_each_entry(block, &ctx->block_list, node) { + dev_dbg(ctx->dev, "Block ipc %d, drv_id %d\n", block->msg_id, + block->drv_id); + if (block->msg_id == ipc && block->drv_id == drv_id) { + dev_dbg(ctx->dev, "free up the block\n"); + block->ret_code = result; + block->data = data; + block->size = size; + block->condition = true; + spin_unlock_bh(&ctx->block_lock); + wake_up(&ctx->wait_queue); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + dev_dbg(ctx->dev, + "Block not found or a response received for a short msg for ipc %d, drv_id %d\n", + ipc, drv_id); + return -EINVAL; +} + +int sst_free_block(struct intel_sst_drv *ctx, struct sst_block *freed) +{ + struct sst_block *block = NULL, *__block; + + dev_dbg(ctx->dev, "Enter\n"); + spin_lock_bh(&ctx->block_lock); + list_for_each_entry_safe(block, __block, &ctx->block_list, node) { + if (block == freed) { + pr_debug("pvt_id freed --> %d\n", freed->drv_id); + /* toggle the index position of pvt_id */ + list_del(&freed->node); + spin_unlock_bh(&ctx->block_lock); + kfree(freed->data); + freed->data = NULL; + kfree(freed); + return 0; + } + } + spin_unlock_bh(&ctx->block_lock); + dev_err(ctx->dev, "block is already freed!!!\n"); + return -EINVAL; +} + +int sst_post_message_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *ipc_msg, bool sync) +{ + struct ipc_post *msg = ipc_msg; + union ipc_header_mrfld header; + unsigned int loop_count = 0; + int retval = 0; + unsigned long irq_flags; + + dev_dbg(sst_drv_ctx->dev, "Enter: sync: %d\n", sync); + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + if (sync) { + while (header.p.header_high.part.busy) { + if (loop_count > 25) { + dev_err(sst_drv_ctx->dev, + "sst: Busy wait failed, cant send this msg\n"); + retval = -EBUSY; + goto out; + } + cpu_relax(); + loop_count++; + header.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCX); + } + } else { + if (list_empty(&sst_drv_ctx->ipc_dispatch_list)) { + /* queue is empty, nothing to send */ + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + dev_dbg(sst_drv_ctx->dev, + "Empty msg queue... NO Action\n"); + return 0; + } + + if (header.p.header_high.part.busy) { + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + dev_dbg(sst_drv_ctx->dev, "Busy not free... post later\n"); + return 0; + } + + /* copy msg from list */ + msg = list_entry(sst_drv_ctx->ipc_dispatch_list.next, + struct ipc_post, node); + list_del(&msg->node); + } + dev_dbg(sst_drv_ctx->dev, "sst: Post message: header = %x\n", + msg->mrfld_header.p.header_high.full); + dev_dbg(sst_drv_ctx->dev, "sst: size = 0x%x\n", + msg->mrfld_header.p.header_low_payload); + + if (msg->mrfld_header.p.header_high.part.large) + memcpy_toio(sst_drv_ctx->mailbox + SST_MAILBOX_SEND, + msg->mailbox_data, + msg->mrfld_header.p.header_low_payload); + + sst_shim_write64(sst_drv_ctx->shim, SST_IPCX, msg->mrfld_header.full); + +out: + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + kfree(msg->mailbox_data); + kfree(msg); + return retval; +} + +void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; + unsigned long irq_flags; + + spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + + /* write 1 to clear*/ + isr.part.busy_interrupt = 1; + sst_shim_write64(sst_drv_ctx->shim, SST_ISRX, isr.full); + + /* Set IA done bit */ + clear_ipc.full = sst_shim_read64(sst_drv_ctx->shim, SST_IPCD); + + clear_ipc.p.header_high.part.busy = 0; + clear_ipc.p.header_high.part.done = 1; + clear_ipc.p.header_low_payload = IPC_ACK_SUCCESS; + sst_shim_write64(sst_drv_ctx->shim, SST_IPCD, clear_ipc.full); + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); + spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); +} + + +/* + * process_fw_init - process the FW init msg + * + * @msg: IPC message mailbox data from FW + * + * This function processes the FW init msg from FW + * marks FW state and prints debug info of loaded FW + */ +static void process_fw_init(struct intel_sst_drv *sst_drv_ctx, + void *msg) +{ + struct ipc_header_fw_init *init = + (struct ipc_header_fw_init *)msg; + int retval = 0; + + dev_dbg(sst_drv_ctx->dev, "*** FW Init msg came***\n"); + if (init->result) { + sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); + dev_err(sst_drv_ctx->dev, "FW Init failed, Error %x\n", + init->result); + retval = init->result; + goto ret; + } + if (memcmp(&sst_drv_ctx->fw_version, &init->fw_version, + sizeof(init->fw_version))) + dev_info(sst_drv_ctx->dev, "FW Version %02x.%02x.%02x.%02x\n", + init->fw_version.type, init->fw_version.major, + init->fw_version.minor, init->fw_version.build); + dev_dbg(sst_drv_ctx->dev, "Build date %s Time %s\n", + init->build_info.date, init->build_info.time); + + /* Save FW version */ + sst_drv_ctx->fw_version.type = init->fw_version.type; + sst_drv_ctx->fw_version.major = init->fw_version.major; + sst_drv_ctx->fw_version.minor = init->fw_version.minor; + sst_drv_ctx->fw_version.build = init->fw_version.build; + +ret: + sst_wake_up_block(sst_drv_ctx, retval, FW_DWNL_ID, 0 , NULL, 0); +} + +static void process_fw_async_msg(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *msg) +{ + u32 msg_id; + int str_id; + u32 data_size, i; + void *data_offset; + struct stream_info *stream; + u32 msg_low, pipe_id; + + msg_low = msg->mrfld_header.p.header_low_payload; + msg_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->cmd_id; + data_offset = (msg->mailbox_data + sizeof(struct ipc_dsp_hdr)); + data_size = msg_low - (sizeof(struct ipc_dsp_hdr)); + + switch (msg_id) { + case IPC_SST_PERIOD_ELAPSED_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) { + dev_dbg(sst_drv_ctx->dev, + "Period elapsed rcvd for pipe id 0x%x\n", + pipe_id); + stream = &sst_drv_ctx->streams[str_id]; + /* If stream is dropped, skip processing this message*/ + if (stream->status == STREAM_INIT) + break; + if (stream->period_elapsed) + stream->period_elapsed(stream->pcm_substream); + if (stream->compr_cb) + stream->compr_cb(stream->compr_cb_param); + } + break; + + case IPC_IA_DRAIN_STREAM_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) { + stream = &sst_drv_ctx->streams[str_id]; + if (stream->drain_notify) + stream->drain_notify(stream->drain_cb_param); + } + break; + + case IPC_IA_FW_ASYNC_ERR_MRFLD: + dev_err(sst_drv_ctx->dev, "FW sent async error msg:\n"); + for (i = 0; i < (data_size/4); i++) + print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, + 16, 4, data_offset, data_size, false); + break; + + case IPC_IA_FW_INIT_CMPLT_MRFLD: + process_fw_init(sst_drv_ctx, data_offset); + break; + + case IPC_IA_BUF_UNDER_RUN_MRFLD: + pipe_id = ((struct ipc_dsp_hdr *)msg->mailbox_data)->pipe_id; + str_id = get_stream_id_mrfld(sst_drv_ctx, pipe_id); + if (str_id > 0) + dev_err(sst_drv_ctx->dev, + "Buffer under-run for pipe:%#x str_id:%d\n", + pipe_id, str_id); + break; + + default: + dev_err(sst_drv_ctx->dev, + "Unrecognized async msg from FW msg_id %#x\n", msg_id); + } +} + +void sst_process_reply_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct ipc_post *msg) +{ + unsigned int drv_id; + void *data; + union ipc_header_high msg_high; + u32 msg_low; + struct ipc_dsp_hdr *dsp_hdr; + + msg_high = msg->mrfld_header.p.header_high; + msg_low = msg->mrfld_header.p.header_low_payload; + + dev_dbg(sst_drv_ctx->dev, "IPC process message header %x payload %x\n", + msg->mrfld_header.p.header_high.full, + msg->mrfld_header.p.header_low_payload); + + drv_id = msg_high.part.drv_id; + + /* Check for async messages first */ + if (drv_id == SST_ASYNC_DRV_ID) { + /*FW sent async large message*/ + process_fw_async_msg(sst_drv_ctx, msg); + return; + } + + /* FW sent short error response for an IPC */ + if (msg_high.part.result && drv_id && !msg_high.part.large) { + /* 32-bit FW error code in msg_low */ + dev_err(sst_drv_ctx->dev, "FW sent error response 0x%x", msg_low); + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + return; + } + + /* + * Process all valid responses + * if it is a large message, the payload contains the size to + * copy from mailbox + **/ + if (msg_high.part.large) { + data = kmemdup((void *)msg->mailbox_data, msg_low, GFP_KERNEL); + if (!data) + return; + /* Copy command id so that we can use to put sst to reset */ + dsp_hdr = (struct ipc_dsp_hdr *)data; + dev_dbg(sst_drv_ctx->dev, "cmd_id %d\n", dsp_hdr->cmd_id); + if (sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, data, msg_low)) + kfree(data); + } else { + sst_wake_up_block(sst_drv_ctx, msg_high.part.result, + msg_high.part.drv_id, + msg_high.part.msg_id, NULL, 0); + } + +} diff --git a/sound/soc/intel/atom/sst/sst_loader.c b/sound/soc/intel/atom/sst/sst_loader.c new file mode 100644 index 000000000..b8c456753 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_loader.c @@ -0,0 +1,462 @@ +/* + * sst_dsp.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file contains all dsp controlling functions like firmware download, + * setting/resetting dsp cores, etc + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +void memcpy32_toio(void __iomem *dst, const void *src, int count) +{ + /* __iowrite32_copy uses 32-bit count values so divide by 4 for + * right count in words + */ + __iowrite32_copy(dst, src, count / 4); +} + +void memcpy32_fromio(void *dst, const void __iomem *src, int count) +{ + /* __ioread32_copy uses 32-bit count values so divide by 4 for + * right count in words + */ + __ioread32_copy(dst, src, count / 4); +} + +/** + * intel_sst_reset_dsp_mrfld - Resetting SST DSP + * + * This resets DSP in case of MRFLD platfroms + */ +int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union config_status_reg_mrfld csr; + + dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full &= ~(0x1); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + return 0; +} + +/** + * sst_start_merrifield - Start the SST DSP processor + * + * This starts the DSP in MERRIFIELD platfroms + */ +int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx) +{ + union config_status_reg_mrfld csr; + + dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n"); + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.full |= 0x7; + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); + + csr.part.xt_snoop = 1; + csr.full &= ~(0x5); + sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); + + csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); + dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n", + csr.full); + return 0; +} + +static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size, + struct fw_module_header **module, u32 *num_modules) +{ + struct sst_fw_header *header; + const void *sst_fw_in_mem = ctx->fw_in_mem; + + dev_dbg(ctx->dev, "Enter\n"); + + /* Read the header information from the data pointer */ + header = (struct sst_fw_header *)sst_fw_in_mem; + dev_dbg(ctx->dev, + "header sign=%s size=%x modules=%x fmt=%x size=%zx\n", + header->signature, header->file_size, header->modules, + header->file_format, sizeof(*header)); + + /* verify FW */ + if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) || + (size != header->file_size + sizeof(*header))) { + /* Invalid FW signature */ + dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n"); + return -EINVAL; + } + *num_modules = header->modules; + *module = (void *)sst_fw_in_mem + sizeof(*header); + + return 0; +} + +/* + * sst_fill_memcpy_list - Fill the memcpy list + * + * @memcpy_list: List to be filled + * @destn: Destination addr to be filled in the list + * @src: Source addr to be filled in the list + * @size: Size to be filled in the list + * + * Adds the node to the list after required fields + * are populated in the node + */ +static int sst_fill_memcpy_list(struct list_head *memcpy_list, + void *destn, const void *src, u32 size, bool is_io) +{ + struct sst_memcpy_list *listnode; + + listnode = kzalloc(sizeof(*listnode), GFP_KERNEL); + if (listnode == NULL) + return -ENOMEM; + listnode->dstn = destn; + listnode->src = src; + listnode->size = size; + listnode->is_io = is_io; + list_add_tail(&listnode->memcpylist, memcpy_list); + + return 0; +} + +/** + * sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list + * + * @sst_drv_ctx : driver context + * @module : FW module header + * @memcpy_list : Pointer to the list to be populated + * Create the memcpy list as the number of block to be copied + * returns error or 0 if module sizes are proper + */ +static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx, + struct fw_module_header *module, struct list_head *memcpy_list) +{ + struct fw_block_info *block; + u32 count; + int ret_val = 0; + void __iomem *ram_iomem; + + dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n", + module->signature, module->mod_size, + module->blocks, module->type); + dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point); + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + if (block->size <= 0) { + dev_err(sst_drv_ctx->dev, "block size invalid\n"); + return -EINVAL; + } + switch (block->type) { + case SST_IRAM: + ram_iomem = sst_drv_ctx->iram; + break; + case SST_DRAM: + ram_iomem = sst_drv_ctx->dram; + break; + case SST_DDR: + ram_iomem = sst_drv_ctx->ddr; + break; + case SST_CUSTOM_INFO: + block = (void *)block + sizeof(*block) + block->size; + continue; + default: + dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n", + block->type, count); + return -EINVAL; + } + + ret_val = sst_fill_memcpy_list(memcpy_list, + ram_iomem + block->ram_offset, + (void *)block + sizeof(*block), block->size, 1); + if (ret_val) + return ret_val; + + block = (void *)block + sizeof(*block) + block->size; + } + return 0; +} + +/** + * sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy + * + * @ctx : pointer to drv context + * @size : size of the firmware + * @fw_list : pointer to list_head to be populated + * This function parses the FW image and saves the parsed image in the list + * for memcpy + */ +static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size, + struct list_head *fw_list) +{ + struct fw_module_header *module; + u32 count, num_modules; + int ret_val; + + ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules); + if (ret_val) + return ret_val; + + for (count = 0; count < num_modules; count++) { + ret_val = sst_parse_module_memcpy(ctx, module, fw_list); + if (ret_val) + return ret_val; + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +} + +/** + * sst_do_memcpy - function initiates the memcpy + * + * @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated + * + * Triggers the memcpy + */ +static void sst_do_memcpy(struct list_head *memcpy_list) +{ + struct sst_memcpy_list *listnode; + + list_for_each_entry(listnode, memcpy_list, memcpylist) { + if (listnode->is_io == true) + memcpy32_toio((void __iomem *)listnode->dstn, + listnode->src, listnode->size); + else + memcpy(listnode->dstn, listnode->src, listnode->size); + } +} + +void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx) +{ + struct sst_memcpy_list *listnode, *tmplistnode; + + /* Free the list */ + if (!list_empty(&sst_drv_ctx->memcpy_list)) { + list_for_each_entry_safe(listnode, tmplistnode, + &sst_drv_ctx->memcpy_list, memcpylist) { + list_del(&listnode->memcpylist); + kfree(listnode); + } + } +} + +static int sst_cache_and_parse_fw(struct intel_sst_drv *sst, + const struct firmware *fw) +{ + int retval = 0; + + sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL); + if (!sst->fw_in_mem) { + retval = -ENOMEM; + goto end_release; + } + dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem); + dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem)); + memcpy(sst->fw_in_mem, fw->data, fw->size); + retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list); + if (retval) { + dev_err(sst->dev, "Failed to parse fw\n"); + kfree(sst->fw_in_mem); + sst->fw_in_mem = NULL; + } + +end_release: + release_firmware(fw); + return retval; + +} + +void sst_firmware_load_cb(const struct firmware *fw, void *context) +{ + struct intel_sst_drv *ctx = context; + + dev_dbg(ctx->dev, "Enter\n"); + + if (fw == NULL) { + dev_err(ctx->dev, "request fw failed\n"); + return; + } + + mutex_lock(&ctx->sst_lock); + + if (ctx->sst_state != SST_RESET || + ctx->fw_in_mem != NULL) { + release_firmware(fw); + mutex_unlock(&ctx->sst_lock); + return; + } + + dev_dbg(ctx->dev, "Request Fw completed\n"); + sst_cache_and_parse_fw(ctx, fw); + mutex_unlock(&ctx->sst_lock); +} + +/* + * sst_request_fw - requests audio fw from kernel and saves a copy + * + * This function requests the SST FW from the kernel, parses it and + * saves a copy in the driver context + */ +static int sst_request_fw(struct intel_sst_drv *sst) +{ + int retval = 0; + const struct firmware *fw; + + retval = request_firmware(&fw, sst->firmware_name, sst->dev); + if (retval) { + dev_err(sst->dev, "request fw failed %d\n", retval); + return retval; + } + if (fw == NULL) { + dev_err(sst->dev, "fw is returning as null\n"); + return -EINVAL; + } + mutex_lock(&sst->sst_lock); + retval = sst_cache_and_parse_fw(sst, fw); + mutex_unlock(&sst->sst_lock); + + return retval; +} + +/* + * Writing the DDR physical base to DCCM offset + * so that FW can use it to setup TLB + */ +static void sst_dccm_config_write(void __iomem *dram_base, + unsigned int ddr_base) +{ + void __iomem *addr; + u32 bss_reset = 0; + + addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET); + memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32)); + bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT); + addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET); + memcpy32_toio(addr, &bss_reset, sizeof(u32)); + +} + +void sst_post_download_mrfld(struct intel_sst_drv *ctx) +{ + sst_dccm_config_write(ctx->dram, ctx->ddr_base); + dev_dbg(ctx->dev, "config written to DCCM\n"); +} + +/** + * sst_load_fw - function to load FW into DSP + * Transfers the FW to DSP using dma/memcpy + */ +int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) +{ + int ret_val = 0; + struct sst_block *block; + + dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n"); + + if (sst_drv_ctx->sst_state != SST_RESET || + sst_drv_ctx->sst_state == SST_SHUTDOWN) + return -EAGAIN; + + if (!sst_drv_ctx->fw_in_mem) { + dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n"); + ret_val = sst_request_fw(sst_drv_ctx); + if (ret_val) + return ret_val; + } + + block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID); + if (block == NULL) + return -ENOMEM; + + /* Prevent C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, 0); + + sst_drv_ctx->sst_state = SST_FW_LOADING; + + ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx); + if (ret_val) + goto restore; + + sst_do_memcpy(&sst_drv_ctx->memcpy_list); + + /* Write the DRAM/DCCM config before enabling FW */ + if (sst_drv_ctx->ops->post_download) + sst_drv_ctx->ops->post_download(sst_drv_ctx); + + /* bring sst out of reset */ + ret_val = sst_drv_ctx->ops->start(sst_drv_ctx); + if (ret_val) + goto restore; + + ret_val = sst_wait_timeout(sst_drv_ctx, block); + if (ret_val) { + dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val); + /* FW download failed due to timeout */ + ret_val = -EBUSY; + + } + + +restore: + /* Re-enable Deeper C-states beyond C6 */ + pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); + sst_free_block(sst_drv_ctx, block); + dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n"); + + if (sst_drv_ctx->ops->restore_dsp_context) + sst_drv_ctx->ops->restore_dsp_context(); + sst_drv_ctx->sst_state = SST_FW_RUNNING; + return ret_val; +} + diff --git a/sound/soc/intel/atom/sst/sst_pci.c b/sound/soc/intel/atom/sst/sst_pci.c new file mode 100644 index 000000000..438c7bcd8 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_pci.c @@ -0,0 +1,209 @@ +/* + * sst_pci.c - SST (LPE) driver init file for pci enumeration. + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" + +static int sst_platform_get_resources(struct intel_sst_drv *ctx) +{ + int ddr_base, ret = 0; + struct pci_dev *pci = ctx->pci; + + ret = pci_request_regions(pci, SST_DRV_NAME); + if (ret) + return ret; + + /* map registers */ + /* DDR base */ + if (ctx->dev_id == SST_MRFLD_PCI_ID) { + ctx->ddr_base = pci_resource_start(pci, 0); + /* check that the relocated IMR base matches with FW Binary */ + ddr_base = relocate_imr_addr_mrfld(ctx->ddr_base); + if (!ctx->pdata->lib_info) { + dev_err(ctx->dev, "lib_info pointer NULL\n"); + ret = -EINVAL; + goto do_release_regions; + } + if (ddr_base != ctx->pdata->lib_info->mod_base) { + dev_err(ctx->dev, + "FW LSP DDR BASE does not match with IFWI\n"); + ret = -EINVAL; + goto do_release_regions; + } + ctx->ddr_end = pci_resource_end(pci, 0); + + ctx->ddr = pcim_iomap(pci, 0, + pci_resource_len(pci, 0)); + if (!ctx->ddr) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "sst: DDR Ptr %p\n", ctx->ddr); + } else { + ctx->ddr = NULL; + } + /* SHIM */ + ctx->shim_phy_add = pci_resource_start(pci, 1); + ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1)); + if (!ctx->shim) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "SST Shim Ptr %p\n", ctx->shim); + + /* Shared SRAM */ + ctx->mailbox_add = pci_resource_start(pci, 2); + ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2)); + if (!ctx->mailbox) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "SRAM Ptr %p\n", ctx->mailbox); + + /* IRAM */ + ctx->iram_end = pci_resource_end(pci, 3); + ctx->iram_base = pci_resource_start(pci, 3); + ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3)); + if (!ctx->iram) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "IRAM Ptr %p\n", ctx->iram); + + /* DRAM */ + ctx->dram_end = pci_resource_end(pci, 4); + ctx->dram_base = pci_resource_start(pci, 4); + ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4)); + if (!ctx->dram) { + ret = -EINVAL; + goto do_release_regions; + } + dev_dbg(ctx->dev, "DRAM Ptr %p\n", ctx->dram); +do_release_regions: + pci_release_regions(pci); + return ret; +} + +/* + * intel_sst_probe - PCI probe function + * + * @pci: PCI device structure + * @pci_id: PCI device ID structure + * + */ +static int intel_sst_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + int ret = 0; + struct intel_sst_drv *sst_drv_ctx; + struct sst_platform_info *sst_pdata = pci->dev.platform_data; + + dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device); + ret = sst_alloc_drv_context(&sst_drv_ctx, &pci->dev, pci->device); + if (ret < 0) + return ret; + + sst_drv_ctx->pdata = sst_pdata; + sst_drv_ctx->irq_num = pci->irq; + snprintf(sst_drv_ctx->firmware_name, sizeof(sst_drv_ctx->firmware_name), + "%s%04x%s", "fw_sst_", + sst_drv_ctx->dev_id, ".bin"); + + ret = sst_context_init(sst_drv_ctx); + if (ret < 0) + return ret; + + /* Init the device */ + ret = pcim_enable_device(pci); + if (ret) { + dev_err(sst_drv_ctx->dev, + "device can't be enabled. Returned err: %d\n", ret); + goto do_free_drv_ctx; + } + sst_drv_ctx->pci = pci_dev_get(pci); + ret = sst_platform_get_resources(sst_drv_ctx); + if (ret < 0) + goto do_free_drv_ctx; + + pci_set_drvdata(pci, sst_drv_ctx); + sst_configure_runtime_pm(sst_drv_ctx); + + return ret; + +do_free_drv_ctx: + sst_context_cleanup(sst_drv_ctx); + dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret); + return ret; +} + +/** + * intel_sst_remove - PCI remove function + * + * @pci: PCI device structure + * + * This function is called by OS when a device is unloaded + * This frees the interrupt etc + */ +static void intel_sst_remove(struct pci_dev *pci) +{ + struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci); + + sst_context_cleanup(sst_drv_ctx); + pci_dev_put(sst_drv_ctx->pci); + pci_release_regions(pci); + pci_set_drvdata(pci, NULL); +} + +/* PCI Routines */ +static const struct pci_device_id intel_sst_ids[] = { + { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0}, + { 0, } +}; + +static struct pci_driver sst_driver = { + .name = SST_DRV_NAME, + .id_table = intel_sst_ids, + .probe = intel_sst_probe, + .remove = intel_sst_remove, +#ifdef CONFIG_PM + .driver = { + .pm = &intel_sst_pm, + }, +#endif +}; + +module_pci_driver(sst_driver); + +MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine PCI Driver"); +MODULE_AUTHOR("Vinod Koul "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_AUTHOR("Dharageswari R "); +MODULE_AUTHOR("KP Jeeja "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("sst"); diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c new file mode 100644 index 000000000..af93244b4 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_pvt.c @@ -0,0 +1,415 @@ +/* + * sst_pvt.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +int sst_shim_write(void __iomem *addr, int offset, int value) +{ + writel(value, addr + offset); + return 0; +} + +u32 sst_shim_read(void __iomem *addr, int offset) +{ + return readl(addr + offset); +} + +u64 sst_reg_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + + return val; +} + +int sst_shim_write64(void __iomem *addr, int offset, u64 value) +{ + memcpy_toio(addr + offset, &value, sizeof(value)); + return 0; +} + +u64 sst_shim_read64(void __iomem *addr, int offset) +{ + u64 val = 0; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + return val; +} + +void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) +{ + mutex_lock(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; + mutex_unlock(&sst_drv_ctx->sst_lock); +} + +/* + * sst_wait_interruptible - wait on event + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits without a timeout (and is interruptable) for a + * given block event + */ +int sst_wait_interruptible(struct intel_sst_drv *sst_drv_ctx, + struct sst_block *block) +{ + int retval = 0; + + if (!wait_event_interruptible(sst_drv_ctx->wait_queue, + block->condition)) { + /* event wake */ + if (block->ret_code < 0) { + dev_err(sst_drv_ctx->dev, + "stream failed %d\n", block->ret_code); + retval = -EBUSY; + } else { + dev_dbg(sst_drv_ctx->dev, "event up\n"); + retval = 0; + } + } else { + dev_err(sst_drv_ctx->dev, "signal interrupted\n"); + retval = -EINTR; + } + return retval; + +} + +/* + * sst_wait_timeout - wait on event for timeout + * + * @sst_drv_ctx: Driver context + * @block: Driver block to wait on + * + * This function waits with a timeout value (and is not interruptible) on a + * given block event + */ +int sst_wait_timeout(struct intel_sst_drv *sst_drv_ctx, struct sst_block *block) +{ + int retval = 0; + + /* + * NOTE: + * Observed that FW processes the alloc msg and replies even + * before the alloc thread has finished execution + */ + dev_dbg(sst_drv_ctx->dev, + "waiting for condition %x ipc %d drv_id %d\n", + block->condition, block->msg_id, block->drv_id); + if (wait_event_timeout(sst_drv_ctx->wait_queue, + block->condition, + msecs_to_jiffies(SST_BLOCK_TIMEOUT))) { + /* event wake */ + dev_dbg(sst_drv_ctx->dev, "Event wake %x\n", + block->condition); + dev_dbg(sst_drv_ctx->dev, "message ret: %d\n", + block->ret_code); + retval = -block->ret_code; + } else { + block->on = false; + dev_err(sst_drv_ctx->dev, + "Wait timed-out condition:%#x, msg_id:%#x fw_state %#x\n", + block->condition, block->msg_id, sst_drv_ctx->sst_state); + sst_drv_ctx->sst_state = SST_RESET; + + retval = -EBUSY; + } + return retval; +} + +/* + * sst_create_ipc_msg - create a IPC message + * + * @arg: ipc message + * @large: large or short message + * + * this function allocates structures to send a large or short + * message to the firmware + */ +int sst_create_ipc_msg(struct ipc_post **arg, bool large) +{ + struct ipc_post *msg; + + msg = kzalloc(sizeof(struct ipc_post), GFP_ATOMIC); + if (!msg) + return -ENOMEM; + if (large) { + msg->mailbox_data = kzalloc(SST_MAILBOX_SIZE, GFP_ATOMIC); + if (!msg->mailbox_data) { + kfree(msg); + return -ENOMEM; + } + } else { + msg->mailbox_data = NULL; + } + msg->is_large = large; + *arg = msg; + return 0; +} + +/* + * sst_create_block_and_ipc_msg - Creates IPC message and sst block + * @arg: passed to sst_create_ipc_message API + * @large: large or short message + * @sst_drv_ctx: sst driver context + * @block: return block allocated + * @msg_id: IPC + * @drv_id: stream id or private id + */ +int sst_create_block_and_ipc_msg(struct ipc_post **arg, bool large, + struct intel_sst_drv *sst_drv_ctx, struct sst_block **block, + u32 msg_id, u32 drv_id) +{ + int retval = 0; + + retval = sst_create_ipc_msg(arg, large); + if (retval) + return retval; + *block = sst_create_block(sst_drv_ctx, msg_id, drv_id); + if (*block == NULL) { + kfree(*arg); + return -ENOMEM; + } + return retval; +} + +/* + * sst_clean_stream - clean the stream context + * + * @stream: stream structure + * + * this function resets the stream contexts + * should be called in free + */ +void sst_clean_stream(struct stream_info *stream) +{ + stream->status = STREAM_UN_INIT; + stream->prev = STREAM_UN_INIT; + mutex_lock(&stream->lock); + stream->cumm_bytes = 0; + mutex_unlock(&stream->lock); +} + +int sst_prepare_and_post_msg(struct intel_sst_drv *sst, + int task_id, int ipc_msg, int cmd_id, int pipe_id, + size_t mbox_data_len, const void *mbox_data, void **data, + bool large, bool fill_dsp, bool sync, bool response) +{ + struct ipc_post *msg = NULL; + struct ipc_dsp_hdr dsp_hdr; + struct sst_block *block; + int ret = 0, pvt_id; + + pvt_id = sst_assign_pvt_id(sst); + if (pvt_id < 0) + return pvt_id; + + if (response) + ret = sst_create_block_and_ipc_msg( + &msg, large, sst, &block, ipc_msg, pvt_id); + else + ret = sst_create_ipc_msg(&msg, large); + + if (ret < 0) { + test_and_clear_bit(pvt_id, &sst->pvt_id); + return -ENOMEM; + } + + dev_dbg(sst->dev, "pvt_id = %d, pipe id = %d, task = %d ipc_msg: %d\n", + pvt_id, pipe_id, task_id, ipc_msg); + sst_fill_header_mrfld(&msg->mrfld_header, ipc_msg, + task_id, large, pvt_id); + msg->mrfld_header.p.header_low_payload = sizeof(dsp_hdr) + mbox_data_len; + msg->mrfld_header.p.header_high.part.res_rqd = !sync; + dev_dbg(sst->dev, "header:%x\n", + msg->mrfld_header.p.header_high.full); + dev_dbg(sst->dev, "response rqd: %x", + msg->mrfld_header.p.header_high.part.res_rqd); + dev_dbg(sst->dev, "msg->mrfld_header.p.header_low_payload:%d", + msg->mrfld_header.p.header_low_payload); + if (fill_dsp) { + sst_fill_header_dsp(&dsp_hdr, cmd_id, pipe_id, mbox_data_len); + memcpy(msg->mailbox_data, &dsp_hdr, sizeof(dsp_hdr)); + if (mbox_data_len) { + memcpy(msg->mailbox_data + sizeof(dsp_hdr), + mbox_data, mbox_data_len); + } + } + + if (sync) + sst->ops->post_message(sst, msg, true); + else + sst_add_to_dispatch_list_and_post(sst, msg); + + if (response) { + ret = sst_wait_timeout(sst, block); + if (ret < 0) + goto out; + + if (data && block->data) { + *data = kmemdup(block->data, block->size, GFP_KERNEL); + if (!*data) { + ret = -ENOMEM; + goto out; + } + } + } +out: + if (response) + sst_free_block(sst, block); + test_and_clear_bit(pvt_id, &sst->pvt_id); + return ret; +} + +int sst_pm_runtime_put(struct intel_sst_drv *sst_drv) +{ + int ret; + + pm_runtime_mark_last_busy(sst_drv->dev); + ret = pm_runtime_put_autosuspend(sst_drv->dev); + if (ret < 0) + return ret; + return 0; +} + +void sst_fill_header_mrfld(union ipc_header_mrfld *header, + int msg, int task_id, int large, int drv_id) +{ + header->full = 0; + header->p.header_high.part.msg_id = msg; + header->p.header_high.part.task_id = task_id; + header->p.header_high.part.large = large; + header->p.header_high.part.drv_id = drv_id; + header->p.header_high.part.done = 0; + header->p.header_high.part.busy = 1; + header->p.header_high.part.res_rqd = 1; +} + +void sst_fill_header_dsp(struct ipc_dsp_hdr *dsp, int msg, + int pipe_id, int len) +{ + dsp->cmd_id = msg; + dsp->mod_index_id = 0xff; + dsp->pipe_id = pipe_id; + dsp->length = len; + dsp->mod_id = 0; +} + +#define SST_MAX_BLOCKS 15 +/* + * sst_assign_pvt_id - assign a pvt id for stream + * + * @sst_drv_ctx : driver context + * + * this function assigns a private id for calls that dont have stream + * context yet, should be called with lock held + * uses bits for the id, and finds first free bits and assigns that + */ +int sst_assign_pvt_id(struct intel_sst_drv *drv) +{ + int local; + + spin_lock(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ + spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); + spin_unlock(&drv->block_lock); + return local; +} + +int sst_validate_strid( + struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + if (str_id <= 0 || str_id > sst_drv_ctx->info.max_streams) { + dev_err(sst_drv_ctx->dev, + "SST ERR: invalid stream id : %d, max %d\n", + str_id, sst_drv_ctx->info.max_streams); + return -EINVAL; + } + + return 0; +} + +struct stream_info *get_stream_info( + struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + if (sst_validate_strid(sst_drv_ctx, str_id)) + return NULL; + return &sst_drv_ctx->streams[str_id]; +} + +int get_stream_id_mrfld(struct intel_sst_drv *sst_drv_ctx, + u32 pipe_id) +{ + int i; + + for (i = 1; i <= sst_drv_ctx->info.max_streams; i++) + if (pipe_id == sst_drv_ctx->streams[i].pipe_id) + return i; + + dev_dbg(sst_drv_ctx->dev, "no such pipe_id(%u)", pipe_id); + return -1; +} + +u32 relocate_imr_addr_mrfld(u32 base_addr) +{ + /* Get the difference from 512MB aligned base addr */ + /* relocate the base */ + base_addr = MRFLD_FW_VIRTUAL_BASE + (base_addr % (512 * 1024 * 1024)); + return base_addr; +} +EXPORT_SYMBOL_GPL(relocate_imr_addr_mrfld); + +void sst_add_to_dispatch_list_and_post(struct intel_sst_drv *sst, + struct ipc_post *msg) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&sst->ipc_spin_lock, irq_flags); + list_add_tail(&msg->node, &sst->ipc_dispatch_list); + spin_unlock_irqrestore(&sst->ipc_spin_lock, irq_flags); + sst->ops->post_message(sst, NULL, false); +} diff --git a/sound/soc/intel/atom/sst/sst_stream.c b/sound/soc/intel/atom/sst/sst_stream.c new file mode 100644 index 000000000..107271f7d --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_stream.c @@ -0,0 +1,473 @@ +/* + * sst_stream.c - Intel SST Driver for audio engine + * + * Copyright (C) 2008-14 Intel Corp + * Authors: Vinod Koul + * Harsha Priya + * Dharageswari R + * KP Jeeja + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../sst-mfld-platform.h" +#include "sst.h" +#include "../../common/sst-dsp.h" + +int sst_alloc_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, void *params) +{ + struct snd_pcm_params *pcm_params; + struct snd_sst_params *str_params; + struct snd_sst_tstamp fw_tstamp; + struct stream_info *str_info; + int i, num_ch, str_id; + + dev_dbg(sst_drv_ctx->dev, "Enter\n"); + + str_params = (struct snd_sst_params *)params; + str_id = str_params->stream_id; + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + + memset(&str_info->alloc_param, 0, sizeof(str_info->alloc_param)); + str_info->alloc_param.operation = str_params->ops; + str_info->alloc_param.codec_type = str_params->codec; + str_info->alloc_param.sg_count = str_params->aparams.sg_count; + str_info->alloc_param.ring_buf_info[0].addr = + str_params->aparams.ring_buf_info[0].addr; + str_info->alloc_param.ring_buf_info[0].size = + str_params->aparams.ring_buf_info[0].size; + str_info->alloc_param.frag_size = str_params->aparams.frag_size; + + memcpy(&str_info->alloc_param.codec_params, &str_params->sparams, + sizeof(struct snd_sst_stream_params)); + + /* + * fill channel map params for multichannel support. + * Ideally channel map should be received from upper layers + * for multichannel support. + * Currently hardcoding as per FW reqm. + */ + num_ch = sst_get_num_channel(str_params); + pcm_params = &str_info->alloc_param.codec_params.uc.pcm_params; + for (i = 0; i < 8; i++) { + if (i < num_ch) + pcm_params->channel_map[i] = i; + else + pcm_params->channel_map[i] = 0xff; + } + + sst_drv_ctx->streams[str_id].status = STREAM_INIT; + sst_drv_ctx->streams[str_id].prev = STREAM_UN_INIT; + sst_drv_ctx->streams[str_id].pipe_id = str_params->device_type; + sst_drv_ctx->streams[str_id].task_id = str_params->task; + sst_drv_ctx->streams[str_id].num_ch = num_ch; + + if (sst_drv_ctx->info.lpe_viewpt_rqd) + str_info->alloc_param.ts = sst_drv_ctx->info.mailbox_start + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + else + str_info->alloc_param.ts = sst_drv_ctx->mailbox_add + + sst_drv_ctx->tstamp + (str_id * sizeof(fw_tstamp)); + + dev_dbg(sst_drv_ctx->dev, "alloc tstamp location = 0x%x\n", + str_info->alloc_param.ts); + dev_dbg(sst_drv_ctx->dev, "assigned pipe id 0x%x to task %d\n", + str_info->pipe_id, str_info->task_id); + + return sst_realloc_stream(sst_drv_ctx, str_id); +} + +/** + * sst_realloc_stream - Send msg for (re-)allocating a stream using the + * @sst_drv_ctx intel_sst_drv context pointer + * @str_id: stream ID + * + * Send a msg for (re-)allocating a stream using the parameters previously + * passed to sst_alloc_stream_mrfld() for the same stream ID. + * Return: 0 or negative errno value. + */ +int sst_realloc_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + struct snd_sst_alloc_response *response; + struct stream_info *str_info; + void *data = NULL; + int ret; + + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + + dev_dbg(sst_drv_ctx->dev, "Alloc for str %d pipe %#x\n", + str_id, str_info->pipe_id); + + ret = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_ALLOC_STREAM_MRFLD, str_info->pipe_id, + sizeof(str_info->alloc_param), &str_info->alloc_param, + &data, true, true, false, true); + + if (ret < 0) { + dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); + /* alloc failed, so reset the state to uninit */ + str_info->status = STREAM_UN_INIT; + str_id = ret; + } else if (data) { + response = (struct snd_sst_alloc_response *)data; + ret = response->str_type.result; + if (!ret) + goto out; + dev_err(sst_drv_ctx->dev, "FW alloc failed ret %d\n", ret); + if (ret == SST_ERR_STREAM_IN_USE) { + dev_err(sst_drv_ctx->dev, + "FW not in clean state, send free for:%d\n", str_id); + sst_free_stream(sst_drv_ctx, str_id); + } + str_id = -ret; + } +out: + kfree(data); + return str_id; +} + +/** +* sst_start_stream - Send msg for a starting stream +* @str_id: stream ID +* +* This function is called by any function which wants to start +* a stream. +*/ +int sst_start_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + u16 data = 0; + + dev_dbg(sst_drv_ctx->dev, "sst_start_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status != STREAM_RUNNING) + return -EBADRQC; + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_START_STREAM_MRFLD, str_info->pipe_id, + sizeof(u16), &data, NULL, true, true, true, false); + + return retval; +} + +int sst_send_byte_stream_mrfld(struct intel_sst_drv *sst_drv_ctx, + struct snd_sst_bytes_v2 *bytes) +{ struct ipc_post *msg = NULL; + u32 length; + int pvt_id, ret = 0; + struct sst_block *block = NULL; + + dev_dbg(sst_drv_ctx->dev, + "type:%u ipc_msg:%u block:%u task_id:%u pipe: %#x length:%#x\n", + bytes->type, bytes->ipc_msg, bytes->block, bytes->task_id, + bytes->pipe_id, bytes->len); + + if (sst_create_ipc_msg(&msg, true)) + return -ENOMEM; + + pvt_id = sst_assign_pvt_id(sst_drv_ctx); + sst_fill_header_mrfld(&msg->mrfld_header, bytes->ipc_msg, + bytes->task_id, 1, pvt_id); + msg->mrfld_header.p.header_high.part.res_rqd = bytes->block; + length = bytes->len; + msg->mrfld_header.p.header_low_payload = length; + dev_dbg(sst_drv_ctx->dev, "length is %d\n", length); + memcpy(msg->mailbox_data, &bytes->bytes, bytes->len); + if (bytes->block) { + block = sst_create_block(sst_drv_ctx, bytes->ipc_msg, pvt_id); + if (block == NULL) { + kfree(msg); + ret = -ENOMEM; + goto out; + } + } + + sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg); + dev_dbg(sst_drv_ctx->dev, "msg->mrfld_header.p.header_low_payload:%d", + msg->mrfld_header.p.header_low_payload); + + if (bytes->block) { + ret = sst_wait_timeout(sst_drv_ctx, block); + if (ret) { + dev_err(sst_drv_ctx->dev, "fw returned err %d\n", ret); + sst_free_block(sst_drv_ctx, block); + goto out; + } + } + if (bytes->type == SND_SST_BYTES_GET) { + /* + * copy the reply and send back + * we need to update only sz and payload + */ + if (bytes->block) { + unsigned char *r = block->data; + + dev_dbg(sst_drv_ctx->dev, "read back %d bytes", + bytes->len); + memcpy(bytes->bytes, r, bytes->len); + } + } + if (bytes->block) + sst_free_block(sst_drv_ctx, block); +out: + test_and_clear_bit(pvt_id, &sst_drv_ctx->pvt_id); + return ret; +} + +/** + * sst_pause_stream - Send msg for a pausing stream + * @str_id: stream ID + * + * This function is called by any function which wants to pause + * an already running stream. + */ +int sst_pause_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_pause_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status == STREAM_PAUSED) + return 0; + if (str_info->status == STREAM_RUNNING || + str_info->status == STREAM_INIT) { + if (str_info->prev == STREAM_UN_INIT) + return -EBADRQC; + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_PAUSE_STREAM_MRFLD, str_info->pipe_id, + 0, NULL, NULL, true, true, false, true); + + if (retval == 0) { + str_info->prev = str_info->status; + str_info->status = STREAM_PAUSED; + } else if (retval == -SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "SST DBG:BADRQC for stream\n"); + } + + return retval; +} + +/** + * sst_resume_stream - Send msg for resuming stream + * @str_id: stream ID + * + * This function is called by any function which wants to resume + * an already paused stream. + */ +int sst_resume_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_resume_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status == STREAM_RUNNING) + return 0; + + if (str_info->resume_status == STREAM_PAUSED && + str_info->resume_prev == STREAM_RUNNING) { + /* + * Stream was running before suspend and re-created on resume, + * start it to get back to running state. + */ + dev_dbg(sst_drv_ctx->dev, "restart recreated stream after resume\n"); + str_info->status = STREAM_RUNNING; + str_info->prev = STREAM_PAUSED; + retval = sst_start_stream(sst_drv_ctx, str_id); + str_info->resume_status = STREAM_UN_INIT; + } else if (str_info->resume_status == STREAM_PAUSED && + str_info->resume_prev == STREAM_INIT) { + /* + * Stream was idle before suspend and re-created on resume, + * keep it as is. + */ + dev_dbg(sst_drv_ctx->dev, "leaving recreated stream idle after resume\n"); + str_info->status = STREAM_INIT; + str_info->prev = STREAM_PAUSED; + str_info->resume_status = STREAM_UN_INIT; + } else if (str_info->status == STREAM_PAUSED) { + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_RESUME_STREAM_MRFLD, + str_info->pipe_id, 0, NULL, NULL, + true, true, false, true); + + if (!retval) { + if (str_info->prev == STREAM_RUNNING) + str_info->status = STREAM_RUNNING; + else + str_info->status = STREAM_INIT; + str_info->prev = STREAM_PAUSED; + } else if (retval == -SST_ERR_INVALID_STREAM_ID) { + retval = -EINVAL; + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + } + } else { + retval = -EBADRQC; + dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream\n"); + } + + return retval; +} + + +/** + * sst_drop_stream - Send msg for stopping stream + * @str_id: stream ID + * + * This function is called by any function which wants to stop + * a stream. + */ +int sst_drop_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drop_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + + if (str_info->status != STREAM_UN_INIT) { + str_info->prev = STREAM_UN_INIT; + str_info->status = STREAM_INIT; + str_info->cumm_bytes = 0; + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, + IPC_CMD, IPC_IA_DROP_STREAM_MRFLD, + str_info->pipe_id, 0, NULL, NULL, + true, true, true, false); + } else { + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "BADQRC for stream, state %x\n", + str_info->status); + } + return retval; +} + +/** +* sst_drain_stream - Send msg for draining stream +* @str_id: stream ID +* +* This function is called by any function which wants to drain +* a stream. +*/ +int sst_drain_stream(struct intel_sst_drv *sst_drv_ctx, + int str_id, bool partial_drain) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_drain_stream for %d\n", str_id); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + if (str_info->status != STREAM_RUNNING && + str_info->status != STREAM_INIT && + str_info->status != STREAM_PAUSED) { + dev_err(sst_drv_ctx->dev, "SST ERR: BADQRC for stream = %d\n", + str_info->status); + return -EBADRQC; + } + + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_DRAIN_STREAM_MRFLD, str_info->pipe_id, + sizeof(u8), &partial_drain, NULL, true, true, false, false); + /* + * with new non blocked drain implementation in core we dont need to + * wait for respsonse, and need to only invoke callback for drain + * complete + */ + + return retval; +} + +/** + * sst_free_stream - Frees a stream + * @str_id: stream ID + * + * This function is called by any function which wants to free + * a stream. + */ +int sst_free_stream(struct intel_sst_drv *sst_drv_ctx, int str_id) +{ + int retval = 0; + struct stream_info *str_info; + + dev_dbg(sst_drv_ctx->dev, "SST DBG:sst_free_stream for %d\n", str_id); + + mutex_lock(&sst_drv_ctx->sst_lock); + if (sst_drv_ctx->sst_state == SST_RESET) { + mutex_unlock(&sst_drv_ctx->sst_lock); + return -ENODEV; + } + mutex_unlock(&sst_drv_ctx->sst_lock); + str_info = get_stream_info(sst_drv_ctx, str_id); + if (!str_info) + return -EINVAL; + + mutex_lock(&str_info->lock); + if (str_info->status != STREAM_UN_INIT) { + str_info->prev = str_info->status; + str_info->status = STREAM_UN_INIT; + mutex_unlock(&str_info->lock); + + dev_dbg(sst_drv_ctx->dev, "Free for str %d pipe %#x\n", + str_id, str_info->pipe_id); + retval = sst_prepare_and_post_msg(sst_drv_ctx, str_info->task_id, IPC_CMD, + IPC_IA_FREE_STREAM_MRFLD, str_info->pipe_id, 0, + NULL, NULL, true, true, false, true); + + dev_dbg(sst_drv_ctx->dev, "sst: wait for free returned %d\n", + retval); + mutex_lock(&sst_drv_ctx->sst_lock); + sst_clean_stream(str_info); + mutex_unlock(&sst_drv_ctx->sst_lock); + dev_dbg(sst_drv_ctx->dev, "SST DBG:Stream freed\n"); + } else { + mutex_unlock(&str_info->lock); + retval = -EBADRQC; + dev_dbg(sst_drv_ctx->dev, "SST DBG:BADQRC for stream\n"); + } + + return retval; +} diff --git a/sound/soc/intel/baytrail/Makefile b/sound/soc/intel/baytrail/Makefile new file mode 100644 index 000000000..488408cad --- /dev/null +++ b/sound/soc/intel/baytrail/Makefile @@ -0,0 +1,4 @@ +snd-soc-sst-baytrail-pcm-objs := \ + sst-baytrail-ipc.o sst-baytrail-pcm.o sst-baytrail-dsp.o + +obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += snd-soc-sst-baytrail-pcm.o diff --git a/sound/soc/intel/baytrail/sst-baytrail-dsp.c b/sound/soc/intel/baytrail/sst-baytrail-dsp.c new file mode 100644 index 000000000..01d023cc0 --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-dsp.c @@ -0,0 +1,366 @@ +/* + * Intel Baytrail SST DSP driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "sst-baytrail-ipc.h" + +#define SST_BYT_FW_SIGNATURE_SIZE 4 +#define SST_BYT_FW_SIGN "$SST" + +#define SST_BYT_IRAM_OFFSET 0xC0000 +#define SST_BYT_DRAM_OFFSET 0x100000 +#define SST_BYT_SHIM_OFFSET 0x140000 + +enum sst_ram_type { + SST_BYT_IRAM = 1, + SST_BYT_DRAM = 2, + SST_BYT_CACHE = 3, +}; + +struct dma_block_info { + enum sst_ram_type type; /* IRAM/DRAM */ + u32 size; /* Bytes */ + u32 ram_offset; /* Offset in I/DRAM */ + u32 rsvd; /* Reserved field */ +}; + +struct fw_header { + unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; + u32 file_size; /* size of fw minus this header */ + u32 modules; /* # of modules */ + u32 file_format; /* version of header format */ + u32 reserved[4]; +}; + +struct sst_byt_fw_module_header { + unsigned char signature[SST_BYT_FW_SIGNATURE_SIZE]; + u32 mod_size; /* size of module */ + u32 blocks; /* # of blocks */ + u32 type; /* codec type, pp lib */ + u32 entry_point; +}; + +static int sst_byt_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, + struct sst_byt_fw_module_header *module) +{ + struct dma_block_info *block; + struct sst_module *mod; + struct sst_module_template template; + int count; + + memset(&template, 0, sizeof(template)); + template.id = module->type; + template.entry = module->entry_point; + + mod = sst_module_new(fw, &template, NULL); + if (mod == NULL) + return -ENOMEM; + + block = (void *)module + sizeof(*module); + + for (count = 0; count < module->blocks; count++) { + + if (block->size <= 0) { + dev_err(dsp->dev, "block %d size invalid\n", count); + return -EINVAL; + } + + switch (block->type) { + case SST_BYT_IRAM: + mod->offset = block->ram_offset + + dsp->addr.iram_offset; + mod->type = SST_MEM_IRAM; + break; + case SST_BYT_DRAM: + mod->offset = block->ram_offset + + dsp->addr.dram_offset; + mod->type = SST_MEM_DRAM; + break; + case SST_BYT_CACHE: + mod->offset = block->ram_offset + + (dsp->addr.fw_ext - dsp->addr.lpe); + mod->type = SST_MEM_CACHE; + break; + default: + dev_err(dsp->dev, "wrong ram type 0x%x in block0x%x\n", + block->type, count); + return -EINVAL; + } + + mod->size = block->size; + mod->data = (void *)block + sizeof(*block); + + sst_module_alloc_blocks(mod); + + block = (void *)block + sizeof(*block) + block->size; + } + return 0; +} + +static int sst_byt_parse_fw_image(struct sst_fw *sst_fw) +{ + struct fw_header *header; + struct sst_byt_fw_module_header *module; + struct sst_dsp *dsp = sst_fw->dsp; + int ret, count; + + /* Read the header information from the data pointer */ + header = (struct fw_header *)sst_fw->dma_buf; + + /* verify FW */ + if ((strncmp(header->signature, SST_BYT_FW_SIGN, 4) != 0) || + (sst_fw->size != header->file_size + sizeof(*header))) { + /* Invalid FW signature */ + dev_err(dsp->dev, "Invalid FW sign/filesize mismatch\n"); + return -EINVAL; + } + + dev_dbg(dsp->dev, + "header sign=%4s size=0x%x modules=0x%x fmt=0x%x size=%zu\n", + header->signature, header->file_size, header->modules, + header->file_format, sizeof(*header)); + + module = (void *)sst_fw->dma_buf + sizeof(*header); + for (count = 0; count < header->modules; count++) { + /* module */ + ret = sst_byt_parse_module(dsp, sst_fw, module); + if (ret < 0) { + dev_err(dsp->dev, "invalid module %d\n", count); + return ret; + } + module = (void *)module + sizeof(*module) + module->mod_size; + } + + return 0; +} + +static void sst_byt_dump_shim(struct sst_dsp *sst) +{ + int i; + u64 reg; + + for (i = 0; i <= 0xF0; i += 8) { + reg = sst_dsp_shim_read64_unlocked(sst, i); + if (reg) + dev_dbg(sst->dev, "shim 0x%2.2x value 0x%16.16llx\n", + i, reg); + } + + for (i = 0x00; i <= 0xff; i += 4) { + reg = readl(sst->addr.pci_cfg + i); + if (reg) + dev_dbg(sst->dev, "pci 0x%2.2x value 0x%8.8x\n", + i, (u32)reg); + } +} + +static irqreturn_t sst_byt_irq(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + u64 isrx; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&sst->spinlock); + + isrx = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); + if (isrx & SST_ISRX_DONE) { + /* ADSP has processed the message request from IA */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCX, + SST_BYT_IPCX_DONE, 0); + ret = IRQ_WAKE_THREAD; + } + if (isrx & SST_BYT_ISRX_REQUEST) { + /* mask message request from ADSP and do processing later */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_BYT_IMRX_REQUEST, + SST_BYT_IMRX_REQUEST); + ret = IRQ_WAKE_THREAD; + } + + spin_unlock(&sst->spinlock); + + return ret; +} + +static void sst_byt_boot(struct sst_dsp *sst) +{ + int tries = 10; + + /* + * save the physical address of extended firmware block in the first + * 4 bytes of the mailbox + */ + memcpy_toio(sst->addr.lpe + SST_BYT_MAILBOX_OFFSET, + &sst->pdata->fw_base, sizeof(u32)); + + /* release stall and wait to unstall */ + sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_STALL, 0x0); + while (tries--) { + if (!(sst_dsp_shim_read64(sst, SST_CSR) & + SST_BYT_CSR_PWAITMODE)) + break; + msleep(100); + } + if (tries < 0) { + dev_err(sst->dev, "unable to start DSP\n"); + sst_byt_dump_shim(sst); + } +} + +static void sst_byt_reset(struct sst_dsp *sst) +{ + /* put DSP into reset, set reset vector and stall */ + sst_dsp_shim_update_bits64(sst, SST_CSR, + SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL, + SST_BYT_CSR_RST | SST_BYT_CSR_VECTOR_SEL | SST_BYT_CSR_STALL); + + udelay(10); + + /* take DSP out of reset and keep stalled for FW loading */ + sst_dsp_shim_update_bits64(sst, SST_CSR, SST_BYT_CSR_RST, 0); +} + +struct sst_adsp_memregion { + u32 start; + u32 end; + int blocks; + enum sst_mem_type type; +}; + +/* BYT test stuff */ +static const struct sst_adsp_memregion byt_region[] = { + {0xC0000, 0x100000, 8, SST_MEM_IRAM}, /* I-SRAM - 8 * 32kB */ + {0x100000, 0x140000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ +}; + +static int sst_byt_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + sst->addr.lpe_base = pdata->lpe_base; + sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); + if (!sst->addr.lpe) + return -ENODEV; + + /* ADSP PCI MMIO config space */ + sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); + if (!sst->addr.pci_cfg) { + iounmap(sst->addr.lpe); + return -ENODEV; + } + + /* SST Extended FW allocation */ + sst->addr.fw_ext = ioremap(pdata->fw_base, pdata->fw_size); + if (!sst->addr.fw_ext) { + iounmap(sst->addr.pci_cfg); + iounmap(sst->addr.lpe); + return -ENODEV; + } + + /* SST Shim */ + sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; + + sst_dsp_mailbox_init(sst, SST_BYT_MAILBOX_OFFSET + 0x204, + SST_BYT_IPC_MAX_PAYLOAD_SIZE, + SST_BYT_MAILBOX_OFFSET, + SST_BYT_IPC_MAX_PAYLOAD_SIZE); + + sst->irq = pdata->irq; + + return 0; +} + +static int sst_byt_init(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + const struct sst_adsp_memregion *region; + struct device *dev; + int ret = -ENODEV, i, j, region_count; + u32 offset, size; + + dev = sst->dev; + + switch (sst->id) { + case SST_DEV_ID_BYT: + region = byt_region; + region_count = ARRAY_SIZE(byt_region); + sst->addr.iram_offset = SST_BYT_IRAM_OFFSET; + sst->addr.dram_offset = SST_BYT_DRAM_OFFSET; + sst->addr.shim_offset = SST_BYT_SHIM_OFFSET; + break; + default: + dev_err(dev, "failed to get mem resources\n"); + return ret; + } + + ret = sst_byt_resource_map(sst, pdata); + if (ret < 0) { + dev_err(dev, "failed to map resources\n"); + return ret; + } + + ret = dma_coerce_mask_and_coherent(sst->dma_dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + /* enable Interrupt from both sides */ + sst_dsp_shim_update_bits64(sst, SST_IMRX, 0x3, 0x0); + sst_dsp_shim_update_bits64(sst, SST_IMRD, 0x3, 0x0); + + /* register DSP memory blocks - ideally we should get this from ACPI */ + for (i = 0; i < region_count; i++) { + offset = region[i].start; + size = (region[i].end - region[i].start) / region[i].blocks; + + /* register individual memory blocks */ + for (j = 0; j < region[i].blocks; j++) { + sst_mem_block_register(sst, offset, size, + region[i].type, NULL, j, sst); + offset += size; + } + } + + return 0; +} + +static void sst_byt_free(struct sst_dsp *sst) +{ + sst_mem_block_unregister_all(sst); + iounmap(sst->addr.lpe); + iounmap(sst->addr.pci_cfg); + iounmap(sst->addr.fw_ext); +} + +struct sst_ops sst_byt_ops = { + .reset = sst_byt_reset, + .boot = sst_byt_boot, + .write = sst_shim32_write, + .read = sst_shim32_read, + .write64 = sst_shim32_write64, + .read64 = sst_shim32_read64, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .irq_handler = sst_byt_irq, + .init = sst_byt_init, + .free = sst_byt_free, + .parse_fw = sst_byt_parse_fw_image, +}; diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.c b/sound/soc/intel/baytrail/sst-baytrail-ipc.c new file mode 100644 index 000000000..260447da3 --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-ipc.c @@ -0,0 +1,782 @@ +/* + * Intel Baytrail SST IPC Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sst-baytrail-ipc.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-ipc.h" + +/* IPC message timeout */ +#define IPC_TIMEOUT_MSECS 300 +#define IPC_BOOT_MSECS 200 + +#define IPC_EMPTY_LIST_SIZE 8 + +/* IPC header bits */ +#define IPC_HEADER_MSG_ID_MASK 0xff +#define IPC_HEADER_MSG_ID(x) ((x) & IPC_HEADER_MSG_ID_MASK) +#define IPC_HEADER_STR_ID_SHIFT 8 +#define IPC_HEADER_STR_ID_MASK 0x1f +#define IPC_HEADER_STR_ID(x) (((x) & 0x1f) << IPC_HEADER_STR_ID_SHIFT) +#define IPC_HEADER_LARGE_SHIFT 13 +#define IPC_HEADER_LARGE(x) (((x) & 0x1) << IPC_HEADER_LARGE_SHIFT) +#define IPC_HEADER_DATA_SHIFT 16 +#define IPC_HEADER_DATA_MASK 0x3fff +#define IPC_HEADER_DATA(x) (((x) & 0x3fff) << IPC_HEADER_DATA_SHIFT) + +/* mask for differentiating between notification and reply message */ +#define IPC_NOTIFICATION (0x1 << 7) + +/* I2L Stream config/control msgs */ +#define IPC_IA_ALLOC_STREAM 0x20 +#define IPC_IA_FREE_STREAM 0x21 +#define IPC_IA_PAUSE_STREAM 0x24 +#define IPC_IA_RESUME_STREAM 0x25 +#define IPC_IA_DROP_STREAM 0x26 +#define IPC_IA_START_STREAM 0x30 + +/* notification messages */ +#define IPC_IA_FW_INIT_CMPLT 0x81 +#define IPC_SST_PERIOD_ELAPSED 0x97 + +/* IPC messages between host and ADSP */ +struct sst_byt_address_info { + u32 addr; + u32 size; +} __packed; + +struct sst_byt_str_type { + u8 codec_type; + u8 str_type; + u8 operation; + u8 protected_str; + u8 time_slots; + u8 reserved; + u16 result; +} __packed; + +struct sst_byt_pcm_params { + u8 num_chan; + u8 pcm_wd_sz; + u8 use_offload_path; + u8 reserved; + u32 sfreq; + u8 channel_map[8]; +} __packed; + +struct sst_byt_frames_info { + u16 num_entries; + u16 rsrvd; + u32 frag_size; + struct sst_byt_address_info ring_buf_info[8]; +} __packed; + +struct sst_byt_alloc_params { + struct sst_byt_str_type str_type; + struct sst_byt_pcm_params pcm_params; + struct sst_byt_frames_info frame_info; +} __packed; + +struct sst_byt_alloc_response { + struct sst_byt_str_type str_type; + u8 reserved[88]; +} __packed; + +struct sst_byt_start_stream_params { + u32 byte_offset; +} __packed; + +struct sst_byt_tstamp { + u64 ring_buffer_counter; + u64 hardware_counter; + u64 frames_decoded; + u64 bytes_decoded; + u64 bytes_copied; + u32 sampling_frequency; + u32 channel_peak[8]; +} __packed; + +struct sst_byt_fw_version { + u8 build; + u8 minor; + u8 major; + u8 type; +} __packed; + +struct sst_byt_fw_build_info { + u8 date[16]; + u8 time[16]; +} __packed; + +struct sst_byt_fw_init { + struct sst_byt_fw_version fw_version; + struct sst_byt_fw_build_info build_info; + u16 result; + u8 module_id; + u8 debug_info; +} __packed; + +struct sst_byt_stream; +struct sst_byt; + +/* stream infomation */ +struct sst_byt_stream { + struct list_head node; + + /* configuration */ + struct sst_byt_alloc_params request; + struct sst_byt_alloc_response reply; + + /* runtime info */ + struct sst_byt *byt; + int str_id; + bool commited; + bool running; + + /* driver callback */ + u32 (*notify_position)(struct sst_byt_stream *stream, void *data); + void *pdata; +}; + +/* SST Baytrail IPC data */ +struct sst_byt { + struct device *dev; + struct sst_dsp *dsp; + + /* stream */ + struct list_head stream_list; + + /* boot */ + wait_queue_head_t boot_wait; + bool boot_complete; + struct sst_fw *fw; + + /* IPC messaging */ + struct sst_generic_ipc ipc; +}; + +static inline u64 sst_byt_header(int msg_id, int data, bool large, int str_id) +{ + return IPC_HEADER_MSG_ID(msg_id) | IPC_HEADER_STR_ID(str_id) | + IPC_HEADER_LARGE(large) | IPC_HEADER_DATA(data) | + SST_BYT_IPCX_BUSY; +} + +static inline u16 sst_byt_header_msg_id(u64 header) +{ + return header & IPC_HEADER_MSG_ID_MASK; +} + +static inline u8 sst_byt_header_str_id(u64 header) +{ + return (header >> IPC_HEADER_STR_ID_SHIFT) & IPC_HEADER_STR_ID_MASK; +} + +static inline u16 sst_byt_header_data(u64 header) +{ + return (header >> IPC_HEADER_DATA_SHIFT) & IPC_HEADER_DATA_MASK; +} + +static struct sst_byt_stream *sst_byt_get_stream(struct sst_byt *byt, + int stream_id) +{ + struct sst_byt_stream *stream; + + list_for_each_entry(stream, &byt->stream_list, node) { + if (stream->str_id == stream_id) + return stream; + } + + return NULL; +} + +static void sst_byt_stream_update(struct sst_byt *byt, struct ipc_message *msg) +{ + struct sst_byt_stream *stream; + u64 header = msg->header; + u8 stream_id = sst_byt_header_str_id(header); + u8 stream_msg = sst_byt_header_msg_id(header); + + stream = sst_byt_get_stream(byt, stream_id); + if (stream == NULL) + return; + + switch (stream_msg) { + case IPC_IA_DROP_STREAM: + case IPC_IA_PAUSE_STREAM: + case IPC_IA_FREE_STREAM: + stream->running = false; + break; + case IPC_IA_START_STREAM: + case IPC_IA_RESUME_STREAM: + stream->running = true; + break; + } +} + +static int sst_byt_process_reply(struct sst_byt *byt, u64 header) +{ + struct ipc_message *msg; + + msg = sst_ipc_reply_find_msg(&byt->ipc, header); + if (msg == NULL) + return 1; + + if (header & IPC_HEADER_LARGE(true)) { + msg->rx_size = sst_byt_header_data(header); + sst_dsp_inbox_read(byt->dsp, msg->rx_data, msg->rx_size); + } + + /* update any stream states */ + sst_byt_stream_update(byt, msg); + + list_del(&msg->list); + /* wake up */ + sst_ipc_tx_msg_reply_complete(&byt->ipc, msg); + + return 1; +} + +static void sst_byt_fw_ready(struct sst_byt *byt, u64 header) +{ + dev_dbg(byt->dev, "ipc: DSP is ready 0x%llX\n", header); + + byt->boot_complete = true; + wake_up(&byt->boot_wait); +} + +static int sst_byt_process_notification(struct sst_byt *byt, + unsigned long *flags) +{ + struct sst_dsp *sst = byt->dsp; + struct sst_byt_stream *stream; + u64 header; + u8 msg_id, stream_id; + int handled = 1; + + header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + msg_id = sst_byt_header_msg_id(header); + + switch (msg_id) { + case IPC_SST_PERIOD_ELAPSED: + stream_id = sst_byt_header_str_id(header); + stream = sst_byt_get_stream(byt, stream_id); + if (stream && stream->running && stream->notify_position) { + spin_unlock_irqrestore(&sst->spinlock, *flags); + stream->notify_position(stream, stream->pdata); + spin_lock_irqsave(&sst->spinlock, *flags); + } + break; + case IPC_IA_FW_INIT_CMPLT: + sst_byt_fw_ready(byt, header); + break; + } + + return handled; +} + +static irqreturn_t sst_byt_irq_thread(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + struct sst_byt *byt = sst_dsp_get_thread_context(sst); + struct sst_generic_ipc *ipc = &byt->ipc; + u64 header; + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + + header = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + if (header & SST_BYT_IPCD_BUSY) { + if (header & IPC_NOTIFICATION) { + /* message from ADSP */ + sst_byt_process_notification(byt, &flags); + } else { + /* reply from ADSP */ + sst_byt_process_reply(byt, header); + } + /* + * clear IPCD BUSY bit and set DONE bit. Tell DSP we have + * processed the message and can accept new. Clear data part + * of the header + */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IPCD, + SST_BYT_IPCD_DONE | SST_BYT_IPCD_BUSY | + IPC_HEADER_DATA(IPC_HEADER_DATA_MASK), + SST_BYT_IPCD_DONE); + /* unmask message request interrupts */ + sst_dsp_shim_update_bits64_unlocked(sst, SST_IMRX, + SST_BYT_IMRX_REQUEST, 0); + } + + spin_unlock_irqrestore(&sst->spinlock, flags); + + /* continue to send any remaining messages... */ + schedule_work(&ipc->kwork); + + return IRQ_HANDLED; +} + +/* stream API */ +struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, + u32 (*notify_position)(struct sst_byt_stream *stream, void *data), + void *data) +{ + struct sst_byt_stream *stream; + struct sst_dsp *sst = byt->dsp; + unsigned long flags; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (stream == NULL) + return NULL; + + spin_lock_irqsave(&sst->spinlock, flags); + list_add(&stream->node, &byt->stream_list); + stream->notify_position = notify_position; + stream->pdata = data; + stream->byt = byt; + stream->str_id = id; + spin_unlock_irqrestore(&sst->spinlock, flags); + + return stream; +} + +int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, + int bits) +{ + stream->request.pcm_params.pcm_wd_sz = bits; + return 0; +} + +int sst_byt_stream_set_channels(struct sst_byt *byt, + struct sst_byt_stream *stream, u8 channels) +{ + stream->request.pcm_params.num_chan = channels; + return 0; +} + +int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, + unsigned int rate) +{ + stream->request.pcm_params.sfreq = rate; + return 0; +} + +/* stream sonfiguration */ +int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, + int codec_type, int stream_type, int operation) +{ + stream->request.str_type.codec_type = codec_type; + stream->request.str_type.str_type = stream_type; + stream->request.str_type.operation = operation; + stream->request.str_type.time_slots = 0xc; + + return 0; +} + +int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, + uint32_t buffer_addr, uint32_t buffer_size) +{ + stream->request.frame_info.num_entries = 1; + stream->request.frame_info.ring_buf_info[0].addr = buffer_addr; + stream->request.frame_info.ring_buf_info[0].size = buffer_size; + /* calculate bytes per 4 ms fragment */ + stream->request.frame_info.frag_size = + stream->request.pcm_params.sfreq * + stream->request.pcm_params.num_chan * + stream->request.pcm_params.pcm_wd_sz / 8 * + 4 / 1000; + return 0; +} + +int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + struct sst_byt_alloc_params *str_req = &stream->request; + struct sst_byt_alloc_response *reply = &stream->reply; + u64 header; + int ret; + + header = sst_byt_header(IPC_IA_ALLOC_STREAM, + sizeof(*str_req) + sizeof(u32), + true, stream->str_id); + ret = sst_ipc_tx_message_wait(&byt->ipc, header, str_req, + sizeof(*str_req), + reply, sizeof(*reply)); + if (ret < 0) { + dev_err(byt->dev, "ipc: error stream commit failed\n"); + return ret; + } + + stream->commited = true; + + return 0; +} + +int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + u64 header; + int ret = 0; + struct sst_dsp *sst = byt->dsp; + unsigned long flags; + + if (!stream->commited) + goto out; + + header = sst_byt_header(IPC_IA_FREE_STREAM, 0, false, stream->str_id); + ret = sst_ipc_tx_message_wait(&byt->ipc, header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(byt->dev, "ipc: free stream %d failed\n", + stream->str_id); + return -EAGAIN; + } + + stream->commited = false; +out: + spin_lock_irqsave(&sst->spinlock, flags); + list_del(&stream->node); + kfree(stream); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return ret; +} + +static int sst_byt_stream_operations(struct sst_byt *byt, int type, + int stream_id, int wait) +{ + u64 header; + + header = sst_byt_header(type, 0, false, stream_id); + if (wait) + return sst_ipc_tx_message_wait(&byt->ipc, header, NULL, + 0, NULL, 0); + else + return sst_ipc_tx_message_nowait(&byt->ipc, header, + NULL, 0); +} + +/* stream ALSA trigger operations */ +int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, + u32 start_offset) +{ + struct sst_byt_start_stream_params start_stream; + void *tx_msg; + size_t size; + u64 header; + int ret; + + start_stream.byte_offset = start_offset; + header = sst_byt_header(IPC_IA_START_STREAM, + sizeof(start_stream) + sizeof(u32), + true, stream->str_id); + tx_msg = &start_stream; + size = sizeof(start_stream); + + ret = sst_ipc_tx_message_nowait(&byt->ipc, header, tx_msg, size); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to start stream %d\n", + stream->str_id); + + return ret; +} + +int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + int ret; + + /* don't stop streams that are not commited */ + if (!stream->commited) + return 0; + + ret = sst_byt_stream_operations(byt, IPC_IA_DROP_STREAM, + stream->str_id, 0); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to stop stream %d\n", + stream->str_id); + return ret; +} + +int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + int ret; + + ret = sst_byt_stream_operations(byt, IPC_IA_PAUSE_STREAM, + stream->str_id, 0); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to pause stream %d\n", + stream->str_id); + + return ret; +} + +int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream) +{ + int ret; + + ret = sst_byt_stream_operations(byt, IPC_IA_RESUME_STREAM, + stream->str_id, 0); + if (ret < 0) + dev_err(byt->dev, "ipc: error failed to resume stream %d\n", + stream->str_id); + + return ret; +} + +int sst_byt_get_dsp_position(struct sst_byt *byt, + struct sst_byt_stream *stream, int buffer_size) +{ + struct sst_dsp *sst = byt->dsp; + struct sst_byt_tstamp fw_tstamp; + u8 str_id = stream->str_id; + u32 tstamp_offset; + + tstamp_offset = SST_BYT_TIMESTAMP_OFFSET + str_id * sizeof(fw_tstamp); + memcpy_fromio(&fw_tstamp, + sst->addr.lpe + tstamp_offset, sizeof(fw_tstamp)); + + return do_div(fw_tstamp.ring_buffer_counter, buffer_size); +} + +struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt) +{ + return byt->dsp; +} + +static struct sst_dsp_device byt_dev = { + .thread = sst_byt_irq_thread, + .ops = &sst_byt_ops, +}; + +int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + + dev_dbg(byt->dev, "dsp reset\n"); + sst_dsp_reset(byt->dsp); + sst_ipc_drop_all(&byt->ipc); + dev_dbg(byt->dev, "dsp in reset\n"); + + dev_dbg(byt->dev, "free all blocks and unload fw\n"); + sst_fw_unload(byt->fw); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_suspend_late); + +int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + int ret; + + dev_dbg(byt->dev, "reload dsp fw\n"); + + sst_dsp_reset(byt->dsp); + + ret = sst_fw_reload(byt->fw); + if (ret < 0) { + dev_err(dev, "error: failed to reload firmware\n"); + return ret; + } + + /* wait for DSP boot completion */ + byt->boot_complete = false; + sst_dsp_boot(byt->dsp); + dev_dbg(byt->dev, "dsp booting...\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_boot); + +int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + int err; + + dev_dbg(byt->dev, "wait for dsp reboot\n"); + + err = wait_event_timeout(byt->boot_wait, byt->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (err == 0) { + dev_err(byt->dev, "ipc: error DSP boot timeout\n"); + return -EIO; + } + + dev_dbg(byt->dev, "dsp rebooted\n"); + return 0; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_wait_for_ready); + +static void byt_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + if (msg->header & IPC_HEADER_LARGE(true)) + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + + sst_dsp_shim_write64_unlocked(ipc->dsp, SST_IPCX, msg->header); +} + +static void byt_shim_dbg(struct sst_generic_ipc *ipc, const char *text) +{ + struct sst_dsp *sst = ipc->dsp; + u64 isr, ipcd, imrx, ipcx; + + ipcx = sst_dsp_shim_read64_unlocked(sst, SST_IPCX); + isr = sst_dsp_shim_read64_unlocked(sst, SST_ISRX); + ipcd = sst_dsp_shim_read64_unlocked(sst, SST_IPCD); + imrx = sst_dsp_shim_read64_unlocked(sst, SST_IMRX); + + dev_err(ipc->dev, + "ipc: --%s-- ipcx 0x%llx isr 0x%llx ipcd 0x%llx imrx 0x%llx\n", + text, ipcx, isr, ipcd, imrx); +} + +static void byt_tx_data_copy(struct ipc_message *msg, char *tx_data, + size_t tx_size) +{ + /* msg content = lower 32-bit of the header + data */ + *(u32 *)msg->tx_data = (u32)(msg->header & (u32)-1); + memcpy(msg->tx_data + sizeof(u32), tx_data, tx_size); + msg->tx_size += sizeof(u32); +} + +static u64 byt_reply_msg_match(u64 header, u64 *mask) +{ + /* match reply to message sent based on msg and stream IDs */ + *mask = IPC_HEADER_MSG_ID_MASK | + IPC_HEADER_STR_ID_MASK << IPC_HEADER_STR_ID_SHIFT; + header &= *mask; + + return header; +} + +static bool byt_is_dsp_busy(struct sst_dsp *dsp) +{ + u64 ipcx; + + ipcx = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); + return (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)); +} + +int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt; + struct sst_generic_ipc *ipc; + struct sst_fw *byt_sst_fw; + struct sst_byt_fw_init init; + int err; + + dev_dbg(dev, "initialising Byt DSP IPC\n"); + + byt = devm_kzalloc(dev, sizeof(*byt), GFP_KERNEL); + if (byt == NULL) + return -ENOMEM; + + byt->dev = dev; + + ipc = &byt->ipc; + ipc->dev = dev; + ipc->ops.tx_msg = byt_tx_msg; + ipc->ops.shim_dbg = byt_shim_dbg; + ipc->ops.tx_data_copy = byt_tx_data_copy; + ipc->ops.reply_msg_match = byt_reply_msg_match; + ipc->ops.is_dsp_busy = byt_is_dsp_busy; + ipc->tx_data_max_size = IPC_MAX_MAILBOX_BYTES; + ipc->rx_data_max_size = IPC_MAX_MAILBOX_BYTES; + + err = sst_ipc_init(ipc); + if (err != 0) + goto ipc_init_err; + + INIT_LIST_HEAD(&byt->stream_list); + init_waitqueue_head(&byt->boot_wait); + byt_dev.thread_context = byt; + + /* init SST shim */ + byt->dsp = sst_dsp_new(dev, &byt_dev, pdata); + if (byt->dsp == NULL) { + err = -ENODEV; + goto dsp_new_err; + } + + ipc->dsp = byt->dsp; + + /* keep the DSP in reset state for base FW loading */ + sst_dsp_reset(byt->dsp); + + byt_sst_fw = sst_fw_new(byt->dsp, pdata->fw, byt); + if (byt_sst_fw == NULL) { + err = -ENODEV; + dev_err(dev, "error: failed to load firmware\n"); + goto fw_err; + } + + /* wait for DSP boot completion */ + sst_dsp_boot(byt->dsp); + err = wait_event_timeout(byt->boot_wait, byt->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (err == 0) { + err = -EIO; + dev_err(byt->dev, "ipc: error DSP boot timeout\n"); + goto boot_err; + } + + /* show firmware information */ + sst_dsp_inbox_read(byt->dsp, &init, sizeof(init)); + dev_info(byt->dev, "FW version: %02x.%02x.%02x.%02x\n", + init.fw_version.major, init.fw_version.minor, + init.fw_version.build, init.fw_version.type); + dev_info(byt->dev, "Build type: %x\n", init.fw_version.type); + dev_info(byt->dev, "Build date: %s %s\n", + init.build_info.date, init.build_info.time); + + pdata->dsp = byt; + byt->fw = byt_sst_fw; + + return 0; + +boot_err: + sst_dsp_reset(byt->dsp); + sst_fw_free(byt_sst_fw); +fw_err: + sst_dsp_free(byt->dsp); +dsp_new_err: + sst_ipc_fini(ipc); +ipc_init_err: + + return err; +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_init); + +void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_byt *byt = pdata->dsp; + + sst_dsp_reset(byt->dsp); + sst_fw_free_all(byt->dsp); + sst_dsp_free(byt->dsp); + sst_ipc_fini(&byt->ipc); +} +EXPORT_SYMBOL_GPL(sst_byt_dsp_free); diff --git a/sound/soc/intel/baytrail/sst-baytrail-ipc.h b/sound/soc/intel/baytrail/sst-baytrail-ipc.h new file mode 100644 index 000000000..8faff6dcf --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-ipc.h @@ -0,0 +1,73 @@ +/* + * Intel Baytrail SST IPC Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#ifndef __SST_BYT_IPC_H +#define __SST_BYT_IPC_H + +#include + +struct sst_byt; +struct sst_byt_stream; +struct sst_pdata; +extern struct sst_ops sst_byt_ops; + + +#define SST_BYT_MAILBOX_OFFSET 0x144000 +#define SST_BYT_TIMESTAMP_OFFSET (SST_BYT_MAILBOX_OFFSET + 0x800) + +/** + * Upfront defined maximum message size that is + * expected by the in/out communication pipes in FW. + */ +#define SST_BYT_IPC_MAX_PAYLOAD_SIZE 200 + +/* stream API */ +struct sst_byt_stream *sst_byt_stream_new(struct sst_byt *byt, int id, + uint32_t (*get_write_position)(struct sst_byt_stream *stream, + void *data), + void *data); + +/* stream configuration */ +int sst_byt_stream_set_bits(struct sst_byt *byt, struct sst_byt_stream *stream, + int bits); +int sst_byt_stream_set_channels(struct sst_byt *byt, + struct sst_byt_stream *stream, u8 channels); +int sst_byt_stream_set_rate(struct sst_byt *byt, struct sst_byt_stream *stream, + unsigned int rate); +int sst_byt_stream_type(struct sst_byt *byt, struct sst_byt_stream *stream, + int codec_type, int stream_type, int operation); +int sst_byt_stream_buffer(struct sst_byt *byt, struct sst_byt_stream *stream, + uint32_t buffer_addr, uint32_t buffer_size); +int sst_byt_stream_commit(struct sst_byt *byt, struct sst_byt_stream *stream); +int sst_byt_stream_free(struct sst_byt *byt, struct sst_byt_stream *stream); + +/* stream ALSA trigger operations */ +int sst_byt_stream_start(struct sst_byt *byt, struct sst_byt_stream *stream, + u32 start_offset); +int sst_byt_stream_stop(struct sst_byt *byt, struct sst_byt_stream *stream); +int sst_byt_stream_pause(struct sst_byt *byt, struct sst_byt_stream *stream); +int sst_byt_stream_resume(struct sst_byt *byt, struct sst_byt_stream *stream); + +int sst_byt_get_dsp_position(struct sst_byt *byt, + struct sst_byt_stream *stream, int buffer_size); + +/* init */ +int sst_byt_dsp_init(struct device *dev, struct sst_pdata *pdata); +void sst_byt_dsp_free(struct device *dev, struct sst_pdata *pdata); +struct sst_dsp *sst_byt_get_dsp(struct sst_byt *byt); +int sst_byt_dsp_suspend_late(struct device *dev, struct sst_pdata *pdata); +int sst_byt_dsp_boot(struct device *dev, struct sst_pdata *pdata); +int sst_byt_dsp_wait_for_ready(struct device *dev, struct sst_pdata *pdata); + +#endif diff --git a/sound/soc/intel/baytrail/sst-baytrail-pcm.c b/sound/soc/intel/baytrail/sst-baytrail-pcm.c new file mode 100644 index 000000000..aabb35bf6 --- /dev/null +++ b/sound/soc/intel/baytrail/sst-baytrail-pcm.c @@ -0,0 +1,491 @@ +/* + * Intel Baytrail SST PCM Support + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "sst-baytrail-ipc.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-dsp.h" + +#define DRV_NAME "byt-dai" +#define BYT_PCM_COUNT 2 + +static const struct snd_pcm_hardware sst_byt_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .period_bytes_min = 384, + .period_bytes_max = 48000, + .periods_min = 2, + .periods_max = 250, + .buffer_bytes_max = 96000, +}; + +/* private data for each PCM DSP stream */ +struct sst_byt_pcm_data { + struct sst_byt_stream *stream; + struct snd_pcm_substream *substream; + struct mutex mutex; + + /* latest DSP DMA hw pointer */ + u32 hw_ptr; + + struct work_struct work; +}; + +/* private data for the driver */ +struct sst_byt_priv_data { + /* runtime DSP */ + struct sst_byt *byt; + + /* DAI data */ + struct sst_byt_pcm_data pcm[BYT_PCM_COUNT]; + + /* flag indicating is stream context restore needed after suspend */ + bool restore_stream; +}; + +/* this may get called several times by oss emulation */ +static int sst_byt_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + u32 rate, bits; + u8 channels; + int ret, playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + dev_dbg(rtd->dev, "PCM: hw_params, pcm_data %p\n", pcm_data); + + ret = sst_byt_stream_type(byt, pcm_data->stream, + 1, 1, !playback); + if (ret < 0) { + dev_err(rtd->dev, "failed to set stream format %d\n", ret); + return ret; + } + + rate = params_rate(params); + ret = sst_byt_stream_set_rate(byt, pcm_data->stream, rate); + if (ret < 0) { + dev_err(rtd->dev, "could not set rate %d\n", rate); + return ret; + } + + bits = snd_pcm_format_width(params_format(params)); + ret = sst_byt_stream_set_bits(byt, pcm_data->stream, bits); + if (ret < 0) { + dev_err(rtd->dev, "could not set formats %d\n", + params_rate(params)); + return ret; + } + + channels = (u8)(params_channels(params) & 0xF); + ret = sst_byt_stream_set_channels(byt, pcm_data->stream, channels); + if (ret < 0) { + dev_err(rtd->dev, "could not set channels %d\n", + params_rate(params)); + return ret; + } + + snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + + ret = sst_byt_stream_buffer(byt, pcm_data->stream, + substream->dma_buffer.addr, + params_buffer_bytes(params)); + if (ret < 0) { + dev_err(rtd->dev, "PCM: failed to set DMA buffer %d\n", ret); + return ret; + } + + ret = sst_byt_stream_commit(byt, pcm_data->stream); + if (ret < 0) { + dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); + return ret; + } + + return 0; +} + +static int sst_byt_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->dev, "PCM: hw_free\n"); + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static int sst_byt_pcm_restore_stream_context(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + int ret; + + /* commit stream using existing stream params */ + ret = sst_byt_stream_commit(byt, pcm_data->stream); + if (ret < 0) { + dev_err(rtd->dev, "PCM: failed stream commit %d\n", ret); + return ret; + } + + sst_byt_stream_start(byt, pcm_data->stream, pcm_data->hw_ptr); + + dev_dbg(rtd->dev, "stream context restored at offset %d\n", + pcm_data->hw_ptr); + + return 0; +} + +static void sst_byt_pcm_work(struct work_struct *work) +{ + struct sst_byt_pcm_data *pcm_data = + container_of(work, struct sst_byt_pcm_data, work); + + if (snd_pcm_running(pcm_data->substream)) + sst_byt_pcm_restore_stream_context(pcm_data->substream); +} + +static int sst_byt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + + dev_dbg(rtd->dev, "PCM: trigger %d\n", cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm_data->hw_ptr = 0; + sst_byt_stream_start(byt, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (pdata->restore_stream == true) + schedule_work(&pcm_data->work); + else + sst_byt_stream_resume(byt, pcm_data->stream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sst_byt_stream_resume(byt, pcm_data->stream); + break; + case SNDRV_PCM_TRIGGER_STOP: + sst_byt_stream_stop(byt, pcm_data->stream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + pdata->restore_stream = false; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sst_byt_stream_pause(byt, pcm_data->stream); + break; + default: + break; + } + + return 0; +} + +static u32 byt_notify_pointer(struct sst_byt_stream *stream, void *data) +{ + struct sst_byt_pcm_data *pcm_data = data; + struct snd_pcm_substream *substream = pcm_data->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_byt *byt = pdata->byt; + u32 pos, hw_pos; + + hw_pos = sst_byt_get_dsp_position(byt, pcm_data->stream, + snd_pcm_lib_buffer_bytes(substream)); + pcm_data->hw_ptr = hw_pos; + pos = frames_to_bytes(runtime, + (runtime->control->appl_ptr % + runtime->buffer_size)); + + dev_dbg(rtd->dev, "PCM: App/DMA pointer %u/%u bytes\n", pos, hw_pos); + + snd_pcm_period_elapsed(substream); + return pos; +} + +static snd_pcm_uframes_t sst_byt_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + + dev_dbg(rtd->dev, "PCM: DMA pointer %u bytes\n", pcm_data->hw_ptr); + + return bytes_to_frames(runtime, pcm_data->hw_ptr); +} + +static int sst_byt_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + + dev_dbg(rtd->dev, "PCM: open\n"); + + mutex_lock(&pcm_data->mutex); + + pcm_data->substream = substream; + + snd_soc_set_runtime_hwparams(substream, &sst_byt_pcm_hardware); + + pcm_data->stream = sst_byt_stream_new(byt, substream->stream + 1, + byt_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "failed to create stream\n"); + mutex_unlock(&pcm_data->mutex); + return -EINVAL; + } + + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int sst_byt_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_byt_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_byt_pcm_data *pcm_data = &pdata->pcm[substream->stream]; + struct sst_byt *byt = pdata->byt; + int ret; + + dev_dbg(rtd->dev, "PCM: close\n"); + + cancel_work_sync(&pcm_data->work); + mutex_lock(&pcm_data->mutex); + ret = sst_byt_stream_free(byt, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "Free stream fail\n"); + goto out; + } + pcm_data->stream = NULL; + +out: + mutex_unlock(&pcm_data->mutex); + return ret; +} + +static int sst_byt_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dev_dbg(rtd->dev, "PCM: mmap\n"); + return snd_pcm_lib_default_mmap(substream, vma); +} + +static const struct snd_pcm_ops sst_byt_pcm_ops = { + .open = sst_byt_pcm_open, + .close = sst_byt_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = sst_byt_pcm_hw_params, + .hw_free = sst_byt_pcm_hw_free, + .trigger = sst_byt_pcm_trigger, + .pointer = sst_byt_pcm_pointer, + .mmap = sst_byt_pcm_mmap, +}; + +static int sst_byt_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + size_t size; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_pdata *pdata = dev_get_platdata(component->dev); + int ret = 0; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + size = sst_byt_pcm_hardware.buffer_bytes_max; + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, + pdata->dma_dev, + size, size); + if (ret) { + dev_err(rtd->dev, "dma buffer allocation failed %d\n", + ret); + return ret; + } + } + + return ret; +} + +static struct snd_soc_dai_driver byt_dais[] = { + { + .name = "Baytrail PCM", + .playback = { + .stream_name = "System Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Analog Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static int sst_byt_pcm_probe(struct snd_soc_component *component) +{ + struct sst_pdata *plat_data = dev_get_platdata(component->dev); + struct sst_byt_priv_data *priv_data; + int i; + + if (!plat_data) + return -ENODEV; + + priv_data = devm_kzalloc(component->dev, sizeof(*priv_data), + GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + priv_data->byt = plat_data->dsp; + snd_soc_component_set_drvdata(component, priv_data); + + for (i = 0; i < BYT_PCM_COUNT; i++) { + mutex_init(&priv_data->pcm[i].mutex); + INIT_WORK(&priv_data->pcm[i].work, sst_byt_pcm_work); + } + + return 0; +} + +static const struct snd_soc_component_driver byt_dai_component = { + .name = DRV_NAME, + .probe = sst_byt_pcm_probe, + .ops = &sst_byt_pcm_ops, + .pcm_new = sst_byt_pcm_new, +}; + +#ifdef CONFIG_PM +static int sst_byt_pcm_dev_suspend_late(struct device *dev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(dev); + struct sst_byt_priv_data *priv_data = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "suspending late\n"); + + ret = sst_byt_dsp_suspend_late(dev, sst_pdata); + if (ret < 0) { + dev_err(dev, "failed to suspend %d\n", ret); + return ret; + } + + priv_data->restore_stream = true; + + return ret; +} + +static int sst_byt_pcm_dev_resume_early(struct device *dev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(dev); + int ret; + + dev_dbg(dev, "resume early\n"); + + /* load fw and boot DSP */ + ret = sst_byt_dsp_boot(dev, sst_pdata); + if (ret) + return ret; + + /* wait for FW to finish booting */ + return sst_byt_dsp_wait_for_ready(dev, sst_pdata); +} + +static const struct dev_pm_ops sst_byt_pm_ops = { + .suspend_late = sst_byt_pcm_dev_suspend_late, + .resume_early = sst_byt_pcm_dev_resume_early, +}; + +#define SST_BYT_PM_OPS (&sst_byt_pm_ops) +#else +#define SST_BYT_PM_OPS NULL +#endif + +static int sst_byt_pcm_dev_probe(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + int ret; + + ret = sst_byt_dsp_init(&pdev->dev, sst_pdata); + if (ret < 0) + return -ENODEV; + + ret = devm_snd_soc_register_component(&pdev->dev, &byt_dai_component, + byt_dais, ARRAY_SIZE(byt_dais)); + if (ret < 0) + goto err_plat; + + return 0; + +err_plat: + sst_byt_dsp_free(&pdev->dev, sst_pdata); + return ret; +} + +static int sst_byt_pcm_dev_remove(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + + sst_byt_dsp_free(&pdev->dev, sst_pdata); + + return 0; +} + +static struct platform_driver sst_byt_pcm_driver = { + .driver = { + .name = "baytrail-pcm-audio", + .pm = SST_BYT_PM_OPS, + }, + + .probe = sst_byt_pcm_dev_probe, + .remove = sst_byt_pcm_dev_remove, +}; +module_platform_driver(sst_byt_pcm_driver); + +MODULE_AUTHOR("Jarkko Nikula"); +MODULE_DESCRIPTION("Baytrail PCM"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:baytrail-pcm-audio"); diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig new file mode 100644 index 000000000..cccda87f4 --- /dev/null +++ b/sound/soc/intel/boards/Kconfig @@ -0,0 +1,300 @@ +menuconfig SND_SOC_INTEL_MACH + bool "Intel Machine drivers" + depends on SND_SOC_INTEL_SST_TOPLEVEL + help + Intel ASoC Machine Drivers. If you have a Intel machine that + has an audio controller with a DSP and I2S or DMIC port, then + enable this option by saying Y + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about Intel ASoC machine drivers. + +if SND_SOC_INTEL_MACH + +if SND_SOC_INTEL_HASWELL + +config SND_SOC_INTEL_HASWELL_MACH + tristate "Haswell Lynxpoint" + depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM + select SND_SOC_RT5640 + help + This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell + Ultrabook platforms. This is a recommended option. + Say Y or m if you have such a device. + If unsure select "N". + +config SND_SOC_INTEL_BDW_RT5677_MACH + tristate "Broadwell with RT5677 codec" + depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM && GPIOLIB + select SND_SOC_RT5677 + help + This adds support for Intel Broadwell platform based boards with + the RT5677 audio codec. This is a recommended option. + Say Y or m if you have such a device. + If unsure select "N". + +config SND_SOC_INTEL_BROADWELL_MACH + tristate "Broadwell Wildcatpoint" + depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM + select SND_SOC_RT286 + help + This adds support for the Wilcatpoint Audio DSP on Intel(R) Broadwell + Ultrabook platforms. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". +endif ## SND_SOC_INTEL_HASWELL + +if SND_SOC_INTEL_BAYTRAIL + +config SND_SOC_INTEL_BYT_MAX98090_MACH + tristate "Baytrail with MAX98090 codec" + depends on X86_INTEL_LPSS && I2C + select SND_SOC_MAX98090 + help + This adds audio driver for Intel Baytrail platform based boards + with the MAX98090 audio codec. This driver is deprecated, use + SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH instead for better + functionality. + +config SND_SOC_INTEL_BYT_RT5640_MACH + tristate "Baytrail with RT5640 codec" + depends on X86_INTEL_LPSS && I2C + select SND_SOC_RT5640 + help + This adds audio driver for Intel Baytrail platform based boards + with the RT5640 audio codec. This driver is deprecated, use + SND_SOC_INTEL_BYTCR_RT5640_MACH instead for better functionality. + +endif ## SND_SOC_INTEL_BAYTRAIL + +if SND_SST_ATOM_HIFI2_PLATFORM + +config SND_SOC_INTEL_BYTCR_RT5640_MACH + tristate "Baytrail and Baytrail-CR with RT5640 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_ACPI + select SND_SOC_RT5640 + help + This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR + platforms with RT5640 audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_BYTCR_RT5651_MACH + tristate "Baytrail and Baytrail-CR with RT5651 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_ACPI + select SND_SOC_RT5651 + help + This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR + platforms with RT5651 audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_CHT_BSW_RT5672_MACH + tristate "Cherrytrail & Braswell with RT5672 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_ACPI + select SND_SOC_RT5670 + help + This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell + platforms with RT5672 audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_CHT_BSW_RT5645_MACH + tristate "Cherrytrail & Braswell with RT5645/5650 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_ACPI + select SND_SOC_RT5645 + help + This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell + platforms with RT5645/5650 audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH + tristate "Cherrytrail & Braswell with MAX98090 & TI codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_MAX98090 + select SND_SOC_TS3A227E + help + This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell + platforms with MAX98090 audio codec it also can support TI jack chip as aux device. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_CHT_BSW_NAU8824_MACH + tristate "Cherrytrail & Braswell with NAU88L24 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_ACPI + select SND_SOC_NAU8824 + help + This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell + platforms with NAU88L24 audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_BYT_CHT_DA7213_MACH + tristate "Baytrail & Cherrytrail with DA7212/7213 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_ACPI + select SND_SOC_DA7213 + help + This adds support for ASoC machine driver for Intel(R) Baytrail & CherryTrail + platforms with DA7212/7213 audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_BYT_CHT_ES8316_MACH + tristate "Baytrail & Cherrytrail with ES8316 codec" + depends on X86_INTEL_LPSS && I2C && ACPI + select SND_SOC_ACPI + select SND_SOC_ES8316 + help + This adds support for ASoC machine driver for Intel(R) Baytrail & + Cherrytrail platforms with ES8316 audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH + tristate "Baytrail & Cherrytrail platform with no codec (MinnowBoard MAX, Up)" + depends on X86_INTEL_LPSS && I2C && ACPI + help + This adds support for ASoC machine driver for the MinnowBoard Max or + Up boards and provides access to I2S signals on the Low-Speed + connector. This is not a recommended option outside of these cases. + It is not intended to be enabled by distros by default. + Say Y or m if you have such a device. + + If unsure select "N". + +endif ## SND_SST_ATOM_HIFI2_PLATFORM + +if SND_SOC_INTEL_SKYLAKE + +config SND_SOC_INTEL_SKL_RT286_MACH + tristate "SKL with RT286 I2S mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_RT286 + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + help + This adds support for ASoC machine driver for Skylake platforms + with RT286 I2S audio codec. + Say Y or m if you have such a device. + If unsure select "N". + +config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH + tristate "SKL with NAU88L25 and SSM4567 in I2S Mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_NAU8825 + select SND_SOC_SSM4567 + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + help + This adds support for ASoC Onboard Codec I2S machine driver. This will + create an alsa sound card for NAU88L25 + SSM4567. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH + tristate "SKL with NAU88L25 and MAX98357A in I2S Mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_NAU8825 + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + help + This adds support for ASoC Onboard Codec I2S machine driver. This will + create an alsa sound card for NAU88L25 + MAX98357A. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH + tristate "Broxton with DA7219 and MAX98357A in I2S Mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_DA7219 + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_HDA_DSP_LOADER + help + This adds support for ASoC machine driver for Broxton-P platforms + with DA7219 + MAX98357A I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_BXT_RT298_MACH + tristate "Broxton with RT298 I2S mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_RT298 + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_HDA_DSP_LOADER + help + This adds support for ASoC machine driver for Broxton platforms + with RT286 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH + tristate "KBL with RT5663 and MAX98927 in I2S Mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_RT5663 + select SND_SOC_MAX98927 + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_SKYLAKE_SSP_CLK + help + This adds support for ASoC Onboard Codec I2S machine driver. This will + create an alsa sound card for RT5663 + MAX98927. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH + tristate "KBL with RT5663, RT5514 and MAX98927 in I2S Mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + depends on SPI + select SND_SOC_RT5663 + select SND_SOC_RT5514 + select SND_SOC_RT5514_SPI + select SND_SOC_MAX98927 + select SND_SOC_HDAC_HDMI + help + This adds support for ASoC Onboard Codec I2S machine driver. This will + create an alsa sound card for RT5663 + RT5514 + MAX98927. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +config SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH + tristate "KBL with DA7219 and MAX98357A in I2S Mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_DA7219 + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + help + This adds support for ASoC Onboard Codec I2S machine driver. This will + create an alsa sound card for DA7219 + MAX98357A I2S audio codec. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH + tristate "GLK with RT5682 and MAX98357A in I2S Mode" + depends on MFD_INTEL_LPSS && I2C && ACPI + select SND_SOC_RT5682 + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_HDA_DSP_LOADER + help + This adds support for ASoC machine driver for Geminilake platforms + with RT5682 + MAX98357A I2S audio codec. + Say Y if you have such a device. + If unsure select "N". + +endif ## SND_SOC_INTEL_SKYLAKE + +endif ## SND_SOC_INTEL_MACH diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile new file mode 100644 index 000000000..87ef8b405 --- /dev/null +++ b/sound/soc/intel/boards/Makefile @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-sst-haswell-objs := haswell.o +snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o +snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o +snd-soc-sst-bdw-rt5677-mach-objs := bdw-rt5677.o +snd-soc-sst-broadwell-objs := broadwell.o +snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o +snd-soc-sst-bxt-rt298-objs := bxt_rt298.o +snd-soc-sst-glk-rt5682_max98357a-objs := glk_rt5682_max98357a.o +snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o +snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o +snd-soc-sst-cht-bsw-rt5672-objs := cht_bsw_rt5672.o +snd-soc-sst-cht-bsw-rt5645-objs := cht_bsw_rt5645.o +snd-soc-sst-cht-bsw-max98090_ti-objs := cht_bsw_max98090_ti.o +snd-soc-sst-cht-bsw-nau8824-objs := cht_bsw_nau8824.o +snd-soc-sst-byt-cht-da7213-objs := bytcht_da7213.o +snd-soc-sst-byt-cht-es8316-objs := bytcht_es8316.o +snd-soc-sst-byt-cht-nocodec-objs := bytcht_nocodec.o +snd-soc-kbl_da7219_max98357a-objs := kbl_da7219_max98357a.o +snd-soc-kbl_rt5663_max98927-objs := kbl_rt5663_max98927.o +snd-soc-kbl_rt5663_rt5514_max98927-objs := kbl_rt5663_rt5514_max98927.o +snd-soc-skl_rt286-objs := skl_rt286.o +snd-skl_nau88l25_max98357a-objs := skl_nau88l25_max98357a.o +snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o + +obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o +obj-$(CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH) += snd-soc-sst-bxt-da7219_max98357a.o +obj-$(CONFIG_SND_SOC_INTEL_BXT_RT298_MACH) += snd-soc-sst-bxt-rt298.o +obj-$(CONFIG_SND_SOC_INTEL_GLK_RT5682_MAX98357A_MACH) += snd-soc-sst-glk-rt5682_max98357a.o +obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o +obj-$(CONFIG_SND_SOC_INTEL_BDW_RT5677_MACH) += snd-soc-sst-bdw-rt5677-mach.o +obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o +obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_NAU8824_MACH) += snd-soc-sst-cht-bsw-nau8824.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_DA7213_MACH) += snd-soc-sst-byt-cht-da7213.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_ES8316_MACH) += snd-soc-sst-byt-cht-es8316.o +obj-$(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) += snd-soc-sst-byt-cht-nocodec.o +obj-$(CONFIG_SND_SOC_INTEL_KBL_DA7219_MAX98357A_MACH) += snd-soc-kbl_da7219_max98357a.o +obj-$(CONFIG_SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH) += snd-soc-kbl_rt5663_max98927.o +obj-$(CONFIG_SND_SOC_INTEL_KBL_RT5663_RT5514_MAX98927_MACH) += snd-soc-kbl_rt5663_rt5514_max98927.o +obj-$(CONFIG_SND_SOC_INTEL_SKL_RT286_MACH) += snd-soc-skl_rt286.o +obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH) += snd-skl_nau88l25_max98357a.o +obj-$(CONFIG_SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH) += snd-soc-skl_nau88l25_ssm4567.o diff --git a/sound/soc/intel/boards/bdw-rt5677.c b/sound/soc/intel/boards/bdw-rt5677.c new file mode 100644 index 000000000..efcfd906c --- /dev/null +++ b/sound/soc/intel/boards/bdw-rt5677.c @@ -0,0 +1,371 @@ +/* + * ASoC machine driver for Intel Broadwell platforms with RT5677 codec + * + * Copyright (c) 2014, The Chromium OS Authors. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../haswell/sst-haswell-ipc.h" + +#include "../../codecs/rt5677.h" + +struct bdw_rt5677_priv { + struct gpio_desc *gpio_hp_en; + struct snd_soc_component *component; +}; + +static int bdw_rt5677_event_hp(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card); + + if (SND_SOC_DAPM_EVENT_ON(event)) + msleep(70); + + gpiod_set_value_cansleep(bdw_rt5677->gpio_hp_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget bdw_rt5677_widgets[] = { + SND_SOC_DAPM_HP("Headphone", bdw_rt5677_event_hp), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Local DMICs", NULL), + SND_SOC_DAPM_MIC("Remote DMICs", NULL), +}; + +static const struct snd_soc_dapm_route bdw_rt5677_map[] = { + /* Speakers */ + {"Speaker", NULL, "PDM1L"}, + {"Speaker", NULL, "PDM1R"}, + + /* Headset jack connectors */ + {"Headphone", NULL, "LOUT1"}, + {"Headphone", NULL, "LOUT2"}, + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + + /* Digital MICs + * Local DMICs: the two DMICs on the mainboard + * Remote DMICs: the two DMICs on the camera module + */ + {"DMIC L1", NULL, "Remote DMICs"}, + {"DMIC R1", NULL, "Remote DMICs"}, + {"DMIC L2", NULL, "Local DMICs"}, + {"DMIC R2", NULL, "Local DMICs"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static const struct snd_kcontrol_new bdw_rt5677_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Local DMICs"), + SOC_DAPM_PIN_SWITCH("Remote DMICs"), +}; + + +static struct snd_soc_jack headphone_jack; +static struct snd_soc_jack mic_jack; + +static struct snd_soc_jack_pin headphone_jack_pin = { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, +}; + +static struct snd_soc_jack_pin mic_jack_pin = { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, +}; + +static struct snd_soc_jack_gpio headphone_jack_gpio = { + .name = "plug-det", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, +}; + +static struct snd_soc_jack_gpio mic_jack_gpio = { + .name = "mic-present", + .report = SND_JACK_MICROPHONE, + .debounce_time = 200, + .invert = 1, +}; + +/* GPIO indexes defined by ACPI */ +enum { + RT5677_GPIO_PLUG_DET = 0, + RT5677_GPIO_MIC_PRESENT_L = 1, + RT5677_GPIO_HOTWORD_DET_L = 2, + RT5677_GPIO_DSP_INT = 3, + RT5677_GPIO_HP_AMP_SHDN_L = 4, +}; + +static const struct acpi_gpio_params plug_det_gpio = { RT5677_GPIO_PLUG_DET, 0, false }; +static const struct acpi_gpio_params mic_present_gpio = { RT5677_GPIO_MIC_PRESENT_L, 0, false }; +static const struct acpi_gpio_params headphone_enable_gpio = { RT5677_GPIO_HP_AMP_SHDN_L, 0, false }; + +static const struct acpi_gpio_mapping bdw_rt5677_gpios[] = { + { "plug-det-gpios", &plug_det_gpio, 1 }, + { "mic-present-gpios", &mic_present_gpio, 1 }, + { "headphone-enable-gpios", &headphone_enable_gpio, 1 }, + { NULL }, +}; + +static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int bdw_rt5677_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_MCLK, 24576000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + return ret; +} + +static const struct snd_soc_ops bdw_rt5677_ops = { + .hw_params = bdw_rt5677_hw_params, +}; + +static int bdw_rt5677_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_pdata *pdata = dev_get_platdata(component->dev); + struct sst_hsw *broadwell = pdata->dsp; + int ret; + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set device config\n"); + return ret; + } + + return 0; +} + +static int bdw_rt5677_init(struct snd_soc_pcm_runtime *rtd) +{ + struct bdw_rt5677_priv *bdw_rt5677 = + snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + int ret; + + ret = devm_acpi_dev_add_driver_gpios(component->dev, bdw_rt5677_gpios); + if (ret) + dev_warn(component->dev, "Failed to add driver gpios\n"); + + /* Enable codec ASRC function for Stereo DAC/Stereo1 ADC/DMIC/I2S1. + * The ASRC clock source is clk_i2s1_asrc. + */ + rt5677_sel_asrc_clk_src(component, RT5677_DA_STEREO_FILTER | + RT5677_AD_STEREO1_FILTER | RT5677_I2S1_SOURCE, + RT5677_CLK_SEL_I2S1_ASRC); + + /* Request rt5677 GPIO for headphone amp control */ + bdw_rt5677->gpio_hp_en = devm_gpiod_get(component->dev, "headphone-enable", + GPIOD_OUT_LOW); + if (IS_ERR(bdw_rt5677->gpio_hp_en)) { + dev_err(component->dev, "Can't find HP_AMP_SHDN_L gpio\n"); + return PTR_ERR(bdw_rt5677->gpio_hp_en); + } + + /* Create and initialize headphone jack */ + if (!snd_soc_card_jack_new(rtd->card, "Headphone Jack", + SND_JACK_HEADPHONE, &headphone_jack, + &headphone_jack_pin, 1)) { + headphone_jack_gpio.gpiod_dev = component->dev; + if (snd_soc_jack_add_gpios(&headphone_jack, 1, + &headphone_jack_gpio)) + dev_err(component->dev, "Can't add headphone jack gpio\n"); + } else { + dev_err(component->dev, "Can't create headphone jack\n"); + } + + /* Create and initialize mic jack */ + if (!snd_soc_card_jack_new(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, &mic_jack, + &mic_jack_pin, 1)) { + mic_jack_gpio.gpiod_dev = component->dev; + if (snd_soc_jack_add_gpios(&mic_jack, 1, &mic_jack_gpio)) + dev_err(component->dev, "Can't add mic jack gpio\n"); + } else { + dev_err(component->dev, "Can't create mic jack\n"); + } + bdw_rt5677->component = component; + + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + return 0; +} + +/* broadwell digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link bdw_rt5677_dais[] = { + /* Front End DAI links */ + { + .name = "System PCM", + .stream_name = "System Playback/Capture", + .cpu_dai_name = "System Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = bdw_rt5677_rtd_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST + }, + .dpcm_capture = 1, + .dpcm_playback = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .id = 0, + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_name = "i2c-RT5677CE:00", + .codec_dai_name = "rt5677-aif1", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broadwell_ssp0_fixup, + .ops = &bdw_rt5677_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + .init = bdw_rt5677_init, + }, +}; + +static int bdw_rt5677_suspend_pre(struct snd_soc_card *card) +{ + struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card); + struct snd_soc_dapm_context *dapm; + + if (bdw_rt5677->component) { + dapm = snd_soc_component_get_dapm(bdw_rt5677->component); + snd_soc_dapm_disable_pin(dapm, "MICBIAS1"); + } + return 0; +} + +static int bdw_rt5677_resume_post(struct snd_soc_card *card) +{ + struct bdw_rt5677_priv *bdw_rt5677 = snd_soc_card_get_drvdata(card); + struct snd_soc_dapm_context *dapm; + + if (bdw_rt5677->component) { + dapm = snd_soc_component_get_dapm(bdw_rt5677->component); + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + } + return 0; +} + +/* ASoC machine driver for Broadwell DSP + RT5677 */ +static struct snd_soc_card bdw_rt5677_card = { + .name = "bdw-rt5677", + .owner = THIS_MODULE, + .dai_link = bdw_rt5677_dais, + .num_links = ARRAY_SIZE(bdw_rt5677_dais), + .dapm_widgets = bdw_rt5677_widgets, + .num_dapm_widgets = ARRAY_SIZE(bdw_rt5677_widgets), + .dapm_routes = bdw_rt5677_map, + .num_dapm_routes = ARRAY_SIZE(bdw_rt5677_map), + .controls = bdw_rt5677_controls, + .num_controls = ARRAY_SIZE(bdw_rt5677_controls), + .fully_routed = true, + .suspend_pre = bdw_rt5677_suspend_pre, + .resume_post = bdw_rt5677_resume_post, +}; + +static int bdw_rt5677_probe(struct platform_device *pdev) +{ + struct bdw_rt5677_priv *bdw_rt5677; + + bdw_rt5677_card.dev = &pdev->dev; + + /* Allocate driver private struct */ + bdw_rt5677 = devm_kzalloc(&pdev->dev, sizeof(struct bdw_rt5677_priv), + GFP_KERNEL); + if (!bdw_rt5677) { + dev_err(&pdev->dev, "Can't allocate bdw_rt5677\n"); + return -ENOMEM; + } + + snd_soc_card_set_drvdata(&bdw_rt5677_card, bdw_rt5677); + + return devm_snd_soc_register_card(&pdev->dev, &bdw_rt5677_card); +} + +static struct platform_driver bdw_rt5677_audio = { + .probe = bdw_rt5677_probe, + .driver = { + .name = "bdw-rt5677", + }, +}; + +module_platform_driver(bdw_rt5677_audio) + +/* Module information */ +MODULE_AUTHOR("Ben Zhang"); +MODULE_DESCRIPTION("Intel Broadwell RT5677 machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bdw-rt5677"); diff --git a/sound/soc/intel/boards/broadwell.c b/sound/soc/intel/boards/broadwell.c new file mode 100644 index 000000000..78ec97b53 --- /dev/null +++ b/sound/soc/intel/boards/broadwell.c @@ -0,0 +1,287 @@ +/* + * Intel Broadwell Wildcatpoint SST Audio + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../haswell/sst-haswell-ipc.h" + +#include "../../codecs/rt286.h" + +static struct snd_soc_jack broadwell_headset; +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin broadwell_headset_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static const struct snd_kcontrol_new broadwell_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), +}; + +static const struct snd_soc_dapm_widget broadwell_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("DMIC1", NULL), + SND_SOC_DAPM_MIC("DMIC2", NULL), + SND_SOC_DAPM_LINE("Line Jack", NULL), +}; + +static const struct snd_soc_dapm_route broadwell_rt286_map[] = { + + /* speaker */ + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, + + /* HP jack connectors - unknown if we have jack deteck */ + {"Headphone Jack", NULL, "HPO Pin"}, + + /* other jacks */ + {"MIC1", NULL, "Mic Jack"}, + {"LINE1", NULL, "Line Jack"}, + + /* digital mics */ + {"DMIC1 Pin", NULL, "DMIC1"}, + {"DMIC2 Pin", NULL, "DMIC2"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static int broadwell_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + int ret = 0; + ret = snd_soc_card_jack_new(rtd->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, &broadwell_headset, + broadwell_headset_pins, ARRAY_SIZE(broadwell_headset_pins)); + if (ret) + return ret; + + rt286_mic_detect(component, &broadwell_headset); + return 0; +} + + +static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int broadwell_rt286_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, + SND_SOC_CLOCK_IN); + + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + return ret; +} + +static const struct snd_soc_ops broadwell_rt286_ops = { + .hw_params = broadwell_rt286_hw_params, +}; + +static int broadwell_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_pdata *pdata = dev_get_platdata(component->dev); + struct sst_hsw *broadwell = pdata->dsp; + int ret; + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(broadwell, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set device config\n"); + return ret; + } + + return 0; +} + +/* broadwell digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link broadwell_rt286_dais[] = { + /* Front End DAI links */ + { + .name = "System PCM", + .stream_name = "System Playback/Capture", + .cpu_dai_name = "System Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = broadwell_rtd_init, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "Offload0", + .stream_name = "Offload0 Playback", + .cpu_dai_name = "Offload0 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Offload1", + .stream_name = "Offload1 Playback", + .cpu_dai_name = "Offload1 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Loopback PCM", + .stream_name = "Loopback", + .cpu_dai_name = "Loopback Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + }, + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .id = 0, + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_name = "i2c-INT343A:00", + .codec_dai_name = "rt286-aif1", + .init = broadwell_rt286_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broadwell_ssp0_fixup, + .ops = &broadwell_rt286_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static int broadwell_suspend(struct snd_soc_card *card){ + struct snd_soc_component *component; + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strcmp(component->name, "i2c-INT343A:00")) { + + dev_dbg(component->dev, "disabling jack detect before going to suspend.\n"); + rt286_mic_detect(component, NULL); + break; + } + } + return 0; +} + +static int broadwell_resume(struct snd_soc_card *card){ + struct snd_soc_component *component; + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strcmp(component->name, "i2c-INT343A:00")) { + + dev_dbg(component->dev, "enabling jack detect for resume.\n"); + rt286_mic_detect(component, &broadwell_headset); + break; + } + } + return 0; +} + +/* broadwell audio machine driver for WPT + RT286S */ +static struct snd_soc_card broadwell_rt286 = { + .name = "broadwell-rt286", + .owner = THIS_MODULE, + .dai_link = broadwell_rt286_dais, + .num_links = ARRAY_SIZE(broadwell_rt286_dais), + .controls = broadwell_controls, + .num_controls = ARRAY_SIZE(broadwell_controls), + .dapm_widgets = broadwell_widgets, + .num_dapm_widgets = ARRAY_SIZE(broadwell_widgets), + .dapm_routes = broadwell_rt286_map, + .num_dapm_routes = ARRAY_SIZE(broadwell_rt286_map), + .fully_routed = true, + .suspend_pre = broadwell_suspend, + .resume_post = broadwell_resume, +}; + +static int broadwell_audio_probe(struct platform_device *pdev) +{ + broadwell_rt286.dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, &broadwell_rt286); +} + +static struct platform_driver broadwell_audio = { + .probe = broadwell_audio_probe, + .driver = { + .name = "broadwell-audio", + }, +}; + +module_platform_driver(broadwell_audio) + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Intel SST Audio for WPT/Broadwell"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:broadwell-audio"); diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c new file mode 100644 index 000000000..6f052fc8d --- /dev/null +++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c @@ -0,0 +1,616 @@ +/* + * Intel Broxton-P I2S Machine Driver + * + * Copyright (C) 2016, Intel Corporation. All rights reserved. + * + * Modified from: + * Intel Skylake I2S Machine driver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/hdac_hdmi.h" +#include "../../codecs/da7219.h" +#include "../../codecs/da7219-aad.h" + +#define BXT_DIALOG_CODEC_DAI "da7219-hifi" +#define BXT_MAXIM_CODEC_DAI "HiFi" +#define DUAL_CHANNEL 2 +#define QUAD_CHANNEL 4 + +static struct snd_soc_jack broxton_headset; +static struct snd_soc_jack broxton_hdmi[3]; + +struct bxt_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct bxt_card_private { + struct list_head hdmi_pcm_list; +}; + +enum { + BXT_DPCM_AUDIO_PB = 0, + BXT_DPCM_AUDIO_CP, + BXT_DPCM_AUDIO_REF_CP, + BXT_DPCM_AUDIO_DMIC_CP, + BXT_DPCM_AUDIO_HDMI1_PB, + BXT_DPCM_AUDIO_HDMI2_PB, + BXT_DPCM_AUDIO_HDMI3_PB, +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + + codec_dai = snd_soc_card_get_codec_dai(card, BXT_DIALOG_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_MCLK, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop PLL: %d\n", ret); + } else if(SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304); + if (ret) + dev_err(card->dev, "failed to start PLL: %d\n", ret); + } + + return ret; +} + +static const struct snd_kcontrol_new broxton_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget broxton_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_POST_PMD|SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_route broxton_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + /* speaker */ + {"Spk", NULL, "Speaker"}, + + /* other jacks */ + {"MIC", NULL, "Headset Mic"}, + + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, + + /* CODEC BE connections */ + {"HiFi Playback", NULL, "ssp5 Tx"}, + {"ssp5 Tx", NULL, "codec0_out"}, + + {"Playback", NULL, "ssp1 Tx"}, + {"ssp1 Tx", NULL, "codec1_out"}, + + {"codec0_in", NULL, "ssp1 Rx"}, + {"ssp1 Rx", NULL, "Capture"}, + + {"HDMI1", NULL, "hif5-0 Output"}, + {"HDMI2", NULL, "hif6-0 Output"}, + {"HDMI2", NULL, "hif7-0 Output"}, + + {"hifi3", NULL, "iDisp3 Tx"}, + {"iDisp3 Tx", NULL, "iDisp3_out"}, + {"hifi2", NULL, "iDisp2 Tx"}, + {"iDisp2 Tx", NULL, "iDisp2_out"}, + {"hifi1", NULL, "iDisp1 Tx"}, + {"iDisp1 Tx", NULL, "iDisp1_out"}, + + /* DMIC */ + {"dmic01_hifi", NULL, "DMIC01 Rx"}, + {"DMIC01 Rx", NULL, "DMIC AIF"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = DUAL_CHANNEL; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int broxton_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = rtd->codec_dai->component; + + /* Configure sysclk for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, 19200000, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &broxton_headset, NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + da7219_aad_jack_det(component, &broxton_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + + return ret; +} + +static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct bxt_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct bxt_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = BXT_DPCM_AUDIO_HDMI1_PB + dai->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int broxton_da7219_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static const unsigned int channels_quad[] = { + QUAD_CHANNEL, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels_quad = { + .count = ARRAY_SIZE(channels_quad), + .list = channels_quad, + .mask = 0, +}; + +static int bxt_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * On this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops broxton_da7219_fe_ops = { + .startup = bxt_fe_startup, +}; + +static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + if (params_channels(params) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static int broxton_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels_quad); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static const struct snd_soc_ops broxton_dmic_ops = { + .startup = broxton_dmic_startup, +}; + +static const unsigned int rates_16000[] = { + 16000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16000 = { + .count = ARRAY_SIZE(rates_16000), + .list = rates_16000, +}; + +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + +static int broxton_refcap_startup(struct snd_pcm_substream *substream) +{ + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_16000); +}; + +static const struct snd_soc_ops broxton_refcap_ops = { + .startup = broxton_refcap_startup, +}; + +/* broxton digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link broxton_dais[] = { + /* Front End DAI links */ + [BXT_DPCM_AUDIO_PB] = + { + .name = "Bxt Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = broxton_da7219_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &broxton_da7219_fe_ops, + }, + [BXT_DPCM_AUDIO_CP] = + { + .name = "Bxt Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &broxton_da7219_fe_ops, + }, + [BXT_DPCM_AUDIO_REF_CP] = + { + .name = "Bxt Audio Reference cap", + .stream_name = "Refcap", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &broxton_refcap_ops, + }, + [BXT_DPCM_AUDIO_DMIC_CP] = + { + .name = "Bxt Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &broxton_dmic_ops, + }, + [BXT_DPCM_AUDIO_HDMI1_PB] = + { + .name = "Bxt HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI2_PB] = + { + .name = "Bxt HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI3_PB] = + { + .name = "Bxt HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + /* Back End DAI links */ + { + /* SSP5 - Codec */ + .name = "SSP5-Codec", + .id = 0, + .cpu_dai_name = "SSP5 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "MX98357A:00", + .codec_dai_name = BXT_MAXIM_CODEC_DAI, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broxton_ssp_fixup, + .dpcm_playback = 1, + }, + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "i2c-DLGS7219:00", + .codec_dai_name = BXT_DIALOG_CODEC_DAI, + .init = broxton_da7219_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broxton_ssp_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:0e.0", + .ignore_suspend = 1, + .be_hw_params_fixup = broxton_dmic_fixup, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +#define NAME_SIZE 32 +static int bxt_card_late_probe(struct snd_soc_card *card) +{ + struct bxt_card_private *ctx = snd_soc_card_get_drvdata(card); + struct bxt_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &broxton_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &broxton_hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* broxton audio machine driver for SPT + da7219 */ +static struct snd_soc_card broxton_audio_card = { + .name = "bxtda7219max", + .owner = THIS_MODULE, + .dai_link = broxton_dais, + .num_links = ARRAY_SIZE(broxton_dais), + .controls = broxton_controls, + .num_controls = ARRAY_SIZE(broxton_controls), + .dapm_widgets = broxton_widgets, + .num_dapm_widgets = ARRAY_SIZE(broxton_widgets), + .dapm_routes = broxton_map, + .num_dapm_routes = ARRAY_SIZE(broxton_map), + .fully_routed = true, + .late_probe = bxt_card_late_probe, +}; + +static int broxton_audio_probe(struct platform_device *pdev) +{ + struct bxt_card_private *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + broxton_audio_card.dev = &pdev->dev; + snd_soc_card_set_drvdata(&broxton_audio_card, ctx); + + return devm_snd_soc_register_card(&pdev->dev, &broxton_audio_card); +} + +static struct platform_driver broxton_audio = { + .probe = broxton_audio_probe, + .driver = { + .name = "bxt_da7219_max98357a", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(broxton_audio) + +/* Module information */ +MODULE_DESCRIPTION("Audio Machine driver-DA7219 & MAX98357A in I2S mode"); +MODULE_AUTHOR("Sathyanarayana Nujella "); +MODULE_AUTHOR("Rohit Ainapure "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_AUTHOR("Conrad Cooke "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bxt_da7219_max98357a"); diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c new file mode 100644 index 000000000..ba76e37a4 --- /dev/null +++ b/sound/soc/intel/boards/bxt_rt298.c @@ -0,0 +1,634 @@ +/* + * Intel Broxton-P I2S Machine Driver + * + * Copyright (C) 2014-2016, Intel Corporation. All rights reserved. + * + * Modified from: + * Intel Skylake I2S Machine driver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/hdac_hdmi.h" +#include "../../codecs/rt298.h" + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack broxton_headset; +static struct snd_soc_jack broxton_hdmi[3]; + +struct bxt_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct bxt_rt286_private { + struct list_head hdmi_pcm_list; +}; + +enum { + BXT_DPCM_AUDIO_PB = 0, + BXT_DPCM_AUDIO_CP, + BXT_DPCM_AUDIO_REF_CP, + BXT_DPCM_AUDIO_DMIC_CP, + BXT_DPCM_AUDIO_HDMI1_PB, + BXT_DPCM_AUDIO_HDMI2_PB, + BXT_DPCM_AUDIO_HDMI3_PB, +}; + +static struct snd_soc_jack_pin broxton_headset_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static const struct snd_kcontrol_new broxton_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), +}; + +static const struct snd_soc_dapm_widget broxton_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("DMIC2", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), +}; + +static const struct snd_soc_dapm_route broxton_rt298_map[] = { + /* speaker */ + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, + + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + + /* other jacks */ + {"MIC1", NULL, "Mic Jack"}, + + /* digital mics */ + {"DMIC1 Pin", NULL, "DMIC2"}, + {"DMic", NULL, "SoC DMIC"}, + + {"HDMI1", NULL, "hif5-0 Output"}, + {"HDMI2", NULL, "hif6-0 Output"}, + {"HDMI2", NULL, "hif7-0 Output"}, + + /* CODEC BE connections */ + { "AIF1 Playback", NULL, "ssp5 Tx"}, + { "ssp5 Tx", NULL, "codec0_out"}, + { "ssp5 Tx", NULL, "codec1_out"}, + + { "codec0_in", NULL, "ssp5 Rx" }, + { "ssp5 Rx", NULL, "AIF1 Capture" }, + + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "Capture" }, + + { "hifi3", NULL, "iDisp3 Tx"}, + { "iDisp3 Tx", NULL, "iDisp3_out"}, + { "hifi2", NULL, "iDisp2 Tx"}, + { "iDisp2 Tx", NULL, "iDisp2_out"}, + { "hifi1", NULL, "iDisp1 Tx"}, + { "iDisp1 Tx", NULL, "iDisp1_out"}, +}; + +static const struct snd_soc_dapm_route geminilake_rt298_map[] = { + /* speaker */ + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, + + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + + /* other jacks */ + {"MIC1", NULL, "Mic Jack"}, + + /* digital mics */ + {"DMIC1 Pin", NULL, "DMIC2"}, + {"DMic", NULL, "SoC DMIC"}, + + {"HDMI1", NULL, "hif5-0 Output"}, + {"HDMI2", NULL, "hif6-0 Output"}, + {"HDMI2", NULL, "hif7-0 Output"}, + + /* CODEC BE connections */ + { "AIF1 Playback", NULL, "ssp2 Tx"}, + { "ssp2 Tx", NULL, "codec0_out"}, + { "ssp2 Tx", NULL, "codec1_out"}, + + { "codec0_in", NULL, "ssp2 Rx" }, + { "ssp2 Rx", NULL, "AIF1 Capture" }, + + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "Capture" }, + + { "dmic_voice", NULL, "DMIC16k Rx" }, + { "DMIC16k Rx", NULL, "Capture" }, + + { "hifi3", NULL, "iDisp3 Tx"}, + { "iDisp3 Tx", NULL, "iDisp3_out"}, + { "hifi2", NULL, "iDisp2 Tx"}, + { "iDisp2 Tx", NULL, "iDisp2_out"}, + { "hifi1", NULL, "iDisp1 Tx"}, + { "iDisp1 Tx", NULL, "iDisp1_out"}, +}; + +static int broxton_rt298_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + int ret = 0; + + ret = snd_soc_card_jack_new(rtd->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &broxton_headset, + broxton_headset_pins, ARRAY_SIZE(broxton_headset_pins)); + + if (ret) + return ret; + + rt298_mic_detect(component, &broxton_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + + return 0; +} + +static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct bxt_rt286_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct bxt_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = BXT_DPCM_AUDIO_HDMI1_PB + dai->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int broxton_ssp5_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP5 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int broxton_rt298_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT298_SCLK_S_PLL, + 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + return ret; +} + +static const struct snd_soc_ops broxton_rt298_ops = { + .hw_params = broxton_rt298_hw_params, +}; + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + channels->min = channels->max = 4; + + return 0; +} + +static const unsigned int channels_dmic[] = { + 1, 2, 3, 4, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static int broxton_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = 4; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_dmic_channels); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static const struct snd_soc_ops broxton_dmic_ops = { + .startup = broxton_dmic_startup, +}; + +static const unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int bxt_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * on this platform for PCM device we support: + * 48Khz + * stereo + * 16-bit audio + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops broxton_rt286_fe_ops = { + .startup = bxt_fe_startup, +}; + +/* broxton digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link broxton_rt298_dais[] = { + /* Front End DAI links */ + [BXT_DPCM_AUDIO_PB] = + { + .name = "Bxt Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .nonatomic = 1, + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = broxton_rt298_fe_init, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &broxton_rt286_fe_ops, + }, + [BXT_DPCM_AUDIO_CP] = + { + .name = "Bxt Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .nonatomic = 1, + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &broxton_rt286_fe_ops, + }, + [BXT_DPCM_AUDIO_REF_CP] = + { + .name = "Bxt Audio Reference cap", + .stream_name = "refcap", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_DMIC_CP] = + { + .name = "Bxt Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &broxton_dmic_ops, + }, + [BXT_DPCM_AUDIO_HDMI1_PB] = + { + .name = "Bxt HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI2_PB] = + { + .name = "Bxt HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI3_PB] = + { + .name = "Bxt HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + /* Back End DAI links */ + { + /* SSP5 - Codec */ + .name = "SSP5-Codec", + .id = 0, + .cpu_dai_name = "SSP5 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "i2c-INT343A:00", + .codec_dai_name = "rt298-aif1", + .init = broxton_rt298_codec_init, + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broxton_ssp5_fixup, + .ops = &broxton_rt298_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 1, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:0e.0", + .be_hw_params_fixup = broxton_dmic_fixup, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "dmic16k", + .id = 2, + .cpu_dai_name = "DMIC16k Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:0e.0", + .be_hw_params_fixup = broxton_dmic_fixup, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +#define NAME_SIZE 32 +static int bxt_card_late_probe(struct snd_soc_card *card) +{ + struct bxt_rt286_private *ctx = snd_soc_card_get_drvdata(card); + struct bxt_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &broxton_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &broxton_hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + + +/* broxton audio machine driver for SPT + RT298S */ +static struct snd_soc_card broxton_rt298 = { + .name = "broxton-rt298", + .owner = THIS_MODULE, + .dai_link = broxton_rt298_dais, + .num_links = ARRAY_SIZE(broxton_rt298_dais), + .controls = broxton_controls, + .num_controls = ARRAY_SIZE(broxton_controls), + .dapm_widgets = broxton_widgets, + .num_dapm_widgets = ARRAY_SIZE(broxton_widgets), + .dapm_routes = broxton_rt298_map, + .num_dapm_routes = ARRAY_SIZE(broxton_rt298_map), + .fully_routed = true, + .late_probe = bxt_card_late_probe, + +}; + +static struct snd_soc_card geminilake_rt298 = { + .name = "geminilake-rt298", + .owner = THIS_MODULE, + .dai_link = broxton_rt298_dais, + .num_links = ARRAY_SIZE(broxton_rt298_dais), + .controls = broxton_controls, + .num_controls = ARRAY_SIZE(broxton_controls), + .dapm_widgets = broxton_widgets, + .num_dapm_widgets = ARRAY_SIZE(broxton_widgets), + .dapm_routes = geminilake_rt298_map, + .num_dapm_routes = ARRAY_SIZE(geminilake_rt298_map), + .fully_routed = true, + .late_probe = bxt_card_late_probe, +}; + +static int broxton_audio_probe(struct platform_device *pdev) +{ + struct bxt_rt286_private *ctx; + struct snd_soc_card *card = + (struct snd_soc_card *)pdev->id_entry->driver_data; + int i; + + for (i = 0; i < ARRAY_SIZE(broxton_rt298_dais); i++) { + if (!strncmp(card->dai_link[i].codec_name, "i2c-INT343A:00", + I2C_NAME_SIZE)) { + if (!strncmp(card->name, "broxton-rt298", + PLATFORM_NAME_SIZE)) { + card->dai_link[i].name = "SSP5-Codec"; + card->dai_link[i].cpu_dai_name = "SSP5 Pin"; + } else if (!strncmp(card->name, "geminilake-rt298", + PLATFORM_NAME_SIZE)) { + card->dai_link[i].name = "SSP2-Codec"; + card->dai_link[i].cpu_dai_name = "SSP2 Pin"; + } + } + } + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, ctx); + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct platform_device_id bxt_board_ids[] = { + { .name = "bxt_alc298s_i2s", .driver_data = + (unsigned long)&broxton_rt298 }, + { .name = "glk_alc298s_i2s", .driver_data = + (unsigned long)&geminilake_rt298 }, + {} +}; + +static struct platform_driver broxton_audio = { + .probe = broxton_audio_probe, + .driver = { + .name = "bxt_alc298s_i2s", + .pm = &snd_soc_pm_ops, + }, + .id_table = bxt_board_ids, +}; +module_platform_driver(broxton_audio) + +/* Module information */ +MODULE_AUTHOR("Ramesh Babu "); +MODULE_AUTHOR("Senthilnathan Veppur "); +MODULE_DESCRIPTION("Intel SST Audio for Broxton"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bxt_alc298s_i2s"); +MODULE_ALIAS("platform:glk_alc298s_i2s"); diff --git a/sound/soc/intel/boards/byt-max98090.c b/sound/soc/intel/boards/byt-max98090.c new file mode 100644 index 000000000..f1283634b --- /dev/null +++ b/sound/soc/intel/boards/byt-max98090.c @@ -0,0 +1,188 @@ +/* + * Intel Baytrail SST MAX98090 machine driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/max98090.h" + +struct byt_max98090_private { + struct snd_soc_jack jack; +}; + +static const struct snd_soc_dapm_widget byt_max98090_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route byt_max98090_audio_map[] = { + {"IN34", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "MICBIAS"}, + {"DMICL", NULL, "Int Mic"}, + {"Headphone", NULL, "HPL"}, + {"Headphone", NULL, "HPR"}, + {"Ext Spk", NULL, "SPKL"}, + {"Ext Spk", NULL, "SPKR"}, +}; + +static const struct snd_kcontrol_new byt_max98090_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .name = "hp", + .report = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, + .debounce_time = 200, + }, + { + .name = "mic", + .invert = 1, + .report = SND_JACK_MICROPHONE, + .debounce_time = 200, + }, +}; + +static const struct acpi_gpio_params hp_gpios = { 0, 0, false }; +static const struct acpi_gpio_params mic_gpios = { 1, 0, false }; + +static const struct acpi_gpio_mapping acpi_byt_max98090_gpios[] = { + { "hp-gpios", &hp_gpios, 1 }, + { "mic-gpios", &mic_gpios, 1 }, + {}, +}; + +static int byt_max98090_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_card *card = runtime->card; + struct byt_max98090_private *drv = snd_soc_card_get_drvdata(card); + struct snd_soc_jack *jack = &drv->jack; + + card->dapm.idle_bias_off = true; + + ret = snd_soc_dai_set_sysclk(runtime->codec_dai, + M98090_REG_SYSTEM_CLOCK, + 25000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "Can't set codec clock %d\n", ret); + return ret; + } + + /* Enable jack detection */ + ret = snd_soc_card_jack_new(runtime->card, "Headset", + SND_JACK_LINEOUT | SND_JACK_HEADSET, jack, + hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); + if (ret) + return ret; + + return snd_soc_jack_add_gpiods(card->dev->parent, jack, + ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); +} + +static struct snd_soc_dai_link byt_max98090_dais[] = { + { + .name = "Baytrail Audio", + .stream_name = "Audio", + .cpu_dai_name = "baytrail-pcm-audio", + .codec_dai_name = "HiFi", + .codec_name = "i2c-193C9890:00", + .platform_name = "baytrail-pcm-audio", + .init = byt_max98090_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, +}; + +static struct snd_soc_card byt_max98090_card = { + .name = "byt-max98090", + .owner = THIS_MODULE, + .dai_link = byt_max98090_dais, + .num_links = ARRAY_SIZE(byt_max98090_dais), + .dapm_widgets = byt_max98090_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_max98090_widgets), + .dapm_routes = byt_max98090_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_max98090_audio_map), + .controls = byt_max98090_controls, + .num_controls = ARRAY_SIZE(byt_max98090_controls), + .fully_routed = true, +}; + +static int byt_max98090_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct byt_max98090_private *priv; + int ret_val; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "allocation failed\n"); + return -ENOMEM; + } + + ret_val = devm_acpi_dev_add_driver_gpios(dev->parent, acpi_byt_max98090_gpios); + if (ret_val) + dev_dbg(dev, "Unable to add GPIO mapping table\n"); + + byt_max98090_card.dev = &pdev->dev; + snd_soc_card_set_drvdata(&byt_max98090_card, priv); + ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_max98090_card); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + + return 0; +} + +static struct platform_driver byt_max98090_driver = { + .probe = byt_max98090_probe, + .driver = { + .name = "byt-max98090", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(byt_max98090_driver) + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); +MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:byt-max98090"); diff --git a/sound/soc/intel/boards/byt-rt5640.c b/sound/soc/intel/boards/byt-rt5640.c new file mode 100644 index 000000000..df902d821 --- /dev/null +++ b/sound/soc/intel/boards/byt-rt5640.c @@ -0,0 +1,230 @@ +/* + * Intel Baytrail SST RT5640 machine driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5640.h" + +#include "../common/sst-dsp.h" + +static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { + {"Headset Mic", NULL, "MICBIAS1"}, + {"IN2P", NULL, "Headset Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Speaker", NULL, "SPOLP"}, + {"Speaker", NULL, "SPOLN"}, + {"Speaker", NULL, "SPORP"}, + {"Speaker", NULL, "SPORN"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = { + {"DMIC1", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = { + {"DMIC2", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = { + {"Internal Mic", NULL, "MICBIAS1"}, + {"IN1P", NULL, "Internal Mic"}, +}; + +enum { + BYT_RT5640_DMIC1_MAP, + BYT_RT5640_DMIC2_MAP, + BYT_RT5640_IN1_MAP, +}; + +#define BYT_RT5640_MAP(quirk) ((quirk) & 0xff) +#define BYT_RT5640_DMIC_EN BIT(16) + +static unsigned long byt_rt5640_quirk = BYT_RT5640_DMIC1_MAP | + BYT_RT5640_DMIC_EN; + +static const struct snd_kcontrol_new byt_rt5640_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int byt_rt5640_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, + params_rate(params) * 256, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set codec clock %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5640_PLL1_S_BCLK1, + params_rate(params) * 64, + params_rate(params) * 256); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set codec pll: %d\n", ret); + return ret; + } + return 0; +} + +static int byt_rt5640_quirk_cb(const struct dmi_system_id *id) +{ + byt_rt5640_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id byt_rt5640_quirk_table[] = { + { + .callback = byt_rt5640_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), + }, + .driver_data = (unsigned long *)BYT_RT5640_IN1_MAP, + }, + { + .callback = byt_rt5640_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "DellInc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"), + }, + .driver_data = (unsigned long *)(BYT_RT5640_DMIC2_MAP | + BYT_RT5640_DMIC_EN), + }, + {} +}; + +static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_component *component = runtime->codec_dai->component; + struct snd_soc_card *card = runtime->card; + const struct snd_soc_dapm_route *custom_map; + int num_routes; + + card->dapm.idle_bias_off = true; + + ret = snd_soc_add_card_controls(card, byt_rt5640_controls, + ARRAY_SIZE(byt_rt5640_controls)); + if (ret) { + dev_err(card->dev, "unable to add card controls\n"); + return ret; + } + + dmi_check_system(byt_rt5640_quirk_table); + switch (BYT_RT5640_MAP(byt_rt5640_quirk)) { + case BYT_RT5640_IN1_MAP: + custom_map = byt_rt5640_intmic_in1_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_in1_map); + break; + case BYT_RT5640_DMIC2_MAP: + custom_map = byt_rt5640_intmic_dmic2_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map); + break; + default: + custom_map = byt_rt5640_intmic_dmic1_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map); + } + + ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); + if (ret) + return ret; + + if (byt_rt5640_quirk & BYT_RT5640_DMIC_EN) { + ret = rt5640_dmic_enable(component, 0, 0); + if (ret) + return ret; + } + + snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); + + return ret; +} + +static struct snd_soc_ops byt_rt5640_ops = { + .hw_params = byt_rt5640_hw_params, +}; + +static struct snd_soc_dai_link byt_rt5640_dais[] = { + { + .name = "Baytrail Audio", + .stream_name = "Audio", + .cpu_dai_name = "baytrail-pcm-audio", + .codec_dai_name = "rt5640-aif1", + .codec_name = "i2c-10EC5640:00", + .platform_name = "baytrail-pcm-audio", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = byt_rt5640_init, + .ops = &byt_rt5640_ops, + }, +}; + +static struct snd_soc_card byt_rt5640_card = { + .name = "byt-rt5640", + .owner = THIS_MODULE, + .dai_link = byt_rt5640_dais, + .num_links = ARRAY_SIZE(byt_rt5640_dais), + .dapm_widgets = byt_rt5640_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets), + .dapm_routes = byt_rt5640_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map), + .fully_routed = true, +}; + +static int byt_rt5640_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &byt_rt5640_card; + + card->dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static struct platform_driver byt_rt5640_audio = { + .probe = byt_rt5640_probe, + .driver = { + .name = "byt-rt5640", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(byt_rt5640_audio) + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail Machine driver"); +MODULE_AUTHOR("Omair Md Abdullah, Jarkko Nikula"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:byt-rt5640"); diff --git a/sound/soc/intel/boards/bytcht_da7213.c b/sound/soc/intel/boards/bytcht_da7213.c new file mode 100644 index 000000000..2179dedb2 --- /dev/null +++ b/sound/soc/intel/boards/bytcht_da7213.c @@ -0,0 +1,274 @@ +/* + * bytcht-da7213.c - ASoc Machine driver for Intel Baytrail and + * Cherrytrail-based platforms, with Dialog DA7213 codec + * + * Copyright (C) 2017 Intel Corporation + * Author: Pierre-Louis Bossart + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/da7213.h" +#include "../atom/sst-atom-controls.h" + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Mic"), + SOC_DAPM_PIN_SWITCH("Aux In"), +}; + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_LINE("Aux In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + {"AUXL", NULL, "Aux In"}, + {"AUXR", NULL, "Aux In"}, + + /* Assume Mic1 is linked to Headset and Mic2 to on-board mic */ + {"MIC1", NULL, "Headset Mic"}, + {"MIC2", NULL, "Mic"}, + + /* SOC-codec link */ + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"Playback", NULL, "ssp2 Tx"}, + {"ssp2 Rx", NULL, "Capture"}, +}; + +static int codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + int ret; + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static int aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static int aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, + 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7213_SYSCLK_PLL_SRM, 0, DA7213_PLL_FREQ_OUT_98304000); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static int aif1_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7213_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static const struct snd_soc_ops aif1_ops = { + .startup = aif1_startup, +}; + +static const struct snd_soc_ops ssp2_ops = { + .hw_params = aif1_hw_params, + .hw_free = aif1_hw_free, + +}; + +static struct snd_soc_dai_link dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &aif1_ops, + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-Codec", + .id = 0, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "da7213-hifi", + .codec_name = "i2c-DLGS7213:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = codec_fixup, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card bytcht_da7213_card = { + .name = "bytcht-da7213", + .owner = THIS_MODULE, + .dai_link = dailink, + .num_links = ARRAY_SIZE(dailink), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static char codec_name[SND_ACPI_I2C_ID_LEN]; + +static int bytcht_da7213_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct snd_soc_acpi_mach *mach; + const char *i2c_name = NULL; + int dai_index = 0; + int ret_val = 0; + int i; + + mach = (&pdev->dev)->platform_data; + card = &bytcht_da7213_card; + card->dev = &pdev->dev; + + /* fix index of codec dai */ + for (i = 0; i < ARRAY_SIZE(dailink); i++) { + if (!strcmp(dailink[i].codec_name, "i2c-DLGS7213:00")) { + dai_index = i; + break; + } + } + + /* fixup codec name based on HID */ + i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); + if (i2c_name) { + snprintf(codec_name, sizeof(codec_name), + "%s%s", "i2c-", i2c_name); + dailink[dai_index].codec_name = codec_name; + } + + ret_val = devm_snd_soc_register_card(&pdev->dev, card); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, card); + return ret_val; +} + +static struct platform_driver bytcht_da7213_driver = { + .driver = { + .name = "bytcht_da7213", + }, + .probe = bytcht_da7213_probe, +}; +module_platform_driver(bytcht_da7213_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail+DA7213 Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcht_da7213"); diff --git a/sound/soc/intel/boards/bytcht_es8316.c b/sound/soc/intel/boards/bytcht_es8316.c new file mode 100644 index 000000000..adc26dfc7 --- /dev/null +++ b/sound/soc/intel/boards/bytcht_es8316.c @@ -0,0 +1,301 @@ +/* + * bytcht_es8316.c - ASoc Machine driver for Intel Baytrail/Cherrytrail + * platforms with Everest ES8316 SoC + * + * Copyright (C) 2017 Endless Mobile, Inc. + * Authors: David Yang , + * Daniel Drake + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../atom/sst-atom-controls.h" +#include "../common/sst-dsp.h" + +struct byt_cht_es8316_private { + struct clk *mclk; +}; + +static const struct snd_soc_dapm_widget byt_cht_es8316_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + + /* + * The codec supports two analog microphone inputs. I have only + * tested MIC1. A DMIC route could also potentially be added + * if such functionality is found on another platform. + */ + SND_SOC_DAPM_MIC("Microphone 1", NULL), + SND_SOC_DAPM_MIC("Microphone 2", NULL), +}; + +static const struct snd_soc_dapm_route byt_cht_es8316_audio_map[] = { + {"MIC1", NULL, "Microphone 1"}, + {"MIC2", NULL, "Microphone 2"}, + + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + + {"Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "Capture"}, +}; + +static const struct snd_kcontrol_new byt_cht_es8316_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Microphone 1"), + SOC_DAPM_PIN_SWITCH("Microphone 2"), +}; + +static int byt_cht_es8316_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct byt_cht_es8316_private *priv = snd_soc_card_get_drvdata(card); + int ret; + + card->dapm.idle_bias_off = true; + + /* + * The firmware might enable the clock at boot (this information + * may or may not be reflected in the enable clock register). + * To change the rate we must disable the clock first to cover these + * cases. Due to common clock framework restrictions that do not allow + * to disable a clock that has not been enabled, we need to enable + * the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); + + ret = clk_set_rate(priv->mclk, 19200000); + if (ret) + dev_err(card->dev, "unable to set MCLK rate\n"); + + ret = clk_prepare_enable(priv->mclk); + if (ret) + dev_err(card->dev, "unable to enable MCLK\n"); + + ret = snd_soc_dai_set_sysclk(runtime->codec_dai, 0, 19200000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "can't set codec clock %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_pcm_stream byt_cht_es8316_dai_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, +}; + +static int byt_cht_es8316_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret; + + /* The DSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS + ); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static int byt_cht_es8316_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops byt_cht_es8316_aif1_ops = { + .startup = byt_cht_es8316_aif1_startup, +}; + +static struct snd_soc_dai_link byt_cht_es8316_dais[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_cht_es8316_aif1_ops, + }, + + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &byt_cht_es8316_aif1_ops, + }, + + /* back ends */ + { + /* Only SSP2 has been tested here, so BYT-CR platforms that + * require SSP0 will not work. + */ + .name = "SSP2-Codec", + .id = 0, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "ES8316 HiFi", + .codec_name = "i2c-ESSX8316:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = byt_cht_es8316_codec_fixup, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + .init = byt_cht_es8316_init, + }, +}; + + +/* SoC card */ +static struct snd_soc_card byt_cht_es8316_card = { + .name = "bytcht-es8316", + .owner = THIS_MODULE, + .dai_link = byt_cht_es8316_dais, + .num_links = ARRAY_SIZE(byt_cht_es8316_dais), + .dapm_widgets = byt_cht_es8316_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_cht_es8316_widgets), + .dapm_routes = byt_cht_es8316_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_cht_es8316_audio_map), + .controls = byt_cht_es8316_controls, + .num_controls = ARRAY_SIZE(byt_cht_es8316_controls), + .fully_routed = true, +}; + +static char codec_name[SND_ACPI_I2C_ID_LEN]; + +static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) +{ + struct byt_cht_es8316_private *priv; + struct snd_soc_acpi_mach *mach; + const char *i2c_name = NULL; + int dai_index = 0; + int i; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mach = (&pdev->dev)->platform_data; + /* fix index of codec dai */ + for (i = 0; i < ARRAY_SIZE(byt_cht_es8316_dais); i++) { + if (!strcmp(byt_cht_es8316_dais[i].codec_name, + "i2c-ESSX8316:00")) { + dai_index = i; + break; + } + } + + /* fixup codec name based on HID */ + i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); + if (i2c_name) { + snprintf(codec_name, sizeof(codec_name), + "%s%s", "i2c-", i2c_name); + byt_cht_es8316_dais[dai_index].codec_name = codec_name; + } + + /* register the soc card */ + byt_cht_es8316_card.dev = &pdev->dev; + snd_soc_card_set_drvdata(&byt_cht_es8316_card, priv); + + priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + if (IS_ERR(priv->mclk)) { + ret = PTR_ERR(priv->mclk); + dev_err(&pdev->dev, + "Failed to get MCLK from pmc_plt_clk_3: %d\n", + ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, &byt_cht_es8316_card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret); + return ret; + } + platform_set_drvdata(pdev, &byt_cht_es8316_card); + return ret; +} + +static struct platform_driver snd_byt_cht_es8316_mc_driver = { + .driver = { + .name = "bytcht_es8316", + }, + .probe = snd_byt_cht_es8316_mc_probe, +}; + +module_platform_driver(snd_byt_cht_es8316_mc_driver); +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail Machine driver"); +MODULE_AUTHOR("David Yang "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcht_es8316"); diff --git a/sound/soc/intel/boards/bytcht_nocodec.c b/sound/soc/intel/boards/bytcht_nocodec.c new file mode 100644 index 000000000..b80ec027a --- /dev/null +++ b/sound/soc/intel/boards/bytcht_nocodec.c @@ -0,0 +1,200 @@ +/* + * bytcht_nocodec.c - ASoc Machine driver for MinnowBoard Max and Up + * to make I2S signals observable on the Low-Speed connector. Audio codec + * is not managed by ASoC/DAPM + * + * Copyright (C) 2015-2017 Intel Corp + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include "../atom/sst-atom-controls.h" + +static const struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"ssp2 Rx", NULL, "Mic"}, + {"Speaker", NULL, "ssp2 Tx"}, +}; + +static int codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret; + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static const unsigned int rates_48000[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static struct snd_soc_ops aif1_ops = { + .startup = aif1_startup, +}; + +static struct snd_soc_dai_link dais[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .ignore_suspend = 1, + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &aif1_ops, + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-LowSpeed Connector", + .id = 0, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = codec_fixup, + .ignore_suspend = 1, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +/* SoC card */ +static struct snd_soc_card bytcht_nocodec_card = { + .name = "bytcht-nocodec", + .owner = THIS_MODULE, + .dai_link = dais, + .num_links = ARRAY_SIZE(dais), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .fully_routed = true, +}; + +static int snd_bytcht_nocodec_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + + /* register the soc card */ + bytcht_nocodec_card.dev = &pdev->dev; + + ret_val = devm_snd_soc_register_card(&pdev->dev, &bytcht_nocodec_card); + + if (ret_val) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", + ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &bytcht_nocodec_card); + return ret_val; +} + +static struct platform_driver snd_bytcht_nocodec_mc_driver = { + .driver = { + .name = "bytcht_nocodec", + }, + .probe = snd_bytcht_nocodec_mc_probe, +}; +module_platform_driver(snd_bytcht_nocodec_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail/Cherrytrail Nocodec Machine driver"); +MODULE_AUTHOR("Pierre-Louis Bossart "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcht_nocodec"); diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c new file mode 100644 index 000000000..c4d19b88d --- /dev/null +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -0,0 +1,1456 @@ +/* + * byt_cr_dpcm_rt5640.c - ASoc Machine driver for Intel Byt CR platform + * + * Copyright (C) 2014 Intel Corp + * Author: Subhransu S. Prusty + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5640.h" +#include "../atom/sst-atom-controls.h" +#include "../common/sst-dsp.h" + +enum { + BYT_RT5640_DMIC1_MAP, + BYT_RT5640_DMIC2_MAP, + BYT_RT5640_IN1_MAP, + BYT_RT5640_IN3_MAP, +}; + +enum { + BYT_RT5640_JD_SRC_GPIO1 = (RT5640_JD_SRC_GPIO1 << 4), + BYT_RT5640_JD_SRC_JD1_IN4P = (RT5640_JD_SRC_JD1_IN4P << 4), + BYT_RT5640_JD_SRC_JD2_IN4N = (RT5640_JD_SRC_JD2_IN4N << 4), + BYT_RT5640_JD_SRC_GPIO2 = (RT5640_JD_SRC_GPIO2 << 4), + BYT_RT5640_JD_SRC_GPIO3 = (RT5640_JD_SRC_GPIO3 << 4), + BYT_RT5640_JD_SRC_GPIO4 = (RT5640_JD_SRC_GPIO4 << 4), +}; + +enum { + BYT_RT5640_OVCD_TH_600UA = (6 << 8), + BYT_RT5640_OVCD_TH_1500UA = (15 << 8), + BYT_RT5640_OVCD_TH_2000UA = (20 << 8), +}; + +enum { + BYT_RT5640_OVCD_SF_0P5 = (RT5640_OVCD_SF_0P5 << 13), + BYT_RT5640_OVCD_SF_0P75 = (RT5640_OVCD_SF_0P75 << 13), + BYT_RT5640_OVCD_SF_1P0 = (RT5640_OVCD_SF_1P0 << 13), + BYT_RT5640_OVCD_SF_1P5 = (RT5640_OVCD_SF_1P5 << 13), +}; + +#define BYT_RT5640_MAP(quirk) ((quirk) & GENMASK(3, 0)) +#define BYT_RT5640_JDSRC(quirk) (((quirk) & GENMASK(7, 4)) >> 4) +#define BYT_RT5640_OVCD_TH(quirk) (((quirk) & GENMASK(12, 8)) >> 8) +#define BYT_RT5640_OVCD_SF(quirk) (((quirk) & GENMASK(14, 13)) >> 13) +#define BYT_RT5640_JD_NOT_INV BIT(16) +#define BYT_RT5640_MONO_SPEAKER BIT(17) +#define BYT_RT5640_DIFF_MIC BIT(18) /* default is single-ended */ +#define BYT_RT5640_SSP2_AIF2 BIT(19) /* default is using AIF1 */ +#define BYT_RT5640_SSP0_AIF1 BIT(20) +#define BYT_RT5640_SSP0_AIF2 BIT(21) +#define BYT_RT5640_MCLK_EN BIT(22) +#define BYT_RT5640_MCLK_25MHZ BIT(23) + +#define BYTCR_INPUT_DEFAULTS \ + (BYT_RT5640_IN3_MAP | \ + BYT_RT5640_JD_SRC_JD1_IN4P | \ + BYT_RT5640_OVCD_TH_2000UA | \ + BYT_RT5640_OVCD_SF_0P75 | \ + BYT_RT5640_DIFF_MIC) + +/* in-diff or dmic-pin + jdsrc + ovcd-th + -sf + jd-inv + terminating entry */ +#define MAX_NO_PROPS 6 + +struct byt_rt5640_private { + struct snd_soc_jack jack; + struct clk *mclk; +}; +static bool is_bytcr; + +static unsigned long byt_rt5640_quirk = BYT_RT5640_MCLK_EN; +static unsigned int quirk_override; +module_param_named(quirk, quirk_override, uint, 0444); +MODULE_PARM_DESC(quirk, "Board-specific quirk override"); + +static void log_quirks(struct device *dev) +{ + int map; + bool has_mclk = false; + bool has_ssp0 = false; + bool has_ssp0_aif1 = false; + bool has_ssp0_aif2 = false; + bool has_ssp2_aif2 = false; + + map = BYT_RT5640_MAP(byt_rt5640_quirk); + switch (map) { + case BYT_RT5640_DMIC1_MAP: + dev_info(dev, "quirk DMIC1_MAP enabled\n"); + break; + case BYT_RT5640_DMIC2_MAP: + dev_info(dev, "quirk DMIC2_MAP enabled\n"); + break; + case BYT_RT5640_IN1_MAP: + dev_info(dev, "quirk IN1_MAP enabled\n"); + break; + case BYT_RT5640_IN3_MAP: + dev_info(dev, "quirk IN3_MAP enabled\n"); + break; + default: + dev_err(dev, "quirk map 0x%x is not supported, microphone input will not work\n", map); + break; + } + if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) { + dev_info(dev, "quirk realtek,jack-detect-source %ld\n", + BYT_RT5640_JDSRC(byt_rt5640_quirk)); + dev_info(dev, "quirk realtek,over-current-threshold-microamp %ld\n", + BYT_RT5640_OVCD_TH(byt_rt5640_quirk) * 100); + dev_info(dev, "quirk realtek,over-current-scale-factor %ld\n", + BYT_RT5640_OVCD_SF(byt_rt5640_quirk)); + } + if (byt_rt5640_quirk & BYT_RT5640_JD_NOT_INV) + dev_info(dev, "quirk JD_NOT_INV enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) + dev_info(dev, "quirk MONO_SPEAKER enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) + dev_info(dev, "quirk DIFF_MIC enabled\n"); + if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) { + dev_info(dev, "quirk SSP0_AIF1 enabled\n"); + has_ssp0 = true; + has_ssp0_aif1 = true; + } + if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) { + dev_info(dev, "quirk SSP0_AIF2 enabled\n"); + has_ssp0 = true; + has_ssp0_aif2 = true; + } + if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) { + dev_info(dev, "quirk SSP2_AIF2 enabled\n"); + has_ssp2_aif2 = true; + } + if (is_bytcr && !has_ssp0) + dev_err(dev, "Invalid routing, bytcr detected but no SSP0-based quirk, audio cannot work with SSP2 on bytcr\n"); + if (has_ssp0_aif1 && has_ssp0_aif2) + dev_err(dev, "Invalid routing, SSP0 cannot be connected to both AIF1 and AIF2\n"); + if (has_ssp0 && has_ssp2_aif2) + dev_err(dev, "Invalid routing, cannot have both SSP0 and SSP2 connected to codec\n"); + + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { + dev_info(dev, "quirk MCLK_EN enabled\n"); + has_mclk = true; + } + if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) { + if (has_mclk) + dev_info(dev, "quirk MCLK_25MHZ enabled\n"); + else + dev_err(dev, "quirk MCLK_25MHZ enabled but quirk MCLK not selected, will be ignored\n"); + } +} + +static int byt_rt5640_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai, + int rate) +{ + int ret; + + /* Configure the PLL before selecting it */ + if (!(byt_rt5640_quirk & BYT_RT5640_MCLK_EN)) { + /* use bitclock as PLL input */ + if ((byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) || + (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) { + /* 2x16 bit slots on SSP0 */ + ret = snd_soc_dai_set_pll(codec_dai, 0, + RT5640_PLL1_S_BCLK1, + rate * 32, rate * 512); + } else { + /* 2x15 bit slots on SSP2 */ + ret = snd_soc_dai_set_pll(codec_dai, 0, + RT5640_PLL1_S_BCLK1, + rate * 50, rate * 512); + } + } else { + if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) { + ret = snd_soc_dai_set_pll(codec_dai, 0, + RT5640_PLL1_S_MCLK, + 25000000, rate * 512); + } else { + ret = snd_soc_dai_set_pll(codec_dai, 0, + RT5640_PLL1_S_MCLK, + 19200000, rate * 512); + } + } + + if (ret < 0) { + dev_err(codec_dai->component->dev, "can't set pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_PLL1, + rate * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->component->dev, "can't set clock %d\n", ret); + return ret; + } + + return 0; +} + +#define BYT_CODEC_DAI1 "rt5640-aif1" +#define BYT_CODEC_DAI2 "rt5640-aif2" + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI1); + if (!codec_dai) + codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI2); + + if (!codec_dai) { + dev_err(card->dev, + "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, + "could not configure MCLK state\n"); + return ret; + } + } + ret = byt_rt5640_prepare_and_enable_pll1(codec_dai, 48000); + } else { + /* + * Set codec clock source to internal clock before + * turning off the platform clock. Codec needs clock + * for Jack detection and button press + */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_RCCLK, + 48000 * 512, + SND_SOC_CLOCK_IN); + if (!ret) { + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) + clk_disable_unprepare(priv->mclk); + } + } + + if (ret < 0) { + dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget byt_rt5640_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + +}; + +static const struct snd_soc_dapm_route byt_rt5640_audio_map[] = { + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "MICBIAS1"}, + {"IN2P", NULL, "Headset Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic1_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, + {"DMIC1", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_dmic2_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, + {"DMIC2", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_in1_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, + {"Internal Mic", NULL, "MICBIAS1"}, + {"IN1P", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_intmic_in3_map[] = { + {"Internal Mic", NULL, "Platform Clock"}, + {"Internal Mic", NULL, "MICBIAS1"}, + {"IN3P", NULL, "Internal Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_ssp2_aif1_map[] = { + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_ssp2_aif2_map[] = { + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"AIF2 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Rx", NULL, "AIF2 Capture"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_ssp0_aif1_map[] = { + {"ssp0 Tx", NULL, "modem_out"}, + {"modem_in", NULL, "ssp0 Rx"}, + + {"AIF1 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_ssp0_aif2_map[] = { + {"ssp0 Tx", NULL, "modem_out"}, + {"modem_in", NULL, "ssp0 Rx"}, + + {"AIF2 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Rx", NULL, "AIF2 Capture"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_stereo_spk_map[] = { + {"Speaker", NULL, "Platform Clock"}, + {"Speaker", NULL, "SPOLP"}, + {"Speaker", NULL, "SPOLN"}, + {"Speaker", NULL, "SPORP"}, + {"Speaker", NULL, "SPORN"}, +}; + +static const struct snd_soc_dapm_route byt_rt5640_mono_spk_map[] = { + {"Speaker", NULL, "Platform Clock"}, + {"Speaker", NULL, "SPOLP"}, + {"Speaker", NULL, "SPOLN"}, +}; + +static const struct snd_kcontrol_new byt_rt5640_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static struct snd_soc_jack_pin rt5640_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int byt_rt5640_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->codec_dai; + + return byt_rt5640_prepare_and_enable_pll1(dai, params_rate(params)); +} + +/* Please keep this list alphabetically sorted */ +static const struct dmi_system_id byt_rt5640_quirk_table[] = { + { /* Acer Iconia Tab 8 W1-810 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Iconia W1-810"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD1_IN4P | + BYT_RT5640_OVCD_TH_1500UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Acer One 10 S1002 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "One S1002"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF2 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire SW5-012"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ARCHOS"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ARCHOS 80 Cesium"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ARCHOS"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ARCHOS 140 CESIUM"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TA"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "T100TAF"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF2 | + BYT_RT5640_MCLK_EN), + }, + { /* Chuwi Vi8 (CWI506) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "i86"), + /* The above are too generic, also match BIOS info */ + DMI_MATCH(DMI_BIOS_VERSION, "CHUWI.D86JLBNR"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + /* Chuwi Vi10 (CWI505) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-PF02"), + DMI_MATCH(DMI_SYS_VENDOR, "ilife"), + DMI_MATCH(DMI_PRODUCT_NAME, "S165"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + /* Chuwi Hi8 (CWI509) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-PA03C"), + DMI_MATCH(DMI_SYS_VENDOR, "ilife"), + DMI_MATCH(DMI_PRODUCT_NAME, "S806"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Circuitco"), + DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Max B3 PLATFORM"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP), + }, + { /* Connect Tablet 9 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Connect"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Tablet 9"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Venue 8 Pro 5830"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_MCLK_EN), + }, + { /* Estar Beauty HD MID 7316R */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Estar"), + DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Glavey TM800A550L */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Above strings are too generic, also match on BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP ElitePad 1000 G2"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_MCLK_EN), + }, + { /* HP Pavilion x2 10-n000nd */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_1500UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* HP Stream 7 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Stream 7 Tablet"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_JD_NOT_INV | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* I.T.Works TW891 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TW891"), + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "TW891"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Lamina I8270 / T701BR.SE */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Lamina"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "T701BR.SE"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_JD_NOT_INV | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Lenovo Miix 2 8 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "20326"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Hiking"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_MCLK_EN), + }, + { /* Lenovo Miix 3-830 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 3-830"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Linx Linx7 tablet */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LINX"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LINX7"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_JD_NOT_INV | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* MPMAN Converter 9, similar hw as the I.T.Works TW891 2-in-1 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MPMAN"), + DMI_MATCH(DMI_PRODUCT_NAME, "Converter9"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { + /* MPMAN MPWIN895CL */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MPMAN"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MPWIN8900CL"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* MSI S100 tablet */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "S100"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_MCLK_EN), + }, + { /* Nuvison/TMax TM800W560 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TMAX"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TM800W560L"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_JD_NOT_INV | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Onda v975w */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* The above are too generic, also match BIOS info */ + DMI_EXACT_MATCH(DMI_BIOS_VERSION, "5.6.5"), + DMI_EXACT_MATCH(DMI_BIOS_DATE, "07/25/2014"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_MCLK_EN), + }, + { /* Pipo W4 */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* The above are too generic, also match BIOS info */ + DMI_MATCH(DMI_BIOS_VERSION, "V8L_WIN32_CHIPHD"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Point of View Mobii TAB-P800W (V2.0) */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* The above are too generic, also match BIOS info */ + DMI_EXACT_MATCH(DMI_BIOS_VERSION, "3BAIR1014"), + DMI_EXACT_MATCH(DMI_BIOS_DATE, "10/24/2014"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF2 | + BYT_RT5640_MCLK_EN), + }, + { /* Point of View Mobii TAB-P800W (V2.1) */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* The above are too generic, also match BIOS info */ + DMI_EXACT_MATCH(DMI_BIOS_VERSION, "3BAIR1013"), + DMI_EXACT_MATCH(DMI_BIOS_DATE, "08/22/2014"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_MONO_SPEAKER | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_SSP0_AIF2 | + BYT_RT5640_MCLK_EN), + }, + { + /* Teclast X89 */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), + DMI_MATCH(DMI_BOARD_NAME, "tPAD"), + }, + .driver_data = (void *)(BYT_RT5640_IN3_MAP | + BYT_RT5640_JD_SRC_JD1_IN4P | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_1P0 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Toshiba Satellite Click Mini L9W-B */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SATELLITE Click Mini L9W-B"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_1500UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF1 | + BYT_RT5640_MCLK_EN), + }, + { /* Toshiba Encore WT8-A */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TOSHIBA WT8-A"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_JD_NOT_INV | + BYT_RT5640_MCLK_EN), + }, + { /* Toshiba Encore WT10-A */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "TOSHIBA WT10-A-103"), + }, + .driver_data = (void *)(BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD1_IN4P | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_SSP0_AIF2 | + BYT_RT5640_MCLK_EN), + }, + { /* Voyo Winpad A15 */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Above strings are too generic, also match on BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "11/20/2014"), + }, + .driver_data = (void *)(BYT_RT5640_IN1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75 | + BYT_RT5640_DIFF_MIC | + BYT_RT5640_MCLK_EN), + }, + { /* Catch-all for generic Insyde tablets, must be last */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + }, + .driver_data = (void *)(BYTCR_INPUT_DEFAULTS | + BYT_RT5640_MCLK_EN | + BYT_RT5640_SSP0_AIF1), + + }, + {} +}; + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int byt_rt5640_add_codec_device_props(const char *i2c_dev_name) +{ + struct property_entry props[MAX_NO_PROPS] = {}; + struct device *i2c_dev; + int ret, cnt = 0; + + i2c_dev = bus_find_device_by_name(&i2c_bus_type, NULL, i2c_dev_name); + if (!i2c_dev) + return -EPROBE_DEFER; + + switch (BYT_RT5640_MAP(byt_rt5640_quirk)) { + case BYT_RT5640_DMIC1_MAP: + props[cnt++] = PROPERTY_ENTRY_U32("realtek,dmic1-data-pin", + RT5640_DMIC1_DATA_PIN_IN1P); + break; + case BYT_RT5640_DMIC2_MAP: + props[cnt++] = PROPERTY_ENTRY_U32("realtek,dmic2-data-pin", + RT5640_DMIC2_DATA_PIN_IN1N); + break; + case BYT_RT5640_IN1_MAP: + if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) + props[cnt++] = + PROPERTY_ENTRY_BOOL("realtek,in1-differential"); + break; + case BYT_RT5640_IN3_MAP: + if (byt_rt5640_quirk & BYT_RT5640_DIFF_MIC) + props[cnt++] = + PROPERTY_ENTRY_BOOL("realtek,in3-differential"); + break; + } + + if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) { + props[cnt++] = PROPERTY_ENTRY_U32( + "realtek,jack-detect-source", + BYT_RT5640_JDSRC(byt_rt5640_quirk)); + + props[cnt++] = PROPERTY_ENTRY_U32( + "realtek,over-current-threshold-microamp", + BYT_RT5640_OVCD_TH(byt_rt5640_quirk) * 100); + + props[cnt++] = PROPERTY_ENTRY_U32( + "realtek,over-current-scale-factor", + BYT_RT5640_OVCD_SF(byt_rt5640_quirk)); + } + + if (byt_rt5640_quirk & BYT_RT5640_JD_NOT_INV) + props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,jack-detect-not-inverted"); + + ret = device_add_properties(i2c_dev, props); + put_device(i2c_dev); + + return ret; +} + +static int byt_rt5640_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = runtime->codec_dai->component; + const struct snd_soc_dapm_route *custom_map; + int num_routes; + int ret; + + card->dapm.idle_bias_off = true; + + /* Start with RC clk for jack-detect (we disable MCLK below) */ + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) + snd_soc_component_update_bits(component, RT5640_GLB_CLK, + RT5640_SCLK_SRC_MASK, RT5640_SCLK_SRC_RCCLK); + + rt5640_sel_asrc_clk_src(component, + RT5640_DA_STEREO_FILTER | + RT5640_DA_MONO_L_FILTER | + RT5640_DA_MONO_R_FILTER | + RT5640_AD_STEREO_FILTER | + RT5640_AD_MONO_L_FILTER | + RT5640_AD_MONO_R_FILTER, + RT5640_CLK_SEL_ASRC); + + ret = snd_soc_add_card_controls(card, byt_rt5640_controls, + ARRAY_SIZE(byt_rt5640_controls)); + if (ret) { + dev_err(card->dev, "unable to add card controls\n"); + return ret; + } + + switch (BYT_RT5640_MAP(byt_rt5640_quirk)) { + case BYT_RT5640_IN1_MAP: + custom_map = byt_rt5640_intmic_in1_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_in1_map); + break; + case BYT_RT5640_IN3_MAP: + custom_map = byt_rt5640_intmic_in3_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_in3_map); + break; + case BYT_RT5640_DMIC2_MAP: + custom_map = byt_rt5640_intmic_dmic2_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic2_map); + break; + default: + custom_map = byt_rt5640_intmic_dmic1_map; + num_routes = ARRAY_SIZE(byt_rt5640_intmic_dmic1_map); + } + + ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); + if (ret) + return ret; + + if (byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_ssp2_aif2_map, + ARRAY_SIZE(byt_rt5640_ssp2_aif2_map)); + } else if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_ssp0_aif1_map, + ARRAY_SIZE(byt_rt5640_ssp0_aif1_map)); + } else if (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_ssp0_aif2_map, + ARRAY_SIZE(byt_rt5640_ssp0_aif2_map)); + } else { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_ssp2_aif1_map, + ARRAY_SIZE(byt_rt5640_ssp2_aif1_map)); + } + if (ret) + return ret; + + if (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_mono_spk_map, + ARRAY_SIZE(byt_rt5640_mono_spk_map)); + } else { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5640_stereo_spk_map, + ARRAY_SIZE(byt_rt5640_stereo_spk_map)); + } + if (ret) + return ret; + + snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); + + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { + /* + * The firmware might enable the clock at + * boot (this information may or may not + * be reflected in the enable clock register). + * To change the rate we must disable the clock + * first to cover these cases. Due to common + * clock framework restrictions that do not allow + * to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); + + if (byt_rt5640_quirk & BYT_RT5640_MCLK_25MHZ) + ret = clk_set_rate(priv->mclk, 25000000); + else + ret = clk_set_rate(priv->mclk, 19200000); + + if (ret) { + dev_err(card->dev, "unable to set MCLK rate\n"); + return ret; + } + } + + if (BYT_RT5640_JDSRC(byt_rt5640_quirk)) { + ret = snd_soc_card_jack_new(card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &priv->jack, rt5640_pins, + ARRAY_SIZE(rt5640_pins)); + if (ret) { + dev_err(card->dev, "Jack creation failed %d\n", ret); + return ret; + } + snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0, + KEY_PLAYPAUSE); + snd_soc_component_set_jack(component, &priv->jack, NULL); + } + + return 0; +} + +static const struct snd_soc_pcm_stream byt_rt5640_dai_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, +}; + +static int byt_rt5640_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret; + + /* The DSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + if ((byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) || + (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) { + + /* set SSP0 to 16-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 16-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS + ); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 16); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + } else { + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 24-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS + ); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + } + return 0; +} + +static int byt_rt5640_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops byt_rt5640_aif1_ops = { + .startup = byt_rt5640_aif1_startup, +}; + +static const struct snd_soc_ops byt_rt5640_be_ssp2_ops = { + .hw_params = byt_rt5640_aif1_hw_params, +}; + +static struct snd_soc_dai_link byt_rt5640_dais[] = { + [MERR_DPCM_AUDIO] = { + .name = "Baytrail Audio Port", + .stream_name = "Baytrail Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_rt5640_aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &byt_rt5640_aif1_ops, + }, + /* back ends */ + { + .name = "SSP2-Codec", + .id = 0, + .cpu_dai_name = "ssp2-port", /* overwritten for ssp0 routing */ + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "rt5640-aif1", /* changed w/ quirk */ + .codec_name = "i2c-10EC5640:00", /* overwritten with HID */ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = byt_rt5640_codec_fixup, + .ignore_suspend = 1, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + .init = byt_rt5640_init, + .ops = &byt_rt5640_be_ssp2_ops, + }, +}; + +/* SoC card */ +static char byt_rt5640_codec_name[SND_ACPI_I2C_ID_LEN]; +static char byt_rt5640_codec_aif_name[12]; /* = "rt5640-aif[1|2]" */ +static char byt_rt5640_cpu_dai_name[10]; /* = "ssp[0|2]-port" */ +static char byt_rt5640_long_name[40]; /* = "bytcr-rt5640-*-spk-*-mic" */ + +static int byt_rt5640_suspend(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + + if (!BYT_RT5640_JDSRC(byt_rt5640_quirk)) + return 0; + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strcmp(component->name, byt_rt5640_codec_name)) { + dev_dbg(component->dev, "disabling jack detect before suspend\n"); + snd_soc_component_set_jack(component, NULL, NULL); + break; + } + } + + return 0; +} + +static int byt_rt5640_resume(struct snd_soc_card *card) +{ + struct byt_rt5640_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + + if (!BYT_RT5640_JDSRC(byt_rt5640_quirk)) + return 0; + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strcmp(component->name, byt_rt5640_codec_name)) { + dev_dbg(component->dev, "re-enabling jack detect after resume\n"); + snd_soc_component_set_jack(component, &priv->jack, NULL); + break; + } + } + + return 0; +} + +static struct snd_soc_card byt_rt5640_card = { + .name = "bytcr-rt5640", + .owner = THIS_MODULE, + .dai_link = byt_rt5640_dais, + .num_links = ARRAY_SIZE(byt_rt5640_dais), + .dapm_widgets = byt_rt5640_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_rt5640_widgets), + .dapm_routes = byt_rt5640_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_rt5640_audio_map), + .fully_routed = true, + .suspend_pre = byt_rt5640_suspend, + .resume_post = byt_rt5640_resume, +}; + +static bool is_valleyview(void) +{ + static const struct x86_cpu_id cpu_ids[] = { + { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ + {} + }; + + if (!x86_match_cpu(cpu_ids)) + return false; + return true; +} + +struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ + u64 aif_value; /* 1: AIF1, 2: AIF2 */ + u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */ +}; + +static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) +{ + const char * const map_name[] = { "dmic1", "dmic2", "in1", "in3" }; + const struct dmi_system_id *dmi_id; + struct byt_rt5640_private *priv; + struct snd_soc_acpi_mach *mach; + const char *i2c_name = NULL; + int ret_val = 0; + int dai_index = 0; + int i; + + is_bytcr = false; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* register the soc card */ + byt_rt5640_card.dev = &pdev->dev; + mach = byt_rt5640_card.dev->platform_data; + snd_soc_card_set_drvdata(&byt_rt5640_card, priv); + + /* fix index of codec dai */ + for (i = 0; i < ARRAY_SIZE(byt_rt5640_dais); i++) { + if (!strcmp(byt_rt5640_dais[i].codec_name, "i2c-10EC5640:00")) { + dai_index = i; + break; + } + } + + /* fixup codec name based on HID */ + i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); + if (i2c_name) { + snprintf(byt_rt5640_codec_name, sizeof(byt_rt5640_codec_name), + "%s%s", "i2c-", i2c_name); + + byt_rt5640_dais[dai_index].codec_name = byt_rt5640_codec_name; + } + + /* + * swap SSP0 if bytcr is detected + * (will be overridden if DMI quirk is detected) + */ + if (is_valleyview()) { + struct sst_platform_info *p_info = mach->pdata; + const struct sst_res_info *res_info = p_info->res_info; + + if (res_info->acpi_ipc_irq_index == 0) + is_bytcr = true; + } + + if (is_bytcr) { + /* + * Baytrail CR platforms may have CHAN package in BIOS, try + * to find relevant routing quirk based as done on Windows + * platforms. We have to read the information directly from the + * BIOS, at this stage the card is not created and the links + * with the codec driver/pdata are non-existent + */ + + struct acpi_chan_package chan_package; + + /* format specified: 2 64-bit integers */ + struct acpi_buffer format = {sizeof("NN"), "NN"}; + struct acpi_buffer state = {0, NULL}; + struct snd_soc_acpi_package_context pkg_ctx; + bool pkg_found = false; + + state.length = sizeof(chan_package); + state.pointer = &chan_package; + + pkg_ctx.name = "CHAN"; + pkg_ctx.length = 2; + pkg_ctx.format = &format; + pkg_ctx.state = &state; + pkg_ctx.data_valid = false; + + pkg_found = snd_soc_acpi_find_package_from_hid(mach->id, + &pkg_ctx); + if (pkg_found) { + if (chan_package.aif_value == 1) { + dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); + byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF1; + } else if (chan_package.aif_value == 2) { + dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); + byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2; + } else { + dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); + pkg_found = false; + } + } + + if (!pkg_found) { + /* no BIOS indications, assume SSP0-AIF2 connection */ + byt_rt5640_quirk |= BYT_RT5640_SSP0_AIF2; + } + + /* change defaults for Baytrail-CR capture */ + byt_rt5640_quirk |= BYTCR_INPUT_DEFAULTS; + } else { + byt_rt5640_quirk |= BYT_RT5640_DMIC1_MAP | + BYT_RT5640_JD_SRC_JD2_IN4N | + BYT_RT5640_OVCD_TH_2000UA | + BYT_RT5640_OVCD_SF_0P75; + } + + /* check quirks before creating card */ + dmi_id = dmi_first_match(byt_rt5640_quirk_table); + if (dmi_id) + byt_rt5640_quirk = (unsigned long)dmi_id->driver_data; + if (quirk_override) { + dev_info(&pdev->dev, "Overriding quirk 0x%x => 0x%x\n", + (unsigned int)byt_rt5640_quirk, quirk_override); + byt_rt5640_quirk = quirk_override; + } + + /* Must be called before register_card, also see declaration comment. */ + ret_val = byt_rt5640_add_codec_device_props(byt_rt5640_codec_name); + if (ret_val) + return ret_val; + + log_quirks(&pdev->dev); + + if ((byt_rt5640_quirk & BYT_RT5640_SSP2_AIF2) || + (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) { + + /* fixup codec aif name */ + snprintf(byt_rt5640_codec_aif_name, + sizeof(byt_rt5640_codec_aif_name), + "%s", "rt5640-aif2"); + + byt_rt5640_dais[dai_index].codec_dai_name = + byt_rt5640_codec_aif_name; + } + + if ((byt_rt5640_quirk & BYT_RT5640_SSP0_AIF1) || + (byt_rt5640_quirk & BYT_RT5640_SSP0_AIF2)) { + + /* fixup cpu dai name name */ + snprintf(byt_rt5640_cpu_dai_name, + sizeof(byt_rt5640_cpu_dai_name), + "%s", "ssp0-port"); + + byt_rt5640_dais[dai_index].cpu_dai_name = + byt_rt5640_cpu_dai_name; + } + + if (byt_rt5640_quirk & BYT_RT5640_MCLK_EN) { + priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + if (IS_ERR(priv->mclk)) { + ret_val = PTR_ERR(priv->mclk); + + dev_err(&pdev->dev, + "Failed to get MCLK from pmc_plt_clk_3: %d\n", + ret_val); + + /* + * Fall back to bit clock usage for -ENOENT (clock not + * available likely due to missing dependencies), bail + * for all other errors, including -EPROBE_DEFER + */ + if (ret_val != -ENOENT) + return ret_val; + byt_rt5640_quirk &= ~BYT_RT5640_MCLK_EN; + } + } + + snprintf(byt_rt5640_long_name, sizeof(byt_rt5640_long_name), + "bytcr-rt5640-%s-spk-%s-mic", + (byt_rt5640_quirk & BYT_RT5640_MONO_SPEAKER) ? + "mono" : "stereo", + map_name[BYT_RT5640_MAP(byt_rt5640_quirk)]); + byt_rt5640_card.long_name = byt_rt5640_long_name; + + ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5640_card); + + if (ret_val) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", + ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &byt_rt5640_card); + return ret_val; +} + +static struct platform_driver snd_byt_rt5640_mc_driver = { + .driver = { + .name = "bytcr_rt5640", + }, + .probe = snd_byt_rt5640_mc_probe, +}; + +module_platform_driver(snd_byt_rt5640_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); +MODULE_AUTHOR("Subhransu S. Prusty "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcr_rt5640"); diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c new file mode 100644 index 000000000..c6c8d20be --- /dev/null +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -0,0 +1,1090 @@ +/* + * bytcr_rt5651.c - ASoc Machine driver for Intel Byt CR platform + * (derived from bytcr_rt5640.c) + * + * Copyright (C) 2015 Intel Corp + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5651.h" +#include "../atom/sst-atom-controls.h" + +enum { + BYT_RT5651_DMIC_MAP, + BYT_RT5651_IN1_MAP, + BYT_RT5651_IN2_MAP, + BYT_RT5651_IN1_IN2_MAP, +}; + +enum { + BYT_RT5651_JD_NULL = (RT5651_JD_NULL << 4), + BYT_RT5651_JD1_1 = (RT5651_JD1_1 << 4), + BYT_RT5651_JD1_2 = (RT5651_JD1_2 << 4), + BYT_RT5651_JD2 = (RT5651_JD2 << 4), +}; + +enum { + BYT_RT5651_OVCD_TH_600UA = (6 << 8), + BYT_RT5651_OVCD_TH_1500UA = (15 << 8), + BYT_RT5651_OVCD_TH_2000UA = (20 << 8), +}; + +enum { + BYT_RT5651_OVCD_SF_0P5 = (RT5651_OVCD_SF_0P5 << 13), + BYT_RT5651_OVCD_SF_0P75 = (RT5651_OVCD_SF_0P75 << 13), + BYT_RT5651_OVCD_SF_1P0 = (RT5651_OVCD_SF_1P0 << 13), + BYT_RT5651_OVCD_SF_1P5 = (RT5651_OVCD_SF_1P5 << 13), +}; + +#define BYT_RT5651_MAP(quirk) ((quirk) & GENMASK(3, 0)) +#define BYT_RT5651_JDSRC(quirk) (((quirk) & GENMASK(7, 4)) >> 4) +#define BYT_RT5651_OVCD_TH(quirk) (((quirk) & GENMASK(12, 8)) >> 8) +#define BYT_RT5651_OVCD_SF(quirk) (((quirk) & GENMASK(14, 13)) >> 13) +#define BYT_RT5651_DMIC_EN BIT(16) +#define BYT_RT5651_MCLK_EN BIT(17) +#define BYT_RT5651_MCLK_25MHZ BIT(18) +#define BYT_RT5651_SSP2_AIF2 BIT(19) /* default is using AIF1 */ +#define BYT_RT5651_SSP0_AIF1 BIT(20) +#define BYT_RT5651_SSP0_AIF2 BIT(21) +#define BYT_RT5651_HP_LR_SWAPPED BIT(22) +#define BYT_RT5651_MONO_SPEAKER BIT(23) + +#define BYT_RT5651_DEFAULT_QUIRKS (BYT_RT5651_MCLK_EN | \ + BYT_RT5651_JD1_1 | \ + BYT_RT5651_OVCD_TH_2000UA | \ + BYT_RT5651_OVCD_SF_0P75) + +/* jack-detect-source + dmic-en + ovcd-th + -sf + terminating empty entry */ +#define MAX_NO_PROPS 5 + +struct byt_rt5651_private { + struct clk *mclk; + struct gpio_desc *ext_amp_gpio; + struct snd_soc_jack jack; +}; + +/* Default: jack-detect on JD1_1, internal mic on in2, headsetmic on in3 */ +static unsigned long byt_rt5651_quirk = BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP; + +static void log_quirks(struct device *dev) +{ + if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_DMIC_MAP) + dev_info(dev, "quirk DMIC_MAP enabled"); + if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_IN1_MAP) + dev_info(dev, "quirk IN1_MAP enabled"); + if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_IN2_MAP) + dev_info(dev, "quirk IN2_MAP enabled"); + if (BYT_RT5651_MAP(byt_rt5651_quirk) == BYT_RT5651_IN1_IN2_MAP) + dev_info(dev, "quirk IN1_IN2_MAP enabled"); + if (BYT_RT5651_JDSRC(byt_rt5651_quirk)) { + dev_info(dev, "quirk realtek,jack-detect-source %ld\n", + BYT_RT5651_JDSRC(byt_rt5651_quirk)); + dev_info(dev, "quirk realtek,over-current-threshold-microamp %ld\n", + BYT_RT5651_OVCD_TH(byt_rt5651_quirk) * 100); + dev_info(dev, "quirk realtek,over-current-scale-factor %ld\n", + BYT_RT5651_OVCD_SF(byt_rt5651_quirk)); + } + if (byt_rt5651_quirk & BYT_RT5651_DMIC_EN) + dev_info(dev, "quirk DMIC enabled"); + if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) + dev_info(dev, "quirk MCLK_EN enabled"); + if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) + dev_info(dev, "quirk MCLK_25MHZ enabled"); + if (byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2) + dev_info(dev, "quirk SSP2_AIF2 enabled\n"); + if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1) + dev_info(dev, "quirk SSP0_AIF1 enabled\n"); + if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2) + dev_info(dev, "quirk SSP0_AIF2 enabled\n"); + if (byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) + dev_info(dev, "quirk MONO_SPEAKER enabled\n"); +} + +#define BYT_CODEC_DAI1 "rt5651-aif1" +#define BYT_CODEC_DAI2 "rt5651-aif2" + +static int byt_rt5651_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai, + int rate, int bclk_ratio) +{ + int clk_id, clk_freq, ret; + + /* Configure the PLL before selecting it */ + if (!(byt_rt5651_quirk & BYT_RT5651_MCLK_EN)) { + clk_id = RT5651_PLL1_S_BCLK1, + clk_freq = rate * bclk_ratio; + } else { + clk_id = RT5651_PLL1_S_MCLK; + if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) + clk_freq = 25000000; + else + clk_freq = 19200000; + } + ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, rate * 512); + if (ret < 0) { + dev_err(codec_dai->component->dev, "can't set pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5651_SCLK_S_PLL1, + rate * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->component->dev, "can't set clock %d\n", ret); + return ret; + } + + return 0; +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI1); + if (!codec_dai) + codec_dai = snd_soc_card_get_codec_dai(card, BYT_CODEC_DAI2); + if (!codec_dai) { + dev_err(card->dev, + "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, + "could not configure MCLK state"); + return ret; + } + } + ret = byt_rt5651_prepare_and_enable_pll1(codec_dai, 48000, 50); + } else { + /* + * Set codec clock source to internal clock before + * turning off the platform clock. Codec needs clock + * for Jack detection and button press + */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5651_SCLK_S_RCCLK, + 48000 * 512, + SND_SOC_CLOCK_IN); + if (!ret) + if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) + clk_disable_unprepare(priv->mclk); + } + + if (ret < 0) { + dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static int rt5651_ext_amp_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); + + if (SND_SOC_DAPM_EVENT_ON(event)) + gpiod_set_value_cansleep(priv->ext_amp_gpio, 1); + else + gpiod_set_value_cansleep(priv->ext_amp_gpio, 0); + + return 0; +} + +static const struct snd_soc_dapm_widget byt_rt5651_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("Ext Amp Power", SND_SOC_NOPM, 0, 0, + rt5651_ext_amp_power_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), +}; + +static const struct snd_soc_dapm_route byt_rt5651_audio_map[] = { + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Internal Mic", NULL, "Platform Clock"}, + {"Speaker", NULL, "Platform Clock"}, + {"Speaker", NULL, "Ext Amp Power"}, + {"Line In", NULL, "Platform Clock"}, + + {"Headset Mic", NULL, "micbias1"}, /* lowercase for rt5651 */ + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Speaker", NULL, "LOUTL"}, + {"Speaker", NULL, "LOUTR"}, + {"IN2P", NULL, "Line In"}, + {"IN2N", NULL, "Line In"}, + +}; + +static const struct snd_soc_dapm_route byt_rt5651_intmic_dmic_map[] = { + {"DMIC L1", NULL, "Internal Mic"}, + {"DMIC R1", NULL, "Internal Mic"}, + {"IN2P", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5651_intmic_in1_map[] = { + {"Internal Mic", NULL, "micbias1"}, + {"IN1P", NULL, "Internal Mic"}, + {"IN3P", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5651_intmic_in2_map[] = { + {"Internal Mic", NULL, "micbias1"}, + {"IN2P", NULL, "Internal Mic"}, + {"IN3P", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5651_intmic_in1_in2_map[] = { + {"Internal Mic", NULL, "micbias1"}, + {"IN1P", NULL, "Internal Mic"}, + {"IN2P", NULL, "Internal Mic"}, + {"IN3P", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route byt_rt5651_ssp0_aif1_map[] = { + {"ssp0 Tx", NULL, "modem_out"}, + {"modem_in", NULL, "ssp0 Rx"}, + + {"AIF1 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_soc_dapm_route byt_rt5651_ssp0_aif2_map[] = { + {"ssp0 Tx", NULL, "modem_out"}, + {"modem_in", NULL, "ssp0 Rx"}, + + {"AIF2 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Rx", NULL, "AIF2 Capture"}, +}; + +static const struct snd_soc_dapm_route byt_rt5651_ssp2_aif1_map[] = { + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_soc_dapm_route byt_rt5651_ssp2_aif2_map[] = { + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + + {"AIF2 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Rx", NULL, "AIF2 Capture"}, +}; + +static const struct snd_kcontrol_new byt_rt5651_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Line In"), +}; + +static struct snd_soc_jack_pin bytcr_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int byt_rt5651_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + snd_pcm_format_t format = params_format(params); + int rate = params_rate(params); + int bclk_ratio; + + if (format == SNDRV_PCM_FORMAT_S16_LE) + bclk_ratio = 32; + else + bclk_ratio = 50; + + return byt_rt5651_prepare_and_enable_pll1(codec_dai, rate, bclk_ratio); +} + +static int byt_rt5651_quirk_cb(const struct dmi_system_id *id) +{ + byt_rt5651_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id byt_rt5651_quirk_table[] = { + { + /* Chuwi Hi8 Pro (CWI513) */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hampoo"), + DMI_MATCH(DMI_PRODUCT_NAME, "X1D3_C806N"), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP | + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, + { + /* Chuwi Vi8 Plus (CWI519) */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Hampoo"), + DMI_MATCH(DMI_PRODUCT_NAME, "D2D3_Vi8A1"), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP | + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, + { + /* I.T.Works TW701, Ployer Momo7w and Trekstor ST70416-6 + * (these all use the same mainboard) */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "INSYDE Corp."), + /* Partial match for all of itWORKS.G.WI71C.JGBMRBA, + * TREK.G.WI71C.JGBMRBA0x and MOMO.G.WI71C.MABMRBA02 */ + DMI_MATCH(DMI_BIOS_VERSION, ".G.WI71C."), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP | + BYT_RT5651_SSP0_AIF1 | + BYT_RT5651_MONO_SPEAKER), + }, + { + /* KIANO SlimNote 14.2 */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "KIANO"), + DMI_MATCH(DMI_PRODUCT_NAME, "KIANO SlimNote 14.2"), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN1_IN2_MAP), + }, + { + /* Minnowboard Max B3 */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Circuitco"), + DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Max B3 PLATFORM"), + }, + .driver_data = (void *)(BYT_RT5651_IN1_MAP), + }, + { + /* Minnowboard Turbot */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ADI"), + DMI_MATCH(DMI_PRODUCT_NAME, "Minnowboard Turbot"), + }, + .driver_data = (void *)(BYT_RT5651_MCLK_EN | + BYT_RT5651_IN1_MAP), + }, + { + /* VIOS LTH17 */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "VIOS"), + DMI_MATCH(DMI_PRODUCT_NAME, "LTH17"), + }, + .driver_data = (void *)(BYT_RT5651_IN1_IN2_MAP | + BYT_RT5651_JD1_1 | + BYT_RT5651_OVCD_TH_2000UA | + BYT_RT5651_OVCD_SF_1P0 | + BYT_RT5651_MCLK_EN), + }, + { + /* Yours Y8W81 (and others using the same mainboard) */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "INSYDE Corp."), + /* Partial match for all devs with a W86C mainboard */ + DMI_MATCH(DMI_BIOS_VERSION, ".F.W86C."), + }, + .driver_data = (void *)(BYT_RT5651_DEFAULT_QUIRKS | + BYT_RT5651_IN2_MAP | + BYT_RT5651_SSP0_AIF1 | + BYT_RT5651_MONO_SPEAKER), + }, + {} +}; + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int byt_rt5651_add_codec_device_props(struct device *i2c_dev) +{ + struct property_entry props[MAX_NO_PROPS] = {}; + int cnt = 0; + + props[cnt++] = PROPERTY_ENTRY_U32("realtek,jack-detect-source", + BYT_RT5651_JDSRC(byt_rt5651_quirk)); + + props[cnt++] = PROPERTY_ENTRY_U32("realtek,over-current-threshold-microamp", + BYT_RT5651_OVCD_TH(byt_rt5651_quirk) * 100); + + props[cnt++] = PROPERTY_ENTRY_U32("realtek,over-current-scale-factor", + BYT_RT5651_OVCD_SF(byt_rt5651_quirk)); + + if (byt_rt5651_quirk & BYT_RT5651_DMIC_EN) + props[cnt++] = PROPERTY_ENTRY_BOOL("realtek,dmic-en"); + + return device_add_properties(i2c_dev, props); +} + +static int byt_rt5651_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_component *codec = runtime->codec_dai->component; + struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); + const struct snd_soc_dapm_route *custom_map; + int num_routes; + int ret; + + card->dapm.idle_bias_off = true; + + /* Start with RC clk for jack-detect (we disable MCLK below) */ + if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) + snd_soc_component_update_bits(codec, RT5651_GLB_CLK, + RT5651_SCLK_SRC_MASK, RT5651_SCLK_SRC_RCCLK); + + switch (BYT_RT5651_MAP(byt_rt5651_quirk)) { + case BYT_RT5651_IN1_MAP: + custom_map = byt_rt5651_intmic_in1_map; + num_routes = ARRAY_SIZE(byt_rt5651_intmic_in1_map); + break; + case BYT_RT5651_IN2_MAP: + custom_map = byt_rt5651_intmic_in2_map; + num_routes = ARRAY_SIZE(byt_rt5651_intmic_in2_map); + break; + case BYT_RT5651_IN1_IN2_MAP: + custom_map = byt_rt5651_intmic_in1_in2_map; + num_routes = ARRAY_SIZE(byt_rt5651_intmic_in1_in2_map); + break; + default: + custom_map = byt_rt5651_intmic_dmic_map; + num_routes = ARRAY_SIZE(byt_rt5651_intmic_dmic_map); + } + ret = snd_soc_dapm_add_routes(&card->dapm, custom_map, num_routes); + if (ret) + return ret; + + if (byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5651_ssp2_aif2_map, + ARRAY_SIZE(byt_rt5651_ssp2_aif2_map)); + } else if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5651_ssp0_aif1_map, + ARRAY_SIZE(byt_rt5651_ssp0_aif1_map)); + } else if (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2) { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5651_ssp0_aif2_map, + ARRAY_SIZE(byt_rt5651_ssp0_aif2_map)); + } else { + ret = snd_soc_dapm_add_routes(&card->dapm, + byt_rt5651_ssp2_aif1_map, + ARRAY_SIZE(byt_rt5651_ssp2_aif1_map)); + } + if (ret) + return ret; + + ret = snd_soc_add_card_controls(card, byt_rt5651_controls, + ARRAY_SIZE(byt_rt5651_controls)); + if (ret) { + dev_err(card->dev, "unable to add card controls\n"); + return ret; + } + snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Speaker"); + + if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { + /* + * The firmware might enable the clock at + * boot (this information may or may not + * be reflected in the enable clock register). + * To change the rate we must disable the clock + * first to cover these cases. Due to common + * clock framework restrictions that do not allow + * to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(priv->mclk); + if (!ret) + clk_disable_unprepare(priv->mclk); + + if (byt_rt5651_quirk & BYT_RT5651_MCLK_25MHZ) + ret = clk_set_rate(priv->mclk, 25000000); + else + ret = clk_set_rate(priv->mclk, 19200000); + + if (ret) + dev_err(card->dev, "unable to set MCLK rate\n"); + } + + if (BYT_RT5651_JDSRC(byt_rt5651_quirk)) { + ret = snd_soc_card_jack_new(runtime->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &priv->jack, bytcr_jack_pins, + ARRAY_SIZE(bytcr_jack_pins)); + if (ret) { + dev_err(runtime->dev, "jack creation failed %d\n", ret); + return ret; + } + + snd_jack_set_key(priv->jack.jack, SND_JACK_BTN_0, + KEY_PLAYPAUSE); + + ret = snd_soc_component_set_jack(codec, &priv->jack, NULL); + if (ret) + return ret; + } + + return 0; +} + +static const struct snd_soc_pcm_stream byt_rt5651_dai_params = { + .formats = SNDRV_PCM_FMTBIT_S24_LE, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, +}; + +static int byt_rt5651_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret, bits; + + /* The DSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + if ((byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1) || + (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2)) { + /* set SSP0 to 16-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + bits = 16; + } else { + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + bits = 24; + } + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS + ); + + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, bits); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + return 0; +} + +static const unsigned int rates_48000[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_48000 = { + .count = ARRAY_SIZE(rates_48000), + .list = rates_48000, +}; + +static int byt_rt5651_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_48000); +} + +static const struct snd_soc_ops byt_rt5651_aif1_ops = { + .startup = byt_rt5651_aif1_startup, +}; + +static const struct snd_soc_ops byt_rt5651_be_ssp2_ops = { + .hw_params = byt_rt5651_aif1_hw_params, +}; + +static struct snd_soc_dai_link byt_rt5651_dais[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &byt_rt5651_aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &byt_rt5651_aif1_ops, + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-Codec", + .id = 0, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "rt5651-aif1", + .codec_name = "i2c-10EC5651:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .be_hw_params_fixup = byt_rt5651_codec_fixup, + .ignore_suspend = 1, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + .init = byt_rt5651_init, + .ops = &byt_rt5651_be_ssp2_ops, + }, +}; + +/* SoC card */ +static char byt_rt5651_codec_name[SND_ACPI_I2C_ID_LEN]; +static char byt_rt5651_codec_aif_name[12]; /* = "rt5651-aif[1|2]" */ +static char byt_rt5651_cpu_dai_name[10]; /* = "ssp[0|2]-port" */ +static char byt_rt5651_long_name[50]; /* = "bytcr-rt5651-*-spk-*-mic[-swapped-hp]" */ + +static int byt_rt5651_suspend(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + + if (!BYT_RT5651_JDSRC(byt_rt5651_quirk)) + return 0; + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strcmp(component->name, byt_rt5651_codec_name)) { + dev_dbg(component->dev, "disabling jack detect before suspend\n"); + snd_soc_component_set_jack(component, NULL, NULL); + break; + } + } + + return 0; +} + +static int byt_rt5651_resume(struct snd_soc_card *card) +{ + struct byt_rt5651_private *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + + if (!BYT_RT5651_JDSRC(byt_rt5651_quirk)) + return 0; + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strcmp(component->name, byt_rt5651_codec_name)) { + dev_dbg(component->dev, "re-enabling jack detect after resume\n"); + snd_soc_component_set_jack(component, &priv->jack, NULL); + break; + } + } + + return 0; +} + +static struct snd_soc_card byt_rt5651_card = { + .name = "bytcr-rt5651", + .owner = THIS_MODULE, + .dai_link = byt_rt5651_dais, + .num_links = ARRAY_SIZE(byt_rt5651_dais), + .dapm_widgets = byt_rt5651_widgets, + .num_dapm_widgets = ARRAY_SIZE(byt_rt5651_widgets), + .dapm_routes = byt_rt5651_audio_map, + .num_dapm_routes = ARRAY_SIZE(byt_rt5651_audio_map), + .fully_routed = true, + .suspend_pre = byt_rt5651_suspend, + .resume_post = byt_rt5651_resume, +}; + +static const struct x86_cpu_id baytrail_cpu_ids[] = { + { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT }, /* Valleyview */ + {} +}; + +static const struct x86_cpu_id cherrytrail_cpu_ids[] = { + { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT }, /* Braswell */ + {} +}; + +static const struct acpi_gpio_params first_gpio = { 0, 0, false }; +static const struct acpi_gpio_params second_gpio = { 1, 0, false }; + +static const struct acpi_gpio_mapping byt_rt5651_amp_en_first[] = { + { "ext-amp-enable-gpios", &first_gpio, 1 }, + { }, +}; + +static const struct acpi_gpio_mapping byt_rt5651_amp_en_second[] = { + { "ext-amp-enable-gpios", &second_gpio, 1 }, + { }, +}; + +/* + * Some boards have I2cSerialBusV2, GpioIo, GpioInt as ACPI resources, other + * boards may have I2cSerialBusV2, GpioInt, GpioIo instead. We want the + * GpioIo one for the ext-amp-enable-gpio and both count for the index in + * acpi_gpio_params index. So we have 2 different mappings and the code + * below figures out which one to use. + */ +struct byt_rt5651_acpi_resource_data { + int gpio_count; + int gpio_int_idx; +}; + +static int snd_byt_rt5651_acpi_resource(struct acpi_resource *ares, void *arg) +{ + struct byt_rt5651_acpi_resource_data *data = arg; + + if (ares->type != ACPI_RESOURCE_TYPE_GPIO) + return 0; + + if (ares->data.gpio.connection_type == ACPI_RESOURCE_GPIO_TYPE_INT) + data->gpio_int_idx = data->gpio_count; + + data->gpio_count++; + return 0; +} + +static void snd_byt_rt5651_mc_add_amp_en_gpio_mapping(struct device *codec) +{ + struct byt_rt5651_acpi_resource_data data = { 0, -1 }; + LIST_HEAD(resources); + int ret; + + ret = acpi_dev_get_resources(ACPI_COMPANION(codec), &resources, + snd_byt_rt5651_acpi_resource, &data); + if (ret < 0) { + dev_warn(codec, "Failed to get ACPI resources, not adding external amplifier GPIO mapping\n"); + return; + } + + /* All info we need is gathered during the walk */ + acpi_dev_free_resource_list(&resources); + + switch (data.gpio_int_idx) { + case 0: + devm_acpi_dev_add_driver_gpios(codec, byt_rt5651_amp_en_second); + break; + case 1: + devm_acpi_dev_add_driver_gpios(codec, byt_rt5651_amp_en_first); + break; + default: + dev_warn(codec, "Unknown GpioInt index %d, not adding external amplifier GPIO mapping\n", + data.gpio_int_idx); + } +} + +struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ + u64 aif_value; /* 1: AIF1, 2: AIF2 */ + u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */ +}; + +static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) +{ + const char * const mic_name[] = { "dmic", "in1", "in2", "in12" }; + struct byt_rt5651_private *priv; + struct snd_soc_acpi_mach *mach; + struct device *codec_dev; + const char *i2c_name = NULL; + const char *hp_swapped; + bool is_bytcr = false; + int ret_val = 0; + int dai_index = 0; + int i; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* register the soc card */ + byt_rt5651_card.dev = &pdev->dev; + + mach = byt_rt5651_card.dev->platform_data; + snd_soc_card_set_drvdata(&byt_rt5651_card, priv); + + /* fix index of codec dai */ + for (i = 0; i < ARRAY_SIZE(byt_rt5651_dais); i++) { + if (!strcmp(byt_rt5651_dais[i].codec_name, "i2c-10EC5651:00")) { + dai_index = i; + break; + } + } + + /* fixup codec name based on HID */ + i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); + if (!i2c_name) { + dev_err(&pdev->dev, "Error cannot find '%s' dev\n", mach->id); + return -ENODEV; + } + snprintf(byt_rt5651_codec_name, sizeof(byt_rt5651_codec_name), + "%s%s", "i2c-", i2c_name); + byt_rt5651_dais[dai_index].codec_name = byt_rt5651_codec_name; + + codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, + byt_rt5651_codec_name); + if (!codec_dev) + return -EPROBE_DEFER; + + /* + * swap SSP0 if bytcr is detected + * (will be overridden if DMI quirk is detected) + */ + if (x86_match_cpu(baytrail_cpu_ids)) { + struct sst_platform_info *p_info = mach->pdata; + const struct sst_res_info *res_info = p_info->res_info; + + if (res_info->acpi_ipc_irq_index == 0) + is_bytcr = true; + } + + if (is_bytcr) { + /* + * Baytrail CR platforms may have CHAN package in BIOS, try + * to find relevant routing quirk based as done on Windows + * platforms. We have to read the information directly from the + * BIOS, at this stage the card is not created and the links + * with the codec driver/pdata are non-existent + */ + + struct acpi_chan_package chan_package; + + /* format specified: 2 64-bit integers */ + struct acpi_buffer format = {sizeof("NN"), "NN"}; + struct acpi_buffer state = {0, NULL}; + struct snd_soc_acpi_package_context pkg_ctx; + bool pkg_found = false; + + state.length = sizeof(chan_package); + state.pointer = &chan_package; + + pkg_ctx.name = "CHAN"; + pkg_ctx.length = 2; + pkg_ctx.format = &format; + pkg_ctx.state = &state; + pkg_ctx.data_valid = false; + + pkg_found = snd_soc_acpi_find_package_from_hid(mach->id, + &pkg_ctx); + if (pkg_found) { + if (chan_package.aif_value == 1) { + dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); + byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF1; + } else if (chan_package.aif_value == 2) { + dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); + byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF2; + } else { + dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); + pkg_found = false; + } + } + + if (!pkg_found) { + /* no BIOS indications, assume SSP0-AIF2 connection */ + byt_rt5651_quirk |= BYT_RT5651_SSP0_AIF2; + } + } + + /* check quirks before creating card */ + dmi_check_system(byt_rt5651_quirk_table); + + /* Must be called before register_card, also see declaration comment. */ + ret_val = byt_rt5651_add_codec_device_props(codec_dev); + if (ret_val) { + put_device(codec_dev); + return ret_val; + } + + /* Cherry Trail devices use an external amplifier enable gpio */ + if (x86_match_cpu(cherrytrail_cpu_ids)) { + snd_byt_rt5651_mc_add_amp_en_gpio_mapping(codec_dev); + priv->ext_amp_gpio = devm_fwnode_get_index_gpiod_from_child( + &pdev->dev, "ext-amp-enable", 0, + codec_dev->fwnode, + GPIOD_OUT_LOW, "speaker-amp"); + if (IS_ERR(priv->ext_amp_gpio)) { + ret_val = PTR_ERR(priv->ext_amp_gpio); + switch (ret_val) { + case -ENOENT: + priv->ext_amp_gpio = NULL; + break; + default: + dev_err(&pdev->dev, "Failed to get ext-amp-enable GPIO: %d\n", + ret_val); + /* fall through */ + case -EPROBE_DEFER: + put_device(codec_dev); + return ret_val; + } + } + } + + put_device(codec_dev); + + log_quirks(&pdev->dev); + + if ((byt_rt5651_quirk & BYT_RT5651_SSP2_AIF2) || + (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2)) { + /* fixup codec aif name */ + snprintf(byt_rt5651_codec_aif_name, + sizeof(byt_rt5651_codec_aif_name), + "%s", "rt5651-aif2"); + + byt_rt5651_dais[dai_index].codec_dai_name = + byt_rt5651_codec_aif_name; + } + + if ((byt_rt5651_quirk & BYT_RT5651_SSP0_AIF1) || + (byt_rt5651_quirk & BYT_RT5651_SSP0_AIF2)) { + /* fixup cpu dai name name */ + snprintf(byt_rt5651_cpu_dai_name, + sizeof(byt_rt5651_cpu_dai_name), + "%s", "ssp0-port"); + + byt_rt5651_dais[dai_index].cpu_dai_name = + byt_rt5651_cpu_dai_name; + } + + if (byt_rt5651_quirk & BYT_RT5651_MCLK_EN) { + priv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + if (IS_ERR(priv->mclk)) { + ret_val = PTR_ERR(priv->mclk); + dev_err(&pdev->dev, + "Failed to get MCLK from pmc_plt_clk_3: %d\n", + ret_val); + /* + * Fall back to bit clock usage for -ENOENT (clock not + * available likely due to missing dependencies), bail + * for all other errors, including -EPROBE_DEFER + */ + if (ret_val != -ENOENT) + return ret_val; + byt_rt5651_quirk &= ~BYT_RT5651_MCLK_EN; + } + } + + if (byt_rt5651_quirk & BYT_RT5651_HP_LR_SWAPPED) + hp_swapped = "-hp-swapped"; + else + hp_swapped = ""; + + snprintf(byt_rt5651_long_name, sizeof(byt_rt5651_long_name), + "bytcr-rt5651-%s-spk-%s-mic%s", + (byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) ? + "mono" : "stereo", + mic_name[BYT_RT5651_MAP(byt_rt5651_quirk)], hp_swapped); + byt_rt5651_card.long_name = byt_rt5651_long_name; + + ret_val = devm_snd_soc_register_card(&pdev->dev, &byt_rt5651_card); + + if (ret_val) { + dev_err(&pdev->dev, "devm_snd_soc_register_card failed %d\n", + ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &byt_rt5651_card); + return ret_val; +} + +static struct platform_driver snd_byt_rt5651_mc_driver = { + .driver = { + .name = "bytcr_rt5651", + }, + .probe = snd_byt_rt5651_mc_probe, +}; + +module_platform_driver(snd_byt_rt5651_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver for RT5651"); +MODULE_AUTHOR("Pierre-Louis Bossart "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bytcr_rt5651"); diff --git a/sound/soc/intel/boards/cht_bsw_max98090_ti.c b/sound/soc/intel/boards/cht_bsw_max98090_ti.c new file mode 100644 index 000000000..e7620017e --- /dev/null +++ b/sound/soc/intel/boards/cht_bsw_max98090_ti.c @@ -0,0 +1,519 @@ +/* + * cht-bsw-max98090.c - ASoc Machine driver for Intel Cherryview-based + * platforms Cherrytrail and Braswell, with max98090 & TI codec. + * + * Copyright (C) 2015 Intel Corp + * Author: Fang, Yang A + * This file is modified from cht_bsw_rt5645.c + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/max98090.h" +#include "../atom/sst-atom-controls.h" +#include "../../codecs/ts3a227e.h" + +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "HiFi" + +#define QUIRK_PMC_PLT_CLK_0 0x01 + +struct cht_mc_private { + struct clk *mclk; + struct snd_soc_jack jack; + bool ts3a227e_present; + int quirks; +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + /* See the comment in snd_cht_mc_probe() */ + if (ctx->quirks & QUIRK_PMC_PLT_CLK_0) + return 0; + + codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = clk_prepare_enable(ctx->mclk); + if (ret < 0) { + dev_err(card->dev, + "could not configure MCLK state"); + return ret; + } + } else { + clk_disable_unprepare(ctx->mclk); + } + + return 0; +} + +static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_audio_map[] = { + {"IN34", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "MICBIAS"}, + {"DMICL", NULL, "Int Mic"}, + {"Headphone", NULL, "HPL"}, + {"Headphone", NULL, "HPR"}, + {"Ext Spk", NULL, "SPKL"}, + {"Ext Spk", NULL, "SPKR"}, + {"HiFi Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "HiFi Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, M98090_REG_SYSTEM_CLOCK, + CHT_PLAT_CLK_3_HZ, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_ti_jack_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct snd_soc_jack *jack = (struct snd_soc_jack *)data; + struct snd_soc_dapm_context *dapm = &jack->card->dapm; + + if (event & SND_JACK_MICROPHONE) { + snd_soc_dapm_force_enable_pin(dapm, "SHDN"); + snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + snd_soc_dapm_sync(dapm); + } else { + snd_soc_dapm_disable_pin(dapm, "MICBIAS"); + snd_soc_dapm_disable_pin(dapm, "SHDN"); + snd_soc_dapm_sync(dapm); + } + + return 0; +} + +static struct notifier_block cht_jack_nb = { + .notifier_call = cht_ti_jack_event, +}; + +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .name = "hp", + .report = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, + .debounce_time = 200, + }, + { + .name = "mic", + .invert = 1, + .report = SND_JACK_MICROPHONE, + .debounce_time = 200, + }, +}; + +static const struct acpi_gpio_params hp_gpios = { 0, 0, false }; +static const struct acpi_gpio_params mic_gpios = { 1, 0, false }; + +static const struct acpi_gpio_mapping acpi_max98090_gpios[] = { + { "hp-gpios", &hp_gpios, 1 }, + { "mic-gpios", &mic_gpios, 1 }, + {}, +}; + +static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + int jack_type; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); + struct snd_soc_jack *jack = &ctx->jack; + + if (ctx->ts3a227e_present) { + /* + * The jack has already been created in the + * cht_max98090_headset_init() function. + */ + snd_soc_jack_notifier_register(jack, &cht_jack_nb); + return 0; + } + + jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE; + + ret = snd_soc_card_jack_new(runtime->card, "Headset Jack", + jack_type, jack, + hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); + if (ret) { + dev_err(runtime->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + ret = snd_soc_jack_add_gpiods(runtime->card->dev->parent, jack, + ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + if (ret) { + /* + * flag error but don't bail if jack detect is broken + * due to platform issues or bad BIOS/configuration + */ + dev_err(runtime->dev, + "jack detection gpios not added, error %d\n", ret); + } + + /* See the comment in snd_cht_mc_probe() */ + if (ctx->quirks & QUIRK_PMC_PLT_CLK_0) + return 0; + + /* + * The firmware might enable the clock at + * boot (this information may or may not + * be reflected in the enable clock register). + * To change the rate we must disable the clock + * first to cover these cases. Due to common + * clock framework restrictions that do not allow + * to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(ctx->mclk); + if (!ret) + clk_disable_unprepare(ctx->mclk); + + ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); + + if (ret) + dev_err(runtime->dev, "unable to set MCLK rate\n"); + + return ret; +} + +static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret = 0; + unsigned int fmt = 0; + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 16); + if (ret < 0) { + dev_err(rtd->dev, "can't set cpu_dai slot fmt: %d\n", ret); + return ret; + } + + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS; + + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); + if (ret < 0) { + dev_err(rtd->dev, "can't set cpu_dai set fmt: %d\n", ret); + return ret; + } + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 16-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int cht_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static int cht_max98090_headset_init(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_jack *jack = &ctx->jack; + int jack_type; + int ret; + + /* + * TI supports 4 butons headset detection + * KEY_MEDIA + * KEY_VOICECOMMAND + * KEY_VOLUMEUP + * KEY_VOLUMEDOWN + */ + jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3; + + ret = snd_soc_card_jack_new(card, "Headset Jack", jack_type, + jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + return ts3a227e_enable_jack_detect(component, jack); +} + +static const struct snd_soc_ops cht_aif1_ops = { + .startup = cht_aif1_startup, +}; + +static const struct snd_soc_ops cht_be_ssp2_ops = { + .hw_params = cht_aif1_hw_params, +}; + +static struct snd_soc_aux_dev cht_max98090_headset_dev = { + .name = "Headset Chip", + .init = cht_max98090_headset_init, + .codec_name = "i2c-104C227E:00", +}; + +static struct snd_soc_dai_link cht_dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &cht_aif1_ops, + }, + /* back ends */ + { + .name = "SSP2-Codec", + .id = 0, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "HiFi", + .codec_name = "i2c-193C9890:00", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .init = cht_codec_init, + .be_hw_params_fixup = cht_codec_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_be_ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_cht = { + .name = "chtmax98090", + .owner = THIS_MODULE, + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .aux_dev = &cht_max98090_headset_dev, + .num_aux_devs = 1, + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), +}; + +static const struct dmi_system_id cht_max98090_quirk_table[] = { + { + /* Clapper model Chromebook */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Clapper"), + }, + .driver_data = (void *)QUIRK_PMC_PLT_CLK_0, + }, + { + /* Gnawty model Chromebook (Acer Chromebook CB3-111) */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Gnawty"), + }, + .driver_data = (void *)QUIRK_PMC_PLT_CLK_0, + }, + { + /* Swanky model Chromebook (Toshiba Chromebook 2) */ + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Swanky"), + }, + .driver_data = (void *)QUIRK_PMC_PLT_CLK_0, + }, + {} +}; + +static int snd_cht_mc_probe(struct platform_device *pdev) +{ + const struct dmi_system_id *dmi_id; + struct device *dev = &pdev->dev; + int ret_val = 0; + struct cht_mc_private *drv; + const char *mclk_name; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + dmi_id = dmi_first_match(cht_max98090_quirk_table); + if (dmi_id) + drv->quirks = (unsigned long)dmi_id->driver_data; + + drv->ts3a227e_present = acpi_dev_found("104C227E"); + if (!drv->ts3a227e_present) { + /* no need probe TI jack detection chip */ + snd_soc_card_cht.aux_dev = NULL; + snd_soc_card_cht.num_aux_devs = 0; + + ret_val = devm_acpi_dev_add_driver_gpios(dev->parent, + acpi_max98090_gpios); + if (ret_val) + dev_dbg(dev, "Unable to add GPIO mapping table\n"); + } + + /* register the soc card */ + snd_soc_card_cht.dev = &pdev->dev; + snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); + + if (drv->quirks & QUIRK_PMC_PLT_CLK_0) + mclk_name = "pmc_plt_clk_0"; + else + mclk_name = "pmc_plt_clk_3"; + + drv->mclk = devm_clk_get(&pdev->dev, mclk_name); + if (IS_ERR(drv->mclk)) { + dev_err(&pdev->dev, + "Failed to get MCLK from %s: %ld\n", + mclk_name, PTR_ERR(drv->mclk)); + return PTR_ERR(drv->mclk); + } + + /* + * Boards which have the MAX98090's clk connected to clk_0 do not seem + * to like it if we muck with the clock. If we disable the clock when + * it is unused we get "max98090 i2c-193C9890:00: PLL unlocked" errors + * and the PLL never seems to lock again. + * So for these boards we enable it here once and leave it at that. + */ + if (drv->quirks & QUIRK_PMC_PLT_CLK_0) { + ret_val = clk_prepare_enable(drv->mclk); + if (ret_val < 0) { + dev_err(&pdev->dev, "MCLK enable error: %d\n", ret_val); + return ret_val; + } + } + + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_cht); + return ret_val; +} + +static int snd_cht_mc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (ctx->quirks & QUIRK_PMC_PLT_CLK_0) + clk_disable_unprepare(ctx->mclk); + + return 0; +} + +static struct platform_driver snd_cht_mc_driver = { + .driver = { + .name = "cht-bsw-max98090", + }, + .probe = snd_cht_mc_probe, + .remove = snd_cht_mc_remove, +}; + +module_platform_driver(snd_cht_mc_driver) + +MODULE_DESCRIPTION("ASoC Intel(R) Braswell Machine driver"); +MODULE_AUTHOR("Fang, Yang A "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cht-bsw-max98090"); diff --git a/sound/soc/intel/boards/cht_bsw_nau8824.c b/sound/soc/intel/boards/cht_bsw_nau8824.c new file mode 100644 index 000000000..30c46977d --- /dev/null +++ b/sound/soc/intel/boards/cht_bsw_nau8824.c @@ -0,0 +1,282 @@ +/* + * cht-bsw-nau8824.c - ASoc Machine driver for Intel Cherryview-based + * platforms Cherrytrail and Braswell, with nau8824 codec. + * + * Copyright (C) 2018 Intel Corp + * Copyright (C) 2018 Nuvoton Technology Corp + * + * Author: Wang, Joseph C + * Co-author: John Hsu + * This file is based on cht_bsw_rt5672.c and cht-bsw-max98090.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../atom/sst-atom-controls.h" +#include "../../codecs/nau8824.h" + +struct cht_mc_private { + struct snd_soc_jack jack; +}; + +static struct snd_soc_jack_pin cht_bsw_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route cht_audio_map[] = { + {"Ext Spk", NULL, "SPKOUTL"}, + {"Ext Spk", NULL, "SPKOUTR"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"MIC1", NULL, "Int Mic"}, + {"MIC2", NULL, "Int Mic"}, + {"HSMIC1", NULL, "Headset Mic"}, + {"HSMIC2", NULL, "Headset Mic"}, + {"Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "Capture"}, +}; + +static const struct snd_kcontrol_new cht_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8824_CLK_FLL_FS, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set FS clock %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, params_rate(params), + params_rate(params) * 256); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); + struct snd_soc_jack *jack = &ctx->jack; + struct snd_soc_dai *codec_dai = runtime->codec_dai; + struct snd_soc_component *component = codec_dai->component; + int ret, jack_type; + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xf, 0x1, 4, 24); + if (ret < 0) { + dev_err(runtime->dev, "can't set codec TDM slot %d\n", ret); + return ret; + } + + /* NAU88L24 supports 4 butons headset detection + * KEY_MEDIA + * KEY_VOICECOMMAND + * KEY_VOLUMEUP + * KEY_VOLUMEDOWN + */ + jack_type = SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3; + ret = snd_soc_card_jack_new(runtime->card, "Headset", jack_type, jack, + cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins)); + if (ret) { + dev_err(runtime->dev, + "Headset Jack creation failed %d\n", ret); + return ret; + } + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_MEDIA); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + nau8824_enable_jack_detect(component, jack); + + return ret; +} + +static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = + hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + snd_mask_none(fmt); + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int cht_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops cht_aif1_ops = { + .startup = cht_aif1_startup, +}; + +static const struct snd_soc_ops cht_be_ssp2_ops = { + .hw_params = cht_aif1_hw_params, +}; + +static struct snd_soc_dai_link cht_dailink[] = { + /* Front End DAI links */ + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_COMPR] = { + .name = "Compressed Port", + .stream_name = "Compress", + .cpu_dai_name = "compress-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + }, + /* Back End DAI links */ + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .id = 1, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = NAU8824_CODEC_DAI, + .codec_name = "i2c-10508824:00", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .init = cht_codec_init, + .be_hw_params_fixup = cht_codec_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_be_ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_cht = { + .name = "chtnau8824", + .owner = THIS_MODULE, + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), +}; + +static int snd_cht_mc_probe(struct platform_device *pdev) +{ + struct cht_mc_private *drv; + int ret_val; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); + + /* register the soc card */ + snd_soc_card_cht.dev = &pdev->dev; + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_cht); + + return ret_val; +} + +static struct platform_driver snd_cht_mc_driver = { + .driver = { + .name = "cht-bsw-nau8824", + }, + .probe = snd_cht_mc_probe, +}; + +module_platform_driver(snd_cht_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); +MODULE_AUTHOR("Wang, Joseph C "); +MODULE_AUTHOR("John Hsu "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cht-bsw-nau8824"); diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c new file mode 100644 index 000000000..f5a5ea6a0 --- /dev/null +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -0,0 +1,701 @@ +/* + * cht-bsw-rt5645.c - ASoc Machine driver for Intel Cherryview-based platforms + * Cherrytrail and Braswell, with RT5645 codec. + * + * Copyright (C) 2015 Intel Corp + * Author: Fang, Yang A + * N,Harshapriya + * This file is modified from cht_bsw_rt5672.c + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5645.h" +#include "../atom/sst-atom-controls.h" + +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI1 "rt5645-aif1" +#define CHT_CODEC_DAI2 "rt5645-aif2" + +struct cht_acpi_card { + char *codec_id; + int codec_type; + struct snd_soc_card *soc_card; +}; + +struct cht_mc_private { + struct snd_soc_jack jack; + struct cht_acpi_card *acpi_card; + char codec_name[SND_ACPI_I2C_ID_LEN]; + struct clk *mclk; +}; + +#define CHT_RT5645_MAP(quirk) ((quirk) & GENMASK(7, 0)) +#define CHT_RT5645_SSP2_AIF2 BIT(16) /* default is using AIF1 */ +#define CHT_RT5645_SSP0_AIF1 BIT(17) +#define CHT_RT5645_SSP0_AIF2 BIT(18) + +static unsigned long cht_rt5645_quirk = 0; + +static void log_quirks(struct device *dev) +{ + if (cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) + dev_info(dev, "quirk SSP2_AIF2 enabled"); + if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) + dev_info(dev, "quirk SSP0_AIF1 enabled"); + if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2) + dev_info(dev, "quirk SSP0_AIF2 enabled"); +} + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI1); + if (!codec_dai) + codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI2); + + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = clk_prepare_enable(ctx->mclk); + if (ret < 0) { + dev_err(card->dev, + "could not configure MCLK state"); + return ret; + } + } else { + /* Set codec sysclk source to its internal clock because codec PLL will + * be off when idle and MCLK will also be off when codec is + * runtime suspended. Codec needs clock for jack detection and button + * press. MCLK is turned off with clock framework or ACPI. + */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_RCCLK, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + clk_disable_unprepare(ctx->mclk); + } + + return 0; +} + +static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_MIC("Int Analog Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_rt5645_audio_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"IN2P", NULL, "Int Analog Mic"}, + {"IN2N", NULL, "Int Analog Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOL"}, + {"Ext Spk", NULL, "SPOR"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Int Analog Mic", NULL, "Platform Clock"}, + {"Int Analog Mic", NULL, "micbias1"}, + {"Int Analog Mic", NULL, "micbias2"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_soc_dapm_route cht_rt5650_audio_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L2", NULL, "Int Mic"}, + {"DMIC R2", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOL"}, + {"Ext Spk", NULL, "SPOR"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_soc_dapm_route cht_rt5645_ssp2_aif1_map[] = { + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_soc_dapm_route cht_rt5645_ssp2_aif2_map[] = { + {"AIF2 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx" }, + {"codec_in1", NULL, "ssp2 Rx" }, + {"ssp2 Rx", NULL, "AIF2 Capture"}, +}; + +static const struct snd_soc_dapm_route cht_rt5645_ssp0_aif1_map[] = { + {"AIF1 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Tx", NULL, "modem_out"}, + {"modem_in", NULL, "ssp0 Rx" }, + {"ssp0 Rx", NULL, "AIF1 Capture"}, +}; + +static const struct snd_soc_dapm_route cht_rt5645_ssp0_aif2_map[] = { + {"AIF2 Playback", NULL, "ssp0 Tx"}, + {"ssp0 Tx", NULL, "modem_out"}, + {"modem_in", NULL, "ssp0 Rx" }, + {"ssp0 Rx", NULL, "AIF2 Capture"}, +}; + +static const struct snd_kcontrol_new cht_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Int Analog Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static struct snd_soc_jack_pin cht_bsw_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, + params_rate(params) * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + + return 0; +} + +/* uncomment when we have a real quirk +static int cht_rt5645_quirk_cb(const struct dmi_system_id *id) +{ + cht_rt5645_quirk = (unsigned long)id->driver_data; + return 1; +} +*/ + +static const struct dmi_system_id cht_rt5645_quirk_table[] = { + { + }, +}; + +static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); + struct snd_soc_component *component = runtime->codec_dai->component; + int jack_type; + int ret; + + if ((cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) || + (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) { + /* Select clk_i2s2_asrc as ASRC clock source */ + rt5645_sel_asrc_clk_src(component, + RT5645_DA_STEREO_FILTER | + RT5645_DA_MONO_L_FILTER | + RT5645_DA_MONO_R_FILTER | + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S2_ASRC); + } else { + /* Select clk_i2s1_asrc as ASRC clock source */ + rt5645_sel_asrc_clk_src(component, + RT5645_DA_STEREO_FILTER | + RT5645_DA_MONO_L_FILTER | + RT5645_DA_MONO_R_FILTER | + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } + + if (cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) { + ret = snd_soc_dapm_add_routes(&card->dapm, + cht_rt5645_ssp2_aif2_map, + ARRAY_SIZE(cht_rt5645_ssp2_aif2_map)); + } else if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) { + ret = snd_soc_dapm_add_routes(&card->dapm, + cht_rt5645_ssp0_aif1_map, + ARRAY_SIZE(cht_rt5645_ssp0_aif1_map)); + } else if (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2) { + ret = snd_soc_dapm_add_routes(&card->dapm, + cht_rt5645_ssp0_aif2_map, + ARRAY_SIZE(cht_rt5645_ssp0_aif2_map)); + } else { + ret = snd_soc_dapm_add_routes(&card->dapm, + cht_rt5645_ssp2_aif1_map, + ARRAY_SIZE(cht_rt5645_ssp2_aif1_map)); + } + if (ret) + return ret; + + if (ctx->acpi_card->codec_type == CODEC_TYPE_RT5650) + jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3; + else + jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE; + + ret = snd_soc_card_jack_new(runtime->card, "Headset", + jack_type, &ctx->jack, + cht_bsw_jack_pins, ARRAY_SIZE(cht_bsw_jack_pins)); + if (ret) { + dev_err(runtime->dev, "Headset jack creation failed %d\n", ret); + return ret; + } + + rt5645_set_jack_detect(component, &ctx->jack, &ctx->jack, &ctx->jack); + + + /* + * The firmware might enable the clock at + * boot (this information may or may not + * be reflected in the enable clock register). + * To change the rate we must disable the clock + * first to cover these cases. Due to common + * clock framework restrictions that do not allow + * to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(ctx->mclk); + if (!ret) + clk_disable_unprepare(ctx->mclk); + + ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); + + if (ret) + dev_err(runtime->dev, "unable to set MCLK rate\n"); + + return ret; +} + +static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + int ret; + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + if ((cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) || + (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) { + + /* set SSP0 to 16-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot, override config + * with explicit setting to I2S 2ch 16-bit. The word length is set with + * dai_set_tdm_slot() since there is no other API exposed + */ + ret = snd_soc_dai_set_fmt(rtd->cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS + ); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(rtd->codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS + ); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to I2S, err %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, 16); + if (ret < 0) { + dev_err(rtd->dev, "can't set I2S config, err %d\n", ret); + return ret; + } + + } else { + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot + */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, + SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to TDM %d\n", ret); + return ret; + } + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(rtd->codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret); + return ret; + } + } + return 0; +} + +static int cht_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops cht_aif1_ops = { + .startup = cht_aif1_startup, +}; + +static const struct snd_soc_ops cht_be_ssp2_ops = { + .hw_params = cht_aif1_hw_params, +}; + +static struct snd_soc_dai_link cht_dailink[] = { + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &cht_aif1_ops, + }, + /* CODEC<->CODEC link */ + /* back ends */ + { + .name = "SSP2-Codec", + .id = 0, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .codec_dai_name = "rt5645-aif1", + .codec_name = "i2c-10EC5645:00", + .init = cht_codec_init, + .be_hw_params_fixup = cht_codec_fixup, + .nonatomic = true, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_be_ssp2_ops, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_chtrt5645 = { + .name = "chtrt5645", + .owner = THIS_MODULE, + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_rt5645_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_rt5645_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), +}; + +static struct snd_soc_card snd_soc_card_chtrt5650 = { + .name = "chtrt5650", + .owner = THIS_MODULE, + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_rt5650_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_rt5650_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), +}; + +static struct cht_acpi_card snd_soc_cards[] = { + {"10EC5640", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, + {"10EC5645", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, + {"10EC5648", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, + {"10EC3270", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, + {"10EC5650", CODEC_TYPE_RT5650, &snd_soc_card_chtrt5650}, +}; + +static char cht_rt5645_codec_name[SND_ACPI_I2C_ID_LEN]; +static char cht_rt5645_codec_aif_name[12]; /* = "rt5645-aif[1|2]" */ +static char cht_rt5645_cpu_dai_name[10]; /* = "ssp[0|2]-port" */ + +static bool is_valleyview(void) +{ + static const struct x86_cpu_id cpu_ids[] = { + { X86_VENDOR_INTEL, 6, 55 }, /* Valleyview, Bay Trail */ + {} + }; + + if (!x86_match_cpu(cpu_ids)) + return false; + return true; +} + +struct acpi_chan_package { /* ACPICA seems to require 64 bit integers */ + u64 aif_value; /* 1: AIF1, 2: AIF2 */ + u64 mclock_value; /* usually 25MHz (0x17d7940), ignored */ +}; + +static int snd_cht_mc_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = snd_soc_cards[0].soc_card; + struct snd_soc_acpi_mach *mach; + struct cht_mc_private *drv; + const char *i2c_name = NULL; + bool found = false; + bool is_bytcr = false; + int dai_index = 0; + int ret_val = 0; + int i; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + mach = (&pdev->dev)->platform_data; + + for (i = 0; i < ARRAY_SIZE(snd_soc_cards); i++) { + if (acpi_dev_found(snd_soc_cards[i].codec_id) && + (!strncmp(snd_soc_cards[i].codec_id, mach->id, 8))) { + dev_dbg(&pdev->dev, + "found codec %s\n", snd_soc_cards[i].codec_id); + card = snd_soc_cards[i].soc_card; + drv->acpi_card = &snd_soc_cards[i]; + found = true; + break; + } + } + + if (!found) { + dev_err(&pdev->dev, "No matching HID found in supported list\n"); + return -ENODEV; + } + + card->dev = &pdev->dev; + sprintf(drv->codec_name, "i2c-%s:00", drv->acpi_card->codec_id); + + /* set correct codec name */ + for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) + if (!strcmp(card->dai_link[i].codec_name, "i2c-10EC5645:00")) { + card->dai_link[i].codec_name = drv->codec_name; + dai_index = i; + } + + /* fixup codec name based on HID */ + i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); + if (i2c_name) { + snprintf(cht_rt5645_codec_name, sizeof(cht_rt5645_codec_name), + "%s%s", "i2c-", i2c_name); + cht_dailink[dai_index].codec_name = cht_rt5645_codec_name; + } + + /* + * swap SSP0 if bytcr is detected + * (will be overridden if DMI quirk is detected) + */ + if (is_valleyview()) { + struct sst_platform_info *p_info = mach->pdata; + const struct sst_res_info *res_info = p_info->res_info; + + if (res_info->acpi_ipc_irq_index == 0) + is_bytcr = true; + } + + if (is_bytcr) { + /* + * Baytrail CR platforms may have CHAN package in BIOS, try + * to find relevant routing quirk based as done on Windows + * platforms. We have to read the information directly from the + * BIOS, at this stage the card is not created and the links + * with the codec driver/pdata are non-existent + */ + + struct acpi_chan_package chan_package; + + /* format specified: 2 64-bit integers */ + struct acpi_buffer format = {sizeof("NN"), "NN"}; + struct acpi_buffer state = {0, NULL}; + struct snd_soc_acpi_package_context pkg_ctx; + bool pkg_found = false; + + state.length = sizeof(chan_package); + state.pointer = &chan_package; + + pkg_ctx.name = "CHAN"; + pkg_ctx.length = 2; + pkg_ctx.format = &format; + pkg_ctx.state = &state; + pkg_ctx.data_valid = false; + + pkg_found = snd_soc_acpi_find_package_from_hid(mach->id, + &pkg_ctx); + if (pkg_found) { + if (chan_package.aif_value == 1) { + dev_info(&pdev->dev, "BIOS Routing: AIF1 connected\n"); + cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF1; + } else if (chan_package.aif_value == 2) { + dev_info(&pdev->dev, "BIOS Routing: AIF2 connected\n"); + cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF2; + } else { + dev_info(&pdev->dev, "BIOS Routing isn't valid, ignored\n"); + pkg_found = false; + } + } + + if (!pkg_found) { + /* no BIOS indications, assume SSP0-AIF2 connection */ + cht_rt5645_quirk |= CHT_RT5645_SSP0_AIF2; + } + } + + /* check quirks before creating card */ + dmi_check_system(cht_rt5645_quirk_table); + log_quirks(&pdev->dev); + + if ((cht_rt5645_quirk & CHT_RT5645_SSP2_AIF2) || + (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) { + + /* fixup codec aif name */ + snprintf(cht_rt5645_codec_aif_name, + sizeof(cht_rt5645_codec_aif_name), + "%s", "rt5645-aif2"); + + cht_dailink[dai_index].codec_dai_name = + cht_rt5645_codec_aif_name; + } + + if ((cht_rt5645_quirk & CHT_RT5645_SSP0_AIF1) || + (cht_rt5645_quirk & CHT_RT5645_SSP0_AIF2)) { + + /* fixup cpu dai name name */ + snprintf(cht_rt5645_cpu_dai_name, + sizeof(cht_rt5645_cpu_dai_name), + "%s", "ssp0-port"); + + cht_dailink[dai_index].cpu_dai_name = + cht_rt5645_cpu_dai_name; + } + + drv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + if (IS_ERR(drv->mclk)) { + dev_err(&pdev->dev, + "Failed to get MCLK from pmc_plt_clk_3: %ld\n", + PTR_ERR(drv->mclk)); + return PTR_ERR(drv->mclk); + } + + snd_soc_card_set_drvdata(card, drv); + ret_val = devm_snd_soc_register_card(&pdev->dev, card); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, card); + return ret_val; +} + +static struct platform_driver snd_cht_mc_driver = { + .driver = { + .name = "cht-bsw-rt5645", + }, + .probe = snd_cht_mc_probe, +}; + +module_platform_driver(snd_cht_mc_driver) + +MODULE_DESCRIPTION("ASoC Intel(R) Braswell Machine driver"); +MODULE_AUTHOR("Fang, Yang A,N,Harshapriya"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cht-bsw-rt5645"); diff --git a/sound/soc/intel/boards/cht_bsw_rt5672.c b/sound/soc/intel/boards/cht_bsw_rt5672.c new file mode 100644 index 000000000..e5aa13058 --- /dev/null +++ b/sound/soc/intel/boards/cht_bsw_rt5672.c @@ -0,0 +1,457 @@ +/* + * cht_bsw_rt5672.c - ASoc Machine driver for Intel Cherryview-based platforms + * Cherrytrail and Braswell, with RT5672 codec. + * + * Copyright (C) 2014 Intel Corp + * Author: Subhransu S. Prusty + * Mengdong Lin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5670.h" +#include "../atom/sst-atom-controls.h" + + +/* The platform clock #3 outputs 19.2Mhz clock to codec as I2S MCLK */ +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "rt5670-aif1" + +struct cht_mc_private { + struct snd_soc_jack headset; + char codec_name[SND_ACPI_I2C_ID_LEN]; + struct clk *mclk; +}; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin cht_bsw_headset_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (ctx->mclk) { + ret = clk_prepare_enable(ctx->mclk); + if (ret < 0) { + dev_err(card->dev, + "could not configure MCLK state"); + return ret; + } + } + + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, 48000 * 512); + if (ret < 0) { + dev_err(card->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + /* set codec sysclk source to PLL */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + } else { + /* Set codec sysclk source to its internal clock because codec + * PLL will be off when idle and MCLK will also be off by ACPI + * when codec is runtime suspended. Codec needs clock for jack + * detection and button press. + */ + snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_RCCLK, + 48000 * 512, SND_SOC_CLOCK_IN); + + if (ctx->mclk) + clk_disable_unprepare(ctx->mclk); + } + return 0; +} + +static const struct snd_soc_dapm_widget cht_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_audio_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Ext Spk", NULL, "SPOLP"}, + {"Ext Spk", NULL, "SPOLN"}, + {"Ext Spk", NULL, "SPORP"}, + {"Ext Spk", NULL, "SPORN"}, + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, + {"Ext Spk", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static int cht_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* set codec PLL source to the 19.2MHz platform clock (MCLK) */ + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5670_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec pll: %d\n", ret); + return ret; + } + + /* set codec sysclk source to PLL */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5670_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret); + return ret; + } + return 0; +} + +static const struct acpi_gpio_params headset_gpios = { 0, 0, false }; + +static const struct acpi_gpio_mapping cht_rt5672_gpios[] = { + { "headset-gpios", &headset_gpios, 1 }, + {}, +}; + +static int cht_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_dai *codec_dai = runtime->codec_dai; + struct snd_soc_component *component = codec_dai->component; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(runtime->card); + + if (devm_acpi_dev_add_driver_gpios(component->dev, cht_rt5672_gpios)) + dev_warn(runtime->dev, "Unable to add GPIO mapping table\n"); + + /* Select codec ASRC clock source to track I2S1 clock, because codec + * is in slave mode and 100fs I2S format (BCLK = 100 * LRCLK) cannot + * be supported by RT5672. Otherwise, ASRC will be disabled and cause + * noise. + */ + rt5670_sel_asrc_clk_src(component, + RT5670_DA_STEREO_FILTER + | RT5670_DA_MONO_L_FILTER + | RT5670_DA_MONO_R_FILTER + | RT5670_AD_STEREO_FILTER + | RT5670_AD_MONO_L_FILTER + | RT5670_AD_MONO_R_FILTER, + RT5670_CLK_SEL_I2S1_ASRC); + + ret = snd_soc_card_jack_new(runtime->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, + &ctx->headset, + cht_bsw_headset_pins, + ARRAY_SIZE(cht_bsw_headset_pins)); + if (ret) + return ret; + + rt5670_set_jack_detect(component, &ctx->headset); + if (ctx->mclk) { + /* + * The firmware might enable the clock at + * boot (this information may or may not + * be reflected in the enable clock register). + * To change the rate we must disable the clock + * first to cover these cases. Due to common + * clock framework restrictions that do not allow + * to disable a clock that has not been enabled, + * we need to enable the clock first. + */ + ret = clk_prepare_enable(ctx->mclk); + if (!ret) + clk_disable_unprepare(ctx->mclk); + + ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); + + if (ret) { + dev_err(runtime->dev, "unable to set MCLK rate\n"); + return ret; + } + } + return 0; +} + +static int cht_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + int ret; + + /* The DSP will covert the FE rate to 48k, stereo, 24bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP2 to 24-bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + /* + * Default mode for SSP configuration is TDM 4 slot + */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, + SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + dev_err(rtd->dev, "can't set format to TDM %d\n", ret); + return ret; + } + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(rtd->codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops cht_aif1_ops = { + .startup = cht_aif1_startup, +}; + +static const struct snd_soc_ops cht_be_ssp2_ops = { + .hw_params = cht_aif1_hw_params, +}; + +static struct snd_soc_dai_link cht_dailink[] = { + /* Front End DAI links */ + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "media-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_aif1_ops, + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .cpu_dai_name = "deepbuffer-cpu-dai", + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + .platform_name = "sst-mfld-platform", + .nonatomic = true, + .dynamic = 1, + .dpcm_playback = 1, + .ops = &cht_aif1_ops, + }, + + /* Back End DAI links */ + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .id = 0, + .cpu_dai_name = "ssp2-port", + .platform_name = "sst-mfld-platform", + .no_pcm = 1, + .nonatomic = true, + .codec_dai_name = "rt5670-aif1", + .codec_name = "i2c-10EC5670:00", + .init = cht_codec_init, + .be_hw_params_fixup = cht_codec_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &cht_be_ssp2_ops, + }, +}; + +static int cht_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strncmp(component->name, + ctx->codec_name, sizeof(ctx->codec_name))) { + + dev_dbg(component->dev, "disabling jack detect before going to suspend.\n"); + rt5670_jack_suspend(component); + break; + } + } + return 0; +} + +static int cht_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct cht_mc_private *ctx = snd_soc_card_get_drvdata(card); + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (!strncmp(component->name, + ctx->codec_name, sizeof(ctx->codec_name))) { + + dev_dbg(component->dev, "enabling jack detect for resume.\n"); + rt5670_jack_resume(component); + break; + } + } + + return 0; +} + +/* SoC card */ +static struct snd_soc_card snd_soc_card_cht = { + .name = "cht-bsw-rt5672", + .owner = THIS_MODULE, + .dai_link = cht_dailink, + .num_links = ARRAY_SIZE(cht_dailink), + .dapm_widgets = cht_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), + .dapm_routes = cht_audio_map, + .num_dapm_routes = ARRAY_SIZE(cht_audio_map), + .controls = cht_mc_controls, + .num_controls = ARRAY_SIZE(cht_mc_controls), + .suspend_pre = cht_suspend_pre, + .resume_post = cht_resume_post, +}; + +#define RT5672_I2C_DEFAULT "i2c-10EC5670:00" + +static int snd_cht_mc_probe(struct platform_device *pdev) +{ + int ret_val = 0; + struct cht_mc_private *drv; + struct snd_soc_acpi_mach *mach = pdev->dev.platform_data; + const char *i2c_name; + int i; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); + if (!drv) + return -ENOMEM; + + strcpy(drv->codec_name, RT5672_I2C_DEFAULT); + + /* fixup codec name based on HID */ + if (mach) { + i2c_name = acpi_dev_get_first_match_name(mach->id, NULL, -1); + if (i2c_name) { + snprintf(drv->codec_name, sizeof(drv->codec_name), + "i2c-%s", i2c_name); + for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) { + if (!strcmp(cht_dailink[i].codec_name, + RT5672_I2C_DEFAULT)) { + cht_dailink[i].codec_name = + drv->codec_name; + break; + } + } + } + } + + drv->mclk = devm_clk_get(&pdev->dev, "pmc_plt_clk_3"); + if (IS_ERR(drv->mclk)) { + dev_err(&pdev->dev, + "Failed to get MCLK from pmc_plt_clk_3: %ld\n", + PTR_ERR(drv->mclk)); + return PTR_ERR(drv->mclk); + } + snd_soc_card_set_drvdata(&snd_soc_card_cht, drv); + + /* register the soc card */ + snd_soc_card_cht.dev = &pdev->dev; + ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); + if (ret_val) { + dev_err(&pdev->dev, + "snd_soc_register_card failed %d\n", ret_val); + return ret_val; + } + platform_set_drvdata(pdev, &snd_soc_card_cht); + return ret_val; +} + +static struct platform_driver snd_cht_mc_driver = { + .driver = { + .name = "cht-bsw-rt5672", + }, + .probe = snd_cht_mc_probe, +}; + +module_platform_driver(snd_cht_mc_driver); + +MODULE_DESCRIPTION("ASoC Intel(R) Baytrail CR Machine driver"); +MODULE_AUTHOR("Subhransu S. Prusty, Mengdong Lin"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:cht-bsw-rt5672"); diff --git a/sound/soc/intel/boards/glk_rt5682_max98357a.c b/sound/soc/intel/boards/glk_rt5682_max98357a.c new file mode 100644 index 000000000..c4b94e261 --- /dev/null +++ b/sound/soc/intel/boards/glk_rt5682_max98357a.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2018 Intel Corporation. + +/* + * Intel Geminilake I2S Machine Driver with MAX98357A & RT5682 Codecs + * + * Modified from: + * Intel Apollolake I2S Machine driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../skylake/skl.h" +#include "../../codecs/rt5682.h" +#include "../../codecs/hdac_hdmi.h" + +/* The platform clock outputs 19.2Mhz clock to codec as I2S MCLK */ +#define GLK_PLAT_CLK_FREQ 19200000 +#define RT5682_PLL_FREQ (48000 * 512) +#define GLK_REALTEK_CODEC_DAI "rt5682-aif1" +#define GLK_MAXIM_CODEC_DAI "HiFi" +#define MAXIM_DEV0_NAME "MX98357A:00" +#define DUAL_CHANNEL 2 +#define QUAD_CHANNEL 4 +#define NAME_SIZE 32 + +static struct snd_soc_jack geminilake_hdmi[3]; + +struct glk_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct glk_card_private { + struct snd_soc_jack geminilake_headset; + struct list_head hdmi_pcm_list; +}; + +enum { + GLK_DPCM_AUDIO_PB = 0, + GLK_DPCM_AUDIO_CP, + GLK_DPCM_AUDIO_HS_PB, + GLK_DPCM_AUDIO_ECHO_REF_CP, + GLK_DPCM_AUDIO_REF_CP, + GLK_DPCM_AUDIO_DMIC_CP, + GLK_DPCM_AUDIO_HDMI1_PB, + GLK_DPCM_AUDIO_HDMI2_PB, + GLK_DPCM_AUDIO_HDMI3_PB, +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret = 0; + + codec_dai = snd_soc_card_get_codec_dai(card, GLK_REALTEK_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop sysclk: %d\n", ret); + } else if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5682_PLL1_S_MCLK, + GLK_PLAT_CLK_FREQ, RT5682_PLL_FREQ); + if (ret < 0) { + dev_err(card->dev, "can't set codec pll: %d\n", ret); + return ret; + } + } + + if (ret) + dev_err(card->dev, "failed to start internal clk: %d\n", ret); + + return ret; +} + +static const struct snd_kcontrol_new geminilake_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget geminilake_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route geminilake_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* speaker */ + { "Spk", NULL, "Speaker" }, + + /* other jacks */ + { "Headset Mic", NULL, "Platform Clock" }, + { "IN1P", NULL, "Headset Mic" }, + + /* digital mics */ + { "DMic", NULL, "SoC DMIC" }, + + /* CODEC BE connections */ + { "HiFi Playback", NULL, "ssp1 Tx" }, + { "ssp1 Tx", NULL, "codec0_out" }, + + { "AIF1 Playback", NULL, "ssp2 Tx" }, + { "ssp2 Tx", NULL, "codec1_out" }, + + { "codec0_in", NULL, "ssp2 Rx" }, + { "ssp2 Rx", NULL, "AIF1 Capture" }, + + { "HDMI1", NULL, "hif5-0 Output" }, + { "HDMI2", NULL, "hif6-0 Output" }, + { "HDMI2", NULL, "hif7-0 Output" }, + + { "hifi3", NULL, "iDisp3 Tx" }, + { "iDisp3 Tx", NULL, "iDisp3_out" }, + { "hifi2", NULL, "iDisp2 Tx" }, + { "iDisp2 Tx", NULL, "iDisp2_out" }, + { "hifi1", NULL, "iDisp1 Tx" }, + { "iDisp1 Tx", NULL, "iDisp1_out" }, + + /* DMIC */ + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "DMIC AIF" }, +}; + +static int geminilake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = DUAL_CHANNEL; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int geminilake_rt5682_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct glk_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_jack *jack; + int ret; + + /* Configure sysclk for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, + RT5682_PLL_FREQ, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &ctx->geminilake_headset, NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + jack = &ctx->geminilake_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return ret; +}; + +static int geminilake_rt5682_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* Set valid bitmask & configuration for I2S in 24 bit */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2, 24); + if (ret < 0) { + dev_err(rtd->dev, "set TDM slot err:%d\n", ret); + return ret; + } + + return ret; +} + +static struct snd_soc_ops geminilake_rt5682_ops = { + .hw_params = geminilake_rt5682_hw_params, +}; + +static int geminilake_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct glk_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct glk_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = GLK_DPCM_AUDIO_HDMI1_PB + dai->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int geminilake_rt5682_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->cpu_dai->component; + struct snd_soc_dapm_context *dapm; + int ret; + + dapm = snd_soc_component_get_dapm(component); + ret = snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + if (ret) { + dev_err(rtd->dev, "Ref Cap ignore suspend failed %d\n", ret); + return ret; + } + + return ret; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static unsigned int channels_quad[] = { + QUAD_CHANNEL, +}; + +static struct snd_pcm_hw_constraint_list constraints_channels_quad = { + .count = ARRAY_SIZE(channels_quad), + .list = channels_quad, + .mask = 0, +}; + +static int geminilake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* + * set BE channel constraint as user FE channels + */ + channels->min = channels->max = 4; + + return 0; +} + +static int geminilake_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels_quad); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static const struct snd_soc_ops geminilake_dmic_ops = { + .startup = geminilake_dmic_startup, +}; + +static const unsigned int rates_16000[] = { + 16000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16000 = { + .count = ARRAY_SIZE(rates_16000), + .list = rates_16000, +}; + +static int geminilake_refcap_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_16000); +}; + +static const struct snd_soc_ops geminilake_refcap_ops = { + .startup = geminilake_refcap_startup, +}; + +/* geminilake digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link geminilake_dais[] = { + /* Front End DAI links */ + [GLK_DPCM_AUDIO_PB] = { + .name = "Glk Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = geminilake_rt5682_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + [GLK_DPCM_AUDIO_CP] = { + .name = "Glk Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + }, + [GLK_DPCM_AUDIO_HS_PB] = { + .name = "Glk Audio Headset Playback", + .stream_name = "Headset Audio", + .cpu_dai_name = "System Pin2", + .platform_name = "0000:00:0e.0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dpcm_playback = 1, + .nonatomic = 1, + .dynamic = 1, + }, + [GLK_DPCM_AUDIO_ECHO_REF_CP] = { + .name = "Glk Audio Echo Reference cap", + .stream_name = "Echoreference Capture", + .cpu_dai_name = "Echoref Pin", + .platform_name = "0000:00:0e.0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = NULL, + .capture_only = 1, + .nonatomic = 1, + }, + [GLK_DPCM_AUDIO_REF_CP] = { + .name = "Glk Audio Reference cap", + .stream_name = "Refcap", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &geminilake_refcap_ops, + }, + [GLK_DPCM_AUDIO_DMIC_CP] = { + .name = "Glk Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &geminilake_dmic_ops, + }, + [GLK_DPCM_AUDIO_HDMI1_PB] = { + .name = "Glk HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [GLK_DPCM_AUDIO_HDMI2_PB] = { + .name = "Glk HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [GLK_DPCM_AUDIO_HDMI3_PB] = { + .name = "Glk HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + /* Back End DAI links */ + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 0, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = MAXIM_DEV0_NAME, + .codec_dai_name = GLK_MAXIM_CODEC_DAI, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = geminilake_ssp_fixup, + .dpcm_playback = 1, + }, + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .id = 1, + .cpu_dai_name = "SSP2 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "i2c-10EC5682:00", + .codec_dai_name = GLK_REALTEK_CODEC_DAI, + .init = geminilake_rt5682_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = geminilake_ssp_fixup, + .ops = &geminilake_rt5682_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:0e.0", + .ignore_suspend = 1, + .be_hw_params_fixup = geminilake_dmic_fixup, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:0e.0", + .init = geminilake_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:0e.0", + .init = geminilake_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:0e.0", + .init = geminilake_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +static int glk_card_late_probe(struct snd_soc_card *card) +{ + struct glk_card_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = NULL; + char jack_name[NAME_SIZE]; + struct glk_hdmi_pcm *pcm; + int err = 0; + int i = 0; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &geminilake_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &geminilake_hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* geminilake audio machine driver for SPT + RT5682 */ +static struct snd_soc_card glk_audio_card_rt5682_m98357a = { + .name = "glkrt5682max", + .owner = THIS_MODULE, + .dai_link = geminilake_dais, + .num_links = ARRAY_SIZE(geminilake_dais), + .controls = geminilake_controls, + .num_controls = ARRAY_SIZE(geminilake_controls), + .dapm_widgets = geminilake_widgets, + .num_dapm_widgets = ARRAY_SIZE(geminilake_widgets), + .dapm_routes = geminilake_map, + .num_dapm_routes = ARRAY_SIZE(geminilake_map), + .fully_routed = true, + .late_probe = glk_card_late_probe, +}; + +static int geminilake_audio_probe(struct platform_device *pdev) +{ + struct glk_card_private *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + glk_audio_card_rt5682_m98357a.dev = &pdev->dev; + snd_soc_card_set_drvdata(&glk_audio_card_rt5682_m98357a, ctx); + + return devm_snd_soc_register_card(&pdev->dev, + &glk_audio_card_rt5682_m98357a); +} + +static const struct platform_device_id glk_board_ids[] = { + { + .name = "glk_rt5682_max98357a", + .driver_data = + (kernel_ulong_t)&glk_audio_card_rt5682_m98357a, + }, + { } +}; + +static struct platform_driver geminilake_audio = { + .probe = geminilake_audio_probe, + .driver = { + .name = "glk_rt5682_max98357a", + .pm = &snd_soc_pm_ops, + }, + .id_table = glk_board_ids, +}; +module_platform_driver(geminilake_audio) + +/* Module information */ +MODULE_DESCRIPTION("Geminilake Audio Machine driver-RT5682 & MAX98357A in I2S mode"); +MODULE_AUTHOR("Naveen Manohar "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:glk_rt5682_max98357a"); diff --git a/sound/soc/intel/boards/haswell.c b/sound/soc/intel/boards/haswell.c new file mode 100644 index 000000000..67eb4a446 --- /dev/null +++ b/sound/soc/intel/boards/haswell.c @@ -0,0 +1,211 @@ +/* + * Intel Haswell Lynxpoint SST Audio + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../haswell/sst-haswell-ipc.h" + +#include "../../codecs/rt5640.h" + +/* Haswell ULT platforms have a Headphone and Mic jack */ +static const struct snd_soc_dapm_widget haswell_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route haswell_rt5640_map[] = { + + {"Headphones", NULL, "HPOR"}, + {"Headphones", NULL, "HPOL"}, + {"IN2P", NULL, "Mic"}, + + /* CODEC BE connections */ + {"SSP0 CODEC IN", NULL, "AIF1 Capture"}, + {"AIF1 Playback", NULL, "SSP0 CODEC OUT"}, +}; + +static int haswell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + params_set_format(params, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int haswell_rt5640_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, 12288000, + SND_SOC_CLOCK_IN); + + if (ret < 0) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + /* set correct codec filter for DAI format and clock config */ + snd_soc_component_update_bits(codec_dai->component, 0x83, 0xffff, 0x8000); + + return ret; +} + +static const struct snd_soc_ops haswell_rt5640_ops = { + .hw_params = haswell_rt5640_hw_params, +}; + +static int haswell_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_pdata *pdata = dev_get_platdata(component->dev); + struct sst_hsw *haswell = pdata->dsp; + int ret; + + /* Set ADSP SSP port settings */ + ret = sst_hsw_device_set_config(haswell, SST_HSW_DEVICE_SSP_0, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ, + SST_HSW_DEVICE_CLOCK_MASTER, 9); + if (ret < 0) { + dev_err(rtd->dev, "failed to set device config\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_dai_link haswell_rt5640_dais[] = { + /* Front End DAI links */ + { + .name = "System", + .stream_name = "System Playback/Capture", + .cpu_dai_name = "System Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = haswell_rtd_init, + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "Offload0", + .stream_name = "Offload0 Playback", + .cpu_dai_name = "Offload0 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Offload1", + .stream_name = "Offload1 Playback", + .cpu_dai_name = "Offload1 Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + }, + { + .name = "Loopback", + .stream_name = "Loopback", + .cpu_dai_name = "Loopback Pin", + .platform_name = "haswell-pcm-audio", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "Codec", + .id = 0, + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_name = "i2c-INT33CA:00", + .codec_dai_name = "rt5640-aif1", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = haswell_ssp0_fixup, + .ops = &haswell_rt5640_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +/* audio machine driver for Haswell Lynxpoint DSP + RT5640 */ +static struct snd_soc_card haswell_rt5640 = { + .name = "haswell-rt5640", + .owner = THIS_MODULE, + .dai_link = haswell_rt5640_dais, + .num_links = ARRAY_SIZE(haswell_rt5640_dais), + .dapm_widgets = haswell_widgets, + .num_dapm_widgets = ARRAY_SIZE(haswell_widgets), + .dapm_routes = haswell_rt5640_map, + .num_dapm_routes = ARRAY_SIZE(haswell_rt5640_map), + .fully_routed = true, +}; + +static int haswell_audio_probe(struct platform_device *pdev) +{ + haswell_rt5640.dev = &pdev->dev; + + return devm_snd_soc_register_card(&pdev->dev, &haswell_rt5640); +} + +static struct platform_driver haswell_audio = { + .probe = haswell_audio_probe, + .driver = { + .name = "haswell-audio", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(haswell_audio) + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Intel SST Audio for Haswell Lynxpoint"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:haswell-audio"); diff --git a/sound/soc/intel/boards/kbl_da7219_max98357a.c b/sound/soc/intel/boards/kbl_da7219_max98357a.c new file mode 100644 index 000000000..07491a0f8 --- /dev/null +++ b/sound/soc/intel/boards/kbl_da7219_max98357a.c @@ -0,0 +1,615 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2017-18 Intel Corporation. + +/* + * Intel Kabylake I2S Machine Driver with MAX98357A & DA7219 Codecs + * + * Modified from: + * Intel Kabylake I2S Machine driver supporting MAXIM98927 and + * RT5663 codecs + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/da7219.h" +#include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" +#include "../../codecs/da7219-aad.h" + +#define KBL_DIALOG_CODEC_DAI "da7219-hifi" +#define KBL_MAXIM_CODEC_DAI "HiFi" +#define MAXIM_DEV0_NAME "MX98357A:00" +#define DUAL_CHANNEL 2 +#define QUAD_CHANNEL 4 + +static struct snd_soc_card *kabylake_audio_card; +static struct snd_soc_jack skylake_hdmi[3]; + +struct kbl_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct kbl_codec_private { + struct snd_soc_jack kabylake_headset; + struct list_head hdmi_pcm_list; +}; + +enum { + KBL_DPCM_AUDIO_PB = 0, + KBL_DPCM_AUDIO_CP, + KBL_DPCM_AUDIO_DMIC_CP, + KBL_DPCM_AUDIO_HDMI1_PB, + KBL_DPCM_AUDIO_HDMI2_PB, + KBL_DPCM_AUDIO_HDMI3_PB, +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret = 0; + + codec_dai = snd_soc_card_get_codec_dai(card, KBL_DIALOG_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_MCLK, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop PLL: %d\n", ret); + } else if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL_SRM, + 0, DA7219_PLL_FREQ_OUT_98304); + if (ret) + dev_err(card->dev, "failed to start PLL: %d\n", ret); + } + + return ret; +} + +static const struct snd_kcontrol_new kabylake_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget kabylake_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("DP", NULL), + SND_SOC_DAPM_SPK("HDMI", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route kabylake_map[] = { + { "Headphone Jack", NULL, "HPL" }, + { "Headphone Jack", NULL, "HPR" }, + + /* speaker */ + { "Spk", NULL, "Speaker" }, + + /* other jacks */ + { "MIC", NULL, "Headset Mic" }, + { "DMic", NULL, "SoC DMIC" }, + + { "HDMI", NULL, "hif5 Output" }, + { "DP", NULL, "hif6 Output" }, + + /* CODEC BE connections */ + { "HiFi Playback", NULL, "ssp0 Tx" }, + { "ssp0 Tx", NULL, "codec0_out" }, + + { "Playback", NULL, "ssp1 Tx" }, + { "ssp1 Tx", NULL, "codec1_out" }, + + { "codec0_in", NULL, "ssp1 Rx" }, + { "ssp1 Rx", NULL, "Capture" }, + + /* DMIC */ + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "DMIC AIF" }, + + { "hifi1", NULL, "iDisp1 Tx" }, + { "iDisp1 Tx", NULL, "iDisp1_out" }, + { "hifi2", NULL, "iDisp2 Tx" }, + { "iDisp2 Tx", NULL, "iDisp2_out" }, + { "hifi3", NULL, "iDisp3 Tx"}, + { "iDisp3 Tx", NULL, "iDisp3_out"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = DUAL_CHANNEL; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int kabylake_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_jack *jack; + int ret; + + /* Configure sysclk for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, 24576000, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_LINEOUT, + &ctx->kabylake_headset, NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + jack = &ctx->kabylake_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + da7219_aad_jack_det(component, &ctx->kabylake_headset); + + ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + if (ret) + dev_err(rtd->dev, "SoC DMIC - Ignore suspend failed %d\n", ret); + + return ret; +} + +static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) +{ + struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct kbl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = device; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB); +} + +static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB); +} + +static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI3_PB); +} + +static int kabylake_da7219_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static unsigned int channels_quad[] = { + QUAD_CHANNEL, +}; + +static struct snd_pcm_hw_constraint_list constraints_channels_quad = { + .count = ARRAY_SIZE(channels_quad), + .list = channels_quad, + .mask = 0, +}; + +static int kbl_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * On this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops kabylake_da7219_fe_ops = { + .startup = kbl_fe_startup, +}; + +static int kabylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* + * set BE channel constraint as user FE channels + */ + + if (params_channels(params) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static int kabylake_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_min = runtime->hw.channels_max = QUAD_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels_quad); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static struct snd_soc_ops kabylake_dmic_ops = { + .startup = kabylake_dmic_startup, +}; + +static const unsigned int rates_16000[] = { + 16000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16000 = { + .count = ARRAY_SIZE(rates_16000), + .list = rates_16000, +}; + +static const unsigned int ch_mono[] = { + 1, +}; + +/* kabylake digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link kabylake_dais[] = { + /* Front End DAI links */ + [KBL_DPCM_AUDIO_PB] = { + .name = "Kbl Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = kabylake_da7219_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &kabylake_da7219_fe_ops, + }, + [KBL_DPCM_AUDIO_CP] = { + .name = "Kbl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &kabylake_da7219_fe_ops, + }, + [KBL_DPCM_AUDIO_DMIC_CP] = { + .name = "Kbl Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &kabylake_dmic_ops, + }, + [KBL_DPCM_AUDIO_HDMI1_PB] = { + .name = "Kbl HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_HDMI2_PB] = { + .name = "Kbl HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_HDMI3_PB] = { + .name = "Kbl HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "SSP0-Codec", + .id = 0, + .cpu_dai_name = "SSP0 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = MAXIM_DEV0_NAME, + .codec_dai_name = KBL_MAXIM_CODEC_DAI, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = kabylake_ssp_fixup, + .dpcm_playback = 1, + }, + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = "i2c-DLGS7219:00", + .codec_dai_name = KBL_DIALOG_CODEC_DAI, + .init = kabylake_da7219_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = kabylake_ssp_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:1f.3", + .be_hw_params_fixup = kabylake_dmic_fixup, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = kabylake_hdmi1_init, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:1f.3", + .init = kabylake_hdmi2_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:1f.3", + .init = kabylake_hdmi3_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +#define NAME_SIZE 32 +static int kabylake_card_late_probe(struct snd_soc_card *card) +{ + struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(card); + struct kbl_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &skylake_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &skylake_hdmi[i]); + if (err < 0) + return err; + + i++; + + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* kabylake audio machine driver for SPT + DA7219 */ +static struct snd_soc_card kabylake_audio_card_da7219_m98357a = { + .name = "kblda7219max", + .owner = THIS_MODULE, + .dai_link = kabylake_dais, + .num_links = ARRAY_SIZE(kabylake_dais), + .controls = kabylake_controls, + .num_controls = ARRAY_SIZE(kabylake_controls), + .dapm_widgets = kabylake_widgets, + .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets), + .dapm_routes = kabylake_map, + .num_dapm_routes = ARRAY_SIZE(kabylake_map), + .fully_routed = true, + .late_probe = kabylake_card_late_probe, +}; + +static int kabylake_audio_probe(struct platform_device *pdev) +{ + struct kbl_codec_private *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + kabylake_audio_card = + (struct snd_soc_card *)pdev->id_entry->driver_data; + + kabylake_audio_card->dev = &pdev->dev; + snd_soc_card_set_drvdata(kabylake_audio_card, ctx); + return devm_snd_soc_register_card(&pdev->dev, kabylake_audio_card); +} + +static const struct platform_device_id kbl_board_ids[] = { + { + .name = "kbl_da7219_max98357a", + .driver_data = + (kernel_ulong_t)&kabylake_audio_card_da7219_m98357a, + }, + { } +}; + +static struct platform_driver kabylake_audio = { + .probe = kabylake_audio_probe, + .driver = { + .name = "kbl_da7219_max98357a", + .pm = &snd_soc_pm_ops, + }, + .id_table = kbl_board_ids, +}; + +module_platform_driver(kabylake_audio) + +/* Module information */ +MODULE_DESCRIPTION("Audio Machine driver-DA7219 & MAX98357A in I2S mode"); +MODULE_AUTHOR("Naveen Manohar "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kbl_da7219_max98357a"); diff --git a/sound/soc/intel/boards/kbl_rt5663_max98927.c b/sound/soc/intel/boards/kbl_rt5663_max98927.c new file mode 100644 index 000000000..21a649074 --- /dev/null +++ b/sound/soc/intel/boards/kbl_rt5663_max98927.c @@ -0,0 +1,1054 @@ +/* + * Intel Kabylake I2S Machine Driver with MAXIM98927 + * and RT5663 Codecs + * + * Copyright (C) 2017, Intel Corporation. All rights reserved. + * + * Modified from: + * Intel Skylake I2S Machine driver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5663.h" +#include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" +#include +#include +#include + +#define KBL_REALTEK_CODEC_DAI "rt5663-aif" +#define KBL_MAXIM_CODEC_DAI "max98927-aif1" +#define DMIC_CH(p) p->list[p->count-1] +#define MAXIM_DEV0_NAME "i2c-MX98927:00" +#define MAXIM_DEV1_NAME "i2c-MX98927:01" + +static struct snd_soc_card *kabylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; +static struct snd_soc_jack skylake_hdmi[3]; + +struct kbl_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct kbl_rt5663_private { + struct snd_soc_jack kabylake_headset; + struct list_head hdmi_pcm_list; + struct clk *mclk; + struct clk *sclk; +}; + +enum { + KBL_DPCM_AUDIO_PB = 0, + KBL_DPCM_AUDIO_CP, + KBL_DPCM_AUDIO_HS_PB, + KBL_DPCM_AUDIO_ECHO_REF_CP, + KBL_DPCM_AUDIO_REF_CP, + KBL_DPCM_AUDIO_DMIC_CP, + KBL_DPCM_AUDIO_HDMI1_PB, + KBL_DPCM_AUDIO_HDMI2_PB, + KBL_DPCM_AUDIO_HDMI3_PB, +}; + +static const struct snd_kcontrol_new kabylake_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct kbl_rt5663_private *priv = snd_soc_card_get_drvdata(card); + int ret = 0; + + /* + * MCLK/SCLK need to be ON early for a successful synchronization of + * codec internal clock. And the clocks are turned off during + * POST_PMD after the stream is stopped. + */ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable MCLK */ + ret = clk_set_rate(priv->mclk, 24000000); + if (ret < 0) { + dev_err(card->dev, "Can't set rate for mclk, err: %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, "Can't enable mclk, err: %d\n", ret); + return ret; + } + + /* Enable SCLK */ + ret = clk_set_rate(priv->sclk, 3072000); + if (ret < 0) { + dev_err(card->dev, "Can't set rate for sclk, err: %d\n", + ret); + clk_disable_unprepare(priv->mclk); + return ret; + } + + ret = clk_prepare_enable(priv->sclk); + if (ret < 0) { + dev_err(card->dev, "Can't enable sclk, err: %d\n", ret); + clk_disable_unprepare(priv->mclk); + } + break; + case SND_SOC_DAPM_POST_PMD: + clk_disable_unprepare(priv->mclk); + clk_disable_unprepare(priv->sclk); + break; + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget kabylake_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route kabylake_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* speaker */ + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, + + /* other jacks */ + { "Headset Mic", NULL, "Platform Clock" }, + { "IN1P", NULL, "Headset Mic" }, + { "IN1N", NULL, "Headset Mic" }, + { "DMic", NULL, "SoC DMIC" }, + + /* CODEC BE connections */ + { "Left HiFi Playback", NULL, "ssp0 Tx" }, + { "Right HiFi Playback", NULL, "ssp0 Tx" }, + { "ssp0 Tx", NULL, "spk_out" }, + + { "AIF Playback", NULL, "ssp1 Tx" }, + { "ssp1 Tx", NULL, "codec1_out" }, + + { "hs_in", NULL, "ssp1 Rx" }, + { "ssp1 Rx", NULL, "AIF Capture" }, + + /* IV feedback path */ + { "codec0_fb_in", NULL, "ssp0 Rx"}, + { "ssp0 Rx", NULL, "Left HiFi Capture" }, + { "ssp0 Rx", NULL, "Right HiFi Capture" }, + + /* DMIC */ + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "DMIC AIF" }, + + { "hifi3", NULL, "iDisp3 Tx"}, + { "iDisp3 Tx", NULL, "iDisp3_out"}, + { "hifi2", NULL, "iDisp2 Tx"}, + { "iDisp2 Tx", NULL, "iDisp2_out"}, + { "hifi1", NULL, "iDisp1 Tx"}, + { "iDisp1 Tx", NULL, "iDisp1_out"}, +}; + +enum { + KBL_DPCM_AUDIO_5663_PB = 0, + KBL_DPCM_AUDIO_5663_CP, + KBL_DPCM_AUDIO_5663_HDMI1_PB, + KBL_DPCM_AUDIO_5663_HDMI2_PB, +}; + +static const struct snd_kcontrol_new kabylake_5663_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget kabylake_5663_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("DP", NULL), + SND_SOC_DAPM_SPK("HDMI", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route kabylake_5663_map[] = { + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* other jacks */ + { "Headset Mic", NULL, "Platform Clock" }, + { "IN1P", NULL, "Headset Mic" }, + { "IN1N", NULL, "Headset Mic" }, + + { "HDMI", NULL, "hif5 Output" }, + { "DP", NULL, "hif6 Output" }, + + /* CODEC BE connections */ + { "AIF Playback", NULL, "ssp1 Tx" }, + { "ssp1 Tx", NULL, "codec1_out" }, + + { "codec0_in", NULL, "ssp1 Rx" }, + { "ssp1 Rx", NULL, "AIF Capture" }, + + { "hifi2", NULL, "iDisp2 Tx"}, + { "iDisp2 Tx", NULL, "iDisp2_out"}, + { "hifi1", NULL, "iDisp1 Tx"}, + { "iDisp1 Tx", NULL, "iDisp1_out"}, +}; + +static struct snd_soc_codec_conf max98927_codec_conf[] = { + { + .dev_name = MAXIM_DEV0_NAME, + .name_prefix = "Right", + }, + { + .dev_name = MAXIM_DEV1_NAME, + .name_prefix = "Left", + }, +}; + +static struct snd_soc_dai_link_component max98927_codec_components[] = { + { /* Left */ + .name = MAXIM_DEV0_NAME, + .dai_name = KBL_MAXIM_CODEC_DAI, + }, + { /* Right */ + .name = MAXIM_DEV1_NAME, + .dai_name = KBL_MAXIM_CODEC_DAI, + }, +}; + +static int kabylake_rt5663_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + ret = snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + if (ret) { + dev_err(rtd->dev, "Ref Cap ignore suspend failed %d\n", ret); + return ret; + } + + return ret; +} + +static int kabylake_rt5663_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_jack *jack; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(kabylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &ctx->kabylake_headset, + NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + jack = &ctx->kabylake_headset; + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + snd_soc_component_set_jack(component, &ctx->kabylake_headset, NULL); + + return ret; +} + +static int kabylake_rt5663_max98927_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + + ret = kabylake_rt5663_codec_init(rtd); + if (ret) + return ret; + + ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + if (ret) { + dev_err(rtd->dev, "SoC DMIC ignore suspend failed %d\n", ret); + return ret; + } + + return ret; +} + +static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) +{ + struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct kbl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = device; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB); +} + +static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB); +} + +static int kabylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI3_PB); +} + +static int kabylake_5663_hdmi1_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_5663_HDMI1_PB); +} + +static int kabylake_5663_hdmi2_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_5663_HDMI2_PB); +} + +static unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int kbl_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * On this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops kabylake_rt5663_fe_ops = { + .startup = kbl_fe_startup, +}; + +static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_soc_dpcm *dpcm = container_of( + params, struct snd_soc_dpcm, hw_params); + struct snd_soc_dai_link *fe_dai_link = dpcm->fe->dai_link; + struct snd_soc_dai_link *be_dai_link = dpcm->be->dai_link; + + /* + * The ADSP will convert the FE rate to 48k, stereo, 24 bit + */ + if (!strcmp(fe_dai_link->name, "Kbl Audio Port") || + !strcmp(fe_dai_link->name, "Kbl Audio Headset Playback") || + !strcmp(fe_dai_link->name, "Kbl Audio Capture Port")) { + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + } + /* + * The speaker on the SSP0 supports S16_LE and not S24_LE. + * thus changing the mask here + */ + if (!strcmp(be_dai_link->name, "SSP0-Codec")) + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static int kabylake_rt5663_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* use ASRC for internal clocks, as PLL rate isn't multiple of BCLK */ + rt5663_sel_asrc_clk_src(codec_dai->component, + RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER, + RT5663_CLK_SEL_I2S1_ASRC); + + ret = snd_soc_dai_set_sysclk(codec_dai, + RT5663_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + return ret; +} + +static struct snd_soc_ops kabylake_rt5663_ops = { + .hw_params = kabylake_rt5663_hw_params, +}; + +static int kabylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0, j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + + if (!strcmp(codec_dai->component->name, MAXIM_DEV0_NAME)) { + /* + * Use channel 4 and 5 for the first amp + */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16); + if (ret < 0) { + dev_err(rtd->dev, "set TDM slot err:%d\n", ret); + return ret; + } + } + if (!strcmp(codec_dai->component->name, MAXIM_DEV1_NAME)) { + /* + * Use channel 6 and 7 for the second amp + */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16); + if (ret < 0) { + dev_err(rtd->dev, "set TDM slot err:%d\n", ret); + return ret; + } + } + } + return ret; +} + +static struct snd_soc_ops kabylake_ssp0_ops = { + .hw_params = kabylake_ssp0_hw_params, +}; + +static unsigned int channels_dmic[] = { + 2, 4, +}; + +static struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + +static int kabylake_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = DMIC_CH(dmic_constraints); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dmic_constraints); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static struct snd_soc_ops kabylake_dmic_ops = { + .startup = kabylake_dmic_startup, +}; + +static unsigned int rates_16000[] = { + 16000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16000 = { + .count = ARRAY_SIZE(rates_16000), + .list = rates_16000, +}; + +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + +static int kabylake_refcap_startup(struct snd_pcm_substream *substream) +{ + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_16000); +} + +static struct snd_soc_ops skylaye_refcap_ops = { + .startup = kabylake_refcap_startup, +}; + +/* kabylake digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link kabylake_dais[] = { + /* Front End DAI links */ + [KBL_DPCM_AUDIO_PB] = { + .name = "Kbl Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = kabylake_rt5663_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &kabylake_rt5663_fe_ops, + }, + [KBL_DPCM_AUDIO_CP] = { + .name = "Kbl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &kabylake_rt5663_fe_ops, + }, + [KBL_DPCM_AUDIO_HS_PB] = { + .name = "Kbl Audio Headset Playback", + .stream_name = "Headset Audio", + .cpu_dai_name = "System Pin2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_ECHO_REF_CP] = { + .name = "Kbl Audio Echo Reference cap", + .stream_name = "Echoreference Capture", + .cpu_dai_name = "Echoref Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .capture_only = 1, + .nonatomic = 1, + }, + [KBL_DPCM_AUDIO_REF_CP] = { + .name = "Kbl Audio Reference cap", + .stream_name = "Wake on Voice", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &skylaye_refcap_ops, + }, + [KBL_DPCM_AUDIO_DMIC_CP] = { + .name = "Kbl Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &kabylake_dmic_ops, + }, + [KBL_DPCM_AUDIO_HDMI1_PB] = { + .name = "Kbl HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_HDMI2_PB] = { + .name = "Kbl HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_HDMI3_PB] = { + .name = "Kbl HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "SSP0-Codec", + .id = 0, + .cpu_dai_name = "SSP0 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codecs = max98927_codec_components, + .num_codecs = ARRAY_SIZE(max98927_codec_components), + .dai_fmt = SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = kabylake_ssp_fixup, + .dpcm_playback = 1, + .ops = &kabylake_ssp0_ops, + }, + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = "i2c-10EC5663:00", + .codec_dai_name = KBL_REALTEK_CODEC_DAI, + .init = kabylake_rt5663_max98927_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = kabylake_ssp_fixup, + .ops = &kabylake_rt5663_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:1f.3", + .be_hw_params_fixup = kabylake_dmic_fixup, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = kabylake_hdmi1_init, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:1f.3", + .init = kabylake_hdmi2_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:1f.3", + .init = kabylake_hdmi3_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +static struct snd_soc_dai_link kabylake_5663_dais[] = { + /* Front End DAI links */ + [KBL_DPCM_AUDIO_5663_PB] = { + .name = "Kbl Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &kabylake_rt5663_fe_ops, + }, + [KBL_DPCM_AUDIO_5663_CP] = { + .name = "Kbl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &kabylake_rt5663_fe_ops, + }, + [KBL_DPCM_AUDIO_5663_HDMI1_PB] = { + .name = "Kbl HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_5663_HDMI2_PB] = { + .name = "Kbl HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + + /* Back End DAI links */ + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 0, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = "i2c-10EC5663:00", + .codec_dai_name = KBL_REALTEK_CODEC_DAI, + .init = kabylake_rt5663_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = kabylake_ssp_fixup, + .ops = &kabylake_rt5663_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "iDisp1", + .id = 1, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = kabylake_5663_hdmi1_init, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 2, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:1f.3", + .init = kabylake_5663_hdmi2_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +#define NAME_SIZE 32 +static int kabylake_card_late_probe(struct snd_soc_card *card) +{ + struct kbl_rt5663_private *ctx = snd_soc_card_get_drvdata(card); + struct kbl_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &skylake_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &skylake_hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* kabylake audio machine driver for SPT + RT5663 */ +static struct snd_soc_card kabylake_audio_card_rt5663_m98927 = { + .name = "kblrt5663max", + .owner = THIS_MODULE, + .dai_link = kabylake_dais, + .num_links = ARRAY_SIZE(kabylake_dais), + .controls = kabylake_controls, + .num_controls = ARRAY_SIZE(kabylake_controls), + .dapm_widgets = kabylake_widgets, + .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets), + .dapm_routes = kabylake_map, + .num_dapm_routes = ARRAY_SIZE(kabylake_map), + .codec_conf = max98927_codec_conf, + .num_configs = ARRAY_SIZE(max98927_codec_conf), + .fully_routed = true, + .late_probe = kabylake_card_late_probe, +}; + +/* kabylake audio machine driver for RT5663 */ +static struct snd_soc_card kabylake_audio_card_rt5663 = { + .name = "kblrt5663", + .owner = THIS_MODULE, + .dai_link = kabylake_5663_dais, + .num_links = ARRAY_SIZE(kabylake_5663_dais), + .controls = kabylake_5663_controls, + .num_controls = ARRAY_SIZE(kabylake_5663_controls), + .dapm_widgets = kabylake_5663_widgets, + .num_dapm_widgets = ARRAY_SIZE(kabylake_5663_widgets), + .dapm_routes = kabylake_5663_map, + .num_dapm_routes = ARRAY_SIZE(kabylake_5663_map), + .fully_routed = true, + .late_probe = kabylake_card_late_probe, +}; + +static int kabylake_audio_probe(struct platform_device *pdev) +{ + struct kbl_rt5663_private *ctx; + struct skl_machine_pdata *pdata; + int ret; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + kabylake_audio_card = + (struct snd_soc_card *)pdev->id_entry->driver_data; + + kabylake_audio_card->dev = &pdev->dev; + snd_soc_card_set_drvdata(kabylake_audio_card, ctx); + + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + + ctx->mclk = devm_clk_get(&pdev->dev, "ssp1_mclk"); + if (IS_ERR(ctx->mclk)) { + ret = PTR_ERR(ctx->mclk); + if (ret == -ENOENT) { + dev_info(&pdev->dev, + "Failed to get ssp1_sclk, defer probe\n"); + return -EPROBE_DEFER; + } + + dev_err(&pdev->dev, "Failed to get ssp1_mclk with err:%d\n", + ret); + return ret; + } + + ctx->sclk = devm_clk_get(&pdev->dev, "ssp1_sclk"); + if (IS_ERR(ctx->sclk)) { + ret = PTR_ERR(ctx->sclk); + if (ret == -ENOENT) { + dev_info(&pdev->dev, + "Failed to get ssp1_sclk, defer probe\n"); + return -EPROBE_DEFER; + } + + dev_err(&pdev->dev, "Failed to get ssp1_sclk with err:%d\n", + ret); + return ret; + } + + return devm_snd_soc_register_card(&pdev->dev, kabylake_audio_card); +} + +static const struct platform_device_id kbl_board_ids[] = { + { + .name = "kbl_rt5663", + .driver_data = (kernel_ulong_t)&kabylake_audio_card_rt5663, + }, + { + .name = "kbl_rt5663_m98927", + .driver_data = + (kernel_ulong_t)&kabylake_audio_card_rt5663_m98927, + }, + { } +}; + +static struct platform_driver kabylake_audio = { + .probe = kabylake_audio_probe, + .driver = { + .name = "kbl_rt5663_m98927", + .pm = &snd_soc_pm_ops, + }, + .id_table = kbl_board_ids, +}; + +module_platform_driver(kabylake_audio) + +/* Module information */ +MODULE_DESCRIPTION("Audio Machine driver-RT5663 & MAX98927 in I2S mode"); +MODULE_AUTHOR("Naveen M "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kbl_rt5663"); +MODULE_ALIAS("platform:kbl_rt5663_m98927"); diff --git a/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c new file mode 100644 index 000000000..f36e33a14 --- /dev/null +++ b/sound/soc/intel/boards/kbl_rt5663_rt5514_max98927.c @@ -0,0 +1,694 @@ +/* + * Intel Kabylake I2S Machine Driver with MAXIM98927 + * RT5514 and RT5663 Codecs + * + * Copyright (C) 2017, Intel Corporation. All rights reserved. + * + * Modified from: + * Intel Kabylake I2S Machine driver supporting MAXIM98927 and + * RT5663 codecs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5514.h" +#include "../../codecs/rt5663.h" +#include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" + +#define KBL_REALTEK_CODEC_DAI "rt5663-aif" +#define KBL_REALTEK_DMIC_CODEC_DAI "rt5514-aif1" +#define KBL_MAXIM_CODEC_DAI "max98927-aif1" +#define MAXIM_DEV0_NAME "i2c-MX98927:00" +#define MAXIM_DEV1_NAME "i2c-MX98927:01" +#define RT5514_DEV_NAME "i2c-10EC5514:00" +#define RT5663_DEV_NAME "i2c-10EC5663:00" +#define RT5514_AIF1_BCLK_FREQ (48000 * 8 * 16) +#define RT5514_AIF1_SYSCLK_FREQ 12288000 +#define NAME_SIZE 32 + +#define DMIC_CH(p) p->list[p->count-1] + + +static struct snd_soc_card kabylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; + +struct kbl_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct kbl_codec_private { + struct snd_soc_jack kabylake_headset; + struct list_head hdmi_pcm_list; + struct snd_soc_jack kabylake_hdmi[2]; +}; + +enum { + KBL_DPCM_AUDIO_PB = 0, + KBL_DPCM_AUDIO_CP, + KBL_DPCM_AUDIO_HS_PB, + KBL_DPCM_AUDIO_ECHO_REF_CP, + KBL_DPCM_AUDIO_DMIC_CP, + KBL_DPCM_AUDIO_RT5514_DSP, + KBL_DPCM_AUDIO_HDMI1_PB, + KBL_DPCM_AUDIO_HDMI2_PB, +}; + +static const struct snd_kcontrol_new kabylake_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), + SOC_DAPM_PIN_SWITCH("DMIC"), +}; + +static const struct snd_soc_dapm_widget kabylake_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + +}; + +static const struct snd_soc_dapm_route kabylake_map[] = { + /* Headphones */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* speaker */ + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, + + /* other jacks */ + { "IN1P", NULL, "Headset Mic" }, + { "IN1N", NULL, "Headset Mic" }, + + /* CODEC BE connections */ + { "Left HiFi Playback", NULL, "ssp0 Tx" }, + { "Right HiFi Playback", NULL, "ssp0 Tx" }, + { "ssp0 Tx", NULL, "spk_out" }, + + { "AIF Playback", NULL, "ssp1 Tx" }, + { "ssp1 Tx", NULL, "codec1_out" }, + + { "hs_in", NULL, "ssp1 Rx" }, + { "ssp1 Rx", NULL, "AIF Capture" }, + + { "codec1_in", NULL, "ssp0 Rx" }, + { "ssp0 Rx", NULL, "AIF1 Capture" }, + + /* IV feedback path */ + { "codec0_fb_in", NULL, "ssp0 Rx"}, + { "ssp0 Rx", NULL, "Left HiFi Capture" }, + { "ssp0 Rx", NULL, "Right HiFi Capture" }, + + /* DMIC */ + { "DMIC1L", NULL, "DMIC" }, + { "DMIC1R", NULL, "DMIC" }, + { "DMIC2L", NULL, "DMIC" }, + { "DMIC2R", NULL, "DMIC" }, + + { "hifi2", NULL, "iDisp2 Tx" }, + { "iDisp2 Tx", NULL, "iDisp2_out" }, + { "hifi1", NULL, "iDisp1 Tx" }, + { "iDisp1 Tx", NULL, "iDisp1_out" }, +}; + +static struct snd_soc_codec_conf max98927_codec_conf[] = { + { + .dev_name = MAXIM_DEV0_NAME, + .name_prefix = "Right", + }, + { + .dev_name = MAXIM_DEV1_NAME, + .name_prefix = "Left", + }, +}; + +static struct snd_soc_dai_link_component ssp0_codec_components[] = { + { /* Left */ + .name = MAXIM_DEV0_NAME, + .dai_name = KBL_MAXIM_CODEC_DAI, + }, + { /* Right */ + .name = MAXIM_DEV1_NAME, + .dai_name = KBL_MAXIM_CODEC_DAI, + }, + { /*dmic */ + .name = RT5514_DEV_NAME, + .dai_name = KBL_REALTEK_DMIC_CODEC_DAI, + }, +}; + +static int kabylake_rt5663_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + int ret; + + dapm = snd_soc_component_get_dapm(component); + ret = snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + if (ret) + dev_err(rtd->dev, "Ref Cap -Ignore suspend failed = %d\n", ret); + + return ret; +} + +static int kabylake_rt5663_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_jack *jack; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(&kabylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &ctx->kabylake_headset, + NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + jack = &ctx->kabylake_headset; + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + snd_soc_component_set_jack(component, &ctx->kabylake_headset, NULL); + + ret = snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "DMIC"); + if (ret) + dev_err(rtd->dev, "DMIC - Ignore suspend failed = %d\n", ret); + + return ret; +} + +static int kabylake_hdmi_init(struct snd_soc_pcm_runtime *rtd, int device) +{ + struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct kbl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = device; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int kabylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI1_PB); +} + +static int kabylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) +{ + return kabylake_hdmi_init(rtd, KBL_DPCM_AUDIO_HDMI2_PB); +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int kbl_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * On this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops kabylake_rt5663_fe_ops = { + .startup = kbl_fe_startup, +}; + +static int kabylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_soc_dpcm *dpcm = container_of( + params, struct snd_soc_dpcm, hw_params); + struct snd_soc_dai_link *fe_dai_link = dpcm->fe->dai_link; + struct snd_soc_dai_link *be_dai_link = dpcm->be->dai_link; + + /* + * The ADSP will convert the FE rate to 48k, stereo, 24 bit + */ + if (!strcmp(fe_dai_link->name, "Kbl Audio Port") || + !strcmp(fe_dai_link->name, "Kbl Audio Headset Playback") || + !strcmp(fe_dai_link->name, "Kbl Audio Capture Port")) { + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + } else if (!strcmp(fe_dai_link->name, "Kbl Audio DMIC cap")) { + if (params_channels(params) == 2 || + DMIC_CH(dmic_constraints) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + } + /* + * The speaker on the SSP0 supports S16_LE and not S24_LE. + * thus changing the mask here + */ + if (!strcmp(be_dai_link->name, "SSP0-Codec")) + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static int kabylake_rt5663_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* use ASRC for internal clocks, as PLL rate isn't multiple of BCLK */ + rt5663_sel_asrc_clk_src(codec_dai->component, + RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER, + RT5663_CLK_SEL_I2S1_ASRC); + + ret = snd_soc_dai_set_sysclk(codec_dai, + RT5663_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + return ret; +} + +static struct snd_soc_ops kabylake_rt5663_ops = { + .hw_params = kabylake_rt5663_hw_params, +}; + +static int kabylake_ssp0_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret = 0, j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + + if (!strcmp(codec_dai->component->name, RT5514_DEV_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0, 8, 16); + if (ret < 0) { + dev_err(rtd->dev, "set TDM slot err:%d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + RT5514_SCLK_S_MCLK, 24576000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(rtd->dev, "set sysclk err: %d\n", ret); + return ret; + } + } + if (!strcmp(codec_dai->component->name, MAXIM_DEV0_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16); + if (ret < 0) { + dev_err(rtd->dev, "DEV0 TDM slot err:%d\n", ret); + return ret; + } + } + + if (!strcmp(codec_dai->component->name, MAXIM_DEV1_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16); + if (ret < 0) { + dev_err(rtd->dev, "DEV1 TDM slot err:%d\n", ret); + return ret; + } + } + } + return ret; +} + +static struct snd_soc_ops kabylake_ssp0_ops = { + .hw_params = kabylake_ssp0_hw_params, +}; + +static const unsigned int channels_dmic[] = { + 4, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + +static int kabylake_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = DMIC_CH(dmic_constraints); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dmic_constraints); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static struct snd_soc_ops kabylake_dmic_ops = { + .startup = kabylake_dmic_startup, +}; + +/* kabylake digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link kabylake_dais[] = { + /* Front End DAI links */ + [KBL_DPCM_AUDIO_PB] = { + .name = "Kbl Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = kabylake_rt5663_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &kabylake_rt5663_fe_ops, + }, + [KBL_DPCM_AUDIO_CP] = { + .name = "Kbl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &kabylake_rt5663_fe_ops, + }, + [KBL_DPCM_AUDIO_HS_PB] = { + .name = "Kbl Audio Headset Playback", + .stream_name = "Headset Audio", + .cpu_dai_name = "System Pin2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_ECHO_REF_CP] = { + .name = "Kbl Audio Echo Reference cap", + .stream_name = "Echoreference Capture", + .cpu_dai_name = "Echoref Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .capture_only = 1, + .nonatomic = 1, + }, + [KBL_DPCM_AUDIO_RT5514_DSP] = { + .name = "rt5514 dsp", + .stream_name = "Wake on Voice", + .cpu_dai_name = "spi-PRP0001:00", + .platform_name = "spi-PRP0001:00", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + }, + [KBL_DPCM_AUDIO_DMIC_CP] = { + .name = "Kbl Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &kabylake_dmic_ops, + }, + [KBL_DPCM_AUDIO_HDMI1_PB] = { + .name = "Kbl HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [KBL_DPCM_AUDIO_HDMI2_PB] = { + .name = "Kbl HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + /* Back End DAI links */ + /* single Back end dai for both max speakers and dmic */ + { + /* SSP0 - Codec */ + .name = "SSP0-Codec", + .id = 0, + .cpu_dai_name = "SSP0 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codecs = ssp0_codec_components, + .num_codecs = ARRAY_SIZE(ssp0_codec_components), + .dai_fmt = SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = kabylake_ssp_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &kabylake_ssp0_ops, + }, + { + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = RT5663_DEV_NAME, + .codec_dai_name = KBL_REALTEK_CODEC_DAI, + .init = kabylake_rt5663_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = kabylake_ssp_fixup, + .ops = &kabylake_rt5663_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = kabylake_hdmi1_init, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:1f.3", + .init = kabylake_hdmi2_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +static int kabylake_card_late_probe(struct snd_soc_card *card) +{ + struct kbl_codec_private *ctx = snd_soc_card_get_drvdata(card); + struct kbl_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP,pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &ctx->kabylake_hdmi[i], + NULL, 0); + + if (err) + return err; + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &ctx->kabylake_hdmi[i]); + if (err < 0) + return err; + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* + * kabylake audio machine driver for MAX98927 + RT5514 + RT5663 + */ +static struct snd_soc_card kabylake_audio_card = { + .name = "kbl_r5514_5663_max", + .owner = THIS_MODULE, + .dai_link = kabylake_dais, + .num_links = ARRAY_SIZE(kabylake_dais), + .controls = kabylake_controls, + .num_controls = ARRAY_SIZE(kabylake_controls), + .dapm_widgets = kabylake_widgets, + .num_dapm_widgets = ARRAY_SIZE(kabylake_widgets), + .dapm_routes = kabylake_map, + .num_dapm_routes = ARRAY_SIZE(kabylake_map), + .codec_conf = max98927_codec_conf, + .num_configs = ARRAY_SIZE(max98927_codec_conf), + .fully_routed = true, + .late_probe = kabylake_card_late_probe, +}; + +static int kabylake_audio_probe(struct platform_device *pdev) +{ + struct kbl_codec_private *ctx; + struct skl_machine_pdata *pdata; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + kabylake_audio_card.dev = &pdev->dev; + snd_soc_card_set_drvdata(&kabylake_audio_card, ctx); + + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + + return devm_snd_soc_register_card(&pdev->dev, &kabylake_audio_card); +} + +static const struct platform_device_id kbl_board_ids[] = { + { .name = "kbl_r5514_5663_max" }, + { } +}; + +static struct platform_driver kabylake_audio = { + .probe = kabylake_audio_probe, + .driver = { + .name = "kbl_r5514_5663_max", + .pm = &snd_soc_pm_ops, + }, + .id_table = kbl_board_ids, +}; + +module_platform_driver(kabylake_audio) + +/* Module information */ +MODULE_DESCRIPTION("Audio Machine driver-RT5663 RT5514 & MAX98927"); +MODULE_AUTHOR("Harsha Priya "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kbl_r5514_5663_max"); diff --git a/sound/soc/intel/boards/skl_nau88l25_max98357a.c b/sound/soc/intel/boards/skl_nau88l25_max98357a.c new file mode 100644 index 000000000..d31482b8c --- /dev/null +++ b/sound/soc/intel/boards/skl_nau88l25_max98357a.c @@ -0,0 +1,685 @@ +/* + * Intel Skylake I2S Machine Driver with MAXIM98357A + * and NAU88L25 + * + * Copyright (C) 2015, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/nau8825.h" +#include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" + +#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" +#define SKL_MAXIM_CODEC_DAI "HiFi" +#define DMIC_CH(p) p->list[p->count-1] + +static struct snd_soc_jack skylake_headset; +static struct snd_soc_card skylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; +static struct snd_soc_jack skylake_hdmi[3]; + +struct skl_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct skl_nau8825_private { + struct list_head hdmi_pcm_list; +}; + +enum { + SKL_DPCM_AUDIO_PB = 0, + SKL_DPCM_AUDIO_CP, + SKL_DPCM_AUDIO_REF_CP, + SKL_DPCM_AUDIO_DMIC_CP, + SKL_DPCM_AUDIO_HDMI1_PB, + SKL_DPCM_AUDIO_HDMI2_PB, + SKL_DPCM_AUDIO_HDMI3_PB, +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found; Unable to set platform clock\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, + NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return -EIO; + } + } else { + ret = snd_soc_dai_set_sysclk(codec_dai, + NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return -EIO; + } + } + + return ret; +} + +static const struct snd_kcontrol_new skylake_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget skylake_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("DP1", NULL), + SND_SOC_DAPM_SPK("DP2", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route skylake_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* speaker */ + { "Spk", NULL, "Speaker" }, + + /* other jacks */ + { "MIC", NULL, "Headset Mic" }, + { "DMic", NULL, "SoC DMIC" }, + + /* CODEC BE connections */ + { "HiFi Playback", NULL, "ssp0 Tx" }, + { "ssp0 Tx", NULL, "codec0_out" }, + + { "Playback", NULL, "ssp1 Tx" }, + { "ssp1 Tx", NULL, "codec1_out" }, + + { "codec0_in", NULL, "ssp1 Rx" }, + { "ssp1 Rx", NULL, "Capture" }, + + /* DMIC */ + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "DMIC AIF" }, + + { "hifi3", NULL, "iDisp3 Tx"}, + { "iDisp3 Tx", NULL, "iDisp3_out"}, + { "hifi2", NULL, "iDisp2 Tx"}, + { "iDisp2 Tx", NULL, "iDisp2_out"}, + { "hifi1", NULL, "iDisp1 Tx"}, + { "iDisp1 Tx", NULL, "iDisp1_out"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static int skylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_component *component = rtd->codec_dai->component; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset, + NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + nau8825_enable_jack_detect(component, &skylake_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + + return ret; +} + +static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) +{ + struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct skl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = SKL_DPCM_AUDIO_HDMI1_PB; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) +{ + struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct skl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = SKL_DPCM_AUDIO_HDMI2_PB; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) +{ + struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct skl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = SKL_DPCM_AUDIO_HDMI3_PB; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int skl_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * On this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops skylake_nau8825_fe_ops = { + .startup = skl_fe_startup, +}; + +static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, + NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops skylake_nau8825_ops = { + .hw_params = skylake_nau8825_hw_params, +}; + +static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static const unsigned int channels_dmic[] = { + 2, 4, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + +static int skylake_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = DMIC_CH(dmic_constraints); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dmic_constraints); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static const struct snd_soc_ops skylake_dmic_ops = { + .startup = skylake_dmic_startup, +}; + +static const unsigned int rates_16000[] = { + 16000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16000 = { + .count = ARRAY_SIZE(rates_16000), + .list = rates_16000, +}; + +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + +static int skylake_refcap_startup(struct snd_pcm_substream *substream) +{ + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_16000); +} + +static const struct snd_soc_ops skylaye_refcap_ops = { + .startup = skylake_refcap_startup, +}; + +/* skylake digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link skylake_dais[] = { + /* Front End DAI links */ + [SKL_DPCM_AUDIO_PB] = { + .name = "Skl Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = skylake_nau8825_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &skylake_nau8825_fe_ops, + }, + [SKL_DPCM_AUDIO_CP] = { + .name = "Skl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &skylake_nau8825_fe_ops, + }, + [SKL_DPCM_AUDIO_REF_CP] = { + .name = "Skl Audio Reference cap", + .stream_name = "Wake on Voice", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &skylaye_refcap_ops, + }, + [SKL_DPCM_AUDIO_DMIC_CP] = { + .name = "Skl Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &skylake_dmic_ops, + }, + [SKL_DPCM_AUDIO_HDMI1_PB] = { + .name = "Skl HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [SKL_DPCM_AUDIO_HDMI2_PB] = { + .name = "Skl HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [SKL_DPCM_AUDIO_HDMI3_PB] = { + .name = "Skl HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "SSP0-Codec", + .id = 0, + .cpu_dai_name = "SSP0 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = "MX98357A:00", + .codec_dai_name = SKL_MAXIM_CODEC_DAI, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = skylake_ssp_fixup, + .dpcm_playback = 1, + }, + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = "i2c-10508825:00", + .codec_dai_name = SKL_NUVOTON_CODEC_DAI, + .init = skylake_nau8825_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = skylake_ssp_fixup, + .ops = &skylake_nau8825_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:1f.3", + .be_hw_params_fixup = skylake_dmic_fixup, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = skylake_hdmi1_init, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:1f.3", + .init = skylake_hdmi2_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:1f.3", + .init = skylake_hdmi3_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +#define NAME_SIZE 32 +static int skylake_card_late_probe(struct snd_soc_card *card) +{ + struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(card); + struct skl_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, + &skylake_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &skylake_hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* skylake audio machine driver for SPT + NAU88L25 */ +static struct snd_soc_card skylake_audio_card = { + .name = "sklnau8825max", + .owner = THIS_MODULE, + .dai_link = skylake_dais, + .num_links = ARRAY_SIZE(skylake_dais), + .controls = skylake_controls, + .num_controls = ARRAY_SIZE(skylake_controls), + .dapm_widgets = skylake_widgets, + .num_dapm_widgets = ARRAY_SIZE(skylake_widgets), + .dapm_routes = skylake_map, + .num_dapm_routes = ARRAY_SIZE(skylake_map), + .fully_routed = true, + .late_probe = skylake_card_late_probe, +}; + +static int skylake_audio_probe(struct platform_device *pdev) +{ + struct skl_nau8825_private *ctx; + struct skl_machine_pdata *pdata; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + skylake_audio_card.dev = &pdev->dev; + snd_soc_card_set_drvdata(&skylake_audio_card, ctx); + + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + + return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card); +} + +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_n88l25_m98357a" }, + { .name = "kbl_n88l25_m98357a" }, + { } +}; + +static struct platform_driver skylake_audio = { + .probe = skylake_audio_probe, + .driver = { + .name = "skl_n88l25_m98357a", + .pm = &snd_soc_pm_ops, + }, + .id_table = skl_board_ids, +}; + +module_platform_driver(skylake_audio) + +/* Module information */ +MODULE_DESCRIPTION("Audio Machine driver-NAU88L25 & MAX98357A in I2S mode"); +MODULE_AUTHOR("Rohit Ainapure +#include +#include +#include +#include +#include +#include +#include "../../codecs/nau8825.h" +#include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" + +#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" +#define SKL_SSM_CODEC_DAI "ssm4567-hifi" +#define DMIC_CH(p) p->list[p->count-1] + +static struct snd_soc_jack skylake_headset; +static struct snd_soc_card skylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; +static struct snd_soc_jack skylake_hdmi[3]; + +struct skl_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct skl_nau88125_private { + struct list_head hdmi_pcm_list; +}; +enum { + SKL_DPCM_AUDIO_PB = 0, + SKL_DPCM_AUDIO_CP, + SKL_DPCM_AUDIO_REF_CP, + SKL_DPCM_AUDIO_DMIC_CP, + SKL_DPCM_AUDIO_HDMI1_PB, + SKL_DPCM_AUDIO_HDMI2_PB, + SKL_DPCM_AUDIO_HDMI3_PB, +}; + +static const struct snd_kcontrol_new skylake_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Left Speaker"), + SOC_DAPM_PIN_SWITCH("Right Speaker"), +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, + NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return -EIO; + } + } else { + ret = snd_soc_dai_set_sysclk(codec_dai, + NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return -EIO; + } + } + return ret; +} + +static const struct snd_soc_dapm_widget skylake_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Left Speaker", NULL), + SND_SOC_DAPM_SPK("Right Speaker", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("DP1", NULL), + SND_SOC_DAPM_SPK("DP2", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route skylake_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HPOL"}, + {"Headphone Jack", NULL, "HPOR"}, + + /* speaker */ + {"Left Speaker", NULL, "Left OUT"}, + {"Right Speaker", NULL, "Right OUT"}, + + /* other jacks */ + {"MIC", NULL, "Headset Mic"}, + {"DMic", NULL, "SoC DMIC"}, + + /* CODEC BE connections */ + { "Left Playback", NULL, "ssp0 Tx"}, + { "Right Playback", NULL, "ssp0 Tx"}, + { "ssp0 Tx", NULL, "codec0_out"}, + + /* IV feedback path */ + { "codec0_lp_in", NULL, "ssp0 Rx"}, + { "ssp0 Rx", NULL, "Left Capture Sense" }, + { "ssp0 Rx", NULL, "Right Capture Sense" }, + + { "Playback", NULL, "ssp1 Tx"}, + { "ssp1 Tx", NULL, "codec1_out"}, + + { "codec0_in", NULL, "ssp1 Rx" }, + { "ssp1 Rx", NULL, "Capture" }, + + /* DMIC */ + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "DMIC AIF" }, + + { "hifi3", NULL, "iDisp3 Tx"}, + { "iDisp3 Tx", NULL, "iDisp3_out"}, + { "hifi2", NULL, "iDisp2 Tx"}, + { "iDisp2 Tx", NULL, "iDisp2_out"}, + { "hifi1", NULL, "iDisp1 Tx"}, + { "iDisp1 Tx", NULL, "iDisp1_out"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static struct snd_soc_codec_conf ssm4567_codec_conf[] = { + { + .dev_name = "i2c-INT343B:00", + .name_prefix = "Left", + }, + { + .dev_name = "i2c-INT343B:01", + .name_prefix = "Right", + }, +}; + +static struct snd_soc_dai_link_component ssm4567_codec_components[] = { + { /* Left */ + .name = "i2c-INT343B:00", + .dai_name = SKL_SSM_CODEC_DAI, + }, + { /* Right */ + .name = "i2c-INT343B:01", + .dai_name = SKL_SSM_CODEC_DAI, + }, +}; + +static int skylake_ssm4567_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + + /* Slot 1 for left */ + ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[0], 0x01, 0x01, 2, 48); + if (ret < 0) + return ret; + + /* Slot 2 for right */ + ret = snd_soc_dai_set_tdm_slot(rtd->codec_dais[1], 0x02, 0x02, 2, 48); + if (ret < 0) + return ret; + + return ret; +} + +static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_component *component = rtd->codec_dai->component; + + /* + * 4 buttons here map to the google Reference headset + * The use of these buttons can be decided by the user space. + */ + ret = snd_soc_card_jack_new(&skylake_audio_card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &skylake_headset, + NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed %d\n", ret); + return ret; + } + + nau8825_enable_jack_detect(component, &skylake_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + + return ret; +} + +static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd) +{ + struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct skl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = SKL_DPCM_AUDIO_HDMI1_PB; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd) +{ + struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct skl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = SKL_DPCM_AUDIO_HDMI2_PB; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + + +static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd) +{ + struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct skl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = SKL_DPCM_AUDIO_HDMI3_PB; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int skl_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * on this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops skylake_nau8825_fe_ops = { + .startup = skl_fe_startup, +}; + +static int skylake_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static int skylake_nau8825_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, + NAU8825_CLK_MCLK, 24000000, SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(rtd->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops skylake_nau8825_ops = { + .hw_params = skylake_nau8825_hw_params, +}; + +static const unsigned int channels_dmic[] = { + 2, 4, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + +static int skylake_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = DMIC_CH(dmic_constraints); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dmic_constraints); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static const struct snd_soc_ops skylake_dmic_ops = { + .startup = skylake_dmic_startup, +}; + +static const unsigned int rates_16000[] = { + 16000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_16000 = { + .count = ARRAY_SIZE(rates_16000), + .list = rates_16000, +}; + +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + +static int skylake_refcap_startup(struct snd_pcm_substream *substream) +{ + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_16000); +} + +static const struct snd_soc_ops skylaye_refcap_ops = { + .startup = skylake_refcap_startup, +}; + +/* skylake digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link skylake_dais[] = { + /* Front End DAI links */ + [SKL_DPCM_AUDIO_PB] = { + .name = "Skl Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = skylake_nau8825_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &skylake_nau8825_fe_ops, + }, + [SKL_DPCM_AUDIO_CP] = { + .name = "Skl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &skylake_nau8825_fe_ops, + }, + [SKL_DPCM_AUDIO_REF_CP] = { + .name = "Skl Audio Reference cap", + .stream_name = "Wake on Voice", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &skylaye_refcap_ops, + }, + [SKL_DPCM_AUDIO_DMIC_CP] = { + .name = "Skl Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &skylake_dmic_ops, + }, + [SKL_DPCM_AUDIO_HDMI1_PB] = { + .name = "Skl HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [SKL_DPCM_AUDIO_HDMI2_PB] = { + .name = "Skl HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .nonatomic = 1, + .dynamic = 1, + }, + [SKL_DPCM_AUDIO_HDMI3_PB] = { + .name = "Skl HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "SSP0-Codec", + .id = 0, + .cpu_dai_name = "SSP0 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codecs = ssm4567_codec_components, + .num_codecs = ARRAY_SIZE(ssm4567_codec_components), + .dai_fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .init = skylake_ssm4567_codec_init, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = skylake_ssp_fixup, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = "i2c-10508825:00", + .codec_dai_name = SKL_NUVOTON_CODEC_DAI, + .init = skylake_nau8825_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = skylake_ssp_fixup, + .ops = &skylake_nau8825_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:1f.3", + .ignore_suspend = 1, + .be_hw_params_fixup = skylake_dmic_fixup, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = skylake_hdmi1_init, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:1f.3", + .init = skylake_hdmi2_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:1f.3", + .init = skylake_hdmi3_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +#define NAME_SIZE 32 +static int skylake_card_late_probe(struct snd_soc_card *card) +{ + struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(card); + struct skl_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, + &skylake_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &skylake_hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* skylake audio machine driver for SPT + NAU88L25 */ +static struct snd_soc_card skylake_audio_card = { + .name = "sklnau8825adi", + .owner = THIS_MODULE, + .dai_link = skylake_dais, + .num_links = ARRAY_SIZE(skylake_dais), + .controls = skylake_controls, + .num_controls = ARRAY_SIZE(skylake_controls), + .dapm_widgets = skylake_widgets, + .num_dapm_widgets = ARRAY_SIZE(skylake_widgets), + .dapm_routes = skylake_map, + .num_dapm_routes = ARRAY_SIZE(skylake_map), + .codec_conf = ssm4567_codec_conf, + .num_configs = ARRAY_SIZE(ssm4567_codec_conf), + .fully_routed = true, + .late_probe = skylake_card_late_probe, +}; + +static int skylake_audio_probe(struct platform_device *pdev) +{ + struct skl_nau88125_private *ctx; + struct skl_machine_pdata *pdata; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + skylake_audio_card.dev = &pdev->dev; + snd_soc_card_set_drvdata(&skylake_audio_card, ctx); + + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + + return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card); +} + +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_n88l25_s4567" }, + { .name = "kbl_n88l25_s4567" }, + { } +}; + +static struct platform_driver skylake_audio = { + .probe = skylake_audio_probe, + .driver = { + .name = "skl_n88l25_s4567", + .pm = &snd_soc_pm_ops, + }, + .id_table = skl_board_ids, +}; + +module_platform_driver(skylake_audio) + +/* Module information */ +MODULE_AUTHOR("Conrad Cooke "); +MODULE_AUTHOR("Harsha Priya "); +MODULE_AUTHOR("Naveen M "); +MODULE_AUTHOR("Sathya Prakash M R "); +MODULE_AUTHOR("Yong Zhi "); +MODULE_DESCRIPTION("Intel Audio Machine driver for SKL with NAU88L25 and SSM4567 in I2S Mode"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:skl_n88l25_s4567"); +MODULE_ALIAS("platform:kbl_n88l25_s4567"); diff --git a/sound/soc/intel/boards/skl_rt286.c b/sound/soc/intel/boards/skl_rt286.c new file mode 100644 index 000000000..0e1818dd4 --- /dev/null +++ b/sound/soc/intel/boards/skl_rt286.c @@ -0,0 +1,565 @@ +/* + * Intel Skylake I2S Machine Driver + * + * Copyright (C) 2014-2015, Intel Corporation. All rights reserved. + * + * Modified from: + * Intel Broadwell Wildcatpoint SST Audio + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt286.h" +#include "../../codecs/hdac_hdmi.h" + +static struct snd_soc_jack skylake_headset; +static struct snd_soc_jack skylake_hdmi[3]; + +struct skl_hdmi_pcm { + struct list_head head; + struct snd_soc_dai *codec_dai; + int device; +}; + +struct skl_rt286_private { + struct list_head hdmi_pcm_list; +}; + +enum { + SKL_DPCM_AUDIO_PB = 0, + SKL_DPCM_AUDIO_DB_PB, + SKL_DPCM_AUDIO_CP, + SKL_DPCM_AUDIO_REF_CP, + SKL_DPCM_AUDIO_DMIC_CP, + SKL_DPCM_AUDIO_HDMI1_PB, + SKL_DPCM_AUDIO_HDMI2_PB, + SKL_DPCM_AUDIO_HDMI3_PB, +}; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin skylake_headset_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static const struct snd_kcontrol_new skylake_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), +}; + +static const struct snd_soc_dapm_widget skylake_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("DMIC2", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), +}; + +static const struct snd_soc_dapm_route skylake_rt286_map[] = { + /* speaker */ + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, + + /* HP jack connectors - unknown if we have jack deteck */ + {"Headphone Jack", NULL, "HPO Pin"}, + + /* other jacks */ + {"MIC1", NULL, "Mic Jack"}, + + /* digital mics */ + {"DMIC1 Pin", NULL, "DMIC2"}, + {"DMic", NULL, "SoC DMIC"}, + + /* CODEC BE connections */ + { "AIF1 Playback", NULL, "ssp0 Tx"}, + { "ssp0 Tx", NULL, "codec0_out"}, + { "ssp0 Tx", NULL, "codec1_out"}, + + { "codec0_in", NULL, "ssp0 Rx" }, + { "codec1_in", NULL, "ssp0 Rx" }, + { "ssp0 Rx", NULL, "AIF1 Capture" }, + + { "dmic01_hifi", NULL, "DMIC01 Rx" }, + { "DMIC01 Rx", NULL, "DMIC AIF" }, + + { "hifi3", NULL, "iDisp3 Tx"}, + { "iDisp3 Tx", NULL, "iDisp3_out"}, + { "hifi2", NULL, "iDisp2 Tx"}, + { "iDisp2 Tx", NULL, "iDisp2_out"}, + { "hifi1", NULL, "iDisp1 Tx"}, + { "iDisp1 Tx", NULL, "iDisp1_out"}, + +}; + +static int skylake_rt286_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + int ret; + + ret = snd_soc_card_jack_new(rtd->card, "Headset", + SND_JACK_HEADSET | SND_JACK_BTN_0, + &skylake_headset, + skylake_headset_pins, ARRAY_SIZE(skylake_headset_pins)); + + if (ret) + return ret; + + rt286_mic_detect(component, &skylake_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + + return 0; +} + +static int skylake_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct skl_rt286_private *ctx = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *dai = rtd->codec_dai; + struct skl_hdmi_pcm *pcm; + + pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->device = SKL_DPCM_AUDIO_HDMI1_PB + dai->id; + pcm->codec_dai = dai; + + list_add_tail(&pcm->head, &ctx->hdmi_pcm_list); + + return 0; +} + +static const unsigned int rates[] = { + 48000, +}; + +static const struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const unsigned int channels[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int skl_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * on this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops skylake_rt286_fe_ops = { + .startup = skl_fe_startup, +}; + +static int skylake_ssp0_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The output is 48KHz, stereo, 16bits */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static int skylake_rt286_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops skylake_rt286_ops = { + .hw_params = skylake_rt286_hw_params, +}; + +static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + if (params_channels(params) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static const unsigned int channels_dmic[] = { + 2, 4, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static int skylake_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = 4; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_dmic_channels); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static const struct snd_soc_ops skylake_dmic_ops = { + .startup = skylake_dmic_startup, +}; + +/* skylake digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link skylake_rt286_dais[] = { + /* Front End DAI links */ + [SKL_DPCM_AUDIO_PB] = { + .name = "Skl Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .nonatomic = 1, + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .init = skylake_rt286_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST + }, + .dpcm_playback = 1, + .ops = &skylake_rt286_fe_ops, + }, + [SKL_DPCM_AUDIO_DB_PB] = { + .name = "Skl Deepbuffer Port", + .stream_name = "Deep Buffer Audio", + .cpu_dai_name = "Deepbuffer Pin", + .platform_name = "0000:00:1f.3", + .nonatomic = 1, + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST + }, + .dpcm_playback = 1, + .ops = &skylake_rt286_fe_ops, + + }, + [SKL_DPCM_AUDIO_CP] = { + .name = "Skl Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:1f.3", + .nonatomic = 1, + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST + }, + .dpcm_capture = 1, + .ops = &skylake_rt286_fe_ops, + }, + [SKL_DPCM_AUDIO_REF_CP] = { + .name = "Skl Audio Reference cap", + .stream_name = "refcap", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + }, + [SKL_DPCM_AUDIO_DMIC_CP] = { + .name = "Skl Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &skylake_dmic_ops, + }, + [SKL_DPCM_AUDIO_HDMI1_PB] = { + .name = "Skl HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [SKL_DPCM_AUDIO_HDMI2_PB] = { + .name = "Skl HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [SKL_DPCM_AUDIO_HDMI3_PB] = { + .name = "Skl HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:1f.3", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + + /* Back End DAI links */ + { + /* SSP0 - Codec */ + .name = "SSP0-Codec", + .id = 0, + .cpu_dai_name = "SSP0 Pin", + .platform_name = "0000:00:1f.3", + .no_pcm = 1, + .codec_name = "i2c-INT343A:00", + .codec_dai_name = "rt286-aif1", + .init = skylake_rt286_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = skylake_ssp0_fixup, + .ops = &skylake_rt286_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 1, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:1f.3", + .be_hw_params_fixup = skylake_dmic_fixup, + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 2, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:1f.3", + .init = skylake_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 3, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:1f.3", + .init = skylake_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 4, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:1f.3", + .init = skylake_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +#define NAME_SIZE 32 +static int skylake_card_late_probe(struct snd_soc_card *card) +{ + struct skl_rt286_private *ctx = snd_soc_card_get_drvdata(card); + struct skl_hdmi_pcm *pcm; + struct snd_soc_component *component = NULL; + int err, i = 0; + char jack_name[NAME_SIZE]; + + list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) { + component = pcm->codec_dai->component; + snprintf(jack_name, sizeof(jack_name), + "HDMI/DP, pcm=%d Jack", pcm->device); + err = snd_soc_card_jack_new(card, jack_name, + SND_JACK_AVOUT, &skylake_hdmi[i], + NULL, 0); + + if (err) + return err; + + err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device, + &skylake_hdmi[i]); + if (err < 0) + return err; + + i++; + } + + if (!component) + return -EINVAL; + + return hdac_hdmi_jack_port_init(component, &card->dapm); +} + +/* skylake audio machine driver for SPT + RT286S */ +static struct snd_soc_card skylake_rt286 = { + .name = "skylake-rt286", + .owner = THIS_MODULE, + .dai_link = skylake_rt286_dais, + .num_links = ARRAY_SIZE(skylake_rt286_dais), + .controls = skylake_controls, + .num_controls = ARRAY_SIZE(skylake_controls), + .dapm_widgets = skylake_widgets, + .num_dapm_widgets = ARRAY_SIZE(skylake_widgets), + .dapm_routes = skylake_rt286_map, + .num_dapm_routes = ARRAY_SIZE(skylake_rt286_map), + .fully_routed = true, + .late_probe = skylake_card_late_probe, +}; + +static int skylake_audio_probe(struct platform_device *pdev) +{ + struct skl_rt286_private *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + INIT_LIST_HEAD(&ctx->hdmi_pcm_list); + + skylake_rt286.dev = &pdev->dev; + snd_soc_card_set_drvdata(&skylake_rt286, ctx); + + return devm_snd_soc_register_card(&pdev->dev, &skylake_rt286); +} + +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_alc286s_i2s" }, + { .name = "kbl_alc286s_i2s" }, + { } +}; + +static struct platform_driver skylake_audio = { + .probe = skylake_audio_probe, + .driver = { + .name = "skl_alc286s_i2s", + .pm = &snd_soc_pm_ops, + }, + .id_table = skl_board_ids, + +}; + +module_platform_driver(skylake_audio) + +/* Module information */ +MODULE_AUTHOR("Omair Mohammed Abdullah "); +MODULE_DESCRIPTION("Intel SST Audio for Skylake"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:skl_alc286s_i2s"); +MODULE_ALIAS("platform:kbl_alc286s_i2s"); diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile new file mode 100644 index 000000000..915a34cdc --- /dev/null +++ b/sound/soc/intel/common/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-sst-dsp-objs := sst-dsp.o +snd-soc-sst-acpi-objs := sst-acpi.o +snd-soc-sst-ipc-objs := sst-ipc.o +snd-soc-sst-firmware-objs := sst-firmware.o +snd-soc-acpi-intel-match-objs := soc-acpi-intel-byt-match.o soc-acpi-intel-cht-match.o \ + soc-acpi-intel-hsw-bdw-match.o \ + soc-acpi-intel-skl-match.o soc-acpi-intel-kbl-match.o \ + soc-acpi-intel-bxt-match.o soc-acpi-intel-glk-match.o \ + soc-acpi-intel-cnl-match.o + +obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o +obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o +obj-$(CONFIG_SND_SOC_INTEL_SST_FIRMWARE) += snd-soc-sst-firmware.o +obj-$(CONFIG_SND_SOC_ACPI_INTEL_MATCH) += snd-soc-acpi-intel-match.o diff --git a/sound/soc/intel/common/soc-acpi-intel-bxt-match.c b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c new file mode 100644 index 000000000..f39386e54 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-bxt-match.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * soc-apci-intel-bxt-match.c - tables and support for BXT ACPI enumeration. + * + * Copyright (c) 2018, Intel Corporation. + * + */ + +#include +#include + +static struct snd_soc_acpi_codecs bxt_codecs = { + .num_codecs = 1, + .codecs = {"MX98357A"} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_bxt_machines[] = { + { + .id = "INT343A", + .drv_name = "bxt_alc298s_i2s", + .fw_filename = "intel/dsp_fw_bxtn.bin", + }, + { + .id = "DLGS7219", + .drv_name = "bxt_da7219_max98357a", + .fw_filename = "intel/dsp_fw_bxtn.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &bxt_codecs, + .sof_fw_filename = "intel/sof-apl.ri", + .sof_tplg_filename = "intel/sof-apl-da7219.tplg", + .asoc_plat_name = "0000:00:0e.0", + }, + { + .id = "104C5122", + .drv_name = "bxt-pcm512x", + .sof_fw_filename = "intel/sof-apl.ri", + .sof_tplg_filename = "intel/sof-apl-pcm512x.tplg", + .asoc_plat_name = "0000:00:0e.0", + }, + { + .id = "1AEC8804", + .drv_name = "bxt-wm8804", + .sof_fw_filename = "intel/sof-apl.ri", + .sof_tplg_filename = "intel/sof-apl-wm8804.tplg", + .asoc_plat_name = "0000:00:0e.0", + }, + { + .id = "INT34C3", + .drv_name = "bxt_tdf8532", + .sof_fw_filename = "intel/sof-apl.ri", + .sof_tplg_filename = "intel/sof-apl-tdf8532.tplg", + .asoc_plat_name = "0000:00:0e.0", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_bxt_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-byt-match.c b/sound/soc/intel/common/soc-acpi-intel-byt-match.c new file mode 100644 index 000000000..4daa8a4f0 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-byt-match.c @@ -0,0 +1,196 @@ +/* + * soc-apci-intel-byt-match.c - tables and support for BYT ACPI enumeration. + * + * Copyright (c) 2017, Intel Corporation. + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include + +static unsigned long byt_machine_id; + +#define BYT_THINKPAD_10 1 + +static int byt_thinkpad10_quirk_cb(const struct dmi_system_id *id) +{ + byt_machine_id = BYT_THINKPAD_10; + return 1; +} + + +static const struct dmi_system_id byt_table[] = { + { + .callback = byt_thinkpad10_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad 10"), + }, + }, + { + .callback = byt_thinkpad10_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad Tablet B"), + }, + }, + { + .callback = byt_thinkpad10_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Miix 2 10"), + }, + }, + { } +}; + +static struct snd_soc_acpi_mach byt_thinkpad_10 = { + .id = "10EC5640", + .drv_name = "cht-bsw-rt5672", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-rt5670.tplg", + .asoc_plat_name = "sst-mfld-platform", +}; + +static struct snd_soc_acpi_mach *byt_quirk(void *arg) +{ + struct snd_soc_acpi_mach *mach = arg; + + dmi_check_system(byt_table); + + if (byt_machine_id == BYT_THINKPAD_10) + return &byt_thinkpad_10; + else + return mach; +} + +struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_legacy_machines[] = { + { + .id = "10EC5640", + .drv_name = "byt-rt5640", + .fw_filename = "intel/fw_sst_0f28.bin-48kHz_i2s_master", + }, + { + .id = "193C9890", + .drv_name = "byt-max98090", + .fw_filename = "intel/fw_sst_0f28.bin-48kHz_i2s_master", + }, + {} +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_baytrail_legacy_machines); + +struct snd_soc_acpi_mach snd_soc_acpi_intel_baytrail_machines[] = { + { + .id = "10EC5640", + .drv_name = "bytcr_rt5640", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "bytcr_rt5640", + .machine_quirk = byt_quirk, + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-rt5640.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC5642", + .drv_name = "bytcr_rt5640", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "bytcr_rt5640", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-rt5640.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "INTCCFFD", + .drv_name = "bytcr_rt5640", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "bytcr_rt5640", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-rt5640.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC5651", + .drv_name = "bytcr_rt5651", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "bytcr_rt5651", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-rt5651.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "DLGS7212", + .drv_name = "bytcht_da7213", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "bytcht_da7213", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-da7213.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "DLGS7213", + .drv_name = "bytcht_da7213", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "bytcht_da7213", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-da7213.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + /* some Baytrail platforms rely on RT5645, use CHT machine driver */ + { + .id = "10EC5645", + .drv_name = "cht-bsw-rt5645", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-rt5645.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC5648", + .drv_name = "cht-bsw-rt5645", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-rt5645.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + /* use CHT driver to Baytrail Chromebooks */ + { + .id = "193C9890", + .drv_name = "cht-bsw-max98090", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-byt.ri", + .sof_tplg_filename = "intel/sof-byt-max98090.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, +#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) + /* + * This is always last in the table so that it is selected only when + * enabled explicitly and there is no codec-related information in SSDT + */ + { + .id = "80860F28", + .drv_name = "bytcht_nocodec", + .fw_filename = "intel/fw_sst_0f28.bin", + .board = "bytcht_nocodec", + }, +#endif + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_baytrail_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c new file mode 100644 index 000000000..91bb99b69 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -0,0 +1,203 @@ +/* + * soc-apci-intel-cht-match.c - tables and support for CHT ACPI enumeration. + * + * Copyright (c) 2017, Intel Corporation. + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include + +static unsigned long cht_machine_id; + +#define CHT_SURFACE_MACH 1 + +static int cht_surface_quirk_cb(const struct dmi_system_id *id) +{ + cht_machine_id = CHT_SURFACE_MACH; + return 1; +} + +static const struct dmi_system_id cht_table[] = { + { + .callback = cht_surface_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, + { } +}; + +static struct snd_soc_acpi_mach cht_surface_mach = { + .id = "10EC5640", + .drv_name = "cht-bsw-rt5645", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5645.tplg", + .asoc_plat_name = "sst-mfld-platform", +}; + +static struct snd_soc_acpi_mach *cht_quirk(void *arg) +{ + struct snd_soc_acpi_mach *mach = arg; + + dmi_check_system(cht_table); + + if (cht_machine_id == CHT_SURFACE_MACH) + return &cht_surface_mach; + else + return mach; +} + +/* Cherryview-based platforms: CherryTrail and Braswell */ +struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { + { + .id = "10EC5670", + .drv_name = "cht-bsw-rt5672", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5670.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC5672", + .drv_name = "cht-bsw-rt5672", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5670.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC5645", + .drv_name = "cht-bsw-rt5645", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5645.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC5650", + .drv_name = "cht-bsw-rt5645", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5645.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC3270", + .drv_name = "cht-bsw-rt5645", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5645.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "193C9890", + .drv_name = "cht-bsw-max98090", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-max98090.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10508824", + .drv_name = "cht-bsw-nau8824", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht-bsw", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-nau8824.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "DLGS7212", + .drv_name = "bytcht_da7213", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "bytcht_da7213", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-da7213.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "DLGS7213", + .drv_name = "bytcht_da7213", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "bytcht_da7213", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-da7213.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "ESSX8316", + .drv_name = "bytcht_es8316", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "bytcht_es8316", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-es8316.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ + { + .id = "10EC5640", + .drv_name = "bytcr_rt5640", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "bytcr_rt5640", + .machine_quirk = cht_quirk, + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5640.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + { + .id = "10EC3276", + .drv_name = "bytcr_rt5640", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "bytcr_rt5640", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5640.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, + /* some CHT-T platforms rely on RT5651, use Baytrail machine driver */ + { + .id = "10EC5651", + .drv_name = "bytcr_rt5651", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "bytcr_rt5651", + .sof_fw_filename = "intel/sof-cht.ri", + .sof_tplg_filename = "intel/sof-cht-rt5651.tplg", + .asoc_plat_name = "sst-mfld-platform", + }, +#if IS_ENABLED(CONFIG_SND_SOC_INTEL_BYT_CHT_NOCODEC_MACH) + /* + * This is always last in the table so that it is selected only when + * enabled explicitly and there is no codec-related information in SSDT + */ + { + .id = "808622A8", + .drv_name = "bytcht_nocodec", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "bytcht_nocodec", + }, +#endif + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cherrytrail_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-cnl-match.c b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c new file mode 100644 index 000000000..ec8e28e7b --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-cnl-match.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * soc-apci-intel-cnl-match.c - tables and support for CNL ACPI enumeration. + * + * Copyright (c) 2018, Intel Corporation. + * + */ + +#include +#include +#include "../skylake/skl.h" + +static struct skl_machine_pdata cnl_pdata = { + .use_tplg_pcm = true, +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_machines[] = { + { + .id = "INT34C2", + .drv_name = "cnl_rt274", + .fw_filename = "intel/dsp_fw_cnl.bin", + .pdata = &cnl_pdata, + .sof_fw_filename = "intel/sof-cnl.ri", + .sof_tplg_filename = "intel/sof-cnl-rt274.tplg", + .asoc_plat_name = "0000:00:1f.3", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_cnl_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-glk-match.c b/sound/soc/intel/common/soc-acpi-intel-glk-match.c new file mode 100644 index 000000000..305875af7 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-glk-match.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * soc-apci-intel-glk-match.c - tables and support for GLK ACPI enumeration. + * + * Copyright (c) 2018, Intel Corporation. + * + */ + +#include +#include + +static struct snd_soc_acpi_codecs glk_codecs = { + .num_codecs = 1, + .codecs = {"MX98357A"} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_machines[] = { + { + .id = "INT343A", + .drv_name = "glk_alc298s_i2s", + .fw_filename = "intel/dsp_fw_glk.bin", + .sof_fw_filename = "intel/sof-glk.ri", + .sof_tplg_filename = "intel/sof-glk-alc298.tplg", + .asoc_plat_name = "0000:00:0e.0", + }, + { + .id = "DLGS7219", + .drv_name = "glk_da7219_max98357a", + .fw_filename = "intel/dsp_fw_glk.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &glk_codecs, + .sof_fw_filename = "intel/sof-glk.ri", + .sof_tplg_filename = "intel/sof-glk-da7219.tplg", + .asoc_plat_name = "0000:00:0e.0", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_glk_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c b/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c new file mode 100644 index 000000000..494a0ea9b --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-hsw-bdw-match.c @@ -0,0 +1,64 @@ +/* + * soc-apci-intel-hsw-bdw-match.c - tables and support for ACPI enumeration. + * + * Copyright (c) 2017, Intel Corporation. + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include + +struct snd_soc_acpi_mach snd_soc_acpi_intel_haswell_machines[] = { + { + .id = "INT33CA", + .drv_name = "haswell-audio", + .fw_filename = "intel/IntcSST1.bin", + .sof_fw_filename = "intel/sof-hsw.ri", + .sof_tplg_filename = "intel/sof-hsw.tplg", + .asoc_plat_name = "haswell-pcm-audio", + }, + {} +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_haswell_machines); + +struct snd_soc_acpi_mach snd_soc_acpi_intel_broadwell_machines[] = { + { + .id = "INT343A", + .drv_name = "broadwell-audio", + .fw_filename = "intel/IntcSST2.bin", + .sof_fw_filename = "intel/sof-bdw.ri", + .sof_tplg_filename = "intel/sof-bdw-rt286.tplg", + .asoc_plat_name = "haswell-pcm-audio", + }, + { + .id = "RT5677CE", + .drv_name = "bdw-rt5677", + .fw_filename = "intel/IntcSST2.bin", + .sof_fw_filename = "intel/sof-bdw.ri", + .sof_tplg_filename = "intel/sof-bdw-rt5677.tplg", + .asoc_plat_name = "haswell-pcm-audio", + }, + { + .id = "INT33CA", + .drv_name = "haswell-audio", + .fw_filename = "intel/IntcSST2.bin", + .sof_fw_filename = "intel/sof-bdw.ri", + .sof_tplg_filename = "intel/sof-bdw-rt5640.tplg", + .asoc_plat_name = "haswell-pcm-audio", + }, + {} +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_broadwell_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-kbl-match.c b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c new file mode 100644 index 000000000..0ee173ca4 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-kbl-match.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * soc-apci-intel-kbl-match.c - tables and support for KBL ACPI enumeration. + * + * Copyright (c) 2018, Intel Corporation. + * + */ + +#include +#include +#include "../skylake/skl.h" + +static struct skl_machine_pdata skl_dmic_data; + +static struct snd_soc_acpi_codecs kbl_codecs = { + .num_codecs = 1, + .codecs = {"10508825"} +}; + +static struct snd_soc_acpi_codecs kbl_poppy_codecs = { + .num_codecs = 1, + .codecs = {"10EC5663"} +}; + +static struct snd_soc_acpi_codecs kbl_5663_5514_codecs = { + .num_codecs = 2, + .codecs = {"10EC5663", "10EC5514"} +}; + +static struct snd_soc_acpi_codecs kbl_7219_98357_codecs = { + .num_codecs = 1, + .codecs = {"MX98357A"} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_kbl_machines[] = { + { + .id = "INT343A", + .drv_name = "kbl_alc286s_i2s", + .fw_filename = "intel/dsp_fw_kbl.bin", + }, + { + .id = "INT343B", + .drv_name = "kbl_n88l25_s4567", + .fw_filename = "intel/dsp_fw_kbl.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &kbl_codecs, + .pdata = &skl_dmic_data, + }, + { + .id = "MX98357A", + .drv_name = "kbl_n88l25_m98357a", + .fw_filename = "intel/dsp_fw_kbl.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &kbl_codecs, + .pdata = &skl_dmic_data, + }, + { + .id = "MX98927", + .drv_name = "kbl_r5514_5663_max", + .fw_filename = "intel/dsp_fw_kbl.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &kbl_5663_5514_codecs, + .pdata = &skl_dmic_data, + }, + { + .id = "MX98927", + .drv_name = "kbl_rt5663_m98927", + .fw_filename = "intel/dsp_fw_kbl.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &kbl_poppy_codecs, + .pdata = &skl_dmic_data, + }, + { + .id = "10EC5663", + .drv_name = "kbl_rt5663", + .fw_filename = "intel/dsp_fw_kbl.bin", + }, + { + .id = "DLGS7219", + .drv_name = "kbl_da7219_max98357a", + .fw_filename = "intel/dsp_fw_kbl.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &kbl_7219_98357_codecs, + .pdata = &skl_dmic_data, + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_kbl_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/soc-acpi-intel-skl-match.c b/sound/soc/intel/common/soc-acpi-intel-skl-match.c new file mode 100644 index 000000000..0c9c0edd3 --- /dev/null +++ b/sound/soc/intel/common/soc-acpi-intel-skl-match.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * soc-apci-intel-skl-match.c - tables and support for SKL ACPI enumeration. + * + * Copyright (c) 2018, Intel Corporation. + * + */ + +#include +#include +#include "../skylake/skl.h" + +static struct skl_machine_pdata skl_dmic_data; + +static struct snd_soc_acpi_codecs skl_codecs = { + .num_codecs = 1, + .codecs = {"10508825"} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_intel_skl_machines[] = { + { + .id = "INT343A", + .drv_name = "skl_alc286s_i2s", + .fw_filename = "intel/dsp_fw_release.bin", + }, + { + .id = "INT343B", + .drv_name = "skl_n88l25_s4567", + .fw_filename = "intel/dsp_fw_release.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &skl_codecs, + .pdata = &skl_dmic_data, + }, + { + .id = "MX98357A", + .drv_name = "skl_n88l25_m98357a", + .fw_filename = "intel/dsp_fw_release.bin", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &skl_codecs, + .pdata = &skl_dmic_data, + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_skl_machines); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Common ACPI Match module"); diff --git a/sound/soc/intel/common/sst-acpi.c b/sound/soc/intel/common/sst-acpi.c new file mode 100644 index 000000000..cf6fbbd4e --- /dev/null +++ b/sound/soc/intel/common/sst-acpi.c @@ -0,0 +1,244 @@ +/* + * Intel SST loader on ACPI systems + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "sst-dsp.h" +#include +#include + +#define SST_LPT_DSP_DMA_ADDR_OFFSET 0x0F0000 +#define SST_WPT_DSP_DMA_ADDR_OFFSET 0x0FE000 +#define SST_LPT_DSP_DMA_SIZE (1024 - 1) + +/* Descriptor for setting up SST platform data */ +struct sst_acpi_desc { + const char *drv_name; + struct snd_soc_acpi_mach *machines; + /* Platform resource indexes. Must set to -1 if not used */ + int resindex_lpe_base; + int resindex_pcicfg_base; + int resindex_fw_base; + int irqindex_host_ipc; + int resindex_dma_base; + /* Unique number identifying the SST core on platform */ + int sst_id; + /* DMA only valid when resindex_dma_base != -1*/ + int dma_engine; + int dma_size; +}; + +struct sst_acpi_priv { + struct platform_device *pdev_mach; + struct platform_device *pdev_pcm; + struct sst_pdata sst_pdata; + struct sst_acpi_desc *desc; + struct snd_soc_acpi_mach *mach; +}; + +static void sst_acpi_fw_cb(const struct firmware *fw, void *context) +{ + struct platform_device *pdev = context; + struct device *dev = &pdev->dev; + struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); + struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; + struct sst_acpi_desc *desc = sst_acpi->desc; + struct snd_soc_acpi_mach *mach = sst_acpi->mach; + + sst_pdata->fw = fw; + if (!fw) { + dev_err(dev, "Cannot load firmware %s\n", mach->fw_filename); + return; + } + + /* register PCM and DAI driver */ + sst_acpi->pdev_pcm = + platform_device_register_data(dev, desc->drv_name, -1, + sst_pdata, sizeof(*sst_pdata)); + if (IS_ERR(sst_acpi->pdev_pcm)) { + dev_err(dev, "Cannot register device %s. Error %d\n", + desc->drv_name, (int)PTR_ERR(sst_acpi->pdev_pcm)); + } + + return; +} + +static int sst_acpi_probe(struct platform_device *pdev) +{ + const struct acpi_device_id *id; + struct device *dev = &pdev->dev; + struct sst_acpi_priv *sst_acpi; + struct sst_pdata *sst_pdata; + struct snd_soc_acpi_mach *mach; + struct sst_acpi_desc *desc; + struct resource *mmio; + int ret = 0; + + sst_acpi = devm_kzalloc(dev, sizeof(*sst_acpi), GFP_KERNEL); + if (sst_acpi == NULL) + return -ENOMEM; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return -ENODEV; + + desc = (struct sst_acpi_desc *)id->driver_data; + mach = snd_soc_acpi_find_machine(desc->machines); + if (mach == NULL) { + dev_err(dev, "No matching ASoC machine driver found\n"); + return -ENODEV; + } + + sst_pdata = &sst_acpi->sst_pdata; + sst_pdata->id = desc->sst_id; + sst_pdata->dma_dev = dev; + sst_acpi->desc = desc; + sst_acpi->mach = mach; + + sst_pdata->resindex_dma_base = desc->resindex_dma_base; + if (desc->resindex_dma_base >= 0) { + sst_pdata->dma_engine = desc->dma_engine; + sst_pdata->dma_base = desc->resindex_dma_base; + sst_pdata->dma_size = desc->dma_size; + } + + if (desc->irqindex_host_ipc >= 0) + sst_pdata->irq = platform_get_irq(pdev, desc->irqindex_host_ipc); + + if (desc->resindex_lpe_base >= 0) { + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_lpe_base); + if (mmio) { + sst_pdata->lpe_base = mmio->start; + sst_pdata->lpe_size = resource_size(mmio); + } + } + + if (desc->resindex_pcicfg_base >= 0) { + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_pcicfg_base); + if (mmio) { + sst_pdata->pcicfg_base = mmio->start; + sst_pdata->pcicfg_size = resource_size(mmio); + } + } + + if (desc->resindex_fw_base >= 0) { + mmio = platform_get_resource(pdev, IORESOURCE_MEM, + desc->resindex_fw_base); + if (mmio) { + sst_pdata->fw_base = mmio->start; + sst_pdata->fw_size = resource_size(mmio); + } + } + + platform_set_drvdata(pdev, sst_acpi); + + /* register machine driver */ + sst_acpi->pdev_mach = + platform_device_register_data(dev, mach->drv_name, -1, + sst_pdata, sizeof(*sst_pdata)); + if (IS_ERR(sst_acpi->pdev_mach)) + return PTR_ERR(sst_acpi->pdev_mach); + + /* continue SST probing after firmware is loaded */ + ret = request_firmware_nowait(THIS_MODULE, true, mach->fw_filename, + dev, GFP_KERNEL, pdev, sst_acpi_fw_cb); + if (ret) + platform_device_unregister(sst_acpi->pdev_mach); + + return ret; +} + +static int sst_acpi_remove(struct platform_device *pdev) +{ + struct sst_acpi_priv *sst_acpi = platform_get_drvdata(pdev); + struct sst_pdata *sst_pdata = &sst_acpi->sst_pdata; + + platform_device_unregister(sst_acpi->pdev_mach); + if (!IS_ERR_OR_NULL(sst_acpi->pdev_pcm)) + platform_device_unregister(sst_acpi->pdev_pcm); + release_firmware(sst_pdata->fw); + + return 0; +} + +static struct sst_acpi_desc sst_acpi_haswell_desc = { + .drv_name = "haswell-pcm-audio", + .machines = snd_soc_acpi_intel_haswell_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_fw_base = -1, + .irqindex_host_ipc = 0, + .sst_id = SST_DEV_ID_LYNX_POINT, + .dma_engine = SST_DMA_TYPE_DW, + .resindex_dma_base = SST_LPT_DSP_DMA_ADDR_OFFSET, + .dma_size = SST_LPT_DSP_DMA_SIZE, +}; + +static struct sst_acpi_desc sst_acpi_broadwell_desc = { + .drv_name = "haswell-pcm-audio", + .machines = snd_soc_acpi_intel_broadwell_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_fw_base = -1, + .irqindex_host_ipc = 0, + .sst_id = SST_DEV_ID_WILDCAT_POINT, + .dma_engine = SST_DMA_TYPE_DW, + .resindex_dma_base = SST_WPT_DSP_DMA_ADDR_OFFSET, + .dma_size = SST_LPT_DSP_DMA_SIZE, +}; + +#if !IS_ENABLED(CONFIG_SND_SST_IPC_ACPI) +static struct sst_acpi_desc sst_acpi_baytrail_desc = { + .drv_name = "baytrail-pcm-audio", + .machines = snd_soc_acpi_intel_baytrail_legacy_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = 1, + .resindex_fw_base = 2, + .irqindex_host_ipc = 5, + .sst_id = SST_DEV_ID_BYT, + .resindex_dma_base = -1, +}; +#endif + +static const struct acpi_device_id sst_acpi_match[] = { + { "INT33C8", (unsigned long)&sst_acpi_haswell_desc }, + { "INT3438", (unsigned long)&sst_acpi_broadwell_desc }, +#if !IS_ENABLED(CONFIG_SND_SST_IPC_ACPI) + { "80860F28", (unsigned long)&sst_acpi_baytrail_desc }, +#endif + { } +}; +MODULE_DEVICE_TABLE(acpi, sst_acpi_match); + +static struct platform_driver sst_acpi_driver = { + .probe = sst_acpi_probe, + .remove = sst_acpi_remove, + .driver = { + .name = "sst-acpi", + .acpi_match_table = ACPI_PTR(sst_acpi_match), + }, +}; +module_platform_driver(sst_acpi_driver); + +MODULE_AUTHOR("Jarkko Nikula "); +MODULE_DESCRIPTION("Intel SST loader on ACPI systems"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-dsp-priv.h b/sound/soc/intel/common/sst-dsp-priv.h new file mode 100644 index 000000000..363145716 --- /dev/null +++ b/sound/soc/intel/common/sst-dsp-priv.h @@ -0,0 +1,392 @@ +/* + * Intel Smart Sound Technology + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SOUND_SOC_SST_DSP_PRIV_H +#define __SOUND_SOC_SST_DSP_PRIV_H + +#include +#include +#include +#include + +#include "../skylake/skl-sst-dsp.h" + +struct sst_mem_block; +struct sst_module; +struct sst_fw; + +/* do we need to remove or keep */ +#define DSP_DRAM_ADDR_OFFSET 0x400000 + +/* + * DSP Operations exported by platform Audio DSP driver. + */ +struct sst_ops { + /* DSP core boot / reset */ + void (*boot)(struct sst_dsp *); + void (*reset)(struct sst_dsp *); + int (*wake)(struct sst_dsp *); + void (*sleep)(struct sst_dsp *); + void (*stall)(struct sst_dsp *); + + /* Shim IO */ + void (*write)(void __iomem *addr, u32 offset, u32 value); + u32 (*read)(void __iomem *addr, u32 offset); + void (*write64)(void __iomem *addr, u32 offset, u64 value); + u64 (*read64)(void __iomem *addr, u32 offset); + + /* DSP I/DRAM IO */ + void (*ram_read)(struct sst_dsp *sst, void *dest, void __iomem *src, + size_t bytes); + void (*ram_write)(struct sst_dsp *sst, void __iomem *dest, void *src, + size_t bytes); + + void (*dump)(struct sst_dsp *); + + /* IRQ handlers */ + irqreturn_t (*irq_handler)(int irq, void *context); + + /* SST init and free */ + int (*init)(struct sst_dsp *sst, struct sst_pdata *pdata); + void (*free)(struct sst_dsp *sst); + + /* FW module parser/loader */ + int (*parse_fw)(struct sst_fw *sst_fw); +}; + +/* + * Audio DSP memory offsets and addresses. + */ +struct sst_addr { + u32 lpe_base; + u32 shim_offset; + u32 iram_offset; + u32 dram_offset; + u32 dsp_iram_offset; + u32 dsp_dram_offset; + u32 sram0_base; + u32 sram1_base; + u32 w0_stat_sz; + u32 w0_up_sz; + void __iomem *lpe; + void __iomem *shim; + void __iomem *pci_cfg; + void __iomem *fw_ext; +}; + +/* + * Audio DSP Mailbox configuration. + */ +struct sst_mailbox { + void __iomem *in_base; + void __iomem *out_base; + size_t in_size; + size_t out_size; +}; + +/* + * Audio DSP memory block types. + */ +enum sst_mem_type { + SST_MEM_IRAM = 0, + SST_MEM_DRAM = 1, + SST_MEM_ANY = 2, + SST_MEM_CACHE= 3, +}; + +/* + * Audio DSP Generic Firmware File. + * + * SST Firmware files can consist of 1..N modules. This generic structure is + * used to manage each firmware file and it's modules regardless of SST firmware + * type. A SST driver may load multiple FW files. + */ +struct sst_fw { + struct sst_dsp *dsp; + + /* base addresses of FW file data */ + dma_addr_t dmable_fw_paddr; /* physical address of fw data */ + void *dma_buf; /* virtual address of fw data */ + u32 size; /* size of fw data */ + + /* lists */ + struct list_head list; /* DSP list of FW */ + struct list_head module_list; /* FW list of modules */ + + void *private; /* core doesn't touch this */ +}; + +/* + * Audio DSP Generic Module Template. + * + * Used to define and register a new FW module. This data is extracted from + * FW module header information. + */ +struct sst_module_template { + u32 id; + u32 entry; /* entry point */ + u32 scratch_size; + u32 persistent_size; +}; + +/* + * Block Allocator - Used to allocate blocks of DSP memory. + */ +struct sst_block_allocator { + u32 id; + u32 offset; + int size; + enum sst_mem_type type; +}; + +/* + * Runtime Module Instance - A module object can be instantiated multiple + * times within the DSP FW. + */ +struct sst_module_runtime { + struct sst_dsp *dsp; + int id; + struct sst_module *module; /* parent module we belong too */ + + u32 persistent_offset; /* private memory offset */ + void *private; + + struct list_head list; + struct list_head block_list; /* list of blocks used */ +}; + +/* + * Runtime Module Context - The runtime context must be manually stored by the + * driver prior to enter S3 and restored after leaving S3. This should really be + * part of the memory context saved by the enter D3 message IPC ??? + */ +struct sst_module_runtime_context { + dma_addr_t dma_buffer; + u32 *buffer; +}; + +/* + * Audio DSP Module State + */ +enum sst_module_state { + SST_MODULE_STATE_UNLOADED = 0, /* default state */ + SST_MODULE_STATE_LOADED, + SST_MODULE_STATE_INITIALIZED, /* and inactive */ + SST_MODULE_STATE_ACTIVE, +}; + +/* + * Audio DSP Generic Module. + * + * Each Firmware file can consist of 1..N modules. A module can span multiple + * ADSP memory blocks. The simplest FW will be a file with 1 module. A module + * can be instantiated multiple times in the DSP. + */ +struct sst_module { + struct sst_dsp *dsp; + struct sst_fw *sst_fw; /* parent FW we belong too */ + + /* module configuration */ + u32 id; + u32 entry; /* module entry point */ + s32 offset; /* module offset in firmware file */ + u32 size; /* module size */ + u32 scratch_size; /* global scratch memory required */ + u32 persistent_size; /* private memory required */ + enum sst_mem_type type; /* destination memory type */ + u32 data_offset; /* offset in ADSP memory space */ + void *data; /* module data */ + + /* runtime */ + u32 usage_count; /* can be unloaded if count == 0 */ + void *private; /* core doesn't touch this */ + + /* lists */ + struct list_head block_list; /* Module list of blocks in use */ + struct list_head list; /* DSP list of modules */ + struct list_head list_fw; /* FW list of modules */ + struct list_head runtime_list; /* list of runtime module objects*/ + + /* state */ + enum sst_module_state state; +}; + +/* + * SST Memory Block operations. + */ +struct sst_block_ops { + int (*enable)(struct sst_mem_block *block); + int (*disable)(struct sst_mem_block *block); +}; + +/* + * SST Generic Memory Block. + * + * SST ADP memory has multiple IRAM and DRAM blocks. Some ADSP blocks can be + * power gated. + */ +struct sst_mem_block { + struct sst_dsp *dsp; + struct sst_module *module; /* module that uses this block */ + + /* block config */ + u32 offset; /* offset from base */ + u32 size; /* block size */ + u32 index; /* block index 0..N */ + enum sst_mem_type type; /* block memory type IRAM/DRAM */ + const struct sst_block_ops *ops;/* block operations, if any */ + + /* block status */ + u32 bytes_used; /* bytes in use by modules */ + void *private; /* generic core does not touch this */ + int users; /* number of modules using this block */ + + /* block lists */ + struct list_head module_list; /* Module list of blocks */ + struct list_head list; /* Map list of free/used blocks */ +}; + +/* + * Generic SST Shim Interface. + */ +struct sst_dsp { + + /* Shared for all platforms */ + + /* runtime */ + struct sst_dsp_device *sst_dev; + spinlock_t spinlock; /* IPC locking */ + struct mutex mutex; /* DSP FW lock */ + struct device *dev; + struct device *dma_dev; + void *thread_context; + int irq; + u32 id; + + /* operations */ + struct sst_ops *ops; + + /* debug FS */ + struct dentry *debugfs_root; + + /* base addresses */ + struct sst_addr addr; + + /* mailbox */ + struct sst_mailbox mailbox; + + /* HSW/Byt data */ + + /* list of free and used ADSP memory blocks */ + struct list_head used_block_list; + struct list_head free_block_list; + + /* SST FW files loaded and their modules */ + struct list_head module_list; + struct list_head fw_list; + + /* scratch buffer */ + struct list_head scratch_block_list; + u32 scratch_offset; + u32 scratch_size; + + /* platform data */ + struct sst_pdata *pdata; + + /* DMA FW loading */ + struct sst_dma *dma; + bool fw_use_dma; + + /* SKL data */ + + const char *fw_name; + + /* To allocate CL dma buffers */ + struct skl_dsp_loader_ops dsp_ops; + struct skl_dsp_fw_ops fw_ops; + int sst_state; + struct skl_cl_dev cl_dev; + u32 intr_status; + const struct firmware *fw; + struct snd_dma_buffer dmab; +}; + +/* Size optimised DRAM/IRAM memcpy */ +static inline void sst_dsp_write(struct sst_dsp *sst, void *src, + u32 dest_offset, size_t bytes) +{ + sst->ops->ram_write(sst, sst->addr.lpe + dest_offset, src, bytes); +} + +static inline void sst_dsp_read(struct sst_dsp *sst, void *dest, + u32 src_offset, size_t bytes) +{ + sst->ops->ram_read(sst, dest, sst->addr.lpe + src_offset, bytes); +} + +static inline void *sst_dsp_get_thread_context(struct sst_dsp *sst) +{ + return sst->thread_context; +} + +/* Create/Free FW files - can contain multiple modules */ +struct sst_fw *sst_fw_new(struct sst_dsp *dsp, + const struct firmware *fw, void *private); +void sst_fw_free(struct sst_fw *sst_fw); +void sst_fw_free_all(struct sst_dsp *dsp); +int sst_fw_reload(struct sst_fw *sst_fw); +void sst_fw_unload(struct sst_fw *sst_fw); + +/* Create/Free firmware modules */ +struct sst_module *sst_module_new(struct sst_fw *sst_fw, + struct sst_module_template *template, void *private); +void sst_module_free(struct sst_module *module); +struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id); +int sst_module_alloc_blocks(struct sst_module *module); +int sst_module_free_blocks(struct sst_module *module); + +/* Create/Free firmware module runtime instances */ +struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, + int id, void *private); +void sst_module_runtime_free(struct sst_module_runtime *runtime); +struct sst_module_runtime *sst_module_runtime_get_from_id( + struct sst_module *module, u32 id); +int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, + int offset); +int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime); +int sst_module_runtime_save(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context); +int sst_module_runtime_restore(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context); + +/* generic block allocation */ +int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list); +int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list); + +/* scratch allocation */ +int sst_block_alloc_scratch(struct sst_dsp *dsp); +void sst_block_free_scratch(struct sst_dsp *dsp); + +/* Register the DSPs memory blocks - would be nice to read from ACPI */ +struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, + u32 size, enum sst_mem_type type, const struct sst_block_ops *ops, + u32 index, void *private); +void sst_mem_block_unregister_all(struct sst_dsp *dsp); + +u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, + enum sst_mem_type type); +#endif diff --git a/sound/soc/intel/common/sst-dsp.c b/sound/soc/intel/common/sst-dsp.c new file mode 100644 index 000000000..fd82f4b1d --- /dev/null +++ b/sound/soc/intel/common/sst-dsp.c @@ -0,0 +1,424 @@ +/* + * Intel Smart Sound Technology (SST) DSP Core Driver + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" + +#define CREATE_TRACE_POINTS +#include + +/* Internal generic low-level SST IO functions - can be overidden */ +void sst_shim32_write(void __iomem *addr, u32 offset, u32 value) +{ + writel(value, addr + offset); +} +EXPORT_SYMBOL_GPL(sst_shim32_write); + +u32 sst_shim32_read(void __iomem *addr, u32 offset) +{ + return readl(addr + offset); +} +EXPORT_SYMBOL_GPL(sst_shim32_read); + +void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value) +{ + memcpy_toio(addr + offset, &value, sizeof(value)); +} +EXPORT_SYMBOL_GPL(sst_shim32_write64); + +u64 sst_shim32_read64(void __iomem *addr, u32 offset) +{ + u64 val; + + memcpy_fromio(&val, addr + offset, sizeof(val)); + return val; +} +EXPORT_SYMBOL_GPL(sst_shim32_read64); + +static inline void _sst_memcpy_toio_32(volatile u32 __iomem *dest, + u32 *src, size_t bytes) +{ + int i, words = bytes >> 2; + + for (i = 0; i < words; i++) + writel(src[i], dest + i); +} + +static inline void _sst_memcpy_fromio_32(u32 *dest, + const volatile __iomem u32 *src, size_t bytes) +{ + int i, words = bytes >> 2; + + for (i = 0; i < words; i++) + dest[i] = readl(src + i); +} + +void sst_memcpy_toio_32(struct sst_dsp *sst, + void __iomem *dest, void *src, size_t bytes) +{ + _sst_memcpy_toio_32(dest, src, bytes); +} +EXPORT_SYMBOL_GPL(sst_memcpy_toio_32); + +void sst_memcpy_fromio_32(struct sst_dsp *sst, void *dest, + void __iomem *src, size_t bytes) +{ + _sst_memcpy_fromio_32(dest, src, bytes); +} +EXPORT_SYMBOL_GPL(sst_memcpy_fromio_32); + +/* Public API */ +void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + sst->ops->write(sst->addr.shim, offset, value); + spin_unlock_irqrestore(&sst->spinlock, flags); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write); + +u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&sst->spinlock, flags); + val = sst->ops->read(sst->addr.shim, offset); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return val; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read); + +void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + sst->ops->write64(sst->addr.shim, offset, value); + spin_unlock_irqrestore(&sst->spinlock, flags); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write64); + +u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset) +{ + unsigned long flags; + u64 val; + + spin_lock_irqsave(&sst->spinlock, flags); + val = sst->ops->read64(sst->addr.shim, offset); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return val; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read64); + +void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value) +{ + sst->ops->write(sst->addr.shim, offset, value); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write_unlocked); + +u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset) +{ + return sst->ops->read(sst->addr.shim, offset); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read_unlocked); + +void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value) +{ + sst->ops->write64(sst->addr.shim, offset, value); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_write64_unlocked); + +u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset) +{ + return sst->ops->read64(sst->addr.shim, offset); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_read64_unlocked); + +int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value) +{ + bool change; + unsigned int old, new; + u32 ret; + + ret = sst_dsp_shim_read_unlocked(sst, offset); + + old = ret; + new = (old & (~mask)) | (value & mask); + + change = (old != new); + if (change) + sst_dsp_shim_write_unlocked(sst, offset, new); + + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_unlocked); + +int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value) +{ + bool change; + u64 old, new; + + old = sst_dsp_shim_read64_unlocked(sst, offset); + + new = (old & (~mask)) | (value & mask); + + change = (old != new); + if (change) + sst_dsp_shim_write64_unlocked(sst, offset, new); + + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64_unlocked); + +/* This is for registers bits with attribute RWC */ +void sst_dsp_shim_update_bits_forced_unlocked(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value) +{ + unsigned int old, new; + u32 ret; + + ret = sst_dsp_shim_read_unlocked(sst, offset); + + old = ret; + new = (old & (~mask)) | (value & mask); + + sst_dsp_shim_write_unlocked(sst, offset, new); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_forced_unlocked); + +int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sst->spinlock, flags); + change = sst_dsp_shim_update_bits_unlocked(sst, offset, mask, value); + spin_unlock_irqrestore(&sst->spinlock, flags); + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits); + +int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value) +{ + unsigned long flags; + bool change; + + spin_lock_irqsave(&sst->spinlock, flags); + change = sst_dsp_shim_update_bits64_unlocked(sst, offset, mask, value); + spin_unlock_irqrestore(&sst->spinlock, flags); + return change; +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits64); + +/* This is for registers bits with attribute RWC */ +void sst_dsp_shim_update_bits_forced(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + sst_dsp_shim_update_bits_forced_unlocked(sst, offset, mask, value); + spin_unlock_irqrestore(&sst->spinlock, flags); +} +EXPORT_SYMBOL_GPL(sst_dsp_shim_update_bits_forced); + +int sst_dsp_register_poll(struct sst_dsp *ctx, u32 offset, u32 mask, + u32 target, u32 time, char *operation) +{ + u32 reg; + unsigned long timeout; + int k = 0, s = 500; + + /* + * split the loop into sleeps of varying resolution. more accurately, + * the range of wakeups are: + * Phase 1(first 5ms): min sleep 0.5ms; max sleep 1ms. + * Phase 2:( 5ms to 10ms) : min sleep 0.5ms; max sleep 10ms + * (usleep_range (500, 1000) and usleep_range(5000, 10000) are + * both possible in this phase depending on whether k > 10 or not). + * Phase 3: (beyond 10 ms) min sleep 5ms; max sleep 10ms. + */ + + timeout = jiffies + msecs_to_jiffies(time); + while ((((reg = sst_dsp_shim_read_unlocked(ctx, offset)) & mask) != target) + && time_before(jiffies, timeout)) { + k++; + if (k > 10) + s = 5000; + + usleep_range(s, 2*s); + } + + if ((reg & mask) == target) { + dev_dbg(ctx->dev, "FW Poll Status: reg=%#x %s successful\n", + reg, operation); + + return 0; + } + + dev_dbg(ctx->dev, "FW Poll Status: reg=%#x %s timedout\n", + reg, operation); + return -ETIME; +} +EXPORT_SYMBOL_GPL(sst_dsp_register_poll); + +void sst_dsp_dump(struct sst_dsp *sst) +{ + if (sst->ops->dump) + sst->ops->dump(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_dump); + +void sst_dsp_reset(struct sst_dsp *sst) +{ + if (sst->ops->reset) + sst->ops->reset(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_reset); + +int sst_dsp_boot(struct sst_dsp *sst) +{ + if (sst->ops->boot) + sst->ops->boot(sst); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_boot); + +int sst_dsp_wake(struct sst_dsp *sst) +{ + if (sst->ops->wake) + return sst->ops->wake(sst); + + return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_wake); + +void sst_dsp_sleep(struct sst_dsp *sst) +{ + if (sst->ops->sleep) + sst->ops->sleep(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_sleep); + +void sst_dsp_stall(struct sst_dsp *sst) +{ + if (sst->ops->stall) + sst->ops->stall(sst); +} +EXPORT_SYMBOL_GPL(sst_dsp_stall); + +void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg) +{ + sst_dsp_shim_write_unlocked(dsp, SST_IPCX, msg | SST_IPCX_BUSY); + trace_sst_ipc_msg_tx(msg); +} +EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_tx); + +u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp) +{ + u32 msg; + + msg = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); + trace_sst_ipc_msg_rx(msg); + + return msg; +} +EXPORT_SYMBOL_GPL(sst_dsp_ipc_msg_rx); + +int sst_dsp_mailbox_init(struct sst_dsp *sst, u32 inbox_offset, size_t inbox_size, + u32 outbox_offset, size_t outbox_size) +{ + sst->mailbox.in_base = sst->addr.lpe + inbox_offset; + sst->mailbox.out_base = sst->addr.lpe + outbox_offset; + sst->mailbox.in_size = inbox_size; + sst->mailbox.out_size = outbox_size; + return 0; +} +EXPORT_SYMBOL_GPL(sst_dsp_mailbox_init); + +void sst_dsp_outbox_write(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_outbox_write(bytes); + + memcpy_toio(sst->mailbox.out_base, message, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_outbox_wdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_outbox_write); + +void sst_dsp_outbox_read(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_outbox_read(bytes); + + memcpy_fromio(message, sst->mailbox.out_base, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_outbox_rdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_outbox_read); + +void sst_dsp_inbox_write(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_inbox_write(bytes); + + memcpy_toio(sst->mailbox.in_base, message, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_inbox_wdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_inbox_write); + +void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes) +{ + u32 i; + + trace_sst_ipc_inbox_read(bytes); + + memcpy_fromio(message, sst->mailbox.in_base, bytes); + + for (i = 0; i < bytes; i += 4) + trace_sst_ipc_inbox_rdata(i, *(u32 *)(message + i)); +} +EXPORT_SYMBOL_GPL(sst_dsp_inbox_read); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood"); +MODULE_DESCRIPTION("Intel SST Core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-dsp.h b/sound/soc/intel/common/sst-dsp.h new file mode 100644 index 000000000..859f0de00 --- /dev/null +++ b/sound/soc/intel/common/sst-dsp.h @@ -0,0 +1,293 @@ +/* + * Intel Smart Sound Technology (SST) Core + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SOUND_SOC_SST_DSP_H +#define __SOUND_SOC_SST_DSP_H + +#include +#include +#include + +/* SST Device IDs */ +#define SST_DEV_ID_LYNX_POINT 0x33C8 +#define SST_DEV_ID_WILDCAT_POINT 0x3438 +#define SST_DEV_ID_BYT 0x0F28 + +/* Supported SST DMA Devices */ +#define SST_DMA_TYPE_DW 1 + +/* autosuspend delay 5s*/ +#define SST_RUNTIME_SUSPEND_DELAY (5 * 1000) + +/* SST Shim register map + * The register naming can differ between products. Some products also + * contain extra functionality. + */ +#define SST_CSR 0x00 +#define SST_PISR 0x08 +#define SST_PIMR 0x10 +#define SST_ISRX 0x18 +#define SST_ISRD 0x20 +#define SST_IMRX 0x28 +#define SST_IMRD 0x30 +#define SST_IPCX 0x38 /* IPC IA -> SST */ +#define SST_IPCD 0x40 /* IPC SST -> IA */ +#define SST_ISRSC 0x48 +#define SST_ISRLPESC 0x50 +#define SST_IMRSC 0x58 +#define SST_IMRLPESC 0x60 +#define SST_IPCSC 0x68 +#define SST_IPCLPESC 0x70 +#define SST_CLKCTL 0x78 +#define SST_CSR2 0x80 +#define SST_LTRC 0xE0 +#define SST_HMDC 0xE8 + +#define SST_SHIM_BEGIN SST_CSR +#define SST_SHIM_END SST_HDMC + +#define SST_DBGO 0xF0 + +#define SST_SHIM_SIZE 0x100 +#define SST_PWMCTRL 0x1000 + +/* SST Shim Register bits + * The register bit naming can differ between products. Some products also + * contain extra functionality. + */ + +/* CSR / CS */ +#define SST_CSR_RST (0x1 << 1) +#define SST_CSR_SBCS0 (0x1 << 2) +#define SST_CSR_SBCS1 (0x1 << 3) +#define SST_CSR_DCS(x) (x << 4) +#define SST_CSR_DCS_MASK (0x7 << 4) +#define SST_CSR_STALL (0x1 << 10) +#define SST_CSR_S0IOCS (0x1 << 21) +#define SST_CSR_S1IOCS (0x1 << 23) +#define SST_CSR_LPCS (0x1 << 31) +#define SST_CSR_24MHZ_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1 | SST_CSR_LPCS) +#define SST_CSR_24MHZ_NO_LPCS (SST_CSR_SBCS0 | SST_CSR_SBCS1) +#define SST_BYT_CSR_RST (0x1 << 0) +#define SST_BYT_CSR_VECTOR_SEL (0x1 << 1) +#define SST_BYT_CSR_STALL (0x1 << 2) +#define SST_BYT_CSR_PWAITMODE (0x1 << 3) + +/* ISRX / ISC */ +#define SST_ISRX_BUSY (0x1 << 1) +#define SST_ISRX_DONE (0x1 << 0) +#define SST_BYT_ISRX_REQUEST (0x1 << 1) + +/* ISRD / ISD */ +#define SST_ISRD_BUSY (0x1 << 1) +#define SST_ISRD_DONE (0x1 << 0) + +/* IMRX / IMC */ +#define SST_IMRX_BUSY (0x1 << 1) +#define SST_IMRX_DONE (0x1 << 0) +#define SST_BYT_IMRX_REQUEST (0x1 << 1) + +/* IMRD / IMD */ +#define SST_IMRD_DONE (0x1 << 0) +#define SST_IMRD_BUSY (0x1 << 1) +#define SST_IMRD_SSP0 (0x1 << 16) +#define SST_IMRD_DMAC0 (0x1 << 21) +#define SST_IMRD_DMAC1 (0x1 << 22) +#define SST_IMRD_DMAC (SST_IMRD_DMAC0 | SST_IMRD_DMAC1) + +/* IPCX / IPCC */ +#define SST_IPCX_DONE (0x1 << 30) +#define SST_IPCX_BUSY (0x1 << 31) +#define SST_BYT_IPCX_DONE ((u64)0x1 << 62) +#define SST_BYT_IPCX_BUSY ((u64)0x1 << 63) + +/* IPCD */ +#define SST_IPCD_DONE (0x1 << 30) +#define SST_IPCD_BUSY (0x1 << 31) +#define SST_BYT_IPCD_DONE ((u64)0x1 << 62) +#define SST_BYT_IPCD_BUSY ((u64)0x1 << 63) + +/* CLKCTL */ +#define SST_CLKCTL_SMOS(x) (x << 24) +#define SST_CLKCTL_MASK (3 << 24) +#define SST_CLKCTL_DCPLCG (1 << 18) +#define SST_CLKCTL_SCOE1 (1 << 17) +#define SST_CLKCTL_SCOE0 (1 << 16) + +/* CSR2 / CS2 */ +#define SST_CSR2_SDFD_SSP0 (1 << 1) +#define SST_CSR2_SDFD_SSP1 (1 << 2) + +/* LTRC */ +#define SST_LTRC_VAL(x) (x << 0) + +/* HMDC */ +#define SST_HMDC_HDDA0(x) (x << 0) +#define SST_HMDC_HDDA1(x) (x << 7) +#define SST_HMDC_HDDA_E0_CH0 1 +#define SST_HMDC_HDDA_E0_CH1 2 +#define SST_HMDC_HDDA_E0_CH2 4 +#define SST_HMDC_HDDA_E0_CH3 8 +#define SST_HMDC_HDDA_E1_CH0 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH0) +#define SST_HMDC_HDDA_E1_CH1 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH1) +#define SST_HMDC_HDDA_E1_CH2 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH2) +#define SST_HMDC_HDDA_E1_CH3 SST_HMDC_HDDA1(SST_HMDC_HDDA_E0_CH3) +#define SST_HMDC_HDDA_E0_ALLCH (SST_HMDC_HDDA_E0_CH0 | SST_HMDC_HDDA_E0_CH1 | \ + SST_HMDC_HDDA_E0_CH2 | SST_HMDC_HDDA_E0_CH3) +#define SST_HMDC_HDDA_E1_ALLCH (SST_HMDC_HDDA_E1_CH0 | SST_HMDC_HDDA_E1_CH1 | \ + SST_HMDC_HDDA_E1_CH2 | SST_HMDC_HDDA_E1_CH3) + + +/* SST Vendor Defined Registers and bits */ +#define SST_VDRTCTL0 0xa0 +#define SST_VDRTCTL1 0xa4 +#define SST_VDRTCTL2 0xa8 +#define SST_VDRTCTL3 0xaC + +/* VDRTCTL0 */ +#define SST_VDRTCL0_D3PGD (1 << 0) +#define SST_VDRTCL0_D3SRAMPGD (1 << 1) +#define SST_VDRTCL0_DSRAMPGE_SHIFT 12 +#define SST_VDRTCL0_DSRAMPGE_MASK (0xfffff << SST_VDRTCL0_DSRAMPGE_SHIFT) +#define SST_VDRTCL0_ISRAMPGE_SHIFT 2 +#define SST_VDRTCL0_ISRAMPGE_MASK (0x3ff << SST_VDRTCL0_ISRAMPGE_SHIFT) + +/* VDRTCTL2 */ +#define SST_VDRTCL2_DCLCGE (1 << 1) +#define SST_VDRTCL2_DTCGE (1 << 10) +#define SST_VDRTCL2_APLLSE_MASK (1 << 31) + +/* PMCS */ +#define SST_PMCS 0x84 +#define SST_PMCS_PS_MASK 0x3 + +struct sst_dsp; + +/* + * SST Device. + * + * This structure is populated by the SST core driver. + */ +struct sst_dsp_device { + /* Mandatory fields */ + struct sst_ops *ops; + irqreturn_t (*thread)(int irq, void *context); + void *thread_context; +}; + +/* + * SST Platform Data. + */ +struct sst_pdata { + /* ACPI data */ + u32 lpe_base; + u32 lpe_size; + u32 pcicfg_base; + u32 pcicfg_size; + u32 fw_base; + u32 fw_size; + int irq; + + /* Firmware */ + const struct firmware *fw; + + /* DMA */ + int resindex_dma_base; /* other fields invalid if equals to -1 */ + u32 dma_base; + u32 dma_size; + int dma_engine; + struct device *dma_dev; + + /* DSP */ + u32 id; + void *dsp; +}; + +#if IS_ENABLED(CONFIG_DW_DMAC_CORE) +/* Initialization */ +struct sst_dsp *sst_dsp_new(struct device *dev, + struct sst_dsp_device *sst_dev, struct sst_pdata *pdata); +void sst_dsp_free(struct sst_dsp *sst); +#endif + +/* SHIM Read / Write */ +void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value); +u32 sst_dsp_shim_read(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value); +void sst_dsp_shim_write64(struct sst_dsp *sst, u32 offset, u64 value); +u64 sst_dsp_shim_read64(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits64(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value); +void sst_dsp_shim_update_bits_forced(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value); + +/* SHIM Read / Write Unlocked for callers already holding sst lock */ +void sst_dsp_shim_write_unlocked(struct sst_dsp *sst, u32 offset, u32 value); +u32 sst_dsp_shim_read_unlocked(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits_unlocked(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value); +void sst_dsp_shim_write64_unlocked(struct sst_dsp *sst, u32 offset, u64 value); +u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset); +int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset, + u64 mask, u64 value); +void sst_dsp_shim_update_bits_forced_unlocked(struct sst_dsp *sst, u32 offset, + u32 mask, u32 value); + +/* Internal generic low-level SST IO functions - can be overidden */ +void sst_shim32_write(void __iomem *addr, u32 offset, u32 value); +u32 sst_shim32_read(void __iomem *addr, u32 offset); +void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value); +u64 sst_shim32_read64(void __iomem *addr, u32 offset); +void sst_memcpy_toio_32(struct sst_dsp *sst, + void __iomem *dest, void *src, size_t bytes); +void sst_memcpy_fromio_32(struct sst_dsp *sst, + void *dest, void __iomem *src, size_t bytes); + +/* DSP reset & boot */ +void sst_dsp_reset(struct sst_dsp *sst); +int sst_dsp_boot(struct sst_dsp *sst); +int sst_dsp_wake(struct sst_dsp *sst); +void sst_dsp_sleep(struct sst_dsp *sst); +void sst_dsp_stall(struct sst_dsp *sst); + +/* DMA */ +int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id); +void sst_dsp_dma_put_channel(struct sst_dsp *dsp); +int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size); +int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size); + +/* Msg IO */ +void sst_dsp_ipc_msg_tx(struct sst_dsp *dsp, u32 msg); +u32 sst_dsp_ipc_msg_rx(struct sst_dsp *dsp); + +/* Mailbox management */ +int sst_dsp_mailbox_init(struct sst_dsp *dsp, u32 inbox_offset, + size_t inbox_size, u32 outbox_offset, size_t outbox_size); +void sst_dsp_inbox_write(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_inbox_read(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_outbox_write(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_outbox_read(struct sst_dsp *dsp, void *message, size_t bytes); +void sst_dsp_mailbox_dump(struct sst_dsp *dsp, size_t bytes); +int sst_dsp_register_poll(struct sst_dsp *dsp, u32 offset, u32 mask, + u32 expected_value, u32 timeout, char *operation); + +/* Debug */ +void sst_dsp_dump(struct sst_dsp *sst); + +#endif diff --git a/sound/soc/intel/common/sst-firmware.c b/sound/soc/intel/common/sst-firmware.c new file mode 100644 index 000000000..6cfcc1042 --- /dev/null +++ b/sound/soc/intel/common/sst-firmware.c @@ -0,0 +1,1282 @@ +/* + * Intel SST Firmware Loader + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* supported DMA engine drivers */ +#include + +#include +#include + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" + +#define SST_DMA_RESOURCES 2 +#define SST_DSP_DMA_MAX_BURST 0x3 +#define SST_HSW_BLOCK_ANY 0xffffffff + +#define SST_HSW_MASK_DMA_ADDR_DSP 0xfff00000 + +struct sst_dma { + struct sst_dsp *sst; + + struct dw_dma_chip *chip; + + struct dma_async_tx_descriptor *desc; + struct dma_chan *ch; +}; + +static inline void sst_memcpy32(volatile void __iomem *dest, void *src, u32 bytes) +{ + u32 tmp = 0; + int i, m, n; + const u8 *src_byte = src; + + m = bytes / 4; + n = bytes % 4; + + /* __iowrite32_copy use 32bit size values so divide by 4 */ + __iowrite32_copy((void *)dest, src, m); + + if (n) { + for (i = 0; i < n; i++) + tmp |= (u32)*(src_byte + m * 4 + i) << (i * 8); + __iowrite32_copy((void *)(dest + m * 4), &tmp, 1); + } + +} + +static void sst_dma_transfer_complete(void *arg) +{ + struct sst_dsp *sst = (struct sst_dsp *)arg; + + dev_dbg(sst->dev, "DMA: callback\n"); +} + +static int sst_dsp_dma_copy(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + struct dma_async_tx_descriptor *desc; + struct sst_dma *dma = sst->dma; + + if (dma->ch == NULL) { + dev_err(sst->dev, "error: no DMA channel\n"); + return -ENODEV; + } + + dev_dbg(sst->dev, "DMA: src: 0x%lx dest 0x%lx size %zu\n", + (unsigned long)src_addr, (unsigned long)dest_addr, size); + + desc = dma->ch->device->device_prep_dma_memcpy(dma->ch, dest_addr, + src_addr, size, DMA_CTRL_ACK); + if (!desc){ + dev_err(sst->dev, "error: dma prep memcpy failed\n"); + return -EINVAL; + } + + desc->callback = sst_dma_transfer_complete; + desc->callback_param = sst; + + desc->tx_submit(desc); + dma_wait_for_async_tx(desc); + + return 0; +} + +/* copy to DSP */ +int sst_dsp_dma_copyto(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + return sst_dsp_dma_copy(sst, dest_addr | SST_HSW_MASK_DMA_ADDR_DSP, + src_addr, size); +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_copyto); + +/* copy from DSP */ +int sst_dsp_dma_copyfrom(struct sst_dsp *sst, dma_addr_t dest_addr, + dma_addr_t src_addr, size_t size) +{ + return sst_dsp_dma_copy(sst, dest_addr, + src_addr | SST_HSW_MASK_DMA_ADDR_DSP, size); +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_copyfrom); + +/* remove module from memory - callers hold locks */ +static void block_list_remove(struct sst_dsp *dsp, + struct list_head *block_list) +{ + struct sst_mem_block *block, *tmp; + int err; + + /* disable each block */ + list_for_each_entry(block, block_list, module_list) { + + if (block->ops && block->ops->disable) { + err = block->ops->disable(block); + if (err < 0) + dev_err(dsp->dev, + "error: cant disable block %d:%d\n", + block->type, block->index); + } + } + + /* mark each block as free */ + list_for_each_entry_safe(block, tmp, block_list, module_list) { + list_del(&block->module_list); + list_move(&block->list, &dsp->free_block_list); + dev_dbg(dsp->dev, "block freed %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + } +} + +/* prepare the memory block to receive data from host - callers hold locks */ +static int block_list_prepare(struct sst_dsp *dsp, + struct list_head *block_list) +{ + struct sst_mem_block *block; + int ret = 0; + + /* enable each block so that's it'e ready for data */ + list_for_each_entry(block, block_list, module_list) { + + if (block->ops && block->ops->enable && !block->users) { + ret = block->ops->enable(block); + if (ret < 0) { + dev_err(dsp->dev, + "error: cant disable block %d:%d\n", + block->type, block->index); + goto err; + } + } + } + return ret; + +err: + list_for_each_entry(block, block_list, module_list) { + if (block->ops && block->ops->disable) + block->ops->disable(block); + } + return ret; +} + +static struct dw_dma_chip *dw_probe(struct device *dev, struct resource *mem, + int irq) +{ + struct dw_dma_chip *chip; + int err; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return ERR_PTR(-ENOMEM); + + chip->irq = irq; + chip->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(chip->regs)) + return ERR_CAST(chip->regs); + + err = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); + if (err) + return ERR_PTR(err); + + chip->dev = dev; + + err = dw_dma_probe(chip); + if (err) + return ERR_PTR(err); + + return chip; +} + +static void dw_remove(struct dw_dma_chip *chip) +{ + dw_dma_remove(chip); +} + +static bool dma_chan_filter(struct dma_chan *chan, void *param) +{ + struct sst_dsp *dsp = (struct sst_dsp *)param; + + return chan->device->dev == dsp->dma_dev; +} + +int sst_dsp_dma_get_channel(struct sst_dsp *dsp, int chan_id) +{ + struct sst_dma *dma = dsp->dma; + struct dma_slave_config slave; + dma_cap_mask_t mask; + int ret; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_MEMCPY, mask); + + dma->ch = dma_request_channel(mask, dma_chan_filter, dsp); + if (dma->ch == NULL) { + dev_err(dsp->dev, "error: DMA request channel failed\n"); + return -EIO; + } + + memset(&slave, 0, sizeof(slave)); + slave.direction = DMA_MEM_TO_DEV; + slave.src_addr_width = + slave.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + slave.src_maxburst = slave.dst_maxburst = SST_DSP_DMA_MAX_BURST; + + ret = dmaengine_slave_config(dma->ch, &slave); + if (ret) { + dev_err(dsp->dev, "error: unable to set DMA slave config %d\n", + ret); + dma_release_channel(dma->ch); + dma->ch = NULL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_get_channel); + +void sst_dsp_dma_put_channel(struct sst_dsp *dsp) +{ + struct sst_dma *dma = dsp->dma; + + if (!dma->ch) + return; + + dma_release_channel(dma->ch); + dma->ch = NULL; +} +EXPORT_SYMBOL_GPL(sst_dsp_dma_put_channel); + +static int sst_dma_new(struct sst_dsp *sst) +{ + struct sst_pdata *sst_pdata = sst->pdata; + struct sst_dma *dma; + struct resource mem; + int ret = 0; + + if (sst->pdata->resindex_dma_base == -1) + /* DMA is not used, return and squelsh error messages */ + return 0; + + /* configure the correct platform data for whatever DMA engine + * is attached to the ADSP IP. */ + switch (sst->pdata->dma_engine) { + case SST_DMA_TYPE_DW: + break; + default: + dev_err(sst->dev, "error: invalid DMA engine %d\n", + sst->pdata->dma_engine); + return -EINVAL; + } + + dma = devm_kzalloc(sst->dev, sizeof(struct sst_dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + + dma->sst = sst; + + memset(&mem, 0, sizeof(mem)); + + mem.start = sst->addr.lpe_base + sst_pdata->dma_base; + mem.end = sst->addr.lpe_base + sst_pdata->dma_base + sst_pdata->dma_size - 1; + mem.flags = IORESOURCE_MEM; + + /* now register DMA engine device */ + dma->chip = dw_probe(sst->dma_dev, &mem, sst_pdata->irq); + if (IS_ERR(dma->chip)) { + dev_err(sst->dev, "error: DMA device register failed\n"); + ret = PTR_ERR(dma->chip); + goto err_dma_dev; + } + + sst->dma = dma; + sst->fw_use_dma = true; + return 0; + +err_dma_dev: + devm_kfree(sst->dev, dma); + return ret; +} + +static void sst_dma_free(struct sst_dma *dma) +{ + + if (dma == NULL) + return; + + if (dma->ch) + dma_release_channel(dma->ch); + + if (dma->chip) + dw_remove(dma->chip); + +} + +/* create new generic firmware object */ +struct sst_fw *sst_fw_new(struct sst_dsp *dsp, + const struct firmware *fw, void *private) +{ + struct sst_fw *sst_fw; + int err; + + if (!dsp->ops->parse_fw) + return NULL; + + sst_fw = kzalloc(sizeof(*sst_fw), GFP_KERNEL); + if (sst_fw == NULL) + return NULL; + + sst_fw->dsp = dsp; + sst_fw->private = private; + sst_fw->size = fw->size; + + /* allocate DMA buffer to store FW data */ + sst_fw->dma_buf = dma_alloc_coherent(dsp->dma_dev, sst_fw->size, + &sst_fw->dmable_fw_paddr, GFP_DMA | GFP_KERNEL); + if (!sst_fw->dma_buf) { + dev_err(dsp->dev, "error: DMA alloc failed\n"); + kfree(sst_fw); + return NULL; + } + + /* copy FW data to DMA-able memory */ + memcpy((void *)sst_fw->dma_buf, (void *)fw->data, fw->size); + + if (dsp->fw_use_dma) { + err = sst_dsp_dma_get_channel(dsp, 0); + if (err < 0) + goto chan_err; + } + + /* call core specific FW paser to load FW data into DSP */ + err = dsp->ops->parse_fw(sst_fw); + if (err < 0) { + dev_err(dsp->dev, "error: parse fw failed %d\n", err); + goto parse_err; + } + + if (dsp->fw_use_dma) + sst_dsp_dma_put_channel(dsp); + + mutex_lock(&dsp->mutex); + list_add(&sst_fw->list, &dsp->fw_list); + mutex_unlock(&dsp->mutex); + + return sst_fw; + +parse_err: + if (dsp->fw_use_dma) + sst_dsp_dma_put_channel(dsp); +chan_err: + dma_free_coherent(dsp->dma_dev, sst_fw->size, + sst_fw->dma_buf, + sst_fw->dmable_fw_paddr); + sst_fw->dma_buf = NULL; + kfree(sst_fw); + return NULL; +} +EXPORT_SYMBOL_GPL(sst_fw_new); + +int sst_fw_reload(struct sst_fw *sst_fw) +{ + struct sst_dsp *dsp = sst_fw->dsp; + int ret; + + dev_dbg(dsp->dev, "reloading firmware\n"); + + /* call core specific FW paser to load FW data into DSP */ + ret = dsp->ops->parse_fw(sst_fw); + if (ret < 0) + dev_err(dsp->dev, "error: parse fw failed %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(sst_fw_reload); + +void sst_fw_unload(struct sst_fw *sst_fw) +{ + struct sst_dsp *dsp = sst_fw->dsp; + struct sst_module *module, *mtmp; + struct sst_module_runtime *runtime, *rtmp; + + dev_dbg(dsp->dev, "unloading firmware\n"); + + mutex_lock(&dsp->mutex); + + /* check module by module */ + list_for_each_entry_safe(module, mtmp, &dsp->module_list, list) { + if (module->sst_fw == sst_fw) { + + /* remove runtime modules */ + list_for_each_entry_safe(runtime, rtmp, &module->runtime_list, list) { + + block_list_remove(dsp, &runtime->block_list); + list_del(&runtime->list); + kfree(runtime); + } + + /* now remove the module */ + block_list_remove(dsp, &module->block_list); + list_del(&module->list); + kfree(module); + } + } + + /* remove all scratch blocks */ + block_list_remove(dsp, &dsp->scratch_block_list); + + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_fw_unload); + +/* free single firmware object */ +void sst_fw_free(struct sst_fw *sst_fw) +{ + struct sst_dsp *dsp = sst_fw->dsp; + + mutex_lock(&dsp->mutex); + list_del(&sst_fw->list); + mutex_unlock(&dsp->mutex); + + if (sst_fw->dma_buf) + dma_free_coherent(dsp->dma_dev, sst_fw->size, sst_fw->dma_buf, + sst_fw->dmable_fw_paddr); + kfree(sst_fw); +} +EXPORT_SYMBOL_GPL(sst_fw_free); + +/* free all firmware objects */ +void sst_fw_free_all(struct sst_dsp *dsp) +{ + struct sst_fw *sst_fw, *t; + + mutex_lock(&dsp->mutex); + list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { + + list_del(&sst_fw->list); + dma_free_coherent(dsp->dev, sst_fw->size, sst_fw->dma_buf, + sst_fw->dmable_fw_paddr); + kfree(sst_fw); + } + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_fw_free_all); + +/* create a new SST generic module from FW template */ +struct sst_module *sst_module_new(struct sst_fw *sst_fw, + struct sst_module_template *template, void *private) +{ + struct sst_dsp *dsp = sst_fw->dsp; + struct sst_module *sst_module; + + sst_module = kzalloc(sizeof(*sst_module), GFP_KERNEL); + if (sst_module == NULL) + return NULL; + + sst_module->id = template->id; + sst_module->dsp = dsp; + sst_module->sst_fw = sst_fw; + sst_module->scratch_size = template->scratch_size; + sst_module->persistent_size = template->persistent_size; + sst_module->entry = template->entry; + sst_module->state = SST_MODULE_STATE_UNLOADED; + + INIT_LIST_HEAD(&sst_module->block_list); + INIT_LIST_HEAD(&sst_module->runtime_list); + + mutex_lock(&dsp->mutex); + list_add(&sst_module->list, &dsp->module_list); + mutex_unlock(&dsp->mutex); + + return sst_module; +} +EXPORT_SYMBOL_GPL(sst_module_new); + +/* free firmware module and remove from available list */ +void sst_module_free(struct sst_module *sst_module) +{ + struct sst_dsp *dsp = sst_module->dsp; + + mutex_lock(&dsp->mutex); + list_del(&sst_module->list); + mutex_unlock(&dsp->mutex); + + kfree(sst_module); +} +EXPORT_SYMBOL_GPL(sst_module_free); + +struct sst_module_runtime *sst_module_runtime_new(struct sst_module *module, + int id, void *private) +{ + struct sst_dsp *dsp = module->dsp; + struct sst_module_runtime *runtime; + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (runtime == NULL) + return NULL; + + runtime->id = id; + runtime->dsp = dsp; + runtime->module = module; + INIT_LIST_HEAD(&runtime->block_list); + + mutex_lock(&dsp->mutex); + list_add(&runtime->list, &module->runtime_list); + mutex_unlock(&dsp->mutex); + + return runtime; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_new); + +void sst_module_runtime_free(struct sst_module_runtime *runtime) +{ + struct sst_dsp *dsp = runtime->dsp; + + mutex_lock(&dsp->mutex); + list_del(&runtime->list); + mutex_unlock(&dsp->mutex); + + kfree(runtime); +} +EXPORT_SYMBOL_GPL(sst_module_runtime_free); + +static struct sst_mem_block *find_block(struct sst_dsp *dsp, + struct sst_block_allocator *ba) +{ + struct sst_mem_block *block; + + list_for_each_entry(block, &dsp->free_block_list, list) { + if (block->type == ba->type && block->offset == ba->offset) + return block; + } + + return NULL; +} + +/* Block allocator must be on block boundary */ +static int block_alloc_contiguous(struct sst_dsp *dsp, + struct sst_block_allocator *ba, struct list_head *block_list) +{ + struct list_head tmp = LIST_HEAD_INIT(tmp); + struct sst_mem_block *block; + u32 block_start = SST_HSW_BLOCK_ANY; + int size = ba->size, offset = ba->offset; + + while (ba->size > 0) { + + block = find_block(dsp, ba); + if (!block) { + list_splice(&tmp, &dsp->free_block_list); + + ba->size = size; + ba->offset = offset; + return -ENOMEM; + } + + list_move_tail(&block->list, &tmp); + ba->offset += block->size; + ba->size -= block->size; + } + ba->size = size; + ba->offset = offset; + + list_for_each_entry(block, &tmp, list) { + + if (block->offset < block_start) + block_start = block->offset; + + list_add(&block->module_list, block_list); + + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + } + + list_splice(&tmp, &dsp->used_block_list); + return 0; +} + +/* allocate first free DSP blocks for data - callers hold locks */ +static int block_alloc(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) +{ + struct sst_mem_block *block, *tmp; + int ret = 0; + + if (ba->size == 0) + return 0; + + /* find first free whole blocks that can hold module */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + if (ba->size > block->size) + continue; + + ba->offset = block->offset; + block->bytes_used = ba->size % block->size; + list_add(&block->module_list, block_list); + list_move(&block->list, &dsp->used_block_list); + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + return 0; + } + + /* then find free multiple blocks that can hold module */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + /* do we span > 1 blocks */ + if (ba->size > block->size) { + + /* align ba to block boundary */ + ba->offset = block->offset; + + ret = block_alloc_contiguous(dsp, ba, block_list); + if (ret == 0) + return ret; + + } + } + + /* not enough free block space */ + return -ENOMEM; +} + +int sst_alloc_blocks(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) +{ + int ret; + + dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", + ba->size, ba->offset, ba->type); + + mutex_lock(&dsp->mutex); + + ret = block_alloc(dsp, ba, block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: can't alloc blocks %d\n", ret); + goto out; + } + + /* prepare DSP blocks for module usage */ + ret = block_list_prepare(dsp, block_list); + if (ret < 0) + dev_err(dsp->dev, "error: prepare failed\n"); + +out: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_alloc_blocks); + +int sst_free_blocks(struct sst_dsp *dsp, struct list_head *block_list) +{ + mutex_lock(&dsp->mutex); + block_list_remove(dsp, block_list); + mutex_unlock(&dsp->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(sst_free_blocks); + +/* allocate memory blocks for static module addresses - callers hold locks */ +static int block_alloc_fixed(struct sst_dsp *dsp, struct sst_block_allocator *ba, + struct list_head *block_list) +{ + struct sst_mem_block *block, *tmp; + struct sst_block_allocator ba_tmp = *ba; + u32 end = ba->offset + ba->size, block_end; + int err; + + /* only IRAM/DRAM blocks are managed */ + if (ba->type != SST_MEM_IRAM && ba->type != SST_MEM_DRAM) + return 0; + + /* are blocks already attached to this module */ + list_for_each_entry_safe(block, tmp, block_list, module_list) { + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + block_end = block->offset + block->size; + + /* find block that holds section */ + if (ba->offset >= block->offset && end <= block_end) + return 0; + + /* does block span more than 1 section */ + if (ba->offset >= block->offset && ba->offset < block_end) { + + /* align ba to block boundary */ + ba_tmp.size -= block_end - ba->offset; + ba_tmp.offset = block_end; + err = block_alloc_contiguous(dsp, &ba_tmp, block_list); + if (err < 0) + return -ENOMEM; + + /* module already owns blocks */ + return 0; + } + } + + /* find first free blocks that can hold section in free list */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + block_end = block->offset + block->size; + + /* ignore blocks with wrong type */ + if (block->type != ba->type) + continue; + + /* find block that holds section */ + if (ba->offset >= block->offset && end <= block_end) { + + /* add block */ + list_move(&block->list, &dsp->used_block_list); + list_add(&block->module_list, block_list); + dev_dbg(dsp->dev, "block allocated %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + return 0; + } + + /* does block span more than 1 section */ + if (ba->offset >= block->offset && ba->offset < block_end) { + + /* add block */ + list_move(&block->list, &dsp->used_block_list); + list_add(&block->module_list, block_list); + /* align ba to block boundary */ + ba_tmp.size -= block_end - ba->offset; + ba_tmp.offset = block_end; + + err = block_alloc_contiguous(dsp, &ba_tmp, block_list); + if (err < 0) + return -ENOMEM; + + return 0; + } + } + + return -ENOMEM; +} + +/* Load fixed module data into DSP memory blocks */ +int sst_module_alloc_blocks(struct sst_module *module) +{ + struct sst_dsp *dsp = module->dsp; + struct sst_fw *sst_fw = module->sst_fw; + struct sst_block_allocator ba; + int ret; + + memset(&ba, 0, sizeof(ba)); + ba.size = module->size; + ba.type = module->type; + ba.offset = module->offset; + + dev_dbg(dsp->dev, "block request 0x%x bytes at offset 0x%x type %d\n", + ba.size, ba.offset, ba.type); + + mutex_lock(&dsp->mutex); + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &module->block_list); + if (ret < 0) { + dev_err(dsp->dev, + "error: no free blocks for section at offset 0x%x size 0x%x\n", + module->offset, module->size); + mutex_unlock(&dsp->mutex); + return -ENOMEM; + } + + /* prepare DSP blocks for module copy */ + ret = block_list_prepare(dsp, &module->block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: fw module prepare failed\n"); + goto err; + } + + /* copy partial module data to blocks */ + if (dsp->fw_use_dma) { + ret = sst_dsp_dma_copyto(dsp, + dsp->addr.lpe_base + module->offset, + sst_fw->dmable_fw_paddr + module->data_offset, + module->size); + if (ret < 0) { + dev_err(dsp->dev, "error: module copy failed\n"); + goto err; + } + } else + sst_memcpy32(dsp->addr.lpe + module->offset, module->data, + module->size); + + mutex_unlock(&dsp->mutex); + return ret; + +err: + block_list_remove(dsp, &module->block_list); + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_alloc_blocks); + +/* Unload entire module from DSP memory */ +int sst_module_free_blocks(struct sst_module *module) +{ + struct sst_dsp *dsp = module->dsp; + + mutex_lock(&dsp->mutex); + block_list_remove(dsp, &module->block_list); + mutex_unlock(&dsp->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(sst_module_free_blocks); + +int sst_module_runtime_alloc_blocks(struct sst_module_runtime *runtime, + int offset) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + struct sst_block_allocator ba; + int ret; + + if (module->persistent_size == 0) + return 0; + + memset(&ba, 0, sizeof(ba)); + ba.size = module->persistent_size; + ba.type = SST_MEM_DRAM; + + mutex_lock(&dsp->mutex); + + /* do we need to allocate at a fixed address ? */ + if (offset != 0) { + + ba.offset = offset; + + dev_dbg(dsp->dev, "persistent fixed block request 0x%x bytes type %d offset 0x%x\n", + ba.size, ba.type, ba.offset); + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &runtime->block_list); + + } else { + dev_dbg(dsp->dev, "persistent block request 0x%x bytes type %d\n", + ba.size, ba.type); + + /* alloc blocks that includes this section */ + ret = block_alloc(dsp, &ba, &runtime->block_list); + } + if (ret < 0) { + dev_err(dsp->dev, + "error: no free blocks for runtime module size 0x%x\n", + module->persistent_size); + mutex_unlock(&dsp->mutex); + return -ENOMEM; + } + runtime->persistent_offset = ba.offset; + + /* prepare DSP blocks for module copy */ + ret = block_list_prepare(dsp, &runtime->block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: runtime block prepare failed\n"); + goto err; + } + + mutex_unlock(&dsp->mutex); + return ret; + +err: + block_list_remove(dsp, &module->block_list); + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_alloc_blocks); + +int sst_module_runtime_free_blocks(struct sst_module_runtime *runtime) +{ + struct sst_dsp *dsp = runtime->dsp; + + mutex_lock(&dsp->mutex); + block_list_remove(dsp, &runtime->block_list); + mutex_unlock(&dsp->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_free_blocks); + +int sst_module_runtime_save(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + int ret = 0; + + dev_dbg(dsp->dev, "saving runtime %d memory at 0x%x size 0x%x\n", + runtime->id, runtime->persistent_offset, + module->persistent_size); + + context->buffer = dma_alloc_coherent(dsp->dma_dev, + module->persistent_size, + &context->dma_buffer, GFP_DMA | GFP_KERNEL); + if (!context->buffer) { + dev_err(dsp->dev, "error: DMA context alloc failed\n"); + return -ENOMEM; + } + + mutex_lock(&dsp->mutex); + + if (dsp->fw_use_dma) { + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) + goto err; + + ret = sst_dsp_dma_copyfrom(dsp, context->dma_buffer, + dsp->addr.lpe_base + runtime->persistent_offset, + module->persistent_size); + sst_dsp_dma_put_channel(dsp); + if (ret < 0) { + dev_err(dsp->dev, "error: context copy failed\n"); + goto err; + } + } else + sst_memcpy32(context->buffer, dsp->addr.lpe + + runtime->persistent_offset, + module->persistent_size); + +err: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_save); + +int sst_module_runtime_restore(struct sst_module_runtime *runtime, + struct sst_module_runtime_context *context) +{ + struct sst_dsp *dsp = runtime->dsp; + struct sst_module *module = runtime->module; + int ret = 0; + + dev_dbg(dsp->dev, "restoring runtime %d memory at 0x%x size 0x%x\n", + runtime->id, runtime->persistent_offset, + module->persistent_size); + + mutex_lock(&dsp->mutex); + + if (!context->buffer) { + dev_info(dsp->dev, "no context buffer need to restore!\n"); + goto err; + } + + if (dsp->fw_use_dma) { + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) + goto err; + + ret = sst_dsp_dma_copyto(dsp, + dsp->addr.lpe_base + runtime->persistent_offset, + context->dma_buffer, module->persistent_size); + sst_dsp_dma_put_channel(dsp); + if (ret < 0) { + dev_err(dsp->dev, "error: module copy failed\n"); + goto err; + } + } else + sst_memcpy32(dsp->addr.lpe + runtime->persistent_offset, + context->buffer, module->persistent_size); + + dma_free_coherent(dsp->dma_dev, module->persistent_size, + context->buffer, context->dma_buffer); + context->buffer = NULL; + +err: + mutex_unlock(&dsp->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_restore); + +/* register a DSP memory block for use with FW based modules */ +struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, + u32 size, enum sst_mem_type type, const struct sst_block_ops *ops, + u32 index, void *private) +{ + struct sst_mem_block *block; + + block = kzalloc(sizeof(*block), GFP_KERNEL); + if (block == NULL) + return NULL; + + block->offset = offset; + block->size = size; + block->index = index; + block->type = type; + block->dsp = dsp; + block->private = private; + block->ops = ops; + + mutex_lock(&dsp->mutex); + list_add(&block->list, &dsp->free_block_list); + mutex_unlock(&dsp->mutex); + + return block; +} +EXPORT_SYMBOL_GPL(sst_mem_block_register); + +/* unregister all DSP memory blocks */ +void sst_mem_block_unregister_all(struct sst_dsp *dsp) +{ + struct sst_mem_block *block, *tmp; + + mutex_lock(&dsp->mutex); + + /* unregister used blocks */ + list_for_each_entry_safe(block, tmp, &dsp->used_block_list, list) { + list_del(&block->list); + kfree(block); + } + + /* unregister free blocks */ + list_for_each_entry_safe(block, tmp, &dsp->free_block_list, list) { + list_del(&block->list); + kfree(block); + } + + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_mem_block_unregister_all); + +/* allocate scratch buffer blocks */ +int sst_block_alloc_scratch(struct sst_dsp *dsp) +{ + struct sst_module *module; + struct sst_block_allocator ba; + int ret; + + mutex_lock(&dsp->mutex); + + /* calculate required scratch size */ + dsp->scratch_size = 0; + list_for_each_entry(module, &dsp->module_list, list) { + dev_dbg(dsp->dev, "module %d scratch req 0x%x bytes\n", + module->id, module->scratch_size); + if (dsp->scratch_size < module->scratch_size) + dsp->scratch_size = module->scratch_size; + } + + dev_dbg(dsp->dev, "scratch buffer required is 0x%x bytes\n", + dsp->scratch_size); + + if (dsp->scratch_size == 0) { + dev_info(dsp->dev, "no modules need scratch buffer\n"); + mutex_unlock(&dsp->mutex); + return 0; + } + + /* allocate blocks for module scratch buffers */ + dev_dbg(dsp->dev, "allocating scratch blocks\n"); + + ba.size = dsp->scratch_size; + ba.type = SST_MEM_DRAM; + + /* do we need to allocate at fixed offset */ + if (dsp->scratch_offset != 0) { + + dev_dbg(dsp->dev, "block request 0x%x bytes type %d at 0x%x\n", + ba.size, ba.type, ba.offset); + + ba.offset = dsp->scratch_offset; + + /* alloc blocks that includes this section */ + ret = block_alloc_fixed(dsp, &ba, &dsp->scratch_block_list); + + } else { + dev_dbg(dsp->dev, "block request 0x%x bytes type %d\n", + ba.size, ba.type); + + ba.offset = 0; + ret = block_alloc(dsp, &ba, &dsp->scratch_block_list); + } + if (ret < 0) { + dev_err(dsp->dev, "error: can't alloc scratch blocks\n"); + mutex_unlock(&dsp->mutex); + return ret; + } + + ret = block_list_prepare(dsp, &dsp->scratch_block_list); + if (ret < 0) { + dev_err(dsp->dev, "error: scratch block prepare failed\n"); + mutex_unlock(&dsp->mutex); + return ret; + } + + /* assign the same offset of scratch to each module */ + dsp->scratch_offset = ba.offset; + mutex_unlock(&dsp->mutex); + return dsp->scratch_size; +} +EXPORT_SYMBOL_GPL(sst_block_alloc_scratch); + +/* free all scratch blocks */ +void sst_block_free_scratch(struct sst_dsp *dsp) +{ + mutex_lock(&dsp->mutex); + block_list_remove(dsp, &dsp->scratch_block_list); + mutex_unlock(&dsp->mutex); +} +EXPORT_SYMBOL_GPL(sst_block_free_scratch); + +/* get a module from it's unique ID */ +struct sst_module *sst_module_get_from_id(struct sst_dsp *dsp, u32 id) +{ + struct sst_module *module; + + mutex_lock(&dsp->mutex); + + list_for_each_entry(module, &dsp->module_list, list) { + if (module->id == id) { + mutex_unlock(&dsp->mutex); + return module; + } + } + + mutex_unlock(&dsp->mutex); + return NULL; +} +EXPORT_SYMBOL_GPL(sst_module_get_from_id); + +struct sst_module_runtime *sst_module_runtime_get_from_id( + struct sst_module *module, u32 id) +{ + struct sst_module_runtime *runtime; + struct sst_dsp *dsp = module->dsp; + + mutex_lock(&dsp->mutex); + + list_for_each_entry(runtime, &module->runtime_list, list) { + if (runtime->id == id) { + mutex_unlock(&dsp->mutex); + return runtime; + } + } + + mutex_unlock(&dsp->mutex); + return NULL; +} +EXPORT_SYMBOL_GPL(sst_module_runtime_get_from_id); + +/* returns block address in DSP address space */ +u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, + enum sst_mem_type type) +{ + switch (type) { + case SST_MEM_IRAM: + return offset - dsp->addr.iram_offset + + dsp->addr.dsp_iram_offset; + case SST_MEM_DRAM: + return offset - dsp->addr.dram_offset + + dsp->addr.dsp_dram_offset; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(sst_dsp_get_offset); + +struct sst_dsp *sst_dsp_new(struct device *dev, + struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) +{ + struct sst_dsp *sst; + int err; + + dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); + + sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); + if (sst == NULL) + return NULL; + + spin_lock_init(&sst->spinlock); + mutex_init(&sst->mutex); + sst->dev = dev; + sst->dma_dev = pdata->dma_dev; + sst->thread_context = sst_dev->thread_context; + sst->sst_dev = sst_dev; + sst->id = pdata->id; + sst->irq = pdata->irq; + sst->ops = sst_dev->ops; + sst->pdata = pdata; + INIT_LIST_HEAD(&sst->used_block_list); + INIT_LIST_HEAD(&sst->free_block_list); + INIT_LIST_HEAD(&sst->module_list); + INIT_LIST_HEAD(&sst->fw_list); + INIT_LIST_HEAD(&sst->scratch_block_list); + + /* Initialise SST Audio DSP */ + if (sst->ops->init) { + err = sst->ops->init(sst, pdata); + if (err < 0) + return NULL; + } + + /* Register the ISR */ + err = request_threaded_irq(sst->irq, sst->ops->irq_handler, + sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); + if (err) + goto irq_err; + + err = sst_dma_new(sst); + if (err) { + dev_err(dev, "sst_dma_new failed %d\n", err); + goto dma_err; + } + + return sst; + +dma_err: + free_irq(sst->irq, sst); +irq_err: + if (sst->ops->free) + sst->ops->free(sst); + + return NULL; +} +EXPORT_SYMBOL_GPL(sst_dsp_new); + +void sst_dsp_free(struct sst_dsp *sst) +{ + free_irq(sst->irq, sst); + if (sst->ops->free) + sst->ops->free(sst); + + sst_dma_free(sst->dma); +} +EXPORT_SYMBOL_GPL(sst_dsp_free); + +MODULE_DESCRIPTION("Intel SST Firmware Loader"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-ipc.c b/sound/soc/intel/common/sst-ipc.c new file mode 100644 index 000000000..771734fd7 --- /dev/null +++ b/sound/soc/intel/common/sst-ipc.c @@ -0,0 +1,329 @@ +/* + * Intel SST generic IPC Support + * + * Copyright (C) 2015, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sst-dsp.h" +#include "sst-dsp-priv.h" +#include "sst-ipc.h" + +/* IPC message timeout (msecs) */ +#define IPC_TIMEOUT_MSECS 300 + +#define IPC_EMPTY_LIST_SIZE 8 + +/* locks held by caller */ +static struct ipc_message *msg_get_empty(struct sst_generic_ipc *ipc) +{ + struct ipc_message *msg = NULL; + + if (!list_empty(&ipc->empty_list)) { + msg = list_first_entry(&ipc->empty_list, struct ipc_message, + list); + list_del(&msg->list); + } + + return msg; +} + +static int tx_wait_done(struct sst_generic_ipc *ipc, + struct ipc_message *msg, void *rx_data) +{ + unsigned long flags; + int ret; + + /* wait for DSP completion (in all cases atm inc pending) */ + ret = wait_event_timeout(msg->waitq, msg->complete, + msecs_to_jiffies(IPC_TIMEOUT_MSECS)); + + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + if (ret == 0) { + if (ipc->ops.shim_dbg != NULL) + ipc->ops.shim_dbg(ipc, "message timeout"); + + list_del(&msg->list); + ret = -ETIMEDOUT; + } else { + + /* copy the data returned from DSP */ + if (msg->rx_size) + memcpy(rx_data, msg->rx_data, msg->rx_size); + ret = msg->errno; + } + + list_add_tail(&msg->list, &ipc->empty_list); + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + return ret; +} + +static int ipc_tx_message(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, + size_t rx_bytes, int wait) +{ + struct ipc_message *msg; + unsigned long flags; + + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + + msg = msg_get_empty(ipc); + if (msg == NULL) { + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + return -EBUSY; + } + + msg->header = header; + msg->tx_size = tx_bytes; + msg->rx_size = rx_bytes; + msg->wait = wait; + msg->errno = 0; + msg->pending = false; + msg->complete = false; + + if ((tx_bytes) && (ipc->ops.tx_data_copy != NULL)) + ipc->ops.tx_data_copy(msg, tx_data, tx_bytes); + + list_add_tail(&msg->list, &ipc->tx_list); + schedule_work(&ipc->kwork); + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + + if (wait) + return tx_wait_done(ipc, msg, rx_data); + else + return 0; +} + +static int msg_empty_list_init(struct sst_generic_ipc *ipc) +{ + int i; + + ipc->msg = kcalloc(IPC_EMPTY_LIST_SIZE, sizeof(struct ipc_message), + GFP_KERNEL); + if (ipc->msg == NULL) + return -ENOMEM; + + for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { + ipc->msg[i].tx_data = kzalloc(ipc->tx_data_max_size, GFP_KERNEL); + if (ipc->msg[i].tx_data == NULL) + goto free_mem; + + ipc->msg[i].rx_data = kzalloc(ipc->rx_data_max_size, GFP_KERNEL); + if (ipc->msg[i].rx_data == NULL) { + kfree(ipc->msg[i].tx_data); + goto free_mem; + } + + init_waitqueue_head(&ipc->msg[i].waitq); + list_add(&ipc->msg[i].list, &ipc->empty_list); + } + + return 0; + +free_mem: + while (i > 0) { + kfree(ipc->msg[i-1].tx_data); + kfree(ipc->msg[i-1].rx_data); + --i; + } + kfree(ipc->msg); + + return -ENOMEM; +} + +static void ipc_tx_msgs(struct work_struct *work) +{ + struct sst_generic_ipc *ipc = + container_of(work, struct sst_generic_ipc, kwork); + struct ipc_message *msg; + + spin_lock_irq(&ipc->dsp->spinlock); + + while (!list_empty(&ipc->tx_list) && !ipc->pending) { + /* if the DSP is busy, we will TX messages after IRQ. + * also postpone if we are in the middle of processing + * completion irq + */ + if (ipc->ops.is_dsp_busy && ipc->ops.is_dsp_busy(ipc->dsp)) { + dev_dbg(ipc->dev, "ipc_tx_msgs dsp busy\n"); + break; + } + + msg = list_first_entry(&ipc->tx_list, struct ipc_message, list); + list_move(&msg->list, &ipc->rx_list); + + if (ipc->ops.tx_msg != NULL) + ipc->ops.tx_msg(ipc, msg); + } + + spin_unlock_irq(&ipc->dsp->spinlock); +} + +int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) +{ + int ret; + + /* + * DSP maybe in lower power active state, so + * check if the DSP supports DSP lp On method + * if so invoke that before sending IPC + */ + if (ipc->ops.check_dsp_lp_on) + if (ipc->ops.check_dsp_lp_on(ipc->dsp, true)) + return -EIO; + + ret = ipc_tx_message(ipc, header, tx_data, tx_bytes, + rx_data, rx_bytes, 1); + + if (ipc->ops.check_dsp_lp_on) + if (ipc->ops.check_dsp_lp_on(ipc->dsp, false)) + return -EIO; + + return ret; +} +EXPORT_SYMBOL_GPL(sst_ipc_tx_message_wait); + +int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes) +{ + return ipc_tx_message(ipc, header, tx_data, tx_bytes, + NULL, 0, 0); +} +EXPORT_SYMBOL_GPL(sst_ipc_tx_message_nowait); + +int sst_ipc_tx_message_nopm(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) +{ + return ipc_tx_message(ipc, header, tx_data, tx_bytes, + rx_data, rx_bytes, 1); +} +EXPORT_SYMBOL_GPL(sst_ipc_tx_message_nopm); + +struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc, + u64 header) +{ + struct ipc_message *msg; + u64 mask; + + if (ipc->ops.reply_msg_match != NULL) + header = ipc->ops.reply_msg_match(header, &mask); + else + mask = (u64)-1; + + if (list_empty(&ipc->rx_list)) { + dev_err(ipc->dev, "error: rx list empty but received 0x%llx\n", + header); + return NULL; + } + + list_for_each_entry(msg, &ipc->rx_list, list) { + if ((msg->header & mask) == header) + return msg; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(sst_ipc_reply_find_msg); + +/* locks held by caller */ +void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc, + struct ipc_message *msg) +{ + msg->complete = true; + + if (!msg->wait) + list_add_tail(&msg->list, &ipc->empty_list); + else + wake_up(&msg->waitq); +} +EXPORT_SYMBOL_GPL(sst_ipc_tx_msg_reply_complete); + +void sst_ipc_drop_all(struct sst_generic_ipc *ipc) +{ + struct ipc_message *msg, *tmp; + unsigned long flags; + int tx_drop_cnt = 0, rx_drop_cnt = 0; + + /* drop all TX and Rx messages before we stall + reset DSP */ + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + + list_for_each_entry_safe(msg, tmp, &ipc->tx_list, list) { + list_move(&msg->list, &ipc->empty_list); + tx_drop_cnt++; + } + + list_for_each_entry_safe(msg, tmp, &ipc->rx_list, list) { + list_move(&msg->list, &ipc->empty_list); + rx_drop_cnt++; + } + + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + + if (tx_drop_cnt || rx_drop_cnt) + dev_err(ipc->dev, "dropped IPC msg RX=%d, TX=%d\n", + tx_drop_cnt, rx_drop_cnt); +} +EXPORT_SYMBOL_GPL(sst_ipc_drop_all); + +int sst_ipc_init(struct sst_generic_ipc *ipc) +{ + int ret; + + INIT_LIST_HEAD(&ipc->tx_list); + INIT_LIST_HEAD(&ipc->rx_list); + INIT_LIST_HEAD(&ipc->empty_list); + init_waitqueue_head(&ipc->wait_txq); + + ret = msg_empty_list_init(ipc); + if (ret < 0) + return -ENOMEM; + + INIT_WORK(&ipc->kwork, ipc_tx_msgs); + return 0; +} +EXPORT_SYMBOL_GPL(sst_ipc_init); + +void sst_ipc_fini(struct sst_generic_ipc *ipc) +{ + int i; + + cancel_work_sync(&ipc->kwork); + + if (ipc->msg) { + for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { + kfree(ipc->msg[i].tx_data); + kfree(ipc->msg[i].rx_data); + } + kfree(ipc->msg); + } +} +EXPORT_SYMBOL_GPL(sst_ipc_fini); + +/* Module information */ +MODULE_AUTHOR("Jin Yao"); +MODULE_DESCRIPTION("Intel SST IPC generic"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/common/sst-ipc.h b/sound/soc/intel/common/sst-ipc.h new file mode 100644 index 000000000..7ed42a640 --- /dev/null +++ b/sound/soc/intel/common/sst-ipc.h @@ -0,0 +1,96 @@ +/* + * Intel SST generic IPC Support + * + * Copyright (C) 2015, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SST_GENERIC_IPC_H +#define __SST_GENERIC_IPC_H + +#include +#include +#include +#include +#include +#include + +#define IPC_MAX_MAILBOX_BYTES 256 + +struct ipc_message { + struct list_head list; + u64 header; + + /* direction wrt host CPU */ + char *tx_data; + size_t tx_size; + char *rx_data; + size_t rx_size; + + wait_queue_head_t waitq; + bool pending; + bool complete; + bool wait; + int errno; +}; + +struct sst_generic_ipc; + +struct sst_plat_ipc_ops { + void (*tx_msg)(struct sst_generic_ipc *, struct ipc_message *); + void (*shim_dbg)(struct sst_generic_ipc *, const char *); + void (*tx_data_copy)(struct ipc_message *, char *, size_t); + u64 (*reply_msg_match)(u64 header, u64 *mask); + bool (*is_dsp_busy)(struct sst_dsp *dsp); + int (*check_dsp_lp_on)(struct sst_dsp *dsp, bool state); +}; + +/* SST generic IPC data */ +struct sst_generic_ipc { + struct device *dev; + struct sst_dsp *dsp; + + /* IPC messaging */ + struct list_head tx_list; + struct list_head rx_list; + struct list_head empty_list; + wait_queue_head_t wait_txq; + struct task_struct *tx_thread; + struct work_struct kwork; + bool pending; + struct ipc_message *msg; + int tx_data_max_size; + int rx_data_max_size; + + struct sst_plat_ipc_ops ops; +}; + +int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes); + +int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes); + +int sst_ipc_tx_message_nopm(struct sst_generic_ipc *ipc, u64 header, + void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes); + +struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc, + u64 header); + +void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc, + struct ipc_message *msg); + +void sst_ipc_drop_all(struct sst_generic_ipc *ipc); +int sst_ipc_init(struct sst_generic_ipc *ipc); +void sst_ipc_fini(struct sst_generic_ipc *ipc); + +#endif diff --git a/sound/soc/intel/haswell/Makefile b/sound/soc/intel/haswell/Makefile new file mode 100644 index 000000000..9c1723112 --- /dev/null +++ b/sound/soc/intel/haswell/Makefile @@ -0,0 +1,4 @@ +snd-soc-sst-haswell-pcm-objs := \ + sst-haswell-ipc.o sst-haswell-pcm.o sst-haswell-dsp.o + +obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += snd-soc-sst-haswell-pcm.o diff --git a/sound/soc/intel/haswell/sst-haswell-dsp.c b/sound/soc/intel/haswell/sst-haswell-dsp.c new file mode 100644 index 000000000..a28220e67 --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-dsp.c @@ -0,0 +1,714 @@ +/* + * Intel Haswell SST DSP driver + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "../haswell/sst-haswell-ipc.h" + +#include + +#define SST_HSW_FW_SIGNATURE_SIZE 4 +#define SST_HSW_FW_SIGN "$SST" +#define SST_HSW_FW_LIB_SIGN "$LIB" + +#define SST_WPT_SHIM_OFFSET 0xFB000 +#define SST_LP_SHIM_OFFSET 0xE7000 +#define SST_WPT_IRAM_OFFSET 0xA0000 +#define SST_LP_IRAM_OFFSET 0x80000 +#define SST_WPT_DSP_DRAM_OFFSET 0x400000 +#define SST_WPT_DSP_IRAM_OFFSET 0x00000 +#define SST_LPT_DSP_DRAM_OFFSET 0x400000 +#define SST_LPT_DSP_IRAM_OFFSET 0x00000 + +#define SST_SHIM_PM_REG 0x84 + +#define SST_HSW_IRAM 1 +#define SST_HSW_DRAM 2 +#define SST_HSW_REGS 3 + +struct dma_block_info { + __le32 type; /* IRAM/DRAM */ + __le32 size; /* Bytes */ + __le32 ram_offset; /* Offset in I/DRAM */ + __le32 rsvd; /* Reserved field */ +} __attribute__((packed)); + +struct fw_module_info { + __le32 persistent_size; + __le32 scratch_size; +} __attribute__((packed)); + +struct fw_header { + unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* FW signature */ + __le32 file_size; /* size of fw minus this header */ + __le32 modules; /* # of modules */ + __le32 file_format; /* version of header format */ + __le32 reserved[4]; +} __attribute__((packed)); + +struct fw_module_header { + unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* module signature */ + __le32 mod_size; /* size of module */ + __le32 blocks; /* # of blocks */ + __le16 padding; + __le16 type; /* codec type, pp lib */ + __le32 entry_point; + struct fw_module_info info; +} __attribute__((packed)); + +static void hsw_free(struct sst_dsp *sst); + +static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw, + struct fw_module_header *module) +{ + struct dma_block_info *block; + struct sst_module *mod; + struct sst_module_template template; + int count, ret; + void __iomem *ram; + int type = le16_to_cpu(module->type); + int entry_point = le32_to_cpu(module->entry_point); + + /* TODO: allowed module types need to be configurable */ + if (type != SST_HSW_MODULE_BASE_FW && + type != SST_HSW_MODULE_PCM_SYSTEM && + type != SST_HSW_MODULE_PCM && + type != SST_HSW_MODULE_PCM_REFERENCE && + type != SST_HSW_MODULE_PCM_CAPTURE && + type != SST_HSW_MODULE_WAVES && + type != SST_HSW_MODULE_LPAL) + return 0; + + dev_dbg(dsp->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n", + module->signature, module->mod_size, + module->blocks, type); + dev_dbg(dsp->dev, " entrypoint 0x%x\n", entry_point); + dev_dbg(dsp->dev, " persistent 0x%x scratch 0x%x\n", + module->info.persistent_size, module->info.scratch_size); + + memset(&template, 0, sizeof(template)); + template.id = type; + template.entry = entry_point - 4; + template.persistent_size = le32_to_cpu(module->info.persistent_size); + template.scratch_size = le32_to_cpu(module->info.scratch_size); + + mod = sst_module_new(fw, &template, NULL); + if (mod == NULL) + return -ENOMEM; + + block = (void *)module + sizeof(*module); + + for (count = 0; count < le32_to_cpu(module->blocks); count++) { + + if (le32_to_cpu(block->size) <= 0) { + dev_err(dsp->dev, + "error: block %d size invalid\n", count); + sst_module_free(mod); + return -EINVAL; + } + + switch (le32_to_cpu(block->type)) { + case SST_HSW_IRAM: + ram = dsp->addr.lpe; + mod->offset = le32_to_cpu(block->ram_offset) + + dsp->addr.iram_offset; + mod->type = SST_MEM_IRAM; + break; + case SST_HSW_DRAM: + case SST_HSW_REGS: + ram = dsp->addr.lpe; + mod->offset = le32_to_cpu(block->ram_offset); + mod->type = SST_MEM_DRAM; + break; + default: + dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n", + block->type, count); + sst_module_free(mod); + return -EINVAL; + } + + mod->size = le32_to_cpu(block->size); + mod->data = (void *)block + sizeof(*block); + mod->data_offset = mod->data - fw->dma_buf; + + dev_dbg(dsp->dev, "module block %d type 0x%x " + "size 0x%x ==> ram %p offset 0x%x\n", + count, mod->type, block->size, ram, + block->ram_offset); + + ret = sst_module_alloc_blocks(mod); + if (ret < 0) { + dev_err(dsp->dev, "error: could not allocate blocks for module %d\n", + count); + sst_module_free(mod); + return ret; + } + + block = (void *)block + sizeof(*block) + + le32_to_cpu(block->size); + } + mod->state = SST_MODULE_STATE_LOADED; + + return 0; +} + +static int hsw_parse_fw_image(struct sst_fw *sst_fw) +{ + struct fw_header *header; + struct fw_module_header *module; + struct sst_dsp *dsp = sst_fw->dsp; + int ret, count; + + /* Read the header information from the data pointer */ + header = (struct fw_header *)sst_fw->dma_buf; + + /* verify FW */ + if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) || + (sst_fw->size != + le32_to_cpu(header->file_size) + sizeof(*header))) { + dev_err(dsp->dev, "error: invalid fw sign/filesize mismatch\n"); + return -EINVAL; + } + + dev_dbg(dsp->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n", + header->file_size, header->modules, + header->file_format, sizeof(*header)); + + /* parse each module */ + module = (void *)sst_fw->dma_buf + sizeof(*header); + for (count = 0; count < le32_to_cpu(header->modules); count++) { + + /* module */ + ret = hsw_parse_module(dsp, sst_fw, module); + if (ret < 0) { + dev_err(dsp->dev, "error: invalid module %d\n", count); + return ret; + } + module = (void *)module + sizeof(*module) + + le32_to_cpu(module->mod_size); + } + + return 0; +} + +static irqreturn_t hsw_irq(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + u32 isr; + int ret = IRQ_NONE; + + spin_lock(&sst->spinlock); + + /* Interrupt arrived, check src */ + isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); + if (isr & SST_ISRX_DONE) { + trace_sst_irq_done(isr, + sst_dsp_shim_read_unlocked(sst, SST_IMRX)); + + /* Mask Done interrupt before return */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_DONE, SST_IMRX_DONE); + ret = IRQ_WAKE_THREAD; + } + + if (isr & SST_ISRX_BUSY) { + trace_sst_irq_busy(isr, + sst_dsp_shim_read_unlocked(sst, SST_IMRX)); + + /* Mask Busy interrupt before return */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_BUSY, SST_IMRX_BUSY); + ret = IRQ_WAKE_THREAD; + } + + spin_unlock(&sst->spinlock); + return ret; +} + +static void hsw_set_dsp_D3(struct sst_dsp *sst) +{ + u32 val; + u32 reg; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* enable power gating and switch off DRAM & IRAM blocks */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + val |= SST_VDRTCL0_DSRAMPGE_MASK | + SST_VDRTCL0_ISRAMPGE_MASK; + val &= ~(SST_VDRTCL0_D3PGD | SST_VDRTCL0_D3SRAMPGD); + writel(val, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* switch off audio PLL */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_APLLSE_MASK; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* disable MCLK(clkctl.smos = 0) */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, + SST_CLKCTL_MASK, 0); + + /* Set D3 state, delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_PMCS); + val |= SST_PMCS_PS_MASK; + writel(val, sst->addr.pci_cfg + SST_PMCS); + udelay(50); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + +} + +static void hsw_reset(struct sst_dsp *sst) +{ + /* put DSP into reset and stall */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_RST | SST_CSR_STALL, + SST_CSR_RST | SST_CSR_STALL); + + /* keep in reset for 10ms */ + mdelay(10); + + /* take DSP out of reset and keep stalled for FW loading */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL); +} + +static int hsw_set_dsp_D0(struct sst_dsp *sst) +{ + int tries = 10; + u32 reg, fw_dump_bit; + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~(SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE); + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* Disable D3PG (VDRTCTL0.D3PGD = 1) */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + reg |= SST_VDRTCL0_D3PGD; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* Set D0 state */ + reg = readl(sst->addr.pci_cfg + SST_PMCS); + reg &= ~SST_PMCS_PS_MASK; + writel(reg, sst->addr.pci_cfg + SST_PMCS); + + /* check that ADSP shim is enabled */ + while (tries--) { + reg = readl(sst->addr.pci_cfg + SST_PMCS) & SST_PMCS_PS_MASK; + if (reg == 0) + goto finish; + + msleep(1); + } + + return -ENODEV; + +finish: + /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, + SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0); + + /* stall DSP core, set clk to 192/96Mhz */ + sst_dsp_shim_update_bits_unlocked(sst, + SST_CSR, SST_CSR_STALL | SST_CSR_DCS_MASK, + SST_CSR_STALL | SST_CSR_DCS(4)); + + /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL, + SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0, + SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0); + + /* Stall and reset core, set CSR */ + hsw_reset(sst); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg |= SST_VDRTCL2_DCLCGE | SST_VDRTCL2_DTCGE; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + + /* switch on audio PLL */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + reg &= ~SST_VDRTCL2_APLLSE_MASK; + writel(reg, sst->addr.pci_cfg + SST_VDRTCTL2); + + /* set default power gating control, enable power gating control for all blocks. that is, + can't be accessed, please enable each block before accessing. */ + reg = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + reg |= SST_VDRTCL0_DSRAMPGE_MASK | SST_VDRTCL0_ISRAMPGE_MASK; + /* for D0, always enable the block(DSRAM[0]) used for FW dump */ + fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; + writel(reg & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + + /* disable DMA finish function for SSP0 & SSP1 */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1, + SST_CSR2_SDFD_SSP1); + + /* set on-demond mode on engine 0,1 for all channels */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); + + /* Enable Interrupt from both sides */ + sst_dsp_shim_update_bits(sst, SST_IMRX, (SST_IMRX_BUSY | SST_IMRX_DONE), + 0x0); + sst_dsp_shim_update_bits(sst, SST_IMRD, (SST_IMRD_DONE | SST_IMRD_BUSY | + SST_IMRD_SSP0 | SST_IMRD_DMAC), 0x0); + + /* clear IPC registers */ + sst_dsp_shim_write(sst, SST_IPCX, 0x0); + sst_dsp_shim_write(sst, SST_IPCD, 0x0); + sst_dsp_shim_write(sst, 0x80, 0x6); + sst_dsp_shim_write(sst, 0xe0, 0x300a); + + return 0; +} + +static void hsw_boot(struct sst_dsp *sst) +{ + /* set oportunistic mode on engine 0,1 for all channels */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, 0); + + /* set DSP to RUN */ + sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0); +} + +static void hsw_stall(struct sst_dsp *sst) +{ + /* stall DSP */ + sst_dsp_shim_update_bits(sst, SST_CSR, + SST_CSR_24MHZ_LPCS | SST_CSR_STALL, + SST_CSR_STALL | SST_CSR_24MHZ_LPCS); +} + +static void hsw_sleep(struct sst_dsp *sst) +{ + dev_dbg(sst->dev, "HSW_PM dsp runtime suspend\n"); + + /* put DSP into reset and stall */ + sst_dsp_shim_update_bits(sst, SST_CSR, + SST_CSR_24MHZ_LPCS | SST_CSR_RST | SST_CSR_STALL, + SST_CSR_RST | SST_CSR_STALL | SST_CSR_24MHZ_LPCS); + + hsw_set_dsp_D3(sst); + dev_dbg(sst->dev, "HSW_PM dsp runtime suspend exit\n"); +} + +static int hsw_wake(struct sst_dsp *sst) +{ + int ret; + + dev_dbg(sst->dev, "HSW_PM dsp runtime resume\n"); + + ret = hsw_set_dsp_D0(sst); + if (ret < 0) + return ret; + + dev_dbg(sst->dev, "HSW_PM dsp runtime resume exit\n"); + + return 0; +} + +struct sst_adsp_memregion { + u32 start; + u32 end; + int blocks; + enum sst_mem_type type; +}; + +/* lynx point ADSP mem regions */ +static const struct sst_adsp_memregion lp_region[] = { + {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */ + {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */ + {0x80000, 0xE0000, 12, SST_MEM_IRAM}, /* I-SRAM - 12 * 32kB */ +}; + +/* wild cat point ADSP mem regions */ +static const struct sst_adsp_memregion wpt_region[] = { + {0x00000, 0xA0000, 20, SST_MEM_DRAM}, /* D-SRAM0,D-SRAM1,D-SRAM2 - 20 * 32kB */ + {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */ +}; + +static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + /* ADSP DRAM & IRAM */ + sst->addr.lpe_base = pdata->lpe_base; + sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size); + if (!sst->addr.lpe) + return -ENODEV; + + /* ADSP PCI MMIO config space */ + sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size); + if (!sst->addr.pci_cfg) { + iounmap(sst->addr.lpe); + return -ENODEV; + } + + /* SST Shim */ + sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset; + return 0; +} + +struct sst_sram_shift { + u32 dev_id; /* SST Device IDs */ + u32 iram_shift; + u32 dram_shift; +}; + +static const struct sst_sram_shift sram_shift[] = { + {SST_DEV_ID_LYNX_POINT, 6, 16}, /* lp */ + {SST_DEV_ID_WILDCAT_POINT, 2, 12}, /* wpt */ +}; + +static u32 hsw_block_get_bit(struct sst_mem_block *block) +{ + u32 bit = 0, shift = 0, index; + struct sst_dsp *sst = block->dsp; + + for (index = 0; index < ARRAY_SIZE(sram_shift); index++) { + if (sram_shift[index].dev_id == sst->id) + break; + } + + if (index < ARRAY_SIZE(sram_shift)) { + switch (block->type) { + case SST_MEM_DRAM: + shift = sram_shift[index].dram_shift; + break; + case SST_MEM_IRAM: + shift = sram_shift[index].iram_shift; + break; + default: + shift = 0; + } + } else + shift = 0; + + bit = 1 << (block->index + shift); + + return bit; +} + +/*dummy read a SRAM block.*/ +static void sst_mem_block_dummy_read(struct sst_mem_block *block) +{ + u32 size; + u8 tmp_buf[4]; + struct sst_dsp *sst = block->dsp; + + size = block->size > 4 ? 4 : block->size; + memcpy_fromio(tmp_buf, sst->addr.lpe + block->offset, size); +} + +/* enable 32kB memory block - locks held by caller */ +static int hsw_block_enable(struct sst_mem_block *block) +{ + struct sst_dsp *sst = block->dsp; + u32 bit, val; + + if (block->users++ > 0) + return 0; + + dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val &= ~SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + bit = hsw_block_get_bit(block); + writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* wait 18 DSP clock ticks */ + udelay(10); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + + /*add a dummy read before the SRAM block is written, otherwise the writing may miss bytes sometimes.*/ + sst_mem_block_dummy_read(block); + return 0; +} + +/* disable 32kB memory block - locks held by caller */ +static int hsw_block_disable(struct sst_mem_block *block) +{ + struct sst_dsp *sst = block->dsp; + u32 bit, val; + + if (--block->users > 0) + return 0; + + dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n", + block->type, block->index, block->offset); + + /* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val &= ~SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + + val = readl(sst->addr.pci_cfg + SST_VDRTCTL0); + bit = hsw_block_get_bit(block); + /* don't disable DSRAM[0], keep it always enable for FW dump*/ + if (bit != (1 << SST_VDRTCL0_DSRAMPGE_SHIFT)) + writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + /* wait 18 DSP clock ticks */ + udelay(10); + + /* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */ + val = readl(sst->addr.pci_cfg + SST_VDRTCTL2); + val |= SST_VDRTCL2_DCLCGE; + writel(val, sst->addr.pci_cfg + SST_VDRTCTL2); + + udelay(50); + + return 0; +} + +static const struct sst_block_ops sst_hsw_ops = { + .enable = hsw_block_enable, + .disable = hsw_block_disable, +}; + +static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata) +{ + const struct sst_adsp_memregion *region; + struct device *dev; + int ret = -ENODEV, i, j, region_count; + u32 offset, size, fw_dump_bit; + + dev = sst->dma_dev; + + switch (sst->id) { + case SST_DEV_ID_LYNX_POINT: + region = lp_region; + region_count = ARRAY_SIZE(lp_region); + sst->addr.iram_offset = SST_LP_IRAM_OFFSET; + sst->addr.dsp_iram_offset = SST_LPT_DSP_IRAM_OFFSET; + sst->addr.dsp_dram_offset = SST_LPT_DSP_DRAM_OFFSET; + sst->addr.shim_offset = SST_LP_SHIM_OFFSET; + break; + case SST_DEV_ID_WILDCAT_POINT: + region = wpt_region; + region_count = ARRAY_SIZE(wpt_region); + sst->addr.iram_offset = SST_WPT_IRAM_OFFSET; + sst->addr.dsp_iram_offset = SST_WPT_DSP_IRAM_OFFSET; + sst->addr.dsp_dram_offset = SST_WPT_DSP_DRAM_OFFSET; + sst->addr.shim_offset = SST_WPT_SHIM_OFFSET; + break; + default: + dev_err(dev, "error: failed to get mem resources\n"); + return ret; + } + + ret = hsw_acpi_resource_map(sst, pdata); + if (ret < 0) { + dev_err(dev, "error: failed to map resources\n"); + return ret; + } + + /* enable the DSP SHIM */ + ret = hsw_set_dsp_D0(sst); + if (ret < 0) { + dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n"); + return ret; + } + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(31)); + if (ret) + return ret; + + + /* register DSP memory blocks - ideally we should get this from ACPI */ + for (i = 0; i < region_count; i++) { + offset = region[i].start; + size = (region[i].end - region[i].start) / region[i].blocks; + + /* register individual memory blocks */ + for (j = 0; j < region[i].blocks; j++) { + sst_mem_block_register(sst, offset, size, + region[i].type, &sst_hsw_ops, j, sst); + offset += size; + } + } + + /* always enable the block(DSRAM[0]) used for FW dump */ + fw_dump_bit = 1 << SST_VDRTCL0_DSRAMPGE_SHIFT; + /* set default power gating control, enable power gating control for all blocks. that is, + can't be accessed, please enable each block before accessing. */ + writel(0xffffffff & ~fw_dump_bit, sst->addr.pci_cfg + SST_VDRTCTL0); + + return 0; +} + +static void hsw_free(struct sst_dsp *sst) +{ + sst_mem_block_unregister_all(sst); + iounmap(sst->addr.lpe); + iounmap(sst->addr.pci_cfg); +} + +struct sst_ops haswell_ops = { + .reset = hsw_reset, + .boot = hsw_boot, + .stall = hsw_stall, + .wake = hsw_wake, + .sleep = hsw_sleep, + .write = sst_shim32_write, + .read = sst_shim32_read, + .write64 = sst_shim32_write64, + .read64 = sst_shim32_read64, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .irq_handler = hsw_irq, + .init = hsw_init, + .free = hsw_free, + .parse_fw = hsw_parse_fw_image, +}; diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.c b/sound/soc/intel/haswell/sst-haswell-ipc.c new file mode 100644 index 000000000..d33bdaf92 --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-ipc.c @@ -0,0 +1,2225 @@ +/* + * Intel SST Haswell/Broadwell IPC Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sst-haswell-ipc.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-ipc.h" + +/* Global Message - Generic */ +#define IPC_GLB_TYPE_SHIFT 24 +#define IPC_GLB_TYPE_MASK (0x1f << IPC_GLB_TYPE_SHIFT) +#define IPC_GLB_TYPE(x) (x << IPC_GLB_TYPE_SHIFT) + +/* Global Message - Reply */ +#define IPC_GLB_REPLY_SHIFT 0 +#define IPC_GLB_REPLY_MASK (0x1f << IPC_GLB_REPLY_SHIFT) +#define IPC_GLB_REPLY_TYPE(x) (x << IPC_GLB_REPLY_TYPE_SHIFT) + +/* Stream Message - Generic */ +#define IPC_STR_TYPE_SHIFT 20 +#define IPC_STR_TYPE_MASK (0xf << IPC_STR_TYPE_SHIFT) +#define IPC_STR_TYPE(x) (x << IPC_STR_TYPE_SHIFT) +#define IPC_STR_ID_SHIFT 16 +#define IPC_STR_ID_MASK (0xf << IPC_STR_ID_SHIFT) +#define IPC_STR_ID(x) (x << IPC_STR_ID_SHIFT) + +/* Stream Message - Reply */ +#define IPC_STR_REPLY_SHIFT 0 +#define IPC_STR_REPLY_MASK (0x1f << IPC_STR_REPLY_SHIFT) + +/* Stream Stage Message - Generic */ +#define IPC_STG_TYPE_SHIFT 12 +#define IPC_STG_TYPE_MASK (0xf << IPC_STG_TYPE_SHIFT) +#define IPC_STG_TYPE(x) (x << IPC_STG_TYPE_SHIFT) +#define IPC_STG_ID_SHIFT 10 +#define IPC_STG_ID_MASK (0x3 << IPC_STG_ID_SHIFT) +#define IPC_STG_ID(x) (x << IPC_STG_ID_SHIFT) + +/* Stream Stage Message - Reply */ +#define IPC_STG_REPLY_SHIFT 0 +#define IPC_STG_REPLY_MASK (0x1f << IPC_STG_REPLY_SHIFT) + +/* Debug Log Message - Generic */ +#define IPC_LOG_OP_SHIFT 20 +#define IPC_LOG_OP_MASK (0xf << IPC_LOG_OP_SHIFT) +#define IPC_LOG_OP_TYPE(x) (x << IPC_LOG_OP_SHIFT) +#define IPC_LOG_ID_SHIFT 16 +#define IPC_LOG_ID_MASK (0xf << IPC_LOG_ID_SHIFT) +#define IPC_LOG_ID(x) (x << IPC_LOG_ID_SHIFT) + +/* Module Message */ +#define IPC_MODULE_OPERATION_SHIFT 20 +#define IPC_MODULE_OPERATION_MASK (0xf << IPC_MODULE_OPERATION_SHIFT) +#define IPC_MODULE_OPERATION(x) (x << IPC_MODULE_OPERATION_SHIFT) + +#define IPC_MODULE_ID_SHIFT 16 +#define IPC_MODULE_ID_MASK (0xf << IPC_MODULE_ID_SHIFT) +#define IPC_MODULE_ID(x) (x << IPC_MODULE_ID_SHIFT) + +/* IPC message timeout (msecs) */ +#define IPC_TIMEOUT_MSECS 300 +#define IPC_BOOT_MSECS 200 +#define IPC_MSG_WAIT 0 +#define IPC_MSG_NOWAIT 1 + +/* Firmware Ready Message */ +#define IPC_FW_READY (0x1 << 29) +#define IPC_STATUS_MASK (0x3 << 30) + +#define IPC_EMPTY_LIST_SIZE 8 +#define IPC_MAX_STREAMS 4 + +/* Mailbox */ +#define IPC_MAX_MAILBOX_BYTES 256 + +#define INVALID_STREAM_HW_ID 0xffffffff + +/* Global Message - Types and Replies */ +enum ipc_glb_type { + IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */ + IPC_GLB_PERFORMANCE_MONITOR = 1, /* Performance monitoring actions */ + IPC_GLB_ALLOCATE_STREAM = 3, /* Request to allocate new stream */ + IPC_GLB_FREE_STREAM = 4, /* Request to free stream */ + IPC_GLB_GET_FW_CAPABILITIES = 5, /* Retrieves firmware capabilities */ + IPC_GLB_STREAM_MESSAGE = 6, /* Message directed to stream or its stages */ + /* Request to store firmware context during D0->D3 transition */ + IPC_GLB_REQUEST_DUMP = 7, + /* Request to restore firmware context during D3->D0 transition */ + IPC_GLB_RESTORE_CONTEXT = 8, + IPC_GLB_GET_DEVICE_FORMATS = 9, /* Set device format */ + IPC_GLB_SET_DEVICE_FORMATS = 10, /* Get device format */ + IPC_GLB_SHORT_REPLY = 11, + IPC_GLB_ENTER_DX_STATE = 12, + IPC_GLB_GET_MIXER_STREAM_INFO = 13, /* Request mixer stream params */ + IPC_GLB_DEBUG_LOG_MESSAGE = 14, /* Message to or from the debug logger. */ + IPC_GLB_MODULE_OPERATION = 15, /* Message to loadable fw module */ + IPC_GLB_REQUEST_TRANSFER = 16, /* < Request Transfer for host */ + IPC_GLB_MAX_IPC_MESSAGE_TYPE = 17, /* Maximum message number */ +}; + +enum ipc_glb_reply { + IPC_GLB_REPLY_SUCCESS = 0, /* The operation was successful. */ + IPC_GLB_REPLY_ERROR_INVALID_PARAM = 1, /* Invalid parameter was passed. */ + IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE = 2, /* Uknown message type was resceived. */ + IPC_GLB_REPLY_OUT_OF_RESOURCES = 3, /* No resources to satisfy the request. */ + IPC_GLB_REPLY_BUSY = 4, /* The system or resource is busy. */ + IPC_GLB_REPLY_PENDING = 5, /* The action was scheduled for processing. */ + IPC_GLB_REPLY_FAILURE = 6, /* Critical error happened. */ + IPC_GLB_REPLY_INVALID_REQUEST = 7, /* Request can not be completed. */ + IPC_GLB_REPLY_STAGE_UNINITIALIZED = 8, /* Processing stage was uninitialized. */ + IPC_GLB_REPLY_NOT_FOUND = 9, /* Required resource can not be found. */ + IPC_GLB_REPLY_SOURCE_NOT_STARTED = 10, /* Source was not started. */ +}; + +enum ipc_module_operation { + IPC_MODULE_NOTIFICATION = 0, + IPC_MODULE_ENABLE = 1, + IPC_MODULE_DISABLE = 2, + IPC_MODULE_GET_PARAMETER = 3, + IPC_MODULE_SET_PARAMETER = 4, + IPC_MODULE_GET_INFO = 5, + IPC_MODULE_MAX_MESSAGE +}; + +/* Stream Message - Types */ +enum ipc_str_operation { + IPC_STR_RESET = 0, + IPC_STR_PAUSE = 1, + IPC_STR_RESUME = 2, + IPC_STR_STAGE_MESSAGE = 3, + IPC_STR_NOTIFICATION = 4, + IPC_STR_MAX_MESSAGE +}; + +/* Stream Stage Message Types */ +enum ipc_stg_operation { + IPC_STG_GET_VOLUME = 0, + IPC_STG_SET_VOLUME, + IPC_STG_SET_WRITE_POSITION, + IPC_STG_SET_FX_ENABLE, + IPC_STG_SET_FX_DISABLE, + IPC_STG_SET_FX_GET_PARAM, + IPC_STG_SET_FX_SET_PARAM, + IPC_STG_SET_FX_GET_INFO, + IPC_STG_MUTE_LOOPBACK, + IPC_STG_MAX_MESSAGE +}; + +/* Stream Stage Message Types For Notification*/ +enum ipc_stg_operation_notify { + IPC_POSITION_CHANGED = 0, + IPC_STG_GLITCH, + IPC_STG_MAX_NOTIFY +}; + +enum ipc_glitch_type { + IPC_GLITCH_UNDERRUN = 1, + IPC_GLITCH_DECODER_ERROR, + IPC_GLITCH_DOUBLED_WRITE_POS, + IPC_GLITCH_MAX +}; + +/* Debug Control */ +enum ipc_debug_operation { + IPC_DEBUG_ENABLE_LOG = 0, + IPC_DEBUG_DISABLE_LOG = 1, + IPC_DEBUG_REQUEST_LOG_DUMP = 2, + IPC_DEBUG_NOTIFY_LOG_DUMP = 3, + IPC_DEBUG_MAX_DEBUG_LOG +}; + +/* Firmware Ready */ +struct sst_hsw_ipc_fw_ready { + u32 inbox_offset; + u32 outbox_offset; + u32 inbox_size; + u32 outbox_size; + u32 fw_info_size; + u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; +} __attribute__((packed)); + +struct sst_hsw_stream; +struct sst_hsw; + +/* Stream infomation */ +struct sst_hsw_stream { + /* configuration */ + struct sst_hsw_ipc_stream_alloc_req request; + struct sst_hsw_ipc_stream_alloc_reply reply; + struct sst_hsw_ipc_stream_free_req free_req; + + /* Mixer info */ + u32 mute_volume[SST_HSW_NO_CHANNELS]; + u32 mute[SST_HSW_NO_CHANNELS]; + + /* runtime info */ + struct sst_hsw *hsw; + int host_id; + bool commited; + bool running; + + /* Notification work */ + struct work_struct notify_work; + u32 header; + + /* Position info from DSP */ + struct sst_hsw_ipc_stream_set_position wpos; + struct sst_hsw_ipc_stream_get_position rpos; + struct sst_hsw_ipc_stream_glitch_position glitch; + + /* Volume info */ + struct sst_hsw_ipc_volume_req vol_req; + + /* driver callback */ + u32 (*notify_position)(struct sst_hsw_stream *stream, void *data); + void *pdata; + + /* record the fw read position when playback */ + snd_pcm_uframes_t old_position; + bool play_silence; + struct list_head node; +}; + +/* FW log ring information */ +struct sst_hsw_log_stream { + dma_addr_t dma_addr; + unsigned char *dma_area; + unsigned char *ring_descr; + int pages; + int size; + + /* Notification work */ + struct work_struct notify_work; + wait_queue_head_t readers_wait_q; + struct mutex rw_mutex; + + u32 last_pos; + u32 curr_pos; + u32 reader_pos; + + /* fw log config */ + u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; + + struct sst_hsw *hsw; +}; + +/* SST Haswell IPC data */ +struct sst_hsw { + struct device *dev; + struct sst_dsp *dsp; + struct platform_device *pdev_pcm; + + /* FW config */ + struct sst_hsw_ipc_fw_ready fw_ready; + struct sst_hsw_ipc_fw_version version; + bool fw_done; + struct sst_fw *sst_fw; + + /* stream */ + struct list_head stream_list; + + /* global mixer */ + struct sst_hsw_ipc_stream_info_reply mixer_info; + enum sst_hsw_volume_curve curve_type; + u32 curve_duration; + u32 mute[SST_HSW_NO_CHANNELS]; + u32 mute_volume[SST_HSW_NO_CHANNELS]; + + /* DX */ + struct sst_hsw_ipc_dx_reply dx; + void *dx_context; + dma_addr_t dx_context_paddr; + enum sst_hsw_device_id dx_dev; + enum sst_hsw_device_mclk dx_mclk; + enum sst_hsw_device_mode dx_mode; + u32 dx_clock_divider; + + /* boot */ + wait_queue_head_t boot_wait; + bool boot_complete; + bool shutdown; + + /* IPC messaging */ + struct sst_generic_ipc ipc; + + /* FW log stream */ + struct sst_hsw_log_stream log_stream; + + /* flags bit field to track module state when resume from RTD3, + * each bit represent state (enabled/disabled) of single module */ + u32 enabled_modules_rtd3; + + /* buffer to store parameter lines */ + u32 param_idx_w; /* write index */ + u32 param_idx_r; /* read index */ + u8 param_buf[WAVES_PARAM_LINES][WAVES_PARAM_COUNT]; +}; + +#define CREATE_TRACE_POINTS +#include + +static inline u32 msg_get_global_type(u32 msg) +{ + return (msg & IPC_GLB_TYPE_MASK) >> IPC_GLB_TYPE_SHIFT; +} + +static inline u32 msg_get_global_reply(u32 msg) +{ + return (msg & IPC_GLB_REPLY_MASK) >> IPC_GLB_REPLY_SHIFT; +} + +static inline u32 msg_get_stream_type(u32 msg) +{ + return (msg & IPC_STR_TYPE_MASK) >> IPC_STR_TYPE_SHIFT; +} + +static inline u32 msg_get_stage_type(u32 msg) +{ + return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; +} + +static inline u32 msg_get_stream_id(u32 msg) +{ + return (msg & IPC_STR_ID_MASK) >> IPC_STR_ID_SHIFT; +} + +static inline u32 msg_get_notify_reason(u32 msg) +{ + return (msg & IPC_STG_TYPE_MASK) >> IPC_STG_TYPE_SHIFT; +} + +static inline u32 msg_get_module_operation(u32 msg) +{ + return (msg & IPC_MODULE_OPERATION_MASK) >> IPC_MODULE_OPERATION_SHIFT; +} + +static inline u32 msg_get_module_id(u32 msg) +{ + return (msg & IPC_MODULE_ID_MASK) >> IPC_MODULE_ID_SHIFT; +} + +u32 create_channel_map(enum sst_hsw_channel_config config) +{ + switch (config) { + case SST_HSW_CHANNEL_CONFIG_MONO: + return (0xFFFFFFF0 | SST_HSW_CHANNEL_CENTER); + case SST_HSW_CHANNEL_CONFIG_STEREO: + return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4)); + case SST_HSW_CHANNEL_CONFIG_2_POINT_1: + return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4) + | (SST_HSW_CHANNEL_LFE << 8 )); + case SST_HSW_CHANNEL_CONFIG_3_POINT_0: + return (0xFFFFF000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8)); + case SST_HSW_CHANNEL_CONFIG_3_POINT_1: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LFE << 12)); + case SST_HSW_CHANNEL_CONFIG_QUATRO: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_RIGHT << 4) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 8) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 12)); + case SST_HSW_CHANNEL_CONFIG_4_POINT_0: + return (0xFFFF0000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_CENTER_SURROUND << 12)); + case SST_HSW_CHANNEL_CONFIG_5_POINT_0: + return (0xFFF00000 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_CENTER << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16)); + case SST_HSW_CHANNEL_CONFIG_5_POINT_1: + return (0xFF000000 | SST_HSW_CHANNEL_CENTER + | (SST_HSW_CHANNEL_LEFT << 4) + | (SST_HSW_CHANNEL_RIGHT << 8) + | (SST_HSW_CHANNEL_LEFT_SURROUND << 12) + | (SST_HSW_CHANNEL_RIGHT_SURROUND << 16) + | (SST_HSW_CHANNEL_LFE << 20)); + case SST_HSW_CHANNEL_CONFIG_DUAL_MONO: + return (0xFFFFFF00 | SST_HSW_CHANNEL_LEFT + | (SST_HSW_CHANNEL_LEFT << 4)); + default: + return 0xFFFFFFFF; + } +} + +static struct sst_hsw_stream *get_stream_by_id(struct sst_hsw *hsw, + int stream_id) +{ + struct sst_hsw_stream *stream; + + list_for_each_entry(stream, &hsw->stream_list, node) { + if (stream->reply.stream_hw_id == stream_id) + return stream; + } + + return NULL; +} + +static void hsw_fw_ready(struct sst_hsw *hsw, u32 header) +{ + struct sst_hsw_ipc_fw_ready fw_ready; + u32 offset; + u8 fw_info[IPC_MAX_MAILBOX_BYTES - 5 * sizeof(u32)]; + char *tmp[5], *pinfo; + int i = 0; + + offset = (header & 0x1FFFFFFF) << 3; + + dev_dbg(hsw->dev, "ipc: DSP is ready 0x%8.8x offset %d\n", + header, offset); + + /* copy data from the DSP FW ready offset */ + sst_dsp_read(hsw->dsp, &fw_ready, offset, sizeof(fw_ready)); + + sst_dsp_mailbox_init(hsw->dsp, fw_ready.inbox_offset, + fw_ready.inbox_size, fw_ready.outbox_offset, + fw_ready.outbox_size); + + hsw->boot_complete = true; + wake_up(&hsw->boot_wait); + + dev_dbg(hsw->dev, " mailbox upstream 0x%x - size 0x%x\n", + fw_ready.inbox_offset, fw_ready.inbox_size); + dev_dbg(hsw->dev, " mailbox downstream 0x%x - size 0x%x\n", + fw_ready.outbox_offset, fw_ready.outbox_size); + if (fw_ready.fw_info_size < sizeof(fw_ready.fw_info)) { + fw_ready.fw_info[fw_ready.fw_info_size] = 0; + dev_dbg(hsw->dev, " Firmware info: %s \n", fw_ready.fw_info); + + /* log the FW version info got from the mailbox here. */ + memcpy(fw_info, fw_ready.fw_info, fw_ready.fw_info_size); + pinfo = &fw_info[0]; + for (i = 0; i < ARRAY_SIZE(tmp); i++) + tmp[i] = strsep(&pinfo, " "); + dev_info(hsw->dev, "FW loaded, mailbox readback FW info: type %s, - " + "version: %s.%s, build %s, source commit id: %s\n", + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]); + } +} + +static void hsw_notification_work(struct work_struct *work) +{ + struct sst_hsw_stream *stream = container_of(work, + struct sst_hsw_stream, notify_work); + struct sst_hsw_ipc_stream_glitch_position *glitch = &stream->glitch; + struct sst_hsw_ipc_stream_get_position *pos = &stream->rpos; + struct sst_hsw *hsw = stream->hsw; + u32 reason; + + reason = msg_get_notify_reason(stream->header); + + switch (reason) { + case IPC_STG_GLITCH: + trace_ipc_notification("DSP stream under/overrun", + stream->reply.stream_hw_id); + sst_dsp_inbox_read(hsw->dsp, glitch, sizeof(*glitch)); + + dev_err(hsw->dev, "glitch %d pos 0x%x write pos 0x%x\n", + glitch->glitch_type, glitch->present_pos, + glitch->write_pos); + break; + + case IPC_POSITION_CHANGED: + trace_ipc_notification("DSP stream position changed for", + stream->reply.stream_hw_id); + sst_dsp_inbox_read(hsw->dsp, pos, sizeof(*pos)); + + if (stream->notify_position) + stream->notify_position(stream, stream->pdata); + + break; + default: + dev_err(hsw->dev, "error: unknown notification 0x%x\n", + stream->header); + break; + } + + /* tell DSP that notification has been handled */ + sst_dsp_shim_update_bits(hsw->dsp, SST_IPCD, + SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); + + /* unmask busy interrupt */ + sst_dsp_shim_update_bits(hsw->dsp, SST_IMRX, SST_IMRX_BUSY, 0); +} + +static void hsw_stream_update(struct sst_hsw *hsw, struct ipc_message *msg) +{ + struct sst_hsw_stream *stream; + u32 header = msg->header & ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + u32 stream_id = msg_get_stream_id(header); + u32 stream_msg = msg_get_stream_type(header); + + stream = get_stream_by_id(hsw, stream_id); + if (stream == NULL) + return; + + switch (stream_msg) { + case IPC_STR_STAGE_MESSAGE: + case IPC_STR_NOTIFICATION: + break; + case IPC_STR_RESET: + trace_ipc_notification("stream reset", stream->reply.stream_hw_id); + break; + case IPC_STR_PAUSE: + stream->running = false; + trace_ipc_notification("stream paused", + stream->reply.stream_hw_id); + break; + case IPC_STR_RESUME: + stream->running = true; + trace_ipc_notification("stream running", + stream->reply.stream_hw_id); + break; + } +} + +static int hsw_process_reply(struct sst_hsw *hsw, u32 header) +{ + struct ipc_message *msg; + u32 reply = msg_get_global_reply(header); + + trace_ipc_reply("processing -->", header); + + msg = sst_ipc_reply_find_msg(&hsw->ipc, header); + if (msg == NULL) { + trace_ipc_error("error: can't find message header", header); + return -EIO; + } + + /* first process the header */ + switch (reply) { + case IPC_GLB_REPLY_PENDING: + trace_ipc_pending_reply("received", header); + msg->pending = true; + hsw->ipc.pending = true; + return 1; + case IPC_GLB_REPLY_SUCCESS: + if (msg->pending) { + trace_ipc_pending_reply("completed", header); + sst_dsp_inbox_read(hsw->dsp, msg->rx_data, + msg->rx_size); + hsw->ipc.pending = false; + } else { + /* copy data from the DSP */ + sst_dsp_outbox_read(hsw->dsp, msg->rx_data, + msg->rx_size); + } + break; + /* these will be rare - but useful for debug */ + case IPC_GLB_REPLY_UNKNOWN_MESSAGE_TYPE: + trace_ipc_error("error: unknown message type", header); + msg->errno = -EBADMSG; + break; + case IPC_GLB_REPLY_OUT_OF_RESOURCES: + trace_ipc_error("error: out of resources", header); + msg->errno = -ENOMEM; + break; + case IPC_GLB_REPLY_BUSY: + trace_ipc_error("error: reply busy", header); + msg->errno = -EBUSY; + break; + case IPC_GLB_REPLY_FAILURE: + trace_ipc_error("error: reply failure", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_STAGE_UNINITIALIZED: + trace_ipc_error("error: stage uninitialized", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_NOT_FOUND: + trace_ipc_error("error: reply not found", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_SOURCE_NOT_STARTED: + trace_ipc_error("error: source not started", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_INVALID_REQUEST: + trace_ipc_error("error: invalid request", header); + msg->errno = -EINVAL; + break; + case IPC_GLB_REPLY_ERROR_INVALID_PARAM: + trace_ipc_error("error: invalid parameter", header); + msg->errno = -EINVAL; + break; + default: + trace_ipc_error("error: unknown reply", header); + msg->errno = -EINVAL; + break; + } + + /* update any stream states */ + if (msg_get_global_type(header) == IPC_GLB_STREAM_MESSAGE) + hsw_stream_update(hsw, msg); + + /* wake up and return the error if we have waiters on this message ? */ + list_del(&msg->list); + sst_ipc_tx_msg_reply_complete(&hsw->ipc, msg); + + return 1; +} + +static int hsw_module_message(struct sst_hsw *hsw, u32 header) +{ + u32 operation, module_id; + int handled = 0; + + operation = msg_get_module_operation(header); + module_id = msg_get_module_id(header); + dev_dbg(hsw->dev, "received module message header: 0x%8.8x\n", + header); + dev_dbg(hsw->dev, "operation: 0x%8.8x module_id: 0x%8.8x\n", + operation, module_id); + + switch (operation) { + case IPC_MODULE_NOTIFICATION: + dev_dbg(hsw->dev, "module notification received"); + handled = 1; + break; + default: + handled = hsw_process_reply(hsw, header); + break; + } + + return handled; +} + +static int hsw_stream_message(struct sst_hsw *hsw, u32 header) +{ + u32 stream_msg, stream_id, stage_type; + struct sst_hsw_stream *stream; + int handled = 0; + + stream_msg = msg_get_stream_type(header); + stream_id = msg_get_stream_id(header); + stage_type = msg_get_stage_type(header); + + stream = get_stream_by_id(hsw, stream_id); + if (stream == NULL) + return handled; + + stream->header = header; + + switch (stream_msg) { + case IPC_STR_STAGE_MESSAGE: + dev_err(hsw->dev, "error: stage msg not implemented 0x%8.8x\n", + header); + break; + case IPC_STR_NOTIFICATION: + schedule_work(&stream->notify_work); + break; + default: + /* handle pending message complete request */ + handled = hsw_process_reply(hsw, header); + break; + } + + return handled; +} + +static int hsw_log_message(struct sst_hsw *hsw, u32 header) +{ + u32 operation = (header & IPC_LOG_OP_MASK) >> IPC_LOG_OP_SHIFT; + struct sst_hsw_log_stream *stream = &hsw->log_stream; + int ret = 1; + + if (operation != IPC_DEBUG_REQUEST_LOG_DUMP) { + dev_err(hsw->dev, + "error: log msg not implemented 0x%8.8x\n", header); + return 0; + } + + mutex_lock(&stream->rw_mutex); + stream->last_pos = stream->curr_pos; + sst_dsp_inbox_read( + hsw->dsp, &stream->curr_pos, sizeof(stream->curr_pos)); + mutex_unlock(&stream->rw_mutex); + + schedule_work(&stream->notify_work); + + return ret; +} + +static int hsw_process_notification(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 type, header; + int handled = 1; + + header = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + type = msg_get_global_type(header); + + trace_ipc_request("processing -->", header); + + /* FW Ready is a special case */ + if (!hsw->boot_complete && header & IPC_FW_READY) { + hsw_fw_ready(hsw, header); + return handled; + } + + switch (type) { + case IPC_GLB_GET_FW_VERSION: + case IPC_GLB_ALLOCATE_STREAM: + case IPC_GLB_FREE_STREAM: + case IPC_GLB_GET_FW_CAPABILITIES: + case IPC_GLB_REQUEST_DUMP: + case IPC_GLB_GET_DEVICE_FORMATS: + case IPC_GLB_SET_DEVICE_FORMATS: + case IPC_GLB_ENTER_DX_STATE: + case IPC_GLB_GET_MIXER_STREAM_INFO: + case IPC_GLB_MAX_IPC_MESSAGE_TYPE: + case IPC_GLB_RESTORE_CONTEXT: + case IPC_GLB_SHORT_REPLY: + dev_err(hsw->dev, "error: message type %d header 0x%x\n", + type, header); + break; + case IPC_GLB_STREAM_MESSAGE: + handled = hsw_stream_message(hsw, header); + break; + case IPC_GLB_DEBUG_LOG_MESSAGE: + handled = hsw_log_message(hsw, header); + break; + case IPC_GLB_MODULE_OPERATION: + handled = hsw_module_message(hsw, header); + break; + default: + dev_err(hsw->dev, "error: unexpected type %d hdr 0x%8.8x\n", + type, header); + break; + } + + return handled; +} + +static irqreturn_t hsw_irq_thread(int irq, void *context) +{ + struct sst_dsp *sst = (struct sst_dsp *) context; + struct sst_hsw *hsw = sst_dsp_get_thread_context(sst); + struct sst_generic_ipc *ipc = &hsw->ipc; + u32 ipcx, ipcd; + unsigned long flags; + + spin_lock_irqsave(&sst->spinlock, flags); + + ipcx = sst_dsp_ipc_msg_rx(hsw->dsp); + ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + + /* reply message from DSP */ + if (ipcx & SST_IPCX_DONE) { + + /* Handle Immediate reply from DSP Core */ + hsw_process_reply(hsw, ipcx); + + /* clear DONE bit - tell DSP we have completed */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IPCX, + SST_IPCX_DONE, 0); + + /* unmask Done interrupt */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_DONE, 0); + } + + /* new message from DSP */ + if (ipcd & SST_IPCD_BUSY) { + + /* Handle Notification and Delayed reply from DSP Core */ + hsw_process_notification(hsw); + + /* clear BUSY bit and set DONE bit - accept new messages */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IPCD, + SST_IPCD_BUSY | SST_IPCD_DONE, SST_IPCD_DONE); + + /* unmask busy interrupt */ + sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, + SST_IMRX_BUSY, 0); + } + + spin_unlock_irqrestore(&sst->spinlock, flags); + + /* continue to send any remaining messages... */ + schedule_work(&ipc->kwork); + + return IRQ_HANDLED; +} + +int sst_hsw_fw_get_version(struct sst_hsw *hsw, + struct sst_hsw_ipc_fw_version *version) +{ + int ret; + + ret = sst_ipc_tx_message_wait(&hsw->ipc, + IPC_GLB_TYPE(IPC_GLB_GET_FW_VERSION), + NULL, 0, version, sizeof(*version)); + if (ret < 0) + dev_err(hsw->dev, "error: get version failed\n"); + + return ret; +} + +/* Mixer Controls */ +int sst_hsw_stream_get_volume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 stage_id, u32 channel, u32 *volume) +{ + if (channel > 1) + return -EINVAL; + + sst_dsp_read(hsw->dsp, volume, + stream->reply.volume_register_address[channel], + sizeof(*volume)); + + return 0; +} + +/* stream volume */ +int sst_hsw_stream_set_volume(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume) +{ + struct sst_hsw_ipc_volume_req *req; + u32 header; + int ret; + + trace_ipc_request("set stream volume", stream->reply.stream_hw_id); + + if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) + return -EINVAL; + + header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | + IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); + header |= (stream->reply.stream_hw_id << IPC_STR_ID_SHIFT); + header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); + header |= (stage_id << IPC_STG_ID_SHIFT); + + req = &stream->vol_req; + req->target_volume = volume; + + /* set both at same time ? */ + if (channel == SST_HSW_CHANNELS_ALL) { + if (hsw->mute[0] && hsw->mute[1]) { + hsw->mute_volume[0] = hsw->mute_volume[1] = volume; + return 0; + } else if (hsw->mute[0]) + req->channel = 1; + else if (hsw->mute[1]) + req->channel = 0; + else + req->channel = SST_HSW_CHANNELS_ALL; + } else { + /* set only 1 channel */ + if (hsw->mute[channel]) { + hsw->mute_volume[channel] = volume; + return 0; + } + req->channel = channel; + } + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, req, + sizeof(*req), NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: set stream volume failed\n"); + return ret; + } + + return 0; +} + +int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 *volume) +{ + if (channel > 1) + return -EINVAL; + + sst_dsp_read(hsw->dsp, volume, + hsw->mixer_info.volume_register_address[channel], + sizeof(*volume)); + + return 0; +} + +/* global mixer volume */ +int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 volume) +{ + struct sst_hsw_ipc_volume_req req; + u32 header; + int ret; + + trace_ipc_request("set mixer volume", volume); + + if (channel >= 2 && channel != SST_HSW_CHANNELS_ALL) + return -EINVAL; + + /* set both at same time ? */ + if (channel == SST_HSW_CHANNELS_ALL) { + if (hsw->mute[0] && hsw->mute[1]) { + hsw->mute_volume[0] = hsw->mute_volume[1] = volume; + return 0; + } else if (hsw->mute[0]) + req.channel = 1; + else if (hsw->mute[1]) + req.channel = 0; + else + req.channel = SST_HSW_CHANNELS_ALL; + } else { + /* set only 1 channel */ + if (hsw->mute[channel]) { + hsw->mute_volume[channel] = volume; + return 0; + } + req.channel = channel; + } + + header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | + IPC_STR_TYPE(IPC_STR_STAGE_MESSAGE); + header |= (hsw->mixer_info.mixer_hw_id << IPC_STR_ID_SHIFT); + header |= (IPC_STG_SET_VOLUME << IPC_STG_TYPE_SHIFT); + header |= (stage_id << IPC_STG_ID_SHIFT); + + req.curve_duration = hsw->curve_duration; + req.curve_type = hsw->curve_type; + req.target_volume = volume; + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &req, + sizeof(req), NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: set mixer volume failed\n"); + return ret; + } + + return 0; +} + +/* Stream API */ +struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, + u32 (*notify_position)(struct sst_hsw_stream *stream, void *data), + void *data) +{ + struct sst_hsw_stream *stream; + struct sst_dsp *sst = hsw->dsp; + unsigned long flags; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (stream == NULL) + return NULL; + + spin_lock_irqsave(&sst->spinlock, flags); + stream->reply.stream_hw_id = INVALID_STREAM_HW_ID; + list_add(&stream->node, &hsw->stream_list); + stream->notify_position = notify_position; + stream->pdata = data; + stream->hsw = hsw; + stream->host_id = id; + + /* work to process notification messages */ + INIT_WORK(&stream->notify_work, hsw_notification_work); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return stream; +} + +int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream) +{ + u32 header; + int ret = 0; + struct sst_dsp *sst = hsw->dsp; + unsigned long flags; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to free, ignore it.\n"); + return 0; + } + + /* dont free DSP streams that are not commited */ + if (!stream->commited) + goto out; + + trace_ipc_request("stream free", stream->host_id); + + stream->free_req.stream_id = stream->reply.stream_hw_id; + header = IPC_GLB_TYPE(IPC_GLB_FREE_STREAM); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &stream->free_req, + sizeof(stream->free_req), NULL, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: free stream %d failed\n", + stream->free_req.stream_id); + return -EAGAIN; + } + + trace_hsw_stream_free_req(stream, &stream->free_req); + +out: + cancel_work_sync(&stream->notify_work); + spin_lock_irqsave(&sst->spinlock, flags); + list_del(&stream->node); + kfree(stream); + spin_unlock_irqrestore(&sst->spinlock, flags); + + return ret; +} + +int sst_hsw_stream_set_bits(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, enum sst_hsw_bitdepth bits) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set bits\n"); + return -EINVAL; + } + + stream->request.format.bitdepth = bits; + return 0; +} + +int sst_hsw_stream_set_channels(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, int channels) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set channels\n"); + return -EINVAL; + } + + stream->request.format.ch_num = channels; + return 0; +} + +int sst_hsw_stream_set_rate(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, int rate) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set rate\n"); + return -EINVAL; + } + + stream->request.format.frequency = rate; + return 0; +} + +int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 map, + enum sst_hsw_channel_config config) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set map\n"); + return -EINVAL; + } + + stream->request.format.map = map; + stream->request.format.config = config; + return 0; +} + +int sst_hsw_stream_set_style(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, enum sst_hsw_interleaving style) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set style\n"); + return -EINVAL; + } + + stream->request.format.style = style; + return 0; +} + +int sst_hsw_stream_set_valid(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 bits) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set valid bits\n"); + return -EINVAL; + } + + stream->request.format.valid_bit = bits; + return 0; +} + +/* Stream Configuration */ +int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_stream_path_id path_id, + enum sst_hsw_stream_type stream_type, + enum sst_hsw_stream_format format_id) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set format\n"); + return -EINVAL; + } + + stream->request.path_id = path_id; + stream->request.stream_type = stream_type; + stream->request.format_id = format_id; + + trace_hsw_stream_alloc_request(stream, &stream->request); + + return 0; +} + +int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 ring_pt_address, u32 num_pages, + u32 ring_size, u32 ring_offset, u32 ring_first_pfn) +{ + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for buffer\n"); + return -EINVAL; + } + + stream->request.ringinfo.ring_pt_address = ring_pt_address; + stream->request.ringinfo.num_pages = num_pages; + stream->request.ringinfo.ring_size = ring_size; + stream->request.ringinfo.ring_offset = ring_offset; + stream->request.ringinfo.ring_first_pfn = ring_first_pfn; + + trace_hsw_stream_buffer(stream); + + return 0; +} + +int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, struct sst_module_runtime *runtime) +{ + struct sst_hsw_module_map *map = &stream->request.map; + struct sst_dsp *dsp = sst_hsw_get_dsp(hsw); + struct sst_module *module = runtime->module; + + if (stream->commited) { + dev_err(hsw->dev, "error: stream committed for set module\n"); + return -EINVAL; + } + + /* only support initial module atm */ + map->module_entries_count = 1; + map->module_entries[0].module_id = module->id; + map->module_entries[0].entry_point = module->entry; + + stream->request.persistent_mem.offset = + sst_dsp_get_offset(dsp, runtime->persistent_offset, SST_MEM_DRAM); + stream->request.persistent_mem.size = module->persistent_size; + + stream->request.scratch_mem.offset = + sst_dsp_get_offset(dsp, dsp->scratch_offset, SST_MEM_DRAM); + stream->request.scratch_mem.size = dsp->scratch_size; + + dev_dbg(hsw->dev, "module %d runtime %d using:\n", module->id, + runtime->id); + dev_dbg(hsw->dev, " persistent offset 0x%x bytes 0x%x\n", + stream->request.persistent_mem.offset, + stream->request.persistent_mem.size); + dev_dbg(hsw->dev, " scratch offset 0x%x bytes 0x%x\n", + stream->request.scratch_mem.offset, + stream->request.scratch_mem.size); + + return 0; +} + +int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream) +{ + struct sst_hsw_ipc_stream_alloc_req *str_req = &stream->request; + struct sst_hsw_ipc_stream_alloc_reply *reply = &stream->reply; + u32 header; + int ret; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to commit, ignore it.\n"); + return 0; + } + + if (stream->commited) { + dev_warn(hsw->dev, "warning: stream is already committed, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream alloc", stream->host_id); + + header = IPC_GLB_TYPE(IPC_GLB_ALLOCATE_STREAM); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, str_req, + sizeof(*str_req), reply, sizeof(*reply)); + if (ret < 0) { + dev_err(hsw->dev, "error: stream commit failed\n"); + return ret; + } + + stream->commited = 1; + trace_hsw_stream_alloc_reply(stream); + + return 0; +} + +snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + return stream->old_position; +} + +void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, snd_pcm_uframes_t val) +{ + stream->old_position = val; +} + +bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + return stream->play_silence; +} + +void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, bool val) +{ + stream->play_silence = val; +} + +/* Stream Information - these calls could be inline but we want the IPC + ABI to be opaque to client PCM drivers to cope with any future ABI changes */ +int sst_hsw_mixer_get_info(struct sst_hsw *hsw) +{ + struct sst_hsw_ipc_stream_info_reply *reply; + u32 header; + int ret; + + reply = &hsw->mixer_info; + header = IPC_GLB_TYPE(IPC_GLB_GET_MIXER_STREAM_INFO); + + trace_ipc_request("get global mixer info", 0); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, NULL, 0, + reply, sizeof(*reply)); + if (ret < 0) { + dev_err(hsw->dev, "error: get stream info failed\n"); + return ret; + } + + trace_hsw_mixer_info_reply(reply); + + return 0; +} + +/* Send stream command */ +static int sst_hsw_stream_operations(struct sst_hsw *hsw, int type, + int stream_id, int wait) +{ + u32 header; + + header = IPC_GLB_TYPE(IPC_GLB_STREAM_MESSAGE) | IPC_STR_TYPE(type); + header |= (stream_id << IPC_STR_ID_SHIFT); + + if (wait) + return sst_ipc_tx_message_wait(&hsw->ipc, header, + NULL, 0, NULL, 0); + else + return sst_ipc_tx_message_nowait(&hsw->ipc, header, NULL, 0); +} + +/* Stream ALSA trigger operations */ +int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait) +{ + int ret; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to pause, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream pause", stream->reply.stream_hw_id); + + ret = sst_hsw_stream_operations(hsw, IPC_STR_PAUSE, + stream->reply.stream_hw_id, wait); + if (ret < 0) + dev_err(hsw->dev, "error: failed to pause stream %d\n", + stream->reply.stream_hw_id); + + return ret; +} + +int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait) +{ + int ret; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to resume, ignore it.\n"); + return 0; + } + + trace_ipc_request("stream resume", stream->reply.stream_hw_id); + + ret = sst_hsw_stream_operations(hsw, IPC_STR_RESUME, + stream->reply.stream_hw_id, wait); + if (ret < 0) + dev_err(hsw->dev, "error: failed to resume stream %d\n", + stream->reply.stream_hw_id); + + return ret; +} + +int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream) +{ + int ret, tries = 10; + + if (!stream) { + dev_warn(hsw->dev, "warning: stream is NULL, no stream to reset, ignore it.\n"); + return 0; + } + + /* dont reset streams that are not commited */ + if (!stream->commited) + return 0; + + /* wait for pause to complete before we reset the stream */ + while (stream->running && --tries) + msleep(1); + if (!tries) { + dev_err(hsw->dev, "error: reset stream %d still running\n", + stream->reply.stream_hw_id); + return -EINVAL; + } + + trace_ipc_request("stream reset", stream->reply.stream_hw_id); + + ret = sst_hsw_stream_operations(hsw, IPC_STR_RESET, + stream->reply.stream_hw_id, 1); + if (ret < 0) + dev_err(hsw->dev, "error: failed to reset stream %d\n", + stream->reply.stream_hw_id); + return ret; +} + +/* Stream pointer positions */ +u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + u32 rpos; + + sst_dsp_read(hsw->dsp, &rpos, + stream->reply.read_position_register_address, sizeof(rpos)); + + return rpos; +} + +/* Stream presentation (monotonic) positions */ +u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream) +{ + u64 ppos; + + sst_dsp_read(hsw->dsp, &ppos, + stream->reply.presentation_position_register_address, + sizeof(ppos)); + + return ppos; +} + +/* physical BE config */ +int sst_hsw_device_set_config(struct sst_hsw *hsw, + enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, + enum sst_hsw_device_mode mode, u32 clock_divider) +{ + struct sst_hsw_ipc_device_config_req config; + u32 header; + int ret; + + trace_ipc_request("set device config", dev); + + hsw->dx_dev = config.ssp_interface = dev; + hsw->dx_mclk = config.clock_frequency = mclk; + hsw->dx_mode = config.mode = mode; + hsw->dx_clock_divider = config.clock_divider = clock_divider; + if (mode == SST_HSW_DEVICE_TDM_CLOCK_MASTER) + config.channels = 4; + else + config.channels = 2; + + trace_hsw_device_config_req(&config); + + header = IPC_GLB_TYPE(IPC_GLB_SET_DEVICE_FORMATS); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &config, + sizeof(config), NULL, 0); + if (ret < 0) + dev_err(hsw->dev, "error: set device formats failed\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(sst_hsw_device_set_config); + +/* DX Config */ +int sst_hsw_dx_set_state(struct sst_hsw *hsw, + enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx) +{ + u32 header, state_; + int ret, item; + + header = IPC_GLB_TYPE(IPC_GLB_ENTER_DX_STATE); + state_ = state; + + trace_ipc_request("PM enter Dx state", state); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, &state_, + sizeof(state_), dx, sizeof(*dx)); + if (ret < 0) { + dev_err(hsw->dev, "ipc: error set dx state %d failed\n", state); + return ret; + } + + for (item = 0; item < dx->entries_no; item++) { + dev_dbg(hsw->dev, + "Item[%d] offset[%x] - size[%x] - source[%x]\n", + item, dx->mem_info[item].offset, + dx->mem_info[item].size, + dx->mem_info[item].source); + } + dev_dbg(hsw->dev, "ipc: got %d entry numbers for state %d\n", + dx->entries_no, state); + + return ret; +} + +struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, + int mod_id, int offset) +{ + struct sst_dsp *dsp = hsw->dsp; + struct sst_module *module; + struct sst_module_runtime *runtime; + int err; + + module = sst_module_get_from_id(dsp, mod_id); + if (module == NULL) { + dev_err(dsp->dev, "error: failed to get module %d for pcm\n", + mod_id); + return NULL; + } + + runtime = sst_module_runtime_new(module, mod_id, NULL); + if (runtime == NULL) { + dev_err(dsp->dev, "error: failed to create module %d runtime\n", + mod_id); + return NULL; + } + + err = sst_module_runtime_alloc_blocks(runtime, offset); + if (err < 0) { + dev_err(dsp->dev, "error: failed to alloc blocks for module %d runtime\n", + mod_id); + sst_module_runtime_free(runtime); + return NULL; + } + + dev_dbg(dsp->dev, "runtime id %d created for module %d\n", runtime->id, + mod_id); + return runtime; +} + +void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime) +{ + sst_module_runtime_free_blocks(runtime); + sst_module_runtime_free(runtime); +} + +#ifdef CONFIG_PM +static int sst_hsw_dx_state_dump(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 item, offset, size; + int ret = 0; + + trace_ipc_request("PM state dump. Items #", SST_HSW_MAX_DX_REGIONS); + + if (hsw->dx.entries_no > SST_HSW_MAX_DX_REGIONS) { + dev_err(hsw->dev, + "error: number of FW context regions greater than %d\n", + SST_HSW_MAX_DX_REGIONS); + memset(&hsw->dx, 0, sizeof(hsw->dx)); + return -EINVAL; + } + + ret = sst_dsp_dma_get_channel(sst, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + /* set on-demond mode on engine 0 channel 3 */ + sst_dsp_shim_update_bits(sst, SST_HMDC, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH, + SST_HMDC_HDDA_E0_ALLCH | SST_HMDC_HDDA_E1_ALLCH); + + for (item = 0; item < hsw->dx.entries_no; item++) { + if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP + && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET + && hsw->dx.mem_info[item].offset < + DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { + + offset = hsw->dx.mem_info[item].offset + - DSP_DRAM_ADDR_OFFSET; + size = (hsw->dx.mem_info[item].size + 3) & (~3); + + ret = sst_dsp_dma_copyfrom(sst, hsw->dx_context_paddr + offset, + sst->addr.lpe_base + offset, size); + if (ret < 0) { + dev_err(hsw->dev, + "error: FW context dump failed\n"); + memset(&hsw->dx, 0, sizeof(hsw->dx)); + goto out; + } + } + } + +out: + sst_dsp_dma_put_channel(sst); + return ret; +} + +static int sst_hsw_dx_state_restore(struct sst_hsw *hsw) +{ + struct sst_dsp *sst = hsw->dsp; + u32 item, offset, size; + int ret; + + for (item = 0; item < hsw->dx.entries_no; item++) { + if (hsw->dx.mem_info[item].source == SST_HSW_DX_TYPE_MEMORY_DUMP + && hsw->dx.mem_info[item].offset > DSP_DRAM_ADDR_OFFSET + && hsw->dx.mem_info[item].offset < + DSP_DRAM_ADDR_OFFSET + SST_HSW_DX_CONTEXT_SIZE) { + + offset = hsw->dx.mem_info[item].offset + - DSP_DRAM_ADDR_OFFSET; + size = (hsw->dx.mem_info[item].size + 3) & (~3); + + ret = sst_dsp_dma_copyto(sst, sst->addr.lpe_base + offset, + hsw->dx_context_paddr + offset, size); + if (ret < 0) { + dev_err(hsw->dev, + "error: FW context restore failed\n"); + return ret; + } + } + } + + return 0; +} + +int sst_hsw_dsp_load(struct sst_hsw *hsw) +{ + struct sst_dsp *dsp = hsw->dsp; + struct sst_fw *sst_fw, *t; + int ret; + + dev_dbg(hsw->dev, "loading audio DSP...."); + + ret = sst_dsp_wake(dsp); + if (ret < 0) { + dev_err(hsw->dev, "error: failed to wake audio DSP\n"); + return -ENODEV; + } + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + list_for_each_entry_safe_reverse(sst_fw, t, &dsp->fw_list, list) { + ret = sst_fw_reload(sst_fw); + if (ret < 0) { + dev_err(hsw->dev, "error: SST FW reload failed\n"); + sst_dsp_dma_put_channel(dsp); + return -ENOMEM; + } + } + ret = sst_block_alloc_scratch(hsw->dsp); + if (ret < 0) + return -EINVAL; + + sst_dsp_dma_put_channel(dsp); + return 0; +} + +static int sst_hsw_dsp_restore(struct sst_hsw *hsw) +{ + struct sst_dsp *dsp = hsw->dsp; + int ret; + + dev_dbg(hsw->dev, "restoring audio DSP...."); + + ret = sst_dsp_dma_get_channel(dsp, 0); + if (ret < 0) { + dev_err(hsw->dev, "error: cant allocate dma channel %d\n", ret); + return ret; + } + + ret = sst_hsw_dx_state_restore(hsw); + if (ret < 0) { + dev_err(hsw->dev, "error: SST FW context restore failed\n"); + sst_dsp_dma_put_channel(dsp); + return -ENOMEM; + } + sst_dsp_dma_put_channel(dsp); + + /* wait for DSP boot completion */ + sst_dsp_boot(dsp); + + return ret; +} + +int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw) +{ + int ret; + + dev_dbg(hsw->dev, "audio dsp runtime suspend\n"); + + ret = sst_hsw_dx_set_state(hsw, SST_HSW_DX_STATE_D3, &hsw->dx); + if (ret < 0) + return ret; + + sst_dsp_stall(hsw->dsp); + + ret = sst_hsw_dx_state_dump(hsw); + if (ret < 0) + return ret; + + sst_ipc_drop_all(&hsw->ipc); + + return 0; +} + +int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw) +{ + struct sst_fw *sst_fw, *t; + struct sst_dsp *dsp = hsw->dsp; + + list_for_each_entry_safe(sst_fw, t, &dsp->fw_list, list) { + sst_fw_unload(sst_fw); + } + sst_block_free_scratch(dsp); + + hsw->boot_complete = false; + + sst_dsp_sleep(dsp); + + return 0; +} + +int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw) +{ + struct device *dev = hsw->dev; + int ret; + + dev_dbg(dev, "audio dsp runtime resume\n"); + + if (hsw->boot_complete) + return 1; /* tell caller no action is required */ + + ret = sst_hsw_dsp_restore(hsw); + if (ret < 0) + dev_err(dev, "error: audio DSP boot failure\n"); + + sst_hsw_init_module_state(hsw); + + ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (ret == 0) { + dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); + return -EIO; + } + + /* Set ADSP SSP port settings - sadly the FW does not store SSP port + settings as part of the PM context. */ + ret = sst_hsw_device_set_config(hsw, hsw->dx_dev, hsw->dx_mclk, + hsw->dx_mode, hsw->dx_clock_divider); + if (ret < 0) + dev_err(dev, "error: SSP re-initialization failed\n"); + + return ret; +} +#endif + +struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw) +{ + return hsw->dsp; +} + +void sst_hsw_init_module_state(struct sst_hsw *hsw) +{ + struct sst_module *module; + enum sst_hsw_module_id id; + + /* the base fw contains several modules */ + for (id = SST_HSW_MODULE_BASE_FW; id < SST_HSW_MAX_MODULE_ID; id++) { + module = sst_module_get_from_id(hsw->dsp, id); + if (module) { + /* module waves is active only after being enabled */ + if (id == SST_HSW_MODULE_WAVES) + module->state = SST_MODULE_STATE_INITIALIZED; + else + module->state = SST_MODULE_STATE_ACTIVE; + } + } +} + +bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id) +{ + struct sst_module *module; + + module = sst_module_get_from_id(hsw->dsp, module_id); + if (module == NULL || module->state == SST_MODULE_STATE_UNLOADED) + return false; + else + return true; +} + +bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id) +{ + struct sst_module *module; + + module = sst_module_get_from_id(hsw->dsp, module_id); + if (module != NULL && module->state == SST_MODULE_STATE_ACTIVE) + return true; + else + return false; +} + +void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + hsw->enabled_modules_rtd3 |= (1 << module_id); +} + +void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + hsw->enabled_modules_rtd3 &= ~(1 << module_id); +} + +bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id) +{ + return hsw->enabled_modules_rtd3 & (1 << module_id); +} + +void sst_hsw_reset_param_buf(struct sst_hsw *hsw) +{ + hsw->param_idx_w = 0; + hsw->param_idx_r = 0; + memset((void *)hsw->param_buf, 0, sizeof(hsw->param_buf)); +} + +int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf) +{ + /* save line to the first available position of param buffer */ + if (hsw->param_idx_w > WAVES_PARAM_LINES - 1) { + dev_warn(hsw->dev, "warning: param buffer overflow!\n"); + return -EPERM; + } + memcpy(hsw->param_buf[hsw->param_idx_w], buf, WAVES_PARAM_COUNT); + hsw->param_idx_w++; + return 0; +} + +int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf) +{ + u8 id = 0; + + /* read the first matching line from param buffer */ + while (hsw->param_idx_r < WAVES_PARAM_LINES) { + id = hsw->param_buf[hsw->param_idx_r][0]; + hsw->param_idx_r++; + if (buf[0] == id) { + memcpy(buf, hsw->param_buf[hsw->param_idx_r], + WAVES_PARAM_COUNT); + break; + } + } + if (hsw->param_idx_r > WAVES_PARAM_LINES - 1) { + dev_dbg(hsw->dev, "end of buffer, roll to the beginning\n"); + hsw->param_idx_r = 0; + return 0; + } + return 0; +} + +int sst_hsw_launch_param_buf(struct sst_hsw *hsw) +{ + int ret, idx; + + if (!sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { + dev_dbg(hsw->dev, "module waves is not active\n"); + return 0; + } + + /* put all param lines to DSP through ipc */ + for (idx = 0; idx < hsw->param_idx_w; idx++) { + ret = sst_hsw_module_set_param(hsw, + SST_HSW_MODULE_WAVES, 0, hsw->param_buf[idx][0], + WAVES_PARAM_COUNT, hsw->param_buf[idx]); + if (ret < 0) + return ret; + } + return 0; +} + +int sst_hsw_module_load(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, char *name) +{ + int ret = 0; + const struct firmware *fw = NULL; + struct sst_fw *hsw_sst_fw; + struct sst_module *module; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + dev_dbg(dev, "sst_hsw_module_load id=%d, name='%s'", module_id, name); + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + /* loading for the first time */ + if (module_id == SST_HSW_MODULE_BASE_FW) { + /* for base module: use fw requested in acpi probe */ + fw = dsp->pdata->fw; + if (!fw) { + dev_err(dev, "request Base fw failed\n"); + return -ENODEV; + } + } else { + /* try and load any other optional modules if they are + * available. Use dev_info instead of dev_err in case + * request firmware failed */ + ret = request_firmware(&fw, name, dev); + if (ret) { + dev_info(dev, "fw image %s not available(%d)\n", + name, ret); + return ret; + } + } + hsw_sst_fw = sst_fw_new(dsp, fw, hsw); + if (hsw_sst_fw == NULL) { + dev_err(dev, "error: failed to load firmware\n"); + ret = -ENOMEM; + goto out; + } + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "error: no module %d in firmware %s\n", + module_id, name); + } + } else + dev_info(dev, "module %d (%s) already loaded\n", + module_id, name); +out: + /* release fw, but base fw should be released by acpi driver */ + if (fw && module_id != SST_HSW_MODULE_BASE_FW) + release_firmware(fw); + + return ret; +} + +int sst_hsw_module_enable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id) +{ + int ret; + u32 header = 0; + struct sst_hsw_ipc_module_config config; + struct sst_module *module; + struct sst_module_runtime *runtime; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + if (!sst_hsw_is_module_loaded(hsw, module_id)) { + dev_dbg(dev, "module %d not loaded\n", module_id); + return 0; + } + + if (sst_hsw_is_module_active(hsw, module_id)) { + dev_info(dev, "module %d already enabled\n", module_id); + return 0; + } + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "module %d not valid\n", module_id); + return -ENXIO; + } + + runtime = sst_module_runtime_get_from_id(module, module_id); + if (runtime == NULL) { + dev_err(dev, "runtime %d not valid", module_id); + return -ENXIO; + } + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_ENABLE) | + IPC_MODULE_ID(module_id); + dev_dbg(dev, "module enable header: %x\n", header); + + config.map.module_entries_count = 1; + config.map.module_entries[0].module_id = module->id; + config.map.module_entries[0].entry_point = module->entry; + + config.persistent_mem.offset = + sst_dsp_get_offset(dsp, + runtime->persistent_offset, SST_MEM_DRAM); + config.persistent_mem.size = module->persistent_size; + + config.scratch_mem.offset = + sst_dsp_get_offset(dsp, + dsp->scratch_offset, SST_MEM_DRAM); + config.scratch_mem.size = module->scratch_size; + dev_dbg(dev, "mod %d enable p:%d @ %x, s:%d @ %x, ep: %x", + config.map.module_entries[0].module_id, + config.persistent_mem.size, + config.persistent_mem.offset, + config.scratch_mem.size, config.scratch_mem.offset, + config.map.module_entries[0].entry_point); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, + &config, sizeof(config), NULL, 0); + if (ret < 0) + dev_err(dev, "ipc: module enable failed - %d\n", ret); + else + module->state = SST_MODULE_STATE_ACTIVE; + + return ret; +} + +int sst_hsw_module_disable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id) +{ + int ret; + u32 header; + struct sst_module *module; + struct device *dev = hsw->dev; + struct sst_dsp *dsp = hsw->dsp; + + if (!sst_hsw_is_module_loaded(hsw, module_id)) { + dev_dbg(dev, "module %d not loaded\n", module_id); + return 0; + } + + if (!sst_hsw_is_module_active(hsw, module_id)) { + dev_info(dev, "module %d already disabled\n", module_id); + return 0; + } + + module = sst_module_get_from_id(dsp, module_id); + if (module == NULL) { + dev_err(dev, "module %d not valid\n", module_id); + return -ENXIO; + } + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_DISABLE) | + IPC_MODULE_ID(module_id); + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, NULL, 0, NULL, 0); + if (ret < 0) + dev_err(dev, "module disable failed - %d\n", ret); + else + module->state = SST_MODULE_STATE_INITIALIZED; + + return ret; +} + +int sst_hsw_module_set_param(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, u32 parameter_id, + u32 param_size, char *param) +{ + int ret; + u32 header = 0; + u32 payload_size = 0, transfer_parameter_size = 0; + struct sst_hsw_transfer_parameter *parameter; + struct device *dev = hsw->dev; + + header = IPC_GLB_TYPE(IPC_GLB_MODULE_OPERATION) | + IPC_MODULE_OPERATION(IPC_MODULE_SET_PARAMETER) | + IPC_MODULE_ID(module_id); + dev_dbg(dev, "sst_hsw_module_set_param header=%x\n", header); + + payload_size = param_size + + sizeof(struct sst_hsw_transfer_parameter) - + sizeof(struct sst_hsw_transfer_list); + dev_dbg(dev, "parameter size : %d\n", param_size); + dev_dbg(dev, "payload size : %d\n", payload_size); + + if (payload_size <= SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE) { + /* short parameter, mailbox can contain data */ + dev_dbg(dev, "transfer parameter size : %d\n", + transfer_parameter_size); + + transfer_parameter_size = ALIGN(payload_size, 4); + dev_dbg(dev, "transfer parameter aligned size : %d\n", + transfer_parameter_size); + + parameter = kzalloc(transfer_parameter_size, GFP_KERNEL); + if (parameter == NULL) + return -ENOMEM; + + memcpy(parameter->data, param, param_size); + } else { + dev_warn(dev, "transfer parameter size too large!"); + return 0; + } + + parameter->parameter_id = parameter_id; + parameter->data_size = param_size; + + ret = sst_ipc_tx_message_wait(&hsw->ipc, header, + parameter, transfer_parameter_size , NULL, 0); + if (ret < 0) + dev_err(dev, "ipc: module set parameter failed - %d\n", ret); + + kfree(parameter); + + return ret; +} + +static struct sst_dsp_device hsw_dev = { + .thread = hsw_irq_thread, + .ops = &haswell_ops, +}; + +static void hsw_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + /* send the message */ + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + sst_dsp_ipc_msg_tx(ipc->dsp, msg->header); +} + +static void hsw_shim_dbg(struct sst_generic_ipc *ipc, const char *text) +{ + struct sst_dsp *sst = ipc->dsp; + u32 isr, ipcd, imrx, ipcx; + + ipcx = sst_dsp_shim_read_unlocked(sst, SST_IPCX); + isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX); + ipcd = sst_dsp_shim_read_unlocked(sst, SST_IPCD); + imrx = sst_dsp_shim_read_unlocked(sst, SST_IMRX); + + dev_err(ipc->dev, + "ipc: --%s-- ipcx 0x%8.8x isr 0x%8.8x ipcd 0x%8.8x imrx 0x%8.8x\n", + text, ipcx, isr, ipcd, imrx); +} + +static void hsw_tx_data_copy(struct ipc_message *msg, char *tx_data, + size_t tx_size) +{ + memcpy(msg->tx_data, tx_data, tx_size); +} + +static u64 hsw_reply_msg_match(u64 header, u64 *mask) +{ + /* clear reply bits & status bits */ + header &= ~(IPC_STATUS_MASK | IPC_GLB_REPLY_MASK); + *mask = (u64)-1; + + return header; +} + +static bool hsw_is_dsp_busy(struct sst_dsp *dsp) +{ + u64 ipcx; + + ipcx = sst_dsp_shim_read_unlocked(dsp, SST_IPCX); + return (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)); +} + +int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_hsw_ipc_fw_version version; + struct sst_hsw *hsw; + struct sst_generic_ipc *ipc; + int ret; + + dev_dbg(dev, "initialising Audio DSP IPC\n"); + + hsw = devm_kzalloc(dev, sizeof(*hsw), GFP_KERNEL); + if (hsw == NULL) + return -ENOMEM; + + hsw->dev = dev; + + ipc = &hsw->ipc; + ipc->dev = dev; + ipc->ops.tx_msg = hsw_tx_msg; + ipc->ops.shim_dbg = hsw_shim_dbg; + ipc->ops.tx_data_copy = hsw_tx_data_copy; + ipc->ops.reply_msg_match = hsw_reply_msg_match; + ipc->ops.is_dsp_busy = hsw_is_dsp_busy; + + ipc->tx_data_max_size = IPC_MAX_MAILBOX_BYTES; + ipc->rx_data_max_size = IPC_MAX_MAILBOX_BYTES; + + ret = sst_ipc_init(ipc); + if (ret != 0) + goto ipc_init_err; + + INIT_LIST_HEAD(&hsw->stream_list); + init_waitqueue_head(&hsw->boot_wait); + hsw_dev.thread_context = hsw; + + /* init SST shim */ + hsw->dsp = sst_dsp_new(dev, &hsw_dev, pdata); + if (hsw->dsp == NULL) { + ret = -ENODEV; + goto dsp_new_err; + } + + ipc->dsp = hsw->dsp; + + /* allocate DMA buffer for context storage */ + hsw->dx_context = dma_alloc_coherent(hsw->dsp->dma_dev, + SST_HSW_DX_CONTEXT_SIZE, &hsw->dx_context_paddr, GFP_KERNEL); + if (hsw->dx_context == NULL) { + ret = -ENOMEM; + goto dma_err; + } + + /* keep the DSP in reset state for base FW loading */ + sst_dsp_reset(hsw->dsp); + + /* load base module and other modules in base firmware image */ + ret = sst_hsw_module_load(hsw, SST_HSW_MODULE_BASE_FW, 0, "Base"); + if (ret < 0) + goto fw_err; + + /* try to load module waves */ + sst_hsw_module_load(hsw, SST_HSW_MODULE_WAVES, 0, "intel/IntcPP01.bin"); + + /* allocate scratch mem regions */ + ret = sst_block_alloc_scratch(hsw->dsp); + if (ret < 0) + goto boot_err; + + /* init param buffer */ + sst_hsw_reset_param_buf(hsw); + + /* wait for DSP boot completion */ + sst_dsp_boot(hsw->dsp); + ret = wait_event_timeout(hsw->boot_wait, hsw->boot_complete, + msecs_to_jiffies(IPC_BOOT_MSECS)); + if (ret == 0) { + ret = -EIO; + dev_err(hsw->dev, "error: audio DSP boot timeout IPCD 0x%x IPCX 0x%x\n", + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCD), + sst_dsp_shim_read_unlocked(hsw->dsp, SST_IPCX)); + goto boot_err; + } + + /* init module state after boot */ + sst_hsw_init_module_state(hsw); + + /* get the FW version */ + sst_hsw_fw_get_version(hsw, &version); + + /* get the globalmixer */ + ret = sst_hsw_mixer_get_info(hsw); + if (ret < 0) { + dev_err(hsw->dev, "error: failed to get stream info\n"); + goto boot_err; + } + + pdata->dsp = hsw; + return 0; + +boot_err: + sst_dsp_reset(hsw->dsp); + sst_fw_free_all(hsw->dsp); +fw_err: + dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, + hsw->dx_context, hsw->dx_context_paddr); +dma_err: + sst_dsp_free(hsw->dsp); +dsp_new_err: + sst_ipc_fini(ipc); +ipc_init_err: + return ret; +} +EXPORT_SYMBOL_GPL(sst_hsw_dsp_init); + +void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata) +{ + struct sst_hsw *hsw = pdata->dsp; + + sst_dsp_reset(hsw->dsp); + sst_fw_free_all(hsw->dsp); + dma_free_coherent(hsw->dsp->dma_dev, SST_HSW_DX_CONTEXT_SIZE, + hsw->dx_context, hsw->dx_context_paddr); + sst_dsp_free(hsw->dsp); + sst_ipc_fini(&hsw->ipc); +} +EXPORT_SYMBOL_GPL(sst_hsw_dsp_free); diff --git a/sound/soc/intel/haswell/sst-haswell-ipc.h b/sound/soc/intel/haswell/sst-haswell-ipc.h new file mode 100644 index 000000000..fbc14df13 --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-ipc.h @@ -0,0 +1,536 @@ +/* + * Intel SST Haswell/Broadwell IPC Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SST_HASWELL_IPC_H +#define __SST_HASWELL_IPC_H + +#include +#include +#include +#include + +#define DRV_NAME "haswell-dai" + +#define SST_HSW_NO_CHANNELS 4 +#define SST_HSW_MAX_DX_REGIONS 14 +#define SST_HSW_DX_CONTEXT_SIZE (640 * 1024) +#define SST_HSW_CHANNELS_ALL 0xffffffff + +#define SST_HSW_FW_LOG_CONFIG_DWORDS 12 +#define SST_HSW_GLOBAL_LOG 15 + +/** + * Upfront defined maximum message size that is + * expected by the in/out communication pipes in FW. + */ +#define SST_HSW_IPC_MAX_PAYLOAD_SIZE 400 +#define SST_HSW_MAX_INFO_SIZE 64 +#define SST_HSW_BUILD_HASH_LENGTH 40 +#define SST_HSW_IPC_MAX_SHORT_PARAMETER_SIZE 500 +#define WAVES_PARAM_COUNT 128 +#define WAVES_PARAM_LINES 160 + +struct sst_hsw; +struct sst_hsw_stream; +struct sst_hsw_log_stream; +struct sst_pdata; +struct sst_module; +struct sst_module_runtime; +extern struct sst_ops haswell_ops; + +/* Stream Allocate Path ID */ +enum sst_hsw_stream_path_id { + SST_HSW_STREAM_PATH_SSP0_OUT = 0, + SST_HSW_STREAM_PATH_SSP0_IN = 1, + SST_HSW_STREAM_PATH_MAX_PATH_ID = 2, +}; + +/* Stream Allocate Stream Type */ +enum sst_hsw_stream_type { + SST_HSW_STREAM_TYPE_RENDER = 0, + SST_HSW_STREAM_TYPE_SYSTEM = 1, + SST_HSW_STREAM_TYPE_CAPTURE = 2, + SST_HSW_STREAM_TYPE_LOOPBACK = 3, + SST_HSW_STREAM_TYPE_MAX_STREAM_TYPE = 4, +}; + +/* Stream Allocate Stream Format */ +enum sst_hsw_stream_format { + SST_HSW_STREAM_FORMAT_PCM_FORMAT = 0, + SST_HSW_STREAM_FORMAT_MP3_FORMAT = 1, + SST_HSW_STREAM_FORMAT_AAC_FORMAT = 2, + SST_HSW_STREAM_FORMAT_MAX_FORMAT_ID = 3, +}; + +/* Device ID */ +enum sst_hsw_device_id { + SST_HSW_DEVICE_SSP_0 = 0, + SST_HSW_DEVICE_SSP_1 = 1, +}; + +/* Device Master Clock Frequency */ +enum sst_hsw_device_mclk { + SST_HSW_DEVICE_MCLK_OFF = 0, + SST_HSW_DEVICE_MCLK_FREQ_6_MHZ = 1, + SST_HSW_DEVICE_MCLK_FREQ_12_MHZ = 2, + SST_HSW_DEVICE_MCLK_FREQ_24_MHZ = 3, +}; + +/* Device Clock Master */ +enum sst_hsw_device_mode { + SST_HSW_DEVICE_CLOCK_SLAVE = 0, + SST_HSW_DEVICE_CLOCK_MASTER = 1, + SST_HSW_DEVICE_TDM_CLOCK_MASTER = 2, +}; + +/* DX Power State */ +enum sst_hsw_dx_state { + SST_HSW_DX_STATE_D0 = 0, + SST_HSW_DX_STATE_D1 = 1, + SST_HSW_DX_STATE_D3 = 3, + SST_HSW_DX_STATE_MAX = 3, +}; + +/* Audio stream stage IDs */ +enum sst_hsw_fx_stage_id { + SST_HSW_STAGE_ID_WAVES = 0, + SST_HSW_STAGE_ID_DTS = 1, + SST_HSW_STAGE_ID_DOLBY = 2, + SST_HSW_STAGE_ID_BOOST = 3, + SST_HSW_STAGE_ID_MAX_FX_ID +}; + +/* DX State Type */ +enum sst_hsw_dx_type { + SST_HSW_DX_TYPE_FW_IMAGE = 0, + SST_HSW_DX_TYPE_MEMORY_DUMP = 1 +}; + +/* Volume Curve Type*/ +enum sst_hsw_volume_curve { + SST_HSW_VOLUME_CURVE_NONE = 0, + SST_HSW_VOLUME_CURVE_FADE = 1 +}; + +/* Sample ordering */ +enum sst_hsw_interleaving { + SST_HSW_INTERLEAVING_PER_CHANNEL = 0, + SST_HSW_INTERLEAVING_PER_SAMPLE = 1, +}; + +/* Channel indices */ +enum sst_hsw_channel_index { + SST_HSW_CHANNEL_LEFT = 0, + SST_HSW_CHANNEL_CENTER = 1, + SST_HSW_CHANNEL_RIGHT = 2, + SST_HSW_CHANNEL_LEFT_SURROUND = 3, + SST_HSW_CHANNEL_CENTER_SURROUND = 3, + SST_HSW_CHANNEL_RIGHT_SURROUND = 4, + SST_HSW_CHANNEL_LFE = 7, + SST_HSW_CHANNEL_INVALID = 0xF, +}; + +/* List of supported channel maps. */ +enum sst_hsw_channel_config { + SST_HSW_CHANNEL_CONFIG_MONO = 0, /* mono only. */ + SST_HSW_CHANNEL_CONFIG_STEREO = 1, /* L & R. */ + SST_HSW_CHANNEL_CONFIG_2_POINT_1 = 2, /* L, R & LFE; PCM only. */ + SST_HSW_CHANNEL_CONFIG_3_POINT_0 = 3, /* L, C & R; MP3 & AAC only. */ + SST_HSW_CHANNEL_CONFIG_3_POINT_1 = 4, /* L, C, R & LFE; PCM only. */ + SST_HSW_CHANNEL_CONFIG_QUATRO = 5, /* L, R, Ls & Rs; PCM only. */ + SST_HSW_CHANNEL_CONFIG_4_POINT_0 = 6, /* L, C, R & Cs; MP3 & AAC only. */ + SST_HSW_CHANNEL_CONFIG_5_POINT_0 = 7, /* L, C, R, Ls & Rs. */ + SST_HSW_CHANNEL_CONFIG_5_POINT_1 = 8, /* L, C, R, Ls, Rs & LFE. */ + SST_HSW_CHANNEL_CONFIG_DUAL_MONO = 9, /* One channel replicated in two. */ + SST_HSW_CHANNEL_CONFIG_INVALID, +}; + +/* List of supported bit depths. */ +enum sst_hsw_bitdepth { + SST_HSW_DEPTH_8BIT = 8, + SST_HSW_DEPTH_16BIT = 16, + SST_HSW_DEPTH_24BIT = 24, /* Default. */ + SST_HSW_DEPTH_32BIT = 32, + SST_HSW_DEPTH_INVALID = 33, +}; + +enum sst_hsw_module_id { + SST_HSW_MODULE_BASE_FW = 0x0, + SST_HSW_MODULE_MP3 = 0x1, + SST_HSW_MODULE_AAC_5_1 = 0x2, + SST_HSW_MODULE_AAC_2_0 = 0x3, + SST_HSW_MODULE_SRC = 0x4, + SST_HSW_MODULE_WAVES = 0x5, + SST_HSW_MODULE_DOLBY = 0x6, + SST_HSW_MODULE_BOOST = 0x7, + SST_HSW_MODULE_LPAL = 0x8, + SST_HSW_MODULE_DTS = 0x9, + SST_HSW_MODULE_PCM_CAPTURE = 0xA, + SST_HSW_MODULE_PCM_SYSTEM = 0xB, + SST_HSW_MODULE_PCM_REFERENCE = 0xC, + SST_HSW_MODULE_PCM = 0xD, + SST_HSW_MODULE_BLUETOOTH_RENDER_MODULE = 0xE, + SST_HSW_MODULE_BLUETOOTH_CAPTURE_MODULE = 0xF, + SST_HSW_MAX_MODULE_ID, +}; + +enum sst_hsw_performance_action { + SST_HSW_PERF_START = 0, + SST_HSW_PERF_STOP = 1, +}; + +struct sst_hsw_transfer_info { + uint32_t destination; /* destination address */ + uint32_t reverse:1; /* if 1 data flows from destination */ + uint32_t size:31; /* transfer size in bytes.*/ + uint16_t first_page_offset; /* offset to data in the first page. */ + uint8_t packed_pages; /* page addresses. Each occupies 20 bits */ +} __attribute__((packed)); + +struct sst_hsw_transfer_list { + uint32_t transfers_count; + struct sst_hsw_transfer_info transfers; +} __attribute__((packed)); + +struct sst_hsw_transfer_parameter { + uint32_t parameter_id; + uint32_t data_size; + union { + uint8_t data[1]; + struct sst_hsw_transfer_list transfer_list; + }; +} __attribute__((packed)); + +/* SST firmware module info */ +struct sst_hsw_module_info { + u8 name[SST_HSW_MAX_INFO_SIZE]; + u8 version[SST_HSW_MAX_INFO_SIZE]; +} __attribute__((packed)); + +/* Module entry point */ +struct sst_hsw_module_entry { + enum sst_hsw_module_id module_id; + u32 entry_point; +} __attribute__((packed)); + +/* Module map - alignement matches DSP */ +struct sst_hsw_module_map { + u8 module_entries_count; + struct sst_hsw_module_entry module_entries[1]; +} __attribute__((packed)); + +struct sst_hsw_memory_info { + u32 offset; + u32 size; +} __attribute__((packed)); + +struct sst_hsw_fx_enable { + struct sst_hsw_module_map module_map; + struct sst_hsw_memory_info persistent_mem; +} __attribute__((packed)); + +struct sst_hsw_ipc_module_config { + struct sst_hsw_module_map map; + struct sst_hsw_memory_info persistent_mem; + struct sst_hsw_memory_info scratch_mem; +} __attribute__((packed)); + +struct sst_hsw_get_fx_param { + u32 parameter_id; + u32 param_size; +} __attribute__((packed)); + +struct sst_hsw_perf_action { + u32 action; +} __attribute__((packed)); + +struct sst_hsw_perf_data { + u64 timestamp; + u64 cycles; + u64 datatime; +} __attribute__((packed)); + +/* FW version */ +struct sst_hsw_ipc_fw_version { + u8 build; + u8 minor; + u8 major; + u8 type; + u8 fw_build_hash[SST_HSW_BUILD_HASH_LENGTH]; + u32 fw_log_providers_hash; +} __attribute__((packed)); + +/* Stream ring info */ +struct sst_hsw_ipc_stream_ring { + u32 ring_pt_address; + u32 num_pages; + u32 ring_size; + u32 ring_offset; + u32 ring_first_pfn; +} __attribute__((packed)); + +/* Debug Dump Log Enable Request */ +struct sst_hsw_ipc_debug_log_enable_req { + struct sst_hsw_ipc_stream_ring ringinfo; + u32 config[SST_HSW_FW_LOG_CONFIG_DWORDS]; +} __attribute__((packed)); + +/* Debug Dump Log Reply */ +struct sst_hsw_ipc_debug_log_reply { + u32 log_buffer_begining; + u32 log_buffer_size; +} __attribute__((packed)); + +/* Stream glitch position */ +struct sst_hsw_ipc_stream_glitch_position { + u32 glitch_type; + u32 present_pos; + u32 write_pos; +} __attribute__((packed)); + +/* Stream get position */ +struct sst_hsw_ipc_stream_get_position { + u32 position; + u32 fw_cycle_count; +} __attribute__((packed)); + +/* Stream set position */ +struct sst_hsw_ipc_stream_set_position { + u32 position; + u32 end_of_buffer; +} __attribute__((packed)); + +/* Stream Free Request */ +struct sst_hsw_ipc_stream_free_req { + u8 stream_id; + u8 reserved[3]; +} __attribute__((packed)); + +/* Set Volume Request */ +struct sst_hsw_ipc_volume_req { + u32 channel; + u32 target_volume; + u64 curve_duration; + u32 curve_type; +} __attribute__((packed)); + +/* Device Configuration Request */ +struct sst_hsw_ipc_device_config_req { + u32 ssp_interface; + u32 clock_frequency; + u32 mode; + u16 clock_divider; + u8 channels; + u8 reserved; +} __attribute__((packed)); + +/* Audio Data formats */ +struct sst_hsw_audio_data_format_ipc { + u32 frequency; + u32 bitdepth; + u32 map; + u32 config; + u32 style; + u8 ch_num; + u8 valid_bit; + u8 reserved[2]; +} __attribute__((packed)); + +/* Stream Allocate Request */ +struct sst_hsw_ipc_stream_alloc_req { + u8 path_id; + u8 stream_type; + u8 format_id; + u8 reserved; + struct sst_hsw_audio_data_format_ipc format; + struct sst_hsw_ipc_stream_ring ringinfo; + struct sst_hsw_module_map map; + struct sst_hsw_memory_info persistent_mem; + struct sst_hsw_memory_info scratch_mem; + u32 number_of_notifications; +} __attribute__((packed)); + +/* Stream Allocate Reply */ +struct sst_hsw_ipc_stream_alloc_reply { + u32 stream_hw_id; + u32 mixer_hw_id; // returns rate ???? + u32 read_position_register_address; + u32 presentation_position_register_address; + u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; + u32 volume_register_address[SST_HSW_NO_CHANNELS]; +} __attribute__((packed)); + +/* Get Mixer Stream Info */ +struct sst_hsw_ipc_stream_info_reply { + u32 mixer_hw_id; + u32 peak_meter_register_address[SST_HSW_NO_CHANNELS]; + u32 volume_register_address[SST_HSW_NO_CHANNELS]; +} __attribute__((packed)); + +/* DX State Request */ +struct sst_hsw_ipc_dx_req { + u8 state; + u8 reserved[3]; +} __attribute__((packed)); + +/* DX State Reply Memory Info Item */ +struct sst_hsw_ipc_dx_memory_item { + u32 offset; + u32 size; + u32 source; +} __attribute__((packed)); + +/* DX State Reply */ +struct sst_hsw_ipc_dx_reply { + u32 entries_no; + struct sst_hsw_ipc_dx_memory_item mem_info[SST_HSW_MAX_DX_REGIONS]; +} __attribute__((packed)); + +struct sst_hsw_ipc_fw_version; + +/* SST Init & Free */ +struct sst_hsw *sst_hsw_new(struct device *dev, const u8 *fw, size_t fw_length, + u32 fw_offset); +void sst_hsw_free(struct sst_hsw *hsw); +int sst_hsw_fw_get_version(struct sst_hsw *hsw, + struct sst_hsw_ipc_fw_version *version); +u32 create_channel_map(enum sst_hsw_channel_config config); + +/* Stream Mixer Controls - */ +int sst_hsw_stream_set_volume(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 volume); +int sst_hsw_stream_get_volume(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 stage_id, u32 channel, u32 *volume); + +/* Global Mixer Controls - */ +int sst_hsw_mixer_set_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 volume); +int sst_hsw_mixer_get_volume(struct sst_hsw *hsw, u32 stage_id, u32 channel, + u32 *volume); + +/* Stream API */ +struct sst_hsw_stream *sst_hsw_stream_new(struct sst_hsw *hsw, int id, + u32 (*get_write_position)(struct sst_hsw_stream *stream, void *data), + void *data); + +int sst_hsw_stream_free(struct sst_hsw *hsw, struct sst_hsw_stream *stream); + +/* Stream Configuration */ +int sst_hsw_stream_format(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_stream_path_id path_id, + enum sst_hsw_stream_type stream_type, + enum sst_hsw_stream_format format_id); + +int sst_hsw_stream_buffer(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 ring_pt_address, u32 num_pages, + u32 ring_size, u32 ring_offset, u32 ring_first_pfn); + +int sst_hsw_stream_commit(struct sst_hsw *hsw, struct sst_hsw_stream *stream); + +int sst_hsw_stream_set_valid(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + u32 bits); +int sst_hsw_stream_set_rate(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int rate); +int sst_hsw_stream_set_bits(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_bitdepth bits); +int sst_hsw_stream_set_channels(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, int channels); +int sst_hsw_stream_set_map_config(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 map, + enum sst_hsw_channel_config config); +int sst_hsw_stream_set_style(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + enum sst_hsw_interleaving style); +int sst_hsw_stream_set_module_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, struct sst_module_runtime *runtime); +int sst_hsw_stream_set_pmemory_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 offset, u32 size); +int sst_hsw_stream_set_smemory_info(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 offset, u32 size); +snd_pcm_uframes_t sst_hsw_stream_get_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); +void sst_hsw_stream_set_old_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, snd_pcm_uframes_t val); +bool sst_hsw_stream_get_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); +void sst_hsw_stream_set_silence_start(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, bool val); +int sst_hsw_mixer_get_info(struct sst_hsw *hsw); + +/* Stream ALSA trigger operations */ +int sst_hsw_stream_pause(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait); +int sst_hsw_stream_resume(struct sst_hsw *hsw, struct sst_hsw_stream *stream, + int wait); +int sst_hsw_stream_reset(struct sst_hsw *hsw, struct sst_hsw_stream *stream); + +/* Stream pointer positions */ +int sst_hsw_stream_get_read_pos(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 *position); +int sst_hsw_stream_get_write_pos(struct sst_hsw *hsw, + struct sst_hsw_stream *stream, u32 *position); +u32 sst_hsw_get_dsp_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); +u64 sst_hsw_get_dsp_presentation_position(struct sst_hsw *hsw, + struct sst_hsw_stream *stream); + +/* HW port config */ +int sst_hsw_device_set_config(struct sst_hsw *hsw, + enum sst_hsw_device_id dev, enum sst_hsw_device_mclk mclk, + enum sst_hsw_device_mode mode, u32 clock_divider); + +/* DX Config */ +int sst_hsw_dx_set_state(struct sst_hsw *hsw, + enum sst_hsw_dx_state state, struct sst_hsw_ipc_dx_reply *dx); + +/* init */ +int sst_hsw_dsp_init(struct device *dev, struct sst_pdata *pdata); +void sst_hsw_dsp_free(struct device *dev, struct sst_pdata *pdata); +struct sst_dsp *sst_hsw_get_dsp(struct sst_hsw *hsw); + +/* fw module function */ +void sst_hsw_init_module_state(struct sst_hsw *hsw); +bool sst_hsw_is_module_loaded(struct sst_hsw *hsw, u32 module_id); +bool sst_hsw_is_module_active(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_set_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_set_module_disabled_rtd3(struct sst_hsw *hsw, u32 module_id); +bool sst_hsw_is_module_enabled_rtd3(struct sst_hsw *hsw, u32 module_id); +void sst_hsw_reset_param_buf(struct sst_hsw *hsw); +int sst_hsw_store_param_line(struct sst_hsw *hsw, u8 *buf); +int sst_hsw_load_param_line(struct sst_hsw *hsw, u8 *buf); +int sst_hsw_launch_param_buf(struct sst_hsw *hsw); + +int sst_hsw_module_load(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, char *name); +int sst_hsw_module_enable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id); +int sst_hsw_module_disable(struct sst_hsw *hsw, + u32 module_id, u32 instance_id); +int sst_hsw_module_set_param(struct sst_hsw *hsw, + u32 module_id, u32 instance_id, u32 parameter_id, + u32 param_size, char *param); + +/* runtime module management */ +struct sst_module_runtime *sst_hsw_runtime_module_create(struct sst_hsw *hsw, + int mod_id, int offset); +void sst_hsw_runtime_module_free(struct sst_module_runtime *runtime); + +/* PM */ +int sst_hsw_dsp_runtime_resume(struct sst_hsw *hsw); +int sst_hsw_dsp_runtime_suspend(struct sst_hsw *hsw); +int sst_hsw_dsp_load(struct sst_hsw *hsw); +int sst_hsw_dsp_runtime_sleep(struct sst_hsw *hsw); + +#endif diff --git a/sound/soc/intel/haswell/sst-haswell-pcm.c b/sound/soc/intel/haswell/sst-haswell-pcm.c new file mode 100644 index 000000000..fe2c826e7 --- /dev/null +++ b/sound/soc/intel/haswell/sst-haswell-pcm.c @@ -0,0 +1,1404 @@ +/* + * Intel SST Haswell/Broadwell PCM Support + * + * Copyright (C) 2013, Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../haswell/sst-haswell-ipc.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-dsp.h" + +#define HSW_PCM_COUNT 6 +#define HSW_VOLUME_MAX 0x7FFFFFFF /* 0dB */ + +#define SST_OLD_POSITION(d, r, o) ((d) + \ + frames_to_bytes(r, o)) +#define SST_SAMPLES(r, x) (bytes_to_samples(r, \ + frames_to_bytes(r, (x)))) + +/* simple volume table */ +static const u32 volume_map[] = { + HSW_VOLUME_MAX >> 30, + HSW_VOLUME_MAX >> 29, + HSW_VOLUME_MAX >> 28, + HSW_VOLUME_MAX >> 27, + HSW_VOLUME_MAX >> 26, + HSW_VOLUME_MAX >> 25, + HSW_VOLUME_MAX >> 24, + HSW_VOLUME_MAX >> 23, + HSW_VOLUME_MAX >> 22, + HSW_VOLUME_MAX >> 21, + HSW_VOLUME_MAX >> 20, + HSW_VOLUME_MAX >> 19, + HSW_VOLUME_MAX >> 18, + HSW_VOLUME_MAX >> 17, + HSW_VOLUME_MAX >> 16, + HSW_VOLUME_MAX >> 15, + HSW_VOLUME_MAX >> 14, + HSW_VOLUME_MAX >> 13, + HSW_VOLUME_MAX >> 12, + HSW_VOLUME_MAX >> 11, + HSW_VOLUME_MAX >> 10, + HSW_VOLUME_MAX >> 9, + HSW_VOLUME_MAX >> 8, + HSW_VOLUME_MAX >> 7, + HSW_VOLUME_MAX >> 6, + HSW_VOLUME_MAX >> 5, + HSW_VOLUME_MAX >> 4, + HSW_VOLUME_MAX >> 3, + HSW_VOLUME_MAX >> 2, + HSW_VOLUME_MAX >> 1, + HSW_VOLUME_MAX >> 0, +}; + +#define HSW_PCM_PERIODS_MAX 64 +#define HSW_PCM_PERIODS_MIN 2 + +#define HSW_PCM_DAI_ID_SYSTEM 0 +#define HSW_PCM_DAI_ID_OFFLOAD0 1 +#define HSW_PCM_DAI_ID_OFFLOAD1 2 +#define HSW_PCM_DAI_ID_LOOPBACK 3 + + +static const struct snd_pcm_hardware hsw_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_DRAIN_TRIGGER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = (HSW_PCM_PERIODS_MAX / HSW_PCM_PERIODS_MIN) * PAGE_SIZE, + .periods_min = HSW_PCM_PERIODS_MIN, + .periods_max = HSW_PCM_PERIODS_MAX, + .buffer_bytes_max = HSW_PCM_PERIODS_MAX * PAGE_SIZE, +}; + +struct hsw_pcm_module_map { + int dai_id; + int stream; + enum sst_hsw_module_id mod_id; +}; + +/* private data for each PCM DSP stream */ +struct hsw_pcm_data { + int dai_id; + struct sst_hsw_stream *stream; + struct sst_module_runtime *runtime; + struct sst_module_runtime_context context; + struct snd_pcm *hsw_pcm; + u32 volume[2]; + struct snd_pcm_substream *substream; + struct snd_compr_stream *cstream; + unsigned int wpos; + struct mutex mutex; + bool allocated; + int persistent_offset; +}; + +enum hsw_pm_state { + HSW_PM_STATE_D0 = 0, + HSW_PM_STATE_RTD3 = 1, + HSW_PM_STATE_D3 = 2, +}; + +/* private data for the driver */ +struct hsw_priv_data { + /* runtime DSP */ + struct sst_hsw *hsw; + struct device *dev; + enum hsw_pm_state pm_state; + struct snd_soc_card *soc_card; + struct sst_module_runtime *runtime_waves; /* sound effect module */ + + /* page tables */ + struct snd_dma_buffer dmab[HSW_PCM_COUNT][2]; + + /* DAI data */ + struct hsw_pcm_data pcm[HSW_PCM_COUNT][2]; +}; + + +/* static mappings between PCMs and modules - may be dynamic in future */ +static struct hsw_pcm_module_map mod_map[] = { + {HSW_PCM_DAI_ID_SYSTEM, 0, SST_HSW_MODULE_PCM_SYSTEM}, + {HSW_PCM_DAI_ID_OFFLOAD0, 0, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_OFFLOAD1, 0, SST_HSW_MODULE_PCM}, + {HSW_PCM_DAI_ID_LOOPBACK, 1, SST_HSW_MODULE_PCM_REFERENCE}, + {HSW_PCM_DAI_ID_SYSTEM, 1, SST_HSW_MODULE_PCM_CAPTURE}, +}; + +static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data); + +static inline u32 hsw_mixer_to_ipc(unsigned int value) +{ + if (value >= ARRAY_SIZE(volume_map)) + return volume_map[0]; + else + return volume_map[value]; +} + +static inline unsigned int hsw_ipc_to_mixer(u32 value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(volume_map); i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + +static int hsw_stream_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_component_get_drvdata(component); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + int dai, stream; + + dai = mod_map[mc->reg].dai_id; + stream = mod_map[mc->reg].stream; + pcm_data = &pdata->pcm[dai][stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + if (!pcm_data->stream) { + pcm_data->volume[0] = + hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + pcm_data->volume[1] = + hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; + } + + if (ucontrol->value.integer.value[0] == + ucontrol->value.integer.value[1]) { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + /* apply volume value to all channels */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, SST_HSW_CHANNELS_ALL, volume); + } else { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 0, volume); + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, 1, volume); + } + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int hsw_stream_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct hsw_priv_data *pdata = + snd_soc_component_get_drvdata(component); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + int dai, stream; + + dai = mod_map[mc->reg].dai_id; + stream = mod_map[mc->reg].stream; + pcm_data = &pdata->pcm[dai][stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + if (!pcm_data->stream) { + ucontrol->value.integer.value[0] = + hsw_ipc_to_mixer(pcm_data->volume[0]); + ucontrol->value.integer.value[1] = + hsw_ipc_to_mixer(pcm_data->volume[1]); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return 0; + } + + sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 0, &volume); + ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); + sst_hsw_stream_get_volume(hsw, pcm_data->stream, 0, 1, &volume); + ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + + return 0; +} + +static int hsw_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_hsw *hsw = pdata->hsw; + u32 volume; + + pm_runtime_get_sync(pdata->dev); + + if (ucontrol->value.integer.value[0] == + ucontrol->value.integer.value[1]) { + + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_mixer_set_volume(hsw, 0, SST_HSW_CHANNELS_ALL, volume); + + } else { + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[0]); + sst_hsw_mixer_set_volume(hsw, 0, 0, volume); + + volume = hsw_mixer_to_ipc(ucontrol->value.integer.value[1]); + sst_hsw_mixer_set_volume(hsw, 0, 1, volume); + } + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + return 0; +} + +static int hsw_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_hsw *hsw = pdata->hsw; + unsigned int volume = 0; + + pm_runtime_get_sync(pdata->dev); + sst_hsw_mixer_get_volume(hsw, 0, 0, &volume); + ucontrol->value.integer.value[0] = hsw_ipc_to_mixer(volume); + + sst_hsw_mixer_get_volume(hsw, 0, 1, &volume); + ucontrol->value.integer.value[1] = hsw_ipc_to_mixer(volume); + + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + return 0; +} + +static int hsw_waves_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_hsw *hsw = pdata->hsw; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + + ucontrol->value.integer.value[0] = + (sst_hsw_is_module_active(hsw, id) || + sst_hsw_is_module_enabled_rtd3(hsw, id)); + return 0; +} + +static int hsw_waves_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_hsw *hsw = pdata->hsw; + int ret = 0; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + bool switch_on = (bool)ucontrol->value.integer.value[0]; + + /* if module is in RAM on the DSP, apply user settings to module through + * ipc. If module is not in RAM on the DSP, store user setting for + * track */ + if (sst_hsw_is_module_loaded(hsw, id)) { + if (switch_on == sst_hsw_is_module_active(hsw, id)) + return 0; + + if (switch_on) + ret = sst_hsw_module_enable(hsw, id, 0); + else + ret = sst_hsw_module_disable(hsw, id, 0); + } else { + if (switch_on == sst_hsw_is_module_enabled_rtd3(hsw, id)) + return 0; + + if (switch_on) + sst_hsw_set_module_enabled_rtd3(hsw, id); + else + sst_hsw_set_module_disabled_rtd3(hsw, id); + } + + return ret; +} + +static int hsw_waves_param_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_hsw *hsw = pdata->hsw; + + /* return a matching line from param buffer */ + return sst_hsw_load_param_line(hsw, ucontrol->value.bytes.data); +} + +static int hsw_waves_param_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_hsw *hsw = pdata->hsw; + int ret; + enum sst_hsw_module_id id = SST_HSW_MODULE_WAVES; + int param_id = ucontrol->value.bytes.data[0]; + int param_size = WAVES_PARAM_COUNT; + + /* clear param buffer and reset buffer index */ + if (param_id == 0xFF) { + sst_hsw_reset_param_buf(hsw); + return 0; + } + + /* store params into buffer */ + ret = sst_hsw_store_param_line(hsw, ucontrol->value.bytes.data); + if (ret < 0) + return ret; + + if (sst_hsw_is_module_active(hsw, id)) + ret = sst_hsw_module_set_param(hsw, id, 0, param_id, + param_size, ucontrol->value.bytes.data); + return ret; +} + +/* TLV used by both global and stream volumes */ +static const DECLARE_TLV_DB_SCALE(hsw_vol_tlv, -9000, 300, 1); + +/* System Pin has no volume control */ +static const struct snd_kcontrol_new hsw_volume_controls[] = { + /* Global DSP volume */ + SOC_DOUBLE_EXT_TLV("Master Playback Volume", 0, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_volume_get, hsw_volume_put, hsw_vol_tlv), + /* Offload 0 volume */ + SOC_DOUBLE_EXT_TLV("Media0 Playback Volume", 1, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* Offload 1 volume */ + SOC_DOUBLE_EXT_TLV("Media1 Playback Volume", 2, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* Mic Capture volume */ + SOC_DOUBLE_EXT_TLV("Mic Capture Volume", 4, 0, 8, + ARRAY_SIZE(volume_map) - 1, 0, + hsw_stream_volume_get, hsw_stream_volume_put, hsw_vol_tlv), + /* enable/disable module waves */ + SOC_SINGLE_BOOL_EXT("Waves Switch", 0, + hsw_waves_switch_get, hsw_waves_switch_put), + /* set parameters to module waves */ + SND_SOC_BYTES_EXT("Waves Set Param", WAVES_PARAM_COUNT, + hsw_waves_param_get, hsw_waves_param_put), +}; + +/* Create DMA buffer page table for DSP */ +static int create_adsp_page_table(struct snd_pcm_substream *substream, + struct hsw_priv_data *pdata, struct snd_soc_pcm_runtime *rtd, + unsigned char *dma_area, size_t size, int pcm) +{ + struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); + int i, pages, stream = substream->stream; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(rtd->dev, "generating page table for %p size 0x%zx pages %d\n", + dma_area, size, pages); + + for (i = 0; i < pages; i++) { + u32 idx = (((i << 2) + i)) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u32 *pg_table; + + dev_dbg(rtd->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); + + pg_table = (u32 *)(pdata->dmab[pcm][stream].area + idx); + + if (i & 1) + *pg_table |= (pfn << 4); + else + *pg_table |= pfn; + } + + return 0; +} + +/* this may get called several times by oss emulation */ +static int hsw_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + struct sst_module *module_data; + struct sst_dsp *dsp; + struct snd_dma_buffer *dmab; + enum sst_hsw_stream_type stream_type; + enum sst_hsw_stream_path_id path_id; + u32 rate, bits, map, pages, module_id; + u8 channels; + int ret, dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + /* check if we are being called a subsequent time */ + if (pcm_data->allocated) { + ret = sst_hsw_stream_reset(hsw, pcm_data->stream); + if (ret < 0) + dev_dbg(rtd->dev, "error: reset stream failed %d\n", + ret); + + ret = sst_hsw_stream_free(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: free stream failed %d\n", + ret); + return ret; + } + pcm_data->allocated = false; + + pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, + hsw_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "error: failed to create stream\n"); + return -EINVAL; + } + } + + /* stream direction */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + path_id = SST_HSW_STREAM_PATH_SSP0_OUT; + else + path_id = SST_HSW_STREAM_PATH_SSP0_IN; + + /* DSP stream type depends on DAI ID */ + switch (rtd->cpu_dai->id) { + case 0: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_type = SST_HSW_STREAM_TYPE_SYSTEM; + module_id = SST_HSW_MODULE_PCM_SYSTEM; + } + else { + stream_type = SST_HSW_STREAM_TYPE_CAPTURE; + module_id = SST_HSW_MODULE_PCM_CAPTURE; + } + break; + case 1: + case 2: + stream_type = SST_HSW_STREAM_TYPE_RENDER; + module_id = SST_HSW_MODULE_PCM; + break; + case 3: + /* path ID needs to be OUT for loopback */ + stream_type = SST_HSW_STREAM_TYPE_LOOPBACK; + path_id = SST_HSW_STREAM_PATH_SSP0_OUT; + module_id = SST_HSW_MODULE_PCM_REFERENCE; + break; + default: + dev_err(rtd->dev, "error: invalid DAI ID %d\n", + rtd->cpu_dai->id); + return -EINVAL; + }; + + ret = sst_hsw_stream_format(hsw, pcm_data->stream, + path_id, stream_type, SST_HSW_STREAM_FORMAT_PCM_FORMAT); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set format %d\n", ret); + return ret; + } + + rate = params_rate(params); + ret = sst_hsw_stream_set_rate(hsw, pcm_data->stream, rate); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set rate %d\n", rate); + return ret; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bits = SST_HSW_DEPTH_16BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 16); + break; + case SNDRV_PCM_FORMAT_S24_LE: + bits = SST_HSW_DEPTH_32BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 24); + break; + case SNDRV_PCM_FORMAT_S8: + bits = SST_HSW_DEPTH_8BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 8); + break; + case SNDRV_PCM_FORMAT_S32_LE: + bits = SST_HSW_DEPTH_32BIT; + sst_hsw_stream_set_valid(hsw, pcm_data->stream, 32); + break; + default: + dev_err(rtd->dev, "error: invalid format %d\n", + params_format(params)); + return -EINVAL; + } + + ret = sst_hsw_stream_set_bits(hsw, pcm_data->stream, bits); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set bits %d\n", bits); + return ret; + } + + channels = params_channels(params); + map = create_channel_map(SST_HSW_CHANNEL_CONFIG_STEREO); + sst_hsw_stream_set_map_config(hsw, pcm_data->stream, + map, SST_HSW_CHANNEL_CONFIG_STEREO); + + ret = sst_hsw_stream_set_channels(hsw, pcm_data->stream, channels); + if (ret < 0) { + dev_err(rtd->dev, "error: could not set channels %d\n", + channels); + return ret; + } + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) { + dev_err(rtd->dev, "error: could not allocate %d bytes for PCM %d\n", + params_buffer_bytes(params), ret); + return ret; + } + + dmab = snd_pcm_get_dma_buf(substream); + + ret = create_adsp_page_table(substream, pdata, rtd, runtime->dma_area, + runtime->dma_bytes, rtd->cpu_dai->id); + if (ret < 0) + return ret; + + sst_hsw_stream_set_style(hsw, pcm_data->stream, + SST_HSW_INTERLEAVING_PER_CHANNEL); + + if (runtime->dma_bytes % PAGE_SIZE) + pages = (runtime->dma_bytes / PAGE_SIZE) + 1; + else + pages = runtime->dma_bytes / PAGE_SIZE; + + ret = sst_hsw_stream_buffer(hsw, pcm_data->stream, + pdata->dmab[rtd->cpu_dai->id][substream->stream].addr, + pages, runtime->dma_bytes, 0, + snd_sgbuf_get_addr(dmab, 0) >> PAGE_SHIFT); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to set DMA buffer %d\n", ret); + return ret; + } + + dsp = sst_hsw_get_dsp(hsw); + + module_data = sst_module_get_from_id(dsp, module_id); + if (module_data == NULL) { + dev_err(rtd->dev, "error: failed to get module config\n"); + return -EINVAL; + } + + sst_hsw_stream_set_module_info(hsw, pcm_data->stream, + pcm_data->runtime); + + ret = sst_hsw_stream_commit(hsw, pcm_data->stream); + if (ret < 0) { + dev_err(rtd->dev, "error: failed to commit stream %d\n", ret); + return ret; + } + + if (!pcm_data->allocated) { + /* Set previous saved volume */ + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 0, pcm_data->volume[0]); + sst_hsw_stream_set_volume(hsw, pcm_data->stream, 0, + 1, pcm_data->volume[1]); + pcm_data->allocated = true; + } + + ret = sst_hsw_stream_pause(hsw, pcm_data->stream, 1); + if (ret < 0) + dev_err(rtd->dev, "error: failed to pause %d\n", ret); + + return 0; +} + +static int hsw_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int hsw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct hsw_pcm_data *pcm_data; + struct sst_hsw_stream *sst_stream; + struct sst_hsw *hsw = pdata->hsw; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t pos; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + sst_stream = pcm_data->stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sst_hsw_stream_set_silence_start(hsw, sst_stream, false); + sst_hsw_stream_resume(hsw, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sst_hsw_stream_set_silence_start(hsw, sst_stream, false); + sst_hsw_stream_pause(hsw, pcm_data->stream, 0); + break; + case SNDRV_PCM_TRIGGER_DRAIN: + pos = runtime->control->appl_ptr % runtime->buffer_size; + sst_hsw_stream_set_old_position(hsw, pcm_data->stream, pos); + sst_hsw_stream_set_silence_start(hsw, sst_stream, true); + break; + default: + break; + } + + return 0; +} + +static u32 hsw_notify_pointer(struct sst_hsw_stream *stream, void *data) +{ + struct hsw_pcm_data *pcm_data = data; + struct snd_pcm_substream *substream = pcm_data->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct sst_hsw *hsw = pdata->hsw; + u32 pos; + snd_pcm_uframes_t position = bytes_to_frames(runtime, + sst_hsw_get_dsp_position(hsw, pcm_data->stream)); + unsigned char *dma_area = runtime->dma_area; + snd_pcm_uframes_t dma_frames = + bytes_to_frames(runtime, runtime->dma_bytes); + snd_pcm_uframes_t old_position; + ssize_t samples; + + pos = frames_to_bytes(runtime, + (runtime->control->appl_ptr % runtime->buffer_size)); + + dev_vdbg(rtd->dev, "PCM: App pointer %d bytes\n", pos); + + /* SST fw don't know where to stop dma + * So, SST driver need to clean the data which has been consumed + */ + if (dma_area == NULL || dma_frames <= 0 + || (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !sst_hsw_stream_get_silence_start(hsw, stream)) { + snd_pcm_period_elapsed(substream); + return pos; + } + + old_position = sst_hsw_stream_get_old_position(hsw, stream); + if (position > old_position) { + if (position < dma_frames) { + samples = SST_SAMPLES(runtime, position - old_position); + snd_pcm_format_set_silence(runtime->format, + SST_OLD_POSITION(dma_area, + runtime, old_position), + samples); + } else + dev_err(rtd->dev, "PCM: position is wrong\n"); + } else { + if (old_position < dma_frames) { + samples = SST_SAMPLES(runtime, + dma_frames - old_position); + snd_pcm_format_set_silence(runtime->format, + SST_OLD_POSITION(dma_area, + runtime, old_position), + samples); + } else + dev_err(rtd->dev, "PCM: dma_bytes is wrong\n"); + if (position < dma_frames) { + samples = SST_SAMPLES(runtime, position); + snd_pcm_format_set_silence(runtime->format, + dma_area, samples); + } else + dev_err(rtd->dev, "PCM: position is wrong\n"); + } + sst_hsw_stream_set_old_position(hsw, stream, position); + + /* let alsa know we have play a period */ + snd_pcm_period_elapsed(substream); + return pos; +} + +static snd_pcm_uframes_t hsw_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + snd_pcm_uframes_t offset; + uint64_t ppos; + u32 position; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + position = sst_hsw_get_dsp_position(hsw, pcm_data->stream); + + offset = bytes_to_frames(runtime, position); + ppos = sst_hsw_get_dsp_presentation_position(hsw, pcm_data->stream); + + dev_vdbg(rtd->dev, "PCM: DMA pointer %du bytes, pos %llu\n", + position, ppos); + return offset; +} + +static int hsw_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + int dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + mutex_lock(&pcm_data->mutex); + pm_runtime_get_sync(pdata->dev); + + pcm_data->substream = substream; + + snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); + + pcm_data->stream = sst_hsw_stream_new(hsw, rtd->cpu_dai->id, + hsw_notify_pointer, pcm_data); + if (pcm_data->stream == NULL) { + dev_err(rtd->dev, "error: failed to create stream\n"); + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return -EINVAL; + } + + mutex_unlock(&pcm_data->mutex); + return 0; +} + +static int hsw_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct hsw_priv_data *pdata = snd_soc_component_get_drvdata(component); + struct hsw_pcm_data *pcm_data; + struct sst_hsw *hsw = pdata->hsw; + int ret, dai; + + dai = mod_map[rtd->cpu_dai->id].dai_id; + pcm_data = &pdata->pcm[dai][substream->stream]; + + mutex_lock(&pcm_data->mutex); + ret = sst_hsw_stream_reset(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: reset stream failed %d\n", ret); + goto out; + } + + ret = sst_hsw_stream_free(hsw, pcm_data->stream); + if (ret < 0) { + dev_dbg(rtd->dev, "error: free stream failed %d\n", ret); + goto out; + } + pcm_data->allocated = 0; + pcm_data->stream = NULL; + +out: + pm_runtime_mark_last_busy(pdata->dev); + pm_runtime_put_autosuspend(pdata->dev); + mutex_unlock(&pcm_data->mutex); + return ret; +} + +static const struct snd_pcm_ops hsw_pcm_ops = { + .open = hsw_pcm_open, + .close = hsw_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hsw_pcm_hw_params, + .hw_free = hsw_pcm_hw_free, + .trigger = hsw_pcm_trigger, + .pointer = hsw_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +static int hsw_pcm_create_modules(struct hsw_priv_data *pdata) +{ + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + /* create new runtime module, use same offset if recreated */ + pcm_data->runtime = sst_hsw_runtime_module_create(hsw, + mod_map[i].mod_id, pcm_data->persistent_offset); + if (pcm_data->runtime == NULL) + goto err; + pcm_data->persistent_offset = + pcm_data->runtime->persistent_offset; + } + + /* create runtime blocks for module waves */ + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES)) { + pdata->runtime_waves = sst_hsw_runtime_module_create(hsw, + SST_HSW_MODULE_WAVES, 0); + if (pdata->runtime_waves == NULL) + goto err; + } + + return 0; + +err: + for (--i; i >= 0; i--) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + sst_hsw_runtime_module_free(pcm_data->runtime); + } + + return -ENODEV; +} + +static void hsw_pcm_free_modules(struct hsw_priv_data *pdata) +{ + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i; + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + if (pcm_data->runtime){ + sst_hsw_runtime_module_free(pcm_data->runtime); + pcm_data->runtime = NULL; + } + } + if (sst_hsw_is_module_loaded(hsw, SST_HSW_MODULE_WAVES) && + pdata->runtime_waves) { + sst_hsw_runtime_module_free(pdata->runtime_waves); + pdata->runtime_waves = NULL; + } +} + +static int hsw_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct sst_pdata *pdata = dev_get_platdata(component->dev); + struct hsw_priv_data *priv_data = dev_get_drvdata(component->dev); + struct device *dev = pdata->dma_dev; + int ret = 0; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream || + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV_SG, + dev, + hsw_pcm_hardware.buffer_bytes_max, + hsw_pcm_hardware.buffer_bytes_max); + if (ret) { + dev_err(rtd->dev, "dma buffer allocation failed %d\n", + ret); + return ret; + } + } + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_PLAYBACK].hsw_pcm = pcm; + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) + priv_data->pcm[rtd->cpu_dai->id][SNDRV_PCM_STREAM_CAPTURE].hsw_pcm = pcm; + + return ret; +} + +#define HSW_FORMATS \ + (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_driver hsw_dais[] = { + { + .name = "System Pin", + .id = HSW_PCM_DAI_ID_SYSTEM, + .playback = { + .stream_name = "System Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Analog Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + /* PCM */ + .name = "Offload0 Pin", + .id = HSW_PCM_DAI_ID_OFFLOAD0, + .playback = { + .stream_name = "Offload0 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = HSW_FORMATS, + }, + }, + { + /* PCM */ + .name = "Offload1 Pin", + .id = HSW_PCM_DAI_ID_OFFLOAD1, + .playback = { + .stream_name = "Offload1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = HSW_FORMATS, + }, + }, + { + .name = "Loopback Pin", + .id = HSW_PCM_DAI_ID_LOOPBACK, + .capture = { + .stream_name = "Loopback Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static const struct snd_soc_dapm_widget widgets[] = { + + /* Backend DAIs */ + SND_SOC_DAPM_AIF_IN("SSP0 CODEC IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SSP0 CODEC OUT", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SSP1 BT IN", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SSP1 BT OUT", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* Global Playback Mixer */ + SND_SOC_DAPM_MIXER("Playback VMixer", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route graph[] = { + + /* Playback Mixer */ + {"Playback VMixer", NULL, "System Playback"}, + {"Playback VMixer", NULL, "Offload0 Playback"}, + {"Playback VMixer", NULL, "Offload1 Playback"}, + + {"SSP0 CODEC OUT", NULL, "Playback VMixer"}, + + {"Analog Capture", NULL, "SSP0 CODEC IN"}, +}; + +static int hsw_pcm_probe(struct snd_soc_component *component) +{ + struct hsw_priv_data *priv_data = snd_soc_component_get_drvdata(component); + struct sst_pdata *pdata = dev_get_platdata(component->dev); + struct device *dma_dev, *dev; + int i, ret = 0; + + if (!pdata) + return -ENODEV; + + dev = component->dev; + dma_dev = pdata->dma_dev; + + priv_data->hsw = pdata->dsp; + priv_data->dev = dev; + priv_data->pm_state = HSW_PM_STATE_D0; + priv_data->soc_card = component->card; + + /* allocate DSP buffer page tables */ + for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { + + /* playback */ + if (hsw_dais[i].playback.channels_min) { + mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_PLAYBACK].mutex); + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, + PAGE_SIZE, &priv_data->dmab[i][0]); + if (ret < 0) + goto err; + } + + /* capture */ + if (hsw_dais[i].capture.channels_min) { + mutex_init(&priv_data->pcm[i][SNDRV_PCM_STREAM_CAPTURE].mutex); + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dma_dev, + PAGE_SIZE, &priv_data->dmab[i][1]); + if (ret < 0) + goto err; + } + } + + /* allocate runtime modules */ + ret = hsw_pcm_create_modules(priv_data); + if (ret < 0) + goto err; + + /* enable runtime PM with auto suspend */ + pm_runtime_set_autosuspend_delay(dev, SST_RUNTIME_SUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err: + for (--i; i >= 0; i--) { + if (hsw_dais[i].playback.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][0]); + if (hsw_dais[i].capture.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][1]); + } + return ret; +} + +static void hsw_pcm_remove(struct snd_soc_component *component) +{ + struct hsw_priv_data *priv_data = + snd_soc_component_get_drvdata(component); + int i; + + pm_runtime_disable(component->dev); + hsw_pcm_free_modules(priv_data); + + for (i = 0; i < ARRAY_SIZE(hsw_dais); i++) { + if (hsw_dais[i].playback.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][0]); + if (hsw_dais[i].capture.channels_min) + snd_dma_free_pages(&priv_data->dmab[i][1]); + } +} + +static const struct snd_soc_component_driver hsw_dai_component = { + .name = DRV_NAME, + .probe = hsw_pcm_probe, + .remove = hsw_pcm_remove, + .ops = &hsw_pcm_ops, + .pcm_new = hsw_pcm_new, + .controls = hsw_volume_controls, + .num_controls = ARRAY_SIZE(hsw_volume_controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = graph, + .num_dapm_routes = ARRAY_SIZE(graph), +}; + +static int hsw_pcm_dev_probe(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + struct hsw_priv_data *priv_data; + int ret; + + if (!sst_pdata) + return -EINVAL; + + priv_data = devm_kzalloc(&pdev->dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + ret = sst_hsw_dsp_init(&pdev->dev, sst_pdata); + if (ret < 0) + return -ENODEV; + + priv_data->hsw = sst_pdata->dsp; + platform_set_drvdata(pdev, priv_data); + + ret = devm_snd_soc_register_component(&pdev->dev, &hsw_dai_component, + hsw_dais, ARRAY_SIZE(hsw_dais)); + if (ret < 0) + goto err_plat; + + return 0; + +err_plat: + sst_hsw_dsp_free(&pdev->dev, sst_pdata); + return 0; +} + +static int hsw_pcm_dev_remove(struct platform_device *pdev) +{ + struct sst_pdata *sst_pdata = dev_get_platdata(&pdev->dev); + + sst_hsw_dsp_free(&pdev->dev, sst_pdata); + + return 0; +} + +#ifdef CONFIG_PM + +static int hsw_pcm_runtime_idle(struct device *dev) +{ + return 0; +} + +static int hsw_pcm_suspend(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + + /* enter D3 state and stall */ + sst_hsw_dsp_runtime_suspend(hsw); + /* free all runtime modules */ + hsw_pcm_free_modules(pdata); + /* put the DSP to sleep, fw unloaded after runtime modules freed */ + sst_hsw_dsp_runtime_sleep(hsw); + return 0; +} + +static int hsw_pcm_runtime_suspend(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + int ret; + + if (pdata->pm_state >= HSW_PM_STATE_RTD3) + return 0; + + /* fw modules will be unloaded on RTD3, set flag to track */ + if (sst_hsw_is_module_active(hsw, SST_HSW_MODULE_WAVES)) { + ret = sst_hsw_module_disable(hsw, SST_HSW_MODULE_WAVES, 0); + if (ret < 0) + return ret; + sst_hsw_set_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES); + } + hsw_pcm_suspend(dev); + pdata->pm_state = HSW_PM_STATE_RTD3; + + return 0; +} + +static int hsw_pcm_runtime_resume(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + int ret; + + if (pdata->pm_state != HSW_PM_STATE_RTD3) + return 0; + + ret = sst_hsw_dsp_load(hsw); + if (ret < 0) { + dev_err(dev, "failed to reload %d\n", ret); + return ret; + } + + ret = hsw_pcm_create_modules(pdata); + if (ret < 0) { + dev_err(dev, "failed to create modules %d\n", ret); + return ret; + } + + ret = sst_hsw_dsp_runtime_resume(hsw); + if (ret < 0) + return ret; + else if (ret == 1) /* no action required */ + return 0; + + /* check flag when resume */ + if (sst_hsw_is_module_enabled_rtd3(hsw, SST_HSW_MODULE_WAVES)) { + ret = sst_hsw_module_enable(hsw, SST_HSW_MODULE_WAVES, 0); + if (ret < 0) + return ret; + /* put parameters from buffer to dsp */ + ret = sst_hsw_launch_param_buf(hsw); + if (ret < 0) + return ret; + /* unset flag */ + sst_hsw_set_module_disabled_rtd3(hsw, SST_HSW_MODULE_WAVES); + } + + pdata->pm_state = HSW_PM_STATE_D0; + return ret; +} + +#else +#define hsw_pcm_runtime_idle NULL +#define hsw_pcm_runtime_suspend NULL +#define hsw_pcm_runtime_resume NULL +#endif + +#ifdef CONFIG_PM + +static void hsw_pcm_complete(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct sst_hsw *hsw = pdata->hsw; + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state != HSW_PM_STATE_D3) + return; + + err = sst_hsw_dsp_load(hsw); + if (err < 0) { + dev_err(dev, "failed to reload %d\n", err); + return; + } + + err = hsw_pcm_create_modules(pdata); + if (err < 0) { + dev_err(dev, "failed to create modules %d\n", err); + return; + } + + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + + err = sst_module_runtime_restore(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to restore context for PCM %d\n", i); + } + + snd_soc_resume(pdata->soc_card->dev); + + err = sst_hsw_dsp_runtime_resume(hsw); + if (err < 0) + return; + else if (err == 1) /* no action required */ + return; + + pdata->pm_state = HSW_PM_STATE_D0; + return; +} + +static int hsw_pcm_prepare(struct device *dev) +{ + struct hsw_priv_data *pdata = dev_get_drvdata(dev); + struct hsw_pcm_data *pcm_data; + int i, err; + + if (pdata->pm_state == HSW_PM_STATE_D3) + return 0; + else if (pdata->pm_state == HSW_PM_STATE_D0) { + /* suspend all active streams */ + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + dev_dbg(dev, "suspending pcm %d\n", i); + snd_pcm_suspend_all(pcm_data->hsw_pcm); + + /* We need to wait until the DSP FW stops the streams */ + msleep(2); + } + + /* preserve persistent memory */ + for (i = 0; i < ARRAY_SIZE(mod_map); i++) { + pcm_data = &pdata->pcm[mod_map[i].dai_id][mod_map[i].stream]; + + if (!pcm_data->substream) + continue; + + dev_dbg(dev, "saving context pcm %d\n", i); + err = sst_module_runtime_save(pcm_data->runtime, + &pcm_data->context); + if (err < 0) + dev_err(dev, "failed to save context for PCM %d\n", i); + } + hsw_pcm_suspend(dev); + } + + snd_soc_suspend(pdata->soc_card->dev); + snd_soc_poweroff(pdata->soc_card->dev); + + pdata->pm_state = HSW_PM_STATE_D3; + + return 0; +} + +#else +#define hsw_pcm_prepare NULL +#define hsw_pcm_complete NULL +#endif + +static const struct dev_pm_ops hsw_pcm_pm = { + .runtime_idle = hsw_pcm_runtime_idle, + .runtime_suspend = hsw_pcm_runtime_suspend, + .runtime_resume = hsw_pcm_runtime_resume, + .prepare = hsw_pcm_prepare, + .complete = hsw_pcm_complete, +}; + +static struct platform_driver hsw_pcm_driver = { + .driver = { + .name = "haswell-pcm-audio", + .pm = &hsw_pcm_pm, + }, + + .probe = hsw_pcm_dev_probe, + .remove = hsw_pcm_dev_remove, +}; +module_platform_driver(hsw_pcm_driver); + +MODULE_AUTHOR("Liam Girdwood, Xingchao Wang"); +MODULE_DESCRIPTION("Haswell/Lynxpoint + Broadwell/Wildcatpoint PCM"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:haswell-pcm-audio"); diff --git a/sound/soc/intel/skylake/Makefile b/sound/soc/intel/skylake/Makefile new file mode 100644 index 000000000..86f6e1d80 --- /dev/null +++ b/sound/soc/intel/skylake/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-skl-objs := skl.o skl-pcm.o skl-nhlt.o skl-messages.o \ +skl-topology.o + +ifdef CONFIG_DEBUG_FS + snd-soc-skl-objs += skl-debug.o +endif + +obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl.o + +# Skylake IPC Support +snd-soc-skl-ipc-objs := skl-sst-ipc.o skl-sst-dsp.o cnl-sst-dsp.o \ + skl-sst-cldma.o skl-sst.o bxt-sst.o cnl-sst.o \ + skl-sst-utils.o + +obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl-ipc.o + +#Skylake Clock device support +snd-soc-skl-ssp-clk-objs := skl-ssp-clk.o + +obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_SSP_CLK) += snd-soc-skl-ssp-clk.o diff --git a/sound/soc/intel/skylake/bxt-sst.c b/sound/soc/intel/skylake/bxt-sst.c new file mode 100644 index 000000000..440bca7af --- /dev/null +++ b/sound/soc/intel/skylake/bxt-sst.c @@ -0,0 +1,642 @@ +/* + * bxt-sst.c - DSP library functions for BXT platform + * + * Copyright (C) 2015-16 Intel Corp + * Author:Rafal Redzimski + * Jeeja KP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "skl-sst-ipc.h" + +#define BXT_BASEFW_TIMEOUT 3000 +#define BXT_INIT_TIMEOUT 300 +#define BXT_ROM_INIT_TIMEOUT 70 +#define BXT_IPC_PURGE_FW 0x01004000 + +#define BXT_ROM_INIT 0x5 +#define BXT_ADSP_SRAM0_BASE 0x80000 + +/* Firmware status window */ +#define BXT_ADSP_FW_STATUS BXT_ADSP_SRAM0_BASE +#define BXT_ADSP_ERROR_CODE (BXT_ADSP_FW_STATUS + 0x4) + +#define BXT_ADSP_SRAM1_BASE 0xA0000 + +#define BXT_INSTANCE_ID 0 +#define BXT_BASE_FW_MODULE_ID 0 + +#define BXT_ADSP_FW_BIN_HDR_OFFSET 0x2000 + +/* Delay before scheduling D0i3 entry */ +#define BXT_D0I3_DELAY 5000 + +#define BXT_FW_ROM_INIT_RETRY 3 + +static unsigned int bxt_get_errorcode(struct sst_dsp *ctx) +{ + return sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE); +} + +static int +bxt_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) +{ + struct snd_dma_buffer dmab; + struct skl_sst *skl = ctx->thread_context; + struct firmware stripped_fw; + int ret = 0, i, dma_id, stream_tag; + + /* library indices start from 1 to N. 0 represents base FW */ + for (i = 1; i < lib_count; i++) { + ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw, + BXT_ADSP_FW_BIN_HDR_OFFSET, i); + if (ret < 0) + goto load_library_failed; + + stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, + stripped_fw.size, &dmab); + if (stream_tag <= 0) { + dev_err(ctx->dev, "Lib prepare DMA err: %x\n", + stream_tag); + ret = stream_tag; + goto load_library_failed; + } + + dma_id = stream_tag - 1; + memcpy(dmab.area, stripped_fw.data, stripped_fw.size); + + ctx->dsp_ops.trigger(ctx->dev, true, stream_tag); + ret = skl_sst_ipc_load_library(&skl->ipc, dma_id, i, true); + if (ret < 0) + dev_err(ctx->dev, "IPC Load Lib for %s fail: %d\n", + linfo[i].name, ret); + + ctx->dsp_ops.trigger(ctx->dev, false, stream_tag); + ctx->dsp_ops.cleanup(ctx->dev, &dmab, stream_tag); + } + + return ret; + +load_library_failed: + skl_release_library(linfo, lib_count); + return ret; +} + +/* + * First boot sequence has some extra steps. Core 0 waits for power + * status on core 1, so power up core 1 also momentarily, keep it in + * reset/stall and then turn it off + */ +static int sst_bxt_prepare_fw(struct sst_dsp *ctx, + const void *fwdata, u32 fwsize) +{ + int stream_tag, ret; + + stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, fwsize, &ctx->dmab); + if (stream_tag <= 0) { + dev_err(ctx->dev, "Failed to prepare DMA FW loading err: %x\n", + stream_tag); + return stream_tag; + } + + ctx->dsp_ops.stream_tag = stream_tag; + memcpy(ctx->dmab.area, fwdata, fwsize); + + /* Step 1: Power up core 0 and core1 */ + ret = skl_dsp_core_power_up(ctx, SKL_DSP_CORE0_MASK | + SKL_DSP_CORE_MASK(1)); + if (ret < 0) { + dev_err(ctx->dev, "dsp core0/1 power up failed\n"); + goto base_fw_load_failed; + } + + /* Step 2: Purge FW request */ + sst_dsp_shim_write(ctx, SKL_ADSP_REG_HIPCI, SKL_ADSP_REG_HIPCI_BUSY | + (BXT_IPC_PURGE_FW | ((stream_tag - 1) << 9))); + + /* Step 3: Unset core0 reset state & unstall/run core0 */ + ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK); + if (ret < 0) { + dev_err(ctx->dev, "Start dsp core failed ret: %d\n", ret); + ret = -EIO; + goto base_fw_load_failed; + } + + /* Step 4: Wait for DONE Bit */ + ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_HIPCIE, + SKL_ADSP_REG_HIPCIE_DONE, + SKL_ADSP_REG_HIPCIE_DONE, + BXT_INIT_TIMEOUT, "HIPCIE Done"); + if (ret < 0) { + dev_err(ctx->dev, "Timeout for Purge Request%d\n", ret); + goto base_fw_load_failed; + } + + /* Step 5: power down core1 */ + ret = skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + if (ret < 0) { + dev_err(ctx->dev, "dsp core1 power down failed\n"); + goto base_fw_load_failed; + } + + /* Step 6: Enable Interrupt */ + skl_ipc_int_enable(ctx); + skl_ipc_op_int_enable(ctx); + + /* Step 7: Wait for ROM init */ + ret = sst_dsp_register_poll(ctx, BXT_ADSP_FW_STATUS, SKL_FW_STS_MASK, + SKL_FW_INIT, BXT_ROM_INIT_TIMEOUT, "ROM Load"); + if (ret < 0) { + dev_err(ctx->dev, "Timeout for ROM init, ret:%d\n", ret); + goto base_fw_load_failed; + } + + return ret; + +base_fw_load_failed: + ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, stream_tag); + skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + return ret; +} + +static int sst_transfer_fw_host_dma(struct sst_dsp *ctx) +{ + int ret; + + ctx->dsp_ops.trigger(ctx->dev, true, ctx->dsp_ops.stream_tag); + ret = sst_dsp_register_poll(ctx, BXT_ADSP_FW_STATUS, SKL_FW_STS_MASK, + BXT_ROM_INIT, BXT_BASEFW_TIMEOUT, "Firmware boot"); + + ctx->dsp_ops.trigger(ctx->dev, false, ctx->dsp_ops.stream_tag); + ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, ctx->dsp_ops.stream_tag); + + return ret; +} + +static int bxt_load_base_firmware(struct sst_dsp *ctx) +{ + struct firmware stripped_fw; + struct skl_sst *skl = ctx->thread_context; + int ret, i; + + if (ctx->fw == NULL) { + ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); + if (ret < 0) { + dev_err(ctx->dev, "Request firmware failed %d\n", ret); + return ret; + } + } + + /* prase uuids on first boot */ + if (skl->is_first_boot) { + ret = snd_skl_parse_uuids(ctx, ctx->fw, BXT_ADSP_FW_BIN_HDR_OFFSET, 0); + if (ret < 0) + goto sst_load_base_firmware_failed; + } + + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + skl_dsp_strip_extended_manifest(&stripped_fw); + + + for (i = 0; i < BXT_FW_ROM_INIT_RETRY; i++) { + ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); + if (ret == 0) + break; + } + + if (ret < 0) { + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), + sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); + + dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret); + goto sst_load_base_firmware_failed; + } + + ret = sst_transfer_fw_host_dma(ctx); + if (ret < 0) { + dev_err(ctx->dev, "Transfer firmware failed %d\n", ret); + dev_info(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), + sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); + + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + } else { + dev_dbg(ctx->dev, "Firmware download successful\n"); + ret = wait_event_timeout(skl->boot_wait, skl->boot_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + if (ret == 0) { + dev_err(ctx->dev, "DSP boot fail, FW Ready timeout\n"); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + ret = -EIO; + } else { + ret = 0; + skl->fw_loaded = true; + } + } + + return ret; + +sst_load_base_firmware_failed: + release_firmware(ctx->fw); + ctx->fw = NULL; + return ret; +} + +/* + * Decide the D0i3 state that can be targeted based on the usecase + * ref counts and DSP state + * + * Decision Matrix: (X= dont care; state = target state) + * + * DSP state != SKL_DSP_RUNNING ; state = no d0i3 + * + * DSP state == SKL_DSP_RUNNING , the following matrix applies + * non_d0i3 >0; streaming =X; non_streaming =X; state = no d0i3 + * non_d0i3 =X; streaming =0; non_streaming =0; state = no d0i3 + * non_d0i3 =0; streaming >0; non_streaming =X; state = streaming d0i3 + * non_d0i3 =0; streaming =0; non_streaming =X; state = non-streaming d0i3 + */ +static int bxt_d0i3_target_state(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + struct skl_d0i3_data *d0i3 = &skl->d0i3; + + if (skl->cores.state[SKL_DSP_CORE0_ID] != SKL_DSP_RUNNING) + return SKL_DSP_D0I3_NONE; + + if (d0i3->non_d0i3) + return SKL_DSP_D0I3_NONE; + else if (d0i3->streaming) + return SKL_DSP_D0I3_STREAMING; + else if (d0i3->non_streaming) + return SKL_DSP_D0I3_NON_STREAMING; + else + return SKL_DSP_D0I3_NONE; +} + +static void bxt_set_dsp_D0i3(struct work_struct *work) +{ + int ret; + struct skl_ipc_d0ix_msg msg; + struct skl_sst *skl = container_of(work, + struct skl_sst, d0i3.work.work); + struct sst_dsp *ctx = skl->dsp; + struct skl_d0i3_data *d0i3 = &skl->d0i3; + int target_state; + + dev_dbg(ctx->dev, "In %s:\n", __func__); + + /* D0i3 entry allowed only if core 0 alone is running */ + if (skl_dsp_get_enabled_cores(ctx) != SKL_DSP_CORE0_MASK) { + dev_warn(ctx->dev, + "D0i3 allowed when only core0 running:Exit\n"); + return; + } + + target_state = bxt_d0i3_target_state(ctx); + if (target_state == SKL_DSP_D0I3_NONE) + return; + + msg.instance_id = 0; + msg.module_id = 0; + msg.wake = 1; + msg.streaming = 0; + if (target_state == SKL_DSP_D0I3_STREAMING) + msg.streaming = 1; + + ret = skl_ipc_set_d0ix(&skl->ipc, &msg); + + if (ret < 0) { + dev_err(ctx->dev, "Failed to set DSP to D0i3 state\n"); + return; + } + + /* Set Vendor specific register D0I3C.I3 to enable D0i3*/ + if (skl->update_d0i3c) + skl->update_d0i3c(skl->dev, true); + + d0i3->state = target_state; + skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING_D0I3; +} + +static int bxt_schedule_dsp_D0i3(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + struct skl_d0i3_data *d0i3 = &skl->d0i3; + + /* Schedule D0i3 only if the usecase ref counts are appropriate */ + if (bxt_d0i3_target_state(ctx) != SKL_DSP_D0I3_NONE) { + + dev_dbg(ctx->dev, "%s: Schedule D0i3\n", __func__); + + schedule_delayed_work(&d0i3->work, + msecs_to_jiffies(BXT_D0I3_DELAY)); + } + + return 0; +} + +static int bxt_set_dsp_D0i0(struct sst_dsp *ctx) +{ + int ret; + struct skl_ipc_d0ix_msg msg; + struct skl_sst *skl = ctx->thread_context; + + dev_dbg(ctx->dev, "In %s:\n", __func__); + + /* First Cancel any pending attempt to put DSP to D0i3 */ + cancel_delayed_work_sync(&skl->d0i3.work); + + /* If DSP is currently in D0i3, bring it to D0i0 */ + if (skl->cores.state[SKL_DSP_CORE0_ID] != SKL_DSP_RUNNING_D0I3) + return 0; + + dev_dbg(ctx->dev, "Set DSP to D0i0\n"); + + msg.instance_id = 0; + msg.module_id = 0; + msg.streaming = 0; + msg.wake = 0; + + if (skl->d0i3.state == SKL_DSP_D0I3_STREAMING) + msg.streaming = 1; + + /* Clear Vendor specific register D0I3C.I3 to disable D0i3*/ + if (skl->update_d0i3c) + skl->update_d0i3c(skl->dev, false); + + ret = skl_ipc_set_d0ix(&skl->ipc, &msg); + if (ret < 0) { + dev_err(ctx->dev, "Failed to set DSP to D0i0\n"); + return ret; + } + + skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING; + skl->d0i3.state = SKL_DSP_D0I3_NONE; + + return 0; +} + +static int bxt_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *skl = ctx->thread_context; + int ret; + struct skl_ipc_dxstate_info dx; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); + + if (skl->fw_loaded == false) { + skl->boot_complete = false; + ret = bxt_load_base_firmware(ctx); + if (ret < 0) { + dev_err(ctx->dev, "reload fw failed: %d\n", ret); + return ret; + } + + if (skl->lib_count > 1) { + ret = bxt_load_library(ctx, skl->lib_info, + skl->lib_count); + if (ret < 0) { + dev_err(ctx->dev, "reload libs failed: %d\n", ret); + return ret; + } + } + skl->cores.state[core_id] = SKL_DSP_RUNNING; + return ret; + } + + /* If core 0 is being turned on, turn on core 1 as well */ + if (core_id == SKL_DSP_CORE0_ID) + ret = skl_dsp_core_power_up(ctx, core_mask | + SKL_DSP_CORE_MASK(1)); + else + ret = skl_dsp_core_power_up(ctx, core_mask); + + if (ret < 0) + goto err; + + if (core_id == SKL_DSP_CORE0_ID) { + + /* + * Enable interrupt after SPA is set and before + * DSP is unstalled + */ + skl_ipc_int_enable(ctx); + skl_ipc_op_int_enable(ctx); + skl->boot_complete = false; + } + + ret = skl_dsp_start_core(ctx, core_mask); + if (ret < 0) + goto err; + + if (core_id == SKL_DSP_CORE0_ID) { + ret = wait_event_timeout(skl->boot_wait, + skl->boot_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + + /* If core 1 was turned on for booting core 0, turn it off */ + skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + if (ret == 0) { + dev_err(ctx->dev, "%s: DSP boot timeout\n", __func__); + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), + sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); + dev_err(ctx->dev, "Failed to set core0 to D0 state\n"); + ret = -EIO; + goto err; + } + } + + /* Tell FW if additional core in now On */ + + if (core_id != SKL_DSP_CORE0_ID) { + dx.core_mask = core_mask; + dx.dx_mask = core_mask; + + ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, + BXT_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, "IPC set_dx for core %d fail: %d\n", + core_id, ret); + goto err; + } + } + + skl->cores.state[core_id] = SKL_DSP_RUNNING; + return 0; +err: + if (core_id == SKL_DSP_CORE0_ID) + core_mask |= SKL_DSP_CORE_MASK(1); + skl_dsp_disable_core(ctx, core_mask); + + return ret; +} + +static int bxt_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) +{ + int ret; + struct skl_ipc_dxstate_info dx; + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); + + dx.core_mask = core_mask; + dx.dx_mask = SKL_IPC_D3_MASK; + + dev_dbg(ctx->dev, "core mask=%x dx_mask=%x\n", + dx.core_mask, dx.dx_mask); + + ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, + BXT_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, + "Failed to set DSP to D3:core id = %d;Continue reset\n", + core_id); + /* + * In case of D3 failure, re-download the firmware, so set + * fw_loaded to false. + */ + skl->fw_loaded = false; + } + + if (core_id == SKL_DSP_CORE0_ID) { + /* disable Interrupt */ + skl_ipc_op_int_disable(ctx); + skl_ipc_int_disable(ctx); + } + ret = skl_dsp_disable_core(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "Failed to disable core %d\n", ret); + return ret; + } + skl->cores.state[core_id] = SKL_DSP_RESET; + return 0; +} + +static const struct skl_dsp_fw_ops bxt_fw_ops = { + .set_state_D0 = bxt_set_dsp_D0, + .set_state_D3 = bxt_set_dsp_D3, + .set_state_D0i3 = bxt_schedule_dsp_D0i3, + .set_state_D0i0 = bxt_set_dsp_D0i0, + .load_fw = bxt_load_base_firmware, + .get_fw_errcode = bxt_get_errorcode, + .load_library = bxt_load_library, +}; + +static struct sst_ops skl_ops = { + .irq_handler = skl_dsp_sst_interrupt, + .write = sst_shim32_write, + .read = sst_shim32_read, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .free = skl_dsp_free, +}; + +static struct sst_dsp_device skl_dev = { + .thread = skl_dsp_irq_thread_handler, + .ops = &skl_ops, +}; + +int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp) +{ + struct skl_sst *skl; + struct sst_dsp *sst; + int ret; + + ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev); + if (ret < 0) { + dev_err(dev, "%s: no device\n", __func__); + return ret; + } + + skl = *dsp; + sst = skl->dsp; + sst->fw_ops = bxt_fw_ops; + sst->addr.lpe = mmio_base; + sst->addr.shim = mmio_base; + sst->addr.sram0_base = BXT_ADSP_SRAM0_BASE; + sst->addr.sram1_base = BXT_ADSP_SRAM1_BASE; + sst->addr.w0_stat_sz = SKL_ADSP_W0_STAT_SZ; + sst->addr.w0_up_sz = SKL_ADSP_W0_UP_SZ; + + sst_dsp_mailbox_init(sst, (BXT_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ), + SKL_ADSP_W0_UP_SZ, BXT_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ); + + ret = skl_ipc_init(dev, skl); + if (ret) { + skl_dsp_free(sst); + return ret; + } + + /* set the D0i3 check */ + skl->ipc.ops.check_dsp_lp_on = skl_ipc_check_D0i0; + + skl->boot_complete = false; + init_waitqueue_head(&skl->boot_wait); + INIT_DELAYED_WORK(&skl->d0i3.work, bxt_set_dsp_D0i3); + skl->d0i3.state = SKL_DSP_D0I3_NONE; + + return skl_dsp_acquire_irq(sst); +} +EXPORT_SYMBOL_GPL(bxt_sst_dsp_init); + +int bxt_sst_init_fw(struct device *dev, struct skl_sst *ctx) +{ + int ret; + struct sst_dsp *sst = ctx->dsp; + + ret = sst->fw_ops.load_fw(sst); + if (ret < 0) { + dev_err(dev, "Load base fw failed: %x\n", ret); + return ret; + } + + skl_dsp_init_core_state(sst); + + if (ctx->lib_count > 1) { + ret = sst->fw_ops.load_library(sst, ctx->lib_info, + ctx->lib_count); + if (ret < 0) { + dev_err(dev, "Load Library failed : %x\n", ret); + return ret; + } + } + ctx->is_first_boot = false; + + return 0; +} +EXPORT_SYMBOL_GPL(bxt_sst_init_fw); + +void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) +{ + + skl_release_library(ctx->lib_info, ctx->lib_count); + if (ctx->dsp->fw) + release_firmware(ctx->dsp->fw); + skl_freeup_uuid_list(ctx); + skl_ipc_free(&ctx->ipc); + ctx->dsp->ops->free(ctx->dsp); +} +EXPORT_SYMBOL_GPL(bxt_sst_dsp_cleanup); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Broxton IPC driver"); diff --git a/sound/soc/intel/skylake/cnl-sst-dsp.c b/sound/soc/intel/skylake/cnl-sst-dsp.c new file mode 100644 index 000000000..2f8326707 --- /dev/null +++ b/sound/soc/intel/skylake/cnl-sst-dsp.c @@ -0,0 +1,274 @@ +/* + * cnl-sst-dsp.c - CNL SST library generic function + * + * Copyright (C) 2016-17, Intel Corporation. + * Author: Guneshwor Singh + * + * Modified from: + * SKL SST library generic function + * Copyright (C) 2014-15, Intel Corporation. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include +#include "../common/sst-dsp.h" +#include "../common/sst-ipc.h" +#include "../common/sst-dsp-priv.h" +#include "cnl-sst-dsp.h" + +/* various timeout values */ +#define CNL_DSP_PU_TO 50 +#define CNL_DSP_PD_TO 50 +#define CNL_DSP_RESET_TO 50 + +static int +cnl_dsp_core_set_reset_state(struct sst_dsp *ctx, unsigned int core_mask) +{ + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, + CNL_ADSP_REG_ADSPCS, CNL_ADSPCS_CRST(core_mask), + CNL_ADSPCS_CRST(core_mask)); + + /* poll with timeout to check if operation successful */ + return sst_dsp_register_poll(ctx, + CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_CRST(core_mask), + CNL_ADSPCS_CRST(core_mask), + CNL_DSP_RESET_TO, + "Set reset"); +} + +static int +cnl_dsp_core_unset_reset_state(struct sst_dsp *ctx, unsigned int core_mask) +{ + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_CRST(core_mask), 0); + + /* poll with timeout to check if operation successful */ + return sst_dsp_register_poll(ctx, + CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_CRST(core_mask), + 0, + CNL_DSP_RESET_TO, + "Unset reset"); +} + +static bool is_cnl_dsp_core_enable(struct sst_dsp *ctx, unsigned int core_mask) +{ + int val; + bool is_enable; + + val = sst_dsp_shim_read_unlocked(ctx, CNL_ADSP_REG_ADSPCS); + + is_enable = (val & CNL_ADSPCS_CPA(core_mask)) && + (val & CNL_ADSPCS_SPA(core_mask)) && + !(val & CNL_ADSPCS_CRST(core_mask)) && + !(val & CNL_ADSPCS_CSTALL(core_mask)); + + dev_dbg(ctx->dev, "DSP core(s) enabled? %d: core_mask %#x\n", + is_enable, core_mask); + + return is_enable; +} + +static int cnl_dsp_reset_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + /* stall core */ + sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_CSTALL(core_mask), + CNL_ADSPCS_CSTALL(core_mask)); + + /* set reset state */ + return cnl_dsp_core_set_reset_state(ctx, core_mask); +} + +static int cnl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + /* unset reset state */ + ret = cnl_dsp_core_unset_reset_state(ctx, core_mask); + if (ret < 0) + return ret; + + /* run core */ + sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_CSTALL(core_mask), 0); + + if (!is_cnl_dsp_core_enable(ctx, core_mask)) { + cnl_dsp_reset_core(ctx, core_mask); + dev_err(ctx->dev, "DSP core mask %#x enable failed\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +static int cnl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask) +{ + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_SPA(core_mask), + CNL_ADSPCS_SPA(core_mask)); + + /* poll with timeout to check if operation successful */ + return sst_dsp_register_poll(ctx, CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_CPA(core_mask), + CNL_ADSPCS_CPA(core_mask), + CNL_DSP_PU_TO, + "Power up"); +} + +static int cnl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask) +{ + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_SPA(core_mask), 0); + + /* poll with timeout to check if operation successful */ + return sst_dsp_register_poll(ctx, + CNL_ADSP_REG_ADSPCS, + CNL_ADSPCS_CPA(core_mask), + 0, + CNL_DSP_PD_TO, + "Power down"); +} + +int cnl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + /* power up */ + ret = cnl_dsp_core_power_up(ctx, core_mask); + if (ret < 0) { + dev_dbg(ctx->dev, "DSP core mask %#x power up failed", + core_mask); + return ret; + } + + return cnl_dsp_start_core(ctx, core_mask); +} + +int cnl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + ret = cnl_dsp_reset_core(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "DSP core mask %#x reset failed\n", + core_mask); + return ret; + } + + /* power down core*/ + ret = cnl_dsp_core_power_down(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "DSP core mask %#x power down failed\n", + core_mask); + return ret; + } + + if (is_cnl_dsp_core_enable(ctx, core_mask)) { + dev_err(ctx->dev, "DSP core mask %#x disable failed\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +irqreturn_t cnl_dsp_sst_interrupt(int irq, void *dev_id) +{ + struct sst_dsp *ctx = dev_id; + u32 val; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&ctx->spinlock); + + val = sst_dsp_shim_read_unlocked(ctx, CNL_ADSP_REG_ADSPIS); + ctx->intr_status = val; + + if (val == 0xffffffff) { + spin_unlock(&ctx->spinlock); + return IRQ_NONE; + } + + if (val & CNL_ADSPIS_IPC) { + cnl_ipc_int_disable(ctx); + ret = IRQ_WAKE_THREAD; + } + + spin_unlock(&ctx->spinlock); + + return ret; +} + +void cnl_dsp_free(struct sst_dsp *dsp) +{ + cnl_ipc_int_disable(dsp); + + free_irq(dsp->irq, dsp); + cnl_ipc_op_int_disable(dsp); + cnl_dsp_disable_core(dsp, SKL_DSP_CORE0_MASK); +} +EXPORT_SYMBOL_GPL(cnl_dsp_free); + +void cnl_ipc_int_enable(struct sst_dsp *ctx) +{ + sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_ADSPIC, + CNL_ADSPIC_IPC, CNL_ADSPIC_IPC); +} + +void cnl_ipc_int_disable(struct sst_dsp *ctx) +{ + sst_dsp_shim_update_bits_unlocked(ctx, CNL_ADSP_REG_ADSPIC, + CNL_ADSPIC_IPC, 0); +} + +void cnl_ipc_op_int_enable(struct sst_dsp *ctx) +{ + /* enable IPC DONE interrupt */ + sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL, + CNL_ADSP_REG_HIPCCTL_DONE, + CNL_ADSP_REG_HIPCCTL_DONE); + + /* enable IPC BUSY interrupt */ + sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL, + CNL_ADSP_REG_HIPCCTL_BUSY, + CNL_ADSP_REG_HIPCCTL_BUSY); +} + +void cnl_ipc_op_int_disable(struct sst_dsp *ctx) +{ + /* disable IPC DONE interrupt */ + sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL, + CNL_ADSP_REG_HIPCCTL_DONE, 0); + + /* disable IPC BUSY interrupt */ + sst_dsp_shim_update_bits(ctx, CNL_ADSP_REG_HIPCCTL, + CNL_ADSP_REG_HIPCCTL_BUSY, 0); +} + +bool cnl_ipc_int_status(struct sst_dsp *ctx) +{ + return sst_dsp_shim_read_unlocked(ctx, CNL_ADSP_REG_ADSPIS) & + CNL_ADSPIS_IPC; +} + +void cnl_ipc_free(struct sst_generic_ipc *ipc) +{ + cnl_ipc_op_int_disable(ipc->dsp); + sst_ipc_fini(ipc); +} diff --git a/sound/soc/intel/skylake/cnl-sst-dsp.h b/sound/soc/intel/skylake/cnl-sst-dsp.h new file mode 100644 index 000000000..09bd218df --- /dev/null +++ b/sound/soc/intel/skylake/cnl-sst-dsp.h @@ -0,0 +1,112 @@ +/* + * Cannonlake SST DSP Support + * + * Copyright (C) 2016-17, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __CNL_SST_DSP_H__ +#define __CNL_SST_DSP_H__ + +struct sst_dsp; +struct skl_sst; +struct sst_dsp_device; +struct sst_generic_ipc; + +/* Intel HD Audio General DSP Registers */ +#define CNL_ADSP_GEN_BASE 0x0 +#define CNL_ADSP_REG_ADSPCS (CNL_ADSP_GEN_BASE + 0x04) +#define CNL_ADSP_REG_ADSPIC (CNL_ADSP_GEN_BASE + 0x08) +#define CNL_ADSP_REG_ADSPIS (CNL_ADSP_GEN_BASE + 0x0c) + +/* Intel HD Audio Inter-Processor Communication Registers */ +#define CNL_ADSP_IPC_BASE 0xc0 +#define CNL_ADSP_REG_HIPCTDR (CNL_ADSP_IPC_BASE + 0x00) +#define CNL_ADSP_REG_HIPCTDA (CNL_ADSP_IPC_BASE + 0x04) +#define CNL_ADSP_REG_HIPCTDD (CNL_ADSP_IPC_BASE + 0x08) +#define CNL_ADSP_REG_HIPCIDR (CNL_ADSP_IPC_BASE + 0x10) +#define CNL_ADSP_REG_HIPCIDA (CNL_ADSP_IPC_BASE + 0x14) +#define CNL_ADSP_REG_HIPCIDD (CNL_ADSP_IPC_BASE + 0x18) +#define CNL_ADSP_REG_HIPCCTL (CNL_ADSP_IPC_BASE + 0x28) + +/* HIPCTDR */ +#define CNL_ADSP_REG_HIPCTDR_BUSY BIT(31) + +/* HIPCTDA */ +#define CNL_ADSP_REG_HIPCTDA_DONE BIT(31) + +/* HIPCIDR */ +#define CNL_ADSP_REG_HIPCIDR_BUSY BIT(31) + +/* HIPCIDA */ +#define CNL_ADSP_REG_HIPCIDA_DONE BIT(31) + +/* CNL HIPCCTL */ +#define CNL_ADSP_REG_HIPCCTL_DONE BIT(1) +#define CNL_ADSP_REG_HIPCCTL_BUSY BIT(0) + +/* CNL HIPCT */ +#define CNL_ADSP_REG_HIPCT_BUSY BIT(31) + +/* Intel HD Audio SRAM Window 1 */ +#define CNL_ADSP_SRAM1_BASE 0xa0000 + +#define CNL_ADSP_MMIO_LEN 0x10000 + +#define CNL_ADSP_W0_STAT_SZ 0x1000 + +#define CNL_ADSP_W0_UP_SZ 0x1000 + +#define CNL_ADSP_W1_SZ 0x1000 + +#define CNL_FW_STS_MASK 0xf + +#define CNL_ADSPIC_IPC 0x1 +#define CNL_ADSPIS_IPC 0x1 + +#define CNL_DSP_CORES 4 +#define CNL_DSP_CORES_MASK ((1 << CNL_DSP_CORES) - 1) + +/* core reset - asserted high */ +#define CNL_ADSPCS_CRST_SHIFT 0 +#define CNL_ADSPCS_CRST(x) (x << CNL_ADSPCS_CRST_SHIFT) + +/* core run/stall - when set to 1 core is stalled */ +#define CNL_ADSPCS_CSTALL_SHIFT 8 +#define CNL_ADSPCS_CSTALL(x) (x << CNL_ADSPCS_CSTALL_SHIFT) + +/* set power active - when set to 1 turn core on */ +#define CNL_ADSPCS_SPA_SHIFT 16 +#define CNL_ADSPCS_SPA(x) (x << CNL_ADSPCS_SPA_SHIFT) + +/* current power active - power status of cores, set by hardware */ +#define CNL_ADSPCS_CPA_SHIFT 24 +#define CNL_ADSPCS_CPA(x) (x << CNL_ADSPCS_CPA_SHIFT) + +int cnl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core); +int cnl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core); +irqreturn_t cnl_dsp_sst_interrupt(int irq, void *dev_id); +void cnl_dsp_free(struct sst_dsp *dsp); + +void cnl_ipc_int_enable(struct sst_dsp *ctx); +void cnl_ipc_int_disable(struct sst_dsp *ctx); +void cnl_ipc_op_int_enable(struct sst_dsp *ctx); +void cnl_ipc_op_int_disable(struct sst_dsp *ctx); +bool cnl_ipc_int_status(struct sst_dsp *ctx); +void cnl_ipc_free(struct sst_generic_ipc *ipc); + +int cnl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp); +int cnl_sst_init_fw(struct device *dev, struct skl_sst *ctx); +void cnl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); + +#endif /*__CNL_SST_DSP_H__*/ diff --git a/sound/soc/intel/skylake/cnl-sst.c b/sound/soc/intel/skylake/cnl-sst.c new file mode 100644 index 000000000..7b429c0fb --- /dev/null +++ b/sound/soc/intel/skylake/cnl-sst.c @@ -0,0 +1,498 @@ +/* + * cnl-sst.c - DSP library functions for CNL platform + * + * Copyright (C) 2016-17, Intel Corporation. + * + * Author: Guneshwor Singh + * + * Modified from: + * HDA DSP library functions for SKL platform + * Copyright (C) 2014-15, Intel Corporation. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-ipc.h" +#include "cnl-sst-dsp.h" +#include "skl-sst-dsp.h" +#include "skl-sst-ipc.h" + +#define CNL_FW_ROM_INIT 0x1 +#define CNL_FW_INIT 0x5 +#define CNL_IPC_PURGE 0x01004000 +#define CNL_INIT_TIMEOUT 300 +#define CNL_BASEFW_TIMEOUT 3000 + +#define CNL_ADSP_SRAM0_BASE 0x80000 + +/* Firmware status window */ +#define CNL_ADSP_FW_STATUS CNL_ADSP_SRAM0_BASE +#define CNL_ADSP_ERROR_CODE (CNL_ADSP_FW_STATUS + 0x4) + +#define CNL_INSTANCE_ID 0 +#define CNL_BASE_FW_MODULE_ID 0 +#define CNL_ADSP_FW_HDR_OFFSET 0x2000 +#define CNL_ROM_CTRL_DMA_ID 0x9 + +static int cnl_prepare_fw(struct sst_dsp *ctx, const void *fwdata, u32 fwsize) +{ + + int ret, stream_tag; + + stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, fwsize, &ctx->dmab); + if (stream_tag <= 0) { + dev_err(ctx->dev, "dma prepare failed: 0%#x\n", stream_tag); + return stream_tag; + } + + ctx->dsp_ops.stream_tag = stream_tag; + memcpy(ctx->dmab.area, fwdata, fwsize); + + /* purge FW request */ + sst_dsp_shim_write(ctx, CNL_ADSP_REG_HIPCIDR, + CNL_ADSP_REG_HIPCIDR_BUSY | (CNL_IPC_PURGE | + ((stream_tag - 1) << CNL_ROM_CTRL_DMA_ID))); + + ret = cnl_dsp_enable_core(ctx, SKL_DSP_CORE0_MASK); + if (ret < 0) { + dev_err(ctx->dev, "dsp boot core failed ret: %d\n", ret); + ret = -EIO; + goto base_fw_load_failed; + } + + /* enable interrupt */ + cnl_ipc_int_enable(ctx); + cnl_ipc_op_int_enable(ctx); + + ret = sst_dsp_register_poll(ctx, CNL_ADSP_FW_STATUS, CNL_FW_STS_MASK, + CNL_FW_ROM_INIT, CNL_INIT_TIMEOUT, + "rom load"); + if (ret < 0) { + dev_err(ctx->dev, "rom init timeout, ret: %d\n", ret); + goto base_fw_load_failed; + } + + return 0; + +base_fw_load_failed: + ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, stream_tag); + cnl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + + return ret; +} + +static int sst_transfer_fw_host_dma(struct sst_dsp *ctx) +{ + int ret; + + ctx->dsp_ops.trigger(ctx->dev, true, ctx->dsp_ops.stream_tag); + ret = sst_dsp_register_poll(ctx, CNL_ADSP_FW_STATUS, CNL_FW_STS_MASK, + CNL_FW_INIT, CNL_BASEFW_TIMEOUT, + "firmware boot"); + + ctx->dsp_ops.trigger(ctx->dev, false, ctx->dsp_ops.stream_tag); + ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, ctx->dsp_ops.stream_tag); + + return ret; +} + +static int cnl_load_base_firmware(struct sst_dsp *ctx) +{ + struct firmware stripped_fw; + struct skl_sst *cnl = ctx->thread_context; + int ret; + + if (!ctx->fw) { + ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); + if (ret < 0) { + dev_err(ctx->dev, "request firmware failed: %d\n", ret); + goto cnl_load_base_firmware_failed; + } + } + + /* parse uuids if first boot */ + if (cnl->is_first_boot) { + ret = snd_skl_parse_uuids(ctx, ctx->fw, + CNL_ADSP_FW_HDR_OFFSET, 0); + if (ret < 0) + goto cnl_load_base_firmware_failed; + } + + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + skl_dsp_strip_extended_manifest(&stripped_fw); + + ret = cnl_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); + if (ret < 0) { + dev_err(ctx->dev, "prepare firmware failed: %d\n", ret); + goto cnl_load_base_firmware_failed; + } + + ret = sst_transfer_fw_host_dma(ctx); + if (ret < 0) { + dev_err(ctx->dev, "transfer firmware failed: %d\n", ret); + cnl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + goto cnl_load_base_firmware_failed; + } + + ret = wait_event_timeout(cnl->boot_wait, cnl->boot_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + if (ret == 0) { + dev_err(ctx->dev, "FW ready timed-out\n"); + cnl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + ret = -EIO; + goto cnl_load_base_firmware_failed; + } + + cnl->fw_loaded = true; + + return 0; + +cnl_load_base_firmware_failed: + release_firmware(ctx->fw); + ctx->fw = NULL; + + return ret; +} + +static int cnl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *cnl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); + struct skl_ipc_dxstate_info dx; + int ret; + + if (!cnl->fw_loaded) { + cnl->boot_complete = false; + ret = cnl_load_base_firmware(ctx); + if (ret < 0) { + dev_err(ctx->dev, "fw reload failed: %d\n", ret); + return ret; + } + + cnl->cores.state[core_id] = SKL_DSP_RUNNING; + return ret; + } + + ret = cnl_dsp_enable_core(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "enable dsp core %d failed: %d\n", + core_id, ret); + goto err; + } + + if (core_id == SKL_DSP_CORE0_ID) { + /* enable interrupt */ + cnl_ipc_int_enable(ctx); + cnl_ipc_op_int_enable(ctx); + cnl->boot_complete = false; + + ret = wait_event_timeout(cnl->boot_wait, cnl->boot_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + if (ret == 0) { + dev_err(ctx->dev, + "dsp boot timeout, status=%#x error=%#x\n", + sst_dsp_shim_read(ctx, CNL_ADSP_FW_STATUS), + sst_dsp_shim_read(ctx, CNL_ADSP_ERROR_CODE)); + ret = -ETIMEDOUT; + goto err; + } + } else { + dx.core_mask = core_mask; + dx.dx_mask = core_mask; + + ret = skl_ipc_set_dx(&cnl->ipc, CNL_INSTANCE_ID, + CNL_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, "set_dx failed, core: %d ret: %d\n", + core_id, ret); + goto err; + } + } + cnl->cores.state[core_id] = SKL_DSP_RUNNING; + + return 0; +err: + cnl_dsp_disable_core(ctx, core_mask); + + return ret; +} + +static int cnl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *cnl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); + struct skl_ipc_dxstate_info dx; + int ret; + + dx.core_mask = core_mask; + dx.dx_mask = SKL_IPC_D3_MASK; + + ret = skl_ipc_set_dx(&cnl->ipc, CNL_INSTANCE_ID, + CNL_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, + "dsp core %d to d3 failed; continue reset\n", + core_id); + cnl->fw_loaded = false; + } + + /* disable interrupts if core 0 */ + if (core_id == SKL_DSP_CORE0_ID) { + skl_ipc_op_int_disable(ctx); + skl_ipc_int_disable(ctx); + } + + ret = cnl_dsp_disable_core(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "disable dsp core %d failed: %d\n", + core_id, ret); + return ret; + } + + cnl->cores.state[core_id] = SKL_DSP_RESET; + + return ret; +} + +static unsigned int cnl_get_errno(struct sst_dsp *ctx) +{ + return sst_dsp_shim_read(ctx, CNL_ADSP_ERROR_CODE); +} + +static const struct skl_dsp_fw_ops cnl_fw_ops = { + .set_state_D0 = cnl_set_dsp_D0, + .set_state_D3 = cnl_set_dsp_D3, + .load_fw = cnl_load_base_firmware, + .get_fw_errcode = cnl_get_errno, +}; + +static struct sst_ops cnl_ops = { + .irq_handler = cnl_dsp_sst_interrupt, + .write = sst_shim32_write, + .read = sst_shim32_read, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .free = cnl_dsp_free, +}; + +#define CNL_IPC_GLB_NOTIFY_RSP_SHIFT 29 +#define CNL_IPC_GLB_NOTIFY_RSP_MASK 0x1 +#define CNL_IPC_GLB_NOTIFY_RSP_TYPE(x) (((x) >> CNL_IPC_GLB_NOTIFY_RSP_SHIFT) \ + & CNL_IPC_GLB_NOTIFY_RSP_MASK) + +static irqreturn_t cnl_dsp_irq_thread_handler(int irq, void *context) +{ + struct sst_dsp *dsp = context; + struct skl_sst *cnl = sst_dsp_get_thread_context(dsp); + struct sst_generic_ipc *ipc = &cnl->ipc; + struct skl_ipc_header header = {0}; + u32 hipcida, hipctdr, hipctdd; + int ipc_irq = 0; + + /* here we handle ipc interrupts only */ + if (!(dsp->intr_status & CNL_ADSPIS_IPC)) + return IRQ_NONE; + + hipcida = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCIDA); + hipctdr = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCTDR); + + /* reply message from dsp */ + if (hipcida & CNL_ADSP_REG_HIPCIDA_DONE) { + sst_dsp_shim_update_bits(dsp, CNL_ADSP_REG_HIPCCTL, + CNL_ADSP_REG_HIPCCTL_DONE, 0); + + /* clear done bit - tell dsp operation is complete */ + sst_dsp_shim_update_bits_forced(dsp, CNL_ADSP_REG_HIPCIDA, + CNL_ADSP_REG_HIPCIDA_DONE, CNL_ADSP_REG_HIPCIDA_DONE); + + ipc_irq = 1; + + /* unmask done interrupt */ + sst_dsp_shim_update_bits(dsp, CNL_ADSP_REG_HIPCCTL, + CNL_ADSP_REG_HIPCCTL_DONE, CNL_ADSP_REG_HIPCCTL_DONE); + } + + /* new message from dsp */ + if (hipctdr & CNL_ADSP_REG_HIPCTDR_BUSY) { + hipctdd = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCTDD); + header.primary = hipctdr; + header.extension = hipctdd; + dev_dbg(dsp->dev, "IPC irq: Firmware respond primary:%x", + header.primary); + dev_dbg(dsp->dev, "IPC irq: Firmware respond extension:%x", + header.extension); + + if (CNL_IPC_GLB_NOTIFY_RSP_TYPE(header.primary)) { + /* Handle Immediate reply from DSP Core */ + skl_ipc_process_reply(ipc, header); + } else { + dev_dbg(dsp->dev, "IPC irq: Notification from firmware\n"); + skl_ipc_process_notification(ipc, header); + } + /* clear busy interrupt */ + sst_dsp_shim_update_bits_forced(dsp, CNL_ADSP_REG_HIPCTDR, + CNL_ADSP_REG_HIPCTDR_BUSY, CNL_ADSP_REG_HIPCTDR_BUSY); + + /* set done bit to ack dsp */ + sst_dsp_shim_update_bits_forced(dsp, CNL_ADSP_REG_HIPCTDA, + CNL_ADSP_REG_HIPCTDA_DONE, CNL_ADSP_REG_HIPCTDA_DONE); + ipc_irq = 1; + } + + if (ipc_irq == 0) + return IRQ_NONE; + + cnl_ipc_int_enable(dsp); + + /* continue to send any remaining messages */ + schedule_work(&ipc->kwork); + + return IRQ_HANDLED; +} + +static struct sst_dsp_device cnl_dev = { + .thread = cnl_dsp_irq_thread_handler, + .ops = &cnl_ops, +}; + +static void cnl_ipc_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + struct skl_ipc_header *header = (struct skl_ipc_header *)(&msg->header); + + if (msg->tx_size) + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + sst_dsp_shim_write_unlocked(ipc->dsp, CNL_ADSP_REG_HIPCIDD, + header->extension); + sst_dsp_shim_write_unlocked(ipc->dsp, CNL_ADSP_REG_HIPCIDR, + header->primary | CNL_ADSP_REG_HIPCIDR_BUSY); +} + +static bool cnl_ipc_is_dsp_busy(struct sst_dsp *dsp) +{ + u32 hipcidr; + + hipcidr = sst_dsp_shim_read_unlocked(dsp, CNL_ADSP_REG_HIPCIDR); + + return (hipcidr & CNL_ADSP_REG_HIPCIDR_BUSY); +} + +static int cnl_ipc_init(struct device *dev, struct skl_sst *cnl) +{ + struct sst_generic_ipc *ipc; + int err; + + ipc = &cnl->ipc; + ipc->dsp = cnl->dsp; + ipc->dev = dev; + + ipc->tx_data_max_size = CNL_ADSP_W1_SZ; + ipc->rx_data_max_size = CNL_ADSP_W0_UP_SZ; + + err = sst_ipc_init(ipc); + if (err) + return err; + + /* + * overriding tx_msg and is_dsp_busy since + * ipc registers are different for cnl + */ + ipc->ops.tx_msg = cnl_ipc_tx_msg; + ipc->ops.tx_data_copy = skl_ipc_tx_data_copy; + ipc->ops.is_dsp_busy = cnl_ipc_is_dsp_busy; + + return 0; +} + +int cnl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp) +{ + struct skl_sst *cnl; + struct sst_dsp *sst; + int ret; + + ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &cnl_dev); + if (ret < 0) { + dev_err(dev, "%s: no device\n", __func__); + return ret; + } + + cnl = *dsp; + sst = cnl->dsp; + sst->fw_ops = cnl_fw_ops; + sst->addr.lpe = mmio_base; + sst->addr.shim = mmio_base; + sst->addr.sram0_base = CNL_ADSP_SRAM0_BASE; + sst->addr.sram1_base = CNL_ADSP_SRAM1_BASE; + sst->addr.w0_stat_sz = CNL_ADSP_W0_STAT_SZ; + sst->addr.w0_up_sz = CNL_ADSP_W0_UP_SZ; + + sst_dsp_mailbox_init(sst, (CNL_ADSP_SRAM0_BASE + CNL_ADSP_W0_STAT_SZ), + CNL_ADSP_W0_UP_SZ, CNL_ADSP_SRAM1_BASE, + CNL_ADSP_W1_SZ); + + ret = cnl_ipc_init(dev, cnl); + if (ret) { + skl_dsp_free(sst); + return ret; + } + + cnl->boot_complete = false; + init_waitqueue_head(&cnl->boot_wait); + + return skl_dsp_acquire_irq(sst); +} +EXPORT_SYMBOL_GPL(cnl_sst_dsp_init); + +int cnl_sst_init_fw(struct device *dev, struct skl_sst *ctx) +{ + int ret; + struct sst_dsp *sst = ctx->dsp; + + ret = ctx->dsp->fw_ops.load_fw(sst); + if (ret < 0) { + dev_err(dev, "load base fw failed: %d", ret); + return ret; + } + + skl_dsp_init_core_state(sst); + + ctx->is_first_boot = false; + + return 0; +} +EXPORT_SYMBOL_GPL(cnl_sst_init_fw); + +void cnl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) +{ + if (ctx->dsp->fw) + release_firmware(ctx->dsp->fw); + + skl_freeup_uuid_list(ctx); + cnl_ipc_free(&ctx->ipc); + + ctx->dsp->ops->free(ctx->dsp); +} +EXPORT_SYMBOL_GPL(cnl_sst_dsp_cleanup); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Cannonlake IPC driver"); diff --git a/sound/soc/intel/skylake/skl-debug.c b/sound/soc/intel/skylake/skl-debug.c new file mode 100644 index 000000000..a0714c0e6 --- /dev/null +++ b/sound/soc/intel/skylake/skl-debug.c @@ -0,0 +1,263 @@ +/* + * skl-debug.c - Debugfs for skl driver + * + * Copyright (C) 2016-17 Intel Corp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#include +#include +#include +#include "skl.h" +#include "skl-sst-dsp.h" +#include "skl-sst-ipc.h" +#include "skl-topology.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" + +#define MOD_BUF PAGE_SIZE +#define FW_REG_BUF PAGE_SIZE +#define FW_REG_SIZE 0x60 + +struct skl_debug { + struct skl *skl; + struct device *dev; + + struct dentry *fs; + struct dentry *modules; + u8 fw_read_buff[FW_REG_BUF]; +}; + +static ssize_t skl_print_pins(struct skl_module_pin *m_pin, char *buf, + int max_pin, ssize_t size, bool direction) +{ + int i; + ssize_t ret = 0; + + for (i = 0; i < max_pin; i++) { + ret += scnprintf(buf + size, MOD_BUF - size, + "%s %d\n\tModule %d\n\tInstance %d\n\t" + "In-used %s\n\tType %s\n" + "\tState %d\n\tIndex %d\n", + direction ? "Input Pin:" : "Output Pin:", + i, m_pin[i].id.module_id, + m_pin[i].id.instance_id, + m_pin[i].in_use ? "Used" : "Unused", + m_pin[i].is_dynamic ? "Dynamic" : "Static", + m_pin[i].pin_state, i); + size += ret; + } + return ret; +} + +static ssize_t skl_print_fmt(struct skl_module_fmt *fmt, char *buf, + ssize_t size, bool direction) +{ + return scnprintf(buf + size, MOD_BUF - size, + "%s\n\tCh %d\n\tFreq %d\n\tBit depth %d\n\t" + "Valid bit depth %d\n\tCh config %#x\n\tInterleaving %d\n\t" + "Sample Type %d\n\tCh Map %#x\n", + direction ? "Input Format:" : "Output Format:", + fmt->channels, fmt->s_freq, fmt->bit_depth, + fmt->valid_bit_depth, fmt->ch_cfg, + fmt->interleaving_style, fmt->sample_type, + fmt->ch_map); +} + +static ssize_t module_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct skl_module_cfg *mconfig = file->private_data; + char *buf; + ssize_t ret; + + buf = kzalloc(MOD_BUF, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, MOD_BUF, "Module:\n\tUUID %pUL\n\tModule id %d\n" + "\tInstance id %d\n\tPvt_id %d\n", mconfig->guid, + mconfig->id.module_id, mconfig->id.instance_id, + mconfig->id.pvt_id); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "Resources:\n\tMCPS %#x\n\tIBS %#x\n\tOBS %#x\t\n", + mconfig->mcps, mconfig->ibs, mconfig->obs); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "Module data:\n\tCore %d\n\tIn queue %d\n\t" + "Out queue %d\n\tType %s\n", + mconfig->core_id, mconfig->max_in_queue, + mconfig->max_out_queue, + mconfig->is_loadable ? "loadable" : "inbuilt"); + + ret += skl_print_fmt(mconfig->in_fmt, buf, ret, true); + ret += skl_print_fmt(mconfig->out_fmt, buf, ret, false); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "Fixup:\n\tParams %#x\n\tConverter %#x\n", + mconfig->params_fixup, mconfig->converter); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "Module Gateway:\n\tType %#x\n\tVbus %#x\n\tHW conn %#x\n\tSlot %#x\n", + mconfig->dev_type, mconfig->vbus_id, + mconfig->hw_conn_type, mconfig->time_slot); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "Pipeline:\n\tID %d\n\tPriority %d\n\tConn Type %d\n\t" + "Pages %#x\n", mconfig->pipe->ppl_id, + mconfig->pipe->pipe_priority, mconfig->pipe->conn_type, + mconfig->pipe->memory_pages); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "\tParams:\n\t\tHost DMA %d\n\t\tLink DMA %d\n", + mconfig->pipe->p_params->host_dma_id, + mconfig->pipe->p_params->link_dma_id); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "\tPCM params:\n\t\tCh %d\n\t\tFreq %d\n\t\tFormat %d\n", + mconfig->pipe->p_params->ch, + mconfig->pipe->p_params->s_freq, + mconfig->pipe->p_params->s_fmt); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "\tLink %#x\n\tStream %#x\n", + mconfig->pipe->p_params->linktype, + mconfig->pipe->p_params->stream); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "\tState %d\n\tPassthru %s\n", + mconfig->pipe->state, + mconfig->pipe->passthru ? "true" : "false"); + + ret += skl_print_pins(mconfig->m_in_pin, buf, + mconfig->max_in_queue, ret, true); + ret += skl_print_pins(mconfig->m_out_pin, buf, + mconfig->max_out_queue, ret, false); + + ret += scnprintf(buf + ret, MOD_BUF - ret, + "Other:\n\tDomain %d\n\tHomogeneous Input %s\n\t" + "Homogeneous Output %s\n\tIn Queue Mask %d\n\t" + "Out Queue Mask %d\n\tDMA ID %d\n\tMem Pages %d\n\t" + "Module Type %d\n\tModule State %d\n", + mconfig->domain, + mconfig->homogenous_inputs ? "true" : "false", + mconfig->homogenous_outputs ? "true" : "false", + mconfig->in_queue_mask, mconfig->out_queue_mask, + mconfig->dma_id, mconfig->mem_pages, mconfig->m_state, + mconfig->m_type); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + return ret; +} + +static const struct file_operations mcfg_fops = { + .open = simple_open, + .read = module_read, + .llseek = default_llseek, +}; + + +void skl_debug_init_module(struct skl_debug *d, + struct snd_soc_dapm_widget *w, + struct skl_module_cfg *mconfig) +{ + if (!debugfs_create_file(w->name, 0444, + d->modules, mconfig, + &mcfg_fops)) + dev_err(d->dev, "%s: module debugfs init failed\n", w->name); +} + +static ssize_t fw_softreg_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct skl_debug *d = file->private_data; + struct sst_dsp *sst = d->skl->skl_sst->dsp; + size_t w0_stat_sz = sst->addr.w0_stat_sz; + void __iomem *in_base = sst->mailbox.in_base; + void __iomem *fw_reg_addr; + unsigned int offset; + char *tmp; + ssize_t ret = 0; + + tmp = kzalloc(FW_REG_BUF, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + fw_reg_addr = in_base - w0_stat_sz; + memset(d->fw_read_buff, 0, FW_REG_BUF); + + if (w0_stat_sz > 0) + __ioread32_copy(d->fw_read_buff, fw_reg_addr, w0_stat_sz >> 2); + + for (offset = 0; offset < FW_REG_SIZE; offset += 16) { + ret += scnprintf(tmp + ret, FW_REG_BUF - ret, "%#.4x: ", offset); + hex_dump_to_buffer(d->fw_read_buff + offset, 16, 16, 4, + tmp + ret, FW_REG_BUF - ret, 0); + ret += strlen(tmp + ret); + + /* print newline for each offset */ + if (FW_REG_BUF - ret > 0) + tmp[ret++] = '\n'; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, tmp, ret); + kfree(tmp); + + return ret; +} + +static const struct file_operations soft_regs_ctrl_fops = { + .open = simple_open, + .read = fw_softreg_read, + .llseek = default_llseek, +}; + +struct skl_debug *skl_debugfs_init(struct skl *skl) +{ + struct skl_debug *d; + + d = devm_kzalloc(&skl->pci->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return NULL; + + /* create the debugfs dir with platform component's debugfs as parent */ + d->fs = debugfs_create_dir("dsp", + skl->component->debugfs_root); + if (IS_ERR(d->fs) || !d->fs) { + dev_err(&skl->pci->dev, "debugfs root creation failed\n"); + return NULL; + } + + d->skl = skl; + d->dev = &skl->pci->dev; + + /* now create the module dir */ + d->modules = debugfs_create_dir("modules", d->fs); + if (IS_ERR(d->modules) || !d->modules) { + dev_err(&skl->pci->dev, "modules debugfs create failed\n"); + goto err; + } + + if (!debugfs_create_file("fw_soft_regs_rd", 0444, d->fs, d, + &soft_regs_ctrl_fops)) { + dev_err(d->dev, "fw soft regs control debugfs init failed\n"); + goto err; + } + + return d; + +err: + debugfs_remove_recursive(d->fs); + return NULL; +} diff --git a/sound/soc/intel/skylake/skl-i2s.h b/sound/soc/intel/skylake/skl-i2s.h new file mode 100644 index 000000000..ad0a1bbca --- /dev/null +++ b/sound/soc/intel/skylake/skl-i2s.h @@ -0,0 +1,95 @@ +/* + * skl-i2s.h - i2s blob mapping + * + * Copyright (C) 2017 Intel Corp + * Author: Subhransu S. Prusty < subhransu.s.prusty@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef __SOUND_SOC_SKL_I2S_H +#define __SOUND_SOC_SKL_I2S_H + +#define SKL_I2S_MAX_TIME_SLOTS 8 +#define SKL_MCLK_DIV_CLK_SRC_MASK GENMASK(17, 16) + +#define SKL_MNDSS_DIV_CLK_SRC_MASK GENMASK(21, 20) +#define SKL_SHIFT(x) (ffs(x) - 1) +#define SKL_MCLK_DIV_RATIO_MASK GENMASK(11, 0) + +#define is_legacy_blob(x) (x.signature != 0xEE) +#define ext_to_legacy_blob(i2s_config_blob_ext) \ + ((struct skl_i2s_config_blob_legacy *) i2s_config_blob_ext) + +#define get_clk_src(mclk, mask) \ + ((mclk.mdivctrl & mask) >> SKL_SHIFT(mask)) +struct skl_i2s_config { + u32 ssc0; + u32 ssc1; + u32 sscto; + u32 sspsp; + u32 sstsa; + u32 ssrsa; + u32 ssc2; + u32 sspsp2; + u32 ssc3; + u32 ssioc; +} __packed; + +struct skl_i2s_config_mclk { + u32 mdivctrl; + u32 mdivr; +}; + +struct skl_i2s_config_mclk_ext { + u32 mdivctrl; + u32 mdivr_count; + u32 mdivr[0]; +} __packed; + +struct skl_i2s_config_blob_signature { + u32 minor_ver : 8; + u32 major_ver : 8; + u32 resvdz : 8; + u32 signature : 8; +} __packed; + +struct skl_i2s_config_blob_header { + struct skl_i2s_config_blob_signature sig; + u32 size; +}; + +/** + * struct skl_i2s_config_blob_legacy - Structure defines I2S Gateway + * configuration legacy blob + * + * @gtw_attr: Gateway attribute for the I2S Gateway + * @tdm_ts_group: TDM slot mapping against channels in the Gateway. + * @i2s_cfg: I2S HW registers + * @mclk: MCLK clock source and divider values + */ +struct skl_i2s_config_blob_legacy { + u32 gtw_attr; + u32 tdm_ts_group[SKL_I2S_MAX_TIME_SLOTS]; + struct skl_i2s_config i2s_cfg; + struct skl_i2s_config_mclk mclk; +}; + +struct skl_i2s_config_blob_ext { + u32 gtw_attr; + struct skl_i2s_config_blob_header hdr; + u32 tdm_ts_group[SKL_I2S_MAX_TIME_SLOTS]; + struct skl_i2s_config i2s_cfg; + struct skl_i2s_config_mclk_ext mclk; +} __packed; +#endif /* __SOUND_SOC_SKL_I2S_H */ diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c new file mode 100644 index 000000000..8bfb8b0fa --- /dev/null +++ b/sound/soc/intel/skylake/skl-messages.c @@ -0,0 +1,1388 @@ +/* + * skl-message.c - HDA DSP interface for FW registration, Pipe and Module + * configurations + * + * Copyright (C) 2015 Intel Corp + * Author:Rafal Redzimski + * Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "skl-sst-dsp.h" +#include "cnl-sst-dsp.h" +#include "skl-sst-ipc.h" +#include "skl.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "skl-topology.h" + +static int skl_alloc_dma_buf(struct device *dev, + struct snd_dma_buffer *dmab, size_t size) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + + if (!bus) + return -ENODEV; + + return bus->io_ops->dma_alloc_pages(bus, SNDRV_DMA_TYPE_DEV, size, dmab); +} + +static int skl_free_dma_buf(struct device *dev, struct snd_dma_buffer *dmab) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + + if (!bus) + return -ENODEV; + + bus->io_ops->dma_free_pages(bus, dmab); + + return 0; +} + +#define SKL_ASTATE_PARAM_ID 4 + +void skl_dsp_set_astate_cfg(struct skl_sst *ctx, u32 cnt, void *data) +{ + struct skl_ipc_large_config_msg msg = {0}; + + msg.large_param_id = SKL_ASTATE_PARAM_ID; + msg.param_data_size = (cnt * sizeof(struct skl_astate_param) + + sizeof(cnt)); + + skl_ipc_set_large_config(&ctx->ipc, &msg, data); +} + +#define NOTIFICATION_PARAM_ID 3 +#define NOTIFICATION_MASK 0xf + +/* disable notfication for underruns/overruns from firmware module */ +void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable) +{ + struct notification_mask mask; + struct skl_ipc_large_config_msg msg = {0}; + + mask.notify = NOTIFICATION_MASK; + mask.enable = enable; + + msg.large_param_id = NOTIFICATION_PARAM_ID; + msg.param_data_size = sizeof(mask); + + skl_ipc_set_large_config(&ctx->ipc, &msg, (u32 *)&mask); +} + +static int skl_dsp_setup_spib(struct device *dev, unsigned int size, + int stream_tag, int enable) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + struct hdac_stream *stream = snd_hdac_get_stream(bus, + SNDRV_PCM_STREAM_PLAYBACK, stream_tag); + struct hdac_ext_stream *estream; + + if (!stream) + return -EINVAL; + + estream = stream_to_hdac_ext_stream(stream); + /* enable/disable SPIB for this hdac stream */ + snd_hdac_ext_stream_spbcap_enable(bus, enable, stream->index); + + /* set the spib value */ + snd_hdac_ext_stream_set_spib(bus, estream, size); + + return 0; +} + +static int skl_dsp_prepare(struct device *dev, unsigned int format, + unsigned int size, struct snd_dma_buffer *dmab) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + struct hdac_ext_stream *estream; + struct hdac_stream *stream; + struct snd_pcm_substream substream; + int ret; + + if (!bus) + return -ENODEV; + + memset(&substream, 0, sizeof(substream)); + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + + estream = snd_hdac_ext_stream_assign(bus, &substream, + HDAC_EXT_STREAM_TYPE_HOST); + if (!estream) + return -ENODEV; + + stream = hdac_stream(estream); + + /* assign decouple host dma channel */ + ret = snd_hdac_dsp_prepare(stream, format, size, dmab); + if (ret < 0) + return ret; + + skl_dsp_setup_spib(dev, size, stream->stream_tag, true); + + return stream->stream_tag; +} + +static int skl_dsp_trigger(struct device *dev, bool start, int stream_tag) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + struct hdac_stream *stream; + + if (!bus) + return -ENODEV; + + stream = snd_hdac_get_stream(bus, + SNDRV_PCM_STREAM_PLAYBACK, stream_tag); + if (!stream) + return -EINVAL; + + snd_hdac_dsp_trigger(stream, start); + + return 0; +} + +static int skl_dsp_cleanup(struct device *dev, + struct snd_dma_buffer *dmab, int stream_tag) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + struct hdac_stream *stream; + struct hdac_ext_stream *estream; + + if (!bus) + return -ENODEV; + + stream = snd_hdac_get_stream(bus, + SNDRV_PCM_STREAM_PLAYBACK, stream_tag); + if (!stream) + return -EINVAL; + + estream = stream_to_hdac_ext_stream(stream); + skl_dsp_setup_spib(dev, 0, stream_tag, false); + snd_hdac_ext_stream_release(estream, HDAC_EXT_STREAM_TYPE_HOST); + + snd_hdac_dsp_cleanup(stream, dmab); + + return 0; +} + +static struct skl_dsp_loader_ops skl_get_loader_ops(void) +{ + struct skl_dsp_loader_ops loader_ops; + + memset(&loader_ops, 0, sizeof(struct skl_dsp_loader_ops)); + + loader_ops.alloc_dma_buf = skl_alloc_dma_buf; + loader_ops.free_dma_buf = skl_free_dma_buf; + + return loader_ops; +}; + +static struct skl_dsp_loader_ops bxt_get_loader_ops(void) +{ + struct skl_dsp_loader_ops loader_ops; + + memset(&loader_ops, 0, sizeof(loader_ops)); + + loader_ops.alloc_dma_buf = skl_alloc_dma_buf; + loader_ops.free_dma_buf = skl_free_dma_buf; + loader_ops.prepare = skl_dsp_prepare; + loader_ops.trigger = skl_dsp_trigger; + loader_ops.cleanup = skl_dsp_cleanup; + + return loader_ops; +}; + +static const struct skl_dsp_ops dsp_ops[] = { + { + .id = 0x9d70, + .num_cores = 2, + .loader_ops = skl_get_loader_ops, + .init = skl_sst_dsp_init, + .init_fw = skl_sst_init_fw, + .cleanup = skl_sst_dsp_cleanup + }, + { + .id = 0x9d71, + .num_cores = 2, + .loader_ops = skl_get_loader_ops, + .init = skl_sst_dsp_init, + .init_fw = skl_sst_init_fw, + .cleanup = skl_sst_dsp_cleanup + }, + { + .id = 0x5a98, + .num_cores = 2, + .loader_ops = bxt_get_loader_ops, + .init = bxt_sst_dsp_init, + .init_fw = bxt_sst_init_fw, + .cleanup = bxt_sst_dsp_cleanup + }, + { + .id = 0x3198, + .num_cores = 2, + .loader_ops = bxt_get_loader_ops, + .init = bxt_sst_dsp_init, + .init_fw = bxt_sst_init_fw, + .cleanup = bxt_sst_dsp_cleanup + }, + { + .id = 0x9dc8, + .num_cores = 4, + .loader_ops = bxt_get_loader_ops, + .init = cnl_sst_dsp_init, + .init_fw = cnl_sst_init_fw, + .cleanup = cnl_sst_dsp_cleanup + }, +}; + +const struct skl_dsp_ops *skl_get_dsp_ops(int pci_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dsp_ops); i++) { + if (dsp_ops[i].id == pci_id) + return &dsp_ops[i]; + } + + return NULL; +} + +int skl_init_dsp(struct skl *skl) +{ + void __iomem *mmio_base; + struct hdac_bus *bus = skl_to_bus(skl); + struct skl_dsp_loader_ops loader_ops; + int irq = bus->irq; + const struct skl_dsp_ops *ops; + struct skl_dsp_cores *cores; + int ret; + + /* enable ppcap interrupt */ + snd_hdac_ext_bus_ppcap_enable(bus, true); + snd_hdac_ext_bus_ppcap_int_enable(bus, true); + + /* read the BAR of the ADSP MMIO */ + mmio_base = pci_ioremap_bar(skl->pci, 4); + if (mmio_base == NULL) { + dev_err(bus->dev, "ioremap error\n"); + return -ENXIO; + } + + ops = skl_get_dsp_ops(skl->pci->device); + if (!ops) { + ret = -EIO; + goto unmap_mmio; + } + + loader_ops = ops->loader_ops(); + ret = ops->init(bus->dev, mmio_base, irq, + skl->fw_name, loader_ops, + &skl->skl_sst); + + if (ret < 0) + goto unmap_mmio; + + skl->skl_sst->dsp_ops = ops; + cores = &skl->skl_sst->cores; + cores->count = ops->num_cores; + + cores->state = kcalloc(cores->count, sizeof(*cores->state), GFP_KERNEL); + if (!cores->state) { + ret = -ENOMEM; + goto unmap_mmio; + } + + cores->usage_count = kcalloc(cores->count, sizeof(*cores->usage_count), + GFP_KERNEL); + if (!cores->usage_count) { + ret = -ENOMEM; + goto free_core_state; + } + + dev_dbg(bus->dev, "dsp registration status=%d\n", ret); + + return 0; + +free_core_state: + kfree(cores->state); + +unmap_mmio: + iounmap(mmio_base); + + return ret; +} + +int skl_free_dsp(struct skl *skl) +{ + struct hdac_bus *bus = skl_to_bus(skl); + struct skl_sst *ctx = skl->skl_sst; + + /* disable ppcap interrupt */ + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + + ctx->dsp_ops->cleanup(bus->dev, ctx); + + kfree(ctx->cores.state); + kfree(ctx->cores.usage_count); + + if (ctx->dsp->addr.lpe) + iounmap(ctx->dsp->addr.lpe); + + return 0; +} + +/* + * In the case of "suspend_active" i.e, the Audio IP being active + * during system suspend, immediately excecute any pending D0i3 work + * before suspending. This is needed for the IP to work in low power + * mode during system suspend. In the case of normal suspend, cancel + * any pending D0i3 work. + */ +int skl_suspend_late_dsp(struct skl *skl) +{ + struct skl_sst *ctx = skl->skl_sst; + struct delayed_work *dwork; + + if (!ctx) + return 0; + + dwork = &ctx->d0i3.work; + + if (dwork->work.func) { + if (skl->supend_active) + flush_delayed_work(dwork); + else + cancel_delayed_work_sync(dwork); + } + + return 0; +} + +int skl_suspend_dsp(struct skl *skl) +{ + struct skl_sst *ctx = skl->skl_sst; + struct hdac_bus *bus = skl_to_bus(skl); + int ret; + + /* if ppcap is not supported return 0 */ + if (!bus->ppcap) + return 0; + + ret = skl_dsp_sleep(ctx->dsp); + if (ret < 0) + return ret; + + /* disable ppcap interrupt */ + snd_hdac_ext_bus_ppcap_int_enable(bus, false); + snd_hdac_ext_bus_ppcap_enable(bus, false); + + return 0; +} + +int skl_resume_dsp(struct skl *skl) +{ + struct skl_sst *ctx = skl->skl_sst; + struct hdac_bus *bus = skl_to_bus(skl); + int ret; + + /* if ppcap is not supported return 0 */ + if (!bus->ppcap) + return 0; + + /* enable ppcap interrupt */ + snd_hdac_ext_bus_ppcap_enable(bus, true); + snd_hdac_ext_bus_ppcap_int_enable(bus, true); + + /* check if DSP 1st boot is done */ + if (skl->skl_sst->is_first_boot == true) + return 0; + + /* + * Disable dynamic clock and power gating during firmware + * and library download + */ + ctx->enable_miscbdcge(ctx->dev, false); + ctx->clock_power_gating(ctx->dev, false); + + ret = skl_dsp_wake(ctx->dsp); + ctx->enable_miscbdcge(ctx->dev, true); + ctx->clock_power_gating(ctx->dev, true); + if (ret < 0) + return ret; + + skl_dsp_enable_notification(skl->skl_sst, false); + + if (skl->cfg.astate_cfg != NULL) { + skl_dsp_set_astate_cfg(skl->skl_sst, skl->cfg.astate_cfg->count, + skl->cfg.astate_cfg); + } + return ret; +} + +enum skl_bitdepth skl_get_bit_depth(int params) +{ + switch (params) { + case 8: + return SKL_DEPTH_8BIT; + + case 16: + return SKL_DEPTH_16BIT; + + case 24: + return SKL_DEPTH_24BIT; + + case 32: + return SKL_DEPTH_32BIT; + + default: + return SKL_DEPTH_INVALID; + + } +} + +/* + * Each module in DSP expects a base module configuration, which consists of + * PCM format information, which we calculate in driver and resource values + * which are read from widget information passed through topology binary + * This is send when we create a module with INIT_INSTANCE IPC msg + */ +static void skl_set_base_module_format(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_base_cfg *base_cfg) +{ + struct skl_module *module = mconfig->module; + struct skl_module_res *res = &module->resources[mconfig->res_idx]; + struct skl_module_iface *fmt = &module->formats[mconfig->fmt_idx]; + struct skl_module_fmt *format = &fmt->inputs[0].fmt; + + base_cfg->audio_fmt.number_of_channels = format->channels; + + base_cfg->audio_fmt.s_freq = format->s_freq; + base_cfg->audio_fmt.bit_depth = format->bit_depth; + base_cfg->audio_fmt.valid_bit_depth = format->valid_bit_depth; + base_cfg->audio_fmt.ch_cfg = format->ch_cfg; + + dev_dbg(ctx->dev, "bit_depth=%x valid_bd=%x ch_config=%x\n", + format->bit_depth, format->valid_bit_depth, + format->ch_cfg); + + base_cfg->audio_fmt.channel_map = format->ch_map; + + base_cfg->audio_fmt.interleaving = format->interleaving_style; + + base_cfg->cps = res->cps; + base_cfg->ibs = res->ibs; + base_cfg->obs = res->obs; + base_cfg->is_pages = res->is_pages; +} + +/* + * Copies copier capabilities into copier module and updates copier module + * config size. + */ +static void skl_copy_copier_caps(struct skl_module_cfg *mconfig, + struct skl_cpr_cfg *cpr_mconfig) +{ + if (mconfig->formats_config.caps_size == 0) + return; + + memcpy(cpr_mconfig->gtw_cfg.config_data, + mconfig->formats_config.caps, + mconfig->formats_config.caps_size); + + cpr_mconfig->gtw_cfg.config_length = + (mconfig->formats_config.caps_size) / 4; +} + +#define SKL_NON_GATEWAY_CPR_NODE_ID 0xFFFFFFFF +/* + * Calculate the gatewat settings required for copier module, type of + * gateway and index of gateway to use + */ +static u32 skl_get_node_id(struct skl_sst *ctx, + struct skl_module_cfg *mconfig) +{ + union skl_connector_node_id node_id = {0}; + union skl_ssp_dma_node ssp_node = {0}; + struct skl_pipe_params *params = mconfig->pipe->p_params; + + switch (mconfig->dev_type) { + case SKL_DEVICE_BT: + node_id.node.dma_type = + (SKL_CONN_SOURCE == mconfig->hw_conn_type) ? + SKL_DMA_I2S_LINK_OUTPUT_CLASS : + SKL_DMA_I2S_LINK_INPUT_CLASS; + node_id.node.vindex = params->host_dma_id + + (mconfig->vbus_id << 3); + break; + + case SKL_DEVICE_I2S: + node_id.node.dma_type = + (SKL_CONN_SOURCE == mconfig->hw_conn_type) ? + SKL_DMA_I2S_LINK_OUTPUT_CLASS : + SKL_DMA_I2S_LINK_INPUT_CLASS; + ssp_node.dma_node.time_slot_index = mconfig->time_slot; + ssp_node.dma_node.i2s_instance = mconfig->vbus_id; + node_id.node.vindex = ssp_node.val; + break; + + case SKL_DEVICE_DMIC: + node_id.node.dma_type = SKL_DMA_DMIC_LINK_INPUT_CLASS; + node_id.node.vindex = mconfig->vbus_id + + (mconfig->time_slot); + break; + + case SKL_DEVICE_HDALINK: + node_id.node.dma_type = + (SKL_CONN_SOURCE == mconfig->hw_conn_type) ? + SKL_DMA_HDA_LINK_OUTPUT_CLASS : + SKL_DMA_HDA_LINK_INPUT_CLASS; + node_id.node.vindex = params->link_dma_id; + break; + + case SKL_DEVICE_HDAHOST: + node_id.node.dma_type = + (SKL_CONN_SOURCE == mconfig->hw_conn_type) ? + SKL_DMA_HDA_HOST_OUTPUT_CLASS : + SKL_DMA_HDA_HOST_INPUT_CLASS; + node_id.node.vindex = params->host_dma_id; + break; + + default: + node_id.val = 0xFFFFFFFF; + break; + } + + return node_id.val; +} + +static void skl_setup_cpr_gateway_cfg(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_cpr_cfg *cpr_mconfig) +{ + u32 dma_io_buf; + struct skl_module_res *res; + int res_idx = mconfig->res_idx; + struct skl *skl = get_skl_ctx(ctx->dev); + + cpr_mconfig->gtw_cfg.node_id = skl_get_node_id(ctx, mconfig); + + if (cpr_mconfig->gtw_cfg.node_id == SKL_NON_GATEWAY_CPR_NODE_ID) { + cpr_mconfig->cpr_feature_mask = 0; + return; + } + + if (skl->nr_modules) { + res = &mconfig->module->resources[mconfig->res_idx]; + cpr_mconfig->gtw_cfg.dma_buffer_size = res->dma_buffer_size; + goto skip_buf_size_calc; + } else { + res = &mconfig->module->resources[res_idx]; + } + + switch (mconfig->hw_conn_type) { + case SKL_CONN_SOURCE: + if (mconfig->dev_type == SKL_DEVICE_HDAHOST) + dma_io_buf = res->ibs; + else + dma_io_buf = res->obs; + break; + + case SKL_CONN_SINK: + if (mconfig->dev_type == SKL_DEVICE_HDAHOST) + dma_io_buf = res->obs; + else + dma_io_buf = res->ibs; + break; + + default: + dev_warn(ctx->dev, "wrong connection type: %d\n", + mconfig->hw_conn_type); + return; + } + + cpr_mconfig->gtw_cfg.dma_buffer_size = + mconfig->dma_buffer_size * dma_io_buf; + + /* fallback to 2ms default value */ + if (!cpr_mconfig->gtw_cfg.dma_buffer_size) { + if (mconfig->hw_conn_type == SKL_CONN_SOURCE) + cpr_mconfig->gtw_cfg.dma_buffer_size = 2 * res->obs; + else + cpr_mconfig->gtw_cfg.dma_buffer_size = 2 * res->ibs; + } + +skip_buf_size_calc: + cpr_mconfig->cpr_feature_mask = 0; + cpr_mconfig->gtw_cfg.config_length = 0; + + skl_copy_copier_caps(mconfig, cpr_mconfig); +} + +#define DMA_CONTROL_ID 5 +#define DMA_I2S_BLOB_SIZE 21 + +int skl_dsp_set_dma_control(struct skl_sst *ctx, u32 *caps, + u32 caps_size, u32 node_id) +{ + struct skl_dma_control *dma_ctrl; + struct skl_ipc_large_config_msg msg = {0}; + int err = 0; + + + /* + * if blob size zero, then return + */ + if (caps_size == 0) + return 0; + + msg.large_param_id = DMA_CONTROL_ID; + msg.param_data_size = sizeof(struct skl_dma_control) + caps_size; + + dma_ctrl = kzalloc(msg.param_data_size, GFP_KERNEL); + if (dma_ctrl == NULL) + return -ENOMEM; + + dma_ctrl->node_id = node_id; + + /* + * NHLT blob may contain additional configs along with i2s blob. + * firmware expects only the i2s blob size as the config_length. + * So fix to i2s blob size. + * size in dwords. + */ + dma_ctrl->config_length = DMA_I2S_BLOB_SIZE; + + memcpy(dma_ctrl->config_data, caps, caps_size); + + err = skl_ipc_set_large_config(&ctx->ipc, &msg, (u32 *)dma_ctrl); + + kfree(dma_ctrl); + return err; +} +EXPORT_SYMBOL_GPL(skl_dsp_set_dma_control); + +static void skl_setup_out_format(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_audio_data_format *out_fmt) +{ + struct skl_module *module = mconfig->module; + struct skl_module_iface *fmt = &module->formats[mconfig->fmt_idx]; + struct skl_module_fmt *format = &fmt->outputs[0].fmt; + + out_fmt->number_of_channels = (u8)format->channels; + out_fmt->s_freq = format->s_freq; + out_fmt->bit_depth = format->bit_depth; + out_fmt->valid_bit_depth = format->valid_bit_depth; + out_fmt->ch_cfg = format->ch_cfg; + + out_fmt->channel_map = format->ch_map; + out_fmt->interleaving = format->interleaving_style; + out_fmt->sample_type = format->sample_type; + + dev_dbg(ctx->dev, "copier out format chan=%d fre=%d bitdepth=%d\n", + out_fmt->number_of_channels, format->s_freq, format->bit_depth); +} + +/* + * DSP needs SRC module for frequency conversion, SRC takes base module + * configuration and the target frequency as extra parameter passed as src + * config + */ +static void skl_set_src_format(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_src_module_cfg *src_mconfig) +{ + struct skl_module *module = mconfig->module; + struct skl_module_iface *iface = &module->formats[mconfig->fmt_idx]; + struct skl_module_fmt *fmt = &iface->outputs[0].fmt; + + skl_set_base_module_format(ctx, mconfig, + (struct skl_base_cfg *)src_mconfig); + + src_mconfig->src_cfg = fmt->s_freq; +} + +/* + * DSP needs updown module to do channel conversion. updown module take base + * module configuration and channel configuration + * It also take coefficients and now we have defaults applied here + */ +static void skl_set_updown_mixer_format(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_up_down_mixer_cfg *mixer_mconfig) +{ + struct skl_module *module = mconfig->module; + struct skl_module_iface *iface = &module->formats[mconfig->fmt_idx]; + struct skl_module_fmt *fmt = &iface->outputs[0].fmt; + + skl_set_base_module_format(ctx, mconfig, + (struct skl_base_cfg *)mixer_mconfig); + mixer_mconfig->out_ch_cfg = fmt->ch_cfg; + mixer_mconfig->ch_map = fmt->ch_map; +} + +/* + * 'copier' is DSP internal module which copies data from Host DMA (HDA host + * dma) or link (hda link, SSP, PDM) + * Here we calculate the copier module parameters, like PCM format, output + * format, gateway settings + * copier_module_config is sent as input buffer with INIT_INSTANCE IPC msg + */ +static void skl_set_copier_format(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_cpr_cfg *cpr_mconfig) +{ + struct skl_audio_data_format *out_fmt = &cpr_mconfig->out_fmt; + struct skl_base_cfg *base_cfg = (struct skl_base_cfg *)cpr_mconfig; + + skl_set_base_module_format(ctx, mconfig, base_cfg); + + skl_setup_out_format(ctx, mconfig, out_fmt); + skl_setup_cpr_gateway_cfg(ctx, mconfig, cpr_mconfig); +} + +/* + * Algo module are DSP pre processing modules. Algo module take base module + * configuration and params + */ + +static void skl_set_algo_format(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_algo_cfg *algo_mcfg) +{ + struct skl_base_cfg *base_cfg = (struct skl_base_cfg *)algo_mcfg; + + skl_set_base_module_format(ctx, mconfig, base_cfg); + + if (mconfig->formats_config.caps_size == 0) + return; + + memcpy(algo_mcfg->params, + mconfig->formats_config.caps, + mconfig->formats_config.caps_size); + +} + +/* + * Mic select module allows selecting one or many input channels, thus + * acting as a demux. + * + * Mic select module take base module configuration and out-format + * configuration + */ +static void skl_set_base_outfmt_format(struct skl_sst *ctx, + struct skl_module_cfg *mconfig, + struct skl_base_outfmt_cfg *base_outfmt_mcfg) +{ + struct skl_audio_data_format *out_fmt = &base_outfmt_mcfg->out_fmt; + struct skl_base_cfg *base_cfg = + (struct skl_base_cfg *)base_outfmt_mcfg; + + skl_set_base_module_format(ctx, mconfig, base_cfg); + skl_setup_out_format(ctx, mconfig, out_fmt); +} + +static u16 skl_get_module_param_size(struct skl_sst *ctx, + struct skl_module_cfg *mconfig) +{ + u16 param_size; + + switch (mconfig->m_type) { + case SKL_MODULE_TYPE_COPIER: + param_size = sizeof(struct skl_cpr_cfg); + param_size += mconfig->formats_config.caps_size; + return param_size; + + case SKL_MODULE_TYPE_SRCINT: + return sizeof(struct skl_src_module_cfg); + + case SKL_MODULE_TYPE_UPDWMIX: + return sizeof(struct skl_up_down_mixer_cfg); + + case SKL_MODULE_TYPE_ALGO: + param_size = sizeof(struct skl_base_cfg); + param_size += mconfig->formats_config.caps_size; + return param_size; + + case SKL_MODULE_TYPE_BASE_OUTFMT: + case SKL_MODULE_TYPE_MIC_SELECT: + case SKL_MODULE_TYPE_KPB: + return sizeof(struct skl_base_outfmt_cfg); + + default: + /* + * return only base cfg when no specific module type is + * specified + */ + return sizeof(struct skl_base_cfg); + } + + return 0; +} + +/* + * DSP firmware supports various modules like copier, SRC, updown etc. + * These modules required various parameters to be calculated and sent for + * the module initialization to DSP. By default a generic module needs only + * base module format configuration + */ + +static int skl_set_module_format(struct skl_sst *ctx, + struct skl_module_cfg *module_config, + u16 *module_config_size, + void **param_data) +{ + u16 param_size; + + param_size = skl_get_module_param_size(ctx, module_config); + + *param_data = kzalloc(param_size, GFP_KERNEL); + if (NULL == *param_data) + return -ENOMEM; + + *module_config_size = param_size; + + switch (module_config->m_type) { + case SKL_MODULE_TYPE_COPIER: + skl_set_copier_format(ctx, module_config, *param_data); + break; + + case SKL_MODULE_TYPE_SRCINT: + skl_set_src_format(ctx, module_config, *param_data); + break; + + case SKL_MODULE_TYPE_UPDWMIX: + skl_set_updown_mixer_format(ctx, module_config, *param_data); + break; + + case SKL_MODULE_TYPE_ALGO: + skl_set_algo_format(ctx, module_config, *param_data); + break; + + case SKL_MODULE_TYPE_BASE_OUTFMT: + case SKL_MODULE_TYPE_MIC_SELECT: + case SKL_MODULE_TYPE_KPB: + skl_set_base_outfmt_format(ctx, module_config, *param_data); + break; + + default: + skl_set_base_module_format(ctx, module_config, *param_data); + break; + + } + + dev_dbg(ctx->dev, "Module type=%d config size: %d bytes\n", + module_config->id.module_id, param_size); + print_hex_dump_debug("Module params:", DUMP_PREFIX_OFFSET, 8, 4, + *param_data, param_size, false); + return 0; +} + +static int skl_get_queue_index(struct skl_module_pin *mpin, + struct skl_module_inst_id id, int max) +{ + int i; + + for (i = 0; i < max; i++) { + if (mpin[i].id.module_id == id.module_id && + mpin[i].id.instance_id == id.instance_id) + return i; + } + + return -EINVAL; +} + +/* + * Allocates queue for each module. + * if dynamic, the pin_index is allocated 0 to max_pin. + * In static, the pin_index is fixed based on module_id and instance id + */ +static int skl_alloc_queue(struct skl_module_pin *mpin, + struct skl_module_cfg *tgt_cfg, int max) +{ + int i; + struct skl_module_inst_id id = tgt_cfg->id; + /* + * if pin in dynamic, find first free pin + * otherwise find match module and instance id pin as topology will + * ensure a unique pin is assigned to this so no need to + * allocate/free + */ + for (i = 0; i < max; i++) { + if (mpin[i].is_dynamic) { + if (!mpin[i].in_use && + mpin[i].pin_state == SKL_PIN_UNBIND) { + + mpin[i].in_use = true; + mpin[i].id.module_id = id.module_id; + mpin[i].id.instance_id = id.instance_id; + mpin[i].id.pvt_id = id.pvt_id; + mpin[i].tgt_mcfg = tgt_cfg; + return i; + } + } else { + if (mpin[i].id.module_id == id.module_id && + mpin[i].id.instance_id == id.instance_id && + mpin[i].pin_state == SKL_PIN_UNBIND) { + + mpin[i].tgt_mcfg = tgt_cfg; + return i; + } + } + } + + return -EINVAL; +} + +static void skl_free_queue(struct skl_module_pin *mpin, int q_index) +{ + if (mpin[q_index].is_dynamic) { + mpin[q_index].in_use = false; + mpin[q_index].id.module_id = 0; + mpin[q_index].id.instance_id = 0; + mpin[q_index].id.pvt_id = 0; + } + mpin[q_index].pin_state = SKL_PIN_UNBIND; + mpin[q_index].tgt_mcfg = NULL; +} + +/* Module state will be set to unint, if all the out pin state is UNBIND */ + +static void skl_clear_module_state(struct skl_module_pin *mpin, int max, + struct skl_module_cfg *mcfg) +{ + int i; + bool found = false; + + for (i = 0; i < max; i++) { + if (mpin[i].pin_state == SKL_PIN_UNBIND) + continue; + found = true; + break; + } + + if (!found) + mcfg->m_state = SKL_MODULE_INIT_DONE; + return; +} + +/* + * A module needs to be instanataited in DSP. A mdoule is present in a + * collection of module referred as a PIPE. + * We first calculate the module format, based on module type and then + * invoke the DSP by sending IPC INIT_INSTANCE using ipc helper + */ +int skl_init_module(struct skl_sst *ctx, + struct skl_module_cfg *mconfig) +{ + u16 module_config_size = 0; + void *param_data = NULL; + int ret; + struct skl_ipc_init_instance_msg msg; + + dev_dbg(ctx->dev, "%s: module_id = %d instance=%d\n", __func__, + mconfig->id.module_id, mconfig->id.pvt_id); + + if (mconfig->pipe->state != SKL_PIPE_CREATED) { + dev_err(ctx->dev, "Pipe not created state= %d pipe_id= %d\n", + mconfig->pipe->state, mconfig->pipe->ppl_id); + return -EIO; + } + + ret = skl_set_module_format(ctx, mconfig, + &module_config_size, ¶m_data); + if (ret < 0) { + dev_err(ctx->dev, "Failed to set module format ret=%d\n", ret); + return ret; + } + + msg.module_id = mconfig->id.module_id; + msg.instance_id = mconfig->id.pvt_id; + msg.ppl_instance_id = mconfig->pipe->ppl_id; + msg.param_data_size = module_config_size; + msg.core_id = mconfig->core_id; + msg.domain = mconfig->domain; + + ret = skl_ipc_init_instance(&ctx->ipc, &msg, param_data); + if (ret < 0) { + dev_err(ctx->dev, "Failed to init instance ret=%d\n", ret); + kfree(param_data); + return ret; + } + mconfig->m_state = SKL_MODULE_INIT_DONE; + kfree(param_data); + return ret; +} + +static void skl_dump_bind_info(struct skl_sst *ctx, struct skl_module_cfg + *src_module, struct skl_module_cfg *dst_module) +{ + dev_dbg(ctx->dev, "%s: src module_id = %d src_instance=%d\n", + __func__, src_module->id.module_id, src_module->id.pvt_id); + dev_dbg(ctx->dev, "%s: dst_module=%d dst_instance=%d\n", __func__, + dst_module->id.module_id, dst_module->id.pvt_id); + + dev_dbg(ctx->dev, "src_module state = %d dst module state = %d\n", + src_module->m_state, dst_module->m_state); +} + +/* + * On module freeup, we need to unbind the module with modules + * it is already bind. + * Find the pin allocated and unbind then using bind_unbind IPC + */ +int skl_unbind_modules(struct skl_sst *ctx, + struct skl_module_cfg *src_mcfg, + struct skl_module_cfg *dst_mcfg) +{ + int ret; + struct skl_ipc_bind_unbind_msg msg; + struct skl_module_inst_id src_id = src_mcfg->id; + struct skl_module_inst_id dst_id = dst_mcfg->id; + int in_max = dst_mcfg->module->max_input_pins; + int out_max = src_mcfg->module->max_output_pins; + int src_index, dst_index, src_pin_state, dst_pin_state; + + skl_dump_bind_info(ctx, src_mcfg, dst_mcfg); + + /* get src queue index */ + src_index = skl_get_queue_index(src_mcfg->m_out_pin, dst_id, out_max); + if (src_index < 0) + return 0; + + msg.src_queue = src_index; + + /* get dst queue index */ + dst_index = skl_get_queue_index(dst_mcfg->m_in_pin, src_id, in_max); + if (dst_index < 0) + return 0; + + msg.dst_queue = dst_index; + + src_pin_state = src_mcfg->m_out_pin[src_index].pin_state; + dst_pin_state = dst_mcfg->m_in_pin[dst_index].pin_state; + + if (src_pin_state != SKL_PIN_BIND_DONE || + dst_pin_state != SKL_PIN_BIND_DONE) + return 0; + + msg.module_id = src_mcfg->id.module_id; + msg.instance_id = src_mcfg->id.pvt_id; + msg.dst_module_id = dst_mcfg->id.module_id; + msg.dst_instance_id = dst_mcfg->id.pvt_id; + msg.bind = false; + + ret = skl_ipc_bind_unbind(&ctx->ipc, &msg); + if (!ret) { + /* free queue only if unbind is success */ + skl_free_queue(src_mcfg->m_out_pin, src_index); + skl_free_queue(dst_mcfg->m_in_pin, dst_index); + + /* + * check only if src module bind state, bind is + * always from src -> sink + */ + skl_clear_module_state(src_mcfg->m_out_pin, out_max, src_mcfg); + } + + return ret; +} + +static void fill_pin_params(struct skl_audio_data_format *pin_fmt, + struct skl_module_fmt *format) +{ + pin_fmt->number_of_channels = format->channels; + pin_fmt->s_freq = format->s_freq; + pin_fmt->bit_depth = format->bit_depth; + pin_fmt->valid_bit_depth = format->valid_bit_depth; + pin_fmt->ch_cfg = format->ch_cfg; + pin_fmt->sample_type = format->sample_type; + pin_fmt->channel_map = format->ch_map; + pin_fmt->interleaving = format->interleaving_style; +} + +#define CPR_SINK_FMT_PARAM_ID 2 + +/* + * Once a module is instantiated it need to be 'bind' with other modules in + * the pipeline. For binding we need to find the module pins which are bind + * together + * This function finds the pins and then sends bund_unbind IPC message to + * DSP using IPC helper + */ +int skl_bind_modules(struct skl_sst *ctx, + struct skl_module_cfg *src_mcfg, + struct skl_module_cfg *dst_mcfg) +{ + int ret = 0; + struct skl_ipc_bind_unbind_msg msg; + int in_max = dst_mcfg->module->max_input_pins; + int out_max = src_mcfg->module->max_output_pins; + int src_index, dst_index; + struct skl_module_fmt *format; + struct skl_cpr_pin_fmt pin_fmt; + struct skl_module *module; + struct skl_module_iface *fmt; + + skl_dump_bind_info(ctx, src_mcfg, dst_mcfg); + + if (src_mcfg->m_state < SKL_MODULE_INIT_DONE || + dst_mcfg->m_state < SKL_MODULE_INIT_DONE) + return 0; + + src_index = skl_alloc_queue(src_mcfg->m_out_pin, dst_mcfg, out_max); + if (src_index < 0) + return -EINVAL; + + msg.src_queue = src_index; + dst_index = skl_alloc_queue(dst_mcfg->m_in_pin, src_mcfg, in_max); + if (dst_index < 0) { + skl_free_queue(src_mcfg->m_out_pin, src_index); + return -EINVAL; + } + + /* + * Copier module requires the separate large_config_set_ipc to + * configure the pins other than 0 + */ + if (src_mcfg->m_type == SKL_MODULE_TYPE_COPIER && src_index > 0) { + pin_fmt.sink_id = src_index; + module = src_mcfg->module; + fmt = &module->formats[src_mcfg->fmt_idx]; + + /* Input fmt is same as that of src module input cfg */ + format = &fmt->inputs[0].fmt; + fill_pin_params(&(pin_fmt.src_fmt), format); + + format = &fmt->outputs[src_index].fmt; + fill_pin_params(&(pin_fmt.dst_fmt), format); + ret = skl_set_module_params(ctx, (void *)&pin_fmt, + sizeof(struct skl_cpr_pin_fmt), + CPR_SINK_FMT_PARAM_ID, src_mcfg); + + if (ret < 0) + goto out; + } + + msg.dst_queue = dst_index; + + dev_dbg(ctx->dev, "src queue = %d dst queue =%d\n", + msg.src_queue, msg.dst_queue); + + msg.module_id = src_mcfg->id.module_id; + msg.instance_id = src_mcfg->id.pvt_id; + msg.dst_module_id = dst_mcfg->id.module_id; + msg.dst_instance_id = dst_mcfg->id.pvt_id; + msg.bind = true; + + ret = skl_ipc_bind_unbind(&ctx->ipc, &msg); + + if (!ret) { + src_mcfg->m_state = SKL_MODULE_BIND_DONE; + src_mcfg->m_out_pin[src_index].pin_state = SKL_PIN_BIND_DONE; + dst_mcfg->m_in_pin[dst_index].pin_state = SKL_PIN_BIND_DONE; + return ret; + } +out: + /* error case , if IPC fails, clear the queue index */ + skl_free_queue(src_mcfg->m_out_pin, src_index); + skl_free_queue(dst_mcfg->m_in_pin, dst_index); + + return ret; +} + +static int skl_set_pipe_state(struct skl_sst *ctx, struct skl_pipe *pipe, + enum skl_ipc_pipeline_state state) +{ + dev_dbg(ctx->dev, "%s: pipe_state = %d\n", __func__, state); + + return skl_ipc_set_pipeline_state(&ctx->ipc, pipe->ppl_id, state); +} + +/* + * A pipeline is a collection of modules. Before a module in instantiated a + * pipeline needs to be created for it. + * This function creates pipeline, by sending create pipeline IPC messages + * to FW + */ +int skl_create_pipeline(struct skl_sst *ctx, struct skl_pipe *pipe) +{ + int ret; + + dev_dbg(ctx->dev, "%s: pipe_id = %d\n", __func__, pipe->ppl_id); + + ret = skl_ipc_create_pipeline(&ctx->ipc, pipe->memory_pages, + pipe->pipe_priority, pipe->ppl_id, + pipe->lp_mode); + if (ret < 0) { + dev_err(ctx->dev, "Failed to create pipeline\n"); + return ret; + } + + pipe->state = SKL_PIPE_CREATED; + + return 0; +} + +/* + * A pipeline needs to be deleted on cleanup. If a pipeline is running, then + * pause the pipeline first and then delete it + * The pipe delete is done by sending delete pipeline IPC. DSP will stop the + * DMA engines and releases resources + */ +int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) +{ + int ret; + + dev_dbg(ctx->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id); + + /* If pipe is started, do stop the pipe in FW. */ + if (pipe->state >= SKL_PIPE_STARTED) { + ret = skl_set_pipe_state(ctx, pipe, PPL_PAUSED); + if (ret < 0) { + dev_err(ctx->dev, "Failed to stop pipeline\n"); + return ret; + } + + pipe->state = SKL_PIPE_PAUSED; + } + + /* If pipe was not created in FW, do not try to delete it */ + if (pipe->state < SKL_PIPE_CREATED) + return 0; + + ret = skl_ipc_delete_pipeline(&ctx->ipc, pipe->ppl_id); + if (ret < 0) { + dev_err(ctx->dev, "Failed to delete pipeline\n"); + return ret; + } + + pipe->state = SKL_PIPE_INVALID; + + return ret; +} + +/* + * A pipeline is also a scheduling entity in DSP which can be run, stopped + * For processing data the pipe need to be run by sending IPC set pipe state + * to DSP + */ +int skl_run_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) +{ + int ret; + + dev_dbg(ctx->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id); + + /* If pipe was not created in FW, do not try to pause or delete */ + if (pipe->state < SKL_PIPE_CREATED) + return 0; + + /* Pipe has to be paused before it is started */ + ret = skl_set_pipe_state(ctx, pipe, PPL_PAUSED); + if (ret < 0) { + dev_err(ctx->dev, "Failed to pause pipe\n"); + return ret; + } + + pipe->state = SKL_PIPE_PAUSED; + + ret = skl_set_pipe_state(ctx, pipe, PPL_RUNNING); + if (ret < 0) { + dev_err(ctx->dev, "Failed to start pipe\n"); + return ret; + } + + pipe->state = SKL_PIPE_STARTED; + + return 0; +} + +/* + * Stop the pipeline by sending set pipe state IPC + * DSP doesnt implement stop so we always send pause message + */ +int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) +{ + int ret; + + dev_dbg(ctx->dev, "In %s pipe=%d\n", __func__, pipe->ppl_id); + + /* If pipe was not created in FW, do not try to pause or delete */ + if (pipe->state < SKL_PIPE_PAUSED) + return 0; + + ret = skl_set_pipe_state(ctx, pipe, PPL_PAUSED); + if (ret < 0) { + dev_dbg(ctx->dev, "Failed to stop pipe\n"); + return ret; + } + + pipe->state = SKL_PIPE_PAUSED; + + return 0; +} + +/* + * Reset the pipeline by sending set pipe state IPC this will reset the DMA + * from the DSP side + */ +int skl_reset_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) +{ + int ret; + + /* If pipe was not created in FW, do not try to pause or delete */ + if (pipe->state < SKL_PIPE_PAUSED) + return 0; + + ret = skl_set_pipe_state(ctx, pipe, PPL_RESET); + if (ret < 0) { + dev_dbg(ctx->dev, "Failed to reset pipe ret=%d\n", ret); + return ret; + } + + pipe->state = SKL_PIPE_RESET; + + return 0; +} + +/* Algo parameter set helper function */ +int skl_set_module_params(struct skl_sst *ctx, u32 *params, int size, + u32 param_id, struct skl_module_cfg *mcfg) +{ + struct skl_ipc_large_config_msg msg; + + msg.module_id = mcfg->id.module_id; + msg.instance_id = mcfg->id.pvt_id; + msg.param_data_size = size; + msg.large_param_id = param_id; + + return skl_ipc_set_large_config(&ctx->ipc, &msg, params); +} + +int skl_get_module_params(struct skl_sst *ctx, u32 *params, int size, + u32 param_id, struct skl_module_cfg *mcfg) +{ + struct skl_ipc_large_config_msg msg; + + msg.module_id = mcfg->id.module_id; + msg.instance_id = mcfg->id.pvt_id; + msg.param_data_size = size; + msg.large_param_id = param_id; + + return skl_ipc_get_large_config(&ctx->ipc, &msg, params); +} diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c new file mode 100644 index 000000000..3cef2ebfd --- /dev/null +++ b/sound/soc/intel/skylake/skl-nhlt.c @@ -0,0 +1,451 @@ +/* + * skl-nhlt.c - Intel SKL Platform NHLT parsing + * + * Copyright (C) 2015 Intel Corp + * Author: Sanjiv Kumar + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ +#include +#include "skl.h" +#include "skl-i2s.h" + +#define NHLT_ACPI_HEADER_SIG "NHLT" + +/* Unique identification for getting NHLT blobs */ +static guid_t osc_guid = + GUID_INIT(0xA69F886E, 0x6CEB, 0x4594, + 0xA4, 0x1F, 0x7B, 0x5D, 0xCE, 0x24, 0xC5, 0x53); + + +struct nhlt_acpi_table *skl_nhlt_init(struct device *dev) +{ + acpi_handle handle; + union acpi_object *obj; + struct nhlt_resource_desc *nhlt_ptr = NULL; + struct nhlt_acpi_table *nhlt_table = NULL; + + handle = ACPI_HANDLE(dev); + if (!handle) { + dev_err(dev, "Didn't find ACPI_HANDLE\n"); + return NULL; + } + + obj = acpi_evaluate_dsm(handle, &osc_guid, 1, 1, NULL); + if (obj && obj->type == ACPI_TYPE_BUFFER) { + nhlt_ptr = (struct nhlt_resource_desc *)obj->buffer.pointer; + if (nhlt_ptr->length) + nhlt_table = (struct nhlt_acpi_table *) + memremap(nhlt_ptr->min_addr, nhlt_ptr->length, + MEMREMAP_WB); + ACPI_FREE(obj); + if (nhlt_table && (strncmp(nhlt_table->header.signature, + NHLT_ACPI_HEADER_SIG, + strlen(NHLT_ACPI_HEADER_SIG)) != 0)) { + memunmap(nhlt_table); + dev_err(dev, "NHLT ACPI header signature incorrect\n"); + return NULL; + } + return nhlt_table; + } + + dev_err(dev, "device specific method to extract NHLT blob failed\n"); + return NULL; +} + +void skl_nhlt_free(struct nhlt_acpi_table *nhlt) +{ + memunmap((void *) nhlt); +} + +static struct nhlt_specific_cfg *skl_get_specific_cfg( + struct device *dev, struct nhlt_fmt *fmt, + u8 no_ch, u32 rate, u16 bps, u8 linktype) +{ + struct nhlt_specific_cfg *sp_config; + struct wav_fmt *wfmt; + struct nhlt_fmt_cfg *fmt_config = fmt->fmt_config; + int i; + + dev_dbg(dev, "Format count =%d\n", fmt->fmt_count); + + for (i = 0; i < fmt->fmt_count; i++) { + wfmt = &fmt_config->fmt_ext.fmt; + dev_dbg(dev, "ch=%d fmt=%d s_rate=%d\n", wfmt->channels, + wfmt->bits_per_sample, wfmt->samples_per_sec); + if (wfmt->channels == no_ch && wfmt->bits_per_sample == bps) { + /* + * if link type is dmic ignore rate check as the blob is + * generic for all rates + */ + sp_config = &fmt_config->config; + if (linktype == NHLT_LINK_DMIC) + return sp_config; + + if (wfmt->samples_per_sec == rate) + return sp_config; + } + + fmt_config = (struct nhlt_fmt_cfg *)(fmt_config->config.caps + + fmt_config->config.size); + } + + return NULL; +} + +static void dump_config(struct device *dev, u32 instance_id, u8 linktype, + u8 s_fmt, u8 num_channels, u32 s_rate, u8 dirn, u16 bps) +{ + dev_dbg(dev, "Input configuration\n"); + dev_dbg(dev, "ch=%d fmt=%d s_rate=%d\n", num_channels, s_fmt, s_rate); + dev_dbg(dev, "vbus_id=%d link_type=%d\n", instance_id, linktype); + dev_dbg(dev, "bits_per_sample=%d\n", bps); +} + +static bool skl_check_ep_match(struct device *dev, struct nhlt_endpoint *epnt, + u32 instance_id, u8 link_type, u8 dirn, u8 dev_type) +{ + dev_dbg(dev, "vbus_id=%d link_type=%d dir=%d dev_type = %d\n", + epnt->virtual_bus_id, epnt->linktype, + epnt->direction, epnt->device_type); + + if ((epnt->virtual_bus_id == instance_id) && + (epnt->linktype == link_type) && + (epnt->direction == dirn)) { + /* do not check dev_type for DMIC link type */ + if (epnt->linktype == NHLT_LINK_DMIC) + return true; + + if (epnt->device_type == dev_type) + return true; + } + + return false; +} + +struct nhlt_specific_cfg +*skl_get_ep_blob(struct skl *skl, u32 instance, u8 link_type, + u8 s_fmt, u8 num_ch, u32 s_rate, + u8 dirn, u8 dev_type) +{ + struct nhlt_fmt *fmt; + struct nhlt_endpoint *epnt; + struct hdac_bus *bus = skl_to_bus(skl); + struct device *dev = bus->dev; + struct nhlt_specific_cfg *sp_config; + struct nhlt_acpi_table *nhlt = skl->nhlt; + u16 bps = (s_fmt == 16) ? 16 : 32; + u8 j; + + dump_config(dev, instance, link_type, s_fmt, num_ch, s_rate, dirn, bps); + + epnt = (struct nhlt_endpoint *)nhlt->desc; + + dev_dbg(dev, "endpoint count =%d\n", nhlt->endpoint_count); + + for (j = 0; j < nhlt->endpoint_count; j++) { + if (skl_check_ep_match(dev, epnt, instance, link_type, + dirn, dev_type)) { + fmt = (struct nhlt_fmt *)(epnt->config.caps + + epnt->config.size); + sp_config = skl_get_specific_cfg(dev, fmt, num_ch, + s_rate, bps, link_type); + if (sp_config) + return sp_config; + } + + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + return NULL; +} + +int skl_get_dmic_geo(struct skl *skl) +{ + struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt; + struct nhlt_endpoint *epnt; + struct nhlt_dmic_array_config *cfg; + struct device *dev = &skl->pci->dev; + unsigned int dmic_geo = 0; + u8 j; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + + for (j = 0; j < nhlt->endpoint_count; j++) { + if (epnt->linktype == NHLT_LINK_DMIC) { + cfg = (struct nhlt_dmic_array_config *) + (epnt->config.caps); + switch (cfg->array_type) { + case NHLT_MIC_ARRAY_2CH_SMALL: + case NHLT_MIC_ARRAY_2CH_BIG: + dmic_geo |= MIC_ARRAY_2CH; + break; + + case NHLT_MIC_ARRAY_4CH_1ST_GEOM: + case NHLT_MIC_ARRAY_4CH_L_SHAPED: + case NHLT_MIC_ARRAY_4CH_2ND_GEOM: + dmic_geo |= MIC_ARRAY_4CH; + break; + + default: + dev_warn(dev, "undefined DMIC array_type 0x%0x\n", + cfg->array_type); + + } + } + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + return dmic_geo; +} + +static void skl_nhlt_trim_space(char *trim) +{ + char *s = trim; + int cnt; + int i; + + cnt = 0; + for (i = 0; s[i]; i++) { + if (!isspace(s[i])) + s[cnt++] = s[i]; + } + + s[cnt] = '\0'; +} + +int skl_nhlt_update_topology_bin(struct skl *skl) +{ + struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt; + struct hdac_bus *bus = skl_to_bus(skl); + struct device *dev = bus->dev; + + dev_dbg(dev, "oem_id %.6s, oem_table_id %.8s oem_revision %d\n", + nhlt->header.oem_id, nhlt->header.oem_table_id, + nhlt->header.oem_revision); + + snprintf(skl->tplg_name, sizeof(skl->tplg_name), "%x-%.6s-%.8s-%d%s", + skl->pci_id, nhlt->header.oem_id, nhlt->header.oem_table_id, + nhlt->header.oem_revision, "-tplg.bin"); + + skl_nhlt_trim_space(skl->tplg_name); + + return 0; +} + +static ssize_t skl_nhlt_platform_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + struct skl *skl = bus_to_skl(bus); + struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt; + char platform_id[32]; + + sprintf(platform_id, "%x-%.6s-%.8s-%d", skl->pci_id, + nhlt->header.oem_id, nhlt->header.oem_table_id, + nhlt->header.oem_revision); + + skl_nhlt_trim_space(platform_id); + return sprintf(buf, "%s\n", platform_id); +} + +static DEVICE_ATTR(platform_id, 0444, skl_nhlt_platform_id_show, NULL); + +int skl_nhlt_create_sysfs(struct skl *skl) +{ + struct device *dev = &skl->pci->dev; + + if (sysfs_create_file(&dev->kobj, &dev_attr_platform_id.attr)) + dev_warn(dev, "Error creating sysfs entry\n"); + + return 0; +} + +void skl_nhlt_remove_sysfs(struct skl *skl) +{ + struct device *dev = &skl->pci->dev; + + sysfs_remove_file(&dev->kobj, &dev_attr_platform_id.attr); +} + +/* + * Queries NHLT for all the fmt configuration for a particular endpoint and + * stores all possible rates supported in a rate table for the corresponding + * sclk/sclkfs. + */ +static void skl_get_ssp_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks, + struct nhlt_fmt *fmt, u8 id) +{ + struct skl_i2s_config_blob_ext *i2s_config_ext; + struct skl_i2s_config_blob_legacy *i2s_config; + struct skl_clk_parent_src *parent; + struct skl_ssp_clk *sclk, *sclkfs; + struct nhlt_fmt_cfg *fmt_cfg; + struct wav_fmt_ext *wav_fmt; + unsigned long rate = 0; + bool present = false; + int rate_index = 0; + u16 channels, bps; + u8 clk_src; + int i, j; + u32 fs; + + sclk = &ssp_clks[SKL_SCLK_OFS]; + sclkfs = &ssp_clks[SKL_SCLKFS_OFS]; + + if (fmt->fmt_count == 0) + return; + + for (i = 0; i < fmt->fmt_count; i++) { + fmt_cfg = &fmt->fmt_config[i]; + wav_fmt = &fmt_cfg->fmt_ext; + + channels = wav_fmt->fmt.channels; + bps = wav_fmt->fmt.bits_per_sample; + fs = wav_fmt->fmt.samples_per_sec; + + /* + * In case of TDM configuration on a ssp, there can + * be more than one blob in which channel masks are + * different for each usecase for a specific rate and bps. + * But the sclk rate will be generated for the total + * number of channels used for that endpoint. + * + * So for the given fs and bps, choose blob which has + * the superset of all channels for that endpoint and + * derive the rate. + */ + for (j = i; j < fmt->fmt_count; j++) { + fmt_cfg = &fmt->fmt_config[j]; + wav_fmt = &fmt_cfg->fmt_ext; + if ((fs == wav_fmt->fmt.samples_per_sec) && + (bps == wav_fmt->fmt.bits_per_sample)) + channels = max_t(u16, channels, + wav_fmt->fmt.channels); + } + + rate = channels * bps * fs; + + /* check if the rate is added already to the given SSP's sclk */ + for (j = 0; (j < SKL_MAX_CLK_RATES) && + (sclk[id].rate_cfg[j].rate != 0); j++) { + if (sclk[id].rate_cfg[j].rate == rate) { + present = true; + break; + } + } + + /* Fill rate and parent for sclk/sclkfs */ + if (!present) { + i2s_config_ext = (struct skl_i2s_config_blob_ext *) + fmt->fmt_config[0].config.caps; + + /* MCLK Divider Source Select */ + if (is_legacy_blob(i2s_config_ext->hdr.sig)) { + i2s_config = ext_to_legacy_blob(i2s_config_ext); + clk_src = get_clk_src(i2s_config->mclk, + SKL_MNDSS_DIV_CLK_SRC_MASK); + } else { + clk_src = get_clk_src(i2s_config_ext->mclk, + SKL_MNDSS_DIV_CLK_SRC_MASK); + } + + parent = skl_get_parent_clk(clk_src); + + /* + * Do not copy the config data if there is no parent + * clock available for this clock source select + */ + if (!parent) + continue; + + sclk[id].rate_cfg[rate_index].rate = rate; + sclk[id].rate_cfg[rate_index].config = fmt_cfg; + sclkfs[id].rate_cfg[rate_index].rate = rate; + sclkfs[id].rate_cfg[rate_index].config = fmt_cfg; + sclk[id].parent_name = parent->name; + sclkfs[id].parent_name = parent->name; + + rate_index++; + } + } +} + +static void skl_get_mclk(struct skl *skl, struct skl_ssp_clk *mclk, + struct nhlt_fmt *fmt, u8 id) +{ + struct skl_i2s_config_blob_ext *i2s_config_ext; + struct skl_i2s_config_blob_legacy *i2s_config; + struct nhlt_specific_cfg *fmt_cfg; + struct skl_clk_parent_src *parent; + u32 clkdiv, div_ratio; + u8 clk_src; + + fmt_cfg = &fmt->fmt_config[0].config; + i2s_config_ext = (struct skl_i2s_config_blob_ext *)fmt_cfg->caps; + + /* MCLK Divider Source Select and divider */ + if (is_legacy_blob(i2s_config_ext->hdr.sig)) { + i2s_config = ext_to_legacy_blob(i2s_config_ext); + clk_src = get_clk_src(i2s_config->mclk, + SKL_MCLK_DIV_CLK_SRC_MASK); + clkdiv = i2s_config->mclk.mdivr & + SKL_MCLK_DIV_RATIO_MASK; + } else { + clk_src = get_clk_src(i2s_config_ext->mclk, + SKL_MCLK_DIV_CLK_SRC_MASK); + clkdiv = i2s_config_ext->mclk.mdivr[0] & + SKL_MCLK_DIV_RATIO_MASK; + } + + /* bypass divider */ + div_ratio = 1; + + if (clkdiv != SKL_MCLK_DIV_RATIO_MASK) + /* Divider is 2 + clkdiv */ + div_ratio = clkdiv + 2; + + /* Calculate MCLK rate from source using div value */ + parent = skl_get_parent_clk(clk_src); + if (!parent) + return; + + mclk[id].rate_cfg[0].rate = parent->rate/div_ratio; + mclk[id].rate_cfg[0].config = &fmt->fmt_config[0]; + mclk[id].parent_name = parent->name; +} + +void skl_get_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks) +{ + struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt; + struct nhlt_endpoint *epnt; + struct nhlt_fmt *fmt; + int i; + u8 id; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + for (i = 0; i < nhlt->endpoint_count; i++) { + if (epnt->linktype == NHLT_LINK_SSP) { + id = epnt->virtual_bus_id; + + fmt = (struct nhlt_fmt *)(epnt->config.caps + + epnt->config.size); + + skl_get_ssp_clks(skl, ssp_clks, fmt, id); + skl_get_mclk(skl, ssp_clks, fmt, id); + } + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } +} diff --git a/sound/soc/intel/skylake/skl-nhlt.h b/sound/soc/intel/skylake/skl-nhlt.h new file mode 100644 index 000000000..116534e7b --- /dev/null +++ b/sound/soc/intel/skylake/skl-nhlt.h @@ -0,0 +1,128 @@ +/* + * skl-nhlt.h - Intel HDA Platform NHLT header + * + * Copyright (C) 2015 Intel Corp + * Author: Sanjiv Kumar + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ +#ifndef __SKL_NHLT_H__ +#define __SKL_NHLT_H__ + +#include + +struct wav_fmt { + u16 fmt_tag; + u16 channels; + u32 samples_per_sec; + u32 avg_bytes_per_sec; + u16 block_align; + u16 bits_per_sample; + u16 cb_size; +} __packed; + +struct wav_fmt_ext { + struct wav_fmt fmt; + union samples { + u16 valid_bits_per_sample; + u16 samples_per_block; + u16 reserved; + } sample; + u32 channel_mask; + u8 sub_fmt[16]; +} __packed; + +enum nhlt_link_type { + NHLT_LINK_HDA = 0, + NHLT_LINK_DSP = 1, + NHLT_LINK_DMIC = 2, + NHLT_LINK_SSP = 3, + NHLT_LINK_INVALID +}; + +enum nhlt_device_type { + NHLT_DEVICE_BT = 0, + NHLT_DEVICE_DMIC = 1, + NHLT_DEVICE_I2S = 4, + NHLT_DEVICE_INVALID +}; + +struct nhlt_specific_cfg { + u32 size; + u8 caps[0]; +} __packed; + +struct nhlt_fmt_cfg { + struct wav_fmt_ext fmt_ext; + struct nhlt_specific_cfg config; +} __packed; + +struct nhlt_fmt { + u8 fmt_count; + struct nhlt_fmt_cfg fmt_config[0]; +} __packed; + +struct nhlt_endpoint { + u32 length; + u8 linktype; + u8 instance_id; + u16 vendor_id; + u16 device_id; + u16 revision_id; + u32 subsystem_id; + u8 device_type; + u8 direction; + u8 virtual_bus_id; + struct nhlt_specific_cfg config; +} __packed; + +struct nhlt_acpi_table { + struct acpi_table_header header; + u8 endpoint_count; + struct nhlt_endpoint desc[0]; +} __packed; + +struct nhlt_resource_desc { + u32 extra; + u16 flags; + u64 addr_spc_gra; + u64 min_addr; + u64 max_addr; + u64 addr_trans_offset; + u64 length; +} __packed; + +#define MIC_ARRAY_2CH 2 +#define MIC_ARRAY_4CH 4 + +struct nhlt_tdm_config { + u8 virtual_slot; + u8 config_type; +} __packed; + +struct nhlt_dmic_array_config { + struct nhlt_tdm_config tdm_config; + u8 array_type; +} __packed; + +enum { + NHLT_MIC_ARRAY_2CH_SMALL = 0xa, + NHLT_MIC_ARRAY_2CH_BIG = 0xb, + NHLT_MIC_ARRAY_4CH_1ST_GEOM = 0xc, + NHLT_MIC_ARRAY_4CH_L_SHAPED = 0xd, + NHLT_MIC_ARRAY_4CH_2ND_GEOM = 0xe, + NHLT_MIC_ARRAY_VENDOR_DEFINED = 0xf, +}; + +#endif diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c new file mode 100644 index 000000000..6b2c8c6e7 --- /dev/null +++ b/sound/soc/intel/skylake/skl-pcm.c @@ -0,0 +1,1487 @@ +/* + * skl-pcm.c -ASoC HDA Platform driver file implementing PCM functionality + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#include +#include +#include +#include +#include +#include "skl.h" +#include "skl-topology.h" +#include "skl-sst-dsp.h" +#include "skl-sst-ipc.h" + +#define HDA_MONO 1 +#define HDA_STEREO 2 +#define HDA_QUAD 4 + +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 | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + 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 | + SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 8, + .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 inline +struct hdac_ext_stream *get_hdac_ext_stream(struct snd_pcm_substream *substream) +{ + return substream->runtime->private_data; +} + +static struct hdac_bus *get_bus_ctx(struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + struct hdac_stream *hstream = hdac_stream(stream); + struct hdac_bus *bus = hstream->bus; + return bus; +} + +static int skl_substream_alloc_pages(struct hdac_bus *bus, + struct snd_pcm_substream *substream, + size_t size) +{ + struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + + hdac_stream(stream)->bufsize = 0; + hdac_stream(stream)->period_bytes = 0; + hdac_stream(stream)->format_val = 0; + + return snd_pcm_lib_malloc_pages(substream, size); +} + +static int skl_substream_free_pages(struct hdac_bus *bus, + struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static void skl_set_pcm_constrains(struct hdac_bus *bus, + struct snd_pcm_runtime *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); +} + +static enum hdac_ext_stream_type skl_get_host_stream_type(struct hdac_bus *bus) +{ + if (bus->ppcap) + return HDAC_EXT_STREAM_TYPE_HOST; + else + return HDAC_EXT_STREAM_TYPE_COUPLED; +} + +/* + * check if the stream opened is marked as ignore_suspend by machine, if so + * then enable suspend_active refcount + * + * The count supend_active does not need lock as it is used in open/close + * and suspend context + */ +static void skl_set_suspend_active(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, bool enable) +{ + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct snd_soc_dapm_widget *w; + struct skl *skl = bus_to_skl(bus); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = dai->playback_widget; + else + w = dai->capture_widget; + + if (w->ignore_suspend && enable) + skl->supend_active++; + else if (w->ignore_suspend && !enable) + skl->supend_active--; +} + +int skl_pcm_host_dma_prepare(struct device *dev, struct skl_pipe_params *params) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + unsigned int format_val; + struct hdac_stream *hstream; + struct hdac_ext_stream *stream; + int err; + + hstream = snd_hdac_get_stream(bus, params->stream, + params->host_dma_id + 1); + if (!hstream) + return -EINVAL; + + stream = stream_to_hdac_ext_stream(hstream); + snd_hdac_ext_stream_decouple(bus, stream, true); + + format_val = snd_hdac_calc_stream_format(params->s_freq, + params->ch, params->format, params->host_bps, 0); + + dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", + format_val, params->s_freq, params->ch, params->format); + + snd_hdac_stream_reset(hdac_stream(stream)); + err = snd_hdac_stream_set_params(hdac_stream(stream), format_val); + if (err < 0) + return err; + + err = snd_hdac_stream_setup(hdac_stream(stream)); + if (err < 0) + return err; + + hdac_stream(stream)->prepared = 1; + + return 0; +} + +int skl_pcm_link_dma_prepare(struct device *dev, struct skl_pipe_params *params) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + unsigned int format_val; + struct hdac_stream *hstream; + struct hdac_ext_stream *stream; + struct hdac_ext_link *link; + unsigned char stream_tag; + + hstream = snd_hdac_get_stream(bus, params->stream, + params->link_dma_id + 1); + if (!hstream) + return -EINVAL; + + stream = stream_to_hdac_ext_stream(hstream); + snd_hdac_ext_stream_decouple(bus, stream, true); + format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, + params->format, params->link_bps, 0); + + dev_dbg(dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", + format_val, params->s_freq, params->ch, params->format); + + snd_hdac_ext_link_stream_reset(stream); + + snd_hdac_ext_link_stream_setup(stream, format_val); + + stream_tag = hstream->stream_tag; + if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { + list_for_each_entry(link, &bus->hlink_list, list) { + if (link->index == params->link_index) + snd_hdac_ext_link_set_stream_id(link, + stream_tag); + } + } + + stream->link_prepared = 1; + + return 0; +} + +static int skl_pcm_open(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct hdac_ext_stream *stream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct skl_dma_params *dma_params; + struct skl *skl = get_skl_ctx(dai->dev); + struct skl_module_cfg *mconfig; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + stream = snd_hdac_ext_stream_assign(bus, substream, + skl_get_host_stream_type(bus)); + if (stream == NULL) + return -EBUSY; + + skl_set_pcm_constrains(bus, runtime); + + /* + * disable WALLCLOCK 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; + } + + runtime->private_data = stream; + + dma_params = kzalloc(sizeof(*dma_params), GFP_KERNEL); + if (!dma_params) + return -ENOMEM; + + dma_params->stream_tag = hdac_stream(stream)->stream_tag; + snd_soc_dai_set_dma_data(dai, substream, dma_params); + + dev_dbg(dai->dev, "stream tag set in dma params=%d\n", + dma_params->stream_tag); + skl_set_suspend_active(substream, dai, true); + snd_pcm_set_sync(substream); + + mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream); + if (!mconfig) + return -EINVAL; + + skl_tplg_d0i3_get(skl, mconfig->d0i3_caps); + + return 0; +} + +static int skl_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct skl *skl = get_skl_ctx(dai->dev); + struct skl_module_cfg *mconfig; + int ret; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream); + + /* + * In case of XRUN recovery or in the case when the application + * calls prepare another time, reset the FW pipe to clean state + */ + if (mconfig && + (substream->runtime->status->state == SNDRV_PCM_STATE_XRUN || + mconfig->pipe->state == SKL_PIPE_CREATED || + mconfig->pipe->state == SKL_PIPE_PAUSED)) { + + ret = skl_reset_pipe(skl->skl_sst, mconfig->pipe); + + if (ret < 0) + return ret; + + ret = skl_pcm_host_dma_prepare(dai->dev, + mconfig->pipe->p_params); + if (ret < 0) + return ret; + } + + return 0; +} + +static int skl_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct skl_pipe_params p_params = {0}; + struct skl_module_cfg *m_cfg; + int ret, dma_id; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + ret = skl_substream_alloc_pages(bus, substream, + params_buffer_bytes(params)); + if (ret < 0) + return ret; + + dev_dbg(dai->dev, "format_val, rate=%d, ch=%d, format=%d\n", + runtime->rate, runtime->channels, runtime->format); + + dma_id = hdac_stream(stream)->stream_tag - 1; + dev_dbg(dai->dev, "dma_id=%d\n", dma_id); + + p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.ch = params_channels(params); + p_params.s_freq = params_rate(params); + p_params.host_dma_id = dma_id; + p_params.stream = substream->stream; + p_params.format = params_format(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.host_bps = dai->driver->playback.sig_bits; + else + p_params.host_bps = dai->driver->capture.sig_bits; + + + m_cfg = skl_tplg_fe_get_cpr_module(dai, p_params.stream); + if (m_cfg) + skl_tplg_update_pipe_params(dai->dev, m_cfg, &p_params); + + return 0; +} + +static void skl_pcm_close(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct skl_dma_params *dma_params = NULL; + struct skl *skl = bus_to_skl(bus); + struct skl_module_cfg *mconfig; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + snd_hdac_ext_stream_release(stream, skl_get_host_stream_type(bus)); + + dma_params = snd_soc_dai_get_dma_data(dai, substream); + /* + * now we should set this to NULL as we are freeing by the + * dma_params + */ + snd_soc_dai_set_dma_data(dai, substream, NULL); + skl_set_suspend_active(substream, dai, false); + + /* + * check if close is for "Reference Pin" and set back the + * CGCTL.MISCBDCGE if disabled by driver + */ + if (!strncmp(dai->name, "Reference Pin", 13) && + skl->skl_sst->miscbdcg_disabled) { + skl->skl_sst->enable_miscbdcge(dai->dev, true); + skl->skl_sst->miscbdcg_disabled = false; + } + + mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream); + if (mconfig) + skl_tplg_d0i3_put(skl, mconfig->d0i3_caps); + + kfree(dma_params); +} + +static int skl_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + struct skl *skl = get_skl_ctx(dai->dev); + struct skl_module_cfg *mconfig; + int ret; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream); + + if (mconfig) { + ret = skl_reset_pipe(skl->skl_sst, mconfig->pipe); + if (ret < 0) + dev_err(dai->dev, "%s:Reset failed ret =%d", + __func__, ret); + } + + snd_hdac_stream_cleanup(hdac_stream(stream)); + hdac_stream(stream)->prepared = 0; + + return skl_substream_free_pages(bus, substream); +} + +static int skl_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct skl_pipe_params p_params = {0}; + + p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.ch = params_channels(params); + p_params.s_freq = params_rate(params); + p_params.stream = substream->stream; + + return skl_tplg_be_update_params(dai, &p_params); +} + +static int skl_decoupled_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct hdac_bus *bus = get_bus_ctx(substream); + struct hdac_ext_stream *stream; + int start; + unsigned long cookie; + struct hdac_stream *hstr; + + stream = get_hdac_ext_stream(substream); + hstr = hdac_stream(stream); + + if (!hstr->prepared) + return -EPIPE; + + 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; + } + + spin_lock_irqsave(&bus->reg_lock, cookie); + + if (start) { + snd_hdac_stream_start(hdac_stream(stream), true); + snd_hdac_stream_timecounter_init(hstr, 0); + } else { + snd_hdac_stream_stop(hdac_stream(stream)); + } + + spin_unlock_irqrestore(&bus->reg_lock, cookie); + + return 0; +} + +static int skl_pcm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct skl *skl = get_skl_ctx(dai->dev); + struct skl_sst *ctx = skl->skl_sst; + struct skl_module_cfg *mconfig; + struct hdac_bus *bus = get_bus_ctx(substream); + struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + struct snd_soc_dapm_widget *w; + int ret; + + mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream); + if (!mconfig) + return -EIO; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + w = dai->playback_widget; + else + w = dai->capture_widget; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + if (!w->ignore_suspend) { + /* + * enable DMA Resume enable bit for the stream, set the + * dpib & lpib position to resume before starting the + * DMA + */ + snd_hdac_ext_stream_drsm_enable(bus, true, + hdac_stream(stream)->index); + snd_hdac_ext_stream_set_dpibr(bus, stream, + stream->lpib); + snd_hdac_ext_stream_set_lpib(stream, stream->lpib); + } + + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * Start HOST DMA and Start FE Pipe.This is to make sure that + * there are no underrun/overrun in the case when the FE + * pipeline is started but there is a delay in starting the + * DMA channel on the host. + */ + ret = skl_decoupled_trigger(substream, cmd); + if (ret < 0) + return ret; + return skl_run_pipe(ctx, mconfig->pipe); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + /* + * Stop FE Pipe first and stop DMA. This is to make sure that + * there are no underrun/overrun in the case if there is a delay + * between the two operations. + */ + ret = skl_stop_pipe(ctx, mconfig->pipe); + if (ret < 0) + return ret; + + ret = skl_decoupled_trigger(substream, cmd); + if ((cmd == SNDRV_PCM_TRIGGER_SUSPEND) && !w->ignore_suspend) { + /* save the dpib and lpib positions */ + stream->dpib = readl(bus->remap_addr + + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hdac_stream(stream)->index)); + + stream->lpib = snd_hdac_stream_get_pos_lpib( + hdac_stream(stream)); + snd_hdac_ext_stream_decouple(bus, stream, false); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + + +static int skl_link_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct hdac_ext_stream *link_dev; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct skl_pipe_params p_params = {0}; + struct hdac_ext_link *link; + int stream_tag; + + link_dev = snd_hdac_ext_stream_assign(bus, substream, + HDAC_EXT_STREAM_TYPE_LINK); + if (!link_dev) + return -EBUSY; + + snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev); + + link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); + if (!link) + return -EINVAL; + + stream_tag = hdac_stream(link_dev)->stream_tag; + + /* set the stream tag in the codec dai dma params */ + snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0); + + p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.ch = params_channels(params); + p_params.s_freq = params_rate(params); + p_params.stream = substream->stream; + p_params.link_dma_id = stream_tag - 1; + p_params.link_index = link->index; + p_params.format = params_format(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.link_bps = codec_dai->driver->playback.sig_bits; + else + p_params.link_bps = codec_dai->driver->capture.sig_bits; + + return skl_tplg_be_update_params(dai, &p_params); +} + +static int skl_link_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct skl *skl = get_skl_ctx(dai->dev); + struct skl_module_cfg *mconfig = NULL; + + /* In case of XRUN recovery, reset the FW pipe to clean state */ + mconfig = skl_tplg_be_get_cpr_module(dai, substream->stream); + if (mconfig && !mconfig->pipe->passthru && + (substream->runtime->status->state == SNDRV_PCM_STATE_XRUN)) + skl_reset_pipe(skl->skl_sst, mconfig->pipe); + + return 0; +} + +static int skl_link_pcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + struct hdac_bus *bus = get_bus_ctx(substream); + struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + + dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd); + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_link_stream_start(link_dev); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + snd_hdac_ext_link_stream_clear(link_dev); + if (cmd == SNDRV_PCM_TRIGGER_SUSPEND) + snd_hdac_ext_stream_decouple(bus, stream, false); + break; + + default: + return -EINVAL; + } + return 0; +} + +static int skl_link_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct hdac_ext_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + struct hdac_ext_link *link; + unsigned char stream_tag; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + link_dev->link_prepared = 0; + + link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name); + if (!link) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(link_dev)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + + snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK); + return 0; +} + +static const struct snd_soc_dai_ops skl_pcm_dai_ops = { + .startup = skl_pcm_open, + .shutdown = skl_pcm_close, + .prepare = skl_pcm_prepare, + .hw_params = skl_pcm_hw_params, + .hw_free = skl_pcm_hw_free, + .trigger = skl_pcm_trigger, +}; + +static const struct snd_soc_dai_ops skl_dmic_dai_ops = { + .hw_params = skl_be_hw_params, +}; + +static const struct snd_soc_dai_ops skl_be_ssp_dai_ops = { + .hw_params = skl_be_hw_params, +}; + +static const struct snd_soc_dai_ops skl_link_dai_ops = { + .prepare = skl_link_pcm_prepare, + .hw_params = skl_link_hw_params, + .hw_free = skl_link_hw_free, + .trigger = skl_link_pcm_trigger, +}; + +static struct snd_soc_dai_driver skl_fe_dai[] = { +{ + .name = "System Pin", + .ops = &skl_pcm_dai_ops, + .playback = { + .stream_name = "System Playback", + .channels_min = HDA_MONO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, + }, + .capture = { + .stream_name = "System Capture", + .channels_min = HDA_MONO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, + }, +}, +{ + .name = "System Pin2", + .ops = &skl_pcm_dai_ops, + .playback = { + .stream_name = "Headset Playback", + .channels_min = HDA_MONO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + }, +}, +{ + .name = "Echoref Pin", + .ops = &skl_pcm_dai_ops, + .capture = { + .stream_name = "Echoreference Capture", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + }, +}, +{ + .name = "Reference Pin", + .ops = &skl_pcm_dai_ops, + .capture = { + .stream_name = "Reference Capture", + .channels_min = HDA_MONO, + .channels_max = HDA_QUAD, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, + }, +}, +{ + .name = "Deepbuffer Pin", + .ops = &skl_pcm_dai_ops, + .playback = { + .stream_name = "Deepbuffer Playback", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, + }, +}, +{ + .name = "LowLatency Pin", + .ops = &skl_pcm_dai_ops, + .playback = { + .stream_name = "Low Latency Playback", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, + }, +}, +{ + .name = "DMIC Pin", + .ops = &skl_pcm_dai_ops, + .capture = { + .stream_name = "DMIC Capture", + .channels_min = HDA_MONO, + .channels_max = HDA_QUAD, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + .sig_bits = 32, + }, +}, +{ + .name = "HDMI1 Pin", + .ops = &skl_pcm_dai_ops, + .playback = { + .stream_name = "HDMI1 Playback", + .channels_min = HDA_STEREO, + .channels_max = 8, + .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, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, + }, +}, +{ + .name = "HDMI2 Pin", + .ops = &skl_pcm_dai_ops, + .playback = { + .stream_name = "HDMI2 Playback", + .channels_min = HDA_STEREO, + .channels_max = 8, + .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, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, + }, +}, +{ + .name = "HDMI3 Pin", + .ops = &skl_pcm_dai_ops, + .playback = { + .stream_name = "HDMI3 Playback", + .channels_min = HDA_STEREO, + .channels_max = 8, + .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, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 32, + }, +}, +}; + +/* BE CPU Dais */ +static struct snd_soc_dai_driver skl_platform_dai[] = { +{ + .name = "SSP0 Pin", + .ops = &skl_be_ssp_dai_ops, + .playback = { + .stream_name = "ssp0 Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp0 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "SSP1 Pin", + .ops = &skl_be_ssp_dai_ops, + .playback = { + .stream_name = "ssp1 Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp1 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "SSP2 Pin", + .ops = &skl_be_ssp_dai_ops, + .playback = { + .stream_name = "ssp2 Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp2 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "SSP3 Pin", + .ops = &skl_be_ssp_dai_ops, + .playback = { + .stream_name = "ssp3 Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp3 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "SSP4 Pin", + .ops = &skl_be_ssp_dai_ops, + .playback = { + .stream_name = "ssp4 Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp4 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "SSP5 Pin", + .ops = &skl_be_ssp_dai_ops, + .playback = { + .stream_name = "ssp5 Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "ssp5 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "iDisp1 Pin", + .ops = &skl_link_dai_ops, + .playback = { + .stream_name = "iDisp1 Tx", + .channels_min = HDA_STEREO, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "iDisp2 Pin", + .ops = &skl_link_dai_ops, + .playback = { + .stream_name = "iDisp2 Tx", + .channels_min = HDA_STEREO, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000| + SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "iDisp3 Pin", + .ops = &skl_link_dai_ops, + .playback = { + .stream_name = "iDisp3 Tx", + .channels_min = HDA_STEREO, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000| + SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "DMIC01 Pin", + .ops = &skl_dmic_dai_ops, + .capture = { + .stream_name = "DMIC01 Rx", + .channels_min = HDA_MONO, + .channels_max = HDA_QUAD, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "DMIC16k Pin", + .ops = &skl_dmic_dai_ops, + .capture = { + .stream_name = "DMIC16k Rx", + .channels_min = HDA_MONO, + .channels_max = HDA_QUAD, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "HD-Codec Pin", + .ops = &skl_link_dai_ops, + .playback = { + .stream_name = "HD-Codec Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "HD-Codec Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +int skl_dai_load(struct snd_soc_component *cmp, int index, + struct snd_soc_dai_driver *dai_drv, + struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai) +{ + dai_drv->ops = &skl_pcm_dai_ops; + + return 0; +} + +static int skl_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai_link = rtd->dai_link; + + dev_dbg(rtd->cpu_dai->dev, "In %s:%s\n", __func__, + dai_link->cpu_dai_name); + + snd_soc_set_runtime_hwparams(substream, &azx_pcm_hw); + + return 0; +} + +static int skl_coupled_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct hdac_bus *bus = get_bus_ctx(substream); + struct hdac_ext_stream *stream; + struct snd_pcm_substream *s; + bool start; + int sbits = 0; + unsigned long cookie; + struct hdac_stream *hstr; + + stream = get_hdac_ext_stream(substream); + hstr = hdac_stream(stream); + + dev_dbg(bus->dev, "In %s cmd=%d\n", __func__, cmd); + + if (!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; + stream = get_hdac_ext_stream(s); + sbits |= 1 << hdac_stream(stream)->index; + snd_pcm_trigger_done(s, substream); + } + + spin_lock_irqsave(&bus->reg_lock, cookie); + + /* first, set SYNC bits of corresponding streams */ + snd_hdac_stream_sync_trigger(hstr, true, sbits, AZX_REG_SSYNC); + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + stream = get_hdac_ext_stream(s); + if (start) + snd_hdac_stream_start(hdac_stream(stream), true); + else + snd_hdac_stream_stop(hdac_stream(stream)); + } + spin_unlock_irqrestore(&bus->reg_lock, cookie); + + snd_hdac_stream_sync(hstr, start, sbits); + + spin_lock_irqsave(&bus->reg_lock, cookie); + + /* reset SYNC bits */ + snd_hdac_stream_sync_trigger(hstr, false, sbits, AZX_REG_SSYNC); + if (start) + snd_hdac_stream_timecounter_init(hstr, sbits); + spin_unlock_irqrestore(&bus->reg_lock, cookie); + + return 0; +} + +static int skl_platform_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct hdac_bus *bus = get_bus_ctx(substream); + + if (!bus->ppcap) + return skl_coupled_trigger(substream, cmd); + + return 0; +} + +static snd_pcm_uframes_t skl_platform_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *hstream = get_hdac_ext_stream(substream); + struct hdac_bus *bus = get_bus_ctx(substream); + unsigned int pos; + + /* + * Use DPIB for Playback stream as the periodic DMA Position-in- + * Buffer Writes may be scheduled at the same time or later than + * the MSI and does not guarantee to reflect the Position of the + * last buffer that was transferred. Whereas DPIB register in + * HAD space reflects the actual data that is transferred. + * Use the position buffer for capture, as DPIB write gets + * completed earlier than the actual data written to the DDR. + * + * For capture stream following workaround is required to fix the + * incorrect position reporting. + * + * 1. Wait for 20us before reading the DMA position in buffer once + * the interrupt is generated for stream completion as update happens + * on the HDA frame boundary i.e. 20.833uSec. + * 2. Read DPIB register to flush the DMA position value. This dummy + * read is required to flush DMA position value. + * 3. Read the DMA Position-in-Buffer. This value now will be equal to + * or greater than period boundary. + */ + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pos = readl(bus->remap_addr + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hdac_stream(hstream)->index)); + } else { + udelay(20); + readl(bus->remap_addr + + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hdac_stream(hstream)->index)); + pos = snd_hdac_stream_get_pos_posbuf(hdac_stream(hstream)); + } + + if (pos >= hdac_stream(hstream)->bufsize) + pos = 0; + + return bytes_to_frames(substream->runtime, pos); +} + +static u64 skl_adjust_codec_delay(struct snd_pcm_substream *substream, + u64 nsec) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + u64 codec_frames, codec_nsecs; + + if (!codec_dai->driver->ops->delay) + return nsec; + + codec_frames = codec_dai->driver->ops->delay(substream, codec_dai); + 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; +} + +static int skl_get_time_info(struct snd_pcm_substream *substream, + struct timespec *system_ts, struct timespec *audio_ts, + struct snd_pcm_audio_tstamp_config *audio_tstamp_config, + struct snd_pcm_audio_tstamp_report *audio_tstamp_report) +{ + struct hdac_ext_stream *sstream = get_hdac_ext_stream(substream); + struct hdac_stream *hstr = hdac_stream(sstream); + 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(&hstr->tc); + nsec = div_u64(nsec, 3); /* can be optimized */ + if (audio_tstamp_config->report_delay) + nsec = skl_adjust_codec_delay(substream, nsec); + + *audio_ts = ns_to_timespec(nsec); + + audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK; + audio_tstamp_report->accuracy_report = 1; /* rest of struct is valid */ + audio_tstamp_report->accuracy = 42; /* 24MHzWallClk == 42ns resolution */ + + } else { + audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT; + } + + return 0; +} + +static const struct snd_pcm_ops skl_platform_ops = { + .open = skl_platform_open, + .ioctl = snd_pcm_lib_ioctl, + .trigger = skl_platform_pcm_trigger, + .pointer = skl_platform_pcm_pointer, + .get_time_info = skl_get_time_info, + .mmap = snd_pcm_lib_default_mmap, + .page = snd_pcm_sgbuf_ops_page, +}; + +static void skl_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +#define MAX_PREALLOC_SIZE (32 * 1024 * 1024) + +static int skl_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct hdac_bus *bus = dev_get_drvdata(dai->dev); + struct snd_pcm *pcm = rtd->pcm; + unsigned int size; + int retval = 0; + struct skl *skl = bus_to_skl(bus); + + if (dai->driver->playback.channels_min || + dai->driver->capture.channels_min) { + /* buffer pre-allocation */ + size = CONFIG_SND_HDA_PREALLOC_SIZE * 1024; + if (size > MAX_PREALLOC_SIZE) + size = MAX_PREALLOC_SIZE; + retval = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(skl->pci), + size, MAX_PREALLOC_SIZE); + if (retval) { + dev_err(dai->dev, "dma buffer allocation fail\n"); + return retval; + } + } + + return retval; +} + +static int skl_get_module_info(struct skl *skl, struct skl_module_cfg *mconfig) +{ + struct skl_sst *ctx = skl->skl_sst; + struct skl_module_inst_id *pin_id; + uuid_le *uuid_mod, *uuid_tplg; + struct skl_module *skl_module; + struct uuid_module *module; + int i, ret = -EIO; + + uuid_mod = (uuid_le *)mconfig->guid; + + if (list_empty(&ctx->uuid_list)) { + dev_err(ctx->dev, "Module list is empty\n"); + return -EIO; + } + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { + mconfig->id.module_id = module->id; + if (mconfig->module) + mconfig->module->loadable = module->is_loadable; + ret = 0; + break; + } + } + + if (ret) + return ret; + + uuid_mod = &module->uuid; + ret = -EIO; + for (i = 0; i < skl->nr_modules; i++) { + skl_module = skl->modules[i]; + uuid_tplg = &skl_module->uuid; + if (!uuid_le_cmp(*uuid_mod, *uuid_tplg)) { + mconfig->module = skl_module; + ret = 0; + break; + } + } + if (skl->nr_modules && ret) + return ret; + + list_for_each_entry(module, &ctx->uuid_list, list) { + for (i = 0; i < MAX_IN_QUEUE; i++) { + pin_id = &mconfig->m_in_pin[i].id; + if (!uuid_le_cmp(pin_id->mod_uuid, module->uuid)) + pin_id->module_id = module->id; + } + + for (i = 0; i < MAX_OUT_QUEUE; i++) { + pin_id = &mconfig->m_out_pin[i].id; + if (!uuid_le_cmp(pin_id->mod_uuid, module->uuid)) + pin_id->module_id = module->id; + } + } + + return 0; +} + +static int skl_populate_modules(struct skl *skl) +{ + struct skl_pipeline *p; + struct skl_pipe_module *m; + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mconfig; + int ret = 0; + + list_for_each_entry(p, &skl->ppl_list, node) { + list_for_each_entry(m, &p->pipe->w_list, node) { + w = m->w; + mconfig = w->priv; + + ret = skl_get_module_info(skl, mconfig); + if (ret < 0) { + dev_err(skl->skl_sst->dev, + "query module info failed\n"); + return ret; + } + + skl_tplg_add_moduleid_in_bind_params(skl, w); + } + } + + return ret; +} + +static int skl_platform_soc_probe(struct snd_soc_component *component) +{ + struct hdac_bus *bus = dev_get_drvdata(component->dev); + struct skl *skl = bus_to_skl(bus); + const struct skl_dsp_ops *ops; + int ret; + + pm_runtime_get_sync(component->dev); + if (bus->ppcap) { + skl->component = component; + + /* init debugfs */ + skl->debugfs = skl_debugfs_init(skl); + + ret = skl_tplg_init(component, bus); + if (ret < 0) { + dev_err(component->dev, "Failed to init topology!\n"); + return ret; + } + + /* load the firmwares, since all is set */ + ops = skl_get_dsp_ops(skl->pci->device); + if (!ops) + return -EIO; + + if (skl->skl_sst->is_first_boot == false) { + dev_err(component->dev, "DSP reports first boot done!!!\n"); + return -EIO; + } + + /* + * Disable dynamic clock and power gating during firmware + * and library download + */ + skl->skl_sst->enable_miscbdcge(component->dev, false); + skl->skl_sst->clock_power_gating(component->dev, false); + + ret = ops->init_fw(component->dev, skl->skl_sst); + skl->skl_sst->enable_miscbdcge(component->dev, true); + skl->skl_sst->clock_power_gating(component->dev, true); + if (ret < 0) { + dev_err(component->dev, "Failed to boot first fw: %d\n", ret); + return ret; + } + skl_populate_modules(skl); + skl->skl_sst->update_d0i3c = skl_update_d0i3c; + skl_dsp_enable_notification(skl->skl_sst, false); + + if (skl->cfg.astate_cfg != NULL) { + skl_dsp_set_astate_cfg(skl->skl_sst, + skl->cfg.astate_cfg->count, + skl->cfg.astate_cfg); + } + } + pm_runtime_mark_last_busy(component->dev); + pm_runtime_put_autosuspend(component->dev); + + return 0; +} + +static const struct snd_soc_component_driver skl_component = { + .name = "pcm", + .probe = skl_platform_soc_probe, + .ops = &skl_platform_ops, + .pcm_new = skl_pcm_new, + .pcm_free = skl_pcm_free, +}; + +int skl_platform_register(struct device *dev) +{ + int ret; + struct snd_soc_dai_driver *dais; + int num_dais = ARRAY_SIZE(skl_platform_dai); + struct hdac_bus *bus = dev_get_drvdata(dev); + struct skl *skl = bus_to_skl(bus); + + INIT_LIST_HEAD(&skl->ppl_list); + INIT_LIST_HEAD(&skl->bind_list); + + skl->dais = kmemdup(skl_platform_dai, sizeof(skl_platform_dai), + GFP_KERNEL); + if (!skl->dais) { + ret = -ENOMEM; + goto err; + } + + if (!skl->use_tplg_pcm) { + dais = krealloc(skl->dais, sizeof(skl_fe_dai) + + sizeof(skl_platform_dai), GFP_KERNEL); + if (!dais) { + ret = -ENOMEM; + goto err; + } + + skl->dais = dais; + memcpy(&skl->dais[ARRAY_SIZE(skl_platform_dai)], skl_fe_dai, + sizeof(skl_fe_dai)); + num_dais += ARRAY_SIZE(skl_fe_dai); + } + + ret = devm_snd_soc_register_component(dev, &skl_component, + skl->dais, num_dais); + if (ret) + dev_err(dev, "soc component registration failed %d\n", ret); +err: + return ret; +} + +int skl_platform_unregister(struct device *dev) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + struct skl *skl = bus_to_skl(bus); + struct skl_module_deferred_bind *modules, *tmp; + + if (!list_empty(&skl->bind_list)) { + list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { + list_del(&modules->node); + kfree(modules); + } + } + + kfree(skl->dais); + + return 0; +} diff --git a/sound/soc/intel/skylake/skl-ssp-clk.c b/sound/soc/intel/skylake/skl-ssp-clk.c new file mode 100644 index 000000000..cda1b5fa7 --- /dev/null +++ b/sound/soc/intel/skylake/skl-ssp-clk.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation + +/* + * skl-ssp-clk.c - ASoC skylake ssp clock driver + */ + +#include +#include +#include +#include +#include +#include +#include "skl.h" +#include "skl-ssp-clk.h" +#include "skl-topology.h" + +#define to_skl_clk(_hw) container_of(_hw, struct skl_clk, hw) + +struct skl_clk_parent { + struct clk_hw *hw; + struct clk_lookup *lookup; +}; + +struct skl_clk { + struct clk_hw hw; + struct clk_lookup *lookup; + unsigned long rate; + struct skl_clk_pdata *pdata; + u32 id; +}; + +struct skl_clk_data { + struct skl_clk_parent parent[SKL_MAX_CLK_SRC]; + struct skl_clk *clk[SKL_MAX_CLK_CNT]; + u8 avail_clk_cnt; +}; + +static int skl_get_clk_type(u32 index) +{ + switch (index) { + case 0 ... (SKL_SCLK_OFS - 1): + return SKL_MCLK; + + case SKL_SCLK_OFS ... (SKL_SCLKFS_OFS - 1): + return SKL_SCLK; + + case SKL_SCLKFS_OFS ... (SKL_MAX_CLK_CNT - 1): + return SKL_SCLK_FS; + + default: + return -EINVAL; + } +} + +static int skl_get_vbus_id(u32 index, u8 clk_type) +{ + switch (clk_type) { + case SKL_MCLK: + return index; + + case SKL_SCLK: + return index - SKL_SCLK_OFS; + + case SKL_SCLK_FS: + return index - SKL_SCLKFS_OFS; + + default: + return -EINVAL; + } +} + +static void skl_fill_clk_ipc(struct skl_clk_rate_cfg_table *rcfg, u8 clk_type) +{ + struct nhlt_fmt_cfg *fmt_cfg; + union skl_clk_ctrl_ipc *ipc; + struct wav_fmt *wfmt; + + if (!rcfg) + return; + + ipc = &rcfg->dma_ctl_ipc; + if (clk_type == SKL_SCLK_FS) { + fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config; + wfmt = &fmt_cfg->fmt_ext.fmt; + + /* Remove TLV Header size */ + ipc->sclk_fs.hdr.size = sizeof(struct skl_dmactrl_sclkfs_cfg) - + sizeof(struct skl_tlv_hdr); + ipc->sclk_fs.sampling_frequency = wfmt->samples_per_sec; + ipc->sclk_fs.bit_depth = wfmt->bits_per_sample; + ipc->sclk_fs.valid_bit_depth = + fmt_cfg->fmt_ext.sample.valid_bits_per_sample; + ipc->sclk_fs.number_of_channels = wfmt->channels; + } else { + ipc->mclk.hdr.type = DMA_CLK_CONTROLS; + /* Remove TLV Header size */ + ipc->mclk.hdr.size = sizeof(struct skl_dmactrl_mclk_cfg) - + sizeof(struct skl_tlv_hdr); + } +} + +/* Sends dma control IPC to turn the clock ON/OFF */ +static int skl_send_clk_dma_control(struct skl *skl, + struct skl_clk_rate_cfg_table *rcfg, + u32 vbus_id, u8 clk_type, + bool enable) +{ + struct nhlt_specific_cfg *sp_cfg; + u32 i2s_config_size, node_id = 0; + struct nhlt_fmt_cfg *fmt_cfg; + union skl_clk_ctrl_ipc *ipc; + void *i2s_config = NULL; + u8 *data, size; + int ret; + + if (!rcfg) + return -EIO; + + ipc = &rcfg->dma_ctl_ipc; + fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config; + sp_cfg = &fmt_cfg->config; + + if (clk_type == SKL_SCLK_FS) { + ipc->sclk_fs.hdr.type = + enable ? DMA_TRANSMITION_START : DMA_TRANSMITION_STOP; + data = (u8 *)&ipc->sclk_fs; + size = sizeof(struct skl_dmactrl_sclkfs_cfg); + } else { + /* 1 to enable mclk, 0 to enable sclk */ + if (clk_type == SKL_SCLK) + ipc->mclk.mclk = 0; + else + ipc->mclk.mclk = 1; + + ipc->mclk.keep_running = enable; + ipc->mclk.warm_up_over = enable; + ipc->mclk.clk_stop_over = !enable; + data = (u8 *)&ipc->mclk; + size = sizeof(struct skl_dmactrl_mclk_cfg); + } + + i2s_config_size = sp_cfg->size + size; + i2s_config = kzalloc(i2s_config_size, GFP_KERNEL); + if (!i2s_config) + return -ENOMEM; + + /* copy blob */ + memcpy(i2s_config, sp_cfg->caps, sp_cfg->size); + + /* copy additional dma controls information */ + memcpy(i2s_config + sp_cfg->size, data, size); + + node_id = ((SKL_DMA_I2S_LINK_INPUT_CLASS << 8) | (vbus_id << 4)); + ret = skl_dsp_set_dma_control(skl->skl_sst, (u32 *)i2s_config, + i2s_config_size, node_id); + kfree(i2s_config); + + return ret; +} + +static struct skl_clk_rate_cfg_table *skl_get_rate_cfg( + struct skl_clk_rate_cfg_table *rcfg, + unsigned long rate) +{ + int i; + + for (i = 0; (i < SKL_MAX_CLK_RATES) && rcfg[i].rate; i++) { + if (rcfg[i].rate == rate) + return &rcfg[i]; + } + + return NULL; +} + +static int skl_clk_change_status(struct skl_clk *clkdev, + bool enable) +{ + struct skl_clk_rate_cfg_table *rcfg; + int vbus_id, clk_type; + + clk_type = skl_get_clk_type(clkdev->id); + if (clk_type < 0) + return clk_type; + + vbus_id = skl_get_vbus_id(clkdev->id, clk_type); + if (vbus_id < 0) + return vbus_id; + + rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg, + clkdev->rate); + if (!rcfg) + return -EINVAL; + + return skl_send_clk_dma_control(clkdev->pdata->pvt_data, rcfg, + vbus_id, clk_type, enable); +} + +static int skl_clk_prepare(struct clk_hw *hw) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + + return skl_clk_change_status(clkdev, true); +} + +static void skl_clk_unprepare(struct clk_hw *hw) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + + skl_clk_change_status(clkdev, false); +} + +static int skl_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + struct skl_clk_rate_cfg_table *rcfg; + int clk_type; + + if (!rate) + return -EINVAL; + + rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg, + rate); + if (!rcfg) + return -EINVAL; + + clk_type = skl_get_clk_type(clkdev->id); + if (clk_type < 0) + return clk_type; + + skl_fill_clk_ipc(rcfg, clk_type); + clkdev->rate = rate; + + return 0; +} + +static unsigned long skl_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + + if (clkdev->rate) + return clkdev->rate; + + return 0; +} + +/* Not supported by clk driver. Implemented to satisfy clk fw */ +static long skl_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return rate; +} + +/* + * prepare/unprepare are used instead of enable/disable as IPC will be sent + * in non-atomic context. + */ +static const struct clk_ops skl_clk_ops = { + .prepare = skl_clk_prepare, + .unprepare = skl_clk_unprepare, + .set_rate = skl_clk_set_rate, + .round_rate = skl_clk_round_rate, + .recalc_rate = skl_clk_recalc_rate, +}; + +static void unregister_parent_src_clk(struct skl_clk_parent *pclk, + unsigned int id) +{ + while (id--) { + clkdev_drop(pclk[id].lookup); + clk_hw_unregister_fixed_rate(pclk[id].hw); + } +} + +static void unregister_src_clk(struct skl_clk_data *dclk) +{ + u8 cnt = dclk->avail_clk_cnt; + + while (cnt--) + clkdev_drop(dclk->clk[cnt]->lookup); +} + +static int skl_register_parent_clks(struct device *dev, + struct skl_clk_parent *parent, + struct skl_clk_parent_src *pclk) +{ + int i, ret; + + for (i = 0; i < SKL_MAX_CLK_SRC; i++) { + + /* Register Parent clock */ + parent[i].hw = clk_hw_register_fixed_rate(dev, pclk[i].name, + pclk[i].parent_name, 0, pclk[i].rate); + if (IS_ERR(parent[i].hw)) { + ret = PTR_ERR(parent[i].hw); + goto err; + } + + parent[i].lookup = clkdev_hw_create(parent[i].hw, pclk[i].name, + NULL); + if (!parent[i].lookup) { + clk_hw_unregister_fixed_rate(parent[i].hw); + ret = -ENOMEM; + goto err; + } + } + + return 0; +err: + unregister_parent_src_clk(parent, i); + return ret; +} + +/* Assign fmt_config to clk_data */ +static struct skl_clk *register_skl_clk(struct device *dev, + struct skl_ssp_clk *clk, + struct skl_clk_pdata *clk_pdata, int id) +{ + struct clk_init_data init; + struct skl_clk *clkdev; + int ret; + + clkdev = devm_kzalloc(dev, sizeof(*clkdev), GFP_KERNEL); + if (!clkdev) + return ERR_PTR(-ENOMEM); + + init.name = clk->name; + init.ops = &skl_clk_ops; + init.flags = CLK_SET_RATE_GATE; + init.parent_names = &clk->parent_name; + init.num_parents = 1; + clkdev->hw.init = &init; + clkdev->pdata = clk_pdata; + + clkdev->id = id; + ret = devm_clk_hw_register(dev, &clkdev->hw); + if (ret) { + clkdev = ERR_PTR(ret); + return clkdev; + } + + clkdev->lookup = clkdev_hw_create(&clkdev->hw, init.name, NULL); + if (!clkdev->lookup) + clkdev = ERR_PTR(-ENOMEM); + + return clkdev; +} + +static int skl_clk_dev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *parent_dev = dev->parent; + struct skl_clk_parent_src *parent_clks; + struct skl_clk_pdata *clk_pdata; + struct skl_clk_data *data; + struct skl_ssp_clk *clks; + int ret, i; + + clk_pdata = dev_get_platdata(&pdev->dev); + parent_clks = clk_pdata->parent_clks; + clks = clk_pdata->ssp_clks; + if (!parent_clks || !clks) + return -EIO; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Register Parent clock */ + ret = skl_register_parent_clks(parent_dev, data->parent, parent_clks); + if (ret < 0) + return ret; + + for (i = 0; i < clk_pdata->num_clks; i++) { + /* + * Only register valid clocks + * i.e. for which nhlt entry is present. + */ + if (clks[i].rate_cfg[0].rate == 0) + continue; + + data->clk[i] = register_skl_clk(dev, &clks[i], clk_pdata, i); + if (IS_ERR(data->clk[i])) { + ret = PTR_ERR(data->clk[i]); + goto err_unreg_skl_clk; + } + + data->avail_clk_cnt++; + } + + platform_set_drvdata(pdev, data); + + return 0; + +err_unreg_skl_clk: + unregister_src_clk(data); + unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC); + + return ret; +} + +static int skl_clk_dev_remove(struct platform_device *pdev) +{ + struct skl_clk_data *data; + + data = platform_get_drvdata(pdev); + unregister_src_clk(data); + unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC); + + return 0; +} + +static struct platform_driver skl_clk_driver = { + .driver = { + .name = "skl-ssp-clk", + }, + .probe = skl_clk_dev_probe, + .remove = skl_clk_dev_remove, +}; + +module_platform_driver(skl_clk_driver); + +MODULE_DESCRIPTION("Skylake clock driver"); +MODULE_AUTHOR("Jaikrishna Nemallapudi "); +MODULE_AUTHOR("Subhransu S. Prusty "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:skl-ssp-clk"); diff --git a/sound/soc/intel/skylake/skl-ssp-clk.h b/sound/soc/intel/skylake/skl-ssp-clk.h new file mode 100644 index 000000000..d1be50f96 --- /dev/null +++ b/sound/soc/intel/skylake/skl-ssp-clk.h @@ -0,0 +1,117 @@ +/* + * skl-ssp-clk.h - Skylake ssp clock information and ipc structure + * + * Copyright (C) 2017 Intel Corp + * Author: Jaikrishna Nemallapudi + * Author: Subhransu S. Prusty + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef SOUND_SOC_SKL_SSP_CLK_H +#define SOUND_SOC_SKL_SSP_CLK_H + +#define SKL_MAX_SSP 6 +/* xtal/cardinal/pll, parent of ssp clocks and mclk */ +#define SKL_MAX_CLK_SRC 3 +#define SKL_MAX_SSP_CLK_TYPES 3 /* mclk, sclk, sclkfs */ + +#define SKL_MAX_CLK_CNT (SKL_MAX_SSP * SKL_MAX_SSP_CLK_TYPES) + +/* Max number of configurations supported for each clock */ +#define SKL_MAX_CLK_RATES 10 + +#define SKL_SCLK_OFS SKL_MAX_SSP +#define SKL_SCLKFS_OFS (SKL_SCLK_OFS + SKL_MAX_SSP) + +enum skl_clk_type { + SKL_MCLK, + SKL_SCLK, + SKL_SCLK_FS, +}; + +enum skl_clk_src_type { + SKL_XTAL, + SKL_CARDINAL, + SKL_PLL, +}; + +struct skl_clk_parent_src { + u8 clk_id; + const char *name; + unsigned long rate; + const char *parent_name; +}; + +struct skl_tlv_hdr { + u32 type; + u32 size; +}; + +struct skl_dmactrl_mclk_cfg { + struct skl_tlv_hdr hdr; + /* DMA Clk TLV params */ + u32 clk_warm_up:16; + u32 mclk:1; + u32 warm_up_over:1; + u32 rsvd0:14; + u32 clk_stop_delay:16; + u32 keep_running:1; + u32 clk_stop_over:1; + u32 rsvd1:14; +}; + +struct skl_dmactrl_sclkfs_cfg { + struct skl_tlv_hdr hdr; + /* DMA SClk&FS TLV params */ + u32 sampling_frequency; + u32 bit_depth; + u32 channel_map; + u32 channel_config; + u32 interleaving_style; + u32 number_of_channels : 8; + u32 valid_bit_depth : 8; + u32 sample_type : 8; + u32 reserved : 8; +}; + +union skl_clk_ctrl_ipc { + struct skl_dmactrl_mclk_cfg mclk; + struct skl_dmactrl_sclkfs_cfg sclk_fs; +}; + +struct skl_clk_rate_cfg_table { + unsigned long rate; + union skl_clk_ctrl_ipc dma_ctl_ipc; + void *config; +}; + +/* + * rate for mclk will be in rates[0]. For sclk and sclkfs, rates[] store + * all possible clocks ssp can generate for that platform. + */ +struct skl_ssp_clk { + const char *name; + const char *parent_name; + struct skl_clk_rate_cfg_table rate_cfg[SKL_MAX_CLK_RATES]; +}; + +struct skl_clk_pdata { + struct skl_clk_parent_src *parent_clks; + int num_clks; + struct skl_ssp_clk *ssp_clks; + void *pvt_data; +}; + +#endif /* SOUND_SOC_SKL_SSP_CLK_H */ diff --git a/sound/soc/intel/skylake/skl-sst-cldma.c b/sound/soc/intel/skylake/skl-sst-cldma.c new file mode 100644 index 000000000..5bc0d38da --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-cldma.c @@ -0,0 +1,375 @@ +/* + * skl-sst-cldma.c - Code Loader DMA handler + * + * Copyright (C) 2015, Intel Corporation. + * Author: Subhransu S. Prusty + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" + +static void skl_cldma_int_enable(struct sst_dsp *ctx) +{ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPIC, + SKL_ADSPIC_CL_DMA, SKL_ADSPIC_CL_DMA); +} + +void skl_cldma_int_disable(struct sst_dsp *ctx) +{ + sst_dsp_shim_update_bits_unlocked(ctx, + SKL_ADSP_REG_ADSPIC, SKL_ADSPIC_CL_DMA, 0); +} + +static void skl_cldma_stream_run(struct sst_dsp *ctx, bool enable) +{ + unsigned char val; + int timeout; + + sst_dsp_shim_update_bits_unlocked(ctx, + SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_RUN_MASK, CL_SD_CTL_RUN(enable)); + + udelay(3); + timeout = 300; + do { + /* waiting for hardware to report that the stream Run bit set */ + val = sst_dsp_shim_read(ctx, SKL_ADSP_REG_CL_SD_CTL) & + CL_SD_CTL_RUN_MASK; + if (enable && val) + break; + else if (!enable && !val) + break; + udelay(3); + } while (--timeout); + + if (timeout == 0) + dev_err(ctx->dev, "Failed to set Run bit=%d enable=%d\n", val, enable); +} + +static void skl_cldma_stream_clear(struct sst_dsp *ctx) +{ + /* make sure Run bit is cleared before setting stream register */ + skl_cldma_stream_run(ctx, 0); + + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_IOCE_MASK, CL_SD_CTL_IOCE(0)); + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_FEIE_MASK, CL_SD_CTL_FEIE(0)); + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_DEIE_MASK, CL_SD_CTL_DEIE(0)); + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_STRM_MASK, CL_SD_CTL_STRM(0)); + + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPL, CL_SD_BDLPLBA(0)); + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPU, 0); + + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_CBL, 0); + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_LVI, 0); +} + +/* Code loader helper APIs */ +static void skl_cldma_setup_bdle(struct sst_dsp *ctx, + struct snd_dma_buffer *dmab_data, + __le32 **bdlp, int size, int with_ioc) +{ + __le32 *bdl = *bdlp; + + ctx->cl_dev.frags = 0; + while (size > 0) { + phys_addr_t addr = virt_to_phys(dmab_data->area + + (ctx->cl_dev.frags * ctx->cl_dev.bufsize)); + + bdl[0] = cpu_to_le32(lower_32_bits(addr)); + bdl[1] = cpu_to_le32(upper_32_bits(addr)); + + bdl[2] = cpu_to_le32(ctx->cl_dev.bufsize); + + size -= ctx->cl_dev.bufsize; + bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01); + + bdl += 4; + ctx->cl_dev.frags++; + } +} + +/* + * Setup controller + * Configure the registers to update the dma buffer address and + * enable interrupts. + * Note: Using the channel 1 for transfer + */ +static void skl_cldma_setup_controller(struct sst_dsp *ctx, + struct snd_dma_buffer *dmab_bdl, unsigned int max_size, + u32 count) +{ + skl_cldma_stream_clear(ctx); + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPL, + CL_SD_BDLPLBA(dmab_bdl->addr)); + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_BDLPU, + CL_SD_BDLPUBA(dmab_bdl->addr)); + + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_CBL, max_size); + sst_dsp_shim_write(ctx, SKL_ADSP_REG_CL_SD_LVI, count - 1); + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_IOCE_MASK, CL_SD_CTL_IOCE(1)); + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_FEIE_MASK, CL_SD_CTL_FEIE(1)); + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_DEIE_MASK, CL_SD_CTL_DEIE(1)); + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_CL_SD_CTL, + CL_SD_CTL_STRM_MASK, CL_SD_CTL_STRM(FW_CL_STREAM_NUMBER)); +} + +static void skl_cldma_setup_spb(struct sst_dsp *ctx, + unsigned int size, bool enable) +{ + if (enable) + sst_dsp_shim_update_bits_unlocked(ctx, + SKL_ADSP_REG_CL_SPBFIFO_SPBFCCTL, + CL_SPBFIFO_SPBFCCTL_SPIBE_MASK, + CL_SPBFIFO_SPBFCCTL_SPIBE(1)); + + sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_CL_SPBFIFO_SPIB, size); +} + +static void skl_cldma_cleanup_spb(struct sst_dsp *ctx) +{ + sst_dsp_shim_update_bits_unlocked(ctx, + SKL_ADSP_REG_CL_SPBFIFO_SPBFCCTL, + CL_SPBFIFO_SPBFCCTL_SPIBE_MASK, + CL_SPBFIFO_SPBFCCTL_SPIBE(0)); + + sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_CL_SPBFIFO_SPIB, 0); +} + +static void skl_cldma_cleanup(struct sst_dsp *ctx) +{ + skl_cldma_cleanup_spb(ctx); + skl_cldma_stream_clear(ctx); + + ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_data); + ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_bdl); +} + +int skl_cldma_wait_interruptible(struct sst_dsp *ctx) +{ + int ret = 0; + + if (!wait_event_timeout(ctx->cl_dev.wait_queue, + ctx->cl_dev.wait_condition, + msecs_to_jiffies(SKL_WAIT_TIMEOUT))) { + dev_err(ctx->dev, "%s: Wait timeout\n", __func__); + ret = -EIO; + goto cleanup; + } + + dev_dbg(ctx->dev, "%s: Event wake\n", __func__); + if (ctx->cl_dev.wake_status != SKL_CL_DMA_BUF_COMPLETE) { + dev_err(ctx->dev, "%s: DMA Error\n", __func__); + ret = -EIO; + } + +cleanup: + ctx->cl_dev.wake_status = SKL_CL_DMA_STATUS_NONE; + return ret; +} + +static void skl_cldma_stop(struct sst_dsp *ctx) +{ + skl_cldma_stream_run(ctx, false); +} + +static void skl_cldma_fill_buffer(struct sst_dsp *ctx, unsigned int size, + const void *curr_pos, bool intr_enable, bool trigger) +{ + dev_dbg(ctx->dev, "Size: %x, intr_enable: %d\n", size, intr_enable); + dev_dbg(ctx->dev, "buf_pos_index:%d, trigger:%d\n", + ctx->cl_dev.dma_buffer_offset, trigger); + dev_dbg(ctx->dev, "spib position: %d\n", ctx->cl_dev.curr_spib_pos); + + /* + * Check if the size exceeds buffer boundary. If it exceeds + * max_buffer size, then copy till buffer size and then copy + * remaining buffer from the start of ring buffer. + */ + if (ctx->cl_dev.dma_buffer_offset + size > ctx->cl_dev.bufsize) { + unsigned int size_b = ctx->cl_dev.bufsize - + ctx->cl_dev.dma_buffer_offset; + memcpy(ctx->cl_dev.dmab_data.area + ctx->cl_dev.dma_buffer_offset, + curr_pos, size_b); + size -= size_b; + curr_pos += size_b; + ctx->cl_dev.dma_buffer_offset = 0; + } + + memcpy(ctx->cl_dev.dmab_data.area + ctx->cl_dev.dma_buffer_offset, + curr_pos, size); + + if (ctx->cl_dev.curr_spib_pos == ctx->cl_dev.bufsize) + ctx->cl_dev.dma_buffer_offset = 0; + else + ctx->cl_dev.dma_buffer_offset = ctx->cl_dev.curr_spib_pos; + + ctx->cl_dev.wait_condition = false; + + if (intr_enable) + skl_cldma_int_enable(ctx); + + ctx->cl_dev.ops.cl_setup_spb(ctx, ctx->cl_dev.curr_spib_pos, trigger); + if (trigger) + ctx->cl_dev.ops.cl_trigger(ctx, true); +} + +/* + * The CL dma doesn't have any way to update the transfer status until a BDL + * buffer is fully transferred + * + * So Copying is divided in two parts. + * 1. Interrupt on buffer done where the size to be transferred is more than + * ring buffer size. + * 2. Polling on fw register to identify if data left to transferred doesn't + * fill the ring buffer. Caller takes care of polling the required status + * register to identify the transfer status. + * 3. if wait flag is set, waits for DBL interrupt to copy the next chunk till + * bytes_left is 0. + * if wait flag is not set, doesn't wait for BDL interrupt. after ccopying + * the first chunk return the no of bytes_left to be copied. + */ +static int +skl_cldma_copy_to_buf(struct sst_dsp *ctx, const void *bin, + u32 total_size, bool wait) +{ + int ret = 0; + bool start = true; + unsigned int excess_bytes; + u32 size; + unsigned int bytes_left = total_size; + const void *curr_pos = bin; + + if (total_size <= 0) + return -EINVAL; + + dev_dbg(ctx->dev, "%s: Total binary size: %u\n", __func__, bytes_left); + + while (bytes_left) { + if (bytes_left > ctx->cl_dev.bufsize) { + + /* + * dma transfers only till the write pointer as + * updated in spib + */ + if (ctx->cl_dev.curr_spib_pos == 0) + ctx->cl_dev.curr_spib_pos = ctx->cl_dev.bufsize; + + size = ctx->cl_dev.bufsize; + skl_cldma_fill_buffer(ctx, size, curr_pos, true, start); + + if (wait) { + start = false; + ret = skl_cldma_wait_interruptible(ctx); + if (ret < 0) { + skl_cldma_stop(ctx); + return ret; + } + } + } else { + skl_cldma_int_disable(ctx); + + if ((ctx->cl_dev.curr_spib_pos + bytes_left) + <= ctx->cl_dev.bufsize) { + ctx->cl_dev.curr_spib_pos += bytes_left; + } else { + excess_bytes = bytes_left - + (ctx->cl_dev.bufsize - + ctx->cl_dev.curr_spib_pos); + ctx->cl_dev.curr_spib_pos = excess_bytes; + } + + size = bytes_left; + skl_cldma_fill_buffer(ctx, size, + curr_pos, false, start); + } + bytes_left -= size; + curr_pos = curr_pos + size; + if (!wait) + return bytes_left; + } + + return bytes_left; +} + +void skl_cldma_process_intr(struct sst_dsp *ctx) +{ + u8 cl_dma_intr_status; + + cl_dma_intr_status = + sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_CL_SD_STS); + + if (!(cl_dma_intr_status & SKL_CL_DMA_SD_INT_COMPLETE)) + ctx->cl_dev.wake_status = SKL_CL_DMA_ERR; + else + ctx->cl_dev.wake_status = SKL_CL_DMA_BUF_COMPLETE; + + ctx->cl_dev.wait_condition = true; + wake_up(&ctx->cl_dev.wait_queue); +} + +int skl_cldma_prepare(struct sst_dsp *ctx) +{ + int ret; + __le32 *bdl; + + ctx->cl_dev.bufsize = SKL_MAX_BUFFER_SIZE; + + /* Allocate cl ops */ + ctx->cl_dev.ops.cl_setup_bdle = skl_cldma_setup_bdle; + ctx->cl_dev.ops.cl_setup_controller = skl_cldma_setup_controller; + ctx->cl_dev.ops.cl_setup_spb = skl_cldma_setup_spb; + ctx->cl_dev.ops.cl_cleanup_spb = skl_cldma_cleanup_spb; + ctx->cl_dev.ops.cl_trigger = skl_cldma_stream_run; + ctx->cl_dev.ops.cl_cleanup_controller = skl_cldma_cleanup; + ctx->cl_dev.ops.cl_copy_to_dmabuf = skl_cldma_copy_to_buf; + ctx->cl_dev.ops.cl_stop_dma = skl_cldma_stop; + + /* Allocate buffer*/ + ret = ctx->dsp_ops.alloc_dma_buf(ctx->dev, + &ctx->cl_dev.dmab_data, ctx->cl_dev.bufsize); + if (ret < 0) { + dev_err(ctx->dev, "Alloc buffer for base fw failed: %x\n", ret); + return ret; + } + /* Setup Code loader BDL */ + ret = ctx->dsp_ops.alloc_dma_buf(ctx->dev, + &ctx->cl_dev.dmab_bdl, PAGE_SIZE); + if (ret < 0) { + dev_err(ctx->dev, "Alloc buffer for blde failed: %x\n", ret); + ctx->dsp_ops.free_dma_buf(ctx->dev, &ctx->cl_dev.dmab_data); + return ret; + } + bdl = (__le32 *)ctx->cl_dev.dmab_bdl.area; + + /* Allocate BDLs */ + ctx->cl_dev.ops.cl_setup_bdle(ctx, &ctx->cl_dev.dmab_data, + &bdl, ctx->cl_dev.bufsize, 1); + ctx->cl_dev.ops.cl_setup_controller(ctx, &ctx->cl_dev.dmab_bdl, + ctx->cl_dev.bufsize, ctx->cl_dev.frags); + + ctx->cl_dev.curr_spib_pos = 0; + ctx->cl_dev.dma_buffer_offset = 0; + init_waitqueue_head(&ctx->cl_dev.wait_queue); + + return ret; +} diff --git a/sound/soc/intel/skylake/skl-sst-cldma.h b/sound/soc/intel/skylake/skl-sst-cldma.h new file mode 100644 index 000000000..ec736921a --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-cldma.h @@ -0,0 +1,251 @@ +/* + * Intel Code Loader DMA support + * + * Copyright (C) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef SKL_SST_CLDMA_H_ +#define SKL_SST_CLDMA_H_ + +#define FW_CL_STREAM_NUMBER 0x1 + +#define DMA_ADDRESS_128_BITS_ALIGNMENT 7 +#define BDL_ALIGN(x) (x >> DMA_ADDRESS_128_BITS_ALIGNMENT) + +#define SKL_ADSPIC_CL_DMA 0x2 +#define SKL_ADSPIS_CL_DMA 0x2 +#define SKL_CL_DMA_SD_INT_DESC_ERR 0x10 /* Descriptor error interrupt */ +#define SKL_CL_DMA_SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */ +#define SKL_CL_DMA_SD_INT_COMPLETE 0x04 /* Buffer completion interrupt */ + +/* Intel HD Audio Code Loader DMA Registers */ + +#define HDA_ADSP_LOADER_BASE 0x80 + +/* Stream Registers */ +#define SKL_ADSP_REG_CL_SD_CTL (HDA_ADSP_LOADER_BASE + 0x00) +#define SKL_ADSP_REG_CL_SD_STS (HDA_ADSP_LOADER_BASE + 0x03) +#define SKL_ADSP_REG_CL_SD_LPIB (HDA_ADSP_LOADER_BASE + 0x04) +#define SKL_ADSP_REG_CL_SD_CBL (HDA_ADSP_LOADER_BASE + 0x08) +#define SKL_ADSP_REG_CL_SD_LVI (HDA_ADSP_LOADER_BASE + 0x0c) +#define SKL_ADSP_REG_CL_SD_FIFOW (HDA_ADSP_LOADER_BASE + 0x0e) +#define SKL_ADSP_REG_CL_SD_FIFOSIZE (HDA_ADSP_LOADER_BASE + 0x10) +#define SKL_ADSP_REG_CL_SD_FORMAT (HDA_ADSP_LOADER_BASE + 0x12) +#define SKL_ADSP_REG_CL_SD_FIFOL (HDA_ADSP_LOADER_BASE + 0x14) +#define SKL_ADSP_REG_CL_SD_BDLPL (HDA_ADSP_LOADER_BASE + 0x18) +#define SKL_ADSP_REG_CL_SD_BDLPU (HDA_ADSP_LOADER_BASE + 0x1c) + +/* CL: Software Position Based FIFO Capability Registers */ +#define SKL_ADSP_REG_CL_SPBFIFO (HDA_ADSP_LOADER_BASE + 0x20) +#define SKL_ADSP_REG_CL_SPBFIFO_SPBFCH (SKL_ADSP_REG_CL_SPBFIFO + 0x0) +#define SKL_ADSP_REG_CL_SPBFIFO_SPBFCCTL (SKL_ADSP_REG_CL_SPBFIFO + 0x4) +#define SKL_ADSP_REG_CL_SPBFIFO_SPIB (SKL_ADSP_REG_CL_SPBFIFO + 0x8) +#define SKL_ADSP_REG_CL_SPBFIFO_MAXFIFOS (SKL_ADSP_REG_CL_SPBFIFO + 0xc) + +/* CL: Stream Descriptor x Control */ + +/* Stream Reset */ +#define CL_SD_CTL_SRST_SHIFT 0 +#define CL_SD_CTL_SRST_MASK (1 << CL_SD_CTL_SRST_SHIFT) +#define CL_SD_CTL_SRST(x) \ + ((x << CL_SD_CTL_SRST_SHIFT) & CL_SD_CTL_SRST_MASK) + +/* Stream Run */ +#define CL_SD_CTL_RUN_SHIFT 1 +#define CL_SD_CTL_RUN_MASK (1 << CL_SD_CTL_RUN_SHIFT) +#define CL_SD_CTL_RUN(x) \ + ((x << CL_SD_CTL_RUN_SHIFT) & CL_SD_CTL_RUN_MASK) + +/* Interrupt On Completion Enable */ +#define CL_SD_CTL_IOCE_SHIFT 2 +#define CL_SD_CTL_IOCE_MASK (1 << CL_SD_CTL_IOCE_SHIFT) +#define CL_SD_CTL_IOCE(x) \ + ((x << CL_SD_CTL_IOCE_SHIFT) & CL_SD_CTL_IOCE_MASK) + +/* FIFO Error Interrupt Enable */ +#define CL_SD_CTL_FEIE_SHIFT 3 +#define CL_SD_CTL_FEIE_MASK (1 << CL_SD_CTL_FEIE_SHIFT) +#define CL_SD_CTL_FEIE(x) \ + ((x << CL_SD_CTL_FEIE_SHIFT) & CL_SD_CTL_FEIE_MASK) + +/* Descriptor Error Interrupt Enable */ +#define CL_SD_CTL_DEIE_SHIFT 4 +#define CL_SD_CTL_DEIE_MASK (1 << CL_SD_CTL_DEIE_SHIFT) +#define CL_SD_CTL_DEIE(x) \ + ((x << CL_SD_CTL_DEIE_SHIFT) & CL_SD_CTL_DEIE_MASK) + +/* FIFO Limit Change */ +#define CL_SD_CTL_FIFOLC_SHIFT 5 +#define CL_SD_CTL_FIFOLC_MASK (1 << CL_SD_CTL_FIFOLC_SHIFT) +#define CL_SD_CTL_FIFOLC(x) \ + ((x << CL_SD_CTL_FIFOLC_SHIFT) & CL_SD_CTL_FIFOLC_MASK) + +/* Stripe Control */ +#define CL_SD_CTL_STRIPE_SHIFT 16 +#define CL_SD_CTL_STRIPE_MASK (0x3 << CL_SD_CTL_STRIPE_SHIFT) +#define CL_SD_CTL_STRIPE(x) \ + ((x << CL_SD_CTL_STRIPE_SHIFT) & CL_SD_CTL_STRIPE_MASK) + +/* Traffic Priority */ +#define CL_SD_CTL_TP_SHIFT 18 +#define CL_SD_CTL_TP_MASK (1 << CL_SD_CTL_TP_SHIFT) +#define CL_SD_CTL_TP(x) \ + ((x << CL_SD_CTL_TP_SHIFT) & CL_SD_CTL_TP_MASK) + +/* Bidirectional Direction Control */ +#define CL_SD_CTL_DIR_SHIFT 19 +#define CL_SD_CTL_DIR_MASK (1 << CL_SD_CTL_DIR_SHIFT) +#define CL_SD_CTL_DIR(x) \ + ((x << CL_SD_CTL_DIR_SHIFT) & CL_SD_CTL_DIR_MASK) + +/* Stream Number */ +#define CL_SD_CTL_STRM_SHIFT 20 +#define CL_SD_CTL_STRM_MASK (0xf << CL_SD_CTL_STRM_SHIFT) +#define CL_SD_CTL_STRM(x) \ + ((x << CL_SD_CTL_STRM_SHIFT) & CL_SD_CTL_STRM_MASK) + +/* CL: Stream Descriptor x Status */ + +/* Buffer Completion Interrupt Status */ +#define CL_SD_STS_BCIS(x) CL_SD_CTL_IOCE(x) + +/* FIFO Error */ +#define CL_SD_STS_FIFOE(x) CL_SD_CTL_FEIE(x) + +/* Descriptor Error */ +#define CL_SD_STS_DESE(x) CL_SD_CTL_DEIE(x) + +/* FIFO Ready */ +#define CL_SD_STS_FIFORDY(x) CL_SD_CTL_FIFOLC(x) + + +/* CL: Stream Descriptor x Last Valid Index */ +#define CL_SD_LVI_SHIFT 0 +#define CL_SD_LVI_MASK (0xff << CL_SD_LVI_SHIFT) +#define CL_SD_LVI(x) ((x << CL_SD_LVI_SHIFT) & CL_SD_LVI_MASK) + +/* CL: Stream Descriptor x FIFO Eviction Watermark */ +#define CL_SD_FIFOW_SHIFT 0 +#define CL_SD_FIFOW_MASK (0x7 << CL_SD_FIFOW_SHIFT) +#define CL_SD_FIFOW(x) \ + ((x << CL_SD_FIFOW_SHIFT) & CL_SD_FIFOW_MASK) + +/* CL: Stream Descriptor x Buffer Descriptor List Pointer Lower Base Address */ + +/* Protect Bits */ +#define CL_SD_BDLPLBA_PROT_SHIFT 0 +#define CL_SD_BDLPLBA_PROT_MASK (1 << CL_SD_BDLPLBA_PROT_SHIFT) +#define CL_SD_BDLPLBA_PROT(x) \ + ((x << CL_SD_BDLPLBA_PROT_SHIFT) & CL_SD_BDLPLBA_PROT_MASK) + +/* Buffer Descriptor List Lower Base Address */ +#define CL_SD_BDLPLBA_SHIFT 7 +#define CL_SD_BDLPLBA_MASK (0x1ffffff << CL_SD_BDLPLBA_SHIFT) +#define CL_SD_BDLPLBA(x) \ + ((BDL_ALIGN(lower_32_bits(x)) << CL_SD_BDLPLBA_SHIFT) & CL_SD_BDLPLBA_MASK) + +/* Buffer Descriptor List Upper Base Address */ +#define CL_SD_BDLPUBA_SHIFT 0 +#define CL_SD_BDLPUBA_MASK (0xffffffff << CL_SD_BDLPUBA_SHIFT) +#define CL_SD_BDLPUBA(x) \ + ((upper_32_bits(x) << CL_SD_BDLPUBA_SHIFT) & CL_SD_BDLPUBA_MASK) + +/* + * Code Loader - Software Position Based FIFO + * Capability Registers x Software Position Based FIFO Header + */ + +/* Next Capability Pointer */ +#define CL_SPBFIFO_SPBFCH_PTR_SHIFT 0 +#define CL_SPBFIFO_SPBFCH_PTR_MASK (0xff << CL_SPBFIFO_SPBFCH_PTR_SHIFT) +#define CL_SPBFIFO_SPBFCH_PTR(x) \ + ((x << CL_SPBFIFO_SPBFCH_PTR_SHIFT) & CL_SPBFIFO_SPBFCH_PTR_MASK) + +/* Capability Identifier */ +#define CL_SPBFIFO_SPBFCH_ID_SHIFT 16 +#define CL_SPBFIFO_SPBFCH_ID_MASK (0xfff << CL_SPBFIFO_SPBFCH_ID_SHIFT) +#define CL_SPBFIFO_SPBFCH_ID(x) \ + ((x << CL_SPBFIFO_SPBFCH_ID_SHIFT) & CL_SPBFIFO_SPBFCH_ID_MASK) + +/* Capability Version */ +#define CL_SPBFIFO_SPBFCH_VER_SHIFT 28 +#define CL_SPBFIFO_SPBFCH_VER_MASK (0xf << CL_SPBFIFO_SPBFCH_VER_SHIFT) +#define CL_SPBFIFO_SPBFCH_VER(x) \ + ((x << CL_SPBFIFO_SPBFCH_VER_SHIFT) & CL_SPBFIFO_SPBFCH_VER_MASK) + +/* Software Position in Buffer Enable */ +#define CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT 0 +#define CL_SPBFIFO_SPBFCCTL_SPIBE_MASK (1 << CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT) +#define CL_SPBFIFO_SPBFCCTL_SPIBE(x) \ + ((x << CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT) & CL_SPBFIFO_SPBFCCTL_SPIBE_MASK) + +/* SST IPC SKL defines */ +#define SKL_WAIT_TIMEOUT 500 /* 500 msec */ +#define SKL_MAX_BUFFER_SIZE (32 * PAGE_SIZE) + +enum skl_cl_dma_wake_states { + SKL_CL_DMA_STATUS_NONE = 0, + SKL_CL_DMA_BUF_COMPLETE, + SKL_CL_DMA_ERR, /* TODO: Expand the error states */ +}; + +struct sst_dsp; + +struct skl_cl_dev_ops { + void (*cl_setup_bdle)(struct sst_dsp *ctx, + struct snd_dma_buffer *dmab_data, + __le32 **bdlp, int size, int with_ioc); + void (*cl_setup_controller)(struct sst_dsp *ctx, + struct snd_dma_buffer *dmab_bdl, + unsigned int max_size, u32 page_count); + void (*cl_setup_spb)(struct sst_dsp *ctx, + unsigned int size, bool enable); + void (*cl_cleanup_spb)(struct sst_dsp *ctx); + void (*cl_trigger)(struct sst_dsp *ctx, bool enable); + void (*cl_cleanup_controller)(struct sst_dsp *ctx); + int (*cl_copy_to_dmabuf)(struct sst_dsp *ctx, + const void *bin, u32 size, bool wait); + void (*cl_stop_dma)(struct sst_dsp *ctx); +}; + +/** + * skl_cl_dev - holds information for code loader dma transfer + * + * @dmab_data: buffer pointer + * @dmab_bdl: buffer descriptor list + * @bufsize: ring buffer size + * @frags: Last valid buffer descriptor index in the BDL + * @curr_spib_pos: Current position in ring buffer + * @dma_buffer_offset: dma buffer offset + * @ops: operations supported on CL dma + * @wait_queue: wait queue to wake for wake event + * @wake_status: DMA wake status + * @wait_condition: condition to wait on wait queue + * @cl_dma_lock: for synchronized access to cldma + */ +struct skl_cl_dev { + struct snd_dma_buffer dmab_data; + struct snd_dma_buffer dmab_bdl; + + unsigned int bufsize; + unsigned int frags; + + unsigned int curr_spib_pos; + unsigned int dma_buffer_offset; + struct skl_cl_dev_ops ops; + + wait_queue_head_t wait_queue; + int wake_status; + bool wait_condition; +}; + +#endif /* SKL_SST_CLDMA_H_ */ diff --git a/sound/soc/intel/skylake/skl-sst-dsp.c b/sound/soc/intel/skylake/skl-sst-dsp.c new file mode 100644 index 000000000..71e31ad0b --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-dsp.c @@ -0,0 +1,470 @@ +/* + * skl-sst-dsp.c - SKL SST library generic function + * + * Copyright (C) 2014-15, Intel Corporation. + * Author:Rafal Redzimski + * Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-ipc.h" +#include "../common/sst-dsp-priv.h" +#include "skl-sst-ipc.h" + +/* various timeout values */ +#define SKL_DSP_PU_TO 50 +#define SKL_DSP_PD_TO 50 +#define SKL_DSP_RESET_TO 50 + +void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state) +{ + mutex_lock(&ctx->mutex); + ctx->sst_state = state; + mutex_unlock(&ctx->mutex); +} + +/* + * Initialize core power state and usage count. To be called after + * successful first boot. Hence core 0 will be running and other cores + * will be reset + */ +void skl_dsp_init_core_state(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + int i; + + skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING; + skl->cores.usage_count[SKL_DSP_CORE0_ID] = 1; + + for (i = SKL_DSP_CORE0_ID + 1; i < skl->cores.count; i++) { + skl->cores.state[i] = SKL_DSP_RESET; + skl->cores.usage_count[i] = 0; + } +} + +/* Get the mask for all enabled cores */ +unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask, en_cores_mask; + u32 val; + + core_mask = SKL_DSP_CORES_MASK(skl->cores.count); + + val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS); + + /* Cores having CPA bit set */ + en_cores_mask = (val & SKL_ADSPCS_CPA_MASK(core_mask)) >> + SKL_ADSPCS_CPA_SHIFT; + + /* And cores having CRST bit cleared */ + en_cores_mask &= (~val & SKL_ADSPCS_CRST_MASK(core_mask)) >> + SKL_ADSPCS_CRST_SHIFT; + + /* And cores having CSTALL bit cleared */ + en_cores_mask &= (~val & SKL_ADSPCS_CSTALL_MASK(core_mask)) >> + SKL_ADSPCS_CSTALL_SHIFT; + en_cores_mask &= core_mask; + + dev_dbg(ctx->dev, "DSP enabled cores mask = %x\n", en_cores_mask); + + return en_cores_mask; +} + +static int +skl_dsp_core_set_reset_state(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, + SKL_ADSP_REG_ADSPCS, SKL_ADSPCS_CRST_MASK(core_mask), + SKL_ADSPCS_CRST_MASK(core_mask)); + + /* poll with timeout to check if operation successful */ + ret = sst_dsp_register_poll(ctx, + SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CRST_MASK(core_mask), + SKL_ADSPCS_CRST_MASK(core_mask), + SKL_DSP_RESET_TO, + "Set reset"); + if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & + SKL_ADSPCS_CRST_MASK(core_mask)) != + SKL_ADSPCS_CRST_MASK(core_mask)) { + dev_err(ctx->dev, "Set reset state failed: core_mask %x\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +int skl_dsp_core_unset_reset_state( + struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + dev_dbg(ctx->dev, "In %s\n", __func__); + + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CRST_MASK(core_mask), 0); + + /* poll with timeout to check if operation successful */ + ret = sst_dsp_register_poll(ctx, + SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CRST_MASK(core_mask), + 0, + SKL_DSP_RESET_TO, + "Unset reset"); + + if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & + SKL_ADSPCS_CRST_MASK(core_mask)) != 0) { + dev_err(ctx->dev, "Unset reset state failed: core_mask %x\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +static bool +is_skl_dsp_core_enable(struct sst_dsp *ctx, unsigned int core_mask) +{ + int val; + bool is_enable; + + val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS); + + is_enable = ((val & SKL_ADSPCS_CPA_MASK(core_mask)) && + (val & SKL_ADSPCS_SPA_MASK(core_mask)) && + !(val & SKL_ADSPCS_CRST_MASK(core_mask)) && + !(val & SKL_ADSPCS_CSTALL_MASK(core_mask))); + + dev_dbg(ctx->dev, "DSP core(s) enabled? %d : core_mask %x\n", + is_enable, core_mask); + + return is_enable; +} + +static int skl_dsp_reset_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + /* stall core */ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CSTALL_MASK(core_mask), + SKL_ADSPCS_CSTALL_MASK(core_mask)); + + /* set reset state */ + return skl_dsp_core_set_reset_state(ctx, core_mask); +} + +int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + /* unset reset state */ + ret = skl_dsp_core_unset_reset_state(ctx, core_mask); + if (ret < 0) + return ret; + + /* run core */ + dev_dbg(ctx->dev, "unstall/run core: core_mask = %x\n", core_mask); + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CSTALL_MASK(core_mask), 0); + + if (!is_skl_dsp_core_enable(ctx, core_mask)) { + skl_dsp_reset_core(ctx, core_mask); + dev_err(ctx->dev, "DSP start core failed: core_mask %x\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_SPA_MASK(core_mask), + SKL_ADSPCS_SPA_MASK(core_mask)); + + /* poll with timeout to check if operation successful */ + ret = sst_dsp_register_poll(ctx, + SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CPA_MASK(core_mask), + SKL_ADSPCS_CPA_MASK(core_mask), + SKL_DSP_PU_TO, + "Power up"); + + if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & + SKL_ADSPCS_CPA_MASK(core_mask)) != + SKL_ADSPCS_CPA_MASK(core_mask)) { + dev_err(ctx->dev, "DSP core power up failed: core_mask %x\n", + core_mask); + ret = -EIO; + } + + return ret; +} + +int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask) +{ + /* update bits */ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_SPA_MASK(core_mask), 0); + + /* poll with timeout to check if operation successful */ + return sst_dsp_register_poll(ctx, + SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CPA_MASK(core_mask), + 0, + SKL_DSP_PD_TO, + "Power down"); +} + +int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + /* power up */ + ret = skl_dsp_core_power_up(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "dsp core power up failed: core_mask %x\n", + core_mask); + return ret; + } + + return skl_dsp_start_core(ctx, core_mask); +} + +int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask) +{ + int ret; + + ret = skl_dsp_reset_core(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "dsp core reset failed: core_mask %x\n", + core_mask); + return ret; + } + + /* power down core*/ + ret = skl_dsp_core_power_down(ctx, core_mask); + if (ret < 0) { + dev_err(ctx->dev, "dsp core power down fail mask %x: %d\n", + core_mask, ret); + return ret; + } + + if (is_skl_dsp_core_enable(ctx, core_mask)) { + dev_err(ctx->dev, "dsp core disable fail mask %x: %d\n", + core_mask, ret); + ret = -EIO; + } + + return ret; +} + +int skl_dsp_boot(struct sst_dsp *ctx) +{ + int ret; + + if (is_skl_dsp_core_enable(ctx, SKL_DSP_CORE0_MASK)) { + ret = skl_dsp_reset_core(ctx, SKL_DSP_CORE0_MASK); + if (ret < 0) { + dev_err(ctx->dev, "dsp core0 reset fail: %d\n", ret); + return ret; + } + + ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK); + if (ret < 0) { + dev_err(ctx->dev, "dsp core0 start fail: %d\n", ret); + return ret; + } + } else { + ret = skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + if (ret < 0) { + dev_err(ctx->dev, "dsp core0 disable fail: %d\n", ret); + return ret; + } + ret = skl_dsp_enable_core(ctx, SKL_DSP_CORE0_MASK); + } + + return ret; +} + +irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id) +{ + struct sst_dsp *ctx = dev_id; + u32 val; + irqreturn_t result = IRQ_NONE; + + spin_lock(&ctx->spinlock); + + val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPIS); + ctx->intr_status = val; + + if (val == 0xffffffff) { + spin_unlock(&ctx->spinlock); + return IRQ_NONE; + } + + if (val & SKL_ADSPIS_IPC) { + skl_ipc_int_disable(ctx); + result = IRQ_WAKE_THREAD; + } + + if (val & SKL_ADSPIS_CL_DMA) { + skl_cldma_int_disable(ctx); + result = IRQ_WAKE_THREAD; + } + + spin_unlock(&ctx->spinlock); + + return result; +} +/* + * skl_dsp_get_core/skl_dsp_put_core will be called inside DAPM context + * within the dapm mutex. Hence no separate lock is used. + */ +int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *skl = ctx->thread_context; + int ret = 0; + + if (core_id >= skl->cores.count) { + dev_err(ctx->dev, "invalid core id: %d\n", core_id); + return -EINVAL; + } + + skl->cores.usage_count[core_id]++; + + if (skl->cores.state[core_id] == SKL_DSP_RESET) { + ret = ctx->fw_ops.set_state_D0(ctx, core_id); + if (ret < 0) { + dev_err(ctx->dev, "unable to get core%d\n", core_id); + goto out; + } + } + +out: + dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", + core_id, skl->cores.state[core_id], + skl->cores.usage_count[core_id]); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_dsp_get_core); + +int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *skl = ctx->thread_context; + int ret = 0; + + if (core_id >= skl->cores.count) { + dev_err(ctx->dev, "invalid core id: %d\n", core_id); + return -EINVAL; + } + + if ((--skl->cores.usage_count[core_id] == 0) && + (skl->cores.state[core_id] != SKL_DSP_RESET)) { + ret = ctx->fw_ops.set_state_D3(ctx, core_id); + if (ret < 0) { + dev_err(ctx->dev, "unable to put core %d: %d\n", + core_id, ret); + skl->cores.usage_count[core_id]++; + } + } + + dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", + core_id, skl->cores.state[core_id], + skl->cores.usage_count[core_id]); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_dsp_put_core); + +int skl_dsp_wake(struct sst_dsp *ctx) +{ + return skl_dsp_get_core(ctx, SKL_DSP_CORE0_ID); +} +EXPORT_SYMBOL_GPL(skl_dsp_wake); + +int skl_dsp_sleep(struct sst_dsp *ctx) +{ + return skl_dsp_put_core(ctx, SKL_DSP_CORE0_ID); +} +EXPORT_SYMBOL_GPL(skl_dsp_sleep); + +struct sst_dsp *skl_dsp_ctx_init(struct device *dev, + struct sst_dsp_device *sst_dev, int irq) +{ + int ret; + struct sst_dsp *sst; + + sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); + if (sst == NULL) + return NULL; + + spin_lock_init(&sst->spinlock); + mutex_init(&sst->mutex); + sst->dev = dev; + sst->sst_dev = sst_dev; + sst->irq = irq; + sst->ops = sst_dev->ops; + sst->thread_context = sst_dev->thread_context; + + /* Initialise SST Audio DSP */ + if (sst->ops->init) { + ret = sst->ops->init(sst, NULL); + if (ret < 0) + return NULL; + } + + return sst; +} + +int skl_dsp_acquire_irq(struct sst_dsp *sst) +{ + struct sst_dsp_device *sst_dev = sst->sst_dev; + int ret; + + /* Register the ISR */ + ret = request_threaded_irq(sst->irq, sst->ops->irq_handler, + sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); + if (ret) + dev_err(sst->dev, "unable to grab threaded IRQ %d, disabling device\n", + sst->irq); + + return ret; +} + +void skl_dsp_free(struct sst_dsp *dsp) +{ + skl_ipc_int_disable(dsp); + + free_irq(dsp->irq, dsp); + skl_ipc_op_int_disable(dsp); + skl_dsp_disable_core(dsp, SKL_DSP_CORE0_MASK); +} +EXPORT_SYMBOL_GPL(skl_dsp_free); + +bool is_skl_dsp_running(struct sst_dsp *ctx) +{ + return (ctx->sst_state == SKL_DSP_RUNNING); +} +EXPORT_SYMBOL_GPL(is_skl_dsp_running); diff --git a/sound/soc/intel/skylake/skl-sst-dsp.h b/sound/soc/intel/skylake/skl-sst-dsp.h new file mode 100644 index 000000000..e1d6f6719 --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-dsp.h @@ -0,0 +1,263 @@ +/* + * Skylake SST DSP Support + * + * Copyright (C) 2014-15, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __SKL_SST_DSP_H__ +#define __SKL_SST_DSP_H__ + +#include +#include +#include +#include +#include "skl-sst-cldma.h" + +struct sst_dsp; +struct skl_sst; +struct sst_dsp_device; +struct skl_lib_info; + +/* Intel HD Audio General DSP Registers */ +#define SKL_ADSP_GEN_BASE 0x0 +#define SKL_ADSP_REG_ADSPCS (SKL_ADSP_GEN_BASE + 0x04) +#define SKL_ADSP_REG_ADSPIC (SKL_ADSP_GEN_BASE + 0x08) +#define SKL_ADSP_REG_ADSPIS (SKL_ADSP_GEN_BASE + 0x0C) +#define SKL_ADSP_REG_ADSPIC2 (SKL_ADSP_GEN_BASE + 0x10) +#define SKL_ADSP_REG_ADSPIS2 (SKL_ADSP_GEN_BASE + 0x14) + +/* Intel HD Audio Inter-Processor Communication Registers */ +#define SKL_ADSP_IPC_BASE 0x40 +#define SKL_ADSP_REG_HIPCT (SKL_ADSP_IPC_BASE + 0x00) +#define SKL_ADSP_REG_HIPCTE (SKL_ADSP_IPC_BASE + 0x04) +#define SKL_ADSP_REG_HIPCI (SKL_ADSP_IPC_BASE + 0x08) +#define SKL_ADSP_REG_HIPCIE (SKL_ADSP_IPC_BASE + 0x0C) +#define SKL_ADSP_REG_HIPCCTL (SKL_ADSP_IPC_BASE + 0x10) + +/* HIPCI */ +#define SKL_ADSP_REG_HIPCI_BUSY BIT(31) + +/* HIPCIE */ +#define SKL_ADSP_REG_HIPCIE_DONE BIT(30) + +/* HIPCCTL */ +#define SKL_ADSP_REG_HIPCCTL_DONE BIT(1) +#define SKL_ADSP_REG_HIPCCTL_BUSY BIT(0) + +/* HIPCT */ +#define SKL_ADSP_REG_HIPCT_BUSY BIT(31) + +/* FW base IDs */ +#define SKL_INSTANCE_ID 0 +#define SKL_BASE_FW_MODULE_ID 0 + +/* Intel HD Audio SRAM Window 1 */ +#define SKL_ADSP_SRAM1_BASE 0xA000 + +#define SKL_ADSP_MMIO_LEN 0x10000 + +#define SKL_ADSP_W0_STAT_SZ 0x1000 + +#define SKL_ADSP_W0_UP_SZ 0x1000 + +#define SKL_ADSP_W1_SZ 0x1000 + +#define SKL_FW_STS_MASK 0xf + +#define SKL_FW_INIT 0x1 +#define SKL_FW_RFW_START 0xf + +#define SKL_ADSPIC_IPC 1 +#define SKL_ADSPIS_IPC 1 + +/* Core ID of core0 */ +#define SKL_DSP_CORE0_ID 0 + +/* Mask for a given core index, c = 0.. number of supported cores - 1 */ +#define SKL_DSP_CORE_MASK(c) BIT(c) + +/* + * Core 0 mask = SKL_DSP_CORE_MASK(0); Defined separately + * since Core0 is primary core and it is used often + */ +#define SKL_DSP_CORE0_MASK BIT(0) + +/* + * Mask for a given number of cores + * nc = number of supported cores + */ +#define SKL_DSP_CORES_MASK(nc) GENMASK((nc - 1), 0) + +/* ADSPCS - Audio DSP Control & Status */ + +/* + * Core Reset - asserted high + * CRST Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CRST_SHIFT 0 +#define SKL_ADSPCS_CRST_MASK(cm) ((cm) << SKL_ADSPCS_CRST_SHIFT) + +/* + * Core run/stall - when set to '1' core is stalled + * CSTALL Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CSTALL_SHIFT 8 +#define SKL_ADSPCS_CSTALL_MASK(cm) ((cm) << SKL_ADSPCS_CSTALL_SHIFT) + +/* + * Set Power Active - when set to '1' turn cores on + * SPA Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_SPA_SHIFT 16 +#define SKL_ADSPCS_SPA_MASK(cm) ((cm) << SKL_ADSPCS_SPA_SHIFT) + +/* + * Current Power Active - power status of cores, set by hardware + * CPA Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CPA_SHIFT 24 +#define SKL_ADSPCS_CPA_MASK(cm) ((cm) << SKL_ADSPCS_CPA_SHIFT) + +/* DSP Core state */ +enum skl_dsp_states { + SKL_DSP_RUNNING = 1, + /* Running in D0i3 state; can be in streaming or non-streaming D0i3 */ + SKL_DSP_RUNNING_D0I3, /* Running in D0i3 state*/ + SKL_DSP_RESET, +}; + +/* D0i3 substates */ +enum skl_dsp_d0i3_states { + SKL_DSP_D0I3_NONE = -1, /* No D0i3 */ + SKL_DSP_D0I3_NON_STREAMING = 0, + SKL_DSP_D0I3_STREAMING = 1, +}; + +struct skl_dsp_fw_ops { + int (*load_fw)(struct sst_dsp *ctx); + /* FW module parser/loader */ + int (*load_library)(struct sst_dsp *ctx, + struct skl_lib_info *linfo, int lib_count); + int (*parse_fw)(struct sst_dsp *ctx); + int (*set_state_D0)(struct sst_dsp *ctx, unsigned int core_id); + int (*set_state_D3)(struct sst_dsp *ctx, unsigned int core_id); + int (*set_state_D0i3)(struct sst_dsp *ctx); + int (*set_state_D0i0)(struct sst_dsp *ctx); + unsigned int (*get_fw_errcode)(struct sst_dsp *ctx); + int (*load_mod)(struct sst_dsp *ctx, u16 mod_id, u8 *mod_name); + int (*unload_mod)(struct sst_dsp *ctx, u16 mod_id); + +}; + +struct skl_dsp_loader_ops { + int stream_tag; + + int (*alloc_dma_buf)(struct device *dev, + struct snd_dma_buffer *dmab, size_t size); + int (*free_dma_buf)(struct device *dev, + struct snd_dma_buffer *dmab); + int (*prepare)(struct device *dev, unsigned int format, + unsigned int byte_size, + struct snd_dma_buffer *bufp); + int (*trigger)(struct device *dev, bool start, int stream_tag); + + int (*cleanup)(struct device *dev, struct snd_dma_buffer *dmab, + int stream_tag); +}; + +#define MAX_INSTANCE_BUFF 2 + +struct uuid_module { + uuid_le uuid; + int id; + int is_loadable; + int max_instance; + u64 pvt_id[MAX_INSTANCE_BUFF]; + int *instance_id; + + struct list_head list; +}; + +struct skl_load_module_info { + u16 mod_id; + const struct firmware *fw; +}; + +struct skl_module_table { + struct skl_load_module_info *mod_info; + unsigned int usage_cnt; + struct list_head list; +}; + +void skl_cldma_process_intr(struct sst_dsp *ctx); +void skl_cldma_int_disable(struct sst_dsp *ctx); +int skl_cldma_prepare(struct sst_dsp *ctx); +int skl_cldma_wait_interruptible(struct sst_dsp *ctx); + +void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state); +struct sst_dsp *skl_dsp_ctx_init(struct device *dev, + struct sst_dsp_device *sst_dev, int irq); +int skl_dsp_acquire_irq(struct sst_dsp *sst); +bool is_skl_dsp_running(struct sst_dsp *ctx); + +unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx); +void skl_dsp_init_core_state(struct sst_dsp *ctx); +int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx, + unsigned int core_mask); +int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask); + +irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id); +int skl_dsp_wake(struct sst_dsp *ctx); +int skl_dsp_sleep(struct sst_dsp *ctx); +void skl_dsp_free(struct sst_dsp *dsp); + +int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id); +int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id); + +int skl_dsp_boot(struct sst_dsp *ctx); +int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp); +int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, + struct skl_sst **dsp); +int skl_sst_init_fw(struct device *dev, struct skl_sst *ctx); +int bxt_sst_init_fw(struct device *dev, struct skl_sst *ctx); +void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); +void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); + +int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw, + unsigned int offset, int index); +int skl_get_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int instance_id); +int skl_put_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int *pvt_id); +int skl_get_pvt_instance_id_map(struct skl_sst *ctx, + int module_id, int instance_id); +void skl_freeup_uuid_list(struct skl_sst *ctx); + +int skl_dsp_strip_extended_manifest(struct firmware *fw); +void skl_dsp_enable_notification(struct skl_sst *ctx, bool enable); + +void skl_dsp_set_astate_cfg(struct skl_sst *ctx, u32 cnt, void *data); + +int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name, + struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp, + struct sst_dsp_device *skl_dev); +int skl_prepare_lib_load(struct skl_sst *skl, struct skl_lib_info *linfo, + struct firmware *stripped_fw, + unsigned int hdr_offset, int index); +void skl_release_library(struct skl_lib_info *linfo, int lib_count); + +#endif /*__SKL_SST_DSP_H__*/ diff --git a/sound/soc/intel/skylake/skl-sst-ipc.c b/sound/soc/intel/skylake/skl-sst-ipc.c new file mode 100644 index 000000000..5234fafb7 --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-ipc.c @@ -0,0 +1,1033 @@ +/* + * skl-sst-ipc.c - Intel skl IPC Support + * + * Copyright (C) 2014-15, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include + +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "skl.h" +#include "skl-sst-dsp.h" +#include "skl-sst-ipc.h" +#include "sound/hdaudio_ext.h" + + +#define IPC_IXC_STATUS_BITS 24 + +/* Global Message - Generic */ +#define IPC_GLB_TYPE_SHIFT 24 +#define IPC_GLB_TYPE_MASK (0xf << IPC_GLB_TYPE_SHIFT) +#define IPC_GLB_TYPE(x) ((x) << IPC_GLB_TYPE_SHIFT) + +/* Global Message - Reply */ +#define IPC_GLB_REPLY_STATUS_SHIFT 24 +#define IPC_GLB_REPLY_STATUS_MASK ((0x1 << IPC_GLB_REPLY_STATUS_SHIFT) - 1) +#define IPC_GLB_REPLY_STATUS(x) ((x) << IPC_GLB_REPLY_STATUS_SHIFT) + +#define IPC_GLB_REPLY_TYPE_SHIFT 29 +#define IPC_GLB_REPLY_TYPE_MASK 0x1F +#define IPC_GLB_REPLY_TYPE(x) (((x) >> IPC_GLB_REPLY_TYPE_SHIFT) \ + & IPC_GLB_RPLY_TYPE_MASK) + +#define IPC_TIMEOUT_MSECS 3000 + +#define IPC_EMPTY_LIST_SIZE 8 + +#define IPC_MSG_TARGET_SHIFT 30 +#define IPC_MSG_TARGET_MASK 0x1 +#define IPC_MSG_TARGET(x) (((x) & IPC_MSG_TARGET_MASK) \ + << IPC_MSG_TARGET_SHIFT) + +#define IPC_MSG_DIR_SHIFT 29 +#define IPC_MSG_DIR_MASK 0x1 +#define IPC_MSG_DIR(x) (((x) & IPC_MSG_DIR_MASK) \ + << IPC_MSG_DIR_SHIFT) +/* Global Notification Message */ +#define IPC_GLB_NOTIFY_TYPE_SHIFT 16 +#define IPC_GLB_NOTIFY_TYPE_MASK 0xFF +#define IPC_GLB_NOTIFY_TYPE(x) (((x) >> IPC_GLB_NOTIFY_TYPE_SHIFT) \ + & IPC_GLB_NOTIFY_TYPE_MASK) + +#define IPC_GLB_NOTIFY_MSG_TYPE_SHIFT 24 +#define IPC_GLB_NOTIFY_MSG_TYPE_MASK 0x1F +#define IPC_GLB_NOTIFY_MSG_TYPE(x) (((x) >> IPC_GLB_NOTIFY_MSG_TYPE_SHIFT) \ + & IPC_GLB_NOTIFY_MSG_TYPE_MASK) + +#define IPC_GLB_NOTIFY_RSP_SHIFT 29 +#define IPC_GLB_NOTIFY_RSP_MASK 0x1 +#define IPC_GLB_NOTIFY_RSP_TYPE(x) (((x) >> IPC_GLB_NOTIFY_RSP_SHIFT) \ + & IPC_GLB_NOTIFY_RSP_MASK) + +/* Pipeline operations */ + +/* Create pipeline message */ +#define IPC_PPL_MEM_SIZE_SHIFT 0 +#define IPC_PPL_MEM_SIZE_MASK 0x7FF +#define IPC_PPL_MEM_SIZE(x) (((x) & IPC_PPL_MEM_SIZE_MASK) \ + << IPC_PPL_MEM_SIZE_SHIFT) + +#define IPC_PPL_TYPE_SHIFT 11 +#define IPC_PPL_TYPE_MASK 0x1F +#define IPC_PPL_TYPE(x) (((x) & IPC_PPL_TYPE_MASK) \ + << IPC_PPL_TYPE_SHIFT) + +#define IPC_INSTANCE_ID_SHIFT 16 +#define IPC_INSTANCE_ID_MASK 0xFF +#define IPC_INSTANCE_ID(x) (((x) & IPC_INSTANCE_ID_MASK) \ + << IPC_INSTANCE_ID_SHIFT) + +#define IPC_PPL_LP_MODE_SHIFT 0 +#define IPC_PPL_LP_MODE_MASK 0x1 +#define IPC_PPL_LP_MODE(x) (((x) & IPC_PPL_LP_MODE_MASK) \ + << IPC_PPL_LP_MODE_SHIFT) + +/* Set pipeline state message */ +#define IPC_PPL_STATE_SHIFT 0 +#define IPC_PPL_STATE_MASK 0x1F +#define IPC_PPL_STATE(x) (((x) & IPC_PPL_STATE_MASK) \ + << IPC_PPL_STATE_SHIFT) + +/* Module operations primary register */ +#define IPC_MOD_ID_SHIFT 0 +#define IPC_MOD_ID_MASK 0xFFFF +#define IPC_MOD_ID(x) (((x) & IPC_MOD_ID_MASK) \ + << IPC_MOD_ID_SHIFT) + +#define IPC_MOD_INSTANCE_ID_SHIFT 16 +#define IPC_MOD_INSTANCE_ID_MASK 0xFF +#define IPC_MOD_INSTANCE_ID(x) (((x) & IPC_MOD_INSTANCE_ID_MASK) \ + << IPC_MOD_INSTANCE_ID_SHIFT) + +/* Init instance message extension register */ +#define IPC_PARAM_BLOCK_SIZE_SHIFT 0 +#define IPC_PARAM_BLOCK_SIZE_MASK 0xFFFF +#define IPC_PARAM_BLOCK_SIZE(x) (((x) & IPC_PARAM_BLOCK_SIZE_MASK) \ + << IPC_PARAM_BLOCK_SIZE_SHIFT) + +#define IPC_PPL_INSTANCE_ID_SHIFT 16 +#define IPC_PPL_INSTANCE_ID_MASK 0xFF +#define IPC_PPL_INSTANCE_ID(x) (((x) & IPC_PPL_INSTANCE_ID_MASK) \ + << IPC_PPL_INSTANCE_ID_SHIFT) + +#define IPC_CORE_ID_SHIFT 24 +#define IPC_CORE_ID_MASK 0x1F +#define IPC_CORE_ID(x) (((x) & IPC_CORE_ID_MASK) \ + << IPC_CORE_ID_SHIFT) + +#define IPC_DOMAIN_SHIFT 28 +#define IPC_DOMAIN_MASK 0x1 +#define IPC_DOMAIN(x) (((x) & IPC_DOMAIN_MASK) \ + << IPC_DOMAIN_SHIFT) + +/* Bind/Unbind message extension register */ +#define IPC_DST_MOD_ID_SHIFT 0 +#define IPC_DST_MOD_ID(x) (((x) & IPC_MOD_ID_MASK) \ + << IPC_DST_MOD_ID_SHIFT) + +#define IPC_DST_MOD_INSTANCE_ID_SHIFT 16 +#define IPC_DST_MOD_INSTANCE_ID(x) (((x) & IPC_MOD_INSTANCE_ID_MASK) \ + << IPC_DST_MOD_INSTANCE_ID_SHIFT) + +#define IPC_DST_QUEUE_SHIFT 24 +#define IPC_DST_QUEUE_MASK 0x7 +#define IPC_DST_QUEUE(x) (((x) & IPC_DST_QUEUE_MASK) \ + << IPC_DST_QUEUE_SHIFT) + +#define IPC_SRC_QUEUE_SHIFT 27 +#define IPC_SRC_QUEUE_MASK 0x7 +#define IPC_SRC_QUEUE(x) (((x) & IPC_SRC_QUEUE_MASK) \ + << IPC_SRC_QUEUE_SHIFT) +/* Load Module count */ +#define IPC_LOAD_MODULE_SHIFT 0 +#define IPC_LOAD_MODULE_MASK 0xFF +#define IPC_LOAD_MODULE_CNT(x) (((x) & IPC_LOAD_MODULE_MASK) \ + << IPC_LOAD_MODULE_SHIFT) + +/* Save pipeline messgae extension register */ +#define IPC_DMA_ID_SHIFT 0 +#define IPC_DMA_ID_MASK 0x1F +#define IPC_DMA_ID(x) (((x) & IPC_DMA_ID_MASK) \ + << IPC_DMA_ID_SHIFT) +/* Large Config message extension register */ +#define IPC_DATA_OFFSET_SZ_SHIFT 0 +#define IPC_DATA_OFFSET_SZ_MASK 0xFFFFF +#define IPC_DATA_OFFSET_SZ(x) (((x) & IPC_DATA_OFFSET_SZ_MASK) \ + << IPC_DATA_OFFSET_SZ_SHIFT) +#define IPC_DATA_OFFSET_SZ_CLEAR ~(IPC_DATA_OFFSET_SZ_MASK \ + << IPC_DATA_OFFSET_SZ_SHIFT) + +#define IPC_LARGE_PARAM_ID_SHIFT 20 +#define IPC_LARGE_PARAM_ID_MASK 0xFF +#define IPC_LARGE_PARAM_ID(x) (((x) & IPC_LARGE_PARAM_ID_MASK) \ + << IPC_LARGE_PARAM_ID_SHIFT) + +#define IPC_FINAL_BLOCK_SHIFT 28 +#define IPC_FINAL_BLOCK_MASK 0x1 +#define IPC_FINAL_BLOCK(x) (((x) & IPC_FINAL_BLOCK_MASK) \ + << IPC_FINAL_BLOCK_SHIFT) + +#define IPC_INITIAL_BLOCK_SHIFT 29 +#define IPC_INITIAL_BLOCK_MASK 0x1 +#define IPC_INITIAL_BLOCK(x) (((x) & IPC_INITIAL_BLOCK_MASK) \ + << IPC_INITIAL_BLOCK_SHIFT) +#define IPC_INITIAL_BLOCK_CLEAR ~(IPC_INITIAL_BLOCK_MASK \ + << IPC_INITIAL_BLOCK_SHIFT) +/* Set D0ix IPC extension register */ +#define IPC_D0IX_WAKE_SHIFT 0 +#define IPC_D0IX_WAKE_MASK 0x1 +#define IPC_D0IX_WAKE(x) (((x) & IPC_D0IX_WAKE_MASK) \ + << IPC_D0IX_WAKE_SHIFT) + +#define IPC_D0IX_STREAMING_SHIFT 1 +#define IPC_D0IX_STREAMING_MASK 0x1 +#define IPC_D0IX_STREAMING(x) (((x) & IPC_D0IX_STREAMING_MASK) \ + << IPC_D0IX_STREAMING_SHIFT) + + +enum skl_ipc_msg_target { + IPC_FW_GEN_MSG = 0, + IPC_MOD_MSG = 1 +}; + +enum skl_ipc_msg_direction { + IPC_MSG_REQUEST = 0, + IPC_MSG_REPLY = 1 +}; + +/* Global Message Types */ +enum skl_ipc_glb_type { + IPC_GLB_GET_FW_VERSION = 0, /* Retrieves firmware version */ + IPC_GLB_LOAD_MULTIPLE_MODS = 15, + IPC_GLB_UNLOAD_MULTIPLE_MODS = 16, + IPC_GLB_CREATE_PPL = 17, + IPC_GLB_DELETE_PPL = 18, + IPC_GLB_SET_PPL_STATE = 19, + IPC_GLB_GET_PPL_STATE = 20, + IPC_GLB_GET_PPL_CONTEXT_SIZE = 21, + IPC_GLB_SAVE_PPL = 22, + IPC_GLB_RESTORE_PPL = 23, + IPC_GLB_LOAD_LIBRARY = 24, + IPC_GLB_NOTIFY = 26, + IPC_GLB_MAX_IPC_MSG_NUMBER = 31 /* Maximum message number */ +}; + +enum skl_ipc_glb_reply { + IPC_GLB_REPLY_SUCCESS = 0, + + IPC_GLB_REPLY_UNKNOWN_MSG_TYPE = 1, + IPC_GLB_REPLY_ERROR_INVALID_PARAM = 2, + + IPC_GLB_REPLY_BUSY = 3, + IPC_GLB_REPLY_PENDING = 4, + IPC_GLB_REPLY_FAILURE = 5, + IPC_GLB_REPLY_INVALID_REQUEST = 6, + + IPC_GLB_REPLY_OUT_OF_MEMORY = 7, + IPC_GLB_REPLY_OUT_OF_MIPS = 8, + + IPC_GLB_REPLY_INVALID_RESOURCE_ID = 9, + IPC_GLB_REPLY_INVALID_RESOURCE_STATE = 10, + + IPC_GLB_REPLY_MOD_MGMT_ERROR = 100, + IPC_GLB_REPLY_MOD_LOAD_CL_FAILED = 101, + IPC_GLB_REPLY_MOD_LOAD_INVALID_HASH = 102, + + IPC_GLB_REPLY_MOD_UNLOAD_INST_EXIST = 103, + IPC_GLB_REPLY_MOD_NOT_INITIALIZED = 104, + + IPC_GLB_REPLY_INVALID_CONFIG_PARAM_ID = 120, + IPC_GLB_REPLY_INVALID_CONFIG_DATA_LEN = 121, + IPC_GLB_REPLY_GATEWAY_NOT_INITIALIZED = 140, + IPC_GLB_REPLY_GATEWAY_NOT_EXIST = 141, + + IPC_GLB_REPLY_PPL_NOT_INITIALIZED = 160, + IPC_GLB_REPLY_PPL_NOT_EXIST = 161, + IPC_GLB_REPLY_PPL_SAVE_FAILED = 162, + IPC_GLB_REPLY_PPL_RESTORE_FAILED = 163, + + IPC_MAX_STATUS = ((1<tx_data, tx_data, tx_size); +} + +static bool skl_ipc_is_dsp_busy(struct sst_dsp *dsp) +{ + u32 hipci; + + hipci = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCI); + return (hipci & SKL_ADSP_REG_HIPCI_BUSY); +} + +/* Lock to be held by caller */ +static void skl_ipc_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg) +{ + struct skl_ipc_header *header = (struct skl_ipc_header *)(&msg->header); + + if (msg->tx_size) + sst_dsp_outbox_write(ipc->dsp, msg->tx_data, msg->tx_size); + sst_dsp_shim_write_unlocked(ipc->dsp, SKL_ADSP_REG_HIPCIE, + header->extension); + sst_dsp_shim_write_unlocked(ipc->dsp, SKL_ADSP_REG_HIPCI, + header->primary | SKL_ADSP_REG_HIPCI_BUSY); +} + +int skl_ipc_check_D0i0(struct sst_dsp *dsp, bool state) +{ + int ret; + + /* check D0i3 support */ + if (!dsp->fw_ops.set_state_D0i0) + return 0; + + /* Attempt D0i0 or D0i3 based on state */ + if (state) + ret = dsp->fw_ops.set_state_D0i0(dsp); + else + ret = dsp->fw_ops.set_state_D0i3(dsp); + + return ret; +} + +static struct ipc_message *skl_ipc_reply_get_msg(struct sst_generic_ipc *ipc, + u64 ipc_header) +{ + struct ipc_message *msg = NULL; + struct skl_ipc_header *header = (struct skl_ipc_header *)(&ipc_header); + + if (list_empty(&ipc->rx_list)) { + dev_err(ipc->dev, "ipc: rx list is empty but received 0x%x\n", + header->primary); + goto out; + } + + msg = list_first_entry(&ipc->rx_list, struct ipc_message, list); + +out: + return msg; + +} + +int skl_ipc_process_notification(struct sst_generic_ipc *ipc, + struct skl_ipc_header header) +{ + struct skl_sst *skl = container_of(ipc, struct skl_sst, ipc); + + if (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) { + switch (IPC_GLB_NOTIFY_TYPE(header.primary)) { + + case IPC_GLB_NOTIFY_UNDERRUN: + dev_err(ipc->dev, "FW Underrun %x\n", header.primary); + break; + + case IPC_GLB_NOTIFY_RESOURCE_EVENT: + dev_err(ipc->dev, "MCPS Budget Violation: %x\n", + header.primary); + break; + + case IPC_GLB_NOTIFY_FW_READY: + skl->boot_complete = true; + wake_up(&skl->boot_wait); + break; + + case IPC_GLB_NOTIFY_PHRASE_DETECTED: + dev_dbg(ipc->dev, "***** Phrase Detected **********\n"); + + /* + * Per HW recomendation, After phrase detection, + * clear the CGCTL.MISCBDCGE. + * + * This will be set back on stream closure + */ + skl->enable_miscbdcge(ipc->dev, false); + skl->miscbdcg_disabled = true; + break; + + default: + dev_err(ipc->dev, "ipc: Unhandled error msg=%x\n", + header.primary); + break; + } + } + + return 0; +} + +static int skl_ipc_set_reply_error_code(u32 reply) +{ + switch (reply) { + case IPC_GLB_REPLY_OUT_OF_MEMORY: + return -ENOMEM; + + case IPC_GLB_REPLY_BUSY: + return -EBUSY; + + default: + return -EINVAL; + } +} + +void skl_ipc_process_reply(struct sst_generic_ipc *ipc, + struct skl_ipc_header header) +{ + struct ipc_message *msg; + u32 reply = header.primary & IPC_GLB_REPLY_STATUS_MASK; + u64 *ipc_header = (u64 *)(&header); + struct skl_sst *skl = container_of(ipc, struct skl_sst, ipc); + unsigned long flags; + + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + msg = skl_ipc_reply_get_msg(ipc, *ipc_header); + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); + if (msg == NULL) { + dev_dbg(ipc->dev, "ipc: rx list is empty\n"); + return; + } + + /* first process the header */ + if (reply == IPC_GLB_REPLY_SUCCESS) { + dev_dbg(ipc->dev, "ipc FW reply %x: success\n", header.primary); + /* copy the rx data from the mailbox */ + sst_dsp_inbox_read(ipc->dsp, msg->rx_data, msg->rx_size); + switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) { + case IPC_GLB_LOAD_MULTIPLE_MODS: + case IPC_GLB_LOAD_LIBRARY: + skl->mod_load_complete = true; + skl->mod_load_status = true; + wake_up(&skl->mod_load_wait); + break; + + default: + break; + + } + } else { + msg->errno = skl_ipc_set_reply_error_code(reply); + dev_err(ipc->dev, "ipc FW reply: reply=%d\n", reply); + dev_err(ipc->dev, "FW Error Code: %u\n", + ipc->dsp->fw_ops.get_fw_errcode(ipc->dsp)); + switch (IPC_GLB_NOTIFY_MSG_TYPE(header.primary)) { + case IPC_GLB_LOAD_MULTIPLE_MODS: + case IPC_GLB_LOAD_LIBRARY: + skl->mod_load_complete = true; + skl->mod_load_status = false; + wake_up(&skl->mod_load_wait); + break; + + default: + break; + + } + } + + spin_lock_irqsave(&ipc->dsp->spinlock, flags); + list_del(&msg->list); + sst_ipc_tx_msg_reply_complete(ipc, msg); + spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); +} + +irqreturn_t skl_dsp_irq_thread_handler(int irq, void *context) +{ + struct sst_dsp *dsp = context; + struct skl_sst *skl = sst_dsp_get_thread_context(dsp); + struct sst_generic_ipc *ipc = &skl->ipc; + struct skl_ipc_header header = {0}; + u32 hipcie, hipct, hipcte; + int ipc_irq = 0; + + if (dsp->intr_status & SKL_ADSPIS_CL_DMA) + skl_cldma_process_intr(dsp); + + /* Here we handle IPC interrupts only */ + if (!(dsp->intr_status & SKL_ADSPIS_IPC)) + return IRQ_NONE; + + hipcie = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCIE); + hipct = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCT); + + /* reply message from DSP */ + if (hipcie & SKL_ADSP_REG_HIPCIE_DONE) { + sst_dsp_shim_update_bits(dsp, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_DONE, 0); + + /* clear DONE bit - tell DSP we have completed the operation */ + sst_dsp_shim_update_bits_forced(dsp, SKL_ADSP_REG_HIPCIE, + SKL_ADSP_REG_HIPCIE_DONE, SKL_ADSP_REG_HIPCIE_DONE); + + ipc_irq = 1; + + /* unmask Done interrupt */ + sst_dsp_shim_update_bits(dsp, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_DONE, SKL_ADSP_REG_HIPCCTL_DONE); + } + + /* New message from DSP */ + if (hipct & SKL_ADSP_REG_HIPCT_BUSY) { + hipcte = sst_dsp_shim_read_unlocked(dsp, SKL_ADSP_REG_HIPCTE); + header.primary = hipct; + header.extension = hipcte; + dev_dbg(dsp->dev, "IPC irq: Firmware respond primary:%x\n", + header.primary); + dev_dbg(dsp->dev, "IPC irq: Firmware respond extension:%x\n", + header.extension); + + if (IPC_GLB_NOTIFY_RSP_TYPE(header.primary)) { + /* Handle Immediate reply from DSP Core */ + skl_ipc_process_reply(ipc, header); + } else { + dev_dbg(dsp->dev, "IPC irq: Notification from firmware\n"); + skl_ipc_process_notification(ipc, header); + } + /* clear busy interrupt */ + sst_dsp_shim_update_bits_forced(dsp, SKL_ADSP_REG_HIPCT, + SKL_ADSP_REG_HIPCT_BUSY, SKL_ADSP_REG_HIPCT_BUSY); + ipc_irq = 1; + } + + if (ipc_irq == 0) + return IRQ_NONE; + + skl_ipc_int_enable(dsp); + + /* continue to send any remaining messages... */ + schedule_work(&ipc->kwork); + + return IRQ_HANDLED; +} + +void skl_ipc_int_enable(struct sst_dsp *ctx) +{ + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_ADSPIC, + SKL_ADSPIC_IPC, SKL_ADSPIC_IPC); +} + +void skl_ipc_int_disable(struct sst_dsp *ctx) +{ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPIC, + SKL_ADSPIC_IPC, 0); +} + +void skl_ipc_op_int_enable(struct sst_dsp *ctx) +{ + /* enable IPC DONE interrupt */ + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_DONE, SKL_ADSP_REG_HIPCCTL_DONE); + + /* Enable IPC BUSY interrupt */ + sst_dsp_shim_update_bits(ctx, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_BUSY, SKL_ADSP_REG_HIPCCTL_BUSY); +} + +void skl_ipc_op_int_disable(struct sst_dsp *ctx) +{ + /* disable IPC DONE interrupt */ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_DONE, 0); + + /* Disable IPC BUSY interrupt */ + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_BUSY, 0); + +} + +bool skl_ipc_int_status(struct sst_dsp *ctx) +{ + return sst_dsp_shim_read_unlocked(ctx, + SKL_ADSP_REG_ADSPIS) & SKL_ADSPIS_IPC; +} + +int skl_ipc_init(struct device *dev, struct skl_sst *skl) +{ + struct sst_generic_ipc *ipc; + int err; + + ipc = &skl->ipc; + ipc->dsp = skl->dsp; + ipc->dev = dev; + + ipc->tx_data_max_size = SKL_ADSP_W1_SZ; + ipc->rx_data_max_size = SKL_ADSP_W0_UP_SZ; + + err = sst_ipc_init(ipc); + if (err) + return err; + + ipc->ops.tx_msg = skl_ipc_tx_msg; + ipc->ops.tx_data_copy = skl_ipc_tx_data_copy; + ipc->ops.is_dsp_busy = skl_ipc_is_dsp_busy; + + return 0; +} + +void skl_ipc_free(struct sst_generic_ipc *ipc) +{ + /* Disable IPC DONE interrupt */ + sst_dsp_shim_update_bits(ipc->dsp, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_DONE, 0); + + /* Disable IPC BUSY interrupt */ + sst_dsp_shim_update_bits(ipc->dsp, SKL_ADSP_REG_HIPCCTL, + SKL_ADSP_REG_HIPCCTL_BUSY, 0); + + sst_ipc_fini(ipc); +} + +int skl_ipc_create_pipeline(struct sst_generic_ipc *ipc, + u16 ppl_mem_size, u8 ppl_type, u8 instance_id, u8 lp_mode) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_CREATE_PPL); + header.primary |= IPC_INSTANCE_ID(instance_id); + header.primary |= IPC_PPL_TYPE(ppl_type); + header.primary |= IPC_PPL_MEM_SIZE(ppl_mem_size); + + header.extension = IPC_PPL_LP_MODE(lp_mode); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: create pipeline fail, err: %d\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_create_pipeline); + +int skl_ipc_delete_pipeline(struct sst_generic_ipc *ipc, u8 instance_id) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_DELETE_PPL); + header.primary |= IPC_INSTANCE_ID(instance_id); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: delete pipeline failed, err %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(skl_ipc_delete_pipeline); + +int skl_ipc_set_pipeline_state(struct sst_generic_ipc *ipc, + u8 instance_id, enum skl_ipc_pipeline_state state) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_SET_PPL_STATE); + header.primary |= IPC_INSTANCE_ID(instance_id); + header.primary |= IPC_PPL_STATE(state); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: set pipeline state failed, err: %d\n", ret); + return ret; + } + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_set_pipeline_state); + +int +skl_ipc_save_pipeline(struct sst_generic_ipc *ipc, u8 instance_id, int dma_id) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_SAVE_PPL); + header.primary |= IPC_INSTANCE_ID(instance_id); + + header.extension = IPC_DMA_ID(dma_id); + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: save pipeline failed, err: %d\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_save_pipeline); + +int skl_ipc_restore_pipeline(struct sst_generic_ipc *ipc, u8 instance_id) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_RESTORE_PPL); + header.primary |= IPC_INSTANCE_ID(instance_id); + + dev_dbg(ipc->dev, "In %s header=%d\n", __func__, header.primary); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: restore pipeline failed, err: %d\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_restore_pipeline); + +int skl_ipc_set_dx(struct sst_generic_ipc *ipc, u8 instance_id, + u16 module_id, struct skl_ipc_dxstate_info *dx) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MOD_SET_DX); + header.primary |= IPC_MOD_INSTANCE_ID(instance_id); + header.primary |= IPC_MOD_ID(module_id); + + dev_dbg(ipc->dev, "In %s primary =%x ext=%x\n", __func__, + header.primary, header.extension); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, + dx, sizeof(*dx), NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: set dx failed, err %d\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_set_dx); + +int skl_ipc_init_instance(struct sst_generic_ipc *ipc, + struct skl_ipc_init_instance_msg *msg, void *param_data) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + u32 *buffer = (u32 *)param_data; + /* param_block_size must be in dwords */ + u16 param_block_size = msg->param_data_size / sizeof(u32); + + print_hex_dump_debug("Param data:", DUMP_PREFIX_NONE, + 16, 4, buffer, param_block_size, false); + + header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MOD_INIT_INSTANCE); + header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MOD_ID(msg->module_id); + + header.extension = IPC_CORE_ID(msg->core_id); + header.extension |= IPC_PPL_INSTANCE_ID(msg->ppl_instance_id); + header.extension |= IPC_PARAM_BLOCK_SIZE(param_block_size); + header.extension |= IPC_DOMAIN(msg->domain); + + dev_dbg(ipc->dev, "In %s primary =%x ext=%x\n", __func__, + header.primary, header.extension); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, param_data, + msg->param_data_size, NULL, 0); + + if (ret < 0) { + dev_err(ipc->dev, "ipc: init instance failed\n"); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_init_instance); + +int skl_ipc_bind_unbind(struct sst_generic_ipc *ipc, + struct skl_ipc_bind_unbind_msg *msg) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + u8 bind_unbind = msg->bind ? IPC_MOD_BIND : IPC_MOD_UNBIND; + int ret; + + header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(bind_unbind); + header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MOD_ID(msg->module_id); + + header.extension = IPC_DST_MOD_ID(msg->dst_module_id); + header.extension |= IPC_DST_MOD_INSTANCE_ID(msg->dst_instance_id); + header.extension |= IPC_DST_QUEUE(msg->dst_queue); + header.extension |= IPC_SRC_QUEUE(msg->src_queue); + + dev_dbg(ipc->dev, "In %s hdr=%x ext=%x\n", __func__, header.primary, + header.extension); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, "ipc: bind/unbind failed\n"); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_bind_unbind); + +/* + * In order to load a module we need to send IPC to initiate that. DMA will + * performed to load the module memory. The FW supports multiple module load + * at single shot, so we can send IPC with N modules represented by + * module_cnt + */ +int skl_ipc_load_modules(struct sst_generic_ipc *ipc, + u8 module_cnt, void *data) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_LOAD_MULTIPLE_MODS); + header.primary |= IPC_LOAD_MODULE_CNT(module_cnt); + + ret = sst_ipc_tx_message_nowait(ipc, *ipc_header, data, + (sizeof(u16) * module_cnt)); + if (ret < 0) + dev_err(ipc->dev, "ipc: load modules failed :%d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_load_modules); + +int skl_ipc_unload_modules(struct sst_generic_ipc *ipc, u8 module_cnt, + void *data) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_UNLOAD_MULTIPLE_MODS); + header.primary |= IPC_LOAD_MODULE_CNT(module_cnt); + + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, data, + (sizeof(u16) * module_cnt), NULL, 0); + if (ret < 0) + dev_err(ipc->dev, "ipc: unload modules failed :%d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_unload_modules); + +int skl_ipc_set_large_config(struct sst_generic_ipc *ipc, + struct skl_ipc_large_config_msg *msg, u32 *param) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret = 0; + size_t sz_remaining, tx_size, data_offset; + + header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MOD_LARGE_CONFIG_SET); + header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MOD_ID(msg->module_id); + + header.extension = IPC_DATA_OFFSET_SZ(msg->param_data_size); + header.extension |= IPC_LARGE_PARAM_ID(msg->large_param_id); + header.extension |= IPC_FINAL_BLOCK(0); + header.extension |= IPC_INITIAL_BLOCK(1); + + sz_remaining = msg->param_data_size; + data_offset = 0; + while (sz_remaining != 0) { + tx_size = sz_remaining > SKL_ADSP_W1_SZ + ? SKL_ADSP_W1_SZ : sz_remaining; + if (tx_size == sz_remaining) + header.extension |= IPC_FINAL_BLOCK(1); + + dev_dbg(ipc->dev, "In %s primary=%#x ext=%#x\n", __func__, + header.primary, header.extension); + dev_dbg(ipc->dev, "transmitting offset: %#x, size: %#x\n", + (unsigned)data_offset, (unsigned)tx_size); + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, + ((char *)param) + data_offset, + tx_size, NULL, 0); + if (ret < 0) { + dev_err(ipc->dev, + "ipc: set large config fail, err: %d\n", ret); + return ret; + } + sz_remaining -= tx_size; + data_offset = msg->param_data_size - sz_remaining; + + /* clear the fields */ + header.extension &= IPC_INITIAL_BLOCK_CLEAR; + header.extension &= IPC_DATA_OFFSET_SZ_CLEAR; + /* fill the fields */ + header.extension |= IPC_INITIAL_BLOCK(0); + header.extension |= IPC_DATA_OFFSET_SZ(data_offset); + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_set_large_config); + +int skl_ipc_get_large_config(struct sst_generic_ipc *ipc, + struct skl_ipc_large_config_msg *msg, u32 *param) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret = 0; + size_t sz_remaining, rx_size, data_offset; + + header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MOD_LARGE_CONFIG_GET); + header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MOD_ID(msg->module_id); + + header.extension = IPC_DATA_OFFSET_SZ(msg->param_data_size); + header.extension |= IPC_LARGE_PARAM_ID(msg->large_param_id); + header.extension |= IPC_FINAL_BLOCK(1); + header.extension |= IPC_INITIAL_BLOCK(1); + + sz_remaining = msg->param_data_size; + data_offset = 0; + + while (sz_remaining != 0) { + rx_size = sz_remaining > SKL_ADSP_W1_SZ + ? SKL_ADSP_W1_SZ : sz_remaining; + if (rx_size == sz_remaining) + header.extension |= IPC_FINAL_BLOCK(1); + + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, NULL, 0, + ((char *)param) + data_offset, + msg->param_data_size); + if (ret < 0) { + dev_err(ipc->dev, + "ipc: get large config fail, err: %d\n", ret); + return ret; + } + sz_remaining -= rx_size; + data_offset = msg->param_data_size - sz_remaining; + + /* clear the fields */ + header.extension &= IPC_INITIAL_BLOCK_CLEAR; + header.extension &= IPC_DATA_OFFSET_SZ_CLEAR; + /* fill the fields */ + header.extension |= IPC_INITIAL_BLOCK(1); + header.extension |= IPC_DATA_OFFSET_SZ(data_offset); + } + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_get_large_config); + +int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, + u8 dma_id, u8 table_id, bool wait) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret = 0; + + header.primary = IPC_MSG_TARGET(IPC_FW_GEN_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_GLB_LOAD_LIBRARY); + header.primary |= IPC_MOD_INSTANCE_ID(table_id); + header.primary |= IPC_MOD_ID(dma_id); + + if (wait) + ret = sst_ipc_tx_message_wait(ipc, *ipc_header, + NULL, 0, NULL, 0); + else + ret = sst_ipc_tx_message_nowait(ipc, *ipc_header, NULL, 0); + + if (ret < 0) + dev_err(ipc->dev, "ipc: load lib failed\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_sst_ipc_load_library); + +int skl_ipc_set_d0ix(struct sst_generic_ipc *ipc, struct skl_ipc_d0ix_msg *msg) +{ + struct skl_ipc_header header = {0}; + u64 *ipc_header = (u64 *)(&header); + int ret; + + header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); + header.primary |= IPC_MSG_DIR(IPC_MSG_REQUEST); + header.primary |= IPC_GLB_TYPE(IPC_MOD_SET_D0IX); + header.primary |= IPC_MOD_INSTANCE_ID(msg->instance_id); + header.primary |= IPC_MOD_ID(msg->module_id); + + header.extension = IPC_D0IX_WAKE(msg->wake); + header.extension |= IPC_D0IX_STREAMING(msg->streaming); + + dev_dbg(ipc->dev, "In %s primary=%x ext=%x\n", __func__, + header.primary, header.extension); + + /* + * Use the nopm IPC here as we dont want it checking for D0iX + */ + ret = sst_ipc_tx_message_nopm(ipc, *ipc_header, NULL, 0, NULL, 0); + if (ret < 0) + dev_err(ipc->dev, "ipc: set d0ix failed, err %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_ipc_set_d0ix); diff --git a/sound/soc/intel/skylake/skl-sst-ipc.h b/sound/soc/intel/skylake/skl-sst-ipc.h new file mode 100644 index 000000000..f74f040df --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-ipc.h @@ -0,0 +1,224 @@ +/* + * Intel SKL IPC Support + * + * Copyright (C) 2014-15, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __SKL_IPC_H +#define __SKL_IPC_H + +#include +#include "../common/sst-ipc.h" + +struct sst_dsp; +struct skl_sst; +struct sst_generic_ipc; + +enum skl_ipc_pipeline_state { + PPL_INVALID_STATE = 0, + PPL_UNINITIALIZED = 1, + PPL_RESET = 2, + PPL_PAUSED = 3, + PPL_RUNNING = 4, + PPL_ERROR_STOP = 5, + PPL_SAVED = 6, + PPL_RESTORED = 7 +}; + +struct skl_ipc_dxstate_info { + u32 core_mask; + u32 dx_mask; +}; + +struct skl_ipc_header { + u32 primary; + u32 extension; +}; + +struct skl_dsp_cores { + unsigned int count; + enum skl_dsp_states *state; + int *usage_count; +}; + +/** + * skl_d0i3_data: skl D0i3 counters data struct + * + * @streaming: Count of usecases that can attempt streaming D0i3 + * @non_streaming: Count of usecases that can attempt non-streaming D0i3 + * @non_d0i3: Count of usecases that cannot attempt D0i3 + * @state: current state + * @work: D0i3 worker thread + */ +struct skl_d0i3_data { + int streaming; + int non_streaming; + int non_d0i3; + enum skl_dsp_d0i3_states state; + struct delayed_work work; +}; + +#define SKL_LIB_NAME_LENGTH 128 +#define SKL_MAX_LIB 16 + +struct skl_lib_info { + char name[SKL_LIB_NAME_LENGTH]; + const struct firmware *fw; +}; + +struct skl_sst { + struct device *dev; + struct sst_dsp *dsp; + + /* boot */ + wait_queue_head_t boot_wait; + bool boot_complete; + + /* module load */ + wait_queue_head_t mod_load_wait; + bool mod_load_complete; + bool mod_load_status; + + /* IPC messaging */ + struct sst_generic_ipc ipc; + + /* callback for miscbdge */ + void (*enable_miscbdcge)(struct device *dev, bool enable); + /* Is CGCTL.MISCBDCGE disabled */ + bool miscbdcg_disabled; + + /* Populate module information */ + struct list_head uuid_list; + + /* Is firmware loaded */ + bool fw_loaded; + + /* first boot ? */ + bool is_first_boot; + + /* multi-core */ + struct skl_dsp_cores cores; + + /* library info */ + struct skl_lib_info lib_info[SKL_MAX_LIB]; + int lib_count; + + /* Callback to update D0i3C register */ + void (*update_d0i3c)(struct device *dev, bool enable); + + struct skl_d0i3_data d0i3; + + const struct skl_dsp_ops *dsp_ops; + + /* Callback to update dynamic clock and power gating registers */ + void (*clock_power_gating)(struct device *dev, bool enable); +}; + +struct skl_ipc_init_instance_msg { + u32 module_id; + u32 instance_id; + u16 param_data_size; + u8 ppl_instance_id; + u8 core_id; + u8 domain; +}; + +struct skl_ipc_bind_unbind_msg { + u32 module_id; + u32 instance_id; + u32 dst_module_id; + u32 dst_instance_id; + u8 src_queue; + u8 dst_queue; + bool bind; +}; + +struct skl_ipc_large_config_msg { + u32 module_id; + u32 instance_id; + u32 large_param_id; + u32 param_data_size; +}; + +struct skl_ipc_d0ix_msg { + u32 module_id; + u32 instance_id; + u8 streaming; + u8 wake; +}; + +#define SKL_IPC_BOOT_MSECS 3000 + +#define SKL_IPC_D3_MASK 0 +#define SKL_IPC_D0_MASK 3 + +irqreturn_t skl_dsp_irq_thread_handler(int irq, void *context); + +int skl_ipc_create_pipeline(struct sst_generic_ipc *sst_ipc, + u16 ppl_mem_size, u8 ppl_type, u8 instance_id, u8 lp_mode); + +int skl_ipc_delete_pipeline(struct sst_generic_ipc *sst_ipc, u8 instance_id); + +int skl_ipc_set_pipeline_state(struct sst_generic_ipc *sst_ipc, + u8 instance_id, enum skl_ipc_pipeline_state state); + +int skl_ipc_save_pipeline(struct sst_generic_ipc *ipc, + u8 instance_id, int dma_id); + +int skl_ipc_restore_pipeline(struct sst_generic_ipc *ipc, u8 instance_id); + +int skl_ipc_init_instance(struct sst_generic_ipc *sst_ipc, + struct skl_ipc_init_instance_msg *msg, void *param_data); + +int skl_ipc_bind_unbind(struct sst_generic_ipc *sst_ipc, + struct skl_ipc_bind_unbind_msg *msg); + +int skl_ipc_load_modules(struct sst_generic_ipc *ipc, + u8 module_cnt, void *data); + +int skl_ipc_unload_modules(struct sst_generic_ipc *ipc, + u8 module_cnt, void *data); + +int skl_ipc_set_dx(struct sst_generic_ipc *ipc, + u8 instance_id, u16 module_id, struct skl_ipc_dxstate_info *dx); + +int skl_ipc_set_large_config(struct sst_generic_ipc *ipc, + struct skl_ipc_large_config_msg *msg, u32 *param); + +int skl_ipc_get_large_config(struct sst_generic_ipc *ipc, + struct skl_ipc_large_config_msg *msg, u32 *param); + +int skl_sst_ipc_load_library(struct sst_generic_ipc *ipc, + u8 dma_id, u8 table_id, bool wait); + +int skl_ipc_set_d0ix(struct sst_generic_ipc *ipc, + struct skl_ipc_d0ix_msg *msg); + +int skl_ipc_check_D0i0(struct sst_dsp *dsp, bool state); + +void skl_ipc_int_enable(struct sst_dsp *dsp); +void skl_ipc_op_int_enable(struct sst_dsp *ctx); +void skl_ipc_op_int_disable(struct sst_dsp *ctx); +void skl_ipc_int_disable(struct sst_dsp *dsp); + +bool skl_ipc_int_status(struct sst_dsp *dsp); +void skl_ipc_free(struct sst_generic_ipc *ipc); +int skl_ipc_init(struct device *dev, struct skl_sst *skl); +void skl_clear_module_cnt(struct sst_dsp *ctx); + +void skl_ipc_process_reply(struct sst_generic_ipc *ipc, + struct skl_ipc_header header); +int skl_ipc_process_notification(struct sst_generic_ipc *ipc, + struct skl_ipc_header header); +void skl_ipc_tx_data_copy(struct ipc_message *msg, char *tx_data, + size_t tx_size); +#endif /* __SKL_IPC_H */ diff --git a/sound/soc/intel/skylake/skl-sst-utils.c b/sound/soc/intel/skylake/skl-sst-utils.c new file mode 100644 index 000000000..2ae405617 --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-utils.c @@ -0,0 +1,448 @@ +/* + * skl-sst-utils.c - SKL sst utils functions + * + * Copyright (C) 2016 Intel Corp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include "skl-sst-dsp.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "skl-sst-ipc.h" + + +#define UUID_STR_SIZE 37 +#define DEFAULT_HASH_SHA256_LEN 32 + +/* FW Extended Manifest Header id = $AE1 */ +#define SKL_EXT_MANIFEST_HEADER_MAGIC 0x31454124 + +struct UUID { + u8 id[16]; +}; + +union seg_flags { + u32 ul; + struct { + u32 contents : 1; + u32 alloc : 1; + u32 load : 1; + u32 read_only : 1; + u32 code : 1; + u32 data : 1; + u32 _rsvd0 : 2; + u32 type : 4; + u32 _rsvd1 : 4; + u32 length : 16; + } r; +} __packed; + +struct segment_desc { + union seg_flags flags; + u32 v_base_addr; + u32 file_offset; +}; + +struct module_type { + u32 load_type : 4; + u32 auto_start : 1; + u32 domain_ll : 1; + u32 domain_dp : 1; + u32 rsvd : 25; +} __packed; + +struct adsp_module_entry { + u32 struct_id; + u8 name[8]; + struct UUID uuid; + struct module_type type; + u8 hash1[DEFAULT_HASH_SHA256_LEN]; + u32 entry_point; + u16 cfg_offset; + u16 cfg_count; + u32 affinity_mask; + u16 instance_max_count; + u16 instance_bss_size; + struct segment_desc segments[3]; +} __packed; + +struct adsp_fw_hdr { + u32 id; + u32 len; + u8 name[8]; + u32 preload_page_count; + u32 fw_image_flags; + u32 feature_mask; + u16 major; + u16 minor; + u16 hotfix; + u16 build; + u32 num_modules; + u32 hw_buf_base; + u32 hw_buf_length; + u32 load_offset; +} __packed; + +struct skl_ext_manifest_hdr { + u32 id; + u32 len; + u16 version_major; + u16 version_minor; + u32 entries; +}; + +static int skl_get_pvtid_map(struct uuid_module *module, int instance_id) +{ + int pvt_id; + + for (pvt_id = 0; pvt_id < module->max_instance; pvt_id++) { + if (module->instance_id[pvt_id] == instance_id) + return pvt_id; + } + return -EINVAL; +} + +int skl_get_pvt_instance_id_map(struct skl_sst *ctx, + int module_id, int instance_id) +{ + struct uuid_module *module; + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (module->id == module_id) + return skl_get_pvtid_map(module, instance_id); + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(skl_get_pvt_instance_id_map); + +static inline int skl_getid_32(struct uuid_module *module, u64 *val, + int word1_mask, int word2_mask) +{ + int index, max_inst, pvt_id; + u32 mask_val; + + max_inst = module->max_instance; + mask_val = (u32)(*val >> word1_mask); + + if (mask_val != 0xffffffff) { + index = ffz(mask_val); + pvt_id = index + word1_mask + word2_mask; + if (pvt_id <= (max_inst - 1)) { + *val |= 1ULL << (index + word1_mask); + return pvt_id; + } + } + + return -EINVAL; +} + +static inline int skl_pvtid_128(struct uuid_module *module) +{ + int j, i, word1_mask, word2_mask = 0, pvt_id; + + for (j = 0; j < MAX_INSTANCE_BUFF; j++) { + word1_mask = 0; + + for (i = 0; i < 2; i++) { + pvt_id = skl_getid_32(module, &module->pvt_id[j], + word1_mask, word2_mask); + if (pvt_id >= 0) + return pvt_id; + + word1_mask += 32; + if ((word1_mask + word2_mask) >= module->max_instance) + return -EINVAL; + } + + word2_mask += 64; + if (word2_mask >= module->max_instance) + return -EINVAL; + } + + return -EINVAL; +} + +/** + * skl_get_pvt_id: generate a private id for use as module id + * + * @ctx: driver context + * @uuid_mod: module's uuid + * @instance_id: module's instance id + * + * This generates a 128 bit private unique id for a module TYPE so that + * module instance is unique + */ +int skl_get_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int instance_id) +{ + struct uuid_module *module; + int pvt_id; + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { + + pvt_id = skl_pvtid_128(module); + if (pvt_id >= 0) { + module->instance_id[pvt_id] = instance_id; + + return pvt_id; + } + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(skl_get_pvt_id); + +/** + * skl_put_pvt_id: free up the private id allocated + * + * @ctx: driver context + * @uuid_mod: module's uuid + * @pvt_id: module pvt id + * + * This frees a 128 bit private unique id previously generated + */ +int skl_put_pvt_id(struct skl_sst *ctx, uuid_le *uuid_mod, int *pvt_id) +{ + int i; + struct uuid_module *module; + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { + + if (*pvt_id != 0) + i = (*pvt_id) / 64; + else + i = 0; + + module->pvt_id[i] &= ~(1 << (*pvt_id)); + *pvt_id = -1; + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(skl_put_pvt_id); + +/* + * Parse the firmware binary to get the UUID, module id + * and loadable flags + */ +int snd_skl_parse_uuids(struct sst_dsp *ctx, const struct firmware *fw, + unsigned int offset, int index) +{ + struct adsp_fw_hdr *adsp_hdr; + struct adsp_module_entry *mod_entry; + int i, num_entry, size; + uuid_le *uuid_bin; + const char *buf; + struct skl_sst *skl = ctx->thread_context; + struct uuid_module *module; + struct firmware stripped_fw; + unsigned int safe_file; + int ret = 0; + + /* Get the FW pointer to derive ADSP header */ + stripped_fw.data = fw->data; + stripped_fw.size = fw->size; + + skl_dsp_strip_extended_manifest(&stripped_fw); + + buf = stripped_fw.data; + + /* check if we have enough space in file to move to header */ + safe_file = sizeof(*adsp_hdr) + offset; + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No space for hdr\n"); + return -EINVAL; + } + + adsp_hdr = (struct adsp_fw_hdr *)(buf + offset); + + /* check 1st module entry is in file */ + safe_file += adsp_hdr->len + sizeof(*mod_entry); + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No module entry\n"); + return -EINVAL; + } + + mod_entry = (struct adsp_module_entry *) + (buf + offset + adsp_hdr->len); + + num_entry = adsp_hdr->num_modules; + + /* check all entries are in file */ + safe_file += num_entry * sizeof(*mod_entry); + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No modules\n"); + return -EINVAL; + } + + + /* + * Read the UUID(GUID) from FW Manifest. + * + * The 16 byte UUID format is: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX + * Populate the UUID table to store module_id and loadable flags + * for the module. + */ + + for (i = 0; i < num_entry; i++, mod_entry++) { + module = kzalloc(sizeof(*module), GFP_KERNEL); + if (!module) { + ret = -ENOMEM; + goto free_uuid_list; + } + + uuid_bin = (uuid_le *)mod_entry->uuid.id; + memcpy(&module->uuid, uuid_bin, sizeof(module->uuid)); + + module->id = (i | (index << 12)); + module->is_loadable = mod_entry->type.load_type; + module->max_instance = mod_entry->instance_max_count; + size = sizeof(int) * mod_entry->instance_max_count; + module->instance_id = devm_kzalloc(ctx->dev, size, GFP_KERNEL); + if (!module->instance_id) { + ret = -ENOMEM; + goto free_uuid_list; + } + + list_add_tail(&module->list, &skl->uuid_list); + + dev_dbg(ctx->dev, + "Adding uuid :%pUL mod id: %d Loadable: %d\n", + &module->uuid, module->id, module->is_loadable); + } + + return 0; + +free_uuid_list: + skl_freeup_uuid_list(skl); + return ret; +} + +void skl_freeup_uuid_list(struct skl_sst *ctx) +{ + struct uuid_module *uuid, *_uuid; + + list_for_each_entry_safe(uuid, _uuid, &ctx->uuid_list, list) { + list_del(&uuid->list); + kfree(uuid); + } +} + +/* + * some firmware binary contains some extended manifest. This needs + * to be stripped in that case before we load and use that image. + * + * Get the module id for the module by checking + * the table for the UUID for the module + */ +int skl_dsp_strip_extended_manifest(struct firmware *fw) +{ + struct skl_ext_manifest_hdr *hdr; + + /* check if fw file is greater than header we are looking */ + if (fw->size < sizeof(hdr)) { + pr_err("%s: Firmware file small, no hdr\n", __func__); + return -EINVAL; + } + + hdr = (struct skl_ext_manifest_hdr *)fw->data; + + if (hdr->id == SKL_EXT_MANIFEST_HEADER_MAGIC) { + fw->size -= hdr->len; + fw->data += hdr->len; + } + + return 0; +} + +int skl_sst_ctx_init(struct device *dev, int irq, const char *fw_name, + struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp, + struct sst_dsp_device *skl_dev) +{ + struct skl_sst *skl; + struct sst_dsp *sst; + + skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); + if (skl == NULL) + return -ENOMEM; + + skl->dev = dev; + skl_dev->thread_context = skl; + INIT_LIST_HEAD(&skl->uuid_list); + skl->dsp = skl_dsp_ctx_init(dev, skl_dev, irq); + if (!skl->dsp) { + dev_err(skl->dev, "%s: no device\n", __func__); + return -ENODEV; + } + + sst = skl->dsp; + sst->fw_name = fw_name; + sst->dsp_ops = dsp_ops; + init_waitqueue_head(&skl->mod_load_wait); + INIT_LIST_HEAD(&sst->module_list); + + skl->is_first_boot = true; + if (dsp) + *dsp = skl; + + return 0; +} + +int skl_prepare_lib_load(struct skl_sst *skl, struct skl_lib_info *linfo, + struct firmware *stripped_fw, + unsigned int hdr_offset, int index) +{ + int ret; + struct sst_dsp *dsp = skl->dsp; + + if (linfo->fw == NULL) { + ret = request_firmware(&linfo->fw, linfo->name, + skl->dev); + if (ret < 0) { + dev_err(skl->dev, "Request lib %s failed:%d\n", + linfo->name, ret); + return ret; + } + } + + if (skl->is_first_boot) { + ret = snd_skl_parse_uuids(dsp, linfo->fw, hdr_offset, index); + if (ret < 0) + return ret; + } + + stripped_fw->data = linfo->fw->data; + stripped_fw->size = linfo->fw->size; + skl_dsp_strip_extended_manifest(stripped_fw); + + return 0; +} + +void skl_release_library(struct skl_lib_info *linfo, int lib_count) +{ + int i; + + /* library indices start from 1 to N. 0 represents base FW */ + for (i = 1; i < lib_count; i++) { + if (linfo[i].fw) { + release_firmware(linfo[i].fw); + linfo[i].fw = NULL; + } + } +} diff --git a/sound/soc/intel/skylake/skl-sst.c b/sound/soc/intel/skylake/skl-sst.c new file mode 100644 index 000000000..5951bbdf1 --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst.c @@ -0,0 +1,611 @@ +/* + * skl-sst.c - HDA DSP library functions for SKL platform + * + * Copyright (C) 2014-15, Intel Corporation. + * Author:Rafal Redzimski + * Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "../common/sst-ipc.h" +#include "skl-sst-ipc.h" + +#define SKL_BASEFW_TIMEOUT 300 +#define SKL_INIT_TIMEOUT 1000 + +/* Intel HD Audio SRAM Window 0*/ +#define SKL_ADSP_SRAM0_BASE 0x8000 + +/* Firmware status window */ +#define SKL_ADSP_FW_STATUS SKL_ADSP_SRAM0_BASE +#define SKL_ADSP_ERROR_CODE (SKL_ADSP_FW_STATUS + 0x4) + +#define SKL_NUM_MODULES 1 + +static bool skl_check_fw_status(struct sst_dsp *ctx, u32 status) +{ + u32 cur_sts; + + cur_sts = sst_dsp_shim_read(ctx, SKL_ADSP_FW_STATUS) & SKL_FW_STS_MASK; + + return (cur_sts == status); +} + +static int skl_transfer_firmware(struct sst_dsp *ctx, + const void *basefw, u32 base_fw_size) +{ + int ret = 0; + + ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, basefw, base_fw_size, + true); + if (ret < 0) + return ret; + + ret = sst_dsp_register_poll(ctx, + SKL_ADSP_FW_STATUS, + SKL_FW_STS_MASK, + SKL_FW_RFW_START, + SKL_BASEFW_TIMEOUT, + "Firmware boot"); + + ctx->cl_dev.ops.cl_stop_dma(ctx); + + return ret; +} + +#define SKL_ADSP_FW_BIN_HDR_OFFSET 0x284 + +static int skl_load_base_firmware(struct sst_dsp *ctx) +{ + int ret = 0, i; + struct skl_sst *skl = ctx->thread_context; + struct firmware stripped_fw; + u32 reg; + + skl->boot_complete = false; + init_waitqueue_head(&skl->boot_wait); + + if (ctx->fw == NULL) { + ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); + if (ret < 0) { + dev_err(ctx->dev, "Request firmware failed %d\n", ret); + return -EIO; + } + } + + /* prase uuids on first boot */ + if (skl->is_first_boot) { + ret = snd_skl_parse_uuids(ctx, ctx->fw, SKL_ADSP_FW_BIN_HDR_OFFSET, 0); + if (ret < 0) { + dev_err(ctx->dev, "UUID parsing err: %d\n", ret); + release_firmware(ctx->fw); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + return ret; + } + } + + /* check for extended manifest */ + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + + skl_dsp_strip_extended_manifest(&stripped_fw); + + ret = skl_dsp_boot(ctx); + if (ret < 0) { + dev_err(ctx->dev, "Boot dsp core failed ret: %d\n", ret); + goto skl_load_base_firmware_failed; + } + + ret = skl_cldma_prepare(ctx); + if (ret < 0) { + dev_err(ctx->dev, "CL dma prepare failed : %d\n", ret); + goto skl_load_base_firmware_failed; + } + + /* enable Interrupt */ + skl_ipc_int_enable(ctx); + skl_ipc_op_int_enable(ctx); + + /* check ROM Status */ + for (i = SKL_INIT_TIMEOUT; i > 0; --i) { + if (skl_check_fw_status(ctx, SKL_FW_INIT)) { + dev_dbg(ctx->dev, + "ROM loaded, we can continue with FW loading\n"); + break; + } + mdelay(1); + } + if (!i) { + reg = sst_dsp_shim_read(ctx, SKL_ADSP_FW_STATUS); + dev_err(ctx->dev, + "Timeout waiting for ROM init done, reg:0x%x\n", reg); + ret = -EIO; + goto transfer_firmware_failed; + } + + ret = skl_transfer_firmware(ctx, stripped_fw.data, stripped_fw.size); + if (ret < 0) { + dev_err(ctx->dev, "Transfer firmware failed%d\n", ret); + goto transfer_firmware_failed; + } else { + ret = wait_event_timeout(skl->boot_wait, skl->boot_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + if (ret == 0) { + dev_err(ctx->dev, "DSP boot failed, FW Ready timed-out\n"); + ret = -EIO; + goto transfer_firmware_failed; + } + + dev_dbg(ctx->dev, "Download firmware successful%d\n", ret); + skl->fw_loaded = true; + } + return 0; +transfer_firmware_failed: + ctx->cl_dev.ops.cl_cleanup_controller(ctx); +skl_load_base_firmware_failed: + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + release_firmware(ctx->fw); + ctx->fw = NULL; + return ret; +} + +static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) +{ + int ret; + struct skl_ipc_dxstate_info dx; + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); + + /* If core0 is being turned on, we need to load the FW */ + if (core_id == SKL_DSP_CORE0_ID) { + ret = skl_load_base_firmware(ctx); + if (ret < 0) { + dev_err(ctx->dev, "unable to load firmware\n"); + return ret; + } + + /* load libs as they are also lost on D3 */ + if (skl->lib_count > 1) { + ret = ctx->fw_ops.load_library(ctx, skl->lib_info, + skl->lib_count); + if (ret < 0) { + dev_err(ctx->dev, "reload libs failed: %d\n", + ret); + return ret; + } + + } + } + + /* + * If any core other than core 0 is being moved to D0, enable the + * core and send the set dx IPC for the core. + */ + if (core_id != SKL_DSP_CORE0_ID) { + ret = skl_dsp_enable_core(ctx, core_mask); + if (ret < 0) + return ret; + + dx.core_mask = core_mask; + dx.dx_mask = core_mask; + + ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, + SKL_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, "Failed to set dsp to D0:core id= %d\n", + core_id); + skl_dsp_disable_core(ctx, core_mask); + } + } + + skl->cores.state[core_id] = SKL_DSP_RUNNING; + + return 0; +} + +static int skl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) +{ + int ret; + struct skl_ipc_dxstate_info dx; + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); + + dx.core_mask = core_mask; + dx.dx_mask = SKL_IPC_D3_MASK; + + ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, SKL_BASE_FW_MODULE_ID, &dx); + if (ret < 0) + dev_err(ctx->dev, "set Dx core %d fail: %d\n", core_id, ret); + + if (core_id == SKL_DSP_CORE0_ID) { + /* disable Interrupt */ + ctx->cl_dev.ops.cl_cleanup_controller(ctx); + skl_cldma_int_disable(ctx); + skl_ipc_op_int_disable(ctx); + skl_ipc_int_disable(ctx); + } + + ret = skl_dsp_disable_core(ctx, core_mask); + if (ret < 0) + return ret; + + skl->cores.state[core_id] = SKL_DSP_RESET; + return ret; +} + +static unsigned int skl_get_errorcode(struct sst_dsp *ctx) +{ + return sst_dsp_shim_read(ctx, SKL_ADSP_ERROR_CODE); +} + +/* + * since get/set_module are called from DAPM context, + * we don't need lock for usage count + */ +static int skl_get_module(struct sst_dsp *ctx, u16 mod_id) +{ + struct skl_module_table *module; + + list_for_each_entry(module, &ctx->module_list, list) { + if (module->mod_info->mod_id == mod_id) + return ++module->usage_cnt; + } + + return -EINVAL; +} + +static int skl_put_module(struct sst_dsp *ctx, u16 mod_id) +{ + struct skl_module_table *module; + + list_for_each_entry(module, &ctx->module_list, list) { + if (module->mod_info->mod_id == mod_id) + return --module->usage_cnt; + } + + return -EINVAL; +} + +static struct skl_module_table *skl_fill_module_table(struct sst_dsp *ctx, + char *mod_name, int mod_id) +{ + const struct firmware *fw; + struct skl_module_table *skl_module; + unsigned int size; + int ret; + + ret = request_firmware(&fw, mod_name, ctx->dev); + if (ret < 0) { + dev_err(ctx->dev, "Request Module %s failed :%d\n", + mod_name, ret); + return NULL; + } + + skl_module = devm_kzalloc(ctx->dev, sizeof(*skl_module), GFP_KERNEL); + if (skl_module == NULL) { + release_firmware(fw); + return NULL; + } + + size = sizeof(*skl_module->mod_info); + skl_module->mod_info = devm_kzalloc(ctx->dev, size, GFP_KERNEL); + if (skl_module->mod_info == NULL) { + release_firmware(fw); + return NULL; + } + + skl_module->mod_info->mod_id = mod_id; + skl_module->mod_info->fw = fw; + list_add(&skl_module->list, &ctx->module_list); + + return skl_module; +} + +/* get a module from it's unique ID */ +static struct skl_module_table *skl_module_get_from_id( + struct sst_dsp *ctx, u16 mod_id) +{ + struct skl_module_table *module; + + if (list_empty(&ctx->module_list)) { + dev_err(ctx->dev, "Module list is empty\n"); + return NULL; + } + + list_for_each_entry(module, &ctx->module_list, list) { + if (module->mod_info->mod_id == mod_id) + return module; + } + + return NULL; +} + +static int skl_transfer_module(struct sst_dsp *ctx, const void *data, + u32 size, u16 mod_id, u8 table_id, bool is_module) +{ + int ret, bytes_left, curr_pos; + struct skl_sst *skl = ctx->thread_context; + skl->mod_load_complete = false; + + bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, data, size, false); + if (bytes_left < 0) + return bytes_left; + + /* check is_module flag to load module or library */ + if (is_module) + ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES, &mod_id); + else + ret = skl_sst_ipc_load_library(&skl->ipc, 0, table_id, false); + + if (ret < 0) { + dev_err(ctx->dev, "Failed to Load %s with err %d\n", + is_module ? "module" : "lib", ret); + goto out; + } + + /* + * if bytes_left > 0 then wait for BDL complete interrupt and + * copy the next chunk till bytes_left is 0. if bytes_left is + * is zero, then wait for load module IPC reply + */ + while (bytes_left > 0) { + curr_pos = size - bytes_left; + + ret = skl_cldma_wait_interruptible(ctx); + if (ret < 0) + goto out; + + bytes_left = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, + data + curr_pos, + bytes_left, false); + } + + ret = wait_event_timeout(skl->mod_load_wait, skl->mod_load_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + if (ret == 0 || !skl->mod_load_status) { + dev_err(ctx->dev, "Module Load failed\n"); + ret = -EIO; + } + +out: + ctx->cl_dev.ops.cl_stop_dma(ctx); + + return ret; +} + +static int +skl_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count) +{ + struct skl_sst *skl = ctx->thread_context; + struct firmware stripped_fw; + int ret, i; + + /* library indices start from 1 to N. 0 represents base FW */ + for (i = 1; i < lib_count; i++) { + ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw, + SKL_ADSP_FW_BIN_HDR_OFFSET, i); + if (ret < 0) + goto load_library_failed; + ret = skl_transfer_module(ctx, stripped_fw.data, + stripped_fw.size, 0, i, false); + if (ret < 0) + goto load_library_failed; + } + + return 0; + +load_library_failed: + skl_release_library(linfo, lib_count); + return ret; +} + +static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, u8 *guid) +{ + struct skl_module_table *module_entry = NULL; + int ret = 0; + char mod_name[64]; /* guid str = 32 chars + 4 hyphens */ + uuid_le *uuid_mod; + + uuid_mod = (uuid_le *)guid; + snprintf(mod_name, sizeof(mod_name), "%s%pUL%s", + "intel/dsp_fw_", uuid_mod, ".bin"); + + module_entry = skl_module_get_from_id(ctx, mod_id); + if (module_entry == NULL) { + module_entry = skl_fill_module_table(ctx, mod_name, mod_id); + if (module_entry == NULL) { + dev_err(ctx->dev, "Failed to Load module\n"); + return -EINVAL; + } + } + + if (!module_entry->usage_cnt) { + ret = skl_transfer_module(ctx, module_entry->mod_info->fw->data, + module_entry->mod_info->fw->size, + mod_id, 0, true); + if (ret < 0) { + dev_err(ctx->dev, "Failed to Load module\n"); + return ret; + } + } + + ret = skl_get_module(ctx, mod_id); + + return ret; +} + +static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id) +{ + int usage_cnt; + struct skl_sst *skl = ctx->thread_context; + int ret = 0; + + usage_cnt = skl_put_module(ctx, mod_id); + if (usage_cnt < 0) { + dev_err(ctx->dev, "Module bad usage cnt!:%d\n", usage_cnt); + return -EIO; + } + + /* if module is used by others return, no need to unload */ + if (usage_cnt > 0) + return 0; + + ret = skl_ipc_unload_modules(&skl->ipc, + SKL_NUM_MODULES, &mod_id); + if (ret < 0) { + dev_err(ctx->dev, "Failed to UnLoad module\n"); + skl_get_module(ctx, mod_id); + return ret; + } + + return ret; +} + +void skl_clear_module_cnt(struct sst_dsp *ctx) +{ + struct skl_module_table *module; + + if (list_empty(&ctx->module_list)) + return; + + list_for_each_entry(module, &ctx->module_list, list) { + module->usage_cnt = 0; + } +} +EXPORT_SYMBOL_GPL(skl_clear_module_cnt); + +static void skl_clear_module_table(struct sst_dsp *ctx) +{ + struct skl_module_table *module, *tmp; + + if (list_empty(&ctx->module_list)) + return; + + list_for_each_entry_safe(module, tmp, &ctx->module_list, list) { + list_del(&module->list); + release_firmware(module->mod_info->fw); + } +} + +static const struct skl_dsp_fw_ops skl_fw_ops = { + .set_state_D0 = skl_set_dsp_D0, + .set_state_D3 = skl_set_dsp_D3, + .load_fw = skl_load_base_firmware, + .get_fw_errcode = skl_get_errorcode, + .load_library = skl_load_library, + .load_mod = skl_load_module, + .unload_mod = skl_unload_module, +}; + +static struct sst_ops skl_ops = { + .irq_handler = skl_dsp_sst_interrupt, + .write = sst_shim32_write, + .read = sst_shim32_read, + .ram_read = sst_memcpy_fromio_32, + .ram_write = sst_memcpy_toio_32, + .free = skl_dsp_free, +}; + +static struct sst_dsp_device skl_dev = { + .thread = skl_dsp_irq_thread_handler, + .ops = &skl_ops, +}; + +int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, + const char *fw_name, struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp) +{ + struct skl_sst *skl; + struct sst_dsp *sst; + int ret; + + ret = skl_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &skl_dev); + if (ret < 0) { + dev_err(dev, "%s: no device\n", __func__); + return ret; + } + + skl = *dsp; + sst = skl->dsp; + sst->addr.lpe = mmio_base; + sst->addr.shim = mmio_base; + sst->addr.sram0_base = SKL_ADSP_SRAM0_BASE; + sst->addr.sram1_base = SKL_ADSP_SRAM1_BASE; + sst->addr.w0_stat_sz = SKL_ADSP_W0_STAT_SZ; + sst->addr.w0_up_sz = SKL_ADSP_W0_UP_SZ; + + sst_dsp_mailbox_init(sst, (SKL_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ), + SKL_ADSP_W0_UP_SZ, SKL_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ); + + ret = skl_ipc_init(dev, skl); + if (ret) { + skl_dsp_free(sst); + return ret; + } + + sst->fw_ops = skl_fw_ops; + + return skl_dsp_acquire_irq(sst); +} +EXPORT_SYMBOL_GPL(skl_sst_dsp_init); + +int skl_sst_init_fw(struct device *dev, struct skl_sst *ctx) +{ + int ret; + struct sst_dsp *sst = ctx->dsp; + + ret = sst->fw_ops.load_fw(sst); + if (ret < 0) { + dev_err(dev, "Load base fw failed : %d\n", ret); + return ret; + } + + skl_dsp_init_core_state(sst); + + if (ctx->lib_count > 1) { + ret = sst->fw_ops.load_library(sst, ctx->lib_info, + ctx->lib_count); + if (ret < 0) { + dev_err(dev, "Load Library failed : %x\n", ret); + return ret; + } + } + ctx->is_first_boot = false; + + return 0; +} +EXPORT_SYMBOL_GPL(skl_sst_init_fw); + +void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) +{ + + if (ctx->dsp->fw) + release_firmware(ctx->dsp->fw); + skl_clear_module_table(ctx->dsp); + skl_freeup_uuid_list(ctx); + skl_ipc_free(&ctx->ipc); + ctx->dsp->ops->free(ctx->dsp); + if (ctx->boot_complete) { + ctx->dsp->cl_dev.ops.cl_cleanup_controller(ctx->dsp); + skl_cldma_int_disable(ctx->dsp); + } +} +EXPORT_SYMBOL_GPL(skl_sst_dsp_cleanup); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Skylake IPC driver"); diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c new file mode 100644 index 000000000..f99c600f8 --- /dev/null +++ b/sound/soc/intel/skylake/skl-topology.c @@ -0,0 +1,3761 @@ +/* + * skl-topology.c - Implements Platform component ALSA controls/widget + * handlers. + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "skl-sst-dsp.h" +#include "skl-sst-ipc.h" +#include "skl-topology.h" +#include "skl.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" + +#define SKL_CH_FIXUP_MASK (1 << 0) +#define SKL_RATE_FIXUP_MASK (1 << 1) +#define SKL_FMT_FIXUP_MASK (1 << 2) +#define SKL_IN_DIR_BIT_MASK BIT(0) +#define SKL_PIN_COUNT_MASK GENMASK(7, 4) + +static const int mic_mono_list[] = { +0, 1, 2, 3, +}; +static const int mic_stereo_list[][SKL_CH_STEREO] = { +{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}, +}; +static const int mic_trio_list[][SKL_CH_TRIO] = { +{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3}, +}; +static const int mic_quatro_list[][SKL_CH_QUATRO] = { +{0, 1, 2, 3}, +}; + +#define CHECK_HW_PARAMS(ch, freq, bps, prm_ch, prm_freq, prm_bps) \ + ((ch == prm_ch) && (bps == prm_bps) && (freq == prm_freq)) + +void skl_tplg_d0i3_get(struct skl *skl, enum d0i3_capability caps) +{ + struct skl_d0i3_data *d0i3 = &skl->skl_sst->d0i3; + + switch (caps) { + case SKL_D0I3_NONE: + d0i3->non_d0i3++; + break; + + case SKL_D0I3_STREAMING: + d0i3->streaming++; + break; + + case SKL_D0I3_NON_STREAMING: + d0i3->non_streaming++; + break; + } +} + +void skl_tplg_d0i3_put(struct skl *skl, enum d0i3_capability caps) +{ + struct skl_d0i3_data *d0i3 = &skl->skl_sst->d0i3; + + switch (caps) { + case SKL_D0I3_NONE: + d0i3->non_d0i3--; + break; + + case SKL_D0I3_STREAMING: + d0i3->streaming--; + break; + + case SKL_D0I3_NON_STREAMING: + d0i3->non_streaming--; + break; + } +} + +/* + * SKL DSP driver modelling uses only few DAPM widgets so for rest we will + * ignore. This helpers checks if the SKL driver handles this widget type + */ +static int is_skl_dsp_widget_type(struct snd_soc_dapm_widget *w, + struct device *dev) +{ + if (w->dapm->dev != dev) + return false; + + switch (w->id) { + case snd_soc_dapm_dai_link: + case snd_soc_dapm_dai_in: + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + case snd_soc_dapm_dai_out: + case snd_soc_dapm_switch: + case snd_soc_dapm_output: + case snd_soc_dapm_mux: + + return false; + default: + return true; + } +} + +/* + * Each pipelines needs memory to be allocated. Check if we have free memory + * from available pool. + */ +static bool skl_is_pipe_mem_avail(struct skl *skl, + struct skl_module_cfg *mconfig) +{ + struct skl_sst *ctx = skl->skl_sst; + + if (skl->resource.mem + mconfig->pipe->memory_pages > + skl->resource.max_mem) { + dev_err(ctx->dev, + "%s: module_id %d instance %d\n", __func__, + mconfig->id.module_id, + mconfig->id.instance_id); + dev_err(ctx->dev, + "exceeds ppl memory available %d mem %d\n", + skl->resource.max_mem, skl->resource.mem); + return false; + } else { + return true; + } +} + +/* + * Add the mem to the mem pool. This is freed when pipe is deleted. + * Note: DSP does actual memory management we only keep track for complete + * pool + */ +static void skl_tplg_alloc_pipe_mem(struct skl *skl, + struct skl_module_cfg *mconfig) +{ + skl->resource.mem += mconfig->pipe->memory_pages; +} + +/* + * Pipeline needs needs DSP CPU resources for computation, this is + * quantified in MCPS (Million Clocks Per Second) required for module/pipe + * + * Each pipelines needs mcps to be allocated. Check if we have mcps for this + * pipe. + */ + +static bool skl_is_pipe_mcps_avail(struct skl *skl, + struct skl_module_cfg *mconfig) +{ + struct skl_sst *ctx = skl->skl_sst; + u8 res_idx = mconfig->res_idx; + struct skl_module_res *res = &mconfig->module->resources[res_idx]; + + if (skl->resource.mcps + res->cps > skl->resource.max_mcps) { + dev_err(ctx->dev, + "%s: module_id %d instance %d\n", __func__, + mconfig->id.module_id, mconfig->id.instance_id); + dev_err(ctx->dev, + "exceeds ppl mcps available %d > mem %d\n", + skl->resource.max_mcps, skl->resource.mcps); + return false; + } else { + return true; + } +} + +static void skl_tplg_alloc_pipe_mcps(struct skl *skl, + struct skl_module_cfg *mconfig) +{ + u8 res_idx = mconfig->res_idx; + struct skl_module_res *res = &mconfig->module->resources[res_idx]; + + skl->resource.mcps += res->cps; +} + +/* + * Free the mcps when tearing down + */ +static void +skl_tplg_free_pipe_mcps(struct skl *skl, struct skl_module_cfg *mconfig) +{ + u8 res_idx = mconfig->res_idx; + struct skl_module_res *res = &mconfig->module->resources[res_idx]; + + skl->resource.mcps -= res->cps; +} + +/* + * Free the memory when tearing down + */ +static void +skl_tplg_free_pipe_mem(struct skl *skl, struct skl_module_cfg *mconfig) +{ + skl->resource.mem -= mconfig->pipe->memory_pages; +} + + +static void skl_dump_mconfig(struct skl_sst *ctx, + struct skl_module_cfg *mcfg) +{ + struct skl_module_iface *iface = &mcfg->module->formats[0]; + + dev_dbg(ctx->dev, "Dumping config\n"); + dev_dbg(ctx->dev, "Input Format:\n"); + dev_dbg(ctx->dev, "channels = %d\n", iface->inputs[0].fmt.channels); + dev_dbg(ctx->dev, "s_freq = %d\n", iface->inputs[0].fmt.s_freq); + dev_dbg(ctx->dev, "ch_cfg = %d\n", iface->inputs[0].fmt.ch_cfg); + dev_dbg(ctx->dev, "valid bit depth = %d\n", + iface->inputs[0].fmt.valid_bit_depth); + dev_dbg(ctx->dev, "Output Format:\n"); + dev_dbg(ctx->dev, "channels = %d\n", iface->outputs[0].fmt.channels); + dev_dbg(ctx->dev, "s_freq = %d\n", iface->outputs[0].fmt.s_freq); + dev_dbg(ctx->dev, "valid bit depth = %d\n", + iface->outputs[0].fmt.valid_bit_depth); + dev_dbg(ctx->dev, "ch_cfg = %d\n", iface->outputs[0].fmt.ch_cfg); +} + +static void skl_tplg_update_chmap(struct skl_module_fmt *fmt, int chs) +{ + int slot_map = 0xFFFFFFFF; + int start_slot = 0; + int i; + + for (i = 0; i < chs; i++) { + /* + * For 2 channels with starting slot as 0, slot map will + * look like 0xFFFFFF10. + */ + slot_map &= (~(0xF << (4 * i)) | (start_slot << (4 * i))); + start_slot++; + } + fmt->ch_map = slot_map; +} + +static void skl_tplg_update_params(struct skl_module_fmt *fmt, + struct skl_pipe_params *params, int fixup) +{ + if (fixup & SKL_RATE_FIXUP_MASK) + fmt->s_freq = params->s_freq; + if (fixup & SKL_CH_FIXUP_MASK) { + fmt->channels = params->ch; + skl_tplg_update_chmap(fmt, fmt->channels); + } + if (fixup & SKL_FMT_FIXUP_MASK) { + fmt->valid_bit_depth = skl_get_bit_depth(params->s_fmt); + + /* + * 16 bit is 16 bit container whereas 24 bit is in 32 bit + * container so update bit depth accordingly + */ + switch (fmt->valid_bit_depth) { + case SKL_DEPTH_16BIT: + fmt->bit_depth = fmt->valid_bit_depth; + break; + + default: + fmt->bit_depth = SKL_DEPTH_32BIT; + break; + } + } + +} + +/* + * A pipeline may have modules which impact the pcm parameters, like SRC, + * channel converter, format converter. + * We need to calculate the output params by applying the 'fixup' + * Topology will tell driver which type of fixup is to be applied by + * supplying the fixup mask, so based on that we calculate the output + * + * Now In FE the pcm hw_params is source/target format. Same is applicable + * for BE with its hw_params invoked. + * here based on FE, BE pipeline and direction we calculate the input and + * outfix and then apply that for a module + */ +static void skl_tplg_update_params_fixup(struct skl_module_cfg *m_cfg, + struct skl_pipe_params *params, bool is_fe) +{ + int in_fixup, out_fixup; + struct skl_module_fmt *in_fmt, *out_fmt; + + /* Fixups will be applied to pin 0 only */ + in_fmt = &m_cfg->module->formats[0].inputs[0].fmt; + out_fmt = &m_cfg->module->formats[0].outputs[0].fmt; + + if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (is_fe) { + in_fixup = m_cfg->params_fixup; + out_fixup = (~m_cfg->converter) & + m_cfg->params_fixup; + } else { + out_fixup = m_cfg->params_fixup; + in_fixup = (~m_cfg->converter) & + m_cfg->params_fixup; + } + } else { + if (is_fe) { + out_fixup = m_cfg->params_fixup; + in_fixup = (~m_cfg->converter) & + m_cfg->params_fixup; + } else { + in_fixup = m_cfg->params_fixup; + out_fixup = (~m_cfg->converter) & + m_cfg->params_fixup; + } + } + + skl_tplg_update_params(in_fmt, params, in_fixup); + skl_tplg_update_params(out_fmt, params, out_fixup); +} + +/* + * A module needs input and output buffers, which are dependent upon pcm + * params, so once we have calculate params, we need buffer calculation as + * well. + */ +static void skl_tplg_update_buffer_size(struct skl_sst *ctx, + struct skl_module_cfg *mcfg) +{ + int multiplier = 1; + struct skl_module_fmt *in_fmt, *out_fmt; + struct skl_module_res *res; + + /* Since fixups is applied to pin 0 only, ibs, obs needs + * change for pin 0 only + */ + res = &mcfg->module->resources[0]; + in_fmt = &mcfg->module->formats[0].inputs[0].fmt; + out_fmt = &mcfg->module->formats[0].outputs[0].fmt; + + if (mcfg->m_type == SKL_MODULE_TYPE_SRCINT) + multiplier = 5; + + res->ibs = DIV_ROUND_UP(in_fmt->s_freq, 1000) * + in_fmt->channels * (in_fmt->bit_depth >> 3) * + multiplier; + + res->obs = DIV_ROUND_UP(out_fmt->s_freq, 1000) * + out_fmt->channels * (out_fmt->bit_depth >> 3) * + multiplier; +} + +static u8 skl_tplg_be_dev_type(int dev_type) +{ + int ret; + + switch (dev_type) { + case SKL_DEVICE_BT: + ret = NHLT_DEVICE_BT; + break; + + case SKL_DEVICE_DMIC: + ret = NHLT_DEVICE_DMIC; + break; + + case SKL_DEVICE_I2S: + ret = NHLT_DEVICE_I2S; + break; + + default: + ret = NHLT_DEVICE_INVALID; + break; + } + + return ret; +} + +static int skl_tplg_update_be_blob(struct snd_soc_dapm_widget *w, + struct skl_sst *ctx) +{ + struct skl_module_cfg *m_cfg = w->priv; + int link_type, dir; + u32 ch, s_freq, s_fmt; + struct nhlt_specific_cfg *cfg; + struct skl *skl = get_skl_ctx(ctx->dev); + u8 dev_type = skl_tplg_be_dev_type(m_cfg->dev_type); + int fmt_idx = m_cfg->fmt_idx; + struct skl_module_iface *m_iface = &m_cfg->module->formats[fmt_idx]; + + /* check if we already have blob */ + if (m_cfg->formats_config.caps_size > 0) + return 0; + + dev_dbg(ctx->dev, "Applying default cfg blob\n"); + switch (m_cfg->dev_type) { + case SKL_DEVICE_DMIC: + link_type = NHLT_LINK_DMIC; + dir = SNDRV_PCM_STREAM_CAPTURE; + s_freq = m_iface->inputs[0].fmt.s_freq; + s_fmt = m_iface->inputs[0].fmt.bit_depth; + ch = m_iface->inputs[0].fmt.channels; + break; + + case SKL_DEVICE_I2S: + link_type = NHLT_LINK_SSP; + if (m_cfg->hw_conn_type == SKL_CONN_SOURCE) { + dir = SNDRV_PCM_STREAM_PLAYBACK; + s_freq = m_iface->outputs[0].fmt.s_freq; + s_fmt = m_iface->outputs[0].fmt.bit_depth; + ch = m_iface->outputs[0].fmt.channels; + } else { + dir = SNDRV_PCM_STREAM_CAPTURE; + s_freq = m_iface->inputs[0].fmt.s_freq; + s_fmt = m_iface->inputs[0].fmt.bit_depth; + ch = m_iface->inputs[0].fmt.channels; + } + break; + + default: + return -EINVAL; + } + + /* update the blob based on virtual bus_id and default params */ + cfg = skl_get_ep_blob(skl, m_cfg->vbus_id, link_type, + s_fmt, ch, s_freq, dir, dev_type); + if (cfg) { + m_cfg->formats_config.caps_size = cfg->size; + m_cfg->formats_config.caps = (u32 *) &cfg->caps; + } else { + dev_err(ctx->dev, "Blob NULL for id %x type %d dirn %d\n", + m_cfg->vbus_id, link_type, dir); + dev_err(ctx->dev, "PCM: ch %d, freq %d, fmt %d\n", + ch, s_freq, s_fmt); + return -EIO; + } + + return 0; +} + +static void skl_tplg_update_module_params(struct snd_soc_dapm_widget *w, + struct skl_sst *ctx) +{ + struct skl_module_cfg *m_cfg = w->priv; + struct skl_pipe_params *params = m_cfg->pipe->p_params; + int p_conn_type = m_cfg->pipe->conn_type; + bool is_fe; + + if (!m_cfg->params_fixup) + return; + + dev_dbg(ctx->dev, "Mconfig for widget=%s BEFORE updation\n", + w->name); + + skl_dump_mconfig(ctx, m_cfg); + + if (p_conn_type == SKL_PIPE_CONN_TYPE_FE) + is_fe = true; + else + is_fe = false; + + skl_tplg_update_params_fixup(m_cfg, params, is_fe); + skl_tplg_update_buffer_size(ctx, m_cfg); + + dev_dbg(ctx->dev, "Mconfig for widget=%s AFTER updation\n", + w->name); + + skl_dump_mconfig(ctx, m_cfg); +} + +/* + * some modules can have multiple params set from user control and + * need to be set after module is initialized. If set_param flag is + * set module params will be done after module is initialised. + */ +static int skl_tplg_set_module_params(struct snd_soc_dapm_widget *w, + struct skl_sst *ctx) +{ + int i, ret; + struct skl_module_cfg *mconfig = w->priv; + const struct snd_kcontrol_new *k; + struct soc_bytes_ext *sb; + struct skl_algo_data *bc; + struct skl_specific_cfg *sp_cfg; + + if (mconfig->formats_config.caps_size > 0 && + mconfig->formats_config.set_params == SKL_PARAM_SET) { + sp_cfg = &mconfig->formats_config; + ret = skl_set_module_params(ctx, sp_cfg->caps, + sp_cfg->caps_size, + sp_cfg->param_id, mconfig); + if (ret < 0) + return ret; + } + + for (i = 0; i < w->num_kcontrols; i++) { + k = &w->kcontrol_news[i]; + if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + sb = (void *) k->private_value; + bc = (struct skl_algo_data *)sb->dobj.private; + + if (bc->set_params == SKL_PARAM_SET) { + ret = skl_set_module_params(ctx, + (u32 *)bc->params, bc->size, + bc->param_id, mconfig); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +/* + * some module param can set from user control and this is required as + * when module is initailzed. if module param is required in init it is + * identifed by set_param flag. if set_param flag is not set, then this + * parameter needs to set as part of module init. + */ +static int skl_tplg_set_module_init_data(struct snd_soc_dapm_widget *w) +{ + const struct snd_kcontrol_new *k; + struct soc_bytes_ext *sb; + struct skl_algo_data *bc; + struct skl_module_cfg *mconfig = w->priv; + int i; + + for (i = 0; i < w->num_kcontrols; i++) { + k = &w->kcontrol_news[i]; + if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + sb = (struct soc_bytes_ext *)k->private_value; + bc = (struct skl_algo_data *)sb->dobj.private; + + if (bc->set_params != SKL_PARAM_INIT) + continue; + + mconfig->formats_config.caps = (u32 *)bc->params; + mconfig->formats_config.caps_size = bc->size; + + break; + } + } + + return 0; +} + +static int skl_tplg_module_prepare(struct skl_sst *ctx, struct skl_pipe *pipe, + struct snd_soc_dapm_widget *w, struct skl_module_cfg *mcfg) +{ + switch (mcfg->dev_type) { + case SKL_DEVICE_HDAHOST: + return skl_pcm_host_dma_prepare(ctx->dev, pipe->p_params); + + case SKL_DEVICE_HDALINK: + return skl_pcm_link_dma_prepare(ctx->dev, pipe->p_params); + } + + return 0; +} + +/* + * Inside a pipe instance, we can have various modules. These modules need + * to instantiated in DSP by invoking INIT_MODULE IPC, which is achieved by + * skl_init_module() routine, so invoke that for all modules in a pipeline + */ +static int +skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) +{ + struct skl_pipe_module *w_module; + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mconfig; + struct skl_sst *ctx = skl->skl_sst; + u8 cfg_idx; + int ret = 0; + + list_for_each_entry(w_module, &pipe->w_list, node) { + uuid_le *uuid_mod; + w = w_module->w; + mconfig = w->priv; + + /* check if module ids are populated */ + if (mconfig->id.module_id < 0) { + dev_err(skl->skl_sst->dev, + "module %pUL id not populated\n", + (uuid_le *)mconfig->guid); + return -EIO; + } + + cfg_idx = mconfig->pipe->cur_config_idx; + mconfig->fmt_idx = mconfig->mod_cfg[cfg_idx].fmt_idx; + mconfig->res_idx = mconfig->mod_cfg[cfg_idx].res_idx; + + /* check resource available */ + if (!skl_is_pipe_mcps_avail(skl, mconfig)) + return -ENOMEM; + + if (mconfig->module->loadable && ctx->dsp->fw_ops.load_mod) { + ret = ctx->dsp->fw_ops.load_mod(ctx->dsp, + mconfig->id.module_id, mconfig->guid); + if (ret < 0) + return ret; + + mconfig->m_state = SKL_MODULE_LOADED; + } + + /* prepare the DMA if the module is gateway cpr */ + ret = skl_tplg_module_prepare(ctx, pipe, w, mconfig); + if (ret < 0) + return ret; + + /* update blob if blob is null for be with default value */ + skl_tplg_update_be_blob(w, ctx); + + /* + * apply fix/conversion to module params based on + * FE/BE params + */ + skl_tplg_update_module_params(w, ctx); + uuid_mod = (uuid_le *)mconfig->guid; + mconfig->id.pvt_id = skl_get_pvt_id(ctx, uuid_mod, + mconfig->id.instance_id); + if (mconfig->id.pvt_id < 0) + return ret; + skl_tplg_set_module_init_data(w); + + ret = skl_dsp_get_core(ctx->dsp, mconfig->core_id); + if (ret < 0) { + dev_err(ctx->dev, "Failed to wake up core %d ret=%d\n", + mconfig->core_id, ret); + return ret; + } + + ret = skl_init_module(ctx, mconfig); + if (ret < 0) { + skl_put_pvt_id(ctx, uuid_mod, &mconfig->id.pvt_id); + goto err; + } + skl_tplg_alloc_pipe_mcps(skl, mconfig); + ret = skl_tplg_set_module_params(w, ctx); + if (ret < 0) + goto err; + } + + return 0; +err: + skl_dsp_put_core(ctx->dsp, mconfig->core_id); + return ret; +} + +static int skl_tplg_unload_pipe_modules(struct skl_sst *ctx, + struct skl_pipe *pipe) +{ + int ret = 0; + struct skl_pipe_module *w_module = NULL; + struct skl_module_cfg *mconfig = NULL; + + list_for_each_entry(w_module, &pipe->w_list, node) { + uuid_le *uuid_mod; + mconfig = w_module->w->priv; + uuid_mod = (uuid_le *)mconfig->guid; + + if (mconfig->module->loadable && ctx->dsp->fw_ops.unload_mod && + mconfig->m_state > SKL_MODULE_UNINIT) { + ret = ctx->dsp->fw_ops.unload_mod(ctx->dsp, + mconfig->id.module_id); + if (ret < 0) + return -EIO; + } + skl_put_pvt_id(ctx, uuid_mod, &mconfig->id.pvt_id); + + ret = skl_dsp_put_core(ctx->dsp, mconfig->core_id); + if (ret < 0) { + /* don't return; continue with other modules */ + dev_err(ctx->dev, "Failed to sleep core %d ret=%d\n", + mconfig->core_id, ret); + } + } + + /* no modules to unload in this path, so return */ + return ret; +} + +/* + * Here, we select pipe format based on the pipe type and pipe + * direction to determine the current config index for the pipeline. + * The config index is then used to select proper module resources. + * Intermediate pipes currently have a fixed format hence we select the + * 0th configuratation by default for such pipes. + */ +static int +skl_tplg_get_pipe_config(struct skl *skl, struct skl_module_cfg *mconfig) +{ + struct skl_sst *ctx = skl->skl_sst; + struct skl_pipe *pipe = mconfig->pipe; + struct skl_pipe_params *params = pipe->p_params; + struct skl_path_config *pconfig = &pipe->configs[0]; + struct skl_pipe_fmt *fmt = NULL; + bool in_fmt = false; + int i; + + if (pipe->nr_cfgs == 0) { + pipe->cur_config_idx = 0; + return 0; + } + + if (pipe->conn_type == SKL_PIPE_CONN_TYPE_NONE) { + dev_dbg(ctx->dev, "No conn_type detected, take 0th config\n"); + pipe->cur_config_idx = 0; + pipe->memory_pages = pconfig->mem_pages; + + return 0; + } + + if ((pipe->conn_type == SKL_PIPE_CONN_TYPE_FE && + pipe->direction == SNDRV_PCM_STREAM_PLAYBACK) || + (pipe->conn_type == SKL_PIPE_CONN_TYPE_BE && + pipe->direction == SNDRV_PCM_STREAM_CAPTURE)) + in_fmt = true; + + for (i = 0; i < pipe->nr_cfgs; i++) { + pconfig = &pipe->configs[i]; + if (in_fmt) + fmt = &pconfig->in_fmt; + else + fmt = &pconfig->out_fmt; + + if (CHECK_HW_PARAMS(params->ch, params->s_freq, params->s_fmt, + fmt->channels, fmt->freq, fmt->bps)) { + pipe->cur_config_idx = i; + pipe->memory_pages = pconfig->mem_pages; + dev_dbg(ctx->dev, "Using pipe config: %d\n", i); + + return 0; + } + } + + dev_err(ctx->dev, "Invalid pipe config: %d %d %d for pipe: %d\n", + params->ch, params->s_freq, params->s_fmt, pipe->ppl_id); + return -EINVAL; +} + +/* + * Mixer module represents a pipeline. So in the Pre-PMU event of mixer we + * need create the pipeline. So we do following: + * - check the resources + * - Create the pipeline + * - Initialize the modules in pipeline + * - finally bind all modules together + */ +static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, + struct skl *skl) +{ + int ret; + struct skl_module_cfg *mconfig = w->priv; + struct skl_pipe_module *w_module; + struct skl_pipe *s_pipe = mconfig->pipe; + struct skl_module_cfg *src_module = NULL, *dst_module, *module; + struct skl_sst *ctx = skl->skl_sst; + struct skl_module_deferred_bind *modules; + + ret = skl_tplg_get_pipe_config(skl, mconfig); + if (ret < 0) + return ret; + + /* check resource available */ + if (!skl_is_pipe_mcps_avail(skl, mconfig)) + return -EBUSY; + + if (!skl_is_pipe_mem_avail(skl, mconfig)) + return -ENOMEM; + + /* + * Create a list of modules for pipe. + * This list contains modules from source to sink + */ + ret = skl_create_pipeline(ctx, mconfig->pipe); + if (ret < 0) + return ret; + + skl_tplg_alloc_pipe_mem(skl, mconfig); + skl_tplg_alloc_pipe_mcps(skl, mconfig); + + /* Init all pipe modules from source to sink */ + ret = skl_tplg_init_pipe_modules(skl, s_pipe); + if (ret < 0) + return ret; + + /* Bind modules from source to sink */ + list_for_each_entry(w_module, &s_pipe->w_list, node) { + dst_module = w_module->w->priv; + + if (src_module == NULL) { + src_module = dst_module; + continue; + } + + ret = skl_bind_modules(ctx, src_module, dst_module); + if (ret < 0) + return ret; + + src_module = dst_module; + } + + /* + * When the destination module is initialized, check for these modules + * in deferred bind list. If found, bind them. + */ + list_for_each_entry(w_module, &s_pipe->w_list, node) { + if (list_empty(&skl->bind_list)) + break; + + list_for_each_entry(modules, &skl->bind_list, node) { + module = w_module->w->priv; + if (modules->dst == module) + skl_bind_modules(ctx, modules->src, + modules->dst); + } + } + + return 0; +} + +static int skl_fill_sink_instance_id(struct skl_sst *ctx, u32 *params, + int size, struct skl_module_cfg *mcfg) +{ + int i, pvt_id; + + if (mcfg->m_type == SKL_MODULE_TYPE_KPB) { + struct skl_kpb_params *kpb_params = + (struct skl_kpb_params *)params; + struct skl_mod_inst_map *inst = kpb_params->u.map; + + for (i = 0; i < kpb_params->num_modules; i++) { + pvt_id = skl_get_pvt_instance_id_map(ctx, inst->mod_id, + inst->inst_id); + if (pvt_id < 0) + return -EINVAL; + + inst->inst_id = pvt_id; + inst++; + } + } + + return 0; +} +/* + * Some modules require params to be set after the module is bound to + * all pins connected. + * + * The module provider initializes set_param flag for such modules and we + * send params after binding + */ +static int skl_tplg_set_module_bind_params(struct snd_soc_dapm_widget *w, + struct skl_module_cfg *mcfg, struct skl_sst *ctx) +{ + int i, ret; + struct skl_module_cfg *mconfig = w->priv; + const struct snd_kcontrol_new *k; + struct soc_bytes_ext *sb; + struct skl_algo_data *bc; + struct skl_specific_cfg *sp_cfg; + u32 *params; + + /* + * check all out/in pins are in bind state. + * if so set the module param + */ + for (i = 0; i < mcfg->module->max_output_pins; i++) { + if (mcfg->m_out_pin[i].pin_state != SKL_PIN_BIND_DONE) + return 0; + } + + for (i = 0; i < mcfg->module->max_input_pins; i++) { + if (mcfg->m_in_pin[i].pin_state != SKL_PIN_BIND_DONE) + return 0; + } + + if (mconfig->formats_config.caps_size > 0 && + mconfig->formats_config.set_params == SKL_PARAM_BIND) { + sp_cfg = &mconfig->formats_config; + ret = skl_set_module_params(ctx, sp_cfg->caps, + sp_cfg->caps_size, + sp_cfg->param_id, mconfig); + if (ret < 0) + return ret; + } + + for (i = 0; i < w->num_kcontrols; i++) { + k = &w->kcontrol_news[i]; + if (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + sb = (void *) k->private_value; + bc = (struct skl_algo_data *)sb->dobj.private; + + if (bc->set_params == SKL_PARAM_BIND) { + params = kzalloc(bc->max, GFP_KERNEL); + if (!params) + return -ENOMEM; + + memcpy(params, bc->params, bc->max); + skl_fill_sink_instance_id(ctx, params, bc->max, + mconfig); + + ret = skl_set_module_params(ctx, params, + bc->max, bc->param_id, mconfig); + kfree(params); + + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +static int skl_get_module_id(struct skl_sst *ctx, uuid_le *uuid) +{ + struct uuid_module *module; + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid, module->uuid) == 0) + return module->id; + } + + return -EINVAL; +} + +static int skl_tplg_find_moduleid_from_uuid(struct skl *skl, + const struct snd_kcontrol_new *k) +{ + struct soc_bytes_ext *sb = (void *) k->private_value; + struct skl_algo_data *bc = (struct skl_algo_data *)sb->dobj.private; + struct skl_kpb_params *uuid_params, *params; + struct hdac_bus *bus = skl_to_bus(skl); + int i, size, module_id; + + if (bc->set_params == SKL_PARAM_BIND && bc->max) { + uuid_params = (struct skl_kpb_params *)bc->params; + size = uuid_params->num_modules * + sizeof(struct skl_mod_inst_map) + + sizeof(uuid_params->num_modules); + + params = devm_kzalloc(bus->dev, size, GFP_KERNEL); + if (!params) + return -ENOMEM; + + params->num_modules = uuid_params->num_modules; + + for (i = 0; i < uuid_params->num_modules; i++) { + module_id = skl_get_module_id(skl->skl_sst, + &uuid_params->u.map_uuid[i].mod_uuid); + if (module_id < 0) { + devm_kfree(bus->dev, params); + return -EINVAL; + } + + params->u.map[i].mod_id = module_id; + params->u.map[i].inst_id = + uuid_params->u.map_uuid[i].inst_id; + } + + devm_kfree(bus->dev, bc->params); + bc->params = (char *)params; + bc->max = size; + } + + return 0; +} + +/* + * Retrieve the module id from UUID mentioned in the + * post bind params + */ +void skl_tplg_add_moduleid_in_bind_params(struct skl *skl, + struct snd_soc_dapm_widget *w) +{ + struct skl_module_cfg *mconfig = w->priv; + int i; + + /* + * Post bind params are used for only for KPB + * to set copier instances to drain the data + * in fast mode + */ + if (mconfig->m_type != SKL_MODULE_TYPE_KPB) + return; + + for (i = 0; i < w->num_kcontrols; i++) + if ((w->kcontrol_news[i].access & + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) && + (skl_tplg_find_moduleid_from_uuid(skl, + &w->kcontrol_news[i]) < 0)) + dev_err(skl->skl_sst->dev, + "%s: invalid kpb post bind params\n", + __func__); +} + +static int skl_tplg_module_add_deferred_bind(struct skl *skl, + struct skl_module_cfg *src, struct skl_module_cfg *dst) +{ + struct skl_module_deferred_bind *m_list, *modules; + int i; + + /* only supported for module with static pin connection */ + for (i = 0; i < dst->module->max_input_pins; i++) { + struct skl_module_pin *pin = &dst->m_in_pin[i]; + + if (pin->is_dynamic) + continue; + + if ((pin->id.module_id == src->id.module_id) && + (pin->id.instance_id == src->id.instance_id)) { + + if (!list_empty(&skl->bind_list)) { + list_for_each_entry(modules, &skl->bind_list, node) { + if (modules->src == src && modules->dst == dst) + return 0; + } + } + + m_list = kzalloc(sizeof(*m_list), GFP_KERNEL); + if (!m_list) + return -ENOMEM; + + m_list->src = src; + m_list->dst = dst; + + list_add(&m_list->node, &skl->bind_list); + } + } + + return 0; +} + +static int skl_tplg_bind_sinks(struct snd_soc_dapm_widget *w, + struct skl *skl, + struct snd_soc_dapm_widget *src_w, + struct skl_module_cfg *src_mconfig) +{ + struct snd_soc_dapm_path *p; + struct snd_soc_dapm_widget *sink = NULL, *next_sink = NULL; + struct skl_module_cfg *sink_mconfig; + struct skl_sst *ctx = skl->skl_sst; + int ret; + + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (!p->connect) + continue; + + dev_dbg(ctx->dev, "%s: src widget=%s\n", __func__, w->name); + dev_dbg(ctx->dev, "%s: sink widget=%s\n", __func__, p->sink->name); + + next_sink = p->sink; + + if (!is_skl_dsp_widget_type(p->sink, ctx->dev)) + return skl_tplg_bind_sinks(p->sink, skl, src_w, src_mconfig); + + /* + * here we will check widgets in sink pipelines, so that + * can be any widgets type and we are only interested if + * they are ones used for SKL so check that first + */ + if ((p->sink->priv != NULL) && + is_skl_dsp_widget_type(p->sink, ctx->dev)) { + + sink = p->sink; + sink_mconfig = sink->priv; + + /* + * Modules other than PGA leaf can be connected + * directly or via switch to a module in another + * pipeline. EX: reference path + * when the path is enabled, the dst module that needs + * to be bound may not be initialized. if the module is + * not initialized, add these modules in the deferred + * bind list and when the dst module is initialised, + * bind this module to the dst_module in deferred list. + */ + if (((src_mconfig->m_state == SKL_MODULE_INIT_DONE) + && (sink_mconfig->m_state == SKL_MODULE_UNINIT))) { + + ret = skl_tplg_module_add_deferred_bind(skl, + src_mconfig, sink_mconfig); + + if (ret < 0) + return ret; + + } + + + if (src_mconfig->m_state == SKL_MODULE_UNINIT || + sink_mconfig->m_state == SKL_MODULE_UNINIT) + continue; + + /* Bind source to sink, mixin is always source */ + ret = skl_bind_modules(ctx, src_mconfig, sink_mconfig); + if (ret) + return ret; + + /* set module params after bind */ + skl_tplg_set_module_bind_params(src_w, src_mconfig, ctx); + skl_tplg_set_module_bind_params(sink, sink_mconfig, ctx); + + /* Start sinks pipe first */ + if (sink_mconfig->pipe->state != SKL_PIPE_STARTED) { + if (sink_mconfig->pipe->conn_type != + SKL_PIPE_CONN_TYPE_FE) + ret = skl_run_pipe(ctx, + sink_mconfig->pipe); + if (ret) + return ret; + } + } + } + + if (!sink && next_sink) + return skl_tplg_bind_sinks(next_sink, skl, src_w, src_mconfig); + + return 0; +} + +/* + * A PGA represents a module in a pipeline. So in the Pre-PMU event of PGA + * we need to do following: + * - Bind to sink pipeline + * Since the sink pipes can be running and we don't get mixer event on + * connect for already running mixer, we need to find the sink pipes + * here and bind to them. This way dynamic connect works. + * - Start sink pipeline, if not running + * - Then run current pipe + */ +static int skl_tplg_pga_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, + struct skl *skl) +{ + struct skl_module_cfg *src_mconfig; + struct skl_sst *ctx = skl->skl_sst; + int ret = 0; + + src_mconfig = w->priv; + + /* + * find which sink it is connected to, bind with the sink, + * if sink is not started, start sink pipe first, then start + * this pipe + */ + ret = skl_tplg_bind_sinks(w, skl, w, src_mconfig); + if (ret) + return ret; + + /* Start source pipe last after starting all sinks */ + if (src_mconfig->pipe->conn_type != SKL_PIPE_CONN_TYPE_FE) + return skl_run_pipe(ctx, src_mconfig->pipe); + + return 0; +} + +static struct snd_soc_dapm_widget *skl_get_src_dsp_widget( + struct snd_soc_dapm_widget *w, struct skl *skl) +{ + struct snd_soc_dapm_path *p; + struct snd_soc_dapm_widget *src_w = NULL; + struct skl_sst *ctx = skl->skl_sst; + + snd_soc_dapm_widget_for_each_source_path(w, p) { + src_w = p->source; + if (!p->connect) + continue; + + dev_dbg(ctx->dev, "sink widget=%s\n", w->name); + dev_dbg(ctx->dev, "src widget=%s\n", p->source->name); + + /* + * here we will check widgets in sink pipelines, so that can + * be any widgets type and we are only interested if they are + * ones used for SKL so check that first + */ + if ((p->source->priv != NULL) && + is_skl_dsp_widget_type(p->source, ctx->dev)) { + return p->source; + } + } + + if (src_w != NULL) + return skl_get_src_dsp_widget(src_w, skl); + + return NULL; +} + +/* + * in the Post-PMU event of mixer we need to do following: + * - Check if this pipe is running + * - if not, then + * - bind this pipeline to its source pipeline + * if source pipe is already running, this means it is a dynamic + * connection and we need to bind only to that pipe + * - start this pipeline + */ +static int skl_tplg_mixer_dapm_post_pmu_event(struct snd_soc_dapm_widget *w, + struct skl *skl) +{ + int ret = 0; + struct snd_soc_dapm_widget *source, *sink; + struct skl_module_cfg *src_mconfig, *sink_mconfig; + struct skl_sst *ctx = skl->skl_sst; + int src_pipe_started = 0; + + sink = w; + sink_mconfig = sink->priv; + + /* + * If source pipe is already started, that means source is driving + * one more sink before this sink got connected, Since source is + * started, bind this sink to source and start this pipe. + */ + source = skl_get_src_dsp_widget(w, skl); + if (source != NULL) { + src_mconfig = source->priv; + sink_mconfig = sink->priv; + src_pipe_started = 1; + + /* + * check pipe state, then no need to bind or start the + * pipe + */ + if (src_mconfig->pipe->state != SKL_PIPE_STARTED) + src_pipe_started = 0; + } + + if (src_pipe_started) { + ret = skl_bind_modules(ctx, src_mconfig, sink_mconfig); + if (ret) + return ret; + + /* set module params after bind */ + skl_tplg_set_module_bind_params(source, src_mconfig, ctx); + skl_tplg_set_module_bind_params(sink, sink_mconfig, ctx); + + if (sink_mconfig->pipe->conn_type != SKL_PIPE_CONN_TYPE_FE) + ret = skl_run_pipe(ctx, sink_mconfig->pipe); + } + + return ret; +} + +/* + * in the Pre-PMD event of mixer we need to do following: + * - Stop the pipe + * - find the source connections and remove that from dapm_path_list + * - unbind with source pipelines if still connected + */ +static int skl_tplg_mixer_dapm_pre_pmd_event(struct snd_soc_dapm_widget *w, + struct skl *skl) +{ + struct skl_module_cfg *src_mconfig, *sink_mconfig; + int ret = 0, i; + struct skl_sst *ctx = skl->skl_sst; + + sink_mconfig = w->priv; + + /* Stop the pipe */ + ret = skl_stop_pipe(ctx, sink_mconfig->pipe); + if (ret) + return ret; + + for (i = 0; i < sink_mconfig->module->max_input_pins; i++) { + if (sink_mconfig->m_in_pin[i].pin_state == SKL_PIN_BIND_DONE) { + src_mconfig = sink_mconfig->m_in_pin[i].tgt_mcfg; + if (!src_mconfig) + continue; + + ret = skl_unbind_modules(ctx, + src_mconfig, sink_mconfig); + } + } + + return ret; +} + +/* + * in the Post-PMD event of mixer we need to do following: + * - Free the mcps used + * - Free the mem used + * - Unbind the modules within the pipeline + * - Delete the pipeline (modules are not required to be explicitly + * deleted, pipeline delete is enough here + */ +static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, + struct skl *skl) +{ + struct skl_module_cfg *mconfig = w->priv; + struct skl_pipe_module *w_module; + struct skl_module_cfg *src_module = NULL, *dst_module; + struct skl_sst *ctx = skl->skl_sst; + struct skl_pipe *s_pipe = mconfig->pipe; + struct skl_module_deferred_bind *modules, *tmp; + + if (s_pipe->state == SKL_PIPE_INVALID) + return -EINVAL; + + skl_tplg_free_pipe_mcps(skl, mconfig); + skl_tplg_free_pipe_mem(skl, mconfig); + + list_for_each_entry(w_module, &s_pipe->w_list, node) { + if (list_empty(&skl->bind_list)) + break; + + src_module = w_module->w->priv; + + list_for_each_entry_safe(modules, tmp, &skl->bind_list, node) { + /* + * When the destination module is deleted, Unbind the + * modules from deferred bind list. + */ + if (modules->dst == src_module) { + skl_unbind_modules(ctx, modules->src, + modules->dst); + } + + /* + * When the source module is deleted, remove this entry + * from the deferred bind list. + */ + if (modules->src == src_module) { + list_del(&modules->node); + modules->src = NULL; + modules->dst = NULL; + kfree(modules); + } + } + } + + list_for_each_entry(w_module, &s_pipe->w_list, node) { + dst_module = w_module->w->priv; + + if (mconfig->m_state >= SKL_MODULE_INIT_DONE) + skl_tplg_free_pipe_mcps(skl, dst_module); + if (src_module == NULL) { + src_module = dst_module; + continue; + } + + skl_unbind_modules(ctx, src_module, dst_module); + src_module = dst_module; + } + + skl_delete_pipe(ctx, mconfig->pipe); + + list_for_each_entry(w_module, &s_pipe->w_list, node) { + src_module = w_module->w->priv; + src_module->m_state = SKL_MODULE_UNINIT; + } + + return skl_tplg_unload_pipe_modules(ctx, s_pipe); +} + +/* + * in the Post-PMD event of PGA we need to do following: + * - Free the mcps used + * - Stop the pipeline + * - In source pipe is connected, unbind with source pipelines + */ +static int skl_tplg_pga_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, + struct skl *skl) +{ + struct skl_module_cfg *src_mconfig, *sink_mconfig; + int ret = 0, i; + struct skl_sst *ctx = skl->skl_sst; + + src_mconfig = w->priv; + + /* Stop the pipe since this is a mixin module */ + ret = skl_stop_pipe(ctx, src_mconfig->pipe); + if (ret) + return ret; + + for (i = 0; i < src_mconfig->module->max_output_pins; i++) { + if (src_mconfig->m_out_pin[i].pin_state == SKL_PIN_BIND_DONE) { + sink_mconfig = src_mconfig->m_out_pin[i].tgt_mcfg; + if (!sink_mconfig) + continue; + /* + * This is a connecter and if path is found that means + * unbind between source and sink has not happened yet + */ + ret = skl_unbind_modules(ctx, src_mconfig, + sink_mconfig); + } + } + + return ret; +} + +/* + * In modelling, we assume there will be ONLY one mixer in a pipeline. If a + * second one is required that is created as another pipe entity. + * The mixer is responsible for pipe management and represent a pipeline + * instance + */ +static int skl_tplg_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct skl *skl = get_skl_ctx(dapm->dev); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return skl_tplg_mixer_dapm_pre_pmu_event(w, skl); + + case SND_SOC_DAPM_POST_PMU: + return skl_tplg_mixer_dapm_post_pmu_event(w, skl); + + case SND_SOC_DAPM_PRE_PMD: + return skl_tplg_mixer_dapm_pre_pmd_event(w, skl); + + case SND_SOC_DAPM_POST_PMD: + return skl_tplg_mixer_dapm_post_pmd_event(w, skl); + } + + return 0; +} + +/* + * In modelling, we assumed rest of the modules in pipeline are PGA. But we + * are interested in last PGA (leaf PGA) in a pipeline to disconnect with + * the sink when it is running (two FE to one BE or one FE to two BE) + * scenarios + */ +static int skl_tplg_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) + +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct skl *skl = get_skl_ctx(dapm->dev); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return skl_tplg_pga_dapm_pre_pmu_event(w, skl); + + case SND_SOC_DAPM_POST_PMD: + return skl_tplg_pga_dapm_post_pmd_event(w, skl); + } + + return 0; +} + +static int skl_tplg_tlv_control_get(struct snd_kcontrol *kcontrol, + unsigned int __user *data, unsigned int size) +{ + struct soc_bytes_ext *sb = + (struct soc_bytes_ext *)kcontrol->private_value; + struct skl_algo_data *bc = (struct skl_algo_data *)sb->dobj.private; + struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol); + struct skl_module_cfg *mconfig = w->priv; + struct skl *skl = get_skl_ctx(w->dapm->dev); + + if (w->power) + skl_get_module_params(skl->skl_sst, (u32 *)bc->params, + bc->size, bc->param_id, mconfig); + + /* decrement size for TLV header */ + size -= 2 * sizeof(u32); + + /* check size as we don't want to send kernel data */ + if (size > bc->max) + size = bc->max; + + if (bc->params) { + if (copy_to_user(data, &bc->param_id, sizeof(u32))) + return -EFAULT; + if (copy_to_user(data + 1, &size, sizeof(u32))) + return -EFAULT; + if (copy_to_user(data + 2, bc->params, size)) + return -EFAULT; + } + + return 0; +} + +#define SKL_PARAM_VENDOR_ID 0xff + +static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, + const unsigned int __user *data, unsigned int size) +{ + struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol); + struct skl_module_cfg *mconfig = w->priv; + struct soc_bytes_ext *sb = + (struct soc_bytes_ext *)kcontrol->private_value; + struct skl_algo_data *ac = (struct skl_algo_data *)sb->dobj.private; + struct skl *skl = get_skl_ctx(w->dapm->dev); + + if (ac->params) { + if (size > ac->max) + return -EINVAL; + + ac->size = size; + /* + * if the param_is is of type Vendor, firmware expects actual + * parameter id and size from the control. + */ + if (ac->param_id == SKL_PARAM_VENDOR_ID) { + if (copy_from_user(ac->params, data, size)) + return -EFAULT; + } else { + if (copy_from_user(ac->params, + data + 2, size)) + return -EFAULT; + } + + if (w->power) + return skl_set_module_params(skl->skl_sst, + (u32 *)ac->params, ac->size, + ac->param_id, mconfig); + } + + return 0; +} + +static int skl_tplg_mic_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol); + struct skl_module_cfg *mconfig = w->priv; + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + u32 ch_type = *((u32 *)ec->dobj.private); + + if (mconfig->dmic_ch_type == ch_type) + ucontrol->value.enumerated.item[0] = + mconfig->dmic_ch_combo_index; + else + ucontrol->value.enumerated.item[0] = 0; + + return 0; +} + +static int skl_fill_mic_sel_params(struct skl_module_cfg *mconfig, + struct skl_mic_sel_config *mic_cfg, struct device *dev) +{ + struct skl_specific_cfg *sp_cfg = &mconfig->formats_config; + + sp_cfg->caps_size = sizeof(struct skl_mic_sel_config); + sp_cfg->set_params = SKL_PARAM_SET; + sp_cfg->param_id = 0x00; + if (!sp_cfg->caps) { + sp_cfg->caps = devm_kzalloc(dev, sp_cfg->caps_size, GFP_KERNEL); + if (!sp_cfg->caps) + return -ENOMEM; + } + + mic_cfg->mic_switch = SKL_MIC_SEL_SWITCH; + mic_cfg->flags = 0; + memcpy(sp_cfg->caps, mic_cfg, sp_cfg->caps_size); + + return 0; +} + +static int skl_tplg_mic_control_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol); + struct skl_module_cfg *mconfig = w->priv; + struct skl_mic_sel_config mic_cfg = {0}; + struct soc_enum *ec = (struct soc_enum *)kcontrol->private_value; + u32 ch_type = *((u32 *)ec->dobj.private); + const int *list; + u8 in_ch, out_ch, index; + + mconfig->dmic_ch_type = ch_type; + mconfig->dmic_ch_combo_index = ucontrol->value.enumerated.item[0]; + + /* enum control index 0 is INVALID, so no channels to be set */ + if (mconfig->dmic_ch_combo_index == 0) + return 0; + + /* No valid channel selection map for index 0, so offset by 1 */ + index = mconfig->dmic_ch_combo_index - 1; + + switch (ch_type) { + case SKL_CH_MONO: + if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_mono_list)) + return -EINVAL; + + list = &mic_mono_list[index]; + break; + + case SKL_CH_STEREO: + if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_stereo_list)) + return -EINVAL; + + list = mic_stereo_list[index]; + break; + + case SKL_CH_TRIO: + if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_trio_list)) + return -EINVAL; + + list = mic_trio_list[index]; + break; + + case SKL_CH_QUATRO: + if (mconfig->dmic_ch_combo_index > ARRAY_SIZE(mic_quatro_list)) + return -EINVAL; + + list = mic_quatro_list[index]; + break; + + default: + dev_err(w->dapm->dev, + "Invalid channel %d for mic_select module\n", + ch_type); + return -EINVAL; + + } + + /* channel type enum map to number of chanels for that type */ + for (out_ch = 0; out_ch < ch_type; out_ch++) { + in_ch = list[out_ch]; + mic_cfg.blob[out_ch][in_ch] = SKL_DEFAULT_MIC_SEL_GAIN; + } + + return skl_fill_mic_sel_params(mconfig, &mic_cfg, w->dapm->dev); +} + +/* + * Fill the dma id for host and link. In case of passthrough + * pipeline, this will both host and link in the same + * pipeline, so need to copy the link and host based on dev_type + */ +static void skl_tplg_fill_dma_id(struct skl_module_cfg *mcfg, + struct skl_pipe_params *params) +{ + struct skl_pipe *pipe = mcfg->pipe; + + if (pipe->passthru) { + switch (mcfg->dev_type) { + case SKL_DEVICE_HDALINK: + pipe->p_params->link_dma_id = params->link_dma_id; + pipe->p_params->link_index = params->link_index; + pipe->p_params->link_bps = params->link_bps; + break; + + case SKL_DEVICE_HDAHOST: + pipe->p_params->host_dma_id = params->host_dma_id; + pipe->p_params->host_bps = params->host_bps; + break; + + default: + break; + } + pipe->p_params->s_fmt = params->s_fmt; + pipe->p_params->ch = params->ch; + pipe->p_params->s_freq = params->s_freq; + pipe->p_params->stream = params->stream; + pipe->p_params->format = params->format; + + } else { + memcpy(pipe->p_params, params, sizeof(*params)); + } +} + +/* + * The FE params are passed by hw_params of the DAI. + * On hw_params, the params are stored in Gateway module of the FE and we + * need to calculate the format in DSP module configuration, that + * conversion is done here + */ +int skl_tplg_update_pipe_params(struct device *dev, + struct skl_module_cfg *mconfig, + struct skl_pipe_params *params) +{ + struct skl_module_res *res = &mconfig->module->resources[0]; + struct skl *skl = get_skl_ctx(dev); + struct skl_module_fmt *format = NULL; + u8 cfg_idx = mconfig->pipe->cur_config_idx; + + skl_tplg_fill_dma_id(mconfig, params); + mconfig->fmt_idx = mconfig->mod_cfg[cfg_idx].fmt_idx; + mconfig->res_idx = mconfig->mod_cfg[cfg_idx].res_idx; + + if (skl->nr_modules) + return 0; + + if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) + format = &mconfig->module->formats[0].inputs[0].fmt; + else + format = &mconfig->module->formats[0].outputs[0].fmt; + + /* set the hw_params */ + format->s_freq = params->s_freq; + format->channels = params->ch; + format->valid_bit_depth = skl_get_bit_depth(params->s_fmt); + + /* + * 16 bit is 16 bit container whereas 24 bit is in 32 bit + * container so update bit depth accordingly + */ + switch (format->valid_bit_depth) { + case SKL_DEPTH_16BIT: + format->bit_depth = format->valid_bit_depth; + break; + + case SKL_DEPTH_24BIT: + case SKL_DEPTH_32BIT: + format->bit_depth = SKL_DEPTH_32BIT; + break; + + default: + dev_err(dev, "Invalid bit depth %x for pipe\n", + format->valid_bit_depth); + return -EINVAL; + } + + if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) { + res->ibs = (format->s_freq / 1000) * + (format->channels) * + (format->bit_depth >> 3); + } else { + res->obs = (format->s_freq / 1000) * + (format->channels) * + (format->bit_depth >> 3); + } + + return 0; +} + +/* + * Query the module config for the FE DAI + * This is used to find the hw_params set for that DAI and apply to FE + * pipeline + */ +struct skl_module_cfg * +skl_tplg_fe_get_cpr_module(struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_path *p = NULL; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + w = dai->playback_widget; + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (p->connect && p->sink->power && + !is_skl_dsp_widget_type(p->sink, dai->dev)) + continue; + + if (p->sink->priv) { + dev_dbg(dai->dev, "set params for %s\n", + p->sink->name); + return p->sink->priv; + } + } + } else { + w = dai->capture_widget; + snd_soc_dapm_widget_for_each_source_path(w, p) { + if (p->connect && p->source->power && + !is_skl_dsp_widget_type(p->source, dai->dev)) + continue; + + if (p->source->priv) { + dev_dbg(dai->dev, "set params for %s\n", + p->source->name); + return p->source->priv; + } + } + } + + return NULL; +} + +static struct skl_module_cfg *skl_get_mconfig_pb_cpr( + struct snd_soc_dai *dai, struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p; + struct skl_module_cfg *mconfig = NULL; + + snd_soc_dapm_widget_for_each_source_path(w, p) { + if (w->endpoints[SND_SOC_DAPM_DIR_OUT] > 0) { + if (p->connect && + (p->sink->id == snd_soc_dapm_aif_out) && + p->source->priv) { + mconfig = p->source->priv; + return mconfig; + } + mconfig = skl_get_mconfig_pb_cpr(dai, p->source); + if (mconfig) + return mconfig; + } + } + return mconfig; +} + +static struct skl_module_cfg *skl_get_mconfig_cap_cpr( + struct snd_soc_dai *dai, struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p; + struct skl_module_cfg *mconfig = NULL; + + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (w->endpoints[SND_SOC_DAPM_DIR_IN] > 0) { + if (p->connect && + (p->source->id == snd_soc_dapm_aif_in) && + p->sink->priv) { + mconfig = p->sink->priv; + return mconfig; + } + mconfig = skl_get_mconfig_cap_cpr(dai, p->sink); + if (mconfig) + return mconfig; + } + } + return mconfig; +} + +struct skl_module_cfg * +skl_tplg_be_get_cpr_module(struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mconfig; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + w = dai->playback_widget; + mconfig = skl_get_mconfig_pb_cpr(dai, w); + } else { + w = dai->capture_widget; + mconfig = skl_get_mconfig_cap_cpr(dai, w); + } + return mconfig; +} + +static u8 skl_tplg_be_link_type(int dev_type) +{ + int ret; + + switch (dev_type) { + case SKL_DEVICE_BT: + ret = NHLT_LINK_SSP; + break; + + case SKL_DEVICE_DMIC: + ret = NHLT_LINK_DMIC; + break; + + case SKL_DEVICE_I2S: + ret = NHLT_LINK_SSP; + break; + + case SKL_DEVICE_HDALINK: + ret = NHLT_LINK_HDA; + break; + + default: + ret = NHLT_LINK_INVALID; + break; + } + + return ret; +} + +/* + * Fill the BE gateway parameters + * The BE gateway expects a blob of parameters which are kept in the ACPI + * NHLT blob, so query the blob for interface type (i2s/pdm) and instance. + * The port can have multiple settings so pick based on the PCM + * parameters + */ +static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai, + struct skl_module_cfg *mconfig, + struct skl_pipe_params *params) +{ + struct nhlt_specific_cfg *cfg; + struct skl *skl = get_skl_ctx(dai->dev); + int link_type = skl_tplg_be_link_type(mconfig->dev_type); + u8 dev_type = skl_tplg_be_dev_type(mconfig->dev_type); + + skl_tplg_fill_dma_id(mconfig, params); + + if (link_type == NHLT_LINK_HDA) + return 0; + + /* update the blob based on virtual bus_id*/ + cfg = skl_get_ep_blob(skl, mconfig->vbus_id, link_type, + params->s_fmt, params->ch, + params->s_freq, params->stream, + dev_type); + if (cfg) { + mconfig->formats_config.caps_size = cfg->size; + mconfig->formats_config.caps = (u32 *) &cfg->caps; + } else { + dev_err(dai->dev, "Blob NULL for id %x type %d dirn %d\n", + mconfig->vbus_id, link_type, + params->stream); + dev_err(dai->dev, "PCM: ch %d, freq %d, fmt %d\n", + params->ch, params->s_freq, params->s_fmt); + return -EINVAL; + } + + return 0; +} + +static int skl_tplg_be_set_src_pipe_params(struct snd_soc_dai *dai, + struct snd_soc_dapm_widget *w, + struct skl_pipe_params *params) +{ + struct snd_soc_dapm_path *p; + int ret = -EIO; + + snd_soc_dapm_widget_for_each_source_path(w, p) { + if (p->connect && is_skl_dsp_widget_type(p->source, dai->dev) && + p->source->priv) { + + ret = skl_tplg_be_fill_pipe_params(dai, + p->source->priv, params); + if (ret < 0) + return ret; + } else { + ret = skl_tplg_be_set_src_pipe_params(dai, + p->source, params); + if (ret < 0) + return ret; + } + } + + return ret; +} + +static int skl_tplg_be_set_sink_pipe_params(struct snd_soc_dai *dai, + struct snd_soc_dapm_widget *w, struct skl_pipe_params *params) +{ + struct snd_soc_dapm_path *p = NULL; + int ret = -EIO; + + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (p->connect && is_skl_dsp_widget_type(p->sink, dai->dev) && + p->sink->priv) { + + ret = skl_tplg_be_fill_pipe_params(dai, + p->sink->priv, params); + if (ret < 0) + return ret; + } else { + ret = skl_tplg_be_set_sink_pipe_params( + dai, p->sink, params); + if (ret < 0) + return ret; + } + } + + return ret; +} + +/* + * BE hw_params can be a source parameters (capture) or sink parameters + * (playback). Based on sink and source we need to either find the source + * list or the sink list and set the pipeline parameters + */ +int skl_tplg_be_update_params(struct snd_soc_dai *dai, + struct skl_pipe_params *params) +{ + struct snd_soc_dapm_widget *w; + + if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) { + w = dai->playback_widget; + + return skl_tplg_be_set_src_pipe_params(dai, w, params); + + } else { + w = dai->capture_widget; + + return skl_tplg_be_set_sink_pipe_params(dai, w, params); + } + + return 0; +} + +static const struct snd_soc_tplg_widget_events skl_tplg_widget_ops[] = { + {SKL_MIXER_EVENT, skl_tplg_mixer_event}, + {SKL_VMIXER_EVENT, skl_tplg_mixer_event}, + {SKL_PGA_EVENT, skl_tplg_pga_event}, +}; + +static const struct snd_soc_tplg_bytes_ext_ops skl_tlv_ops[] = { + {SKL_CONTROL_TYPE_BYTE_TLV, skl_tplg_tlv_control_get, + skl_tplg_tlv_control_set}, +}; + +static const struct snd_soc_tplg_kcontrol_ops skl_tplg_kcontrol_ops[] = { + { + .id = SKL_CONTROL_TYPE_MIC_SELECT, + .get = skl_tplg_mic_control_get, + .put = skl_tplg_mic_control_set, + }, +}; + +static int skl_tplg_fill_pipe_cfg(struct device *dev, + struct skl_pipe *pipe, u32 tkn, + u32 tkn_val, int conf_idx, int dir) +{ + struct skl_pipe_fmt *fmt; + struct skl_path_config *config; + + switch (dir) { + case SKL_DIR_IN: + fmt = &pipe->configs[conf_idx].in_fmt; + break; + + case SKL_DIR_OUT: + fmt = &pipe->configs[conf_idx].out_fmt; + break; + + default: + dev_err(dev, "Invalid direction: %d\n", dir); + return -EINVAL; + } + + config = &pipe->configs[conf_idx]; + + switch (tkn) { + case SKL_TKN_U32_CFG_FREQ: + fmt->freq = tkn_val; + break; + + case SKL_TKN_U8_CFG_CHAN: + fmt->channels = tkn_val; + break; + + case SKL_TKN_U8_CFG_BPS: + fmt->bps = tkn_val; + break; + + case SKL_TKN_U32_PATH_MEM_PGS: + config->mem_pages = tkn_val; + break; + + default: + dev_err(dev, "Invalid token config: %d\n", tkn); + return -EINVAL; + } + + return 0; +} + +static int skl_tplg_fill_pipe_tkn(struct device *dev, + struct skl_pipe *pipe, u32 tkn, + u32 tkn_val) +{ + + switch (tkn) { + case SKL_TKN_U32_PIPE_CONN_TYPE: + pipe->conn_type = tkn_val; + break; + + case SKL_TKN_U32_PIPE_PRIORITY: + pipe->pipe_priority = tkn_val; + break; + + case SKL_TKN_U32_PIPE_MEM_PGS: + pipe->memory_pages = tkn_val; + break; + + case SKL_TKN_U32_PMODE: + pipe->lp_mode = tkn_val; + break; + + case SKL_TKN_U32_PIPE_DIRECTION: + pipe->direction = tkn_val; + break; + + case SKL_TKN_U32_NUM_CONFIGS: + pipe->nr_cfgs = tkn_val; + break; + + default: + dev_err(dev, "Token not handled %d\n", tkn); + return -EINVAL; + } + + return 0; +} + +/* + * Add pipeline by parsing the relevant tokens + * Return an existing pipe if the pipe already exists. + */ +static int skl_tplg_add_pipe(struct device *dev, + struct skl_module_cfg *mconfig, struct skl *skl, + struct snd_soc_tplg_vendor_value_elem *tkn_elem) +{ + struct skl_pipeline *ppl; + struct skl_pipe *pipe; + struct skl_pipe_params *params; + + list_for_each_entry(ppl, &skl->ppl_list, node) { + if (ppl->pipe->ppl_id == tkn_elem->value) { + mconfig->pipe = ppl->pipe; + return -EEXIST; + } + } + + ppl = devm_kzalloc(dev, sizeof(*ppl), GFP_KERNEL); + if (!ppl) + return -ENOMEM; + + pipe = devm_kzalloc(dev, sizeof(*pipe), GFP_KERNEL); + if (!pipe) + return -ENOMEM; + + params = devm_kzalloc(dev, sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + pipe->p_params = params; + pipe->ppl_id = tkn_elem->value; + INIT_LIST_HEAD(&pipe->w_list); + + ppl->pipe = pipe; + list_add(&ppl->node, &skl->ppl_list); + + mconfig->pipe = pipe; + mconfig->pipe->state = SKL_PIPE_INVALID; + + return 0; +} + +static int skl_tplg_get_uuid(struct device *dev, u8 *guid, + struct snd_soc_tplg_vendor_uuid_elem *uuid_tkn) +{ + if (uuid_tkn->token == SKL_TKN_UUID) { + memcpy(guid, &uuid_tkn->uuid, 16); + return 0; + } + + dev_err(dev, "Not an UUID token %d\n", uuid_tkn->token); + + return -EINVAL; +} + +static int skl_tplg_fill_pin(struct device *dev, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + struct skl_module_pin *m_pin, + int pin_index) +{ + int ret; + + switch (tkn_elem->token) { + case SKL_TKN_U32_PIN_MOD_ID: + m_pin[pin_index].id.module_id = tkn_elem->value; + break; + + case SKL_TKN_U32_PIN_INST_ID: + m_pin[pin_index].id.instance_id = tkn_elem->value; + break; + + case SKL_TKN_UUID: + ret = skl_tplg_get_uuid(dev, m_pin[pin_index].id.mod_uuid.b, + (struct snd_soc_tplg_vendor_uuid_elem *)tkn_elem); + if (ret < 0) + return ret; + + break; + + default: + dev_err(dev, "%d Not a pin token\n", tkn_elem->token); + return -EINVAL; + } + + return 0; +} + +/* + * Parse for pin config specific tokens to fill up the + * module private data + */ +static int skl_tplg_fill_pins_info(struct device *dev, + struct skl_module_cfg *mconfig, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + int dir, int pin_count) +{ + int ret; + struct skl_module_pin *m_pin; + + switch (dir) { + case SKL_DIR_IN: + m_pin = mconfig->m_in_pin; + break; + + case SKL_DIR_OUT: + m_pin = mconfig->m_out_pin; + break; + + default: + dev_err(dev, "Invalid direction value\n"); + return -EINVAL; + } + + ret = skl_tplg_fill_pin(dev, tkn_elem, m_pin, pin_count); + if (ret < 0) + return ret; + + m_pin[pin_count].in_use = false; + m_pin[pin_count].pin_state = SKL_PIN_UNBIND; + + return 0; +} + +/* + * Fill up input/output module config format based + * on the direction + */ +static int skl_tplg_fill_fmt(struct device *dev, + struct skl_module_fmt *dst_fmt, + u32 tkn, u32 value) +{ + switch (tkn) { + case SKL_TKN_U32_FMT_CH: + dst_fmt->channels = value; + break; + + case SKL_TKN_U32_FMT_FREQ: + dst_fmt->s_freq = value; + break; + + case SKL_TKN_U32_FMT_BIT_DEPTH: + dst_fmt->bit_depth = value; + break; + + case SKL_TKN_U32_FMT_SAMPLE_SIZE: + dst_fmt->valid_bit_depth = value; + break; + + case SKL_TKN_U32_FMT_CH_CONFIG: + dst_fmt->ch_cfg = value; + break; + + case SKL_TKN_U32_FMT_INTERLEAVE: + dst_fmt->interleaving_style = value; + break; + + case SKL_TKN_U32_FMT_SAMPLE_TYPE: + dst_fmt->sample_type = value; + break; + + case SKL_TKN_U32_FMT_CH_MAP: + dst_fmt->ch_map = value; + break; + + default: + dev_err(dev, "Invalid token %d\n", tkn); + return -EINVAL; + } + + return 0; +} + +static int skl_tplg_widget_fill_fmt(struct device *dev, + struct skl_module_iface *fmt, + u32 tkn, u32 val, u32 dir, int fmt_idx) +{ + struct skl_module_fmt *dst_fmt; + + if (!fmt) + return -EINVAL; + + switch (dir) { + case SKL_DIR_IN: + dst_fmt = &fmt->inputs[fmt_idx].fmt; + break; + + case SKL_DIR_OUT: + dst_fmt = &fmt->outputs[fmt_idx].fmt; + break; + + default: + dev_err(dev, "Invalid direction: %d\n", dir); + return -EINVAL; + } + + return skl_tplg_fill_fmt(dev, dst_fmt, tkn, val); +} + +static void skl_tplg_fill_pin_dynamic_val( + struct skl_module_pin *mpin, u32 pin_count, u32 value) +{ + int i; + + for (i = 0; i < pin_count; i++) + mpin[i].is_dynamic = value; +} + +/* + * Resource table in the manifest has pin specific resources + * like pin and pin buffer size + */ +static int skl_tplg_manifest_pin_res_tkn(struct device *dev, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + struct skl_module_res *res, int pin_idx, int dir) +{ + struct skl_module_pin_resources *m_pin; + + switch (dir) { + case SKL_DIR_IN: + m_pin = &res->input[pin_idx]; + break; + + case SKL_DIR_OUT: + m_pin = &res->output[pin_idx]; + break; + + default: + dev_err(dev, "Invalid pin direction: %d\n", dir); + return -EINVAL; + } + + switch (tkn_elem->token) { + case SKL_TKN_MM_U32_RES_PIN_ID: + m_pin->pin_index = tkn_elem->value; + break; + + case SKL_TKN_MM_U32_PIN_BUF: + m_pin->buf_size = tkn_elem->value; + break; + + default: + dev_err(dev, "Invalid token: %d\n", tkn_elem->token); + return -EINVAL; + } + + return 0; +} + +/* + * Fill module specific resources from the manifest's resource + * table like CPS, DMA size, mem_pages. + */ +static int skl_tplg_fill_res_tkn(struct device *dev, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + struct skl_module_res *res, + int pin_idx, int dir) +{ + int ret, tkn_count = 0; + + if (!res) + return -EINVAL; + + switch (tkn_elem->token) { + case SKL_TKN_MM_U32_CPS: + res->cps = tkn_elem->value; + break; + + case SKL_TKN_MM_U32_DMA_SIZE: + res->dma_buffer_size = tkn_elem->value; + break; + + case SKL_TKN_MM_U32_CPC: + res->cpc = tkn_elem->value; + break; + + case SKL_TKN_U32_MEM_PAGES: + res->is_pages = tkn_elem->value; + break; + + case SKL_TKN_U32_OBS: + res->obs = tkn_elem->value; + break; + + case SKL_TKN_U32_IBS: + res->ibs = tkn_elem->value; + break; + + case SKL_TKN_U32_MAX_MCPS: + res->cps = tkn_elem->value; + break; + + case SKL_TKN_MM_U32_RES_PIN_ID: + case SKL_TKN_MM_U32_PIN_BUF: + ret = skl_tplg_manifest_pin_res_tkn(dev, tkn_elem, res, + pin_idx, dir); + if (ret < 0) + return ret; + break; + + default: + dev_err(dev, "Not a res type token: %d", tkn_elem->token); + return -EINVAL; + + } + tkn_count++; + + return tkn_count; +} + +/* + * Parse tokens to fill up the module private data + */ +static int skl_tplg_get_token(struct device *dev, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + struct skl *skl, struct skl_module_cfg *mconfig) +{ + int tkn_count = 0; + int ret; + static int is_pipe_exists; + static int pin_index, dir, conf_idx; + struct skl_module_iface *iface = NULL; + struct skl_module_res *res = NULL; + int res_idx = mconfig->res_idx; + int fmt_idx = mconfig->fmt_idx; + + /* + * If the manifest structure contains no modules, fill all + * the module data to 0th index. + * res_idx and fmt_idx are default set to 0. + */ + if (skl->nr_modules == 0) { + res = &mconfig->module->resources[res_idx]; + iface = &mconfig->module->formats[fmt_idx]; + } + + if (tkn_elem->token > SKL_TKN_MAX) + return -EINVAL; + + switch (tkn_elem->token) { + case SKL_TKN_U8_IN_QUEUE_COUNT: + mconfig->module->max_input_pins = tkn_elem->value; + break; + + case SKL_TKN_U8_OUT_QUEUE_COUNT: + mconfig->module->max_output_pins = tkn_elem->value; + break; + + case SKL_TKN_U8_DYN_IN_PIN: + if (!mconfig->m_in_pin) + mconfig->m_in_pin = + devm_kcalloc(dev, MAX_IN_QUEUE, + sizeof(*mconfig->m_in_pin), + GFP_KERNEL); + if (!mconfig->m_in_pin) + return -ENOMEM; + + skl_tplg_fill_pin_dynamic_val(mconfig->m_in_pin, MAX_IN_QUEUE, + tkn_elem->value); + break; + + case SKL_TKN_U8_DYN_OUT_PIN: + if (!mconfig->m_out_pin) + mconfig->m_out_pin = + devm_kcalloc(dev, MAX_IN_QUEUE, + sizeof(*mconfig->m_in_pin), + GFP_KERNEL); + if (!mconfig->m_out_pin) + return -ENOMEM; + + skl_tplg_fill_pin_dynamic_val(mconfig->m_out_pin, MAX_OUT_QUEUE, + tkn_elem->value); + break; + + case SKL_TKN_U8_TIME_SLOT: + mconfig->time_slot = tkn_elem->value; + break; + + case SKL_TKN_U8_CORE_ID: + mconfig->core_id = tkn_elem->value; + break; + + case SKL_TKN_U8_MOD_TYPE: + mconfig->m_type = tkn_elem->value; + break; + + case SKL_TKN_U8_DEV_TYPE: + mconfig->dev_type = tkn_elem->value; + break; + + case SKL_TKN_U8_HW_CONN_TYPE: + mconfig->hw_conn_type = tkn_elem->value; + break; + + case SKL_TKN_U16_MOD_INST_ID: + mconfig->id.instance_id = + tkn_elem->value; + break; + + case SKL_TKN_U32_MEM_PAGES: + case SKL_TKN_U32_MAX_MCPS: + case SKL_TKN_U32_OBS: + case SKL_TKN_U32_IBS: + ret = skl_tplg_fill_res_tkn(dev, tkn_elem, res, pin_index, dir); + if (ret < 0) + return ret; + + break; + + case SKL_TKN_U32_VBUS_ID: + mconfig->vbus_id = tkn_elem->value; + break; + + case SKL_TKN_U32_PARAMS_FIXUP: + mconfig->params_fixup = tkn_elem->value; + break; + + case SKL_TKN_U32_CONVERTER: + mconfig->converter = tkn_elem->value; + break; + + case SKL_TKN_U32_D0I3_CAPS: + mconfig->d0i3_caps = tkn_elem->value; + break; + + case SKL_TKN_U32_PIPE_ID: + ret = skl_tplg_add_pipe(dev, + mconfig, skl, tkn_elem); + + if (ret < 0) { + if (ret == -EEXIST) { + is_pipe_exists = 1; + break; + } + return is_pipe_exists; + } + + break; + + case SKL_TKN_U32_PIPE_CONFIG_ID: + conf_idx = tkn_elem->value; + break; + + case SKL_TKN_U32_PIPE_CONN_TYPE: + case SKL_TKN_U32_PIPE_PRIORITY: + case SKL_TKN_U32_PIPE_MEM_PGS: + case SKL_TKN_U32_PMODE: + case SKL_TKN_U32_PIPE_DIRECTION: + case SKL_TKN_U32_NUM_CONFIGS: + if (is_pipe_exists) { + ret = skl_tplg_fill_pipe_tkn(dev, mconfig->pipe, + tkn_elem->token, tkn_elem->value); + if (ret < 0) + return ret; + } + + break; + + case SKL_TKN_U32_PATH_MEM_PGS: + case SKL_TKN_U32_CFG_FREQ: + case SKL_TKN_U8_CFG_CHAN: + case SKL_TKN_U8_CFG_BPS: + if (mconfig->pipe->nr_cfgs) { + ret = skl_tplg_fill_pipe_cfg(dev, mconfig->pipe, + tkn_elem->token, tkn_elem->value, + conf_idx, dir); + if (ret < 0) + return ret; + } + break; + + case SKL_TKN_CFG_MOD_RES_ID: + mconfig->mod_cfg[conf_idx].res_idx = tkn_elem->value; + break; + + case SKL_TKN_CFG_MOD_FMT_ID: + mconfig->mod_cfg[conf_idx].fmt_idx = tkn_elem->value; + break; + + /* + * SKL_TKN_U32_DIR_PIN_COUNT token has the value for both + * direction and the pin count. The first four bits represent + * direction and next four the pin count. + */ + case SKL_TKN_U32_DIR_PIN_COUNT: + dir = tkn_elem->value & SKL_IN_DIR_BIT_MASK; + pin_index = (tkn_elem->value & + SKL_PIN_COUNT_MASK) >> 4; + + break; + + case SKL_TKN_U32_FMT_CH: + case SKL_TKN_U32_FMT_FREQ: + case SKL_TKN_U32_FMT_BIT_DEPTH: + case SKL_TKN_U32_FMT_SAMPLE_SIZE: + case SKL_TKN_U32_FMT_CH_CONFIG: + case SKL_TKN_U32_FMT_INTERLEAVE: + case SKL_TKN_U32_FMT_SAMPLE_TYPE: + case SKL_TKN_U32_FMT_CH_MAP: + ret = skl_tplg_widget_fill_fmt(dev, iface, tkn_elem->token, + tkn_elem->value, dir, pin_index); + + if (ret < 0) + return ret; + + break; + + case SKL_TKN_U32_PIN_MOD_ID: + case SKL_TKN_U32_PIN_INST_ID: + case SKL_TKN_UUID: + ret = skl_tplg_fill_pins_info(dev, + mconfig, tkn_elem, dir, + pin_index); + if (ret < 0) + return ret; + + break; + + case SKL_TKN_U32_CAPS_SIZE: + mconfig->formats_config.caps_size = + tkn_elem->value; + + break; + + case SKL_TKN_U32_CAPS_SET_PARAMS: + mconfig->formats_config.set_params = + tkn_elem->value; + break; + + case SKL_TKN_U32_CAPS_PARAMS_ID: + mconfig->formats_config.param_id = + tkn_elem->value; + break; + + case SKL_TKN_U32_PROC_DOMAIN: + mconfig->domain = + tkn_elem->value; + + break; + + case SKL_TKN_U32_DMA_BUF_SIZE: + mconfig->dma_buffer_size = tkn_elem->value; + break; + + case SKL_TKN_U8_IN_PIN_TYPE: + case SKL_TKN_U8_OUT_PIN_TYPE: + case SKL_TKN_U8_CONN_TYPE: + break; + + default: + dev_err(dev, "Token %d not handled\n", + tkn_elem->token); + return -EINVAL; + } + + tkn_count++; + + return tkn_count; +} + +/* + * Parse the vendor array for specific tokens to construct + * module private data + */ +static int skl_tplg_get_tokens(struct device *dev, + char *pvt_data, struct skl *skl, + struct skl_module_cfg *mconfig, int block_size) +{ + struct snd_soc_tplg_vendor_array *array; + struct snd_soc_tplg_vendor_value_elem *tkn_elem; + int tkn_count = 0, ret; + int off = 0, tuple_size = 0; + bool is_module_guid = true; + + if (block_size <= 0) + return -EINVAL; + + while (tuple_size < block_size) { + array = (struct snd_soc_tplg_vendor_array *)(pvt_data + off); + + off += array->size; + + switch (array->type) { + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + dev_warn(dev, "no string tokens expected for skl tplg\n"); + continue; + + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + if (is_module_guid) { + ret = skl_tplg_get_uuid(dev, mconfig->guid, + array->uuid); + is_module_guid = false; + } else { + ret = skl_tplg_get_token(dev, array->value, skl, + mconfig); + } + + if (ret < 0) + return ret; + + tuple_size += sizeof(*array->uuid); + + continue; + + default: + tkn_elem = array->value; + tkn_count = 0; + break; + } + + while (tkn_count <= (array->num_elems - 1)) { + ret = skl_tplg_get_token(dev, tkn_elem, + skl, mconfig); + + if (ret < 0) + return ret; + + tkn_count = tkn_count + ret; + tkn_elem++; + } + + tuple_size += tkn_count * sizeof(*tkn_elem); + } + + return off; +} + +/* + * Every data block is preceded by a descriptor to read the number + * of data blocks, they type of the block and it's size + */ +static int skl_tplg_get_desc_blocks(struct device *dev, + struct snd_soc_tplg_vendor_array *array) +{ + struct snd_soc_tplg_vendor_value_elem *tkn_elem; + + tkn_elem = array->value; + + switch (tkn_elem->token) { + case SKL_TKN_U8_NUM_BLOCKS: + case SKL_TKN_U8_BLOCK_TYPE: + case SKL_TKN_U16_BLOCK_SIZE: + return tkn_elem->value; + + default: + dev_err(dev, "Invalid descriptor token %d\n", tkn_elem->token); + break; + } + + return -EINVAL; +} + +/* Functions to parse private data from configuration file format v4 */ + +/* + * Add pipeline from topology binary into driver pipeline list + * + * If already added we return that instance + * Otherwise we create a new instance and add into driver list + */ +static int skl_tplg_add_pipe_v4(struct device *dev, + struct skl_module_cfg *mconfig, struct skl *skl, + struct skl_dfw_v4_pipe *dfw_pipe) +{ + struct skl_pipeline *ppl; + struct skl_pipe *pipe; + struct skl_pipe_params *params; + + list_for_each_entry(ppl, &skl->ppl_list, node) { + if (ppl->pipe->ppl_id == dfw_pipe->pipe_id) { + mconfig->pipe = ppl->pipe; + return 0; + } + } + + ppl = devm_kzalloc(dev, sizeof(*ppl), GFP_KERNEL); + if (!ppl) + return -ENOMEM; + + pipe = devm_kzalloc(dev, sizeof(*pipe), GFP_KERNEL); + if (!pipe) + return -ENOMEM; + + params = devm_kzalloc(dev, sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + pipe->ppl_id = dfw_pipe->pipe_id; + pipe->memory_pages = dfw_pipe->memory_pages; + pipe->pipe_priority = dfw_pipe->pipe_priority; + pipe->conn_type = dfw_pipe->conn_type; + pipe->state = SKL_PIPE_INVALID; + pipe->p_params = params; + INIT_LIST_HEAD(&pipe->w_list); + + ppl->pipe = pipe; + list_add(&ppl->node, &skl->ppl_list); + + mconfig->pipe = pipe; + + return 0; +} + +static void skl_fill_module_pin_info_v4(struct skl_dfw_v4_module_pin *dfw_pin, + struct skl_module_pin *m_pin, + bool is_dynamic, int max_pin) +{ + int i; + + for (i = 0; i < max_pin; i++) { + m_pin[i].id.module_id = dfw_pin[i].module_id; + m_pin[i].id.instance_id = dfw_pin[i].instance_id; + m_pin[i].in_use = false; + m_pin[i].is_dynamic = is_dynamic; + m_pin[i].pin_state = SKL_PIN_UNBIND; + } +} + +static void skl_tplg_fill_fmt_v4(struct skl_module_pin_fmt *dst_fmt, + struct skl_dfw_v4_module_fmt *src_fmt, + int pins) +{ + int i; + + for (i = 0; i < pins; i++) { + dst_fmt[i].fmt.channels = src_fmt[i].channels; + dst_fmt[i].fmt.s_freq = src_fmt[i].freq; + dst_fmt[i].fmt.bit_depth = src_fmt[i].bit_depth; + dst_fmt[i].fmt.valid_bit_depth = src_fmt[i].valid_bit_depth; + dst_fmt[i].fmt.ch_cfg = src_fmt[i].ch_cfg; + dst_fmt[i].fmt.ch_map = src_fmt[i].ch_map; + dst_fmt[i].fmt.interleaving_style = + src_fmt[i].interleaving_style; + dst_fmt[i].fmt.sample_type = src_fmt[i].sample_type; + } +} + +static int skl_tplg_get_pvt_data_v4(struct snd_soc_tplg_dapm_widget *tplg_w, + struct skl *skl, struct device *dev, + struct skl_module_cfg *mconfig) +{ + struct skl_dfw_v4_module *dfw = + (struct skl_dfw_v4_module *)tplg_w->priv.data; + int ret; + + dev_dbg(dev, "Parsing Skylake v4 widget topology data\n"); + + ret = guid_parse(dfw->uuid, (guid_t *)mconfig->guid); + if (ret) + return ret; + mconfig->id.module_id = -1; + mconfig->id.instance_id = dfw->instance_id; + mconfig->module->resources[0].cps = dfw->max_mcps; + mconfig->module->resources[0].ibs = dfw->ibs; + mconfig->module->resources[0].obs = dfw->obs; + mconfig->core_id = dfw->core_id; + mconfig->module->max_input_pins = dfw->max_in_queue; + mconfig->module->max_output_pins = dfw->max_out_queue; + mconfig->module->loadable = dfw->is_loadable; + skl_tplg_fill_fmt_v4(mconfig->module->formats[0].inputs, dfw->in_fmt, + MAX_IN_QUEUE); + skl_tplg_fill_fmt_v4(mconfig->module->formats[0].outputs, dfw->out_fmt, + MAX_OUT_QUEUE); + + mconfig->params_fixup = dfw->params_fixup; + mconfig->converter = dfw->converter; + mconfig->m_type = dfw->module_type; + mconfig->vbus_id = dfw->vbus_id; + mconfig->module->resources[0].is_pages = dfw->mem_pages; + + ret = skl_tplg_add_pipe_v4(dev, mconfig, skl, &dfw->pipe); + if (ret) + return ret; + + mconfig->dev_type = dfw->dev_type; + mconfig->hw_conn_type = dfw->hw_conn_type; + mconfig->time_slot = dfw->time_slot; + mconfig->formats_config.caps_size = dfw->caps.caps_size; + + mconfig->m_in_pin = devm_kcalloc(dev, + MAX_IN_QUEUE, sizeof(*mconfig->m_in_pin), + GFP_KERNEL); + if (!mconfig->m_in_pin) + return -ENOMEM; + + mconfig->m_out_pin = devm_kcalloc(dev, + MAX_OUT_QUEUE, sizeof(*mconfig->m_out_pin), + GFP_KERNEL); + if (!mconfig->m_out_pin) + return -ENOMEM; + + skl_fill_module_pin_info_v4(dfw->in_pin, mconfig->m_in_pin, + dfw->is_dynamic_in_pin, + mconfig->module->max_input_pins); + skl_fill_module_pin_info_v4(dfw->out_pin, mconfig->m_out_pin, + dfw->is_dynamic_out_pin, + mconfig->module->max_output_pins); + + if (mconfig->formats_config.caps_size) { + mconfig->formats_config.set_params = dfw->caps.set_params; + mconfig->formats_config.param_id = dfw->caps.param_id; + mconfig->formats_config.caps = + devm_kzalloc(dev, mconfig->formats_config.caps_size, + GFP_KERNEL); + if (!mconfig->formats_config.caps) + return -ENOMEM; + memcpy(mconfig->formats_config.caps, dfw->caps.caps, + dfw->caps.caps_size); + } + + return 0; +} + +/* + * Parse the private data for the token and corresponding value. + * The private data can have multiple data blocks. So, a data block + * is preceded by a descriptor for number of blocks and a descriptor + * for the type and size of the suceeding data block. + */ +static int skl_tplg_get_pvt_data(struct snd_soc_tplg_dapm_widget *tplg_w, + struct skl *skl, struct device *dev, + struct skl_module_cfg *mconfig) +{ + struct snd_soc_tplg_vendor_array *array; + int num_blocks, block_size = 0, block_type, off = 0; + char *data; + int ret; + + /* + * v4 configuration files have a valid UUID at the start of + * the widget's private data. + */ + if (uuid_is_valid((char *)tplg_w->priv.data)) + return skl_tplg_get_pvt_data_v4(tplg_w, skl, dev, mconfig); + + /* Read the NUM_DATA_BLOCKS descriptor */ + array = (struct snd_soc_tplg_vendor_array *)tplg_w->priv.data; + ret = skl_tplg_get_desc_blocks(dev, array); + if (ret < 0) + return ret; + num_blocks = ret; + + off += array->size; + /* Read the BLOCK_TYPE and BLOCK_SIZE descriptor */ + while (num_blocks > 0) { + array = (struct snd_soc_tplg_vendor_array *) + (tplg_w->priv.data + off); + + ret = skl_tplg_get_desc_blocks(dev, array); + + if (ret < 0) + return ret; + block_type = ret; + off += array->size; + + array = (struct snd_soc_tplg_vendor_array *) + (tplg_w->priv.data + off); + + ret = skl_tplg_get_desc_blocks(dev, array); + + if (ret < 0) + return ret; + block_size = ret; + off += array->size; + + array = (struct snd_soc_tplg_vendor_array *) + (tplg_w->priv.data + off); + + data = (tplg_w->priv.data + off); + + if (block_type == SKL_TYPE_TUPLE) { + ret = skl_tplg_get_tokens(dev, data, + skl, mconfig, block_size); + + if (ret < 0) + return ret; + + --num_blocks; + } else { + if (mconfig->formats_config.caps_size > 0) + memcpy(mconfig->formats_config.caps, data, + mconfig->formats_config.caps_size); + --num_blocks; + ret = mconfig->formats_config.caps_size; + } + off += ret; + } + + return 0; +} + +static void skl_clear_pin_config(struct snd_soc_component *component, + struct snd_soc_dapm_widget *w) +{ + int i; + struct skl_module_cfg *mconfig; + struct skl_pipe *pipe; + + if (!strncmp(w->dapm->component->name, component->name, + strlen(component->name))) { + mconfig = w->priv; + pipe = mconfig->pipe; + for (i = 0; i < mconfig->module->max_input_pins; i++) { + mconfig->m_in_pin[i].in_use = false; + mconfig->m_in_pin[i].pin_state = SKL_PIN_UNBIND; + } + for (i = 0; i < mconfig->module->max_output_pins; i++) { + mconfig->m_out_pin[i].in_use = false; + mconfig->m_out_pin[i].pin_state = SKL_PIN_UNBIND; + } + pipe->state = SKL_PIPE_INVALID; + mconfig->m_state = SKL_MODULE_UNINIT; + } +} + +void skl_cleanup_resources(struct skl *skl) +{ + struct skl_sst *ctx = skl->skl_sst; + struct snd_soc_component *soc_component = skl->component; + struct snd_soc_dapm_widget *w; + struct snd_soc_card *card; + + if (soc_component == NULL) + return; + + card = soc_component->card; + if (!card || !card->instantiated) + return; + + skl->resource.mem = 0; + skl->resource.mcps = 0; + + list_for_each_entry(w, &card->widgets, list) { + if (is_skl_dsp_widget_type(w, ctx->dev) && w->priv != NULL) + skl_clear_pin_config(soc_component, w); + } + + skl_clear_module_cnt(ctx->dsp); +} + +/* + * Topology core widget load callback + * + * This is used to save the private data for each widget which gives + * information to the driver about module and pipeline parameters which DSP + * FW expects like ids, resource values, formats etc + */ +static int skl_tplg_widget_load(struct snd_soc_component *cmpnt, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tplg_w) +{ + int ret; + struct hdac_bus *bus = snd_soc_component_get_drvdata(cmpnt); + struct skl *skl = bus_to_skl(bus); + struct skl_module_cfg *mconfig; + + if (!tplg_w->priv.size) + goto bind_event; + + mconfig = devm_kzalloc(bus->dev, sizeof(*mconfig), GFP_KERNEL); + + if (!mconfig) + return -ENOMEM; + + if (skl->nr_modules == 0) { + mconfig->module = devm_kzalloc(bus->dev, + sizeof(*mconfig->module), GFP_KERNEL); + if (!mconfig->module) + return -ENOMEM; + } + + w->priv = mconfig; + + /* + * module binary can be loaded later, so set it to query when + * module is load for a use case + */ + mconfig->id.module_id = -1; + + /* Parse private data for tuples */ + ret = skl_tplg_get_pvt_data(tplg_w, skl, bus->dev, mconfig); + if (ret < 0) + return ret; + + skl_debug_init_module(skl->debugfs, w, mconfig); + +bind_event: + if (tplg_w->event_type == 0) { + dev_dbg(bus->dev, "ASoC: No event handler required\n"); + return 0; + } + + ret = snd_soc_tplg_widget_bind_event(w, skl_tplg_widget_ops, + ARRAY_SIZE(skl_tplg_widget_ops), + tplg_w->event_type); + + if (ret) { + dev_err(bus->dev, "%s: No matching event handlers found for %d\n", + __func__, tplg_w->event_type); + return -EINVAL; + } + + return 0; +} + +static int skl_init_algo_data(struct device *dev, struct soc_bytes_ext *be, + struct snd_soc_tplg_bytes_control *bc) +{ + struct skl_algo_data *ac; + struct skl_dfw_algo_data *dfw_ac = + (struct skl_dfw_algo_data *)bc->priv.data; + + ac = devm_kzalloc(dev, sizeof(*ac), GFP_KERNEL); + if (!ac) + return -ENOMEM; + + /* Fill private data */ + ac->max = dfw_ac->max; + ac->param_id = dfw_ac->param_id; + ac->set_params = dfw_ac->set_params; + ac->size = dfw_ac->max; + + if (ac->max) { + ac->params = (char *) devm_kzalloc(dev, ac->max, GFP_KERNEL); + if (!ac->params) + return -ENOMEM; + + memcpy(ac->params, dfw_ac->params, ac->max); + } + + be->dobj.private = ac; + return 0; +} + +static int skl_init_enum_data(struct device *dev, struct soc_enum *se, + struct snd_soc_tplg_enum_control *ec) +{ + + void *data; + + if (ec->priv.size) { + data = devm_kzalloc(dev, sizeof(ec->priv.size), GFP_KERNEL); + if (!data) + return -ENOMEM; + memcpy(data, ec->priv.data, ec->priv.size); + se->dobj.private = data; + } + + return 0; + +} + +static int skl_tplg_control_load(struct snd_soc_component *cmpnt, + int index, + struct snd_kcontrol_new *kctl, + struct snd_soc_tplg_ctl_hdr *hdr) +{ + struct soc_bytes_ext *sb; + struct snd_soc_tplg_bytes_control *tplg_bc; + struct snd_soc_tplg_enum_control *tplg_ec; + struct hdac_bus *bus = snd_soc_component_get_drvdata(cmpnt); + struct soc_enum *se; + + switch (hdr->ops.info) { + case SND_SOC_TPLG_CTL_BYTES: + tplg_bc = container_of(hdr, + struct snd_soc_tplg_bytes_control, hdr); + if (kctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + sb = (struct soc_bytes_ext *)kctl->private_value; + if (tplg_bc->priv.size) + return skl_init_algo_data( + bus->dev, sb, tplg_bc); + } + break; + + case SND_SOC_TPLG_CTL_ENUM: + tplg_ec = container_of(hdr, + struct snd_soc_tplg_enum_control, hdr); + if (kctl->access & SNDRV_CTL_ELEM_ACCESS_READWRITE) { + se = (struct soc_enum *)kctl->private_value; + if (tplg_ec->priv.size) + return skl_init_enum_data(bus->dev, se, + tplg_ec); + } + break; + + default: + dev_dbg(bus->dev, "Control load not supported %d:%d:%d\n", + hdr->ops.get, hdr->ops.put, hdr->ops.info); + break; + } + + return 0; +} + +static int skl_tplg_fill_str_mfest_tkn(struct device *dev, + struct snd_soc_tplg_vendor_string_elem *str_elem, + struct skl *skl) +{ + int tkn_count = 0; + static int ref_count; + + switch (str_elem->token) { + case SKL_TKN_STR_LIB_NAME: + if (ref_count > skl->skl_sst->lib_count - 1) { + ref_count = 0; + return -EINVAL; + } + + strncpy(skl->skl_sst->lib_info[ref_count].name, + str_elem->string, + ARRAY_SIZE(skl->skl_sst->lib_info[ref_count].name)); + ref_count++; + break; + + default: + dev_err(dev, "Not a string token %d\n", str_elem->token); + break; + } + tkn_count++; + + return tkn_count; +} + +static int skl_tplg_get_str_tkn(struct device *dev, + struct snd_soc_tplg_vendor_array *array, + struct skl *skl) +{ + int tkn_count = 0, ret; + struct snd_soc_tplg_vendor_string_elem *str_elem; + + str_elem = (struct snd_soc_tplg_vendor_string_elem *)array->value; + while (tkn_count < array->num_elems) { + ret = skl_tplg_fill_str_mfest_tkn(dev, str_elem, skl); + str_elem++; + + if (ret < 0) + return ret; + + tkn_count = tkn_count + ret; + } + + return tkn_count; +} + +static int skl_tplg_manifest_fill_fmt(struct device *dev, + struct skl_module_iface *fmt, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + u32 dir, int fmt_idx) +{ + struct skl_module_pin_fmt *dst_fmt; + struct skl_module_fmt *mod_fmt; + int ret; + + if (!fmt) + return -EINVAL; + + switch (dir) { + case SKL_DIR_IN: + dst_fmt = &fmt->inputs[fmt_idx]; + break; + + case SKL_DIR_OUT: + dst_fmt = &fmt->outputs[fmt_idx]; + break; + + default: + dev_err(dev, "Invalid direction: %d\n", dir); + return -EINVAL; + } + + mod_fmt = &dst_fmt->fmt; + + switch (tkn_elem->token) { + case SKL_TKN_MM_U32_INTF_PIN_ID: + dst_fmt->id = tkn_elem->value; + break; + + default: + ret = skl_tplg_fill_fmt(dev, mod_fmt, tkn_elem->token, + tkn_elem->value); + if (ret < 0) + return ret; + break; + } + + return 0; +} + +static int skl_tplg_fill_mod_info(struct device *dev, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + struct skl_module *mod) +{ + + if (!mod) + return -EINVAL; + + switch (tkn_elem->token) { + case SKL_TKN_U8_IN_PIN_TYPE: + mod->input_pin_type = tkn_elem->value; + break; + + case SKL_TKN_U8_OUT_PIN_TYPE: + mod->output_pin_type = tkn_elem->value; + break; + + case SKL_TKN_U8_IN_QUEUE_COUNT: + mod->max_input_pins = tkn_elem->value; + break; + + case SKL_TKN_U8_OUT_QUEUE_COUNT: + mod->max_output_pins = tkn_elem->value; + break; + + case SKL_TKN_MM_U8_NUM_RES: + mod->nr_resources = tkn_elem->value; + break; + + case SKL_TKN_MM_U8_NUM_INTF: + mod->nr_interfaces = tkn_elem->value; + break; + + default: + dev_err(dev, "Invalid mod info token %d", tkn_elem->token); + return -EINVAL; + } + + return 0; +} + + +static int skl_tplg_get_int_tkn(struct device *dev, + struct snd_soc_tplg_vendor_value_elem *tkn_elem, + struct skl *skl) +{ + int tkn_count = 0, ret, size; + static int mod_idx, res_val_idx, intf_val_idx, dir, pin_idx; + struct skl_module_res *res = NULL; + struct skl_module_iface *fmt = NULL; + struct skl_module *mod = NULL; + static struct skl_astate_param *astate_table; + static int astate_cfg_idx, count; + int i; + + if (skl->modules) { + mod = skl->modules[mod_idx]; + res = &mod->resources[res_val_idx]; + fmt = &mod->formats[intf_val_idx]; + } + + switch (tkn_elem->token) { + case SKL_TKN_U32_LIB_COUNT: + skl->skl_sst->lib_count = tkn_elem->value; + break; + + case SKL_TKN_U8_NUM_MOD: + skl->nr_modules = tkn_elem->value; + skl->modules = devm_kcalloc(dev, skl->nr_modules, + sizeof(*skl->modules), GFP_KERNEL); + if (!skl->modules) + return -ENOMEM; + + for (i = 0; i < skl->nr_modules; i++) { + skl->modules[i] = devm_kzalloc(dev, + sizeof(struct skl_module), GFP_KERNEL); + if (!skl->modules[i]) + return -ENOMEM; + } + break; + + case SKL_TKN_MM_U8_MOD_IDX: + mod_idx = tkn_elem->value; + break; + + case SKL_TKN_U32_ASTATE_COUNT: + if (astate_table != NULL) { + dev_err(dev, "More than one entry for A-State count"); + return -EINVAL; + } + + if (tkn_elem->value > SKL_MAX_ASTATE_CFG) { + dev_err(dev, "Invalid A-State count %d\n", + tkn_elem->value); + return -EINVAL; + } + + size = tkn_elem->value * sizeof(struct skl_astate_param) + + sizeof(count); + skl->cfg.astate_cfg = devm_kzalloc(dev, size, GFP_KERNEL); + if (!skl->cfg.astate_cfg) + return -ENOMEM; + + astate_table = skl->cfg.astate_cfg->astate_table; + count = skl->cfg.astate_cfg->count = tkn_elem->value; + break; + + case SKL_TKN_U32_ASTATE_IDX: + if (tkn_elem->value >= count) { + dev_err(dev, "Invalid A-State index %d\n", + tkn_elem->value); + return -EINVAL; + } + + astate_cfg_idx = tkn_elem->value; + break; + + case SKL_TKN_U32_ASTATE_KCPS: + astate_table[astate_cfg_idx].kcps = tkn_elem->value; + break; + + case SKL_TKN_U32_ASTATE_CLK_SRC: + astate_table[astate_cfg_idx].clk_src = tkn_elem->value; + break; + + case SKL_TKN_U8_IN_PIN_TYPE: + case SKL_TKN_U8_OUT_PIN_TYPE: + case SKL_TKN_U8_IN_QUEUE_COUNT: + case SKL_TKN_U8_OUT_QUEUE_COUNT: + case SKL_TKN_MM_U8_NUM_RES: + case SKL_TKN_MM_U8_NUM_INTF: + ret = skl_tplg_fill_mod_info(dev, tkn_elem, mod); + if (ret < 0) + return ret; + break; + + case SKL_TKN_U32_DIR_PIN_COUNT: + dir = tkn_elem->value & SKL_IN_DIR_BIT_MASK; + pin_idx = (tkn_elem->value & SKL_PIN_COUNT_MASK) >> 4; + break; + + case SKL_TKN_MM_U32_RES_ID: + if (!res) + return -EINVAL; + + res->id = tkn_elem->value; + res_val_idx = tkn_elem->value; + break; + + case SKL_TKN_MM_U32_FMT_ID: + if (!fmt) + return -EINVAL; + + fmt->fmt_idx = tkn_elem->value; + intf_val_idx = tkn_elem->value; + break; + + case SKL_TKN_MM_U32_CPS: + case SKL_TKN_MM_U32_DMA_SIZE: + case SKL_TKN_MM_U32_CPC: + case SKL_TKN_U32_MEM_PAGES: + case SKL_TKN_U32_OBS: + case SKL_TKN_U32_IBS: + case SKL_TKN_MM_U32_RES_PIN_ID: + case SKL_TKN_MM_U32_PIN_BUF: + ret = skl_tplg_fill_res_tkn(dev, tkn_elem, res, pin_idx, dir); + if (ret < 0) + return ret; + + break; + + case SKL_TKN_MM_U32_NUM_IN_FMT: + if (!fmt) + return -EINVAL; + + res->nr_input_pins = tkn_elem->value; + break; + + case SKL_TKN_MM_U32_NUM_OUT_FMT: + if (!fmt) + return -EINVAL; + + res->nr_output_pins = tkn_elem->value; + break; + + case SKL_TKN_U32_FMT_CH: + case SKL_TKN_U32_FMT_FREQ: + case SKL_TKN_U32_FMT_BIT_DEPTH: + case SKL_TKN_U32_FMT_SAMPLE_SIZE: + case SKL_TKN_U32_FMT_CH_CONFIG: + case SKL_TKN_U32_FMT_INTERLEAVE: + case SKL_TKN_U32_FMT_SAMPLE_TYPE: + case SKL_TKN_U32_FMT_CH_MAP: + case SKL_TKN_MM_U32_INTF_PIN_ID: + ret = skl_tplg_manifest_fill_fmt(dev, fmt, tkn_elem, + dir, pin_idx); + if (ret < 0) + return ret; + break; + + default: + dev_err(dev, "Not a manifest token %d\n", tkn_elem->token); + return -EINVAL; + } + tkn_count++; + + return tkn_count; +} + +static int skl_tplg_get_manifest_uuid(struct device *dev, + struct skl *skl, + struct snd_soc_tplg_vendor_uuid_elem *uuid_tkn) +{ + static int ref_count; + struct skl_module *mod; + + if (uuid_tkn->token == SKL_TKN_UUID) { + mod = skl->modules[ref_count]; + memcpy(&mod->uuid, &uuid_tkn->uuid, sizeof(uuid_tkn->uuid)); + ref_count++; + } else { + dev_err(dev, "Not an UUID token tkn %d\n", uuid_tkn->token); + return -EINVAL; + } + + return 0; +} + +/* + * Fill the manifest structure by parsing the tokens based on the + * type. + */ +static int skl_tplg_get_manifest_tkn(struct device *dev, + char *pvt_data, struct skl *skl, + int block_size) +{ + int tkn_count = 0, ret; + int off = 0, tuple_size = 0; + struct snd_soc_tplg_vendor_array *array; + struct snd_soc_tplg_vendor_value_elem *tkn_elem; + + if (block_size <= 0) + return -EINVAL; + + while (tuple_size < block_size) { + array = (struct snd_soc_tplg_vendor_array *)(pvt_data + off); + off += array->size; + switch (array->type) { + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + ret = skl_tplg_get_str_tkn(dev, array, skl); + + if (ret < 0) + return ret; + tkn_count = ret; + + tuple_size += tkn_count * + sizeof(struct snd_soc_tplg_vendor_string_elem); + continue; + + case SND_SOC_TPLG_TUPLE_TYPE_UUID: + ret = skl_tplg_get_manifest_uuid(dev, skl, array->uuid); + if (ret < 0) + return ret; + + tuple_size += sizeof(*array->uuid); + continue; + + default: + tkn_elem = array->value; + tkn_count = 0; + break; + } + + while (tkn_count <= array->num_elems - 1) { + ret = skl_tplg_get_int_tkn(dev, + tkn_elem, skl); + if (ret < 0) + return ret; + + tkn_count = tkn_count + ret; + tkn_elem++; + } + tuple_size += (tkn_count * sizeof(*tkn_elem)); + tkn_count = 0; + } + + return off; +} + +/* + * Parse manifest private data for tokens. The private data block is + * preceded by descriptors for type and size of data block. + */ +static int skl_tplg_get_manifest_data(struct snd_soc_tplg_manifest *manifest, + struct device *dev, struct skl *skl) +{ + struct snd_soc_tplg_vendor_array *array; + int num_blocks, block_size = 0, block_type, off = 0; + char *data; + int ret; + + /* Read the NUM_DATA_BLOCKS descriptor */ + array = (struct snd_soc_tplg_vendor_array *)manifest->priv.data; + ret = skl_tplg_get_desc_blocks(dev, array); + if (ret < 0) + return ret; + num_blocks = ret; + + off += array->size; + /* Read the BLOCK_TYPE and BLOCK_SIZE descriptor */ + while (num_blocks > 0) { + array = (struct snd_soc_tplg_vendor_array *) + (manifest->priv.data + off); + ret = skl_tplg_get_desc_blocks(dev, array); + + if (ret < 0) + return ret; + block_type = ret; + off += array->size; + + array = (struct snd_soc_tplg_vendor_array *) + (manifest->priv.data + off); + + ret = skl_tplg_get_desc_blocks(dev, array); + + if (ret < 0) + return ret; + block_size = ret; + off += array->size; + + array = (struct snd_soc_tplg_vendor_array *) + (manifest->priv.data + off); + + data = (manifest->priv.data + off); + + if (block_type == SKL_TYPE_TUPLE) { + ret = skl_tplg_get_manifest_tkn(dev, data, skl, + block_size); + + if (ret < 0) + return ret; + + --num_blocks; + } else { + return -EINVAL; + } + off += ret; + } + + return 0; +} + +static int skl_manifest_load(struct snd_soc_component *cmpnt, int index, + struct snd_soc_tplg_manifest *manifest) +{ + struct hdac_bus *bus = snd_soc_component_get_drvdata(cmpnt); + struct skl *skl = bus_to_skl(bus); + + /* proceed only if we have private data defined */ + if (manifest->priv.size == 0) + return 0; + + skl_tplg_get_manifest_data(manifest, bus->dev, skl); + + if (skl->skl_sst->lib_count > SKL_MAX_LIB) { + dev_err(bus->dev, "Exceeding max Library count. Got:%d\n", + skl->skl_sst->lib_count); + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_tplg_ops skl_tplg_ops = { + .widget_load = skl_tplg_widget_load, + .control_load = skl_tplg_control_load, + .bytes_ext_ops = skl_tlv_ops, + .bytes_ext_ops_count = ARRAY_SIZE(skl_tlv_ops), + .io_ops = skl_tplg_kcontrol_ops, + .io_ops_count = ARRAY_SIZE(skl_tplg_kcontrol_ops), + .manifest = skl_manifest_load, + .dai_load = skl_dai_load, +}; + +/* + * A pipe can have multiple modules, each of them will be a DAPM widget as + * well. While managing a pipeline we need to get the list of all the + * widgets in a pipelines, so this helper - skl_tplg_create_pipe_widget_list() + * helps to get the SKL type widgets in that pipeline + */ +static int skl_tplg_create_pipe_widget_list(struct snd_soc_component *component) +{ + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mcfg = NULL; + struct skl_pipe_module *p_module = NULL; + struct skl_pipe *pipe; + + list_for_each_entry(w, &component->card->widgets, list) { + if (is_skl_dsp_widget_type(w, component->dev) && w->priv) { + mcfg = w->priv; + pipe = mcfg->pipe; + + p_module = devm_kzalloc(component->dev, + sizeof(*p_module), GFP_KERNEL); + if (!p_module) + return -ENOMEM; + + p_module->w = w; + list_add_tail(&p_module->node, &pipe->w_list); + } + } + + return 0; +} + +static void skl_tplg_set_pipe_type(struct skl *skl, struct skl_pipe *pipe) +{ + struct skl_pipe_module *w_module; + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mconfig; + bool host_found = false, link_found = false; + + list_for_each_entry(w_module, &pipe->w_list, node) { + w = w_module->w; + mconfig = w->priv; + + if (mconfig->dev_type == SKL_DEVICE_HDAHOST) + host_found = true; + else if (mconfig->dev_type != SKL_DEVICE_NONE) + link_found = true; + } + + if (host_found && link_found) + pipe->passthru = true; + else + pipe->passthru = false; +} + +/* This will be read from topology manifest, currently defined here */ +#define SKL_MAX_MCPS 30000000 +#define SKL_FW_MAX_MEM 1000000 + +/* + * SKL topology init routine + */ +int skl_tplg_init(struct snd_soc_component *component, struct hdac_bus *bus) +{ + int ret; + const struct firmware *fw; + struct skl *skl = bus_to_skl(bus); + struct skl_pipeline *ppl; + + ret = request_firmware(&fw, skl->tplg_name, bus->dev); + if (ret < 0) { + dev_info(bus->dev, "tplg fw %s load failed with %d, falling back to dfw_sst.bin", + skl->tplg_name, ret); + ret = request_firmware(&fw, "dfw_sst.bin", bus->dev); + if (ret < 0) { + dev_err(bus->dev, "Fallback tplg fw %s load failed with %d\n", + "dfw_sst.bin", ret); + return ret; + } + } + + /* + * The complete tplg for SKL is loaded as index 0, we don't use + * any other index + */ + ret = snd_soc_tplg_component_load(component, + &skl_tplg_ops, fw, 0); + if (ret < 0) { + dev_err(bus->dev, "tplg component load failed%d\n", ret); + release_firmware(fw); + return -EINVAL; + } + + skl->resource.max_mcps = SKL_MAX_MCPS; + skl->resource.max_mem = SKL_FW_MAX_MEM; + + skl->tplg = fw; + ret = skl_tplg_create_pipe_widget_list(component); + if (ret < 0) + return ret; + + list_for_each_entry(ppl, &skl->ppl_list, node) + skl_tplg_set_pipe_type(skl, ppl->pipe); + + return 0; +} diff --git a/sound/soc/intel/skylake/skl-topology.h b/sound/soc/intel/skylake/skl-topology.h new file mode 100644 index 000000000..82282cac9 --- /dev/null +++ b/sound/soc/intel/skylake/skl-topology.h @@ -0,0 +1,520 @@ +/* + * skl_topology.h - Intel HDA Platform topology header file + * + * Copyright (C) 2014-15 Intel Corp + * Author: Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef __SKL_TOPOLOGY_H__ +#define __SKL_TOPOLOGY_H__ + +#include + +#include +#include +#include +#include "skl.h" + +#define BITS_PER_BYTE 8 +#define MAX_TS_GROUPS 8 +#define MAX_DMIC_TS_GROUPS 4 +#define MAX_FIXED_DMIC_PARAMS_SIZE 727 + +/* Maximum number of coefficients up down mixer module */ +#define UP_DOWN_MIXER_MAX_COEFF 8 + +#define MODULE_MAX_IN_PINS 8 +#define MODULE_MAX_OUT_PINS 8 + +#define SKL_MIC_CH_SUPPORT 4 +#define SKL_MIC_MAX_CH_SUPPORT 8 +#define SKL_DEFAULT_MIC_SEL_GAIN 0x3FF +#define SKL_MIC_SEL_SWITCH 0x3 + +#define SKL_OUTPUT_PIN 0 +#define SKL_INPUT_PIN 1 +#define SKL_MAX_PATH_CONFIGS 8 +#define SKL_MAX_MODULES_IN_PIPE 8 +#define SKL_MAX_MODULE_FORMATS 32 +#define SKL_MAX_MODULE_RESOURCES 32 + +enum skl_channel_index { + SKL_CHANNEL_LEFT = 0, + SKL_CHANNEL_RIGHT = 1, + SKL_CHANNEL_CENTER = 2, + SKL_CHANNEL_LEFT_SURROUND = 3, + SKL_CHANNEL_CENTER_SURROUND = 3, + SKL_CHANNEL_RIGHT_SURROUND = 4, + SKL_CHANNEL_LFE = 7, + SKL_CHANNEL_INVALID = 0xF, +}; + +enum skl_bitdepth { + SKL_DEPTH_8BIT = 8, + SKL_DEPTH_16BIT = 16, + SKL_DEPTH_24BIT = 24, + SKL_DEPTH_32BIT = 32, + SKL_DEPTH_INVALID +}; + + +enum skl_s_freq { + SKL_FS_8000 = 8000, + SKL_FS_11025 = 11025, + SKL_FS_12000 = 12000, + SKL_FS_16000 = 16000, + SKL_FS_22050 = 22050, + SKL_FS_24000 = 24000, + SKL_FS_32000 = 32000, + SKL_FS_44100 = 44100, + SKL_FS_48000 = 48000, + SKL_FS_64000 = 64000, + SKL_FS_88200 = 88200, + SKL_FS_96000 = 96000, + SKL_FS_128000 = 128000, + SKL_FS_176400 = 176400, + SKL_FS_192000 = 192000, + SKL_FS_INVALID +}; + +enum skl_widget_type { + SKL_WIDGET_VMIXER = 1, + SKL_WIDGET_MIXER = 2, + SKL_WIDGET_PGA = 3, + SKL_WIDGET_MUX = 4 +}; + +struct skl_audio_data_format { + enum skl_s_freq s_freq; + enum skl_bitdepth bit_depth; + u32 channel_map; + enum skl_ch_cfg ch_cfg; + enum skl_interleaving interleaving; + u8 number_of_channels; + u8 valid_bit_depth; + u8 sample_type; + u8 reserved[1]; +} __packed; + +struct skl_base_cfg { + u32 cps; + u32 ibs; + u32 obs; + u32 is_pages; + struct skl_audio_data_format audio_fmt; +}; + +struct skl_cpr_gtw_cfg { + u32 node_id; + u32 dma_buffer_size; + u32 config_length; + /* not mandatory; required only for DMIC/I2S */ + u32 config_data[1]; +} __packed; + +struct skl_dma_control { + u32 node_id; + u32 config_length; + u32 config_data[0]; +} __packed; + +struct skl_cpr_cfg { + struct skl_base_cfg base_cfg; + struct skl_audio_data_format out_fmt; + u32 cpr_feature_mask; + struct skl_cpr_gtw_cfg gtw_cfg; +} __packed; + +struct skl_cpr_pin_fmt { + u32 sink_id; + struct skl_audio_data_format src_fmt; + struct skl_audio_data_format dst_fmt; +} __packed; + +struct skl_src_module_cfg { + struct skl_base_cfg base_cfg; + enum skl_s_freq src_cfg; +} __packed; + +struct notification_mask { + u32 notify; + u32 enable; +} __packed; + +struct skl_up_down_mixer_cfg { + struct skl_base_cfg base_cfg; + enum skl_ch_cfg out_ch_cfg; + /* This should be set to 1 if user coefficients are required */ + u32 coeff_sel; + /* Pass the user coeff in this array */ + s32 coeff[UP_DOWN_MIXER_MAX_COEFF]; + u32 ch_map; +} __packed; + +struct skl_algo_cfg { + struct skl_base_cfg base_cfg; + char params[0]; +} __packed; + +struct skl_base_outfmt_cfg { + struct skl_base_cfg base_cfg; + struct skl_audio_data_format out_fmt; +} __packed; + +enum skl_dma_type { + SKL_DMA_HDA_HOST_OUTPUT_CLASS = 0, + SKL_DMA_HDA_HOST_INPUT_CLASS = 1, + SKL_DMA_HDA_HOST_INOUT_CLASS = 2, + SKL_DMA_HDA_LINK_OUTPUT_CLASS = 8, + SKL_DMA_HDA_LINK_INPUT_CLASS = 9, + SKL_DMA_HDA_LINK_INOUT_CLASS = 0xA, + SKL_DMA_DMIC_LINK_INPUT_CLASS = 0xB, + SKL_DMA_I2S_LINK_OUTPUT_CLASS = 0xC, + SKL_DMA_I2S_LINK_INPUT_CLASS = 0xD, +}; + +union skl_ssp_dma_node { + u8 val; + struct { + u8 time_slot_index:4; + u8 i2s_instance:4; + } dma_node; +}; + +union skl_connector_node_id { + u32 val; + struct { + u32 vindex:8; + u32 dma_type:4; + u32 rsvd:20; + } node; +}; + +struct skl_module_fmt { + u32 channels; + u32 s_freq; + u32 bit_depth; + u32 valid_bit_depth; + u32 ch_cfg; + u32 interleaving_style; + u32 sample_type; + u32 ch_map; +}; + +struct skl_module_cfg; + +struct skl_mod_inst_map { + u16 mod_id; + u16 inst_id; +}; + +struct skl_uuid_inst_map { + u16 inst_id; + u16 reserved; + uuid_le mod_uuid; +} __packed; + +struct skl_kpb_params { + u32 num_modules; + union { + struct skl_mod_inst_map map[0]; + struct skl_uuid_inst_map map_uuid[0]; + } u; +}; + +struct skl_module_inst_id { + uuid_le mod_uuid; + int module_id; + u32 instance_id; + int pvt_id; +}; + +enum skl_module_pin_state { + SKL_PIN_UNBIND = 0, + SKL_PIN_BIND_DONE = 1, +}; + +struct skl_module_pin { + struct skl_module_inst_id id; + bool is_dynamic; + bool in_use; + enum skl_module_pin_state pin_state; + struct skl_module_cfg *tgt_mcfg; +}; + +struct skl_specific_cfg { + u32 set_params; + u32 param_id; + u32 caps_size; + u32 *caps; +}; + +enum skl_pipe_state { + SKL_PIPE_INVALID = 0, + SKL_PIPE_CREATED = 1, + SKL_PIPE_PAUSED = 2, + SKL_PIPE_STARTED = 3, + SKL_PIPE_RESET = 4 +}; + +struct skl_pipe_module { + struct snd_soc_dapm_widget *w; + struct list_head node; +}; + +struct skl_pipe_params { + u8 host_dma_id; + u8 link_dma_id; + u32 ch; + u32 s_freq; + u32 s_fmt; + u8 linktype; + snd_pcm_format_t format; + int link_index; + int stream; + unsigned int host_bps; + unsigned int link_bps; +}; + +struct skl_pipe_fmt { + u32 freq; + u8 channels; + u8 bps; +}; + +struct skl_pipe_mcfg { + u8 res_idx; + u8 fmt_idx; +}; + +struct skl_path_config { + u8 mem_pages; + struct skl_pipe_fmt in_fmt; + struct skl_pipe_fmt out_fmt; +}; + +struct skl_pipe { + u8 ppl_id; + u8 pipe_priority; + u16 conn_type; + u32 memory_pages; + u8 lp_mode; + struct skl_pipe_params *p_params; + enum skl_pipe_state state; + u8 direction; + u8 cur_config_idx; + u8 nr_cfgs; + struct skl_path_config configs[SKL_MAX_PATH_CONFIGS]; + struct list_head w_list; + bool passthru; +}; + +enum skl_module_state { + SKL_MODULE_UNINIT = 0, + SKL_MODULE_LOADED = 1, + SKL_MODULE_INIT_DONE = 2, + SKL_MODULE_BIND_DONE = 3, + SKL_MODULE_UNLOADED = 4, +}; + +enum d0i3_capability { + SKL_D0I3_NONE = 0, + SKL_D0I3_STREAMING = 1, + SKL_D0I3_NON_STREAMING = 2, +}; + +struct skl_module_pin_fmt { + u8 id; + struct skl_module_fmt fmt; +}; + +struct skl_module_iface { + u8 fmt_idx; + u8 nr_in_fmt; + u8 nr_out_fmt; + struct skl_module_pin_fmt inputs[MAX_IN_QUEUE]; + struct skl_module_pin_fmt outputs[MAX_OUT_QUEUE]; +}; + +struct skl_module_pin_resources { + u8 pin_index; + u32 buf_size; +}; + +struct skl_module_res { + u8 id; + u32 is_pages; + u32 cps; + u32 ibs; + u32 obs; + u32 dma_buffer_size; + u32 cpc; + u8 nr_input_pins; + u8 nr_output_pins; + struct skl_module_pin_resources input[MAX_IN_QUEUE]; + struct skl_module_pin_resources output[MAX_OUT_QUEUE]; +}; + +struct skl_module { + uuid_le uuid; + u8 loadable; + u8 input_pin_type; + u8 output_pin_type; + u8 max_input_pins; + u8 max_output_pins; + u8 nr_resources; + u8 nr_interfaces; + struct skl_module_res resources[SKL_MAX_MODULE_RESOURCES]; + struct skl_module_iface formats[SKL_MAX_MODULE_FORMATS]; +}; + +struct skl_module_cfg { + u8 guid[16]; + struct skl_module_inst_id id; + struct skl_module *module; + int res_idx; + int fmt_idx; + u8 domain; + bool homogenous_inputs; + bool homogenous_outputs; + struct skl_module_fmt in_fmt[MODULE_MAX_IN_PINS]; + struct skl_module_fmt out_fmt[MODULE_MAX_OUT_PINS]; + u8 max_in_queue; + u8 max_out_queue; + u8 in_queue_mask; + u8 out_queue_mask; + u8 in_queue; + u8 out_queue; + u32 mcps; + u32 ibs; + u32 obs; + u8 is_loadable; + u8 core_id; + u8 dev_type; + u8 dma_id; + u8 time_slot; + u8 dmic_ch_combo_index; + u32 dmic_ch_type; + u32 params_fixup; + u32 converter; + u32 vbus_id; + u32 mem_pages; + enum d0i3_capability d0i3_caps; + u32 dma_buffer_size; /* in milli seconds */ + struct skl_module_pin *m_in_pin; + struct skl_module_pin *m_out_pin; + enum skl_module_type m_type; + enum skl_hw_conn_type hw_conn_type; + enum skl_module_state m_state; + struct skl_pipe *pipe; + struct skl_specific_cfg formats_config; + struct skl_pipe_mcfg mod_cfg[SKL_MAX_MODULES_IN_PIPE]; +}; + +struct skl_algo_data { + u32 param_id; + u32 set_params; + u32 max; + u32 size; + char *params; +}; + +struct skl_pipeline { + struct skl_pipe *pipe; + struct list_head node; +}; + +struct skl_module_deferred_bind { + struct skl_module_cfg *src; + struct skl_module_cfg *dst; + struct list_head node; +}; + +struct skl_mic_sel_config { + u16 mic_switch; + u16 flags; + u16 blob[SKL_MIC_MAX_CH_SUPPORT][SKL_MIC_MAX_CH_SUPPORT]; +} __packed; + +enum skl_channel { + SKL_CH_MONO = 1, + SKL_CH_STEREO = 2, + SKL_CH_TRIO = 3, + SKL_CH_QUATRO = 4, +}; + +static inline struct skl *get_skl_ctx(struct device *dev) +{ + struct hdac_bus *bus = dev_get_drvdata(dev); + + return bus_to_skl(bus); +} + +int skl_tplg_be_update_params(struct snd_soc_dai *dai, + struct skl_pipe_params *params); +int skl_dsp_set_dma_control(struct skl_sst *ctx, u32 *caps, + u32 caps_size, u32 node_id); +void skl_tplg_set_be_dmic_config(struct snd_soc_dai *dai, + struct skl_pipe_params *params, int stream); +int skl_tplg_init(struct snd_soc_component *component, + struct hdac_bus *ebus); +struct skl_module_cfg *skl_tplg_fe_get_cpr_module( + struct snd_soc_dai *dai, int stream); +int skl_tplg_update_pipe_params(struct device *dev, + struct skl_module_cfg *mconfig, struct skl_pipe_params *params); + +void skl_tplg_d0i3_get(struct skl *skl, enum d0i3_capability caps); +void skl_tplg_d0i3_put(struct skl *skl, enum d0i3_capability caps); + +int skl_create_pipeline(struct skl_sst *ctx, struct skl_pipe *pipe); + +int skl_run_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); + +int skl_pause_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); + +int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); + +int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); + +int skl_reset_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); + +int skl_init_module(struct skl_sst *ctx, struct skl_module_cfg *module_config); + +int skl_bind_modules(struct skl_sst *ctx, struct skl_module_cfg + *src_module, struct skl_module_cfg *dst_module); + +int skl_unbind_modules(struct skl_sst *ctx, struct skl_module_cfg + *src_module, struct skl_module_cfg *dst_module); + +int skl_set_module_params(struct skl_sst *ctx, u32 *params, int size, + u32 param_id, struct skl_module_cfg *mcfg); +int skl_get_module_params(struct skl_sst *ctx, u32 *params, int size, + u32 param_id, struct skl_module_cfg *mcfg); + +struct skl_module_cfg *skl_tplg_be_get_cpr_module(struct snd_soc_dai *dai, + int stream); +enum skl_bitdepth skl_get_bit_depth(int params); +int skl_pcm_host_dma_prepare(struct device *dev, + struct skl_pipe_params *params); +int skl_pcm_link_dma_prepare(struct device *dev, + struct skl_pipe_params *params); + +int skl_dai_load(struct snd_soc_component *cmp, int index, + struct snd_soc_dai_driver *dai_drv, + struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai); +void skl_tplg_add_moduleid_in_bind_params(struct skl *skl, + struct snd_soc_dapm_widget *w); +#endif diff --git a/sound/soc/intel/skylake/skl.c b/sound/soc/intel/skylake/skl.c new file mode 100644 index 000000000..50f16a0f6 --- /dev/null +++ b/sound/soc/intel/skylake/skl.c @@ -0,0 +1,1049 @@ +/* + * skl.c - Implementation of ASoC Intel SKL HD Audio driver + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP + * + * Derived mostly from Intel HDA driver with following copyrights: + * Copyright (c) 2004 Takashi Iwai + * PeiSen Hou + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "skl.h" +#include "skl-sst-dsp.h" +#include "skl-sst-ipc.h" + +/* + * initialize the PCI registers + */ +static void skl_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 skl_init_pci(struct skl *skl) +{ + struct hdac_bus *bus = skl_to_bus(skl); + + /* + * 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. + */ + dev_dbg(bus->dev, "Clearing TCSEL\n"); + skl_update_pci_byte(skl->pci, AZX_PCIREG_TCSEL, 0x07, 0); +} + +static void update_pci_dword(struct pci_dev *pci, + unsigned int reg, u32 mask, u32 val) +{ + u32 data = 0; + + pci_read_config_dword(pci, reg, &data); + data &= ~mask; + data |= (val & mask); + pci_write_config_dword(pci, reg, data); +} + +/* + * skl_enable_miscbdcge - enable/dsiable CGCTL.MISCBDCGE bits + * + * @dev: device pointer + * @enable: enable/disable flag + */ +static void skl_enable_miscbdcge(struct device *dev, bool enable) +{ + struct pci_dev *pci = to_pci_dev(dev); + u32 val; + + val = enable ? AZX_CGCTL_MISCBDCGE_MASK : 0; + + update_pci_dword(pci, AZX_PCIREG_CGCTL, AZX_CGCTL_MISCBDCGE_MASK, val); +} + +/** + * skl_clock_power_gating: Enable/Disable clock and power gating + * + * @dev: Device pointer + * @enable: Enable/Disable flag + */ +static void skl_clock_power_gating(struct device *dev, bool enable) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + u32 val; + + /* Update PDCGE bit of CGCTL register */ + val = enable ? AZX_CGCTL_ADSPDCGE : 0; + update_pci_dword(pci, AZX_PCIREG_CGCTL, AZX_CGCTL_ADSPDCGE, val); + + /* Update L1SEN bit of EM2 register */ + val = enable ? AZX_REG_VS_EM2_L1SEN : 0; + snd_hdac_chip_updatel(bus, VS_EM2, AZX_REG_VS_EM2_L1SEN, val); + + /* Update ADSPPGD bit of PGCTL register */ + val = enable ? 0 : AZX_PGCTL_ADSPPGD; + update_pci_dword(pci, AZX_PCIREG_PGCTL, AZX_PGCTL_ADSPPGD, val); +} + +/* + * While performing reset, controller may not come back properly causing + * issues, so recommendation is to set CGCTL.MISCBDCGE to 0 then do reset + * (init chip) and then again set CGCTL.MISCBDCGE to 1 + */ +static int skl_init_chip(struct hdac_bus *bus, bool full_reset) +{ + struct hdac_ext_link *hlink; + int ret; + + skl_enable_miscbdcge(bus->dev, false); + ret = snd_hdac_bus_init_chip(bus, full_reset); + + /* Reset stream-to-link mapping */ + list_for_each_entry(hlink, &bus->hlink_list, list) + bus->io_ops->reg_writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); + + skl_enable_miscbdcge(bus->dev, true); + + return ret; +} + +void skl_update_d0i3c(struct device *dev, bool enable) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + u8 reg; + int timeout = 50; + + reg = snd_hdac_chip_readb(bus, VS_D0I3C); + /* Do not write to D0I3C until command in progress bit is cleared */ + while ((reg & AZX_REG_VS_D0I3C_CIP) && --timeout) { + udelay(10); + reg = snd_hdac_chip_readb(bus, VS_D0I3C); + } + + /* Highly unlikely. But if it happens, flag error explicitly */ + if (!timeout) { + dev_err(bus->dev, "Before D0I3C update: D0I3C CIP timeout\n"); + return; + } + + if (enable) + reg = reg | AZX_REG_VS_D0I3C_I3; + else + reg = reg & (~AZX_REG_VS_D0I3C_I3); + + snd_hdac_chip_writeb(bus, VS_D0I3C, reg); + + timeout = 50; + /* Wait for cmd in progress to be cleared before exiting the function */ + reg = snd_hdac_chip_readb(bus, VS_D0I3C); + while ((reg & AZX_REG_VS_D0I3C_CIP) && --timeout) { + udelay(10); + reg = snd_hdac_chip_readb(bus, VS_D0I3C); + } + + /* Highly unlikely. But if it happens, flag error explicitly */ + if (!timeout) { + dev_err(bus->dev, "After D0I3C update: D0I3C CIP timeout\n"); + return; + } + + dev_dbg(bus->dev, "D0I3C register = 0x%x\n", + snd_hdac_chip_readb(bus, VS_D0I3C)); +} + +/* called from IRQ */ +static void skl_stream_update(struct hdac_bus *bus, struct hdac_stream *hstr) +{ + snd_pcm_period_elapsed(hstr->substream); +} + +static irqreturn_t skl_interrupt(int irq, void *dev_id) +{ + struct hdac_bus *bus = dev_id; + u32 status; + + if (!pm_runtime_active(bus->dev)) + return IRQ_NONE; + + spin_lock(&bus->reg_lock); + + status = snd_hdac_chip_readl(bus, INTSTS); + if (status == 0 || status == 0xffffffff) { + spin_unlock(&bus->reg_lock); + return IRQ_NONE; + } + + /* clear rirb int */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { + if (status & RIRB_INT_RESPONSE) + snd_hdac_bus_update_rirb(bus); + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); + } + + spin_unlock(&bus->reg_lock); + + return snd_hdac_chip_readl(bus, INTSTS) ? IRQ_WAKE_THREAD : IRQ_HANDLED; +} + +static irqreturn_t skl_threaded_handler(int irq, void *dev_id) +{ + struct hdac_bus *bus = dev_id; + u32 status; + + status = snd_hdac_chip_readl(bus, INTSTS); + + snd_hdac_bus_handle_stream_irq(bus, status, skl_stream_update); + + return IRQ_HANDLED; +} + +static int skl_acquire_irq(struct hdac_bus *bus, int do_disconnect) +{ + struct skl *skl = bus_to_skl(bus); + int ret; + + ret = request_threaded_irq(skl->pci->irq, skl_interrupt, + skl_threaded_handler, + IRQF_SHARED, + KBUILD_MODNAME, bus); + if (ret) { + dev_err(bus->dev, + "unable to grab IRQ %d, disabling device\n", + skl->pci->irq); + return ret; + } + + bus->irq = skl->pci->irq; + pci_intx(skl->pci, 1); + + return 0; +} + +static int skl_suspend_late(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + struct skl *skl = bus_to_skl(bus); + + return skl_suspend_late_dsp(skl); +} + +#ifdef CONFIG_PM +static int _skl_suspend(struct hdac_bus *bus) +{ + struct skl *skl = bus_to_skl(bus); + struct pci_dev *pci = to_pci_dev(bus->dev); + int ret; + + snd_hdac_ext_bus_link_power_down_all(bus); + + ret = skl_suspend_dsp(skl); + if (ret < 0) + return ret; + + snd_hdac_bus_stop_chip(bus); + update_pci_dword(pci, AZX_PCIREG_PGCTL, + AZX_PGCTL_LSRMD_MASK, AZX_PGCTL_LSRMD_MASK); + skl_enable_miscbdcge(bus->dev, false); + snd_hdac_bus_enter_link_reset(bus); + skl_enable_miscbdcge(bus->dev, true); + skl_cleanup_resources(skl); + + return 0; +} + +static int _skl_resume(struct hdac_bus *bus) +{ + struct skl *skl = bus_to_skl(bus); + + skl_init_pci(skl); + skl_init_chip(bus, true); + + return skl_resume_dsp(skl); +} +#endif + +#ifdef CONFIG_PM_SLEEP +/* + * power management + */ +static int skl_suspend(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + struct skl *skl = bus_to_skl(bus); + int ret = 0; + + /* + * Do not suspend if streams which are marked ignore suspend are + * running, we need to save the state for these and continue + */ + if (skl->supend_active) { + /* turn off the links and stop the CORB/RIRB DMA if it is On */ + snd_hdac_ext_bus_link_power_down_all(bus); + + if (bus->cmd_dma_state) + snd_hdac_bus_stop_cmd_io(bus); + + enable_irq_wake(bus->irq); + pci_save_state(pci); + } else { + ret = _skl_suspend(bus); + if (ret < 0) + return ret; + skl->skl_sst->fw_loaded = false; + } + + if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) { + ret = snd_hdac_display_power(bus, false); + if (ret < 0) + dev_err(bus->dev, + "Cannot turn OFF display power on i915\n"); + } + + return ret; +} + +static int skl_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + struct skl *skl = bus_to_skl(bus); + struct hdac_ext_link *hlink = NULL; + int ret; + + /* Turned OFF in HDMI codec driver after codec reconfiguration */ + if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) { + ret = snd_hdac_display_power(bus, true); + if (ret < 0) { + dev_err(bus->dev, + "Cannot turn on display power on i915\n"); + return ret; + } + } + + /* + * resume only when we are not in suspend active, otherwise need to + * restore the device + */ + if (skl->supend_active) { + pci_restore_state(pci); + snd_hdac_ext_bus_link_power_up_all(bus); + disable_irq_wake(bus->irq); + /* + * turn On the links which are On before active suspend + * and start the CORB/RIRB DMA if On before + * active suspend. + */ + list_for_each_entry(hlink, &bus->hlink_list, list) { + if (hlink->ref_count) + snd_hdac_ext_bus_link_power_up(hlink); + } + + ret = 0; + if (bus->cmd_dma_state) + snd_hdac_bus_init_cmd_io(bus); + } else { + ret = _skl_resume(bus); + + /* turn off the links which are off before suspend */ + list_for_each_entry(hlink, &bus->hlink_list, list) { + if (!hlink->ref_count) + snd_hdac_ext_bus_link_power_down(hlink); + } + + if (!bus->cmd_dma_state) + snd_hdac_bus_stop_cmd_io(bus); + } + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int skl_runtime_suspend(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + + dev_dbg(bus->dev, "in %s\n", __func__); + + return _skl_suspend(bus); +} + +static int skl_runtime_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct hdac_bus *bus = pci_get_drvdata(pci); + + dev_dbg(bus->dev, "in %s\n", __func__); + + return _skl_resume(bus); +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops skl_pm = { + SET_SYSTEM_SLEEP_PM_OPS(skl_suspend, skl_resume) + SET_RUNTIME_PM_OPS(skl_runtime_suspend, skl_runtime_resume, NULL) + .suspend_late = skl_suspend_late, +}; + +/* + * destructor + */ +static int skl_free(struct hdac_bus *bus) +{ + struct skl *skl = bus_to_skl(bus); + + skl->init_done = 0; /* to be sure */ + + snd_hdac_ext_stop_streams(bus); + + if (bus->irq >= 0) + free_irq(bus->irq, (void *)bus); + snd_hdac_bus_free_stream_pages(bus); + snd_hdac_stream_free_all(bus); + snd_hdac_link_free_all(bus); + + if (bus->remap_addr) + iounmap(bus->remap_addr); + + pci_release_regions(skl->pci); + pci_disable_device(skl->pci); + + snd_hdac_ext_bus_exit(bus); + + cancel_work_sync(&skl->probe_work); + if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) + snd_hdac_i915_exit(bus); + + return 0; +} + +/* + * For each ssp there are 3 clocks (mclk/sclk/sclkfs). + * e.g. for ssp0, clocks will be named as + * "ssp0_mclk", "ssp0_sclk", "ssp0_sclkfs" + * So for skl+, there are 6 ssps, so 18 clocks will be created. + */ +static struct skl_ssp_clk skl_ssp_clks[] = { + {.name = "ssp0_mclk"}, {.name = "ssp1_mclk"}, {.name = "ssp2_mclk"}, + {.name = "ssp3_mclk"}, {.name = "ssp4_mclk"}, {.name = "ssp5_mclk"}, + {.name = "ssp0_sclk"}, {.name = "ssp1_sclk"}, {.name = "ssp2_sclk"}, + {.name = "ssp3_sclk"}, {.name = "ssp4_sclk"}, {.name = "ssp5_sclk"}, + {.name = "ssp0_sclkfs"}, {.name = "ssp1_sclkfs"}, + {.name = "ssp2_sclkfs"}, + {.name = "ssp3_sclkfs"}, {.name = "ssp4_sclkfs"}, + {.name = "ssp5_sclkfs"}, +}; + +static int skl_find_machine(struct skl *skl, void *driver_data) +{ + struct hdac_bus *bus = skl_to_bus(skl); + struct snd_soc_acpi_mach *mach = driver_data; + struct skl_machine_pdata *pdata; + + mach = snd_soc_acpi_find_machine(mach); + if (mach == NULL) { + dev_err(bus->dev, "No matching machine driver found\n"); + return -ENODEV; + } + + skl->mach = mach; + skl->fw_name = mach->fw_filename; + pdata = mach->pdata; + + if (pdata) { + skl->use_tplg_pcm = pdata->use_tplg_pcm; + pdata->dmic_num = skl_get_dmic_geo(skl); + } + + return 0; +} + +static int skl_machine_device_register(struct skl *skl) +{ + struct hdac_bus *bus = skl_to_bus(skl); + struct snd_soc_acpi_mach *mach = skl->mach; + struct platform_device *pdev; + int ret; + + pdev = platform_device_alloc(mach->drv_name, -1); + if (pdev == NULL) { + dev_err(bus->dev, "platform device alloc failed\n"); + return -EIO; + } + + ret = platform_device_add(pdev); + if (ret) { + dev_err(bus->dev, "failed to add machine device\n"); + platform_device_put(pdev); + return -EIO; + } + + if (mach->pdata) + dev_set_drvdata(&pdev->dev, mach->pdata); + + skl->i2s_dev = pdev; + + return 0; +} + +static void skl_machine_device_unregister(struct skl *skl) +{ + if (skl->i2s_dev) + platform_device_unregister(skl->i2s_dev); +} + +static int skl_dmic_device_register(struct skl *skl) +{ + struct hdac_bus *bus = skl_to_bus(skl); + struct platform_device *pdev; + int ret; + + /* SKL has one dmic port, so allocate dmic device for this */ + pdev = platform_device_alloc("dmic-codec", -1); + if (!pdev) { + dev_err(bus->dev, "failed to allocate dmic device\n"); + return -ENOMEM; + } + + ret = platform_device_add(pdev); + if (ret) { + dev_err(bus->dev, "failed to add dmic device: %d\n", ret); + platform_device_put(pdev); + return ret; + } + skl->dmic_dev = pdev; + + return 0; +} + +static void skl_dmic_device_unregister(struct skl *skl) +{ + if (skl->dmic_dev) + platform_device_unregister(skl->dmic_dev); +} + +static struct skl_clk_parent_src skl_clk_src[] = { + { .clk_id = SKL_XTAL, .name = "xtal" }, + { .clk_id = SKL_CARDINAL, .name = "cardinal", .rate = 24576000 }, + { .clk_id = SKL_PLL, .name = "pll", .rate = 96000000 }, +}; + +struct skl_clk_parent_src *skl_get_parent_clk(u8 clk_id) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(skl_clk_src); i++) { + if (skl_clk_src[i].clk_id == clk_id) + return &skl_clk_src[i]; + } + + return NULL; +} + +static void init_skl_xtal_rate(int pci_id) +{ + switch (pci_id) { + case 0x9d70: + case 0x9d71: + skl_clk_src[0].rate = 24000000; + return; + + default: + skl_clk_src[0].rate = 19200000; + return; + } +} + +static int skl_clock_device_register(struct skl *skl) +{ + struct platform_device_info pdevinfo = {NULL}; + struct skl_clk_pdata *clk_pdata; + + clk_pdata = devm_kzalloc(&skl->pci->dev, sizeof(*clk_pdata), + GFP_KERNEL); + if (!clk_pdata) + return -ENOMEM; + + init_skl_xtal_rate(skl->pci->device); + + clk_pdata->parent_clks = skl_clk_src; + clk_pdata->ssp_clks = skl_ssp_clks; + clk_pdata->num_clks = ARRAY_SIZE(skl_ssp_clks); + + /* Query NHLT to fill the rates and parent */ + skl_get_clks(skl, clk_pdata->ssp_clks); + clk_pdata->pvt_data = skl; + + /* Register Platform device */ + pdevinfo.parent = &skl->pci->dev; + pdevinfo.id = -1; + pdevinfo.name = "skl-ssp-clk"; + pdevinfo.data = clk_pdata; + pdevinfo.size_data = sizeof(*clk_pdata); + skl->clk_dev = platform_device_register_full(&pdevinfo); + return PTR_ERR_OR_ZERO(skl->clk_dev); +} + +static void skl_clock_device_unregister(struct skl *skl) +{ + if (skl->clk_dev) + platform_device_unregister(skl->clk_dev); +} + +/* + * Probe the given codec address + */ +static int probe_codec(struct hdac_bus *bus, int addr) +{ + unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | + (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; + unsigned int res = -1; + struct skl *skl = bus_to_skl(bus); + struct hdac_device *hdev; + + mutex_lock(&bus->cmd_mutex); + snd_hdac_bus_send_cmd(bus, cmd); + snd_hdac_bus_get_response(bus, addr, &res); + mutex_unlock(&bus->cmd_mutex); + if (res == -1) + return -EIO; + dev_dbg(bus->dev, "codec #%d probed OK\n", addr); + + hdev = devm_kzalloc(&skl->pci->dev, sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + + return snd_hdac_ext_bus_device_init(bus, addr, hdev); +} + +/* Codec initialization */ +static void skl_codec_create(struct hdac_bus *bus) +{ + int c, max_slots; + + max_slots = HDA_MAX_CODECS; + + /* First try to probe all given codec slots */ + for (c = 0; c < max_slots; c++) { + if ((bus->codec_mask & (1 << c))) { + if (probe_codec(bus, c) < 0) { + /* + * Some BIOSen give you wrong codec addresses + * that don't exist + */ + dev_warn(bus->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 bus, + * and disturbs the further communications. + * Thus if an error occurs during probing, + * better to reset the controller bus to get + * back to the sanity state. + */ + snd_hdac_bus_stop_chip(bus); + skl_init_chip(bus, true); + } + } + } +} + +static const struct hdac_bus_ops bus_core_ops = { + .command = snd_hdac_bus_send_cmd, + .get_response = snd_hdac_bus_get_response, +}; + +static int skl_i915_init(struct hdac_bus *bus) +{ + int err; + + /* + * The HDMI codec is in GPU so we need to ensure that it is powered + * up and ready for probe + */ + err = snd_hdac_i915_init(bus); + if (err < 0) + return err; + + err = snd_hdac_display_power(bus, true); + if (err < 0) + dev_err(bus->dev, "Cannot turn on display power on i915\n"); + + return err; +} + +static void skl_probe_work(struct work_struct *work) +{ + struct skl *skl = container_of(work, struct skl, probe_work); + struct hdac_bus *bus = skl_to_bus(skl); + struct hdac_ext_link *hlink = NULL; + int err; + + if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) { + err = skl_i915_init(bus); + if (err < 0) + return; + } + + err = skl_init_chip(bus, true); + if (err < 0) { + dev_err(bus->dev, "Init chip failed with err: %d\n", err); + goto out_err; + } + + /* codec detection */ + if (!bus->codec_mask) + dev_info(bus->dev, "no hda codecs found!\n"); + + /* create codec instances */ + skl_codec_create(bus); + + /* register platform dai and controls */ + err = skl_platform_register(bus->dev); + if (err < 0) { + dev_err(bus->dev, "platform register failed: %d\n", err); + return; + } + + if (bus->ppcap) { + err = skl_machine_device_register(skl); + if (err < 0) { + dev_err(bus->dev, "machine register failed: %d\n", err); + goto out_err; + } + } + + /* + * we are done probing so decrement link counts + */ + list_for_each_entry(hlink, &bus->hlink_list, list) + snd_hdac_ext_bus_link_put(bus, hlink); + + if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) { + err = snd_hdac_display_power(bus, false); + if (err < 0) { + dev_err(bus->dev, "Cannot turn off display power on i915\n"); + skl_machine_device_unregister(skl); + return; + } + } + + /* configure PM */ + pm_runtime_put_noidle(bus->dev); + pm_runtime_allow(bus->dev); + skl->init_done = 1; + + return; + +out_err: + if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) + err = snd_hdac_display_power(bus, false); +} + +/* + * constructor + */ +static int skl_create(struct pci_dev *pci, + const struct hdac_io_ops *io_ops, + struct skl **rskl) +{ + struct skl *skl; + struct hdac_bus *bus; + + int err; + + *rskl = NULL; + + err = pci_enable_device(pci); + if (err < 0) + return err; + + skl = devm_kzalloc(&pci->dev, sizeof(*skl), GFP_KERNEL); + if (!skl) { + pci_disable_device(pci); + return -ENOMEM; + } + + bus = skl_to_bus(skl); + snd_hdac_ext_bus_init(bus, &pci->dev, &bus_core_ops, io_ops, NULL); + bus->use_posbuf = 1; + skl->pci = pci; + INIT_WORK(&skl->probe_work, skl_probe_work); + bus->bdl_pos_adj = 0; + + *rskl = skl; + + return 0; +} + +static int skl_first_init(struct hdac_bus *bus) +{ + struct skl *skl = bus_to_skl(bus); + struct pci_dev *pci = skl->pci; + int err; + unsigned short gcap; + int cp_streams, pb_streams, start_idx; + + err = pci_request_regions(pci, "Skylake HD audio"); + if (err < 0) + return err; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (bus->remap_addr == NULL) { + dev_err(bus->dev, "ioremap error\n"); + return -ENXIO; + } + + snd_hdac_bus_reset_link(bus, true); + + snd_hdac_bus_parse_capabilities(bus); + + if (skl_acquire_irq(bus, 0) < 0) + return -EBUSY; + + pci_set_master(pci); + synchronize_irq(bus->irq); + + gcap = snd_hdac_chip_readw(bus, GCAP); + dev_dbg(bus->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* allow 64bit DMA address if supported by H/W */ + if (!dma_set_mask(bus->dev, DMA_BIT_MASK(64))) { + dma_set_coherent_mask(bus->dev, DMA_BIT_MASK(64)); + } else { + dma_set_mask(bus->dev, DMA_BIT_MASK(32)); + dma_set_coherent_mask(bus->dev, DMA_BIT_MASK(32)); + } + + /* read number of streams from GCAP register */ + cp_streams = (gcap >> 8) & 0x0f; + pb_streams = (gcap >> 12) & 0x0f; + + if (!pb_streams && !cp_streams) + return -EIO; + + bus->num_streams = cp_streams + pb_streams; + + /* initialize streams */ + snd_hdac_ext_stream_init_all + (bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); + start_idx = cp_streams; + snd_hdac_ext_stream_init_all + (bus, start_idx, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); + + err = snd_hdac_bus_alloc_stream_pages(bus); + if (err < 0) + return err; + + /* initialize chip */ + skl_init_pci(skl); + + return skl_init_chip(bus, true); +} + +static int skl_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct skl *skl; + struct hdac_bus *bus = NULL; + int err; + + /* we use ext core ops, so provide NULL for ops here */ + err = skl_create(pci, NULL, &skl); + if (err < 0) + return err; + + bus = skl_to_bus(skl); + + err = skl_first_init(bus); + if (err < 0) + goto out_free; + + skl->pci_id = pci->device; + + device_disable_async_suspend(bus->dev); + + skl->nhlt = skl_nhlt_init(bus->dev); + + if (skl->nhlt == NULL) { + err = -ENODEV; + goto out_free; + } + + err = skl_nhlt_create_sysfs(skl); + if (err < 0) + goto out_nhlt_free; + + skl_nhlt_update_topology_bin(skl); + + pci_set_drvdata(skl->pci, bus); + + /* check if dsp is there */ + if (bus->ppcap) { + /* create device for dsp clk */ + err = skl_clock_device_register(skl); + if (err < 0) + goto out_clk_free; + + err = skl_find_machine(skl, (void *)pci_id->driver_data); + if (err < 0) + goto out_nhlt_free; + + err = skl_init_dsp(skl); + if (err < 0) { + dev_dbg(bus->dev, "error failed to register dsp\n"); + goto out_nhlt_free; + } + skl->skl_sst->enable_miscbdcge = skl_enable_miscbdcge; + skl->skl_sst->clock_power_gating = skl_clock_power_gating; + } + if (bus->mlcap) + snd_hdac_ext_bus_get_ml_capabilities(bus); + + snd_hdac_bus_stop_chip(bus); + + /* create device for soc dmic */ + err = skl_dmic_device_register(skl); + if (err < 0) + goto out_dsp_free; + + schedule_work(&skl->probe_work); + + return 0; + +out_dsp_free: + skl_free_dsp(skl); +out_clk_free: + skl_clock_device_unregister(skl); +out_nhlt_free: + skl_nhlt_free(skl->nhlt); +out_free: + skl_free(bus); + + return err; +} + +static void skl_shutdown(struct pci_dev *pci) +{ + struct hdac_bus *bus = pci_get_drvdata(pci); + struct hdac_stream *s; + struct hdac_ext_stream *stream; + struct skl *skl; + + if (!bus) + return; + + skl = bus_to_skl(bus); + + if (!skl->init_done) + return; + + snd_hdac_ext_stop_streams(bus); + list_for_each_entry(s, &bus->stream_list, list) { + stream = stream_to_hdac_ext_stream(s); + snd_hdac_ext_stream_decouple(bus, stream, false); + } + + snd_hdac_bus_stop_chip(bus); +} + +static void skl_remove(struct pci_dev *pci) +{ + struct hdac_bus *bus = pci_get_drvdata(pci); + struct skl *skl = bus_to_skl(bus); + + release_firmware(skl->tplg); + + pm_runtime_get_noresume(&pci->dev); + + /* codec removal, invoke bus_device_remove */ + snd_hdac_ext_bus_device_remove(bus); + + skl->debugfs = NULL; + skl_platform_unregister(&pci->dev); + skl_free_dsp(skl); + skl_machine_device_unregister(skl); + skl_dmic_device_unregister(skl); + skl_clock_device_unregister(skl); + skl_nhlt_remove_sysfs(skl); + skl_nhlt_free(skl->nhlt); + skl_free(bus); + dev_set_drvdata(&pci->dev, NULL); +} + +/* PCI IDs */ +static const struct pci_device_id skl_ids[] = { + /* Sunrise Point-LP */ + { PCI_DEVICE(0x8086, 0x9d70), + .driver_data = (unsigned long)&snd_soc_acpi_intel_skl_machines}, + /* BXT-P */ + { PCI_DEVICE(0x8086, 0x5a98), + .driver_data = (unsigned long)&snd_soc_acpi_intel_bxt_machines}, + /* KBL */ + { PCI_DEVICE(0x8086, 0x9D71), + .driver_data = (unsigned long)&snd_soc_acpi_intel_kbl_machines}, + /* GLK */ + { PCI_DEVICE(0x8086, 0x3198), + .driver_data = (unsigned long)&snd_soc_acpi_intel_glk_machines}, + /* CNL */ + { PCI_DEVICE(0x8086, 0x9dc8), + .driver_data = (unsigned long)&snd_soc_acpi_intel_cnl_machines}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, skl_ids); + +/* pci_driver definition */ +static struct pci_driver skl_driver = { + .name = KBUILD_MODNAME, + .id_table = skl_ids, + .probe = skl_probe, + .remove = skl_remove, + .shutdown = skl_shutdown, + .driver = { + .pm = &skl_pm, + }, +}; +module_pci_driver(skl_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Skylake ASoC HDA driver"); diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h new file mode 100644 index 000000000..78aa8bdcb --- /dev/null +++ b/sound/soc/intel/skylake/skl.h @@ -0,0 +1,178 @@ +/* + * skl.h - HD Audio skylake defintions. + * + * Copyright (C) 2015 Intel Corp + * Author: Jeeja KP + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef __SOUND_SOC_SKL_H +#define __SOUND_SOC_SKL_H + +#include +#include +#include +#include "skl-nhlt.h" +#include "skl-ssp-clk.h" + +#define SKL_SUSPEND_DELAY 2000 + +#define SKL_MAX_ASTATE_CFG 3 + +#define AZX_PCIREG_PGCTL 0x44 +#define AZX_PGCTL_LSRMD_MASK (1 << 4) +#define AZX_PGCTL_ADSPPGD BIT(2) +#define AZX_PCIREG_CGCTL 0x48 +#define AZX_CGCTL_MISCBDCGE_MASK (1 << 6) +#define AZX_CGCTL_ADSPDCGE BIT(1) +/* D0I3C Register fields */ +#define AZX_REG_VS_D0I3C_CIP 0x1 /* Command in progress */ +#define AZX_REG_VS_D0I3C_I3 0x4 /* D0i3 enable */ +#define SKL_MAX_DMACTRL_CFG 18 +#define DMA_CLK_CONTROLS 1 +#define DMA_TRANSMITION_START 2 +#define DMA_TRANSMITION_STOP 3 + +#define AZX_REG_VS_EM2_L1SEN BIT(13) + +struct skl_dsp_resource { + u32 max_mcps; + u32 max_mem; + u32 mcps; + u32 mem; +}; + +struct skl_debug; + +struct skl_astate_param { + u32 kcps; + u32 clk_src; +}; + +struct skl_astate_config { + u32 count; + struct skl_astate_param astate_table[0]; +}; + +struct skl_fw_config { + struct skl_astate_config *astate_cfg; +}; + +struct skl { + struct hdac_bus hbus; + struct pci_dev *pci; + + unsigned int init_done:1; /* delayed init status */ + struct platform_device *dmic_dev; + struct platform_device *i2s_dev; + struct platform_device *clk_dev; + struct snd_soc_component *component; + struct snd_soc_dai_driver *dais; + + struct nhlt_acpi_table *nhlt; /* nhlt ptr */ + struct skl_sst *skl_sst; /* sst skl ctx */ + + struct skl_dsp_resource resource; + struct list_head ppl_list; + struct list_head bind_list; + + const char *fw_name; + char tplg_name[64]; + unsigned short pci_id; + const struct firmware *tplg; + + int supend_active; + + struct work_struct probe_work; + + struct skl_debug *debugfs; + u8 nr_modules; + struct skl_module **modules; + bool use_tplg_pcm; + struct skl_fw_config cfg; + struct snd_soc_acpi_mach *mach; +}; + +#define skl_to_bus(s) (&(s)->hbus) +#define bus_to_skl(bus) container_of(bus, struct skl, hbus) + +/* to pass dai dma data */ +struct skl_dma_params { + u32 format; + u8 stream_tag; +}; + +struct skl_machine_pdata { + u32 dmic_num; + bool use_tplg_pcm; /* use dais and dai links from topology */ +}; + +struct skl_dsp_ops { + int id; + unsigned int num_cores; + struct skl_dsp_loader_ops (*loader_ops)(void); + int (*init)(struct device *dev, void __iomem *mmio_base, + int irq, const char *fw_name, + struct skl_dsp_loader_ops loader_ops, + struct skl_sst **skl_sst); + int (*init_fw)(struct device *dev, struct skl_sst *ctx); + void (*cleanup)(struct device *dev, struct skl_sst *ctx); +}; + +int skl_platform_unregister(struct device *dev); +int skl_platform_register(struct device *dev); + +struct nhlt_acpi_table *skl_nhlt_init(struct device *dev); +void skl_nhlt_free(struct nhlt_acpi_table *addr); +struct nhlt_specific_cfg *skl_get_ep_blob(struct skl *skl, u32 instance, + u8 link_type, u8 s_fmt, u8 no_ch, + u32 s_rate, u8 dirn, u8 dev_type); + +int skl_get_dmic_geo(struct skl *skl); +int skl_nhlt_update_topology_bin(struct skl *skl); +int skl_init_dsp(struct skl *skl); +int skl_free_dsp(struct skl *skl); +int skl_suspend_late_dsp(struct skl *skl); +int skl_suspend_dsp(struct skl *skl); +int skl_resume_dsp(struct skl *skl); +void skl_cleanup_resources(struct skl *skl); +const struct skl_dsp_ops *skl_get_dsp_ops(int pci_id); +void skl_update_d0i3c(struct device *dev, bool enable); +int skl_nhlt_create_sysfs(struct skl *skl); +void skl_nhlt_remove_sysfs(struct skl *skl); +void skl_get_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks); +struct skl_clk_parent_src *skl_get_parent_clk(u8 clk_id); +int skl_dsp_set_dma_control(struct skl_sst *ctx, u32 *caps, + u32 caps_size, u32 node_id); + +struct skl_module_cfg; + +#ifdef CONFIG_DEBUG_FS +struct skl_debug *skl_debugfs_init(struct skl *skl); +void skl_debug_init_module(struct skl_debug *d, + struct snd_soc_dapm_widget *w, + struct skl_module_cfg *mconfig); +#else +static inline struct skl_debug *skl_debugfs_init(struct skl *skl) +{ + return NULL; +} +static inline void skl_debug_init_module(struct skl_debug *d, + struct snd_soc_dapm_widget *w, + struct skl_module_cfg *mconfig) +{} +#endif + +#endif /* __SOUND_SOC_SKL_H */ diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig new file mode 100644 index 000000000..1a354a6b6 --- /dev/null +++ b/sound/soc/jz4740/Kconfig @@ -0,0 +1,29 @@ +config SND_JZ4740_SOC + tristate "SoC Audio for Ingenic JZ4740 SoC" + depends on MACH_JZ4740 || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to + the JZ4740 I2S interface. You will also need to select the audio + interfaces to support below. + +if SND_JZ4740_SOC + +config SND_JZ4740_SOC_I2S + tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC" + depends on HAS_IOMEM + help + Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740 + based boards. + +config SND_JZ4740_SOC_QI_LB60 + tristate "SoC Audio support for Qi LB60" + depends on HAS_IOMEM + depends on JZ4740_QI_LB60 || COMPILE_TEST + select SND_JZ4740_SOC_I2S + select SND_SOC_JZ4740_CODEC + help + Say Y if you want to add support for ASoC audio on the Qi LB60 board + a.k.a Qi Ben NanoNote. + +endif diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile new file mode 100644 index 000000000..d32c54055 --- /dev/null +++ b/sound/soc/jz4740/Makefile @@ -0,0 +1,11 @@ +# +# Jz4740 Platform Support +# +snd-soc-jz4740-i2s-objs := jz4740-i2s.o + +obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o + +# Jz4740 Machine Support +snd-soc-qi-lb60-objs := qi_lb60.o + +obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c new file mode 100644 index 000000000..2c6b0ac97 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "jz4740-i2s.h" + +#define JZ4740_DMA_TYPE_AIC_TRANSMIT 24 +#define JZ4740_DMA_TYPE_AIC_RECEIVE 25 + +#define JZ_REG_AIC_CONF 0x00 +#define JZ_REG_AIC_CTRL 0x04 +#define JZ_REG_AIC_I2S_FMT 0x10 +#define JZ_REG_AIC_FIFO_STATUS 0x14 +#define JZ_REG_AIC_I2S_STATUS 0x1c +#define JZ_REG_AIC_CLK_DIV 0x30 +#define JZ_REG_AIC_FIFO 0x34 + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12) +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf << 8) +#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6) +#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5) +#define JZ_AIC_CONF_I2S BIT(4) +#define JZ_AIC_CONF_RESET BIT(3) +#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2) +#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1) +#define JZ_AIC_CONF_ENABLE BIT(0) + +#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12 +#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8 +#define JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 24 +#define JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 16 +#define JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_MASK \ + (0xf << JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) +#define JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_MASK \ + (0x1f << JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19) +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16) +#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15) +#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14) +#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11) +#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10) +#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9) +#define JZ_AIC_CTRL_FLUSH BIT(8) +#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6) +#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5) +#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4) +#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3) +#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2) +#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1) +#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0) + +#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19 +#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16 + +#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12) +#define JZ_AIC_I2S_FMT_DISABLE_BIT_ICLK BIT(13) +#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4) +#define JZ_AIC_I2S_FMT_MSB BIT(0) + +#define JZ_AIC_I2S_STATUS_BUSY BIT(2) + +#define JZ_AIC_CLK_DIV_MASK 0xf +#define I2SDIV_DV_SHIFT 0 +#define I2SDIV_DV_MASK (0xf << I2SDIV_DV_SHIFT) +#define I2SDIV_IDV_SHIFT 8 +#define I2SDIV_IDV_MASK (0xf << I2SDIV_IDV_SHIFT) + +enum jz47xx_i2s_version { + JZ_I2S_JZ4740, + JZ_I2S_JZ4780, +}; + +struct jz4740_i2s { + struct resource *mem; + void __iomem *base; + dma_addr_t phys_base; + + struct clk *clk_aic; + struct clk *clk_i2s; + + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; + + enum jz47xx_i2s_version version; +}; + +static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s, + unsigned int reg) +{ + return readl(i2s->base + reg); +} + +static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s, + unsigned int reg, uint32_t value) +{ + writel(value, i2s->base + reg); +} + +static int jz4740_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + uint32_t conf, ctrl; + int ret; + + if (dai->active) + return 0; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + ctrl |= JZ_AIC_CTRL_FLUSH; + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + ret = clk_prepare_enable(i2s->clk_i2s); + if (ret) + return ret; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + uint32_t conf; + + if (dai->active) + return; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable_unprepare(i2s->clk_i2s); +} + +static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + uint32_t ctrl; + uint32_t mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA; + else + mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ctrl |= mask; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl &= ~mask; + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + + return 0; +} + +static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + uint32_t format = 0; + uint32_t conf; + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + + conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER; + format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK; + break; + case SND_SOC_DAIFMT_CBM_CFS: + conf |= JZ_AIC_CONF_SYNC_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: + conf |= JZ_AIC_CONF_BIT_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_MSB: + format |= JZ_AIC_I2S_FMT_MSB; + break; + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format); + + return 0; +} + +static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int sample_size; + uint32_t ctrl, div_reg; + int div; + + ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL); + + div_reg = jz4740_i2s_read(i2s, JZ_REG_AIC_CLK_DIV); + div = clk_get_rate(i2s->clk_i2s) / (64 * params_rate(params)); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + sample_size = 0; + break; + case SNDRV_PCM_FORMAT_S16: + sample_size = 1; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET; + if (params_channels(params) == 1) + ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO; + else + ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO; + + div_reg &= ~I2SDIV_DV_MASK; + div_reg |= (div - 1) << I2SDIV_DV_SHIFT; + } else { + ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK; + ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET; + + if (i2s->version >= JZ_I2S_JZ4780) { + div_reg &= ~I2SDIV_IDV_MASK; + div_reg |= (div - 1) << I2SDIV_IDV_SHIFT; + } else { + div_reg &= ~I2SDIV_DV_MASK; + div_reg |= (div - 1) << I2SDIV_DV_SHIFT; + } + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl); + jz4740_i2s_write(i2s, JZ_REG_AIC_CLK_DIV, div_reg); + + return 0; +} + +static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + struct clk *parent; + int ret = 0; + + switch (clk_id) { + case JZ4740_I2S_CLKSRC_EXT: + parent = clk_get(NULL, "ext"); + if (IS_ERR(parent)) + return PTR_ERR(parent); + clk_set_parent(i2s->clk_i2s, parent); + break; + case JZ4740_I2S_CLKSRC_PLL: + parent = clk_get(NULL, "pll half"); + if (IS_ERR(parent)) + return PTR_ERR(parent); + clk_set_parent(i2s->clk_i2s, parent); + ret = clk_set_rate(i2s->clk_i2s, freq); + break; + default: + return -EINVAL; + } + clk_put(parent); + + return ret; +} + +static int jz4740_i2s_suspend(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + uint32_t conf; + + if (dai->active) { + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf &= ~JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + clk_disable_unprepare(i2s->clk_i2s); + } + + clk_disable_unprepare(i2s->clk_aic); + + return 0; +} + +static int jz4740_i2s_resume(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + uint32_t conf; + int ret; + + ret = clk_prepare_enable(i2s->clk_aic); + if (ret) + return ret; + + if (dai->active) { + ret = clk_prepare_enable(i2s->clk_i2s); + if (ret) { + clk_disable_unprepare(i2s->clk_aic); + return ret; + } + + conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); + conf |= JZ_AIC_CONF_ENABLE; + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + } + + return 0; +} + +static void jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + + /* Playback */ + dma_data = &i2s->playback_dma_data; + dma_data->maxburst = 16; + dma_data->slave_id = JZ4740_DMA_TYPE_AIC_TRANSMIT; + dma_data->addr = i2s->phys_base + JZ_REG_AIC_FIFO; + + /* Capture */ + dma_data = &i2s->capture_dma_data; + dma_data->maxburst = 16; + dma_data->slave_id = JZ4740_DMA_TYPE_AIC_RECEIVE; + dma_data->addr = i2s->phys_base + JZ_REG_AIC_FIFO; +} + +static int jz4740_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + uint32_t conf; + int ret; + + ret = clk_prepare_enable(i2s->clk_aic); + if (ret) + return ret; + + jz4740_i2c_init_pcm_config(i2s); + snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, + &i2s->capture_dma_data); + + if (i2s->version >= JZ_I2S_JZ4780) { + conf = (7 << JZ4780_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | + (8 << JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | + JZ_AIC_CONF_OVERFLOW_PLAY_LAST | + JZ_AIC_CONF_I2S | + JZ_AIC_CONF_INTERNAL_CODEC; + } else { + conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) | + (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) | + JZ_AIC_CONF_OVERFLOW_PLAY_LAST | + JZ_AIC_CONF_I2S | + JZ_AIC_CONF_INTERNAL_CODEC; + } + + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET); + jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf); + + return 0; +} + +static int jz4740_i2s_dai_remove(struct snd_soc_dai *dai) +{ + struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(i2s->clk_aic); + return 0; +} + +static const struct snd_soc_dai_ops jz4740_i2s_dai_ops = { + .startup = jz4740_i2s_startup, + .shutdown = jz4740_i2s_shutdown, + .trigger = jz4740_i2s_trigger, + .hw_params = jz4740_i2s_hw_params, + .set_fmt = jz4740_i2s_set_fmt, + .set_sysclk = jz4740_i2s_set_sysclk, +}; + +#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_driver jz4740_i2s_dai = { + .probe = jz4740_i2s_dai_probe, + .remove = jz4740_i2s_dai_remove, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .symmetric_rates = 1, + .ops = &jz4740_i2s_dai_ops, + .suspend = jz4740_i2s_suspend, + .resume = jz4740_i2s_resume, +}; + +static struct snd_soc_dai_driver jz4780_i2s_dai = { + .probe = jz4740_i2s_dai_probe, + .remove = jz4740_i2s_dai_remove, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = JZ4740_I2S_FMTS, + }, + .ops = &jz4740_i2s_dai_ops, + .suspend = jz4740_i2s_suspend, + .resume = jz4740_i2s_resume, +}; + +static const struct snd_soc_component_driver jz4740_i2s_component = { + .name = "jz4740-i2s", +}; + +#ifdef CONFIG_OF +static const struct of_device_id jz4740_of_matches[] = { + { .compatible = "ingenic,jz4740-i2s", .data = (void *)JZ_I2S_JZ4740 }, + { .compatible = "ingenic,jz4780-i2s", .data = (void *)JZ_I2S_JZ4780 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, jz4740_of_matches); +#endif + +static int jz4740_i2s_dev_probe(struct platform_device *pdev) +{ + struct jz4740_i2s *i2s; + struct resource *mem; + int ret; + const struct of_device_id *match; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + match = of_match_device(jz4740_of_matches, &pdev->dev); + if (match) + i2s->version = (enum jz47xx_i2s_version)match->data; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(i2s->base)) + return PTR_ERR(i2s->base); + + i2s->phys_base = mem->start; + + i2s->clk_aic = devm_clk_get(&pdev->dev, "aic"); + if (IS_ERR(i2s->clk_aic)) + return PTR_ERR(i2s->clk_aic); + + i2s->clk_i2s = devm_clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s->clk_i2s)) + return PTR_ERR(i2s->clk_i2s); + + platform_set_drvdata(pdev, i2s); + + if (i2s->version == JZ_I2S_JZ4780) + ret = devm_snd_soc_register_component(&pdev->dev, + &jz4740_i2s_component, &jz4780_i2s_dai, 1); + else + ret = devm_snd_soc_register_component(&pdev->dev, + &jz4740_i2s_component, &jz4740_i2s_dai, 1); + + if (ret) + return ret; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + SND_DMAENGINE_PCM_FLAG_COMPAT); +} + +static struct platform_driver jz4740_i2s_driver = { + .probe = jz4740_i2s_dev_probe, + .driver = { + .name = "jz4740-i2s", + .of_match_table = of_match_ptr(jz4740_of_matches) + }, +}; + +module_platform_driver(jz4740_i2s_driver); + +MODULE_AUTHOR("Lars-Peter Clausen, "); +MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-i2s"); diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h new file mode 100644 index 000000000..5e49339d8 --- /dev/null +++ b/sound/soc/jz4740/jz4740-i2s.h @@ -0,0 +1,16 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _JZ4740_I2S_H +#define _JZ4740_I2S_H + +/* I2S clock source */ +#define JZ4740_I2S_CLKSRC_EXT 0 +#define JZ4740_I2S_CLKSRC_PLL 1 + +#define JZ4740_I2S_BIT_CLK 0 + +#endif diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c new file mode 100644 index 000000000..53586999f --- /dev/null +++ b/sound/soc/jz4740/qi_lb60.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2009, Lars-Peter Clausen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct qi_lb60 { + struct gpio_desc *snd_gpio; + struct gpio_desc *amp_gpio; +}; + +static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *ctrl, int event) +{ + struct qi_lb60 *qi_lb60 = snd_soc_card_get_drvdata(widget->dapm->card); + int on = !SND_SOC_DAPM_EVENT_OFF(event); + + gpiod_set_value_cansleep(qi_lb60->snd_gpio, on); + gpiod_set_value_cansleep(qi_lb60->amp_gpio, on); + + return 0; +} + +static const struct snd_soc_dapm_widget qi_lb60_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static const struct snd_soc_dapm_route qi_lb60_routes[] = { + {"Mic", NULL, "MIC"}, + {"Speaker", NULL, "LOUT"}, + {"Speaker", NULL, "ROUT"}, +}; + +static struct snd_soc_dai_link qi_lb60_dai = { + .name = "jz4740", + .stream_name = "jz4740", + .cpu_dai_name = "jz4740-i2s", + .platform_name = "jz4740-i2s", + .codec_dai_name = "jz4740-hifi", + .codec_name = "jz4740-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +static struct snd_soc_card qi_lb60_card = { + .name = "QI LB60", + .owner = THIS_MODULE, + .dai_link = &qi_lb60_dai, + .num_links = 1, + + .dapm_widgets = qi_lb60_widgets, + .num_dapm_widgets = ARRAY_SIZE(qi_lb60_widgets), + .dapm_routes = qi_lb60_routes, + .num_dapm_routes = ARRAY_SIZE(qi_lb60_routes), + .fully_routed = true, +}; + +static int qi_lb60_probe(struct platform_device *pdev) +{ + struct qi_lb60 *qi_lb60; + struct snd_soc_card *card = &qi_lb60_card; + + qi_lb60 = devm_kzalloc(&pdev->dev, sizeof(*qi_lb60), GFP_KERNEL); + if (!qi_lb60) + return -ENOMEM; + + qi_lb60->snd_gpio = devm_gpiod_get(&pdev->dev, "snd", GPIOD_OUT_LOW); + if (IS_ERR(qi_lb60->snd_gpio)) + return PTR_ERR(qi_lb60->snd_gpio); + + qi_lb60->amp_gpio = devm_gpiod_get(&pdev->dev, "amp", GPIOD_OUT_LOW); + if (IS_ERR(qi_lb60->amp_gpio)) + return PTR_ERR(qi_lb60->amp_gpio); + + card->dev = &pdev->dev; + + snd_soc_card_set_drvdata(card, qi_lb60); + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static struct platform_driver qi_lb60_driver = { + .driver = { + .name = "qi-lb60-audio", + }, + .probe = qi_lb60_probe, +}; + +module_platform_driver(qi_lb60_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qi-lb60-audio"); diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig new file mode 100644 index 000000000..132bb83f8 --- /dev/null +++ b/sound/soc/kirkwood/Kconfig @@ -0,0 +1,17 @@ +config SND_KIRKWOOD_SOC + tristate "SoC Audio for the Marvell Kirkwood and Dove chips" + depends on ARCH_DOVE || ARCH_MVEBU || COMPILE_TEST + help + Say Y or M if you want to add support for codecs attached to + the Kirkwood I2S interface. You will also need to select the + audio interfaces to support below. + +config SND_KIRKWOOD_SOC_ARMADA370_DB + tristate "SoC Audio support for Armada 370 DB" + depends on SND_KIRKWOOD_SOC && (ARCH_MVEBU || COMPILE_TEST) && I2C + select SND_SOC_CS42L51 + select SND_SOC_SPDIF + help + Say Y if you want to add support for SoC audio on + the Armada 370 Development Board. + diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile new file mode 100644 index 000000000..c36b03d80 --- /dev/null +++ b/sound/soc/kirkwood/Makefile @@ -0,0 +1,7 @@ +snd-soc-kirkwood-objs := kirkwood-dma.o kirkwood-i2s.o + +obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o + +snd-soc-armada-370-db-objs := armada-370-db.o + +obj-$(CONFIG_SND_KIRKWOOD_SOC_ARMADA370_DB) += snd-soc-armada-370-db.o diff --git a/sound/soc/kirkwood/armada-370-db.c b/sound/soc/kirkwood/armada-370-db.c new file mode 100644 index 000000000..677a48d7b --- /dev/null +++ b/sound/soc/kirkwood/armada-370-db.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014 Marvell + * + * Thomas Petazzoni + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/cs42l51.h" + +static int a370db_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int freq; + + switch (params_rate(params)) { + default: + case 44100: + freq = 11289600; + break; + case 48000: + freq = 12288000; + break; + case 96000: + freq = 24576000; + break; + } + + return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN); +} + +static const struct snd_soc_ops a370db_ops = { + .hw_params = a370db_hw_params, +}; + +static const struct snd_soc_dapm_widget a370db_dapm_widgets[] = { + SND_SOC_DAPM_HP("Out Jack", NULL), + SND_SOC_DAPM_LINE("In Jack", NULL), +}; + +static const struct snd_soc_dapm_route a370db_route[] = { + { "Out Jack", NULL, "HPL" }, + { "Out Jack", NULL, "HPR" }, + { "AIN1L", NULL, "In Jack" }, + { "AIN1L", NULL, "In Jack" }, +}; + +static struct snd_soc_dai_link a370db_dai[] = { +{ + .name = "CS42L51", + .stream_name = "analog", + .cpu_dai_name = "i2s", + .codec_dai_name = "cs42l51-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS, + .ops = &a370db_ops, +}, +{ + .name = "S/PDIF out", + .stream_name = "spdif-out", + .cpu_dai_name = "spdif", + .codec_dai_name = "dit-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS, +}, +{ + .name = "S/PDIF in", + .stream_name = "spdif-in", + .cpu_dai_name = "spdif", + .codec_dai_name = "dir-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS, +}, +}; + +static struct snd_soc_card a370db = { + .name = "a370db", + .owner = THIS_MODULE, + .dai_link = a370db_dai, + .num_links = ARRAY_SIZE(a370db_dai), + .dapm_widgets = a370db_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(a370db_dapm_widgets), + .dapm_routes = a370db_route, + .num_dapm_routes = ARRAY_SIZE(a370db_route), +}; + +static int a370db_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &a370db; + + card->dev = &pdev->dev; + + a370db_dai[0].cpu_of_node = + of_parse_phandle(pdev->dev.of_node, + "marvell,audio-controller", 0); + a370db_dai[0].platform_of_node = a370db_dai[0].cpu_of_node; + + a370db_dai[0].codec_of_node = + of_parse_phandle(pdev->dev.of_node, + "marvell,audio-codec", 0); + + a370db_dai[1].cpu_of_node = a370db_dai[0].cpu_of_node; + a370db_dai[1].platform_of_node = a370db_dai[0].cpu_of_node; + + a370db_dai[1].codec_of_node = + of_parse_phandle(pdev->dev.of_node, + "marvell,audio-codec", 1); + + a370db_dai[2].cpu_of_node = a370db_dai[0].cpu_of_node; + a370db_dai[2].platform_of_node = a370db_dai[0].cpu_of_node; + + a370db_dai[2].codec_of_node = + of_parse_phandle(pdev->dev.of_node, + "marvell,audio-codec", 2); + + return devm_snd_soc_register_card(card->dev, card); +} + +static const struct of_device_id a370db_dt_ids[] = { + { .compatible = "marvell,a370db-audio" }, + { }, +}; +MODULE_DEVICE_TABLE(of, a370db_dt_ids); + +static struct platform_driver a370db_driver = { + .driver = { + .name = "a370db-audio", + .of_match_table = of_match_ptr(a370db_dt_ids), + }, + .probe = a370db_probe, +}; + +module_platform_driver(a370db_driver); + +MODULE_AUTHOR("Thomas Petazzoni "); +MODULE_DESCRIPTION("ALSA SoC a370db audio client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:a370db-audio"); diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c new file mode 100644 index 000000000..255cc4590 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-dma.c @@ -0,0 +1,326 @@ +/* + * kirkwood-dma.c + * + * (c) 2010 Arnaud Patard + * (c) 2010 Arnaud Patard + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kirkwood.h" + +static struct kirkwood_dma_data *kirkwood_priv(struct snd_pcm_substream *subs) +{ + struct snd_soc_pcm_runtime *soc_runtime = subs->private_data; + return snd_soc_dai_get_drvdata(soc_runtime->cpu_dai); +} + +static const struct snd_pcm_hardware kirkwood_dma_snd_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .buffer_bytes_max = KIRKWOOD_SND_MAX_BUFFER_BYTES, + .period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES, + .period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES, + .periods_min = KIRKWOOD_SND_MIN_PERIODS, + .periods_max = KIRKWOOD_SND_MAX_PERIODS, + .fifo_size = 0, +}; + +static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id) +{ + struct kirkwood_dma_data *priv = dev_id; + unsigned long mask, status, cause; + + mask = readl(priv->io + KIRKWOOD_INT_MASK); + status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask; + + cause = readl(priv->io + KIRKWOOD_ERR_CAUSE); + if (unlikely(cause)) { + printk(KERN_WARNING "%s: got err interrupt 0x%lx\n", + __func__, cause); + writel(cause, priv->io + KIRKWOOD_ERR_CAUSE); + } + + /* we've enabled only bytes interrupts ... */ + if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \ + KIRKWOOD_INT_CAUSE_REC_BYTES)) { + printk(KERN_WARNING "%s: unexpected interrupt %lx\n", + __func__, status); + return IRQ_NONE; + } + + /* ack int */ + writel(status, priv->io + KIRKWOOD_INT_CAUSE); + + if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES) + snd_pcm_period_elapsed(priv->substream_play); + + if (status & KIRKWOOD_INT_CAUSE_REC_BYTES) + snd_pcm_period_elapsed(priv->substream_rec); + + return IRQ_HANDLED; +} + +static void +kirkwood_dma_conf_mbus_windows(void __iomem *base, int win, + unsigned long dma, + const struct mbus_dram_target_info *dram) +{ + int i; + + /* First disable and clear windows */ + writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); + writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); + + /* try to find matching cs for current dma address */ + for (i = 0; i < dram->num_cs; i++) { + const struct mbus_dram_window *cs = dram->cs + i; + if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) { + writel(cs->base & 0xffff0000, + base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); + writel(((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); + } + } +} + +static int kirkwood_dma_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct kirkwood_dma_data *priv = kirkwood_priv(substream); + const struct mbus_dram_target_info *dram; + unsigned long addr; + + snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw); + + /* Ensure that all constraints linked to dma burst are fulfilled */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + priv->burst * 2, + KIRKWOOD_AUDIO_BUF_MAX-1); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + priv->burst); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + priv->burst); + if (err < 0) + return err; + + if (!priv->substream_play && !priv->substream_rec) { + err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED, + "kirkwood-i2s", priv); + if (err) + return err; + + /* + * Enable Error interrupts. We're only ack'ing them but + * it's useful for diagnostics + */ + writel((unsigned int)-1, priv->io + KIRKWOOD_ERR_MASK); + } + + dram = mv_mbus_dram_info(); + addr = substream->dma_buffer.addr; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (priv->substream_play) + return -EBUSY; + priv->substream_play = substream; + kirkwood_dma_conf_mbus_windows(priv->io, + KIRKWOOD_PLAYBACK_WIN, addr, dram); + } else { + if (priv->substream_rec) + return -EBUSY; + priv->substream_rec = substream; + kirkwood_dma_conf_mbus_windows(priv->io, + KIRKWOOD_RECORD_WIN, addr, dram); + } + + return 0; +} + +static int kirkwood_dma_close(struct snd_pcm_substream *substream) +{ + struct kirkwood_dma_data *priv = kirkwood_priv(substream); + + if (!priv) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + priv->substream_play = NULL; + else + priv->substream_rec = NULL; + + if (!priv->substream_play && !priv->substream_rec) { + writel(0, priv->io + KIRKWOOD_ERR_MASK); + free_irq(priv->irq, priv); + } + + return 0; +} + +static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + return 0; +} + +static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int kirkwood_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct kirkwood_dma_data *priv = kirkwood_priv(substream); + unsigned long size, count; + + /* compute buffer size in term of "words" as requested in specs */ + size = frames_to_bytes(runtime, runtime->buffer_size); + size = (size>>2)-1; + count = snd_pcm_lib_period_bytes(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR); + writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE); + } else { + writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR); + writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE); + } + + + return 0; +} + +static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream + *substream) +{ + struct kirkwood_dma_data *priv = kirkwood_priv(substream); + snd_pcm_uframes_t count; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + count = bytes_to_frames(substream->runtime, + readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT)); + else + count = bytes_to_frames(substream->runtime, + readl(priv->io + KIRKWOOD_REC_BYTE_COUNT)); + + return count; +} + +static const struct snd_pcm_ops kirkwood_dma_ops = { + .open = kirkwood_dma_open, + .close = kirkwood_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = kirkwood_dma_hw_params, + .hw_free = kirkwood_dma_hw_free, + .prepare = kirkwood_dma_prepare, + .pointer = kirkwood_dma_pointer, +}; + +static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = kirkwood_dma_snd_hw.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + buf->private_data = NULL; + + return 0; +} + +static int kirkwood_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = kirkwood_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = kirkwood_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +const struct snd_soc_component_driver kirkwood_soc_component = { + .name = DRV_NAME, + .ops = &kirkwood_dma_ops, + .pcm_new = kirkwood_dma_new, + .pcm_free = kirkwood_dma_free_dma_buffers, +}; diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c new file mode 100644 index 000000000..4395bb702 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -0,0 +1,654 @@ +/* + * kirkwood-i2s.c + * + * (c) 2010 Arnaud Patard + * (c) 2010 Arnaud Patard + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kirkwood.h" + +#define KIRKWOOD_I2S_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define KIRKWOOD_SPDIF_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long mask; + unsigned long value; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + mask = KIRKWOOD_I2S_CTL_RJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + mask = KIRKWOOD_I2S_CTL_LJ; + break; + case SND_SOC_DAIFMT_I2S: + mask = KIRKWOOD_I2S_CTL_I2S; + break; + default: + return -EINVAL; + } + + /* + * Set same format for playback and record + * This avoids some troubles. + */ + value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL); + value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL); + + value = readl(priv->io+KIRKWOOD_I2S_RECCTL); + value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+KIRKWOOD_I2S_RECCTL); + + return 0; +} + +static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate) +{ + unsigned long value; + + value = KIRKWOOD_DCO_CTL_OFFSET_0; + switch (rate) { + default: + case 44100: + value |= KIRKWOOD_DCO_CTL_FREQ_11; + break; + case 48000: + value |= KIRKWOOD_DCO_CTL_FREQ_12; + break; + case 96000: + value |= KIRKWOOD_DCO_CTL_FREQ_24; + break; + } + writel(value, io + KIRKWOOD_DCO_CTL); + + /* wait for dco locked */ + do { + cpu_relax(); + value = readl(io + KIRKWOOD_DCO_SPCR_STATUS); + value &= KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK; + } while (value == 0); +} + +static void kirkwood_set_rate(struct snd_soc_dai *dai, + struct kirkwood_dma_data *priv, unsigned long rate) +{ + uint32_t clks_ctrl; + + if (IS_ERR(priv->extclk)) { + /* use internal dco for the supported rates + * defined in kirkwood_i2s_dai */ + dev_dbg(dai->dev, "%s: dco set rate = %lu\n", + __func__, rate); + kirkwood_set_dco(priv->io, rate); + + clks_ctrl = KIRKWOOD_MCLK_SOURCE_DCO; + } else { + /* use the external clock for the other rates + * defined in kirkwood_i2s_dai_extclk */ + dev_dbg(dai->dev, "%s: extclk set rate = %lu -> %lu\n", + __func__, rate, 256 * rate); + clk_set_rate(priv->extclk, 256 * rate); + + clks_ctrl = KIRKWOOD_MCLK_SOURCE_EXTCLK; + } + writel(clks_ctrl, priv->io + KIRKWOOD_CLOCKS_CTRL); +} + +static int kirkwood_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_set_dma_data(dai, substream, priv); + return 0; +} + +static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); + uint32_t ctl_play, ctl_rec; + unsigned int i2s_reg; + unsigned long i2s_value; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_reg = KIRKWOOD_I2S_PLAYCTL; + } else { + i2s_reg = KIRKWOOD_I2S_RECCTL; + } + + kirkwood_set_rate(dai, priv, params_rate(params)); + + i2s_value = readl(priv->io+i2s_reg); + i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK; + + /* + * Size settings in play/rec i2s control regs and play/rec control + * regs must be the same. + */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_16_C | + KIRKWOOD_PLAYCTL_I2S_EN | + KIRKWOOD_PLAYCTL_SPDIF_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_16_C | + KIRKWOOD_RECCTL_I2S_EN | + KIRKWOOD_RECCTL_SPDIF_EN; + break; + /* + * doesn't work... S20_3LE != kirkwood 20bit format ? + * + case SNDRV_PCM_FORMAT_S20_3LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_20 | + KIRKWOOD_PLAYCTL_I2S_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_20 | + KIRKWOOD_RECCTL_I2S_EN; + break; + */ + case SNDRV_PCM_FORMAT_S24_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_24 | + KIRKWOOD_PLAYCTL_I2S_EN | + KIRKWOOD_PLAYCTL_SPDIF_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_24 | + KIRKWOOD_RECCTL_I2S_EN | + KIRKWOOD_RECCTL_SPDIF_EN; + break; + case SNDRV_PCM_FORMAT_S32_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_32 | + KIRKWOOD_PLAYCTL_I2S_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_32 | + KIRKWOOD_RECCTL_I2S_EN; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (params_channels(params) == 1) + ctl_play |= KIRKWOOD_PLAYCTL_MONO_BOTH; + else + ctl_play |= KIRKWOOD_PLAYCTL_MONO_OFF; + + priv->ctl_play &= ~(KIRKWOOD_PLAYCTL_MONO_MASK | + KIRKWOOD_PLAYCTL_ENABLE_MASK | + KIRKWOOD_PLAYCTL_SIZE_MASK); + priv->ctl_play |= ctl_play; + } else { + priv->ctl_rec &= ~(KIRKWOOD_RECCTL_ENABLE_MASK | + KIRKWOOD_RECCTL_SIZE_MASK); + priv->ctl_rec |= ctl_rec; + } + + writel(i2s_value, priv->io+i2s_reg); + + return 0; +} + +static unsigned kirkwood_i2s_play_mute(unsigned ctl) +{ + if (!(ctl & KIRKWOOD_PLAYCTL_I2S_EN)) + ctl |= KIRKWOOD_PLAYCTL_I2S_MUTE; + if (!(ctl & KIRKWOOD_PLAYCTL_SPDIF_EN)) + ctl |= KIRKWOOD_PLAYCTL_SPDIF_MUTE; + return ctl; +} + +static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); + uint32_t ctl, value; + + ctl = readl(priv->io + KIRKWOOD_PLAYCTL); + if ((ctl & KIRKWOOD_PLAYCTL_ENABLE_MASK) == 0) { + unsigned timeout = 5000; + /* + * The Armada510 spec says that if we enter pause mode, the + * busy bit must be read back as clear _twice_. Make sure + * we respect that otherwise we get DMA underruns. + */ + do { + value = ctl; + ctl = readl(priv->io + KIRKWOOD_PLAYCTL); + if (!((ctl | value) & KIRKWOOD_PLAYCTL_PLAY_BUSY)) + break; + udelay(1); + } while (timeout--); + + if ((ctl | value) & KIRKWOOD_PLAYCTL_PLAY_BUSY) + dev_notice(dai->dev, "timed out waiting for busy to deassert: %08x\n", + ctl); + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* configure */ + ctl = priv->ctl_play; + if (dai->id == 0) + ctl &= ~KIRKWOOD_PLAYCTL_SPDIF_EN; /* i2s */ + else + ctl &= ~KIRKWOOD_PLAYCTL_I2S_EN; /* spdif */ + ctl = kirkwood_i2s_play_mute(ctl); + value = ctl & ~KIRKWOOD_PLAYCTL_ENABLE_MASK; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + /* enable interrupts */ + if (!runtime->no_period_wakeup) { + value = readl(priv->io + KIRKWOOD_INT_MASK); + value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + } + + /* enable playback */ + writel(ctl, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + ctl |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE | + KIRKWOOD_PLAYCTL_SPDIF_MUTE; + writel(ctl, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* disable all playbacks */ + ctl &= ~KIRKWOOD_PLAYCTL_ENABLE_MASK; + writel(ctl, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + ctl |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE | + KIRKWOOD_PLAYCTL_SPDIF_MUTE; + writel(ctl, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ctl &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE | + KIRKWOOD_PLAYCTL_SPDIF_MUTE); + ctl = kirkwood_i2s_play_mute(ctl); + writel(ctl, priv->io + KIRKWOOD_PLAYCTL); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); + uint32_t ctl, value; + + value = readl(priv->io + KIRKWOOD_RECCTL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* configure */ + ctl = priv->ctl_rec; + if (dai->id == 0) + ctl &= ~KIRKWOOD_RECCTL_SPDIF_EN; /* i2s */ + else + ctl &= ~KIRKWOOD_RECCTL_I2S_EN; /* spdif */ + + value = ctl & ~KIRKWOOD_RECCTL_ENABLE_MASK; + writel(value, priv->io + KIRKWOOD_RECCTL); + + /* enable interrupts */ + value = readl(priv->io + KIRKWOOD_INT_MASK); + value |= KIRKWOOD_INT_CAUSE_REC_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* enable record */ + writel(ctl, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE; + writel(value, priv->io + KIRKWOOD_RECCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* disable all records */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~KIRKWOOD_RECCTL_ENABLE_MASK; + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE; + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE); + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return kirkwood_i2s_play_trigger(substream, cmd, dai); + else + return kirkwood_i2s_rec_trigger(substream, cmd, dai); + + return 0; +} + +static int kirkwood_i2s_init(struct kirkwood_dma_data *priv) +{ + unsigned long value; + unsigned int reg_data; + + /* put system in a "safe" state : */ + /* disable audio interrupts */ + writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE); + writel(0, priv->io + KIRKWOOD_INT_MASK); + + reg_data = readl(priv->io + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + writel(reg_data, priv->io + 0x1200); + + msleep(500); + + reg_data = readl(priv->io + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + writel(reg_data, priv->io + 0x1200); + + /* disable playback/record */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~KIRKWOOD_PLAYCTL_ENABLE_MASK; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~KIRKWOOD_RECCTL_ENABLE_MASK; + writel(value, priv->io + KIRKWOOD_RECCTL); + + return 0; + +} + +static const struct snd_soc_dai_ops kirkwood_i2s_dai_ops = { + .startup = kirkwood_i2s_startup, + .trigger = kirkwood_i2s_trigger, + .hw_params = kirkwood_i2s_hw_params, + .set_fmt = kirkwood_i2s_set_fmt, +}; + +static struct snd_soc_dai_driver kirkwood_i2s_dai[2] = { + { + .name = "i2s", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .formats = KIRKWOOD_I2S_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .formats = KIRKWOOD_I2S_FORMATS, + }, + .ops = &kirkwood_i2s_dai_ops, + }, + { + .name = "spdif", + .id = 1, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .formats = KIRKWOOD_SPDIF_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .formats = KIRKWOOD_SPDIF_FORMATS, + }, + .ops = &kirkwood_i2s_dai_ops, + }, +}; + +static struct snd_soc_dai_driver kirkwood_i2s_dai_extclk[2] = { + { + .name = "i2s", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = KIRKWOOD_I2S_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = KIRKWOOD_I2S_FORMATS, + }, + .ops = &kirkwood_i2s_dai_ops, + }, + { + .name = "spdif", + .id = 1, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = KIRKWOOD_SPDIF_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = KIRKWOOD_SPDIF_FORMATS, + }, + .ops = &kirkwood_i2s_dai_ops, + }, +}; + +static int kirkwood_i2s_dev_probe(struct platform_device *pdev) +{ + struct kirkwood_asoc_platform_data *data = pdev->dev.platform_data; + struct snd_soc_dai_driver *soc_dai = kirkwood_i2s_dai; + struct kirkwood_dma_data *priv; + struct resource *mem; + struct device_node *np = pdev->dev.of_node; + int err; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, priv); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(priv->io)) + return PTR_ERR(priv->io); + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) { + dev_err(&pdev->dev, "platform_get_irq failed: %d\n", priv->irq); + return priv->irq; + } + + if (np) { + priv->burst = 128; /* might be 32 or 128 */ + } else if (data) { + priv->burst = data->burst; + } else { + dev_err(&pdev->dev, "no DT nor platform data ?!\n"); + return -EINVAL; + } + + priv->clk = devm_clk_get(&pdev->dev, np ? "internal" : NULL); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "no clock\n"); + return PTR_ERR(priv->clk); + } + + priv->extclk = devm_clk_get(&pdev->dev, "extclk"); + if (IS_ERR(priv->extclk)) { + if (PTR_ERR(priv->extclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } else { + if (clk_is_match(priv->extclk, priv->clk)) { + devm_clk_put(&pdev->dev, priv->extclk); + priv->extclk = ERR_PTR(-EINVAL); + } else { + dev_info(&pdev->dev, "found external clock\n"); + clk_prepare_enable(priv->extclk); + soc_dai = kirkwood_i2s_dai_extclk; + } + } + + err = clk_prepare_enable(priv->clk); + if (err < 0) + return err; + + /* Some sensible defaults - this reflects the powerup values */ + priv->ctl_play = KIRKWOOD_PLAYCTL_SIZE_24; + priv->ctl_rec = KIRKWOOD_RECCTL_SIZE_24; + + /* Select the burst size */ + if (priv->burst == 32) { + priv->ctl_play |= KIRKWOOD_PLAYCTL_BURST_32; + priv->ctl_rec |= KIRKWOOD_RECCTL_BURST_32; + } else { + priv->ctl_play |= KIRKWOOD_PLAYCTL_BURST_128; + priv->ctl_rec |= KIRKWOOD_RECCTL_BURST_128; + } + + err = snd_soc_register_component(&pdev->dev, &kirkwood_soc_component, + soc_dai, 2); + if (err) { + dev_err(&pdev->dev, "snd_soc_register_component failed\n"); + goto err_component; + } + + kirkwood_i2s_init(priv); + + return 0; + + err_component: + if (!IS_ERR(priv->extclk)) + clk_disable_unprepare(priv->extclk); + clk_disable_unprepare(priv->clk); + + return err; +} + +static int kirkwood_i2s_dev_remove(struct platform_device *pdev) +{ + struct kirkwood_dma_data *priv = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + if (!IS_ERR(priv->extclk)) + clk_disable_unprepare(priv->extclk); + clk_disable_unprepare(priv->clk); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id mvebu_audio_of_match[] = { + { .compatible = "marvell,kirkwood-audio" }, + { .compatible = "marvell,dove-audio" }, + { .compatible = "marvell,armada370-audio" }, + { } +}; +MODULE_DEVICE_TABLE(of, mvebu_audio_of_match); +#endif + +static struct platform_driver kirkwood_i2s_driver = { + .probe = kirkwood_i2s_dev_probe, + .remove = kirkwood_i2s_dev_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(mvebu_audio_of_match), + }, +}; + +module_platform_driver(kirkwood_i2s_driver); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard, "); +MODULE_DESCRIPTION("Kirkwood I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mvebu-audio"); diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h new file mode 100644 index 000000000..c13ee5f69 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood.h @@ -0,0 +1,150 @@ +/* + * kirkwood.h + * + * (c) 2010 Arnaud Patard + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _KIRKWOOD_AUDIO_H +#define _KIRKWOOD_AUDIO_H + +#define DRV_NAME "mvebu-audio" + +#define KIRKWOOD_RECORD_WIN 0 +#define KIRKWOOD_PLAYBACK_WIN 1 +#define KIRKWOOD_MAX_AUDIO_WIN 2 + +#define KIRKWOOD_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3)) +#define KIRKWOOD_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3)) + + +#define KIRKWOOD_RECCTL 0x1000 +#define KIRKWOOD_RECCTL_SPDIF_EN (1<<11) +#define KIRKWOOD_RECCTL_I2S_EN (1<<10) +#define KIRKWOOD_RECCTL_PAUSE (1<<9) +#define KIRKWOOD_RECCTL_MUTE (1<<8) +#define KIRKWOOD_RECCTL_BURST_MASK (3<<5) +#define KIRKWOOD_RECCTL_BURST_128 (2<<5) +#define KIRKWOOD_RECCTL_BURST_32 (1<<5) +#define KIRKWOOD_RECCTL_MONO (1<<4) +#define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT (1<<3) +#define KIRKWOOD_RECCTL_MONO_CHAN_LEFT (0<<3) +#define KIRKWOOD_RECCTL_SIZE_MASK (7<<0) +#define KIRKWOOD_RECCTL_SIZE_16 (7<<0) +#define KIRKWOOD_RECCTL_SIZE_16_C (3<<0) +#define KIRKWOOD_RECCTL_SIZE_20 (2<<0) +#define KIRKWOOD_RECCTL_SIZE_24 (1<<0) +#define KIRKWOOD_RECCTL_SIZE_32 (0<<0) + +#define KIRKWOOD_RECCTL_ENABLE_MASK (KIRKWOOD_RECCTL_SPDIF_EN | \ + KIRKWOOD_RECCTL_I2S_EN) + +#define KIRKWOOD_REC_BUF_ADDR 0x1004 +#define KIRKWOOD_REC_BUF_SIZE 0x1008 +#define KIRKWOOD_REC_BYTE_COUNT 0x100C + +#define KIRKWOOD_PLAYCTL 0x1100 +#define KIRKWOOD_PLAYCTL_PLAY_BUSY (1<<16) +#define KIRKWOOD_PLAYCTL_BURST_MASK (3<<11) +#define KIRKWOOD_PLAYCTL_BURST_128 (2<<11) +#define KIRKWOOD_PLAYCTL_BURST_32 (1<<11) +#define KIRKWOOD_PLAYCTL_PAUSE (1<<9) +#define KIRKWOOD_PLAYCTL_SPDIF_MUTE (1<<8) +#define KIRKWOOD_PLAYCTL_MONO_MASK (3<<5) +#define KIRKWOOD_PLAYCTL_MONO_BOTH (3<<5) +#define KIRKWOOD_PLAYCTL_MONO_OFF (0<<5) +#define KIRKWOOD_PLAYCTL_I2S_MUTE (1<<7) +#define KIRKWOOD_PLAYCTL_SPDIF_EN (1<<4) +#define KIRKWOOD_PLAYCTL_I2S_EN (1<<3) +#define KIRKWOOD_PLAYCTL_SIZE_MASK (7<<0) +#define KIRKWOOD_PLAYCTL_SIZE_16 (7<<0) +#define KIRKWOOD_PLAYCTL_SIZE_16_C (3<<0) +#define KIRKWOOD_PLAYCTL_SIZE_20 (2<<0) +#define KIRKWOOD_PLAYCTL_SIZE_24 (1<<0) +#define KIRKWOOD_PLAYCTL_SIZE_32 (0<<0) + +#define KIRKWOOD_PLAYCTL_ENABLE_MASK (KIRKWOOD_PLAYCTL_SPDIF_EN | \ + KIRKWOOD_PLAYCTL_I2S_EN) + +#define KIRKWOOD_PLAY_BUF_ADDR 0x1104 +#define KIRKWOOD_PLAY_BUF_SIZE 0x1108 +#define KIRKWOOD_PLAY_BYTE_COUNT 0x110C + +#define KIRKWOOD_DCO_CTL 0x1204 +#define KIRKWOOD_DCO_CTL_OFFSET_MASK (0xFFF<<2) +#define KIRKWOOD_DCO_CTL_OFFSET_0 (0x800<<2) +#define KIRKWOOD_DCO_CTL_FREQ_MASK (3<<0) +#define KIRKWOOD_DCO_CTL_FREQ_11 (0<<0) +#define KIRKWOOD_DCO_CTL_FREQ_12 (1<<0) +#define KIRKWOOD_DCO_CTL_FREQ_24 (2<<0) + +#define KIRKWOOD_DCO_SPCR_STATUS 0x120c +#define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK (1<<16) + +#define KIRKWOOD_CLOCKS_CTRL 0x1230 +#define KIRKWOOD_MCLK_SOURCE_MASK (3<<0) +#define KIRKWOOD_MCLK_SOURCE_DCO (0<<0) +#define KIRKWOOD_MCLK_SOURCE_EXTCLK (3<<0) + +#define KIRKWOOD_ERR_CAUSE 0x1300 +#define KIRKWOOD_ERR_MASK 0x1304 + +#define KIRKWOOD_INT_CAUSE 0x1308 +#define KIRKWOOD_INT_MASK 0x130C +#define KIRKWOOD_INT_CAUSE_PLAY_BYTES (1<<14) +#define KIRKWOOD_INT_CAUSE_REC_BYTES (1<<13) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_END (1<<7) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q (1<<6) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF (1<<5) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q (1<<4) +#define KIRKWOOD_INT_CAUSE_DMA_REC_END (1<<3) +#define KIRKWOOD_INT_CAUSE_DMA_REC_3Q (1<<2) +#define KIRKWOOD_INT_CAUSE_DMA_REC_HALF (1<<1) +#define KIRKWOOD_INT_CAUSE_DMA_REC_1Q (1<<0) + +#define KIRKWOOD_REC_BYTE_INT_COUNT 0x1310 +#define KIRKWOOD_PLAY_BYTE_INT_COUNT 0x1314 +#define KIRKWOOD_BYTE_INT_COUNT_MASK 0xffffff + +#define KIRKWOOD_I2S_PLAYCTL 0x2508 +#define KIRKWOOD_I2S_RECCTL 0x2408 +#define KIRKWOOD_I2S_CTL_JUST_MASK (0xf<<26) +#define KIRKWOOD_I2S_CTL_LJ (0<<26) +#define KIRKWOOD_I2S_CTL_I2S (5<<26) +#define KIRKWOOD_I2S_CTL_RJ (8<<26) +#define KIRKWOOD_I2S_CTL_SIZE_MASK (3<<30) +#define KIRKWOOD_I2S_CTL_SIZE_16 (3<<30) +#define KIRKWOOD_I2S_CTL_SIZE_20 (2<<30) +#define KIRKWOOD_I2S_CTL_SIZE_24 (1<<30) +#define KIRKWOOD_I2S_CTL_SIZE_32 (0<<30) + +#define KIRKWOOD_AUDIO_BUF_MAX (16*1024*1024) + +/* Theses values come from the marvell alsa driver */ +/* need to find where they come from */ +#define KIRKWOOD_SND_MIN_PERIODS 2 +#define KIRKWOOD_SND_MAX_PERIODS 16 +#define KIRKWOOD_SND_MIN_PERIOD_BYTES 256 +#define KIRKWOOD_SND_MAX_PERIOD_BYTES 0x8000 +#define KIRKWOOD_SND_MAX_BUFFER_BYTES (KIRKWOOD_SND_MAX_PERIOD_BYTES \ + * KIRKWOOD_SND_MAX_PERIODS) + +struct kirkwood_dma_data { + void __iomem *io; + struct clk *clk; + struct clk *extclk; + uint32_t ctl_play; + uint32_t ctl_rec; + struct snd_pcm_substream *substream_play; + struct snd_pcm_substream *substream_rec; + int irq; + int burst; +}; + +extern const struct snd_soc_component_driver kirkwood_soc_component; + +#endif diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig new file mode 100644 index 000000000..e731d40af --- /dev/null +++ b/sound/soc/mediatek/Kconfig @@ -0,0 +1,107 @@ +config SND_SOC_MEDIATEK + tristate + +config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" + depends on ARCH_MEDIATEK + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT2701_CS42448 + tristate "ASoc Audio driver for MT2701 with CS42448 codec" + depends on SND_SOC_MT2701 && I2C + select SND_SOC_CS42XX8_I2C + select SND_SOC_BT_SCO + help + This adds ASoC driver for Mediatek MT2701 boards + with the CS42448 codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT2701_WM8960 + tristate "ASoc Audio driver for MT2701 with WM8960 codec" + depends on SND_SOC_MT2701 && I2C + select SND_SOC_WM8960 + help + This adds ASoC driver for Mediatek MT2701 boards + with the WM8960 codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT6797 + tristate "ASoC support for Mediatek MT6797 chip" + depends on ARCH_MEDIATEK + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT6797 boards + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT6797_MT6351 + tristate "ASoc Audio driver for MT6797 with MT6351 codec" + depends on SND_SOC_MT6797 && MTK_PMIC_WRAP + select SND_SOC_MT6351 + help + This adds ASoC driver for Mediatek MT6797 boards + with the MT6351 codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8173 + tristate "ASoC support for Mediatek MT8173 chip" + depends on ARCH_MEDIATEK + select SND_SOC_MEDIATEK + help + This adds ASoC platform driver support for Mediatek MT8173 chip + that can be used with other codecs. + Select Y if you have such device. + Ex: MT8173 + +config SND_SOC_MT8173_MAX98090 + tristate "ASoC Audio driver for MT8173 with MAX98090 codec" + depends on SND_SOC_MT8173 && I2C + select SND_SOC_MAX98090 + help + This adds ASoC driver for Mediatek MT8173 boards + with the MAX98090 audio codec. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8173_RT5650 + tristate "ASoC Audio driver for MT8173 with RT5650 codec" + depends on SND_SOC_MT8173 && I2C + select SND_SOC_RT5645 + select SND_SOC_HDMI_CODEC + help + This adds ASoC driver for Mediatek MT8173 boards + with the RT5650 audio codec. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8173_RT5650_RT5514 + tristate "ASoC Audio driver for MT8173 with RT5650 RT5514 codecs" + depends on SND_SOC_MT8173 && I2C + select SND_SOC_RT5645 + select SND_SOC_RT5514 + help + This adds ASoC driver for Mediatek MT8173 boards + with the RT5650 and RT5514 codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8173_RT5650_RT5676 + tristate "ASoC Audio driver for MT8173 with RT5650 RT5676 codecs" + depends on SND_SOC_MT8173 && I2C + select SND_SOC_RT5645 + select SND_SOC_RT5677 + select SND_SOC_HDMI_CODEC + help + This adds ASoC driver for Mediatek MT8173 boards + with the RT5650 and RT5676 codecs. + Select Y if you have such device. + If unsure select "N". diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile new file mode 100644 index 000000000..3bb2c4753 --- /dev/null +++ b/sound/soc/mediatek/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_SND_SOC_MEDIATEK) += common/ +obj-$(CONFIG_SND_SOC_MT2701) += mt2701/ +obj-$(CONFIG_SND_SOC_MT6797) += mt6797/ +obj-$(CONFIG_SND_SOC_MT8173) += mt8173/ diff --git a/sound/soc/mediatek/common/Makefile b/sound/soc/mediatek/common/Makefile new file mode 100644 index 000000000..cdadabc5f --- /dev/null +++ b/sound/soc/mediatek/common/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# platform driver +snd-soc-mtk-common-objs := mtk-afe-platform-driver.o mtk-afe-fe-dai.o +obj-$(CONFIG_SND_SOC_MEDIATEK) += snd-soc-mtk-common.o diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c new file mode 100644 index 000000000..cf4978be0 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtk-afe-fe-dais.c -- Mediatek afe fe dai operator + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + */ + +#include +#include +#include +#include +#include "mtk-afe-platform-driver.h" +#include "mtk-afe-fe-dai.h" +#include "mtk-base-afe.h" + +#define AFE_BASE_END_OFFSET 8 + +static int mtk_regmap_update_bits(struct regmap *map, int reg, + unsigned int mask, + unsigned int val) +{ + if (reg < 0) + return 0; + return regmap_update_bits(map, reg, mask, val); +} + +static int mtk_regmap_write(struct regmap *map, int reg, unsigned int val) +{ + if (reg < 0) + return 0; + return regmap_write(map, reg, val); +} + +int mtk_afe_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *runtime = substream->runtime; + int memif_num = rtd->cpu_dai->id; + struct mtk_base_afe_memif *memif = &afe->memif[memif_num]; + const struct snd_pcm_hardware *mtk_afe_hardware = afe->mtk_afe_hardware; + int ret; + + memif->substream = substream; + + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); + /* enable agent */ + mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, + 1 << memif->data->agent_disable_shift, + 0 << memif->data->agent_disable_shift); + + snd_soc_set_runtime_hwparams(substream, mtk_afe_hardware); + + /* + * Capture cannot use ping-pong buffer since hw_ptr at IRQ may be + * smaller than period_size due to AFE's internal buffer. + * This easily leads to overrun when avail_min is period_size. + * One more period can hold the possible unread buffer. + */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + int periods_max = mtk_afe_hardware->periods_max; + + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIODS, + 3, periods_max); + if (ret < 0) { + dev_err(afe->dev, "hw_constraint_minmax failed\n"); + return ret; + } + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + + /* dynamic allocate irq to memif */ + if (memif->irq_usage < 0) { + int irq_id = mtk_dynamic_irq_acquire(afe); + + if (irq_id != afe->irqs_size) { + /* link */ + memif->irq_usage = irq_id; + } else { + dev_err(afe->dev, "%s() error: no more asys irq\n", + __func__); + ret = -EBUSY; + } + } + return ret; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_startup); + +void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int irq_id; + + irq_id = memif->irq_usage; + + mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, + 1 << memif->data->agent_disable_shift, + 1 << memif->data->agent_disable_shift); + + if (!memif->const_irq) { + mtk_dynamic_irq_release(afe, irq_id); + memif->irq_usage = -1; + memif->substream = NULL; + } +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_shutdown); + +int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int msb_at_bit33 = 0; + int ret, fs = 0; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + msb_at_bit33 = upper_32_bits(substream->runtime->dma_addr) ? 1 : 0; + memif->phys_buf_addr = lower_32_bits(substream->runtime->dma_addr); + memif->buffer_size = substream->runtime->dma_bytes; + + /* start */ + mtk_regmap_write(afe->regmap, memif->data->reg_ofs_base, + memif->phys_buf_addr); + /* end */ + mtk_regmap_write(afe->regmap, + memif->data->reg_ofs_base + AFE_BASE_END_OFFSET, + memif->phys_buf_addr + memif->buffer_size - 1); + + /* set MSB to 33-bit */ + mtk_regmap_update_bits(afe->regmap, memif->data->msb_reg, + 1 << memif->data->msb_shift, + msb_at_bit33 << memif->data->msb_shift); + + /* set channel */ + if (memif->data->mono_shift >= 0) { + unsigned int mono = (params_channels(params) == 1) ? 1 : 0; + + mtk_regmap_update_bits(afe->regmap, memif->data->mono_reg, + 1 << memif->data->mono_shift, + mono << memif->data->mono_shift); + } + + /* set rate */ + if (memif->data->fs_shift < 0) + return 0; + + fs = afe->memif_fs(substream, params_rate(params)); + + if (fs < 0) + return -EINVAL; + + mtk_regmap_update_bits(afe->regmap, memif->data->fs_reg, + memif->data->fs_maskbit << memif->data->fs_shift, + fs << memif->data->fs_shift); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_params); + +int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_lib_free_pages(substream); +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_free); + +int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime * const runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage]; + const struct mtk_base_irq_data *irq_data = irqs->irq_data; + unsigned int counter = runtime->period_size; + int fs; + + dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (memif->data->enable_shift >= 0) + mtk_regmap_update_bits(afe->regmap, + memif->data->enable_reg, + 1 << memif->data->enable_shift, + 1 << memif->data->enable_shift); + + /* set irq counter */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, + irq_data->irq_cnt_maskbit + << irq_data->irq_cnt_shift, + counter << irq_data->irq_cnt_shift); + + /* set irq fs */ + fs = afe->irq_fs(substream, runtime->rate); + + if (fs < 0) + return -EINVAL; + + mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, + irq_data->irq_fs_maskbit + << irq_data->irq_fs_shift, + fs << irq_data->irq_fs_shift); + + /* enable interrupt */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + 1 << irq_data->irq_en_shift, + 1 << irq_data->irq_en_shift); + + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + mtk_regmap_update_bits(afe->regmap, memif->data->enable_reg, + 1 << memif->data->enable_shift, 0); + /* disable interrupt */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + 1 << irq_data->irq_en_shift, + 0 << irq_data->irq_en_shift); + /* and clear pending IRQ */ + mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, + 1 << irq_data->irq_clr_shift); + return 0; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_trigger); + +int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int hd_audio = 0; + + /* set hd mode */ + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + hd_audio = 0; + break; + case SNDRV_PCM_FORMAT_S32_LE: + hd_audio = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hd_audio = 1; + break; + default: + dev_err(afe->dev, "%s() error: unsupported format %d\n", + __func__, substream->runtime->format); + break; + } + + mtk_regmap_update_bits(afe->regmap, memif->data->hd_reg, + 1 << memif->data->hd_shift, + hd_audio << memif->data->hd_shift); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_prepare); + +const struct snd_soc_dai_ops mtk_afe_fe_ops = { + .startup = mtk_afe_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mtk_afe_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, +}; +EXPORT_SYMBOL_GPL(mtk_afe_fe_ops); + +static DEFINE_MUTEX(irqs_lock); +int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe) +{ + int i; + + mutex_lock(&afe->irq_alloc_lock); + for (i = 0; i < afe->irqs_size; ++i) { + if (afe->irqs[i].irq_occupyed == 0) { + afe->irqs[i].irq_occupyed = 1; + mutex_unlock(&afe->irq_alloc_lock); + return i; + } + } + mutex_unlock(&afe->irq_alloc_lock); + return afe->irqs_size; +} +EXPORT_SYMBOL_GPL(mtk_dynamic_irq_acquire); + +int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id) +{ + mutex_lock(&afe->irq_alloc_lock); + if (irq_id >= 0 && irq_id < afe->irqs_size) { + afe->irqs[irq_id].irq_occupyed = 0; + mutex_unlock(&afe->irq_alloc_lock); + return 0; + } + mutex_unlock(&afe->irq_alloc_lock); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtk_dynamic_irq_release); + +int mtk_afe_dai_suspend(struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct device *dev = afe->dev; + struct regmap *regmap = afe->regmap; + int i; + + if (pm_runtime_status_suspended(dev) || afe->suspended) + return 0; + + if (!afe->reg_back_up) + afe->reg_back_up = + devm_kcalloc(dev, afe->reg_back_up_list_num, + sizeof(unsigned int), GFP_KERNEL); + + for (i = 0; i < afe->reg_back_up_list_num; i++) + regmap_read(regmap, afe->reg_back_up_list[i], + &afe->reg_back_up[i]); + + afe->suspended = true; + afe->runtime_suspend(dev); + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_dai_suspend); + +int mtk_afe_dai_resume(struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct device *dev = afe->dev; + struct regmap *regmap = afe->regmap; + int i = 0; + + if (pm_runtime_status_suspended(dev) || !afe->suspended) + return 0; + + afe->runtime_resume(dev); + + if (!afe->reg_back_up) + dev_dbg(dev, "%s no reg_backup\n", __func__); + + for (i = 0; i < afe->reg_back_up_list_num; i++) + mtk_regmap_write(regmap, afe->reg_back_up_list[i], + afe->reg_back_up[i]); + + afe->suspended = false; + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_dai_resume); + +MODULE_DESCRIPTION("Mediatek simple fe dai operator"); +MODULE_AUTHOR("Garlic Tseng "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.h b/sound/soc/mediatek/common/mtk-afe-fe-dai.h new file mode 100644 index 000000000..55074fb98 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtk-afe-fe-dais.h -- Mediatek afe fe dai operator definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + */ + +#ifndef _MTK_AFE_FE_DAI_H_ +#define _MTK_AFE_FE_DAI_H_ + +struct snd_soc_dai_ops; +struct mtk_base_afe; +struct mtk_base_afe_memif; + +int mtk_afe_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); +int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai); + +extern const struct snd_soc_dai_ops mtk_afe_fe_ops; + +int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe); +int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id); +int mtk_afe_dai_suspend(struct snd_soc_dai *dai); +int mtk_afe_dai_resume(struct snd_soc_dai *dai); + +#endif diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.c b/sound/soc/mediatek/common/mtk-afe-platform-driver.c new file mode 100644 index 000000000..697aa50af --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mtk-afe-platform-driver.c -- Mediatek afe platform driver + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + */ + +#include +#include +#include + +#include "mtk-afe-platform-driver.h" +#include "mtk-base-afe.h" + +int mtk_afe_combine_sub_dai(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + size_t num_dai_drivers = 0, dai_idx = 0; + + /* calcualte total dai driver size */ + list_for_each_entry(dai, &afe->sub_dais, list) { + num_dai_drivers += dai->num_dai_drivers; + } + + dev_info(afe->dev, "%s(), num of dai %zd\n", __func__, num_dai_drivers); + + /* combine sub_dais */ + afe->num_dai_drivers = num_dai_drivers; + afe->dai_drivers = devm_kcalloc(afe->dev, + num_dai_drivers, + sizeof(struct snd_soc_dai_driver), + GFP_KERNEL); + if (!afe->dai_drivers) + return -ENOMEM; + + list_for_each_entry(dai, &afe->sub_dais, list) { + /* dai driver */ + memcpy(&afe->dai_drivers[dai_idx], + dai->dai_drivers, + dai->num_dai_drivers * + sizeof(struct snd_soc_dai_driver)); + dai_idx += dai->num_dai_drivers; + } + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_combine_sub_dai); + +int mtk_afe_add_sub_dai_control(struct snd_soc_component *component) +{ + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + struct mtk_base_afe_dai *dai; + + list_for_each_entry(dai, &afe->sub_dais, list) { + if (dai->controls) + snd_soc_add_component_controls(component, + dai->controls, + dai->num_controls); + + if (dai->dapm_widgets) + snd_soc_dapm_new_controls(&component->dapm, + dai->dapm_widgets, + dai->num_dapm_widgets); + } + /* add routes after all widgets are added */ + list_for_each_entry(dai, &afe->sub_dais, list) { + if (dai->dapm_routes) + snd_soc_dapm_add_routes(&component->dapm, + dai->dapm_routes, + dai->num_dapm_routes); + } + + snd_soc_dapm_new_widgets(component->dapm.card); + + return 0; + +} +EXPORT_SYMBOL_GPL(mtk_afe_add_sub_dai_control); + +static snd_pcm_uframes_t mtk_afe_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + const struct mtk_base_memif_data *memif_data = memif->data; + struct regmap *regmap = afe->regmap; + struct device *dev = afe->dev; + int reg_ofs_base = memif_data->reg_ofs_base; + int reg_ofs_cur = memif_data->reg_ofs_cur; + unsigned int hw_ptr = 0, hw_base = 0; + int ret, pcm_ptr_bytes; + + ret = regmap_read(regmap, reg_ofs_cur, &hw_ptr); + if (ret || hw_ptr == 0) { + dev_err(dev, "%s hw_ptr err\n", __func__); + pcm_ptr_bytes = 0; + goto POINTER_RETURN_FRAMES; + } + + ret = regmap_read(regmap, reg_ofs_base, &hw_base); + if (ret || hw_base == 0) { + dev_err(dev, "%s hw_ptr err\n", __func__); + pcm_ptr_bytes = 0; + goto POINTER_RETURN_FRAMES; + } + + pcm_ptr_bytes = hw_ptr - hw_base; + +POINTER_RETURN_FRAMES: + return bytes_to_frames(substream->runtime, pcm_ptr_bytes); +} + +const struct snd_pcm_ops mtk_afe_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .pointer = mtk_afe_pcm_pointer, +}; +EXPORT_SYMBOL_GPL(mtk_afe_pcm_ops); + +int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + size_t size; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + + size = afe->mtk_afe_hardware->buffer_bytes_max; + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + afe->dev, + size, size); +} +EXPORT_SYMBOL_GPL(mtk_afe_pcm_new); + +void mtk_afe_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} +EXPORT_SYMBOL_GPL(mtk_afe_pcm_free); + +const struct snd_soc_component_driver mtk_afe_pcm_platform = { + .name = AFE_PCM_NAME, + .ops = &mtk_afe_pcm_ops, + .pcm_new = mtk_afe_pcm_new, + .pcm_free = mtk_afe_pcm_free, +}; +EXPORT_SYMBOL_GPL(mtk_afe_pcm_platform); + +MODULE_DESCRIPTION("Mediatek simple platform driver"); +MODULE_AUTHOR("Garlic Tseng "); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.h b/sound/soc/mediatek/common/mtk-afe-platform-driver.h new file mode 100644 index 000000000..88df67977 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtk-afe-platform-driver.h -- Mediatek afe platform driver definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + */ + +#ifndef _MTK_AFE_PLATFORM_DRIVER_H_ +#define _MTK_AFE_PLATFORM_DRIVER_H_ + +#define AFE_PCM_NAME "mtk-afe-pcm" +extern const struct snd_pcm_ops mtk_afe_pcm_ops; +extern const struct snd_soc_component_driver mtk_afe_pcm_platform; + +struct mtk_base_afe; +struct snd_pcm; +struct snd_soc_component; +struct snd_soc_pcm_runtime; + + +int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd); +void mtk_afe_pcm_free(struct snd_pcm *pcm); + +int mtk_afe_combine_sub_dai(struct mtk_base_afe *afe); +int mtk_afe_add_sub_dai_control(struct snd_soc_component *component); +#endif + diff --git a/sound/soc/mediatek/common/mtk-base-afe.h b/sound/soc/mediatek/common/mtk-base-afe.h new file mode 100644 index 000000000..bd8d5e0c6 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-base-afe.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mtk-base-afe.h -- Mediatek base afe structure + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + */ + +#ifndef _MTK_BASE_AFE_H_ +#define _MTK_BASE_AFE_H_ + +#define MTK_STREAM_NUM (SNDRV_PCM_STREAM_LAST + 1) + +struct mtk_base_memif_data { + int id; + const char *name; + int reg_ofs_base; + int reg_ofs_cur; + int fs_reg; + int fs_shift; + int fs_maskbit; + int mono_reg; + int mono_shift; + int enable_reg; + int enable_shift; + int hd_reg; + int hd_shift; + int msb_reg; + int msb_shift; + int agent_disable_reg; + int agent_disable_shift; +}; + +struct mtk_base_irq_data { + int id; + int irq_cnt_reg; + int irq_cnt_shift; + int irq_cnt_maskbit; + int irq_fs_reg; + int irq_fs_shift; + int irq_fs_maskbit; + int irq_en_reg; + int irq_en_shift; + int irq_clr_reg; + int irq_clr_shift; +}; + +struct device; +struct list_head; +struct mtk_base_afe_memif; +struct mtk_base_afe_irq; +struct mtk_base_afe_dai; +struct regmap; +struct snd_pcm_substream; +struct snd_soc_dai; + +struct mtk_base_afe { + void __iomem *base_addr; + struct device *dev; + struct regmap *regmap; + struct mutex irq_alloc_lock; /* dynamic alloc irq lock */ + + unsigned int const *reg_back_up_list; + unsigned int *reg_back_up; + unsigned int reg_back_up_list_num; + + int (*runtime_suspend)(struct device *dev); + int (*runtime_resume)(struct device *dev); + bool suspended; + + struct mtk_base_afe_memif *memif; + int memif_size; + struct mtk_base_afe_irq *irqs; + int irqs_size; + + struct list_head sub_dais; + struct snd_soc_dai_driver *dai_drivers; + unsigned int num_dai_drivers; + + const struct snd_pcm_hardware *mtk_afe_hardware; + int (*memif_fs)(struct snd_pcm_substream *substream, + unsigned int rate); + int (*irq_fs)(struct snd_pcm_substream *substream, + unsigned int rate); + + void *platform_priv; +}; + +struct mtk_base_afe_memif { + unsigned int phys_buf_addr; + int buffer_size; + struct snd_pcm_substream *substream; + const struct mtk_base_memif_data *data; + int irq_usage; + int const_irq; +}; + +struct mtk_base_afe_irq { + const struct mtk_base_irq_data *irq_data; + int irq_occupyed; +}; + +struct mtk_base_afe_dai { + struct snd_soc_dai_driver *dai_drivers; + unsigned int num_dai_drivers; + + const struct snd_kcontrol_new *controls; + unsigned int num_controls; + const struct snd_soc_dapm_widget *dapm_widgets; + unsigned int num_dapm_widgets; + const struct snd_soc_dapm_route *dapm_routes; + unsigned int num_dapm_routes; + + struct list_head list; +}; + +#endif + diff --git a/sound/soc/mediatek/mt2701/Makefile b/sound/soc/mediatek/mt2701/Makefile new file mode 100644 index 000000000..21d5e697c --- /dev/null +++ b/sound/soc/mediatek/mt2701/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# platform driver +snd-soc-mt2701-afe-objs := mt2701-afe-pcm.o mt2701-afe-clock-ctrl.o +obj-$(CONFIG_SND_SOC_MT2701) += snd-soc-mt2701-afe.o + +# machine driver +obj-$(CONFIG_SND_SOC_MT2701_CS42448) += mt2701-cs42448.o +obj-$(CONFIG_SND_SOC_MT2701_WM8960) += mt2701-wm8960.o diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c new file mode 100644 index 000000000..ae620890b --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt2701-afe-clock-ctrl.c -- Mediatek 2701 afe clock ctrl + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + * Ryder Lee + */ + +#include "mt2701-afe-common.h" +#include "mt2701-afe-clock-ctrl.h" + +static const char *const base_clks[] = { + [MT2701_INFRA_SYS_AUDIO] = "infra_sys_audio_clk", + [MT2701_TOP_AUD_MCLK_SRC0] = "top_audio_mux1_sel", + [MT2701_TOP_AUD_MCLK_SRC1] = "top_audio_mux2_sel", + [MT2701_TOP_AUD_A1SYS] = "top_audio_a1sys_hp", + [MT2701_TOP_AUD_A2SYS] = "top_audio_a2sys_hp", + [MT2701_AUDSYS_AFE] = "audio_afe_pd", + [MT2701_AUDSYS_AFE_CONN] = "audio_afe_conn_pd", + [MT2701_AUDSYS_A1SYS] = "audio_a1sys_pd", + [MT2701_AUDSYS_A2SYS] = "audio_a2sys_pd", +}; + +int mt2701_init_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i; + + for (i = 0; i < MT2701_BASE_CLK_NUM; i++) { + afe_priv->base_ck[i] = devm_clk_get(afe->dev, base_clks[i]); + if (IS_ERR(afe_priv->base_ck[i])) { + dev_err(afe->dev, "failed to get %s\n", base_clks[i]); + return PTR_ERR(afe_priv->base_ck[i]); + } + } + + /* Get I2S related clocks */ + for (i = 0; i < afe_priv->soc->i2s_num; i++) { + struct mt2701_i2s_path *i2s_path = &afe_priv->i2s_path[i]; + struct clk *i2s_ck; + char name[13]; + + snprintf(name, sizeof(name), "i2s%d_src_sel", i); + i2s_path->sel_ck = devm_clk_get(afe->dev, name); + if (IS_ERR(i2s_path->sel_ck)) { + dev_err(afe->dev, "failed to get %s\n", name); + return PTR_ERR(i2s_path->sel_ck); + } + + snprintf(name, sizeof(name), "i2s%d_src_div", i); + i2s_path->div_ck = devm_clk_get(afe->dev, name); + if (IS_ERR(i2s_path->div_ck)) { + dev_err(afe->dev, "failed to get %s\n", name); + return PTR_ERR(i2s_path->div_ck); + } + + snprintf(name, sizeof(name), "i2s%d_mclk_en", i); + i2s_path->mclk_ck = devm_clk_get(afe->dev, name); + if (IS_ERR(i2s_path->mclk_ck)) { + dev_err(afe->dev, "failed to get %s\n", name); + return PTR_ERR(i2s_path->mclk_ck); + } + + snprintf(name, sizeof(name), "i2so%d_hop_ck", i); + i2s_ck = devm_clk_get(afe->dev, name); + if (IS_ERR(i2s_ck)) { + dev_err(afe->dev, "failed to get %s\n", name); + return PTR_ERR(i2s_ck); + } + i2s_path->hop_ck[SNDRV_PCM_STREAM_PLAYBACK] = i2s_ck; + + snprintf(name, sizeof(name), "i2si%d_hop_ck", i); + i2s_ck = devm_clk_get(afe->dev, name); + if (IS_ERR(i2s_ck)) { + dev_err(afe->dev, "failed to get %s\n", name); + return PTR_ERR(i2s_ck); + } + i2s_path->hop_ck[SNDRV_PCM_STREAM_CAPTURE] = i2s_ck; + + snprintf(name, sizeof(name), "asrc%d_out_ck", i); + i2s_path->asrco_ck = devm_clk_get(afe->dev, name); + if (IS_ERR(i2s_path->asrco_ck)) { + dev_err(afe->dev, "failed to get %s\n", name); + return PTR_ERR(i2s_path->asrco_ck); + } + } + + /* Some platforms may support BT path */ + afe_priv->mrgif_ck = devm_clk_get(afe->dev, "audio_mrgif_pd"); + if (IS_ERR(afe_priv->mrgif_ck)) { + if (PTR_ERR(afe_priv->mrgif_ck) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + afe_priv->mrgif_ck = NULL; + } + + return 0; +} + +int mt2701_afe_enable_i2s(struct mtk_base_afe *afe, + struct mt2701_i2s_path *i2s_path, + int dir) +{ + int ret; + + ret = clk_prepare_enable(i2s_path->asrco_ck); + if (ret) { + dev_err(afe->dev, "failed to enable ASRC clock %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(i2s_path->hop_ck[dir]); + if (ret) { + dev_err(afe->dev, "failed to enable I2S clock %d\n", ret); + goto err_hop_ck; + } + + return 0; + +err_hop_ck: + clk_disable_unprepare(i2s_path->asrco_ck); + + return ret; +} + +void mt2701_afe_disable_i2s(struct mtk_base_afe *afe, + struct mt2701_i2s_path *i2s_path, + int dir) +{ + clk_disable_unprepare(i2s_path->hop_ck[dir]); + clk_disable_unprepare(i2s_path->asrco_ck); +} + +int mt2701_afe_enable_mclk(struct mtk_base_afe *afe, int id) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + struct mt2701_i2s_path *i2s_path = &afe_priv->i2s_path[id]; + + return clk_prepare_enable(i2s_path->mclk_ck); +} + +void mt2701_afe_disable_mclk(struct mtk_base_afe *afe, int id) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + struct mt2701_i2s_path *i2s_path = &afe_priv->i2s_path[id]; + + clk_disable_unprepare(i2s_path->mclk_ck); +} + +int mt2701_enable_btmrg_clk(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + return clk_prepare_enable(afe_priv->mrgif_ck); +} + +void mt2701_disable_btmrg_clk(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->mrgif_ck); +} + +static int mt2701_afe_enable_audsys(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret; + + /* Enable infra clock gate */ + ret = clk_prepare_enable(afe_priv->base_ck[MT2701_INFRA_SYS_AUDIO]); + if (ret) + return ret; + + /* Enable top a1sys clock gate */ + ret = clk_prepare_enable(afe_priv->base_ck[MT2701_TOP_AUD_A1SYS]); + if (ret) + goto err_a1sys; + + /* Enable top a2sys clock gate */ + ret = clk_prepare_enable(afe_priv->base_ck[MT2701_TOP_AUD_A2SYS]); + if (ret) + goto err_a2sys; + + /* Internal clock gates */ + ret = clk_prepare_enable(afe_priv->base_ck[MT2701_AUDSYS_AFE]); + if (ret) + goto err_afe; + + ret = clk_prepare_enable(afe_priv->base_ck[MT2701_AUDSYS_A1SYS]); + if (ret) + goto err_audio_a1sys; + + ret = clk_prepare_enable(afe_priv->base_ck[MT2701_AUDSYS_A2SYS]); + if (ret) + goto err_audio_a2sys; + + ret = clk_prepare_enable(afe_priv->base_ck[MT2701_AUDSYS_AFE_CONN]); + if (ret) + goto err_afe_conn; + + return 0; + +err_afe_conn: + clk_disable_unprepare(afe_priv->base_ck[MT2701_AUDSYS_A2SYS]); +err_audio_a2sys: + clk_disable_unprepare(afe_priv->base_ck[MT2701_AUDSYS_A1SYS]); +err_audio_a1sys: + clk_disable_unprepare(afe_priv->base_ck[MT2701_AUDSYS_AFE]); +err_afe: + clk_disable_unprepare(afe_priv->base_ck[MT2701_TOP_AUD_A2SYS]); +err_a2sys: + clk_disable_unprepare(afe_priv->base_ck[MT2701_TOP_AUD_A1SYS]); +err_a1sys: + clk_disable_unprepare(afe_priv->base_ck[MT2701_INFRA_SYS_AUDIO]); + + return ret; +} + +static void mt2701_afe_disable_audsys(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->base_ck[MT2701_AUDSYS_AFE_CONN]); + clk_disable_unprepare(afe_priv->base_ck[MT2701_AUDSYS_A2SYS]); + clk_disable_unprepare(afe_priv->base_ck[MT2701_AUDSYS_A1SYS]); + clk_disable_unprepare(afe_priv->base_ck[MT2701_AUDSYS_AFE]); + clk_disable_unprepare(afe_priv->base_ck[MT2701_TOP_AUD_A1SYS]); + clk_disable_unprepare(afe_priv->base_ck[MT2701_TOP_AUD_A2SYS]); + clk_disable_unprepare(afe_priv->base_ck[MT2701_INFRA_SYS_AUDIO]); +} + +int mt2701_afe_enable_clock(struct mtk_base_afe *afe) +{ + int ret; + + /* Enable audio system */ + ret = mt2701_afe_enable_audsys(afe); + if (ret) { + dev_err(afe->dev, "failed to enable audio system %d\n", ret); + return ret; + } + + regmap_update_bits(afe->regmap, ASYS_TOP_CON, + ASYS_TOP_CON_ASYS_TIMING_ON, + ASYS_TOP_CON_ASYS_TIMING_ON); + regmap_update_bits(afe->regmap, AFE_DAC_CON0, + AFE_DAC_CON0_AFE_ON, + AFE_DAC_CON0_AFE_ON); + + /* Configure ASRC */ + regmap_write(afe->regmap, PWR1_ASM_CON1, PWR1_ASM_CON1_INIT_VAL); + regmap_write(afe->regmap, PWR2_ASM_CON1, PWR2_ASM_CON1_INIT_VAL); + + return 0; +} + +int mt2701_afe_disable_clock(struct mtk_base_afe *afe) +{ + regmap_update_bits(afe->regmap, ASYS_TOP_CON, + ASYS_TOP_CON_ASYS_TIMING_ON, 0); + regmap_update_bits(afe->regmap, AFE_DAC_CON0, + AFE_DAC_CON0_AFE_ON, 0); + + mt2701_afe_disable_audsys(afe); + + return 0; +} + +int mt2701_mclk_configuration(struct mtk_base_afe *afe, int id) + +{ + struct mt2701_afe_private *priv = afe->platform_priv; + struct mt2701_i2s_path *i2s_path = &priv->i2s_path[id]; + int ret = -EINVAL; + + /* Set mclk source */ + if (!(MT2701_PLL_DOMAIN_0_RATE % i2s_path->mclk_rate)) + ret = clk_set_parent(i2s_path->sel_ck, + priv->base_ck[MT2701_TOP_AUD_MCLK_SRC0]); + else if (!(MT2701_PLL_DOMAIN_1_RATE % i2s_path->mclk_rate)) + ret = clk_set_parent(i2s_path->sel_ck, + priv->base_ck[MT2701_TOP_AUD_MCLK_SRC1]); + + if (ret) { + dev_err(afe->dev, "failed to set mclk source\n"); + return ret; + } + + /* Set mclk divider */ + ret = clk_set_rate(i2s_path->div_ck, i2s_path->mclk_rate); + if (ret) { + dev_err(afe->dev, "failed to set mclk divider %d\n", ret); + return ret; + } + + return 0; +} diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h new file mode 100644 index 000000000..580fead2a --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt2701-afe-clock-ctrl.h -- Mediatek 2701 afe clock ctrl definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + * Ryder Lee + */ + +#ifndef _MT2701_AFE_CLOCK_CTRL_H_ +#define _MT2701_AFE_CLOCK_CTRL_H_ + +struct mtk_base_afe; +struct mt2701_i2s_path; + +int mt2701_init_clock(struct mtk_base_afe *afe); +int mt2701_afe_enable_clock(struct mtk_base_afe *afe); +int mt2701_afe_disable_clock(struct mtk_base_afe *afe); + +int mt2701_afe_enable_i2s(struct mtk_base_afe *afe, + struct mt2701_i2s_path *path, + int dir); +void mt2701_afe_disable_i2s(struct mtk_base_afe *afe, + struct mt2701_i2s_path *path, + int dir); +int mt2701_afe_enable_mclk(struct mtk_base_afe *afe, int id); +void mt2701_afe_disable_mclk(struct mtk_base_afe *afe, int id); + +int mt2701_enable_btmrg_clk(struct mtk_base_afe *afe); +void mt2701_disable_btmrg_clk(struct mtk_base_afe *afe); + +int mt2701_mclk_configuration(struct mtk_base_afe *afe, int id); + +#endif diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-common.h b/sound/soc/mediatek/mt2701/mt2701-afe-common.h new file mode 100644 index 000000000..d44faba27 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-common.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt2701-afe-common.h -- Mediatek 2701 audio driver definitions + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + */ + +#ifndef _MT_2701_AFE_COMMON_H_ +#define _MT_2701_AFE_COMMON_H_ + +#include +#include +#include +#include "mt2701-reg.h" +#include "../common/mtk-base-afe.h" + +#define MT2701_PLL_DOMAIN_0_RATE 98304000 +#define MT2701_PLL_DOMAIN_1_RATE 90316800 + +enum { + MT2701_MEMIF_DL1, + MT2701_MEMIF_DL2, + MT2701_MEMIF_DL3, + MT2701_MEMIF_DL4, + MT2701_MEMIF_DL5, + MT2701_MEMIF_DL_SINGLE_NUM, + MT2701_MEMIF_DLM = MT2701_MEMIF_DL_SINGLE_NUM, + MT2701_MEMIF_UL1, + MT2701_MEMIF_UL2, + MT2701_MEMIF_UL3, + MT2701_MEMIF_UL4, + MT2701_MEMIF_UL5, + MT2701_MEMIF_DLBT, + MT2701_MEMIF_ULBT, + MT2701_MEMIF_NUM, + MT2701_IO_I2S = MT2701_MEMIF_NUM, + MT2701_IO_2ND_I2S, + MT2701_IO_3RD_I2S, + MT2701_IO_4TH_I2S, + MT2701_IO_5TH_I2S, + MT2701_IO_6TH_I2S, + MT2701_IO_MRG, +}; + +enum { + MT2701_IRQ_ASYS_IRQ1, + MT2701_IRQ_ASYS_IRQ2, + MT2701_IRQ_ASYS_IRQ3, + MT2701_IRQ_ASYS_END, +}; + +enum audio_base_clock { + MT2701_INFRA_SYS_AUDIO, + MT2701_TOP_AUD_MCLK_SRC0, + MT2701_TOP_AUD_MCLK_SRC1, + MT2701_TOP_AUD_A1SYS, + MT2701_TOP_AUD_A2SYS, + MT2701_AUDSYS_AFE, + MT2701_AUDSYS_AFE_CONN, + MT2701_AUDSYS_A1SYS, + MT2701_AUDSYS_A2SYS, + MT2701_BASE_CLK_NUM, +}; + +static const unsigned int mt2701_afe_backup_list[] = { + AUDIO_TOP_CON0, + AUDIO_TOP_CON4, + AUDIO_TOP_CON5, + ASYS_TOP_CON, + AFE_CONN0, + AFE_CONN1, + AFE_CONN2, + AFE_CONN3, + AFE_CONN15, + AFE_CONN16, + AFE_CONN17, + AFE_CONN18, + AFE_CONN19, + AFE_CONN20, + AFE_CONN21, + AFE_CONN22, + AFE_DAC_CON0, + AFE_MEMIF_PBUF_SIZE, +}; + +struct mt2701_i2s_data { + int i2s_ctrl_reg; + int i2s_asrc_fs_shift; + int i2s_asrc_fs_mask; +}; + +struct mt2701_i2s_path { + int mclk_rate; + int on[MTK_STREAM_NUM]; + int occupied[MTK_STREAM_NUM]; + const struct mt2701_i2s_data *i2s_data[MTK_STREAM_NUM]; + struct clk *hop_ck[MTK_STREAM_NUM]; + struct clk *sel_ck; + struct clk *div_ck; + struct clk *mclk_ck; + struct clk *asrco_ck; +}; + +struct mt2701_soc_variants { + bool has_one_heart_mode; + int i2s_num; +}; + +struct mt2701_afe_private { + struct mt2701_i2s_path *i2s_path; + struct clk *base_ck[MT2701_BASE_CLK_NUM]; + struct clk *mrgif_ck; + bool mrg_enable[MTK_STREAM_NUM]; + + const struct mt2701_soc_variants *soc; +}; + +#endif diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c new file mode 100644 index 000000000..968fba4d7 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -0,0 +1,1506 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Mediatek ALSA SoC AFE platform driver for 2701 + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + * Ir Lian + * Ryder Lee + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mt2701-afe-common.h" +#include "mt2701-afe-clock-ctrl.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +static const struct snd_pcm_hardware mt2701_afe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 1024, + .period_bytes_max = 1024 * 256, + .periods_min = 4, + .periods_max = 1024, + .buffer_bytes_max = 1024 * 1024, + .fifo_size = 0, +}; + +struct mt2701_afe_rate { + unsigned int rate; + unsigned int regvalue; +}; + +static const struct mt2701_afe_rate mt2701_afe_i2s_rates[] = { + { .rate = 8000, .regvalue = 0 }, + { .rate = 12000, .regvalue = 1 }, + { .rate = 16000, .regvalue = 2 }, + { .rate = 24000, .regvalue = 3 }, + { .rate = 32000, .regvalue = 4 }, + { .rate = 48000, .regvalue = 5 }, + { .rate = 96000, .regvalue = 6 }, + { .rate = 192000, .regvalue = 7 }, + { .rate = 384000, .regvalue = 8 }, + { .rate = 7350, .regvalue = 16 }, + { .rate = 11025, .regvalue = 17 }, + { .rate = 14700, .regvalue = 18 }, + { .rate = 22050, .regvalue = 19 }, + { .rate = 29400, .regvalue = 20 }, + { .rate = 44100, .regvalue = 21 }, + { .rate = 88200, .regvalue = 22 }, + { .rate = 176400, .regvalue = 23 }, + { .rate = 352800, .regvalue = 24 }, +}; + +static int mt2701_dai_num_to_i2s(struct mtk_base_afe *afe, int num) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int val = num - MT2701_IO_I2S; + + if (val < 0 || val >= afe_priv->soc->i2s_num) { + dev_err(afe->dev, "%s, num not available, num %d, val %d\n", + __func__, num, val); + return -EINVAL; + } + return val; +} + +static int mt2701_afe_i2s_fs(unsigned int sample_rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mt2701_afe_i2s_rates); i++) + if (mt2701_afe_i2s_rates[i].rate == sample_rate) + return mt2701_afe_i2s_rates[i].regvalue; + + return -EINVAL; +} + +static int mt2701_afe_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + bool mode = afe_priv->soc->has_one_heart_mode; + + if (i2s_num < 0) + return i2s_num; + + return mt2701_afe_enable_mclk(afe, mode ? 1 : i2s_num); +} + +static int mt2701_afe_i2s_path_disable(struct mtk_base_afe *afe, + struct mt2701_i2s_path *i2s_path, + int stream_dir) +{ + const struct mt2701_i2s_data *i2s_data = i2s_path->i2s_data[stream_dir]; + + if (--i2s_path->on[stream_dir] < 0) + i2s_path->on[stream_dir] = 0; + + if (i2s_path->on[stream_dir]) + return 0; + + /* disable i2s */ + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_I2S_EN, 0); + + mt2701_afe_disable_i2s(afe, i2s_path, stream_dir); + + return 0; +} + +static void mt2701_afe_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + bool mode = afe_priv->soc->has_one_heart_mode; + + if (i2s_num < 0) + return; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (i2s_path->occupied[substream->stream]) + i2s_path->occupied[substream->stream] = 0; + else + goto exit; + + mt2701_afe_i2s_path_disable(afe, i2s_path, substream->stream); + + /* need to disable i2s-out path when disable i2s-in */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mt2701_afe_i2s_path_disable(afe, i2s_path, !substream->stream); + +exit: + /* disable mclk */ + mt2701_afe_disable_mclk(afe, mode ? 1 : i2s_num); +} + +static int mt2701_i2s_path_enable(struct mtk_base_afe *afe, + struct mt2701_i2s_path *i2s_path, + int stream_dir, int rate) +{ + const struct mt2701_i2s_data *i2s_data = i2s_path->i2s_data[stream_dir]; + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int reg, fs, w_len = 1; /* now we support bck 64bits only */ + unsigned int mask, val; + + /* no need to enable if already done */ + if (++i2s_path->on[stream_dir] != 1) + return 0; + + fs = mt2701_afe_i2s_fs(rate); + + mask = ASYS_I2S_CON_FS | + ASYS_I2S_CON_I2S_COUPLE_MODE | /* 0 */ + ASYS_I2S_CON_I2S_MODE | + ASYS_I2S_CON_WIDE_MODE; + + val = ASYS_I2S_CON_FS_SET(fs) | + ASYS_I2S_CON_I2S_MODE | + ASYS_I2S_CON_WIDE_MODE_SET(w_len); + + if (stream_dir == SNDRV_PCM_STREAM_CAPTURE) { + mask |= ASYS_I2S_IN_PHASE_FIX; + val |= ASYS_I2S_IN_PHASE_FIX; + reg = ASMI_TIMING_CON1; + } else { + if (afe_priv->soc->has_one_heart_mode) { + mask |= ASYS_I2S_CON_ONE_HEART_MODE; + val |= ASYS_I2S_CON_ONE_HEART_MODE; + } + reg = ASMO_TIMING_CON1; + } + + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, mask, val); + + regmap_update_bits(afe->regmap, reg, + i2s_data->i2s_asrc_fs_mask + << i2s_data->i2s_asrc_fs_shift, + fs << i2s_data->i2s_asrc_fs_shift); + + /* enable i2s */ + mt2701_afe_enable_i2s(afe, i2s_path, stream_dir); + + /* reset i2s hw status before enable */ + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_RESET, ASYS_I2S_CON_RESET); + udelay(1); + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_RESET, 0); + udelay(1); + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_I2S_EN, ASYS_I2S_CON_I2S_EN); + return 0; +} + +static int mt2701_afe_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret, i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + bool mode = afe_priv->soc->has_one_heart_mode; + + if (i2s_num < 0) + return i2s_num; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (i2s_path->occupied[substream->stream]) + return -EBUSY; + + ret = mt2701_mclk_configuration(afe, mode ? 1 : i2s_num); + if (ret) + return ret; + + i2s_path->occupied[substream->stream] = 1; + + /* need to enable i2s-out path when enable i2s-in */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mt2701_i2s_path_enable(afe, i2s_path, !substream->stream, + substream->runtime->rate); + + mt2701_i2s_path_enable(afe, i2s_path, substream->stream, + substream->runtime->rate); + + return 0; +} + +static int mt2701_afe_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + bool mode = afe_priv->soc->has_one_heart_mode; + + if (i2s_num < 0) + return i2s_num; + + /* mclk */ + if (dir == SND_SOC_CLOCK_IN) { + dev_warn(dai->dev, "The SoCs doesn't support mclk input\n"); + return -EINVAL; + } + + afe_priv->i2s_path[mode ? 1 : i2s_num].mclk_rate = freq; + + return 0; +} + +static int mt2701_btmrg_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret; + + ret = mt2701_enable_btmrg_clk(afe); + if (ret) + return ret; + + afe_priv->mrg_enable[substream->stream] = 1; + + return 0; +} + +static int mt2701_btmrg_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + int stream_fs; + u32 val, msk; + + stream_fs = params_rate(params); + + if (stream_fs != 8000 && stream_fs != 16000) { + dev_err(afe->dev, "unsupported rate %d\n", stream_fs); + return -EINVAL; + } + + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_I2S_MODE_MASK, + AFE_MRGIF_CON_I2S_MODE_32K); + + val = AFE_DAIBT_CON0_BT_FUNC_EN | AFE_DAIBT_CON0_BT_FUNC_RDY + | AFE_DAIBT_CON0_MRG_USE; + msk = val; + + if (stream_fs == 16000) + val |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN; + + msk |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN; + + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, msk, val); + + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, + AFE_DAIBT_CON0_DAIBT_EN, + AFE_DAIBT_CON0_DAIBT_EN); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_I2S_EN, + AFE_MRGIF_CON_MRG_I2S_EN); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_EN, + AFE_MRGIF_CON_MRG_EN); + return 0; +} + +static void mt2701_btmrg_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + /* if the other direction stream is not occupied */ + if (!afe_priv->mrg_enable[!substream->stream]) { + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, + AFE_DAIBT_CON0_DAIBT_EN, 0); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_EN, 0); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_I2S_EN, 0); + mt2701_disable_btmrg_clk(afe); + } + + afe_priv->mrg_enable[substream->stream] = 0; +} + +static int mt2701_simple_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mtk_base_afe_memif *memif_tmp; + int stream_dir = substream->stream; + + /* can't run single DL & DLM at the same time */ + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) { + memif_tmp = &afe->memif[MT2701_MEMIF_DLM]; + if (memif_tmp->substream) { + dev_warn(afe->dev, "memif is not available"); + return -EBUSY; + } + } + + return mtk_afe_fe_startup(substream, dai); +} + +static int mt2701_simple_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + int stream_dir = substream->stream; + + /* single DL use PAIR_INTERLEAVE */ + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_MASK, + AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE); + + return mtk_afe_fe_hw_params(substream, params, dai); +} + +static int mt2701_dlm_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mtk_base_afe_memif *memif_tmp; + const struct mtk_base_memif_data *memif_data; + int i; + + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_tmp = &afe->memif[i]; + if (memif_tmp->substream) + return -EBUSY; + } + + /* enable agent for all signal DL (due to hw design) */ + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_data = afe->memif[i].data; + regmap_update_bits(afe->regmap, + memif_data->agent_disable_reg, + 1 << memif_data->agent_disable_shift, + 0 << memif_data->agent_disable_shift); + } + + return mtk_afe_fe_startup(substream, dai); +} + +static void mt2701_dlm_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + const struct mtk_base_memif_data *memif_data; + int i; + + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_data = afe->memif[i].data; + regmap_update_bits(afe->regmap, + memif_data->agent_disable_reg, + 1 << memif_data->agent_disable_shift, + 1 << memif_data->agent_disable_shift); + } + + return mtk_afe_fe_shutdown(substream, dai); +} + +static int mt2701_dlm_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + int channels = params_channels(params); + + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_MASK, + AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE); + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK, + AFE_MEMIF_PBUF_SIZE_DLM_32BYTES); + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK, + AFE_MEMIF_PBUF_SIZE_DLM_CH(channels)); + + return mtk_afe_fe_hw_params(substream, params, dai); +} + +static int mt2701_dlm_fe_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mtk_base_afe_memif *memif_tmp = &afe->memif[MT2701_MEMIF_DL1]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg, + 1 << memif_tmp->data->enable_shift, + 1 << memif_tmp->data->enable_shift); + mtk_afe_fe_trigger(substream, cmd, dai); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + mtk_afe_fe_trigger(substream, cmd, dai); + regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg, + 1 << memif_tmp->data->enable_shift, 0); + + return 0; + default: + return -EINVAL; + } +} + +static int mt2701_memif_fs(struct snd_pcm_substream *substream, + unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int fs; + + if (rtd->cpu_dai->id != MT2701_MEMIF_ULBT) + fs = mt2701_afe_i2s_fs(rate); + else + fs = (rate == 16000 ? 1 : 0); + + return fs; +} + +static int mt2701_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + return mt2701_afe_i2s_fs(rate); +} + +/* FE DAIs */ +static const struct snd_soc_dai_ops mt2701_single_memif_dai_ops = { + .startup = mt2701_simple_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mt2701_simple_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, +}; + +static const struct snd_soc_dai_ops mt2701_dlm_memif_dai_ops = { + .startup = mt2701_dlm_fe_startup, + .shutdown = mt2701_dlm_fe_shutdown, + .hw_params = mt2701_dlm_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mt2701_dlm_fe_trigger, +}; + +/* I2S BE DAIs */ +static const struct snd_soc_dai_ops mt2701_afe_i2s_ops = { + .startup = mt2701_afe_i2s_startup, + .shutdown = mt2701_afe_i2s_shutdown, + .prepare = mt2701_afe_i2s_prepare, + .set_sysclk = mt2701_afe_i2s_set_sysclk, +}; + +/* MRG BE DAIs */ +static const struct snd_soc_dai_ops mt2701_btmrg_ops = { + .startup = mt2701_btmrg_startup, + .shutdown = mt2701_btmrg_shutdown, + .hw_params = mt2701_btmrg_hw_params, +}; + +static struct snd_soc_dai_driver mt2701_afe_pcm_dais[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "PCMO0", + .id = MT2701_MEMIF_DL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM_multi", + .id = MT2701_MEMIF_DLM, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DLM", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_dlm_memif_dai_ops, + }, + { + .name = "PCM0", + .id = MT2701_MEMIF_UL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "UL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM1", + .id = MT2701_MEMIF_UL2, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "UL2", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM_BT_DL", + .id = MT2701_MEMIF_DLBT, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DLBT", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM_BT_UL", + .id = MT2701_MEMIF_ULBT, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "ULBT", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_single_memif_dai_ops, + }, + /* BE DAIs */ + { + .name = "I2S0", + .id = MT2701_IO_I2S, + .playback = { + .stream_name = "I2S0 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .capture = { + .stream_name = "I2S0 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S1", + .id = MT2701_IO_2ND_I2S, + .playback = { + .stream_name = "I2S1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S2", + .id = MT2701_IO_3RD_I2S, + .playback = { + .stream_name = "I2S2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S3", + .id = MT2701_IO_4TH_I2S, + .playback = { + .stream_name = "I2S3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "MRG BT", + .id = MT2701_IO_MRG, + .playback = { + .stream_name = "BT Playback", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "BT Capture", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_btmrg_ops, + .symmetric_rates = 1, + } +}; + +static const struct snd_kcontrol_new mt2701_afe_o00_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I00 Switch", AFE_CONN0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o01_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I01 Switch", AFE_CONN1, 1, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o02_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I02 Switch", AFE_CONN2, 2, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o03_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 3, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o14_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I26 Switch", AFE_CONN14, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o15_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I12 Switch", AFE_CONN15, 12, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o16_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I13 Switch", AFE_CONN16, 13, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o17_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I14 Switch", AFE_CONN17, 14, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o18_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I15 Switch", AFE_CONN18, 15, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o19_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I16 Switch", AFE_CONN19, 16, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o20_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN20, 17, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o21_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN21, 18, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o22_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I19 Switch", AFE_CONN22, 19, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o23_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I20 Switch", AFE_CONN23, 20, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o24_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I21 Switch", AFE_CONN24, 21, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o31_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I35 Switch", AFE_CONN41, 9, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_i02_mix[] = { + SOC_DAPM_SINGLE("I2S0 Switch", SND_SOC_NOPM, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s0[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S0 Out Switch", + ASYS_I2SO1_CON, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s1[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S1 Out Switch", + ASYS_I2SO2_CON, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s2[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S2 Out Switch", + PWR2_TOP_CON, 17, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s3[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S3 Out Switch", + PWR2_TOP_CON, 18, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s4[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S4 Out Switch", + PWR2_TOP_CON, 19, 1, 0), +}; + +static const struct snd_soc_dapm_widget mt2701_afe_pcm_widgets[] = { + /* inter-connections */ + SND_SOC_DAPM_MIXER("I00", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I01", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I02", SND_SOC_NOPM, 0, 0, mt2701_afe_i02_mix, + ARRAY_SIZE(mt2701_afe_i02_mix)), + SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I12", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I13", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I14", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I15", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I16", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I17", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I19", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I26", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I35", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("O00", SND_SOC_NOPM, 0, 0, mt2701_afe_o00_mix, + ARRAY_SIZE(mt2701_afe_o00_mix)), + SND_SOC_DAPM_MIXER("O01", SND_SOC_NOPM, 0, 0, mt2701_afe_o01_mix, + ARRAY_SIZE(mt2701_afe_o01_mix)), + SND_SOC_DAPM_MIXER("O02", SND_SOC_NOPM, 0, 0, mt2701_afe_o02_mix, + ARRAY_SIZE(mt2701_afe_o02_mix)), + SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0, mt2701_afe_o03_mix, + ARRAY_SIZE(mt2701_afe_o03_mix)), + SND_SOC_DAPM_MIXER("O14", SND_SOC_NOPM, 0, 0, mt2701_afe_o14_mix, + ARRAY_SIZE(mt2701_afe_o14_mix)), + SND_SOC_DAPM_MIXER("O15", SND_SOC_NOPM, 0, 0, mt2701_afe_o15_mix, + ARRAY_SIZE(mt2701_afe_o15_mix)), + SND_SOC_DAPM_MIXER("O16", SND_SOC_NOPM, 0, 0, mt2701_afe_o16_mix, + ARRAY_SIZE(mt2701_afe_o16_mix)), + SND_SOC_DAPM_MIXER("O17", SND_SOC_NOPM, 0, 0, mt2701_afe_o17_mix, + ARRAY_SIZE(mt2701_afe_o17_mix)), + SND_SOC_DAPM_MIXER("O18", SND_SOC_NOPM, 0, 0, mt2701_afe_o18_mix, + ARRAY_SIZE(mt2701_afe_o18_mix)), + SND_SOC_DAPM_MIXER("O19", SND_SOC_NOPM, 0, 0, mt2701_afe_o19_mix, + ARRAY_SIZE(mt2701_afe_o19_mix)), + SND_SOC_DAPM_MIXER("O20", SND_SOC_NOPM, 0, 0, mt2701_afe_o20_mix, + ARRAY_SIZE(mt2701_afe_o20_mix)), + SND_SOC_DAPM_MIXER("O21", SND_SOC_NOPM, 0, 0, mt2701_afe_o21_mix, + ARRAY_SIZE(mt2701_afe_o21_mix)), + SND_SOC_DAPM_MIXER("O22", SND_SOC_NOPM, 0, 0, mt2701_afe_o22_mix, + ARRAY_SIZE(mt2701_afe_o22_mix)), + SND_SOC_DAPM_MIXER("O31", SND_SOC_NOPM, 0, 0, mt2701_afe_o31_mix, + ARRAY_SIZE(mt2701_afe_o31_mix)), + + SND_SOC_DAPM_MIXER("I12I13", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s0, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s0)), + SND_SOC_DAPM_MIXER("I14I15", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s1, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s1)), + SND_SOC_DAPM_MIXER("I16I17", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s2, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s2)), + SND_SOC_DAPM_MIXER("I18I19", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s3, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s3)), +}; + +static const struct snd_soc_dapm_route mt2701_afe_pcm_routes[] = { + {"I12", NULL, "DL1"}, + {"I13", NULL, "DL1"}, + {"I35", NULL, "DLBT"}, + + {"I2S0 Playback", NULL, "O15"}, + {"I2S0 Playback", NULL, "O16"}, + {"I2S1 Playback", NULL, "O17"}, + {"I2S1 Playback", NULL, "O18"}, + {"I2S2 Playback", NULL, "O19"}, + {"I2S2 Playback", NULL, "O20"}, + {"I2S3 Playback", NULL, "O21"}, + {"I2S3 Playback", NULL, "O22"}, + {"BT Playback", NULL, "O31"}, + + {"UL1", NULL, "O00"}, + {"UL1", NULL, "O01"}, + {"UL2", NULL, "O02"}, + {"UL2", NULL, "O03"}, + {"ULBT", NULL, "O14"}, + + {"I00", NULL, "I2S0 Capture"}, + {"I01", NULL, "I2S0 Capture"}, + {"I02", NULL, "I2S1 Capture"}, + {"I03", NULL, "I2S1 Capture"}, + /* I02,03 link to UL2, also need to open I2S0 */ + {"I02", "I2S0 Switch", "I2S0 Capture"}, + + {"I26", NULL, "BT Capture"}, + + {"I12I13", "Multich I2S0 Out Switch", "DLM"}, + {"I14I15", "Multich I2S1 Out Switch", "DLM"}, + {"I16I17", "Multich I2S2 Out Switch", "DLM"}, + {"I18I19", "Multich I2S3 Out Switch", "DLM"}, + + { "I12", NULL, "I12I13" }, + { "I13", NULL, "I12I13" }, + { "I14", NULL, "I14I15" }, + { "I15", NULL, "I14I15" }, + { "I16", NULL, "I16I17" }, + { "I17", NULL, "I16I17" }, + { "I18", NULL, "I18I19" }, + { "I19", NULL, "I18I19" }, + + { "O00", "I00 Switch", "I00" }, + { "O01", "I01 Switch", "I01" }, + { "O02", "I02 Switch", "I02" }, + { "O03", "I03 Switch", "I03" }, + { "O14", "I26 Switch", "I26" }, + { "O15", "I12 Switch", "I12" }, + { "O16", "I13 Switch", "I13" }, + { "O17", "I14 Switch", "I14" }, + { "O18", "I15 Switch", "I15" }, + { "O19", "I16 Switch", "I16" }, + { "O20", "I17 Switch", "I17" }, + { "O21", "I18 Switch", "I18" }, + { "O22", "I19 Switch", "I19" }, + { "O31", "I35 Switch", "I35" }, +}; + +static int mt2701_afe_pcm_probe(struct snd_soc_component *component) +{ + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + + snd_soc_component_init_regmap(component, afe->regmap); + + return 0; +} + +static const struct snd_soc_component_driver mt2701_afe_pcm_dai_component = { + .probe = mt2701_afe_pcm_probe, + .name = "mt2701-afe-pcm-dai", + .dapm_widgets = mt2701_afe_pcm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_afe_pcm_widgets), + .dapm_routes = mt2701_afe_pcm_routes, + .num_dapm_routes = ARRAY_SIZE(mt2701_afe_pcm_routes), +}; + +static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { + { + .name = "DL1", + .id = MT2701_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 16, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 1, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 6, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL2", + .id = MT2701_MEMIF_DL2, + .reg_ofs_base = AFE_DL2_BASE, + .reg_ofs_cur = AFE_DL2_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 5, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 17, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 2, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 2, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 7, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL3", + .id = MT2701_MEMIF_DL3, + .reg_ofs_base = AFE_DL3_BASE, + .reg_ofs_cur = AFE_DL3_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 18, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 3, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 4, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 8, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL4", + .id = MT2701_MEMIF_DL4, + .reg_ofs_base = AFE_DL4_BASE, + .reg_ofs_cur = AFE_DL4_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 15, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 19, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 4, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 6, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 9, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL5", + .id = MT2701_MEMIF_DL5, + .reg_ofs_base = AFE_DL5_BASE, + .reg_ofs_cur = AFE_DL5_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 20, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 20, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 5, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 8, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 10, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DLM", + .id = MT2701_MEMIF_DLM, + .reg_ofs_base = AFE_DLMCH_BASE, + .reg_ofs_cur = AFE_DLMCH_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = -1, + .mono_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 7, + .hd_reg = AFE_MEMIF_PBUF_SIZE, + .hd_shift = 28, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 12, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL1", + .id = MT2701_MEMIF_UL1, + .reg_ofs_base = AFE_VUL_BASE, + .reg_ofs_cur = AFE_VUL_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 0, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 10, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 0, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL2", + .id = MT2701_MEMIF_UL2, + .reg_ofs_base = AFE_UL2_BASE, + .reg_ofs_cur = AFE_UL2_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 5, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 2, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 11, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 2, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 1, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL3", + .id = MT2701_MEMIF_UL3, + .reg_ofs_base = AFE_UL3_BASE, + .reg_ofs_cur = AFE_UL3_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 4, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 12, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 2, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL4", + .id = MT2701_MEMIF_UL4, + .reg_ofs_base = AFE_UL4_BASE, + .reg_ofs_cur = AFE_UL4_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 15, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 6, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 13, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 6, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 3, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL5", + .id = MT2701_MEMIF_UL5, + .reg_ofs_base = AFE_UL5_BASE, + .reg_ofs_cur = AFE_UL5_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 20, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 8, + .fs_maskbit = 0x1f, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 14, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 8, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 4, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DLBT", + .id = MT2701_MEMIF_DLBT, + .reg_ofs_base = AFE_ARB1_BASE, + .reg_ofs_cur = AFE_ARB1_CUR, + .fs_reg = AFE_DAC_CON3, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 22, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 8, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 14, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 13, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "ULBT", + .id = MT2701_MEMIF_ULBT, + .reg_ofs_base = AFE_DAI_BASE, + .reg_ofs_cur = AFE_DAI_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 30, + .fs_maskbit = 0x1, + .mono_reg = -1, + .mono_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 17, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 20, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 16, + .msb_reg = -1, + .msb_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[MT2701_IRQ_ASYS_END] = { + { + .id = MT2701_IRQ_ASYS_IRQ1, + .irq_cnt_reg = ASYS_IRQ1_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ1_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ1_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 0, + }, + { + .id = MT2701_IRQ_ASYS_IRQ2, + .irq_cnt_reg = ASYS_IRQ2_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ2_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ2_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 1, + }, + { + .id = MT2701_IRQ_ASYS_IRQ3, + .irq_cnt_reg = ASYS_IRQ3_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ3_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ3_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 2, + } +}; + +static const struct mt2701_i2s_data mt2701_i2s_data[][2] = { + { + { ASYS_I2SO1_CON, 0, 0x1f }, + { ASYS_I2SIN1_CON, 0, 0x1f }, + }, + { + { ASYS_I2SO2_CON, 5, 0x1f }, + { ASYS_I2SIN2_CON, 5, 0x1f }, + }, + { + { ASYS_I2SO3_CON, 10, 0x1f }, + { ASYS_I2SIN3_CON, 10, 0x1f }, + }, + { + { ASYS_I2SO4_CON, 15, 0x1f }, + { ASYS_I2SIN4_CON, 15, 0x1f }, + }, + /* TODO - extend control registers supported by newer SoCs */ +}; + +static irqreturn_t mt2701_asys_isr(int irq_id, void *dev) +{ + int id; + struct mtk_base_afe *afe = dev; + struct mtk_base_afe_memif *memif; + struct mtk_base_afe_irq *irq; + u32 status; + + regmap_read(afe->regmap, ASYS_IRQ_STATUS, &status); + regmap_write(afe->regmap, ASYS_IRQ_CLR, status); + + for (id = 0; id < MT2701_MEMIF_NUM; ++id) { + memif = &afe->memif[id]; + if (memif->irq_usage < 0) + continue; + + irq = &afe->irqs[memif->irq_usage]; + if (status & 1 << irq->irq_data->irq_clr_shift) + snd_pcm_period_elapsed(memif->substream); + } + + return IRQ_HANDLED; +} + +static int mt2701_afe_runtime_suspend(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + + return mt2701_afe_disable_clock(afe); +} + +static int mt2701_afe_runtime_resume(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + + return mt2701_afe_enable_clock(afe); +} + +static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) +{ + struct mtk_base_afe *afe; + struct mt2701_afe_private *afe_priv; + struct device *dev; + int i, irq_id, ret; + + afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + + afe_priv = afe->platform_priv; + afe_priv->soc = of_device_get_match_data(&pdev->dev); + afe->dev = &pdev->dev; + dev = afe->dev; + + afe_priv->i2s_path = devm_kcalloc(dev, + afe_priv->soc->i2s_num, + sizeof(struct mt2701_i2s_path), + GFP_KERNEL); + if (!afe_priv->i2s_path) + return -ENOMEM; + + irq_id = platform_get_irq_byname(pdev, "asys"); + if (irq_id < 0) { + dev_err(dev, "unable to get ASYS IRQ\n"); + return irq_id; + } + + ret = devm_request_irq(dev, irq_id, mt2701_asys_isr, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) { + dev_err(dev, "could not request_irq for asys-isr\n"); + return ret; + } + + afe->regmap = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(afe->regmap)) { + dev_err(dev, "could not get regmap from parent\n"); + return PTR_ERR(afe->regmap); + } + + mutex_init(&afe->irq_alloc_lock); + + /* memif initialize */ + afe->memif_size = MT2701_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + for (i = 0; i < afe->memif_size; i++) { + afe->memif[i].data = &memif_data[i]; + afe->memif[i].irq_usage = -1; + } + + /* irq initialize */ + afe->irqs_size = MT2701_IRQ_ASYS_END; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + /* I2S initialize */ + for (i = 0; i < afe_priv->soc->i2s_num; i++) { + afe_priv->i2s_path[i].i2s_data[SNDRV_PCM_STREAM_PLAYBACK] = + &mt2701_i2s_data[i][SNDRV_PCM_STREAM_PLAYBACK]; + afe_priv->i2s_path[i].i2s_data[SNDRV_PCM_STREAM_CAPTURE] = + &mt2701_i2s_data[i][SNDRV_PCM_STREAM_CAPTURE]; + } + + afe->mtk_afe_hardware = &mt2701_afe_hardware; + afe->memif_fs = mt2701_memif_fs; + afe->irq_fs = mt2701_irq_fs; + afe->reg_back_up_list = mt2701_afe_backup_list; + afe->reg_back_up_list_num = ARRAY_SIZE(mt2701_afe_backup_list); + afe->runtime_resume = mt2701_afe_runtime_resume; + afe->runtime_suspend = mt2701_afe_runtime_suspend; + + /* initial audio related clock */ + ret = mt2701_init_clock(afe); + if (ret) { + dev_err(dev, "init clock error\n"); + return ret; + } + + platform_set_drvdata(pdev, afe); + + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = mt2701_afe_runtime_resume(dev); + if (ret) + goto err_pm_disable; + } + pm_runtime_get_sync(dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &mtk_afe_pcm_platform, + NULL, 0); + if (ret) { + dev_warn(dev, "err_platform\n"); + goto err_platform; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &mt2701_afe_pcm_dai_component, + mt2701_afe_pcm_dais, + ARRAY_SIZE(mt2701_afe_pcm_dais)); + if (ret) { + dev_warn(dev, "err_dai_component\n"); + goto err_platform; + } + + return 0; + +err_platform: + pm_runtime_put_sync(dev); +err_pm_disable: + pm_runtime_disable(dev); + + return ret; +} + +static int mt2701_afe_pcm_dev_remove(struct platform_device *pdev) +{ + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + mt2701_afe_runtime_suspend(&pdev->dev); + + return 0; +} + +static const struct mt2701_soc_variants mt2701_soc_v1 = { + .i2s_num = 4, +}; + +static const struct mt2701_soc_variants mt2701_soc_v2 = { + .has_one_heart_mode = true, + .i2s_num = 4, +}; + +static const struct of_device_id mt2701_afe_pcm_dt_match[] = { + { .compatible = "mediatek,mt2701-audio", .data = &mt2701_soc_v1 }, + { .compatible = "mediatek,mt7622-audio", .data = &mt2701_soc_v2 }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt2701_afe_pcm_dt_match); + +static const struct dev_pm_ops mt2701_afe_pm_ops = { + SET_RUNTIME_PM_OPS(mt2701_afe_runtime_suspend, + mt2701_afe_runtime_resume, NULL) +}; + +static struct platform_driver mt2701_afe_pcm_driver = { + .driver = { + .name = "mt2701-audio", + .of_match_table = mt2701_afe_pcm_dt_match, +#ifdef CONFIG_PM + .pm = &mt2701_afe_pm_ops, +#endif + }, + .probe = mt2701_afe_pcm_dev_probe, + .remove = mt2701_afe_pcm_dev_remove, +}; + +module_platform_driver(mt2701_afe_pcm_driver); + +MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver for 2701"); +MODULE_AUTHOR("Garlic Tseng "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c new file mode 100644 index 000000000..666282b86 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt2701-cs42448.c -- MT2701 CS42448 ALSA SoC machine driver + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Ir Lian + * Garlic Tseng + */ + +#include +#include +#include +#include +#include +#include + +#include "mt2701-afe-common.h" + +struct mt2701_cs42448_private { + int i2s1_in_mux; + int i2s1_in_mux_gpio_sel_1; + int i2s1_in_mux_gpio_sel_2; +}; + +static const char * const i2sin_mux_switch_text[] = { + "ADC_SDOUT2", + "ADC_SDOUT3", + "I2S_IN_1", + "I2S_IN_2", +}; + +static const struct soc_enum i2sin_mux_enum = + SOC_ENUM_SINGLE_EXT(4, i2sin_mux_switch_text); + +static int mt2701_cs42448_i2sin1_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.integer.value[0] = priv->i2s1_in_mux; + return 0; +} + +static int mt2701_cs42448_i2sin1_mux_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card); + + if (ucontrol->value.integer.value[0] == priv->i2s1_in_mux) + return 0; + + switch (ucontrol->value.integer.value[0]) { + case 0: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0); + break; + case 1: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0); + break; + case 2: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1); + break; + case 3: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1); + break; + default: + dev_warn(card->dev, "%s invalid setting\n", __func__); + } + + priv->i2s1_in_mux = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_soc_dapm_widget + mt2701_cs42448_asoc_card_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_LINE("Tuner In", NULL), + SND_SOC_DAPM_LINE("Satellite Tuner In", NULL), + SND_SOC_DAPM_LINE("AUX In", NULL), +}; + +static const struct snd_kcontrol_new mt2701_cs42448_controls[] = { + SOC_DAPM_PIN_SWITCH("Line Out Jack"), + SOC_DAPM_PIN_SWITCH("AMIC"), + SOC_DAPM_PIN_SWITCH("Tuner In"), + SOC_DAPM_PIN_SWITCH("Satellite Tuner In"), + SOC_DAPM_PIN_SWITCH("AUX In"), + SOC_ENUM_EXT("I2SIN1_MUX_Switch", i2sin_mux_enum, + mt2701_cs42448_i2sin1_mux_get, + mt2701_cs42448_i2sin1_mux_set), +}; + +static const unsigned int mt2701_cs42448_sampling_rates[] = {48000}; + +static const struct snd_pcm_hw_constraint_list mt2701_cs42448_constraints_rates = { + .count = ARRAY_SIZE(mt2701_cs42448_sampling_rates), + .list = mt2701_cs42448_sampling_rates, + .mask = 0, +}; + +static int mt2701_cs42448_fe_ops_startup(struct snd_pcm_substream *substream) +{ + int err; + + err = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &mt2701_cs42448_constraints_rates); + if (err < 0) { + dev_err(substream->pcm->card->dev, + "%s snd_pcm_hw_constraint_list failed: 0x%x\n", + __func__, err); + return err; + } + return 0; +} + +static const struct snd_soc_ops mt2701_cs42448_48k_fe_ops = { + .startup = mt2701_cs42448_fe_ops_startup, +}; + +static int mt2701_cs42448_be_ops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + unsigned int div_mclk_over_bck = rate > 192000 ? 2 : 4; + unsigned int div_bck_over_lrck = 64; + + mclk_rate = rate * div_bck_over_lrck * div_mclk_over_bck; + + /* mt2701 mclk */ + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, SND_SOC_CLOCK_OUT); + + /* codec mclk */ + snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, SND_SOC_CLOCK_IN); + + return 0; +} + +static struct snd_soc_ops mt2701_cs42448_be_ops = { + .hw_params = mt2701_cs42448_be_ops_hw_params +}; + +enum { + DAI_LINK_FE_MULTI_CH_OUT, + DAI_LINK_FE_PCM0_IN, + DAI_LINK_FE_PCM1_IN, + DAI_LINK_FE_BT_OUT, + DAI_LINK_FE_BT_IN, + DAI_LINK_BE_I2S0, + DAI_LINK_BE_I2S1, + DAI_LINK_BE_I2S2, + DAI_LINK_BE_I2S3, + DAI_LINK_BE_MRG_BT, +}; + +static struct snd_soc_dai_link mt2701_cs42448_dai_links[] = { + /* FE */ + [DAI_LINK_FE_MULTI_CH_OUT] = { + .name = "mt2701-cs42448-multi-ch-out", + .stream_name = "mt2701-cs42448-multi-ch-out", + .cpu_dai_name = "PCM_multi", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_FE_PCM0_IN] = { + .name = "mt2701-cs42448-pcm0", + .stream_name = "mt2701-cs42448-pcm0-data-UL", + .cpu_dai_name = "PCM0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_FE_PCM1_IN] = { + .name = "mt2701-cs42448-pcm1-data-UL", + .stream_name = "mt2701-cs42448-pcm1-data-UL", + .cpu_dai_name = "PCM1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_FE_BT_OUT] = { + .name = "mt2701-cs42448-pcm-BT-out", + .stream_name = "mt2701-cs42448-pcm-BT", + .cpu_dai_name = "PCM_BT_DL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_FE_BT_IN] = { + .name = "mt2701-cs42448-pcm-BT-in", + .stream_name = "mt2701-cs42448-pcm-BT", + .cpu_dai_name = "PCM_BT_UL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* BE */ + [DAI_LINK_BE_I2S0] = { + .name = "mt2701-cs42448-I2S0", + .cpu_dai_name = "I2S0", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S1] = { + .name = "mt2701-cs42448-I2S1", + .cpu_dai_name = "I2S1", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S2] = { + .name = "mt2701-cs42448-I2S2", + .cpu_dai_name = "I2S2", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S3] = { + .name = "mt2701-cs42448-I2S3", + .cpu_dai_name = "I2S3", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_MRG_BT] = { + .name = "mt2701-cs42448-MRG-BT", + .cpu_dai_name = "MRG BT", + .no_pcm = 1, + .codec_dai_name = "bt-sco-pcm-wb", + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt2701_cs42448_soc_card = { + .name = "mt2701-cs42448", + .owner = THIS_MODULE, + .dai_link = mt2701_cs42448_dai_links, + .num_links = ARRAY_SIZE(mt2701_cs42448_dai_links), + .controls = mt2701_cs42448_controls, + .num_controls = ARRAY_SIZE(mt2701_cs42448_controls), + .dapm_widgets = mt2701_cs42448_asoc_card_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_cs42448_asoc_card_dapm_widgets), +}; + +static int mt2701_cs42448_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt2701_cs42448_soc_card; + int ret; + int i; + struct device_node *platform_node, *codec_node, *codec_node_bt_mrg; + struct mt2701_cs42448_private *priv = + devm_kzalloc(&pdev->dev, sizeof(struct mt2701_cs42448_private), + GFP_KERNEL); + struct device *dev = &pdev->dev; + + if (!priv) + return -ENOMEM; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_cs42448_dai_links[i].platform_name) + continue; + mt2701_cs42448_dai_links[i].platform_of_node = platform_node; + } + + card->dev = dev; + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_cs42448_dai_links[i].codec_name) + continue; + mt2701_cs42448_dai_links[i].codec_of_node = codec_node; + } + + codec_node_bt_mrg = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec-bt-mrg", 0); + if (!codec_node_bt_mrg) { + dev_err(&pdev->dev, + "Property 'audio-codec-bt-mrg' missing or invalid\n"); + return -EINVAL; + } + mt2701_cs42448_dai_links[DAI_LINK_BE_MRG_BT].codec_of_node + = codec_node_bt_mrg; + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + return ret; + } + + priv->i2s1_in_mux_gpio_sel_1 = + of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio1", 0); + if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_1)) { + ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_1, + "i2s1_in_mux_gpio_sel_1"); + if (ret) + dev_warn(&pdev->dev, "%s devm_gpio_request fail %d\n", + __func__, ret); + gpio_direction_output(priv->i2s1_in_mux_gpio_sel_1, 0); + } + + priv->i2s1_in_mux_gpio_sel_2 = + of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio2", 0); + if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_2)) { + ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_2, + "i2s1_in_mux_gpio_sel_2"); + if (ret) + dev_warn(&pdev->dev, "%s devm_gpio_request fail2 %d\n", + __func__, ret); + gpio_direction_output(priv->i2s1_in_mux_gpio_sel_2, 0); + } + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt2701_cs42448_machine_dt_match[] = { + {.compatible = "mediatek,mt2701-cs42448-machine",}, + {} +}; +#endif + +static struct platform_driver mt2701_cs42448_machine = { + .driver = { + .name = "mt2701-cs42448", + #ifdef CONFIG_OF + .of_match_table = mt2701_cs42448_machine_dt_match, + #endif + }, + .probe = mt2701_cs42448_machine_probe, +}; + +module_platform_driver(mt2701_cs42448_machine); + +/* Module information */ +MODULE_DESCRIPTION("MT2701 CS42448 ALSA SoC machine driver"); +MODULE_AUTHOR("Ir Lian "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt2701 cs42448 soc card"); diff --git a/sound/soc/mediatek/mt2701/mt2701-reg.h b/sound/soc/mediatek/mt2701/mt2701-reg.h new file mode 100644 index 000000000..c84d14cdd --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-reg.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt2701-reg.h -- Mediatek 2701 audio driver reg definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng + */ + +#ifndef _MT2701_REG_H_ +#define _MT2701_REG_H_ + +#define AUDIO_TOP_CON0 0x0000 +#define AUDIO_TOP_CON4 0x0010 +#define AUDIO_TOP_CON5 0x0014 +#define AFE_DAIBT_CON0 0x001c +#define AFE_MRGIF_CON 0x003c +#define ASMI_TIMING_CON1 0x0100 +#define ASMO_TIMING_CON1 0x0104 +#define PWR1_ASM_CON1 0x0108 +#define ASYS_TOP_CON 0x0600 +#define ASYS_I2SIN1_CON 0x0604 +#define ASYS_I2SIN2_CON 0x0608 +#define ASYS_I2SIN3_CON 0x060c +#define ASYS_I2SIN4_CON 0x0610 +#define ASYS_I2SIN5_CON 0x0614 +#define ASYS_I2SO1_CON 0x061C +#define ASYS_I2SO2_CON 0x0620 +#define ASYS_I2SO3_CON 0x0624 +#define ASYS_I2SO4_CON 0x0628 +#define ASYS_I2SO5_CON 0x062c +#define PWR2_TOP_CON 0x0634 +#define AFE_CONN0 0x06c0 +#define AFE_CONN1 0x06c4 +#define AFE_CONN2 0x06c8 +#define AFE_CONN3 0x06cc +#define AFE_CONN14 0x06f8 +#define AFE_CONN15 0x06fc +#define AFE_CONN16 0x0700 +#define AFE_CONN17 0x0704 +#define AFE_CONN18 0x0708 +#define AFE_CONN19 0x070c +#define AFE_CONN20 0x0710 +#define AFE_CONN21 0x0714 +#define AFE_CONN22 0x0718 +#define AFE_CONN23 0x071c +#define AFE_CONN24 0x0720 +#define AFE_CONN41 0x0764 +#define ASYS_IRQ1_CON 0x0780 +#define ASYS_IRQ2_CON 0x0784 +#define ASYS_IRQ3_CON 0x0788 +#define ASYS_IRQ_CLR 0x07c0 +#define ASYS_IRQ_STATUS 0x07c4 +#define PWR2_ASM_CON1 0x1070 +#define AFE_DAC_CON0 0x1200 +#define AFE_DAC_CON1 0x1204 +#define AFE_DAC_CON2 0x1208 +#define AFE_DAC_CON3 0x120c +#define AFE_DAC_CON4 0x1210 +#define AFE_MEMIF_HD_CON1 0x121c +#define AFE_MEMIF_PBUF_SIZE 0x1238 +#define AFE_MEMIF_HD_CON0 0x123c +#define AFE_DL1_BASE 0x1240 +#define AFE_DL1_CUR 0x1244 +#define AFE_DL2_BASE 0x1250 +#define AFE_DL2_CUR 0x1254 +#define AFE_DL3_BASE 0x1260 +#define AFE_DL3_CUR 0x1264 +#define AFE_DL4_BASE 0x1270 +#define AFE_DL4_CUR 0x1274 +#define AFE_DL5_BASE 0x1280 +#define AFE_DL5_CUR 0x1284 +#define AFE_DLMCH_BASE 0x12a0 +#define AFE_DLMCH_CUR 0x12a4 +#define AFE_ARB1_BASE 0x12b0 +#define AFE_ARB1_CUR 0x12b4 +#define AFE_VUL_BASE 0x1300 +#define AFE_VUL_CUR 0x130c +#define AFE_UL2_BASE 0x1310 +#define AFE_UL2_END 0x1318 +#define AFE_UL2_CUR 0x131c +#define AFE_UL3_BASE 0x1320 +#define AFE_UL3_END 0x1328 +#define AFE_UL3_CUR 0x132c +#define AFE_UL4_BASE 0x1330 +#define AFE_UL4_END 0x1338 +#define AFE_UL4_CUR 0x133c +#define AFE_UL5_BASE 0x1340 +#define AFE_UL5_END 0x1348 +#define AFE_UL5_CUR 0x134c +#define AFE_DAI_BASE 0x1370 +#define AFE_DAI_CUR 0x137c + +/* AFE_DAIBT_CON0 (0x001c) */ +#define AFE_DAIBT_CON0_DAIBT_EN (0x1 << 0) +#define AFE_DAIBT_CON0_BT_FUNC_EN (0x1 << 1) +#define AFE_DAIBT_CON0_BT_FUNC_RDY (0x1 << 3) +#define AFE_DAIBT_CON0_BT_WIDE_MODE_EN (0x1 << 9) +#define AFE_DAIBT_CON0_MRG_USE (0x1 << 12) + +/* PWR1_ASM_CON1 (0x0108) */ +#define PWR1_ASM_CON1_INIT_VAL (0x492) + +/* AFE_MRGIF_CON (0x003c) */ +#define AFE_MRGIF_CON_MRG_EN (0x1 << 0) +#define AFE_MRGIF_CON_MRG_I2S_EN (0x1 << 16) +#define AFE_MRGIF_CON_I2S_MODE_MASK (0xf << 20) +#define AFE_MRGIF_CON_I2S_MODE_32K (0x4 << 20) + +/* ASYS_TOP_CON (0x0600) */ +#define ASYS_TOP_CON_ASYS_TIMING_ON (0x3 << 0) + +/* PWR2_ASM_CON1 (0x1070) */ +#define PWR2_ASM_CON1_INIT_VAL (0x492492) + +/* AFE_DAC_CON0 (0x1200) */ +#define AFE_DAC_CON0_AFE_ON (0x1 << 0) + +/* AFE_MEMIF_PBUF_SIZE (0x1238) */ +#define AFE_MEMIF_PBUF_SIZE_DLM_MASK (0x1 << 29) +#define AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE (0x0 << 29) +#define AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE (0x1 << 29) +#define DLMCH_BIT_WIDTH_MASK (0x1 << 28) +#define AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK (0xf << 24) +#define AFE_MEMIF_PBUF_SIZE_DLM_CH(x) ((x) << 24) +#define AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK (0x3 << 12) +#define AFE_MEMIF_PBUF_SIZE_DLM_32BYTES (0x1 << 12) + +/* I2S in/out register bit control */ +#define ASYS_I2S_CON_FS (0x1f << 8) +#define ASYS_I2S_CON_FS_SET(x) ((x) << 8) +#define ASYS_I2S_CON_RESET (0x1 << 30) +#define ASYS_I2S_CON_I2S_EN (0x1 << 0) +#define ASYS_I2S_CON_ONE_HEART_MODE (0x1 << 16) +#define ASYS_I2S_CON_I2S_COUPLE_MODE (0x1 << 17) +/* 0:EIAJ 1:I2S */ +#define ASYS_I2S_CON_I2S_MODE (0x1 << 3) +#define ASYS_I2S_CON_WIDE_MODE (0x1 << 1) +#define ASYS_I2S_CON_WIDE_MODE_SET(x) ((x) << 1) +#define ASYS_I2S_IN_PHASE_FIX (0x1 << 31) + +#endif diff --git a/sound/soc/mediatek/mt2701/mt2701-wm8960.c b/sound/soc/mediatek/mt2701/mt2701-wm8960.c new file mode 100644 index 000000000..a5ede216b --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-wm8960.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt2701-wm8960.c -- MT2701 WM8960 ALSA SoC machine driver + * + * Copyright (c) 2017 MediaTek Inc. + * Author: Ryder Lee + */ + +#include +#include + +#include "mt2701-afe-common.h" + +static const struct snd_soc_dapm_widget mt2701_wm8960_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), +}; + +static const struct snd_kcontrol_new mt2701_wm8960_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("AMIC"), +}; + +static int mt2701_wm8960_be_ops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + unsigned int div_mclk_over_bck = rate > 192000 ? 2 : 4; + unsigned int div_bck_over_lrck = 64; + + mclk_rate = rate * div_bck_over_lrck * div_mclk_over_bck; + + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, SND_SOC_CLOCK_OUT); + snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, SND_SOC_CLOCK_IN); + + return 0; +} + +static struct snd_soc_ops mt2701_wm8960_be_ops = { + .hw_params = mt2701_wm8960_be_ops_hw_params +}; + +static struct snd_soc_dai_link mt2701_wm8960_dai_links[] = { + /* FE */ + { + .name = "wm8960-playback", + .stream_name = "wm8960-playback", + .cpu_dai_name = "PCMO0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "wm8960-capture", + .stream_name = "wm8960-capture", + .cpu_dai_name = "PCM0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* BE */ + { + .name = "wm8960-codec", + .cpu_dai_name = "I2S0", + .no_pcm = 1, + .codec_dai_name = "wm8960-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_wm8960_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt2701_wm8960_card = { + .name = "mt2701-wm8960", + .owner = THIS_MODULE, + .dai_link = mt2701_wm8960_dai_links, + .num_links = ARRAY_SIZE(mt2701_wm8960_dai_links), + .controls = mt2701_wm8960_controls, + .num_controls = ARRAY_SIZE(mt2701_wm8960_controls), + .dapm_widgets = mt2701_wm8960_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_wm8960_widgets), +}; + +static int mt2701_wm8960_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt2701_wm8960_card; + struct device_node *platform_node, *codec_node; + int ret, i; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_wm8960_dai_links[i].platform_name) + continue; + mt2701_wm8960_dai_links[i].platform_of_node = platform_node; + } + + card->dev = &pdev->dev; + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto put_platform_node; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_wm8960_dai_links[i].codec_name) + continue; + mt2701_wm8960_dai_links[i].codec_of_node = codec_node; + } + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + goto put_codec_node; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + +put_codec_node: + of_node_put(codec_node); +put_platform_node: + of_node_put(platform_node); + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt2701_wm8960_machine_dt_match[] = { + {.compatible = "mediatek,mt2701-wm8960-machine",}, + {} +}; +#endif + +static struct platform_driver mt2701_wm8960_machine = { + .driver = { + .name = "mt2701-wm8960", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = mt2701_wm8960_machine_dt_match, +#endif + }, + .probe = mt2701_wm8960_machine_probe, +}; + +module_platform_driver(mt2701_wm8960_machine); + +/* Module information */ +MODULE_DESCRIPTION("MT2701 WM8960 ALSA SoC machine driver"); +MODULE_AUTHOR("Ryder Lee "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt2701 wm8960 soc card"); + diff --git a/sound/soc/mediatek/mt6797/Makefile b/sound/soc/mediatek/mt6797/Makefile new file mode 100644 index 000000000..bf6e179ea --- /dev/null +++ b/sound/soc/mediatek/mt6797/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +# platform driver +snd-soc-mt6797-afe-objs := \ + mt6797-afe-pcm.o \ + mt6797-afe-clk.o \ + mt6797-dai-pcm.o \ + mt6797-dai-hostless.o \ + mt6797-dai-adda.o + +obj-$(CONFIG_SND_SOC_MT6797) += snd-soc-mt6797-afe.o + +# machine driver +obj-$(CONFIG_SND_SOC_MT6797_MT6351) += mt6797-mt6351.o diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-clk.c b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c new file mode 100644 index 000000000..6f3e6acfc --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt6797-afe-clk.c -- Mediatek 6797 afe clock ctrl +// +// Copyright (c) 2018 MediaTek Inc. +// Author: KaiChieh Chuang + +#include + +#include "mt6797-afe-common.h" +#include "mt6797-afe-clk.h" + +enum { + CLK_INFRA_SYS_AUD, + CLK_INFRA_SYS_AUD_26M, + CLK_TOP_MUX_AUD, + CLK_TOP_MUX_AUD_BUS, + CLK_TOP_SYSPLL3_D4, + CLK_TOP_SYSPLL1_D4, + CLK_CLK26M, + CLK_NUM +}; + +static const char *aud_clks[CLK_NUM] = { + [CLK_INFRA_SYS_AUD] = "infra_sys_audio_clk", + [CLK_INFRA_SYS_AUD_26M] = "infra_sys_audio_26m", + [CLK_TOP_MUX_AUD] = "top_mux_audio", + [CLK_TOP_MUX_AUD_BUS] = "top_mux_aud_intbus", + [CLK_TOP_SYSPLL3_D4] = "top_sys_pll3_d4", + [CLK_TOP_SYSPLL1_D4] = "top_sys_pll1_d4", + [CLK_CLK26M] = "top_clk26m_clk", +}; + +int mt6797_init_clock(struct mtk_base_afe *afe) +{ + struct mt6797_afe_private *afe_priv = afe->platform_priv; + int i; + + afe_priv->clk = devm_kcalloc(afe->dev, CLK_NUM, sizeof(*afe_priv->clk), + GFP_KERNEL); + if (!afe_priv->clk) + return -ENOMEM; + + for (i = 0; i < CLK_NUM; i++) { + afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); + if (IS_ERR(afe_priv->clk[i])) { + dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", + __func__, aud_clks[i], + PTR_ERR(afe_priv->clk[i])); + return PTR_ERR(afe_priv->clk[i]); + } + } + + return 0; +} + +int mt6797_afe_enable_clock(struct mtk_base_afe *afe) +{ + struct mt6797_afe_private *afe_priv = afe->platform_priv; + int ret; + + ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_SYS_AUD]); + if (ret) { + dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", + __func__, aud_clks[CLK_INFRA_SYS_AUD], ret); + goto CLK_INFRA_SYS_AUDIO_ERR; + } + + ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_SYS_AUD_26M]); + if (ret) { + dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", + __func__, aud_clks[CLK_INFRA_SYS_AUD_26M], ret); + goto CLK_INFRA_SYS_AUD_26M_ERR; + } + + ret = clk_prepare_enable(afe_priv->clk[CLK_TOP_MUX_AUD]); + if (ret) { + dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", + __func__, aud_clks[CLK_TOP_MUX_AUD], ret); + goto CLK_MUX_AUDIO_ERR; + } + + ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD], + afe_priv->clk[CLK_CLK26M]); + if (ret) { + dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", + __func__, aud_clks[CLK_TOP_MUX_AUD], + aud_clks[CLK_CLK26M], ret); + goto CLK_MUX_AUDIO_ERR; + } + + ret = clk_prepare_enable(afe_priv->clk[CLK_TOP_MUX_AUD_BUS]); + if (ret) { + dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", + __func__, aud_clks[CLK_TOP_MUX_AUD_BUS], ret); + goto CLK_MUX_AUDIO_INTBUS_ERR; + } + + return ret; + +CLK_MUX_AUDIO_INTBUS_ERR: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_BUS]); +CLK_MUX_AUDIO_ERR: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD]); +CLK_INFRA_SYS_AUD_26M_ERR: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUD_26M]); +CLK_INFRA_SYS_AUDIO_ERR: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUD]); + + return 0; +} + +int mt6797_afe_disable_clock(struct mtk_base_afe *afe) +{ + struct mt6797_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_BUS]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD]); + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUD_26M]); + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUD]); + + return 0; +} diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-clk.h b/sound/soc/mediatek/mt6797/mt6797-afe-clk.h new file mode 100644 index 000000000..a6f0cb572 --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-afe-clk.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt6797-afe-clk.h -- Mediatek 6797 afe clock ctrl definition + * + * Copyright (c) 2018 MediaTek Inc. + * Author: KaiChieh Chuang + */ + +#ifndef _MT6797_AFE_CLK_H_ +#define _MT6797_AFE_CLK_H_ + +struct mtk_base_afe; + +int mt6797_init_clock(struct mtk_base_afe *afe); +int mt6797_afe_enable_clock(struct mtk_base_afe *afe); +int mt6797_afe_disable_clock(struct mtk_base_afe *afe); +#endif diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-common.h b/sound/soc/mediatek/mt6797/mt6797-afe-common.h new file mode 100644 index 000000000..4eac9977b --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-afe-common.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt6797-afe-common.h -- Mediatek 6797 audio driver definitions + * + * Copyright (c) 2018 MediaTek Inc. + * Author: KaiChieh Chuang + */ + +#ifndef _MT_6797_AFE_COMMON_H_ +#define _MT_6797_AFE_COMMON_H_ + +#include +#include +#include +#include "../common/mtk-base-afe.h" + +enum { + MT6797_MEMIF_DL1, + MT6797_MEMIF_DL2, + MT6797_MEMIF_DL3, + MT6797_MEMIF_VUL, + MT6797_MEMIF_AWB, + MT6797_MEMIF_VUL12, + MT6797_MEMIF_DAI, + MT6797_MEMIF_MOD_DAI, + MT6797_MEMIF_NUM, + MT6797_DAI_ADDA = MT6797_MEMIF_NUM, + MT6797_DAI_PCM_1, + MT6797_DAI_PCM_2, + MT6797_DAI_HOSTLESS_LPBK, + MT6797_DAI_HOSTLESS_SPEECH, + MT6797_DAI_NUM, +}; + +enum { + MT6797_IRQ_1, + MT6797_IRQ_2, + MT6797_IRQ_3, + MT6797_IRQ_4, + MT6797_IRQ_7, + MT6797_IRQ_NUM, +}; + +struct clk; + +struct mt6797_afe_private { + struct clk **clk; +}; + +unsigned int mt6797_general_rate_transform(struct device *dev, + unsigned int rate); +unsigned int mt6797_rate_transform(struct device *dev, + unsigned int rate, int aud_blk); + +/* dai register */ +int mt6797_dai_adda_register(struct mtk_base_afe *afe); +int mt6797_dai_pcm_register(struct mtk_base_afe *afe); +int mt6797_dai_hostless_register(struct mtk_base_afe *afe); +#endif diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c new file mode 100644 index 000000000..192f4d7b3 --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c @@ -0,0 +1,937 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Mediatek ALSA SoC AFE platform driver for 6797 +// +// Copyright (c) 2018 MediaTek Inc. +// Author: KaiChieh Chuang + +#include +#include +#include +#include +#include +#include + +#include "mt6797-afe-common.h" +#include "mt6797-afe-clk.h" +#include "mt6797-interconnection.h" +#include "mt6797-reg.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +enum { + MTK_AFE_RATE_8K = 0, + MTK_AFE_RATE_11K = 1, + MTK_AFE_RATE_12K = 2, + MTK_AFE_RATE_384K = 3, + MTK_AFE_RATE_16K = 4, + MTK_AFE_RATE_22K = 5, + MTK_AFE_RATE_24K = 6, + MTK_AFE_RATE_130K = 7, + MTK_AFE_RATE_32K = 8, + MTK_AFE_RATE_44K = 9, + MTK_AFE_RATE_48K = 10, + MTK_AFE_RATE_88K = 11, + MTK_AFE_RATE_96K = 12, + MTK_AFE_RATE_174K = 13, + MTK_AFE_RATE_192K = 14, + MTK_AFE_RATE_260K = 15, +}; + +enum { + MTK_AFE_DAI_MEMIF_RATE_8K = 0, + MTK_AFE_DAI_MEMIF_RATE_16K = 1, + MTK_AFE_DAI_MEMIF_RATE_32K = 2, +}; + +enum { + MTK_AFE_PCM_RATE_8K = 0, + MTK_AFE_PCM_RATE_16K = 1, + MTK_AFE_PCM_RATE_32K = 2, + MTK_AFE_PCM_RATE_48K = 3, +}; + +unsigned int mt6797_general_rate_transform(struct device *dev, + unsigned int rate) +{ + switch (rate) { + case 8000: + return MTK_AFE_RATE_8K; + case 11025: + return MTK_AFE_RATE_11K; + case 12000: + return MTK_AFE_RATE_12K; + case 16000: + return MTK_AFE_RATE_16K; + case 22050: + return MTK_AFE_RATE_22K; + case 24000: + return MTK_AFE_RATE_24K; + case 32000: + return MTK_AFE_RATE_32K; + case 44100: + return MTK_AFE_RATE_44K; + case 48000: + return MTK_AFE_RATE_48K; + case 88200: + return MTK_AFE_RATE_88K; + case 96000: + return MTK_AFE_RATE_96K; + case 130000: + return MTK_AFE_RATE_130K; + case 176400: + return MTK_AFE_RATE_174K; + case 192000: + return MTK_AFE_RATE_192K; + case 260000: + return MTK_AFE_RATE_260K; + default: + dev_warn(dev, "%s(), rate %u invalid, use %d!!!\n", + __func__, rate, MTK_AFE_RATE_48K); + return MTK_AFE_RATE_48K; + } +} + +static unsigned int dai_memif_rate_transform(struct device *dev, + unsigned int rate) +{ + switch (rate) { + case 8000: + return MTK_AFE_DAI_MEMIF_RATE_8K; + case 16000: + return MTK_AFE_DAI_MEMIF_RATE_16K; + case 32000: + return MTK_AFE_DAI_MEMIF_RATE_32K; + default: + dev_warn(dev, "%s(), rate %u invalid, use %d!!!\n", + __func__, rate, MTK_AFE_DAI_MEMIF_RATE_16K); + return MTK_AFE_DAI_MEMIF_RATE_16K; + } +} + +unsigned int mt6797_rate_transform(struct device *dev, + unsigned int rate, int aud_blk) +{ + switch (aud_blk) { + case MT6797_MEMIF_DAI: + case MT6797_MEMIF_MOD_DAI: + return dai_memif_rate_transform(dev, rate); + default: + return mt6797_general_rate_transform(dev, rate); + } +} + +static const struct snd_pcm_hardware mt6797_afe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 256, + .period_bytes_max = 4 * 48 * 1024, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 8 * 48 * 1024, + .fifo_size = 0, +}; + +static int mt6797_memif_fs(struct snd_pcm_substream *substream, + unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + int id = rtd->cpu_dai->id; + + return mt6797_rate_transform(afe->dev, rate, id); +} + +static int mt6797_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + + return mt6797_general_rate_transform(afe->dev, rate); +} + +#define MTK_PCM_RATES (SNDRV_PCM_RATE_8000_48000 |\ + SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000) + +#define MTK_PCM_DAI_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000) + +#define MTK_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mt6797_memif_dai_driver[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "DL1", + .id = MT6797_MEMIF_DL1, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, + { + .name = "DL2", + .id = MT6797_MEMIF_DL2, + .playback = { + .stream_name = "DL2", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, + { + .name = "DL3", + .id = MT6797_MEMIF_DL3, + .playback = { + .stream_name = "DL3", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, + { + .name = "UL1", + .id = MT6797_MEMIF_VUL12, + .capture = { + .stream_name = "UL1", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, + { + .name = "UL2", + .id = MT6797_MEMIF_AWB, + .capture = { + .stream_name = "UL2", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, + { + .name = "UL3", + .id = MT6797_MEMIF_VUL, + .capture = { + .stream_name = "UL3", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, + { + .name = "UL_MONO_1", + .id = MT6797_MEMIF_MOD_DAI, + .capture = { + .stream_name = "UL_MONO_1", + .channels_min = 1, + .channels_max = 1, + .rates = MTK_PCM_DAI_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, + { + .name = "UL_MONO_2", + .id = MT6797_MEMIF_DAI, + .capture = { + .stream_name = "UL_MONO_2", + .channels_min = 1, + .channels_max = 1, + .rates = MTK_PCM_DAI_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_afe_fe_ops, + }, +}; + +/* dma widget & routes*/ +static const struct snd_kcontrol_new memif_ul1_ch1_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN21, + I_ADDA_UL_CH1, 1, 0), +}; + +static const struct snd_kcontrol_new memif_ul1_ch2_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN22, + I_ADDA_UL_CH2, 1, 0), +}; + +static const struct snd_kcontrol_new memif_ul2_ch1_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN5, + I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH1", AFE_CONN5, + I_DL1_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH1", AFE_CONN5, + I_DL2_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH1", AFE_CONN5, + I_DL3_CH1, 1, 0), +}; + +static const struct snd_kcontrol_new memif_ul2_ch2_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN6, + I_ADDA_UL_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH2", AFE_CONN6, + I_DL1_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH2", AFE_CONN6, + I_DL2_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH2", AFE_CONN6, + I_DL3_CH2, 1, 0), +}; + +static const struct snd_kcontrol_new memif_ul3_ch1_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN9, + I_ADDA_UL_CH1, 1, 0), +}; + +static const struct snd_kcontrol_new memif_ul3_ch2_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN10, + I_ADDA_UL_CH2, 1, 0), +}; + +static const struct snd_kcontrol_new memif_ul_mono_1_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN12, + I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN12, + I_ADDA_UL_CH2, 1, 0), +}; + +static const struct snd_kcontrol_new memif_ul_mono_2_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN11, + I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN11, + I_ADDA_UL_CH2, 1, 0), +}; + +static const struct snd_soc_dapm_widget mt6797_memif_widgets[] = { + /* memif */ + SND_SOC_DAPM_MIXER("UL1_CH1", SND_SOC_NOPM, 0, 0, + memif_ul1_ch1_mix, ARRAY_SIZE(memif_ul1_ch1_mix)), + SND_SOC_DAPM_MIXER("UL1_CH2", SND_SOC_NOPM, 0, 0, + memif_ul1_ch2_mix, ARRAY_SIZE(memif_ul1_ch2_mix)), + + SND_SOC_DAPM_MIXER("UL2_CH1", SND_SOC_NOPM, 0, 0, + memif_ul2_ch1_mix, ARRAY_SIZE(memif_ul2_ch1_mix)), + SND_SOC_DAPM_MIXER("UL2_CH2", SND_SOC_NOPM, 0, 0, + memif_ul2_ch2_mix, ARRAY_SIZE(memif_ul2_ch2_mix)), + + SND_SOC_DAPM_MIXER("UL3_CH1", SND_SOC_NOPM, 0, 0, + memif_ul3_ch1_mix, ARRAY_SIZE(memif_ul3_ch1_mix)), + SND_SOC_DAPM_MIXER("UL3_CH2", SND_SOC_NOPM, 0, 0, + memif_ul3_ch2_mix, ARRAY_SIZE(memif_ul3_ch2_mix)), + + SND_SOC_DAPM_MIXER("UL_MONO_1_CH1", SND_SOC_NOPM, 0, 0, + memif_ul_mono_1_mix, + ARRAY_SIZE(memif_ul_mono_1_mix)), + + SND_SOC_DAPM_MIXER("UL_MONO_2_CH1", SND_SOC_NOPM, 0, 0, + memif_ul_mono_2_mix, + ARRAY_SIZE(memif_ul_mono_2_mix)), +}; + +static const struct snd_soc_dapm_route mt6797_memif_routes[] = { + /* capture */ + {"UL1", NULL, "UL1_CH1"}, + {"UL1", NULL, "UL1_CH2"}, + {"UL1_CH1", "ADDA_UL_CH1", "ADDA Capture"}, + {"UL1_CH2", "ADDA_UL_CH2", "ADDA Capture"}, + + {"UL2", NULL, "UL2_CH1"}, + {"UL2", NULL, "UL2_CH2"}, + {"UL2_CH1", "ADDA_UL_CH1", "ADDA Capture"}, + {"UL2_CH2", "ADDA_UL_CH2", "ADDA Capture"}, + + {"UL3", NULL, "UL3_CH1"}, + {"UL3", NULL, "UL3_CH2"}, + {"UL3_CH1", "ADDA_UL_CH1", "ADDA Capture"}, + {"UL3_CH2", "ADDA_UL_CH2", "ADDA Capture"}, + + {"UL_MONO_1", NULL, "UL_MONO_1_CH1"}, + {"UL_MONO_1_CH1", "ADDA_UL_CH1", "ADDA Capture"}, + {"UL_MONO_1_CH1", "ADDA_UL_CH2", "ADDA Capture"}, + + {"UL_MONO_2", NULL, "UL_MONO_2_CH1"}, + {"UL_MONO_2_CH1", "ADDA_UL_CH1", "ADDA Capture"}, + {"UL_MONO_2_CH1", "ADDA_UL_CH2", "ADDA Capture"}, +}; + +static const struct snd_soc_component_driver mt6797_afe_pcm_dai_component = { + .name = "mt6797-afe-pcm-dai", +}; + +static const struct mtk_base_memif_data memif_data[MT6797_MEMIF_NUM] = { + [MT6797_MEMIF_DL1] = { + .name = "DL1", + .id = MT6797_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = DL1_MODE_SFT, + .fs_maskbit = DL1_MODE_MASK, + .mono_reg = AFE_DAC_CON1, + .mono_shift = DL1_DATA_SFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = DL1_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = DL1_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, + [MT6797_MEMIF_DL2] = { + .name = "DL2", + .id = MT6797_MEMIF_DL2, + .reg_ofs_base = AFE_DL2_BASE, + .reg_ofs_cur = AFE_DL2_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = DL2_MODE_SFT, + .fs_maskbit = DL2_MODE_MASK, + .mono_reg = AFE_DAC_CON1, + .mono_shift = DL2_DATA_SFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = DL2_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = DL2_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, + [MT6797_MEMIF_DL3] = { + .name = "DL3", + .id = MT6797_MEMIF_DL3, + .reg_ofs_base = AFE_DL3_BASE, + .reg_ofs_cur = AFE_DL3_CUR, + .fs_reg = AFE_DAC_CON0, + .fs_shift = DL3_MODE_SFT, + .fs_maskbit = DL3_MODE_MASK, + .mono_reg = AFE_DAC_CON1, + .mono_shift = DL3_DATA_SFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = DL3_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = DL3_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, + [MT6797_MEMIF_VUL] = { + .name = "VUL", + .id = MT6797_MEMIF_VUL, + .reg_ofs_base = AFE_VUL_BASE, + .reg_ofs_cur = AFE_VUL_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = VUL_MODE_SFT, + .fs_maskbit = VUL_MODE_MASK, + .mono_reg = AFE_DAC_CON1, + .mono_shift = VUL_DATA_SFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = VUL_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = VUL_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, + [MT6797_MEMIF_AWB] = { + .name = "AWB", + .id = MT6797_MEMIF_AWB, + .reg_ofs_base = AFE_AWB_BASE, + .reg_ofs_cur = AFE_AWB_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = AWB_MODE_SFT, + .fs_maskbit = AWB_MODE_MASK, + .mono_reg = AFE_DAC_CON1, + .mono_shift = AWB_DATA_SFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = AWB_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = AWB_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, + [MT6797_MEMIF_VUL12] = { + .name = "VUL12", + .id = MT6797_MEMIF_VUL12, + .reg_ofs_base = AFE_VUL_D2_BASE, + .reg_ofs_cur = AFE_VUL_D2_CUR, + .fs_reg = AFE_DAC_CON0, + .fs_shift = VUL_DATA2_MODE_SFT, + .fs_maskbit = VUL_DATA2_MODE_MASK, + .mono_reg = AFE_DAC_CON0, + .mono_shift = VUL_DATA2_DATA_SFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = VUL_DATA2_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = VUL_DATA2_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, + [MT6797_MEMIF_DAI] = { + .name = "DAI", + .id = MT6797_MEMIF_DAI, + .reg_ofs_base = AFE_DAI_BASE, + .reg_ofs_cur = AFE_DAI_CUR, + .fs_reg = AFE_DAC_CON0, + .fs_shift = DAI_MODE_SFT, + .fs_maskbit = DAI_MODE_MASK, + .mono_reg = -1, + .mono_shift = 0, + .enable_reg = AFE_DAC_CON0, + .enable_shift = DAI_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = DAI_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, + [MT6797_MEMIF_MOD_DAI] = { + .name = "MOD_DAI", + .id = MT6797_MEMIF_MOD_DAI, + .reg_ofs_base = AFE_MOD_DAI_BASE, + .reg_ofs_cur = AFE_MOD_DAI_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = MOD_DAI_MODE_SFT, + .fs_maskbit = MOD_DAI_MODE_MASK, + .mono_reg = -1, + .mono_shift = 0, + .enable_reg = AFE_DAC_CON0, + .enable_shift = MOD_DAI_ON_SFT, + .hd_reg = AFE_MEMIF_HD_MODE, + .hd_shift = MOD_DAI_HD_SFT, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + .msb_reg = -1, + .msb_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[MT6797_IRQ_NUM] = { + [MT6797_IRQ_1] = { + .id = MT6797_IRQ_1, + .irq_cnt_reg = AFE_IRQ_MCU_CNT1, + .irq_cnt_shift = AFE_IRQ_MCU_CNT1_SFT, + .irq_cnt_maskbit = AFE_IRQ_MCU_CNT1_MASK, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = IRQ1_MCU_MODE_SFT, + .irq_fs_maskbit = IRQ1_MCU_MODE_MASK, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = IRQ1_MCU_ON_SFT, + .irq_clr_reg = AFE_IRQ_MCU_CLR, + .irq_clr_shift = IRQ1_MCU_CLR_SFT, + }, + [MT6797_IRQ_2] = { + .id = MT6797_IRQ_2, + .irq_cnt_reg = AFE_IRQ_MCU_CNT2, + .irq_cnt_shift = AFE_IRQ_MCU_CNT2_SFT, + .irq_cnt_maskbit = AFE_IRQ_MCU_CNT2_MASK, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = IRQ2_MCU_MODE_SFT, + .irq_fs_maskbit = IRQ2_MCU_MODE_MASK, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = IRQ2_MCU_ON_SFT, + .irq_clr_reg = AFE_IRQ_MCU_CLR, + .irq_clr_shift = IRQ2_MCU_CLR_SFT, + }, + [MT6797_IRQ_3] = { + .id = MT6797_IRQ_3, + .irq_cnt_reg = AFE_IRQ_MCU_CNT3, + .irq_cnt_shift = AFE_IRQ_MCU_CNT3_SFT, + .irq_cnt_maskbit = AFE_IRQ_MCU_CNT3_MASK, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = IRQ3_MCU_MODE_SFT, + .irq_fs_maskbit = IRQ3_MCU_MODE_MASK, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = IRQ3_MCU_ON_SFT, + .irq_clr_reg = AFE_IRQ_MCU_CLR, + .irq_clr_shift = IRQ3_MCU_CLR_SFT, + }, + [MT6797_IRQ_4] = { + .id = MT6797_IRQ_4, + .irq_cnt_reg = AFE_IRQ_MCU_CNT4, + .irq_cnt_shift = AFE_IRQ_MCU_CNT4_SFT, + .irq_cnt_maskbit = AFE_IRQ_MCU_CNT4_MASK, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = IRQ4_MCU_MODE_SFT, + .irq_fs_maskbit = IRQ4_MCU_MODE_MASK, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = IRQ4_MCU_ON_SFT, + .irq_clr_reg = AFE_IRQ_MCU_CLR, + .irq_clr_shift = IRQ4_MCU_CLR_SFT, + }, + [MT6797_IRQ_7] = { + .id = MT6797_IRQ_7, + .irq_cnt_reg = AFE_IRQ_MCU_CNT7, + .irq_cnt_shift = AFE_IRQ_MCU_CNT7_SFT, + .irq_cnt_maskbit = AFE_IRQ_MCU_CNT7_MASK, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = IRQ7_MCU_MODE_SFT, + .irq_fs_maskbit = IRQ7_MCU_MODE_MASK, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = IRQ7_MCU_ON_SFT, + .irq_clr_reg = AFE_IRQ_MCU_CLR, + .irq_clr_shift = IRQ7_MCU_CLR_SFT, + }, +}; + +static const struct regmap_config mt6797_afe_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_MAX_REGISTER, +}; + +static irqreturn_t mt6797_afe_irq_handler(int irq_id, void *dev) +{ + struct mtk_base_afe *afe = dev; + struct mtk_base_afe_irq *irq; + unsigned int status; + unsigned int mcu_en; + int ret; + int i; + irqreturn_t irq_ret = IRQ_HANDLED; + + /* get irq that is sent to MCU */ + regmap_read(afe->regmap, AFE_IRQ_MCU_EN, &mcu_en); + + ret = regmap_read(afe->regmap, AFE_IRQ_MCU_STATUS, &status); + if (ret || (status & mcu_en) == 0) { + dev_err(afe->dev, "%s(), irq status err, ret %d, status 0x%x, mcu_en 0x%x\n", + __func__, ret, status, mcu_en); + + /* only clear IRQ which is sent to MCU */ + status = mcu_en & AFE_IRQ_STATUS_BITS; + + irq_ret = IRQ_NONE; + goto err_irq; + } + + for (i = 0; i < MT6797_MEMIF_NUM; i++) { + struct mtk_base_afe_memif *memif = &afe->memif[i]; + + if (!memif->substream) + continue; + + irq = &afe->irqs[memif->irq_usage]; + + if (status & (1 << irq->irq_data->irq_en_shift)) + snd_pcm_period_elapsed(memif->substream); + } + +err_irq: + /* clear irq */ + regmap_write(afe->regmap, + AFE_IRQ_MCU_CLR, + status & AFE_IRQ_STATUS_BITS); + + return irq_ret; +} + +static int mt6797_afe_runtime_suspend(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + unsigned int afe_on_retm; + int retry = 0; + + /* disable AFE */ + regmap_update_bits(afe->regmap, AFE_DAC_CON0, AFE_ON_MASK_SFT, 0x0); + do { + regmap_read(afe->regmap, AFE_DAC_CON0, &afe_on_retm); + if ((afe_on_retm & AFE_ON_RETM_MASK_SFT) == 0) + break; + + udelay(10); + } while (++retry < 100000); + + if (retry) + dev_warn(afe->dev, "%s(), retry %d\n", __func__, retry); + + /* make sure all irq status are cleared */ + regmap_update_bits(afe->regmap, AFE_IRQ_MCU_CLR, 0xffff, 0xffff); + + return mt6797_afe_disable_clock(afe); +} + +static int mt6797_afe_runtime_resume(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + int ret; + + ret = mt6797_afe_enable_clock(afe); + if (ret) + return ret; + + /* irq signal to mcu only */ + regmap_write(afe->regmap, AFE_IRQ_MCU_EN, AFE_IRQ_MCU_EN_MASK_SFT); + + /* force all memif use normal mode */ + regmap_update_bits(afe->regmap, AFE_MEMIF_HDALIGN, + 0x7ff << 16, 0x7ff << 16); + /* force cpu use normal mode when access sram data */ + regmap_update_bits(afe->regmap, AFE_MEMIF_MSB, + CPU_COMPACT_MODE_MASK_SFT, 0); + /* force cpu use 8_24 format when writing 32bit data */ + regmap_update_bits(afe->regmap, AFE_MEMIF_MSB, + CPU_HD_ALIGN_MASK_SFT, 0); + + /* set all output port to 24bit */ + regmap_update_bits(afe->regmap, AFE_CONN_24BIT, + 0x3fffffff, 0x3fffffff); + + /* enable AFE */ + regmap_update_bits(afe->regmap, AFE_DAC_CON0, + AFE_ON_MASK_SFT, + 0x1 << AFE_ON_SFT); + + return 0; +} + +static int mt6797_afe_component_probe(struct snd_soc_component *component) +{ + return mtk_afe_add_sub_dai_control(component); +} + +static const struct snd_soc_component_driver mt6797_afe_component = { + .name = AFE_PCM_NAME, + .ops = &mtk_afe_pcm_ops, + .pcm_new = mtk_afe_pcm_new, + .pcm_free = mtk_afe_pcm_free, + .probe = mt6797_afe_component_probe, +}; + +static int mt6797_dai_memif_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = mt6797_memif_dai_driver; + dai->num_dai_drivers = ARRAY_SIZE(mt6797_memif_dai_driver); + + dai->dapm_widgets = mt6797_memif_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(mt6797_memif_widgets); + dai->dapm_routes = mt6797_memif_routes; + dai->num_dapm_routes = ARRAY_SIZE(mt6797_memif_routes); + return 0; +} + +typedef int (*dai_register_cb)(struct mtk_base_afe *); +static const dai_register_cb dai_register_cbs[] = { + mt6797_dai_adda_register, + mt6797_dai_pcm_register, + mt6797_dai_hostless_register, + mt6797_dai_memif_register, +}; + +static int mt6797_afe_pcm_dev_probe(struct platform_device *pdev) +{ + struct mtk_base_afe *afe; + struct mt6797_afe_private *afe_priv; + struct resource *res; + struct device *dev; + int i, irq_id, ret; + + afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + + afe_priv = afe->platform_priv; + afe->dev = &pdev->dev; + dev = afe->dev; + + /* initial audio related clock */ + ret = mt6797_init_clock(afe); + if (ret) { + dev_err(dev, "init clock error\n"); + return ret; + } + + /* regmap init */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + afe->base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &mt6797_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + /* init memif */ + afe->memif_size = MT6797_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + for (i = 0; i < afe->memif_size; i++) { + afe->memif[i].data = &memif_data[i]; + afe->memif[i].irq_usage = -1; + } + + mutex_init(&afe->irq_alloc_lock); + + /* irq initialize */ + afe->irqs_size = MT6797_IRQ_NUM; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + /* request irq */ + irq_id = platform_get_irq(pdev, 0); + if (!irq_id) { + dev_err(dev, "%s no irq found\n", dev->of_node->name); + return -ENXIO; + } + ret = devm_request_irq(dev, irq_id, mt6797_afe_irq_handler, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) { + dev_err(dev, "could not request_irq for asys-isr\n"); + return ret; + } + + /* init sub_dais */ + INIT_LIST_HEAD(&afe->sub_dais); + + for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) { + ret = dai_register_cbs[i](afe); + if (ret) { + dev_warn(afe->dev, "dai register i %d fail, ret %d\n", + i, ret); + return ret; + } + } + + /* init dai_driver and component_driver */ + ret = mtk_afe_combine_sub_dai(afe); + if (ret) { + dev_warn(afe->dev, "mtk_afe_combine_sub_dai fail, ret %d\n", + ret); + return ret; + } + + afe->mtk_afe_hardware = &mt6797_afe_hardware; + afe->memif_fs = mt6797_memif_fs; + afe->irq_fs = mt6797_irq_fs; + + afe->runtime_resume = mt6797_afe_runtime_resume; + afe->runtime_suspend = mt6797_afe_runtime_suspend; + + platform_set_drvdata(pdev, afe); + + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) + goto err_pm_disable; + pm_runtime_get_sync(&pdev->dev); + + /* register component */ + ret = devm_snd_soc_register_component(dev, &mt6797_afe_component, + NULL, 0); + if (ret) { + dev_warn(dev, "err_platform\n"); + goto err_pm_disable; + } + + ret = devm_snd_soc_register_component(afe->dev, + &mt6797_afe_pcm_dai_component, + afe->dai_drivers, + afe->num_dai_drivers); + if (ret) { + dev_warn(dev, "err_dai_component\n"); + goto err_pm_disable; + } + + return 0; + +err_pm_disable: + pm_runtime_disable(dev); + + return ret; +} + +static int mt6797_afe_pcm_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + mt6797_afe_runtime_suspend(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); + + return 0; +} + +static const struct of_device_id mt6797_afe_pcm_dt_match[] = { + { .compatible = "mediatek,mt6797-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt6797_afe_pcm_dt_match); + +static const struct dev_pm_ops mt6797_afe_pm_ops = { + SET_RUNTIME_PM_OPS(mt6797_afe_runtime_suspend, + mt6797_afe_runtime_resume, NULL) +}; + +static struct platform_driver mt6797_afe_pcm_driver = { + .driver = { + .name = "mt6797-audio", + .of_match_table = mt6797_afe_pcm_dt_match, +#ifdef CONFIG_PM + .pm = &mt6797_afe_pm_ops, +#endif + }, + .probe = mt6797_afe_pcm_dev_probe, + .remove = mt6797_afe_pcm_dev_remove, +}; + +module_platform_driver(mt6797_afe_pcm_driver); + +MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver for 6797"); +MODULE_AUTHOR("KaiChieh Chuang "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mediatek/mt6797/mt6797-dai-adda.c b/sound/soc/mediatek/mt6797/mt6797-dai-adda.c new file mode 100644 index 000000000..0ac6409c6 --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-dai-adda.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// MediaTek ALSA SoC Audio DAI ADDA Control +// +// Copyright (c) 2018 MediaTek Inc. +// Author: KaiChieh Chuang + +#include +#include +#include "mt6797-afe-common.h" +#include "mt6797-interconnection.h" +#include "mt6797-reg.h" + +enum { + MTK_AFE_ADDA_DL_RATE_8K = 0, + MTK_AFE_ADDA_DL_RATE_11K = 1, + MTK_AFE_ADDA_DL_RATE_12K = 2, + MTK_AFE_ADDA_DL_RATE_16K = 3, + MTK_AFE_ADDA_DL_RATE_22K = 4, + MTK_AFE_ADDA_DL_RATE_24K = 5, + MTK_AFE_ADDA_DL_RATE_32K = 6, + MTK_AFE_ADDA_DL_RATE_44K = 7, + MTK_AFE_ADDA_DL_RATE_48K = 8, + MTK_AFE_ADDA_DL_RATE_96K = 9, + MTK_AFE_ADDA_DL_RATE_192K = 10, +}; + +enum { + MTK_AFE_ADDA_UL_RATE_8K = 0, + MTK_AFE_ADDA_UL_RATE_16K = 1, + MTK_AFE_ADDA_UL_RATE_32K = 2, + MTK_AFE_ADDA_UL_RATE_48K = 3, + MTK_AFE_ADDA_UL_RATE_96K = 4, + MTK_AFE_ADDA_UL_RATE_192K = 5, + MTK_AFE_ADDA_UL_RATE_48K_HD = 6, +}; + +static unsigned int adda_dl_rate_transform(struct mtk_base_afe *afe, + unsigned int rate) +{ + switch (rate) { + case 8000: + return MTK_AFE_ADDA_DL_RATE_8K; + case 11025: + return MTK_AFE_ADDA_DL_RATE_11K; + case 12000: + return MTK_AFE_ADDA_DL_RATE_12K; + case 16000: + return MTK_AFE_ADDA_DL_RATE_16K; + case 22050: + return MTK_AFE_ADDA_DL_RATE_22K; + case 24000: + return MTK_AFE_ADDA_DL_RATE_24K; + case 32000: + return MTK_AFE_ADDA_DL_RATE_32K; + case 44100: + return MTK_AFE_ADDA_DL_RATE_44K; + case 48000: + return MTK_AFE_ADDA_DL_RATE_48K; + case 96000: + return MTK_AFE_ADDA_DL_RATE_96K; + case 192000: + return MTK_AFE_ADDA_DL_RATE_192K; + default: + dev_warn(afe->dev, "%s(), rate %d invalid, use 48kHz!!!\n", + __func__, rate); + return MTK_AFE_ADDA_DL_RATE_48K; + } +} + +static unsigned int adda_ul_rate_transform(struct mtk_base_afe *afe, + unsigned int rate) +{ + switch (rate) { + case 8000: + return MTK_AFE_ADDA_UL_RATE_8K; + case 16000: + return MTK_AFE_ADDA_UL_RATE_16K; + case 32000: + return MTK_AFE_ADDA_UL_RATE_32K; + case 48000: + return MTK_AFE_ADDA_UL_RATE_48K; + case 96000: + return MTK_AFE_ADDA_UL_RATE_96K; + case 192000: + return MTK_AFE_ADDA_UL_RATE_192K; + default: + dev_warn(afe->dev, "%s(), rate %d invalid, use 48kHz!!!\n", + __func__, rate); + return MTK_AFE_ADDA_UL_RATE_48K; + } +} + +/* dai component */ +static const struct snd_kcontrol_new mtk_adda_dl_ch1_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH1", AFE_CONN3, I_DL1_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH1", AFE_CONN3, I_DL2_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH1", AFE_CONN3, I_DL3_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN3, + I_ADDA_UL_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN3, + I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("PCM_1_CAP_CH1", AFE_CONN3, + I_PCM_1_CAP_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("PCM_2_CAP_CH1", AFE_CONN3, + I_PCM_2_CAP_CH1, 1, 0), +}; + +static const struct snd_kcontrol_new mtk_adda_dl_ch2_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH1", AFE_CONN4, I_DL1_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH2", AFE_CONN4, I_DL1_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH1", AFE_CONN4, I_DL2_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH2", AFE_CONN4, I_DL2_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH1", AFE_CONN4, I_DL3_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH2", AFE_CONN4, I_DL3_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN4, + I_ADDA_UL_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN4, + I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("PCM_1_CAP_CH1", AFE_CONN4, + I_PCM_1_CAP_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("PCM_2_CAP_CH1", AFE_CONN4, + I_PCM_2_CAP_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("PCM_1_CAP_CH2", AFE_CONN4, + I_PCM_1_CAP_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("PCM_2_CAP_CH2", AFE_CONN4, + I_PCM_2_CAP_CH2, 1, 0), +}; + +static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + + dev_dbg(afe->dev, "%s(), name %s, event 0x%x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_POST_PMD: + /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ + usleep_range(125, 135); + break; + default: + break; + } + + return 0; +} + +enum { + SUPPLY_SEQ_AUD_TOP_PDN, + SUPPLY_SEQ_ADDA_AFE_ON, + SUPPLY_SEQ_ADDA_DL_ON, + SUPPLY_SEQ_ADDA_UL_ON, +}; + +static const struct snd_soc_dapm_widget mtk_dai_adda_widgets[] = { + /* adda */ + SND_SOC_DAPM_MIXER("ADDA_DL_CH1", SND_SOC_NOPM, 0, 0, + mtk_adda_dl_ch1_mix, + ARRAY_SIZE(mtk_adda_dl_ch1_mix)), + SND_SOC_DAPM_MIXER("ADDA_DL_CH2", SND_SOC_NOPM, 0, 0, + mtk_adda_dl_ch2_mix, + ARRAY_SIZE(mtk_adda_dl_ch2_mix)), + + SND_SOC_DAPM_SUPPLY_S("ADDA Enable", SUPPLY_SEQ_ADDA_AFE_ON, + AFE_ADDA_UL_DL_CON0, ADDA_AFE_ON_SFT, 0, + NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("ADDA Playback Enable", SUPPLY_SEQ_ADDA_DL_ON, + AFE_ADDA_DL_SRC2_CON0, + DL_2_SRC_ON_TMP_CTL_PRE_SFT, 0, + NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("ADDA Capture Enable", SUPPLY_SEQ_ADDA_UL_ON, + AFE_ADDA_UL_SRC_CON0, + UL_SRC_ON_TMP_CTL_SFT, 0, + mtk_adda_ul_event, + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_SUPPLY_S("aud_dac_clk", SUPPLY_SEQ_AUD_TOP_PDN, + AUDIO_TOP_CON0, PDN_DAC_SFT, 1, + NULL, 0), + SND_SOC_DAPM_SUPPLY_S("aud_dac_predis_clk", SUPPLY_SEQ_AUD_TOP_PDN, + AUDIO_TOP_CON0, PDN_DAC_PREDIS_SFT, 1, + NULL, 0), + + SND_SOC_DAPM_SUPPLY_S("aud_adc_clk", SUPPLY_SEQ_AUD_TOP_PDN, + AUDIO_TOP_CON0, PDN_ADC_SFT, 1, + NULL, 0), + + SND_SOC_DAPM_CLOCK_SUPPLY("mtkaif_26m_clk"), +}; + +static const struct snd_soc_dapm_route mtk_dai_adda_routes[] = { + /* playback */ + {"ADDA_DL_CH1", "DL1_CH1", "DL1"}, + {"ADDA_DL_CH2", "DL1_CH1", "DL1"}, + {"ADDA_DL_CH2", "DL1_CH2", "DL1"}, + + {"ADDA_DL_CH1", "DL2_CH1", "DL2"}, + {"ADDA_DL_CH2", "DL2_CH1", "DL2"}, + {"ADDA_DL_CH2", "DL2_CH2", "DL2"}, + + {"ADDA_DL_CH1", "DL3_CH1", "DL3"}, + {"ADDA_DL_CH2", "DL3_CH1", "DL3"}, + {"ADDA_DL_CH2", "DL3_CH2", "DL3"}, + + {"ADDA Playback", NULL, "ADDA_DL_CH1"}, + {"ADDA Playback", NULL, "ADDA_DL_CH2"}, + + /* adda enable */ + {"ADDA Playback", NULL, "ADDA Enable"}, + {"ADDA Playback", NULL, "ADDA Playback Enable"}, + {"ADDA Capture", NULL, "ADDA Enable"}, + {"ADDA Capture", NULL, "ADDA Capture Enable"}, + + /* clk */ + {"ADDA Playback", NULL, "mtkaif_26m_clk"}, + {"ADDA Playback", NULL, "aud_dac_clk"}, + {"ADDA Playback", NULL, "aud_dac_predis_clk"}, + + {"ADDA Capture", NULL, "mtkaif_26m_clk"}, + {"ADDA Capture", NULL, "aud_adc_clk"}, +}; + +/* dai ops */ +static int mtk_dai_adda_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + + dev_dbg(afe->dev, "%s(), id %d, stream %d, rate %d\n", + __func__, dai->id, substream->stream, rate); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + unsigned int dl_src2_con0 = 0; + unsigned int dl_src2_con1 = 0; + + /* clean predistortion */ + regmap_write(afe->regmap, AFE_ADDA_PREDIS_CON0, 0); + regmap_write(afe->regmap, AFE_ADDA_PREDIS_CON1, 0); + + /* set input sampling rate */ + dl_src2_con0 = adda_dl_rate_transform(afe, rate) << 28; + + /* set output mode */ + switch (rate) { + case 192000: + dl_src2_con0 |= (0x1 << 24); /* UP_SAMPLING_RATE_X2 */ + dl_src2_con0 |= 1 << 14; + break; + case 96000: + dl_src2_con0 |= (0x2 << 24); /* UP_SAMPLING_RATE_X4 */ + dl_src2_con0 |= 1 << 14; + break; + default: + dl_src2_con0 |= (0x3 << 24); /* UP_SAMPLING_RATE_X8 */ + break; + } + + /* turn off mute function */ + dl_src2_con0 |= (0x03 << 11); + + /* set voice input data if input sample rate is 8k or 16k */ + if (rate == 8000 || rate == 16000) + dl_src2_con0 |= 0x01 << 5; + + if (rate < 96000) { + /* SA suggest apply -0.3db to audio/speech path */ + dl_src2_con1 = 0xf74f0000; + } else { + /* SA suggest apply -0.3db to audio/speech path + * with DL gain set to half, + * 0xFFFF = 0dB -> 0x8000 = 0dB when 96k, 192k + */ + dl_src2_con1 = 0x7ba70000; + } + + /* turn on down-link gain */ + dl_src2_con0 = dl_src2_con0 | (0x01 << 1); + + regmap_write(afe->regmap, AFE_ADDA_DL_SRC2_CON0, dl_src2_con0); + regmap_write(afe->regmap, AFE_ADDA_DL_SRC2_CON1, dl_src2_con1); + } else { + unsigned int voice_mode = 0; + unsigned int ul_src_con0 = 0; /* default value */ + + /* Using Internal ADC */ + regmap_update_bits(afe->regmap, + AFE_ADDA_TOP_CON0, + 0x1 << 0, + 0x0 << 0); + + voice_mode = adda_ul_rate_transform(afe, rate); + + ul_src_con0 |= (voice_mode << 17) & (0x7 << 17); + + /* up8x txif sat on */ + regmap_write(afe->regmap, AFE_ADDA_NEWIF_CFG0, 0x03F87201); + + if (rate >= 96000) { /* hires */ + /* use hires format [1 0 23] */ + regmap_update_bits(afe->regmap, + AFE_ADDA_NEWIF_CFG0, + 0x1 << 5, + 0x1 << 5); + + regmap_update_bits(afe->regmap, + AFE_ADDA_NEWIF_CFG2, + 0xf << 28, + voice_mode << 28); + } else { /* normal 8~48k */ + /* use fixed 260k anc path */ + regmap_update_bits(afe->regmap, + AFE_ADDA_NEWIF_CFG2, + 0xf << 28, + 8 << 28); + + /* ul_use_cic_out */ + ul_src_con0 |= 0x1 << 20; + } + + regmap_update_bits(afe->regmap, + AFE_ADDA_NEWIF_CFG2, + 0xf << 28, + 8 << 28); + + regmap_update_bits(afe->regmap, + AFE_ADDA_UL_SRC_CON0, + 0xfffffffe, + ul_src_con0); + } + + return 0; +} + +static const struct snd_soc_dai_ops mtk_dai_adda_ops = { + .hw_params = mtk_dai_adda_hw_params, +}; + +/* dai driver */ +#define MTK_ADDA_PLAYBACK_RATES (SNDRV_PCM_RATE_8000_48000 |\ + SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_192000) + +#define MTK_ADDA_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_192000) + +#define MTK_ADDA_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mtk_dai_adda_driver[] = { + { + .name = "ADDA", + .id = MT6797_DAI_ADDA, + .playback = { + .stream_name = "ADDA Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_ADDA_PLAYBACK_RATES, + .formats = MTK_ADDA_FORMATS, + }, + .capture = { + .stream_name = "ADDA Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_ADDA_CAPTURE_RATES, + .formats = MTK_ADDA_FORMATS, + }, + .ops = &mtk_dai_adda_ops, + }, +}; + +int mt6797_dai_adda_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = mtk_dai_adda_driver; + dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_adda_driver); + + dai->dapm_widgets = mtk_dai_adda_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_adda_widgets); + dai->dapm_routes = mtk_dai_adda_routes; + dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_adda_routes); + return 0; +} diff --git a/sound/soc/mediatek/mt6797/mt6797-dai-hostless.c b/sound/soc/mediatek/mt6797/mt6797-dai-hostless.c new file mode 100644 index 000000000..ed23e6a53 --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-dai-hostless.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// MediaTek ALSA SoC Audio DAI Hostless Control +// +// Copyright (c) 2018 MediaTek Inc. +// Author: KaiChieh Chuang + +#include "mt6797-afe-common.h" + +/* dai component */ +static const struct snd_soc_dapm_route mtk_dai_hostless_routes[] = { + /* Hostless ADDA Loopback */ + {"ADDA_DL_CH1", "ADDA_UL_CH1", "Hostless LPBK DL"}, + {"ADDA_DL_CH1", "ADDA_UL_CH2", "Hostless LPBK DL"}, + {"ADDA_DL_CH2", "ADDA_UL_CH1", "Hostless LPBK DL"}, + {"ADDA_DL_CH2", "ADDA_UL_CH2", "Hostless LPBK DL"}, + {"Hostless LPBK UL", NULL, "ADDA Capture"}, + + /* Hostless Speech */ + {"ADDA_DL_CH1", "PCM_1_CAP_CH1", "Hostless Speech DL"}, + {"ADDA_DL_CH2", "PCM_1_CAP_CH1", "Hostless Speech DL"}, + {"ADDA_DL_CH2", "PCM_1_CAP_CH2", "Hostless Speech DL"}, + {"ADDA_DL_CH1", "PCM_2_CAP_CH1", "Hostless Speech DL"}, + {"ADDA_DL_CH2", "PCM_2_CAP_CH1", "Hostless Speech DL"}, + {"ADDA_DL_CH2", "PCM_2_CAP_CH2", "Hostless Speech DL"}, + {"PCM_1_PB_CH1", "ADDA_UL_CH1", "Hostless Speech DL"}, + {"PCM_1_PB_CH2", "ADDA_UL_CH2", "Hostless Speech DL"}, + {"PCM_2_PB_CH1", "ADDA_UL_CH1", "Hostless Speech DL"}, + {"PCM_2_PB_CH2", "ADDA_UL_CH2", "Hostless Speech DL"}, + + {"Hostless Speech UL", NULL, "PCM 1 Capture"}, + {"Hostless Speech UL", NULL, "PCM 2 Capture"}, + {"Hostless Speech UL", NULL, "ADDA Capture"}, +}; + +/* dai ops */ +static int mtk_dai_hostless_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + return snd_soc_set_runtime_hwparams(substream, afe->mtk_afe_hardware); +} + +static const struct snd_soc_dai_ops mtk_dai_hostless_ops = { + .startup = mtk_dai_hostless_startup, +}; + +/* dai driver */ +#define MTK_HOSTLESS_RATES (SNDRV_PCM_RATE_8000_48000 |\ + SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000) + +#define MTK_HOSTLESS_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mtk_dai_hostless_driver[] = { + { + .name = "Hostless LPBK DAI", + .id = MT6797_DAI_HOSTLESS_LPBK, + .playback = { + .stream_name = "Hostless LPBK DL", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_HOSTLESS_RATES, + .formats = MTK_HOSTLESS_FORMATS, + }, + .capture = { + .stream_name = "Hostless LPBK UL", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_HOSTLESS_RATES, + .formats = MTK_HOSTLESS_FORMATS, + }, + .ops = &mtk_dai_hostless_ops, + }, + { + .name = "Hostless Speech DAI", + .id = MT6797_DAI_HOSTLESS_SPEECH, + .playback = { + .stream_name = "Hostless Speech DL", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_HOSTLESS_RATES, + .formats = MTK_HOSTLESS_FORMATS, + }, + .capture = { + .stream_name = "Hostless Speech UL", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_HOSTLESS_RATES, + .formats = MTK_HOSTLESS_FORMATS, + }, + .ops = &mtk_dai_hostless_ops, + }, +}; + +int mt6797_dai_hostless_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = mtk_dai_hostless_driver; + dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_hostless_driver); + + dai->dapm_routes = mtk_dai_hostless_routes; + dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_hostless_routes); + + return 0; +} diff --git a/sound/soc/mediatek/mt6797/mt6797-dai-pcm.c b/sound/soc/mediatek/mt6797/mt6797-dai-pcm.c new file mode 100644 index 000000000..3136f0bc7 --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-dai-pcm.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// MediaTek ALSA SoC Audio DAI I2S Control +// +// Copyright (c) 2018 MediaTek Inc. +// Author: KaiChieh Chuang + +#include +#include +#include "mt6797-afe-common.h" +#include "mt6797-interconnection.h" +#include "mt6797-reg.h" + +enum AUD_TX_LCH_RPT { + AUD_TX_LCH_RPT_NO_REPEAT = 0, + AUD_TX_LCH_RPT_REPEAT = 1 +}; + +enum AUD_VBT_16K_MODE { + AUD_VBT_16K_MODE_DISABLE = 0, + AUD_VBT_16K_MODE_ENABLE = 1 +}; + +enum AUD_EXT_MODEM { + AUD_EXT_MODEM_SELECT_INTERNAL = 0, + AUD_EXT_MODEM_SELECT_EXTERNAL = 1 +}; + +enum AUD_PCM_SYNC_TYPE { + /* bck sync length = 1 */ + AUD_PCM_ONE_BCK_CYCLE_SYNC = 0, + /* bck sync length = PCM_INTF_CON1[9:13] */ + AUD_PCM_EXTENDED_BCK_CYCLE_SYNC = 1 +}; + +enum AUD_BT_MODE { + AUD_BT_MODE_DUAL_MIC_ON_TX = 0, + AUD_BT_MODE_SINGLE_MIC_ON_TX = 1 +}; + +enum AUD_PCM_AFIFO_SRC { + /* slave mode & external modem uses different crystal */ + AUD_PCM_AFIFO_ASRC = 0, + /* slave mode & external modem uses the same crystal */ + AUD_PCM_AFIFO_AFIFO = 1 +}; + +enum AUD_PCM_CLOCK_SOURCE { + AUD_PCM_CLOCK_MASTER_MODE = 0, + AUD_PCM_CLOCK_SLAVE_MODE = 1 +}; + +enum AUD_PCM_WLEN { + AUD_PCM_WLEN_PCM_32_BCK_CYCLES = 0, + AUD_PCM_WLEN_PCM_64_BCK_CYCLES = 1 +}; + +enum AUD_PCM_MODE { + AUD_PCM_MODE_PCM_MODE_8K = 0, + AUD_PCM_MODE_PCM_MODE_16K = 1, + AUD_PCM_MODE_PCM_MODE_32K = 2, + AUD_PCM_MODE_PCM_MODE_48K = 3, +}; + +enum AUD_PCM_FMT { + AUD_PCM_FMT_I2S = 0, + AUD_PCM_FMT_EIAJ = 1, + AUD_PCM_FMT_PCM_MODE_A = 2, + AUD_PCM_FMT_PCM_MODE_B = 3 +}; + +enum AUD_BCLK_OUT_INV { + AUD_BCLK_OUT_INV_NO_INVERSE = 0, + AUD_BCLK_OUT_INV_INVERSE = 1 +}; + +enum AUD_PCM_EN { + AUD_PCM_EN_DISABLE = 0, + AUD_PCM_EN_ENABLE = 1 +}; + +/* dai component */ +static const struct snd_kcontrol_new mtk_pcm_1_playback_ch1_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN7, + I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH1", AFE_CONN7, + I_DL2_CH1, 1, 0), +}; + +static const struct snd_kcontrol_new mtk_pcm_1_playback_ch2_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN8, + I_ADDA_UL_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH2", AFE_CONN8, + I_DL2_CH2, 1, 0), +}; + +static const struct snd_kcontrol_new mtk_pcm_1_playback_ch4_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH1", AFE_CONN27, + I_DL1_CH1, 1, 0), +}; + +static const struct snd_kcontrol_new mtk_pcm_2_playback_ch1_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN17, + I_ADDA_UL_CH1, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH1", AFE_CONN17, + I_DL2_CH1, 1, 0), +}; + +static const struct snd_kcontrol_new mtk_pcm_2_playback_ch2_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN18, + I_ADDA_UL_CH2, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH2", AFE_CONN18, + I_DL2_CH2, 1, 0), +}; + +static const struct snd_kcontrol_new mtk_pcm_2_playback_ch4_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH1", AFE_CONN24, + I_DL1_CH1, 1, 0), +}; + +static const struct snd_soc_dapm_widget mtk_dai_pcm_widgets[] = { + /* inter-connections */ + SND_SOC_DAPM_MIXER("PCM_1_PB_CH1", SND_SOC_NOPM, 0, 0, + mtk_pcm_1_playback_ch1_mix, + ARRAY_SIZE(mtk_pcm_1_playback_ch1_mix)), + SND_SOC_DAPM_MIXER("PCM_1_PB_CH2", SND_SOC_NOPM, 0, 0, + mtk_pcm_1_playback_ch2_mix, + ARRAY_SIZE(mtk_pcm_1_playback_ch2_mix)), + SND_SOC_DAPM_MIXER("PCM_1_PB_CH4", SND_SOC_NOPM, 0, 0, + mtk_pcm_1_playback_ch4_mix, + ARRAY_SIZE(mtk_pcm_1_playback_ch4_mix)), + SND_SOC_DAPM_MIXER("PCM_2_PB_CH1", SND_SOC_NOPM, 0, 0, + mtk_pcm_2_playback_ch1_mix, + ARRAY_SIZE(mtk_pcm_2_playback_ch1_mix)), + SND_SOC_DAPM_MIXER("PCM_2_PB_CH2", SND_SOC_NOPM, 0, 0, + mtk_pcm_2_playback_ch2_mix, + ARRAY_SIZE(mtk_pcm_2_playback_ch2_mix)), + SND_SOC_DAPM_MIXER("PCM_2_PB_CH4", SND_SOC_NOPM, 0, 0, + mtk_pcm_2_playback_ch4_mix, + ARRAY_SIZE(mtk_pcm_2_playback_ch4_mix)), + + SND_SOC_DAPM_SUPPLY("PCM_1_EN", PCM_INTF_CON1, PCM_EN_SFT, 0, + NULL, 0), + + SND_SOC_DAPM_SUPPLY("PCM_2_EN", PCM2_INTF_CON, PCM2_EN_SFT, 0, + NULL, 0), + + SND_SOC_DAPM_INPUT("MD1_TO_AFE"), + SND_SOC_DAPM_INPUT("MD2_TO_AFE"), + SND_SOC_DAPM_OUTPUT("AFE_TO_MD1"), + SND_SOC_DAPM_OUTPUT("AFE_TO_MD2"), +}; + +static const struct snd_soc_dapm_route mtk_dai_pcm_routes[] = { + {"PCM 1 Playback", NULL, "PCM_1_PB_CH1"}, + {"PCM 1 Playback", NULL, "PCM_1_PB_CH2"}, + {"PCM 1 Playback", NULL, "PCM_1_PB_CH4"}, + {"PCM 2 Playback", NULL, "PCM_2_PB_CH1"}, + {"PCM 2 Playback", NULL, "PCM_2_PB_CH2"}, + {"PCM 2 Playback", NULL, "PCM_2_PB_CH4"}, + + {"PCM 1 Playback", NULL, "PCM_1_EN"}, + {"PCM 2 Playback", NULL, "PCM_2_EN"}, + {"PCM 1 Capture", NULL, "PCM_1_EN"}, + {"PCM 2 Capture", NULL, "PCM_2_EN"}, + + {"AFE_TO_MD1", NULL, "PCM 2 Playback"}, + {"AFE_TO_MD2", NULL, "PCM 1 Playback"}, + {"PCM 2 Capture", NULL, "MD1_TO_AFE"}, + {"PCM 1 Capture", NULL, "MD2_TO_AFE"}, + + {"PCM_1_PB_CH1", "DL2_CH1", "DL2"}, + {"PCM_1_PB_CH2", "DL2_CH2", "DL2"}, + {"PCM_1_PB_CH4", "DL1_CH1", "DL1"}, + {"PCM_2_PB_CH1", "DL2_CH1", "DL2"}, + {"PCM_2_PB_CH2", "DL2_CH2", "DL2"}, + {"PCM_2_PB_CH4", "DL1_CH1", "DL1"}, +}; + +/* dai ops */ +static int mtk_dai_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + unsigned int rate_reg = mt6797_rate_transform(afe->dev, rate, dai->id); + unsigned int pcm_con = 0; + + dev_dbg(afe->dev, "%s(), id %d, stream %d, rate %d, rate_reg %d, widget active p %d, c %d\n", + __func__, + dai->id, + substream->stream, + rate, + rate_reg, + dai->playback_widget->active, + dai->capture_widget->active); + + if (dai->playback_widget->active || dai->capture_widget->active) + return 0; + + switch (dai->id) { + case MT6797_DAI_PCM_1: + pcm_con |= AUD_BCLK_OUT_INV_NO_INVERSE << PCM_BCLK_OUT_INV_SFT; + pcm_con |= AUD_TX_LCH_RPT_NO_REPEAT << PCM_TX_LCH_RPT_SFT; + pcm_con |= AUD_VBT_16K_MODE_DISABLE << PCM_VBT_16K_MODE_SFT; + pcm_con |= AUD_EXT_MODEM_SELECT_INTERNAL << PCM_EXT_MODEM_SFT; + pcm_con |= 0 << PCM_SYNC_LENGTH_SFT; + pcm_con |= AUD_PCM_ONE_BCK_CYCLE_SYNC << PCM_SYNC_TYPE_SFT; + pcm_con |= AUD_BT_MODE_DUAL_MIC_ON_TX << PCM_BT_MODE_SFT; + pcm_con |= AUD_PCM_AFIFO_AFIFO << PCM_BYP_ASRC_SFT; + pcm_con |= AUD_PCM_CLOCK_SLAVE_MODE << PCM_SLAVE_SFT; + pcm_con |= rate_reg << PCM_MODE_SFT; + pcm_con |= AUD_PCM_FMT_PCM_MODE_B << PCM_FMT_SFT; + + regmap_update_bits(afe->regmap, PCM_INTF_CON1, + 0xfffffffe, pcm_con); + break; + case MT6797_DAI_PCM_2: + pcm_con |= AUD_TX_LCH_RPT_NO_REPEAT << PCM2_TX_LCH_RPT_SFT; + pcm_con |= AUD_VBT_16K_MODE_DISABLE << PCM2_VBT_16K_MODE_SFT; + pcm_con |= AUD_BT_MODE_DUAL_MIC_ON_TX << PCM2_BT_MODE_SFT; + pcm_con |= AUD_PCM_AFIFO_AFIFO << PCM2_AFIFO_SFT; + pcm_con |= AUD_PCM_WLEN_PCM_32_BCK_CYCLES << PCM2_WLEN_SFT; + pcm_con |= rate_reg << PCM2_MODE_SFT; + pcm_con |= AUD_PCM_FMT_PCM_MODE_B << PCM2_FMT_SFT; + + regmap_update_bits(afe->regmap, PCM2_INTF_CON, + 0xfffffffe, pcm_con); + break; + default: + dev_warn(afe->dev, "%s(), id %d not support\n", + __func__, dai->id); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops mtk_dai_pcm_ops = { + .hw_params = mtk_dai_pcm_hw_params, +}; + +/* dai driver */ +#define MTK_PCM_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_48000) + +#define MTK_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mtk_dai_pcm_driver[] = { + { + .name = "PCM 1", + .id = MT6797_DAI_PCM_1, + .playback = { + .stream_name = "PCM 1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .capture = { + .stream_name = "PCM 1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_dai_pcm_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, + { + .name = "PCM 2", + .id = MT6797_DAI_PCM_2, + .playback = { + .stream_name = "PCM 2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .capture = { + .stream_name = "PCM 2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MTK_PCM_RATES, + .formats = MTK_PCM_FORMATS, + }, + .ops = &mtk_dai_pcm_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + }, +}; + +int mt6797_dai_pcm_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = mtk_dai_pcm_driver; + dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_pcm_driver); + + dai->dapm_widgets = mtk_dai_pcm_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_pcm_widgets); + dai->dapm_routes = mtk_dai_pcm_routes; + dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_pcm_routes); + return 0; +} diff --git a/sound/soc/mediatek/mt6797/mt6797-interconnection.h b/sound/soc/mediatek/mt6797/mt6797-interconnection.h new file mode 100644 index 000000000..07b759b20 --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-interconnection.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Mediatek MT6797 audio driver interconnection definition + * + * Copyright (c) 2018 MediaTek Inc. + * Author: KaiChieh Chuang + */ + +#ifndef _MT6797_INTERCONNECTION_H_ +#define _MT6797_INTERCONNECTION_H_ + +#define I_I2S0_CH1 0 +#define I_I2S0_CH2 1 +#define I_ADDA_UL_CH1 3 +#define I_ADDA_UL_CH2 4 +#define I_DL1_CH1 5 +#define I_DL1_CH2 6 +#define I_DL2_CH1 7 +#define I_DL2_CH2 8 +#define I_PCM_1_CAP_CH1 9 +#define I_GAIN1_OUT_CH1 10 +#define I_GAIN1_OUT_CH2 11 +#define I_GAIN2_OUT_CH1 12 +#define I_GAIN2_OUT_CH2 13 +#define I_PCM_2_CAP_CH1 14 +#define I_PCM_2_CAP_CH2 21 +#define I_PCM_1_CAP_CH2 22 +#define I_DL3_CH1 23 +#define I_DL3_CH2 24 +#define I_I2S2_CH1 25 +#define I_I2S2_CH2 26 + +#endif diff --git a/sound/soc/mediatek/mt6797/mt6797-mt6351.c b/sound/soc/mediatek/mt6797/mt6797-mt6351.c new file mode 100644 index 000000000..b1558c57b --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-mt6351.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mt6797-mt6351.c -- MT6797 MT6351 ALSA SoC machine driver +// +// Copyright (c) 2018 MediaTek Inc. +// Author: KaiChieh Chuang + +#include +#include + +#include "mt6797-afe-common.h" + +static struct snd_soc_dai_link mt6797_mt6351_dai_links[] = { + /* FE */ + { + .name = "Playback_1", + .stream_name = "Playback_1", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_2", + .stream_name = "Playback_2", + .cpu_dai_name = "DL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Playback_3", + .stream_name = "Playback_3", + .cpu_dai_name = "DL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "Capture_1", + .stream_name = "Capture_1", + .cpu_dai_name = "UL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_2", + .stream_name = "Capture_2", + .cpu_dai_name = "UL2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_3", + .stream_name = "Capture_3", + .cpu_dai_name = "UL3", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Capture_Mono_1", + .stream_name = "Capture_Mono_1", + .cpu_dai_name = "UL_MONO_1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_capture = 1, + }, + { + .name = "Hostless_LPBK", + .stream_name = "Hostless_LPBK", + .cpu_dai_name = "Hostless LPBK DAI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "Hostless_Speech", + .stream_name = "Hostless_Speech", + .cpu_dai_name = "Hostless Speech DAI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_PRE, + SND_SOC_DPCM_TRIGGER_PRE}, + .dynamic = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + /* BE */ + { + .name = "Primary Codec", + .cpu_dai_name = "ADDA", + .codec_dai_name = "mt6351-snd-codec-aif1", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 1", + .cpu_dai_name = "PCM 1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, + { + .name = "PCM 2", + .cpu_dai_name = "PCM 2", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ignore_suspend = 1, + }, +}; + +static struct snd_soc_card mt6797_mt6351_card = { + .name = "mt6797-mt6351", + .owner = THIS_MODULE, + .dai_link = mt6797_mt6351_dai_links, + .num_links = ARRAY_SIZE(mt6797_mt6351_dai_links), +}; + +static int mt6797_mt6351_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt6797_mt6351_card; + struct device_node *platform_node, *codec_node; + int ret, i; + + card->dev = &pdev->dev; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt6797_mt6351_dai_links[i].platform_name) + continue; + mt6797_mt6351_dai_links[i].platform_of_node = platform_node; + } + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt6797_mt6351_dai_links[i].codec_name) + continue; + mt6797_mt6351_dai_links[i].codec_of_node = codec_node; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt6797_mt6351_dt_match[] = { + {.compatible = "mediatek,mt6797-mt6351-sound",}, + {} +}; +#endif + +static struct platform_driver mt6797_mt6351_driver = { + .driver = { + .name = "mt6797-mt6351", + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = mt6797_mt6351_dt_match, +#endif + }, + .probe = mt6797_mt6351_dev_probe, +}; + +module_platform_driver(mt6797_mt6351_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT6797 MT6351 ALSA SoC machine driver"); +MODULE_AUTHOR("KaiChieh Chuang "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt6797 mt6351 soc card"); + diff --git a/sound/soc/mediatek/mt6797/mt6797-reg.h b/sound/soc/mediatek/mt6797/mt6797-reg.h new file mode 100644 index 000000000..978f146c1 --- /dev/null +++ b/sound/soc/mediatek/mt6797/mt6797-reg.h @@ -0,0 +1,1015 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt6797-reg.h -- Mediatek 6797 audio driver reg definition + * + * Copyright (c) 2018 MediaTek Inc. + * Author: KaiChieh Chuang + */ + +#ifndef _MT6797_REG_H_ +#define _MT6797_REG_H_ + +#define AUDIO_TOP_CON0 0x0000 +#define AUDIO_TOP_CON1 0x0004 +#define AUDIO_TOP_CON3 0x000c +#define AFE_DAC_CON0 0x0010 +#define AFE_DAC_CON1 0x0014 +#define AFE_I2S_CON 0x0018 +#define AFE_DAIBT_CON0 0x001c +#define AFE_CONN0 0x0020 +#define AFE_CONN1 0x0024 +#define AFE_CONN2 0x0028 +#define AFE_CONN3 0x002c +#define AFE_CONN4 0x0030 +#define AFE_I2S_CON1 0x0034 +#define AFE_I2S_CON2 0x0038 +#define AFE_MRGIF_CON 0x003c +#define AFE_DL1_BASE 0x0040 +#define AFE_DL1_CUR 0x0044 +#define AFE_DL1_END 0x0048 +#define AFE_I2S_CON3 0x004c +#define AFE_DL2_BASE 0x0050 +#define AFE_DL2_CUR 0x0054 +#define AFE_DL2_END 0x0058 +#define AFE_CONN5 0x005c +#define AFE_CONN_24BIT 0x006c +#define AFE_AWB_BASE 0x0070 +#define AFE_AWB_END 0x0078 +#define AFE_AWB_CUR 0x007c +#define AFE_VUL_BASE 0x0080 +#define AFE_VUL_END 0x0088 +#define AFE_VUL_CUR 0x008c +#define AFE_DAI_BASE 0x0090 +#define AFE_DAI_END 0x0098 +#define AFE_DAI_CUR 0x009c +#define AFE_CONN6 0x00bc +#define AFE_MEMIF_MSB 0x00cc +#define AFE_MEMIF_MON0 0x00d0 +#define AFE_MEMIF_MON1 0x00d4 +#define AFE_MEMIF_MON2 0x00d8 +#define AFE_MEMIF_MON4 0x00e0 +#define AFE_ADDA_DL_SRC2_CON0 0x0108 +#define AFE_ADDA_DL_SRC2_CON1 0x010c +#define AFE_ADDA_UL_SRC_CON0 0x0114 +#define AFE_ADDA_UL_SRC_CON1 0x0118 +#define AFE_ADDA_TOP_CON0 0x0120 +#define AFE_ADDA_UL_DL_CON0 0x0124 +#define AFE_ADDA_SRC_DEBUG 0x012c +#define AFE_ADDA_SRC_DEBUG_MON0 0x0130 +#define AFE_ADDA_SRC_DEBUG_MON1 0x0134 +#define AFE_ADDA_NEWIF_CFG0 0x0138 +#define AFE_ADDA_NEWIF_CFG1 0x013c +#define AFE_ADDA_NEWIF_CFG2 0x0140 +#define AFE_DMA_CTL 0x0150 +#define AFE_DMA_MON0 0x0154 +#define AFE_DMA_MON1 0x0158 +#define AFE_SIDETONE_DEBUG 0x01d0 +#define AFE_SIDETONE_MON 0x01d4 +#define AFE_SIDETONE_CON0 0x01e0 +#define AFE_SIDETONE_COEFF 0x01e4 +#define AFE_SIDETONE_CON1 0x01e8 +#define AFE_SIDETONE_GAIN 0x01ec +#define AFE_SGEN_CON0 0x01f0 +#define AFE_SINEGEN_CON_TDM 0x01fc +#define AFE_TOP_CON0 0x0200 +#define AFE_ADDA_PREDIS_CON0 0x0260 +#define AFE_ADDA_PREDIS_CON1 0x0264 +#define AFE_MRGIF_MON0 0x0270 +#define AFE_MRGIF_MON1 0x0274 +#define AFE_MRGIF_MON2 0x0278 +#define AFE_I2S_MON 0x027c +#define AFE_MOD_DAI_BASE 0x0330 +#define AFE_MOD_DAI_END 0x0338 +#define AFE_MOD_DAI_CUR 0x033c +#define AFE_VUL_D2_BASE 0x0350 +#define AFE_VUL_D2_END 0x0358 +#define AFE_VUL_D2_CUR 0x035c +#define AFE_DL3_BASE 0x0360 +#define AFE_DL3_CUR 0x0364 +#define AFE_DL3_END 0x0368 +#define AFE_HDMI_OUT_CON0 0x0370 +#define AFE_HDMI_BASE 0x0374 +#define AFE_HDMI_CUR 0x0378 +#define AFE_HDMI_END 0x037c +#define AFE_HDMI_CONN0 0x0390 +#define AFE_IRQ3_MCU_CNT_MON 0x0398 +#define AFE_IRQ4_MCU_CNT_MON 0x039c +#define AFE_IRQ_MCU_CON 0x03a0 +#define AFE_IRQ_MCU_STATUS 0x03a4 +#define AFE_IRQ_MCU_CLR 0x03a8 +#define AFE_IRQ_MCU_CNT1 0x03ac +#define AFE_IRQ_MCU_CNT2 0x03b0 +#define AFE_IRQ_MCU_EN 0x03b4 +#define AFE_IRQ_MCU_MON2 0x03b8 +#define AFE_IRQ_MCU_CNT5 0x03bc +#define AFE_IRQ1_MCU_CNT_MON 0x03c0 +#define AFE_IRQ2_MCU_CNT_MON 0x03c4 +#define AFE_IRQ1_MCU_EN_CNT_MON 0x03c8 +#define AFE_IRQ5_MCU_CNT_MON 0x03cc +#define AFE_MEMIF_MINLEN 0x03d0 +#define AFE_MEMIF_MAXLEN 0x03d4 +#define AFE_MEMIF_PBUF_SIZE 0x03d8 +#define AFE_IRQ_MCU_CNT7 0x03dc +#define AFE_IRQ7_MCU_CNT_MON 0x03e0 +#define AFE_IRQ_MCU_CNT3 0x03e4 +#define AFE_IRQ_MCU_CNT4 0x03e8 +#define AFE_APLL1_TUNER_CFG 0x03f0 +#define AFE_APLL2_TUNER_CFG 0x03f4 +#define AFE_MEMIF_HD_MODE 0x03f8 +#define AFE_MEMIF_HDALIGN 0x03fc +#define AFE_GAIN1_CON0 0x0410 +#define AFE_GAIN1_CON1 0x0414 +#define AFE_GAIN1_CON2 0x0418 +#define AFE_GAIN1_CON3 0x041c +#define AFE_CONN7 0x0420 +#define AFE_GAIN1_CUR 0x0424 +#define AFE_GAIN2_CON0 0x0428 +#define AFE_GAIN2_CON1 0x042c +#define AFE_GAIN2_CON2 0x0430 +#define AFE_GAIN2_CON3 0x0434 +#define AFE_CONN8 0x0438 +#define AFE_GAIN2_CUR 0x043c +#define AFE_CONN9 0x0440 +#define AFE_CONN10 0x0444 +#define AFE_CONN11 0x0448 +#define AFE_CONN12 0x044c +#define AFE_CONN13 0x0450 +#define AFE_CONN14 0x0454 +#define AFE_CONN15 0x0458 +#define AFE_CONN16 0x045c +#define AFE_CONN17 0x0460 +#define AFE_CONN18 0x0464 +#define AFE_CONN19 0x0468 +#define AFE_CONN20 0x046c +#define AFE_CONN21 0x0470 +#define AFE_CONN22 0x0474 +#define AFE_CONN23 0x0478 +#define AFE_CONN24 0x047c +#define AFE_CONN_RS 0x0494 +#define AFE_CONN_DI 0x0498 +#define AFE_CONN25 0x04b0 +#define AFE_CONN26 0x04b4 +#define AFE_CONN27 0x04b8 +#define AFE_CONN28 0x04bc +#define AFE_CONN29 0x04c0 +#define AFE_SRAM_DELSEL_CON0 0x04f0 +#define AFE_SRAM_DELSEL_CON1 0x04f4 +#define AFE_ASRC_CON0 0x0500 +#define AFE_ASRC_CON1 0x0504 +#define AFE_ASRC_CON2 0x0508 +#define AFE_ASRC_CON3 0x050c +#define AFE_ASRC_CON4 0x0510 +#define AFE_ASRC_CON5 0x0514 +#define AFE_ASRC_CON6 0x0518 +#define AFE_ASRC_CON7 0x051c +#define AFE_ASRC_CON8 0x0520 +#define AFE_ASRC_CON9 0x0524 +#define AFE_ASRC_CON10 0x0528 +#define AFE_ASRC_CON11 0x052c +#define PCM_INTF_CON1 0x0530 +#define PCM_INTF_CON2 0x0538 +#define PCM2_INTF_CON 0x053c +#define AFE_TDM_CON1 0x0548 +#define AFE_TDM_CON2 0x054c +#define AFE_ASRC_CON13 0x0550 +#define AFE_ASRC_CON14 0x0554 +#define AFE_ASRC_CON15 0x0558 +#define AFE_ASRC_CON16 0x055c +#define AFE_ASRC_CON17 0x0560 +#define AFE_ASRC_CON18 0x0564 +#define AFE_ASRC_CON19 0x0568 +#define AFE_ASRC_CON20 0x056c +#define AFE_ASRC_CON21 0x0570 +#define CLK_AUDDIV_0 0x05a0 +#define CLK_AUDDIV_1 0x05a4 +#define CLK_AUDDIV_2 0x05a8 +#define CLK_AUDDIV_3 0x05ac +#define AUDIO_TOP_DBG_CON 0x05c8 +#define AUDIO_TOP_DBG_MON0 0x05cc +#define AUDIO_TOP_DBG_MON1 0x05d0 +#define AUDIO_TOP_DBG_MON2 0x05d4 +#define AFE_ADDA2_TOP_CON0 0x0600 +#define AFE_ASRC4_CON0 0x06c0 +#define AFE_ASRC4_CON1 0x06c4 +#define AFE_ASRC4_CON2 0x06c8 +#define AFE_ASRC4_CON3 0x06cc +#define AFE_ASRC4_CON4 0x06d0 +#define AFE_ASRC4_CON5 0x06d4 +#define AFE_ASRC4_CON6 0x06d8 +#define AFE_ASRC4_CON7 0x06dc +#define AFE_ASRC4_CON8 0x06e0 +#define AFE_ASRC4_CON9 0x06e4 +#define AFE_ASRC4_CON10 0x06e8 +#define AFE_ASRC4_CON11 0x06ec +#define AFE_ASRC4_CON12 0x06f0 +#define AFE_ASRC4_CON13 0x06f4 +#define AFE_ASRC4_CON14 0x06f8 +#define AFE_ASRC2_CON0 0x0700 +#define AFE_ASRC2_CON1 0x0704 +#define AFE_ASRC2_CON2 0x0708 +#define AFE_ASRC2_CON3 0x070c +#define AFE_ASRC2_CON4 0x0710 +#define AFE_ASRC2_CON5 0x0714 +#define AFE_ASRC2_CON6 0x0718 +#define AFE_ASRC2_CON7 0x071c +#define AFE_ASRC2_CON8 0x0720 +#define AFE_ASRC2_CON9 0x0724 +#define AFE_ASRC2_CON10 0x0728 +#define AFE_ASRC2_CON11 0x072c +#define AFE_ASRC2_CON12 0x0730 +#define AFE_ASRC2_CON13 0x0734 +#define AFE_ASRC2_CON14 0x0738 +#define AFE_ASRC3_CON0 0x0740 +#define AFE_ASRC3_CON1 0x0744 +#define AFE_ASRC3_CON2 0x0748 +#define AFE_ASRC3_CON3 0x074c +#define AFE_ASRC3_CON4 0x0750 +#define AFE_ASRC3_CON5 0x0754 +#define AFE_ASRC3_CON6 0x0758 +#define AFE_ASRC3_CON7 0x075c +#define AFE_ASRC3_CON8 0x0760 +#define AFE_ASRC3_CON9 0x0764 +#define AFE_ASRC3_CON10 0x0768 +#define AFE_ASRC3_CON11 0x076c +#define AFE_ASRC3_CON12 0x0770 +#define AFE_ASRC3_CON13 0x0774 +#define AFE_ASRC3_CON14 0x0778 +#define AFE_GENERAL_REG0 0x0800 +#define AFE_GENERAL_REG1 0x0804 +#define AFE_GENERAL_REG2 0x0808 +#define AFE_GENERAL_REG3 0x080c +#define AFE_GENERAL_REG4 0x0810 +#define AFE_GENERAL_REG5 0x0814 +#define AFE_GENERAL_REG6 0x0818 +#define AFE_GENERAL_REG7 0x081c +#define AFE_GENERAL_REG8 0x0820 +#define AFE_GENERAL_REG9 0x0824 +#define AFE_GENERAL_REG10 0x0828 +#define AFE_GENERAL_REG11 0x082c +#define AFE_GENERAL_REG12 0x0830 +#define AFE_GENERAL_REG13 0x0834 +#define AFE_GENERAL_REG14 0x0838 +#define AFE_GENERAL_REG15 0x083c +#define AFE_CBIP_CFG0 0x0840 +#define AFE_CBIP_MON0 0x0844 +#define AFE_CBIP_SLV_MUX_MON0 0x0848 +#define AFE_CBIP_SLV_DECODER_MON0 0x084c + +#define AFE_MAX_REGISTER AFE_CBIP_SLV_DECODER_MON0 +#define AFE_IRQ_STATUS_BITS 0x5f + +/* AUDIO_TOP_CON0 */ +#define AHB_IDLE_EN_INT_SFT 30 +#define AHB_IDLE_EN_INT_MASK 0x1 +#define AHB_IDLE_EN_INT_MASK_SFT (0x1 << 30) +#define AHB_IDLE_EN_EXT_SFT 29 +#define AHB_IDLE_EN_EXT_MASK 0x1 +#define AHB_IDLE_EN_EXT_MASK_SFT (0x1 << 29) +#define PDN_TML_SFT 27 +#define PDN_TML_MASK 0x1 +#define PDN_TML_MASK_SFT (0x1 << 27) +#define PDN_DAC_PREDIS_SFT 26 +#define PDN_DAC_PREDIS_MASK 0x1 +#define PDN_DAC_PREDIS_MASK_SFT (0x1 << 26) +#define PDN_DAC_SFT 25 +#define PDN_DAC_MASK 0x1 +#define PDN_DAC_MASK_SFT (0x1 << 25) +#define PDN_ADC_SFT 24 +#define PDN_ADC_MASK 0x1 +#define PDN_ADC_MASK_SFT (0x1 << 24) +#define PDN_TDM_CK_SFT 20 +#define PDN_TDM_CK_MASK 0x1 +#define PDN_TDM_CK_MASK_SFT (0x1 << 20) +#define PDN_APLL_TUNER_SFT 19 +#define PDN_APLL_TUNER_MASK 0x1 +#define PDN_APLL_TUNER_MASK_SFT (0x1 << 19) +#define PDN_APLL2_TUNER_SFT 18 +#define PDN_APLL2_TUNER_MASK 0x1 +#define PDN_APLL2_TUNER_MASK_SFT (0x1 << 18) +#define APB3_SEL_SFT 14 +#define APB3_SEL_MASK 0x1 +#define APB3_SEL_MASK_SFT (0x1 << 14) +#define APB_R2T_SFT 13 +#define APB_R2T_MASK 0x1 +#define APB_R2T_MASK_SFT (0x1 << 13) +#define APB_W2T_SFT 12 +#define APB_W2T_MASK 0x1 +#define APB_W2T_MASK_SFT (0x1 << 12) +#define PDN_24M_SFT 9 +#define PDN_24M_MASK 0x1 +#define PDN_24M_MASK_SFT (0x1 << 9) +#define PDN_22M_SFT 8 +#define PDN_22M_MASK 0x1 +#define PDN_22M_MASK_SFT (0x1 << 8) +#define PDN_ADDA4_ADC_SFT 7 +#define PDN_ADDA4_ADC_MASK 0x1 +#define PDN_ADDA4_ADC_MASK_SFT (0x1 << 7) +#define PDN_I2S_SFT 6 +#define PDN_I2S_MASK 0x1 +#define PDN_I2S_MASK_SFT (0x1 << 6) +#define PDN_AFE_SFT 2 +#define PDN_AFE_MASK 0x1 +#define PDN_AFE_MASK_SFT (0x1 << 2) + +/* AUDIO_TOP_CON1 */ +#define PDN_ADC_HIRES_TML_SFT 17 +#define PDN_ADC_HIRES_TML_MASK 0x1 +#define PDN_ADC_HIRES_TML_MASK_SFT (0x1 << 17) +#define PDN_ADC_HIRES_SFT 16 +#define PDN_ADC_HIRES_MASK 0x1 +#define PDN_ADC_HIRES_MASK_SFT (0x1 << 16) +#define I2S4_BCLK_SW_CG_SFT 7 +#define I2S4_BCLK_SW_CG_MASK 0x1 +#define I2S4_BCLK_SW_CG_MASK_SFT (0x1 << 7) +#define I2S3_BCLK_SW_CG_SFT 6 +#define I2S3_BCLK_SW_CG_MASK 0x1 +#define I2S3_BCLK_SW_CG_MASK_SFT (0x1 << 6) +#define I2S2_BCLK_SW_CG_SFT 5 +#define I2S2_BCLK_SW_CG_MASK 0x1 +#define I2S2_BCLK_SW_CG_MASK_SFT (0x1 << 5) +#define I2S1_BCLK_SW_CG_SFT 4 +#define I2S1_BCLK_SW_CG_MASK 0x1 +#define I2S1_BCLK_SW_CG_MASK_SFT (0x1 << 4) +#define I2S_SOFT_RST2_SFT 2 +#define I2S_SOFT_RST2_MASK 0x1 +#define I2S_SOFT_RST2_MASK_SFT (0x1 << 2) +#define I2S_SOFT_RST_SFT 1 +#define I2S_SOFT_RST_MASK 0x1 +#define I2S_SOFT_RST_MASK_SFT (0x1 << 1) + +/* AFE_DAC_CON0 */ +#define AFE_AWB_RETM_SFT 31 +#define AFE_AWB_RETM_MASK 0x1 +#define AFE_AWB_RETM_MASK_SFT (0x1 << 31) +#define AFE_DL1_DATA2_RETM_SFT 30 +#define AFE_DL1_DATA2_RETM_MASK 0x1 +#define AFE_DL1_DATA2_RETM_MASK_SFT (0x1 << 30) +#define AFE_DL2_RETM_SFT 29 +#define AFE_DL2_RETM_MASK 0x1 +#define AFE_DL2_RETM_MASK_SFT (0x1 << 29) +#define AFE_DL1_RETM_SFT 28 +#define AFE_DL1_RETM_MASK 0x1 +#define AFE_DL1_RETM_MASK_SFT (0x1 << 28) +#define AFE_ON_RETM_SFT 27 +#define AFE_ON_RETM_MASK 0x1 +#define AFE_ON_RETM_MASK_SFT (0x1 << 27) +#define MOD_DAI_DUP_WR_SFT 26 +#define MOD_DAI_DUP_WR_MASK 0x1 +#define MOD_DAI_DUP_WR_MASK_SFT (0x1 << 26) +#define DAI_MODE_SFT 24 +#define DAI_MODE_MASK 0x3 +#define DAI_MODE_MASK_SFT (0x3 << 24) +#define VUL_DATA2_MODE_SFT 20 +#define VUL_DATA2_MODE_MASK 0xf +#define VUL_DATA2_MODE_MASK_SFT (0xf << 20) +#define DL1_DATA2_MODE_SFT 16 +#define DL1_DATA2_MODE_MASK 0xf +#define DL1_DATA2_MODE_MASK_SFT (0xf << 16) +#define DL3_MODE_SFT 12 +#define DL3_MODE_MASK 0xf +#define DL3_MODE_MASK_SFT (0xf << 12) +#define VUL_DATA2_R_MONO_SFT 11 +#define VUL_DATA2_R_MONO_MASK 0x1 +#define VUL_DATA2_R_MONO_MASK_SFT (0x1 << 11) +#define VUL_DATA2_DATA_SFT 10 +#define VUL_DATA2_DATA_MASK 0x1 +#define VUL_DATA2_DATA_MASK_SFT (0x1 << 10) +#define VUL_DATA2_ON_SFT 9 +#define VUL_DATA2_ON_MASK 0x1 +#define VUL_DATA2_ON_MASK_SFT (0x1 << 9) +#define DL1_DATA2_ON_SFT 8 +#define DL1_DATA2_ON_MASK 0x1 +#define DL1_DATA2_ON_MASK_SFT (0x1 << 8) +#define MOD_DAI_ON_SFT 7 +#define MOD_DAI_ON_MASK 0x1 +#define MOD_DAI_ON_MASK_SFT (0x1 << 7) +#define AWB_ON_SFT 6 +#define AWB_ON_MASK 0x1 +#define AWB_ON_MASK_SFT (0x1 << 6) +#define DL3_ON_SFT 5 +#define DL3_ON_MASK 0x1 +#define DL3_ON_MASK_SFT (0x1 << 5) +#define DAI_ON_SFT 4 +#define DAI_ON_MASK 0x1 +#define DAI_ON_MASK_SFT (0x1 << 4) +#define VUL_ON_SFT 3 +#define VUL_ON_MASK 0x1 +#define VUL_ON_MASK_SFT (0x1 << 3) +#define DL2_ON_SFT 2 +#define DL2_ON_MASK 0x1 +#define DL2_ON_MASK_SFT (0x1 << 2) +#define DL1_ON_SFT 1 +#define DL1_ON_MASK 0x1 +#define DL1_ON_MASK_SFT (0x1 << 1) +#define AFE_ON_SFT 0 +#define AFE_ON_MASK 0x1 +#define AFE_ON_MASK_SFT (0x1 << 0) + +/* AFE_DAC_CON1 */ +#define MOD_DAI_MODE_SFT 30 +#define MOD_DAI_MODE_MASK 0x3 +#define MOD_DAI_MODE_MASK_SFT (0x3 << 30) +#define DAI_DUP_WR_SFT 29 +#define DAI_DUP_WR_MASK 0x1 +#define DAI_DUP_WR_MASK_SFT (0x1 << 29) +#define VUL_R_MONO_SFT 28 +#define VUL_R_MONO_MASK 0x1 +#define VUL_R_MONO_MASK_SFT (0x1 << 28) +#define VUL_DATA_SFT 27 +#define VUL_DATA_MASK 0x1 +#define VUL_DATA_MASK_SFT (0x1 << 27) +#define AXI_2X1_CG_DISABLE_SFT 26 +#define AXI_2X1_CG_DISABLE_MASK 0x1 +#define AXI_2X1_CG_DISABLE_MASK_SFT (0x1 << 26) +#define AWB_R_MONO_SFT 25 +#define AWB_R_MONO_MASK 0x1 +#define AWB_R_MONO_MASK_SFT (0x1 << 25) +#define AWB_DATA_SFT 24 +#define AWB_DATA_MASK 0x1 +#define AWB_DATA_MASK_SFT (0x1 << 24) +#define DL3_DATA_SFT 23 +#define DL3_DATA_MASK 0x1 +#define DL3_DATA_MASK_SFT (0x1 << 23) +#define DL2_DATA_SFT 22 +#define DL2_DATA_MASK 0x1 +#define DL2_DATA_MASK_SFT (0x1 << 22) +#define DL1_DATA_SFT 21 +#define DL1_DATA_MASK 0x1 +#define DL1_DATA_MASK_SFT (0x1 << 21) +#define DL1_DATA2_DATA_SFT 20 +#define DL1_DATA2_DATA_MASK 0x1 +#define DL1_DATA2_DATA_MASK_SFT (0x1 << 20) +#define VUL_MODE_SFT 16 +#define VUL_MODE_MASK 0xf +#define VUL_MODE_MASK_SFT (0xf << 16) +#define AWB_MODE_SFT 12 +#define AWB_MODE_MASK 0xf +#define AWB_MODE_MASK_SFT (0xf << 12) +#define I2S_MODE_SFT 8 +#define I2S_MODE_MASK 0xf +#define I2S_MODE_MASK_SFT (0xf << 8) +#define DL2_MODE_SFT 4 +#define DL2_MODE_MASK 0xf +#define DL2_MODE_MASK_SFT (0xf << 4) +#define DL1_MODE_SFT 0 +#define DL1_MODE_MASK 0xf +#define DL1_MODE_MASK_SFT (0xf << 0) + +/* AFE_ADDA_DL_SRC2_CON0 */ +#define DL_2_INPUT_MODE_CTL_SFT 28 +#define DL_2_INPUT_MODE_CTL_MASK 0xf +#define DL_2_INPUT_MODE_CTL_MASK_SFT (0xf << 28) +#define DL_2_CH1_SATURATION_EN_CTL_SFT 27 +#define DL_2_CH1_SATURATION_EN_CTL_MASK 0x1 +#define DL_2_CH1_SATURATION_EN_CTL_MASK_SFT (0x1 << 27) +#define DL_2_CH2_SATURATION_EN_CTL_SFT 26 +#define DL_2_CH2_SATURATION_EN_CTL_MASK 0x1 +#define DL_2_CH2_SATURATION_EN_CTL_MASK_SFT (0x1 << 26) +#define DL_2_OUTPUT_SEL_CTL_SFT 24 +#define DL_2_OUTPUT_SEL_CTL_MASK 0x3 +#define DL_2_OUTPUT_SEL_CTL_MASK_SFT (0x3 << 24) +#define DL_2_FADEIN_0START_EN_SFT 16 +#define DL_2_FADEIN_0START_EN_MASK 0x3 +#define DL_2_FADEIN_0START_EN_MASK_SFT (0x3 << 16) +#define DL_DISABLE_HW_CG_CTL_SFT 15 +#define DL_DISABLE_HW_CG_CTL_MASK 0x1 +#define DL_DISABLE_HW_CG_CTL_MASK_SFT (0x1 << 15) +#define C_DATA_EN_SEL_CTL_PRE_SFT 14 +#define C_DATA_EN_SEL_CTL_PRE_MASK 0x1 +#define C_DATA_EN_SEL_CTL_PRE_MASK_SFT (0x1 << 14) +#define DL_2_SIDE_TONE_ON_CTL_PRE_SFT 13 +#define DL_2_SIDE_TONE_ON_CTL_PRE_MASK 0x1 +#define DL_2_SIDE_TONE_ON_CTL_PRE_MASK_SFT (0x1 << 13) +#define DL_2_MUTE_CH1_OFF_CTL_PRE_SFT 12 +#define DL_2_MUTE_CH1_OFF_CTL_PRE_MASK 0x1 +#define DL_2_MUTE_CH1_OFF_CTL_PRE_MASK_SFT (0x1 << 12) +#define DL_2_MUTE_CH2_OFF_CTL_PRE_SFT 11 +#define DL_2_MUTE_CH2_OFF_CTL_PRE_MASK 0x1 +#define DL_2_MUTE_CH2_OFF_CTL_PRE_MASK_SFT (0x1 << 11) +#define DL2_ARAMPSP_CTL_PRE_SFT 9 +#define DL2_ARAMPSP_CTL_PRE_MASK 0x3 +#define DL2_ARAMPSP_CTL_PRE_MASK_SFT (0x3 << 9) +#define DL_2_IIRMODE_CTL_PRE_SFT 6 +#define DL_2_IIRMODE_CTL_PRE_MASK 0x7 +#define DL_2_IIRMODE_CTL_PRE_MASK_SFT (0x7 << 6) +#define DL_2_VOICE_MODE_CTL_PRE_SFT 5 +#define DL_2_VOICE_MODE_CTL_PRE_MASK 0x1 +#define DL_2_VOICE_MODE_CTL_PRE_MASK_SFT (0x1 << 5) +#define D2_2_MUTE_CH1_ON_CTL_PRE_SFT 4 +#define D2_2_MUTE_CH1_ON_CTL_PRE_MASK 0x1 +#define D2_2_MUTE_CH1_ON_CTL_PRE_MASK_SFT (0x1 << 4) +#define D2_2_MUTE_CH2_ON_CTL_PRE_SFT 3 +#define D2_2_MUTE_CH2_ON_CTL_PRE_MASK 0x1 +#define D2_2_MUTE_CH2_ON_CTL_PRE_MASK_SFT (0x1 << 3) +#define DL_2_IIR_ON_CTL_PRE_SFT 2 +#define DL_2_IIR_ON_CTL_PRE_MASK 0x1 +#define DL_2_IIR_ON_CTL_PRE_MASK_SFT (0x1 << 2) +#define DL_2_GAIN_ON_CTL_PRE_SFT 1 +#define DL_2_GAIN_ON_CTL_PRE_MASK 0x1 +#define DL_2_GAIN_ON_CTL_PRE_MASK_SFT (0x1 << 1) +#define DL_2_SRC_ON_TMP_CTL_PRE_SFT 0 +#define DL_2_SRC_ON_TMP_CTL_PRE_MASK 0x1 +#define DL_2_SRC_ON_TMP_CTL_PRE_MASK_SFT (0x1 << 0) + +/* AFE_ADDA_DL_SRC2_CON1 */ +#define DL_2_GAIN_CTL_PRE_SFT 16 +#define DL_2_GAIN_CTL_PRE_MASK 0xffff +#define DL_2_GAIN_CTL_PRE_MASK_SFT (0xffff << 16) +#define DL_2_GAIN_MODE_CTL_SFT 0 +#define DL_2_GAIN_MODE_CTL_MASK 0x1 +#define DL_2_GAIN_MODE_CTL_MASK_SFT (0x1 << 0) + +/* AFE_ADDA_UL_SRC_CON0 */ +#define C_COMB_OUT_SIN_GEN_CTL_SFT 31 +#define C_COMB_OUT_SIN_GEN_CTL_MASK 0x1 +#define C_COMB_OUT_SIN_GEN_CTL_MASK_SFT (0x1 << 31) +#define C_BASEBAND_SIN_GEN_CTL_SFT 30 +#define C_BASEBAND_SIN_GEN_CTL_MASK 0x1 +#define C_BASEBAND_SIN_GEN_CTL_MASK_SFT (0x1 << 30) +#define C_DIGMIC_PHASE_SEL_CH1_CTL_SFT 27 +#define C_DIGMIC_PHASE_SEL_CH1_CTL_MASK 0x7 +#define C_DIGMIC_PHASE_SEL_CH1_CTL_MASK_SFT (0x7 << 27) +#define C_DIGMIC_PHASE_SEL_CH2_CTL_SFT 24 +#define C_DIGMIC_PHASE_SEL_CH2_CTL_MASK 0x7 +#define C_DIGMIC_PHASE_SEL_CH2_CTL_MASK_SFT (0x7 << 24) +#define C_TWO_DIGITAL_MIC_CTL_SFT 23 +#define C_TWO_DIGITAL_MIC_CTL_MASK 0x1 +#define C_TWO_DIGITAL_MIC_CTL_MASK_SFT (0x1 << 23) +#define UL_MODE_3P25M_CH2_CTL_SFT 22 +#define UL_MODE_3P25M_CH2_CTL_MASK 0x1 +#define UL_MODE_3P25M_CH2_CTL_MASK_SFT (0x1 << 22) +#define UL_MODE_3P25M_CH1_CTL_SFT 21 +#define UL_MODE_3P25M_CH1_CTL_MASK 0x1 +#define UL_MODE_3P25M_CH1_CTL_MASK_SFT (0x1 << 21) +#define UL_SRC_USE_CIC_OUT_CTL_SFT 20 +#define UL_SRC_USE_CIC_OUT_CTL_MASK 0x1 +#define UL_SRC_USE_CIC_OUT_CTL_MASK_SFT (0x1 << 20) +#define UL_VOICE_MODE_CH1_CH2_CTL_SFT 17 +#define UL_VOICE_MODE_CH1_CH2_CTL_MASK 0x7 +#define UL_VOICE_MODE_CH1_CH2_CTL_MASK_SFT (0x7 << 17) +#define DMIC_LOW_POWER_MODE_CTL_SFT 14 +#define DMIC_LOW_POWER_MODE_CTL_MASK 0x3 +#define DMIC_LOW_POWER_MODE_CTL_MASK_SFT (0x3 << 14) +#define DMIC_48K_SEL_CTL_SFT 13 +#define DMIC_48K_SEL_CTL_MASK 0x1 +#define DMIC_48K_SEL_CTL_MASK_SFT (0x1 << 13) +#define UL_DISABLE_HW_CG_CTL_SFT 12 +#define UL_DISABLE_HW_CG_CTL_MASK 0x1 +#define UL_DISABLE_HW_CG_CTL_MASK_SFT (0x1 << 12) +#define UL_IIR_ON_TMP_CTL_SFT 10 +#define UL_IIR_ON_TMP_CTL_MASK 0x1 +#define UL_IIR_ON_TMP_CTL_MASK_SFT (0x1 << 10) +#define UL_IIRMODE_CTL_SFT 7 +#define UL_IIRMODE_CTL_MASK 0x7 +#define UL_IIRMODE_CTL_MASK_SFT (0x7 << 7) +#define DIGMIC_3P25M_1P625M_SEL_CTL_SFT 5 +#define DIGMIC_3P25M_1P625M_SEL_CTL_MASK 0x1 +#define DIGMIC_3P25M_1P625M_SEL_CTL_MASK_SFT (0x1 << 5) +#define AGC_260K_SEL_CH2_CTL_SFT 4 +#define AGC_260K_SEL_CH2_CTL_MASK 0x1 +#define AGC_260K_SEL_CH2_CTL_MASK_SFT (0x1 << 4) +#define AGC_260K_SEL_CH1_CTL_SFT 3 +#define AGC_260K_SEL_CH1_CTL_MASK 0x1 +#define AGC_260K_SEL_CH1_CTL_MASK_SFT (0x1 << 3) +#define UL_LOOP_BACK_MODE_CTL_SFT 2 +#define UL_LOOP_BACK_MODE_CTL_MASK 0x1 +#define UL_LOOP_BACK_MODE_CTL_MASK_SFT (0x1 << 2) +#define UL_SDM_3_LEVEL_CTL_SFT 1 +#define UL_SDM_3_LEVEL_CTL_MASK 0x1 +#define UL_SDM_3_LEVEL_CTL_MASK_SFT (0x1 << 1) +#define UL_SRC_ON_TMP_CTL_SFT 0 +#define UL_SRC_ON_TMP_CTL_MASK 0x1 +#define UL_SRC_ON_TMP_CTL_MASK_SFT (0x1 << 0) + +/* AFE_ADDA_UL_SRC_CON1 */ +#define C_SDM_RESET_CTL_SFT 31 +#define C_SDM_RESET_CTL_MASK 0x1 +#define C_SDM_RESET_CTL_MASK_SFT (0x1 << 31) +#define ADITHON_CTL_SFT 30 +#define ADITHON_CTL_MASK 0x1 +#define ADITHON_CTL_MASK_SFT (0x1 << 30) +#define ADITHVAL_CTL_SFT 28 +#define ADITHVAL_CTL_MASK 0x3 +#define ADITHVAL_CTL_MASK_SFT (0x3 << 28) +#define C_DAC_EN_CTL_SFT 27 +#define C_DAC_EN_CTL_MASK 0x1 +#define C_DAC_EN_CTL_MASK_SFT (0x1 << 27) +#define C_MUTE_SW_CTL_SFT 26 +#define C_MUTE_SW_CTL_MASK 0x1 +#define C_MUTE_SW_CTL_MASK_SFT (0x1 << 26) +#define ASDM_SRC_SEL_CTL_SFT 25 +#define ASDM_SRC_SEL_CTL_MASK 0x1 +#define ASDM_SRC_SEL_CTL_MASK_SFT (0x1 << 25) +#define C_AMP_DIV_CH2_CTL_SFT 21 +#define C_AMP_DIV_CH2_CTL_MASK 0x7 +#define C_AMP_DIV_CH2_CTL_MASK_SFT (0x7 << 21) +#define C_FREQ_DIV_CH2_CTL_SFT 16 +#define C_FREQ_DIV_CH2_CTL_MASK 0x1f +#define C_FREQ_DIV_CH2_CTL_MASK_SFT (0x1f << 16) +#define C_SINE_MODE_CH2_CTL_SFT 12 +#define C_SINE_MODE_CH2_CTL_MASK 0xf +#define C_SINE_MODE_CH2_CTL_MASK_SFT (0xf << 12) +#define C_AMP_DIV_CH1_CTL_SFT 9 +#define C_AMP_DIV_CH1_CTL_MASK 0x7 +#define C_AMP_DIV_CH1_CTL_MASK_SFT (0x7 << 9) +#define C_FREQ_DIV_CH1_CTL_SFT 4 +#define C_FREQ_DIV_CH1_CTL_MASK 0x1f +#define C_FREQ_DIV_CH1_CTL_MASK_SFT (0x1f << 4) +#define C_SINE_MODE_CH1_CTL_SFT 0 +#define C_SINE_MODE_CH1_CTL_MASK 0xf +#define C_SINE_MODE_CH1_CTL_MASK_SFT (0xf << 0) + +/* AFE_ADDA_TOP_CON0 */ +#define C_LOOP_BACK_MODE_CTL_SFT 12 +#define C_LOOP_BACK_MODE_CTL_MASK 0xf +#define C_LOOP_BACK_MODE_CTL_MASK_SFT (0xf << 12) +#define C_EXT_ADC_CTL_SFT 0 +#define C_EXT_ADC_CTL_MASK 0x1 +#define C_EXT_ADC_CTL_MASK_SFT (0x1 << 0) + +/* AFE_ADDA_UL_DL_CON0 */ +#define AFE_UL_DL_CON0_RESERVED_SFT 1 +#define AFE_UL_DL_CON0_RESERVED_MASK 0x3fff +#define AFE_UL_DL_CON0_RESERVED_MASK_SFT (0x3fff << 1) +#define ADDA_AFE_ON_SFT 0 +#define ADDA_AFE_ON_MASK 0x1 +#define ADDA_AFE_ON_MASK_SFT (0x1 << 0) + +/* AFE_IRQ_MCU_CON */ +#define IRQ7_MCU_MODE_SFT 24 +#define IRQ7_MCU_MODE_MASK 0xf +#define IRQ7_MCU_MODE_MASK_SFT (0xf << 24) +#define IRQ4_MCU_MODE_SFT 20 +#define IRQ4_MCU_MODE_MASK 0xf +#define IRQ4_MCU_MODE_MASK_SFT (0xf << 20) +#define IRQ3_MCU_MODE_SFT 16 +#define IRQ3_MCU_MODE_MASK 0xf +#define IRQ3_MCU_MODE_MASK_SFT (0xf << 16) +#define IRQ7_MCU_ON_SFT 14 +#define IRQ7_MCU_ON_MASK 0x1 +#define IRQ7_MCU_ON_MASK_SFT (0x1 << 14) +#define IRQ5_MCU_ON_SFT 12 +#define IRQ5_MCU_ON_MASK 0x1 +#define IRQ5_MCU_ON_MASK_SFT (0x1 << 12) +#define IRQ2_MCU_MODE_SFT 8 +#define IRQ2_MCU_MODE_MASK 0xf +#define IRQ2_MCU_MODE_MASK_SFT (0xf << 8) +#define IRQ1_MCU_MODE_SFT 4 +#define IRQ1_MCU_MODE_MASK 0xf +#define IRQ1_MCU_MODE_MASK_SFT (0xf << 4) +#define IRQ4_MCU_ON_SFT 3 +#define IRQ4_MCU_ON_MASK 0x1 +#define IRQ4_MCU_ON_MASK_SFT (0x1 << 3) +#define IRQ3_MCU_ON_SFT 2 +#define IRQ3_MCU_ON_MASK 0x1 +#define IRQ3_MCU_ON_MASK_SFT (0x1 << 2) +#define IRQ2_MCU_ON_SFT 1 +#define IRQ2_MCU_ON_MASK 0x1 +#define IRQ2_MCU_ON_MASK_SFT (0x1 << 1) +#define IRQ1_MCU_ON_SFT 0 +#define IRQ1_MCU_ON_MASK 0x1 +#define IRQ1_MCU_ON_MASK_SFT (0x1 << 0) + +/* AFE_IRQ_MCU_EN */ +#define AFE_IRQ_CM4_EN_SFT 16 +#define AFE_IRQ_CM4_EN_MASK 0x7f +#define AFE_IRQ_CM4_EN_MASK_SFT (0x7f << 16) +#define AFE_IRQ_MD32_EN_SFT 8 +#define AFE_IRQ_MD32_EN_MASK 0x7f +#define AFE_IRQ_MD32_EN_MASK_SFT (0x7f << 8) +#define AFE_IRQ_MCU_EN_SFT 0 +#define AFE_IRQ_MCU_EN_MASK 0x7f +#define AFE_IRQ_MCU_EN_MASK_SFT (0x7f << 0) + +/* AFE_IRQ_MCU_CLR */ +#define IRQ7_MCU_CLR_SFT 6 +#define IRQ7_MCU_CLR_MASK 0x1 +#define IRQ7_MCU_CLR_MASK_SFT (0x1 << 6) +#define IRQ5_MCU_CLR_SFT 4 +#define IRQ5_MCU_CLR_MASK 0x1 +#define IRQ5_MCU_CLR_MASK_SFT (0x1 << 4) +#define IRQ4_MCU_CLR_SFT 3 +#define IRQ4_MCU_CLR_MASK 0x1 +#define IRQ4_MCU_CLR_MASK_SFT (0x1 << 3) +#define IRQ3_MCU_CLR_SFT 2 +#define IRQ3_MCU_CLR_MASK 0x1 +#define IRQ3_MCU_CLR_MASK_SFT (0x1 << 2) +#define IRQ2_MCU_CLR_SFT 1 +#define IRQ2_MCU_CLR_MASK 0x1 +#define IRQ2_MCU_CLR_MASK_SFT (0x1 << 1) +#define IRQ1_MCU_CLR_SFT 0 +#define IRQ1_MCU_CLR_MASK 0x1 +#define IRQ1_MCU_CLR_MASK_SFT (0x1 << 0) + +/* AFE_IRQ_MCU_CNT1 */ +#define AFE_IRQ_MCU_CNT1_SFT 0 +#define AFE_IRQ_MCU_CNT1_MASK 0x3ffff +#define AFE_IRQ_MCU_CNT1_MASK_SFT (0x3ffff << 0) + +/* AFE_IRQ_MCU_CNT2 */ +#define AFE_IRQ_MCU_CNT2_SFT 0 +#define AFE_IRQ_MCU_CNT2_MASK 0x3ffff +#define AFE_IRQ_MCU_CNT2_MASK_SFT (0x3ffff << 0) + +/* AFE_IRQ_MCU_CNT3 */ +#define AFE_IRQ_MCU_CNT3_SFT 0 +#define AFE_IRQ_MCU_CNT3_MASK 0x3ffff +#define AFE_IRQ_MCU_CNT3_MASK_SFT (0x3ffff << 0) + +/* AFE_IRQ_MCU_CNT4 */ +#define AFE_IRQ_MCU_CNT4_SFT 0 +#define AFE_IRQ_MCU_CNT4_MASK 0x3ffff +#define AFE_IRQ_MCU_CNT4_MASK_SFT (0x3ffff << 0) + +/* AFE_IRQ_MCU_CNT5 */ +#define AFE_IRQ_MCU_CNT5_SFT 0 +#define AFE_IRQ_MCU_CNT5_MASK 0x3ffff +#define AFE_IRQ_MCU_CNT5_MASK_SFT (0x3ffff << 0) + +/* AFE_IRQ_MCU_CNT7 */ +#define AFE_IRQ_MCU_CNT7_SFT 0 +#define AFE_IRQ_MCU_CNT7_MASK 0x3ffff +#define AFE_IRQ_MCU_CNT7_MASK_SFT (0x3ffff << 0) + +/* AFE_MEMIF_MSB */ +#define CPU_COMPACT_MODE_SFT 23 +#define CPU_COMPACT_MODE_MASK 0x1 +#define CPU_COMPACT_MODE_MASK_SFT (0x1 << 23) +#define CPU_HD_ALIGN_SFT 22 +#define CPU_HD_ALIGN_MASK 0x1 +#define CPU_HD_ALIGN_MASK_SFT (0x1 << 22) + +/* AFE_MEMIF_HD_MODE */ +#define HDMI_HD_SFT 20 +#define HDMI_HD_MASK 0x3 +#define HDMI_HD_MASK_SFT (0x3 << 20) +#define MOD_DAI_HD_SFT 18 +#define MOD_DAI_HD_MASK 0x3 +#define MOD_DAI_HD_MASK_SFT (0x3 << 18) +#define DAI_HD_SFT 16 +#define DAI_HD_MASK 0x3 +#define DAI_HD_MASK_SFT (0x3 << 16) +#define VUL_DATA2_HD_SFT 12 +#define VUL_DATA2_HD_MASK 0x3 +#define VUL_DATA2_HD_MASK_SFT (0x3 << 12) +#define VUL_HD_SFT 10 +#define VUL_HD_MASK 0x3 +#define VUL_HD_MASK_SFT (0x3 << 10) +#define AWB_HD_SFT 8 +#define AWB_HD_MASK 0x3 +#define AWB_HD_MASK_SFT (0x3 << 8) +#define DL3_HD_SFT 6 +#define DL3_HD_MASK 0x3 +#define DL3_HD_MASK_SFT (0x3 << 6) +#define DL2_HD_SFT 4 +#define DL2_HD_MASK 0x3 +#define DL2_HD_MASK_SFT (0x3 << 4) +#define DL1_DATA2_HD_SFT 2 +#define DL1_DATA2_HD_MASK 0x3 +#define DL1_DATA2_HD_MASK_SFT (0x3 << 2) +#define DL1_HD_SFT 0 +#define DL1_HD_MASK 0x3 +#define DL1_HD_MASK_SFT (0x3 << 0) + +/* AFE_MEMIF_HDALIGN */ +#define HDMI_NORMAL_MODE_SFT 26 +#define HDMI_NORMAL_MODE_MASK 0x1 +#define HDMI_NORMAL_MODE_MASK_SFT (0x1 << 26) +#define MOD_DAI_NORMAL_MODE_SFT 25 +#define MOD_DAI_NORMAL_MODE_MASK 0x1 +#define MOD_DAI_NORMAL_MODE_MASK_SFT (0x1 << 25) +#define DAI_NORMAL_MODE_SFT 24 +#define DAI_NORMAL_MODE_MASK 0x1 +#define DAI_NORMAL_MODE_MASK_SFT (0x1 << 24) +#define VUL_DATA2_NORMAL_MODE_SFT 22 +#define VUL_DATA2_NORMAL_MODE_MASK 0x1 +#define VUL_DATA2_NORMAL_MODE_MASK_SFT (0x1 << 22) +#define VUL_NORMAL_MODE_SFT 21 +#define VUL_NORMAL_MODE_MASK 0x1 +#define VUL_NORMAL_MODE_MASK_SFT (0x1 << 21) +#define AWB_NORMAL_MODE_SFT 20 +#define AWB_NORMAL_MODE_MASK 0x1 +#define AWB_NORMAL_MODE_MASK_SFT (0x1 << 20) +#define DL3_NORMAL_MODE_SFT 19 +#define DL3_NORMAL_MODE_MASK 0x1 +#define DL3_NORMAL_MODE_MASK_SFT (0x1 << 19) +#define DL2_NORMAL_MODE_SFT 18 +#define DL2_NORMAL_MODE_MASK 0x1 +#define DL2_NORMAL_MODE_MASK_SFT (0x1 << 18) +#define DL1_DATA2_NORMAL_MODE_SFT 17 +#define DL1_DATA2_NORMAL_MODE_MASK 0x1 +#define DL1_DATA2_NORMAL_MODE_MASK_SFT (0x1 << 17) +#define DL1_NORMAL_MODE_SFT 16 +#define DL1_NORMAL_MODE_MASK 0x1 +#define DL1_NORMAL_MODE_MASK_SFT (0x1 << 16) +#define HDMI_HD_ALIGN_SFT 10 +#define HDMI_HD_ALIGN_MASK 0x1 +#define HDMI_HD_ALIGN_MASK_SFT (0x1 << 10) +#define MOD_DAI_HD_ALIGN_SFT 9 +#define MOD_DAI_HD_ALIGN_MASK 0x1 +#define MOD_DAI_HD_ALIGN_MASK_SFT (0x1 << 9) +#define DAI_ALIGN_SFT 8 +#define DAI_ALIGN_MASK 0x1 +#define DAI_ALIGN_MASK_SFT (0x1 << 8) +#define VUL2_HD_ALIGN_SFT 7 +#define VUL2_HD_ALIGN_MASK 0x1 +#define VUL2_HD_ALIGN_MASK_SFT (0x1 << 7) +#define VUL_DATA2_HD_ALIGN_SFT 6 +#define VUL_DATA2_HD_ALIGN_MASK 0x1 +#define VUL_DATA2_HD_ALIGN_MASK_SFT (0x1 << 6) +#define VUL_HD_ALIGN_SFT 5 +#define VUL_HD_ALIGN_MASK 0x1 +#define VUL_HD_ALIGN_MASK_SFT (0x1 << 5) +#define AWB_HD_ALIGN_SFT 4 +#define AWB_HD_ALIGN_MASK 0x1 +#define AWB_HD_ALIGN_MASK_SFT (0x1 << 4) +#define DL3_HD_ALIGN_SFT 3 +#define DL3_HD_ALIGN_MASK 0x1 +#define DL3_HD_ALIGN_MASK_SFT (0x1 << 3) +#define DL2_HD_ALIGN_SFT 2 +#define DL2_HD_ALIGN_MASK 0x1 +#define DL2_HD_ALIGN_MASK_SFT (0x1 << 2) +#define DL1_DATA2_HD_ALIGN_SFT 1 +#define DL1_DATA2_HD_ALIGN_MASK 0x1 +#define DL1_DATA2_HD_ALIGN_MASK_SFT (0x1 << 1) +#define DL1_HD_ALIGN_SFT 0 +#define DL1_HD_ALIGN_MASK 0x1 +#define DL1_HD_ALIGN_MASK_SFT (0x1 << 0) + +/* PCM_INTF_CON1 */ +#define PCM_FIX_VALUE_SEL_SFT 31 +#define PCM_FIX_VALUE_SEL_MASK 0x1 +#define PCM_FIX_VALUE_SEL_MASK_SFT (0x1 << 31) +#define PCM_BUFFER_LOOPBACK_SFT 30 +#define PCM_BUFFER_LOOPBACK_MASK 0x1 +#define PCM_BUFFER_LOOPBACK_MASK_SFT (0x1 << 30) +#define PCM_PARALLEL_LOOPBACK_SFT 29 +#define PCM_PARALLEL_LOOPBACK_MASK 0x1 +#define PCM_PARALLEL_LOOPBACK_MASK_SFT (0x1 << 29) +#define PCM_SERIAL_LOOPBACK_SFT 28 +#define PCM_SERIAL_LOOPBACK_MASK 0x1 +#define PCM_SERIAL_LOOPBACK_MASK_SFT (0x1 << 28) +#define PCM_DAI_PCM_LOOPBACK_SFT 27 +#define PCM_DAI_PCM_LOOPBACK_MASK 0x1 +#define PCM_DAI_PCM_LOOPBACK_MASK_SFT (0x1 << 27) +#define PCM_I2S_PCM_LOOPBACK_SFT 26 +#define PCM_I2S_PCM_LOOPBACK_MASK 0x1 +#define PCM_I2S_PCM_LOOPBACK_MASK_SFT (0x1 << 26) +#define PCM_SYNC_DELSEL_SFT 25 +#define PCM_SYNC_DELSEL_MASK 0x1 +#define PCM_SYNC_DELSEL_MASK_SFT (0x1 << 25) +#define PCM_TX_LR_SWAP_SFT 24 +#define PCM_TX_LR_SWAP_MASK 0x1 +#define PCM_TX_LR_SWAP_MASK_SFT (0x1 << 24) +#define PCM_SYNC_OUT_INV_SFT 23 +#define PCM_SYNC_OUT_INV_MASK 0x1 +#define PCM_SYNC_OUT_INV_MASK_SFT (0x1 << 23) +#define PCM_BCLK_OUT_INV_SFT 22 +#define PCM_BCLK_OUT_INV_MASK 0x1 +#define PCM_BCLK_OUT_INV_MASK_SFT (0x1 << 22) +#define PCM_SYNC_IN_INV_SFT 21 +#define PCM_SYNC_IN_INV_MASK 0x1 +#define PCM_SYNC_IN_INV_MASK_SFT (0x1 << 21) +#define PCM_BCLK_IN_INV_SFT 20 +#define PCM_BCLK_IN_INV_MASK 0x1 +#define PCM_BCLK_IN_INV_MASK_SFT (0x1 << 20) +#define PCM_TX_LCH_RPT_SFT 19 +#define PCM_TX_LCH_RPT_MASK 0x1 +#define PCM_TX_LCH_RPT_MASK_SFT (0x1 << 19) +#define PCM_VBT_16K_MODE_SFT 18 +#define PCM_VBT_16K_MODE_MASK 0x1 +#define PCM_VBT_16K_MODE_MASK_SFT (0x1 << 18) +#define PCM_EXT_MODEM_SFT 17 +#define PCM_EXT_MODEM_MASK 0x1 +#define PCM_EXT_MODEM_MASK_SFT (0x1 << 17) +#define PCM_24BIT_SFT 16 +#define PCM_24BIT_MASK 0x1 +#define PCM_24BIT_MASK_SFT (0x1 << 16) +#define PCM_WLEN_SFT 14 +#define PCM_WLEN_MASK 0x3 +#define PCM_WLEN_MASK_SFT (0x3 << 14) +#define PCM_SYNC_LENGTH_SFT 9 +#define PCM_SYNC_LENGTH_MASK 0x1f +#define PCM_SYNC_LENGTH_MASK_SFT (0x1f << 9) +#define PCM_SYNC_TYPE_SFT 8 +#define PCM_SYNC_TYPE_MASK 0x1 +#define PCM_SYNC_TYPE_MASK_SFT (0x1 << 8) +#define PCM_BT_MODE_SFT 7 +#define PCM_BT_MODE_MASK 0x1 +#define PCM_BT_MODE_MASK_SFT (0x1 << 7) +#define PCM_BYP_ASRC_SFT 6 +#define PCM_BYP_ASRC_MASK 0x1 +#define PCM_BYP_ASRC_MASK_SFT (0x1 << 6) +#define PCM_SLAVE_SFT 5 +#define PCM_SLAVE_MASK 0x1 +#define PCM_SLAVE_MASK_SFT (0x1 << 5) +#define PCM_MODE_SFT 3 +#define PCM_MODE_MASK 0x3 +#define PCM_MODE_MASK_SFT (0x3 << 3) +#define PCM_FMT_SFT 1 +#define PCM_FMT_MASK 0x3 +#define PCM_FMT_MASK_SFT (0x3 << 1) +#define PCM_EN_SFT 0 +#define PCM_EN_MASK 0x1 +#define PCM_EN_MASK_SFT (0x1 << 0) + +/* PCM_INTF_CON2 */ +#define PCM1_TX_FIFO_OV_SFT 31 +#define PCM1_TX_FIFO_OV_MASK 0x1 +#define PCM1_TX_FIFO_OV_MASK_SFT (0x1 << 31) +#define PCM1_RX_FIFO_OV_SFT 30 +#define PCM1_RX_FIFO_OV_MASK 0x1 +#define PCM1_RX_FIFO_OV_MASK_SFT (0x1 << 30) +#define PCM2_TX_FIFO_OV_SFT 29 +#define PCM2_TX_FIFO_OV_MASK 0x1 +#define PCM2_TX_FIFO_OV_MASK_SFT (0x1 << 29) +#define PCM2_RX_FIFO_OV_SFT 28 +#define PCM2_RX_FIFO_OV_MASK 0x1 +#define PCM2_RX_FIFO_OV_MASK_SFT (0x1 << 28) +#define PCM1_SYNC_GLITCH_SFT 27 +#define PCM1_SYNC_GLITCH_MASK 0x1 +#define PCM1_SYNC_GLITCH_MASK_SFT (0x1 << 27) +#define PCM2_SYNC_GLITCH_SFT 26 +#define PCM2_SYNC_GLITCH_MASK 0x1 +#define PCM2_SYNC_GLITCH_MASK_SFT (0x1 << 26) +#define PCM1_PCM2_LOOPBACK_SFT 15 +#define PCM1_PCM2_LOOPBACK_MASK 0x1 +#define PCM1_PCM2_LOOPBACK_MASK_SFT (0x1 << 15) +#define DAI_PCM_LOOPBACK_CH_SFT 13 +#define DAI_PCM_LOOPBACK_CH_MASK 0x1 +#define DAI_PCM_LOOPBACK_CH_MASK_SFT (0x1 << 13) +#define I2S_PCM_LOOPBACK_CH_SFT 12 +#define I2S_PCM_LOOPBACK_CH_MASK 0x1 +#define I2S_PCM_LOOPBACK_CH_MASK_SFT (0x1 << 12) +#define PCM_USE_MD3_SFT 8 +#define PCM_USE_MD3_MASK 0x1 +#define PCM_USE_MD3_MASK_SFT (0x1 << 8) +#define TX_FIX_VALUE_SFT 0 +#define TX_FIX_VALUE_MASK 0xff +#define TX_FIX_VALUE_MASK_SFT (0xff << 0) + +/* PCM2_INTF_CON */ +#define PCM2_TX_FIX_VALUE_SFT 24 +#define PCM2_TX_FIX_VALUE_MASK 0xff +#define PCM2_TX_FIX_VALUE_MASK_SFT (0xff << 24) +#define PCM2_FIX_VALUE_SEL_SFT 23 +#define PCM2_FIX_VALUE_SEL_MASK 0x1 +#define PCM2_FIX_VALUE_SEL_MASK_SFT (0x1 << 23) +#define PCM2_BUFFER_LOOPBACK_SFT 22 +#define PCM2_BUFFER_LOOPBACK_MASK 0x1 +#define PCM2_BUFFER_LOOPBACK_MASK_SFT (0x1 << 22) +#define PCM2_PARALLEL_LOOPBACK_SFT 21 +#define PCM2_PARALLEL_LOOPBACK_MASK 0x1 +#define PCM2_PARALLEL_LOOPBACK_MASK_SFT (0x1 << 21) +#define PCM2_SERIAL_LOOPBACK_SFT 20 +#define PCM2_SERIAL_LOOPBACK_MASK 0x1 +#define PCM2_SERIAL_LOOPBACK_MASK_SFT (0x1 << 20) +#define PCM2_DAI_PCM_LOOPBACK_SFT 19 +#define PCM2_DAI_PCM_LOOPBACK_MASK 0x1 +#define PCM2_DAI_PCM_LOOPBACK_MASK_SFT (0x1 << 19) +#define PCM2_I2S_PCM_LOOPBACK_SFT 18 +#define PCM2_I2S_PCM_LOOPBACK_MASK 0x1 +#define PCM2_I2S_PCM_LOOPBACK_MASK_SFT (0x1 << 18) +#define PCM2_SYNC_DELSEL_SFT 17 +#define PCM2_SYNC_DELSEL_MASK 0x1 +#define PCM2_SYNC_DELSEL_MASK_SFT (0x1 << 17) +#define PCM2_TX_LR_SWAP_SFT 16 +#define PCM2_TX_LR_SWAP_MASK 0x1 +#define PCM2_TX_LR_SWAP_MASK_SFT (0x1 << 16) +#define PCM2_SYNC_IN_INV_SFT 15 +#define PCM2_SYNC_IN_INV_MASK 0x1 +#define PCM2_SYNC_IN_INV_MASK_SFT (0x1 << 15) +#define PCM2_BCLK_IN_INV_SFT 14 +#define PCM2_BCLK_IN_INV_MASK 0x1 +#define PCM2_BCLK_IN_INV_MASK_SFT (0x1 << 14) +#define PCM2_TX_LCH_RPT_SFT 13 +#define PCM2_TX_LCH_RPT_MASK 0x1 +#define PCM2_TX_LCH_RPT_MASK_SFT (0x1 << 13) +#define PCM2_VBT_16K_MODE_SFT 12 +#define PCM2_VBT_16K_MODE_MASK 0x1 +#define PCM2_VBT_16K_MODE_MASK_SFT (0x1 << 12) +#define PCM2_LOOPBACK_CH_SEL_SFT 10 +#define PCM2_LOOPBACK_CH_SEL_MASK 0x3 +#define PCM2_LOOPBACK_CH_SEL_MASK_SFT (0x3 << 10) +#define PCM2_TX2_BT_MODE_SFT 8 +#define PCM2_TX2_BT_MODE_MASK 0x1 +#define PCM2_TX2_BT_MODE_MASK_SFT (0x1 << 8) +#define PCM2_BT_MODE_SFT 7 +#define PCM2_BT_MODE_MASK 0x1 +#define PCM2_BT_MODE_MASK_SFT (0x1 << 7) +#define PCM2_AFIFO_SFT 6 +#define PCM2_AFIFO_MASK 0x1 +#define PCM2_AFIFO_MASK_SFT (0x1 << 6) +#define PCM2_WLEN_SFT 5 +#define PCM2_WLEN_MASK 0x1 +#define PCM2_WLEN_MASK_SFT (0x1 << 5) +#define PCM2_MODE_SFT 3 +#define PCM2_MODE_MASK 0x3 +#define PCM2_MODE_MASK_SFT (0x3 << 3) +#define PCM2_FMT_SFT 1 +#define PCM2_FMT_MASK 0x3 +#define PCM2_FMT_MASK_SFT (0x3 << 1) +#define PCM2_EN_SFT 0 +#define PCM2_EN_MASK 0x1 +#define PCM2_EN_MASK_SFT (0x1 << 0) +#endif diff --git a/sound/soc/mediatek/mt8173/Makefile b/sound/soc/mediatek/mt8173/Makefile new file mode 100644 index 000000000..c1eed0d26 --- /dev/null +++ b/sound/soc/mediatek/mt8173/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# MTK Platform Support +obj-$(CONFIG_SND_SOC_MT8173) += mt8173-afe-pcm.o +# Machine support +obj-$(CONFIG_SND_SOC_MT8173_MAX98090) += mt8173-max98090.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650) += mt8173-rt5650.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5514) += mt8173-rt5650-rt5514.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5676) += mt8173-rt5650-rt5676.o diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-common.h b/sound/soc/mediatek/mt8173/mt8173-afe-common.h new file mode 100644 index 000000000..396fe2355 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-afe-common.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * mt8173_afe_common.h -- Mediatek 8173 audio driver common definitions + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen + * Sascha Hauer + * Hidalgo Huang + * Ir Lian + */ + +#ifndef _MT8173_AFE_COMMON_H_ +#define _MT8173_AFE_COMMON_H_ + +#include +#include + +enum { + MT8173_AFE_MEMIF_DL1, + MT8173_AFE_MEMIF_DL2, + MT8173_AFE_MEMIF_VUL, + MT8173_AFE_MEMIF_DAI, + MT8173_AFE_MEMIF_AWB, + MT8173_AFE_MEMIF_MOD_DAI, + MT8173_AFE_MEMIF_HDMI, + MT8173_AFE_MEMIF_NUM, + MT8173_AFE_IO_MOD_PCM1 = MT8173_AFE_MEMIF_NUM, + MT8173_AFE_IO_MOD_PCM2, + MT8173_AFE_IO_PMIC, + MT8173_AFE_IO_I2S, + MT8173_AFE_IO_2ND_I2S, + MT8173_AFE_IO_HW_GAIN1, + MT8173_AFE_IO_HW_GAIN2, + MT8173_AFE_IO_MRG_O, + MT8173_AFE_IO_MRG_I, + MT8173_AFE_IO_DAIBT, + MT8173_AFE_IO_HDMI, +}; + +enum { + MT8173_AFE_IRQ_DL1, + MT8173_AFE_IRQ_DL2, + MT8173_AFE_IRQ_VUL, + MT8173_AFE_IRQ_DAI, + MT8173_AFE_IRQ_AWB, + MT8173_AFE_IRQ_MOD_DAI, + MT8173_AFE_IRQ_HDMI, + MT8173_AFE_IRQ_NUM, +}; + +enum { + MT8173_CLK_INFRASYS_AUD, + MT8173_CLK_TOP_PDN_AUD, + MT8173_CLK_TOP_PDN_AUD_BUS, + MT8173_CLK_I2S0_M, + MT8173_CLK_I2S1_M, + MT8173_CLK_I2S2_M, + MT8173_CLK_I2S3_M, + MT8173_CLK_I2S3_B, + MT8173_CLK_BCK0, + MT8173_CLK_BCK1, + MT8173_CLK_NUM +}; + +#endif diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c new file mode 100644 index 000000000..c0b669750 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -0,0 +1,1222 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Mediatek 8173 ALSA SoC AFE platform driver + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen + * Sascha Hauer + * Hidalgo Huang + * Ir Lian + */ + +#include +#include +#include +#include +#include +#include +#include +#include "mt8173-afe-common.h" +#include "../common/mtk-base-afe.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +/***************************************************************************** + * R E G I S T E R D E F I N I T I O N + *****************************************************************************/ +#define AUDIO_TOP_CON0 0x0000 +#define AUDIO_TOP_CON1 0x0004 +#define AFE_DAC_CON0 0x0010 +#define AFE_DAC_CON1 0x0014 +#define AFE_I2S_CON1 0x0034 +#define AFE_I2S_CON2 0x0038 +#define AFE_CONN_24BIT 0x006c +#define AFE_MEMIF_MSB 0x00cc + +#define AFE_CONN1 0x0024 +#define AFE_CONN2 0x0028 +#define AFE_CONN3 0x002c +#define AFE_CONN7 0x0460 +#define AFE_CONN8 0x0464 +#define AFE_HDMI_CONN0 0x0390 + +/* Memory interface */ +#define AFE_DL1_BASE 0x0040 +#define AFE_DL1_CUR 0x0044 +#define AFE_DL1_END 0x0048 +#define AFE_DL2_BASE 0x0050 +#define AFE_DL2_CUR 0x0054 +#define AFE_AWB_BASE 0x0070 +#define AFE_AWB_CUR 0x007c +#define AFE_VUL_BASE 0x0080 +#define AFE_VUL_CUR 0x008c +#define AFE_VUL_END 0x0088 +#define AFE_DAI_BASE 0x0090 +#define AFE_DAI_CUR 0x009c +#define AFE_MOD_PCM_BASE 0x0330 +#define AFE_MOD_PCM_CUR 0x033c +#define AFE_HDMI_OUT_BASE 0x0374 +#define AFE_HDMI_OUT_CUR 0x0378 +#define AFE_HDMI_OUT_END 0x037c + +#define AFE_ADDA_TOP_CON0 0x0120 +#define AFE_ADDA2_TOP_CON0 0x0600 + +#define AFE_HDMI_OUT_CON0 0x0370 + +#define AFE_IRQ_MCU_CON 0x03a0 +#define AFE_IRQ_STATUS 0x03a4 +#define AFE_IRQ_CLR 0x03a8 +#define AFE_IRQ_CNT1 0x03ac +#define AFE_IRQ_CNT2 0x03b0 +#define AFE_IRQ_MCU_EN 0x03b4 +#define AFE_IRQ_CNT5 0x03bc +#define AFE_IRQ_CNT7 0x03dc + +#define AFE_TDM_CON1 0x0548 +#define AFE_TDM_CON2 0x054c + +#define AFE_IRQ_STATUS_BITS 0xff + +/* AUDIO_TOP_CON0 (0x0000) */ +#define AUD_TCON0_PDN_SPDF (0x1 << 21) +#define AUD_TCON0_PDN_HDMI (0x1 << 20) +#define AUD_TCON0_PDN_24M (0x1 << 9) +#define AUD_TCON0_PDN_22M (0x1 << 8) +#define AUD_TCON0_PDN_AFE (0x1 << 2) + +/* AFE_I2S_CON1 (0x0034) */ +#define AFE_I2S_CON1_LOW_JITTER_CLK (0x1 << 12) +#define AFE_I2S_CON1_RATE(x) (((x) & 0xf) << 8) +#define AFE_I2S_CON1_FORMAT_I2S (0x1 << 3) +#define AFE_I2S_CON1_EN (0x1 << 0) + +/* AFE_I2S_CON2 (0x0038) */ +#define AFE_I2S_CON2_LOW_JITTER_CLK (0x1 << 12) +#define AFE_I2S_CON2_RATE(x) (((x) & 0xf) << 8) +#define AFE_I2S_CON2_FORMAT_I2S (0x1 << 3) +#define AFE_I2S_CON2_EN (0x1 << 0) + +/* AFE_CONN_24BIT (0x006c) */ +#define AFE_CONN_24BIT_O04 (0x1 << 4) +#define AFE_CONN_24BIT_O03 (0x1 << 3) + +/* AFE_HDMI_CONN0 (0x0390) */ +#define AFE_HDMI_CONN0_O37_I37 (0x7 << 21) +#define AFE_HDMI_CONN0_O36_I36 (0x6 << 18) +#define AFE_HDMI_CONN0_O35_I33 (0x3 << 15) +#define AFE_HDMI_CONN0_O34_I32 (0x2 << 12) +#define AFE_HDMI_CONN0_O33_I35 (0x5 << 9) +#define AFE_HDMI_CONN0_O32_I34 (0x4 << 6) +#define AFE_HDMI_CONN0_O31_I31 (0x1 << 3) +#define AFE_HDMI_CONN0_O30_I30 (0x0 << 0) + +/* AFE_TDM_CON1 (0x0548) */ +#define AFE_TDM_CON1_LRCK_WIDTH(x) (((x) - 1) << 24) +#define AFE_TDM_CON1_32_BCK_CYCLES (0x2 << 12) +#define AFE_TDM_CON1_WLEN_32BIT (0x2 << 8) +#define AFE_TDM_CON1_MSB_ALIGNED (0x1 << 4) +#define AFE_TDM_CON1_1_BCK_DELAY (0x1 << 3) +#define AFE_TDM_CON1_LRCK_INV (0x1 << 2) +#define AFE_TDM_CON1_BCK_INV (0x1 << 1) +#define AFE_TDM_CON1_EN (0x1 << 0) + +enum afe_tdm_ch_start { + AFE_TDM_CH_START_O30_O31 = 0, + AFE_TDM_CH_START_O32_O33, + AFE_TDM_CH_START_O34_O35, + AFE_TDM_CH_START_O36_O37, + AFE_TDM_CH_ZERO, +}; + +static const unsigned int mt8173_afe_backup_list[] = { + AUDIO_TOP_CON0, + AFE_CONN1, + AFE_CONN2, + AFE_CONN7, + AFE_CONN8, + AFE_DAC_CON1, + AFE_DL1_BASE, + AFE_DL1_END, + AFE_VUL_BASE, + AFE_VUL_END, + AFE_HDMI_OUT_BASE, + AFE_HDMI_OUT_END, + AFE_HDMI_CONN0, + AFE_DAC_CON0, +}; + +struct mt8173_afe_private { + struct clk *clocks[MT8173_CLK_NUM]; +}; + +static const struct snd_pcm_hardware mt8173_afe_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .buffer_bytes_max = 256 * 1024, + .period_bytes_min = 512, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = 256, + .fifo_size = 0, +}; + +struct mt8173_afe_rate { + unsigned int rate; + unsigned int regvalue; +}; + +static const struct mt8173_afe_rate mt8173_afe_i2s_rates[] = { + { .rate = 8000, .regvalue = 0 }, + { .rate = 11025, .regvalue = 1 }, + { .rate = 12000, .regvalue = 2 }, + { .rate = 16000, .regvalue = 4 }, + { .rate = 22050, .regvalue = 5 }, + { .rate = 24000, .regvalue = 6 }, + { .rate = 32000, .regvalue = 8 }, + { .rate = 44100, .regvalue = 9 }, + { .rate = 48000, .regvalue = 10 }, + { .rate = 88000, .regvalue = 11 }, + { .rate = 96000, .regvalue = 12 }, + { .rate = 174000, .regvalue = 13 }, + { .rate = 192000, .regvalue = 14 }, +}; + +static int mt8173_afe_i2s_fs(unsigned int sample_rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mt8173_afe_i2s_rates); i++) + if (mt8173_afe_i2s_rates[i].rate == sample_rate) + return mt8173_afe_i2s_rates[i].regvalue; + + return -EINVAL; +} + +static int mt8173_afe_set_i2s(struct mtk_base_afe *afe, unsigned int rate) +{ + unsigned int val; + int fs = mt8173_afe_i2s_fs(rate); + + if (fs < 0) + return -EINVAL; + + /* from external ADC */ + regmap_update_bits(afe->regmap, AFE_ADDA_TOP_CON0, 0x1, 0x1); + regmap_update_bits(afe->regmap, AFE_ADDA2_TOP_CON0, 0x1, 0x1); + + /* set input */ + val = AFE_I2S_CON2_LOW_JITTER_CLK | + AFE_I2S_CON2_RATE(fs) | + AFE_I2S_CON2_FORMAT_I2S; + + regmap_update_bits(afe->regmap, AFE_I2S_CON2, ~AFE_I2S_CON2_EN, val); + + /* set output */ + val = AFE_I2S_CON1_LOW_JITTER_CLK | + AFE_I2S_CON1_RATE(fs) | + AFE_I2S_CON1_FORMAT_I2S; + + regmap_update_bits(afe->regmap, AFE_I2S_CON1, ~AFE_I2S_CON1_EN, val); + return 0; +} + +static void mt8173_afe_set_i2s_enable(struct mtk_base_afe *afe, bool enable) +{ + unsigned int val; + + regmap_read(afe->regmap, AFE_I2S_CON2, &val); + if (!!(val & AFE_I2S_CON2_EN) == enable) + return; + + /* input */ + regmap_update_bits(afe->regmap, AFE_I2S_CON2, 0x1, enable); + + /* output */ + regmap_update_bits(afe->regmap, AFE_I2S_CON1, 0x1, enable); +} + +static int mt8173_afe_dais_enable_clks(struct mtk_base_afe *afe, + struct clk *m_ck, struct clk *b_ck) +{ + int ret; + + if (m_ck) { + ret = clk_prepare_enable(m_ck); + if (ret) { + dev_err(afe->dev, "Failed to enable m_ck\n"); + return ret; + } + } + + if (b_ck) { + ret = clk_prepare_enable(b_ck); + if (ret) { + dev_err(afe->dev, "Failed to enable b_ck\n"); + return ret; + } + } + return 0; +} + +static int mt8173_afe_dais_set_clks(struct mtk_base_afe *afe, + struct clk *m_ck, unsigned int mck_rate, + struct clk *b_ck, unsigned int bck_rate) +{ + int ret; + + if (m_ck) { + ret = clk_set_rate(m_ck, mck_rate); + if (ret) { + dev_err(afe->dev, "Failed to set m_ck rate\n"); + return ret; + } + } + + if (b_ck) { + ret = clk_set_rate(b_ck, bck_rate); + if (ret) { + dev_err(afe->dev, "Failed to set b_ck rate\n"); + return ret; + } + } + return 0; +} + +static void mt8173_afe_dais_disable_clks(struct mtk_base_afe *afe, + struct clk *m_ck, struct clk *b_ck) +{ + if (m_ck) + clk_disable_unprepare(m_ck); + if (b_ck) + clk_disable_unprepare(b_ck); +} + +static int mt8173_afe_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + if (dai->active) + return 0; + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M, 0); + return 0; +} + +static void mt8173_afe_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + if (dai->active) + return; + + mt8173_afe_set_i2s_enable(afe, false); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M, + AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M); +} + +static int mt8173_afe_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime * const runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + int ret; + + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S1_M], + runtime->rate * 256, NULL, 0); + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S2_M], + runtime->rate * 256, NULL, 0); + /* config I2S */ + ret = mt8173_afe_set_i2s(afe, substream->runtime->rate); + if (ret) + return ret; + + mt8173_afe_set_i2s_enable(afe, true); + + return 0; +} + +static int mt8173_afe_hdmi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + if (dai->active) + return 0; + + mt8173_afe_dais_enable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + afe_priv->clocks[MT8173_CLK_I2S3_B]); + return 0; +} + +static void mt8173_afe_hdmi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + if (dai->active) + return; + + mt8173_afe_dais_disable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + afe_priv->clocks[MT8173_CLK_I2S3_B]); +} + +static int mt8173_afe_hdmi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime * const runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + unsigned int val; + + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + runtime->rate * 128, + afe_priv->clocks[MT8173_CLK_I2S3_B], + runtime->rate * runtime->channels * 32); + + val = AFE_TDM_CON1_BCK_INV | + AFE_TDM_CON1_LRCK_INV | + AFE_TDM_CON1_1_BCK_DELAY | + AFE_TDM_CON1_MSB_ALIGNED | /* I2S mode */ + AFE_TDM_CON1_WLEN_32BIT | + AFE_TDM_CON1_32_BCK_CYCLES | + AFE_TDM_CON1_LRCK_WIDTH(32); + regmap_update_bits(afe->regmap, AFE_TDM_CON1, ~AFE_TDM_CON1_EN, val); + + /* set tdm2 config */ + switch (runtime->channels) { + case 1: + case 2: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_ZERO << 4); + val |= (AFE_TDM_CH_ZERO << 8); + val |= (AFE_TDM_CH_ZERO << 12); + break; + case 3: + case 4: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_START_O32_O33 << 4); + val |= (AFE_TDM_CH_ZERO << 8); + val |= (AFE_TDM_CH_ZERO << 12); + break; + case 5: + case 6: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_START_O32_O33 << 4); + val |= (AFE_TDM_CH_START_O34_O35 << 8); + val |= (AFE_TDM_CH_ZERO << 12); + break; + case 7: + case 8: + val = AFE_TDM_CH_START_O30_O31; + val |= (AFE_TDM_CH_START_O32_O33 << 4); + val |= (AFE_TDM_CH_START_O34_O35 << 8); + val |= (AFE_TDM_CH_START_O36_O37 << 12); + break; + default: + val = 0; + } + regmap_update_bits(afe->regmap, AFE_TDM_CON2, 0x0000ffff, val); + + regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, + 0x000000f0, runtime->channels << 4); + return 0; +} + +static int mt8173_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + dev_info(afe->dev, "%s cmd=%d %s\n", __func__, cmd, dai->name); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF, 0); + + /* set connections: O30~O37: L/R/LS/RS/C/LFE/CH7/CH8 */ + regmap_write(afe->regmap, AFE_HDMI_CONN0, + AFE_HDMI_CONN0_O30_I30 | + AFE_HDMI_CONN0_O31_I31 | + AFE_HDMI_CONN0_O32_I34 | + AFE_HDMI_CONN0_O33_I35 | + AFE_HDMI_CONN0_O34_I32 | + AFE_HDMI_CONN0_O35_I33 | + AFE_HDMI_CONN0_O36_I36 | + AFE_HDMI_CONN0_O37_I37); + + /* enable Out control */ + regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, 0x1, 0x1); + + /* enable tdm */ + regmap_update_bits(afe->regmap, AFE_TDM_CON1, 0x1, 0x1); + + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + /* disable tdm */ + regmap_update_bits(afe->regmap, AFE_TDM_CON1, 0x1, 0); + + /* disable Out control */ + regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, 0x1, 0); + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF, + AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF); + return 0; + default: + return -EINVAL; + } +} + +static int mt8173_memif_fs(struct snd_pcm_substream *substream, + unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int fs; + + if (memif->data->id == MT8173_AFE_MEMIF_DAI || + memif->data->id == MT8173_AFE_MEMIF_MOD_DAI) { + switch (rate) { + case 8000: + fs = 0; + break; + case 16000: + fs = 1; + break; + case 32000: + fs = 2; + break; + default: + return -EINVAL; + } + } else { + fs = mt8173_afe_i2s_fs(rate); + } + return fs; +} + +static int mt8173_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + return mt8173_afe_i2s_fs(rate); +} + +/* BE DAIs */ +static const struct snd_soc_dai_ops mt8173_afe_i2s_ops = { + .startup = mt8173_afe_i2s_startup, + .shutdown = mt8173_afe_i2s_shutdown, + .prepare = mt8173_afe_i2s_prepare, +}; + +static const struct snd_soc_dai_ops mt8173_afe_hdmi_ops = { + .startup = mt8173_afe_hdmi_startup, + .shutdown = mt8173_afe_hdmi_shutdown, + .prepare = mt8173_afe_hdmi_prepare, + .trigger = mt8173_afe_hdmi_trigger, +}; + +static struct snd_soc_dai_driver mt8173_afe_pcm_dais[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "DL1", /* downlink 1 */ + .id = MT8173_AFE_MEMIF_DL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mtk_afe_fe_ops, + }, { + .name = "VUL", /* voice uplink */ + .id = MT8173_AFE_MEMIF_VUL, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "VUL", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mtk_afe_fe_ops, + }, { + /* BE DAIs */ + .name = "I2S", + .id = MT8173_AFE_IO_I2S, + .playback = { + .stream_name = "I2S Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "I2S Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt8173_afe_i2s_ops, + .symmetric_rates = 1, + }, +}; + +static struct snd_soc_dai_driver mt8173_afe_hdmi_dais[] = { + /* FE DAIs */ + { + .name = "HDMI", + .id = MT8173_AFE_MEMIF_HDMI, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "HDMI", + .channels_min = 2, + .channels_max = 8, + .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, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mtk_afe_fe_ops, + }, { + /* BE DAIs */ + .name = "HDMIO", + .id = MT8173_AFE_IO_HDMI, + .playback = { + .stream_name = "HDMIO Playback", + .channels_min = 2, + .channels_max = 8, + .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, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt8173_afe_hdmi_ops, + }, +}; + +static const struct snd_kcontrol_new mt8173_afe_o03_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I05 Switch", AFE_CONN1, 21, 1, 0), +}; + +static const struct snd_kcontrol_new mt8173_afe_o04_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I06 Switch", AFE_CONN2, 6, 1, 0), +}; + +static const struct snd_kcontrol_new mt8173_afe_o09_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 0, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN7, 30, 1, 0), +}; + +static const struct snd_kcontrol_new mt8173_afe_o10_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I04 Switch", AFE_CONN3, 3, 1, 0), + SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN8, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget mt8173_afe_pcm_widgets[] = { + /* inter-connections */ + SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I04", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I05", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I06", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I17", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0, + mt8173_afe_o03_mix, ARRAY_SIZE(mt8173_afe_o03_mix)), + SND_SOC_DAPM_MIXER("O04", SND_SOC_NOPM, 0, 0, + mt8173_afe_o04_mix, ARRAY_SIZE(mt8173_afe_o04_mix)), + SND_SOC_DAPM_MIXER("O09", SND_SOC_NOPM, 0, 0, + mt8173_afe_o09_mix, ARRAY_SIZE(mt8173_afe_o09_mix)), + SND_SOC_DAPM_MIXER("O10", SND_SOC_NOPM, 0, 0, + mt8173_afe_o10_mix, ARRAY_SIZE(mt8173_afe_o10_mix)), +}; + +static const struct snd_soc_dapm_route mt8173_afe_pcm_routes[] = { + {"I05", NULL, "DL1"}, + {"I06", NULL, "DL1"}, + {"I2S Playback", NULL, "O03"}, + {"I2S Playback", NULL, "O04"}, + {"VUL", NULL, "O09"}, + {"VUL", NULL, "O10"}, + {"I03", NULL, "I2S Capture"}, + {"I04", NULL, "I2S Capture"}, + {"I17", NULL, "I2S Capture"}, + {"I18", NULL, "I2S Capture"}, + { "O03", "I05 Switch", "I05" }, + { "O04", "I06 Switch", "I06" }, + { "O09", "I17 Switch", "I17" }, + { "O09", "I03 Switch", "I03" }, + { "O10", "I18 Switch", "I18" }, + { "O10", "I04 Switch", "I04" }, +}; + +static const struct snd_soc_dapm_route mt8173_afe_hdmi_routes[] = { + {"HDMIO Playback", NULL, "HDMI"}, +}; + +static const struct snd_soc_component_driver mt8173_afe_pcm_dai_component = { + .name = "mt8173-afe-pcm-dai", + .dapm_widgets = mt8173_afe_pcm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_afe_pcm_widgets), + .dapm_routes = mt8173_afe_pcm_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_afe_pcm_routes), +}; + +static const struct snd_soc_component_driver mt8173_afe_hdmi_dai_component = { + .name = "mt8173-afe-hdmi-dai", + .dapm_routes = mt8173_afe_hdmi_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_afe_hdmi_routes), +}; + +static const char *aud_clks[MT8173_CLK_NUM] = { + [MT8173_CLK_INFRASYS_AUD] = "infra_sys_audio_clk", + [MT8173_CLK_TOP_PDN_AUD] = "top_pdn_audio", + [MT8173_CLK_TOP_PDN_AUD_BUS] = "top_pdn_aud_intbus", + [MT8173_CLK_I2S0_M] = "i2s0_m", + [MT8173_CLK_I2S1_M] = "i2s1_m", + [MT8173_CLK_I2S2_M] = "i2s2_m", + [MT8173_CLK_I2S3_M] = "i2s3_m", + [MT8173_CLK_I2S3_B] = "i2s3_b", + [MT8173_CLK_BCK0] = "bck0", + [MT8173_CLK_BCK1] = "bck1", +}; + +static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { + { + .name = "DL1", + .id = MT8173_AFE_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 21, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 1, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 0, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "DL2", + .id = MT8173_AFE_MEMIF_DL2, + .reg_ofs_base = AFE_DL2_BASE, + .reg_ofs_cur = AFE_DL2_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 4, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 22, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 2, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "VUL", + .id = MT8173_AFE_MEMIF_VUL, + .reg_ofs_base = AFE_VUL_BASE, + .reg_ofs_cur = AFE_VUL_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 16, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 27, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 3, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 6, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "DAI", + .id = MT8173_AFE_MEMIF_DAI, + .reg_ofs_base = AFE_DAI_BASE, + .reg_ofs_cur = AFE_DAI_CUR, + .fs_reg = AFE_DAC_CON0, + .fs_shift = 24, + .fs_maskbit = 0x3, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 4, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 5, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "AWB", + .id = MT8173_AFE_MEMIF_AWB, + .reg_ofs_base = AFE_AWB_BASE, + .reg_ofs_cur = AFE_AWB_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 12, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 24, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 6, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 3, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "MOD_DAI", + .id = MT8173_AFE_MEMIF_MOD_DAI, + .reg_ofs_base = AFE_MOD_PCM_BASE, + .reg_ofs_cur = AFE_MOD_PCM_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 30, + .fs_maskbit = 0x3, + .mono_reg = AFE_DAC_CON1, + .mono_shift = 30, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 7, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 4, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, { + .name = "HDMI", + .id = MT8173_AFE_MEMIF_HDMI, + .reg_ofs_base = AFE_HDMI_OUT_BASE, + .reg_ofs_cur = AFE_HDMI_OUT_CUR, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = -1, + .enable_shift = -1, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 8, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[MT8173_AFE_IRQ_NUM] = { + { + .id = MT8173_AFE_IRQ_DL1, + .irq_cnt_reg = AFE_IRQ_CNT1, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 0, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 4, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 0, + }, { + .id = MT8173_AFE_IRQ_DL2, + .irq_cnt_reg = AFE_IRQ_CNT1, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 2, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 16, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 2, + + }, { + .id = MT8173_AFE_IRQ_VUL, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 1, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 8, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 1, + }, { + .id = MT8173_AFE_IRQ_DAI, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 3, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 20, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 3, + }, { + .id = MT8173_AFE_IRQ_AWB, + .irq_cnt_reg = AFE_IRQ_CNT7, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 14, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 6, + }, { + .id = MT8173_AFE_IRQ_DAI, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 3, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 20, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 3, + }, { + .id = MT8173_AFE_IRQ_HDMI, + .irq_cnt_reg = AFE_IRQ_CNT5, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 12, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 4, + }, +}; + +static const struct regmap_config mt8173_afe_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_ADDA2_TOP_CON0, + .cache_type = REGCACHE_NONE, +}; + +static irqreturn_t mt8173_afe_irq_handler(int irq, void *dev_id) +{ + struct mtk_base_afe *afe = dev_id; + unsigned int reg_value; + int i, ret; + + ret = regmap_read(afe->regmap, AFE_IRQ_STATUS, ®_value); + if (ret) { + dev_err(afe->dev, "%s irq status err\n", __func__); + reg_value = AFE_IRQ_STATUS_BITS; + goto err_irq; + } + + for (i = 0; i < MT8173_AFE_MEMIF_NUM; i++) { + struct mtk_base_afe_memif *memif = &afe->memif[i]; + struct mtk_base_afe_irq *irq; + + if (memif->irq_usage < 0) + continue; + + irq = &afe->irqs[memif->irq_usage]; + + if (!(reg_value & (1 << irq->irq_data->irq_clr_shift))) + continue; + + snd_pcm_period_elapsed(memif->substream); + } + +err_irq: + /* clear irq */ + regmap_write(afe->regmap, AFE_IRQ_CLR, + reg_value & AFE_IRQ_STATUS_BITS); + + return IRQ_HANDLED; +} + +static int mt8173_afe_runtime_suspend(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + /* disable AFE */ + regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0); + + /* disable AFE clk */ + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUD_TCON0_PDN_AFE, AUD_TCON0_PDN_AFE); + + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK1]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); + return 0; +} + +static int mt8173_afe_runtime_resume(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + int ret; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); + if (ret) + return ret; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); + if (ret) + goto err_infra; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); + if (ret) + goto err_top_aud_bus; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK0]); + if (ret) + goto err_top_aud; + + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK1]); + if (ret) + goto err_bck0; + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S1_M]); + if (ret) + goto err_i2s1_m; + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S2_M]); + if (ret) + goto err_i2s2_m; + + /* enable AFE clk */ + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_AFE, 0); + + /* set O3/O4 16bits */ + regmap_update_bits(afe->regmap, AFE_CONN_24BIT, + AFE_CONN_24BIT_O03 | AFE_CONN_24BIT_O04, 0); + + /* unmask all IRQs */ + regmap_update_bits(afe->regmap, AFE_IRQ_MCU_EN, 0xff, 0xff); + + /* enable AFE */ + regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); + return 0; + +err_i2s1_m: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); +err_i2s2_m: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); +err_bck0: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); +err_top_aud: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); +err_top_aud_bus: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); +err_infra: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); + return ret; +} + +static int mt8173_afe_init_audio_clk(struct mtk_base_afe *afe) +{ + size_t i; + struct mt8173_afe_private *afe_priv = afe->platform_priv; + + for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { + afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); + if (IS_ERR(afe_priv->clocks[i])) { + dev_err(afe->dev, "%s devm_clk_get %s fail\n", + __func__, aud_clks[i]); + return PTR_ERR(afe_priv->clocks[i]); + } + } + clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK0], 22579200); /* 22M */ + clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK1], 24576000); /* 24M */ + return 0; +} + +static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) +{ + int ret, i; + int irq_id; + struct mtk_base_afe *afe; + struct mt8173_afe_private *afe_priv; + struct resource *res; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(33)); + if (ret) + return ret; + + afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), + GFP_KERNEL); + afe_priv = afe->platform_priv; + if (!afe_priv) + return -ENOMEM; + + afe->dev = &pdev->dev; + + irq_id = platform_get_irq(pdev, 0); + if (irq_id <= 0) { + dev_err(afe->dev, "np %s no irq\n", afe->dev->of_node->name); + return irq_id < 0 ? irq_id : -ENXIO; + } + ret = devm_request_irq(afe->dev, irq_id, mt8173_afe_irq_handler, + 0, "Afe_ISR_Handle", (void *)afe); + if (ret) { + dev_err(afe->dev, "could not request_irq\n"); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + afe->base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &mt8173_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + /* initial audio related clock */ + ret = mt8173_afe_init_audio_clk(afe); + if (ret) { + dev_err(afe->dev, "mt8173_afe_init_audio_clk fail\n"); + return ret; + } + + /* memif % irq initialize*/ + afe->memif_size = MT8173_AFE_MEMIF_NUM; + afe->memif = devm_kcalloc(afe->dev, afe->memif_size, + sizeof(*afe->memif), GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + afe->irqs_size = MT8173_AFE_IRQ_NUM; + afe->irqs = devm_kcalloc(afe->dev, afe->irqs_size, + sizeof(*afe->irqs), GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) { + afe->memif[i].data = &memif_data[i]; + afe->irqs[i].irq_data = &irq_data[i]; + afe->irqs[i].irq_occupyed = true; + afe->memif[i].irq_usage = i; + afe->memif[i].const_irq = 1; + } + + afe->mtk_afe_hardware = &mt8173_afe_hardware; + afe->memif_fs = mt8173_memif_fs; + afe->irq_fs = mt8173_irq_fs; + + platform_set_drvdata(pdev, afe); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = mt8173_afe_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + afe->reg_back_up_list = mt8173_afe_backup_list; + afe->reg_back_up_list_num = ARRAY_SIZE(mt8173_afe_backup_list); + afe->runtime_resume = mt8173_afe_runtime_resume; + afe->runtime_suspend = mt8173_afe_runtime_suspend; + + ret = devm_snd_soc_register_component(&pdev->dev, + &mtk_afe_pcm_platform, + NULL, 0); + if (ret) + goto err_pm_disable; + + ret = devm_snd_soc_register_component(&pdev->dev, + &mt8173_afe_pcm_dai_component, + mt8173_afe_pcm_dais, + ARRAY_SIZE(mt8173_afe_pcm_dais)); + if (ret) + goto err_pm_disable; + + ret = devm_snd_soc_register_component(&pdev->dev, + &mt8173_afe_hdmi_dai_component, + mt8173_afe_hdmi_dais, + ARRAY_SIZE(mt8173_afe_hdmi_dais)); + if (ret) + goto err_pm_disable; + + dev_info(&pdev->dev, "MT8173 AFE driver initialized.\n"); + return 0; + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int mt8173_afe_pcm_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + mt8173_afe_runtime_suspend(&pdev->dev); + return 0; +} + +static const struct of_device_id mt8173_afe_pcm_dt_match[] = { + { .compatible = "mediatek,mt8173-afe-pcm", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_afe_pcm_dt_match); + +static const struct dev_pm_ops mt8173_afe_pm_ops = { + SET_RUNTIME_PM_OPS(mt8173_afe_runtime_suspend, + mt8173_afe_runtime_resume, NULL) +}; + +static struct platform_driver mt8173_afe_pcm_driver = { + .driver = { + .name = "mt8173-afe-pcm", + .of_match_table = mt8173_afe_pcm_dt_match, + .pm = &mt8173_afe_pm_ops, + }, + .probe = mt8173_afe_pcm_dev_probe, + .remove = mt8173_afe_pcm_dev_remove, +}; + +module_platform_driver(mt8173_afe_pcm_driver); + +MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver"); +MODULE_AUTHOR("Koro Chen "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mediatek/mt8173/mt8173-max98090.c b/sound/soc/mediatek/mt8173/mt8173-max98090.c new file mode 100644 index 000000000..c9fc719c2 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-max98090.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt8173-max98090.c -- MT8173 MAX98090 ALSA SoC machine driver + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen + */ + +#include +#include +#include +#include +#include "../../codecs/max98090.h" + +static struct snd_soc_jack mt8173_max98090_jack; + +static struct snd_soc_jack_pin mt8173_max98090_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static const struct snd_soc_dapm_widget mt8173_max98090_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_max98090_routes[] = { + {"Speaker", NULL, "SPKL"}, + {"Speaker", NULL, "SPKR"}, + {"DMICL", NULL, "Int Mic"}, + {"Headphone", NULL, "HPL"}, + {"Headphone", NULL, "HPR"}, + {"Headset Mic", NULL, "MICBIAS"}, + {"IN34", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new mt8173_max98090_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_max98090_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + return snd_soc_dai_set_sysclk(codec_dai, 0, params_rate(params) * 256, + SND_SOC_CLOCK_IN); +} + +static const struct snd_soc_ops mt8173_max98090_ops = { + .hw_params = mt8173_max98090_hw_params, +}; + +static int mt8173_max98090_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + struct snd_soc_card *card = runtime->card; + struct snd_soc_component *component = runtime->codec_dai->component; + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headphone", SND_JACK_HEADPHONE, + &mt8173_max98090_jack, + mt8173_max98090_jack_pins, + ARRAY_SIZE(mt8173_max98090_jack_pins)); + if (ret) { + dev_err(card->dev, "Can't create a new Jack %d\n", ret); + return ret; + } + + return max98090_mic_detect(component, &mt8173_max98090_jack); +} + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_max98090_dais[] = { + /* Front End DAI links */ + { + .name = "MAX98090 Playback", + .stream_name = "MAX98090 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + { + .name = "MAX98090 Capture", + .stream_name = "MAX98090 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* Back End DAI links */ + { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codec_dai_name = "HiFi", + .init = mt8173_max98090_init, + .ops = &mt8173_max98090_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt8173_max98090_card = { + .name = "mt8173-max98090", + .owner = THIS_MODULE, + .dai_link = mt8173_max98090_dais, + .num_links = ARRAY_SIZE(mt8173_max98090_dais), + .controls = mt8173_max98090_controls, + .num_controls = ARRAY_SIZE(mt8173_max98090_controls), + .dapm_widgets = mt8173_max98090_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_max98090_widgets), + .dapm_routes = mt8173_max98090_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_max98090_routes), +}; + +static int mt8173_max98090_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_max98090_card; + struct device_node *codec_node, *platform_node; + int ret, i; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt8173_max98090_dais[i].platform_name) + continue; + mt8173_max98090_dais[i].platform_of_node = platform_node; + } + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto put_platform_node; + } + for (i = 0; i < card->num_links; i++) { + if (mt8173_max98090_dais[i].codec_name) + continue; + mt8173_max98090_dais[i].codec_of_node = codec_node; + } + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + of_node_put(codec_node); + +put_platform_node: + of_node_put(platform_node); + return ret; +} + +static const struct of_device_id mt8173_max98090_dt_match[] = { + { .compatible = "mediatek,mt8173-max98090", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_max98090_dt_match); + +static struct platform_driver mt8173_max98090_driver = { + .driver = { + .name = "mt8173-max98090", + .of_match_table = mt8173_max98090_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_max98090_dev_probe, +}; + +module_platform_driver(mt8173_max98090_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 MAX98090 ALSA SoC machine driver"); +MODULE_AUTHOR("Koro Chen "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mt8173-max98090"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c new file mode 100644 index 000000000..cdb394071 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt8173-rt5650-rt5514.c -- MT8173 machine driver with RT5650/5514 codecs + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Koro Chen + */ + +#include +#include +#include +#include +#include +#include "../../codecs/rt5645.h" + +#define MCLK_FOR_CODECS 12288000 + +static const struct snd_soc_dapm_widget mt8173_rt5650_rt5514_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_rt5650_rt5514_routes[] = { + {"Speaker", NULL, "SPOL"}, + {"Speaker", NULL, "SPOR"}, + {"Sub DMIC1L", NULL, "Int Mic"}, + {"Sub DMIC1R", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new mt8173_rt5650_rt5514_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_rt5650_rt5514_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + /* pll from mclk 12.288M */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, MCLK_FOR_CODECS, + params_rate(params) * 512); + if (ret) + return ret; + + /* sysclk from pll */ + ret = snd_soc_dai_set_sysclk(codec_dai, 1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + return 0; +} + +static const struct snd_soc_ops mt8173_rt5650_rt5514_ops = { + .hw_params = mt8173_rt5650_rt5514_hw_params, +}; + +static struct snd_soc_jack mt8173_rt5650_rt5514_jack; + +static int mt8173_rt5650_rt5514_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_component *component = runtime->codec_dais[0]->component; + int ret; + + rt5645_sel_asrc_clk_src(component, + RT5645_DA_STEREO_FILTER | + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &mt8173_rt5650_rt5514_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Can't new Headset Jack %d\n", ret); + return ret; + } + + return rt5645_set_jack_detect(component, + &mt8173_rt5650_rt5514_jack, + &mt8173_rt5650_rt5514_jack, + &mt8173_rt5650_rt5514_jack); +} + +static struct snd_soc_dai_link_component mt8173_rt5650_rt5514_codecs[] = { + { + .dai_name = "rt5645-aif1", + }, + { + .dai_name = "rt5514-aif1", + }, +}; + +enum { + DAI_LINK_PLAYBACK, + DAI_LINK_CAPTURE, + DAI_LINK_CODEC_I2S, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_rt5650_rt5514_dais[] = { + /* Front End DAI links */ + [DAI_LINK_PLAYBACK] = { + .name = "rt5650_rt5514 Playback", + .stream_name = "rt5650_rt5514 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_CAPTURE] = { + .name = "rt5650_rt5514 Capture", + .stream_name = "rt5650_rt5514 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* Back End DAI links */ + [DAI_LINK_CODEC_I2S] = { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codecs = mt8173_rt5650_rt5514_codecs, + .num_codecs = 2, + .init = mt8173_rt5650_rt5514_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &mt8173_rt5650_rt5514_ops, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_codec_conf mt8173_rt5650_rt5514_codec_conf[] = { + { + .name_prefix = "Sub", + }, +}; + +static struct snd_soc_card mt8173_rt5650_rt5514_card = { + .name = "mtk-rt5650-rt5514", + .owner = THIS_MODULE, + .dai_link = mt8173_rt5650_rt5514_dais, + .num_links = ARRAY_SIZE(mt8173_rt5650_rt5514_dais), + .codec_conf = mt8173_rt5650_rt5514_codec_conf, + .num_configs = ARRAY_SIZE(mt8173_rt5650_rt5514_codec_conf), + .controls = mt8173_rt5650_rt5514_controls, + .num_controls = ARRAY_SIZE(mt8173_rt5650_rt5514_controls), + .dapm_widgets = mt8173_rt5650_rt5514_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_rt5514_widgets), + .dapm_routes = mt8173_rt5650_rt5514_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_rt5514_routes), +}; + +static int mt8173_rt5650_rt5514_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_rt5650_rt5514_card; + struct device_node *platform_node; + int i, ret; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < card->num_links; i++) { + if (mt8173_rt5650_rt5514_dais[i].platform_name) + continue; + mt8173_rt5650_rt5514_dais[i].platform_of_node = platform_node; + } + + mt8173_rt5650_rt5514_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0); + if (!mt8173_rt5650_rt5514_codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5514_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); + if (!mt8173_rt5650_rt5514_codecs[1].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5514_codec_conf[0].of_node = + mt8173_rt5650_rt5514_codecs[1].of_node; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + of_node_put(platform_node); + return ret; +} + +static const struct of_device_id mt8173_rt5650_rt5514_dt_match[] = { + { .compatible = "mediatek,mt8173-rt5650-rt5514", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_rt5650_rt5514_dt_match); + +static struct platform_driver mt8173_rt5650_rt5514_driver = { + .driver = { + .name = "mtk-rt5650-rt5514", + .of_match_table = mt8173_rt5650_rt5514_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_rt5650_rt5514_dev_probe, +}; + +module_platform_driver(mt8173_rt5650_rt5514_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 RT5650 and RT5514 SoC machine driver"); +MODULE_AUTHOR("Koro Chen "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mtk-rt5650-rt5514"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c new file mode 100644 index 000000000..242f99716 --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt8173-rt5650-rt5676.c -- MT8173 machine driver with RT5650/5676 codecs + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen + */ + +#include +#include +#include +#include +#include +#include "../../codecs/rt5645.h" +#include "../../codecs/rt5677.h" + +#define MCLK_FOR_CODECS 12288000 + +static const struct snd_soc_dapm_widget mt8173_rt5650_rt5676_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_rt5650_rt5676_routes[] = { + {"Speaker", NULL, "SPOL"}, + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "Sub AIF2TX"}, /* IF2 ADC to 5650 */ + {"Sub DMIC L1", NULL, "Int Mic"}, /* DMIC from 5676 */ + {"Sub DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"Headphone", NULL, "Sub AIF2TX"}, /* IF2 ADC to 5650 */ + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"Sub AIF2RX", NULL, "Headset Mic"}, /* IF2 DAC from 5650 */ +}; + +static const struct snd_kcontrol_new mt8173_rt5650_rt5676_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_rt5650_rt5676_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + /* pll from mclk 12.288M */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, MCLK_FOR_CODECS, + params_rate(params) * 512); + if (ret) + return ret; + + /* sysclk from pll */ + ret = snd_soc_dai_set_sysclk(codec_dai, 1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + return 0; +} + +static const struct snd_soc_ops mt8173_rt5650_rt5676_ops = { + .hw_params = mt8173_rt5650_rt5676_hw_params, +}; + +static struct snd_soc_jack mt8173_rt5650_rt5676_jack; + +static int mt8173_rt5650_rt5676_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_component *component = runtime->codec_dais[0]->component; + struct snd_soc_component *component_sub = runtime->codec_dais[1]->component; + int ret; + + rt5645_sel_asrc_clk_src(component, + RT5645_DA_STEREO_FILTER | + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + rt5677_sel_asrc_clk_src(component_sub, + RT5677_DA_STEREO_FILTER | + RT5677_AD_STEREO1_FILTER, + RT5677_CLK_SEL_I2S1_ASRC); + rt5677_sel_asrc_clk_src(component_sub, + RT5677_AD_STEREO2_FILTER | + RT5677_I2S2_SOURCE, + RT5677_CLK_SEL_I2S2_ASRC); + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &mt8173_rt5650_rt5676_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Can't new Headset Jack %d\n", ret); + return ret; + } + + return rt5645_set_jack_detect(component, + &mt8173_rt5650_rt5676_jack, + &mt8173_rt5650_rt5676_jack, + &mt8173_rt5650_rt5676_jack); +} + +static struct snd_soc_dai_link_component mt8173_rt5650_rt5676_codecs[] = { + { + .dai_name = "rt5645-aif1", + }, + { + .dai_name = "rt5677-aif1", + }, +}; + +enum { + DAI_LINK_PLAYBACK, + DAI_LINK_CAPTURE, + DAI_LINK_HDMI, + DAI_LINK_CODEC_I2S, + DAI_LINK_HDMI_I2S, + DAI_LINK_INTERCODEC +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_rt5650_rt5676_dais[] = { + /* Front End DAI links */ + [DAI_LINK_PLAYBACK] = { + .name = "rt5650_rt5676 Playback", + .stream_name = "rt5650_rt5676 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_CAPTURE] = { + .name = "rt5650_rt5676 Capture", + .stream_name = "rt5650_rt5676 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI] = { + .name = "HDMI", + .stream_name = "HDMI PCM", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + + /* Back End DAI links */ + [DAI_LINK_CODEC_I2S] = { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codecs = mt8173_rt5650_rt5676_codecs, + .num_codecs = 2, + .init = mt8173_rt5650_rt5676_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &mt8173_rt5650_rt5676_ops, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI_I2S] = { + .name = "HDMI BE", + .cpu_dai_name = "HDMIO", + .no_pcm = 1, + .codec_dai_name = "i2s-hifi", + .dpcm_playback = 1, + }, + /* rt5676 <-> rt5650 intercodec link: Sets rt5676 I2S2 as master */ + [DAI_LINK_INTERCODEC] = { + .name = "rt5650_rt5676 intercodec", + .stream_name = "rt5650_rt5676 intercodec", + .cpu_dai_name = "snd-soc-dummy-dai", + .platform_name = "snd-soc-dummy", + .no_pcm = 1, + .codec_dai_name = "rt5677-aif2", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + }, + +}; + +static struct snd_soc_codec_conf mt8173_rt5650_rt5676_codec_conf[] = { + { + .name_prefix = "Sub", + }, +}; + +static struct snd_soc_card mt8173_rt5650_rt5676_card = { + .name = "mtk-rt5650-rt5676", + .owner = THIS_MODULE, + .dai_link = mt8173_rt5650_rt5676_dais, + .num_links = ARRAY_SIZE(mt8173_rt5650_rt5676_dais), + .codec_conf = mt8173_rt5650_rt5676_codec_conf, + .num_configs = ARRAY_SIZE(mt8173_rt5650_rt5676_codec_conf), + .controls = mt8173_rt5650_rt5676_controls, + .num_controls = ARRAY_SIZE(mt8173_rt5650_rt5676_controls), + .dapm_widgets = mt8173_rt5650_rt5676_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_rt5676_widgets), + .dapm_routes = mt8173_rt5650_rt5676_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_rt5676_routes), +}; + +static int mt8173_rt5650_rt5676_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_rt5650_rt5676_card; + struct device_node *platform_node; + int i, ret; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < card->num_links; i++) { + if (mt8173_rt5650_rt5676_dais[i].platform_name) + continue; + mt8173_rt5650_rt5676_dais[i].platform_of_node = platform_node; + } + + mt8173_rt5650_rt5676_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0); + if (!mt8173_rt5650_rt5676_codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5676_codecs[1].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); + if (!mt8173_rt5650_rt5676_codecs[1].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_rt5676_codec_conf[0].of_node = + mt8173_rt5650_rt5676_codecs[1].of_node; + + mt8173_rt5650_rt5676_dais[DAI_LINK_INTERCODEC].codec_of_node = + mt8173_rt5650_rt5676_codecs[1].of_node; + + mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 2); + if (!mt8173_rt5650_rt5676_dais[DAI_LINK_HDMI_I2S].codec_of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + of_node_put(platform_node); + return ret; +} + +static const struct of_device_id mt8173_rt5650_rt5676_dt_match[] = { + { .compatible = "mediatek,mt8173-rt5650-rt5676", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_rt5650_rt5676_dt_match); + +static struct platform_driver mt8173_rt5650_rt5676_driver = { + .driver = { + .name = "mtk-rt5650-rt5676", + .of_match_table = mt8173_rt5650_rt5676_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_rt5650_rt5676_dev_probe, +}; + +module_platform_driver(mt8173_rt5650_rt5676_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 RT5650 and RT5676 SoC machine driver"); +MODULE_AUTHOR("Koro Chen "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mtk-rt5650-rt5676"); + diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c new file mode 100644 index 000000000..14011a70b --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mt8173-rt5650.c -- MT8173 machine driver with RT5650 codecs + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Koro Chen + */ + +#include +#include +#include +#include +#include +#include "../../codecs/rt5645.h" + +#define MCLK_FOR_CODECS 12288000 + +enum mt8173_rt5650_mclk { + MT8173_RT5650_MCLK_EXTERNAL = 0, + MT8173_RT5650_MCLK_INTERNAL, +}; + +struct mt8173_rt5650_platform_data { + enum mt8173_rt5650_mclk pll_from; + /* 0 = external oscillator; 1 = internal source from mt8173 */ +}; + +static struct mt8173_rt5650_platform_data mt8173_rt5650_priv = { + .pll_from = MT8173_RT5650_MCLK_EXTERNAL, +}; + +static const struct snd_soc_dapm_widget mt8173_rt5650_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route mt8173_rt5650_routes[] = { + {"Speaker", NULL, "SPOL"}, + {"Speaker", NULL, "SPOR"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "HPOL"}, + {"Headphone", NULL, "HPOR"}, + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new mt8173_rt5650_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int mt8173_rt5650_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int mclk_clock; + int i, ret; + + switch (mt8173_rt5650_priv.pll_from) { + case MT8173_RT5650_MCLK_EXTERNAL: + /* mclk = 12.288M */ + mclk_clock = MCLK_FOR_CODECS; + break; + case MT8173_RT5650_MCLK_INTERNAL: + /* mclk = sampling rate*256 */ + mclk_clock = params_rate(params) * 256; + break; + default: + /* mclk = 12.288M */ + mclk_clock = MCLK_FOR_CODECS; + break; + } + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + /* pll from mclk */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, mclk_clock, + params_rate(params) * 512); + if (ret) + return ret; + + /* sysclk from pll */ + ret = snd_soc_dai_set_sysclk(codec_dai, 1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) + return ret; + } + return 0; +} + +static const struct snd_soc_ops mt8173_rt5650_ops = { + .hw_params = mt8173_rt5650_hw_params, +}; + +static struct snd_soc_jack mt8173_rt5650_jack; + +static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + struct snd_soc_component *component = runtime->codec_dais[0]->component; + const char *codec_capture_dai = runtime->codec_dais[1]->name; + int ret; + + rt5645_sel_asrc_clk_src(component, + RT5645_DA_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + + if (!strcmp(codec_capture_dai, "rt5645-aif1")) { + rt5645_sel_asrc_clk_src(component, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } else if (!strcmp(codec_capture_dai, "rt5645-aif2")) { + rt5645_sel_asrc_clk_src(component, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S2_ASRC); + } else { + dev_warn(card->dev, + "Only one dai codec found in DTS, enabled rt5645 AD filter\n"); + rt5645_sel_asrc_clk_src(component, + RT5645_AD_STEREO_FILTER, + RT5645_CLK_SEL_I2S1_ASRC); + } + + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &mt8173_rt5650_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "Can't new Headset Jack %d\n", ret); + return ret; + } + + return rt5645_set_jack_detect(component, + &mt8173_rt5650_jack, + &mt8173_rt5650_jack, + &mt8173_rt5650_jack); +} + +static struct snd_soc_dai_link_component mt8173_rt5650_codecs[] = { + { + /* Playback */ + .dai_name = "rt5645-aif1", + }, + { + /* Capture */ + .dai_name = "rt5645-aif1", + }, +}; + +enum { + DAI_LINK_PLAYBACK, + DAI_LINK_CAPTURE, + DAI_LINK_HDMI, + DAI_LINK_CODEC_I2S, + DAI_LINK_HDMI_I2S, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link mt8173_rt5650_dais[] = { + /* Front End DAI links */ + [DAI_LINK_PLAYBACK] = { + .name = "rt5650 Playback", + .stream_name = "rt5650 Playback", + .cpu_dai_name = "DL1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_CAPTURE] = { + .name = "rt5650 Capture", + .stream_name = "rt5650 Capture", + .cpu_dai_name = "VUL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI] = { + .name = "HDMI", + .stream_name = "HDMI PCM", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + /* Back End DAI links */ + [DAI_LINK_CODEC_I2S] = { + .name = "Codec", + .cpu_dai_name = "I2S", + .no_pcm = 1, + .codecs = mt8173_rt5650_codecs, + .num_codecs = 2, + .init = mt8173_rt5650_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &mt8173_rt5650_ops, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_HDMI_I2S] = { + .name = "HDMI BE", + .cpu_dai_name = "HDMIO", + .no_pcm = 1, + .codec_dai_name = "i2s-hifi", + .dpcm_playback = 1, + }, +}; + +static struct snd_soc_card mt8173_rt5650_card = { + .name = "mtk-rt5650", + .owner = THIS_MODULE, + .dai_link = mt8173_rt5650_dais, + .num_links = ARRAY_SIZE(mt8173_rt5650_dais), + .controls = mt8173_rt5650_controls, + .num_controls = ARRAY_SIZE(mt8173_rt5650_controls), + .dapm_widgets = mt8173_rt5650_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_widgets), + .dapm_routes = mt8173_rt5650_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_routes), +}; + +static int mt8173_rt5650_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt8173_rt5650_card; + struct device_node *platform_node; + struct device_node *np; + const char *codec_capture_dai; + int i, ret; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < card->num_links; i++) { + if (mt8173_rt5650_dais[i].platform_name) + continue; + mt8173_rt5650_dais[i].platform_of_node = platform_node; + } + + mt8173_rt5650_codecs[0].of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0); + if (!mt8173_rt5650_codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + mt8173_rt5650_codecs[1].of_node = mt8173_rt5650_codecs[0].of_node; + + np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); + if (np) { + ret = snd_soc_of_get_dai_name(np, &codec_capture_dai); + of_node_put(np); + if (ret < 0) { + dev_err(&pdev->dev, + "%s codec_capture_dai name fail %d\n", + __func__, ret); + return ret; + } + mt8173_rt5650_codecs[1].dai_name = codec_capture_dai; + } + + if (device_property_present(&pdev->dev, "mediatek,mclk")) { + ret = device_property_read_u32(&pdev->dev, + "mediatek,mclk", + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, + "%s snd_soc_register_card fail %d\n", + __func__, ret); + } + } + + mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); + if (!mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + + of_node_put(platform_node); + return ret; +} + +static const struct of_device_id mt8173_rt5650_dt_match[] = { + { .compatible = "mediatek,mt8173-rt5650", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt8173_rt5650_dt_match); + +static struct platform_driver mt8173_rt5650_driver = { + .driver = { + .name = "mtk-rt5650", + .of_match_table = mt8173_rt5650_dt_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, + .probe = mt8173_rt5650_dev_probe, +}; + +module_platform_driver(mt8173_rt5650_driver); + +/* Module information */ +MODULE_DESCRIPTION("MT8173 RT5650 SoC machine driver"); +MODULE_AUTHOR("Koro Chen "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mtk-rt5650"); + diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig new file mode 100644 index 000000000..19fd4d583 --- /dev/null +++ b/sound/soc/meson/Kconfig @@ -0,0 +1,65 @@ +menu "ASoC support for Amlogic platforms" + depends on ARCH_MESON || (COMPILE_TEST && COMMON_CLK) + +config SND_MESON_AXG_FIFO + tristate + select REGMAP_MMIO + +config SND_MESON_AXG_FRDDR + tristate "Amlogic AXG Playback FIFO support" + select SND_MESON_AXG_FIFO + help + Select Y or M to add support for the frontend playback interfaces + embedded in the Amlogic AXG SoC family + +config SND_MESON_AXG_TODDR + tristate "Amlogic AXG Capture FIFO support" + select SND_MESON_AXG_FIFO + help + Select Y or M to add support for the frontend capture interfaces + embedded in the Amlogic AXG SoC family + +config SND_MESON_AXG_TDM_FORMATTER + tristate + select REGMAP_MMIO + +config SND_MESON_AXG_TDM_INTERFACE + tristate + select SND_MESON_AXG_TDM_FORMATTER + +config SND_MESON_AXG_TDMIN + tristate "Amlogic AXG TDM Input Support" + select SND_MESON_AXG_TDM_FORMATTER + select SND_MESON_AXG_TDM_INTERFACE + help + Select Y or M to add support for TDM input formatter embedded + in the Amlogic AXG SoC family + +config SND_MESON_AXG_TDMOUT + tristate "Amlogic AXG TDM Output Support" + select SND_MESON_AXG_TDM_FORMATTER + select SND_MESON_AXG_TDM_INTERFACE + help + Select Y or M to add support for TDM output formatter embedded + in the Amlogic AXG SoC family + +config SND_MESON_AXG_SOUND_CARD + tristate "Amlogic AXG Sound Card Support" + select SND_MESON_AXG_TDM_INTERFACE + imply SND_MESON_AXG_FRDDR + imply SND_MESON_AXG_TODDR + imply SND_MESON_AXG_TDMIN + imply SND_MESON_AXG_TDMOUT + imply SND_MESON_AXG_SPDIFOUT + help + Select Y or M to add support for the AXG SoC sound card + +config SND_MESON_AXG_SPDIFOUT + tristate "Amlogic AXG SPDIF Output Support" + select SND_PCM_IEC958 + imply SND_SOC_SPDIF + help + Select Y or M to add support for SPDIF output serializer embedded + in the Amlogic AXG SoC family + +endmenu diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile new file mode 100644 index 000000000..c5e003b09 --- /dev/null +++ b/sound/soc/meson/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: (GPL-2.0 OR MIT) + +snd-soc-meson-axg-fifo-objs := axg-fifo.o +snd-soc-meson-axg-frddr-objs := axg-frddr.o +snd-soc-meson-axg-toddr-objs := axg-toddr.o +snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o +snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o +snd-soc-meson-axg-tdmin-objs := axg-tdmin.o +snd-soc-meson-axg-tdmout-objs := axg-tdmout.o +snd-soc-meson-axg-sound-card-objs := axg-card.o +snd-soc-meson-axg-spdifout-objs := axg-spdifout.o + +obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o +obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o +obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o +obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o +obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o +obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o +obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o +obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o +obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c new file mode 100644 index 000000000..2914ba0d9 --- /dev/null +++ b/sound/soc/meson/axg-card.c @@ -0,0 +1,671 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include + +#include "axg-tdm.h" + +struct axg_card { + struct snd_soc_card card; + void **link_data; +}; + +struct axg_dai_link_tdm_mask { + u32 tx; + u32 rx; +}; + +struct axg_dai_link_tdm_data { + unsigned int mclk_fs; + unsigned int slots; + unsigned int slot_width; + u32 *tx_mask; + u32 *rx_mask; + struct axg_dai_link_tdm_mask *codec_masks; +}; + +#define PREFIX "amlogic," + +static int axg_card_reallocate_links(struct axg_card *priv, + unsigned int num_links) +{ + struct snd_soc_dai_link *links; + void **ldata; + + links = krealloc(priv->card.dai_link, + num_links * sizeof(*priv->card.dai_link), + GFP_KERNEL | __GFP_ZERO); + ldata = krealloc(priv->link_data, + num_links * sizeof(*priv->link_data), + GFP_KERNEL | __GFP_ZERO); + + if (!links || !ldata) { + dev_err(priv->card.dev, "failed to allocate links\n"); + return -ENOMEM; + } + + priv->card.dai_link = links; + priv->link_data = ldata; + priv->card.num_links = num_links; + return 0; +} + +static int axg_card_parse_dai(struct snd_soc_card *card, + struct device_node *node, + struct device_node **dai_of_node, + const char **dai_name) +{ + struct of_phandle_args args; + int ret; + + if (!dai_name || !dai_of_node || !node) + return -EINVAL; + + ret = of_parse_phandle_with_args(node, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(card->dev, "can't parse dai %d\n", ret); + return ret; + } + *dai_of_node = args.np; + + return snd_soc_get_dai_name(&args, dai_name); +} + +static int axg_card_set_link_name(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + const char *prefix) +{ + char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", + prefix, link->cpu_of_node->full_name); + if (!name) + return -ENOMEM; + + link->name = name; + link->stream_name = name; + + return 0; +} + +static void axg_card_clean_references(struct axg_card *priv) +{ + struct snd_soc_card *card = &priv->card; + struct snd_soc_dai_link *link; + int i, j; + + if (card->dai_link) { + for (i = 0; i < card->num_links; i++) { + link = &card->dai_link[i]; + of_node_put(link->cpu_of_node); + for (j = 0; j < link->num_codecs; j++) + of_node_put(link->codecs[j].of_node); + } + } + + if (card->aux_dev) { + for (i = 0; i < card->num_aux_devs; i++) + of_node_put(card->aux_dev[i].codec_of_node); + } + + kfree(card->dai_link); + kfree(priv->link_data); +} + +static int axg_card_add_aux_devices(struct snd_soc_card *card) +{ + struct device_node *node = card->dev->of_node; + struct snd_soc_aux_dev *aux; + int num, i; + + num = of_count_phandle_with_args(node, "audio-aux-devs", NULL); + if (num == -ENOENT) { + /* + * It is ok to have no auxiliary devices but for this card it + * is a strange situtation. Let's warn the about it. + */ + dev_warn(card->dev, "card has no auxiliary devices\n"); + return 0; + } else if (num < 0) { + dev_err(card->dev, "error getting auxiliary devices: %d\n", + num); + return num; + } + + aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL); + if (!aux) + return -ENOMEM; + card->aux_dev = aux; + card->num_aux_devs = num; + + for (i = 0; i < card->num_aux_devs; i++, aux++) { + aux->codec_of_node = + of_parse_phandle(node, "audio-aux-devs", i); + if (!aux->codec_of_node) + return -EINVAL; + } + + return 0; +} + +static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); + struct axg_dai_link_tdm_data *be = + (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; + struct snd_soc_dai *codec_dai; + unsigned int mclk; + int ret, i; + + if (be->mclk_fs) { + mclk = params_rate(params) * be->mclk_fs; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + return ret; + } + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + return ret; + } + + return 0; +} + +static const struct snd_soc_ops axg_card_tdm_be_ops = { + .hw_params = axg_card_tdm_be_hw_params, +}; + +static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); + struct axg_dai_link_tdm_data *be = + (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; + struct snd_soc_dai *codec_dai; + int ret, i; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + ret = snd_soc_dai_set_tdm_slot(codec_dai, + be->codec_masks[i].tx, + be->codec_masks[i].rx, + be->slots, be->slot_width); + if (ret && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "setting tdm link slots failed\n"); + return ret; + } + } + + ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, be->tx_mask, be->rx_mask, + be->slots, be->slot_width); + if (ret) { + dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n"); + return ret; + } + + return 0; +} + +static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); + struct axg_dai_link_tdm_data *be = + (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; + int ret; + + /* The loopback rx_mask is the pad tx_mask */ + ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, NULL, be->tx_mask, + be->slots, be->slot_width); + if (ret) { + dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n"); + return ret; + } + + return 0; +} + +static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + int *index) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *pad = &card->dai_link[*index]; + struct snd_soc_dai_link *lb; + int ret; + + /* extend links */ + ret = axg_card_reallocate_links(priv, card->num_links + 1); + if (ret) + return ret; + + lb = &card->dai_link[*index + 1]; + + lb->name = kasprintf(GFP_KERNEL, "%s-lb", pad->name); + if (!lb->name) + return -ENOMEM; + + lb->stream_name = lb->name; + lb->cpu_of_node = pad->cpu_of_node; + lb->cpu_dai_name = "TDM Loopback"; + lb->codec_name = "snd-soc-dummy"; + lb->codec_dai_name = "snd-soc-dummy-dai"; + lb->dpcm_capture = 1; + lb->no_pcm = 1; + lb->ops = &axg_card_tdm_be_ops; + lb->init = axg_card_tdm_dai_lb_init; + + /* Provide the same link data to the loopback */ + priv->link_data[*index + 1] = priv->link_data[*index]; + + /* + * axg_card_clean_references() will iterate over this link, + * make sure the node count is balanced + */ + of_node_get(lb->cpu_of_node); + + /* Let add_links continue where it should */ + *index += 1; + + return 0; +} + +static unsigned int axg_card_parse_daifmt(struct device_node *node, + struct device_node *cpu_node) +{ + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + unsigned int daifmt; + + daifmt = snd_soc_of_parse_daifmt(node, PREFIX, + &bitclkmaster, &framemaster); + daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + /* If no master is provided, default to cpu master */ + if (!bitclkmaster || bitclkmaster == cpu_node) { + daifmt |= (!framemaster || framemaster == cpu_node) ? + SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBS_CFM; + } else { + daifmt |= (!framemaster || framemaster == cpu_node) ? + SND_SOC_DAIFMT_CBM_CFS : SND_SOC_DAIFMT_CBM_CFM; + } + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + return daifmt; +} + +static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + struct device_node *node, + struct axg_dai_link_tdm_data *be) +{ + char propname[32]; + u32 tx, rx; + int i; + + be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, + sizeof(*be->tx_mask), GFP_KERNEL); + be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, + sizeof(*be->rx_mask), GFP_KERNEL); + if (!be->tx_mask || !be->rx_mask) + return -ENOMEM; + + for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) { + snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i); + snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]); + tx = max(tx, be->tx_mask[i]); + } + + /* Disable playback is the interface has no tx slots */ + if (!tx) + link->dpcm_playback = 0; + + for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) { + snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i); + snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]); + rx = max(rx, be->rx_mask[i]); + } + + /* Disable capture is the interface has no rx slots */ + if (!rx) + link->dpcm_capture = 0; + + /* ... but the interface should at least have one of them */ + if (!tx && !rx) { + dev_err(card->dev, "tdm link has no cpu slots\n"); + return -EINVAL; + } + + of_property_read_u32(node, "dai-tdm-slot-num", &be->slots); + if (!be->slots) { + /* + * If the slot number is not provided, set it such as it + * accommodates the largest mask + */ + be->slots = fls(max(tx, rx)); + } else if (be->slots < fls(max(tx, rx)) || be->slots > 32) { + /* + * Error if the slots can't accommodate the largest mask or + * if it is just too big + */ + dev_err(card->dev, "bad slot number\n"); + return -EINVAL; + } + + of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width); + + return 0; +} + +static int axg_card_parse_codecs_masks(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + struct device_node *node, + struct axg_dai_link_tdm_data *be) +{ + struct axg_dai_link_tdm_mask *codec_mask; + struct device_node *np; + + codec_mask = devm_kcalloc(card->dev, link->num_codecs, + sizeof(*codec_mask), GFP_KERNEL); + if (!codec_mask) + return -ENOMEM; + + be->codec_masks = codec_mask; + + for_each_child_of_node(node, np) { + snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", + &codec_mask->rx); + snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", + &codec_mask->tx); + + codec_mask++; + } + + return 0; +} + +static int axg_card_parse_tdm(struct snd_soc_card *card, + struct device_node *node, + int *index) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct axg_dai_link_tdm_data *be; + int ret; + + /* Allocate tdm link parameters */ + be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; + + /* Setup tdm link */ + link->ops = &axg_card_tdm_be_ops; + link->init = axg_card_tdm_dai_init; + link->dai_fmt = axg_card_parse_daifmt(node, link->cpu_of_node); + + of_property_read_u32(node, "mclk-fs", &be->mclk_fs); + + ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); + if (ret) { + dev_err(card->dev, "error parsing tdm link slots\n"); + return ret; + } + + ret = axg_card_parse_codecs_masks(card, link, node, be); + if (ret) + return ret; + + /* Add loopback if the pad dai has playback */ + if (link->dpcm_playback) { + ret = axg_card_add_tdm_loopback(card, index); + if (ret) + return ret; + } + + return 0; +} + +static int axg_card_set_be_link(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + struct device_node *node) +{ + struct snd_soc_dai_link_component *codec; + struct device_node *np; + int ret, num_codecs; + + link->no_pcm = 1; + link->dpcm_playback = 1; + link->dpcm_capture = 1; + + num_codecs = of_get_child_count(node); + if (!num_codecs) { + dev_err(card->dev, "be link %s has no codec\n", + node->full_name); + return -EINVAL; + } + + codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + + link->codecs = codec; + link->num_codecs = num_codecs; + + for_each_child_of_node(node, np) { + ret = axg_card_parse_dai(card, np, &codec->of_node, + &codec->dai_name); + if (ret) { + of_node_put(np); + return ret; + } + + codec++; + } + + ret = axg_card_set_link_name(card, link, "be"); + if (ret) + dev_err(card->dev, "error setting %s link name\n", np->name); + + return ret; +} + +static int axg_card_set_fe_link(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + bool is_playback) +{ + link->dynamic = 1; + link->dpcm_merged_format = 1; + link->dpcm_merged_chan = 1; + link->dpcm_merged_rate = 1; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + + if (is_playback) + link->dpcm_playback = 1; + else + link->dpcm_capture = 1; + + return axg_card_set_link_name(card, link, "fe"); +} + +static int axg_card_cpu_is_capture_fe(struct device_node *np) +{ + return of_device_is_compatible(np, PREFIX "axg-toddr"); +} + +static int axg_card_cpu_is_playback_fe(struct device_node *np) +{ + return of_device_is_compatible(np, PREFIX "axg-frddr"); +} + +static int axg_card_cpu_is_tdm_iface(struct device_node *np) +{ + return of_device_is_compatible(np, PREFIX "axg-tdm-iface"); +} + +static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, + int *index) +{ + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + int ret; + + ret = axg_card_parse_dai(card, np, &dai_link->cpu_of_node, + &dai_link->cpu_dai_name); + if (ret) + return ret; + + if (axg_card_cpu_is_playback_fe(dai_link->cpu_of_node)) + ret = axg_card_set_fe_link(card, dai_link, true); + else if (axg_card_cpu_is_capture_fe(dai_link->cpu_of_node)) + ret = axg_card_set_fe_link(card, dai_link, false); + else + ret = axg_card_set_be_link(card, dai_link, np); + + if (ret) + return ret; + + if (axg_card_cpu_is_tdm_iface(dai_link->cpu_of_node)) + ret = axg_card_parse_tdm(card, np, index); + + return ret; +} + +static int axg_card_add_links(struct snd_soc_card *card) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(card); + struct device_node *node = card->dev->of_node; + struct device_node *np; + int num, i, ret; + + num = of_get_child_count(node); + if (!num) { + dev_err(card->dev, "card has no links\n"); + return -EINVAL; + } + + ret = axg_card_reallocate_links(priv, num); + if (ret) + return ret; + + i = 0; + for_each_child_of_node(node, np) { + ret = axg_card_add_link(card, np, &i); + if (ret) { + of_node_put(np); + return ret; + } + + i++; + } + + return 0; +} + +static int axg_card_parse_of_optional(struct snd_soc_card *card, + const char *propname, + int (*func)(struct snd_soc_card *c, + const char *p)) +{ + /* If property is not provided, don't fail ... */ + if (!of_property_read_bool(card->dev->of_node, propname)) + return 0; + + /* ... but do fail if it is provided and the parsing fails */ + return func(card, propname); +} + +static const struct of_device_id axg_card_of_match[] = { + { .compatible = "amlogic,axg-sound-card", }, + {} +}; +MODULE_DEVICE_TABLE(of, axg_card_of_match); + +static int axg_card_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct axg_card *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + snd_soc_card_set_drvdata(&priv->card, priv); + + priv->card.owner = THIS_MODULE; + priv->card.dev = dev; + + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret < 0) + return ret; + + ret = axg_card_parse_of_optional(&priv->card, "audio-routing", + snd_soc_of_parse_audio_routing); + if (ret) { + dev_err(dev, "error while parsing routing\n"); + return ret; + } + + ret = axg_card_parse_of_optional(&priv->card, "audio-widgets", + snd_soc_of_parse_audio_simple_widgets); + if (ret) { + dev_err(dev, "error while parsing widgets\n"); + return ret; + } + + ret = axg_card_add_links(&priv->card); + if (ret) + goto out_err; + + ret = axg_card_add_aux_devices(&priv->card); + if (ret) + goto out_err; + + ret = devm_snd_soc_register_card(dev, &priv->card); + if (ret) + goto out_err; + + return 0; + +out_err: + axg_card_clean_references(priv); + return ret; +} + +static int axg_card_remove(struct platform_device *pdev) +{ + struct axg_card *priv = platform_get_drvdata(pdev); + + axg_card_clean_references(priv); + + return 0; +} + +static struct platform_driver axg_card_pdrv = { + .probe = axg_card_probe, + .remove = axg_card_remove, + .driver = { + .name = "axg-sound-card", + .of_match_table = axg_card_of_match, + }, +}; +module_platform_driver(axg_card_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-fifo.c b/sound/soc/meson/axg-fifo.c new file mode 100644 index 000000000..b229c182b --- /dev/null +++ b/sound/soc/meson/axg-fifo.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "axg-fifo.h" + +/* + * This file implements the platform operations common to the playback and + * capture frontend DAI. The logic behind this two types of fifo is very + * similar but some difference exist. + * These differences the respective DAI drivers + */ + +static struct snd_pcm_hardware axg_fifo_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE), + + .formats = AXG_FIFO_FORMATS, + .rate_min = 5512, + .rate_max = 192000, + .channels_min = 1, + .channels_max = AXG_FIFO_CH_MAX, + .period_bytes_min = AXG_FIFO_MIN_DEPTH, + .period_bytes_max = UINT_MAX, + .periods_min = 2, + .periods_max = UINT_MAX, + + /* No real justification for this */ + .buffer_bytes_max = 1 * 1024 * 1024, +}; + +static struct snd_soc_dai *axg_fifo_dai(struct snd_pcm_substream *ss) +{ + struct snd_soc_pcm_runtime *rtd = ss->private_data; + + return rtd->cpu_dai; +} + +static struct axg_fifo *axg_fifo_data(struct snd_pcm_substream *ss) +{ + struct snd_soc_dai *dai = axg_fifo_dai(ss); + + return snd_soc_dai_get_drvdata(dai); +} + +static struct device *axg_fifo_dev(struct snd_pcm_substream *ss) +{ + struct snd_soc_dai *dai = axg_fifo_dai(ss); + + return dai->dev; +} + +static void __dma_enable(struct axg_fifo *fifo, bool enable) +{ + regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_DMA_EN, + enable ? CTRL0_DMA_EN : 0); +} + +static int axg_fifo_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct axg_fifo *fifo = axg_fifo_data(ss); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + __dma_enable(fifo, true); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + __dma_enable(fifo, false); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t axg_fifo_pcm_pointer(struct snd_pcm_substream *ss) +{ + struct axg_fifo *fifo = axg_fifo_data(ss); + struct snd_pcm_runtime *runtime = ss->runtime; + unsigned int addr; + + regmap_read(fifo->map, FIFO_STATUS2, &addr); + + return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr); +} + +static int axg_fifo_pcm_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = ss->runtime; + struct axg_fifo *fifo = axg_fifo_data(ss); + dma_addr_t end_ptr; + unsigned int burst_num; + int ret; + + ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + /* Setup dma memory pointers */ + end_ptr = runtime->dma_addr + runtime->dma_bytes - AXG_FIFO_BURST; + regmap_write(fifo->map, FIFO_START_ADDR, runtime->dma_addr); + regmap_write(fifo->map, FIFO_FINISH_ADDR, end_ptr); + + /* Setup interrupt periodicity */ + burst_num = params_period_bytes(params) / AXG_FIFO_BURST; + regmap_write(fifo->map, FIFO_INT_ADDR, burst_num); + + /* Enable block count irq */ + regmap_update_bits(fifo->map, FIFO_CTRL0, + CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT), + CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT)); + + return 0; +} + +static int axg_fifo_pcm_hw_free(struct snd_pcm_substream *ss) +{ + struct axg_fifo *fifo = axg_fifo_data(ss); + + /* Disable the block count irq */ + regmap_update_bits(fifo->map, FIFO_CTRL0, + CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT), 0); + + return snd_pcm_lib_free_pages(ss); +} + +static void axg_fifo_ack_irq(struct axg_fifo *fifo, u8 mask) +{ + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_INT_CLR(FIFO_INT_MASK), + CTRL1_INT_CLR(mask)); + + /* Clear must also be cleared */ + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_INT_CLR(FIFO_INT_MASK), + 0); +} + +static irqreturn_t axg_fifo_pcm_irq_block(int irq, void *dev_id) +{ + struct snd_pcm_substream *ss = dev_id; + struct axg_fifo *fifo = axg_fifo_data(ss); + unsigned int status; + + regmap_read(fifo->map, FIFO_STATUS1, &status); + + status = STATUS1_INT_STS(status) & FIFO_INT_MASK; + if (status & FIFO_INT_COUNT_REPEAT) + snd_pcm_period_elapsed(ss); + else + dev_dbg(axg_fifo_dev(ss), "unexpected irq - STS 0x%02x\n", + status); + + /* Ack irqs */ + axg_fifo_ack_irq(fifo, status); + + return IRQ_RETVAL(status); +} + +static int axg_fifo_pcm_open(struct snd_pcm_substream *ss) +{ + struct axg_fifo *fifo = axg_fifo_data(ss); + struct device *dev = axg_fifo_dev(ss); + int ret; + + snd_soc_set_runtime_hwparams(ss, &axg_fifo_hw); + + /* + * Make sure the buffer and period size are multiple of the FIFO + * minimum depth size + */ + ret = snd_pcm_hw_constraint_step(ss->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + AXG_FIFO_MIN_DEPTH); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_step(ss->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + AXG_FIFO_MIN_DEPTH); + if (ret) + return ret; + + ret = request_irq(fifo->irq, axg_fifo_pcm_irq_block, 0, + dev_name(dev), ss); + if (ret) + return ret; + + /* Enable pclk to access registers and clock the fifo ip */ + ret = clk_prepare_enable(fifo->pclk); + if (ret) + goto free_irq; + + /* Setup status2 so it reports the memory pointer */ + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_STATUS2_SEL_MASK, + CTRL1_STATUS2_SEL(STATUS2_SEL_DDR_READ)); + + /* Make sure the dma is initially disabled */ + __dma_enable(fifo, false); + + /* Disable irqs until params are ready */ + regmap_update_bits(fifo->map, FIFO_CTRL0, + CTRL0_INT_EN(FIFO_INT_MASK), 0); + + /* Clear any pending interrupt */ + axg_fifo_ack_irq(fifo, FIFO_INT_MASK); + + /* Take memory arbitror out of reset */ + ret = reset_control_deassert(fifo->arb); + if (ret) + goto free_clk; + + return 0; + +free_clk: + clk_disable_unprepare(fifo->pclk); +free_irq: + free_irq(fifo->irq, ss); + return ret; +} + +static int axg_fifo_pcm_close(struct snd_pcm_substream *ss) +{ + struct axg_fifo *fifo = axg_fifo_data(ss); + int ret; + + /* Put the memory arbitror back in reset */ + ret = reset_control_assert(fifo->arb); + + /* Disable fifo ip and register access */ + clk_disable_unprepare(fifo->pclk); + + /* remove IRQ */ + free_irq(fifo->irq, ss); + + return ret; +} + +const struct snd_pcm_ops axg_fifo_pcm_ops = { + .open = axg_fifo_pcm_open, + .close = axg_fifo_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = axg_fifo_pcm_hw_params, + .hw_free = axg_fifo_pcm_hw_free, + .pointer = axg_fifo_pcm_pointer, + .trigger = axg_fifo_pcm_trigger, +}; +EXPORT_SYMBOL_GPL(axg_fifo_pcm_ops); + +int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type) +{ + struct snd_card *card = rtd->card->snd_card; + size_t size = axg_fifo_hw.buffer_bytes_max; + + return snd_pcm_lib_preallocate_pages(rtd->pcm->streams[type].substream, + SNDRV_DMA_TYPE_DEV, card->dev, + size, size); +} +EXPORT_SYMBOL_GPL(axg_fifo_pcm_new); + +static const struct regmap_config axg_fifo_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = FIFO_STATUS2, +}; + +int axg_fifo_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct axg_fifo_match_data *data; + struct axg_fifo *fifo; + struct resource *res; + void __iomem *regs; + + data = of_device_get_match_data(dev); + if (!data) { + dev_err(dev, "failed to match device\n"); + return -ENODEV; + } + + fifo = devm_kzalloc(dev, sizeof(*fifo), GFP_KERNEL); + if (!fifo) + return -ENOMEM; + platform_set_drvdata(pdev, fifo); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + fifo->map = devm_regmap_init_mmio(dev, regs, &axg_fifo_regmap_cfg); + if (IS_ERR(fifo->map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(fifo->map)); + return PTR_ERR(fifo->map); + } + + fifo->pclk = devm_clk_get(dev, NULL); + if (IS_ERR(fifo->pclk)) { + if (PTR_ERR(fifo->pclk) != -EPROBE_DEFER) + dev_err(dev, "failed to get pclk: %ld\n", + PTR_ERR(fifo->pclk)); + return PTR_ERR(fifo->pclk); + } + + fifo->arb = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(fifo->arb)) { + if (PTR_ERR(fifo->arb) != -EPROBE_DEFER) + dev_err(dev, "failed to get arb reset: %ld\n", + PTR_ERR(fifo->arb)); + return PTR_ERR(fifo->arb); + } + + fifo->irq = of_irq_get(dev->of_node, 0); + if (fifo->irq <= 0) { + dev_err(dev, "failed to get irq: %d\n", fifo->irq); + return fifo->irq; + } + + return devm_snd_soc_register_component(dev, data->component_drv, + data->dai_drv, 1); +} +EXPORT_SYMBOL_GPL(axg_fifo_probe); + +MODULE_DESCRIPTION("Amlogic AXG fifo driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-fifo.h b/sound/soc/meson/axg-fifo.h new file mode 100644 index 000000000..cb6c4013c --- /dev/null +++ b/sound/soc/meson/axg-fifo.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet + */ + +#ifndef _MESON_AXG_FIFO_H +#define _MESON_AXG_FIFO_H + +struct clk; +struct platform_device; +struct regmap; +struct reset_control; + +struct snd_soc_component_driver; +struct snd_soc_dai; +struct snd_soc_dai_driver; +struct snd_pcm_ops; +struct snd_soc_pcm_runtime; + +#define AXG_FIFO_CH_MAX 128 +#define AXG_FIFO_RATES (SNDRV_PCM_RATE_5512 | \ + SNDRV_PCM_RATE_8000_192000) +#define AXG_FIFO_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define AXG_FIFO_BURST 8 +#define AXG_FIFO_MIN_CNT 64 +#define AXG_FIFO_MIN_DEPTH (AXG_FIFO_BURST * AXG_FIFO_MIN_CNT) + +#define FIFO_INT_ADDR_FINISH BIT(0) +#define FIFO_INT_ADDR_INT BIT(1) +#define FIFO_INT_COUNT_REPEAT BIT(2) +#define FIFO_INT_COUNT_ONCE BIT(3) +#define FIFO_INT_FIFO_ZERO BIT(4) +#define FIFO_INT_FIFO_DEPTH BIT(5) +#define FIFO_INT_MASK GENMASK(7, 0) + +#define FIFO_CTRL0 0x00 +#define CTRL0_DMA_EN BIT(31) +#define CTRL0_INT_EN(x) ((x) << 16) +#define CTRL0_SEL_MASK GENMASK(2, 0) +#define CTRL0_SEL_SHIFT 0 +#define FIFO_CTRL1 0x04 +#define CTRL1_INT_CLR(x) ((x) << 0) +#define CTRL1_STATUS2_SEL_MASK GENMASK(11, 8) +#define CTRL1_STATUS2_SEL(x) ((x) << 8) +#define STATUS2_SEL_DDR_READ 0 +#define CTRL1_THRESHOLD_MASK GENMASK(23, 16) +#define CTRL1_THRESHOLD(x) ((x) << 16) +#define CTRL1_FRDDR_DEPTH_MASK GENMASK(31, 24) +#define CTRL1_FRDDR_DEPTH(x) ((x) << 24) +#define FIFO_START_ADDR 0x08 +#define FIFO_FINISH_ADDR 0x0c +#define FIFO_INT_ADDR 0x10 +#define FIFO_STATUS1 0x14 +#define STATUS1_INT_STS(x) ((x) << 0) +#define FIFO_STATUS2 0x18 + +struct axg_fifo { + struct regmap *map; + struct clk *pclk; + struct reset_control *arb; + int irq; +}; + +struct axg_fifo_match_data { + const struct snd_soc_component_driver *component_drv; + struct snd_soc_dai_driver *dai_drv; +}; + +extern const struct snd_pcm_ops axg_fifo_pcm_ops; + +int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type); +int axg_fifo_probe(struct platform_device *pdev); + +#endif /* _MESON_AXG_FIFO_H */ diff --git a/sound/soc/meson/axg-frddr.c b/sound/soc/meson/axg-frddr.c new file mode 100644 index 000000000..a6f6f6a2e --- /dev/null +++ b/sound/soc/meson/axg-frddr.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +/* This driver implements the frontend playback DAI of AXG based SoCs */ + +#include +#include +#include +#include +#include +#include + +#include "axg-fifo.h" + +#define CTRL0_FRDDR_PP_MODE BIT(30) + +static int axg_frddr_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); + unsigned int fifo_depth, fifo_threshold; + int ret; + + /* Enable pclk to access registers and clock the fifo ip */ + ret = clk_prepare_enable(fifo->pclk); + if (ret) + return ret; + + /* Apply single buffer mode to the interface */ + regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_FRDDR_PP_MODE, 0); + + /* + * TODO: We could adapt the fifo depth and the fifo threshold + * depending on the expected memory throughput and lantencies + * For now, we'll just use the same values as the vendor kernel + * Depth and threshold are zero based. + */ + fifo_depth = AXG_FIFO_MIN_CNT - 1; + fifo_threshold = (AXG_FIFO_MIN_CNT / 2) - 1; + regmap_update_bits(fifo->map, FIFO_CTRL1, + CTRL1_FRDDR_DEPTH_MASK | CTRL1_THRESHOLD_MASK, + CTRL1_FRDDR_DEPTH(fifo_depth) | + CTRL1_THRESHOLD(fifo_threshold)); + + return 0; +} + +static void axg_frddr_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(fifo->pclk); +} + +static int axg_frddr_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) +{ + return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_PLAYBACK); +} + +static const struct snd_soc_dai_ops axg_frddr_ops = { + .startup = axg_frddr_dai_startup, + .shutdown = axg_frddr_dai_shutdown, +}; + +static struct snd_soc_dai_driver axg_frddr_dai_drv = { + .name = "FRDDR", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AXG_FIFO_CH_MAX, + .rates = AXG_FIFO_RATES, + .formats = AXG_FIFO_FORMATS, + }, + .ops = &axg_frddr_ops, + .pcm_new = axg_frddr_pcm_new, +}; + +static const char * const axg_frddr_sel_texts[] = { + "OUT 0", "OUT 1", "OUT 2", "OUT 3" +}; + +static SOC_ENUM_SINGLE_DECL(axg_frddr_sel_enum, FIFO_CTRL0, CTRL0_SEL_SHIFT, + axg_frddr_sel_texts); + +static const struct snd_kcontrol_new axg_frddr_out_demux = + SOC_DAPM_ENUM("Output Sink", axg_frddr_sel_enum); + +static const struct snd_soc_dapm_widget axg_frddr_dapm_widgets[] = { + SND_SOC_DAPM_DEMUX("SINK SEL", SND_SOC_NOPM, 0, 0, + &axg_frddr_out_demux), + SND_SOC_DAPM_AIF_OUT("OUT 0", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT 1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT 2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("OUT 3", NULL, 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route axg_frddr_dapm_routes[] = { + { "SINK SEL", NULL, "Playback" }, + { "OUT 0", "OUT 0", "SINK SEL" }, + { "OUT 1", "OUT 1", "SINK SEL" }, + { "OUT 2", "OUT 2", "SINK SEL" }, + { "OUT 3", "OUT 3", "SINK SEL" }, +}; + +static const struct snd_soc_component_driver axg_frddr_component_drv = { + .dapm_widgets = axg_frddr_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_frddr_dapm_widgets), + .dapm_routes = axg_frddr_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_frddr_dapm_routes), + .ops = &axg_fifo_pcm_ops +}; + +static const struct axg_fifo_match_data axg_frddr_match_data = { + .component_drv = &axg_frddr_component_drv, + .dai_drv = &axg_frddr_dai_drv +}; + +static const struct of_device_id axg_frddr_of_match[] = { + { + .compatible = "amlogic,axg-frddr", + .data = &axg_frddr_match_data, + }, {} +}; +MODULE_DEVICE_TABLE(of, axg_frddr_of_match); + +static struct platform_driver axg_frddr_pdrv = { + .probe = axg_fifo_probe, + .driver = { + .name = "axg-frddr", + .of_match_table = axg_frddr_of_match, + }, +}; +module_platform_driver(axg_frddr_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG playback fifo driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-spdifout.c b/sound/soc/meson/axg-spdifout.c new file mode 100644 index 000000000..9dea52805 --- /dev/null +++ b/sound/soc/meson/axg-spdifout.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * NOTE: + * The meaning of bits SPDIFOUT_CTRL0_XXX_SEL is actually the opposite + * of what the documentation says. Manual control on V, U and C bits is + * applied when the related sel bits are cleared + */ + +#define SPDIFOUT_STAT 0x00 +#define SPDIFOUT_GAIN0 0x04 +#define SPDIFOUT_GAIN1 0x08 +#define SPDIFOUT_CTRL0 0x0c +#define SPDIFOUT_CTRL0_EN BIT(31) +#define SPDIFOUT_CTRL0_RST_OUT BIT(29) +#define SPDIFOUT_CTRL0_RST_IN BIT(28) +#define SPDIFOUT_CTRL0_USEL BIT(26) +#define SPDIFOUT_CTRL0_USET BIT(25) +#define SPDIFOUT_CTRL0_CHSTS_SEL BIT(24) +#define SPDIFOUT_CTRL0_DATA_SEL BIT(20) +#define SPDIFOUT_CTRL0_MSB_FIRST BIT(19) +#define SPDIFOUT_CTRL0_VSEL BIT(18) +#define SPDIFOUT_CTRL0_VSET BIT(17) +#define SPDIFOUT_CTRL0_MASK_MASK GENMASK(11, 4) +#define SPDIFOUT_CTRL0_MASK(x) ((x) << 4) +#define SPDIFOUT_CTRL1 0x10 +#define SPDIFOUT_CTRL1_MSB_POS_MASK GENMASK(12, 8) +#define SPDIFOUT_CTRL1_MSB_POS(x) ((x) << 8) +#define SPDIFOUT_CTRL1_TYPE_MASK GENMASK(6, 4) +#define SPDIFOUT_CTRL1_TYPE(x) ((x) << 4) +#define SPDIFOUT_PREAMB 0x14 +#define SPDIFOUT_SWAP 0x18 +#define SPDIFOUT_CHSTS0 0x1c +#define SPDIFOUT_CHSTS1 0x20 +#define SPDIFOUT_CHSTS2 0x24 +#define SPDIFOUT_CHSTS3 0x28 +#define SPDIFOUT_CHSTS4 0x2c +#define SPDIFOUT_CHSTS5 0x30 +#define SPDIFOUT_CHSTS6 0x34 +#define SPDIFOUT_CHSTS7 0x38 +#define SPDIFOUT_CHSTS8 0x3c +#define SPDIFOUT_CHSTS9 0x40 +#define SPDIFOUT_CHSTSA 0x44 +#define SPDIFOUT_CHSTSB 0x48 +#define SPDIFOUT_MUTE_VAL 0x4c + +struct axg_spdifout { + struct regmap *map; + struct clk *mclk; + struct clk *pclk; +}; + +static void axg_spdifout_enable(struct regmap *map) +{ + /* Apply both reset */ + regmap_update_bits(map, SPDIFOUT_CTRL0, + SPDIFOUT_CTRL0_RST_OUT | SPDIFOUT_CTRL0_RST_IN, + 0); + + /* Clear out reset before in reset */ + regmap_update_bits(map, SPDIFOUT_CTRL0, + SPDIFOUT_CTRL0_RST_OUT, SPDIFOUT_CTRL0_RST_OUT); + regmap_update_bits(map, SPDIFOUT_CTRL0, + SPDIFOUT_CTRL0_RST_IN, SPDIFOUT_CTRL0_RST_IN); + + /* Enable spdifout */ + regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN, + SPDIFOUT_CTRL0_EN); +} + +static void axg_spdifout_disable(struct regmap *map) +{ + regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN, 0); +} + +static int axg_spdifout_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + axg_spdifout_enable(priv->map); + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + axg_spdifout_disable(priv->map); + return 0; + + default: + return -EINVAL; + } +} + +static int axg_spdifout_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + + /* Use spdif valid bit to perform digital mute */ + regmap_update_bits(priv->map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_VSET, + mute ? SPDIFOUT_CTRL0_VSET : 0); + + return 0; +} + +static int axg_spdifout_sample_fmt(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + unsigned int val; + + /* Set the samples spdifout will pull from the FIFO */ + switch (params_channels(params)) { + case 1: + val = SPDIFOUT_CTRL0_MASK(0x1); + break; + case 2: + val = SPDIFOUT_CTRL0_MASK(0x3); + break; + default: + dev_err(dai->dev, "too many channels for spdif dai: %u\n", + params_channels(params)); + return -EINVAL; + } + + regmap_update_bits(priv->map, SPDIFOUT_CTRL0, + SPDIFOUT_CTRL0_MASK_MASK, val); + + /* FIFO data are arranged in chunks of 64bits */ + switch (params_physical_width(params)) { + case 8: + /* 8 samples of 8 bits */ + val = SPDIFOUT_CTRL1_TYPE(0); + break; + case 16: + /* 4 samples of 16 bits - right justified */ + val = SPDIFOUT_CTRL1_TYPE(2); + break; + case 32: + /* 2 samples of 32 bits - right justified */ + val = SPDIFOUT_CTRL1_TYPE(4); + break; + default: + dev_err(dai->dev, "Unsupported physical width: %u\n", + params_physical_width(params)); + return -EINVAL; + } + + /* Position of the MSB in FIFO samples */ + val |= SPDIFOUT_CTRL1_MSB_POS(params_width(params) - 1); + + regmap_update_bits(priv->map, SPDIFOUT_CTRL1, + SPDIFOUT_CTRL1_MSB_POS_MASK | + SPDIFOUT_CTRL1_TYPE_MASK, val); + + regmap_update_bits(priv->map, SPDIFOUT_CTRL0, + SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL, + 0); + + return 0; +} + +static int axg_spdifout_set_chsts(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + unsigned int offset; + int ret; + u8 cs[4]; + u32 val; + + ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, 4); + if (ret < 0) { + dev_err(dai->dev, "Creating IEC958 channel status failed %d\n", + ret); + return ret; + } + val = cs[0] | cs[1] << 8 | cs[2] << 16 | cs[3] << 24; + + /* Setup channel status A bits [31 - 0]*/ + regmap_write(priv->map, SPDIFOUT_CHSTS0, val); + + /* Clear channel status A bits [191 - 32] */ + for (offset = SPDIFOUT_CHSTS1; offset <= SPDIFOUT_CHSTS5; + offset += regmap_get_reg_stride(priv->map)) + regmap_write(priv->map, offset, 0); + + /* Setup channel status B bits [31 - 0]*/ + regmap_write(priv->map, SPDIFOUT_CHSTS6, val); + + /* Clear channel status B bits [191 - 32] */ + for (offset = SPDIFOUT_CHSTS7; offset <= SPDIFOUT_CHSTSB; + offset += regmap_get_reg_stride(priv->map)) + regmap_write(priv->map, offset, 0); + + return 0; +} + +static int axg_spdifout_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + int ret; + + /* 2 * 32bits per subframe * 2 channels = 128 */ + ret = clk_set_rate(priv->mclk, rate * 128); + if (ret) { + dev_err(dai->dev, "failed to set spdif clock\n"); + return ret; + } + + ret = axg_spdifout_sample_fmt(params, dai); + if (ret) { + dev_err(dai->dev, "failed to setup sample format\n"); + return ret; + } + + ret = axg_spdifout_set_chsts(params, dai); + if (ret) { + dev_err(dai->dev, "failed to setup channel status words\n"); + return ret; + } + + return 0; +} + +static int axg_spdifout_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + int ret; + + /* Clock the spdif output block */ + ret = clk_prepare_enable(priv->pclk); + if (ret) { + dev_err(dai->dev, "failed to enable pclk\n"); + return ret; + } + + /* Make sure the block is initially stopped */ + axg_spdifout_disable(priv->map); + + /* Insert data from bit 27 lsb first */ + regmap_update_bits(priv->map, SPDIFOUT_CTRL0, + SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL, + 0); + + /* Manual control of V, C and U, U = 0 */ + regmap_update_bits(priv->map, SPDIFOUT_CTRL0, + SPDIFOUT_CTRL0_CHSTS_SEL | SPDIFOUT_CTRL0_VSEL | + SPDIFOUT_CTRL0_USEL | SPDIFOUT_CTRL0_USET, + 0); + + /* Static SWAP configuration ATM */ + regmap_write(priv->map, SPDIFOUT_SWAP, 0x10); + + return 0; +} + +static void axg_spdifout_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(priv->pclk); +} + +static const struct snd_soc_dai_ops axg_spdifout_ops = { + .trigger = axg_spdifout_trigger, + .digital_mute = axg_spdifout_digital_mute, + .hw_params = axg_spdifout_hw_params, + .startup = axg_spdifout_startup, + .shutdown = axg_spdifout_shutdown, +}; + +static struct snd_soc_dai_driver axg_spdifout_dai_drv[] = { + { + .name = "SPDIF Output", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .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), + .formats = (SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &axg_spdifout_ops, + }, +}; + +static const char * const spdifout_sel_texts[] = { + "IN 0", "IN 1", "IN 2", +}; + +static SOC_ENUM_SINGLE_DECL(axg_spdifout_sel_enum, SPDIFOUT_CTRL1, 24, + spdifout_sel_texts); + +static const struct snd_kcontrol_new axg_spdifout_in_mux = + SOC_DAPM_ENUM("Input Source", axg_spdifout_sel_enum); + +static const struct snd_soc_dapm_widget axg_spdifout_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_spdifout_in_mux), +}; + +static const struct snd_soc_dapm_route axg_spdifout_dapm_routes[] = { + { "SRC SEL", "IN 0", "IN 0" }, + { "SRC SEL", "IN 1", "IN 1" }, + { "SRC SEL", "IN 2", "IN 2" }, + { "Playback", NULL, "SRC SEL" }, +}; + +static const struct snd_kcontrol_new axg_spdifout_controls[] = { + SOC_DOUBLE("Playback Volume", SPDIFOUT_GAIN0, 0, 8, 255, 0), + SOC_DOUBLE("Playback Switch", SPDIFOUT_CTRL0, 22, 21, 1, 1), + SOC_SINGLE("Playback Gain Enable Switch", + SPDIFOUT_CTRL1, 26, 1, 0), + SOC_SINGLE("Playback Channels Mix Switch", + SPDIFOUT_CTRL0, 23, 1, 0), +}; + +static int axg_spdifout_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct axg_spdifout *priv = snd_soc_component_get_drvdata(component); + enum snd_soc_bias_level now = + snd_soc_component_get_bias_level(component); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (now == SND_SOC_BIAS_STANDBY) + ret = clk_prepare_enable(priv->mclk); + break; + + case SND_SOC_BIAS_STANDBY: + if (now == SND_SOC_BIAS_PREPARE) + clk_disable_unprepare(priv->mclk); + break; + + case SND_SOC_BIAS_OFF: + case SND_SOC_BIAS_ON: + break; + } + + return ret; +} + +static const struct snd_soc_component_driver axg_spdifout_component_drv = { + .controls = axg_spdifout_controls, + .num_controls = ARRAY_SIZE(axg_spdifout_controls), + .dapm_widgets = axg_spdifout_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_spdifout_dapm_widgets), + .dapm_routes = axg_spdifout_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_spdifout_dapm_routes), + .set_bias_level = axg_spdifout_set_bias_level, +}; + +static const struct regmap_config axg_spdifout_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SPDIFOUT_MUTE_VAL, +}; + +static const struct of_device_id axg_spdifout_of_match[] = { + { .compatible = "amlogic,axg-spdifout", }, + {} +}; +MODULE_DEVICE_TABLE(of, axg_spdifout_of_match); + +static int axg_spdifout_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct axg_spdifout *priv; + struct resource *res; + void __iomem *regs; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->map = devm_regmap_init_mmio(dev, regs, &axg_spdifout_regmap_cfg); + if (IS_ERR(priv->map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(priv->map)); + return PTR_ERR(priv->map); + } + + priv->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(priv->pclk)) { + ret = PTR_ERR(priv->pclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get pclk: %d\n", ret); + return ret; + } + + priv->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(priv->mclk)) { + ret = PTR_ERR(priv->mclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get mclk: %d\n", ret); + return ret; + } + + return devm_snd_soc_register_component(dev, &axg_spdifout_component_drv, + axg_spdifout_dai_drv, ARRAY_SIZE(axg_spdifout_dai_drv)); +} + +static struct platform_driver axg_spdifout_pdrv = { + .probe = axg_spdifout_probe, + .driver = { + .name = "axg-spdifout", + .of_match_table = axg_spdifout_of_match, + }, +}; +module_platform_driver(axg_spdifout_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG SPDIF Output driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-tdm-formatter.c b/sound/soc/meson/axg-tdm-formatter.c new file mode 100644 index 000000000..43e390f93 --- /dev/null +++ b/sound/soc/meson/axg-tdm-formatter.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include +#include + +#include "axg-tdm-formatter.h" + +struct axg_tdm_formatter { + struct list_head list; + struct axg_tdm_stream *stream; + const struct axg_tdm_formatter_driver *drv; + struct clk *pclk; + struct clk *sclk; + struct clk *lrclk; + struct clk *sclk_sel; + struct clk *lrclk_sel; + bool enabled; + struct regmap *map; +}; + +int axg_tdm_formatter_set_channel_masks(struct regmap *map, + struct axg_tdm_stream *ts, + unsigned int offset) +{ + unsigned int val, ch = ts->channels; + unsigned long mask; + int i, j; + + /* + * Distribute the channels of the stream over the available slots + * of each TDM lane + */ + for (i = 0; i < AXG_TDM_NUM_LANES; i++) { + val = 0; + mask = ts->mask[i]; + + for (j = find_first_bit(&mask, 32); + (j < 32) && ch; + j = find_next_bit(&mask, 32, j + 1)) { + val |= 1 << j; + ch -= 1; + } + + regmap_write(map, offset, val); + offset += regmap_get_reg_stride(map); + } + + /* + * If we still have channel left at the end of the process, it means + * the stream has more channels than we can accommodate and we should + * have caught this earlier. + */ + if (WARN_ON(ch != 0)) { + pr_err("channel mask error\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks); + +static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter) +{ + struct axg_tdm_stream *ts = formatter->stream; + bool invert = formatter->drv->invert_sclk; + int ret; + + /* Do nothing if the formatter is already enabled */ + if (formatter->enabled) + return 0; + + /* + * If sclk is inverted, invert it back and provide the inversion + * required by the formatter + */ + invert ^= axg_tdm_sclk_invert(ts->iface->fmt); + ret = clk_set_phase(formatter->sclk, invert ? 180 : 0); + if (ret) + return ret; + + /* Setup the stream parameter in the formatter */ + ret = formatter->drv->ops->prepare(formatter->map, formatter->stream); + if (ret) + return ret; + + /* Enable the signal clocks feeding the formatter */ + ret = clk_prepare_enable(formatter->sclk); + if (ret) + return ret; + + ret = clk_prepare_enable(formatter->lrclk); + if (ret) { + clk_disable_unprepare(formatter->sclk); + return ret; + } + + /* Finally, actually enable the formatter */ + formatter->drv->ops->enable(formatter->map); + formatter->enabled = true; + + return 0; +} + +static void axg_tdm_formatter_disable(struct axg_tdm_formatter *formatter) +{ + /* Do nothing if the formatter is already disabled */ + if (!formatter->enabled) + return; + + formatter->drv->ops->disable(formatter->map); + clk_disable_unprepare(formatter->lrclk); + clk_disable_unprepare(formatter->sclk); + formatter->enabled = false; +} + +static int axg_tdm_formatter_attach(struct axg_tdm_formatter *formatter) +{ + struct axg_tdm_stream *ts = formatter->stream; + int ret = 0; + + mutex_lock(&ts->lock); + + /* Catch up if the stream is already running when we attach */ + if (ts->ready) { + ret = axg_tdm_formatter_enable(formatter); + if (ret) { + pr_err("failed to enable formatter\n"); + goto out; + } + } + + list_add_tail(&formatter->list, &ts->formatter_list); +out: + mutex_unlock(&ts->lock); + return ret; +} + +static void axg_tdm_formatter_dettach(struct axg_tdm_formatter *formatter) +{ + struct axg_tdm_stream *ts = formatter->stream; + + mutex_lock(&ts->lock); + list_del(&formatter->list); + mutex_unlock(&ts->lock); + + axg_tdm_formatter_disable(formatter); +} + +static int axg_tdm_formatter_power_up(struct axg_tdm_formatter *formatter, + struct snd_soc_dapm_widget *w) +{ + struct axg_tdm_stream *ts = formatter->drv->ops->get_stream(w); + int ret; + + /* + * If we don't get a stream at this stage, it would mean that the + * widget is powering up but is not attached to any backend DAI. + * It should not happen, ever ! + */ + if (WARN_ON(!ts)) + return -ENODEV; + + /* Clock our device */ + ret = clk_prepare_enable(formatter->pclk); + if (ret) + return ret; + + /* Reparent the bit clock to the TDM interface */ + ret = clk_set_parent(formatter->sclk_sel, ts->iface->sclk); + if (ret) + goto disable_pclk; + + /* Reparent the sample clock to the TDM interface */ + ret = clk_set_parent(formatter->lrclk_sel, ts->iface->lrclk); + if (ret) + goto disable_pclk; + + formatter->stream = ts; + ret = axg_tdm_formatter_attach(formatter); + if (ret) + goto disable_pclk; + + return 0; + +disable_pclk: + clk_disable_unprepare(formatter->pclk); + return ret; +} + +static void axg_tdm_formatter_power_down(struct axg_tdm_formatter *formatter) +{ + axg_tdm_formatter_dettach(formatter); + clk_disable_unprepare(formatter->pclk); + formatter->stream = NULL; +} + +int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, + int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct axg_tdm_formatter *formatter = snd_soc_component_get_drvdata(c); + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = axg_tdm_formatter_power_up(formatter, w); + break; + + case SND_SOC_DAPM_PRE_PMD: + axg_tdm_formatter_power_down(formatter); + break; + + default: + dev_err(c->dev, "Unexpected event %d\n", event); + return -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(axg_tdm_formatter_event); + +int axg_tdm_formatter_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct axg_tdm_formatter_driver *drv; + struct axg_tdm_formatter *formatter; + struct resource *res; + void __iomem *regs; + int ret; + + drv = of_device_get_match_data(dev); + if (!drv) { + dev_err(dev, "failed to match device\n"); + return -ENODEV; + } + + formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); + if (!formatter) + return -ENOMEM; + platform_set_drvdata(pdev, formatter); + formatter->drv = drv; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); + if (IS_ERR(formatter->map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(formatter->map)); + return PTR_ERR(formatter->map); + } + + /* Peripharal clock */ + formatter->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(formatter->pclk)) { + ret = PTR_ERR(formatter->pclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get pclk: %d\n", ret); + return ret; + } + + /* Formatter bit clock */ + formatter->sclk = devm_clk_get(dev, "sclk"); + if (IS_ERR(formatter->sclk)) { + ret = PTR_ERR(formatter->sclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get sclk: %d\n", ret); + return ret; + } + + /* Formatter sample clock */ + formatter->lrclk = devm_clk_get(dev, "lrclk"); + if (IS_ERR(formatter->lrclk)) { + ret = PTR_ERR(formatter->lrclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get lrclk: %d\n", ret); + return ret; + } + + /* Formatter bit clock input multiplexer */ + formatter->sclk_sel = devm_clk_get(dev, "sclk_sel"); + if (IS_ERR(formatter->sclk_sel)) { + ret = PTR_ERR(formatter->sclk_sel); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get sclk_sel: %d\n", ret); + return ret; + } + + /* Formatter sample clock input multiplexer */ + formatter->lrclk_sel = devm_clk_get(dev, "lrclk_sel"); + if (IS_ERR(formatter->lrclk_sel)) { + ret = PTR_ERR(formatter->lrclk_sel); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get lrclk_sel: %d\n", ret); + return ret; + } + + return devm_snd_soc_register_component(dev, drv->component_drv, + NULL, 0); +} +EXPORT_SYMBOL_GPL(axg_tdm_formatter_probe); + +int axg_tdm_stream_start(struct axg_tdm_stream *ts) +{ + struct axg_tdm_formatter *formatter; + int ret = 0; + + mutex_lock(&ts->lock); + ts->ready = true; + + /* Start all the formatters attached to the stream */ + list_for_each_entry(formatter, &ts->formatter_list, list) { + ret = axg_tdm_formatter_enable(formatter); + if (ret) { + pr_err("failed to start tdm stream\n"); + goto out; + } + } + +out: + mutex_unlock(&ts->lock); + return ret; +} +EXPORT_SYMBOL_GPL(axg_tdm_stream_start); + +void axg_tdm_stream_stop(struct axg_tdm_stream *ts) +{ + struct axg_tdm_formatter *formatter; + + mutex_lock(&ts->lock); + ts->ready = false; + + /* Stop all the formatters attached to the stream */ + list_for_each_entry(formatter, &ts->formatter_list, list) { + axg_tdm_formatter_disable(formatter); + } + + mutex_unlock(&ts->lock); +} +EXPORT_SYMBOL_GPL(axg_tdm_stream_stop); + +struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface) +{ + struct axg_tdm_stream *ts; + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (ts) { + INIT_LIST_HEAD(&ts->formatter_list); + mutex_init(&ts->lock); + ts->iface = iface; + } + + return ts; +} +EXPORT_SYMBOL_GPL(axg_tdm_stream_alloc); + +void axg_tdm_stream_free(struct axg_tdm_stream *ts) +{ + /* + * If the list is not empty, it would mean that one of the formatter + * widget is still powered and attached to the interface while we + * we are removing the TDM DAI. It should not be possible + */ + WARN_ON(!list_empty(&ts->formatter_list)); + mutex_destroy(&ts->lock); + kfree(ts); +} +EXPORT_SYMBOL_GPL(axg_tdm_stream_free); + +MODULE_DESCRIPTION("Amlogic AXG TDM formatter driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-tdm-formatter.h b/sound/soc/meson/axg-tdm-formatter.h new file mode 100644 index 000000000..cf947caf3 --- /dev/null +++ b/sound/soc/meson/axg-tdm-formatter.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) + * + * Copyright (c) 2018 Baylibre SAS. + * Author: Jerome Brunet + */ + +#ifndef _MESON_AXG_TDM_FORMATTER_H +#define _MESON_AXG_TDM_FORMATTER_H + +#include "axg-tdm.h" + +struct platform_device; +struct regmap; +struct snd_soc_dapm_widget; +struct snd_kcontrol; + +struct axg_tdm_formatter_ops { + struct axg_tdm_stream *(*get_stream)(struct snd_soc_dapm_widget *w); + void (*enable)(struct regmap *map); + void (*disable)(struct regmap *map); + int (*prepare)(struct regmap *map, struct axg_tdm_stream *ts); +}; + +struct axg_tdm_formatter_driver { + const struct snd_soc_component_driver *component_drv; + const struct regmap_config *regmap_cfg; + const struct axg_tdm_formatter_ops *ops; + bool invert_sclk; +}; + +int axg_tdm_formatter_set_channel_masks(struct regmap *map, + struct axg_tdm_stream *ts, + unsigned int offset); +int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, + int event); +int axg_tdm_formatter_probe(struct platform_device *pdev); + +#endif /* _MESON_AXG_TDM_FORMATTER_H */ diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c new file mode 100644 index 000000000..01cc551a8 --- /dev/null +++ b/sound/soc/meson/axg-tdm-interface.c @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include +#include +#include + +#include "axg-tdm.h" + +enum { + TDM_IFACE_PAD, + TDM_IFACE_LOOPBACK, +}; + +static unsigned int axg_tdm_slots_total(u32 *mask) +{ + unsigned int slots = 0; + int i; + + if (!mask) + return 0; + + /* Count the total number of slots provided by all 4 lanes */ + for (i = 0; i < AXG_TDM_NUM_LANES; i++) + slots += hweight32(mask[i]); + + return slots; +} + +int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, + u32 *rx_mask, unsigned int slots, + unsigned int slot_width) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *tx = (struct axg_tdm_stream *) + dai->playback_dma_data; + struct axg_tdm_stream *rx = (struct axg_tdm_stream *) + dai->capture_dma_data; + unsigned int tx_slots, rx_slots; + + tx_slots = axg_tdm_slots_total(tx_mask); + rx_slots = axg_tdm_slots_total(rx_mask); + + /* We should at least have a slot for a valid interface */ + if (!tx_slots && !rx_slots) { + dev_err(dai->dev, "interface has no slot\n"); + return -EINVAL; + } + + /* + * Amend the dai driver channel number and let dpcm channel merge do + * its job + */ + if (tx) { + tx->mask = tx_mask; + dai->driver->playback.channels_max = tx_slots; + } + + if (rx) { + rx->mask = rx_mask; + dai->driver->capture.channels_max = rx_slots; + } + + iface->slots = slots; + + switch (slot_width) { + case 0: + /* defaults width to 32 if not provided */ + iface->slot_width = 32; + break; + case 8: + case 16: + case 24: + case 32: + iface->slot_width = slot_width; + break; + default: + dev_err(dai->dev, "unsupported slot width: %d\n", slot_width); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(axg_tdm_set_tdm_slots); + +static int axg_tdm_iface_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + int ret = -ENOTSUPP; + + if (dir == SND_SOC_CLOCK_OUT && clk_id == 0) { + if (!iface->mclk) { + dev_warn(dai->dev, "master clock not provided\n"); + } else { + ret = clk_set_rate(iface->mclk, freq); + if (!ret) + iface->mclk_rate = freq; + } + } + + return ret; +} + +static int axg_tdm_iface_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + if (!iface->mclk) { + dev_err(dai->dev, "cpu clock master: mclk missing\n"); + return -ENODEV; + } + break; + + case SND_SOC_DAIFMT_CBM_CFM: + break; + + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + dev_err(dai->dev, "only CBS_CFS and CBM_CFM are supported\n"); + /* Fall-through */ + default: + return -EINVAL; + } + + iface->fmt = fmt; + return 0; +} + +static int axg_tdm_iface_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *ts = + snd_soc_dai_get_dma_data(dai, substream); + int ret; + + if (!axg_tdm_slots_total(ts->mask)) { + dev_err(dai->dev, "interface has not slots\n"); + return -EINVAL; + } + + /* Apply component wide rate symmetry */ + if (dai->component->active) { + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + iface->rate); + if (ret < 0) { + dev_err(dai->dev, + "can't set iface rate constraint\n"); + return ret; + } + } + + return 0; +} + +static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + unsigned int channels = params_channels(params); + unsigned int width = params_width(params); + + /* Save rate and sample_bits for component symmetry */ + iface->rate = params_rate(params); + + /* Make sure this interface can cope with the stream */ + if (axg_tdm_slots_total(ts->mask) < channels) { + dev_err(dai->dev, "not enough slots for channels\n"); + return -EINVAL; + } + + if (iface->slot_width < width) { + dev_err(dai->dev, "incompatible slots width for stream\n"); + return -EINVAL; + } + + /* Save the parameter for tdmout/tdmin widgets */ + ts->physical_width = params_physical_width(params); + ts->width = params_width(params); + ts->channels = params_channels(params); + + return 0; +} + +static int axg_tdm_iface_set_lrclk(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + unsigned int ratio_num; + int ret; + + ret = clk_set_rate(iface->lrclk, params_rate(params)); + if (ret) { + dev_err(dai->dev, "setting sample clock failed: %d\n", ret); + return ret; + } + + switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + /* 50% duty cycle ratio */ + ratio_num = 1; + break; + + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* + * A zero duty cycle ratio will result in setting the mininum + * ratio possible which, for this clock, is 1 cycle of the + * parent bclk clock high and the rest low, This is exactly + * what we want here. + */ + ratio_num = 0; + break; + + default: + return -EINVAL; + } + + ret = clk_set_duty_cycle(iface->lrclk, ratio_num, 2); + if (ret) { + dev_err(dai->dev, + "setting sample clock duty cycle failed: %d\n", ret); + return ret; + } + + /* Set sample clock inversion */ + ret = clk_set_phase(iface->lrclk, + axg_tdm_lrclk_invert(iface->fmt) ? 180 : 0); + if (ret) { + dev_err(dai->dev, + "setting sample clock phase failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + unsigned long srate; + int ret; + + srate = iface->slots * iface->slot_width * params_rate(params); + + if (!iface->mclk_rate) { + /* If no specific mclk is requested, default to bit clock * 4 */ + clk_set_rate(iface->mclk, 4 * srate); + } else { + /* Check if we can actually get the bit clock from mclk */ + if (iface->mclk_rate % srate) { + dev_err(dai->dev, + "can't derive sclk %lu from mclk %lu\n", + srate, iface->mclk_rate); + return -EINVAL; + } + } + + ret = clk_set_rate(iface->sclk, srate); + if (ret) { + dev_err(dai->dev, "setting bit clock failed: %d\n", ret); + return ret; + } + + /* Set the bit clock inversion */ + ret = clk_set_phase(iface->sclk, + axg_tdm_sclk_invert(iface->fmt) ? 0 : 180); + if (ret) { + dev_err(dai->dev, "setting bit clock phase failed: %d\n", ret); + return ret; + } + + return ret; +} + +static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + int ret; + + switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + if (iface->slots > 2) { + dev_err(dai->dev, "bad slot number for format: %d\n", + iface->slots); + return -EINVAL; + } + break; + + case SND_SOC_DAI_FORMAT_DSP_A: + case SND_SOC_DAI_FORMAT_DSP_B: + break; + + default: + dev_err(dai->dev, "unsupported dai format\n"); + return -EINVAL; + } + + ret = axg_tdm_iface_set_stream(substream, params, dai); + if (ret) + return ret; + + if ((iface->fmt & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBS_CFS) { + ret = axg_tdm_iface_set_sclk(dai, params); + if (ret) + return ret; + + ret = axg_tdm_iface_set_lrclk(dai, params); + if (ret) + return ret; + } + + return 0; +} + +static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + + /* Stop all attached formatters */ + axg_tdm_stream_stop(ts); + + return 0; +} + +static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + + /* Force all attached formatters to update */ + return axg_tdm_stream_reset(ts); +} + +static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai) +{ + if (dai->capture_dma_data) + axg_tdm_stream_free(dai->capture_dma_data); + + if (dai->playback_dma_data) + axg_tdm_stream_free(dai->playback_dma_data); + + return 0; +} + +static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + + if (dai->capture_widget) { + dai->capture_dma_data = axg_tdm_stream_alloc(iface); + if (!dai->capture_dma_data) + return -ENOMEM; + } + + if (dai->playback_widget) { + dai->playback_dma_data = axg_tdm_stream_alloc(iface); + if (!dai->playback_dma_data) { + axg_tdm_iface_remove_dai(dai); + return -ENOMEM; + } + } + + return 0; +} + +static const struct snd_soc_dai_ops axg_tdm_iface_ops = { + .set_sysclk = axg_tdm_iface_set_sysclk, + .set_fmt = axg_tdm_iface_set_fmt, + .startup = axg_tdm_iface_startup, + .hw_params = axg_tdm_iface_hw_params, + .prepare = axg_tdm_iface_prepare, + .hw_free = axg_tdm_iface_hw_free, +}; + +/* TDM Backend DAIs */ +static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = { + [TDM_IFACE_PAD] = { + .name = "TDM Pad", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AXG_TDM_CHANNEL_MAX, + .rates = AXG_TDM_RATES, + .formats = AXG_TDM_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AXG_TDM_CHANNEL_MAX, + .rates = AXG_TDM_RATES, + .formats = AXG_TDM_FORMATS, + }, + .id = TDM_IFACE_PAD, + .ops = &axg_tdm_iface_ops, + .probe = axg_tdm_iface_probe_dai, + .remove = axg_tdm_iface_remove_dai, + }, + [TDM_IFACE_LOOPBACK] = { + .name = "TDM Loopback", + .capture = { + .stream_name = "Loopback", + .channels_min = 1, + .channels_max = AXG_TDM_CHANNEL_MAX, + .rates = AXG_TDM_RATES, + .formats = AXG_TDM_FORMATS, + }, + .id = TDM_IFACE_LOOPBACK, + .ops = &axg_tdm_iface_ops, + .probe = axg_tdm_iface_probe_dai, + .remove = axg_tdm_iface_remove_dai, + }, +}; + +static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct axg_tdm_iface *iface = snd_soc_component_get_drvdata(component); + enum snd_soc_bias_level now = + snd_soc_component_get_bias_level(component); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (now == SND_SOC_BIAS_STANDBY) + ret = clk_prepare_enable(iface->mclk); + break; + + case SND_SOC_BIAS_STANDBY: + if (now == SND_SOC_BIAS_PREPARE) + clk_disable_unprepare(iface->mclk); + break; + + case SND_SOC_BIAS_OFF: + case SND_SOC_BIAS_ON: + break; + } + + return ret; +} + +static const struct snd_soc_dapm_widget axg_tdm_iface_dapm_widgets[] = { + SND_SOC_DAPM_SIGGEN("Playback Signal"), +}; + +static const struct snd_soc_dapm_route axg_tdm_iface_dapm_routes[] = { + { "Loopback", NULL, "Playback Signal" }, +}; + +static const struct snd_soc_component_driver axg_tdm_iface_component_drv = { + .dapm_widgets = axg_tdm_iface_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_tdm_iface_dapm_widgets), + .dapm_routes = axg_tdm_iface_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_tdm_iface_dapm_routes), + .set_bias_level = axg_tdm_iface_set_bias_level, +}; + +static const struct of_device_id axg_tdm_iface_of_match[] = { + { .compatible = "amlogic,axg-tdm-iface", }, + {} +}; +MODULE_DEVICE_TABLE(of, axg_tdm_iface_of_match); + +static int axg_tdm_iface_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_dai_driver *dai_drv; + struct axg_tdm_iface *iface; + int ret, i; + + iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL); + if (!iface) + return -ENOMEM; + platform_set_drvdata(pdev, iface); + + /* + * Duplicate dai driver: depending on the slot masks configuration + * We'll change the number of channel provided by DAI stream, so dpcm + * channel merge can be done properly + */ + dai_drv = devm_kcalloc(dev, ARRAY_SIZE(axg_tdm_iface_dai_drv), + sizeof(*dai_drv), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(axg_tdm_iface_dai_drv); i++) + memcpy(&dai_drv[i], &axg_tdm_iface_dai_drv[i], + sizeof(*dai_drv)); + + /* Bit clock provided on the pad */ + iface->sclk = devm_clk_get(dev, "sclk"); + if (IS_ERR(iface->sclk)) { + ret = PTR_ERR(iface->sclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get sclk: %d\n", ret); + return ret; + } + + /* Sample clock provided on the pad */ + iface->lrclk = devm_clk_get(dev, "lrclk"); + if (IS_ERR(iface->lrclk)) { + ret = PTR_ERR(iface->lrclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get lrclk: %d\n", ret); + return ret; + } + + /* + * mclk maybe be missing when the cpu dai is in slave mode and + * the codec does not require it to provide a master clock. + * At this point, ignore the error if mclk is missing. We'll + * throw an error if the cpu dai is master and mclk is missing + */ + iface->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(iface->mclk)) { + ret = PTR_ERR(iface->mclk); + if (ret == -ENOENT) { + iface->mclk = NULL; + } else { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get mclk: %d\n", ret); + return ret; + } + } + + return devm_snd_soc_register_component(dev, + &axg_tdm_iface_component_drv, dai_drv, + ARRAY_SIZE(axg_tdm_iface_dai_drv)); +} + +static struct platform_driver axg_tdm_iface_pdrv = { + .probe = axg_tdm_iface_probe, + .driver = { + .name = "axg-tdm-iface", + .of_match_table = axg_tdm_iface_of_match, + }, +}; +module_platform_driver(axg_tdm_iface_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG TDM interface driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h new file mode 100644 index 000000000..5774ce091 --- /dev/null +++ b/sound/soc/meson/axg-tdm.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) + * + * Copyright (c) 2018 Baylibre SAS. + * Author: Jerome Brunet + */ + +#ifndef _MESON_AXG_TDM_H +#define _MESON_AXG_TDM_H + +#include +#include +#include +#include +#include + +#define AXG_TDM_NUM_LANES 4 +#define AXG_TDM_CHANNEL_MAX 128 +#define AXG_TDM_RATES (SNDRV_PCM_RATE_5512 | \ + SNDRV_PCM_RATE_8000_192000) +#define AXG_TDM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct axg_tdm_iface { + struct clk *sclk; + struct clk *lrclk; + struct clk *mclk; + unsigned long mclk_rate; + + /* format is common to all the DAIs of the iface */ + unsigned int fmt; + unsigned int slots; + unsigned int slot_width; + + /* For component wide symmetry */ + int rate; +}; + +static inline bool axg_tdm_lrclk_invert(unsigned int fmt) +{ + return ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) ^ + !!(fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_NB_IF)); +} + +static inline bool axg_tdm_sclk_invert(unsigned int fmt) +{ + return fmt & (SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_IB_NF); +} + +struct axg_tdm_stream { + struct axg_tdm_iface *iface; + struct list_head formatter_list; + struct mutex lock; + unsigned int channels; + unsigned int width; + unsigned int physical_width; + u32 *mask; + bool ready; +}; + +struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface); +void axg_tdm_stream_free(struct axg_tdm_stream *ts); +int axg_tdm_stream_start(struct axg_tdm_stream *ts); +void axg_tdm_stream_stop(struct axg_tdm_stream *ts); + +static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts) +{ + axg_tdm_stream_stop(ts); + return axg_tdm_stream_start(ts); +} + +int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, + u32 *rx_mask, unsigned int slots, + unsigned int slot_width); + +#endif /* _MESON_AXG_TDM_H */ diff --git a/sound/soc/meson/axg-tdmin.c b/sound/soc/meson/axg-tdmin.c new file mode 100644 index 000000000..37207bbeb --- /dev/null +++ b/sound/soc/meson/axg-tdmin.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include +#include + +#include "axg-tdm-formatter.h" + +#define TDMIN_CTRL 0x00 +#define TDMIN_CTRL_ENABLE BIT(31) +#define TDMIN_CTRL_I2S_MODE BIT(30) +#define TDMIN_CTRL_RST_OUT BIT(29) +#define TDMIN_CTRL_RST_IN BIT(28) +#define TDMIN_CTRL_WS_INV BIT(25) +#define TDMIN_CTRL_SEL_SHIFT 20 +#define TDMIN_CTRL_IN_BIT_SKEW_MASK GENMASK(18, 16) +#define TDMIN_CTRL_IN_BIT_SKEW(x) ((x) << 16) +#define TDMIN_CTRL_LSB_FIRST BIT(5) +#define TDMIN_CTRL_BITNUM_MASK GENMASK(4, 0) +#define TDMIN_CTRL_BITNUM(x) ((x) << 0) +#define TDMIN_SWAP 0x04 +#define TDMIN_MASK0 0x08 +#define TDMIN_MASK1 0x0c +#define TDMIN_MASK2 0x10 +#define TDMIN_MASK3 0x14 +#define TDMIN_STAT 0x18 +#define TDMIN_MUTE_VAL 0x1c +#define TDMIN_MUTE0 0x20 +#define TDMIN_MUTE1 0x24 +#define TDMIN_MUTE2 0x28 +#define TDMIN_MUTE3 0x2c + +static const struct regmap_config axg_tdmin_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TDMIN_MUTE3, +}; + +static const char * const axg_tdmin_sel_texts[] = { + "IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5", +}; + +/* Change to special mux control to reset dapm */ +static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL, + TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts); + +static const struct snd_kcontrol_new axg_tdmin_in_mux = + SOC_DAPM_ENUM("Input Source", axg_tdmin_sel_enum); + +static struct snd_soc_dai * +axg_tdmin_get_be(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p = NULL; + struct snd_soc_dai *be; + + snd_soc_dapm_widget_for_each_source_path(w, p) { + if (!p->connect) + continue; + + if (p->source->id == snd_soc_dapm_dai_out) + return (struct snd_soc_dai *)p->source->priv; + + be = axg_tdmin_get_be(p->source); + if (be) + return be; + } + + return NULL; +} + +static struct axg_tdm_stream * +axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dai *be = axg_tdmin_get_be(w); + + if (!be) + return NULL; + + return be->capture_dma_data; +} + +static void axg_tdmin_enable(struct regmap *map) +{ + /* Apply both reset */ + regmap_update_bits(map, TDMIN_CTRL, + TDMIN_CTRL_RST_OUT | TDMIN_CTRL_RST_IN, 0); + + /* Clear out reset before in reset */ + regmap_update_bits(map, TDMIN_CTRL, + TDMIN_CTRL_RST_OUT, TDMIN_CTRL_RST_OUT); + regmap_update_bits(map, TDMIN_CTRL, + TDMIN_CTRL_RST_IN, TDMIN_CTRL_RST_IN); + + /* Actually enable tdmin */ + regmap_update_bits(map, TDMIN_CTRL, + TDMIN_CTRL_ENABLE, TDMIN_CTRL_ENABLE); +} + +static void axg_tdmin_disable(struct regmap *map) +{ + regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0); +} + +static int axg_tdmin_prepare(struct regmap *map, struct axg_tdm_stream *ts) +{ + unsigned int val = 0; + + /* Set stream skew */ + switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_DSP_A: + val |= TDMIN_CTRL_IN_BIT_SKEW(3); + break; + + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_DSP_B: + val = TDMIN_CTRL_IN_BIT_SKEW(2); + break; + + default: + pr_err("Unsupported format: %u\n", + ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + /* Set stream format mode */ + switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + val |= TDMIN_CTRL_I2S_MODE; + break; + } + + /* If the sample clock is inverted, invert it back for the formatter */ + if (axg_tdm_lrclk_invert(ts->iface->fmt)) + val |= TDMIN_CTRL_WS_INV; + + /* Set the slot width */ + val |= TDMIN_CTRL_BITNUM(ts->iface->slot_width - 1); + + /* + * The following also reset LSB_FIRST which result in the formatter + * placing the first bit received at bit 31 + */ + regmap_update_bits(map, TDMIN_CTRL, + (TDMIN_CTRL_IN_BIT_SKEW_MASK | TDMIN_CTRL_WS_INV | + TDMIN_CTRL_I2S_MODE | TDMIN_CTRL_LSB_FIRST | + TDMIN_CTRL_BITNUM_MASK), val); + + /* Set static swap mask configuration */ + regmap_write(map, TDMIN_SWAP, 0x76543210); + + return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0); +} + +static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmin_in_mux), + SND_SOC_DAPM_PGA_E("DEC", SND_SOC_NOPM, 0, 0, NULL, 0, + axg_tdm_formatter_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), + SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = { + { "SRC SEL", "IN 0", "IN 0" }, + { "SRC SEL", "IN 1", "IN 1" }, + { "SRC SEL", "IN 2", "IN 2" }, + { "SRC SEL", "IN 3", "IN 3" }, + { "SRC SEL", "IN 4", "IN 4" }, + { "SRC SEL", "IN 5", "IN 5" }, + { "DEC", NULL, "SRC SEL" }, + { "OUT", NULL, "DEC" }, +}; + +static const struct snd_soc_component_driver axg_tdmin_component_drv = { + .dapm_widgets = axg_tdmin_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_tdmin_dapm_widgets), + .dapm_routes = axg_tdmin_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_tdmin_dapm_routes), +}; + +static const struct axg_tdm_formatter_ops axg_tdmin_ops = { + .get_stream = axg_tdmin_get_tdm_stream, + .prepare = axg_tdmin_prepare, + .enable = axg_tdmin_enable, + .disable = axg_tdmin_disable, +}; + +static const struct axg_tdm_formatter_driver axg_tdmin_drv = { + .component_drv = &axg_tdmin_component_drv, + .regmap_cfg = &axg_tdmin_regmap_cfg, + .ops = &axg_tdmin_ops, + .invert_sclk = false, +}; + +static const struct of_device_id axg_tdmin_of_match[] = { + { + .compatible = "amlogic,axg-tdmin", + .data = &axg_tdmin_drv, + }, {} +}; +MODULE_DEVICE_TABLE(of, axg_tdmin_of_match); + +static struct platform_driver axg_tdmin_pdrv = { + .probe = axg_tdm_formatter_probe, + .driver = { + .name = "axg-tdmin", + .of_match_table = axg_tdmin_of_match, + }, +}; +module_platform_driver(axg_tdmin_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-tdmout.c b/sound/soc/meson/axg-tdmout.c new file mode 100644 index 000000000..d11acb3cc --- /dev/null +++ b/sound/soc/meson/axg-tdmout.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include +#include + +#include "axg-tdm-formatter.h" + +#define TDMOUT_CTRL0 0x00 +#define TDMOUT_CTRL0_BITNUM_MASK GENMASK(4, 0) +#define TDMOUT_CTRL0_BITNUM(x) ((x) << 0) +#define TDMOUT_CTRL0_SLOTNUM_MASK GENMASK(9, 5) +#define TDMOUT_CTRL0_SLOTNUM(x) ((x) << 5) +#define TDMOUT_CTRL0_INIT_BITNUM_MASK GENMASK(19, 15) +#define TDMOUT_CTRL0_INIT_BITNUM(x) ((x) << 15) +#define TDMOUT_CTRL0_ENABLE BIT(31) +#define TDMOUT_CTRL0_RST_OUT BIT(29) +#define TDMOUT_CTRL0_RST_IN BIT(28) +#define TDMOUT_CTRL1 0x04 +#define TDMOUT_CTRL1_TYPE_MASK GENMASK(6, 4) +#define TDMOUT_CTRL1_TYPE(x) ((x) << 4) +#define TDMOUT_CTRL1_MSB_POS_MASK GENMASK(12, 8) +#define TDMOUT_CTRL1_MSB_POS(x) ((x) << 8) +#define TDMOUT_CTRL1_SEL_SHIFT 24 +#define TDMOUT_CTRL1_GAIN_EN 26 +#define TDMOUT_CTRL1_WS_INV BIT(28) +#define TDMOUT_SWAP 0x08 +#define TDMOUT_MASK0 0x0c +#define TDMOUT_MASK1 0x10 +#define TDMOUT_MASK2 0x14 +#define TDMOUT_MASK3 0x18 +#define TDMOUT_STAT 0x1c +#define TDMOUT_GAIN0 0x20 +#define TDMOUT_GAIN1 0x24 +#define TDMOUT_MUTE_VAL 0x28 +#define TDMOUT_MUTE0 0x2c +#define TDMOUT_MUTE1 0x30 +#define TDMOUT_MUTE2 0x34 +#define TDMOUT_MUTE3 0x38 +#define TDMOUT_MASK_VAL 0x3c + +static const struct regmap_config axg_tdmout_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TDMOUT_MASK_VAL, +}; + +static const struct snd_kcontrol_new axg_tdmout_controls[] = { + SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0, 0, 8, 255, 0), + SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0), + SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1, 0, 8, 255, 0), + SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0), + SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1, + TDMOUT_CTRL1_GAIN_EN, 1, 0), +}; + +static const char * const tdmout_sel_texts[] = { + "IN 0", "IN 1", "IN 2", +}; + +static SOC_ENUM_SINGLE_DECL(axg_tdmout_sel_enum, TDMOUT_CTRL1, + TDMOUT_CTRL1_SEL_SHIFT, tdmout_sel_texts); + +static const struct snd_kcontrol_new axg_tdmout_in_mux = + SOC_DAPM_ENUM("Input Source", axg_tdmout_sel_enum); + +static struct snd_soc_dai * +axg_tdmout_get_be(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p = NULL; + struct snd_soc_dai *be; + + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (!p->connect) + continue; + + if (p->sink->id == snd_soc_dapm_dai_in) + return (struct snd_soc_dai *)p->sink->priv; + + be = axg_tdmout_get_be(p->sink); + if (be) + return be; + } + + return NULL; +} + +static struct axg_tdm_stream * +axg_tdmout_get_tdm_stream(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dai *be = axg_tdmout_get_be(w); + + if (!be) + return NULL; + + return be->playback_dma_data; +} + +static void axg_tdmout_enable(struct regmap *map) +{ + /* Apply both reset */ + regmap_update_bits(map, TDMOUT_CTRL0, + TDMOUT_CTRL0_RST_OUT | TDMOUT_CTRL0_RST_IN, 0); + + /* Clear out reset before in reset */ + regmap_update_bits(map, TDMOUT_CTRL0, + TDMOUT_CTRL0_RST_OUT, TDMOUT_CTRL0_RST_OUT); + regmap_update_bits(map, TDMOUT_CTRL0, + TDMOUT_CTRL0_RST_IN, TDMOUT_CTRL0_RST_IN); + + /* Actually enable tdmout */ + regmap_update_bits(map, TDMOUT_CTRL0, + TDMOUT_CTRL0_ENABLE, TDMOUT_CTRL0_ENABLE); +} + +static void axg_tdmout_disable(struct regmap *map) +{ + regmap_update_bits(map, TDMOUT_CTRL0, TDMOUT_CTRL0_ENABLE, 0); +} + +static int axg_tdmout_prepare(struct regmap *map, struct axg_tdm_stream *ts) +{ + unsigned int val = 0; + + /* Set the stream skew */ + switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_DSP_A: + val |= TDMOUT_CTRL0_INIT_BITNUM(1); + break; + + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_DSP_B: + val |= TDMOUT_CTRL0_INIT_BITNUM(2); + break; + + default: + pr_err("Unsupported format: %u\n", + ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + /* Set the slot width */ + val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1); + + /* Set the slot number */ + val |= TDMOUT_CTRL0_SLOTNUM(ts->iface->slots - 1); + + regmap_update_bits(map, TDMOUT_CTRL0, + TDMOUT_CTRL0_INIT_BITNUM_MASK | + TDMOUT_CTRL0_BITNUM_MASK | + TDMOUT_CTRL0_SLOTNUM_MASK, val); + + /* Set the sample width */ + val = TDMOUT_CTRL1_MSB_POS(ts->width - 1); + + /* FIFO data are arranged in chunks of 64bits */ + switch (ts->physical_width) { + case 8: + /* 8 samples of 8 bits */ + val |= TDMOUT_CTRL1_TYPE(0); + break; + case 16: + /* 4 samples of 16 bits - right justified */ + val |= TDMOUT_CTRL1_TYPE(2); + break; + case 32: + /* 2 samples of 32 bits - right justified */ + val |= TDMOUT_CTRL1_TYPE(4); + break; + default: + pr_err("Unsupported physical width: %u\n", + ts->physical_width); + return -EINVAL; + } + + /* If the sample clock is inverted, invert it back for the formatter */ + if (axg_tdm_lrclk_invert(ts->iface->fmt)) + val |= TDMOUT_CTRL1_WS_INV; + + regmap_update_bits(map, TDMOUT_CTRL1, + (TDMOUT_CTRL1_TYPE_MASK | TDMOUT_CTRL1_MSB_POS_MASK | + TDMOUT_CTRL1_WS_INV), val); + + /* Set static swap mask configuration */ + regmap_write(map, TDMOUT_SWAP, 0x76543210); + + return axg_tdm_formatter_set_channel_masks(map, ts, TDMOUT_MASK0); +} + +static const struct snd_soc_dapm_widget axg_tdmout_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmout_in_mux), + SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0, + axg_tdm_formatter_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), + SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route axg_tdmout_dapm_routes[] = { + { "SRC SEL", "IN 0", "IN 0" }, + { "SRC SEL", "IN 1", "IN 1" }, + { "SRC SEL", "IN 2", "IN 2" }, + { "ENC", NULL, "SRC SEL" }, + { "OUT", NULL, "ENC" }, +}; + +static const struct snd_soc_component_driver axg_tdmout_component_drv = { + .controls = axg_tdmout_controls, + .num_controls = ARRAY_SIZE(axg_tdmout_controls), + .dapm_widgets = axg_tdmout_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_tdmout_dapm_widgets), + .dapm_routes = axg_tdmout_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_tdmout_dapm_routes), +}; + +static const struct axg_tdm_formatter_ops axg_tdmout_ops = { + .get_stream = axg_tdmout_get_tdm_stream, + .prepare = axg_tdmout_prepare, + .enable = axg_tdmout_enable, + .disable = axg_tdmout_disable, +}; + +static const struct axg_tdm_formatter_driver axg_tdmout_drv = { + .component_drv = &axg_tdmout_component_drv, + .regmap_cfg = &axg_tdmout_regmap_cfg, + .ops = &axg_tdmout_ops, + .invert_sclk = true, +}; + +static const struct of_device_id axg_tdmout_of_match[] = { + { + .compatible = "amlogic,axg-tdmout", + .data = &axg_tdmout_drv, + }, {} +}; +MODULE_DEVICE_TABLE(of, axg_tdmout_of_match); + +static struct platform_driver axg_tdmout_pdrv = { + .probe = axg_tdm_formatter_probe, + .driver = { + .name = "axg-tdmout", + .of_match_table = axg_tdmout_of_match, + }, +}; +module_platform_driver(axg_tdmout_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG TDM output formatter driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-toddr.c b/sound/soc/meson/axg-toddr.c new file mode 100644 index 000000000..c2c9bb312 --- /dev/null +++ b/sound/soc/meson/axg-toddr.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +/* This driver implements the frontend capture DAI of AXG based SoCs */ + +#include +#include +#include +#include +#include +#include +#include + +#include "axg-fifo.h" + +#define CTRL0_TODDR_SEL_RESAMPLE BIT(30) +#define CTRL0_TODDR_EXT_SIGNED BIT(29) +#define CTRL0_TODDR_PP_MODE BIT(28) +#define CTRL0_TODDR_TYPE_MASK GENMASK(15, 13) +#define CTRL0_TODDR_TYPE(x) ((x) << 13) +#define CTRL0_TODDR_MSB_POS_MASK GENMASK(12, 8) +#define CTRL0_TODDR_MSB_POS(x) ((x) << 8) +#define CTRL0_TODDR_LSB_POS_MASK GENMASK(7, 3) +#define CTRL0_TODDR_LSB_POS(x) ((x) << 3) + +static int axg_toddr_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) +{ + return axg_fifo_pcm_new(rtd, SNDRV_PCM_STREAM_CAPTURE); +} + +static int axg_toddr_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); + unsigned int type, width, msb = 31; + + /* + * NOTE: + * Almost all backend will place the MSB at bit 31, except SPDIF Input + * which will put it at index 28. When adding support for the SPDIF + * Input, we'll need to find which type of backend we are connected to. + */ + + switch (params_physical_width(params)) { + case 8: + type = 0; /* 8 samples of 8 bits */ + break; + case 16: + type = 2; /* 4 samples of 16 bits - right justified */ + break; + case 32: + type = 4; /* 2 samples of 32 bits - right justified */ + break; + default: + return -EINVAL; + } + + width = params_width(params); + + regmap_update_bits(fifo->map, FIFO_CTRL0, + CTRL0_TODDR_TYPE_MASK | + CTRL0_TODDR_MSB_POS_MASK | + CTRL0_TODDR_LSB_POS_MASK, + CTRL0_TODDR_TYPE(type) | + CTRL0_TODDR_MSB_POS(msb) | + CTRL0_TODDR_LSB_POS(msb - (width - 1))); + + return 0; +} + +static int axg_toddr_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); + unsigned int fifo_threshold; + int ret; + + /* Enable pclk to access registers and clock the fifo ip */ + ret = clk_prepare_enable(fifo->pclk); + if (ret) + return ret; + + /* Select orginal data - resampling not supported ATM */ + regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_SEL_RESAMPLE, 0); + + /* Only signed format are supported ATM */ + regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_EXT_SIGNED, + CTRL0_TODDR_EXT_SIGNED); + + /* Apply single buffer mode to the interface */ + regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_TODDR_PP_MODE, 0); + + /* TODDR does not have a configurable fifo depth */ + fifo_threshold = AXG_FIFO_MIN_CNT - 1; + regmap_update_bits(fifo->map, FIFO_CTRL1, CTRL1_THRESHOLD_MASK, + CTRL1_THRESHOLD(fifo_threshold)); + + return 0; +} + +static void axg_toddr_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_fifo *fifo = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(fifo->pclk); +} + +static const struct snd_soc_dai_ops axg_toddr_ops = { + .hw_params = axg_toddr_dai_hw_params, + .startup = axg_toddr_dai_startup, + .shutdown = axg_toddr_dai_shutdown, +}; + +static struct snd_soc_dai_driver axg_toddr_dai_drv = { + .name = "TODDR", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AXG_FIFO_CH_MAX, + .rates = AXG_FIFO_RATES, + .formats = AXG_FIFO_FORMATS, + }, + .ops = &axg_toddr_ops, + .pcm_new = axg_toddr_pcm_new, +}; + +static const char * const axg_toddr_sel_texts[] = { + "IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 6" +}; + +static const unsigned int axg_toddr_sel_values[] = { + 0, 1, 2, 3, 4, 6 +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(axg_toddr_sel_enum, FIFO_CTRL0, + CTRL0_SEL_SHIFT, CTRL0_SEL_MASK, + axg_toddr_sel_texts, axg_toddr_sel_values); + +static const struct snd_kcontrol_new axg_toddr_in_mux = + SOC_DAPM_ENUM("Input Source", axg_toddr_sel_enum); + +static const struct snd_soc_dapm_widget axg_toddr_dapm_widgets[] = { + SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_toddr_in_mux), + SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route axg_toddr_dapm_routes[] = { + { "Capture", NULL, "SRC SEL" }, + { "SRC SEL", "IN 0", "IN 0" }, + { "SRC SEL", "IN 1", "IN 1" }, + { "SRC SEL", "IN 2", "IN 2" }, + { "SRC SEL", "IN 3", "IN 3" }, + { "SRC SEL", "IN 4", "IN 4" }, + { "SRC SEL", "IN 6", "IN 6" }, +}; + +static const struct snd_soc_component_driver axg_toddr_component_drv = { + .dapm_widgets = axg_toddr_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_toddr_dapm_widgets), + .dapm_routes = axg_toddr_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_toddr_dapm_routes), + .ops = &axg_fifo_pcm_ops +}; + +static const struct axg_fifo_match_data axg_toddr_match_data = { + .component_drv = &axg_toddr_component_drv, + .dai_drv = &axg_toddr_dai_drv +}; + +static const struct of_device_id axg_toddr_of_match[] = { + { + .compatible = "amlogic,axg-toddr", + .data = &axg_toddr_match_data, + }, {} +}; +MODULE_DEVICE_TABLE(of, axg_toddr_of_match); + +static struct platform_driver axg_toddr_pdrv = { + .probe = axg_fifo_probe, + .driver = { + .name = "axg-toddr", + .of_match_table = axg_toddr_of_match, + }, +}; +module_platform_driver(axg_toddr_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG capture fifo driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mxs/Kconfig b/sound/soc/mxs/Kconfig new file mode 100644 index 000000000..219235c02 --- /dev/null +++ b/sound/soc/mxs/Kconfig @@ -0,0 +1,21 @@ +menuconfig SND_MXS_SOC + tristate "SoC Audio for Freescale MXS CPUs" + depends on ARCH_MXS || COMPILE_TEST + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to + the MXS SAIF interface. + + +if SND_MXS_SOC + +config SND_SOC_MXS_SGTL5000 + tristate "SoC Audio support for MXS boards with sgtl5000" + depends on I2C + select SND_SOC_SGTL5000 + help + Say Y if you want to add support for SoC audio on an MXS board with + a sgtl5000 codec. + +endif # SND_MXS_SOC diff --git a/sound/soc/mxs/Makefile b/sound/soc/mxs/Makefile new file mode 100644 index 000000000..ab0a9a553 --- /dev/null +++ b/sound/soc/mxs/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# MXS Platform Support +snd-soc-mxs-objs := mxs-saif.o +snd-soc-mxs-pcm-objs := mxs-pcm.o + +obj-$(CONFIG_SND_MXS_SOC) += snd-soc-mxs.o snd-soc-mxs-pcm.o + +# i.MX Machine Support +snd-soc-mxs-sgtl5000-objs := mxs-sgtl5000.o + +obj-$(CONFIG_SND_SOC_MXS_SGTL5000) += snd-soc-mxs-sgtl5000.o diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c new file mode 100644 index 000000000..a371b4f91 --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Based on sound/soc/imx/imx-pcm-dma-mx2.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "mxs-pcm.h" + +static const struct snd_pcm_hardware snd_mxs_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_HALF_DUPLEX, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 52, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32, +}; + +static const struct snd_dmaengine_pcm_config mxs_dmaengine_pcm_config = { + .pcm_hardware = &snd_mxs_hardware, + .prealloc_buffer_size = 64 * 1024, +}; + +int mxs_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, &mxs_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX); +} +EXPORT_SYMBOL_GPL(mxs_pcm_platform_register); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h new file mode 100644 index 000000000..035ea0436 --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _MXS_PCM_H +#define _MXS_PCM_H + +int mxs_pcm_platform_register(struct device *dev); + +#endif diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c new file mode 100644 index 000000000..6d0ab4e75 --- /dev/null +++ b/sound/soc/mxs/mxs-saif.c @@ -0,0 +1,872 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mxs-saif.h" + +#define MXS_SET_ADDR 0x4 +#define MXS_CLR_ADDR 0x8 + +static struct mxs_saif *mxs_saif[2]; + +/* + * SAIF is a little different with other normal SOC DAIs on clock using. + * + * For MXS, two SAIF modules are instantiated on-chip. + * Each SAIF has a set of clock pins and can be operating in master + * mode simultaneously if they are connected to different off-chip codecs. + * Also, one of the two SAIFs can master or drive the clock pins while the + * other SAIF, in slave mode, receives clocking from the master SAIF. + * This also means that both SAIFs must operate at the same sample rate. + * + * We abstract this as each saif has a master, the master could be + * itself or other saifs. In the generic saif driver, saif does not need + * to know the different clkmux. Saif only needs to know who is its master + * and operating its master to generate the proper clock rate for it. + * The master id is provided in mach-specific layer according to different + * clkmux setting. + */ + +static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + switch (clk_id) { + case MXS_SAIF_MCLK: + saif->mclk = freq; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * Since SAIF may work on EXTMASTER mode, IOW, it's working BITCLK&LRCLK + * is provided by other SAIF, we provide a interface here to get its master + * from its master_id. + * Note that the master could be itself. + */ +static inline struct mxs_saif *mxs_saif_get_master(struct mxs_saif * saif) +{ + return mxs_saif[saif->master_id]; +} + +/* + * Set SAIF clock and MCLK + */ +static int mxs_saif_set_clk(struct mxs_saif *saif, + unsigned int mclk, + unsigned int rate) +{ + u32 scr; + int ret; + struct mxs_saif *master_saif; + + dev_dbg(saif->dev, "mclk %d rate %d\n", mclk, rate); + + /* Set master saif to generate proper clock */ + master_saif = mxs_saif_get_master(saif); + if (!master_saif) + return -EINVAL; + + dev_dbg(saif->dev, "master saif%d\n", master_saif->id); + + /* Checking if can playback and capture simutaneously */ + if (master_saif->ongoing && rate != master_saif->cur_rate) { + dev_err(saif->dev, + "can not change clock, master saif%d(rate %d) is ongoing\n", + master_saif->id, master_saif->cur_rate); + return -EINVAL; + } + + scr = __raw_readl(master_saif->base + SAIF_CTRL); + scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE; + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + + /* + * Set SAIF clock + * + * The SAIF clock should be either 384*fs or 512*fs. + * If MCLK is used, the SAIF clk ratio needs to match mclk ratio. + * For 256x, 128x, 64x, and 32x sub-rates, set saif clk as 512*fs. + * For 192x, 96x, and 48x sub-rates, set saif clk as 384*fs. + * + * If MCLK is not used, we just set saif clk to 512*fs. + */ + ret = clk_prepare_enable(master_saif->clk); + if (ret) + return ret; + + if (master_saif->mclk_in_use) { + switch (mclk / rate) { + case 32: + case 64: + case 128: + case 256: + case 512: + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + ret = clk_set_rate(master_saif->clk, 512 * rate); + break; + case 48: + case 96: + case 192: + case 384: + scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; + ret = clk_set_rate(master_saif->clk, 384 * rate); + break; + default: + /* SAIF MCLK should be a sub-rate of 512x or 384x */ + clk_disable_unprepare(master_saif->clk); + return -EINVAL; + } + } else { + ret = clk_set_rate(master_saif->clk, 512 * rate); + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + } + + clk_disable_unprepare(master_saif->clk); + + if (ret) + return ret; + + master_saif->cur_rate = rate; + + if (!master_saif->mclk_in_use) { + __raw_writel(scr, master_saif->base + SAIF_CTRL); + return 0; + } + + /* + * Program the over-sample rate for MCLK output + * + * The available MCLK range is 32x, 48x... 512x. The rate + * could be from 8kHz to 192kH. + */ + switch (mclk / rate) { + case 32: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(4); + break; + case 64: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); + break; + case 128: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); + break; + case 256: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); + break; + case 512: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); + break; + case 48: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); + break; + case 96: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); + break; + case 192: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); + break; + case 384: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); + break; + default: + return -EINVAL; + } + + __raw_writel(scr, master_saif->base + SAIF_CTRL); + + return 0; +} + +/* + * Put and disable MCLK. + */ +int mxs_saif_put_mclk(unsigned int saif_id) +{ + struct mxs_saif *saif = mxs_saif[saif_id]; + u32 stat; + + if (!saif) + return -EINVAL; + + stat = __raw_readl(saif->base + SAIF_STAT); + if (stat & BM_SAIF_STAT_BUSY) { + dev_err(saif->dev, "error: busy\n"); + return -EBUSY; + } + + clk_disable_unprepare(saif->clk); + + /* disable MCLK output */ + __raw_writel(BM_SAIF_CTRL_CLKGATE, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + saif->mclk_in_use = 0; + return 0; +} +EXPORT_SYMBOL_GPL(mxs_saif_put_mclk); + +/* + * Get MCLK and set clock rate, then enable it + * + * This interface is used for codecs who are using MCLK provided + * by saif. + */ +int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, + unsigned int rate) +{ + struct mxs_saif *saif = mxs_saif[saif_id]; + u32 stat; + int ret; + struct mxs_saif *master_saif; + + if (!saif) + return -EINVAL; + + /* Clear Reset */ + __raw_writel(BM_SAIF_CTRL_SFTRST, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + /* FIXME: need clear clk gate for register r/w */ + __raw_writel(BM_SAIF_CTRL_CLKGATE, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + master_saif = mxs_saif_get_master(saif); + if (saif != master_saif) { + dev_err(saif->dev, "can not get mclk from a non-master saif\n"); + return -EINVAL; + } + + stat = __raw_readl(saif->base + SAIF_STAT); + if (stat & BM_SAIF_STAT_BUSY) { + dev_err(saif->dev, "error: busy\n"); + return -EBUSY; + } + + saif->mclk_in_use = 1; + ret = mxs_saif_set_clk(saif, mclk, rate); + if (ret) + return ret; + + ret = clk_prepare_enable(saif->clk); + if (ret) + return ret; + + /* enable MCLK output */ + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + + return 0; +} +EXPORT_SYMBOL_GPL(mxs_saif_get_mclk); + +/* + * SAIF DAI format configuration. + * Should only be called when port is inactive. + */ +static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + u32 scr, stat; + u32 scr0; + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + stat = __raw_readl(saif->base + SAIF_STAT); + if (stat & BM_SAIF_STAT_BUSY) { + dev_err(cpu_dai->dev, "error: busy\n"); + return -EBUSY; + } + + /* If SAIF1 is configured as slave, the clk gate needs to be cleared + * before the register can be written. + */ + if (saif->id != saif->master_id) { + __raw_writel(BM_SAIF_CTRL_SFTRST, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + __raw_writel(BM_SAIF_CTRL_CLKGATE, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + } + + scr0 = __raw_readl(saif->base + SAIF_CTRL); + scr0 = scr0 & ~BM_SAIF_CTRL_BITCLK_EDGE & ~BM_SAIF_CTRL_LRCLK_POLARITY \ + & ~BM_SAIF_CTRL_JUSTIFY & ~BM_SAIF_CTRL_DELAY; + scr = 0; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data frame low 1clk before data */ + scr |= BM_SAIF_CTRL_DELAY; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data frame high with data */ + scr &= ~BM_SAIF_CTRL_DELAY; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + scr &= ~BM_SAIF_CTRL_JUSTIFY; + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + scr |= BM_SAIF_CTRL_BITCLK_EDGE; + scr |= BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_IB_NF: + scr |= BM_SAIF_CTRL_BITCLK_EDGE; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_NB_IF: + scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; + scr |= BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_NB_NF: + scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + } + + /* + * Note: We simply just support master mode since SAIF TX can only + * work as master. + * Here the master is relative to codec side. + * Saif internally could be slave when working on EXTMASTER mode. + * We just hide this to machine driver. + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + if (saif->id == saif->master_id) + scr &= ~BM_SAIF_CTRL_SLAVE_MODE; + else + scr |= BM_SAIF_CTRL_SLAVE_MODE; + + __raw_writel(scr | scr0, saif->base + SAIF_CTRL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mxs_saif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + /* clear error status to 0 for each re-open */ + saif->fifo_underrun = 0; + saif->fifo_overrun = 0; + + /* Clear Reset for normal operations */ + __raw_writel(BM_SAIF_CTRL_SFTRST, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + /* clear clock gate */ + __raw_writel(BM_SAIF_CTRL_CLKGATE, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + ret = clk_prepare(saif->clk); + if (ret) + return ret; + + return 0; +} + +static void mxs_saif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + clk_unprepare(saif->clk); +} + +/* + * Should only be called when port is inactive. + * although can be called multiple times by upper layers. + */ +static int mxs_saif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + struct mxs_saif *master_saif; + u32 scr, stat; + int ret; + + master_saif = mxs_saif_get_master(saif); + if (!master_saif) + return -EINVAL; + + /* mclk should already be set */ + if (!saif->mclk && saif->mclk_in_use) { + dev_err(cpu_dai->dev, "set mclk first\n"); + return -EINVAL; + } + + stat = __raw_readl(saif->base + SAIF_STAT); + if (!saif->mclk_in_use && (stat & BM_SAIF_STAT_BUSY)) { + dev_err(cpu_dai->dev, "error: busy\n"); + return -EBUSY; + } + + /* + * Set saif clk based on sample rate. + * If mclk is used, we also set mclk, if not, saif->mclk is + * default 0, means not used. + */ + ret = mxs_saif_set_clk(saif, saif->mclk, params_rate(params)); + if (ret) { + dev_err(cpu_dai->dev, "unable to get proper clk\n"); + return ret; + } + + if (saif != master_saif) { + /* + * Set an initial clock rate for the saif internal logic to work + * properly. This is important when working in EXTMASTER mode + * that uses the other saif's BITCLK&LRCLK but it still needs a + * basic clock which should be fast enough for the internal + * logic. + */ + ret = clk_enable(saif->clk); + if (ret) + return ret; + + ret = clk_set_rate(saif->clk, 24000000); + clk_disable(saif->clk); + if (ret) + return ret; + + ret = clk_prepare(master_saif->clk); + if (ret) + return ret; + } + + scr = __raw_readl(saif->base + SAIF_CTRL); + + scr &= ~BM_SAIF_CTRL_WORD_LENGTH; + scr &= ~BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(0); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(4); + scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + break; + case SNDRV_PCM_FORMAT_S24_LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(8); + scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + break; + default: + return -EINVAL; + } + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* enable TX mode */ + scr &= ~BM_SAIF_CTRL_READ_MODE; + } else { + /* enable RX mode */ + scr |= BM_SAIF_CTRL_READ_MODE; + } + + __raw_writel(scr, saif->base + SAIF_CTRL); + return 0; +} + +static int mxs_saif_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + /* enable FIFO error irqs */ + __raw_writel(BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + + return 0; +} + +static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + struct mxs_saif *master_saif; + u32 delay; + int ret; + + master_saif = mxs_saif_get_master(saif); + if (!master_saif) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (saif->state == MXS_SAIF_STATE_RUNNING) + return 0; + + dev_dbg(cpu_dai->dev, "start\n"); + + ret = clk_enable(master_saif->clk); + if (ret) { + dev_err(saif->dev, "Failed to enable master clock\n"); + return ret; + } + + /* + * If the saif's master is not itself, we also need to enable + * itself clk for its internal basic logic to work. + */ + if (saif != master_saif) { + ret = clk_enable(saif->clk); + if (ret) { + dev_err(saif->dev, "Failed to enable master clock\n"); + clk_disable(master_saif->clk); + return ret; + } + + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + } + + if (!master_saif->mclk_in_use) + __raw_writel(BM_SAIF_CTRL_RUN, + master_saif->base + SAIF_CTRL + MXS_SET_ADDR); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * write data to saif data register to trigger + * the transfer. + * For 24-bit format the 32-bit FIFO register stores + * only one channel, so we need to write twice. + * This is also safe for the other non 24-bit formats. + */ + __raw_writel(0, saif->base + SAIF_DATA); + __raw_writel(0, saif->base + SAIF_DATA); + } else { + /* + * read data from saif data register to trigger + * the receive. + * For 24-bit format the 32-bit FIFO register stores + * only one channel, so we need to read twice. + * This is also safe for the other non 24-bit formats. + */ + __raw_readl(saif->base + SAIF_DATA); + __raw_readl(saif->base + SAIF_DATA); + } + + master_saif->ongoing = 1; + saif->state = MXS_SAIF_STATE_RUNNING; + + dev_dbg(saif->dev, "CTRL 0x%x STAT 0x%x\n", + __raw_readl(saif->base + SAIF_CTRL), + __raw_readl(saif->base + SAIF_STAT)); + + dev_dbg(master_saif->dev, "CTRL 0x%x STAT 0x%x\n", + __raw_readl(master_saif->base + SAIF_CTRL), + __raw_readl(master_saif->base + SAIF_STAT)); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (saif->state == MXS_SAIF_STATE_STOPPED) + return 0; + + dev_dbg(cpu_dai->dev, "stop\n"); + + /* wait a while for the current sample to complete */ + delay = USEC_PER_SEC / master_saif->cur_rate; + + if (!master_saif->mclk_in_use) { + __raw_writel(BM_SAIF_CTRL_RUN, + master_saif->base + SAIF_CTRL + MXS_CLR_ADDR); + udelay(delay); + } + clk_disable(master_saif->clk); + + if (saif != master_saif) { + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + udelay(delay); + clk_disable(saif->clk); + } + + master_saif->ongoing = 0; + saif->state = MXS_SAIF_STATE_STOPPED; + + break; + default: + return -EINVAL; + } + + return 0; +} + +#define MXS_SAIF_RATES SNDRV_PCM_RATE_8000_192000 +#define MXS_SAIF_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops mxs_saif_dai_ops = { + .startup = mxs_saif_startup, + .shutdown = mxs_saif_shutdown, + .trigger = mxs_saif_trigger, + .prepare = mxs_saif_prepare, + .hw_params = mxs_saif_hw_params, + .set_sysclk = mxs_saif_set_dai_sysclk, + .set_fmt = mxs_saif_set_dai_fmt, +}; + +static int mxs_saif_dai_probe(struct snd_soc_dai *dai) +{ + struct mxs_saif *saif = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, saif); + + return 0; +} + +static struct snd_soc_dai_driver mxs_saif_dai = { + .name = "mxs-saif", + .probe = mxs_saif_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SAIF_RATES, + .formats = MXS_SAIF_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SAIF_RATES, + .formats = MXS_SAIF_FORMATS, + }, + .ops = &mxs_saif_dai_ops, +}; + +static const struct snd_soc_component_driver mxs_saif_component = { + .name = "mxs-saif", +}; + +static irqreturn_t mxs_saif_irq(int irq, void *dev_id) +{ + struct mxs_saif *saif = dev_id; + unsigned int stat; + + stat = __raw_readl(saif->base + SAIF_STAT); + if (!(stat & (BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | + BM_SAIF_STAT_FIFO_OVERFLOW_IRQ))) + return IRQ_NONE; + + if (stat & BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ) { + dev_dbg(saif->dev, "underrun!!! %d\n", ++saif->fifo_underrun); + __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ, + saif->base + SAIF_STAT + MXS_CLR_ADDR); + } + + if (stat & BM_SAIF_STAT_FIFO_OVERFLOW_IRQ) { + dev_dbg(saif->dev, "overrun!!! %d\n", ++saif->fifo_overrun); + __raw_writel(BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, + saif->base + SAIF_STAT + MXS_CLR_ADDR); + } + + dev_dbg(saif->dev, "SAIF_CTRL %x SAIF_STAT %x\n", + __raw_readl(saif->base + SAIF_CTRL), + __raw_readl(saif->base + SAIF_STAT)); + + return IRQ_HANDLED; +} + +static int mxs_saif_mclk_init(struct platform_device *pdev) +{ + struct mxs_saif *saif = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + struct clk *clk; + int ret; + + clk = clk_register_divider(&pdev->dev, "mxs_saif_mclk", + __clk_get_name(saif->clk), 0, + saif->base + SAIF_CTRL, + BP_SAIF_CTRL_BITCLK_MULT_RATE, 3, + 0, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + if (ret == -EEXIST) + return 0; + dev_err(&pdev->dev, "failed to register mclk: %d\n", ret); + return PTR_ERR(clk); + } + + ret = of_clk_add_provider(np, of_clk_src_simple_get, clk); + if (ret) + return ret; + + return 0; +} + +static int mxs_saif_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *iores; + struct mxs_saif *saif; + int irq, ret = 0; + struct device_node *master; + + if (!np) + return -EINVAL; + + saif = devm_kzalloc(&pdev->dev, sizeof(*saif), GFP_KERNEL); + if (!saif) + return -ENOMEM; + + ret = of_alias_get_id(np, "saif"); + if (ret < 0) + return ret; + else + saif->id = ret; + + if (saif->id >= ARRAY_SIZE(mxs_saif)) { + dev_err(&pdev->dev, "get wrong saif id\n"); + return -EINVAL; + } + + /* + * If there is no "fsl,saif-master" phandle, it's a saif + * master. Otherwise, it's a slave and its phandle points + * to the master. + */ + master = of_parse_phandle(np, "fsl,saif-master", 0); + if (!master) { + saif->master_id = saif->id; + } else { + ret = of_alias_get_id(master, "saif"); + of_node_put(master); + if (ret < 0) + return ret; + else + saif->master_id = ret; + + if (saif->master_id >= ARRAY_SIZE(mxs_saif)) { + dev_err(&pdev->dev, "get wrong master id\n"); + return -EINVAL; + } + } + + mxs_saif[saif->id] = saif; + + saif->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(saif->clk)) { + ret = PTR_ERR(saif->clk); + dev_err(&pdev->dev, "Cannot get the clock: %d\n", + ret); + return ret; + } + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + saif->base = devm_ioremap_resource(&pdev->dev, iores); + if (IS_ERR(saif->base)) + return PTR_ERR(saif->base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + dev_err(&pdev->dev, "failed to get irq resource: %d\n", + ret); + return ret; + } + + saif->dev = &pdev->dev; + ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, + dev_name(&pdev->dev), saif); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + + platform_set_drvdata(pdev, saif); + + /* We only support saif0 being tx and clock master */ + if (saif->id == 0) { + ret = mxs_saif_mclk_init(pdev); + if (ret) + dev_warn(&pdev->dev, "failed to init clocks\n"); + } + + ret = devm_snd_soc_register_component(&pdev->dev, &mxs_saif_component, + &mxs_saif_dai, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + return ret; + } + + ret = mxs_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "register PCM failed: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id mxs_saif_dt_ids[] = { + { .compatible = "fsl,imx28-saif", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_saif_dt_ids); + +static struct platform_driver mxs_saif_driver = { + .probe = mxs_saif_probe, + + .driver = { + .name = "mxs-saif", + .of_match_table = mxs_saif_dt_ids, + }, +}; + +module_platform_driver(mxs_saif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS ASoC SAIF driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mxs-saif"); diff --git a/sound/soc/mxs/mxs-saif.h b/sound/soc/mxs/mxs-saif.h new file mode 100644 index 000000000..9a4c0b291 --- /dev/null +++ b/sound/soc/mxs/mxs-saif.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#ifndef _MXS_SAIF_H +#define _MXS_SAIF_H + +#define SAIF_CTRL 0x0 +#define SAIF_STAT 0x10 +#define SAIF_DATA 0x20 +#define SAIF_VERSION 0X30 + +/* SAIF_CTRL */ +#define BM_SAIF_CTRL_SFTRST 0x80000000 +#define BM_SAIF_CTRL_CLKGATE 0x40000000 +#define BP_SAIF_CTRL_BITCLK_MULT_RATE 27 +#define BM_SAIF_CTRL_BITCLK_MULT_RATE 0x38000000 +#define BF_SAIF_CTRL_BITCLK_MULT_RATE(v) \ + (((v) << 27) & BM_SAIF_CTRL_BITCLK_MULT_RATE) +#define BM_SAIF_CTRL_BITCLK_BASE_RATE 0x04000000 +#define BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN 0x02000000 +#define BM_SAIF_CTRL_FIFO_SERVICE_IRQ_EN 0x01000000 +#define BP_SAIF_CTRL_RSRVD2 21 +#define BM_SAIF_CTRL_RSRVD2 0x00E00000 + +#define BP_SAIF_CTRL_DMAWAIT_COUNT 16 +#define BM_SAIF_CTRL_DMAWAIT_COUNT 0x001F0000 +#define BF_SAIF_CTRL_DMAWAIT_COUNT(v) \ + (((v) << 16) & BM_SAIF_CTRL_DMAWAIT_COUNT) +#define BP_SAIF_CTRL_CHANNEL_NUM_SELECT 14 +#define BM_SAIF_CTRL_CHANNEL_NUM_SELECT 0x0000C000 +#define BF_SAIF_CTRL_CHANNEL_NUM_SELECT(v) \ + (((v) << 14) & BM_SAIF_CTRL_CHANNEL_NUM_SELECT) +#define BM_SAIF_CTRL_LRCLK_PULSE 0x00002000 +#define BM_SAIF_CTRL_BIT_ORDER 0x00001000 +#define BM_SAIF_CTRL_DELAY 0x00000800 +#define BM_SAIF_CTRL_JUSTIFY 0x00000400 +#define BM_SAIF_CTRL_LRCLK_POLARITY 0x00000200 +#define BM_SAIF_CTRL_BITCLK_EDGE 0x00000100 +#define BP_SAIF_CTRL_WORD_LENGTH 4 +#define BM_SAIF_CTRL_WORD_LENGTH 0x000000F0 +#define BF_SAIF_CTRL_WORD_LENGTH(v) \ + (((v) << 4) & BM_SAIF_CTRL_WORD_LENGTH) +#define BM_SAIF_CTRL_BITCLK_48XFS_ENABLE 0x00000008 +#define BM_SAIF_CTRL_SLAVE_MODE 0x00000004 +#define BM_SAIF_CTRL_READ_MODE 0x00000002 +#define BM_SAIF_CTRL_RUN 0x00000001 + +/* SAIF_STAT */ +#define BM_SAIF_STAT_PRESENT 0x80000000 +#define BP_SAIF_STAT_RSRVD2 17 +#define BM_SAIF_STAT_RSRVD2 0x7FFE0000 +#define BF_SAIF_STAT_RSRVD2(v) \ + (((v) << 17) & BM_SAIF_STAT_RSRVD2) +#define BM_SAIF_STAT_DMA_PREQ 0x00010000 +#define BP_SAIF_STAT_RSRVD1 7 +#define BM_SAIF_STAT_RSRVD1 0x0000FF80 +#define BF_SAIF_STAT_RSRVD1(v) \ + (((v) << 7) & BM_SAIF_STAT_RSRVD1) + +#define BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ 0x00000040 +#define BM_SAIF_STAT_FIFO_OVERFLOW_IRQ 0x00000020 +#define BM_SAIF_STAT_FIFO_SERVICE_IRQ 0x00000010 +#define BP_SAIF_STAT_RSRVD0 1 +#define BM_SAIF_STAT_RSRVD0 0x0000000E +#define BF_SAIF_STAT_RSRVD0(v) \ + (((v) << 1) & BM_SAIF_STAT_RSRVD0) +#define BM_SAIF_STAT_BUSY 0x00000001 + +/* SAFI_DATA */ +#define BP_SAIF_DATA_PCM_RIGHT 16 +#define BM_SAIF_DATA_PCM_RIGHT 0xFFFF0000 +#define BF_SAIF_DATA_PCM_RIGHT(v) \ + (((v) << 16) & BM_SAIF_DATA_PCM_RIGHT) +#define BP_SAIF_DATA_PCM_LEFT 0 +#define BM_SAIF_DATA_PCM_LEFT 0x0000FFFF +#define BF_SAIF_DATA_PCM_LEFT(v) \ + (((v) << 0) & BM_SAIF_DATA_PCM_LEFT) + +/* SAIF_VERSION */ +#define BP_SAIF_VERSION_MAJOR 24 +#define BM_SAIF_VERSION_MAJOR 0xFF000000 +#define BF_SAIF_VERSION_MAJOR(v) \ + (((v) << 24) & BM_SAIF_VERSION_MAJOR) +#define BP_SAIF_VERSION_MINOR 16 +#define BM_SAIF_VERSION_MINOR 0x00FF0000 +#define BF_SAIF_VERSION_MINOR(v) \ + (((v) << 16) & BM_SAIF_VERSION_MINOR) +#define BP_SAIF_VERSION_STEP 0 +#define BM_SAIF_VERSION_STEP 0x0000FFFF +#define BF_SAIF_VERSION_STEP(v) \ + (((v) << 0) & BM_SAIF_VERSION_STEP) + +#define MXS_SAIF_MCLK 0 + +#include "mxs-pcm.h" + +struct mxs_saif { + struct device *dev; + struct clk *clk; + unsigned int mclk; + unsigned int mclk_in_use; + void __iomem *base; + unsigned int id; + unsigned int master_id; + unsigned int cur_rate; + unsigned int ongoing; + + u32 fifo_underrun; + u32 fifo_overrun; + + enum { + MXS_SAIF_STATE_STOPPED, + MXS_SAIF_STATE_RUNNING, + } state; +}; + +extern int mxs_saif_put_mclk(unsigned int saif_id); +extern int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, + unsigned int rate); +#endif diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c new file mode 100644 index 000000000..c40e0ab49 --- /dev/null +++ b/sound/soc/mxs/mxs-sgtl5000.c @@ -0,0 +1,205 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/sgtl5000.h" +#include "mxs-saif.h" + +static int mxs_sgtl5000_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int rate = params_rate(params); + u32 mclk; + int ret; + + /* sgtl5000 does not support 512*rate when in 96000 fs */ + switch (rate) { + case 96000: + mclk = 256 * rate; + break; + default: + mclk = 512 * rate; + break; + } + + /* Set SGTL5000's SYSCLK (provided by SAIF MCLK) */ + ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, mclk, 0); + if (ret) { + dev_err(codec_dai->dev, "Failed to set sysclk to %u.%03uMHz\n", + mclk / 1000000, mclk / 1000 % 1000); + return ret; + } + + /* The SAIF MCLK should be the same as SGTL5000_SYSCLK */ + ret = snd_soc_dai_set_sysclk(cpu_dai, MXS_SAIF_MCLK, mclk, 0); + if (ret) { + dev_err(cpu_dai->dev, "Failed to set sysclk to %u.%03uMHz\n", + mclk / 1000000, mclk / 1000 % 1000); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops mxs_sgtl5000_hifi_ops = { + .hw_params = mxs_sgtl5000_hw_params, +}; + +#define MXS_SGTL5000_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS) + +static struct snd_soc_dai_link mxs_sgtl5000_dai[] = { + { + .name = "HiFi Tx", + .stream_name = "HiFi Playback", + .codec_dai_name = "sgtl5000", + .dai_fmt = MXS_SGTL5000_DAI_FMT, + .ops = &mxs_sgtl5000_hifi_ops, + .playback_only = true, + }, { + .name = "HiFi Rx", + .stream_name = "HiFi Capture", + .codec_dai_name = "sgtl5000", + .dai_fmt = MXS_SGTL5000_DAI_FMT, + .ops = &mxs_sgtl5000_hifi_ops, + .capture_only = true, + }, +}; + +static const struct snd_soc_dapm_widget mxs_sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Line Out Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static struct snd_soc_card mxs_sgtl5000 = { + .name = "mxs_sgtl5000", + .owner = THIS_MODULE, + .dai_link = mxs_sgtl5000_dai, + .num_links = ARRAY_SIZE(mxs_sgtl5000_dai), +}; + +static int mxs_sgtl5000_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mxs_sgtl5000; + int ret, i; + struct device_node *np = pdev->dev.of_node; + struct device_node *saif_np[2], *codec_np; + + saif_np[0] = of_parse_phandle(np, "saif-controllers", 0); + saif_np[1] = of_parse_phandle(np, "saif-controllers", 1); + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!saif_np[0] || !saif_np[1] || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + of_node_put(codec_np); + of_node_put(saif_np[0]); + of_node_put(saif_np[1]); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + mxs_sgtl5000_dai[i].codec_name = NULL; + mxs_sgtl5000_dai[i].codec_of_node = codec_np; + mxs_sgtl5000_dai[i].cpu_dai_name = NULL; + mxs_sgtl5000_dai[i].cpu_of_node = saif_np[i]; + mxs_sgtl5000_dai[i].platform_name = NULL; + mxs_sgtl5000_dai[i].platform_of_node = saif_np[i]; + } + + of_node_put(codec_np); + of_node_put(saif_np[0]); + of_node_put(saif_np[1]); + + /* + * Set an init clock(11.28Mhz) for sgtl5000 initialization(i2c r/w). + * The Sgtl5000 sysclk is derived from saif0 mclk and it's range + * should be >= 8MHz and <= 27M. + */ + ret = mxs_saif_get_mclk(0, 44100 * 256, 44100); + if (ret) { + dev_err(&pdev->dev, "failed to get mclk\n"); + return ret; + } + + card->dev = &pdev->dev; + + if (of_find_property(np, "audio-routing", NULL)) { + card->dapm_widgets = mxs_sgtl5000_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(mxs_sgtl5000_dapm_widgets); + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", + ret); + return ret; + } + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + return 0; +} + +static int mxs_sgtl5000_remove(struct platform_device *pdev) +{ + mxs_saif_put_mclk(0); + + return 0; +} + +static const struct of_device_id mxs_sgtl5000_dt_ids[] = { + { .compatible = "fsl,mxs-audio-sgtl5000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_sgtl5000_dt_ids); + +static struct platform_driver mxs_sgtl5000_audio_driver = { + .driver = { + .name = "mxs-sgtl5000", + .of_match_table = mxs_sgtl5000_dt_ids, + }, + .probe = mxs_sgtl5000_probe, + .remove = mxs_sgtl5000_remove, +}; + +module_platform_driver(mxs_sgtl5000_audio_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS ALSA SoC Machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mxs-sgtl5000"); diff --git a/sound/soc/nuc900/Kconfig b/sound/soc/nuc900/Kconfig new file mode 100644 index 000000000..7f0c954df --- /dev/null +++ b/sound/soc/nuc900/Kconfig @@ -0,0 +1,28 @@ +## +## NUC900 series AC97 API +## +config SND_SOC_NUC900 + tristate "SoC Audio for NUC900 series" + depends on ARCH_W90X900 + select SND_SOC_NUC900_AC97 + help + This option enables support for AC97 mode on the NUC900 SoC. + +config SND_SOC_NUC900_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + + +## +## Boards +## +config SND_SOC_NUC900EVB + tristate "NUC900 AC97 support for demo board" + depends on SND_SOC_NUC900 + select SND_SOC_NUC900_AC97 + select SND_SOC_AC97_CODEC + help + Select this option to enable audio (AC97) on the + NUC900 demoboard. diff --git a/sound/soc/nuc900/Makefile b/sound/soc/nuc900/Makefile new file mode 100644 index 000000000..c7ba2b954 --- /dev/null +++ b/sound/soc/nuc900/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# NUC900 series audio +snd-soc-nuc900-pcm-objs := nuc900-pcm.o +snd-soc-nuc900-ac97-objs := nuc900-ac97.o + +obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o +obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o + +# Boards +snd-soc-nuc900-audio-objs := nuc900-audio.o + +obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o diff --git a/sound/soc/nuc900/nuc900-ac97.c b/sound/soc/nuc900/nuc900-ac97.c new file mode 100644 index 000000000..81b09d740 --- /dev/null +++ b/sound/soc/nuc900/nuc900-ac97.c @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2009-2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation;version 2 of the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "nuc900-audio.h" + +static DEFINE_MUTEX(ac97_mutex); +struct nuc900_audio *nuc900_ac97_data; +EXPORT_SYMBOL_GPL(nuc900_ac97_data); + +static int nuc900_checkready(void) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + + if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY)) + return -EPERM; + + return 0; +} + +/* AC97 controller reads codec register */ +static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long timeout = 0x10000, val; + + mutex_lock(&ac97_mutex); + + val = nuc900_checkready(); + if (val) { + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + goto out; + } + + /* set the R_WB bit and write register index */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg); + + /* set the valid frame bit and valid slots */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + val |= (VALID_FRAME | SLOT1_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); + + udelay(100); + + /* polling the AC_R_FINISH */ + while (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_R_FINISH) + && --timeout) + mdelay(1); + + if (!timeout) { + dev_err(nuc900_audio->dev, "AC97 read register time out !\n"); + val = -EPERM; + goto out; + } + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ; + val &= ~SLOT1_VALID; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); + + if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) { + dev_err(nuc900_audio->dev, + "R_INDEX of REG_ACTL_ACIS1 not match!\n"); + } + + udelay(100); + val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF); + +out: + mutex_unlock(&ac97_mutex); + return val; +} + +/* AC97 controller writes to codec register */ +static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long tmp, timeout = 0x10000; + + mutex_lock(&ac97_mutex); + + tmp = nuc900_checkready(); + if (tmp) + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + + /* clear the R_WB bit and write register index */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg); + + /* write register value */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val); + + /* set the valid frame bit and valid slots */ + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + udelay(100); + + /* polling the AC_W_FINISH */ + while ((AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_W_FINISH) + && --timeout) + mdelay(1); + + if (!timeout) + dev_err(nuc900_audio->dev, "AC97 write register time out !\n"); + + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp &= ~(SLOT1_VALID | SLOT2_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + mutex_unlock(&ac97_mutex); + +} + +static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* warm reset AC 97 */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val |= AC_W_RES; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + udelay(100); + + val = nuc900_checkready(); + if (val) + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + + mutex_unlock(&ac97_mutex); +} + +static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* reset Audio Controller */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val |= ACTL_RESET_BIT; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val &= (~ACTL_RESET_BIT); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + /* reset AC-link interface */ + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val |= AC_RESET; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val &= ~AC_RESET; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + /* cold reset AC 97 */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val |= AC_C_RES; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val &= (~AC_C_RES); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + udelay(100); + + mutex_unlock(&ac97_mutex); + +} + +/* AC97 controller operations */ +static struct snd_ac97_bus_ops nuc900_ac97_ops = { + .read = nuc900_ac97_read, + .write = nuc900_ac97_write, + .reset = nuc900_ac97_cold_reset, + .warm_reset = nuc900_ac97_warm_reset, +}; + +static int nuc900_ac97_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + int ret; + unsigned long val, tmp; + + ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); + tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp); + val |= AC_PLAY; + } else { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); + tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ); + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp); + val |= AC_RECORD; + } + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp &= ~(SLOT3_VALID | SLOT4_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR); + val &= ~AC_PLAY; + } else { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR); + val &= ~AC_RECORD; + } + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int nuc900_ac97_probe(struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* enable unit clock */ + clk_enable(nuc900_audio->clk); + + /* enable audio controller and AC-link interface */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val |= (IIS_AC_PIN_SEL | ACLINK_EN); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); + + mutex_unlock(&ac97_mutex); + + return 0; +} + +static int nuc900_ac97_remove(struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + + clk_disable(nuc900_audio->clk); + return 0; +} + +static const struct snd_soc_dai_ops nuc900_ac97_dai_ops = { + .trigger = nuc900_ac97_trigger, +}; + +static struct snd_soc_dai_driver nuc900_ac97_dai = { + .probe = nuc900_ac97_probe, + .remove = nuc900_ac97_remove, + .bus_control = true, + .playback = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &nuc900_ac97_dai_ops, +}; + +static const struct snd_soc_component_driver nuc900_ac97_component = { + .name = "nuc900-ac97", +}; + +static int nuc900_ac97_drvprobe(struct platform_device *pdev) +{ + struct nuc900_audio *nuc900_audio; + int ret; + + if (nuc900_ac97_data) + return -EBUSY; + + nuc900_audio = devm_kzalloc(&pdev->dev, sizeof(struct nuc900_audio), + GFP_KERNEL); + if (!nuc900_audio) + return -ENOMEM; + + spin_lock_init(&nuc900_audio->lock); + + nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nuc900_audio->mmio = devm_ioremap_resource(&pdev->dev, + nuc900_audio->res); + if (IS_ERR(nuc900_audio->mmio)) + return PTR_ERR(nuc900_audio->mmio); + + nuc900_audio->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(nuc900_audio->clk)) { + ret = PTR_ERR(nuc900_audio->clk); + goto out; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto out; + nuc900_audio->irq_num = ret; + + nuc900_ac97_data = nuc900_audio; + + ret = snd_soc_set_ac97_ops(&nuc900_ac97_ops); + if (ret) + goto out; + + ret = snd_soc_register_component(&pdev->dev, &nuc900_ac97_component, + &nuc900_ac97_dai, 1); + if (ret) + goto out; + + /* enbale ac97 multifunction pin */ + mfp_set_groupg(nuc900_audio->dev, NULL); + + return 0; + +out: + snd_soc_set_ac97_ops(NULL); + return ret; +} + +static int nuc900_ac97_drvremove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + + nuc900_ac97_data = NULL; + snd_soc_set_ac97_ops(NULL); + + return 0; +} + +static struct platform_driver nuc900_ac97_driver = { + .driver = { + .name = "nuc900-ac97", + }, + .probe = nuc900_ac97_drvprobe, + .remove = nuc900_ac97_drvremove, +}; + +module_platform_driver(nuc900_ac97_driver); + +MODULE_AUTHOR("Wan ZongShun "); +MODULE_DESCRIPTION("NUC900 AC97 SoC driver!"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nuc900-ac97"); diff --git a/sound/soc/nuc900/nuc900-audio.c b/sound/soc/nuc900/nuc900-audio.c new file mode 100644 index 000000000..2f6e6fd6e --- /dev/null +++ b/sound/soc/nuc900/nuc900-audio.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation;version 2 of the License. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "nuc900-audio.h" + +static struct snd_soc_dai_link nuc900evb_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "nuc900-ac97", + .codec_dai_name = "ac97-hifi", + .codec_name = "ac97-codec", + .platform_name = "nuc900-pcm-audio", +}; + +static struct snd_soc_card nuc900evb_audio_machine = { + .name = "NUC900EVB_AC97", + .owner = THIS_MODULE, + .dai_link = &nuc900evb_ac97_dai, + .num_links = 1, +}; + +static struct platform_device *nuc900evb_asoc_dev; + +static int __init nuc900evb_audio_init(void) +{ + int ret; + + ret = -ENOMEM; + nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1); + if (!nuc900evb_asoc_dev) + goto out; + + /* nuc900 board audio device */ + platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_audio_machine); + + ret = platform_device_add(nuc900evb_asoc_dev); + + if (ret) { + platform_device_put(nuc900evb_asoc_dev); + nuc900evb_asoc_dev = NULL; + } + +out: + return ret; +} + +static void __exit nuc900evb_audio_exit(void) +{ + platform_device_unregister(nuc900evb_asoc_dev); +} + +module_init(nuc900evb_audio_init); +module_exit(nuc900evb_audio_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NUC900 Series ASoC audio support"); +MODULE_AUTHOR("Wan ZongShun"); diff --git a/sound/soc/nuc900/nuc900-audio.h b/sound/soc/nuc900/nuc900-audio.h new file mode 100644 index 000000000..d0b725705 --- /dev/null +++ b/sound/soc/nuc900/nuc900-audio.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation;version 2 of the License. + * + */ + +#ifndef _NUC900_AUDIO_H +#define _NUC900_AUDIO_H + +#include + +/* Audio Control Registers */ +#define ACTL_CON 0x00 +#define ACTL_RESET 0x04 +#define ACTL_RDSTB 0x08 +#define ACTL_RDST_LENGTH 0x0C +#define ACTL_RDSTC 0x10 +#define ACTL_RSR 0x14 +#define ACTL_PDSTB 0x18 +#define ACTL_PDST_LENGTH 0x1C +#define ACTL_PDSTC 0x20 +#define ACTL_PSR 0x24 +#define ACTL_IISCON 0x28 +#define ACTL_ACCON 0x2C +#define ACTL_ACOS0 0x30 +#define ACTL_ACOS1 0x34 +#define ACTL_ACOS2 0x38 +#define ACTL_ACIS0 0x3C +#define ACTL_ACIS1 0x40 +#define ACTL_ACIS2 0x44 +#define ACTL_COUNTER 0x48 + +/* bit definition of REG_ACTL_CON register */ +#define R_DMA_IRQ 0x1000 +#define T_DMA_IRQ 0x0800 +#define IIS_AC_PIN_SEL 0x0100 +#define FIFO_TH 0x0080 +#define ADC_EN 0x0010 +#define M80_EN 0x0008 +#define ACLINK_EN 0x0004 +#define IIS_EN 0x0002 + +/* bit definition of REG_ACTL_RESET register */ +#define W5691_PLAY 0x20000 +#define ACTL_RESET_BIT 0x10000 +#define RECORD_RIGHT_CHNNEL 0x08000 +#define RECORD_LEFT_CHNNEL 0x04000 +#define PLAY_RIGHT_CHNNEL 0x02000 +#define PLAY_LEFT_CHNNEL 0x01000 +#define DAC_PLAY 0x00800 +#define ADC_RECORD 0x00400 +#define M80_PLAY 0x00200 +#define AC_RECORD 0x00100 +#define AC_PLAY 0x00080 +#define IIS_RECORD 0x00040 +#define IIS_PLAY 0x00020 +#define DAC_RESET 0x00010 +#define ADC_RESET 0x00008 +#define M80_RESET 0x00004 +#define AC_RESET 0x00002 +#define IIS_RESET 0x00001 + +/* bit definition of REG_ACTL_ACCON register */ +#define AC_BCLK_PU_EN 0x20 +#define AC_R_FINISH 0x10 +#define AC_W_FINISH 0x08 +#define AC_W_RES 0x04 +#define AC_C_RES 0x02 + +/* bit definition of ACTL_RSR register */ +#define R_FIFO_EMPTY 0x04 +#define R_DMA_END_IRQ 0x02 +#define R_DMA_MIDDLE_IRQ 0x01 + +/* bit definition of ACTL_PSR register */ +#define P_FIFO_EMPTY 0x04 +#define P_DMA_END_IRQ 0x02 +#define P_DMA_MIDDLE_IRQ 0x01 + +/* bit definition of ACTL_ACOS0 register */ +#define SLOT1_VALID 0x01 +#define SLOT2_VALID 0x02 +#define SLOT3_VALID 0x04 +#define SLOT4_VALID 0x08 +#define VALID_FRAME 0x10 + +/* bit definition of ACTL_ACOS1 register */ +#define R_WB 0x80 + +#define CODEC_READY 0x10 +#define RESET_PRSR 0x00 +#define AUDIO_WRITE(addr, val) __raw_writel(val, addr) +#define AUDIO_READ(addr) __raw_readl(addr) + +struct nuc900_audio { + void __iomem *mmio; + spinlock_t lock; + unsigned long irq_num; + struct resource *res; + struct clk *clk; + struct device *dev; + +}; + +extern struct nuc900_audio *nuc900_ac97_data; + +#endif /*end _NUC900_AUDIO_H */ diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c new file mode 100644 index 000000000..ad8392a87 --- /dev/null +++ b/sound/soc/nuc900/nuc900-pcm.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation;version 2 of the License. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "nuc900-audio.h" + +static const struct snd_pcm_hardware nuc900_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = 4*1024, + .period_bytes_min = 1*1024, + .period_bytes_max = 4*1024, + .periods_min = 1, + .periods_max = 1024, +}; + +static int nuc900_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); +} + +static void nuc900_update_dma_register(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + void __iomem *mmio_addr, *mmio_len; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mmio_addr = nuc900_audio->mmio + ACTL_PDSTB; + mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH; + } else { + mmio_addr = nuc900_audio->mmio + ACTL_RDSTB; + mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH; + } + + AUDIO_WRITE(mmio_addr, runtime->dma_addr); + AUDIO_WRITE(mmio_len, runtime->dma_bytes); +} + +static void nuc900_dma_start(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long val; + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val |= (T_DMA_IRQ | R_DMA_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); +} + +static void nuc900_dma_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long val; + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val &= ~(T_DMA_IRQ | R_DMA_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); +} + +static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct nuc900_audio *nuc900_audio = substream->runtime->private_data; + unsigned long val; + + spin_lock(&nuc900_audio->lock); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + + if (val & R_DMA_IRQ) { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); + + if (val & R_DMA_MIDDLE_IRQ) { + val |= R_DMA_MIDDLE_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); + } + + if (val & R_DMA_END_IRQ) { + val |= R_DMA_END_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); + } + } else if (val & T_DMA_IRQ) { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); + + if (val & P_DMA_MIDDLE_IRQ) { + val |= P_DMA_MIDDLE_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); + } + + if (val & P_DMA_END_IRQ) { + val |= P_DMA_END_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); + } + } else { + dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n"); + spin_unlock(&nuc900_audio->lock); + return IRQ_HANDLED; + } + + spin_unlock(&nuc900_audio->lock); + + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +static int nuc900_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int nuc900_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long flags, val; + int ret = 0; + + spin_lock_irqsave(&nuc900_audio->lock, flags); + + nuc900_update_dma_register(substream); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + + switch (runtime->channels) { + case 1: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); + val |= PLAY_RIGHT_CHNNEL; + } else { + val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); + val |= RECORD_RIGHT_CHNNEL; + } + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + break; + case 2: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); + else + val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + break; + default: + ret = -EINVAL; + } + spin_unlock_irqrestore(&nuc900_audio->lock, flags); + return ret; +} + +static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + nuc900_dma_start(substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + nuc900_dma_stop(substream); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int nuc900_dma_getposition(struct snd_pcm_substream *substream, + dma_addr_t *src, dma_addr_t *dst) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + + if (src != NULL) + *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC); + + if (dst != NULL) + *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC); + + return 0; +} + +static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + dma_addr_t src, dst; + unsigned long res; + + nuc900_dma_getposition(substream, &src, &dst); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + res = dst - runtime->dma_addr; + else + res = src - runtime->dma_addr; + + return bytes_to_frames(substream->runtime, res); +} + +static int nuc900_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio; + + snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware); + + nuc900_audio = nuc900_ac97_data; + + if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt, + 0, "nuc900-dma", substream)) + return -EBUSY; + + runtime->private_data = nuc900_audio; + + return 0; +} + +static int nuc900_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + + free_irq(nuc900_audio->irq_num, substream); + + return 0; +} + +static int nuc900_dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +static const struct snd_pcm_ops nuc900_dma_ops = { + .open = nuc900_dma_open, + .close = nuc900_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = nuc900_dma_hw_params, + .hw_free = nuc900_dma_hw_free, + .prepare = nuc900_dma_prepare, + .trigger = nuc900_dma_trigger, + .pointer = nuc900_dma_pointer, + .mmap = nuc900_dma_mmap, +}; + +static int nuc900_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, 4 * 1024, (4 * 1024) - 1); + + return 0; +} + +static const struct snd_soc_component_driver nuc900_soc_component = { + .ops = &nuc900_dma_ops, + .pcm_new = nuc900_dma_new, +}; + +static int nuc900_soc_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &nuc900_soc_component, + NULL, 0); +} + +static struct platform_driver nuc900_pcm_driver = { + .driver = { + .name = "nuc900-pcm-audio", + }, + + .probe = nuc900_soc_platform_probe, +}; + +module_platform_driver(nuc900_pcm_driver); + +MODULE_AUTHOR("Wan ZongShun, "); +MODULE_DESCRIPTION("nuc900 Audio DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig new file mode 100644 index 000000000..6dccea6fd --- /dev/null +++ b/sound/soc/omap/Kconfig @@ -0,0 +1,129 @@ +config SND_OMAP_SOC + tristate "SoC Audio for Texas Instruments OMAP chips (deprecated)" + depends on (ARCH_OMAP && DMA_OMAP) || (ARM && COMPILE_TEST) + select SND_SDMA_SOC + +config SND_SDMA_SOC + tristate "SoC Audio for Texas Instruments chips using sDMA" + depends on DMA_OMAP || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_OMAP_SOC_DMIC + tristate + +config SND_OMAP_SOC_MCBSP + tristate + +config SND_OMAP_SOC_MCPDM + tristate + +config SND_OMAP_SOC_HDMI_AUDIO + tristate "HDMI audio support for OMAP4+ based SoCs" + depends on SND_SDMA_SOC + help + For HDMI audio to work OMAPDSS HDMI support should be + enabled. + The hdmi audio driver implements cpu-dai component using the + callbacks provided by OMAPDSS and registers the component + under DSS HDMI device. Omap-pcm is registered for platform + component also under DSS HDMI device. Dummy codec is used as + as codec component. The hdmi audio driver implements also + the card and registers it under its own platform device. + The device for the driver is registered by OMAPDSS hdmi + driver. + +config SND_OMAP_SOC_N810 + tristate "SoC Audio support for Nokia N810" + depends on SND_SDMA_SOC && MACH_NOKIA_N810 && I2C + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC3X + help + Say Y if you want to add support for SoC audio on Nokia N810. + +config SND_OMAP_SOC_RX51 + tristate "SoC Audio support for Nokia N900 (RX-51)" + depends on SND_SDMA_SOC && ARM && I2C + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC3X + select SND_SOC_TPA6130A2 + depends on GPIOLIB + help + Say Y if you want to add support for SoC audio on Nokia N900 + cellphone. + +config SND_OMAP_SOC_AMS_DELTA + tristate "SoC Audio support for Amstrad E3 (Delta) videophone" + depends on SND_SDMA_SOC && MACH_AMS_DELTA && TTY + select SND_OMAP_SOC_MCBSP + select SND_SOC_CX20442 + help + Say Y if you want to add support for SoC audio device connected to + a handset and a speakerphone found on Amstrad E3 (Delta) videophone. + + Note that in order to get those devices fully supported, you have to + build the kernel with standard serial port driver included and + configured for at least 4 ports. Then, from userspace, you must load + a line discipline #19 on the modem (ttyS3) serial line. The simplest + way to achieve this is to install util-linux-ng and use the included + ldattach utility. This can be started automatically from udev, + a simple rule like this one should do the trick (it does for me): + ACTION=="add", KERNEL=="controlC0", \ + RUN+="/usr/sbin/ldattach 19 /dev/ttyS3" + +config SND_OMAP_SOC_OSK5912 + tristate "SoC Audio support for omap osk5912" + depends on SND_SDMA_SOC && MACH_OMAP_OSK && I2C + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC23_I2C + help + Say Y if you want to add support for SoC audio on osk5912. + +config SND_OMAP_SOC_AM3517EVM + tristate "SoC Audio support for OMAP3517 / AM3517 EVM" + depends on SND_SDMA_SOC && MACH_OMAP3517EVM && I2C + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC23_I2C + help + Say Y if you want to add support for SoC audio on the OMAP3517 / AM3517 + EVM. + +config SND_OMAP_SOC_OMAP_TWL4030 + tristate "SoC Audio support for TI SoC based boards with twl4030 codec" + depends on TWL4030_CORE && SND_SDMA_SOC + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for SoC audio on TI SoC based boards + using twl4030 as c codec. This driver currently supports: + - Beagleboard or Devkit8000 + - Gumstix Overo or CompuLab CM-T35/CM-T3730 + - IGEP v2 + - OMAP3EVM + - SDP3430 + - Zoom2 + +config SND_OMAP_SOC_OMAP_ABE_TWL6040 + tristate "SoC Audio support for OMAP boards using ABE and twl6040 codec" + depends on TWL6040_CORE && SND_SDMA_SOC && COMMON_CLK + depends on ARCH_OMAP4 || (SOC_OMAP5 && MFD_PALMAS) || COMPILE_TEST + select SND_OMAP_SOC_DMIC + select SND_OMAP_SOC_MCPDM + select SND_SOC_TWL6040 + select SND_SOC_DMIC + select COMMON_CLK_PALMAS if (SOC_OMAP5 && MFD_PALMAS) + select CLK_TWL6040 + help + Say Y if you want to add support for SoC audio on OMAP boards using + ABE and twl6040 codec. This driver currently supports: + - SDP4430/Blaze boards + - PandaBoard (4430) + - PandaBoardES (4460) + - omap5-uevm (5432) + +config SND_OMAP_SOC_OMAP3_PANDORA + tristate "SoC Audio support for OMAP3 Pandora" + depends on TWL4030_CORE && SND_SDMA_SOC && MACH_OMAP3_PANDORA + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for SoC audio on the OMAP3 Pandora. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile new file mode 100644 index 000000000..53eba3413 --- /dev/null +++ b/sound/soc/omap/Makefile @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +# OMAP Platform Support +snd-soc-sdma-objs := sdma-pcm.o +snd-soc-omap-dmic-objs := omap-dmic.o +snd-soc-omap-mcbsp-objs := omap-mcbsp.o mcbsp.o +snd-soc-omap-mcpdm-objs := omap-mcpdm.o +snd-soc-omap-hdmi-audio-objs := omap-hdmi-audio.o + +obj-$(CONFIG_SND_SDMA_SOC) += snd-soc-sdma.o +obj-$(CONFIG_SND_OMAP_SOC_DMIC) += snd-soc-omap-dmic.o +obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o +obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o +obj-$(CONFIG_SND_OMAP_SOC_HDMI_AUDIO) += snd-soc-omap-hdmi-audio.o + +# OMAP Machine Support +snd-soc-n810-objs := n810.o +snd-soc-rx51-objs := rx51.o +snd-soc-ams-delta-objs := ams-delta.o +snd-soc-osk5912-objs := osk5912.o +snd-soc-am3517evm-objs := am3517evm.o +snd-soc-omap-abe-twl6040-objs := omap-abe-twl6040.o +snd-soc-omap-twl4030-objs := omap-twl4030.o +snd-soc-omap3pandora-objs := omap3pandora.o + +obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o +obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o +obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o +obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o +obj-$(CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040) += snd-soc-omap-abe-twl6040.o +obj-$(CONFIG_SND_OMAP_SOC_OMAP_TWL4030) += snd-soc-omap-twl4030.o +obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o diff --git a/sound/soc/omap/am3517evm.c b/sound/soc/omap/am3517evm.c new file mode 100644 index 000000000..d5651026e --- /dev/null +++ b/sound/soc/omap/am3517evm.c @@ -0,0 +1,141 @@ +/* + * am3517evm.c -- ALSA SoC support for OMAP3517 / AM3517 EVM + * + * Author: Anuj Aggarwal + * + * Based on sound/soc/omap/beagle.c by Steve Sakoman + * + * Copyright (C) 2009 Texas Instruments Incorporated + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "omap-mcbsp.h" + +#include "../codecs/tlv320aic23.h" + +#define CODEC_CLOCK 12000000 + +static int am3517evm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + CODEC_CLOCK, SND_SOC_CLOCK_IN); + if (ret < 0) + printk(KERN_ERR "can't set codec system clock\n"); + + return ret; +} + +static const struct snd_soc_ops am3517evm_ops = { + .hw_params = am3517evm_hw_params, +}; + +/* am3517evm machine dapm widgets */ +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Line Out", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Line Out connected to LLOUT, RLOUT */ + {"Line Out", NULL, "LOUT"}, + {"Line Out", NULL, "ROUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic In"}, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link am3517evm_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .cpu_dai_name = "omap-mcbsp.1", + .codec_dai_name = "tlv320aic23-hifi", + .platform_name = "omap-mcbsp.1", + .codec_name = "tlv320aic23-codec.2-001a", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &am3517evm_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_am3517evm = { + .name = "am3517evm", + .owner = THIS_MODULE, + .dai_link = &am3517evm_dai, + .num_links = 1, + + .dapm_widgets = tlv320aic23_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct platform_device *am3517evm_snd_device; + +static int __init am3517evm_soc_init(void) +{ + int ret; + + if (!machine_is_omap3517evm()) + return -ENODEV; + pr_info("OMAP3517 / AM3517 EVM SoC init\n"); + + am3517evm_snd_device = platform_device_alloc("soc-audio", -1); + if (!am3517evm_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(am3517evm_snd_device, &snd_soc_am3517evm); + + ret = platform_device_add(am3517evm_snd_device); + if (ret) + goto err1; + + return 0; + +err1: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(am3517evm_snd_device); + + return ret; +} + +static void __exit am3517evm_soc_exit(void) +{ + platform_device_unregister(am3517evm_snd_device); +} + +module_init(am3517evm_soc_init); +module_exit(am3517evm_soc_exit); + +MODULE_AUTHOR("Anuj Aggarwal "); +MODULE_DESCRIPTION("ALSA SoC OMAP3517 / AM3517 EVM"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c new file mode 100644 index 000000000..4dce494df --- /dev/null +++ b/sound/soc/omap/ams-delta.c @@ -0,0 +1,594 @@ +/* + * ams-delta.c -- SoC audio for Amstrad E3 (Delta) videophone + * + * Copyright (C) 2009 Janusz Krzysztofik + * + * Initially based on sound/soc/omap/osk5912.x + * Copyright (C) 2008 Mistral Solutions + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "omap-mcbsp.h" +#include "../codecs/cx20442.h" + +/* Board specific DAPM widgets */ +static const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = { + /* Handset */ + SND_SOC_DAPM_MIC("Mouthpiece", NULL), + SND_SOC_DAPM_HP("Earpiece", NULL), + /* Handsfree/Speakerphone */ + SND_SOC_DAPM_MIC("Microphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +/* How they are connected to codec pins */ +static const struct snd_soc_dapm_route ams_delta_audio_map[] = { + {"TELIN", NULL, "Mouthpiece"}, + {"Earpiece", NULL, "TELOUT"}, + + {"MIC", NULL, "Microphone"}, + {"Speaker", NULL, "SPKOUT"}, +}; + +/* + * Controls, functional after the modem line discipline is activated. + */ + +/* Virtual switch: audio input/output constellations */ +static const char *ams_delta_audio_mode[] = + {"Mixed", "Handset", "Handsfree", "Speakerphone"}; + +/* Selection <-> pin translation */ +#define AMS_DELTA_MOUTHPIECE 0 +#define AMS_DELTA_EARPIECE 1 +#define AMS_DELTA_MICROPHONE 2 +#define AMS_DELTA_SPEAKER 3 +#define AMS_DELTA_AGC 4 + +#define AMS_DELTA_MIXED ((1 << AMS_DELTA_EARPIECE) | \ + (1 << AMS_DELTA_MICROPHONE)) +#define AMS_DELTA_HANDSET ((1 << AMS_DELTA_MOUTHPIECE) | \ + (1 << AMS_DELTA_EARPIECE)) +#define AMS_DELTA_HANDSFREE ((1 << AMS_DELTA_MICROPHONE) | \ + (1 << AMS_DELTA_SPEAKER)) +#define AMS_DELTA_SPEAKERPHONE (AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC)) + +static const unsigned short ams_delta_audio_mode_pins[] = { + AMS_DELTA_MIXED, + AMS_DELTA_HANDSET, + AMS_DELTA_HANDSFREE, + AMS_DELTA_SPEAKERPHONE, +}; + +static unsigned short ams_delta_audio_agc; + +/* + * Used for passing a codec structure pointer + * from the board initialization code to the tty line discipline. + */ +static struct snd_soc_component *cx20442_codec; + +static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = &card->dapm; + struct soc_enum *control = (struct soc_enum *)kcontrol->private_value; + unsigned short pins; + int pin, changed = 0; + + /* Refuse any mode changes if we are not able to control the codec. */ + if (!cx20442_codec->card->pop_time) + return -EUNATCH; + + if (ucontrol->value.enumerated.item[0] >= control->items) + return -EINVAL; + + snd_soc_dapm_mutex_lock(dapm); + + /* Translate selection to bitmap */ + pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]]; + + /* Setup pins after corresponding bits if changed */ + pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE)); + + if (pin != snd_soc_dapm_get_pin_status(dapm, "Mouthpiece")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin_unlocked(dapm, "Mouthpiece"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece"); + } + pin = !!(pins & (1 << AMS_DELTA_EARPIECE)); + if (pin != snd_soc_dapm_get_pin_status(dapm, "Earpiece")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Earpiece"); + } + pin = !!(pins & (1 << AMS_DELTA_MICROPHONE)); + if (pin != snd_soc_dapm_get_pin_status(dapm, "Microphone")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Microphone"); + } + pin = !!(pins & (1 << AMS_DELTA_SPEAKER)); + if (pin != snd_soc_dapm_get_pin_status(dapm, "Speaker")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker"); + } + pin = !!(pins & (1 << AMS_DELTA_AGC)); + if (pin != ams_delta_audio_agc) { + ams_delta_audio_agc = pin; + changed = 1; + if (pin) + snd_soc_dapm_enable_pin_unlocked(dapm, "AGCIN"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN"); + } + + if (changed) + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); + + return changed; +} + +static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = &card->dapm; + unsigned short pins, mode; + + pins = ((snd_soc_dapm_get_pin_status(dapm, "Mouthpiece") << + AMS_DELTA_MOUTHPIECE) | + (snd_soc_dapm_get_pin_status(dapm, "Earpiece") << + AMS_DELTA_EARPIECE)); + if (pins) + pins |= (snd_soc_dapm_get_pin_status(dapm, "Microphone") << + AMS_DELTA_MICROPHONE); + else + pins = ((snd_soc_dapm_get_pin_status(dapm, "Microphone") << + AMS_DELTA_MICROPHONE) | + (snd_soc_dapm_get_pin_status(dapm, "Speaker") << + AMS_DELTA_SPEAKER) | + (ams_delta_audio_agc << AMS_DELTA_AGC)); + + for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++) + if (pins == ams_delta_audio_mode_pins[mode]) + break; + + if (mode >= ARRAY_SIZE(ams_delta_audio_mode)) + return -EINVAL; + + ucontrol->value.enumerated.item[0] = mode; + + return 0; +} + +static const SOC_ENUM_SINGLE_EXT_DECL(ams_delta_audio_enum, + ams_delta_audio_mode); + +static const struct snd_kcontrol_new ams_delta_audio_controls[] = { + SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum, + ams_delta_get_audio_mode, ams_delta_set_audio_mode), +}; + +/* Hook switch */ +static struct snd_soc_jack ams_delta_hook_switch; +static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = { + { + .name = "hook_switch", + .report = SND_JACK_HEADSET, + .invert = 1, + .debounce_time = 150, + } +}; + +/* After we are able to control the codec over the modem, + * the hook switch can be used for dynamic DAPM reconfiguration. */ +static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = { + /* Handset */ + { + .pin = "Mouthpiece", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Earpiece", + .mask = SND_JACK_HEADPHONE, + }, + /* Handsfree */ + { + .pin = "Microphone", + .mask = SND_JACK_MICROPHONE, + .invert = 1, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + + +/* + * Modem line discipline, required for making above controls functional. + * Activated from userspace with ldattach, possibly invoked from udev rule. + */ + +/* To actually apply any modem controlled configuration changes to the codec, + * we must connect codec DAI pins to the modem for a moment. Be careful not + * to interfere with our digital mute function that shares the same hardware. */ +static struct timer_list cx81801_timer; +static bool cx81801_cmd_pending; +static bool ams_delta_muted; +static DEFINE_SPINLOCK(ams_delta_lock); +static struct gpio_desc *gpiod_modem_codec; + +static void cx81801_timeout(struct timer_list *unused) +{ + int muted; + + spin_lock(&ams_delta_lock); + cx81801_cmd_pending = 0; + muted = ams_delta_muted; + spin_unlock(&ams_delta_lock); + + /* Reconnect the codec DAI back from the modem to the CPU DAI + * only if digital mute still off */ + if (!muted) + gpiod_set_value(gpiod_modem_codec, 0); +} + +/* Line discipline .open() */ +static int cx81801_open(struct tty_struct *tty) +{ + int ret; + + if (!cx20442_codec) + return -ENODEV; + + /* + * Pass the codec structure pointer for use by other ldisc callbacks, + * both the card and the codec specific parts. + */ + tty->disc_data = cx20442_codec; + + ret = v253_ops.open(tty); + + if (ret < 0) + tty->disc_data = NULL; + + return ret; +} + +/* Line discipline .close() */ +static void cx81801_close(struct tty_struct *tty) +{ + struct snd_soc_component *component = tty->disc_data; + struct snd_soc_dapm_context *dapm = &component->card->dapm; + + del_timer_sync(&cx81801_timer); + + /* Prevent the hook switch from further changing the DAPM pins */ + INIT_LIST_HEAD(&ams_delta_hook_switch.pins); + + if (!component) + return; + + v253_ops.close(tty); + + /* Revert back to default audio input/output constellation */ + snd_soc_dapm_mutex_lock(dapm); + + snd_soc_dapm_disable_pin_unlocked(dapm, "Mouthpiece"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Earpiece"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Microphone"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker"); + snd_soc_dapm_disable_pin_unlocked(dapm, "AGCIN"); + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +/* Line discipline .hangup() */ +static int cx81801_hangup(struct tty_struct *tty) +{ + cx81801_close(tty); + return 0; +} + +/* Line discipline .receive_buf() */ +static void cx81801_receive(struct tty_struct *tty, + const unsigned char *cp, char *fp, int count) +{ + struct snd_soc_component *component = tty->disc_data; + const unsigned char *c; + int apply, ret; + + if (!component) + return; + + if (!component->card->pop_time) { + /* First modem response, complete setup procedure */ + + /* Initialize timer used for config pulse generation */ + timer_setup(&cx81801_timer, cx81801_timeout, 0); + + v253_ops.receive_buf(tty, cp, fp, count); + + /* Link hook switch to DAPM pins */ + ret = snd_soc_jack_add_pins(&ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_pins), + ams_delta_hook_switch_pins); + if (ret) + dev_warn(component->dev, + "Failed to link hook switch to DAPM pins, " + "will continue with hook switch unlinked.\n"); + + return; + } + + v253_ops.receive_buf(tty, cp, fp, count); + + for (c = &cp[count - 1]; c >= cp; c--) { + if (*c != '\r') + continue; + /* Complete modem response received, apply config to codec */ + + spin_lock_bh(&ams_delta_lock); + mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150)); + apply = !ams_delta_muted && !cx81801_cmd_pending; + cx81801_cmd_pending = 1; + spin_unlock_bh(&ams_delta_lock); + + /* Apply config pulse by connecting the codec to the modem + * if not already done */ + if (apply) + gpiod_set_value(gpiod_modem_codec, 1); + break; + } +} + +/* Line discipline .write_wakeup() */ +static void cx81801_wakeup(struct tty_struct *tty) +{ + v253_ops.write_wakeup(tty); +} + +static struct tty_ldisc_ops cx81801_ops = { + .magic = TTY_LDISC_MAGIC, + .name = "cx81801", + .owner = THIS_MODULE, + .open = cx81801_open, + .close = cx81801_close, + .hangup = cx81801_hangup, + .receive_buf = cx81801_receive, + .write_wakeup = cx81801_wakeup, +}; + + +/* + * Even if not very useful, the sound card can still work without any of the + * above functonality activated. You can still control its audio input/output + * constellation and speakerphone gain from userspace by issuing AT commands + * over the modem port. + */ + +static struct snd_soc_ops ams_delta_ops; + + +/* Digital mute implemented using modem/CPU multiplexer. + * Shares hardware with codec config pulse generation */ +static bool ams_delta_muted = 1; + +static int ams_delta_digital_mute(struct snd_soc_dai *dai, int mute) +{ + int apply; + + if (ams_delta_muted == mute) + return 0; + + spin_lock_bh(&ams_delta_lock); + ams_delta_muted = mute; + apply = !cx81801_cmd_pending; + spin_unlock_bh(&ams_delta_lock); + + if (apply) + gpiod_set_value(gpiod_modem_codec, !!mute); + return 0; +} + +/* Our codec DAI probably doesn't have its own .ops structure */ +static const struct snd_soc_dai_ops ams_delta_dai_ops = { + .digital_mute = ams_delta_digital_mute, +}; + +/* Will be used if the codec ever has its own digital_mute function */ +static int ams_delta_startup(struct snd_pcm_substream *substream) +{ + return ams_delta_digital_mute(NULL, 0); +} + +static void ams_delta_shutdown(struct snd_pcm_substream *substream) +{ + ams_delta_digital_mute(NULL, 1); +} + + +/* + * Card initialization + */ + +static int ams_delta_cx20442_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = &card->dapm; + int ret; + /* Codec is ready, now add/activate board specific controls */ + + /* Store a pointer to the codec structure for tty ldisc use */ + cx20442_codec = rtd->codec_dai->component; + + /* Add hook switch - can be used to control the codec from userspace + * even if line discipline fails */ + ret = snd_soc_card_jack_new(card, "hook_switch", SND_JACK_HEADSET, + &ams_delta_hook_switch, NULL, 0); + if (ret) + dev_warn(card->dev, + "Failed to allocate resources for hook switch, " + "will continue without one.\n"); + else { + ret = snd_soc_jack_add_gpiods(card->dev, &ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_gpios), + ams_delta_hook_switch_gpios); + if (ret) + dev_warn(card->dev, + "Failed to set up hook switch GPIO line, " + "will continue with hook switch inactive.\n"); + } + + gpiod_modem_codec = devm_gpiod_get(card->dev, "modem_codec", + GPIOD_OUT_HIGH); + if (IS_ERR(gpiod_modem_codec)) { + dev_warn(card->dev, "Failed to obtain modem_codec GPIO\n"); + return 0; + } + + /* Set up digital mute if not provided by the codec */ + if (!codec_dai->driver->ops) { + codec_dai->driver->ops = &ams_delta_dai_ops; + } else { + ams_delta_ops.startup = ams_delta_startup; + ams_delta_ops.shutdown = ams_delta_shutdown; + } + + /* Register optional line discipline for over the modem control */ + ret = tty_register_ldisc(N_V253, &cx81801_ops); + if (ret) { + dev_warn(card->dev, + "Failed to register line discipline, " + "will continue without any controls.\n"); + return 0; + } + + /* Set up initial pin constellation */ + snd_soc_dapm_disable_pin(dapm, "Mouthpiece"); + snd_soc_dapm_disable_pin(dapm, "Speaker"); + snd_soc_dapm_disable_pin(dapm, "AGCIN"); + snd_soc_dapm_disable_pin(dapm, "AGCOUT"); + + return 0; +} + +/* DAI glue - connects codec <--> CPU */ +static struct snd_soc_dai_link ams_delta_dai_link = { + .name = "CX20442", + .stream_name = "CX20442", + .cpu_dai_name = "omap-mcbsp.1", + .codec_dai_name = "cx20442-voice", + .init = ams_delta_cx20442_init, + .platform_name = "omap-mcbsp.1", + .codec_name = "cx20442-codec", + .ops = &ams_delta_ops, + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +/* Audio card driver */ +static struct snd_soc_card ams_delta_audio_card = { + .name = "AMS_DELTA", + .owner = THIS_MODULE, + .dai_link = &ams_delta_dai_link, + .num_links = 1, + + .controls = ams_delta_audio_controls, + .num_controls = ARRAY_SIZE(ams_delta_audio_controls), + .dapm_widgets = ams_delta_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ams_delta_dapm_widgets), + .dapm_routes = ams_delta_audio_map, + .num_dapm_routes = ARRAY_SIZE(ams_delta_audio_map), +}; + +/* Module init/exit */ +static int ams_delta_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &ams_delta_audio_card; + int ret; + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + card->dev = NULL; + return ret; + } + return 0; +} + +static int ams_delta_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + if (tty_unregister_ldisc(N_V253) != 0) + dev_warn(&pdev->dev, + "failed to unregister V253 line discipline\n"); + + snd_soc_unregister_card(card); + card->dev = NULL; + return 0; +} + +#define DRV_NAME "ams-delta-audio" + +static struct platform_driver ams_delta_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = ams_delta_probe, + .remove = ams_delta_remove, +}; + +module_platform_driver(ams_delta_driver); + +MODULE_AUTHOR("Janusz Krzysztofik "); +MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/omap/mcbsp.c b/sound/soc/omap/mcbsp.c new file mode 100644 index 000000000..79d4dc785 --- /dev/null +++ b/sound/soc/omap/mcbsp.c @@ -0,0 +1,1104 @@ +/* + * sound/soc/omap/mcbsp.c + * + * Copyright (C) 2004 Nokia Corporation + * Author: Samuel Ortiz + * + * Contact: Jarkko Nikula + * Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Multichannel mode not supported. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mcbsp.h" + +static void omap_mcbsp_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) +{ + void __iomem *addr = mcbsp->io_base + reg * mcbsp->pdata->reg_step; + + if (mcbsp->pdata->reg_size == 2) { + ((u16 *)mcbsp->reg_cache)[reg] = (u16)val; + writew_relaxed((u16)val, addr); + } else { + ((u32 *)mcbsp->reg_cache)[reg] = val; + writel_relaxed(val, addr); + } +} + +static int omap_mcbsp_read(struct omap_mcbsp *mcbsp, u16 reg, bool from_cache) +{ + void __iomem *addr = mcbsp->io_base + reg * mcbsp->pdata->reg_step; + + if (mcbsp->pdata->reg_size == 2) { + return !from_cache ? readw_relaxed(addr) : + ((u16 *)mcbsp->reg_cache)[reg]; + } else { + return !from_cache ? readl_relaxed(addr) : + ((u32 *)mcbsp->reg_cache)[reg]; + } +} + +static void omap_mcbsp_st_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) +{ + writel_relaxed(val, mcbsp->st_data->io_base_st + reg); +} + +static int omap_mcbsp_st_read(struct omap_mcbsp *mcbsp, u16 reg) +{ + return readl_relaxed(mcbsp->st_data->io_base_st + reg); +} + +#define MCBSP_READ(mcbsp, reg) \ + omap_mcbsp_read(mcbsp, OMAP_MCBSP_REG_##reg, 0) +#define MCBSP_WRITE(mcbsp, reg, val) \ + omap_mcbsp_write(mcbsp, OMAP_MCBSP_REG_##reg, val) +#define MCBSP_READ_CACHE(mcbsp, reg) \ + omap_mcbsp_read(mcbsp, OMAP_MCBSP_REG_##reg, 1) + +#define MCBSP_ST_READ(mcbsp, reg) \ + omap_mcbsp_st_read(mcbsp, OMAP_ST_REG_##reg) +#define MCBSP_ST_WRITE(mcbsp, reg, val) \ + omap_mcbsp_st_write(mcbsp, OMAP_ST_REG_##reg, val) + +static void omap_mcbsp_dump_reg(struct omap_mcbsp *mcbsp) +{ + dev_dbg(mcbsp->dev, "**** McBSP%d regs ****\n", mcbsp->id); + dev_dbg(mcbsp->dev, "DRR2: 0x%04x\n", + MCBSP_READ(mcbsp, DRR2)); + dev_dbg(mcbsp->dev, "DRR1: 0x%04x\n", + MCBSP_READ(mcbsp, DRR1)); + dev_dbg(mcbsp->dev, "DXR2: 0x%04x\n", + MCBSP_READ(mcbsp, DXR2)); + dev_dbg(mcbsp->dev, "DXR1: 0x%04x\n", + MCBSP_READ(mcbsp, DXR1)); + dev_dbg(mcbsp->dev, "SPCR2: 0x%04x\n", + MCBSP_READ(mcbsp, SPCR2)); + dev_dbg(mcbsp->dev, "SPCR1: 0x%04x\n", + MCBSP_READ(mcbsp, SPCR1)); + dev_dbg(mcbsp->dev, "RCR2: 0x%04x\n", + MCBSP_READ(mcbsp, RCR2)); + dev_dbg(mcbsp->dev, "RCR1: 0x%04x\n", + MCBSP_READ(mcbsp, RCR1)); + dev_dbg(mcbsp->dev, "XCR2: 0x%04x\n", + MCBSP_READ(mcbsp, XCR2)); + dev_dbg(mcbsp->dev, "XCR1: 0x%04x\n", + MCBSP_READ(mcbsp, XCR1)); + dev_dbg(mcbsp->dev, "SRGR2: 0x%04x\n", + MCBSP_READ(mcbsp, SRGR2)); + dev_dbg(mcbsp->dev, "SRGR1: 0x%04x\n", + MCBSP_READ(mcbsp, SRGR1)); + dev_dbg(mcbsp->dev, "PCR0: 0x%04x\n", + MCBSP_READ(mcbsp, PCR0)); + dev_dbg(mcbsp->dev, "***********************\n"); +} + +static irqreturn_t omap_mcbsp_irq_handler(int irq, void *dev_id) +{ + struct omap_mcbsp *mcbsp = dev_id; + u16 irqst; + + irqst = MCBSP_READ(mcbsp, IRQST); + dev_dbg(mcbsp->dev, "IRQ callback : 0x%x\n", irqst); + + if (irqst & RSYNCERREN) + dev_err(mcbsp->dev, "RX Frame Sync Error!\n"); + if (irqst & RFSREN) + dev_dbg(mcbsp->dev, "RX Frame Sync\n"); + if (irqst & REOFEN) + dev_dbg(mcbsp->dev, "RX End Of Frame\n"); + if (irqst & RRDYEN) + dev_dbg(mcbsp->dev, "RX Buffer Threshold Reached\n"); + if (irqst & RUNDFLEN) + dev_err(mcbsp->dev, "RX Buffer Underflow!\n"); + if (irqst & ROVFLEN) + dev_err(mcbsp->dev, "RX Buffer Overflow!\n"); + + if (irqst & XSYNCERREN) + dev_err(mcbsp->dev, "TX Frame Sync Error!\n"); + if (irqst & XFSXEN) + dev_dbg(mcbsp->dev, "TX Frame Sync\n"); + if (irqst & XEOFEN) + dev_dbg(mcbsp->dev, "TX End Of Frame\n"); + if (irqst & XRDYEN) + dev_dbg(mcbsp->dev, "TX Buffer threshold Reached\n"); + if (irqst & XUNDFLEN) + dev_err(mcbsp->dev, "TX Buffer Underflow!\n"); + if (irqst & XOVFLEN) + dev_err(mcbsp->dev, "TX Buffer Overflow!\n"); + if (irqst & XEMPTYEOFEN) + dev_dbg(mcbsp->dev, "TX Buffer empty at end of frame\n"); + + MCBSP_WRITE(mcbsp, IRQST, irqst); + + return IRQ_HANDLED; +} + +static irqreturn_t omap_mcbsp_tx_irq_handler(int irq, void *dev_id) +{ + struct omap_mcbsp *mcbsp_tx = dev_id; + u16 irqst_spcr2; + + irqst_spcr2 = MCBSP_READ(mcbsp_tx, SPCR2); + dev_dbg(mcbsp_tx->dev, "TX IRQ callback : 0x%x\n", irqst_spcr2); + + if (irqst_spcr2 & XSYNC_ERR) { + dev_err(mcbsp_tx->dev, "TX Frame Sync Error! : 0x%x\n", + irqst_spcr2); + /* Writing zero to XSYNC_ERR clears the IRQ */ + MCBSP_WRITE(mcbsp_tx, SPCR2, MCBSP_READ_CACHE(mcbsp_tx, SPCR2)); + } + + return IRQ_HANDLED; +} + +static irqreturn_t omap_mcbsp_rx_irq_handler(int irq, void *dev_id) +{ + struct omap_mcbsp *mcbsp_rx = dev_id; + u16 irqst_spcr1; + + irqst_spcr1 = MCBSP_READ(mcbsp_rx, SPCR1); + dev_dbg(mcbsp_rx->dev, "RX IRQ callback : 0x%x\n", irqst_spcr1); + + if (irqst_spcr1 & RSYNC_ERR) { + dev_err(mcbsp_rx->dev, "RX Frame Sync Error! : 0x%x\n", + irqst_spcr1); + /* Writing zero to RSYNC_ERR clears the IRQ */ + MCBSP_WRITE(mcbsp_rx, SPCR1, MCBSP_READ_CACHE(mcbsp_rx, SPCR1)); + } + + return IRQ_HANDLED; +} + +/* + * omap_mcbsp_config simply write a config to the + * appropriate McBSP. + * You either call this function or set the McBSP registers + * by yourself before calling omap_mcbsp_start(). + */ +void omap_mcbsp_config(struct omap_mcbsp *mcbsp, + const struct omap_mcbsp_reg_cfg *config) +{ + dev_dbg(mcbsp->dev, "Configuring McBSP%d phys_base: 0x%08lx\n", + mcbsp->id, mcbsp->phys_base); + + /* We write the given config */ + MCBSP_WRITE(mcbsp, SPCR2, config->spcr2); + MCBSP_WRITE(mcbsp, SPCR1, config->spcr1); + MCBSP_WRITE(mcbsp, RCR2, config->rcr2); + MCBSP_WRITE(mcbsp, RCR1, config->rcr1); + MCBSP_WRITE(mcbsp, XCR2, config->xcr2); + MCBSP_WRITE(mcbsp, XCR1, config->xcr1); + MCBSP_WRITE(mcbsp, SRGR2, config->srgr2); + MCBSP_WRITE(mcbsp, SRGR1, config->srgr1); + MCBSP_WRITE(mcbsp, MCR2, config->mcr2); + MCBSP_WRITE(mcbsp, MCR1, config->mcr1); + MCBSP_WRITE(mcbsp, PCR0, config->pcr0); + if (mcbsp->pdata->has_ccr) { + MCBSP_WRITE(mcbsp, XCCR, config->xccr); + MCBSP_WRITE(mcbsp, RCCR, config->rccr); + } + /* Enable wakeup behavior */ + if (mcbsp->pdata->has_wakeup) + MCBSP_WRITE(mcbsp, WAKEUPEN, XRDYEN | RRDYEN); + + /* Enable TX/RX sync error interrupts by default */ + if (mcbsp->irq) + MCBSP_WRITE(mcbsp, IRQEN, RSYNCERREN | XSYNCERREN | + RUNDFLEN | ROVFLEN | XUNDFLEN | XOVFLEN); +} + +/** + * omap_mcbsp_dma_reg_params - returns the address of mcbsp data register + * @id - mcbsp id + * @stream - indicates the direction of data flow (rx or tx) + * + * Returns the address of mcbsp data transmit register or data receive register + * to be used by DMA for transferring/receiving data based on the value of + * @stream for the requested mcbsp given by @id + */ +static int omap_mcbsp_dma_reg_params(struct omap_mcbsp *mcbsp, + unsigned int stream) +{ + int data_reg; + + if (mcbsp->pdata->reg_size == 2) { + if (stream) + data_reg = OMAP_MCBSP_REG_DRR1; + else + data_reg = OMAP_MCBSP_REG_DXR1; + } else { + if (stream) + data_reg = OMAP_MCBSP_REG_DRR; + else + data_reg = OMAP_MCBSP_REG_DXR; + } + + return mcbsp->phys_dma_base + data_reg * mcbsp->pdata->reg_step; +} + +static void omap_st_on(struct omap_mcbsp *mcbsp) +{ + unsigned int w; + + if (mcbsp->pdata->force_ick_on) + mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, true); + + /* Disable Sidetone clock auto-gating for normal operation */ + w = MCBSP_ST_READ(mcbsp, SYSCONFIG); + MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w & ~(ST_AUTOIDLE)); + + /* Enable McBSP Sidetone */ + w = MCBSP_READ(mcbsp, SSELCR); + MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN); + + /* Enable Sidetone from Sidetone Core */ + w = MCBSP_ST_READ(mcbsp, SSELCR); + MCBSP_ST_WRITE(mcbsp, SSELCR, w | ST_SIDETONEEN); +} + +static void omap_st_off(struct omap_mcbsp *mcbsp) +{ + unsigned int w; + + w = MCBSP_ST_READ(mcbsp, SSELCR); + MCBSP_ST_WRITE(mcbsp, SSELCR, w & ~(ST_SIDETONEEN)); + + w = MCBSP_READ(mcbsp, SSELCR); + MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN)); + + /* Enable Sidetone clock auto-gating to reduce power consumption */ + w = MCBSP_ST_READ(mcbsp, SYSCONFIG); + MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE); + + if (mcbsp->pdata->force_ick_on) + mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, false); +} + +static void omap_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir) +{ + u16 val, i; + + val = MCBSP_ST_READ(mcbsp, SSELCR); + + if (val & ST_COEFFWREN) + MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); + + MCBSP_ST_WRITE(mcbsp, SSELCR, val | ST_COEFFWREN); + + for (i = 0; i < 128; i++) + MCBSP_ST_WRITE(mcbsp, SFIRCR, fir[i]); + + i = 0; + + val = MCBSP_ST_READ(mcbsp, SSELCR); + while (!(val & ST_COEFFWRDONE) && (++i < 1000)) + val = MCBSP_ST_READ(mcbsp, SSELCR); + + MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); + + if (i == 1000) + dev_err(mcbsp->dev, "McBSP FIR load error!\n"); +} + +static void omap_st_chgain(struct omap_mcbsp *mcbsp) +{ + u16 w; + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + w = MCBSP_ST_READ(mcbsp, SSELCR); + + MCBSP_ST_WRITE(mcbsp, SGAINCR, ST_CH0GAIN(st_data->ch0gain) | \ + ST_CH1GAIN(st_data->ch1gain)); +} + +int omap_st_set_chgain(struct omap_mcbsp *mcbsp, int channel, s16 chgain) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int ret = 0; + + if (!st_data) + return -ENOENT; + + spin_lock_irq(&mcbsp->lock); + if (channel == 0) + st_data->ch0gain = chgain; + else if (channel == 1) + st_data->ch1gain = chgain; + else + ret = -EINVAL; + + if (st_data->enabled) + omap_st_chgain(mcbsp); + spin_unlock_irq(&mcbsp->lock); + + return ret; +} + +int omap_st_get_chgain(struct omap_mcbsp *mcbsp, int channel, s16 *chgain) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int ret = 0; + + if (!st_data) + return -ENOENT; + + spin_lock_irq(&mcbsp->lock); + if (channel == 0) + *chgain = st_data->ch0gain; + else if (channel == 1) + *chgain = st_data->ch1gain; + else + ret = -EINVAL; + spin_unlock_irq(&mcbsp->lock); + + return ret; +} + +static int omap_st_start(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (st_data->enabled && !st_data->running) { + omap_st_fir_write(mcbsp, st_data->taps); + omap_st_chgain(mcbsp); + + if (!mcbsp->free) { + omap_st_on(mcbsp); + st_data->running = 1; + } + } + + return 0; +} + +int omap_st_enable(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (!st_data) + return -ENODEV; + + spin_lock_irq(&mcbsp->lock); + st_data->enabled = 1; + omap_st_start(mcbsp); + spin_unlock_irq(&mcbsp->lock); + + return 0; +} + +static int omap_st_stop(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (st_data->running) { + if (!mcbsp->free) { + omap_st_off(mcbsp); + st_data->running = 0; + } + } + + return 0; +} + +int omap_st_disable(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int ret = 0; + + if (!st_data) + return -ENODEV; + + spin_lock_irq(&mcbsp->lock); + omap_st_stop(mcbsp); + st_data->enabled = 0; + spin_unlock_irq(&mcbsp->lock); + + return ret; +} + +int omap_st_is_enabled(struct omap_mcbsp *mcbsp) +{ + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + + if (!st_data) + return -ENODEV; + + return st_data->enabled; +} + +/* + * omap_mcbsp_set_rx_threshold configures the transmit threshold in words. + * The threshold parameter is 1 based, and it is converted (threshold - 1) + * for the THRSH2 register. + */ +void omap_mcbsp_set_tx_threshold(struct omap_mcbsp *mcbsp, u16 threshold) +{ + if (mcbsp->pdata->buffer_size == 0) + return; + + if (threshold && threshold <= mcbsp->max_tx_thres) + MCBSP_WRITE(mcbsp, THRSH2, threshold - 1); +} + +/* + * omap_mcbsp_set_rx_threshold configures the receive threshold in words. + * The threshold parameter is 1 based, and it is converted (threshold - 1) + * for the THRSH1 register. + */ +void omap_mcbsp_set_rx_threshold(struct omap_mcbsp *mcbsp, u16 threshold) +{ + if (mcbsp->pdata->buffer_size == 0) + return; + + if (threshold && threshold <= mcbsp->max_rx_thres) + MCBSP_WRITE(mcbsp, THRSH1, threshold - 1); +} + +/* + * omap_mcbsp_get_tx_delay returns the number of used slots in the McBSP FIFO + */ +u16 omap_mcbsp_get_tx_delay(struct omap_mcbsp *mcbsp) +{ + u16 buffstat; + + if (mcbsp->pdata->buffer_size == 0) + return 0; + + /* Returns the number of free locations in the buffer */ + buffstat = MCBSP_READ(mcbsp, XBUFFSTAT); + + /* Number of slots are different in McBSP ports */ + return mcbsp->pdata->buffer_size - buffstat; +} + +/* + * omap_mcbsp_get_rx_delay returns the number of free slots in the McBSP FIFO + * to reach the threshold value (when the DMA will be triggered to read it) + */ +u16 omap_mcbsp_get_rx_delay(struct omap_mcbsp *mcbsp) +{ + u16 buffstat, threshold; + + if (mcbsp->pdata->buffer_size == 0) + return 0; + + /* Returns the number of used locations in the buffer */ + buffstat = MCBSP_READ(mcbsp, RBUFFSTAT); + /* RX threshold */ + threshold = MCBSP_READ(mcbsp, THRSH1); + + /* Return the number of location till we reach the threshold limit */ + if (threshold <= buffstat) + return 0; + else + return threshold - buffstat; +} + +int omap_mcbsp_request(struct omap_mcbsp *mcbsp) +{ + void *reg_cache; + int err; + + reg_cache = kzalloc(mcbsp->reg_cache_size, GFP_KERNEL); + if (!reg_cache) { + return -ENOMEM; + } + + spin_lock(&mcbsp->lock); + if (!mcbsp->free) { + dev_err(mcbsp->dev, "McBSP%d is currently in use\n", + mcbsp->id); + err = -EBUSY; + goto err_kfree; + } + + mcbsp->free = false; + mcbsp->reg_cache = reg_cache; + spin_unlock(&mcbsp->lock); + + if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->request) + mcbsp->pdata->ops->request(mcbsp->id - 1); + + /* + * Make sure that transmitter, receiver and sample-rate generator are + * not running before activating IRQs. + */ + MCBSP_WRITE(mcbsp, SPCR1, 0); + MCBSP_WRITE(mcbsp, SPCR2, 0); + + if (mcbsp->irq) { + err = request_irq(mcbsp->irq, omap_mcbsp_irq_handler, 0, + "McBSP", (void *)mcbsp); + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request IRQ\n"); + goto err_clk_disable; + } + } else { + err = request_irq(mcbsp->tx_irq, omap_mcbsp_tx_irq_handler, 0, + "McBSP TX", (void *)mcbsp); + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request TX IRQ\n"); + goto err_clk_disable; + } + + err = request_irq(mcbsp->rx_irq, omap_mcbsp_rx_irq_handler, 0, + "McBSP RX", (void *)mcbsp); + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request RX IRQ\n"); + goto err_free_irq; + } + } + + return 0; +err_free_irq: + free_irq(mcbsp->tx_irq, (void *)mcbsp); +err_clk_disable: + if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free) + mcbsp->pdata->ops->free(mcbsp->id - 1); + + /* Disable wakeup behavior */ + if (mcbsp->pdata->has_wakeup) + MCBSP_WRITE(mcbsp, WAKEUPEN, 0); + + spin_lock(&mcbsp->lock); + mcbsp->free = true; + mcbsp->reg_cache = NULL; +err_kfree: + spin_unlock(&mcbsp->lock); + kfree(reg_cache); + + return err; +} + +void omap_mcbsp_free(struct omap_mcbsp *mcbsp) +{ + void *reg_cache; + + if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free) + mcbsp->pdata->ops->free(mcbsp->id - 1); + + /* Disable wakeup behavior */ + if (mcbsp->pdata->has_wakeup) + MCBSP_WRITE(mcbsp, WAKEUPEN, 0); + + /* Disable interrupt requests */ + if (mcbsp->irq) + MCBSP_WRITE(mcbsp, IRQEN, 0); + + if (mcbsp->irq) { + free_irq(mcbsp->irq, (void *)mcbsp); + } else { + free_irq(mcbsp->rx_irq, (void *)mcbsp); + free_irq(mcbsp->tx_irq, (void *)mcbsp); + } + + reg_cache = mcbsp->reg_cache; + + /* + * Select CLKS source from internal source unconditionally before + * marking the McBSP port as free. + * If the external clock source via MCBSP_CLKS pin has been selected the + * system will refuse to enter idle if the CLKS pin source is not reset + * back to internal source. + */ + if (!mcbsp_omap1()) + omap2_mcbsp_set_clks_src(mcbsp, MCBSP_CLKS_PRCM_SRC); + + spin_lock(&mcbsp->lock); + if (mcbsp->free) + dev_err(mcbsp->dev, "McBSP%d was not reserved\n", mcbsp->id); + else + mcbsp->free = true; + mcbsp->reg_cache = NULL; + spin_unlock(&mcbsp->lock); + + kfree(reg_cache); +} + +/* + * Here we start the McBSP, by enabling transmitter, receiver or both. + * If no transmitter or receiver is active prior calling, then sample-rate + * generator and frame sync are started. + */ +void omap_mcbsp_start(struct omap_mcbsp *mcbsp, int tx, int rx) +{ + int enable_srg = 0; + u16 w; + + if (mcbsp->st_data) + omap_st_start(mcbsp); + + /* Only enable SRG, if McBSP is master */ + w = MCBSP_READ_CACHE(mcbsp, PCR0); + if (w & (FSXM | FSRM | CLKXM | CLKRM)) + enable_srg = !((MCBSP_READ_CACHE(mcbsp, SPCR2) | + MCBSP_READ_CACHE(mcbsp, SPCR1)) & 1); + + if (enable_srg) { + /* Start the sample generator */ + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 6)); + } + + /* Enable transmitter and receiver */ + tx &= 1; + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w | tx); + + rx &= 1; + w = MCBSP_READ_CACHE(mcbsp, SPCR1); + MCBSP_WRITE(mcbsp, SPCR1, w | rx); + + /* + * Worst case: CLKSRG*2 = 8000khz: (1/8000) * 2 * 2 usec + * REVISIT: 100us may give enough time for two CLKSRG, however + * due to some unknown PM related, clock gating etc. reason it + * is now at 500us. + */ + udelay(500); + + if (enable_srg) { + /* Start frame sync */ + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 7)); + } + + if (mcbsp->pdata->has_ccr) { + /* Release the transmitter and receiver */ + w = MCBSP_READ_CACHE(mcbsp, XCCR); + w &= ~(tx ? XDISABLE : 0); + MCBSP_WRITE(mcbsp, XCCR, w); + w = MCBSP_READ_CACHE(mcbsp, RCCR); + w &= ~(rx ? RDISABLE : 0); + MCBSP_WRITE(mcbsp, RCCR, w); + } + + /* Dump McBSP Regs */ + omap_mcbsp_dump_reg(mcbsp); +} + +void omap_mcbsp_stop(struct omap_mcbsp *mcbsp, int tx, int rx) +{ + int idle; + u16 w; + + /* Reset transmitter */ + tx &= 1; + if (mcbsp->pdata->has_ccr) { + w = MCBSP_READ_CACHE(mcbsp, XCCR); + w |= (tx ? XDISABLE : 0); + MCBSP_WRITE(mcbsp, XCCR, w); + } + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w & ~tx); + + /* Reset receiver */ + rx &= 1; + if (mcbsp->pdata->has_ccr) { + w = MCBSP_READ_CACHE(mcbsp, RCCR); + w |= (rx ? RDISABLE : 0); + MCBSP_WRITE(mcbsp, RCCR, w); + } + w = MCBSP_READ_CACHE(mcbsp, SPCR1); + MCBSP_WRITE(mcbsp, SPCR1, w & ~rx); + + idle = !((MCBSP_READ_CACHE(mcbsp, SPCR2) | + MCBSP_READ_CACHE(mcbsp, SPCR1)) & 1); + + if (idle) { + /* Reset the sample rate generator */ + w = MCBSP_READ_CACHE(mcbsp, SPCR2); + MCBSP_WRITE(mcbsp, SPCR2, w & ~(1 << 6)); + } + + if (mcbsp->st_data) + omap_st_stop(mcbsp); +} + +int omap2_mcbsp_set_clks_src(struct omap_mcbsp *mcbsp, u8 fck_src_id) +{ + struct clk *fck_src; + const char *src; + int r; + + if (fck_src_id == MCBSP_CLKS_PAD_SRC) + src = "pad_fck"; + else if (fck_src_id == MCBSP_CLKS_PRCM_SRC) + src = "prcm_fck"; + else + return -EINVAL; + + fck_src = clk_get(mcbsp->dev, src); + if (IS_ERR(fck_src)) { + dev_err(mcbsp->dev, "CLKS: could not clk_get() %s\n", src); + return -EINVAL; + } + + pm_runtime_put_sync(mcbsp->dev); + + r = clk_set_parent(mcbsp->fclk, fck_src); + if (r) { + dev_err(mcbsp->dev, "CLKS: could not clk_set_parent() to %s\n", + src); + clk_put(fck_src); + return r; + } + + pm_runtime_get_sync(mcbsp->dev); + + clk_put(fck_src); + + return 0; + +} + +#define max_thres(m) (mcbsp->pdata->buffer_size) +#define valid_threshold(m, val) ((val) <= max_thres(m)) +#define THRESHOLD_PROP_BUILDER(prop) \ +static ssize_t prop##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ + \ + return sprintf(buf, "%u\n", mcbsp->prop); \ +} \ + \ +static ssize_t prop##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ + unsigned long val; \ + int status; \ + \ + status = kstrtoul(buf, 0, &val); \ + if (status) \ + return status; \ + \ + if (!valid_threshold(mcbsp, val)) \ + return -EDOM; \ + \ + mcbsp->prop = val; \ + return size; \ +} \ + \ +static DEVICE_ATTR(prop, 0644, prop##_show, prop##_store); + +THRESHOLD_PROP_BUILDER(max_tx_thres); +THRESHOLD_PROP_BUILDER(max_rx_thres); + +static const char *dma_op_modes[] = { + "element", "threshold", +}; + +static ssize_t dma_op_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + int dma_op_mode, i = 0; + ssize_t len = 0; + const char * const *s; + + dma_op_mode = mcbsp->dma_op_mode; + + for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++) { + if (dma_op_mode == i) + len += sprintf(buf + len, "[%s] ", *s); + else + len += sprintf(buf + len, "%s ", *s); + } + len += sprintf(buf + len, "\n"); + + return len; +} + +static ssize_t dma_op_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + int i; + + i = sysfs_match_string(dma_op_modes, buf); + if (i < 0) + return i; + + spin_lock_irq(&mcbsp->lock); + if (!mcbsp->free) { + size = -EBUSY; + goto unlock; + } + mcbsp->dma_op_mode = i; + +unlock: + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR_RW(dma_op_mode); + +static const struct attribute *additional_attrs[] = { + &dev_attr_max_tx_thres.attr, + &dev_attr_max_rx_thres.attr, + &dev_attr_dma_op_mode.attr, + NULL, +}; + +static const struct attribute_group additional_attr_group = { + .attrs = (struct attribute **)additional_attrs, +}; + +static ssize_t st_taps_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + ssize_t status = 0; + int i; + + spin_lock_irq(&mcbsp->lock); + for (i = 0; i < st_data->nr_taps; i++) + status += sprintf(&buf[status], (i ? ", %d" : "%d"), + st_data->taps[i]); + if (i) + status += sprintf(&buf[status], "\n"); + spin_unlock_irq(&mcbsp->lock); + + return status; +} + +static ssize_t st_taps_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + struct omap_mcbsp_st_data *st_data = mcbsp->st_data; + int val, tmp, status, i = 0; + + spin_lock_irq(&mcbsp->lock); + memset(st_data->taps, 0, sizeof(st_data->taps)); + st_data->nr_taps = 0; + + do { + status = sscanf(buf, "%d%n", &val, &tmp); + if (status < 0 || status == 0) { + size = -EINVAL; + goto out; + } + if (val < -32768 || val > 32767) { + size = -EINVAL; + goto out; + } + st_data->taps[i++] = val; + buf += tmp; + if (*buf != ',') + break; + buf++; + } while (1); + + st_data->nr_taps = i; + +out: + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR_RW(st_taps); + +static const struct attribute *sidetone_attrs[] = { + &dev_attr_st_taps.attr, + NULL, +}; + +static const struct attribute_group sidetone_attr_group = { + .attrs = (struct attribute **)sidetone_attrs, +}; + +static int omap_st_add(struct omap_mcbsp *mcbsp, struct resource *res) +{ + struct omap_mcbsp_st_data *st_data; + int err; + + st_data = devm_kzalloc(mcbsp->dev, sizeof(*mcbsp->st_data), GFP_KERNEL); + if (!st_data) + return -ENOMEM; + + st_data->mcbsp_iclk = clk_get(mcbsp->dev, "ick"); + if (IS_ERR(st_data->mcbsp_iclk)) { + dev_warn(mcbsp->dev, + "Failed to get ick, sidetone might be broken\n"); + st_data->mcbsp_iclk = NULL; + } + + st_data->io_base_st = devm_ioremap(mcbsp->dev, res->start, + resource_size(res)); + if (!st_data->io_base_st) + return -ENOMEM; + + err = sysfs_create_group(&mcbsp->dev->kobj, &sidetone_attr_group); + if (err) + return err; + + mcbsp->st_data = st_data; + return 0; +} + +/* + * McBSP1 and McBSP3 are directly mapped on 1610 and 1510. + * 730 has only 2 McBSP, and both of them are MPU peripherals. + */ +int omap_mcbsp_init(struct platform_device *pdev) +{ + struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); + struct resource *res; + int ret = 0; + + spin_lock_init(&mcbsp->lock); + mcbsp->free = true; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + mcbsp->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mcbsp->io_base)) + return PTR_ERR(mcbsp->io_base); + + mcbsp->phys_base = res->start; + mcbsp->reg_cache_size = resource_size(res); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) + mcbsp->phys_dma_base = mcbsp->phys_base; + else + mcbsp->phys_dma_base = res->start; + + /* + * OMAP1, 2 uses two interrupt lines: TX, RX + * OMAP2430, OMAP3 SoC have combined IRQ line as well. + * OMAP4 and newer SoC only have the combined IRQ line. + * Use the combined IRQ if available since it gives better debugging + * possibilities. + */ + mcbsp->irq = platform_get_irq_byname(pdev, "common"); + if (mcbsp->irq == -ENXIO) { + mcbsp->tx_irq = platform_get_irq_byname(pdev, "tx"); + + if (mcbsp->tx_irq == -ENXIO) { + mcbsp->irq = platform_get_irq(pdev, 0); + mcbsp->tx_irq = 0; + } else { + mcbsp->rx_irq = platform_get_irq_byname(pdev, "rx"); + mcbsp->irq = 0; + } + } + + if (!pdev->dev.of_node) { + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); + if (!res) { + dev_err(&pdev->dev, "invalid tx DMA channel\n"); + return -ENODEV; + } + mcbsp->dma_req[0] = res->start; + mcbsp->dma_data[0].filter_data = &mcbsp->dma_req[0]; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); + if (!res) { + dev_err(&pdev->dev, "invalid rx DMA channel\n"); + return -ENODEV; + } + mcbsp->dma_req[1] = res->start; + mcbsp->dma_data[1].filter_data = &mcbsp->dma_req[1]; + } else { + mcbsp->dma_data[0].filter_data = "tx"; + mcbsp->dma_data[1].filter_data = "rx"; + } + + mcbsp->dma_data[0].addr = omap_mcbsp_dma_reg_params(mcbsp, 0); + mcbsp->dma_data[0].maxburst = 4; + + mcbsp->dma_data[1].addr = omap_mcbsp_dma_reg_params(mcbsp, 1); + mcbsp->dma_data[1].maxburst = 4; + + mcbsp->fclk = clk_get(&pdev->dev, "fck"); + if (IS_ERR(mcbsp->fclk)) { + ret = PTR_ERR(mcbsp->fclk); + dev_err(mcbsp->dev, "unable to get fck: %d\n", ret); + return ret; + } + + mcbsp->dma_op_mode = MCBSP_DMA_MODE_ELEMENT; + if (mcbsp->pdata->buffer_size) { + /* + * Initially configure the maximum thresholds to a safe value. + * The McBSP FIFO usage with these values should not go under + * 16 locations. + * If the whole FIFO without safety buffer is used, than there + * is a possibility that the DMA will be not able to push the + * new data on time, causing channel shifts in runtime. + */ + mcbsp->max_tx_thres = max_thres(mcbsp) - 0x10; + mcbsp->max_rx_thres = max_thres(mcbsp) - 0x10; + + ret = sysfs_create_group(&mcbsp->dev->kobj, + &additional_attr_group); + if (ret) { + dev_err(mcbsp->dev, + "Unable to create additional controls\n"); + goto err_thres; + } + } else { + mcbsp->max_tx_thres = -EINVAL; + mcbsp->max_rx_thres = -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sidetone"); + if (res) { + ret = omap_st_add(mcbsp, res); + if (ret) { + dev_err(mcbsp->dev, + "Unable to create sidetone controls\n"); + goto err_st; + } + } + + return 0; + +err_st: + if (mcbsp->pdata->buffer_size) + sysfs_remove_group(&mcbsp->dev->kobj, &additional_attr_group); +err_thres: + clk_put(mcbsp->fclk); + return ret; +} + +void omap_mcbsp_cleanup(struct omap_mcbsp *mcbsp) +{ + if (mcbsp->pdata->buffer_size) + sysfs_remove_group(&mcbsp->dev->kobj, &additional_attr_group); + + if (mcbsp->st_data) { + sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group); + clk_put(mcbsp->st_data->mcbsp_iclk); + } +} diff --git a/sound/soc/omap/mcbsp.h b/sound/soc/omap/mcbsp.h new file mode 100644 index 000000000..46ae1269a --- /dev/null +++ b/sound/soc/omap/mcbsp.h @@ -0,0 +1,358 @@ +/* + * sound/soc/omap/mcbsp.h + * + * OMAP Multi-Channel Buffered Serial Port + * + * Contact: Jarkko Nikula + * Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __ASOC_MCBSP_H +#define __ASOC_MCBSP_H + +#ifdef CONFIG_ARCH_OMAP1 +#define mcbsp_omap1() 1 +#else +#define mcbsp_omap1() 0 +#endif + +#include + +/* McBSP register numbers. Register address offset = num * reg_step */ +enum { + /* Common registers */ + OMAP_MCBSP_REG_SPCR2 = 4, + OMAP_MCBSP_REG_SPCR1, + OMAP_MCBSP_REG_RCR2, + OMAP_MCBSP_REG_RCR1, + OMAP_MCBSP_REG_XCR2, + OMAP_MCBSP_REG_XCR1, + OMAP_MCBSP_REG_SRGR2, + OMAP_MCBSP_REG_SRGR1, + OMAP_MCBSP_REG_MCR2, + OMAP_MCBSP_REG_MCR1, + OMAP_MCBSP_REG_RCERA, + OMAP_MCBSP_REG_RCERB, + OMAP_MCBSP_REG_XCERA, + OMAP_MCBSP_REG_XCERB, + OMAP_MCBSP_REG_PCR0, + OMAP_MCBSP_REG_RCERC, + OMAP_MCBSP_REG_RCERD, + OMAP_MCBSP_REG_XCERC, + OMAP_MCBSP_REG_XCERD, + OMAP_MCBSP_REG_RCERE, + OMAP_MCBSP_REG_RCERF, + OMAP_MCBSP_REG_XCERE, + OMAP_MCBSP_REG_XCERF, + OMAP_MCBSP_REG_RCERG, + OMAP_MCBSP_REG_RCERH, + OMAP_MCBSP_REG_XCERG, + OMAP_MCBSP_REG_XCERH, + + /* OMAP1-OMAP2420 registers */ + OMAP_MCBSP_REG_DRR2 = 0, + OMAP_MCBSP_REG_DRR1, + OMAP_MCBSP_REG_DXR2, + OMAP_MCBSP_REG_DXR1, + + /* OMAP2430 and onwards */ + OMAP_MCBSP_REG_DRR = 0, + OMAP_MCBSP_REG_DXR = 2, + OMAP_MCBSP_REG_SYSCON = 35, + OMAP_MCBSP_REG_THRSH2, + OMAP_MCBSP_REG_THRSH1, + OMAP_MCBSP_REG_IRQST = 40, + OMAP_MCBSP_REG_IRQEN, + OMAP_MCBSP_REG_WAKEUPEN, + OMAP_MCBSP_REG_XCCR, + OMAP_MCBSP_REG_RCCR, + OMAP_MCBSP_REG_XBUFFSTAT, + OMAP_MCBSP_REG_RBUFFSTAT, + OMAP_MCBSP_REG_SSELCR, +}; + +/* OMAP3 sidetone control registers */ +#define OMAP_ST_REG_REV 0x00 +#define OMAP_ST_REG_SYSCONFIG 0x10 +#define OMAP_ST_REG_IRQSTATUS 0x18 +#define OMAP_ST_REG_IRQENABLE 0x1C +#define OMAP_ST_REG_SGAINCR 0x24 +#define OMAP_ST_REG_SFIRCR 0x28 +#define OMAP_ST_REG_SSELCR 0x2C + +/************************** McBSP SPCR1 bit definitions ***********************/ +#define RRST BIT(0) +#define RRDY BIT(1) +#define RFULL BIT(2) +#define RSYNC_ERR BIT(3) +#define RINTM(value) (((value) & 0x3) << 4) /* bits 4:5 */ +#define ABIS BIT(6) +#define DXENA BIT(7) +#define CLKSTP(value) (((value) & 0x3) << 11) /* bits 11:12 */ +#define RJUST(value) (((value) & 0x3) << 13) /* bits 13:14 */ +#define ALB BIT(15) +#define DLB BIT(15) + +/************************** McBSP SPCR2 bit definitions ***********************/ +#define XRST BIT(0) +#define XRDY BIT(1) +#define XEMPTY BIT(2) +#define XSYNC_ERR BIT(3) +#define XINTM(value) (((value) & 0x3) << 4) /* bits 4:5 */ +#define GRST BIT(6) +#define FRST BIT(7) +#define SOFT BIT(8) +#define FREE BIT(9) + +/************************** McBSP PCR bit definitions *************************/ +#define CLKRP BIT(0) +#define CLKXP BIT(1) +#define FSRP BIT(2) +#define FSXP BIT(3) +#define DR_STAT BIT(4) +#define DX_STAT BIT(5) +#define CLKS_STAT BIT(6) +#define SCLKME BIT(7) +#define CLKRM BIT(8) +#define CLKXM BIT(9) +#define FSRM BIT(10) +#define FSXM BIT(11) +#define RIOEN BIT(12) +#define XIOEN BIT(13) +#define IDLE_EN BIT(14) + +/************************** McBSP RCR1 bit definitions ************************/ +#define RWDLEN1(value) (((value) & 0x7) << 5) /* Bits 5:7 */ +#define RFRLEN1(value) (((value) & 0x7f) << 8) /* Bits 8:14 */ + +/************************** McBSP XCR1 bit definitions ************************/ +#define XWDLEN1(value) (((value) & 0x7) << 5) /* Bits 5:7 */ +#define XFRLEN1(value) (((value) & 0x7f) << 8) /* Bits 8:14 */ + +/*************************** McBSP RCR2 bit definitions ***********************/ +#define RDATDLY(value) ((value) & 0x3) /* Bits 0:1 */ +#define RFIG BIT(2) +#define RCOMPAND(value) (((value) & 0x3) << 3) /* Bits 3:4 */ +#define RWDLEN2(value) (((value) & 0x7) << 5) /* Bits 5:7 */ +#define RFRLEN2(value) (((value) & 0x7f) << 8) /* Bits 8:14 */ +#define RPHASE BIT(15) + +/*************************** McBSP XCR2 bit definitions ***********************/ +#define XDATDLY(value) ((value) & 0x3) /* Bits 0:1 */ +#define XFIG BIT(2) +#define XCOMPAND(value) (((value) & 0x3) << 3) /* Bits 3:4 */ +#define XWDLEN2(value) (((value) & 0x7) << 5) /* Bits 5:7 */ +#define XFRLEN2(value) (((value) & 0x7f) << 8) /* Bits 8:14 */ +#define XPHASE BIT(15) + +/************************* McBSP SRGR1 bit definitions ************************/ +#define CLKGDV(value) ((value) & 0x7f) /* Bits 0:7 */ +#define FWID(value) (((value) & 0xff) << 8) /* Bits 8:15 */ + +/************************* McBSP SRGR2 bit definitions ************************/ +#define FPER(value) ((value) & 0x0fff) /* Bits 0:11 */ +#define FSGM BIT(12) +#define CLKSM BIT(13) +#define CLKSP BIT(14) +#define GSYNC BIT(15) + +/************************* McBSP MCR1 bit definitions *************************/ +#define RMCM BIT(0) +#define RCBLK(value) (((value) & 0x7) << 2) /* Bits 2:4 */ +#define RPABLK(value) (((value) & 0x3) << 5) /* Bits 5:6 */ +#define RPBBLK(value) (((value) & 0x3) << 7) /* Bits 7:8 */ + +/************************* McBSP MCR2 bit definitions *************************/ +#define XMCM(value) ((value) & 0x3) /* Bits 0:1 */ +#define XCBLK(value) (((value) & 0x7) << 2) /* Bits 2:4 */ +#define XPABLK(value) (((value) & 0x3) << 5) /* Bits 5:6 */ +#define XPBBLK(value) (((value) & 0x3) << 7) /* Bits 7:8 */ + +/*********************** McBSP XCCR bit definitions *************************/ +#define XDISABLE BIT(0) +#define XDMAEN BIT(3) +#define DILB BIT(5) +#define XFULL_CYCLE BIT(11) +#define DXENDLY(value) (((value) & 0x3) << 12) /* Bits 12:13 */ +#define PPCONNECT BIT(14) +#define EXTCLKGATE BIT(15) + +/********************** McBSP RCCR bit definitions *************************/ +#define RDISABLE BIT(0) +#define RDMAEN BIT(3) +#define RFULL_CYCLE BIT(11) + +/********************** McBSP SYSCONFIG bit definitions ********************/ +#define SOFTRST BIT(1) +#define ENAWAKEUP BIT(2) +#define SIDLEMODE(value) (((value) & 0x3) << 3) +#define CLOCKACTIVITY(value) (((value) & 0x3) << 8) + +/********************** McBSP SSELCR bit definitions ***********************/ +#define SIDETONEEN BIT(10) + +/********************** McBSP Sidetone SYSCONFIG bit definitions ***********/ +#define ST_AUTOIDLE BIT(0) + +/********************** McBSP Sidetone SGAINCR bit definitions *************/ +#define ST_CH0GAIN(value) ((value) & 0xffff) /* Bits 0:15 */ +#define ST_CH1GAIN(value) (((value) & 0xffff) << 16) /* Bits 16:31 */ + +/********************** McBSP Sidetone SFIRCR bit definitions **************/ +#define ST_FIRCOEFF(value) ((value) & 0xffff) /* Bits 0:15 */ + +/********************** McBSP Sidetone SSELCR bit definitions **************/ +#define ST_SIDETONEEN BIT(0) +#define ST_COEFFWREN BIT(1) +#define ST_COEFFWRDONE BIT(2) + +/********************** McBSP DMA operating modes **************************/ +#define MCBSP_DMA_MODE_ELEMENT 0 +#define MCBSP_DMA_MODE_THRESHOLD 1 + +/********************** McBSP WAKEUPEN/IRQST/IRQEN bit definitions *********/ +#define RSYNCERREN BIT(0) +#define RFSREN BIT(1) +#define REOFEN BIT(2) +#define RRDYEN BIT(3) +#define RUNDFLEN BIT(4) +#define ROVFLEN BIT(5) +#define XSYNCERREN BIT(7) +#define XFSXEN BIT(8) +#define XEOFEN BIT(9) +#define XRDYEN BIT(10) +#define XUNDFLEN BIT(11) +#define XOVFLEN BIT(12) +#define XEMPTYEOFEN BIT(14) + +/* Clock signal muxing options */ +#define CLKR_SRC_CLKR 0 /* CLKR signal is from the CLKR pin */ +#define CLKR_SRC_CLKX 1 /* CLKR signal is from the CLKX pin */ +#define FSR_SRC_FSR 2 /* FSR signal is from the FSR pin */ +#define FSR_SRC_FSX 3 /* FSR signal is from the FSX pin */ + +/* McBSP functional clock sources */ +#define MCBSP_CLKS_PRCM_SRC 0 +#define MCBSP_CLKS_PAD_SRC 1 + +/* we don't do multichannel for now */ +struct omap_mcbsp_reg_cfg { + u16 spcr2; + u16 spcr1; + u16 rcr2; + u16 rcr1; + u16 xcr2; + u16 xcr1; + u16 srgr2; + u16 srgr1; + u16 mcr2; + u16 mcr1; + u16 pcr0; + u16 rcerc; + u16 rcerd; + u16 xcerc; + u16 xcerd; + u16 rcere; + u16 rcerf; + u16 xcere; + u16 xcerf; + u16 rcerg; + u16 rcerh; + u16 xcerg; + u16 xcerh; + u16 xccr; + u16 rccr; +}; + +struct omap_mcbsp_st_data { + void __iomem *io_base_st; + struct clk *mcbsp_iclk; + bool running; + bool enabled; + s16 taps[128]; /* Sidetone filter coefficients */ + int nr_taps; /* Number of filter coefficients in use */ + s16 ch0gain; + s16 ch1gain; +}; + +struct omap_mcbsp { + struct device *dev; + struct clk *fclk; + spinlock_t lock; + unsigned long phys_base; + unsigned long phys_dma_base; + void __iomem *io_base; + u8 id; + /* + * Flags indicating is the bus already activated and configured by + * another substream + */ + int active; + int configured; + u8 free; + + int irq; + int rx_irq; + int tx_irq; + + /* Protect the field .free, while checking if the mcbsp is in use */ + struct omap_mcbsp_platform_data *pdata; + struct omap_mcbsp_st_data *st_data; + struct omap_mcbsp_reg_cfg cfg_regs; + struct snd_dmaengine_dai_dma_data dma_data[2]; + unsigned int dma_req[2]; + int dma_op_mode; + u16 max_tx_thres; + u16 max_rx_thres; + void *reg_cache; + int reg_cache_size; + + unsigned int fmt; + unsigned int in_freq; + unsigned int latency[2]; + int clk_div; + int wlen; + + struct pm_qos_request pm_qos_req; +}; + +void omap_mcbsp_config(struct omap_mcbsp *mcbsp, + const struct omap_mcbsp_reg_cfg *config); +void omap_mcbsp_set_tx_threshold(struct omap_mcbsp *mcbsp, u16 threshold); +void omap_mcbsp_set_rx_threshold(struct omap_mcbsp *mcbsp, u16 threshold); +u16 omap_mcbsp_get_tx_delay(struct omap_mcbsp *mcbsp); +u16 omap_mcbsp_get_rx_delay(struct omap_mcbsp *mcbsp); +int omap_mcbsp_get_dma_op_mode(struct omap_mcbsp *mcbsp); +int omap_mcbsp_request(struct omap_mcbsp *mcbsp); +void omap_mcbsp_free(struct omap_mcbsp *mcbsp); +void omap_mcbsp_start(struct omap_mcbsp *mcbsp, int tx, int rx); +void omap_mcbsp_stop(struct omap_mcbsp *mcbsp, int tx, int rx); + +/* McBSP functional clock source changing function */ +int omap2_mcbsp_set_clks_src(struct omap_mcbsp *mcbsp, u8 fck_src_id); + +/* Sidetone specific API */ +int omap_st_set_chgain(struct omap_mcbsp *mcbsp, int channel, s16 chgain); +int omap_st_get_chgain(struct omap_mcbsp *mcbsp, int channel, s16 *chgain); +int omap_st_enable(struct omap_mcbsp *mcbsp); +int omap_st_disable(struct omap_mcbsp *mcbsp); +int omap_st_is_enabled(struct omap_mcbsp *mcbsp); + +int omap_mcbsp_init(struct platform_device *pdev); +void omap_mcbsp_cleanup(struct omap_mcbsp *mcbsp); + +#endif /* __ASOC_MCBSP_H */ diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c new file mode 100644 index 000000000..9cfefe44a --- /dev/null +++ b/sound/soc/omap/n810.c @@ -0,0 +1,378 @@ +/* + * n810.c -- SoC audio for Nokia N810 + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Jarkko Nikula + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-mcbsp.h" + +#define N810_HEADSET_AMP_GPIO 10 +#define N810_SPEAKER_AMP_GPIO 101 + +enum { + N810_JACK_DISABLED, + N810_JACK_HP, + N810_JACK_HS, + N810_JACK_MIC, +}; + +static struct clk *sys_clkout2; +static struct clk *sys_clkout2_src; +static struct clk *func96m_clk; + +static int n810_spk_func; +static int n810_jack_func; +static int n810_dmic_func; + +static void n810_ext_control(struct snd_soc_dapm_context *dapm) +{ + int hp = 0, line1l = 0; + + switch (n810_jack_func) { + case N810_JACK_HS: + line1l = 1; + case N810_JACK_HP: + hp = 1; + break; + case N810_JACK_MIC: + line1l = 1; + break; + } + + snd_soc_dapm_mutex_lock(dapm); + + if (n810_spk_func) + snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk"); + + if (hp) + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + if (line1l) + snd_soc_dapm_enable_pin_unlocked(dapm, "HS Mic"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "HS Mic"); + + if (n810_dmic_func) + snd_soc_dapm_enable_pin_unlocked(dapm, "DMic"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "DMic"); + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +static int n810_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2); + + n810_ext_control(&rtd->card->dapm); + return clk_prepare_enable(sys_clkout2); +} + +static void n810_shutdown(struct snd_pcm_substream *substream) +{ + clk_disable_unprepare(sys_clkout2); +} + +static int n810_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int err; + + /* Set the codec system clock for DAC and ADC */ + err = snd_soc_dai_set_sysclk(codec_dai, 0, 12000000, + SND_SOC_CLOCK_IN); + + return err; +} + +static const struct snd_soc_ops n810_ops = { + .startup = n810_startup, + .hw_params = n810_hw_params, + .shutdown = n810_shutdown, +}; + +static int n810_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = n810_spk_func; + + return 0; +} + +static int n810_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (n810_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + n810_spk_func = ucontrol->value.enumerated.item[0]; + n810_ext_control(&card->dapm); + + return 1; +} + +static int n810_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = n810_jack_func; + + return 0; +} + +static int n810_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (n810_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + n810_jack_func = ucontrol->value.enumerated.item[0]; + n810_ext_control(&card->dapm); + + return 1; +} + +static int n810_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = n810_dmic_func; + + return 0; +} + +static int n810_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (n810_dmic_func == ucontrol->value.enumerated.item[0]) + return 0; + + n810_dmic_func = ucontrol->value.enumerated.item[0]; + n810_ext_control(&card->dapm); + + return 1; +} + +static int n810_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(N810_SPEAKER_AMP_GPIO, 1); + else + gpio_set_value(N810_SPEAKER_AMP_GPIO, 0); + + return 0; +} + +static int n810_jack_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(N810_HEADSET_AMP_GPIO, 1); + else + gpio_set_value(N810_HEADSET_AMP_GPIO, 0); + + return 0; +} + +static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event), + SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event), + SND_SOC_DAPM_MIC("DMic", NULL), + SND_SOC_DAPM_MIC("HS Mic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "HPLOUT"}, + {"Headphone Jack", NULL, "HPROUT"}, + + {"Ext Spk", NULL, "LLOUT"}, + {"Ext Spk", NULL, "RLOUT"}, + + {"DMic Rate 64", NULL, "DMic"}, + {"DMic", NULL, "Mic Bias"}, + + /* + * Note that the mic bias is coming from Retu/Vilma and we don't have + * control over it atm. The analog HS mic is not working. <- TODO + */ + {"LINE1L", NULL, "HS Mic"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *jack_function[] = {"Off", "Headphone", "Headset", "Mic"}; +static const char *input_function[] = {"ADC", "Digital Mic"}; +static const struct soc_enum n810_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), +}; + +static const struct snd_kcontrol_new aic33_n810_controls[] = { + SOC_ENUM_EXT("Speaker Function", n810_enum[0], + n810_get_spk, n810_set_spk), + SOC_ENUM_EXT("Jack Function", n810_enum[1], + n810_get_jack, n810_set_jack), + SOC_ENUM_EXT("Input Select", n810_enum[2], + n810_get_input, n810_set_input), +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link n810_dai = { + .name = "TLV320AIC33", + .stream_name = "AIC33", + .cpu_dai_name = "48076000.mcbsp", + .platform_name = "48076000.mcbsp", + .codec_name = "tlv320aic3x-codec.1-0018", + .codec_dai_name = "tlv320aic3x-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &n810_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_n810 = { + .name = "N810", + .owner = THIS_MODULE, + .dai_link = &n810_dai, + .num_links = 1, + + .controls = aic33_n810_controls, + .num_controls = ARRAY_SIZE(aic33_n810_controls), + .dapm_widgets = aic33_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aic33_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, +}; + +static struct platform_device *n810_snd_device; + +static int __init n810_soc_init(void) +{ + int err; + struct device *dev; + + if (!of_have_populated_dt() || + (!of_machine_is_compatible("nokia,n810") && + !of_machine_is_compatible("nokia,n810-wimax"))) + return -ENODEV; + + n810_snd_device = platform_device_alloc("soc-audio", -1); + if (!n810_snd_device) + return -ENOMEM; + + platform_set_drvdata(n810_snd_device, &snd_soc_n810); + err = platform_device_add(n810_snd_device); + if (err) + goto err1; + + dev = &n810_snd_device->dev; + + sys_clkout2_src = clk_get(dev, "sys_clkout2_src"); + if (IS_ERR(sys_clkout2_src)) { + dev_err(dev, "Could not get sys_clkout2_src clock\n"); + err = PTR_ERR(sys_clkout2_src); + goto err2; + } + sys_clkout2 = clk_get(dev, "sys_clkout2"); + if (IS_ERR(sys_clkout2)) { + dev_err(dev, "Could not get sys_clkout2\n"); + err = PTR_ERR(sys_clkout2); + goto err3; + } + /* + * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use + * 96 MHz as its parent in order to get 12 MHz + */ + func96m_clk = clk_get(dev, "func_96m_ck"); + if (IS_ERR(func96m_clk)) { + dev_err(dev, "Could not get func 96M clock\n"); + err = PTR_ERR(func96m_clk); + goto err4; + } + clk_set_parent(sys_clkout2_src, func96m_clk); + clk_set_rate(sys_clkout2, 12000000); + + if (WARN_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) || + (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0))) { + err = -EINVAL; + goto err4; + } + + gpio_direction_output(N810_HEADSET_AMP_GPIO, 0); + gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0); + + return 0; +err4: + clk_put(sys_clkout2); +err3: + clk_put(sys_clkout2_src); +err2: + platform_device_del(n810_snd_device); +err1: + platform_device_put(n810_snd_device); + + return err; +} + +static void __exit n810_soc_exit(void) +{ + gpio_free(N810_SPEAKER_AMP_GPIO); + gpio_free(N810_HEADSET_AMP_GPIO); + clk_put(sys_clkout2_src); + clk_put(sys_clkout2); + clk_put(func96m_clk); + + platform_device_unregister(n810_snd_device); +} + +module_init(n810_soc_init); +module_exit(n810_soc_exit); + +MODULE_AUTHOR("Jarkko Nikula "); +MODULE_DESCRIPTION("ALSA SoC Nokia N810"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/omap-abe-twl6040.c b/sound/soc/omap/omap-abe-twl6040.c new file mode 100644 index 000000000..fed45b41f --- /dev/null +++ b/sound/soc/omap/omap-abe-twl6040.c @@ -0,0 +1,353 @@ +/* + * omap-abe-twl6040.c -- SoC audio for TI OMAP based boards with ABE and + * twl6040 codec + * + * Author: Misael Lopez Cruz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-dmic.h" +#include "omap-mcpdm.h" +#include "../codecs/twl6040.h" + +struct abe_twl6040 { + struct snd_soc_card card; + struct snd_soc_dai_link dai_links[2]; + int jack_detection; /* board can detect jack events */ + int mclk_freq; /* MCLK frequency speed for twl6040 */ +}; + +static struct platform_device *dmic_codec_dev; + +static int omap_abe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card); + int clk_id, freq; + int ret; + + clk_id = twl6040_get_clk_id(codec_dai->component); + if (clk_id == TWL6040_SYSCLK_SEL_HPPLL) + freq = priv->mclk_freq; + else if (clk_id == TWL6040_SYSCLK_SEL_LPPLL) + freq = 32768; + else + return -EINVAL; + + /* set the codec mclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq, + SND_SOC_CLOCK_IN); + if (ret) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + return ret; +} + +static const struct snd_soc_ops omap_abe_ops = { + .hw_params = omap_abe_hw_params, +}; + +static int omap_abe_dmic_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_SYSCLK_PAD_CLKS, + 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set DMIC cpu system clock\n"); + return ret; + } + ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_DMIC_ABE_DMIC_CLK, 2400000, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + printk(KERN_ERR "can't set DMIC output clock\n"); + return ret; + } + return 0; +} + +static struct snd_soc_ops omap_abe_dmic_ops = { + .hw_params = omap_abe_dmic_hw_params, +}; + +/* Headset jack */ +static struct snd_soc_jack hs_jack; + +/*Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headset Stereophone", + .mask = SND_JACK_HEADPHONE, + }, +}; + +/* SDP4430 machine DAPM */ +static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { + /* Outputs */ + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_SPK("Earphone Spk", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_SPK("Vibrator", NULL), + + /* Inputs */ + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Main Handset Mic", NULL), + SND_SOC_DAPM_MIC("Sub Handset Mic", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + + /* Digital microphones */ + SND_SOC_DAPM_MIC("Digital Mic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Routings for outputs */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, + + {"Earphone Spk", NULL, "EP"}, + + {"Ext Spk", NULL, "HFL"}, + {"Ext Spk", NULL, "HFR"}, + + {"Line Out", NULL, "AUXL"}, + {"Line Out", NULL, "AUXR"}, + + {"Vibrator", NULL, "VIBRAL"}, + {"Vibrator", NULL, "VIBRAR"}, + + /* Routings for inputs */ + {"HSMIC", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "Headset Mic Bias"}, + + {"MAINMIC", NULL, "Main Handset Mic"}, + {"Main Handset Mic", NULL, "Main Mic Bias"}, + + {"SUBMIC", NULL, "Sub Handset Mic"}, + {"Sub Handset Mic", NULL, "Main Mic Bias"}, + + {"AFML", NULL, "Line In"}, + {"AFMR", NULL, "Line In"}, +}; + +static int omap_abe_twl6040_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + struct snd_soc_card *card = rtd->card; + struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card); + int hs_trim; + int ret = 0; + + /* + * Configure McPDM offset cancellation based on the HSOTRIM value from + * twl6040. + */ + hs_trim = twl6040_get_trim_value(component, TWL6040_TRIM_HSOTRIM); + omap_mcpdm_configure_dn_offsets(rtd, TWL6040_HSF_TRIM_LEFT(hs_trim), + TWL6040_HSF_TRIM_RIGHT(hs_trim)); + + /* Headset jack detection only if it is supported */ + if (priv->jack_detection) { + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET, &hs_jack, + hs_jack_pins, + ARRAY_SIZE(hs_jack_pins)); + if (ret) + return ret; + + twl6040_hs_jack_detect(component, &hs_jack, SND_JACK_HEADSET); + } + + return 0; +} + +static const struct snd_soc_dapm_route dmic_audio_map[] = { + {"DMic", NULL, "Digital Mic"}, + {"Digital Mic", NULL, "Digital Mic1 Bias"}, +}; + +static int omap_abe_dmic_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + + return snd_soc_dapm_add_routes(dapm, dmic_audio_map, + ARRAY_SIZE(dmic_audio_map)); +} + +static int omap_abe_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct snd_soc_card *card; + struct device_node *dai_node; + struct abe_twl6040 *priv; + int num_links = 0; + int ret = 0; + + if (!node) { + dev_err(&pdev->dev, "of node is missing.\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(struct abe_twl6040), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + card = &priv->card; + card->dev = &pdev->dev; + card->owner = THIS_MODULE; + card->dapm_widgets = twl6040_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(twl6040_dapm_widgets); + card->dapm_routes = audio_map; + card->num_dapm_routes = ARRAY_SIZE(audio_map); + + if (snd_soc_of_parse_card_name(card, "ti,model")) { + dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); + if (ret) { + dev_err(&pdev->dev, "Error while parsing DAPM routing\n"); + return ret; + } + + dai_node = of_parse_phandle(node, "ti,mcpdm", 0); + if (!dai_node) { + dev_err(&pdev->dev, "McPDM node is not provided\n"); + return -EINVAL; + } + + priv->dai_links[0].name = "DMIC"; + priv->dai_links[0].stream_name = "TWL6040"; + priv->dai_links[0].cpu_of_node = dai_node; + priv->dai_links[0].platform_of_node = dai_node; + priv->dai_links[0].codec_dai_name = "twl6040-legacy"; + priv->dai_links[0].codec_name = "twl6040-codec"; + priv->dai_links[0].init = omap_abe_twl6040_init; + priv->dai_links[0].ops = &omap_abe_ops; + + dai_node = of_parse_phandle(node, "ti,dmic", 0); + if (dai_node) { + num_links = 2; + priv->dai_links[1].name = "TWL6040"; + priv->dai_links[1].stream_name = "DMIC Capture"; + priv->dai_links[1].cpu_of_node = dai_node; + priv->dai_links[1].platform_of_node = dai_node; + priv->dai_links[1].codec_dai_name = "dmic-hifi"; + priv->dai_links[1].codec_name = "dmic-codec"; + priv->dai_links[1].init = omap_abe_dmic_init; + priv->dai_links[1].ops = &omap_abe_dmic_ops; + } else { + num_links = 1; + } + + priv->jack_detection = of_property_read_bool(node, "ti,jack-detection"); + of_property_read_u32(node, "ti,mclk-freq", &priv->mclk_freq); + if (!priv->mclk_freq) { + dev_err(&pdev->dev, "MCLK frequency not provided\n"); + return -EINVAL; + } + + card->fully_routed = 1; + + if (!priv->mclk_freq) { + dev_err(&pdev->dev, "MCLK frequency missing\n"); + return -ENODEV; + } + + card->dai_link = priv->dai_links; + card->num_links = num_links; + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static const struct of_device_id omap_abe_of_match[] = { + {.compatible = "ti,abe-twl6040", }, + { }, +}; +MODULE_DEVICE_TABLE(of, omap_abe_of_match); + +static struct platform_driver omap_abe_driver = { + .driver = { + .name = "omap-abe-twl6040", + .pm = &snd_soc_pm_ops, + .of_match_table = omap_abe_of_match, + }, + .probe = omap_abe_probe, +}; + +static int __init omap_abe_init(void) +{ + int ret; + + dmic_codec_dev = platform_device_register_simple("dmic-codec", -1, NULL, + 0); + if (IS_ERR(dmic_codec_dev)) { + pr_err("%s: dmic-codec device registration failed\n", __func__); + return PTR_ERR(dmic_codec_dev); + } + + ret = platform_driver_register(&omap_abe_driver); + if (ret) { + pr_err("%s: platform driver registration failed\n", __func__); + platform_device_unregister(dmic_codec_dev); + } + + return ret; +} +module_init(omap_abe_init); + +static void __exit omap_abe_exit(void) +{ + platform_driver_unregister(&omap_abe_driver); + platform_device_unregister(dmic_codec_dev); +} +module_exit(omap_abe_exit); + +MODULE_AUTHOR("Misael Lopez Cruz "); +MODULE_DESCRIPTION("ALSA SoC for OMAP boards with ABE and twl6040 codec"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:omap-abe-twl6040"); diff --git a/sound/soc/omap/omap-dmic.c b/sound/soc/omap/omap-dmic.c new file mode 100644 index 000000000..cba9645b6 --- /dev/null +++ b/sound/soc/omap/omap-dmic.c @@ -0,0 +1,541 @@ +/* + * omap-dmic.c -- OMAP ASoC DMIC DAI driver + * + * Copyright (C) 2010 - 2011 Texas Instruments + * + * Author: David Lambert + * Misael Lopez Cruz + * Liam Girdwood + * Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "omap-dmic.h" +#include "sdma-pcm.h" + +struct omap_dmic { + struct device *dev; + void __iomem *io_base; + struct clk *fclk; + struct pm_qos_request pm_qos_req; + int latency; + int fclk_freq; + int out_freq; + int clk_div; + int sysclk; + int threshold; + u32 ch_enabled; + bool active; + struct mutex mutex; + + struct snd_dmaengine_dai_dma_data dma_data; +}; + +static inline void omap_dmic_write(struct omap_dmic *dmic, u16 reg, u32 val) +{ + writel_relaxed(val, dmic->io_base + reg); +} + +static inline int omap_dmic_read(struct omap_dmic *dmic, u16 reg) +{ + return readl_relaxed(dmic->io_base + reg); +} + +static inline void omap_dmic_start(struct omap_dmic *dmic) +{ + u32 ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG); + + /* Configure DMA controller */ + omap_dmic_write(dmic, OMAP_DMIC_DMAENABLE_SET_REG, + OMAP_DMIC_DMA_ENABLE); + + omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, ctrl | dmic->ch_enabled); +} + +static inline void omap_dmic_stop(struct omap_dmic *dmic) +{ + u32 ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG); + omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, + ctrl & ~OMAP_DMIC_UP_ENABLE_MASK); + + /* Disable DMA request generation */ + omap_dmic_write(dmic, OMAP_DMIC_DMAENABLE_CLR_REG, + OMAP_DMIC_DMA_ENABLE); + +} + +static inline int dmic_is_enabled(struct omap_dmic *dmic) +{ + return omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG) & + OMAP_DMIC_UP_ENABLE_MASK; +} + +static int omap_dmic_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + mutex_lock(&dmic->mutex); + + if (!dai->active) + dmic->active = 1; + else + ret = -EBUSY; + + mutex_unlock(&dmic->mutex); + + return ret; +} + +static void omap_dmic_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + + mutex_lock(&dmic->mutex); + + pm_qos_remove_request(&dmic->pm_qos_req); + + if (!dai->active) + dmic->active = 0; + + mutex_unlock(&dmic->mutex); +} + +static int omap_dmic_select_divider(struct omap_dmic *dmic, int sample_rate) +{ + int divider = -EINVAL; + + /* + * 192KHz rate is only supported with 19.2MHz/3.84MHz clock + * configuration. + */ + if (sample_rate == 192000) { + if (dmic->fclk_freq == 19200000 && dmic->out_freq == 3840000) + divider = 0x6; /* Divider: 5 (192KHz sampling rate) */ + else + dev_err(dmic->dev, + "invalid clock configuration for 192KHz\n"); + + return divider; + } + + switch (dmic->out_freq) { + case 1536000: + if (dmic->fclk_freq != 24576000) + goto div_err; + divider = 0x4; /* Divider: 16 */ + break; + case 2400000: + switch (dmic->fclk_freq) { + case 12000000: + divider = 0x5; /* Divider: 5 */ + break; + case 19200000: + divider = 0x0; /* Divider: 8 */ + break; + case 24000000: + divider = 0x2; /* Divider: 10 */ + break; + default: + goto div_err; + } + break; + case 3072000: + if (dmic->fclk_freq != 24576000) + goto div_err; + divider = 0x3; /* Divider: 8 */ + break; + case 3840000: + if (dmic->fclk_freq != 19200000) + goto div_err; + divider = 0x1; /* Divider: 5 (96KHz sampling rate) */ + break; + default: + dev_err(dmic->dev, "invalid out frequency: %dHz\n", + dmic->out_freq); + break; + } + + return divider; + +div_err: + dev_err(dmic->dev, "invalid out frequency %dHz for %dHz input\n", + dmic->out_freq, dmic->fclk_freq); + return -EINVAL; +} + +static int omap_dmic_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + struct snd_dmaengine_dai_dma_data *dma_data; + int channels; + + dmic->clk_div = omap_dmic_select_divider(dmic, params_rate(params)); + if (dmic->clk_div < 0) { + dev_err(dmic->dev, "no valid divider for %dHz from %dHz\n", + dmic->out_freq, dmic->fclk_freq); + return -EINVAL; + } + + dmic->ch_enabled = 0; + channels = params_channels(params); + switch (channels) { + case 6: + dmic->ch_enabled |= OMAP_DMIC_UP3_ENABLE; + /* fall through */ + case 4: + dmic->ch_enabled |= OMAP_DMIC_UP2_ENABLE; + /* fall through */ + case 2: + dmic->ch_enabled |= OMAP_DMIC_UP1_ENABLE; + break; + default: + dev_err(dmic->dev, "invalid number of legacy channels\n"); + return -EINVAL; + } + + /* packet size is threshold * channels */ + dma_data = snd_soc_dai_get_dma_data(dai, substream); + dma_data->maxburst = dmic->threshold * channels; + dmic->latency = (OMAP_DMIC_THRES_MAX - dmic->threshold) * USEC_PER_SEC / + params_rate(params); + + return 0; +} + +static int omap_dmic_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + u32 ctrl; + + if (pm_qos_request_active(&dmic->pm_qos_req)) + pm_qos_update_request(&dmic->pm_qos_req, dmic->latency); + + /* Configure uplink threshold */ + omap_dmic_write(dmic, OMAP_DMIC_FIFO_CTRL_REG, dmic->threshold); + + ctrl = omap_dmic_read(dmic, OMAP_DMIC_CTRL_REG); + + /* Set dmic out format */ + ctrl &= ~(OMAP_DMIC_FORMAT | OMAP_DMIC_POLAR_MASK); + ctrl |= (OMAP_DMICOUTFORMAT_LJUST | OMAP_DMIC_POLAR1 | + OMAP_DMIC_POLAR2 | OMAP_DMIC_POLAR3); + + /* Configure dmic clock divider */ + ctrl &= ~OMAP_DMIC_CLK_DIV_MASK; + ctrl |= OMAP_DMIC_CLK_DIV(dmic->clk_div); + + omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, ctrl); + + omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, + ctrl | OMAP_DMICOUTFORMAT_LJUST | OMAP_DMIC_POLAR1 | + OMAP_DMIC_POLAR2 | OMAP_DMIC_POLAR3); + + return 0; +} + +static int omap_dmic_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + omap_dmic_start(dmic); + break; + case SNDRV_PCM_TRIGGER_STOP: + omap_dmic_stop(dmic); + break; + default: + break; + } + + return 0; +} + +static int omap_dmic_select_fclk(struct omap_dmic *dmic, int clk_id, + unsigned int freq) +{ + struct clk *parent_clk, *mux; + char *parent_clk_name; + int ret = 0; + + switch (freq) { + case 12000000: + case 19200000: + case 24000000: + case 24576000: + break; + default: + dev_err(dmic->dev, "invalid input frequency: %dHz\n", freq); + dmic->fclk_freq = 0; + return -EINVAL; + } + + if (dmic->sysclk == clk_id) { + dmic->fclk_freq = freq; + return 0; + } + + /* re-parent not allowed if a stream is ongoing */ + if (dmic->active && dmic_is_enabled(dmic)) { + dev_err(dmic->dev, "can't re-parent when DMIC active\n"); + return -EBUSY; + } + + switch (clk_id) { + case OMAP_DMIC_SYSCLK_PAD_CLKS: + parent_clk_name = "pad_clks_ck"; + break; + case OMAP_DMIC_SYSCLK_SLIMBLUS_CLKS: + parent_clk_name = "slimbus_clk"; + break; + case OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS: + parent_clk_name = "dmic_sync_mux_ck"; + break; + default: + dev_err(dmic->dev, "fclk clk_id (%d) not supported\n", clk_id); + return -EINVAL; + } + + parent_clk = clk_get(dmic->dev, parent_clk_name); + if (IS_ERR(parent_clk)) { + dev_err(dmic->dev, "can't get %s\n", parent_clk_name); + return -ENODEV; + } + + mux = clk_get_parent(dmic->fclk); + if (IS_ERR(mux)) { + dev_err(dmic->dev, "can't get fck mux parent\n"); + clk_put(parent_clk); + return -ENODEV; + } + + mutex_lock(&dmic->mutex); + if (dmic->active) { + /* disable clock while reparenting */ + pm_runtime_put_sync(dmic->dev); + ret = clk_set_parent(mux, parent_clk); + pm_runtime_get_sync(dmic->dev); + } else { + ret = clk_set_parent(mux, parent_clk); + } + mutex_unlock(&dmic->mutex); + + if (ret < 0) { + dev_err(dmic->dev, "re-parent failed\n"); + goto err_busy; + } + + dmic->sysclk = clk_id; + dmic->fclk_freq = freq; + +err_busy: + clk_put(mux); + clk_put(parent_clk); + + return ret; +} + +static int omap_dmic_select_outclk(struct omap_dmic *dmic, int clk_id, + unsigned int freq) +{ + int ret = 0; + + if (clk_id != OMAP_DMIC_ABE_DMIC_CLK) { + dev_err(dmic->dev, "output clk_id (%d) not supported\n", + clk_id); + return -EINVAL; + } + + switch (freq) { + case 1536000: + case 2400000: + case 3072000: + case 3840000: + dmic->out_freq = freq; + break; + default: + dev_err(dmic->dev, "invalid out frequency: %dHz\n", freq); + dmic->out_freq = 0; + ret = -EINVAL; + } + + return ret; +} + +static int omap_dmic_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + + if (dir == SND_SOC_CLOCK_IN) + return omap_dmic_select_fclk(dmic, clk_id, freq); + else if (dir == SND_SOC_CLOCK_OUT) + return omap_dmic_select_outclk(dmic, clk_id, freq); + + dev_err(dmic->dev, "invalid clock direction (%d)\n", dir); + return -EINVAL; +} + +static const struct snd_soc_dai_ops omap_dmic_dai_ops = { + .startup = omap_dmic_dai_startup, + .shutdown = omap_dmic_dai_shutdown, + .hw_params = omap_dmic_dai_hw_params, + .prepare = omap_dmic_dai_prepare, + .trigger = omap_dmic_dai_trigger, + .set_sysclk = omap_dmic_set_dai_sysclk, +}; + +static int omap_dmic_probe(struct snd_soc_dai *dai) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + + pm_runtime_enable(dmic->dev); + + /* Disable lines while request is ongoing */ + pm_runtime_get_sync(dmic->dev); + omap_dmic_write(dmic, OMAP_DMIC_CTRL_REG, 0x00); + pm_runtime_put_sync(dmic->dev); + + /* Configure DMIC threshold value */ + dmic->threshold = OMAP_DMIC_THRES_MAX - 3; + + snd_soc_dai_init_dma_data(dai, NULL, &dmic->dma_data); + + return 0; +} + +static int omap_dmic_remove(struct snd_soc_dai *dai) +{ + struct omap_dmic *dmic = snd_soc_dai_get_drvdata(dai); + + pm_runtime_disable(dmic->dev); + + return 0; +} + +static struct snd_soc_dai_driver omap_dmic_dai = { + .name = "omap-dmic", + .probe = omap_dmic_probe, + .remove = omap_dmic_remove, + .capture = { + .channels_min = 2, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 24, + }, + .ops = &omap_dmic_dai_ops, +}; + +static const struct snd_soc_component_driver omap_dmic_component = { + .name = "omap-dmic", +}; + +static int asoc_dmic_probe(struct platform_device *pdev) +{ + struct omap_dmic *dmic; + struct resource *res; + int ret; + + dmic = devm_kzalloc(&pdev->dev, sizeof(struct omap_dmic), GFP_KERNEL); + if (!dmic) + return -ENOMEM; + + platform_set_drvdata(pdev, dmic); + dmic->dev = &pdev->dev; + dmic->sysclk = OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS; + + mutex_init(&dmic->mutex); + + dmic->fclk = devm_clk_get(dmic->dev, "fck"); + if (IS_ERR(dmic->fclk)) { + dev_err(dmic->dev, "cant get fck\n"); + return -ENODEV; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) { + dev_err(dmic->dev, "invalid dma memory resource\n"); + return -ENODEV; + } + dmic->dma_data.addr = res->start + OMAP_DMIC_DATA_REG; + + dmic->dma_data.filter_data = "up_link"; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); + dmic->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dmic->io_base)) + return PTR_ERR(dmic->io_base); + + + ret = devm_snd_soc_register_component(&pdev->dev, + &omap_dmic_component, + &omap_dmic_dai, 1); + if (ret) + return ret; + + ret = sdma_pcm_platform_register(&pdev->dev, NULL, "up_link"); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id omap_dmic_of_match[] = { + { .compatible = "ti,omap4-dmic", }, + { } +}; +MODULE_DEVICE_TABLE(of, omap_dmic_of_match); + +static struct platform_driver asoc_dmic_driver = { + .driver = { + .name = "omap-dmic", + .of_match_table = omap_dmic_of_match, + }, + .probe = asoc_dmic_probe, +}; + +module_platform_driver(asoc_dmic_driver); + +MODULE_ALIAS("platform:omap-dmic"); +MODULE_AUTHOR("Peter Ujfalusi "); +MODULE_DESCRIPTION("OMAP DMIC ASoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/omap-dmic.h b/sound/soc/omap/omap-dmic.h new file mode 100644 index 000000000..231e728bf --- /dev/null +++ b/sound/soc/omap/omap-dmic.h @@ -0,0 +1,69 @@ +/* + * omap-dmic.h -- OMAP Digital Microphone Controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _OMAP_DMIC_H +#define _OMAP_DMIC_H + +#define OMAP_DMIC_REVISION_REG 0x00 +#define OMAP_DMIC_SYSCONFIG_REG 0x10 +#define OMAP_DMIC_IRQSTATUS_RAW_REG 0x24 +#define OMAP_DMIC_IRQSTATUS_REG 0x28 +#define OMAP_DMIC_IRQENABLE_SET_REG 0x2C +#define OMAP_DMIC_IRQENABLE_CLR_REG 0x30 +#define OMAP_DMIC_IRQWAKE_EN_REG 0x34 +#define OMAP_DMIC_DMAENABLE_SET_REG 0x38 +#define OMAP_DMIC_DMAENABLE_CLR_REG 0x3C +#define OMAP_DMIC_DMAWAKEEN_REG 0x40 +#define OMAP_DMIC_CTRL_REG 0x44 +#define OMAP_DMIC_DATA_REG 0x48 +#define OMAP_DMIC_FIFO_CTRL_REG 0x4C +#define OMAP_DMIC_FIFO_DMIC1R_DATA_REG 0x50 +#define OMAP_DMIC_FIFO_DMIC1L_DATA_REG 0x54 +#define OMAP_DMIC_FIFO_DMIC2R_DATA_REG 0x58 +#define OMAP_DMIC_FIFO_DMIC2L_DATA_REG 0x5C +#define OMAP_DMIC_FIFO_DMIC3R_DATA_REG 0x60 +#define OMAP_DMIC_FIFO_DMIC3L_DATA_REG 0x64 + +/* IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR bit fields */ +#define OMAP_DMIC_IRQ (1 << 0) +#define OMAP_DMIC_IRQ_FULL (1 << 1) +#define OMAP_DMIC_IRQ_ALMST_EMPTY (1 << 2) +#define OMAP_DMIC_IRQ_EMPTY (1 << 3) +#define OMAP_DMIC_IRQ_MASK 0x07 + +/* DMIC_DMAENABLE bit fields */ +#define OMAP_DMIC_DMA_ENABLE 0x1 + +/* DMIC_CTRL bit fields */ +#define OMAP_DMIC_UP1_ENABLE (1 << 0) +#define OMAP_DMIC_UP2_ENABLE (1 << 1) +#define OMAP_DMIC_UP3_ENABLE (1 << 2) +#define OMAP_DMIC_UP_ENABLE_MASK 0x7 +#define OMAP_DMIC_FORMAT (1 << 3) +#define OMAP_DMIC_POLAR1 (1 << 4) +#define OMAP_DMIC_POLAR2 (1 << 5) +#define OMAP_DMIC_POLAR3 (1 << 6) +#define OMAP_DMIC_POLAR_MASK (0x7 << 4) +#define OMAP_DMIC_CLK_DIV(x) (((x) & 0x7) << 7) +#define OMAP_DMIC_CLK_DIV_MASK (0x7 << 7) +#define OMAP_DMIC_RESET (1 << 10) + +#define OMAP_DMICOUTFORMAT_LJUST (0 << 3) +#define OMAP_DMICOUTFORMAT_RJUST (1 << 3) + +/* DMIC_FIFO_CTRL bit fields */ +#define OMAP_DMIC_THRES_MAX 0xF + +enum omap_dmic_clk { + OMAP_DMIC_SYSCLK_PAD_CLKS, /* PAD_CLKS */ + OMAP_DMIC_SYSCLK_SLIMBLUS_CLKS, /* SLIMBUS_CLK */ + OMAP_DMIC_SYSCLK_SYNC_MUX_CLKS, /* DMIC_SYNC_MUX_CLK */ + OMAP_DMIC_ABE_DMIC_CLK, /* abe_dmic_clk */ +}; + +#endif diff --git a/sound/soc/omap/omap-hdmi-audio.c b/sound/soc/omap/omap-hdmi-audio.c new file mode 100644 index 000000000..8a99a8837 --- /dev/null +++ b/sound/soc/omap/omap-hdmi-audio.c @@ -0,0 +1,420 @@ +/* + * omap-hdmi-audio.c -- OMAP4+ DSS HDMI audio support library + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Jyri Sarha + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdma-pcm.h" + +#define DRV_NAME "omap-hdmi-audio" + +struct hdmi_audio_data { + struct snd_soc_card *card; + + const struct omap_hdmi_audio_ops *ops; + struct device *dssdev; + struct snd_dmaengine_dai_dma_data dma_data; + struct omap_dss_audio dss_audio; + struct snd_aes_iec958 iec; + struct snd_cea_861_aud_if cea; + + struct mutex current_stream_lock; + struct snd_pcm_substream *current_stream; +}; + +static +struct hdmi_audio_data *card_drvdata_substream(struct snd_pcm_substream *ss) +{ + struct snd_soc_pcm_runtime *rtd = ss->private_data; + + return snd_soc_card_get_drvdata(rtd->card); +} + +static void hdmi_dai_abort(struct device *dev) +{ + struct hdmi_audio_data *ad = dev_get_drvdata(dev); + + mutex_lock(&ad->current_stream_lock); + if (ad->current_stream && ad->current_stream->runtime && + snd_pcm_running(ad->current_stream)) { + dev_err(dev, "HDMI display disabled, aborting playback\n"); + snd_pcm_stream_lock_irq(ad->current_stream); + snd_pcm_stop(ad->current_stream, SNDRV_PCM_STATE_DISCONNECTED); + snd_pcm_stream_unlock_irq(ad->current_stream); + } + mutex_unlock(&ad->current_stream_lock); +} + +static int hdmi_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + int ret; + /* + * Make sure that the period bytes are multiple of the DMA packet size. + * Largest packet size we use is 32 32-bit words = 128 bytes + */ + ret = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128); + if (ret < 0) { + dev_err(dai->dev, "Could not apply period constraint: %d\n", + ret); + return ret; + } + ret = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128); + if (ret < 0) { + dev_err(dai->dev, "Could not apply buffer constraint: %d\n", + ret); + return ret; + } + + snd_soc_dai_set_dma_data(dai, substream, &ad->dma_data); + + mutex_lock(&ad->current_stream_lock); + ad->current_stream = substream; + mutex_unlock(&ad->current_stream_lock); + + ret = ad->ops->audio_startup(ad->dssdev, hdmi_dai_abort); + + if (ret) { + mutex_lock(&ad->current_stream_lock); + ad->current_stream = NULL; + mutex_unlock(&ad->current_stream_lock); + } + + return ret; +} + +static int hdmi_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + struct snd_aes_iec958 *iec = &ad->iec; + struct snd_cea_861_aud_if *cea = &ad->cea; + + WARN_ON(ad->current_stream != substream); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + ad->dma_data.maxburst = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + ad->dma_data.maxburst = 32; + break; + default: + dev_err(dai->dev, "format not supported!\n"); + return -EINVAL; + } + + ad->dss_audio.iec = iec; + ad->dss_audio.cea = cea; + /* + * fill the IEC-60958 channel status word + */ + /* initialize the word bytes */ + memset(iec->status, 0, sizeof(iec->status)); + + /* specify IEC-60958-3 (commercial use) */ + iec->status[0] &= ~IEC958_AES0_PROFESSIONAL; + + /* specify that the audio is LPCM*/ + iec->status[0] &= ~IEC958_AES0_NONAUDIO; + + iec->status[0] |= IEC958_AES0_CON_NOT_COPYRIGHT; + + iec->status[0] |= IEC958_AES0_CON_EMPHASIS_NONE; + + iec->status[1] = IEC958_AES1_CON_GENERAL; + + iec->status[2] |= IEC958_AES2_CON_SOURCE_UNSPEC; + + iec->status[2] |= IEC958_AES2_CON_CHANNEL_UNSPEC; + + switch (params_rate(params)) { + case 32000: + iec->status[3] |= IEC958_AES3_CON_FS_32000; + break; + case 44100: + iec->status[3] |= IEC958_AES3_CON_FS_44100; + break; + case 48000: + iec->status[3] |= IEC958_AES3_CON_FS_48000; + break; + case 88200: + iec->status[3] |= IEC958_AES3_CON_FS_88200; + break; + case 96000: + iec->status[3] |= IEC958_AES3_CON_FS_96000; + break; + case 176400: + iec->status[3] |= IEC958_AES3_CON_FS_176400; + break; + case 192000: + iec->status[3] |= IEC958_AES3_CON_FS_192000; + break; + default: + dev_err(dai->dev, "rate not supported!\n"); + return -EINVAL; + } + + /* specify the clock accuracy */ + iec->status[3] |= IEC958_AES3_CON_CLOCK_1000PPM; + + /* + * specify the word length. The same word length value can mean + * two different lengths. Hence, we need to specify the maximum + * word length as well. + */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + iec->status[4] |= IEC958_AES4_CON_WORDLEN_20_16; + iec->status[4] &= ~IEC958_AES4_CON_MAX_WORDLEN_24; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iec->status[4] |= IEC958_AES4_CON_WORDLEN_24_20; + iec->status[4] |= IEC958_AES4_CON_MAX_WORDLEN_24; + break; + default: + dev_err(dai->dev, "format not supported!\n"); + return -EINVAL; + } + + /* + * Fill the CEA-861 audio infoframe (see spec for details) + */ + + cea->db1_ct_cc = (params_channels(params) - 1) + & CEA861_AUDIO_INFOFRAME_DB1CC; + cea->db1_ct_cc |= CEA861_AUDIO_INFOFRAME_DB1CT_FROM_STREAM; + + cea->db2_sf_ss = CEA861_AUDIO_INFOFRAME_DB2SF_FROM_STREAM; + cea->db2_sf_ss |= CEA861_AUDIO_INFOFRAME_DB2SS_FROM_STREAM; + + cea->db3 = 0; /* not used, all zeros */ + + if (params_channels(params) == 2) + cea->db4_ca = 0x0; + else if (params_channels(params) == 6) + cea->db4_ca = 0xb; + else + cea->db4_ca = 0x13; + + if (cea->db4_ca == 0x00) + cea->db5_dminh_lsv = CEA861_AUDIO_INFOFRAME_DB5_DM_INH_PERMITTED; + else + cea->db5_dminh_lsv = CEA861_AUDIO_INFOFRAME_DB5_DM_INH_PROHIBITED; + + /* the expression is trivial but makes clear what we are doing */ + cea->db5_dminh_lsv |= (0 & CEA861_AUDIO_INFOFRAME_DB5_LSV); + + return ad->ops->audio_config(ad->dssdev, &ad->dss_audio); +} + +static int hdmi_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + int err = 0; + + WARN_ON(ad->current_stream != substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + err = ad->ops->audio_start(ad->dssdev); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ad->ops->audio_stop(ad->dssdev); + break; + default: + err = -EINVAL; + } + return err; +} + +static void hdmi_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_audio_data *ad = card_drvdata_substream(substream); + + WARN_ON(ad->current_stream != substream); + + ad->ops->audio_shutdown(ad->dssdev); + + mutex_lock(&ad->current_stream_lock); + ad->current_stream = NULL; + mutex_unlock(&ad->current_stream_lock); +} + +static const struct snd_soc_dai_ops hdmi_dai_ops = { + .startup = hdmi_dai_startup, + .hw_params = hdmi_dai_hw_params, + .trigger = hdmi_dai_trigger, + .shutdown = hdmi_dai_shutdown, +}; + +static const struct snd_soc_component_driver omap_hdmi_component = { + .name = "omapdss_hdmi", +}; + +static struct snd_soc_dai_driver omap5_hdmi_dai = { + .name = "omap5-hdmi-dai", + .playback = { + .channels_min = 2, + .channels_max = 8, + .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), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &hdmi_dai_ops, +}; + +static struct snd_soc_dai_driver omap4_hdmi_dai = { + .name = "omap4-hdmi-dai", + .playback = { + .channels_min = 2, + .channels_max = 8, + .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), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &hdmi_dai_ops, +}; + +static int omap_hdmi_audio_probe(struct platform_device *pdev) +{ + struct omap_hdmi_audio_pdata *ha = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct hdmi_audio_data *ad; + struct snd_soc_dai_driver *dai_drv; + struct snd_soc_card *card; + int ret; + + if (!ha) { + dev_err(dev, "No platform data\n"); + return -EINVAL; + } + + ad = devm_kzalloc(dev, sizeof(*ad), GFP_KERNEL); + if (!ad) + return -ENOMEM; + ad->dssdev = ha->dev; + ad->ops = ha->ops; + ad->dma_data.addr = ha->audio_dma_addr; + ad->dma_data.filter_data = "audio_tx"; + ad->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + mutex_init(&ad->current_stream_lock); + + switch (ha->version) { + case 4: + dai_drv = &omap4_hdmi_dai; + break; + case 5: + dai_drv = &omap5_hdmi_dai; + break; + default: + return -EINVAL; + } + ret = snd_soc_register_component(ad->dssdev, &omap_hdmi_component, + dai_drv, 1); + if (ret) + return ret; + + ret = sdma_pcm_platform_register(ad->dssdev, "audio_tx", NULL); + if (ret) + return ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = devm_kasprintf(dev, GFP_KERNEL, + "HDMI %s", dev_name(ad->dssdev)); + if (!card->name) + return -ENOMEM; + + card->owner = THIS_MODULE; + card->dai_link = + devm_kzalloc(dev, sizeof(*(card->dai_link)), GFP_KERNEL); + if (!card->dai_link) + return -ENOMEM; + card->dai_link->name = card->name; + card->dai_link->stream_name = card->name; + card->dai_link->cpu_dai_name = dev_name(ad->dssdev); + card->dai_link->platform_name = dev_name(ad->dssdev); + card->dai_link->codec_name = "snd-soc-dummy"; + card->dai_link->codec_dai_name = "snd-soc-dummy-dai"; + card->num_links = 1; + card->dev = dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); + snd_soc_unregister_component(ad->dssdev); + return ret; + } + + ad->card = card; + snd_soc_card_set_drvdata(card, ad); + + dev_set_drvdata(dev, ad); + + return 0; +} + +static int omap_hdmi_audio_remove(struct platform_device *pdev) +{ + struct hdmi_audio_data *ad = platform_get_drvdata(pdev); + + snd_soc_unregister_card(ad->card); + snd_soc_unregister_component(ad->dssdev); + return 0; +} + +static struct platform_driver hdmi_audio_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = omap_hdmi_audio_probe, + .remove = omap_hdmi_audio_remove, +}; + +module_platform_driver(hdmi_audio_driver); + +MODULE_AUTHOR("Jyri Sarha "); +MODULE_DESCRIPTION("OMAP HDMI Audio Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c new file mode 100644 index 000000000..2d6decbfc --- /dev/null +++ b/sound/soc/omap/omap-mcbsp.c @@ -0,0 +1,906 @@ +/* + * omap-mcbsp.c -- OMAP ALSA SoC DAI driver using McBSP port + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Jarkko Nikula + * Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "mcbsp.h" +#include "omap-mcbsp.h" +#include "sdma-pcm.h" + +#define OMAP_MCBSP_RATES (SNDRV_PCM_RATE_8000_96000) + +#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \ + xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = omap_mcbsp_st_info_volsw, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = (unsigned long) &(struct soc_mixer_control) \ + {.min = xmin, .max = xmax} } + +enum { + OMAP_MCBSP_WORD_8 = 0, + OMAP_MCBSP_WORD_12, + OMAP_MCBSP_WORD_16, + OMAP_MCBSP_WORD_20, + OMAP_MCBSP_WORD_24, + OMAP_MCBSP_WORD_32, +}; + +/* + * Stream DMA parameters. DMA request line and port address are set runtime + * since they are different between OMAP1 and later OMAPs + */ +static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream, + unsigned int packet_size) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + int words; + + /* + * Configure McBSP threshold based on either: + * packet_size, when the sDMA is in packet mode, or based on the + * period size in THRESHOLD mode, otherwise use McBSP threshold = 1 + * for mono streams. + */ + if (packet_size) + words = packet_size; + else + words = 1; + + /* Configure McBSP internal buffer usage */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_mcbsp_set_tx_threshold(mcbsp, words); + else + omap_mcbsp_set_rx_threshold(mcbsp, words); +} + +static int omap_mcbsp_hwrule_min_buffersize(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *buffer_size = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct omap_mcbsp *mcbsp = rule->private; + struct snd_interval frames; + int size; + + snd_interval_any(&frames); + size = mcbsp->pdata->buffer_size; + + frames.min = size / channels->min; + frames.integer = 1; + return snd_interval_refine(buffer_size, &frames); +} + +static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + int err = 0; + + if (!cpu_dai->active) + err = omap_mcbsp_request(mcbsp); + + /* + * OMAP3 McBSP FIFO is word structured. + * McBSP2 has 1024 + 256 = 1280 word long buffer, + * McBSP1,3,4,5 has 128 word long buffer + * This means that the size of the FIFO depends on the sample format. + * For example on McBSP3: + * 16bit samples: size is 128 * 2 = 256 bytes + * 32bit samples: size is 128 * 4 = 512 bytes + * It is simpler to place constraint for buffer and period based on + * channels. + * McBSP3 as example again (16 or 32 bit samples): + * 1 channel (mono): size is 128 frames (128 words) + * 2 channels (stereo): size is 128 / 2 = 64 frames (2 * 64 words) + * 4 channels: size is 128 / 4 = 32 frames (4 * 32 words) + */ + if (mcbsp->pdata->buffer_size) { + /* + * Rule for the buffer size. We should not allow + * smaller buffer than the FIFO size to avoid underruns. + * This applies only for the playback stream. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + omap_mcbsp_hwrule_min_buffersize, + mcbsp, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + + /* Make sure, that the period size is always even */ + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2); + } + + return err; +} + +static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + int tx = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + int stream1 = tx ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; + int stream2 = tx ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + + if (mcbsp->latency[stream2]) + pm_qos_update_request(&mcbsp->pm_qos_req, + mcbsp->latency[stream2]); + else if (mcbsp->latency[stream1]) + pm_qos_remove_request(&mcbsp->pm_qos_req); + + mcbsp->latency[stream1] = 0; + + if (!cpu_dai->active) { + omap_mcbsp_free(mcbsp); + mcbsp->configured = 0; + } +} + +static int omap_mcbsp_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + struct pm_qos_request *pm_qos_req = &mcbsp->pm_qos_req; + int tx = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + int stream1 = tx ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; + int stream2 = tx ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + int latency = mcbsp->latency[stream2]; + + /* Prevent omap hardware from hitting off between FIFO fills */ + if (!latency || mcbsp->latency[stream1] < latency) + latency = mcbsp->latency[stream1]; + + if (pm_qos_request_active(pm_qos_req)) + pm_qos_update_request(pm_qos_req, latency); + else if (latency) + pm_qos_add_request(pm_qos_req, PM_QOS_CPU_DMA_LATENCY, latency); + + return 0; +} + +static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + int err = 0, play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + mcbsp->active++; + omap_mcbsp_start(mcbsp, play, !play); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + omap_mcbsp_stop(mcbsp, play, !play); + mcbsp->active--; + break; + default: + err = -EINVAL; + } + + return err; +} + +static snd_pcm_sframes_t omap_mcbsp_dai_delay( + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + u16 fifo_use; + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + fifo_use = omap_mcbsp_get_tx_delay(mcbsp); + else + fifo_use = omap_mcbsp_get_rx_delay(mcbsp); + + /* + * Divide the used locations with the channel count to get the + * FIFO usage in samples (don't care about partial samples in the + * buffer). + */ + delay = fifo_use / substream->runtime->channels; + + return delay; +} + +static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs; + struct snd_dmaengine_dai_dma_data *dma_data; + int wlen, channels, wpf; + int pkt_size = 0; + unsigned int format, div, framesize, master; + unsigned int buffer_size = mcbsp->pdata->buffer_size; + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wlen = 16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wlen = 32; + break; + default: + return -EINVAL; + } + if (buffer_size) { + int latency; + + if (mcbsp->dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) { + int period_words, max_thrsh; + int divider = 0; + + period_words = params_period_bytes(params) / (wlen / 8); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + max_thrsh = mcbsp->max_tx_thres; + else + max_thrsh = mcbsp->max_rx_thres; + /* + * Use sDMA packet mode if McBSP is in threshold mode: + * If period words less than the FIFO size the packet + * size is set to the number of period words, otherwise + * Look for the biggest threshold value which divides + * the period size evenly. + */ + divider = period_words / max_thrsh; + if (period_words % max_thrsh) + divider++; + while (period_words % divider && + divider < period_words) + divider++; + if (divider == period_words) + return -EINVAL; + + pkt_size = period_words / divider; + } else if (channels > 1) { + /* Use packet mode for non mono streams */ + pkt_size = channels; + } + + latency = (buffer_size - pkt_size) / channels; + latency = latency * USEC_PER_SEC / + (params->rate_num / params->rate_den); + mcbsp->latency[substream->stream] = latency; + + omap_mcbsp_set_threshold(substream, pkt_size); + } + + dma_data->maxburst = pkt_size; + + if (mcbsp->configured) { + /* McBSP already configured by another stream */ + return 0; + } + + regs->rcr2 &= ~(RPHASE | RFRLEN2(0x7f) | RWDLEN2(7)); + regs->xcr2 &= ~(RPHASE | XFRLEN2(0x7f) | XWDLEN2(7)); + regs->rcr1 &= ~(RFRLEN1(0x7f) | RWDLEN1(7)); + regs->xcr1 &= ~(XFRLEN1(0x7f) | XWDLEN1(7)); + format = mcbsp->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + wpf = channels; + if (channels == 2 && (format == SND_SOC_DAIFMT_I2S || + format == SND_SOC_DAIFMT_LEFT_J)) { + /* Use dual-phase frames */ + regs->rcr2 |= RPHASE; + regs->xcr2 |= XPHASE; + /* Set 1 word per (McBSP) frame for phase1 and phase2 */ + wpf--; + regs->rcr2 |= RFRLEN2(wpf - 1); + regs->xcr2 |= XFRLEN2(wpf - 1); + } + + regs->rcr1 |= RFRLEN1(wpf - 1); + regs->xcr1 |= XFRLEN1(wpf - 1); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + /* Set word lengths */ + regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_16); + regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_16); + regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16); + regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_16); + break; + case SNDRV_PCM_FORMAT_S32_LE: + /* Set word lengths */ + regs->rcr2 |= RWDLEN2(OMAP_MCBSP_WORD_32); + regs->rcr1 |= RWDLEN1(OMAP_MCBSP_WORD_32); + regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_32); + regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_32); + break; + default: + /* Unsupported PCM format */ + return -EINVAL; + } + + /* In McBSP master modes, FRAME (i.e. sample rate) is generated + * by _counting_ BCLKs. Calculate frame size in BCLKs */ + master = mcbsp->fmt & SND_SOC_DAIFMT_MASTER_MASK; + if (master == SND_SOC_DAIFMT_CBS_CFS) { + div = mcbsp->clk_div ? mcbsp->clk_div : 1; + framesize = (mcbsp->in_freq / div) / params_rate(params); + + if (framesize < wlen * channels) { + printk(KERN_ERR "%s: not enough bandwidth for desired rate and " + "channels\n", __func__); + return -EINVAL; + } + } else + framesize = wlen * channels; + + /* Set FS period and length in terms of bit clock periods */ + regs->srgr2 &= ~FPER(0xfff); + regs->srgr1 &= ~FWID(0xff); + switch (format) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + regs->srgr2 |= FPER(framesize - 1); + regs->srgr1 |= FWID((framesize >> 1) - 1); + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + regs->srgr2 |= FPER(framesize - 1); + regs->srgr1 |= FWID(0); + break; + } + + omap_mcbsp_config(mcbsp, &mcbsp->cfg_regs); + mcbsp->wlen = wlen; + mcbsp->configured = 1; + + return 0; +} + +/* + * This must be called before _set_clkdiv and _set_sysclk since McBSP register + * cache is initialized here + */ +static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs; + bool inv_fs = false; + + if (mcbsp->configured) + return 0; + + mcbsp->fmt = fmt; + memset(regs, 0, sizeof(*regs)); + /* Generic McBSP register settings */ + regs->spcr2 |= XINTM(3) | FREE; + regs->spcr1 |= RINTM(3); + /* RFIG and XFIG are not defined in 2430 and on OMAP3+ */ + if (!mcbsp->pdata->has_ccr) { + regs->rcr2 |= RFIG; + regs->xcr2 |= XFIG; + } + + /* Configure XCCR/RCCR only for revisions which have ccr registers */ + if (mcbsp->pdata->has_ccr) { + regs->xccr = DXENDLY(1) | XDMAEN | XDISABLE; + regs->rccr = RFULL_CYCLE | RDMAEN | RDISABLE; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* 1-bit data delay */ + regs->rcr2 |= RDATDLY(1); + regs->xcr2 |= XDATDLY(1); + break; + case SND_SOC_DAIFMT_LEFT_J: + /* 0-bit data delay */ + regs->rcr2 |= RDATDLY(0); + regs->xcr2 |= XDATDLY(0); + regs->spcr1 |= RJUST(2); + /* Invert FS polarity configuration */ + inv_fs = true; + break; + case SND_SOC_DAIFMT_DSP_A: + /* 1-bit data delay */ + regs->rcr2 |= RDATDLY(1); + regs->xcr2 |= XDATDLY(1); + /* Invert FS polarity configuration */ + inv_fs = true; + break; + case SND_SOC_DAIFMT_DSP_B: + /* 0-bit data delay */ + regs->rcr2 |= RDATDLY(0); + regs->xcr2 |= XDATDLY(0); + /* Invert FS polarity configuration */ + inv_fs = true; + break; + default: + /* Unsupported data format */ + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* McBSP master. Set FS and bit clocks as outputs */ + regs->pcr0 |= FSXM | FSRM | + CLKXM | CLKRM; + /* Sample rate generator drives the FS */ + regs->srgr2 |= FSGM; + break; + case SND_SOC_DAIFMT_CBM_CFS: + /* McBSP slave. FS clock as output */ + regs->srgr2 |= FSGM; + regs->pcr0 |= FSXM | FSRM; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* McBSP slave */ + break; + default: + /* Unsupported master/slave configuration */ + return -EINVAL; + } + + /* Set bit clock (CLKX/CLKR) and FS polarities */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* + * Normal BCLK + FS. + * FS active low. TX data driven on falling edge of bit clock + * and RX data sampled on rising edge of bit clock. + */ + regs->pcr0 |= FSXP | FSRP | + CLKXP | CLKRP; + break; + case SND_SOC_DAIFMT_NB_IF: + regs->pcr0 |= CLKXP | CLKRP; + break; + case SND_SOC_DAIFMT_IB_NF: + regs->pcr0 |= FSXP | FSRP; + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + return -EINVAL; + } + if (inv_fs == true) + regs->pcr0 ^= FSXP | FSRP; + + return 0; +} + +static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs; + + if (div_id != OMAP_MCBSP_CLKGDV) + return -ENODEV; + + mcbsp->clk_div = div; + regs->srgr1 &= ~CLKGDV(0xff); + regs->srgr1 |= CLKGDV(div - 1); + + return 0; +} + +static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, + int dir) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + struct omap_mcbsp_reg_cfg *regs = &mcbsp->cfg_regs; + int err = 0; + + if (mcbsp->active) { + if (freq == mcbsp->in_freq) + return 0; + else + return -EBUSY; + } + + mcbsp->in_freq = freq; + regs->srgr2 &= ~CLKSM; + regs->pcr0 &= ~SCLKME; + + switch (clk_id) { + case OMAP_MCBSP_SYSCLK_CLK: + regs->srgr2 |= CLKSM; + break; + case OMAP_MCBSP_SYSCLK_CLKS_FCLK: + if (mcbsp_omap1()) { + err = -EINVAL; + break; + } + err = omap2_mcbsp_set_clks_src(mcbsp, + MCBSP_CLKS_PRCM_SRC); + break; + case OMAP_MCBSP_SYSCLK_CLKS_EXT: + if (mcbsp_omap1()) { + err = 0; + break; + } + err = omap2_mcbsp_set_clks_src(mcbsp, + MCBSP_CLKS_PAD_SRC); + break; + + case OMAP_MCBSP_SYSCLK_CLKX_EXT: + regs->srgr2 |= CLKSM; + regs->pcr0 |= SCLKME; + /* + * If McBSP is master but yet the CLKX/CLKR pin drives the SRG, + * disable output on those pins. This enables to inject the + * reference clock through CLKX/CLKR. For this to work + * set_dai_sysclk() _needs_ to be called after set_dai_fmt(). + */ + regs->pcr0 &= ~CLKXM; + break; + case OMAP_MCBSP_SYSCLK_CLKR_EXT: + regs->pcr0 |= SCLKME; + /* Disable ouput on CLKR pin in master mode */ + regs->pcr0 &= ~CLKRM; + break; + default: + err = -ENODEV; + } + + return err; +} + +static const struct snd_soc_dai_ops mcbsp_dai_ops = { + .startup = omap_mcbsp_dai_startup, + .shutdown = omap_mcbsp_dai_shutdown, + .prepare = omap_mcbsp_dai_prepare, + .trigger = omap_mcbsp_dai_trigger, + .delay = omap_mcbsp_dai_delay, + .hw_params = omap_mcbsp_dai_hw_params, + .set_fmt = omap_mcbsp_dai_set_dai_fmt, + .set_clkdiv = omap_mcbsp_dai_set_clkdiv, + .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, +}; + +static int omap_mcbsp_probe(struct snd_soc_dai *dai) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(dai); + + pm_runtime_enable(mcbsp->dev); + + snd_soc_dai_init_dma_data(dai, + &mcbsp->dma_data[SNDRV_PCM_STREAM_PLAYBACK], + &mcbsp->dma_data[SNDRV_PCM_STREAM_CAPTURE]); + + return 0; +} + +static int omap_mcbsp_remove(struct snd_soc_dai *dai) +{ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(dai); + + pm_runtime_disable(mcbsp->dev); + + return 0; +} + +static struct snd_soc_dai_driver omap_mcbsp_dai = { + .probe = omap_mcbsp_probe, + .remove = omap_mcbsp_remove, + .playback = { + .channels_min = 1, + .channels_max = 16, + .rates = OMAP_MCBSP_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 16, + .rates = OMAP_MCBSP_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &mcbsp_dai_ops, +}; + +static const struct snd_soc_component_driver omap_mcbsp_component = { + .name = "omap-mcbsp", +}; + +static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = min; + uinfo->value.integer.max = max; + return 0; +} + +#define OMAP_MCBSP_ST_CHANNEL_VOLUME(channel) \ +static int \ +omap_mcbsp_set_st_ch##channel##_volume(struct snd_kcontrol *kc, \ + struct snd_ctl_elem_value *uc) \ +{ \ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \ + struct soc_mixer_control *mc = \ + (struct soc_mixer_control *)kc->private_value; \ + int max = mc->max; \ + int min = mc->min; \ + int val = uc->value.integer.value[0]; \ + \ + if (val < min || val > max) \ + return -EINVAL; \ + \ + /* OMAP McBSP implementation uses index values 0..4 */ \ + return omap_st_set_chgain(mcbsp, channel, val); \ +} \ + \ +static int \ +omap_mcbsp_get_st_ch##channel##_volume(struct snd_kcontrol *kc, \ + struct snd_ctl_elem_value *uc) \ +{ \ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \ + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \ + s16 chgain; \ + \ + if (omap_st_get_chgain(mcbsp, channel, &chgain)) \ + return -EAGAIN; \ + \ + uc->value.integer.value[0] = chgain; \ + return 0; \ +} + +OMAP_MCBSP_ST_CHANNEL_VOLUME(0) +OMAP_MCBSP_ST_CHANNEL_VOLUME(1) + +static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + u8 value = ucontrol->value.integer.value[0]; + + if (value == omap_st_is_enabled(mcbsp)) + return 0; + + if (value) + omap_st_enable(mcbsp); + else + omap_st_disable(mcbsp); + + return 1; +} + +static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = omap_st_is_enabled(mcbsp); + return 0; +} + +#define OMAP_MCBSP_ST_CONTROLS(port) \ +static const struct snd_kcontrol_new omap_mcbsp##port##_st_controls[] = { \ +SOC_SINGLE_EXT("McBSP" #port " Sidetone Switch", 1, 0, 1, 0, \ + omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode), \ +OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 0 Volume", \ + -32768, 32767, \ + omap_mcbsp_get_st_ch0_volume, \ + omap_mcbsp_set_st_ch0_volume), \ +OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 1 Volume", \ + -32768, 32767, \ + omap_mcbsp_get_st_ch1_volume, \ + omap_mcbsp_set_st_ch1_volume), \ +} + +OMAP_MCBSP_ST_CONTROLS(2); +OMAP_MCBSP_ST_CONTROLS(3); + +int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id) +{ + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); + + if (!mcbsp->st_data) { + dev_warn(mcbsp->dev, "No sidetone data for port\n"); + return 0; + } + + switch (port_id) { + case 2: /* McBSP 2 */ + return snd_soc_add_dai_controls(cpu_dai, + omap_mcbsp2_st_controls, + ARRAY_SIZE(omap_mcbsp2_st_controls)); + case 3: /* McBSP 3 */ + return snd_soc_add_dai_controls(cpu_dai, + omap_mcbsp3_st_controls, + ARRAY_SIZE(omap_mcbsp3_st_controls)); + default: + dev_err(mcbsp->dev, "Port %d not supported\n", port_id); + break; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls); + +static struct omap_mcbsp_platform_data omap2420_pdata = { + .reg_step = 4, + .reg_size = 2, +}; + +static struct omap_mcbsp_platform_data omap2430_pdata = { + .reg_step = 4, + .reg_size = 4, + .has_ccr = true, +}; + +static struct omap_mcbsp_platform_data omap3_pdata = { + .reg_step = 4, + .reg_size = 4, + .has_ccr = true, + .has_wakeup = true, +}; + +static struct omap_mcbsp_platform_data omap4_pdata = { + .reg_step = 4, + .reg_size = 4, + .has_ccr = true, + .has_wakeup = true, +}; + +static const struct of_device_id omap_mcbsp_of_match[] = { + { + .compatible = "ti,omap2420-mcbsp", + .data = &omap2420_pdata, + }, + { + .compatible = "ti,omap2430-mcbsp", + .data = &omap2430_pdata, + }, + { + .compatible = "ti,omap3-mcbsp", + .data = &omap3_pdata, + }, + { + .compatible = "ti,omap4-mcbsp", + .data = &omap4_pdata, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, omap_mcbsp_of_match); + +static int asoc_mcbsp_probe(struct platform_device *pdev) +{ + struct omap_mcbsp_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct omap_mcbsp *mcbsp; + const struct of_device_id *match; + int ret; + + match = of_match_device(omap_mcbsp_of_match, &pdev->dev); + if (match) { + struct device_node *node = pdev->dev.of_node; + struct omap_mcbsp_platform_data *pdata_quirk = pdata; + int buffer_size; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct omap_mcbsp_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + memcpy(pdata, match->data, sizeof(*pdata)); + if (!of_property_read_u32(node, "ti,buffer-size", &buffer_size)) + pdata->buffer_size = buffer_size; + if (pdata_quirk) + pdata->force_ick_on = pdata_quirk->force_ick_on; + } else if (!pdata) { + dev_err(&pdev->dev, "missing platform data.\n"); + return -EINVAL; + } + mcbsp = devm_kzalloc(&pdev->dev, sizeof(struct omap_mcbsp), GFP_KERNEL); + if (!mcbsp) + return -ENOMEM; + + mcbsp->id = pdev->id; + mcbsp->pdata = pdata; + mcbsp->dev = &pdev->dev; + platform_set_drvdata(pdev, mcbsp); + + ret = omap_mcbsp_init(pdev); + if (ret) + return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &omap_mcbsp_component, + &omap_mcbsp_dai, 1); + if (ret) + return ret; + + return sdma_pcm_platform_register(&pdev->dev, NULL, NULL); +} + +static int asoc_mcbsp_remove(struct platform_device *pdev) +{ + struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); + + if (mcbsp->pdata->ops && mcbsp->pdata->ops->free) + mcbsp->pdata->ops->free(mcbsp->id); + + if (pm_qos_request_active(&mcbsp->pm_qos_req)) + pm_qos_remove_request(&mcbsp->pm_qos_req); + + omap_mcbsp_cleanup(mcbsp); + + clk_put(mcbsp->fclk); + + return 0; +} + +static struct platform_driver asoc_mcbsp_driver = { + .driver = { + .name = "omap-mcbsp", + .of_match_table = omap_mcbsp_of_match, + }, + + .probe = asoc_mcbsp_probe, + .remove = asoc_mcbsp_remove, +}; + +module_platform_driver(asoc_mcbsp_driver); + +MODULE_AUTHOR("Jarkko Nikula "); +MODULE_DESCRIPTION("OMAP I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:omap-mcbsp"); diff --git a/sound/soc/omap/omap-mcbsp.h b/sound/soc/omap/omap-mcbsp.h new file mode 100644 index 000000000..2e3369c27 --- /dev/null +++ b/sound/soc/omap/omap-mcbsp.h @@ -0,0 +1,44 @@ +/* + * omap-mcbsp.h + * + * Copyright (C) 2008 Nokia Corporation + * + * Contact: Jarkko Nikula + * Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __OMAP_I2S_H__ +#define __OMAP_I2S_H__ + +/* Source clocks for McBSP sample rate generator */ +enum omap_mcbsp_clksrg_clk { + OMAP_MCBSP_SYSCLK_CLKS_FCLK, /* Internal FCLK */ + OMAP_MCBSP_SYSCLK_CLKS_EXT, /* External CLKS pin */ + OMAP_MCBSP_SYSCLK_CLK, /* Internal ICLK */ + OMAP_MCBSP_SYSCLK_CLKX_EXT, /* External CLKX pin */ + OMAP_MCBSP_SYSCLK_CLKR_EXT, /* External CLKR pin */ +}; + +/* McBSP dividers */ +enum omap_mcbsp_div { + OMAP_MCBSP_CLKGDV, /* Sample rate generator divider */ +}; + +int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id); + +#endif diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c new file mode 100644 index 000000000..7d5bdc5a2 --- /dev/null +++ b/sound/soc/omap/omap-mcpdm.c @@ -0,0 +1,619 @@ +/* + * omap-mcpdm.c -- OMAP ALSA SoC DAI driver using McPDM port + * + * Copyright (C) 2009 - 2011 Texas Instruments + * + * Author: Misael Lopez Cruz + * Contact: Jorge Eduardo Candelaria + * Margarita Olaya + * Peter Ujfalusi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "omap-mcpdm.h" +#include "sdma-pcm.h" + +struct mcpdm_link_config { + u32 link_mask; /* channel mask for the direction */ + u32 threshold; /* FIFO threshold */ +}; + +struct omap_mcpdm { + struct device *dev; + unsigned long phys_base; + void __iomem *io_base; + int irq; + struct pm_qos_request pm_qos_req; + int latency[2]; + + struct mutex mutex; + + /* Playback/Capture configuration */ + struct mcpdm_link_config config[2]; + + /* McPDM dn offsets for rx1, and 2 channels */ + u32 dn_rx_offset; + + /* McPDM needs to be restarted due to runtime reconfiguration */ + bool restart; + + /* pm state for suspend/resume handling */ + int pm_active_count; + + struct snd_dmaengine_dai_dma_data dma_data[2]; +}; + +/* + * Stream DMA parameters + */ + +static inline void omap_mcpdm_write(struct omap_mcpdm *mcpdm, u16 reg, u32 val) +{ + writel_relaxed(val, mcpdm->io_base + reg); +} + +static inline int omap_mcpdm_read(struct omap_mcpdm *mcpdm, u16 reg) +{ + return readl_relaxed(mcpdm->io_base + reg); +} + +#ifdef DEBUG +static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) +{ + dev_dbg(mcpdm->dev, "***********************\n"); + dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS_RAW)); + dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS)); + dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_SET)); + dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_CLR)); + dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_IRQWAKE_EN)); + dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_SET)); + dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_CLR)); + dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_DMAWAKEEN)); + dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL)); + dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_DN_DATA)); + dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_UP_DATA)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_DN)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_UP)); + dev_dbg(mcpdm->dev, "***********************\n"); +} +#else +static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) {} +#endif + +/* + * Enables the transfer through the PDM interface to/from the Phoenix + * codec by enabling the corresponding UP or DN channels. + */ +static void omap_mcpdm_start(struct omap_mcpdm *mcpdm) +{ + u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); + u32 link_mask = mcpdm->config[0].link_mask | mcpdm->config[1].link_mask; + + ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); + + ctrl |= link_mask; + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); + + ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); +} + +/* + * Disables the transfer through the PDM interface to/from the Phoenix + * codec by disabling the corresponding UP or DN channels. + */ +static void omap_mcpdm_stop(struct omap_mcpdm *mcpdm) +{ + u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); + u32 link_mask = MCPDM_PDM_DN_MASK | MCPDM_PDM_UP_MASK; + + ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); + + ctrl &= ~(link_mask); + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); + + ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); + +} + +/* + * Is the physical McPDM interface active. + */ +static inline int omap_mcpdm_active(struct omap_mcpdm *mcpdm) +{ + return omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL) & + (MCPDM_PDM_DN_MASK | MCPDM_PDM_UP_MASK); +} + +/* + * Configures McPDM uplink, and downlink for audio. + * This function should be called before omap_mcpdm_start. + */ +static void omap_mcpdm_open_streams(struct omap_mcpdm *mcpdm) +{ + u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); + + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl | MCPDM_WD_EN); + + omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_SET, + MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL | + MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); + + /* Enable DN RX1/2 offset cancellation feature, if configured */ + if (mcpdm->dn_rx_offset) { + u32 dn_offset = mcpdm->dn_rx_offset; + + omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, dn_offset); + dn_offset |= (MCPDM_DN_OFST_RX1_EN | MCPDM_DN_OFST_RX2_EN); + omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, dn_offset); + } + + omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_DN, + mcpdm->config[SNDRV_PCM_STREAM_PLAYBACK].threshold); + omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_UP, + mcpdm->config[SNDRV_PCM_STREAM_CAPTURE].threshold); + + omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_SET, + MCPDM_DMA_DN_ENABLE | MCPDM_DMA_UP_ENABLE); +} + +/* + * Cleans McPDM uplink, and downlink configuration. + * This function should be called when the stream is closed. + */ +static void omap_mcpdm_close_streams(struct omap_mcpdm *mcpdm) +{ + /* Disable irq request generation for downlink */ + omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR, + MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL); + + /* Disable DMA request generation for downlink */ + omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_DN_ENABLE); + + /* Disable irq request generation for uplink */ + omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR, + MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); + + /* Disable DMA request generation for uplink */ + omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_UP_ENABLE); + + /* Disable RX1/2 offset cancellation */ + if (mcpdm->dn_rx_offset) + omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, 0); +} + +static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id) +{ + struct omap_mcpdm *mcpdm = dev_id; + int irq_status; + + irq_status = omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS); + + /* Acknowledge irq event */ + omap_mcpdm_write(mcpdm, MCPDM_REG_IRQSTATUS, irq_status); + + if (irq_status & MCPDM_DN_IRQ_FULL) + dev_dbg(mcpdm->dev, "DN (playback) FIFO Full\n"); + + if (irq_status & MCPDM_DN_IRQ_EMPTY) + dev_dbg(mcpdm->dev, "DN (playback) FIFO Empty\n"); + + if (irq_status & MCPDM_DN_IRQ) + dev_dbg(mcpdm->dev, "DN (playback) write request\n"); + + if (irq_status & MCPDM_UP_IRQ_FULL) + dev_dbg(mcpdm->dev, "UP (capture) FIFO Full\n"); + + if (irq_status & MCPDM_UP_IRQ_EMPTY) + dev_dbg(mcpdm->dev, "UP (capture) FIFO Empty\n"); + + if (irq_status & MCPDM_UP_IRQ) + dev_dbg(mcpdm->dev, "UP (capture) write request\n"); + + return IRQ_HANDLED; +} + +static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + mutex_lock(&mcpdm->mutex); + + if (!dai->active) + omap_mcpdm_open_streams(mcpdm); + + mutex_unlock(&mcpdm->mutex); + + return 0; +} + +static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + int tx = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + int stream1 = tx ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; + int stream2 = tx ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + + mutex_lock(&mcpdm->mutex); + + if (!dai->active) { + if (omap_mcpdm_active(mcpdm)) { + omap_mcpdm_stop(mcpdm); + omap_mcpdm_close_streams(mcpdm); + mcpdm->config[0].link_mask = 0; + mcpdm->config[1].link_mask = 0; + } + } + + if (mcpdm->latency[stream2]) + pm_qos_update_request(&mcpdm->pm_qos_req, + mcpdm->latency[stream2]); + else if (mcpdm->latency[stream1]) + pm_qos_remove_request(&mcpdm->pm_qos_req); + + mcpdm->latency[stream1] = 0; + + mutex_unlock(&mcpdm->mutex); +} + +static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + int stream = substream->stream; + struct snd_dmaengine_dai_dma_data *dma_data; + u32 threshold; + int channels, latency; + int link_mask = 0; + + channels = params_channels(params); + switch (channels) { + case 5: + if (stream == SNDRV_PCM_STREAM_CAPTURE) + /* up to 3 channels for capture */ + return -EINVAL; + link_mask |= 1 << 4; + /* fall through */ + case 4: + if (stream == SNDRV_PCM_STREAM_CAPTURE) + /* up to 3 channels for capture */ + return -EINVAL; + link_mask |= 1 << 3; + /* fall through */ + case 3: + link_mask |= 1 << 2; + /* fall through */ + case 2: + link_mask |= 1 << 1; + /* fall through */ + case 1: + link_mask |= 1 << 0; + break; + default: + /* unsupported number of channels */ + return -EINVAL; + } + + dma_data = snd_soc_dai_get_dma_data(dai, substream); + + threshold = mcpdm->config[stream].threshold; + /* Configure McPDM channels, and DMA packet size */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + link_mask <<= 3; + + /* If capture is not running assume a stereo stream to come */ + if (!mcpdm->config[!stream].link_mask) + mcpdm->config[!stream].link_mask = 0x3; + + dma_data->maxburst = + (MCPDM_DN_THRES_MAX - threshold) * channels; + latency = threshold; + } else { + /* If playback is not running assume a stereo stream to come */ + if (!mcpdm->config[!stream].link_mask) + mcpdm->config[!stream].link_mask = (0x3 << 3); + + dma_data->maxburst = threshold * channels; + latency = (MCPDM_DN_THRES_MAX - threshold); + } + + /* + * The DMA must act to a DMA request within latency time (usec) to avoid + * under/overflow + */ + mcpdm->latency[stream] = latency * USEC_PER_SEC / params_rate(params); + + if (!mcpdm->latency[stream]) + mcpdm->latency[stream] = 10; + + /* Check if we need to restart McPDM with this stream */ + if (mcpdm->config[stream].link_mask && + mcpdm->config[stream].link_mask != link_mask) + mcpdm->restart = true; + + mcpdm->config[stream].link_mask = link_mask; + + return 0; +} + +static int omap_mcpdm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + struct pm_qos_request *pm_qos_req = &mcpdm->pm_qos_req; + int tx = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + int stream1 = tx ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; + int stream2 = tx ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + int latency = mcpdm->latency[stream2]; + + /* Prevent omap hardware from hitting off between FIFO fills */ + if (!latency || mcpdm->latency[stream1] < latency) + latency = mcpdm->latency[stream1]; + + if (pm_qos_request_active(pm_qos_req)) + pm_qos_update_request(pm_qos_req, latency); + else if (latency) + pm_qos_add_request(pm_qos_req, PM_QOS_CPU_DMA_LATENCY, latency); + + if (!omap_mcpdm_active(mcpdm)) { + omap_mcpdm_start(mcpdm); + omap_mcpdm_reg_dump(mcpdm); + } else if (mcpdm->restart) { + omap_mcpdm_stop(mcpdm); + omap_mcpdm_start(mcpdm); + mcpdm->restart = false; + omap_mcpdm_reg_dump(mcpdm); + } + + return 0; +} + +static const struct snd_soc_dai_ops omap_mcpdm_dai_ops = { + .startup = omap_mcpdm_dai_startup, + .shutdown = omap_mcpdm_dai_shutdown, + .hw_params = omap_mcpdm_dai_hw_params, + .prepare = omap_mcpdm_prepare, +}; + +static int omap_mcpdm_probe(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + int ret; + + pm_runtime_enable(mcpdm->dev); + + /* Disable lines while request is ongoing */ + pm_runtime_get_sync(mcpdm->dev); + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, 0x00); + + ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, 0, "McPDM", + (void *)mcpdm); + + pm_runtime_put_sync(mcpdm->dev); + + if (ret) { + dev_err(mcpdm->dev, "Request for IRQ failed\n"); + pm_runtime_disable(mcpdm->dev); + } + + /* Configure McPDM threshold values */ + mcpdm->config[SNDRV_PCM_STREAM_PLAYBACK].threshold = 2; + mcpdm->config[SNDRV_PCM_STREAM_CAPTURE].threshold = + MCPDM_UP_THRES_MAX - 3; + + snd_soc_dai_init_dma_data(dai, + &mcpdm->dma_data[SNDRV_PCM_STREAM_PLAYBACK], + &mcpdm->dma_data[SNDRV_PCM_STREAM_CAPTURE]); + + return ret; +} + +static int omap_mcpdm_remove(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + free_irq(mcpdm->irq, (void *)mcpdm); + pm_runtime_disable(mcpdm->dev); + + if (pm_qos_request_active(&mcpdm->pm_qos_req)) + pm_qos_remove_request(&mcpdm->pm_qos_req); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int omap_mcpdm_suspend(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + if (dai->active) { + omap_mcpdm_stop(mcpdm); + omap_mcpdm_close_streams(mcpdm); + } + + mcpdm->pm_active_count = 0; + while (pm_runtime_active(mcpdm->dev)) { + pm_runtime_put_sync(mcpdm->dev); + mcpdm->pm_active_count++; + } + + return 0; +} + +static int omap_mcpdm_resume(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + if (mcpdm->pm_active_count) { + while (mcpdm->pm_active_count--) + pm_runtime_get_sync(mcpdm->dev); + + if (dai->active) { + omap_mcpdm_open_streams(mcpdm); + omap_mcpdm_start(mcpdm); + } + } + + + return 0; +} +#else +#define omap_mcpdm_suspend NULL +#define omap_mcpdm_resume NULL +#endif + +#define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define OMAP_MCPDM_FORMATS SNDRV_PCM_FMTBIT_S32_LE + +static struct snd_soc_dai_driver omap_mcpdm_dai = { + .probe = omap_mcpdm_probe, + .remove = omap_mcpdm_remove, + .suspend = omap_mcpdm_suspend, + .resume = omap_mcpdm_resume, + .probe_order = SND_SOC_COMP_ORDER_LATE, + .remove_order = SND_SOC_COMP_ORDER_EARLY, + .playback = { + .channels_min = 1, + .channels_max = 5, + .rates = OMAP_MCPDM_RATES, + .formats = OMAP_MCPDM_FORMATS, + .sig_bits = 24, + }, + .capture = { + .channels_min = 1, + .channels_max = 3, + .rates = OMAP_MCPDM_RATES, + .formats = OMAP_MCPDM_FORMATS, + .sig_bits = 24, + }, + .ops = &omap_mcpdm_dai_ops, +}; + +static const struct snd_soc_component_driver omap_mcpdm_component = { + .name = "omap-mcpdm", +}; + +void omap_mcpdm_configure_dn_offsets(struct snd_soc_pcm_runtime *rtd, + u8 rx1, u8 rx2) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + mcpdm->dn_rx_offset = MCPDM_DNOFST_RX1(rx1) | MCPDM_DNOFST_RX2(rx2); +} +EXPORT_SYMBOL_GPL(omap_mcpdm_configure_dn_offsets); + +static int asoc_mcpdm_probe(struct platform_device *pdev) +{ + struct omap_mcpdm *mcpdm; + struct resource *res; + int ret; + + mcpdm = devm_kzalloc(&pdev->dev, sizeof(struct omap_mcpdm), GFP_KERNEL); + if (!mcpdm) + return -ENOMEM; + + platform_set_drvdata(pdev, mcpdm); + + mutex_init(&mcpdm->mutex); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (res == NULL) + return -ENOMEM; + + mcpdm->dma_data[0].addr = res->start + MCPDM_REG_DN_DATA; + mcpdm->dma_data[1].addr = res->start + MCPDM_REG_UP_DATA; + + mcpdm->dma_data[0].filter_data = "dn_link"; + mcpdm->dma_data[1].filter_data = "up_link"; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); + mcpdm->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mcpdm->io_base)) + return PTR_ERR(mcpdm->io_base); + + mcpdm->irq = platform_get_irq(pdev, 0); + if (mcpdm->irq < 0) + return mcpdm->irq; + + mcpdm->dev = &pdev->dev; + + ret = devm_snd_soc_register_component(&pdev->dev, + &omap_mcpdm_component, + &omap_mcpdm_dai, 1); + if (ret) + return ret; + + return sdma_pcm_platform_register(&pdev->dev, "dn_link", "up_link"); +} + +static const struct of_device_id omap_mcpdm_of_match[] = { + { .compatible = "ti,omap4-mcpdm", }, + { } +}; +MODULE_DEVICE_TABLE(of, omap_mcpdm_of_match); + +static struct platform_driver asoc_mcpdm_driver = { + .driver = { + .name = "omap-mcpdm", + .of_match_table = omap_mcpdm_of_match, + }, + + .probe = asoc_mcpdm_probe, +}; + +module_platform_driver(asoc_mcpdm_driver); + +MODULE_ALIAS("platform:omap-mcpdm"); +MODULE_AUTHOR("Misael Lopez Cruz "); +MODULE_DESCRIPTION("OMAP PDM SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/omap-mcpdm.h b/sound/soc/omap/omap-mcpdm.h new file mode 100644 index 000000000..de8cf2659 --- /dev/null +++ b/sound/soc/omap/omap-mcpdm.h @@ -0,0 +1,107 @@ +/* + * omap-mcpdm.h + * + * Copyright (C) 2009 - 2011 Texas Instruments + * + * Contact: Misael Lopez Cruz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __OMAP_MCPDM_H__ +#define __OMAP_MCPDM_H__ + +#define MCPDM_REG_REVISION 0x00 +#define MCPDM_REG_SYSCONFIG 0x10 +#define MCPDM_REG_IRQSTATUS_RAW 0x24 +#define MCPDM_REG_IRQSTATUS 0x28 +#define MCPDM_REG_IRQENABLE_SET 0x2C +#define MCPDM_REG_IRQENABLE_CLR 0x30 +#define MCPDM_REG_IRQWAKE_EN 0x34 +#define MCPDM_REG_DMAENABLE_SET 0x38 +#define MCPDM_REG_DMAENABLE_CLR 0x3C +#define MCPDM_REG_DMAWAKEEN 0x40 +#define MCPDM_REG_CTRL 0x44 +#define MCPDM_REG_DN_DATA 0x48 +#define MCPDM_REG_UP_DATA 0x4C +#define MCPDM_REG_FIFO_CTRL_DN 0x50 +#define MCPDM_REG_FIFO_CTRL_UP 0x54 +#define MCPDM_REG_DN_OFFSET 0x58 + +/* + * MCPDM_IRQ bit fields + * IRQSTATUS_RAW, IRQSTATUS, IRQENABLE_SET, IRQENABLE_CLR + */ + +#define MCPDM_DN_IRQ (1 << 0) +#define MCPDM_DN_IRQ_EMPTY (1 << 1) +#define MCPDM_DN_IRQ_ALMST_EMPTY (1 << 2) +#define MCPDM_DN_IRQ_FULL (1 << 3) + +#define MCPDM_UP_IRQ (1 << 8) +#define MCPDM_UP_IRQ_EMPTY (1 << 9) +#define MCPDM_UP_IRQ_ALMST_FULL (1 << 10) +#define MCPDM_UP_IRQ_FULL (1 << 11) + +#define MCPDM_DOWNLINK_IRQ_MASK 0x00F +#define MCPDM_UPLINK_IRQ_MASK 0xF00 + +/* + * MCPDM_DMAENABLE bit fields + */ + +#define MCPDM_DMA_DN_ENABLE (1 << 0) +#define MCPDM_DMA_UP_ENABLE (1 << 1) + +/* + * MCPDM_CTRL bit fields + */ + +#define MCPDM_PDM_UPLINK_EN(x) (1 << (x - 1)) /* ch1 is at bit 0 */ +#define MCPDM_PDM_DOWNLINK_EN(x) (1 << (x + 2)) /* ch1 is at bit 3 */ +#define MCPDM_PDMOUTFORMAT (1 << 8) +#define MCPDM_CMD_INT (1 << 9) +#define MCPDM_STATUS_INT (1 << 10) +#define MCPDM_SW_UP_RST (1 << 11) +#define MCPDM_SW_DN_RST (1 << 12) +#define MCPDM_WD_EN (1 << 14) +#define MCPDM_PDM_UP_MASK 0x7 +#define MCPDM_PDM_DN_MASK (0x1f << 3) + + +#define MCPDM_PDMOUTFORMAT_LJUST (0 << 8) +#define MCPDM_PDMOUTFORMAT_RJUST (1 << 8) + +/* + * MCPDM_FIFO_CTRL bit fields + */ + +#define MCPDM_UP_THRES_MAX 0xF +#define MCPDM_DN_THRES_MAX 0xF + +/* + * MCPDM_DN_OFFSET bit fields + */ + +#define MCPDM_DN_OFST_RX1_EN (1 << 0) +#define MCPDM_DNOFST_RX1(x) ((x & 0x1f) << 1) +#define MCPDM_DN_OFST_RX2_EN (1 << 8) +#define MCPDM_DNOFST_RX2(x) ((x & 0x1f) << 9) + +void omap_mcpdm_configure_dn_offsets(struct snd_soc_pcm_runtime *rtd, + u8 rx1, u8 rx2); + +#endif /* End of __OMAP_MCPDM_H__ */ diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c new file mode 100644 index 000000000..cccc31674 --- /dev/null +++ b/sound/soc/omap/omap-twl4030.c @@ -0,0 +1,353 @@ +/* + * omap-twl4030.c -- SoC audio for TI SoC based boards with twl4030 codec + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * All rights reserved. + * + * Author: Peter Ujfalusi + * + * This driver replaces the following machine drivers: + * omap3beagle (Author: Steve Sakoman ) + * omap3evm (Author: Anuj Aggarwal ) + * overo (Author: Steve Sakoman ) + * igep0020 (Author: Enric Balletbo i Serra ) + * zoom2 (Author: Misael Lopez Cruz ) + * sdp3430 (Author: Misael Lopez Cruz ) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-mcbsp.h" + +struct omap_twl4030 { + int jack_detect; /* board can detect jack events */ + struct snd_soc_jack hs_jack; +}; + +static int omap_twl4030_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int fmt; + + switch (params_channels(params)) { + case 2: /* Stereo I2S mode */ + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + case 4: /* Four channel TDM mode */ + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + default: + return -EINVAL; + } + + return snd_soc_runtime_set_dai_fmt(rtd, fmt); +} + +static const struct snd_soc_ops omap_twl4030_ops = { + .hw_params = omap_twl4030_hw_params, +}; + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_SPK("Earpiece Spk", NULL), + SND_SOC_DAPM_SPK("Handsfree Spk", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_SPK("Carkit Spk", NULL), + + SND_SOC_DAPM_MIC("Main Mic", NULL), + SND_SOC_DAPM_MIC("Sub Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Carkit Mic", NULL), + SND_SOC_DAPM_MIC("Digital0 Mic", NULL), + SND_SOC_DAPM_MIC("Digital1 Mic", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Headset Stereophone: HSOL, HSOR */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, + /* External Speakers: HFL, HFR */ + {"Handsfree Spk", NULL, "HFL"}, + {"Handsfree Spk", NULL, "HFR"}, + /* External Speakers: PredrivL, PredrivR */ + {"Ext Spk", NULL, "PREDRIVEL"}, + {"Ext Spk", NULL, "PREDRIVER"}, + /* Carkit speakers: CARKITL, CARKITR */ + {"Carkit Spk", NULL, "CARKITL"}, + {"Carkit Spk", NULL, "CARKITR"}, + /* Earpiece */ + {"Earpiece Spk", NULL, "EARPIECE"}, + + /* External Mics: MAINMIC, SUBMIC with bias */ + {"MAINMIC", NULL, "Main Mic"}, + {"Main Mic", NULL, "Mic Bias 1"}, + {"SUBMIC", NULL, "Sub Mic"}, + {"Sub Mic", NULL, "Mic Bias 2"}, + /* Headset Mic: HSMIC with bias */ + {"HSMIC", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "Headset Mic Bias"}, + /* Digital Mics: DIGIMIC0, DIGIMIC1 with bias */ + {"DIGIMIC0", NULL, "Digital0 Mic"}, + {"Digital0 Mic", NULL, "Mic Bias 1"}, + {"DIGIMIC1", NULL, "Digital1 Mic"}, + {"Digital1 Mic", NULL, "Mic Bias 2"}, + /* Carkit In: CARKITMIC */ + {"CARKITMIC", NULL, "Carkit Mic"}, + /* Aux In: AUXL, AUXR */ + {"AUXL", NULL, "Line In"}, + {"AUXR", NULL, "Line In"}, +}; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headset Stereophone", + .mask = SND_JACK_HEADPHONE, + }, +}; + +/* Headset jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .name = "hsdet-gpio", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + }, +}; + +static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, + int connected, char *pin) +{ + if (!connected) + snd_soc_dapm_disable_pin(dapm, pin); +} + +static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = &card->dapm; + struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); + int ret = 0; + + /* Headset jack detection only if it is supported */ + if (priv->jack_detect > 0) { + hs_jack_gpios[0].gpio = priv->jack_detect; + + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET, &priv->hs_jack, + hs_jack_pins, + ARRAY_SIZE(hs_jack_pins)); + if (ret) + return ret; + + ret = snd_soc_jack_add_gpios(&priv->hs_jack, + ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + if (ret) + return ret; + } + + /* + * NULL pdata means we booted with DT. In this case the routing is + * provided and the card is fully routed, no need to mark pins. + */ + if (!pdata || !pdata->custom_routing) + return ret; + + /* Disable not connected paths if not used */ + twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); + twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); + twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); + twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); + twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); + + twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); + twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); + twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); + twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); + twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); + twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); + twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); + + return ret; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link omap_twl4030_dai_links[] = { + { + .name = "TWL4030 HiFi", + .stream_name = "TWL4030 HiFi", + .cpu_dai_name = "omap-mcbsp.2", + .codec_dai_name = "twl4030-hifi", + .platform_name = "omap-mcbsp.2", + .codec_name = "twl4030-codec", + .init = omap_twl4030_init, + .ops = &omap_twl4030_ops, + }, + { + .name = "TWL4030 Voice", + .stream_name = "TWL4030 Voice", + .cpu_dai_name = "omap-mcbsp.3", + .codec_dai_name = "twl4030-voice", + .platform_name = "omap-mcbsp.3", + .codec_name = "twl4030-codec", + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card omap_twl4030_card = { + .owner = THIS_MODULE, + .dai_link = omap_twl4030_dai_links, + .num_links = ARRAY_SIZE(omap_twl4030_dai_links), + + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int omap_twl4030_probe(struct platform_device *pdev) +{ + struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); + struct device_node *node = pdev->dev.of_node; + struct snd_soc_card *card = &omap_twl4030_card; + struct omap_twl4030 *priv; + int ret = 0; + + card->dev = &pdev->dev; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_twl4030), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + if (node) { + struct device_node *dai_node; + struct property *prop; + + if (snd_soc_of_parse_card_name(card, "ti,model")) { + dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + + dai_node = of_parse_phandle(node, "ti,mcbsp", 0); + if (!dai_node) { + dev_err(&pdev->dev, "McBSP node is not provided\n"); + return -EINVAL; + } + omap_twl4030_dai_links[0].cpu_dai_name = NULL; + omap_twl4030_dai_links[0].cpu_of_node = dai_node; + + omap_twl4030_dai_links[0].platform_name = NULL; + omap_twl4030_dai_links[0].platform_of_node = dai_node; + + dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); + if (!dai_node) { + card->num_links = 1; + } else { + omap_twl4030_dai_links[1].cpu_dai_name = NULL; + omap_twl4030_dai_links[1].cpu_of_node = dai_node; + + omap_twl4030_dai_links[1].platform_name = NULL; + omap_twl4030_dai_links[1].platform_of_node = dai_node; + } + + priv->jack_detect = of_get_named_gpio(node, + "ti,jack-det-gpio", 0); + + /* Optional: audio routing can be provided */ + prop = of_find_property(node, "ti,audio-routing", NULL); + if (prop) { + ret = snd_soc_of_parse_audio_routing(card, + "ti,audio-routing"); + if (ret) + return ret; + + card->fully_routed = 1; + } + } else if (pdata) { + if (pdata->card_name) { + card->name = pdata->card_name; + } else { + dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + + if (!pdata->voice_connected) + card->num_links = 1; + + priv->jack_detect = pdata->jack_detect; + } else { + dev_err(&pdev->dev, "Missing pdata\n"); + return -ENODEV; + } + + snd_soc_card_set_drvdata(card, priv); + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", + ret); + return ret; + } + + return 0; +} + +static const struct of_device_id omap_twl4030_of_match[] = { + {.compatible = "ti,omap-twl4030", }, + { }, +}; +MODULE_DEVICE_TABLE(of, omap_twl4030_of_match); + +static struct platform_driver omap_twl4030_driver = { + .driver = { + .name = "omap-twl4030", + .pm = &snd_soc_pm_ops, + .of_match_table = omap_twl4030_of_match, + }, + .probe = omap_twl4030_probe, +}; + +module_platform_driver(omap_twl4030_driver); + +MODULE_AUTHOR("Peter Ujfalusi "); +MODULE_DESCRIPTION("ALSA SoC for TI SoC based boards with twl4030 codec"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:omap-twl4030"); diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c new file mode 100644 index 000000000..4e3de7121 --- /dev/null +++ b/sound/soc/omap/omap3pandora.c @@ -0,0 +1,315 @@ +/* + * omap3pandora.c -- SoC audio for Pandora Handheld Console + * + * Author: Gražvydas Ignotas + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "omap-mcbsp.h" + +#define OMAP3_PANDORA_DAC_POWER_GPIO 118 +#define OMAP3_PANDORA_AMP_POWER_GPIO 14 + +#define PREFIX "ASoC omap3pandora: " + +static struct regulator *omap3pandora_dac_reg; + +static int omap3pandora_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err(PREFIX "can't set codec system clock\n"); + return ret; + } + + /* Set McBSP clock to external */ + ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_EXT, + 256 * params_rate(params), + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err(PREFIX "can't set cpu system clock\n"); + return ret; + } + + ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, 8); + if (ret < 0) { + pr_err(PREFIX "can't set SRG clock divider\n"); + return ret; + } + + return 0; +} + +static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret; + + /* + * The PCM1773 DAC datasheet requires 1ms delay between switching + * VCC power on/off and /PD pin high/low + */ + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = regulator_enable(omap3pandora_dac_reg); + if (ret) { + dev_err(w->dapm->dev, "Failed to power DAC: %d\n", ret); + return ret; + } + mdelay(1); + gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1); + } else { + gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0); + mdelay(1); + regulator_disable(omap3pandora_dac_reg); + } + + return 0; +} + +static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1); + else + gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0); + + return 0; +} + +/* + * Audio paths on Pandora board: + * + * |O| ---> PCM DAC +-> AMP -> Headphone Jack + * |M| A +--------> Line Out + * |A| <~~clk~~+ + * |P| <--- TWL4030 <--------- Line In and MICs + */ +static const struct snd_soc_dapm_widget omap3pandora_dapm_widgets[] = { + SND_SOC_DAPM_DAC_E("PCM DAC", "HiFi Playback", SND_SOC_NOPM, + 0, 0, omap3pandora_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Headphone Amplifier", SND_SOC_NOPM, + 0, 0, NULL, 0, omap3pandora_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + + SND_SOC_DAPM_MIC("Mic (internal)", NULL), + SND_SOC_DAPM_MIC("Mic (external)", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route omap3pandora_map[] = { + {"PCM DAC", NULL, "APLL Enable"}, + {"Headphone Amplifier", NULL, "PCM DAC"}, + {"Line Out", NULL, "PCM DAC"}, + {"Headphone Jack", NULL, "Headphone Amplifier"}, + + {"AUXL", NULL, "Line In"}, + {"AUXR", NULL, "Line In"}, + + {"MAINMIC", NULL, "Mic (internal)"}, + {"Mic (internal)", NULL, "Mic Bias 1"}, + + {"SUBMIC", NULL, "Mic (external)"}, + {"Mic (external)", NULL, "Mic Bias 2"}, +}; + +static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + + /* All TWL4030 output pins are floating */ + snd_soc_dapm_nc_pin(dapm, "EARPIECE"); + snd_soc_dapm_nc_pin(dapm, "PREDRIVEL"); + snd_soc_dapm_nc_pin(dapm, "PREDRIVER"); + snd_soc_dapm_nc_pin(dapm, "HSOL"); + snd_soc_dapm_nc_pin(dapm, "HSOR"); + snd_soc_dapm_nc_pin(dapm, "CARKITL"); + snd_soc_dapm_nc_pin(dapm, "CARKITR"); + snd_soc_dapm_nc_pin(dapm, "HFL"); + snd_soc_dapm_nc_pin(dapm, "HFR"); + snd_soc_dapm_nc_pin(dapm, "VIBRA"); + + return 0; +} + +static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + + /* Not comnnected */ + snd_soc_dapm_nc_pin(dapm, "HSMIC"); + snd_soc_dapm_nc_pin(dapm, "CARKITMIC"); + snd_soc_dapm_nc_pin(dapm, "DIGIMIC0"); + snd_soc_dapm_nc_pin(dapm, "DIGIMIC1"); + + return 0; +} + +static const struct snd_soc_ops omap3pandora_ops = { + .hw_params = omap3pandora_hw_params, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link omap3pandora_dai[] = { + { + .name = "PCM1773", + .stream_name = "HiFi Out", + .cpu_dai_name = "omap-mcbsp.2", + .codec_dai_name = "twl4030-hifi", + .platform_name = "omap-mcbsp.2", + .codec_name = "twl4030-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &omap3pandora_ops, + .init = omap3pandora_out_init, + }, { + .name = "TWL4030", + .stream_name = "Line/Mic In", + .cpu_dai_name = "omap-mcbsp.4", + .codec_dai_name = "twl4030-hifi", + .platform_name = "omap-mcbsp.4", + .codec_name = "twl4030-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &omap3pandora_ops, + .init = omap3pandora_in_init, + } +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_omap3pandora = { + .name = "omap3pandora", + .owner = THIS_MODULE, + .dai_link = omap3pandora_dai, + .num_links = ARRAY_SIZE(omap3pandora_dai), + + .dapm_widgets = omap3pandora_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(omap3pandora_dapm_widgets), + .dapm_routes = omap3pandora_map, + .num_dapm_routes = ARRAY_SIZE(omap3pandora_map), +}; + +static struct platform_device *omap3pandora_snd_device; + +static int __init omap3pandora_soc_init(void) +{ + int ret; + + if (!machine_is_omap3_pandora()) + return -ENODEV; + + pr_info("OMAP3 Pandora SoC init\n"); + + ret = gpio_request(OMAP3_PANDORA_DAC_POWER_GPIO, "dac_power"); + if (ret) { + pr_err(PREFIX "Failed to get DAC power GPIO\n"); + return ret; + } + + ret = gpio_direction_output(OMAP3_PANDORA_DAC_POWER_GPIO, 0); + if (ret) { + pr_err(PREFIX "Failed to set DAC power GPIO direction\n"); + goto fail0; + } + + ret = gpio_request(OMAP3_PANDORA_AMP_POWER_GPIO, "amp_power"); + if (ret) { + pr_err(PREFIX "Failed to get amp power GPIO\n"); + goto fail0; + } + + ret = gpio_direction_output(OMAP3_PANDORA_AMP_POWER_GPIO, 0); + if (ret) { + pr_err(PREFIX "Failed to set amp power GPIO direction\n"); + goto fail1; + } + + omap3pandora_snd_device = platform_device_alloc("soc-audio", -1); + if (omap3pandora_snd_device == NULL) { + pr_err(PREFIX "Platform device allocation failed\n"); + ret = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(omap3pandora_snd_device, &snd_soc_card_omap3pandora); + + ret = platform_device_add(omap3pandora_snd_device); + if (ret) { + pr_err(PREFIX "Unable to add platform device\n"); + goto fail2; + } + + omap3pandora_dac_reg = regulator_get(&omap3pandora_snd_device->dev, "vcc"); + if (IS_ERR(omap3pandora_dac_reg)) { + pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n", + dev_name(&omap3pandora_snd_device->dev), + PTR_ERR(omap3pandora_dac_reg)); + ret = PTR_ERR(omap3pandora_dac_reg); + goto fail3; + } + + return 0; + +fail3: + platform_device_del(omap3pandora_snd_device); +fail2: + platform_device_put(omap3pandora_snd_device); +fail1: + gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO); +fail0: + gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO); + return ret; +} +module_init(omap3pandora_soc_init); + +static void __exit omap3pandora_soc_exit(void) +{ + regulator_put(omap3pandora_dac_reg); + platform_device_unregister(omap3pandora_snd_device); + gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO); + gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO); +} +module_exit(omap3pandora_soc_exit); + +MODULE_AUTHOR("Grazvydas Ignotas "); +MODULE_DESCRIPTION("ALSA SoC OMAP3 Pandora"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c new file mode 100644 index 000000000..e4096779c --- /dev/null +++ b/sound/soc/omap/osk5912.c @@ -0,0 +1,187 @@ +/* + * osk5912.c -- SoC audio for OSK 5912 + * + * Copyright (C) 2008 Mistral Solutions + * + * Contact: Arun KS + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-mcbsp.h" +#include "../codecs/tlv320aic23.h" + +#define CODEC_CLOCK 12000000 + +static struct clk *tlv320aic23_mclk; + +static int osk_startup(struct snd_pcm_substream *substream) +{ + return clk_enable(tlv320aic23_mclk); +} + +static void osk_shutdown(struct snd_pcm_substream *substream) +{ + clk_disable(tlv320aic23_mclk); +} + +static int osk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int err; + + /* Set the codec system clock for DAC and ADC */ + err = + snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN); + + if (err < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return err; + } + + return err; +} + +static const struct snd_soc_ops osk_ops = { + .startup = osk_startup, + .hw_params = osk_hw_params, + .shutdown = osk_shutdown, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link osk_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .cpu_dai_name = "omap-mcbsp.1", + .codec_dai_name = "tlv320aic23-hifi", + .platform_name = "omap-mcbsp.1", + .codec_name = "tlv320aic23-codec", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &osk_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_card_osk = { + .name = "OSK5912", + .owner = THIS_MODULE, + .dai_link = &osk_dai, + .num_links = 1, + + .dapm_widgets = tlv320aic23_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct platform_device *osk_snd_device; + +static int __init osk_soc_init(void) +{ + int err; + u32 curRate; + struct device *dev; + + if (!(machine_is_omap_osk())) + return -ENODEV; + + osk_snd_device = platform_device_alloc("soc-audio", -1); + if (!osk_snd_device) + return -ENOMEM; + + platform_set_drvdata(osk_snd_device, &snd_soc_card_osk); + err = platform_device_add(osk_snd_device); + if (err) + goto err1; + + dev = &osk_snd_device->dev; + + tlv320aic23_mclk = clk_get(dev, "mclk"); + if (IS_ERR(tlv320aic23_mclk)) { + printk(KERN_ERR "Could not get mclk clock\n"); + err = PTR_ERR(tlv320aic23_mclk); + goto err2; + } + + /* + * Configure 12 MHz output on MCLK. + */ + curRate = (uint) clk_get_rate(tlv320aic23_mclk); + if (curRate != CODEC_CLOCK) { + if (clk_set_rate(tlv320aic23_mclk, CODEC_CLOCK)) { + printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n"); + err = -ECANCELED; + goto err3; + } + } + + printk(KERN_INFO "MCLK = %d [%d]\n", + (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK); + + return 0; + +err3: + clk_put(tlv320aic23_mclk); +err2: + platform_device_del(osk_snd_device); +err1: + platform_device_put(osk_snd_device); + + return err; + +} + +static void __exit osk_soc_exit(void) +{ + clk_put(tlv320aic23_mclk); + platform_device_unregister(osk_snd_device); +} + +module_init(osk_soc_init); +module_exit(osk_soc_exit); + +MODULE_AUTHOR("Arun KS "); +MODULE_DESCRIPTION("ALSA SoC OSK 5912"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c new file mode 100644 index 000000000..57448bd5a --- /dev/null +++ b/sound/soc/omap/rx51.c @@ -0,0 +1,493 @@ +/* + * rx51.c -- SoC audio for Nokia RX-51 + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi + * Eduardo Valentin + * Jarkko Nikula + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "omap-mcbsp.h" + +enum { + RX51_JACK_DISABLED, + RX51_JACK_TVOUT, /* tv-out with stereo output */ + RX51_JACK_HP, /* headphone: stereo output, no mic */ + RX51_JACK_HS, /* headset: stereo output with mic */ +}; + +struct rx51_audio_pdata { + struct gpio_desc *tvout_selection_gpio; + struct gpio_desc *jack_detection_gpio; + struct gpio_desc *eci_sw_gpio; + struct gpio_desc *speaker_amp_gpio; +}; + +static int rx51_spk_func; +static int rx51_dmic_func; +static int rx51_jack_func; + +static void rx51_ext_control(struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_card *card = dapm->card; + struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card); + int hp = 0, hs = 0, tvout = 0; + + switch (rx51_jack_func) { + case RX51_JACK_TVOUT: + tvout = 1; + hp = 1; + break; + case RX51_JACK_HS: + hs = 1; + case RX51_JACK_HP: + hp = 1; + break; + } + + snd_soc_dapm_mutex_lock(dapm); + + if (rx51_spk_func) + snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk"); + if (rx51_dmic_func) + snd_soc_dapm_enable_pin_unlocked(dapm, "DMic"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "DMic"); + if (hp) + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + if (hs) + snd_soc_dapm_enable_pin_unlocked(dapm, "HS Mic"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "HS Mic"); + + gpiod_set_value(pdata->tvout_selection_gpio, tvout); + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +static int rx51_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2); + rx51_ext_control(&card->dapm); + + return 0; +} + +static int rx51_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* Set the codec system clock for DAC and ADC */ + return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000, + SND_SOC_CLOCK_IN); +} + +static const struct snd_soc_ops rx51_ops = { + .startup = rx51_startup, + .hw_params = rx51_hw_params, +}; + +static int rx51_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = rx51_spk_func; + + return 0; +} + +static int rx51_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (rx51_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + rx51_spk_func = ucontrol->value.enumerated.item[0]; + rx51_ext_control(&card->dapm); + + return 1; +} + +static int rx51_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card); + + gpiod_set_raw_value_cansleep(pdata->speaker_amp_gpio, + !!SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static int rx51_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = rx51_dmic_func; + + return 0; +} + +static int rx51_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (rx51_dmic_func == ucontrol->value.enumerated.item[0]) + return 0; + + rx51_dmic_func = ucontrol->value.enumerated.item[0]; + rx51_ext_control(&card->dapm); + + return 1; +} + +static int rx51_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = rx51_jack_func; + + return 0; +} + +static int rx51_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (rx51_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + rx51_jack_func = ucontrol->value.enumerated.item[0]; + rx51_ext_control(&card->dapm); + + return 1; +} + +static struct snd_soc_jack rx51_av_jack; + +static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = { + { + .name = "avdet-gpio", + .report = SND_JACK_HEADSET, + .invert = 1, + .debounce_time = 200, + }, +}; + +static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event), + SND_SOC_DAPM_MIC("DMic", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("HS Mic", NULL), + SND_SOC_DAPM_LINE("FM Transmitter", NULL), + SND_SOC_DAPM_SPK("Earphone", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Ext Spk", NULL, "HPLOUT"}, + {"Ext Spk", NULL, "HPROUT"}, + {"Ext Spk", NULL, "HPLCOM"}, + {"Ext Spk", NULL, "HPRCOM"}, + {"FM Transmitter", NULL, "LLOUT"}, + {"FM Transmitter", NULL, "RLOUT"}, + + {"Headphone Jack", NULL, "TPA6130A2 HPLEFT"}, + {"Headphone Jack", NULL, "TPA6130A2 HPRIGHT"}, + {"TPA6130A2 LEFTIN", NULL, "LLOUT"}, + {"TPA6130A2 RIGHTIN", NULL, "RLOUT"}, + + {"DMic Rate 64", NULL, "DMic"}, + {"DMic", NULL, "Mic Bias"}, + + {"b LINE2R", NULL, "MONO_LOUT"}, + {"Earphone", NULL, "b HPLOUT"}, + + {"LINE1L", NULL, "HS Mic"}, + {"HS Mic", NULL, "b Mic Bias"}, +}; + +static const char * const spk_function[] = {"Off", "On"}; +static const char * const input_function[] = {"ADC", "Digital Mic"}; +static const char * const jack_function[] = { + "Off", "TV-OUT", "Headphone", "Headset" +}; + +static const struct soc_enum rx51_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), +}; + +static const struct snd_kcontrol_new aic34_rx51_controls[] = { + SOC_ENUM_EXT("Speaker Function", rx51_enum[0], + rx51_get_spk, rx51_set_spk), + SOC_ENUM_EXT("Input Select", rx51_enum[1], + rx51_get_input, rx51_set_input), + SOC_ENUM_EXT("Jack Function", rx51_enum[2], + rx51_get_jack, rx51_set_jack), + SOC_DAPM_PIN_SWITCH("FM Transmitter"), + SOC_DAPM_PIN_SWITCH("Earphone"), +}; + +static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card); + int err; + + snd_soc_limit_volume(card, "TPA6130A2 Headphone Playback Volume", 42); + + err = omap_mcbsp_st_add_controls(rtd, 2); + if (err < 0) { + dev_err(card->dev, "Failed to add MCBSP controls\n"); + return err; + } + + /* AV jack detection */ + err = snd_soc_card_jack_new(rtd->card, "AV Jack", + SND_JACK_HEADSET | SND_JACK_VIDEOOUT, + &rx51_av_jack, NULL, 0); + if (err) { + dev_err(card->dev, "Failed to add AV Jack\n"); + return err; + } + + /* prepare gpio for snd_soc_jack_add_gpios */ + rx51_av_jack_gpios[0].gpio = desc_to_gpio(pdata->jack_detection_gpio); + devm_gpiod_put(card->dev, pdata->jack_detection_gpio); + + err = snd_soc_jack_add_gpios(&rx51_av_jack, + ARRAY_SIZE(rx51_av_jack_gpios), + rx51_av_jack_gpios); + if (err) { + dev_err(card->dev, "Failed to add GPIOs\n"); + return err; + } + + return err; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx51_dai[] = { + { + .name = "TLV320AIC34", + .stream_name = "AIC34", + .cpu_dai_name = "omap-mcbsp.2", + .codec_dai_name = "tlv320aic3x-hifi", + .platform_name = "omap-mcbsp.2", + .codec_name = "tlv320aic3x-codec.2-0018", + .dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .init = rx51_aic34_init, + .ops = &rx51_ops, + }, +}; + +static struct snd_soc_aux_dev rx51_aux_dev[] = { + { + .name = "TLV320AIC34b", + .codec_name = "tlv320aic3x-codec.2-0019", + }, + { + .name = "TPA61320A2", + .codec_name = "tpa6130a2.2-0060", + }, +}; + +static struct snd_soc_codec_conf rx51_codec_conf[] = { + { + .dev_name = "tlv320aic3x-codec.2-0019", + .name_prefix = "b", + }, + { + .dev_name = "tpa6130a2.2-0060", + .name_prefix = "TPA6130A2", + }, +}; + +/* Audio card */ +static struct snd_soc_card rx51_sound_card = { + .name = "RX-51", + .owner = THIS_MODULE, + .dai_link = rx51_dai, + .num_links = ARRAY_SIZE(rx51_dai), + .aux_dev = rx51_aux_dev, + .num_aux_devs = ARRAY_SIZE(rx51_aux_dev), + .codec_conf = rx51_codec_conf, + .num_configs = ARRAY_SIZE(rx51_codec_conf), + .fully_routed = true, + + .controls = aic34_rx51_controls, + .num_controls = ARRAY_SIZE(aic34_rx51_controls), + .dapm_widgets = aic34_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aic34_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int rx51_soc_probe(struct platform_device *pdev) +{ + struct rx51_audio_pdata *pdata; + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &rx51_sound_card; + int err; + + if (!machine_is_nokia_rx51() && !of_machine_is_compatible("nokia,omap3-n900")) + return -ENODEV; + + card->dev = &pdev->dev; + + if (np) { + struct device_node *dai_node; + + dai_node = of_parse_phandle(np, "nokia,cpu-dai", 0); + if (!dai_node) { + dev_err(&pdev->dev, "McBSP node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].cpu_dai_name = NULL; + rx51_dai[0].platform_name = NULL; + rx51_dai[0].cpu_of_node = dai_node; + rx51_dai[0].platform_of_node = dai_node; + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 0); + if (!dai_node) { + dev_err(&pdev->dev, "Codec node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].codec_name = NULL; + rx51_dai[0].codec_of_node = dai_node; + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 1); + if (!dai_node) { + dev_err(&pdev->dev, "Auxiliary Codec node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[0].codec_name = NULL; + rx51_aux_dev[0].codec_of_node = dai_node; + rx51_codec_conf[0].dev_name = NULL; + rx51_codec_conf[0].of_node = dai_node; + + dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0); + if (!dai_node) { + dev_err(&pdev->dev, "Headphone amplifier node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[1].codec_name = NULL; + rx51_aux_dev[1].codec_of_node = dai_node; + rx51_codec_conf[1].dev_name = NULL; + rx51_codec_conf[1].of_node = dai_node; + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, pdata); + + pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, + "tvout-selection", + GPIOD_OUT_LOW); + if (IS_ERR(pdata->tvout_selection_gpio)) { + dev_err(card->dev, "could not get tvout selection gpio\n"); + return PTR_ERR(pdata->tvout_selection_gpio); + } + + pdata->jack_detection_gpio = devm_gpiod_get(card->dev, + "jack-detection", + GPIOD_ASIS); + if (IS_ERR(pdata->jack_detection_gpio)) { + dev_err(card->dev, "could not get jack detection gpio\n"); + return PTR_ERR(pdata->jack_detection_gpio); + } + + pdata->eci_sw_gpio = devm_gpiod_get(card->dev, "eci-switch", + GPIOD_OUT_HIGH); + if (IS_ERR(pdata->eci_sw_gpio)) { + dev_err(card->dev, "could not get eci switch gpio\n"); + return PTR_ERR(pdata->eci_sw_gpio); + } + + pdata->speaker_amp_gpio = devm_gpiod_get(card->dev, + "speaker-amplifier", + GPIOD_OUT_LOW); + if (IS_ERR(pdata->speaker_amp_gpio)) { + dev_err(card->dev, "could not get speaker enable gpio\n"); + return PTR_ERR(pdata->speaker_amp_gpio); + } + + err = devm_snd_soc_register_card(card->dev, card); + if (err) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", err); + return err; + } + + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id rx51_audio_of_match[] = { + { .compatible = "nokia,n900-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rx51_audio_of_match); +#endif + +static struct platform_driver rx51_soc_driver = { + .driver = { + .name = "rx51-audio", + .of_match_table = of_match_ptr(rx51_audio_of_match), + }, + .probe = rx51_soc_probe, +}; + +module_platform_driver(rx51_soc_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ALSA SoC Nokia RX-51"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rx51-audio"); diff --git a/sound/soc/omap/sdma-pcm.c b/sound/soc/omap/sdma-pcm.c new file mode 100644 index 000000000..21a9c2499 --- /dev/null +++ b/sound/soc/omap/sdma-pcm.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com + * Author: Peter Ujfalusi + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdma-pcm.h" + +static const struct snd_pcm_hardware sdma_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_INTERLEAVED, + .period_bytes_min = 32, + .period_bytes_max = 64 * 1024, + .buffer_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = 255, +}; + +static const struct snd_dmaengine_pcm_config sdma_dmaengine_pcm_config = { + .pcm_hardware = &sdma_pcm_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .compat_filter_fn = omap_dma_filter_fn, + .prealloc_buffer_size = 128 * 1024, +}; + +int sdma_pcm_platform_register(struct device *dev, + char *txdmachan, char *rxdmachan) +{ + struct snd_dmaengine_pcm_config *config; + unsigned int flags = SND_DMAENGINE_PCM_FLAG_COMPAT; + + /* Standard names for the directions: 'tx' and 'rx' */ + if (!txdmachan && !rxdmachan) + return devm_snd_dmaengine_pcm_register(dev, + &sdma_dmaengine_pcm_config, + flags); + + config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL); + if (!config) + return -ENOMEM; + + *config = sdma_dmaengine_pcm_config; + + if (!txdmachan || !rxdmachan) { + /* One direction only PCM */ + flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; + if (!txdmachan) { + txdmachan = rxdmachan; + rxdmachan = NULL; + } + } + + config->chan_names[0] = txdmachan; + config->chan_names[1] = rxdmachan; + + return devm_snd_dmaengine_pcm_register(dev, config, flags); +} +EXPORT_SYMBOL_GPL(sdma_pcm_platform_register); + +MODULE_AUTHOR("Peter Ujfalusi "); +MODULE_DESCRIPTION("sDMA PCM ASoC platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/omap/sdma-pcm.h b/sound/soc/omap/sdma-pcm.h new file mode 100644 index 000000000..34a7f90b2 --- /dev/null +++ b/sound/soc/omap/sdma-pcm.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com + * Author: Peter Ujfalusi + */ + +#ifndef __SDMA_PCM_H__ +#define __SDMA_PCM_H__ + +#if IS_ENABLED(CONFIG_SND_SDMA_SOC) +int sdma_pcm_platform_register(struct device *dev, + char *txdmachan, char *rxdmachan); +#else +static inline int sdma_pcm_platform_register(struct device *dev, + char *txdmachan, char *rxdmachan) +{ + return -ENODEV; +} +#endif /* CONFIG_SND_SDMA_SOC */ + +#endif /* __SDMA_PCM_H__ */ diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig new file mode 100644 index 000000000..776e148b0 --- /dev/null +++ b/sound/soc/pxa/Kconfig @@ -0,0 +1,221 @@ +config SND_PXA2XX_SOC + tristate "SoC Audio for the Intel PXA2xx chip" + depends on ARCH_PXA || COMPILE_TEST + select SND_PXA2XX_LIB + help + Say Y or M if you want to add support for codecs attached to + the PXA2xx AC97, I2S or SSP interface. You will also need + to select the audio interfaces to support below. + +config SND_MMP_SOC + bool "Soc Audio for Marvell MMP chips" + depends on ARCH_MMP + select MMP_SRAM + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_ARM + help + Say Y if you want to add support for codecs attached to + the MMP SSPA interface. + +config SND_PXA2XX_AC97 + tristate + select SND_AC97_CODEC + +config SND_PXA2XX_SOC_AC97 + tristate + select AC97_BUS + select SND_PXA2XX_LIB + select SND_PXA2XX_LIB_AC97 + select SND_SOC_AC97_BUS + +config SND_PXA2XX_SOC_I2S + select SND_PXA2XX_LIB + tristate + +config SND_PXA_SOC_SSP + tristate "Soc Audio via PXA2xx/PXA3xx SSP ports" + depends on PLAT_PXA + select PXA_SSP + select SND_PXA2XX_LIB + +config SND_MMP_SOC_SSPA + tristate + +config SND_PXA2XX_SOC_CORGI + tristate "SoC Audio support for Sharp Zaurus SL-C7x0" + depends on SND_PXA2XX_SOC && PXA_SHARP_C7xx && I2C + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8731 + help + Say Y if you want to add support for SoC audio on Sharp + Zaurus SL-C7x0 models (Corgi, Shepherd, Husky). + +config SND_PXA2XX_SOC_SPITZ + tristate "SoC Audio support for Sharp Zaurus SL-Cxx00" + depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00 && I2C + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8750 + help + Say Y if you want to add support for SoC audio on Sharp + Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita). + +config SND_PXA2XX_SOC_Z2 + tristate "SoC Audio support for Zipit Z2" + depends on SND_PXA2XX_SOC && MACH_ZIPIT2 && I2C + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8750 + help + Say Y if you want to add support for SoC audio on Zipit Z2. + +config SND_PXA2XX_SOC_POODLE + tristate "SoC Audio support for Poodle" + depends on SND_PXA2XX_SOC && MACH_POODLE && I2C + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8731 + help + Say Y if you want to add support for SoC audio on Sharp + Zaurus SL-5600 model (Poodle). + +config SND_PXA2XX_SOC_TOSA + tristate "SoC AC97 Audio support for Tosa" + depends on SND_PXA2XX_SOC && MACH_TOSA + depends on MFD_TC6393XB + select SND_PXA2XX_SOC_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for SoC audio on Sharp + Zaurus SL-C6000x models (Tosa). + +config SND_PXA2XX_SOC_E740 + tristate "SoC AC97 Audio support for e740" + depends on SND_PXA2XX_SOC && MACH_E740 + select SND_SOC_WM9705 + select SND_PXA2XX_SOC_AC97 + help + Say Y if you want to add support for SoC audio on the + toshiba e740 PDA + +config SND_PXA2XX_SOC_E750 + tristate "SoC AC97 Audio support for e750" + depends on SND_PXA2XX_SOC && MACH_E750 + select SND_SOC_WM9705 + select SND_PXA2XX_SOC_AC97 + help + Say Y if you want to add support for SoC audio on the + toshiba e750 PDA + +config SND_PXA2XX_SOC_E800 + tristate "SoC AC97 Audio support for e800" + depends on SND_PXA2XX_SOC && MACH_E800 + select SND_SOC_WM9712 + select SND_PXA2XX_SOC_AC97 + help + Say Y if you want to add support for SoC audio on the + Toshiba e800 PDA + +config SND_PXA2XX_SOC_EM_X270 + tristate "SoC Audio support for CompuLab EM-x270, eXeda and CM-X300" + depends on SND_PXA2XX_SOC && (MACH_EM_X270 || MACH_EXEDA || \ + MACH_CM_X300) + select SND_PXA2XX_SOC_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for SoC audio on + CompuLab EM-x270, eXeda and CM-X300 machines. + +config SND_PXA2XX_SOC_PALM27X + bool "SoC Audio support for Palm T|X, T5, E2 and LifeDrive" + depends on SND_PXA2XX_SOC && (MACH_PALMLD || MACH_PALMTX || \ + MACH_PALMT5 || MACH_PALMTE2) + select SND_PXA2XX_SOC_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for SoC audio on + Palm T|X, T5, E2 or LifeDrive handheld computer. + +config SND_PXA910_SOC + tristate "SoC Audio for Marvell PXA910 chip" + depends on ARCH_MMP && SND + select SND_PCM + help + Say Y if you want to add support for SoC audio on the + Marvell PXA910 reference platform. + +config SND_SOC_TTC_DKB + tristate "SoC Audio support for TTC DKB" + depends on SND_PXA910_SOC && MACH_TTC_DKB && I2C=y + select PXA_SSP + select SND_PXA_SOC_SSP + select SND_MMP_SOC + select MFD_88PM860X + select SND_SOC_88PM860X + help + Say Y if you want to add support for SoC audio on TTC DKB + + +config SND_SOC_ZYLONITE + tristate "SoC Audio support for Marvell Zylonite" + depends on SND_PXA2XX_SOC && MACH_ZYLONITE + select SND_PXA2XX_SOC_AC97 + select SND_PXA_SOC_SSP + select SND_SOC_WM9713 + help + Say Y if you want to add support for SoC audio on the + Marvell Zylonite reference platform. + +config SND_SOC_RAUMFELD + tristate "SoC Audio support Raumfeld audio adapter" + depends on SND_PXA2XX_SOC && (MACH_RAUMFELD_SPEAKER || MACH_RAUMFELD_CONNECTOR) + depends on I2C && SPI_MASTER + select SND_PXA_SOC_SSP + select SND_SOC_CS4270 + select SND_SOC_AK4104 + help + Say Y if you want to add support for SoC audio on Raumfeld devices + +config SND_PXA2XX_SOC_HX4700 + tristate "SoC Audio support for HP iPAQ hx4700" + depends on SND_PXA2XX_SOC && MACH_H4700 && I2C + select SND_PXA2XX_SOC_I2S + select SND_SOC_AK4641 + help + Say Y if you want to add support for SoC audio on the + HP iPAQ hx4700. + +config SND_PXA2XX_SOC_MAGICIAN + tristate "SoC Audio support for HTC Magician" + depends on SND_PXA2XX_SOC && MACH_MAGICIAN && I2C + select SND_PXA2XX_SOC_I2S + select SND_PXA_SOC_SSP + select SND_SOC_UDA1380 + help + Say Y if you want to add support for SoC audio on the + HTC Magician. + +config SND_PXA2XX_SOC_MIOA701 + tristate "SoC Audio support for MIO A701" + depends on SND_PXA2XX_SOC && MACH_MIOA701 + select SND_PXA2XX_SOC_AC97 + select SND_SOC_WM9713 + help + Say Y if you want to add support for SoC audio on the + MIO A701. + +config SND_PXA2XX_SOC_IMOTE2 + tristate "SoC Audio support for IMote 2" + depends on SND_PXA2XX_SOC && MACH_INTELMOTE2 && I2C + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8940 + help + Say Y if you want to add support for SoC audio on the + IMote 2. + +config SND_MMP_SOC_BROWNSTONE + tristate "SoC Audio support for Marvell Brownstone" + depends on SND_MMP_SOC && MACH_BROWNSTONE && I2C + select SND_MMP_SOC_SSPA + select MFD_WM8994 + select SND_SOC_WM8994 + help + Say Y if you want to add support for SoC audio on the + Marvell Brownstone reference platform. diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile new file mode 100644 index 000000000..5b265662f --- /dev/null +++ b/sound/soc/pxa/Makefile @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: GPL-2.0 +# PXA Platform Support +snd-soc-pxa2xx-objs := pxa2xx-pcm.o +snd-soc-pxa2xx-ac97-objs := pxa2xx-ac97.o +snd-soc-pxa2xx-i2s-objs := pxa2xx-i2s.o +snd-soc-pxa-ssp-objs := pxa-ssp.o +snd-soc-mmp-objs := mmp-pcm.o +snd-soc-mmp-sspa-objs := mmp-sspa.o + +obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o +obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o +obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o +obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o +obj-$(CONFIG_SND_MMP_SOC) += snd-soc-mmp.o +obj-$(CONFIG_SND_MMP_SOC_SSPA) += snd-soc-mmp-sspa.o + +# PXA Machine Support +snd-soc-corgi-objs := corgi.o +snd-soc-poodle-objs := poodle.o +snd-soc-tosa-objs := tosa.o +snd-soc-e740-objs := e740_wm9705.o +snd-soc-e750-objs := e750_wm9705.o +snd-soc-e800-objs := e800_wm9712.o +snd-soc-spitz-objs := spitz.o +snd-soc-em-x270-objs := em-x270.o +snd-soc-palm27x-objs := palm27x.o +snd-soc-zylonite-objs := zylonite.o +snd-soc-hx4700-objs := hx4700.o +snd-soc-magician-objs := magician.o +snd-soc-mioa701-objs := mioa701_wm9713.o +snd-soc-z2-objs := z2.o +snd-soc-imote2-objs := imote2.o +snd-soc-raumfeld-objs := raumfeld.o +snd-soc-brownstone-objs := brownstone.o +snd-soc-ttc-dkb-objs := ttc-dkb.o + +obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o +obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o +obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o +obj-$(CONFIG_SND_PXA2XX_SOC_E740) += snd-soc-e740.o +obj-$(CONFIG_SND_PXA2XX_SOC_E750) += snd-soc-e750.o +obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o +obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o +obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o +obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o +obj-$(CONFIG_SND_PXA2XX_SOC_HX4700) += snd-soc-hx4700.o +obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o +obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o +obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o +obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o +obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o +obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o +obj-$(CONFIG_SND_MMP_SOC_BROWNSTONE) += snd-soc-brownstone.o +obj-$(CONFIG_SND_SOC_TTC_DKB) += snd-soc-ttc-dkb.o diff --git a/sound/soc/pxa/brownstone.c b/sound/soc/pxa/brownstone.c new file mode 100644 index 000000000..9a3f5b799 --- /dev/null +++ b/sound/soc/pxa/brownstone.c @@ -0,0 +1,136 @@ +/* + * linux/sound/soc/pxa/brownstone.c + * + * Copyright (C) 2011 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#include "../codecs/wm8994.h" +#include "mmp-sspa.h" + +static const struct snd_kcontrol_new brownstone_dapm_control[] = { + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static const struct snd_soc_dapm_widget brownstone_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Main Mic", NULL), +}; + +static const struct snd_soc_dapm_route brownstone_audio_map[] = { + {"Ext Spk", NULL, "SPKOUTLP"}, + {"Ext Spk", NULL, "SPKOUTLN"}, + {"Ext Spk", NULL, "SPKOUTRP"}, + {"Ext Spk", NULL, "SPKOUTRN"}, + + {"Headset Stereophone", NULL, "HPOUT1L"}, + {"Headset Stereophone", NULL, "HPOUT1R"}, + + {"IN1RN", NULL, "Headset Mic"}, + + {"DMIC1DAT", NULL, "MICBIAS1"}, + {"MICBIAS1", NULL, "Main Mic"}, +}; + +static int brownstone_wm8994_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int freq_out, sspa_mclk, sysclk; + + if (params_rate(params) > 11025) { + freq_out = params_rate(params) * 512; + sysclk = params_rate(params) * 256; + sspa_mclk = params_rate(params) * 64; + } else { + freq_out = params_rate(params) * 1024; + sysclk = params_rate(params) * 512; + sspa_mclk = params_rate(params) * 64; + } + + snd_soc_dai_set_sysclk(cpu_dai, MMP_SSPA_CLK_AUDIO, freq_out, 0); + snd_soc_dai_set_pll(cpu_dai, MMP_SYSCLK, 0, freq_out, sysclk); + snd_soc_dai_set_pll(cpu_dai, MMP_SSPA_CLK, 0, freq_out, sspa_mclk); + + /* set wm8994 sysclk */ + snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, sysclk, 0); + + return 0; +} + +/* machine stream operations */ +static const struct snd_soc_ops brownstone_ops = { + .hw_params = brownstone_wm8994_hw_params, +}; + +static struct snd_soc_dai_link brownstone_wm8994_dai[] = { +{ + .name = "WM8994", + .stream_name = "WM8994 HiFi", + .cpu_dai_name = "mmp-sspa-dai.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "mmp-pcm-audio", + .codec_name = "wm8994-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &brownstone_ops, +}, +}; + +/* audio machine driver */ +static struct snd_soc_card brownstone = { + .name = "brownstone", + .owner = THIS_MODULE, + .dai_link = brownstone_wm8994_dai, + .num_links = ARRAY_SIZE(brownstone_wm8994_dai), + + .controls = brownstone_dapm_control, + .num_controls = ARRAY_SIZE(brownstone_dapm_control), + .dapm_widgets = brownstone_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(brownstone_dapm_widgets), + .dapm_routes = brownstone_audio_map, + .num_dapm_routes = ARRAY_SIZE(brownstone_audio_map), + .fully_routed = true, +}; + +static int brownstone_probe(struct platform_device *pdev) +{ + int ret; + + brownstone.dev = &pdev->dev; + ret = devm_snd_soc_register_card(&pdev->dev, &brownstone); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + return ret; +} + +static struct platform_driver mmp_driver = { + .driver = { + .name = "brownstone-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = brownstone_probe, +}; + +module_platform_driver(mmp_driver); + +MODULE_AUTHOR("Leo Yan "); +MODULE_DESCRIPTION("ALSA SoC Brownstone"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:brownstone-audio"); diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c new file mode 100644 index 000000000..054e0d65d --- /dev/null +++ b/sound/soc/pxa/corgi.c @@ -0,0 +1,319 @@ +/* + * corgi.c -- SoC audio for Corgi + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood + * Richard Purdie + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm8731.h" +#include "pxa2xx-i2s.h" + +#define CORGI_HP 0 +#define CORGI_MIC 1 +#define CORGI_LINE 2 +#define CORGI_HEADSET 3 +#define CORGI_HP_OFF 4 +#define CORGI_SPK_ON 0 +#define CORGI_SPK_OFF 1 + + /* audio clock in Hz - rounded from 12.235MHz */ +#define CORGI_AUDIO_CLOCK 12288000 + +static int corgi_jack_func; +static int corgi_spk_func; + +static void corgi_ext_control(struct snd_soc_dapm_context *dapm) +{ + snd_soc_dapm_mutex_lock(dapm); + + /* set up jack connection */ + switch (corgi_jack_func) { + case CORGI_HP: + /* set = unmute headphone */ + gpio_set_value(CORGI_GPIO_MUTE_L, 1); + gpio_set_value(CORGI_GPIO_MUTE_R, 1); + snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + break; + case CORGI_MIC: + /* reset = mute headphone */ + gpio_set_value(CORGI_GPIO_MUTE_L, 0); + gpio_set_value(CORGI_GPIO_MUTE_R, 0); + snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + break; + case CORGI_LINE: + gpio_set_value(CORGI_GPIO_MUTE_L, 0); + gpio_set_value(CORGI_GPIO_MUTE_R, 0); + snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Line Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + break; + case CORGI_HEADSET: + gpio_set_value(CORGI_GPIO_MUTE_L, 0); + gpio_set_value(CORGI_GPIO_MUTE_R, 1); + snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Jack"); + break; + } + + if (corgi_spk_func == CORGI_SPK_ON) + snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk"); + + /* signal a DAPM event */ + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +static int corgi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* check the jack status at stream startup */ + corgi_ext_control(&rtd->card->dapm); + + return 0; +} + +/* we need to unmute the HP at shutdown as the mute burns power on corgi */ +static void corgi_shutdown(struct snd_pcm_substream *substream) +{ + /* set = unmute headphone */ + gpio_set_value(CORGI_GPIO_MUTE_L, 1); + gpio_set_value(CORGI_GPIO_MUTE_R, 1); +} + +static int corgi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops corgi_ops = { + .startup = corgi_startup, + .hw_params = corgi_hw_params, + .shutdown = corgi_shutdown, +}; + +static int corgi_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = corgi_jack_func; + return 0; +} + +static int corgi_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (corgi_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + corgi_jack_func = ucontrol->value.enumerated.item[0]; + corgi_ext_control(&card->dapm); + return 1; +} + +static int corgi_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = corgi_spk_func; + return 0; +} + +static int corgi_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (corgi_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + corgi_spk_func = ucontrol->value.enumerated.item[0]; + corgi_ext_control(&card->dapm); + return 1; +} + +static int corgi_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int corgi_mic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(CORGI_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +/* corgi machine dapm widgets */ +static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { +SND_SOC_DAPM_HP("Headphone Jack", NULL), +SND_SOC_DAPM_MIC("Mic Jack", corgi_mic_event), +SND_SOC_DAPM_SPK("Ext Spk", corgi_amp_event), +SND_SOC_DAPM_LINE("Line Jack", NULL), +SND_SOC_DAPM_HP("Headset Jack", NULL), +}; + +/* Corgi machine audio map (connections to the codec pins) */ +static const struct snd_soc_dapm_route corgi_audio_map[] = { + + /* headset Jack - in = micin, out = LHPOUT*/ + {"Headset Jack", NULL, "LHPOUT"}, + + /* headphone connected to LHPOUT1, RHPOUT1 */ + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + /* speaker connected to LOUT, ROUT */ + {"Ext Spk", NULL, "ROUT"}, + {"Ext Spk", NULL, "LOUT"}, + + /* mic is connected to MICIN (via right channel of headphone jack) */ + {"MICIN", NULL, "Mic Jack"}, + + /* Same as the above but no mic bias for line signals */ + {"MICIN", NULL, "Line Jack"}, +}; + +static const char * const jack_function[] = {"Headphone", "Mic", "Line", + "Headset", "Off"}; +static const char * const spk_function[] = {"On", "Off"}; +static const struct soc_enum corgi_enum[] = { + SOC_ENUM_SINGLE_EXT(5, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static const struct snd_kcontrol_new wm8731_corgi_controls[] = { + SOC_ENUM_EXT("Jack Function", corgi_enum[0], corgi_get_jack, + corgi_set_jack), + SOC_ENUM_EXT("Speaker Function", corgi_enum[1], corgi_get_spk, + corgi_set_spk), +}; + +/* corgi digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link corgi_dai = { + .name = "WM8731", + .stream_name = "WM8731", + .cpu_dai_name = "pxa2xx-i2s", + .codec_dai_name = "wm8731-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm8731.0-001b", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &corgi_ops, +}; + +/* corgi audio machine driver */ +static struct snd_soc_card corgi = { + .name = "Corgi", + .owner = THIS_MODULE, + .dai_link = &corgi_dai, + .num_links = 1, + + .controls = wm8731_corgi_controls, + .num_controls = ARRAY_SIZE(wm8731_corgi_controls), + .dapm_widgets = wm8731_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), + .dapm_routes = corgi_audio_map, + .num_dapm_routes = ARRAY_SIZE(corgi_audio_map), + .fully_routed = true, +}; + +static int corgi_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &corgi; + int ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + return ret; +} + +static struct platform_driver corgi_driver = { + .driver = { + .name = "corgi-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = corgi_probe, +}; + +module_platform_driver(corgi_driver); + +/* Module information */ +MODULE_AUTHOR("Richard Purdie"); +MODULE_DESCRIPTION("ALSA SoC Corgi"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:corgi-audio"); diff --git a/sound/soc/pxa/e740_wm9705.c b/sound/soc/pxa/e740_wm9705.c new file mode 100644 index 000000000..8ab703263 --- /dev/null +++ b/sound/soc/pxa/e740_wm9705.c @@ -0,0 +1,167 @@ +/* + * e740-wm9705.c -- SoC audio for e740 + * + * Copyright 2007 (c) Ian Molton + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 ONLY. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#define E740_AUDIO_OUT 1 +#define E740_AUDIO_IN 2 + +static int e740_audio_power; + +static void e740_sync_audio_power(int status) +{ + gpio_set_value(GPIO_E740_WM9705_nAVDD2, !status); + gpio_set_value(GPIO_E740_AMP_ON, (status & E740_AUDIO_OUT) ? 1 : 0); + gpio_set_value(GPIO_E740_MIC_ON, (status & E740_AUDIO_IN) ? 1 : 0); +} + +static int e740_mic_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (event & SND_SOC_DAPM_PRE_PMU) + e740_audio_power |= E740_AUDIO_IN; + else if (event & SND_SOC_DAPM_POST_PMD) + e740_audio_power &= ~E740_AUDIO_IN; + + e740_sync_audio_power(e740_audio_power); + + return 0; +} + +static int e740_output_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (event & SND_SOC_DAPM_PRE_PMU) + e740_audio_power |= E740_AUDIO_OUT; + else if (event & SND_SOC_DAPM_POST_PMD) + e740_audio_power &= ~E740_AUDIO_OUT; + + e740_sync_audio_power(e740_audio_power); + + return 0; +} + +static const struct snd_soc_dapm_widget e740_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic (Internal)", NULL), + SND_SOC_DAPM_PGA_E("Output Amp", SND_SOC_NOPM, 0, 0, NULL, 0, + e740_output_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("Mic Amp", SND_SOC_NOPM, 0, 0, NULL, 0, + e740_mic_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Output Amp", NULL, "LOUT"}, + {"Output Amp", NULL, "ROUT"}, + {"Output Amp", NULL, "MONOOUT"}, + + {"Speaker", NULL, "Output Amp"}, + {"Headphone Jack", NULL, "Output Amp"}, + + {"MIC1", NULL, "Mic Amp"}, + {"Mic Amp", NULL, "Mic (Internal)"}, +}; + +static struct snd_soc_dai_link e740_dai[] = { + { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9705-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9705-codec", + }, + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9705-aux", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9705-codec", + }, +}; + +static struct snd_soc_card e740 = { + .name = "Toshiba e740", + .owner = THIS_MODULE, + .dai_link = e740_dai, + .num_links = ARRAY_SIZE(e740_dai), + + .dapm_widgets = e740_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(e740_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, +}; + +static struct gpio e740_audio_gpios[] = { + { GPIO_E740_MIC_ON, GPIOF_OUT_INIT_LOW, "Mic amp" }, + { GPIO_E740_AMP_ON, GPIOF_OUT_INIT_LOW, "Output amp" }, + { GPIO_E740_WM9705_nAVDD2, GPIOF_OUT_INIT_HIGH, "Audio power" }, +}; + +static int e740_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &e740; + int ret; + + ret = gpio_request_array(e740_audio_gpios, + ARRAY_SIZE(e740_audio_gpios)); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + gpio_free_array(e740_audio_gpios, ARRAY_SIZE(e740_audio_gpios)); + } + return ret; +} + +static int e740_remove(struct platform_device *pdev) +{ + gpio_free_array(e740_audio_gpios, ARRAY_SIZE(e740_audio_gpios)); + return 0; +} + +static struct platform_driver e740_driver = { + .driver = { + .name = "e740-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = e740_probe, + .remove = e740_remove, +}; + +module_platform_driver(e740_driver); + +/* Module information */ +MODULE_AUTHOR("Ian Molton "); +MODULE_DESCRIPTION("ALSA SoC driver for e740"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:e740-audio"); diff --git a/sound/soc/pxa/e750_wm9705.c b/sound/soc/pxa/e750_wm9705.c new file mode 100644 index 000000000..82bcbbb18 --- /dev/null +++ b/sound/soc/pxa/e750_wm9705.c @@ -0,0 +1,150 @@ +/* + * e750-wm9705.c -- SoC audio for e750 + * + * Copyright 2007 (c) Ian Molton + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 ONLY. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +static int e750_spk_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (event & SND_SOC_DAPM_PRE_PMU) + gpio_set_value(GPIO_E750_SPK_AMP_OFF, 0); + else if (event & SND_SOC_DAPM_POST_PMD) + gpio_set_value(GPIO_E750_SPK_AMP_OFF, 1); + + return 0; +} + +static int e750_hp_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (event & SND_SOC_DAPM_PRE_PMU) + gpio_set_value(GPIO_E750_HP_AMP_OFF, 0); + else if (event & SND_SOC_DAPM_POST_PMD) + gpio_set_value(GPIO_E750_HP_AMP_OFF, 1); + + return 0; +} + +static const struct snd_soc_dapm_widget e750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_MIC("Mic (Internal)", NULL), + SND_SOC_DAPM_PGA_E("Headphone Amp", SND_SOC_NOPM, 0, 0, NULL, 0, + e750_hp_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("Speaker Amp", SND_SOC_NOPM, 0, 0, NULL, 0, + e750_spk_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Amp", NULL, "HPOUTL"}, + {"Headphone Amp", NULL, "HPOUTR"}, + {"Headphone Jack", NULL, "Headphone Amp"}, + + {"Speaker Amp", NULL, "MONOOUT"}, + {"Speaker", NULL, "Speaker Amp"}, + + {"MIC1", NULL, "Mic (Internal)"}, +}; + +static struct snd_soc_dai_link e750_dai[] = { + { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9705-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9705-codec", + /* use ops to check startup state */ + }, + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9705-aux", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9705-codec", + }, +}; + +static struct snd_soc_card e750 = { + .name = "Toshiba e750", + .owner = THIS_MODULE, + .dai_link = e750_dai, + .num_links = ARRAY_SIZE(e750_dai), + + .dapm_widgets = e750_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(e750_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, +}; + +static struct gpio e750_audio_gpios[] = { + { GPIO_E750_HP_AMP_OFF, GPIOF_OUT_INIT_HIGH, "Headphone amp" }, + { GPIO_E750_SPK_AMP_OFF, GPIOF_OUT_INIT_HIGH, "Speaker amp" }, +}; + +static int e750_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &e750; + int ret; + + ret = gpio_request_array(e750_audio_gpios, + ARRAY_SIZE(e750_audio_gpios)); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + gpio_free_array(e750_audio_gpios, ARRAY_SIZE(e750_audio_gpios)); + } + return ret; +} + +static int e750_remove(struct platform_device *pdev) +{ + gpio_free_array(e750_audio_gpios, ARRAY_SIZE(e750_audio_gpios)); + return 0; +} + +static struct platform_driver e750_driver = { + .driver = { + .name = "e750-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = e750_probe, + .remove = e750_remove, +}; + +module_platform_driver(e750_driver); + +/* Module information */ +MODULE_AUTHOR("Ian Molton "); +MODULE_DESCRIPTION("ALSA SoC driver for e750"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:e750-audio"); diff --git a/sound/soc/pxa/e800_wm9712.c b/sound/soc/pxa/e800_wm9712.c new file mode 100644 index 000000000..1ed8aa234 --- /dev/null +++ b/sound/soc/pxa/e800_wm9712.c @@ -0,0 +1,149 @@ +/* + * e800-wm9712.c -- SoC audio for e800 + * + * Copyright 2007 (c) Ian Molton + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 ONLY. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +static int e800_spk_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (event & SND_SOC_DAPM_PRE_PMU) + gpio_set_value(GPIO_E800_SPK_AMP_ON, 1); + else if (event & SND_SOC_DAPM_POST_PMD) + gpio_set_value(GPIO_E800_SPK_AMP_ON, 0); + + return 0; +} + +static int e800_hp_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (event & SND_SOC_DAPM_PRE_PMU) + gpio_set_value(GPIO_E800_HP_AMP_OFF, 0); + else if (event & SND_SOC_DAPM_POST_PMD) + gpio_set_value(GPIO_E800_HP_AMP_OFF, 1); + + return 0; +} + +static const struct snd_soc_dapm_widget e800_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic (Internal1)", NULL), + SND_SOC_DAPM_MIC("Mic (Internal2)", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_PGA_E("Headphone Amp", SND_SOC_NOPM, 0, 0, NULL, 0, + e800_hp_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("Speaker Amp", SND_SOC_NOPM, 0, 0, NULL, 0, + e800_spk_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "HPOUTL"}, + {"Headphone Jack", NULL, "HPOUTR"}, + {"Headphone Jack", NULL, "Headphone Amp"}, + + {"Speaker Amp", NULL, "MONOOUT"}, + {"Speaker", NULL, "Speaker Amp"}, + + {"MIC1", NULL, "Mic (Internal1)"}, + {"MIC2", NULL, "Mic (Internal2)"}, +}; + +static struct snd_soc_dai_link e800_dai[] = { + { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9712-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9712-codec", + }, + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9712-aux", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9712-codec", + }, +}; + +static struct snd_soc_card e800 = { + .name = "Toshiba e800", + .owner = THIS_MODULE, + .dai_link = e800_dai, + .num_links = ARRAY_SIZE(e800_dai), + + .dapm_widgets = e800_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(e800_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct gpio e800_audio_gpios[] = { + { GPIO_E800_SPK_AMP_ON, GPIOF_OUT_INIT_HIGH, "Headphone amp" }, + { GPIO_E800_HP_AMP_OFF, GPIOF_OUT_INIT_HIGH, "Speaker amp" }, +}; + +static int e800_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &e800; + int ret; + + ret = gpio_request_array(e800_audio_gpios, + ARRAY_SIZE(e800_audio_gpios)); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + gpio_free_array(e800_audio_gpios, ARRAY_SIZE(e800_audio_gpios)); + } + return ret; +} + +static int e800_remove(struct platform_device *pdev) +{ + gpio_free_array(e800_audio_gpios, ARRAY_SIZE(e800_audio_gpios)); + return 0; +} + +static struct platform_driver e800_driver = { + .driver = { + .name = "e800-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = e800_probe, + .remove = e800_remove, +}; + +module_platform_driver(e800_driver); + +/* Module information */ +MODULE_AUTHOR("Ian Molton "); +MODULE_DESCRIPTION("ALSA SoC driver for e800"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:e800-audio"); diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c new file mode 100644 index 000000000..e046770ce --- /dev/null +++ b/sound/soc/pxa/em-x270.c @@ -0,0 +1,93 @@ +/* + * SoC audio driver for EM-X270, eXeda and CM-X300 + * + * Copyright 2007, 2009 CompuLab, Ltd. + * + * Author: Mike Rapoport + * + * Copied from tosa.c: + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood + * Richard Purdie + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +static struct snd_soc_dai_link em_x270_dai[] = { + { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9712-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9712-codec", + }, + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9712-aux", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9712-codec", + }, +}; + +static struct snd_soc_card em_x270 = { + .name = "EM-X270", + .owner = THIS_MODULE, + .dai_link = em_x270_dai, + .num_links = ARRAY_SIZE(em_x270_dai), +}; + +static struct platform_device *em_x270_snd_device; + +static int __init em_x270_init(void) +{ + int ret; + + if (!(machine_is_em_x270() || machine_is_exeda() + || machine_is_cm_x300())) + return -ENODEV; + + em_x270_snd_device = platform_device_alloc("soc-audio", -1); + if (!em_x270_snd_device) + return -ENOMEM; + + platform_set_drvdata(em_x270_snd_device, &em_x270); + ret = platform_device_add(em_x270_snd_device); + + if (ret) + platform_device_put(em_x270_snd_device); + + return ret; +} + +static void __exit em_x270_exit(void) +{ + platform_device_unregister(em_x270_snd_device); +} + +module_init(em_x270_init); +module_exit(em_x270_exit); + +/* Module information */ +MODULE_AUTHOR("Mike Rapoport"); +MODULE_DESCRIPTION("ALSA SoC EM-X270, eXeda and CM-X300"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/hx4700.c b/sound/soc/pxa/hx4700.c new file mode 100644 index 000000000..6cdef5d49 --- /dev/null +++ b/sound/soc/pxa/hx4700.c @@ -0,0 +1,217 @@ +/* + * SoC audio for HP iPAQ hx4700 + * + * Copyright (c) 2009 Philipp Zabel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "pxa2xx-i2s.h" + +static struct snd_soc_jack hs_jack; + +/* Headphones jack detection DAPM pin */ +static struct snd_soc_jack_pin hs_jack_pin[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Speaker", + /* disable speaker when hp jack is inserted */ + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + +/* Headphones jack detection GPIO */ +static struct snd_soc_jack_gpio hs_jack_gpio = { + .gpio = GPIO75_HX4700_EARPHONE_nDET, + .invert = true, + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, +}; + +/* + * iPAQ hx4700 uses I2S for capture and playback. + */ +static int hx4700_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + /* set the I2S system clock as output */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* inform codec driver about clock freq * + * (PXA I2S always uses divider 256) */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 256 * params_rate(params), + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops hx4700_ops = { + .hw_params = hx4700_hw_params, +}; + +static int hx4700_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(GPIO107_HX4700_SPK_nSD, !!SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int hx4700_hp_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(GPIO92_HX4700_HP_DRIVER, !!SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +/* hx4700 machine dapm widgets */ +static const struct snd_soc_dapm_widget hx4700_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", hx4700_hp_power), + SND_SOC_DAPM_SPK("Speaker", hx4700_spk_power), + SND_SOC_DAPM_MIC("Built-in Microphone", NULL), +}; + +/* hx4700 machine audio_map */ +static const struct snd_soc_dapm_route hx4700_audio_map[] = { + + /* Headphone connected to LOUT, ROUT */ + {"Headphone Jack", NULL, "LOUT"}, + {"Headphone Jack", NULL, "ROUT"}, + + /* Speaker connected to MOUT2 */ + {"Speaker", NULL, "MOUT2"}, + + /* Microphone connected to MICIN */ + {"MICIN", NULL, "Built-in Microphone"}, + {"AIN", NULL, "MICOUT"}, +}; + +/* + * Logic for a ak4641 as connected on a HP iPAQ hx4700 + */ +static int hx4700_ak4641_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + + /* Jack detection API stuff */ + err = snd_soc_card_jack_new(rtd->card, "Headphone Jack", + SND_JACK_HEADPHONE, &hs_jack, hs_jack_pin, + ARRAY_SIZE(hs_jack_pin)); + if (err) + return err; + + err = snd_soc_jack_add_gpios(&hs_jack, 1, &hs_jack_gpio); + + return err; +} + +/* hx4700 digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link hx4700_dai = { + .name = "ak4641", + .stream_name = "AK4641", + .cpu_dai_name = "pxa2xx-i2s", + .codec_dai_name = "ak4641-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "ak4641.0-0012", + .init = hx4700_ak4641_init, + .dai_fmt = SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &hx4700_ops, +}; + +/* hx4700 audio machine driver */ +static struct snd_soc_card snd_soc_card_hx4700 = { + .name = "iPAQ hx4700", + .owner = THIS_MODULE, + .dai_link = &hx4700_dai, + .num_links = 1, + .dapm_widgets = hx4700_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(hx4700_dapm_widgets), + .dapm_routes = hx4700_audio_map, + .num_dapm_routes = ARRAY_SIZE(hx4700_audio_map), + .fully_routed = true, +}; + +static struct gpio hx4700_audio_gpios[] = { + { GPIO107_HX4700_SPK_nSD, GPIOF_OUT_INIT_HIGH, "SPK_POWER" }, + { GPIO92_HX4700_HP_DRIVER, GPIOF_OUT_INIT_LOW, "EP_POWER" }, +}; + +static int hx4700_audio_probe(struct platform_device *pdev) +{ + int ret; + + if (!machine_is_h4700()) + return -ENODEV; + + ret = gpio_request_array(hx4700_audio_gpios, + ARRAY_SIZE(hx4700_audio_gpios)); + if (ret) + return ret; + + snd_soc_card_hx4700.dev = &pdev->dev; + ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_hx4700); + if (ret) + gpio_free_array(hx4700_audio_gpios, + ARRAY_SIZE(hx4700_audio_gpios)); + + return ret; +} + +static int hx4700_audio_remove(struct platform_device *pdev) +{ + gpio_set_value(GPIO92_HX4700_HP_DRIVER, 0); + gpio_set_value(GPIO107_HX4700_SPK_nSD, 0); + + gpio_free_array(hx4700_audio_gpios, ARRAY_SIZE(hx4700_audio_gpios)); + return 0; +} + +static struct platform_driver hx4700_audio_driver = { + .driver = { + .name = "hx4700-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = hx4700_audio_probe, + .remove = hx4700_audio_remove, +}; + +module_platform_driver(hx4700_audio_driver); + +MODULE_AUTHOR("Philipp Zabel"); +MODULE_DESCRIPTION("ALSA SoC iPAQ hx4700"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:hx4700-audio"); diff --git a/sound/soc/pxa/imote2.c b/sound/soc/pxa/imote2.c new file mode 100644 index 000000000..78475376f --- /dev/null +++ b/sound/soc/pxa/imote2.c @@ -0,0 +1,95 @@ + +#include +#include + +#include + +#include "../codecs/wm8940.h" +#include "pxa2xx-i2s.h" + +static int imote2_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, clk, + SND_SOC_CLOCK_OUT); + + return ret; +} + +static const struct snd_soc_ops imote2_asoc_ops = { + .hw_params = imote2_asoc_hw_params, +}; + +static struct snd_soc_dai_link imote2_dai = { + .name = "WM8940", + .stream_name = "WM8940", + .cpu_dai_name = "pxa2xx-i2s", + .codec_dai_name = "wm8940-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm8940-codec.0-0034", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &imote2_asoc_ops, +}; + +static struct snd_soc_card imote2 = { + .name = "Imote2", + .owner = THIS_MODULE, + .dai_link = &imote2_dai, + .num_links = 1, +}; + +static int imote2_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &imote2; + int ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + return ret; +} + +static struct platform_driver imote2_driver = { + .driver = { + .name = "imote2-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = imote2_probe, +}; + +module_platform_driver(imote2_driver); + +MODULE_AUTHOR("Jonathan Cameron"); +MODULE_DESCRIPTION("ALSA SoC Imote 2"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imote2-audio"); diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c new file mode 100644 index 000000000..935a248e5 --- /dev/null +++ b/sound/soc/pxa/magician.c @@ -0,0 +1,432 @@ +/* + * SoC audio for HTC Magician + * + * Copyright (c) 2006 Philipp Zabel + * + * based on spitz.c, + * Authors: Liam Girdwood + * Richard Purdie + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "../codecs/uda1380.h" +#include "pxa2xx-i2s.h" +#include "pxa-ssp.h" + +#define MAGICIAN_MIC 0 +#define MAGICIAN_MIC_EXT 1 + +static int magician_hp_switch; +static int magician_spk_switch = 1; +static int magician_in_sel = MAGICIAN_MIC; + +static void magician_ext_control(struct snd_soc_dapm_context *dapm) +{ + + snd_soc_dapm_mutex_lock(dapm); + + if (magician_spk_switch) + snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker"); + if (magician_hp_switch) + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + + switch (magician_in_sel) { + case MAGICIAN_MIC: + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Mic"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Call Mic"); + break; + case MAGICIAN_MIC_EXT: + snd_soc_dapm_disable_pin_unlocked(dapm, "Call Mic"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Mic"); + break; + } + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +static int magician_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* check the jack status at stream startup */ + magician_ext_control(&rtd->card->dapm); + + return 0; +} + +/* + * Magician uses SSP port for playback. + */ +static int magician_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int width; + int ret = 0; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_MSB | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + width = snd_pcm_format_physical_width(params_format(params)); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 0, 1, width); + if (ret < 0) + return ret; + + /* set audio clock as clock source */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +/* + * Magician uses I2S for capture. + */ +static int magician_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_MSB | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the I2S system clock as output */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops magician_capture_ops = { + .startup = magician_startup, + .hw_params = magician_capture_hw_params, +}; + +static const struct snd_soc_ops magician_playback_ops = { + .startup = magician_startup, + .hw_params = magician_playback_hw_params, +}; + +static int magician_get_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = magician_hp_switch; + return 0; +} + +static int magician_set_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (magician_hp_switch == ucontrol->value.integer.value[0]) + return 0; + + magician_hp_switch = ucontrol->value.integer.value[0]; + magician_ext_control(&card->dapm); + return 1; +} + +static int magician_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = magician_spk_switch; + return 0; +} + +static int magician_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (magician_spk_switch == ucontrol->value.integer.value[0]) + return 0; + + magician_spk_switch = ucontrol->value.integer.value[0]; + magician_ext_control(&card->dapm); + return 1; +} + +static int magician_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = magician_in_sel; + return 0; +} + +static int magician_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (magician_in_sel == ucontrol->value.enumerated.item[0]) + return 0; + + magician_in_sel = ucontrol->value.enumerated.item[0]; + + switch (magician_in_sel) { + case MAGICIAN_MIC: + gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 1); + break; + case MAGICIAN_MIC_EXT: + gpio_set_value(EGPIO_MAGICIAN_IN_SEL1, 0); + } + + return 1; +} + +static int magician_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int magician_hp_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(EGPIO_MAGICIAN_EP_POWER, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int magician_mic_bias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +/* magician machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", magician_hp_power), + SND_SOC_DAPM_SPK("Speaker", magician_spk_power), + SND_SOC_DAPM_MIC("Call Mic", magician_mic_bias), + SND_SOC_DAPM_MIC("Headset Mic", magician_mic_bias), +}; + +/* magician machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Headphone connected to VOUTL, VOUTR */ + {"Headphone Jack", NULL, "VOUTL"}, + {"Headphone Jack", NULL, "VOUTR"}, + + /* Speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* Mics are connected to VINM */ + {"VINM", NULL, "Headset Mic"}, + {"VINM", NULL, "Call Mic"}, +}; + +static const char * const input_select[] = {"Call Mic", "Headset Mic"}; +static const struct soc_enum magician_in_sel_enum = + SOC_ENUM_SINGLE_EXT(2, input_select); + +static const struct snd_kcontrol_new uda1380_magician_controls[] = { + SOC_SINGLE_BOOL_EXT("Headphone Switch", + (unsigned long)&magician_hp_switch, + magician_get_hp, magician_set_hp), + SOC_SINGLE_BOOL_EXT("Speaker Switch", + (unsigned long)&magician_spk_switch, + magician_get_spk, magician_set_spk), + SOC_ENUM_EXT("Input Select", magician_in_sel_enum, + magician_get_input, magician_set_input), +}; + +/* magician digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link magician_dai[] = { +{ + .name = "uda1380", + .stream_name = "UDA1380 Playback", + .cpu_dai_name = "pxa-ssp-dai.0", + .codec_dai_name = "uda1380-hifi-playback", + .platform_name = "pxa-pcm-audio", + .codec_name = "uda1380-codec.0-0018", + .ops = &magician_playback_ops, +}, +{ + .name = "uda1380", + .stream_name = "UDA1380 Capture", + .cpu_dai_name = "pxa2xx-i2s", + .codec_dai_name = "uda1380-hifi-capture", + .platform_name = "pxa-pcm-audio", + .codec_name = "uda1380-codec.0-0018", + .ops = &magician_capture_ops, +} +}; + +/* magician audio machine driver */ +static struct snd_soc_card snd_soc_card_magician = { + .name = "Magician", + .owner = THIS_MODULE, + .dai_link = magician_dai, + .num_links = ARRAY_SIZE(magician_dai), + + .controls = uda1380_magician_controls, + .num_controls = ARRAY_SIZE(uda1380_magician_controls), + .dapm_widgets = uda1380_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, +}; + +static struct platform_device *magician_snd_device; + +/* + * FIXME: move into magician board file once merged into the pxa tree + */ +static struct uda1380_platform_data uda1380_info = { + .gpio_power = EGPIO_MAGICIAN_CODEC_POWER, + .gpio_reset = EGPIO_MAGICIAN_CODEC_RESET, + .dac_clk = UDA1380_DAC_CLK_WSPLL, +}; + +static struct i2c_board_info i2c_board_info[] = { + { + I2C_BOARD_INFO("uda1380", 0x18), + .platform_data = &uda1380_info, + }, +}; + +static int __init magician_init(void) +{ + int ret; + struct i2c_adapter *adapter; + struct i2c_client *client; + + if (!machine_is_magician()) + return -ENODEV; + + adapter = i2c_get_adapter(0); + if (!adapter) + return -ENODEV; + client = i2c_new_device(adapter, i2c_board_info); + i2c_put_adapter(adapter); + if (!client) + return -ENODEV; + + ret = gpio_request(EGPIO_MAGICIAN_SPK_POWER, "SPK_POWER"); + if (ret) + goto err_request_spk; + ret = gpio_request(EGPIO_MAGICIAN_EP_POWER, "EP_POWER"); + if (ret) + goto err_request_ep; + ret = gpio_request(EGPIO_MAGICIAN_MIC_POWER, "MIC_POWER"); + if (ret) + goto err_request_mic; + ret = gpio_request(EGPIO_MAGICIAN_IN_SEL0, "IN_SEL0"); + if (ret) + goto err_request_in_sel0; + ret = gpio_request(EGPIO_MAGICIAN_IN_SEL1, "IN_SEL1"); + if (ret) + goto err_request_in_sel1; + + gpio_set_value(EGPIO_MAGICIAN_IN_SEL0, 0); + + magician_snd_device = platform_device_alloc("soc-audio", -1); + if (!magician_snd_device) { + ret = -ENOMEM; + goto err_pdev; + } + + platform_set_drvdata(magician_snd_device, &snd_soc_card_magician); + ret = platform_device_add(magician_snd_device); + if (ret) { + platform_device_put(magician_snd_device); + goto err_pdev; + } + + return 0; + +err_pdev: + gpio_free(EGPIO_MAGICIAN_IN_SEL1); +err_request_in_sel1: + gpio_free(EGPIO_MAGICIAN_IN_SEL0); +err_request_in_sel0: + gpio_free(EGPIO_MAGICIAN_MIC_POWER); +err_request_mic: + gpio_free(EGPIO_MAGICIAN_EP_POWER); +err_request_ep: + gpio_free(EGPIO_MAGICIAN_SPK_POWER); +err_request_spk: + return ret; +} + +static void __exit magician_exit(void) +{ + platform_device_unregister(magician_snd_device); + + gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, 0); + gpio_set_value(EGPIO_MAGICIAN_EP_POWER, 0); + gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, 0); + + gpio_free(EGPIO_MAGICIAN_IN_SEL1); + gpio_free(EGPIO_MAGICIAN_IN_SEL0); + gpio_free(EGPIO_MAGICIAN_MIC_POWER); + gpio_free(EGPIO_MAGICIAN_EP_POWER); + gpio_free(EGPIO_MAGICIAN_SPK_POWER); +} + +module_init(magician_init); +module_exit(magician_exit); + +MODULE_AUTHOR("Philipp Zabel"); +MODULE_DESCRIPTION("ALSA SoC Magician"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c new file mode 100644 index 000000000..47052fe3f --- /dev/null +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -0,0 +1,209 @@ +/* + * Handles the Mitac mioa701 SoC system + * + * Copyright (C) 2008 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation in version 2 of the License. + * + * 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 + * + * This is a little schema of the sound interconnections : + * + * Sagem X200 Wolfson WM9713 + * +--------+ +-------------------+ Rear Speaker + * | | | | /-+ + * | +--->----->---+MONOIN SPKL+--->----+-+ | + * | GSM | | | | | | + * | +--->----->---+PCBEEP SPKR+--->----+-+ | + * | CHIP | | | \-+ + * | +---<-----<---+MONO | + * | | | | Front Speaker + * +--------+ | | /-+ + * | HPL+--->----+-+ | + * | | | | | + * | OUT3+--->----+-+ | + * | | \-+ + * | | + * | | Front Micro + * | | + + * | MIC1+-----<--+o+ + * | | + + * +-------------------+ --- + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/wm9713.h" + +#define AC97_GPIO_PULL 0x58 + +/* Use GPIO8 for rear speaker amplifier */ +static int rear_amp_power(struct snd_soc_component *component, int power) +{ + unsigned short reg; + + if (power) { + reg = snd_soc_component_read32(component, AC97_GPIO_CFG); + snd_soc_component_write(component, AC97_GPIO_CFG, reg | 0x0100); + reg = snd_soc_component_read32(component, AC97_GPIO_PULL); + snd_soc_component_write(component, AC97_GPIO_PULL, reg | (1<<15)); + } else { + reg = snd_soc_component_read32(component, AC97_GPIO_CFG); + snd_soc_component_write(component, AC97_GPIO_CFG, reg & ~0x0100); + reg = snd_soc_component_read32(component, AC97_GPIO_PULL); + snd_soc_component_write(component, AC97_GPIO_PULL, reg & ~(1<<15)); + } + + return 0; +} + +static int rear_amp_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_card *card = widget->dapm->card; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + component = rtd->codec_dai->component; + return rear_amp_power(component, SND_SOC_DAPM_EVENT_ON(event)); +} + +/* mioa701 machine dapm widgets */ +static const struct snd_soc_dapm_widget mioa701_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Front Speaker", NULL), + SND_SOC_DAPM_SPK("Rear Speaker", rear_amp_event), + SND_SOC_DAPM_MIC("Headset", NULL), + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Front Mic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Call Mic */ + {"Mic Bias", NULL, "Front Mic"}, + {"MIC1", NULL, "Mic Bias"}, + + /* Headset Mic */ + {"LINEL", NULL, "Headset Mic"}, + {"LINER", NULL, "Headset Mic"}, + + /* GSM Module */ + {"MONOIN", NULL, "GSM Line Out"}, + {"PCBEEP", NULL, "GSM Line Out"}, + {"GSM Line In", NULL, "MONO"}, + + /* headphone connected to HPL, HPR */ + {"Headset", NULL, "HPL"}, + {"Headset", NULL, "HPR"}, + + /* front speaker connected to HPL, OUT3 */ + {"Front Speaker", NULL, "HPL"}, + {"Front Speaker", NULL, "OUT3"}, + + /* rear speaker connected to SPKL, SPKR */ + {"Rear Speaker", NULL, "SPKL"}, + {"Rear Speaker", NULL, "SPKR"}, +}; + +static int mioa701_wm9713_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + + /* Prepare GPIO8 for rear speaker amplifier */ + snd_soc_component_update_bits(component, AC97_GPIO_CFG, 0x100, 0x100); + + /* Prepare MIC input */ + snd_soc_component_update_bits(component, AC97_3D_CONTROL, 0xc000, 0xc000); + + return 0; +} + +static struct snd_soc_ops mioa701_ops; + +static struct snd_soc_dai_link mioa701_dai[] = { + { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9713-hifi", + .codec_name = "wm9713-codec", + .init = mioa701_wm9713_init, + .platform_name = "pxa-pcm-audio", + .ops = &mioa701_ops, + }, + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9713-aux", + .codec_name = "wm9713-codec", + .platform_name = "pxa-pcm-audio", + .ops = &mioa701_ops, + }, +}; + +static struct snd_soc_card mioa701 = { + .name = "MioA701", + .owner = THIS_MODULE, + .dai_link = mioa701_dai, + .num_links = ARRAY_SIZE(mioa701_dai), + + .dapm_widgets = mioa701_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mioa701_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int mioa701_wm9713_probe(struct platform_device *pdev) +{ + int rc; + + if (!machine_is_mioa701()) + return -ENODEV; + + mioa701.dev = &pdev->dev; + rc = devm_snd_soc_register_card(&pdev->dev, &mioa701); + if (!rc) + dev_warn(&pdev->dev, "Be warned that incorrect mixers/muxes setup will " + "lead to overheating and possible destruction of your device." + " Do not use without a good knowledge of mio's board design!\n"); + return rc; +} + +static struct platform_driver mioa701_wm9713_driver = { + .probe = mioa701_wm9713_probe, + .driver = { + .name = "mioa701-wm9713", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(mioa701_wm9713_driver); + +/* Module information */ +MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)"); +MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mioa701-wm9713"); diff --git a/sound/soc/pxa/mmp-pcm.c b/sound/soc/pxa/mmp-pcm.c new file mode 100644 index 000000000..d2d4652de --- /dev/null +++ b/sound/soc/pxa/mmp-pcm.c @@ -0,0 +1,255 @@ +/* + * linux/sound/soc/pxa/mmp-pcm.c + * + * Copyright (C) 2011 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "mmp-pcm" + +struct mmp_dma_data { + int ssp_id; + struct resource *dma_res; +}; + +#define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP | \ + SNDRV_PCM_INFO_MMAP_VALID | \ + SNDRV_PCM_INFO_INTERLEAVED | \ + SNDRV_PCM_INFO_PAUSE | \ + SNDRV_PCM_INFO_RESUME | \ + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) + +static struct snd_pcm_hardware mmp_pcm_hardware[] = { + { + .info = MMP_PCM_INFO, + .period_bytes_min = 1024, + .period_bytes_max = 2048, + .periods_min = 2, + .periods_max = 32, + .buffer_bytes_max = 4096, + .fifo_size = 32, + }, + { + .info = MMP_PCM_INFO, + .period_bytes_min = 1024, + .period_bytes_max = 2048, + .periods_min = 2, + .periods_max = 32, + .buffer_bytes_max = 4096, + .fifo_size = 32, + }, +}; + +static int mmp_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; + + ret = + snd_dmaengine_pcm_prepare_slave_config(substream, params, + &slave_config); + if (ret) + return ret; + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) + return ret; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static bool filter(struct dma_chan *chan, void *param) +{ + struct mmp_dma_data *dma_data = param; + bool found = false; + char *devname; + + devname = kasprintf(GFP_KERNEL, "%s.%d", dma_data->dma_res->name, + dma_data->ssp_id); + if ((strcmp(dev_name(chan->device->dev), devname) == 0) && + (chan->chan_id == dma_data->dma_res->start)) { + found = true; + } + + kfree(devname); + return found; +} + +static int mmp_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct platform_device *pdev = to_platform_device(component->dev); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct mmp_dma_data dma_data; + struct resource *r; + + r = platform_get_resource(pdev, IORESOURCE_DMA, substream->stream); + if (!r) + return -EBUSY; + + snd_soc_set_runtime_hwparams(substream, + &mmp_pcm_hardware[substream->stream]); + + dma_data.dma_res = r; + dma_data.ssp_id = cpu_dai->id; + + return snd_dmaengine_pcm_open_request_chan(substream, filter, + &dma_data); +} + +static int mmp_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long off = vma->vm_pgoff; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + return remap_pfn_range(vma, vma->vm_start, + __phys_to_pfn(runtime->dma_addr) + off, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static const struct snd_pcm_ops mmp_pcm_ops = { + .open = mmp_pcm_open, + .close = snd_dmaengine_pcm_close_release_chan, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = mmp_pcm_hw_params, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = snd_dmaengine_pcm_pointer, + .mmap = mmp_pcm_mmap, +}; + +static void mmp_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + struct gen_pool *gpool; + + gpool = sram_get_gpool("asram"); + if (!gpool) + return; + + for (stream = 0; stream < 2; stream++) { + size_t size = mmp_pcm_hardware[stream].buffer_bytes_max; + + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + gen_pool_free(gpool, (unsigned long)buf->area, size); + buf->area = NULL; + } + +} + +static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, + int stream) +{ + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = mmp_pcm_hardware[stream].buffer_bytes_max; + struct gen_pool *gpool; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = substream->pcm->card->dev; + buf->private_data = NULL; + + gpool = sram_get_gpool("asram"); + if (!gpool) + return -ENOMEM; + + buf->area = gen_pool_dma_alloc(gpool, size, &buf->addr); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0, stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + + ret = mmp_pcm_preallocate_dma_buffer(substream, stream); + if (ret) + goto err; + } + + return 0; + +err: + mmp_pcm_free_dma_buffers(pcm); + return ret; +} + +static const struct snd_soc_component_driver mmp_soc_component = { + .name = DRV_NAME, + .ops = &mmp_pcm_ops, + .pcm_new = mmp_pcm_new, + .pcm_free = mmp_pcm_free_dma_buffers, +}; + +static int mmp_pcm_probe(struct platform_device *pdev) +{ + struct mmp_audio_platdata *pdata = pdev->dev.platform_data; + + if (pdata) { + mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].buffer_bytes_max = + pdata->buffer_max_playback; + mmp_pcm_hardware[SNDRV_PCM_STREAM_PLAYBACK].period_bytes_max = + pdata->period_max_playback; + mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].buffer_bytes_max = + pdata->buffer_max_capture; + mmp_pcm_hardware[SNDRV_PCM_STREAM_CAPTURE].period_bytes_max = + pdata->period_max_capture; + } + return devm_snd_soc_register_component(&pdev->dev, &mmp_soc_component, + NULL, 0); +} + +static struct platform_driver mmp_pcm_driver = { + .driver = { + .name = "mmp-pcm-audio", + }, + + .probe = mmp_pcm_probe, +}; + +module_platform_driver(mmp_pcm_driver); + +MODULE_AUTHOR("Leo Yan "); +MODULE_DESCRIPTION("MMP Soc Audio DMA module"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mmp-pcm-audio"); diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c new file mode 100644 index 000000000..12d4513eb --- /dev/null +++ b/sound/soc/pxa/mmp-sspa.c @@ -0,0 +1,483 @@ +/* + * linux/sound/soc/pxa/mmp-sspa.c + * Base on pxa2xx-ssp.c + * + * Copyright (C) 2011 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "mmp-sspa.h" + +/* + * SSPA audio private data + */ +struct sspa_priv { + struct ssp_device *sspa; + struct snd_dmaengine_dai_dma_data *dma_params; + struct clk *audio_clk; + struct clk *sysclk; + int dai_fmt; + int running_cnt; +}; + +static void mmp_sspa_write_reg(struct ssp_device *sspa, u32 reg, u32 val) +{ + __raw_writel(val, sspa->mmio_base + reg); +} + +static u32 mmp_sspa_read_reg(struct ssp_device *sspa, u32 reg) +{ + return __raw_readl(sspa->mmio_base + reg); +} + +static void mmp_sspa_tx_enable(struct ssp_device *sspa) +{ + unsigned int sspa_sp; + + sspa_sp = mmp_sspa_read_reg(sspa, SSPA_TXSP); + sspa_sp |= SSPA_SP_S_EN; + sspa_sp |= SSPA_SP_WEN; + mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp); +} + +static void mmp_sspa_tx_disable(struct ssp_device *sspa) +{ + unsigned int sspa_sp; + + sspa_sp = mmp_sspa_read_reg(sspa, SSPA_TXSP); + sspa_sp &= ~SSPA_SP_S_EN; + sspa_sp |= SSPA_SP_WEN; + mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp); +} + +static void mmp_sspa_rx_enable(struct ssp_device *sspa) +{ + unsigned int sspa_sp; + + sspa_sp = mmp_sspa_read_reg(sspa, SSPA_RXSP); + sspa_sp |= SSPA_SP_S_EN; + sspa_sp |= SSPA_SP_WEN; + mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp); +} + +static void mmp_sspa_rx_disable(struct ssp_device *sspa) +{ + unsigned int sspa_sp; + + sspa_sp = mmp_sspa_read_reg(sspa, SSPA_RXSP); + sspa_sp &= ~SSPA_SP_S_EN; + sspa_sp |= SSPA_SP_WEN; + mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp); +} + +static int mmp_sspa_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sspa_priv *priv = snd_soc_dai_get_drvdata(dai); + + clk_enable(priv->sysclk); + clk_enable(priv->sspa->clk); + + return 0; +} + +static void mmp_sspa_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sspa_priv *priv = snd_soc_dai_get_drvdata(dai); + + clk_disable(priv->sspa->clk); + clk_disable(priv->sysclk); + +} + +/* + * Set the SSP ports SYSCLK. + */ +static int mmp_sspa_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct sspa_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (clk_id) { + case MMP_SSPA_CLK_AUDIO: + ret = clk_set_rate(priv->audio_clk, freq); + if (ret) + return ret; + break; + case MMP_SSPA_CLK_PLL: + case MMP_SSPA_CLK_VCXO: + /* not support yet */ + return -EINVAL; + default: + return -EINVAL; + } + + return 0; +} + +static int mmp_sspa_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct sspa_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (pll_id) { + case MMP_SYSCLK: + ret = clk_set_rate(priv->sysclk, freq_out); + if (ret) + return ret; + break; + case MMP_SSPA_CLK: + ret = clk_set_rate(priv->sspa->clk, freq_out); + if (ret) + return ret; + break; + default: + return -ENODEV; + } + + return 0; +} + +/* + * Set up the sspa dai format. The sspa port must be inactive + * before calling this function as the physical + * interface format is changed. + */ +static int mmp_sspa_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *sspa = sspa_priv->sspa; + u32 sspa_sp, sspa_ctrl; + + /* check if we need to change anything at all */ + if (sspa_priv->dai_fmt == fmt) + return 0; + + /* we can only change the settings if the port is not in use */ + if ((mmp_sspa_read_reg(sspa, SSPA_TXSP) & SSPA_SP_S_EN) || + (mmp_sspa_read_reg(sspa, SSPA_RXSP) & SSPA_SP_S_EN)) { + dev_err(&sspa->pdev->dev, + "can't change hardware dai format: stream is in use\n"); + return -EINVAL; + } + + /* reset port settings */ + sspa_sp = SSPA_SP_WEN | SSPA_SP_S_RST | SSPA_SP_FFLUSH; + sspa_ctrl = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + sspa_sp |= SSPA_SP_MSL; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + sspa_sp |= SSPA_SP_FSP; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + sspa_sp |= SSPA_TXSP_FPER(63); + sspa_sp |= SSPA_SP_FWID(31); + sspa_ctrl |= SSPA_CTL_XDATDLY(1); + break; + default: + return -EINVAL; + } + + mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp); + mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp); + + sspa_sp &= ~(SSPA_SP_S_RST | SSPA_SP_FFLUSH); + mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp); + mmp_sspa_write_reg(sspa, SSPA_RXSP, sspa_sp); + + /* + * FIXME: hw issue, for the tx serial port, + * can not config the master/slave mode; + * so must clean this bit. + * The master/slave mode has been set in the + * rx port. + */ + sspa_sp &= ~SSPA_SP_MSL; + mmp_sspa_write_reg(sspa, SSPA_TXSP, sspa_sp); + + mmp_sspa_write_reg(sspa, SSPA_TXCTL, sspa_ctrl); + mmp_sspa_write_reg(sspa, SSPA_RXCTL, sspa_ctrl); + + /* Since we are configuring the timings for the format by hand + * we have to defer some things until hw_params() where we + * know parameters like the sample size. + */ + sspa_priv->dai_fmt = fmt; + return 0; +} + +/* + * Set the SSPA audio DMA parameters and sample size. + * Can be called multiple times by oss emulation. + */ +static int mmp_sspa_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(dai); + struct ssp_device *sspa = sspa_priv->sspa; + struct snd_dmaengine_dai_dma_data *dma_params; + u32 sspa_ctrl; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sspa_ctrl = mmp_sspa_read_reg(sspa, SSPA_TXCTL); + else + sspa_ctrl = mmp_sspa_read_reg(sspa, SSPA_RXCTL); + + sspa_ctrl &= ~SSPA_CTL_XFRLEN1_MASK; + sspa_ctrl |= SSPA_CTL_XFRLEN1(params_channels(params) - 1); + sspa_ctrl &= ~SSPA_CTL_XWDLEN1_MASK; + sspa_ctrl |= SSPA_CTL_XWDLEN1(SSPA_CTL_32_BITS); + sspa_ctrl &= ~SSPA_CTL_XSSZ1_MASK; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_8_BITS); + break; + case SNDRV_PCM_FORMAT_S16_LE: + sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_16_BITS); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_20_BITS); + break; + case SNDRV_PCM_FORMAT_S24_3LE: + sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_24_BITS); + break; + case SNDRV_PCM_FORMAT_S32_LE: + sspa_ctrl |= SSPA_CTL_XSSZ1(SSPA_CTL_32_BITS); + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mmp_sspa_write_reg(sspa, SSPA_TXCTL, sspa_ctrl); + mmp_sspa_write_reg(sspa, SSPA_TXFIFO_LL, 0x1); + } else { + mmp_sspa_write_reg(sspa, SSPA_RXCTL, sspa_ctrl); + mmp_sspa_write_reg(sspa, SSPA_RXFIFO_UL, 0x0); + } + + dma_params = &sspa_priv->dma_params[substream->stream]; + dma_params->addr = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + (sspa->phys_base + SSPA_TXD) : + (sspa->phys_base + SSPA_RXD); + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_params); + return 0; +} + +static int mmp_sspa_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sspa_priv *sspa_priv = snd_soc_dai_get_drvdata(dai); + struct ssp_device *sspa = sspa_priv->sspa; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * whatever playback or capture, must enable rx. + * this is a hw issue, so need check if rx has been + * enabled or not; if has been enabled by another + * stream, do not enable again. + */ + if (!sspa_priv->running_cnt) + mmp_sspa_rx_enable(sspa); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mmp_sspa_tx_enable(sspa); + + sspa_priv->running_cnt++; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sspa_priv->running_cnt--; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mmp_sspa_tx_disable(sspa); + + /* have no capture stream, disable rx port */ + if (!sspa_priv->running_cnt) + mmp_sspa_rx_disable(sspa); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int mmp_sspa_probe(struct snd_soc_dai *dai) +{ + struct sspa_priv *priv = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, priv); + return 0; + +} + +#define MMP_SSPA_RATES SNDRV_PCM_RATE_8000_192000 +#define MMP_SSPA_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops mmp_sspa_dai_ops = { + .startup = mmp_sspa_startup, + .shutdown = mmp_sspa_shutdown, + .trigger = mmp_sspa_trigger, + .hw_params = mmp_sspa_hw_params, + .set_sysclk = mmp_sspa_set_dai_sysclk, + .set_pll = mmp_sspa_set_dai_pll, + .set_fmt = mmp_sspa_set_dai_fmt, +}; + +static struct snd_soc_dai_driver mmp_sspa_dai = { + .probe = mmp_sspa_probe, + .playback = { + .channels_min = 1, + .channels_max = 128, + .rates = MMP_SSPA_RATES, + .formats = MMP_SSPA_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = MMP_SSPA_RATES, + .formats = MMP_SSPA_FORMATS, + }, + .ops = &mmp_sspa_dai_ops, +}; + +static const struct snd_soc_component_driver mmp_sspa_component = { + .name = "mmp-sspa", +}; + +static int asoc_mmp_sspa_probe(struct platform_device *pdev) +{ + struct sspa_priv *priv; + struct resource *res; + + priv = devm_kzalloc(&pdev->dev, + sizeof(struct sspa_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->sspa = devm_kzalloc(&pdev->dev, + sizeof(struct ssp_device), GFP_KERNEL); + if (priv->sspa == NULL) + return -ENOMEM; + + priv->dma_params = devm_kcalloc(&pdev->dev, + 2, sizeof(struct snd_dmaengine_dai_dma_data), + GFP_KERNEL); + if (priv->dma_params == NULL) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->sspa->mmio_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->sspa->mmio_base)) + return PTR_ERR(priv->sspa->mmio_base); + + priv->sspa->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->sspa->clk)) + return PTR_ERR(priv->sspa->clk); + + priv->audio_clk = clk_get(NULL, "mmp-audio"); + if (IS_ERR(priv->audio_clk)) + return PTR_ERR(priv->audio_clk); + + priv->sysclk = clk_get(NULL, "mmp-sysclk"); + if (IS_ERR(priv->sysclk)) { + clk_put(priv->audio_clk); + return PTR_ERR(priv->sysclk); + } + clk_enable(priv->audio_clk); + priv->dai_fmt = (unsigned int) -1; + platform_set_drvdata(pdev, priv); + + return devm_snd_soc_register_component(&pdev->dev, &mmp_sspa_component, + &mmp_sspa_dai, 1); +} + +static int asoc_mmp_sspa_remove(struct platform_device *pdev) +{ + struct sspa_priv *priv = platform_get_drvdata(pdev); + + clk_disable(priv->audio_clk); + clk_put(priv->audio_clk); + clk_put(priv->sysclk); + return 0; +} + +static struct platform_driver asoc_mmp_sspa_driver = { + .driver = { + .name = "mmp-sspa-dai", + }, + .probe = asoc_mmp_sspa_probe, + .remove = asoc_mmp_sspa_remove, +}; + +module_platform_driver(asoc_mmp_sspa_driver); + +MODULE_AUTHOR("Leo Yan "); +MODULE_DESCRIPTION("MMP SSPA SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mmp-sspa-dai"); diff --git a/sound/soc/pxa/mmp-sspa.h b/sound/soc/pxa/mmp-sspa.h new file mode 100644 index 000000000..ea365cb9e --- /dev/null +++ b/sound/soc/pxa/mmp-sspa.h @@ -0,0 +1,92 @@ +/* + * linux/sound/soc/pxa/mmp-sspa.h + * + * Copyright (C) 2011 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _MMP_SSPA_H +#define _MMP_SSPA_H + +/* + * SSPA Registers + */ +#define SSPA_RXD (0x00) +#define SSPA_RXID (0x04) +#define SSPA_RXCTL (0x08) +#define SSPA_RXSP (0x0c) +#define SSPA_RXFIFO_UL (0x10) +#define SSPA_RXINT_MASK (0x14) +#define SSPA_RXC (0x18) +#define SSPA_RXFIFO_NOFS (0x1c) +#define SSPA_RXFIFO_SIZE (0x20) + +#define SSPA_TXD (0x80) +#define SSPA_TXID (0x84) +#define SSPA_TXCTL (0x88) +#define SSPA_TXSP (0x8c) +#define SSPA_TXFIFO_LL (0x90) +#define SSPA_TXINT_MASK (0x94) +#define SSPA_TXC (0x98) +#define SSPA_TXFIFO_NOFS (0x9c) +#define SSPA_TXFIFO_SIZE (0xa0) + +/* SSPA Control Register */ +#define SSPA_CTL_XPH (1 << 31) /* Read Phase */ +#define SSPA_CTL_XFIG (1 << 15) /* Transmit Zeros when FIFO Empty */ +#define SSPA_CTL_JST (1 << 3) /* Audio Sample Justification */ +#define SSPA_CTL_XFRLEN2_MASK (7 << 24) +#define SSPA_CTL_XFRLEN2(x) ((x) << 24) /* Transmit Frame Length in Phase 2 */ +#define SSPA_CTL_XWDLEN2_MASK (7 << 21) +#define SSPA_CTL_XWDLEN2(x) ((x) << 21) /* Transmit Word Length in Phase 2 */ +#define SSPA_CTL_XDATDLY(x) ((x) << 19) /* Tansmit Data Delay */ +#define SSPA_CTL_XSSZ2_MASK (7 << 16) +#define SSPA_CTL_XSSZ2(x) ((x) << 16) /* Transmit Sample Audio Size */ +#define SSPA_CTL_XFRLEN1_MASK (7 << 8) +#define SSPA_CTL_XFRLEN1(x) ((x) << 8) /* Transmit Frame Length in Phase 1 */ +#define SSPA_CTL_XWDLEN1_MASK (7 << 5) +#define SSPA_CTL_XWDLEN1(x) ((x) << 5) /* Transmit Word Length in Phase 1 */ +#define SSPA_CTL_XSSZ1_MASK (7 << 0) +#define SSPA_CTL_XSSZ1(x) ((x) << 0) /* XSSZ1 */ + +#define SSPA_CTL_8_BITS (0x0) /* Sample Size */ +#define SSPA_CTL_12_BITS (0x1) +#define SSPA_CTL_16_BITS (0x2) +#define SSPA_CTL_20_BITS (0x3) +#define SSPA_CTL_24_BITS (0x4) +#define SSPA_CTL_32_BITS (0x5) + +/* SSPA Serial Port Register */ +#define SSPA_SP_WEN (1 << 31) /* Write Configuration Enable */ +#define SSPA_SP_MSL (1 << 18) /* Master Slave Configuration */ +#define SSPA_SP_CLKP (1 << 17) /* CLKP Polarity Clock Edge Select */ +#define SSPA_SP_FSP (1 << 16) /* FSP Polarity Clock Edge Select */ +#define SSPA_SP_FFLUSH (1 << 2) /* FIFO Flush */ +#define SSPA_SP_S_RST (1 << 1) /* Active High Reset Signal */ +#define SSPA_SP_S_EN (1 << 0) /* Serial Clock Domain Enable */ +#define SSPA_SP_FWID(x) ((x) << 20) /* Frame-Sync Width */ +#define SSPA_TXSP_FPER(x) ((x) << 4) /* Frame-Sync Active */ + +/* sspa clock sources */ +#define MMP_SSPA_CLK_PLL 0 +#define MMP_SSPA_CLK_VCXO 1 +#define MMP_SSPA_CLK_AUDIO 3 + +/* sspa pll id */ +#define MMP_SYSCLK 0 +#define MMP_SSPA_CLK 1 + +#endif /* _MMP_SSPA_H */ diff --git a/sound/soc/pxa/palm27x.c b/sound/soc/pxa/palm27x.c new file mode 100644 index 000000000..971670485 --- /dev/null +++ b/sound/soc/pxa/palm27x.c @@ -0,0 +1,161 @@ +/* + * linux/sound/soc/pxa/palm27x.c + * + * SoC Audio driver for Palm T|X, T5 and LifeDrive + * + * based on tosa.c + * + * Copyright (C) 2008 Marek Vasut + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +static struct snd_soc_jack hs_jack; + +/* Headphones jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +/* Headphones jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + [0] = { + /* gpio is set on per-platform basis */ + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, + }, +}; + +/* Palm27x machine dapm widgets */ +static const struct snd_soc_dapm_widget palm27x_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext. Speaker", NULL), + SND_SOC_DAPM_MIC("Ext. Microphone", NULL), +}; + +/* PalmTX audio map */ +static const struct snd_soc_dapm_route audio_map[] = { + /* headphone connected to HPOUTL, HPOUTR */ + {"Headphone Jack", NULL, "HPOUTL"}, + {"Headphone Jack", NULL, "HPOUTR"}, + + /* ext speaker connected to ROUT2, LOUT2 */ + {"Ext. Speaker", NULL, "LOUT2"}, + {"Ext. Speaker", NULL, "ROUT2"}, + + /* mic connected to MIC1 */ + {"MIC1", NULL, "Ext. Microphone"}, +}; + +static struct snd_soc_card palm27x_asoc; + +static int palm27x_ac97_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + + /* Jack detection API stuff */ + err = snd_soc_card_jack_new(rtd->card, "Headphone Jack", + SND_JACK_HEADPHONE, &hs_jack, hs_jack_pins, + ARRAY_SIZE(hs_jack_pins)); + if (err) + return err; + + err = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + + return err; +} + +static struct snd_soc_dai_link palm27x_dai[] = { +{ + .name = "AC97 HiFi", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9712-hifi", + .codec_name = "wm9712-codec", + .platform_name = "pxa-pcm-audio", + .init = palm27x_ac97_init, +}, +{ + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9712-aux", + .codec_name = "wm9712-codec", + .platform_name = "pxa-pcm-audio", +}, +}; + +static struct snd_soc_card palm27x_asoc = { + .name = "Palm/PXA27x", + .owner = THIS_MODULE, + .dai_link = palm27x_dai, + .num_links = ARRAY_SIZE(palm27x_dai), + .dapm_widgets = palm27x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(palm27x_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, +}; + +static int palm27x_asoc_probe(struct platform_device *pdev) +{ + int ret; + + if (!(machine_is_palmtx() || machine_is_palmt5() || + machine_is_palmld() || machine_is_palmte2())) + return -ENODEV; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "please supply platform_data\n"); + return -ENODEV; + } + + hs_jack_gpios[0].gpio = ((struct palm27x_asoc_info *) + (pdev->dev.platform_data))->jack_gpio; + + palm27x_asoc.dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &palm27x_asoc); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + return ret; +} + +static struct platform_driver palm27x_wm9712_driver = { + .probe = palm27x_asoc_probe, + .driver = { + .name = "palm27x-asoc", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(palm27x_wm9712_driver); + +/* Module information */ +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("ALSA SoC Palm T|X, T5 and LifeDrive"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:palm27x-asoc"); diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c new file mode 100644 index 000000000..b6693f32f --- /dev/null +++ b/sound/soc/pxa/poodle.c @@ -0,0 +1,291 @@ +/* + * poodle.c -- SoC audio for Poodle + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood + * Richard Purdie + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8731.h" +#include "pxa2xx-i2s.h" + +#define POODLE_HP 1 +#define POODLE_HP_OFF 0 +#define POODLE_SPK_ON 1 +#define POODLE_SPK_OFF 0 + + /* audio clock in Hz - rounded from 12.235MHz */ +#define POODLE_AUDIO_CLOCK 12288000 + +static int poodle_jack_func; +static int poodle_spk_func; + +static void poodle_ext_control(struct snd_soc_dapm_context *dapm) +{ + /* set up jack connection */ + if (poodle_jack_func == POODLE_HP) { + /* set = unmute headphone */ + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_L, 1); + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_R, 1); + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + } else { + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_L, 0); + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_R, 0); + snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); + } + + /* set the enpoints to their new connetion states */ + if (poodle_spk_func == POODLE_SPK_ON) + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + + /* signal a DAPM event */ + snd_soc_dapm_sync(dapm); +} + +static int poodle_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* check the jack status at stream startup */ + poodle_ext_control(&rtd->card->dapm); + + return 0; +} + +/* we need to unmute the HP at shutdown as the mute burns power on poodle */ +static void poodle_shutdown(struct snd_pcm_substream *substream) +{ + /* set = unmute headphone */ + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_L, 1); + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_R, 1); +} + +static int poodle_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops poodle_ops = { + .startup = poodle_startup, + .hw_params = poodle_hw_params, + .shutdown = poodle_shutdown, +}; + +static int poodle_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = poodle_jack_func; + return 0; +} + +static int poodle_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (poodle_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + poodle_jack_func = ucontrol->value.enumerated.item[0]; + poodle_ext_control(&card->dapm); + return 1; +} + +static int poodle_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = poodle_spk_func; + return 0; +} + +static int poodle_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (poodle_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + poodle_spk_func = ucontrol->value.enumerated.item[0]; + poodle_ext_control(&card->dapm); + return 1; +} + +static int poodle_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_AMP_ON, 0); + else + locomo_gpio_write(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_AMP_ON, 1); + + return 0; +} + +/* poodle machine dapm widgets */ +static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { +SND_SOC_DAPM_HP("Headphone Jack", NULL), +SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event), +SND_SOC_DAPM_MIC("Microphone", NULL), +}; + +/* Corgi machine connections to the codec pins */ +static const struct snd_soc_dapm_route poodle_audio_map[] = { + + /* headphone connected to LHPOUT1, RHPOUT1 */ + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + /* speaker connected to LOUT, ROUT */ + {"Ext Spk", NULL, "ROUT"}, + {"Ext Spk", NULL, "LOUT"}, + + {"MICIN", NULL, "Microphone"}, +}; + +static const char * const jack_function[] = {"Off", "Headphone"}; +static const char * const spk_function[] = {"Off", "On"}; +static const struct soc_enum poodle_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static const struct snd_kcontrol_new wm8731_poodle_controls[] = { + SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack, + poodle_set_jack), + SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk, + poodle_set_spk), +}; + +/* poodle digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link poodle_dai = { + .name = "WM8731", + .stream_name = "WM8731", + .cpu_dai_name = "pxa2xx-i2s", + .codec_dai_name = "wm8731-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm8731.0-001b", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &poodle_ops, +}; + +/* poodle audio machine driver */ +static struct snd_soc_card poodle = { + .name = "Poodle", + .dai_link = &poodle_dai, + .num_links = 1, + .owner = THIS_MODULE, + + .controls = wm8731_poodle_controls, + .num_controls = ARRAY_SIZE(wm8731_poodle_controls), + .dapm_widgets = wm8731_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets), + .dapm_routes = poodle_audio_map, + .num_dapm_routes = ARRAY_SIZE(poodle_audio_map), + .fully_routed = true, +}; + +static int poodle_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &poodle; + int ret; + + locomo_gpio_set_dir(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_AMP_ON, 0); + /* should we mute HP at startup - burning power ?*/ + locomo_gpio_set_dir(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_L, 0); + locomo_gpio_set_dir(&poodle_locomo_device.dev, + POODLE_LOCOMO_GPIO_MUTE_R, 0); + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + return ret; +} + +static struct platform_driver poodle_driver = { + .driver = { + .name = "poodle-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = poodle_probe, +}; + +module_platform_driver(poodle_driver); + +/* Module information */ +MODULE_AUTHOR("Richard Purdie"); +MODULE_DESCRIPTION("ALSA SoC Poodle"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:poodle-audio"); diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c new file mode 100644 index 000000000..69033e1a8 --- /dev/null +++ b/sound/soc/pxa/pxa-ssp.c @@ -0,0 +1,904 @@ +/* + * pxa-ssp.c -- ALSA Soc Audio Layer + * + * Copyright 2005,2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * TODO: + * o Test network mode for > 16bit sample size + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "pxa-ssp.h" + +/* + * SSP audio private data + */ +struct ssp_priv { + struct ssp_device *ssp; + struct clk *extclk; + unsigned long ssp_clk; + unsigned int sysclk; + unsigned int dai_fmt; + unsigned int configured_dai_fmt; +#ifdef CONFIG_PM + uint32_t cr0; + uint32_t cr1; + uint32_t to; + uint32_t psp; +#endif +}; + +static void dump_registers(struct ssp_device *ssp) +{ + dev_dbg(&ssp->pdev->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n", + pxa_ssp_read_reg(ssp, SSCR0), pxa_ssp_read_reg(ssp, SSCR1), + pxa_ssp_read_reg(ssp, SSTO)); + + dev_dbg(&ssp->pdev->dev, "SSPSP 0x%08x SSSR 0x%08x SSACD 0x%08x\n", + pxa_ssp_read_reg(ssp, SSPSP), pxa_ssp_read_reg(ssp, SSSR), + pxa_ssp_read_reg(ssp, SSACD)); +} + +static void pxa_ssp_enable(struct ssp_device *ssp) +{ + uint32_t sscr0; + + sscr0 = __raw_readl(ssp->mmio_base + SSCR0) | SSCR0_SSE; + __raw_writel(sscr0, ssp->mmio_base + SSCR0); +} + +static void pxa_ssp_disable(struct ssp_device *ssp) +{ + uint32_t sscr0; + + sscr0 = __raw_readl(ssp->mmio_base + SSCR0) & ~SSCR0_SSE; + __raw_writel(sscr0, ssp->mmio_base + SSCR0); +} + +static void pxa_ssp_set_dma_params(struct ssp_device *ssp, int width4, + int out, struct snd_dmaengine_dai_dma_data *dma) +{ + dma->addr_width = width4 ? DMA_SLAVE_BUSWIDTH_4_BYTES : + DMA_SLAVE_BUSWIDTH_2_BYTES; + dma->maxburst = 16; + dma->addr = ssp->phys_base + SSDR; +} + +static int pxa_ssp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + struct snd_dmaengine_dai_dma_data *dma; + int ret = 0; + + if (!cpu_dai->active) { + clk_prepare_enable(ssp->clk); + pxa_ssp_disable(ssp); + } + + dma = kzalloc(sizeof(struct snd_dmaengine_dai_dma_data), GFP_KERNEL); + if (!dma) + return -ENOMEM; + dma->chan_name = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "tx" : "rx"; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma); + + return ret; +} + +static void pxa_ssp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + + if (!cpu_dai->active) { + pxa_ssp_disable(ssp); + clk_disable_unprepare(ssp->clk); + } + + kfree(snd_soc_dai_get_dma_data(cpu_dai, substream)); + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); +} + +#ifdef CONFIG_PM + +static int pxa_ssp_suspend(struct snd_soc_dai *cpu_dai) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + + if (!cpu_dai->active) + clk_prepare_enable(ssp->clk); + + priv->cr0 = __raw_readl(ssp->mmio_base + SSCR0); + priv->cr1 = __raw_readl(ssp->mmio_base + SSCR1); + priv->to = __raw_readl(ssp->mmio_base + SSTO); + priv->psp = __raw_readl(ssp->mmio_base + SSPSP); + + pxa_ssp_disable(ssp); + clk_disable_unprepare(ssp->clk); + return 0; +} + +static int pxa_ssp_resume(struct snd_soc_dai *cpu_dai) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + uint32_t sssr = SSSR_ROR | SSSR_TUR | SSSR_BCE; + + clk_prepare_enable(ssp->clk); + + __raw_writel(sssr, ssp->mmio_base + SSSR); + __raw_writel(priv->cr0 & ~SSCR0_SSE, ssp->mmio_base + SSCR0); + __raw_writel(priv->cr1, ssp->mmio_base + SSCR1); + __raw_writel(priv->to, ssp->mmio_base + SSTO); + __raw_writel(priv->psp, ssp->mmio_base + SSPSP); + + if (cpu_dai->active) + pxa_ssp_enable(ssp); + else + clk_disable_unprepare(ssp->clk); + + return 0; +} + +#else +#define pxa_ssp_suspend NULL +#define pxa_ssp_resume NULL +#endif + +/** + * ssp_set_clkdiv - set SSP clock divider + * @div: serial clock rate divider + */ +static void pxa_ssp_set_scr(struct ssp_device *ssp, u32 div) +{ + u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0); + + if (ssp->type == PXA25x_SSP) { + sscr0 &= ~0x0000ff00; + sscr0 |= ((div - 2)/2) << 8; /* 2..512 */ + } else { + sscr0 &= ~0x000fff00; + sscr0 |= (div - 1) << 8; /* 1..4096 */ + } + pxa_ssp_write_reg(ssp, SSCR0, sscr0); +} + +/* + * Set the SSP ports SYSCLK. + */ +static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + + u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0) & + ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS); + + if (priv->extclk) { + int ret; + + /* + * For DT based boards, if an extclk is given, use it + * here and configure PXA_SSP_CLK_EXT. + */ + + ret = clk_set_rate(priv->extclk, freq); + if (ret < 0) + return ret; + + clk_id = PXA_SSP_CLK_EXT; + } + + dev_dbg(&ssp->pdev->dev, + "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n", + cpu_dai->id, clk_id, freq); + + switch (clk_id) { + case PXA_SSP_CLK_NET_PLL: + sscr0 |= SSCR0_MOD; + break; + case PXA_SSP_CLK_PLL: + /* Internal PLL is fixed */ + if (ssp->type == PXA25x_SSP) + priv->sysclk = 1843200; + else + priv->sysclk = 13000000; + break; + case PXA_SSP_CLK_EXT: + priv->sysclk = freq; + sscr0 |= SSCR0_ECS; + break; + case PXA_SSP_CLK_NET: + priv->sysclk = freq; + sscr0 |= SSCR0_NCS | SSCR0_MOD; + break; + case PXA_SSP_CLK_AUDIO: + priv->sysclk = 0; + pxa_ssp_set_scr(ssp, 1); + sscr0 |= SSCR0_ACS; + break; + default: + return -ENODEV; + } + + /* The SSP clock must be disabled when changing SSP clock mode + * on PXA2xx. On PXA3xx it must be enabled when doing so. */ + if (ssp->type != PXA3xx_SSP) + clk_disable_unprepare(ssp->clk); + pxa_ssp_write_reg(ssp, SSCR0, sscr0); + if (ssp->type != PXA3xx_SSP) + clk_prepare_enable(ssp->clk); + + return 0; +} + +/* + * Configure the PLL frequency pxa27x and (afaik - pxa320 only) + */ +static int pxa_ssp_set_pll(struct ssp_priv *priv, unsigned int freq) +{ + struct ssp_device *ssp = priv->ssp; + u32 ssacd = pxa_ssp_read_reg(ssp, SSACD) & ~0x70; + + if (ssp->type == PXA3xx_SSP) + pxa_ssp_write_reg(ssp, SSACDD, 0); + + switch (freq) { + case 5622000: + break; + case 11345000: + ssacd |= (0x1 << 4); + break; + case 12235000: + ssacd |= (0x2 << 4); + break; + case 14857000: + ssacd |= (0x3 << 4); + break; + case 32842000: + ssacd |= (0x4 << 4); + break; + case 48000000: + ssacd |= (0x5 << 4); + break; + case 0: + /* Disable */ + break; + + default: + /* PXA3xx has a clock ditherer which can be used to generate + * a wider range of frequencies - calculate a value for it. + */ + if (ssp->type == PXA3xx_SSP) { + u32 val; + u64 tmp = 19968; + + tmp *= 1000000; + do_div(tmp, freq); + val = tmp; + + val = (val << 16) | 64; + pxa_ssp_write_reg(ssp, SSACDD, val); + + ssacd |= (0x6 << 4); + + dev_dbg(&ssp->pdev->dev, + "Using SSACDD %x to supply %uHz\n", + val, freq); + break; + } + + return -EINVAL; + } + + pxa_ssp_write_reg(ssp, SSACD, ssacd); + + return 0; +} + +/* + * Set the active slots in TDM/Network mode + */ +static int pxa_ssp_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + u32 sscr0; + + sscr0 = pxa_ssp_read_reg(ssp, SSCR0); + sscr0 &= ~(SSCR0_MOD | SSCR0_SlotsPerFrm(8) | SSCR0_EDSS | SSCR0_DSS); + + /* set slot width */ + if (slot_width > 16) + sscr0 |= SSCR0_EDSS | SSCR0_DataSize(slot_width - 16); + else + sscr0 |= SSCR0_DataSize(slot_width); + + if (slots > 1) { + /* enable network mode */ + sscr0 |= SSCR0_MOD; + + /* set number of active slots */ + sscr0 |= SSCR0_SlotsPerFrm(slots); + + /* set active slot mask */ + pxa_ssp_write_reg(ssp, SSTSA, tx_mask); + pxa_ssp_write_reg(ssp, SSRSA, rx_mask); + } + pxa_ssp_write_reg(ssp, SSCR0, sscr0); + + return 0; +} + +/* + * Tristate the SSP DAI lines + */ +static int pxa_ssp_set_dai_tristate(struct snd_soc_dai *cpu_dai, + int tristate) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + u32 sscr1; + + sscr1 = pxa_ssp_read_reg(ssp, SSCR1); + if (tristate) + sscr1 &= ~SSCR1_TTE; + else + sscr1 |= SSCR1_TTE; + pxa_ssp_write_reg(ssp, SSCR1, sscr1); + + return 0; +} + +static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + case SND_SOC_DAIFMT_IB_NF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + break; + + default: + return -EINVAL; + } + + /* Settings will be applied in hw_params() */ + priv->dai_fmt = fmt; + + return 0; +} + +/* + * Set up the SSP DAI format. + * The SSP Port must be inactive before calling this function as the + * physical interface format is changed. + */ +static int pxa_ssp_configure_dai_fmt(struct ssp_priv *priv) +{ + struct ssp_device *ssp = priv->ssp; + u32 sscr0, sscr1, sspsp, scfr; + + /* check if we need to change anything at all */ + if (priv->configured_dai_fmt == priv->dai_fmt) + return 0; + + /* reset port settings */ + sscr0 = pxa_ssp_read_reg(ssp, SSCR0) & + ~(SSCR0_PSP | SSCR0_MOD); + sscr1 = pxa_ssp_read_reg(ssp, SSCR1) & + ~(SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR | + SSCR1_RWOT | SSCR1_TRAIL | SSCR1_TFT | SSCR1_RFT); + sspsp = pxa_ssp_read_reg(ssp, SSPSP) & + ~(SSPSP_SFRMP | SSPSP_SCMODE(3)); + + sscr1 |= SSCR1_RxTresh(8) | SSCR1_TxTresh(7); + + switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + sscr1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + sscr1 |= SSCR1_SCLKDIR | SSCR1_SCFR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (priv->dai_fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + sspsp |= SSPSP_SFRMP; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_IF: + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; + break; + default: + return -EINVAL; + } + + switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + sscr0 |= SSCR0_PSP; + sscr1 |= SSCR1_RWOT | SSCR1_TRAIL; + /* See hw_params() */ + break; + + case SND_SOC_DAIFMT_DSP_A: + sspsp |= SSPSP_FSRT; + /* fall through */ + case SND_SOC_DAIFMT_DSP_B: + sscr0 |= SSCR0_MOD | SSCR0_PSP; + sscr1 |= SSCR1_TRAIL | SSCR1_RWOT; + break; + + default: + return -EINVAL; + } + + pxa_ssp_write_reg(ssp, SSCR0, sscr0); + pxa_ssp_write_reg(ssp, SSCR1, sscr1); + pxa_ssp_write_reg(ssp, SSPSP, sspsp); + + switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + scfr = pxa_ssp_read_reg(ssp, SSCR1) | SSCR1_SCFR; + pxa_ssp_write_reg(ssp, SSCR1, scfr); + + while (pxa_ssp_read_reg(ssp, SSSR) & SSSR_BSY) + cpu_relax(); + break; + } + + dump_registers(ssp); + + /* Since we are configuring the timings for the format by hand + * we have to defer some things until hw_params() where we + * know parameters like the sample size. + */ + priv->configured_dai_fmt = priv->dai_fmt; + + return 0; +} + +struct pxa_ssp_clock_mode { + int rate; + int pll; + u8 acds; + u8 scdb; +}; + +static const struct pxa_ssp_clock_mode pxa_ssp_clock_modes[] = { + { .rate = 8000, .pll = 32842000, .acds = SSACD_ACDS_32, .scdb = SSACD_SCDB_4X }, + { .rate = 11025, .pll = 5622000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_4X }, + { .rate = 16000, .pll = 32842000, .acds = SSACD_ACDS_16, .scdb = SSACD_SCDB_4X }, + { .rate = 22050, .pll = 5622000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X }, + { .rate = 44100, .pll = 11345000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X }, + { .rate = 48000, .pll = 12235000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X }, + { .rate = 96000, .pll = 12235000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_1X }, + {} +}; + +/* + * Set the SSP audio DMA parameters and sample size. + * Can be called multiple times by oss emulation. + */ +static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + int chn = params_channels(params); + u32 sscr0, sspsp; + int width = snd_pcm_format_physical_width(params_format(params)); + int ttsa = pxa_ssp_read_reg(ssp, SSTSA) & 0xf; + struct snd_dmaengine_dai_dma_data *dma_data; + int rate = params_rate(params); + int bclk = rate * chn * (width / 8); + int ret; + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + + /* Network mode with one active slot (ttsa == 1) can be used + * to force 16-bit frame width on the wire (for S16_LE), even + * with two channels. Use 16-bit DMA transfers for this case. + */ + pxa_ssp_set_dma_params(ssp, + ((chn == 2) && (ttsa != 1)) || (width == 32), + substream->stream == SNDRV_PCM_STREAM_PLAYBACK, dma_data); + + /* we can only change the settings if the port is not in use */ + if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) + return 0; + + ret = pxa_ssp_configure_dai_fmt(priv); + if (ret < 0) + return ret; + + /* clear selected SSP bits */ + sscr0 = pxa_ssp_read_reg(ssp, SSCR0) & ~(SSCR0_DSS | SSCR0_EDSS); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (ssp->type == PXA3xx_SSP) + sscr0 |= SSCR0_FPCKE; + sscr0 |= SSCR0_DataSize(16); + break; + case SNDRV_PCM_FORMAT_S24_LE: + sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(8)); + break; + case SNDRV_PCM_FORMAT_S32_LE: + sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(16)); + break; + } + pxa_ssp_write_reg(ssp, SSCR0, sscr0); + + if (sscr0 & SSCR0_ACS) { + ret = pxa_ssp_set_pll(priv, bclk); + + /* + * If we were able to generate the bclk directly, + * all is fine. Otherwise, look up the closest rate + * from the table and also set the dividers. + */ + + if (ret < 0) { + const struct pxa_ssp_clock_mode *m; + int ssacd, acds; + + for (m = pxa_ssp_clock_modes; m->rate; m++) { + if (m->rate == rate) + break; + } + + if (!m->rate) + return -EINVAL; + + acds = m->acds; + + /* The values in the table are for 16 bits */ + if (width == 32) + acds--; + + ret = pxa_ssp_set_pll(priv, bclk); + if (ret < 0) + return ret; + + ssacd = pxa_ssp_read_reg(ssp, SSACD); + ssacd &= ~(SSACD_ACDS(7) | SSACD_SCDB_1X); + ssacd |= SSACD_ACDS(m->acds); + ssacd |= m->scdb; + pxa_ssp_write_reg(ssp, SSACD, ssacd); + } + } else if (sscr0 & SSCR0_ECS) { + /* + * For setups with external clocking, the PLL and its diviers + * are not active. Instead, the SCR bits in SSCR0 can be used + * to divide the clock. + */ + pxa_ssp_set_scr(ssp, bclk / rate); + } + + switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + sspsp = pxa_ssp_read_reg(ssp, SSPSP); + + if (((priv->sysclk / bclk) == 64) && (width == 16)) { + /* This is a special case where the bitclk is 64fs + * and we're not dealing with 2*32 bits of audio + * samples. + * + * The SSP values used for that are all found out by + * trying and failing a lot; some of the registers + * needed for that mode are only available on PXA3xx. + */ + if (ssp->type != PXA3xx_SSP) + return -EINVAL; + + sspsp |= SSPSP_SFRMWDTH(width * 2); + sspsp |= SSPSP_SFRMDLY(width * 4); + sspsp |= SSPSP_EDMYSTOP(3); + sspsp |= SSPSP_DMYSTOP(3); + sspsp |= SSPSP_DMYSTRT(1); + } else { + /* The frame width is the width the LRCLK is + * asserted for; the delay is expressed in + * half cycle units. We need the extra cycle + * because the data starts clocking out one BCLK + * after LRCLK changes polarity. + */ + sspsp |= SSPSP_SFRMWDTH(width + 1); + sspsp |= SSPSP_SFRMDLY((width + 1) * 2); + sspsp |= SSPSP_DMYSTRT(1); + } + + pxa_ssp_write_reg(ssp, SSPSP, sspsp); + break; + default: + break; + } + + /* When we use a network mode, we always require TDM slots + * - complain loudly and fail if they've not been set up yet. + */ + if ((sscr0 & SSCR0_MOD) && !ttsa) { + dev_err(&ssp->pdev->dev, "No TDM timeslot configured\n"); + return -EINVAL; + } + + dump_registers(ssp); + + return 0; +} + +static void pxa_ssp_set_running_bit(struct snd_pcm_substream *substream, + struct ssp_device *ssp, int value) +{ + uint32_t sscr0 = pxa_ssp_read_reg(ssp, SSCR0); + uint32_t sscr1 = pxa_ssp_read_reg(ssp, SSCR1); + uint32_t sspsp = pxa_ssp_read_reg(ssp, SSPSP); + uint32_t sssr = pxa_ssp_read_reg(ssp, SSSR); + + if (value && (sscr0 & SSCR0_SSE)) + pxa_ssp_write_reg(ssp, SSCR0, sscr0 & ~SSCR0_SSE); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (value) + sscr1 |= SSCR1_TSRE; + else + sscr1 &= ~SSCR1_TSRE; + } else { + if (value) + sscr1 |= SSCR1_RSRE; + else + sscr1 &= ~SSCR1_RSRE; + } + + pxa_ssp_write_reg(ssp, SSCR1, sscr1); + + if (value) { + pxa_ssp_write_reg(ssp, SSSR, sssr); + pxa_ssp_write_reg(ssp, SSPSP, sspsp); + pxa_ssp_write_reg(ssp, SSCR0, sscr0 | SSCR0_SSE); + } +} + +static int pxa_ssp_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + int ret = 0; + struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); + struct ssp_device *ssp = priv->ssp; + int val; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + pxa_ssp_enable(ssp); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pxa_ssp_set_running_bit(substream, ssp, 1); + val = pxa_ssp_read_reg(ssp, SSSR); + pxa_ssp_write_reg(ssp, SSSR, val); + break; + case SNDRV_PCM_TRIGGER_START: + pxa_ssp_set_running_bit(substream, ssp, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pxa_ssp_set_running_bit(substream, ssp, 0); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + pxa_ssp_disable(ssp); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pxa_ssp_set_running_bit(substream, ssp, 0); + break; + + default: + ret = -EINVAL; + } + + dump_registers(ssp); + + return ret; +} + +static int pxa_ssp_probe(struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct ssp_priv *priv; + int ret; + + priv = kzalloc(sizeof(struct ssp_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (dev->of_node) { + struct device_node *ssp_handle; + + ssp_handle = of_parse_phandle(dev->of_node, "port", 0); + if (!ssp_handle) { + dev_err(dev, "unable to get 'port' phandle\n"); + ret = -ENODEV; + goto err_priv; + } + + priv->ssp = pxa_ssp_request_of(ssp_handle, "SoC audio"); + if (priv->ssp == NULL) { + ret = -ENODEV; + goto err_priv; + } + + priv->extclk = devm_clk_get(dev, "extclk"); + if (IS_ERR(priv->extclk)) { + ret = PTR_ERR(priv->extclk); + if (ret == -EPROBE_DEFER) + return ret; + + priv->extclk = NULL; + } + } else { + priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio"); + if (priv->ssp == NULL) { + ret = -ENODEV; + goto err_priv; + } + } + + priv->dai_fmt = (unsigned int) -1; + snd_soc_dai_set_drvdata(dai, priv); + + return 0; + +err_priv: + kfree(priv); + return ret; +} + +static int pxa_ssp_remove(struct snd_soc_dai *dai) +{ + struct ssp_priv *priv = snd_soc_dai_get_drvdata(dai); + + pxa_ssp_free(priv->ssp); + kfree(priv); + return 0; +} + +#define PXA_SSP_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + 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) + +#define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops pxa_ssp_dai_ops = { + .startup = pxa_ssp_startup, + .shutdown = pxa_ssp_shutdown, + .trigger = pxa_ssp_trigger, + .hw_params = pxa_ssp_hw_params, + .set_sysclk = pxa_ssp_set_dai_sysclk, + .set_fmt = pxa_ssp_set_dai_fmt, + .set_tdm_slot = pxa_ssp_set_dai_tdm_slot, + .set_tristate = pxa_ssp_set_dai_tristate, +}; + +static struct snd_soc_dai_driver pxa_ssp_dai = { + .probe = pxa_ssp_probe, + .remove = pxa_ssp_remove, + .suspend = pxa_ssp_suspend, + .resume = pxa_ssp_resume, + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = PXA_SSP_RATES, + .formats = PXA_SSP_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 8, + .rates = PXA_SSP_RATES, + .formats = PXA_SSP_FORMATS, + }, + .ops = &pxa_ssp_dai_ops, +}; + +static const struct snd_soc_component_driver pxa_ssp_component = { + .name = "pxa-ssp", + .ops = &pxa2xx_pcm_ops, + .pcm_new = pxa2xx_soc_pcm_new, + .pcm_free = pxa2xx_pcm_free_dma_buffers, +}; + +#ifdef CONFIG_OF +static const struct of_device_id pxa_ssp_of_ids[] = { + { .compatible = "mrvl,pxa-ssp-dai" }, + {} +}; +MODULE_DEVICE_TABLE(of, pxa_ssp_of_ids); +#endif + +static int asoc_ssp_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &pxa_ssp_component, + &pxa_ssp_dai, 1); +} + +static struct platform_driver asoc_ssp_driver = { + .driver = { + .name = "pxa-ssp-dai", + .of_match_table = of_match_ptr(pxa_ssp_of_ids), + }, + + .probe = asoc_ssp_probe, +}; + +module_platform_driver(asoc_ssp_driver); + +/* Module information */ +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa-ssp-dai"); diff --git a/sound/soc/pxa/pxa-ssp.h b/sound/soc/pxa/pxa-ssp.h new file mode 100644 index 000000000..abf6ec080 --- /dev/null +++ b/sound/soc/pxa/pxa-ssp.h @@ -0,0 +1,39 @@ +/* + * ASoC PXA SSP port support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PXA_SSP_H +#define _PXA_SSP_H + +/* SSP clock sources */ +#define PXA_SSP_CLK_PLL 0 +#define PXA_SSP_CLK_EXT 1 +#define PXA_SSP_CLK_NET 2 +#define PXA_SSP_CLK_AUDIO 3 +#define PXA_SSP_CLK_NET_PLL 4 + +/* SSP audio dividers */ +#define PXA_SSP_AUDIO_DIV_ACDS 0 +#define PXA_SSP_AUDIO_DIV_SCDB 1 +#define PXA_SSP_DIV_SCR 2 + +/* SSP ACDS audio dividers values */ +#define PXA_SSP_CLK_AUDIO_DIV_1 0 +#define PXA_SSP_CLK_AUDIO_DIV_2 1 +#define PXA_SSP_CLK_AUDIO_DIV_4 2 +#define PXA_SSP_CLK_AUDIO_DIV_8 3 +#define PXA_SSP_CLK_AUDIO_DIV_16 4 +#define PXA_SSP_CLK_AUDIO_DIV_32 5 + +/* SSP divider bypass */ +#define PXA_SSP_CLK_SCDB_4 0 +#define PXA_SSP_CLK_SCDB_1 1 +#define PXA_SSP_CLK_SCDB_8 2 + +#define PXA_SSP_PLL_OUT 0 + +#endif diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c new file mode 100644 index 000000000..9f779657b --- /dev/null +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -0,0 +1,300 @@ +/* + * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip. + * + * Author: Nicolas Pitre + * Created: Dec 02, 2004 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +static void pxa2xx_ac97_warm_reset(struct snd_ac97 *ac97) +{ + pxa2xx_ac97_try_warm_reset(); + + pxa2xx_ac97_finish_reset(); +} + +static void pxa2xx_ac97_cold_reset(struct snd_ac97 *ac97) +{ + pxa2xx_ac97_try_cold_reset(); + + pxa2xx_ac97_finish_reset(); +} + +static unsigned short pxa2xx_ac97_legacy_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int ret; + + ret = pxa2xx_ac97_read(ac97->num, reg); + if (ret < 0) + return 0; + else + return (unsigned short)(ret & 0xffff); +} + +static void pxa2xx_ac97_legacy_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int ret; + + ret = pxa2xx_ac97_write(ac97->num, reg, val); +} + +static struct snd_ac97_bus_ops pxa2xx_ac97_ops = { + .read = pxa2xx_ac97_legacy_read, + .write = pxa2xx_ac97_legacy_write, + .warm_reset = pxa2xx_ac97_warm_reset, + .reset = pxa2xx_ac97_cold_reset, +}; + +static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_stereo_in = { + .addr = __PREG(PCDR), + .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .chan_name = "pcm_pcm_stereo_in", + .maxburst = 32, +}; + +static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_stereo_out = { + .addr = __PREG(PCDR), + .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .chan_name = "pcm_pcm_stereo_out", + .maxburst = 32, +}; + +static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_aux_mono_out = { + .addr = __PREG(MODR), + .addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES, + .chan_name = "pcm_aux_mono_out", + .maxburst = 16, +}; + +static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_aux_mono_in = { + .addr = __PREG(MODR), + .addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES, + .chan_name = "pcm_aux_mono_in", + .maxburst = 16, +}; + +static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_mic_mono_in = { + .addr = __PREG(MCDR), + .addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES, + .chan_name = "pcm_aux_mic_mono", + .maxburst = 16, +}; + +static int pxa2xx_ac97_hifi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &pxa2xx_ac97_pcm_stereo_out; + else + dma_data = &pxa2xx_ac97_pcm_stereo_in; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + return 0; +} + +static int pxa2xx_ac97_aux_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &pxa2xx_ac97_pcm_aux_mono_out; + else + dma_data = &pxa2xx_ac97_pcm_aux_mono_in; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + return 0; +} + +static int pxa2xx_ac97_mic_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + snd_soc_dai_set_dma_data(cpu_dai, substream, + &pxa2xx_ac97_pcm_mic_mono_in); + + return 0; +} + +#define PXA2XX_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +static const struct snd_soc_dai_ops pxa_ac97_hifi_dai_ops = { + .startup = pxa2xx_ac97_hifi_startup, +}; + +static const struct snd_soc_dai_ops pxa_ac97_aux_dai_ops = { + .startup = pxa2xx_ac97_aux_startup, +}; + +static const struct snd_soc_dai_ops pxa_ac97_mic_dai_ops = { + .startup = pxa2xx_ac97_mic_startup, +}; + +/* + * There is only 1 physical AC97 interface for pxa2xx, but it + * has extra fifo's that can be used for aux DACs and ADCs. + */ +static struct snd_soc_dai_driver pxa_ac97_dai_driver[] = { +{ + .name = "pxa2xx-ac97", + .bus_control = true, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = PXA2XX_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = PXA2XX_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &pxa_ac97_hifi_dai_ops, +}, +{ + .name = "pxa2xx-ac97-aux", + .bus_control = true, + .playback = { + .stream_name = "AC97 Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = PXA2XX_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "AC97 Aux Capture", + .channels_min = 1, + .channels_max = 1, + .rates = PXA2XX_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &pxa_ac97_aux_dai_ops, +}, +{ + .name = "pxa2xx-ac97-mic", + .bus_control = true, + .capture = { + .stream_name = "AC97 Mic Capture", + .channels_min = 1, + .channels_max = 1, + .rates = PXA2XX_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &pxa_ac97_mic_dai_ops, +}, +}; + +static const struct snd_soc_component_driver pxa_ac97_component = { + .name = "pxa-ac97", + .ops = &pxa2xx_pcm_ops, + .pcm_new = pxa2xx_soc_pcm_new, + .pcm_free = pxa2xx_pcm_free_dma_buffers, +}; + +#ifdef CONFIG_OF +static const struct of_device_id pxa2xx_ac97_dt_ids[] = { + { .compatible = "marvell,pxa250-ac97", }, + { .compatible = "marvell,pxa270-ac97", }, + { .compatible = "marvell,pxa300-ac97", }, + { } +}; +MODULE_DEVICE_TABLE(of, pxa2xx_ac97_dt_ids); + +#endif + +static int pxa2xx_ac97_dev_probe(struct platform_device *pdev) +{ + int ret; + + if (pdev->id != -1) { + dev_err(&pdev->dev, "PXA2xx has only one AC97 port.\n"); + return -ENXIO; + } + + ret = pxa2xx_ac97_hw_probe(pdev); + if (ret) { + dev_err(&pdev->dev, "PXA2xx AC97 hw probe error (%d)\n", ret); + return ret; + } + + ret = snd_soc_set_ac97_ops(&pxa2xx_ac97_ops); + if (ret != 0) + return ret; + + /* Punt most of the init to the SoC probe; we may need the machine + * driver to do interesting things with the clocking to get us up + * and running. + */ + return snd_soc_register_component(&pdev->dev, &pxa_ac97_component, + pxa_ac97_dai_driver, ARRAY_SIZE(pxa_ac97_dai_driver)); +} + +static int pxa2xx_ac97_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + snd_soc_set_ac97_ops(NULL); + pxa2xx_ac97_hw_remove(pdev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pxa2xx_ac97_dev_suspend(struct device *dev) +{ + return pxa2xx_ac97_hw_suspend(); +} + +static int pxa2xx_ac97_dev_resume(struct device *dev) +{ + return pxa2xx_ac97_hw_resume(); +} + +static SIMPLE_DEV_PM_OPS(pxa2xx_ac97_pm_ops, + pxa2xx_ac97_dev_suspend, pxa2xx_ac97_dev_resume); +#endif + +static struct platform_driver pxa2xx_ac97_driver = { + .probe = pxa2xx_ac97_dev_probe, + .remove = pxa2xx_ac97_dev_remove, + .driver = { + .name = "pxa2xx-ac97", +#ifdef CONFIG_PM_SLEEP + .pm = &pxa2xx_ac97_pm_ops, +#endif + .of_match_table = of_match_ptr(pxa2xx_ac97_dt_ids), + }, +}; + +module_platform_driver(pxa2xx_ac97_driver); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa2xx-ac97"); diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c new file mode 100644 index 000000000..42820121e --- /dev/null +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -0,0 +1,404 @@ +/* + * pxa2xx-i2s.c -- ALSA Soc Audio Layer + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * lrg@slimlogic.co.uk + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pxa2xx-i2s.h" + +/* + * I2S Controller Register and Bit Definitions + */ +#define SACR0 __REG(0x40400000) /* Global Control Register */ +#define SACR1 __REG(0x40400004) /* Serial Audio I 2 S/MSB-Justified Control Register */ +#define SASR0 __REG(0x4040000C) /* Serial Audio I 2 S/MSB-Justified Interface and FIFO Status Register */ +#define SAIMR __REG(0x40400014) /* Serial Audio Interrupt Mask Register */ +#define SAICR __REG(0x40400018) /* Serial Audio Interrupt Clear Register */ +#define SADIV __REG(0x40400060) /* Audio Clock Divider Register. */ +#define SADR __REG(0x40400080) /* Serial Audio Data Register (TX and RX FIFO access Register). */ + +#define SACR0_RFTH(x) ((x) << 12) /* Rx FIFO Interrupt or DMA Trigger Threshold */ +#define SACR0_TFTH(x) ((x) << 8) /* Tx FIFO Interrupt or DMA Trigger Threshold */ +#define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */ +#define SACR0_EFWR (1 << 4) /* Enable EFWR Function */ +#define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */ +#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */ +#define SACR0_ENB (1 << 0) /* Enable I2S Link */ +#define SACR1_ENLBF (1 << 5) /* Enable Loopback */ +#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */ +#define SACR1_DREC (1 << 3) /* Disable Recording Function */ +#define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */ + +#define SASR0_I2SOFF (1 << 7) /* Controller Status */ +#define SASR0_ROR (1 << 6) /* Rx FIFO Overrun */ +#define SASR0_TUR (1 << 5) /* Tx FIFO Underrun */ +#define SASR0_RFS (1 << 4) /* Rx FIFO Service Request */ +#define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */ +#define SASR0_BSY (1 << 2) /* I2S Busy */ +#define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */ +#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */ + +#define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */ +#define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */ + +#define SAIMR_ROR (1 << 6) /* Enable Rx FIFO Overrun Condition Interrupt */ +#define SAIMR_TUR (1 << 5) /* Enable Tx FIFO Underrun Condition Interrupt */ +#define SAIMR_RFS (1 << 4) /* Enable Rx FIFO Service Interrupt */ +#define SAIMR_TFS (1 << 3) /* Enable Tx FIFO Service Interrupt */ + +struct pxa_i2s_port { + u32 sadiv; + u32 sacr0; + u32 sacr1; + u32 saimr; + int master; + u32 fmt; +}; +static struct pxa_i2s_port pxa_i2s; +static struct clk *clk_i2s; +static int clk_ena = 0; + +static struct snd_dmaengine_dai_dma_data pxa2xx_i2s_pcm_stereo_out = { + .addr = __PREG(SADR), + .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .chan_name = "tx", + .maxburst = 32, +}; + +static struct snd_dmaengine_dai_dma_data pxa2xx_i2s_pcm_stereo_in = { + .addr = __PREG(SADR), + .addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .chan_name = "rx", + .maxburst = 32, +}; + +static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + if (IS_ERR(clk_i2s)) + return PTR_ERR(clk_i2s); + + if (!cpu_dai->active) + SACR0 = 0; + + return 0; +} + +/* wait for I2S controller to be ready */ +static int pxa_i2s_wait(void) +{ + int i; + + /* flush the Rx FIFO */ + for (i = 0; i < 16; i++) + SADR; + return 0; +} + +static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + pxa_i2s.fmt = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + pxa_i2s.fmt = SACR1_AMSL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + pxa_i2s.master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + pxa_i2s.master = 0; + break; + default: + break; + } + return 0; +} + +static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + if (clk_id != PXA2XX_I2S_SYSCLK) + return -ENODEV; + + return 0; +} + +static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + + if (WARN_ON(IS_ERR(clk_i2s))) + return -EINVAL; + clk_prepare_enable(clk_i2s); + clk_ena = 1; + pxa_i2s_wait(); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &pxa2xx_i2s_pcm_stereo_out; + else + dma_data = &pxa2xx_i2s_pcm_stereo_in; + + snd_soc_dai_set_dma_data(dai, substream, dma_data); + + /* is port used by another stream */ + if (!(SACR0 & SACR0_ENB)) { + SACR0 = 0; + if (pxa_i2s.master) + SACR0 |= SACR0_BCKD; + + SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1); + SACR1 |= pxa_i2s.fmt; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SAIMR |= SAIMR_TFS; + else + SAIMR |= SAIMR_RFS; + + switch (params_rate(params)) { + case 8000: + SADIV = 0x48; + break; + case 11025: + SADIV = 0x34; + break; + case 16000: + SADIV = 0x24; + break; + case 22050: + SADIV = 0x1a; + break; + case 44100: + SADIV = 0xd; + break; + case 48000: + SADIV = 0xc; + break; + case 96000: /* not in manual and possibly slightly inaccurate */ + SADIV = 0x6; + break; + } + + return 0; +} + +static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SACR1 &= ~SACR1_DRPL; + else + SACR1 &= ~SACR1_DREC; + SACR0 |= SACR0_ENB; + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + SACR1 |= SACR1_DRPL; + SAIMR &= ~SAIMR_TFS; + } else { + SACR1 |= SACR1_DREC; + SAIMR &= ~SAIMR_RFS; + } + + if ((SACR1 & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) { + SACR0 &= ~SACR0_ENB; + pxa_i2s_wait(); + if (clk_ena) { + clk_disable_unprepare(clk_i2s); + clk_ena = 0; + } + } +} + +#ifdef CONFIG_PM +static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai) +{ + /* store registers */ + pxa_i2s.sacr0 = SACR0; + pxa_i2s.sacr1 = SACR1; + pxa_i2s.saimr = SAIMR; + pxa_i2s.sadiv = SADIV; + + /* deactivate link */ + SACR0 &= ~SACR0_ENB; + pxa_i2s_wait(); + return 0; +} + +static int pxa2xx_i2s_resume(struct snd_soc_dai *dai) +{ + pxa_i2s_wait(); + + SACR0 = pxa_i2s.sacr0 & ~SACR0_ENB; + SACR1 = pxa_i2s.sacr1; + SAIMR = pxa_i2s.saimr; + SADIV = pxa_i2s.sadiv; + + SACR0 = pxa_i2s.sacr0; + + return 0; +} + +#else +#define pxa2xx_i2s_suspend NULL +#define pxa2xx_i2s_resume NULL +#endif + +static int pxa2xx_i2s_probe(struct snd_soc_dai *dai) +{ + clk_i2s = clk_get(dai->dev, "I2SCLK"); + if (IS_ERR(clk_i2s)) + return PTR_ERR(clk_i2s); + + /* + * PXA Developer's Manual: + * If SACR0[ENB] is toggled in the middle of a normal operation, + * the SACR0[RST] bit must also be set and cleared to reset all + * I2S controller registers. + */ + SACR0 = SACR0_RST; + SACR0 = 0; + /* Make sure RPL and REC are disabled */ + SACR1 = SACR1_DRPL | SACR1_DREC; + /* Along with FIFO servicing */ + SAIMR &= ~(SAIMR_RFS | SAIMR_TFS); + + snd_soc_dai_init_dma_data(dai, &pxa2xx_i2s_pcm_stereo_out, + &pxa2xx_i2s_pcm_stereo_in); + + return 0; +} + +static int pxa2xx_i2s_remove(struct snd_soc_dai *dai) +{ + clk_put(clk_i2s); + clk_i2s = ERR_PTR(-ENOENT); + return 0; +} + +#define PXA2XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) + +static const struct snd_soc_dai_ops pxa_i2s_dai_ops = { + .startup = pxa2xx_i2s_startup, + .shutdown = pxa2xx_i2s_shutdown, + .trigger = pxa2xx_i2s_trigger, + .hw_params = pxa2xx_i2s_hw_params, + .set_fmt = pxa2xx_i2s_set_dai_fmt, + .set_sysclk = pxa2xx_i2s_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver pxa_i2s_dai = { + .probe = pxa2xx_i2s_probe, + .remove = pxa2xx_i2s_remove, + .suspend = pxa2xx_i2s_suspend, + .resume = pxa2xx_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = PXA2XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = PXA2XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &pxa_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver pxa_i2s_component = { + .name = "pxa-i2s", + .ops = &pxa2xx_pcm_ops, + .pcm_new = pxa2xx_soc_pcm_new, + .pcm_free = pxa2xx_pcm_free_dma_buffers, +}; + +static int pxa2xx_i2s_drv_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &pxa_i2s_component, + &pxa_i2s_dai, 1); +} + +static struct platform_driver pxa2xx_i2s_driver = { + .probe = pxa2xx_i2s_drv_probe, + + .driver = { + .name = "pxa2xx-i2s", + }, +}; + +static int __init pxa2xx_i2s_init(void) +{ + clk_i2s = ERR_PTR(-ENOENT); + return platform_driver_register(&pxa2xx_i2s_driver); +} + +static void __exit pxa2xx_i2s_exit(void) +{ + platform_driver_unregister(&pxa2xx_i2s_driver); +} + +module_init(pxa2xx_i2s_init); +module_exit(pxa2xx_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); +MODULE_DESCRIPTION("pxa2xx I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa2xx-i2s"); diff --git a/sound/soc/pxa/pxa2xx-i2s.h b/sound/soc/pxa/pxa2xx-i2s.h new file mode 100644 index 000000000..7e218e210 --- /dev/null +++ b/sound/soc/pxa/pxa2xx-i2s.h @@ -0,0 +1,15 @@ +/* + * linux/sound/soc/pxa/pxa2xx-i2s.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PXA2XX_I2S_H +#define _PXA2XX_I2S_H + +/* I2S clock */ +#define PXA2XX_I2S_SYSCLK 0 + +#endif diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c new file mode 100644 index 000000000..72eaaef1b --- /dev/null +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -0,0 +1,48 @@ +/* + * linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: (C) 2004 MontaVista Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +static const struct snd_soc_component_driver pxa2xx_soc_platform = { + .ops = &pxa2xx_pcm_ops, + .pcm_new = pxa2xx_soc_pcm_new, + .pcm_free = pxa2xx_pcm_free_dma_buffers, +}; + +static int pxa2xx_soc_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &pxa2xx_soc_platform, + NULL, 0); +} + +static struct platform_driver pxa_pcm_driver = { + .driver = { + .name = "pxa-pcm-audio", + }, + + .probe = pxa2xx_soc_platform_probe, +}; + +module_platform_driver(pxa_pcm_driver); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa-pcm-audio"); diff --git a/sound/soc/pxa/raumfeld.c b/sound/soc/pxa/raumfeld.c new file mode 100644 index 000000000..111a907c4 --- /dev/null +++ b/sound/soc/pxa/raumfeld.c @@ -0,0 +1,318 @@ +/* + * raumfeld_audio.c -- SoC audio for Raumfeld audio devices + * + * Copyright (c) 2009 Daniel Mack + * + * based on code from: + * + * Wolfson Microelectronics PLC. + * Openedhand Ltd. + * Liam Girdwood + * Richard Purdie + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "pxa-ssp.h" + +#define GPIO_SPDIF_RESET (38) +#define GPIO_MCLK_RESET (111) +#define GPIO_CODEC_RESET (120) + +static struct i2c_client *max9486_client; +static struct i2c_board_info max9486_hwmon_info = { + I2C_BOARD_INFO("max9485", 0x63), +}; + +#define MAX9485_MCLK_FREQ_112896 0x22 +#define MAX9485_MCLK_FREQ_122880 0x23 +#define MAX9485_MCLK_FREQ_225792 0x32 +#define MAX9485_MCLK_FREQ_245760 0x33 + +static void set_max9485_clk(char clk) +{ + i2c_master_send(max9486_client, &clk, 1); +} + +static void raumfeld_enable_audio(bool en) +{ + if (en) { + gpio_set_value(GPIO_MCLK_RESET, 1); + + /* wait some time to let the clocks become stable */ + msleep(100); + + gpio_set_value(GPIO_SPDIF_RESET, 1); + gpio_set_value(GPIO_CODEC_RESET, 1); + } else { + gpio_set_value(GPIO_MCLK_RESET, 0); + gpio_set_value(GPIO_SPDIF_RESET, 0); + gpio_set_value(GPIO_CODEC_RESET, 0); + } +} + +/* CS4270 */ +static int raumfeld_cs4270_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* set freq to 0 to enable all possible codec sample rates */ + return snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0); +} + +static void raumfeld_cs4270_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* set freq to 0 to enable all possible codec sample rates */ + snd_soc_dai_set_sysclk(codec_dai, 0, 0, 0); +} + +static int raumfeld_cs4270_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 44100: + set_max9485_clk(MAX9485_MCLK_FREQ_112896); + clk = 11289600; + break; + case 48000: + set_max9485_clk(MAX9485_MCLK_FREQ_122880); + clk = 12288000; + break; + case 88200: + set_max9485_clk(MAX9485_MCLK_FREQ_225792); + clk = 22579200; + break; + case 96000: + set_max9485_clk(MAX9485_MCLK_FREQ_245760); + clk = 24576000; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, 0); + if (ret < 0) + return ret; + + /* setup the CPU DAI */ + ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops raumfeld_cs4270_ops = { + .startup = raumfeld_cs4270_startup, + .shutdown = raumfeld_cs4270_shutdown, + .hw_params = raumfeld_cs4270_hw_params, +}; + +static int raumfeld_analog_suspend(struct snd_soc_card *card) +{ + raumfeld_enable_audio(false); + return 0; +} + +static int raumfeld_analog_resume(struct snd_soc_card *card) +{ + raumfeld_enable_audio(true); + return 0; +} + +/* AK4104 */ + +static int raumfeld_ak4104_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0, clk = 0; + + switch (params_rate(params)) { + case 44100: + set_max9485_clk(MAX9485_MCLK_FREQ_112896); + clk = 11289600; + break; + case 48000: + set_max9485_clk(MAX9485_MCLK_FREQ_122880); + clk = 12288000; + break; + case 88200: + set_max9485_clk(MAX9485_MCLK_FREQ_225792); + clk = 22579200; + break; + case 96000: + set_max9485_clk(MAX9485_MCLK_FREQ_245760); + clk = 24576000; + break; + default: + return -EINVAL; + } + + /* setup the CPU DAI */ + ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, clk); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, PXA_SSP_DIV_SCR, 4); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_EXT, clk, 1); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops raumfeld_ak4104_ops = { + .hw_params = raumfeld_ak4104_hw_params, +}; + +#define DAI_LINK_CS4270 \ +{ \ + .name = "CS4270", \ + .stream_name = "CS4270", \ + .cpu_dai_name = "pxa-ssp-dai.0", \ + .platform_name = "pxa-pcm-audio", \ + .codec_dai_name = "cs4270-hifi", \ + .codec_name = "cs4270.0-0048", \ + .dai_fmt = SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS, \ + .ops = &raumfeld_cs4270_ops, \ +} + +#define DAI_LINK_AK4104 \ +{ \ + .name = "ak4104", \ + .stream_name = "Playback", \ + .cpu_dai_name = "pxa-ssp-dai.1", \ + .codec_dai_name = "ak4104-hifi", \ + .platform_name = "pxa-pcm-audio", \ + .dai_fmt = SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS, \ + .ops = &raumfeld_ak4104_ops, \ + .codec_name = "spi0.0", \ +} + +static struct snd_soc_dai_link snd_soc_raumfeld_connector_dai[] = { + DAI_LINK_CS4270, + DAI_LINK_AK4104, +}; + +static struct snd_soc_dai_link snd_soc_raumfeld_speaker_dai[] = { + DAI_LINK_CS4270, +}; + +static struct snd_soc_card snd_soc_raumfeld_connector = { + .name = "Raumfeld Connector", + .owner = THIS_MODULE, + .dai_link = snd_soc_raumfeld_connector_dai, + .num_links = ARRAY_SIZE(snd_soc_raumfeld_connector_dai), + .suspend_post = raumfeld_analog_suspend, + .resume_pre = raumfeld_analog_resume, +}; + +static struct snd_soc_card snd_soc_raumfeld_speaker = { + .name = "Raumfeld Speaker", + .owner = THIS_MODULE, + .dai_link = snd_soc_raumfeld_speaker_dai, + .num_links = ARRAY_SIZE(snd_soc_raumfeld_speaker_dai), + .suspend_post = raumfeld_analog_suspend, + .resume_pre = raumfeld_analog_resume, +}; + +static struct platform_device *raumfeld_audio_device; + +static int __init raumfeld_audio_init(void) +{ + int ret; + + if (!machine_is_raumfeld_speaker() && + !machine_is_raumfeld_connector()) + return 0; + + max9486_client = i2c_new_device(i2c_get_adapter(0), + &max9486_hwmon_info); + + if (!max9486_client) + return -ENOMEM; + + set_max9485_clk(MAX9485_MCLK_FREQ_122880); + + /* Register analog device */ + raumfeld_audio_device = platform_device_alloc("soc-audio", 0); + if (!raumfeld_audio_device) + return -ENOMEM; + + if (machine_is_raumfeld_speaker()) + platform_set_drvdata(raumfeld_audio_device, + &snd_soc_raumfeld_speaker); + + if (machine_is_raumfeld_connector()) + platform_set_drvdata(raumfeld_audio_device, + &snd_soc_raumfeld_connector); + + ret = platform_device_add(raumfeld_audio_device); + if (ret < 0) { + platform_device_put(raumfeld_audio_device); + return ret; + } + + raumfeld_enable_audio(true); + return 0; +} + +static void __exit raumfeld_audio_exit(void) +{ + raumfeld_enable_audio(false); + + platform_device_unregister(raumfeld_audio_device); + + i2c_unregister_device(max9486_client); + + gpio_free(GPIO_MCLK_RESET); + gpio_free(GPIO_CODEC_RESET); + gpio_free(GPIO_SPDIF_RESET); +} + +module_init(raumfeld_audio_init); +module_exit(raumfeld_audio_exit); + +/* Module information */ +MODULE_AUTHOR("Daniel Mack "); +MODULE_DESCRIPTION("Raumfeld audio SoC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c new file mode 100644 index 000000000..1671da648 --- /dev/null +++ b/sound/soc/pxa/spitz.c @@ -0,0 +1,343 @@ +/* + * spitz.c -- SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood + * Richard Purdie + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../codecs/wm8750.h" +#include "pxa2xx-i2s.h" + +#define SPITZ_HP 0 +#define SPITZ_MIC 1 +#define SPITZ_LINE 2 +#define SPITZ_HEADSET 3 +#define SPITZ_HP_OFF 4 +#define SPITZ_SPK_ON 0 +#define SPITZ_SPK_OFF 1 + + /* audio clock in Hz - rounded from 12.235MHz */ +#define SPITZ_AUDIO_CLOCK 12288000 + +static int spitz_jack_func; +static int spitz_spk_func; +static int spitz_mic_gpio; + +static void spitz_ext_control(struct snd_soc_dapm_context *dapm) +{ + snd_soc_dapm_mutex_lock(dapm); + + if (spitz_spk_func == SPITZ_SPK_ON) + snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk"); + + /* set up jack connection */ + switch (spitz_jack_func) { + case SPITZ_HP: + /* enable and unmute hp jack, disable mic bias */ + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 1); + gpio_set_value(SPITZ_GPIO_MUTE_R, 1); + break; + case SPITZ_MIC: + /* enable mic jack and bias, mute hp */ + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 0); + break; + case SPITZ_LINE: + /* enable line jack, disable mic bias and mute hp */ + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Line Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 0); + break; + case SPITZ_HEADSET: + /* enable and unmute headset jack enable mic bias, mute L hp */ + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 1); + break; + case SPITZ_HP_OFF: + + /* jack removed, everything off */ + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 0); + break; + } + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +static int spitz_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* check the jack status at stream startup */ + spitz_ext_control(&rtd->card->dapm); + + return 0; +} + +static int spitz_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops spitz_ops = { + .startup = spitz_startup, + .hw_params = spitz_hw_params, +}; + +static int spitz_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = spitz_jack_func; + return 0; +} + +static int spitz_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (spitz_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + spitz_jack_func = ucontrol->value.enumerated.item[0]; + spitz_ext_control(&card->dapm); + return 1; +} + +static int spitz_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = spitz_spk_func; + return 0; +} + +static int spitz_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (spitz_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + spitz_spk_func = ucontrol->value.enumerated.item[0]; + spitz_ext_control(&card->dapm); + return 1; +} + +static int spitz_mic_bias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value_cansleep(spitz_mic_gpio, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +/* spitz machine dapm widgets */ +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_LINE("Line Jack", NULL), + + /* headset is a mic and mono headphone */ + SND_SOC_DAPM_HP("Headset Jack", NULL), +}; + +/* Spitz machine audio_map */ +static const struct snd_soc_dapm_route spitz_audio_map[] = { + + /* headphone connected to LOUT1, ROUT1 */ + {"Headphone Jack", NULL, "LOUT1"}, + {"Headphone Jack", NULL, "ROUT1"}, + + /* headset connected to ROUT1 and LINPUT1 with bias (def below) */ + {"Headset Jack", NULL, "ROUT1"}, + + /* ext speaker connected to LOUT2, ROUT2 */ + {"Ext Spk", NULL, "ROUT2"}, + {"Ext Spk", NULL, "LOUT2"}, + + /* mic is connected to input 1 - with bias */ + {"LINPUT1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic Jack"}, + + /* line is connected to input 1 - no bias */ + {"LINPUT1", NULL, "Line Jack"}, +}; + +static const char * const jack_function[] = {"Headphone", "Mic", "Line", + "Headset", "Off"}; +static const char * const spk_function[] = {"On", "Off"}; +static const struct soc_enum spitz_enum[] = { + SOC_ENUM_SINGLE_EXT(5, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static const struct snd_kcontrol_new wm8750_spitz_controls[] = { + SOC_ENUM_EXT("Jack Function", spitz_enum[0], spitz_get_jack, + spitz_set_jack), + SOC_ENUM_EXT("Speaker Function", spitz_enum[1], spitz_get_spk, + spitz_set_spk), +}; + +/* spitz digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link spitz_dai = { + .name = "wm8750", + .stream_name = "WM8750", + .cpu_dai_name = "pxa2xx-i2s", + .codec_dai_name = "wm8750-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm8750.0-001b", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &spitz_ops, +}; + +/* spitz audio machine driver */ +static struct snd_soc_card snd_soc_spitz = { + .name = "Spitz", + .owner = THIS_MODULE, + .dai_link = &spitz_dai, + .num_links = 1, + + .controls = wm8750_spitz_controls, + .num_controls = ARRAY_SIZE(wm8750_spitz_controls), + .dapm_widgets = wm8750_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets), + .dapm_routes = spitz_audio_map, + .num_dapm_routes = ARRAY_SIZE(spitz_audio_map), + .fully_routed = true, +}; + +static int spitz_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_spitz; + int ret; + + if (machine_is_akita()) + spitz_mic_gpio = AKITA_GPIO_MIC_BIAS; + else + spitz_mic_gpio = SPITZ_GPIO_MIC_BIAS; + + ret = gpio_request(spitz_mic_gpio, "MIC GPIO"); + if (ret) + goto err1; + + ret = gpio_direction_output(spitz_mic_gpio, 0); + if (ret) + goto err2; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + goto err2; + } + + return 0; + +err2: + gpio_free(spitz_mic_gpio); +err1: + return ret; +} + +static int spitz_remove(struct platform_device *pdev) +{ + gpio_free(spitz_mic_gpio); + return 0; +} + +static struct platform_driver spitz_driver = { + .driver = { + .name = "spitz-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = spitz_probe, + .remove = spitz_remove, +}; + +module_platform_driver(spitz_driver); + +MODULE_AUTHOR("Richard Purdie"); +MODULE_DESCRIPTION("ALSA SoC Spitz"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spitz-audio"); diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c new file mode 100644 index 000000000..ae9c12e1e --- /dev/null +++ b/sound/soc/pxa/tosa.c @@ -0,0 +1,263 @@ +/* + * tosa.c -- SoC audio for Tosa + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood + * Richard Purdie + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * GPIO's + * 1 - Jack Insertion + * 5 - Hookswitch (headset answer/hang up switch) + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define TOSA_HP 0 +#define TOSA_MIC_INT 1 +#define TOSA_HEADSET 2 +#define TOSA_HP_OFF 3 +#define TOSA_SPK_ON 0 +#define TOSA_SPK_OFF 1 + +static int tosa_jack_func; +static int tosa_spk_func; + +static void tosa_ext_control(struct snd_soc_dapm_context *dapm) +{ + + snd_soc_dapm_mutex_lock(dapm); + + /* set up jack connection */ + switch (tosa_jack_func) { + case TOSA_HP: + snd_soc_dapm_disable_pin_unlocked(dapm, "Mic (Internal)"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + break; + case TOSA_MIC_INT: + snd_soc_dapm_enable_pin_unlocked(dapm, "Mic (Internal)"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack"); + break; + case TOSA_HEADSET: + snd_soc_dapm_disable_pin_unlocked(dapm, "Mic (Internal)"); + snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Jack"); + break; + } + + if (tosa_spk_func == TOSA_SPK_ON) + snd_soc_dapm_enable_pin_unlocked(dapm, "Speaker"); + else + snd_soc_dapm_disable_pin_unlocked(dapm, "Speaker"); + + snd_soc_dapm_sync_unlocked(dapm); + + snd_soc_dapm_mutex_unlock(dapm); +} + +static int tosa_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* check the jack status at stream startup */ + tosa_ext_control(&rtd->card->dapm); + + return 0; +} + +static const struct snd_soc_ops tosa_ops = { + .startup = tosa_startup, +}; + +static int tosa_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = tosa_jack_func; + return 0; +} + +static int tosa_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (tosa_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + tosa_jack_func = ucontrol->value.enumerated.item[0]; + tosa_ext_control(&card->dapm); + return 1; +} + +static int tosa_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = tosa_spk_func; + return 0; +} + +static int tosa_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (tosa_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + tosa_spk_func = ucontrol->value.enumerated.item[0]; + tosa_ext_control(&card->dapm); + return 1; +} + +/* tosa dapm event handlers */ +static int tosa_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0); + return 0; +} + +/* tosa machine dapm widgets */ +static const struct snd_soc_dapm_widget tosa_dapm_widgets[] = { +SND_SOC_DAPM_HP("Headphone Jack", tosa_hp_event), +SND_SOC_DAPM_HP("Headset Jack", NULL), +SND_SOC_DAPM_MIC("Mic (Internal)", NULL), +SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +/* tosa audio map */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* headphone connected to HPOUTL, HPOUTR */ + {"Headphone Jack", NULL, "HPOUTL"}, + {"Headphone Jack", NULL, "HPOUTR"}, + + /* ext speaker connected to LOUT2, ROUT2 */ + {"Speaker", NULL, "LOUT2"}, + {"Speaker", NULL, "ROUT2"}, + + /* internal mic is connected to mic1, mic2 differential - with bias */ + {"MIC1", NULL, "Mic Bias"}, + {"MIC2", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic (Internal)"}, + + /* headset is connected to HPOUTR, and LINEINR with bias */ + {"Headset Jack", NULL, "HPOUTR"}, + {"LINEINR", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Jack"}, +}; + +static const char * const jack_function[] = {"Headphone", "Mic", "Line", + "Headset", "Off"}; +static const char * const spk_function[] = {"On", "Off"}; +static const struct soc_enum tosa_enum[] = { + SOC_ENUM_SINGLE_EXT(5, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static const struct snd_kcontrol_new tosa_controls[] = { + SOC_ENUM_EXT("Jack Function", tosa_enum[0], tosa_get_jack, + tosa_set_jack), + SOC_ENUM_EXT("Speaker Function", tosa_enum[1], tosa_get_spk, + tosa_set_spk), +}; + +static struct snd_soc_dai_link tosa_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9712-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9712-codec", + .ops = &tosa_ops, +}, +{ + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9712-aux", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm9712-codec", + .ops = &tosa_ops, +}, +}; + +static struct snd_soc_card tosa = { + .name = "Tosa", + .owner = THIS_MODULE, + .dai_link = tosa_dai, + .num_links = ARRAY_SIZE(tosa_dai), + + .controls = tosa_controls, + .num_controls = ARRAY_SIZE(tosa_controls), + .dapm_widgets = tosa_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tosa_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, +}; + +static int tosa_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = ⤩ + int ret; + + ret = gpio_request_one(TOSA_GPIO_L_MUTE, GPIOF_OUT_INIT_LOW, + "Headphone Jack"); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + gpio_free(TOSA_GPIO_L_MUTE); + } + return ret; +} + +static int tosa_remove(struct platform_device *pdev) +{ + gpio_free(TOSA_GPIO_L_MUTE); + return 0; +} + +static struct platform_driver tosa_driver = { + .driver = { + .name = "tosa-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = tosa_probe, + .remove = tosa_remove, +}; + +module_platform_driver(tosa_driver); + +/* Module information */ +MODULE_AUTHOR("Richard Purdie"); +MODULE_DESCRIPTION("ALSA SoC Tosa"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:tosa-audio"); diff --git a/sound/soc/pxa/ttc-dkb.c b/sound/soc/pxa/ttc-dkb.c new file mode 100644 index 000000000..5d6e61a4b --- /dev/null +++ b/sound/soc/pxa/ttc-dkb.c @@ -0,0 +1,153 @@ +/* + * linux/sound/soc/pxa/ttc_dkb.c + * + * Copyright (C) 2012 Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/88pm860x-codec.h" + +static struct snd_soc_jack hs_jack, mic_jack; + +static struct snd_soc_jack_pin hs_jack_pins[] = { + { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE, }, +}; + +static struct snd_soc_jack_pin mic_jack_pins[] = { + { .pin = "Headset Mic 2", .mask = SND_JACK_MICROPHONE, }, +}; + +/* ttc machine dapm widgets */ +static const struct snd_soc_dapm_widget ttc_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_LINE("Lineout Out 1", NULL), + SND_SOC_DAPM_LINE("Lineout Out 2", NULL), + SND_SOC_DAPM_SPK("Ext Speaker", NULL), + SND_SOC_DAPM_MIC("Ext Mic 1", NULL), + SND_SOC_DAPM_MIC("Headset Mic 2", NULL), + SND_SOC_DAPM_MIC("Ext Mic 3", NULL), +}; + +/* ttc machine audio map */ +static const struct snd_soc_dapm_route ttc_audio_map[] = { + {"Headset Stereophone", NULL, "HS1"}, + {"Headset Stereophone", NULL, "HS2"}, + + {"Ext Speaker", NULL, "LSP"}, + {"Ext Speaker", NULL, "LSN"}, + + {"Lineout Out 1", NULL, "LINEOUT1"}, + {"Lineout Out 2", NULL, "LINEOUT2"}, + + {"MIC1P", NULL, "Mic1 Bias"}, + {"MIC1N", NULL, "Mic1 Bias"}, + {"Mic1 Bias", NULL, "Ext Mic 1"}, + + {"MIC2P", NULL, "Mic1 Bias"}, + {"MIC2N", NULL, "Mic1 Bias"}, + {"Mic1 Bias", NULL, "Headset Mic 2"}, + + {"MIC3P", NULL, "Mic3 Bias"}, + {"MIC3N", NULL, "Mic3 Bias"}, + {"Mic3 Bias", NULL, "Ext Mic 3"}, +}; + +static int ttc_pm860x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + + /* Headset jack detection */ + snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2, + &hs_jack, hs_jack_pins, ARRAY_SIZE(hs_jack_pins)); + snd_soc_card_jack_new(rtd->card, "Microphone Jack", SND_JACK_MICROPHONE, + &mic_jack, mic_jack_pins, + ARRAY_SIZE(mic_jack_pins)); + + /* headphone, microphone detection & headset short detection */ + pm860x_hs_jack_detect(component, &hs_jack, SND_JACK_HEADPHONE, + SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2); + pm860x_mic_jack_detect(component, &hs_jack, SND_JACK_MICROPHONE); + + return 0; +} + +/* ttc/td-dkb digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link ttc_pm860x_hifi_dai[] = { +{ + .name = "88pm860x i2s", + .stream_name = "audio playback", + .codec_name = "88pm860x-codec", + .platform_name = "mmp-pcm-audio", + .cpu_dai_name = "pxa-ssp-dai.1", + .codec_dai_name = "88pm860x-i2s", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .init = ttc_pm860x_init, +}, +}; + +/* ttc/td audio machine driver */ +static struct snd_soc_card ttc_dkb_card = { + .name = "ttc-dkb-hifi", + .owner = THIS_MODULE, + .dai_link = ttc_pm860x_hifi_dai, + .num_links = ARRAY_SIZE(ttc_pm860x_hifi_dai), + + .dapm_widgets = ttc_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ttc_dapm_widgets), + .dapm_routes = ttc_audio_map, + .num_dapm_routes = ARRAY_SIZE(ttc_audio_map), +}; + +static int ttc_dkb_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &ttc_dkb_card; + int ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static struct platform_driver ttc_dkb_driver = { + .driver = { + .name = "ttc-dkb-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = ttc_dkb_probe, +}; + +module_platform_driver(ttc_dkb_driver); + +/* Module information */ +MODULE_AUTHOR("Qiao Zhou, "); +MODULE_DESCRIPTION("ALSA SoC TTC DKB"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ttc-dkb-audio"); diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c new file mode 100644 index 000000000..5b0eccd2b --- /dev/null +++ b/sound/soc/pxa/z2.c @@ -0,0 +1,220 @@ +/* + * linux/sound/soc/pxa/z2.c + * + * SoC Audio driver for Aeronix Zipit Z2 + * + * Copyright (C) 2009 Ken McGuire + * Copyright (C) 2010 Marek Vasut + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/wm8750.h" +#include "pxa2xx-i2s.h" + +static struct snd_soc_card snd_soc_z2; + +static int z2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_jack hs_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Ext Spk", + .mask = SND_JACK_HEADPHONE, + .invert = 1 + }, +}; + +/* Headset jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .gpio = GPIO37_ZIPITZ2_HEADSET_DETECT, + .name = "hsdet-gpio", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + .invert = 1, + }, +}; + +/* z2 machine dapm widgets */ +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + + /* headset is a mic and mono headphone */ + SND_SOC_DAPM_HP("Headset Jack", NULL), +}; + +/* Z2 machine audio_map */ +static const struct snd_soc_dapm_route z2_audio_map[] = { + + /* headphone connected to LOUT1, ROUT1 */ + {"Headphone Jack", NULL, "LOUT1"}, + {"Headphone Jack", NULL, "ROUT1"}, + + /* ext speaker connected to LOUT2, ROUT2 */ + {"Ext Spk", NULL, "ROUT2"}, + {"Ext Spk", NULL, "LOUT2"}, + + /* mic is connected to R input 2 - with bias */ + {"RINPUT2", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic Jack"}, +}; + +/* + * Logic for a wm8750 as connected on a Z2 Device + */ +static int z2_wm8750_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + + /* Jack detection API stuff */ + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", SND_JACK_HEADSET, + &hs_jack, hs_jack_pins, + ARRAY_SIZE(hs_jack_pins)); + if (ret) + goto err; + + ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + if (ret) + goto err; + + return 0; + +err: + return ret; +} + +static const struct snd_soc_ops z2_ops = { + .hw_params = z2_hw_params, +}; + +/* z2 digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link z2_dai = { + .name = "wm8750", + .stream_name = "WM8750", + .cpu_dai_name = "pxa2xx-i2s", + .codec_dai_name = "wm8750-hifi", + .platform_name = "pxa-pcm-audio", + .codec_name = "wm8750.0-001b", + .init = z2_wm8750_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &z2_ops, +}; + +/* z2 audio machine driver */ +static struct snd_soc_card snd_soc_z2 = { + .name = "Z2", + .owner = THIS_MODULE, + .dai_link = &z2_dai, + .num_links = 1, + + .dapm_widgets = wm8750_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets), + .dapm_routes = z2_audio_map, + .num_dapm_routes = ARRAY_SIZE(z2_audio_map), + .fully_routed = true, +}; + +static struct platform_device *z2_snd_device; + +static int __init z2_init(void) +{ + int ret; + + if (!machine_is_zipit2()) + return -ENODEV; + + z2_snd_device = platform_device_alloc("soc-audio", -1); + if (!z2_snd_device) + return -ENOMEM; + + platform_set_drvdata(z2_snd_device, &snd_soc_z2); + ret = platform_device_add(z2_snd_device); + + if (ret) + platform_device_put(z2_snd_device); + + return ret; +} + +static void __exit z2_exit(void) +{ + platform_device_unregister(z2_snd_device); +} + +module_init(z2_init); +module_exit(z2_exit); + +MODULE_AUTHOR("Ken McGuire , " + "Marek Vasut "); +MODULE_DESCRIPTION("ALSA SoC ZipitZ2"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c new file mode 100644 index 000000000..230eee450 --- /dev/null +++ b/sound/soc/pxa/zylonite.c @@ -0,0 +1,265 @@ +/* + * zylonite.c -- SoC audio for Zylonite + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/wm9713.h" +#include "pxa-ssp.h" + +/* + * There is a physical switch SW15 on the board which changes the MCLK + * for the WM9713 between the standard AC97 master clock and the + * output of the CLK_POUT signal from the PXA. + */ +static int clk_pout; +module_param(clk_pout, int, 0); +MODULE_PARM_DESC(clk_pout, "Use CLK_POUT as WM9713 MCLK (SW15 on board)."); + +static struct clk *pout; + +static struct snd_soc_card zylonite; + +static const struct snd_soc_dapm_widget zylonite_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Microphone", NULL), + SND_SOC_DAPM_MIC("Handset Microphone", NULL), + SND_SOC_DAPM_SPK("Multiactor", NULL), + SND_SOC_DAPM_SPK("Headset Earpiece", NULL), +}; + +/* Currently supported audio map */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Headphone output connected to HPL/HPR */ + { "Headphone", NULL, "HPL" }, + { "Headphone", NULL, "HPR" }, + + /* On-board earpiece */ + { "Headset Earpiece", NULL, "OUT3" }, + + /* Headphone mic */ + { "MIC2A", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "Headset Microphone" }, + + /* On-board mic */ + { "MIC1", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "Handset Microphone" }, + + /* Multiactor differentially connected over SPKL/SPKR */ + { "Multiactor", NULL, "SPKL" }, + { "Multiactor", NULL, "SPKR" }, +}; + +static int zylonite_wm9713_init(struct snd_soc_pcm_runtime *rtd) +{ + if (clk_pout) + snd_soc_dai_set_pll(rtd->codec_dai, 0, 0, + clk_get_rate(pout), 0); + + return 0; +} + +static int zylonite_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int wm9713_div = 0; + int ret = 0; + int rate = params_rate(params); + + /* Only support ratios that we can generate neatly from the AC97 + * based master clock - in particular, this excludes 44.1kHz. + * In most applications the voice DAC will be used for telephony + * data so multiples of 8kHz will be the common case. + */ + switch (rate) { + case 8000: + wm9713_div = 12; + break; + case 16000: + wm9713_div = 6; + break; + case 48000: + wm9713_div = 2; + break; + default: + /* Don't support OSS emulation */ + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0, 1); + if (ret < 0) + return ret; + + if (clk_pout) + ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_PLL_DIV, + WM9713_PCMDIV(wm9713_div)); + else + ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_DIV, + WM9713_PCMDIV(wm9713_div)); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops zylonite_voice_ops = { + .hw_params = zylonite_voice_hw_params, +}; + +static struct snd_soc_dai_link zylonite_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .codec_name = "wm9713-codec", + .platform_name = "pxa-pcm-audio", + .cpu_dai_name = "pxa2xx-ac97", + .codec_dai_name = "wm9713-hifi", + .init = zylonite_wm9713_init, +}, +{ + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .codec_name = "wm9713-codec", + .platform_name = "pxa-pcm-audio", + .cpu_dai_name = "pxa2xx-ac97-aux", + .codec_dai_name = "wm9713-aux", +}, +{ + .name = "WM9713 Voice", + .stream_name = "WM9713 Voice", + .codec_name = "wm9713-codec", + .platform_name = "pxa-pcm-audio", + .cpu_dai_name = "pxa-ssp-dai.2", + .codec_dai_name = "wm9713-voice", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &zylonite_voice_ops, +}, +}; + +static int zylonite_probe(struct snd_soc_card *card) +{ + int ret; + + if (clk_pout) { + pout = clk_get(NULL, "CLK_POUT"); + if (IS_ERR(pout)) { + dev_err(card->dev, "Unable to obtain CLK_POUT: %ld\n", + PTR_ERR(pout)); + return PTR_ERR(pout); + } + + ret = clk_enable(pout); + if (ret != 0) { + dev_err(card->dev, "Unable to enable CLK_POUT: %d\n", + ret); + clk_put(pout); + return ret; + } + + dev_dbg(card->dev, "MCLK enabled at %luHz\n", + clk_get_rate(pout)); + } + + return 0; +} + +static int zylonite_remove(struct snd_soc_card *card) +{ + if (clk_pout) { + clk_disable(pout); + clk_put(pout); + } + + return 0; +} + +static int zylonite_suspend_post(struct snd_soc_card *card) +{ + if (clk_pout) + clk_disable(pout); + + return 0; +} + +static int zylonite_resume_pre(struct snd_soc_card *card) +{ + int ret = 0; + + if (clk_pout) { + ret = clk_enable(pout); + if (ret != 0) + dev_err(card->dev, "Unable to enable CLK_POUT: %d\n", + ret); + } + + return ret; +} + +static struct snd_soc_card zylonite = { + .name = "Zylonite", + .owner = THIS_MODULE, + .probe = &zylonite_probe, + .remove = &zylonite_remove, + .suspend_post = &zylonite_suspend_post, + .resume_pre = &zylonite_resume_pre, + .dai_link = zylonite_dai, + .num_links = ARRAY_SIZE(zylonite_dai), + + .dapm_widgets = zylonite_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(zylonite_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct platform_device *zylonite_snd_ac97_device; + +static int __init zylonite_init(void) +{ + int ret; + + zylonite_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!zylonite_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(zylonite_snd_ac97_device, &zylonite); + + ret = platform_device_add(zylonite_snd_ac97_device); + if (ret != 0) + platform_device_put(zylonite_snd_ac97_device); + + return ret; +} + +static void __exit zylonite_exit(void) +{ + platform_device_unregister(zylonite_snd_ac97_device); +} + +module_init(zylonite_init); +module_exit(zylonite_exit); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("ALSA SoC WM9713 Zylonite"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig new file mode 100644 index 000000000..57941413d --- /dev/null +++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,106 @@ +config SND_SOC_QCOM + tristate "ASoC support for QCOM platforms" + depends on ARCH_QCOM || COMPILE_TEST + help + Say Y or M if you want to add support to use audio devices + in Qualcomm Technologies SOC-based platforms. + +config SND_SOC_LPASS_CPU + tristate + select REGMAP_MMIO + +config SND_SOC_LPASS_PLATFORM + tristate + select REGMAP_MMIO + +config SND_SOC_LPASS_IPQ806X + tristate + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + +config SND_SOC_LPASS_APQ8016 + tristate + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + +config SND_SOC_STORM + tristate "ASoC I2S support for Storm boards" + depends on SND_SOC_QCOM + select SND_SOC_LPASS_IPQ806X + select SND_SOC_MAX98357A + help + Say Y or M if you want add support for SoC audio on the + Qualcomm Technologies IPQ806X-based Storm board. + +config SND_SOC_APQ8016_SBC + tristate "SoC Audio support for APQ8016 SBC platforms" + depends on SND_SOC_QCOM + select SND_SOC_LPASS_APQ8016 + help + Support for Qualcomm Technologies LPASS audio block in + APQ8016 SOC-based systems. + Say Y if you want to use audio devices on MI2S. + +config SND_SOC_QCOM_COMMON + tristate + +config SND_SOC_QDSP6_COMMON + tristate + +config SND_SOC_QDSP6_CORE + tristate + +config SND_SOC_QDSP6_AFE + tristate + +config SND_SOC_QDSP6_AFE_DAI + tristate + +config SND_SOC_QDSP6_ADM + tristate + +config SND_SOC_QDSP6_ROUTING + tristate + +config SND_SOC_QDSP6_ASM + tristate + +config SND_SOC_QDSP6_ASM_DAI + tristate + +config SND_SOC_QDSP6 + tristate "SoC ALSA audio driver for QDSP6" + depends on QCOM_APR + select SND_SOC_QDSP6_COMMON + select SND_SOC_QDSP6_CORE + select SND_SOC_QDSP6_AFE + select SND_SOC_QDSP6_AFE_DAI + select SND_SOC_QDSP6_ADM + select SND_SOC_QDSP6_ROUTING + select SND_SOC_QDSP6_ASM + select SND_SOC_QDSP6_ASM_DAI + help + To add support for MSM QDSP6 Soc Audio. + This will enable sound soc platform specific + audio drivers. This includes q6asm, q6adm, + q6afe interfaces to DSP using apr. + +config SND_SOC_MSM8996 + tristate "SoC Machine driver for MSM8996 and APQ8096 boards" + depends on QCOM_APR + select SND_SOC_QDSP6 + select SND_SOC_QCOM_COMMON + help + Support for Qualcomm Technologies LPASS audio block in + APQ8096 SoC-based systems. + Say Y if you want to use audio device on this SoCs + +config SND_SOC_SDM845 + tristate "SoC Machine driver for SDM845 boards" + depends on QCOM_APR + select SND_SOC_QDSP6 + select SND_SOC_QCOM_COMMON + help + To add support for audio on Qualcomm Technologies Inc. + SDM845 SoC-based systems. + Say Y if you want to use audio device on this SoCs. diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile new file mode 100644 index 000000000..41b2c7a23 --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +# Platform +snd-soc-lpass-cpu-objs := lpass-cpu.o +snd-soc-lpass-platform-objs := lpass-platform.o +snd-soc-lpass-ipq806x-objs := lpass-ipq806x.o +snd-soc-lpass-apq8016-objs := lpass-apq8016.o + +obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o +obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o +obj-$(CONFIG_SND_SOC_LPASS_IPQ806X) += snd-soc-lpass-ipq806x.o +obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o + +# Machine +snd-soc-storm-objs := storm.o +snd-soc-apq8016-sbc-objs := apq8016_sbc.o +snd-soc-apq8096-objs := apq8096.o +snd-soc-sdm845-objs := sdm845.o +snd-soc-qcom-common-objs := common.o + +obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o +obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o +obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o +obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o +obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o + +#DSP lib +obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ diff --git a/sound/soc/qcom/apq8016_sbc.c b/sound/soc/qcom/apq8016_sbc.c new file mode 100644 index 000000000..121460db8 --- /dev/null +++ b/sound/soc/qcom/apq8016_sbc.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct apq8016_sbc_data { + void __iomem *mic_iomux; + void __iomem *spkr_iomux; + struct snd_soc_jack jack; + bool jack_setup; + struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ +}; + +#define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21) +#define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17) +#define MIC_CTRL_TLMM_SCLK_EN BIT(1) +#define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16)) +#define DEFAULT_MCLK_RATE 9600000 + +static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_component *component; + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_card *card = rtd->card; + struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card); + int i, rval; + + switch (cpu_dai->id) { + case MI2S_PRIMARY: + writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11, + pdata->spkr_iomux); + break; + + case MI2S_QUATERNARY: + /* Configure the Quat MI2S to TLMM */ + writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 | + MIC_CTRL_TLMM_SCLK_EN, + pdata->mic_iomux); + break; + case MI2S_TERTIARY: + writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL | + MIC_CTRL_TLMM_SCLK_EN, + pdata->mic_iomux); + + break; + + default: + dev_err(card->dev, "unsupported cpu dai configuration\n"); + return -EINVAL; + + } + + if (!pdata->jack_setup) { + struct snd_jack *jack; + + rval = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_HEADPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | + SND_JACK_BTN_4, + &pdata->jack, NULL, 0); + + if (rval < 0) { + dev_err(card->dev, "Unable to add Headphone Jack\n"); + return rval; + } + + jack = pdata->jack.jack; + + snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + pdata->jack_setup = true; + } + + for (i = 0 ; i < dai_link->num_codecs; i++) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + + component = dai->component; + /* Set default mclk for internal codec */ + rval = snd_soc_component_set_sysclk(component, 0, 0, DEFAULT_MCLK_RATE, + SND_SOC_CLOCK_IN); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set mclk: %d\n", rval); + return rval; + } + rval = snd_soc_component_set_jack(component, &pdata->jack, NULL); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set jack: %d\n", rval); + return rval; + } + } + + return 0; +} + +static struct apq8016_sbc_data *apq8016_sbc_parse_of(struct snd_soc_card *card) +{ + struct device *dev = card->dev; + struct snd_soc_dai_link *link; + struct device_node *np, *codec, *cpu, *node = dev->of_node; + struct apq8016_sbc_data *data; + int ret, num_links; + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(dev, "Error parsing card name: %d\n", ret); + return ERR_PTR(ret); + } + + /* DAPM routes */ + if (of_property_read_bool(node, "qcom,audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, + "qcom,audio-routing"); + if (ret) + return ERR_PTR(ret); + } + + + /* Populate links */ + num_links = of_get_child_count(node); + + /* Allocate the private data and the DAI link array */ + data = devm_kzalloc(dev, + struct_size(data, dai_link, num_links), + GFP_KERNEL); + if (!data) + return ERR_PTR(-ENOMEM); + + card->dai_link = &data->dai_link[0]; + card->num_links = num_links; + + link = data->dai_link; + + for_each_child_of_node(node, np) { + cpu = of_get_child_by_name(np, "cpu"); + codec = of_get_child_by_name(np, "codec"); + + if (!cpu || !codec) { + dev_err(dev, "Can't find cpu/codec DT node\n"); + ret = -EINVAL; + goto error; + } + + link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + if (!link->cpu_of_node) { + dev_err(card->dev, "error getting cpu phandle\n"); + ret = -EINVAL; + goto error; + } + + ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name); + if (ret) { + dev_err(card->dev, "error getting cpu dai name\n"); + goto error; + } + + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + + if (ret < 0) { + dev_err(card->dev, "error getting codec dai name\n"); + goto error; + } + + link->platform_of_node = link->cpu_of_node; + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { + dev_err(card->dev, "error getting codec dai_link name\n"); + goto error; + } + + link->stream_name = link->name; + link->init = apq8016_sbc_dai_init; + link++; + + of_node_put(cpu); + of_node_put(codec); + } + + return data; + + error: + of_node_put(np); + of_node_put(cpu); + of_node_put(codec); + return ERR_PTR(ret); +} + +static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = { + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Secondary Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), +}; + +static int apq8016_sbc_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + struct apq8016_sbc_data *data; + struct resource *res; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + card->dapm_widgets = apq8016_sbc_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(apq8016_sbc_dapm_widgets); + data = apq8016_sbc_parse_of(card); + if (IS_ERR(data)) { + dev_err(&pdev->dev, "Error resolving dai links: %ld\n", + PTR_ERR(data)); + return PTR_ERR(data); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux"); + data->mic_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->mic_iomux)) + return PTR_ERR(data->mic_iomux); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux"); + data->spkr_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->spkr_iomux)) + return PTR_ERR(data->spkr_iomux); + + snd_soc_card_set_drvdata(card, data); + + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct of_device_id apq8016_sbc_device_id[] = { + { .compatible = "qcom,apq8016-sbc-sndcard" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id); + +static struct platform_driver apq8016_sbc_platform_driver = { + .driver = { + .name = "qcom-apq8016-sbc", + .of_match_table = of_match_ptr(apq8016_sbc_device_id), + }, + .probe = apq8016_sbc_platform_probe, +}; +module_platform_driver(apq8016_sbc_platform_driver); + +MODULE_AUTHOR("Srinivas Kandagatla +#include +#include +#include +#include +#include +#include "common.h" + +static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static void apq8096_add_be_ops(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link = card->dai_link; + int i, num_links = card->num_links; + + for (i = 0; i < num_links; i++) { + if (link->no_pcm == 1) + link->be_hw_params_fixup = apq8096_be_hw_params_fixup; + link++; + } +} + +static int apq8096_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ret; + + card = kzalloc(sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + dev_set_drvdata(dev, card); + ret = qcom_snd_parse_of(card); + if (ret) { + dev_err(dev, "Error parsing OF data\n"); + goto err; + } + + apq8096_add_be_ops(card); + ret = snd_soc_register_card(card); + if (ret) + goto err_card_register; + + return 0; + +err_card_register: + kfree(card->dai_link); +err: + kfree(card); + return ret; +} + +static int apq8096_platform_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_card(card); + kfree(card->dai_link); + kfree(card); + + return 0; +} + +static const struct of_device_id msm_snd_apq8096_dt_match[] = { + {.compatible = "qcom,apq8096-sndcard"}, + {} +}; + +MODULE_DEVICE_TABLE(of, msm_snd_apq8096_dt_match); + +static struct platform_driver msm_snd_apq8096_driver = { + .probe = apq8096_platform_probe, + .remove = apq8096_platform_remove, + .driver = { + .name = "msm-snd-apq8096", + .of_match_table = msm_snd_apq8096_dt_match, + }, +}; +module_platform_driver(msm_snd_apq8096_driver); +MODULE_AUTHOR("Srinivas Kandagatla +#include "common.h" + +int qcom_snd_parse_of(struct snd_soc_card *card) +{ + struct device_node *np; + struct device_node *codec = NULL; + struct device_node *platform = NULL; + struct device_node *cpu = NULL; + struct device *dev = card->dev; + struct snd_soc_dai_link *link; + struct of_phandle_args args; + int ret, num_links; + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret) { + dev_err(dev, "Error parsing card name: %d\n", ret); + return ret; + } + + /* DAPM routes */ + if (of_property_read_bool(dev->of_node, "audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, + "audio-routing"); + if (ret) + return ret; + } + + /* Populate links */ + num_links = of_get_child_count(dev->of_node); + + /* Allocate the DAI link array */ + card->dai_link = kcalloc(num_links, sizeof(*link), GFP_KERNEL); + if (!card->dai_link) + return -ENOMEM; + + card->num_links = num_links; + link = card->dai_link; + for_each_child_of_node(dev->of_node, np) { + cpu = of_get_child_by_name(np, "cpu"); + platform = of_get_child_by_name(np, "platform"); + codec = of_get_child_by_name(np, "codec"); + + if (!cpu) { + dev_err(dev, "Can't find cpu DT node\n"); + ret = -EINVAL; + goto err; + } + + ret = of_parse_phandle_with_args(cpu, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + dev_err(card->dev, "error getting cpu phandle\n"); + goto err; + } + link->cpu_of_node = args.np; + link->id = args.args[0]; + + ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name); + if (ret) { + dev_err(card->dev, "error getting cpu dai name\n"); + goto err; + } + + if (codec && platform) { + link->platform_of_node = of_parse_phandle(platform, + "sound-dai", + 0); + if (!link->platform_of_node) { + dev_err(card->dev, "platform dai not found\n"); + ret = -EINVAL; + goto err; + } + + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { + dev_err(card->dev, "codec dai not found\n"); + goto err; + } + link->no_pcm = 1; + link->ignore_pmdown_time = 1; + } else { + link->platform_of_node = link->cpu_of_node; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + link->dynamic = 1; + } + + link->ignore_suspend = 1; + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { + dev_err(card->dev, "error getting codec dai_link name\n"); + goto err; + } + + link->dpcm_playback = 1; + link->dpcm_capture = 1; + link->stream_name = link->name; + link++; + + of_node_put(cpu); + of_node_put(codec); + of_node_put(platform); + } + + return 0; +err: + of_node_put(np); + of_node_put(cpu); + of_node_put(codec); + of_node_put(platform); + kfree(card->dai_link); + return ret; +} +EXPORT_SYMBOL(qcom_snd_parse_of); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h new file mode 100644 index 000000000..f05c05b12 --- /dev/null +++ b/sound/soc/qcom/common.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// Copyright (c) 2018, The Linux Foundation. All rights reserved. + +#ifndef __QCOM_SND_COMMON_H__ +#define __QCOM_SND_COMMON_H__ + +#include + +int qcom_snd_parse_of(struct snd_soc_card *card); + +#endif diff --git a/sound/soc/qcom/lpass-apq8016.c b/sound/soc/qcom/lpass-apq8016.c new file mode 100644 index 000000000..8a74844d9 --- /dev/null +++ b/sound/soc/qcom/lpass-apq8016.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass-apq8016.c -- ALSA SoC CPU DAI driver for APQ8016 LPASS + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = { + [MI2S_PRIMARY] = { + .id = MI2S_PRIMARY, + .name = "Primary MI2S", + .playback = { + .stream_name = "Primary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_SECONDARY] = { + .id = MI2S_SECONDARY, + .name = "Secondary MI2S", + .playback = { + .stream_name = "Secondary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_TERTIARY] = { + .id = MI2S_TERTIARY, + .name = "Tertiary MI2S", + .capture = { + .stream_name = "Tertiary Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_QUATERNARY] = { + .id = MI2S_QUATERNARY, + .name = "Quatenary MI2S", + .playback = { + .stream_name = "Quatenary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .stream_name = "Quatenary Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, +}; + +static int apq8016_lpass_alloc_dma_channel(struct lpass_data *drvdata, + int direction) +{ + struct lpass_variant *v = drvdata->variant; + int chan = 0; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + chan = find_first_zero_bit(&drvdata->dma_ch_bit_map, + v->rdma_channels); + + if (chan >= v->rdma_channels) + return -EBUSY; + } else { + chan = find_next_zero_bit(&drvdata->dma_ch_bit_map, + v->wrdma_channel_start + + v->wrdma_channels, + v->wrdma_channel_start); + + if (chan >= v->wrdma_channel_start + v->wrdma_channels) + return -EBUSY; + } + + set_bit(chan, &drvdata->dma_ch_bit_map); + + return chan; +} + +static int apq8016_lpass_free_dma_channel(struct lpass_data *drvdata, int chan) +{ + clear_bit(chan, &drvdata->dma_ch_bit_map); + + return 0; +} + +static int apq8016_lpass_init(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int ret; + + drvdata->pcnoc_mport_clk = devm_clk_get(dev, "pcnoc-mport-clk"); + if (IS_ERR(drvdata->pcnoc_mport_clk)) { + dev_err(&pdev->dev, "error getting pcnoc-mport-clk: %ld\n", + PTR_ERR(drvdata->pcnoc_mport_clk)); + return PTR_ERR(drvdata->pcnoc_mport_clk); + } + + ret = clk_prepare_enable(drvdata->pcnoc_mport_clk); + if (ret) { + dev_err(&pdev->dev, "Error enabling pcnoc-mport-clk: %d\n", + ret); + return ret; + } + + drvdata->pcnoc_sway_clk = devm_clk_get(dev, "pcnoc-sway-clk"); + if (IS_ERR(drvdata->pcnoc_sway_clk)) { + dev_err(&pdev->dev, "error getting pcnoc-sway-clk: %ld\n", + PTR_ERR(drvdata->pcnoc_sway_clk)); + return PTR_ERR(drvdata->pcnoc_sway_clk); + } + + ret = clk_prepare_enable(drvdata->pcnoc_sway_clk); + if (ret) { + dev_err(&pdev->dev, "Error enabling pcnoc_sway_clk: %d\n", ret); + return ret; + } + + return 0; +} + +static int apq8016_lpass_exit(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->pcnoc_mport_clk); + clk_disable_unprepare(drvdata->pcnoc_sway_clk); + + return 0; +} + + +static struct lpass_variant apq8016_data = { + .i2sctrl_reg_base = 0x1000, + .i2sctrl_reg_stride = 0x1000, + .i2s_ports = 4, + .irq_reg_base = 0x6000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0x8400, + .rdma_reg_stride = 0x1000, + .rdma_channels = 2, + .dmactl_audif_start = 1, + .wrdma_reg_base = 0xB000, + .wrdma_reg_stride = 0x1000, + .wrdma_channel_start = 5, + .wrdma_channels = 2, + .dai_driver = apq8016_lpass_cpu_dai_driver, + .num_dai = ARRAY_SIZE(apq8016_lpass_cpu_dai_driver), + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk0", + "mi2s-osr-clk1", + "mi2s-osr-clk2", + "mi2s-osr-clk3", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk0", + "mi2s-bit-clk1", + "mi2s-bit-clk2", + "mi2s-bit-clk3", + }, + .init = apq8016_lpass_init, + .exit = apq8016_lpass_exit, + .alloc_dma_channel = apq8016_lpass_alloc_dma_channel, + .free_dma_channel = apq8016_lpass_free_dma_channel, +}; + +static const struct of_device_id apq8016_lpass_cpu_device_id[] = { + { .compatible = "qcom,lpass-cpu-apq8016", .data = &apq8016_data }, + {} +}; +MODULE_DEVICE_TABLE(of, apq8016_lpass_cpu_device_id); + +static struct platform_driver apq8016_lpass_cpu_platform_driver = { + .driver = { + .name = "apq8016-lpass-cpu", + .of_match_table = of_match_ptr(apq8016_lpass_cpu_device_id), + }, + .probe = asoc_qcom_lpass_cpu_platform_probe, + .remove = asoc_qcom_lpass_cpu_platform_remove, +}; +module_platform_driver(apq8016_lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("APQ8016 LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c new file mode 100644 index 000000000..475579a98 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu.c @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass-cpu.c -- ALSA SoC CPU DAI driver for QTi LPASS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_set_rate(drvdata->mi2s_osr_clk[dai->driver->id], freq); + if (ret) + dev_err(dai->dev, "error setting mi2s osrclk to %u: %d\n", + freq, ret); + + return ret; +} + +static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_prepare_enable(drvdata->mi2s_osr_clk[dai->driver->id]); + if (ret) { + dev_err(dai->dev, "error in enabling mi2s osr clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->mi2s_bit_clk[dai->driver->id]); + if (ret) { + dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret); + clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); + return ret; + } + + return 0; +} + +static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]); + + clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); +} + +static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + unsigned int regval; + int bitwidth, ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "invalid bit width given: %d\n", bitwidth); + return bitwidth; + } + + regval = LPAIF_I2SCTL_LOOPBACK_DISABLE | + LPAIF_I2SCTL_WSSRC_INTERNAL; + + switch (bitwidth) { + case 16: + regval |= LPAIF_I2SCTL_BITWIDTH_16; + break; + case 24: + regval |= LPAIF_I2SCTL_BITWIDTH_24; + break; + case 32: + regval |= LPAIF_I2SCTL_BITWIDTH_32; + break; + default: + dev_err(dai->dev, "invalid bitwidth given: %d\n", bitwidth); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (channels) { + case 1: + regval |= LPAIF_I2SCTL_SPKMODE_SD0; + regval |= LPAIF_I2SCTL_SPKMONO_MONO; + break; + case 2: + regval |= LPAIF_I2SCTL_SPKMODE_SD0; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 4: + regval |= LPAIF_I2SCTL_SPKMODE_QUAD01; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 6: + regval |= LPAIF_I2SCTL_SPKMODE_6CH; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + case 8: + regval |= LPAIF_I2SCTL_SPKMODE_8CH; + regval |= LPAIF_I2SCTL_SPKMONO_STEREO; + break; + default: + dev_err(dai->dev, "invalid channels given: %u\n", + channels); + return -EINVAL; + } + } else { + switch (channels) { + case 1: + regval |= LPAIF_I2SCTL_MICMODE_SD0; + regval |= LPAIF_I2SCTL_MICMONO_MONO; + break; + case 2: + regval |= LPAIF_I2SCTL_MICMODE_SD0; + regval |= LPAIF_I2SCTL_MICMONO_STEREO; + break; + case 4: + regval |= LPAIF_I2SCTL_MICMODE_QUAD01; + regval |= LPAIF_I2SCTL_MICMONO_STEREO; + break; + case 6: + regval |= LPAIF_I2SCTL_MICMODE_6CH; + regval |= LPAIF_I2SCTL_MICMONO_STEREO; + break; + case 8: + regval |= LPAIF_I2SCTL_MICMODE_8CH; + regval |= LPAIF_I2SCTL_MICMONO_STEREO; + break; + default: + dev_err(dai->dev, "invalid channels given: %u\n", + channels); + return -EINVAL; + } + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id), + regval); + if (ret) { + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret); + return ret; + } + + ret = clk_set_rate(drvdata->mi2s_bit_clk[dai->driver->id], + rate * bitwidth * 2); + if (ret) { + dev_err(dai->dev, "error setting mi2s bitclk to %u: %d\n", + rate * bitwidth * 2, ret); + return ret; + } + + return 0; +} + +static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + unsigned int val, mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val = LPAIF_I2SCTL_SPKEN_ENABLE; + mask = LPAIF_I2SCTL_SPKEN_MASK; + } else { + val = LPAIF_I2SCTL_MICEN_ENABLE; + mask = LPAIF_I2SCTL_MICEN_MASK; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id), + mask, val); + if (ret) + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret); + + return ret; +} + +static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret = -EINVAL; + unsigned int val, mask; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val = LPAIF_I2SCTL_SPKEN_ENABLE; + mask = LPAIF_I2SCTL_SPKEN_MASK; + } else { + val = LPAIF_I2SCTL_MICEN_ENABLE; + mask = LPAIF_I2SCTL_MICEN_MASK; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->variant, + dai->driver->id), + mask, val); + if (ret) + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", + ret); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val = LPAIF_I2SCTL_SPKEN_DISABLE; + mask = LPAIF_I2SCTL_SPKEN_MASK; + } else { + val = LPAIF_I2SCTL_MICEN_DISABLE; + mask = LPAIF_I2SCTL_MICEN_MASK; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->variant, + dai->driver->id), + mask, val); + if (ret) + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", + ret); + break; + } + + return ret; +} + +const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops = { + .set_sysclk = lpass_cpu_daiops_set_sysclk, + .startup = lpass_cpu_daiops_startup, + .shutdown = lpass_cpu_daiops_shutdown, + .hw_params = lpass_cpu_daiops_hw_params, + .prepare = lpass_cpu_daiops_prepare, + .trigger = lpass_cpu_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops); + +int asoc_qcom_lpass_cpu_dai_probe(struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id), 0); + if (ret) + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_probe); + +static const struct snd_soc_component_driver lpass_cpu_comp_driver = { + .name = "lpass-cpu", +}; + +static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + for (i = 0; i < v->i2s_ports; ++i) + if (reg == LPAIF_I2SCTL_REG(v, i)) + return true; + + for (i = 0; i < v->irq_ports; ++i) { + if (reg == LPAIF_IRQEN_REG(v, i)) + return true; + if (reg == LPAIF_IRQCLEAR_REG(v, i)) + return true; + } + + for (i = 0; i < v->rdma_channels; ++i) { + if (reg == LPAIF_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(v, i)) + return true; + if (reg == LPAIF_RDMAPER_REG(v, i)) + return true; + } + + for (i = 0; i < v->wrdma_channels; ++i) { + if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + for (i = 0; i < v->i2s_ports; ++i) + if (reg == LPAIF_I2SCTL_REG(v, i)) + return true; + + for (i = 0; i < v->irq_ports; ++i) { + if (reg == LPAIF_IRQEN_REG(v, i)) + return true; + if (reg == LPAIF_IRQSTAT_REG(v, i)) + return true; + } + + for (i = 0; i < v->rdma_channels; ++i) { + if (reg == LPAIF_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(v, i)) + return true; + if (reg == LPAIF_RDMACURR_REG(v, i)) + return true; + if (reg == LPAIF_RDMAPER_REG(v, i)) + return true; + } + + for (i = 0; i < v->wrdma_channels; ++i) { + if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + for (i = 0; i < v->irq_ports; ++i) + if (reg == LPAIF_IRQSTAT_REG(v, i)) + return true; + + for (i = 0; i < v->rdma_channels; ++i) + if (reg == LPAIF_RDMACURR_REG(v, i)) + return true; + + for (i = 0; i < v->wrdma_channels; ++i) + if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start)) + return true; + + return false; +} + +static struct regmap_config lpass_cpu_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .writeable_reg = lpass_cpu_regmap_writeable, + .readable_reg = lpass_cpu_regmap_readable, + .volatile_reg = lpass_cpu_regmap_volatile, + .cache_type = REGCACHE_FLAT, +}; + +int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) +{ + struct lpass_data *drvdata; + struct device_node *dsp_of_node; + struct resource *res; + struct lpass_variant *variant; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + int ret, i, dai_id; + + dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0); + if (dsp_of_node) { + dev_err(&pdev->dev, "DSP exists and holds audio resources\n"); + return -EBUSY; + } + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return -EINVAL; + + drvdata->variant = (struct lpass_variant *)match->data; + variant = drvdata->variant; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif"); + + drvdata->lpaif = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR((void const __force *)drvdata->lpaif)) { + dev_err(&pdev->dev, "error mapping reg resource: %ld\n", + PTR_ERR((void const __force *)drvdata->lpaif)); + return PTR_ERR((void const __force *)drvdata->lpaif); + } + + lpass_cpu_regmap_config.max_register = LPAIF_WRDMAPER_REG(variant, + variant->wrdma_channels + + variant->wrdma_channel_start); + + drvdata->lpaif_map = devm_regmap_init_mmio(&pdev->dev, drvdata->lpaif, + &lpass_cpu_regmap_config); + if (IS_ERR(drvdata->lpaif_map)) { + dev_err(&pdev->dev, "error initializing regmap: %ld\n", + PTR_ERR(drvdata->lpaif_map)); + return PTR_ERR(drvdata->lpaif_map); + } + + if (variant->init) + variant->init(pdev); + + for (i = 0; i < variant->num_dai; i++) { + dai_id = variant->dai_driver[i].id; + drvdata->mi2s_osr_clk[dai_id] = devm_clk_get(&pdev->dev, + variant->dai_osr_clk_names[i]); + if (IS_ERR(drvdata->mi2s_osr_clk[dai_id])) { + dev_warn(&pdev->dev, + "%s() error getting optional %s: %ld\n", + __func__, + variant->dai_osr_clk_names[i], + PTR_ERR(drvdata->mi2s_osr_clk[dai_id])); + + drvdata->mi2s_osr_clk[dai_id] = NULL; + } + + drvdata->mi2s_bit_clk[dai_id] = devm_clk_get(&pdev->dev, + variant->dai_bit_clk_names[i]); + if (IS_ERR(drvdata->mi2s_bit_clk[dai_id])) { + dev_err(&pdev->dev, + "error getting %s: %ld\n", + variant->dai_bit_clk_names[i], + PTR_ERR(drvdata->mi2s_bit_clk[dai_id])); + return PTR_ERR(drvdata->mi2s_bit_clk[dai_id]); + } + } + + drvdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix-clk"); + if (IS_ERR(drvdata->ahbix_clk)) { + dev_err(&pdev->dev, "error getting ahbix-clk: %ld\n", + PTR_ERR(drvdata->ahbix_clk)); + return PTR_ERR(drvdata->ahbix_clk); + } + + ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY); + if (ret) { + dev_err(&pdev->dev, "error setting rate on ahbix_clk: %d\n", + ret); + return ret; + } + dev_dbg(&pdev->dev, "set ahbix_clk rate to %lu\n", + clk_get_rate(drvdata->ahbix_clk)); + + ret = clk_prepare_enable(drvdata->ahbix_clk); + if (ret) { + dev_err(&pdev->dev, "error enabling ahbix_clk: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &lpass_cpu_comp_driver, + variant->dai_driver, + variant->num_dai); + if (ret) { + dev_err(&pdev->dev, "error registering cpu driver: %d\n", ret); + goto err_clk; + } + + ret = asoc_qcom_lpass_platform_register(pdev); + if (ret) { + dev_err(&pdev->dev, "error registering platform driver: %d\n", + ret); + goto err_clk; + } + + return 0; + +err_clk: + clk_disable_unprepare(drvdata->ahbix_clk); + return ret; +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_probe); + +int asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + if (drvdata->variant->exit) + drvdata->variant->exit(pdev); + + clk_disable_unprepare(drvdata->ahbix_clk); + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_remove); + +MODULE_DESCRIPTION("QTi LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-ipq806x.c b/sound/soc/qcom/lpass-ipq806x.c new file mode 100644 index 000000000..ca1e1f2d2 --- /dev/null +++ b/sound/soc/qcom/lpass-ipq806x.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass-ipq806x.c -- ALSA SoC CPU DAI driver for QTi LPASS + * Splited out the IPQ8064 soc specific from lpass-cpu.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +enum lpaif_i2s_ports { + IPQ806X_LPAIF_I2S_PORT_CODEC_SPK, + IPQ806X_LPAIF_I2S_PORT_CODEC_MIC, + IPQ806X_LPAIF_I2S_PORT_SEC_SPK, + IPQ806X_LPAIF_I2S_PORT_SEC_MIC, + IPQ806X_LPAIF_I2S_PORT_MI2S, +}; + +enum lpaif_dma_channels { + IPQ806X_LPAIF_RDMA_CHAN_MI2S, + IPQ806X_LPAIF_RDMA_CHAN_PCM0, + IPQ806X_LPAIF_RDMA_CHAN_PCM1, +}; + +static struct snd_soc_dai_driver ipq806x_lpass_cpu_dai_driver = { + .id = IPQ806X_LPAIF_I2S_PORT_MI2S, + .playback = { + .stream_name = "lpass-cpu-playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, +}; + +static int ipq806x_lpass_alloc_dma_channel(struct lpass_data *drvdata, int dir) +{ + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + return IPQ806X_LPAIF_RDMA_CHAN_MI2S; + else /* Capture currently not implemented */ + return -EINVAL; +} + +static int ipq806x_lpass_free_dma_channel(struct lpass_data *drvdata, int chan) +{ + return 0; +} + +static struct lpass_variant ipq806x_data = { + .i2sctrl_reg_base = 0x0010, + .i2sctrl_reg_stride = 0x04, + .i2s_ports = 5, + .irq_reg_base = 0x3000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0x6000, + .rdma_reg_stride = 0x1000, + .rdma_channels = 4, + .wrdma_reg_base = 0xB000, + .wrdma_reg_stride = 0x1000, + .wrdma_channel_start = 5, + .wrdma_channels = 4, + .dai_driver = &ipq806x_lpass_cpu_dai_driver, + .num_dai = 1, + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk", + }, + .alloc_dma_channel = ipq806x_lpass_alloc_dma_channel, + .free_dma_channel = ipq806x_lpass_free_dma_channel, +}; + +static const struct of_device_id ipq806x_lpass_cpu_device_id[] = { + { .compatible = "qcom,lpass-cpu", .data = &ipq806x_data }, + {} +}; +MODULE_DEVICE_TABLE(of, ipq806x_lpass_cpu_device_id); + +static struct platform_driver ipq806x_lpass_cpu_platform_driver = { + .driver = { + .name = "lpass-cpu", + .of_match_table = of_match_ptr(ipq806x_lpass_cpu_device_id), + }, + .probe = asoc_qcom_lpass_cpu_platform_probe, + .remove = asoc_qcom_lpass_cpu_platform_remove, +}; +module_platform_driver(ipq806x_lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("QTi LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h new file mode 100644 index 000000000..2240bc68e --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif-reg.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LPASS_LPAIF_REG_H__ +#define __LPASS_LPAIF_REG_H__ + +/* LPAIF I2S */ + +#define LPAIF_I2SCTL_REG_ADDR(v, addr, port) \ + (v->i2sctrl_reg_base + (addr) + v->i2sctrl_reg_stride * (port)) + +#define LPAIF_I2SCTL_REG(v, port) LPAIF_I2SCTL_REG_ADDR(v, 0x0, (port)) +#define LPAIF_I2SCTL_LOOPBACK_MASK 0x8000 +#define LPAIF_I2SCTL_LOOPBACK_SHIFT 15 +#define LPAIF_I2SCTL_LOOPBACK_DISABLE (0 << LPAIF_I2SCTL_LOOPBACK_SHIFT) +#define LPAIF_I2SCTL_LOOPBACK_ENABLE (1 << LPAIF_I2SCTL_LOOPBACK_SHIFT) + +#define LPAIF_I2SCTL_SPKEN_MASK 0x4000 +#define LPAIF_I2SCTL_SPKEN_SHIFT 14 +#define LPAIF_I2SCTL_SPKEN_DISABLE (0 << LPAIF_I2SCTL_SPKEN_SHIFT) +#define LPAIF_I2SCTL_SPKEN_ENABLE (1 << LPAIF_I2SCTL_SPKEN_SHIFT) + +#define LPAIF_I2SCTL_SPKMODE_MASK 0x3C00 +#define LPAIF_I2SCTL_SPKMODE_SHIFT 10 +#define LPAIF_I2SCTL_SPKMODE_NONE (0 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD0 (1 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD1 (2 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD2 (3 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_SD3 (4 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_QUAD01 (5 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_QUAD23 (6 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_6CH (7 << LPAIF_I2SCTL_SPKMODE_SHIFT) +#define LPAIF_I2SCTL_SPKMODE_8CH (8 << LPAIF_I2SCTL_SPKMODE_SHIFT) + +#define LPAIF_I2SCTL_SPKMONO_MASK 0x0200 +#define LPAIF_I2SCTL_SPKMONO_SHIFT 9 +#define LPAIF_I2SCTL_SPKMONO_STEREO (0 << LPAIF_I2SCTL_SPKMONO_SHIFT) +#define LPAIF_I2SCTL_SPKMONO_MONO (1 << LPAIF_I2SCTL_SPKMONO_SHIFT) + +#define LPAIF_I2SCTL_MICEN_MASK GENMASK(8, 8) +#define LPAIF_I2SCTL_MICEN_SHIFT 8 +#define LPAIF_I2SCTL_MICEN_DISABLE (0 << LPAIF_I2SCTL_MICEN_SHIFT) +#define LPAIF_I2SCTL_MICEN_ENABLE (1 << LPAIF_I2SCTL_MICEN_SHIFT) + +#define LPAIF_I2SCTL_MICMODE_MASK GENMASK(7, 4) +#define LPAIF_I2SCTL_MICMODE_SHIFT 4 +#define LPAIF_I2SCTL_MICMODE_NONE (0 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_SD0 (1 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_SD1 (2 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_SD2 (3 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_SD3 (4 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_QUAD01 (5 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_QUAD23 (6 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_6CH (7 << LPAIF_I2SCTL_MICMODE_SHIFT) +#define LPAIF_I2SCTL_MICMODE_8CH (8 << LPAIF_I2SCTL_MICMODE_SHIFT) + +#define LPAIF_I2SCTL_MIMONO_MASK GENMASK(3, 3) +#define LPAIF_I2SCTL_MICMONO_SHIFT 3 +#define LPAIF_I2SCTL_MICMONO_STEREO (0 << LPAIF_I2SCTL_MICMONO_SHIFT) +#define LPAIF_I2SCTL_MICMONO_MONO (1 << LPAIF_I2SCTL_MICMONO_SHIFT) + +#define LPAIF_I2SCTL_WSSRC_MASK 0x0004 +#define LPAIF_I2SCTL_WSSRC_SHIFT 2 +#define LPAIF_I2SCTL_WSSRC_INTERNAL (0 << LPAIF_I2SCTL_WSSRC_SHIFT) +#define LPAIF_I2SCTL_WSSRC_EXTERNAL (1 << LPAIF_I2SCTL_WSSRC_SHIFT) + +#define LPAIF_I2SCTL_BITWIDTH_MASK 0x0003 +#define LPAIF_I2SCTL_BITWIDTH_SHIFT 0 +#define LPAIF_I2SCTL_BITWIDTH_16 (0 << LPAIF_I2SCTL_BITWIDTH_SHIFT) +#define LPAIF_I2SCTL_BITWIDTH_24 (1 << LPAIF_I2SCTL_BITWIDTH_SHIFT) +#define LPAIF_I2SCTL_BITWIDTH_32 (2 << LPAIF_I2SCTL_BITWIDTH_SHIFT) + +/* LPAIF IRQ */ +#define LPAIF_IRQ_REG_ADDR(v, addr, port) \ + (v->irq_reg_base + (addr) + v->irq_reg_stride * (port)) + +#define LPAIF_IRQ_PORT_HOST 0 + +#define LPAIF_IRQEN_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x0, (port)) +#define LPAIF_IRQSTAT_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x4, (port)) +#define LPAIF_IRQCLEAR_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0xC, (port)) + +#define LPAIF_IRQ_BITSTRIDE 3 + +#define LPAIF_IRQ_PER(chan) (1 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_XRUN(chan) (2 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_ERR(chan) (4 << (LPAIF_IRQ_BITSTRIDE * (chan))) + +#define LPAIF_IRQ_ALL(chan) (7 << (LPAIF_IRQ_BITSTRIDE * (chan))) + +/* LPAIF DMA */ + +#define LPAIF_RDMA_REG_ADDR(v, addr, chan) \ + (v->rdma_reg_base + (addr) + v->rdma_reg_stride * (chan)) + +#define LPAIF_RDMACTL_AUDINTF(id) (id << LPAIF_RDMACTL_AUDINTF_SHIFT) + +#define LPAIF_RDMACTL_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x00, (chan)) +#define LPAIF_RDMABASE_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x04, (chan)) +#define LPAIF_RDMABUFF_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x08, (chan)) +#define LPAIF_RDMACURR_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x0C, (chan)) +#define LPAIF_RDMAPER_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x10, (chan)) +#define LPAIF_RDMAPERCNT_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x14, (chan)) + +#define LPAIF_WRDMA_REG_ADDR(v, addr, chan) \ + (v->wrdma_reg_base + (addr) + \ + v->wrdma_reg_stride * (chan - v->wrdma_channel_start)) + +#define LPAIF_WRDMACTL_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x00, (chan)) +#define LPAIF_WRDMABASE_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x04, (chan)) +#define LPAIF_WRDMABUFF_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x08, (chan)) +#define LPAIF_WRDMACURR_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x0C, (chan)) +#define LPAIF_WRDMAPER_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x10, (chan)) +#define LPAIF_WRDMAPERCNT_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x14, (chan)) + +#define __LPAIF_DMA_REG(v, chan, dir, reg) \ + (dir == SNDRV_PCM_STREAM_PLAYBACK) ? \ + LPAIF_RDMA##reg##_REG(v, chan) : \ + LPAIF_WRDMA##reg##_REG(v, chan) + +#define LPAIF_DMACTL_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, CTL) +#define LPAIF_DMABASE_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, BASE) +#define LPAIF_DMABUFF_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, BUFF) +#define LPAIF_DMACURR_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, CURR) +#define LPAIF_DMAPER_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, PER) +#define LPAIF_DMAPERCNT_REG(v, chan, dir) __LPAIF_DMA_REG(v, chan, dir, PERCNT) + +#define LPAIF_DMACTL_BURSTEN_MASK 0x800 +#define LPAIF_DMACTL_BURSTEN_SHIFT 11 +#define LPAIF_DMACTL_BURSTEN_SINGLE (0 << LPAIF_DMACTL_BURSTEN_SHIFT) +#define LPAIF_DMACTL_BURSTEN_INCR4 (1 << LPAIF_DMACTL_BURSTEN_SHIFT) + +#define LPAIF_DMACTL_WPSCNT_MASK 0x700 +#define LPAIF_DMACTL_WPSCNT_SHIFT 8 +#define LPAIF_DMACTL_WPSCNT_ONE (0 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_TWO (1 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_THREE (2 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_FOUR (3 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_SIX (5 << LPAIF_DMACTL_WPSCNT_SHIFT) +#define LPAIF_DMACTL_WPSCNT_EIGHT (7 << LPAIF_DMACTL_WPSCNT_SHIFT) + +#define LPAIF_DMACTL_AUDINTF_MASK 0x0F0 +#define LPAIF_DMACTL_AUDINTF_SHIFT 4 +#define LPAIF_DMACTL_AUDINTF(id) (id << LPAIF_DMACTL_AUDINTF_SHIFT) + +#define LPAIF_DMACTL_FIFOWM_MASK 0x00E +#define LPAIF_DMACTL_FIFOWM_SHIFT 1 +#define LPAIF_DMACTL_FIFOWM_1 (0 << LPAIF_DMACTL_FIFOWM_SHIFT) +#define LPAIF_DMACTL_FIFOWM_2 (1 << LPAIF_DMACTL_FIFOWM_SHIFT) +#define LPAIF_DMACTL_FIFOWM_3 (2 << LPAIF_DMACTL_FIFOWM_SHIFT) +#define LPAIF_DMACTL_FIFOWM_4 (3 << LPAIF_DMACTL_FIFOWM_SHIFT) +#define LPAIF_DMACTL_FIFOWM_5 (4 << LPAIF_DMACTL_FIFOWM_SHIFT) +#define LPAIF_DMACTL_FIFOWM_6 (5 << LPAIF_DMACTL_FIFOWM_SHIFT) +#define LPAIF_DMACTL_FIFOWM_7 (6 << LPAIF_DMACTL_FIFOWM_SHIFT) +#define LPAIF_DMACTL_FIFOWM_8 (7 << LPAIF_DMACTL_FIFOWM_SHIFT) + +#define LPAIF_DMACTL_ENABLE_MASK 0x1 +#define LPAIF_DMACTL_ENABLE_SHIFT 0 +#define LPAIF_DMACTL_ENABLE_OFF (0 << LPAIF_DMACTL_ENABLE_SHIFT) +#define LPAIF_DMACTL_ENABLE_ON (1 << LPAIF_DMACTL_ENABLE_SHIFT) + +#define LPAIF_DMACTL_DYNCLK_MASK BIT(12) +#define LPAIF_DMACTL_DYNCLK_SHIFT 12 +#define LPAIF_DMACTL_DYNCLK_OFF (0 << LPAIF_DMACTL_DYNCLK_SHIFT) +#define LPAIF_DMACTL_DYNCLK_ON (1 << LPAIF_DMACTL_DYNCLK_SHIFT) +#endif /* __LPASS_LPAIF_REG_H__ */ diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c new file mode 100644 index 000000000..1d06e2b7b --- /dev/null +++ b/sound/soc/qcom/lpass-platform.c @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass-platform.c -- ALSA SoC platform driver for QTi LPASS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +#define DRV_NAME "lpass-platform" + +struct lpass_pcm_data { + int dma_ch; + int i2s_port; +}; + +#define LPASS_PLATFORM_BUFFER_SIZE (16 * 1024) +#define LPASS_PLATFORM_PERIODS 2 + +static const struct snd_pcm_hardware lpass_platform_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = LPASS_PLATFORM_BUFFER_SIZE, + .period_bytes_max = LPASS_PLATFORM_BUFFER_SIZE / + LPASS_PLATFORM_PERIODS, + .period_bytes_min = LPASS_PLATFORM_BUFFER_SIZE / + LPASS_PLATFORM_PERIODS, + .periods_min = LPASS_PLATFORM_PERIODS, + .periods_max = LPASS_PLATFORM_PERIODS, + .fifo_size = 0, +}; + +static int lpass_platform_pcmops_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct lpass_variant *v = drvdata->variant; + int ret, dma_ch, dir = substream->stream; + struct lpass_pcm_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->i2s_port = cpu_dai->driver->id; + runtime->private_data = data; + + if (v->alloc_dma_channel) + dma_ch = v->alloc_dma_channel(drvdata, dir); + else + dma_ch = 0; + + if (dma_ch < 0) { + kfree(data); + return dma_ch; + } + + drvdata->substream[dma_ch] = substream; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_DMACTL_REG(v, dma_ch, dir), 0); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + + data->dma_ch = dma_ch; + + snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware); + + runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(data); + dev_err(soc_runtime->dev, "setting constraints failed: %d\n", + ret); + return -EINVAL; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int lpass_platform_pcmops_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct lpass_variant *v = drvdata->variant; + struct lpass_pcm_data *data; + + data = runtime->private_data; + drvdata->substream[data->dma_ch] = NULL; + if (v->free_dma_channel) + v->free_dma_channel(drvdata, data->dma_ch); + + kfree(data); + return 0; +} + +static int lpass_platform_pcmops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int regval; + int ch, dir = substream->stream; + int bitwidth; + int ret, dma_port = pcm_data->i2s_port + v->dmactl_audif_start; + + ch = pcm_data->dma_ch; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(soc_runtime->dev, "invalid bit width given: %d\n", + bitwidth); + return bitwidth; + } + + regval = LPAIF_DMACTL_BURSTEN_INCR4 | + LPAIF_DMACTL_AUDINTF(dma_port) | + LPAIF_DMACTL_FIFOWM_8; + + switch (bitwidth) { + case 16: + switch (channels) { + case 1: + case 2: + regval |= LPAIF_DMACTL_WPSCNT_ONE; + break; + case 4: + regval |= LPAIF_DMACTL_WPSCNT_TWO; + break; + case 6: + regval |= LPAIF_DMACTL_WPSCNT_THREE; + break; + case 8: + regval |= LPAIF_DMACTL_WPSCNT_FOUR; + break; + default: + dev_err(soc_runtime->dev, + "invalid PCM config given: bw=%d, ch=%u\n", + bitwidth, channels); + return -EINVAL; + } + break; + case 24: + case 32: + switch (channels) { + case 1: + regval |= LPAIF_DMACTL_WPSCNT_ONE; + break; + case 2: + regval |= LPAIF_DMACTL_WPSCNT_TWO; + break; + case 4: + regval |= LPAIF_DMACTL_WPSCNT_FOUR; + break; + case 6: + regval |= LPAIF_DMACTL_WPSCNT_SIX; + break; + case 8: + regval |= LPAIF_DMACTL_WPSCNT_EIGHT; + break; + default: + dev_err(soc_runtime->dev, + "invalid PCM config given: bw=%d, ch=%u\n", + bitwidth, channels); + return -EINVAL; + } + break; + default: + dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n", + bitwidth, channels); + return -EINVAL; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_DMACTL_REG(v, ch, dir), regval); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n", + ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + unsigned int reg; + int ret; + + reg = LPAIF_DMACTL_REG(v, pcm_data->dma_ch, substream->stream); + ret = regmap_write(drvdata->lpaif_map, reg, 0); + if (ret) + dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n", + ret); + + return ret; +} + +static int lpass_platform_pcmops_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + int ret, ch, dir = substream->stream; + + ch = pcm_data->dma_ch; + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_DMABASE_REG(v, ch, dir), + runtime->dma_addr); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmabase reg: %d\n", + ret); + return ret; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_DMABUFF_REG(v, ch, dir), + (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmabuff reg: %d\n", + ret); + return ret; + } + + ret = regmap_write(drvdata->lpaif_map, + LPAIF_DMAPER_REG(v, ch, dir), + (snd_pcm_lib_period_bytes(substream) >> 2) - 1); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmaper reg: %d\n", + ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_DMACTL_REG(v, ch, dir), + LPAIF_DMACTL_ENABLE_MASK, LPAIF_DMACTL_ENABLE_ON); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n", + ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + int ret, ch, dir = substream->stream; + + ch = pcm_data->dma_ch; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* clear status before enabling interrupts */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(ch)); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(ch), + LPAIF_IRQ_ALL(ch)); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to irqen reg: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_DMACTL_REG(v, ch, dir), + LPAIF_DMACTL_ENABLE_MASK, + LPAIF_DMACTL_ENABLE_ON); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_DMACTL_REG(v, ch, dir), + LPAIF_DMACTL_ENABLE_MASK, + LPAIF_DMACTL_ENABLE_OFF); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ALL(ch), 0); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to irqen reg: %d\n", ret); + return ret; + } + break; + } + + return 0; +} + +static snd_pcm_uframes_t lpass_platform_pcmops_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + unsigned int base_addr, curr_addr; + int ret, ch, dir = substream->stream; + + ch = pcm_data->dma_ch; + + ret = regmap_read(drvdata->lpaif_map, + LPAIF_DMABASE_REG(v, ch, dir), &base_addr); + if (ret) { + dev_err(soc_runtime->dev, + "error reading from rdmabase reg: %d\n", ret); + return ret; + } + + ret = regmap_read(drvdata->lpaif_map, + LPAIF_DMACURR_REG(v, ch, dir), &curr_addr); + if (ret) { + dev_err(soc_runtime->dev, + "error reading from rdmacurr reg: %d\n", ret); + return ret; + } + + return bytes_to_frames(substream->runtime, curr_addr - base_addr); +} + +static int lpass_platform_pcmops_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static const struct snd_pcm_ops lpass_platform_pcm_ops = { + .open = lpass_platform_pcmops_open, + .close = lpass_platform_pcmops_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = lpass_platform_pcmops_hw_params, + .hw_free = lpass_platform_pcmops_hw_free, + .prepare = lpass_platform_pcmops_prepare, + .trigger = lpass_platform_pcmops_trigger, + .pointer = lpass_platform_pcmops_pointer, + .mmap = lpass_platform_pcmops_mmap, +}; + +static irqreturn_t lpass_dma_interrupt_handler( + struct snd_pcm_substream *substream, + struct lpass_data *drvdata, + int chan, u32 interrupts) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct lpass_variant *v = drvdata->variant; + irqreturn_t ret = IRQ_NONE; + int rv; + + if (interrupts & LPAIF_IRQ_PER(chan)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_PER(chan)); + if (rv) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", rv); + return IRQ_NONE; + } + snd_pcm_period_elapsed(substream); + ret = IRQ_HANDLED; + } + + if (interrupts & LPAIF_IRQ_XRUN(chan)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_XRUN(chan)); + if (rv) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", rv); + return IRQ_NONE; + } + dev_warn(soc_runtime->dev, "xrun warning\n"); + snd_pcm_stop_xrun(substream); + ret = IRQ_HANDLED; + } + + if (interrupts & LPAIF_IRQ_ERR(chan)) { + rv = regmap_write(drvdata->lpaif_map, + LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST), + LPAIF_IRQ_ERR(chan)); + if (rv) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", rv); + return IRQ_NONE; + } + dev_err(soc_runtime->dev, "bus access error\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data) +{ + struct lpass_data *drvdata = data; + struct lpass_variant *v = drvdata->variant; + unsigned int irqs; + int rv, chan; + + rv = regmap_read(drvdata->lpaif_map, + LPAIF_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs); + if (rv) { + pr_err("error reading from irqstat reg: %d\n", rv); + return IRQ_NONE; + } + + /* Handle per channel interrupts */ + for (chan = 0; chan < LPASS_MAX_DMA_CHANNELS; chan++) { + if (irqs & LPAIF_IRQ_ALL(chan) && drvdata->substream[chan]) { + rv = lpass_dma_interrupt_handler( + drvdata->substream[chan], + drvdata, chan, irqs); + if (rv != IRQ_HANDLED) + return rv; + } + } + + return IRQ_HANDLED; +} + +static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_pcm *pcm = soc_runtime->pcm; + struct snd_pcm_substream *psubstream, *csubstream; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(soc_runtime, DRV_NAME); + int ret = -EINVAL; + size_t size = lpass_platform_pcm_hardware.buffer_bytes_max; + + psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (psubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + component->dev, + size, &psubstream->dma_buffer); + if (ret) { + dev_err(soc_runtime->dev, "Cannot allocate buffer(s)\n"); + return ret; + } + } + + csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (csubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + component->dev, + size, &csubstream->dma_buffer); + if (ret) { + dev_err(soc_runtime->dev, "Cannot allocate buffer(s)\n"); + if (psubstream) + snd_dma_free_pages(&psubstream->dma_buffer); + return ret; + } + + } + + return 0; +} + +static void lpass_platform_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static const struct snd_soc_component_driver lpass_component_driver = { + .name = DRV_NAME, + .pcm_new = lpass_platform_pcm_new, + .pcm_free = lpass_platform_pcm_free, + .ops = &lpass_platform_pcm_ops, +}; + +int asoc_qcom_lpass_platform_register(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct lpass_variant *v = drvdata->variant; + int ret; + + drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif"); + if (drvdata->lpaif_irq < 0) { + dev_err(&pdev->dev, "error getting irq handle: %d\n", + drvdata->lpaif_irq); + return -ENODEV; + } + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0); + if (ret) { + dev_err(&pdev->dev, "error writing to irqen reg: %d\n", ret); + return ret; + } + + ret = devm_request_irq(&pdev->dev, drvdata->lpaif_irq, + lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING, + "lpass-irq-lpaif", drvdata); + if (ret) { + dev_err(&pdev->dev, "irq request failed: %d\n", ret); + return ret; + } + + + return devm_snd_soc_register_component(&pdev->dev, + &lpass_component_driver, NULL, 0); +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_platform_register); + +MODULE_DESCRIPTION("QTi LPASS Platform Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h new file mode 100644 index 000000000..b848db2d6 --- /dev/null +++ b/sound/soc/qcom/lpass.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * lpass.h - Definitions for the QTi LPASS + */ + +#ifndef __LPASS_H__ +#define __LPASS_H__ + +#include +#include +#include +#include + +#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 +#define LPASS_MAX_MI2S_PORTS (8) +#define LPASS_MAX_DMA_CHANNELS (8) + +/* Both the CPU DAI and platform drivers will access this data */ +struct lpass_data { + + /* AHB-I/X bus clocks inside the low-power audio subsystem (LPASS) */ + struct clk *ahbix_clk; + + /* MI2S system clock */ + struct clk *mi2s_osr_clk[LPASS_MAX_MI2S_PORTS]; + + /* MI2S bit clock (derived from system clock by a divider */ + struct clk *mi2s_bit_clk[LPASS_MAX_MI2S_PORTS]; + + /* low-power audio interface (LPAIF) registers */ + void __iomem *lpaif; + + /* regmap backed by the low-power audio interface (LPAIF) registers */ + struct regmap *lpaif_map; + + /* interrupts from the low-power audio interface (LPAIF) */ + int lpaif_irq; + + /* SOC specific variations in the LPASS IP integration */ + struct lpass_variant *variant; + + /* bit map to keep track of static channel allocations */ + unsigned long dma_ch_bit_map; + + /* used it for handling interrupt per dma channel */ + struct snd_pcm_substream *substream[LPASS_MAX_DMA_CHANNELS]; + + /* 8016 specific */ + struct clk *pcnoc_mport_clk; + struct clk *pcnoc_sway_clk; + +}; + +/* Vairant data per each SOC */ +struct lpass_variant { + u32 i2sctrl_reg_base; + u32 i2sctrl_reg_stride; + u32 i2s_ports; + u32 irq_reg_base; + u32 irq_reg_stride; + u32 irq_ports; + u32 rdma_reg_base; + u32 rdma_reg_stride; + u32 rdma_channels; + u32 wrdma_reg_base; + u32 wrdma_reg_stride; + u32 wrdma_channels; + + /** + * on SOCs like APQ8016 the channel control bits start + * at different offset to ipq806x + **/ + u32 dmactl_audif_start; + u32 wrdma_channel_start; + /* SOC specific initialization like clocks */ + int (*init)(struct platform_device *pdev); + int (*exit)(struct platform_device *pdev); + int (*alloc_dma_channel)(struct lpass_data *data, int direction); + int (*free_dma_channel)(struct lpass_data *data, int ch); + + /* SOC specific dais */ + struct snd_soc_dai_driver *dai_driver; + int num_dai; + const char * const *dai_osr_clk_names; + const char * const *dai_bit_clk_names; +}; + +/* register the platform driver from the CPU DAI driver */ +int asoc_qcom_lpass_platform_register(struct platform_device *); +int asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev); +int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev); +int asoc_qcom_lpass_cpu_dai_probe(struct snd_soc_dai *dai); +extern const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops; + +#endif /* __LPASS_H__ */ diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile new file mode 100644 index 000000000..c33b3cacb --- /dev/null +++ b/sound/soc/qcom/qdsp6/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o +obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o +obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o +obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o +obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c new file mode 100644 index 000000000..932c3ebfd --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6adm.h" +#include "q6afe.h" +#include "q6core.h" +#include "q6dsp-errno.h" +#include "q6dsp-common.h" + +#define ADM_CMD_DEVICE_OPEN_V5 0x00010326 +#define ADM_CMDRSP_DEVICE_OPEN_V5 0x00010329 +#define ADM_CMD_DEVICE_CLOSE_V5 0x00010327 +#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5 0x00010325 + +#define TIMEOUT_MS 1000 +#define RESET_COPP_ID 99 +#define INVALID_COPP_ID 0xFF +/* Definition for a legacy device session. */ +#define ADM_LEGACY_DEVICE_SESSION 0 +#define ADM_MATRIX_ID_AUDIO_RX 0 +#define ADM_MATRIX_ID_AUDIO_TX 1 + +struct q6copp { + int afe_port; + int copp_idx; + int id; + int topology; + int mode; + int rate; + int bit_width; + int channels; + int app_type; + int acdb_id; + + struct aprv2_ibasic_rsp_result_t result; + struct kref refcount; + wait_queue_head_t wait; + struct list_head node; + struct q6adm *adm; +}; + +struct q6adm { + struct apr_device *apr; + struct device *dev; + struct q6core_svc_api_info ainfo; + unsigned long copp_bitmap[AFE_MAX_PORTS]; + struct list_head copps_list; + spinlock_t copps_list_lock; + struct aprv2_ibasic_rsp_result_t result; + struct mutex lock; + wait_queue_head_t matrix_map_wait; +}; + +struct q6adm_cmd_device_open_v5 { + u16 flags; + u16 mode_of_operation; + u16 endpoint_id_1; + u16 endpoint_id_2; + u32 topology_id; + u16 dev_num_channel; + u16 bit_width; + u32 sample_rate; + u8 dev_channel_mapping[8]; +} __packed; + +struct q6adm_cmd_matrix_map_routings_v5 { + u32 matrix_id; + u32 num_sessions; +} __packed; + +struct q6adm_session_map_node_v5 { + u16 session_id; + u16 num_copps; +} __packed; + +static struct q6copp *q6adm_find_copp(struct q6adm *adm, int port_idx, + int copp_idx) +{ + struct q6copp *c = NULL; + struct q6copp *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&adm->copps_list_lock, flags); + list_for_each_entry(c, &adm->copps_list, node) { + if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) { + ret = c; + kref_get(&c->refcount); + break; + } + } + + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + + return ret; + +} + +static void q6adm_free_copp(struct kref *ref) +{ + struct q6copp *c = container_of(ref, struct q6copp, refcount); + struct q6adm *adm = c->adm; + unsigned long flags; + + spin_lock_irqsave(&adm->copps_list_lock, flags); + clear_bit(c->copp_idx, &adm->copp_bitmap[c->afe_port]); + list_del(&c->node); + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + kfree(c); +} + +static int q6adm_callback(struct apr_device *adev, struct apr_resp_pkt *data) +{ + struct aprv2_ibasic_rsp_result_t *result = data->payload; + int port_idx, copp_idx; + struct apr_hdr *hdr = &data->hdr; + struct q6copp *copp; + struct q6adm *adm = dev_get_drvdata(&adev->dev); + + if (!data->payload_size) + return 0; + + copp_idx = (hdr->token) & 0XFF; + port_idx = ((hdr->token) >> 16) & 0xFF; + if (port_idx < 0 || port_idx >= AFE_MAX_PORTS) { + dev_err(&adev->dev, "Invalid port idx %d token %d\n", + port_idx, hdr->token); + return 0; + } + if (copp_idx < 0 || copp_idx >= MAX_COPPS_PER_PORT) { + dev_err(&adev->dev, "Invalid copp idx %d token %d\n", + copp_idx, hdr->token); + return 0; + } + + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: { + if (result->status != 0) { + dev_err(&adev->dev, "cmd = 0x%x return error = 0x%x\n", + result->opcode, result->status); + } + switch (result->opcode) { + case ADM_CMD_DEVICE_OPEN_V5: + case ADM_CMD_DEVICE_CLOSE_V5: + copp = q6adm_find_copp(adm, port_idx, copp_idx); + if (!copp) + return 0; + + copp->result = *result; + wake_up(&copp->wait); + kref_put(&copp->refcount, q6adm_free_copp); + break; + case ADM_CMD_MATRIX_MAP_ROUTINGS_V5: + adm->result = *result; + wake_up(&adm->matrix_map_wait); + break; + + default: + dev_err(&adev->dev, "Unknown Cmd: 0x%x\n", + result->opcode); + break; + } + return 0; + } + case ADM_CMDRSP_DEVICE_OPEN_V5: { + struct adm_cmd_rsp_device_open_v5 { + u32 status; + u16 copp_id; + u16 reserved; + } __packed * open = data->payload; + + copp = q6adm_find_copp(adm, port_idx, copp_idx); + if (!copp) + return 0; + + if (open->copp_id == INVALID_COPP_ID) { + dev_err(&adev->dev, "Invalid coppid rxed %d\n", + open->copp_id); + copp->result.status = ADSP_EBADPARAM; + wake_up(&copp->wait); + kref_put(&copp->refcount, q6adm_free_copp); + break; + } + copp->result.opcode = hdr->opcode; + copp->id = open->copp_id; + wake_up(&copp->wait); + kref_put(&copp->refcount, q6adm_free_copp); + } + break; + default: + dev_err(&adev->dev, "Unknown cmd:0x%x\n", + hdr->opcode); + break; + } + + return 0; +} + +static struct q6copp *q6adm_alloc_copp(struct q6adm *adm, int port_idx) +{ + struct q6copp *c; + int idx; + + idx = find_first_zero_bit(&adm->copp_bitmap[port_idx], + MAX_COPPS_PER_PORT); + + if (idx > MAX_COPPS_PER_PORT) + return ERR_PTR(-EBUSY); + + c = kzalloc(sizeof(*c), GFP_ATOMIC); + if (!c) + return ERR_PTR(-ENOMEM); + + set_bit(idx, &adm->copp_bitmap[port_idx]); + c->copp_idx = idx; + c->afe_port = port_idx; + c->adm = adm; + + init_waitqueue_head(&c->wait); + + return c; +} + +static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct q6copp *copp, + struct apr_pkt *pkt, uint32_t rsp_opcode) +{ + struct device *dev = adm->dev; + uint32_t opcode = pkt->hdr.opcode; + int ret; + + mutex_lock(&adm->lock); + copp->result.opcode = 0; + copp->result.status = 0; + ret = apr_send_pkt(adm->apr, pkt); + if (ret < 0) { + dev_err(dev, "Failed to send APR packet\n"); + ret = -EINVAL; + goto err; + } + + /* Wait for the callback with copp id */ + if (rsp_opcode) + ret = wait_event_timeout(copp->wait, + (copp->result.opcode == opcode) || + (copp->result.opcode == rsp_opcode), + msecs_to_jiffies(TIMEOUT_MS)); + else + ret = wait_event_timeout(copp->wait, + (copp->result.opcode == opcode), + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) { + dev_err(dev, "ADM copp cmd timedout\n"); + ret = -ETIMEDOUT; + } else if (copp->result.status > 0) { + dev_err(dev, "DSP returned error[%d]\n", + copp->result.status); + ret = -EINVAL; + } + +err: + mutex_unlock(&adm->lock); + return ret; +} + +static int q6adm_device_close(struct q6adm *adm, struct q6copp *copp, + int port_id, int copp_idx) +{ + struct apr_pkt close; + + close.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + close.hdr.pkt_size = sizeof(close); + close.hdr.src_port = port_id; + close.hdr.dest_port = copp->id; + close.hdr.token = port_id << 16 | copp_idx; + close.hdr.opcode = ADM_CMD_DEVICE_CLOSE_V5; + + return q6adm_apr_send_copp_pkt(adm, copp, &close, 0); +} + +static struct q6copp *q6adm_find_matching_copp(struct q6adm *adm, + int port_id, int topology, + int mode, int rate, + int channel_mode, int bit_width, + int app_type) +{ + struct q6copp *c = NULL; + struct q6copp *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&adm->copps_list_lock, flags); + + list_for_each_entry(c, &adm->copps_list, node) { + if ((port_id == c->afe_port) && (topology == c->topology) && + (mode == c->mode) && (rate == c->rate) && + (bit_width == c->bit_width) && (app_type == c->app_type)) { + ret = c; + kref_get(&c->refcount); + } + } + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + + return ret; +} + +static int q6adm_device_open(struct q6adm *adm, struct q6copp *copp, + int port_id, int path, int topology, + int channel_mode, int bit_width, int rate) +{ + struct q6adm_cmd_device_open_v5 *open; + int afe_port = q6afe_get_port_id(port_id); + struct apr_pkt *pkt; + void *p; + int ret, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*open); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + open = p + APR_HDR_SIZE; + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = afe_port; + pkt->hdr.dest_port = afe_port; + pkt->hdr.token = port_id << 16 | copp->copp_idx; + pkt->hdr.opcode = ADM_CMD_DEVICE_OPEN_V5; + open->flags = ADM_LEGACY_DEVICE_SESSION; + open->mode_of_operation = path; + open->endpoint_id_1 = afe_port; + open->topology_id = topology; + open->dev_num_channel = channel_mode & 0x00FF; + open->bit_width = bit_width; + open->sample_rate = rate; + + ret = q6dsp_map_channels(&open->dev_channel_mapping[0], + channel_mode); + if (ret) + goto err; + + ret = q6adm_apr_send_copp_pkt(adm, copp, pkt, + ADM_CMDRSP_DEVICE_OPEN_V5); + +err: + kfree(pkt); + return ret; +} + +/** + * q6adm_open() - open adm and grab a free copp + * + * @dev: Pointer to adm child device. + * @port_id: port id + * @path: playback or capture path. + * @rate: rate at which copp is required. + * @channel_mode: channel mode + * @topology: adm topology id + * @perf_mode: performace mode. + * @bit_width: audio sample bit width + * @app_type: Application type. + * @acdb_id: ACDB id + * + * Return: Will be an negative on error or a valid copp pointer on success. + */ +struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate, + int channel_mode, int topology, int perf_mode, + uint16_t bit_width, int app_type, int acdb_id) +{ + struct q6adm *adm = dev_get_drvdata(dev->parent); + struct q6copp *copp; + unsigned long flags; + int ret = 0; + + if (port_id < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", port_id); + return ERR_PTR(-EINVAL); + } + + copp = q6adm_find_matching_copp(adm, port_id, topology, perf_mode, + rate, channel_mode, bit_width, app_type); + if (copp) { + dev_err(dev, "Found Matching Copp 0x%x\n", copp->copp_idx); + return copp; + } + + spin_lock_irqsave(&adm->copps_list_lock, flags); + copp = q6adm_alloc_copp(adm, port_id); + if (IS_ERR_OR_NULL(copp)) { + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + return ERR_CAST(copp); + } + + list_add_tail(&copp->node, &adm->copps_list); + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + + kref_init(&copp->refcount); + copp->topology = topology; + copp->mode = perf_mode; + copp->rate = rate; + copp->channels = channel_mode; + copp->bit_width = bit_width; + copp->app_type = app_type; + + + ret = q6adm_device_open(adm, copp, port_id, path, topology, + channel_mode, bit_width, rate); + if (ret < 0) { + kref_put(&copp->refcount, q6adm_free_copp); + return ERR_PTR(ret); + } + + return copp; +} +EXPORT_SYMBOL_GPL(q6adm_open); + +/** + * q6adm_get_copp_id() - get copp index + * + * @copp: Pointer to valid copp + * + * Return: Will be an negative on error or a valid copp index on success. + **/ +int q6adm_get_copp_id(struct q6copp *copp) +{ + if (!copp) + return -EINVAL; + + return copp->copp_idx; +} +EXPORT_SYMBOL_GPL(q6adm_get_copp_id); + +/** + * q6adm_matrix_map() - Map asm streams and afe ports using payload + * + * @dev: Pointer to adm child device. + * @path: playback or capture path. + * @payload_map: map between session id and afe ports. + * @perf_mode: Performace mode. + * + * Return: Will be an negative on error or a zero on success. + */ +int q6adm_matrix_map(struct device *dev, int path, + struct route_payload payload_map, int perf_mode) +{ + struct q6adm *adm = dev_get_drvdata(dev->parent); + struct q6adm_cmd_matrix_map_routings_v5 *route; + struct q6adm_session_map_node_v5 *node; + struct apr_pkt *pkt; + uint16_t *copps_list; + int pkt_size, ret, i, copp_idx; + void *matrix_map = NULL; + struct q6copp *copp; + + /* Assumes port_ids have already been validated during adm_open */ + pkt_size = (APR_HDR_SIZE + sizeof(*route) + sizeof(*node) + + (sizeof(uint32_t) * payload_map.num_copps)); + + matrix_map = kzalloc(pkt_size, GFP_KERNEL); + if (!matrix_map) + return -ENOMEM; + + pkt = matrix_map; + route = matrix_map + APR_HDR_SIZE; + node = matrix_map + APR_HDR_SIZE + sizeof(*route); + copps_list = matrix_map + APR_HDR_SIZE + sizeof(*route) + sizeof(*node); + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.token = 0; + pkt->hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS_V5; + route->num_sessions = 1; + + switch (path) { + case ADM_PATH_PLAYBACK: + route->matrix_id = ADM_MATRIX_ID_AUDIO_RX; + break; + case ADM_PATH_LIVE_REC: + route->matrix_id = ADM_MATRIX_ID_AUDIO_TX; + break; + default: + dev_err(dev, "Wrong path set[%d]\n", path); + break; + } + + node->session_id = payload_map.session_id; + node->num_copps = payload_map.num_copps; + + for (i = 0; i < payload_map.num_copps; i++) { + int port_idx = payload_map.port_id[i]; + + if (port_idx < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", + payload_map.port_id[i]); + kfree(pkt); + return -EINVAL; + } + copp_idx = payload_map.copp_idx[i]; + + copp = q6adm_find_copp(adm, port_idx, copp_idx); + if (!copp) { + kfree(pkt); + return -EINVAL; + } + + copps_list[i] = copp->id; + kref_put(&copp->refcount, q6adm_free_copp); + } + + mutex_lock(&adm->lock); + adm->result.status = 0; + adm->result.opcode = 0; + + ret = apr_send_pkt(adm->apr, pkt); + if (ret < 0) { + dev_err(dev, "routing for stream %d failed ret %d\n", + payload_map.session_id, ret); + goto fail_cmd; + } + ret = wait_event_timeout(adm->matrix_map_wait, + adm->result.opcode == pkt->hdr.opcode, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + dev_err(dev, "routing for stream %d failed\n", + payload_map.session_id); + ret = -ETIMEDOUT; + goto fail_cmd; + } else if (adm->result.status > 0) { + dev_err(dev, "DSP returned error[%d]\n", + adm->result.status); + ret = -EINVAL; + goto fail_cmd; + } + +fail_cmd: + mutex_unlock(&adm->lock); + kfree(pkt); + return ret; +} +EXPORT_SYMBOL_GPL(q6adm_matrix_map); + +/** + * q6adm_close() - Close adm copp + * + * @dev: Pointer to adm child device. + * @copp: pointer to previously opened copp + * + * Return: Will be an negative on error or a zero on success. + */ +int q6adm_close(struct device *dev, struct q6copp *copp) +{ + struct q6adm *adm = dev_get_drvdata(dev->parent); + int ret = 0; + + ret = q6adm_device_close(adm, copp, copp->afe_port, copp->copp_idx); + if (ret < 0) { + dev_err(adm->dev, "Failed to close copp %d\n", ret); + return ret; + } + + kref_put(&copp->refcount, q6adm_free_copp); + + return 0; +} +EXPORT_SYMBOL_GPL(q6adm_close); + +static int q6adm_probe(struct apr_device *adev) +{ + struct device *dev = &adev->dev; + struct q6adm *adm; + + adm = devm_kzalloc(&adev->dev, sizeof(*adm), GFP_KERNEL); + if (!adm) + return -ENOMEM; + + adm->apr = adev; + dev_set_drvdata(&adev->dev, adm); + adm->dev = dev; + q6core_get_svc_api_info(adev->svc_id, &adm->ainfo); + mutex_init(&adm->lock); + init_waitqueue_head(&adm->matrix_map_wait); + + INIT_LIST_HEAD(&adm->copps_list); + spin_lock_init(&adm->copps_list_lock); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static int q6adm_remove(struct apr_device *adev) +{ + of_platform_depopulate(&adev->dev); + + return 0; +} + +static const struct of_device_id q6adm_device_id[] = { + { .compatible = "qcom,q6adm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6adm_device_id); + +static struct apr_driver qcom_q6adm_driver = { + .probe = q6adm_probe, + .remove = q6adm_remove, + .callback = q6adm_callback, + .driver = { + .name = "qcom-q6adm", + .of_match_table = of_match_ptr(q6adm_device_id), + }, +}; + +module_apr_driver(qcom_q6adm_driver); +MODULE_DESCRIPTION("Q6 Audio Device Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6adm.h b/sound/soc/qcom/qdsp6/q6adm.h new file mode 100644 index 000000000..4f56999b7 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6_ADM_V2_H__ +#define __Q6_ADM_V2_H__ + +#define ADM_PATH_PLAYBACK 0x1 +#define ADM_PATH_LIVE_REC 0x2 +#define MAX_COPPS_PER_PORT 8 +#define NULL_COPP_TOPOLOGY 0x00010312 + +/* multiple copp per stream. */ +struct route_payload { + int num_copps; + int session_id; + int copp_idx[MAX_COPPS_PER_PORT]; + int port_id[MAX_COPPS_PER_PORT]; +}; + +struct q6copp; +struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate, + int channel_mode, int topology, int perf_mode, + uint16_t bit_width, int app_type, int acdb_id); +int q6adm_close(struct device *dev, struct q6copp *copp); +int q6adm_get_copp_id(struct q6copp *copp); +int q6adm_matrix_map(struct device *dev, int path, + struct route_payload payload_map, int perf_mode); + +#endif /* __Q6_ADM_V2_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c new file mode 100644 index 000000000..1fc1939b9 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -0,0 +1,1456 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6afe.h" + +#define Q6AFE_TDM_PB_DAI(pre, num, did) { \ + .playback = { \ + .stream_name = pre" TDM"#num" Playback", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .ops = &q6tdm_ops, \ + .id = did, \ + .probe = msm_dai_q6_dai_probe, \ + .remove = msm_dai_q6_dai_remove, \ + } + +#define Q6AFE_TDM_CAP_DAI(pre, num, did) { \ + .capture = { \ + .stream_name = pre" TDM"#num" Capture", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .ops = &q6tdm_ops, \ + .id = did, \ + .probe = msm_dai_q6_dai_probe, \ + .remove = msm_dai_q6_dai_remove, \ + } + +struct q6afe_dai_priv_data { + uint32_t sd_line_mask; + uint32_t sync_mode; + uint32_t sync_src; + uint32_t data_out_enable; + uint32_t invert_sync; + uint32_t data_delay; + uint32_t data_align; +}; + +struct q6afe_dai_data { + struct q6afe_port *port[AFE_PORT_MAX]; + struct q6afe_port_config port_config[AFE_PORT_MAX]; + bool is_port_started[AFE_PORT_MAX]; + struct q6afe_dai_priv_data priv[AFE_PORT_MAX]; +}; + +static int q6slim_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_slim_cfg *slim = &dai_data->port_config[dai->id].slim; + + slim->sample_rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_SPECIAL: + slim->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + slim->bit_width = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + slim->bit_width = 32; + break; + default: + pr_err("%s: format %d\n", + __func__, params_format(params)); + return -EINVAL; + } + + return 0; +} + +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int channels = params_channels(params); + struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; + + hdmi->sample_rate = params_rate(params); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + hdmi->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hdmi->bit_width = 24; + break; + } + + /* HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4 */ + switch (channels) { + case 2: + hdmi->channel_allocation = 0; + break; + case 3: + hdmi->channel_allocation = 0x02; + break; + case 4: + hdmi->channel_allocation = 0x06; + break; + case 5: + hdmi->channel_allocation = 0x0A; + break; + case 6: + hdmi->channel_allocation = 0x0B; + break; + case 7: + hdmi->channel_allocation = 0x12; + break; + case 8: + hdmi->channel_allocation = 0x13; + break; + default: + dev_err(dai->dev, "invalid Channels = %u\n", channels); + return -EINVAL; + } + + return 0; +} + +static int q6i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg; + + i2s->sample_rate = params_rate(params); + i2s->bit_width = params_width(params); + i2s->num_channels = params_channels(params); + i2s->sd_line_mask = dai_data->priv[dai->id].sd_line_mask; + + return 0; +} + +static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg; + + i2s->fmt = fmt; + + return 0; +} + +static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm; + unsigned int cap_mask; + int rc = 0; + + /* HW only supports 16 and 32 bit slot width configuration */ + if ((slot_width != 16) && (slot_width != 32)) { + dev_err(dai->dev, "%s: invalid slot_width %d\n", + __func__, slot_width); + return -EINVAL; + } + + /* HW supports 1-32 slots configuration. Typical: 1, 2, 4, 8, 16, 32 */ + switch (slots) { + case 2: + cap_mask = 0x03; + break; + case 4: + cap_mask = 0x0F; + break; + case 8: + cap_mask = 0xFF; + break; + case 16: + cap_mask = 0xFFFF; + break; + default: + dev_err(dai->dev, "%s: invalid slots %d\n", + __func__, slots); + return -EINVAL; + } + + switch (dai->id) { + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + tdm->nslots_per_frame = slots; + tdm->slot_width = slot_width; + /* TDM RX dais ids are even and tx are odd */ + tdm->slot_mask = (dai->id & 0x1 ? tx_mask : rx_mask) & cap_mask; + break; + default: + dev_err(dai->dev, "%s: invalid dai id 0x%x\n", + __func__, dai->id); + return -EINVAL; + } + + return rc; +} + +static int q6tdm_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm; + int rc = 0; + int i = 0; + + switch (dai->id) { + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + if (dai->id & 0x1) { + if (!tx_slot) { + dev_err(dai->dev, "tx slot not found\n"); + return -EINVAL; + } + if (tx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) { + dev_err(dai->dev, "invalid tx num %d\n", + tx_num); + return -EINVAL; + } + + for (i = 0; i < tx_num; i++) + tdm->ch_mapping[i] = tx_slot[i]; + + for (i = tx_num; i < AFE_PORT_MAX_AUDIO_CHAN_CNT; i++) + tdm->ch_mapping[i] = Q6AFE_CMAP_INVALID; + + tdm->num_channels = tx_num; + } else { + /* rx */ + if (!rx_slot) { + dev_err(dai->dev, "rx slot not found\n"); + return -EINVAL; + } + if (rx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) { + dev_err(dai->dev, "invalid rx num %d\n", + rx_num); + return -EINVAL; + } + + for (i = 0; i < rx_num; i++) + tdm->ch_mapping[i] = rx_slot[i]; + + for (i = rx_num; i < AFE_PORT_MAX_AUDIO_CHAN_CNT; i++) + tdm->ch_mapping[i] = Q6AFE_CMAP_INVALID; + + tdm->num_channels = rx_num; + } + + break; + default: + dev_err(dai->dev, "%s: invalid dai id 0x%x\n", + __func__, dai->id); + return -EINVAL; + } + + return rc; +} + +static int q6tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm; + + tdm->bit_width = params_width(params); + tdm->sample_rate = params_rate(params); + tdm->num_channels = params_channels(params); + tdm->data_align_type = dai_data->priv[dai->id].data_align; + tdm->sync_src = dai_data->priv[dai->id].sync_src; + tdm->sync_mode = dai_data->priv[dai->id].sync_mode; + + return 0; +} +static void q6afe_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + if (!dai_data->is_port_started[dai->id]) + return; + + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) + dev_err(dai->dev, "fail to close AFE port (%d)\n", rc); + + dai_data->is_port_started[dai->id] = false; + +} + +static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + if (dai_data->is_port_started[dai->id]) { + /* stop the port and restart with new port config */ + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to close AFE port (%d)\n", rc); + return rc; + } + } + + switch (dai->id) { + case HDMI_RX: + q6afe_hdmi_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].hdmi); + break; + case SLIMBUS_0_RX ... SLIMBUS_6_TX: + q6afe_slim_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].slim); + break; + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + if (rc < 0) { + dev_err(dai->dev, "fail to prepare AFE port %x\n", + dai->id); + return rc; + } + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + q6afe_tdm_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].tdm); + break; + default: + return -EINVAL; + } + + rc = q6afe_port_start(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to start AFE port %x\n", dai->id); + return rc; + } + dai_data->is_port_started[dai->id] = true; + + return 0; +} + +static int q6slim_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_port_config *pcfg = &dai_data->port_config[dai->id]; + int i; + + if (dai->id & 0x1) { + /* TX */ + if (!tx_slot) { + pr_err("%s: tx slot not found\n", __func__); + return -EINVAL; + } + + for (i = 0; i < tx_num; i++) + pcfg->slim.ch_mapping[i] = tx_slot[i]; + + pcfg->slim.num_channels = tx_num; + + + } else { + if (!rx_slot) { + pr_err("%s: rx slot not found\n", __func__); + return -EINVAL; + } + + for (i = 0; i < rx_num; i++) + pcfg->slim.ch_mapping[i] = rx_slot[i]; + + pcfg->slim.num_channels = rx_num; + + } + + return 0; +} + +static int q6afe_mi2s_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_port *port = dai_data->port[dai->id]; + + switch (clk_id) { + case LPAIF_DIG_CLK: + return q6afe_port_set_sysclk(port, clk_id, 0, 5, freq, dir); + case LPAIF_BIT_CLK: + case LPAIF_OSR_CLK: + return q6afe_port_set_sysclk(port, clk_id, + Q6AFE_LPASS_CLK_SRC_INTERNAL, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, + freq, dir); + case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR: + case Q6AFE_LPASS_CLK_ID_MCLK_1 ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1: + return q6afe_port_set_sysclk(port, clk_id, + Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, + freq, dir); + case Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT ... Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT: + return q6afe_port_set_sysclk(port, clk_id, + Q6AFE_LPASS_CLK_ATTRIBUTE_INVERT_COUPLE_NO, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, + freq, dir); + } + + return 0; +} + +static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + {"HDMI Playback", NULL, "HDMI_RX"}, + {"Slimbus1 Playback", NULL, "SLIMBUS_1_RX"}, + {"Slimbus2 Playback", NULL, "SLIMBUS_2_RX"}, + {"Slimbus3 Playback", NULL, "SLIMBUS_3_RX"}, + {"Slimbus4 Playback", NULL, "SLIMBUS_4_RX"}, + {"Slimbus5 Playback", NULL, "SLIMBUS_5_RX"}, + {"Slimbus6 Playback", NULL, "SLIMBUS_6_RX"}, + + {"SLIMBUS_0_TX", NULL, "Slimbus Capture"}, + {"SLIMBUS_1_TX", NULL, "Slimbus1 Capture"}, + {"SLIMBUS_2_TX", NULL, "Slimbus2 Capture"}, + {"SLIMBUS_3_TX", NULL, "Slimbus3 Capture"}, + {"SLIMBUS_4_TX", NULL, "Slimbus4 Capture"}, + {"SLIMBUS_5_TX", NULL, "Slimbus5 Capture"}, + {"SLIMBUS_6_TX", NULL, "Slimbus6 Capture"}, + + {"Primary MI2S Playback", NULL, "PRI_MI2S_RX"}, + {"Secondary MI2S Playback", NULL, "SEC_MI2S_RX"}, + {"Tertiary MI2S Playback", NULL, "TERT_MI2S_RX"}, + {"Quaternary MI2S Playback", NULL, "QUAT_MI2S_RX"}, + + {"Primary TDM0 Playback", NULL, "PRIMARY_TDM_RX_0"}, + {"Primary TDM1 Playback", NULL, "PRIMARY_TDM_RX_1"}, + {"Primary TDM2 Playback", NULL, "PRIMARY_TDM_RX_2"}, + {"Primary TDM3 Playback", NULL, "PRIMARY_TDM_RX_3"}, + {"Primary TDM4 Playback", NULL, "PRIMARY_TDM_RX_4"}, + {"Primary TDM5 Playback", NULL, "PRIMARY_TDM_RX_5"}, + {"Primary TDM6 Playback", NULL, "PRIMARY_TDM_RX_6"}, + {"Primary TDM7 Playback", NULL, "PRIMARY_TDM_RX_7"}, + + {"Secondary TDM0 Playback", NULL, "SEC_TDM_RX_0"}, + {"Secondary TDM1 Playback", NULL, "SEC_TDM_RX_1"}, + {"Secondary TDM2 Playback", NULL, "SEC_TDM_RX_2"}, + {"Secondary TDM3 Playback", NULL, "SEC_TDM_RX_3"}, + {"Secondary TDM4 Playback", NULL, "SEC_TDM_RX_4"}, + {"Secondary TDM5 Playback", NULL, "SEC_TDM_RX_5"}, + {"Secondary TDM6 Playback", NULL, "SEC_TDM_RX_6"}, + {"Secondary TDM7 Playback", NULL, "SEC_TDM_RX_7"}, + + {"Tertiary TDM0 Playback", NULL, "TERT_TDM_RX_0"}, + {"Tertiary TDM1 Playback", NULL, "TERT_TDM_RX_1"}, + {"Tertiary TDM2 Playback", NULL, "TERT_TDM_RX_2"}, + {"Tertiary TDM3 Playback", NULL, "TERT_TDM_RX_3"}, + {"Tertiary TDM4 Playback", NULL, "TERT_TDM_RX_4"}, + {"Tertiary TDM5 Playback", NULL, "TERT_TDM_RX_5"}, + {"Tertiary TDM6 Playback", NULL, "TERT_TDM_RX_6"}, + {"Tertiary TDM7 Playback", NULL, "TERT_TDM_RX_7"}, + + {"Quaternary TDM0 Playback", NULL, "QUAT_TDM_RX_0"}, + {"Quaternary TDM1 Playback", NULL, "QUAT_TDM_RX_1"}, + {"Quaternary TDM2 Playback", NULL, "QUAT_TDM_RX_2"}, + {"Quaternary TDM3 Playback", NULL, "QUAT_TDM_RX_3"}, + {"Quaternary TDM4 Playback", NULL, "QUAT_TDM_RX_4"}, + {"Quaternary TDM5 Playback", NULL, "QUAT_TDM_RX_5"}, + {"Quaternary TDM6 Playback", NULL, "QUAT_TDM_RX_6"}, + {"Quaternary TDM7 Playback", NULL, "QUAT_TDM_RX_7"}, + + {"Quinary TDM0 Playback", NULL, "QUIN_TDM_RX_0"}, + {"Quinary TDM1 Playback", NULL, "QUIN_TDM_RX_1"}, + {"Quinary TDM2 Playback", NULL, "QUIN_TDM_RX_2"}, + {"Quinary TDM3 Playback", NULL, "QUIN_TDM_RX_3"}, + {"Quinary TDM4 Playback", NULL, "QUIN_TDM_RX_4"}, + {"Quinary TDM5 Playback", NULL, "QUIN_TDM_RX_5"}, + {"Quinary TDM6 Playback", NULL, "QUIN_TDM_RX_6"}, + {"Quinary TDM7 Playback", NULL, "QUIN_TDM_RX_7"}, + + {"PRIMARY_TDM_TX_0", NULL, "Primary TDM0 Capture"}, + {"PRIMARY_TDM_TX_1", NULL, "Primary TDM1 Capture"}, + {"PRIMARY_TDM_TX_2", NULL, "Primary TDM2 Capture"}, + {"PRIMARY_TDM_TX_3", NULL, "Primary TDM3 Capture"}, + {"PRIMARY_TDM_TX_4", NULL, "Primary TDM4 Capture"}, + {"PRIMARY_TDM_TX_5", NULL, "Primary TDM5 Capture"}, + {"PRIMARY_TDM_TX_6", NULL, "Primary TDM6 Capture"}, + {"PRIMARY_TDM_TX_7", NULL, "Primary TDM7 Capture"}, + + {"SEC_TDM_TX_0", NULL, "Secondary TDM0 Capture"}, + {"SEC_TDM_TX_1", NULL, "Secondary TDM1 Capture"}, + {"SEC_TDM_TX_2", NULL, "Secondary TDM2 Capture"}, + {"SEC_TDM_TX_3", NULL, "Secondary TDM3 Capture"}, + {"SEC_TDM_TX_4", NULL, "Secondary TDM4 Capture"}, + {"SEC_TDM_TX_5", NULL, "Secondary TDM5 Capture"}, + {"SEC_TDM_TX_6", NULL, "Secondary TDM6 Capture"}, + {"SEC_TDM_TX_7", NULL, "Secondary TDM7 Capture"}, + + {"TERT_TDM_TX_0", NULL, "Tertiary TDM0 Capture"}, + {"TERT_TDM_TX_1", NULL, "Tertiary TDM1 Capture"}, + {"TERT_TDM_TX_2", NULL, "Tertiary TDM2 Capture"}, + {"TERT_TDM_TX_3", NULL, "Tertiary TDM3 Capture"}, + {"TERT_TDM_TX_4", NULL, "Tertiary TDM4 Capture"}, + {"TERT_TDM_TX_5", NULL, "Tertiary TDM5 Capture"}, + {"TERT_TDM_TX_6", NULL, "Tertiary TDM6 Capture"}, + {"TERT_TDM_TX_7", NULL, "Tertiary TDM7 Capture"}, + + {"QUAT_TDM_TX_0", NULL, "Quaternary TDM0 Capture"}, + {"QUAT_TDM_TX_1", NULL, "Quaternary TDM1 Capture"}, + {"QUAT_TDM_TX_2", NULL, "Quaternary TDM2 Capture"}, + {"QUAT_TDM_TX_3", NULL, "Quaternary TDM3 Capture"}, + {"QUAT_TDM_TX_4", NULL, "Quaternary TDM4 Capture"}, + {"QUAT_TDM_TX_5", NULL, "Quaternary TDM5 Capture"}, + {"QUAT_TDM_TX_6", NULL, "Quaternary TDM6 Capture"}, + {"QUAT_TDM_TX_7", NULL, "Quaternary TDM7 Capture"}, + + {"QUIN_TDM_TX_0", NULL, "Quinary TDM0 Capture"}, + {"QUIN_TDM_TX_1", NULL, "Quinary TDM1 Capture"}, + {"QUIN_TDM_TX_2", NULL, "Quinary TDM2 Capture"}, + {"QUIN_TDM_TX_3", NULL, "Quinary TDM3 Capture"}, + {"QUIN_TDM_TX_4", NULL, "Quinary TDM4 Capture"}, + {"QUIN_TDM_TX_5", NULL, "Quinary TDM5 Capture"}, + {"QUIN_TDM_TX_6", NULL, "Quinary TDM6 Capture"}, + {"QUIN_TDM_TX_7", NULL, "Quinary TDM7 Capture"}, + + {"TERT_MI2S_TX", NULL, "Tertiary MI2S Capture"}, + {"PRI_MI2S_TX", NULL, "Primary MI2S Capture"}, + {"SEC_MI2S_TX", NULL, "Secondary MI2S Capture"}, + {"QUAT_MI2S_TX", NULL, "Quaternary MI2S Capture"}, +}; + +static struct snd_soc_dai_ops q6hdmi_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6hdmi_hw_params, + .shutdown = q6afe_dai_shutdown, +}; + +static struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6i2s_hw_params, + .set_fmt = q6i2s_set_fmt, + .shutdown = q6afe_dai_shutdown, + .set_sysclk = q6afe_mi2s_set_sysclk, +}; + +static struct snd_soc_dai_ops q6slim_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6slim_hw_params, + .shutdown = q6afe_dai_shutdown, + .set_channel_map = q6slim_set_channel_map, +}; + +static struct snd_soc_dai_ops q6tdm_ops = { + .prepare = q6afe_dai_prepare, + .shutdown = q6afe_dai_shutdown, + .set_sysclk = q6afe_mi2s_set_sysclk, + .set_tdm_slot = q6tdm_set_tdm_slot, + .set_channel_map = q6tdm_set_channel_map, + .hw_params = q6tdm_hw_params, +}; + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_port *port; + + port = q6afe_port_get_from_id(dai->dev, dai->id); + if (IS_ERR(port)) { + dev_err(dai->dev, "Unable to get afe port\n"); + return -EINVAL; + } + dai_data->port[dai->id] = port; + + return 0; +} + +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + + q6afe_port_put(dai_data->port[dai->id]); + dai_data->port[dai->id] = NULL; + + return 0; +} + +static struct snd_soc_dai_driver q6afe_dais[] = { + { + .playback = { + .stream_name = "HDMI Playback", + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 8, + .rate_max = 192000, + .rate_min = 48000, + }, + .ops = &q6hdmi_ops, + .id = HDMI_RX, + .name = "HDMI", + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .name = "SLIMBUS_0_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_0_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .playback = { + .stream_name = "Slimbus Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .name = "SLIMBUS_0_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_0_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus1 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_1_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_1_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .name = "SLIMBUS_1_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_1_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus1 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus2 Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_2_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_2_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_2_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_2_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus2 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus3 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_3_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_3_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_3_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_3_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus3 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus4 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_4_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_4_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_4_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_4_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus4 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus5 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_5_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_5_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_5_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_5_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus5 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus6 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .ops = &q6slim_ops, + .name = "SLIMBUS_6_RX", + .id = SLIMBUS_6_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_6_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_6_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus6 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Primary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = PRIMARY_MI2S_RX, + .name = "PRI_MI2S_RX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Primary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = PRIMARY_MI2S_TX, + .name = "PRI_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Secondary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "SEC_MI2S_RX", + .id = SECONDARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Secondary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = SECONDARY_MI2S_TX, + .name = "SEC_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Tertiary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "TERT_MI2S_RX", + .id = TERTIARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Tertiary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = TERTIARY_MI2S_TX, + .name = "TERT_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Quaternary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "QUAT_MI2S_RX", + .id = QUATERNARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Quaternary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = QUATERNARY_MI2S_TX, + .name = "QUAT_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Primary", 3, PRIMARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Primary", 4, PRIMARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Primary", 5, PRIMARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Primary", 6, PRIMARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Primary", 7, PRIMARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Primary", 0, PRIMARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Primary", 1, PRIMARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Primary", 2, PRIMARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Primary", 3, PRIMARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Primary", 4, PRIMARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Primary", 5, PRIMARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Primary", 6, PRIMARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Primary", 7, PRIMARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Secondary", 0, SECONDARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Secondary", 1, SECONDARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Secondary", 2, SECONDARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Secondary", 3, SECONDARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Secondary", 4, SECONDARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Secondary", 5, SECONDARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Secondary", 6, SECONDARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Secondary", 7, SECONDARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Secondary", 0, SECONDARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Secondary", 1, SECONDARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Secondary", 2, SECONDARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Secondary", 3, SECONDARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Secondary", 4, SECONDARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Secondary", 5, SECONDARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Secondary", 6, SECONDARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Secondary", 7, SECONDARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Tertiary", 0, TERTIARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Tertiary", 1, TERTIARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Tertiary", 2, TERTIARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Tertiary", 3, TERTIARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Tertiary", 4, TERTIARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Tertiary", 5, TERTIARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Tertiary", 6, TERTIARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Tertiary", 7, TERTIARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Tertiary", 0, TERTIARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Tertiary", 1, TERTIARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Tertiary", 2, TERTIARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Tertiary", 3, TERTIARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Tertiary", 4, TERTIARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Tertiary", 5, TERTIARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Tertiary", 6, TERTIARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Tertiary", 7, TERTIARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Quaternary", 0, QUATERNARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Quaternary", 1, QUATERNARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Quaternary", 2, QUATERNARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Quaternary", 3, QUATERNARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Quaternary", 4, QUATERNARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Quaternary", 5, QUATERNARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Quaternary", 6, QUATERNARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Quaternary", 7, QUATERNARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Quaternary", 0, QUATERNARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Quaternary", 1, QUATERNARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Quaternary", 2, QUATERNARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Quaternary", 3, QUATERNARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Quaternary", 4, QUATERNARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Quaternary", 5, QUATERNARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Quaternary", 6, QUATERNARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Quaternary", 7, QUATERNARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Quinary", 0, QUINARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Quinary", 1, QUINARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Quinary", 2, QUINARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Quinary", 3, QUINARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Quinary", 4, QUINARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Quinary", 5, QUINARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Quinary", 6, QUINARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Quinary", 7, QUINARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Quinary", 0, QUINARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Quinary", 1, QUINARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Quinary", 2, QUINARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Quinary", 3, QUINARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Quinary", 4, QUINARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Quinary", 5, QUINARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Quinary", 6, QUINARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Quinary", 7, QUINARY_TDM_TX_7), +}; + +static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, + struct of_phandle_args *args, + const char **dai_name) +{ + int id = args->args[0]; + int ret = -EINVAL; + int i; + + for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) { + if (q6afe_dais[i].id == id) { + *dai_name = q6afe_dais[i].name; + ret = 0; + break; + } + } + + return ret; +} + +static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + SND_SOC_DAPM_AIF_IN("HDMI_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_0_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_1_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_2_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_3_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_4_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_5_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_6_RX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_TX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_TX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_2_TX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_3_TX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_TX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_5_TX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_6_TX", NULL, 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_MI2S_RX", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_MI2S_TX", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_MI2S_RX", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_MI2S_TX", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_MI2S_RX", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_MI2S_TX", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_MI2S_RX_SD1", + "Secondary MI2S Playback SD1", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRI_MI2S_RX", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRI_MI2S_TX", NULL, + 0, 0, 0, 0), + + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_7", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_7", NULL, + 0, 0, 0, 0), + + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_7", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_7", NULL, + 0, 0, 0, 0), + + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_7", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_7", NULL, + 0, 0, 0, 0), + + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_7", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_7", NULL, + 0, 0, 0, 0), + + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_7", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_0", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_1", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_2", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_3", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_4", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_5", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_6", NULL, + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_7", NULL, + 0, 0, 0, 0), +}; + +static const struct snd_soc_component_driver q6afe_dai_component = { + .name = "q6afe-dai-component", + .dapm_widgets = q6afe_dai_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets), + .dapm_routes = q6afe_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes), + .of_xlate_dai_name = q6afe_of_xlate_dai_name, + +}; + +static void of_q6afe_parse_dai_data(struct device *dev, + struct q6afe_dai_data *data) +{ + struct device_node *node; + int ret; + + for_each_child_of_node(dev->of_node, node) { + unsigned int lines[Q6AFE_MAX_MI2S_LINES]; + struct q6afe_dai_priv_data *priv; + int id, i, num_lines; + + ret = of_property_read_u32(node, "reg", &id); + if (ret || id < 0 || id >= AFE_PORT_MAX) { + dev_err(dev, "valid dai id not found:%d\n", ret); + continue; + } + + switch (id) { + /* MI2S specific properties */ + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + priv = &data->priv[id]; + ret = of_property_read_variable_u32_array(node, + "qcom,sd-lines", + lines, 0, + Q6AFE_MAX_MI2S_LINES); + if (ret < 0) + num_lines = 0; + else + num_lines = ret; + + priv->sd_line_mask = 0; + + for (i = 0; i < num_lines; i++) + priv->sd_line_mask |= BIT(lines[i]); + + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + priv = &data->priv[id]; + ret = of_property_read_u32(node, "qcom,tdm-sync-mode", + &priv->sync_mode); + if (ret) { + dev_err(dev, "No Sync mode from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-sync-src", + &priv->sync_src); + if (ret) { + dev_err(dev, "No Sync Src from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-data-out", + &priv->data_out_enable); + if (ret) { + dev_err(dev, "No Data out enable from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-invert-sync", + &priv->invert_sync); + if (ret) { + dev_err(dev, "No Invert sync from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-data-delay", + &priv->data_delay); + if (ret) { + dev_err(dev, "No Data Delay from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-data-align", + &priv->data_align); + if (ret) { + dev_err(dev, "No Data align from DT\n"); + break; + } + break; + default: + break; + } + } +} + +static int q6afe_dai_dev_probe(struct platform_device *pdev) +{ + struct q6afe_dai_data *dai_data; + struct device *dev = &pdev->dev; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); + + of_q6afe_parse_dai_data(dev, dai_data); + + return devm_snd_soc_register_component(dev, &q6afe_dai_component, + q6afe_dais, ARRAY_SIZE(q6afe_dais)); +} + +static const struct of_device_id q6afe_dai_device_id[] = { + { .compatible = "qcom,q6afe-dais" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6afe_dai_device_id); + +static struct platform_driver q6afe_dai_platform_driver = { + .driver = { + .name = "q6afe-dai", + .of_match_table = of_match_ptr(q6afe_dai_device_id), + }, + .probe = q6afe_dai_dev_probe, +}; +module_platform_driver(q6afe_dai_platform_driver); + +MODULE_DESCRIPTION("Q6 Audio Fronend dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c new file mode 100644 index 000000000..829b5e987 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -0,0 +1,1504 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6dsp-errno.h" +#include "q6core.h" +#include "q6afe.h" + +/* AFE CMDs */ +#define AFE_PORT_CMD_DEVICE_START 0x000100E5 +#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6 +#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF +#define AFE_SVC_CMD_SET_PARAM 0x000100f3 +#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106 +#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210 +#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C +#define AFE_MODULE_TDM 0x0001028A + +#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235 + +#define AFE_PARAM_ID_LPAIF_CLK_CONFIG 0x00010238 +#define AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG 0x00010239 + +#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212 +#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D +#define AFE_PARAM_ID_TDM_CONFIG 0x0001029D +#define AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG 0x00010297 + +/* I2S config specific */ +#define AFE_API_VERSION_I2S_CONFIG 0x1 +#define AFE_PORT_I2S_SD0 0x1 +#define AFE_PORT_I2S_SD1 0x2 +#define AFE_PORT_I2S_SD2 0x3 +#define AFE_PORT_I2S_SD3 0x4 +#define AFE_PORT_I2S_SD0_MASK BIT(0x0) +#define AFE_PORT_I2S_SD1_MASK BIT(0x1) +#define AFE_PORT_I2S_SD2_MASK BIT(0x2) +#define AFE_PORT_I2S_SD3_MASK BIT(0x3) +#define AFE_PORT_I2S_SD0_1_MASK GENMASK(1, 0) +#define AFE_PORT_I2S_SD2_3_MASK GENMASK(3, 2) +#define AFE_PORT_I2S_SD0_1_2_MASK GENMASK(2, 0) +#define AFE_PORT_I2S_SD0_1_2_3_MASK GENMASK(3, 0) +#define AFE_PORT_I2S_QUAD01 0x5 +#define AFE_PORT_I2S_QUAD23 0x6 +#define AFE_PORT_I2S_6CHS 0x7 +#define AFE_PORT_I2S_8CHS 0x8 +#define AFE_PORT_I2S_MONO 0x0 +#define AFE_PORT_I2S_STEREO 0x1 +#define AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL 0x0 +#define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1 +#define AFE_LINEAR_PCM_DATA 0x0 + + +/* Port IDs */ +#define AFE_API_VERSION_HDMI_CONFIG 0x1 +#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E + +#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1 +/* Clock set API version */ +#define AFE_API_VERSION_CLOCK_SET 1 +#define Q6AFE_LPASS_CLK_CONFIG_API_VERSION 0x1 +#define AFE_MODULE_CLOCK_SET 0x0001028F +#define AFE_PARAM_ID_CLOCK_SET 0x00010290 + +/* SLIMbus Rx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000 +/* SLIMbus Tx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001 +/* SLIMbus Rx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002 +/* SLIMbus Tx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003 +/* SLIMbus Rx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004 +/* SLIMbus Tx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005 +/* SLIMbus Rx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006 +/* SLIMbus Tx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007 +/* SLIMbus Rx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008 +/* SLIMbus Tx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009 +/* SLIMbus Rx port on channel 5. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX 0x400a +/* SLIMbus Tx port on channel 5. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX 0x400b +/* SLIMbus Rx port on channel 6. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX 0x400c +/* SLIMbus Tx port on channel 6. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX 0x400d +#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000 +#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001 +#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002 +#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003 +#define AFE_PORT_ID_TERTIARY_MI2S_RX 0x1004 +#define AFE_PORT_ID_TERTIARY_MI2S_TX 0x1005 +#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 +#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007 + +/* Start of the range of port IDs for TDM devices. */ +#define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 + +/* End of the range of port IDs for TDM devices. */ +#define AFE_PORT_ID_TDM_PORT_RANGE_END \ + (AFE_PORT_ID_TDM_PORT_RANGE_START+0x50-1) + +/* Size of the range of port IDs for TDM ports. */ +#define AFE_PORT_ID_TDM_PORT_RANGE_SIZE \ + (AFE_PORT_ID_TDM_PORT_RANGE_END - \ + AFE_PORT_ID_TDM_PORT_RANGE_START+1) + +#define AFE_PORT_ID_PRIMARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x00) +#define AFE_PORT_ID_PRIMARY_TDM_RX_1 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x02) +#define AFE_PORT_ID_PRIMARY_TDM_RX_2 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x04) +#define AFE_PORT_ID_PRIMARY_TDM_RX_3 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x06) +#define AFE_PORT_ID_PRIMARY_TDM_RX_4 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x08) +#define AFE_PORT_ID_PRIMARY_TDM_RX_5 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_PRIMARY_TDM_RX_6 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_PRIMARY_TDM_RX_7 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_PRIMARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x01) +#define AFE_PORT_ID_PRIMARY_TDM_TX_1 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x02) +#define AFE_PORT_ID_PRIMARY_TDM_TX_2 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x04) +#define AFE_PORT_ID_PRIMARY_TDM_TX_3 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x06) +#define AFE_PORT_ID_PRIMARY_TDM_TX_4 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x08) +#define AFE_PORT_ID_PRIMARY_TDM_TX_5 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_PRIMARY_TDM_TX_6 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_PRIMARY_TDM_TX_7 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_SECONDARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x10) +#define AFE_PORT_ID_SECONDARY_TDM_RX_1 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x02) +#define AFE_PORT_ID_SECONDARY_TDM_RX_2 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x04) +#define AFE_PORT_ID_SECONDARY_TDM_RX_3 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x06) +#define AFE_PORT_ID_SECONDARY_TDM_RX_4 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x08) +#define AFE_PORT_ID_SECONDARY_TDM_RX_5 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_SECONDARY_TDM_RX_6 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_SECONDARY_TDM_RX_7 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_SECONDARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x11) +#define AFE_PORT_ID_SECONDARY_TDM_TX_1 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x02) +#define AFE_PORT_ID_SECONDARY_TDM_TX_2 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x04) +#define AFE_PORT_ID_SECONDARY_TDM_TX_3 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x06) +#define AFE_PORT_ID_SECONDARY_TDM_TX_4 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x08) +#define AFE_PORT_ID_SECONDARY_TDM_TX_5 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_SECONDARY_TDM_TX_6 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_SECONDARY_TDM_TX_7 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_TERTIARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x20) +#define AFE_PORT_ID_TERTIARY_TDM_RX_1 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x02) +#define AFE_PORT_ID_TERTIARY_TDM_RX_2 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x04) +#define AFE_PORT_ID_TERTIARY_TDM_RX_3 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x06) +#define AFE_PORT_ID_TERTIARY_TDM_RX_4 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x08) +#define AFE_PORT_ID_TERTIARY_TDM_RX_5 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_TERTIARY_TDM_RX_6 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_TERTIARY_TDM_RX_7 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_TERTIARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x21) +#define AFE_PORT_ID_TERTIARY_TDM_TX_1 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x02) +#define AFE_PORT_ID_TERTIARY_TDM_TX_2 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x04) +#define AFE_PORT_ID_TERTIARY_TDM_TX_3 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x06) +#define AFE_PORT_ID_TERTIARY_TDM_TX_4 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x08) +#define AFE_PORT_ID_TERTIARY_TDM_TX_5 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_TERTIARY_TDM_TX_6 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_TERTIARY_TDM_TX_7 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_QUATERNARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x30) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_1 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x02) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_2 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x04) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_3 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x06) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_4 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x08) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_5 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_6 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_7 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_QUATERNARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x31) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_1 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x02) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_2 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x04) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_3 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x06) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_4 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x08) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_5 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_6 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_7 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_QUINARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x40) +#define AFE_PORT_ID_QUINARY_TDM_RX_1 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x02) +#define AFE_PORT_ID_QUINARY_TDM_RX_2 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x04) +#define AFE_PORT_ID_QUINARY_TDM_RX_3 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x06) +#define AFE_PORT_ID_QUINARY_TDM_RX_4 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x08) +#define AFE_PORT_ID_QUINARY_TDM_RX_5 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_QUINARY_TDM_RX_6 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_QUINARY_TDM_RX_7 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_QUINARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x41) +#define AFE_PORT_ID_QUINARY_TDM_TX_1 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x02) +#define AFE_PORT_ID_QUINARY_TDM_TX_2 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x04) +#define AFE_PORT_ID_QUINARY_TDM_TX_3 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x06) +#define AFE_PORT_ID_QUINARY_TDM_TX_4 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x08) +#define AFE_PORT_ID_QUINARY_TDM_TX_5 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_QUINARY_TDM_TX_6 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_QUINARY_TDM_TX_7 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x0E) + +#define Q6AFE_LPASS_MODE_CLK1_VALID 1 +#define Q6AFE_LPASS_MODE_CLK2_VALID 2 +#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1 +#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0 +#define AFE_API_VERSION_TDM_CONFIG 1 +#define AFE_API_VERSION_SLOT_MAPPING_CONFIG 1 + +#define TIMEOUT_MS 1000 +#define AFE_CMD_RESP_AVAIL 0 +#define AFE_CMD_RESP_NONE 1 + +struct q6afe { + struct apr_device *apr; + struct device *dev; + struct q6core_svc_api_info ainfo; + struct mutex lock; + struct list_head port_list; + spinlock_t port_list_lock; +}; + +struct afe_port_cmd_device_start { + u16 port_id; + u16 reserved; +} __packed; + +struct afe_port_cmd_device_stop { + u16 port_id; + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0.*/ +} __packed; + +struct afe_port_param_data_v2 { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; +} __packed; + +struct afe_svc_cmd_set_param { + uint32_t payload_size; + uint32_t payload_address_lsw; + uint32_t payload_address_msw; + uint32_t mem_map_handle; +} __packed; + +struct afe_port_cmd_set_param_v2 { + u16 port_id; + u16 payload_size; + u32 payload_address_lsw; + u32 payload_address_msw; + u32 mem_map_handle; +} __packed; + +struct afe_param_id_hdmi_multi_chan_audio_cfg { + u32 hdmi_cfg_minor_version; + u16 datatype; + u16 channel_allocation; + u32 sample_rate; + u16 bit_width; + u16 reserved; +} __packed; + +struct afe_param_id_slimbus_cfg { + u32 sb_cfg_minor_version; +/* Minor version used for tracking the version of the SLIMBUS + * configuration interface. + * Supported values: #AFE_API_VERSION_SLIMBUS_CONFIG + */ + + u16 slimbus_dev_id; +/* SLIMbus hardware device ID, which is required to handle + * multiple SLIMbus hardware blocks. + * Supported values: - #AFE_SLIMBUS_DEVICE_1 - #AFE_SLIMBUS_DEVICE_2 + */ + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + u16 data_format; +/* Data format supported by the SLIMbus hardware. The default is + * 0 (#AFE_SB_DATA_FORMAT_NOT_INDICATED), which indicates the + * hardware does not perform any format conversions before the data + * transfer. + */ + u16 num_channels; +/* Number of channels. + * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT + */ + u8 shared_ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; +/* Mapping of shared channel IDs (128 to 255) to which the + * master port is to be connected. + * Shared_channel_mapping[i] represents the shared channel assigned + * for audio channel i in multichannel audio data. + */ + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - #AFE_PORT_SAMPLE_RATE_192K + */ +} __packed; + +struct afe_clk_cfg { + u32 i2s_cfg_minor_version; + u32 clk_val1; + u32 clk_val2; + u16 clk_src; + u16 clk_root; + u16 clk_set_mode; + u16 reserved; +} __packed; + +struct afe_digital_clk_cfg { + u32 i2s_cfg_minor_version; + u32 clk_val; + u16 clk_root; + u16 reserved; +} __packed; + +struct afe_param_id_i2s_cfg { + u32 i2s_cfg_minor_version; + u16 bit_width; + u16 channel_mode; + u16 mono_stereo; + u16 ws_src; + u32 sample_rate; + u16 data_format; + u16 reserved; +} __packed; + +struct afe_param_id_tdm_cfg { + u32 tdm_cfg_minor_version; + u32 num_channels; + u32 sample_rate; + u32 bit_width; + u16 data_format; + u16 sync_mode; + u16 sync_src; + u16 nslots_per_frame; + u16 ctrl_data_out_enable; + u16 ctrl_invert_sync_pulse; + u16 ctrl_sync_data_delay; + u16 slot_width; + u32 slot_mask; +} __packed; + +union afe_port_config { + struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; + struct afe_param_id_slimbus_cfg slim_cfg; + struct afe_param_id_i2s_cfg i2s_cfg; + struct afe_param_id_tdm_cfg tdm_cfg; +} __packed; + + +struct afe_clk_set { + uint32_t clk_set_minor_version; + uint32_t clk_id; + uint32_t clk_freq_in_hz; + uint16_t clk_attri; + uint16_t clk_root; + uint32_t enable; +}; + +struct afe_param_id_slot_mapping_cfg { + u32 minor_version; + u16 num_channels; + u16 bitwidth; + u32 data_align_type; + u16 ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; +} __packed; + +struct q6afe_port { + wait_queue_head_t wait; + union afe_port_config port_cfg; + struct afe_param_id_slot_mapping_cfg *scfg; + struct aprv2_ibasic_rsp_result_t result; + int token; + int id; + int cfg_type; + struct q6afe *afe; + struct kref refcount; + struct list_head node; +}; + +struct afe_port_map { + int port_id; + int token; + int is_rx; + int is_dig_pcm; +}; + +/* + * Mapping between Virtual Port IDs to DSP AFE Port ID + * On B Family SoCs DSP Port IDs are consistent across multiple SoCs + * on A Family SoCs DSP port IDs are same as virtual Port IDs. + */ + +static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX, HDMI_RX, 1, 1}, + [SLIMBUS_0_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX, + SLIMBUS_0_RX, 1, 1}, + [SLIMBUS_1_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX, + SLIMBUS_1_RX, 1, 1}, + [SLIMBUS_2_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX, + SLIMBUS_2_RX, 1, 1}, + [SLIMBUS_3_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX, + SLIMBUS_3_RX, 1, 1}, + [SLIMBUS_4_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX, + SLIMBUS_4_RX, 1, 1}, + [SLIMBUS_5_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX, + SLIMBUS_5_RX, 1, 1}, + [SLIMBUS_6_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX, + SLIMBUS_6_RX, 1, 1}, + [SLIMBUS_0_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX, + SLIMBUS_0_TX, 0, 1}, + [SLIMBUS_1_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX, + SLIMBUS_1_TX, 0, 1}, + [SLIMBUS_2_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX, + SLIMBUS_2_TX, 0, 1}, + [SLIMBUS_3_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX, + SLIMBUS_3_TX, 0, 1}, + [SLIMBUS_4_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX, + SLIMBUS_4_TX, 0, 1}, + [SLIMBUS_5_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX, + SLIMBUS_5_TX, 0, 1}, + [SLIMBUS_6_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX, + SLIMBUS_6_TX, 0, 1}, + [PRIMARY_MI2S_RX] = { AFE_PORT_ID_PRIMARY_MI2S_RX, + PRIMARY_MI2S_RX, 1, 1}, + [PRIMARY_MI2S_TX] = { AFE_PORT_ID_PRIMARY_MI2S_TX, + PRIMARY_MI2S_RX, 0, 1}, + [SECONDARY_MI2S_RX] = { AFE_PORT_ID_SECONDARY_MI2S_RX, + SECONDARY_MI2S_RX, 1, 1}, + [SECONDARY_MI2S_TX] = { AFE_PORT_ID_SECONDARY_MI2S_TX, + SECONDARY_MI2S_TX, 0, 1}, + [TERTIARY_MI2S_RX] = { AFE_PORT_ID_TERTIARY_MI2S_RX, + TERTIARY_MI2S_RX, 1, 1}, + [TERTIARY_MI2S_TX] = { AFE_PORT_ID_TERTIARY_MI2S_TX, + TERTIARY_MI2S_TX, 0, 1}, + [QUATERNARY_MI2S_RX] = { AFE_PORT_ID_QUATERNARY_MI2S_RX, + QUATERNARY_MI2S_RX, 1, 1}, + [QUATERNARY_MI2S_TX] = { AFE_PORT_ID_QUATERNARY_MI2S_TX, + QUATERNARY_MI2S_TX, 0, 1}, + [PRIMARY_TDM_RX_0] = { AFE_PORT_ID_PRIMARY_TDM_RX, + PRIMARY_TDM_RX_0, 1, 1}, + [PRIMARY_TDM_TX_0] = { AFE_PORT_ID_PRIMARY_TDM_TX, + PRIMARY_TDM_TX_0, 0, 1}, + [PRIMARY_TDM_RX_1] = { AFE_PORT_ID_PRIMARY_TDM_RX_1, + PRIMARY_TDM_RX_1, 1, 1}, + [PRIMARY_TDM_TX_1] = { AFE_PORT_ID_PRIMARY_TDM_TX_1, + PRIMARY_TDM_TX_1, 0, 1}, + [PRIMARY_TDM_RX_2] = { AFE_PORT_ID_PRIMARY_TDM_RX_2, + PRIMARY_TDM_RX_2, 1, 1}, + [PRIMARY_TDM_TX_2] = { AFE_PORT_ID_PRIMARY_TDM_TX_2, + PRIMARY_TDM_TX_2, 0, 1}, + [PRIMARY_TDM_RX_3] = { AFE_PORT_ID_PRIMARY_TDM_RX_3, + PRIMARY_TDM_RX_3, 1, 1}, + [PRIMARY_TDM_TX_3] = { AFE_PORT_ID_PRIMARY_TDM_TX_3, + PRIMARY_TDM_TX_3, 0, 1}, + [PRIMARY_TDM_RX_4] = { AFE_PORT_ID_PRIMARY_TDM_RX_4, + PRIMARY_TDM_RX_4, 1, 1}, + [PRIMARY_TDM_TX_4] = { AFE_PORT_ID_PRIMARY_TDM_TX_4, + PRIMARY_TDM_TX_4, 0, 1}, + [PRIMARY_TDM_RX_5] = { AFE_PORT_ID_PRIMARY_TDM_RX_5, + PRIMARY_TDM_RX_5, 1, 1}, + [PRIMARY_TDM_TX_5] = { AFE_PORT_ID_PRIMARY_TDM_TX_5, + PRIMARY_TDM_TX_5, 0, 1}, + [PRIMARY_TDM_RX_6] = { AFE_PORT_ID_PRIMARY_TDM_RX_6, + PRIMARY_TDM_RX_6, 1, 1}, + [PRIMARY_TDM_TX_6] = { AFE_PORT_ID_PRIMARY_TDM_TX_6, + PRIMARY_TDM_TX_6, 0, 1}, + [PRIMARY_TDM_RX_7] = { AFE_PORT_ID_PRIMARY_TDM_RX_7, + PRIMARY_TDM_RX_7, 1, 1}, + [PRIMARY_TDM_TX_7] = { AFE_PORT_ID_PRIMARY_TDM_TX_7, + PRIMARY_TDM_TX_7, 0, 1}, + [SECONDARY_TDM_RX_0] = { AFE_PORT_ID_SECONDARY_TDM_RX, + SECONDARY_TDM_RX_0, 1, 1}, + [SECONDARY_TDM_TX_0] = { AFE_PORT_ID_SECONDARY_TDM_TX, + SECONDARY_TDM_TX_0, 0, 1}, + [SECONDARY_TDM_RX_1] = { AFE_PORT_ID_SECONDARY_TDM_RX_1, + SECONDARY_TDM_RX_1, 1, 1}, + [SECONDARY_TDM_TX_1] = { AFE_PORT_ID_SECONDARY_TDM_TX_1, + SECONDARY_TDM_TX_1, 0, 1}, + [SECONDARY_TDM_RX_2] = { AFE_PORT_ID_SECONDARY_TDM_RX_2, + SECONDARY_TDM_RX_2, 1, 1}, + [SECONDARY_TDM_TX_2] = { AFE_PORT_ID_SECONDARY_TDM_TX_2, + SECONDARY_TDM_TX_2, 0, 1}, + [SECONDARY_TDM_RX_3] = { AFE_PORT_ID_SECONDARY_TDM_RX_3, + SECONDARY_TDM_RX_3, 1, 1}, + [SECONDARY_TDM_TX_3] = { AFE_PORT_ID_SECONDARY_TDM_TX_3, + SECONDARY_TDM_TX_3, 0, 1}, + [SECONDARY_TDM_RX_4] = { AFE_PORT_ID_SECONDARY_TDM_RX_4, + SECONDARY_TDM_RX_4, 1, 1}, + [SECONDARY_TDM_TX_4] = { AFE_PORT_ID_SECONDARY_TDM_TX_4, + SECONDARY_TDM_TX_4, 0, 1}, + [SECONDARY_TDM_RX_5] = { AFE_PORT_ID_SECONDARY_TDM_RX_5, + SECONDARY_TDM_RX_5, 1, 1}, + [SECONDARY_TDM_TX_5] = { AFE_PORT_ID_SECONDARY_TDM_TX_5, + SECONDARY_TDM_TX_5, 0, 1}, + [SECONDARY_TDM_RX_6] = { AFE_PORT_ID_SECONDARY_TDM_RX_6, + SECONDARY_TDM_RX_6, 1, 1}, + [SECONDARY_TDM_TX_6] = { AFE_PORT_ID_SECONDARY_TDM_TX_6, + SECONDARY_TDM_TX_6, 0, 1}, + [SECONDARY_TDM_RX_7] = { AFE_PORT_ID_SECONDARY_TDM_RX_7, + SECONDARY_TDM_RX_7, 1, 1}, + [SECONDARY_TDM_TX_7] = { AFE_PORT_ID_SECONDARY_TDM_TX_7, + SECONDARY_TDM_TX_7, 0, 1}, + [TERTIARY_TDM_RX_0] = { AFE_PORT_ID_TERTIARY_TDM_RX, + TERTIARY_TDM_RX_0, 1, 1}, + [TERTIARY_TDM_TX_0] = { AFE_PORT_ID_TERTIARY_TDM_TX, + TERTIARY_TDM_TX_0, 0, 1}, + [TERTIARY_TDM_RX_1] = { AFE_PORT_ID_TERTIARY_TDM_RX_1, + TERTIARY_TDM_RX_1, 1, 1}, + [TERTIARY_TDM_TX_1] = { AFE_PORT_ID_TERTIARY_TDM_TX_1, + TERTIARY_TDM_TX_1, 0, 1}, + [TERTIARY_TDM_RX_2] = { AFE_PORT_ID_TERTIARY_TDM_RX_2, + TERTIARY_TDM_RX_2, 1, 1}, + [TERTIARY_TDM_TX_2] = { AFE_PORT_ID_TERTIARY_TDM_TX_2, + TERTIARY_TDM_TX_2, 0, 1}, + [TERTIARY_TDM_RX_3] = { AFE_PORT_ID_TERTIARY_TDM_RX_3, + TERTIARY_TDM_RX_3, 1, 1}, + [TERTIARY_TDM_TX_3] = { AFE_PORT_ID_TERTIARY_TDM_TX_3, + TERTIARY_TDM_TX_3, 0, 1}, + [TERTIARY_TDM_RX_4] = { AFE_PORT_ID_TERTIARY_TDM_RX_4, + TERTIARY_TDM_RX_4, 1, 1}, + [TERTIARY_TDM_TX_4] = { AFE_PORT_ID_TERTIARY_TDM_TX_4, + TERTIARY_TDM_TX_4, 0, 1}, + [TERTIARY_TDM_RX_5] = { AFE_PORT_ID_TERTIARY_TDM_RX_5, + TERTIARY_TDM_RX_5, 1, 1}, + [TERTIARY_TDM_TX_5] = { AFE_PORT_ID_TERTIARY_TDM_TX_5, + TERTIARY_TDM_TX_5, 0, 1}, + [TERTIARY_TDM_RX_6] = { AFE_PORT_ID_TERTIARY_TDM_RX_6, + TERTIARY_TDM_RX_6, 1, 1}, + [TERTIARY_TDM_TX_6] = { AFE_PORT_ID_TERTIARY_TDM_TX_6, + TERTIARY_TDM_TX_6, 0, 1}, + [TERTIARY_TDM_RX_7] = { AFE_PORT_ID_TERTIARY_TDM_RX_7, + TERTIARY_TDM_RX_7, 1, 1}, + [TERTIARY_TDM_TX_7] = { AFE_PORT_ID_TERTIARY_TDM_TX_7, + TERTIARY_TDM_TX_7, 0, 1}, + [QUATERNARY_TDM_RX_0] = { AFE_PORT_ID_QUATERNARY_TDM_RX, + QUATERNARY_TDM_RX_0, 1, 1}, + [QUATERNARY_TDM_TX_0] = { AFE_PORT_ID_QUATERNARY_TDM_TX, + QUATERNARY_TDM_TX_0, 0, 1}, + [QUATERNARY_TDM_RX_1] = { AFE_PORT_ID_QUATERNARY_TDM_RX_1, + QUATERNARY_TDM_RX_1, 1, 1}, + [QUATERNARY_TDM_TX_1] = { AFE_PORT_ID_QUATERNARY_TDM_TX_1, + QUATERNARY_TDM_TX_1, 0, 1}, + [QUATERNARY_TDM_RX_2] = { AFE_PORT_ID_QUATERNARY_TDM_RX_2, + QUATERNARY_TDM_RX_2, 1, 1}, + [QUATERNARY_TDM_TX_2] = { AFE_PORT_ID_QUATERNARY_TDM_TX_2, + QUATERNARY_TDM_TX_2, 0, 1}, + [QUATERNARY_TDM_RX_3] = { AFE_PORT_ID_QUATERNARY_TDM_RX_3, + QUATERNARY_TDM_RX_3, 1, 1}, + [QUATERNARY_TDM_TX_3] = { AFE_PORT_ID_QUATERNARY_TDM_TX_3, + QUATERNARY_TDM_TX_3, 0, 1}, + [QUATERNARY_TDM_RX_4] = { AFE_PORT_ID_QUATERNARY_TDM_RX_4, + QUATERNARY_TDM_RX_4, 1, 1}, + [QUATERNARY_TDM_TX_4] = { AFE_PORT_ID_QUATERNARY_TDM_TX_4, + QUATERNARY_TDM_TX_4, 0, 1}, + [QUATERNARY_TDM_RX_5] = { AFE_PORT_ID_QUATERNARY_TDM_RX_5, + QUATERNARY_TDM_RX_5, 1, 1}, + [QUATERNARY_TDM_TX_5] = { AFE_PORT_ID_QUATERNARY_TDM_TX_5, + QUATERNARY_TDM_TX_5, 0, 1}, + [QUATERNARY_TDM_RX_6] = { AFE_PORT_ID_QUATERNARY_TDM_RX_6, + QUATERNARY_TDM_RX_6, 1, 1}, + [QUATERNARY_TDM_TX_6] = { AFE_PORT_ID_QUATERNARY_TDM_TX_6, + QUATERNARY_TDM_TX_6, 0, 1}, + [QUATERNARY_TDM_RX_7] = { AFE_PORT_ID_QUATERNARY_TDM_RX_7, + QUATERNARY_TDM_RX_7, 1, 1}, + [QUATERNARY_TDM_TX_7] = { AFE_PORT_ID_QUATERNARY_TDM_TX_7, + QUATERNARY_TDM_TX_7, 0, 1}, + [QUINARY_TDM_RX_0] = { AFE_PORT_ID_QUINARY_TDM_RX, + QUINARY_TDM_RX_0, 1, 1}, + [QUINARY_TDM_TX_0] = { AFE_PORT_ID_QUINARY_TDM_TX, + QUINARY_TDM_TX_0, 0, 1}, + [QUINARY_TDM_RX_1] = { AFE_PORT_ID_QUINARY_TDM_RX_1, + QUINARY_TDM_RX_1, 1, 1}, + [QUINARY_TDM_TX_1] = { AFE_PORT_ID_QUINARY_TDM_TX_1, + QUINARY_TDM_TX_1, 0, 1}, + [QUINARY_TDM_RX_2] = { AFE_PORT_ID_QUINARY_TDM_RX_2, + QUINARY_TDM_RX_2, 1, 1}, + [QUINARY_TDM_TX_2] = { AFE_PORT_ID_QUINARY_TDM_TX_2, + QUINARY_TDM_TX_2, 0, 1}, + [QUINARY_TDM_RX_3] = { AFE_PORT_ID_QUINARY_TDM_RX_3, + QUINARY_TDM_RX_3, 1, 1}, + [QUINARY_TDM_TX_3] = { AFE_PORT_ID_QUINARY_TDM_TX_3, + QUINARY_TDM_TX_3, 0, 1}, + [QUINARY_TDM_RX_4] = { AFE_PORT_ID_QUINARY_TDM_RX_4, + QUINARY_TDM_RX_4, 1, 1}, + [QUINARY_TDM_TX_4] = { AFE_PORT_ID_QUINARY_TDM_TX_4, + QUINARY_TDM_TX_4, 0, 1}, + [QUINARY_TDM_RX_5] = { AFE_PORT_ID_QUINARY_TDM_RX_5, + QUINARY_TDM_RX_5, 1, 1}, + [QUINARY_TDM_TX_5] = { AFE_PORT_ID_QUINARY_TDM_TX_5, + QUINARY_TDM_TX_5, 0, 1}, + [QUINARY_TDM_RX_6] = { AFE_PORT_ID_QUINARY_TDM_RX_6, + QUINARY_TDM_RX_6, 1, 1}, + [QUINARY_TDM_TX_6] = { AFE_PORT_ID_QUINARY_TDM_TX_6, + QUINARY_TDM_TX_6, 0, 1}, + [QUINARY_TDM_RX_7] = { AFE_PORT_ID_QUINARY_TDM_RX_7, + QUINARY_TDM_RX_7, 1, 1}, + [QUINARY_TDM_TX_7] = { AFE_PORT_ID_QUINARY_TDM_TX_7, + QUINARY_TDM_TX_7, 0, 1}, +}; + +static void q6afe_port_free(struct kref *ref) +{ + struct q6afe_port *port; + struct q6afe *afe; + unsigned long flags; + + port = container_of(ref, struct q6afe_port, refcount); + afe = port->afe; + spin_lock_irqsave(&afe->port_list_lock, flags); + list_del(&port->node); + spin_unlock_irqrestore(&afe->port_list_lock, flags); + kfree(port->scfg); + kfree(port); +} + +static struct q6afe_port *q6afe_find_port(struct q6afe *afe, int token) +{ + struct q6afe_port *p = NULL; + struct q6afe_port *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&afe->port_list_lock, flags); + list_for_each_entry(p, &afe->port_list, node) + if (p->token == token) { + ret = p; + kref_get(&p->refcount); + break; + } + + spin_unlock_irqrestore(&afe->port_list_lock, flags); + return ret; +} + +static int q6afe_callback(struct apr_device *adev, struct apr_resp_pkt *data) +{ + struct q6afe *afe = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *res; + struct apr_hdr *hdr = &data->hdr; + struct q6afe_port *port; + + if (!data->payload_size) + return 0; + + res = data->payload; + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: { + if (res->status) { + dev_err(afe->dev, "cmd = 0x%x returned error = 0x%x\n", + res->opcode, res->status); + } + switch (res->opcode) { + case AFE_PORT_CMD_SET_PARAM_V2: + case AFE_PORT_CMD_DEVICE_STOP: + case AFE_PORT_CMD_DEVICE_START: + case AFE_SVC_CMD_SET_PARAM: + port = q6afe_find_port(afe, hdr->token); + if (port) { + port->result = *res; + wake_up(&port->wait); + kref_put(&port->refcount, q6afe_port_free); + } + break; + default: + dev_err(afe->dev, "Unknown cmd 0x%x\n", res->opcode); + break; + } + } + break; + default: + break; + } + + return 0; +} + +/** + * q6afe_get_port_id() - Get port id from a given port index + * + * @index: port index + * + * Return: Will be an negative on error or valid port_id on success + */ +int q6afe_get_port_id(int index) +{ + if (index < 0 || index >= AFE_PORT_MAX) + return -EINVAL; + + return port_maps[index].port_id; +} +EXPORT_SYMBOL_GPL(q6afe_get_port_id); + +static int afe_apr_send_pkt(struct q6afe *afe, struct apr_pkt *pkt, + struct q6afe_port *port) +{ + wait_queue_head_t *wait = &port->wait; + struct apr_hdr *hdr = &pkt->hdr; + int ret; + + mutex_lock(&afe->lock); + port->result.opcode = 0; + port->result.status = 0; + + ret = apr_send_pkt(afe->apr, pkt); + if (ret < 0) { + dev_err(afe->dev, "packet not transmitted (%d)\n", ret); + ret = -EINVAL; + goto err; + } + + ret = wait_event_timeout(*wait, (port->result.opcode == hdr->opcode), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + ret = -ETIMEDOUT; + } else if (port->result.status > 0) { + dev_err(afe->dev, "DSP returned error[%x]\n", + port->result.status); + ret = -EINVAL; + } else { + ret = 0; + } + +err: + mutex_unlock(&afe->lock); + + return ret; +} + +static int q6afe_port_set_param(struct q6afe_port *port, void *data, + int param_id, int module_id, int psize) +{ + struct afe_svc_cmd_set_param *param; + struct afe_port_param_data_v2 *pdata; + struct q6afe *afe = port->afe; + struct apr_pkt *pkt; + u16 port_id = port->id; + int ret, pkt_size; + void *p, *pl; + + pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize; + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + param = p + APR_HDR_SIZE; + pdata = p + APR_HDR_SIZE + sizeof(*param); + pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata); + memcpy(pl, data, psize); + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = port->token; + pkt->hdr.opcode = AFE_SVC_CMD_SET_PARAM; + + param->payload_size = sizeof(*pdata) + psize; + param->payload_address_lsw = 0x00; + param->payload_address_msw = 0x00; + param->mem_map_handle = 0x00; + pdata->module_id = module_id; + pdata->param_id = param_id; + pdata->param_size = psize; + + ret = afe_apr_send_pkt(afe, pkt, port); + if (ret) + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + + kfree(pkt); + return ret; +} + +static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data, + int param_id, int module_id, int psize) +{ + struct afe_port_cmd_set_param_v2 *param; + struct afe_port_param_data_v2 *pdata; + struct q6afe *afe = port->afe; + struct apr_pkt *pkt; + u16 port_id = port->id; + int ret, pkt_size; + void *p, *pl; + + pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize; + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + param = p + APR_HDR_SIZE; + pdata = p + APR_HDR_SIZE + sizeof(*param); + pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata); + memcpy(pl, data, psize); + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = port->token; + pkt->hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + + param->port_id = port_id; + param->payload_size = sizeof(*pdata) + psize; + param->payload_address_lsw = 0x00; + param->payload_address_msw = 0x00; + param->mem_map_handle = 0x00; + pdata->module_id = module_id; + pdata->param_id = param_id; + pdata->param_size = psize; + + ret = afe_apr_send_pkt(afe, pkt, port); + if (ret) + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + + kfree(pkt); + return ret; +} + +static int q6afe_set_lpass_clock(struct q6afe_port *port, + struct afe_clk_cfg *cfg) +{ + return q6afe_port_set_param_v2(port, cfg, + AFE_PARAM_ID_LPAIF_CLK_CONFIG, + AFE_MODULE_AUDIO_DEV_INTERFACE, + sizeof(*cfg)); +} + +static int q6afe_set_lpass_clock_v2(struct q6afe_port *port, + struct afe_clk_set *cfg) +{ + return q6afe_port_set_param(port, cfg, AFE_PARAM_ID_CLOCK_SET, + AFE_MODULE_CLOCK_SET, sizeof(*cfg)); +} + +static int q6afe_set_digital_codec_core_clock(struct q6afe_port *port, + struct afe_digital_clk_cfg *cfg) +{ + return q6afe_port_set_param_v2(port, cfg, + AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG, + AFE_MODULE_AUDIO_DEV_INTERFACE, + sizeof(*cfg)); +} + +int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, + int clk_src, int clk_root, + unsigned int freq, int dir) +{ + struct afe_clk_cfg ccfg = {0,}; + struct afe_clk_set cset = {0,}; + struct afe_digital_clk_cfg dcfg = {0,}; + int ret; + + switch (clk_id) { + case LPAIF_DIG_CLK: + dcfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + dcfg.clk_val = freq; + dcfg.clk_root = clk_root; + ret = q6afe_set_digital_codec_core_clock(port, &dcfg); + break; + case LPAIF_BIT_CLK: + ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + ccfg.clk_val1 = freq; + ccfg.clk_src = clk_src; + ccfg.clk_root = clk_root; + ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK1_VALID; + ret = q6afe_set_lpass_clock(port, &ccfg); + break; + + case LPAIF_OSR_CLK: + ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + ccfg.clk_val2 = freq; + ccfg.clk_src = clk_src; + ccfg.clk_root = clk_root; + ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK2_VALID; + ret = q6afe_set_lpass_clock(port, &ccfg); + break; + case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR: + case Q6AFE_LPASS_CLK_ID_MCLK_1 ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1: + case Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT ... Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT: + cset.clk_set_minor_version = AFE_API_VERSION_CLOCK_SET; + cset.clk_id = clk_id; + cset.clk_freq_in_hz = freq; + cset.clk_attri = clk_src; + cset.clk_root = clk_root; + cset.enable = !!freq; + ret = q6afe_set_lpass_clock_v2(port, &cset); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_set_sysclk); + +/** + * q6afe_port_stop() - Stop a afe port + * + * @port: Instance of port to stop + * + * Return: Will be an negative on packet size on success. + */ +int q6afe_port_stop(struct q6afe_port *port) +{ + struct afe_port_cmd_device_stop *stop; + struct q6afe *afe = port->afe; + struct apr_pkt *pkt; + int port_id = port->id; + int ret = 0; + int index, pkt_size; + void *p; + + port_id = port->id; + index = port->token; + if (index < 0 || index >= AFE_PORT_MAX) { + dev_err(afe->dev, "AFE port index[%d] invalid!\n", index); + return -EINVAL; + } + + pkt_size = APR_HDR_SIZE + sizeof(*stop); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + stop = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = index; + pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_STOP; + stop->port_id = port_id; + stop->reserved = 0; + + ret = afe_apr_send_pkt(afe, pkt, port); + if (ret) + dev_err(afe->dev, "AFE close failed %d\n", ret); + + kfree(pkt); + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_stop); + +/** + * q6afe_slim_port_prepare() - Prepare slim afe port. + * + * @port: Instance of afe port + * @cfg: SLIM configuration for the afe port + * + */ +void q6afe_slim_port_prepare(struct q6afe_port *port, + struct q6afe_slim_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->slim_cfg.sb_cfg_minor_version = AFE_API_VERSION_SLIMBUS_CONFIG; + pcfg->slim_cfg.sample_rate = cfg->sample_rate; + pcfg->slim_cfg.bit_width = cfg->bit_width; + pcfg->slim_cfg.num_channels = cfg->num_channels; + pcfg->slim_cfg.data_format = cfg->data_format; + pcfg->slim_cfg.shared_ch_mapping[0] = cfg->ch_mapping[0]; + pcfg->slim_cfg.shared_ch_mapping[1] = cfg->ch_mapping[1]; + pcfg->slim_cfg.shared_ch_mapping[2] = cfg->ch_mapping[2]; + pcfg->slim_cfg.shared_ch_mapping[3] = cfg->ch_mapping[3]; + +} +EXPORT_SYMBOL_GPL(q6afe_slim_port_prepare); + +/** + * q6afe_tdm_port_prepare() - Prepare tdm afe port. + * + * @port: Instance of afe port + * @cfg: TDM configuration for the afe port + * + */ +void q6afe_tdm_port_prepare(struct q6afe_port *port, + struct q6afe_tdm_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->tdm_cfg.tdm_cfg_minor_version = AFE_API_VERSION_TDM_CONFIG; + pcfg->tdm_cfg.num_channels = cfg->num_channels; + pcfg->tdm_cfg.sample_rate = cfg->sample_rate; + pcfg->tdm_cfg.bit_width = cfg->bit_width; + pcfg->tdm_cfg.data_format = cfg->data_format; + pcfg->tdm_cfg.sync_mode = cfg->sync_mode; + pcfg->tdm_cfg.sync_src = cfg->sync_src; + pcfg->tdm_cfg.nslots_per_frame = cfg->nslots_per_frame; + + pcfg->tdm_cfg.slot_width = cfg->slot_width; + pcfg->tdm_cfg.slot_mask = cfg->slot_mask; + port->scfg = kzalloc(sizeof(*port->scfg), GFP_KERNEL); + if (!port->scfg) + return; + + port->scfg->minor_version = AFE_API_VERSION_SLOT_MAPPING_CONFIG; + port->scfg->num_channels = cfg->num_channels; + port->scfg->bitwidth = cfg->bit_width; + port->scfg->data_align_type = cfg->data_align_type; + memcpy(port->scfg->ch_mapping, cfg->ch_mapping, + sizeof(u16) * AFE_PORT_MAX_AUDIO_CHAN_CNT); +} +EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); + +/** + * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. + * + * @port: Instance of afe port + * @cfg: HDMI configuration for the afe port + * + */ +void q6afe_hdmi_port_prepare(struct q6afe_port *port, + struct q6afe_hdmi_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->hdmi_multi_ch.hdmi_cfg_minor_version = + AFE_API_VERSION_HDMI_CONFIG; + pcfg->hdmi_multi_ch.datatype = cfg->datatype; + pcfg->hdmi_multi_ch.channel_allocation = cfg->channel_allocation; + pcfg->hdmi_multi_ch.sample_rate = cfg->sample_rate; + pcfg->hdmi_multi_ch.bit_width = cfg->bit_width; +} +EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare); + +/** + * q6afe_i2s_port_prepare() - Prepare i2s afe port. + * + * @port: Instance of afe port + * @cfg: I2S configuration for the afe port + * Return: Will be an negative on error and zero on success. + */ +int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + struct device *dev = port->afe->dev; + int num_sd_lines; + + pcfg->i2s_cfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + pcfg->i2s_cfg.sample_rate = cfg->sample_rate; + pcfg->i2s_cfg.bit_width = cfg->bit_width; + pcfg->i2s_cfg.data_format = AFE_LINEAR_PCM_DATA; + + switch (cfg->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* CPU is slave */ + pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL; + break; + default: + break; + } + + num_sd_lines = hweight_long(cfg->sd_line_mask); + + switch (num_sd_lines) { + case 0: + dev_err(dev, "no line is assigned\n"); + return -EINVAL; + case 1: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0; + break; + case AFE_PORT_I2S_SD1_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD1; + break; + case AFE_PORT_I2S_SD2_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2; + break; + case AFE_PORT_I2S_SD3_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD3; + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + case 2: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_1_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD01; + break; + case AFE_PORT_I2S_SD2_3_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD23; + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + case 3: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_1_2_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_6CHS; + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + case 4: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_1_2_3_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_8CHS; + + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + + switch (cfg->num_channels) { + case 1: + case 2: + switch (pcfg->i2s_cfg.channel_mode) { + case AFE_PORT_I2S_QUAD01: + case AFE_PORT_I2S_6CHS: + case AFE_PORT_I2S_8CHS: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0; + break; + case AFE_PORT_I2S_QUAD23: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2; + break; + } + + if (cfg->num_channels == 2) + pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_STEREO; + else + pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_MONO; + + break; + case 3: + case 4: + if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_QUAD01) { + dev_err(dev, "Invalid Channel mode\n"); + return -EINVAL; + } + break; + case 5: + case 6: + if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_6CHS) { + dev_err(dev, "Invalid Channel mode\n"); + return -EINVAL; + } + break; + case 7: + case 8: + if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_8CHS) { + dev_err(dev, "Invalid Channel mode\n"); + return -EINVAL; + } + break; + default: + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6afe_i2s_port_prepare); + +/** + * q6afe_port_start() - Start a afe port + * + * @port: Instance of port to start + * + * Return: Will be an negative on packet size on success. + */ +int q6afe_port_start(struct q6afe_port *port) +{ + struct afe_port_cmd_device_start *start; + struct q6afe *afe = port->afe; + int port_id = port->id; + int ret, param_id = port->cfg_type; + struct apr_pkt *pkt; + int pkt_size; + void *p; + + ret = q6afe_port_set_param_v2(port, &port->port_cfg, param_id, + AFE_MODULE_AUDIO_DEV_INTERFACE, + sizeof(port->port_cfg)); + if (ret) { + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + return ret; + } + + if (port->scfg) { + ret = q6afe_port_set_param_v2(port, port->scfg, + AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG, + AFE_MODULE_TDM, sizeof(*port->scfg)); + if (ret) { + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + return ret; + } + } + + pkt_size = APR_HDR_SIZE + sizeof(*start); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + start = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = port->token; + pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_START; + + start->port_id = port_id; + + ret = afe_apr_send_pkt(afe, pkt, port); + if (ret) + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + + kfree(pkt); + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_start); + +/** + * q6afe_port_get_from_id() - Get port instance from a port id + * + * @dev: Pointer to afe child device. + * @id: port id + * + * Return: Will be an error pointer on error or a valid afe port + * on success. + */ +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) +{ + int port_id; + struct q6afe *afe = dev_get_drvdata(dev->parent); + struct q6afe_port *port; + unsigned long flags; + int cfg_type; + + if (id < 0 || id >= AFE_PORT_MAX) { + dev_err(dev, "AFE port token[%d] invalid!\n", id); + return ERR_PTR(-EINVAL); + } + + /* if port is multiple times bind/unbind before callback finishes */ + port = q6afe_find_port(afe, id); + if (port) { + dev_err(dev, "AFE Port already open\n"); + return port; + } + + port_id = port_maps[id].port_id; + + switch (port_id) { + case AFE_PORT_ID_MULTICHAN_HDMI_RX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX: + cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; + break; + + case AFE_PORT_ID_PRIMARY_MI2S_RX: + case AFE_PORT_ID_PRIMARY_MI2S_TX: + case AFE_PORT_ID_SECONDARY_MI2S_RX: + case AFE_PORT_ID_SECONDARY_MI2S_TX: + case AFE_PORT_ID_TERTIARY_MI2S_RX: + case AFE_PORT_ID_TERTIARY_MI2S_TX: + case AFE_PORT_ID_QUATERNARY_MI2S_RX: + case AFE_PORT_ID_QUATERNARY_MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: + cfg_type = AFE_PARAM_ID_TDM_CONFIG; + break; + + default: + dev_err(dev, "Invalid port id 0x%x\n", port_id); + return ERR_PTR(-EINVAL); + } + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + init_waitqueue_head(&port->wait); + + port->token = id; + port->id = port_id; + port->afe = afe; + port->cfg_type = cfg_type; + kref_init(&port->refcount); + + spin_lock_irqsave(&afe->port_list_lock, flags); + list_add_tail(&port->node, &afe->port_list); + spin_unlock_irqrestore(&afe->port_list_lock, flags); + + return port; + +} +EXPORT_SYMBOL_GPL(q6afe_port_get_from_id); + +/** + * q6afe_port_put() - Release port reference + * + * @port: Instance of port to put + */ +void q6afe_port_put(struct q6afe_port *port) +{ + kref_put(&port->refcount, q6afe_port_free); +} +EXPORT_SYMBOL_GPL(q6afe_port_put); + +static int q6afe_probe(struct apr_device *adev) +{ + struct q6afe *afe; + struct device *dev = &adev->dev; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + q6core_get_svc_api_info(adev->svc_id, &afe->ainfo); + afe->apr = adev; + mutex_init(&afe->lock); + afe->dev = dev; + INIT_LIST_HEAD(&afe->port_list); + spin_lock_init(&afe->port_list_lock); + + dev_set_drvdata(dev, afe); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static int q6afe_remove(struct apr_device *adev) +{ + of_platform_depopulate(&adev->dev); + + return 0; +} + +static const struct of_device_id q6afe_device_id[] = { + { .compatible = "qcom,q6afe" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6afe_device_id); + +static struct apr_driver qcom_q6afe_driver = { + .probe = q6afe_probe, + .remove = q6afe_remove, + .callback = q6afe_callback, + .driver = { + .name = "qcom-q6afe", + .of_match_table = of_match_ptr(q6afe_device_id), + + }, +}; + +module_apr_driver(qcom_q6afe_driver); +MODULE_DESCRIPTION("Q6 Audio Front End"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h new file mode 100644 index 000000000..c7ed5422b --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6AFE_H__ +#define __Q6AFE_H__ + +#include + +#define AFE_PORT_MAX 105 + +#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 +#define AFE_MAX_PORTS AFE_PORT_MAX + +#define Q6AFE_MAX_MI2S_LINES 4 + +#define AFE_MAX_CHAN_COUNT 8 +#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8 + +#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1 +#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0 + +#define LPAIF_DIG_CLK 1 +#define LPAIF_BIT_CLK 2 +#define LPAIF_OSR_CLK 3 + +/* Clock ID for Primary I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 +/* Clock ID for Primary I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_EBIT 0x101 +/* Clock ID for Secondary I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT 0x102 +/* Clock ID for Secondary I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_EBIT 0x103 +/* Clock ID for Tertiary I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT 0x104 +/* Clock ID for Tertiary I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_MI2S_EBIT 0x105 +/* Clock ID for Quartnery I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT 0x106 +/* Clock ID for Quartnery I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_EBIT 0x107 +/* Clock ID for Speaker I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_IBIT 0x108 +/* Clock ID for Speaker I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_EBIT 0x109 +/* Clock ID for Speaker I2S OSR */ +#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_OSR 0x10A + +/* Clock ID for QUINARY I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_IBIT 0x10B +/* Clock ID for QUINARY I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_EBIT 0x10C +/* Clock ID for SENARY I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_IBIT 0x10D +/* Clock ID for SENARY I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_EBIT 0x10E +/* Clock ID for INT0 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT 0x10F +/* Clock ID for INT1 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT1_MI2S_IBIT 0x110 +/* Clock ID for INT2 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT2_MI2S_IBIT 0x111 +/* Clock ID for INT3 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT 0x112 +/* Clock ID for INT4 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT4_MI2S_IBIT 0x113 +/* Clock ID for INT5 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT5_MI2S_IBIT 0x114 +/* Clock ID for INT6 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT6_MI2S_IBIT 0x115 + +/* Clock ID for QUINARY MI2S OSR CLK */ +#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + +/* Clock ID for Primary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_PCM_IBIT 0x200 +/* Clock ID for Primary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_PCM_EBIT 0x201 +/* Clock ID for Secondary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_PCM_IBIT 0x202 +/* Clock ID for Secondary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_PCM_EBIT 0x203 +/* Clock ID for Tertiary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_PCM_IBIT 0x204 +/* Clock ID for Tertiary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_PCM_EBIT 0x205 +/* Clock ID for Quartery PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_IBIT 0x206 +/* Clock ID for Quartery PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_EBIT 0x207 +/* Clock ID for Quinary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_IBIT 0x208 +/* Clock ID for Quinary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_EBIT 0x209 +/* Clock ID for QUINARY PCM OSR */ +#define Q6AFE_LPASS_CLK_ID_QUI_PCM_OSR 0x20A + +/** Clock ID for Primary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT 0x200 +/** Clock ID for Primary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_TDM_EBIT 0x201 +/** Clock ID for Secondary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_TDM_IBIT 0x202 +/** Clock ID for Secondary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_TDM_EBIT 0x203 +/** Clock ID for Tertiary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_TDM_IBIT 0x204 +/** Clock ID for Tertiary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_TDM_EBIT 0x205 +/** Clock ID for Quartery TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT 0x206 +/** Clock ID for Quartery TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_EBIT 0x207 +/** Clock ID for Quinary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_IBIT 0x208 +/** Clock ID for Quinary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT 0x209 +/** Clock ID for Quinary TDM OSR */ +#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_OSR 0x20A + +/* Clock ID for MCLK1 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_1 0x300 +/* Clock ID for MCLK2 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_2 0x301 +/* Clock ID for MCLK3 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_3 0x302 +/* Clock ID for MCLK4 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_4 0x304 +/* Clock ID for Internal Digital Codec Core */ +#define Q6AFE_LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE 0x303 +/* Clock ID for INT MCLK0 */ +#define Q6AFE_LPASS_CLK_ID_INT_MCLK_0 0x305 +/* Clock ID for INT MCLK1 */ +#define Q6AFE_LPASS_CLK_ID_INT_MCLK_1 0x306 + +/* Clock attribute for invalid use (reserved for internal usage) */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVALID 0x0 +/* Clock attribute for no couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +/* Clock attribute for dividend couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVIDEND 0x2 +/* Clock attribute for divisor couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR 0x3 +/* Clock attribute for invert and no couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVERT_COUPLE_NO 0x4 + +#define Q6AFE_CMAP_INVALID 0xFFFF + +struct q6afe_hdmi_cfg { + u16 datatype; + u16 channel_allocation; + u32 sample_rate; + u16 bit_width; +}; + +struct q6afe_slim_cfg { + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + u8 ch_mapping[AFE_MAX_CHAN_COUNT]; +}; + +struct q6afe_i2s_cfg { + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + u32 sd_line_mask; + int fmt; +}; + +struct q6afe_tdm_cfg { + u16 num_channels; + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 sync_mode; + u16 sync_src; + u16 nslots_per_frame; + u16 slot_width; + u16 slot_mask; + u32 data_align_type; + u16 ch_mapping[AFE_MAX_CHAN_COUNT]; +}; + +struct q6afe_port_config { + struct q6afe_hdmi_cfg hdmi; + struct q6afe_slim_cfg slim; + struct q6afe_i2s_cfg i2s_cfg; + struct q6afe_tdm_cfg tdm; +}; + +struct q6afe_port; + +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); +int q6afe_port_start(struct q6afe_port *port); +int q6afe_port_stop(struct q6afe_port *port); +void q6afe_port_put(struct q6afe_port *port); +int q6afe_get_port_id(int index); +void q6afe_hdmi_port_prepare(struct q6afe_port *port, + struct q6afe_hdmi_cfg *cfg); +void q6afe_slim_port_prepare(struct q6afe_port *port, + struct q6afe_slim_cfg *cfg); +int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg); +void q6afe_tdm_port_prepare(struct q6afe_port *port, struct q6afe_tdm_cfg *cfg); + +int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, + int clk_src, int clk_root, + unsigned int freq, int dir); +#endif /* __Q6AFE_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c new file mode 100644 index 000000000..c1a7d376a --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6asm.h" +#include "q6routing.h" +#include "q6dsp-errno.h" + +#define DRV_NAME "q6asm-fe-dai" + +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 65536 +#define PLAYBACK_MIN_PERIOD_SIZE 128 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 8 +#define CAPTURE_MAX_PERIOD_SIZE 4096 +#define CAPTURE_MIN_PERIOD_SIZE 320 +#define SID_MASK_DEFAULT 0xF + +enum stream_state { + Q6ASM_STREAM_IDLE = 0, + Q6ASM_STREAM_STOPPED, + Q6ASM_STREAM_RUNNING, +}; + +struct q6asm_dai_rtd { + struct snd_pcm_substream *substream; + phys_addr_t phys; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int periods; + uint16_t bits_per_sample; + uint16_t source; /* Encoding source bit mask */ + struct audio_client *audio_client; + uint16_t session_id; + enum stream_state state; +}; + +struct q6asm_dai_data { + long long int sid; +}; + +static struct snd_pcm_hardware q6asm_dai_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * + CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware q6asm_dai_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = (PLAYBACK_MAX_NUM_PERIODS * + PLAYBACK_MAX_PERIOD_SIZE), + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +#define Q6ASM_FEDAI_DRIVER(num) { \ + .playback = { \ + .stream_name = "MultiMedia"#num" Playback", \ + .rates = (SNDRV_PCM_RATE_8000_192000| \ + SNDRV_PCM_RATE_KNOT), \ + .formats = (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE), \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 192000, \ + }, \ + .capture = { \ + .stream_name = "MultiMedia"#num" Capture", \ + .rates = (SNDRV_PCM_RATE_8000_48000| \ + SNDRV_PCM_RATE_KNOT), \ + .formats = (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE), \ + .channels_min = 1, \ + .channels_max = 4, \ + .rate_min = 8000, \ + .rate_max = 48000, \ + }, \ + .name = "MultiMedia"#num, \ + .probe = fe_dai_probe, \ + .id = MSM_FRONTEND_DAI_MULTIMEDIA##num, \ + } + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 88200, 96000, 176400, 192000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6asm_dai_rtd *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + + switch (opcode) { + case ASM_CLIENT_EVENT_CMD_RUN_DONE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + q6asm_write_async(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + break; + case ASM_CLIENT_EVENT_CMD_EOS_DONE: + prtd->state = Q6ASM_STREAM_STOPPED; + break; + case ASM_CLIENT_EVENT_DATA_WRITE_DONE: { + prtd->pcm_irq_pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == Q6ASM_STREAM_RUNNING) + q6asm_write_async(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + + break; + } + case ASM_CLIENT_EVENT_DATA_READ_DONE: + prtd->pcm_irq_pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == Q6ASM_STREAM_RUNNING) + q6asm_read(prtd->audio_client); + + break; + default: + break; + } +} + +static int q6asm_dai_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct q6asm_dai_rtd *prtd = runtime->private_data; + struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME); + struct q6asm_dai_data *pdata; + int ret, i; + + pdata = snd_soc_component_get_drvdata(c); + if (!pdata) + return -EINVAL; + + if (!prtd || !prtd->audio_client) { + pr_err("%s: private data null or audio client freed\n", + __func__); + return -EINVAL; + } + + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + if (prtd->state) { + /* clear the previous setup if any */ + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_unmap_memory_regions(substream->stream, + prtd->audio_client); + q6routing_stream_close(soc_prtd->dai_link->id, + substream->stream); + } + + ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client, + prtd->phys, + (prtd->pcm_size / prtd->periods), + prtd->periods); + + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed rc = %d\n", + ret); + return -ENOMEM; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM, + prtd->bits_per_sample); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM, + prtd->bits_per_sample); + } + + if (ret < 0) { + pr_err("%s: q6asm_open_write failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + return -ENOMEM; + } + + prtd->session_id = q6asm_get_session_id(prtd->audio_client); + ret = q6routing_stream_open(soc_prtd->dai_link->id, LEGACY_PCM_MODE, + prtd->session_id, substream->stream); + if (ret) { + pr_err("%s: stream reg failed ret:%d\n", __func__, ret); + return ret; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_media_format_block_multi_ch_pcm( + prtd->audio_client, runtime->rate, + runtime->channels, NULL, + prtd->bits_per_sample); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = q6asm_enc_cfg_blk_pcm_format_support(prtd->audio_client, + runtime->rate, runtime->channels, + prtd->bits_per_sample); + + /* Queue the buffers */ + for (i = 0; i < runtime->periods; i++) + q6asm_read(prtd->audio_client); + + } + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + prtd->state = Q6ASM_STREAM_RUNNING; + + return 0; +} + +static int q6asm_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + prtd->state = Q6ASM_STREAM_STOPPED; + ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6asm_dai_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_prtd->cpu_dai; + struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME); + struct q6asm_dai_rtd *prtd; + struct q6asm_dai_data *pdata; + struct device *dev = c->dev; + int ret = 0; + int stream_id; + + stream_id = cpu_dai->driver->id; + + pdata = snd_soc_component_get_drvdata(c); + if (!pdata) { + pr_err("Drv data not found ..\n"); + return -EINVAL; + } + + prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc(dev, + (q6asm_cb)event_handler, prtd, stream_id, + LEGACY_PCM_MODE); + if (IS_ERR(prtd->audio_client)) { + pr_info("%s: Could not allocate memory\n", __func__); + ret = PTR_ERR(prtd->audio_client); + kfree(prtd); + return ret; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = q6asm_dai_hardware_playback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + runtime->hw = q6asm_dai_hardware_capture; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE, + PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE); + if (ret < 0) { + pr_err("constraint for buffer bytes min max ret = %d\n", + ret); + } + } + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret < 0) { + pr_err("constraint for period bytes step ret = %d\n", + ret); + } + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret < 0) { + pr_err("constraint for buffer bytes step ret = %d\n", + ret); + } + + runtime->private_data = prtd; + + snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_playback); + + runtime->dma_bytes = q6asm_dai_hardware_playback.buffer_bytes_max; + + + if (pdata->sid < 0) + prtd->phys = substream->dma_buffer.addr; + else + prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int q6asm_dai_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + if (prtd->audio_client) { + if (prtd->state) + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + + q6asm_unmap_memory_regions(substream->stream, + prtd->audio_client); + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + } + q6routing_stream_close(soc_prtd->dai_link->id, + substream->stream); + kfree(prtd); + return 0; +} + +static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int q6asm_dai_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME); + struct device *dev = c->dev; + + return dma_mmap_coherent(dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static int q6asm_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + prtd->pcm_size = params_buffer_bytes(params); + prtd->periods = params_periods(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + prtd->bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + prtd->bits_per_sample = 24; + break; + } + + return 0; +} + +static struct snd_pcm_ops q6asm_dai_ops = { + .open = q6asm_dai_open, + .hw_params = q6asm_dai_hw_params, + .close = q6asm_dai_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = q6asm_dai_prepare, + .trigger = q6asm_dai_trigger, + .pointer = q6asm_dai_pointer, + .mmap = q6asm_dai_mmap, +}; + +static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm_substream *psubstream, *csubstream; + struct snd_soc_component *c = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct snd_pcm *pcm = rtd->pcm; + struct device *dev; + int size, ret; + + dev = c->dev; + size = q6asm_dai_hardware_playback.buffer_bytes_max; + psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (psubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, + &psubstream->dma_buffer); + if (ret) { + dev_err(dev, "Cannot allocate buffer(s)\n"); + return ret; + } + } + + csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (csubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, + &csubstream->dma_buffer); + if (ret) { + dev_err(dev, "Cannot allocate buffer(s)\n"); + if (psubstream) + snd_dma_free_pages(&psubstream->dma_buffer); + return ret; + } + } + + return ret; +} + +static void q6asm_dai_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static const struct snd_soc_dapm_route afe_pcm_routes[] = { + {"MM_DL1", NULL, "MultiMedia1 Playback" }, + {"MM_DL2", NULL, "MultiMedia2 Playback" }, + {"MM_DL3", NULL, "MultiMedia3 Playback" }, + {"MM_DL4", NULL, "MultiMedia4 Playback" }, + {"MM_DL5", NULL, "MultiMedia5 Playback" }, + {"MM_DL6", NULL, "MultiMedia6 Playback" }, + {"MM_DL7", NULL, "MultiMedia7 Playback" }, + {"MM_DL7", NULL, "MultiMedia8 Playback" }, + {"MultiMedia1 Capture", NULL, "MM_UL1"}, + {"MultiMedia2 Capture", NULL, "MM_UL2"}, + {"MultiMedia3 Capture", NULL, "MM_UL3"}, + {"MultiMedia4 Capture", NULL, "MM_UL4"}, + {"MultiMedia5 Capture", NULL, "MM_UL5"}, + {"MultiMedia6 Capture", NULL, "MM_UL6"}, + {"MultiMedia7 Capture", NULL, "MM_UL7"}, + {"MultiMedia8 Capture", NULL, "MM_UL8"}, + +}; + +static int fe_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_dapm_context *dapm; + + dapm = snd_soc_component_get_dapm(dai->component); + snd_soc_dapm_add_routes(dapm, afe_pcm_routes, + ARRAY_SIZE(afe_pcm_routes)); + + return 0; +} + + +static const struct snd_soc_component_driver q6asm_fe_dai_component = { + .name = DRV_NAME, + .ops = &q6asm_dai_ops, + .pcm_new = q6asm_dai_pcm_new, + .pcm_free = q6asm_dai_pcm_free, + +}; + +static struct snd_soc_dai_driver q6asm_fe_dais[] = { + Q6ASM_FEDAI_DRIVER(1), + Q6ASM_FEDAI_DRIVER(2), + Q6ASM_FEDAI_DRIVER(3), + Q6ASM_FEDAI_DRIVER(4), + Q6ASM_FEDAI_DRIVER(5), + Q6ASM_FEDAI_DRIVER(6), + Q6ASM_FEDAI_DRIVER(7), + Q6ASM_FEDAI_DRIVER(8), +}; + +static int q6asm_dai_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct of_phandle_args args; + struct q6asm_dai_data *pdata; + int rc; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); + if (rc < 0) + pdata->sid = -1; + else + pdata->sid = args.args[0] & SID_MASK_DEFAULT; + + dev_set_drvdata(dev, pdata); + + return devm_snd_soc_register_component(dev, &q6asm_fe_dai_component, + q6asm_fe_dais, + ARRAY_SIZE(q6asm_fe_dais)); +} + +static const struct of_device_id q6asm_dai_device_id[] = { + { .compatible = "qcom,q6asm-dais" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6asm_dai_device_id); + +static struct platform_driver q6asm_dai_platform_driver = { + .driver = { + .name = "q6asm-dai", + .of_match_table = of_match_ptr(q6asm_dai_device_id), + }, + .probe = q6asm_dai_probe, +}; +module_platform_driver(q6asm_dai_platform_driver); + +MODULE_DESCRIPTION("Q6ASM dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c new file mode 100644 index 000000000..1bdacf797 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -0,0 +1,1387 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6asm.h" +#include "q6core.h" +#include "q6dsp-errno.h" +#include "q6dsp-common.h" + +#define ASM_STREAM_CMD_CLOSE 0x00010BCD +#define ASM_STREAM_CMD_FLUSH 0x00010BCE +#define ASM_SESSION_CMD_PAUSE 0x00010BD3 +#define ASM_DATA_CMD_EOS 0x00010BDB +#define ASM_DATA_EVENT_RENDERED_EOS 0x00010C1C +#define ASM_NULL_POPP_TOPOLOGY 0x00010C68 +#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09 +#define ASM_STREAM_CMD_SET_ENCDEC_PARAM 0x00010C10 +#define ASM_STREAM_POSTPROC_TOPO_ID_NONE 0x00010C68 +#define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92 +#define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93 +#define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94 +#define ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2 0x00010D98 +#define ASM_DATA_EVENT_WRITE_DONE_V2 0x00010D99 +#define ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2 0x00010DA3 +#define ASM_SESSION_CMD_RUN_V2 0x00010DAA +#define ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 0x00010DA5 +#define ASM_DATA_CMD_WRITE_V2 0x00010DAB +#define ASM_DATA_CMD_READ_V2 0x00010DAC +#define ASM_SESSION_CMD_SUSPEND 0x00010DEC +#define ASM_STREAM_CMD_OPEN_WRITE_V3 0x00010DB3 +#define ASM_STREAM_CMD_OPEN_READ_V3 0x00010DB4 +#define ASM_DATA_EVENT_READ_DONE_V2 0x00010D9A +#define ASM_STREAM_CMD_OPEN_READWRITE_V2 0x00010D8D + + +#define ASM_LEGACY_STREAM_SESSION 0 +/* Bit shift for the stream_perf_mode subfield. */ +#define ASM_SHIFT_STREAM_PERF_MODE_FLAG_IN_OPEN_READ 29 +#define ASM_END_POINT_DEVICE_MATRIX 0 +#define ASM_DEFAULT_APP_TYPE 0 +#define ASM_SYNC_IO_MODE 0x0001 +#define ASM_ASYNC_IO_MODE 0x0002 +#define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ +#define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */ +#define ASM_SHIFT_GAPLESS_MODE_FLAG 31 +#define ADSP_MEMORY_MAP_SHMEM8_4K_POOL 3 + +struct avs_cmd_shared_mem_map_regions { + u16 mem_pool_id; + u16 num_regions; + u32 property_flag; +} __packed; + +struct avs_shared_map_region_payload { + u32 shm_addr_lsw; + u32 shm_addr_msw; + u32 mem_size_bytes; +} __packed; + +struct avs_cmd_shared_mem_unmap_regions { + u32 mem_map_handle; +} __packed; + +struct asm_data_cmd_media_fmt_update_v2 { + u32 fmt_blk_size; +} __packed; + +struct asm_multi_channel_pcm_fmt_blk_v2 { + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u16 num_channels; + u16 bits_per_sample; + u32 sample_rate; + u16 is_signed; + u16 reserved; + u8 channel_mapping[PCM_MAX_NUM_CHANNEL]; +} __packed; + +struct asm_stream_cmd_set_encdec_param { + u32 param_id; + u32 param_size; +} __packed; + +struct asm_enc_cfg_blk_param_v2 { + u32 frames_per_buf; + u32 enc_cfg_blk_size; +} __packed; + +struct asm_multi_channel_pcm_enc_cfg_v2 { + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + uint16_t num_channels; + uint16_t bits_per_sample; + uint32_t sample_rate; + uint16_t is_signed; + uint16_t reserved; + uint8_t channel_mapping[8]; +} __packed; + +struct asm_data_cmd_read_v2 { + u32 buf_addr_lsw; + u32 buf_addr_msw; + u32 mem_map_handle; + u32 buf_size; + u32 seq_id; +} __packed; + +struct asm_data_cmd_read_v2_done { + u32 status; + u32 buf_addr_lsw; + u32 buf_addr_msw; +}; + +struct asm_stream_cmd_open_read_v3 { + u32 mode_flags; + u32 src_endpointype; + u32 preprocopo_id; + u32 enc_cfg_id; + u16 bits_per_sample; + u16 reserved; +} __packed; + +struct asm_data_cmd_write_v2 { + u32 buf_addr_lsw; + u32 buf_addr_msw; + u32 mem_map_handle; + u32 buf_size; + u32 seq_id; + u32 timestamp_lsw; + u32 timestamp_msw; + u32 flags; +} __packed; + +struct asm_stream_cmd_open_write_v3 { + uint32_t mode_flags; + uint16_t sink_endpointype; + uint16_t bits_per_sample; + uint32_t postprocopo_id; + uint32_t dec_fmt_id; +} __packed; + +struct asm_session_cmd_run_v2 { + u32 flags; + u32 time_lsw; + u32 time_msw; +} __packed; + +struct audio_buffer { + phys_addr_t phys; + uint32_t size; /* size of buffer */ +}; + +struct audio_port_data { + struct audio_buffer *buf; + uint32_t num_periods; + uint32_t dsp_buf; + uint32_t mem_map_handle; +}; + +struct q6asm { + struct apr_device *adev; + struct device *dev; + struct q6core_svc_api_info ainfo; + wait_queue_head_t mem_wait; + spinlock_t slock; + struct audio_client *session[MAX_SESSIONS + 1]; +}; + +struct audio_client { + int session; + q6asm_cb cb; + void *priv; + uint32_t io_mode; + struct apr_device *adev; + struct mutex cmd_lock; + spinlock_t lock; + struct kref refcount; + /* idx:1 out port, 0: in port */ + struct audio_port_data port[2]; + wait_queue_head_t cmd_wait; + struct aprv2_ibasic_rsp_result_t result; + int perf_mode; + int stream_id; + struct q6asm *q6asm; + struct device *dev; +}; + +static inline void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, bool cmd_flg, + uint32_t stream_id) +{ + hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD; + hdr->src_port = ((ac->session << 8) & 0xFF00) | (stream_id); + hdr->dest_port = ((ac->session << 8) & 0xFF00) | (stream_id); + hdr->pkt_size = pkt_size; + if (cmd_flg) + hdr->token = ac->session; +} + +static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac, + struct apr_pkt *pkt, uint32_t rsp_opcode) +{ + struct apr_hdr *hdr = &pkt->hdr; + int rc; + + mutex_lock(&ac->cmd_lock); + ac->result.opcode = 0; + ac->result.status = 0; + rc = apr_send_pkt(a->adev, pkt); + if (rc < 0) + goto err; + + if (rsp_opcode) + rc = wait_event_timeout(a->mem_wait, + (ac->result.opcode == hdr->opcode) || + (ac->result.opcode == rsp_opcode), + 5 * HZ); + else + rc = wait_event_timeout(a->mem_wait, + (ac->result.opcode == hdr->opcode), + 5 * HZ); + + if (!rc) { + dev_err(a->dev, "CMD timeout\n"); + rc = -ETIMEDOUT; + } else if (ac->result.status > 0) { + dev_err(a->dev, "DSP returned error[%x]\n", + ac->result.status); + rc = -EINVAL; + } + +err: + mutex_unlock(&ac->cmd_lock); + return rc; +} + +static int __q6asm_memory_unmap(struct audio_client *ac, + phys_addr_t buf_add, int dir) +{ + struct avs_cmd_shared_mem_unmap_regions *mem_unmap; + struct q6asm *a = dev_get_drvdata(ac->dev->parent); + struct apr_pkt *pkt; + int rc, pkt_size; + void *p; + + if (ac->port[dir].mem_map_handle == 0) { + dev_err(ac->dev, "invalid mem handle\n"); + return -EINVAL; + } + + pkt_size = APR_HDR_SIZE + sizeof(*mem_unmap); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + mem_unmap = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.token = ((ac->session << 8) | dir); + + pkt->hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + mem_unmap->mem_map_handle = ac->port[dir].mem_map_handle; + + rc = q6asm_apr_send_session_pkt(a, ac, pkt, 0); + if (rc < 0) { + kfree(pkt); + return rc; + } + + ac->port[dir].mem_map_handle = 0; + + kfree(pkt); + return 0; +} + + +static void q6asm_audio_client_free_buf(struct audio_client *ac, + struct audio_port_data *port) +{ + unsigned long flags; + + spin_lock_irqsave(&ac->lock, flags); + port->num_periods = 0; + kfree(port->buf); + port->buf = NULL; + spin_unlock_irqrestore(&ac->lock, flags); +} + +/** + * q6asm_unmap_memory_regions() - unmap memory regions in the dsp. + * + * @dir: direction of audio stream + * @ac: audio client instanace + * + * Return: Will be an negative value on failure or zero on success + */ +int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + + port = &ac->port[dir]; + if (!port->buf) { + rc = -EINVAL; + goto err; + } + + cnt = port->num_periods - 1; + if (cnt >= 0) { + rc = __q6asm_memory_unmap(ac, port->buf[dir].phys, dir); + if (rc < 0) { + dev_err(ac->dev, "%s: Memory_unmap_regions failed %d\n", + __func__, rc); + goto err; + } + } + + q6asm_audio_client_free_buf(ac, port); + +err: + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions); + +static int __q6asm_memory_map_regions(struct audio_client *ac, int dir, + size_t period_sz, unsigned int periods, + bool is_contiguous) +{ + struct avs_cmd_shared_mem_map_regions *cmd = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct q6asm *a = dev_get_drvdata(ac->dev->parent); + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + struct apr_pkt *pkt; + void *p; + unsigned long flags; + uint32_t num_regions, buf_sz; + int rc, i, pkt_size; + + if (is_contiguous) { + num_regions = 1; + buf_sz = period_sz * periods; + } else { + buf_sz = period_sz; + num_regions = periods; + } + + /* DSP expects size should be aligned to 4K */ + buf_sz = ALIGN(buf_sz, 4096); + + pkt_size = APR_HDR_SIZE + sizeof(*cmd) + + (sizeof(*mregions) * num_regions); + + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + cmd = p + APR_HDR_SIZE; + mregions = p + APR_HDR_SIZE + sizeof(*cmd); + + pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.token = ((ac->session << 8) | dir); + pkt->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + + cmd->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL; + cmd->num_regions = num_regions; + cmd->property_flag = 0x00; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[dir]; + + for (i = 0; i < num_regions; i++) { + ab = &port->buf[i]; + mregions->shm_addr_lsw = lower_32_bits(ab->phys); + mregions->shm_addr_msw = upper_32_bits(ab->phys); + mregions->mem_size_bytes = buf_sz; + ++mregions; + } + spin_unlock_irqrestore(&ac->lock, flags); + + rc = q6asm_apr_send_session_pkt(a, ac, pkt, + ASM_CMDRSP_SHARED_MEM_MAP_REGIONS); + + kfree(pkt); + + return rc; +} + +/** + * q6asm_map_memory_regions() - map memory regions in the dsp. + * + * @dir: direction of audio stream + * @ac: audio client instanace + * @phys: physcial address that needs mapping. + * @period_sz: audio period size + * @periods: number of periods + * + * Return: Will be an negative value on failure or zero on success + */ +int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac, + phys_addr_t phys, + size_t period_sz, unsigned int periods) +{ + struct audio_buffer *buf; + unsigned long flags; + int cnt; + int rc; + + spin_lock_irqsave(&ac->lock, flags); + if (ac->port[dir].buf) { + dev_err(ac->dev, "Buffer already allocated\n"); + spin_unlock_irqrestore(&ac->lock, flags); + return 0; + } + + buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_ATOMIC); + if (!buf) { + spin_unlock_irqrestore(&ac->lock, flags); + return -ENOMEM; + } + + + ac->port[dir].buf = buf; + + buf[0].phys = phys; + buf[0].size = period_sz; + + for (cnt = 1; cnt < periods; cnt++) { + if (period_sz > 0) { + buf[cnt].phys = buf[0].phys + (cnt * period_sz); + buf[cnt].size = period_sz; + } + } + ac->port[dir].num_periods = periods; + + spin_unlock_irqrestore(&ac->lock, flags); + + rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1); + if (rc < 0) { + dev_err(ac->dev, "Memory_map_regions failed\n"); + q6asm_audio_client_free_buf(ac, &ac->port[dir]); + } + + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_map_memory_regions); + +static void q6asm_audio_client_release(struct kref *ref) +{ + struct audio_client *ac; + struct q6asm *a; + unsigned long flags; + + ac = container_of(ref, struct audio_client, refcount); + a = ac->q6asm; + + spin_lock_irqsave(&a->slock, flags); + a->session[ac->session] = NULL; + spin_unlock_irqrestore(&a->slock, flags); + + kfree(ac); +} + +/** + * q6asm_audio_client_free() - Freee allocated audio client + * + * @ac: audio client to free + */ +void q6asm_audio_client_free(struct audio_client *ac) +{ + kref_put(&ac->refcount, q6asm_audio_client_release); +} +EXPORT_SYMBOL_GPL(q6asm_audio_client_free); + +static struct audio_client *q6asm_get_audio_client(struct q6asm *a, + int session_id) +{ + struct audio_client *ac = NULL; + unsigned long flags; + + spin_lock_irqsave(&a->slock, flags); + if ((session_id <= 0) || (session_id > MAX_SESSIONS)) { + dev_err(a->dev, "invalid session: %d\n", session_id); + goto err; + } + + /* check for valid session */ + if (!a->session[session_id]) + goto err; + else if (a->session[session_id]->session != session_id) + goto err; + + ac = a->session[session_id]; + kref_get(&ac->refcount); +err: + spin_unlock_irqrestore(&a->slock, flags); + return ac; +} + +static int32_t q6asm_stream_callback(struct apr_device *adev, + struct apr_resp_pkt *data, + int session_id) +{ + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + struct apr_hdr *hdr = &data->hdr; + struct audio_port_data *port; + struct audio_client *ac; + uint32_t client_event = 0; + int ret = 0; + + ac = q6asm_get_audio_client(q6asm, session_id); + if (!ac)/* Audio client might already be freed by now */ + return 0; + + result = data->payload; + + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: + switch (result->opcode) { + case ASM_SESSION_CMD_PAUSE: + client_event = ASM_CLIENT_EVENT_CMD_PAUSE_DONE; + break; + case ASM_SESSION_CMD_SUSPEND: + client_event = ASM_CLIENT_EVENT_CMD_SUSPEND_DONE; + break; + case ASM_STREAM_CMD_FLUSH: + client_event = ASM_CLIENT_EVENT_CMD_FLUSH_DONE; + break; + case ASM_SESSION_CMD_RUN_V2: + client_event = ASM_CLIENT_EVENT_CMD_RUN_DONE; + break; + case ASM_STREAM_CMD_CLOSE: + client_event = ASM_CLIENT_EVENT_CMD_CLOSE_DONE; + break; + case ASM_STREAM_CMD_FLUSH_READBUFS: + client_event = ASM_CLIENT_EVENT_CMD_OUT_FLUSH_DONE; + break; + case ASM_STREAM_CMD_OPEN_WRITE_V3: + case ASM_STREAM_CMD_OPEN_READ_V3: + case ASM_STREAM_CMD_OPEN_READWRITE_V2: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2: + if (result->status != 0) { + dev_err(ac->dev, + "cmd = 0x%x returned error = 0x%x\n", + result->opcode, result->status); + ac->result = *result; + wake_up(&ac->cmd_wait); + ret = 0; + goto done; + } + break; + default: + dev_err(ac->dev, "command[0x%x] not expecting rsp\n", + result->opcode); + break; + } + + ac->result = *result; + wake_up(&ac->cmd_wait); + + if (ac->cb) + ac->cb(client_event, hdr->token, + data->payload, ac->priv); + + ret = 0; + goto done; + + case ASM_DATA_EVENT_WRITE_DONE_V2: + client_event = ASM_CLIENT_EVENT_DATA_WRITE_DONE; + if (ac->io_mode & ASM_SYNC_IO_MODE) { + phys_addr_t phys; + unsigned long flags; + + spin_lock_irqsave(&ac->lock, flags); + + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + + if (!port->buf) { + spin_unlock_irqrestore(&ac->lock, flags); + ret = 0; + goto done; + } + + phys = port->buf[hdr->token].phys; + + if (lower_32_bits(phys) != result->opcode || + upper_32_bits(phys) != result->status) { + dev_err(ac->dev, "Expected addr %pa\n", + &port->buf[hdr->token].phys); + spin_unlock_irqrestore(&ac->lock, flags); + ret = -EINVAL; + goto done; + } + spin_unlock_irqrestore(&ac->lock, flags); + } + break; + case ASM_DATA_EVENT_READ_DONE_V2: + client_event = ASM_CLIENT_EVENT_DATA_READ_DONE; + if (ac->io_mode & ASM_SYNC_IO_MODE) { + struct asm_data_cmd_read_v2_done *done = data->payload; + unsigned long flags; + phys_addr_t phys; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_CAPTURE]; + if (!port->buf) { + spin_unlock_irqrestore(&ac->lock, flags); + ret = 0; + goto done; + } + + phys = port->buf[hdr->token].phys; + + if (upper_32_bits(phys) != done->buf_addr_msw || + lower_32_bits(phys) != done->buf_addr_lsw) { + dev_err(ac->dev, "Expected addr %pa %08x-%08x\n", + &port->buf[hdr->token].phys, + done->buf_addr_lsw, + done->buf_addr_msw); + spin_unlock_irqrestore(&ac->lock, flags); + ret = -EINVAL; + goto done; + } + spin_unlock_irqrestore(&ac->lock, flags); + } + + break; + case ASM_DATA_EVENT_RENDERED_EOS: + client_event = ASM_CLIENT_EVENT_CMD_EOS_DONE; + break; + } + + if (ac->cb) + ac->cb(client_event, hdr->token, data->payload, ac->priv); + +done: + kref_put(&ac->refcount, q6asm_audio_client_release); + return ret; +} + +static int q6asm_srvc_callback(struct apr_device *adev, + struct apr_resp_pkt *data) +{ + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + struct audio_port_data *port; + struct audio_client *ac = NULL; + struct apr_hdr *hdr = &data->hdr; + struct q6asm *a; + uint32_t sid = 0; + uint32_t dir = 0; + int session_id; + + session_id = (hdr->dest_port >> 8) & 0xFF; + if (session_id) + return q6asm_stream_callback(adev, data, session_id); + + sid = (hdr->token >> 8) & 0x0F; + ac = q6asm_get_audio_client(q6asm, sid); + if (!ac) { + dev_err(&adev->dev, "Audio Client not active\n"); + return 0; + } + + a = dev_get_drvdata(ac->dev->parent); + dir = (hdr->token & 0x0F); + port = &ac->port[dir]; + result = data->payload; + + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: + switch (result->opcode) { + case ASM_CMD_SHARED_MEM_MAP_REGIONS: + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + ac->result = *result; + wake_up(&a->mem_wait); + break; + default: + dev_err(&adev->dev, "command[0x%x] not expecting rsp\n", + result->opcode); + break; + } + goto done; + case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS: + ac->result.status = 0; + ac->result.opcode = hdr->opcode; + port->mem_map_handle = result->opcode; + wake_up(&a->mem_wait); + break; + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + ac->result.opcode = hdr->opcode; + ac->result.status = 0; + port->mem_map_handle = 0; + wake_up(&a->mem_wait); + break; + default: + dev_dbg(&adev->dev, "command[0x%x]success [0x%x]\n", + result->opcode, result->status); + break; + } + + if (ac->cb) + ac->cb(hdr->opcode, hdr->token, data->payload, ac->priv); + +done: + kref_put(&ac->refcount, q6asm_audio_client_release); + + return 0; +} + +/** + * q6asm_get_session_id() - get session id for audio client + * + * @c: audio client pointer + * + * Return: Will be an session id of the audio client. + */ +int q6asm_get_session_id(struct audio_client *c) +{ + return c->session; +} +EXPORT_SYMBOL_GPL(q6asm_get_session_id); + +/** + * q6asm_audio_client_alloc() - Allocate a new audio client + * + * @dev: Pointer to asm child device. + * @cb: event callback. + * @priv: private data associated with this client. + * @stream_id: stream id + * @perf_mode: performace mode for this client + * + * Return: Will be an error pointer on error or a valid audio client + * on success. + */ +struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb, + void *priv, int stream_id, + int perf_mode) +{ + struct q6asm *a = dev_get_drvdata(dev->parent); + struct audio_client *ac; + unsigned long flags; + + ac = q6asm_get_audio_client(a, stream_id + 1); + if (ac) { + dev_err(dev, "Audio Client already active\n"); + return ac; + } + + ac = kzalloc(sizeof(*ac), GFP_KERNEL); + if (!ac) + return ERR_PTR(-ENOMEM); + + spin_lock_irqsave(&a->slock, flags); + a->session[stream_id + 1] = ac; + spin_unlock_irqrestore(&a->slock, flags); + ac->session = stream_id + 1; + ac->cb = cb; + ac->dev = dev; + ac->q6asm = a; + ac->priv = priv; + ac->io_mode = ASM_SYNC_IO_MODE; + ac->perf_mode = perf_mode; + /* DSP expects stream id from 1 */ + ac->stream_id = 1; + ac->adev = a->adev; + kref_init(&ac->refcount); + + init_waitqueue_head(&ac->cmd_wait); + mutex_init(&ac->cmd_lock); + spin_lock_init(&ac->lock); + + return ac; +} +EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc); + +static int q6asm_ac_send_cmd_sync(struct audio_client *ac, struct apr_pkt *pkt) +{ + struct apr_hdr *hdr = &pkt->hdr; + int rc; + + mutex_lock(&ac->cmd_lock); + ac->result.opcode = 0; + ac->result.status = 0; + + rc = apr_send_pkt(ac->adev, pkt); + if (rc < 0) + goto err; + + rc = wait_event_timeout(ac->cmd_wait, + (ac->result.opcode == hdr->opcode), 5 * HZ); + if (!rc) { + dev_err(ac->dev, "CMD timeout\n"); + rc = -ETIMEDOUT; + goto err; + } + + if (ac->result.status > 0) { + dev_err(ac->dev, "DSP returned error[%x]\n", + ac->result.status); + rc = -EINVAL; + } else { + rc = 0; + } + + +err: + mutex_unlock(&ac->cmd_lock); + return rc; +} + +/** + * q6asm_open_write() - Open audio client for writing + * + * @ac: audio client pointer + * @format: audio sample format + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_open_write(struct audio_client *ac, uint32_t format, + uint16_t bits_per_sample) +{ + struct asm_stream_cmd_open_write_v3 *open; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*open); + + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + open = p + APR_HDR_SIZE; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id); + + pkt->hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3; + open->mode_flags = 0x00; + open->mode_flags |= ASM_LEGACY_STREAM_SESSION; + + /* source endpoint : matrix */ + open->sink_endpointype = ASM_END_POINT_DEVICE_MATRIX; + open->bits_per_sample = bits_per_sample; + open->postprocopo_id = ASM_NULL_POPP_TOPOLOGY; + + switch (format) { + case FORMAT_LINEAR_PCM: + open->dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + default: + dev_err(ac->dev, "Invalid format 0x%x\n", format); + rc = -EINVAL; + goto err; + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + if (rc < 0) + goto err; + + ac->io_mode |= ASM_TUN_WRITE_IO_MODE; + +err: + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_open_write); + +static int __q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts, bool wait) +{ + struct asm_session_cmd_run_v2 *run; + struct apr_pkt *pkt; + int pkt_size, rc; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*run); + p = kzalloc(pkt_size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + pkt = p; + run = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id); + + pkt->hdr.opcode = ASM_SESSION_CMD_RUN_V2; + run->flags = flags; + run->time_lsw = lsw_ts; + run->time_msw = msw_ts; + if (wait) { + rc = q6asm_ac_send_cmd_sync(ac, pkt); + } else { + rc = apr_send_pkt(ac->adev, pkt); + if (rc == pkt_size) + rc = 0; + } + + kfree(pkt); + return rc; +} + +/** + * q6asm_run() - start the audio client + * + * @ac: audio client pointer + * @flags: flags associated with write + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + return __q6asm_run(ac, flags, msw_ts, lsw_ts, true); +} +EXPORT_SYMBOL_GPL(q6asm_run); + +/** + * q6asm_run_nowait() - start the audio client withou blocking + * + * @ac: audio client pointer + * @flags: flags associated with write + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + return __q6asm_run(ac, flags, msw_ts, lsw_ts, false); +} +EXPORT_SYMBOL_GPL(q6asm_run_nowait); + +/** + * q6asm_media_format_block_multi_ch_pcm() - setup pcm configuration + * + * @ac: audio client pointer + * @rate: audio sample rate + * @channels: number of audio channels. + * @channel_map: channel map pointer + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels, + u8 channel_map[PCM_MAX_NUM_CHANNEL], + uint16_t bits_per_sample) +{ + struct asm_multi_channel_pcm_fmt_blk_v2 *fmt; + struct apr_pkt *pkt; + u8 *channel_mapping; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*fmt); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + fmt = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id); + + pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk); + fmt->num_channels = channels; + fmt->bits_per_sample = bits_per_sample; + fmt->sample_rate = rate; + fmt->is_signed = 1; + + channel_mapping = fmt->channel_mapping; + + if (channel_map) { + memcpy(channel_mapping, channel_map, PCM_MAX_NUM_CHANNEL); + } else { + if (q6dsp_map_channels(channel_mapping, channels)) { + dev_err(ac->dev, " map channels failed %d\n", channels); + rc = -EINVAL; + goto err; + } + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + +err: + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm); + +/** + * q6asm_enc_cfg_blk_pcm_format_support() - setup pcm configuration for capture + * + * @ac: audio client pointer + * @rate: audio sample rate + * @channels: number of audio channels. + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_enc_cfg_blk_pcm_format_support(struct audio_client *ac, + uint32_t rate, uint32_t channels, uint16_t bits_per_sample) +{ + struct asm_multi_channel_pcm_enc_cfg_v2 *enc_cfg; + struct apr_pkt *pkt; + u8 *channel_mapping; + u32 frames_per_buf = 0; + int pkt_size, rc; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*enc_cfg); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + enc_cfg = p + APR_HDR_SIZE; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id); + + pkt->hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg->encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg->encdec.param_size = sizeof(*enc_cfg) - sizeof(enc_cfg->encdec); + enc_cfg->encblk.frames_per_buf = frames_per_buf; + enc_cfg->encblk.enc_cfg_blk_size = enc_cfg->encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg->num_channels = channels; + enc_cfg->bits_per_sample = bits_per_sample; + enc_cfg->sample_rate = rate; + enc_cfg->is_signed = 1; + channel_mapping = enc_cfg->channel_mapping; + + if (q6dsp_map_channels(channel_mapping, channels)) { + rc = -EINVAL; + goto err; + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); +err: + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_enc_cfg_blk_pcm_format_support); + +/** + * q6asm_read() - read data of period size from audio client + * + * @ac: audio client pointer + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_read(struct audio_client *ac) +{ + struct asm_data_cmd_read_v2 *read; + struct audio_port_data *port; + struct audio_buffer *ab; + struct apr_pkt *pkt; + unsigned long flags; + int pkt_size; + int rc = 0; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*read); + p = kzalloc(pkt_size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + pkt = p; + read = p + APR_HDR_SIZE; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_CAPTURE]; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, ac->stream_id); + ab = &port->buf[port->dsp_buf]; + pkt->hdr.opcode = ASM_DATA_CMD_READ_V2; + read->buf_addr_lsw = lower_32_bits(ab->phys); + read->buf_addr_msw = upper_32_bits(ab->phys); + read->mem_map_handle = port->mem_map_handle; + + read->buf_size = ab->size; + read->seq_id = port->dsp_buf; + pkt->hdr.token = port->dsp_buf; + + port->dsp_buf++; + + if (port->dsp_buf >= port->num_periods) + port->dsp_buf = 0; + + spin_unlock_irqrestore(&ac->lock, flags); + rc = apr_send_pkt(ac->adev, pkt); + if (rc == pkt_size) + rc = 0; + else + pr_err("read op[0x%x]rc[%d]\n", pkt->hdr.opcode, rc); + + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_read); + +static int __q6asm_open_read(struct audio_client *ac, + uint32_t format, uint16_t bits_per_sample) +{ + struct asm_stream_cmd_open_read_v3 *open; + struct apr_pkt *pkt; + int pkt_size, rc; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*open); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + open = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, ac->stream_id); + pkt->hdr.opcode = ASM_STREAM_CMD_OPEN_READ_V3; + /* Stream prio : High, provide meta info with encoded frames */ + open->src_endpointype = ASM_END_POINT_DEVICE_MATRIX; + + open->preprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_NONE; + open->bits_per_sample = bits_per_sample; + open->mode_flags = 0x0; + + open->mode_flags |= ASM_LEGACY_STREAM_SESSION << + ASM_SHIFT_STREAM_PERF_MODE_FLAG_IN_OPEN_READ; + + switch (format) { + case FORMAT_LINEAR_PCM: + open->mode_flags |= 0x00; + open->enc_cfg_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + default: + pr_err("Invalid format[%d]\n", format); + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + + kfree(pkt); + return rc; +} + +/** + * q6asm_open_read() - Open audio client for reading + * + * @ac: audio client pointer + * @format: audio sample format + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_open_read(struct audio_client *ac, uint32_t format, + uint16_t bits_per_sample) +{ + return __q6asm_open_read(ac, format, bits_per_sample); +} +EXPORT_SYMBOL_GPL(q6asm_open_read); + +/** + * q6asm_write_async() - non blocking write + * + * @ac: audio client pointer + * @len: lenght in bytes + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * @wflags: flags associated with write + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_write_async(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t wflags) +{ + struct asm_data_cmd_write_v2 *write; + struct audio_port_data *port; + struct audio_buffer *ab; + unsigned long flags; + struct apr_pkt *pkt; + int pkt_size; + int rc = 0; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*write); + p = kzalloc(pkt_size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + pkt = p; + write = p + APR_HDR_SIZE; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, ac->stream_id); + + ab = &port->buf[port->dsp_buf]; + pkt->hdr.token = port->dsp_buf; + pkt->hdr.opcode = ASM_DATA_CMD_WRITE_V2; + write->buf_addr_lsw = lower_32_bits(ab->phys); + write->buf_addr_msw = upper_32_bits(ab->phys); + write->buf_size = len; + write->seq_id = port->dsp_buf; + write->timestamp_lsw = lsw_ts; + write->timestamp_msw = msw_ts; + write->mem_map_handle = + ac->port[SNDRV_PCM_STREAM_PLAYBACK].mem_map_handle; + + if (wflags == NO_TIMESTAMP) + write->flags = (wflags & 0x800000FF); + else + write->flags = (0x80000000 | wflags); + + port->dsp_buf++; + + if (port->dsp_buf >= port->num_periods) + port->dsp_buf = 0; + + spin_unlock_irqrestore(&ac->lock, flags); + rc = apr_send_pkt(ac->adev, pkt); + if (rc == pkt_size) + rc = 0; + + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_write_async); + +static void q6asm_reset_buf_state(struct audio_client *ac) +{ + struct audio_port_data *port = NULL; + unsigned long flags; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + port->dsp_buf = 0; + port = &ac->port[SNDRV_PCM_STREAM_CAPTURE]; + port->dsp_buf = 0; + spin_unlock_irqrestore(&ac->lock, flags); +} + +static int __q6asm_cmd(struct audio_client *ac, int cmd, bool wait) +{ + int stream_id = ac->stream_id; + struct apr_pkt pkt; + int rc; + + q6asm_add_hdr(ac, &pkt.hdr, APR_HDR_SIZE, true, stream_id); + + switch (cmd) { + case CMD_PAUSE: + pkt.hdr.opcode = ASM_SESSION_CMD_PAUSE; + break; + case CMD_SUSPEND: + pkt.hdr.opcode = ASM_SESSION_CMD_SUSPEND; + break; + case CMD_FLUSH: + pkt.hdr.opcode = ASM_STREAM_CMD_FLUSH; + break; + case CMD_OUT_FLUSH: + pkt.hdr.opcode = ASM_STREAM_CMD_FLUSH_READBUFS; + break; + case CMD_EOS: + pkt.hdr.opcode = ASM_DATA_CMD_EOS; + break; + case CMD_CLOSE: + pkt.hdr.opcode = ASM_STREAM_CMD_CLOSE; + break; + default: + return -EINVAL; + } + + if (wait) + rc = q6asm_ac_send_cmd_sync(ac, &pkt); + else + return apr_send_pkt(ac->adev, &pkt); + + if (rc < 0) + return rc; + + if (cmd == CMD_FLUSH) + q6asm_reset_buf_state(ac); + + return 0; +} + +/** + * q6asm_cmd() - run cmd on audio client + * + * @ac: audio client pointer + * @cmd: command to run on audio client. + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_cmd(struct audio_client *ac, int cmd) +{ + return __q6asm_cmd(ac, cmd, true); +} +EXPORT_SYMBOL_GPL(q6asm_cmd); + +/** + * q6asm_cmd_nowait() - non blocking, run cmd on audio client + * + * @ac: audio client pointer + * @cmd: command to run on audio client. + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_cmd_nowait(struct audio_client *ac, int cmd) +{ + return __q6asm_cmd(ac, cmd, false); +} +EXPORT_SYMBOL_GPL(q6asm_cmd_nowait); + +static int q6asm_probe(struct apr_device *adev) +{ + struct device *dev = &adev->dev; + struct q6asm *q6asm; + + q6asm = devm_kzalloc(dev, sizeof(*q6asm), GFP_KERNEL); + if (!q6asm) + return -ENOMEM; + + q6core_get_svc_api_info(adev->svc_id, &q6asm->ainfo); + + q6asm->dev = dev; + q6asm->adev = adev; + init_waitqueue_head(&q6asm->mem_wait); + spin_lock_init(&q6asm->slock); + dev_set_drvdata(dev, q6asm); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static int q6asm_remove(struct apr_device *adev) +{ + of_platform_depopulate(&adev->dev); + + return 0; +} +static const struct of_device_id q6asm_device_id[] = { + { .compatible = "qcom,q6asm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6asm_device_id); + +static struct apr_driver qcom_q6asm_driver = { + .probe = q6asm_probe, + .remove = q6asm_remove, + .callback = q6asm_srvc_callback, + .driver = { + .name = "qcom-q6asm", + .of_match_table = of_match_ptr(q6asm_device_id), + }, +}; + +module_apr_driver(qcom_q6asm_driver); +MODULE_DESCRIPTION("Q6 Audio Stream Manager driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h new file mode 100644 index 000000000..9f5fb573e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__ +#include "q6dsp-common.h" +#include + +/* ASM client callback events */ +#define CMD_PAUSE 0x0001 +#define ASM_CLIENT_EVENT_CMD_PAUSE_DONE 0x1001 +#define CMD_FLUSH 0x0002 +#define ASM_CLIENT_EVENT_CMD_FLUSH_DONE 0x1002 +#define CMD_EOS 0x0003 +#define ASM_CLIENT_EVENT_CMD_EOS_DONE 0x1003 +#define CMD_CLOSE 0x0004 +#define ASM_CLIENT_EVENT_CMD_CLOSE_DONE 0x1004 +#define CMD_OUT_FLUSH 0x0005 +#define ASM_CLIENT_EVENT_CMD_OUT_FLUSH_DONE 0x1005 +#define CMD_SUSPEND 0x0006 +#define ASM_CLIENT_EVENT_CMD_SUSPEND_DONE 0x1006 +#define ASM_CLIENT_EVENT_CMD_RUN_DONE 0x1008 +#define ASM_CLIENT_EVENT_DATA_WRITE_DONE 0x1009 +#define ASM_CLIENT_EVENT_DATA_READ_DONE 0x100a + +enum { + LEGACY_PCM_MODE = 0, + LOW_LATENCY_PCM_MODE, + ULTRA_LOW_LATENCY_PCM_MODE, + ULL_POST_PROCESSING_PCM_MODE, +}; + +#define MAX_SESSIONS 8 +#define NO_TIMESTAMP 0xFF00 +#define FORMAT_LINEAR_PCM 0x0000 + +typedef void (*q6asm_cb) (uint32_t opcode, uint32_t token, + void *payload, void *priv); +struct audio_client; +struct audio_client *q6asm_audio_client_alloc(struct device *dev, + q6asm_cb cb, void *priv, + int session_id, int perf_mode); +void q6asm_audio_client_free(struct audio_client *ac); +int q6asm_write_async(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); +int q6asm_open_write(struct audio_client *ac, uint32_t format, + uint16_t bits_per_sample); + +int q6asm_open_read(struct audio_client *ac, uint32_t format, + uint16_t bits_per_sample); +int q6asm_enc_cfg_blk_pcm_format_support(struct audio_client *ac, + uint32_t rate, uint32_t channels, uint16_t bits_per_sample); +int q6asm_read(struct audio_client *ac); + +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels, + u8 channel_map[PCM_MAX_NUM_CHANNEL], + uint16_t bits_per_sample); +int q6asm_run(struct audio_client *ac, uint32_t flags, uint32_t msw_ts, + uint32_t lsw_ts); +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, uint32_t msw_ts, + uint32_t lsw_ts); +int q6asm_cmd(struct audio_client *ac, int cmd); +int q6asm_cmd_nowait(struct audio_client *ac, int cmd); +int q6asm_get_session_id(struct audio_client *ac); +int q6asm_map_memory_regions(unsigned int dir, + struct audio_client *ac, + phys_addr_t phys, + size_t bufsz, unsigned int bufcnt); +int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac); +#endif /* __Q6_ASM_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c new file mode 100644 index 000000000..06f03a5fe --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6core.h" +#include "q6dsp-errno.h" + +#define ADSP_STATE_READY_TIMEOUT_MS 3000 +#define Q6_READY_TIMEOUT_MS 100 +#define AVCS_CMD_ADSP_EVENT_GET_STATE 0x0001290C +#define AVCS_CMDRSP_ADSP_EVENT_GET_STATE 0x0001290D +#define AVCS_GET_VERSIONS 0x00012905 +#define AVCS_GET_VERSIONS_RSP 0x00012906 +#define AVCS_CMD_GET_FWK_VERSION 0x001292c +#define AVCS_CMDRSP_GET_FWK_VERSION 0x001292d + +struct avcs_svc_info { + uint32_t service_id; + uint32_t version; +} __packed; + +struct avcs_cmdrsp_get_version { + uint32_t build_id; + uint32_t num_services; + struct avcs_svc_info svc_api_info[]; +} __packed; + +/* for ADSP2.8 and above */ +struct avcs_svc_api_info { + uint32_t service_id; + uint32_t api_version; + uint32_t api_branch_version; +} __packed; + +struct avcs_cmdrsp_get_fwk_version { + uint32_t build_major_version; + uint32_t build_minor_version; + uint32_t build_branch_version; + uint32_t build_subbranch_version; + uint32_t num_services; + struct avcs_svc_api_info svc_api_info[]; +} __packed; + +struct q6core { + struct apr_device *adev; + wait_queue_head_t wait; + uint32_t avcs_state; + struct mutex lock; + bool resp_received; + uint32_t num_services; + struct avcs_cmdrsp_get_fwk_version *fwk_version; + struct avcs_cmdrsp_get_version *svc_version; + bool fwk_version_supported; + bool get_state_supported; + bool get_version_supported; + bool is_version_requested; +}; + +static struct q6core *g_core; + +static int q6core_callback(struct apr_device *adev, struct apr_resp_pkt *data) +{ + struct q6core *core = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + struct apr_hdr *hdr = &data->hdr; + + result = data->payload; + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT:{ + result = data->payload; + switch (result->opcode) { + case AVCS_GET_VERSIONS: + if (result->status == ADSP_EUNSUPPORTED) + core->get_version_supported = false; + core->resp_received = true; + break; + case AVCS_CMD_GET_FWK_VERSION: + if (result->status == ADSP_EUNSUPPORTED) + core->fwk_version_supported = false; + core->resp_received = true; + break; + case AVCS_CMD_ADSP_EVENT_GET_STATE: + if (result->status == ADSP_EUNSUPPORTED) + core->get_state_supported = false; + core->resp_received = true; + break; + } + break; + } + case AVCS_CMDRSP_GET_FWK_VERSION: { + struct avcs_cmdrsp_get_fwk_version *fwk; + int bytes; + + fwk = data->payload; + bytes = sizeof(*fwk) + fwk->num_services * + sizeof(fwk->svc_api_info[0]); + + core->fwk_version = kzalloc(bytes, GFP_ATOMIC); + if (!core->fwk_version) + return -ENOMEM; + + memcpy(core->fwk_version, data->payload, bytes); + + core->fwk_version_supported = true; + core->resp_received = true; + + break; + } + case AVCS_GET_VERSIONS_RSP: { + struct avcs_cmdrsp_get_version *v; + int len; + + v = data->payload; + + len = sizeof(*v) + v->num_services * sizeof(v->svc_api_info[0]); + + core->svc_version = kzalloc(len, GFP_ATOMIC); + if (!core->svc_version) + return -ENOMEM; + + memcpy(core->svc_version, data->payload, len); + + core->get_version_supported = true; + core->resp_received = true; + + break; + } + case AVCS_CMDRSP_ADSP_EVENT_GET_STATE: + core->get_state_supported = true; + core->avcs_state = result->opcode; + + core->resp_received = true; + break; + default: + dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n", + hdr->opcode); + break; + } + + if (core->resp_received) + wake_up(&core->wait); + + return 0; +} + +static int q6core_get_fwk_versions(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_pkt pkt; + int rc; + + pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pkt.hdr.pkt_size = APR_HDR_SIZE; + pkt.hdr.opcode = AVCS_CMD_GET_FWK_VERSION; + + rc = apr_send_pkt(adev, &pkt); + if (rc < 0) + return rc; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + + if (!core->fwk_version_supported) + return -ENOTSUPP; + else + return 0; + } + + + return rc; +} + +static int q6core_get_svc_versions(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_pkt pkt; + int rc; + + pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pkt.hdr.pkt_size = APR_HDR_SIZE; + pkt.hdr.opcode = AVCS_GET_VERSIONS; + + rc = apr_send_pkt(adev, &pkt); + if (rc < 0) + return rc; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + return 0; + } + + return rc; +} + +static bool __q6core_is_adsp_ready(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_pkt pkt; + int rc; + + core->get_state_supported = false; + + pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pkt.hdr.pkt_size = APR_HDR_SIZE; + pkt.hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE; + + rc = apr_send_pkt(adev, &pkt); + if (rc < 0) + return false; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + + if (core->avcs_state) + return true; + } + + /* assume that the adsp is up if we not support this command */ + if (!core->get_state_supported) + return true; + + return false; +} + +/** + * q6core_get_svc_api_info() - Get version number of a service. + * + * @svc_id: service id of the service. + * @ainfo: Valid struct pointer to fill svc api information. + * + * Return: zero on success and error code on failure or unsupported + */ +int q6core_get_svc_api_info(int svc_id, struct q6core_svc_api_info *ainfo) +{ + int i; + int ret = -ENOTSUPP; + + if (!g_core || !ainfo) + return 0; + + mutex_lock(&g_core->lock); + if (!g_core->is_version_requested) { + if (q6core_get_fwk_versions(g_core) == -ENOTSUPP) + q6core_get_svc_versions(g_core); + g_core->is_version_requested = true; + } + + if (g_core->fwk_version_supported) { + for (i = 0; i < g_core->fwk_version->num_services; i++) { + struct avcs_svc_api_info *info; + + info = &g_core->fwk_version->svc_api_info[i]; + if (svc_id != info->service_id) + continue; + + ainfo->api_version = info->api_version; + ainfo->api_branch_version = info->api_branch_version; + ret = 0; + break; + } + } else if (g_core->get_version_supported) { + for (i = 0; i < g_core->svc_version->num_services; i++) { + struct avcs_svc_info *info; + + info = &g_core->svc_version->svc_api_info[i]; + if (svc_id != info->service_id) + continue; + + ainfo->api_version = info->version; + ainfo->api_branch_version = 0; + ret = 0; + break; + } + } + + mutex_unlock(&g_core->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(q6core_get_svc_api_info); + +/** + * q6core_is_adsp_ready() - Get status of adsp + * + * Return: Will be an true if adsp is ready and false if not. + */ +bool q6core_is_adsp_ready(void) +{ + unsigned long timeout; + bool ret = false; + + if (!g_core) + return false; + + mutex_lock(&g_core->lock); + timeout = jiffies + msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS); + for (;;) { + if (__q6core_is_adsp_ready(g_core)) { + ret = true; + break; + } + + if (!time_after(timeout, jiffies)) { + ret = false; + break; + } + } + + mutex_unlock(&g_core->lock); + return ret; +} +EXPORT_SYMBOL_GPL(q6core_is_adsp_ready); + +static int q6core_probe(struct apr_device *adev) +{ + g_core = kzalloc(sizeof(*g_core), GFP_KERNEL); + if (!g_core) + return -ENOMEM; + + dev_set_drvdata(&adev->dev, g_core); + + mutex_init(&g_core->lock); + g_core->adev = adev; + init_waitqueue_head(&g_core->wait); + return 0; +} + +static int q6core_exit(struct apr_device *adev) +{ + struct q6core *core = dev_get_drvdata(&adev->dev); + + if (core->fwk_version_supported) + kfree(core->fwk_version); + if (core->get_version_supported) + kfree(core->svc_version); + + g_core = NULL; + kfree(core); + + return 0; +} + +static const struct of_device_id q6core_device_id[] = { + { .compatible = "qcom,q6core" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6core_device_id); + +static struct apr_driver qcom_q6core_driver = { + .probe = q6core_probe, + .remove = q6core_exit, + .callback = q6core_callback, + .driver = { + .name = "qcom-q6core", + .of_match_table = of_match_ptr(q6core_device_id), + }, +}; + +module_apr_driver(qcom_q6core_driver); +MODULE_DESCRIPTION("q6 core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6core.h b/sound/soc/qcom/qdsp6/q6core.h new file mode 100644 index 000000000..4105b1d73 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6CORE_H__ +#define __Q6CORE_H__ + +struct q6core_svc_api_info { + uint32_t service_id; + uint32_t api_version; + uint32_t api_branch_version; +}; + +bool q6core_is_adsp_ready(void); +int q6core_get_svc_api_info(int svc_id, struct q6core_svc_api_info *ainfo); + +#endif /* __Q6CORE_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.c b/sound/soc/qcom/qdsp6/q6dsp-common.c new file mode 100644 index 000000000..d39300349 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-common.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include "q6dsp-common.h" +#include +#include +#include +#include + +int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch) +{ + memset(ch_map, 0, PCM_MAX_NUM_CHANNEL); + + switch (ch) { + case 1: + ch_map[0] = PCM_CHANNEL_FC; + break; + case 2: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + break; + case 3: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_FC; + break; + case 4: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LS; + ch_map[3] = PCM_CHANNEL_RS; + break; + case 5: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_FC; + ch_map[3] = PCM_CHANNEL_LS; + ch_map[4] = PCM_CHANNEL_RS; + break; + case 6: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LFE; + ch_map[3] = PCM_CHANNEL_FC; + ch_map[4] = PCM_CHANNEL_LS; + ch_map[5] = PCM_CHANNEL_RS; + break; + case 8: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LFE; + ch_map[3] = PCM_CHANNEL_FC; + ch_map[4] = PCM_CHANNEL_LS; + ch_map[5] = PCM_CHANNEL_RS; + ch_map[6] = PCM_CHANNEL_LB; + ch_map[7] = PCM_CHANNEL_RB; + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6dsp_map_channels); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.h b/sound/soc/qcom/qdsp6/q6dsp-common.h new file mode 100644 index 000000000..01094d108 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6DSP_COMMON_H__ +#define __Q6DSP_COMMON_H__ + +#include + +#define PCM_MAX_NUM_CHANNEL 8 +#define PCM_CHANNEL_NULL 0 + +#define PCM_CHANNEL_FL 1 /* Front left channel. */ +#define PCM_CHANNEL_FR 2 /* Front right channel. */ +#define PCM_CHANNEL_FC 3 /* Front center channel. */ +#define PCM_CHANNEL_LS 4 /* Left surround channel. */ +#define PCM_CHANNEL_RS 5 /* Right surround channel. */ +#define PCM_CHANNEL_LFE 6 /* Low frequency effect channel. */ +#define PCM_CHANNEL_CS 7 /* Center surround channel; Rear center ch */ +#define PCM_CHANNEL_LB 8 /* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_RB 9 /* Right back channel; Rear right channel. */ +#define PCM_CHANNELS 10 /* Top surround channel. */ + +int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch); + +#endif /* __Q6DSP_COMMON_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6dsp-errno.h b/sound/soc/qcom/qdsp6/q6dsp-errno.h new file mode 100644 index 000000000..1ec00ff8c --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-errno.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6DSP_ERR_NO_H__ +#define __Q6DSP_ERR_NO_H__ +#include + +/* Success. The operation completed with no errors. */ +#define ADSP_EOK 0x00000000 +/* General failure. */ +#define ADSP_EFAILED 0x00000001 +/* Bad operation parameter. */ +#define ADSP_EBADPARAM 0x00000002 +/* Unsupported routine or operation. */ +#define ADSP_EUNSUPPORTED 0x00000003 +/* Unsupported version. */ +#define ADSP_EVERSION 0x00000004 +/* Unexpected problem encountered. */ +#define ADSP_EUNEXPECTED 0x00000005 +/* Unhandled problem occurred. */ +#define ADSP_EPANIC 0x00000006 +/* Unable to allocate resource. */ +#define ADSP_ENORESOURCE 0x00000007 +/* Invalid handle. */ +#define ADSP_EHANDLE 0x00000008 +/* Operation is already processed. */ +#define ADSP_EALREADY 0x00000009 +/* Operation is not ready to be processed. */ +#define ADSP_ENOTREADY 0x0000000A +/* Operation is pending completion. */ +#define ADSP_EPENDING 0x0000000B +/* Operation could not be accepted or processed. */ +#define ADSP_EBUSY 0x0000000C +/* Operation aborted due to an error. */ +#define ADSP_EABORTED 0x0000000D +/* Operation preempted by a higher priority. */ +#define ADSP_EPREEMPTED 0x0000000E +/* Operation requests intervention to complete. */ +#define ADSP_ECONTINUE 0x0000000F +/* Operation requests immediate intervention to complete. */ +#define ADSP_EIMMEDIATE 0x00000010 +/* Operation is not implemented. */ +#define ADSP_ENOTIMPL 0x00000011 +/* Operation needs more data or resources. */ +#define ADSP_ENEEDMORE 0x00000012 +/* Operation does not have memory. */ +#define ADSP_ENOMEMORY 0x00000014 +/* Item does not exist. */ +#define ADSP_ENOTEXIST 0x00000015 +/* Max count for adsp error code sent to HLOS*/ + +#endif /*__Q6DSP_ERR_NO_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c new file mode 100644 index 000000000..33ec1a744 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -0,0 +1,1045 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6afe.h" +#include "q6asm.h" +#include "q6adm.h" +#include "q6routing.h" + +#define DRV_NAME "q6routing-component" + +#define Q6ROUTING_RX_MIXERS(id) \ + SOC_SINGLE_EXT("MultiMedia1", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia2", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia3", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia4", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia5", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia6", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia7", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia8", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), + +#define Q6ROUTING_RX_DAPM_ROUTE(mix_name, s) \ + { mix_name, "MultiMedia1", "MM_DL1" }, \ + { mix_name, "MultiMedia2", "MM_DL2" }, \ + { mix_name, "MultiMedia3", "MM_DL3" }, \ + { mix_name, "MultiMedia4", "MM_DL4" }, \ + { mix_name, "MultiMedia5", "MM_DL5" }, \ + { mix_name, "MultiMedia6", "MM_DL6" }, \ + { mix_name, "MultiMedia7", "MM_DL7" }, \ + { mix_name, "MultiMedia8", "MM_DL8" }, \ + { s, NULL, mix_name } + +#define Q6ROUTING_TX_DAPM_ROUTE(mix_name) \ + { mix_name, "PRI_MI2S_TX", "PRI_MI2S_TX" }, \ + { mix_name, "SEC_MI2S_TX", "SEC_MI2S_TX" }, \ + { mix_name, "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, \ + { mix_name, "TERT_MI2S_TX", "TERT_MI2S_TX" }, \ + { mix_name, "SLIMBUS_0_TX", "SLIMBUS_0_TX" }, \ + { mix_name, "SLIMBUS_1_TX", "SLIMBUS_1_TX" }, \ + { mix_name, "SLIMBUS_2_TX", "SLIMBUS_2_TX" }, \ + { mix_name, "SLIMBUS_3_TX", "SLIMBUS_3_TX" }, \ + { mix_name, "SLIMBUS_4_TX", "SLIMBUS_4_TX" }, \ + { mix_name, "SLIMBUS_5_TX", "SLIMBUS_5_TX" }, \ + { mix_name, "SLIMBUS_6_TX", "SLIMBUS_6_TX" }, \ + { mix_name, "PRIMARY_TDM_TX_0", "PRIMARY_TDM_TX_0"}, \ + { mix_name, "PRIMARY_TDM_TX_1", "PRIMARY_TDM_TX_1"}, \ + { mix_name, "PRIMARY_TDM_TX_2", "PRIMARY_TDM_TX_2"}, \ + { mix_name, "PRIMARY_TDM_TX_3", "PRIMARY_TDM_TX_3"}, \ + { mix_name, "PRIMARY_TDM_TX_4", "PRIMARY_TDM_TX_4"}, \ + { mix_name, "PRIMARY_TDM_TX_5", "PRIMARY_TDM_TX_5"}, \ + { mix_name, "PRIMARY_TDM_TX_6", "PRIMARY_TDM_TX_6"}, \ + { mix_name, "PRIMARY_TDM_TX_7", "PRIMARY_TDM_TX_7"}, \ + { mix_name, "SEC_TDM_TX_0", "SEC_TDM_TX_0"}, \ + { mix_name, "SEC_TDM_TX_1", "SEC_TDM_TX_1"}, \ + { mix_name, "SEC_TDM_TX_2", "SEC_TDM_TX_2"}, \ + { mix_name, "SEC_TDM_TX_3", "SEC_TDM_TX_3"}, \ + { mix_name, "SEC_TDM_TX_4", "SEC_TDM_TX_4"}, \ + { mix_name, "SEC_TDM_TX_5", "SEC_TDM_TX_5"}, \ + { mix_name, "SEC_TDM_TX_6", "SEC_TDM_TX_6"}, \ + { mix_name, "SEC_TDM_TX_7", "SEC_TDM_TX_7"}, \ + { mix_name, "TERT_TDM_TX_0", "TERT_TDM_TX_0"}, \ + { mix_name, "TERT_TDM_TX_1", "TERT_TDM_TX_1"}, \ + { mix_name, "TERT_TDM_TX_2", "TERT_TDM_TX_2"}, \ + { mix_name, "TERT_TDM_TX_3", "TERT_TDM_TX_3"}, \ + { mix_name, "TERT_TDM_TX_4", "TERT_TDM_TX_4"}, \ + { mix_name, "TERT_TDM_TX_5", "TERT_TDM_TX_5"}, \ + { mix_name, "TERT_TDM_TX_6", "TERT_TDM_TX_6"}, \ + { mix_name, "TERT_TDM_TX_7", "TERT_TDM_TX_7"}, \ + { mix_name, "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, \ + { mix_name, "QUAT_TDM_TX_1", "QUAT_TDM_TX_1"}, \ + { mix_name, "QUAT_TDM_TX_2", "QUAT_TDM_TX_2"}, \ + { mix_name, "QUAT_TDM_TX_3", "QUAT_TDM_TX_3"}, \ + { mix_name, "QUAT_TDM_TX_4", "QUAT_TDM_TX_4"}, \ + { mix_name, "QUAT_TDM_TX_5", "QUAT_TDM_TX_5"}, \ + { mix_name, "QUAT_TDM_TX_6", "QUAT_TDM_TX_6"}, \ + { mix_name, "QUAT_TDM_TX_7", "QUAT_TDM_TX_7"}, \ + { mix_name, "QUIN_TDM_TX_0", "QUIN_TDM_TX_0"}, \ + { mix_name, "QUIN_TDM_TX_1", "QUIN_TDM_TX_1"}, \ + { mix_name, "QUIN_TDM_TX_2", "QUIN_TDM_TX_2"}, \ + { mix_name, "QUIN_TDM_TX_3", "QUIN_TDM_TX_3"}, \ + { mix_name, "QUIN_TDM_TX_4", "QUIN_TDM_TX_4"}, \ + { mix_name, "QUIN_TDM_TX_5", "QUIN_TDM_TX_5"}, \ + { mix_name, "QUIN_TDM_TX_6", "QUIN_TDM_TX_6"}, \ + { mix_name, "QUIN_TDM_TX_7", "QUIN_TDM_TX_7"} + +#define Q6ROUTING_TX_MIXERS(id) \ + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_0_TX", SLIMBUS_0_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_1_TX", SLIMBUS_1_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_2_TX", SLIMBUS_2_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_3_TX", SLIMBUS_3_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_4_TX", SLIMBUS_4_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_5_TX", SLIMBUS_5_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_6_TX", SLIMBUS_6_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_0", PRIMARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_1", PRIMARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_2", PRIMARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_3", PRIMARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_4", PRIMARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_5", PRIMARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_6", PRIMARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_7", PRIMARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_0", SECONDARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_1", SECONDARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_2", SECONDARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_3", SECONDARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_4", SECONDARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_5", SECONDARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_6", SECONDARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_7", SECONDARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_0", TERTIARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_1", TERTIARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_2", TERTIARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_3", TERTIARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_4", TERTIARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_5", TERTIARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_6", TERTIARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_7", TERTIARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_0", QUATERNARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_1", QUATERNARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_2", QUATERNARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_3", QUATERNARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_4", QUATERNARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_5", QUATERNARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_6", QUATERNARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_7", QUATERNARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_0", QUINARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_1", QUINARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_2", QUINARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_3", QUINARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_4", QUINARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_5", QUINARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_6", QUINARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_7", QUINARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), + +struct session_data { + int state; + int port_id; + int path_type; + int app_type; + int acdb_id; + int sample_rate; + int bits_per_sample; + int channels; + int perf_mode; + int numcopps; + int fedai_id; + unsigned long copp_map; + struct q6copp *copps[MAX_COPPS_PER_PORT]; +}; + +struct msm_routing_data { + struct session_data sessions[MAX_SESSIONS]; + struct session_data port_data[AFE_MAX_PORTS]; + struct device *dev; + struct mutex lock; +}; + +static struct msm_routing_data *routing_data; + +/** + * q6routing_stream_open() - Register a new stream for route setup + * + * @fedai_id: Frontend dai id. + * @perf_mode: Performance mode. + * @stream_id: ASM stream id to map. + * @stream_type: Direction of stream + * + * Return: Will be an negative on error or a zero on success. + */ +int q6routing_stream_open(int fedai_id, int perf_mode, + int stream_id, int stream_type) +{ + int j, topology, num_copps = 0; + struct route_payload payload; + struct q6copp *copp; + int copp_idx; + struct session_data *session, *pdata; + + if (!routing_data) { + pr_err("Routing driver not yet ready\n"); + return -EINVAL; + } + + session = &routing_data->sessions[stream_id - 1]; + pdata = &routing_data->port_data[session->port_id]; + + mutex_lock(&routing_data->lock); + session->fedai_id = fedai_id; + + session->path_type = pdata->path_type; + session->sample_rate = pdata->sample_rate; + session->channels = pdata->channels; + session->bits_per_sample = pdata->bits_per_sample; + + payload.num_copps = 0; /* only RX needs to use payload */ + topology = NULL_COPP_TOPOLOGY; + copp = q6adm_open(routing_data->dev, session->port_id, + session->path_type, session->sample_rate, + session->channels, topology, perf_mode, + session->bits_per_sample, 0, 0); + + if (IS_ERR_OR_NULL(copp)) { + mutex_unlock(&routing_data->lock); + return -EINVAL; + } + + copp_idx = q6adm_get_copp_id(copp); + set_bit(copp_idx, &session->copp_map); + session->copps[copp_idx] = copp; + + for_each_set_bit(j, &session->copp_map, MAX_COPPS_PER_PORT) { + payload.port_id[num_copps] = session->port_id; + payload.copp_idx[num_copps] = j; + num_copps++; + } + + if (num_copps) { + payload.num_copps = num_copps; + payload.session_id = stream_id; + q6adm_matrix_map(routing_data->dev, session->path_type, + payload, perf_mode); + } + mutex_unlock(&routing_data->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(q6routing_stream_open); + +static struct session_data *get_session_from_id(struct msm_routing_data *data, + int fedai_id) +{ + int i; + + for (i = 0; i < MAX_SESSIONS; i++) { + if (fedai_id == data->sessions[i].fedai_id) + return &data->sessions[i]; + } + + return NULL; +} +/** + * q6routing_stream_close() - Deregister a stream + * + * @fedai_id: Frontend dai id. + * @stream_type: Direction of stream + * + * Return: Will be an negative on error or a zero on success. + */ +void q6routing_stream_close(int fedai_id, int stream_type) +{ + struct session_data *session; + int idx; + + session = get_session_from_id(routing_data, fedai_id); + if (!session) + return; + + for_each_set_bit(idx, &session->copp_map, MAX_COPPS_PER_PORT) { + if (session->copps[idx]) { + q6adm_close(routing_data->dev, session->copps[idx]); + session->copps[idx] = NULL; + } + } + + session->fedai_id = -1; + session->copp_map = 0; +} +EXPORT_SYMBOL_GPL(q6routing_stream_close); + +static int msm_routing_get_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int session_id = mc->shift; + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct msm_routing_data *priv = dev_get_drvdata(c->dev); + struct session_data *session = &priv->sessions[session_id]; + + if (session->port_id == mc->reg) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct msm_routing_data *data = dev_get_drvdata(c->dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_update *update = NULL; + int be_id = mc->reg; + int session_id = mc->shift; + struct session_data *session = &data->sessions[session_id]; + + if (ucontrol->value.integer.value[0]) { + if (session->port_id == be_id) + return 0; + + session->port_id = be_id; + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 1, update); + } else { + if (session->port_id == -1 || session->port_id != be_id) + return 0; + + session->port_id = -1; + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 0, update); + } + + return 1; +} + +static const struct snd_kcontrol_new hdmi_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(HDMI_RX) }; + +static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_MI2S_RX) }; + +static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_MI2S_RX) }; + +static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_MI2S_RX) }; + +static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_MI2S_RX) }; + +static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_0_RX) }; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_1_RX) }; + +static const struct snd_kcontrol_new slimbus_2_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_2_RX) }; + +static const struct snd_kcontrol_new slimbus_3_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_3_RX) }; + +static const struct snd_kcontrol_new slimbus_4_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_4_RX) }; + +static const struct snd_kcontrol_new slimbus_5_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_5_RX) }; + +static const struct snd_kcontrol_new slimbus_6_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_6_RX) }; + +static const struct snd_kcontrol_new pri_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new pri_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new pri_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new pri_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new pri_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new pri_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new pri_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new pri_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new sec_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new sec_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new sec_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new sec_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new sec_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new sec_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new sec_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new sec_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new tert_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new tert_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new tert_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new tert_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new tert_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new tert_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new tert_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new tert_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new quat_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new quat_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new quat_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new quat_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new quat_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new quat_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new quat_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new quat_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new quin_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new quin_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new quin_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new quin_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new quin_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new quin_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new quin_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new quin_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_7) }; + + +static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) }; + +static const struct snd_kcontrol_new mmul2_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA2) }; + +static const struct snd_kcontrol_new mmul3_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA3) }; + +static const struct snd_kcontrol_new mmul4_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA4) }; + +static const struct snd_kcontrol_new mmul5_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA5) }; + +static const struct snd_kcontrol_new mmul6_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA6) }; + +static const struct snd_kcontrol_new mmul7_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA7) }; + +static const struct snd_kcontrol_new mmul8_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA8) }; + +static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + /* Frontend AIF */ + SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL5", "MultiMedia5 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL6", "MultiMedia6 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL7", "MultiMedia7 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL8", "MultiMedia8 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL2", "MultiMedia2 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL3", "MultiMedia3 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL4", "MultiMedia4 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL5", "MultiMedia5 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL6", "MultiMedia6 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL7", "MultiMedia7 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL8", "MultiMedia8 Capture", 0, 0, 0, 0), + + /* Mixer definitions */ + SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, + hdmi_mixer_controls, + ARRAY_SIZE(hdmi_mixer_controls)), + + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_rx_mixer_controls, + ARRAY_SIZE(slimbus_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, + ARRAY_SIZE(slimbus_1_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_2_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_2_rx_mixer_controls, + ARRAY_SIZE(slimbus_2_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_3_rx_mixer_controls, + ARRAY_SIZE(slimbus_3_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_4_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_4_rx_mixer_controls, + ARRAY_SIZE(slimbus_4_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_5_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_5_rx_mixer_controls, + ARRAY_SIZE(slimbus_5_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_6_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_6_rx_mixer_controls, + ARRAY_SIZE(slimbus_6_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("PRI_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + primary_mi2s_rx_mixer_controls, + ARRAY_SIZE(primary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + secondary_mi2s_rx_mixer_controls, + ARRAY_SIZE(secondary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + quaternary_mi2s_rx_mixer_controls, + ARRAY_SIZE(quaternary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + tertiary_mi2s_rx_mixer_controls, + ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_0_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_1_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_2_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_3_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_4_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_5_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_6_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_7_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("SEC_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_0_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_1_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_2_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_3_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_4_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_5_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_6_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_7_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("TERT_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_0_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_1_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_2_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_3_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_4_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_5_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_6_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_7_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_0_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_1_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_2_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_3_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_4_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_5_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_6_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_7_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_0_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_1_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_2_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_3_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_4_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_5_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_6_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_7_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_7_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, + mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia3 Mixer", SND_SOC_NOPM, 0, 0, + mmul3_mixer_controls, ARRAY_SIZE(mmul3_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia4 Mixer", SND_SOC_NOPM, 0, 0, + mmul4_mixer_controls, ARRAY_SIZE(mmul4_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia5 Mixer", SND_SOC_NOPM, 0, 0, + mmul5_mixer_controls, ARRAY_SIZE(mmul5_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia6 Mixer", SND_SOC_NOPM, 0, 0, + mmul6_mixer_controls, ARRAY_SIZE(mmul6_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia7 Mixer", SND_SOC_NOPM, 0, 0, + mmul7_mixer_controls, ARRAY_SIZE(mmul7_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia8 Mixer", SND_SOC_NOPM, 0, 0, + mmul8_mixer_controls, ARRAY_SIZE(mmul8_mixer_controls)), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + Q6ROUTING_RX_DAPM_ROUTE("HDMI Mixer", "HDMI_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_0_RX Audio Mixer", "SLIMBUS_0_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_1_RX Audio Mixer", "SLIMBUS_1_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_2_RX Audio Mixer", "SLIMBUS_2_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_3_RX Audio Mixer", "SLIMBUS_3_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_4_RX Audio Mixer", "SLIMBUS_4_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_5_RX Audio Mixer", "SLIMBUS_5_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_6_RX Audio Mixer", "SLIMBUS_6_RX"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_MI2S_RX Audio Mixer", "QUAT_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_MI2S_RX Audio Mixer", "TERT_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_MI2S_RX Audio Mixer", "SEC_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("PRI_MI2S_RX Audio Mixer", "PRI_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_0 Audio Mixer", + "PRIMARY_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_1 Audio Mixer", + "PRIMARY_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_2 Audio Mixer", + "PRIMARY_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_3 Audio Mixer", + "PRIMARY_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_4 Audio Mixer", + "PRIMARY_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_5 Audio Mixer", + "PRIMARY_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_6 Audio Mixer", + "PRIMARY_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_7 Audio Mixer", + "PRIMARY_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_0 Audio Mixer", "SEC_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_1 Audio Mixer", "SEC_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_2 Audio Mixer", "SEC_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_3 Audio Mixer", "SEC_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_4 Audio Mixer", "SEC_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_5 Audio Mixer", "SEC_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_6 Audio Mixer", "SEC_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_7 Audio Mixer", "SEC_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_0 Audio Mixer", "TERT_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_1 Audio Mixer", "TERT_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_2 Audio Mixer", "TERT_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_3 Audio Mixer", "TERT_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_4 Audio Mixer", "TERT_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_5 Audio Mixer", "TERT_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_6 Audio Mixer", "TERT_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_7 Audio Mixer", "TERT_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_0 Audio Mixer", "QUAT_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_1 Audio Mixer", "QUAT_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_2 Audio Mixer", "QUAT_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_3 Audio Mixer", "QUAT_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_4 Audio Mixer", "QUAT_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_5 Audio Mixer", "QUAT_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_6 Audio Mixer", "QUAT_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_7 Audio Mixer", "QUAT_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_0 Audio Mixer", "QUIN_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_1 Audio Mixer", "QUIN_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_2 Audio Mixer", "QUIN_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_3 Audio Mixer", "QUIN_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_4 Audio Mixer", "QUIN_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_5 Audio Mixer", "QUIN_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_6 Audio Mixer", "QUIN_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_7 Audio Mixer", "QUIN_TDM_RX_7"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia4 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia5 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia6 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia7 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia8 Mixer"), + + {"MM_UL1", NULL, "MultiMedia1 Mixer"}, + {"MM_UL2", NULL, "MultiMedia2 Mixer"}, + {"MM_UL3", NULL, "MultiMedia3 Mixer"}, + {"MM_UL4", NULL, "MultiMedia4 Mixer"}, + {"MM_UL5", NULL, "MultiMedia5 Mixer"}, + {"MM_UL6", NULL, "MultiMedia6 Mixer"}, + {"MM_UL7", NULL, "MultiMedia7 Mixer"}, + {"MM_UL8", NULL, "MultiMedia8 Mixer"}, +}; + +static int routing_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *c = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct msm_routing_data *data = dev_get_drvdata(c->dev); + unsigned int be_id = rtd->cpu_dai->id; + struct session_data *session; + int path_type; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + path_type = ADM_PATH_PLAYBACK; + else + path_type = ADM_PATH_LIVE_REC; + + if (be_id >= AFE_MAX_PORTS) + return -EINVAL; + + session = &data->port_data[be_id]; + + mutex_lock(&data->lock); + + session->path_type = path_type; + session->sample_rate = params_rate(params); + session->channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + session->bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + session->bits_per_sample = 24; + break; + default: + break; + } + + mutex_unlock(&data->lock); + return 0; +} + +static struct snd_pcm_ops q6pcm_routing_ops = { + .hw_params = routing_hw_params, +}; + +static int msm_routing_probe(struct snd_soc_component *c) +{ + int i; + + for (i = 0; i < MAX_SESSIONS; i++) { + routing_data->sessions[i].port_id = -1; + routing_data->sessions[i].fedai_id = -1; + } + + return 0; +} + +static unsigned int q6routing_reg_read(struct snd_soc_component *component, + unsigned int reg) +{ + /* default value */ + return 0; +} + +static int q6routing_reg_write(struct snd_soc_component *component, + unsigned int reg, unsigned int val) +{ + /* dummy */ + return 0; +} + +static const struct snd_soc_component_driver msm_soc_routing_component = { + .ops = &q6pcm_routing_ops, + .probe = msm_routing_probe, + .name = DRV_NAME, + .dapm_widgets = msm_qdsp6_widgets, + .num_dapm_widgets = ARRAY_SIZE(msm_qdsp6_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .read = q6routing_reg_read, + .write = q6routing_reg_write, +}; + +static int q6pcm_routing_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + routing_data = kzalloc(sizeof(*routing_data), GFP_KERNEL); + if (!routing_data) + return -ENOMEM; + + routing_data->dev = dev; + + mutex_init(&routing_data->lock); + dev_set_drvdata(dev, routing_data); + + return devm_snd_soc_register_component(dev, &msm_soc_routing_component, + NULL, 0); +} + +static int q6pcm_routing_remove(struct platform_device *pdev) +{ + kfree(routing_data); + routing_data = NULL; + + return 0; +} + +static const struct of_device_id q6pcm_routing_device_id[] = { + { .compatible = "qcom,q6adm-routing" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6pcm_routing_device_id); + +static struct platform_driver q6pcm_routing_platform_driver = { + .driver = { + .name = "q6routing", + .of_match_table = of_match_ptr(q6pcm_routing_device_id), + }, + .probe = q6pcm_routing_probe, + .remove = q6pcm_routing_remove, +}; +module_platform_driver(q6pcm_routing_platform_driver); + +MODULE_DESCRIPTION("Q6 Routing platform"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6routing.h b/sound/soc/qcom/qdsp6/q6routing.h new file mode 100644 index 000000000..35514e651 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _Q6_PCM_ROUTING_H +#define _Q6_PCM_ROUTING_H + +int q6routing_stream_open(int fedai_id, int perf_mode, + int stream_id, int stream_type); +void q6routing_stream_close(int fedai_id, int stream_type); + +#endif /*_Q6_PCM_ROUTING_H */ diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c new file mode 100644 index 000000000..5fdbfa363 --- /dev/null +++ b/sound/soc/qcom/sdm845.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include "common.h" +#include "qdsp6/q6afe.h" + +#define DEFAULT_SAMPLE_RATE_48K 48000 +#define DEFAULT_MCLK_RATE 24576000 +#define DEFAULT_BCLK_RATE 12288000 + +struct sdm845_snd_data { + struct snd_soc_card *card; + uint32_t pri_mi2s_clk_count; + uint32_t quat_tdm_clk_count; +}; + +static unsigned int tdm_slot_offset[8] = {0, 4, 8, 12, 16, 20, 24, 28}; + +static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + int channels, slot_width; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + slot_width = 32; + break; + default: + dev_err(rtd->dev, "%s: invalid param format 0x%x\n", + __func__, params_format(params)); + return -EINVAL; + } + + channels = params_channels(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3, + 8, slot_width); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", + __func__, ret); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, + channels, tdm_slot_offset); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n", + __func__, ret); + goto end; + } + } else { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0, + 8, slot_width); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", + __func__, ret); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, channels, + tdm_slot_offset, 0, NULL); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n", + __func__, ret); + goto end; + } + } +end: + return ret; +} + +static int sdm845_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + switch (cpu_dai->id) { + case QUATERNARY_TDM_RX_0: + case QUATERNARY_TDM_TX_0: + ret = sdm845_tdm_snd_hw_params(substream, params); + break; + default: + pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); + break; + } + return ret; +} + +static int sdm845_snd_startup(struct snd_pcm_substream *substream) +{ + unsigned int fmt = SND_SOC_DAIFMT_CBS_CFS; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX: + case PRIMARY_MI2S_TX: + if (++(data->pri_mi2s_clk_count) == 1) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_MCLK_1, + DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT, + DEFAULT_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + } + snd_soc_dai_set_fmt(cpu_dai, fmt); + break; + + case QUATERNARY_TDM_RX_0: + case QUATERNARY_TDM_TX_0: + if (++(data->quat_tdm_clk_count) == 1) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT, + DEFAULT_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + } + break; + + default: + pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); + break; + } + return 0; +} + +static void sdm845_snd_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX: + case PRIMARY_MI2S_TX: + if (--(data->pri_mi2s_clk_count) == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_MCLK_1, + 0, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT, + 0, SNDRV_PCM_STREAM_PLAYBACK); + }; + break; + + case QUATERNARY_TDM_RX_0: + case QUATERNARY_TDM_TX_0: + if (--(data->quat_tdm_clk_count) == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT, + 0, SNDRV_PCM_STREAM_PLAYBACK); + } + break; + + default: + pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); + break; + } +} + +static struct snd_soc_ops sdm845_be_ops = { + .hw_params = sdm845_snd_hw_params, + .startup = sdm845_snd_startup, + .shutdown = sdm845_snd_shutdown, +}; + +static int sdm845_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + rate->min = rate->max = DEFAULT_SAMPLE_RATE_48K; + channels->min = channels->max = 2; + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static void sdm845_add_be_ops(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link = card->dai_link; + int i, num_links = card->num_links; + + for (i = 0; i < num_links; i++) { + if (link->no_pcm == 1) { + link->ops = &sdm845_be_ops; + link->be_hw_params_fixup = sdm845_be_hw_params_fixup; + } + link++; + } +} + +static int sdm845_snd_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct sdm845_snd_data *data; + struct device *dev = &pdev->dev; + int ret; + + card = kzalloc(sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + /* Allocate the private data */ + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto data_alloc_fail; + } + + card->dev = dev; + card->owner = THIS_MODULE; + dev_set_drvdata(dev, card); + ret = qcom_snd_parse_of(card); + if (ret) { + dev_err(dev, "Error parsing OF data\n"); + goto parse_dt_fail; + } + + data->card = card; + snd_soc_card_set_drvdata(card, data); + + sdm845_add_be_ops(card); + ret = snd_soc_register_card(card); + if (ret) { + dev_err(dev, "Sound card registration failed\n"); + goto register_card_fail; + } + return ret; + +register_card_fail: + kfree(card->dai_link); +parse_dt_fail: + kfree(data); +data_alloc_fail: + kfree(card); + return ret; +} + +static int sdm845_snd_platform_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = dev_get_drvdata(&pdev->dev); + struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + kfree(card->dai_link); + kfree(data); + kfree(card); + return 0; +} + +static const struct of_device_id sdm845_snd_device_id[] = { + { .compatible = "qcom,sdm845-sndcard" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sdm845_snd_device_id); + +static struct platform_driver sdm845_snd_driver = { + .probe = sdm845_snd_platform_probe, + .remove = sdm845_snd_platform_remove, + .driver = { + .name = "msm-snd-sdm845", + .of_match_table = sdm845_snd_device_id, + }, +}; +module_platform_driver(sdm845_snd_driver); + +MODULE_DESCRIPTION("sdm845 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c new file mode 100644 index 000000000..00a3f4c1b --- /dev/null +++ b/sound/soc/qcom/storm.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * storm.c -- ALSA SoC machine driver for QTi ipq806x-based Storm board + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define STORM_SYSCLK_MULT 4 + +static int storm_ops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_card *card = soc_runtime->card; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int sysclk_freq; + int bitwidth, ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(card->dev, "invalid bit width given: %d\n", bitwidth); + return bitwidth; + } + + /* + * as the CPU DAI is the I2S bus master and no system clock is needed by + * the MAX98357a DAC, simply set the system clock to be a constant + * multiple of the bit clock for the clock divider + */ + sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT; + + ret = snd_soc_dai_set_sysclk(soc_runtime->cpu_dai, 0, sysclk_freq, 0); + if (ret) { + dev_err(card->dev, "error setting sysclk to %u: %d\n", + sysclk_freq, ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops storm_soc_ops = { + .hw_params = storm_ops_hw_params, +}; + +static struct snd_soc_dai_link storm_dai_link = { + .name = "Primary", + .stream_name = "Primary", + .codec_dai_name = "HiFi", + .ops = &storm_soc_ops, +}; + +static int storm_parse_of(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link = card->dai_link; + struct device_node *np = card->dev->of_node; + + dai_link->cpu_of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpu_of_node) { + dev_err(card->dev, "error getting cpu phandle\n"); + return -EINVAL; + } + dai_link->platform_of_node = dai_link->cpu_of_node; + + dai_link->codec_of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codec_of_node) { + dev_err(card->dev, "error getting codec phandle\n"); + return -EINVAL; + } + + return 0; +} + +static int storm_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = &pdev->dev; + card->owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(&pdev->dev, "error parsing card name: %d\n", ret); + return ret; + } + + card->dai_link = &storm_dai_link; + card->num_links = 1; + + ret = storm_parse_of(card); + if (ret) { + dev_err(&pdev->dev, "error resolving dai links: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "error registering soundcard: %d\n", ret); + + return ret; + +} + +#ifdef CONFIG_OF +static const struct of_device_id storm_device_id[] = { + { .compatible = "google,storm-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, storm_device_id); +#endif + +static struct platform_driver storm_platform_driver = { + .driver = { + .name = "storm-audio", + .of_match_table = + of_match_ptr(storm_device_id), + }, + .probe = storm_platform_probe, +}; +module_platform_driver(storm_platform_driver); + +MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig new file mode 100644 index 000000000..957046ac6 --- /dev/null +++ b/sound/soc/rockchip/Kconfig @@ -0,0 +1,78 @@ +config SND_SOC_ROCKCHIP + tristate "ASoC support for Rockchip" + depends on COMPILE_TEST || ARCH_ROCKCHIP + help + Say Y or M if you want to add support for codecs attached to + the Rockchip SoCs' Audio interfaces. You will also need to + select the audio interfaces to support below. + +config SND_SOC_ROCKCHIP_I2S + tristate "Rockchip I2S Device Driver" + depends on CLKDEV_LOOKUP && SND_SOC_ROCKCHIP + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2S driver for + Rockchip I2S device. The device supports upto maximum of + 8 channels each for play and record. + +config SND_SOC_ROCKCHIP_PDM + tristate "Rockchip PDM Controller Driver" + depends on CLKDEV_LOOKUP && SND_SOC_ROCKCHIP + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for PDM driver for + Rockchip PDM Controller. The Controller supports up to maximum of + 8 channels record. + +config SND_SOC_ROCKCHIP_SPDIF + tristate "Rockchip SPDIF Device Driver" + depends on CLKDEV_LOOKUP && SND_SOC_ROCKCHIP + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for SPDIF driver for + Rockchip SPDIF transceiver device. + +config SND_SOC_ROCKCHIP_MAX98090 + tristate "ASoC support for Rockchip boards using a MAX98090 codec" + depends on SND_SOC_ROCKCHIP && I2C && GPIOLIB && CLKDEV_LOOKUP + select SND_SOC_ROCKCHIP_I2S + select SND_SOC_MAX98090 + select SND_SOC_TS3A227E + help + Say Y or M here if you want to add support for SoC audio on Rockchip + boards using the MAX98090 codec, such as Veyron. + +config SND_SOC_ROCKCHIP_RT5645 + tristate "ASoC support for Rockchip boards using a RT5645/RT5650 codec" + depends on SND_SOC_ROCKCHIP && I2C && GPIOLIB && CLKDEV_LOOKUP + select SND_SOC_ROCKCHIP_I2S + select SND_SOC_RT5645 + help + Say Y or M here if you want to add support for SoC audio on Rockchip + boards using the RT5645/RT5650 codec, such as Veyron. + +config SND_SOC_RK3288_HDMI_ANALOG + tristate "ASoC support multiple codecs for Rockchip RK3288 boards" + depends on SND_SOC_ROCKCHIP && I2C && GPIOLIB && CLKDEV_LOOKUP + select SND_SOC_ROCKCHIP_I2S + select SND_SOC_HDMI_CODEC + select SND_SOC_ES8328_I2C + select SND_SOC_ES8328_SPI if SPI_MASTER + select DRM_DW_HDMI_I2S_AUDIO if DRM_DW_HDMI + help + Say Y or M here if you want to add support for SoC audio on Rockchip + RK3288 boards using an analog output and the built-in HDMI audio. + +config SND_SOC_RK3399_GRU_SOUND + tristate "ASoC support multiple codecs for Rockchip RK3399 GRU boards" + depends on SND_SOC_ROCKCHIP && I2C && GPIOLIB && CLKDEV_LOOKUP && SPI + select SND_SOC_ROCKCHIP_I2S + select SND_SOC_MAX98357A + select SND_SOC_RT5514 + select SND_SOC_DA7219 + select SND_SOC_RT5514_SPI + select SND_SOC_HDMI_CODEC + select SND_SOC_DMIC + help + Say Y or M here if you want to add support multiple codecs for SoC + audio on Rockchip RK3399 GRU boards. diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile new file mode 100644 index 000000000..65e814d46 --- /dev/null +++ b/sound/soc/rockchip/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +# ROCKCHIP Platform Support +snd-soc-rockchip-i2s-objs := rockchip_i2s.o +snd-soc-rockchip-pcm-objs := rockchip_pcm.o +snd-soc-rockchip-pdm-objs := rockchip_pdm.o +snd-soc-rockchip-spdif-objs := rockchip_spdif.o + +obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S) += snd-soc-rockchip-i2s.o snd-soc-rockchip-pcm.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_PDM) += snd-soc-rockchip-pdm.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_SPDIF) += snd-soc-rockchip-spdif.o + +snd-soc-rockchip-max98090-objs := rockchip_max98090.o +snd-soc-rockchip-rt5645-objs := rockchip_rt5645.o +snd-soc-rk3288-hdmi-analog-objs := rk3288_hdmi_analog.o +snd-soc-rk3399-gru-sound-objs := rk3399_gru_sound.o + +obj-$(CONFIG_SND_SOC_ROCKCHIP_MAX98090) += snd-soc-rockchip-max98090.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_RT5645) += snd-soc-rockchip-rt5645.o +obj-$(CONFIG_SND_SOC_RK3288_HDMI_ANALOG) += snd-soc-rk3288-hdmi-analog.o +obj-$(CONFIG_SND_SOC_RK3399_GRU_SOUND) += snd-soc-rk3399-gru-sound.o diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c new file mode 100644 index 000000000..929b3fe28 --- /dev/null +++ b/sound/soc/rockchip/rk3288_hdmi_analog.c @@ -0,0 +1,300 @@ +/* + * Rockchip machine ASoC driver for RK3288 boards that have an HDMI and analog + * audio output + * + * Copyright (c) 2016, Collabora Ltd. + * + * Authors: Sjoerd Simons , + * Romain Perier + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_i2s.h" + +#define DRV_NAME "rk3288-snd-hdmi-analog" + +struct rk_drvdata { + int gpio_hp_en; + int gpio_hp_det; +}; + +static int rk_hp_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct rk_drvdata *machine = snd_soc_card_get_drvdata(w->dapm->card); + + if (!gpio_is_valid(machine->gpio_hp_en)) + return 0; + + gpio_set_value_cansleep(machine->gpio_hp_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static struct snd_soc_jack headphone_jack; +static struct snd_soc_jack_pin headphone_jack_pins[] = { + { + .pin = "Analog", + .mask = SND_JACK_HEADPHONE + }, +}; + +static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { + SND_SOC_DAPM_HP("Analog", rk_hp_power), + SND_SOC_DAPM_LINE("HDMI", NULL), +}; + +static const struct snd_kcontrol_new rk_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Analog"), + SOC_DAPM_PIN_SWITCH("HDMI"), +}; + +static int rk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int mclk; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 192000: + mclk = 24576000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + + if (ret && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, "Can't set cpu clock %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_jack_gpio rk_hp_jack_gpio = { + .name = "Headphone detection", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150 +}; + +static int rk_init(struct snd_soc_pcm_runtime *runtime) +{ + struct rk_drvdata *machine = snd_soc_card_get_drvdata(runtime->card); + + /* Enable Headset Jack detection */ + if (gpio_is_valid(machine->gpio_hp_det)) { + snd_soc_card_jack_new(runtime->card, "Headphone Jack", + SND_JACK_HEADPHONE, &headphone_jack, + headphone_jack_pins, + ARRAY_SIZE(headphone_jack_pins)); + rk_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_jack_add_gpios(&headphone_jack, 1, &rk_hp_jack_gpio); + } + + return 0; +} + +static const struct snd_soc_ops rk_ops = { + .hw_params = rk_hw_params, +}; + +static struct snd_soc_dai_link_component rk_codecs[] = { + { }, + { + .name = "hdmi-audio-codec.2.auto", + .dai_name = "i2s-hifi", + }, +}; + +static struct snd_soc_dai_link rk_dailink = { + .name = "Codecs", + .stream_name = "Audio", + .init = rk_init, + .ops = &rk_ops, + .codecs = rk_codecs, + .num_codecs = ARRAY_SIZE(rk_codecs), + /* Set codecs as slave */ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_card_rk = { + .name = "ROCKCHIP-I2S", + .dai_link = &rk_dailink, + .num_links = 1, + .num_aux_devs = 0, + .dapm_widgets = rk_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets), + .controls = rk_mc_controls, + .num_controls = ARRAY_SIZE(rk_mc_controls), +}; + +static int snd_rk_mc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &snd_soc_card_rk; + struct device_node *np = pdev->dev.of_node; + struct rk_drvdata *machine; + struct of_phandle_args args; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct rk_drvdata), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + + machine->gpio_hp_det = of_get_named_gpio(np, + "rockchip,hp-det-gpios", 0); + if (!gpio_is_valid(machine->gpio_hp_det) && machine->gpio_hp_det != -ENODEV) + return machine->gpio_hp_det; + + machine->gpio_hp_en = of_get_named_gpio(np, + "rockchip,hp-en-gpios", 0); + if (!gpio_is_valid(machine->gpio_hp_en) && machine->gpio_hp_en != -ENODEV) + return machine->gpio_hp_en; + + if (gpio_is_valid(machine->gpio_hp_en)) { + ret = devm_gpio_request_one(&pdev->dev, machine->gpio_hp_en, + GPIOF_OUT_INIT_LOW, "hp_en"); + if (ret) { + dev_err(card->dev, "cannot get hp_en gpio\n"); + return ret; + } + } + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); + if (ret) { + dev_err(card->dev, "SoC parse card name failed %d\n", ret); + return ret; + } + + rk_dailink.codecs[0].of_node = of_parse_phandle(np, + "rockchip,audio-codec", + 0); + if (!rk_dailink.codecs[0].of_node) { + dev_err(&pdev->dev, + "Property 'rockchip,audio-codec' missing or invalid\n"); + return -EINVAL; + } + ret = of_parse_phandle_with_fixed_args(np, "rockchip,audio-codec", + 0, 0, &args); + if (ret) { + dev_err(&pdev->dev, + "Unable to parse property 'rockchip,audio-codec'\n"); + return ret; + } + + ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); + if (ret) { + dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); + return ret; + } + + rk_dailink.cpu_of_node = of_parse_phandle(np, "rockchip,i2s-controller", + 0); + if (!rk_dailink.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'rockchip,i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + rk_dailink.platform_of_node = rk_dailink.cpu_of_node; + + ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); + if (ret) { + dev_err(&pdev->dev, + "Unable to parse 'rockchip,routing' property\n"); + return ret; + } + + snd_soc_card_set_drvdata(card, machine); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (ret) { + dev_err(&pdev->dev, + "Soc register card failed %d\n", ret); + return ret; + } + + return ret; +} + +static const struct of_device_id rockchip_sound_of_match[] = { + { .compatible = "rockchip,rk3288-hdmi-analog", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, rockchip_sound_of_match); + +static struct platform_driver rockchip_sound_driver = { + .probe = snd_rk_mc_probe, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = rockchip_sound_of_match, + }, +}; + +module_platform_driver(rockchip_sound_driver); + +MODULE_AUTHOR("Sjoerd Simons "); +MODULE_DESCRIPTION("Rockchip RK3288 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c new file mode 100644 index 000000000..f2a51ae2b --- /dev/null +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -0,0 +1,576 @@ +/* + * Rockchip machine ASoC driver for boards using MAX98357A/RT5514/DA7219 + * + * Copyright (c) 2016, ROCKCHIP CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rockchip_i2s.h" +#include "../codecs/da7219.h" +#include "../codecs/da7219-aad.h" +#include "../codecs/rt5514.h" + +#define DRV_NAME "rk3399-gru-sound" + +#define SOUND_FS 256 + +static unsigned int dmic_wakeup_delay; + +static struct snd_soc_jack rockchip_sound_jack; + +static const struct snd_soc_dapm_widget rockchip_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_LINE("HDMI", NULL), +}; + +static const struct snd_kcontrol_new rockchip_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("HDMI"), +}; + +static int rockchip_sound_max98357a_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int mclk; + int ret; + + /* max98357a supports these sample rates */ + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + mclk = params_rate(params) * SOUND_FS; + break; + default: + dev_err(rtd->card->dev, "%s() doesn't support this sample rate: %d\n", + __func__, params_rate(params)); + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, mclk, 0); + if (ret) { + dev_err(rtd->card->dev, "%s() error setting sysclk to %u: %d\n", + __func__, mclk, ret); + return ret; + } + + return 0; +} + +static int rockchip_sound_rt5514_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int mclk; + int ret; + + mclk = params_rate(params) * SOUND_FS; + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(rtd->card->dev, "Can't set cpu clock out %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5514_SCLK_S_MCLK, + mclk, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->card->dev, "%s() error setting sysclk to %u: %d\n", + __func__, params_rate(params) * 512, ret); + return ret; + } + + /* Wait for DMIC stable */ + msleep(dmic_wakeup_delay); + + return 0; +} + +static int rockchip_sound_da7219_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int mclk, ret; + + /* in bypass mode, the mclk has to be one of the frequencies below */ + switch (params_rate(params)) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(codec_dai->dev, "Can't set cpu clock out %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Can't set codec clock in %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(codec_dai->dev, "Can't set pll sysclk mclk %d\n", ret); + return ret; + } + + return 0; +} + +static int rockchip_sound_da7219_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dais[0]->component; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + /* We need default MCLK and PLL settings for the accessory detection */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 12288000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Init can't set codec clock in %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(codec_dai->dev, "Init can't set pll sysclk mclk %d\n", ret); + return ret; + } + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_LINEOUT | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &rockchip_sound_jack, NULL, 0); + + if (ret) { + dev_err(rtd->card->dev, "New Headset Jack failed! (%d)\n", ret); + return ret; + } + + snd_jack_set_key( + rockchip_sound_jack.jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key( + rockchip_sound_jack.jack, SND_JACK_BTN_1, KEY_VOLUMEUP); + snd_jack_set_key( + rockchip_sound_jack.jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key( + rockchip_sound_jack.jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + da7219_aad_jack_det(component, &rockchip_sound_jack); + + return 0; +} + +static int rockchip_sound_dmic_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int mclk; + int ret; + + mclk = params_rate(params) * SOUND_FS; + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, mclk, 0); + if (ret) { + dev_err(rtd->card->dev, "%s() error setting sysclk to %u: %d\n", + __func__, mclk, ret); + return ret; + } + + /* Wait for DMIC stable */ + msleep(dmic_wakeup_delay); + + return 0; +} + +static const struct snd_soc_ops rockchip_sound_max98357a_ops = { + .hw_params = rockchip_sound_max98357a_hw_params, +}; + +static const struct snd_soc_ops rockchip_sound_rt5514_ops = { + .hw_params = rockchip_sound_rt5514_hw_params, +}; + +static const struct snd_soc_ops rockchip_sound_da7219_ops = { + .hw_params = rockchip_sound_da7219_hw_params, +}; + +static const struct snd_soc_ops rockchip_sound_dmic_ops = { + .hw_params = rockchip_sound_dmic_hw_params, +}; + +static struct snd_soc_card rockchip_sound_card = { + .name = "rk3399-gru-sound", + .owner = THIS_MODULE, + .dapm_widgets = rockchip_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rockchip_dapm_widgets), + .controls = rockchip_controls, + .num_controls = ARRAY_SIZE(rockchip_controls), +}; + +enum { + DAILINK_CDNDP, + DAILINK_DA7219, + DAILINK_DMIC, + DAILINK_MAX98357A, + DAILINK_RT5514, + DAILINK_RT5514_DSP, +}; + +static const struct snd_soc_dai_link rockchip_dais[] = { + [DAILINK_CDNDP] = { + .name = "DP", + .stream_name = "DP PCM", + .codec_dai_name = "spdif-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, + [DAILINK_DA7219] = { + .name = "DA7219", + .stream_name = "DA7219 PCM", + .codec_dai_name = "da7219-hifi", + .init = rockchip_sound_da7219_init, + .ops = &rockchip_sound_da7219_ops, + /* set da7219 as slave */ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, + [DAILINK_DMIC] = { + .name = "DMIC", + .stream_name = "DMIC PCM", + .codec_dai_name = "dmic-hifi", + .ops = &rockchip_sound_dmic_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, + [DAILINK_MAX98357A] = { + .name = "MAX98357A", + .stream_name = "MAX98357A PCM", + .codec_dai_name = "HiFi", + .ops = &rockchip_sound_max98357a_ops, + /* set max98357a as slave */ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, + [DAILINK_RT5514] = { + .name = "RT5514", + .stream_name = "RT5514 PCM", + .codec_dai_name = "rt5514-aif1", + .ops = &rockchip_sound_rt5514_ops, + /* set rt5514 as slave */ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + }, + /* RT5514 DSP for voice wakeup via spi bus */ + [DAILINK_RT5514_DSP] = { + .name = "RT5514 DSP", + .stream_name = "Wake on Voice", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + }, +}; + +static const struct snd_soc_dapm_route rockchip_sound_cdndp_routes[] = { + /* Output */ + {"HDMI", NULL, "TX"}, +}; + +static const struct snd_soc_dapm_route rockchip_sound_da7219_routes[] = { + /* Output */ + {"Headphones", NULL, "HPL"}, + {"Headphones", NULL, "HPR"}, + + /* Input */ + {"MIC", NULL, "Headset Mic"}, +}; + +static const struct snd_soc_dapm_route rockchip_sound_dmic_routes[] = { + /* Input */ + {"DMic", NULL, "Int Mic"}, +}; + +static const struct snd_soc_dapm_route rockchip_sound_max98357a_routes[] = { + /* Output */ + {"Speakers", NULL, "Speaker"}, +}; + +static const struct snd_soc_dapm_route rockchip_sound_rt5514_routes[] = { + /* Input */ + {"DMIC1L", NULL, "Int Mic"}, + {"DMIC1R", NULL, "Int Mic"}, +}; + +struct rockchip_sound_route { + const struct snd_soc_dapm_route *routes; + int num_routes; +}; + +static const struct rockchip_sound_route rockchip_routes[] = { + [DAILINK_CDNDP] = { + .routes = rockchip_sound_cdndp_routes, + .num_routes = ARRAY_SIZE(rockchip_sound_cdndp_routes), + }, + [DAILINK_DA7219] = { + .routes = rockchip_sound_da7219_routes, + .num_routes = ARRAY_SIZE(rockchip_sound_da7219_routes), + }, + [DAILINK_DMIC] = { + .routes = rockchip_sound_dmic_routes, + .num_routes = ARRAY_SIZE(rockchip_sound_dmic_routes), + }, + [DAILINK_MAX98357A] = { + .routes = rockchip_sound_max98357a_routes, + .num_routes = ARRAY_SIZE(rockchip_sound_max98357a_routes), + }, + [DAILINK_RT5514] = { + .routes = rockchip_sound_rt5514_routes, + .num_routes = ARRAY_SIZE(rockchip_sound_rt5514_routes), + }, + [DAILINK_RT5514_DSP] = {}, +}; + +struct dailink_match_data { + const char *compatible; + struct bus_type *bus_type; +}; + +static const struct dailink_match_data dailink_match[] = { + [DAILINK_CDNDP] = { + .compatible = "rockchip,rk3399-cdn-dp", + }, + [DAILINK_DA7219] = { + .compatible = "dlg,da7219", + }, + [DAILINK_DMIC] = { + .compatible = "dmic-codec", + }, + [DAILINK_MAX98357A] = { + .compatible = "maxim,max98357a", + }, + [DAILINK_RT5514] = { + .compatible = "realtek,rt5514", + .bus_type = &i2c_bus_type, + }, + [DAILINK_RT5514_DSP] = { + .compatible = "realtek,rt5514", + .bus_type = &spi_bus_type, + }, +}; + +static int of_dev_node_match(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int rockchip_sound_codec_node_match(struct device_node *np_codec) +{ + struct device *dev; + int i; + + for (i = 0; i < ARRAY_SIZE(dailink_match); i++) { + if (!of_device_is_compatible(np_codec, + dailink_match[i].compatible)) + continue; + + if (dailink_match[i].bus_type) { + dev = bus_find_device(dailink_match[i].bus_type, NULL, + np_codec, of_dev_node_match); + if (!dev) + continue; + put_device(dev); + } + + return i; + } + return -1; +} + +static int rockchip_sound_of_parse_dais(struct device *dev, + struct snd_soc_card *card) +{ + struct device_node *np_cpu, *np_cpu0, *np_cpu1; + struct device_node *np_codec; + struct snd_soc_dai_link *dai; + struct snd_soc_dapm_route *routes; + int i, index; + int num_routes; + + card->dai_link = devm_kzalloc(dev, sizeof(rockchip_dais), + GFP_KERNEL); + if (!card->dai_link) + return -ENOMEM; + + num_routes = 0; + for (i = 0; i < ARRAY_SIZE(rockchip_routes); i++) + num_routes += rockchip_routes[i].num_routes; + routes = devm_kcalloc(dev, num_routes, sizeof(*routes), + GFP_KERNEL); + if (!routes) + return -ENOMEM; + card->dapm_routes = routes; + + np_cpu0 = of_parse_phandle(dev->of_node, "rockchip,cpu", 0); + np_cpu1 = of_parse_phandle(dev->of_node, "rockchip,cpu", 1); + + card->num_dapm_routes = 0; + card->num_links = 0; + for (i = 0; i < ARRAY_SIZE(rockchip_dais); i++) { + np_codec = of_parse_phandle(dev->of_node, + "rockchip,codec", i); + if (!np_codec) + break; + + if (!of_device_is_available(np_codec)) + continue; + + index = rockchip_sound_codec_node_match(np_codec); + if (index < 0) + continue; + + switch (index) { + case DAILINK_CDNDP: + np_cpu = np_cpu1; + break; + case DAILINK_RT5514_DSP: + np_cpu = np_codec; + break; + default: + np_cpu = np_cpu0; + break; + } + + if (!np_cpu) { + dev_err(dev, "Missing 'rockchip,cpu' for %s\n", + rockchip_dais[index].name); + return -EINVAL; + } + + dai = &card->dai_link[card->num_links++]; + *dai = rockchip_dais[index]; + + if (!dai->codec_name) + dai->codec_of_node = np_codec; + dai->platform_of_node = np_cpu; + dai->cpu_of_node = np_cpu; + + if (card->num_dapm_routes + rockchip_routes[index].num_routes > + num_routes) { + dev_err(dev, "Too many routes\n"); + return -EINVAL; + } + + memcpy(routes + card->num_dapm_routes, + rockchip_routes[index].routes, + rockchip_routes[index].num_routes * sizeof(*routes)); + card->num_dapm_routes += rockchip_routes[index].num_routes; + } + + return 0; +} + +static int rockchip_sound_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &rockchip_sound_card; + int ret; + + ret = rockchip_sound_of_parse_dais(&pdev->dev, card); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to parse dais: %d\n", ret); + return ret; + } + + /* Set DMIC wakeup delay */ + ret = device_property_read_u32(&pdev->dev, "dmic-wakeup-delay-ms", + &dmic_wakeup_delay); + if (ret) { + dmic_wakeup_delay = 0; + dev_dbg(&pdev->dev, + "no optional property 'dmic-wakeup-delay-ms' found, default: no delay\n"); + } + + card->dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct of_device_id rockchip_sound_of_match[] = { + { .compatible = "rockchip,rk3399-gru-sound", }, + {}, +}; + +static struct platform_driver rockchip_sound_driver = { + .probe = rockchip_sound_probe, + .driver = { + .name = DRV_NAME, + .of_match_table = rockchip_sound_of_match, +#ifdef CONFIG_PM + .pm = &snd_soc_pm_ops, +#endif + }, +}; + +module_platform_driver(rockchip_sound_driver); + +MODULE_AUTHOR("Xing Zheng "); +MODULE_DESCRIPTION("Rockchip ASoC Machine Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, rockchip_sound_of_match); diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c new file mode 100644 index 000000000..c6d11a593 --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -0,0 +1,736 @@ +/* sound/soc/rockchip/rockchip_i2s.c + * + * ALSA SoC Audio Layer - Rockchip I2S Controller driver + * + * Copyright (c) 2014 Rockchip Electronics Co. Ltd. + * Author: Jianqun + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_i2s.h" +#include "rockchip_pcm.h" + +#define DRV_NAME "rockchip-i2s" + +struct rk_i2s_pins { + u32 reg_offset; + u32 shift; +}; + +struct rk_i2s_dev { + struct device *dev; + + struct clk *hclk; + struct clk *mclk; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + struct regmap *regmap; + struct regmap *grf; + +/* + * Used to indicate the tx/rx status. + * I2S controller hopes to start the tx and rx together, + * also to stop them when they are both try to stop. +*/ + bool tx_start; + bool rx_start; + bool is_master_mode; + const struct rk_i2s_pins *pins; +}; + +static int i2s_runtime_suspend(struct device *dev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + clk_disable_unprepare(i2s->mclk); + + return 0; +} + +static int i2s_runtime_resume(struct device *dev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->mclk); + if (ret) { + dev_err(i2s->dev, "clock enable failed %d\n", ret); + return ret; + } + + regcache_cache_only(i2s->regmap, false); + regcache_mark_dirty(i2s->regmap); + + ret = regcache_sync(i2s->regmap); + if (ret) + clk_disable_unprepare(i2s->mclk); + + return ret; +} + +static inline struct rk_i2s_dev *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +static void rockchip_snd_txctrl(struct rk_i2s_dev *i2s, int on) +{ + unsigned int val = 0; + int retry = 10; + + if (on) { + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_TDE_ENABLE, I2S_DMACR_TDE_ENABLE); + + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | I2S_XFER_RXS_START, + I2S_XFER_TXS_START | I2S_XFER_RXS_START); + + i2s->tx_start = true; + } else { + i2s->tx_start = false; + + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_TDE_ENABLE, I2S_DMACR_TDE_DISABLE); + + if (!i2s->rx_start) { + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_STOP | + I2S_XFER_RXS_STOP); + + udelay(150); + regmap_update_bits(i2s->regmap, I2S_CLR, + I2S_CLR_TXC | I2S_CLR_RXC, + I2S_CLR_TXC | I2S_CLR_RXC); + + regmap_read(i2s->regmap, I2S_CLR, &val); + + /* Should wait for clear operation to finish */ + while (val) { + regmap_read(i2s->regmap, I2S_CLR, &val); + retry--; + if (!retry) { + dev_warn(i2s->dev, "fail to clear\n"); + break; + } + } + } + } +} + +static void rockchip_snd_rxctrl(struct rk_i2s_dev *i2s, int on) +{ + unsigned int val = 0; + int retry = 10; + + if (on) { + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_RDE_ENABLE, I2S_DMACR_RDE_ENABLE); + + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | I2S_XFER_RXS_START, + I2S_XFER_TXS_START | I2S_XFER_RXS_START); + + i2s->rx_start = true; + } else { + i2s->rx_start = false; + + regmap_update_bits(i2s->regmap, I2S_DMACR, + I2S_DMACR_RDE_ENABLE, I2S_DMACR_RDE_DISABLE); + + if (!i2s->tx_start) { + regmap_update_bits(i2s->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_STOP | + I2S_XFER_RXS_STOP); + + udelay(150); + regmap_update_bits(i2s->regmap, I2S_CLR, + I2S_CLR_TXC | I2S_CLR_RXC, + I2S_CLR_TXC | I2S_CLR_RXC); + + regmap_read(i2s->regmap, I2S_CLR, &val); + + /* Should wait for clear operation to finish */ + while (val) { + regmap_read(i2s->regmap, I2S_CLR, &val); + retry--; + if (!retry) { + dev_warn(i2s->dev, "fail to clear\n"); + break; + } + } + } + } +} + +static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct rk_i2s_dev *i2s = to_info(cpu_dai); + unsigned int mask = 0, val = 0; + int ret = 0; + + pm_runtime_get_sync(cpu_dai->dev); + mask = I2S_CKR_MSS_MASK; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Set source clock in Master mode */ + val = I2S_CKR_MSS_MASTER; + i2s->is_master_mode = true; + break; + case SND_SOC_DAIFMT_CBM_CFM: + val = I2S_CKR_MSS_SLAVE; + i2s->is_master_mode = false; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s->regmap, I2S_CKR, mask, val); + + mask = I2S_CKR_CKP_MASK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = I2S_CKR_CKP_NEG; + break; + case SND_SOC_DAIFMT_IB_NF: + val = I2S_CKR_CKP_POS; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s->regmap, I2S_CKR, mask, val); + + mask = I2S_TXCR_IBM_MASK | I2S_TXCR_TFS_MASK | I2S_TXCR_PBM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_TXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_TXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_TXCR_IBM_NORMAL; + break; + case SND_SOC_DAIFMT_DSP_A: /* PCM delay 1 bit mode */ + val = I2S_TXCR_TFS_PCM | I2S_TXCR_PBM_MODE(1); + break; + case SND_SOC_DAIFMT_DSP_B: /* PCM no delay mode */ + val = I2S_TXCR_TFS_PCM; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s->regmap, I2S_TXCR, mask, val); + + mask = I2S_RXCR_IBM_MASK | I2S_RXCR_TFS_MASK | I2S_RXCR_PBM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_RXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_RXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_RXCR_IBM_NORMAL; + break; + case SND_SOC_DAIFMT_DSP_A: /* PCM delay 1 bit mode */ + val = I2S_RXCR_TFS_PCM | I2S_RXCR_PBM_MODE(1); + break; + case SND_SOC_DAIFMT_DSP_B: /* PCM no delay mode */ + val = I2S_RXCR_TFS_PCM; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s->regmap, I2S_RXCR, mask, val); + +err_pm_put: + pm_runtime_put(cpu_dai->dev); + + return ret; +} + +static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = to_info(dai); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int val = 0; + unsigned int mclk_rate, bclk_rate, div_bclk, div_lrck; + + if (i2s->is_master_mode) { + mclk_rate = clk_get_rate(i2s->mclk); + bclk_rate = 2 * 32 * params_rate(params); + if (bclk_rate && mclk_rate % bclk_rate) + return -EINVAL; + + div_bclk = mclk_rate / bclk_rate; + div_lrck = bclk_rate / params_rate(params); + regmap_update_bits(i2s->regmap, I2S_CKR, + I2S_CKR_MDIV_MASK, + I2S_CKR_MDIV(div_bclk)); + + regmap_update_bits(i2s->regmap, I2S_CKR, + I2S_CKR_TSD_MASK | + I2S_CKR_RSD_MASK, + I2S_CKR_TSD(div_lrck) | + I2S_CKR_RSD(div_lrck)); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + val |= I2S_TXCR_VDW(8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + val |= I2S_TXCR_VDW(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= I2S_TXCR_VDW(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + val |= I2S_TXCR_VDW(24); + break; + case SNDRV_PCM_FORMAT_S32_LE: + val |= I2S_TXCR_VDW(32); + break; + default: + return -EINVAL; + } + + switch (params_channels(params)) { + case 8: + val |= I2S_CHN_8; + break; + case 6: + val |= I2S_CHN_6; + break; + case 4: + val |= I2S_CHN_4; + break; + case 2: + val |= I2S_CHN_2; + break; + default: + dev_err(i2s->dev, "invalid channel: %d\n", + params_channels(params)); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + regmap_update_bits(i2s->regmap, I2S_RXCR, + I2S_RXCR_VDW_MASK | I2S_RXCR_CSR_MASK, + val); + else + regmap_update_bits(i2s->regmap, I2S_TXCR, + I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, + val); + + if (!IS_ERR(i2s->grf) && i2s->pins) { + regmap_read(i2s->regmap, I2S_TXCR, &val); + val &= I2S_TXCR_CSR_MASK; + + switch (val) { + case I2S_CHN_4: + val = I2S_IO_4CH_OUT_6CH_IN; + break; + case I2S_CHN_6: + val = I2S_IO_6CH_OUT_4CH_IN; + break; + case I2S_CHN_8: + val = I2S_IO_8CH_OUT_2CH_IN; + break; + default: + val = I2S_IO_2CH_OUT_8CH_IN; + break; + } + + val <<= i2s->pins->shift; + val |= (I2S_IO_DIRECTION_MASK << i2s->pins->shift) << 16; + regmap_write(i2s->grf, i2s->pins->reg_offset, val); + } + + regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_TDL_MASK, + I2S_DMACR_TDL(16)); + regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_RDL_MASK, + I2S_DMACR_RDL(16)); + + val = I2S_CKR_TRCM_TXRX; + if (dai->driver->symmetric_rates && rtd->dai_link->symmetric_rates) + val = I2S_CKR_TRCM_TXONLY; + + regmap_update_bits(i2s->regmap, I2S_CKR, + I2S_CKR_TRCM_MASK, + val); + return 0; +} + +static int rockchip_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = to_info(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 1); + else + rockchip_snd_txctrl(i2s, 1); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s, 0); + else + rockchip_snd_txctrl(i2s, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rockchip_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct rk_i2s_dev *i2s = to_info(cpu_dai); + int ret; + + ret = clk_set_rate(i2s->mclk, freq); + if (ret) + dev_err(i2s->dev, "Fail to set mclk %d\n", ret); + + return ret; +} + +static int rockchip_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct rk_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = { + .hw_params = rockchip_i2s_hw_params, + .set_sysclk = rockchip_i2s_set_sysclk, + .set_fmt = rockchip_i2s_set_fmt, + .trigger = rockchip_i2s_trigger, +}; + +static struct snd_soc_dai_driver rockchip_i2s_dai = { + .probe = rockchip_i2s_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .ops = &rockchip_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver rockchip_i2s_component = { + .name = DRV_NAME, +}; + +static bool rockchip_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + case I2S_RXDR: + case I2S_FIFOLR: + case I2S_INTSR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_INTSR: + case I2S_CLR: + case I2S_FIFOLR: + case I2S_TXDR: + case I2S_RXDR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_RXDR: + return true; + default: + return false; + } +} + +static const struct reg_default rockchip_i2s_reg_defaults[] = { + {0x00, 0x0000000f}, + {0x04, 0x0000000f}, + {0x08, 0x00071f1f}, + {0x10, 0x001f0000}, + {0x14, 0x01f00000}, +}; + +static const struct regmap_config rockchip_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = I2S_RXDR, + .reg_defaults = rockchip_i2s_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rockchip_i2s_reg_defaults), + .writeable_reg = rockchip_i2s_wr_reg, + .readable_reg = rockchip_i2s_rd_reg, + .volatile_reg = rockchip_i2s_volatile_reg, + .precious_reg = rockchip_i2s_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static const struct rk_i2s_pins rk3399_i2s_pins = { + .reg_offset = 0xe220, + .shift = 11, +}; + +static const struct of_device_id rockchip_i2s_match[] = { + { .compatible = "rockchip,rk3066-i2s", }, + { .compatible = "rockchip,rk3188-i2s", }, + { .compatible = "rockchip,rk3288-i2s", }, + { .compatible = "rockchip,rk3399-i2s", .data = &rk3399_i2s_pins }, + {}, +}; + +static int rockchip_i2s_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + const struct of_device_id *of_id; + struct rk_i2s_dev *i2s; + struct snd_soc_dai_driver *soc_dai; + struct resource *res; + void __iomem *regs; + int ret; + int val; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->dev = &pdev->dev; + + i2s->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf"); + if (!IS_ERR(i2s->grf)) { + of_id = of_match_device(rockchip_i2s_match, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + i2s->pins = of_id->data; + } + + /* try to prepare related clocks */ + i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk"); + if (IS_ERR(i2s->hclk)) { + dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); + return PTR_ERR(i2s->hclk); + } + ret = clk_prepare_enable(i2s->hclk); + if (ret) { + dev_err(i2s->dev, "hclock enable failed %d\n", ret); + return ret; + } + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); + if (IS_ERR(i2s->mclk)) { + dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); + return PTR_ERR(i2s->mclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &rockchip_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, + "Failed to initialise managed register map\n"); + return PTR_ERR(i2s->regmap); + } + + i2s->playback_dma_data.addr = res->start + I2S_TXDR; + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma_data.maxburst = 4; + + i2s->capture_dma_data.addr = res->start + I2S_RXDR; + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma_data.maxburst = 4; + + dev_set_drvdata(&pdev->dev, i2s); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + soc_dai = devm_kmemdup(&pdev->dev, &rockchip_i2s_dai, + sizeof(*soc_dai), GFP_KERNEL); + if (!soc_dai) { + ret = -ENOMEM; + goto err_pm_disable; + } + + if (!of_property_read_u32(node, "rockchip,playback-channels", &val)) { + if (val >= 2 && val <= 8) + soc_dai->playback.channels_max = val; + } + + if (!of_property_read_u32(node, "rockchip,capture-channels", &val)) { + if (val >= 2 && val <= 8) + soc_dai->capture.channels_max = val; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_component, + soc_dai, 1); + + if (ret) { + dev_err(&pdev->dev, "Could not register DAI\n"); + goto err_suspend; + } + + ret = rockchip_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM\n"); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int rockchip_i2s_remove(struct platform_device *pdev) +{ + struct rk_i2s_dev *i2s = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_runtime_suspend(&pdev->dev); + + clk_disable_unprepare(i2s->hclk); + + return 0; +} + +static const struct dev_pm_ops rockchip_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, + NULL) +}; + +static struct platform_driver rockchip_i2s_driver = { + .probe = rockchip_i2s_probe, + .remove = rockchip_i2s_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(rockchip_i2s_match), + .pm = &rockchip_i2s_pm_ops, + }, +}; +module_platform_driver(rockchip_i2s_driver); + +MODULE_DESCRIPTION("ROCKCHIP IIS ASoC Interface"); +MODULE_AUTHOR("jianqun "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, rockchip_i2s_match); diff --git a/sound/soc/rockchip/rockchip_i2s.h b/sound/soc/rockchip/rockchip_i2s.h new file mode 100644 index 000000000..a7b8527d8 --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s.h @@ -0,0 +1,249 @@ +/* + * sound/soc/rockchip/rockchip_i2s.h + * + * ALSA SoC Audio Layer - Rockchip I2S Controller driver + * + * Copyright (c) 2014 Rockchip Electronics Co. Ltd. + * Author: Jianqun xu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _ROCKCHIP_IIS_H +#define _ROCKCHIP_IIS_H + +/* + * TXCR + * transmit operation control register +*/ +#define I2S_TXCR_RCNT_SHIFT 17 +#define I2S_TXCR_RCNT_MASK (0x3f << I2S_TXCR_RCNT_SHIFT) +#define I2S_TXCR_CSR_SHIFT 15 +#define I2S_TXCR_CSR(x) (x << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_CSR_MASK (3 << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_HWT BIT(14) +#define I2S_TXCR_SJM_SHIFT 12 +#define I2S_TXCR_SJM_R (0 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_SJM_L (1 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_FBM_SHIFT 11 +#define I2S_TXCR_FBM_MSB (0 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_FBM_LSB (1 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_IBM_SHIFT 9 +#define I2S_TXCR_IBM_NORMAL (0 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_LSJM (1 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_RSJM (2 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_MASK (3 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_PBM_SHIFT 7 +#define I2S_TXCR_PBM_MODE(x) (x << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_PBM_MASK (3 << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_TFS_SHIFT 5 +#define I2S_TXCR_TFS_I2S (0 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_PCM (1 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_MASK (1 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_VDW_SHIFT 0 +#define I2S_TXCR_VDW(x) ((x - 1) << I2S_TXCR_VDW_SHIFT) +#define I2S_TXCR_VDW_MASK (0x1f << I2S_TXCR_VDW_SHIFT) + +/* + * RXCR + * receive operation control register +*/ +#define I2S_RXCR_CSR_SHIFT 15 +#define I2S_RXCR_CSR(x) (x << I2S_RXCR_CSR_SHIFT) +#define I2S_RXCR_CSR_MASK (3 << I2S_RXCR_CSR_SHIFT) +#define I2S_RXCR_HWT BIT(14) +#define I2S_RXCR_SJM_SHIFT 12 +#define I2S_RXCR_SJM_R (0 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_SJM_L (1 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_FBM_SHIFT 11 +#define I2S_RXCR_FBM_MSB (0 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_FBM_LSB (1 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_IBM_SHIFT 9 +#define I2S_RXCR_IBM_NORMAL (0 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_LSJM (1 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_RSJM (2 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_MASK (3 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_PBM_SHIFT 7 +#define I2S_RXCR_PBM_MODE(x) (x << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_PBM_MASK (3 << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_TFS_SHIFT 5 +#define I2S_RXCR_TFS_I2S (0 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_PCM (1 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_MASK (1 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_VDW_SHIFT 0 +#define I2S_RXCR_VDW(x) ((x - 1) << I2S_RXCR_VDW_SHIFT) +#define I2S_RXCR_VDW_MASK (0x1f << I2S_RXCR_VDW_SHIFT) + +/* + * CKR + * clock generation register +*/ +#define I2S_CKR_TRCM_SHIFT 28 +#define I2S_CKR_TRCM(x) (x << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXRX (0 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXONLY (1 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_RXONLY (2 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_MASK (3 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_MSS_SHIFT 27 +#define I2S_CKR_MSS_MASTER (0 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_SLAVE (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_MASK (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_CKP_SHIFT 26 +#define I2S_CKR_CKP_NEG (0 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_CKP_POS (1 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_CKP_MASK (1 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_RLP_SHIFT 25 +#define I2S_CKR_RLP_NORMAL (0 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_RLP_OPPSITE (1 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_TLP_SHIFT 24 +#define I2S_CKR_TLP_NORMAL (0 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_TLP_OPPSITE (1 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_MDIV_SHIFT 16 +#define I2S_CKR_MDIV(x) ((x - 1) << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_MDIV_MASK (0xff << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_RSD_SHIFT 8 +#define I2S_CKR_RSD(x) ((x - 1) << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_RSD_MASK (0xff << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_TSD_SHIFT 0 +#define I2S_CKR_TSD(x) ((x - 1) << I2S_CKR_TSD_SHIFT) +#define I2S_CKR_TSD_MASK (0xff << I2S_CKR_TSD_SHIFT) + +/* + * FIFOLR + * FIFO level register +*/ +#define I2S_FIFOLR_RFL_SHIFT 24 +#define I2S_FIFOLR_RFL_MASK (0x3f << I2S_FIFOLR_RFL_SHIFT) +#define I2S_FIFOLR_TFL3_SHIFT 18 +#define I2S_FIFOLR_TFL3_MASK (0x3f << I2S_FIFOLR_TFL3_SHIFT) +#define I2S_FIFOLR_TFL2_SHIFT 12 +#define I2S_FIFOLR_TFL2_MASK (0x3f << I2S_FIFOLR_TFL2_SHIFT) +#define I2S_FIFOLR_TFL1_SHIFT 6 +#define I2S_FIFOLR_TFL1_MASK (0x3f << I2S_FIFOLR_TFL1_SHIFT) +#define I2S_FIFOLR_TFL0_SHIFT 0 +#define I2S_FIFOLR_TFL0_MASK (0x3f << I2S_FIFOLR_TFL0_SHIFT) + +/* + * DMACR + * DMA control register +*/ +#define I2S_DMACR_RDE_SHIFT 24 +#define I2S_DMACR_RDE_DISABLE (0 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDE_ENABLE (1 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDL_SHIFT 16 +#define I2S_DMACR_RDL(x) ((x - 1) << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_RDL_MASK (0x1f << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_TDE_SHIFT 8 +#define I2S_DMACR_TDE_DISABLE (0 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDE_ENABLE (1 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDL_SHIFT 0 +#define I2S_DMACR_TDL(x) ((x) << I2S_DMACR_TDL_SHIFT) +#define I2S_DMACR_TDL_MASK (0x1f << I2S_DMACR_TDL_SHIFT) + +/* + * INTCR + * interrupt control register +*/ +#define I2S_INTCR_RFT_SHIFT 20 +#define I2S_INTCR_RFT(x) ((x - 1) << I2S_INTCR_RFT_SHIFT) +#define I2S_INTCR_RXOIC BIT(18) +#define I2S_INTCR_RXOIE_SHIFT 17 +#define I2S_INTCR_RXOIE_DISABLE (0 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXOIE_ENABLE (1 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXFIE_SHIFT 16 +#define I2S_INTCR_RXFIE_DISABLE (0 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_RXFIE_ENABLE (1 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_TFT_SHIFT 4 +#define I2S_INTCR_TFT(x) ((x - 1) << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TFT_MASK (0x1f << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TXUIC BIT(2) +#define I2S_INTCR_TXUIE_SHIFT 1 +#define I2S_INTCR_TXUIE_DISABLE (0 << I2S_INTCR_TXUIE_SHIFT) +#define I2S_INTCR_TXUIE_ENABLE (1 << I2S_INTCR_TXUIE_SHIFT) + +/* + * INTSR + * interrupt status register +*/ +#define I2S_INTSR_TXEIE_SHIFT 0 +#define I2S_INTSR_TXEIE_DISABLE (0 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_TXEIE_ENABLE (1 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_RXOI_SHIFT 17 +#define I2S_INTSR_RXOI_INA (0 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXOI_ACT (1 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXFI_SHIFT 16 +#define I2S_INTSR_RXFI_INA (0 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_RXFI_ACT (1 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_TXUI_SHIFT 1 +#define I2S_INTSR_TXUI_INA (0 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXUI_ACT (1 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXEI_SHIFT 0 +#define I2S_INTSR_TXEI_INA (0 << I2S_INTSR_TXEI_SHIFT) +#define I2S_INTSR_TXEI_ACT (1 << I2S_INTSR_TXEI_SHIFT) + +/* + * XFER + * Transfer start register +*/ +#define I2S_XFER_RXS_SHIFT 1 +#define I2S_XFER_RXS_STOP (0 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_RXS_START (1 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_TXS_SHIFT 0 +#define I2S_XFER_TXS_STOP (0 << I2S_XFER_TXS_SHIFT) +#define I2S_XFER_TXS_START (1 << I2S_XFER_TXS_SHIFT) + +/* + * CLR + * clear SCLK domain logic register +*/ +#define I2S_CLR_RXC BIT(1) +#define I2S_CLR_TXC BIT(0) + +/* + * TXDR + * Transimt FIFO data register, write only. +*/ +#define I2S_TXDR_MASK (0xff) + +/* + * RXDR + * Receive FIFO data register, write only. +*/ +#define I2S_RXDR_MASK (0xff) + +/* Clock divider id */ +enum { + ROCKCHIP_DIV_MCLK = 0, + ROCKCHIP_DIV_BCLK, +}; + +/* channel select */ +#define I2S_CSR_SHIFT 15 +#define I2S_CHN_2 (0 << I2S_CSR_SHIFT) +#define I2S_CHN_4 (1 << I2S_CSR_SHIFT) +#define I2S_CHN_6 (2 << I2S_CSR_SHIFT) +#define I2S_CHN_8 (3 << I2S_CSR_SHIFT) + +/* I2S REGS */ +#define I2S_TXCR (0x0000) +#define I2S_RXCR (0x0004) +#define I2S_CKR (0x0008) +#define I2S_FIFOLR (0x000c) +#define I2S_DMACR (0x0010) +#define I2S_INTCR (0x0014) +#define I2S_INTSR (0x0018) +#define I2S_XFER (0x001c) +#define I2S_CLR (0x0020) +#define I2S_TXDR (0x0024) +#define I2S_RXDR (0x0028) + +/* io direction cfg register */ +#define I2S_IO_DIRECTION_MASK (7) +#define I2S_IO_8CH_OUT_2CH_IN (0) +#define I2S_IO_6CH_OUT_4CH_IN (4) +#define I2S_IO_4CH_OUT_6CH_IN (6) +#define I2S_IO_2CH_OUT_8CH_IN (7) + +#endif /* _ROCKCHIP_IIS_H */ diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c new file mode 100644 index 000000000..789d6f1e2 --- /dev/null +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -0,0 +1,251 @@ +/* + * Rockchip machine ASoC driver for boards using a MAX90809 CODEC. + * + * Copyright (c) 2014, ROCKCHIP CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_i2s.h" +#include "../codecs/ts3a227e.h" + +#define DRV_NAME "rockchip-snd-max98090" + +static struct snd_soc_jack headset_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin headset_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + +}; + +static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route rk_audio_map[] = { + {"IN34", NULL, "Headset Mic"}, + {"IN34", NULL, "MICBIAS"}, + {"Headset Mic", NULL, "MICBIAS"}, + {"DMICL", NULL, "Int Mic"}, + {"Headphone", NULL, "HPL"}, + {"Headphone", NULL, "HPR"}, + {"Speaker", NULL, "SPKL"}, + {"Speaker", NULL, "SPKR"}, +}; + +static const struct snd_kcontrol_new rk_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int rk_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int mclk; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret); + return ret; + } + + return ret; +} + +static const struct snd_soc_ops rk_aif1_ops = { + .hw_params = rk_aif1_hw_params, +}; + +static struct snd_soc_dai_link rk_dailink = { + .name = "max98090", + .stream_name = "Audio", + .codec_dai_name = "HiFi", + .ops = &rk_aif1_ops, + /* set max98090 as slave */ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static int rk_98090_headset_init(struct snd_soc_component *component); + +static struct snd_soc_aux_dev rk_98090_headset_dev = { + .name = "Headset Chip", + .init = rk_98090_headset_init, +}; + +static struct snd_soc_card snd_soc_card_rk = { + .name = "ROCKCHIP-I2S", + .owner = THIS_MODULE, + .dai_link = &rk_dailink, + .num_links = 1, + .aux_dev = &rk_98090_headset_dev, + .num_aux_devs = 1, + .dapm_widgets = rk_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets), + .dapm_routes = rk_audio_map, + .num_dapm_routes = ARRAY_SIZE(rk_audio_map), + .controls = rk_mc_controls, + .num_controls = ARRAY_SIZE(rk_mc_controls), +}; + +static int rk_98090_headset_init(struct snd_soc_component *component) +{ + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(&snd_soc_card_rk, "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, + headset_jack_pins, + ARRAY_SIZE(headset_jack_pins)); + if (ret) + return ret; + + ret = ts3a227e_enable_jack_detect(component, &headset_jack); + + return ret; +} + +static int snd_rk_mc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &snd_soc_card_rk; + struct device_node *np = pdev->dev.of_node; + + /* register the soc card */ + card->dev = &pdev->dev; + + rk_dailink.codec_of_node = of_parse_phandle(np, + "rockchip,audio-codec", 0); + if (!rk_dailink.codec_of_node) { + dev_err(&pdev->dev, + "Property 'rockchip,audio-codec' missing or invalid\n"); + return -EINVAL; + } + + rk_dailink.cpu_of_node = of_parse_phandle(np, + "rockchip,i2s-controller", 0); + if (!rk_dailink.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'rockchip,i2s-controller' missing or invalid\n"); + return -EINVAL; + } + + rk_dailink.platform_of_node = rk_dailink.cpu_of_node; + + rk_98090_headset_dev.codec_of_node = of_parse_phandle(np, + "rockchip,headset-codec", 0); + if (!rk_98090_headset_dev.codec_of_node) { + dev_err(&pdev->dev, + "Property 'rockchip,headset-codec' missing/invalid\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); + if (ret) { + dev_err(&pdev->dev, + "Soc parse card name failed %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "Soc register card failed %d\n", ret); + return ret; + } + + return ret; +} + +static const struct of_device_id rockchip_max98090_of_match[] = { + { .compatible = "rockchip,rockchip-audio-max98090", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, rockchip_max98090_of_match); + +static struct platform_driver snd_rk_mc_driver = { + .probe = snd_rk_mc_probe, + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = rockchip_max98090_of_match, + }, +}; + +module_platform_driver(snd_rk_mc_driver); + +MODULE_AUTHOR("jianqun "); +MODULE_DESCRIPTION("Rockchip max98090 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/rockchip/rockchip_pcm.c b/sound/soc/rockchip/rockchip_pcm.c new file mode 100644 index 000000000..4ac78d7a4 --- /dev/null +++ b/sound/soc/rockchip/rockchip_pcm.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Rockchip Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "rockchip_pcm.h" + +static const struct snd_pcm_hardware snd_rockchip_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 52, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32, +}; + +static const struct snd_dmaengine_pcm_config rk_dmaengine_pcm_config = { + .pcm_hardware = &snd_rockchip_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prealloc_buffer_size = 32 * 1024, +}; + +int rockchip_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, &rk_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(rockchip_pcm_platform_register); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/rockchip/rockchip_pcm.h b/sound/soc/rockchip/rockchip_pcm.h new file mode 100644 index 000000000..d6c36115c --- /dev/null +++ b/sound/soc/rockchip/rockchip_pcm.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018 Rockchip Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _ROCKCHIP_PCM_H +#define _ROCKCHIP_PCM_H + +int rockchip_pcm_platform_register(struct device *dev); + +#endif diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c new file mode 100644 index 000000000..ad16c8310 --- /dev/null +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -0,0 +1,520 @@ +/* + * Rockchip PDM ALSA SoC Digital Audio Interface(DAI) driver + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_pdm.h" + +#define PDM_DMA_BURST_SIZE (16) /* size * width: 16*4 = 64 bytes */ + +struct rk_pdm_dev { + struct device *dev; + struct clk *clk; + struct clk *hclk; + struct regmap *regmap; + struct snd_dmaengine_dai_dma_data capture_dma_data; +}; + +struct rk_pdm_clkref { + unsigned int sr; + unsigned int clk; +}; + +static struct rk_pdm_clkref clkref[] = { + { 8000, 40960000 }, + { 11025, 56448000 }, + { 12000, 61440000 }, +}; + +static unsigned int get_pdm_clk(unsigned int sr) +{ + unsigned int i, count, clk, div; + + clk = 0; + if (!sr) + return clk; + + count = ARRAY_SIZE(clkref); + for (i = 0; i < count; i++) { + if (sr % clkref[i].sr) + continue; + div = sr / clkref[i].sr; + if ((div & (div - 1)) == 0) { + clk = clkref[i].clk; + break; + } + } + + return clk; +} + +static inline struct rk_pdm_dev *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +static void rockchip_pdm_rxctrl(struct rk_pdm_dev *pdm, int on) +{ + if (on) { + regmap_update_bits(pdm->regmap, PDM_DMA_CTRL, + PDM_DMA_RD_MSK, PDM_DMA_RD_EN); + regmap_update_bits(pdm->regmap, PDM_SYSCONFIG, + PDM_RX_MASK, PDM_RX_START); + } else { + regmap_update_bits(pdm->regmap, PDM_DMA_CTRL, + PDM_DMA_RD_MSK, PDM_DMA_RD_DIS); + regmap_update_bits(pdm->regmap, PDM_SYSCONFIG, + PDM_RX_MASK | PDM_RX_CLR_MASK, + PDM_RX_STOP | PDM_RX_CLR_WR); + } +} + +static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rk_pdm_dev *pdm = to_info(dai); + unsigned int val = 0; + unsigned int clk_rate, clk_div, samplerate; + int ret; + + samplerate = params_rate(params); + clk_rate = get_pdm_clk(samplerate); + if (!clk_rate) + return -EINVAL; + + ret = clk_set_rate(pdm->clk, clk_rate); + if (ret) + return -EINVAL; + + clk_div = DIV_ROUND_CLOSEST(clk_rate, samplerate); + + switch (clk_div) { + case 320: + val = PDM_CLK_320FS; + break; + case 640: + val = PDM_CLK_640FS; + break; + case 1280: + val = PDM_CLK_1280FS; + break; + case 2560: + val = PDM_CLK_2560FS; + break; + case 5120: + val = PDM_CLK_5120FS; + break; + default: + dev_err(pdm->dev, "unsupported div: %d\n", clk_div); + return -EINVAL; + } + + regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_DS_RATIO_MSK, val); + regmap_update_bits(pdm->regmap, PDM_HPF_CTRL, + PDM_HPF_CF_MSK, PDM_HPF_60HZ); + regmap_update_bits(pdm->regmap, PDM_HPF_CTRL, + PDM_HPF_LE | PDM_HPF_RE, PDM_HPF_LE | PDM_HPF_RE); + regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_CLK_EN, PDM_CLK_EN); + + val = 0; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + val |= PDM_VDW(8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + val |= PDM_VDW(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= PDM_VDW(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + val |= PDM_VDW(24); + break; + case SNDRV_PCM_FORMAT_S32_LE: + val |= PDM_VDW(32); + break; + default: + return -EINVAL; + } + + switch (params_channels(params)) { + case 8: + val |= PDM_PATH3_EN; + /* fallthrough */ + case 6: + val |= PDM_PATH2_EN; + /* fallthrough */ + case 4: + val |= PDM_PATH1_EN; + /* fallthrough */ + case 2: + val |= PDM_PATH0_EN; + break; + default: + dev_err(pdm->dev, "invalid channel: %d\n", + params_channels(params)); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + regmap_update_bits(pdm->regmap, PDM_CTRL0, + PDM_PATH_MSK | PDM_VDW_MSK, + val); + regmap_update_bits(pdm->regmap, PDM_DMA_CTRL, PDM_DMA_RDL_MSK, + PDM_DMA_RDL(16)); + regmap_update_bits(pdm->regmap, PDM_SYSCONFIG, + PDM_RX_MASK | PDM_RX_CLR_MASK, + PDM_RX_STOP | PDM_RX_CLR_WR); + } + + return 0; +} + +static int rockchip_pdm_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct rk_pdm_dev *pdm = to_info(cpu_dai); + unsigned int mask = 0, val = 0; + + mask = PDM_CKP_MSK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = PDM_CKP_NORMAL; + break; + case SND_SOC_DAIFMT_IB_NF: + val = PDM_CKP_INVERTED; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(cpu_dai->dev); + regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, mask, val); + pm_runtime_put(cpu_dai->dev); + + return 0; +} + +static int rockchip_pdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct rk_pdm_dev *pdm = to_info(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_pdm_rxctrl(pdm, 1); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_pdm_rxctrl(pdm, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rockchip_pdm_dai_probe(struct snd_soc_dai *dai) +{ + struct rk_pdm_dev *pdm = to_info(dai); + + dai->capture_dma_data = &pdm->capture_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops rockchip_pdm_dai_ops = { + .set_fmt = rockchip_pdm_set_fmt, + .trigger = rockchip_pdm_trigger, + .hw_params = rockchip_pdm_hw_params, +}; + +#define ROCKCHIP_PDM_RATES SNDRV_PCM_RATE_8000_192000 +#define ROCKCHIP_PDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver rockchip_pdm_dai = { + .probe = rockchip_pdm_dai_probe, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = ROCKCHIP_PDM_RATES, + .formats = ROCKCHIP_PDM_FORMATS, + }, + .ops = &rockchip_pdm_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver rockchip_pdm_component = { + .name = "rockchip-pdm", +}; + +static int rockchip_pdm_runtime_suspend(struct device *dev) +{ + struct rk_pdm_dev *pdm = dev_get_drvdata(dev); + + clk_disable_unprepare(pdm->clk); + clk_disable_unprepare(pdm->hclk); + + return 0; +} + +static int rockchip_pdm_runtime_resume(struct device *dev) +{ + struct rk_pdm_dev *pdm = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(pdm->clk); + if (ret) { + dev_err(pdm->dev, "clock enable failed %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(pdm->hclk); + if (ret) { + dev_err(pdm->dev, "hclock enable failed %d\n", ret); + return ret; + } + + return 0; +} + +static bool rockchip_pdm_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PDM_SYSCONFIG: + case PDM_CTRL0: + case PDM_CTRL1: + case PDM_CLK_CTRL: + case PDM_HPF_CTRL: + case PDM_FIFO_CTRL: + case PDM_DMA_CTRL: + case PDM_INT_EN: + case PDM_INT_CLR: + case PDM_DATA_VALID: + return true; + default: + return false; + } +} + +static bool rockchip_pdm_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PDM_SYSCONFIG: + case PDM_CTRL0: + case PDM_CTRL1: + case PDM_CLK_CTRL: + case PDM_HPF_CTRL: + case PDM_FIFO_CTRL: + case PDM_DMA_CTRL: + case PDM_INT_EN: + case PDM_INT_CLR: + case PDM_INT_ST: + case PDM_DATA_VALID: + case PDM_VERSION: + return true; + default: + return false; + } +} + +static bool rockchip_pdm_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PDM_SYSCONFIG: + case PDM_INT_CLR: + case PDM_INT_ST: + return true; + default: + return false; + } +} + +static const struct regmap_config rockchip_pdm_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = PDM_VERSION, + .writeable_reg = rockchip_pdm_wr_reg, + .readable_reg = rockchip_pdm_rd_reg, + .volatile_reg = rockchip_pdm_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int rockchip_pdm_probe(struct platform_device *pdev) +{ + struct rk_pdm_dev *pdm; + struct resource *res; + void __iomem *regs; + int ret; + + pdm = devm_kzalloc(&pdev->dev, sizeof(*pdm), GFP_KERNEL); + if (!pdm) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + pdm->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &rockchip_pdm_regmap_config); + if (IS_ERR(pdm->regmap)) + return PTR_ERR(pdm->regmap); + + pdm->capture_dma_data.addr = res->start + PDM_RXFIFO_DATA; + pdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + pdm->capture_dma_data.maxburst = PDM_DMA_BURST_SIZE; + + pdm->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, pdm); + + pdm->clk = devm_clk_get(&pdev->dev, "pdm_clk"); + if (IS_ERR(pdm->clk)) + return PTR_ERR(pdm->clk); + + pdm->hclk = devm_clk_get(&pdev->dev, "pdm_hclk"); + if (IS_ERR(pdm->hclk)) + return PTR_ERR(pdm->hclk); + + ret = clk_prepare_enable(pdm->hclk); + if (ret) + return ret; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = rockchip_pdm_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_pdm_component, + &rockchip_pdm_dai, 1); + + if (ret) { + dev_err(&pdev->dev, "could not register dai: %d\n", ret); + goto err_suspend; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "could not register pcm: %d\n", ret); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + rockchip_pdm_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + clk_disable_unprepare(pdm->hclk); + + return ret; +} + +static int rockchip_pdm_remove(struct platform_device *pdev) +{ + struct rk_pdm_dev *pdm = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + rockchip_pdm_runtime_suspend(&pdev->dev); + + clk_disable_unprepare(pdm->clk); + clk_disable_unprepare(pdm->hclk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int rockchip_pdm_suspend(struct device *dev) +{ + struct rk_pdm_dev *pdm = dev_get_drvdata(dev); + + regcache_mark_dirty(pdm->regmap); + + return 0; +} + +static int rockchip_pdm_resume(struct device *dev) +{ + struct rk_pdm_dev *pdm = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put(dev); + return ret; + } + + ret = regcache_sync(pdm->regmap); + + pm_runtime_put(dev); + + return ret; +} +#endif + +static const struct dev_pm_ops rockchip_pdm_pm_ops = { + SET_RUNTIME_PM_OPS(rockchip_pdm_runtime_suspend, + rockchip_pdm_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(rockchip_pdm_suspend, rockchip_pdm_resume) +}; + +static const struct of_device_id rockchip_pdm_match[] = { + { .compatible = "rockchip,pdm", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_pdm_match); + +static struct platform_driver rockchip_pdm_driver = { + .probe = rockchip_pdm_probe, + .remove = rockchip_pdm_remove, + .driver = { + .name = "rockchip-pdm", + .of_match_table = of_match_ptr(rockchip_pdm_match), + .pm = &rockchip_pdm_pm_ops, + }, +}; + +module_platform_driver(rockchip_pdm_driver); + +MODULE_AUTHOR("Sugar "); +MODULE_DESCRIPTION("Rockchip PDM Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/rockchip/rockchip_pdm.h b/sound/soc/rockchip/rockchip_pdm.h new file mode 100644 index 000000000..886b48d12 --- /dev/null +++ b/sound/soc/rockchip/rockchip_pdm.h @@ -0,0 +1,83 @@ +/* + * Rockchip PDM ALSA SoC Digital Audio Interface(DAI) driver + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _ROCKCHIP_PDM_H +#define _ROCKCHIP_PDM_H + +/* PDM REGS */ +#define PDM_SYSCONFIG (0x0000) +#define PDM_CTRL0 (0x0004) +#define PDM_CTRL1 (0x0008) +#define PDM_CLK_CTRL (0x000c) +#define PDM_HPF_CTRL (0x0010) +#define PDM_FIFO_CTRL (0x0014) +#define PDM_DMA_CTRL (0x0018) +#define PDM_INT_EN (0x001c) +#define PDM_INT_CLR (0x0020) +#define PDM_INT_ST (0x0024) +#define PDM_RXFIFO_DATA (0x0030) +#define PDM_DATA_VALID (0x0054) +#define PDM_VERSION (0x0058) + +/* PDM_SYSCONFIG */ +#define PDM_RX_MASK (0x1 << 2) +#define PDM_RX_START (0x1 << 2) +#define PDM_RX_STOP (0x0 << 2) +#define PDM_RX_CLR_MASK (0x1 << 0) +#define PDM_RX_CLR_WR (0x1 << 0) +#define PDM_RX_CLR_DONE (0x0 << 0) + +/* PDM CTRL0 */ +#define PDM_PATH_MSK (0xf << 27) +#define PDM_PATH3_EN BIT(30) +#define PDM_PATH2_EN BIT(29) +#define PDM_PATH1_EN BIT(28) +#define PDM_PATH0_EN BIT(27) +#define PDM_HWT_EN BIT(26) +#define PDM_VDW_MSK (0x1f << 0) +#define PDM_VDW(X) ((X - 1) << 0) + +/* PDM CLK CTRL */ +#define PDM_CLK_MSK BIT(5) +#define PDM_CLK_EN BIT(5) +#define PDM_CLK_DIS (0x0 << 5) +#define PDM_CKP_MSK BIT(3) +#define PDM_CKP_NORMAL (0x0 << 3) +#define PDM_CKP_INVERTED BIT(3) +#define PDM_DS_RATIO_MSK (0x7 << 0) +#define PDM_CLK_320FS (0x0 << 0) +#define PDM_CLK_640FS (0x1 << 0) +#define PDM_CLK_1280FS (0x2 << 0) +#define PDM_CLK_2560FS (0x3 << 0) +#define PDM_CLK_5120FS (0x4 << 0) + +/* PDM HPF CTRL */ +#define PDM_HPF_LE BIT(3) +#define PDM_HPF_RE BIT(2) +#define PDM_HPF_CF_MSK (0x3 << 0) +#define PDM_HPF_3P79HZ (0x0 << 0) +#define PDM_HPF_60HZ (0x1 << 0) +#define PDM_HPF_243HZ (0x2 << 0) +#define PDM_HPF_493HZ (0x3 << 0) + +/* PDM DMA CTRL */ +#define PDM_DMA_RD_MSK BIT(8) +#define PDM_DMA_RD_EN BIT(8) +#define PDM_DMA_RD_DIS (0x0 << 8) +#define PDM_DMA_RDL_MSK (0x7f << 0) +#define PDM_DMA_RDL(X) ((X - 1) << 0) + +#endif /* _ROCKCHIP_PDM_H */ diff --git a/sound/soc/rockchip/rockchip_rt5645.c b/sound/soc/rockchip/rockchip_rt5645.c new file mode 100644 index 000000000..881c32498 --- /dev/null +++ b/sound/soc/rockchip/rockchip_rt5645.c @@ -0,0 +1,248 @@ +/* + * Rockchip machine ASoC driver for boards using a RT5645/RT5650 CODEC. + * + * Copyright (c) 2015, ROCKCHIP CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rockchip_i2s.h" +#include "../codecs/rt5645.h" + +#define DRV_NAME "rockchip-snd-rt5645" + +static struct snd_soc_jack headset_jack; + +static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static const struct snd_soc_dapm_route rk_audio_map[] = { + /* Input Lines */ + {"DMIC L2", NULL, "Int Mic"}, + {"DMIC R2", NULL, "Int Mic"}, + {"RECMIXL", NULL, "Headset Mic"}, + {"RECMIXR", NULL, "Headset Mic"}, + + /* Output Lines */ + {"Headphones", NULL, "HPOR"}, + {"Headphones", NULL, "HPOL"}, + {"Speakers", NULL, "SPOL"}, + {"Speakers", NULL, "SPOR"}, +}; + +static const struct snd_kcontrol_new rk_mc_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static int rk_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int mclk; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Can't set codec clock %d\n", ret); + return ret; + } + + return ret; +} + +static int rk_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_card *card = runtime->card; + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, NULL, 0); + if (ret) { + dev_err(card->dev, "New Headset Jack failed! (%d)\n", ret); + return ret; + } + + return rt5645_set_jack_detect(runtime->codec_dai->component, + &headset_jack, + &headset_jack, + &headset_jack); +} + +static const struct snd_soc_ops rk_aif1_ops = { + .hw_params = rk_aif1_hw_params, +}; + +static struct snd_soc_dai_link rk_dailink = { + .name = "rt5645", + .stream_name = "rt5645 PCM", + .codec_dai_name = "rt5645-aif1", + .init = rk_init, + .ops = &rk_aif1_ops, + /* set rt5645 as slave */ + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_card_rk = { + .name = "I2S-RT5650", + .owner = THIS_MODULE, + .dai_link = &rk_dailink, + .num_links = 1, + .dapm_widgets = rk_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rk_dapm_widgets), + .dapm_routes = rk_audio_map, + .num_dapm_routes = ARRAY_SIZE(rk_audio_map), + .controls = rk_mc_controls, + .num_controls = ARRAY_SIZE(rk_mc_controls), +}; + +static int snd_rk_mc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct snd_soc_card *card = &snd_soc_card_rk; + struct device_node *np = pdev->dev.of_node; + + /* register the soc card */ + card->dev = &pdev->dev; + + rk_dailink.codec_of_node = of_parse_phandle(np, + "rockchip,audio-codec", 0); + if (!rk_dailink.codec_of_node) { + dev_err(&pdev->dev, + "Property 'rockchip,audio-codec' missing or invalid\n"); + return -EINVAL; + } + + rk_dailink.cpu_of_node = of_parse_phandle(np, + "rockchip,i2s-controller", 0); + if (!rk_dailink.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'rockchip,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto put_codec_of_node; + } + + rk_dailink.platform_of_node = rk_dailink.cpu_of_node; + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); + if (ret) { + dev_err(&pdev->dev, + "Soc parse card name failed %d\n", ret); + goto put_cpu_of_node; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, + "Soc register card failed %d\n", ret); + goto put_cpu_of_node; + } + + return ret; + +put_cpu_of_node: + of_node_put(rk_dailink.cpu_of_node); + rk_dailink.cpu_of_node = NULL; +put_codec_of_node: + of_node_put(rk_dailink.codec_of_node); + rk_dailink.codec_of_node = NULL; + + return ret; +} + +static int snd_rk_mc_remove(struct platform_device *pdev) +{ + of_node_put(rk_dailink.cpu_of_node); + rk_dailink.cpu_of_node = NULL; + of_node_put(rk_dailink.codec_of_node); + rk_dailink.codec_of_node = NULL; + + return 0; +} + +static const struct of_device_id rockchip_rt5645_of_match[] = { + { .compatible = "rockchip,rockchip-audio-rt5645", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, rockchip_rt5645_of_match); + +static struct platform_driver snd_rk_mc_driver = { + .probe = snd_rk_mc_probe, + .remove = snd_rk_mc_remove, + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = rockchip_rt5645_of_match, + }, +}; + +module_platform_driver(snd_rk_mc_driver); + +MODULE_AUTHOR("Xing Zheng "); +MODULE_DESCRIPTION("Rockchip rt5645 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c new file mode 100644 index 000000000..a89fe9b64 --- /dev/null +++ b/sound/soc/rockchip/rockchip_spdif.c @@ -0,0 +1,421 @@ +/* sound/soc/rockchip/rk_spdif.c + * + * ALSA SoC Audio Layer - Rockchip I2S Controller driver + * + * Copyright (c) 2014 Rockchip Electronics Co. Ltd. + * Author: Jianqun + * Copyright (c) 2015 Collabora Ltd. + * Author: Sjoerd Simons + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_spdif.h" + +enum rk_spdif_type { + RK_SPDIF_RK3066, + RK_SPDIF_RK3188, + RK_SPDIF_RK3288, + RK_SPDIF_RK3366, +}; + +#define RK3288_GRF_SOC_CON2 0x24c + +struct rk_spdif_dev { + struct device *dev; + + struct clk *mclk; + struct clk *hclk; + + struct snd_dmaengine_dai_dma_data playback_dma_data; + + struct regmap *regmap; +}; + +static const struct of_device_id rk_spdif_match[] = { + { .compatible = "rockchip,rk3066-spdif", + .data = (void *)RK_SPDIF_RK3066 }, + { .compatible = "rockchip,rk3188-spdif", + .data = (void *)RK_SPDIF_RK3188 }, + { .compatible = "rockchip,rk3228-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3288-spdif", + .data = (void *)RK_SPDIF_RK3288 }, + { .compatible = "rockchip,rk3328-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3366-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3368-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + { .compatible = "rockchip,rk3399-spdif", + .data = (void *)RK_SPDIF_RK3366 }, + {}, +}; +MODULE_DEVICE_TABLE(of, rk_spdif_match); + +static int __maybe_unused rk_spdif_runtime_suspend(struct device *dev) +{ + struct rk_spdif_dev *spdif = dev_get_drvdata(dev); + + regcache_cache_only(spdif->regmap, true); + clk_disable_unprepare(spdif->mclk); + clk_disable_unprepare(spdif->hclk); + + return 0; +} + +static int __maybe_unused rk_spdif_runtime_resume(struct device *dev) +{ + struct rk_spdif_dev *spdif = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spdif->mclk); + if (ret) { + dev_err(spdif->dev, "mclk clock enable failed %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(spdif->hclk); + if (ret) { + dev_err(spdif->dev, "hclk clock enable failed %d\n", ret); + return ret; + } + + regcache_cache_only(spdif->regmap, false); + regcache_mark_dirty(spdif->regmap); + + ret = regcache_sync(spdif->regmap); + if (ret) { + clk_disable_unprepare(spdif->mclk); + clk_disable_unprepare(spdif->hclk); + } + + return ret; +} + +static int rk_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int val = SPDIF_CFGR_HALFWORD_ENABLE; + int srate, mclk; + int ret; + + srate = params_rate(params); + mclk = srate * 128; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val |= SPDIF_CFGR_VDW_16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= SPDIF_CFGR_VDW_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val |= SPDIF_CFGR_VDW_24; + break; + default: + return -EINVAL; + } + + /* Set clock and calculate divider */ + ret = clk_set_rate(spdif->mclk, mclk); + if (ret != 0) { + dev_err(spdif->dev, "Failed to set module clock rate: %d\n", + ret); + return ret; + } + + ret = regmap_update_bits(spdif->regmap, SPDIF_CFGR, + SPDIF_CFGR_CLK_DIV_MASK | SPDIF_CFGR_HALFWORD_ENABLE | + SDPIF_CFGR_VDW_MASK, + val); + + return ret; +} + +static int rk_spdif_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(spdif->regmap, SPDIF_DMACR, + SPDIF_DMACR_TDE_ENABLE | + SPDIF_DMACR_TDL_MASK, + SPDIF_DMACR_TDE_ENABLE | + SPDIF_DMACR_TDL(16)); + + if (ret != 0) + return ret; + + ret = regmap_update_bits(spdif->regmap, SPDIF_XFER, + SPDIF_XFER_TXS_START, + SPDIF_XFER_TXS_START); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(spdif->regmap, SPDIF_DMACR, + SPDIF_DMACR_TDE_ENABLE, + SPDIF_DMACR_TDE_DISABLE); + + if (ret != 0) + return ret; + + ret = regmap_update_bits(spdif->regmap, SPDIF_XFER, + SPDIF_XFER_TXS_START, + SPDIF_XFER_TXS_STOP); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rk_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + + dai->playback_dma_data = &spdif->playback_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops rk_spdif_dai_ops = { + .hw_params = rk_spdif_hw_params, + .trigger = rk_spdif_trigger, +}; + +static struct snd_soc_dai_driver rk_spdif_dai = { + .probe = rk_spdif_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + }, + .ops = &rk_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver rk_spdif_component = { + .name = "rockchip-spdif", +}; + +static bool rk_spdif_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SPDIF_CFGR: + case SPDIF_DMACR: + case SPDIF_INTCR: + case SPDIF_XFER: + case SPDIF_SMPDR: + return true; + default: + return false; + } +} + +static bool rk_spdif_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SPDIF_CFGR: + case SPDIF_SDBLR: + case SPDIF_INTCR: + case SPDIF_INTSR: + case SPDIF_XFER: + return true; + default: + return false; + } +} + +static bool rk_spdif_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SPDIF_INTSR: + case SPDIF_SDBLR: + return true; + default: + return false; + } +} + +static const struct regmap_config rk_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SPDIF_SMPDR, + .writeable_reg = rk_spdif_wr_reg, + .readable_reg = rk_spdif_rd_reg, + .volatile_reg = rk_spdif_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int rk_spdif_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct rk_spdif_dev *spdif; + const struct of_device_id *match; + struct resource *res; + void __iomem *regs; + int ret; + + match = of_match_node(rk_spdif_match, np); + if (match->data == (void *)RK_SPDIF_RK3288) { + struct regmap *grf; + + grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(grf)) { + dev_err(&pdev->dev, + "rockchip_spdif missing 'rockchip,grf' \n"); + return PTR_ERR(grf); + } + + /* Select the 8 channel SPDIF solution on RK3288 as + * the 2 channel one does not appear to work + */ + regmap_write(grf, RK3288_GRF_SOC_CON2, BIT(1) << 16); + } + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + spdif->hclk = devm_clk_get(&pdev->dev, "hclk"); + if (IS_ERR(spdif->hclk)) { + dev_err(&pdev->dev, "Can't retrieve rk_spdif bus clock\n"); + return PTR_ERR(spdif->hclk); + } + ret = clk_prepare_enable(spdif->hclk); + if (ret) { + dev_err(spdif->dev, "hclock enable failed %d\n", ret); + return ret; + } + + spdif->mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(spdif->mclk)) { + dev_err(&pdev->dev, "Can't retrieve rk_spdif master clock\n"); + ret = PTR_ERR(spdif->mclk); + goto err_disable_hclk; + } + + ret = clk_prepare_enable(spdif->mclk); + if (ret) { + dev_err(spdif->dev, "clock enable failed %d\n", ret); + goto err_disable_clocks; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err_disable_clocks; + } + + spdif->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "hclk", regs, + &rk_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) { + dev_err(&pdev->dev, + "Failed to initialise managed register map\n"); + ret = PTR_ERR(spdif->regmap); + goto err_disable_clocks; + } + + spdif->playback_dma_data.addr = res->start + SPDIF_SMPDR; + spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->playback_dma_data.maxburst = 4; + + spdif->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, spdif); + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_request_idle(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, + &rk_spdif_component, + &rk_spdif_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI\n"); + goto err_pm_runtime; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM\n"); + goto err_pm_runtime; + } + + return 0; + +err_pm_runtime: + pm_runtime_disable(&pdev->dev); +err_disable_clocks: + clk_disable_unprepare(spdif->mclk); +err_disable_hclk: + clk_disable_unprepare(spdif->hclk); + + return ret; +} + +static int rk_spdif_remove(struct platform_device *pdev) +{ + struct rk_spdif_dev *spdif = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + rk_spdif_runtime_suspend(&pdev->dev); + + clk_disable_unprepare(spdif->mclk); + clk_disable_unprepare(spdif->hclk); + + return 0; +} + +static const struct dev_pm_ops rk_spdif_pm_ops = { + SET_RUNTIME_PM_OPS(rk_spdif_runtime_suspend, rk_spdif_runtime_resume, + NULL) +}; + +static struct platform_driver rk_spdif_driver = { + .probe = rk_spdif_probe, + .remove = rk_spdif_remove, + .driver = { + .name = "rockchip-spdif", + .of_match_table = of_match_ptr(rk_spdif_match), + .pm = &rk_spdif_pm_ops, + }, +}; +module_platform_driver(rk_spdif_driver); + +MODULE_ALIAS("platform:rockchip-spdif"); +MODULE_DESCRIPTION("ROCKCHIP SPDIF transceiver Interface"); +MODULE_AUTHOR("Sjoerd Simons "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/rockchip/rockchip_spdif.h b/sound/soc/rockchip/rockchip_spdif.h new file mode 100644 index 000000000..3ef12770a --- /dev/null +++ b/sound/soc/rockchip/rockchip_spdif.h @@ -0,0 +1,63 @@ +/* + * ALSA SoC Audio Layer - Rockchip SPDIF transceiver driver + * + * Copyright (c) 2015 Collabora Ltd. + * Author: Sjoerd Simons + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _ROCKCHIP_SPDIF_H +#define _ROCKCHIP_SPDIF_H + +/* + * CFGR + * transfer configuration register +*/ +#define SPDIF_CFGR_CLK_DIV_SHIFT (16) +#define SPDIF_CFGR_CLK_DIV_MASK (0xff << SPDIF_CFGR_CLK_DIV_SHIFT) +#define SPDIF_CFGR_CLK_DIV(x) (x << SPDIF_CFGR_CLK_DIV_SHIFT) + +#define SPDIF_CFGR_HALFWORD_SHIFT 2 +#define SPDIF_CFGR_HALFWORD_DISABLE (0 << SPDIF_CFGR_HALFWORD_SHIFT) +#define SPDIF_CFGR_HALFWORD_ENABLE (1 << SPDIF_CFGR_HALFWORD_SHIFT) + +#define SPDIF_CFGR_VDW_SHIFT 0 +#define SPDIF_CFGR_VDW(x) (x << SPDIF_CFGR_VDW_SHIFT) +#define SDPIF_CFGR_VDW_MASK (0xf << SPDIF_CFGR_VDW_SHIFT) + +#define SPDIF_CFGR_VDW_16 SPDIF_CFGR_VDW(0x0) +#define SPDIF_CFGR_VDW_20 SPDIF_CFGR_VDW(0x1) +#define SPDIF_CFGR_VDW_24 SPDIF_CFGR_VDW(0x2) + +/* + * DMACR + * DMA control register +*/ +#define SPDIF_DMACR_TDE_SHIFT 5 +#define SPDIF_DMACR_TDE_DISABLE (0 << SPDIF_DMACR_TDE_SHIFT) +#define SPDIF_DMACR_TDE_ENABLE (1 << SPDIF_DMACR_TDE_SHIFT) + +#define SPDIF_DMACR_TDL_SHIFT 0 +#define SPDIF_DMACR_TDL(x) ((x) << SPDIF_DMACR_TDL_SHIFT) +#define SPDIF_DMACR_TDL_MASK (0x1f << SPDIF_DMACR_TDL_SHIFT) + +/* + * XFER + * Transfer control register +*/ +#define SPDIF_XFER_TXS_SHIFT 0 +#define SPDIF_XFER_TXS_STOP (0 << SPDIF_XFER_TXS_SHIFT) +#define SPDIF_XFER_TXS_START (1 << SPDIF_XFER_TXS_SHIFT) + +#define SPDIF_CFGR (0x0000) +#define SPDIF_SDBLR (0x0004) +#define SPDIF_DMACR (0x0008) +#define SPDIF_INTCR (0x000c) +#define SPDIF_INTSR (0x0010) +#define SPDIF_XFER (0x0018) +#define SPDIF_SMPDR (0x0020) + +#endif /* _ROCKCHIP_SPDIF_H */ diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig new file mode 100644 index 000000000..0520f5afd --- /dev/null +++ b/sound/soc/samsung/Kconfig @@ -0,0 +1,212 @@ +menuconfig SND_SOC_SAMSUNG + tristate "ASoC support for Samsung" + depends on PLAT_SAMSUNG || ARCH_EXYNOS || COMPILE_TEST + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + ---help--- + Say Y or M if you want to add support for codecs attached to + the Samsung SoCs' Audio interfaces. You will also need to + select the audio interfaces to support below. + +if SND_SOC_SAMSUNG + +config SND_S3C24XX_I2S + tristate + +config SND_S3C_I2SV2_SOC + tristate + +config SND_S3C2412_SOC_I2S + tristate + select SND_S3C_I2SV2_SOC + +config SND_SAMSUNG_PCM + tristate "Samsung PCM interface support" + +config SND_SAMSUNG_SPDIF + tristate "Samsung SPDIF transmitter support" + select SND_SOC_SPDIF + +config SND_SAMSUNG_I2S + tristate "Samsung I2S interface support" + +config SND_SOC_SAMSUNG_NEO1973_WM8753 + tristate "Audio support for Openmoko Neo1973 Smartphones (GTA02)" + depends on MACH_NEO1973_GTA02 + select SND_S3C24XX_I2S + select SND_SOC_WM8753 + select SND_SOC_BT_SCO + help + Say Y here to enable audio support for the Openmoko Neo1973 + Smartphones. + +config SND_SOC_SAMSUNG_JIVE_WM8750 + tristate "SoC I2S Audio support for Jive" + depends on MACH_JIVE && I2C + select SND_SOC_WM8750 + select SND_S3C2412_SOC_I2S + help + Say Y if you want to add support for SoC audio on the Jive. + +config SND_SOC_SAMSUNG_SMDK_WM8580 + tristate "SoC I2S Audio support for WM8580 on SMDK" + depends on MACH_SMDK6410 || COMPILE_TEST + depends on I2C + select SND_SOC_WM8580 + select SND_SAMSUNG_I2S + help + Say Y if you want to add support for SoC audio on the SMDKs. + +config SND_SOC_SAMSUNG_SMDK_WM8994 + tristate "SoC I2S Audio support for WM8994 on SMDK" + depends on I2C=y + select MFD_WM8994 + select SND_SOC_WM8994 + select SND_SAMSUNG_I2S + help + Say Y if you want to add support for SoC audio on the SMDKs. + +config SND_SOC_SAMSUNG_S3C24XX_UDA134X + tristate "SoC I2S Audio support UDA134X wired to a S3C24XX" + depends on ARCH_S3C24XX + select SND_S3C24XX_I2S + select SND_SOC_L3 + select SND_SOC_UDA134X + +config SND_SOC_SAMSUNG_SIMTEC + tristate + help + Internal node for common S3C24XX/Simtec suppor + +config SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23 + tristate "SoC I2S Audio support for TLV320AIC23 on Simtec boards" + depends on ARCH_S3C24XX && I2C + select SND_S3C24XX_I2S + select SND_SOC_TLV320AIC23_I2C + select SND_SOC_SAMSUNG_SIMTEC + +config SND_SOC_SAMSUNG_SIMTEC_HERMES + tristate "SoC I2S Audio support for Simtec Hermes board" + depends on ARCH_S3C24XX && I2C + select SND_S3C24XX_I2S + select SND_SOC_TLV320AIC3X + select SND_SOC_SAMSUNG_SIMTEC + +config SND_SOC_SAMSUNG_H1940_UDA1380 + tristate "Audio support for the HP iPAQ H1940" + depends on ARCH_H1940 && I2C + select SND_S3C24XX_I2S + select SND_SOC_UDA1380 + help + This driver provides audio support for HP iPAQ h1940 PDA. + +config SND_SOC_SAMSUNG_RX1950_UDA1380 + tristate "Audio support for the HP iPAQ RX1950" + depends on MACH_RX1950 && I2C + select SND_S3C24XX_I2S + select SND_SOC_UDA1380 + help + This driver provides audio support for HP iPAQ RX1950 PDA. + +config SND_SOC_SMARTQ + tristate "SoC I2S Audio support for SmartQ board" + depends on MACH_SMARTQ || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + depends on I2C + select SND_SAMSUNG_I2S + select SND_SOC_WM8750 + +config SND_SOC_SAMSUNG_SMDK_SPDIF + tristate "SoC S/PDIF Audio support for SMDK" + select SND_SAMSUNG_SPDIF + help + Say Y if you want to add support for SoC S/PDIF audio on the SMDK. + +config SND_SOC_SMDK_WM8994_PCM + tristate "SoC PCM Audio support for WM8994 on SMDK" + depends on I2C=y + select MFD_WM8994 + select SND_SOC_WM8994 + select SND_SAMSUNG_PCM + help + Say Y if you want to add support for SoC audio on the SMDK + +config SND_SOC_SPEYSIDE + tristate "Audio support for Wolfson Speyside" + depends on I2C && SPI_MASTER + depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST + select SND_SAMSUNG_I2S + select SND_SOC_WM8996 + select SND_SOC_WM9081 + select SND_SOC_WM0010 + select SND_SOC_WM1250_EV1 + +config SND_SOC_TOBERMORY + tristate "Audio support for Wolfson Tobermory" + depends on INPUT && I2C + depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST + select SND_SAMSUNG_I2S + select SND_SOC_WM8962 + +config SND_SOC_BELLS + tristate "Audio support for Wolfson Bells" + depends on MFD_ARIZONA && I2C && SPI_MASTER + depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST + select SND_SAMSUNG_I2S + select SND_SOC_WM5102 + select SND_SOC_WM5110 + select SND_SOC_WM9081 + select SND_SOC_WM0010 + select SND_SOC_WM1250_EV1 + +config SND_SOC_LOWLAND + tristate "Audio support for Wolfson Lowland" + depends on I2C + depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST + select SND_SAMSUNG_I2S + select SND_SOC_WM5100 + select SND_SOC_WM9081 + +config SND_SOC_LITTLEMILL + tristate "Audio support for Wolfson Littlemill" + depends on I2C + depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST + select SND_SAMSUNG_I2S + select MFD_WM8994 + select SND_SOC_WM8994 + +config SND_SOC_SNOW + tristate "Audio support for Google Snow boards" + depends on I2C + select SND_SOC_MAX98090 + select SND_SOC_MAX98095 + select SND_SAMSUNG_I2S + help + Say Y if you want to add audio support for various Snow + boards based on Exynos5 series of SoCs. + +config SND_SOC_ODROID + tristate "Audio support for Odroid XU3/XU4" + depends on SND_SOC_SAMSUNG && I2C + select SND_SOC_MAX98090 + select SND_SAMSUNG_I2S + help + Say Y here to enable audio support for the Odroid XU3/XU4. + +config SND_SOC_ARNDALE_RT5631_ALC5631 + tristate "Audio support for RT5631(ALC5631) on Arndale Board" + depends on I2C + select SND_SAMSUNG_I2S + select SND_SOC_RT5631 + +config SND_SOC_SAMSUNG_TM2_WM5110 + tristate "SoC I2S Audio support for WM5110 on TM2 board" + depends on SND_SOC_SAMSUNG && MFD_ARIZONA && I2C && SPI_MASTER + depends on GPIOLIB || COMPILE_TEST + select SND_SOC_MAX98504 + select SND_SOC_WM5110 + select SND_SAMSUNG_I2S + help + Say Y if you want to add support for SoC audio on the TM2 board. + +endif #SND_SOC_SAMSUNG diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile new file mode 100644 index 000000000..c3b76035f --- /dev/null +++ b/sound/soc/samsung/Makefile @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0 +# S3c24XX Platform Support +snd-soc-s3c-dma-objs := dmaengine.o +snd-soc-idma-objs := idma.o +snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o +snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o +snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o +snd-soc-samsung-spdif-objs := spdif.o +snd-soc-pcm-objs := pcm.o +snd-soc-i2s-objs := i2s.o + +obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c-dma.o +obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o +obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o +obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o +obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o +obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o +obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o +obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o + +# S3C24XX Machine Support +snd-soc-jive-wm8750-objs := jive_wm8750.o +snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o +snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o +snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o +snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o +snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o +snd-soc-h1940-uda1380-objs := h1940_uda1380.o +snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o +snd-soc-smdk-wm8580-objs := smdk_wm8580.o +snd-soc-smdk-wm8994-objs := smdk_wm8994.o +snd-soc-snow-objs := snow.o +snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o +snd-soc-smdk-spdif-objs := smdk_spdif.o +snd-soc-smdk-wm8994pcm-objs := smdk_wm8994pcm.o +snd-soc-speyside-objs := speyside.o +snd-soc-tobermory-objs := tobermory.o +snd-soc-lowland-objs := lowland.o +snd-soc-littlemill-objs := littlemill.o +snd-soc-bells-objs := bells.o +snd-soc-odroid-objs := odroid.o +snd-soc-arndale-rt5631-objs := arndale_rt5631.o +snd-soc-tm2-wm5110-objs := tm2_wm5110.o + +obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o +obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o +obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC) += snd-soc-s3c24xx-simtec.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o +obj-$(CONFIG_SND_SOC_SAMSUNG_H1940_UDA1380) += snd-soc-h1940-uda1380.o +obj-$(CONFIG_SND_SOC_SAMSUNG_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8580) += snd-soc-smdk-wm8580.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994) += snd-soc-smdk-wm8994.o +obj-$(CONFIG_SND_SOC_SNOW) += snd-soc-snow.o +obj-$(CONFIG_SND_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o +obj-$(CONFIG_SND_SOC_SMDK_WM8994_PCM) += snd-soc-smdk-wm8994pcm.o +obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o +obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o +obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o +obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o +obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o +obj-$(CONFIG_SND_SOC_ODROID) += snd-soc-odroid.o +obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o +obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o diff --git a/sound/soc/samsung/arndale_rt5631.c b/sound/soc/samsung/arndale_rt5631.c new file mode 100644 index 000000000..ee1fda92f --- /dev/null +++ b/sound/soc/samsung/arndale_rt5631.c @@ -0,0 +1,140 @@ +/* + * arndale_rt5631.c + * + * Copyright (c) 2014, Insignal Co., Ltd. + * + * Author: Claude + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "i2s.h" + +static int arndale_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int rfs, ret; + unsigned long rclk; + + rfs = 256; + + rclk = params_rate(params) * rfs; + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0, + 0, SND_SOC_CLOCK_OUT); + + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops arndale_ops = { + .hw_params = arndale_hw_params, +}; + +static struct snd_soc_dai_link arndale_rt5631_dai[] = { + { + .name = "RT5631 HiFi", + .stream_name = "Primary", + .codec_dai_name = "rt5631-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .ops = &arndale_ops, + }, +}; + +static struct snd_soc_card arndale_rt5631 = { + .name = "Arndale RT5631", + .owner = THIS_MODULE, + .dai_link = arndale_rt5631_dai, + .num_links = ARRAY_SIZE(arndale_rt5631_dai), +}; + +static int arndale_audio_probe(struct platform_device *pdev) +{ + int n, ret; + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &arndale_rt5631; + + card->dev = &pdev->dev; + + for (n = 0; np && n < ARRAY_SIZE(arndale_rt5631_dai); n++) { + if (!arndale_rt5631_dai[n].cpu_dai_name) { + arndale_rt5631_dai[n].cpu_of_node = of_parse_phandle(np, + "samsung,audio-cpu", n); + + if (!arndale_rt5631_dai[n].cpu_of_node) { + dev_err(&pdev->dev, + "Property 'samsung,audio-cpu' missing or invalid\n"); + return -EINVAL; + } + } + if (!arndale_rt5631_dai[n].platform_name) + arndale_rt5631_dai[n].platform_of_node = + arndale_rt5631_dai[n].cpu_of_node; + + arndale_rt5631_dai[n].codec_name = NULL; + arndale_rt5631_dai[n].codec_of_node = of_parse_phandle(np, + "samsung,audio-codec", n); + if (!arndale_rt5631_dai[0].codec_of_node) { + dev_err(&pdev->dev, + "Property 'samsung,audio-codec' missing or invalid\n"); + return -EINVAL; + } + } + + ret = devm_snd_soc_register_card(card->dev, card); + + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret); + + return ret; +} + +static const struct of_device_id samsung_arndale_rt5631_of_match[] __maybe_unused = { + { .compatible = "samsung,arndale-rt5631", }, + { .compatible = "samsung,arndale-alc5631", }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_arndale_rt5631_of_match); + +static struct platform_driver arndale_audio_driver = { + .driver = { + .name = "arndale-audio", + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(samsung_arndale_rt5631_of_match), + }, + .probe = arndale_audio_probe, +}; + +module_platform_driver(arndale_audio_driver); + +MODULE_AUTHOR("Claude "); +MODULE_DESCRIPTION("ALSA SoC Driver for Arndale Board"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/bells.c b/sound/soc/samsung/bells.c new file mode 100644 index 000000000..0e66cd8ef --- /dev/null +++ b/sound/soc/samsung/bells.c @@ -0,0 +1,477 @@ +/* + * Bells audio support + * + * Copyright 2012 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "../codecs/wm5102.h" +#include "../codecs/wm9081.h" + +/* BCLK2 is fixed at this currently */ +#define BCLK2_RATE (64 * 8000) + +/* + * Expect a 24.576MHz crystal if one is fitted (the driver will function + * if this is not fitted). + */ +#define MCLK_RATE 24576000 + +#define SYS_AUDIO_RATE 44100 +#define SYS_MCLK_RATE (SYS_AUDIO_RATE * 512) + +#define DAI_AP_DSP 0 +#define DAI_DSP_CODEC 1 +#define DAI_CODEC_CP 2 +#define DAI_CODEC_SUB 3 + +struct bells_drvdata { + int sysclk_rate; + int asyncclk_rate; +}; + +static struct bells_drvdata wm2200_drvdata = { + .sysclk_rate = 22579200, +}; + +static struct bells_drvdata wm5102_drvdata = { + .sysclk_rate = 45158400, + .asyncclk_rate = 49152000, +}; + +static struct bells_drvdata wm5110_drvdata = { + .sysclk_rate = 135475200, + .asyncclk_rate = 147456000, +}; + +static int bells_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct snd_soc_component *component; + struct bells_drvdata *bells = card->drvdata; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_DSP_CODEC].name); + codec_dai = rtd->codec_dai; + component = codec_dai->component; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + + ret = snd_soc_component_set_pll(component, WM5102_FLL1, + ARIZONA_FLL_SRC_MCLK1, + MCLK_RATE, + bells->sysclk_rate); + if (ret < 0) + pr_err("Failed to start FLL: %d\n", ret); + + if (bells->asyncclk_rate) { + ret = snd_soc_component_set_pll(component, WM5102_FLL2, + ARIZONA_FLL_SRC_AIF2BCLK, + BCLK2_RATE, + bells->asyncclk_rate); + if (ret < 0) + pr_err("Failed to start FLL: %d\n", ret); + } + break; + + default: + break; + } + + return 0; +} + +static int bells_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + struct snd_soc_component *component; + struct bells_drvdata *bells = card->drvdata; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_DSP_CODEC].name); + codec_dai = rtd->codec_dai; + component = codec_dai->component; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + ret = snd_soc_component_set_pll(component, WM5102_FLL1, 0, 0, 0); + if (ret < 0) { + pr_err("Failed to stop FLL: %d\n", ret); + return ret; + } + + if (bells->asyncclk_rate) { + ret = snd_soc_component_set_pll(component, WM5102_FLL2, + 0, 0, 0); + if (ret < 0) { + pr_err("Failed to stop FLL: %d\n", ret); + return ret; + } + } + break; + + default: + break; + } + + dapm->bias_level = level; + + return 0; +} + +static int bells_late_probe(struct snd_soc_card *card) +{ + struct bells_drvdata *bells = card->drvdata; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *wm0010; + struct snd_soc_component *component; + struct snd_soc_dai *aif1_dai; + struct snd_soc_dai *aif2_dai; + struct snd_soc_dai *aif3_dai; + struct snd_soc_dai *wm9081_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_AP_DSP].name); + wm0010 = rtd->codec_dai->component; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_DSP_CODEC].name); + component = rtd->codec_dai->component; + aif1_dai = rtd->codec_dai; + + ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + bells->sysclk_rate, + SND_SOC_CLOCK_IN); + if (ret != 0) { + dev_err(component->dev, "Failed to set SYSCLK: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(wm0010, 0, 0, SYS_MCLK_RATE, 0); + if (ret != 0) { + dev_err(wm0010->dev, "Failed to set WM0010 clock: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0); + if (ret != 0) + dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); + + ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_OPCLK, 0, + SYS_MCLK_RATE, SND_SOC_CLOCK_OUT); + if (ret != 0) + dev_err(component->dev, "Failed to set OPCLK: %d\n", ret); + + if (card->num_rtd == DAI_CODEC_CP) + return 0; + + ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_ASYNCCLK, + ARIZONA_CLK_SRC_FLL2, + bells->asyncclk_rate, + SND_SOC_CLOCK_IN); + if (ret != 0) { + dev_err(component->dev, "Failed to set ASYNCCLK: %d\n", ret); + return ret; + } + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_CODEC_CP].name); + aif2_dai = rtd->cpu_dai; + + ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0); + if (ret != 0) { + dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret); + return ret; + } + + if (card->num_rtd == DAI_CODEC_SUB) + return 0; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[DAI_CODEC_SUB].name); + aif3_dai = rtd->cpu_dai; + wm9081_dai = rtd->codec_dai; + + ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0); + if (ret != 0) { + dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(wm9081_dai->component, WM9081_SYSCLK_MCLK, + 0, SYS_MCLK_RATE, 0); + if (ret != 0) { + dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_pcm_stream baseband_params = { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 2, + .channels_max = 2, +}; + +static const struct snd_soc_pcm_stream sub_params = { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = SYS_AUDIO_RATE, + .rate_max = SYS_AUDIO_RATE, + .channels_min = 2, + .channels_max = 2, +}; + +static struct snd_soc_dai_link bells_dai_wm2200[] = { + { + .name = "CPU-DSP", + .stream_name = "CPU-DSP", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm0010-sdi1", + .platform_name = "samsung-i2s.0", + .codec_name = "spi0.0", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + }, + { + .name = "DSP-CODEC", + .stream_name = "DSP-CODEC", + .cpu_dai_name = "wm0010-sdi2", + .codec_dai_name = "wm2200", + .codec_name = "wm2200.1-003a", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .params = &sub_params, + .ignore_suspend = 1, + }, +}; + +static struct snd_soc_dai_link bells_dai_wm5102[] = { + { + .name = "CPU-DSP", + .stream_name = "CPU-DSP", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm0010-sdi1", + .platform_name = "samsung-i2s.0", + .codec_name = "spi0.0", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + }, + { + .name = "DSP-CODEC", + .stream_name = "DSP-CODEC", + .cpu_dai_name = "wm0010-sdi2", + .codec_dai_name = "wm5102-aif1", + .codec_name = "wm5102-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .params = &sub_params, + .ignore_suspend = 1, + }, + { + .name = "Baseband", + .stream_name = "Baseband", + .cpu_dai_name = "wm5102-aif2", + .codec_dai_name = "wm1250-ev1", + .codec_name = "wm1250-ev1.1-0027", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .params = &baseband_params, + }, + { + .name = "Sub", + .stream_name = "Sub", + .cpu_dai_name = "wm5102-aif3", + .codec_dai_name = "wm9081-hifi", + .codec_name = "wm9081.1-006c", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .params = &sub_params, + }, +}; + +static struct snd_soc_dai_link bells_dai_wm5110[] = { + { + .name = "CPU-DSP", + .stream_name = "CPU-DSP", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm0010-sdi1", + .platform_name = "samsung-i2s.0", + .codec_name = "spi0.0", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + }, + { + .name = "DSP-CODEC", + .stream_name = "DSP-CODEC", + .cpu_dai_name = "wm0010-sdi2", + .codec_dai_name = "wm5110-aif1", + .codec_name = "wm5110-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .params = &sub_params, + .ignore_suspend = 1, + }, + { + .name = "Baseband", + .stream_name = "Baseband", + .cpu_dai_name = "wm5110-aif2", + .codec_dai_name = "wm1250-ev1", + .codec_name = "wm1250-ev1.1-0027", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .params = &baseband_params, + }, + { + .name = "Sub", + .stream_name = "Sub", + .cpu_dai_name = "wm5110-aif3", + .codec_dai_name = "wm9081-hifi", + .codec_name = "wm9081.1-006c", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, + .ignore_suspend = 1, + .params = &sub_params, + }, +}; + +static struct snd_soc_codec_conf bells_codec_conf[] = { + { + .dev_name = "wm9081.1-006c", + .name_prefix = "Sub", + }, +}; + +static struct snd_soc_dapm_widget bells_widgets[] = { + SND_SOC_DAPM_MIC("DMIC", NULL), +}; + +static struct snd_soc_dapm_route bells_routes[] = { + { "Sub CLK_SYS", NULL, "OPCLK" }, + { "CLKIN", NULL, "OPCLK" }, + + { "DMIC", NULL, "MICBIAS2" }, + { "IN2L", NULL, "DMIC" }, + { "IN2R", NULL, "DMIC" }, +}; + +static struct snd_soc_card bells_cards[] = { + { + .name = "Bells WM2200", + .owner = THIS_MODULE, + .dai_link = bells_dai_wm2200, + .num_links = ARRAY_SIZE(bells_dai_wm2200), + .codec_conf = bells_codec_conf, + .num_configs = ARRAY_SIZE(bells_codec_conf), + + .late_probe = bells_late_probe, + + .dapm_widgets = bells_widgets, + .num_dapm_widgets = ARRAY_SIZE(bells_widgets), + .dapm_routes = bells_routes, + .num_dapm_routes = ARRAY_SIZE(bells_routes), + + .set_bias_level = bells_set_bias_level, + .set_bias_level_post = bells_set_bias_level_post, + + .drvdata = &wm2200_drvdata, + }, + { + .name = "Bells WM5102", + .owner = THIS_MODULE, + .dai_link = bells_dai_wm5102, + .num_links = ARRAY_SIZE(bells_dai_wm5102), + .codec_conf = bells_codec_conf, + .num_configs = ARRAY_SIZE(bells_codec_conf), + + .late_probe = bells_late_probe, + + .dapm_widgets = bells_widgets, + .num_dapm_widgets = ARRAY_SIZE(bells_widgets), + .dapm_routes = bells_routes, + .num_dapm_routes = ARRAY_SIZE(bells_routes), + + .set_bias_level = bells_set_bias_level, + .set_bias_level_post = bells_set_bias_level_post, + + .drvdata = &wm5102_drvdata, + }, + { + .name = "Bells WM5110", + .owner = THIS_MODULE, + .dai_link = bells_dai_wm5110, + .num_links = ARRAY_SIZE(bells_dai_wm5110), + .codec_conf = bells_codec_conf, + .num_configs = ARRAY_SIZE(bells_codec_conf), + + .late_probe = bells_late_probe, + + .dapm_widgets = bells_widgets, + .num_dapm_widgets = ARRAY_SIZE(bells_widgets), + .dapm_routes = bells_routes, + .num_dapm_routes = ARRAY_SIZE(bells_routes), + + .set_bias_level = bells_set_bias_level, + .set_bias_level_post = bells_set_bias_level_post, + + .drvdata = &wm5110_drvdata, + }, +}; + +static int bells_probe(struct platform_device *pdev) +{ + int ret; + + bells_cards[pdev->id].dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, &bells_cards[pdev->id]); + if (ret) + dev_err(&pdev->dev, + "snd_soc_register_card(%s) failed: %d\n", + bells_cards[pdev->id].name, ret); + + return ret; +} + +static struct platform_driver bells_driver = { + .driver = { + .name = "bells", + .pm = &snd_soc_pm_ops, + }, + .probe = bells_probe, +}; + +module_platform_driver(bells_driver); + +MODULE_DESCRIPTION("Bells audio support"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bells"); diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h new file mode 100644 index 000000000..7ae580d67 --- /dev/null +++ b/sound/soc/samsung/dma.h @@ -0,0 +1,21 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * ALSA PCM interface for the Samsung SoC + */ + +#ifndef _SAMSUNG_DMA_H +#define _SAMSUNG_DMA_H + +#include + +/* + * @tx, @rx arguments can be NULL if the DMA channel names are "tx", "rx", + * otherwise actual DMA channel names must be passed to this function. + */ +int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter, + const char *tx, const char *rx); +#endif /* _SAMSUNG_DMA_H */ diff --git a/sound/soc/samsung/dmaengine.c b/sound/soc/samsung/dmaengine.c new file mode 100644 index 000000000..9104c98de --- /dev/null +++ b/sound/soc/samsung/dmaengine.c @@ -0,0 +1,53 @@ +/* + * dmaengine.c - Samsung dmaengine wrapper + * + * Author: Mark Brown + * Copyright 2013 Linaro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "dma.h" + +int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter, + const char *tx, const char *rx) +{ + unsigned int flags = SND_DMAENGINE_PCM_FLAG_COMPAT; + struct snd_dmaengine_pcm_config *pcm_conf; + + pcm_conf = devm_kzalloc(dev, sizeof(*pcm_conf), GFP_KERNEL); + if (!pcm_conf) + return -ENOMEM; + + pcm_conf->prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config; + pcm_conf->compat_filter_fn = filter; + + if (dev->of_node) { + pcm_conf->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = tx; + pcm_conf->chan_names[SNDRV_PCM_STREAM_CAPTURE] = rx; + } else { + flags |= SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME; + } + + return devm_snd_dmaengine_pcm_register(dev, pcm_conf, flags); +} +EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register); + +MODULE_AUTHOR("Mark Brown "); +MODULE_DESCRIPTION("Samsung dmaengine ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c new file mode 100644 index 000000000..051935162 --- /dev/null +++ b/sound/soc/samsung/h1940_uda1380.c @@ -0,0 +1,252 @@ +/* + * h1940-uda1380.c -- ALSA Soc Audio Layer + * + * Copyright (c) 2010 Arnaud Patard + * Copyright (c) 2010 Vasily Khoruzhick + * + * Based on version from Arnaud Patard + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include + +#include +#include + +#include "regs-iis.h" +#include + +#include +#include "s3c24xx-i2s.h" + +static const unsigned int rates[] = { + 11025, + 22050, + 44100, +}; + +static const struct snd_pcm_hw_constraint_list hw_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + +static struct snd_soc_jack_gpio hp_jack_gpios[] = { + { + .gpio = S3C2410_GPG(4), + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .invert = 1, + .debounce_time = 200, + }, +}; + +static int h1940_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_rates); +} + +static int h1940_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int div; + int ret; + unsigned int rate = params_rate(params); + + switch (rate) { + case 11025: + case 22050: + case 44100: + div = s3c24xx_i2s_get_clockrate() / (384 * rate); + if (s3c24xx_i2s_get_clockrate() % (384 * rate) > (192 * rate)) + div++; + break; + default: + dev_err(rtd->dev, "%s: rate %d is not supported\n", + __func__, rate); + return -EINVAL; + } + + /* select clock source */ + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, rate, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_384FS); + if (ret < 0) + return ret; + + /* set BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(div, div)); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops h1940_ops = { + .startup = h1940_startup, + .hw_params = h1940_hw_params, +}; + +static int h1940_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(S3C_GPIO_END + 9, 1); + else + gpio_set_value(S3C_GPIO_END + 9, 0); + + return 0; +} + +/* h1940 machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", h1940_spk_power), +}; + +/* h1940 machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + /* headphone connected to VOUTLHP, VOUTRHP */ + {"Headphone Jack", NULL, "VOUTLHP"}, + {"Headphone Jack", NULL, "VOUTRHP"}, + + /* ext speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* mic is connected to VINM */ + {"VINM", NULL, "Mic Jack"}, +}; + +static struct platform_device *s3c24xx_snd_device; + +static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE, + &hp_jack, hp_jack_pins, ARRAY_SIZE(hp_jack_pins)); + + snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + + return 0; +} + +/* s3c24xx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link h1940_uda1380_dai[] = { + { + .name = "uda1380", + .stream_name = "UDA1380 Duplex", + .cpu_dai_name = "s3c24xx-iis", + .codec_dai_name = "uda1380-hifi", + .init = h1940_uda1380_init, + .platform_name = "s3c24xx-iis", + .codec_name = "uda1380-codec.0-001a", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &h1940_ops, + }, +}; + +static struct snd_soc_card h1940_asoc = { + .name = "h1940", + .owner = THIS_MODULE, + .dai_link = h1940_uda1380_dai, + .num_links = ARRAY_SIZE(h1940_uda1380_dai), + + .dapm_widgets = uda1380_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int __init h1940_init(void) +{ + int ret; + + if (!machine_is_h1940()) + return -ENODEV; + + /* configure some gpios */ + ret = gpio_request(S3C_GPIO_END + 9, "speaker-power"); + if (ret) + goto err_out; + + ret = gpio_direction_output(S3C_GPIO_END + 9, 0); + if (ret) + goto err_gpio; + + s3c24xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!s3c24xx_snd_device) { + ret = -ENOMEM; + goto err_gpio; + } + + platform_set_drvdata(s3c24xx_snd_device, &h1940_asoc); + ret = platform_device_add(s3c24xx_snd_device); + + if (ret) + goto err_plat; + + return 0; + +err_plat: + platform_device_put(s3c24xx_snd_device); +err_gpio: + gpio_free(S3C_GPIO_END + 9); + +err_out: + return ret; +} + +static void __exit h1940_exit(void) +{ + platform_device_unregister(s3c24xx_snd_device); + gpio_free(S3C_GPIO_END + 9); +} + +module_init(h1940_init); +module_exit(h1940_exit); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick"); +MODULE_DESCRIPTION("ALSA SoC H1940"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h new file mode 100644 index 000000000..964985ea2 --- /dev/null +++ b/sound/soc/samsung/i2s-regs.h @@ -0,0 +1,163 @@ +/* + * linux/sound/soc/samsung/i2s-regs.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung I2S driver's register header + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __SND_SOC_SAMSUNG_I2S_REGS_H +#define __SND_SOC_SAMSUNG_I2S_REGS_H + +#define I2SCON 0x0 +#define I2SMOD 0x4 +#define I2SFIC 0x8 +#define I2SPSR 0xc +#define I2STXD 0x10 +#define I2SRXD 0x14 +#define I2SFICS 0x18 +#define I2STXDS 0x1c +#define I2SAHB 0x20 +#define I2SSTR0 0x24 +#define I2SSIZE 0x28 +#define I2STRNCNT 0x2c +#define I2SLVL0ADDR 0x30 +#define I2SLVL1ADDR 0x34 +#define I2SLVL2ADDR 0x38 +#define I2SLVL3ADDR 0x3c +#define I2SSTR1 0x40 +#define I2SVER 0x44 +#define I2SFIC1 0x48 +#define I2STDM 0x4c +#define I2SFSTA 0x50 + +#define CON_RSTCLR (1 << 31) +#define CON_FRXOFSTATUS (1 << 26) +#define CON_FRXORINTEN (1 << 25) +#define CON_FTXSURSTAT (1 << 24) +#define CON_FTXSURINTEN (1 << 23) +#define CON_TXSDMA_PAUSE (1 << 20) +#define CON_TXSDMA_ACTIVE (1 << 18) + +#define CON_FTXURSTATUS (1 << 17) +#define CON_FTXURINTEN (1 << 16) +#define CON_TXFIFO2_EMPTY (1 << 15) +#define CON_TXFIFO1_EMPTY (1 << 14) +#define CON_TXFIFO2_FULL (1 << 13) +#define CON_TXFIFO1_FULL (1 << 12) + +#define CON_LRINDEX (1 << 11) +#define CON_TXFIFO_EMPTY (1 << 10) +#define CON_RXFIFO_EMPTY (1 << 9) +#define CON_TXFIFO_FULL (1 << 8) +#define CON_RXFIFO_FULL (1 << 7) +#define CON_TXDMA_PAUSE (1 << 6) +#define CON_RXDMA_PAUSE (1 << 5) +#define CON_TXCH_PAUSE (1 << 4) +#define CON_RXCH_PAUSE (1 << 3) +#define CON_TXDMA_ACTIVE (1 << 2) +#define CON_RXDMA_ACTIVE (1 << 1) +#define CON_ACTIVE (1 << 0) + +#define MOD_OPCLK_SHIFT 30 +#define MOD_OPCLK_CDCLK_OUT (0 << MOD_OPCLK_SHIFT) +#define MOD_OPCLK_CDCLK_IN (1 << MOD_OPCLK_SHIFT) +#define MOD_OPCLK_BCLK_OUT (2 << MOD_OPCLK_SHIFT) +#define MOD_OPCLK_PCLK (3 << MOD_OPCLK_SHIFT) +#define MOD_OPCLK_MASK (3 << MOD_OPCLK_SHIFT) +#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ + +#define MOD_BLCS_SHIFT 26 +#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT) +#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT) +#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT) +#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT) +#define MOD_BLCP_SHIFT 24 +#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT) +#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT) +#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT) +#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT) + +#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ +#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ +#define MOD_C1DD_HHALF (1 << 19) +#define MOD_C1DD_LHALF (1 << 18) +#define MOD_DC2_EN (1 << 17) +#define MOD_DC1_EN (1 << 16) +#define MOD_BLC_16BIT (0 << 13) +#define MOD_BLC_8BIT (1 << 13) +#define MOD_BLC_24BIT (2 << 13) +#define MOD_BLC_MASK (3 << 13) + +#define MOD_TXONLY (0 << 8) +#define MOD_RXONLY (1 << 8) +#define MOD_TXRX (2 << 8) +#define MOD_MASK (3 << 8) +#define MOD_LRP_SHIFT 7 +#define MOD_LR_LLOW 0 +#define MOD_LR_RLOW 1 +#define MOD_SDF_SHIFT 5 +#define MOD_SDF_IIS 0 +#define MOD_SDF_MSB 1 +#define MOD_SDF_LSB 2 +#define MOD_SDF_MASK 3 +#define MOD_RCLK_SHIFT 3 +#define MOD_RCLK_256FS 0 +#define MOD_RCLK_512FS 1 +#define MOD_RCLK_384FS 2 +#define MOD_RCLK_768FS 3 +#define MOD_RCLK_MASK 3 +#define MOD_BCLK_SHIFT 1 +#define MOD_BCLK_32FS 0 +#define MOD_BCLK_48FS 1 +#define MOD_BCLK_16FS 2 +#define MOD_BCLK_24FS 3 +#define MOD_BCLK_MASK 3 +#define MOD_8BIT (1 << 0) + +#define EXYNOS5420_MOD_LRP_SHIFT 15 +#define EXYNOS5420_MOD_SDF_SHIFT 6 +#define EXYNOS5420_MOD_RCLK_SHIFT 4 +#define EXYNOS5420_MOD_BCLK_SHIFT 0 +#define EXYNOS5420_MOD_BCLK_64FS 4 +#define EXYNOS5420_MOD_BCLK_96FS 5 +#define EXYNOS5420_MOD_BCLK_128FS 6 +#define EXYNOS5420_MOD_BCLK_192FS 7 +#define EXYNOS5420_MOD_BCLK_256FS 8 +#define EXYNOS5420_MOD_BCLK_MASK 0xf + +#define EXYNOS7_MOD_RCLK_64FS 4 +#define EXYNOS7_MOD_RCLK_128FS 5 +#define EXYNOS7_MOD_RCLK_96FS 6 +#define EXYNOS7_MOD_RCLK_192FS 7 + +#define PSR_PSREN (1 << 15) + +#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf) + +#define FIC_TXFLUSH (1 << 15) +#define FIC_RXFLUSH (1 << 7) + +#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf) +#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf) +#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f) + +#define AHB_INTENLVL0 (1 << 24) +#define AHB_LVL0INT (1 << 20) +#define AHB_CLRLVL0INT (1 << 16) +#define AHB_DMARLD (1 << 5) +#define AHB_INTMASK (1 << 3) +#define AHB_DMAEN (1 << 0) +#define AHB_LVLINTMASK (0xf << 20) + +#define I2SSIZE_TRNMSK (0xffff) +#define I2SSIZE_SHIFT (16) + +#endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */ diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c new file mode 100644 index 000000000..d4bde4834 --- /dev/null +++ b/sound/soc/samsung/i2s.c @@ -0,0 +1,1589 @@ +/* sound/soc/samsung/i2s.c + * + * ALSA SoC Audio Layer - Samsung I2S Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd. + * Jaswinder Singh + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "dma.h" +#include "idma.h" +#include "i2s.h" +#include "i2s-regs.h" + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) + +struct samsung_i2s_variant_regs { + unsigned int bfs_off; + unsigned int rfs_off; + unsigned int sdf_off; + unsigned int txr_off; + unsigned int rclksrc_off; + unsigned int mss_off; + unsigned int cdclkcon_off; + unsigned int lrp_off; + unsigned int bfs_mask; + unsigned int rfs_mask; + unsigned int ftx0cnt_off; +}; + +struct samsung_i2s_dai_data { + u32 quirks; + unsigned int pcm_rates; + const struct samsung_i2s_variant_regs *i2s_variant_regs; +}; + +struct i2s_dai { + /* Platform device for this DAI */ + struct platform_device *pdev; + /* Memory mapped SFR region */ + void __iomem *addr; + /* Rate of RCLK source clock */ + unsigned long rclk_srcrate; + /* Frame Clock */ + unsigned frmclk; + /* + * Specifically requested RCLK,BCLK by MACHINE Driver. + * 0 indicates CPU driver is free to choose any value. + */ + unsigned rfs, bfs; + /* I2S Controller's core clock */ + struct clk *clk; + /* Clock for generating I2S signals */ + struct clk *op_clk; + /* Pointer to the Primary_Fifo if this is Sec_Fifo, NULL otherwise */ + struct i2s_dai *pri_dai; + /* Pointer to the Secondary_Fifo if it has one, NULL otherwise */ + struct i2s_dai *sec_dai; +#define DAI_OPENED (1 << 0) /* Dai is opened */ +#define DAI_MANAGER (1 << 1) /* Dai is the manager */ + unsigned mode; + /* Driver for this DAI */ + struct snd_soc_dai_driver i2s_dai_drv; + /* DMA parameters */ + struct snd_dmaengine_dai_dma_data dma_playback; + struct snd_dmaengine_dai_dma_data dma_capture; + struct snd_dmaengine_dai_dma_data idma_playback; + dma_filter_fn filter; + u32 quirks; + u32 suspend_i2smod; + u32 suspend_i2scon; + u32 suspend_i2spsr; + const struct samsung_i2s_variant_regs *variant_regs; + + /* Spinlock protecting access to the device's registers */ + spinlock_t spinlock; + spinlock_t *lock; + + /* Below fields are only valid if this is the primary FIFO */ + struct clk *clk_table[3]; + struct clk_onecell_data clk_data; +}; + +/* Lock for cross i/f checks */ +static DEFINE_SPINLOCK(lock); + +/* If this is the 'overlay' stereo DAI */ +static inline bool is_secondary(struct i2s_dai *i2s) +{ + return i2s->pri_dai ? true : false; +} + +/* If operating in SoC-Slave mode */ +static inline bool is_slave(struct i2s_dai *i2s) +{ + u32 mod = readl(i2s->addr + I2SMOD); + return (mod & (1 << i2s->variant_regs->mss_off)) ? true : false; +} + +/* If this interface of the controller is transmitting data */ +static inline bool tx_active(struct i2s_dai *i2s) +{ + u32 active; + + if (!i2s) + return false; + + active = readl(i2s->addr + I2SCON); + + if (is_secondary(i2s)) + active &= CON_TXSDMA_ACTIVE; + else + active &= CON_TXDMA_ACTIVE; + + return active ? true : false; +} + +/* Return pointer to the other DAI */ +static inline struct i2s_dai *get_other_dai(struct i2s_dai *i2s) +{ + return i2s->pri_dai ? : i2s->sec_dai; +} + +/* If the other interface of the controller is transmitting data */ +static inline bool other_tx_active(struct i2s_dai *i2s) +{ + struct i2s_dai *other = get_other_dai(i2s); + + return tx_active(other); +} + +/* If any interface of the controller is transmitting data */ +static inline bool any_tx_active(struct i2s_dai *i2s) +{ + return tx_active(i2s) || other_tx_active(i2s); +} + +/* If this interface of the controller is receiving data */ +static inline bool rx_active(struct i2s_dai *i2s) +{ + u32 active; + + if (!i2s) + return false; + + active = readl(i2s->addr + I2SCON) & CON_RXDMA_ACTIVE; + + return active ? true : false; +} + +/* If the other interface of the controller is receiving data */ +static inline bool other_rx_active(struct i2s_dai *i2s) +{ + struct i2s_dai *other = get_other_dai(i2s); + + return rx_active(other); +} + +/* If any interface of the controller is receiving data */ +static inline bool any_rx_active(struct i2s_dai *i2s) +{ + return rx_active(i2s) || other_rx_active(i2s); +} + +/* If the other DAI is transmitting or receiving data */ +static inline bool other_active(struct i2s_dai *i2s) +{ + return other_rx_active(i2s) || other_tx_active(i2s); +} + +/* If this DAI is transmitting or receiving data */ +static inline bool this_active(struct i2s_dai *i2s) +{ + return tx_active(i2s) || rx_active(i2s); +} + +/* If the controller is active anyway */ +static inline bool any_active(struct i2s_dai *i2s) +{ + return this_active(i2s) || other_active(i2s); +} + +static inline struct i2s_dai *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +static inline bool is_opened(struct i2s_dai *i2s) +{ + if (i2s && (i2s->mode & DAI_OPENED)) + return true; + else + return false; +} + +static inline bool is_manager(struct i2s_dai *i2s) +{ + if (is_opened(i2s) && (i2s->mode & DAI_MANAGER)) + return true; + else + return false; +} + +/* Read RCLK of I2S (in multiples of LRCLK) */ +static inline unsigned get_rfs(struct i2s_dai *i2s) +{ + u32 rfs; + rfs = readl(i2s->addr + I2SMOD) >> i2s->variant_regs->rfs_off; + rfs &= i2s->variant_regs->rfs_mask; + + switch (rfs) { + case 7: return 192; + case 6: return 96; + case 5: return 128; + case 4: return 64; + case 3: return 768; + case 2: return 384; + case 1: return 512; + default: return 256; + } +} + +/* Write RCLK of I2S (in multiples of LRCLK) */ +static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) +{ + u32 mod = readl(i2s->addr + I2SMOD); + int rfs_shift = i2s->variant_regs->rfs_off; + + mod &= ~(i2s->variant_regs->rfs_mask << rfs_shift); + + switch (rfs) { + case 192: + mod |= (EXYNOS7_MOD_RCLK_192FS << rfs_shift); + break; + case 96: + mod |= (EXYNOS7_MOD_RCLK_96FS << rfs_shift); + break; + case 128: + mod |= (EXYNOS7_MOD_RCLK_128FS << rfs_shift); + break; + case 64: + mod |= (EXYNOS7_MOD_RCLK_64FS << rfs_shift); + break; + case 768: + mod |= (MOD_RCLK_768FS << rfs_shift); + break; + case 512: + mod |= (MOD_RCLK_512FS << rfs_shift); + break; + case 384: + mod |= (MOD_RCLK_384FS << rfs_shift); + break; + default: + mod |= (MOD_RCLK_256FS << rfs_shift); + break; + } + + writel(mod, i2s->addr + I2SMOD); +} + +/* Read Bit-Clock of I2S (in multiples of LRCLK) */ +static inline unsigned get_bfs(struct i2s_dai *i2s) +{ + u32 bfs; + bfs = readl(i2s->addr + I2SMOD) >> i2s->variant_regs->bfs_off; + bfs &= i2s->variant_regs->bfs_mask; + + switch (bfs) { + case 8: return 256; + case 7: return 192; + case 6: return 128; + case 5: return 96; + case 4: return 64; + case 3: return 24; + case 2: return 16; + case 1: return 48; + default: return 32; + } +} + +/* Write Bit-Clock of I2S (in multiples of LRCLK) */ +static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs) +{ + u32 mod = readl(i2s->addr + I2SMOD); + int tdm = i2s->quirks & QUIRK_SUPPORTS_TDM; + int bfs_shift = i2s->variant_regs->bfs_off; + + /* Non-TDM I2S controllers do not support BCLK > 48 * FS */ + if (!tdm && bfs > 48) { + dev_err(&i2s->pdev->dev, "Unsupported BCLK divider\n"); + return; + } + + mod &= ~(i2s->variant_regs->bfs_mask << bfs_shift); + + switch (bfs) { + case 48: + mod |= (MOD_BCLK_48FS << bfs_shift); + break; + case 32: + mod |= (MOD_BCLK_32FS << bfs_shift); + break; + case 24: + mod |= (MOD_BCLK_24FS << bfs_shift); + break; + case 16: + mod |= (MOD_BCLK_16FS << bfs_shift); + break; + case 64: + mod |= (EXYNOS5420_MOD_BCLK_64FS << bfs_shift); + break; + case 96: + mod |= (EXYNOS5420_MOD_BCLK_96FS << bfs_shift); + break; + case 128: + mod |= (EXYNOS5420_MOD_BCLK_128FS << bfs_shift); + break; + case 192: + mod |= (EXYNOS5420_MOD_BCLK_192FS << bfs_shift); + break; + case 256: + mod |= (EXYNOS5420_MOD_BCLK_256FS << bfs_shift); + break; + default: + dev_err(&i2s->pdev->dev, "Wrong BCLK Divider!\n"); + return; + } + + writel(mod, i2s->addr + I2SMOD); +} + +/* Sample-Size */ +static inline int get_blc(struct i2s_dai *i2s) +{ + int blc = readl(i2s->addr + I2SMOD); + + blc = (blc >> 13) & 0x3; + + switch (blc) { + case 2: return 24; + case 1: return 8; + default: return 16; + } +} + +/* TX Channel Control */ +static void i2s_txctrl(struct i2s_dai *i2s, int on) +{ + void __iomem *addr = i2s->addr; + int txr_off = i2s->variant_regs->txr_off; + u32 con = readl(addr + I2SCON); + u32 mod = readl(addr + I2SMOD) & ~(3 << txr_off); + + if (on) { + con |= CON_ACTIVE; + con &= ~CON_TXCH_PAUSE; + + if (is_secondary(i2s)) { + con |= CON_TXSDMA_ACTIVE; + con &= ~CON_TXSDMA_PAUSE; + } else { + con |= CON_TXDMA_ACTIVE; + con &= ~CON_TXDMA_PAUSE; + } + + if (any_rx_active(i2s)) + mod |= 2 << txr_off; + else + mod |= 0 << txr_off; + } else { + if (is_secondary(i2s)) { + con |= CON_TXSDMA_PAUSE; + con &= ~CON_TXSDMA_ACTIVE; + } else { + con |= CON_TXDMA_PAUSE; + con &= ~CON_TXDMA_ACTIVE; + } + + if (other_tx_active(i2s)) { + writel(con, addr + I2SCON); + return; + } + + con |= CON_TXCH_PAUSE; + + if (any_rx_active(i2s)) + mod |= 1 << txr_off; + else + con &= ~CON_ACTIVE; + } + + writel(mod, addr + I2SMOD); + writel(con, addr + I2SCON); +} + +/* RX Channel Control */ +static void i2s_rxctrl(struct i2s_dai *i2s, int on) +{ + void __iomem *addr = i2s->addr; + int txr_off = i2s->variant_regs->txr_off; + u32 con = readl(addr + I2SCON); + u32 mod = readl(addr + I2SMOD) & ~(3 << txr_off); + + if (on) { + con |= CON_RXDMA_ACTIVE | CON_ACTIVE; + con &= ~(CON_RXDMA_PAUSE | CON_RXCH_PAUSE); + + if (any_tx_active(i2s)) + mod |= 2 << txr_off; + else + mod |= 1 << txr_off; + } else { + con |= CON_RXDMA_PAUSE | CON_RXCH_PAUSE; + con &= ~CON_RXDMA_ACTIVE; + + if (any_tx_active(i2s)) + mod |= 0 << txr_off; + else + con &= ~CON_ACTIVE; + } + + writel(mod, addr + I2SMOD); + writel(con, addr + I2SCON); +} + +/* Flush FIFO of an interface */ +static inline void i2s_fifo(struct i2s_dai *i2s, u32 flush) +{ + void __iomem *fic; + u32 val; + + if (!i2s) + return; + + if (is_secondary(i2s)) + fic = i2s->addr + I2SFICS; + else + fic = i2s->addr + I2SFIC; + + /* Flush the FIFO */ + writel(readl(fic) | flush, fic); + + /* Be patient */ + val = msecs_to_loops(1) / 1000; /* 1 usec */ + while (--val) + cpu_relax(); + + writel(readl(fic) & ~flush, fic); +} + +static int i2s_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int rfs, int dir) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = get_other_dai(i2s); + const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs; + unsigned int cdcon_mask = 1 << i2s_regs->cdclkcon_off; + unsigned int rsrc_mask = 1 << i2s_regs->rclksrc_off; + u32 mod, mask, val = 0; + unsigned long flags; + int ret = 0; + + pm_runtime_get_sync(dai->dev); + + spin_lock_irqsave(i2s->lock, flags); + mod = readl(i2s->addr + I2SMOD); + spin_unlock_irqrestore(i2s->lock, flags); + + switch (clk_id) { + case SAMSUNG_I2S_OPCLK: + mask = MOD_OPCLK_MASK; + val = (dir << MOD_OPCLK_SHIFT) & MOD_OPCLK_MASK; + break; + case SAMSUNG_I2S_CDCLK: + mask = 1 << i2s_regs->cdclkcon_off; + /* Shouldn't matter in GATING(CLOCK_IN) mode */ + if (dir == SND_SOC_CLOCK_IN) + rfs = 0; + + if ((rfs && other && other->rfs && (other->rfs != rfs)) || + (any_active(i2s) && + (((dir == SND_SOC_CLOCK_IN) + && !(mod & cdcon_mask)) || + ((dir == SND_SOC_CLOCK_OUT) + && (mod & cdcon_mask))))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + ret = -EAGAIN; + goto err; + } + + if (dir == SND_SOC_CLOCK_IN) + val = 1 << i2s_regs->cdclkcon_off; + + i2s->rfs = rfs; + break; + + case SAMSUNG_I2S_RCLKSRC_0: /* clock corrsponding to IISMOD[10] := 0 */ + case SAMSUNG_I2S_RCLKSRC_1: /* clock corrsponding to IISMOD[10] := 1 */ + mask = 1 << i2s_regs->rclksrc_off; + + if ((i2s->quirks & QUIRK_NO_MUXPSR) + || (clk_id == SAMSUNG_I2S_RCLKSRC_0)) + clk_id = 0; + else + clk_id = 1; + + if (!any_active(i2s)) { + if (i2s->op_clk && !IS_ERR(i2s->op_clk)) { + if ((clk_id && !(mod & rsrc_mask)) || + (!clk_id && (mod & rsrc_mask))) { + clk_disable_unprepare(i2s->op_clk); + clk_put(i2s->op_clk); + } else { + i2s->rclk_srcrate = + clk_get_rate(i2s->op_clk); + goto done; + } + } + + if (clk_id) + i2s->op_clk = clk_get(&i2s->pdev->dev, + "i2s_opclk1"); + else + i2s->op_clk = clk_get(&i2s->pdev->dev, + "i2s_opclk0"); + + if (WARN_ON(IS_ERR(i2s->op_clk))) { + ret = PTR_ERR(i2s->op_clk); + i2s->op_clk = NULL; + goto err; + } + + ret = clk_prepare_enable(i2s->op_clk); + if (ret) { + clk_put(i2s->op_clk); + i2s->op_clk = NULL; + goto err; + } + i2s->rclk_srcrate = clk_get_rate(i2s->op_clk); + + /* Over-ride the other's */ + if (other) { + other->op_clk = i2s->op_clk; + other->rclk_srcrate = i2s->rclk_srcrate; + } + } else if ((!clk_id && (mod & rsrc_mask)) + || (clk_id && !(mod & rsrc_mask))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + ret = -EAGAIN; + goto err; + } else { + /* Call can't be on the active DAI */ + i2s->op_clk = other->op_clk; + i2s->rclk_srcrate = other->rclk_srcrate; + goto done; + } + + if (clk_id == 1) + val = 1 << i2s_regs->rclksrc_off; + break; + default: + dev_err(&i2s->pdev->dev, "We don't serve that!\n"); + ret = -EINVAL; + goto err; + } + + spin_lock_irqsave(i2s->lock, flags); + mod = readl(i2s->addr + I2SMOD); + mod = (mod & ~mask) | val; + writel(mod, i2s->addr + I2SMOD); + spin_unlock_irqrestore(i2s->lock, flags); +done: + pm_runtime_put(dai->dev); + + return 0; +err: + pm_runtime_put(dai->dev); + return ret; +} + +static int i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = get_other_dai(i2s); + int lrp_shift, sdf_shift, sdf_mask, lrp_rlow, mod_slave; + u32 mod, tmp = 0; + unsigned long flags; + + lrp_shift = i2s->variant_regs->lrp_off; + sdf_shift = i2s->variant_regs->sdf_off; + mod_slave = 1 << i2s->variant_regs->mss_off; + + sdf_mask = MOD_SDF_MASK << sdf_shift; + lrp_rlow = MOD_LR_RLOW << lrp_shift; + + /* Format is priority */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + tmp |= lrp_rlow; + tmp |= (MOD_SDF_MSB << sdf_shift); + break; + case SND_SOC_DAIFMT_LEFT_J: + tmp |= lrp_rlow; + tmp |= (MOD_SDF_LSB << sdf_shift); + break; + case SND_SOC_DAIFMT_I2S: + tmp |= (MOD_SDF_IIS << sdf_shift); + break; + default: + dev_err(&i2s->pdev->dev, "Format not supported\n"); + return -EINVAL; + } + + /* + * INV flag is relative to the FORMAT flag - if set it simply + * flips the polarity specified by the Standard + */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + if (tmp & lrp_rlow) + tmp &= ~lrp_rlow; + else + tmp |= lrp_rlow; + break; + default: + dev_err(&i2s->pdev->dev, "Polarity not supported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tmp |= mod_slave; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* + * Set default source clock in Master mode, only when the + * CLK_I2S_RCLK_SRC clock is not exposed so we ensure any + * clock configuration assigned in DT is not overwritten. + */ + if (i2s->rclk_srcrate == 0 && i2s->clk_data.clks == NULL && + other->clk_data.clks == NULL) + i2s_set_sysclk(dai, SAMSUNG_I2S_RCLKSRC_0, + 0, SND_SOC_CLOCK_IN); + break; + default: + dev_err(&i2s->pdev->dev, "master/slave format not supported\n"); + return -EINVAL; + } + + pm_runtime_get_sync(dai->dev); + spin_lock_irqsave(i2s->lock, flags); + mod = readl(i2s->addr + I2SMOD); + /* + * Don't change the I2S mode if any controller is active on this + * channel. + */ + if (any_active(i2s) && + ((mod & (sdf_mask | lrp_rlow | mod_slave)) != tmp)) { + spin_unlock_irqrestore(i2s->lock, flags); + pm_runtime_put(dai->dev); + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } + + mod &= ~(sdf_mask | lrp_rlow | mod_slave); + mod |= tmp; + writel(mod, i2s->addr + I2SMOD); + spin_unlock_irqrestore(i2s->lock, flags); + pm_runtime_put(dai->dev); + + return 0; +} + +static int i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = get_other_dai(i2s); + u32 mod, mask = 0, val = 0; + struct clk *rclksrc; + unsigned long flags; + + WARN_ON(!pm_runtime_active(dai->dev)); + + if (!is_secondary(i2s)) + mask |= (MOD_DC2_EN | MOD_DC1_EN); + + switch (params_channels(params)) { + case 6: + val |= MOD_DC2_EN; + /* fall through */ + case 4: + val |= MOD_DC1_EN; + break; + case 2: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s->dma_playback.addr_width = 4; + else + i2s->dma_capture.addr_width = 4; + break; + case 1: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s->dma_playback.addr_width = 2; + else + i2s->dma_capture.addr_width = 2; + + break; + default: + dev_err(&i2s->pdev->dev, "%d channels not supported\n", + params_channels(params)); + return -EINVAL; + } + + if (is_secondary(i2s)) + mask |= MOD_BLCS_MASK; + else + mask |= MOD_BLCP_MASK; + + if (is_manager(i2s)) + mask |= MOD_BLC_MASK; + + switch (params_width(params)) { + case 8: + if (is_secondary(i2s)) + val |= MOD_BLCS_8BIT; + else + val |= MOD_BLCP_8BIT; + if (is_manager(i2s)) + val |= MOD_BLC_8BIT; + break; + case 16: + if (is_secondary(i2s)) + val |= MOD_BLCS_16BIT; + else + val |= MOD_BLCP_16BIT; + if (is_manager(i2s)) + val |= MOD_BLC_16BIT; + break; + case 24: + if (is_secondary(i2s)) + val |= MOD_BLCS_24BIT; + else + val |= MOD_BLCP_24BIT; + if (is_manager(i2s)) + val |= MOD_BLC_24BIT; + break; + default: + dev_err(&i2s->pdev->dev, "Format(%d) not supported\n", + params_format(params)); + return -EINVAL; + } + + spin_lock_irqsave(i2s->lock, flags); + mod = readl(i2s->addr + I2SMOD); + mod = (mod & ~mask) | val; + writel(mod, i2s->addr + I2SMOD); + spin_unlock_irqrestore(i2s->lock, flags); + + snd_soc_dai_init_dma_data(dai, &i2s->dma_playback, &i2s->dma_capture); + + i2s->frmclk = params_rate(params); + + rclksrc = i2s->clk_table[CLK_I2S_RCLK_SRC]; + if (!rclksrc || IS_ERR(rclksrc)) + rclksrc = other->clk_table[CLK_I2S_RCLK_SRC]; + + if (rclksrc && !IS_ERR(rclksrc)) + i2s->rclk_srcrate = clk_get_rate(rclksrc); + + return 0; +} + +/* We set constraints on the substream acc to the version of I2S */ +static int i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = get_other_dai(i2s); + unsigned long flags; + + pm_runtime_get_sync(dai->dev); + + spin_lock_irqsave(&lock, flags); + + i2s->mode |= DAI_OPENED; + + if (is_manager(other)) + i2s->mode &= ~DAI_MANAGER; + else + i2s->mode |= DAI_MANAGER; + + if (!any_active(i2s) && (i2s->quirks & QUIRK_NEED_RSTCLR)) + writel(CON_RSTCLR, i2s->addr + I2SCON); + + spin_unlock_irqrestore(&lock, flags); + + return 0; +} + +static void i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = get_other_dai(i2s); + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + + i2s->mode &= ~DAI_OPENED; + i2s->mode &= ~DAI_MANAGER; + + if (is_opened(other)) + other->mode |= DAI_MANAGER; + + /* Reset any constraint on RFS and BFS */ + i2s->rfs = 0; + i2s->bfs = 0; + + spin_unlock_irqrestore(&lock, flags); + + pm_runtime_put(dai->dev); +} + +static int config_setup(struct i2s_dai *i2s) +{ + struct i2s_dai *other = get_other_dai(i2s); + unsigned rfs, bfs, blc; + u32 psr; + + blc = get_blc(i2s); + + bfs = i2s->bfs; + + if (!bfs && other) + bfs = other->bfs; + + /* Select least possible multiple(2) if no constraint set */ + if (!bfs) + bfs = blc * 2; + + rfs = i2s->rfs; + + if (!rfs && other) + rfs = other->rfs; + + if ((rfs == 256 || rfs == 512) && (blc == 24)) { + dev_err(&i2s->pdev->dev, + "%d-RFS not supported for 24-blc\n", rfs); + return -EINVAL; + } + + if (!rfs) { + if (bfs == 16 || bfs == 32) + rfs = 256; + else + rfs = 384; + } + + /* If already setup and running */ + if (any_active(i2s) && (get_rfs(i2s) != rfs || get_bfs(i2s) != bfs)) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } + + set_bfs(i2s, bfs); + set_rfs(i2s, rfs); + + /* Don't bother with PSR in Slave mode */ + if (is_slave(i2s)) + return 0; + + if (!(i2s->quirks & QUIRK_NO_MUXPSR)) { + psr = i2s->rclk_srcrate / i2s->frmclk / rfs; + writel(((psr - 1) << 8) | PSR_PSREN, i2s->addr + I2SPSR); + dev_dbg(&i2s->pdev->dev, + "RCLK_SRC=%luHz PSR=%u, RCLK=%dfs, BCLK=%dfs\n", + i2s->rclk_srcrate, psr, rfs, bfs); + } + + return 0; +} + +static int i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_dai *i2s = to_info(rtd->cpu_dai); + unsigned long flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pm_runtime_get_sync(dai->dev); + spin_lock_irqsave(i2s->lock, flags); + + if (config_setup(i2s)) { + spin_unlock_irqrestore(i2s->lock, flags); + return -EINVAL; + } + + if (capture) + i2s_rxctrl(i2s, 1); + else + i2s_txctrl(i2s, 1); + + spin_unlock_irqrestore(i2s->lock, flags); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(i2s->lock, flags); + + if (capture) { + i2s_rxctrl(i2s, 0); + i2s_fifo(i2s, FIC_RXFLUSH); + } else { + i2s_txctrl(i2s, 0); + i2s_fifo(i2s, FIC_TXFLUSH); + } + + spin_unlock_irqrestore(i2s->lock, flags); + pm_runtime_put(dai->dev); + break; + } + + return 0; +} + +static int i2s_set_clkdiv(struct snd_soc_dai *dai, + int div_id, int div) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = get_other_dai(i2s); + + switch (div_id) { + case SAMSUNG_I2S_DIV_BCLK: + pm_runtime_get_sync(dai->dev); + if ((any_active(i2s) && div && (get_bfs(i2s) != div)) + || (other && other->bfs && (other->bfs != div))) { + pm_runtime_put(dai->dev); + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } + i2s->bfs = div; + pm_runtime_put(dai->dev); + break; + default: + dev_err(&i2s->pdev->dev, + "Invalid clock divider(%d)\n", div_id); + return -EINVAL; + } + + return 0; +} + +static snd_pcm_sframes_t +i2s_delay(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + u32 reg = readl(i2s->addr + I2SFIC); + snd_pcm_sframes_t delay; + const struct samsung_i2s_variant_regs *i2s_regs = i2s->variant_regs; + + WARN_ON(!pm_runtime_active(dai->dev)); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + delay = FIC_RXCOUNT(reg); + else if (is_secondary(i2s)) + delay = FICS_TXCOUNT(readl(i2s->addr + I2SFICS)); + else + delay = (reg >> i2s_regs->ftx0cnt_off) & 0x7f; + + return delay; +} + +#ifdef CONFIG_PM +static int i2s_suspend(struct snd_soc_dai *dai) +{ + return pm_runtime_force_suspend(dai->dev); +} + +static int i2s_resume(struct snd_soc_dai *dai) +{ + return pm_runtime_force_resume(dai->dev); +} +#else +#define i2s_suspend NULL +#define i2s_resume NULL +#endif + +static int samsung_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = get_other_dai(i2s); + unsigned long flags; + + pm_runtime_get_sync(dai->dev); + + if (is_secondary(i2s)) { /* If this is probe on the secondary DAI */ + snd_soc_dai_init_dma_data(dai, &other->sec_dai->dma_playback, + NULL); + } else { + snd_soc_dai_init_dma_data(dai, &i2s->dma_playback, + &i2s->dma_capture); + + if (i2s->quirks & QUIRK_NEED_RSTCLR) + writel(CON_RSTCLR, i2s->addr + I2SCON); + + if (i2s->quirks & QUIRK_SUPPORTS_IDMA) + idma_reg_addr_init(i2s->addr, + i2s->sec_dai->idma_playback.addr); + } + + /* Reset any constraint on RFS and BFS */ + i2s->rfs = 0; + i2s->bfs = 0; + i2s->rclk_srcrate = 0; + + spin_lock_irqsave(i2s->lock, flags); + i2s_txctrl(i2s, 0); + i2s_rxctrl(i2s, 0); + i2s_fifo(i2s, FIC_TXFLUSH); + i2s_fifo(other, FIC_TXFLUSH); + i2s_fifo(i2s, FIC_RXFLUSH); + spin_unlock_irqrestore(i2s->lock, flags); + + /* Gate CDCLK by default */ + if (!is_opened(other)) + i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_IN); + pm_runtime_put(dai->dev); + + return 0; +} + +static int samsung_i2s_dai_remove(struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = snd_soc_dai_get_drvdata(dai); + unsigned long flags; + + pm_runtime_get_sync(dai->dev); + + if (!is_secondary(i2s)) { + if (i2s->quirks & QUIRK_NEED_RSTCLR) { + spin_lock_irqsave(i2s->lock, flags); + writel(0, i2s->addr + I2SCON); + spin_unlock_irqrestore(i2s->lock, flags); + } + } + + pm_runtime_put(dai->dev); + + return 0; +} + +static const struct snd_soc_dai_ops samsung_i2s_dai_ops = { + .trigger = i2s_trigger, + .hw_params = i2s_hw_params, + .set_fmt = i2s_set_fmt, + .set_clkdiv = i2s_set_clkdiv, + .set_sysclk = i2s_set_sysclk, + .startup = i2s_startup, + .shutdown = i2s_shutdown, + .delay = i2s_delay, +}; + +static const struct snd_soc_component_driver samsung_i2s_component = { + .name = "samsung-i2s", +}; + +#define SAMSUNG_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, + const struct samsung_i2s_dai_data *i2s_dai_data, + bool sec) +{ + struct i2s_dai *i2s; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct i2s_dai), GFP_KERNEL); + if (i2s == NULL) + return NULL; + + i2s->pdev = pdev; + i2s->pri_dai = NULL; + i2s->sec_dai = NULL; + i2s->i2s_dai_drv.id = 1; + i2s->i2s_dai_drv.symmetric_rates = 1; + i2s->i2s_dai_drv.probe = samsung_i2s_dai_probe; + i2s->i2s_dai_drv.remove = samsung_i2s_dai_remove; + i2s->i2s_dai_drv.ops = &samsung_i2s_dai_ops; + i2s->i2s_dai_drv.suspend = i2s_suspend; + i2s->i2s_dai_drv.resume = i2s_resume; + i2s->i2s_dai_drv.playback.channels_min = 1; + i2s->i2s_dai_drv.playback.channels_max = 2; + i2s->i2s_dai_drv.playback.rates = i2s_dai_data->pcm_rates; + i2s->i2s_dai_drv.playback.formats = SAMSUNG_I2S_FMTS; + + if (!sec) { + i2s->i2s_dai_drv.name = SAMSUNG_I2S_DAI; + i2s->i2s_dai_drv.capture.channels_min = 1; + i2s->i2s_dai_drv.capture.channels_max = 2; + i2s->i2s_dai_drv.capture.rates = i2s_dai_data->pcm_rates; + i2s->i2s_dai_drv.capture.formats = SAMSUNG_I2S_FMTS; + } else { + i2s->i2s_dai_drv.name = SAMSUNG_I2S_DAI_SEC; + } + return i2s; +} + +#ifdef CONFIG_PM +static int i2s_runtime_suspend(struct device *dev) +{ + struct i2s_dai *i2s = dev_get_drvdata(dev); + + i2s->suspend_i2smod = readl(i2s->addr + I2SMOD); + i2s->suspend_i2scon = readl(i2s->addr + I2SCON); + i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR); + + if (i2s->op_clk) + clk_disable_unprepare(i2s->op_clk); + clk_disable_unprepare(i2s->clk); + + return 0; +} + +static int i2s_runtime_resume(struct device *dev) +{ + struct i2s_dai *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->clk); + if (ret) + return ret; + + if (i2s->op_clk) { + ret = clk_prepare_enable(i2s->op_clk); + if (ret) { + clk_disable_unprepare(i2s->clk); + return ret; + } + } + + writel(i2s->suspend_i2scon, i2s->addr + I2SCON); + writel(i2s->suspend_i2smod, i2s->addr + I2SMOD); + writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR); + + return 0; +} +#endif /* CONFIG_PM */ + +static void i2s_unregister_clocks(struct i2s_dai *i2s) +{ + int i; + + for (i = 0; i < i2s->clk_data.clk_num; i++) { + if (!IS_ERR(i2s->clk_table[i])) + clk_unregister(i2s->clk_table[i]); + } +} + +static void i2s_unregister_clock_provider(struct platform_device *pdev) +{ + struct i2s_dai *i2s = dev_get_drvdata(&pdev->dev); + + of_clk_del_provider(pdev->dev.of_node); + i2s_unregister_clocks(i2s); +} + +static int i2s_register_clock_provider(struct platform_device *pdev) +{ + const char * const i2s_clk_desc[] = { "cdclk", "rclk_src", "prescaler" }; + const char *clk_name[2] = { "i2s_opclk0", "i2s_opclk1" }; + const char *p_names[2] = { NULL }; + struct device *dev = &pdev->dev; + struct i2s_dai *i2s = dev_get_drvdata(dev); + const struct samsung_i2s_variant_regs *reg_info = i2s->variant_regs; + const char *i2s_clk_name[ARRAY_SIZE(i2s_clk_desc)]; + struct clk *rclksrc; + int ret, i; + + /* Register the clock provider only if it's expected in the DTB */ + if (!of_find_property(dev->of_node, "#clock-cells", NULL)) + return 0; + + /* Get the RCLKSRC mux clock parent clock names */ + for (i = 0; i < ARRAY_SIZE(p_names); i++) { + rclksrc = clk_get(dev, clk_name[i]); + if (IS_ERR(rclksrc)) + continue; + p_names[i] = __clk_get_name(rclksrc); + clk_put(rclksrc); + } + + for (i = 0; i < ARRAY_SIZE(i2s_clk_desc); i++) { + i2s_clk_name[i] = devm_kasprintf(dev, GFP_KERNEL, "%s_%s", + dev_name(dev), i2s_clk_desc[i]); + if (!i2s_clk_name[i]) + return -ENOMEM; + } + + if (!(i2s->quirks & QUIRK_NO_MUXPSR)) { + /* Activate the prescaler */ + u32 val = readl(i2s->addr + I2SPSR); + writel(val | PSR_PSREN, i2s->addr + I2SPSR); + + i2s->clk_table[CLK_I2S_RCLK_SRC] = clk_register_mux(dev, + i2s_clk_name[CLK_I2S_RCLK_SRC], p_names, + ARRAY_SIZE(p_names), + CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, + i2s->addr + I2SMOD, reg_info->rclksrc_off, + 1, 0, i2s->lock); + + i2s->clk_table[CLK_I2S_RCLK_PSR] = clk_register_divider(dev, + i2s_clk_name[CLK_I2S_RCLK_PSR], + i2s_clk_name[CLK_I2S_RCLK_SRC], + CLK_SET_RATE_PARENT, + i2s->addr + I2SPSR, 8, 6, 0, i2s->lock); + + p_names[0] = i2s_clk_name[CLK_I2S_RCLK_PSR]; + i2s->clk_data.clk_num = 2; + } + + i2s->clk_table[CLK_I2S_CDCLK] = clk_register_gate(dev, + i2s_clk_name[CLK_I2S_CDCLK], p_names[0], + CLK_SET_RATE_PARENT, + i2s->addr + I2SMOD, reg_info->cdclkcon_off, + CLK_GATE_SET_TO_DISABLE, i2s->lock); + + i2s->clk_data.clk_num += 1; + i2s->clk_data.clks = i2s->clk_table; + + ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, + &i2s->clk_data); + if (ret < 0) { + dev_err(dev, "failed to add clock provider: %d\n", ret); + i2s_unregister_clocks(i2s); + } + + return ret; +} + +static int samsung_i2s_probe(struct platform_device *pdev) +{ + struct i2s_dai *pri_dai, *sec_dai = NULL; + struct s3c_audio_pdata *i2s_pdata = pdev->dev.platform_data; + struct resource *res; + u32 regs_base, quirks = 0, idma_addr = 0; + struct device_node *np = pdev->dev.of_node; + const struct samsung_i2s_dai_data *i2s_dai_data; + int ret; + + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) + i2s_dai_data = of_device_get_match_data(&pdev->dev); + else + i2s_dai_data = (struct samsung_i2s_dai_data *) + platform_get_device_id(pdev)->driver_data; + + pri_dai = i2s_alloc_dai(pdev, i2s_dai_data, false); + if (!pri_dai) { + dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); + return -ENOMEM; + } + + spin_lock_init(&pri_dai->spinlock); + pri_dai->lock = &pri_dai->spinlock; + + if (!np) { + if (i2s_pdata == NULL) { + dev_err(&pdev->dev, "Can't work without s3c_audio_pdata\n"); + return -EINVAL; + } + + pri_dai->dma_playback.filter_data = i2s_pdata->dma_playback; + pri_dai->dma_capture.filter_data = i2s_pdata->dma_capture; + pri_dai->filter = i2s_pdata->dma_filter; + + quirks = i2s_pdata->type.quirks; + idma_addr = i2s_pdata->type.idma_addr; + } else { + quirks = i2s_dai_data->quirks; + if (of_property_read_u32(np, "samsung,idma-addr", + &idma_addr)) { + if (quirks & QUIRK_SUPPORTS_IDMA) { + dev_info(&pdev->dev, "idma address is not"\ + "specified"); + } + } + } + quirks &= ~(QUIRK_SEC_DAI | QUIRK_SUPPORTS_IDMA); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pri_dai->addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pri_dai->addr)) + return PTR_ERR(pri_dai->addr); + + regs_base = res->start; + + pri_dai->clk = devm_clk_get(&pdev->dev, "iis"); + if (IS_ERR(pri_dai->clk)) { + dev_err(&pdev->dev, "Failed to get iis clock\n"); + return PTR_ERR(pri_dai->clk); + } + + ret = clk_prepare_enable(pri_dai->clk); + if (ret != 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + pri_dai->dma_playback.addr = regs_base + I2STXD; + pri_dai->dma_capture.addr = regs_base + I2SRXD; + pri_dai->dma_playback.chan_name = "tx"; + pri_dai->dma_capture.chan_name = "rx"; + pri_dai->dma_playback.addr_width = 4; + pri_dai->dma_capture.addr_width = 4; + pri_dai->quirks = quirks; + pri_dai->variant_regs = i2s_dai_data->i2s_variant_regs; + + if (quirks & QUIRK_PRI_6CHAN) + pri_dai->i2s_dai_drv.playback.channels_max = 6; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter, + NULL, NULL); + if (ret < 0) + goto err_disable_clk; + + ret = devm_snd_soc_register_component(&pdev->dev, + &samsung_i2s_component, + &pri_dai->i2s_dai_drv, 1); + if (ret < 0) + goto err_disable_clk; + + if (quirks & QUIRK_SEC_DAI) { + sec_dai = i2s_alloc_dai(pdev, i2s_dai_data, true); + if (!sec_dai) { + dev_err(&pdev->dev, "Unable to alloc I2S_sec\n"); + ret = -ENOMEM; + goto err_disable_clk; + } + + sec_dai->lock = &pri_dai->spinlock; + sec_dai->variant_regs = pri_dai->variant_regs; + sec_dai->dma_playback.addr = regs_base + I2STXDS; + sec_dai->dma_playback.chan_name = "tx-sec"; + + if (!np) { + sec_dai->dma_playback.filter_data = i2s_pdata->dma_play_sec; + sec_dai->filter = i2s_pdata->dma_filter; + } + + sec_dai->dma_playback.addr_width = 4; + sec_dai->addr = pri_dai->addr; + sec_dai->clk = pri_dai->clk; + sec_dai->quirks = quirks; + sec_dai->idma_playback.addr = idma_addr; + sec_dai->pri_dai = pri_dai; + pri_dai->sec_dai = sec_dai; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, + sec_dai->filter, "tx-sec", NULL); + if (ret < 0) + goto err_disable_clk; + + ret = devm_snd_soc_register_component(&pdev->dev, + &samsung_i2s_component, + &sec_dai->i2s_dai_drv, 1); + if (ret < 0) + goto err_disable_clk; + } + + if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto err_disable_clk; + } + + dev_set_drvdata(&pdev->dev, pri_dai); + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = i2s_register_clock_provider(pdev); + if (ret < 0) + goto err_disable_pm; + + pri_dai->op_clk = clk_get_parent(pri_dai->clk_table[CLK_I2S_RCLK_SRC]); + + return 0; + +err_disable_pm: + pm_runtime_disable(&pdev->dev); +err_disable_clk: + clk_disable_unprepare(pri_dai->clk); + return ret; +} + +static int samsung_i2s_remove(struct platform_device *pdev) +{ + struct i2s_dai *pri_dai; + + pri_dai = dev_get_drvdata(&pdev->dev); + + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + i2s_unregister_clock_provider(pdev); + clk_disable_unprepare(pri_dai->clk); + pm_runtime_put_noidle(&pdev->dev); + + return 0; +} + +static const struct samsung_i2s_variant_regs i2sv3_regs = { + .bfs_off = 1, + .rfs_off = 3, + .sdf_off = 5, + .txr_off = 8, + .rclksrc_off = 10, + .mss_off = 11, + .cdclkcon_off = 12, + .lrp_off = 7, + .bfs_mask = 0x3, + .rfs_mask = 0x3, + .ftx0cnt_off = 8, +}; + +static const struct samsung_i2s_variant_regs i2sv6_regs = { + .bfs_off = 0, + .rfs_off = 4, + .sdf_off = 6, + .txr_off = 8, + .rclksrc_off = 10, + .mss_off = 11, + .cdclkcon_off = 12, + .lrp_off = 15, + .bfs_mask = 0xf, + .rfs_mask = 0x3, + .ftx0cnt_off = 8, +}; + +static const struct samsung_i2s_variant_regs i2sv7_regs = { + .bfs_off = 0, + .rfs_off = 4, + .sdf_off = 7, + .txr_off = 9, + .rclksrc_off = 11, + .mss_off = 12, + .cdclkcon_off = 22, + .lrp_off = 15, + .bfs_mask = 0xf, + .rfs_mask = 0x7, + .ftx0cnt_off = 0, +}; + +static const struct samsung_i2s_variant_regs i2sv5_i2s1_regs = { + .bfs_off = 0, + .rfs_off = 3, + .sdf_off = 6, + .txr_off = 8, + .rclksrc_off = 10, + .mss_off = 11, + .cdclkcon_off = 12, + .lrp_off = 15, + .bfs_mask = 0x7, + .rfs_mask = 0x7, + .ftx0cnt_off = 8, +}; + +static const struct samsung_i2s_dai_data i2sv3_dai_type = { + .quirks = QUIRK_NO_MUXPSR, + .pcm_rates = SNDRV_PCM_RATE_8000_96000, + .i2s_variant_regs = &i2sv3_regs, +}; + +static const struct samsung_i2s_dai_data i2sv5_dai_type = { + .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | + QUIRK_SUPPORTS_IDMA, + .pcm_rates = SNDRV_PCM_RATE_8000_96000, + .i2s_variant_regs = &i2sv3_regs, +}; + +static const struct samsung_i2s_dai_data i2sv6_dai_type = { + .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | + QUIRK_SUPPORTS_TDM | QUIRK_SUPPORTS_IDMA, + .pcm_rates = SNDRV_PCM_RATE_8000_96000, + .i2s_variant_regs = &i2sv6_regs, +}; + +static const struct samsung_i2s_dai_data i2sv7_dai_type = { + .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | + QUIRK_SUPPORTS_TDM, + .pcm_rates = SNDRV_PCM_RATE_8000_192000, + .i2s_variant_regs = &i2sv7_regs, +}; + +static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 = { + .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR, + .pcm_rates = SNDRV_PCM_RATE_8000_96000, + .i2s_variant_regs = &i2sv5_i2s1_regs, +}; + +static const struct platform_device_id samsung_i2s_driver_ids[] = { + { + .name = "samsung-i2s", + .driver_data = (kernel_ulong_t)&i2sv3_dai_type, + }, + {}, +}; +MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids); + +#ifdef CONFIG_OF +static const struct of_device_id exynos_i2s_match[] = { + { + .compatible = "samsung,s3c6410-i2s", + .data = &i2sv3_dai_type, + }, { + .compatible = "samsung,s5pv210-i2s", + .data = &i2sv5_dai_type, + }, { + .compatible = "samsung,exynos5420-i2s", + .data = &i2sv6_dai_type, + }, { + .compatible = "samsung,exynos7-i2s", + .data = &i2sv7_dai_type, + }, { + .compatible = "samsung,exynos7-i2s1", + .data = &i2sv5_dai_type_i2s1, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_i2s_match); +#endif + +static const struct dev_pm_ops samsung_i2s_pm = { + SET_RUNTIME_PM_OPS(i2s_runtime_suspend, + i2s_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver samsung_i2s_driver = { + .probe = samsung_i2s_probe, + .remove = samsung_i2s_remove, + .id_table = samsung_i2s_driver_ids, + .driver = { + .name = "samsung-i2s", + .of_match_table = of_match_ptr(exynos_i2s_match), + .pm = &samsung_i2s_pm, + }, +}; + +module_platform_driver(samsung_i2s_driver); + +/* Module information */ +MODULE_AUTHOR("Jaswinder Singh, "); +MODULE_DESCRIPTION("Samsung I2S Interface"); +MODULE_ALIAS("platform:samsung-i2s"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/i2s.h b/sound/soc/samsung/i2s.h new file mode 100644 index 000000000..a9832a955 --- /dev/null +++ b/sound/soc/samsung/i2s.h @@ -0,0 +1,31 @@ +/* sound/soc/samsung/i2s.h + * + * ALSA SoC Audio Layer - Samsung I2S Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd. + * Jaswinder Singh + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_SAMSUNG_I2S_H +#define __SND_SOC_SAMSUNG_I2S_H + +#define SAMSUNG_I2S_DAI "samsung-i2s" +#define SAMSUNG_I2S_DAI_SEC "samsung-i2s-sec" + +#define SAMSUNG_I2S_DIV_BCLK 1 + +#define SAMSUNG_I2S_RCLKSRC_0 0 +#define SAMSUNG_I2S_RCLKSRC_1 1 +#define SAMSUNG_I2S_CDCLK 2 +/* Operation clock for IIS logic */ +#define SAMSUNG_I2S_OPCLK 3 +#define SAMSUNG_I2S_OPCLK_CDCLK_OUT 0 /* CODEC clock out */ +#define SAMSUNG_I2S_OPCLK_CDCLK_IN 1 /* CODEC clock in */ +#define SAMSUNG_I2S_OPCLK_BCLK_OUT 2 /* Bit clock out */ +#define SAMSUNG_I2S_OPCLK_PCLK 3 /* Audio bus clock */ + +#endif /* __SND_SOC_SAMSUNG_I2S_H */ diff --git a/sound/soc/samsung/idma.c b/sound/soc/samsung/idma.c new file mode 100644 index 000000000..e397f5e10 --- /dev/null +++ b/sound/soc/samsung/idma.c @@ -0,0 +1,432 @@ +/* + * sound/soc/samsung/idma.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * I2S0's Internal DMA driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2s.h" +#include "idma.h" +#include "i2s-regs.h" + +#define ST_RUNNING (1<<0) +#define ST_OPENED (1<<1) + +static const struct snd_pcm_hardware idma_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = MAX_IDMA_BUFFER, + .period_bytes_min = 128, + .period_bytes_max = MAX_IDMA_PERIOD, + .periods_min = 1, + .periods_max = 2, +}; + +struct idma_ctrl { + spinlock_t lock; + int state; + dma_addr_t start; + dma_addr_t pos; + dma_addr_t end; + dma_addr_t period; + dma_addr_t periodsz; + void *token; + void (*cb)(void *dt, int bytes_xfer); +}; + +static struct idma_info { + spinlock_t lock; + void __iomem *regs; + dma_addr_t lp_tx_addr; +} idma; + +static int idma_irq; + +static void idma_getpos(dma_addr_t *src) +{ + *src = idma.lp_tx_addr + + (readl(idma.regs + I2STRNCNT) & 0xffffff) * 4; +} + +static int idma_enqueue(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct idma_ctrl *prtd = substream->runtime->private_data; + u32 val; + + spin_lock(&prtd->lock); + prtd->token = (void *) substream; + spin_unlock(&prtd->lock); + + /* Internal DMA Level0 Interrupt Address */ + val = idma.lp_tx_addr + prtd->periodsz; + writel(val, idma.regs + I2SLVL0ADDR); + + /* Start address0 of I2S internal DMA operation. */ + val = idma.lp_tx_addr; + writel(val, idma.regs + I2SSTR0); + + /* + * Transfer block size for I2S internal DMA. + * Should decide transfer size before start dma operation + */ + val = readl(idma.regs + I2SSIZE); + val &= ~(I2SSIZE_TRNMSK << I2SSIZE_SHIFT); + val |= (((runtime->dma_bytes >> 2) & + I2SSIZE_TRNMSK) << I2SSIZE_SHIFT); + writel(val, idma.regs + I2SSIZE); + + val = readl(idma.regs + I2SAHB); + val |= AHB_INTENLVL0; + writel(val, idma.regs + I2SAHB); + + return 0; +} + +static void idma_setcallbk(struct snd_pcm_substream *substream, + void (*cb)(void *, int)) +{ + struct idma_ctrl *prtd = substream->runtime->private_data; + + spin_lock(&prtd->lock); + prtd->cb = cb; + spin_unlock(&prtd->lock); +} + +static void idma_control(int op) +{ + u32 val = readl(idma.regs + I2SAHB); + + spin_lock(&idma.lock); + + switch (op) { + case LPAM_DMA_START: + val |= (AHB_INTENLVL0 | AHB_DMAEN); + break; + case LPAM_DMA_STOP: + val &= ~(AHB_INTENLVL0 | AHB_DMAEN); + break; + default: + spin_unlock(&idma.lock); + return; + } + + writel(val, idma.regs + I2SAHB); + spin_unlock(&idma.lock); +} + +static void idma_done(void *id, int bytes_xfer) +{ + struct snd_pcm_substream *substream = id; + struct idma_ctrl *prtd = substream->runtime->private_data; + + if (prtd && (prtd->state & ST_RUNNING)) + snd_pcm_period_elapsed(substream); +} + +static int idma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct idma_ctrl *prtd = substream->runtime->private_data; + u32 mod = readl(idma.regs + I2SMOD); + u32 ahb = readl(idma.regs + I2SAHB); + + ahb |= (AHB_DMARLD | AHB_INTMASK); + mod |= MOD_TXS_IDMA; + writel(ahb, idma.regs + I2SAHB); + writel(mod, idma.regs + I2SMOD); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->start = prtd->pos = runtime->dma_addr; + prtd->period = params_periods(params); + prtd->periodsz = params_period_bytes(params); + prtd->end = runtime->dma_addr + runtime->dma_bytes; + + idma_setcallbk(substream, idma_done); + + return 0; +} + +static int idma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int idma_prepare(struct snd_pcm_substream *substream) +{ + struct idma_ctrl *prtd = substream->runtime->private_data; + + prtd->pos = prtd->start; + + /* flush the DMA channel */ + idma_control(LPAM_DMA_STOP); + idma_enqueue(substream); + + return 0; +} + +static int idma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct idma_ctrl *prtd = substream->runtime->private_data; + int ret = 0; + + spin_lock(&prtd->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + prtd->state |= ST_RUNNING; + idma_control(LPAM_DMA_START); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + prtd->state &= ~ST_RUNNING; + idma_control(LPAM_DMA_STOP); + break; + + default: + ret = -EINVAL; + break; + } + + spin_unlock(&prtd->lock); + + return ret; +} + +static snd_pcm_uframes_t + idma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct idma_ctrl *prtd = runtime->private_data; + dma_addr_t src; + unsigned long res; + + spin_lock(&prtd->lock); + + idma_getpos(&src); + res = src - prtd->start; + + spin_unlock(&prtd->lock); + + return bytes_to_frames(substream->runtime, res); +} + +static int idma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long size, offset; + int ret; + + /* From snd_pcm_lib_mmap_iomem */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + size = vma->vm_end - vma->vm_start; + offset = vma->vm_pgoff << PAGE_SHIFT; + ret = io_remap_pfn_range(vma, vma->vm_start, + (runtime->dma_addr + offset) >> PAGE_SHIFT, + size, vma->vm_page_prot); + + return ret; +} + +static irqreturn_t iis_irq(int irqno, void *dev_id) +{ + struct idma_ctrl *prtd = (struct idma_ctrl *)dev_id; + u32 iisahb, val, addr; + + iisahb = readl(idma.regs + I2SAHB); + + val = (iisahb & AHB_LVL0INT) ? AHB_CLRLVL0INT : 0; + + if (val) { + iisahb |= val; + writel(iisahb, idma.regs + I2SAHB); + + addr = readl(idma.regs + I2SLVL0ADDR) - idma.lp_tx_addr; + addr += prtd->periodsz; + addr %= (u32)(prtd->end - prtd->start); + addr += idma.lp_tx_addr; + + writel(addr, idma.regs + I2SLVL0ADDR); + + if (prtd->cb) + prtd->cb(prtd->token, prtd->period); + } + + return IRQ_HANDLED; +} + +static int idma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct idma_ctrl *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &idma_hardware); + + prtd = kzalloc(sizeof(struct idma_ctrl), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + ret = request_irq(idma_irq, iis_irq, 0, "i2s", prtd); + if (ret < 0) { + pr_err("fail to claim i2s irq , ret = %d\n", ret); + kfree(prtd); + return ret; + } + + spin_lock_init(&prtd->lock); + + runtime->private_data = prtd; + + return 0; +} + +static int idma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct idma_ctrl *prtd = runtime->private_data; + + free_irq(idma_irq, prtd); + + if (!prtd) + pr_err("idma_close called with prtd == NULL\n"); + + kfree(prtd); + + return 0; +} + +static const struct snd_pcm_ops idma_ops = { + .open = idma_open, + .close = idma_close, + .ioctl = snd_pcm_lib_ioctl, + .trigger = idma_trigger, + .pointer = idma_pointer, + .mmap = idma_mmap, + .hw_params = idma_hw_params, + .hw_free = idma_hw_free, + .prepare = idma_prepare, +}; + +static void idma_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (!substream) + return; + + buf = &substream->dma_buffer; + if (!buf->area) + return; + + iounmap((void __iomem *)buf->area); + + buf->area = NULL; + buf->addr = 0; +} + +static int preallocate_idma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + /* Assign PCM buffer pointers */ + buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS; + buf->addr = idma.lp_tx_addr; + buf->bytes = idma_hardware.buffer_bytes_max; + buf->area = (unsigned char * __force)ioremap(buf->addr, buf->bytes); + if (!buf->area) + return -ENOMEM; + + return 0; +} + +static int idma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = preallocate_idma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + } + + return ret; +} + +void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr) +{ + spin_lock_init(&idma.lock); + idma.regs = regs; + idma.lp_tx_addr = addr; +} +EXPORT_SYMBOL_GPL(idma_reg_addr_init); + +static const struct snd_soc_component_driver asoc_idma_platform = { + .ops = &idma_ops, + .pcm_new = idma_new, + .pcm_free = idma_free, +}; + +static int asoc_idma_platform_probe(struct platform_device *pdev) +{ + idma_irq = platform_get_irq(pdev, 0); + if (idma_irq < 0) + return idma_irq; + + return devm_snd_soc_register_component(&pdev->dev, &asoc_idma_platform, + NULL, 0); +} + +static struct platform_driver asoc_idma_driver = { + .driver = { + .name = "samsung-idma", + }, + + .probe = asoc_idma_platform_probe, +}; + +module_platform_driver(asoc_idma_driver); + +MODULE_AUTHOR("Jaswinder Singh, "); +MODULE_DESCRIPTION("Samsung ASoC IDMA Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/idma.h b/sound/soc/samsung/idma.h new file mode 100644 index 000000000..864494697 --- /dev/null +++ b/sound/soc/samsung/idma.h @@ -0,0 +1,26 @@ +/* + * sound/soc/samsung/idma.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __SND_SOC_SAMSUNG_IDMA_H_ +#define __SND_SOC_SAMSUNG_IDMA_H_ + +extern void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr); + +/* dma_state */ +#define LPAM_DMA_STOP 0 +#define LPAM_DMA_START 1 + +#define MAX_IDMA_PERIOD (128 * 1024) +#define MAX_IDMA_BUFFER (160 * 1024) + +#endif /* __SND_SOC_SAMSUNG_IDMA_H_ */ diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c new file mode 100644 index 000000000..529b10dc5 --- /dev/null +++ b/sound/soc/samsung/jive_wm8750.c @@ -0,0 +1,146 @@ +/* sound/soc/samsung/jive_wm8750.c + * + * Copyright 2007,2008 Simtec Electronics + * + * Based on sound/soc/pxa/spitz.c + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include + +#include + +#include "s3c2412-i2s.h" +#include "../codecs/wm8750.h" + +static const struct snd_soc_dapm_route audio_map[] = { + { "Headphone Jack", NULL, "LOUT1" }, + { "Headphone Jack", NULL, "ROUT1" }, + { "Internal Speaker", NULL, "LOUT2" }, + { "Internal Speaker", NULL, "ROUT2" }, + { "LINPUT1", NULL, "Line Input" }, + { "RINPUT1", NULL, "Line Input" }, +}; + +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Internal Speaker", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static int jive_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct s3c_i2sv2_rate_calc div; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params), + s3c_i2sv2_get_clock(cpu_dai)); + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_RCLK, div.fs_div); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_PRESCALER, + div.clk_div - 1); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops jive_ops = { + .hw_params = jive_hw_params, +}; + +static struct snd_soc_dai_link jive_dai = { + .name = "wm8750", + .stream_name = "WM8750", + .cpu_dai_name = "s3c2412-i2s", + .codec_dai_name = "wm8750-hifi", + .platform_name = "s3c2412-i2s", + .codec_name = "wm8750.0-001a", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &jive_ops, +}; + +/* jive audio machine driver */ +static struct snd_soc_card snd_soc_machine_jive = { + .name = "Jive", + .owner = THIS_MODULE, + .dai_link = &jive_dai, + .num_links = 1, + + .dapm_widgets = wm8750_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .fully_routed = true, +}; + +static struct platform_device *jive_snd_device; + +static int __init jive_init(void) +{ + int ret; + + if (!machine_is_jive()) + return 0; + + printk("JIVE WM8750 Audio support\n"); + + jive_snd_device = platform_device_alloc("soc-audio", -1); + if (!jive_snd_device) + return -ENOMEM; + + platform_set_drvdata(jive_snd_device, &snd_soc_machine_jive); + ret = platform_device_add(jive_snd_device); + + if (ret) + platform_device_put(jive_snd_device); + + return ret; +} + +static void __exit jive_exit(void) +{ + platform_device_unregister(jive_snd_device); +} + +module_init(jive_init); +module_exit(jive_exit); + +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("ALSA SoC Jive Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/littlemill.c b/sound/soc/samsung/littlemill.c new file mode 100644 index 000000000..087f8d738 --- /dev/null +++ b/sound/soc/samsung/littlemill.c @@ -0,0 +1,348 @@ +/* + * Littlemill audio support + * + * Copyright 2011 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "../codecs/wm8994.h" + +static int sample_rate = 44100; + +static int littlemill_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *aif1_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + aif1_dai = rtd->codec_dai; + + if (dapm->dev != aif1_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + /* + * If we've not already clocked things via hw_params() + * then do so now, otherwise these are noops. + */ + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK2, 32768, + sample_rate * 512); + if (ret < 0) { + pr_err("Failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_FLL1, + sample_rate * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to set SYSCLK: %d\n", ret); + return ret; + } + } + break; + + default: + break; + } + + return 0; +} + +static int littlemill_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *aif1_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + aif1_dai = rtd->codec_dai; + + if (dapm->dev != aif1_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to switch away from FLL1: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + 0, 0, 0); + if (ret < 0) { + pr_err("Failed to stop FLL1: %d\n", ret); + return ret; + } + break; + + default: + break; + } + + dapm->bias_level = level; + + return 0; +} + +static int littlemill_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + sample_rate = params_rate(params); + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK2, 32768, + sample_rate * 512); + if (ret < 0) { + pr_err("Failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8994_SYSCLK_FLL1, + sample_rate * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to set SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops littlemill_ops = { + .hw_params = littlemill_hw_params, +}; + +static const struct snd_soc_pcm_stream baseband_params = { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 2, + .channels_max = 2, +}; + +static struct snd_soc_dai_link littlemill_dai[] = { + { + .name = "CPU", + .stream_name = "CPU", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-i2s.0", + .codec_name = "wm8994-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &littlemill_ops, + }, + { + .name = "Baseband", + .stream_name = "Baseband", + .cpu_dai_name = "wm8994-aif2", + .codec_dai_name = "wm1250-ev1", + .codec_name = "wm1250-ev1.1-0027", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .params = &baseband_params, + }, +}; + +static int bbclk_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *aif2_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name); + aif2_dai = rtd->cpu_dai; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, + WM8994_FLL_SRC_BCLK, 64 * 8000, + 8000 * 256); + if (ret < 0) { + pr_err("Failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2, + 8000 * 256, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to set SYSCLK: %d\n", ret); + return ret; + } + break; + case SND_SOC_DAPM_POST_PMD: + ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to switch away from FLL2: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, + 0, 0, 0); + if (ret < 0) { + pr_err("Failed to stop FLL2: %d\n", ret); + return ret; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("WM1250 Input"), + SOC_DAPM_PIN_SWITCH("WM1250 Output"), +}; + +static struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), + + SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0, + bbclk_ev, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static struct snd_soc_dapm_route audio_paths[] = { + { "Headphone", NULL, "HPOUT1L" }, + { "Headphone", NULL, "HPOUT1R" }, + + { "AMIC", NULL, "MICBIAS1" }, /* Default for AMICBIAS jumper */ + { "IN1LN", NULL, "AMIC" }, + + { "DMIC", NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */ + { "DMIC1DAT", NULL, "DMIC" }, + { "DMIC2DAT", NULL, "DMIC" }, + + { "AIF2CLK", NULL, "Baseband Clock" }, +}; + +static struct snd_soc_jack littlemill_headset; + +static int littlemill_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; + struct snd_soc_dai *aif1_dai; + struct snd_soc_dai *aif2_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + component = rtd->codec_dai->component; + aif1_dai = rtd->codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name); + aif2_dai = rtd->cpu_dai; + + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_card_jack_new(card, "Headset", + SND_JACK_HEADSET | SND_JACK_MECHANICAL | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | + SND_JACK_BTN_4 | SND_JACK_BTN_5, + &littlemill_headset, NULL, 0); + if (ret) + return ret; + + /* This will check device compatibility itself */ + wm8958_mic_detect(component, &littlemill_headset, NULL, NULL, NULL, NULL); + + /* As will this */ + wm8994_mic_detect(component, &littlemill_headset, 1); + + return 0; +} + +static struct snd_soc_card littlemill = { + .name = "Littlemill", + .owner = THIS_MODULE, + .dai_link = littlemill_dai, + .num_links = ARRAY_SIZE(littlemill_dai), + + .set_bias_level = littlemill_set_bias_level, + .set_bias_level_post = littlemill_set_bias_level_post, + + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = audio_paths, + .num_dapm_routes = ARRAY_SIZE(audio_paths), + + .late_probe = littlemill_late_probe, +}; + +static int littlemill_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &littlemill; + int ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static struct platform_driver littlemill_driver = { + .driver = { + .name = "littlemill", + .pm = &snd_soc_pm_ops, + }, + .probe = littlemill_probe, +}; + +module_platform_driver(littlemill_driver); + +MODULE_DESCRIPTION("Littlemill audio support"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:littlemill"); diff --git a/sound/soc/samsung/lowland.c b/sound/soc/samsung/lowland.c new file mode 100644 index 000000000..c9081f42f --- /dev/null +++ b/sound/soc/samsung/lowland.c @@ -0,0 +1,205 @@ +/* + * Lowland audio support + * + * Copyright 2011 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "../codecs/wm5100.h" +#include "../codecs/wm9081.h" + +#define MCLK1_RATE (44100 * 512) +#define CLKOUT_RATE (44100 * 256) + +static struct snd_soc_jack lowland_headset; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin lowland_headset_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE | SND_JACK_LINEOUT, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + int ret; + + ret = snd_soc_component_set_sysclk(component, WM5100_CLK_SYSCLK, + WM5100_CLKSRC_MCLK1, MCLK1_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to set SYSCLK clock source: %d\n", ret); + return ret; + } + + /* Clock OPCLK, used by the other audio components. */ + ret = snd_soc_component_set_sysclk(component, WM5100_CLK_OPCLK, 0, + CLKOUT_RATE, 0); + if (ret < 0) { + pr_err("Failed to set OPCLK rate: %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new(rtd->card, "Headset", SND_JACK_LINEOUT | + SND_JACK_HEADSET | SND_JACK_BTN_0, + &lowland_headset, lowland_headset_pins, + ARRAY_SIZE(lowland_headset_pins)); + if (ret) + return ret; + + wm5100_detect(component, &lowland_headset); + + return 0; +} + +static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = rtd->codec_dai->component; + + snd_soc_dapm_nc_pin(&rtd->card->dapm, "LINEOUT"); + + /* At any time the WM9081 is active it will have this clock */ + return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0, + CLKOUT_RATE, 0); +} + +static const struct snd_soc_pcm_stream sub_params = { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, +}; + +static struct snd_soc_dai_link lowland_dai[] = { + { + .name = "CPU", + .stream_name = "CPU", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm5100-aif1", + .platform_name = "samsung-i2s.0", + .codec_name = "wm5100.1-001a", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .init = lowland_wm5100_init, + }, + { + .name = "Baseband", + .stream_name = "Baseband", + .cpu_dai_name = "wm5100-aif2", + .codec_dai_name = "wm1250-ev1", + .codec_name = "wm1250-ev1.1-0027", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + }, + { + .name = "Sub Speaker", + .stream_name = "Sub Speaker", + .cpu_dai_name = "wm5100-aif3", + .codec_dai_name = "wm9081-hifi", + .codec_name = "wm9081.1-006c", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .params = &sub_params, + .init = lowland_wm9081_init, + }, +}; + +static struct snd_soc_codec_conf lowland_codec_conf[] = { + { + .dev_name = "wm9081.1-006c", + .name_prefix = "Sub", + }, +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Main Speaker"), + SOC_DAPM_PIN_SWITCH("Main DMIC"), + SOC_DAPM_PIN_SWITCH("Main AMIC"), + SOC_DAPM_PIN_SWITCH("WM1250 Input"), + SOC_DAPM_PIN_SWITCH("WM1250 Output"), + SOC_DAPM_PIN_SWITCH("Headphone"), +}; + +static struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + + SND_SOC_DAPM_SPK("Main Speaker", NULL), + + SND_SOC_DAPM_MIC("Main AMIC", NULL), + SND_SOC_DAPM_MIC("Main DMIC", NULL), +}; + +static struct snd_soc_dapm_route audio_paths[] = { + { "Sub IN1", NULL, "HPOUT2L" }, + { "Sub IN2", NULL, "HPOUT2R" }, + + { "Main Speaker", NULL, "Sub SPKN" }, + { "Main Speaker", NULL, "Sub SPKP" }, + { "Main Speaker", NULL, "SPKDAT1" }, +}; + +static struct snd_soc_card lowland = { + .name = "Lowland", + .owner = THIS_MODULE, + .dai_link = lowland_dai, + .num_links = ARRAY_SIZE(lowland_dai), + .codec_conf = lowland_codec_conf, + .num_configs = ARRAY_SIZE(lowland_codec_conf), + + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = audio_paths, + .num_dapm_routes = ARRAY_SIZE(audio_paths), +}; + +static int lowland_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &lowland; + int ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static struct platform_driver lowland_driver = { + .driver = { + .name = "lowland", + .pm = &snd_soc_pm_ops, + }, + .probe = lowland_probe, +}; + +module_platform_driver(lowland_driver); + +MODULE_DESCRIPTION("Lowland audio support"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lowland"); diff --git a/sound/soc/samsung/neo1973_wm8753.c b/sound/soc/samsung/neo1973_wm8753.c new file mode 100644 index 000000000..65602b935 --- /dev/null +++ b/sound/soc/samsung/neo1973_wm8753.c @@ -0,0 +1,395 @@ +/* + * neo1973_wm8753.c -- SoC audio for Openmoko Neo1973 and Freerunner devices + * + * Copyright 2007 Openmoko Inc + * Author: Graeme Gregory + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * Copyright 2009 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include + +#include + +#include +#include +#include "regs-iis.h" + +#include "../codecs/wm8753.h" +#include "s3c24xx-i2s.h" + +static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int pll_out = 0, bclk = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + switch (params_rate(params)) { + case 8000: + case 16000: + pll_out = 12288000; + break; + case 48000: + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(4, 4)); + if (ret < 0) + return ret; + + /* codec PLL input is PCLK/4 */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, + iis_clkrate / 4, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0); +} + +/* + * Neo1973 WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops neo1973_hifi_ops = { + .hw_params = neo1973_hifi_hw_params, + .hw_free = neo1973_hifi_hw_free, +}; + +static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pcmdiv = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + if (params_rate(params) != 8000) + return -EINVAL; + if (params_channels(params) != 1) + return -EINVAL; + + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + if (ret < 0) + return ret; + + /* configure and enable PLL for 12.288MHz output */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, + iis_clkrate / 4, 12288000); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0); +} + +static struct snd_soc_ops neo1973_voice_ops = { + .hw_params = neo1973_voice_hw_params, + .hw_free = neo1973_voice_hw_free, +}; + +static int gta02_speaker_enabled; + +static int lm4853_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + gta02_speaker_enabled = ucontrol->value.integer.value[0]; + + gpio_set_value(S3C2410_GPJ(2), !gta02_speaker_enabled); + + return 0; +} + +static int lm4853_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = gta02_speaker_enabled; + return 0; +} + +static int lm4853_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + gpio_set_value(S3C2410_GPJ(1), SND_SOC_DAPM_EVENT_OFF(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget neo1973_wm8753_dapm_widgets[] = { + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_SPK("Handset Spk", NULL), + SND_SOC_DAPM_SPK("Stereo Out", lm4853_event), +}; + +static const struct snd_soc_dapm_route neo1973_wm8753_routes[] = { + /* Connections to the GSM Module */ + {"GSM Line Out", NULL, "MONO1"}, + {"GSM Line Out", NULL, "MONO2"}, + {"RXP", NULL, "GSM Line In"}, + {"RXN", NULL, "GSM Line In"}, + + /* Connections to Headset */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, + + /* Call Mic */ + {"MIC2", NULL, "Mic Bias"}, + {"MIC2N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Handset Mic"}, + + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, + + /* Connections to the amp */ + {"Stereo Out", NULL, "LOUT1"}, + {"Stereo Out", NULL, "ROUT1"}, + + /* Call Speaker */ + {"Handset Spk", NULL, "LOUT2"}, + {"Handset Spk", NULL, "ROUT2"}, +}; + +static const struct snd_kcontrol_new neo1973_wm8753_controls[] = { + SOC_DAPM_PIN_SWITCH("GSM Line Out"), + SOC_DAPM_PIN_SWITCH("GSM Line In"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Spk"), + SOC_DAPM_PIN_SWITCH("Stereo Out"), + + SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0, + lm4853_get_spk, + lm4853_set_spk), +}; + +static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + + /* set endpoints to default off mode */ + snd_soc_dapm_disable_pin(&card->dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(&card->dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(&card->dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(&card->dapm, "Handset Mic"); + snd_soc_dapm_disable_pin(&card->dapm, "Stereo Out"); + snd_soc_dapm_disable_pin(&card->dapm, "Handset Spk"); + + /* allow audio paths from the GSM modem to run during suspend */ + snd_soc_dapm_ignore_suspend(&card->dapm, "GSM Line Out"); + snd_soc_dapm_ignore_suspend(&card->dapm, "GSM Line In"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Handset Mic"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Stereo Out"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Handset Spk"); + + return 0; +} + +static struct snd_soc_dai_link neo1973_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .platform_name = "s3c24xx-iis", + .cpu_dai_name = "s3c24xx-iis", + .codec_dai_name = "wm8753-hifi", + .codec_name = "wm8753.0-001a", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .init = neo1973_wm8753_init, + .ops = &neo1973_hifi_ops, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai_name = "bt-sco-pcm", + .codec_dai_name = "wm8753-voice", + .codec_name = "wm8753.0-001a", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &neo1973_voice_ops, +}, +}; + +static struct snd_soc_aux_dev neo1973_aux_devs[] = { + { + .name = "dfbmcs320", + .codec_name = "dfbmcs320.0", + }, +}; + +static struct snd_soc_codec_conf neo1973_codec_conf[] = { + { + .dev_name = "lm4857.0-007c", + .name_prefix = "Amp", + }, +}; + +static const struct gpio neo1973_gta02_gpios[] = { + { S3C2410_GPJ(2), GPIOF_OUT_INIT_HIGH, "GTA02_HP_IN" }, + { S3C2410_GPJ(1), GPIOF_OUT_INIT_HIGH, "GTA02_AMP_SHUT" }, +}; + +static struct snd_soc_card neo1973 = { + .name = "neo1973", + .owner = THIS_MODULE, + .dai_link = neo1973_dai, + .num_links = ARRAY_SIZE(neo1973_dai), + .aux_dev = neo1973_aux_devs, + .num_aux_devs = ARRAY_SIZE(neo1973_aux_devs), + .codec_conf = neo1973_codec_conf, + .num_configs = ARRAY_SIZE(neo1973_codec_conf), + + .controls = neo1973_wm8753_controls, + .num_controls = ARRAY_SIZE(neo1973_wm8753_controls), + .dapm_widgets = neo1973_wm8753_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(neo1973_wm8753_dapm_widgets), + .dapm_routes = neo1973_wm8753_routes, + .num_dapm_routes = ARRAY_SIZE(neo1973_wm8753_routes), + .fully_routed = true, +}; + +static struct platform_device *neo1973_snd_device; + +static int __init neo1973_init(void) +{ + int ret; + + if (!machine_is_neo1973_gta02()) + return -ENODEV; + + if (machine_is_neo1973_gta02()) { + neo1973.name = "neo1973gta02"; + neo1973.num_aux_devs = 1; + + ret = gpio_request_array(neo1973_gta02_gpios, + ARRAY_SIZE(neo1973_gta02_gpios)); + if (ret) + return ret; + } + + neo1973_snd_device = platform_device_alloc("soc-audio", -1); + if (!neo1973_snd_device) { + ret = -ENOMEM; + goto err_gpio_free; + } + + platform_set_drvdata(neo1973_snd_device, &neo1973); + ret = platform_device_add(neo1973_snd_device); + + if (ret) + goto err_put_device; + + return 0; + +err_put_device: + platform_device_put(neo1973_snd_device); +err_gpio_free: + if (machine_is_neo1973_gta02()) { + gpio_free_array(neo1973_gta02_gpios, + ARRAY_SIZE(neo1973_gta02_gpios)); + } + return ret; +} +module_init(neo1973_init); + +static void __exit neo1973_exit(void) +{ + platform_device_unregister(neo1973_snd_device); + + if (machine_is_neo1973_gta02()) { + gpio_free_array(neo1973_gta02_gpios, + ARRAY_SIZE(neo1973_gta02_gpios)); + } +} +module_exit(neo1973_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 and Frerunner"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c new file mode 100644 index 000000000..45c6d7396 --- /dev/null +++ b/sound/soc/samsung/odroid.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2017 Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include "i2s.h" +#include "i2s-regs.h" + +struct odroid_priv { + struct snd_soc_card card; + struct snd_soc_dai_link dai_link; + + struct clk *clk_i2s_bus; + struct clk *sclk_i2s; +}; + +static int odroid_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2); + return 0; +} + +static int odroid_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card); + unsigned int pll_freq, rclk_freq, rfs; + int ret; + + switch (params_rate(params)) { + case 64000: + pll_freq = 196608001U; + rfs = 384; + break; + case 44100: + case 88200: + pll_freq = 180633609U; + rfs = 512; + break; + case 32000: + case 48000: + case 96000: + pll_freq = 196608001U; + rfs = 512; + break; + default: + return -EINVAL; + } + + ret = clk_set_rate(priv->clk_i2s_bus, pll_freq / 2 + 1); + if (ret < 0) + return ret; + + /* + * We add 2 to the rclk_freq value in order to avoid too low clock + * frequency values due to the EPLL output frequency not being exact + * multiple of the audio sampling rate. + */ + rclk_freq = params_rate(params) * rfs + 2; + + ret = clk_set_rate(priv->sclk_i2s, rclk_freq); + if (ret < 0) + return ret; + + if (rtd->num_codecs > 1) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[1]; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk_freq, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct snd_soc_ops odroid_card_ops = { + .startup = odroid_card_startup, + .hw_params = odroid_card_hw_params, +}; + +static int odroid_audio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *cpu, *codec; + struct odroid_priv *priv; + struct snd_soc_dai_link *link; + struct snd_soc_card *card; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card = &priv->card; + card->dev = dev; + + card->owner = THIS_MODULE; + card->fully_routed = true; + + snd_soc_card_set_drvdata(card, priv); + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret < 0) + return ret; + + if (of_property_read_bool(dev->of_node, "samsung,audio-widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets(card, + "samsung,audio-widgets"); + if (ret < 0) + return ret; + } + + if (of_property_read_bool(dev->of_node, "samsung,audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, + "samsung,audio-routing"); + if (ret < 0) + return ret; + } + + link = &priv->dai_link; + + link->ops = &odroid_card_ops; + link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + card->dai_link = &priv->dai_link; + card->num_links = 1; + + cpu = of_get_child_by_name(dev->of_node, "cpu"); + codec = of_get_child_by_name(dev->of_node, "codec"); + + link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + if (!link->cpu_of_node) { + dev_err(dev, "Failed parsing cpu/sound-dai property\n"); + return -EINVAL; + } + + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) + goto err_put_codec_n; + + link->platform_of_node = link->cpu_of_node; + + link->name = "Primary"; + link->stream_name = link->name; + + + priv->sclk_i2s = of_clk_get_by_name(link->cpu_of_node, "i2s_opclk1"); + if (IS_ERR(priv->sclk_i2s)) { + ret = PTR_ERR(priv->sclk_i2s); + goto err_put_i2s_n; + } + + priv->clk_i2s_bus = of_clk_get_by_name(link->cpu_of_node, "iis"); + if (IS_ERR(priv->clk_i2s_bus)) { + ret = PTR_ERR(priv->clk_i2s_bus); + goto err_put_sclk; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) { + dev_err(dev, "snd_soc_register_card() failed: %d\n", ret); + goto err_put_clk_i2s; + } + + return 0; + +err_put_clk_i2s: + clk_put(priv->clk_i2s_bus); +err_put_sclk: + clk_put(priv->sclk_i2s); +err_put_i2s_n: + of_node_put(link->cpu_of_node); +err_put_codec_n: + snd_soc_of_put_dai_link_codecs(link); + return ret; +} + +static int odroid_audio_remove(struct platform_device *pdev) +{ + struct odroid_priv *priv = platform_get_drvdata(pdev); + + of_node_put(priv->dai_link.cpu_of_node); + snd_soc_of_put_dai_link_codecs(&priv->dai_link); + clk_put(priv->sclk_i2s); + clk_put(priv->clk_i2s_bus); + + return 0; +} + +static const struct of_device_id odroid_audio_of_match[] = { + { .compatible = "hardkernel,odroid-xu3-audio" }, + { .compatible = "hardkernel,odroid-xu4-audio" }, + { .compatible = "samsung,odroid-xu3-audio" }, + { .compatible = "samsung,odroid-xu4-audio" }, + { }, +}; +MODULE_DEVICE_TABLE(of, odroid_audio_of_match); + +static struct platform_driver odroid_audio_driver = { + .driver = { + .name = "odroid-audio", + .of_match_table = odroid_audio_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = odroid_audio_probe, + .remove = odroid_audio_remove, +}; +module_platform_driver(odroid_audio_driver); + +MODULE_AUTHOR("Sylwester Nawrocki "); +MODULE_DESCRIPTION("Odroid XU3/XU4 audio support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c new file mode 100644 index 000000000..37f95eee1 --- /dev/null +++ b/sound/soc/samsung/pcm.c @@ -0,0 +1,607 @@ +/* sound/soc/samsung/pcm.c + * + * ALSA SoC Audio Layer - S3C PCM-Controller driver + * + * Copyright (c) 2009 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh + * based upon I2S drivers by Ben Dooks. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "dma.h" +#include "pcm.h" + +/*Register Offsets */ +#define S3C_PCM_CTL 0x00 +#define S3C_PCM_CLKCTL 0x04 +#define S3C_PCM_TXFIFO 0x08 +#define S3C_PCM_RXFIFO 0x0C +#define S3C_PCM_IRQCTL 0x10 +#define S3C_PCM_IRQSTAT 0x14 +#define S3C_PCM_FIFOSTAT 0x18 +#define S3C_PCM_CLRINT 0x20 + +/* PCM_CTL Bit-Fields */ +#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f +#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13 +#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f +#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7 +#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6) +#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5) +#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4) +#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3) +#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2) +#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1) +#define S3C_PCM_CTL_ENABLE (0x1 << 0) + +/* PCM_CLKCTL Bit-Fields */ +#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19) +#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18) +#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff +#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff +#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9 +#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0 + +/* PCM_TXFIFO Bit-Fields */ +#define S3C_PCM_TXFIFO_DVALID (0x1 << 16) +#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0) + +/* PCM_RXFIFO Bit-Fields */ +#define S3C_PCM_RXFIFO_DVALID (0x1 << 16) +#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0) + +/* PCM_IRQCTL Bit-Fields */ +#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14) +#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12) +#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11) +#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10) +#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9) +#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8) +#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7) +#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6) +#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5) +#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4) +#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3) +#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2) +#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1) +#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0) + +/* PCM_IRQSTAT Bit-Fields */ +#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13) +#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12) +#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11) +#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10) +#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9) +#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8) +#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7) +#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6) +#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5) +#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4) +#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3) +#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2) +#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1) +#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0) + +/* PCM_FIFOSTAT Bit-Fields */ +#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14) +#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12) +#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10) +#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4) +#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2) +#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0) + +/** + * struct s3c_pcm_info - S3C PCM Controller information + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device register block. + * @dma_playback: DMA information for playback channel. + * @dma_capture: DMA information for capture channel. + */ +struct s3c_pcm_info { + spinlock_t lock; + struct device *dev; + void __iomem *regs; + + unsigned int sclk_per_fs; + + /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */ + unsigned int idleclk; + + struct clk *pclk; + struct clk *cclk; + + struct snd_dmaengine_dai_dma_data *dma_playback; + struct snd_dmaengine_dai_dma_data *dma_capture; +}; + +static struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_out[] = { + [0] = { + .addr_width = 4, + }, + [1] = { + .addr_width = 4, + }, +}; + +static struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_in[] = { + [0] = { + .addr_width = 4, + }, + [1] = { + .addr_width = 4, + }, +}; + +static struct s3c_pcm_info s3c_pcm[2]; + +static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on) +{ + void __iomem *regs = pcm->regs; + u32 ctl, clkctl; + + clkctl = readl(regs + S3C_PCM_CLKCTL); + ctl = readl(regs + S3C_PCM_CTL); + ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK + << S3C_PCM_CTL_TXDIPSTICK_SHIFT); + + if (on) { + ctl |= S3C_PCM_CTL_TXDMA_EN; + ctl |= S3C_PCM_CTL_TXFIFO_EN; + ctl |= S3C_PCM_CTL_ENABLE; + ctl |= (0x4<idleclk) + clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; + } + } + + writel(clkctl, regs + S3C_PCM_CLKCTL); + writel(ctl, regs + S3C_PCM_CTL); +} + +static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on) +{ + void __iomem *regs = pcm->regs; + u32 ctl, clkctl; + + ctl = readl(regs + S3C_PCM_CTL); + clkctl = readl(regs + S3C_PCM_CLKCTL); + ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK + << S3C_PCM_CTL_RXDIPSTICK_SHIFT); + + if (on) { + ctl |= S3C_PCM_CTL_RXDMA_EN; + ctl |= S3C_PCM_CTL_RXFIFO_EN; + ctl |= S3C_PCM_CTL_ENABLE; + ctl |= (0x20<idleclk) + clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; + } + } + + writel(clkctl, regs + S3C_PCM_CLKCTL); + writel(ctl, regs + S3C_PCM_CTL); +} + +static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai); + unsigned long flags; + + dev_dbg(pcm->dev, "Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&pcm->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c_pcm_snd_rxctrl(pcm, 1); + else + s3c_pcm_snd_txctrl(pcm, 1); + + spin_unlock_irqrestore(&pcm->lock, flags); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&pcm->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c_pcm_snd_rxctrl(pcm, 0); + else + s3c_pcm_snd_txctrl(pcm, 0); + + spin_unlock_irqrestore(&pcm->lock, flags); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int s3c_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai); + void __iomem *regs = pcm->regs; + struct clk *clk; + int sclk_div, sync_div; + unsigned long flags; + u32 clkctl; + + dev_dbg(pcm->dev, "Entered %s\n", __func__); + + /* Strictly check for sample size */ + switch (params_width(params)) { + case 16: + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&pcm->lock, flags); + + /* Get hold of the PCMSOURCE_CLK */ + clkctl = readl(regs + S3C_PCM_CLKCTL); + if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK) + clk = pcm->pclk; + else + clk = pcm->cclk; + + /* Set the SCLK divider */ + sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs / + params_rate(params) / 2 - 1; + + clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK + << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); + clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK) + << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); + + /* Set the SYNC divider */ + sync_div = pcm->sclk_per_fs - 1; + + clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK + << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); + clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK) + << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); + + writel(clkctl, regs + S3C_PCM_CLKCTL); + + spin_unlock_irqrestore(&pcm->lock, flags); + + dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n", + clk_get_rate(clk), pcm->sclk_per_fs, + sclk_div, sync_div); + + return 0; +} + +static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); + void __iomem *regs = pcm->regs; + unsigned long flags; + int ret = 0; + u32 ctl; + + dev_dbg(pcm->dev, "Entered %s\n", __func__); + + spin_lock_irqsave(&pcm->lock, flags); + + ctl = readl(regs + S3C_PCM_CTL); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + /* Nothing to do, IB_NF by default */ + break; + default: + dev_err(pcm->dev, "Unsupported clock inversion!\n"); + ret = -EINVAL; + goto exit; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Nothing to do, Master by default */ + break; + default: + dev_err(pcm->dev, "Unsupported master/slave format!\n"); + ret = -EINVAL; + goto exit; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + pcm->idleclk = 1; + break; + case SND_SOC_DAIFMT_GATED: + pcm->idleclk = 0; + break; + default: + dev_err(pcm->dev, "Invalid Clock gating request!\n"); + ret = -EINVAL; + goto exit; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC; + ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC; + break; + case SND_SOC_DAIFMT_DSP_B: + ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC; + ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC; + break; + default: + dev_err(pcm->dev, "Unsupported data format!\n"); + ret = -EINVAL; + goto exit; + } + + writel(ctl, regs + S3C_PCM_CTL); + +exit: + spin_unlock_irqrestore(&pcm->lock, flags); + + return ret; +} + +static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); + + switch (div_id) { + case S3C_PCM_SCLK_PER_FS: + pcm->sclk_per_fs = div; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); + void __iomem *regs = pcm->regs; + u32 clkctl = readl(regs + S3C_PCM_CLKCTL); + + switch (clk_id) { + case S3C_PCM_CLKSRC_PCLK: + clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK; + break; + + case S3C_PCM_CLKSRC_MUX: + clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK; + + if (clk_get_rate(pcm->cclk) != freq) + clk_set_rate(pcm->cclk, freq); + + break; + + default: + return -EINVAL; + } + + writel(clkctl, regs + S3C_PCM_CLKCTL); + + return 0; +} + +static const struct snd_soc_dai_ops s3c_pcm_dai_ops = { + .set_sysclk = s3c_pcm_set_sysclk, + .set_clkdiv = s3c_pcm_set_clkdiv, + .trigger = s3c_pcm_trigger, + .hw_params = s3c_pcm_hw_params, + .set_fmt = s3c_pcm_set_fmt, +}; + +static int s3c_pcm_dai_probe(struct snd_soc_dai *dai) +{ + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, pcm->dma_playback, pcm->dma_capture); + + return 0; +} + +#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000 + +#define S3C_PCM_DAI_DECLARE \ + .symmetric_rates = 1, \ + .probe = s3c_pcm_dai_probe, \ + .ops = &s3c_pcm_dai_ops, \ + .playback = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = S3C_PCM_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .capture = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = S3C_PCM_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + } + +static struct snd_soc_dai_driver s3c_pcm_dai[] = { + [0] = { + .name = "samsung-pcm.0", + S3C_PCM_DAI_DECLARE, + }, + [1] = { + .name = "samsung-pcm.1", + S3C_PCM_DAI_DECLARE, + }, +}; + +static const struct snd_soc_component_driver s3c_pcm_component = { + .name = "s3c-pcm", +}; + +static int s3c_pcm_dev_probe(struct platform_device *pdev) +{ + struct s3c_pcm_info *pcm; + struct resource *mem_res; + struct s3c_audio_pdata *pcm_pdata; + dma_filter_fn filter; + int ret; + + /* Check for valid device index */ + if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) { + dev_err(&pdev->dev, "id %d out of range\n", pdev->id); + return -EINVAL; + } + + pcm_pdata = pdev->dev.platform_data; + + if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + return -EINVAL; + } + + pcm = &s3c_pcm[pdev->id]; + pcm->dev = &pdev->dev; + + spin_lock_init(&pcm->lock); + + /* Default is 128fs */ + pcm->sclk_per_fs = 128; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pcm->regs = devm_ioremap_resource(&pdev->dev, mem_res); + if (IS_ERR(pcm->regs)) + return PTR_ERR(pcm->regs); + + pcm->cclk = devm_clk_get(&pdev->dev, "audio-bus"); + if (IS_ERR(pcm->cclk)) { + dev_err(&pdev->dev, "failed to get audio-bus clock\n"); + return PTR_ERR(pcm->cclk); + } + ret = clk_prepare_enable(pcm->cclk); + if (ret) + return ret; + + /* record our pcm structure for later use in the callbacks */ + dev_set_drvdata(&pdev->dev, pcm); + + pcm->pclk = devm_clk_get(&pdev->dev, "pcm"); + if (IS_ERR(pcm->pclk)) { + dev_err(&pdev->dev, "failed to get pcm clock\n"); + ret = PTR_ERR(pcm->pclk); + goto err_dis_cclk; + } + ret = clk_prepare_enable(pcm->pclk); + if (ret) + goto err_dis_cclk; + + s3c_pcm_stereo_in[pdev->id].addr = mem_res->start + S3C_PCM_RXFIFO; + s3c_pcm_stereo_out[pdev->id].addr = mem_res->start + S3C_PCM_TXFIFO; + + filter = NULL; + if (pcm_pdata) { + s3c_pcm_stereo_in[pdev->id].filter_data = pcm_pdata->dma_capture; + s3c_pcm_stereo_out[pdev->id].filter_data = pcm_pdata->dma_playback; + filter = pcm_pdata->dma_filter; + } + + pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id]; + pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id]; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); + goto err_dis_pclk; + } + + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &s3c_pcm_component, + &s3c_pcm_dai[pdev->id], 1); + if (ret != 0) { + dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret); + goto err_dis_pm; + } + + return 0; + +err_dis_pm: + pm_runtime_disable(&pdev->dev); +err_dis_pclk: + clk_disable_unprepare(pcm->pclk); +err_dis_cclk: + clk_disable_unprepare(pcm->cclk); + return ret; +} + +static int s3c_pcm_dev_remove(struct platform_device *pdev) +{ + struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id]; + + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(pcm->cclk); + clk_disable_unprepare(pcm->pclk); + + return 0; +} + +static struct platform_driver s3c_pcm_driver = { + .probe = s3c_pcm_dev_probe, + .remove = s3c_pcm_dev_remove, + .driver = { + .name = "samsung-pcm", + }, +}; + +module_platform_driver(s3c_pcm_driver); + +/* Module information */ +MODULE_AUTHOR("Jaswinder Singh, "); +MODULE_DESCRIPTION("S3C PCM Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-pcm"); diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h new file mode 100644 index 000000000..726baf814 --- /dev/null +++ b/sound/soc/samsung/pcm.h @@ -0,0 +1,17 @@ +/* sound/soc/samsung/pcm.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __S3C_PCM_H +#define __S3C_PCM_H __FILE__ + +#define S3C_PCM_CLKSRC_PCLK 0 +#define S3C_PCM_CLKSRC_MUX 1 + +#define S3C_PCM_SCLK_PER_FS 0 + +#endif /* __S3C_PCM_H */ diff --git a/sound/soc/samsung/regs-i2s-v2.h b/sound/soc/samsung/regs-i2s-v2.h new file mode 100644 index 000000000..5e5e56805 --- /dev/null +++ b/sound/soc/samsung/regs-i2s-v2.h @@ -0,0 +1,115 @@ +/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h + * + * Copyright 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * S3C2412 IIS register definition +*/ + +#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H +#define __ASM_ARCH_REGS_S3C2412_IIS_H + +#define S3C2412_IISCON (0x00) +#define S3C2412_IISMOD (0x04) +#define S3C2412_IISFIC (0x08) +#define S3C2412_IISPSR (0x0C) +#define S3C2412_IISTXD (0x10) +#define S3C2412_IISRXD (0x14) + +#define S5PC1XX_IISFICS 0x18 +#define S5PC1XX_IISTXDS 0x1C + +#define S5PC1XX_IISCON_SW_RST (1 << 31) +#define S5PC1XX_IISCON_FRXOFSTATUS (1 << 26) +#define S5PC1XX_IISCON_FRXORINTEN (1 << 25) +#define S5PC1XX_IISCON_FTXSURSTAT (1 << 24) +#define S5PC1XX_IISCON_FTXSURINTEN (1 << 23) +#define S5PC1XX_IISCON_TXSDMAPAUSE (1 << 20) +#define S5PC1XX_IISCON_TXSDMACTIVE (1 << 18) + +#define S3C64XX_IISCON_FTXURSTATUS (1 << 17) +#define S3C64XX_IISCON_FTXURINTEN (1 << 16) +#define S3C64XX_IISCON_TXFIFO2_EMPTY (1 << 15) +#define S3C64XX_IISCON_TXFIFO1_EMPTY (1 << 14) +#define S3C64XX_IISCON_TXFIFO2_FULL (1 << 13) +#define S3C64XX_IISCON_TXFIFO1_FULL (1 << 12) + +#define S3C2412_IISCON_LRINDEX (1 << 11) +#define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10) +#define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9) +#define S3C2412_IISCON_TXFIFO_FULL (1 << 8) +#define S3C2412_IISCON_RXFIFO_FULL (1 << 7) +#define S3C2412_IISCON_TXDMA_PAUSE (1 << 6) +#define S3C2412_IISCON_RXDMA_PAUSE (1 << 5) +#define S3C2412_IISCON_TXCH_PAUSE (1 << 4) +#define S3C2412_IISCON_RXCH_PAUSE (1 << 3) +#define S3C2412_IISCON_TXDMA_ACTIVE (1 << 2) +#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) +#define S3C2412_IISCON_IIS_ACTIVE (1 << 0) + +#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT (0 << 30) +#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN (1 << 30) +#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT (2 << 30) +#define S5PC1XX_IISMOD_OPCLK_PCLK (3 << 30) +#define S5PC1XX_IISMOD_OPCLK_MASK (3 << 30) +#define S5PC1XX_IISMOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ +#define S5PC1XX_IISMOD_BLCS_MASK 0x3 +#define S5PC1XX_IISMOD_BLCS_SHIFT 26 +#define S5PC1XX_IISMOD_BLCP_MASK 0x3 +#define S5PC1XX_IISMOD_BLCP_SHIFT 24 + +#define S3C64XX_IISMOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ +#define S3C64XX_IISMOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ +#define S3C64XX_IISMOD_C1DD_HHALF (1 << 19) +#define S3C64XX_IISMOD_C1DD_LHALF (1 << 18) +#define S3C64XX_IISMOD_DC2_EN (1 << 17) +#define S3C64XX_IISMOD_DC1_EN (1 << 16) +#define S3C64XX_IISMOD_BLC_16BIT (0 << 13) +#define S3C64XX_IISMOD_BLC_8BIT (1 << 13) +#define S3C64XX_IISMOD_BLC_24BIT (2 << 13) +#define S3C64XX_IISMOD_BLC_MASK (3 << 13) + +#define S3C2412_IISMOD_IMS_SYSMUX (1 << 10) +#define S3C2412_IISMOD_SLAVE (1 << 11) +#define S3C2412_IISMOD_MODE_TXONLY (0 << 8) +#define S3C2412_IISMOD_MODE_RXONLY (1 << 8) +#define S3C2412_IISMOD_MODE_TXRX (2 << 8) +#define S3C2412_IISMOD_MODE_MASK (3 << 8) +#define S3C2412_IISMOD_LR_LLOW (0 << 7) +#define S3C2412_IISMOD_LR_RLOW (1 << 7) +#define S3C2412_IISMOD_SDF_IIS (0 << 5) +#define S3C2412_IISMOD_SDF_MSB (1 << 5) +#define S3C2412_IISMOD_SDF_LSB (2 << 5) +#define S3C2412_IISMOD_SDF_MASK (3 << 5) +#define S3C2412_IISMOD_RCLK_256FS (0 << 3) +#define S3C2412_IISMOD_RCLK_512FS (1 << 3) +#define S3C2412_IISMOD_RCLK_384FS (2 << 3) +#define S3C2412_IISMOD_RCLK_768FS (3 << 3) +#define S3C2412_IISMOD_RCLK_MASK (3 << 3) +#define S3C2412_IISMOD_BCLK_32FS (0 << 1) +#define S3C2412_IISMOD_BCLK_48FS (1 << 1) +#define S3C2412_IISMOD_BCLK_16FS (2 << 1) +#define S3C2412_IISMOD_BCLK_24FS (3 << 1) +#define S3C2412_IISMOD_BCLK_MASK (3 << 1) +#define S3C2412_IISMOD_8BIT (1 << 0) + +#define S3C64XX_IISMOD_CDCLKCON (1 << 12) + +#define S3C2412_IISPSR_PSREN (1 << 15) + +#define S3C64XX_IISFIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define S3C64XX_IISFIC_TX1COUNT(x) (((x) >> 16) & 0xf) + +#define S3C2412_IISFIC_TXFLUSH (1 << 15) +#define S3C2412_IISFIC_RXFLUSH (1 << 7) +#define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf) +#define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf) + +#define S5PC1XX_IISFICS_TXFLUSH (1 << 15) +#define S5PC1XX_IISFICS_TXCOUNT(x) (((x) >> 8) & 0x7f) + +#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */ diff --git a/sound/soc/samsung/regs-iis.h b/sound/soc/samsung/regs-iis.h new file mode 100644 index 000000000..dc6cbbe9c --- /dev/null +++ b/sound/soc/samsung/regs-iis.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2003 Simtec Electronics + * http://www.simtec.co.uk/products/SWLINUX/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * S3C2410 IIS register definition +*/ + +#ifndef __SAMSUNG_REGS_IIS_H__ +#define __SAMSUNG_REGS_IIS_H__ + +#define S3C2410_IISCON (0x00) + +#define S3C2410_IISCON_LRINDEX (1 << 8) +#define S3C2410_IISCON_TXFIFORDY (1 << 7) +#define S3C2410_IISCON_RXFIFORDY (1 << 6) +#define S3C2410_IISCON_TXDMAEN (1 << 5) +#define S3C2410_IISCON_RXDMAEN (1 << 4) +#define S3C2410_IISCON_TXIDLE (1 << 3) +#define S3C2410_IISCON_RXIDLE (1 << 2) +#define S3C2410_IISCON_PSCEN (1 << 1) +#define S3C2410_IISCON_IISEN (1 << 0) + +#define S3C2410_IISMOD (0x04) + +#define S3C2440_IISMOD_MPLL (1 << 9) +#define S3C2410_IISMOD_SLAVE (1 << 8) +#define S3C2410_IISMOD_NOXFER (0 << 6) +#define S3C2410_IISMOD_RXMODE (1 << 6) +#define S3C2410_IISMOD_TXMODE (2 << 6) +#define S3C2410_IISMOD_TXRXMODE (3 << 6) +#define S3C2410_IISMOD_LR_LLOW (0 << 5) +#define S3C2410_IISMOD_LR_RLOW (1 << 5) +#define S3C2410_IISMOD_IIS (0 << 4) +#define S3C2410_IISMOD_MSB (1 << 4) +#define S3C2410_IISMOD_8BIT (0 << 3) +#define S3C2410_IISMOD_16BIT (1 << 3) +#define S3C2410_IISMOD_BITMASK (1 << 3) +#define S3C2410_IISMOD_256FS (0 << 2) +#define S3C2410_IISMOD_384FS (1 << 2) +#define S3C2410_IISMOD_16FS (0 << 0) +#define S3C2410_IISMOD_32FS (1 << 0) +#define S3C2410_IISMOD_48FS (2 << 0) +#define S3C2410_IISMOD_FS_MASK (3 << 0) + +#define S3C2410_IISPSR (0x08) + +#define S3C2410_IISPSR_INTMASK (31 << 5) +#define S3C2410_IISPSR_INTSHIFT (5) +#define S3C2410_IISPSR_EXTMASK (31 << 0) +#define S3C2410_IISPSR_EXTSHFIT (0) + +#define S3C2410_IISFCON (0x0c) + +#define S3C2410_IISFCON_TXDMA (1 << 15) +#define S3C2410_IISFCON_RXDMA (1 << 14) +#define S3C2410_IISFCON_TXENABLE (1 << 13) +#define S3C2410_IISFCON_RXENABLE (1 << 12) +#define S3C2410_IISFCON_TXMASK (0x3f << 6) +#define S3C2410_IISFCON_TXSHIFT (6) +#define S3C2410_IISFCON_RXMASK (0x3f) +#define S3C2410_IISFCON_RXSHIFT (0) + +#define S3C2410_IISFIFO (0x10) + +#endif /* __SAMSUNG_REGS_IIS_H__ */ diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c new file mode 100644 index 000000000..a064ca7d7 --- /dev/null +++ b/sound/soc/samsung/rx1950_uda1380.c @@ -0,0 +1,273 @@ +/* + * rx1950.c -- ALSA Soc Audio Layer + * + * Copyright (c) 2010 Vasily Khoruzhick + * + * Based on smdk2440.c and magician.c + * + * Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com + * Philipp Zabel + * Denis Grigoriev + * Vasily Khoruzhick + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include + +#include +#include + +#include +#include "regs-iis.h" +#include + +#include "s3c24xx-i2s.h" + +static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd); +static int rx1950_startup(struct snd_pcm_substream *substream); +static int rx1950_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); +static int rx1950_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +static const unsigned int rates[] = { + 16000, + 44100, + 48000, +}; + +static const struct snd_pcm_hw_constraint_list hw_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + +static struct snd_soc_jack_gpio hp_jack_gpios[] = { + [0] = { + .gpio = S3C2410_GPG(12), + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .invert = 1, + .debounce_time = 200, + }, +}; + +static struct snd_soc_ops rx1950_ops = { + .startup = rx1950_startup, + .hw_params = rx1950_hw_params, +}; + +/* s3c24xx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx1950_uda1380_dai[] = { + { + .name = "uda1380", + .stream_name = "UDA1380 Duplex", + .cpu_dai_name = "s3c24xx-iis", + .codec_dai_name = "uda1380-hifi", + .init = rx1950_uda1380_init, + .platform_name = "s3c24xx-iis", + .codec_name = "uda1380-codec.0-001a", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &rx1950_ops, + }, +}; + +/* rx1950 machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", rx1950_spk_power), +}; + +/* rx1950 machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + /* headphone connected to VOUTLHP, VOUTRHP */ + {"Headphone Jack", NULL, "VOUTLHP"}, + {"Headphone Jack", NULL, "VOUTRHP"}, + + /* ext speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* mic is connected to VINM */ + {"VINM", NULL, "Mic Jack"}, +}; + +static struct snd_soc_card rx1950_asoc = { + .name = "rx1950", + .owner = THIS_MODULE, + .dai_link = rx1950_uda1380_dai, + .num_links = ARRAY_SIZE(rx1950_uda1380_dai), + + .dapm_widgets = uda1380_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct platform_device *s3c24xx_snd_device; + +static int rx1950_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_rates); +} + +static int rx1950_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(S3C2410_GPA(1), 1); + else + gpio_set_value(S3C2410_GPA(1), 0); + + return 0; +} + +static int rx1950_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int div; + int ret; + unsigned int rate = params_rate(params); + int clk_source, fs_mode; + + switch (rate) { + case 16000: + case 48000: + clk_source = S3C24XX_CLKSRC_PCLK; + fs_mode = S3C2410_IISMOD_256FS; + div = s3c24xx_i2s_get_clockrate() / (256 * rate); + if (s3c24xx_i2s_get_clockrate() % (256 * rate) > (128 * rate)) + div++; + break; + case 44100: + case 88200: + clk_source = S3C24XX_CLKSRC_MPLL; + fs_mode = S3C2410_IISMOD_384FS; + div = 1; + break; + default: + printk(KERN_ERR "%s: rate %d is not supported\n", + __func__, rate); + return -EINVAL; + } + + /* select clock source */ + ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source, rate, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + fs_mode); + if (ret < 0) + return ret; + + /* set BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(div, div)); + if (ret < 0) + return ret; + + return 0; +} + +static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd) +{ + snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE, + &hp_jack, hp_jack_pins, ARRAY_SIZE(hp_jack_pins)); + + snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + + return 0; +} + +static int __init rx1950_init(void) +{ + int ret; + + if (!machine_is_rx1950()) + return -ENODEV; + + /* configure some gpios */ + ret = gpio_request(S3C2410_GPA(1), "speaker-power"); + if (ret) + goto err_gpio; + + ret = gpio_direction_output(S3C2410_GPA(1), 0); + if (ret) + goto err_gpio_conf; + + s3c24xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!s3c24xx_snd_device) { + ret = -ENOMEM; + goto err_plat_alloc; + } + + platform_set_drvdata(s3c24xx_snd_device, &rx1950_asoc); + ret = platform_device_add(s3c24xx_snd_device); + + if (ret) { + platform_device_put(s3c24xx_snd_device); + goto err_plat_add; + } + + return 0; + +err_plat_add: +err_plat_alloc: +err_gpio_conf: + gpio_free(S3C2410_GPA(1)); + +err_gpio: + return ret; +} + +static void __exit rx1950_exit(void) +{ + platform_device_unregister(s3c24xx_snd_device); + gpio_free(S3C2410_GPA(1)); +} + +module_init(rx1950_init); +module_exit(rx1950_exit); + +/* Module information */ +MODULE_AUTHOR("Vasily Khoruzhick"); +MODULE_DESCRIPTION("ALSA SoC RX1950"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c new file mode 100644 index 000000000..58c3e9bfc --- /dev/null +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -0,0 +1,741 @@ +/* ALSA Soc Audio Layer - I2S core for newer Samsung SoCs. + * + * Copyright (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com + * linux@wolfsonmicro.com + * + * Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include + +#include +#include + +#include "regs-i2s-v2.h" +#include "s3c-i2s-v2.h" + +#undef S3C_IIS_V2_SUPPORTED + +#if defined(CONFIG_CPU_S3C2412) \ + || defined(CONFIG_ARCH_S3C64XX) || defined(CONFIG_CPU_S5PV210) +#define S3C_IIS_V2_SUPPORTED +#endif + +#ifndef S3C_IIS_V2_SUPPORTED +#error Unsupported CPU model +#endif + +#define S3C2412_I2S_DEBUG_CON 0 + +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return snd_soc_dai_get_drvdata(cpu_dai); +} + +#define bit_set(v, b) (((v) & (b)) ? 1 : 0) + +#if S3C2412_I2S_DEBUG_CON +static void dbg_showcon(const char *fn, u32 con) +{ + printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn, + bit_set(con, S3C2412_IISCON_LRINDEX), + bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_TXFIFO_FULL), + bit_set(con, S3C2412_IISCON_RXFIFO_FULL)); + + printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n", + fn, + bit_set(con, S3C2412_IISCON_TXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_RXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_TXCH_PAUSE), + bit_set(con, S3C2412_IISCON_RXCH_PAUSE)); + printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn, + bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_IIS_ACTIVE)); +} +#else +static inline void dbg_showcon(const char *fn, u32 con) +{ +} +#endif + +/* Turn on or off the transmission path. */ +static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) +{ + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + pr_debug("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_TXDMA_PAUSE; + con &= ~S3C2412_IISCON_TXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXONLY: + case S3C2412_IISMOD_MODE_TXRX: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_RXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } else { + /* Note, we do not have any indication that the FIFO problems + * tha the S3C2410/2440 had apply here, so we should be able + * to disable the DMA and TX without resetting the FIFOS. + */ + + con |= S3C2412_IISCON_TXDMA_PAUSE; + con |= S3C2412_IISCON_TXCH_PAUSE; + con &= ~S3C2412_IISCON_TXDMA_ACTIVE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_RXONLY; + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + con &= ~S3C2412_IISCON_IIS_ACTIVE; + break; + + default: + dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } + + fic = readl(regs + S3C2412_IISFIC); + dbg_showcon(__func__, con); + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} + +static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) +{ + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + pr_debug("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_RXDMA_PAUSE; + con &= ~S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + case S3C2412_IISMOD_MODE_RXONLY: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } else { + /* See txctrl notes on FIFOs. */ + + con &= ~S3C2412_IISCON_RXDMA_ACTIVE; + con |= S3C2412_IISCON_RXDMA_PAUSE; + con |= S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_RXONLY: + con &= ~S3C2412_IISCON_IIS_ACTIVE; + mod &= ~S3C2412_IISMOD_MODE_MASK; + break; + + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXONLY; + break; + + default: + dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } + + fic = readl(regs + S3C2412_IISFIC); + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) + +/* + * Wait for the LR signal to allow synchronisation to the L/R clock + * from the codec. May only be needed for slave mode. + */ +static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s) +{ + u32 iiscon; + unsigned long loops = msecs_to_loops(5); + + pr_debug("Entered %s\n", __func__); + + while (--loops) { + iiscon = readl(i2s->regs + S3C2412_IISCON); + if (iiscon & S3C2412_IISCON_LRINDEX) + break; + + cpu_relax(); + } + + if (!loops) { + printk(KERN_ERR "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Set S3C2412 I2S DAI format + */ +static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("hw_params r: IISMOD: %x \n", iismod); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = 0; + iismod |= S3C2412_IISMOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = 1; + iismod &= ~S3C2412_IISMOD_SLAVE; + break; + default: + pr_err("unknown master/slave format\n"); + return -EINVAL; + } + + iismod &= ~S3C2412_IISMOD_SDF_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + iismod |= S3C2412_IISMOD_LR_RLOW; + iismod |= S3C2412_IISMOD_SDF_MSB; + break; + case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2412_IISMOD_LR_RLOW; + iismod |= S3C2412_IISMOD_SDF_LSB; + break; + case SND_SOC_DAIFMT_I2S: + iismod &= ~S3C2412_IISMOD_LR_RLOW; + iismod |= S3C2412_IISMOD_SDF_IIS; + break; + default: + pr_err("Unknown data format\n"); + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("hw_params w: IISMOD: %x \n", iismod); + return 0; +} + +static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + struct snd_dmaengine_dai_dma_data *dma_data; + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = i2s->dma_playback; + else + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(dai, substream, dma_data); + + /* Working copies of register */ + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); + + iismod &= ~S3C64XX_IISMOD_BLC_MASK; + /* Sample size */ + switch (params_width(params)) { + case 8: + iismod |= S3C64XX_IISMOD_BLC_8BIT; + break; + case 16: + break; + case 24: + iismod |= S3C64XX_IISMOD_BLC_24BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + pr_debug("Entered %s\n", __func__); + pr_debug("%s r: IISMOD: %x\n", __func__, iismod); + + switch (clk_id) { + case S3C_I2SV2_CLKSRC_PCLK: + iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; + break; + + case S3C_I2SV2_CLKSRC_AUDIOBUS: + iismod |= S3C2412_IISMOD_IMS_SYSMUX; + break; + + case S3C_I2SV2_CLKSRC_CDCLK: + /* Error if controller doesn't have the CDCLKCON bit */ + if (!(i2s->feature & S3C_FEATURE_CDCLKCON)) + return -EINVAL; + + switch (dir) { + case SND_SOC_CLOCK_IN: + iismod |= S3C64XX_IISMOD_CDCLKCON; + break; + case SND_SOC_CLOCK_OUT: + iismod &= ~S3C64XX_IISMOD_CDCLKCON; + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_i2sv2_info *i2s = to_info(rtd->cpu_dai); + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + unsigned long irqs; + int ret = 0; + + pr_debug("Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* On start, ensure that the FIFOs are cleared and reset. */ + + writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH, + i2s->regs + S3C2412_IISFIC); + + /* clear again, just in case */ + writel(0x0, i2s->regs + S3C2412_IISFIC); + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!i2s->master) { + ret = s3c2412_snd_lrsync(i2s); + if (ret) + goto exit_err; + } + + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(i2s, 1); + else + s3c2412_snd_txctrl(i2s, 1); + + local_irq_restore(irqs); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(i2s, 0); + else + s3c2412_snd_txctrl(i2s, 0); + + local_irq_restore(irqs); + break; + default: + ret = -EINVAL; + break; + } + +exit_err: + return ret; +} + +/* + * Set S3C2412 Clock dividers + */ +static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 reg; + + pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div); + + switch (div_id) { + case S3C_I2SV2_DIV_BCLK: + switch (div) { + case 16: + div = S3C2412_IISMOD_BCLK_16FS; + break; + + case 32: + div = S3C2412_IISMOD_BCLK_32FS; + break; + + case 24: + div = S3C2412_IISMOD_BCLK_24FS; + break; + + case 48: + div = S3C2412_IISMOD_BCLK_48FS; + break; + + default: + return -EINVAL; + } + + reg = readl(i2s->regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_BCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + + pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C_I2SV2_DIV_RCLK: + switch (div) { + case 256: + div = S3C2412_IISMOD_RCLK_256FS; + break; + + case 384: + div = S3C2412_IISMOD_RCLK_384FS; + break; + + case 512: + div = S3C2412_IISMOD_RCLK_512FS; + break; + + case 768: + div = S3C2412_IISMOD_RCLK_768FS; + break; + + default: + return -EINVAL; + } + + reg = readl(i2s->regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_RCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C_I2SV2_DIV_PRESCALER: + if (div >= 0) { + writel((div << 8) | S3C2412_IISPSR_PSREN, + i2s->regs + S3C2412_IISPSR); + } else { + writel(0x0, i2s->regs + S3C2412_IISPSR); + } + pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR)); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + u32 reg = readl(i2s->regs + S3C2412_IISFIC); + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = S3C2412_IISFIC_TXCOUNT(reg); + else + delay = S3C2412_IISFIC_RXCOUNT(reg); + + return delay; +} + +struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISMOD_IMS_SYSMUX) + return i2s->iis_cclk; + else + return i2s->iis_pclk; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock); + +/* default table of all avaialable root fs divisors */ +static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; + +int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk) +{ + unsigned long clkrate = clk_get_rate(clk); + unsigned int div; + unsigned int fsclk; + unsigned int actual; + unsigned int fs; + unsigned int fsdiv; + signed int deviation = 0; + unsigned int best_fs = 0; + unsigned int best_div = 0; + unsigned int best_rate = 0; + unsigned int best_deviation = INT_MAX; + + pr_debug("Input clock rate %ldHz\n", clkrate); + + if (fstab == NULL) + fstab = iis_fs_tab; + + for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) { + fsdiv = iis_fs_tab[fs]; + + fsclk = clkrate / fsdiv; + div = fsclk / rate; + + if ((fsclk % rate) > (rate / 2)) + div++; + + if (div <= 1) + continue; + + actual = clkrate / (fsdiv * div); + deviation = actual - rate; + + printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n", + fsdiv, div, actual, deviation); + + deviation = abs(deviation); + + if (deviation < best_deviation) { + best_fs = fsdiv; + best_div = div; + best_rate = actual; + best_deviation = deviation; + } + + if (deviation == 0) + break; + } + + printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n", + best_fs, best_div, best_rate); + + info->fs_div = best_fs; + info->clk_div = best_div; + + return 0; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_iis_calc_rate); + +int s3c_i2sv2_probe(struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s, + unsigned long base) +{ + struct device *dev = dai->dev; + unsigned int iismod; + + i2s->dev = dev; + + /* record our i2s structure for later use in the callbacks */ + snd_soc_dai_set_drvdata(dai, i2s); + + i2s->iis_pclk = clk_get(dev, "iis"); + if (IS_ERR(i2s->iis_pclk)) { + dev_err(dev, "failed to get iis_clock\n"); + return -ENOENT; + } + + clk_prepare_enable(i2s->iis_pclk); + + /* Mark ourselves as in TXRX mode so we can run through our cleanup + * process without warnings. */ + iismod = readl(i2s->regs + S3C2412_IISMOD); + iismod |= S3C2412_IISMOD_MODE_TXRX; + writel(iismod, i2s->regs + S3C2412_IISMOD); + s3c2412_snd_txctrl(i2s, 0); + s3c2412_snd_rxctrl(i2s, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_probe); + +void s3c_i2sv2_cleanup(struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s) +{ + clk_disable_unprepare(i2s->iis_pclk); + clk_put(i2s->iis_pclk); + i2s->iis_pclk = NULL; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_cleanup); + +#ifdef CONFIG_PM +static int s3c2412_i2s_suspend(struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + u32 iismod; + + if (dai->active) { + i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD); + i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON); + i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR); + + /* some basic suspend checks */ + + iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISCON_RXDMA_ACTIVE) + pr_warning("%s: RXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_TXDMA_ACTIVE) + pr_warning("%s: TXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_IIS_ACTIVE) + pr_warning("%s: IIS active\n", __func__); + } + + return 0; +} + +static int s3c2412_i2s_resume(struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + + pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n", + dai->active, i2s->suspend_iismod, i2s->suspend_iiscon); + + if (dai->active) { + writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON); + writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD); + writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR); + + writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH, + i2s->regs + S3C2412_IISFIC); + + ndelay(250); + writel(0x0, i2s->regs + S3C2412_IISFIC); + } + + return 0; +} +#else +#define s3c2412_i2s_suspend NULL +#define s3c2412_i2s_resume NULL +#endif + +int s3c_i2sv2_register_component(struct device *dev, int id, + const struct snd_soc_component_driver *cmp_drv, + struct snd_soc_dai_driver *dai_drv) +{ + struct snd_soc_dai_ops *ops = (struct snd_soc_dai_ops *)dai_drv->ops; + + ops->trigger = s3c2412_i2s_trigger; + if (!ops->hw_params) + ops->hw_params = s3c_i2sv2_hw_params; + ops->set_fmt = s3c2412_i2s_set_fmt; + ops->set_clkdiv = s3c2412_i2s_set_clkdiv; + ops->set_sysclk = s3c_i2sv2_set_sysclk; + + /* Allow overriding by (for example) IISv4 */ + if (!ops->delay) + ops->delay = s3c2412_i2s_delay; + + dai_drv->suspend = s3c2412_i2s_suspend; + dai_drv->resume = s3c2412_i2s_resume; + + return devm_snd_soc_register_component(dev, cmp_drv, dai_drv, 1); +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_register_component); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c-i2s-v2.h b/sound/soc/samsung/s3c-i2s-v2.h new file mode 100644 index 000000000..3fca20f7a --- /dev/null +++ b/sound/soc/samsung/s3c-i2s-v2.h @@ -0,0 +1,114 @@ +/* sound/soc/samsung/s3c-i2s-v2.h + * + * ALSA Soc Audio Layer - S3C_I2SV2 I2S driver + * + * Copyright (c) 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. +*/ + +/* This code is the core support for the I2S block found in a number of + * Samsung SoC devices which is unofficially named I2S-V2. Currently the + * S3C2412 and the S3C64XX series use this block to provide 1 or 2 I2S + * channels via configurable GPIO. + */ + +#ifndef __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H +#define __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H __FILE__ + +#define S3C_I2SV2_DIV_BCLK (1) +#define S3C_I2SV2_DIV_RCLK (2) +#define S3C_I2SV2_DIV_PRESCALER (3) + +#define S3C_I2SV2_CLKSRC_PCLK 0 +#define S3C_I2SV2_CLKSRC_AUDIOBUS 1 +#define S3C_I2SV2_CLKSRC_CDCLK 2 + +/* Set this flag for I2S controllers that have the bit IISMOD[12] + * bridge/break RCLK signal and external Xi2sCDCLK pin. + */ +#define S3C_FEATURE_CDCLKCON (1 << 0) + +/** + * struct s3c_i2sv2_info - S3C I2S-V2 information + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device registe block. + * @feature: Set of bit-flags indicating features of the controller. + * @master: True if the I2S core is the I2S bit clock master. + * @dma_playback: DMA information for playback channel. + * @dma_capture: DMA information for capture channel. + * @suspend_iismod: PM save for the IISMOD register. + * @suspend_iiscon: PM save for the IISCON register. + * @suspend_iispsr: PM save for the IISPSR register. + * + * This is the private codec state for the hardware associated with an + * I2S channel such as the register mappings and clock sources. + */ +struct s3c_i2sv2_info { + struct device *dev; + void __iomem *regs; + + u32 feature; + + struct clk *iis_pclk; + struct clk *iis_cclk; + + unsigned char master; + + struct snd_dmaengine_dai_dma_data *dma_playback; + struct snd_dmaengine_dai_dma_data *dma_capture; + + u32 suspend_iismod; + u32 suspend_iiscon; + u32 suspend_iispsr; + + unsigned long base; +}; + +extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai); + +struct s3c_i2sv2_rate_calc { + unsigned int clk_div; /* for prescaler */ + unsigned int fs_div; /* for root frame clock */ +}; + +extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk); + +/** + * s3c_i2sv2_probe - probe for i2s device helper + * @dai: The ASoC DAI structure supplied to the original probe. + * @i2s: Our local i2s structure to fill in. + * @base: The base address for the registers. + */ +extern int s3c_i2sv2_probe(struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s, + unsigned long base); + +/** + * s3c_i2sv2_cleanup - cleanup resources allocated in s3c_i2sv2_probe + * @dai: The ASoC DAI structure supplied to the original probe. + * @i2s: Our local i2s structure to fill in. + */ +extern void s3c_i2sv2_cleanup(struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s); +/** + * s3c_i2sv2_register_component - register component and dai with soc core + * @dev: DAI device + * @id: DAI ID + * @drv: The driver structure to register + * + * Fill in any missing fields and then register the given dai with the + * soc core. + */ +extern int s3c_i2sv2_register_component(struct device *dev, int id, + const struct snd_soc_component_driver *cmp_drv, + struct snd_soc_dai_driver *dai_drv); + +#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */ diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c new file mode 100644 index 000000000..cc0840fff --- /dev/null +++ b/sound/soc/samsung/s3c2412-i2s.c @@ -0,0 +1,208 @@ +/* sound/soc/samsung/s3c2412-i2s.c + * + * ALSA Soc Audio Layer - S3C2412 I2S driver + * + * Copyright (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com + * linux@wolfsonmicro.com + * + * Copyright (c) 2007, 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "dma.h" +#include "regs-i2s-v2.h" +#include "s3c2412-i2s.h" + +#include + +static struct snd_dmaengine_dai_dma_data s3c2412_i2s_pcm_stereo_out = { + .chan_name = "tx", + .addr_width = 4, +}; + +static struct snd_dmaengine_dai_dma_data s3c2412_i2s_pcm_stereo_in = { + .chan_name = "rx", + .addr_width = 4, +}; + +static struct s3c_i2sv2_info s3c2412_i2s; + +static int s3c2412_i2s_probe(struct snd_soc_dai *dai) +{ + int ret; + + pr_debug("Entered %s\n", __func__); + + snd_soc_dai_init_dma_data(dai, &s3c2412_i2s_pcm_stereo_out, + &s3c2412_i2s_pcm_stereo_in); + + ret = s3c_i2sv2_probe(dai, &s3c2412_i2s, S3C2410_PA_IIS); + if (ret) + return ret; + + s3c2412_i2s.dma_capture = &s3c2412_i2s_pcm_stereo_in; + s3c2412_i2s.dma_playback = &s3c2412_i2s_pcm_stereo_out; + + s3c2412_i2s.iis_cclk = devm_clk_get(dai->dev, "i2sclk"); + if (IS_ERR(s3c2412_i2s.iis_cclk)) { + pr_err("failed to get i2sclk clock\n"); + ret = PTR_ERR(s3c2412_i2s.iis_cclk); + goto err; + } + + /* Set MPLL as the source for IIS CLK */ + + clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll")); + ret = clk_prepare_enable(s3c2412_i2s.iis_cclk); + if (ret) + goto err; + + /* Configure the I2S pins (GPE0...GPE4) in correct mode */ + s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2), + S3C_GPIO_PULL_NONE); + + return 0; + +err: + s3c_i2sv2_cleanup(dai, &s3c2412_i2s); + + return ret; +} + +static int s3c2412_i2s_remove(struct snd_soc_dai *dai) +{ + clk_disable_unprepare(s3c2412_i2s.iis_cclk); + s3c_i2sv2_cleanup(dai, &s3c2412_i2s); + + return 0; +} + +static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(cpu_dai); + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); + + switch (params_width(params)) { + case 8: + iismod |= S3C2412_IISMOD_8BIT; + break; + case 16: + iismod &= ~S3C2412_IISMOD_8BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +#define S3C2412_I2S_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +static const struct snd_soc_dai_ops s3c2412_i2s_dai_ops = { + .hw_params = s3c2412_i2s_hw_params, +}; + +static struct snd_soc_dai_driver s3c2412_i2s_dai = { + .probe = s3c2412_i2s_probe, + .remove = s3c2412_i2s_remove, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C2412_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C2412_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &s3c2412_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver s3c2412_i2s_component = { + .name = "s3c2412-i2s", +}; + +static int s3c2412_iis_dev_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res; + struct s3c_audio_pdata *pdata = dev_get_platdata(&pdev->dev); + + if (!pdata) { + dev_err(&pdev->dev, "missing platform data"); + return -ENXIO; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + s3c2412_i2s.regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(s3c2412_i2s.regs)) + return PTR_ERR(s3c2412_i2s.regs); + + s3c2412_i2s_pcm_stereo_out.addr = res->start + S3C2412_IISTXD; + s3c2412_i2s_pcm_stereo_out.filter_data = pdata->dma_playback; + s3c2412_i2s_pcm_stereo_in.addr = res->start + S3C2412_IISRXD; + s3c2412_i2s_pcm_stereo_in.filter_data = pdata->dma_capture; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, + pdata->dma_filter, + NULL, NULL); + if (ret) { + pr_err("failed to register the DMA: %d\n", ret); + return ret; + } + + ret = s3c_i2sv2_register_component(&pdev->dev, -1, + &s3c2412_i2s_component, + &s3c2412_i2s_dai); + if (ret) + pr_err("failed to register the dai\n"); + + return ret; +} + +static struct platform_driver s3c2412_iis_driver = { + .probe = s3c2412_iis_dev_probe, + .driver = { + .name = "s3c2412-iis", + }, +}; + +module_platform_driver(s3c2412_iis_driver); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, "); +MODULE_DESCRIPTION("S3C2412 I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c2412-iis"); diff --git a/sound/soc/samsung/s3c2412-i2s.h b/sound/soc/samsung/s3c2412-i2s.h new file mode 100644 index 000000000..02ad5794c --- /dev/null +++ b/sound/soc/samsung/s3c2412-i2s.h @@ -0,0 +1,27 @@ +/* sound/soc/samsung/s3c2412-i2s.c + * + * ALSA Soc Audio Layer - S3C2412 I2S driver + * + * Copyright (c) 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. +*/ + +#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H +#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__ + +#include "s3c-i2s-v2.h" + +#define S3C2412_DIV_BCLK S3C_I2SV2_DIV_BCLK +#define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK +#define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER + +#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK +#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS + +#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */ diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c new file mode 100644 index 000000000..8d58d0218 --- /dev/null +++ b/sound/soc/samsung/s3c24xx-i2s.c @@ -0,0 +1,476 @@ +/* + * s3c24xx-i2s.c -- ALSA Soc Audio Layer + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Copyright 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include "regs-iis.h" + +#include "dma.h" +#include "s3c24xx-i2s.h" + +static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_out = { + .chan_name = "tx", + .addr_width = 2, +}; + +static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_in = { + .chan_name = "rx", + .addr_width = 2, +}; + +struct s3c24xx_i2s_info { + void __iomem *regs; + struct clk *iis_clk; + u32 iiscon; + u32 iismod; + u32 iisfcon; + u32 iispsr; +}; +static struct s3c24xx_i2s_info s3c24xx_i2s; + +static void s3c24xx_snd_txctrl(int on) +{ + u32 iisfcon; + u32 iiscon; + u32 iismod; + + iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); + + if (on) { + iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; + iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; + iiscon &= ~S3C2410_IISCON_TXIDLE; + iismod |= S3C2410_IISMOD_TXMODE; + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + } else { + /* note, we have to disable the FIFOs otherwise bad things + * seem to happen when the DMA stops. According to the + * Samsung supplied kernel, this should allow the DMA + * engine and FIFOs to reset. If this isn't allowed, the + * DMA engine will simply freeze randomly. + */ + + iisfcon &= ~S3C2410_IISFCON_TXENABLE; + iisfcon &= ~S3C2410_IISFCON_TXDMA; + iiscon |= S3C2410_IISCON_TXIDLE; + iiscon &= ~S3C2410_IISCON_TXDMAEN; + iismod &= ~S3C2410_IISMOD_TXMODE; + + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + } + + pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); +} + +static void s3c24xx_snd_rxctrl(int on) +{ + u32 iisfcon; + u32 iiscon; + u32 iismod; + + iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); + + if (on) { + iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; + iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; + iiscon &= ~S3C2410_IISCON_RXIDLE; + iismod |= S3C2410_IISMOD_RXMODE; + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + } else { + /* note, we have to disable the FIFOs otherwise bad things + * seem to happen when the DMA stops. According to the + * Samsung supplied kernel, this should allow the DMA + * engine and FIFOs to reset. If this isn't allowed, the + * DMA engine will simply freeze randomly. + */ + + iisfcon &= ~S3C2410_IISFCON_RXENABLE; + iisfcon &= ~S3C2410_IISFCON_RXDMA; + iiscon |= S3C2410_IISCON_RXIDLE; + iiscon &= ~S3C2410_IISCON_RXDMAEN; + iismod &= ~S3C2410_IISMOD_RXMODE; + + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + } + + pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); +} + +/* + * Wait for the LR signal to allow synchronisation to the L/R clock + * from the codec. May only be needed for slave mode. + */ +static int s3c24xx_snd_lrsync(void) +{ + u32 iiscon; + int timeout = 50; /* 5ms */ + + while (1) { + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + if (iiscon & S3C2410_IISCON_LRINDEX) + break; + + if (!timeout--) + return -ETIMEDOUT; + udelay(100); + } + + return 0; +} + +/* + * Check whether CPU is the master or slave + */ +static inline int s3c24xx_snd_is_clkmaster(void) +{ + return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; +} + +/* + * Set S3C24xx I2S DAI format + */ +static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + u32 iismod; + + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params r: IISMOD: %x \n", iismod); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iismod |= S3C2410_IISMOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iismod &= ~S3C2410_IISMOD_SLAVE; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2410_IISMOD_MSB; + break; + case SND_SOC_DAIFMT_I2S: + iismod &= ~S3C2410_IISMOD_MSB; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params w: IISMOD: %x \n", iismod); + + return 0; +} + +static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_dmaengine_dai_dma_data *dma_data; + u32 iismod; + + dma_data = snd_soc_dai_get_dma_data(dai, substream); + + /* Working copies of register */ + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params r: IISMOD: %x\n", iismod); + + switch (params_width(params)) { + case 8: + iismod &= ~S3C2410_IISMOD_16BIT; + dma_data->addr_width = 1; + break; + case 16: + iismod |= S3C2410_IISMOD_16BIT; + dma_data->addr_width = 2; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params w: IISMOD: %x\n", iismod); + + return 0; +} + +static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!s3c24xx_snd_is_clkmaster()) { + ret = s3c24xx_snd_lrsync(); + if (ret) + goto exit_err; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c24xx_snd_rxctrl(1); + else + s3c24xx_snd_txctrl(1); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c24xx_snd_rxctrl(0); + else + s3c24xx_snd_txctrl(0); + break; + default: + ret = -EINVAL; + break; + } + +exit_err: + return ret; +} + +/* + * Set S3C24xx Clock source + */ +static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + iismod &= ~S3C2440_IISMOD_MPLL; + + switch (clk_id) { + case S3C24XX_CLKSRC_PCLK: + break; + case S3C24XX_CLKSRC_MPLL: + iismod |= S3C2440_IISMOD_MPLL; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + return 0; +} + +/* + * Set S3C24xx Clock dividers + */ +static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + u32 reg; + + switch (div_id) { + case S3C24XX_DIV_BCLK: + reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; + writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); + break; + case S3C24XX_DIV_MCLK: + reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); + writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); + break; + case S3C24XX_DIV_PRESCALER: + writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); + reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * To avoid duplicating clock code, allow machine driver to + * get the clockrate from here. + */ +u32 s3c24xx_i2s_get_clockrate(void) +{ + return clk_get_rate(s3c24xx_i2s.iis_clk); +} +EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); + +static int s3c24xx_i2s_probe(struct snd_soc_dai *dai) +{ + int ret; + snd_soc_dai_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out, + &s3c24xx_i2s_pcm_stereo_in); + + s3c24xx_i2s.iis_clk = devm_clk_get(dai->dev, "iis"); + if (IS_ERR(s3c24xx_i2s.iis_clk)) { + pr_err("failed to get iis_clock\n"); + return PTR_ERR(s3c24xx_i2s.iis_clk); + } + ret = clk_prepare_enable(s3c24xx_i2s.iis_clk); + if (ret) + return ret; + + /* Configure the I2S pins (GPE0...GPE4) in correct mode */ + s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2), + S3C_GPIO_PULL_NONE); + + writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); + + s3c24xx_snd_txctrl(0); + s3c24xx_snd_rxctrl(0); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai) +{ + s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR); + + clk_disable_unprepare(s3c24xx_i2s.iis_clk); + + return 0; +} + +static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai) +{ + int ret; + + ret = clk_prepare_enable(s3c24xx_i2s.iis_clk); + if (ret) + return ret; + + writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR); + + return 0; +} +#else +#define s3c24xx_i2s_suspend NULL +#define s3c24xx_i2s_resume NULL +#endif + +#define S3C24XX_I2S_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = { + .trigger = s3c24xx_i2s_trigger, + .hw_params = s3c24xx_i2s_hw_params, + .set_fmt = s3c24xx_i2s_set_fmt, + .set_clkdiv = s3c24xx_i2s_set_clkdiv, + .set_sysclk = s3c24xx_i2s_set_sysclk, +}; + +static struct snd_soc_dai_driver s3c24xx_i2s_dai = { + .probe = s3c24xx_i2s_probe, + .suspend = s3c24xx_i2s_suspend, + .resume = s3c24xx_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C24XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C24XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &s3c24xx_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver s3c24xx_i2s_component = { + .name = "s3c24xx-i2s", +}; + +static int s3c24xx_iis_dev_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + s3c24xx_i2s.regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(s3c24xx_i2s.regs)) + return PTR_ERR(s3c24xx_i2s.regs); + + s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO; + s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, NULL, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register the DMA: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1); + if (ret) + dev_err(&pdev->dev, "Failed to register the DAI\n"); + + return ret; +} + +static struct platform_driver s3c24xx_iis_driver = { + .probe = s3c24xx_iis_dev_probe, + .driver = { + .name = "s3c24xx-iis", + }, +}; + +module_platform_driver(s3c24xx_iis_driver); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, "); +MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c24xx-iis"); diff --git a/sound/soc/samsung/s3c24xx-i2s.h b/sound/soc/samsung/s3c24xx-i2s.h new file mode 100644 index 000000000..f9ca04eda --- /dev/null +++ b/sound/soc/samsung/s3c24xx-i2s.h @@ -0,0 +1,35 @@ +/* + * s3c24xx-i2s.c -- ALSA Soc Audio Layer + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 10th Nov 2006 Initial version. + */ + +#ifndef S3C24XXI2S_H_ +#define S3C24XXI2S_H_ + +/* clock sources */ +#define S3C24XX_CLKSRC_PCLK 0 +#define S3C24XX_CLKSRC_MPLL 1 + +/* Clock dividers */ +#define S3C24XX_DIV_MCLK 0 +#define S3C24XX_DIV_BCLK 1 +#define S3C24XX_DIV_PRESCALER 2 + +/* prescaler */ +#define S3C24XX_PRESCALE(a,b) \ + (((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT)) + +u32 s3c24xx_i2s_get_clockrate(void); + +#endif /*S3C24XXI2S_H_*/ diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c new file mode 100644 index 000000000..6de63f3e3 --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec.c @@ -0,0 +1,372 @@ +/* sound/soc/samsung/s3c24xx_simtec.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include + +#include + +#include + +#include "s3c24xx-i2s.h" +#include "s3c24xx_simtec.h" + +static struct s3c24xx_audio_simtec_pdata *pdata; +static struct clk *xtal_clk; + +static int spk_gain; +static int spk_unmute; + +/** + * speaker_gain_get - read the speaker gain setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be updated. + * + * Read the value for the AMP gain control. + */ +static int speaker_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spk_gain; + return 0; +} + +/** + * speaker_gain_set - set the value of the speaker amp gain + * @value: The value to write. + */ +static void speaker_gain_set(int value) +{ + gpio_set_value_cansleep(pdata->amp_gain[0], value & 1); + gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1); +} + +/** + * speaker_gain_put - set the speaker gain setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be set. + * + * Set the value of the speaker gain from the specified + * @ucontrol setting. + * + * Note, if the speaker amp is muted, then we do not set a gain value + * as at-least one of the ICs that is fitted will try and power up even + * if the main control is set to off. + */ +static int speaker_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = ucontrol->value.integer.value[0]; + + spk_gain = value; + + if (!spk_unmute) + speaker_gain_set(value); + + return 0; +} + +static const struct snd_kcontrol_new amp_gain_controls[] = { + SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0, + speaker_gain_get, speaker_gain_put), +}; + +/** + * spk_unmute_state - set the unmute state of the speaker + * @to: zero to unmute, non-zero to ununmute. + */ +static void spk_unmute_state(int to) +{ + pr_debug("%s: to=%d\n", __func__, to); + + spk_unmute = to; + gpio_set_value(pdata->amp_gpio, to); + + /* if we're umuting, also re-set the gain */ + if (to && pdata->amp_gain[0] > 0) + speaker_gain_set(spk_gain); +} + +/** + * speaker_unmute_get - read the speaker unmute setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be updated. + * + * Read the value for the AMP gain control. + */ +static int speaker_unmute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spk_unmute; + return 0; +} + +/** + * speaker_unmute_put - set the speaker unmute setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be set. + * + * Set the value of the speaker gain from the specified + * @ucontrol setting. + */ +static int speaker_unmute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + spk_unmute_state(ucontrol->value.integer.value[0]); + return 0; +} + +/* This is added as a manual control as the speaker amps create clicks + * when their power state is changed, which are far more noticeable than + * anything produced by the CODEC itself. + */ +static const struct snd_kcontrol_new amp_unmute_controls[] = { + SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0, + speaker_unmute_get, speaker_unmute_put), +}; + +void simtec_audio_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + + if (pdata->amp_gpio > 0) { + pr_debug("%s: adding amp routes\n", __func__); + + snd_soc_add_card_controls(card, amp_unmute_controls, + ARRAY_SIZE(amp_unmute_controls)); + } + + if (pdata->amp_gain[0] > 0) { + pr_debug("%s: adding amp controls\n", __func__); + snd_soc_add_card_controls(card, amp_gain_controls, + ARRAY_SIZE(amp_gain_controls)); + } +} +EXPORT_SYMBOL_GPL(simtec_audio_init); + +#define CODEC_CLOCK 12000000 + +/** + * simtec_hw_params - update hardware parameters + * @substream: The audio substream instance. + * @params: The parameters requested. + * + * Update the codec data routing and configuration settings + * from the supplied data. + */ +static int simtec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + CODEC_CLOCK, SND_SOC_CLOCK_IN); + if (ret) { + pr_err( "%s: failed setting codec sysclk\n", __func__); + return ret; + } + + if (pdata->use_mpllin) { + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL, + 0, SND_SOC_CLOCK_OUT); + + if (ret) { + pr_err("%s: failed to set MPLLin as clksrc\n", + __func__); + return ret; + } + } + + if (pdata->output_cdclk) { + int cdclk_scale; + + cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK; + cdclk_scale--; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + cdclk_scale); + } + + return 0; +} + +static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd) +{ + /* call any board supplied startup code, this currently only + * covers the bast/vr1000 which have a CPLD in the way of the + * LRCLK */ + if (pd->startup) + pd->startup(); + + return 0; +} + +static const struct snd_soc_ops simtec_snd_ops = { + .hw_params = simtec_hw_params, +}; + +/** + * attach_gpio_amp - get and configure the necessary gpios + * @dev: The device we're probing. + * @pd: The platform data supplied by the board. + * + * If there is a GPIO based amplifier attached to the board, claim + * the necessary GPIO lines for it, and set default values. + */ +static int attach_gpio_amp(struct device *dev, + struct s3c24xx_audio_simtec_pdata *pd) +{ + int ret; + + /* attach gpio amp gain (if any) */ + if (pdata->amp_gain[0] > 0) { + ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0"); + if (ret) { + dev_err(dev, "cannot get amp gpio gain0\n"); + return ret; + } + + ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1"); + if (ret) { + dev_err(dev, "cannot get amp gpio gain1\n"); + gpio_free(pdata->amp_gain[0]); + return ret; + } + + gpio_direction_output(pd->amp_gain[0], 0); + gpio_direction_output(pd->amp_gain[1], 0); + } + + /* note, currently we assume GPA0 isn't valid amp */ + if (pdata->amp_gpio > 0) { + ret = gpio_request(pd->amp_gpio, "gpio-amp"); + if (ret) { + dev_err(dev, "cannot get amp gpio %d (%d)\n", + pd->amp_gpio, ret); + goto err_amp; + } + + /* set the amp off at startup */ + spk_unmute_state(0); + } + + return 0; + +err_amp: + if (pd->amp_gain[0] > 0) { + gpio_free(pd->amp_gain[0]); + gpio_free(pd->amp_gain[1]); + } + + return ret; +} + +static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd) +{ + if (pd->amp_gain[0] > 0) { + gpio_free(pd->amp_gain[0]); + gpio_free(pd->amp_gain[1]); + } + + if (pd->amp_gpio > 0) + gpio_free(pd->amp_gpio); +} + +#ifdef CONFIG_PM +static int simtec_audio_resume(struct device *dev) +{ + simtec_call_startup(pdata); + return 0; +} + +const struct dev_pm_ops simtec_audio_pmops = { + .resume = simtec_audio_resume, +}; +EXPORT_SYMBOL_GPL(simtec_audio_pmops); +#endif + +int simtec_audio_core_probe(struct platform_device *pdev, + struct snd_soc_card *card) +{ + struct platform_device *snd_dev; + int ret; + + card->dai_link->ops = &simtec_snd_ops; + card->dai_link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data supplied\n"); + return -EINVAL; + } + + simtec_call_startup(pdata); + + xtal_clk = clk_get(&pdev->dev, "xtal"); + if (IS_ERR(xtal_clk)) { + dev_err(&pdev->dev, "could not get clkout0\n"); + return -EINVAL; + } + + dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk)); + + ret = attach_gpio_amp(&pdev->dev, pdata); + if (ret) + goto err_clk; + + snd_dev = platform_device_alloc("soc-audio", -1); + if (!snd_dev) { + dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n"); + ret = -ENOMEM; + goto err_gpio; + } + + platform_set_drvdata(snd_dev, card); + + ret = platform_device_add(snd_dev); + if (ret) { + dev_err(&pdev->dev, "failed to add soc-audio dev\n"); + goto err_pdev; + } + + platform_set_drvdata(pdev, snd_dev); + return 0; + +err_pdev: + platform_device_put(snd_dev); + +err_gpio: + detach_gpio_amp(pdata); + +err_clk: + clk_put(xtal_clk); + return ret; +} +EXPORT_SYMBOL_GPL(simtec_audio_core_probe); + +int simtec_audio_remove(struct platform_device *pdev) +{ + struct platform_device *snd_dev = platform_get_drvdata(pdev); + + platform_device_unregister(snd_dev); + + detach_gpio_amp(pdata); + clk_put(xtal_clk); + return 0; +} +EXPORT_SYMBOL_GPL(simtec_audio_remove); + +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c24xx_simtec.h b/sound/soc/samsung/s3c24xx_simtec.h new file mode 100644 index 000000000..8270748a2 --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec.h @@ -0,0 +1,22 @@ +/* sound/soc/samsung/s3c24xx_simtec.h + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +extern void simtec_audio_init(struct snd_soc_pcm_runtime *rtd); + +extern int simtec_audio_core_probe(struct platform_device *pdev, + struct snd_soc_card *card); + +extern int simtec_audio_remove(struct platform_device *pdev); + +#ifdef CONFIG_PM +extern const struct dev_pm_ops simtec_audio_pmops; +#define simtec_audio_pm &simtec_audio_pmops +#else +#define simtec_audio_pm NULL +#endif diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c new file mode 100644 index 000000000..7ac924c59 --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c @@ -0,0 +1,114 @@ +/* sound/soc/samsung/s3c24xx_simtec_hermes.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include + +#include "s3c24xx_simtec.h" + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_LINE("GSM Out", NULL), + SND_SOC_DAPM_LINE("GSM In", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_LINE("ZV", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_soc_dapm_route base_map[] = { + /* Headphone connected to HP{L,R}OUT and HP{L,R}COM */ + + { "Headphone Jack", NULL, "HPLOUT" }, + { "Headphone Jack", NULL, "HPLCOM" }, + { "Headphone Jack", NULL, "HPROUT" }, + { "Headphone Jack", NULL, "HPRCOM" }, + + /* ZV connected to Line1 */ + + { "LINE1L", NULL, "ZV" }, + { "LINE1R", NULL, "ZV" }, + + /* Line In connected to Line2 */ + + { "LINE2L", NULL, "Line In" }, + { "LINE2R", NULL, "Line In" }, + + /* Microphone connected to MIC3R and MIC_BIAS */ + + { "MIC3L", NULL, "Mic Jack" }, + + /* GSM connected to MONO_LOUT and MIC3L (in) */ + + { "GSM Out", NULL, "MONO_LOUT" }, + { "MIC3L", NULL, "GSM In" }, + + /* Speaker is connected to LINEOUT{LN,LP,RN,RP}, however we are + * not using the DAPM to power it up and down as there it makes + * a click when powering up. */ +}; + +/** + * simtec_hermes_init - initialise and add controls + * @codec; The codec instance to attach to. + * + * Attach our controls and configure the necessary codec + * mappings for our sound card instance. +*/ +static int simtec_hermes_init(struct snd_soc_pcm_runtime *rtd) +{ + simtec_audio_init(rtd); + + return 0; +} + +static struct snd_soc_dai_link simtec_dai_aic33 = { + .name = "tlv320aic33", + .stream_name = "TLV320AIC33", + .codec_name = "tlv320aic3x-codec.0-001a", + .cpu_dai_name = "s3c24xx-iis", + .codec_dai_name = "tlv320aic3x-hifi", + .platform_name = "s3c24xx-iis", + .init = simtec_hermes_init, +}; + +/* simtec audio machine driver */ +static struct snd_soc_card snd_soc_machine_simtec_aic33 = { + .name = "Simtec-Hermes", + .owner = THIS_MODULE, + .dai_link = &simtec_dai_aic33, + .num_links = 1, + + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = base_map, + .num_dapm_routes = ARRAY_SIZE(base_map), +}; + +static int simtec_audio_hermes_probe(struct platform_device *pd) +{ + dev_info(&pd->dev, "probing....\n"); + return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic33); +} + +static struct platform_driver simtec_audio_hermes_platdrv = { + .driver = { + .name = "s3c24xx-simtec-hermes-snd", + .pm = simtec_audio_pm, + }, + .probe = simtec_audio_hermes_probe, + .remove = simtec_audio_remove, +}; + +module_platform_driver(simtec_audio_hermes_platdrv); + +MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c new file mode 100644 index 000000000..b4ed2fc1a --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c @@ -0,0 +1,102 @@ +/* sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include + +#include "s3c24xx_simtec.h" + +/* supported machines: + * + * Machine Connections AMP + * ------- ----------- --- + * BAST MIC, HPOUT, LOUT, LIN TPA2001D1 (HPOUTL,R) (gain hardwired) + * VR1000 HPOUT, LIN None + * VR2000 LIN, LOUT, MIC, HP LM4871 (HPOUTL,R) + * DePicture LIN, LOUT, MIC, HP LM4871 (HPOUTL,R) + * Anubis LIN, LOUT, MIC, HP TPA2001D1 (HPOUTL,R) + */ + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route base_map[] = { + { "Headphone Jack", NULL, "LHPOUT"}, + { "Headphone Jack", NULL, "RHPOUT"}, + + { "Line Out", NULL, "LOUT" }, + { "Line Out", NULL, "ROUT" }, + + { "LLINEIN", NULL, "Line In"}, + { "RLINEIN", NULL, "Line In"}, + + { "MICIN", NULL, "Mic Jack"}, +}; + +/** + * simtec_tlv320aic23_init - initialise and add controls + * @codec; The codec instance to attach to. + * + * Attach our controls and configure the necessary codec + * mappings for our sound card instance. +*/ +static int simtec_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd) +{ + simtec_audio_init(rtd); + + return 0; +} + +static struct snd_soc_dai_link simtec_dai_aic23 = { + .name = "tlv320aic23", + .stream_name = "TLV320AIC23", + .codec_name = "tlv320aic3x-codec.0-001a", + .cpu_dai_name = "s3c24xx-iis", + .codec_dai_name = "tlv320aic3x-hifi", + .platform_name = "s3c24xx-iis", + .init = simtec_tlv320aic23_init, +}; + +/* simtec audio machine driver */ +static struct snd_soc_card snd_soc_machine_simtec_aic23 = { + .name = "Simtec", + .owner = THIS_MODULE, + .dai_link = &simtec_dai_aic23, + .num_links = 1, + + .dapm_widgets = dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dapm_widgets), + .dapm_routes = base_map, + .num_dapm_routes = ARRAY_SIZE(base_map), +}; + +static int simtec_audio_tlv320aic23_probe(struct platform_device *pd) +{ + return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic23); +} + +static struct platform_driver simtec_audio_tlv320aic23_driver = { + .driver = { + .name = "s3c24xx-simtec-tlv320aic23", + .pm = simtec_audio_pm, + }, + .probe = simtec_audio_tlv320aic23_probe, + .remove = simtec_audio_remove, +}; + +module_platform_driver(simtec_audio_tlv320aic23_driver); + +MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c new file mode 100644 index 000000000..5fb3bab6b --- /dev/null +++ b/sound/soc/samsung/s3c24xx_uda134x.c @@ -0,0 +1,259 @@ +/* + * Modifications by Christian Pellegrin + * + * s3c24xx_uda134x.c -- S3C24XX_UDA134X ALSA SoC Audio board driver + * + * Copyright 2007 Dension Audio Systems Ltd. + * Author: Zoltan Devai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#include + +#include "regs-iis.h" +#include "s3c24xx-i2s.h" + +struct s3c24xx_uda134x { + struct clk *xtal; + struct clk *pclk; + struct mutex clk_lock; + int clk_users; +}; + +/* #define ENFORCE_RATES 1 */ +/* + Unfortunately the S3C24XX in master mode has a limited capacity of + generating the clock for the codec. If you define this only rates + that are really available will be enforced. But be careful, most + user level application just want the usual sampling frequencies (8, + 11.025, 22.050, 44.1 kHz) and anyway resampling is a costly + operation for embedded systems. So if you aren't very lucky or your + hardware engineer wasn't very forward-looking it's better to leave + this undefined. If you do so an approximate value for the requested + sampling rate in the range -/+ 5% will be chosen. If this in not + possible an error will be returned. +*/ + +static unsigned int rates[33 * 2]; +#ifdef ENFORCE_RATES +static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; +#endif + +static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + mutex_lock(&priv->clk_lock); + + if (priv->clk_users == 0) { + priv->xtal = clk_get(rtd->dev, "xtal"); + if (IS_ERR(priv->xtal)) { + dev_err(rtd->dev, "%s cannot get xtal\n", __func__); + ret = PTR_ERR(priv->xtal); + } else { + priv->pclk = clk_get(cpu_dai->dev, "iis"); + if (IS_ERR(priv->pclk)) { + dev_err(rtd->dev, "%s cannot get pclk\n", + __func__); + clk_put(priv->xtal); + ret = PTR_ERR(priv->pclk); + } + } + if (!ret) { + int i, j; + + for (i = 0; i < 2; i++) { + int fs = i ? 256 : 384; + + rates[i*33] = clk_get_rate(priv->xtal) / fs; + for (j = 1; j < 33; j++) + rates[i*33 + j] = clk_get_rate(priv->pclk) / + (j * fs); + } + } + } + priv->clk_users += 1; + mutex_unlock(&priv->clk_lock); + + if (!ret) { +#ifdef ENFORCE_RATES + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); + if (ret < 0) + dev_err(rtd->dev, "%s cannot set constraints\n", + __func__); +#endif + } + return ret; +} + +static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card); + + mutex_lock(&priv->clk_lock); + priv->clk_users -= 1; + if (priv->clk_users == 0) { + clk_put(priv->xtal); + priv->xtal = NULL; + clk_put(priv->pclk); + priv->pclk = NULL; + } + mutex_unlock(&priv->clk_lock); +} + +static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret = 0; + int clk_source, fs_mode; + unsigned long rate = params_rate(params); + long err, cerr; + unsigned int div; + int i, bi; + + err = 999999; + bi = 0; + for (i = 0; i < 2*33; i++) { + cerr = rates[i] - rate; + if (cerr < 0) + cerr = -cerr; + if (cerr < err) { + err = cerr; + bi = i; + } + } + if (bi / 33 == 1) + fs_mode = S3C2410_IISMOD_256FS; + else + fs_mode = S3C2410_IISMOD_384FS; + if (bi % 33 == 0) { + clk_source = S3C24XX_CLKSRC_MPLL; + div = 1; + } else { + clk_source = S3C24XX_CLKSRC_PCLK; + div = bi % 33; + } + + dev_dbg(rtd->dev, "%s desired rate %lu, %d\n", __func__, rate, bi); + + clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate; + + dev_dbg(rtd->dev, "%s will use: %s %s %d sysclk %d err %ld\n", __func__, + fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS", + clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK", + div, clk, err); + + if ((err * 100 / rate) > 5) { + dev_err(rtd->dev, "effective frequency too different " + "from desired (%ld%%)\n", err * 100 / rate); + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(div, div)); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_soc_ops s3c24xx_uda134x_ops = { + .startup = s3c24xx_uda134x_startup, + .shutdown = s3c24xx_uda134x_shutdown, + .hw_params = s3c24xx_uda134x_hw_params, +}; + +static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = { + .name = "UDA134X", + .stream_name = "UDA134X", + .codec_name = "uda134x-codec", + .codec_dai_name = "uda134x-hifi", + .cpu_dai_name = "s3c24xx-iis", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &s3c24xx_uda134x_ops, + .platform_name = "s3c24xx-iis", +}; + +static struct snd_soc_card snd_soc_s3c24xx_uda134x = { + .name = "S3C24XX_UDA134X", + .owner = THIS_MODULE, + .dai_link = &s3c24xx_uda134x_dai_link, + .num_links = 1, +}; + +static int s3c24xx_uda134x_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x; + struct s3c24xx_uda134x *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->clk_lock); + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "failed to register card: %d\n", ret); + + return ret; +} + +static struct platform_driver s3c24xx_uda134x_driver = { + .probe = s3c24xx_uda134x_probe, + .driver = { + .name = "s3c24xx_uda134x", + }, +}; +module_platform_driver(s3c24xx_uda134x_driver); + +MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin "); +MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c new file mode 100644 index 000000000..cf0f54e65 --- /dev/null +++ b/sound/soc/samsung/smartq_wm8987.c @@ -0,0 +1,229 @@ +/* sound/soc/samsung/smartq_wm8987.c + * + * Copyright 2010 Maurus Cuelenaere + * + * Based on smdk6410_wm8987.c + * Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com + * Graeme Gregory - graeme.gregory@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include + +#include +#include + +#include "i2s.h" +#include "../codecs/wm8750.h" + +/* + * WM8987 is register compatible with WM8750, so using that as base driver. + */ + +static struct snd_soc_card snd_soc_smartq; + +static int smartq_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 32000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + clk = 11289600; + break; + } + + /* Use PCLK for I2S signal generation */ + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* Gate the RCLK output on PAD */ + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SmartQ WM8987 HiFi DAI operations. + */ +static struct snd_soc_ops smartq_hifi_ops = { + .hw_params = smartq_hifi_hw_params, +}; + +static struct snd_soc_jack smartq_jack; + +static struct snd_soc_jack_pin smartq_jack_pins[] = { + /* Disable speaker when headphone is plugged in */ + { + .pin = "Internal Speaker", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio smartq_jack_gpios[] = { + { + .gpio = -1, + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, + }, +}; + +static const struct snd_kcontrol_new wm8987_smartq_controls[] = { + SOC_DAPM_PIN_SWITCH("Internal Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static int smartq_speaker_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, + int event) +{ + struct gpio_desc *gpio = snd_soc_card_get_drvdata(&snd_soc_smartq); + + gpiod_set_value(gpio, SND_SOC_DAPM_EVENT_OFF(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LOUT2"}, + {"Headphone Jack", NULL, "ROUT2"}, + + {"Internal Speaker", NULL, "LOUT2"}, + {"Internal Speaker", NULL, "ROUT2"}, + + {"Mic Bias", NULL, "Internal Mic"}, + {"LINPUT2", NULL, "Mic Bias"}, +}; + +static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + int err = 0; + + /* set endpoints to not connected */ + snd_soc_dapm_nc_pin(dapm, "LINPUT1"); + snd_soc_dapm_nc_pin(dapm, "RINPUT1"); + snd_soc_dapm_nc_pin(dapm, "OUT3"); + snd_soc_dapm_nc_pin(dapm, "ROUT1"); + + /* Headphone jack detection */ + err = snd_soc_card_jack_new(rtd->card, "Headphone Jack", + SND_JACK_HEADPHONE, &smartq_jack, + smartq_jack_pins, + ARRAY_SIZE(smartq_jack_pins)); + if (err) + return err; + + err = snd_soc_jack_add_gpios(&smartq_jack, + ARRAY_SIZE(smartq_jack_gpios), + smartq_jack_gpios); + + return err; +} + +static struct snd_soc_dai_link smartq_dai[] = { + { + .name = "wm8987", + .stream_name = "SmartQ Hi-Fi", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8750-hifi", + .platform_name = "samsung-i2s.0", + .codec_name = "wm8750.0-0x1a", + .init = smartq_wm8987_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &smartq_hifi_ops, + }, +}; + +static struct snd_soc_card snd_soc_smartq = { + .name = "SmartQ", + .owner = THIS_MODULE, + .dai_link = smartq_dai, + .num_links = ARRAY_SIZE(smartq_dai), + + .dapm_widgets = wm8987_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8987_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), + .controls = wm8987_smartq_controls, + .num_controls = ARRAY_SIZE(wm8987_smartq_controls), +}; + +static int smartq_probe(struct platform_device *pdev) +{ + struct gpio_desc *gpio; + int ret; + + platform_set_drvdata(pdev, &snd_soc_smartq); + + /* Initialise GPIOs used by amplifiers */ + gpio = devm_gpiod_get(&pdev->dev, "amplifiers shutdown", + GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "Failed to register GPK12\n"); + ret = PTR_ERR(gpio); + goto out; + } + snd_soc_card_set_drvdata(&snd_soc_smartq, gpio); + + ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_smartq); + if (ret) + dev_err(&pdev->dev, "Failed to register card\n"); + +out: + return ret; +} + +static struct platform_driver smartq_driver = { + .driver = { + .name = "smartq-audio", + }, + .probe = smartq_probe, +}; + +module_platform_driver(smartq_driver); + +/* Module information */ +MODULE_AUTHOR("Maurus Cuelenaere "); +MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c new file mode 100644 index 000000000..7fc7cc6d1 --- /dev/null +++ b/sound/soc/samsung/smdk_spdif.c @@ -0,0 +1,223 @@ +/* + * smdk_spdif.c -- S/PDIF audio for SMDK + * + * Copyright 2010 Samsung Electronics Co. Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + */ + +#include +#include + +#include + +#include "spdif.h" + +/* Audio clock settings are belonged to board specific part. Every + * board can set audio source clock setting which is matched with H/W + * like this function-'set_audio_clock_heirachy'. + */ +static int set_audio_clock_heirachy(struct platform_device *pdev) +{ + struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif; + int ret = 0; + + fout_epll = clk_get(NULL, "fout_epll"); + if (IS_ERR(fout_epll)) { + printk(KERN_WARNING "%s: Cannot find fout_epll.\n", + __func__); + return -EINVAL; + } + + mout_epll = clk_get(NULL, "mout_epll"); + if (IS_ERR(mout_epll)) { + printk(KERN_WARNING "%s: Cannot find mout_epll.\n", + __func__); + ret = -EINVAL; + goto out1; + } + + sclk_audio0 = clk_get(&pdev->dev, "sclk_audio"); + if (IS_ERR(sclk_audio0)) { + printk(KERN_WARNING "%s: Cannot find sclk_audio.\n", + __func__); + ret = -EINVAL; + goto out2; + } + + sclk_spdif = clk_get(NULL, "sclk_spdif"); + if (IS_ERR(sclk_spdif)) { + printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n", + __func__); + ret = -EINVAL; + goto out3; + } + + /* Set audio clock hierarchy for S/PDIF */ + clk_set_parent(mout_epll, fout_epll); + clk_set_parent(sclk_audio0, mout_epll); + clk_set_parent(sclk_spdif, sclk_audio0); + + clk_put(sclk_spdif); +out3: + clk_put(sclk_audio0); +out2: + clk_put(mout_epll); +out1: + clk_put(fout_epll); + + return ret; +} + +/* We should haved to set clock directly on this part because of clock + * scheme of Samsudng SoCs did not support to set rates from abstrct + * clock of it's hierarchy. + */ +static int set_audio_clock_rate(unsigned long epll_rate, + unsigned long audio_rate) +{ + struct clk *fout_epll, *sclk_spdif; + + fout_epll = clk_get(NULL, "fout_epll"); + if (IS_ERR(fout_epll)) { + printk(KERN_ERR "%s: failed to get fout_epll\n", __func__); + return -ENOENT; + } + + clk_set_rate(fout_epll, epll_rate); + clk_put(fout_epll); + + sclk_spdif = clk_get(NULL, "sclk_spdif"); + if (IS_ERR(sclk_spdif)) { + printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__); + return -ENOENT; + } + + clk_set_rate(sclk_spdif, audio_rate); + clk_put(sclk_spdif); + + return 0; +} + +static int smdk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned long pll_out, rclk_rate; + int ret, ratio; + + switch (params_rate(params)) { + case 44100: + pll_out = 45158400; + break; + case 32000: + case 48000: + case 96000: + pll_out = 49152000; + break; + default: + return -EINVAL; + } + + /* Setting ratio to 512fs helps to use S/PDIF with HDMI without + * modify S/PDIF ASoC machine driver. + */ + ratio = 512; + rclk_rate = params_rate(params) * ratio; + + /* Set audio source clock rates */ + ret = set_audio_clock_rate(pll_out, rclk_rate); + if (ret < 0) + return ret; + + /* Set S/PDIF uses internal source clock */ + ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, + rclk_rate, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return ret; +} + +static const struct snd_soc_ops smdk_spdif_ops = { + .hw_params = smdk_hw_params, +}; + +static struct snd_soc_dai_link smdk_dai = { + .name = "S/PDIF", + .stream_name = "S/PDIF PCM Playback", + .platform_name = "samsung-spdif", + .cpu_dai_name = "samsung-spdif", + .codec_dai_name = "dit-hifi", + .codec_name = "spdif-dit", + .ops = &smdk_spdif_ops, +}; + +static struct snd_soc_card smdk = { + .name = "SMDK-S/PDIF", + .owner = THIS_MODULE, + .dai_link = &smdk_dai, + .num_links = 1, +}; + +static struct platform_device *smdk_snd_spdif_dit_device; +static struct platform_device *smdk_snd_spdif_device; + +static int __init smdk_init(void) +{ + int ret; + + smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1); + if (!smdk_snd_spdif_dit_device) + return -ENOMEM; + + ret = platform_device_add(smdk_snd_spdif_dit_device); + if (ret) + goto err1; + + smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1); + if (!smdk_snd_spdif_device) { + ret = -ENOMEM; + goto err2; + } + + platform_set_drvdata(smdk_snd_spdif_device, &smdk); + + ret = platform_device_add(smdk_snd_spdif_device); + if (ret) + goto err3; + + /* Set audio clock hierarchy manually */ + ret = set_audio_clock_heirachy(smdk_snd_spdif_device); + if (ret) + goto err4; + + return 0; +err4: + platform_device_del(smdk_snd_spdif_device); +err3: + platform_device_put(smdk_snd_spdif_device); +err2: + platform_device_del(smdk_snd_spdif_dit_device); +err1: + platform_device_put(smdk_snd_spdif_dit_device); + return ret; +} + +static void __exit smdk_exit(void) +{ + platform_device_unregister(smdk_snd_spdif_device); + platform_device_unregister(smdk_snd_spdif_dit_device); +} + +module_init(smdk_init); +module_exit(smdk_exit); + +MODULE_AUTHOR("Seungwhan Youn, "); +MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c new file mode 100644 index 000000000..6e4dfa7e2 --- /dev/null +++ b/sound/soc/samsung/smdk_wm8580.c @@ -0,0 +1,214 @@ +/* + * smdk_wm8580.c + * + * Copyright (c) 2009 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include + +#include "../codecs/wm8580.h" +#include "i2s.h" + +/* + * Default CFG switch settings to use this driver: + * + * SMDK6410: Set CFG1 1-3 Off, CFG2 1-4 On + */ + +/* SMDK has a 12MHZ crystal attached to WM8580 */ +#define SMDK_WM8580_FREQ 12000000 + +static int smdk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int rfs, ret; + + switch (params_width(params)) { + case 8: + case 16: + break; + default: + return -EINVAL; + } + + /* The Fvco for WM8580 PLLs must fall within [90,100]MHz. + * This criterion can't be met if we request PLL output + * as {8000x256, 64000x256, 11025x256}Hz. + * As a wayout, we rather change rfs to a minimum value that + * results in (params_rate(params) * rfs), and itself, acceptable + * to both - the CODEC and the CPU. + */ + switch (params_rate(params)) { + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + rfs = 256; + break; + case 64000: + rfs = 384; + break; + case 8000: + case 11025: + rfs = 512; + break; + default: + return -EINVAL; + } + pll_out = params_rate(params) * rfs; + + /* Set WM8580 to drive MCLK from its PLLA */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK, + WM8580_CLKSRC_PLLA); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0, + SMDK_WM8580_FREQ, pll_out); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SMDK WM8580 DAI operations. + */ +static struct snd_soc_ops smdk_ops = { + .hw_params = smdk_hw_params, +}; + +/* SMDK Playback widgets */ +static const struct snd_soc_dapm_widget smdk_wm8580_dapm_widgets[] = { + SND_SOC_DAPM_HP("Front", NULL), + SND_SOC_DAPM_HP("Center+Sub", NULL), + SND_SOC_DAPM_HP("Rear", NULL), + + SND_SOC_DAPM_MIC("MicIn", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), +}; + +/* SMDK-PAIFTX connections */ +static const struct snd_soc_dapm_route smdk_wm8580_audio_map[] = { + /* MicIn feeds AINL */ + {"AINL", NULL, "MicIn"}, + + /* LineIn feeds AINL/R */ + {"AINL", NULL, "LineIn"}, + {"AINR", NULL, "LineIn"}, + + /* Front Left/Right are fed VOUT1L/R */ + {"Front", NULL, "VOUT1L"}, + {"Front", NULL, "VOUT1R"}, + + /* Center/Sub are fed VOUT2L/R */ + {"Center+Sub", NULL, "VOUT2L"}, + {"Center+Sub", NULL, "VOUT2R"}, + + /* Rear Left/Right are fed VOUT3L/R */ + {"Rear", NULL, "VOUT3L"}, + {"Rear", NULL, "VOUT3R"}, +}; + +static int smdk_wm8580_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + /* Enabling the microphone requires the fitting of a 0R + * resistor to connect the line from the microphone jack. + */ + snd_soc_dapm_disable_pin(&rtd->card->dapm, "MicIn"); + + return 0; +} + +enum { + PRI_PLAYBACK = 0, + PRI_CAPTURE, +}; + +#define SMDK_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static struct snd_soc_dai_link smdk_dai[] = { + [PRI_PLAYBACK] = { /* Primary Playback i/f */ + .name = "WM8580 PAIF RX", + .stream_name = "Playback", + .cpu_dai_name = "samsung-i2s.2", + .codec_dai_name = "wm8580-hifi-playback", + .platform_name = "samsung-i2s.0", + .codec_name = "wm8580.0-001b", + .dai_fmt = SMDK_DAI_FMT, + .ops = &smdk_ops, + }, + [PRI_CAPTURE] = { /* Primary Capture i/f */ + .name = "WM8580 PAIF TX", + .stream_name = "Capture", + .cpu_dai_name = "samsung-i2s.2", + .codec_dai_name = "wm8580-hifi-capture", + .platform_name = "samsung-i2s.0", + .codec_name = "wm8580.0-001b", + .dai_fmt = SMDK_DAI_FMT, + .init = smdk_wm8580_init_paiftx, + .ops = &smdk_ops, + }, +}; + +static struct snd_soc_card smdk = { + .name = "SMDK-I2S", + .owner = THIS_MODULE, + .dai_link = smdk_dai, + .num_links = ARRAY_SIZE(smdk_dai), + + .dapm_widgets = smdk_wm8580_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(smdk_wm8580_dapm_widgets), + .dapm_routes = smdk_wm8580_audio_map, + .num_dapm_routes = ARRAY_SIZE(smdk_wm8580_audio_map), +}; + +static struct platform_device *smdk_snd_device; + +static int __init smdk_audio_init(void) +{ + int ret; + + smdk_snd_device = platform_device_alloc("soc-audio", -1); + if (!smdk_snd_device) + return -ENOMEM; + + platform_set_drvdata(smdk_snd_device, &smdk); + ret = platform_device_add(smdk_snd_device); + + if (ret) + platform_device_put(smdk_snd_device); + + return ret; +} +module_init(smdk_audio_init); + +static void __exit smdk_audio_exit(void) +{ + platform_device_unregister(smdk_snd_device); +} +module_exit(smdk_audio_exit); + +MODULE_AUTHOR("Jaswinder Singh, jassisinghbrar@gmail.com"); +MODULE_DESCRIPTION("ALSA SoC SMDK WM8580"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c new file mode 100644 index 000000000..ff57b192d --- /dev/null +++ b/sound/soc/samsung/smdk_wm8994.c @@ -0,0 +1,203 @@ +/* + * smdk_wm8994.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include "../codecs/wm8994.h" +#include +#include +#include +#include +#include + + /* + * Default CFG switch settings to use this driver: + * SMDKV310: CFG5-1000, CFG7-111111 + */ + + /* + * Configure audio route as :- + * $ amixer sset 'DAC1' on,on + * $ amixer sset 'Right Headphone Mux' 'DAC' + * $ amixer sset 'Left Headphone Mux' 'DAC' + * $ amixer sset 'DAC1R Mixer AIF1.1' on + * $ amixer sset 'DAC1L Mixer AIF1.1' on + * $ amixer sset 'IN2L' on + * $ amixer sset 'IN2L PGA IN2LN' on + * $ amixer sset 'MIXINL IN2L' on + * $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on + * $ amixer sset 'IN2R' on + * $ amixer sset 'IN2R PGA IN2RN' on + * $ amixer sset 'MIXINR IN2R' on + * $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on + */ + +/* SMDK has a 16.934MHZ crystal attached to WM8994 */ +#define SMDK_WM8994_FREQ 16934000 + +struct smdk_wm8994_data { + int mclk1_rate; +}; + +/* Default SMDKs */ +static struct smdk_wm8994_data smdk_board_data = { + .mclk1_rate = SMDK_WM8994_FREQ, +}; + +static int smdk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int ret; + + /* AIF1CLK should be >=3MHz for optimal performance */ + if (params_width(params) == 24) + pll_out = params_rate(params) * 384; + else if (params_rate(params) == 8000 || params_rate(params) == 11025) + pll_out = params_rate(params) * 512; + else + pll_out = params_rate(params) * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, + SMDK_WM8994_FREQ, pll_out); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SMDK WM8994 DAI operations. + */ +static struct snd_soc_ops smdk_ops = { + .hw_params = smdk_hw_params, +}; + +static int smdk_wm8994_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + + /* Other pins NC */ + snd_soc_dapm_nc_pin(dapm, "HPOUT2P"); + snd_soc_dapm_nc_pin(dapm, "HPOUT2N"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTLN"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTLP"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTRP"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTRN"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT1N"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT1P"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT2N"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT2P"); + snd_soc_dapm_nc_pin(dapm, "IN1LP"); + snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN"); + snd_soc_dapm_nc_pin(dapm, "IN1RP"); + snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP"); + + return 0; +} + +static struct snd_soc_dai_link smdk_dai[] = { + { /* Primary DAI i/f */ + .name = "WM8994 AIF1", + .stream_name = "Pri_Dai", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-i2s.0", + .codec_name = "wm8994-codec", + .init = smdk_wm8994_init_paiftx, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &smdk_ops, + }, { /* Sec_Fifo Playback i/f */ + .name = "Sec_FIFO TX", + .stream_name = "Sec_Dai", + .cpu_dai_name = "samsung-i2s-sec", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-i2s-sec", + .codec_name = "wm8994-codec", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &smdk_ops, + }, +}; + +static struct snd_soc_card smdk = { + .name = "SMDK-I2S", + .owner = THIS_MODULE, + .dai_link = smdk_dai, + .num_links = ARRAY_SIZE(smdk_dai), +}; + +static const struct of_device_id samsung_wm8994_of_match[] = { + { .compatible = "samsung,smdk-wm8994", .data = &smdk_board_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match); + +static int smdk_audio_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &smdk; + struct smdk_wm8994_data *board; + const struct of_device_id *id; + + card->dev = &pdev->dev; + + board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL); + if (!board) + return -ENOMEM; + + if (np) { + smdk_dai[0].cpu_dai_name = NULL; + smdk_dai[0].cpu_of_node = of_parse_phandle(np, + "samsung,i2s-controller", 0); + if (!smdk_dai[0].cpu_of_node) { + dev_err(&pdev->dev, + "Property 'samsung,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + } + + smdk_dai[0].platform_name = NULL; + smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node; + } + + id = of_match_device(of_match_ptr(samsung_wm8994_of_match), &pdev->dev); + if (id) + *board = *((struct smdk_wm8994_data *)id->data); + + platform_set_drvdata(pdev, board); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret); + + return ret; +} + +static struct platform_driver smdk_audio_driver = { + .driver = { + .name = "smdk-audio-wm8994", + .of_match_table = of_match_ptr(samsung_wm8994_of_match), + .pm = &snd_soc_pm_ops, + }, + .probe = smdk_audio_probe, +}; + +module_platform_driver(smdk_audio_driver); + +MODULE_DESCRIPTION("ALSA SoC SMDK WM8994"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:smdk-audio-wm8994"); diff --git a/sound/soc/samsung/smdk_wm8994pcm.c b/sound/soc/samsung/smdk_wm8994pcm.c new file mode 100644 index 000000000..2e621496b --- /dev/null +++ b/sound/soc/samsung/smdk_wm8994pcm.c @@ -0,0 +1,142 @@ +/* + * sound/soc/samsung/smdk_wm8994pcm.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include + +#include "../codecs/wm8994.h" +#include "pcm.h" + +/* + * Board Settings: + * o '1' means 'ON' + * o '0' means 'OFF' + * o 'X' means 'Don't care' + * + * SMDKC210, SMDKV310: CFG3- 1001, CFG5-1000, CFG7-111111 + */ + +/* + * Configure audio route as :- + * $ amixer sset 'DAC1' on,on + * $ amixer sset 'Right Headphone Mux' 'DAC' + * $ amixer sset 'Left Headphone Mux' 'DAC' + * $ amixer sset 'DAC1R Mixer AIF1.1' on + * $ amixer sset 'DAC1L Mixer AIF1.1' on + * $ amixer sset 'IN2L' on + * $ amixer sset 'IN2L PGA IN2LN' on + * $ amixer sset 'MIXINL IN2L' on + * $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on + * $ amixer sset 'IN2R' on + * $ amixer sset 'IN2R PGA IN2RN' on + * $ amixer sset 'MIXINR IN2R' on + * $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on + */ + +/* SMDK has a 16.9344MHZ crystal attached to WM8994 */ +#define SMDK_WM8994_FREQ 16934400 + +static int smdk_wm8994_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned long mclk_freq; + int rfs, ret; + + switch(params_rate(params)) { + case 8000: + rfs = 512; + break; + default: + dev_err(cpu_dai->dev, "%s:%d Sampling Rate %u not supported!\n", + __func__, __LINE__, params_rate(params)); + return -EINVAL; + } + + mclk_freq = params_rate(params) * rfs; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + mclk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, + SMDK_WM8994_FREQ, mclk_freq); + if (ret < 0) + return ret; + + /* Set PCM source clock on CPU */ + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX, + mclk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* Set SCLK_DIV for making bclk */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops smdk_wm8994_pcm_ops = { + .hw_params = smdk_wm8994_pcm_hw_params, +}; + +static struct snd_soc_dai_link smdk_dai[] = { + { + .name = "WM8994 PAIF PCM", + .stream_name = "Primary PCM", + .cpu_dai_name = "samsung-pcm.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-pcm.0", + .codec_name = "wm8994-codec", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &smdk_wm8994_pcm_ops, + }, +}; + +static struct snd_soc_card smdk_pcm = { + .name = "SMDK-PCM", + .owner = THIS_MODULE, + .dai_link = smdk_dai, + .num_links = 1, +}; + +static int snd_smdk_probe(struct platform_device *pdev) +{ + int ret = 0; + + smdk_pcm.dev = &pdev->dev; + ret = devm_snd_soc_register_card(&pdev->dev, &smdk_pcm); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret); + + return ret; +} + +static struct platform_driver snd_smdk_driver = { + .driver = { + .name = "samsung-smdk-pcm", + }, + .probe = snd_smdk_probe, +}; + +module_platform_driver(snd_smdk_driver); + +MODULE_AUTHOR("Sangbeom Kim, "); +MODULE_DESCRIPTION("ALSA SoC SMDK WM8994 for PCM"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c new file mode 100644 index 000000000..5d8efc2d5 --- /dev/null +++ b/sound/soc/samsung/snow.c @@ -0,0 +1,258 @@ +/* + * ASoC machine driver for Snow boards + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "i2s.h" + +#define FIN_PLL_RATE 24000000 + +struct snow_priv { + struct snd_soc_dai_link dai_link; + struct clk *clk_i2s_bus; +}; + +static int snow_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + static const unsigned int pll_rate[] = { + 73728000U, 67737602U, 49152000U, 45158401U, 32768001U + }; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snow_priv *priv = snd_soc_card_get_drvdata(rtd->card); + int bfs, psr, rfs, bitwidth; + unsigned long int rclk; + long int freq = -EINVAL; + int ret, i; + + bitwidth = snd_pcm_format_width(params_format(params)); + if (bitwidth < 0) { + dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth); + return bitwidth; + } + + if (bitwidth != 16 && bitwidth != 24) { + dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth); + return -EINVAL; + } + + bfs = 2 * bitwidth; + + switch (params_rate(params)) { + case 16000: + case 22050: + case 24000: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + rfs = 8 * bfs; + break; + case 64000: + rfs = 384; + break; + case 8000: + case 11025: + case 12000: + rfs = 16 * bfs; + break; + default: + return -EINVAL; + } + + rclk = params_rate(params) * rfs; + + for (psr = 8; psr > 0; psr /= 2) { + for (i = 0; i < ARRAY_SIZE(pll_rate); i++) { + if ((pll_rate[i] - rclk * psr) <= 2) { + freq = pll_rate[i]; + break; + } + } + } + if (freq < 0) { + dev_err(rtd->card->dev, "Unsupported RCLK rate: %lu\n", rclk); + return -EINVAL; + } + + ret = clk_set_rate(priv->clk_i2s_bus, freq); + if (ret < 0) { + dev_err(rtd->card->dev, "I2S bus clock rate set failed\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops snow_card_ops = { + .hw_params = snow_card_hw_params, +}; + +static int snow_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + + /* In the multi-codec case codec_dais 0 is MAX98095 and 1 is HDMI. */ + if (rtd->num_codecs > 1) + codec_dai = rtd->codec_dais[0]; + else + codec_dai = rtd->codec_dai; + + /* Set the MCLK rate for the codec */ + return snd_soc_dai_set_sysclk(codec_dai, 0, + FIN_PLL_RATE, SND_SOC_CLOCK_IN); +} + +static struct snd_soc_card snow_snd = { + .name = "Snow-I2S", + .owner = THIS_MODULE, + .late_probe = snow_late_probe, +}; + +static int snow_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card = &snow_snd; + struct device_node *cpu, *codec; + struct snd_soc_dai_link *link; + struct snow_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + link = &priv->dai_link; + + link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + link->name = "Primary"; + link->stream_name = link->name; + + card->dai_link = link; + card->num_links = 1; + card->dev = dev; + + /* Try new DT bindings with HDMI support first. */ + cpu = of_get_child_by_name(dev->of_node, "cpu"); + + if (cpu) { + link->ops = &snow_card_ops; + + link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + of_node_put(cpu); + + if (!link->cpu_of_node) { + dev_err(dev, "Failed parsing cpu/sound-dai property\n"); + return -EINVAL; + } + + codec = of_get_child_by_name(dev->of_node, "codec"); + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + of_node_put(codec); + + if (ret < 0) { + of_node_put(link->cpu_of_node); + dev_err(dev, "Failed parsing codec node\n"); + return ret; + } + + priv->clk_i2s_bus = of_clk_get_by_name(link->cpu_of_node, + "i2s_opclk0"); + if (IS_ERR(priv->clk_i2s_bus)) { + snd_soc_of_put_dai_link_codecs(link); + of_node_put(link->cpu_of_node); + return PTR_ERR(priv->clk_i2s_bus); + } + } else { + link->codec_dai_name = "HiFi", + + link->cpu_of_node = of_parse_phandle(dev->of_node, + "samsung,i2s-controller", 0); + if (!link->cpu_of_node) { + dev_err(dev, "i2s-controller property parse error\n"); + return -EINVAL; + } + + link->codec_of_node = of_parse_phandle(dev->of_node, + "samsung,audio-codec", 0); + if (!link->codec_of_node) { + of_node_put(link->cpu_of_node); + dev_err(dev, "audio-codec property parse error\n"); + return -EINVAL; + } + } + + link->platform_of_node = link->cpu_of_node; + + /* Update card-name if provided through DT, else use default name */ + snd_soc_of_parse_card_name(card, "samsung,model"); + + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + return ret; + } + + return ret; +} + +static int snow_remove(struct platform_device *pdev) +{ + struct snow_priv *priv = platform_get_drvdata(pdev); + struct snd_soc_dai_link *link = &priv->dai_link; + + of_node_put(link->cpu_of_node); + of_node_put(link->codec_of_node); + snd_soc_of_put_dai_link_codecs(link); + + clk_put(priv->clk_i2s_bus); + + return 0; +} + +static const struct of_device_id snow_of_match[] = { + { .compatible = "google,snow-audio-max98090", }, + { .compatible = "google,snow-audio-max98091", }, + { .compatible = "google,snow-audio-max98095", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snow_of_match); + +static struct platform_driver snow_driver = { + .driver = { + .name = "snow-audio", + .pm = &snd_soc_pm_ops, + .of_match_table = snow_of_match, + }, + .probe = snow_probe, + .remove = snow_remove, +}; + +module_platform_driver(snow_driver); + +MODULE_DESCRIPTION("ALSA SoC Audio machine driver for Snow"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c new file mode 100644 index 000000000..cb59911e6 --- /dev/null +++ b/sound/soc/samsung/spdif.c @@ -0,0 +1,491 @@ +/* sound/soc/samsung/spdif.c + * + * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * http://www.samsung.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#include + +#include + +#include "dma.h" +#include "spdif.h" + +/* Registers */ +#define CLKCON 0x00 +#define CON 0x04 +#define BSTAS 0x08 +#define CSTAS 0x0C +#define DATA_OUTBUF 0x10 +#define DCNT 0x14 +#define BSTAS_S 0x18 +#define DCNT_S 0x1C + +#define CLKCTL_MASK 0x7 +#define CLKCTL_MCLK_EXT (0x1 << 2) +#define CLKCTL_PWR_ON (0x1 << 0) + +#define CON_MASK 0x3ffffff +#define CON_FIFO_TH_SHIFT 19 +#define CON_FIFO_TH_MASK (0x7 << 19) +#define CON_USERDATA_23RDBIT (0x1 << 12) + +#define CON_SW_RESET (0x1 << 5) + +#define CON_MCLKDIV_MASK (0x3 << 3) +#define CON_MCLKDIV_256FS (0x0 << 3) +#define CON_MCLKDIV_384FS (0x1 << 3) +#define CON_MCLKDIV_512FS (0x2 << 3) + +#define CON_PCM_MASK (0x3 << 1) +#define CON_PCM_16BIT (0x0 << 1) +#define CON_PCM_20BIT (0x1 << 1) +#define CON_PCM_24BIT (0x2 << 1) + +#define CON_PCM_DATA (0x1 << 0) + +#define CSTAS_MASK 0x3fffffff +#define CSTAS_SAMP_FREQ_MASK (0xF << 24) +#define CSTAS_SAMP_FREQ_44 (0x0 << 24) +#define CSTAS_SAMP_FREQ_48 (0x2 << 24) +#define CSTAS_SAMP_FREQ_32 (0x3 << 24) +#define CSTAS_SAMP_FREQ_96 (0xA << 24) + +#define CSTAS_CATEGORY_MASK (0xFF << 8) +#define CSTAS_CATEGORY_CODE_CDP (0x01 << 8) + +#define CSTAS_NO_COPYRIGHT (0x1 << 2) + +/** + * struct samsung_spdif_info - Samsung S/PDIF Controller information + * @lock: Spin lock for S/PDIF. + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device register block. + * @clk_rate: Current clock rate for calcurate ratio. + * @pclk: The peri-clock pointer for spdif master operation. + * @sclk: The source clock pointer for making sync signals. + * @save_clkcon: Backup clkcon reg. in suspend. + * @save_con: Backup con reg. in suspend. + * @save_cstas: Backup cstas reg. in suspend. + * @dma_playback: DMA information for playback channel. + */ +struct samsung_spdif_info { + spinlock_t lock; + struct device *dev; + void __iomem *regs; + unsigned long clk_rate; + struct clk *pclk; + struct clk *sclk; + u32 saved_clkcon; + u32 saved_con; + u32 saved_cstas; + struct snd_dmaengine_dai_dma_data *dma_playback; +}; + +static struct snd_dmaengine_dai_dma_data spdif_stereo_out; +static struct samsung_spdif_info spdif_info; + +static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return snd_soc_dai_get_drvdata(cpu_dai); +} + +static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on) +{ + void __iomem *regs = spdif->regs; + u32 clkcon; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + clkcon = readl(regs + CLKCON) & CLKCTL_MASK; + if (on) + writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON); + else + writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON); +} + +static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct samsung_spdif_info *spdif = to_info(cpu_dai); + u32 clkcon; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + clkcon = readl(spdif->regs + CLKCON); + + if (clk_id == SND_SOC_SPDIF_INT_MCLK) + clkcon &= ~CLKCTL_MCLK_EXT; + else + clkcon |= CLKCTL_MCLK_EXT; + + writel(clkcon, spdif->regs + CLKCON); + + spdif->clk_rate = freq; + + return 0; +} + +static int spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai); + unsigned long flags; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&spdif->lock, flags); + spdif_snd_txctrl(spdif, 1); + spin_unlock_irqrestore(&spdif->lock, flags); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&spdif->lock, flags); + spdif_snd_txctrl(spdif, 0); + spin_unlock_irqrestore(&spdif->lock, flags); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int spdif_sysclk_ratios[] = { + 512, 384, 256, +}; + +static int spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai); + void __iomem *regs = spdif->regs; + struct snd_dmaengine_dai_dma_data *dma_data; + u32 con, clkcon, cstas; + unsigned long flags; + int i, ratio; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = spdif->dma_playback; + else { + dev_err(spdif->dev, "Capture is not supported\n"); + return -EINVAL; + } + + snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data); + + spin_lock_irqsave(&spdif->lock, flags); + + con = readl(regs + CON) & CON_MASK; + cstas = readl(regs + CSTAS) & CSTAS_MASK; + clkcon = readl(regs + CLKCON) & CLKCTL_MASK; + + con &= ~CON_FIFO_TH_MASK; + con |= (0x7 << CON_FIFO_TH_SHIFT); + con |= CON_USERDATA_23RDBIT; + con |= CON_PCM_DATA; + + con &= ~CON_PCM_MASK; + switch (params_width(params)) { + case 16: + con |= CON_PCM_16BIT; + break; + default: + dev_err(spdif->dev, "Unsupported data size.\n"); + goto err; + } + + ratio = spdif->clk_rate / params_rate(params); + for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++) + if (ratio == spdif_sysclk_ratios[i]) + break; + if (i == ARRAY_SIZE(spdif_sysclk_ratios)) { + dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n", + spdif->clk_rate, params_rate(params)); + goto err; + } + + con &= ~CON_MCLKDIV_MASK; + switch (ratio) { + case 256: + con |= CON_MCLKDIV_256FS; + break; + case 384: + con |= CON_MCLKDIV_384FS; + break; + case 512: + con |= CON_MCLKDIV_512FS; + break; + } + + cstas &= ~CSTAS_SAMP_FREQ_MASK; + switch (params_rate(params)) { + case 44100: + cstas |= CSTAS_SAMP_FREQ_44; + break; + case 48000: + cstas |= CSTAS_SAMP_FREQ_48; + break; + case 32000: + cstas |= CSTAS_SAMP_FREQ_32; + break; + case 96000: + cstas |= CSTAS_SAMP_FREQ_96; + break; + default: + dev_err(spdif->dev, "Invalid sampling rate %d\n", + params_rate(params)); + goto err; + } + + cstas &= ~CSTAS_CATEGORY_MASK; + cstas |= CSTAS_CATEGORY_CODE_CDP; + cstas |= CSTAS_NO_COPYRIGHT; + + writel(con, regs + CON); + writel(cstas, regs + CSTAS); + writel(clkcon, regs + CLKCON); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +err: + spin_unlock_irqrestore(&spdif->lock, flags); + return -EINVAL; +} + +static void spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai); + void __iomem *regs = spdif->regs; + u32 con, clkcon; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + con = readl(regs + CON) & CON_MASK; + clkcon = readl(regs + CLKCON) & CLKCTL_MASK; + + writel(con | CON_SW_RESET, regs + CON); + cpu_relax(); + + writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON); +} + +#ifdef CONFIG_PM +static int spdif_suspend(struct snd_soc_dai *cpu_dai) +{ + struct samsung_spdif_info *spdif = to_info(cpu_dai); + u32 con = spdif->saved_con; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + spdif->saved_clkcon = readl(spdif->regs + CLKCON) & CLKCTL_MASK; + spdif->saved_con = readl(spdif->regs + CON) & CON_MASK; + spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK; + + writel(con | CON_SW_RESET, spdif->regs + CON); + cpu_relax(); + + return 0; +} + +static int spdif_resume(struct snd_soc_dai *cpu_dai) +{ + struct samsung_spdif_info *spdif = to_info(cpu_dai); + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + writel(spdif->saved_clkcon, spdif->regs + CLKCON); + writel(spdif->saved_con, spdif->regs + CON); + writel(spdif->saved_cstas, spdif->regs + CSTAS); + + return 0; +} +#else +#define spdif_suspend NULL +#define spdif_resume NULL +#endif + +static const struct snd_soc_dai_ops spdif_dai_ops = { + .set_sysclk = spdif_set_sysclk, + .trigger = spdif_trigger, + .hw_params = spdif_hw_params, + .shutdown = spdif_shutdown, +}; + +static struct snd_soc_dai_driver samsung_spdif_dai = { + .name = "samsung-spdif", + .playback = { + .stream_name = "S/PDIF Playback", + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .ops = &spdif_dai_ops, + .suspend = spdif_suspend, + .resume = spdif_resume, +}; + +static const struct snd_soc_component_driver samsung_spdif_component = { + .name = "samsung-spdif", +}; + +static int spdif_probe(struct platform_device *pdev) +{ + struct s3c_audio_pdata *spdif_pdata; + struct resource *mem_res; + struct samsung_spdif_info *spdif; + dma_filter_fn filter; + int ret; + + spdif_pdata = pdev->dev.platform_data; + + dev_dbg(&pdev->dev, "Entered %s\n", __func__); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + dev_err(&pdev->dev, "Unable to get register resource.\n"); + return -ENXIO; + } + + if (spdif_pdata && spdif_pdata->cfg_gpio + && spdif_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure GPIO pins\n"); + return -EINVAL; + } + + spdif = &spdif_info; + spdif->dev = &pdev->dev; + + spin_lock_init(&spdif->lock); + + spdif->pclk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif->pclk)) { + dev_err(&pdev->dev, "failed to get peri-clock\n"); + ret = -ENOENT; + goto err0; + } + ret = clk_prepare_enable(spdif->pclk); + if (ret) + goto err0; + + spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif"); + if (IS_ERR(spdif->sclk)) { + dev_err(&pdev->dev, "failed to get internal source clock\n"); + ret = -ENOENT; + goto err1; + } + ret = clk_prepare_enable(spdif->sclk); + if (ret) + goto err1; + + /* Request S/PDIF Register's memory region */ + if (!request_mem_region(mem_res->start, + resource_size(mem_res), "samsung-spdif")) { + dev_err(&pdev->dev, "Unable to request register region\n"); + ret = -EBUSY; + goto err2; + } + + spdif->regs = ioremap(mem_res->start, 0x100); + if (spdif->regs == NULL) { + dev_err(&pdev->dev, "Cannot ioremap registers\n"); + ret = -ENXIO; + goto err3; + } + + spdif_stereo_out.addr_width = 2; + spdif_stereo_out.addr = mem_res->start + DATA_OUTBUF; + filter = NULL; + if (spdif_pdata) { + spdif_stereo_out.filter_data = spdif_pdata->dma_playback; + filter = spdif_pdata->dma_filter; + } + spdif->dma_playback = &spdif_stereo_out; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to register DMA: %d\n", ret); + goto err4; + } + + dev_set_drvdata(&pdev->dev, spdif); + + ret = devm_snd_soc_register_component(&pdev->dev, + &samsung_spdif_component, &samsung_spdif_dai, 1); + if (ret != 0) { + dev_err(&pdev->dev, "fail to register dai\n"); + goto err4; + } + + return 0; +err4: + iounmap(spdif->regs); +err3: + release_mem_region(mem_res->start, resource_size(mem_res)); +err2: + clk_disable_unprepare(spdif->sclk); +err1: + clk_disable_unprepare(spdif->pclk); +err0: + return ret; +} + +static int spdif_remove(struct platform_device *pdev) +{ + struct samsung_spdif_info *spdif = &spdif_info; + struct resource *mem_res; + + iounmap(spdif->regs); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem_res) + release_mem_region(mem_res->start, resource_size(mem_res)); + + clk_disable_unprepare(spdif->sclk); + clk_disable_unprepare(spdif->pclk); + + return 0; +} + +static struct platform_driver samsung_spdif_driver = { + .probe = spdif_probe, + .remove = spdif_remove, + .driver = { + .name = "samsung-spdif", + }, +}; + +module_platform_driver(samsung_spdif_driver); + +MODULE_AUTHOR("Seungwhan Youn, "); +MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-spdif"); diff --git a/sound/soc/samsung/spdif.h b/sound/soc/samsung/spdif.h new file mode 100644 index 000000000..4f72cb446 --- /dev/null +++ b/sound/soc/samsung/spdif.h @@ -0,0 +1,19 @@ +/* sound/soc/samsung/spdif.h + * + * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * http://www.samsung.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_SAMSUNG_SPDIF_H +#define __SND_SOC_SAMSUNG_SPDIF_H __FILE__ + +#define SND_SOC_SPDIF_INT_MCLK 0 +#define SND_SOC_SPDIF_EXT_MCLK 1 + +#endif /* __SND_SOC_SAMSUNG_SPDIF_H */ diff --git a/sound/soc/samsung/speyside.c b/sound/soc/samsung/speyside.c new file mode 100644 index 000000000..4b4147d07 --- /dev/null +++ b/sound/soc/samsung/speyside.c @@ -0,0 +1,353 @@ +/* + * Speyside audio support + * + * Copyright 2011 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "../codecs/wm8996.h" +#include "../codecs/wm9081.h" + +#define WM8996_HPSEL_GPIO 214 +#define MCLK_AUDIO_RATE (512 * 48000) + +static int speyside_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name); + codec_dai = rtd->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + ret = snd_soc_dai_set_sysclk(codec_dai, WM8996_SYSCLK_MCLK2, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8996_FLL_MCLK2, + 0, 0, 0); + if (ret < 0) { + pr_err("Failed to stop FLL\n"); + return ret; + } + break; + + default: + break; + } + + return 0; +} + +static int speyside_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[1].name); + codec_dai = rtd->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) { + ret = snd_soc_dai_set_pll(codec_dai, 0, + WM8996_FLL_MCLK2, + 32768, MCLK_AUDIO_RATE); + if (ret < 0) { + pr_err("Failed to start FLL\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8996_SYSCLK_FLL, + MCLK_AUDIO_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + } + break; + + default: + break; + } + + card->dapm.bias_level = level; + + return 0; +} + +static struct snd_soc_jack speyside_headset; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin speyside_headset_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +/* Default the headphone selection to active high */ +static int speyside_jack_polarity; + +static int speyside_get_micbias(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + if (speyside_jack_polarity && (strcmp(source->name, "MICB1") == 0)) + return 1; + if (!speyside_jack_polarity && (strcmp(source->name, "MICB2") == 0)) + return 1; + + return 0; +} + +static void speyside_set_polarity(struct snd_soc_component *component, + int polarity) +{ + speyside_jack_polarity = !polarity; + gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity); + + /* Re-run DAPM to make sure we're using the correct mic bias */ + snd_soc_dapm_sync(snd_soc_component_get_dapm(component)); +} + +static int speyside_wm0010_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(dai, 0, MCLK_AUDIO_RATE, 0); + if (ret < 0) + return ret; + + return 0; +} + +static int speyside_wm8996_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->codec_dai; + struct snd_soc_component *component = dai->component; + int ret; + + ret = snd_soc_dai_set_sysclk(dai, WM8996_SYSCLK_MCLK2, 32768, 0); + if (ret < 0) + return ret; + + ret = gpio_request(WM8996_HPSEL_GPIO, "HP_SEL"); + if (ret != 0) + pr_err("Failed to request HP_SEL GPIO: %d\n", ret); + gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity); + + ret = snd_soc_card_jack_new(rtd->card, "Headset", SND_JACK_LINEOUT | + SND_JACK_HEADSET | SND_JACK_BTN_0, + &speyside_headset, speyside_headset_pins, + ARRAY_SIZE(speyside_headset_pins)); + if (ret) + return ret; + + wm8996_detect(component, &speyside_headset, speyside_set_polarity); + + return 0; +} + +static int speyside_late_probe(struct snd_soc_card *card) +{ + snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Main AMIC"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Main DMIC"); + snd_soc_dapm_ignore_suspend(&card->dapm, "Main Speaker"); + snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Output"); + snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Input"); + + return 0; +} + +static const struct snd_soc_pcm_stream dsp_codec_params = { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, +}; + +static struct snd_soc_dai_link speyside_dai[] = { + { + .name = "CPU-DSP", + .stream_name = "CPU-DSP", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm0010-sdi1", + .platform_name = "samsung-i2s.0", + .codec_name = "spi0.0", + .init = speyside_wm0010_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + }, + { + .name = "DSP-CODEC", + .stream_name = "DSP-CODEC", + .cpu_dai_name = "wm0010-sdi2", + .codec_dai_name = "wm8996-aif1", + .codec_name = "wm8996.1-001a", + .init = speyside_wm8996_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .params = &dsp_codec_params, + .ignore_suspend = 1, + }, + { + .name = "Baseband", + .stream_name = "Baseband", + .cpu_dai_name = "wm8996-aif2", + .codec_dai_name = "wm1250-ev1", + .codec_name = "wm1250-ev1.1-0027", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + }, +}; + +static int speyside_wm9081_init(struct snd_soc_component *component) +{ + /* At any time the WM9081 is active it will have this clock */ + return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0, + MCLK_AUDIO_RATE, 0); +} + +static struct snd_soc_aux_dev speyside_aux_dev[] = { + { + .name = "wm9081", + .codec_name = "wm9081.1-006c", + .init = speyside_wm9081_init, + }, +}; + +static struct snd_soc_codec_conf speyside_codec_conf[] = { + { + .dev_name = "wm9081.1-006c", + .name_prefix = "Sub", + }, +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Main Speaker"), + SOC_DAPM_PIN_SWITCH("Main DMIC"), + SOC_DAPM_PIN_SWITCH("Main AMIC"), + SOC_DAPM_PIN_SWITCH("WM1250 Input"), + SOC_DAPM_PIN_SWITCH("WM1250 Output"), + SOC_DAPM_PIN_SWITCH("Headphone"), +}; + +static struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + + SND_SOC_DAPM_SPK("Main Speaker", NULL), + + SND_SOC_DAPM_MIC("Main AMIC", NULL), + SND_SOC_DAPM_MIC("Main DMIC", NULL), +}; + +static struct snd_soc_dapm_route audio_paths[] = { + { "IN1RN", NULL, "MICB1" }, + { "IN1RP", NULL, "MICB1" }, + { "IN1RN", NULL, "MICB2" }, + { "IN1RP", NULL, "MICB2" }, + { "MICB1", NULL, "Headset Mic", speyside_get_micbias }, + { "MICB2", NULL, "Headset Mic", speyside_get_micbias }, + + { "IN1LP", NULL, "MICB2" }, + { "IN1RN", NULL, "MICB1" }, + { "MICB2", NULL, "Main AMIC" }, + + { "DMIC1DAT", NULL, "MICB1" }, + { "DMIC2DAT", NULL, "MICB1" }, + { "MICB1", NULL, "Main DMIC" }, + + { "Headphone", NULL, "HPOUT1L" }, + { "Headphone", NULL, "HPOUT1R" }, + + { "Sub IN1", NULL, "HPOUT2L" }, + { "Sub IN2", NULL, "HPOUT2R" }, + + { "Main Speaker", NULL, "Sub SPKN" }, + { "Main Speaker", NULL, "Sub SPKP" }, + { "Main Speaker", NULL, "SPKDAT" }, +}; + +static struct snd_soc_card speyside = { + .name = "Speyside", + .owner = THIS_MODULE, + .dai_link = speyside_dai, + .num_links = ARRAY_SIZE(speyside_dai), + .aux_dev = speyside_aux_dev, + .num_aux_devs = ARRAY_SIZE(speyside_aux_dev), + .codec_conf = speyside_codec_conf, + .num_configs = ARRAY_SIZE(speyside_codec_conf), + + .set_bias_level = speyside_set_bias_level, + .set_bias_level_post = speyside_set_bias_level_post, + + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = audio_paths, + .num_dapm_routes = ARRAY_SIZE(audio_paths), + .fully_routed = true, + + .late_probe = speyside_late_probe, +}; + +static int speyside_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &speyside; + int ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static struct platform_driver speyside_driver = { + .driver = { + .name = "speyside", + .pm = &snd_soc_pm_ops, + }, + .probe = speyside_probe, +}; + +module_platform_driver(speyside_driver); + +MODULE_DESCRIPTION("Speyside audio support"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:speyside"); diff --git a/sound/soc/samsung/tm2_wm5110.c b/sound/soc/samsung/tm2_wm5110.c new file mode 100644 index 000000000..e5890485f --- /dev/null +++ b/sound/soc/samsung/tm2_wm5110.c @@ -0,0 +1,660 @@ +/* + * Copyright (C) 2015 - 2016 Samsung Electronics Co., Ltd. + * + * Authors: Inha Song + * Sylwester Nawrocki + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "i2s.h" +#include "../codecs/wm5110.h" + +/* + * The source clock is XCLKOUT with its mux set to the external fixed rate + * oscillator (XXTI). + */ +#define MCLK_RATE 24000000U + +#define TM2_DAI_AIF1 0 +#define TM2_DAI_AIF2 1 + +struct tm2_machine_priv { + struct snd_soc_component *component; + unsigned int sysclk_rate; + struct gpio_desc *gpio_mic_bias; +}; + +static int tm2_start_sysclk(struct snd_soc_card *card) +{ + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = priv->component; + int ret; + + ret = snd_soc_component_set_pll(component, WM5110_FLL1_REFCLK, + ARIZONA_FLL_SRC_MCLK1, + MCLK_RATE, + priv->sysclk_rate); + if (ret < 0) { + dev_err(component->dev, "Failed to set FLL1 source: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_pll(component, WM5110_FLL1, + ARIZONA_FLL_SRC_MCLK1, + MCLK_RATE, + priv->sysclk_rate); + if (ret < 0) { + dev_err(component->dev, "Failed to start FLL1: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, + priv->sysclk_rate, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(component->dev, "Failed to set SYSCLK source: %d\n", ret); + return ret; + } + + return 0; +} + +static int tm2_stop_sysclk(struct snd_soc_card *card) +{ + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = priv->component; + int ret; + + ret = snd_soc_component_set_pll(component, WM5110_FLL1, 0, 0, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to stop FLL1: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK, + ARIZONA_CLK_SRC_FLL1, 0, 0); + if (ret < 0) { + dev_err(component->dev, "Failed to stop SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +static int tm2_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = rtd->codec_dai->component; + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(rtd->card); + + switch (params_rate(params)) { + case 4000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 48000: + case 96000: + case 192000: + /* Highest possible SYSCLK frequency: 147.456MHz */ + priv->sysclk_rate = 147456000U; + break; + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + /* Highest possible SYSCLK frequency: 135.4752 MHz */ + priv->sysclk_rate = 135475200U; + break; + default: + dev_err(component->dev, "Not supported sample rate: %d\n", + params_rate(params)); + return -EINVAL; + } + + return tm2_start_sysclk(rtd->card); +} + +static struct snd_soc_ops tm2_aif1_ops = { + .hw_params = tm2_aif1_hw_params, +}; + +static int tm2_aif2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = rtd->codec_dai->component; + unsigned int asyncclk_rate; + int ret; + + switch (params_rate(params)) { + case 8000: + case 12000: + case 16000: + /* Highest possible ASYNCCLK frequency: 49.152MHz */ + asyncclk_rate = 49152000U; + break; + case 11025: + /* Highest possible ASYNCCLK frequency: 45.1584 MHz */ + asyncclk_rate = 45158400U; + break; + default: + dev_err(component->dev, "Not supported sample rate: %d\n", + params_rate(params)); + return -EINVAL; + } + + ret = snd_soc_component_set_pll(component, WM5110_FLL2_REFCLK, + ARIZONA_FLL_SRC_MCLK1, + MCLK_RATE, + asyncclk_rate); + if (ret < 0) { + dev_err(component->dev, "Failed to set FLL2 source: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_pll(component, WM5110_FLL2, + ARIZONA_FLL_SRC_MCLK1, + MCLK_RATE, + asyncclk_rate); + if (ret < 0) { + dev_err(component->dev, "Failed to start FLL2: %d\n", ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_ASYNCCLK, + ARIZONA_CLK_SRC_FLL2, + asyncclk_rate, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(component->dev, "Failed to set ASYNCCLK source: %d\n", ret); + return ret; + } + + return 0; +} + +static int tm2_aif2_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = rtd->codec_dai->component; + int ret; + + /* disable FLL2 */ + ret = snd_soc_component_set_pll(component, WM5110_FLL2, ARIZONA_FLL_SRC_MCLK1, + 0, 0); + if (ret < 0) + dev_err(component->dev, "Failed to stop FLL2: %d\n", ret); + + return ret; +} + +static struct snd_soc_ops tm2_aif2_ops = { + .hw_params = tm2_aif2_hw_params, + .hw_free = tm2_aif2_hw_free, +}; + +static int tm2_hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int bfs; + int bitwidth, ret; + + bitwidth = snd_pcm_format_width(params_format(params)); + if (bitwidth < 0) { + dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth); + return bitwidth; + } + + switch (bitwidth) { + case 48: + bfs = 64; + break; + case 16: + bfs = 32; + break; + default: + dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth); + return -EINVAL; + } + + switch (params_rate(params)) { + case 48000: + case 96000: + case 192000: + break; + default: + dev_err(rtd->card->dev, "Unsupported sample rate: %d\n", + params_rate(params)); + return -EINVAL; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, + 0, SAMSUNG_I2S_OPCLK_PCLK); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops tm2_hdmi_ops = { + .hw_params = tm2_hdmi_hw_params, +}; + +static int tm2_mic_bias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_card *card = w->dapm->card; + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + gpiod_set_value_cansleep(priv->gpio_mic_bias, 1); + break; + case SND_SOC_DAPM_POST_PMD: + gpiod_set_value_cansleep(priv->gpio_mic_bias, 0); + break; + } + + return 0; +} + +static int tm2_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + + if (dapm->dev != rtd->codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + if (card->dapm.bias_level == SND_SOC_BIAS_OFF) + tm2_start_sysclk(card); + break; + case SND_SOC_BIAS_OFF: + tm2_stop_sysclk(card); + break; + default: + break; + } + + return 0; +} + +static struct snd_soc_aux_dev tm2_speaker_amp_dev; + +static int tm2_late_probe(struct snd_soc_card *card) +{ + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link_component dlc = { 0 }; + unsigned int ch_map[] = { 0, 1 }; + struct snd_soc_dai *amp_pdm_dai; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *aif1_dai; + struct snd_soc_dai *aif2_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[TM2_DAI_AIF1].name); + aif1_dai = rtd->codec_dai; + priv->component = rtd->codec_dai->component; + + ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0); + if (ret < 0) { + dev_err(aif1_dai->dev, "Failed to set SYSCLK: %d\n", ret); + return ret; + } + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[TM2_DAI_AIF2].name); + aif2_dai = rtd->codec_dai; + + ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0); + if (ret < 0) { + dev_err(aif2_dai->dev, "Failed to set ASYNCCLK: %d\n", ret); + return ret; + } + + dlc.of_node = tm2_speaker_amp_dev.codec_of_node; + amp_pdm_dai = snd_soc_find_dai(&dlc); + if (!amp_pdm_dai) + return -ENODEV; + + /* Set the MAX98504 V/I sense PDM Tx DAI channel mapping */ + ret = snd_soc_dai_set_channel_map(amp_pdm_dai, ARRAY_SIZE(ch_map), + ch_map, 0, NULL); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_tdm_slot(amp_pdm_dai, 0x3, 0x0, 2, 16); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_kcontrol_new tm2_controls[] = { + SOC_DAPM_PIN_SWITCH("HP"), + SOC_DAPM_PIN_SWITCH("SPK"), + SOC_DAPM_PIN_SWITCH("RCV"), + SOC_DAPM_PIN_SWITCH("VPS"), + SOC_DAPM_PIN_SWITCH("HDMI"), + + SOC_DAPM_PIN_SWITCH("Main Mic"), + SOC_DAPM_PIN_SWITCH("Sub Mic"), + SOC_DAPM_PIN_SWITCH("Third Mic"), + + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget tm2_dapm_widgets[] = { + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_SPK("SPK", NULL), + SND_SOC_DAPM_SPK("RCV", NULL), + SND_SOC_DAPM_LINE("VPS", NULL), + SND_SOC_DAPM_LINE("HDMI", NULL), + + SND_SOC_DAPM_MIC("Main Mic", tm2_mic_bias), + SND_SOC_DAPM_MIC("Sub Mic", NULL), + SND_SOC_DAPM_MIC("Third Mic", NULL), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_component_driver tm2_component = { + .name = "tm2-audio", +}; + +static struct snd_soc_dai_driver tm2_ext_dai[] = { + { + .name = "Voice call", + .playback = { + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "Bluetooth", + .playback = { + .channels_min = 1, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 16000, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static struct snd_soc_dai_link tm2_dai_links[] = { + { + .name = "WM5110 AIF1", + .stream_name = "HiFi Primary", + .cpu_dai_name = SAMSUNG_I2S_DAI, + .codec_dai_name = "wm5110-aif1", + .ops = &tm2_aif1_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + }, { + .name = "WM5110 Voice", + .stream_name = "Voice call", + .cpu_dai_name = SAMSUNG_I2S_DAI, + .codec_dai_name = "wm5110-aif2", + .ops = &tm2_aif2_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + }, { + .name = "WM5110 BT", + .stream_name = "Bluetooth", + .cpu_dai_name = SAMSUNG_I2S_DAI, + .codec_dai_name = "wm5110-aif3", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + }, { + .name = "HDMI", + .stream_name = "i2s1", + .ops = &tm2_hdmi_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + } +}; + +static struct snd_soc_card tm2_card = { + .owner = THIS_MODULE, + + .dai_link = tm2_dai_links, + .controls = tm2_controls, + .num_controls = ARRAY_SIZE(tm2_controls), + .dapm_widgets = tm2_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tm2_dapm_widgets), + .aux_dev = &tm2_speaker_amp_dev, + .num_aux_devs = 1, + + .late_probe = tm2_late_probe, + .set_bias_level = tm2_set_bias_level, +}; + +static int tm2_probe(struct platform_device *pdev) +{ + struct device_node *cpu_dai_node[2] = {}; + struct device_node *codec_dai_node[2] = {}; + const char *cells_name = NULL; + struct device *dev = &pdev->dev; + struct snd_soc_card *card = &tm2_card; + struct tm2_machine_priv *priv; + struct of_phandle_args args; + int num_codecs, ret, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, priv); + card->dev = dev; + + priv->gpio_mic_bias = devm_gpiod_get(dev, "mic-bias", GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpio_mic_bias)) { + dev_err(dev, "Failed to get mic bias gpio\n"); + return PTR_ERR(priv->gpio_mic_bias); + } + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret < 0) { + dev_err(dev, "Card name is not specified\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing"); + if (ret < 0) { + dev_err(dev, "Audio routing is not specified or invalid\n"); + return ret; + } + + card->aux_dev[0].codec_of_node = of_parse_phandle(dev->of_node, + "audio-amplifier", 0); + if (!card->aux_dev[0].codec_of_node) { + dev_err(dev, "audio-amplifier property invalid or missing\n"); + return -EINVAL; + } + + num_codecs = of_count_phandle_with_args(dev->of_node, "audio-codec", + NULL); + + /* Skip the HDMI link if not specified in DT */ + if (num_codecs > 1) { + card->num_links = ARRAY_SIZE(tm2_dai_links); + cells_name = "#sound-dai-cells"; + } else { + card->num_links = ARRAY_SIZE(tm2_dai_links) - 1; + } + + for (i = 0; i < num_codecs; i++) { + struct of_phandle_args args; + + ret = of_parse_phandle_with_args(dev->of_node, "i2s-controller", + cells_name, i, &args); + if (ret) { + dev_err(dev, "i2s-controller property parse error: %d\n", i); + ret = -EINVAL; + goto dai_node_put; + } + cpu_dai_node[i] = args.np; + + codec_dai_node[i] = of_parse_phandle(dev->of_node, + "audio-codec", i); + if (!codec_dai_node[i]) { + dev_err(dev, "audio-codec property parse error\n"); + ret = -EINVAL; + goto dai_node_put; + } + } + + /* Initialize WM5110 - I2S and HDMI - I2S1 DAI links */ + for (i = 0; i < card->num_links; i++) { + unsigned int dai_index = 0; /* WM5110 */ + + card->dai_link[i].cpu_name = NULL; + card->dai_link[i].platform_name = NULL; + + if (num_codecs > 1 && i == card->num_links - 1) + dai_index = 1; /* HDMI */ + + card->dai_link[i].codec_of_node = codec_dai_node[dai_index]; + card->dai_link[i].cpu_of_node = cpu_dai_node[dai_index]; + card->dai_link[i].platform_of_node = cpu_dai_node[dai_index]; + } + + if (num_codecs > 1) { + /* HDMI DAI link (I2S1) */ + i = card->num_links - 1; + + ret = of_parse_phandle_with_fixed_args(dev->of_node, + "audio-codec", 0, 1, &args); + if (ret) { + dev_err(dev, "audio-codec property parse error\n"); + goto dai_node_put; + } + + ret = snd_soc_get_dai_name(&args, &card->dai_link[i].codec_dai_name); + if (ret) { + dev_err(dev, "Unable to get codec_dai_name\n"); + goto dai_node_put; + } + } + + ret = devm_snd_soc_register_component(dev, &tm2_component, + tm2_ext_dai, ARRAY_SIZE(tm2_ext_dai)); + if (ret < 0) { + dev_err(dev, "Failed to register component: %d\n", ret); + goto dai_node_put; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) { + dev_err(dev, "Failed to register card: %d\n", ret); + goto dai_node_put; + } + +dai_node_put: + for (i = 0; i < num_codecs; i++) { + of_node_put(codec_dai_node[i]); + of_node_put(cpu_dai_node[i]); + } + + of_node_put(card->aux_dev[0].codec_of_node); + + return ret; +} + +static int tm2_pm_prepare(struct device *dev) +{ + struct snd_soc_card *card = dev_get_drvdata(dev); + + return tm2_stop_sysclk(card); +} + +static void tm2_pm_complete(struct device *dev) +{ + struct snd_soc_card *card = dev_get_drvdata(dev); + + tm2_start_sysclk(card); +} + +static const struct dev_pm_ops tm2_pm_ops = { + .prepare = tm2_pm_prepare, + .suspend = snd_soc_suspend, + .resume = snd_soc_resume, + .complete = tm2_pm_complete, + .freeze = snd_soc_suspend, + .thaw = snd_soc_resume, + .poweroff = snd_soc_poweroff, + .restore = snd_soc_resume, +}; + +static const struct of_device_id tm2_of_match[] = { + { .compatible = "samsung,tm2-audio" }, + { }, +}; +MODULE_DEVICE_TABLE(of, tm2_of_match); + +static struct platform_driver tm2_driver = { + .driver = { + .name = "tm2-audio", + .pm = &tm2_pm_ops, + .of_match_table = tm2_of_match, + }, + .probe = tm2_probe, +}; +module_platform_driver(tm2_driver); + +MODULE_AUTHOR("Inha Song "); +MODULE_DESCRIPTION("ALSA SoC Exynos TM2 Audio Support"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/samsung/tobermory.c b/sound/soc/samsung/tobermory.c new file mode 100644 index 000000000..998727cb4 --- /dev/null +++ b/sound/soc/samsung/tobermory.c @@ -0,0 +1,255 @@ +/* + * Tobermory audio support + * + * Copyright 2011 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "../codecs/wm8962.h" + +static int sample_rate = 44100; + +static int tobermory_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + WM8962_FLL_MCLK, 32768, + sample_rate * 512); + if (ret < 0) + pr_err("Failed to start FLL: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8962_SYSCLK_FLL, + sample_rate * 512, + SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to set SYSCLK: %d\n", ret); + snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + 0, 0, 0); + return ret; + } + } + break; + + default: + break; + } + + return 0; +} + +static int tobermory_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *codec_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + codec_dai = rtd->codec_dai; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("Failed to switch away from FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + 0, 0, 0); + if (ret < 0) { + pr_err("Failed to stop FLL: %d\n", ret); + return ret; + } + break; + + default: + break; + } + + dapm->bias_level = level; + + return 0; +} + +static int tobermory_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + sample_rate = params_rate(params); + + return 0; +} + +static struct snd_soc_ops tobermory_ops = { + .hw_params = tobermory_hw_params, +}; + +static struct snd_soc_dai_link tobermory_dai[] = { + { + .name = "CPU", + .stream_name = "CPU", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8962", + .platform_name = "samsung-i2s.0", + .codec_name = "wm8962.1-001a", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &tobermory_ops, + }, +}; + +static const struct snd_kcontrol_new controls[] = { + SOC_DAPM_PIN_SWITCH("Main Speaker"), + SOC_DAPM_PIN_SWITCH("DMIC"), +}; + +static struct snd_soc_dapm_widget widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + + SND_SOC_DAPM_MIC("DMIC", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + + SND_SOC_DAPM_SPK("Main Speaker", NULL), +}; + +static struct snd_soc_dapm_route audio_paths[] = { + { "Headphone", NULL, "HPOUTL" }, + { "Headphone", NULL, "HPOUTR" }, + + { "Main Speaker", NULL, "SPKOUTL" }, + { "Main Speaker", NULL, "SPKOUTR" }, + + { "Headset Mic", NULL, "MICBIAS" }, + { "IN4L", NULL, "Headset Mic" }, + { "IN4R", NULL, "Headset Mic" }, + + { "AMIC", NULL, "MICBIAS" }, + { "IN1L", NULL, "AMIC" }, + { "IN1R", NULL, "AMIC" }, + + { "DMIC", NULL, "MICBIAS" }, + { "DMICDAT", NULL, "DMIC" }, +}; + +static struct snd_soc_jack tobermory_headset; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin tobermory_headset_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int tobermory_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; + struct snd_soc_dai *codec_dai; + int ret; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + component = rtd->codec_dai->component; + codec_dai = rtd->codec_dai; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + 32768, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_card_jack_new(card, "Headset", SND_JACK_HEADSET | + SND_JACK_BTN_0, &tobermory_headset, + tobermory_headset_pins, + ARRAY_SIZE(tobermory_headset_pins)); + if (ret) + return ret; + + wm8962_mic_detect(component, &tobermory_headset); + + return 0; +} + +static struct snd_soc_card tobermory = { + .name = "Tobermory", + .owner = THIS_MODULE, + .dai_link = tobermory_dai, + .num_links = ARRAY_SIZE(tobermory_dai), + + .set_bias_level = tobermory_set_bias_level, + .set_bias_level_post = tobermory_set_bias_level_post, + + .controls = controls, + .num_controls = ARRAY_SIZE(controls), + .dapm_widgets = widgets, + .num_dapm_widgets = ARRAY_SIZE(widgets), + .dapm_routes = audio_paths, + .num_dapm_routes = ARRAY_SIZE(audio_paths), + .fully_routed = true, + + .late_probe = tobermory_late_probe, +}; + +static int tobermory_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &tobermory; + int ret; + + card->dev = &pdev->dev; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + + return ret; +} + +static struct platform_driver tobermory_driver = { + .driver = { + .name = "tobermory", + .pm = &snd_soc_pm_ops, + }, + .probe = tobermory_probe, +}; + +module_platform_driver(tobermory_driver); + +MODULE_DESCRIPTION("Tobermory audio support"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:tobermory"); diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig new file mode 100644 index 000000000..dc20f0f70 --- /dev/null +++ b/sound/soc/sh/Kconfig @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0 +menu "SoC Audio support for Renesas SoCs" + depends on SUPERH || ARCH_RENESAS || COMPILE_TEST + +config SND_SOC_PCM_SH7760 + tristate "SoC Audio support for Renesas SH7760" + depends on CPU_SUBTYPE_SH7760 && SH_DMABRG + help + Enable this option for SH7760 AC97/I2S audio support. + + +## +## Audio unit modules +## + +config SND_SOC_SH4_HAC + tristate + select AC97_BUS + select SND_SOC_AC97_BUS + +config SND_SOC_SH4_SSI + tristate + +config SND_SOC_SH4_FSI + tristate "SH4 FSI support" + select SND_SIMPLE_CARD + help + This option enables FSI sound support + +config SND_SOC_SH4_SIU + tristate + depends on ARCH_SHMOBILE && HAVE_CLK + select DMA_ENGINE + select DMADEVICES + select SH_DMAE + select FW_LOADER + +config SND_SOC_RCAR + tristate "R-Car series SRU/SCU/SSIU/SSI support" + depends on COMMON_CLK + depends on OF || COMPILE_TEST + select SND_SIMPLE_CARD_UTILS + select REGMAP_MMIO + help + This option enables R-Car SRU/SCU/SSIU/SSI sound support + +## +## Boards +## + +config SND_SH7760_AC97 + tristate "SH7760 AC97 sound support" + depends on CPU_SUBTYPE_SH7760 && SND_SOC_PCM_SH7760 + select SND_SOC_SH4_HAC + select SND_SOC_AC97_CODEC + help + This option enables generic sound support for the first + AC97 unit of the SH7760. + +config SND_SIU_MIGOR + tristate "SIU sound support on Migo-R" + depends on SH_MIGOR && I2C + select SND_SOC_SH4_SIU + select SND_SOC_WM8978 + help + This option enables sound support for the SH7722 Migo-R board + +endmenu diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile new file mode 100644 index 000000000..51bd7c816 --- /dev/null +++ b/sound/soc/sh/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +## DMA engines +snd-soc-dma-sh7760-objs := dma-sh7760.o +obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o + +## audio units found on some SH-4 +snd-soc-hac-objs := hac.o +snd-soc-ssi-objs := ssi.o +snd-soc-fsi-objs := fsi.o +snd-soc-siu-objs := siu_pcm.o siu_dai.o +obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o +obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o +obj-$(CONFIG_SND_SOC_SH4_FSI) += snd-soc-fsi.o +obj-$(CONFIG_SND_SOC_SH4_SIU) += snd-soc-siu.o + +## audio units for R-Car +obj-$(CONFIG_SND_SOC_RCAR) += rcar/ + +## boards +snd-soc-sh7760-ac97-objs := sh7760-ac97.o +snd-soc-migor-objs := migor.o + +obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o +obj-$(CONFIG_SND_SIU_MIGOR) += snd-soc-migor.o diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c new file mode 100644 index 000000000..922fb6aa3 --- /dev/null +++ b/sound/soc/sh/dma-sh7760.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// SH7760 ("camelot") DMABRG audio DMA unit support +// +// Copyright (C) 2007 Manuel Lauss +// +// The SH7760 DMABRG provides 4 dma channels (2x rec, 2x play), which +// trigger an interrupt when one half of the programmed transfer size +// has been xmitted. +// +// FIXME: little-endian only for now + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* registers and bits */ +#define BRGATXSAR 0x00 +#define BRGARXDAR 0x04 +#define BRGATXTCR 0x08 +#define BRGARXTCR 0x0C +#define BRGACR 0x10 +#define BRGATXTCNT 0x14 +#define BRGARXTCNT 0x18 + +#define ACR_RAR (1 << 18) +#define ACR_RDS (1 << 17) +#define ACR_RDE (1 << 16) +#define ACR_TAR (1 << 2) +#define ACR_TDS (1 << 1) +#define ACR_TDE (1 << 0) + +/* receiver/transmitter data alignment */ +#define ACR_RAM_NONE (0 << 24) +#define ACR_RAM_4BYTE (1 << 24) +#define ACR_RAM_2WORD (2 << 24) +#define ACR_TAM_NONE (0 << 8) +#define ACR_TAM_4BYTE (1 << 8) +#define ACR_TAM_2WORD (2 << 8) + + +struct camelot_pcm { + unsigned long mmio; /* DMABRG audio channel control reg MMIO */ + unsigned int txid; /* ID of first DMABRG IRQ for this unit */ + + struct snd_pcm_substream *tx_ss; + unsigned long tx_period_size; + unsigned int tx_period; + + struct snd_pcm_substream *rx_ss; + unsigned long rx_period_size; + unsigned int rx_period; + +} cam_pcm_data[2] = { + { + .mmio = 0xFE3C0040, + .txid = DMABRGIRQ_A0TXF, + }, + { + .mmio = 0xFE3C0060, + .txid = DMABRGIRQ_A1TXF, + }, +}; + +#define BRGREG(x) (*(unsigned long *)(cam->mmio + (x))) + +/* + * set a minimum of 16kb per period, to avoid interrupt-"storm" and + * resulting skipping. In general, the bigger the minimum size, the + * better for overall system performance. (The SH7760 is a puny CPU + * with a slow SDRAM interface and poor internal bus bandwidth, + * *especially* when the LCDC is active). The minimum for the DMAC + * is 8 bytes; 16kbytes are enough to get skip-free playback of a + * 44kHz/16bit/stereo MP3 on a lightly loaded system, and maintain + * reasonable responsiveness in MPlayer. + */ +#define DMABRG_PERIOD_MIN 16 * 1024 +#define DMABRG_PERIOD_MAX 0x03fffffc +#define DMABRG_PREALLOC_BUFFER 32 * 1024 +#define DMABRG_PREALLOC_BUFFER_MAX 32 * 1024 + +static const struct snd_pcm_hardware camelot_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH), + .buffer_bytes_max = DMABRG_PERIOD_MAX, + .period_bytes_min = DMABRG_PERIOD_MIN, + .period_bytes_max = DMABRG_PERIOD_MAX / 2, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 128, +}; + +static void camelot_txdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->tx_period ^= 1; + snd_pcm_period_elapsed(cam->tx_ss); +} + +static void camelot_rxdma(void *data) +{ + struct camelot_pcm *cam = data; + cam->rx_period ^= 1; + snd_pcm_period_elapsed(cam->rx_ss); +} + +static int camelot_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret, dmairq; + + snd_soc_set_runtime_hwparams(substream, &camelot_pcm_hardware); + + /* DMABRG buffer half/full events */ + dmairq = (recv) ? cam->txid + 2 : cam->txid; + if (recv) { + cam->rx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_rxdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1,camelot_rxdma, cam); + } else { + cam->tx_ss = substream; + ret = dmabrg_request_irq(dmairq, camelot_txdma, cam); + if (unlikely(ret)) { + pr_debug("audio unit %d irqs already taken!\n", + rtd->cpu_dai->id); + return -EBUSY; + } + (void)dmabrg_request_irq(dmairq + 1, camelot_txdma, cam); + } + return 0; +} + +static int camelot_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int dmairq; + + dmairq = (recv) ? cam->txid + 2 : cam->txid; + + if (recv) + cam->rx_ss = NULL; + else + cam->tx_ss = NULL; + + dmabrg_free_irq(dmairq + 1); + dmabrg_free_irq(dmairq); + + return 0; +} + +static int camelot_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + + if (recv) { + cam->rx_period_size = params_period_bytes(hw_params); + cam->rx_period = 0; + } else { + cam->tx_period_size = params_period_bytes(hw_params); + cam->tx_period = 0; + } + return 0; +} + +static int camelot_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int camelot_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id]; + + pr_debug("PCM data: addr 0x%08ulx len %d\n", + (u32)runtime->dma_addr, runtime->dma_bytes); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + BRGREG(BRGATXSAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGATXTCR) = runtime->dma_bytes; + } else { + BRGREG(BRGARXDAR) = (unsigned long)runtime->dma_area; + BRGREG(BRGARXTCR) = runtime->dma_bytes; + } + + return 0; +} + +static inline void dmabrg_play_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: XFER start, auto-addr-reload */ + BRGREG(BRGACR) = acr | ACR_TDE | ACR_TAR | ACR_TAM_2WORD; +} + +static inline void dmabrg_play_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data transmission */ + BRGREG(BRGACR) = acr | ACR_TDS; +} + +static inline void dmabrg_rec_dma_start(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* start DMABRG engine: recv start, auto-reload */ + BRGREG(BRGACR) = acr | ACR_RDE | ACR_RAR | ACR_RAM_2WORD; +} + +static inline void dmabrg_rec_dma_stop(struct camelot_pcm *cam) +{ + unsigned long acr = BRGREG(BRGACR) & ~(ACR_TDS | ACR_RDS); + /* forcibly terminate data receiver */ + BRGREG(BRGACR) = acr | ACR_RDS; +} + +static int camelot_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (recv) + dmabrg_rec_dma_start(cam); + else + dmabrg_play_dma_start(cam); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (recv) + dmabrg_rec_dma_stop(cam); + else + dmabrg_play_dma_stop(cam); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t camelot_pos(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct camelot_pcm *cam = &cam_pcm_data[rtd->cpu_dai->id]; + int recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0:1; + unsigned long pos; + + /* cannot use the DMABRG pointer register: under load, by the + * time ALSA comes around to read the register, it is already + * far ahead (or worse, already done with the fragment) of the + * position at the time the IRQ was triggered, which results in + * fast-playback sound in my test application (ScummVM) + */ + if (recv) + pos = cam->rx_period ? cam->rx_period_size : 0; + else + pos = cam->tx_period ? cam->tx_period_size : 0; + + return bytes_to_frames(runtime, pos); +} + +static const struct snd_pcm_ops camelot_pcm_ops = { + .open = camelot_pcm_open, + .close = camelot_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = camelot_hw_params, + .hw_free = camelot_hw_free, + .prepare = camelot_prepare, + .trigger = camelot_trigger, + .pointer = camelot_pos, +}; + +static int camelot_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel + * in MMAP mode (i.e. aplay -M) + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + DMABRG_PREALLOC_BUFFER, DMABRG_PREALLOC_BUFFER_MAX); + + return 0; +} + +static const struct snd_soc_component_driver sh7760_soc_component = { + .ops = &camelot_pcm_ops, + .pcm_new = camelot_pcm_new, +}; + +static int sh7760_soc_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &sh7760_soc_component, + NULL, 0); +} + +static struct platform_driver sh7760_pcm_driver = { + .driver = { + .name = "sh7760-pcm-audio", + }, + + .probe = sh7760_soc_platform_probe, +}; + +module_platform_driver(sh7760_pcm_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SH7760 Audio DMA (DMABRG) driver"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c new file mode 100644 index 000000000..f486e2b2c --- /dev/null +++ b/sound/soc/sh/fsi.c @@ -0,0 +1,2121 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Fifo-attached Serial Interface (FSI) support for SH7724 +// +// Copyright (C) 2009 Renesas Solutions Corp. +// Kuninori Morimoto +// +// Based on ssi.c +// Copyright (c) 2007 Manuel Lauss + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PortA/PortB register */ +#define REG_DO_FMT 0x0000 +#define REG_DOFF_CTL 0x0004 +#define REG_DOFF_ST 0x0008 +#define REG_DI_FMT 0x000C +#define REG_DIFF_CTL 0x0010 +#define REG_DIFF_ST 0x0014 +#define REG_CKG1 0x0018 +#define REG_CKG2 0x001C +#define REG_DIDT 0x0020 +#define REG_DODT 0x0024 +#define REG_MUTE_ST 0x0028 +#define REG_OUT_DMAC 0x002C +#define REG_OUT_SEL 0x0030 +#define REG_IN_DMAC 0x0038 + +/* master register */ +#define MST_CLK_RST 0x0210 +#define MST_SOFT_RST 0x0214 +#define MST_FIFO_SZ 0x0218 + +/* core register (depend on FSI version) */ +#define A_MST_CTLR 0x0180 +#define B_MST_CTLR 0x01A0 +#define CPU_INT_ST 0x01F4 +#define CPU_IEMSK 0x01F8 +#define CPU_IMSK 0x01FC +#define INT_ST 0x0200 +#define IEMSK 0x0204 +#define IMSK 0x0208 + +/* DO_FMT */ +/* DI_FMT */ +#define CR_BWS_MASK (0x3 << 20) /* FSI2 */ +#define CR_BWS_24 (0x0 << 20) /* FSI2 */ +#define CR_BWS_16 (0x1 << 20) /* FSI2 */ +#define CR_BWS_20 (0x2 << 20) /* FSI2 */ + +#define CR_DTMD_PCM (0x0 << 8) /* FSI2 */ +#define CR_DTMD_SPDIF_PCM (0x1 << 8) /* FSI2 */ +#define CR_DTMD_SPDIF_STREAM (0x2 << 8) /* FSI2 */ + +#define CR_MONO (0x0 << 4) +#define CR_MONO_D (0x1 << 4) +#define CR_PCM (0x2 << 4) +#define CR_I2S (0x3 << 4) +#define CR_TDM (0x4 << 4) +#define CR_TDM_D (0x5 << 4) + +/* OUT_DMAC */ +/* IN_DMAC */ +#define VDMD_MASK (0x3 << 4) +#define VDMD_FRONT (0x0 << 4) /* Package in front */ +#define VDMD_BACK (0x1 << 4) /* Package in back */ +#define VDMD_STREAM (0x2 << 4) /* Stream mode(16bit * 2) */ + +#define DMA_ON (0x1 << 0) + +/* DOFF_CTL */ +/* DIFF_CTL */ +#define IRQ_HALF 0x00100000 +#define FIFO_CLR 0x00000001 + +/* DOFF_ST */ +#define ERR_OVER 0x00000010 +#define ERR_UNDER 0x00000001 +#define ST_ERR (ERR_OVER | ERR_UNDER) + +/* CKG1 */ +#define ACKMD_MASK 0x00007000 +#define BPFMD_MASK 0x00000700 +#define DIMD (1 << 4) +#define DOMD (1 << 0) + +/* A/B MST_CTLR */ +#define BP (1 << 4) /* Fix the signal of Biphase output */ +#define SE (1 << 0) /* Fix the master clock */ + +/* CLK_RST */ +#define CRB (1 << 4) +#define CRA (1 << 0) + +/* IO SHIFT / MACRO */ +#define BI_SHIFT 12 +#define BO_SHIFT 8 +#define AI_SHIFT 4 +#define AO_SHIFT 0 +#define AB_IO(param, shift) (param << shift) + +/* SOFT_RST */ +#define PBSR (1 << 12) /* Port B Software Reset */ +#define PASR (1 << 8) /* Port A Software Reset */ +#define IR (1 << 4) /* Interrupt Reset */ +#define FSISR (1 << 0) /* Software Reset */ + +/* OUT_SEL (FSI2) */ +#define DMMD (1 << 4) /* SPDIF output timing 0: Biphase only */ + /* 1: Biphase and serial */ + +/* FIFO_SZ */ +#define FIFO_SZ_MASK 0x7 + +#define FSI_RATES SNDRV_PCM_RATE_8000_96000 + +#define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +/* + * bus options + * + * 0x000000BA + * + * A : sample widtht 16bit setting + * B : sample widtht 24bit setting + */ + +#define SHIFT_16DATA 0 +#define SHIFT_24DATA 4 + +#define PACKAGE_24BITBUS_BACK 0 +#define PACKAGE_24BITBUS_FRONT 1 +#define PACKAGE_16BITBUS_STREAM 2 + +#define BUSOP_SET(s, a) ((a) << SHIFT_ ## s ## DATA) +#define BUSOP_GET(s, a) (((a) >> SHIFT_ ## s ## DATA) & 0xF) + +/* + * FSI driver use below type name for variable + * + * xxx_num : number of data + * xxx_pos : position of data + * xxx_capa : capacity of data + */ + +/* + * period/frame/sample image + * + * ex) PCM (2ch) + * + * period pos period pos + * [n] [n + 1] + * |<-------------------- period--------------------->| + * ==|============================================ ... =|== + * | | + * ||<----- frame ----->|<------ frame ----->| ... | + * |+--------------------+--------------------+- ... | + * ||[ sample ][ sample ]|[ sample ][ sample ]| ... | + * |+--------------------+--------------------+- ... | + * ==|============================================ ... =|== + */ + +/* + * FSI FIFO image + * + * | | + * | | + * | [ sample ] | + * | [ sample ] | + * | [ sample ] | + * | [ sample ] | + * --> go to codecs + */ + +/* + * FSI clock + * + * FSIxCLK [CPG] (ick) -------> | + * |-> FSI_DIV (div)-> FSI2 + * FSIxCK [external] (xck) ---> | + */ + +/* + * struct + */ + +struct fsi_stream_handler; +struct fsi_stream { + + /* + * these are initialized by fsi_stream_init() + */ + struct snd_pcm_substream *substream; + int fifo_sample_capa; /* sample capacity of FSI FIFO */ + int buff_sample_capa; /* sample capacity of ALSA buffer */ + int buff_sample_pos; /* sample position of ALSA buffer */ + int period_samples; /* sample number / 1 period */ + int period_pos; /* current period position */ + int sample_width; /* sample width */ + int uerr_num; + int oerr_num; + + /* + * bus options + */ + u32 bus_option; + + /* + * thse are initialized by fsi_handler_init() + */ + struct fsi_stream_handler *handler; + struct fsi_priv *priv; + + /* + * these are for DMAEngine + */ + struct dma_chan *chan; + int dma_id; +}; + +struct fsi_clk { + /* see [FSI clock] */ + struct clk *own; + struct clk *xck; + struct clk *ick; + struct clk *div; + int (*set_rate)(struct device *dev, + struct fsi_priv *fsi); + + unsigned long rate; + unsigned int count; +}; + +struct fsi_priv { + void __iomem *base; + phys_addr_t phys; + struct fsi_master *master; + + struct fsi_stream playback; + struct fsi_stream capture; + + struct fsi_clk clock; + + u32 fmt; + + int chan_num:16; + unsigned int clk_master:1; + unsigned int clk_cpg:1; + unsigned int spdif:1; + unsigned int enable_stream:1; + unsigned int bit_clk_inv:1; + unsigned int lr_clk_inv:1; +}; + +struct fsi_stream_handler { + int (*init)(struct fsi_priv *fsi, struct fsi_stream *io); + int (*quit)(struct fsi_priv *fsi, struct fsi_stream *io); + int (*probe)(struct fsi_priv *fsi, struct fsi_stream *io, struct device *dev); + int (*transfer)(struct fsi_priv *fsi, struct fsi_stream *io); + int (*remove)(struct fsi_priv *fsi, struct fsi_stream *io); + int (*start_stop)(struct fsi_priv *fsi, struct fsi_stream *io, + int enable); +}; +#define fsi_stream_handler_call(io, func, args...) \ + (!(io) ? -ENODEV : \ + !((io)->handler->func) ? 0 : \ + (io)->handler->func(args)) + +struct fsi_core { + int ver; + + u32 int_st; + u32 iemsk; + u32 imsk; + u32 a_mclk; + u32 b_mclk; +}; + +struct fsi_master { + void __iomem *base; + struct fsi_priv fsia; + struct fsi_priv fsib; + const struct fsi_core *core; + spinlock_t lock; +}; + +static inline int fsi_stream_is_play(struct fsi_priv *fsi, + struct fsi_stream *io) +{ + return &fsi->playback == io; +} + + +/* + * basic read write function + */ + +static void __fsi_reg_write(u32 __iomem *reg, u32 data) +{ + /* valid data area is 24bit */ + data &= 0x00ffffff; + + __raw_writel(data, reg); +} + +static u32 __fsi_reg_read(u32 __iomem *reg) +{ + return __raw_readl(reg); +} + +static void __fsi_reg_mask_set(u32 __iomem *reg, u32 mask, u32 data) +{ + u32 val = __fsi_reg_read(reg); + + val &= ~mask; + val |= data & mask; + + __fsi_reg_write(reg, val); +} + +#define fsi_reg_write(p, r, d)\ + __fsi_reg_write((p->base + REG_##r), d) + +#define fsi_reg_read(p, r)\ + __fsi_reg_read((p->base + REG_##r)) + +#define fsi_reg_mask_set(p, r, m, d)\ + __fsi_reg_mask_set((p->base + REG_##r), m, d) + +#define fsi_master_read(p, r) _fsi_master_read(p, MST_##r) +#define fsi_core_read(p, r) _fsi_master_read(p, p->core->r) +static u32 _fsi_master_read(struct fsi_master *master, u32 reg) +{ + u32 ret; + unsigned long flags; + + spin_lock_irqsave(&master->lock, flags); + ret = __fsi_reg_read(master->base + reg); + spin_unlock_irqrestore(&master->lock, flags); + + return ret; +} + +#define fsi_master_mask_set(p, r, m, d) _fsi_master_mask_set(p, MST_##r, m, d) +#define fsi_core_mask_set(p, r, m, d) _fsi_master_mask_set(p, p->core->r, m, d) +static void _fsi_master_mask_set(struct fsi_master *master, + u32 reg, u32 mask, u32 data) +{ + unsigned long flags; + + spin_lock_irqsave(&master->lock, flags); + __fsi_reg_mask_set(master->base + reg, mask, data); + spin_unlock_irqrestore(&master->lock, flags); +} + +/* + * basic function + */ +static int fsi_version(struct fsi_master *master) +{ + return master->core->ver; +} + +static struct fsi_master *fsi_get_master(struct fsi_priv *fsi) +{ + return fsi->master; +} + +static int fsi_is_clk_master(struct fsi_priv *fsi) +{ + return fsi->clk_master; +} + +static int fsi_is_port_a(struct fsi_priv *fsi) +{ + return fsi->master->base == fsi->base; +} + +static int fsi_is_spdif(struct fsi_priv *fsi) +{ + return fsi->spdif; +} + +static int fsi_is_enable_stream(struct fsi_priv *fsi) +{ + return fsi->enable_stream; +} + +static int fsi_is_play(struct snd_pcm_substream *substream) +{ + return substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +} + +static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + return rtd->cpu_dai; +} + +static struct fsi_priv *fsi_get_priv_frm_dai(struct snd_soc_dai *dai) +{ + struct fsi_master *master = snd_soc_dai_get_drvdata(dai); + + if (dai->id == 0) + return &master->fsia; + else + return &master->fsib; +} + +static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream) +{ + return fsi_get_priv_frm_dai(fsi_get_dai(substream)); +} + +static u32 fsi_get_port_shift(struct fsi_priv *fsi, struct fsi_stream *io) +{ + int is_play = fsi_stream_is_play(fsi, io); + int is_porta = fsi_is_port_a(fsi); + u32 shift; + + if (is_porta) + shift = is_play ? AO_SHIFT : AI_SHIFT; + else + shift = is_play ? BO_SHIFT : BI_SHIFT; + + return shift; +} + +static int fsi_frame2sample(struct fsi_priv *fsi, int frames) +{ + return frames * fsi->chan_num; +} + +static int fsi_sample2frame(struct fsi_priv *fsi, int samples) +{ + return samples / fsi->chan_num; +} + +static int fsi_get_current_fifo_samples(struct fsi_priv *fsi, + struct fsi_stream *io) +{ + int is_play = fsi_stream_is_play(fsi, io); + u32 status; + int frames; + + status = is_play ? + fsi_reg_read(fsi, DOFF_ST) : + fsi_reg_read(fsi, DIFF_ST); + + frames = 0x1ff & (status >> 8); + + return fsi_frame2sample(fsi, frames); +} + +static void fsi_count_fifo_err(struct fsi_priv *fsi) +{ + u32 ostatus = fsi_reg_read(fsi, DOFF_ST); + u32 istatus = fsi_reg_read(fsi, DIFF_ST); + + if (ostatus & ERR_OVER) + fsi->playback.oerr_num++; + + if (ostatus & ERR_UNDER) + fsi->playback.uerr_num++; + + if (istatus & ERR_OVER) + fsi->capture.oerr_num++; + + if (istatus & ERR_UNDER) + fsi->capture.uerr_num++; + + fsi_reg_write(fsi, DOFF_ST, 0); + fsi_reg_write(fsi, DIFF_ST, 0); +} + +/* + * fsi_stream_xx() function + */ +static inline struct fsi_stream *fsi_stream_get(struct fsi_priv *fsi, + struct snd_pcm_substream *substream) +{ + return fsi_is_play(substream) ? &fsi->playback : &fsi->capture; +} + +static int fsi_stream_is_working(struct fsi_priv *fsi, + struct fsi_stream *io) +{ + struct fsi_master *master = fsi_get_master(fsi); + unsigned long flags; + int ret; + + spin_lock_irqsave(&master->lock, flags); + ret = !!(io->substream && io->substream->runtime); + spin_unlock_irqrestore(&master->lock, flags); + + return ret; +} + +static struct fsi_priv *fsi_stream_to_priv(struct fsi_stream *io) +{ + return io->priv; +} + +static void fsi_stream_init(struct fsi_priv *fsi, + struct fsi_stream *io, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsi_master *master = fsi_get_master(fsi); + unsigned long flags; + + spin_lock_irqsave(&master->lock, flags); + io->substream = substream; + io->buff_sample_capa = fsi_frame2sample(fsi, runtime->buffer_size); + io->buff_sample_pos = 0; + io->period_samples = fsi_frame2sample(fsi, runtime->period_size); + io->period_pos = 0; + io->sample_width = samples_to_bytes(runtime, 1); + io->bus_option = 0; + io->oerr_num = -1; /* ignore 1st err */ + io->uerr_num = -1; /* ignore 1st err */ + fsi_stream_handler_call(io, init, fsi, io); + spin_unlock_irqrestore(&master->lock, flags); +} + +static void fsi_stream_quit(struct fsi_priv *fsi, struct fsi_stream *io) +{ + struct snd_soc_dai *dai = fsi_get_dai(io->substream); + struct fsi_master *master = fsi_get_master(fsi); + unsigned long flags; + + spin_lock_irqsave(&master->lock, flags); + + if (io->oerr_num > 0) + dev_err(dai->dev, "over_run = %d\n", io->oerr_num); + + if (io->uerr_num > 0) + dev_err(dai->dev, "under_run = %d\n", io->uerr_num); + + fsi_stream_handler_call(io, quit, fsi, io); + io->substream = NULL; + io->buff_sample_capa = 0; + io->buff_sample_pos = 0; + io->period_samples = 0; + io->period_pos = 0; + io->sample_width = 0; + io->bus_option = 0; + io->oerr_num = 0; + io->uerr_num = 0; + spin_unlock_irqrestore(&master->lock, flags); +} + +static int fsi_stream_transfer(struct fsi_stream *io) +{ + struct fsi_priv *fsi = fsi_stream_to_priv(io); + if (!fsi) + return -EIO; + + return fsi_stream_handler_call(io, transfer, fsi, io); +} + +#define fsi_stream_start(fsi, io)\ + fsi_stream_handler_call(io, start_stop, fsi, io, 1) + +#define fsi_stream_stop(fsi, io)\ + fsi_stream_handler_call(io, start_stop, fsi, io, 0) + +static int fsi_stream_probe(struct fsi_priv *fsi, struct device *dev) +{ + struct fsi_stream *io; + int ret1, ret2; + + io = &fsi->playback; + ret1 = fsi_stream_handler_call(io, probe, fsi, io, dev); + + io = &fsi->capture; + ret2 = fsi_stream_handler_call(io, probe, fsi, io, dev); + + if (ret1 < 0) + return ret1; + if (ret2 < 0) + return ret2; + + return 0; +} + +static int fsi_stream_remove(struct fsi_priv *fsi) +{ + struct fsi_stream *io; + int ret1, ret2; + + io = &fsi->playback; + ret1 = fsi_stream_handler_call(io, remove, fsi, io); + + io = &fsi->capture; + ret2 = fsi_stream_handler_call(io, remove, fsi, io); + + if (ret1 < 0) + return ret1; + if (ret2 < 0) + return ret2; + + return 0; +} + +/* + * format/bus/dma setting + */ +static void fsi_format_bus_setup(struct fsi_priv *fsi, struct fsi_stream *io, + u32 bus, struct device *dev) +{ + struct fsi_master *master = fsi_get_master(fsi); + int is_play = fsi_stream_is_play(fsi, io); + u32 fmt = fsi->fmt; + + if (fsi_version(master) >= 2) { + u32 dma = 0; + + /* + * FSI2 needs DMA/Bus setting + */ + switch (bus) { + case PACKAGE_24BITBUS_FRONT: + fmt |= CR_BWS_24; + dma |= VDMD_FRONT; + dev_dbg(dev, "24bit bus / package in front\n"); + break; + case PACKAGE_16BITBUS_STREAM: + fmt |= CR_BWS_16; + dma |= VDMD_STREAM; + dev_dbg(dev, "16bit bus / stream mode\n"); + break; + case PACKAGE_24BITBUS_BACK: + default: + fmt |= CR_BWS_24; + dma |= VDMD_BACK; + dev_dbg(dev, "24bit bus / package in back\n"); + break; + } + + if (is_play) + fsi_reg_write(fsi, OUT_DMAC, dma); + else + fsi_reg_write(fsi, IN_DMAC, dma); + } + + if (is_play) + fsi_reg_write(fsi, DO_FMT, fmt); + else + fsi_reg_write(fsi, DI_FMT, fmt); +} + +/* + * irq function + */ + +static void fsi_irq_enable(struct fsi_priv *fsi, struct fsi_stream *io) +{ + u32 data = AB_IO(1, fsi_get_port_shift(fsi, io)); + struct fsi_master *master = fsi_get_master(fsi); + + fsi_core_mask_set(master, imsk, data, data); + fsi_core_mask_set(master, iemsk, data, data); +} + +static void fsi_irq_disable(struct fsi_priv *fsi, struct fsi_stream *io) +{ + u32 data = AB_IO(1, fsi_get_port_shift(fsi, io)); + struct fsi_master *master = fsi_get_master(fsi); + + fsi_core_mask_set(master, imsk, data, 0); + fsi_core_mask_set(master, iemsk, data, 0); +} + +static u32 fsi_irq_get_status(struct fsi_master *master) +{ + return fsi_core_read(master, int_st); +} + +static void fsi_irq_clear_status(struct fsi_priv *fsi) +{ + u32 data = 0; + struct fsi_master *master = fsi_get_master(fsi); + + data |= AB_IO(1, fsi_get_port_shift(fsi, &fsi->playback)); + data |= AB_IO(1, fsi_get_port_shift(fsi, &fsi->capture)); + + /* clear interrupt factor */ + fsi_core_mask_set(master, int_st, data, 0); +} + +/* + * SPDIF master clock function + * + * These functions are used later FSI2 + */ +static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable) +{ + struct fsi_master *master = fsi_get_master(fsi); + u32 mask, val; + + mask = BP | SE; + val = enable ? mask : 0; + + fsi_is_port_a(fsi) ? + fsi_core_mask_set(master, a_mclk, mask, val) : + fsi_core_mask_set(master, b_mclk, mask, val); +} + +/* + * clock function + */ +static int fsi_clk_init(struct device *dev, + struct fsi_priv *fsi, + int xck, + int ick, + int div, + int (*set_rate)(struct device *dev, + struct fsi_priv *fsi)) +{ + struct fsi_clk *clock = &fsi->clock; + int is_porta = fsi_is_port_a(fsi); + + clock->xck = NULL; + clock->ick = NULL; + clock->div = NULL; + clock->rate = 0; + clock->count = 0; + clock->set_rate = set_rate; + + clock->own = devm_clk_get(dev, NULL); + if (IS_ERR(clock->own)) + return -EINVAL; + + /* external clock */ + if (xck) { + clock->xck = devm_clk_get(dev, is_porta ? "xcka" : "xckb"); + if (IS_ERR(clock->xck)) { + dev_err(dev, "can't get xck clock\n"); + return -EINVAL; + } + if (clock->xck == clock->own) { + dev_err(dev, "cpu doesn't support xck clock\n"); + return -EINVAL; + } + } + + /* FSIACLK/FSIBCLK */ + if (ick) { + clock->ick = devm_clk_get(dev, is_porta ? "icka" : "ickb"); + if (IS_ERR(clock->ick)) { + dev_err(dev, "can't get ick clock\n"); + return -EINVAL; + } + if (clock->ick == clock->own) { + dev_err(dev, "cpu doesn't support ick clock\n"); + return -EINVAL; + } + } + + /* FSI-DIV */ + if (div) { + clock->div = devm_clk_get(dev, is_porta ? "diva" : "divb"); + if (IS_ERR(clock->div)) { + dev_err(dev, "can't get div clock\n"); + return -EINVAL; + } + if (clock->div == clock->own) { + dev_err(dev, "cpu doens't support div clock\n"); + return -EINVAL; + } + } + + return 0; +} + +#define fsi_clk_invalid(fsi) fsi_clk_valid(fsi, 0) +static void fsi_clk_valid(struct fsi_priv *fsi, unsigned long rate) +{ + fsi->clock.rate = rate; +} + +static int fsi_clk_is_valid(struct fsi_priv *fsi) +{ + return fsi->clock.set_rate && + fsi->clock.rate; +} + +static int fsi_clk_enable(struct device *dev, + struct fsi_priv *fsi) +{ + struct fsi_clk *clock = &fsi->clock; + int ret = -EINVAL; + + if (!fsi_clk_is_valid(fsi)) + return ret; + + if (0 == clock->count) { + ret = clock->set_rate(dev, fsi); + if (ret < 0) { + fsi_clk_invalid(fsi); + return ret; + } + + ret = clk_enable(clock->xck); + if (ret) + goto err; + ret = clk_enable(clock->ick); + if (ret) + goto disable_xck; + ret = clk_enable(clock->div); + if (ret) + goto disable_ick; + + clock->count++; + } + + return ret; + +disable_ick: + clk_disable(clock->ick); +disable_xck: + clk_disable(clock->xck); +err: + return ret; +} + +static int fsi_clk_disable(struct device *dev, + struct fsi_priv *fsi) +{ + struct fsi_clk *clock = &fsi->clock; + + if (!fsi_clk_is_valid(fsi)) + return -EINVAL; + + if (1 == clock->count--) { + clk_disable(clock->xck); + clk_disable(clock->ick); + clk_disable(clock->div); + } + + return 0; +} + +static int fsi_clk_set_ackbpf(struct device *dev, + struct fsi_priv *fsi, + int ackmd, int bpfmd) +{ + u32 data = 0; + + /* check ackmd/bpfmd relationship */ + if (bpfmd > ackmd) { + dev_err(dev, "unsupported rate (%d/%d)\n", ackmd, bpfmd); + return -EINVAL; + } + + /* ACKMD */ + switch (ackmd) { + case 512: + data |= (0x0 << 12); + break; + case 256: + data |= (0x1 << 12); + break; + case 128: + data |= (0x2 << 12); + break; + case 64: + data |= (0x3 << 12); + break; + case 32: + data |= (0x4 << 12); + break; + default: + dev_err(dev, "unsupported ackmd (%d)\n", ackmd); + return -EINVAL; + } + + /* BPFMD */ + switch (bpfmd) { + case 32: + data |= (0x0 << 8); + break; + case 64: + data |= (0x1 << 8); + break; + case 128: + data |= (0x2 << 8); + break; + case 256: + data |= (0x3 << 8); + break; + case 512: + data |= (0x4 << 8); + break; + case 16: + data |= (0x7 << 8); + break; + default: + dev_err(dev, "unsupported bpfmd (%d)\n", bpfmd); + return -EINVAL; + } + + dev_dbg(dev, "ACKMD/BPFMD = %d/%d\n", ackmd, bpfmd); + + fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data); + udelay(10); + + return 0; +} + +static int fsi_clk_set_rate_external(struct device *dev, + struct fsi_priv *fsi) +{ + struct clk *xck = fsi->clock.xck; + struct clk *ick = fsi->clock.ick; + unsigned long rate = fsi->clock.rate; + unsigned long xrate; + int ackmd, bpfmd; + int ret = 0; + + /* check clock rate */ + xrate = clk_get_rate(xck); + if (xrate % rate) { + dev_err(dev, "unsupported clock rate\n"); + return -EINVAL; + } + + clk_set_parent(ick, xck); + clk_set_rate(ick, xrate); + + bpfmd = fsi->chan_num * 32; + ackmd = xrate / rate; + + dev_dbg(dev, "external/rate = %ld/%ld\n", xrate, rate); + + ret = fsi_clk_set_ackbpf(dev, fsi, ackmd, bpfmd); + if (ret < 0) + dev_err(dev, "%s failed", __func__); + + return ret; +} + +static int fsi_clk_set_rate_cpg(struct device *dev, + struct fsi_priv *fsi) +{ + struct clk *ick = fsi->clock.ick; + struct clk *div = fsi->clock.div; + unsigned long rate = fsi->clock.rate; + unsigned long target = 0; /* 12288000 or 11289600 */ + unsigned long actual, cout; + unsigned long diff, min; + unsigned long best_cout, best_act; + int adj; + int ackmd, bpfmd; + int ret = -EINVAL; + + if (!(12288000 % rate)) + target = 12288000; + if (!(11289600 % rate)) + target = 11289600; + if (!target) { + dev_err(dev, "unsupported rate\n"); + return ret; + } + + bpfmd = fsi->chan_num * 32; + ackmd = target / rate; + ret = fsi_clk_set_ackbpf(dev, fsi, ackmd, bpfmd); + if (ret < 0) { + dev_err(dev, "%s failed", __func__); + return ret; + } + + /* + * The clock flow is + * + * [CPG] = cout => [FSI_DIV] = audio => [FSI] => [codec] + * + * But, it needs to find best match of CPG and FSI_DIV + * combination, since it is difficult to generate correct + * frequency of audio clock from ick clock only. + * Because ick is created from its parent clock. + * + * target = rate x [512/256/128/64]fs + * cout = round(target x adjustment) + * actual = cout / adjustment (by FSI-DIV) ~= target + * audio = actual + */ + min = ~0; + best_cout = 0; + best_act = 0; + for (adj = 1; adj < 0xffff; adj++) { + + cout = target * adj; + if (cout > 100000000) /* max clock = 100MHz */ + break; + + /* cout/actual audio clock */ + cout = clk_round_rate(ick, cout); + actual = cout / adj; + + /* find best frequency */ + diff = abs(actual - target); + if (diff < min) { + min = diff; + best_cout = cout; + best_act = actual; + } + } + + ret = clk_set_rate(ick, best_cout); + if (ret < 0) { + dev_err(dev, "ick clock failed\n"); + return -EIO; + } + + ret = clk_set_rate(div, clk_round_rate(div, best_act)); + if (ret < 0) { + dev_err(dev, "div clock failed\n"); + return -EIO; + } + + dev_dbg(dev, "ick/div = %ld/%ld\n", + clk_get_rate(ick), clk_get_rate(div)); + + return ret; +} + +static void fsi_pointer_update(struct fsi_stream *io, int size) +{ + io->buff_sample_pos += size; + + if (io->buff_sample_pos >= + io->period_samples * (io->period_pos + 1)) { + struct snd_pcm_substream *substream = io->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + io->period_pos++; + + if (io->period_pos >= runtime->periods) { + io->buff_sample_pos = 0; + io->period_pos = 0; + } + + snd_pcm_period_elapsed(substream); + } +} + +/* + * pio data transfer handler + */ +static void fsi_pio_push16(struct fsi_priv *fsi, u8 *_buf, int samples) +{ + int i; + + if (fsi_is_enable_stream(fsi)) { + /* + * stream mode + * see + * fsi_pio_push_init() + */ + u32 *buf = (u32 *)_buf; + + for (i = 0; i < samples / 2; i++) + fsi_reg_write(fsi, DODT, buf[i]); + } else { + /* normal mode */ + u16 *buf = (u16 *)_buf; + + for (i = 0; i < samples; i++) + fsi_reg_write(fsi, DODT, ((u32)*(buf + i) << 8)); + } +} + +static void fsi_pio_pop16(struct fsi_priv *fsi, u8 *_buf, int samples) +{ + u16 *buf = (u16 *)_buf; + int i; + + for (i = 0; i < samples; i++) + *(buf + i) = (u16)(fsi_reg_read(fsi, DIDT) >> 8); +} + +static void fsi_pio_push32(struct fsi_priv *fsi, u8 *_buf, int samples) +{ + u32 *buf = (u32 *)_buf; + int i; + + for (i = 0; i < samples; i++) + fsi_reg_write(fsi, DODT, *(buf + i)); +} + +static void fsi_pio_pop32(struct fsi_priv *fsi, u8 *_buf, int samples) +{ + u32 *buf = (u32 *)_buf; + int i; + + for (i = 0; i < samples; i++) + *(buf + i) = fsi_reg_read(fsi, DIDT); +} + +static u8 *fsi_pio_get_area(struct fsi_priv *fsi, struct fsi_stream *io) +{ + struct snd_pcm_runtime *runtime = io->substream->runtime; + + return runtime->dma_area + + samples_to_bytes(runtime, io->buff_sample_pos); +} + +static int fsi_pio_transfer(struct fsi_priv *fsi, struct fsi_stream *io, + void (*run16)(struct fsi_priv *fsi, u8 *buf, int samples), + void (*run32)(struct fsi_priv *fsi, u8 *buf, int samples), + int samples) +{ + u8 *buf; + + if (!fsi_stream_is_working(fsi, io)) + return -EINVAL; + + buf = fsi_pio_get_area(fsi, io); + + switch (io->sample_width) { + case 2: + run16(fsi, buf, samples); + break; + case 4: + run32(fsi, buf, samples); + break; + default: + return -EINVAL; + } + + fsi_pointer_update(io, samples); + + return 0; +} + +static int fsi_pio_pop(struct fsi_priv *fsi, struct fsi_stream *io) +{ + int sample_residues; /* samples in FSI fifo */ + int sample_space; /* ALSA free samples space */ + int samples; + + sample_residues = fsi_get_current_fifo_samples(fsi, io); + sample_space = io->buff_sample_capa - io->buff_sample_pos; + + samples = min(sample_residues, sample_space); + + return fsi_pio_transfer(fsi, io, + fsi_pio_pop16, + fsi_pio_pop32, + samples); +} + +static int fsi_pio_push(struct fsi_priv *fsi, struct fsi_stream *io) +{ + int sample_residues; /* ALSA residue samples */ + int sample_space; /* FSI fifo free samples space */ + int samples; + + sample_residues = io->buff_sample_capa - io->buff_sample_pos; + sample_space = io->fifo_sample_capa - + fsi_get_current_fifo_samples(fsi, io); + + samples = min(sample_residues, sample_space); + + return fsi_pio_transfer(fsi, io, + fsi_pio_push16, + fsi_pio_push32, + samples); +} + +static int fsi_pio_start_stop(struct fsi_priv *fsi, struct fsi_stream *io, + int enable) +{ + struct fsi_master *master = fsi_get_master(fsi); + u32 clk = fsi_is_port_a(fsi) ? CRA : CRB; + + if (enable) + fsi_irq_enable(fsi, io); + else + fsi_irq_disable(fsi, io); + + if (fsi_is_clk_master(fsi)) + fsi_master_mask_set(master, CLK_RST, clk, (enable) ? clk : 0); + + return 0; +} + +static int fsi_pio_push_init(struct fsi_priv *fsi, struct fsi_stream *io) +{ + /* + * we can use 16bit stream mode + * when "playback" and "16bit data" + * and platform allows "stream mode" + * see + * fsi_pio_push16() + */ + if (fsi_is_enable_stream(fsi)) + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_16BITBUS_STREAM); + else + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_24BITBUS_BACK); + return 0; +} + +static int fsi_pio_pop_init(struct fsi_priv *fsi, struct fsi_stream *io) +{ + /* + * always 24bit bus, package back when "capture" + */ + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_24BITBUS_BACK); + return 0; +} + +static struct fsi_stream_handler fsi_pio_push_handler = { + .init = fsi_pio_push_init, + .transfer = fsi_pio_push, + .start_stop = fsi_pio_start_stop, +}; + +static struct fsi_stream_handler fsi_pio_pop_handler = { + .init = fsi_pio_pop_init, + .transfer = fsi_pio_pop, + .start_stop = fsi_pio_start_stop, +}; + +static irqreturn_t fsi_interrupt(int irq, void *data) +{ + struct fsi_master *master = data; + u32 int_st = fsi_irq_get_status(master); + + /* clear irq status */ + fsi_master_mask_set(master, SOFT_RST, IR, 0); + fsi_master_mask_set(master, SOFT_RST, IR, IR); + + if (int_st & AB_IO(1, AO_SHIFT)) + fsi_stream_transfer(&master->fsia.playback); + if (int_st & AB_IO(1, BO_SHIFT)) + fsi_stream_transfer(&master->fsib.playback); + if (int_st & AB_IO(1, AI_SHIFT)) + fsi_stream_transfer(&master->fsia.capture); + if (int_st & AB_IO(1, BI_SHIFT)) + fsi_stream_transfer(&master->fsib.capture); + + fsi_count_fifo_err(&master->fsia); + fsi_count_fifo_err(&master->fsib); + + fsi_irq_clear_status(&master->fsia); + fsi_irq_clear_status(&master->fsib); + + return IRQ_HANDLED; +} + +/* + * dma data transfer handler + */ +static int fsi_dma_init(struct fsi_priv *fsi, struct fsi_stream *io) +{ + /* + * 24bit data : 24bit bus / package in back + * 16bit data : 16bit bus / stream mode + */ + io->bus_option = BUSOP_SET(24, PACKAGE_24BITBUS_BACK) | + BUSOP_SET(16, PACKAGE_16BITBUS_STREAM); + + return 0; +} + +static void fsi_dma_complete(void *data) +{ + struct fsi_stream *io = (struct fsi_stream *)data; + struct fsi_priv *fsi = fsi_stream_to_priv(io); + + fsi_pointer_update(io, io->period_samples); + + fsi_count_fifo_err(fsi); +} + +static int fsi_dma_transfer(struct fsi_priv *fsi, struct fsi_stream *io) +{ + struct snd_soc_dai *dai = fsi_get_dai(io->substream); + struct snd_pcm_substream *substream = io->substream; + struct dma_async_tx_descriptor *desc; + int is_play = fsi_stream_is_play(fsi, io); + enum dma_transfer_direction dir; + int ret = -EIO; + + if (is_play) + dir = DMA_MEM_TO_DEV; + else + dir = DMA_DEV_TO_MEM; + + desc = dmaengine_prep_dma_cyclic(io->chan, + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(dai->dev, "dmaengine_prep_dma_cyclic() fail\n"); + goto fsi_dma_transfer_err; + } + + desc->callback = fsi_dma_complete; + desc->callback_param = io; + + if (dmaengine_submit(desc) < 0) { + dev_err(dai->dev, "tx_submit() fail\n"); + goto fsi_dma_transfer_err; + } + + dma_async_issue_pending(io->chan); + + /* + * FIXME + * + * In DMAEngine case, codec and FSI cannot be started simultaneously + * since FSI is using the scheduler work queue. + * Therefore, in capture case, probably FSI FIFO will have got + * overflow error in this point. + * in that case, DMA cannot start transfer until error was cleared. + */ + if (!is_play) { + if (ERR_OVER & fsi_reg_read(fsi, DIFF_ST)) { + fsi_reg_mask_set(fsi, DIFF_CTL, FIFO_CLR, FIFO_CLR); + fsi_reg_write(fsi, DIFF_ST, 0); + } + } + + ret = 0; + +fsi_dma_transfer_err: + return ret; +} + +static int fsi_dma_push_start_stop(struct fsi_priv *fsi, struct fsi_stream *io, + int start) +{ + struct fsi_master *master = fsi_get_master(fsi); + u32 clk = fsi_is_port_a(fsi) ? CRA : CRB; + u32 enable = start ? DMA_ON : 0; + + fsi_reg_mask_set(fsi, OUT_DMAC, DMA_ON, enable); + + dmaengine_terminate_all(io->chan); + + if (fsi_is_clk_master(fsi)) + fsi_master_mask_set(master, CLK_RST, clk, (enable) ? clk : 0); + + return 0; +} + +static int fsi_dma_probe(struct fsi_priv *fsi, struct fsi_stream *io, struct device *dev) +{ + int is_play = fsi_stream_is_play(fsi, io); + +#ifdef CONFIG_SUPERH + dma_cap_mask_t mask; + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + io->chan = dma_request_channel(mask, shdma_chan_filter, + (void *)io->dma_id); +#else + io->chan = dma_request_slave_channel(dev, is_play ? "tx" : "rx"); +#endif + if (io->chan) { + struct dma_slave_config cfg = {}; + int ret; + + if (is_play) { + cfg.dst_addr = fsi->phys + REG_DODT; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.direction = DMA_MEM_TO_DEV; + } else { + cfg.src_addr = fsi->phys + REG_DIDT; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.direction = DMA_DEV_TO_MEM; + } + + ret = dmaengine_slave_config(io->chan, &cfg); + if (ret < 0) { + dma_release_channel(io->chan); + io->chan = NULL; + } + } + + if (!io->chan) { + + /* switch to PIO handler */ + if (is_play) + fsi->playback.handler = &fsi_pio_push_handler; + else + fsi->capture.handler = &fsi_pio_pop_handler; + + dev_info(dev, "switch handler (dma => pio)\n"); + + /* probe again */ + return fsi_stream_probe(fsi, dev); + } + + return 0; +} + +static int fsi_dma_remove(struct fsi_priv *fsi, struct fsi_stream *io) +{ + fsi_stream_stop(fsi, io); + + if (io->chan) + dma_release_channel(io->chan); + + io->chan = NULL; + return 0; +} + +static struct fsi_stream_handler fsi_dma_push_handler = { + .init = fsi_dma_init, + .probe = fsi_dma_probe, + .transfer = fsi_dma_transfer, + .remove = fsi_dma_remove, + .start_stop = fsi_dma_push_start_stop, +}; + +/* + * dai ops + */ +static void fsi_fifo_init(struct fsi_priv *fsi, + struct fsi_stream *io, + struct device *dev) +{ + struct fsi_master *master = fsi_get_master(fsi); + int is_play = fsi_stream_is_play(fsi, io); + u32 shift, i; + int frame_capa; + + /* get on-chip RAM capacity */ + shift = fsi_master_read(master, FIFO_SZ); + shift >>= fsi_get_port_shift(fsi, io); + shift &= FIFO_SZ_MASK; + frame_capa = 256 << shift; + dev_dbg(dev, "fifo = %d words\n", frame_capa); + + /* + * The maximum number of sample data varies depending + * on the number of channels selected for the format. + * + * FIFOs are used in 4-channel units in 3-channel mode + * and in 8-channel units in 5- to 7-channel mode + * meaning that more FIFOs than the required size of DPRAM + * are used. + * + * ex) if 256 words of DP-RAM is connected + * 1 channel: 256 (256 x 1 = 256) + * 2 channels: 128 (128 x 2 = 256) + * 3 channels: 64 ( 64 x 3 = 192) + * 4 channels: 64 ( 64 x 4 = 256) + * 5 channels: 32 ( 32 x 5 = 160) + * 6 channels: 32 ( 32 x 6 = 192) + * 7 channels: 32 ( 32 x 7 = 224) + * 8 channels: 32 ( 32 x 8 = 256) + */ + for (i = 1; i < fsi->chan_num; i <<= 1) + frame_capa >>= 1; + dev_dbg(dev, "%d channel %d store\n", + fsi->chan_num, frame_capa); + + io->fifo_sample_capa = fsi_frame2sample(fsi, frame_capa); + + /* + * set interrupt generation factor + * clear FIFO + */ + if (is_play) { + fsi_reg_write(fsi, DOFF_CTL, IRQ_HALF); + fsi_reg_mask_set(fsi, DOFF_CTL, FIFO_CLR, FIFO_CLR); + } else { + fsi_reg_write(fsi, DIFF_CTL, IRQ_HALF); + fsi_reg_mask_set(fsi, DIFF_CTL, FIFO_CLR, FIFO_CLR); + } +} + +static int fsi_hw_startup(struct fsi_priv *fsi, + struct fsi_stream *io, + struct device *dev) +{ + u32 data = 0; + + /* clock setting */ + if (fsi_is_clk_master(fsi)) + data = DIMD | DOMD; + + fsi_reg_mask_set(fsi, CKG1, (DIMD | DOMD), data); + + /* clock inversion (CKG2) */ + data = 0; + if (fsi->bit_clk_inv) + data |= (1 << 0); + if (fsi->lr_clk_inv) + data |= (1 << 4); + if (fsi_is_clk_master(fsi)) + data <<= 8; + fsi_reg_write(fsi, CKG2, data); + + /* spdif ? */ + if (fsi_is_spdif(fsi)) { + fsi_spdif_clk_ctrl(fsi, 1); + fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD); + } + + /* + * get bus settings + */ + data = 0; + switch (io->sample_width) { + case 2: + data = BUSOP_GET(16, io->bus_option); + break; + case 4: + data = BUSOP_GET(24, io->bus_option); + break; + } + fsi_format_bus_setup(fsi, io, data, dev); + + /* irq clear */ + fsi_irq_disable(fsi, io); + fsi_irq_clear_status(fsi); + + /* fifo init */ + fsi_fifo_init(fsi, io, dev); + + /* start master clock */ + if (fsi_is_clk_master(fsi)) + return fsi_clk_enable(dev, fsi); + + return 0; +} + +static int fsi_hw_shutdown(struct fsi_priv *fsi, + struct device *dev) +{ + /* stop master clock */ + if (fsi_is_clk_master(fsi)) + return fsi_clk_disable(dev, fsi); + + return 0; +} + +static int fsi_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get_priv(substream); + + fsi_clk_invalid(fsi); + + return 0; +} + +static void fsi_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get_priv(substream); + + fsi_clk_invalid(fsi); +} + +static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get_priv(substream); + struct fsi_stream *io = fsi_stream_get(fsi, substream); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + fsi_stream_init(fsi, io, substream); + if (!ret) + ret = fsi_hw_startup(fsi, io, dai->dev); + if (!ret) + ret = fsi_stream_start(fsi, io); + if (!ret) + ret = fsi_stream_transfer(io); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (!ret) + ret = fsi_hw_shutdown(fsi, dai->dev); + fsi_stream_stop(fsi, io); + fsi_stream_quit(fsi, io); + break; + } + + return ret; +} + +static int fsi_set_fmt_dai(struct fsi_priv *fsi, unsigned int fmt) +{ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fsi->fmt = CR_I2S; + fsi->chan_num = 2; + break; + case SND_SOC_DAIFMT_LEFT_J: + fsi->fmt = CR_PCM; + fsi->chan_num = 2; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsi_set_fmt_spdif(struct fsi_priv *fsi) +{ + struct fsi_master *master = fsi_get_master(fsi); + + if (fsi_version(master) < 2) + return -EINVAL; + + fsi->fmt = CR_DTMD_SPDIF_PCM | CR_PCM; + fsi->chan_num = 2; + + return 0; +} + +static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsi_priv *fsi = fsi_get_priv_frm_dai(dai); + int ret; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + fsi->clk_master = 1; /* codec is slave, cpu is master */ + break; + default: + return -EINVAL; + } + + /* set clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + fsi->bit_clk_inv = 0; + fsi->lr_clk_inv = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + fsi->bit_clk_inv = 1; + fsi->lr_clk_inv = 0; + break; + case SND_SOC_DAIFMT_IB_IF: + fsi->bit_clk_inv = 1; + fsi->lr_clk_inv = 1; + break; + case SND_SOC_DAIFMT_NB_NF: + default: + fsi->bit_clk_inv = 0; + fsi->lr_clk_inv = 0; + break; + } + + if (fsi_is_clk_master(fsi)) { + if (fsi->clk_cpg) + fsi_clk_init(dai->dev, fsi, 0, 1, 1, + fsi_clk_set_rate_cpg); + else + fsi_clk_init(dai->dev, fsi, 1, 1, 0, + fsi_clk_set_rate_external); + } + + /* set format */ + if (fsi_is_spdif(fsi)) + ret = fsi_set_fmt_spdif(fsi); + else + ret = fsi_set_fmt_dai(fsi, fmt & SND_SOC_DAIFMT_FORMAT_MASK); + + return ret; +} + +static int fsi_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get_priv(substream); + + if (fsi_is_clk_master(fsi)) + fsi_clk_valid(fsi, params_rate(params)); + + return 0; +} + +static const struct snd_soc_dai_ops fsi_dai_ops = { + .startup = fsi_dai_startup, + .shutdown = fsi_dai_shutdown, + .trigger = fsi_dai_trigger, + .set_fmt = fsi_dai_set_fmt, + .hw_params = fsi_dai_hw_params, +}; + +/* + * pcm ops + */ + +static const struct snd_pcm_hardware fsi_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 256, +}; + +static int fsi_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + + return ret; +} + +static int fsi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int fsi_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream) +{ + struct fsi_priv *fsi = fsi_get_priv(substream); + struct fsi_stream *io = fsi_stream_get(fsi, substream); + + return fsi_sample2frame(fsi, io->buff_sample_pos); +} + +static const struct snd_pcm_ops fsi_pcm_ops = { + .open = fsi_pcm_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsi_hw_params, + .hw_free = fsi_hw_free, + .pointer = fsi_pointer, +}; + +/* + * snd_soc_component + */ + +#define PREALLOC_BUFFER (32 * 1024) +#define PREALLOC_BUFFER_MAX (32 * 1024) + +static int fsi_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + return snd_pcm_lib_preallocate_pages_for_all( + rtd->pcm, + SNDRV_DMA_TYPE_DEV, + rtd->card->snd_card->dev, + PREALLOC_BUFFER, PREALLOC_BUFFER_MAX); +} + +/* + * alsa struct + */ + +static struct snd_soc_dai_driver fsi_soc_dai[] = { + { + .name = "fsia-dai", + .playback = { + .rates = FSI_RATES, + .formats = FSI_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = FSI_RATES, + .formats = FSI_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &fsi_dai_ops, + }, + { + .name = "fsib-dai", + .playback = { + .rates = FSI_RATES, + .formats = FSI_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = FSI_RATES, + .formats = FSI_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &fsi_dai_ops, + }, +}; + +static const struct snd_soc_component_driver fsi_soc_component = { + .name = "fsi", + .ops = &fsi_pcm_ops, + .pcm_new = fsi_pcm_new, +}; + +/* + * platform function + */ +static void fsi_of_parse(char *name, + struct device_node *np, + struct sh_fsi_port_info *info, + struct device *dev) +{ + int i; + char prop[128]; + unsigned long flags = 0; + struct { + char *name; + unsigned int val; + } of_parse_property[] = { + { "spdif-connection", SH_FSI_FMT_SPDIF }, + { "stream-mode-support", SH_FSI_ENABLE_STREAM_MODE }, + { "use-internal-clock", SH_FSI_CLK_CPG }, + }; + + for (i = 0; i < ARRAY_SIZE(of_parse_property); i++) { + sprintf(prop, "%s,%s", name, of_parse_property[i].name); + if (of_get_property(np, prop, NULL)) + flags |= of_parse_property[i].val; + } + info->flags = flags; + + dev_dbg(dev, "%s flags : %lx\n", name, info->flags); +} + +static void fsi_port_info_init(struct fsi_priv *fsi, + struct sh_fsi_port_info *info) +{ + if (info->flags & SH_FSI_FMT_SPDIF) + fsi->spdif = 1; + + if (info->flags & SH_FSI_CLK_CPG) + fsi->clk_cpg = 1; + + if (info->flags & SH_FSI_ENABLE_STREAM_MODE) + fsi->enable_stream = 1; +} + +static void fsi_handler_init(struct fsi_priv *fsi, + struct sh_fsi_port_info *info) +{ + fsi->playback.handler = &fsi_pio_push_handler; /* default PIO */ + fsi->playback.priv = fsi; + fsi->capture.handler = &fsi_pio_pop_handler; /* default PIO */ + fsi->capture.priv = fsi; + + if (info->tx_id) { + fsi->playback.dma_id = info->tx_id; + fsi->playback.handler = &fsi_dma_push_handler; + } +} + +static const struct fsi_core fsi1_core = { + .ver = 1, + + /* Interrupt */ + .int_st = INT_ST, + .iemsk = IEMSK, + .imsk = IMSK, +}; + +static const struct fsi_core fsi2_core = { + .ver = 2, + + /* Interrupt */ + .int_st = CPU_INT_ST, + .iemsk = CPU_IEMSK, + .imsk = CPU_IMSK, + .a_mclk = A_MST_CTLR, + .b_mclk = B_MST_CTLR, +}; + +static const struct of_device_id fsi_of_match[] = { + { .compatible = "renesas,sh_fsi", .data = &fsi1_core}, + { .compatible = "renesas,sh_fsi2", .data = &fsi2_core}, + {}, +}; +MODULE_DEVICE_TABLE(of, fsi_of_match); + +static const struct platform_device_id fsi_id_table[] = { + { "sh_fsi", (kernel_ulong_t)&fsi1_core }, + {}, +}; +MODULE_DEVICE_TABLE(platform, fsi_id_table); + +static int fsi_probe(struct platform_device *pdev) +{ + struct fsi_master *master; + struct device_node *np = pdev->dev.of_node; + struct sh_fsi_platform_info info; + const struct fsi_core *core; + struct fsi_priv *fsi; + struct resource *res; + unsigned int irq; + int ret; + + memset(&info, 0, sizeof(info)); + + core = NULL; + if (np) { + core = of_device_get_match_data(&pdev->dev); + fsi_of_parse("fsia", np, &info.port_a, &pdev->dev); + fsi_of_parse("fsib", np, &info.port_b, &pdev->dev); + } else { + const struct platform_device_id *id_entry = pdev->id_entry; + if (id_entry) + core = (struct fsi_core *)id_entry->driver_data; + + if (pdev->dev.platform_data) + memcpy(&info, pdev->dev.platform_data, sizeof(info)); + } + + if (!core) { + dev_err(&pdev->dev, "unknown fsi device\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || (int)irq <= 0) { + dev_err(&pdev->dev, "Not enough FSI platform resources.\n"); + return -ENODEV; + } + + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + master->base = devm_ioremap_nocache(&pdev->dev, + res->start, resource_size(res)); + if (!master->base) { + dev_err(&pdev->dev, "Unable to ioremap FSI registers.\n"); + return -ENXIO; + } + + /* master setting */ + master->core = core; + spin_lock_init(&master->lock); + + /* FSI A setting */ + fsi = &master->fsia; + fsi->base = master->base; + fsi->phys = res->start; + fsi->master = master; + fsi_port_info_init(fsi, &info.port_a); + fsi_handler_init(fsi, &info.port_a); + ret = fsi_stream_probe(fsi, &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "FSIA stream probe failed\n"); + return ret; + } + + /* FSI B setting */ + fsi = &master->fsib; + fsi->base = master->base + 0x40; + fsi->phys = res->start + 0x40; + fsi->master = master; + fsi_port_info_init(fsi, &info.port_b); + fsi_handler_init(fsi, &info.port_b); + ret = fsi_stream_probe(fsi, &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "FSIB stream probe failed\n"); + goto exit_fsia; + } + + pm_runtime_enable(&pdev->dev); + dev_set_drvdata(&pdev->dev, master); + + ret = devm_request_irq(&pdev->dev, irq, &fsi_interrupt, 0, + dev_name(&pdev->dev), master); + if (ret) { + dev_err(&pdev->dev, "irq request err\n"); + goto exit_fsib; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &fsi_soc_component, + fsi_soc_dai, ARRAY_SIZE(fsi_soc_dai)); + if (ret < 0) { + dev_err(&pdev->dev, "cannot snd component register\n"); + goto exit_fsib; + } + + return ret; + +exit_fsib: + pm_runtime_disable(&pdev->dev); + fsi_stream_remove(&master->fsib); +exit_fsia: + fsi_stream_remove(&master->fsia); + + return ret; +} + +static int fsi_remove(struct platform_device *pdev) +{ + struct fsi_master *master; + + master = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + fsi_stream_remove(&master->fsia); + fsi_stream_remove(&master->fsib); + + return 0; +} + +static void __fsi_suspend(struct fsi_priv *fsi, + struct fsi_stream *io, + struct device *dev) +{ + if (!fsi_stream_is_working(fsi, io)) + return; + + fsi_stream_stop(fsi, io); + fsi_hw_shutdown(fsi, dev); +} + +static void __fsi_resume(struct fsi_priv *fsi, + struct fsi_stream *io, + struct device *dev) +{ + if (!fsi_stream_is_working(fsi, io)) + return; + + fsi_hw_startup(fsi, io, dev); + fsi_stream_start(fsi, io); +} + +static int fsi_suspend(struct device *dev) +{ + struct fsi_master *master = dev_get_drvdata(dev); + struct fsi_priv *fsia = &master->fsia; + struct fsi_priv *fsib = &master->fsib; + + __fsi_suspend(fsia, &fsia->playback, dev); + __fsi_suspend(fsia, &fsia->capture, dev); + + __fsi_suspend(fsib, &fsib->playback, dev); + __fsi_suspend(fsib, &fsib->capture, dev); + + return 0; +} + +static int fsi_resume(struct device *dev) +{ + struct fsi_master *master = dev_get_drvdata(dev); + struct fsi_priv *fsia = &master->fsia; + struct fsi_priv *fsib = &master->fsib; + + __fsi_resume(fsia, &fsia->playback, dev); + __fsi_resume(fsia, &fsia->capture, dev); + + __fsi_resume(fsib, &fsib->playback, dev); + __fsi_resume(fsib, &fsib->capture, dev); + + return 0; +} + +static const struct dev_pm_ops fsi_pm_ops = { + .suspend = fsi_suspend, + .resume = fsi_resume, +}; + +static struct platform_driver fsi_driver = { + .driver = { + .name = "fsi-pcm-audio", + .pm = &fsi_pm_ops, + .of_match_table = fsi_of_match, + }, + .probe = fsi_probe, + .remove = fsi_remove, + .id_table = fsi_id_table, +}; + +module_platform_driver(fsi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SuperH onchip FSI audio driver"); +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_ALIAS("platform:fsi-pcm-audio"); diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c new file mode 100644 index 000000000..c2b496398 --- /dev/null +++ b/sound/soc/sh/hac.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Hitachi Audio Controller (AC97) support for SH7760/SH7780 +// +// Copyright (c) 2007 Manuel Lauss +// +// dont forget to set IPSEL/OMSEL register bits (in your board code) to +// enable HAC output pins! + +/* BIG FAT FIXME: although the SH7760 has 2 independent AC97 units, only + * the FIRST can be used since ASoC does not pass any information to the + * ac97_read/write() functions regarding WHICH unit to use. You'll have + * to edit the code a bit to use the other AC97 unit. --mlau + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* regs and bits */ +#define HACCR 0x08 +#define HACCSAR 0x20 +#define HACCSDR 0x24 +#define HACPCML 0x28 +#define HACPCMR 0x2C +#define HACTIER 0x50 +#define HACTSR 0x54 +#define HACRIER 0x58 +#define HACRSR 0x5C +#define HACACR 0x60 + +#define CR_CR (1 << 15) /* "codec-ready" indicator */ +#define CR_CDRT (1 << 11) /* cold reset */ +#define CR_WMRT (1 << 10) /* warm reset */ +#define CR_B9 (1 << 9) /* the mysterious "bit 9" */ +#define CR_ST (1 << 5) /* AC97 link start bit */ + +#define CSAR_RD (1 << 19) /* AC97 data read bit */ +#define CSAR_WR (0) + +#define TSR_CMDAMT (1 << 31) +#define TSR_CMDDMT (1 << 30) + +#define RSR_STARY (1 << 22) +#define RSR_STDRY (1 << 21) + +#define ACR_DMARX16 (1 << 30) +#define ACR_DMATX16 (1 << 29) +#define ACR_TX12ATOM (1 << 26) +#define ACR_DMARX20 ((1 << 24) | (1 << 22)) +#define ACR_DMATX20 ((1 << 23) | (1 << 21)) + +#define CSDR_SHIFT 4 +#define CSDR_MASK (0xffff << CSDR_SHIFT) +#define CSAR_SHIFT 12 +#define CSAR_MASK (0x7f << CSAR_SHIFT) + +#define AC97_WRITE_RETRY 1 +#define AC97_READ_RETRY 5 + +/* manual-suggested AC97 codec access timeouts (us) */ +#define TMO_E1 500 /* 21 < E1 < 1000 */ +#define TMO_E2 13 /* 13 < E2 */ +#define TMO_E3 21 /* 21 < E3 */ +#define TMO_E4 500 /* 21 < E4 < 1000 */ + +struct hac_priv { + unsigned long mmio; /* HAC base address */ +} hac_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE240000, + }, + { + .mmio = 0xFE250000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE40000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +#define HACREG(reg) (*(unsigned long *)(hac->mmio + (reg))) + +/* + * AC97 read/write flow as outlined in the SH7760 manual (pages 903-906) + */ +static int hac_get_codec_data(struct hac_priv *hac, unsigned short r, + unsigned short *v) +{ + unsigned int to1, to2, i; + unsigned short adr; + + for (i = AC97_READ_RETRY; i; i--) { + *v = 0; + /* wait for HAC to receive something from the codec */ + for (to1 = TMO_E4; + to1 && !(HACREG(HACRSR) & RSR_STARY); + --to1) + udelay(1); + for (to2 = TMO_E4; + to2 && !(HACREG(HACRSR) & RSR_STDRY); + --to2) + udelay(1); + + if (!to1 && !to2) + return 0; /* codec comm is down */ + + adr = ((HACREG(HACCSAR) & CSAR_MASK) >> CSAR_SHIFT); + *v = ((HACREG(HACCSDR) & CSDR_MASK) >> CSDR_SHIFT); + + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + + if (r == adr) + break; + + /* manual says: wait at least 21 usec before retrying */ + udelay(21); + } + HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); + return i; +} + +static unsigned short hac_read_codec_aux(struct hac_priv *hac, + unsigned short reg) +{ + unsigned short val; + unsigned int i, to; + + for (i = AC97_READ_RETRY; i; i--) { + /* send_read_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDAMT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) | CSAR_RD; + local_irq_enable(); + + for (to = TMO_E3; + to && !(HACREG(HACTSR) & TSR_CMDAMT); + --to) + udelay(1); + + HACREG(HACTSR) &= ~TSR_CMDAMT; + val = 0; + if (hac_get_codec_data(hac, reg, &val) != 0) + break; + } + + return i ? val : ~0; +} + +static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int i, to; + /* write_codec_aux */ + for (i = AC97_WRITE_RETRY; i; i--) { + /* send_write_request */ + local_irq_disable(); + HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT); + HACREG(HACCSDR) = (val << CSDR_SHIFT); + HACREG(HACCSAR) = (reg << CSAR_SHIFT) & (~CSAR_RD); + local_irq_enable(); + + /* poll-wait for CMDAMT and CMDDMT */ + for (to = TMO_E1; + to && !(HACREG(HACTSR) & (TSR_CMDAMT|TSR_CMDDMT)); + --to) + udelay(1); + + HACREG(HACTSR) &= ~(TSR_CMDAMT | TSR_CMDDMT); + if (to) + break; + /* timeout, try again */ + } +} + +static unsigned short hac_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + return hac_read_codec_aux(hac, reg); +} + +static void hac_ac97_warmrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac = &hac_cpu_data[unit_id]; + unsigned int tmo; + + HACREG(HACCR) = CR_WMRT | CR_ST | CR_B9; + msleep(10); + HACREG(HACCR) = CR_ST | CR_B9; + for (tmo = 1000; (tmo > 0) && !(HACREG(HACCR) & CR_CR); tmo--) + udelay(1); + + if (!tmo) + printk(KERN_INFO "hac: reset: AC97 link down!\n"); + /* settings this bit lets us have a conversation with codec */ + HACREG(HACACR) |= ACR_TX12ATOM; +} + +static void hac_ac97_coldrst(struct snd_ac97 *ac97) +{ + int unit_id = 0 /* ac97->private_data */; + struct hac_priv *hac; + hac = &hac_cpu_data[unit_id]; + + HACREG(HACCR) = 0; + HACREG(HACCR) = CR_CDRT | CR_ST | CR_B9; + msleep(10); + hac_ac97_warmrst(ac97); +} + +static struct snd_ac97_bus_ops hac_ac97_ops = { + .read = hac_ac97_read, + .write = hac_ac97_write, + .reset = hac_ac97_coldrst, + .warm_reset = hac_ac97_warmrst, +}; + +static int hac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hac_priv *hac = &hac_cpu_data[dai->id]; + int d = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + switch (params->msbits) { + case 16: + HACREG(HACACR) |= d ? ACR_DMARX16 : ACR_DMATX16; + HACREG(HACACR) &= d ? ~ACR_DMARX20 : ~ACR_DMATX20; + break; + case 20: + HACREG(HACACR) &= d ? ~ACR_DMARX16 : ~ACR_DMATX16; + HACREG(HACACR) |= d ? ACR_DMARX20 : ACR_DMATX20; + break; + default: + pr_debug("hac: invalid depth %d bit\n", params->msbits); + return -EINVAL; + break; + } + + return 0; +} + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AC97_FMTS \ + SNDRV_PCM_FMTBIT_S16_LE + +static const struct snd_soc_dai_ops hac_dai_ops = { + .hw_params = hac_hw_params, +}; + +static struct snd_soc_dai_driver sh4_hac_dai[] = { +{ + .name = "hac-dai.0", + .bus_control = true, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &hac_dai_ops, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "hac-dai.1", + .id = 1, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &hac_dai_ops, + +}, +#endif +}; + +static const struct snd_soc_component_driver sh4_hac_component = { + .name = "sh4-hac", +}; + +static int hac_soc_platform_probe(struct platform_device *pdev) +{ + int ret; + + ret = snd_soc_set_ac97_ops(&hac_ac97_ops); + if (ret != 0) + return ret; + + return snd_soc_register_component(&pdev->dev, &sh4_hac_component, + sh4_hac_dai, ARRAY_SIZE(sh4_hac_dai)); +} + +static int hac_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + snd_soc_set_ac97_ops(NULL); + return 0; +} + +static struct platform_driver hac_pcm_driver = { + .driver = { + .name = "hac-pcm-audio", + }, + + .probe = hac_soc_platform_probe, + .remove = hac_soc_platform_remove, +}; + +module_platform_driver(hac_pcm_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SuperH onchip HAC (AC97) audio driver"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/soc/sh/migor.c b/sound/soc/sh/migor.c new file mode 100644 index 000000000..8739c9f60 --- /dev/null +++ b/sound/soc/sh/migor.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ALSA SoC driver for Migo-R +// +// Copyright (C) 2009-2010 Guennadi Liakhovetski + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include "../codecs/wm8978.h" +#include "siu.h" + +/* Default 8000Hz sampling frequency */ +static unsigned long codec_freq = 8000 * 512; + +static unsigned int use_count; + +/* External clock, sourced from the codec at the SIUMCKB pin */ +static unsigned long siumckb_recalc(struct clk *clk) +{ + return codec_freq; +} + +static struct sh_clk_ops siumckb_clk_ops = { + .recalc = siumckb_recalc, +}; + +static struct clk siumckb_clk = { + .ops = &siumckb_clk_ops, + .rate = 0, /* initialised at run-time */ +}; + +static struct clk_lookup *siumckb_lookup; + +static int migor_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + unsigned int rate = params_rate(params); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512); + if (ret < 0) + return ret; + + codec_freq = rate * 512; + /* + * This propagates the parent frequency change to children and + * recalculates the frequency table + */ + clk_set_rate(&siumckb_clk, codec_freq); + dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq); + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SIU_CLKB_EXT, + codec_freq / 2, SND_SOC_CLOCK_IN); + + if (!ret) + use_count++; + + return ret; +} + +static int migor_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + if (use_count) { + use_count--; + + if (!use_count) + snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0, + SND_SOC_CLOCK_IN); + } else { + dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n"); + } + + return 0; +} + +static const struct snd_soc_ops migor_dai_ops = { + .hw_params = migor_hw_params, + .hw_free = migor_hw_free, +}; + +static const struct snd_soc_dapm_widget migor_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Onboard Microphone", NULL), + SND_SOC_DAPM_MIC("External Microphone", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */ + { "Headphone", NULL, "OUT4 VMID" }, + { "OUT4 VMID", NULL, "LHP" }, + { "OUT4 VMID", NULL, "RHP" }, + + /* On-board microphone */ + { "RMICN", NULL, "Mic Bias" }, + { "RMICP", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "Onboard Microphone" }, + + /* External microphone */ + { "LMICN", NULL, "Mic Bias" }, + { "LMICP", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "External Microphone" }, +}; + +/* migor digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link migor_dai = { + .name = "wm8978", + .stream_name = "WM8978", + .cpu_dai_name = "siu-pcm-audio", + .codec_dai_name = "wm8978-hifi", + .platform_name = "siu-pcm-audio", + .codec_name = "wm8978.0-001a", + .dai_fmt = SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &migor_dai_ops, +}; + +/* migor audio machine driver */ +static struct snd_soc_card snd_soc_migor = { + .name = "Migo-R", + .owner = THIS_MODULE, + .dai_link = &migor_dai, + .num_links = 1, + + .dapm_widgets = migor_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(migor_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct platform_device *migor_snd_device; + +static int __init migor_init(void) +{ + int ret; + + ret = clk_register(&siumckb_clk); + if (ret < 0) + return ret; + + siumckb_lookup = clkdev_create(&siumckb_clk, "siumckb_clk", NULL); + if (!siumckb_lookup) { + ret = -ENOMEM; + goto eclkdevalloc; + } + + /* Port number used on this machine: port B */ + migor_snd_device = platform_device_alloc("soc-audio", 1); + if (!migor_snd_device) { + ret = -ENOMEM; + goto epdevalloc; + } + + platform_set_drvdata(migor_snd_device, &snd_soc_migor); + + ret = platform_device_add(migor_snd_device); + if (ret) + goto epdevadd; + + return 0; + +epdevadd: + platform_device_put(migor_snd_device); +epdevalloc: + clkdev_drop(siumckb_lookup); +eclkdevalloc: + clk_unregister(&siumckb_clk); + return ret; +} + +static void __exit migor_exit(void) +{ + clkdev_drop(siumckb_lookup); + clk_unregister(&siumckb_clk); + platform_device_unregister(migor_snd_device); +} + +module_init(migor_init); +module_exit(migor_exit); + +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_DESCRIPTION("ALSA SoC Migor"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sh/rcar/Makefile b/sound/soc/sh/rcar/Makefile new file mode 100644 index 000000000..5d1ff8ef2 --- /dev/null +++ b/sound/soc/sh/rcar/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-rcar-objs := core.o gen.o dma.o adg.o ssi.o ssiu.o src.o ctu.o mix.o dvc.o cmd.o +obj-$(CONFIG_SND_SOC_RCAR) += snd-soc-rcar.o diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c new file mode 100644 index 000000000..dc0826003 --- /dev/null +++ b/sound/soc/sh/rcar/adg.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Helper routines for R-Car sound ADG. +// +// Copyright (C) 2013 Kuninori Morimoto + +#include +#include "rsnd.h" + +#define CLKA 0 +#define CLKB 1 +#define CLKC 2 +#define CLKI 3 +#define CLKMAX 4 + +#define CLKOUT 0 +#define CLKOUT1 1 +#define CLKOUT2 2 +#define CLKOUT3 3 +#define CLKOUTMAX 4 + +#define BRRx_MASK(x) (0x3FF & x) + +static struct rsnd_mod_ops adg_ops = { + .name = "adg", +}; + +struct rsnd_adg { + struct clk *clk[CLKMAX]; + struct clk *clkout[CLKOUTMAX]; + struct clk_onecell_data onecell; + struct rsnd_mod mod; + int clk_rate[CLKMAX]; + u32 flags; + u32 ckr; + u32 rbga; + u32 rbgb; + + int rbga_rate_for_441khz; /* RBGA */ + int rbgb_rate_for_48khz; /* RBGB */ +}; + +#define LRCLK_ASYNC (1 << 0) +#define AUDIO_OUT_48 (1 << 1) + +#define for_each_rsnd_clk(pos, adg, i) \ + for (i = 0; \ + (i < CLKMAX) && \ + ((pos) = adg->clk[i]); \ + i++) +#define for_each_rsnd_clkout(pos, adg, i) \ + for (i = 0; \ + (i < CLKOUTMAX) && \ + ((pos) = adg->clkout[i]); \ + i++) +#define rsnd_priv_to_adg(priv) ((struct rsnd_adg *)(priv)->adg) + +static const char * const clk_name[] = { + [CLKA] = "clk_a", + [CLKB] = "clk_b", + [CLKC] = "clk_c", + [CLKI] = "clk_i", +}; + +static u32 rsnd_adg_calculate_rbgx(unsigned long div) +{ + int i, ratio; + + if (!div) + return 0; + + for (i = 3; i >= 0; i--) { + ratio = 2 << (i * 2); + if (0 == (div % ratio)) + return (u32)((i << 8) | ((div / ratio) - 1)); + } + + return ~0; +} + +static u32 rsnd_adg_ssi_ws_timing_gen2(struct rsnd_dai_stream *io) +{ + struct rsnd_mod *ssi_mod = rsnd_io_to_mod_ssi(io); + int id = rsnd_mod_id(ssi_mod); + int ws = id; + + if (rsnd_ssi_is_pin_sharing(io)) { + switch (id) { + case 1: + case 2: + ws = 0; + break; + case 4: + ws = 3; + break; + case 8: + ws = 7; + break; + } + } + + return (0x6 + ws) << 8; +} + +static void __rsnd_adg_get_timesel_ratio(struct rsnd_priv *priv, + struct rsnd_dai_stream *io, + unsigned int target_rate, + unsigned int *target_val, + unsigned int *target_en) +{ + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); + struct device *dev = rsnd_priv_to_dev(priv); + int idx, sel, div, step; + unsigned int val, en; + unsigned int min, diff; + unsigned int sel_rate[] = { + adg->clk_rate[CLKA], /* 0000: CLKA */ + adg->clk_rate[CLKB], /* 0001: CLKB */ + adg->clk_rate[CLKC], /* 0010: CLKC */ + adg->rbga_rate_for_441khz, /* 0011: RBGA */ + adg->rbgb_rate_for_48khz, /* 0100: RBGB */ + }; + + min = ~0; + val = 0; + en = 0; + for (sel = 0; sel < ARRAY_SIZE(sel_rate); sel++) { + idx = 0; + step = 2; + + if (!sel_rate[sel]) + continue; + + for (div = 2; div <= 98304; div += step) { + diff = abs(target_rate - sel_rate[sel] / div); + if (min > diff) { + val = (sel << 8) | idx; + min = diff; + en = 1 << (sel + 1); /* fixme */ + } + + /* + * step of 0_0000 / 0_0001 / 0_1101 + * are out of order + */ + if ((idx > 2) && (idx % 2)) + step *= 2; + if (idx == 0x1c) { + div += step; + step *= 2; + } + idx++; + } + } + + if (min == ~0) { + dev_err(dev, "no Input clock\n"); + return; + } + + *target_val = val; + if (target_en) + *target_en = en; +} + +static void rsnd_adg_get_timesel_ratio(struct rsnd_priv *priv, + struct rsnd_dai_stream *io, + unsigned int in_rate, + unsigned int out_rate, + u32 *in, u32 *out, u32 *en) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + unsigned int target_rate; + u32 *target_val; + u32 _in; + u32 _out; + u32 _en; + + /* default = SSI WS */ + _in = + _out = rsnd_adg_ssi_ws_timing_gen2(io); + + target_rate = 0; + target_val = NULL; + _en = 0; + if (runtime->rate != in_rate) { + target_rate = out_rate; + target_val = &_out; + } else if (runtime->rate != out_rate) { + target_rate = in_rate; + target_val = &_in; + } + + if (target_rate) + __rsnd_adg_get_timesel_ratio(priv, io, + target_rate, + target_val, &_en); + + if (in) + *in = _in; + if (out) + *out = _out; + if (en) + *en = _en; +} + +int rsnd_adg_set_cmd_timsel_gen2(struct rsnd_mod *cmd_mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(cmd_mod); + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); + struct rsnd_mod *adg_mod = rsnd_mod_get(adg); + int id = rsnd_mod_id(cmd_mod); + int shift = (id % 2) ? 16 : 0; + u32 mask, val; + + rsnd_adg_get_timesel_ratio(priv, io, + rsnd_src_get_in_rate(priv, io), + rsnd_src_get_out_rate(priv, io), + NULL, &val, NULL); + + val = val << shift; + mask = 0x0f1f << shift; + + rsnd_mod_bset(adg_mod, CMDOUT_TIMSEL, mask, val); + + return 0; +} + +int rsnd_adg_set_src_timesel_gen2(struct rsnd_mod *src_mod, + struct rsnd_dai_stream *io, + unsigned int in_rate, + unsigned int out_rate) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(src_mod); + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); + struct rsnd_mod *adg_mod = rsnd_mod_get(adg); + u32 in, out; + u32 mask, en; + int id = rsnd_mod_id(src_mod); + int shift = (id % 2) ? 16 : 0; + + rsnd_mod_confirm_src(src_mod); + + rsnd_adg_get_timesel_ratio(priv, io, + in_rate, out_rate, + &in, &out, &en); + + in = in << shift; + out = out << shift; + mask = 0x0f1f << shift; + + switch (id / 2) { + case 0: + rsnd_mod_bset(adg_mod, SRCIN_TIMSEL0, mask, in); + rsnd_mod_bset(adg_mod, SRCOUT_TIMSEL0, mask, out); + break; + case 1: + rsnd_mod_bset(adg_mod, SRCIN_TIMSEL1, mask, in); + rsnd_mod_bset(adg_mod, SRCOUT_TIMSEL1, mask, out); + break; + case 2: + rsnd_mod_bset(adg_mod, SRCIN_TIMSEL2, mask, in); + rsnd_mod_bset(adg_mod, SRCOUT_TIMSEL2, mask, out); + break; + case 3: + rsnd_mod_bset(adg_mod, SRCIN_TIMSEL3, mask, in); + rsnd_mod_bset(adg_mod, SRCOUT_TIMSEL3, mask, out); + break; + case 4: + rsnd_mod_bset(adg_mod, SRCIN_TIMSEL4, mask, in); + rsnd_mod_bset(adg_mod, SRCOUT_TIMSEL4, mask, out); + break; + } + + if (en) + rsnd_mod_bset(adg_mod, DIV_EN, en, en); + + return 0; +} + +static void rsnd_adg_set_ssi_clk(struct rsnd_mod *ssi_mod, u32 val) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(ssi_mod); + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); + struct rsnd_mod *adg_mod = rsnd_mod_get(adg); + struct device *dev = rsnd_priv_to_dev(priv); + int id = rsnd_mod_id(ssi_mod); + int shift = (id % 4) * 8; + u32 mask = 0xFF << shift; + + rsnd_mod_confirm_ssi(ssi_mod); + + val = val << shift; + + /* + * SSI 8 is not connected to ADG. + * it works with SSI 7 + */ + if (id == 8) + return; + + switch (id / 4) { + case 0: + rsnd_mod_bset(adg_mod, AUDIO_CLK_SEL0, mask, val); + break; + case 1: + rsnd_mod_bset(adg_mod, AUDIO_CLK_SEL1, mask, val); + break; + case 2: + rsnd_mod_bset(adg_mod, AUDIO_CLK_SEL2, mask, val); + break; + } + + dev_dbg(dev, "AUDIO_CLK_SEL is 0x%x\n", val); +} + +int rsnd_adg_clk_query(struct rsnd_priv *priv, unsigned int rate) +{ + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); + int i; + int sel_table[] = { + [CLKA] = 0x1, + [CLKB] = 0x2, + [CLKC] = 0x3, + [CLKI] = 0x0, + }; + + /* + * find suitable clock from + * AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC/AUDIO_CLKI. + */ + for (i = 0; i < CLKMAX; i++) + if (rate == adg->clk_rate[i]) + return sel_table[i]; + + /* + * find divided clock from BRGA/BRGB + */ + if (rate == adg->rbga_rate_for_441khz) + return 0x10; + + if (rate == adg->rbgb_rate_for_48khz) + return 0x20; + + return -EIO; +} + +int rsnd_adg_ssi_clk_stop(struct rsnd_mod *ssi_mod) +{ + rsnd_adg_set_ssi_clk(ssi_mod, 0); + + return 0; +} + +int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *ssi_mod, unsigned int rate) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(ssi_mod); + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_mod *adg_mod = rsnd_mod_get(adg); + int data; + u32 ckr = 0; + + data = rsnd_adg_clk_query(priv, rate); + if (data < 0) + return data; + + rsnd_adg_set_ssi_clk(ssi_mod, data); + + if (rsnd_flags_has(adg, LRCLK_ASYNC)) { + if (rsnd_flags_has(adg, AUDIO_OUT_48)) + ckr = 0x80000000; + } else { + if (0 == (rate % 8000)) + ckr = 0x80000000; + } + + rsnd_mod_bset(adg_mod, BRGCKR, 0x80770000, adg->ckr | ckr); + rsnd_mod_write(adg_mod, BRRA, adg->rbga); + rsnd_mod_write(adg_mod, BRRB, adg->rbgb); + + dev_dbg(dev, "CLKOUT is based on BRG%c (= %dHz)\n", + (ckr) ? 'B' : 'A', + (ckr) ? adg->rbgb_rate_for_48khz : + adg->rbga_rate_for_441khz); + + return 0; +} + +void rsnd_adg_clk_control(struct rsnd_priv *priv, int enable) +{ + struct rsnd_adg *adg = rsnd_priv_to_adg(priv); + struct device *dev = rsnd_priv_to_dev(priv); + struct clk *clk; + int i, ret; + + for_each_rsnd_clk(clk, adg, i) { + ret = 0; + if (enable) { + ret = clk_prepare_enable(clk); + + /* + * We shouldn't use clk_get_rate() under + * atomic context. Let's keep it when + * rsnd_adg_clk_enable() was called + */ + adg->clk_rate[i] = clk_get_rate(adg->clk[i]); + } else { + clk_disable_unprepare(clk); + } + + if (ret < 0) + dev_warn(dev, "can't use clk %d\n", i); + } +} + +static void rsnd_adg_get_clkin(struct rsnd_priv *priv, + struct rsnd_adg *adg) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct clk *clk; + int i; + + for (i = 0; i < CLKMAX; i++) { + clk = devm_clk_get(dev, clk_name[i]); + adg->clk[i] = IS_ERR(clk) ? NULL : clk; + } +} + +static void rsnd_adg_get_clkout(struct rsnd_priv *priv, + struct rsnd_adg *adg) +{ + struct clk *clk; + struct device *dev = rsnd_priv_to_dev(priv); + struct device_node *np = dev->of_node; + struct property *prop; + u32 ckr, rbgx, rbga, rbgb; + u32 rate, div; +#define REQ_SIZE 2 + u32 req_rate[REQ_SIZE] = {}; + uint32_t count = 0; + unsigned long req_48kHz_rate, req_441kHz_rate; + int i, req_size; + const char *parent_clk_name = NULL; + static const char * const clkout_name[] = { + [CLKOUT] = "audio_clkout", + [CLKOUT1] = "audio_clkout1", + [CLKOUT2] = "audio_clkout2", + [CLKOUT3] = "audio_clkout3", + }; + int brg_table[] = { + [CLKA] = 0x0, + [CLKB] = 0x1, + [CLKC] = 0x4, + [CLKI] = 0x2, + }; + + ckr = 0; + rbga = 2; /* default 1/6 */ + rbgb = 2; /* default 1/6 */ + + /* + * ADG supports BRRA/BRRB output only + * this means all clkout0/1/2/3 will be same rate + */ + prop = of_find_property(np, "clock-frequency", NULL); + if (!prop) + goto rsnd_adg_get_clkout_end; + + req_size = prop->length / sizeof(u32); + if (req_size > REQ_SIZE) { + dev_err(dev, + "too many clock-frequency, use top %d\n", REQ_SIZE); + req_size = REQ_SIZE; + } + + of_property_read_u32_array(np, "clock-frequency", req_rate, req_size); + req_48kHz_rate = 0; + req_441kHz_rate = 0; + for (i = 0; i < req_size; i++) { + if (0 == (req_rate[i] % 44100)) + req_441kHz_rate = req_rate[i]; + if (0 == (req_rate[i] % 48000)) + req_48kHz_rate = req_rate[i]; + } + + if (req_rate[0] % 48000 == 0) + rsnd_flags_set(adg, AUDIO_OUT_48); + + if (of_get_property(np, "clkout-lr-asynchronous", NULL)) + rsnd_flags_set(adg, LRCLK_ASYNC); + + /* + * This driver is assuming that AUDIO_CLKA/AUDIO_CLKB/AUDIO_CLKC + * have 44.1kHz or 48kHz base clocks for now. + * + * SSI itself can divide parent clock by 1/1 - 1/16 + * see + * rsnd_adg_ssi_clk_try_start() + * rsnd_ssi_master_clk_start() + */ + adg->rbga_rate_for_441khz = 0; + adg->rbgb_rate_for_48khz = 0; + for_each_rsnd_clk(clk, adg, i) { + rate = clk_get_rate(clk); + + if (0 == rate) /* not used */ + continue; + + /* RBGA */ + if (!adg->rbga_rate_for_441khz && (0 == rate % 44100)) { + div = 6; + if (req_441kHz_rate) + div = rate / req_441kHz_rate; + rbgx = rsnd_adg_calculate_rbgx(div); + if (BRRx_MASK(rbgx) == rbgx) { + rbga = rbgx; + adg->rbga_rate_for_441khz = rate / div; + ckr |= brg_table[i] << 20; + if (req_441kHz_rate && + !rsnd_flags_has(adg, AUDIO_OUT_48)) + parent_clk_name = __clk_get_name(clk); + } + } + + /* RBGB */ + if (!adg->rbgb_rate_for_48khz && (0 == rate % 48000)) { + div = 6; + if (req_48kHz_rate) + div = rate / req_48kHz_rate; + rbgx = rsnd_adg_calculate_rbgx(div); + if (BRRx_MASK(rbgx) == rbgx) { + rbgb = rbgx; + adg->rbgb_rate_for_48khz = rate / div; + ckr |= brg_table[i] << 16; + if (req_48kHz_rate && + rsnd_flags_has(adg, AUDIO_OUT_48)) + parent_clk_name = __clk_get_name(clk); + } + } + } + + /* + * ADG supports BRRA/BRRB output only. + * this means all clkout0/1/2/3 will be * same rate + */ + + of_property_read_u32(np, "#clock-cells", &count); + /* + * for clkout + */ + if (!count) { + clk = clk_register_fixed_rate(dev, clkout_name[CLKOUT], + parent_clk_name, 0, req_rate[0]); + if (!IS_ERR(clk)) { + adg->clkout[CLKOUT] = clk; + of_clk_add_provider(np, of_clk_src_simple_get, clk); + } + } + /* + * for clkout0/1/2/3 + */ + else { + for (i = 0; i < CLKOUTMAX; i++) { + clk = clk_register_fixed_rate(dev, clkout_name[i], + parent_clk_name, 0, + req_rate[0]); + if (!IS_ERR(clk)) + adg->clkout[i] = clk; + } + adg->onecell.clks = adg->clkout; + adg->onecell.clk_num = CLKOUTMAX; + of_clk_add_provider(np, of_clk_src_onecell_get, + &adg->onecell); + } + +rsnd_adg_get_clkout_end: + adg->ckr = ckr; + adg->rbga = rbga; + adg->rbgb = rbgb; +} + +#ifdef DEBUG +static void rsnd_adg_clk_dbg_info(struct rsnd_priv *priv, struct rsnd_adg *adg) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct clk *clk; + int i; + + for_each_rsnd_clk(clk, adg, i) + dev_dbg(dev, "%s : %p : %ld\n", + clk_name[i], clk, clk_get_rate(clk)); + + dev_dbg(dev, "BRGCKR = 0x%08x, BRRA/BRRB = 0x%x/0x%x\n", + adg->ckr, adg->rbga, adg->rbgb); + dev_dbg(dev, "BRGA (for 44100 base) = %d\n", adg->rbga_rate_for_441khz); + dev_dbg(dev, "BRGB (for 48000 base) = %d\n", adg->rbgb_rate_for_48khz); + + /* + * Actual CLKOUT will be exchanged in rsnd_adg_ssi_clk_try_start() + * by BRGCKR::BRGCKR_31 + */ + for_each_rsnd_clkout(clk, adg, i) + dev_dbg(dev, "clkout %d : %p : %ld\n", i, + clk, clk_get_rate(clk)); +} +#else +#define rsnd_adg_clk_dbg_info(priv, adg) +#endif + +int rsnd_adg_probe(struct rsnd_priv *priv) +{ + struct rsnd_adg *adg; + struct device *dev = rsnd_priv_to_dev(priv); + int ret; + + adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL); + if (!adg) + return -ENOMEM; + + ret = rsnd_mod_init(priv, &adg->mod, &adg_ops, + NULL, NULL, 0, 0); + if (ret) + return ret; + + rsnd_adg_get_clkin(priv, adg); + rsnd_adg_get_clkout(priv, adg); + rsnd_adg_clk_dbg_info(priv, adg); + + priv->adg = adg; + + rsnd_adg_clk_enable(priv); + + return 0; +} + +void rsnd_adg_remove(struct rsnd_priv *priv) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct device_node *np = dev->of_node; + struct rsnd_adg *adg = priv->adg; + struct clk *clk; + int i; + + for_each_rsnd_clkout(clk, adg, i) + if (adg->clkout[i]) + clk_unregister_fixed_rate(adg->clkout[i]); + + of_clk_del_provider(np); + + rsnd_adg_clk_disable(priv); +} diff --git a/sound/soc/sh/rcar/cmd.c b/sound/soc/sh/rcar/cmd.c new file mode 100644 index 000000000..cc191cd5f --- /dev/null +++ b/sound/soc/sh/rcar/cmd.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car CMD support +// +// Copyright (C) 2015 Renesas Solutions Corp. +// Kuninori Morimoto + +#include "rsnd.h" + +struct rsnd_cmd { + struct rsnd_mod mod; +}; + +#define CMD_NAME "cmd" + +#define rsnd_cmd_nr(priv) ((priv)->cmd_nr) +#define for_each_rsnd_cmd(pos, priv, i) \ + for ((i) = 0; \ + ((i) < rsnd_cmd_nr(priv)) && \ + ((pos) = (struct rsnd_cmd *)(priv)->cmd + i); \ + i++) + +static int rsnd_cmd_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); + struct rsnd_mod *mix = rsnd_io_to_mod_mix(io); + struct device *dev = rsnd_priv_to_dev(priv); + u32 data; + static const u32 path[] = { + [1] = 1 << 0, + [5] = 1 << 8, + [6] = 1 << 12, + [9] = 1 << 15, + }; + + if (!mix && !dvc) + return 0; + + if (ARRAY_SIZE(path) < rsnd_mod_id(mod) + 1) + return -ENXIO; + + if (mix) { + struct rsnd_dai *rdai; + struct rsnd_mod *src; + struct rsnd_dai_stream *tio; + int i; + + /* + * it is assuming that integrater is well understanding about + * data path. Here doesn't check impossible connection, + * like src2 + src5 + */ + data = 0; + for_each_rsnd_dai(rdai, priv, i) { + tio = &rdai->playback; + src = rsnd_io_to_mod_src(tio); + if (mix == rsnd_io_to_mod_mix(tio)) + data |= path[rsnd_mod_id(src)]; + + tio = &rdai->capture; + src = rsnd_io_to_mod_src(tio); + if (mix == rsnd_io_to_mod_mix(tio)) + data |= path[rsnd_mod_id(src)]; + } + + } else { + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + + static const u8 cmd_case[] = { + [0] = 0x3, + [1] = 0x3, + [2] = 0x4, + [3] = 0x1, + [4] = 0x2, + [5] = 0x4, + [6] = 0x1, + [9] = 0x2, + }; + + if (unlikely(!src)) + return -EIO; + + data = path[rsnd_mod_id(src)] | + cmd_case[rsnd_mod_id(src)] << 16; + } + + dev_dbg(dev, "ctu/mix path = 0x%08x\n", data); + + rsnd_mod_write(mod, CMD_ROUTE_SLCT, data); + rsnd_mod_write(mod, CMD_BUSIF_MODE, rsnd_get_busif_shift(io, mod) | 1); + rsnd_mod_write(mod, CMD_BUSIF_DALIGN, rsnd_get_dalign(mod, io)); + + rsnd_adg_set_cmd_timsel_gen2(mod, io); + + return 0; +} + +static int rsnd_cmd_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_mod_write(mod, CMD_CTRL, 0x10); + + return 0; +} + +static int rsnd_cmd_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_mod_write(mod, CMD_CTRL, 0); + + return 0; +} + +static struct rsnd_mod_ops rsnd_cmd_ops = { + .name = CMD_NAME, + .init = rsnd_cmd_init, + .start = rsnd_cmd_start, + .stop = rsnd_cmd_stop, +}; + +static struct rsnd_mod *rsnd_cmd_mod_get(struct rsnd_priv *priv, int id) +{ + if (WARN_ON(id < 0 || id >= rsnd_cmd_nr(priv))) + id = 0; + + return rsnd_mod_get((struct rsnd_cmd *)(priv->cmd) + id); +} +int rsnd_cmd_attach(struct rsnd_dai_stream *io, int id) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_mod *mod = rsnd_cmd_mod_get(priv, id); + + return rsnd_dai_connect(mod, io, mod->type); +} + +int rsnd_cmd_probe(struct rsnd_priv *priv) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_cmd *cmd; + int i, nr, ret; + + /* This driver doesn't support Gen1 at this point */ + if (rsnd_is_gen1(priv)) + return 0; + + /* same number as DVC */ + nr = priv->dvc_nr; + if (!nr) + return 0; + + cmd = devm_kcalloc(dev, nr, sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + priv->cmd_nr = nr; + priv->cmd = cmd; + + for_each_rsnd_cmd(cmd, priv, i) { + ret = rsnd_mod_init(priv, rsnd_mod_get(cmd), + &rsnd_cmd_ops, NULL, + rsnd_mod_get_status, RSND_MOD_CMD, i); + if (ret) + return ret; + } + + return 0; +} + +void rsnd_cmd_remove(struct rsnd_priv *priv) +{ + struct rsnd_cmd *cmd; + int i; + + for_each_rsnd_cmd(cmd, priv, i) { + rsnd_mod_quit(rsnd_mod_get(cmd)); + } +} diff --git a/sound/soc/sh/rcar/core.c b/sound/soc/sh/rcar/core.c new file mode 100644 index 000000000..4e6ecdc83 --- /dev/null +++ b/sound/soc/sh/rcar/core.c @@ -0,0 +1,1655 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car SRU/SCU/SSIU/SSI support +// +// Copyright (C) 2013 Renesas Solutions Corp. +// Kuninori Morimoto +// +// Based on fsi.c +// Kuninori Morimoto + +/* + * Renesas R-Car sound device structure + * + * Gen1 + * + * SRU : Sound Routing Unit + * - SRC : Sampling Rate Converter + * - CMD + * - CTU : Channel Count Conversion Unit + * - MIX : Mixer + * - DVC : Digital Volume and Mute Function + * - SSI : Serial Sound Interface + * + * Gen2 + * + * SCU : Sampling Rate Converter Unit + * - SRC : Sampling Rate Converter + * - CMD + * - CTU : Channel Count Conversion Unit + * - MIX : Mixer + * - DVC : Digital Volume and Mute Function + * SSIU : Serial Sound Interface Unit + * - SSI : Serial Sound Interface + */ + +/* + * driver data Image + * + * rsnd_priv + * | + * | ** this depends on Gen1/Gen2 + * | + * +- gen + * | + * | ** these depend on data path + * | ** gen and platform data control it + * | + * +- rdai[0] + * | | sru ssiu ssi + * | +- playback -> [mod] -> [mod] -> [mod] -> ... + * | | + * | | sru ssiu ssi + * | +- capture -> [mod] -> [mod] -> [mod] -> ... + * | + * +- rdai[1] + * | | sru ssiu ssi + * | +- playback -> [mod] -> [mod] -> [mod] -> ... + * | | + * | | sru ssiu ssi + * | +- capture -> [mod] -> [mod] -> [mod] -> ... + * ... + * | + * | ** these control ssi + * | + * +- ssi + * | | + * | +- ssi[0] + * | +- ssi[1] + * | +- ssi[2] + * | ... + * | + * | ** these control src + * | + * +- src + * | + * +- src[0] + * +- src[1] + * +- src[2] + * ... + * + * + * for_each_rsnd_dai(xx, priv, xx) + * rdai[0] => rdai[1] => rdai[2] => ... + * + * for_each_rsnd_mod(xx, rdai, xx) + * [mod] => [mod] => [mod] => ... + * + * rsnd_dai_call(xxx, fn ) + * [mod]->fn() -> [mod]->fn() -> [mod]->fn()... + * + */ + +/* + * you can enable below define if you don't need + * DAI status debug message when debugging + * see rsnd_dbg_dai_call() + * + * #define RSND_DEBUG_NO_DAI_CALL 1 + */ + +#include +#include "rsnd.h" + +#define RSND_RATES SNDRV_PCM_RATE_8000_192000 +#define RSND_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +static const struct of_device_id rsnd_of_match[] = { + { .compatible = "renesas,rcar_sound-gen1", .data = (void *)RSND_GEN1 }, + { .compatible = "renesas,rcar_sound-gen2", .data = (void *)RSND_GEN2 }, + { .compatible = "renesas,rcar_sound-gen3", .data = (void *)RSND_GEN3 }, + {}, +}; +MODULE_DEVICE_TABLE(of, rsnd_of_match); + +/* + * rsnd_mod functions + */ +void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type) +{ + if (mod->type != type) { + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + + dev_warn(dev, "%s[%d] is not your expected module\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + } +} + +struct dma_chan *rsnd_mod_dma_req(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + if (!mod || !mod->ops || !mod->ops->dma_req) + return NULL; + + return mod->ops->dma_req(io, mod); +} + +u32 *rsnd_mod_get_status(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + enum rsnd_mod_type type) +{ + return &mod->status; +} + +int rsnd_mod_init(struct rsnd_priv *priv, + struct rsnd_mod *mod, + struct rsnd_mod_ops *ops, + struct clk *clk, + u32* (*get_status)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + enum rsnd_mod_type type), + enum rsnd_mod_type type, + int id) +{ + int ret = clk_prepare(clk); + + if (ret) + return ret; + + mod->id = id; + mod->ops = ops; + mod->type = type; + mod->clk = clk; + mod->priv = priv; + mod->get_status = get_status; + + return ret; +} + +void rsnd_mod_quit(struct rsnd_mod *mod) +{ + clk_unprepare(mod->clk); + mod->clk = NULL; +} + +void rsnd_mod_interrupt(struct rsnd_mod *mod, + void (*callback)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io)) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dai_stream *io; + struct rsnd_dai *rdai; + int i; + + for_each_rsnd_dai(rdai, priv, i) { + io = &rdai->playback; + if (mod == io->mod[mod->type]) + callback(mod, io); + + io = &rdai->capture; + if (mod == io->mod[mod->type]) + callback(mod, io); + } +} + +int rsnd_io_is_working(struct rsnd_dai_stream *io) +{ + /* see rsnd_dai_stream_init/quit() */ + if (io->substream) + return snd_pcm_running(io->substream); + + return 0; +} + +int rsnd_runtime_channel_original_with_params(struct rsnd_dai_stream *io, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + + /* + * params will be added when refine + * see + * __rsnd_soc_hw_rule_rate() + * __rsnd_soc_hw_rule_channels() + */ + if (params) + return params_channels(params); + else + return runtime->channels; +} + +int rsnd_runtime_channel_after_ctu_with_params(struct rsnd_dai_stream *io, + struct snd_pcm_hw_params *params) +{ + int chan = rsnd_runtime_channel_original_with_params(io, params); + struct rsnd_mod *ctu_mod = rsnd_io_to_mod_ctu(io); + + if (ctu_mod) { + u32 converted_chan = rsnd_ctu_converted_channel(ctu_mod); + + if (converted_chan) + return converted_chan; + } + + return chan; +} + +int rsnd_runtime_channel_for_ssi_with_params(struct rsnd_dai_stream *io, + struct snd_pcm_hw_params *params) +{ + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + int chan = rsnd_io_is_play(io) ? + rsnd_runtime_channel_after_ctu_with_params(io, params) : + rsnd_runtime_channel_original_with_params(io, params); + + /* Use Multi SSI */ + if (rsnd_runtime_is_ssi_multi(io)) + chan /= rsnd_rdai_ssi_lane_get(rdai); + + /* TDM Extend Mode needs 8ch */ + if (chan == 6) + chan = 8; + + return chan; +} + +int rsnd_runtime_is_ssi_multi(struct rsnd_dai_stream *io) +{ + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + int lane = rsnd_rdai_ssi_lane_get(rdai); + int chan = rsnd_io_is_play(io) ? + rsnd_runtime_channel_after_ctu(io) : + rsnd_runtime_channel_original(io); + + return (chan > 2) && (lane > 1); +} + +int rsnd_runtime_is_ssi_tdm(struct rsnd_dai_stream *io) +{ + return rsnd_runtime_channel_for_ssi(io) >= 6; +} + +/* + * ADINR function + */ +u32 rsnd_get_adinr_bit(struct rsnd_mod *mod, struct rsnd_dai_stream *io) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct device *dev = rsnd_priv_to_dev(priv); + + switch (snd_pcm_format_width(runtime->format)) { + case 16: + return 8 << 16; + case 24: + return 0 << 16; + } + + dev_warn(dev, "not supported sample bits\n"); + + return 0; +} + +/* + * DALIGN function + */ +u32 rsnd_get_dalign(struct rsnd_mod *mod, struct rsnd_dai_stream *io) +{ + struct rsnd_mod *ssiu = rsnd_io_to_mod_ssiu(io); + struct rsnd_mod *target; + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + + /* + * *Hardware* L/R and *Software* L/R are inverted for 16bit data. + * 31..16 15...0 + * HW: [L ch] [R ch] + * SW: [R ch] [L ch] + * We need to care about inversion timing to control + * Playback/Capture correctly. + * The point is [DVC] needs *Hardware* L/R, [MEM] needs *Software* L/R + * + * sL/R : software L/R + * hL/R : hardware L/R + * (*) : conversion timing + * + * Playback + * sL/R (*) hL/R hL/R hL/R hL/R hL/R + * [MEM] -> [SRC] -> [DVC] -> [CMD] -> [SSIU] -> [SSI] -> codec + * + * Capture + * hL/R hL/R hL/R hL/R hL/R (*) sL/R + * codec -> [SSI] -> [SSIU] -> [SRC] -> [DVC] -> [CMD] -> [MEM] + */ + if (rsnd_io_is_play(io)) { + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + + target = src ? src : ssiu; + } else { + struct rsnd_mod *cmd = rsnd_io_to_mod_cmd(io); + + target = cmd ? cmd : ssiu; + } + + /* Non target mod or 24bit data needs normal DALIGN */ + if ((snd_pcm_format_width(runtime->format) != 16) || + (mod != target)) + return 0x76543210; + /* Target mod needs inverted DALIGN when 16bit */ + else + return 0x67452301; +} + +u32 rsnd_get_busif_shift(struct rsnd_dai_stream *io, struct rsnd_mod *mod) +{ + enum rsnd_mod_type playback_mods[] = { + RSND_MOD_SRC, + RSND_MOD_CMD, + RSND_MOD_SSIU, + }; + enum rsnd_mod_type capture_mods[] = { + RSND_MOD_CMD, + RSND_MOD_SRC, + RSND_MOD_SSIU, + }; + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_mod *tmod = NULL; + enum rsnd_mod_type *mods = + rsnd_io_is_play(io) ? + playback_mods : capture_mods; + int i; + + /* + * This is needed for 24bit data + * We need to shift 8bit + * + * Linux 24bit data is located as 0x00****** + * HW 24bit data is located as 0x******00 + * + */ + if (snd_pcm_format_width(runtime->format) == 16) + return 0; + + for (i = 0; i < ARRAY_SIZE(playback_mods); i++) { + tmod = rsnd_io_to_mod(io, mods[i]); + if (tmod) + break; + } + + if (tmod != mod) + return 0; + + if (rsnd_io_is_play(io)) + return (0 << 20) | /* shift to Left */ + (8 << 16); /* 8bit */ + else + return (1 << 20) | /* shift to Right */ + (8 << 16); /* 8bit */ +} + +/* + * rsnd_dai functions + */ +struct rsnd_mod *rsnd_mod_next(int *iterator, + struct rsnd_dai_stream *io, + enum rsnd_mod_type *array, + int array_size) +{ + struct rsnd_mod *mod; + enum rsnd_mod_type type; + int max = array ? array_size : RSND_MOD_MAX; + + for (; *iterator < max; (*iterator)++) { + type = (array) ? array[*iterator] : *iterator; + mod = rsnd_io_to_mod(io, type); + if (mod) + return mod; + } + + return NULL; +} + +static enum rsnd_mod_type rsnd_mod_sequence[][RSND_MOD_MAX] = { + { + /* CAPTURE */ + RSND_MOD_AUDMAPP, + RSND_MOD_AUDMA, + RSND_MOD_DVC, + RSND_MOD_MIX, + RSND_MOD_CTU, + RSND_MOD_CMD, + RSND_MOD_SRC, + RSND_MOD_SSIU, + RSND_MOD_SSIM3, + RSND_MOD_SSIM2, + RSND_MOD_SSIM1, + RSND_MOD_SSIP, + RSND_MOD_SSI, + }, { + /* PLAYBACK */ + RSND_MOD_AUDMAPP, + RSND_MOD_AUDMA, + RSND_MOD_SSIM3, + RSND_MOD_SSIM2, + RSND_MOD_SSIM1, + RSND_MOD_SSIP, + RSND_MOD_SSI, + RSND_MOD_SSIU, + RSND_MOD_DVC, + RSND_MOD_MIX, + RSND_MOD_CTU, + RSND_MOD_CMD, + RSND_MOD_SRC, + }, +}; + +static int rsnd_status_update(u32 *status, + int shift, int add, int timing) +{ + u32 mask = 0xF << shift; + u8 val = (*status >> shift) & 0xF; + u8 next_val = (val + add) & 0xF; + int func_call = (val == timing); + + if (next_val == 0xF) /* underflow case */ + func_call = 0; + else + *status = (*status & ~mask) + (next_val << shift); + + return func_call; +} + +#define rsnd_dai_call(fn, io, param...) \ +({ \ + struct device *dev = rsnd_priv_to_dev(rsnd_io_to_priv(io)); \ + struct rsnd_mod *mod; \ + int is_play = rsnd_io_is_play(io); \ + int ret = 0, i; \ + enum rsnd_mod_type *types = rsnd_mod_sequence[is_play]; \ + for_each_rsnd_mod_arrays(i, mod, io, types, RSND_MOD_MAX) { \ + int tmp = 0; \ + u32 *status = mod->get_status(io, mod, types[i]); \ + int func_call = rsnd_status_update(status, \ + __rsnd_mod_shift_##fn, \ + __rsnd_mod_add_##fn, \ + __rsnd_mod_call_##fn); \ + rsnd_dbg_dai_call(dev, "%s[%d]\t0x%08x %s\n", \ + rsnd_mod_name(mod), rsnd_mod_id(mod), *status, \ + (func_call && (mod)->ops->fn) ? #fn : ""); \ + if (func_call && (mod)->ops->fn) \ + tmp = (mod)->ops->fn(mod, io, param); \ + if (tmp && (tmp != -EPROBE_DEFER)) \ + dev_err(dev, "%s[%d] : %s error %d\n", \ + rsnd_mod_name(mod), rsnd_mod_id(mod), \ + #fn, tmp); \ + ret |= tmp; \ + } \ + ret; \ +}) + +int rsnd_dai_connect(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + enum rsnd_mod_type type) +{ + struct rsnd_priv *priv; + struct device *dev; + + if (!mod) + return -EIO; + + if (io->mod[type] == mod) + return 0; + + if (io->mod[type]) + return -EINVAL; + + priv = rsnd_mod_to_priv(mod); + dev = rsnd_priv_to_dev(priv); + + io->mod[type] = mod; + + dev_dbg(dev, "%s[%d] is connected to io (%s)\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), + rsnd_io_is_play(io) ? "Playback" : "Capture"); + + return 0; +} + +static void rsnd_dai_disconnect(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + enum rsnd_mod_type type) +{ + io->mod[type] = NULL; +} + +int rsnd_rdai_channels_ctrl(struct rsnd_dai *rdai, + int max_channels) +{ + if (max_channels > 0) + rdai->max_channels = max_channels; + + return rdai->max_channels; +} + +int rsnd_rdai_ssi_lane_ctrl(struct rsnd_dai *rdai, + int ssi_lane) +{ + if (ssi_lane > 0) + rdai->ssi_lane = ssi_lane; + + return rdai->ssi_lane; +} + +struct rsnd_dai *rsnd_rdai_get(struct rsnd_priv *priv, int id) +{ + if ((id < 0) || (id >= rsnd_rdai_nr(priv))) + return NULL; + + return priv->rdai + id; +} + +static struct snd_soc_dai_driver +*rsnd_daidrv_get(struct rsnd_priv *priv, int id) +{ + if ((id < 0) || (id >= rsnd_rdai_nr(priv))) + return NULL; + + return priv->daidrv + id; +} + +#define rsnd_dai_to_priv(dai) snd_soc_dai_get_drvdata(dai) +static struct rsnd_dai *rsnd_dai_to_rdai(struct snd_soc_dai *dai) +{ + struct rsnd_priv *priv = rsnd_dai_to_priv(dai); + + return rsnd_rdai_get(priv, dai->id); +} + +/* + * rsnd_soc_dai functions + */ +void rsnd_dai_period_elapsed(struct rsnd_dai_stream *io) +{ + struct snd_pcm_substream *substream = io->substream; + + /* + * this function should be called... + * + * - if rsnd_dai_pointer_update() returns true + * - without spin lock + */ + + snd_pcm_period_elapsed(substream); +} + +static void rsnd_dai_stream_init(struct rsnd_dai_stream *io, + struct snd_pcm_substream *substream) +{ + io->substream = substream; +} + +static void rsnd_dai_stream_quit(struct rsnd_dai_stream *io) +{ + io->substream = NULL; +} + +static +struct snd_soc_dai *rsnd_substream_to_dai(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + return rtd->cpu_dai; +} + +static +struct rsnd_dai_stream *rsnd_rdai_to_io(struct rsnd_dai *rdai, + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return &rdai->playback; + else + return &rdai->capture; +} + +static int rsnd_soc_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct rsnd_priv *priv = rsnd_dai_to_priv(dai); + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream); + int ret; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + ret = rsnd_dai_call(init, io, priv); + if (ret < 0) + goto dai_trigger_end; + + ret = rsnd_dai_call(start, io, priv); + if (ret < 0) + goto dai_trigger_end; + + ret = rsnd_dai_call(irq, io, priv, 1); + if (ret < 0) + goto dai_trigger_end; + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + ret = rsnd_dai_call(irq, io, priv, 0); + + ret |= rsnd_dai_call(stop, io, priv); + + ret |= rsnd_dai_call(quit, io, priv); + + break; + default: + ret = -EINVAL; + } + +dai_trigger_end: + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +static int rsnd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + rdai->clk_master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFS: + rdai->clk_master = 1; /* codec is slave, cpu is master */ + break; + default: + return -EINVAL; + } + + /* set format */ + rdai->bit_clk_inv = 0; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + rdai->sys_delay = 0; + rdai->data_alignment = 0; + rdai->frm_clk_inv = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + rdai->sys_delay = 1; + rdai->data_alignment = 0; + rdai->frm_clk_inv = 1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + rdai->sys_delay = 1; + rdai->data_alignment = 1; + rdai->frm_clk_inv = 1; + break; + } + + /* set clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + rdai->frm_clk_inv = !rdai->frm_clk_inv; + break; + case SND_SOC_DAIFMT_IB_NF: + rdai->bit_clk_inv = !rdai->bit_clk_inv; + break; + case SND_SOC_DAIFMT_IB_IF: + rdai->bit_clk_inv = !rdai->bit_clk_inv; + rdai->frm_clk_inv = !rdai->frm_clk_inv; + break; + case SND_SOC_DAIFMT_NB_NF: + default: + break; + } + + return 0; +} + +static int rsnd_soc_set_dai_tdm_slot(struct snd_soc_dai *dai, + u32 tx_mask, u32 rx_mask, + int slots, int slot_width) +{ + struct rsnd_priv *priv = rsnd_dai_to_priv(dai); + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct device *dev = rsnd_priv_to_dev(priv); + + switch (slots) { + case 2: + case 6: + case 8: + /* TDM Extend Mode */ + rsnd_rdai_channels_set(rdai, slots); + rsnd_rdai_ssi_lane_set(rdai, 1); + break; + default: + dev_err(dev, "unsupported TDM slots (%d)\n", slots); + return -EINVAL; + } + + return 0; +} + +static unsigned int rsnd_soc_hw_channels_list[] = { + 2, 6, 8, +}; + +static unsigned int rsnd_soc_hw_rate_list[] = { + 8000, + 11025, + 16000, + 22050, + 32000, + 44100, + 48000, + 64000, + 88200, + 96000, + 176400, + 192000, +}; + +static int rsnd_soc_hw_rule(struct rsnd_priv *priv, + unsigned int *list, int list_num, + struct snd_interval *baseline, struct snd_interval *iv) +{ + struct snd_interval p; + unsigned int rate; + int i; + + snd_interval_any(&p); + p.min = UINT_MAX; + p.max = 0; + + for (i = 0; i < list_num; i++) { + + if (!snd_interval_test(iv, list[i])) + continue; + + rate = rsnd_ssi_clk_query(priv, + baseline->min, list[i], NULL); + if (rate > 0) { + p.min = min(p.min, list[i]); + p.max = max(p.max, list[i]); + } + + rate = rsnd_ssi_clk_query(priv, + baseline->max, list[i], NULL); + if (rate > 0) { + p.min = min(p.min, list[i]); + p.max = max(p.max, list[i]); + } + } + + return snd_interval_refine(iv, &p); +} + +static int __rsnd_soc_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule, + int is_play) +{ + struct snd_interval *ic_ = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *ir = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval ic; + struct snd_soc_dai *dai = rule->private; + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai); + struct rsnd_dai_stream *io = is_play ? &rdai->playback : &rdai->capture; + + /* + * possible sampling rate limitation is same as + * 2ch if it supports multi ssi + * and same as 8ch if TDM 6ch (see rsnd_ssi_config_init()) + */ + ic = *ic_; + ic.min = + ic.max = rsnd_runtime_channel_for_ssi_with_params(io, params); + + return rsnd_soc_hw_rule(priv, rsnd_soc_hw_rate_list, + ARRAY_SIZE(rsnd_soc_hw_rate_list), + &ic, ir); +} + +static int rsnd_soc_hw_rule_rate_playback(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + return __rsnd_soc_hw_rule_rate(params, rule, 1); +} + +static int rsnd_soc_hw_rule_rate_capture(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + return __rsnd_soc_hw_rule_rate(params, rule, 0); +} + +static int __rsnd_soc_hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule, + int is_play) +{ + struct snd_interval *ic_ = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *ir = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval ic; + struct snd_soc_dai *dai = rule->private; + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai); + struct rsnd_dai_stream *io = is_play ? &rdai->playback : &rdai->capture; + + /* + * possible sampling rate limitation is same as + * 2ch if it supports multi ssi + * and same as 8ch if TDM 6ch (see rsnd_ssi_config_init()) + */ + ic = *ic_; + ic.min = + ic.max = rsnd_runtime_channel_for_ssi_with_params(io, params); + + return rsnd_soc_hw_rule(priv, rsnd_soc_hw_channels_list, + ARRAY_SIZE(rsnd_soc_hw_channels_list), + ir, &ic); +} + +static int rsnd_soc_hw_rule_channels_playback(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + return __rsnd_soc_hw_rule_channels(params, rule, 1); +} + +static int rsnd_soc_hw_rule_channels_capture(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + return __rsnd_soc_hw_rule_channels(params, rule, 0); +} + +static const struct snd_pcm_hardware rsnd_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 256, +}; + +static int rsnd_soc_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai); + struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream); + struct snd_pcm_hw_constraint_list *constraint = &rdai->constraint; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int max_channels = rsnd_rdai_channels_get(rdai); + int ret; + int i; + + rsnd_dai_stream_init(io, substream); + + /* + * Channel Limitation + * It depends on Platform design + */ + constraint->list = rsnd_soc_hw_channels_list; + constraint->count = 0; + constraint->mask = 0; + + for (i = 0; i < ARRAY_SIZE(rsnd_soc_hw_channels_list); i++) { + if (rsnd_soc_hw_channels_list[i] > max_channels) + break; + constraint->count = i + 1; + } + + snd_soc_set_runtime_hwparams(substream, &rsnd_pcm_hardware); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, constraint); + + snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + + /* + * Sampling Rate / Channel Limitation + * It depends on Clock Master Mode + */ + if (rsnd_rdai_is_clk_master(rdai)) { + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + is_play ? rsnd_soc_hw_rule_rate_playback : + rsnd_soc_hw_rule_rate_capture, + dai, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + is_play ? rsnd_soc_hw_rule_channels_playback : + rsnd_soc_hw_rule_channels_capture, + dai, + SNDRV_PCM_HW_PARAM_RATE, -1); + } + + /* + * call rsnd_dai_call without spinlock + */ + ret = rsnd_dai_call(nolock_start, io, priv); + if (ret < 0) + rsnd_dai_call(nolock_stop, io, priv); + + return ret; +} + +static void rsnd_soc_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai); + struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream); + + /* + * call rsnd_dai_call without spinlock + */ + rsnd_dai_call(nolock_stop, io, priv); + + rsnd_dai_stream_quit(io); +} + +static int rsnd_soc_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rsnd_priv *priv = rsnd_dai_to_priv(dai); + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream); + + return rsnd_dai_call(prepare, io, priv); +} + +static const struct snd_soc_dai_ops rsnd_soc_dai_ops = { + .startup = rsnd_soc_dai_startup, + .shutdown = rsnd_soc_dai_shutdown, + .trigger = rsnd_soc_dai_trigger, + .set_fmt = rsnd_soc_dai_set_fmt, + .set_tdm_slot = rsnd_soc_set_dai_tdm_slot, + .prepare = rsnd_soc_dai_prepare, +}; + +void rsnd_parse_connect_common(struct rsnd_dai *rdai, + struct rsnd_mod* (*mod_get)(struct rsnd_priv *priv, int id), + struct device_node *node, + struct device_node *playback, + struct device_node *capture) +{ + struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai); + struct device_node *np; + struct rsnd_mod *mod; + int i; + + if (!node) + return; + + i = 0; + for_each_child_of_node(node, np) { + mod = mod_get(priv, i); + if (np == playback) + rsnd_dai_connect(mod, &rdai->playback, mod->type); + if (np == capture) + rsnd_dai_connect(mod, &rdai->capture, mod->type); + i++; + } + + of_node_put(node); +} + +static struct device_node *rsnd_dai_of_node(struct rsnd_priv *priv, + int *is_graph) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct device_node *np = dev->of_node; + struct device_node *dai_node; + struct device_node *ret; + + *is_graph = 0; + + /* + * parse both previous dai (= rcar_sound,dai), and + * graph dai (= ports/port) + */ + dai_node = of_get_child_by_name(np, RSND_NODE_DAI); + if (dai_node) { + ret = dai_node; + goto of_node_compatible; + } + + ret = np; + + dai_node = of_graph_get_next_endpoint(np, NULL); + if (dai_node) + goto of_node_graph; + + return NULL; + +of_node_graph: + *is_graph = 1; +of_node_compatible: + of_node_put(dai_node); + + return ret; +} + +static void __rsnd_dai_probe(struct rsnd_priv *priv, + struct device_node *dai_np, + int dai_i) +{ + struct device_node *playback, *capture; + struct rsnd_dai_stream *io_playback; + struct rsnd_dai_stream *io_capture; + struct snd_soc_dai_driver *drv; + struct rsnd_dai *rdai; + struct device *dev = rsnd_priv_to_dev(priv); + int io_i; + + rdai = rsnd_rdai_get(priv, dai_i); + drv = rsnd_daidrv_get(priv, dai_i); + io_playback = &rdai->playback; + io_capture = &rdai->capture; + + snprintf(rdai->name, RSND_DAI_NAME_SIZE, "rsnd-dai.%d", dai_i); + + rdai->priv = priv; + drv->name = rdai->name; + drv->ops = &rsnd_soc_dai_ops; + + snprintf(rdai->playback.name, RSND_DAI_NAME_SIZE, + "DAI%d Playback", dai_i); + drv->playback.rates = RSND_RATES; + drv->playback.formats = RSND_FMTS; + drv->playback.channels_min = 2; + drv->playback.channels_max = 8; + drv->playback.stream_name = rdai->playback.name; + + snprintf(rdai->capture.name, RSND_DAI_NAME_SIZE, + "DAI%d Capture", dai_i); + drv->capture.rates = RSND_RATES; + drv->capture.formats = RSND_FMTS; + drv->capture.channels_min = 2; + drv->capture.channels_max = 8; + drv->capture.stream_name = rdai->capture.name; + + rdai->playback.rdai = rdai; + rdai->capture.rdai = rdai; + rsnd_rdai_channels_set(rdai, 2); /* default 2ch */ + rsnd_rdai_ssi_lane_set(rdai, 1); /* default 1lane */ + + for (io_i = 0;; io_i++) { + playback = of_parse_phandle(dai_np, "playback", io_i); + capture = of_parse_phandle(dai_np, "capture", io_i); + + if (!playback && !capture) + break; + + rsnd_parse_connect_ssi(rdai, playback, capture); + rsnd_parse_connect_src(rdai, playback, capture); + rsnd_parse_connect_ctu(rdai, playback, capture); + rsnd_parse_connect_mix(rdai, playback, capture); + rsnd_parse_connect_dvc(rdai, playback, capture); + + of_node_put(playback); + of_node_put(capture); + } + + if (rsnd_ssi_is_pin_sharing(io_capture) || + rsnd_ssi_is_pin_sharing(io_playback)) { + /* should have symmetric_rates if pin sharing */ + drv->symmetric_rates = 1; + } + + dev_dbg(dev, "%s (%s/%s)\n", rdai->name, + rsnd_io_to_mod_ssi(io_playback) ? "play" : " -- ", + rsnd_io_to_mod_ssi(io_capture) ? "capture" : " -- "); +} + +static int rsnd_dai_probe(struct rsnd_priv *priv) +{ + struct device_node *dai_node; + struct device_node *dai_np; + struct snd_soc_dai_driver *rdrv; + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dai *rdai; + int nr; + int is_graph; + int dai_i; + + dai_node = rsnd_dai_of_node(priv, &is_graph); + if (is_graph) + nr = of_graph_get_endpoint_count(dai_node); + else + nr = of_get_child_count(dai_node); + + if (!nr) + return -EINVAL; + + rdrv = devm_kcalloc(dev, nr, sizeof(*rdrv), GFP_KERNEL); + rdai = devm_kcalloc(dev, nr, sizeof(*rdai), GFP_KERNEL); + if (!rdrv || !rdai) + return -ENOMEM; + + priv->rdai_nr = nr; + priv->daidrv = rdrv; + priv->rdai = rdai; + + /* + * parse all dai + */ + dai_i = 0; + if (is_graph) { + for_each_endpoint_of_node(dai_node, dai_np) { + __rsnd_dai_probe(priv, dai_np, dai_i); + rsnd_ssi_parse_hdmi_connection(priv, dai_np, dai_i); + dai_i++; + } + } else { + for_each_child_of_node(dai_node, dai_np) + __rsnd_dai_probe(priv, dai_np, dai_i++); + } + + return 0; +} + +/* + * pcm ops + */ +static int rsnd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_dai *dai = rsnd_substream_to_dai(substream); + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream); + int ret; + + ret = rsnd_dai_call(hw_params, io, substream, hw_params); + if (ret) + return ret; + + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static snd_pcm_uframes_t rsnd_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_dai *dai = rsnd_substream_to_dai(substream); + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + struct rsnd_dai_stream *io = rsnd_rdai_to_io(rdai, substream); + snd_pcm_uframes_t pointer = 0; + + rsnd_dai_call(pointer, io, &pointer); + + return pointer; +} + +static const struct snd_pcm_ops rsnd_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .hw_params = rsnd_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .pointer = rsnd_pointer, +}; + +/* + * snd_kcontrol + */ +static int rsnd_kctrl_info(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_info *uinfo) +{ + struct rsnd_kctrl_cfg *cfg = snd_kcontrol_chip(kctrl); + + if (cfg->texts) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = cfg->size; + uinfo->value.enumerated.items = cfg->max; + if (uinfo->value.enumerated.item >= cfg->max) + uinfo->value.enumerated.item = cfg->max - 1; + strlcpy(uinfo->value.enumerated.name, + cfg->texts[uinfo->value.enumerated.item], + sizeof(uinfo->value.enumerated.name)); + } else { + uinfo->count = cfg->size; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = cfg->max; + uinfo->type = (cfg->max == 1) ? + SNDRV_CTL_ELEM_TYPE_BOOLEAN : + SNDRV_CTL_ELEM_TYPE_INTEGER; + } + + return 0; +} + +static int rsnd_kctrl_get(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *uc) +{ + struct rsnd_kctrl_cfg *cfg = snd_kcontrol_chip(kctrl); + int i; + + for (i = 0; i < cfg->size; i++) + if (cfg->texts) + uc->value.enumerated.item[i] = cfg->val[i]; + else + uc->value.integer.value[i] = cfg->val[i]; + + return 0; +} + +static int rsnd_kctrl_put(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *uc) +{ + struct rsnd_kctrl_cfg *cfg = snd_kcontrol_chip(kctrl); + int i, change = 0; + + if (!cfg->accept(cfg->io)) + return 0; + + for (i = 0; i < cfg->size; i++) { + if (cfg->texts) { + change |= (uc->value.enumerated.item[i] != cfg->val[i]); + cfg->val[i] = uc->value.enumerated.item[i]; + } else { + change |= (uc->value.integer.value[i] != cfg->val[i]); + cfg->val[i] = uc->value.integer.value[i]; + } + } + + if (change && cfg->update) + cfg->update(cfg->io, cfg->mod); + + return change; +} + +int rsnd_kctrl_accept_anytime(struct rsnd_dai_stream *io) +{ + return 1; +} + +int rsnd_kctrl_accept_runtime(struct rsnd_dai_stream *io) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + + return !!runtime; +} + +struct rsnd_kctrl_cfg *rsnd_kctrl_init_m(struct rsnd_kctrl_cfg_m *cfg) +{ + cfg->cfg.val = cfg->val; + + return &cfg->cfg; +} + +struct rsnd_kctrl_cfg *rsnd_kctrl_init_s(struct rsnd_kctrl_cfg_s *cfg) +{ + cfg->cfg.val = &cfg->val; + + return &cfg->cfg; +} + +const char * const volume_ramp_rate[] = { + "128 dB/1 step", /* 00000 */ + "64 dB/1 step", /* 00001 */ + "32 dB/1 step", /* 00010 */ + "16 dB/1 step", /* 00011 */ + "8 dB/1 step", /* 00100 */ + "4 dB/1 step", /* 00101 */ + "2 dB/1 step", /* 00110 */ + "1 dB/1 step", /* 00111 */ + "0.5 dB/1 step", /* 01000 */ + "0.25 dB/1 step", /* 01001 */ + "0.125 dB/1 step", /* 01010 = VOLUME_RAMP_MAX_MIX */ + "0.125 dB/2 steps", /* 01011 */ + "0.125 dB/4 steps", /* 01100 */ + "0.125 dB/8 steps", /* 01101 */ + "0.125 dB/16 steps", /* 01110 */ + "0.125 dB/32 steps", /* 01111 */ + "0.125 dB/64 steps", /* 10000 */ + "0.125 dB/128 steps", /* 10001 */ + "0.125 dB/256 steps", /* 10010 */ + "0.125 dB/512 steps", /* 10011 */ + "0.125 dB/1024 steps", /* 10100 */ + "0.125 dB/2048 steps", /* 10101 */ + "0.125 dB/4096 steps", /* 10110 */ + "0.125 dB/8192 steps", /* 10111 = VOLUME_RAMP_MAX_DVC */ +}; + +int rsnd_kctrl_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + int (*accept)(struct rsnd_dai_stream *io), + void (*update)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod), + struct rsnd_kctrl_cfg *cfg, + const char * const *texts, + int size, + u32 max) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_kcontrol *kctrl; + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = name, + .info = rsnd_kctrl_info, + .index = rtd->num, + .get = rsnd_kctrl_get, + .put = rsnd_kctrl_put, + }; + int ret; + + /* + * 1) Avoid duplicate register for DVC with MIX case + * 2) Allow duplicate register for MIX + * 3) re-register if card was rebinded + */ + list_for_each_entry(kctrl, &card->controls, list) { + struct rsnd_kctrl_cfg *c = kctrl->private_data; + + if (c == cfg) + return 0; + } + + if (size > RSND_MAX_CHANNELS) + return -EINVAL; + + kctrl = snd_ctl_new1(&knew, cfg); + if (!kctrl) + return -ENOMEM; + + ret = snd_ctl_add(card, kctrl); + if (ret < 0) + return ret; + + cfg->texts = texts; + cfg->max = max; + cfg->size = size; + cfg->accept = accept; + cfg->update = update; + cfg->card = card; + cfg->kctrl = kctrl; + cfg->io = io; + cfg->mod = mod; + + return 0; +} + +/* + * snd_soc_component + */ + +#define PREALLOC_BUFFER (32 * 1024) +#define PREALLOC_BUFFER_MAX (32 * 1024) + +static int rsnd_preallocate_pages(struct snd_soc_pcm_runtime *rtd, + struct rsnd_dai_stream *io, + int stream) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct device *dev = rsnd_priv_to_dev(priv); + struct snd_pcm_substream *substream; + int err; + + /* + * use Audio-DMAC dev if we can use IPMMU + * see + * rsnd_dmaen_attach() + */ + if (io->dmac_dev) + dev = io->dmac_dev; + + for (substream = rtd->pcm->streams[stream].substream; + substream; + substream = substream->next) { + err = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV, + dev, + PREALLOC_BUFFER, PREALLOC_BUFFER_MAX); + if (err < 0) + return err; + } + + return 0; +} + +static int rsnd_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct rsnd_dai *rdai = rsnd_dai_to_rdai(dai); + int ret; + + ret = rsnd_dai_call(pcm_new, &rdai->playback, rtd); + if (ret) + return ret; + + ret = rsnd_dai_call(pcm_new, &rdai->capture, rtd); + if (ret) + return ret; + + ret = rsnd_preallocate_pages(rtd, &rdai->playback, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + + ret = rsnd_preallocate_pages(rtd, &rdai->capture, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + + return 0; +} + +static const struct snd_soc_component_driver rsnd_soc_component = { + .ops = &rsnd_pcm_ops, + .pcm_new = rsnd_pcm_new, + .name = "rsnd", +}; + +static int rsnd_rdai_continuance_probe(struct rsnd_priv *priv, + struct rsnd_dai_stream *io) +{ + int ret; + + ret = rsnd_dai_call(probe, io, priv); + if (ret == -EAGAIN) { + struct rsnd_mod *ssi_mod = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *mod; + int i; + + /* + * Fallback to PIO mode + */ + + /* + * call "remove" for SSI/SRC/DVC + * SSI will be switch to PIO mode if it was DMA mode + * see + * rsnd_dma_init() + * rsnd_ssi_fallback() + */ + rsnd_dai_call(remove, io, priv); + + /* + * remove all mod from io + * and, re connect ssi + */ + for_each_rsnd_mod(i, mod, io) + rsnd_dai_disconnect(mod, io, i); + rsnd_dai_connect(ssi_mod, io, RSND_MOD_SSI); + + /* + * fallback + */ + rsnd_dai_call(fallback, io, priv); + + /* + * retry to "probe". + * DAI has SSI which is PIO mode only now. + */ + ret = rsnd_dai_call(probe, io, priv); + } + + return ret; +} + +/* + * rsnd probe + */ +static int rsnd_probe(struct platform_device *pdev) +{ + struct rsnd_priv *priv; + struct device *dev = &pdev->dev; + struct rsnd_dai *rdai; + int (*probe_func[])(struct rsnd_priv *priv) = { + rsnd_gen_probe, + rsnd_dma_probe, + rsnd_ssi_probe, + rsnd_ssiu_probe, + rsnd_src_probe, + rsnd_ctu_probe, + rsnd_mix_probe, + rsnd_dvc_probe, + rsnd_cmd_probe, + rsnd_adg_probe, + rsnd_dai_probe, + }; + int ret, i; + + /* + * init priv data + */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENODEV; + + priv->pdev = pdev; + priv->flags = (unsigned long)of_device_get_match_data(dev); + spin_lock_init(&priv->lock); + + /* + * init each module + */ + for (i = 0; i < ARRAY_SIZE(probe_func); i++) { + ret = probe_func[i](priv); + if (ret) + return ret; + } + + for_each_rsnd_dai(rdai, priv, i) { + ret = rsnd_rdai_continuance_probe(priv, &rdai->playback); + if (ret) + goto exit_snd_probe; + + ret = rsnd_rdai_continuance_probe(priv, &rdai->capture); + if (ret) + goto exit_snd_probe; + } + + dev_set_drvdata(dev, priv); + + /* + * asoc register + */ + ret = devm_snd_soc_register_component(dev, &rsnd_soc_component, + priv->daidrv, rsnd_rdai_nr(priv)); + if (ret < 0) { + dev_err(dev, "cannot snd dai register\n"); + goto exit_snd_probe; + } + + pm_runtime_enable(dev); + + dev_info(dev, "probed\n"); + return ret; + +exit_snd_probe: + for_each_rsnd_dai(rdai, priv, i) { + rsnd_dai_call(remove, &rdai->playback, priv); + rsnd_dai_call(remove, &rdai->capture, priv); + } + + /* + * adg is very special mod which can't use rsnd_dai_call(remove), + * and it registers ADG clock on probe. + * It should be unregister if probe failed. + * Mainly it is assuming -EPROBE_DEFER case + */ + rsnd_adg_remove(priv); + + return ret; +} + +static int rsnd_remove(struct platform_device *pdev) +{ + struct rsnd_priv *priv = dev_get_drvdata(&pdev->dev); + struct rsnd_dai *rdai; + void (*remove_func[])(struct rsnd_priv *priv) = { + rsnd_ssi_remove, + rsnd_ssiu_remove, + rsnd_src_remove, + rsnd_ctu_remove, + rsnd_mix_remove, + rsnd_dvc_remove, + rsnd_cmd_remove, + rsnd_adg_remove, + }; + int ret = 0, i; + + snd_soc_disconnect_sync(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + for_each_rsnd_dai(rdai, priv, i) { + ret |= rsnd_dai_call(remove, &rdai->playback, priv); + ret |= rsnd_dai_call(remove, &rdai->capture, priv); + } + + for (i = 0; i < ARRAY_SIZE(remove_func); i++) + remove_func[i](priv); + + return ret; +} + +static int __maybe_unused rsnd_suspend(struct device *dev) +{ + struct rsnd_priv *priv = dev_get_drvdata(dev); + + rsnd_adg_clk_disable(priv); + + return 0; +} + +static int __maybe_unused rsnd_resume(struct device *dev) +{ + struct rsnd_priv *priv = dev_get_drvdata(dev); + + rsnd_adg_clk_enable(priv); + + return 0; +} + +static const struct dev_pm_ops rsnd_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rsnd_suspend, rsnd_resume) +}; + +static struct platform_driver rsnd_driver = { + .driver = { + .name = "rcar_sound", + .pm = &rsnd_pm_ops, + .of_match_table = rsnd_of_match, + }, + .probe = rsnd_probe, + .remove = rsnd_remove, +}; +module_platform_driver(rsnd_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car audio driver"); +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_ALIAS("platform:rcar-pcm-audio"); diff --git a/sound/soc/sh/rcar/ctu.c b/sound/soc/sh/rcar/ctu.c new file mode 100644 index 000000000..6a55aa753 --- /dev/null +++ b/sound/soc/sh/rcar/ctu.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ctu.c +// +// Copyright (c) 2015 Kuninori Morimoto + +#include "rsnd.h" + +#define CTU_NAME_SIZE 16 +#define CTU_NAME "ctu" + +/* + * User needs to setup CTU by amixer, and its settings are + * based on below registers + * + * CTUn_CPMDR : amixser set "CTU Pass" + * CTUn_SV0xR : amixser set "CTU SV0" + * CTUn_SV1xR : amixser set "CTU SV1" + * CTUn_SV2xR : amixser set "CTU SV2" + * CTUn_SV3xR : amixser set "CTU SV3" + * + * [CTU Pass] + * 0000: default + * 0001: Connect input data of channel 0 + * 0010: Connect input data of channel 1 + * 0011: Connect input data of channel 2 + * 0100: Connect input data of channel 3 + * 0101: Connect input data of channel 4 + * 0110: Connect input data of channel 5 + * 0111: Connect input data of channel 6 + * 1000: Connect input data of channel 7 + * 1001: Connect calculated data by scale values of matrix row 0 + * 1010: Connect calculated data by scale values of matrix row 1 + * 1011: Connect calculated data by scale values of matrix row 2 + * 1100: Connect calculated data by scale values of matrix row 3 + * + * [CTU SVx] + * [Output0] = [SV00, SV01, SV02, SV03, SV04, SV05, SV06, SV07] + * [Output1] = [SV10, SV11, SV12, SV13, SV14, SV15, SV16, SV17] + * [Output2] = [SV20, SV21, SV22, SV23, SV24, SV25, SV26, SV27] + * [Output3] = [SV30, SV31, SV32, SV33, SV34, SV35, SV36, SV37] + * [Output4] = [ 0, 0, 0, 0, 0, 0, 0, 0 ] + * [Output5] = [ 0, 0, 0, 0, 0, 0, 0, 0 ] + * [Output6] = [ 0, 0, 0, 0, 0, 0, 0, 0 ] + * [Output7] = [ 0, 0, 0, 0, 0, 0, 0, 0 ] + * + * [SVxx] + * Plus Minus + * value time dB value time dB + * ----------------------------------------------------------------------- + * H'7F_FFFF 2 6 H'80_0000 2 6 + * ... + * H'40_0000 1 0 H'C0_0000 1 0 + * ... + * H'00_0001 2.38 x 10^-7 -132 + * H'00_0000 0 Mute H'FF_FFFF 2.38 x 10^-7 -132 + * + * + * Ex) Input ch -> Output ch + * 1ch -> 0ch + * 0ch -> 1ch + * + * amixer set "CTU Reset" on + * amixer set "CTU Pass" 9,10 + * amixer set "CTU SV0" 0,4194304 + * amixer set "CTU SV1" 4194304,0 + * or + * amixer set "CTU Reset" on + * amixer set "CTU Pass" 2,1 + */ + +struct rsnd_ctu { + struct rsnd_mod mod; + struct rsnd_kctrl_cfg_m pass; + struct rsnd_kctrl_cfg_m sv0; + struct rsnd_kctrl_cfg_m sv1; + struct rsnd_kctrl_cfg_m sv2; + struct rsnd_kctrl_cfg_m sv3; + struct rsnd_kctrl_cfg_s reset; + int channels; + u32 flags; +}; + +#define KCTRL_INITIALIZED (1 << 0) + +#define rsnd_ctu_nr(priv) ((priv)->ctu_nr) +#define for_each_rsnd_ctu(pos, priv, i) \ + for ((i) = 0; \ + ((i) < rsnd_ctu_nr(priv)) && \ + ((pos) = (struct rsnd_ctu *)(priv)->ctu + i); \ + i++) + +#define rsnd_mod_to_ctu(_mod) \ + container_of((_mod), struct rsnd_ctu, mod) + +#define rsnd_ctu_get(priv, id) ((struct rsnd_ctu *)(priv->ctu) + id) + +static void rsnd_ctu_activation(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, CTU_SWRSR, 0); + rsnd_mod_write(mod, CTU_SWRSR, 1); +} + +static void rsnd_ctu_halt(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, CTU_CTUIR, 1); + rsnd_mod_write(mod, CTU_SWRSR, 0); +} + +int rsnd_ctu_converted_channel(struct rsnd_mod *mod) +{ + struct rsnd_ctu *ctu = rsnd_mod_to_ctu(mod); + + return ctu->channels; +} + +static int rsnd_ctu_probe_(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + return rsnd_cmd_attach(io, rsnd_mod_id(mod) / 4); +} + +static void rsnd_ctu_value_init(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_ctu *ctu = rsnd_mod_to_ctu(mod); + u32 cpmdr = 0; + u32 scmdr = 0; + int i; + + for (i = 0; i < RSND_MAX_CHANNELS; i++) { + u32 val = rsnd_kctrl_valm(ctu->pass, i); + + cpmdr |= val << (28 - (i * 4)); + + if ((val > 0x8) && (scmdr < (val - 0x8))) + scmdr = val - 0x8; + } + + rsnd_mod_write(mod, CTU_CTUIR, 1); + + rsnd_mod_write(mod, CTU_ADINR, rsnd_runtime_channel_original(io)); + + rsnd_mod_write(mod, CTU_CPMDR, cpmdr); + + rsnd_mod_write(mod, CTU_SCMDR, scmdr); + + if (scmdr > 0) { + rsnd_mod_write(mod, CTU_SV00R, rsnd_kctrl_valm(ctu->sv0, 0)); + rsnd_mod_write(mod, CTU_SV01R, rsnd_kctrl_valm(ctu->sv0, 1)); + rsnd_mod_write(mod, CTU_SV02R, rsnd_kctrl_valm(ctu->sv0, 2)); + rsnd_mod_write(mod, CTU_SV03R, rsnd_kctrl_valm(ctu->sv0, 3)); + rsnd_mod_write(mod, CTU_SV04R, rsnd_kctrl_valm(ctu->sv0, 4)); + rsnd_mod_write(mod, CTU_SV05R, rsnd_kctrl_valm(ctu->sv0, 5)); + rsnd_mod_write(mod, CTU_SV06R, rsnd_kctrl_valm(ctu->sv0, 6)); + rsnd_mod_write(mod, CTU_SV07R, rsnd_kctrl_valm(ctu->sv0, 7)); + } + if (scmdr > 1) { + rsnd_mod_write(mod, CTU_SV10R, rsnd_kctrl_valm(ctu->sv1, 0)); + rsnd_mod_write(mod, CTU_SV11R, rsnd_kctrl_valm(ctu->sv1, 1)); + rsnd_mod_write(mod, CTU_SV12R, rsnd_kctrl_valm(ctu->sv1, 2)); + rsnd_mod_write(mod, CTU_SV13R, rsnd_kctrl_valm(ctu->sv1, 3)); + rsnd_mod_write(mod, CTU_SV14R, rsnd_kctrl_valm(ctu->sv1, 4)); + rsnd_mod_write(mod, CTU_SV15R, rsnd_kctrl_valm(ctu->sv1, 5)); + rsnd_mod_write(mod, CTU_SV16R, rsnd_kctrl_valm(ctu->sv1, 6)); + rsnd_mod_write(mod, CTU_SV17R, rsnd_kctrl_valm(ctu->sv1, 7)); + } + if (scmdr > 2) { + rsnd_mod_write(mod, CTU_SV20R, rsnd_kctrl_valm(ctu->sv2, 0)); + rsnd_mod_write(mod, CTU_SV21R, rsnd_kctrl_valm(ctu->sv2, 1)); + rsnd_mod_write(mod, CTU_SV22R, rsnd_kctrl_valm(ctu->sv2, 2)); + rsnd_mod_write(mod, CTU_SV23R, rsnd_kctrl_valm(ctu->sv2, 3)); + rsnd_mod_write(mod, CTU_SV24R, rsnd_kctrl_valm(ctu->sv2, 4)); + rsnd_mod_write(mod, CTU_SV25R, rsnd_kctrl_valm(ctu->sv2, 5)); + rsnd_mod_write(mod, CTU_SV26R, rsnd_kctrl_valm(ctu->sv2, 6)); + rsnd_mod_write(mod, CTU_SV27R, rsnd_kctrl_valm(ctu->sv2, 7)); + } + if (scmdr > 3) { + rsnd_mod_write(mod, CTU_SV30R, rsnd_kctrl_valm(ctu->sv3, 0)); + rsnd_mod_write(mod, CTU_SV31R, rsnd_kctrl_valm(ctu->sv3, 1)); + rsnd_mod_write(mod, CTU_SV32R, rsnd_kctrl_valm(ctu->sv3, 2)); + rsnd_mod_write(mod, CTU_SV33R, rsnd_kctrl_valm(ctu->sv3, 3)); + rsnd_mod_write(mod, CTU_SV34R, rsnd_kctrl_valm(ctu->sv3, 4)); + rsnd_mod_write(mod, CTU_SV35R, rsnd_kctrl_valm(ctu->sv3, 5)); + rsnd_mod_write(mod, CTU_SV36R, rsnd_kctrl_valm(ctu->sv3, 6)); + rsnd_mod_write(mod, CTU_SV37R, rsnd_kctrl_valm(ctu->sv3, 7)); + } + + rsnd_mod_write(mod, CTU_CTUIR, 0); +} + +static void rsnd_ctu_value_reset(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_ctu *ctu = rsnd_mod_to_ctu(mod); + int i; + + if (!rsnd_kctrl_vals(ctu->reset)) + return; + + for (i = 0; i < RSND_MAX_CHANNELS; i++) { + rsnd_kctrl_valm(ctu->pass, i) = 0; + rsnd_kctrl_valm(ctu->sv0, i) = 0; + rsnd_kctrl_valm(ctu->sv1, i) = 0; + rsnd_kctrl_valm(ctu->sv2, i) = 0; + rsnd_kctrl_valm(ctu->sv3, i) = 0; + } + rsnd_kctrl_vals(ctu->reset) = 0; +} + +static int rsnd_ctu_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_mod_power_on(mod); + + rsnd_ctu_activation(mod); + + rsnd_ctu_value_init(io, mod); + + return 0; +} + +static int rsnd_ctu_quit(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_ctu_halt(mod); + + rsnd_mod_power_off(mod); + + return 0; +} + +static int rsnd_ctu_hw_params(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *fe_params) +{ + struct rsnd_ctu *ctu = rsnd_mod_to_ctu(mod); + struct snd_soc_pcm_runtime *fe = substream->private_data; + + /* + * CTU assumes that it is used under DPCM if user want to use + * channel transfer. Then, CTU should be FE. + * And then, this function will be called *after* BE settings. + * this means, each BE already has fixuped hw_params. + * see + * dpcm_fe_dai_hw_params() + * dpcm_be_dai_hw_params() + */ + ctu->channels = 0; + if (fe->dai_link->dynamic) { + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + struct snd_soc_dpcm *dpcm; + struct snd_pcm_hw_params *be_params; + int stream = substream->stream; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + be_params = &dpcm->hw_params; + if (params_channels(fe_params) != params_channels(be_params)) + ctu->channels = params_channels(be_params); + } + + dev_dbg(dev, "CTU convert channels %d\n", ctu->channels); + } + + return 0; +} + +static int rsnd_ctu_pcm_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd) +{ + struct rsnd_ctu *ctu = rsnd_mod_to_ctu(mod); + int ret; + + if (rsnd_flags_has(ctu, KCTRL_INITIALIZED)) + return 0; + + /* CTU Pass */ + ret = rsnd_kctrl_new_m(mod, io, rtd, "CTU Pass", + rsnd_kctrl_accept_anytime, + NULL, + &ctu->pass, RSND_MAX_CHANNELS, + 0xC); + + /* ROW0 */ + ret = rsnd_kctrl_new_m(mod, io, rtd, "CTU SV0", + rsnd_kctrl_accept_anytime, + NULL, + &ctu->sv0, RSND_MAX_CHANNELS, + 0x00FFFFFF); + if (ret < 0) + return ret; + + /* ROW1 */ + ret = rsnd_kctrl_new_m(mod, io, rtd, "CTU SV1", + rsnd_kctrl_accept_anytime, + NULL, + &ctu->sv1, RSND_MAX_CHANNELS, + 0x00FFFFFF); + if (ret < 0) + return ret; + + /* ROW2 */ + ret = rsnd_kctrl_new_m(mod, io, rtd, "CTU SV2", + rsnd_kctrl_accept_anytime, + NULL, + &ctu->sv2, RSND_MAX_CHANNELS, + 0x00FFFFFF); + if (ret < 0) + return ret; + + /* ROW3 */ + ret = rsnd_kctrl_new_m(mod, io, rtd, "CTU SV3", + rsnd_kctrl_accept_anytime, + NULL, + &ctu->sv3, RSND_MAX_CHANNELS, + 0x00FFFFFF); + if (ret < 0) + return ret; + + /* Reset */ + ret = rsnd_kctrl_new_s(mod, io, rtd, "CTU Reset", + rsnd_kctrl_accept_anytime, + rsnd_ctu_value_reset, + &ctu->reset, 1); + + rsnd_flags_set(ctu, KCTRL_INITIALIZED); + + return ret; +} + +static struct rsnd_mod_ops rsnd_ctu_ops = { + .name = CTU_NAME, + .probe = rsnd_ctu_probe_, + .init = rsnd_ctu_init, + .quit = rsnd_ctu_quit, + .hw_params = rsnd_ctu_hw_params, + .pcm_new = rsnd_ctu_pcm_new, +}; + +struct rsnd_mod *rsnd_ctu_mod_get(struct rsnd_priv *priv, int id) +{ + if (WARN_ON(id < 0 || id >= rsnd_ctu_nr(priv))) + id = 0; + + return rsnd_mod_get(rsnd_ctu_get(priv, id)); +} + +int rsnd_ctu_probe(struct rsnd_priv *priv) +{ + struct device_node *node; + struct device_node *np; + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_ctu *ctu; + struct clk *clk; + char name[CTU_NAME_SIZE]; + int i, nr, ret; + + /* This driver doesn't support Gen1 at this point */ + if (rsnd_is_gen1(priv)) + return 0; + + node = rsnd_ctu_of_node(priv); + if (!node) + return 0; /* not used is not error */ + + nr = of_get_child_count(node); + if (!nr) { + ret = -EINVAL; + goto rsnd_ctu_probe_done; + } + + ctu = devm_kcalloc(dev, nr, sizeof(*ctu), GFP_KERNEL); + if (!ctu) { + ret = -ENOMEM; + goto rsnd_ctu_probe_done; + } + + priv->ctu_nr = nr; + priv->ctu = ctu; + + i = 0; + ret = 0; + for_each_child_of_node(node, np) { + ctu = rsnd_ctu_get(priv, i); + + /* + * CTU00, CTU01, CTU02, CTU03 => CTU0 + * CTU10, CTU11, CTU12, CTU13 => CTU1 + */ + snprintf(name, CTU_NAME_SIZE, "%s.%d", + CTU_NAME, i / 4); + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(np); + goto rsnd_ctu_probe_done; + } + + ret = rsnd_mod_init(priv, rsnd_mod_get(ctu), &rsnd_ctu_ops, + clk, rsnd_mod_get_status, RSND_MOD_CTU, i); + if (ret) { + of_node_put(np); + goto rsnd_ctu_probe_done; + } + + i++; + } + + +rsnd_ctu_probe_done: + of_node_put(node); + + return ret; +} + +void rsnd_ctu_remove(struct rsnd_priv *priv) +{ + struct rsnd_ctu *ctu; + int i; + + for_each_rsnd_ctu(ctu, priv, i) { + rsnd_mod_quit(rsnd_mod_get(ctu)); + } +} diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c new file mode 100644 index 000000000..d65ea7bc4 --- /dev/null +++ b/sound/soc/sh/rcar/dma.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car Audio DMAC support +// +// Copyright (C) 2015 Renesas Electronics Corp. +// Copyright (c) 2015 Kuninori Morimoto + +#include +#include +#include "rsnd.h" + +/* + * Audio DMAC peri peri register + */ +#define PDMASAR 0x00 +#define PDMADAR 0x04 +#define PDMACHCR 0x0c + +/* PDMACHCR */ +#define PDMACHCR_DE (1 << 0) + + +struct rsnd_dmaen { + struct dma_chan *chan; + dma_cookie_t cookie; + unsigned int dma_len; +}; + +struct rsnd_dmapp { + int dmapp_id; + u32 chcr; +}; + +struct rsnd_dma { + struct rsnd_mod mod; + struct rsnd_mod *mod_from; + struct rsnd_mod *mod_to; + dma_addr_t src_addr; + dma_addr_t dst_addr; + union { + struct rsnd_dmaen en; + struct rsnd_dmapp pp; + } dma; +}; + +struct rsnd_dma_ctrl { + void __iomem *base; + int dmaen_num; + int dmapp_num; +}; + +#define rsnd_priv_to_dmac(p) ((struct rsnd_dma_ctrl *)(p)->dma) +#define rsnd_mod_to_dma(_mod) container_of((_mod), struct rsnd_dma, mod) +#define rsnd_dma_to_dmaen(dma) (&(dma)->dma.en) +#define rsnd_dma_to_dmapp(dma) (&(dma)->dma.pp) + +/* for DEBUG */ +static struct rsnd_mod_ops mem_ops = { + .name = "mem", +}; + +static struct rsnd_mod mem = { +}; + +/* + * Audio DMAC + */ +static void __rsnd_dmaen_complete(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + if (rsnd_io_is_working(io)) + rsnd_dai_period_elapsed(io); +} + +static void rsnd_dmaen_complete(void *data) +{ + struct rsnd_mod *mod = data; + + rsnd_mod_interrupt(mod, __rsnd_dmaen_complete); +} + +static struct dma_chan *rsnd_dmaen_request_channel(struct rsnd_dai_stream *io, + struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to) +{ + if ((!mod_from && !mod_to) || + (mod_from && mod_to)) + return NULL; + + if (mod_from) + return rsnd_mod_dma_req(io, mod_from); + else + return rsnd_mod_dma_req(io, mod_to); +} + +static int rsnd_dmaen_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + + if (dmaen->chan) + dmaengine_terminate_all(dmaen->chan); + + return 0; +} + +static int rsnd_dmaen_nolock_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + + /* + * DMAEngine release uses mutex lock. + * Thus, it shouldn't be called under spinlock. + * Let's call it under nolock_start + */ + if (dmaen->chan) + dma_release_channel(dmaen->chan); + + dmaen->chan = NULL; + + return 0; +} + +static int rsnd_dmaen_nolock_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + struct device *dev = rsnd_priv_to_dev(priv); + + if (dmaen->chan) { + dev_err(dev, "it already has dma channel\n"); + return -EIO; + } + + /* + * DMAEngine request uses mutex lock. + * Thus, it shouldn't be called under spinlock. + * Let's call it under nolock_start + */ + dmaen->chan = rsnd_dmaen_request_channel(io, + dma->mod_from, + dma->mod_to); + if (IS_ERR_OR_NULL(dmaen->chan)) { + dmaen->chan = NULL; + dev_err(dev, "can't get dma channel\n"); + return -EIO; + } + + return 0; +} + +static int rsnd_dmaen_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + struct snd_pcm_substream *substream = io->substream; + struct device *dev = rsnd_priv_to_dev(priv); + struct dma_async_tx_descriptor *desc; + struct dma_slave_config cfg = {}; + int is_play = rsnd_io_is_play(io); + int ret; + + cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + cfg.src_addr = dma->src_addr; + cfg.dst_addr = dma->dst_addr; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + dev_dbg(dev, "%s[%d] %pad -> %pad\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), + &cfg.src_addr, &cfg.dst_addr); + + ret = dmaengine_slave_config(dmaen->chan, &cfg); + if (ret < 0) + return ret; + + desc = dmaengine_prep_dma_cyclic(dmaen->chan, + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!desc) { + dev_err(dev, "dmaengine_prep_slave_sg() fail\n"); + return -EIO; + } + + desc->callback = rsnd_dmaen_complete; + desc->callback_param = rsnd_mod_get(dma); + + dmaen->dma_len = snd_pcm_lib_buffer_bytes(substream); + + dmaen->cookie = dmaengine_submit(desc); + if (dmaen->cookie < 0) { + dev_err(dev, "dmaengine_submit() fail\n"); + return -EIO; + } + + dma_async_issue_pending(dmaen->chan); + + return 0; +} + +struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node, + struct rsnd_mod *mod, char *name) +{ + struct dma_chan *chan = NULL; + struct device_node *np; + int i = 0; + + for_each_child_of_node(of_node, np) { + if (i == rsnd_mod_id(mod) && (!chan)) + chan = of_dma_request_slave_channel(np, name); + i++; + } + + /* It should call of_node_put(), since, it is rsnd_xxx_of_node() */ + of_node_put(of_node); + + return chan; +} + +static int rsnd_dmaen_attach(struct rsnd_dai_stream *io, + struct rsnd_dma *dma, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct dma_chan *chan; + + /* try to get DMAEngine channel */ + chan = rsnd_dmaen_request_channel(io, mod_from, mod_to); + if (IS_ERR_OR_NULL(chan)) { + /* Let's follow when -EPROBE_DEFER case */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return PTR_ERR(chan); + + /* + * DMA failed. try to PIO mode + * see + * rsnd_ssi_fallback() + * rsnd_rdai_continuance_probe() + */ + return -EAGAIN; + } + + /* + * use it for IPMMU if needed + * see + * rsnd_preallocate_pages() + */ + io->dmac_dev = chan->device->dev; + + dma_release_channel(chan); + + dmac->dmaen_num++; + + return 0; +} + +static int rsnd_dmaen_pointer(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + snd_pcm_uframes_t *pointer) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + struct dma_tx_state state; + enum dma_status status; + unsigned int pos = 0; + + status = dmaengine_tx_status(dmaen->chan, dmaen->cookie, &state); + if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) { + if (state.residue > 0 && state.residue <= dmaen->dma_len) + pos = dmaen->dma_len - state.residue; + } + *pointer = bytes_to_frames(runtime, pos); + + return 0; +} + +static struct rsnd_mod_ops rsnd_dmaen_ops = { + .name = "audmac", + .nolock_start = rsnd_dmaen_nolock_start, + .nolock_stop = rsnd_dmaen_nolock_stop, + .start = rsnd_dmaen_start, + .stop = rsnd_dmaen_stop, + .pointer= rsnd_dmaen_pointer, +}; + +/* + * Audio DMAC peri peri + */ +static const u8 gen2_id_table_ssiu[] = { + 0x00, /* SSI00 */ + 0x04, /* SSI10 */ + 0x08, /* SSI20 */ + 0x0c, /* SSI3 */ + 0x0d, /* SSI4 */ + 0x0e, /* SSI5 */ + 0x0f, /* SSI6 */ + 0x10, /* SSI7 */ + 0x11, /* SSI8 */ + 0x12, /* SSI90 */ +}; +static const u8 gen2_id_table_scu[] = { + 0x2d, /* SCU_SRCI0 */ + 0x2e, /* SCU_SRCI1 */ + 0x2f, /* SCU_SRCI2 */ + 0x30, /* SCU_SRCI3 */ + 0x31, /* SCU_SRCI4 */ + 0x32, /* SCU_SRCI5 */ + 0x33, /* SCU_SRCI6 */ + 0x34, /* SCU_SRCI7 */ + 0x35, /* SCU_SRCI8 */ + 0x36, /* SCU_SRCI9 */ +}; +static const u8 gen2_id_table_cmd[] = { + 0x37, /* SCU_CMD0 */ + 0x38, /* SCU_CMD1 */ +}; + +static u32 rsnd_dmapp_get_id(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); + const u8 *entry = NULL; + int id = rsnd_mod_id(mod); + int size = 0; + + if (mod == ssi) { + entry = gen2_id_table_ssiu; + size = ARRAY_SIZE(gen2_id_table_ssiu); + } else if (mod == src) { + entry = gen2_id_table_scu; + size = ARRAY_SIZE(gen2_id_table_scu); + } else if (mod == dvc) { + entry = gen2_id_table_cmd; + size = ARRAY_SIZE(gen2_id_table_cmd); + } + + if ((!entry) || (size <= id)) { + struct device *dev = rsnd_priv_to_dev(rsnd_io_to_priv(io)); + + dev_err(dev, "unknown connection (%s[%d])\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + + /* use non-prohibited SRS number as error */ + return 0x00; /* SSI00 */ + } + + return entry[id]; +} + +static u32 rsnd_dmapp_get_chcr(struct rsnd_dai_stream *io, + struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to) +{ + return (rsnd_dmapp_get_id(io, mod_from) << 24) + + (rsnd_dmapp_get_id(io, mod_to) << 16); +} + +#define rsnd_dmapp_addr(dmac, dma, reg) \ + (dmac->base + 0x20 + reg + \ + (0x10 * rsnd_dma_to_dmapp(dma)->dmapp_id)) +static void rsnd_dmapp_write(struct rsnd_dma *dma, u32 data, u32 reg) +{ + struct rsnd_mod *mod = rsnd_mod_get(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + + dev_dbg(dev, "w %p : %08x\n", rsnd_dmapp_addr(dmac, dma, reg), data); + + iowrite32(data, rsnd_dmapp_addr(dmac, dma, reg)); +} + +static u32 rsnd_dmapp_read(struct rsnd_dma *dma, u32 reg) +{ + struct rsnd_mod *mod = rsnd_mod_get(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + + return ioread32(rsnd_dmapp_addr(dmac, dma, reg)); +} + +static void rsnd_dmapp_bset(struct rsnd_dma *dma, u32 data, u32 mask, u32 reg) +{ + struct rsnd_mod *mod = rsnd_mod_get(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + void __iomem *addr = rsnd_dmapp_addr(dmac, dma, reg); + u32 val = ioread32(addr); + + val &= ~mask; + val |= (data & mask); + + iowrite32(val, addr); +} + +static int rsnd_dmapp_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + int i; + + rsnd_dmapp_bset(dma, 0, PDMACHCR_DE, PDMACHCR); + + for (i = 0; i < 1024; i++) { + if (0 == (rsnd_dmapp_read(dma, PDMACHCR) & PDMACHCR_DE)) + return 0; + udelay(1); + } + + return -EIO; +} + +static int rsnd_dmapp_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma); + + rsnd_dmapp_write(dma, dma->src_addr, PDMASAR); + rsnd_dmapp_write(dma, dma->dst_addr, PDMADAR); + rsnd_dmapp_write(dma, dmapp->chcr, PDMACHCR); + + return 0; +} + +static int rsnd_dmapp_attach(struct rsnd_dai_stream *io, + struct rsnd_dma *dma, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) +{ + struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma); + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + + dmapp->dmapp_id = dmac->dmapp_num; + dmapp->chcr = rsnd_dmapp_get_chcr(io, mod_from, mod_to) | PDMACHCR_DE; + + dmac->dmapp_num++; + + dev_dbg(dev, "id/src/dst/chcr = %d/%pad/%pad/%08x\n", + dmapp->dmapp_id, &dma->src_addr, &dma->dst_addr, dmapp->chcr); + + return 0; +} + +static struct rsnd_mod_ops rsnd_dmapp_ops = { + .name = "audmac-pp", + .start = rsnd_dmapp_start, + .stop = rsnd_dmapp_stop, + .quit = rsnd_dmapp_stop, +}; + +/* + * Common DMAC Interface + */ + +/* + * DMA read/write register offset + * + * RSND_xxx_I_N for Audio DMAC input + * RSND_xxx_O_N for Audio DMAC output + * RSND_xxx_I_P for Audio DMAC peri peri input + * RSND_xxx_O_P for Audio DMAC peri peri output + * + * ex) R-Car H2 case + * mod / DMAC in / DMAC out / DMAC PP in / DMAC pp out + * SSI : 0xec541000 / 0xec241008 / 0xec24100c + * SSIU: 0xec541000 / 0xec100000 / 0xec100000 / 0xec400000 / 0xec400000 + * SCU : 0xec500000 / 0xec000000 / 0xec004000 / 0xec300000 / 0xec304000 + * CMD : 0xec500000 / / 0xec008000 0xec308000 + */ +#define RDMA_SSI_I_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0x8) +#define RDMA_SSI_O_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0xc) + +#define RDMA_SSIU_I_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) +#define RDMA_SSIU_O_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i)) + +#define RDMA_SSIU_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) +#define RDMA_SSIU_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i)) + +#define RDMA_SRC_I_N(addr, i) (addr ##_reg - 0x00500000 + (0x400 * i)) +#define RDMA_SRC_O_N(addr, i) (addr ##_reg - 0x004fc000 + (0x400 * i)) + +#define RDMA_SRC_I_P(addr, i) (addr ##_reg - 0x00200000 + (0x400 * i)) +#define RDMA_SRC_O_P(addr, i) (addr ##_reg - 0x001fc000 + (0x400 * i)) + +#define RDMA_CMD_O_N(addr, i) (addr ##_reg - 0x004f8000 + (0x400 * i)) +#define RDMA_CMD_O_P(addr, i) (addr ##_reg - 0x001f8000 + (0x400 * i)) + +static dma_addr_t +rsnd_gen2_dma_addr(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + int is_play, int is_from) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct device *dev = rsnd_priv_to_dev(priv); + phys_addr_t ssi_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SSI); + phys_addr_t src_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SCU); + int is_ssi = !!(rsnd_io_to_mod_ssi(io) == mod); + int use_src = !!rsnd_io_to_mod_src(io); + int use_cmd = !!rsnd_io_to_mod_dvc(io) || + !!rsnd_io_to_mod_mix(io) || + !!rsnd_io_to_mod_ctu(io); + int id = rsnd_mod_id(mod); + struct dma_addr { + dma_addr_t out_addr; + dma_addr_t in_addr; + } dma_addrs[3][2][3] = { + /* SRC */ + /* Capture */ + {{{ 0, 0 }, + { RDMA_SRC_O_N(src, id), RDMA_SRC_I_P(src, id) }, + { RDMA_CMD_O_N(src, id), RDMA_SRC_I_P(src, id) } }, + /* Playback */ + {{ 0, 0, }, + { RDMA_SRC_O_P(src, id), RDMA_SRC_I_N(src, id) }, + { RDMA_CMD_O_P(src, id), RDMA_SRC_I_N(src, id) } } + }, + /* SSI */ + /* Capture */ + {{{ RDMA_SSI_O_N(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 } }, + /* Playback */ + {{ 0, RDMA_SSI_I_N(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) } } + }, + /* SSIU */ + /* Capture */ + {{{ RDMA_SSIU_O_N(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 }, + { RDMA_SSIU_O_P(ssi, id), 0 } }, + /* Playback */ + {{ 0, RDMA_SSIU_I_N(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id) } } }, + }; + + /* it shouldn't happen */ + if (use_cmd && !use_src) + dev_err(dev, "DVC is selected without SRC\n"); + + /* use SSIU or SSI ? */ + if (is_ssi && rsnd_ssi_use_busif(io)) + is_ssi++; + + return (is_from) ? + dma_addrs[is_ssi][is_play][use_src + use_cmd].out_addr : + dma_addrs[is_ssi][is_play][use_src + use_cmd].in_addr; +} + +static dma_addr_t rsnd_dma_addr(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + int is_play, int is_from) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + + /* + * gen1 uses default DMA addr + */ + if (rsnd_is_gen1(priv)) + return 0; + + if (!mod) + return 0; + + return rsnd_gen2_dma_addr(io, mod, is_play, is_from); +} + +#define MOD_MAX (RSND_MOD_MAX + 1) /* +Memory */ +static void rsnd_dma_of_path(struct rsnd_mod *this, + struct rsnd_dai_stream *io, + int is_play, + struct rsnd_mod **mod_from, + struct rsnd_mod **mod_to) +{ + struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + struct rsnd_mod *ctu = rsnd_io_to_mod_ctu(io); + struct rsnd_mod *mix = rsnd_io_to_mod_mix(io); + struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); + struct rsnd_mod *mod[MOD_MAX]; + struct rsnd_mod *mod_start, *mod_end; + struct rsnd_priv *priv = rsnd_mod_to_priv(this); + struct device *dev = rsnd_priv_to_dev(priv); + int nr, i, idx; + + if (!ssi) + return; + + nr = 0; + for (i = 0; i < MOD_MAX; i++) { + mod[i] = NULL; + nr += !!rsnd_io_to_mod(io, i); + } + + /* + * [S] -*-> [E] + * [S] -*-> SRC -o-> [E] + * [S] -*-> SRC -> DVC -o-> [E] + * [S] -*-> SRC -> CTU -> MIX -> DVC -o-> [E] + * + * playback [S] = mem + * [E] = SSI + * + * capture [S] = SSI + * [E] = mem + * + * -*-> Audio DMAC + * -o-> Audio DMAC peri peri + */ + mod_start = (is_play) ? NULL : ssi; + mod_end = (is_play) ? ssi : NULL; + + idx = 0; + mod[idx++] = mod_start; + for (i = 1; i < nr; i++) { + if (src) { + mod[idx++] = src; + src = NULL; + } else if (ctu) { + mod[idx++] = ctu; + ctu = NULL; + } else if (mix) { + mod[idx++] = mix; + mix = NULL; + } else if (dvc) { + mod[idx++] = dvc; + dvc = NULL; + } + } + mod[idx] = mod_end; + + /* + * | SSI | SRC | + * -------------+-----+-----+ + * is_play | o | * | + * !is_play | * | o | + */ + if ((this == ssi) == (is_play)) { + *mod_from = mod[idx - 1]; + *mod_to = mod[idx]; + } else { + *mod_from = mod[0]; + *mod_to = mod[1]; + } + + dev_dbg(dev, "module connection (this is %s[%d])\n", + rsnd_mod_name(this), rsnd_mod_id(this)); + for (i = 0; i <= idx; i++) { + dev_dbg(dev, " %s[%d]%s\n", + rsnd_mod_name(mod[i] ? mod[i] : &mem), + rsnd_mod_id (mod[i] ? mod[i] : &mem), + (mod[i] == *mod_from) ? " from" : + (mod[i] == *mod_to) ? " to" : ""); + } +} + +static int rsnd_dma_alloc(struct rsnd_dai_stream *io, struct rsnd_mod *mod, + struct rsnd_mod **dma_mod) +{ + struct rsnd_mod *mod_from = NULL; + struct rsnd_mod *mod_to = NULL; + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dma *dma; + struct rsnd_mod_ops *ops; + enum rsnd_mod_type type; + int (*attach)(struct rsnd_dai_stream *io, struct rsnd_dma *dma, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to); + int is_play = rsnd_io_is_play(io); + int ret, dma_id; + + /* + * DMA failed. try to PIO mode + * see + * rsnd_ssi_fallback() + * rsnd_rdai_continuance_probe() + */ + if (!dmac) + return -EAGAIN; + + rsnd_dma_of_path(mod, io, is_play, &mod_from, &mod_to); + + /* for Gen2 or later */ + if (mod_from && mod_to) { + ops = &rsnd_dmapp_ops; + attach = rsnd_dmapp_attach; + dma_id = dmac->dmapp_num; + type = RSND_MOD_AUDMAPP; + } else { + ops = &rsnd_dmaen_ops; + attach = rsnd_dmaen_attach; + dma_id = dmac->dmaen_num; + type = RSND_MOD_AUDMA; + } + + /* for Gen1, overwrite */ + if (rsnd_is_gen1(priv)) { + ops = &rsnd_dmaen_ops; + attach = rsnd_dmaen_attach; + dma_id = dmac->dmaen_num; + type = RSND_MOD_AUDMA; + } + + dma = devm_kzalloc(dev, sizeof(*dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + + *dma_mod = rsnd_mod_get(dma); + + ret = rsnd_mod_init(priv, *dma_mod, ops, NULL, + rsnd_mod_get_status, type, dma_id); + if (ret < 0) + return ret; + + dev_dbg(dev, "%s[%d] %s[%d] -> %s[%d]\n", + rsnd_mod_name(*dma_mod), rsnd_mod_id(*dma_mod), + rsnd_mod_name(mod_from ? mod_from : &mem), + rsnd_mod_id (mod_from ? mod_from : &mem), + rsnd_mod_name(mod_to ? mod_to : &mem), + rsnd_mod_id (mod_to ? mod_to : &mem)); + + ret = attach(io, dma, mod_from, mod_to); + if (ret < 0) + return ret; + + dma->src_addr = rsnd_dma_addr(io, mod_from, is_play, 1); + dma->dst_addr = rsnd_dma_addr(io, mod_to, is_play, 0); + dma->mod_from = mod_from; + dma->mod_to = mod_to; + + return 0; +} + +int rsnd_dma_attach(struct rsnd_dai_stream *io, struct rsnd_mod *mod, + struct rsnd_mod **dma_mod) +{ + if (!(*dma_mod)) { + int ret = rsnd_dma_alloc(io, mod, dma_mod); + + if (ret < 0) + return ret; + } + + return rsnd_dai_connect(*dma_mod, io, (*dma_mod)->type); +} + +int rsnd_dma_probe(struct rsnd_priv *priv) +{ + struct platform_device *pdev = rsnd_priv_to_pdev(priv); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dma_ctrl *dmac; + struct resource *res; + + /* + * for Gen1 + */ + if (rsnd_is_gen1(priv)) + return 0; + + /* + * for Gen2 or later + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audmapp"); + dmac = devm_kzalloc(dev, sizeof(*dmac), GFP_KERNEL); + if (!dmac || !res) { + dev_err(dev, "dma allocate failed\n"); + return 0; /* it will be PIO mode */ + } + + dmac->dmapp_num = 0; + dmac->base = devm_ioremap_resource(dev, res); + if (IS_ERR(dmac->base)) + return PTR_ERR(dmac->base); + + priv->dma = dmac; + + /* dummy mem mod for debug */ + return rsnd_mod_init(NULL, &mem, &mem_ops, NULL, NULL, 0, 0); +} diff --git a/sound/soc/sh/rcar/dvc.c b/sound/soc/sh/rcar/dvc.c new file mode 100644 index 000000000..024ece46b --- /dev/null +++ b/sound/soc/sh/rcar/dvc.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car DVC support +// +// Copyright (C) 2014 Renesas Solutions Corp. +// Kuninori Morimoto + +/* + * Playback Volume + * amixer set "DVC Out" 100% + * + * Capture Volume + * amixer set "DVC In" 100% + * + * Playback Mute + * amixer set "DVC Out Mute" on + * + * Capture Mute + * amixer set "DVC In Mute" on + * + * Volume Ramp + * amixer set "DVC Out Ramp Up Rate" "0.125 dB/64 steps" + * amixer set "DVC Out Ramp Down Rate" "0.125 dB/512 steps" + * amixer set "DVC Out Ramp" on + * aplay xxx.wav & + * amixer set "DVC Out" 80% // Volume Down + * amixer set "DVC Out" 100% // Volume Up + */ + +#include "rsnd.h" + +#define RSND_DVC_NAME_SIZE 16 + +#define DVC_NAME "dvc" + +struct rsnd_dvc { + struct rsnd_mod mod; + struct rsnd_kctrl_cfg_m volume; + struct rsnd_kctrl_cfg_m mute; + struct rsnd_kctrl_cfg_s ren; /* Ramp Enable */ + struct rsnd_kctrl_cfg_s rup; /* Ramp Rate Up */ + struct rsnd_kctrl_cfg_s rdown; /* Ramp Rate Down */ +}; + +#define rsnd_dvc_get(priv, id) ((struct rsnd_dvc *)(priv->dvc) + id) +#define rsnd_dvc_nr(priv) ((priv)->dvc_nr) + +#define rsnd_mod_to_dvc(_mod) \ + container_of((_mod), struct rsnd_dvc, mod) + +#define for_each_rsnd_dvc(pos, priv, i) \ + for ((i) = 0; \ + ((i) < rsnd_dvc_nr(priv)) && \ + ((pos) = (struct rsnd_dvc *)(priv)->dvc + i); \ + i++) + +static void rsnd_dvc_activation(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, DVC_SWRSR, 0); + rsnd_mod_write(mod, DVC_SWRSR, 1); +} + +static void rsnd_dvc_halt(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, DVC_DVUIR, 1); + rsnd_mod_write(mod, DVC_SWRSR, 0); +} + +#define rsnd_dvc_get_vrpdr(dvc) (rsnd_kctrl_vals(dvc->rup) << 8 | \ + rsnd_kctrl_vals(dvc->rdown)) +#define rsnd_dvc_get_vrdbr(dvc) (0x3ff - (rsnd_kctrl_valm(dvc->volume, 0) >> 13)) + +static void rsnd_dvc_volume_parameter(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + u32 val[RSND_MAX_CHANNELS]; + int i; + + /* Enable Ramp */ + if (rsnd_kctrl_vals(dvc->ren)) + for (i = 0; i < RSND_MAX_CHANNELS; i++) + val[i] = rsnd_kctrl_max(dvc->volume); + else + for (i = 0; i < RSND_MAX_CHANNELS; i++) + val[i] = rsnd_kctrl_valm(dvc->volume, i); + + /* Enable Digital Volume */ + rsnd_mod_write(mod, DVC_VOL0R, val[0]); + rsnd_mod_write(mod, DVC_VOL1R, val[1]); + rsnd_mod_write(mod, DVC_VOL2R, val[2]); + rsnd_mod_write(mod, DVC_VOL3R, val[3]); + rsnd_mod_write(mod, DVC_VOL4R, val[4]); + rsnd_mod_write(mod, DVC_VOL5R, val[5]); + rsnd_mod_write(mod, DVC_VOL6R, val[6]); + rsnd_mod_write(mod, DVC_VOL7R, val[7]); +} + +static void rsnd_dvc_volume_init(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + u32 adinr = 0; + u32 dvucr = 0; + u32 vrctr = 0; + u32 vrpdr = 0; + u32 vrdbr = 0; + + adinr = rsnd_get_adinr_bit(mod, io) | + rsnd_runtime_channel_after_ctu(io); + + /* Enable Digital Volume, Zero Cross Mute Mode */ + dvucr |= 0x101; + + /* Enable Ramp */ + if (rsnd_kctrl_vals(dvc->ren)) { + dvucr |= 0x10; + + /* + * FIXME !! + * use scale-downed Digital Volume + * as Volume Ramp + * 7F FFFF -> 3FF + */ + vrctr = 0xff; + vrpdr = rsnd_dvc_get_vrpdr(dvc); + vrdbr = rsnd_dvc_get_vrdbr(dvc); + } + + /* Initialize operation */ + rsnd_mod_write(mod, DVC_DVUIR, 1); + + /* General Information */ + rsnd_mod_write(mod, DVC_ADINR, adinr); + rsnd_mod_write(mod, DVC_DVUCR, dvucr); + + /* Volume Ramp Parameter */ + rsnd_mod_write(mod, DVC_VRCTR, vrctr); + rsnd_mod_write(mod, DVC_VRPDR, vrpdr); + rsnd_mod_write(mod, DVC_VRDBR, vrdbr); + + /* Digital Volume Function Parameter */ + rsnd_dvc_volume_parameter(io, mod); + + /* cancel operation */ + rsnd_mod_write(mod, DVC_DVUIR, 0); +} + +static void rsnd_dvc_volume_update(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + u32 zcmcr = 0; + u32 vrpdr = 0; + u32 vrdbr = 0; + int i; + + for (i = 0; i < rsnd_kctrl_size(dvc->mute); i++) + zcmcr |= (!!rsnd_kctrl_valm(dvc->mute, i)) << i; + + if (rsnd_kctrl_vals(dvc->ren)) { + vrpdr = rsnd_dvc_get_vrpdr(dvc); + vrdbr = rsnd_dvc_get_vrdbr(dvc); + } + + /* Disable DVC Register access */ + rsnd_mod_write(mod, DVC_DVUER, 0); + + /* Zero Cross Mute Function */ + rsnd_mod_write(mod, DVC_ZCMCR, zcmcr); + + /* Volume Ramp Function */ + rsnd_mod_write(mod, DVC_VRPDR, vrpdr); + rsnd_mod_write(mod, DVC_VRDBR, vrdbr); + /* add DVC_VRWTR here */ + + /* Digital Volume Function Parameter */ + rsnd_dvc_volume_parameter(io, mod); + + /* Enable DVC Register access */ + rsnd_mod_write(mod, DVC_DVUER, 1); +} + +static int rsnd_dvc_probe_(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + return rsnd_cmd_attach(io, rsnd_mod_id(mod)); +} + +static int rsnd_dvc_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_mod_power_on(mod); + + rsnd_dvc_activation(mod); + + rsnd_dvc_volume_init(io, mod); + + rsnd_dvc_volume_update(io, mod); + + return 0; +} + +static int rsnd_dvc_quit(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_dvc_halt(mod); + + rsnd_mod_power_off(mod); + + return 0; +} + +static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd) +{ + struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + int is_play = rsnd_io_is_play(io); + int channels = rsnd_rdai_channels_get(rdai); + int ret; + + /* Volume */ + ret = rsnd_kctrl_new_m(mod, io, rtd, + is_play ? + "DVC Out Playback Volume" : "DVC In Capture Volume", + rsnd_kctrl_accept_anytime, + rsnd_dvc_volume_update, + &dvc->volume, channels, + 0x00800000 - 1); + if (ret < 0) + return ret; + + /* Mute */ + ret = rsnd_kctrl_new_m(mod, io, rtd, + is_play ? + "DVC Out Mute Switch" : "DVC In Mute Switch", + rsnd_kctrl_accept_anytime, + rsnd_dvc_volume_update, + &dvc->mute, channels, + 1); + if (ret < 0) + return ret; + + /* Ramp */ + ret = rsnd_kctrl_new_s(mod, io, rtd, + is_play ? + "DVC Out Ramp Switch" : "DVC In Ramp Switch", + rsnd_kctrl_accept_anytime, + rsnd_dvc_volume_update, + &dvc->ren, 1); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_e(mod, io, rtd, + is_play ? + "DVC Out Ramp Up Rate" : "DVC In Ramp Up Rate", + rsnd_kctrl_accept_anytime, + rsnd_dvc_volume_update, + &dvc->rup, + volume_ramp_rate, + VOLUME_RAMP_MAX_DVC); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_e(mod, io, rtd, + is_play ? + "DVC Out Ramp Down Rate" : "DVC In Ramp Down Rate", + rsnd_kctrl_accept_anytime, + rsnd_dvc_volume_update, + &dvc->rdown, + volume_ramp_rate, + VOLUME_RAMP_MAX_DVC); + + if (ret < 0) + return ret; + + return 0; +} + +static struct dma_chan *rsnd_dvc_dma_req(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + + return rsnd_dma_request_channel(rsnd_dvc_of_node(priv), + mod, "tx"); +} + +static struct rsnd_mod_ops rsnd_dvc_ops = { + .name = DVC_NAME, + .dma_req = rsnd_dvc_dma_req, + .probe = rsnd_dvc_probe_, + .init = rsnd_dvc_init, + .quit = rsnd_dvc_quit, + .pcm_new = rsnd_dvc_pcm_new, +}; + +struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id) +{ + if (WARN_ON(id < 0 || id >= rsnd_dvc_nr(priv))) + id = 0; + + return rsnd_mod_get(rsnd_dvc_get(priv, id)); +} + +int rsnd_dvc_probe(struct rsnd_priv *priv) +{ + struct device_node *node; + struct device_node *np; + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dvc *dvc; + struct clk *clk; + char name[RSND_DVC_NAME_SIZE]; + int i, nr, ret; + + /* This driver doesn't support Gen1 at this point */ + if (rsnd_is_gen1(priv)) + return 0; + + node = rsnd_dvc_of_node(priv); + if (!node) + return 0; /* not used is not error */ + + nr = of_get_child_count(node); + if (!nr) { + ret = -EINVAL; + goto rsnd_dvc_probe_done; + } + + dvc = devm_kcalloc(dev, nr, sizeof(*dvc), GFP_KERNEL); + if (!dvc) { + ret = -ENOMEM; + goto rsnd_dvc_probe_done; + } + + priv->dvc_nr = nr; + priv->dvc = dvc; + + i = 0; + ret = 0; + for_each_child_of_node(node, np) { + dvc = rsnd_dvc_get(priv, i); + + snprintf(name, RSND_DVC_NAME_SIZE, "%s.%d", + DVC_NAME, i); + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(np); + goto rsnd_dvc_probe_done; + } + + ret = rsnd_mod_init(priv, rsnd_mod_get(dvc), &rsnd_dvc_ops, + clk, rsnd_mod_get_status, RSND_MOD_DVC, i); + if (ret) { + of_node_put(np); + goto rsnd_dvc_probe_done; + } + + i++; + } + +rsnd_dvc_probe_done: + of_node_put(node); + + return ret; +} + +void rsnd_dvc_remove(struct rsnd_priv *priv) +{ + struct rsnd_dvc *dvc; + int i; + + for_each_rsnd_dvc(dvc, priv, i) { + rsnd_mod_quit(rsnd_mod_get(dvc)); + } +} diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c new file mode 100644 index 000000000..0230301fe --- /dev/null +++ b/sound/soc/sh/rcar/gen.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car Gen1 SRU/SSI support +// +// Copyright (C) 2013 Renesas Solutions Corp. +// Kuninori Morimoto + +/* + * #define DEBUG + * + * you can also add below in + * ${LINUX}/drivers/base/regmap/regmap.c + * for regmap debug + * + * #define LOG_DEVICE "xxxx.rcar_sound" + */ + +#include "rsnd.h" + +struct rsnd_gen { + struct rsnd_gen_ops *ops; + + /* RSND_BASE_MAX base */ + void __iomem *base[RSND_BASE_MAX]; + phys_addr_t res[RSND_BASE_MAX]; + struct regmap *regmap[RSND_BASE_MAX]; + + /* RSND_REG_MAX base */ + struct regmap_field *regs[RSND_REG_MAX]; + const char *reg_name[RSND_REG_MAX]; +}; + +#define rsnd_priv_to_gen(p) ((struct rsnd_gen *)(p)->gen) +#define rsnd_reg_name(gen, id) ((gen)->reg_name[id]) + +struct rsnd_regmap_field_conf { + int idx; + unsigned int reg_offset; + unsigned int id_offset; + const char *reg_name; +}; + +#define RSND_REG_SET(id, offset, _id_offset, n) \ +{ \ + .idx = id, \ + .reg_offset = offset, \ + .id_offset = _id_offset, \ + .reg_name = n, \ +} +/* single address mapping */ +#define RSND_GEN_S_REG(id, offset) \ + RSND_REG_SET(RSND_REG_##id, offset, 0, #id) + +/* multi address mapping */ +#define RSND_GEN_M_REG(id, offset, _id_offset) \ + RSND_REG_SET(RSND_REG_##id, offset, _id_offset, #id) + +/* + * basic function + */ +static int rsnd_is_accessible_reg(struct rsnd_priv *priv, + struct rsnd_gen *gen, enum rsnd_reg reg) +{ + if (!gen->regs[reg]) { + struct device *dev = rsnd_priv_to_dev(priv); + + dev_err(dev, "unsupported register access %x\n", reg); + return 0; + } + + return 1; +} + +u32 rsnd_read(struct rsnd_priv *priv, + struct rsnd_mod *mod, enum rsnd_reg reg) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_gen *gen = rsnd_priv_to_gen(priv); + u32 val; + + if (!rsnd_is_accessible_reg(priv, gen, reg)) + return 0; + + regmap_fields_read(gen->regs[reg], rsnd_mod_id(mod), &val); + + dev_dbg(dev, "r %s[%d] - %-18s (%4d) : %08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), + rsnd_reg_name(gen, reg), reg, val); + + return val; +} + +void rsnd_write(struct rsnd_priv *priv, + struct rsnd_mod *mod, + enum rsnd_reg reg, u32 data) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_gen *gen = rsnd_priv_to_gen(priv); + + if (!rsnd_is_accessible_reg(priv, gen, reg)) + return; + + regmap_fields_force_write(gen->regs[reg], rsnd_mod_id(mod), data); + + dev_dbg(dev, "w %s[%d] - %-18s (%4d) : %08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), + rsnd_reg_name(gen, reg), reg, data); +} + +void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, + enum rsnd_reg reg, u32 mask, u32 data) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_gen *gen = rsnd_priv_to_gen(priv); + + if (!rsnd_is_accessible_reg(priv, gen, reg)) + return; + + regmap_fields_force_update_bits(gen->regs[reg], + rsnd_mod_id(mod), mask, data); + + dev_dbg(dev, "b %s[%d] - %-18s (%4d) : %08x/%08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), + rsnd_reg_name(gen, reg), reg, data, mask); + +} + +phys_addr_t rsnd_gen_get_phy_addr(struct rsnd_priv *priv, int reg_id) +{ + struct rsnd_gen *gen = rsnd_priv_to_gen(priv); + + return gen->res[reg_id]; +} + +#define rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf) \ + _rsnd_gen_regmap_init(priv, id_size, reg_id, name, conf, ARRAY_SIZE(conf)) +static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, + int id_size, + int reg_id, + const char *name, + const struct rsnd_regmap_field_conf *conf, + int conf_size) +{ + struct platform_device *pdev = rsnd_priv_to_pdev(priv); + struct rsnd_gen *gen = rsnd_priv_to_gen(priv); + struct device *dev = rsnd_priv_to_dev(priv); + struct resource *res; + struct regmap_config regc; + struct regmap_field *regs; + struct regmap *regmap; + struct reg_field regf; + void __iomem *base; + int i; + + memset(®c, 0, sizeof(regc)); + regc.reg_bits = 32; + regc.val_bits = 32; + regc.reg_stride = 4; + regc.name = name; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, reg_id); + if (!res) + return -ENODEV; + + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(dev, base, ®c); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* RSND_BASE_MAX base */ + gen->base[reg_id] = base; + gen->regmap[reg_id] = regmap; + gen->res[reg_id] = res->start; + + for (i = 0; i < conf_size; i++) { + + regf.reg = conf[i].reg_offset; + regf.id_offset = conf[i].id_offset; + regf.lsb = 0; + regf.msb = 31; + regf.id_size = id_size; + + regs = devm_regmap_field_alloc(dev, regmap, regf); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + /* RSND_REG_MAX base */ + gen->regs[conf[i].idx] = regs; + gen->reg_name[conf[i].idx] = conf[i].reg_name; + } + + return 0; +} + +/* + * Gen2 + */ +static int rsnd_gen2_probe(struct rsnd_priv *priv) +{ + static const struct rsnd_regmap_field_conf conf_ssiu[] = { + RSND_GEN_S_REG(SSI_MODE0, 0x800), + RSND_GEN_S_REG(SSI_MODE1, 0x804), + RSND_GEN_S_REG(SSI_MODE2, 0x808), + RSND_GEN_S_REG(SSI_CONTROL, 0x810), + RSND_GEN_S_REG(SSI_SYS_STATUS0, 0x840), + RSND_GEN_S_REG(SSI_SYS_STATUS1, 0x844), + RSND_GEN_S_REG(SSI_SYS_STATUS2, 0x848), + RSND_GEN_S_REG(SSI_SYS_STATUS3, 0x84c), + RSND_GEN_S_REG(SSI_SYS_STATUS4, 0x880), + RSND_GEN_S_REG(SSI_SYS_STATUS5, 0x884), + RSND_GEN_S_REG(SSI_SYS_STATUS6, 0x888), + RSND_GEN_S_REG(SSI_SYS_STATUS7, 0x88c), + RSND_GEN_S_REG(HDMI0_SEL, 0x9e0), + RSND_GEN_S_REG(HDMI1_SEL, 0x9e4), + + /* FIXME: it needs SSI_MODE2/3 in the future */ + RSND_GEN_M_REG(SSI_BUSIF_MODE, 0x0, 0x80), + RSND_GEN_M_REG(SSI_BUSIF_ADINR, 0x4, 0x80), + RSND_GEN_M_REG(SSI_BUSIF_DALIGN,0x8, 0x80), + RSND_GEN_M_REG(SSI_MODE, 0xc, 0x80), + RSND_GEN_M_REG(SSI_CTRL, 0x10, 0x80), + RSND_GEN_M_REG(SSI_INT_ENABLE, 0x18, 0x80), + }; + + static const struct rsnd_regmap_field_conf conf_scu[] = { + RSND_GEN_M_REG(SRC_I_BUSIF_MODE,0x0, 0x20), + RSND_GEN_M_REG(SRC_O_BUSIF_MODE,0x4, 0x20), + RSND_GEN_M_REG(SRC_BUSIF_DALIGN,0x8, 0x20), + RSND_GEN_M_REG(SRC_ROUTE_MODE0, 0xc, 0x20), + RSND_GEN_M_REG(SRC_CTRL, 0x10, 0x20), + RSND_GEN_M_REG(SRC_INT_ENABLE0, 0x18, 0x20), + RSND_GEN_M_REG(CMD_BUSIF_MODE, 0x184, 0x20), + RSND_GEN_M_REG(CMD_BUSIF_DALIGN,0x188, 0x20), + RSND_GEN_M_REG(CMD_ROUTE_SLCT, 0x18c, 0x20), + RSND_GEN_M_REG(CMD_CTRL, 0x190, 0x20), + RSND_GEN_S_REG(SCU_SYS_STATUS0, 0x1c8), + RSND_GEN_S_REG(SCU_SYS_INT_EN0, 0x1cc), + RSND_GEN_S_REG(SCU_SYS_STATUS1, 0x1d0), + RSND_GEN_S_REG(SCU_SYS_INT_EN1, 0x1d4), + RSND_GEN_M_REG(SRC_SWRSR, 0x200, 0x40), + RSND_GEN_M_REG(SRC_SRCIR, 0x204, 0x40), + RSND_GEN_M_REG(SRC_ADINR, 0x214, 0x40), + RSND_GEN_M_REG(SRC_IFSCR, 0x21c, 0x40), + RSND_GEN_M_REG(SRC_IFSVR, 0x220, 0x40), + RSND_GEN_M_REG(SRC_SRCCR, 0x224, 0x40), + RSND_GEN_M_REG(SRC_BSDSR, 0x22c, 0x40), + RSND_GEN_M_REG(SRC_BSISR, 0x238, 0x40), + RSND_GEN_M_REG(CTU_SWRSR, 0x500, 0x100), + RSND_GEN_M_REG(CTU_CTUIR, 0x504, 0x100), + RSND_GEN_M_REG(CTU_ADINR, 0x508, 0x100), + RSND_GEN_M_REG(CTU_CPMDR, 0x510, 0x100), + RSND_GEN_M_REG(CTU_SCMDR, 0x514, 0x100), + RSND_GEN_M_REG(CTU_SV00R, 0x518, 0x100), + RSND_GEN_M_REG(CTU_SV01R, 0x51c, 0x100), + RSND_GEN_M_REG(CTU_SV02R, 0x520, 0x100), + RSND_GEN_M_REG(CTU_SV03R, 0x524, 0x100), + RSND_GEN_M_REG(CTU_SV04R, 0x528, 0x100), + RSND_GEN_M_REG(CTU_SV05R, 0x52c, 0x100), + RSND_GEN_M_REG(CTU_SV06R, 0x530, 0x100), + RSND_GEN_M_REG(CTU_SV07R, 0x534, 0x100), + RSND_GEN_M_REG(CTU_SV10R, 0x538, 0x100), + RSND_GEN_M_REG(CTU_SV11R, 0x53c, 0x100), + RSND_GEN_M_REG(CTU_SV12R, 0x540, 0x100), + RSND_GEN_M_REG(CTU_SV13R, 0x544, 0x100), + RSND_GEN_M_REG(CTU_SV14R, 0x548, 0x100), + RSND_GEN_M_REG(CTU_SV15R, 0x54c, 0x100), + RSND_GEN_M_REG(CTU_SV16R, 0x550, 0x100), + RSND_GEN_M_REG(CTU_SV17R, 0x554, 0x100), + RSND_GEN_M_REG(CTU_SV20R, 0x558, 0x100), + RSND_GEN_M_REG(CTU_SV21R, 0x55c, 0x100), + RSND_GEN_M_REG(CTU_SV22R, 0x560, 0x100), + RSND_GEN_M_REG(CTU_SV23R, 0x564, 0x100), + RSND_GEN_M_REG(CTU_SV24R, 0x568, 0x100), + RSND_GEN_M_REG(CTU_SV25R, 0x56c, 0x100), + RSND_GEN_M_REG(CTU_SV26R, 0x570, 0x100), + RSND_GEN_M_REG(CTU_SV27R, 0x574, 0x100), + RSND_GEN_M_REG(CTU_SV30R, 0x578, 0x100), + RSND_GEN_M_REG(CTU_SV31R, 0x57c, 0x100), + RSND_GEN_M_REG(CTU_SV32R, 0x580, 0x100), + RSND_GEN_M_REG(CTU_SV33R, 0x584, 0x100), + RSND_GEN_M_REG(CTU_SV34R, 0x588, 0x100), + RSND_GEN_M_REG(CTU_SV35R, 0x58c, 0x100), + RSND_GEN_M_REG(CTU_SV36R, 0x590, 0x100), + RSND_GEN_M_REG(CTU_SV37R, 0x594, 0x100), + RSND_GEN_M_REG(MIX_SWRSR, 0xd00, 0x40), + RSND_GEN_M_REG(MIX_MIXIR, 0xd04, 0x40), + RSND_GEN_M_REG(MIX_ADINR, 0xd08, 0x40), + RSND_GEN_M_REG(MIX_MIXMR, 0xd10, 0x40), + RSND_GEN_M_REG(MIX_MVPDR, 0xd14, 0x40), + RSND_GEN_M_REG(MIX_MDBAR, 0xd18, 0x40), + RSND_GEN_M_REG(MIX_MDBBR, 0xd1c, 0x40), + RSND_GEN_M_REG(MIX_MDBCR, 0xd20, 0x40), + RSND_GEN_M_REG(MIX_MDBDR, 0xd24, 0x40), + RSND_GEN_M_REG(MIX_MDBER, 0xd28, 0x40), + RSND_GEN_M_REG(DVC_SWRSR, 0xe00, 0x100), + RSND_GEN_M_REG(DVC_DVUIR, 0xe04, 0x100), + RSND_GEN_M_REG(DVC_ADINR, 0xe08, 0x100), + RSND_GEN_M_REG(DVC_DVUCR, 0xe10, 0x100), + RSND_GEN_M_REG(DVC_ZCMCR, 0xe14, 0x100), + RSND_GEN_M_REG(DVC_VRCTR, 0xe18, 0x100), + RSND_GEN_M_REG(DVC_VRPDR, 0xe1c, 0x100), + RSND_GEN_M_REG(DVC_VRDBR, 0xe20, 0x100), + RSND_GEN_M_REG(DVC_VOL0R, 0xe28, 0x100), + RSND_GEN_M_REG(DVC_VOL1R, 0xe2c, 0x100), + RSND_GEN_M_REG(DVC_VOL2R, 0xe30, 0x100), + RSND_GEN_M_REG(DVC_VOL3R, 0xe34, 0x100), + RSND_GEN_M_REG(DVC_VOL4R, 0xe38, 0x100), + RSND_GEN_M_REG(DVC_VOL5R, 0xe3c, 0x100), + RSND_GEN_M_REG(DVC_VOL6R, 0xe40, 0x100), + RSND_GEN_M_REG(DVC_VOL7R, 0xe44, 0x100), + RSND_GEN_M_REG(DVC_DVUER, 0xe48, 0x100), + }; + static const struct rsnd_regmap_field_conf conf_adg[] = { + RSND_GEN_S_REG(BRRA, 0x00), + RSND_GEN_S_REG(BRRB, 0x04), + RSND_GEN_S_REG(BRGCKR, 0x08), + RSND_GEN_S_REG(AUDIO_CLK_SEL0, 0x0c), + RSND_GEN_S_REG(AUDIO_CLK_SEL1, 0x10), + RSND_GEN_S_REG(AUDIO_CLK_SEL2, 0x14), + RSND_GEN_S_REG(DIV_EN, 0x30), + RSND_GEN_S_REG(SRCIN_TIMSEL0, 0x34), + RSND_GEN_S_REG(SRCIN_TIMSEL1, 0x38), + RSND_GEN_S_REG(SRCIN_TIMSEL2, 0x3c), + RSND_GEN_S_REG(SRCIN_TIMSEL3, 0x40), + RSND_GEN_S_REG(SRCIN_TIMSEL4, 0x44), + RSND_GEN_S_REG(SRCOUT_TIMSEL0, 0x48), + RSND_GEN_S_REG(SRCOUT_TIMSEL1, 0x4c), + RSND_GEN_S_REG(SRCOUT_TIMSEL2, 0x50), + RSND_GEN_S_REG(SRCOUT_TIMSEL3, 0x54), + RSND_GEN_S_REG(SRCOUT_TIMSEL4, 0x58), + RSND_GEN_S_REG(CMDOUT_TIMSEL, 0x5c), + }; + static const struct rsnd_regmap_field_conf conf_ssi[] = { + RSND_GEN_M_REG(SSICR, 0x00, 0x40), + RSND_GEN_M_REG(SSISR, 0x04, 0x40), + RSND_GEN_M_REG(SSITDR, 0x08, 0x40), + RSND_GEN_M_REG(SSIRDR, 0x0c, 0x40), + RSND_GEN_M_REG(SSIWSR, 0x20, 0x40), + }; + int ret_ssiu; + int ret_scu; + int ret_adg; + int ret_ssi; + + ret_ssiu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSIU, "ssiu", conf_ssiu); + ret_scu = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SCU, "scu", conf_scu); + ret_adg = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_ADG, "adg", conf_adg); + ret_ssi = rsnd_gen_regmap_init(priv, 10, RSND_GEN2_SSI, "ssi", conf_ssi); + if (ret_ssiu < 0 || + ret_scu < 0 || + ret_adg < 0 || + ret_ssi < 0) + return ret_ssiu | ret_scu | ret_adg | ret_ssi; + + return 0; +} + +/* + * Gen1 + */ + +static int rsnd_gen1_probe(struct rsnd_priv *priv) +{ + static const struct rsnd_regmap_field_conf conf_adg[] = { + RSND_GEN_S_REG(BRRA, 0x00), + RSND_GEN_S_REG(BRRB, 0x04), + RSND_GEN_S_REG(BRGCKR, 0x08), + RSND_GEN_S_REG(AUDIO_CLK_SEL0, 0x0c), + RSND_GEN_S_REG(AUDIO_CLK_SEL1, 0x10), + }; + static const struct rsnd_regmap_field_conf conf_ssi[] = { + RSND_GEN_M_REG(SSICR, 0x00, 0x40), + RSND_GEN_M_REG(SSISR, 0x04, 0x40), + RSND_GEN_M_REG(SSITDR, 0x08, 0x40), + RSND_GEN_M_REG(SSIRDR, 0x0c, 0x40), + RSND_GEN_M_REG(SSIWSR, 0x20, 0x40), + }; + int ret_adg; + int ret_ssi; + + ret_adg = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_ADG, "adg", conf_adg); + ret_ssi = rsnd_gen_regmap_init(priv, 9, RSND_GEN1_SSI, "ssi", conf_ssi); + if (ret_adg < 0 || + ret_ssi < 0) + return ret_adg | ret_ssi; + + return 0; +} + +/* + * Gen + */ +int rsnd_gen_probe(struct rsnd_priv *priv) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_gen *gen; + int ret; + + gen = devm_kzalloc(dev, sizeof(*gen), GFP_KERNEL); + if (!gen) + return -ENOMEM; + + priv->gen = gen; + + ret = -ENODEV; + if (rsnd_is_gen1(priv)) + ret = rsnd_gen1_probe(priv); + else if (rsnd_is_gen2(priv) || + rsnd_is_gen3(priv)) + ret = rsnd_gen2_probe(priv); + + if (ret < 0) + dev_err(dev, "unknown generation R-Car sound device\n"); + + return ret; +} diff --git a/sound/soc/sh/rcar/mix.c b/sound/soc/sh/rcar/mix.c new file mode 100644 index 000000000..8e3b57eaa --- /dev/null +++ b/sound/soc/sh/rcar/mix.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// mix.c +// +// Copyright (c) 2015 Kuninori Morimoto + +/* + * CTUn MIXn + * +------+ +------+ + * [SRC3 / SRC6] -> |CTU n0| -> [MIX n0| -> + * [SRC4 / SRC9] -> |CTU n1| -> [MIX n1| -> + * [SRC0 / SRC1] -> |CTU n2| -> [MIX n2| -> + * [SRC2 / SRC5] -> |CTU n3| -> [MIX n3| -> + * +------+ +------+ + * + * ex) + * DAI0 : playback = <&src0 &ctu02 &mix0 &dvc0 &ssi0>; + * DAI1 : playback = <&src2 &ctu03 &mix0 &dvc0 &ssi0>; + * + * MIX Volume + * amixer set "MIX",0 100% // DAI0 Volume + * amixer set "MIX",1 100% // DAI1 Volume + * + * Volume Ramp + * amixer set "MIX Ramp Up Rate" "0.125 dB/1 step" + * amixer set "MIX Ramp Down Rate" "4 dB/1 step" + * amixer set "MIX Ramp" on + * aplay xxx.wav & + * amixer set "MIX",0 80% // DAI0 Volume Down + * amixer set "MIX",1 100% // DAI1 Volume Up + */ + +#include "rsnd.h" + +#define MIX_NAME_SIZE 16 +#define MIX_NAME "mix" + +struct rsnd_mix { + struct rsnd_mod mod; + struct rsnd_kctrl_cfg_s volumeA; /* MDBAR */ + struct rsnd_kctrl_cfg_s volumeB; /* MDBBR */ + struct rsnd_kctrl_cfg_s volumeC; /* MDBCR */ + struct rsnd_kctrl_cfg_s volumeD; /* MDBDR */ + struct rsnd_kctrl_cfg_s ren; /* Ramp Enable */ + struct rsnd_kctrl_cfg_s rup; /* Ramp Rate Up */ + struct rsnd_kctrl_cfg_s rdw; /* Ramp Rate Down */ + u32 flags; +}; + +#define ONCE_KCTRL_INITIALIZED (1 << 0) +#define HAS_VOLA (1 << 1) +#define HAS_VOLB (1 << 2) +#define HAS_VOLC (1 << 3) +#define HAS_VOLD (1 << 4) + +#define VOL_MAX 0x3ff + +#define rsnd_mod_to_mix(_mod) \ + container_of((_mod), struct rsnd_mix, mod) + +#define rsnd_mix_get(priv, id) ((struct rsnd_mix *)(priv->mix) + id) +#define rsnd_mix_nr(priv) ((priv)->mix_nr) +#define for_each_rsnd_mix(pos, priv, i) \ + for ((i) = 0; \ + ((i) < rsnd_mix_nr(priv)) && \ + ((pos) = (struct rsnd_mix *)(priv)->mix + i); \ + i++) + +static void rsnd_mix_activation(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, MIX_SWRSR, 0); + rsnd_mod_write(mod, MIX_SWRSR, 1); +} + +static void rsnd_mix_halt(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, MIX_MIXIR, 1); + rsnd_mod_write(mod, MIX_SWRSR, 0); +} + +#define rsnd_mix_get_vol(mix, X) \ + rsnd_flags_has(mix, HAS_VOL##X) ? \ + (VOL_MAX - rsnd_kctrl_vals(mix->volume##X)) : 0 +static void rsnd_mix_volume_parameter(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_mix *mix = rsnd_mod_to_mix(mod); + u32 volA = rsnd_mix_get_vol(mix, A); + u32 volB = rsnd_mix_get_vol(mix, B); + u32 volC = rsnd_mix_get_vol(mix, C); + u32 volD = rsnd_mix_get_vol(mix, D); + + dev_dbg(dev, "MIX A/B/C/D = %02x/%02x/%02x/%02x\n", + volA, volB, volC, volD); + + rsnd_mod_write(mod, MIX_MDBAR, volA); + rsnd_mod_write(mod, MIX_MDBBR, volB); + rsnd_mod_write(mod, MIX_MDBCR, volC); + rsnd_mod_write(mod, MIX_MDBDR, volD); +} + +static void rsnd_mix_volume_init(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_mix *mix = rsnd_mod_to_mix(mod); + + rsnd_mod_write(mod, MIX_MIXIR, 1); + + /* General Information */ + rsnd_mod_write(mod, MIX_ADINR, rsnd_runtime_channel_after_ctu(io)); + + /* volume step */ + rsnd_mod_write(mod, MIX_MIXMR, rsnd_kctrl_vals(mix->ren)); + rsnd_mod_write(mod, MIX_MVPDR, rsnd_kctrl_vals(mix->rup) << 8 | + rsnd_kctrl_vals(mix->rdw)); + + /* common volume parameter */ + rsnd_mix_volume_parameter(io, mod); + + rsnd_mod_write(mod, MIX_MIXIR, 0); +} + +static void rsnd_mix_volume_update(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + /* Disable MIX dB setting */ + rsnd_mod_write(mod, MIX_MDBER, 0); + + /* common volume parameter */ + rsnd_mix_volume_parameter(io, mod); + + /* Enable MIX dB setting */ + rsnd_mod_write(mod, MIX_MDBER, 1); +} + +static int rsnd_mix_probe_(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + return rsnd_cmd_attach(io, rsnd_mod_id(mod)); +} + +static int rsnd_mix_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_mod_power_on(mod); + + rsnd_mix_activation(mod); + + rsnd_mix_volume_init(io, mod); + + rsnd_mix_volume_update(io, mod); + + return 0; +} + +static int rsnd_mix_quit(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_mix_halt(mod); + + rsnd_mod_power_off(mod); + + return 0; +} + +static int rsnd_mix_pcm_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_mix *mix = rsnd_mod_to_mix(mod); + struct rsnd_mod *src_mod = rsnd_io_to_mod_src(io); + struct rsnd_kctrl_cfg_s *volume; + int ret; + + switch (rsnd_mod_id(src_mod)) { + case 3: + case 6: /* MDBAR */ + volume = &mix->volumeA; + rsnd_flags_set(mix, HAS_VOLA); + break; + case 4: + case 9: /* MDBBR */ + volume = &mix->volumeB; + rsnd_flags_set(mix, HAS_VOLB); + break; + case 0: + case 1: /* MDBCR */ + volume = &mix->volumeC; + rsnd_flags_set(mix, HAS_VOLC); + break; + case 2: + case 5: /* MDBDR */ + volume = &mix->volumeD; + rsnd_flags_set(mix, HAS_VOLD); + break; + default: + dev_err(dev, "unknown SRC is connected\n"); + return -EINVAL; + } + + /* Volume */ + ret = rsnd_kctrl_new_s(mod, io, rtd, + "MIX Playback Volume", + rsnd_kctrl_accept_anytime, + rsnd_mix_volume_update, + volume, VOL_MAX); + if (ret < 0) + return ret; + rsnd_kctrl_vals(*volume) = VOL_MAX; + + if (rsnd_flags_has(mix, ONCE_KCTRL_INITIALIZED)) + return ret; + + /* Ramp */ + ret = rsnd_kctrl_new_s(mod, io, rtd, + "MIX Ramp Switch", + rsnd_kctrl_accept_anytime, + rsnd_mix_volume_update, + &mix->ren, 1); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_e(mod, io, rtd, + "MIX Ramp Up Rate", + rsnd_kctrl_accept_anytime, + rsnd_mix_volume_update, + &mix->rup, + volume_ramp_rate, + VOLUME_RAMP_MAX_MIX); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_e(mod, io, rtd, + "MIX Ramp Down Rate", + rsnd_kctrl_accept_anytime, + rsnd_mix_volume_update, + &mix->rdw, + volume_ramp_rate, + VOLUME_RAMP_MAX_MIX); + + rsnd_flags_set(mix, ONCE_KCTRL_INITIALIZED); + + return ret; +} + +static struct rsnd_mod_ops rsnd_mix_ops = { + .name = MIX_NAME, + .probe = rsnd_mix_probe_, + .init = rsnd_mix_init, + .quit = rsnd_mix_quit, + .pcm_new = rsnd_mix_pcm_new, +}; + +struct rsnd_mod *rsnd_mix_mod_get(struct rsnd_priv *priv, int id) +{ + if (WARN_ON(id < 0 || id >= rsnd_mix_nr(priv))) + id = 0; + + return rsnd_mod_get(rsnd_mix_get(priv, id)); +} + +int rsnd_mix_probe(struct rsnd_priv *priv) +{ + struct device_node *node; + struct device_node *np; + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_mix *mix; + struct clk *clk; + char name[MIX_NAME_SIZE]; + int i, nr, ret; + + /* This driver doesn't support Gen1 at this point */ + if (rsnd_is_gen1(priv)) + return 0; + + node = rsnd_mix_of_node(priv); + if (!node) + return 0; /* not used is not error */ + + nr = of_get_child_count(node); + if (!nr) { + ret = -EINVAL; + goto rsnd_mix_probe_done; + } + + mix = devm_kcalloc(dev, nr, sizeof(*mix), GFP_KERNEL); + if (!mix) { + ret = -ENOMEM; + goto rsnd_mix_probe_done; + } + + priv->mix_nr = nr; + priv->mix = mix; + + i = 0; + ret = 0; + for_each_child_of_node(node, np) { + mix = rsnd_mix_get(priv, i); + + snprintf(name, MIX_NAME_SIZE, "%s.%d", + MIX_NAME, i); + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(np); + goto rsnd_mix_probe_done; + } + + ret = rsnd_mod_init(priv, rsnd_mod_get(mix), &rsnd_mix_ops, + clk, rsnd_mod_get_status, RSND_MOD_MIX, i); + if (ret) { + of_node_put(np); + goto rsnd_mix_probe_done; + } + + i++; + } + +rsnd_mix_probe_done: + of_node_put(node); + + return ret; +} + +void rsnd_mix_remove(struct rsnd_priv *priv) +{ + struct rsnd_mix *mix; + int i; + + for_each_rsnd_mix(mix, priv, i) { + rsnd_mod_quit(rsnd_mod_get(mix)); + } +} diff --git a/sound/soc/sh/rcar/rsnd.h b/sound/soc/sh/rcar/rsnd.h new file mode 100644 index 000000000..f64c7058b --- /dev/null +++ b/sound/soc/sh/rcar/rsnd.h @@ -0,0 +1,818 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car +// +// Copyright (C) 2013 Renesas Solutions Corp. +// Kuninori Morimoto + +#ifndef RSND_H +#define RSND_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RSND_GEN1_SRU 0 +#define RSND_GEN1_ADG 1 +#define RSND_GEN1_SSI 2 + +#define RSND_GEN2_SCU 0 +#define RSND_GEN2_ADG 1 +#define RSND_GEN2_SSIU 2 +#define RSND_GEN2_SSI 3 + +#define RSND_BASE_MAX 4 + +/* + * pseudo register + * + * The register address offsets SRU/SCU/SSIU on Gen1/Gen2 are very different. + * This driver uses pseudo register in order to hide it. + * see gen1/gen2 for detail + */ +enum rsnd_reg { + /* SCU (MIX/CTU/DVC) */ + RSND_REG_SRC_I_BUSIF_MODE, + RSND_REG_SRC_O_BUSIF_MODE, + RSND_REG_SRC_ROUTE_MODE0, + RSND_REG_SRC_SWRSR, + RSND_REG_SRC_SRCIR, + RSND_REG_SRC_ADINR, + RSND_REG_SRC_IFSCR, + RSND_REG_SRC_IFSVR, + RSND_REG_SRC_SRCCR, + RSND_REG_SRC_CTRL, + RSND_REG_SRC_BSDSR, + RSND_REG_SRC_BSISR, + RSND_REG_SRC_INT_ENABLE0, + RSND_REG_SRC_BUSIF_DALIGN, + RSND_REG_SRCIN_TIMSEL0, + RSND_REG_SRCIN_TIMSEL1, + RSND_REG_SRCIN_TIMSEL2, + RSND_REG_SRCIN_TIMSEL3, + RSND_REG_SRCIN_TIMSEL4, + RSND_REG_SRCOUT_TIMSEL0, + RSND_REG_SRCOUT_TIMSEL1, + RSND_REG_SRCOUT_TIMSEL2, + RSND_REG_SRCOUT_TIMSEL3, + RSND_REG_SRCOUT_TIMSEL4, + RSND_REG_SCU_SYS_STATUS0, + RSND_REG_SCU_SYS_STATUS1, + RSND_REG_SCU_SYS_INT_EN0, + RSND_REG_SCU_SYS_INT_EN1, + RSND_REG_CMD_CTRL, + RSND_REG_CMD_BUSIF_MODE, + RSND_REG_CMD_BUSIF_DALIGN, + RSND_REG_CMD_ROUTE_SLCT, + RSND_REG_CMDOUT_TIMSEL, + RSND_REG_CTU_SWRSR, + RSND_REG_CTU_CTUIR, + RSND_REG_CTU_ADINR, + RSND_REG_CTU_CPMDR, + RSND_REG_CTU_SCMDR, + RSND_REG_CTU_SV00R, + RSND_REG_CTU_SV01R, + RSND_REG_CTU_SV02R, + RSND_REG_CTU_SV03R, + RSND_REG_CTU_SV04R, + RSND_REG_CTU_SV05R, + RSND_REG_CTU_SV06R, + RSND_REG_CTU_SV07R, + RSND_REG_CTU_SV10R, + RSND_REG_CTU_SV11R, + RSND_REG_CTU_SV12R, + RSND_REG_CTU_SV13R, + RSND_REG_CTU_SV14R, + RSND_REG_CTU_SV15R, + RSND_REG_CTU_SV16R, + RSND_REG_CTU_SV17R, + RSND_REG_CTU_SV20R, + RSND_REG_CTU_SV21R, + RSND_REG_CTU_SV22R, + RSND_REG_CTU_SV23R, + RSND_REG_CTU_SV24R, + RSND_REG_CTU_SV25R, + RSND_REG_CTU_SV26R, + RSND_REG_CTU_SV27R, + RSND_REG_CTU_SV30R, + RSND_REG_CTU_SV31R, + RSND_REG_CTU_SV32R, + RSND_REG_CTU_SV33R, + RSND_REG_CTU_SV34R, + RSND_REG_CTU_SV35R, + RSND_REG_CTU_SV36R, + RSND_REG_CTU_SV37R, + RSND_REG_MIX_SWRSR, + RSND_REG_MIX_MIXIR, + RSND_REG_MIX_ADINR, + RSND_REG_MIX_MIXMR, + RSND_REG_MIX_MVPDR, + RSND_REG_MIX_MDBAR, + RSND_REG_MIX_MDBBR, + RSND_REG_MIX_MDBCR, + RSND_REG_MIX_MDBDR, + RSND_REG_MIX_MDBER, + RSND_REG_DVC_SWRSR, + RSND_REG_DVC_DVUIR, + RSND_REG_DVC_ADINR, + RSND_REG_DVC_DVUCR, + RSND_REG_DVC_ZCMCR, + RSND_REG_DVC_VOL0R, + RSND_REG_DVC_VOL1R, + RSND_REG_DVC_VOL2R, + RSND_REG_DVC_VOL3R, + RSND_REG_DVC_VOL4R, + RSND_REG_DVC_VOL5R, + RSND_REG_DVC_VOL6R, + RSND_REG_DVC_VOL7R, + RSND_REG_DVC_DVUER, + RSND_REG_DVC_VRCTR, + RSND_REG_DVC_VRPDR, + RSND_REG_DVC_VRDBR, + + /* ADG */ + RSND_REG_BRRA, + RSND_REG_BRRB, + RSND_REG_BRGCKR, + RSND_REG_DIV_EN, + RSND_REG_AUDIO_CLK_SEL0, + RSND_REG_AUDIO_CLK_SEL1, + RSND_REG_AUDIO_CLK_SEL2, + + /* SSIU */ + RSND_REG_SSI_MODE, + RSND_REG_SSI_MODE0, + RSND_REG_SSI_MODE1, + RSND_REG_SSI_MODE2, + RSND_REG_SSI_CONTROL, + RSND_REG_SSI_CTRL, + RSND_REG_SSI_BUSIF_MODE, + RSND_REG_SSI_BUSIF_ADINR, + RSND_REG_SSI_BUSIF_DALIGN, + RSND_REG_SSI_INT_ENABLE, + RSND_REG_SSI_SYS_STATUS0, + RSND_REG_SSI_SYS_STATUS1, + RSND_REG_SSI_SYS_STATUS2, + RSND_REG_SSI_SYS_STATUS3, + RSND_REG_SSI_SYS_STATUS4, + RSND_REG_SSI_SYS_STATUS5, + RSND_REG_SSI_SYS_STATUS6, + RSND_REG_SSI_SYS_STATUS7, + RSND_REG_HDMI0_SEL, + RSND_REG_HDMI1_SEL, + + /* SSI */ + RSND_REG_SSICR, + RSND_REG_SSISR, + RSND_REG_SSITDR, + RSND_REG_SSIRDR, + RSND_REG_SSIWSR, + + RSND_REG_MAX, +}; + +struct rsnd_priv; +struct rsnd_mod; +struct rsnd_dai; +struct rsnd_dai_stream; + +/* + * R-Car basic functions + */ +#define rsnd_mod_read(m, r) \ + rsnd_read(rsnd_mod_to_priv(m), m, RSND_REG_##r) +#define rsnd_mod_write(m, r, d) \ + rsnd_write(rsnd_mod_to_priv(m), m, RSND_REG_##r, d) +#define rsnd_mod_bset(m, r, s, d) \ + rsnd_bset(rsnd_mod_to_priv(m), m, RSND_REG_##r, s, d) + +u32 rsnd_read(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg); +void rsnd_write(struct rsnd_priv *priv, struct rsnd_mod *mod, + enum rsnd_reg reg, u32 data); +void rsnd_force_write(struct rsnd_priv *priv, struct rsnd_mod *mod, + enum rsnd_reg reg, u32 data); +void rsnd_bset(struct rsnd_priv *priv, struct rsnd_mod *mod, enum rsnd_reg reg, + u32 mask, u32 data); +u32 rsnd_get_adinr_bit(struct rsnd_mod *mod, struct rsnd_dai_stream *io); +u32 rsnd_get_dalign(struct rsnd_mod *mod, struct rsnd_dai_stream *io); +u32 rsnd_get_busif_shift(struct rsnd_dai_stream *io, struct rsnd_mod *mod); + +/* + * R-Car DMA + */ +int rsnd_dma_attach(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, struct rsnd_mod **dma_mod); +int rsnd_dma_probe(struct rsnd_priv *priv); +struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node, + struct rsnd_mod *mod, char *name); + +/* + * R-Car sound mod + */ +enum rsnd_mod_type { + RSND_MOD_AUDMAPP, + RSND_MOD_AUDMA, + RSND_MOD_DVC, + RSND_MOD_MIX, + RSND_MOD_CTU, + RSND_MOD_CMD, + RSND_MOD_SRC, + RSND_MOD_SSIM3, /* SSI multi 3 */ + RSND_MOD_SSIM2, /* SSI multi 2 */ + RSND_MOD_SSIM1, /* SSI multi 1 */ + RSND_MOD_SSIP, /* SSI parent */ + RSND_MOD_SSI, + RSND_MOD_SSIU, + RSND_MOD_MAX, +}; + +struct rsnd_mod_ops { + char *name; + struct dma_chan* (*dma_req)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod); + int (*probe)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*remove)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*init)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*quit)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*start)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*stop)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*irq)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv, int enable); + int (*pcm_new)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd); + int (*hw_params)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params); + int (*pointer)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + snd_pcm_uframes_t *pointer); + int (*fallback)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*nolock_start)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*nolock_stop)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); + int (*prepare)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv); +}; + +struct rsnd_dai_stream; +struct rsnd_mod { + int id; + enum rsnd_mod_type type; + struct rsnd_mod_ops *ops; + struct rsnd_priv *priv; + struct clk *clk; + u32 *(*get_status)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + enum rsnd_mod_type type); + u32 status; +}; +/* + * status + * + * 0xH0000CBA + * + * A 0: nolock_start 1: nolock_stop + * B 0: init 1: quit + * C 0: start 1: stop + * + * H is always called (see __rsnd_mod_call) + * H 0: probe 1: remove + * H 0: pcm_new + * H 0: fallback + * H 0: hw_params + * H 0: pointer + * H 0: prepare + */ +#define __rsnd_mod_shift_nolock_start 0 +#define __rsnd_mod_shift_nolock_stop 0 +#define __rsnd_mod_shift_init 4 +#define __rsnd_mod_shift_quit 4 +#define __rsnd_mod_shift_start 8 +#define __rsnd_mod_shift_stop 8 +#define __rsnd_mod_shift_probe 28 /* always called */ +#define __rsnd_mod_shift_remove 28 /* always called */ +#define __rsnd_mod_shift_irq 28 /* always called */ +#define __rsnd_mod_shift_pcm_new 28 /* always called */ +#define __rsnd_mod_shift_fallback 28 /* always called */ +#define __rsnd_mod_shift_hw_params 28 /* always called */ +#define __rsnd_mod_shift_pointer 28 /* always called */ +#define __rsnd_mod_shift_prepare 28 /* always called */ + +#define __rsnd_mod_add_probe 0 +#define __rsnd_mod_add_remove 0 +#define __rsnd_mod_add_nolock_start 1 +#define __rsnd_mod_add_nolock_stop -1 +#define __rsnd_mod_add_init 1 +#define __rsnd_mod_add_quit -1 +#define __rsnd_mod_add_start 1 +#define __rsnd_mod_add_stop -1 +#define __rsnd_mod_add_irq 0 +#define __rsnd_mod_add_pcm_new 0 +#define __rsnd_mod_add_fallback 0 +#define __rsnd_mod_add_hw_params 0 +#define __rsnd_mod_add_pointer 0 +#define __rsnd_mod_add_prepare 0 + +#define __rsnd_mod_call_probe 0 +#define __rsnd_mod_call_remove 0 +#define __rsnd_mod_call_init 0 +#define __rsnd_mod_call_quit 1 +#define __rsnd_mod_call_start 0 +#define __rsnd_mod_call_stop 1 +#define __rsnd_mod_call_irq 0 +#define __rsnd_mod_call_pcm_new 0 +#define __rsnd_mod_call_fallback 0 +#define __rsnd_mod_call_hw_params 0 +#define __rsnd_mod_call_pointer 0 +#define __rsnd_mod_call_nolock_start 0 +#define __rsnd_mod_call_nolock_stop 1 +#define __rsnd_mod_call_prepare 0 + +#define rsnd_mod_to_priv(mod) ((mod)->priv) +#define rsnd_mod_name(mod) ((mod)->ops->name) +#define rsnd_mod_id(mod) ((mod)->id) +#define rsnd_mod_power_on(mod) clk_enable((mod)->clk) +#define rsnd_mod_power_off(mod) clk_disable((mod)->clk) +#define rsnd_mod_get(ip) (&(ip)->mod) + +int rsnd_mod_init(struct rsnd_priv *priv, + struct rsnd_mod *mod, + struct rsnd_mod_ops *ops, + struct clk *clk, + u32* (*get_status)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + enum rsnd_mod_type type), + enum rsnd_mod_type type, + int id); +void rsnd_mod_quit(struct rsnd_mod *mod); +struct dma_chan *rsnd_mod_dma_req(struct rsnd_dai_stream *io, + struct rsnd_mod *mod); +void rsnd_mod_interrupt(struct rsnd_mod *mod, + void (*callback)(struct rsnd_mod *mod, + struct rsnd_dai_stream *io)); +u32 *rsnd_mod_get_status(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + enum rsnd_mod_type type); +struct rsnd_mod *rsnd_mod_next(int *iterator, + struct rsnd_dai_stream *io, + enum rsnd_mod_type *array, + int array_size); +#define for_each_rsnd_mod(iterator, pos, io) \ + for (iterator = 0; \ + (pos = rsnd_mod_next(&iterator, io, NULL, 0)); iterator++) +#define for_each_rsnd_mod_arrays(iterator, pos, io, array, size) \ + for (iterator = 0; \ + (pos = rsnd_mod_next(&iterator, io, array, size)); iterator++) +#define for_each_rsnd_mod_array(iterator, pos, io, array) \ + for_each_rsnd_mod_arrays(iterator, pos, io, array, ARRAY_SIZE(array)) + +void rsnd_parse_connect_common(struct rsnd_dai *rdai, + struct rsnd_mod* (*mod_get)(struct rsnd_priv *priv, int id), + struct device_node *node, + struct device_node *playback, + struct device_node *capture); + +#define rsnd_runtime_channel_original(io) \ + rsnd_runtime_channel_original_with_params(io, NULL) +int rsnd_runtime_channel_original_with_params(struct rsnd_dai_stream *io, + struct snd_pcm_hw_params *params); +#define rsnd_runtime_channel_after_ctu(io) \ + rsnd_runtime_channel_after_ctu_with_params(io, NULL) +int rsnd_runtime_channel_after_ctu_with_params(struct rsnd_dai_stream *io, + struct snd_pcm_hw_params *params); +#define rsnd_runtime_channel_for_ssi(io) \ + rsnd_runtime_channel_for_ssi_with_params(io, NULL) +int rsnd_runtime_channel_for_ssi_with_params(struct rsnd_dai_stream *io, + struct snd_pcm_hw_params *params); +int rsnd_runtime_is_ssi_multi(struct rsnd_dai_stream *io); +int rsnd_runtime_is_ssi_tdm(struct rsnd_dai_stream *io); + +/* + * DT + */ +#define rsnd_parse_of_node(priv, node) \ + of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, node) +#define RSND_NODE_DAI "rcar_sound,dai" +#define RSND_NODE_SSI "rcar_sound,ssi" +#define RSND_NODE_SRC "rcar_sound,src" +#define RSND_NODE_CTU "rcar_sound,ctu" +#define RSND_NODE_MIX "rcar_sound,mix" +#define RSND_NODE_DVC "rcar_sound,dvc" + +/* + * R-Car sound DAI + */ +#define RSND_DAI_NAME_SIZE 16 +struct rsnd_dai_stream { + char name[RSND_DAI_NAME_SIZE]; + struct snd_pcm_substream *substream; + struct rsnd_mod *mod[RSND_MOD_MAX]; + struct rsnd_mod *dma; + struct rsnd_dai *rdai; + struct device *dmac_dev; /* for IPMMU */ + u32 parent_ssi_status; +}; +#define rsnd_io_to_mod(io, i) ((i) < RSND_MOD_MAX ? (io)->mod[(i)] : NULL) +#define rsnd_io_to_mod_ssi(io) rsnd_io_to_mod((io), RSND_MOD_SSI) +#define rsnd_io_to_mod_ssiu(io) rsnd_io_to_mod((io), RSND_MOD_SSIU) +#define rsnd_io_to_mod_ssip(io) rsnd_io_to_mod((io), RSND_MOD_SSIP) +#define rsnd_io_to_mod_src(io) rsnd_io_to_mod((io), RSND_MOD_SRC) +#define rsnd_io_to_mod_ctu(io) rsnd_io_to_mod((io), RSND_MOD_CTU) +#define rsnd_io_to_mod_mix(io) rsnd_io_to_mod((io), RSND_MOD_MIX) +#define rsnd_io_to_mod_dvc(io) rsnd_io_to_mod((io), RSND_MOD_DVC) +#define rsnd_io_to_mod_cmd(io) rsnd_io_to_mod((io), RSND_MOD_CMD) +#define rsnd_io_to_rdai(io) ((io)->rdai) +#define rsnd_io_to_priv(io) (rsnd_rdai_to_priv(rsnd_io_to_rdai(io))) +#define rsnd_io_is_play(io) (&rsnd_io_to_rdai(io)->playback == io) +#define rsnd_io_to_runtime(io) ((io)->substream ? \ + (io)->substream->runtime : NULL) +int rsnd_io_is_working(struct rsnd_dai_stream *io); + +struct rsnd_dai { + char name[RSND_DAI_NAME_SIZE]; + struct rsnd_dai_stream playback; + struct rsnd_dai_stream capture; + struct rsnd_priv *priv; + struct snd_pcm_hw_constraint_list constraint; + + int max_channels; /* 2ch - 16ch */ + int ssi_lane; /* 1lane - 4lane */ + + unsigned int clk_master:1; + unsigned int bit_clk_inv:1; + unsigned int frm_clk_inv:1; + unsigned int sys_delay:1; + unsigned int data_alignment:1; +}; + +#define rsnd_rdai_nr(priv) ((priv)->rdai_nr) +#define rsnd_rdai_is_clk_master(rdai) ((rdai)->clk_master) +#define rsnd_rdai_to_priv(rdai) ((rdai)->priv) +#define for_each_rsnd_dai(rdai, priv, i) \ + for (i = 0; \ + (i < rsnd_rdai_nr(priv)) && \ + ((rdai) = rsnd_rdai_get(priv, i)); \ + i++) + +struct rsnd_dai *rsnd_rdai_get(struct rsnd_priv *priv, int id); + +#define rsnd_rdai_channels_set(rdai, max_channels) \ + rsnd_rdai_channels_ctrl(rdai, max_channels) +#define rsnd_rdai_channels_get(rdai) \ + rsnd_rdai_channels_ctrl(rdai, 0) +int rsnd_rdai_channels_ctrl(struct rsnd_dai *rdai, + int max_channels); + +#define rsnd_rdai_ssi_lane_set(rdai, ssi_lane) \ + rsnd_rdai_ssi_lane_ctrl(rdai, ssi_lane) +#define rsnd_rdai_ssi_lane_get(rdai) \ + rsnd_rdai_ssi_lane_ctrl(rdai, 0) +int rsnd_rdai_ssi_lane_ctrl(struct rsnd_dai *rdai, + int ssi_lane); + +void rsnd_dai_period_elapsed(struct rsnd_dai_stream *io); +int rsnd_dai_connect(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + enum rsnd_mod_type type); + +/* + * R-Car Gen1/Gen2 + */ +int rsnd_gen_probe(struct rsnd_priv *priv); +void __iomem *rsnd_gen_reg_get(struct rsnd_priv *priv, + struct rsnd_mod *mod, + enum rsnd_reg reg); +phys_addr_t rsnd_gen_get_phy_addr(struct rsnd_priv *priv, int reg_id); + +/* + * R-Car ADG + */ +int rsnd_adg_clk_query(struct rsnd_priv *priv, unsigned int rate); +int rsnd_adg_ssi_clk_stop(struct rsnd_mod *mod); +int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *mod, unsigned int rate); +int rsnd_adg_probe(struct rsnd_priv *priv); +void rsnd_adg_remove(struct rsnd_priv *priv); +int rsnd_adg_set_src_timesel_gen2(struct rsnd_mod *src_mod, + struct rsnd_dai_stream *io, + unsigned int in_rate, + unsigned int out_rate); +int rsnd_adg_set_cmd_timsel_gen2(struct rsnd_mod *mod, + struct rsnd_dai_stream *io); +#define rsnd_adg_clk_enable(priv) rsnd_adg_clk_control(priv, 1) +#define rsnd_adg_clk_disable(priv) rsnd_adg_clk_control(priv, 0) +void rsnd_adg_clk_control(struct rsnd_priv *priv, int enable); + +/* + * R-Car sound priv + */ +struct rsnd_priv { + + struct platform_device *pdev; + spinlock_t lock; + unsigned long flags; +#define RSND_GEN_MASK (0xF << 0) +#define RSND_GEN1 (1 << 0) +#define RSND_GEN2 (2 << 0) +#define RSND_GEN3 (3 << 0) + + /* + * below value will be filled on rsnd_gen_probe() + */ + void *gen; + + /* + * below value will be filled on rsnd_adg_probe() + */ + void *adg; + + /* + * below value will be filled on rsnd_dma_probe() + */ + void *dma; + + /* + * below value will be filled on rsnd_ssi_probe() + */ + void *ssi; + int ssi_nr; + + /* + * below value will be filled on rsnd_ssiu_probe() + */ + void *ssiu; + int ssiu_nr; + + /* + * below value will be filled on rsnd_src_probe() + */ + void *src; + int src_nr; + + /* + * below value will be filled on rsnd_ctu_probe() + */ + void *ctu; + int ctu_nr; + + /* + * below value will be filled on rsnd_mix_probe() + */ + void *mix; + int mix_nr; + + /* + * below value will be filled on rsnd_dvc_probe() + */ + void *dvc; + int dvc_nr; + + /* + * below value will be filled on rsnd_cmd_probe() + */ + void *cmd; + int cmd_nr; + + /* + * below value will be filled on rsnd_dai_probe() + */ + struct snd_soc_dai_driver *daidrv; + struct rsnd_dai *rdai; + int rdai_nr; +}; + +#define rsnd_priv_to_pdev(priv) ((priv)->pdev) +#define rsnd_priv_to_dev(priv) (&(rsnd_priv_to_pdev(priv)->dev)) + +#define rsnd_is_gen1(priv) (((priv)->flags & RSND_GEN_MASK) == RSND_GEN1) +#define rsnd_is_gen2(priv) (((priv)->flags & RSND_GEN_MASK) == RSND_GEN2) +#define rsnd_is_gen3(priv) (((priv)->flags & RSND_GEN_MASK) == RSND_GEN3) + +#define rsnd_flags_has(p, f) ((p)->flags & (f)) +#define rsnd_flags_set(p, f) ((p)->flags |= (f)) +#define rsnd_flags_del(p, f) ((p)->flags &= ~(f)) + +/* + * rsnd_kctrl + */ +struct rsnd_kctrl_cfg { + unsigned int max; + unsigned int size; + u32 *val; + const char * const *texts; + int (*accept)(struct rsnd_dai_stream *io); + void (*update)(struct rsnd_dai_stream *io, struct rsnd_mod *mod); + struct rsnd_dai_stream *io; + struct snd_card *card; + struct snd_kcontrol *kctrl; + struct rsnd_mod *mod; +}; + +#define RSND_MAX_CHANNELS 8 +struct rsnd_kctrl_cfg_m { + struct rsnd_kctrl_cfg cfg; + u32 val[RSND_MAX_CHANNELS]; +}; + +struct rsnd_kctrl_cfg_s { + struct rsnd_kctrl_cfg cfg; + u32 val; +}; +#define rsnd_kctrl_size(x) ((x).cfg.size) +#define rsnd_kctrl_max(x) ((x).cfg.max) +#define rsnd_kctrl_valm(x, i) ((x).val[i]) /* = (x).cfg.val[i] */ +#define rsnd_kctrl_vals(x) ((x).val) /* = (x).cfg.val[0] */ + +int rsnd_kctrl_accept_anytime(struct rsnd_dai_stream *io); +int rsnd_kctrl_accept_runtime(struct rsnd_dai_stream *io); +struct rsnd_kctrl_cfg *rsnd_kctrl_init_m(struct rsnd_kctrl_cfg_m *cfg); +struct rsnd_kctrl_cfg *rsnd_kctrl_init_s(struct rsnd_kctrl_cfg_s *cfg); +int rsnd_kctrl_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd, + const unsigned char *name, + int (*accept)(struct rsnd_dai_stream *io), + void (*update)(struct rsnd_dai_stream *io, + struct rsnd_mod *mod), + struct rsnd_kctrl_cfg *cfg, + const char * const *texts, + int size, + u32 max); + +#define rsnd_kctrl_new_m(mod, io, rtd, name, accept, update, cfg, size, max) \ + rsnd_kctrl_new(mod, io, rtd, name, accept, update, rsnd_kctrl_init_m(cfg), \ + NULL, size, max) + +#define rsnd_kctrl_new_s(mod, io, rtd, name, accept, update, cfg, max) \ + rsnd_kctrl_new(mod, io, rtd, name, accept, update, rsnd_kctrl_init_s(cfg), \ + NULL, 1, max) + +#define rsnd_kctrl_new_e(mod, io, rtd, name, accept, update, cfg, texts, size) \ + rsnd_kctrl_new(mod, io, rtd, name, accept, update, rsnd_kctrl_init_s(cfg), \ + texts, 1, size) + +extern const char * const volume_ramp_rate[]; +#define VOLUME_RAMP_MAX_DVC (0x17 + 1) +#define VOLUME_RAMP_MAX_MIX (0x0a + 1) + +/* + * R-Car SSI + */ +int rsnd_ssi_probe(struct rsnd_priv *priv); +void rsnd_ssi_remove(struct rsnd_priv *priv); +struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id); +int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod); +int rsnd_ssi_use_busif(struct rsnd_dai_stream *io); +u32 rsnd_ssi_multi_slaves_runtime(struct rsnd_dai_stream *io); + +#define RSND_SSI_HDMI_PORT0 0xf0 +#define RSND_SSI_HDMI_PORT1 0xf1 +int rsnd_ssi_hdmi_port(struct rsnd_dai_stream *io); +void rsnd_ssi_parse_hdmi_connection(struct rsnd_priv *priv, + struct device_node *endpoint, + int dai_i); + +#define rsnd_ssi_is_pin_sharing(io) \ + __rsnd_ssi_is_pin_sharing(rsnd_io_to_mod_ssi(io)) +int __rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod); + +#define rsnd_ssi_of_node(priv) rsnd_parse_of_node(priv, RSND_NODE_SSI) +void rsnd_parse_connect_ssi(struct rsnd_dai *rdai, + struct device_node *playback, + struct device_node *capture); +unsigned int rsnd_ssi_clk_query(struct rsnd_priv *priv, + int param1, int param2, int *idx); + +/* + * R-Car SSIU + */ +int rsnd_ssiu_attach(struct rsnd_dai_stream *io, + struct rsnd_mod *mod); +int rsnd_ssiu_probe(struct rsnd_priv *priv); +void rsnd_ssiu_remove(struct rsnd_priv *priv); + +/* + * R-Car SRC + */ +int rsnd_src_probe(struct rsnd_priv *priv); +void rsnd_src_remove(struct rsnd_priv *priv); +struct rsnd_mod *rsnd_src_mod_get(struct rsnd_priv *priv, int id); + +#define rsnd_src_get_in_rate(priv, io) rsnd_src_get_rate(priv, io, 1) +#define rsnd_src_get_out_rate(priv, io) rsnd_src_get_rate(priv, io, 0) +unsigned int rsnd_src_get_rate(struct rsnd_priv *priv, + struct rsnd_dai_stream *io, + int is_in); + +#define rsnd_src_of_node(priv) rsnd_parse_of_node(priv, RSND_NODE_SRC) +#define rsnd_parse_connect_src(rdai, playback, capture) \ + rsnd_parse_connect_common(rdai, rsnd_src_mod_get, \ + rsnd_src_of_node(rsnd_rdai_to_priv(rdai)), \ + playback, capture) + +/* + * R-Car CTU + */ +int rsnd_ctu_probe(struct rsnd_priv *priv); +void rsnd_ctu_remove(struct rsnd_priv *priv); +int rsnd_ctu_converted_channel(struct rsnd_mod *mod); +struct rsnd_mod *rsnd_ctu_mod_get(struct rsnd_priv *priv, int id); +#define rsnd_ctu_of_node(priv) rsnd_parse_of_node(priv, RSND_NODE_CTU) +#define rsnd_parse_connect_ctu(rdai, playback, capture) \ + rsnd_parse_connect_common(rdai, rsnd_ctu_mod_get, \ + rsnd_ctu_of_node(rsnd_rdai_to_priv(rdai)), \ + playback, capture) + +/* + * R-Car MIX + */ +int rsnd_mix_probe(struct rsnd_priv *priv); +void rsnd_mix_remove(struct rsnd_priv *priv); +struct rsnd_mod *rsnd_mix_mod_get(struct rsnd_priv *priv, int id); +#define rsnd_mix_of_node(priv) rsnd_parse_of_node(priv, RSND_NODE_MIX) +#define rsnd_parse_connect_mix(rdai, playback, capture) \ + rsnd_parse_connect_common(rdai, rsnd_mix_mod_get, \ + rsnd_mix_of_node(rsnd_rdai_to_priv(rdai)), \ + playback, capture) + +/* + * R-Car DVC + */ +int rsnd_dvc_probe(struct rsnd_priv *priv); +void rsnd_dvc_remove(struct rsnd_priv *priv); +struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id); +#define rsnd_dvc_of_node(priv) rsnd_parse_of_node(priv, RSND_NODE_DVC) +#define rsnd_parse_connect_dvc(rdai, playback, capture) \ + rsnd_parse_connect_common(rdai, rsnd_dvc_mod_get, \ + rsnd_dvc_of_node(rsnd_rdai_to_priv(rdai)), \ + playback, capture) + +/* + * R-Car CMD + */ +int rsnd_cmd_probe(struct rsnd_priv *priv); +void rsnd_cmd_remove(struct rsnd_priv *priv); +int rsnd_cmd_attach(struct rsnd_dai_stream *io, int id); + +void rsnd_mod_make_sure(struct rsnd_mod *mod, enum rsnd_mod_type type); +#ifdef DEBUG +#define rsnd_mod_confirm_ssi(mssi) rsnd_mod_make_sure(mssi, RSND_MOD_SSI) +#define rsnd_mod_confirm_src(msrc) rsnd_mod_make_sure(msrc, RSND_MOD_SRC) +#define rsnd_mod_confirm_dvc(mdvc) rsnd_mod_make_sure(mdvc, RSND_MOD_DVC) +#else +#define rsnd_mod_confirm_ssi(mssi) +#define rsnd_mod_confirm_src(msrc) +#define rsnd_mod_confirm_dvc(mdvc) +#endif + +/* + * If you don't need interrupt status debug message, + * define RSND_DEBUG_NO_IRQ_STATUS as 1 on top of src.c/ssi.c + * + * #define RSND_DEBUG_NO_IRQ_STATUS 1 + */ +#define rsnd_dbg_irq_status(dev, param...) \ + if (!IS_BUILTIN(RSND_DEBUG_NO_IRQ_STATUS)) \ + dev_dbg(dev, param) + +/* + * If you don't need rsnd_dai_call debug message, + * define RSND_DEBUG_NO_DAI_CALL as 1 on top of core.c + * + * #define RSND_DEBUG_NO_DAI_CALL 1 + */ +#define rsnd_dbg_dai_call(dev, param...) \ + if (!IS_BUILTIN(RSND_DEBUG_NO_DAI_CALL)) \ + dev_dbg(dev, param) + +#endif diff --git a/sound/soc/sh/rcar/src.c b/sound/soc/sh/rcar/src.c new file mode 100644 index 000000000..beccfbac7 --- /dev/null +++ b/sound/soc/sh/rcar/src.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car SRC support +// +// Copyright (C) 2013 Renesas Solutions Corp. +// Kuninori Morimoto + +/* + * you can enable below define if you don't need + * SSI interrupt status debug message when debugging + * see rsnd_dbg_irq_status() + * + * #define RSND_DEBUG_NO_IRQ_STATUS 1 + */ + +#include "rsnd.h" + +#define SRC_NAME "src" + +/* SCU_SYSTEM_STATUS0/1 */ +#define OUF_SRC(id) ((1 << (id + 16)) | (1 << id)) + +struct rsnd_src { + struct rsnd_mod mod; + struct rsnd_mod *dma; + struct rsnd_kctrl_cfg_s sen; /* sync convert enable */ + struct rsnd_kctrl_cfg_s sync; /* sync convert */ + u32 convert_rate; /* sampling rate convert */ + int irq; +}; + +#define RSND_SRC_NAME_SIZE 16 + +#define rsnd_src_get(priv, id) ((struct rsnd_src *)(priv->src) + id) +#define rsnd_src_nr(priv) ((priv)->src_nr) +#define rsnd_src_sync_is_enabled(mod) (rsnd_mod_to_src(mod)->sen.val) + +#define rsnd_mod_to_src(_mod) \ + container_of((_mod), struct rsnd_src, mod) + +#define for_each_rsnd_src(pos, priv, i) \ + for ((i) = 0; \ + ((i) < rsnd_src_nr(priv)) && \ + ((pos) = (struct rsnd_src *)(priv)->src + i); \ + i++) + + +/* + * image of SRC (Sampling Rate Converter) + * + * 96kHz <-> +-----+ 48kHz +-----+ 48kHz +-------+ + * 48kHz <-> | SRC | <------> | SSI | <-----> | codec | + * 44.1kHz <-> +-----+ +-----+ +-------+ + * ... + * + */ + +static void rsnd_src_activation(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, SRC_SWRSR, 0); + rsnd_mod_write(mod, SRC_SWRSR, 1); +} + +static void rsnd_src_halt(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, SRC_SRCIR, 1); + rsnd_mod_write(mod, SRC_SWRSR, 0); +} + +static struct dma_chan *rsnd_src_dma_req(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + int is_play = rsnd_io_is_play(io); + + return rsnd_dma_request_channel(rsnd_src_of_node(priv), + mod, + is_play ? "rx" : "tx"); +} + +static u32 rsnd_src_convert_rate(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_src *src = rsnd_mod_to_src(mod); + u32 convert_rate; + + if (!runtime) + return 0; + + if (!rsnd_src_sync_is_enabled(mod)) + return src->convert_rate; + + convert_rate = src->sync.val; + + if (!convert_rate) + convert_rate = src->convert_rate; + + if (!convert_rate) + convert_rate = runtime->rate; + + return convert_rate; +} + +unsigned int rsnd_src_get_rate(struct rsnd_priv *priv, + struct rsnd_dai_stream *io, + int is_in) +{ + struct rsnd_mod *src_mod = rsnd_io_to_mod_src(io); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + unsigned int rate = 0; + int is_play = rsnd_io_is_play(io); + + /* + * Playback + * runtime_rate -> [SRC] -> convert_rate + * + * Capture + * convert_rate -> [SRC] -> runtime_rate + */ + + if (is_play == is_in) + return runtime->rate; + + /* + * return convert rate if SRC is used, + * otherwise, return runtime->rate as usual + */ + if (src_mod) + rate = rsnd_src_convert_rate(io, src_mod); + + if (!rate) + rate = runtime->rate; + + return rate; +} + +static int rsnd_src_hw_params(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *fe_params) +{ + struct rsnd_src *src = rsnd_mod_to_src(mod); + struct snd_soc_pcm_runtime *fe = substream->private_data; + + /* + * SRC assumes that it is used under DPCM if user want to use + * sampling rate convert. Then, SRC should be FE. + * And then, this function will be called *after* BE settings. + * this means, each BE already has fixuped hw_params. + * see + * dpcm_fe_dai_hw_params() + * dpcm_be_dai_hw_params() + */ + src->convert_rate = 0; + if (fe->dai_link->dynamic) { + int stream = substream->stream; + struct snd_soc_dpcm *dpcm; + struct snd_pcm_hw_params *be_params; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + be_params = &dpcm->hw_params; + + if (params_rate(fe_params) != params_rate(be_params)) + src->convert_rate = params_rate(be_params); + } + } + + return 0; +} + +static void rsnd_src_set_convert_rate(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + int is_play = rsnd_io_is_play(io); + int use_src = 0; + u32 fin, fout; + u32 ifscr, fsrate, adinr; + u32 cr, route; + u32 bsdsr, bsisr; + u32 i_busif, o_busif, tmp; + uint ratio; + + if (!runtime) + return; + + fin = rsnd_src_get_in_rate(priv, io); + fout = rsnd_src_get_out_rate(priv, io); + + /* 6 - 1/6 are very enough ratio for SRC_BSDSR */ + if (fin == fout) + ratio = 0; + else if (fin > fout) + ratio = 100 * fin / fout; + else + ratio = 100 * fout / fin; + + if (ratio > 600) { + dev_err(dev, "FSO/FSI ratio error\n"); + return; + } + + use_src = (fin != fout) | rsnd_src_sync_is_enabled(mod); + + /* + * SRC_ADINR + */ + adinr = rsnd_get_adinr_bit(mod, io) | + rsnd_runtime_channel_original(io); + + /* + * SRC_IFSCR / SRC_IFSVR + */ + ifscr = 0; + fsrate = 0; + if (use_src) { + u64 n; + + ifscr = 1; + n = (u64)0x0400000 * fin; + do_div(n, fout); + fsrate = n; + } + + /* + * SRC_SRCCR / SRC_ROUTE_MODE0 + */ + cr = 0x00011110; + route = 0x0; + if (use_src) { + route = 0x1; + + if (rsnd_src_sync_is_enabled(mod)) { + cr |= 0x1; + route |= rsnd_io_is_play(io) ? + (0x1 << 24) : (0x1 << 25); + } + } + + /* + * SRC_BSDSR / SRC_BSISR + */ + switch (rsnd_mod_id(mod)) { + case 5: + case 6: + case 7: + case 8: + bsdsr = 0x02400000; /* 6 - 1/6 */ + bsisr = 0x00100060; /* 6 - 1/6 */ + break; + default: + bsdsr = 0x01800000; /* 6 - 1/6 */ + bsisr = 0x00100060 ;/* 6 - 1/6 */ + break; + } + + /* BUSIF_MODE */ + tmp = rsnd_get_busif_shift(io, mod); + i_busif = ( is_play ? tmp : 0) | 1; + o_busif = (!is_play ? tmp : 0) | 1; + + rsnd_mod_write(mod, SRC_ROUTE_MODE0, route); + + rsnd_mod_write(mod, SRC_SRCIR, 1); /* initialize */ + rsnd_mod_write(mod, SRC_ADINR, adinr); + rsnd_mod_write(mod, SRC_IFSCR, ifscr); + rsnd_mod_write(mod, SRC_IFSVR, fsrate); + rsnd_mod_write(mod, SRC_SRCCR, cr); + rsnd_mod_write(mod, SRC_BSDSR, bsdsr); + rsnd_mod_write(mod, SRC_BSISR, bsisr); + rsnd_mod_write(mod, SRC_SRCIR, 0); /* cancel initialize */ + + rsnd_mod_write(mod, SRC_I_BUSIF_MODE, i_busif); + rsnd_mod_write(mod, SRC_O_BUSIF_MODE, o_busif); + + rsnd_mod_write(mod, SRC_BUSIF_DALIGN, rsnd_get_dalign(mod, io)); + + rsnd_adg_set_src_timesel_gen2(mod, io, fin, fout); +} + +static int rsnd_src_irq(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv, + int enable) +{ + struct rsnd_src *src = rsnd_mod_to_src(mod); + u32 sys_int_val, int_val, sys_int_mask; + int irq = src->irq; + int id = rsnd_mod_id(mod); + + sys_int_val = + sys_int_mask = OUF_SRC(id); + int_val = 0x3300; + + /* + * IRQ is not supported on non-DT + * see + * rsnd_src_probe_() + */ + if ((irq <= 0) || !enable) { + sys_int_val = 0; + int_val = 0; + } + + /* + * WORKAROUND + * + * ignore over flow error when rsnd_src_sync_is_enabled() + */ + if (rsnd_src_sync_is_enabled(mod)) + sys_int_val = sys_int_val & 0xffff; + + rsnd_mod_write(mod, SRC_INT_ENABLE0, int_val); + rsnd_mod_bset(mod, SCU_SYS_INT_EN0, sys_int_mask, sys_int_val); + rsnd_mod_bset(mod, SCU_SYS_INT_EN1, sys_int_mask, sys_int_val); + + return 0; +} + +static void rsnd_src_status_clear(struct rsnd_mod *mod) +{ + u32 val = OUF_SRC(rsnd_mod_id(mod)); + + rsnd_mod_write(mod, SCU_SYS_STATUS0, val); + rsnd_mod_write(mod, SCU_SYS_STATUS1, val); +} + +static bool rsnd_src_error_occurred(struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + u32 val0, val1; + u32 status0, status1; + bool ret = false; + + val0 = val1 = OUF_SRC(rsnd_mod_id(mod)); + + /* + * WORKAROUND + * + * ignore over flow error when rsnd_src_sync_is_enabled() + */ + if (rsnd_src_sync_is_enabled(mod)) + val0 = val0 & 0xffff; + + status0 = rsnd_mod_read(mod, SCU_SYS_STATUS0); + status1 = rsnd_mod_read(mod, SCU_SYS_STATUS1); + if ((status0 & val0) || (status1 & val1)) { + rsnd_dbg_irq_status(dev, "%s[%d] err status : 0x%08x, 0x%08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), + status0, status1); + + ret = true; + } + + return ret; +} + +static int rsnd_src_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + u32 val; + + /* + * WORKAROUND + * + * Enable SRC output if you want to use sync convert together with DVC + */ + val = (rsnd_io_to_mod_dvc(io) && !rsnd_src_sync_is_enabled(mod)) ? + 0x01 : 0x11; + + rsnd_mod_write(mod, SRC_CTRL, val); + + return 0; +} + +static int rsnd_src_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + rsnd_mod_write(mod, SRC_CTRL, 0); + + return 0; +} + +static int rsnd_src_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_src *src = rsnd_mod_to_src(mod); + + /* reset sync convert_rate */ + src->sync.val = 0; + + rsnd_mod_power_on(mod); + + rsnd_src_activation(mod); + + rsnd_src_set_convert_rate(io, mod); + + rsnd_src_status_clear(mod); + + return 0; +} + +static int rsnd_src_quit(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_src *src = rsnd_mod_to_src(mod); + + rsnd_src_halt(mod); + + rsnd_mod_power_off(mod); + + /* reset sync convert_rate */ + src->sync.val = 0; + + return 0; +} + +static void __rsnd_src_interrupt(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + bool stop = false; + + spin_lock(&priv->lock); + + /* ignore all cases if not working */ + if (!rsnd_io_is_working(io)) + goto rsnd_src_interrupt_out; + + if (rsnd_src_error_occurred(mod)) + stop = true; + + rsnd_src_status_clear(mod); +rsnd_src_interrupt_out: + + spin_unlock(&priv->lock); + + if (stop) + snd_pcm_stop_xrun(io->substream); +} + +static irqreturn_t rsnd_src_interrupt(int irq, void *data) +{ + struct rsnd_mod *mod = data; + + rsnd_mod_interrupt(mod, __rsnd_src_interrupt); + + return IRQ_HANDLED; +} + +static int rsnd_src_probe_(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_src *src = rsnd_mod_to_src(mod); + struct device *dev = rsnd_priv_to_dev(priv); + int irq = src->irq; + int ret; + + if (irq > 0) { + /* + * IRQ is not supported on non-DT + * see + * rsnd_src_irq() + */ + ret = devm_request_irq(dev, irq, + rsnd_src_interrupt, + IRQF_SHARED, + dev_name(dev), mod); + if (ret) + return ret; + } + + ret = rsnd_dma_attach(io, mod, &src->dma); + + return ret; +} + +static int rsnd_src_pcm_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd) +{ + struct rsnd_src *src = rsnd_mod_to_src(mod); + int ret; + + /* + * enable SRC sync convert if possible + */ + + /* + * It can't use SRC Synchronous convert + * when Capture if it uses CMD + */ + if (rsnd_io_to_mod_cmd(io) && !rsnd_io_is_play(io)) + return 0; + + /* + * enable sync convert + */ + ret = rsnd_kctrl_new_s(mod, io, rtd, + rsnd_io_is_play(io) ? + "SRC Out Rate Switch" : + "SRC In Rate Switch", + rsnd_kctrl_accept_anytime, + rsnd_src_set_convert_rate, + &src->sen, 1); + if (ret < 0) + return ret; + + ret = rsnd_kctrl_new_s(mod, io, rtd, + rsnd_io_is_play(io) ? + "SRC Out Rate" : + "SRC In Rate", + rsnd_kctrl_accept_runtime, + rsnd_src_set_convert_rate, + &src->sync, 192000); + + return ret; +} + +static struct rsnd_mod_ops rsnd_src_ops = { + .name = SRC_NAME, + .dma_req = rsnd_src_dma_req, + .probe = rsnd_src_probe_, + .init = rsnd_src_init, + .quit = rsnd_src_quit, + .start = rsnd_src_start, + .stop = rsnd_src_stop, + .irq = rsnd_src_irq, + .hw_params = rsnd_src_hw_params, + .pcm_new = rsnd_src_pcm_new, +}; + +struct rsnd_mod *rsnd_src_mod_get(struct rsnd_priv *priv, int id) +{ + if (WARN_ON(id < 0 || id >= rsnd_src_nr(priv))) + id = 0; + + return rsnd_mod_get(rsnd_src_get(priv, id)); +} + +int rsnd_src_probe(struct rsnd_priv *priv) +{ + struct device_node *node; + struct device_node *np; + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_src *src; + struct clk *clk; + char name[RSND_SRC_NAME_SIZE]; + int i, nr, ret; + + /* This driver doesn't support Gen1 at this point */ + if (rsnd_is_gen1(priv)) + return 0; + + node = rsnd_src_of_node(priv); + if (!node) + return 0; /* not used is not error */ + + nr = of_get_child_count(node); + if (!nr) { + ret = -EINVAL; + goto rsnd_src_probe_done; + } + + src = devm_kcalloc(dev, nr, sizeof(*src), GFP_KERNEL); + if (!src) { + ret = -ENOMEM; + goto rsnd_src_probe_done; + } + + priv->src_nr = nr; + priv->src = src; + + i = 0; + for_each_child_of_node(node, np) { + if (!of_device_is_available(np)) + goto skip; + + src = rsnd_src_get(priv, i); + + snprintf(name, RSND_SRC_NAME_SIZE, "%s.%d", + SRC_NAME, i); + + src->irq = irq_of_parse_and_map(np, 0); + if (!src->irq) { + ret = -EINVAL; + of_node_put(np); + goto rsnd_src_probe_done; + } + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(np); + goto rsnd_src_probe_done; + } + + ret = rsnd_mod_init(priv, rsnd_mod_get(src), + &rsnd_src_ops, clk, rsnd_mod_get_status, + RSND_MOD_SRC, i); + if (ret) { + of_node_put(np); + goto rsnd_src_probe_done; + } + +skip: + i++; + } + + ret = 0; + +rsnd_src_probe_done: + of_node_put(node); + + return ret; +} + +void rsnd_src_remove(struct rsnd_priv *priv) +{ + struct rsnd_src *src; + int i; + + for_each_rsnd_src(src, priv, i) { + rsnd_mod_quit(rsnd_mod_get(src)); + } +} diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c new file mode 100644 index 000000000..a6cf2ac22 --- /dev/null +++ b/sound/soc/sh/rcar/ssi.c @@ -0,0 +1,1207 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car SSIU/SSI support +// +// Copyright (C) 2013 Renesas Solutions Corp. +// Kuninori Morimoto +// +// Based on fsi.c +// Kuninori Morimoto + +/* + * you can enable below define if you don't need + * SSI interrupt status debug message when debugging + * see rsnd_dbg_irq_status() + * + * #define RSND_DEBUG_NO_IRQ_STATUS 1 + */ + +#include +#include +#include "rsnd.h" +#define RSND_SSI_NAME_SIZE 16 + +/* + * SSICR + */ +#define FORCE (1 << 31) /* Fixed */ +#define DMEN (1 << 28) /* DMA Enable */ +#define UIEN (1 << 27) /* Underflow Interrupt Enable */ +#define OIEN (1 << 26) /* Overflow Interrupt Enable */ +#define IIEN (1 << 25) /* Idle Mode Interrupt Enable */ +#define DIEN (1 << 24) /* Data Interrupt Enable */ +#define CHNL_4 (1 << 22) /* Channels */ +#define CHNL_6 (2 << 22) /* Channels */ +#define CHNL_8 (3 << 22) /* Channels */ +#define DWL_MASK (7 << 19) /* Data Word Length mask */ +#define DWL_8 (0 << 19) /* Data Word Length */ +#define DWL_16 (1 << 19) /* Data Word Length */ +#define DWL_18 (2 << 19) /* Data Word Length */ +#define DWL_20 (3 << 19) /* Data Word Length */ +#define DWL_22 (4 << 19) /* Data Word Length */ +#define DWL_24 (5 << 19) /* Data Word Length */ +#define DWL_32 (6 << 19) /* Data Word Length */ + +#define SWL_32 (3 << 16) /* R/W System Word Length */ +#define SCKD (1 << 15) /* Serial Bit Clock Direction */ +#define SWSD (1 << 14) /* Serial WS Direction */ +#define SCKP (1 << 13) /* Serial Bit Clock Polarity */ +#define SWSP (1 << 12) /* Serial WS Polarity */ +#define SDTA (1 << 10) /* Serial Data Alignment */ +#define PDTA (1 << 9) /* Parallel Data Alignment */ +#define DEL (1 << 8) /* Serial Data Delay */ +#define CKDV(v) (v << 4) /* Serial Clock Division Ratio */ +#define TRMD (1 << 1) /* Transmit/Receive Mode Select */ +#define EN (1 << 0) /* SSI Module Enable */ + +/* + * SSISR + */ +#define UIRQ (1 << 27) /* Underflow Error Interrupt Status */ +#define OIRQ (1 << 26) /* Overflow Error Interrupt Status */ +#define IIRQ (1 << 25) /* Idle Mode Interrupt Status */ +#define DIRQ (1 << 24) /* Data Interrupt Status Flag */ + +/* + * SSIWSR + */ +#define CONT (1 << 8) /* WS Continue Function */ +#define WS_MODE (1 << 0) /* WS Mode */ + +#define SSI_NAME "ssi" + +struct rsnd_ssi { + struct rsnd_mod mod; + + u32 flags; + u32 cr_own; + u32 cr_clk; + u32 cr_mode; + u32 cr_en; + u32 wsr; + int chan; + int rate; + int irq; + unsigned int usrcnt; + + /* for PIO */ + int byte_pos; + int byte_per_period; + int next_period_byte; +}; + +/* flags */ +#define RSND_SSI_CLK_PIN_SHARE (1 << 0) +#define RSND_SSI_NO_BUSIF (1 << 1) /* SSI+DMA without BUSIF */ +#define RSND_SSI_HDMI0 (1 << 2) /* for HDMI0 */ +#define RSND_SSI_HDMI1 (1 << 3) /* for HDMI1 */ +#define RSND_SSI_PROBED (1 << 4) + +#define for_each_rsnd_ssi(pos, priv, i) \ + for (i = 0; \ + (i < rsnd_ssi_nr(priv)) && \ + ((pos) = ((struct rsnd_ssi *)(priv)->ssi + i)); \ + i++) + +#define rsnd_ssi_get(priv, id) ((struct rsnd_ssi *)(priv->ssi) + id) +#define rsnd_ssi_nr(priv) ((priv)->ssi_nr) +#define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod) +#define rsnd_ssi_is_parent(ssi, io) ((ssi) == rsnd_io_to_mod_ssip(io)) +#define rsnd_ssi_is_multi_slave(mod, io) \ + (rsnd_ssi_multi_slaves(io) & (1 << rsnd_mod_id(mod))) +#define rsnd_ssi_is_run_mods(mod, io) \ + (rsnd_ssi_run_mods(io) & (1 << rsnd_mod_id(mod))) +#define rsnd_ssi_can_output_clk(mod) (!__rsnd_ssi_is_pin_sharing(mod)) + +int rsnd_ssi_hdmi_port(struct rsnd_dai_stream *io) +{ + struct rsnd_mod *mod = rsnd_io_to_mod_ssi(io); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + + if (rsnd_flags_has(ssi, RSND_SSI_HDMI0)) + return RSND_SSI_HDMI_PORT0; + + if (rsnd_flags_has(ssi, RSND_SSI_HDMI1)) + return RSND_SSI_HDMI_PORT1; + + return 0; +} + +int rsnd_ssi_use_busif(struct rsnd_dai_stream *io) +{ + struct rsnd_mod *mod = rsnd_io_to_mod_ssi(io); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + int use_busif = 0; + + if (!rsnd_ssi_is_dma_mode(mod)) + return 0; + + if (!(rsnd_flags_has(ssi, RSND_SSI_NO_BUSIF))) + use_busif = 1; + if (rsnd_io_to_mod_src(io)) + use_busif = 1; + + return use_busif; +} + +static void rsnd_ssi_status_clear(struct rsnd_mod *mod) +{ + rsnd_mod_write(mod, SSISR, 0); +} + +static u32 rsnd_ssi_status_get(struct rsnd_mod *mod) +{ + return rsnd_mod_read(mod, SSISR); +} + +static void rsnd_ssi_status_check(struct rsnd_mod *mod, + u32 bit) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + u32 status; + int i; + + for (i = 0; i < 1024; i++) { + status = rsnd_ssi_status_get(mod); + if (status & bit) + return; + + udelay(5); + } + + dev_warn(dev, "%s[%d] status check failed\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); +} + +static u32 rsnd_ssi_multi_slaves(struct rsnd_dai_stream *io) +{ + struct rsnd_mod *mod; + enum rsnd_mod_type types[] = { + RSND_MOD_SSIM1, + RSND_MOD_SSIM2, + RSND_MOD_SSIM3, + }; + int i, mask; + + mask = 0; + for (i = 0; i < ARRAY_SIZE(types); i++) { + mod = rsnd_io_to_mod(io, types[i]); + if (!mod) + continue; + + mask |= 1 << rsnd_mod_id(mod); + } + + return mask; +} + +static u32 rsnd_ssi_run_mods(struct rsnd_dai_stream *io) +{ + struct rsnd_mod *ssi_mod = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io); + u32 mods; + + mods = rsnd_ssi_multi_slaves_runtime(io) | + 1 << rsnd_mod_id(ssi_mod); + + if (ssi_parent_mod) + mods |= 1 << rsnd_mod_id(ssi_parent_mod); + + return mods; +} + +u32 rsnd_ssi_multi_slaves_runtime(struct rsnd_dai_stream *io) +{ + if (rsnd_runtime_is_ssi_multi(io)) + return rsnd_ssi_multi_slaves(io); + + return 0; +} + +unsigned int rsnd_ssi_clk_query(struct rsnd_priv *priv, + int param1, int param2, int *idx) +{ + int ssi_clk_mul_table[] = { + 1, 2, 4, 8, 16, 6, 12, + }; + int j, ret; + unsigned int main_rate; + + for (j = 0; j < ARRAY_SIZE(ssi_clk_mul_table); j++) { + + /* + * It will set SSIWSR.CONT here, but SSICR.CKDV = 000 + * with it is not allowed. (SSIWSR.WS_MODE with + * SSICR.CKDV = 000 is not allowed either). + * Skip it. See SSICR.CKDV + */ + if (j == 0) + continue; + + /* + * this driver is assuming that + * system word is 32bit x chan + * see rsnd_ssi_init() + */ + main_rate = 32 * param1 * param2 * ssi_clk_mul_table[j]; + + ret = rsnd_adg_clk_query(priv, main_rate); + if (ret < 0) + continue; + + if (idx) + *idx = j; + + return main_rate; + } + + return 0; +} + +static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + int chan = rsnd_runtime_channel_for_ssi(io); + int idx, ret; + unsigned int main_rate; + unsigned int rate = rsnd_io_is_play(io) ? + rsnd_src_get_out_rate(priv, io) : + rsnd_src_get_in_rate(priv, io); + + if (!rsnd_rdai_is_clk_master(rdai)) + return 0; + + if (!rsnd_ssi_can_output_clk(mod)) + return 0; + + if (rsnd_ssi_is_multi_slave(mod, io)) + return 0; + + if (ssi->usrcnt > 0) { + if (ssi->rate != rate) { + dev_err(dev, "SSI parent/child should use same rate\n"); + return -EINVAL; + } + + return 0; + } + + main_rate = rsnd_ssi_clk_query(priv, rate, chan, &idx); + if (!main_rate) { + dev_err(dev, "unsupported clock rate\n"); + return -EIO; + } + + ret = rsnd_adg_ssi_clk_try_start(mod, main_rate); + if (ret < 0) + return ret; + + /* + * SSI clock will be output contiguously + * by below settings. + * This means, rsnd_ssi_master_clk_start() + * and rsnd_ssi_register_setup() are necessary + * for SSI parent + * + * SSICR : FORCE, SCKD, SWSD + * SSIWSR : CONT + */ + ssi->cr_clk = FORCE | SWL_32 | SCKD | SWSD | CKDV(idx); + ssi->wsr = CONT; + ssi->rate = rate; + + dev_dbg(dev, "%s[%d] outputs %u Hz\n", + rsnd_mod_name(mod), + rsnd_mod_id(mod), rate); + + return 0; +} + +static void rsnd_ssi_master_clk_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + + if (!rsnd_rdai_is_clk_master(rdai)) + return; + + if (!rsnd_ssi_can_output_clk(mod)) + return; + + if (ssi->usrcnt > 1) + return; + + ssi->cr_clk = 0; + ssi->rate = 0; + + rsnd_adg_ssi_clk_stop(mod); +} + +static void rsnd_ssi_config_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + u32 cr_own = ssi->cr_own; + u32 cr_mode = ssi->cr_mode; + u32 wsr = ssi->wsr; + int is_tdm; + + is_tdm = rsnd_runtime_is_ssi_tdm(io); + + /* + * always use 32bit system word. + * see also rsnd_ssi_master_clk_enable() + */ + cr_own |= FORCE | SWL_32; + + if (rdai->bit_clk_inv) + cr_own |= SCKP; + if (rdai->frm_clk_inv ^ is_tdm) + cr_own |= SWSP; + if (rdai->data_alignment) + cr_own |= SDTA; + if (rdai->sys_delay) + cr_own |= DEL; + + /* + * We shouldn't exchange SWSP after running. + * This means, parent needs to care it. + */ + if (rsnd_ssi_is_parent(mod, io)) + goto init_end; + + if (rsnd_io_is_play(io)) + cr_own |= TRMD; + + cr_own &= ~DWL_MASK; + switch (snd_pcm_format_width(runtime->format)) { + case 16: + cr_own |= DWL_16; + break; + case 24: + cr_own |= DWL_24; + break; + } + + if (rsnd_ssi_is_dma_mode(mod)) { + cr_mode = UIEN | OIEN | /* over/under run */ + DMEN; /* DMA : enable DMA */ + } else { + cr_mode = DIEN; /* PIO : enable Data interrupt */ + } + + /* + * TDM Extend Mode + * see + * rsnd_ssiu_init_gen2() + */ + wsr = ssi->wsr; + if (is_tdm) { + wsr |= WS_MODE; + cr_own |= CHNL_8; + } +init_end: + ssi->cr_own = cr_own; + ssi->cr_mode = cr_mode; + ssi->wsr = wsr; +} + +static void rsnd_ssi_register_setup(struct rsnd_mod *mod) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + + rsnd_mod_write(mod, SSIWSR, ssi->wsr); + rsnd_mod_write(mod, SSICR, ssi->cr_own | + ssi->cr_clk | + ssi->cr_mode | + ssi->cr_en); +} + +/* + * SSI mod common functions + */ +static int rsnd_ssi_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + + if (!rsnd_ssi_is_run_mods(mod, io)) + return 0; + + ssi->usrcnt++; + + rsnd_mod_power_on(mod); + + rsnd_ssi_config_init(mod, io); + + rsnd_ssi_register_setup(mod); + + /* clear error status */ + rsnd_ssi_status_clear(mod); + + return 0; +} + +static int rsnd_ssi_quit(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct device *dev = rsnd_priv_to_dev(priv); + + if (!rsnd_ssi_is_run_mods(mod, io)) + return 0; + + if (!ssi->usrcnt) { + dev_err(dev, "%s[%d] usrcnt error\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + return -EIO; + } + + rsnd_ssi_master_clk_stop(mod, io); + + rsnd_mod_power_off(mod); + + ssi->usrcnt--; + + if (!ssi->usrcnt) { + ssi->cr_own = 0; + ssi->cr_mode = 0; + ssi->wsr = 0; + } + + return 0; +} + +static int rsnd_ssi_hw_params(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + int chan = params_channels(params); + + /* + * snd_pcm_ops::hw_params will be called *before* + * snd_soc_dai_ops::trigger. Thus, ssi->usrcnt is 0 + * in 1st call. + */ + if (ssi->usrcnt) { + /* + * Already working. + * It will happen if SSI has parent/child connection. + * it is error if child <-> parent SSI uses + * different channels. + */ + if (ssi->chan != chan) + return -EIO; + } + + ssi->chan = chan; + + return 0; +} + +static int rsnd_ssi_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + + if (!rsnd_ssi_is_run_mods(mod, io)) + return 0; + + /* + * EN will be set via SSIU :: SSI_CONTROL + * if Multi channel mode + */ + if (rsnd_ssi_multi_slaves_runtime(io)) + return 0; + + /* + * EN is for data output. + * SSI parent EN is not needed. + */ + if (rsnd_ssi_is_parent(mod, io)) + return 0; + + ssi->cr_en = EN; + + rsnd_mod_write(mod, SSICR, ssi->cr_own | + ssi->cr_clk | + ssi->cr_mode | + ssi->cr_en); + + return 0; +} + +static int rsnd_ssi_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + u32 cr; + + if (!rsnd_ssi_is_run_mods(mod, io)) + return 0; + + if (rsnd_ssi_is_parent(mod, io)) + return 0; + + cr = ssi->cr_own | + ssi->cr_clk; + + /* + * disable all IRQ, + * Playback: Wait all data was sent + * Capture: It might not receave data. Do nothing + */ + if (rsnd_io_is_play(io)) { + rsnd_mod_write(mod, SSICR, cr | ssi->cr_en); + rsnd_ssi_status_check(mod, DIRQ); + } + + /* In multi-SSI mode, stop is performed by setting ssi0129 in + * SSI_CONTROL to 0 (in rsnd_ssio_stop_gen2). Do nothing here. + */ + if (rsnd_ssi_multi_slaves_runtime(io)) + return 0; + + /* + * disable SSI, + * and, wait idle state + */ + rsnd_mod_write(mod, SSICR, cr); /* disabled all */ + rsnd_ssi_status_check(mod, IIRQ); + + ssi->cr_en = 0; + + return 0; +} + +static int rsnd_ssi_irq(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv, + int enable) +{ + u32 val = 0; + + if (rsnd_is_gen1(priv)) + return 0; + + if (rsnd_ssi_is_parent(mod, io)) + return 0; + + if (!rsnd_ssi_is_run_mods(mod, io)) + return 0; + + if (enable) + val = rsnd_ssi_is_dma_mode(mod) ? 0x0e000000 : 0x0f000000; + + rsnd_mod_write(mod, SSI_INT_ENABLE, val); + + return 0; +} + +static bool rsnd_ssi_pio_interrupt(struct rsnd_mod *mod, + struct rsnd_dai_stream *io); +static void __rsnd_ssi_interrupt(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + int is_dma = rsnd_ssi_is_dma_mode(mod); + u32 status; + bool elapsed = false; + bool stop = false; + + spin_lock(&priv->lock); + + /* ignore all cases if not working */ + if (!rsnd_io_is_working(io)) + goto rsnd_ssi_interrupt_out; + + status = rsnd_ssi_status_get(mod); + + /* PIO only */ + if (!is_dma && (status & DIRQ)) + elapsed = rsnd_ssi_pio_interrupt(mod, io); + + /* DMA only */ + if (is_dma && (status & (UIRQ | OIRQ))) { + rsnd_dbg_irq_status(dev, "%s[%d] err status : 0x%08x\n", + rsnd_mod_name(mod), rsnd_mod_id(mod), status); + + stop = true; + } + + rsnd_ssi_status_clear(mod); +rsnd_ssi_interrupt_out: + spin_unlock(&priv->lock); + + if (elapsed) + rsnd_dai_period_elapsed(io); + + if (stop) + snd_pcm_stop_xrun(io->substream); + +} + +static irqreturn_t rsnd_ssi_interrupt(int irq, void *data) +{ + struct rsnd_mod *mod = data; + + rsnd_mod_interrupt(mod, __rsnd_ssi_interrupt); + + return IRQ_HANDLED; +} + +/* + * SSI PIO + */ +static void rsnd_ssi_parent_attach(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + + if (!__rsnd_ssi_is_pin_sharing(mod)) + return; + + if (!rsnd_rdai_is_clk_master(rdai)) + return; + + if (rsnd_ssi_is_multi_slave(mod, io)) + return; + + switch (rsnd_mod_id(mod)) { + case 1: + case 2: + rsnd_dai_connect(rsnd_ssi_mod_get(priv, 0), io, RSND_MOD_SSIP); + break; + case 4: + rsnd_dai_connect(rsnd_ssi_mod_get(priv, 3), io, RSND_MOD_SSIP); + break; + case 8: + rsnd_dai_connect(rsnd_ssi_mod_get(priv, 7), io, RSND_MOD_SSIP); + break; + } +} + +static int rsnd_ssi_pcm_new(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct snd_soc_pcm_runtime *rtd) +{ + /* + * rsnd_rdai_is_clk_master() will be enabled after set_fmt, + * and, pcm_new will be called after it. + * This function reuse pcm_new at this point. + */ + rsnd_ssi_parent_attach(mod, io); + + return 0; +} + +static int rsnd_ssi_common_probe(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + int ret; + + /* + * SSIP/SSIU/IRQ are not needed on + * SSI Multi slaves + */ + if (rsnd_ssi_is_multi_slave(mod, io)) + return 0; + + /* + * It can't judge ssi parent at this point + * see rsnd_ssi_pcm_new() + */ + + ret = rsnd_ssiu_attach(io, mod); + if (ret < 0) + return ret; + + /* + * SSI might be called again as PIO fallback + * It is easy to manual handling for IRQ request/free + * + * OTOH, this function might be called many times if platform is + * using MIX. It needs xxx_attach() many times on xxx_probe(). + * Because of it, we can't control .probe/.remove calling count by + * mod->status. + * But it don't need to call request_irq() many times. + * Let's control it by RSND_SSI_PROBED flag. + */ + if (!rsnd_flags_has(ssi, RSND_SSI_PROBED)) { + ret = request_irq(ssi->irq, + rsnd_ssi_interrupt, + IRQF_SHARED, + dev_name(dev), mod); + + rsnd_flags_set(ssi, RSND_SSI_PROBED); + } + + return ret; +} + +static int rsnd_ssi_common_remove(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct rsnd_mod *pure_ssi_mod = rsnd_io_to_mod_ssi(io); + + /* Do nothing if non SSI (= SSI parent, multi SSI) mod */ + if (pure_ssi_mod != mod) + return 0; + + /* PIO will request IRQ again */ + if (rsnd_flags_has(ssi, RSND_SSI_PROBED)) { + free_irq(ssi->irq, mod); + + rsnd_flags_del(ssi, RSND_SSI_PROBED); + } + + return 0; +} + +/* + * SSI PIO functions + */ +static bool rsnd_ssi_pio_interrupt(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + u32 *buf = (u32 *)(runtime->dma_area + ssi->byte_pos); + int shift = 0; + int byte_pos; + bool elapsed = false; + + if (snd_pcm_format_width(runtime->format) == 24) + shift = 8; + + /* + * 8/16/32 data can be assesse to TDR/RDR register + * directly as 32bit data + * see rsnd_ssi_init() + */ + if (rsnd_io_is_play(io)) + rsnd_mod_write(mod, SSITDR, (*buf) << shift); + else + *buf = (rsnd_mod_read(mod, SSIRDR) >> shift); + + byte_pos = ssi->byte_pos + sizeof(*buf); + + if (byte_pos >= ssi->next_period_byte) { + int period_pos = byte_pos / ssi->byte_per_period; + + if (period_pos >= runtime->periods) { + byte_pos = 0; + period_pos = 0; + } + + ssi->next_period_byte = (period_pos + 1) * ssi->byte_per_period; + + elapsed = true; + } + + WRITE_ONCE(ssi->byte_pos, byte_pos); + + return elapsed; +} + +static int rsnd_ssi_pio_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + + if (!rsnd_ssi_is_parent(mod, io)) { + ssi->byte_pos = 0; + ssi->byte_per_period = runtime->period_size * + runtime->channels * + samples_to_bytes(runtime, 1); + ssi->next_period_byte = ssi->byte_per_period; + } + + return rsnd_ssi_init(mod, io, priv); +} + +static int rsnd_ssi_pio_pointer(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + snd_pcm_uframes_t *pointer) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + + *pointer = bytes_to_frames(runtime, READ_ONCE(ssi->byte_pos)); + + return 0; +} + +static int rsnd_ssi_prepare(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + return rsnd_ssi_master_clk_start(mod, io); +} + +static struct rsnd_mod_ops rsnd_ssi_pio_ops = { + .name = SSI_NAME, + .probe = rsnd_ssi_common_probe, + .remove = rsnd_ssi_common_remove, + .init = rsnd_ssi_pio_init, + .quit = rsnd_ssi_quit, + .start = rsnd_ssi_start, + .stop = rsnd_ssi_stop, + .irq = rsnd_ssi_irq, + .pointer = rsnd_ssi_pio_pointer, + .pcm_new = rsnd_ssi_pcm_new, + .hw_params = rsnd_ssi_hw_params, + .prepare = rsnd_ssi_prepare, +}; + +static int rsnd_ssi_dma_probe(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + int ret; + + /* + * SSIP/SSIU/IRQ/DMA are not needed on + * SSI Multi slaves + */ + if (rsnd_ssi_is_multi_slave(mod, io)) + return 0; + + ret = rsnd_ssi_common_probe(mod, io, priv); + if (ret) + return ret; + + /* SSI probe might be called many times in MUX multi path */ + ret = rsnd_dma_attach(io, mod, &io->dma); + + return ret; +} + +static int rsnd_ssi_fallback(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct device *dev = rsnd_priv_to_dev(priv); + + /* + * fallback to PIO + * + * SSI .probe might be called again. + * see + * rsnd_rdai_continuance_probe() + */ + mod->ops = &rsnd_ssi_pio_ops; + + dev_info(dev, "%s[%d] fallback to PIO mode\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + + return 0; +} + +static struct dma_chan *rsnd_ssi_dma_req(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + int is_play = rsnd_io_is_play(io); + char *name; + + if (rsnd_ssi_use_busif(io)) + name = is_play ? "rxu" : "txu"; + else + name = is_play ? "rx" : "tx"; + + return rsnd_dma_request_channel(rsnd_ssi_of_node(priv), + mod, name); +} + +static struct rsnd_mod_ops rsnd_ssi_dma_ops = { + .name = SSI_NAME, + .dma_req = rsnd_ssi_dma_req, + .probe = rsnd_ssi_dma_probe, + .remove = rsnd_ssi_common_remove, + .init = rsnd_ssi_init, + .quit = rsnd_ssi_quit, + .start = rsnd_ssi_start, + .stop = rsnd_ssi_stop, + .irq = rsnd_ssi_irq, + .pcm_new = rsnd_ssi_pcm_new, + .fallback = rsnd_ssi_fallback, + .hw_params = rsnd_ssi_hw_params, + .prepare = rsnd_ssi_prepare, +}; + +int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod) +{ + return mod->ops == &rsnd_ssi_dma_ops; +} + + +/* + * ssi mod function + */ +static void rsnd_ssi_connect(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + enum rsnd_mod_type types[] = { + RSND_MOD_SSI, + RSND_MOD_SSIM1, + RSND_MOD_SSIM2, + RSND_MOD_SSIM3, + }; + enum rsnd_mod_type type; + int i; + + /* try SSI -> SSIM1 -> SSIM2 -> SSIM3 */ + for (i = 0; i < ARRAY_SIZE(types); i++) { + type = types[i]; + if (!rsnd_io_to_mod(io, type)) { + rsnd_dai_connect(mod, io, type); + rsnd_rdai_channels_set(rdai, (i + 1) * 2); + rsnd_rdai_ssi_lane_set(rdai, (i + 1)); + return; + } + } +} + +void rsnd_parse_connect_ssi(struct rsnd_dai *rdai, + struct device_node *playback, + struct device_node *capture) +{ + struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai); + struct device_node *node; + struct device_node *np; + struct rsnd_mod *mod; + int i; + + node = rsnd_ssi_of_node(priv); + if (!node) + return; + + i = 0; + for_each_child_of_node(node, np) { + mod = rsnd_ssi_mod_get(priv, i); + if (np == playback) + rsnd_ssi_connect(mod, &rdai->playback); + if (np == capture) + rsnd_ssi_connect(mod, &rdai->capture); + i++; + } + + of_node_put(node); +} + +static void __rsnd_ssi_parse_hdmi_connection(struct rsnd_priv *priv, + struct rsnd_dai_stream *io, + struct device_node *remote_ep) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_mod *mod = rsnd_io_to_mod_ssi(io); + struct rsnd_ssi *ssi; + struct device_node *remote_node = of_graph_get_port_parent(remote_ep); + + /* support Gen3 only */ + if (!rsnd_is_gen3(priv)) + return; + + if (!mod) + return; + + ssi = rsnd_mod_to_ssi(mod); + + /* HDMI0 */ + if (strstr(remote_node->full_name, "hdmi@fead0000")) { + rsnd_flags_set(ssi, RSND_SSI_HDMI0); + dev_dbg(dev, "%s[%d] connected to HDMI0\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + } + + /* HDMI1 */ + if (strstr(remote_node->full_name, "hdmi@feae0000")) { + rsnd_flags_set(ssi, RSND_SSI_HDMI1); + dev_dbg(dev, "%s[%d] connected to HDMI1\n", + rsnd_mod_name(mod), rsnd_mod_id(mod)); + } +} + +void rsnd_ssi_parse_hdmi_connection(struct rsnd_priv *priv, + struct device_node *endpoint, + int dai_i) +{ + struct rsnd_dai *rdai = rsnd_rdai_get(priv, dai_i); + struct device_node *remote_ep; + + remote_ep = of_graph_get_remote_endpoint(endpoint); + if (!remote_ep) + return; + + __rsnd_ssi_parse_hdmi_connection(priv, &rdai->playback, remote_ep); + __rsnd_ssi_parse_hdmi_connection(priv, &rdai->capture, remote_ep); +} + +struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id) +{ + if (WARN_ON(id < 0 || id >= rsnd_ssi_nr(priv))) + id = 0; + + return rsnd_mod_get(rsnd_ssi_get(priv, id)); +} + +int __rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod) +{ + if (!mod) + return 0; + + return !!(rsnd_flags_has(rsnd_mod_to_ssi(mod), RSND_SSI_CLK_PIN_SHARE)); +} + +static u32 *rsnd_ssi_get_status(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + enum rsnd_mod_type type) +{ + /* + * SSIP (= SSI parent) needs to be special, otherwise, + * 2nd SSI might doesn't start. see also rsnd_mod_call() + * + * We can't include parent SSI status on SSI, because we don't know + * how many SSI requests parent SSI. Thus, it is localed on "io" now. + * ex) trouble case + * Playback: SSI0 + * Capture : SSI1 (needs SSI0) + * + * 1) start Capture -> SSI0/SSI1 are started. + * 2) start Playback -> SSI0 doesn't work, because it is already + * marked as "started" on 1) + * + * OTOH, using each mod's status is good for MUX case. + * It doesn't need to start in 2nd start + * ex) + * IO-0: SRC0 -> CTU1 -+-> MUX -> DVC -> SSIU -> SSI0 + * | + * IO-1: SRC1 -> CTU2 -+ + * + * 1) start IO-0 -> start SSI0 + * 2) start IO-1 -> SSI0 doesn't need to start, because it is + * already started on 1) + */ + if (type == RSND_MOD_SSIP) + return &io->parent_ssi_status; + + return rsnd_mod_get_status(io, mod, type); +} + +int rsnd_ssi_probe(struct rsnd_priv *priv) +{ + struct device_node *node; + struct device_node *np; + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_mod_ops *ops; + struct clk *clk; + struct rsnd_ssi *ssi; + char name[RSND_SSI_NAME_SIZE]; + int i, nr, ret; + + node = rsnd_ssi_of_node(priv); + if (!node) + return -EINVAL; + + nr = of_get_child_count(node); + if (!nr) { + ret = -EINVAL; + goto rsnd_ssi_probe_done; + } + + ssi = devm_kcalloc(dev, nr, sizeof(*ssi), GFP_KERNEL); + if (!ssi) { + ret = -ENOMEM; + goto rsnd_ssi_probe_done; + } + + priv->ssi = ssi; + priv->ssi_nr = nr; + + i = 0; + for_each_child_of_node(node, np) { + if (!of_device_is_available(np)) + goto skip; + + ssi = rsnd_ssi_get(priv, i); + + snprintf(name, RSND_SSI_NAME_SIZE, "%s.%d", + SSI_NAME, i); + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(np); + goto rsnd_ssi_probe_done; + } + + if (of_get_property(np, "shared-pin", NULL)) + rsnd_flags_set(ssi, RSND_SSI_CLK_PIN_SHARE); + + if (of_get_property(np, "no-busif", NULL)) + rsnd_flags_set(ssi, RSND_SSI_NO_BUSIF); + + ssi->irq = irq_of_parse_and_map(np, 0); + if (!ssi->irq) { + ret = -EINVAL; + of_node_put(np); + goto rsnd_ssi_probe_done; + } + + if (of_property_read_bool(np, "pio-transfer")) + ops = &rsnd_ssi_pio_ops; + else + ops = &rsnd_ssi_dma_ops; + + ret = rsnd_mod_init(priv, rsnd_mod_get(ssi), ops, clk, + rsnd_ssi_get_status, RSND_MOD_SSI, i); + if (ret) { + of_node_put(np); + goto rsnd_ssi_probe_done; + } +skip: + i++; + } + + ret = 0; + +rsnd_ssi_probe_done: + of_node_put(node); + + return ret; +} + +void rsnd_ssi_remove(struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi; + int i; + + for_each_rsnd_ssi(ssi, priv, i) { + rsnd_mod_quit(rsnd_mod_get(ssi)); + } +} diff --git a/sound/soc/sh/rcar/ssiu.c b/sound/soc/sh/rcar/ssiu.c new file mode 100644 index 000000000..7b5eb316c --- /dev/null +++ b/sound/soc/sh/rcar/ssiu.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car SSIU support +// +// Copyright (c) 2015 Kuninori Morimoto + +#include "rsnd.h" + +#define SSIU_NAME "ssiu" + +struct rsnd_ssiu { + struct rsnd_mod mod; +}; + +#define rsnd_ssiu_nr(priv) ((priv)->ssiu_nr) +#define for_each_rsnd_ssiu(pos, priv, i) \ + for (i = 0; \ + (i < rsnd_ssiu_nr(priv)) && \ + ((pos) = ((struct rsnd_ssiu *)(priv)->ssiu + i)); \ + i++) + +static int rsnd_ssiu_init(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dai *rdai = rsnd_io_to_rdai(io); + u32 multi_ssi_slaves = rsnd_ssi_multi_slaves_runtime(io); + int use_busif = rsnd_ssi_use_busif(io); + int id = rsnd_mod_id(mod); + u32 mask1, val1; + u32 mask2, val2; + + /* clear status */ + switch (id) { + case 0: + case 1: + case 2: + case 3: + case 4: + rsnd_mod_write(mod, SSI_SYS_STATUS0, 0xf << (id * 4)); + rsnd_mod_write(mod, SSI_SYS_STATUS2, 0xf << (id * 4)); + rsnd_mod_write(mod, SSI_SYS_STATUS4, 0xf << (id * 4)); + rsnd_mod_write(mod, SSI_SYS_STATUS6, 0xf << (id * 4)); + break; + case 9: + rsnd_mod_write(mod, SSI_SYS_STATUS1, 0xf << 4); + rsnd_mod_write(mod, SSI_SYS_STATUS3, 0xf << 4); + rsnd_mod_write(mod, SSI_SYS_STATUS5, 0xf << 4); + rsnd_mod_write(mod, SSI_SYS_STATUS7, 0xf << 4); + break; + } + + /* + * SSI_MODE0 + */ + rsnd_mod_bset(mod, SSI_MODE0, (1 << id), !use_busif << id); + + /* + * SSI_MODE1 + */ + mask1 = (1 << 4) | (1 << 20); /* mask sync bit */ + mask2 = (1 << 4); /* mask sync bit */ + val1 = val2 = 0; + if (id == 8) { + /* + * SSI8 pin is sharing with SSI7, nothing to do. + */ + } else if (rsnd_ssi_is_pin_sharing(io)) { + int shift = -1; + + switch (id) { + case 1: + shift = 0; + break; + case 2: + shift = 2; + break; + case 4: + shift = 16; + break; + default: + return -EINVAL; + } + + mask1 |= 0x3 << shift; + val1 = rsnd_rdai_is_clk_master(rdai) ? + 0x2 << shift : 0x1 << shift; + + } else if (multi_ssi_slaves) { + + mask2 |= 0x00000007; + mask1 |= 0x0000000f; + + switch (multi_ssi_slaves) { + case 0x0206: /* SSI0/1/2/9 */ + val2 = (1 << 4) | /* SSI0129 sync */ + (rsnd_rdai_is_clk_master(rdai) ? 0x2 : 0x1); + /* fall through */ + case 0x0006: /* SSI0/1/2 */ + val1 = rsnd_rdai_is_clk_master(rdai) ? + 0xa : 0x5; + + if (!val2) /* SSI012 sync */ + val1 |= (1 << 4); + } + } + + rsnd_mod_bset(mod, SSI_MODE1, mask1, val1); + rsnd_mod_bset(mod, SSI_MODE2, mask2, val2); + + return 0; +} + +static struct rsnd_mod_ops rsnd_ssiu_ops_gen1 = { + .name = SSIU_NAME, + .init = rsnd_ssiu_init, +}; + +static int rsnd_ssiu_init_gen2(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + int hdmi = rsnd_ssi_hdmi_port(io); + int ret; + u32 mode = 0; + + ret = rsnd_ssiu_init(mod, io, priv); + if (ret < 0) + return ret; + + if (rsnd_runtime_is_ssi_tdm(io)) { + /* + * TDM Extend Mode + * see + * rsnd_ssi_config_init() + */ + mode = 0x1; + } + + rsnd_mod_write(mod, SSI_MODE, mode); + + if (rsnd_ssi_use_busif(io)) { + rsnd_mod_write(mod, SSI_BUSIF_ADINR, + rsnd_get_adinr_bit(mod, io) | + (rsnd_io_is_play(io) ? + rsnd_runtime_channel_after_ctu(io) : + rsnd_runtime_channel_original(io))); + rsnd_mod_write(mod, SSI_BUSIF_MODE, + rsnd_get_busif_shift(io, mod) | 1); + rsnd_mod_write(mod, SSI_BUSIF_DALIGN, + rsnd_get_dalign(mod, io)); + } + + if (hdmi) { + enum rsnd_mod_type rsnd_ssi_array[] = { + RSND_MOD_SSIM1, + RSND_MOD_SSIM2, + RSND_MOD_SSIM3, + }; + struct rsnd_mod *ssi_mod = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *pos; + u32 val; + int i, shift; + + i = rsnd_mod_id(ssi_mod); + + /* output all same SSI as default */ + val = i << 16 | + i << 20 | + i << 24 | + i << 28 | + i; + + for_each_rsnd_mod_array(i, pos, io, rsnd_ssi_array) { + shift = (i * 4) + 20; + val = (val & ~(0xF << shift)) | + rsnd_mod_id(pos) << shift; + } + + switch (hdmi) { + case RSND_SSI_HDMI_PORT0: + rsnd_mod_write(mod, HDMI0_SEL, val); + break; + case RSND_SSI_HDMI_PORT1: + rsnd_mod_write(mod, HDMI1_SEL, val); + break; + } + } + + return 0; +} + +static int rsnd_ssiu_start_gen2(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + if (!rsnd_ssi_use_busif(io)) + return 0; + + rsnd_mod_write(mod, SSI_CTRL, 0x1); + + if (rsnd_ssi_multi_slaves_runtime(io)) + rsnd_mod_write(mod, SSI_CONTROL, 0x1); + + return 0; +} + +static int rsnd_ssiu_stop_gen2(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + if (!rsnd_ssi_use_busif(io)) + return 0; + + rsnd_mod_write(mod, SSI_CTRL, 0); + + if (rsnd_ssi_multi_slaves_runtime(io)) + rsnd_mod_write(mod, SSI_CONTROL, 0); + + return 0; +} + +static struct rsnd_mod_ops rsnd_ssiu_ops_gen2 = { + .name = SSIU_NAME, + .init = rsnd_ssiu_init_gen2, + .start = rsnd_ssiu_start_gen2, + .stop = rsnd_ssiu_stop_gen2, +}; + +static struct rsnd_mod *rsnd_ssiu_mod_get(struct rsnd_priv *priv, int id) +{ + if (WARN_ON(id < 0 || id >= rsnd_ssiu_nr(priv))) + id = 0; + + return rsnd_mod_get((struct rsnd_ssiu *)(priv->ssiu) + id); +} + +int rsnd_ssiu_attach(struct rsnd_dai_stream *io, + struct rsnd_mod *ssi_mod) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_mod *mod = rsnd_ssiu_mod_get(priv, rsnd_mod_id(ssi_mod)); + + rsnd_mod_confirm_ssi(ssi_mod); + + return rsnd_dai_connect(mod, io, mod->type); +} + +int rsnd_ssiu_probe(struct rsnd_priv *priv) +{ + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_ssiu *ssiu; + struct rsnd_mod_ops *ops; + int i, nr, ret; + + /* same number to SSI */ + nr = priv->ssi_nr; + ssiu = devm_kcalloc(dev, nr, sizeof(*ssiu), GFP_KERNEL); + if (!ssiu) + return -ENOMEM; + + priv->ssiu = ssiu; + priv->ssiu_nr = nr; + + if (rsnd_is_gen1(priv)) + ops = &rsnd_ssiu_ops_gen1; + else + ops = &rsnd_ssiu_ops_gen2; + + for_each_rsnd_ssiu(ssiu, priv, i) { + ret = rsnd_mod_init(priv, rsnd_mod_get(ssiu), + ops, NULL, rsnd_mod_get_status, + RSND_MOD_SSIU, i); + if (ret) + return ret; + } + + return 0; +} + +void rsnd_ssiu_remove(struct rsnd_priv *priv) +{ + struct rsnd_ssiu *ssiu; + int i; + + for_each_rsnd_ssiu(ssiu, priv, i) { + rsnd_mod_quit(rsnd_mod_get(ssiu)); + } +} diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c new file mode 100644 index 000000000..4bb4c13cf --- /dev/null +++ b/sound/soc/sh/sh7760-ac97.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Generic AC97 sound support for SH7760 +// +// (c) 2007 Manuel Lauss + +#include +#include +#include +#include +#include +#include +#include + +#define IPSEL 0xFE400034 + +static struct snd_soc_dai_link sh7760_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "hac-dai.0", /* HAC0 */ + .codec_dai_name = "ac97-hifi", + .platform_name = "sh7760-pcm-audio", + .codec_name = "ac97-codec", + .ops = NULL, +}; + +static struct snd_soc_card sh7760_ac97_soc_machine = { + .name = "SH7760 AC97", + .owner = THIS_MODULE, + .dai_link = &sh7760_ac97_dai, + .num_links = 1, +}; + +static struct platform_device *sh7760_ac97_snd_device; + +static int __init sh7760_ac97_init(void) +{ + int ret; + unsigned short ipsel; + + /* enable both AC97 controllers in pinmux reg */ + ipsel = __raw_readw(IPSEL); + __raw_writew(ipsel | (3 << 10), IPSEL); + + ret = -ENOMEM; + sh7760_ac97_snd_device = platform_device_alloc("soc-audio", -1); + if (!sh7760_ac97_snd_device) + goto out; + + platform_set_drvdata(sh7760_ac97_snd_device, + &sh7760_ac97_soc_machine); + ret = platform_device_add(sh7760_ac97_snd_device); + + if (ret) + platform_device_put(sh7760_ac97_snd_device); + +out: + return ret; +} + +static void __exit sh7760_ac97_exit(void) +{ + platform_device_unregister(sh7760_ac97_snd_device); +} + +module_init(sh7760_ac97_init); +module_exit(sh7760_ac97_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Generic SH7760 AC97 sound machine"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/soc/sh/siu.h b/sound/soc/sh/siu.h new file mode 100644 index 000000000..63a508fdf --- /dev/null +++ b/sound/soc/sh/siu.h @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// siu.h - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral. +// +// Copyright (C) 2009-2010 Guennadi Liakhovetski +// Copyright (C) 2006 Carlos Munoz + +#ifndef SIU_H +#define SIU_H + +/* Common kernel and user-space firmware-building defines and types */ + +#define YRAM0_SIZE (0x0040 / 4) /* 16 */ +#define YRAM1_SIZE (0x0080 / 4) /* 32 */ +#define YRAM2_SIZE (0x0040 / 4) /* 16 */ +#define YRAM3_SIZE (0x0080 / 4) /* 32 */ +#define YRAM4_SIZE (0x0080 / 4) /* 32 */ +#define YRAM_DEF_SIZE (YRAM0_SIZE + YRAM1_SIZE + YRAM2_SIZE + \ + YRAM3_SIZE + YRAM4_SIZE) +#define YRAM_FIR_SIZE (0x0400 / 4) /* 256 */ +#define YRAM_IIR_SIZE (0x0200 / 4) /* 128 */ + +#define XRAM0_SIZE (0x0400 / 4) /* 256 */ +#define XRAM1_SIZE (0x0200 / 4) /* 128 */ +#define XRAM2_SIZE (0x0200 / 4) /* 128 */ + +/* PRAM program array size */ +#define PRAM0_SIZE (0x0100 / 4) /* 64 */ +#define PRAM1_SIZE ((0x2000 - 0x0100) / 4) /* 1984 */ + +#include + +struct siu_spb_param { + __u32 ab1a; /* input FIFO address */ + __u32 ab0a; /* output FIFO address */ + __u32 dir; /* 0=the ather except CPUOUTPUT, 1=CPUINPUT */ + __u32 event; /* SPB program starting conditions */ + __u32 stfifo; /* STFIFO register setting value */ + __u32 trdat; /* TRDAT register setting value */ +}; + +struct siu_firmware { + __u32 yram_fir_coeff[YRAM_FIR_SIZE]; + __u32 pram0[PRAM0_SIZE]; + __u32 pram1[PRAM1_SIZE]; + __u32 yram0[YRAM0_SIZE]; + __u32 yram1[YRAM1_SIZE]; + __u32 yram2[YRAM2_SIZE]; + __u32 yram3[YRAM3_SIZE]; + __u32 yram4[YRAM4_SIZE]; + __u32 spbpar_num; + struct siu_spb_param spbpar[32]; +}; + +#ifdef __KERNEL__ + +#include +#include +#include +#include + +#include +#include +#include + +#define SIU_PERIOD_BYTES_MAX 8192 /* DMA transfer/period size */ +#define SIU_PERIOD_BYTES_MIN 256 /* DMA transfer/period size */ +#define SIU_PERIODS_MAX 64 /* Max periods in buffer */ +#define SIU_PERIODS_MIN 4 /* Min periods in buffer */ +#define SIU_BUFFER_BYTES_MAX (SIU_PERIOD_BYTES_MAX * SIU_PERIODS_MAX) + +/* SIU ports: only one can be used at a time */ +enum { + SIU_PORT_A, + SIU_PORT_B, + SIU_PORT_NUM, +}; + +/* SIU clock configuration */ +enum { + SIU_CLKA_PLL, + SIU_CLKA_EXT, + SIU_CLKB_PLL, + SIU_CLKB_EXT +}; + +struct device; +struct siu_info { + struct device *dev; + int port_id; + u32 __iomem *pram; + u32 __iomem *xram; + u32 __iomem *yram; + u32 __iomem *reg; + struct siu_firmware fw; +}; + +struct siu_stream { + struct tasklet_struct tasklet; + struct snd_pcm_substream *substream; + snd_pcm_format_t format; + size_t buf_bytes; + size_t period_bytes; + int cur_period; /* Period currently in dma */ + u32 volume; + snd_pcm_sframes_t xfer_cnt; /* Number of frames */ + u8 rw_flg; /* transfer status */ + /* DMA status */ + struct dma_chan *chan; /* DMA channel */ + struct dma_async_tx_descriptor *tx_desc; + dma_cookie_t cookie; + struct sh_dmae_slave param; +}; + +struct siu_port { + unsigned long play_cap; /* Used to track full duplex */ + struct snd_pcm *pcm; + struct siu_stream playback; + struct siu_stream capture; + u32 stfifo; /* STFIFO value from firmware */ + u32 trdat; /* TRDAT value from firmware */ +}; + +extern struct siu_port *siu_ports[SIU_PORT_NUM]; + +static inline struct siu_port *siu_port_info(struct snd_pcm_substream *substream) +{ + struct platform_device *pdev = + to_platform_device(substream->pcm->card->dev); + return siu_ports[pdev->id]; +} + +/* Register access */ +static inline void siu_write32(u32 __iomem *addr, u32 val) +{ + __raw_writel(val, addr); +} + +static inline u32 siu_read32(u32 __iomem *addr) +{ + return __raw_readl(addr); +} + +/* SIU registers */ +#define SIU_IFCTL (0x000 / sizeof(u32)) +#define SIU_SRCTL (0x004 / sizeof(u32)) +#define SIU_SFORM (0x008 / sizeof(u32)) +#define SIU_CKCTL (0x00c / sizeof(u32)) +#define SIU_TRDAT (0x010 / sizeof(u32)) +#define SIU_STFIFO (0x014 / sizeof(u32)) +#define SIU_DPAK (0x01c / sizeof(u32)) +#define SIU_CKREV (0x020 / sizeof(u32)) +#define SIU_EVNTC (0x028 / sizeof(u32)) +#define SIU_SBCTL (0x040 / sizeof(u32)) +#define SIU_SBPSET (0x044 / sizeof(u32)) +#define SIU_SBFSTS (0x068 / sizeof(u32)) +#define SIU_SBDVCA (0x06c / sizeof(u32)) +#define SIU_SBDVCB (0x070 / sizeof(u32)) +#define SIU_SBACTIV (0x074 / sizeof(u32)) +#define SIU_DMAIA (0x090 / sizeof(u32)) +#define SIU_DMAIB (0x094 / sizeof(u32)) +#define SIU_DMAOA (0x098 / sizeof(u32)) +#define SIU_DMAOB (0x09c / sizeof(u32)) +#define SIU_DMAML (0x0a0 / sizeof(u32)) +#define SIU_SPSTS (0x0cc / sizeof(u32)) +#define SIU_SPCTL (0x0d0 / sizeof(u32)) +#define SIU_BRGASEL (0x100 / sizeof(u32)) +#define SIU_BRRA (0x104 / sizeof(u32)) +#define SIU_BRGBSEL (0x108 / sizeof(u32)) +#define SIU_BRRB (0x10c / sizeof(u32)) + +extern struct snd_soc_component_driver siu_component; +extern struct siu_info *siu_i2s_data; + +int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card); +void siu_free_port(struct siu_port *port_info); + +#endif + +#endif /* SIU_H */ diff --git a/sound/soc/sh/siu_dai.c b/sound/soc/sh/siu_dai.c new file mode 100644 index 000000000..f2a386fcd --- /dev/null +++ b/sound/soc/sh/siu_dai.c @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral. +// +// Copyright (C) 2009-2010 Guennadi Liakhovetski +// Copyright (C) 2006 Carlos Munoz + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "siu.h" + +/* Board specifics */ +#if defined(CONFIG_CPU_SUBTYPE_SH7722) +# define SIU_MAX_VOLUME 0x1000 +#else +# define SIU_MAX_VOLUME 0x7fff +#endif + +#define PRAM_SIZE 0x2000 +#define XRAM_SIZE 0x800 +#define YRAM_SIZE 0x800 + +#define XRAM_OFFSET 0x4000 +#define YRAM_OFFSET 0x6000 +#define REG_OFFSET 0xc000 + +#define PLAYBACK_ENABLED 1 +#define CAPTURE_ENABLED 2 + +#define VOLUME_CAPTURE 0 +#define VOLUME_PLAYBACK 1 +#define DFLT_VOLUME_LEVEL 0x08000800 + +/* + * SPDIF is only available on port A and on some SIU implementations it is only + * available for input. Due to the lack of hardware to test it, SPDIF is left + * disabled in this driver version + */ +struct format_flag { + u32 i2s; + u32 pcm; + u32 spdif; + u32 mask; +}; + +struct port_flag { + struct format_flag playback; + struct format_flag capture; +}; + +struct siu_info *siu_i2s_data; + +static struct port_flag siu_flags[SIU_PORT_NUM] = { + [SIU_PORT_A] = { + .playback = { + .i2s = 0x50000000, + .pcm = 0x40000000, + .spdif = 0x80000000, /* not on all SIU versions */ + .mask = 0xd0000000, + }, + .capture = { + .i2s = 0x05000000, + .pcm = 0x04000000, + .spdif = 0x08000000, + .mask = 0x0d000000, + }, + }, + [SIU_PORT_B] = { + .playback = { + .i2s = 0x00500000, + .pcm = 0x00400000, + .spdif = 0, /* impossible - turn off */ + .mask = 0x00500000, + }, + .capture = { + .i2s = 0x00050000, + .pcm = 0x00040000, + .spdif = 0, /* impossible - turn off */ + .mask = 0x00050000, + }, + }, +}; + +static void siu_dai_start(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + + dev_dbg(port_info->pcm->card->dev, "%s\n", __func__); + + /* Issue software reset to siu */ + siu_write32(base + SIU_SRCTL, 0); + + /* Wait for the reset to take effect */ + udelay(1); + + port_info->stfifo = 0; + port_info->trdat = 0; + + /* portA, portB, SIU operate */ + siu_write32(base + SIU_SRCTL, 0x301); + + /* portA=256fs, portB=256fs */ + siu_write32(base + SIU_CKCTL, 0x40400000); + + /* portA's BRG does not divide SIUCKA */ + siu_write32(base + SIU_BRGASEL, 0); + siu_write32(base + SIU_BRRA, 0); + + /* portB's BRG divides SIUCKB by half */ + siu_write32(base + SIU_BRGBSEL, 1); + siu_write32(base + SIU_BRRB, 0); + + siu_write32(base + SIU_IFCTL, 0x44440000); + + /* portA: 32 bit/fs, master; portB: 32 bit/fs, master */ + siu_write32(base + SIU_SFORM, 0x0c0c0000); + + /* + * Volume levels: looks like the DSP firmware implements volume controls + * differently from what's described in the datasheet + */ + siu_write32(base + SIU_SBDVCA, port_info->playback.volume); + siu_write32(base + SIU_SBDVCB, port_info->capture.volume); +} + +static void siu_dai_stop(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + + /* SIU software reset */ + siu_write32(base + SIU_SRCTL, 0); +} + +static void siu_dai_spbAselect(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + struct siu_firmware *fw = &info->fw; + u32 *ydef = fw->yram0; + u32 idx; + + /* path A use */ + if (!info->port_id) + idx = 1; /* portA */ + else + idx = 2; /* portB */ + + ydef[0] = (fw->spbpar[idx].ab1a << 16) | + (fw->spbpar[idx].ab0a << 8) | + (fw->spbpar[idx].dir << 7) | 3; + ydef[1] = fw->yram0[1]; /* 0x03000300 */ + ydef[2] = (16 / 2) << 24; + ydef[3] = fw->yram0[3]; /* 0 */ + ydef[4] = fw->yram0[4]; /* 0 */ + ydef[7] = fw->spbpar[idx].event; + port_info->stfifo |= fw->spbpar[idx].stfifo; + port_info->trdat |= fw->spbpar[idx].trdat; +} + +static void siu_dai_spbBselect(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + struct siu_firmware *fw = &info->fw; + u32 *ydef = fw->yram0; + u32 idx; + + /* path B use */ + if (!info->port_id) + idx = 7; /* portA */ + else + idx = 8; /* portB */ + + ydef[5] = (fw->spbpar[idx].ab1a << 16) | + (fw->spbpar[idx].ab0a << 8) | 1; + ydef[6] = fw->spbpar[idx].event; + port_info->stfifo |= fw->spbpar[idx].stfifo; + port_info->trdat |= fw->spbpar[idx].trdat; +} + +static void siu_dai_open(struct siu_stream *siu_stream) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + u32 srctl, ifctl; + + srctl = siu_read32(base + SIU_SRCTL); + ifctl = siu_read32(base + SIU_IFCTL); + + switch (info->port_id) { + case SIU_PORT_A: + /* portA operates */ + srctl |= 0x200; + ifctl &= ~0xc2; + break; + case SIU_PORT_B: + /* portB operates */ + srctl |= 0x100; + ifctl &= ~0x31; + break; + } + + siu_write32(base + SIU_SRCTL, srctl); + /* Unmute and configure portA */ + siu_write32(base + SIU_IFCTL, ifctl); +} + +/* + * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower + * packing is supported + */ +static void siu_dai_pcmdatapack(struct siu_stream *siu_stream) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + u32 dpak; + + dpak = siu_read32(base + SIU_DPAK); + + switch (info->port_id) { + case SIU_PORT_A: + dpak &= ~0xc0000000; + break; + case SIU_PORT_B: + dpak &= ~0x00c00000; + break; + } + + siu_write32(base + SIU_DPAK, dpak); +} + +static int siu_dai_spbstart(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + struct siu_firmware *fw = &info->fw; + u32 *ydef = fw->yram0; + int cnt; + u32 __iomem *add; + u32 *ptr; + + /* Load SPB Program in PRAM */ + ptr = fw->pram0; + add = info->pram; + for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++) + siu_write32(add, *ptr); + + ptr = fw->pram1; + add = info->pram + (0x0100 / sizeof(u32)); + for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++) + siu_write32(add, *ptr); + + /* XRAM initialization */ + add = info->xram; + for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++) + siu_write32(add, 0); + + /* YRAM variable area initialization */ + add = info->yram; + for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++) + siu_write32(add, ydef[cnt]); + + /* YRAM FIR coefficient area initialization */ + add = info->yram + (0x0200 / sizeof(u32)); + for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++) + siu_write32(add, fw->yram_fir_coeff[cnt]); + + /* YRAM IIR coefficient area initialization */ + add = info->yram + (0x0600 / sizeof(u32)); + for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++) + siu_write32(add, 0); + + siu_write32(base + SIU_TRDAT, port_info->trdat); + port_info->trdat = 0x0; + + + /* SPB start condition: software */ + siu_write32(base + SIU_SBACTIV, 0); + /* Start SPB */ + siu_write32(base + SIU_SBCTL, 0xc0000000); + /* Wait for program to halt */ + cnt = 0x10000; + while (--cnt && siu_read32(base + SIU_SBCTL) != 0x80000000) + cpu_relax(); + + if (!cnt) + return -EBUSY; + + /* SPB program start address setting */ + siu_write32(base + SIU_SBPSET, 0x00400000); + /* SPB hardware start(FIFOCTL source) */ + siu_write32(base + SIU_SBACTIV, 0xc0000000); + + return 0; +} + +static void siu_dai_spbstop(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + + siu_write32(base + SIU_SBACTIV, 0); + /* SPB stop */ + siu_write32(base + SIU_SBCTL, 0); + + port_info->stfifo = 0; +} + +/* API functions */ + +/* Playback and capture hardware properties are identical */ +static const struct snd_pcm_hardware siu_dai_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = SIU_BUFFER_BYTES_MAX, + .period_bytes_min = SIU_PERIOD_BYTES_MIN, + .period_bytes_max = SIU_PERIOD_BYTES_MAX, + .periods_min = SIU_PERIODS_MIN, + .periods_max = SIU_PERIODS_MAX, +}; + +static int siu_dai_info_volume(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_info *uinfo) +{ + struct siu_port *port_info = snd_kcontrol_chip(kctrl); + + dev_dbg(port_info->pcm->card->dev, "%s\n", __func__); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SIU_MAX_VOLUME; + + return 0; +} + +static int siu_dai_get_volume(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct siu_port *port_info = snd_kcontrol_chip(kctrl); + struct device *dev = port_info->pcm->card->dev; + u32 vol; + + dev_dbg(dev, "%s\n", __func__); + + switch (kctrl->private_value) { + case VOLUME_PLAYBACK: + /* Playback is always on port 0 */ + vol = port_info->playback.volume; + ucontrol->value.integer.value[0] = vol & 0xffff; + ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; + break; + case VOLUME_CAPTURE: + /* Capture is always on port 1 */ + vol = port_info->capture.volume; + ucontrol->value.integer.value[0] = vol & 0xffff; + ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; + break; + default: + dev_err(dev, "%s() invalid private_value=%ld\n", + __func__, kctrl->private_value); + return -EINVAL; + } + + return 0; +} + +static int siu_dai_put_volume(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct siu_port *port_info = snd_kcontrol_chip(kctrl); + struct device *dev = port_info->pcm->card->dev; + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + u32 new_vol; + u32 cur_vol; + + dev_dbg(dev, "%s\n", __func__); + + if (ucontrol->value.integer.value[0] < 0 || + ucontrol->value.integer.value[0] > SIU_MAX_VOLUME || + ucontrol->value.integer.value[1] < 0 || + ucontrol->value.integer.value[1] > SIU_MAX_VOLUME) + return -EINVAL; + + new_vol = ucontrol->value.integer.value[0] | + ucontrol->value.integer.value[1] << 16; + + /* See comment above - DSP firmware implementation */ + switch (kctrl->private_value) { + case VOLUME_PLAYBACK: + /* Playback is always on port 0 */ + cur_vol = port_info->playback.volume; + siu_write32(base + SIU_SBDVCA, new_vol); + port_info->playback.volume = new_vol; + break; + case VOLUME_CAPTURE: + /* Capture is always on port 1 */ + cur_vol = port_info->capture.volume; + siu_write32(base + SIU_SBDVCB, new_vol); + port_info->capture.volume = new_vol; + break; + default: + dev_err(dev, "%s() invalid private_value=%ld\n", + __func__, kctrl->private_value); + return -EINVAL; + } + + if (cur_vol != new_vol) + return 1; + + return 0; +} + +static const struct snd_kcontrol_new playback_controls = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .info = siu_dai_info_volume, + .get = siu_dai_get_volume, + .put = siu_dai_put_volume, + .private_value = VOLUME_PLAYBACK, +}; + +static const struct snd_kcontrol_new capture_controls = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Volume", + .index = 0, + .info = siu_dai_info_volume, + .get = siu_dai_get_volume, + .put = siu_dai_put_volume, + .private_value = VOLUME_CAPTURE, +}; + +int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card) +{ + struct device *dev = card->dev; + struct snd_kcontrol *kctrl; + int ret; + + *port_info = kzalloc(sizeof(**port_info), GFP_KERNEL); + if (!*port_info) + return -ENOMEM; + + dev_dbg(dev, "%s: port #%d@%p\n", __func__, port, *port_info); + + (*port_info)->playback.volume = DFLT_VOLUME_LEVEL; + (*port_info)->capture.volume = DFLT_VOLUME_LEVEL; + + /* + * Add mixer support. The SPB is used to change the volume. Both + * ports use the same SPB. Therefore, we only register one + * control instance since it will be used by both channels. + * In error case we continue without controls. + */ + kctrl = snd_ctl_new1(&playback_controls, *port_info); + ret = snd_ctl_add(card, kctrl); + if (ret < 0) + dev_err(dev, + "failed to add playback controls %p port=%d err=%d\n", + kctrl, port, ret); + + kctrl = snd_ctl_new1(&capture_controls, *port_info); + ret = snd_ctl_add(card, kctrl); + if (ret < 0) + dev_err(dev, + "failed to add capture controls %p port=%d err=%d\n", + kctrl, port, ret); + + return 0; +} + +void siu_free_port(struct siu_port *port_info) +{ + kfree(port_info); +} + +static int siu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct siu_info *info = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *rt = substream->runtime; + struct siu_port *port_info = siu_port_info(substream); + int ret; + + dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__, + info->port_id, port_info); + + snd_soc_set_runtime_hwparams(substream, &siu_dai_pcm_hw); + + ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (unlikely(ret < 0)) + return ret; + + siu_dai_start(port_info); + + return 0; +} + +static void siu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct siu_info *info = snd_soc_dai_get_drvdata(dai); + struct siu_port *port_info = siu_port_info(substream); + + dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__, + info->port_id, port_info); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port_info->play_cap &= ~PLAYBACK_ENABLED; + else + port_info->play_cap &= ~CAPTURE_ENABLED; + + /* Stop the siu if the other stream is not using it */ + if (!port_info->play_cap) { + /* during stmread or stmwrite ? */ + if (WARN_ON(port_info->playback.rw_flg || port_info->capture.rw_flg)) + return; + siu_dai_spbstop(port_info); + siu_dai_stop(port_info); + } +} + +/* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */ +static int siu_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct siu_info *info = snd_soc_dai_get_drvdata(dai); + struct snd_pcm_runtime *rt = substream->runtime; + struct siu_port *port_info = siu_port_info(substream); + struct siu_stream *siu_stream; + int self, ret; + + dev_dbg(substream->pcm->card->dev, + "%s: port %d, active streams %lx, %d channels\n", + __func__, info->port_id, port_info->play_cap, rt->channels); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + self = PLAYBACK_ENABLED; + siu_stream = &port_info->playback; + } else { + self = CAPTURE_ENABLED; + siu_stream = &port_info->capture; + } + + /* Set up the siu if not already done */ + if (!port_info->play_cap) { + siu_stream->rw_flg = 0; /* stream-data transfer flag */ + + siu_dai_spbAselect(port_info); + siu_dai_spbBselect(port_info); + + siu_dai_open(siu_stream); + + siu_dai_pcmdatapack(siu_stream); + + ret = siu_dai_spbstart(port_info); + if (ret < 0) + goto fail; + } else { + ret = 0; + } + + port_info->play_cap |= self; + +fail: + return ret; +} + +/* + * SIU can set bus format to I2S / PCM / SPDIF independently for playback and + * capture, however, the current API sets the bus format globally for a DAI. + */ +static int siu_dai_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct siu_info *info = snd_soc_dai_get_drvdata(dai); + u32 __iomem *base = info->reg; + u32 ifctl; + + dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n", + __func__, fmt, info->port_id); + + if (info->port_id < 0) + return -ENODEV; + + /* Here select between I2S / PCM / SPDIF */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ifctl = siu_flags[info->port_id].playback.i2s | + siu_flags[info->port_id].capture.i2s; + break; + case SND_SOC_DAIFMT_LEFT_J: + ifctl = siu_flags[info->port_id].playback.pcm | + siu_flags[info->port_id].capture.pcm; + break; + /* SPDIF disabled - see comment at the top */ + default: + return -EINVAL; + } + + ifctl |= ~(siu_flags[info->port_id].playback.mask | + siu_flags[info->port_id].capture.mask) & + siu_read32(base + SIU_IFCTL); + siu_write32(base + SIU_IFCTL, ifctl); + + return 0; +} + +static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct clk *siu_clk, *parent_clk; + char *siu_name, *parent_name; + int ret; + + if (dir != SND_SOC_CLOCK_IN) + return -EINVAL; + + dev_dbg(dai->dev, "%s: using clock %d\n", __func__, clk_id); + + switch (clk_id) { + case SIU_CLKA_PLL: + siu_name = "siua_clk"; + parent_name = "pll_clk"; + break; + case SIU_CLKA_EXT: + siu_name = "siua_clk"; + parent_name = "siumcka_clk"; + break; + case SIU_CLKB_PLL: + siu_name = "siub_clk"; + parent_name = "pll_clk"; + break; + case SIU_CLKB_EXT: + siu_name = "siub_clk"; + parent_name = "siumckb_clk"; + break; + default: + return -EINVAL; + } + + siu_clk = clk_get(dai->dev, siu_name); + if (IS_ERR(siu_clk)) { + dev_err(dai->dev, "%s: cannot get a SIU clock: %ld\n", __func__, + PTR_ERR(siu_clk)); + return PTR_ERR(siu_clk); + } + + parent_clk = clk_get(dai->dev, parent_name); + if (IS_ERR(parent_clk)) { + ret = PTR_ERR(parent_clk); + dev_err(dai->dev, "cannot get a SIU clock parent: %d\n", ret); + goto epclkget; + } + + ret = clk_set_parent(siu_clk, parent_clk); + if (ret < 0) { + dev_err(dai->dev, "cannot reparent the SIU clock: %d\n", ret); + goto eclksetp; + } + + ret = clk_set_rate(siu_clk, freq); + if (ret < 0) + dev_err(dai->dev, "cannot set SIU clock rate: %d\n", ret); + + /* TODO: when clkdev gets reference counting we'll move these to siu_dai_shutdown() */ +eclksetp: + clk_put(parent_clk); +epclkget: + clk_put(siu_clk); + + return ret; +} + +static const struct snd_soc_dai_ops siu_dai_ops = { + .startup = siu_dai_startup, + .shutdown = siu_dai_shutdown, + .prepare = siu_dai_prepare, + .set_sysclk = siu_dai_set_sysclk, + .set_fmt = siu_dai_set_fmt, +}; + +static struct snd_soc_dai_driver siu_i2s_dai = { + .name = "siu-i2s-dai", + .playback = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_8000_48000, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_8000_48000, + }, + .ops = &siu_dai_ops, +}; + +static int siu_probe(struct platform_device *pdev) +{ + const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; + + info = devm_kmalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + siu_i2s_data = info; + info->dev = &pdev->dev; + + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + return ret; + + /* + * Loaded firmware is "const" - read only, but we have to modify it in + * snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect() + */ + memcpy(&info->fw, fw_entry->data, fw_entry->size); + + release_firmware(fw_entry); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + region = devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name); + if (!region) { + dev_err(&pdev->dev, "SIU region already claimed\n"); + return -EBUSY; + } + + info->pram = devm_ioremap(&pdev->dev, res->start, PRAM_SIZE); + if (!info->pram) + return -ENOMEM; + info->xram = devm_ioremap(&pdev->dev, res->start + XRAM_OFFSET, + XRAM_SIZE); + if (!info->xram) + return -ENOMEM; + info->yram = devm_ioremap(&pdev->dev, res->start + YRAM_OFFSET, + YRAM_SIZE); + if (!info->yram) + return -ENOMEM; + info->reg = devm_ioremap(&pdev->dev, res->start + REG_OFFSET, + resource_size(res) - REG_OFFSET); + if (!info->reg) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, info); + + /* register using ARRAY version so we can keep dai name */ + ret = devm_snd_soc_register_component(&pdev->dev, &siu_component, + &siu_i2s_dai, 1); + if (ret < 0) + return ret; + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int siu_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static struct platform_driver siu_driver = { + .driver = { + .name = "siu-pcm-audio", + }, + .probe = siu_probe, + .remove = siu_remove, +}; + +module_platform_driver(siu_driver); + +MODULE_AUTHOR("Carlos Munoz "); +MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sh/siu_pcm.c b/sound/soc/sh/siu_pcm.c new file mode 100644 index 000000000..e263757e4 --- /dev/null +++ b/sound/soc/sh/siu_pcm.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral. +// +// Copyright (C) 2009-2010 Guennadi Liakhovetski +// Copyright (C) 2006 Carlos Munoz + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "siu.h" + +#define DRV_NAME "siu-i2s" +#define GET_MAX_PERIODS(buf_bytes, period_bytes) \ + ((buf_bytes) / (period_bytes)) +#define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \ + ((buf_addr) + ((period_num) * (period_bytes))) + +#define RWF_STM_RD 0x01 /* Read in progress */ +#define RWF_STM_WT 0x02 /* Write in progress */ + +struct siu_port *siu_ports[SIU_PORT_NUM]; + +/* transfersize is number of u32 dma transfers per period */ +static int siu_pcm_stmwrite_stop(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->playback; + u32 stfifo; + + if (!siu_stream->rw_flg) + return -EPERM; + + /* output FIFO disable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, stfifo & ~0x0c180c18); + pr_debug("%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo & ~0x0c180c18); + + /* during stmwrite clear */ + siu_stream->rw_flg = 0; + + return 0; +} + +static int siu_pcm_stmwrite_start(struct siu_port *port_info) +{ + struct siu_stream *siu_stream = &port_info->playback; + + if (siu_stream->rw_flg) + return -EPERM; + + /* Current period in buffer */ + port_info->playback.cur_period = 0; + + /* during stmwrite flag set */ + siu_stream->rw_flg = RWF_STM_WT; + + /* DMA transfer start */ + tasklet_schedule(&siu_stream->tasklet); + + return 0; +} + +static void siu_dma_tx_complete(void *arg) +{ + struct siu_stream *siu_stream = arg; + + if (!siu_stream->rw_flg) + return; + + /* Update completed period count */ + if (++siu_stream->cur_period >= + GET_MAX_PERIODS(siu_stream->buf_bytes, + siu_stream->period_bytes)) + siu_stream->cur_period = 0; + + pr_debug("%s: done period #%d (%u/%u bytes), cookie %d\n", + __func__, siu_stream->cur_period, + siu_stream->cur_period * siu_stream->period_bytes, + siu_stream->buf_bytes, siu_stream->cookie); + + tasklet_schedule(&siu_stream->tasklet); + + /* Notify alsa: a period is done */ + snd_pcm_period_elapsed(siu_stream->substream); +} + +static int siu_pcm_wr_set(struct siu_port *port_info, + dma_addr_t buff, u32 size) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->playback; + struct snd_pcm_substream *substream = siu_stream->substream; + struct device *dev = substream->pcm->card->dev; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + struct scatterlist sg; + u32 stfifo; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)), + size, offset_in_page(buff)); + sg_dma_len(&sg) = size; + sg_dma_address(&sg) = buff; + + desc = dmaengine_prep_slave_sg(siu_stream->chan, + &sg, 1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(dev, "Failed to allocate a dma descriptor\n"); + return -ENOMEM; + } + + desc->callback = siu_dma_tx_complete; + desc->callback_param = siu_stream; + cookie = dmaengine_submit(desc); + if (cookie < 0) { + dev_err(dev, "Failed to submit a dma transfer\n"); + return cookie; + } + + siu_stream->tx_desc = desc; + siu_stream->cookie = cookie; + + dma_async_issue_pending(siu_stream->chan); + + /* only output FIFO enable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, stfifo | (port_info->stfifo & 0x0c180c18)); + dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo | (port_info->stfifo & 0x0c180c18)); + + return 0; +} + +static int siu_pcm_rd_set(struct siu_port *port_info, + dma_addr_t buff, size_t size) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->capture; + struct snd_pcm_substream *substream = siu_stream->substream; + struct device *dev = substream->pcm->card->dev; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + struct scatterlist sg; + u32 stfifo; + + dev_dbg(dev, "%s: %u@%llx\n", __func__, size, (unsigned long long)buff); + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)), + size, offset_in_page(buff)); + sg_dma_len(&sg) = size; + sg_dma_address(&sg) = buff; + + desc = dmaengine_prep_slave_sg(siu_stream->chan, + &sg, 1, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(dev, "Failed to allocate dma descriptor\n"); + return -ENOMEM; + } + + desc->callback = siu_dma_tx_complete; + desc->callback_param = siu_stream; + cookie = dmaengine_submit(desc); + if (cookie < 0) { + dev_err(dev, "Failed to submit dma descriptor\n"); + return cookie; + } + + siu_stream->tx_desc = desc; + siu_stream->cookie = cookie; + + dma_async_issue_pending(siu_stream->chan); + + /* only input FIFO enable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, siu_read32(base + SIU_STFIFO) | + (port_info->stfifo & 0x13071307)); + dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo | (port_info->stfifo & 0x13071307)); + + return 0; +} + +static void siu_io_tasklet(unsigned long data) +{ + struct siu_stream *siu_stream = (struct siu_stream *)data; + struct snd_pcm_substream *substream = siu_stream->substream; + struct device *dev = substream->pcm->card->dev; + struct snd_pcm_runtime *rt = substream->runtime; + struct siu_port *port_info = siu_port_info(substream); + + dev_dbg(dev, "%s: flags %x\n", __func__, siu_stream->rw_flg); + + if (!siu_stream->rw_flg) { + dev_dbg(dev, "%s: stream inactive\n", __func__); + return; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + dma_addr_t buff; + size_t count; + u8 *virt; + + buff = (dma_addr_t)PERIOD_OFFSET(rt->dma_addr, + siu_stream->cur_period, + siu_stream->period_bytes); + virt = PERIOD_OFFSET(rt->dma_area, + siu_stream->cur_period, + siu_stream->period_bytes); + count = siu_stream->period_bytes; + + /* DMA transfer start */ + siu_pcm_rd_set(port_info, buff, count); + } else { + siu_pcm_wr_set(port_info, + (dma_addr_t)PERIOD_OFFSET(rt->dma_addr, + siu_stream->cur_period, + siu_stream->period_bytes), + siu_stream->period_bytes); + } +} + +/* Capture */ +static int siu_pcm_stmread_start(struct siu_port *port_info) +{ + struct siu_stream *siu_stream = &port_info->capture; + + if (siu_stream->xfer_cnt > 0x1000000) + return -EINVAL; + if (siu_stream->rw_flg) + return -EPERM; + + /* Current period in buffer */ + siu_stream->cur_period = 0; + + /* during stmread flag set */ + siu_stream->rw_flg = RWF_STM_RD; + + tasklet_schedule(&siu_stream->tasklet); + + return 0; +} + +static int siu_pcm_stmread_stop(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->capture; + struct device *dev = siu_stream->substream->pcm->card->dev; + u32 stfifo; + + if (!siu_stream->rw_flg) + return -EPERM; + + /* input FIFO disable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, stfifo & ~0x13071307); + dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo & ~0x13071307); + + /* during stmread flag clear */ + siu_stream->rw_flg = 0; + + return 0; +} + +static int siu_pcm_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *hw_params) +{ + struct siu_info *info = siu_i2s_data; + struct device *dev = ss->pcm->card->dev; + int ret; + + dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); + + ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); + if (ret < 0) + dev_err(dev, "snd_pcm_lib_malloc_pages() failed\n"); + + return ret; +} + +static int siu_pcm_hw_free(struct snd_pcm_substream *ss) +{ + struct siu_info *info = siu_i2s_data; + struct siu_port *port_info = siu_port_info(ss); + struct device *dev = ss->pcm->card->dev; + struct siu_stream *siu_stream; + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); + + return snd_pcm_lib_free_pages(ss); +} + +static bool filter(struct dma_chan *chan, void *slave) +{ + struct sh_dmae_slave *param = slave; + + pr_debug("%s: slave ID %d\n", __func__, param->shdma_slave.slave_id); + + chan->private = ¶m->shdma_slave; + return true; +} + +static int siu_pcm_open(struct snd_pcm_substream *ss) +{ + /* Playback / Capture */ + struct snd_soc_pcm_runtime *rtd = ss->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct siu_platform *pdata = component->dev->platform_data; + struct siu_info *info = siu_i2s_data; + struct siu_port *port_info = siu_port_info(ss); + struct siu_stream *siu_stream; + u32 port = info->port_id; + struct device *dev = ss->pcm->card->dev; + dma_cap_mask_t mask; + struct sh_dmae_slave *param; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + dev_dbg(dev, "%s, port=%d@%p\n", __func__, port, port_info); + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) { + siu_stream = &port_info->playback; + param = &siu_stream->param; + param->shdma_slave.slave_id = port ? pdata->dma_slave_tx_b : + pdata->dma_slave_tx_a; + } else { + siu_stream = &port_info->capture; + param = &siu_stream->param; + param->shdma_slave.slave_id = port ? pdata->dma_slave_rx_b : + pdata->dma_slave_rx_a; + } + + /* Get DMA channel */ + siu_stream->chan = dma_request_channel(mask, filter, param); + if (!siu_stream->chan) { + dev_err(dev, "DMA channel allocation failed!\n"); + return -EBUSY; + } + + siu_stream->substream = ss; + + return 0; +} + +static int siu_pcm_close(struct snd_pcm_substream *ss) +{ + struct siu_info *info = siu_i2s_data; + struct device *dev = ss->pcm->card->dev; + struct siu_port *port_info = siu_port_info(ss); + struct siu_stream *siu_stream; + + dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + dma_release_channel(siu_stream->chan); + siu_stream->chan = NULL; + + siu_stream->substream = NULL; + + return 0; +} + +static int siu_pcm_prepare(struct snd_pcm_substream *ss) +{ + struct siu_info *info = siu_i2s_data; + struct siu_port *port_info = siu_port_info(ss); + struct device *dev = ss->pcm->card->dev; + struct snd_pcm_runtime *rt = ss->runtime; + struct siu_stream *siu_stream; + snd_pcm_sframes_t xfer_cnt; + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + rt = siu_stream->substream->runtime; + + siu_stream->buf_bytes = snd_pcm_lib_buffer_bytes(ss); + siu_stream->period_bytes = snd_pcm_lib_period_bytes(ss); + + dev_dbg(dev, "%s: port=%d, %d channels, period=%u bytes\n", __func__, + info->port_id, rt->channels, siu_stream->period_bytes); + + /* We only support buffers that are multiples of the period */ + if (siu_stream->buf_bytes % siu_stream->period_bytes) { + dev_err(dev, "%s() - buffer=%d not multiple of period=%d\n", + __func__, siu_stream->buf_bytes, + siu_stream->period_bytes); + return -EINVAL; + } + + xfer_cnt = bytes_to_frames(rt, siu_stream->period_bytes); + if (!xfer_cnt || xfer_cnt > 0x1000000) + return -EINVAL; + + siu_stream->format = rt->format; + siu_stream->xfer_cnt = xfer_cnt; + + dev_dbg(dev, "port=%d buf=%lx buf_bytes=%d period_bytes=%d " + "format=%d channels=%d xfer_cnt=%d\n", info->port_id, + (unsigned long)rt->dma_addr, siu_stream->buf_bytes, + siu_stream->period_bytes, + siu_stream->format, rt->channels, (int)xfer_cnt); + + return 0; +} + +static int siu_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct siu_info *info = siu_i2s_data; + struct device *dev = ss->pcm->card->dev; + struct siu_port *port_info = siu_port_info(ss); + int ret; + + dev_dbg(dev, "%s: port=%d@%p, cmd=%d\n", __func__, + info->port_id, port_info, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = siu_pcm_stmwrite_start(port_info); + else + ret = siu_pcm_stmread_start(port_info); + + if (ret < 0) + dev_warn(dev, "%s: start failed on port=%d\n", + __func__, info->port_id); + + break; + case SNDRV_PCM_TRIGGER_STOP: + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_pcm_stmwrite_stop(port_info); + else + siu_pcm_stmread_stop(port_info); + ret = 0; + + break; + default: + dev_err(dev, "%s() unsupported cmd=%d\n", __func__, cmd); + ret = -EINVAL; + } + + return ret; +} + +/* + * So far only resolution of one period is supported, subject to extending the + * dmangine API + */ +static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss) +{ + struct device *dev = ss->pcm->card->dev; + struct siu_info *info = siu_i2s_data; + u32 __iomem *base = info->reg; + struct siu_port *port_info = siu_port_info(ss); + struct snd_pcm_runtime *rt = ss->runtime; + size_t ptr; + struct siu_stream *siu_stream; + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + /* + * ptr is the offset into the buffer where the dma is currently at. We + * check if the dma buffer has just wrapped. + */ + ptr = PERIOD_OFFSET(rt->dma_addr, + siu_stream->cur_period, + siu_stream->period_bytes) - rt->dma_addr; + + dev_dbg(dev, + "%s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d\n", + __func__, info->port_id, siu_read32(base + SIU_EVNTC), + siu_read32(base + SIU_SBFSTS), ptr, siu_stream->buf_bytes, + siu_stream->cookie); + + if (ptr >= siu_stream->buf_bytes) + ptr = 0; + + return bytes_to_frames(ss->runtime, ptr); +} + +static int siu_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + /* card->dev == socdev->dev, see snd_soc_new_pcms() */ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct siu_info *info = siu_i2s_data; + struct platform_device *pdev = to_platform_device(card->dev); + int ret; + int i; + + /* pdev->id selects between SIUA and SIUB */ + if (pdev->id < 0 || pdev->id >= SIU_PORT_NUM) + return -EINVAL; + + info->port_id = pdev->id; + + /* + * While the siu has 2 ports, only one port can be on at a time (only 1 + * SPB). So far all the boards using the siu had only one of the ports + * wired to a codec. To simplify things, we only register one port with + * alsa. In case both ports are needed, it should be changed here + */ + for (i = pdev->id; i < pdev->id + 1; i++) { + struct siu_port **port_info = &siu_ports[i]; + + ret = siu_init_port(i, port_info, card); + if (ret < 0) + return ret; + + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, NULL, + SIU_BUFFER_BYTES_MAX, SIU_BUFFER_BYTES_MAX); + if (ret < 0) { + dev_err(card->dev, + "snd_pcm_lib_preallocate_pages_for_all() err=%d", + ret); + goto fail; + } + + (*port_info)->pcm = pcm; + + /* IO tasklets */ + tasklet_init(&(*port_info)->playback.tasklet, siu_io_tasklet, + (unsigned long)&(*port_info)->playback); + tasklet_init(&(*port_info)->capture.tasklet, siu_io_tasklet, + (unsigned long)&(*port_info)->capture); + } + + dev_info(card->dev, "SuperH SIU driver initialized.\n"); + return 0; + +fail: + siu_free_port(siu_ports[pdev->id]); + dev_err(card->dev, "SIU: failed to initialize.\n"); + return ret; +} + +static void siu_pcm_free(struct snd_pcm *pcm) +{ + struct platform_device *pdev = to_platform_device(pcm->card->dev); + struct siu_port *port_info = siu_ports[pdev->id]; + + tasklet_kill(&port_info->capture.tasklet); + tasklet_kill(&port_info->playback.tasklet); + + siu_free_port(port_info); + + dev_dbg(pcm->card->dev, "%s\n", __func__); +} + +static const struct snd_pcm_ops siu_pcm_ops = { + .open = siu_pcm_open, + .close = siu_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = siu_pcm_hw_params, + .hw_free = siu_pcm_hw_free, + .prepare = siu_pcm_prepare, + .trigger = siu_pcm_trigger, + .pointer = siu_pcm_pointer_dma, +}; + +struct snd_soc_component_driver siu_component = { + .name = DRV_NAME, + .ops = &siu_pcm_ops, + .pcm_new = siu_pcm_new, + .pcm_free = siu_pcm_free, +}; +EXPORT_SYMBOL_GPL(siu_component); diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c new file mode 100644 index 000000000..8125fa384 --- /dev/null +++ b/sound/soc/sh/ssi.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Serial Sound Interface (I2S) support for SH7760/SH7780 +// +// Copyright (c) 2007 Manuel Lauss +// +// dont forget to set IPSEL/OMSEL register bits (in your board code) to +// enable SSI output pins! + +/* + * LIMITATIONS: + * The SSI unit has only one physical data line, so full duplex is + * impossible. This can be remedied on the SH7760 by using the + * other SSI unit for recording; however the SH7780 has only 1 SSI + * unit, and its pins are shared with the AC97 unit, among others. + * + * FEATURES: + * The SSI features "compressed mode": in this mode it continuously + * streams PCM data over the I2S lines and uses LRCK as a handshake + * signal. Can be used to send compressed data (AC3/DTS) to a DSP. + * The number of bits sent over the wire in a frame can be adjusted + * and can be independent from the actual sample bit depth. This is + * useful to support TDM mode codecs like the AD1939 which have a + * fixed TDM slot size, regardless of sample resolution. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSICR 0x00 +#define SSISR 0x04 + +#define CR_DMAEN (1 << 28) +#define CR_CHNL_SHIFT 22 +#define CR_CHNL_MASK (3 << CR_CHNL_SHIFT) +#define CR_DWL_SHIFT 19 +#define CR_DWL_MASK (7 << CR_DWL_SHIFT) +#define CR_SWL_SHIFT 16 +#define CR_SWL_MASK (7 << CR_SWL_SHIFT) +#define CR_SCK_MASTER (1 << 15) /* bitclock master bit */ +#define CR_SWS_MASTER (1 << 14) /* wordselect master bit */ +#define CR_SCKP (1 << 13) /* I2Sclock polarity */ +#define CR_SWSP (1 << 12) /* LRCK polarity */ +#define CR_SPDP (1 << 11) +#define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */ +#define CR_PDTA (1 << 9) /* fifo data alignment */ +#define CR_DEL (1 << 8) /* delay data by 1 i2sclk */ +#define CR_BREN (1 << 7) /* clock gating in burst mode */ +#define CR_CKDIV_SHIFT 4 +#define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */ +#define CR_MUTE (1 << 3) /* SSI mute */ +#define CR_CPEN (1 << 2) /* compressed mode */ +#define CR_TRMD (1 << 1) /* transmit/receive select */ +#define CR_EN (1 << 0) /* enable SSI */ + +#define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg))) + +struct ssi_priv { + unsigned long mmio; + unsigned long sysclk; + int inuse; +} ssi_cpu_data[] = { +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + { + .mmio = 0xFE680000, + }, + { + .mmio = 0xFE690000, + }, +#elif defined(CONFIG_CPU_SUBTYPE_SH7780) + { + .mmio = 0xFFE70000, + }, +#else +#error "Unsupported SuperH SoC" +#endif +}; + +/* + * track usage of the SSI; it is simplex-only so prevent attempts of + * concurrent playback + capture. FIXME: any locking required? + */ +static int ssi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + if (ssi->inuse) { + pr_debug("ssi: already in use!\n"); + return -EBUSY; + } else + ssi->inuse = 1; + return 0; +} + +static void ssi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + + ssi->inuse = 0; +} + +static int ssi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + SSIREG(SSICR) |= CR_DMAEN | CR_EN; + break; + case SNDRV_PCM_TRIGGER_STOP: + SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr = SSIREG(SSICR); + unsigned int bits, channels, swl, recv, i; + + channels = params_channels(params); + bits = params->msbits; + recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; + + pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); + pr_debug("bits: %u channels: %u\n", bits, channels); + + ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | + CR_SWL_MASK); + + /* direction (send/receive) */ + if (!recv) + ssicr |= CR_TRMD; /* transmit */ + + /* channels */ + if ((channels < 2) || (channels > 8) || (channels & 1)) { + pr_debug("ssi: invalid number of channels\n"); + return -EINVAL; + } + ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT; + + /* DATA WORD LENGTH (DWL): databits in audio sample */ + i = 0; + switch (bits) { + case 32: ++i; + case 24: ++i; + case 22: ++i; + case 20: ++i; + case 18: ++i; + case 16: ++i; + ssicr |= i << CR_DWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid sample width\n"); + return -EINVAL; + } + + /* + * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S + * wires. This is usually bits_per_sample x channels/2; i.e. in + * Stereo mode the SWL equals DWL. SWL can be bigger than the + * product of (channels_per_slot x samplebits), e.g. for codecs + * like the AD1939 which only accept 32bit wide TDM slots. For + * "standard" I2S operation we set SWL = chans / 2 * DWL here. + * Waiting for ASoC to get TDM support ;-) + */ + if ((bits > 16) && (bits <= 24)) { + bits = 24; /* these are padded by the SSI */ + /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */ + } + i = 0; + swl = (bits * channels) / 2; + switch (swl) { + case 256: ++i; + case 128: ++i; + case 64: ++i; + case 48: ++i; + case 32: ++i; + case 16: ++i; + ssicr |= i << CR_SWL_SHIFT; + case 8: break; + default: + pr_debug("ssi: invalid system word length computed\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + + pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr); + return 0; +} + +static int ssi_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id]; + + ssi->sysclk = freq; + + return 0; +} + +/* + * This divider is used to generate the SSI_SCK (I2S bitclock) from the + * clock at the HAC_BIT_CLK ("oversampling clock") pin. + */ +static int ssi_set_clkdiv(struct snd_soc_dai *dai, int did, int div) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr; + int i; + + i = 0; + ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK; + switch (div) { + case 16: ++i; + case 8: ++i; + case 4: ++i; + case 2: ++i; + SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT); + case 1: break; + default: + pr_debug("ssi: invalid sck divider %d\n", div); + return -EINVAL; + } + + return 0; +} + +static int ssi_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; + unsigned long ssicr = SSIREG(SSICR); + + pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr); + + ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP | + CR_SWS_MASTER | CR_SCK_MASTER); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + ssicr |= CR_DEL | CR_PDTA; + break; + case SND_SOC_DAIFMT_LEFT_J: + ssicr |= CR_DEL; + break; + default: + pr_debug("ssi: unsupported format\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + break; + case SND_SOC_DAIFMT_GATED: + ssicr |= CR_BREN; + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + ssicr |= CR_SCKP; /* sample data at low clkedge */ + break; + case SND_SOC_DAIFMT_NB_IF: + ssicr |= CR_SCKP | CR_SWSP; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + ssicr |= CR_SWSP; /* word select starts low */ + break; + default: + pr_debug("ssi: invalid inversion\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + ssicr |= CR_SCK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: + ssicr |= CR_SWS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssicr |= CR_SWS_MASTER | CR_SCK_MASTER; + break; + default: + pr_debug("ssi: invalid master/slave configuration\n"); + return -EINVAL; + } + + SSIREG(SSICR) = ssicr; + pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr); + + return 0; +} + +/* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in + * Master mode, so really this is board specific; the SSI can do any + * rate with the right bitclk and divider settings. + */ +#define SSI_RATES \ + SNDRV_PCM_RATE_8000_192000 + +/* the SSI can do 8-32 bit samples, with 8 possible channels */ +#define SSI_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) + +static const struct snd_soc_dai_ops ssi_dai_ops = { + .startup = ssi_startup, + .shutdown = ssi_shutdown, + .trigger = ssi_trigger, + .hw_params = ssi_hw_params, + .set_sysclk = ssi_set_sysclk, + .set_clkdiv = ssi_set_clkdiv, + .set_fmt = ssi_set_fmt, +}; + +static struct snd_soc_dai_driver sh4_ssi_dai[] = { +{ + .name = "ssi-dai.0", + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = &ssi_dai_ops, +}, +#ifdef CONFIG_CPU_SUBTYPE_SH7760 +{ + .name = "ssi-dai.1", + .playback = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .capture = { + .rates = SSI_RATES, + .formats = SSI_FMTS, + .channels_min = 2, + .channels_max = 8, + }, + .ops = &ssi_dai_ops, +}, +#endif +}; + +static const struct snd_soc_component_driver sh4_ssi_component = { + .name = "sh4-ssi", +}; + +static int sh4_soc_dai_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &sh4_ssi_component, + sh4_ssi_dai, + ARRAY_SIZE(sh4_ssi_dai)); +} + +static struct platform_driver sh4_ssi_driver = { + .driver = { + .name = "sh4-ssi-dai", + }, + + .probe = sh4_soc_dai_probe, +}; + +module_platform_driver(sh4_ssi_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver"); +MODULE_AUTHOR("Manuel Lauss "); diff --git a/sound/soc/sirf/Kconfig b/sound/soc/sirf/Kconfig new file mode 100644 index 000000000..840058dca --- /dev/null +++ b/sound/soc/sirf/Kconfig @@ -0,0 +1,20 @@ +config SND_SOC_SIRF + tristate "SoC Audio for the SiRF SoC chips" + depends on ARCH_SIRF || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_SOC_SIRF_AUDIO + tristate "SoC Audio support for SiRF internal audio codec" + depends on SND_SOC_SIRF + select SND_SOC_SIRF_AUDIO_CODEC + select SND_SOC_SIRF_AUDIO_PORT + +config SND_SOC_SIRF_AUDIO_PORT + select REGMAP_MMIO + tristate + +config SND_SOC_SIRF_USP + tristate "SoC Audio (I2S protocol) for SiRF SoC USP interface" + depends on SND_SOC_SIRF + select REGMAP_MMIO + tristate diff --git a/sound/soc/sirf/Makefile b/sound/soc/sirf/Makefile new file mode 100644 index 000000000..16ed11965 --- /dev/null +++ b/sound/soc/sirf/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-sirf-audio-objs := sirf-audio.o +snd-soc-sirf-audio-port-objs := sirf-audio-port.o +snd-soc-sirf-usp-objs := sirf-usp.o + +obj-$(CONFIG_SND_SOC_SIRF_AUDIO) += snd-soc-sirf-audio.o +obj-$(CONFIG_SND_SOC_SIRF_AUDIO_PORT) += snd-soc-sirf-audio-port.o +obj-$(CONFIG_SND_SOC_SIRF_USP) += snd-soc-sirf-usp.o diff --git a/sound/soc/sirf/sirf-audio-port.c b/sound/soc/sirf/sirf-audio-port.c new file mode 100644 index 000000000..be066de74 --- /dev/null +++ b/sound/soc/sirf/sirf-audio-port.c @@ -0,0 +1,87 @@ +/* + * SiRF Audio port driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ +#include +#include +#include + +struct sirf_audio_port { + struct regmap *regmap; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; +}; + + +static int sirf_audio_port_dai_probe(struct snd_soc_dai *dai) +{ + struct sirf_audio_port *port = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &port->playback_dma_data, + &port->capture_dma_data); + return 0; +} + +static struct snd_soc_dai_driver sirf_audio_port_dai = { + .probe = sirf_audio_port_dai_probe, + .name = "sirf-audio-port", + .id = 0, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static const struct snd_soc_component_driver sirf_audio_port_component = { + .name = "sirf-audio-port", +}; + +static int sirf_audio_port_probe(struct platform_device *pdev) +{ + int ret; + struct sirf_audio_port *port; + + port = devm_kzalloc(&pdev->dev, + sizeof(struct sirf_audio_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + ret = devm_snd_soc_register_component(&pdev->dev, + &sirf_audio_port_component, &sirf_audio_port_dai, 1); + if (ret) + return ret; + + platform_set_drvdata(pdev, port); + return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +} + +static const struct of_device_id sirf_audio_port_of_match[] = { + { .compatible = "sirf,audio-port", }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_audio_port_of_match); + +static struct platform_driver sirf_audio_port_driver = { + .driver = { + .name = "sirf-audio-port", + .of_match_table = sirf_audio_port_of_match, + }, + .probe = sirf_audio_port_probe, +}; + +module_platform_driver(sirf_audio_port_driver); + +MODULE_DESCRIPTION("SiRF Audio Port driver"); +MODULE_AUTHOR("RongJun Ying "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sirf/sirf-audio.c b/sound/soc/sirf/sirf-audio.c new file mode 100644 index 000000000..f2bc50790 --- /dev/null +++ b/sound/soc/sirf/sirf-audio.c @@ -0,0 +1,156 @@ +/* + * SiRF audio card driver + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct sirf_audio_card { + unsigned int gpio_hp_pa; + unsigned int gpio_spk_pa; +}; + +static int sirf_audio_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *ctrl, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct sirf_audio_card *sirf_audio_card = snd_soc_card_get_drvdata(card); + int on = !SND_SOC_DAPM_EVENT_OFF(event); + + if (gpio_is_valid(sirf_audio_card->gpio_hp_pa)) + gpio_set_value(sirf_audio_card->gpio_hp_pa, on); + return 0; +} + +static int sirf_audio_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *ctrl, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct sirf_audio_card *sirf_audio_card = snd_soc_card_get_drvdata(card); + int on = !SND_SOC_DAPM_EVENT_OFF(event); + + if (gpio_is_valid(sirf_audio_card->gpio_spk_pa)) + gpio_set_value(sirf_audio_card->gpio_spk_pa, on); + + return 0; +} +static const struct snd_soc_dapm_widget sirf_audio_dapm_widgets[] = { + SND_SOC_DAPM_HP("Hp", sirf_audio_hp_event), + SND_SOC_DAPM_SPK("Ext Spk", sirf_audio_spk_event), + SND_SOC_DAPM_MIC("Ext Mic", NULL), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"Hp", NULL, "HPOUTL"}, + {"Hp", NULL, "HPOUTR"}, + {"Ext Spk", NULL, "SPKOUT"}, + {"MICIN1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Ext Mic"}, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link sirf_audio_dai_link[] = { + { + .name = "SiRF audio card", + .stream_name = "SiRF audio HiFi", + .codec_dai_name = "sirf-audio-codec", + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_sirf_audio_card = { + .name = "SiRF audio card", + .owner = THIS_MODULE, + .dai_link = sirf_audio_dai_link, + .num_links = ARRAY_SIZE(sirf_audio_dai_link), + .dapm_widgets = sirf_audio_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sirf_audio_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), +}; + +static int sirf_audio_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_sirf_audio_card; + struct sirf_audio_card *sirf_audio_card; + int ret; + + sirf_audio_card = devm_kzalloc(&pdev->dev, sizeof(struct sirf_audio_card), + GFP_KERNEL); + if (sirf_audio_card == NULL) + return -ENOMEM; + + sirf_audio_dai_link[0].cpu_of_node = + of_parse_phandle(pdev->dev.of_node, "sirf,audio-platform", 0); + sirf_audio_dai_link[0].platform_of_node = + of_parse_phandle(pdev->dev.of_node, "sirf,audio-platform", 0); + sirf_audio_dai_link[0].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "sirf,audio-codec", 0); + sirf_audio_card->gpio_spk_pa = of_get_named_gpio(pdev->dev.of_node, + "spk-pa-gpios", 0); + sirf_audio_card->gpio_hp_pa = of_get_named_gpio(pdev->dev.of_node, + "hp-pa-gpios", 0); + if (gpio_is_valid(sirf_audio_card->gpio_spk_pa)) { + ret = devm_gpio_request_one(&pdev->dev, + sirf_audio_card->gpio_spk_pa, + GPIOF_OUT_INIT_LOW, "SPA_PA_SD"); + if (ret) { + dev_err(&pdev->dev, + "Failed to request GPIO_%d for reset: %d\n", + sirf_audio_card->gpio_spk_pa, ret); + return ret; + } + } + if (gpio_is_valid(sirf_audio_card->gpio_hp_pa)) { + ret = devm_gpio_request_one(&pdev->dev, + sirf_audio_card->gpio_hp_pa, + GPIOF_OUT_INIT_LOW, "HP_PA_SD"); + if (ret) { + dev_err(&pdev->dev, + "Failed to request GPIO_%d for reset: %d\n", + sirf_audio_card->gpio_hp_pa, ret); + return ret; + } + } + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, sirf_audio_card); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret); + + return ret; +} + +static const struct of_device_id sirf_audio_of_match[] = { + {.compatible = "sirf,sirf-audio-card", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sirf_audio_of_match); + +static struct platform_driver sirf_audio_driver = { + .driver = { + .name = "sirf-audio-card", + .pm = &snd_soc_pm_ops, + .of_match_table = sirf_audio_of_match, + }, + .probe = sirf_audio_probe, +}; +module_platform_driver(sirf_audio_driver); + +MODULE_AUTHOR("RongJun Ying "); +MODULE_DESCRIPTION("ALSA SoC SIRF audio card driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sirf/sirf-usp.c b/sound/soc/sirf/sirf-usp.c new file mode 100644 index 000000000..d70fcd4a1 --- /dev/null +++ b/sound/soc/sirf/sirf-usp.c @@ -0,0 +1,438 @@ +/* + * SiRF USP in I2S/DSP mode + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sirf-usp.h" + +struct sirf_usp { + struct regmap *regmap; + struct clk *clk; + u32 mode1_reg; + u32 mode2_reg; + int daifmt_format; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; +}; + +static void sirf_usp_tx_enable(struct sirf_usp *usp) +{ + regmap_update_bits(usp->regmap, USP_TX_FIFO_OP, + USP_TX_FIFO_RESET, USP_TX_FIFO_RESET); + regmap_write(usp->regmap, USP_TX_FIFO_OP, 0); + + regmap_update_bits(usp->regmap, USP_TX_FIFO_OP, + USP_TX_FIFO_START, USP_TX_FIFO_START); + + regmap_update_bits(usp->regmap, USP_TX_RX_ENABLE, + USP_TX_ENA, USP_TX_ENA); +} + +static void sirf_usp_tx_disable(struct sirf_usp *usp) +{ + regmap_update_bits(usp->regmap, USP_TX_RX_ENABLE, + USP_TX_ENA, ~USP_TX_ENA); + /* FIFO stop */ + regmap_write(usp->regmap, USP_TX_FIFO_OP, 0); +} + +static void sirf_usp_rx_enable(struct sirf_usp *usp) +{ + regmap_update_bits(usp->regmap, USP_RX_FIFO_OP, + USP_RX_FIFO_RESET, USP_RX_FIFO_RESET); + regmap_write(usp->regmap, USP_RX_FIFO_OP, 0); + + regmap_update_bits(usp->regmap, USP_RX_FIFO_OP, + USP_RX_FIFO_START, USP_RX_FIFO_START); + + regmap_update_bits(usp->regmap, USP_TX_RX_ENABLE, + USP_RX_ENA, USP_RX_ENA); +} + +static void sirf_usp_rx_disable(struct sirf_usp *usp) +{ + regmap_update_bits(usp->regmap, USP_TX_RX_ENABLE, + USP_RX_ENA, ~USP_RX_ENA); + /* FIFO stop */ + regmap_write(usp->regmap, USP_RX_FIFO_OP, 0); +} + +static int sirf_usp_pcm_dai_probe(struct snd_soc_dai *dai) +{ + struct sirf_usp *usp = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &usp->playback_dma_data, + &usp->capture_dma_data); + return 0; +} + +static int sirf_usp_pcm_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct sirf_usp *usp = snd_soc_dai_get_drvdata(dai); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + dev_err(dai->dev, "Only CBM and CFM supported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_DSP_A: + usp->daifmt_format = (fmt & SND_SOC_DAIFMT_FORMAT_MASK); + break; + default: + dev_err(dai->dev, "Only I2S and DSP_A format supported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + usp->daifmt_format |= (fmt & SND_SOC_DAIFMT_INV_MASK); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void sirf_usp_i2s_init(struct sirf_usp *usp) +{ + /* Configure RISC mode */ + regmap_update_bits(usp->regmap, USP_RISC_DSP_MODE, + USP_RISC_DSP_SEL, ~USP_RISC_DSP_SEL); + + /* + * Configure DMA IO Length register + * Set no limit, USP can receive data continuously until it is diabled + */ + regmap_write(usp->regmap, USP_TX_DMA_IO_LEN, 0); + regmap_write(usp->regmap, USP_RX_DMA_IO_LEN, 0); + + /* Configure Mode2 register */ + regmap_write(usp->regmap, USP_MODE2, (1 << USP_RXD_DELAY_LEN_OFFSET) | + (0 << USP_TXD_DELAY_LEN_OFFSET) | + USP_TFS_CLK_SLAVE_MODE | USP_RFS_CLK_SLAVE_MODE); + + /* Configure Mode1 register */ + regmap_write(usp->regmap, USP_MODE1, + USP_SYNC_MODE | USP_EN | USP_TXD_ACT_EDGE_FALLING | + USP_RFS_ACT_LEVEL_LOGIC1 | USP_TFS_ACT_LEVEL_LOGIC1 | + USP_TX_UFLOW_REPEAT_ZERO | USP_CLOCK_MODE_SLAVE); + + /* Configure RX DMA IO Control register */ + regmap_write(usp->regmap, USP_RX_DMA_IO_CTRL, 0); + + /* Congiure RX FIFO Control register */ + regmap_write(usp->regmap, USP_RX_FIFO_CTRL, + (USP_RX_FIFO_THRESHOLD << USP_RX_FIFO_THD_OFFSET) | + (USP_TX_RX_FIFO_WIDTH_DWORD << USP_RX_FIFO_WIDTH_OFFSET)); + + /* Congiure RX FIFO Level Check register */ + regmap_write(usp->regmap, USP_RX_FIFO_LEVEL_CHK, + RX_FIFO_SC(0x04) | RX_FIFO_LC(0x0E) | RX_FIFO_HC(0x1B)); + + /* Configure TX DMA IO Control register*/ + regmap_write(usp->regmap, USP_TX_DMA_IO_CTRL, 0); + + /* Configure TX FIFO Control register */ + regmap_write(usp->regmap, USP_TX_FIFO_CTRL, + (USP_TX_FIFO_THRESHOLD << USP_TX_FIFO_THD_OFFSET) | + (USP_TX_RX_FIFO_WIDTH_DWORD << USP_TX_FIFO_WIDTH_OFFSET)); + /* Congiure TX FIFO Level Check register */ + regmap_write(usp->regmap, USP_TX_FIFO_LEVEL_CHK, + TX_FIFO_SC(0x1B) | TX_FIFO_LC(0x0E) | TX_FIFO_HC(0x04)); +} + +static int sirf_usp_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sirf_usp *usp = snd_soc_dai_get_drvdata(dai); + u32 data_len, frame_len, shifter_len; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + data_len = 16; + frame_len = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + data_len = 24; + frame_len = 32; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + data_len = 24; + frame_len = 24; + break; + default: + dev_err(dai->dev, "Format unsupported\n"); + return -EINVAL; + } + + shifter_len = data_len; + + switch (usp->daifmt_format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + regmap_update_bits(usp->regmap, USP_RX_FRAME_CTRL, + USP_I2S_SYNC_CHG, USP_I2S_SYNC_CHG); + break; + case SND_SOC_DAIFMT_DSP_A: + regmap_update_bits(usp->regmap, USP_RX_FRAME_CTRL, + USP_I2S_SYNC_CHG, 0); + frame_len = data_len * params_channels(params); + data_len = frame_len; + break; + default: + dev_err(dai->dev, "Only support I2S and DSP_A mode\n"); + return -EINVAL; + } + + switch (usp->daifmt_format & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + regmap_update_bits(usp->regmap, USP_MODE1, + USP_RXD_ACT_EDGE_FALLING | USP_TXD_ACT_EDGE_FALLING, + USP_RXD_ACT_EDGE_FALLING); + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_update_bits(usp->regmap, USP_TX_FRAME_CTRL, + USP_TXC_DATA_LEN_MASK | USP_TXC_FRAME_LEN_MASK + | USP_TXC_SHIFTER_LEN_MASK | USP_TXC_SLAVE_CLK_SAMPLE, + ((data_len - 1) << USP_TXC_DATA_LEN_OFFSET) + | ((frame_len - 1) << USP_TXC_FRAME_LEN_OFFSET) + | ((shifter_len - 1) << USP_TXC_SHIFTER_LEN_OFFSET) + | USP_TXC_SLAVE_CLK_SAMPLE); + else + regmap_update_bits(usp->regmap, USP_RX_FRAME_CTRL, + USP_RXC_DATA_LEN_MASK | USP_RXC_FRAME_LEN_MASK + | USP_RXC_SHIFTER_LEN_MASK | USP_SINGLE_SYNC_MODE, + ((data_len - 1) << USP_RXC_DATA_LEN_OFFSET) + | ((frame_len - 1) << USP_RXC_FRAME_LEN_OFFSET) + | ((shifter_len - 1) << USP_RXC_SHIFTER_LEN_OFFSET) + | USP_SINGLE_SYNC_MODE); + + return 0; +} + +static int sirf_usp_pcm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sirf_usp *usp = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sirf_usp_tx_enable(usp); + else + sirf_usp_rx_enable(usp); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sirf_usp_tx_disable(usp); + else + sirf_usp_rx_disable(usp); + break; + } + + return 0; +} + +static const struct snd_soc_dai_ops sirf_usp_pcm_dai_ops = { + .trigger = sirf_usp_pcm_trigger, + .set_fmt = sirf_usp_pcm_set_dai_fmt, + .hw_params = sirf_usp_pcm_hw_params, +}; + +static struct snd_soc_dai_driver sirf_usp_pcm_dai = { + .probe = sirf_usp_pcm_dai_probe, + .name = "sirf-usp-pcm", + .id = 0, + .playback = { + .stream_name = "SiRF USP PCM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE, + }, + .capture = { + .stream_name = "SiRF USP PCM Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE, + }, + .ops = &sirf_usp_pcm_dai_ops, +}; + +static int sirf_usp_pcm_runtime_suspend(struct device *dev) +{ + struct sirf_usp *usp = dev_get_drvdata(dev); + + clk_disable_unprepare(usp->clk); + return 0; +} + +static int sirf_usp_pcm_runtime_resume(struct device *dev) +{ + struct sirf_usp *usp = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(usp->clk); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + sirf_usp_i2s_init(usp); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int sirf_usp_pcm_suspend(struct device *dev) +{ + struct sirf_usp *usp = dev_get_drvdata(dev); + + if (!pm_runtime_status_suspended(dev)) { + regmap_read(usp->regmap, USP_MODE1, &usp->mode1_reg); + regmap_read(usp->regmap, USP_MODE2, &usp->mode2_reg); + sirf_usp_pcm_runtime_suspend(dev); + } + return 0; +} + +static int sirf_usp_pcm_resume(struct device *dev) +{ + struct sirf_usp *usp = dev_get_drvdata(dev); + int ret; + + if (!pm_runtime_status_suspended(dev)) { + ret = sirf_usp_pcm_runtime_resume(dev); + if (ret) + return ret; + regmap_write(usp->regmap, USP_MODE1, usp->mode1_reg); + regmap_write(usp->regmap, USP_MODE2, usp->mode2_reg); + } + return 0; +} +#endif + +static const struct snd_soc_component_driver sirf_usp_component = { + .name = "sirf-usp", +}; + +static const struct regmap_config sirf_usp_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = USP_RX_FIFO_DATA, + .cache_type = REGCACHE_NONE, +}; + +static int sirf_usp_pcm_probe(struct platform_device *pdev) +{ + int ret; + struct sirf_usp *usp; + void __iomem *base; + struct resource *mem_res; + + usp = devm_kzalloc(&pdev->dev, sizeof(struct sirf_usp), + GFP_KERNEL); + if (!usp) + return -ENOMEM; + + platform_set_drvdata(pdev, usp); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem_res); + if (IS_ERR(base)) + return PTR_ERR(base); + usp->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sirf_usp_regmap_config); + if (IS_ERR(usp->regmap)) + return PTR_ERR(usp->regmap); + + usp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(usp->clk)) { + dev_err(&pdev->dev, "Get clock failed.\n"); + return PTR_ERR(usp->clk); + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = sirf_usp_pcm_runtime_resume(&pdev->dev); + if (ret) + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &sirf_usp_component, + &sirf_usp_pcm_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register Audio SoC dai failed.\n"); + return ret; + } + return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +} + +static int sirf_usp_pcm_remove(struct platform_device *pdev) +{ + if (!pm_runtime_enabled(&pdev->dev)) + sirf_usp_pcm_runtime_suspend(&pdev->dev); + else + pm_runtime_disable(&pdev->dev); + return 0; +} + +static const struct of_device_id sirf_usp_pcm_of_match[] = { + { .compatible = "sirf,prima2-usp-pcm", }, + {} +}; +MODULE_DEVICE_TABLE(of, sirf_usp_pcm_of_match); + +static const struct dev_pm_ops sirf_usp_pcm_pm_ops = { + SET_RUNTIME_PM_OPS(sirf_usp_pcm_runtime_suspend, + sirf_usp_pcm_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(sirf_usp_pcm_suspend, sirf_usp_pcm_resume) +}; + +static struct platform_driver sirf_usp_pcm_driver = { + .driver = { + .name = "sirf-usp-pcm", + .of_match_table = sirf_usp_pcm_of_match, + .pm = &sirf_usp_pcm_pm_ops, + }, + .probe = sirf_usp_pcm_probe, + .remove = sirf_usp_pcm_remove, +}; + +module_platform_driver(sirf_usp_pcm_driver); + +MODULE_DESCRIPTION("SiRF SoC USP PCM bus driver"); +MODULE_AUTHOR("RongJun Ying "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sirf/sirf-usp.h b/sound/soc/sirf/sirf-usp.h new file mode 100644 index 000000000..e22e13a61 --- /dev/null +++ b/sound/soc/sirf/sirf-usp.h @@ -0,0 +1,293 @@ +/* + * arch/arm/mach-prima2/include/mach/sirfsoc_usp.h + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#ifndef _SIRF_USP_H +#define _SIRF_USP_H + +/* USP Registers */ +#define USP_MODE1 0x00 +#define USP_MODE2 0x04 +#define USP_TX_FRAME_CTRL 0x08 +#define USP_RX_FRAME_CTRL 0x0C +#define USP_TX_RX_ENABLE 0x10 +#define USP_INT_ENABLE 0x14 +#define USP_INT_STATUS 0x18 +#define USP_PIN_IO_DATA 0x1C +#define USP_RISC_DSP_MODE 0x20 +#define USP_AYSNC_PARAM_REG 0x24 +#define USP_IRDA_X_MODE_DIV 0x28 +#define USP_SM_CFG 0x2C +#define USP_TX_DMA_IO_CTRL 0x100 +#define USP_TX_DMA_IO_LEN 0x104 +#define USP_TX_FIFO_CTRL 0x108 +#define USP_TX_FIFO_LEVEL_CHK 0x10C +#define USP_TX_FIFO_OP 0x110 +#define USP_TX_FIFO_STATUS 0x114 +#define USP_TX_FIFO_DATA 0x118 +#define USP_RX_DMA_IO_CTRL 0x120 +#define USP_RX_DMA_IO_LEN 0x124 +#define USP_RX_FIFO_CTRL 0x128 +#define USP_RX_FIFO_LEVEL_CHK 0x12C +#define USP_RX_FIFO_OP 0x130 +#define USP_RX_FIFO_STATUS 0x134 +#define USP_RX_FIFO_DATA 0x138 + +/* USP MODE register-1 */ +#define USP_SYNC_MODE 0x00000001 +#define USP_CLOCK_MODE_SLAVE 0x00000002 +#define USP_LOOP_BACK_EN 0x00000004 +#define USP_HPSIR_EN 0x00000008 +#define USP_ENDIAN_CTRL_LSBF 0x00000010 +#define USP_EN 0x00000020 +#define USP_RXD_ACT_EDGE_FALLING 0x00000040 +#define USP_TXD_ACT_EDGE_FALLING 0x00000080 +#define USP_RFS_ACT_LEVEL_LOGIC1 0x00000100 +#define USP_TFS_ACT_LEVEL_LOGIC1 0x00000200 +#define USP_SCLK_IDLE_MODE_TOGGLE 0x00000400 +#define USP_SCLK_IDLE_LEVEL_LOGIC1 0x00000800 +#define USP_SCLK_PIN_MODE_IO 0x00001000 +#define USP_RFS_PIN_MODE_IO 0x00002000 +#define USP_TFS_PIN_MODE_IO 0x00004000 +#define USP_RXD_PIN_MODE_IO 0x00008000 +#define USP_TXD_PIN_MODE_IO 0x00010000 +#define USP_SCLK_IO_MODE_INPUT 0x00020000 +#define USP_RFS_IO_MODE_INPUT 0x00040000 +#define USP_TFS_IO_MODE_INPUT 0x00080000 +#define USP_RXD_IO_MODE_INPUT 0x00100000 +#define USP_TXD_IO_MODE_INPUT 0x00200000 +#define USP_IRDA_WIDTH_DIV_MASK 0x3FC00000 +#define USP_IRDA_WIDTH_DIV_OFFSET 0 +#define USP_IRDA_IDLE_LEVEL_HIGH 0x40000000 +#define USP_TX_UFLOW_REPEAT_ZERO 0x80000000 +#define USP_TX_ENDIAN_MODE 0x00000020 +#define USP_RX_ENDIAN_MODE 0x00000020 + +/* USP Mode Register-2 */ +#define USP_RXD_DELAY_LEN_MASK 0x000000FF +#define USP_RXD_DELAY_LEN_OFFSET 0 + +#define USP_TXD_DELAY_LEN_MASK 0x0000FF00 +#define USP_TXD_DELAY_LEN_OFFSET 8 + +#define USP_ENA_CTRL_MODE 0x00010000 +#define USP_FRAME_CTRL_MODE 0x00020000 +#define USP_TFS_SOURCE_MODE 0x00040000 +#define USP_TFS_MS_MODE 0x00080000 +#define USP_CLK_DIVISOR_MASK 0x7FE00000 +#define USP_CLK_DIVISOR_OFFSET 21 + +#define USP_TFS_CLK_SLAVE_MODE (1<<20) +#define USP_RFS_CLK_SLAVE_MODE (1<<19) + +#define USP_IRDA_DATA_WIDTH 0x80000000 + +/* USP Transmit Frame Control Register */ + +#define USP_TXC_DATA_LEN_MASK 0x000000FF +#define USP_TXC_DATA_LEN_OFFSET 0 + +#define USP_TXC_SYNC_LEN_MASK 0x0000FF00 +#define USP_TXC_SYNC_LEN_OFFSET 8 + +#define USP_TXC_FRAME_LEN_MASK 0x00FF0000 +#define USP_TXC_FRAME_LEN_OFFSET 16 + +#define USP_TXC_SHIFTER_LEN_MASK 0x1F000000 +#define USP_TXC_SHIFTER_LEN_OFFSET 24 + +#define USP_TXC_SLAVE_CLK_SAMPLE 0x20000000 + +#define USP_TXC_CLK_DIVISOR_MASK 0xC0000000 +#define USP_TXC_CLK_DIVISOR_OFFSET 30 + +/* USP Receive Frame Control Register */ + +#define USP_RXC_DATA_LEN_MASK 0x000000FF +#define USP_RXC_DATA_LEN_OFFSET 0 + +#define USP_RXC_FRAME_LEN_MASK 0x0000FF00 +#define USP_RXC_FRAME_LEN_OFFSET 8 + +#define USP_RXC_SHIFTER_LEN_MASK 0x001F0000 +#define USP_RXC_SHIFTER_LEN_OFFSET 16 + +#define USP_START_EDGE_MODE 0x00800000 +#define USP_I2S_SYNC_CHG 0x00200000 + +#define USP_RXC_CLK_DIVISOR_MASK 0x0F000000 +#define USP_RXC_CLK_DIVISOR_OFFSET 24 +#define USP_SINGLE_SYNC_MODE 0x00400000 + +/* Tx - RX Enable Register */ + +#define USP_RX_ENA 0x00000001 +#define USP_TX_ENA 0x00000002 + +/* USP Interrupt Enable and status Register */ +#define USP_RX_DONE_INT 0x00000001 +#define USP_TX_DONE_INT 0x00000002 +#define USP_RX_OFLOW_INT 0x00000004 +#define USP_TX_UFLOW_INT 0x00000008 +#define USP_RX_IO_DMA_INT 0x00000010 +#define USP_TX_IO_DMA_INT 0x00000020 +#define USP_RXFIFO_FULL_INT 0x00000040 +#define USP_TXFIFO_EMPTY_INT 0x00000080 +#define USP_RXFIFO_THD_INT 0x00000100 +#define USP_TXFIFO_THD_INT 0x00000200 +#define USP_UART_FRM_ERR_INT 0x00000400 +#define USP_RX_TIMEOUT_INT 0x00000800 +#define USP_TX_ALLOUT_INT 0x00001000 +#define USP_RXD_BREAK_INT 0x00008000 + +/* All possible TX interruots */ +#define USP_TX_INTERRUPT (USP_TX_DONE_INT|USP_TX_UFLOW_INT|\ + USP_TX_IO_DMA_INT|\ + USP_TXFIFO_EMPTY_INT|\ + USP_TXFIFO_THD_INT) +/* All possible RX interruots */ +#define USP_RX_INTERRUPT (USP_RX_DONE_INT|USP_RX_OFLOW_INT|\ + USP_RX_IO_DMA_INT|\ + USP_RXFIFO_FULL_INT|\ + USP_RXFIFO_THD_INT|\ + USP_RX_TIMEOUT_INT) + +#define USP_INT_ALL 0x1FFF + +/* USP Pin I/O Data Register */ + +#define USP_RFS_PIN_VALUE_MASK 0x00000001 +#define USP_TFS_PIN_VALUE_MASK 0x00000002 +#define USP_RXD_PIN_VALUE_MASK 0x00000004 +#define USP_TXD_PIN_VALUE_MASK 0x00000008 +#define USP_SCLK_PIN_VALUE_MASK 0x00000010 + +/* USP RISC/DSP Mode Register */ +#define USP_RISC_DSP_SEL 0x00000001 + +/* USP ASYNC PARAMETER Register*/ + +#define USP_ASYNC_TIMEOUT_MASK 0x0000FFFF +#define USP_ASYNC_TIMEOUT_OFFSET 0 +#define USP_ASYNC_TIMEOUT(x) (((x)&USP_ASYNC_TIMEOUT_MASK) \ + < +// with code, comments and ideas from :- +// Richard Purdie + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct snd_ac97_reset_cfg { + struct pinctrl *pctl; + struct pinctrl_state *pstate_reset; + struct pinctrl_state *pstate_warm_reset; + struct pinctrl_state *pstate_run; + int gpio_sdata; + int gpio_sync; + int gpio_reset; +}; + +struct snd_ac97_gpio_priv { +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif + unsigned int gpios_set; + struct snd_soc_component *component; +}; + +static struct snd_ac97_bus soc_ac97_bus = { + .ops = NULL, /* Gets initialized in snd_soc_set_ac97_ops() */ +}; + +static void soc_ac97_device_release(struct device *dev) +{ + kfree(to_ac97_t(dev)); +} + +#ifdef CONFIG_GPIOLIB +static inline struct snd_soc_component *gpio_to_component(struct gpio_chip *chip) +{ + struct snd_ac97_gpio_priv *gpio_priv = gpiochip_get_data(chip); + + return gpio_priv->component; +} + +static int snd_soc_ac97_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + if (offset >= AC97_NUM_GPIOS) + return -EINVAL; + + return 0; +} + +static int snd_soc_ac97_gpio_direction_in(struct gpio_chip *chip, + unsigned offset) +{ + struct snd_soc_component *component = gpio_to_component(chip); + + dev_dbg(component->dev, "set gpio %d to output\n", offset); + return snd_soc_component_update_bits(component, AC97_GPIO_CFG, + 1 << offset, 1 << offset); +} + +static int snd_soc_ac97_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct snd_soc_component *component = gpio_to_component(chip); + int ret; + + if (snd_soc_component_read(component, AC97_GPIO_STATUS, &ret) < 0) + ret = -1; + + dev_dbg(component->dev, "get gpio %d : %d\n", offset, + ret < 0 ? ret : ret & (1 << offset)); + + return ret < 0 ? ret : !!(ret & (1 << offset)); +} + +static void snd_soc_ac97_gpio_set(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct snd_ac97_gpio_priv *gpio_priv = gpiochip_get_data(chip); + struct snd_soc_component *component = gpio_to_component(chip); + + gpio_priv->gpios_set &= ~(1 << offset); + gpio_priv->gpios_set |= (!!value) << offset; + snd_soc_component_write(component, AC97_GPIO_STATUS, + gpio_priv->gpios_set); + dev_dbg(component->dev, "set gpio %d to %d\n", offset, !!value); +} + +static int snd_soc_ac97_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct snd_soc_component *component = gpio_to_component(chip); + + dev_dbg(component->dev, "set gpio %d to output\n", offset); + snd_soc_ac97_gpio_set(chip, offset, value); + return snd_soc_component_update_bits(component, AC97_GPIO_CFG, + 1 << offset, 0); +} + +static const struct gpio_chip snd_soc_ac97_gpio_chip = { + .label = "snd_soc_ac97", + .owner = THIS_MODULE, + .request = snd_soc_ac97_gpio_request, + .direction_input = snd_soc_ac97_gpio_direction_in, + .get = snd_soc_ac97_gpio_get, + .direction_output = snd_soc_ac97_gpio_direction_out, + .set = snd_soc_ac97_gpio_set, + .can_sleep = 1, +}; + +static int snd_soc_ac97_init_gpio(struct snd_ac97 *ac97, + struct snd_soc_component *component) +{ + struct snd_ac97_gpio_priv *gpio_priv; + int ret; + + gpio_priv = devm_kzalloc(component->dev, sizeof(*gpio_priv), GFP_KERNEL); + if (!gpio_priv) + return -ENOMEM; + ac97->gpio_priv = gpio_priv; + gpio_priv->component = component; + gpio_priv->gpio_chip = snd_soc_ac97_gpio_chip; + gpio_priv->gpio_chip.ngpio = AC97_NUM_GPIOS; + gpio_priv->gpio_chip.parent = component->dev; + gpio_priv->gpio_chip.base = -1; + + ret = gpiochip_add_data(&gpio_priv->gpio_chip, gpio_priv); + if (ret != 0) + dev_err(component->dev, "Failed to add GPIOs: %d\n", ret); + return ret; +} + +static void snd_soc_ac97_free_gpio(struct snd_ac97 *ac97) +{ + gpiochip_remove(&ac97->gpio_priv->gpio_chip); +} +#else +static int snd_soc_ac97_init_gpio(struct snd_ac97 *ac97, + struct snd_soc_component *component) +{ + return 0; +} + +static void snd_soc_ac97_free_gpio(struct snd_ac97 *ac97) +{ +} +#endif + +/** + * snd_soc_alloc_ac97_component() - Allocate new a AC'97 device + * @component: The COMPONENT for which to create the AC'97 device + * + * Allocated a new snd_ac97 device and intializes it, but does not yet register + * it. The caller is responsible to either call device_add(&ac97->dev) to + * register the device, or to call put_device(&ac97->dev) to free the device. + * + * Returns: A snd_ac97 device or a PTR_ERR in case of an error. + */ +struct snd_ac97 *snd_soc_alloc_ac97_component(struct snd_soc_component *component) +{ + struct snd_ac97 *ac97; + + ac97 = kzalloc(sizeof(struct snd_ac97), GFP_KERNEL); + if (ac97 == NULL) + return ERR_PTR(-ENOMEM); + + ac97->bus = &soc_ac97_bus; + ac97->num = 0; + + ac97->dev.bus = &ac97_bus_type; + ac97->dev.parent = component->card->dev; + ac97->dev.release = soc_ac97_device_release; + + dev_set_name(&ac97->dev, "%d-%d:%s", + component->card->snd_card->number, 0, + component->name); + + device_initialize(&ac97->dev); + + return ac97; +} +EXPORT_SYMBOL(snd_soc_alloc_ac97_component); + +/** + * snd_soc_new_ac97_component - initailise AC97 device + * @component: audio component + * @id: The expected device ID + * @id_mask: Mask that is applied to the device ID before comparing with @id + * + * Initialises AC97 component resources for use by ad-hoc devices only. + * + * If @id is not 0 this function will reset the device, then read the ID from + * the device and check if it matches the expected ID. If it doesn't match an + * error will be returned and device will not be registered. + * + * Returns: A PTR_ERR() on failure or a valid snd_ac97 struct on success. + */ +struct snd_ac97 *snd_soc_new_ac97_component(struct snd_soc_component *component, + unsigned int id, unsigned int id_mask) +{ + struct snd_ac97 *ac97; + int ret; + + ac97 = snd_soc_alloc_ac97_component(component); + if (IS_ERR(ac97)) + return ac97; + + if (id) { + ret = snd_ac97_reset(ac97, false, id, id_mask); + if (ret < 0) { + dev_err(component->dev, "Failed to reset AC97 device: %d\n", + ret); + goto err_put_device; + } + } + + ret = device_add(&ac97->dev); + if (ret) + goto err_put_device; + + ret = snd_soc_ac97_init_gpio(ac97, component); + if (ret) + goto err_put_device; + + return ac97; + +err_put_device: + put_device(&ac97->dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(snd_soc_new_ac97_component); + +/** + * snd_soc_free_ac97_component - free AC97 component device + * @ac97: snd_ac97 device to be freed + * + * Frees AC97 component device resources. + */ +void snd_soc_free_ac97_component(struct snd_ac97 *ac97) +{ + snd_soc_ac97_free_gpio(ac97); + device_del(&ac97->dev); + ac97->bus = NULL; + put_device(&ac97->dev); +} +EXPORT_SYMBOL_GPL(snd_soc_free_ac97_component); + +static struct snd_ac97_reset_cfg snd_ac97_rst_cfg; + +static void snd_soc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_warm_reset); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 1); + + udelay(10); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); + msleep(2); +} + +static void snd_soc_ac97_reset(struct snd_ac97 *ac97) +{ + struct pinctrl *pctl = snd_ac97_rst_cfg.pctl; + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_reset); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_sync, 0); + gpio_direction_output(snd_ac97_rst_cfg.gpio_sdata, 0); + gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 0); + + udelay(10); + + gpio_direction_output(snd_ac97_rst_cfg.gpio_reset, 1); + + pinctrl_select_state(pctl, snd_ac97_rst_cfg.pstate_run); + msleep(2); +} + +static int snd_soc_ac97_parse_pinctl(struct device *dev, + struct snd_ac97_reset_cfg *cfg) +{ + struct pinctrl *p; + struct pinctrl_state *state; + int gpio; + int ret; + + p = devm_pinctrl_get(dev); + if (IS_ERR(p)) { + dev_err(dev, "Failed to get pinctrl\n"); + return PTR_ERR(p); + } + cfg->pctl = p; + + state = pinctrl_lookup_state(p, "ac97-reset"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-reset\n"); + return PTR_ERR(state); + } + cfg->pstate_reset = state; + + state = pinctrl_lookup_state(p, "ac97-warm-reset"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-warm-reset\n"); + return PTR_ERR(state); + } + cfg->pstate_warm_reset = state; + + state = pinctrl_lookup_state(p, "ac97-running"); + if (IS_ERR(state)) { + dev_err(dev, "Can't find pinctrl state ac97-running\n"); + return PTR_ERR(state); + } + cfg->pstate_run = state; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 0); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-sync gpio\n"); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link sync"); + if (ret) { + dev_err(dev, "Failed requesting ac97-sync gpio\n"); + return ret; + } + cfg->gpio_sync = gpio; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 1); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-sdata gpio %d\n", gpio); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link sdata"); + if (ret) { + dev_err(dev, "Failed requesting ac97-sdata gpio\n"); + return ret; + } + cfg->gpio_sdata = gpio; + + gpio = of_get_named_gpio(dev->of_node, "ac97-gpios", 2); + if (gpio < 0) { + dev_err(dev, "Can't find ac97-reset gpio\n"); + return gpio; + } + ret = devm_gpio_request(dev, gpio, "AC97 link reset"); + if (ret) { + dev_err(dev, "Failed requesting ac97-reset gpio\n"); + return ret; + } + cfg->gpio_reset = gpio; + + return 0; +} + +struct snd_ac97_bus_ops *soc_ac97_ops; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops) +{ + if (ops == soc_ac97_ops) + return 0; + + if (soc_ac97_ops && ops) + return -EBUSY; + + soc_ac97_ops = ops; + soc_ac97_bus.ops = ops; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops); + +/** + * snd_soc_set_ac97_ops_of_reset - Set ac97 ops with generic ac97 reset functions + * + * This function sets the reset and warm_reset properties of ops and parses + * the device node of pdev to get pinctrl states and gpio numbers to use. + */ +int snd_soc_set_ac97_ops_of_reset(struct snd_ac97_bus_ops *ops, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_ac97_reset_cfg cfg; + int ret; + + ret = snd_soc_ac97_parse_pinctl(dev, &cfg); + if (ret) + return ret; + + ret = snd_soc_set_ac97_ops(ops); + if (ret) + return ret; + + ops->warm_reset = snd_soc_ac97_warm_reset; + ops->reset = snd_soc_ac97_reset; + + snd_ac97_rst_cfg = cfg; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_set_ac97_ops_of_reset); diff --git a/sound/soc/soc-acpi.c b/sound/soc/soc-acpi.c new file mode 100644 index 000000000..4fb29f0e5 --- /dev/null +++ b/sound/soc/soc-acpi.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// soc-apci.c - support for ACPI enumeration. +// +// Copyright (c) 2013-15, Intel Corporation. + +#include + +struct snd_soc_acpi_mach * +snd_soc_acpi_find_machine(struct snd_soc_acpi_mach *machines) +{ + struct snd_soc_acpi_mach *mach; + struct snd_soc_acpi_mach *mach_alt; + + for (mach = machines; mach->id[0]; mach++) { + if (acpi_dev_present(mach->id, NULL, -1)) { + if (mach->machine_quirk) { + mach_alt = mach->machine_quirk(mach); + if (!mach_alt) + continue; /* not full match, ignore */ + mach = mach_alt; + } + + return mach; + } + } + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_acpi_find_machine); + +static acpi_status snd_soc_acpi_find_package(acpi_handle handle, u32 level, + void *context, void **ret) +{ + struct acpi_device *adev; + acpi_status status = AE_OK; + struct snd_soc_acpi_package_context *pkg_ctx = context; + + pkg_ctx->data_valid = false; + + if (acpi_bus_get_device(handle, &adev)) + return AE_OK; + + if (adev->status.present && adev->status.functional) { + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *myobj = NULL; + + status = acpi_evaluate_object_typed(handle, pkg_ctx->name, + NULL, &buffer, + ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) + return AE_OK; + + myobj = buffer.pointer; + if (!myobj || myobj->package.count != pkg_ctx->length) { + kfree(buffer.pointer); + return AE_OK; + } + + status = acpi_extract_package(myobj, + pkg_ctx->format, pkg_ctx->state); + if (ACPI_FAILURE(status)) { + kfree(buffer.pointer); + return AE_OK; + } + + kfree(buffer.pointer); + pkg_ctx->data_valid = true; + return AE_CTRL_TERMINATE; + } + + return AE_OK; +} + +bool snd_soc_acpi_find_package_from_hid(const u8 hid[ACPI_ID_LEN], + struct snd_soc_acpi_package_context *ctx) +{ + acpi_status status; + + status = acpi_get_devices(hid, snd_soc_acpi_find_package, ctx, NULL); + + if (ACPI_FAILURE(status) || !ctx->data_valid) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(snd_soc_acpi_find_package_from_hid); + +struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg) +{ + struct snd_soc_acpi_mach *mach = arg; + struct snd_soc_acpi_codecs *codec_list = + (struct snd_soc_acpi_codecs *) mach->quirk_data; + int i; + + if (mach->quirk_data == NULL) + return mach; + + for (i = 0; i < codec_list->num_codecs; i++) { + if (!acpi_dev_present(codec_list->codecs[i], NULL, -1)) + return NULL; + } + + return mach; +} +EXPORT_SYMBOL_GPL(snd_soc_acpi_codec_list); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ALSA SoC ACPI module"); diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c new file mode 100644 index 000000000..409d082e8 --- /dev/null +++ b/sound/soc/soc-compress.c @@ -0,0 +1,993 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-compress.c -- ALSA SoC Compress +// +// Copyright (C) 2012 Intel Corp. +// +// Authors: Namarta Kohli +// Ramesh Babu K V +// Vinod Koul + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int soc_compr_components_open(struct snd_compr_stream *cstream, + struct snd_soc_component **last) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + int ret; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->open) + continue; + + ret = component->driver->compr_ops->open(cstream); + if (ret < 0) { + dev_err(component->dev, + "Compress ASoC: can't open platform %s: %d\n", + component->name, ret); + + *last = component; + return ret; + } + } + + *last = NULL; + return 0; +} + +static int soc_compr_components_free(struct snd_compr_stream *cstream, + struct snd_soc_component *last) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (component == last) + break; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->free) + continue; + + component->driver->compr_ops->free(cstream); + } + + return 0; +} + +static int soc_compr_open(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->startup) { + ret = cpu_dai->driver->cops->startup(cstream, cpu_dai); + if (ret < 0) { + dev_err(cpu_dai->dev, + "Compress ASoC: can't open interface %s: %d\n", + cpu_dai->name, ret); + goto out; + } + } + + ret = soc_compr_components_open(cstream, &component); + if (ret < 0) + goto machine_err; + + if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->startup) { + ret = rtd->dai_link->compr_ops->startup(cstream); + if (ret < 0) { + dev_err(rtd->dev, + "Compress ASoC: %s startup failed: %d\n", + rtd->dai_link->name, ret); + goto machine_err; + } + } + + snd_soc_runtime_activate(rtd, cstream->direction); + + mutex_unlock(&rtd->pcm_mutex); + + return 0; + +machine_err: + soc_compr_components_free(cstream, component); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->shutdown) + cpu_dai->driver->cops->shutdown(cstream, cpu_dai); +out: + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_open_fe(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *fe = cstream->private_data; + struct snd_pcm_substream *fe_substream = + fe->pcm->streams[cstream->direction].substream; + struct snd_soc_component *component; + struct snd_soc_dai *cpu_dai = fe->cpu_dai; + struct snd_soc_dpcm *dpcm; + struct snd_soc_dapm_widget_list *list; + int stream; + int ret; + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else + stream = SNDRV_PCM_STREAM_CAPTURE; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + fe->dpcm[stream].runtime = fe_substream->runtime; + + ret = dpcm_path_get(fe, stream, &list); + if (ret < 0) + goto be_err; + else if (ret == 0) + dev_dbg(fe->dev, "Compress ASoC: %s no valid %s route\n", + fe->dai_link->name, stream ? "capture" : "playback"); + /* calculate valid and active FE <-> BE dpcms */ + dpcm_process_paths(fe, stream, &list, 1); + fe->dpcm[stream].runtime = fe_substream->runtime; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + ret = dpcm_be_dai_startup(fe, stream); + if (ret < 0) { + /* clean up all links */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + + dpcm_be_disconnect(fe, stream); + fe->dpcm[stream].runtime = NULL; + goto out; + } + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->startup) { + ret = cpu_dai->driver->cops->startup(cstream, cpu_dai); + if (ret < 0) { + dev_err(cpu_dai->dev, + "Compress ASoC: can't open interface %s: %d\n", + cpu_dai->name, ret); + goto out; + } + } + + ret = soc_compr_components_open(cstream, &component); + if (ret < 0) + goto open_err; + + if (fe->dai_link->compr_ops && fe->dai_link->compr_ops->startup) { + ret = fe->dai_link->compr_ops->startup(cstream); + if (ret < 0) { + pr_err("Compress ASoC: %s startup failed: %d\n", + fe->dai_link->name, ret); + goto machine_err; + } + } + + dpcm_clear_pending_state(fe, stream); + dpcm_path_put(&list); + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + snd_soc_runtime_activate(fe, stream); + + mutex_unlock(&fe->card->mutex); + + return 0; + +machine_err: + soc_compr_components_free(cstream, component); +open_err: + if (cpu_dai->driver->cops && cpu_dai->driver->cops->shutdown) + cpu_dai->driver->cops->shutdown(cstream, cpu_dai); +out: + dpcm_path_put(&list); +be_err: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->mutex); + return ret; +} + +/* + * Power down the audio subsystem pmdown_time msecs after close is called. + * This is to ensure there are no pops or clicks in between any music tracks + * due to DAPM power cycling. + */ +static void close_delayed_work(struct work_struct *work) +{ + struct snd_soc_pcm_runtime *rtd = + container_of(work, struct snd_soc_pcm_runtime, delayed_work.work); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + dev_dbg(rtd->dev, + "Compress ASoC: pop wq checking: %s status: %s waiting: %s\n", + codec_dai->driver->playback.stream_name, + codec_dai->playback_active ? "active" : "inactive", + rtd->pop_wait ? "yes" : "no"); + + /* are we waiting on this codec DAI stream */ + if (rtd->pop_wait == 1) { + rtd->pop_wait = 0; + snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_STOP); + } + + mutex_unlock(&rtd->pcm_mutex); +} + +static int soc_compr_free(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int stream; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else + stream = SNDRV_PCM_STREAM_CAPTURE; + + snd_soc_runtime_deactivate(rtd, stream); + + snd_soc_dai_digital_mute(codec_dai, 1, cstream->direction); + + if (!cpu_dai->active) + cpu_dai->rate = 0; + + if (!codec_dai->active) + codec_dai->rate = 0; + + if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->shutdown) + rtd->dai_link->compr_ops->shutdown(cstream); + + soc_compr_components_free(cstream, NULL); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->shutdown) + cpu_dai->driver->cops->shutdown(cstream, cpu_dai); + + if (cstream->direction == SND_COMPRESS_PLAYBACK) { + if (snd_soc_runtime_ignore_pmdown_time(rtd)) { + snd_soc_dapm_stream_event(rtd, + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_STOP); + } else { + rtd->pop_wait = 1; + queue_delayed_work(system_power_efficient_wq, + &rtd->delayed_work, + msecs_to_jiffies(rtd->pmdown_time)); + } + } else { + /* capture streams can be powered down now */ + snd_soc_dapm_stream_event(rtd, + SNDRV_PCM_STREAM_CAPTURE, + SND_SOC_DAPM_STREAM_STOP); + } + + mutex_unlock(&rtd->pcm_mutex); + return 0; +} + +static int soc_compr_free_fe(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *fe = cstream->private_data; + struct snd_soc_dai *cpu_dai = fe->cpu_dai; + struct snd_soc_dpcm *dpcm; + int stream, ret; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else + stream = SNDRV_PCM_STREAM_CAPTURE; + + snd_soc_runtime_deactivate(fe, stream); + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + ret = dpcm_be_dai_hw_free(fe, stream); + if (ret < 0) + dev_err(fe->dev, "Compressed ASoC: hw_free failed: %d\n", ret); + + ret = dpcm_be_dai_shutdown(fe, stream); + + /* mark FE's links ready to prune */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + + dpcm_be_disconnect(fe, stream); + + fe->dpcm[stream].runtime = NULL; + + if (fe->dai_link->compr_ops && fe->dai_link->compr_ops->shutdown) + fe->dai_link->compr_ops->shutdown(cstream); + + soc_compr_components_free(cstream, NULL); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->shutdown) + cpu_dai->driver->cops->shutdown(cstream, cpu_dai); + + mutex_unlock(&fe->card->mutex); + return 0; +} + +static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) +{ + + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0, __ret; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->trigger) + continue; + + __ret = component->driver->compr_ops->trigger(cstream, cmd); + if (__ret < 0) + ret = __ret; + } + if (ret < 0) + goto out; + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->trigger) + cpu_dai->driver->cops->trigger(cstream, cmd, cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_soc_dai_digital_mute(codec_dai, 0, cstream->direction); + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_soc_dai_digital_mute(codec_dai, 1, cstream->direction); + break; + } + +out: + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = fe->cpu_dai; + int ret = 0, __ret, stream; + + if (cmd == SND_COMPR_TRIGGER_PARTIAL_DRAIN || + cmd == SND_COMPR_TRIGGER_DRAIN) { + + for_each_rtdcom(fe, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->trigger) + continue; + + __ret = component->driver->compr_ops->trigger(cstream, cmd); + if (__ret < 0) + ret = __ret; + } + return ret; + } + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else + stream = SNDRV_PCM_STREAM_CAPTURE; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->trigger) { + ret = cpu_dai->driver->cops->trigger(cstream, cmd, cpu_dai); + if (ret < 0) + goto out; + } + + for_each_rtdcom(fe, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->trigger) + continue; + + __ret = component->driver->compr_ops->trigger(cstream, cmd); + if (__ret < 0) + ret = __ret; + } + if (ret < 0) + goto out; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + ret = dpcm_be_dai_trigger(fe, stream, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + break; + } + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->mutex); + return ret; +} + +static int soc_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0, __ret; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + /* + * First we call set_params for the CPU DAI, then the component + * driver this should configure the SoC side. If the machine has + * compressed ops then we call that as well. The expectation is + * that these callbacks will configure everything for this compress + * path, like configuring a PCM port for a CODEC. + */ + if (cpu_dai->driver->cops && cpu_dai->driver->cops->set_params) { + ret = cpu_dai->driver->cops->set_params(cstream, params, cpu_dai); + if (ret < 0) + goto err; + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->set_params) + continue; + + __ret = component->driver->compr_ops->set_params(cstream, params); + if (__ret < 0) + ret = __ret; + } + if (ret < 0) + goto err; + + if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->set_params) { + ret = rtd->dai_link->compr_ops->set_params(cstream); + if (ret < 0) + goto err; + } + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_START); + else + snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE, + SND_SOC_DAPM_STREAM_START); + + /* cancel any delayed stream shutdown that is pending */ + rtd->pop_wait = 0; + mutex_unlock(&rtd->pcm_mutex); + + cancel_delayed_work_sync(&rtd->delayed_work); + + return ret; + +err: + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_set_params_fe(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_soc_pcm_runtime *fe = cstream->private_data; + struct snd_pcm_substream *fe_substream = + fe->pcm->streams[cstream->direction].substream; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = fe->cpu_dai; + int ret = 0, __ret, stream; + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else + stream = SNDRV_PCM_STREAM_CAPTURE; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + + /* + * Create an empty hw_params for the BE as the machine driver must + * fix this up to match DSP decoder and ASRC configuration. + * I.e. machine driver fixup for compressed BE is mandatory. + */ + memset(&fe->dpcm[fe_substream->stream].hw_params, 0, + sizeof(struct snd_pcm_hw_params)); + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + ret = dpcm_be_dai_hw_params(fe, stream); + if (ret < 0) + goto out; + + ret = dpcm_be_dai_prepare(fe, stream); + if (ret < 0) + goto out; + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->set_params) { + ret = cpu_dai->driver->cops->set_params(cstream, params, cpu_dai); + if (ret < 0) + goto out; + } + + for_each_rtdcom(fe, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->set_params) + continue; + + __ret = component->driver->compr_ops->set_params(cstream, params); + if (__ret < 0) + ret = __ret; + } + if (ret < 0) + goto out; + + if (fe->dai_link->compr_ops && fe->dai_link->compr_ops->set_params) { + ret = fe->dai_link->compr_ops->set_params(cstream); + if (ret < 0) + goto out; + } + + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START); + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + mutex_unlock(&fe->card->mutex); + return ret; +} + +static int soc_compr_get_params(struct snd_compr_stream *cstream, + struct snd_codec *params) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0, __ret; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->get_params) { + ret = cpu_dai->driver->cops->get_params(cstream, params, cpu_dai); + if (ret < 0) + goto err; + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->get_params) + continue; + + __ret = component->driver->compr_ops->get_params(cstream, params); + if (__ret < 0) + ret = __ret; + } + +err: + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + int ret = 0, __ret; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->get_caps) + continue; + + __ret = component->driver->compr_ops->get_caps(cstream, caps); + if (__ret < 0) + ret = __ret; + } + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_get_codec_caps(struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + int ret = 0, __ret; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->get_codec_caps) + continue; + + __ret = component->driver->compr_ops->get_codec_caps(cstream, codec); + if (__ret < 0) + ret = __ret; + } + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_ack(struct snd_compr_stream *cstream, size_t bytes) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0, __ret; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->ack) { + ret = cpu_dai->driver->cops->ack(cstream, bytes, cpu_dai); + if (ret < 0) + goto err; + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->ack) + continue; + + __ret = component->driver->compr_ops->ack(cstream, bytes); + if (__ret < 0) + ret = __ret; + } + +err: + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + int ret = 0, __ret; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->pointer) + cpu_dai->driver->cops->pointer(cstream, tstamp, cpu_dai); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->pointer) + continue; + + __ret = component->driver->compr_ops->pointer(cstream, tstamp); + if (__ret < 0) + ret = __ret; + } + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_copy(struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + int ret = 0; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->copy) + continue; + + ret = component->driver->compr_ops->copy(cstream, buf, count); + break; + } + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static int soc_compr_set_metadata(struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0, __ret; + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->set_metadata) { + ret = cpu_dai->driver->cops->set_metadata(cstream, metadata, cpu_dai); + if (ret < 0) + return ret; + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->set_metadata) + continue; + + __ret = component->driver->compr_ops->set_metadata(cstream, metadata); + if (__ret < 0) + ret = __ret; + } + + return ret; +} + +static int soc_compr_get_metadata(struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0, __ret; + + if (cpu_dai->driver->cops && cpu_dai->driver->cops->get_metadata) { + ret = cpu_dai->driver->cops->get_metadata(cstream, metadata, cpu_dai); + if (ret < 0) + return ret; + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->get_metadata) + continue; + + __ret = component->driver->compr_ops->get_metadata(cstream, metadata); + if (__ret < 0) + ret = __ret; + } + + return ret; +} + +/* ASoC Compress operations */ +static struct snd_compr_ops soc_compr_ops = { + .open = soc_compr_open, + .free = soc_compr_free, + .set_params = soc_compr_set_params, + .set_metadata = soc_compr_set_metadata, + .get_metadata = soc_compr_get_metadata, + .get_params = soc_compr_get_params, + .trigger = soc_compr_trigger, + .pointer = soc_compr_pointer, + .ack = soc_compr_ack, + .get_caps = soc_compr_get_caps, + .get_codec_caps = soc_compr_get_codec_caps +}; + +/* ASoC Dynamic Compress operations */ +static struct snd_compr_ops soc_compr_dyn_ops = { + .open = soc_compr_open_fe, + .free = soc_compr_free_fe, + .set_params = soc_compr_set_params_fe, + .get_params = soc_compr_get_params, + .set_metadata = soc_compr_set_metadata, + .get_metadata = soc_compr_get_metadata, + .trigger = soc_compr_trigger_fe, + .pointer = soc_compr_pointer, + .ack = soc_compr_ack, + .get_caps = soc_compr_get_caps, + .get_codec_caps = soc_compr_get_codec_caps +}; + +/** + * snd_soc_new_compress - create a new compress. + * + * @rtd: The runtime for which we will create compress + * @num: the device index number (zero based - shared with normal PCMs) + * + * Return: 0 for success, else error. + */ +int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) +{ + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_compr *compr; + struct snd_pcm *be_pcm; + char new_name[64]; + int ret = 0, direction = 0; + int playback = 0, capture = 0; + + if (rtd->num_codecs > 1) { + dev_err(rtd->card->dev, + "Compress ASoC: Multicodec not supported\n"); + return -EINVAL; + } + + /* check client and interface hw capabilities */ + if (codec_dai->driver->playback.channels_min) + playback = 1; + if (codec_dai->driver->capture.channels_min) + capture = 1; + + capture = capture && cpu_dai->driver->capture.channels_min; + playback = playback && cpu_dai->driver->playback.channels_min; + + /* + * Compress devices are unidirectional so only one of the directions + * should be set, check for that (xor) + */ + if (playback + capture != 1) { + dev_err(rtd->card->dev, + "Compress ASoC: Invalid direction for P %d, C %d\n", + playback, capture); + return -EINVAL; + } + + if (playback) + direction = SND_COMPRESS_PLAYBACK; + else + direction = SND_COMPRESS_CAPTURE; + + compr = kzalloc(sizeof(*compr), GFP_KERNEL); + if (!compr) + return -ENOMEM; + + compr->ops = devm_kzalloc(rtd->card->dev, sizeof(soc_compr_ops), + GFP_KERNEL); + if (!compr->ops) { + ret = -ENOMEM; + goto compr_err; + } + + if (rtd->dai_link->dynamic) { + snprintf(new_name, sizeof(new_name), "(%s)", + rtd->dai_link->stream_name); + + ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, + rtd->dai_link->dpcm_playback, + rtd->dai_link->dpcm_capture, &be_pcm); + if (ret < 0) { + dev_err(rtd->card->dev, + "Compress ASoC: can't create compressed for %s: %d\n", + rtd->dai_link->name, ret); + goto compr_err; + } + + rtd->pcm = be_pcm; + rtd->fe_compr = 1; + if (rtd->dai_link->dpcm_playback) + be_pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; + else if (rtd->dai_link->dpcm_capture) + be_pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd; + memcpy(compr->ops, &soc_compr_dyn_ops, sizeof(soc_compr_dyn_ops)); + } else { + snprintf(new_name, sizeof(new_name), "%s %s-%d", + rtd->dai_link->stream_name, codec_dai->name, num); + + memcpy(compr->ops, &soc_compr_ops, sizeof(soc_compr_ops)); + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->compr_ops || + !component->driver->compr_ops->copy) + continue; + + compr->ops->copy = soc_compr_copy; + break; + } + + mutex_init(&compr->lock); + ret = snd_compress_new(rtd->card->snd_card, num, direction, + new_name, compr); + if (ret < 0) { + component = rtd->codec_dai->component; + dev_err(component->dev, + "Compress ASoC: can't create compress for codec %s: %d\n", + component->name, ret); + goto compr_err; + } + + /* DAPM dai link stream work */ + INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); + + rtd->compr = compr; + compr->private_data = rtd; + + dev_info(rtd->card->dev, "Compress ASoC: %s <-> %s mapping ok\n", + codec_dai->name, cpu_dai->name); + return ret; + +compr_err: + kfree(compr); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_new_compress); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c new file mode 100644 index 000000000..9ca7dff55 --- /dev/null +++ b/sound/soc/soc-core.c @@ -0,0 +1,3886 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-core.c -- ALSA SoC Audio Layer +// +// Copyright 2005 Wolfson Microelectronics PLC. +// Copyright 2005 Openedhand Ltd. +// Copyright (C) 2010 Slimlogic Ltd. +// Copyright (C) 2010 Texas Instruments Inc. +// +// Author: Liam Girdwood +// with code, comments and ideas from :- +// Richard Purdie +// +// TODO: +// o Add hw rules to enforce rates, etc. +// o More testing with other codecs/machines. +// o Add more codecs and platforms to ensure good API coverage. +// o Support TDM on PCM and I2S + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include + +#define NAME_SIZE 32 + +#ifdef CONFIG_DEBUG_FS +struct dentry *snd_soc_debugfs_root; +EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); +#endif + +static DEFINE_MUTEX(client_mutex); +static LIST_HEAD(component_list); + +/* + * This is a timeout to do a DAPM powerdown after a stream is closed(). + * It can be used to eliminate pops between different playback streams, e.g. + * between two audio tracks. + */ +static int pmdown_time = 5000; +module_param(pmdown_time, int, 0); +MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)"); + +/* If a DMI filed contain strings in this blacklist (e.g. + * "Type2 - Board Manufacturer" or "Type1 - TBD by OEM"), it will be taken + * as invalid and dropped when setting the card long name from DMI info. + */ +static const char * const dmi_blacklist[] = { + "To be filled by OEM", + "TBD by OEM", + "Default String", + "Board Manufacturer", + "Board Vendor Name", + "Board Product Name", + NULL, /* terminator */ +}; + +static ssize_t pmdown_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); + + return sprintf(buf, "%ld\n", rtd->pmdown_time); +} + +static ssize_t pmdown_time_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); + int ret; + + ret = kstrtol(buf, 10, &rtd->pmdown_time); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR(pmdown_time, 0644, pmdown_time_show, pmdown_time_set); + +static struct attribute *soc_dev_attrs[] = { + &dev_attr_pmdown_time.attr, + NULL +}; + +static umode_t soc_dev_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); + + if (attr == &dev_attr_pmdown_time.attr) + return attr->mode; /* always visible */ + return rtd->num_codecs ? attr->mode : 0; /* enabled only with codec */ +} + +static const struct attribute_group soc_dapm_dev_group = { + .attrs = soc_dapm_dev_attrs, + .is_visible = soc_dev_attr_is_visible, +}; + +static const struct attribute_group soc_dev_group = { + .attrs = soc_dev_attrs, + .is_visible = soc_dev_attr_is_visible, +}; + +static const struct attribute_group *soc_dev_attr_groups[] = { + &soc_dapm_dev_group, + &soc_dev_group, + NULL +}; + +#ifdef CONFIG_DEBUG_FS +static void soc_init_component_debugfs(struct snd_soc_component *component) +{ + if (!component->card->debugfs_card_root) + return; + + if (component->debugfs_prefix) { + char *name; + + name = kasprintf(GFP_KERNEL, "%s:%s", + component->debugfs_prefix, component->name); + if (name) { + component->debugfs_root = debugfs_create_dir(name, + component->card->debugfs_card_root); + kfree(name); + } + } else { + component->debugfs_root = debugfs_create_dir(component->name, + component->card->debugfs_card_root); + } + + if (!component->debugfs_root) { + dev_warn(component->dev, + "ASoC: Failed to create component debugfs directory\n"); + return; + } + + snd_soc_dapm_debugfs_init(snd_soc_component_get_dapm(component), + component->debugfs_root); +} + +static void soc_cleanup_component_debugfs(struct snd_soc_component *component) +{ + debugfs_remove_recursive(component->debugfs_root); +} + +static int dai_list_show(struct seq_file *m, void *v) +{ + struct snd_soc_component *component; + struct snd_soc_dai *dai; + + mutex_lock(&client_mutex); + + list_for_each_entry(component, &component_list, list) + list_for_each_entry(dai, &component->dai_list, list) + seq_printf(m, "%s\n", dai->name); + + mutex_unlock(&client_mutex); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(dai_list); + +static int component_list_show(struct seq_file *m, void *v) +{ + struct snd_soc_component *component; + + mutex_lock(&client_mutex); + + list_for_each_entry(component, &component_list, list) + seq_printf(m, "%s\n", component->name); + + mutex_unlock(&client_mutex); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(component_list); + +static void soc_init_card_debugfs(struct snd_soc_card *card) +{ + if (!snd_soc_debugfs_root) + return; + + card->debugfs_card_root = debugfs_create_dir(card->name, + snd_soc_debugfs_root); + if (!card->debugfs_card_root) { + dev_warn(card->dev, + "ASoC: Failed to create card debugfs directory\n"); + return; + } + + card->debugfs_pop_time = debugfs_create_u32("dapm_pop_time", 0644, + card->debugfs_card_root, + &card->pop_time); + if (!card->debugfs_pop_time) + dev_warn(card->dev, + "ASoC: Failed to create pop time debugfs file\n"); +} + +static void soc_cleanup_card_debugfs(struct snd_soc_card *card) +{ + debugfs_remove_recursive(card->debugfs_card_root); +} + +static void snd_soc_debugfs_init(void) +{ + snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); + if (IS_ERR_OR_NULL(snd_soc_debugfs_root)) { + pr_warn("ASoC: Failed to create debugfs directory\n"); + snd_soc_debugfs_root = NULL; + return; + } + + if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL, + &dai_list_fops)) + pr_warn("ASoC: Failed to create DAI list debugfs file\n"); + + if (!debugfs_create_file("components", 0444, snd_soc_debugfs_root, NULL, + &component_list_fops)) + pr_warn("ASoC: Failed to create component list debugfs file\n"); +} + +static void snd_soc_debugfs_exit(void) +{ + debugfs_remove_recursive(snd_soc_debugfs_root); +} + +#else + +static inline void soc_init_component_debugfs( + struct snd_soc_component *component) +{ +} + +static inline void soc_cleanup_component_debugfs( + struct snd_soc_component *component) +{ +} + +static inline void soc_init_card_debugfs(struct snd_soc_card *card) +{ +} + +static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) +{ +} + +static inline void snd_soc_debugfs_init(void) +{ +} + +static inline void snd_soc_debugfs_exit(void) +{ +} + +#endif + +static int snd_soc_rtdcom_add(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_component *component) +{ + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_rtdcom_list *new_rtdcom; + + for_each_rtdcom(rtd, rtdcom) { + /* already connected */ + if (rtdcom->component == component) + return 0; + } + + new_rtdcom = kmalloc(sizeof(*new_rtdcom), GFP_KERNEL); + if (!new_rtdcom) + return -ENOMEM; + + new_rtdcom->component = component; + INIT_LIST_HEAD(&new_rtdcom->list); + + list_add_tail(&new_rtdcom->list, &rtd->component_list); + + return 0; +} + +static void snd_soc_rtdcom_del_all(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_rtdcom_list *rtdcom1, *rtdcom2; + + for_each_rtdcom_safe(rtd, rtdcom1, rtdcom2) + kfree(rtdcom1); + + INIT_LIST_HEAD(&rtd->component_list); +} + +struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd, + const char *driver_name) +{ + struct snd_soc_rtdcom_list *rtdcom; + + if (!driver_name) + return NULL; + + for_each_rtdcom(rtd, rtdcom) { + const char *component_name = rtdcom->component->driver->name; + + if (!component_name) + continue; + + if ((component_name == driver_name) || + strcmp(component_name, driver_name) == 0) + return rtdcom->component; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_rtdcom_lookup); + +struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card, + const char *dai_link, int stream) +{ + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + if (rtd->dai_link->no_pcm && + !strcmp(rtd->dai_link->name, dai_link)) + return rtd->pcm->streams[stream].substream; + } + dev_dbg(card->dev, "ASoC: failed to find dai link %s\n", dai_link); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_substream); + +static const struct snd_soc_ops null_snd_soc_ops; + +static struct snd_soc_pcm_runtime *soc_new_pcm_runtime( + struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct snd_soc_pcm_runtime *rtd; + + rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime), GFP_KERNEL); + if (!rtd) + return NULL; + + INIT_LIST_HEAD(&rtd->component_list); + rtd->card = card; + rtd->dai_link = dai_link; + if (!rtd->dai_link->ops) + rtd->dai_link->ops = &null_snd_soc_ops; + + rtd->codec_dais = kcalloc(dai_link->num_codecs, + sizeof(struct snd_soc_dai *), + GFP_KERNEL); + if (!rtd->codec_dais) { + kfree(rtd); + return NULL; + } + + return rtd; +} + +static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd) +{ + kfree(rtd->codec_dais); + snd_soc_rtdcom_del_all(rtd); + kfree(rtd); +} + +static void soc_add_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) +{ + list_add_tail(&rtd->list, &card->rtd_list); + rtd->num = card->num_rtd; + card->num_rtd++; +} + +static void soc_remove_pcm_runtimes(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd, *_rtd; + + list_for_each_entry_safe(rtd, _rtd, &card->rtd_list, list) { + list_del(&rtd->list); + soc_free_pcm_runtime(rtd); + } + + card->num_rtd = 0; +} + +struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, + const char *dai_link) +{ + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + if (!strcmp(rtd->dai_link->name, dai_link)) + return rtd; + } + dev_dbg(card->dev, "ASoC: failed to find rtd %s\n", dai_link); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_pcm_runtime); + +static void codec2codec_close_delayed_work(struct work_struct *work) +{ + /* Currently nothing to do for c2c links + * Since c2c links are internal nodes in the DAPM graph and + * don't interface with the outside world or application layer + * we don't have to do any special handling on close. + */ +} + +#ifdef CONFIG_PM_SLEEP +/* powers down audio subsystem for suspend */ +int snd_soc_suspend(struct device *dev) +{ + struct snd_soc_card *card = dev_get_drvdata(dev); + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + int i; + + /* If the card is not initialized yet there is nothing to do */ + if (!card->instantiated) + return 0; + + /* Due to the resume being scheduled into a workqueue we could + * suspend before that's finished - wait for it to complete. + */ + snd_power_wait(card->snd_card, SNDRV_CTL_POWER_D0); + + /* we're going to block userspace touching us until resume completes */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot); + + /* mute any active DACs */ + list_for_each_entry(rtd, &card->rtd_list, list) { + + if (rtd->dai_link->ignore_suspend) + continue; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + struct snd_soc_dai_driver *drv = dai->driver; + + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 1); + } + } + + /* suspend all pcms */ + list_for_each_entry(rtd, &card->rtd_list, list) { + if (rtd->dai_link->ignore_suspend) + continue; + + snd_pcm_suspend_all(rtd->pcm); + } + + if (card->suspend_pre) + card->suspend_pre(card); + + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + if (rtd->dai_link->ignore_suspend) + continue; + + if (cpu_dai->driver->suspend && !cpu_dai->driver->bus_control) + cpu_dai->driver->suspend(cpu_dai); + } + + /* close any waiting streams */ + list_for_each_entry(rtd, &card->rtd_list, list) + flush_delayed_work(&rtd->delayed_work); + + list_for_each_entry(rtd, &card->rtd_list, list) { + + if (rtd->dai_link->ignore_suspend) + continue; + + snd_soc_dapm_stream_event(rtd, + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_SUSPEND); + + snd_soc_dapm_stream_event(rtd, + SNDRV_PCM_STREAM_CAPTURE, + SND_SOC_DAPM_STREAM_SUSPEND); + } + + /* Recheck all endpoints too, their state is affected by suspend */ + dapm_mark_endpoints_dirty(card); + snd_soc_dapm_sync(&card->dapm); + + /* suspend all COMPONENTs */ + list_for_each_entry(component, &card->component_dev_list, card_list) { + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + + /* If there are paths active then the COMPONENT will be held with + * bias _ON and should not be suspended. */ + if (!component->suspended) { + switch (snd_soc_dapm_get_bias_level(dapm)) { + case SND_SOC_BIAS_STANDBY: + /* + * If the COMPONENT is capable of idle + * bias off then being in STANDBY + * means it's doing something, + * otherwise fall through. + */ + if (dapm->idle_bias_off) { + dev_dbg(component->dev, + "ASoC: idle_bias_off CODEC on over suspend\n"); + break; + } + /* fall through */ + + case SND_SOC_BIAS_OFF: + if (component->driver->suspend) + component->driver->suspend(component); + component->suspended = 1; + if (component->regmap) + regcache_mark_dirty(component->regmap); + /* deactivate pins to sleep state */ + pinctrl_pm_select_sleep_state(component->dev); + break; + default: + dev_dbg(component->dev, + "ASoC: COMPONENT is on over suspend\n"); + break; + } + } + } + + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + if (rtd->dai_link->ignore_suspend) + continue; + + if (cpu_dai->driver->suspend && cpu_dai->driver->bus_control) + cpu_dai->driver->suspend(cpu_dai); + + /* deactivate pins to sleep state */ + pinctrl_pm_select_sleep_state(cpu_dai->dev); + } + + if (card->suspend_post) + card->suspend_post(card); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_suspend); + +/* deferred resume work, so resume can complete before we finished + * setting our codec back up, which can be very slow on I2C + */ +static void soc_resume_deferred(struct work_struct *work) +{ + struct snd_soc_card *card = + container_of(work, struct snd_soc_card, deferred_resume_work); + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; + int i; + + /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time, + * so userspace apps are blocked from touching us + */ + + dev_dbg(card->dev, "ASoC: starting resume work\n"); + + /* Bring us up into D2 so that DAPM starts enabling things */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2); + + if (card->resume_pre) + card->resume_pre(card); + + /* resume control bus DAIs */ + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + if (rtd->dai_link->ignore_suspend) + continue; + + if (cpu_dai->driver->resume && cpu_dai->driver->bus_control) + cpu_dai->driver->resume(cpu_dai); + } + + list_for_each_entry(component, &card->component_dev_list, card_list) { + if (component->suspended) { + if (component->driver->resume) + component->driver->resume(component); + component->suspended = 0; + } + } + + list_for_each_entry(rtd, &card->rtd_list, list) { + + if (rtd->dai_link->ignore_suspend) + continue; + + snd_soc_dapm_stream_event(rtd, + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_RESUME); + + snd_soc_dapm_stream_event(rtd, + SNDRV_PCM_STREAM_CAPTURE, + SND_SOC_DAPM_STREAM_RESUME); + } + + /* unmute any active DACs */ + list_for_each_entry(rtd, &card->rtd_list, list) { + + if (rtd->dai_link->ignore_suspend) + continue; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + struct snd_soc_dai_driver *drv = dai->driver; + + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 0); + } + } + + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + if (rtd->dai_link->ignore_suspend) + continue; + + if (cpu_dai->driver->resume && !cpu_dai->driver->bus_control) + cpu_dai->driver->resume(cpu_dai); + } + + if (card->resume_post) + card->resume_post(card); + + dev_dbg(card->dev, "ASoC: resume work completed\n"); + + /* Recheck all endpoints too, their state is affected by suspend */ + dapm_mark_endpoints_dirty(card); + snd_soc_dapm_sync(&card->dapm); + + /* userspace can access us now we are back as we were before */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0); +} + +/* powers up audio subsystem after a suspend */ +int snd_soc_resume(struct device *dev) +{ + struct snd_soc_card *card = dev_get_drvdata(dev); + bool bus_control = false; + struct snd_soc_pcm_runtime *rtd; + + /* If the card is not initialized yet there is nothing to do */ + if (!card->instantiated) + return 0; + + /* activate pins from sleep state */ + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai **codec_dais = rtd->codec_dais; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + + if (cpu_dai->active) + pinctrl_pm_select_default_state(cpu_dai->dev); + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = codec_dais[j]; + if (codec_dai->active) + pinctrl_pm_select_default_state(codec_dai->dev); + } + } + + /* + * DAIs that also act as the control bus master might have other drivers + * hanging off them so need to resume immediately. Other drivers don't + * have that problem and may take a substantial amount of time to resume + * due to I/O costs and anti-pop so handle them out of line. + */ + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + bus_control |= cpu_dai->driver->bus_control; + } + if (bus_control) { + dev_dbg(dev, "ASoC: Resuming control bus master immediately\n"); + soc_resume_deferred(&card->deferred_resume_work); + } else { + dev_dbg(dev, "ASoC: Scheduling resume work\n"); + if (!schedule_work(&card->deferred_resume_work)) + dev_err(dev, "ASoC: resume work item may be lost\n"); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_resume); +#else +#define snd_soc_suspend NULL +#define snd_soc_resume NULL +#endif + +static const struct snd_soc_dai_ops null_dai_ops = { +}; + +static struct snd_soc_component *soc_find_component( + const struct device_node *of_node, const char *name) +{ + struct snd_soc_component *component; + + lockdep_assert_held(&client_mutex); + + list_for_each_entry(component, &component_list, list) { + if (of_node) { + if (component->dev->of_node == of_node) + return component; + } else if (strcmp(component->name, name) == 0) { + return component; + } + } + + return NULL; +} + +/** + * snd_soc_find_dai - Find a registered DAI + * + * @dlc: name of the DAI or the DAI driver and optional component info to match + * + * This function will search all registered components and their DAIs to + * find the DAI of the same name. The component's of_node and name + * should also match if being specified. + * + * Return: pointer of DAI, or NULL if not found. + */ +struct snd_soc_dai *snd_soc_find_dai( + const struct snd_soc_dai_link_component *dlc) +{ + struct snd_soc_component *component; + struct snd_soc_dai *dai; + struct device_node *component_of_node; + + lockdep_assert_held(&client_mutex); + + /* Find CPU DAI from registered DAIs*/ + list_for_each_entry(component, &component_list, list) { + component_of_node = component->dev->of_node; + if (!component_of_node && component->dev->parent) + component_of_node = component->dev->parent->of_node; + + if (dlc->of_node && component_of_node != dlc->of_node) + continue; + if (dlc->name && strcmp(component->name, dlc->name)) + continue; + list_for_each_entry(dai, &component->dai_list, list) { + if (dlc->dai_name && strcmp(dai->name, dlc->dai_name) + && (!dai->driver->name + || strcmp(dai->driver->name, dlc->dai_name))) + continue; + + return dai; + } + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_find_dai); + + +/** + * snd_soc_find_dai_link - Find a DAI link + * + * @card: soc card + * @id: DAI link ID to match + * @name: DAI link name to match, optional + * @stream_name: DAI link stream name to match, optional + * + * This function will search all existing DAI links of the soc card to + * find the link of the same ID. Since DAI links may not have their + * unique ID, so name and stream name should also match if being + * specified. + * + * Return: pointer of DAI link, or NULL if not found. + */ +struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card, + int id, const char *name, + const char *stream_name) +{ + struct snd_soc_dai_link *link, *_link; + + lockdep_assert_held(&client_mutex); + + list_for_each_entry_safe(link, _link, &card->dai_link_list, list) { + if (link->id != id) + continue; + + if (name && (!link->name || strcmp(name, link->name))) + continue; + + if (stream_name && (!link->stream_name + || strcmp(stream_name, link->stream_name))) + continue; + + return link; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_find_dai_link); + +static bool soc_is_dai_link_bound(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) +{ + struct snd_soc_pcm_runtime *rtd; + + list_for_each_entry(rtd, &card->rtd_list, list) { + if (rtd->dai_link == dai_link) + return true; + } + + return false; +} + +static int soc_bind_dai_link(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai_link_component *codecs = dai_link->codecs; + struct snd_soc_dai_link_component cpu_dai_component; + struct snd_soc_component *component; + struct snd_soc_dai **codec_dais; + struct device_node *platform_of_node; + const char *platform_name; + int i; + + if (dai_link->ignore) + return 0; + + dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name); + + if (soc_is_dai_link_bound(card, dai_link)) { + dev_dbg(card->dev, "ASoC: dai link %s already bound\n", + dai_link->name); + return 0; + } + + rtd = soc_new_pcm_runtime(card, dai_link); + if (!rtd) + return -ENOMEM; + + cpu_dai_component.name = dai_link->cpu_name; + cpu_dai_component.of_node = dai_link->cpu_of_node; + cpu_dai_component.dai_name = dai_link->cpu_dai_name; + rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component); + if (!rtd->cpu_dai) { + dev_info(card->dev, "ASoC: CPU DAI %s not registered\n", + dai_link->cpu_dai_name); + goto _err_defer; + } + snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component); + + rtd->num_codecs = dai_link->num_codecs; + + /* Find CODEC from registered CODECs */ + codec_dais = rtd->codec_dais; + for (i = 0; i < rtd->num_codecs; i++) { + codec_dais[i] = snd_soc_find_dai(&codecs[i]); + if (!codec_dais[i]) { + dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n", + codecs[i].dai_name); + goto _err_defer; + } + snd_soc_rtdcom_add(rtd, codec_dais[i]->component); + } + + /* Single codec links expect codec and codec_dai in runtime data */ + rtd->codec_dai = codec_dais[0]; + + /* if there's no platform we match on the empty platform */ + platform_name = dai_link->platform_name; + if (!platform_name && !dai_link->platform_of_node) + platform_name = "snd-soc-dummy"; + + /* find one from the set of registered platforms */ + list_for_each_entry(component, &component_list, list) { + platform_of_node = component->dev->of_node; + if (!platform_of_node && component->dev->parent->of_node) + platform_of_node = component->dev->parent->of_node; + + if (dai_link->platform_of_node) { + if (platform_of_node != dai_link->platform_of_node) + continue; + } else { + if (strcmp(component->name, platform_name)) + continue; + } + + snd_soc_rtdcom_add(rtd, component); + } + + soc_add_pcm_runtime(card, rtd); + return 0; + +_err_defer: + soc_free_pcm_runtime(rtd); + return -EPROBE_DEFER; +} + +static void soc_remove_component(struct snd_soc_component *component) +{ + if (!component->card) + return; + + list_del(&component->card_list); + + if (component->driver->remove) + component->driver->remove(component); + + snd_soc_dapm_free(snd_soc_component_get_dapm(component)); + + soc_cleanup_component_debugfs(component); + component->card = NULL; + module_put(component->dev->driver->owner); +} + +static void soc_remove_dai(struct snd_soc_dai *dai, int order) +{ + int err; + + if (dai && dai->probed && + dai->driver->remove_order == order) { + if (dai->driver->remove) { + err = dai->driver->remove(dai); + if (err < 0) + dev_err(dai->dev, + "ASoC: failed to remove %s: %d\n", + dai->name, err); + } + dai->probed = 0; + } +} + +static void soc_remove_link_dais(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd, int order) +{ + int i; + + /* unregister the rtd device */ + if (rtd->dev_registered) { + device_unregister(rtd->dev); + rtd->dev_registered = 0; + } + + /* remove the CODEC DAI */ + for (i = 0; i < rtd->num_codecs; i++) + soc_remove_dai(rtd->codec_dais[i], order); + + soc_remove_dai(rtd->cpu_dai, order); +} + +static void soc_remove_link_components(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd, int order) +{ + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (component->driver->remove_order == order) + soc_remove_component(component); + } +} + +static void soc_remove_dai_links(struct snd_soc_card *card) +{ + int order; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai_link *link, *_link; + + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + list_for_each_entry(rtd, &card->rtd_list, list) + soc_remove_link_dais(card, rtd, order); + } + + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + list_for_each_entry(rtd, &card->rtd_list, list) + soc_remove_link_components(card, rtd, order); + } + + list_for_each_entry_safe(link, _link, &card->dai_link_list, list) { + if (link->dobj.type == SND_SOC_DOBJ_DAI_LINK) + dev_warn(card->dev, "Topology forgot to remove link %s?\n", + link->name); + + list_del(&link->list); + card->num_dai_links--; + } +} + +static int snd_soc_init_multicodec(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) +{ + /* Legacy codec/codec_dai link is a single entry in multicodec */ + if (dai_link->codec_name || dai_link->codec_of_node || + dai_link->codec_dai_name) { + dai_link->num_codecs = 1; + + dai_link->codecs = devm_kzalloc(card->dev, + sizeof(struct snd_soc_dai_link_component), + GFP_KERNEL); + if (!dai_link->codecs) + return -ENOMEM; + + dai_link->codecs[0].name = dai_link->codec_name; + dai_link->codecs[0].of_node = dai_link->codec_of_node; + dai_link->codecs[0].dai_name = dai_link->codec_dai_name; + } + + if (!dai_link->codecs) { + dev_err(card->dev, "ASoC: DAI link has no CODECs\n"); + return -EINVAL; + } + + return 0; +} + +static int soc_init_dai_link(struct snd_soc_card *card, + struct snd_soc_dai_link *link) +{ + int i, ret; + + ret = snd_soc_init_multicodec(card, link); + if (ret) { + dev_err(card->dev, "ASoC: failed to init multicodec\n"); + return ret; + } + + for (i = 0; i < link->num_codecs; i++) { + /* + * Codec must be specified by 1 of name or OF node, + * not both or neither. + */ + if (!!link->codecs[i].name == + !!link->codecs[i].of_node) { + dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n", + link->name); + return -EINVAL; + } + /* Codec DAI name must be specified */ + if (!link->codecs[i].dai_name) { + dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n", + link->name); + return -EINVAL; + } + } + + /* + * Platform may be specified by either name or OF node, but + * can be left unspecified, and a dummy platform will be used. + */ + if (link->platform_name && link->platform_of_node) { + dev_err(card->dev, + "ASoC: Both platform name/of_node are set for %s\n", + link->name); + return -EINVAL; + } + + /* + * CPU device may be specified by either name or OF node, but + * can be left unspecified, and will be matched based on DAI + * name alone.. + */ + if (link->cpu_name && link->cpu_of_node) { + dev_err(card->dev, + "ASoC: Neither/both cpu name/of_node are set for %s\n", + link->name); + return -EINVAL; + } + /* + * At least one of CPU DAI name or CPU device name/node must be + * specified + */ + if (!link->cpu_dai_name && + !(link->cpu_name || link->cpu_of_node)) { + dev_err(card->dev, + "ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n", + link->name); + return -EINVAL; + } + + return 0; +} + +void snd_soc_disconnect_sync(struct device *dev) +{ + struct snd_soc_component *component = snd_soc_lookup_component(dev, NULL); + + if (!component || !component->card) + return; + + snd_card_disconnect_sync(component->card->snd_card); +} +EXPORT_SYMBOL_GPL(snd_soc_disconnect_sync); + +/** + * snd_soc_add_dai_link - Add a DAI link dynamically + * @card: The ASoC card to which the DAI link is added + * @dai_link: The new DAI link to add + * + * This function adds a DAI link to the ASoC card's link list. + * + * Note: Topology can use this API to add DAI links when probing the + * topology component. And machine drivers can still define static + * DAI links in dai_link array. + */ +int snd_soc_add_dai_link(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) +{ + if (dai_link->dobj.type + && dai_link->dobj.type != SND_SOC_DOBJ_DAI_LINK) { + dev_err(card->dev, "Invalid dai link type %d\n", + dai_link->dobj.type); + return -EINVAL; + } + + lockdep_assert_held(&client_mutex); + /* Notify the machine driver for extra initialization + * on the link created by topology. + */ + if (dai_link->dobj.type && card->add_dai_link) + card->add_dai_link(card, dai_link); + + list_add_tail(&dai_link->list, &card->dai_link_list); + card->num_dai_links++; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_add_dai_link); + +/** + * snd_soc_remove_dai_link - Remove a DAI link from the list + * @card: The ASoC card that owns the link + * @dai_link: The DAI link to remove + * + * This function removes a DAI link from the ASoC card's link list. + * + * For DAI links previously added by topology, topology should + * remove them by using the dobj embedded in the link. + */ +void snd_soc_remove_dai_link(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link) +{ + struct snd_soc_dai_link *link, *_link; + + if (dai_link->dobj.type + && dai_link->dobj.type != SND_SOC_DOBJ_DAI_LINK) { + dev_err(card->dev, "Invalid dai link type %d\n", + dai_link->dobj.type); + return; + } + + lockdep_assert_held(&client_mutex); + /* Notify the machine driver for extra destruction + * on the link created by topology. + */ + if (dai_link->dobj.type && card->remove_dai_link) + card->remove_dai_link(card, dai_link); + + list_for_each_entry_safe(link, _link, &card->dai_link_list, list) { + if (link == dai_link) { + list_del(&link->list); + card->num_dai_links--; + return; + } + } +} +EXPORT_SYMBOL_GPL(snd_soc_remove_dai_link); + +static void soc_set_of_name_prefix(struct snd_soc_component *component) +{ + struct device_node *component_of_node = component->dev->of_node; + const char *str; + int ret; + + if (!component_of_node && component->dev->parent) + component_of_node = component->dev->parent->of_node; + + ret = of_property_read_string(component_of_node, "sound-name-prefix", + &str); + if (!ret) + component->name_prefix = str; +} + +static void soc_set_name_prefix(struct snd_soc_card *card, + struct snd_soc_component *component) +{ + int i; + + for (i = 0; i < card->num_configs && card->codec_conf; i++) { + struct snd_soc_codec_conf *map = &card->codec_conf[i]; + struct device_node *component_of_node = component->dev->of_node; + + if (!component_of_node && component->dev->parent) + component_of_node = component->dev->parent->of_node; + + if (map->of_node && component_of_node != map->of_node) + continue; + if (map->dev_name && strcmp(component->name, map->dev_name)) + continue; + component->name_prefix = map->name_prefix; + return; + } + + /* + * If there is no configuration table or no match in the table, + * check if a prefix is provided in the node + */ + soc_set_of_name_prefix(component); +} + +static int soc_probe_component(struct snd_soc_card *card, + struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component); + struct snd_soc_dai *dai; + int ret; + + if (!strcmp(component->name, "snd-soc-dummy")) + return 0; + + if (component->card) { + if (component->card != card) { + dev_err(component->dev, + "Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n", + card->name, component->card->name); + return -ENODEV; + } + return 0; + } + + if (!try_module_get(component->dev->driver->owner)) + return -ENODEV; + + component->card = card; + dapm->card = card; + soc_set_name_prefix(card, component); + + soc_init_component_debugfs(component); + + if (component->driver->dapm_widgets) { + ret = snd_soc_dapm_new_controls(dapm, + component->driver->dapm_widgets, + component->driver->num_dapm_widgets); + + if (ret != 0) { + dev_err(component->dev, + "Failed to create new controls %d\n", ret); + goto err_probe; + } + } + + list_for_each_entry(dai, &component->dai_list, list) { + ret = snd_soc_dapm_new_dai_widgets(dapm, dai); + if (ret != 0) { + dev_err(component->dev, + "Failed to create DAI widgets %d\n", ret); + goto err_probe; + } + } + + if (component->driver->probe) { + ret = component->driver->probe(component); + if (ret < 0) { + dev_err(component->dev, + "ASoC: failed to probe component %d\n", ret); + goto err_probe; + } + + WARN(dapm->idle_bias_off && + dapm->bias_level != SND_SOC_BIAS_OFF, + "codec %s can not start from non-off bias with idle_bias_off==1\n", + component->name); + } + + /* machine specific init */ + if (component->init) { + ret = component->init(component); + if (ret < 0) { + dev_err(component->dev, + "Failed to do machine specific init %d\n", ret); + goto err_probe; + } + } + + if (component->driver->controls) + snd_soc_add_component_controls(component, + component->driver->controls, + component->driver->num_controls); + if (component->driver->dapm_routes) + snd_soc_dapm_add_routes(dapm, + component->driver->dapm_routes, + component->driver->num_dapm_routes); + + list_add(&dapm->list, &card->dapm_list); + list_add(&component->card_list, &card->component_dev_list); + + return 0; + +err_probe: + soc_cleanup_component_debugfs(component); + component->card = NULL; + module_put(component->dev->driver->owner); + + return ret; +} + +static void rtd_release(struct device *dev) +{ + kfree(dev); +} + +static int soc_post_component_init(struct snd_soc_pcm_runtime *rtd, + const char *name) +{ + int ret = 0; + + /* register the rtd device */ + rtd->dev = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!rtd->dev) + return -ENOMEM; + device_initialize(rtd->dev); + rtd->dev->parent = rtd->card->dev; + rtd->dev->release = rtd_release; + rtd->dev->groups = soc_dev_attr_groups; + dev_set_name(rtd->dev, "%s", name); + dev_set_drvdata(rtd->dev, rtd); + mutex_init(&rtd->pcm_mutex); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].be_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_PLAYBACK].fe_clients); + INIT_LIST_HEAD(&rtd->dpcm[SNDRV_PCM_STREAM_CAPTURE].fe_clients); + ret = device_add(rtd->dev); + if (ret < 0) { + /* calling put_device() here to free the rtd->dev */ + put_device(rtd->dev); + dev_err(rtd->card->dev, + "ASoC: failed to register runtime device: %d\n", ret); + return ret; + } + rtd->dev_registered = 1; + return 0; +} + +static int soc_probe_link_components(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd, + int order) +{ + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + int ret; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (component->driver->probe_order == order) { + ret = soc_probe_component(card, component); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int soc_probe_dai(struct snd_soc_dai *dai, int order) +{ + if (dai->probed || + dai->driver->probe_order != order) + return 0; + + if (dai->driver->probe) { + int ret = dai->driver->probe(dai); + if (ret < 0) { + dev_err(dai->dev, "ASoC: failed to probe DAI %s: %d\n", + dai->name, ret); + return ret; + } + } + + dai->probed = 1; + + return 0; +} + +static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais, + struct snd_soc_pcm_runtime *rtd) +{ + int i, ret = 0; + + for (i = 0; i < num_dais; ++i) { + struct snd_soc_dai_driver *drv = dais[i]->driver; + + if (!rtd->dai_link->no_pcm && drv->pcm_new) + ret = drv->pcm_new(rtd, dais[i]); + if (ret < 0) { + dev_err(dais[i]->dev, + "ASoC: Failed to bind %s with pcm device\n", + dais[i]->name); + return ret; + } + } + + return 0; +} + +static int soc_link_dai_widgets(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dapm_widget *sink, *source; + int ret; + + if (rtd->num_codecs > 1) + dev_warn(card->dev, "ASoC: Multiple codecs not supported yet\n"); + + /* link the DAI widgets */ + sink = codec_dai->playback_widget; + source = cpu_dai->capture_widget; + if (sink && source) { + ret = snd_soc_dapm_new_pcm(card, rtd, dai_link->params, + dai_link->num_params, + source, sink); + if (ret != 0) { + dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n", + sink->name, source->name, ret); + return ret; + } + } + + sink = cpu_dai->playback_widget; + source = codec_dai->capture_widget; + if (sink && source) { + ret = snd_soc_dapm_new_pcm(card, rtd, dai_link->params, + dai_link->num_params, + source, sink); + if (ret != 0) { + dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n", + sink->name, source->name, ret); + return ret; + } + } + + return 0; +} + +static int soc_probe_link_dais(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd, int order) +{ + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + int i, ret, num; + + dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n", + card->name, rtd->num, order); + + /* set default power off timeout */ + rtd->pmdown_time = pmdown_time; + + ret = soc_probe_dai(cpu_dai, order); + if (ret) + return ret; + + /* probe the CODEC DAI */ + for (i = 0; i < rtd->num_codecs; i++) { + ret = soc_probe_dai(rtd->codec_dais[i], order); + if (ret) + return ret; + } + + /* complete DAI probe during last probe */ + if (order != SND_SOC_COMP_ORDER_LAST) + return 0; + + /* do machine specific initialization */ + if (dai_link->init) { + ret = dai_link->init(rtd); + if (ret < 0) { + dev_err(card->dev, "ASoC: failed to init %s: %d\n", + dai_link->name, ret); + return ret; + } + } + + if (dai_link->dai_fmt) + snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt); + + ret = soc_post_component_init(rtd, dai_link->name); + if (ret) + return ret; + +#ifdef CONFIG_DEBUG_FS + /* add DPCM sysfs entries */ + if (dai_link->dynamic) + soc_dpcm_debugfs_add(rtd); +#endif + + num = rtd->num; + + /* + * most drivers will register their PCMs using DAI link ordering but + * topology based drivers can use the DAI link id field to set PCM + * device number and then use rtd + a base offset of the BEs. + */ + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->use_dai_pcm_id) + continue; + + if (rtd->dai_link->no_pcm) + num += component->driver->be_pcm_base; + else + num = rtd->dai_link->id; + } + + if (cpu_dai->driver->compress_new) { + /*create compress_device"*/ + ret = cpu_dai->driver->compress_new(rtd, num); + if (ret < 0) { + dev_err(card->dev, "ASoC: can't create compress %s\n", + dai_link->stream_name); + return ret; + } + } else { + + if (!dai_link->params) { + /* create the pcm */ + ret = soc_new_pcm(rtd, num); + if (ret < 0) { + dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", + dai_link->stream_name, ret); + return ret; + } + ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd); + if (ret < 0) + return ret; + ret = soc_link_dai_pcm_new(rtd->codec_dais, + rtd->num_codecs, rtd); + if (ret < 0) + return ret; + } else { + INIT_DELAYED_WORK(&rtd->delayed_work, + codec2codec_close_delayed_work); + + /* link the DAI widgets */ + ret = soc_link_dai_widgets(card, dai_link, rtd); + if (ret) + return ret; + } + } + + return 0; +} + +static int soc_bind_aux_dev(struct snd_soc_card *card, int num) +{ + struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num]; + struct snd_soc_component *component; + const char *name; + struct device_node *codec_of_node; + + if (aux_dev->codec_of_node || aux_dev->codec_name) { + /* codecs, usually analog devices */ + name = aux_dev->codec_name; + codec_of_node = aux_dev->codec_of_node; + component = soc_find_component(codec_of_node, name); + if (!component) { + if (codec_of_node) + name = of_node_full_name(codec_of_node); + goto err_defer; + } + } else if (aux_dev->name) { + /* generic components */ + name = aux_dev->name; + component = soc_find_component(NULL, name); + if (!component) + goto err_defer; + } else { + dev_err(card->dev, "ASoC: Invalid auxiliary device\n"); + return -EINVAL; + } + + component->init = aux_dev->init; + list_add(&component->card_aux_list, &card->aux_comp_list); + + return 0; + +err_defer: + dev_err(card->dev, "ASoC: %s not registered\n", name); + return -EPROBE_DEFER; +} + +static int soc_probe_aux_devices(struct snd_soc_card *card) +{ + struct snd_soc_component *comp; + int order; + int ret; + + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + list_for_each_entry(comp, &card->aux_comp_list, card_aux_list) { + if (comp->driver->probe_order == order) { + ret = soc_probe_component(card, comp); + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to probe aux component %s %d\n", + comp->name, ret); + return ret; + } + } + } + } + + return 0; +} + +static void soc_remove_aux_devices(struct snd_soc_card *card) +{ + struct snd_soc_component *comp, *_comp; + int order; + + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + list_for_each_entry_safe(comp, _comp, + &card->aux_comp_list, card_aux_list) { + + if (comp->driver->remove_order == order) { + soc_remove_component(comp); + /* remove it from the card's aux_comp_list */ + list_del(&comp->card_aux_list); + } + } + } +} + +/** + * snd_soc_runtime_set_dai_fmt() - Change DAI link format for a ASoC runtime + * @rtd: The runtime for which the DAI link format should be changed + * @dai_fmt: The new DAI link format + * + * This function updates the DAI link format for all DAIs connected to the DAI + * link for the specified runtime. + * + * Note: For setups with a static format set the dai_fmt field in the + * corresponding snd_dai_link struct instead of using this function. + * + * Returns 0 on success, otherwise a negative error code. + */ +int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, + unsigned int dai_fmt) +{ + struct snd_soc_dai **codec_dais = rtd->codec_dais; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int i; + int ret; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = codec_dais[i]; + + ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); + if (ret != 0 && ret != -ENOTSUPP) { + dev_warn(codec_dai->dev, + "ASoC: Failed to set DAI format: %d\n", ret); + return ret; + } + } + + /* Flip the polarity for the "CPU" end of a CODEC<->CODEC link */ + /* the component which has non_legacy_dai_naming is Codec */ + if (cpu_dai->component->driver->non_legacy_dai_naming) { + unsigned int inv_dai_fmt; + + inv_dai_fmt = dai_fmt & ~SND_SOC_DAIFMT_MASTER_MASK; + switch (dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + break; + case SND_SOC_DAIFMT_CBM_CFS: + inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFM; + break; + case SND_SOC_DAIFMT_CBS_CFM: + inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + break; + } + + dai_fmt = inv_dai_fmt; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt); + if (ret != 0 && ret != -ENOTSUPP) { + dev_warn(cpu_dai->dev, + "ASoC: Failed to set DAI format: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); + + +#ifdef CONFIG_DMI +/* Trim special characters, and replace '-' with '_' since '-' is used to + * separate different DMI fields in the card long name. Only number and + * alphabet characters and a few separator characters are kept. + */ +static void cleanup_dmi_name(char *name) +{ + int i, j = 0; + + for (i = 0; name[i]; i++) { + if (isalnum(name[i]) || (name[i] == '.') + || (name[i] == '_')) + name[j++] = name[i]; + else if (name[i] == '-') + name[j++] = '_'; + } + + name[j] = '\0'; +} + +/* Check if a DMI field is valid, i.e. not containing any string + * in the black list. + */ +static int is_dmi_valid(const char *field) +{ + int i = 0; + + while (dmi_blacklist[i]) { + if (strstr(field, dmi_blacklist[i])) + return 0; + i++; + } + + return 1; +} + +/** + * snd_soc_set_dmi_name() - Register DMI names to card + * @card: The card to register DMI names + * @flavour: The flavour "differentiator" for the card amongst its peers. + * + * An Intel machine driver may be used by many different devices but are + * difficult for userspace to differentiate, since machine drivers ususally + * use their own name as the card short name and leave the card long name + * blank. To differentiate such devices and fix bugs due to lack of + * device-specific configurations, this function allows DMI info to be used + * as the sound card long name, in the format of + * "vendor-product-version-board" + * (Character '-' is used to separate different DMI fields here). + * This will help the user space to load the device-specific Use Case Manager + * (UCM) configurations for the card. + * + * Possible card long names may be: + * DellInc.-XPS139343-01-0310JH + * ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA + * Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX + * + * This function also supports flavoring the card longname to provide + * the extra differentiation, like "vendor-product-version-board-flavor". + * + * We only keep number and alphabet characters and a few separator characters + * in the card long name since UCM in the user space uses the card long names + * as card configuration directory names and AudoConf cannot support special + * charactors like SPACE. + * + * Returns 0 on success, otherwise a negative error code. + */ +int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour) +{ + const char *vendor, *product, *product_version, *board; + size_t longname_buf_size = sizeof(card->snd_card->longname); + size_t len; + + if (card->long_name) + return 0; /* long name already set by driver or from DMI */ + + /* make up dmi long name as: vendor.product.version.board */ + vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + if (!vendor || !is_dmi_valid(vendor)) { + dev_warn(card->dev, "ASoC: no DMI vendor name!\n"); + return 0; + } + + + snprintf(card->dmi_longname, sizeof(card->snd_card->longname), + "%s", vendor); + cleanup_dmi_name(card->dmi_longname); + + product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (product && is_dmi_valid(product)) { + len = strlen(card->dmi_longname); + snprintf(card->dmi_longname + len, + longname_buf_size - len, + "-%s", product); + + len++; /* skip the separator "-" */ + if (len < longname_buf_size) + cleanup_dmi_name(card->dmi_longname + len); + + /* some vendors like Lenovo may only put a self-explanatory + * name in the product version field + */ + product_version = dmi_get_system_info(DMI_PRODUCT_VERSION); + if (product_version && is_dmi_valid(product_version)) { + len = strlen(card->dmi_longname); + snprintf(card->dmi_longname + len, + longname_buf_size - len, + "-%s", product_version); + + len++; + if (len < longname_buf_size) + cleanup_dmi_name(card->dmi_longname + len); + } + } + + board = dmi_get_system_info(DMI_BOARD_NAME); + if (board && is_dmi_valid(board)) { + len = strlen(card->dmi_longname); + snprintf(card->dmi_longname + len, + longname_buf_size - len, + "-%s", board); + + len++; + if (len < longname_buf_size) + cleanup_dmi_name(card->dmi_longname + len); + } else if (!product) { + /* fall back to using legacy name */ + dev_warn(card->dev, "ASoC: no DMI board/product name!\n"); + return 0; + } + + /* Add flavour to dmi long name */ + if (flavour) { + len = strlen(card->dmi_longname); + snprintf(card->dmi_longname + len, + longname_buf_size - len, + "-%s", flavour); + + len++; + if (len < longname_buf_size) + cleanup_dmi_name(card->dmi_longname + len); + } + + /* set the card long name */ + card->long_name = card->dmi_longname; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_set_dmi_name); +#endif /* CONFIG_DMI */ + +static void soc_check_tplg_fes(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + const struct snd_soc_component_driver *comp_drv; + struct snd_soc_dai_link *dai_link; + int i; + + list_for_each_entry(component, &component_list, list) { + + /* does this component override FEs ? */ + if (!component->driver->ignore_machine) + continue; + + /* for this machine ? */ + if (strcmp(component->driver->ignore_machine, + card->dev->driver->name)) + continue; + + /* machine matches, so override the rtd data */ + for (i = 0; i < card->num_links; i++) { + + dai_link = &card->dai_link[i]; + + /* ignore this FE */ + if (dai_link->dynamic) { + dai_link->ignore = true; + continue; + } + + dev_info(card->dev, "info: override FE DAI link %s\n", + card->dai_link[i].name); + + /* override platform component */ + dai_link->platform_name = component->name; + + /* convert non BE into BE */ + if (!dai_link->no_pcm) { + dai_link->no_pcm = 1; + + if (dai_link->dpcm_playback) + dev_warn(card->dev, + "invalid configuration, dailink %s has flags no_pcm=0 and dpcm_playback=1\n", + dai_link->name); + if (dai_link->dpcm_capture) + dev_warn(card->dev, + "invalid configuration, dailink %s has flags no_pcm=0 and dpcm_capture=1\n", + dai_link->name); + + /* convert normal link into DPCM one */ + if (!(dai_link->dpcm_playback || + dai_link->dpcm_capture)) { + dai_link->dpcm_playback = !dai_link->capture_only; + dai_link->dpcm_capture = !dai_link->playback_only; + } + } + + /* override any BE fixups */ + dai_link->be_hw_params_fixup = + component->driver->be_hw_params_fixup; + + /* most BE links don't set stream name, so set it to + * dai link name if it's NULL to help bind widgets. + */ + if (!dai_link->stream_name) + dai_link->stream_name = dai_link->name; + } + + /* Inform userspace we are using alternate topology */ + if (component->driver->topology_name_prefix) { + + /* topology shortname created ? */ + if (!card->topology_shortname_created) { + comp_drv = component->driver; + + snprintf(card->topology_shortname, 32, "%s-%s", + comp_drv->topology_name_prefix, + card->name); + card->topology_shortname_created = true; + } + + /* use topology shortname */ + card->name = card->topology_shortname; + } + } +} + +static int snd_soc_instantiate_card(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai_link *dai_link; + int ret, i, order; + + mutex_lock(&client_mutex); + mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT); + + /* check whether any platform is ignore machine FE and using topology */ + soc_check_tplg_fes(card); + + /* bind DAIs */ + for (i = 0; i < card->num_links; i++) { + ret = soc_bind_dai_link(card, &card->dai_link[i]); + if (ret != 0) + goto base_error; + } + + /* bind aux_devs too */ + for (i = 0; i < card->num_aux_devs; i++) { + ret = soc_bind_aux_dev(card, i); + if (ret != 0) + goto base_error; + } + + /* add predefined DAI links to the list */ + for (i = 0; i < card->num_links; i++) + snd_soc_add_dai_link(card, card->dai_link+i); + + /* card bind complete so register a sound card */ + ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + card->owner, 0, &card->snd_card); + if (ret < 0) { + dev_err(card->dev, + "ASoC: can't create sound card for card %s: %d\n", + card->name, ret); + goto base_error; + } + + soc_init_card_debugfs(card); + + card->dapm.bias_level = SND_SOC_BIAS_OFF; + card->dapm.dev = card->dev; + card->dapm.card = card; + list_add(&card->dapm.list, &card->dapm_list); + +#ifdef CONFIG_DEBUG_FS + snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); +#endif + +#ifdef CONFIG_PM_SLEEP + /* deferred resume work */ + INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); +#endif + + if (card->dapm_widgets) + snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, + card->num_dapm_widgets); + + if (card->of_dapm_widgets) + snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets, + card->num_of_dapm_widgets); + + /* initialise the sound card only once */ + if (card->probe) { + ret = card->probe(card); + if (ret < 0) + goto card_probe_error; + } + + /* probe all components used by DAI links on this card */ + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + list_for_each_entry(rtd, &card->rtd_list, list) { + ret = soc_probe_link_components(card, rtd, order); + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to instantiate card %d\n", + ret); + goto probe_dai_err; + } + } + } + + /* probe auxiliary components */ + ret = soc_probe_aux_devices(card); + if (ret < 0) + goto probe_dai_err; + + /* Find new DAI links added during probing components and bind them. + * Components with topology may bring new DAIs and DAI links. + */ + list_for_each_entry(dai_link, &card->dai_link_list, list) { + if (soc_is_dai_link_bound(card, dai_link)) + continue; + + ret = soc_init_dai_link(card, dai_link); + if (ret) + goto probe_dai_err; + ret = soc_bind_dai_link(card, dai_link); + if (ret) + goto probe_dai_err; + } + + /* probe all DAI links on this card */ + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + list_for_each_entry(rtd, &card->rtd_list, list) { + ret = soc_probe_link_dais(card, rtd, order); + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to instantiate card %d\n", + ret); + goto probe_dai_err; + } + } + } + + snd_soc_dapm_link_dai_widgets(card); + snd_soc_dapm_connect_dai_link_widgets(card); + + if (card->controls) + snd_soc_add_card_controls(card, card->controls, card->num_controls); + + if (card->dapm_routes) + snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, + card->num_dapm_routes); + + if (card->of_dapm_routes) + snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, + card->num_of_dapm_routes); + + /* try to set some sane longname if DMI is available */ + snd_soc_set_dmi_name(card, NULL); + + snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), + "%s", card->name); + snprintf(card->snd_card->longname, sizeof(card->snd_card->longname), + "%s", card->long_name ? card->long_name : card->name); + snprintf(card->snd_card->driver, sizeof(card->snd_card->driver), + "%s", card->driver_name ? card->driver_name : card->name); + for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) { + switch (card->snd_card->driver[i]) { + case '_': + case '-': + case '\0': + break; + default: + if (!isalnum(card->snd_card->driver[i])) + card->snd_card->driver[i] = '_'; + break; + } + } + + if (card->late_probe) { + ret = card->late_probe(card); + if (ret < 0) { + dev_err(card->dev, "ASoC: %s late_probe() failed: %d\n", + card->name, ret); + goto probe_aux_dev_err; + } + } + + snd_soc_dapm_new_widgets(card); + + ret = snd_card_register(card->snd_card); + if (ret < 0) { + dev_err(card->dev, "ASoC: failed to register soundcard %d\n", + ret); + goto probe_aux_dev_err; + } + + card->instantiated = 1; + dapm_mark_endpoints_dirty(card); + snd_soc_dapm_sync(&card->dapm); + mutex_unlock(&card->mutex); + mutex_unlock(&client_mutex); + + return 0; + +probe_aux_dev_err: + soc_remove_aux_devices(card); + +probe_dai_err: + soc_remove_dai_links(card); + +card_probe_error: + if (card->remove) + card->remove(card); + + snd_soc_dapm_free(&card->dapm); + soc_cleanup_card_debugfs(card); + snd_card_free(card->snd_card); + +base_error: + soc_remove_pcm_runtimes(card); + mutex_unlock(&card->mutex); + mutex_unlock(&client_mutex); + + return ret; +} + +/* probes a new socdev */ +static int soc_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + /* + * no card, so machine driver should be registering card + * we should not be here in that case so ret error + */ + if (!card) + return -EINVAL; + + dev_warn(&pdev->dev, + "ASoC: machine %s should use snd_soc_register_card()\n", + card->name); + + /* Bodge while we unpick instantiation */ + card->dev = &pdev->dev; + + return snd_soc_register_card(card); +} + +static int soc_cleanup_card_resources(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + + /* make sure any delayed work runs */ + list_for_each_entry(rtd, &card->rtd_list, list) + flush_delayed_work(&rtd->delayed_work); + + /* free the ALSA card at first; this syncs with pending operations */ + snd_card_free(card->snd_card); + + /* remove and free each DAI */ + soc_remove_dai_links(card); + soc_remove_pcm_runtimes(card); + + /* remove auxiliary devices */ + soc_remove_aux_devices(card); + + snd_soc_dapm_free(&card->dapm); + soc_cleanup_card_debugfs(card); + + /* remove the card */ + if (card->remove) + card->remove(card); + + return 0; +} + +/* removes a socdev */ +static int soc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + return 0; +} + +int snd_soc_poweroff(struct device *dev) +{ + struct snd_soc_card *card = dev_get_drvdata(dev); + struct snd_soc_pcm_runtime *rtd; + + if (!card->instantiated) + return 0; + + /* Flush out pmdown_time work - we actually do want to run it + * now, we're shutting down so no imminent restart. */ + list_for_each_entry(rtd, &card->rtd_list, list) + flush_delayed_work(&rtd->delayed_work); + + snd_soc_dapm_shutdown(card); + + /* deactivate pins to sleep state */ + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i; + + pinctrl_pm_select_sleep_state(cpu_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + pinctrl_pm_select_sleep_state(codec_dai->dev); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_poweroff); + +const struct dev_pm_ops snd_soc_pm_ops = { + .suspend = snd_soc_suspend, + .resume = snd_soc_resume, + .freeze = snd_soc_suspend, + .thaw = snd_soc_resume, + .poweroff = snd_soc_poweroff, + .restore = snd_soc_resume, +}; +EXPORT_SYMBOL_GPL(snd_soc_pm_ops); + +/* ASoC platform driver */ +static struct platform_driver soc_driver = { + .driver = { + .name = "soc-audio", + .pm = &snd_soc_pm_ops, + }, + .probe = soc_probe, + .remove = soc_remove, +}; + +/** + * snd_soc_cnew - create new control + * @_template: control template + * @data: control private data + * @long_name: control long name + * @prefix: control name prefix + * + * Create a new mixer control from a template control. + * + * Returns 0 for success, else error. + */ +struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, + void *data, const char *long_name, + const char *prefix) +{ + struct snd_kcontrol_new template; + struct snd_kcontrol *kcontrol; + char *name = NULL; + + memcpy(&template, _template, sizeof(template)); + template.index = 0; + + if (!long_name) + long_name = template.name; + + if (prefix) { + name = kasprintf(GFP_KERNEL, "%s %s", prefix, long_name); + if (!name) + return NULL; + + template.name = name; + } else { + template.name = long_name; + } + + kcontrol = snd_ctl_new1(&template, data); + + kfree(name); + + return kcontrol; +} +EXPORT_SYMBOL_GPL(snd_soc_cnew); + +static int snd_soc_add_controls(struct snd_card *card, struct device *dev, + const struct snd_kcontrol_new *controls, int num_controls, + const char *prefix, void *data) +{ + int err, i; + + for (i = 0; i < num_controls; i++) { + const struct snd_kcontrol_new *control = &controls[i]; + err = snd_ctl_add(card, snd_soc_cnew(control, data, + control->name, prefix)); + if (err < 0) { + dev_err(dev, "ASoC: Failed to add %s: %d\n", + control->name, err); + return err; + } + } + + return 0; +} + +struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, + const char *name) +{ + struct snd_card *card = soc_card->snd_card; + struct snd_kcontrol *kctl; + + if (unlikely(!name)) + return NULL; + + list_for_each_entry(kctl, &card->controls, list) + if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) + return kctl; + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_card_get_kcontrol); + +/** + * snd_soc_add_component_controls - Add an array of controls to a component. + * + * @component: Component to add controls to + * @controls: Array of controls to add + * @num_controls: Number of elements in the array + * + * Return: 0 for success, else error. + */ +int snd_soc_add_component_controls(struct snd_soc_component *component, + const struct snd_kcontrol_new *controls, unsigned int num_controls) +{ + struct snd_card *card = component->card->snd_card; + + return snd_soc_add_controls(card, component->dev, controls, + num_controls, component->name_prefix, component); +} +EXPORT_SYMBOL_GPL(snd_soc_add_component_controls); + +/** + * snd_soc_add_card_controls - add an array of controls to a SoC card. + * Convenience function to add a list of controls. + * + * @soc_card: SoC card to add controls to + * @controls: array of controls to add + * @num_controls: number of elements in the array + * + * Return 0 for success, else error. + */ +int snd_soc_add_card_controls(struct snd_soc_card *soc_card, + const struct snd_kcontrol_new *controls, int num_controls) +{ + struct snd_card *card = soc_card->snd_card; + + return snd_soc_add_controls(card, soc_card->dev, controls, num_controls, + NULL, soc_card); +} +EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); + +/** + * snd_soc_add_dai_controls - add an array of controls to a DAI. + * Convienience function to add a list of controls. + * + * @dai: DAI to add controls to + * @controls: array of controls to add + * @num_controls: number of elements in the array + * + * Return 0 for success, else error. + */ +int snd_soc_add_dai_controls(struct snd_soc_dai *dai, + const struct snd_kcontrol_new *controls, int num_controls) +{ + struct snd_card *card = dai->component->card->snd_card; + + return snd_soc_add_controls(card, dai->dev, controls, num_controls, + NULL, dai); +} +EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls); + +/** + * snd_soc_dai_set_sysclk - configure DAI system or master clock. + * @dai: DAI + * @clk_id: DAI specific clock ID + * @freq: new clock frequency in Hz + * @dir: new clock direction - input/output. + * + * Configures the DAI master (MCLK) or system (SYSCLK) clocking. + */ +int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + if (dai->driver->ops->set_sysclk) + return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir); + + return snd_soc_component_set_sysclk(dai->component, clk_id, 0, + freq, dir); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); + +/** + * snd_soc_component_set_sysclk - configure COMPONENT system or master clock. + * @component: COMPONENT + * @clk_id: DAI specific clock ID + * @source: Source for the clock + * @freq: new clock frequency in Hz + * @dir: new clock direction - input/output. + * + * Configures the CODEC master (MCLK) or system (SYSCLK) clocking. + */ +int snd_soc_component_set_sysclk(struct snd_soc_component *component, int clk_id, + int source, unsigned int freq, int dir) +{ + if (component->driver->set_sysclk) + return component->driver->set_sysclk(component, clk_id, source, + freq, dir); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_component_set_sysclk); + +/** + * snd_soc_dai_set_clkdiv - configure DAI clock dividers. + * @dai: DAI + * @div_id: DAI specific clock divider ID + * @div: new clock divisor. + * + * Configures the clock dividers. This is used to derive the best DAI bit and + * frame clocks from the system or master clock. It's best to set the DAI bit + * and frame clocks as low as possible to save system power. + */ +int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, + int div_id, int div) +{ + if (dai->driver->ops->set_clkdiv) + return dai->driver->ops->set_clkdiv(dai, div_id, div); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); + +/** + * snd_soc_dai_set_pll - configure DAI PLL. + * @dai: DAI + * @pll_id: DAI specific PLL ID + * @source: DAI specific source for the PLL + * @freq_in: PLL input clock frequency in Hz + * @freq_out: requested PLL output clock frequency in Hz + * + * Configures and enables PLL to generate output clock based on input clock. + */ +int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + if (dai->driver->ops->set_pll) + return dai->driver->ops->set_pll(dai, pll_id, source, + freq_in, freq_out); + + return snd_soc_component_set_pll(dai->component, pll_id, source, + freq_in, freq_out); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); + +/* + * snd_soc_component_set_pll - configure component PLL. + * @component: COMPONENT + * @pll_id: DAI specific PLL ID + * @source: DAI specific source for the PLL + * @freq_in: PLL input clock frequency in Hz + * @freq_out: requested PLL output clock frequency in Hz + * + * Configures and enables PLL to generate output clock based on input clock. + */ +int snd_soc_component_set_pll(struct snd_soc_component *component, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + if (component->driver->set_pll) + return component->driver->set_pll(component, pll_id, source, + freq_in, freq_out); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_component_set_pll); + +/** + * snd_soc_dai_set_bclk_ratio - configure BCLK to sample rate ratio. + * @dai: DAI + * @ratio: Ratio of BCLK to Sample rate. + * + * Configures the DAI for a preset BCLK to sample rate ratio. + */ +int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + if (dai->driver->ops->set_bclk_ratio) + return dai->driver->ops->set_bclk_ratio(dai, ratio); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio); + +/** + * snd_soc_dai_set_fmt - configure DAI hardware audio format. + * @dai: DAI + * @fmt: SND_SOC_DAIFMT_* format value. + * + * Configures the DAI hardware format and clocking. + */ +int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + if (dai->driver == NULL) + return -EINVAL; + if (dai->driver->ops->set_fmt == NULL) + return -ENOTSUPP; + return dai->driver->ops->set_fmt(dai, fmt); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); + +/** + * snd_soc_xlate_tdm_slot - generate tx/rx slot mask. + * @slots: Number of slots in use. + * @tx_mask: bitmask representing active TX slots. + * @rx_mask: bitmask representing active RX slots. + * + * Generates the TDM tx and rx slot default masks for DAI. + */ +static int snd_soc_xlate_tdm_slot_mask(unsigned int slots, + unsigned int *tx_mask, + unsigned int *rx_mask) +{ + if (*tx_mask || *rx_mask) + return 0; + + if (!slots) + return -EINVAL; + + *tx_mask = (1 << slots) - 1; + *rx_mask = (1 << slots) - 1; + + return 0; +} + +/** + * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation + * @dai: The DAI to configure + * @tx_mask: bitmask representing active TX slots. + * @rx_mask: bitmask representing active RX slots. + * @slots: Number of slots in use. + * @slot_width: Width in bits for each slot. + * + * This function configures the specified DAI for TDM operation. @slot contains + * the total number of slots of the TDM stream and @slot_with the width of each + * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the + * active slots of the TDM stream for the specified DAI, i.e. which slots the + * DAI should write to or read from. If a bit is set the corresponding slot is + * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to + * the first slot, bit 1 to the second slot and so on. The first active slot + * maps to the first channel of the DAI, the second active slot to the second + * channel and so on. + * + * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask, + * @rx_mask and @slot_width will be ignored. + * + * Returns 0 on success, a negative error code otherwise. + */ +int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + if (dai->driver->ops->xlate_tdm_slot_mask) + dai->driver->ops->xlate_tdm_slot_mask(slots, + &tx_mask, &rx_mask); + else + snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask); + + dai->tx_mask = tx_mask; + dai->rx_mask = rx_mask; + + if (dai->driver->ops->set_tdm_slot) + return dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask, + slots, slot_width); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); + +/** + * snd_soc_dai_set_channel_map - configure DAI audio channel map + * @dai: DAI + * @tx_num: how many TX channels + * @tx_slot: pointer to an array which imply the TX slot number channel + * 0~num-1 uses + * @rx_num: how many RX channels + * @rx_slot: pointer to an array which imply the RX slot number channel + * 0~num-1 uses + * + * configure the relationship between channel number and TDM slot number. + */ +int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + if (dai->driver->ops->set_channel_map) + return dai->driver->ops->set_channel_map(dai, tx_num, tx_slot, + rx_num, rx_slot); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); + +/** + * snd_soc_dai_get_channel_map - Get DAI audio channel map + * @dai: DAI + * @tx_num: how many TX channels + * @tx_slot: pointer to an array which imply the TX slot number channel + * 0~num-1 uses + * @rx_num: how many RX channels + * @rx_slot: pointer to an array which imply the RX slot number channel + * 0~num-1 uses + */ +int snd_soc_dai_get_channel_map(struct snd_soc_dai *dai, + unsigned int *tx_num, unsigned int *tx_slot, + unsigned int *rx_num, unsigned int *rx_slot) +{ + if (dai->driver->ops->get_channel_map) + return dai->driver->ops->get_channel_map(dai, tx_num, tx_slot, + rx_num, rx_slot); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map); + +/** + * snd_soc_dai_set_tristate - configure DAI system or master clock. + * @dai: DAI + * @tristate: tristate enable + * + * Tristates the DAI so that others can use it. + */ +int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + if (dai->driver->ops->set_tristate) + return dai->driver->ops->set_tristate(dai, tristate); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); + +/** + * snd_soc_dai_digital_mute - configure DAI system or master clock. + * @dai: DAI + * @mute: mute enable + * @direction: stream to mute + * + * Mutes the DAI DAC. + */ +int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute, + int direction) +{ + if (!dai->driver) + return -ENOTSUPP; + + if (dai->driver->ops->mute_stream) + return dai->driver->ops->mute_stream(dai, mute, direction); + else if (direction == SNDRV_PCM_STREAM_PLAYBACK && + dai->driver->ops->digital_mute) + return dai->driver->ops->digital_mute(dai, mute); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); + +/** + * snd_soc_register_card - Register a card with the ASoC core + * + * @card: Card to register + * + */ +int snd_soc_register_card(struct snd_soc_card *card) +{ + int i, ret; + struct snd_soc_pcm_runtime *rtd; + + if (!card->name || !card->dev) + return -EINVAL; + + for (i = 0; i < card->num_links; i++) { + struct snd_soc_dai_link *link = &card->dai_link[i]; + + ret = soc_init_dai_link(card, link); + if (ret) { + dev_err(card->dev, "ASoC: failed to init link %s\n", + link->name); + return ret; + } + } + + dev_set_drvdata(card->dev, card); + + snd_soc_initialize_card_lists(card); + + INIT_LIST_HEAD(&card->dai_link_list); + card->num_dai_links = 0; + + INIT_LIST_HEAD(&card->rtd_list); + card->num_rtd = 0; + + INIT_LIST_HEAD(&card->dapm_dirty); + INIT_LIST_HEAD(&card->dobj_list); + card->instantiated = 0; + mutex_init(&card->mutex); + mutex_init(&card->dapm_mutex); + spin_lock_init(&card->dpcm_lock); + + ret = snd_soc_instantiate_card(card); + if (ret != 0) + return ret; + + /* deactivate pins to sleep state */ + list_for_each_entry(rtd, &card->rtd_list, list) { + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int j; + + for (j = 0; j < rtd->num_codecs; j++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[j]; + if (!codec_dai->active) + pinctrl_pm_select_sleep_state(codec_dai->dev); + } + + if (!cpu_dai->active) + pinctrl_pm_select_sleep_state(cpu_dai->dev); + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_register_card); + +/** + * snd_soc_unregister_card - Unregister a card with the ASoC core + * + * @card: Card to unregister + * + */ +int snd_soc_unregister_card(struct snd_soc_card *card) +{ + if (card->instantiated) { + card->instantiated = false; + snd_soc_dapm_shutdown(card); + soc_cleanup_card_resources(card); + dev_dbg(card->dev, "ASoC: Unregistered card '%s'\n", card->name); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_unregister_card); + +/* + * Simplify DAI link configuration by removing ".-1" from device names + * and sanitizing names. + */ +static char *fmt_single_name(struct device *dev, int *id) +{ + char *found, name[NAME_SIZE]; + int id1, id2; + + if (dev_name(dev) == NULL) + return NULL; + + strlcpy(name, dev_name(dev), NAME_SIZE); + + /* are we a "%s.%d" name (platform and SPI components) */ + found = strstr(name, dev->driver->name); + if (found) { + /* get ID */ + if (sscanf(&found[strlen(dev->driver->name)], ".%d", id) == 1) { + + /* discard ID from name if ID == -1 */ + if (*id == -1) + found[strlen(dev->driver->name)] = '\0'; + } + + } else { + /* I2C component devices are named "bus-addr" */ + if (sscanf(name, "%x-%x", &id1, &id2) == 2) { + char tmp[NAME_SIZE]; + + /* create unique ID number from I2C addr and bus */ + *id = ((id1 & 0xffff) << 16) + id2; + + /* sanitize component name for DAI link creation */ + snprintf(tmp, NAME_SIZE, "%s.%s", dev->driver->name, name); + strlcpy(name, tmp, NAME_SIZE); + } else + *id = 0; + } + + return kstrdup(name, GFP_KERNEL); +} + +/* + * Simplify DAI link naming for single devices with multiple DAIs by removing + * any ".-1" and using the DAI name (instead of device name). + */ +static inline char *fmt_multiple_name(struct device *dev, + struct snd_soc_dai_driver *dai_drv) +{ + if (dai_drv->name == NULL) { + dev_err(dev, + "ASoC: error - multiple DAI %s registered with no name\n", + dev_name(dev)); + return NULL; + } + + return kstrdup(dai_drv->name, GFP_KERNEL); +} + +/** + * snd_soc_unregister_dai - Unregister DAIs from the ASoC core + * + * @component: The component for which the DAIs should be unregistered + */ +static void snd_soc_unregister_dais(struct snd_soc_component *component) +{ + struct snd_soc_dai *dai, *_dai; + + list_for_each_entry_safe(dai, _dai, &component->dai_list, list) { + dev_dbg(component->dev, "ASoC: Unregistered DAI '%s'\n", + dai->name); + list_del(&dai->list); + kfree(dai->name); + kfree(dai); + } +} + +/* Create a DAI and add it to the component's DAI list */ +static struct snd_soc_dai *soc_add_dai(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + bool legacy_dai_naming) +{ + struct device *dev = component->dev; + struct snd_soc_dai *dai; + + dev_dbg(dev, "ASoC: dynamically register DAI %s\n", dev_name(dev)); + + dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); + if (dai == NULL) + return NULL; + + /* + * Back in the old days when we still had component-less DAIs, + * instead of having a static name, component-less DAIs would + * inherit the name of the parent device so it is possible to + * register multiple instances of the DAI. We still need to keep + * the same naming style even though those DAIs are not + * component-less anymore. + */ + if (legacy_dai_naming && + (dai_drv->id == 0 || dai_drv->name == NULL)) { + dai->name = fmt_single_name(dev, &dai->id); + } else { + dai->name = fmt_multiple_name(dev, dai_drv); + if (dai_drv->id) + dai->id = dai_drv->id; + else + dai->id = component->num_dai; + } + if (dai->name == NULL) { + kfree(dai); + return NULL; + } + + dai->component = component; + dai->dev = dev; + dai->driver = dai_drv; + if (!dai->driver->ops) + dai->driver->ops = &null_dai_ops; + + list_add_tail(&dai->list, &component->dai_list); + component->num_dai++; + + dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); + return dai; +} + +/** + * snd_soc_register_dais - Register a DAI with the ASoC core + * + * @component: The component the DAIs are registered for + * @dai_drv: DAI driver to use for the DAIs + * @count: Number of DAIs + * @legacy_dai_naming: Use the legacy naming scheme and let the DAI inherit the + * parent's name. + */ +static int snd_soc_register_dais(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, size_t count) +{ + struct device *dev = component->dev; + struct snd_soc_dai *dai; + unsigned int i; + int ret; + + dev_dbg(dev, "ASoC: dai register %s #%zu\n", dev_name(dev), count); + + for (i = 0; i < count; i++) { + + dai = soc_add_dai(component, dai_drv + i, + count == 1 && !component->driver->non_legacy_dai_naming); + if (dai == NULL) { + ret = -ENOMEM; + goto err; + } + } + + return 0; + +err: + snd_soc_unregister_dais(component); + + return ret; +} + +/** + * snd_soc_register_dai - Register a DAI dynamically & create its widgets + * + * @component: The component the DAIs are registered for + * @dai_drv: DAI driver to use for the DAI + * + * Topology can use this API to register DAIs when probing a component. + * These DAIs's widgets will be freed in the card cleanup and the DAIs + * will be freed in the component cleanup. + */ +int snd_soc_register_dai(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + struct snd_soc_dai *dai; + int ret; + + if (dai_drv->dobj.type != SND_SOC_DOBJ_PCM) { + dev_err(component->dev, "Invalid dai type %d\n", + dai_drv->dobj.type); + return -EINVAL; + } + + lockdep_assert_held(&client_mutex); + dai = soc_add_dai(component, dai_drv, false); + if (!dai) + return -ENOMEM; + + /* Create the DAI widgets here. After adding DAIs, topology may + * also add routes that need these widgets as source or sink. + */ + ret = snd_soc_dapm_new_dai_widgets(dapm, dai); + if (ret != 0) { + dev_err(component->dev, + "Failed to create DAI widgets %d\n", ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_register_dai); + +static void snd_soc_component_seq_notifier(struct snd_soc_dapm_context *dapm, + enum snd_soc_dapm_type type, int subseq) +{ + struct snd_soc_component *component = dapm->component; + + component->driver->seq_notifier(component, type, subseq); +} + +static int snd_soc_component_stream_event(struct snd_soc_dapm_context *dapm, + int event) +{ + struct snd_soc_component *component = dapm->component; + + return component->driver->stream_event(component, event); +} + +static int snd_soc_component_set_bias_level(struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_component *component = dapm->component; + + return component->driver->set_bias_level(component, level); +} + +static int snd_soc_component_initialize(struct snd_soc_component *component, + const struct snd_soc_component_driver *driver, struct device *dev) +{ + struct snd_soc_dapm_context *dapm; + + component->name = fmt_single_name(dev, &component->id); + if (!component->name) { + dev_err(dev, "ASoC: Failed to allocate name\n"); + return -ENOMEM; + } + + component->dev = dev; + component->driver = driver; + + dapm = snd_soc_component_get_dapm(component); + dapm->dev = dev; + dapm->component = component; + dapm->bias_level = SND_SOC_BIAS_OFF; + dapm->idle_bias_off = !driver->idle_bias_on; + dapm->suspend_bias_off = driver->suspend_bias_off; + if (driver->seq_notifier) + dapm->seq_notifier = snd_soc_component_seq_notifier; + if (driver->stream_event) + dapm->stream_event = snd_soc_component_stream_event; + if (driver->set_bias_level) + dapm->set_bias_level = snd_soc_component_set_bias_level; + + INIT_LIST_HEAD(&component->dai_list); + mutex_init(&component->io_mutex); + + return 0; +} + +static void snd_soc_component_setup_regmap(struct snd_soc_component *component) +{ + int val_bytes = regmap_get_val_bytes(component->regmap); + + /* Errors are legitimate for non-integer byte multiples */ + if (val_bytes > 0) + component->val_bytes = val_bytes; +} + +#ifdef CONFIG_REGMAP + +/** + * snd_soc_component_init_regmap() - Initialize regmap instance for the component + * @component: The component for which to initialize the regmap instance + * @regmap: The regmap instance that should be used by the component + * + * This function allows deferred assignment of the regmap instance that is + * associated with the component. Only use this if the regmap instance is not + * yet ready when the component is registered. The function must also be called + * before the first IO attempt of the component. + */ +void snd_soc_component_init_regmap(struct snd_soc_component *component, + struct regmap *regmap) +{ + component->regmap = regmap; + snd_soc_component_setup_regmap(component); +} +EXPORT_SYMBOL_GPL(snd_soc_component_init_regmap); + +/** + * snd_soc_component_exit_regmap() - De-initialize regmap instance for the component + * @component: The component for which to de-initialize the regmap instance + * + * Calls regmap_exit() on the regmap instance associated to the component and + * removes the regmap instance from the component. + * + * This function should only be used if snd_soc_component_init_regmap() was used + * to initialize the regmap instance. + */ +void snd_soc_component_exit_regmap(struct snd_soc_component *component) +{ + regmap_exit(component->regmap); + component->regmap = NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_component_exit_regmap); + +#endif + +static void snd_soc_component_add(struct snd_soc_component *component) +{ + mutex_lock(&client_mutex); + + if (!component->driver->write && !component->driver->read) { + if (!component->regmap) + component->regmap = dev_get_regmap(component->dev, NULL); + if (component->regmap) + snd_soc_component_setup_regmap(component); + } + + list_add(&component->list, &component_list); + INIT_LIST_HEAD(&component->dobj_list); + + mutex_unlock(&client_mutex); +} + +static void snd_soc_component_cleanup(struct snd_soc_component *component) +{ + snd_soc_unregister_dais(component); + kfree(component->name); +} + +static void snd_soc_component_del_unlocked(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + + if (card) + snd_soc_unregister_card(card); + + list_del(&component->list); +} + +#define ENDIANNESS_MAP(name) \ + (SNDRV_PCM_FMTBIT_##name##LE | SNDRV_PCM_FMTBIT_##name##BE) +static u64 endianness_format_map[] = { + ENDIANNESS_MAP(S16_), + ENDIANNESS_MAP(U16_), + ENDIANNESS_MAP(S24_), + ENDIANNESS_MAP(U24_), + ENDIANNESS_MAP(S32_), + ENDIANNESS_MAP(U32_), + ENDIANNESS_MAP(S24_3), + ENDIANNESS_MAP(U24_3), + ENDIANNESS_MAP(S20_3), + ENDIANNESS_MAP(U20_3), + ENDIANNESS_MAP(S18_3), + ENDIANNESS_MAP(U18_3), + ENDIANNESS_MAP(FLOAT_), + ENDIANNESS_MAP(FLOAT64_), + ENDIANNESS_MAP(IEC958_SUBFRAME_), +}; + +/* + * Fix up the DAI formats for endianness: codecs don't actually see + * the endianness of the data but we're using the CPU format + * definitions which do need to include endianness so we ensure that + * codec DAIs always have both big and little endian variants set. + */ +static void convert_endianness_formats(struct snd_soc_pcm_stream *stream) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(endianness_format_map); i++) + if (stream->formats & endianness_format_map[i]) + stream->formats |= endianness_format_map[i]; +} + +int snd_soc_add_component(struct device *dev, + struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) +{ + int ret; + int i; + + ret = snd_soc_component_initialize(component, component_driver, dev); + if (ret) + goto err_free; + + if (component_driver->endianness) { + for (i = 0; i < num_dai; i++) { + convert_endianness_formats(&dai_drv[i].playback); + convert_endianness_formats(&dai_drv[i].capture); + } + } + + ret = snd_soc_register_dais(component, dai_drv, num_dai); + if (ret < 0) { + dev_err(dev, "ASoC: Failed to register DAIs: %d\n", ret); + goto err_cleanup; + } + + snd_soc_component_add(component); + + return 0; + +err_cleanup: + snd_soc_component_cleanup(component); +err_free: + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_add_component); + +int snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) +{ + struct snd_soc_component *component; + + component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + if (!component) + return -ENOMEM; + + return snd_soc_add_component(dev, component, component_driver, + dai_drv, num_dai); +} +EXPORT_SYMBOL_GPL(snd_soc_register_component); + +/** + * snd_soc_unregister_component - Unregister all related component + * from the ASoC core + * + * @dev: The device to unregister + */ +static int __snd_soc_unregister_component(struct device *dev) +{ + struct snd_soc_component *component; + int found = 0; + + mutex_lock(&client_mutex); + list_for_each_entry(component, &component_list, list) { + if (dev != component->dev) + continue; + + snd_soc_tplg_component_remove(component, SND_SOC_TPLG_INDEX_ALL); + snd_soc_component_del_unlocked(component); + found = 1; + break; + } + mutex_unlock(&client_mutex); + + if (found) { + snd_soc_component_cleanup(component); + } + + return found; +} + +void snd_soc_unregister_component(struct device *dev) +{ + while (__snd_soc_unregister_component(dev)); +} +EXPORT_SYMBOL_GPL(snd_soc_unregister_component); + +struct snd_soc_component *snd_soc_lookup_component(struct device *dev, + const char *driver_name) +{ + struct snd_soc_component *component; + struct snd_soc_component *ret; + + ret = NULL; + mutex_lock(&client_mutex); + list_for_each_entry(component, &component_list, list) { + if (dev != component->dev) + continue; + + if (driver_name && + (driver_name != component->driver->name) && + (strcmp(component->driver->name, driver_name) != 0)) + continue; + + ret = component; + break; + } + mutex_unlock(&client_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_lookup_component); + +/* Retrieve a card's name from device tree */ +int snd_soc_of_parse_card_name(struct snd_soc_card *card, + const char *propname) +{ + struct device_node *np; + int ret; + + if (!card->dev) { + pr_err("card->dev is not set before calling %s\n", __func__); + return -EINVAL; + } + + np = card->dev->of_node; + + ret = of_property_read_string_index(np, propname, 0, &card->name); + /* + * EINVAL means the property does not exist. This is fine providing + * card->name was previously set, which is checked later in + * snd_soc_register_card. + */ + if (ret < 0 && ret != -EINVAL) { + dev_err(card->dev, + "ASoC: Property '%s' could not be read: %d\n", + propname, ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_of_parse_card_name); + +static const struct snd_soc_dapm_widget simple_widgets[] = { + SND_SOC_DAPM_MIC("Microphone", NULL), + SND_SOC_DAPM_LINE("Line", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card, + const char *propname) +{ + struct device_node *np = card->dev->of_node; + struct snd_soc_dapm_widget *widgets; + const char *template, *wname; + int i, j, num_widgets, ret; + + num_widgets = of_property_count_strings(np, propname); + if (num_widgets < 0) { + dev_err(card->dev, + "ASoC: Property '%s' does not exist\n", propname); + return -EINVAL; + } + if (num_widgets & 1) { + dev_err(card->dev, + "ASoC: Property '%s' length is not even\n", propname); + return -EINVAL; + } + + num_widgets /= 2; + if (!num_widgets) { + dev_err(card->dev, "ASoC: Property '%s's length is zero\n", + propname); + return -EINVAL; + } + + widgets = devm_kcalloc(card->dev, num_widgets, sizeof(*widgets), + GFP_KERNEL); + if (!widgets) { + dev_err(card->dev, + "ASoC: Could not allocate memory for widgets\n"); + return -ENOMEM; + } + + for (i = 0; i < num_widgets; i++) { + ret = of_property_read_string_index(np, propname, + 2 * i, &template); + if (ret) { + dev_err(card->dev, + "ASoC: Property '%s' index %d read error:%d\n", + propname, 2 * i, ret); + return -EINVAL; + } + + for (j = 0; j < ARRAY_SIZE(simple_widgets); j++) { + if (!strncmp(template, simple_widgets[j].name, + strlen(simple_widgets[j].name))) { + widgets[i] = simple_widgets[j]; + break; + } + } + + if (j >= ARRAY_SIZE(simple_widgets)) { + dev_err(card->dev, + "ASoC: DAPM widget '%s' is not supported\n", + template); + return -EINVAL; + } + + ret = of_property_read_string_index(np, propname, + (2 * i) + 1, + &wname); + if (ret) { + dev_err(card->dev, + "ASoC: Property '%s' index %d read error:%d\n", + propname, (2 * i) + 1, ret); + return -EINVAL; + } + + widgets[i].name = wname; + } + + card->of_dapm_widgets = widgets; + card->num_of_dapm_widgets = num_widgets; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_simple_widgets); + +int snd_soc_of_get_slot_mask(struct device_node *np, + const char *prop_name, + unsigned int *mask) +{ + u32 val; + const __be32 *of_slot_mask = of_get_property(np, prop_name, &val); + int i; + + if (!of_slot_mask) + return 0; + val /= sizeof(u32); + for (i = 0; i < val; i++) + if (be32_to_cpup(&of_slot_mask[i])) + *mask |= (1 << i); + + return val; +} +EXPORT_SYMBOL_GPL(snd_soc_of_get_slot_mask); + +int snd_soc_of_parse_tdm_slot(struct device_node *np, + unsigned int *tx_mask, + unsigned int *rx_mask, + unsigned int *slots, + unsigned int *slot_width) +{ + u32 val; + int ret; + + if (tx_mask) + snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", tx_mask); + if (rx_mask) + snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", rx_mask); + + if (of_property_read_bool(np, "dai-tdm-slot-num")) { + ret = of_property_read_u32(np, "dai-tdm-slot-num", &val); + if (ret) + return ret; + + if (slots) + *slots = val; + } + + if (of_property_read_bool(np, "dai-tdm-slot-width")) { + ret = of_property_read_u32(np, "dai-tdm-slot-width", &val); + if (ret) + return ret; + + if (slot_width) + *slot_width = val; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_of_parse_tdm_slot); + +void snd_soc_of_parse_audio_prefix(struct snd_soc_card *card, + struct snd_soc_codec_conf *codec_conf, + struct device_node *of_node, + const char *propname) +{ + struct device_node *np = card->dev->of_node; + const char *str; + int ret; + + ret = of_property_read_string(np, propname, &str); + if (ret < 0) { + /* no prefix is not error */ + return; + } + + codec_conf->of_node = of_node; + codec_conf->name_prefix = str; +} +EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_prefix); + +int snd_soc_of_parse_audio_routing(struct snd_soc_card *card, + const char *propname) +{ + struct device_node *np = card->dev->of_node; + int num_routes; + struct snd_soc_dapm_route *routes; + int i, ret; + + num_routes = of_property_count_strings(np, propname); + if (num_routes < 0 || num_routes & 1) { + dev_err(card->dev, + "ASoC: Property '%s' does not exist or its length is not even\n", + propname); + return -EINVAL; + } + num_routes /= 2; + if (!num_routes) { + dev_err(card->dev, "ASoC: Property '%s's length is zero\n", + propname); + return -EINVAL; + } + + routes = devm_kcalloc(card->dev, num_routes, sizeof(*routes), + GFP_KERNEL); + if (!routes) { + dev_err(card->dev, + "ASoC: Could not allocate DAPM route table\n"); + return -ENOMEM; + } + + for (i = 0; i < num_routes; i++) { + ret = of_property_read_string_index(np, propname, + 2 * i, &routes[i].sink); + if (ret) { + dev_err(card->dev, + "ASoC: Property '%s' index %d could not be read: %d\n", + propname, 2 * i, ret); + return -EINVAL; + } + ret = of_property_read_string_index(np, propname, + (2 * i) + 1, &routes[i].source); + if (ret) { + dev_err(card->dev, + "ASoC: Property '%s' index %d could not be read: %d\n", + propname, (2 * i) + 1, ret); + return -EINVAL; + } + } + + card->num_of_dapm_routes = num_routes; + card->of_dapm_routes = routes; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_routing); + +unsigned int snd_soc_of_parse_daifmt(struct device_node *np, + const char *prefix, + struct device_node **bitclkmaster, + struct device_node **framemaster) +{ + int ret, i; + char prop[128]; + unsigned int format = 0; + int bit, frame; + const char *str; + struct { + char *name; + unsigned int val; + } of_fmt_table[] = { + { "i2s", SND_SOC_DAIFMT_I2S }, + { "right_j", SND_SOC_DAIFMT_RIGHT_J }, + { "left_j", SND_SOC_DAIFMT_LEFT_J }, + { "dsp_a", SND_SOC_DAIFMT_DSP_A }, + { "dsp_b", SND_SOC_DAIFMT_DSP_B }, + { "ac97", SND_SOC_DAIFMT_AC97 }, + { "pdm", SND_SOC_DAIFMT_PDM}, + { "msb", SND_SOC_DAIFMT_MSB }, + { "lsb", SND_SOC_DAIFMT_LSB }, + }; + + if (!prefix) + prefix = ""; + + /* + * check "dai-format = xxx" + * or "[prefix]format = xxx" + * SND_SOC_DAIFMT_FORMAT_MASK area + */ + ret = of_property_read_string(np, "dai-format", &str); + if (ret < 0) { + snprintf(prop, sizeof(prop), "%sformat", prefix); + ret = of_property_read_string(np, prop, &str); + } + if (ret == 0) { + for (i = 0; i < ARRAY_SIZE(of_fmt_table); i++) { + if (strcmp(str, of_fmt_table[i].name) == 0) { + format |= of_fmt_table[i].val; + break; + } + } + } + + /* + * check "[prefix]continuous-clock" + * SND_SOC_DAIFMT_CLOCK_MASK area + */ + snprintf(prop, sizeof(prop), "%scontinuous-clock", prefix); + if (of_property_read_bool(np, prop)) + format |= SND_SOC_DAIFMT_CONT; + else + format |= SND_SOC_DAIFMT_GATED; + + /* + * check "[prefix]bitclock-inversion" + * check "[prefix]frame-inversion" + * SND_SOC_DAIFMT_INV_MASK area + */ + snprintf(prop, sizeof(prop), "%sbitclock-inversion", prefix); + bit = !!of_get_property(np, prop, NULL); + + snprintf(prop, sizeof(prop), "%sframe-inversion", prefix); + frame = !!of_get_property(np, prop, NULL); + + switch ((bit << 4) + frame) { + case 0x11: + format |= SND_SOC_DAIFMT_IB_IF; + break; + case 0x10: + format |= SND_SOC_DAIFMT_IB_NF; + break; + case 0x01: + format |= SND_SOC_DAIFMT_NB_IF; + break; + default: + /* SND_SOC_DAIFMT_NB_NF is default */ + break; + } + + /* + * check "[prefix]bitclock-master" + * check "[prefix]frame-master" + * SND_SOC_DAIFMT_MASTER_MASK area + */ + snprintf(prop, sizeof(prop), "%sbitclock-master", prefix); + bit = !!of_get_property(np, prop, NULL); + if (bit && bitclkmaster) + *bitclkmaster = of_parse_phandle(np, prop, 0); + + snprintf(prop, sizeof(prop), "%sframe-master", prefix); + frame = !!of_get_property(np, prop, NULL); + if (frame && framemaster) + *framemaster = of_parse_phandle(np, prop, 0); + + switch ((bit << 4) + frame) { + case 0x11: + format |= SND_SOC_DAIFMT_CBM_CFM; + break; + case 0x10: + format |= SND_SOC_DAIFMT_CBM_CFS; + break; + case 0x01: + format |= SND_SOC_DAIFMT_CBS_CFM; + break; + default: + format |= SND_SOC_DAIFMT_CBS_CFS; + break; + } + + return format; +} +EXPORT_SYMBOL_GPL(snd_soc_of_parse_daifmt); + +int snd_soc_get_dai_id(struct device_node *ep) +{ + struct snd_soc_component *pos; + struct device_node *node; + int ret; + + node = of_graph_get_port_parent(ep); + + /* + * For example HDMI case, HDMI has video/sound port, + * but ALSA SoC needs sound port number only. + * Thus counting HDMI DT port/endpoint doesn't work. + * Then, it should have .of_xlate_dai_id + */ + ret = -ENOTSUPP; + mutex_lock(&client_mutex); + list_for_each_entry(pos, &component_list, list) { + struct device_node *component_of_node = pos->dev->of_node; + + if (!component_of_node && pos->dev->parent) + component_of_node = pos->dev->parent->of_node; + + if (component_of_node != node) + continue; + + if (pos->driver->of_xlate_dai_id) + ret = pos->driver->of_xlate_dai_id(pos, ep); + + break; + } + mutex_unlock(&client_mutex); + + of_node_put(node); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_id); + +int snd_soc_get_dai_name(struct of_phandle_args *args, + const char **dai_name) +{ + struct snd_soc_component *pos; + struct device_node *component_of_node; + int ret = -EPROBE_DEFER; + + mutex_lock(&client_mutex); + list_for_each_entry(pos, &component_list, list) { + component_of_node = pos->dev->of_node; + if (!component_of_node && pos->dev->parent) + component_of_node = pos->dev->parent->of_node; + + if (component_of_node != args->np || !pos->num_dai) + continue; + + if (pos->driver->of_xlate_dai_name) { + ret = pos->driver->of_xlate_dai_name(pos, + args, + dai_name); + } else { + struct snd_soc_dai *dai; + int id = -1; + + switch (args->args_count) { + case 0: + id = 0; /* same as dai_drv[0] */ + break; + case 1: + id = args->args[0]; + break; + default: + /* not supported */ + break; + } + + if (id < 0 || id >= pos->num_dai) { + ret = -EINVAL; + continue; + } + + ret = 0; + + /* find target DAI */ + list_for_each_entry(dai, &pos->dai_list, list) { + if (id == 0) + break; + id--; + } + + *dai_name = dai->driver->name; + if (!*dai_name) + *dai_name = pos->name; + } + + break; + } + mutex_unlock(&client_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_name); + +int snd_soc_of_get_dai_name(struct device_node *of_node, + const char **dai_name) +{ + struct of_phandle_args args; + int ret; + + ret = of_parse_phandle_with_args(of_node, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) + return ret; + + ret = snd_soc_get_dai_name(&args, dai_name); + + of_node_put(args.np); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_name); + +/* + * snd_soc_of_put_dai_link_codecs - Dereference device nodes in the codecs array + * @dai_link: DAI link + * + * Dereference device nodes acquired by snd_soc_of_get_dai_link_codecs(). + */ +void snd_soc_of_put_dai_link_codecs(struct snd_soc_dai_link *dai_link) +{ + struct snd_soc_dai_link_component *component = dai_link->codecs; + int index; + + for (index = 0; index < dai_link->num_codecs; index++, component++) { + if (!component->of_node) + break; + of_node_put(component->of_node); + component->of_node = NULL; + } +} +EXPORT_SYMBOL_GPL(snd_soc_of_put_dai_link_codecs); + +/* + * snd_soc_of_get_dai_link_codecs - Parse a list of CODECs in the devicetree + * @dev: Card device + * @of_node: Device node + * @dai_link: DAI link + * + * Builds an array of CODEC DAI components from the DAI link property + * 'sound-dai'. + * The array is set in the DAI link and the number of DAIs is set accordingly. + * The device nodes in the array (of_node) must be dereferenced by calling + * snd_soc_of_put_dai_link_codecs() on @dai_link. + * + * Returns 0 for success + */ +int snd_soc_of_get_dai_link_codecs(struct device *dev, + struct device_node *of_node, + struct snd_soc_dai_link *dai_link) +{ + struct of_phandle_args args; + struct snd_soc_dai_link_component *component; + char *name; + int index, num_codecs, ret; + + /* Count the number of CODECs */ + name = "sound-dai"; + num_codecs = of_count_phandle_with_args(of_node, name, + "#sound-dai-cells"); + if (num_codecs <= 0) { + if (num_codecs == -ENOENT) + dev_err(dev, "No 'sound-dai' property\n"); + else + dev_err(dev, "Bad phandle in 'sound-dai'\n"); + return num_codecs; + } + component = devm_kcalloc(dev, + num_codecs, sizeof(*component), + GFP_KERNEL); + if (!component) + return -ENOMEM; + dai_link->codecs = component; + dai_link->num_codecs = num_codecs; + + /* Parse the list */ + for (index = 0, component = dai_link->codecs; + index < dai_link->num_codecs; + index++, component++) { + ret = of_parse_phandle_with_args(of_node, name, + "#sound-dai-cells", + index, &args); + if (ret) + goto err; + component->of_node = args.np; + ret = snd_soc_get_dai_name(&args, &component->dai_name); + if (ret < 0) + goto err; + } + return 0; +err: + snd_soc_of_put_dai_link_codecs(dai_link); + dai_link->codecs = NULL; + dai_link->num_codecs = 0; + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_link_codecs); + +static int __init snd_soc_init(void) +{ + snd_soc_debugfs_init(); + snd_soc_util_init(); + + return platform_driver_register(&soc_driver); +} +module_init(snd_soc_init); + +static void __exit snd_soc_exit(void) +{ + snd_soc_util_exit(); + snd_soc_debugfs_exit(); + + platform_driver_unregister(&soc_driver); +} +module_exit(snd_soc_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); +MODULE_DESCRIPTION("ALSA SoC Core"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:soc-audio"); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c new file mode 100644 index 000000000..4d70e6bc2 --- /dev/null +++ b/sound/soc/soc-dapm.c @@ -0,0 +1,4637 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-dapm.c -- ALSA SoC Dynamic Audio Power Management +// +// Copyright 2005 Wolfson Microelectronics PLC. +// Author: Liam Girdwood +// +// Features: +// o Changes power status of internal codec blocks depending on the +// dynamic configuration of codec internal audio paths and active +// DACs/ADCs. +// o Platform power domain - can support external components i.e. amps and +// mic/headphone insertion events. +// o Automatic Mic Bias support +// o Jack insertion power event initiation - e.g. hp insertion will enable +// sinks, dacs, etc +// o Delayed power down of audio subsystem to reduce pops between a quick +// device reopen. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DAPM_UPDATE_STAT(widget, val) widget->dapm->card->dapm_stats.val++; + +#define SND_SOC_DAPM_DIR_REVERSE(x) ((x == SND_SOC_DAPM_DIR_IN) ? \ + SND_SOC_DAPM_DIR_OUT : SND_SOC_DAPM_DIR_IN) + +#define snd_soc_dapm_for_each_direction(dir) \ + for ((dir) = SND_SOC_DAPM_DIR_IN; (dir) <= SND_SOC_DAPM_DIR_OUT; \ + (dir)++) + +static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink, + const char *control, + int (*connected)(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink)); + +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget); + +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget); + +/* dapm power sequences - make this per codec in the future */ +static int dapm_up_seq[] = { + [snd_soc_dapm_pre] = 0, + [snd_soc_dapm_regulator_supply] = 1, + [snd_soc_dapm_pinctrl] = 1, + [snd_soc_dapm_clock_supply] = 1, + [snd_soc_dapm_supply] = 2, + [snd_soc_dapm_micbias] = 3, + [snd_soc_dapm_vmid] = 3, + [snd_soc_dapm_dai_link] = 2, + [snd_soc_dapm_dai_in] = 4, + [snd_soc_dapm_dai_out] = 4, + [snd_soc_dapm_aif_in] = 4, + [snd_soc_dapm_aif_out] = 4, + [snd_soc_dapm_mic] = 5, + [snd_soc_dapm_siggen] = 5, + [snd_soc_dapm_input] = 5, + [snd_soc_dapm_output] = 5, + [snd_soc_dapm_mux] = 6, + [snd_soc_dapm_demux] = 6, + [snd_soc_dapm_dac] = 7, + [snd_soc_dapm_switch] = 8, + [snd_soc_dapm_mixer] = 8, + [snd_soc_dapm_mixer_named_ctl] = 8, + [snd_soc_dapm_pga] = 9, + [snd_soc_dapm_buffer] = 9, + [snd_soc_dapm_scheduler] = 9, + [snd_soc_dapm_effect] = 9, + [snd_soc_dapm_src] = 9, + [snd_soc_dapm_asrc] = 9, + [snd_soc_dapm_encoder] = 9, + [snd_soc_dapm_decoder] = 9, + [snd_soc_dapm_adc] = 10, + [snd_soc_dapm_out_drv] = 11, + [snd_soc_dapm_hp] = 11, + [snd_soc_dapm_spk] = 11, + [snd_soc_dapm_line] = 11, + [snd_soc_dapm_sink] = 11, + [snd_soc_dapm_kcontrol] = 12, + [snd_soc_dapm_post] = 13, +}; + +static int dapm_down_seq[] = { + [snd_soc_dapm_pre] = 0, + [snd_soc_dapm_kcontrol] = 1, + [snd_soc_dapm_adc] = 2, + [snd_soc_dapm_hp] = 3, + [snd_soc_dapm_spk] = 3, + [snd_soc_dapm_line] = 3, + [snd_soc_dapm_out_drv] = 3, + [snd_soc_dapm_sink] = 3, + [snd_soc_dapm_pga] = 4, + [snd_soc_dapm_buffer] = 4, + [snd_soc_dapm_scheduler] = 4, + [snd_soc_dapm_effect] = 4, + [snd_soc_dapm_src] = 4, + [snd_soc_dapm_asrc] = 4, + [snd_soc_dapm_encoder] = 4, + [snd_soc_dapm_decoder] = 4, + [snd_soc_dapm_switch] = 5, + [snd_soc_dapm_mixer_named_ctl] = 5, + [snd_soc_dapm_mixer] = 5, + [snd_soc_dapm_dac] = 6, + [snd_soc_dapm_mic] = 7, + [snd_soc_dapm_siggen] = 7, + [snd_soc_dapm_input] = 7, + [snd_soc_dapm_output] = 7, + [snd_soc_dapm_micbias] = 8, + [snd_soc_dapm_vmid] = 8, + [snd_soc_dapm_mux] = 9, + [snd_soc_dapm_demux] = 9, + [snd_soc_dapm_aif_in] = 10, + [snd_soc_dapm_aif_out] = 10, + [snd_soc_dapm_dai_in] = 10, + [snd_soc_dapm_dai_out] = 10, + [snd_soc_dapm_dai_link] = 11, + [snd_soc_dapm_supply] = 12, + [snd_soc_dapm_clock_supply] = 13, + [snd_soc_dapm_pinctrl] = 13, + [snd_soc_dapm_regulator_supply] = 13, + [snd_soc_dapm_post] = 14, +}; + +static void dapm_assert_locked(struct snd_soc_dapm_context *dapm) +{ + if (dapm->card && dapm->card->instantiated) + lockdep_assert_held(&dapm->card->dapm_mutex); +} + +static void pop_wait(u32 pop_time) +{ + if (pop_time) + schedule_timeout_uninterruptible(msecs_to_jiffies(pop_time)); +} + +static void pop_dbg(struct device *dev, u32 pop_time, const char *fmt, ...) +{ + va_list args; + char *buf; + + if (!pop_time) + return; + + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (buf == NULL) + return; + + va_start(args, fmt); + vsnprintf(buf, PAGE_SIZE, fmt, args); + dev_info(dev, "%s", buf); + va_end(args); + + kfree(buf); +} + +static bool dapm_dirty_widget(struct snd_soc_dapm_widget *w) +{ + return !list_empty(&w->dirty); +} + +static void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason) +{ + dapm_assert_locked(w->dapm); + + if (!dapm_dirty_widget(w)) { + dev_vdbg(w->dapm->dev, "Marking %s dirty due to %s\n", + w->name, reason); + list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty); + } +} + +/* + * Common implementation for dapm_widget_invalidate_input_paths() and + * dapm_widget_invalidate_output_paths(). The function is inlined since the + * combined size of the two specialized functions is only marginally larger then + * the size of the generic function and at the same time the fast path of the + * specialized functions is significantly smaller than the generic function. + */ +static __always_inline void dapm_widget_invalidate_paths( + struct snd_soc_dapm_widget *w, enum snd_soc_dapm_direction dir) +{ + enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); + struct snd_soc_dapm_widget *node; + struct snd_soc_dapm_path *p; + LIST_HEAD(list); + + dapm_assert_locked(w->dapm); + + if (w->endpoints[dir] == -1) + return; + + list_add_tail(&w->work_list, &list); + w->endpoints[dir] = -1; + + list_for_each_entry(w, &list, work_list) { + snd_soc_dapm_widget_for_each_path(w, dir, p) { + if (p->is_supply || p->weak || !p->connect) + continue; + node = p->node[rdir]; + if (node->endpoints[dir] != -1) { + node->endpoints[dir] = -1; + list_add_tail(&node->work_list, &list); + } + } + } +} + +/* + * dapm_widget_invalidate_input_paths() - Invalidate the cached number of + * input paths + * @w: The widget for which to invalidate the cached number of input paths + * + * Resets the cached number of inputs for the specified widget and all widgets + * that can be reached via outcoming paths from the widget. + * + * This function must be called if the number of output paths for a widget might + * have changed. E.g. if the source state of a widget changes or a path is added + * or activated with the widget as the sink. + */ +static void dapm_widget_invalidate_input_paths(struct snd_soc_dapm_widget *w) +{ + dapm_widget_invalidate_paths(w, SND_SOC_DAPM_DIR_IN); +} + +/* + * dapm_widget_invalidate_output_paths() - Invalidate the cached number of + * output paths + * @w: The widget for which to invalidate the cached number of output paths + * + * Resets the cached number of outputs for the specified widget and all widgets + * that can be reached via incoming paths from the widget. + * + * This function must be called if the number of output paths for a widget might + * have changed. E.g. if the sink state of a widget changes or a path is added + * or activated with the widget as the source. + */ +static void dapm_widget_invalidate_output_paths(struct snd_soc_dapm_widget *w) +{ + dapm_widget_invalidate_paths(w, SND_SOC_DAPM_DIR_OUT); +} + +/* + * dapm_path_invalidate() - Invalidates the cached number of inputs and outputs + * for the widgets connected to a path + * @p: The path to invalidate + * + * Resets the cached number of inputs for the sink of the path and the cached + * number of outputs for the source of the path. + * + * This function must be called when a path is added, removed or the connected + * state changes. + */ +static void dapm_path_invalidate(struct snd_soc_dapm_path *p) +{ + /* + * Weak paths or supply paths do not influence the number of input or + * output paths of their neighbors. + */ + if (p->weak || p->is_supply) + return; + + /* + * The number of connected endpoints is the sum of the number of + * connected endpoints of all neighbors. If a node with 0 connected + * endpoints is either connected or disconnected that sum won't change, + * so there is no need to re-check the path. + */ + if (p->source->endpoints[SND_SOC_DAPM_DIR_IN] != 0) + dapm_widget_invalidate_input_paths(p->sink); + if (p->sink->endpoints[SND_SOC_DAPM_DIR_OUT] != 0) + dapm_widget_invalidate_output_paths(p->source); +} + +void dapm_mark_endpoints_dirty(struct snd_soc_card *card) +{ + struct snd_soc_dapm_widget *w; + + mutex_lock(&card->dapm_mutex); + + list_for_each_entry(w, &card->widgets, list) { + if (w->is_ep) { + dapm_mark_dirty(w, "Rechecking endpoints"); + if (w->is_ep & SND_SOC_DAPM_EP_SINK) + dapm_widget_invalidate_output_paths(w); + if (w->is_ep & SND_SOC_DAPM_EP_SOURCE) + dapm_widget_invalidate_input_paths(w); + } + } + + mutex_unlock(&card->dapm_mutex); +} +EXPORT_SYMBOL_GPL(dapm_mark_endpoints_dirty); + +/* create a new dapm widget */ +static inline struct snd_soc_dapm_widget *dapm_cnew_widget( + const struct snd_soc_dapm_widget *_widget) +{ + return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL); +} + +struct dapm_kcontrol_data { + unsigned int value; + struct snd_soc_dapm_widget *widget; + struct list_head paths; + struct snd_soc_dapm_widget_list *wlist; +}; + +static int dapm_kcontrol_data_alloc(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *kcontrol, const char *ctrl_name) +{ + struct dapm_kcontrol_data *data; + struct soc_mixer_control *mc; + struct soc_enum *e; + const char *name; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + INIT_LIST_HEAD(&data->paths); + + switch (widget->id) { + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + mc = (struct soc_mixer_control *)kcontrol->private_value; + + if (mc->autodisable && snd_soc_volsw_is_stereo(mc)) + dev_warn(widget->dapm->dev, + "ASoC: Unsupported stereo autodisable control '%s'\n", + ctrl_name); + + if (mc->autodisable) { + struct snd_soc_dapm_widget template; + + name = kasprintf(GFP_KERNEL, "%s %s", ctrl_name, + "Autodisable"); + if (!name) { + ret = -ENOMEM; + goto err_data; + } + + memset(&template, 0, sizeof(template)); + template.reg = mc->reg; + template.mask = (1 << fls(mc->max)) - 1; + template.shift = mc->shift; + if (mc->invert) + template.off_val = mc->max; + else + template.off_val = 0; + template.on_val = template.off_val; + template.id = snd_soc_dapm_kcontrol; + template.name = name; + + data->value = template.on_val; + + data->widget = + snd_soc_dapm_new_control_unlocked(widget->dapm, + &template); + kfree(name); + if (IS_ERR(data->widget)) { + ret = PTR_ERR(data->widget); + goto err_data; + } + if (!data->widget) { + ret = -ENOMEM; + goto err_data; + } + } + break; + case snd_soc_dapm_demux: + case snd_soc_dapm_mux: + e = (struct soc_enum *)kcontrol->private_value; + + if (e->autodisable) { + struct snd_soc_dapm_widget template; + + name = kasprintf(GFP_KERNEL, "%s %s", ctrl_name, + "Autodisable"); + if (!name) { + ret = -ENOMEM; + goto err_data; + } + + memset(&template, 0, sizeof(template)); + template.reg = e->reg; + template.mask = e->mask; + template.shift = e->shift_l; + template.off_val = snd_soc_enum_item_to_val(e, 0); + template.on_val = template.off_val; + template.id = snd_soc_dapm_kcontrol; + template.name = name; + + data->value = template.on_val; + + data->widget = snd_soc_dapm_new_control_unlocked( + widget->dapm, &template); + kfree(name); + if (IS_ERR(data->widget)) { + ret = PTR_ERR(data->widget); + goto err_data; + } + if (!data->widget) { + ret = -ENOMEM; + goto err_data; + } + + snd_soc_dapm_add_path(widget->dapm, data->widget, + widget, NULL, NULL); + } + break; + default: + break; + } + + kcontrol->private_data = data; + + return 0; + +err_data: + kfree(data); + return ret; +} + +static void dapm_kcontrol_free(struct snd_kcontrol *kctl) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kctl); + + list_del(&data->paths); + kfree(data->wlist); + kfree(data); +} + +static struct snd_soc_dapm_widget_list *dapm_kcontrol_get_wlist( + const struct snd_kcontrol *kcontrol) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); + + return data->wlist; +} + +static int dapm_kcontrol_add_widget(struct snd_kcontrol *kcontrol, + struct snd_soc_dapm_widget *widget) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *new_wlist; + unsigned int n; + + if (data->wlist) + n = data->wlist->num_widgets + 1; + else + n = 1; + + new_wlist = krealloc(data->wlist, + sizeof(*new_wlist) + sizeof(widget) * n, GFP_KERNEL); + if (!new_wlist) + return -ENOMEM; + + new_wlist->widgets[n - 1] = widget; + new_wlist->num_widgets = n; + + data->wlist = new_wlist; + + return 0; +} + +static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol, + struct snd_soc_dapm_path *path) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); + + list_add_tail(&path->list_kcontrol, &data->paths); +} + +static bool dapm_kcontrol_is_powered(const struct snd_kcontrol *kcontrol) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); + + if (!data->widget) + return true; + + return data->widget->power; +} + +static struct list_head *dapm_kcontrol_get_path_list( + const struct snd_kcontrol *kcontrol) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); + + return &data->paths; +} + +#define dapm_kcontrol_for_each_path(path, kcontrol) \ + list_for_each_entry(path, dapm_kcontrol_get_path_list(kcontrol), \ + list_kcontrol) + +unsigned int dapm_kcontrol_get_value(const struct snd_kcontrol *kcontrol) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); + + return data->value; +} +EXPORT_SYMBOL_GPL(dapm_kcontrol_get_value); + +static bool dapm_kcontrol_set_value(const struct snd_kcontrol *kcontrol, + unsigned int value) +{ + struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol); + + if (data->value == value) + return false; + + if (data->widget) { + switch (dapm_kcontrol_get_wlist(kcontrol)->widgets[0]->id) { + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + data->widget->on_val = value & data->widget->mask; + break; + case snd_soc_dapm_demux: + case snd_soc_dapm_mux: + data->widget->on_val = value >> data->widget->shift; + break; + default: + data->widget->on_val = value; + break; + } + } + + data->value = value; + + return true; +} + +/** + * snd_soc_dapm_kcontrol_widget() - Returns the widget associated to a + * kcontrol + * @kcontrol: The kcontrol + */ +struct snd_soc_dapm_widget *snd_soc_dapm_kcontrol_widget( + struct snd_kcontrol *kcontrol) +{ + return dapm_kcontrol_get_wlist(kcontrol)->widgets[0]; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_widget); + +/** + * snd_soc_dapm_kcontrol_dapm() - Returns the dapm context associated to a + * kcontrol + * @kcontrol: The kcontrol + * + * Note: This function must only be used on kcontrols that are known to have + * been registered for a CODEC. Otherwise the behaviour is undefined. + */ +struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm( + struct snd_kcontrol *kcontrol) +{ + return dapm_kcontrol_get_wlist(kcontrol)->widgets[0]->dapm; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_kcontrol_dapm); + +static void dapm_reset(struct snd_soc_card *card) +{ + struct snd_soc_dapm_widget *w; + + lockdep_assert_held(&card->dapm_mutex); + + memset(&card->dapm_stats, 0, sizeof(card->dapm_stats)); + + list_for_each_entry(w, &card->widgets, list) { + w->new_power = w->power; + w->power_checked = false; + } +} + +static const char *soc_dapm_prefix(struct snd_soc_dapm_context *dapm) +{ + if (!dapm->component) + return NULL; + return dapm->component->name_prefix; +} + +static int soc_dapm_read(struct snd_soc_dapm_context *dapm, int reg, + unsigned int *value) +{ + if (!dapm->component) + return -EIO; + return snd_soc_component_read(dapm->component, reg, value); +} + +static int soc_dapm_update_bits(struct snd_soc_dapm_context *dapm, + int reg, unsigned int mask, unsigned int value) +{ + if (!dapm->component) + return -EIO; + return snd_soc_component_update_bits(dapm->component, reg, + mask, value); +} + +static int soc_dapm_test_bits(struct snd_soc_dapm_context *dapm, + int reg, unsigned int mask, unsigned int value) +{ + if (!dapm->component) + return -EIO; + return snd_soc_component_test_bits(dapm->component, reg, mask, value); +} + +static void soc_dapm_async_complete(struct snd_soc_dapm_context *dapm) +{ + if (dapm->component) + snd_soc_component_async_complete(dapm->component); +} + +static struct snd_soc_dapm_widget * +dapm_wcache_lookup(struct snd_soc_dapm_wcache *wcache, const char *name) +{ + struct snd_soc_dapm_widget *w = wcache->widget; + struct list_head *wlist; + const int depth = 2; + int i = 0; + + if (w) { + wlist = &w->dapm->card->widgets; + + list_for_each_entry_from(w, wlist, list) { + if (!strcmp(name, w->name)) + return w; + + if (++i == depth) + break; + } + } + + return NULL; +} + +static inline void dapm_wcache_update(struct snd_soc_dapm_wcache *wcache, + struct snd_soc_dapm_widget *w) +{ + wcache->widget = w; +} + +/** + * snd_soc_dapm_force_bias_level() - Sets the DAPM bias level + * @dapm: The DAPM context for which to set the level + * @level: The level to set + * + * Forces the DAPM bias level to a specific state. It will call the bias level + * callback of DAPM context with the specified level. This will even happen if + * the context is already at the same level. Furthermore it will not go through + * the normal bias level sequencing, meaning any intermediate states between the + * current and the target state will not be entered. + * + * Note that the change in bias level is only temporary and the next time + * snd_soc_dapm_sync() is called the state will be set to the level as + * determined by the DAPM core. The function is mainly intended to be used to + * used during probe or resume from suspend to power up the device so + * initialization can be done, before the DAPM core takes over. + */ +int snd_soc_dapm_force_bias_level(struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + int ret = 0; + + if (dapm->set_bias_level) + ret = dapm->set_bias_level(dapm, level); + + if (ret == 0) + dapm->bias_level = level; + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_force_bias_level); + +/** + * snd_soc_dapm_set_bias_level - set the bias level for the system + * @dapm: DAPM context + * @level: level to configure + * + * Configure the bias (power) levels for the SoC audio device. + * + * Returns 0 for success else error. + */ +static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_card *card = dapm->card; + int ret = 0; + + trace_snd_soc_bias_level_start(card, level); + + if (card && card->set_bias_level) + ret = card->set_bias_level(card, dapm, level); + if (ret != 0) + goto out; + + if (!card || dapm != &card->dapm) + ret = snd_soc_dapm_force_bias_level(dapm, level); + + if (ret != 0) + goto out; + + if (card && card->set_bias_level_post) + ret = card->set_bias_level_post(card, dapm, level); +out: + trace_snd_soc_bias_level_done(card, level); + + return ret; +} + +/* connect mux widget to its interconnecting audio paths */ +static int dapm_connect_mux(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_path *path, const char *control_name, + struct snd_soc_dapm_widget *w) +{ + const struct snd_kcontrol_new *kcontrol = &w->kcontrol_news[0]; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val, item; + int i; + + if (e->reg != SND_SOC_NOPM) { + soc_dapm_read(dapm, e->reg, &val); + val = (val >> e->shift_l) & e->mask; + item = snd_soc_enum_val_to_item(e, val); + } else { + /* since a virtual mux has no backing registers to + * decide which path to connect, it will try to match + * with the first enumeration. This is to ensure + * that the default mux choice (the first) will be + * correctly powered up during initialization. + */ + item = 0; + } + + i = match_string(e->texts, e->items, control_name); + if (i < 0) + return -ENODEV; + + path->name = e->texts[i]; + path->connect = (i == item); + return 0; + +} + +/* set up initial codec paths */ +static void dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i, + int nth_path) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *) + p->sink->kcontrol_news[i].private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + + if (reg != SND_SOC_NOPM) { + soc_dapm_read(p->sink->dapm, reg, &val); + /* + * The nth_path argument allows this function to know + * which path of a kcontrol it is setting the initial + * status for. Ideally this would support any number + * of paths and channels. But since kcontrols only come + * in mono and stereo variants, we are limited to 2 + * channels. + * + * The following code assumes for stereo controls the + * first path is the left channel, and all remaining + * paths are the right channel. + */ + if (snd_soc_volsw_is_stereo(mc) && nth_path > 0) { + if (reg != mc->rreg) + soc_dapm_read(p->sink->dapm, mc->rreg, &val); + val = (val >> mc->rshift) & mask; + } else { + val = (val >> shift) & mask; + } + if (invert) + val = max - val; + p->connect = !!val; + } else { + /* since a virtual mixer has no backing registers to + * decide which path to connect, it will try to match + * with initial state. This is to ensure + * that the default mixer choice will be + * correctly powered up during initialization. + */ + p->connect = invert; + } +} + +/* connect mixer widget to its interconnecting audio paths */ +static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_path *path, const char *control_name) +{ + int i, nth_path = 0; + + /* search for mixer kcontrol */ + for (i = 0; i < path->sink->num_kcontrols; i++) { + if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) { + path->name = path->sink->kcontrol_news[i].name; + dapm_set_mixer_path_status(path, i, nth_path++); + return 0; + } + } + return -ENODEV; +} + +static int dapm_is_shared_kcontrol(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *kcontrolw, + const struct snd_kcontrol_new *kcontrol_new, + struct snd_kcontrol **kcontrol) +{ + struct snd_soc_dapm_widget *w; + int i; + + *kcontrol = NULL; + + list_for_each_entry(w, &dapm->card->widgets, list) { + if (w == kcontrolw || w->dapm != kcontrolw->dapm) + continue; + for (i = 0; i < w->num_kcontrols; i++) { + if (&w->kcontrol_news[i] == kcontrol_new) { + if (w->kcontrols) + *kcontrol = w->kcontrols[i]; + return 1; + } + } + } + + return 0; +} + +/* + * Determine if a kcontrol is shared. If it is, look it up. If it isn't, + * create it. Either way, add the widget into the control's widget list + */ +static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, + int kci) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_card *card = dapm->card->snd_card; + const char *prefix; + size_t prefix_len; + int shared; + struct snd_kcontrol *kcontrol; + bool wname_in_long_name, kcname_in_long_name; + char *long_name = NULL; + const char *name; + int ret = 0; + + prefix = soc_dapm_prefix(dapm); + if (prefix) + prefix_len = strlen(prefix) + 1; + else + prefix_len = 0; + + shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci], + &kcontrol); + + if (!kcontrol) { + if (shared) { + wname_in_long_name = false; + kcname_in_long_name = true; + } else { + switch (w->id) { + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_pga: + case snd_soc_dapm_out_drv: + wname_in_long_name = true; + kcname_in_long_name = true; + break; + case snd_soc_dapm_mixer_named_ctl: + wname_in_long_name = false; + kcname_in_long_name = true; + break; + case snd_soc_dapm_demux: + case snd_soc_dapm_mux: + wname_in_long_name = true; + kcname_in_long_name = false; + break; + default: + return -EINVAL; + } + } + + if (wname_in_long_name && kcname_in_long_name) { + /* + * The control will get a prefix from the control + * creation process but we're also using the same + * prefix for widgets so cut the prefix off the + * front of the widget name. + */ + long_name = kasprintf(GFP_KERNEL, "%s %s", + w->name + prefix_len, + w->kcontrol_news[kci].name); + if (long_name == NULL) + return -ENOMEM; + + name = long_name; + } else if (wname_in_long_name) { + long_name = NULL; + name = w->name + prefix_len; + } else { + long_name = NULL; + name = w->kcontrol_news[kci].name; + } + + kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name, + prefix); + if (!kcontrol) { + ret = -ENOMEM; + goto exit_free; + } + + kcontrol->private_free = dapm_kcontrol_free; + + ret = dapm_kcontrol_data_alloc(w, kcontrol, name); + if (ret) { + snd_ctl_free_one(kcontrol); + goto exit_free; + } + + ret = snd_ctl_add(card, kcontrol); + if (ret < 0) { + dev_err(dapm->dev, + "ASoC: failed to add widget %s dapm kcontrol %s: %d\n", + w->name, name, ret); + goto exit_free; + } + } + + ret = dapm_kcontrol_add_widget(kcontrol, w); + if (ret == 0) + w->kcontrols[kci] = kcontrol; + +exit_free: + kfree(long_name); + + return ret; +} + +/* create new dapm mixer control */ +static int dapm_new_mixer(struct snd_soc_dapm_widget *w) +{ + int i, ret; + struct snd_soc_dapm_path *path; + struct dapm_kcontrol_data *data; + + /* add kcontrol */ + for (i = 0; i < w->num_kcontrols; i++) { + /* match name */ + snd_soc_dapm_widget_for_each_source_path(w, path) { + /* mixer/mux paths name must match control name */ + if (path->name != (char *)w->kcontrol_news[i].name) + continue; + + if (!w->kcontrols[i]) { + ret = dapm_create_or_share_kcontrol(w, i); + if (ret < 0) + return ret; + } + + dapm_kcontrol_add_path(w->kcontrols[i], path); + + data = snd_kcontrol_chip(w->kcontrols[i]); + if (data->widget) + snd_soc_dapm_add_path(data->widget->dapm, + data->widget, + path->source, + NULL, NULL); + } + } + + return 0; +} + +/* create new dapm mux control */ +static int dapm_new_mux(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + enum snd_soc_dapm_direction dir; + struct snd_soc_dapm_path *path; + const char *type; + int ret; + + switch (w->id) { + case snd_soc_dapm_mux: + dir = SND_SOC_DAPM_DIR_OUT; + type = "mux"; + break; + case snd_soc_dapm_demux: + dir = SND_SOC_DAPM_DIR_IN; + type = "demux"; + break; + default: + return -EINVAL; + } + + if (w->num_kcontrols != 1) { + dev_err(dapm->dev, + "ASoC: %s %s has incorrect number of controls\n", type, + w->name); + return -EINVAL; + } + + if (list_empty(&w->edges[dir])) { + dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name); + return -EINVAL; + } + + ret = dapm_create_or_share_kcontrol(w, 0); + if (ret < 0) + return ret; + + snd_soc_dapm_widget_for_each_path(w, dir, path) { + if (path->name) + dapm_kcontrol_add_path(w->kcontrols[0], path); + } + + return 0; +} + +/* create new dapm volume control */ +static int dapm_new_pga(struct snd_soc_dapm_widget *w) +{ + int i, ret; + + for (i = 0; i < w->num_kcontrols; i++) { + ret = dapm_create_or_share_kcontrol(w, i); + if (ret < 0) + return ret; + } + + return 0; +} + +/* create new dapm dai link control */ +static int dapm_new_dai_link(struct snd_soc_dapm_widget *w) +{ + int i, ret; + struct snd_kcontrol *kcontrol; + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_card *card = dapm->card->snd_card; + + /* create control for links with > 1 config */ + if (w->num_params <= 1) + return 0; + + /* add kcontrol */ + for (i = 0; i < w->num_kcontrols; i++) { + kcontrol = snd_soc_cnew(&w->kcontrol_news[i], w, + w->name, NULL); + ret = snd_ctl_add(card, kcontrol); + if (ret < 0) { + dev_err(dapm->dev, + "ASoC: failed to add widget %s dapm kcontrol %s: %d\n", + w->name, w->kcontrol_news[i].name, ret); + return ret; + } + kcontrol->private_data = w; + w->kcontrols[i] = kcontrol; + } + + return 0; +} + +/* We implement power down on suspend by checking the power state of + * the ALSA card - when we are suspending the ALSA state for the card + * is set to D3. + */ +static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) +{ + int level = snd_power_get_state(widget->dapm->card->snd_card); + + switch (level) { + case SNDRV_CTL_POWER_D3hot: + case SNDRV_CTL_POWER_D3cold: + if (widget->ignore_suspend) + dev_dbg(widget->dapm->dev, "ASoC: %s ignoring suspend\n", + widget->name); + return widget->ignore_suspend; + default: + return 1; + } +} + +static int dapm_widget_list_create(struct snd_soc_dapm_widget_list **list, + struct list_head *widgets) +{ + struct snd_soc_dapm_widget *w; + struct list_head *it; + unsigned int size = 0; + unsigned int i = 0; + + list_for_each(it, widgets) + size++; + + *list = kzalloc(struct_size(*list, widgets, size), GFP_KERNEL); + if (*list == NULL) + return -ENOMEM; + + list_for_each_entry(w, widgets, work_list) + (*list)->widgets[i++] = w; + + (*list)->num_widgets = i; + + return 0; +} + +/* + * Common implementation for is_connected_output_ep() and + * is_connected_input_ep(). The function is inlined since the combined size of + * the two specialized functions is only marginally larger then the size of the + * generic function and at the same time the fast path of the specialized + * functions is significantly smaller than the generic function. + */ +static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, + struct list_head *list, enum snd_soc_dapm_direction dir, + int (*fn)(struct snd_soc_dapm_widget *, struct list_head *, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)), + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)) +{ + enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); + struct snd_soc_dapm_path *path; + int con = 0; + + if (widget->endpoints[dir] >= 0) + return widget->endpoints[dir]; + + DAPM_UPDATE_STAT(widget, path_checks); + + /* do we need to add this widget to the list ? */ + if (list) + list_add_tail(&widget->work_list, list); + + if (custom_stop_condition && custom_stop_condition(widget, dir)) { + list = NULL; + custom_stop_condition = NULL; + } + + if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) { + widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget); + return widget->endpoints[dir]; + } + + snd_soc_dapm_widget_for_each_path(widget, rdir, path) { + DAPM_UPDATE_STAT(widget, neighbour_checks); + + if (path->weak || path->is_supply) + continue; + + if (path->walking) + return 1; + + trace_snd_soc_dapm_path(widget, dir, path); + + if (path->connect) { + path->walking = 1; + con += fn(path->node[dir], list, custom_stop_condition); + path->walking = 0; + } + } + + widget->endpoints[dir] = con; + + return con; +} + +/* + * Recursively check for a completed path to an active or physically connected + * output widget. Returns number of complete paths. + * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if widgets from that point + * in the graph onwards should not be added to the widget list. + */ +static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, + struct list_head *list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, + enum snd_soc_dapm_direction)) +{ + return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT, + is_connected_output_ep, custom_stop_condition); +} + +/* + * Recursively check for a completed path to an active or physically connected + * input widget. Returns number of complete paths. + * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. + */ +static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, + struct list_head *list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, + enum snd_soc_dapm_direction)) +{ + return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN, + is_connected_input_ep, custom_stop_condition); +} + +/** + * snd_soc_dapm_get_connected_widgets - query audio path and it's widgets. + * @dai: the soc DAI. + * @stream: stream direction. + * @list: list of active widgets for this stream. + * @custom_stop_condition: (optional) a function meant to stop the widget graph + * walk based on custom logic. + * + * Queries DAPM graph as to whether a valid audio stream path exists for + * the initial stream specified by name. This takes into account + * current mixer and mux kcontrol settings. Creates list of valid widgets. + * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. + * + * Returns the number of valid paths or negative error. + */ +int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, + struct snd_soc_dapm_widget_list **list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)) +{ + struct snd_soc_card *card = dai->component->card; + struct snd_soc_dapm_widget *w; + LIST_HEAD(widgets); + int paths; + int ret; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + /* + * For is_connected_{output,input}_ep fully discover the graph we need + * to reset the cached number of inputs and outputs. + */ + list_for_each_entry(w, &card->widgets, list) { + w->endpoints[SND_SOC_DAPM_DIR_IN] = -1; + w->endpoints[SND_SOC_DAPM_DIR_OUT] = -1; + } + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + paths = is_connected_output_ep(dai->playback_widget, &widgets, + custom_stop_condition); + else + paths = is_connected_input_ep(dai->capture_widget, &widgets, + custom_stop_condition); + + /* Drop starting point */ + list_del(widgets.next); + + ret = dapm_widget_list_create(list, &widgets); + if (ret) + paths = ret; + + trace_snd_soc_dapm_connected(paths, stream); + mutex_unlock(&card->dapm_mutex); + + return paths; +} + +/* + * Handler for regulator supply widget. + */ +int dapm_regulator_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int ret; + + soc_dapm_async_complete(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) { + ret = regulator_allow_bypass(w->regulator, false); + if (ret != 0) + dev_warn(w->dapm->dev, + "ASoC: Failed to unbypass %s: %d\n", + w->name, ret); + } + + return regulator_enable(w->regulator); + } else { + if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) { + ret = regulator_allow_bypass(w->regulator, true); + if (ret != 0) + dev_warn(w->dapm->dev, + "ASoC: Failed to bypass %s: %d\n", + w->name, ret); + } + + return regulator_disable_deferred(w->regulator, w->shift); + } +} +EXPORT_SYMBOL_GPL(dapm_regulator_event); + +/* + * Handler for pinctrl widget. + */ +int dapm_pinctrl_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_dapm_pinctrl_priv *priv = w->priv; + struct pinctrl *p = w->pinctrl; + struct pinctrl_state *s; + + if (!p || !priv) + return -EIO; + + if (SND_SOC_DAPM_EVENT_ON(event)) + s = pinctrl_lookup_state(p, priv->active_state); + else + s = pinctrl_lookup_state(p, priv->sleep_state); + + if (IS_ERR(s)) + return PTR_ERR(s); + + return pinctrl_select_state(p, s); +} +EXPORT_SYMBOL_GPL(dapm_pinctrl_event); + +/* + * Handler for clock supply widget. + */ +int dapm_clock_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (!w->clk) + return -EIO; + + soc_dapm_async_complete(w->dapm); + +#ifdef CONFIG_HAVE_CLK + if (SND_SOC_DAPM_EVENT_ON(event)) { + return clk_prepare_enable(w->clk); + } else { + clk_disable_unprepare(w->clk); + return 0; + } +#endif + return 0; +} +EXPORT_SYMBOL_GPL(dapm_clock_event); + +static int dapm_widget_power_check(struct snd_soc_dapm_widget *w) +{ + if (w->power_checked) + return w->new_power; + + if (w->force) + w->new_power = 1; + else + w->new_power = w->power_check(w); + + w->power_checked = true; + + return w->new_power; +} + +/* Generic check to see if a widget should be powered. */ +static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) +{ + int in, out; + + DAPM_UPDATE_STAT(w, power_checks); + + in = is_connected_input_ep(w, NULL, NULL); + out = is_connected_output_ep(w, NULL, NULL); + return out != 0 && in != 0; +} + +/* Check to see if a power supply is needed */ +static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *path; + + DAPM_UPDATE_STAT(w, power_checks); + + /* Check if one of our outputs is connected */ + snd_soc_dapm_widget_for_each_sink_path(w, path) { + DAPM_UPDATE_STAT(w, neighbour_checks); + + if (path->weak) + continue; + + if (path->connected && + !path->connected(path->source, path->sink)) + continue; + + if (dapm_widget_power_check(path->sink)) + return 1; + } + + return 0; +} + +static int dapm_always_on_check_power(struct snd_soc_dapm_widget *w) +{ + return w->connected; +} + +static int dapm_seq_compare(struct snd_soc_dapm_widget *a, + struct snd_soc_dapm_widget *b, + bool power_up) +{ + int *sort; + + if (power_up) + sort = dapm_up_seq; + else + sort = dapm_down_seq; + + if (sort[a->id] != sort[b->id]) + return sort[a->id] - sort[b->id]; + if (a->subseq != b->subseq) { + if (power_up) + return a->subseq - b->subseq; + else + return b->subseq - a->subseq; + } + if (a->reg != b->reg) + return a->reg - b->reg; + if (a->dapm != b->dapm) + return (unsigned long)a->dapm - (unsigned long)b->dapm; + + return 0; +} + +/* Insert a widget in order into a DAPM power sequence. */ +static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, + struct list_head *list, + bool power_up) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, list, power_list) + if (dapm_seq_compare(new_widget, w, power_up) < 0) { + list_add_tail(&new_widget->power_list, &w->power_list); + return; + } + + list_add_tail(&new_widget->power_list, list); +} + +static void dapm_seq_check_event(struct snd_soc_card *card, + struct snd_soc_dapm_widget *w, int event) +{ + const char *ev_name; + int power, ret; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ev_name = "PRE_PMU"; + power = 1; + break; + case SND_SOC_DAPM_POST_PMU: + ev_name = "POST_PMU"; + power = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + ev_name = "PRE_PMD"; + power = 0; + break; + case SND_SOC_DAPM_POST_PMD: + ev_name = "POST_PMD"; + power = 0; + break; + case SND_SOC_DAPM_WILL_PMU: + ev_name = "WILL_PMU"; + power = 1; + break; + case SND_SOC_DAPM_WILL_PMD: + ev_name = "WILL_PMD"; + power = 0; + break; + default: + WARN(1, "Unknown event %d\n", event); + return; + } + + if (w->new_power != power) + return; + + if (w->event && (w->event_flags & event)) { + pop_dbg(w->dapm->dev, card->pop_time, "pop test : %s %s\n", + w->name, ev_name); + soc_dapm_async_complete(w->dapm); + trace_snd_soc_dapm_widget_event_start(w, event); + ret = w->event(w, NULL, event); + trace_snd_soc_dapm_widget_event_done(w, event); + if (ret < 0) + dev_err(w->dapm->dev, "ASoC: %s: %s event failed: %d\n", + ev_name, w->name, ret); + } +} + +/* Apply the coalesced changes from a DAPM sequence */ +static void dapm_seq_run_coalesced(struct snd_soc_card *card, + struct list_head *pending) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_dapm_widget *w; + int reg; + unsigned int value = 0; + unsigned int mask = 0; + + w = list_first_entry(pending, struct snd_soc_dapm_widget, power_list); + reg = w->reg; + dapm = w->dapm; + + list_for_each_entry(w, pending, power_list) { + WARN_ON(reg != w->reg || dapm != w->dapm); + w->power = w->new_power; + + mask |= w->mask << w->shift; + if (w->power) + value |= w->on_val << w->shift; + else + value |= w->off_val << w->shift; + + pop_dbg(dapm->dev, card->pop_time, + "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n", + w->name, reg, value, mask); + + /* Check for events */ + dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMU); + dapm_seq_check_event(card, w, SND_SOC_DAPM_PRE_PMD); + } + + if (reg >= 0) { + /* Any widget will do, they should all be updating the + * same register. + */ + + pop_dbg(dapm->dev, card->pop_time, + "pop test : Applying 0x%x/0x%x to %x in %dms\n", + value, mask, reg, card->pop_time); + pop_wait(card->pop_time); + soc_dapm_update_bits(dapm, reg, mask, value); + } + + list_for_each_entry(w, pending, power_list) { + dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMU); + dapm_seq_check_event(card, w, SND_SOC_DAPM_POST_PMD); + } +} + +/* Apply a DAPM power sequence. + * + * We walk over a pre-sorted list of widgets to apply power to. In + * order to minimise the number of writes to the device required + * multiple widgets will be updated in a single write where possible. + * Currently anything that requires more than a single write is not + * handled. + */ +static void dapm_seq_run(struct snd_soc_card *card, + struct list_head *list, int event, bool power_up) +{ + struct snd_soc_dapm_widget *w, *n; + struct snd_soc_dapm_context *d; + LIST_HEAD(pending); + int cur_sort = -1; + int cur_subseq = -1; + int cur_reg = SND_SOC_NOPM; + struct snd_soc_dapm_context *cur_dapm = NULL; + int ret, i; + int *sort; + + if (power_up) + sort = dapm_up_seq; + else + sort = dapm_down_seq; + + list_for_each_entry_safe(w, n, list, power_list) { + ret = 0; + + /* Do we need to apply any queued changes? */ + if (sort[w->id] != cur_sort || w->reg != cur_reg || + w->dapm != cur_dapm || w->subseq != cur_subseq) { + if (!list_empty(&pending)) + dapm_seq_run_coalesced(card, &pending); + + if (cur_dapm && cur_dapm->seq_notifier) { + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) + if (sort[i] == cur_sort) + cur_dapm->seq_notifier(cur_dapm, + i, + cur_subseq); + } + + if (cur_dapm && w->dapm != cur_dapm) + soc_dapm_async_complete(cur_dapm); + + INIT_LIST_HEAD(&pending); + cur_sort = -1; + cur_subseq = INT_MIN; + cur_reg = SND_SOC_NOPM; + cur_dapm = NULL; + } + + switch (w->id) { + case snd_soc_dapm_pre: + if (!w->event) + continue; + + if (event == SND_SOC_DAPM_STREAM_START) + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); + else if (event == SND_SOC_DAPM_STREAM_STOP) + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); + break; + + case snd_soc_dapm_post: + if (!w->event) + continue; + + if (event == SND_SOC_DAPM_STREAM_START) + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + else if (event == SND_SOC_DAPM_STREAM_STOP) + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); + break; + + default: + /* Queue it up for application */ + cur_sort = sort[w->id]; + cur_subseq = w->subseq; + cur_reg = w->reg; + cur_dapm = w->dapm; + list_move(&w->power_list, &pending); + break; + } + + if (ret < 0) + dev_err(w->dapm->dev, + "ASoC: Failed to apply widget power: %d\n", ret); + } + + if (!list_empty(&pending)) + dapm_seq_run_coalesced(card, &pending); + + if (cur_dapm && cur_dapm->seq_notifier) { + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) + if (sort[i] == cur_sort) + cur_dapm->seq_notifier(cur_dapm, + i, cur_subseq); + } + + list_for_each_entry(d, &card->dapm_list, list) { + soc_dapm_async_complete(d); + } +} + +static void dapm_widget_update(struct snd_soc_card *card) +{ + struct snd_soc_dapm_update *update = card->update; + struct snd_soc_dapm_widget_list *wlist; + struct snd_soc_dapm_widget *w = NULL; + unsigned int wi; + int ret; + + if (!update || !dapm_kcontrol_is_powered(update->kcontrol)) + return; + + wlist = dapm_kcontrol_get_wlist(update->kcontrol); + + for (wi = 0; wi < wlist->num_widgets; wi++) { + w = wlist->widgets[wi]; + + if (w->event && (w->event_flags & SND_SOC_DAPM_PRE_REG)) { + ret = w->event(w, update->kcontrol, SND_SOC_DAPM_PRE_REG); + if (ret != 0) + dev_err(w->dapm->dev, "ASoC: %s DAPM pre-event failed: %d\n", + w->name, ret); + } + } + + if (!w) + return; + + ret = soc_dapm_update_bits(w->dapm, update->reg, update->mask, + update->val); + if (ret < 0) + dev_err(w->dapm->dev, "ASoC: %s DAPM update failed: %d\n", + w->name, ret); + + if (update->has_second_set) { + ret = soc_dapm_update_bits(w->dapm, update->reg2, + update->mask2, update->val2); + if (ret < 0) + dev_err(w->dapm->dev, + "ASoC: %s DAPM update failed: %d\n", + w->name, ret); + } + + for (wi = 0; wi < wlist->num_widgets; wi++) { + w = wlist->widgets[wi]; + + if (w->event && (w->event_flags & SND_SOC_DAPM_POST_REG)) { + ret = w->event(w, update->kcontrol, SND_SOC_DAPM_POST_REG); + if (ret != 0) + dev_err(w->dapm->dev, "ASoC: %s DAPM post-event failed: %d\n", + w->name, ret); + } + } +} + +/* Async callback run prior to DAPM sequences - brings to _PREPARE if + * they're changing state. + */ +static void dapm_pre_sequence_async(void *data, async_cookie_t cookie) +{ + struct snd_soc_dapm_context *d = data; + int ret; + + /* If we're off and we're not supposed to go into STANDBY */ + if (d->bias_level == SND_SOC_BIAS_OFF && + d->target_bias_level != SND_SOC_BIAS_OFF) { + if (d->dev) + pm_runtime_get_sync(d->dev); + + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY); + if (ret != 0) + dev_err(d->dev, + "ASoC: Failed to turn on bias: %d\n", ret); + } + + /* Prepare for a transition to ON or away from ON */ + if ((d->target_bias_level == SND_SOC_BIAS_ON && + d->bias_level != SND_SOC_BIAS_ON) || + (d->target_bias_level != SND_SOC_BIAS_ON && + d->bias_level == SND_SOC_BIAS_ON)) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE); + if (ret != 0) + dev_err(d->dev, + "ASoC: Failed to prepare bias: %d\n", ret); + } +} + +/* Async callback run prior to DAPM sequences - brings to their final + * state. + */ +static void dapm_post_sequence_async(void *data, async_cookie_t cookie) +{ + struct snd_soc_dapm_context *d = data; + int ret; + + /* If we just powered the last thing off drop to standby bias */ + if (d->bias_level == SND_SOC_BIAS_PREPARE && + (d->target_bias_level == SND_SOC_BIAS_STANDBY || + d->target_bias_level == SND_SOC_BIAS_OFF)) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY); + if (ret != 0) + dev_err(d->dev, "ASoC: Failed to apply standby bias: %d\n", + ret); + } + + /* If we're in standby and can support bias off then do that */ + if (d->bias_level == SND_SOC_BIAS_STANDBY && + d->target_bias_level == SND_SOC_BIAS_OFF) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF); + if (ret != 0) + dev_err(d->dev, "ASoC: Failed to turn off bias: %d\n", + ret); + + if (d->dev) + pm_runtime_put(d->dev); + } + + /* If we just powered up then move to active bias */ + if (d->bias_level == SND_SOC_BIAS_PREPARE && + d->target_bias_level == SND_SOC_BIAS_ON) { + ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON); + if (ret != 0) + dev_err(d->dev, "ASoC: Failed to apply active bias: %d\n", + ret); + } +} + +static void dapm_widget_set_peer_power(struct snd_soc_dapm_widget *peer, + bool power, bool connect) +{ + /* If a connection is being made or broken then that update + * will have marked the peer dirty, otherwise the widgets are + * not connected and this update has no impact. */ + if (!connect) + return; + + /* If the peer is already in the state we're moving to then we + * won't have an impact on it. */ + if (power != peer->power) + dapm_mark_dirty(peer, "peer state change"); +} + +static void dapm_widget_set_power(struct snd_soc_dapm_widget *w, bool power, + struct list_head *up_list, + struct list_head *down_list) +{ + struct snd_soc_dapm_path *path; + + if (w->power == power) + return; + + trace_snd_soc_dapm_widget_power(w, power); + + /* If we changed our power state perhaps our neigbours changed + * also. + */ + snd_soc_dapm_widget_for_each_source_path(w, path) + dapm_widget_set_peer_power(path->source, power, path->connect); + + /* Supplies can't affect their outputs, only their inputs */ + if (!w->is_supply) { + snd_soc_dapm_widget_for_each_sink_path(w, path) + dapm_widget_set_peer_power(path->sink, power, + path->connect); + } + + if (power) + dapm_seq_insert(w, up_list, true); + else + dapm_seq_insert(w, down_list, false); +} + +static void dapm_power_one_widget(struct snd_soc_dapm_widget *w, + struct list_head *up_list, + struct list_head *down_list) +{ + int power; + + switch (w->id) { + case snd_soc_dapm_pre: + dapm_seq_insert(w, down_list, false); + break; + case snd_soc_dapm_post: + dapm_seq_insert(w, up_list, true); + break; + + default: + power = dapm_widget_power_check(w); + + dapm_widget_set_power(w, power, up_list, down_list); + break; + } +} + +static bool dapm_idle_bias_off(struct snd_soc_dapm_context *dapm) +{ + if (dapm->idle_bias_off) + return true; + + switch (snd_power_get_state(dapm->card->snd_card)) { + case SNDRV_CTL_POWER_D3hot: + case SNDRV_CTL_POWER_D3cold: + return dapm->suspend_bias_off; + default: + break; + } + + return false; +} + +/* + * Scan each dapm widget for complete audio path. + * A complete path is a route that has valid endpoints i.e.:- + * + * o DAC to output pin. + * o Input pin to ADC. + * o Input pin to Output pin (bypass, sidetone) + * o DAC to ADC (loopback). + */ +static int dapm_power_widgets(struct snd_soc_card *card, int event) +{ + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_context *d; + LIST_HEAD(up_list); + LIST_HEAD(down_list); + ASYNC_DOMAIN_EXCLUSIVE(async_domain); + enum snd_soc_bias_level bias; + + lockdep_assert_held(&card->dapm_mutex); + + trace_snd_soc_dapm_start(card); + + list_for_each_entry(d, &card->dapm_list, list) { + if (dapm_idle_bias_off(d)) + d->target_bias_level = SND_SOC_BIAS_OFF; + else + d->target_bias_level = SND_SOC_BIAS_STANDBY; + } + + dapm_reset(card); + + /* Check which widgets we need to power and store them in + * lists indicating if they should be powered up or down. We + * only check widgets that have been flagged as dirty but note + * that new widgets may be added to the dirty list while we + * iterate. + */ + list_for_each_entry(w, &card->dapm_dirty, dirty) { + dapm_power_one_widget(w, &up_list, &down_list); + } + + list_for_each_entry(w, &card->widgets, list) { + switch (w->id) { + case snd_soc_dapm_pre: + case snd_soc_dapm_post: + /* These widgets always need to be powered */ + break; + default: + list_del_init(&w->dirty); + break; + } + + if (w->new_power) { + d = w->dapm; + + /* Supplies and micbiases only bring the + * context up to STANDBY as unless something + * else is active and passing audio they + * generally don't require full power. Signal + * generators are virtual pins and have no + * power impact themselves. + */ + switch (w->id) { + case snd_soc_dapm_siggen: + case snd_soc_dapm_vmid: + break; + case snd_soc_dapm_supply: + case snd_soc_dapm_regulator_supply: + case snd_soc_dapm_pinctrl: + case snd_soc_dapm_clock_supply: + case snd_soc_dapm_micbias: + if (d->target_bias_level < SND_SOC_BIAS_STANDBY) + d->target_bias_level = SND_SOC_BIAS_STANDBY; + break; + default: + d->target_bias_level = SND_SOC_BIAS_ON; + break; + } + } + + } + + /* Force all contexts in the card to the same bias state if + * they're not ground referenced. + */ + bias = SND_SOC_BIAS_OFF; + list_for_each_entry(d, &card->dapm_list, list) + if (d->target_bias_level > bias) + bias = d->target_bias_level; + list_for_each_entry(d, &card->dapm_list, list) + if (!dapm_idle_bias_off(d)) + d->target_bias_level = bias; + + trace_snd_soc_dapm_walk_done(card); + + /* Run card bias changes at first */ + dapm_pre_sequence_async(&card->dapm, 0); + /* Run other bias changes in parallel */ + list_for_each_entry(d, &card->dapm_list, list) { + if (d != &card->dapm) + async_schedule_domain(dapm_pre_sequence_async, d, + &async_domain); + } + async_synchronize_full_domain(&async_domain); + + list_for_each_entry(w, &down_list, power_list) { + dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMD); + } + + list_for_each_entry(w, &up_list, power_list) { + dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMU); + } + + /* Power down widgets first; try to avoid amplifying pops. */ + dapm_seq_run(card, &down_list, event, false); + + dapm_widget_update(card); + + /* Now power up. */ + dapm_seq_run(card, &up_list, event, true); + + /* Run all the bias changes in parallel */ + list_for_each_entry(d, &card->dapm_list, list) { + if (d != &card->dapm) + async_schedule_domain(dapm_post_sequence_async, d, + &async_domain); + } + async_synchronize_full_domain(&async_domain); + /* Run card bias changes at last */ + dapm_post_sequence_async(&card->dapm, 0); + + /* do we need to notify any clients that DAPM event is complete */ + list_for_each_entry(d, &card->dapm_list, list) { + if (d->stream_event) + d->stream_event(d, event); + } + + pop_dbg(card->dev, card->pop_time, + "DAPM sequencing finished, waiting %dms\n", card->pop_time); + pop_wait(card->pop_time); + + trace_snd_soc_dapm_done(card); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t dapm_widget_power_read_file(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_dapm_widget *w = file->private_data; + struct snd_soc_card *card = w->dapm->card; + enum snd_soc_dapm_direction dir, rdir; + char *buf; + int in, out; + ssize_t ret; + struct snd_soc_dapm_path *p = NULL; + + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&card->dapm_mutex); + + /* Supply widgets are not handled by is_connected_{input,output}_ep() */ + if (w->is_supply) { + in = 0; + out = 0; + } else { + in = is_connected_input_ep(w, NULL, NULL); + out = is_connected_output_ep(w, NULL, NULL); + } + + ret = scnprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d", + w->name, w->power ? "On" : "Off", + w->force ? " (forced)" : "", in, out); + + if (w->reg >= 0) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + " - R%d(0x%x) mask 0x%x", + w->reg, w->reg, w->mask << w->shift); + + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n"); + + if (w->sname) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, " stream %s %s\n", + w->sname, + w->active ? "active" : "inactive"); + + snd_soc_dapm_for_each_direction(dir) { + rdir = SND_SOC_DAPM_DIR_REVERSE(dir); + snd_soc_dapm_widget_for_each_path(w, dir, p) { + if (p->connected && !p->connected(p->source, p->sink)) + continue; + + if (!p->connect) + continue; + + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + " %s \"%s\" \"%s\"\n", + (rdir == SND_SOC_DAPM_DIR_IN) ? "in" : "out", + p->name ? p->name : "static", + p->node[rdir]->name); + } + } + + mutex_unlock(&card->dapm_mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + return ret; +} + +static const struct file_operations dapm_widget_power_fops = { + .open = simple_open, + .read = dapm_widget_power_read_file, + .llseek = default_llseek, +}; + +static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_dapm_context *dapm = file->private_data; + char *level; + + switch (dapm->bias_level) { + case SND_SOC_BIAS_ON: + level = "On\n"; + break; + case SND_SOC_BIAS_PREPARE: + level = "Prepare\n"; + break; + case SND_SOC_BIAS_STANDBY: + level = "Standby\n"; + break; + case SND_SOC_BIAS_OFF: + level = "Off\n"; + break; + default: + WARN(1, "Unknown bias_level %d\n", dapm->bias_level); + level = "Unknown\n"; + break; + } + + return simple_read_from_buffer(user_buf, count, ppos, level, + strlen(level)); +} + +static const struct file_operations dapm_bias_fops = { + .open = simple_open, + .read = dapm_bias_read_file, + .llseek = default_llseek, +}; + +void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, + struct dentry *parent) +{ + struct dentry *d; + + if (!parent || IS_ERR(parent)) + return; + + dapm->debugfs_dapm = debugfs_create_dir("dapm", parent); + + if (IS_ERR(dapm->debugfs_dapm)) { + dev_warn(dapm->dev, + "ASoC: Failed to create DAPM debugfs directory %ld\n", + PTR_ERR(dapm->debugfs_dapm)); + return; + } + + d = debugfs_create_file("bias_level", 0444, + dapm->debugfs_dapm, dapm, + &dapm_bias_fops); + if (IS_ERR(d)) + dev_warn(dapm->dev, + "ASoC: Failed to create bias level debugfs file: %ld\n", + PTR_ERR(d)); +} + +static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct dentry *d; + + if (!dapm->debugfs_dapm || !w->name) + return; + + d = debugfs_create_file(w->name, 0444, + dapm->debugfs_dapm, w, + &dapm_widget_power_fops); + if (IS_ERR(d)) + dev_warn(w->dapm->dev, + "ASoC: Failed to create %s debugfs file: %ld\n", + w->name, PTR_ERR(d)); +} + +static void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) +{ + debugfs_remove_recursive(dapm->debugfs_dapm); +} + +#else +void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, + struct dentry *parent) +{ +} + +static inline void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) +{ +} + +static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) +{ +} + +#endif + +/* + * soc_dapm_connect_path() - Connects or disconnects a path + * @path: The path to update + * @connect: The new connect state of the path. True if the path is connected, + * false if it is disconnected. + * @reason: The reason why the path changed (for debugging only) + */ +static void soc_dapm_connect_path(struct snd_soc_dapm_path *path, + bool connect, const char *reason) +{ + if (path->connect == connect) + return; + + path->connect = connect; + dapm_mark_dirty(path->source, reason); + dapm_mark_dirty(path->sink, reason); + dapm_path_invalidate(path); +} + +/* test and update the power status of a mux widget */ +static int soc_dapm_mux_update_power(struct snd_soc_card *card, + struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e) +{ + struct snd_soc_dapm_path *path; + int found = 0; + bool connect; + + lockdep_assert_held(&card->dapm_mutex); + + /* find dapm widget path assoc with kcontrol */ + dapm_kcontrol_for_each_path(path, kcontrol) { + found = 1; + /* we now need to match the string in the enum to the path */ + if (!(strcmp(path->name, e->texts[mux]))) + connect = true; + else + connect = false; + + soc_dapm_connect_path(path, connect, "mux update"); + } + + if (found) + dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP); + + return found; +} + +int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_context *dapm, + struct snd_kcontrol *kcontrol, int mux, struct soc_enum *e, + struct snd_soc_dapm_update *update) +{ + struct snd_soc_card *card = dapm->card; + int ret; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + card->update = update; + ret = soc_dapm_mux_update_power(card, kcontrol, mux, e); + card->update = NULL; + mutex_unlock(&card->dapm_mutex); + if (ret > 0) + soc_dpcm_runtime_update(card); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_mux_update_power); + +/* test and update the power status of a mixer or switch widget */ +static int soc_dapm_mixer_update_power(struct snd_soc_card *card, + struct snd_kcontrol *kcontrol, + int connect, int rconnect) +{ + struct snd_soc_dapm_path *path; + int found = 0; + + lockdep_assert_held(&card->dapm_mutex); + + /* find dapm widget path assoc with kcontrol */ + dapm_kcontrol_for_each_path(path, kcontrol) { + /* + * Ideally this function should support any number of + * paths and channels. But since kcontrols only come + * in mono and stereo variants, we are limited to 2 + * channels. + * + * The following code assumes for stereo controls the + * first path (when 'found == 0') is the left channel, + * and all remaining paths (when 'found == 1') are the + * right channel. + * + * A stereo control is signified by a valid 'rconnect' + * value, either 0 for unconnected, or >= 0 for connected. + * This is chosen instead of using snd_soc_volsw_is_stereo, + * so that the behavior of snd_soc_dapm_mixer_update_power + * doesn't change even when the kcontrol passed in is + * stereo. + * + * It passes 'connect' as the path connect status for + * the left channel, and 'rconnect' for the right + * channel. + */ + if (found && rconnect >= 0) + soc_dapm_connect_path(path, rconnect, "mixer update"); + else + soc_dapm_connect_path(path, connect, "mixer update"); + found = 1; + } + + if (found) + dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP); + + return found; +} + +int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_context *dapm, + struct snd_kcontrol *kcontrol, int connect, + struct snd_soc_dapm_update *update) +{ + struct snd_soc_card *card = dapm->card; + int ret; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + card->update = update; + ret = soc_dapm_mixer_update_power(card, kcontrol, connect, -1); + card->update = NULL; + mutex_unlock(&card->dapm_mutex); + if (ret > 0) + soc_dpcm_runtime_update(card); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power); + +static ssize_t dapm_widget_show_component(struct snd_soc_component *cmpnt, + char *buf) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct snd_soc_dapm_widget *w; + int count = 0; + char *state = "not set"; + + /* card won't be set for the dummy component, as a spot fix + * we're checking for that case specifically here but in future + * we will ensure that the dummy component looks like others. + */ + if (!cmpnt->card) + return 0; + + list_for_each_entry(w, &cmpnt->card->widgets, list) { + if (w->dapm != dapm) + continue; + + /* only display widgets that burn power */ + switch (w->id) { + case snd_soc_dapm_hp: + case snd_soc_dapm_mic: + case snd_soc_dapm_spk: + case snd_soc_dapm_line: + case snd_soc_dapm_micbias: + case snd_soc_dapm_dac: + case snd_soc_dapm_adc: + case snd_soc_dapm_pga: + case snd_soc_dapm_out_drv: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: + case snd_soc_dapm_regulator_supply: + case snd_soc_dapm_pinctrl: + case snd_soc_dapm_clock_supply: + if (w->name) + count += sprintf(buf + count, "%s: %s\n", + w->name, w->power ? "On":"Off"); + break; + default: + break; + } + } + + switch (snd_soc_dapm_get_bias_level(dapm)) { + case SND_SOC_BIAS_ON: + state = "On"; + break; + case SND_SOC_BIAS_PREPARE: + state = "Prepare"; + break; + case SND_SOC_BIAS_STANDBY: + state = "Standby"; + break; + case SND_SOC_BIAS_OFF: + state = "Off"; + break; + } + count += sprintf(buf + count, "PM State: %s\n", state); + + return count; +} + +/* show dapm widget status in sys fs */ +static ssize_t dapm_widget_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev); + int i, count = 0; + + mutex_lock(&rtd->card->dapm_mutex); + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_component *cmpnt = rtd->codec_dais[i]->component; + + count += dapm_widget_show_component(cmpnt, buf + count); + } + + mutex_unlock(&rtd->card->dapm_mutex); + + return count; +} + +static DEVICE_ATTR_RO(dapm_widget); + +struct attribute *soc_dapm_dev_attrs[] = { + &dev_attr_dapm_widget.attr, + NULL +}; + +static void dapm_free_path(struct snd_soc_dapm_path *path) +{ + list_del(&path->list_node[SND_SOC_DAPM_DIR_IN]); + list_del(&path->list_node[SND_SOC_DAPM_DIR_OUT]); + list_del(&path->list_kcontrol); + list_del(&path->list); + kfree(path); +} + +void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p, *next_p; + enum snd_soc_dapm_direction dir; + + list_del(&w->list); + list_del(&w->dirty); + /* + * remove source and sink paths associated to this widget. + * While removing the path, remove reference to it from both + * source and sink widgets so that path is removed only once. + */ + snd_soc_dapm_for_each_direction(dir) { + snd_soc_dapm_widget_for_each_path_safe(w, dir, p, next_p) + dapm_free_path(p); + } + + kfree(w->kcontrols); + kfree_const(w->name); + kfree(w); +} + +void snd_soc_dapm_reset_cache(struct snd_soc_dapm_context *dapm) +{ + dapm->path_sink_cache.widget = NULL; + dapm->path_source_cache.widget = NULL; +} + +/* free all dapm widgets and resources */ +static void dapm_free_widgets(struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_dapm_widget *w, *next_w; + + list_for_each_entry_safe(w, next_w, &dapm->card->widgets, list) { + if (w->dapm != dapm) + continue; + snd_soc_dapm_free_widget(w); + } + snd_soc_dapm_reset_cache(dapm); +} + +static struct snd_soc_dapm_widget *dapm_find_widget( + struct snd_soc_dapm_context *dapm, const char *pin, + bool search_other_contexts) +{ + struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_widget *fallback = NULL; + + list_for_each_entry(w, &dapm->card->widgets, list) { + if (!strcmp(w->name, pin)) { + if (w->dapm == dapm) + return w; + else + fallback = w; + } + } + + if (search_other_contexts) + return fallback; + + return NULL; +} + +/* + * set the DAPM pin status: + * returns 1 when the value has been updated, 0 when unchanged, or a negative + * error code; called from kcontrol put callback + */ +static int __snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, + const char *pin, int status) +{ + struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true); + int ret = 0; + + dapm_assert_locked(dapm); + + if (!w) { + dev_err(dapm->dev, "ASoC: DAPM unknown pin %s\n", pin); + return -EINVAL; + } + + if (w->connected != status) { + dapm_mark_dirty(w, "pin configuration"); + dapm_widget_invalidate_input_paths(w); + dapm_widget_invalidate_output_paths(w); + ret = 1; + } + + w->connected = status; + if (status == 0) + w->force = 0; + + return ret; +} + +/* + * similar as __snd_soc_dapm_set_pin(), but returns 0 when successful; + * called from several API functions below + */ +static int snd_soc_dapm_set_pin(struct snd_soc_dapm_context *dapm, + const char *pin, int status) +{ + int ret = __snd_soc_dapm_set_pin(dapm, pin, status); + + return ret < 0 ? ret : 0; +} + +/** + * snd_soc_dapm_sync_unlocked - scan and power dapm paths + * @dapm: DAPM context + * + * Walks all dapm audio paths and powers widgets according to their + * stream or path usage. + * + * Requires external locking. + * + * Returns 0 for success. + */ +int snd_soc_dapm_sync_unlocked(struct snd_soc_dapm_context *dapm) +{ + /* + * Suppress early reports (eg, jacks syncing their state) to avoid + * silly DAPM runs during card startup. + */ + if (!dapm->card || !dapm->card->instantiated) + return 0; + + return dapm_power_widgets(dapm->card, SND_SOC_DAPM_STREAM_NOP); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_sync_unlocked); + +/** + * snd_soc_dapm_sync - scan and power dapm paths + * @dapm: DAPM context + * + * Walks all dapm audio paths and powers widgets according to their + * stream or path usage. + * + * Returns 0 for success. + */ +int snd_soc_dapm_sync(struct snd_soc_dapm_context *dapm) +{ + int ret; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + ret = snd_soc_dapm_sync_unlocked(dapm); + mutex_unlock(&dapm->card->dapm_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_sync); + +/* + * dapm_update_widget_flags() - Re-compute widget sink and source flags + * @w: The widget for which to update the flags + * + * Some widgets have a dynamic category which depends on which neighbors they + * are connected to. This function update the category for these widgets. + * + * This function must be called whenever a path is added or removed to a widget. + */ +static void dapm_update_widget_flags(struct snd_soc_dapm_widget *w) +{ + enum snd_soc_dapm_direction dir; + struct snd_soc_dapm_path *p; + unsigned int ep; + + switch (w->id) { + case snd_soc_dapm_input: + /* On a fully routed card an input is never a source */ + if (w->dapm->card->fully_routed) + return; + ep = SND_SOC_DAPM_EP_SOURCE; + snd_soc_dapm_widget_for_each_source_path(w, p) { + if (p->source->id == snd_soc_dapm_micbias || + p->source->id == snd_soc_dapm_mic || + p->source->id == snd_soc_dapm_line || + p->source->id == snd_soc_dapm_output) { + ep = 0; + break; + } + } + break; + case snd_soc_dapm_output: + /* On a fully routed card a output is never a sink */ + if (w->dapm->card->fully_routed) + return; + ep = SND_SOC_DAPM_EP_SINK; + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (p->sink->id == snd_soc_dapm_spk || + p->sink->id == snd_soc_dapm_hp || + p->sink->id == snd_soc_dapm_line || + p->sink->id == snd_soc_dapm_input) { + ep = 0; + break; + } + } + break; + case snd_soc_dapm_line: + ep = 0; + snd_soc_dapm_for_each_direction(dir) { + if (!list_empty(&w->edges[dir])) + ep |= SND_SOC_DAPM_DIR_TO_EP(dir); + } + break; + default: + return; + } + + w->is_ep = ep; +} + +static int snd_soc_dapm_check_dynamic_path(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink, + const char *control) +{ + bool dynamic_source = false; + bool dynamic_sink = false; + + if (!control) + return 0; + + switch (source->id) { + case snd_soc_dapm_demux: + dynamic_source = true; + break; + default: + break; + } + + switch (sink->id) { + case snd_soc_dapm_mux: + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + dynamic_sink = true; + break; + default: + break; + } + + if (dynamic_source && dynamic_sink) { + dev_err(dapm->dev, + "Direct connection between demux and mixer/mux not supported for path %s -> [%s] -> %s\n", + source->name, control, sink->name); + return -EINVAL; + } else if (!dynamic_source && !dynamic_sink) { + dev_err(dapm->dev, + "Control not supported for path %s -> [%s] -> %s\n", + source->name, control, sink->name); + return -EINVAL; + } + + return 0; +} + +static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, + struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink, + const char *control, + int (*connected)(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink)) +{ + struct snd_soc_dapm_widget *widgets[2]; + enum snd_soc_dapm_direction dir; + struct snd_soc_dapm_path *path; + int ret; + + if (wsink->is_supply && !wsource->is_supply) { + dev_err(dapm->dev, + "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n", + wsource->name, wsink->name); + return -EINVAL; + } + + if (connected && !wsource->is_supply) { + dev_err(dapm->dev, + "connected() callback only supported for supply widgets (%s -> %s)\n", + wsource->name, wsink->name); + return -EINVAL; + } + + if (wsource->is_supply && control) { + dev_err(dapm->dev, + "Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n", + wsource->name, control, wsink->name); + return -EINVAL; + } + + ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control); + if (ret) + return ret; + + path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL); + if (!path) + return -ENOMEM; + + path->node[SND_SOC_DAPM_DIR_IN] = wsource; + path->node[SND_SOC_DAPM_DIR_OUT] = wsink; + widgets[SND_SOC_DAPM_DIR_IN] = wsource; + widgets[SND_SOC_DAPM_DIR_OUT] = wsink; + + path->connected = connected; + INIT_LIST_HEAD(&path->list); + INIT_LIST_HEAD(&path->list_kcontrol); + + if (wsource->is_supply || wsink->is_supply) + path->is_supply = 1; + + /* connect static paths */ + if (control == NULL) { + path->connect = 1; + } else { + switch (wsource->id) { + case snd_soc_dapm_demux: + ret = dapm_connect_mux(dapm, path, control, wsource); + if (ret) + goto err; + break; + default: + break; + } + + switch (wsink->id) { + case snd_soc_dapm_mux: + ret = dapm_connect_mux(dapm, path, control, wsink); + if (ret != 0) + goto err; + break; + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + ret = dapm_connect_mixer(dapm, path, control); + if (ret != 0) + goto err; + break; + default: + break; + } + } + + list_add(&path->list, &dapm->card->paths); + snd_soc_dapm_for_each_direction(dir) + list_add(&path->list_node[dir], &widgets[dir]->edges[dir]); + + snd_soc_dapm_for_each_direction(dir) { + dapm_update_widget_flags(widgets[dir]); + dapm_mark_dirty(widgets[dir], "Route added"); + } + + if (dapm->card->instantiated && path->connect) + dapm_path_invalidate(path); + + return 0; +err: + kfree(path); + return ret; +} + +static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_route *route) +{ + struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w; + struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL; + const char *sink; + const char *source; + char prefixed_sink[80]; + char prefixed_source[80]; + const char *prefix; + int ret; + + prefix = soc_dapm_prefix(dapm); + if (prefix) { + snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s", + prefix, route->sink); + sink = prefixed_sink; + snprintf(prefixed_source, sizeof(prefixed_source), "%s %s", + prefix, route->source); + source = prefixed_source; + } else { + sink = route->sink; + source = route->source; + } + + wsource = dapm_wcache_lookup(&dapm->path_source_cache, source); + wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink); + + if (wsink && wsource) + goto skip_search; + + /* + * find src and dest widgets over all widgets but favor a widget from + * current DAPM context + */ + list_for_each_entry(w, &dapm->card->widgets, list) { + if (!wsink && !(strcmp(w->name, sink))) { + wtsink = w; + if (w->dapm == dapm) { + wsink = w; + if (wsource) + break; + } + continue; + } + if (!wsource && !(strcmp(w->name, source))) { + wtsource = w; + if (w->dapm == dapm) { + wsource = w; + if (wsink) + break; + } + } + } + /* use widget from another DAPM context if not found from this */ + if (!wsink) + wsink = wtsink; + if (!wsource) + wsource = wtsource; + + if (wsource == NULL) { + dev_err(dapm->dev, "ASoC: no source widget found for %s\n", + route->source); + return -ENODEV; + } + if (wsink == NULL) { + dev_err(dapm->dev, "ASoC: no sink widget found for %s\n", + route->sink); + return -ENODEV; + } + +skip_search: + dapm_wcache_update(&dapm->path_sink_cache, wsink); + dapm_wcache_update(&dapm->path_source_cache, wsource); + + ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control, + route->connected); + if (ret) + goto err; + + return 0; +err: + dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n", + source, route->control, sink); + return ret; +} + +static int snd_soc_dapm_del_route(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_route *route) +{ + struct snd_soc_dapm_widget *wsource, *wsink; + struct snd_soc_dapm_path *path, *p; + const char *sink; + const char *source; + char prefixed_sink[80]; + char prefixed_source[80]; + const char *prefix; + + if (route->control) { + dev_err(dapm->dev, + "ASoC: Removal of routes with controls not supported\n"); + return -EINVAL; + } + + prefix = soc_dapm_prefix(dapm); + if (prefix) { + snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s", + prefix, route->sink); + sink = prefixed_sink; + snprintf(prefixed_source, sizeof(prefixed_source), "%s %s", + prefix, route->source); + source = prefixed_source; + } else { + sink = route->sink; + source = route->source; + } + + path = NULL; + list_for_each_entry(p, &dapm->card->paths, list) { + if (strcmp(p->source->name, source) != 0) + continue; + if (strcmp(p->sink->name, sink) != 0) + continue; + path = p; + break; + } + + if (path) { + wsource = path->source; + wsink = path->sink; + + dapm_mark_dirty(wsource, "Route removed"); + dapm_mark_dirty(wsink, "Route removed"); + if (path->connect) + dapm_path_invalidate(path); + + dapm_free_path(path); + + /* Update any path related flags */ + dapm_update_widget_flags(wsource); + dapm_update_widget_flags(wsink); + } else { + dev_warn(dapm->dev, "ASoC: Route %s->%s does not exist\n", + source, sink); + } + + return 0; +} + +/** + * snd_soc_dapm_add_routes - Add routes between DAPM widgets + * @dapm: DAPM context + * @route: audio routes + * @num: number of routes + * + * Connects 2 dapm widgets together via a named audio path. The sink is + * the widget receiving the audio signal, whilst the source is the sender + * of the audio signal. + * + * Returns 0 for success else error. On error all resources can be freed + * with a call to snd_soc_card_free(). + */ +int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_route *route, int num) +{ + int i, r, ret = 0; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + for (i = 0; i < num; i++) { + r = snd_soc_dapm_add_route(dapm, route); + if (r < 0) { + dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n", + route->source, + route->control ? route->control : "direct", + route->sink); + ret = r; + } + route++; + } + mutex_unlock(&dapm->card->dapm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes); + +/** + * snd_soc_dapm_del_routes - Remove routes between DAPM widgets + * @dapm: DAPM context + * @route: audio routes + * @num: number of routes + * + * Removes routes from the DAPM context. + */ +int snd_soc_dapm_del_routes(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_route *route, int num) +{ + int i; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + for (i = 0; i < num; i++) { + snd_soc_dapm_del_route(dapm, route); + route++; + } + mutex_unlock(&dapm->card->dapm_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_del_routes); + +static int snd_soc_dapm_weak_route(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_route *route) +{ + struct snd_soc_dapm_widget *source = dapm_find_widget(dapm, + route->source, + true); + struct snd_soc_dapm_widget *sink = dapm_find_widget(dapm, + route->sink, + true); + struct snd_soc_dapm_path *path; + int count = 0; + + if (!source) { + dev_err(dapm->dev, "ASoC: Unable to find source %s for weak route\n", + route->source); + return -ENODEV; + } + + if (!sink) { + dev_err(dapm->dev, "ASoC: Unable to find sink %s for weak route\n", + route->sink); + return -ENODEV; + } + + if (route->control || route->connected) + dev_warn(dapm->dev, "ASoC: Ignoring control for weak route %s->%s\n", + route->source, route->sink); + + snd_soc_dapm_widget_for_each_sink_path(source, path) { + if (path->sink == sink) { + path->weak = 1; + count++; + } + } + + if (count == 0) + dev_err(dapm->dev, "ASoC: No path found for weak route %s->%s\n", + route->source, route->sink); + if (count > 1) + dev_warn(dapm->dev, "ASoC: %d paths found for weak route %s->%s\n", + count, route->source, route->sink); + + return 0; +} + +/** + * snd_soc_dapm_weak_routes - Mark routes between DAPM widgets as weak + * @dapm: DAPM context + * @route: audio routes + * @num: number of routes + * + * Mark existing routes matching those specified in the passed array + * as being weak, meaning that they are ignored for the purpose of + * power decisions. The main intended use case is for sidetone paths + * which couple audio between other independent paths if they are both + * active in order to make the combination work better at the user + * level but which aren't intended to be "used". + * + * Note that CODEC drivers should not use this as sidetone type paths + * can frequently also be used as bypass paths. + */ +int snd_soc_dapm_weak_routes(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_route *route, int num) +{ + int i, err; + int ret = 0; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); + for (i = 0; i < num; i++) { + err = snd_soc_dapm_weak_route(dapm, route); + if (err) + ret = err; + route++; + } + mutex_unlock(&dapm->card->dapm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_weak_routes); + +/** + * snd_soc_dapm_new_widgets - add new dapm widgets + * @card: card to be checked for new dapm widgets + * + * Checks the codec for any new dapm widgets and creates them if found. + * + * Returns 0 for success. + */ +int snd_soc_dapm_new_widgets(struct snd_soc_card *card) +{ + struct snd_soc_dapm_widget *w; + unsigned int val; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); + + list_for_each_entry(w, &card->widgets, list) + { + if (w->new) + continue; + + if (w->num_kcontrols) { + w->kcontrols = kcalloc(w->num_kcontrols, + sizeof(struct snd_kcontrol *), + GFP_KERNEL); + if (!w->kcontrols) { + mutex_unlock(&card->dapm_mutex); + return -ENOMEM; + } + } + + switch(w->id) { + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + dapm_new_mixer(w); + break; + case snd_soc_dapm_mux: + case snd_soc_dapm_demux: + dapm_new_mux(w); + break; + case snd_soc_dapm_pga: + case snd_soc_dapm_out_drv: + dapm_new_pga(w); + break; + case snd_soc_dapm_dai_link: + dapm_new_dai_link(w); + break; + default: + break; + } + + /* Read the initial power state from the device */ + if (w->reg >= 0) { + soc_dapm_read(w->dapm, w->reg, &val); + val = val >> w->shift; + val &= w->mask; + if (val == w->on_val) + w->power = 1; + } + + w->new = 1; + + dapm_mark_dirty(w, "new widget"); + dapm_debugfs_add_widget(w); + } + + dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP); + mutex_unlock(&card->dapm_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets); + +/** + * snd_soc_dapm_get_volsw - dapm mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a dapm mixer control. + * + * Returns 0 for success. + */ +int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int width = fls(max); + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int reg_val, val, rval = 0; + int ret = 0; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + if (dapm_kcontrol_is_powered(kcontrol) && reg != SND_SOC_NOPM) { + ret = soc_dapm_read(dapm, reg, ®_val); + val = (reg_val >> shift) & mask; + + if (ret == 0 && reg != mc->rreg) + ret = soc_dapm_read(dapm, mc->rreg, ®_val); + + if (snd_soc_volsw_is_stereo(mc)) + rval = (reg_val >> mc->rshift) & mask; + } else { + reg_val = dapm_kcontrol_get_value(kcontrol); + val = reg_val & mask; + + if (snd_soc_volsw_is_stereo(mc)) + rval = (reg_val >> width) & mask; + } + mutex_unlock(&card->dapm_mutex); + + if (ret) + return ret; + + if (invert) + ucontrol->value.integer.value[0] = max - val; + else + ucontrol->value.integer.value[0] = val; + + if (snd_soc_volsw_is_stereo(mc)) { + if (invert) + ucontrol->value.integer.value[1] = max - rval; + else + ucontrol->value.integer.value[1] = rval; + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw); + +/** + * snd_soc_dapm_put_volsw - dapm mixer set callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a dapm mixer control. + * + * Returns 0 for success. + */ +int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int width = fls(max); + unsigned int mask = (1 << width) - 1; + unsigned int invert = mc->invert; + unsigned int val, rval = 0; + int connect, rconnect = -1, change, reg_change = 0; + struct snd_soc_dapm_update update = {}; + int ret = 0; + + val = (ucontrol->value.integer.value[0] & mask); + connect = !!val; + + if (invert) + val = max - val; + + if (snd_soc_volsw_is_stereo(mc)) { + rval = (ucontrol->value.integer.value[1] & mask); + rconnect = !!rval; + if (invert) + rval = max - rval; + } + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + /* This assumes field width < (bits in unsigned int / 2) */ + if (width > sizeof(unsigned int) * 8 / 2) + dev_warn(dapm->dev, + "ASoC: control %s field width limit exceeded\n", + kcontrol->id.name); + change = dapm_kcontrol_set_value(kcontrol, val | (rval << width)); + + if (reg != SND_SOC_NOPM) { + val = val << shift; + rval = rval << mc->rshift; + + reg_change = soc_dapm_test_bits(dapm, reg, mask << shift, val); + + if (snd_soc_volsw_is_stereo(mc)) + reg_change |= soc_dapm_test_bits(dapm, mc->rreg, + mask << mc->rshift, + rval); + } + + if (change || reg_change) { + if (reg_change) { + if (snd_soc_volsw_is_stereo(mc)) { + update.has_second_set = true; + update.reg2 = mc->rreg; + update.mask2 = mask << mc->rshift; + update.val2 = rval; + } + update.kcontrol = kcontrol; + update.reg = reg; + update.mask = mask << shift; + update.val = val; + card->update = &update; + } + + ret = soc_dapm_mixer_update_power(card, kcontrol, connect, + rconnect); + + card->update = NULL; + } + + mutex_unlock(&card->dapm_mutex); + + if (ret > 0) + soc_dpcm_runtime_update(card); + + return change; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw); + +/** + * snd_soc_dapm_get_enum_double - dapm enumerated double mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a dapm enumerated double mixer control. + * + * Returns 0 for success. + */ +int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int reg_val, val; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + if (e->reg != SND_SOC_NOPM && dapm_kcontrol_is_powered(kcontrol)) { + int ret = soc_dapm_read(dapm, e->reg, ®_val); + if (ret) { + mutex_unlock(&card->dapm_mutex); + return ret; + } + } else { + reg_val = dapm_kcontrol_get_value(kcontrol); + } + mutex_unlock(&card->dapm_mutex); + + val = (reg_val >> e->shift_l) & e->mask; + ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(e, val); + if (e->shift_l != e->shift_r) { + val = (reg_val >> e->shift_r) & e->mask; + val = snd_soc_enum_val_to_item(e, val); + ucontrol->value.enumerated.item[1] = val; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double); + +/** + * snd_soc_dapm_put_enum_double - dapm enumerated double mixer set callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a dapm enumerated double mixer control. + * + * Returns 0 for success. + */ +int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val, change, reg_change = 0; + unsigned int mask; + struct snd_soc_dapm_update update = {}; + int ret = 0; + + if (item[0] >= e->items) + return -EINVAL; + + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + mask = e->mask << e->shift_l; + if (e->shift_l != e->shift_r) { + if (item[1] > e->items) + return -EINVAL; + val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; + mask |= e->mask << e->shift_r; + } + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + change = dapm_kcontrol_set_value(kcontrol, val); + + if (e->reg != SND_SOC_NOPM) + reg_change = soc_dapm_test_bits(dapm, e->reg, mask, val); + + if (change || reg_change) { + if (reg_change) { + update.kcontrol = kcontrol; + update.reg = e->reg; + update.mask = mask; + update.val = val; + card->update = &update; + } + + ret = soc_dapm_mux_update_power(card, kcontrol, item[0], e); + + card->update = NULL; + } + + mutex_unlock(&card->dapm_mutex); + + if (ret > 0) + soc_dpcm_runtime_update(card); + + return change; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double); + +/** + * snd_soc_dapm_info_pin_switch - Info for a pin switch + * + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a pin switch control. + */ +int snd_soc_dapm_info_pin_switch(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; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_info_pin_switch); + +/** + * snd_soc_dapm_get_pin_switch - Get information for a pin switch + * + * @kcontrol: mixer control + * @ucontrol: Value + */ +int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + const char *pin = (const char *)kcontrol->private_value; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + ucontrol->value.integer.value[0] = + snd_soc_dapm_get_pin_status(&card->dapm, pin); + + mutex_unlock(&card->dapm_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_switch); + +/** + * snd_soc_dapm_put_pin_switch - Set information for a pin switch + * + * @kcontrol: mixer control + * @ucontrol: Value + */ +int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + const char *pin = (const char *)kcontrol->private_value; + int ret; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + ret = __snd_soc_dapm_set_pin(&card->dapm, pin, + !!ucontrol->value.integer.value[0]); + mutex_unlock(&card->dapm_mutex); + + snd_soc_dapm_sync(&card->dapm); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_put_pin_switch); + +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_dapm_widget *w; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + w = snd_soc_dapm_new_control_unlocked(dapm, widget); + /* Do not nag about probe deferrals */ + if (IS_ERR(w)) { + int ret = PTR_ERR(w); + + if (ret != -EPROBE_DEFER) + dev_err(dapm->dev, + "ASoC: Failed to create DAPM control %s (%d)\n", + widget->name, ret); + goto out_unlock; + } + if (!w) + dev_err(dapm->dev, + "ASoC: Failed to create DAPM control %s\n", + widget->name); + +out_unlock: + mutex_unlock(&dapm->card->dapm_mutex); + return w; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control); + +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget) +{ + enum snd_soc_dapm_direction dir; + struct snd_soc_dapm_widget *w; + const char *prefix; + int ret; + + if ((w = dapm_cnew_widget(widget)) == NULL) + return NULL; + + switch (w->id) { + case snd_soc_dapm_regulator_supply: + w->regulator = devm_regulator_get(dapm->dev, w->name); + if (IS_ERR(w->regulator)) { + ret = PTR_ERR(w->regulator); + if (ret == -EPROBE_DEFER) + return ERR_PTR(ret); + dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n", + w->name, ret); + return NULL; + } + + if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) { + ret = regulator_allow_bypass(w->regulator, true); + if (ret != 0) + dev_warn(w->dapm->dev, + "ASoC: Failed to bypass %s: %d\n", + w->name, ret); + } + break; + case snd_soc_dapm_pinctrl: + w->pinctrl = devm_pinctrl_get(dapm->dev); + if (IS_ERR(w->pinctrl)) { + ret = PTR_ERR(w->pinctrl); + if (ret == -EPROBE_DEFER) + return ERR_PTR(ret); + dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n", + w->name, ret); + return NULL; + } + break; + case snd_soc_dapm_clock_supply: +#ifdef CONFIG_CLKDEV_LOOKUP + w->clk = devm_clk_get(dapm->dev, w->name); + if (IS_ERR(w->clk)) { + ret = PTR_ERR(w->clk); + if (ret == -EPROBE_DEFER) + return ERR_PTR(ret); + dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n", + w->name, ret); + return NULL; + } +#else + return NULL; +#endif + break; + default: + break; + } + + prefix = soc_dapm_prefix(dapm); + if (prefix) + w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name); + else + w->name = kstrdup_const(widget->name, GFP_KERNEL); + if (w->name == NULL) { + kfree(w); + return NULL; + } + + switch (w->id) { + case snd_soc_dapm_mic: + w->is_ep = SND_SOC_DAPM_EP_SOURCE; + w->power_check = dapm_generic_check_power; + break; + case snd_soc_dapm_input: + if (!dapm->card->fully_routed) + w->is_ep = SND_SOC_DAPM_EP_SOURCE; + w->power_check = dapm_generic_check_power; + break; + case snd_soc_dapm_spk: + case snd_soc_dapm_hp: + w->is_ep = SND_SOC_DAPM_EP_SINK; + w->power_check = dapm_generic_check_power; + break; + case snd_soc_dapm_output: + if (!dapm->card->fully_routed) + w->is_ep = SND_SOC_DAPM_EP_SINK; + w->power_check = dapm_generic_check_power; + break; + case snd_soc_dapm_vmid: + case snd_soc_dapm_siggen: + w->is_ep = SND_SOC_DAPM_EP_SOURCE; + w->power_check = dapm_always_on_check_power; + break; + case snd_soc_dapm_sink: + w->is_ep = SND_SOC_DAPM_EP_SINK; + w->power_check = dapm_always_on_check_power; + break; + + case snd_soc_dapm_mux: + case snd_soc_dapm_demux: + case snd_soc_dapm_switch: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_adc: + case snd_soc_dapm_aif_out: + case snd_soc_dapm_dac: + case snd_soc_dapm_aif_in: + case snd_soc_dapm_pga: + case snd_soc_dapm_out_drv: + case snd_soc_dapm_micbias: + case snd_soc_dapm_line: + case snd_soc_dapm_dai_link: + case snd_soc_dapm_dai_out: + case snd_soc_dapm_dai_in: + w->power_check = dapm_generic_check_power; + break; + case snd_soc_dapm_supply: + case snd_soc_dapm_regulator_supply: + case snd_soc_dapm_pinctrl: + case snd_soc_dapm_clock_supply: + case snd_soc_dapm_kcontrol: + w->is_supply = 1; + w->power_check = dapm_supply_check_power; + break; + default: + w->power_check = dapm_always_on_check_power; + break; + } + + w->dapm = dapm; + INIT_LIST_HEAD(&w->list); + INIT_LIST_HEAD(&w->dirty); + list_add_tail(&w->list, &dapm->card->widgets); + + snd_soc_dapm_for_each_direction(dir) { + INIT_LIST_HEAD(&w->edges[dir]); + w->endpoints[dir] = -1; + } + + /* machine layer sets up unconnected pins and insertions */ + w->connected = 1; + return w; +} + +/** + * snd_soc_dapm_new_controls - create new dapm controls + * @dapm: DAPM context + * @widget: widget array + * @num: number of widgets + * + * Creates new DAPM controls based upon the templates. + * + * Returns 0 for success else error. + */ +int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget, + int num) +{ + struct snd_soc_dapm_widget *w; + int i; + int ret = 0; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT); + for (i = 0; i < num; i++) { + w = snd_soc_dapm_new_control_unlocked(dapm, widget); + if (IS_ERR(w)) { + ret = PTR_ERR(w); + /* Do not nag about probe deferrals */ + if (ret == -EPROBE_DEFER) + break; + dev_err(dapm->dev, + "ASoC: Failed to create DAPM control %s (%d)\n", + widget->name, ret); + break; + } + if (!w) { + dev_err(dapm->dev, + "ASoC: Failed to create DAPM control %s\n", + widget->name); + ret = -ENOMEM; + break; + } + widget++; + } + mutex_unlock(&dapm->card->dapm_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls); + +static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_dapm_path *source_p, *sink_p; + struct snd_soc_dai *source, *sink; + struct snd_soc_pcm_runtime *rtd = w->priv; + const struct snd_soc_pcm_stream *config = w->params + w->params_select; + struct snd_pcm_substream substream; + struct snd_pcm_hw_params *params = NULL; + struct snd_pcm_runtime *runtime = NULL; + unsigned int fmt; + int ret = 0; + + if (WARN_ON(!config) || + WARN_ON(list_empty(&w->edges[SND_SOC_DAPM_DIR_OUT]) || + list_empty(&w->edges[SND_SOC_DAPM_DIR_IN]))) + return -EINVAL; + + /* We only support a single source and sink, pick the first */ + source_p = list_first_entry(&w->edges[SND_SOC_DAPM_DIR_OUT], + struct snd_soc_dapm_path, + list_node[SND_SOC_DAPM_DIR_OUT]); + sink_p = list_first_entry(&w->edges[SND_SOC_DAPM_DIR_IN], + struct snd_soc_dapm_path, + list_node[SND_SOC_DAPM_DIR_IN]); + + source = source_p->source->priv; + sink = sink_p->sink->priv; + + /* Be a little careful as we don't want to overflow the mask array */ + if (config->formats) { + fmt = ffs(config->formats) - 1; + } else { + dev_warn(w->dapm->dev, "ASoC: Invalid format %llx specified\n", + config->formats); + fmt = 0; + } + + /* Currently very limited parameter selection */ + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) { + ret = -ENOMEM; + goto out; + } + snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), fmt); + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = + config->rate_min; + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->max = + config->rate_max; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min + = config->channels_min; + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max + = config->channels_max; + + memset(&substream, 0, sizeof(substream)); + + /* Allocate a dummy snd_pcm_runtime for startup() and other ops() */ + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) { + ret = -ENOMEM; + goto out; + } + substream.runtime = runtime; + substream.private_data = rtd; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + substream.stream = SNDRV_PCM_STREAM_CAPTURE; + if (source->driver->ops->startup) { + ret = source->driver->ops->startup(&substream, source); + if (ret < 0) { + dev_err(source->dev, + "ASoC: startup() failed: %d\n", ret); + goto out; + } + source->active++; + } + ret = soc_dai_hw_params(&substream, params, source); + if (ret < 0) + goto out; + + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + if (sink->driver->ops->startup) { + ret = sink->driver->ops->startup(&substream, sink); + if (ret < 0) { + dev_err(sink->dev, + "ASoC: startup() failed: %d\n", ret); + goto out; + } + sink->active++; + } + ret = soc_dai_hw_params(&substream, params, sink); + if (ret < 0) + goto out; + break; + + case SND_SOC_DAPM_POST_PMU: + ret = snd_soc_dai_digital_mute(sink, 0, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret != 0 && ret != -ENOTSUPP) + dev_warn(sink->dev, "ASoC: Failed to unmute: %d\n", ret); + ret = 0; + break; + + case SND_SOC_DAPM_PRE_PMD: + ret = snd_soc_dai_digital_mute(sink, 1, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret != 0 && ret != -ENOTSUPP) + dev_warn(sink->dev, "ASoC: Failed to mute: %d\n", ret); + ret = 0; + + source->active--; + if (source->driver->ops->shutdown) { + substream.stream = SNDRV_PCM_STREAM_CAPTURE; + source->driver->ops->shutdown(&substream, source); + } + + sink->active--; + if (sink->driver->ops->shutdown) { + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; + sink->driver->ops->shutdown(&substream, sink); + } + break; + + default: + WARN(1, "Unknown event %d\n", event); + ret = -EINVAL; + } + +out: + kfree(runtime); + kfree(params); + return ret; +} + +static int snd_soc_dapm_dai_link_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = w->params_select; + + return 0; +} + +static int snd_soc_dapm_dai_link_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol); + + /* Can't change the config when widget is already powered */ + if (w->power) + return -EBUSY; + + if (ucontrol->value.enumerated.item[0] == w->params_select) + return 0; + + if (ucontrol->value.enumerated.item[0] >= w->num_params) + return -EINVAL; + + w->params_select = ucontrol->value.enumerated.item[0]; + + return 1; +} + +static void +snd_soc_dapm_free_kcontrol(struct snd_soc_card *card, + unsigned long *private_value, + int num_params, + const char **w_param_text) +{ + int count; + + devm_kfree(card->dev, (void *)*private_value); + + if (!w_param_text) + return; + + for (count = 0 ; count < num_params; count++) + devm_kfree(card->dev, (void *)w_param_text[count]); + devm_kfree(card->dev, w_param_text); +} + +static struct snd_kcontrol_new * +snd_soc_dapm_alloc_kcontrol(struct snd_soc_card *card, + char *link_name, + const struct snd_soc_pcm_stream *params, + int num_params, const char **w_param_text, + unsigned long *private_value) +{ + struct soc_enum w_param_enum[] = { + SOC_ENUM_SINGLE(0, 0, 0, NULL), + }; + struct snd_kcontrol_new kcontrol_dai_link[] = { + SOC_ENUM_EXT(NULL, w_param_enum[0], + snd_soc_dapm_dai_link_get, + snd_soc_dapm_dai_link_put), + }; + struct snd_kcontrol_new *kcontrol_news; + const struct snd_soc_pcm_stream *config = params; + int count; + + for (count = 0 ; count < num_params; count++) { + if (!config->stream_name) { + dev_warn(card->dapm.dev, + "ASoC: anonymous config %d for dai link %s\n", + count, link_name); + w_param_text[count] = + devm_kasprintf(card->dev, GFP_KERNEL, + "Anonymous Configuration %d", + count); + } else { + w_param_text[count] = devm_kmemdup(card->dev, + config->stream_name, + strlen(config->stream_name) + 1, + GFP_KERNEL); + } + if (!w_param_text[count]) + goto outfree_w_param; + config++; + } + + w_param_enum[0].items = num_params; + w_param_enum[0].texts = w_param_text; + + *private_value = + (unsigned long) devm_kmemdup(card->dev, + (void *)(kcontrol_dai_link[0].private_value), + sizeof(struct soc_enum), GFP_KERNEL); + if (!*private_value) { + dev_err(card->dev, "ASoC: Failed to create control for %s widget\n", + link_name); + goto outfree_w_param; + } + kcontrol_dai_link[0].private_value = *private_value; + /* duplicate kcontrol_dai_link on heap so that memory persists */ + kcontrol_news = devm_kmemdup(card->dev, &kcontrol_dai_link[0], + sizeof(struct snd_kcontrol_new), + GFP_KERNEL); + if (!kcontrol_news) { + dev_err(card->dev, "ASoC: Failed to create control for %s widget\n", + link_name); + goto outfree_w_param; + } + return kcontrol_news; + +outfree_w_param: + snd_soc_dapm_free_kcontrol(card, private_value, num_params, w_param_text); + return NULL; +} + +int snd_soc_dapm_new_pcm(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd, + const struct snd_soc_pcm_stream *params, + unsigned int num_params, + struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_dapm_widget template; + struct snd_soc_dapm_widget *w; + const char **w_param_text; + unsigned long private_value; + char *link_name; + int ret; + + link_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-%s", + source->name, sink->name); + if (!link_name) + return -ENOMEM; + + memset(&template, 0, sizeof(template)); + template.reg = SND_SOC_NOPM; + template.id = snd_soc_dapm_dai_link; + template.name = link_name; + template.event = snd_soc_dai_link_event; + template.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD; + template.kcontrol_news = NULL; + + /* allocate memory for control, only in case of multiple configs */ + if (num_params > 1) { + w_param_text = devm_kcalloc(card->dev, num_params, + sizeof(char *), GFP_KERNEL); + if (!w_param_text) { + ret = -ENOMEM; + goto param_fail; + } + + template.num_kcontrols = 1; + template.kcontrol_news = + snd_soc_dapm_alloc_kcontrol(card, + link_name, params, num_params, + w_param_text, &private_value); + if (!template.kcontrol_news) { + ret = -ENOMEM; + goto param_fail; + } + } else { + w_param_text = NULL; + } + dev_dbg(card->dev, "ASoC: adding %s widget\n", link_name); + + w = snd_soc_dapm_new_control_unlocked(&card->dapm, &template); + if (IS_ERR(w)) { + ret = PTR_ERR(w); + /* Do not nag about probe deferrals */ + if (ret != -EPROBE_DEFER) + dev_err(card->dev, + "ASoC: Failed to create %s widget (%d)\n", + link_name, ret); + goto outfree_kcontrol_news; + } + if (!w) { + dev_err(card->dev, "ASoC: Failed to create %s widget\n", + link_name); + ret = -ENOMEM; + goto outfree_kcontrol_news; + } + + w->params = params; + w->num_params = num_params; + w->priv = rtd; + + ret = snd_soc_dapm_add_path(&card->dapm, source, w, NULL, NULL); + if (ret) + goto outfree_w; + return snd_soc_dapm_add_path(&card->dapm, w, sink, NULL, NULL); + +outfree_w: + devm_kfree(card->dev, w); +outfree_kcontrol_news: + devm_kfree(card->dev, (void *)template.kcontrol_news); + snd_soc_dapm_free_kcontrol(card, &private_value, num_params, w_param_text); +param_fail: + devm_kfree(card->dev, link_name); + return ret; +} + +int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, + struct snd_soc_dai *dai) +{ + struct snd_soc_dapm_widget template; + struct snd_soc_dapm_widget *w; + + WARN_ON(dapm->dev != dai->dev); + + memset(&template, 0, sizeof(template)); + template.reg = SND_SOC_NOPM; + + if (dai->driver->playback.stream_name) { + template.id = snd_soc_dapm_dai_in; + template.name = dai->driver->playback.stream_name; + template.sname = dai->driver->playback.stream_name; + + dev_dbg(dai->dev, "ASoC: adding %s widget\n", + template.name); + + w = snd_soc_dapm_new_control_unlocked(dapm, &template); + if (IS_ERR(w)) { + int ret = PTR_ERR(w); + + /* Do not nag about probe deferrals */ + if (ret != -EPROBE_DEFER) + dev_err(dapm->dev, + "ASoC: Failed to create %s widget (%d)\n", + dai->driver->playback.stream_name, ret); + return ret; + } + if (!w) { + dev_err(dapm->dev, "ASoC: Failed to create %s widget\n", + dai->driver->playback.stream_name); + return -ENOMEM; + } + + w->priv = dai; + dai->playback_widget = w; + } + + if (dai->driver->capture.stream_name) { + template.id = snd_soc_dapm_dai_out; + template.name = dai->driver->capture.stream_name; + template.sname = dai->driver->capture.stream_name; + + dev_dbg(dai->dev, "ASoC: adding %s widget\n", + template.name); + + w = snd_soc_dapm_new_control_unlocked(dapm, &template); + if (IS_ERR(w)) { + int ret = PTR_ERR(w); + + /* Do not nag about probe deferrals */ + if (ret != -EPROBE_DEFER) + dev_err(dapm->dev, + "ASoC: Failed to create %s widget (%d)\n", + dai->driver->playback.stream_name, ret); + return ret; + } + if (!w) { + dev_err(dapm->dev, "ASoC: Failed to create %s widget\n", + dai->driver->capture.stream_name); + return -ENOMEM; + } + + w->priv = dai; + dai->capture_widget = w; + } + + return 0; +} + +int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card) +{ + struct snd_soc_dapm_widget *dai_w, *w; + struct snd_soc_dapm_widget *src, *sink; + struct snd_soc_dai *dai; + + /* For each DAI widget... */ + list_for_each_entry(dai_w, &card->widgets, list) { + switch (dai_w->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + break; + default: + continue; + } + + /* let users know there is no DAI to link */ + if (!dai_w->priv) { + dev_dbg(card->dev, "dai widget %s has no DAI\n", + dai_w->name); + continue; + } + + dai = dai_w->priv; + + /* ...find all widgets with the same stream and link them */ + list_for_each_entry(w, &card->widgets, list) { + if (w->dapm != dai_w->dapm) + continue; + + switch (w->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + continue; + default: + break; + } + + if (!w->sname || !strstr(w->sname, dai_w->sname)) + continue; + + if (dai_w->id == snd_soc_dapm_dai_in) { + src = dai_w; + sink = w; + } else { + src = w; + sink = dai_w; + } + dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name); + snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL); + } + } + + return 0; +} + +static void dapm_connect_dai_link_widgets(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dapm_widget *sink, *source; + int i; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + + /* connect BE DAI playback if widgets are valid */ + if (codec_dai->playback_widget && cpu_dai->playback_widget) { + source = cpu_dai->playback_widget; + sink = codec_dai->playback_widget; + dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n", + cpu_dai->component->name, source->name, + codec_dai->component->name, sink->name); + + snd_soc_dapm_add_path(&card->dapm, source, sink, + NULL, NULL); + } + + /* connect BE DAI capture if widgets are valid */ + if (codec_dai->capture_widget && cpu_dai->capture_widget) { + source = codec_dai->capture_widget; + sink = cpu_dai->capture_widget; + dev_dbg(rtd->dev, "connected DAI link %s:%s -> %s:%s\n", + codec_dai->component->name, source->name, + cpu_dai->component->name, sink->name); + + snd_soc_dapm_add_path(&card->dapm, source, sink, + NULL, NULL); + } + } +} + +static void soc_dapm_dai_stream_event(struct snd_soc_dai *dai, int stream, + int event) +{ + struct snd_soc_dapm_widget *w; + unsigned int ep; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + w = dai->playback_widget; + else + w = dai->capture_widget; + + if (w) { + dapm_mark_dirty(w, "stream event"); + + if (w->id == snd_soc_dapm_dai_in) { + ep = SND_SOC_DAPM_EP_SOURCE; + dapm_widget_invalidate_input_paths(w); + } else { + ep = SND_SOC_DAPM_EP_SINK; + dapm_widget_invalidate_output_paths(w); + } + + switch (event) { + case SND_SOC_DAPM_STREAM_START: + w->active = 1; + w->is_ep = ep; + break; + case SND_SOC_DAPM_STREAM_STOP: + w->active = 0; + w->is_ep = 0; + break; + case SND_SOC_DAPM_STREAM_SUSPEND: + case SND_SOC_DAPM_STREAM_RESUME: + case SND_SOC_DAPM_STREAM_PAUSE_PUSH: + case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: + break; + } + } +} + +void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + + /* for each BE DAI link... */ + list_for_each_entry(rtd, &card->rtd_list, list) { + /* + * dynamic FE links have no fixed DAI mapping. + * CODEC<->CODEC links have no direct connection. + */ + if (rtd->dai_link->dynamic || rtd->dai_link->params) + continue; + + dapm_connect_dai_link_widgets(card, rtd); + } +} + +static void soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, + int event) +{ + int i; + + soc_dapm_dai_stream_event(rtd->cpu_dai, stream, event); + for (i = 0; i < rtd->num_codecs; i++) + soc_dapm_dai_stream_event(rtd->codec_dais[i], stream, event); + + dapm_power_widgets(rtd->card, event); +} + +/** + * snd_soc_dapm_stream_event - send a stream event to the dapm core + * @rtd: PCM runtime data + * @stream: stream name + * @event: stream event + * + * Sends a stream event to the dapm core. The core then makes any + * necessary widget power changes. + * + * Returns 0 for success else error. + */ +void snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, int stream, + int event) +{ + struct snd_soc_card *card = rtd->card; + + mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + soc_dapm_stream_event(rtd, stream, event); + mutex_unlock(&card->dapm_mutex); +} + +/** + * snd_soc_dapm_enable_pin_unlocked - enable pin. + * @dapm: DAPM context + * @pin: pin name + * + * Enables input/output pin and its parents or children widgets iff there is + * a valid audio route and active audio stream. + * + * Requires external locking. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_enable_pin_unlocked(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + return snd_soc_dapm_set_pin(dapm, pin, 1); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin_unlocked); + +/** + * snd_soc_dapm_enable_pin - enable pin. + * @dapm: DAPM context + * @pin: pin name + * + * Enables input/output pin and its parents or children widgets iff there is + * a valid audio route and active audio stream. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_enable_pin(struct snd_soc_dapm_context *dapm, const char *pin) +{ + int ret; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + ret = snd_soc_dapm_set_pin(dapm, pin, 1); + + mutex_unlock(&dapm->card->dapm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin); + +/** + * snd_soc_dapm_force_enable_pin_unlocked - force a pin to be enabled + * @dapm: DAPM context + * @pin: pin name + * + * Enables input/output pin regardless of any other state. This is + * intended for use with microphone bias supplies used in microphone + * jack detection. + * + * Requires external locking. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_force_enable_pin_unlocked(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true); + + if (!w) { + dev_err(dapm->dev, "ASoC: unknown pin %s\n", pin); + return -EINVAL; + } + + dev_dbg(w->dapm->dev, "ASoC: force enable pin %s\n", pin); + if (!w->connected) { + /* + * w->force does not affect the number of input or output paths, + * so we only have to recheck if w->connected is changed + */ + dapm_widget_invalidate_input_paths(w); + dapm_widget_invalidate_output_paths(w); + w->connected = 1; + } + w->force = 1; + dapm_mark_dirty(w, "force enable"); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_force_enable_pin_unlocked); + +/** + * snd_soc_dapm_force_enable_pin - force a pin to be enabled + * @dapm: DAPM context + * @pin: pin name + * + * Enables input/output pin regardless of any other state. This is + * intended for use with microphone bias supplies used in microphone + * jack detection. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_force_enable_pin(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + int ret; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, pin); + + mutex_unlock(&dapm->card->dapm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_force_enable_pin); + +/** + * snd_soc_dapm_disable_pin_unlocked - disable pin. + * @dapm: DAPM context + * @pin: pin name + * + * Disables input/output pin and its parents or children widgets. + * + * Requires external locking. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_disable_pin_unlocked(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + return snd_soc_dapm_set_pin(dapm, pin, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin_unlocked); + +/** + * snd_soc_dapm_disable_pin - disable pin. + * @dapm: DAPM context + * @pin: pin name + * + * Disables input/output pin and its parents or children widgets. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_disable_pin(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + int ret; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + ret = snd_soc_dapm_set_pin(dapm, pin, 0); + + mutex_unlock(&dapm->card->dapm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin); + +/** + * snd_soc_dapm_nc_pin_unlocked - permanently disable pin. + * @dapm: DAPM context + * @pin: pin name + * + * Marks the specified pin as being not connected, disabling it along + * any parent or child widgets. At present this is identical to + * snd_soc_dapm_disable_pin() but in future it will be extended to do + * additional things such as disabling controls which only affect + * paths through the pin. + * + * Requires external locking. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_nc_pin_unlocked(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + return snd_soc_dapm_set_pin(dapm, pin, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_nc_pin_unlocked); + +/** + * snd_soc_dapm_nc_pin - permanently disable pin. + * @dapm: DAPM context + * @pin: pin name + * + * Marks the specified pin as being not connected, disabling it along + * any parent or child widgets. At present this is identical to + * snd_soc_dapm_disable_pin() but in future it will be extended to do + * additional things such as disabling controls which only affect + * paths through the pin. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_nc_pin(struct snd_soc_dapm_context *dapm, const char *pin) +{ + int ret; + + mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + ret = snd_soc_dapm_set_pin(dapm, pin, 0); + + mutex_unlock(&dapm->card->dapm_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_nc_pin); + +/** + * snd_soc_dapm_get_pin_status - get audio pin status + * @dapm: DAPM context + * @pin: audio signal pin endpoint (or start point) + * + * Get audio pin status - connected or disconnected. + * + * Returns 1 for connected otherwise 0. + */ +int snd_soc_dapm_get_pin_status(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, true); + + if (w) + return w->connected; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status); + +/** + * snd_soc_dapm_ignore_suspend - ignore suspend status for DAPM endpoint + * @dapm: DAPM context + * @pin: audio signal pin endpoint (or start point) + * + * Mark the given endpoint or pin as ignoring suspend. When the + * system is disabled a path between two endpoints flagged as ignoring + * suspend will not be disabled. The path must already be enabled via + * normal means at suspend time, it will not be turned on if it was not + * already enabled. + */ +int snd_soc_dapm_ignore_suspend(struct snd_soc_dapm_context *dapm, + const char *pin) +{ + struct snd_soc_dapm_widget *w = dapm_find_widget(dapm, pin, false); + + if (!w) { + dev_err(dapm->dev, "ASoC: unknown pin %s\n", pin); + return -EINVAL; + } + + w->ignore_suspend = 1; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend); + +/** + * snd_soc_dapm_free - free dapm resources + * @dapm: DAPM context + * + * Free all dapm widgets and resources. + */ +void snd_soc_dapm_free(struct snd_soc_dapm_context *dapm) +{ + dapm_debugfs_cleanup(dapm); + dapm_free_widgets(dapm); + list_del(&dapm->list); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_free); + +static void soc_dapm_shutdown_dapm(struct snd_soc_dapm_context *dapm) +{ + struct snd_soc_card *card = dapm->card; + struct snd_soc_dapm_widget *w; + LIST_HEAD(down_list); + int powerdown = 0; + + mutex_lock(&card->dapm_mutex); + + list_for_each_entry(w, &dapm->card->widgets, list) { + if (w->dapm != dapm) + continue; + if (w->power) { + dapm_seq_insert(w, &down_list, false); + w->new_power = 0; + powerdown = 1; + } + } + + /* If there were no widgets to power down we're already in + * standby. + */ + if (powerdown) { + if (dapm->bias_level == SND_SOC_BIAS_ON) + snd_soc_dapm_set_bias_level(dapm, + SND_SOC_BIAS_PREPARE); + dapm_seq_run(card, &down_list, 0, false); + if (dapm->bias_level == SND_SOC_BIAS_PREPARE) + snd_soc_dapm_set_bias_level(dapm, + SND_SOC_BIAS_STANDBY); + } + + mutex_unlock(&card->dapm_mutex); +} + +/* + * snd_soc_dapm_shutdown - callback for system shutdown + */ +void snd_soc_dapm_shutdown(struct snd_soc_card *card) +{ + struct snd_soc_dapm_context *dapm; + + list_for_each_entry(dapm, &card->dapm_list, list) { + if (dapm != &card->dapm) { + soc_dapm_shutdown_dapm(dapm); + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) + snd_soc_dapm_set_bias_level(dapm, + SND_SOC_BIAS_OFF); + } + } + + soc_dapm_shutdown_dapm(&card->dapm); + if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) + snd_soc_dapm_set_bias_level(&card->dapm, + SND_SOC_BIAS_OFF); +} + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); +MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/soc-devres.c b/sound/soc/soc-devres.c new file mode 100644 index 000000000..a9ea172a6 --- /dev/null +++ b/sound/soc/soc-devres.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-devres.c -- ALSA SoC Audio Layer devres functions +// +// Copyright (C) 2013 Linaro Ltd + +#include +#include +#include +#include + +static void devm_component_release(struct device *dev, void *res) +{ + snd_soc_unregister_component(*(struct device **)res); +} + +/** + * devm_snd_soc_register_component - resource managed component registration + * @dev: Device used to manage component + * @cmpnt_drv: Component driver + * @dai_drv: DAI driver + * @num_dai: Number of DAIs to register + * + * Register a component with automatic unregistration when the device is + * unregistered. + */ +int devm_snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *cmpnt_drv, + struct snd_soc_dai_driver *dai_drv, int num_dai) +{ + struct device **ptr; + int ret; + + ptr = devres_alloc(devm_component_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai); + if (ret == 0) { + *ptr = dev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_snd_soc_register_component); + +static void devm_card_release(struct device *dev, void *res) +{ + snd_soc_unregister_card(*(struct snd_soc_card **)res); +} + +/** + * devm_snd_soc_register_card - resource managed card registration + * @dev: Device used to manage card + * @card: Card to register + * + * Register a card with automatic unregistration when the device is + * unregistered. + */ +int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card) +{ + struct snd_soc_card **ptr; + int ret; + + ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = snd_soc_register_card(card); + if (ret == 0) { + *ptr = card; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_snd_soc_register_card); + +#ifdef CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM + +static void devm_dmaengine_pcm_release(struct device *dev, void *res) +{ + snd_dmaengine_pcm_unregister(*(struct device **)res); +} + +/** + * devm_snd_dmaengine_pcm_register - resource managed dmaengine PCM registration + * @dev: The parent device for the PCM device + * @config: Platform specific PCM configuration + * @flags: Platform specific quirks + * + * Register a dmaengine based PCM device with automatic unregistration when the + * device is unregistered. + */ +int devm_snd_dmaengine_pcm_register(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags) +{ + struct device **ptr; + int ret; + + ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = snd_dmaengine_pcm_register(dev, config, flags); + if (ret == 0) { + *ptr = dev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_snd_dmaengine_pcm_register); + +#endif diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c new file mode 100644 index 000000000..232df04ca --- /dev/null +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (C) 2013, Analog Devices Inc. +// Author: Lars-Peter Clausen + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * The platforms dmaengine driver does not support reporting the amount of + * bytes that are still left to transfer. + */ +#define SND_DMAENGINE_PCM_FLAG_NO_RESIDUE BIT(31) + +struct dmaengine_pcm { + struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1]; + const struct snd_dmaengine_pcm_config *config; + struct snd_soc_component component; + unsigned int flags; +}; + +static struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p) +{ + return container_of(p, struct dmaengine_pcm, component); +} + +static struct device *dmaengine_dma_dev(struct dmaengine_pcm *pcm, + struct snd_pcm_substream *substream) +{ + if (!pcm->chan[substream->stream]) + return NULL; + + return pcm->chan[substream->stream]->device->dev; +} + +/** + * snd_dmaengine_pcm_prepare_slave_config() - Generic prepare_slave_config callback + * @substream: PCM substream + * @params: hw_params + * @slave_config: DMA slave config to prepare + * + * This function can be used as a generic prepare_slave_config callback for + * platforms which make use of the snd_dmaengine_dai_dma_data struct for their + * DAI DMA data. Internally the function will first call + * snd_hwparams_to_dma_slave_config to fill in the slave config based on the + * hw_params, followed by snd_dmaengine_set_config_from_dai_data to fill in the + * remaining fields based on the DAI DMA data. + */ +int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + int ret; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config); + if (ret) + return ret; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data, + slave_config); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_prepare_slave_config); + +static int dmaengine_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + int (*prepare_slave_config)(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config); + struct dma_slave_config slave_config; + int ret; + + memset(&slave_config, 0, sizeof(slave_config)); + + if (!pcm->config) + prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config; + else + prepare_slave_config = pcm->config->prepare_slave_config; + + if (prepare_slave_config) { + ret = prepare_slave_config(substream, params, &slave_config); + if (ret) + return ret; + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) + return ret; + } + + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); +} + +static int dmaengine_pcm_set_runtime_hwparams(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + struct device *dma_dev = dmaengine_dma_dev(pcm, substream); + struct dma_chan *chan = pcm->chan[substream->stream]; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_caps dma_caps; + struct snd_pcm_hardware hw; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + snd_pcm_format_t i; + int ret; + + if (pcm->config && pcm->config->pcm_hardware) + return snd_soc_set_runtime_hwparams(substream, + pcm->config->pcm_hardware); + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + memset(&hw, 0, sizeof(hw)); + hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED; + hw.periods_min = 2; + hw.periods_max = UINT_MAX; + hw.period_bytes_min = 256; + hw.period_bytes_max = dma_get_max_seg_size(dma_dev); + hw.buffer_bytes_max = SIZE_MAX; + hw.fifo_size = dma_data->fifo_size; + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + hw.info |= SNDRV_PCM_INFO_BATCH; + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause && dma_caps.cmd_resume) + hw.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + hw.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = SNDRV_PCM_FORMAT_FIRST; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + hw.formats |= pcm_format_to_bits(i); + break; + default: + /* Unsupported types */ + break; + } + } + + return snd_soc_set_runtime_hwparams(substream, &hw); +} + +static int dmaengine_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + struct dma_chan *chan = pcm->chan[substream->stream]; + int ret; + + ret = dmaengine_pcm_set_runtime_hwparams(substream); + if (ret) + return ret; + + return snd_dmaengine_pcm_open(substream, chan); +} + +static struct dma_chan *dmaengine_pcm_compat_request_channel( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) +{ + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + struct snd_dmaengine_dai_dma_data *dma_data; + dma_filter_fn fn = NULL; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) && pcm->chan[0]) + return pcm->chan[0]; + + if (pcm->config && pcm->config->compat_request_channel) + return pcm->config->compat_request_channel(rtd, substream); + + if (pcm->config) + fn = pcm->config->compat_filter_fn; + + return snd_dmaengine_pcm_request_channel(fn, dma_data->filter_data); +} + +static bool dmaengine_pcm_can_report_residue(struct device *dev, + struct dma_chan *chan) +{ + struct dma_slave_caps dma_caps; + int ret; + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret != 0) { + dev_warn(dev, "Failed to get DMA channel capabilities, falling back to period counting: %d\n", + ret); + return false; + } + + if (dma_caps.residue_granularity == DMA_RESIDUE_GRANULARITY_DESCRIPTOR) + return false; + + return true; +} + +static int dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + const struct snd_dmaengine_pcm_config *config = pcm->config; + struct device *dev = component->dev; + struct snd_dmaengine_dai_dma_data *dma_data; + struct snd_pcm_substream *substream; + size_t prealloc_buffer_size; + size_t max_buffer_size; + unsigned int i; + int ret; + + if (config && config->prealloc_buffer_size) { + prealloc_buffer_size = config->prealloc_buffer_size; + max_buffer_size = config->pcm_hardware->buffer_bytes_max; + } else { + prealloc_buffer_size = 512 * 1024; + max_buffer_size = SIZE_MAX; + } + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++) { + substream = rtd->pcm->streams[i].substream; + if (!substream) + continue; + + dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + if (!pcm->chan[i] && + (pcm->flags & SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME)) + pcm->chan[i] = dma_request_slave_channel(dev, + dma_data->chan_name); + + if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) { + pcm->chan[i] = dmaengine_pcm_compat_request_channel(rtd, + substream); + } + + if (!pcm->chan[i]) { + dev_err(component->dev, + "Missing dma channel for stream: %d\n", i); + return -EINVAL; + } + + ret = snd_pcm_lib_preallocate_pages(substream, + SNDRV_DMA_TYPE_DEV_IRAM, + dmaengine_dma_dev(pcm, substream), + prealloc_buffer_size, + max_buffer_size); + if (ret) + return ret; + + if (!dmaengine_pcm_can_report_residue(dev, pcm->chan[i])) + pcm->flags |= SND_DMAENGINE_PCM_FLAG_NO_RESIDUE; + + if (rtd->pcm->streams[i].pcm->name[0] == '\0') { + strncpy(rtd->pcm->streams[i].pcm->name, + rtd->pcm->streams[i].pcm->id, + sizeof(rtd->pcm->streams[i].pcm->name)); + } + } + + return 0; +} + +static snd_pcm_uframes_t dmaengine_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + return snd_dmaengine_pcm_pointer_no_residue(substream); + else + return snd_dmaengine_pcm_pointer(substream); +} + +static int dmaengine_copy_user(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void __user *buf, unsigned long bytes) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component = + snd_soc_rtdcom_lookup(rtd, SND_DMAENGINE_PCM_DRV_NAME); + struct snd_pcm_runtime *runtime = substream->runtime; + struct dmaengine_pcm *pcm = soc_component_to_pcm(component); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) = pcm->config->process; + bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + void *dma_ptr = runtime->dma_area + hwoff + + channel * (runtime->dma_bytes / runtime->channels); + int ret; + + if (is_playback) + if (copy_from_user(dma_ptr, buf, bytes)) + return -EFAULT; + + if (process) { + ret = process(substream, channel, hwoff, (__force void *)buf, bytes); + if (ret < 0) + return ret; + } + + if (!is_playback) + if (copy_to_user(buf, dma_ptr, bytes)) + return -EFAULT; + + return 0; +} + +static const struct snd_pcm_ops dmaengine_pcm_ops = { + .open = dmaengine_pcm_open, + .close = snd_dmaengine_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dmaengine_pcm_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = dmaengine_pcm_pointer, +}; + +static const struct snd_pcm_ops dmaengine_pcm_process_ops = { + .open = dmaengine_pcm_open, + .close = snd_dmaengine_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dmaengine_pcm_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .trigger = snd_dmaengine_pcm_trigger, + .pointer = dmaengine_pcm_pointer, + .copy_user = dmaengine_copy_user, +}; + +static const struct snd_soc_component_driver dmaengine_pcm_component = { + .name = SND_DMAENGINE_PCM_DRV_NAME, + .probe_order = SND_SOC_COMP_ORDER_LATE, + .ops = &dmaengine_pcm_ops, + .pcm_new = dmaengine_pcm_new, +}; + +static const struct snd_soc_component_driver dmaengine_pcm_component_process = { + .name = SND_DMAENGINE_PCM_DRV_NAME, + .probe_order = SND_SOC_COMP_ORDER_LATE, + .ops = &dmaengine_pcm_process_ops, + .pcm_new = dmaengine_pcm_new, +}; + +static const char * const dmaengine_pcm_dma_channel_names[] = { + [SNDRV_PCM_STREAM_PLAYBACK] = "tx", + [SNDRV_PCM_STREAM_CAPTURE] = "rx", +}; + +static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + struct device *dev, const struct snd_dmaengine_pcm_config *config) +{ + unsigned int i; + const char *name; + struct dma_chan *chan; + + if ((pcm->flags & (SND_DMAENGINE_PCM_FLAG_NO_DT | + SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME)) || + !dev->of_node) + return 0; + + if (config && config->dma_dev) { + /* + * If this warning is seen, it probably means that your Linux + * device structure does not match your HW device structure. + * It would be best to refactor the Linux device structure to + * correctly match the HW structure. + */ + dev_warn(dev, "DMA channels sourced from device %s", + dev_name(config->dma_dev)); + dev = config->dma_dev; + } + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; + i++) { + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + name = "rx-tx"; + else + name = dmaengine_pcm_dma_channel_names[i]; + if (config && config->chan_names[i]) + name = config->chan_names[i]; + chan = dma_request_slave_channel_reason(dev, name); + if (IS_ERR(chan)) { + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; + pcm->chan[i] = NULL; + } else { + pcm->chan[i] = chan; + } + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + break; + } + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + pcm->chan[1] = pcm->chan[0]; + + return 0; +} + +static void dmaengine_pcm_release_chan(struct dmaengine_pcm *pcm) +{ + unsigned int i; + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; + i++) { + if (!pcm->chan[i]) + continue; + dma_release_channel(pcm->chan[i]); + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + break; + } +} + +/** + * snd_dmaengine_pcm_register - Register a dmaengine based PCM device + * @dev: The parent device for the PCM device + * @config: Platform specific PCM configuration + * @flags: Platform specific quirks + */ +int snd_dmaengine_pcm_register(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags) +{ + struct dmaengine_pcm *pcm; + int ret; + + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + +#ifdef CONFIG_DEBUG_FS + pcm->component.debugfs_prefix = "dma"; +#endif + pcm->config = config; + pcm->flags = flags; + + ret = dmaengine_pcm_request_chan_of(pcm, dev, config); + if (ret) + goto err_free_dma; + + if (config && config->process) + ret = snd_soc_add_component(dev, &pcm->component, + &dmaengine_pcm_component_process, + NULL, 0); + else + ret = snd_soc_add_component(dev, &pcm->component, + &dmaengine_pcm_component, NULL, 0); + if (ret) + goto err_free_dma; + + return 0; + +err_free_dma: + dmaengine_pcm_release_chan(pcm); + kfree(pcm); + return ret; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_register); + +/** + * snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device + * @dev: Parent device the PCM was register with + * + * Removes a dmaengine based PCM device previously registered with + * snd_dmaengine_pcm_register. + */ +void snd_dmaengine_pcm_unregister(struct device *dev) +{ + struct snd_soc_component *component; + struct dmaengine_pcm *pcm; + + component = snd_soc_lookup_component(dev, SND_DMAENGINE_PCM_DRV_NAME); + if (!component) + return; + + pcm = soc_component_to_pcm(component); + + snd_soc_unregister_component(dev); + dmaengine_pcm_release_chan(pcm); + kfree(pcm); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_unregister); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/soc-io.c b/sound/soc/soc-io.c new file mode 100644 index 000000000..1ff9175e9 --- /dev/null +++ b/sound/soc/soc-io.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-io.c -- ASoC register I/O helpers +// +// Copyright 2009-2011 Wolfson Microelectronics PLC. +// +// Author: Mark Brown + +#include +#include +#include +#include +#include + +/** + * snd_soc_component_read() - Read register value + * @component: Component to read from + * @reg: Register to read + * @val: Pointer to where the read value is stored + * + * Return: 0 on success, a negative error code otherwise. + */ +int snd_soc_component_read(struct snd_soc_component *component, + unsigned int reg, unsigned int *val) +{ + int ret; + + if (component->regmap) + ret = regmap_read(component->regmap, reg, val); + else if (component->driver->read) { + *val = component->driver->read(component, reg); + ret = 0; + } + else + ret = -EIO; + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_read); + +unsigned int snd_soc_component_read32(struct snd_soc_component *component, + unsigned int reg) +{ + unsigned int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret < 0) + return -1; + + return val; +} +EXPORT_SYMBOL_GPL(snd_soc_component_read32); + +/** + * snd_soc_component_write() - Write register value + * @component: Component to write to + * @reg: Register to write + * @val: Value to write to the register + * + * Return: 0 on success, a negative error code otherwise. + */ +int snd_soc_component_write(struct snd_soc_component *component, + unsigned int reg, unsigned int val) +{ + if (component->regmap) + return regmap_write(component->regmap, reg, val); + else if (component->driver->write) + return component->driver->write(component, reg, val); + else + return -EIO; +} +EXPORT_SYMBOL_GPL(snd_soc_component_write); + +static int snd_soc_component_update_bits_legacy( + struct snd_soc_component *component, unsigned int reg, + unsigned int mask, unsigned int val, bool *change) +{ + unsigned int old, new; + int ret; + + mutex_lock(&component->io_mutex); + + ret = snd_soc_component_read(component, reg, &old); + if (ret < 0) + goto out_unlock; + + new = (old & ~mask) | (val & mask); + *change = old != new; + if (*change) + ret = snd_soc_component_write(component, reg, new); +out_unlock: + mutex_unlock(&component->io_mutex); + + return ret; +} + +/** + * snd_soc_component_update_bits() - Perform read/modify/write cycle + * @component: Component to update + * @reg: Register to update + * @mask: Mask that specifies which bits to update + * @val: New value for the bits specified by mask + * + * Return: 1 if the operation was successful and the value of the register + * changed, 0 if the operation was successful, but the value did not change. + * Returns a negative error code otherwise. + */ +int snd_soc_component_update_bits(struct snd_soc_component *component, + unsigned int reg, unsigned int mask, unsigned int val) +{ + bool change; + int ret; + + if (component->regmap) + ret = regmap_update_bits_check(component->regmap, reg, mask, + val, &change); + else + ret = snd_soc_component_update_bits_legacy(component, reg, + mask, val, &change); + + if (ret < 0) + return ret; + return change; +} +EXPORT_SYMBOL_GPL(snd_soc_component_update_bits); + +/** + * snd_soc_component_update_bits_async() - Perform asynchronous + * read/modify/write cycle + * @component: Component to update + * @reg: Register to update + * @mask: Mask that specifies which bits to update + * @val: New value for the bits specified by mask + * + * This function is similar to snd_soc_component_update_bits(), but the update + * operation is scheduled asynchronously. This means it may not be completed + * when the function returns. To make sure that all scheduled updates have been + * completed snd_soc_component_async_complete() must be called. + * + * Return: 1 if the operation was successful and the value of the register + * changed, 0 if the operation was successful, but the value did not change. + * Returns a negative error code otherwise. + */ +int snd_soc_component_update_bits_async(struct snd_soc_component *component, + unsigned int reg, unsigned int mask, unsigned int val) +{ + bool change; + int ret; + + if (component->regmap) + ret = regmap_update_bits_check_async(component->regmap, reg, + mask, val, &change); + else + ret = snd_soc_component_update_bits_legacy(component, reg, + mask, val, &change); + + if (ret < 0) + return ret; + return change; +} +EXPORT_SYMBOL_GPL(snd_soc_component_update_bits_async); + +/** + * snd_soc_component_async_complete() - Ensure asynchronous I/O has completed + * @component: Component for which to wait + * + * This function blocks until all asynchronous I/O which has previously been + * scheduled using snd_soc_component_update_bits_async() has completed. + */ +void snd_soc_component_async_complete(struct snd_soc_component *component) +{ + if (component->regmap) + regmap_async_complete(component->regmap); +} +EXPORT_SYMBOL_GPL(snd_soc_component_async_complete); + +/** + * snd_soc_component_test_bits - Test register for change + * @component: component + * @reg: Register to test + * @mask: Mask that specifies which bits to test + * @value: Value to test against + * + * Tests a register with a new value and checks if the new value is + * different from the old value. + * + * Return: 1 for change, otherwise 0. + */ +int snd_soc_component_test_bits(struct snd_soc_component *component, + unsigned int reg, unsigned int mask, unsigned int value) +{ + unsigned int old, new; + int ret; + + ret = snd_soc_component_read(component, reg, &old); + if (ret < 0) + return ret; + new = (old & ~mask) | value; + return old != new; +} +EXPORT_SYMBOL_GPL(snd_soc_component_test_bits); diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c new file mode 100644 index 000000000..2a528e73b --- /dev/null +++ b/sound/soc/soc-jack.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-jack.c -- ALSA SoC jack handling +// +// Copyright 2008 Wolfson Microelectronics PLC. +// +// Author: Mark Brown + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct jack_gpio_tbl { + int count; + struct snd_soc_jack *jack; + struct snd_soc_jack_gpio *gpios; +}; + +/** + * snd_soc_component_set_jack - configure component jack. + * @component: COMPONENTs + * @jack: structure to use for the jack + * @data: can be used if codec driver need extra data for configuring jack + * + * Configures and enables jack detection function. + */ +int snd_soc_component_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *data) +{ + if (component->driver->set_jack) + return component->driver->set_jack(component, jack, data); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_component_set_jack); + +/** + * snd_soc_card_jack_new - Create a new jack + * @card: ASoC card + * @id: an identifying string for this jack + * @type: a bitmask of enum snd_jack_type values that can be detected by + * this jack + * @jack: structure to use for the jack + * @pins: Array of jack pins to be added to the jack or NULL + * @num_pins: Number of elements in the @pins array + * + * Creates a new jack object. + * + * Returns zero if successful, or a negative error code on failure. + * On success jack will be initialised. + */ +int snd_soc_card_jack_new(struct snd_soc_card *card, const char *id, int type, + struct snd_soc_jack *jack, struct snd_soc_jack_pin *pins, + unsigned int num_pins) +{ + int ret; + + mutex_init(&jack->mutex); + jack->card = card; + INIT_LIST_HEAD(&jack->pins); + INIT_LIST_HEAD(&jack->jack_zones); + BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); + + ret = snd_jack_new(card->snd_card, id, type, &jack->jack, false, false); + if (ret) + return ret; + + if (num_pins) + return snd_soc_jack_add_pins(jack, num_pins, pins); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_card_jack_new); + +/** + * snd_soc_jack_report - Report the current status for a jack + * + * @jack: the jack + * @status: a bitmask of enum snd_jack_type values that are currently detected. + * @mask: a bitmask of enum snd_jack_type values that being reported. + * + * If configured using snd_soc_jack_add_pins() then the associated + * DAPM pins will be enabled or disabled as appropriate and DAPM + * synchronised. + * + * Note: This function uses mutexes and should be called from a + * context which can sleep (such as a workqueue). + */ +void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_jack_pin *pin; + unsigned int sync = 0; + int enable; + + if (!jack) + return; + trace_snd_soc_jack_report(jack, mask, status); + + dapm = &jack->card->dapm; + + mutex_lock(&jack->mutex); + + jack->status &= ~mask; + jack->status |= status & mask; + + trace_snd_soc_jack_notify(jack, status); + + list_for_each_entry(pin, &jack->pins, list) { + enable = pin->mask & jack->status; + + if (pin->invert) + enable = !enable; + + if (enable) + snd_soc_dapm_enable_pin(dapm, pin->pin); + else + snd_soc_dapm_disable_pin(dapm, pin->pin); + + /* we need to sync for this case only */ + sync = 1; + } + + /* Report before the DAPM sync to help users updating micbias status */ + blocking_notifier_call_chain(&jack->notifier, jack->status, jack); + + if (sync) + snd_soc_dapm_sync(dapm); + + snd_jack_report(jack->jack, jack->status); + + mutex_unlock(&jack->mutex); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_report); + +/** + * snd_soc_jack_add_zones - Associate voltage zones with jack + * + * @jack: ASoC jack + * @count: Number of zones + * @zones: Array of zones + * + * After this function has been called the zones specified in the + * array will be associated with the jack. + */ +int snd_soc_jack_add_zones(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_zone *zones) +{ + int i; + + for (i = 0; i < count; i++) { + INIT_LIST_HEAD(&zones[i].list); + list_add(&(zones[i].list), &jack->jack_zones); + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_zones); + +/** + * snd_soc_jack_get_type - Based on the mic bias value, this function returns + * the type of jack from the zones declared in the jack type + * + * @jack: ASoC jack + * @micbias_voltage: mic bias voltage at adc channel when jack is plugged in + * + * Based on the mic bias value passed, this function helps identify + * the type of jack from the already declared jack zones + */ +int snd_soc_jack_get_type(struct snd_soc_jack *jack, int micbias_voltage) +{ + struct snd_soc_jack_zone *zone; + + list_for_each_entry(zone, &jack->jack_zones, list) { + if (micbias_voltage >= zone->min_mv && + micbias_voltage < zone->max_mv) + return zone->jack_type; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_get_type); + +/** + * snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack + * + * @jack: ASoC jack + * @count: Number of pins + * @pins: Array of pins + * + * After this function has been called the DAPM pins specified in the + * pins array will have their status updated to reflect the current + * state of the jack whenever the jack status is updated. + */ +int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_pin *pins) +{ + int i; + + for (i = 0; i < count; i++) { + if (!pins[i].pin) { + dev_err(jack->card->dev, "ASoC: No name for pin %d\n", + i); + return -EINVAL; + } + if (!pins[i].mask) { + dev_err(jack->card->dev, "ASoC: No mask for pin %d" + " (%s)\n", i, pins[i].pin); + return -EINVAL; + } + + INIT_LIST_HEAD(&pins[i].list); + list_add(&(pins[i].list), &jack->pins); + snd_jack_add_new_kctl(jack->jack, pins[i].pin, pins[i].mask); + } + + /* Update to reflect the last reported status; canned jack + * implementations are likely to set their state before the + * card has an opportunity to associate pins. + */ + snd_soc_jack_report(jack, 0, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins); + +/** + * snd_soc_jack_notifier_register - Register a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to register + * + * Register for notification of the current status of the jack. Note + * that it is not possible to report additional jack events in the + * callback from the notifier, this is intended to support + * applications such as enabling electrical detection only when a + * mechanical detection event has occurred. + */ +void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_register(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register); + +/** + * snd_soc_jack_notifier_unregister - Unregister a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to unregister + * + * Stop notifying for status changes. + */ +void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_unregister); + +#ifdef CONFIG_GPIOLIB +/* gpio detect */ +static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) +{ + struct snd_soc_jack *jack = gpio->jack; + int enable; + int report; + + enable = gpiod_get_value_cansleep(gpio->desc); + if (gpio->invert) + enable = !enable; + + if (enable) + report = gpio->report; + else + report = 0; + + if (gpio->jack_status_check) + report = gpio->jack_status_check(gpio->data); + + snd_soc_jack_report(jack, report, gpio->report); +} + +/* irq handler for gpio pin */ +static irqreturn_t gpio_handler(int irq, void *data) +{ + struct snd_soc_jack_gpio *gpio = data; + struct device *dev = gpio->jack->card->dev; + + trace_snd_soc_jack_irq(gpio->name); + + if (device_may_wakeup(dev)) + pm_wakeup_event(dev, gpio->debounce_time + 50); + + queue_delayed_work(system_power_efficient_wq, &gpio->work, + msecs_to_jiffies(gpio->debounce_time)); + + return IRQ_HANDLED; +} + +/* gpio work */ +static void gpio_work(struct work_struct *work) +{ + struct snd_soc_jack_gpio *gpio; + + gpio = container_of(work, struct snd_soc_jack_gpio, work.work); + snd_soc_jack_gpio_detect(gpio); +} + +static int snd_soc_jack_pm_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct snd_soc_jack_gpio *gpio = + container_of(nb, struct snd_soc_jack_gpio, pm_notifier); + + switch (action) { + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + /* + * Use workqueue so we do not have to care about running + * concurrently with work triggered by the interrupt handler. + */ + queue_delayed_work(system_power_efficient_wq, &gpio->work, 0); + break; + } + + return NOTIFY_DONE; +} + +static void jack_free_gpios(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_gpio *gpios) +{ + int i; + + for (i = 0; i < count; i++) { + gpiod_unexport(gpios[i].desc); + unregister_pm_notifier(&gpios[i].pm_notifier); + free_irq(gpiod_to_irq(gpios[i].desc), &gpios[i]); + cancel_delayed_work_sync(&gpios[i].work); + gpiod_put(gpios[i].desc); + gpios[i].jack = NULL; + } +} + +static void jack_devres_free_gpios(struct device *dev, void *res) +{ + struct jack_gpio_tbl *tbl = res; + + jack_free_gpios(tbl->jack, tbl->count, tbl->gpios); +} + +/** + * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack + * + * @jack: ASoC jack + * @count: number of pins + * @gpios: array of gpio pins + * + * This function will request gpio, set data direction and request irq + * for each gpio in the array. + */ +int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_gpio *gpios) +{ + int i, ret; + struct jack_gpio_tbl *tbl; + + tbl = devres_alloc(jack_devres_free_gpios, sizeof(*tbl), GFP_KERNEL); + if (!tbl) + return -ENOMEM; + tbl->jack = jack; + tbl->count = count; + tbl->gpios = gpios; + + for (i = 0; i < count; i++) { + if (!gpios[i].name) { + dev_err(jack->card->dev, + "ASoC: No name for gpio at index %d\n", i); + ret = -EINVAL; + goto undo; + } + + if (gpios[i].desc) { + /* Already have a GPIO descriptor. */ + goto got_gpio; + } else if (gpios[i].gpiod_dev) { + /* Get a GPIO descriptor */ + gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev, + gpios[i].name, + gpios[i].idx, GPIOD_IN); + if (IS_ERR(gpios[i].desc)) { + ret = PTR_ERR(gpios[i].desc); + dev_err(gpios[i].gpiod_dev, + "ASoC: Cannot get gpio at index %d: %d", + i, ret); + goto undo; + } + } else { + /* legacy GPIO number */ + if (!gpio_is_valid(gpios[i].gpio)) { + dev_err(jack->card->dev, + "ASoC: Invalid gpio %d\n", + gpios[i].gpio); + ret = -EINVAL; + goto undo; + } + + ret = gpio_request_one(gpios[i].gpio, GPIOF_IN, + gpios[i].name); + if (ret) + goto undo; + + gpios[i].desc = gpio_to_desc(gpios[i].gpio); + } +got_gpio: + INIT_DELAYED_WORK(&gpios[i].work, gpio_work); + gpios[i].jack = jack; + + ret = request_any_context_irq(gpiod_to_irq(gpios[i].desc), + gpio_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + gpios[i].name, + &gpios[i]); + if (ret < 0) + goto err; + + if (gpios[i].wake) { + ret = irq_set_irq_wake(gpiod_to_irq(gpios[i].desc), 1); + if (ret != 0) + dev_err(jack->card->dev, + "ASoC: Failed to mark GPIO at index %d as wake source: %d\n", + i, ret); + } + + /* + * Register PM notifier so we do not miss state transitions + * happening while system is asleep. + */ + gpios[i].pm_notifier.notifier_call = snd_soc_jack_pm_notifier; + register_pm_notifier(&gpios[i].pm_notifier); + + /* Expose GPIO value over sysfs for diagnostic purposes */ + gpiod_export(gpios[i].desc, false); + + /* Update initial jack status */ + schedule_delayed_work(&gpios[i].work, + msecs_to_jiffies(gpios[i].debounce_time)); + } + + devres_add(jack->card->dev, tbl); + return 0; + +err: + gpio_free(gpios[i].gpio); +undo: + jack_free_gpios(jack, i, gpios); + devres_free(tbl); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpios); + +/** + * snd_soc_jack_add_gpiods - Associate GPIO descriptor pins with an ASoC jack + * + * @gpiod_dev: GPIO consumer device + * @jack: ASoC jack + * @count: number of pins + * @gpios: array of gpio pins + * + * This function will request gpio, set data direction and request irq + * for each gpio in the array. + */ +int snd_soc_jack_add_gpiods(struct device *gpiod_dev, + struct snd_soc_jack *jack, + int count, struct snd_soc_jack_gpio *gpios) +{ + int i; + + for (i = 0; i < count; i++) + gpios[i].gpiod_dev = gpiod_dev; + + return snd_soc_jack_add_gpios(jack, count, gpios); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpiods); + +/** + * snd_soc_jack_free_gpios - Release GPIO pins' resources of an ASoC jack + * + * @jack: ASoC jack + * @count: number of pins + * @gpios: array of gpio pins + * + * Release gpio and irq resources for gpio pins associated with an ASoC jack. + */ +void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_gpio *gpios) +{ + jack_free_gpios(jack, count, gpios); + devres_destroy(jack->card->dev, jack_devres_free_gpios, NULL, NULL); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_free_gpios); +#endif /* CONFIG_GPIOLIB */ diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c new file mode 100644 index 000000000..7a37312c8 --- /dev/null +++ b/sound/soc/soc-ops.c @@ -0,0 +1,1025 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-ops.c -- Generic ASoC operations +// +// Copyright 2005 Wolfson Microelectronics PLC. +// Copyright 2005 Openedhand Ltd. +// Copyright (C) 2010 Slimlogic Ltd. +// Copyright (C) 2010 Texas Instruments Inc. +// +// Author: Liam Girdwood +// with code, comments and ideas from :- +// Richard Purdie + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * snd_soc_info_enum_double - enumerated double mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a double enumerated + * mixer control. + * + * Returns 0 for success. + */ +int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + + return snd_ctl_enum_info(uinfo, e->shift_l == e->shift_r ? 1 : 2, + e->items, e->texts); +} +EXPORT_SYMBOL_GPL(snd_soc_info_enum_double); + +/** + * snd_soc_get_enum_double - enumerated double mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a double enumerated mixer. + * + * Returns 0 for success. + */ +int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int val, item; + unsigned int reg_val; + int ret; + + ret = snd_soc_component_read(component, e->reg, ®_val); + if (ret) + return ret; + val = (reg_val >> e->shift_l) & e->mask; + item = snd_soc_enum_val_to_item(e, val); + ucontrol->value.enumerated.item[0] = item; + if (e->shift_l != e->shift_r) { + val = (reg_val >> e->shift_r) & e->mask; + item = snd_soc_enum_val_to_item(e, val); + ucontrol->value.enumerated.item[1] = item; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_enum_double); + +/** + * snd_soc_put_enum_double - enumerated double mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a double enumerated mixer. + * + * Returns 0 for success. + */ +int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val; + unsigned int mask; + + if (item[0] >= e->items) + return -EINVAL; + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + mask = e->mask << e->shift_l; + if (e->shift_l != e->shift_r) { + if (item[1] >= e->items) + return -EINVAL; + val |= snd_soc_enum_item_to_val(e, item[1]) << e->shift_r; + mask |= e->mask << e->shift_r; + } + + return snd_soc_component_update_bits(component, e->reg, mask, val); +} +EXPORT_SYMBOL_GPL(snd_soc_put_enum_double); + +/** + * snd_soc_read_signed - Read a codec register and interpret as signed value + * @component: component + * @reg: Register to read + * @mask: Mask to use after shifting the register value + * @shift: Right shift of register value + * @sign_bit: Bit that describes if a number is negative or not. + * @signed_val: Pointer to where the read value should be stored + * + * This functions reads a codec register. The register value is shifted right + * by 'shift' bits and masked with the given 'mask'. Afterwards it translates + * the given registervalue into a signed integer if sign_bit is non-zero. + * + * Returns 0 on sucess, otherwise an error value + */ +static int snd_soc_read_signed(struct snd_soc_component *component, + unsigned int reg, unsigned int mask, unsigned int shift, + unsigned int sign_bit, int *signed_val) +{ + int ret; + unsigned int val; + + ret = snd_soc_component_read(component, reg, &val); + if (ret < 0) + return ret; + + val = (val >> shift) & mask; + + if (!sign_bit) { + *signed_val = val; + return 0; + } + + /* non-negative number */ + if (!(val & BIT(sign_bit))) { + *signed_val = val; + return 0; + } + + ret = val; + + /* + * The register most probably does not contain a full-sized int. + * Instead we have an arbitrary number of bits in a signed + * representation which has to be translated into a full-sized int. + * This is done by filling up all bits above the sign-bit. + */ + ret |= ~((int)(BIT(sign_bit) - 1)); + + *signed_val = ret; + + return 0; +} + +/** + * snd_soc_info_volsw - single mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a single mixer control, or a double + * mixer control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int platform_max; + + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume")) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = platform_max - mc->min; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw); + +/** + * snd_soc_info_volsw_sx - Mixer info callback for SX TLV controls + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a single mixer control, or a double + * mixer control that spans 2 registers of the SX TLV type. SX TLV controls + * have a range that represents both positive and negative values either side + * of zero but without a sign bit. + * + * Returns 0 for success. + */ +int snd_soc_info_volsw_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + snd_soc_info_volsw(kcontrol, uinfo); + /* Max represents the number of levels in an SX control not the + * maximum value, so add the minimum value back on + */ + uinfo->value.integer.max += mc->min; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw_sx); + +/** + * snd_soc_get_volsw - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + int sign_bit = mc->sign_bit; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + int val; + int ret; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = val - min; + if (invert) + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + + if (snd_soc_volsw_is_stereo(mc)) { + if (reg == reg2) + ret = snd_soc_read_signed(component, reg, mask, rshift, + sign_bit, &val); + else + ret = snd_soc_read_signed(component, reg2, mask, shift, + sign_bit, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[1] = val - min; + if (invert) + ucontrol->value.integer.value[1] = + max - ucontrol->value.integer.value[1]; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw); + +/** + * snd_soc_put_volsw - single mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + unsigned int sign_bit = mc->sign_bit; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + int err, ret; + bool type_2r = false; + unsigned int val2 = 0; + unsigned int val, val_mask; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + val = ucontrol->value.integer.value[0]; + if (mc->platform_max && ((int)val + min) > mc->platform_max) + return -EINVAL; + if (val > max - min) + return -EINVAL; + if (val < 0) + return -EINVAL; + val = (val + min) & mask; + if (invert) + val = max - val; + val_mask = mask << shift; + val = val << shift; + if (snd_soc_volsw_is_stereo(mc)) { + val2 = ucontrol->value.integer.value[1]; + if (mc->platform_max && ((int)val2 + min) > mc->platform_max) + return -EINVAL; + if (val2 > max - min) + return -EINVAL; + if (val2 < 0) + return -EINVAL; + val2 = (val2 + min) & mask; + if (invert) + val2 = max - val2; + if (reg == reg2) { + val_mask |= mask << rshift; + val |= val2 << rshift; + } else { + val2 = val2 << shift; + type_2r = true; + } + } + err = snd_soc_component_update_bits(component, reg, val_mask, val); + if (err < 0) + return err; + ret = err; + + if (type_2r) { + err = snd_soc_component_update_bits(component, reg2, val_mask, + val2); + /* Don't discard any error code or drop change flag */ + if (ret == 0 || err < 0) { + ret = err; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw); + +/** + * snd_soc_get_volsw_sx - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + unsigned int mask = (1U << (fls(min + max) - 1)) - 1; + unsigned int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret < 0) + return ret; + + ucontrol->value.integer.value[0] = ((val >> shift) - min) & mask; + + if (snd_soc_volsw_is_stereo(mc)) { + ret = snd_soc_component_read(component, reg2, &val); + if (ret < 0) + return ret; + + val = ((val >> rshift) - min) & mask; + ucontrol->value.integer.value[1] = val; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw_sx); + +/** + * snd_soc_put_volsw_sx - double mixer set callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a double mixer control that spans 2 registers. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int min = mc->min; + unsigned int mask = (1U << (fls(min + max) - 1)) - 1; + int err = 0; + unsigned int val, val_mask, val2 = 0; + + val = ucontrol->value.integer.value[0]; + if (mc->platform_max && val > mc->platform_max) + return -EINVAL; + if (val > max - min) + return -EINVAL; + if (val < 0) + return -EINVAL; + val_mask = mask << shift; + val = (val + min) & mask; + val = val << shift; + + err = snd_soc_component_update_bits(component, reg, val_mask, val); + if (err < 0) + return err; + + if (snd_soc_volsw_is_stereo(mc)) { + val_mask = mask << rshift; + val2 = (ucontrol->value.integer.value[1] + min) & mask; + val2 = val2 << rshift; + + err = snd_soc_component_update_bits(component, reg2, val_mask, + val2); + } + return err; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); + +/** + * snd_soc_info_volsw_range - single mixer info callback with range. + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information, within a range, about a single + * mixer control. + * + * returns 0 for success. + */ +int snd_soc_info_volsw_range(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int platform_max; + int min = mc->min; + + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = platform_max - min; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw_range); + +/** + * snd_soc_put_volsw_range - single mixer put value callback with range. + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value, within a range, for a single mixer control. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int rreg = mc->rreg; + unsigned int shift = mc->shift; + int min = mc->min; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val, val_mask; + int err, ret, tmp; + + tmp = ucontrol->value.integer.value[0]; + if (tmp < 0) + return -EINVAL; + if (mc->platform_max && tmp > mc->platform_max) + return -EINVAL; + if (tmp > mc->max - mc->min + 1) + return -EINVAL; + + if (invert) + val = (max - ucontrol->value.integer.value[0]) & mask; + else + val = ((ucontrol->value.integer.value[0] + min) & mask); + val_mask = mask << shift; + val = val << shift; + + err = snd_soc_component_update_bits(component, reg, val_mask, val); + if (err < 0) + return err; + ret = err; + + if (snd_soc_volsw_is_stereo(mc)) { + tmp = ucontrol->value.integer.value[1]; + if (tmp < 0) + return -EINVAL; + if (mc->platform_max && tmp > mc->platform_max) + return -EINVAL; + if (tmp > mc->max - mc->min + 1) + return -EINVAL; + + if (invert) + val = (max - ucontrol->value.integer.value[1]) & mask; + else + val = ((ucontrol->value.integer.value[1] + min) & mask); + val_mask = mask << shift; + val = val << shift; + + err = snd_soc_component_update_bits(component, rreg, val_mask, + val); + /* Don't discard any error code or drop change flag */ + if (ret == 0 || err < 0) { + ret = err; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_range); + +/** + * snd_soc_get_volsw_range - single mixer get callback with range + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value, within a range, of a single mixer control. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int rreg = mc->rreg; + unsigned int shift = mc->shift; + int min = mc->min; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; + unsigned int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = (val >> shift) & mask; + if (invert) + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + else + ucontrol->value.integer.value[0] = + ucontrol->value.integer.value[0] - min; + + if (snd_soc_volsw_is_stereo(mc)) { + ret = snd_soc_component_read(component, rreg, &val); + if (ret) + return ret; + + ucontrol->value.integer.value[1] = (val >> shift) & mask; + if (invert) + ucontrol->value.integer.value[1] = + max - ucontrol->value.integer.value[1]; + else + ucontrol->value.integer.value[1] = + ucontrol->value.integer.value[1] - min; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range); + +/** + * snd_soc_limit_volume - Set new limit to an existing volume control. + * + * @card: where to look for the control + * @name: Name of the control + * @max: new maximum limit + * + * Return 0 for success, else error. + */ +int snd_soc_limit_volume(struct snd_soc_card *card, + const char *name, int max) +{ + struct snd_card *snd_card = card->snd_card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + int found = 0; + int ret = -EINVAL; + + /* Sanity check for name and max */ + if (unlikely(!name || max <= 0)) + return -EINVAL; + + list_for_each_entry(kctl, &snd_card->controls, list) { + if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) { + found = 1; + break; + } + } + if (found) { + mc = (struct soc_mixer_control *)kctl->private_value; + if (max <= mc->max) { + mc->platform_max = max; + ret = 0; + } + } + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_limit_volume); + +int snd_soc_bytes_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes *params = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = params->num_regs * component->val_bytes; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_info); + +int snd_soc_bytes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes *params = (void *)kcontrol->private_value; + int ret; + + if (component->regmap) + ret = regmap_raw_read(component->regmap, params->base, + ucontrol->value.bytes.data, + params->num_regs * component->val_bytes); + else + ret = -EINVAL; + + /* Hide any masked bytes to ensure consistent data reporting */ + if (ret == 0 && params->mask) { + switch (component->val_bytes) { + case 1: + ucontrol->value.bytes.data[0] &= ~params->mask; + break; + case 2: + ((u16 *)(&ucontrol->value.bytes.data))[0] + &= cpu_to_be16(~params->mask); + break; + case 4: + ((u32 *)(&ucontrol->value.bytes.data))[0] + &= cpu_to_be32(~params->mask); + break; + default: + return -EINVAL; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_get); + +int snd_soc_bytes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes *params = (void *)kcontrol->private_value; + int ret, len; + unsigned int val, mask; + void *data; + + if (!component->regmap || !params->num_regs) + return -EINVAL; + + len = params->num_regs * component->val_bytes; + + data = kmemdup(ucontrol->value.bytes.data, len, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + /* + * If we've got a mask then we need to preserve the register + * bits. We shouldn't modify the incoming data so take a + * copy. + */ + if (params->mask) { + ret = regmap_read(component->regmap, params->base, &val); + if (ret != 0) + goto out; + + val &= params->mask; + + switch (component->val_bytes) { + case 1: + ((u8 *)data)[0] &= ~params->mask; + ((u8 *)data)[0] |= val; + break; + case 2: + mask = ~params->mask; + ret = regmap_parse_val(component->regmap, + &mask, &mask); + if (ret != 0) + goto out; + + ((u16 *)data)[0] &= mask; + + ret = regmap_parse_val(component->regmap, + &val, &val); + if (ret != 0) + goto out; + + ((u16 *)data)[0] |= val; + break; + case 4: + mask = ~params->mask; + ret = regmap_parse_val(component->regmap, + &mask, &mask); + if (ret != 0) + goto out; + + ((u32 *)data)[0] &= mask; + + ret = regmap_parse_val(component->regmap, + &val, &val); + if (ret != 0) + goto out; + + ((u32 *)data)[0] |= val; + break; + default: + ret = -EINVAL; + goto out; + } + } + + ret = regmap_raw_write(component->regmap, params->base, + data, len); + +out: + kfree(data); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_put); + +int snd_soc_bytes_info_ext(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *ucontrol) +{ + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + + ucontrol->type = SNDRV_CTL_ELEM_TYPE_BYTES; + ucontrol->count = params->max; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_info_ext); + +int snd_soc_bytes_tlv_callback(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + unsigned int count = size < params->max ? size : params->max; + int ret = -ENXIO; + + switch (op_flag) { + case SNDRV_CTL_TLV_OP_READ: + if (params->get) + ret = params->get(kcontrol, tlv, count); + break; + case SNDRV_CTL_TLV_OP_WRITE: + if (params->put) + ret = params->put(kcontrol, tlv, count); + break; + } + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_bytes_tlv_callback); + +/** + * snd_soc_info_xr_sx - signed multi register info callback + * @kcontrol: mreg control + * @uinfo: control element information + * + * Callback to provide information of a control that can + * span multiple codec registers which together + * forms a single signed value in a MSB/LSB manner. + * + * Returns 0 for success. + */ +int snd_soc_info_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = mc->min; + uinfo->value.integer.max = mc->max; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_xr_sx); + +/** + * snd_soc_get_xr_sx - signed multi register get callback + * @kcontrol: mreg control + * @ucontrol: control element information + * + * Callback to get the value of a control that can span + * multiple codec registers which together forms a single + * signed value in a MSB/LSB manner. The control supports + * specifying total no of bits used to allow for bitfields + * across the multiple codec registers. + * + * Returns 0 for success. + */ +int snd_soc_get_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regbase = mc->regbase; + unsigned int regcount = mc->regcount; + unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; + unsigned int regwmask = (1UL<invert; + unsigned long mask = (1UL<nbits)-1; + long min = mc->min; + long max = mc->max; + long val = 0; + unsigned int regval; + unsigned int i; + int ret; + + for (i = 0; i < regcount; i++) { + ret = snd_soc_component_read(component, regbase+i, ®val); + if (ret) + return ret; + val |= (regval & regwmask) << (regwshift*(regcount-i-1)); + } + val &= mask; + if (min < 0 && val > max) + val |= ~mask; + if (invert) + val = max - val; + ucontrol->value.integer.value[0] = val; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_xr_sx); + +/** + * snd_soc_put_xr_sx - signed multi register get callback + * @kcontrol: mreg control + * @ucontrol: control element information + * + * Callback to set the value of a control that can span + * multiple codec registers which together forms a single + * signed value in a MSB/LSB manner. The control supports + * specifying total no of bits used to allow for bitfields + * across the multiple codec registers. + * + * Returns 0 for success. + */ +int snd_soc_put_xr_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mreg_control *mc = + (struct soc_mreg_control *)kcontrol->private_value; + unsigned int regbase = mc->regbase; + unsigned int regcount = mc->regcount; + unsigned int regwshift = component->val_bytes * BITS_PER_BYTE; + unsigned int regwmask = (1UL<invert; + unsigned long mask = (1UL<nbits)-1; + long max = mc->max; + long val = ucontrol->value.integer.value[0]; + unsigned int i, regval, regmask; + int err; + + if (val < mc->min || val > mc->max) + return -EINVAL; + if (invert) + val = max - val; + val &= mask; + for (i = 0; i < regcount; i++) { + regval = (val >> (regwshift*(regcount-i-1))) & regwmask; + regmask = (mask >> (regwshift*(regcount-i-1))) & regwmask; + err = snd_soc_component_update_bits(component, regbase+i, + regmask, regval); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_put_xr_sx); + +/** + * snd_soc_get_strobe - strobe get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback get the value of a strobe mixer control. + * + * Returns 0 for success. + */ +int snd_soc_get_strobe(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = 1 << shift; + unsigned int invert = mc->invert != 0; + unsigned int val; + int ret; + + ret = snd_soc_component_read(component, reg, &val); + if (ret) + return ret; + + val &= mask; + + if (shift != 0 && val != 0) + val = val >> shift; + ucontrol->value.enumerated.item[0] = val ^ invert; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_strobe); + +/** + * snd_soc_put_strobe - strobe put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback strobe a register bit to high then low (or the inverse) + * in one pass of a single mixer enum control. + * + * Returns 1 for success. + */ +int snd_soc_put_strobe(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = 1 << shift; + unsigned int invert = mc->invert != 0; + unsigned int strobe = ucontrol->value.enumerated.item[0] != 0; + unsigned int val1 = (strobe ^ invert) ? mask : 0; + unsigned int val2 = (strobe ^ invert) ? 0 : mask; + int err; + + err = snd_soc_component_update_bits(component, reg, mask, val1); + if (err < 0) + return err; + + return snd_soc_component_update_bits(component, reg, mask, val2); +} +EXPORT_SYMBOL_GPL(snd_soc_put_strobe); diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c new file mode 100644 index 000000000..c03b653bf --- /dev/null +++ b/sound/soc/soc-pcm.c @@ -0,0 +1,3491 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-pcm.c -- ALSA SoC PCM +// +// Copyright 2005 Wolfson Microelectronics PLC. +// Copyright 2005 Openedhand Ltd. +// Copyright (C) 2010 Slimlogic Ltd. +// Copyright (C) 2010 Texas Instruments Inc. +// +// Authors: Liam Girdwood +// Mark Brown + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DPCM_MAX_BE_USERS 8 + +/* + * snd_soc_dai_stream_valid() - check if a DAI supports the given stream + * + * Returns true if the DAI supports the indicated stream type. + */ +static bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int stream) +{ + struct snd_soc_pcm_stream *codec_stream; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &dai->driver->playback; + else + codec_stream = &dai->driver->capture; + + /* If the codec specifies any channels at all, it supports the stream */ + return codec_stream->channels_min; +} + +/** + * snd_soc_runtime_activate() - Increment active count for PCM runtime components + * @rtd: ASoC PCM runtime that is activated + * @stream: Direction of the PCM stream + * + * Increments the active count for all the DAIs and components attached to a PCM + * runtime. Should typically be called when a stream is opened. + * + * Must be called with the rtd->pcm_mutex being held + */ +void snd_soc_runtime_activate(struct snd_soc_pcm_runtime *rtd, int stream) +{ + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i; + + lockdep_assert_held(&rtd->pcm_mutex); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + cpu_dai->playback_active++; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->playback_active++; + } else { + cpu_dai->capture_active++; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->capture_active++; + } + + cpu_dai->active++; + cpu_dai->component->active++; + for (i = 0; i < rtd->num_codecs; i++) { + rtd->codec_dais[i]->active++; + rtd->codec_dais[i]->component->active++; + } +} + +/** + * snd_soc_runtime_deactivate() - Decrement active count for PCM runtime components + * @rtd: ASoC PCM runtime that is deactivated + * @stream: Direction of the PCM stream + * + * Decrements the active count for all the DAIs and components attached to a PCM + * runtime. Should typically be called when a stream is closed. + * + * Must be called with the rtd->pcm_mutex being held + */ +void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) +{ + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i; + + lockdep_assert_held(&rtd->pcm_mutex); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + cpu_dai->playback_active--; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->playback_active--; + } else { + cpu_dai->capture_active--; + for (i = 0; i < rtd->num_codecs; i++) + rtd->codec_dais[i]->capture_active--; + } + + cpu_dai->active--; + cpu_dai->component->active--; + for (i = 0; i < rtd->num_codecs; i++) { + rtd->codec_dais[i]->component->active--; + rtd->codec_dais[i]->active--; + } +} + +/** + * snd_soc_runtime_ignore_pmdown_time() - Check whether to ignore the power down delay + * @rtd: The ASoC PCM runtime that should be checked. + * + * This function checks whether the power down delay should be ignored for a + * specific PCM runtime. Returns true if the delay is 0, if it the DAI link has + * been configured to ignore the delay, or if none of the components benefits + * from having the delay. + */ +bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + bool ignore = true; + + if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) + return true; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + ignore &= !component->driver->use_pmdown_time; + } + + return ignore; +} + +/** + * snd_soc_set_runtime_hwparams - set the runtime hardware parameters + * @substream: the pcm substream + * @hw: the hardware parameters + * + * Sets the substream runtime hardware parameters. + */ +int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, + const struct snd_pcm_hardware *hw) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + runtime->hw.info = hw->info; + runtime->hw.formats = hw->formats; + runtime->hw.period_bytes_min = hw->period_bytes_min; + runtime->hw.period_bytes_max = hw->period_bytes_max; + runtime->hw.periods_min = hw->periods_min; + runtime->hw.periods_max = hw->periods_max; + runtime->hw.buffer_bytes_max = hw->buffer_bytes_max; + runtime->hw.fifo_size = hw->fifo_size; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams); + +/* DPCM stream event, send event to FE and all active BEs. */ +int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, + int event) +{ + struct snd_soc_dpcm *dpcm; + + list_for_each_entry(dpcm, &fe->dpcm[dir].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + + dev_dbg(be->dev, "ASoC: BE %s event %d dir %d\n", + be->dai_link->name, event, dir); + + if ((event == SND_SOC_DAPM_STREAM_STOP) && + (be->dpcm[dir].users >= 1)) + continue; + + snd_soc_dapm_stream_event(be, dir, event); + } + + snd_soc_dapm_stream_event(fe, dir, event); + + return 0; +} + +static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, + struct snd_soc_dai *soc_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret; + + if (soc_dai->rate && (soc_dai->driver->symmetric_rates || + rtd->dai_link->symmetric_rates)) { + dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n", + soc_dai->rate); + + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + soc_dai->rate); + if (ret < 0) { + dev_err(soc_dai->dev, + "ASoC: Unable to apply rate constraint: %d\n", + ret); + return ret; + } + } + + if (soc_dai->channels && (soc_dai->driver->symmetric_channels || + rtd->dai_link->symmetric_channels)) { + dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d channel(s)\n", + soc_dai->channels); + + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + soc_dai->channels); + if (ret < 0) { + dev_err(soc_dai->dev, + "ASoC: Unable to apply channel symmetry constraint: %d\n", + ret); + return ret; + } + } + + if (soc_dai->sample_bits && (soc_dai->driver->symmetric_samplebits || + rtd->dai_link->symmetric_samplebits)) { + dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d sample bits\n", + soc_dai->sample_bits); + + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + soc_dai->sample_bits); + if (ret < 0) { + dev_err(soc_dai->dev, + "ASoC: Unable to apply sample bits symmetry constraint: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int rate, channels, sample_bits, symmetry, i; + + rate = params_rate(params); + channels = params_channels(params); + sample_bits = snd_pcm_format_physical_width(params_format(params)); + + /* reject unmatched parameters when applying symmetry */ + symmetry = cpu_dai->driver->symmetric_rates || + rtd->dai_link->symmetric_rates; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry |= rtd->codec_dais[i]->driver->symmetric_rates; + + if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) { + dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n", + cpu_dai->rate, rate); + return -EINVAL; + } + + symmetry = cpu_dai->driver->symmetric_channels || + rtd->dai_link->symmetric_channels; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry |= rtd->codec_dais[i]->driver->symmetric_channels; + + if (symmetry && cpu_dai->channels && cpu_dai->channels != channels) { + dev_err(rtd->dev, "ASoC: unmatched channel symmetry: %d - %d\n", + cpu_dai->channels, channels); + return -EINVAL; + } + + symmetry = cpu_dai->driver->symmetric_samplebits || + rtd->dai_link->symmetric_samplebits; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry |= rtd->codec_dais[i]->driver->symmetric_samplebits; + + if (symmetry && cpu_dai->sample_bits && cpu_dai->sample_bits != sample_bits) { + dev_err(rtd->dev, "ASoC: unmatched sample bits symmetry: %d - %d\n", + cpu_dai->sample_bits, sample_bits); + return -EINVAL; + } + + return 0; +} + +static bool soc_pcm_has_symmetry(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_driver *cpu_driver = rtd->cpu_dai->driver; + struct snd_soc_dai_link *link = rtd->dai_link; + unsigned int symmetry, i; + + symmetry = cpu_driver->symmetric_rates || link->symmetric_rates || + cpu_driver->symmetric_channels || link->symmetric_channels || + cpu_driver->symmetric_samplebits || link->symmetric_samplebits; + + for (i = 0; i < rtd->num_codecs; i++) + symmetry = symmetry || + rtd->codec_dais[i]->driver->symmetric_rates || + rtd->codec_dais[i]->driver->symmetric_channels || + rtd->codec_dais[i]->driver->symmetric_samplebits; + + return symmetry; +} + +static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret; + + if (!bits) + return; + + ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, bits); + if (ret != 0) + dev_warn(rtd->dev, "ASoC: Failed to set MSB %d: %d\n", + bits, ret); +} + +static void soc_pcm_apply_msb(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i; + unsigned int bits = 0, cpu_bits; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->playback.sig_bits == 0) { + bits = 0; + break; + } + bits = max(codec_dai->driver->playback.sig_bits, bits); + } + cpu_bits = cpu_dai->driver->playback.sig_bits; + } else { + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->capture.sig_bits == 0) { + bits = 0; + break; + } + bits = max(codec_dai->driver->capture.sig_bits, bits); + } + cpu_bits = cpu_dai->driver->capture.sig_bits; + } + + soc_pcm_set_msb(substream, bits); + soc_pcm_set_msb(substream, cpu_bits); +} + +static void soc_pcm_init_runtime_hw(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_driver *cpu_dai_drv = rtd->cpu_dai->driver; + struct snd_soc_dai_driver *codec_dai_drv; + struct snd_soc_pcm_stream *codec_stream; + struct snd_soc_pcm_stream *cpu_stream; + unsigned int chan_min = 0, chan_max = UINT_MAX; + unsigned int rate_min = 0, rate_max = UINT_MAX; + unsigned int rates = UINT_MAX; + u64 formats = ULLONG_MAX; + int i; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_stream = &cpu_dai_drv->playback; + else + cpu_stream = &cpu_dai_drv->capture; + + /* first calculate min/max only for CODECs in the DAI link */ + for (i = 0; i < rtd->num_codecs; i++) { + + /* + * Skip CODECs which don't support the current stream type. + * Otherwise, since the rate, channel, and format values will + * zero in that case, we would have no usable settings left, + * causing the resulting setup to fail. + * At least one CODEC should match, otherwise we should have + * bailed out on a higher level, since there would be no + * CODEC to support the transfer direction in that case. + */ + if (!snd_soc_dai_stream_valid(rtd->codec_dais[i], + substream->stream)) + continue; + + codec_dai_drv = rtd->codec_dais[i]->driver; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &codec_dai_drv->playback; + else + codec_stream = &codec_dai_drv->capture; + chan_min = max(chan_min, codec_stream->channels_min); + chan_max = min(chan_max, codec_stream->channels_max); + rate_min = max(rate_min, codec_stream->rate_min); + rate_max = min_not_zero(rate_max, codec_stream->rate_max); + formats &= codec_stream->formats; + rates = snd_pcm_rate_mask_intersect(codec_stream->rates, rates); + } + + /* + * chan min/max cannot be enforced if there are multiple CODEC DAIs + * connected to a single CPU DAI, use CPU DAI's directly and let + * channel allocation be fixed up later + */ + if (rtd->num_codecs > 1) { + chan_min = cpu_stream->channels_min; + chan_max = cpu_stream->channels_max; + } + + hw->channels_min = max(chan_min, cpu_stream->channels_min); + hw->channels_max = min(chan_max, cpu_stream->channels_max); + if (hw->formats) + hw->formats &= formats & cpu_stream->formats; + else + hw->formats = formats & cpu_stream->formats; + hw->rates = snd_pcm_rate_mask_intersect(rates, cpu_stream->rates); + + snd_pcm_limit_hw_rates(runtime); + + hw->rate_min = max(hw->rate_min, cpu_stream->rate_min); + hw->rate_min = max(hw->rate_min, rate_min); + hw->rate_max = min_not_zero(hw->rate_max, cpu_stream->rate_max); + hw->rate_max = min_not_zero(hw->rate_max, rate_max); +} + +static int soc_pcm_components_close(struct snd_pcm_substream *substream, + struct snd_soc_component *last) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (component == last) + break; + + if (!component->driver->ops || + !component->driver->ops->close) + continue; + + component->driver->ops->close(substream); + } + + return 0; +} + +/* + * Called by ALSA when a PCM substream is opened, the runtime->hw record is + * then initialized and any private data can be allocated. This also calls + * startup for the cpu DAI, component, machine and codec DAI. + */ +static int soc_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + const char *codec_dai_name = "multicodec"; + int i, ret = 0; + + pinctrl_pm_select_default_state(cpu_dai->dev); + for (i = 0; i < rtd->num_codecs; i++) + pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + pm_runtime_get_sync(component->dev); + } + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + /* startup the audio subsystem */ + if (cpu_dai->driver->ops->startup) { + ret = cpu_dai->driver->ops->startup(substream, cpu_dai); + if (ret < 0) { + dev_err(cpu_dai->dev, "ASoC: can't open interface" + " %s: %d\n", cpu_dai->name, ret); + goto out; + } + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->open) + continue; + + ret = component->driver->ops->open(substream); + if (ret < 0) { + dev_err(component->dev, + "ASoC: can't open component %s: %d\n", + component->name, ret); + goto component_err; + } + } + component = NULL; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->startup) { + ret = codec_dai->driver->ops->startup(substream, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "ASoC: can't open codec %s: %d\n", + codec_dai->name, ret); + goto codec_dai_err; + } + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_dai->tx_mask = 0; + else + codec_dai->rx_mask = 0; + } + + if (rtd->dai_link->ops->startup) { + ret = rtd->dai_link->ops->startup(substream); + if (ret < 0) { + pr_err("ASoC: %s startup failed: %d\n", + rtd->dai_link->name, ret); + goto machine_err; + } + } + + /* Dynamic PCM DAI links compat checks use dynamic capabilities */ + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) + goto dynamic; + + /* Check that the codec and cpu DAIs are compatible */ + soc_pcm_init_runtime_hw(substream); + + if (rtd->num_codecs == 1) + codec_dai_name = rtd->codec_dai->name; + + if (soc_pcm_has_symmetry(substream)) + runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + + ret = -EINVAL; + if (!runtime->hw.rates) { + printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n", + codec_dai_name, cpu_dai->name); + goto config_err; + } + if (!runtime->hw.formats) { + printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n", + codec_dai_name, cpu_dai->name); + goto config_err; + } + if (!runtime->hw.channels_min || !runtime->hw.channels_max || + runtime->hw.channels_min > runtime->hw.channels_max) { + printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n", + codec_dai_name, cpu_dai->name); + goto config_err; + } + + soc_pcm_apply_msb(substream); + + /* Symmetry only applies if we've already got an active stream. */ + if (cpu_dai->active) { + ret = soc_pcm_apply_symmetry(substream, cpu_dai); + if (ret != 0) + goto config_err; + } + + for (i = 0; i < rtd->num_codecs; i++) { + if (rtd->codec_dais[i]->active) { + ret = soc_pcm_apply_symmetry(substream, + rtd->codec_dais[i]); + if (ret != 0) + goto config_err; + } + } + + pr_debug("ASoC: %s <-> %s info:\n", + codec_dai_name, cpu_dai->name); + pr_debug("ASoC: rate mask 0x%x\n", runtime->hw.rates); + pr_debug("ASoC: min ch %d max ch %d\n", runtime->hw.channels_min, + runtime->hw.channels_max); + pr_debug("ASoC: min rate %d max rate %d\n", runtime->hw.rate_min, + runtime->hw.rate_max); + +dynamic: + + snd_soc_runtime_activate(rtd, substream->stream); + + mutex_unlock(&rtd->pcm_mutex); + return 0; + +config_err: + if (rtd->dai_link->ops->shutdown) + rtd->dai_link->ops->shutdown(substream); + +machine_err: + i = rtd->num_codecs; + +codec_dai_err: + while (--i >= 0) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->shutdown) + codec_dai->driver->ops->shutdown(substream, codec_dai); + } + +component_err: + soc_pcm_components_close(substream, component); + + if (cpu_dai->driver->ops->shutdown) + cpu_dai->driver->ops->shutdown(substream, cpu_dai); +out: + mutex_unlock(&rtd->pcm_mutex); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + pm_runtime_mark_last_busy(component->dev); + pm_runtime_put_autosuspend(component->dev); + } + + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->active) + pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); + } + if (!cpu_dai->active) + pinctrl_pm_select_sleep_state(cpu_dai->dev); + + return ret; +} + +/* + * Power down the audio subsystem pmdown_time msecs after close is called. + * This is to ensure there are no pops or clicks in between any music tracks + * due to DAPM power cycling. + */ +static void close_delayed_work(struct work_struct *work) +{ + struct snd_soc_pcm_runtime *rtd = + container_of(work, struct snd_soc_pcm_runtime, delayed_work.work); + struct snd_soc_dai *codec_dai = rtd->codec_dais[0]; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + dev_dbg(rtd->dev, "ASoC: pop wq checking: %s status: %s waiting: %s\n", + codec_dai->driver->playback.stream_name, + codec_dai->playback_active ? "active" : "inactive", + rtd->pop_wait ? "yes" : "no"); + + /* are we waiting on this codec DAI stream */ + if (rtd->pop_wait == 1) { + rtd->pop_wait = 0; + snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_STOP); + } + + mutex_unlock(&rtd->pcm_mutex); +} + +/* + * Called by ALSA when a PCM substream is closed. Private data can be + * freed here. The cpu DAI, codec DAI, machine and components are also + * shutdown. + */ +static int soc_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + snd_soc_runtime_deactivate(rtd, substream->stream); + + /* clear the corresponding DAIs rate when inactive */ + if (!cpu_dai->active) + cpu_dai->rate = 0; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (!codec_dai->active) + codec_dai->rate = 0; + } + + snd_soc_dai_digital_mute(cpu_dai, 1, substream->stream); + + if (cpu_dai->driver->ops->shutdown) + cpu_dai->driver->ops->shutdown(substream, cpu_dai); + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->shutdown) + codec_dai->driver->ops->shutdown(substream, codec_dai); + } + + if (rtd->dai_link->ops->shutdown) + rtd->dai_link->ops->shutdown(substream); + + soc_pcm_components_close(substream, NULL); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (snd_soc_runtime_ignore_pmdown_time(rtd)) { + /* powered down playback stream now */ + snd_soc_dapm_stream_event(rtd, + SNDRV_PCM_STREAM_PLAYBACK, + SND_SOC_DAPM_STREAM_STOP); + } else { + /* start delayed pop wq here for playback streams */ + rtd->pop_wait = 1; + queue_delayed_work(system_power_efficient_wq, + &rtd->delayed_work, + msecs_to_jiffies(rtd->pmdown_time)); + } + } else { + /* capture streams can be powered down now */ + snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE, + SND_SOC_DAPM_STREAM_STOP); + } + + mutex_unlock(&rtd->pcm_mutex); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + pm_runtime_mark_last_busy(component->dev); + pm_runtime_put_autosuspend(component->dev); + } + + for (i = 0; i < rtd->num_codecs; i++) { + if (!rtd->codec_dais[i]->active) + pinctrl_pm_select_sleep_state(rtd->codec_dais[i]->dev); + } + if (!cpu_dai->active) + pinctrl_pm_select_sleep_state(cpu_dai->dev); + + return 0; +} + +/* + * Called by ALSA when the PCM substream is prepared, can set format, sample + * rate, etc. This function is non atomic and can be called multiple times, + * it can refer to the runtime info. + */ +static int soc_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i, ret = 0; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + if (rtd->dai_link->ops->prepare) { + ret = rtd->dai_link->ops->prepare(substream); + if (ret < 0) { + dev_err(rtd->card->dev, "ASoC: machine prepare error:" + " %d\n", ret); + goto out; + } + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->prepare) + continue; + + ret = component->driver->ops->prepare(substream); + if (ret < 0) { + dev_err(component->dev, + "ASoC: platform prepare error: %d\n", ret); + goto out; + } + } + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->prepare) { + ret = codec_dai->driver->ops->prepare(substream, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "ASoC: codec DAI prepare error: %d\n", + ret); + goto out; + } + } + } + + if (cpu_dai->driver->ops->prepare) { + ret = cpu_dai->driver->ops->prepare(substream, cpu_dai); + if (ret < 0) { + dev_err(cpu_dai->dev, + "ASoC: cpu DAI prepare error: %d\n", ret); + goto out; + } + } + + /* cancel any delayed stream shutdown that is pending */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + rtd->pop_wait) { + rtd->pop_wait = 0; + cancel_delayed_work(&rtd->delayed_work); + } + + snd_soc_dapm_stream_event(rtd, substream->stream, + SND_SOC_DAPM_STREAM_START); + + for (i = 0; i < rtd->num_codecs; i++) + snd_soc_dai_digital_mute(rtd->codec_dais[i], 0, + substream->stream); + snd_soc_dai_digital_mute(cpu_dai, 0, substream->stream); + +out: + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, + unsigned int mask) +{ + struct snd_interval *interval; + int channels = hweight_long(mask); + + interval = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + interval->min = channels; + interval->max = channels; +} + +int soc_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int ret; + + /* perform any topology hw_params fixups before DAI */ + if (rtd->dai_link->be_hw_params_fixup) { + ret = rtd->dai_link->be_hw_params_fixup(rtd, params); + if (ret < 0) { + dev_err(rtd->dev, + "ASoC: hw_params topology fixup failed %d\n", + ret); + return ret; + } + } + + if (dai->driver->ops->hw_params) { + ret = dai->driver->ops->hw_params(substream, params, dai); + if (ret < 0) { + dev_err(dai->dev, "ASoC: can't set %s hw params: %d\n", + dai->name, ret); + return ret; + } + } + + return 0; +} + +static int soc_pcm_components_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_component *last) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (component == last) + break; + + if (!component->driver->ops || + !component->driver->ops->hw_free) + continue; + + component->driver->ops->hw_free(substream); + } + + return 0; +} + +/* + * Called by ALSA when the hardware params are set by application. This + * function can also be called multiple times and can allocate buffers + * (using snd_pcm_lib_* ). It's non-atomic. + */ +static int soc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i, ret = 0; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + if (rtd->dai_link->ops->hw_params) { + ret = rtd->dai_link->ops->hw_params(substream, params); + if (ret < 0) { + dev_err(rtd->card->dev, "ASoC: machine hw_params" + " failed: %d\n", ret); + goto out; + } + } + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + struct snd_pcm_hw_params codec_params; + + /* + * Skip CODECs which don't support the current stream type, + * the idea being that if a CODEC is not used for the currently + * set up transfer direction, it should not need to be + * configured, especially since the configuration used might + * not even be supported by that CODEC. There may be cases + * however where a CODEC needs to be set up although it is + * actually not being used for the transfer, e.g. if a + * capture-only CODEC is acting as an LRCLK and/or BCLK master + * for the DAI link including a playback-only CODEC. + * If this becomes necessary, we will have to augment the + * machine driver setup with information on how to act, so + * we can do the right thing here. + */ + if (!snd_soc_dai_stream_valid(codec_dai, substream->stream)) + continue; + + /* copy params for each codec */ + codec_params = *params; + + /* fixup params based on TDM slot masks */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + codec_dai->tx_mask) + soc_pcm_codec_params_fixup(&codec_params, + codec_dai->tx_mask); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + codec_dai->rx_mask) + soc_pcm_codec_params_fixup(&codec_params, + codec_dai->rx_mask); + + ret = soc_dai_hw_params(substream, &codec_params, codec_dai); + if(ret < 0) + goto codec_err; + + codec_dai->rate = params_rate(&codec_params); + codec_dai->channels = params_channels(&codec_params); + codec_dai->sample_bits = snd_pcm_format_physical_width( + params_format(&codec_params)); + } + + ret = soc_dai_hw_params(substream, params, cpu_dai); + if (ret < 0) + goto interface_err; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->hw_params) + continue; + + ret = component->driver->ops->hw_params(substream, params); + if (ret < 0) { + dev_err(component->dev, + "ASoC: %s hw params failed: %d\n", + component->name, ret); + goto component_err; + } + } + component = NULL; + + /* store the parameters for each DAIs */ + cpu_dai->rate = params_rate(params); + cpu_dai->channels = params_channels(params); + cpu_dai->sample_bits = + snd_pcm_format_physical_width(params_format(params)); + + ret = soc_pcm_params_symmetry(substream, params); + if (ret) + goto component_err; +out: + mutex_unlock(&rtd->pcm_mutex); + return ret; + +component_err: + soc_pcm_components_hw_free(substream, component); + + if (cpu_dai->driver->ops->hw_free) + cpu_dai->driver->ops->hw_free(substream, cpu_dai); + +interface_err: + i = rtd->num_codecs; + +codec_err: + while (--i >= 0) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->hw_free) + codec_dai->driver->ops->hw_free(substream, codec_dai); + codec_dai->rate = 0; + } + + if (rtd->dai_link->ops->hw_free) + rtd->dai_link->ops->hw_free(substream); + + mutex_unlock(&rtd->pcm_mutex); + return ret; +} + +/* + * Frees resources allocated by hw_params, can be called multiple times + */ +static int soc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + bool playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i; + + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + /* clear the corresponding DAIs parameters when going to be inactive */ + if (cpu_dai->active == 1) { + cpu_dai->rate = 0; + cpu_dai->channels = 0; + cpu_dai->sample_bits = 0; + } + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->active == 1) { + codec_dai->rate = 0; + codec_dai->channels = 0; + codec_dai->sample_bits = 0; + } + } + + /* apply codec digital mute */ + for (i = 0; i < rtd->num_codecs; i++) { + if ((playback && rtd->codec_dais[i]->playback_active == 1) || + (!playback && rtd->codec_dais[i]->capture_active == 1)) + snd_soc_dai_digital_mute(rtd->codec_dais[i], 1, + substream->stream); + } + + /* free any machine hw params */ + if (rtd->dai_link->ops->hw_free) + rtd->dai_link->ops->hw_free(substream); + + /* free any component resources */ + soc_pcm_components_hw_free(substream, NULL); + + /* now free hw params for the DAIs */ + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->hw_free) + codec_dai->driver->ops->hw_free(substream, codec_dai); + } + + if (cpu_dai->driver->ops->hw_free) + cpu_dai->driver->ops->hw_free(substream, cpu_dai); + + mutex_unlock(&rtd->pcm_mutex); + return 0; +} + +static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->trigger) { + ret = codec_dai->driver->ops->trigger(substream, + cmd, codec_dai); + if (ret < 0) + return ret; + } + } + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->trigger) + continue; + + ret = component->driver->ops->trigger(substream, cmd); + if (ret < 0) + return ret; + } + + if (cpu_dai->driver->ops->trigger) { + ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); + if (ret < 0) + return ret; + } + + if (rtd->dai_link->ops->trigger) { + ret = rtd->dai_link->ops->trigger(substream, cmd); + if (ret < 0) + return ret; + } + + return 0; +} + +static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + int i, ret; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->bespoke_trigger) { + ret = codec_dai->driver->ops->bespoke_trigger(substream, + cmd, codec_dai); + if (ret < 0) + return ret; + } + } + + if (cpu_dai->driver->ops->bespoke_trigger) { + ret = cpu_dai->driver->ops->bespoke_trigger(substream, cmd, cpu_dai); + if (ret < 0) + return ret; + } + return 0; +} +/* + * soc level wrapper for pointer callback + * If cpu_dai, codec_dai, component driver has the delay callback, then + * the runtime->delay will be updated accordingly. + */ +static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t offset = 0; + snd_pcm_sframes_t delay = 0; + snd_pcm_sframes_t codec_delay = 0; + int i; + + /* clearing the previous total delay */ + runtime->delay = 0; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->pointer) + continue; + + /* FIXME: use 1st pointer */ + offset = component->driver->ops->pointer(substream); + break; + } + /* base delay if assigned in pointer callback */ + delay = runtime->delay; + + if (cpu_dai->driver->ops->delay) + delay += cpu_dai->driver->ops->delay(substream, cpu_dai); + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->ops->delay) + codec_delay = max(codec_delay, + codec_dai->driver->ops->delay(substream, + codec_dai)); + } + delay += codec_delay; + + runtime->delay = delay; + + return offset; +} + +/* connect a FE and BE */ +static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + unsigned long flags; + + /* only add new dpcms */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + if (dpcm->be == be && dpcm->fe == fe) + return 0; + } + + dpcm = kzalloc(sizeof(struct snd_soc_dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + + dpcm->be = be; + dpcm->fe = fe; + be->dpcm[stream].runtime = fe->dpcm[stream].runtime; + dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW; + spin_lock_irqsave(&fe->card->dpcm_lock, flags); + list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients); + list_add(&dpcm->list_fe, &be->dpcm[stream].fe_clients); + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); + + dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n", + stream ? "capture" : "playback", fe->dai_link->name, + stream ? "<-" : "->", be->dai_link->name); + +#ifdef CONFIG_DEBUG_FS + if (fe->debugfs_dpcm_root) + dpcm->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644, + fe->debugfs_dpcm_root, &dpcm->state); +#endif + return 1; +} + +/* reparent a BE onto another FE */ +static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + struct snd_pcm_substream *fe_substream, *be_substream; + + /* reparent if BE is connected to other FEs */ + if (!be->dpcm[stream].users) + return; + + be_substream = snd_soc_dpcm_get_substream(be, stream); + + list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + if (dpcm->fe == fe) + continue; + + dev_dbg(fe->dev, "reparent %s path %s %s %s\n", + stream ? "capture" : "playback", + dpcm->fe->dai_link->name, + stream ? "<-" : "->", dpcm->be->dai_link->name); + + fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, stream); + be_substream->runtime = fe_substream->runtime; + break; + } +} + +/* disconnect a BE and FE */ +void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm, *d; + unsigned long flags; + + list_for_each_entry_safe(dpcm, d, &fe->dpcm[stream].be_clients, list_be) { + dev_dbg(fe->dev, "ASoC: BE %s disconnect check for %s\n", + stream ? "capture" : "playback", + dpcm->be->dai_link->name); + + if (dpcm->state != SND_SOC_DPCM_LINK_STATE_FREE) + continue; + + dev_dbg(fe->dev, "freed DSP %s path %s %s %s\n", + stream ? "capture" : "playback", fe->dai_link->name, + stream ? "<-" : "->", dpcm->be->dai_link->name); + + /* BEs still alive need new FE */ + dpcm_be_reparent(fe, dpcm->be, stream); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(dpcm->debugfs_state); +#endif + spin_lock_irqsave(&fe->card->dpcm_lock, flags); + list_del(&dpcm->list_be); + list_del(&dpcm->list_fe); + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); + kfree(dpcm); + } +} + +/* get BE for DAI widget and stream */ +static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, + struct snd_soc_dapm_widget *widget, int stream) +{ + struct snd_soc_pcm_runtime *be; + int i; + + dev_dbg(card->dev, "ASoC: find BE for widget %s\n", widget->name); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + list_for_each_entry(be, &card->rtd_list, list) { + + if (!be->dai_link->no_pcm) + continue; + + dev_dbg(card->dev, "ASoC: try BE : %s\n", + be->cpu_dai->playback_widget ? + be->cpu_dai->playback_widget->name : "(not set)"); + + if (be->cpu_dai->playback_widget == widget) + return be; + + for (i = 0; i < be->num_codecs; i++) { + struct snd_soc_dai *dai = be->codec_dais[i]; + if (dai->playback_widget == widget) + return be; + } + } + } else { + + list_for_each_entry(be, &card->rtd_list, list) { + + if (!be->dai_link->no_pcm) + continue; + + dev_dbg(card->dev, "ASoC: try BE %s\n", + be->cpu_dai->capture_widget ? + be->cpu_dai->capture_widget->name : "(not set)"); + + if (be->cpu_dai->capture_widget == widget) + return be; + + for (i = 0; i < be->num_codecs; i++) { + struct snd_soc_dai *dai = be->codec_dais[i]; + if (dai->capture_widget == widget) + return be; + } + } + } + + /* dai link name and stream name set correctly ? */ + dev_err(card->dev, "ASoC: can't get %s BE for %s\n", + stream ? "capture" : "playback", widget->name); + return NULL; +} + +static inline struct snd_soc_dapm_widget * + dai_get_widget(struct snd_soc_dai *dai, int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + return dai->playback_widget; + else + return dai->capture_widget; +} + +static int widget_in_list(struct snd_soc_dapm_widget_list *list, + struct snd_soc_dapm_widget *widget) +{ + int i; + + for (i = 0; i < list->num_widgets; i++) { + if (widget == list->widgets[i]) + return 1; + } + + return 0; +} + +static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, + enum snd_soc_dapm_direction dir) +{ + struct snd_soc_card *card = widget->dapm->card; + struct snd_soc_pcm_runtime *rtd; + int i; + + if (dir == SND_SOC_DAPM_DIR_OUT) { + list_for_each_entry(rtd, &card->rtd_list, list) { + if (!rtd->dai_link->no_pcm) + continue; + + if (rtd->cpu_dai->playback_widget == widget) + return true; + + for (i = 0; i < rtd->num_codecs; ++i) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + if (dai->playback_widget == widget) + return true; + } + } + } else { /* SND_SOC_DAPM_DIR_IN */ + list_for_each_entry(rtd, &card->rtd_list, list) { + if (!rtd->dai_link->no_pcm) + continue; + + if (rtd->cpu_dai->capture_widget == widget) + return true; + + for (i = 0; i < rtd->num_codecs; ++i) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + if (dai->capture_widget == widget) + return true; + } + } + } + + return false; +} + +int dpcm_path_get(struct snd_soc_pcm_runtime *fe, + int stream, struct snd_soc_dapm_widget_list **list) +{ + struct snd_soc_dai *cpu_dai = fe->cpu_dai; + int paths; + + /* get number of valid DAI paths and their widgets */ + paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list, + dpcm_end_walk_at_be); + + dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, + stream ? "capture" : "playback"); + + return paths; +} + +static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_dpcm *dpcm; + struct snd_soc_dapm_widget_list *list = *list_; + struct snd_soc_dapm_widget *widget; + int prune = 0; + + /* Destroy any old FE <--> BE connections */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + unsigned int i; + + /* is there a valid CPU DAI widget for this BE */ + widget = dai_get_widget(dpcm->be->cpu_dai, stream); + + /* prune the BE if it's no longer in our active list */ + if (widget && widget_in_list(list, widget)) + continue; + + /* is there a valid CODEC DAI widget for this BE */ + for (i = 0; i < dpcm->be->num_codecs; i++) { + struct snd_soc_dai *dai = dpcm->be->codec_dais[i]; + widget = dai_get_widget(dai, stream); + + /* prune the BE if it's no longer in our active list */ + if (widget && widget_in_list(list, widget)) + continue; + } + + dev_dbg(fe->dev, "ASoC: pruning %s BE %s for %s\n", + stream ? "capture" : "playback", + dpcm->be->dai_link->name, fe->dai_link->name); + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + dpcm->be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + prune++; + } + + dev_dbg(fe->dev, "ASoC: found %d old BE paths for pruning\n", prune); + return prune; +} + +static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_card *card = fe->card; + struct snd_soc_dapm_widget_list *list = *list_; + struct snd_soc_pcm_runtime *be; + int i, new = 0, err; + + /* Create any new FE <--> BE connections */ + for (i = 0; i < list->num_widgets; i++) { + + switch (list->widgets[i]->id) { + case snd_soc_dapm_dai_in: + if (stream != SNDRV_PCM_STREAM_PLAYBACK) + continue; + break; + case snd_soc_dapm_dai_out: + if (stream != SNDRV_PCM_STREAM_CAPTURE) + continue; + break; + default: + continue; + } + + /* is there a valid BE rtd for this widget */ + be = dpcm_get_be(card, list->widgets[i], stream); + if (!be) { + dev_err(fe->dev, "ASoC: no BE found for %s\n", + list->widgets[i]->name); + continue; + } + + /* make sure BE is a real BE */ + if (!be->dai_link->no_pcm) + continue; + + /* don't connect if FE is not running */ + if (!fe->dpcm[stream].runtime && !fe->fe_compr) + continue; + + /* newly connected FE and BE */ + err = dpcm_be_connect(fe, be, stream); + if (err < 0) { + dev_err(fe->dev, "ASoC: can't connect %s\n", + list->widgets[i]->name); + break; + } else if (err == 0) /* already connected */ + continue; + + /* new */ + be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + new++; + } + + dev_dbg(fe->dev, "ASoC: found %d new BE paths\n", new); + return new; +} + +/* + * Find the corresponding BE DAIs that source or sink audio to this + * FE substream. + */ +int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, + int stream, struct snd_soc_dapm_widget_list **list, int new) +{ + if (new) + return dpcm_add_paths(fe, stream, list); + else + return dpcm_prune_paths(fe, stream, list); +} + +void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + unsigned long flags; + + spin_lock_irqsave(&fe->card->dpcm_lock, flags); + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->be->dpcm[stream].runtime_update = + SND_SOC_DPCM_UPDATE_NO; + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); +} + +static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, + int stream) +{ + struct snd_soc_dpcm *dpcm; + + /* disable any enabled and non active backends */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "ASoC: no users %s at close - state %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + continue; + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } +} + +int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + int err, count = 0; + + /* only startup BE DAIs that are either sinks or sources to this FE DAI */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (!be_substream) { + dev_err(be->dev, "ASoC: no backend %s stream\n", + stream ? "capture" : "playback"); + continue; + } + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* first time the dpcm is open ? */ + if (be->dpcm[stream].users == DPCM_MAX_BE_USERS) + dev_err(be->dev, "ASoC: too many users %s at open %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (be->dpcm[stream].users++ != 0) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_NEW) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_CLOSE)) + continue; + + dev_dbg(be->dev, "ASoC: open %s BE %s\n", + stream ? "capture" : "playback", be->dai_link->name); + + be_substream->runtime = be->dpcm[stream].runtime; + err = soc_pcm_open(be_substream); + if (err < 0) { + dev_err(be->dev, "ASoC: BE open failed %d\n", err); + be->dpcm[stream].users--; + if (be->dpcm[stream].users < 0) + dev_err(be->dev, "ASoC: no users %s at unwind %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + goto unwind; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + count++; + } + + return count; + +unwind: + /* disable any enabled and non active backends */ + list_for_each_entry_continue_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "ASoC: no users %s at close %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + continue; + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } + + return err; +} + +static void dpcm_init_runtime_hw(struct snd_pcm_runtime *runtime, + struct snd_soc_pcm_stream *stream) +{ + runtime->hw.rate_min = stream->rate_min; + runtime->hw.rate_max = min_not_zero(stream->rate_max, UINT_MAX); + runtime->hw.channels_min = stream->channels_min; + runtime->hw.channels_max = stream->channels_max; + if (runtime->hw.formats) + runtime->hw.formats &= stream->formats; + else + runtime->hw.formats = stream->formats; + runtime->hw.rates = stream->rates; +} + +static void dpcm_runtime_merge_format(struct snd_pcm_substream *substream, + u64 *formats) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_dpcm *dpcm; + int stream = substream->stream; + + if (!fe->dai_link->dpcm_merged_format) + return; + + /* + * It returns merged BE codec format + * if FE want to use it (= dpcm_merged_format) + */ + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_soc_dai_driver *codec_dai_drv; + struct snd_soc_pcm_stream *codec_stream; + int i; + + for (i = 0; i < be->num_codecs; i++) { + /* + * Skip CODECs which don't support the current stream + * type. See soc_pcm_init_runtime_hw() for more details + */ + if (!snd_soc_dai_stream_valid(be->codec_dais[i], + stream)) + continue; + + codec_dai_drv = be->codec_dais[i]->driver; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &codec_dai_drv->playback; + else + codec_stream = &codec_dai_drv->capture; + + *formats &= codec_stream->formats; + } + } +} + +static void dpcm_runtime_merge_chan(struct snd_pcm_substream *substream, + unsigned int *channels_min, + unsigned int *channels_max) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_dpcm *dpcm; + int stream = substream->stream; + + if (!fe->dai_link->dpcm_merged_chan) + return; + + /* + * It returns merged BE codec channel; + * if FE want to use it (= dpcm_merged_chan) + */ + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_soc_dai_driver *cpu_dai_drv = be->cpu_dai->driver; + struct snd_soc_dai_driver *codec_dai_drv; + struct snd_soc_pcm_stream *codec_stream; + struct snd_soc_pcm_stream *cpu_stream; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_stream = &cpu_dai_drv->playback; + else + cpu_stream = &cpu_dai_drv->capture; + + *channels_min = max(*channels_min, cpu_stream->channels_min); + *channels_max = min(*channels_max, cpu_stream->channels_max); + + /* + * chan min/max cannot be enforced if there are multiple CODEC + * DAIs connected to a single CPU DAI, use CPU DAI's directly + */ + if (be->num_codecs == 1) { + codec_dai_drv = be->codec_dais[0]->driver; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &codec_dai_drv->playback; + else + codec_stream = &codec_dai_drv->capture; + + *channels_min = max(*channels_min, + codec_stream->channels_min); + *channels_max = min(*channels_max, + codec_stream->channels_max); + } + } +} + +static void dpcm_runtime_merge_rate(struct snd_pcm_substream *substream, + unsigned int *rates, + unsigned int *rate_min, + unsigned int *rate_max) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_dpcm *dpcm; + int stream = substream->stream; + + if (!fe->dai_link->dpcm_merged_rate) + return; + + /* + * It returns merged BE codec channel; + * if FE want to use it (= dpcm_merged_chan) + */ + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_soc_dai_driver *cpu_dai_drv = be->cpu_dai->driver; + struct snd_soc_dai_driver *codec_dai_drv; + struct snd_soc_pcm_stream *codec_stream; + struct snd_soc_pcm_stream *cpu_stream; + int i; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_stream = &cpu_dai_drv->playback; + else + cpu_stream = &cpu_dai_drv->capture; + + *rate_min = max(*rate_min, cpu_stream->rate_min); + *rate_max = min_not_zero(*rate_max, cpu_stream->rate_max); + *rates = snd_pcm_rate_mask_intersect(*rates, cpu_stream->rates); + + for (i = 0; i < be->num_codecs; i++) { + /* + * Skip CODECs which don't support the current stream + * type. See soc_pcm_init_runtime_hw() for more details + */ + if (!snd_soc_dai_stream_valid(be->codec_dais[i], + stream)) + continue; + + codec_dai_drv = be->codec_dais[i]->driver; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + codec_stream = &codec_dai_drv->playback; + else + codec_stream = &codec_dai_drv->capture; + + *rate_min = max(*rate_min, codec_stream->rate_min); + *rate_max = min_not_zero(*rate_max, + codec_stream->rate_max); + *rates = snd_pcm_rate_mask_intersect(*rates, + codec_stream->rates); + } + } +} + +static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback); + else + dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture); + + dpcm_runtime_merge_format(substream, &runtime->hw.formats); + dpcm_runtime_merge_chan(substream, &runtime->hw.channels_min, + &runtime->hw.channels_max); + dpcm_runtime_merge_rate(substream, &runtime->hw.rates, + &runtime->hw.rate_min, &runtime->hw.rate_max); +} + +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd); + +/* Set FE's runtime_update state; the state is protected via PCM stream lock + * for avoiding the race with trigger callback. + * If the state is unset and a trigger is pending while the previous operation, + * process the pending trigger action here. + */ +static void dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe, + int stream, enum snd_soc_dpcm_update state) +{ + struct snd_pcm_substream *substream = + snd_soc_dpcm_get_substream(fe, stream); + + snd_pcm_stream_lock_irq(substream); + if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) { + dpcm_fe_dai_do_trigger(substream, + fe->dpcm[stream].trigger_pending - 1); + fe->dpcm[stream].trigger_pending = 0; + } + fe->dpcm[stream].runtime_update = state; + snd_pcm_stream_unlock_irq(substream); +} + +static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, + int stream) +{ + struct snd_soc_dpcm *dpcm; + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_soc_dai *fe_cpu_dai = fe->cpu_dai; + int err; + + /* apply symmetry for FE */ + if (soc_pcm_has_symmetry(fe_substream)) + fe_substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + + /* Symmetry only applies if we've got an active stream. */ + if (fe_cpu_dai->active) { + err = soc_pcm_apply_symmetry(fe_substream, fe_cpu_dai); + if (err < 0) + return err; + } + + /* apply symmetry for BE */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + struct snd_soc_pcm_runtime *rtd = be_substream->private_data; + int i; + + if (rtd->dai_link->be_hw_params_fixup) + continue; + + if (soc_pcm_has_symmetry(be_substream)) + be_substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + + /* Symmetry only applies if we've got an active stream. */ + if (rtd->cpu_dai->active) { + err = soc_pcm_apply_symmetry(fe_substream, + rtd->cpu_dai); + if (err < 0) + return err; + } + + for (i = 0; i < rtd->num_codecs; i++) { + if (rtd->codec_dais[i]->active) { + err = soc_pcm_apply_symmetry(fe_substream, + rtd->codec_dais[i]); + if (err < 0) + return err; + } + } + } + + return 0; +} + +static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_pcm_runtime *runtime = fe_substream->runtime; + int stream = fe_substream->stream, ret = 0; + + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); + + ret = dpcm_be_dai_startup(fe, fe_substream->stream); + if (ret < 0) { + dev_err(fe->dev,"ASoC: failed to start some BEs %d\n", ret); + goto be_err; + } + + dev_dbg(fe->dev, "ASoC: open FE %s\n", fe->dai_link->name); + + /* start the DAI frontend */ + ret = soc_pcm_open(fe_substream); + if (ret < 0) { + dev_err(fe->dev,"ASoC: failed to start FE %d\n", ret); + goto unwind; + } + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + + dpcm_set_fe_runtime(fe_substream); + snd_pcm_limit_hw_rates(runtime); + + ret = dpcm_apply_symmetry(fe_substream, stream); + if (ret < 0) { + dev_err(fe->dev, "ASoC: failed to apply dpcm symmetry %d\n", + ret); + goto unwind; + } + + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + return 0; + +unwind: + dpcm_be_dai_startup_unwind(fe, fe_substream->stream); +be_err: + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + return ret; +} + +int dpcm_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + + /* only shutdown BEs that are either sinks or sources to this FE DAI */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if (be->dpcm[stream].users == 0) + dev_err(be->dev, "ASoC: no users %s at close - state %d\n", + stream ? "capture" : "playback", + be->dpcm[stream].state); + + if (--be->dpcm[stream].users != 0) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN)) { + soc_pcm_hw_free(be_substream); + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; + } + + dev_dbg(be->dev, "ASoC: close BE %s\n", + be->dai_link->name); + + soc_pcm_close(be_substream); + be_substream->runtime = NULL; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + } + return 0; +} + +static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream; + + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); + + /* shutdown the BEs */ + dpcm_be_dai_shutdown(fe, substream->stream); + + dev_dbg(fe->dev, "ASoC: close FE %s\n", fe->dai_link->name); + + /* now shutdown the frontend */ + soc_pcm_close(substream); + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + return 0; +} + +int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + + /* only hw_params backends that are either sinks or sources + * to this frontend DAI */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only free hw when no longer used - check all FEs */ + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + /* do not free hw if this BE is used by other FE */ + if (be->dpcm[stream].users > 1) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) + continue; + + dev_dbg(be->dev, "ASoC: hw_free BE %s\n", + be->dai_link->name); + + soc_pcm_hw_free(be_substream); + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; + } + + return 0; +} + +static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int err, stream = substream->stream; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); + + dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name); + + /* call hw_free on the frontend */ + err = soc_pcm_hw_free(substream); + if (err < 0) + dev_err(fe->dev,"ASoC: hw_free FE %s failed\n", + fe->dai_link->name); + + /* only hw_params backends that are either sinks or sources + * to this frontend DAI */ + err = dpcm_be_dai_hw_free(fe, stream); + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + + mutex_unlock(&fe->card->mutex); + return 0; +} + +int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + int ret; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* copy params for each dpcm */ + memcpy(&dpcm->hw_params, &fe->dpcm[stream].hw_params, + sizeof(struct snd_pcm_hw_params)); + + /* perform any hw_params fixups */ + if (be->dai_link->be_hw_params_fixup) { + ret = be->dai_link->be_hw_params_fixup(be, + &dpcm->hw_params); + if (ret < 0) { + dev_err(be->dev, + "ASoC: hw_params BE fixup failed %d\n", + ret); + goto unwind; + } + } + + /* only allow hw_params() if no connected FEs are running */ + if (!snd_soc_dpcm_can_be_params(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE)) + continue; + + dev_dbg(be->dev, "ASoC: hw_params BE %s\n", + be->dai_link->name); + + ret = soc_pcm_hw_params(be_substream, &dpcm->hw_params); + if (ret < 0) { + dev_err(dpcm->be->dev, + "ASoC: hw_params BE failed %d\n", ret); + goto unwind; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; + } + return 0; + +unwind: + /* disable any enabled and non active backends */ + list_for_each_entry_continue_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + /* only allow hw_free() if no connected FEs are running */ + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + continue; + + soc_pcm_hw_free(be_substream); + } + + return ret; +} + +static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int ret, stream = substream->stream; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); + + memcpy(&fe->dpcm[substream->stream].hw_params, params, + sizeof(struct snd_pcm_hw_params)); + ret = dpcm_be_dai_hw_params(fe, substream->stream); + if (ret < 0) { + dev_err(fe->dev,"ASoC: hw_params BE failed %d\n", ret); + goto out; + } + + dev_dbg(fe->dev, "ASoC: hw_params FE %s rate %d chan %x fmt %d\n", + fe->dai_link->name, params_rate(params), + params_channels(params), params_format(params)); + + /* call hw_params on the frontend */ + ret = soc_pcm_hw_params(substream, params); + if (ret < 0) { + dev_err(fe->dev,"ASoC: hw_params FE failed %d\n", ret); + dpcm_be_dai_hw_free(fe, stream); + } else + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; + +out: + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + mutex_unlock(&fe->card->mutex); + return ret; +} + +static int dpcm_do_trigger(struct snd_soc_dpcm *dpcm, + struct snd_pcm_substream *substream, int cmd) +{ + int ret; + + dev_dbg(dpcm->be->dev, "ASoC: trigger BE %s cmd %d\n", + dpcm->be->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + if (ret < 0) + dev_err(dpcm->be->dev,"ASoC: trigger BE failed %d\n", ret); + + return ret; +} + +int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, + int cmd) +{ + struct snd_soc_dpcm *dpcm; + int ret = 0; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_RESUME: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_SUSPEND; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + continue; + + if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) + continue; + + ret = dpcm_do_trigger(dpcm, be_substream, cmd); + if (ret) + return ret; + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger); + +static int dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream, + int cmd, bool fe_first) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int ret; + + /* call trigger on the frontend before the backend. */ + if (fe_first) { + dev_dbg(fe->dev, "ASoC: pre trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + if (ret < 0) + return ret; + + ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); + return ret; + } + + /* call trigger on the frontend after the backend. */ + ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); + if (ret < 0) + return ret; + + dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_trigger(substream, cmd); + + return ret; +} + +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream; + int ret = 0; + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + switch (trigger) { + case SND_SOC_DPCM_TRIGGER_PRE: + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_DRAIN: + ret = dpcm_dai_trigger_fe_be(substream, cmd, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = dpcm_dai_trigger_fe_be(substream, cmd, false); + break; + default: + ret = -EINVAL; + break; + } + break; + case SND_SOC_DPCM_TRIGGER_POST: + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_DRAIN: + ret = dpcm_dai_trigger_fe_be(substream, cmd, false); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = dpcm_dai_trigger_fe_be(substream, cmd, true); + break; + default: + ret = -EINVAL; + break; + } + break; + case SND_SOC_DPCM_TRIGGER_BESPOKE: + /* bespoke trigger() - handles both FE and BEs */ + + dev_dbg(fe->dev, "ASoC: bespoke trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = soc_pcm_bespoke_trigger(substream, cmd); + break; + default: + dev_err(fe->dev, "ASoC: invalid trigger cmd %d for %s\n", cmd, + fe->dai_link->name); + ret = -EINVAL; + goto out; + } + + if (ret < 0) { + dev_err(fe->dev, "ASoC: trigger FE cmd: %d failed: %d\n", + cmd, ret); + goto out; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + break; + } + +out: + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + return ret; +} + +static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream; + + /* if FE's runtime_update is already set, we're in race; + * process this trigger later at exit + */ + if (fe->dpcm[stream].runtime_update != SND_SOC_DPCM_UPDATE_NO) { + fe->dpcm[stream].trigger_pending = cmd + 1; + return 0; /* delayed, assuming it's successful */ + } + + /* we're alone, let's trigger */ + return dpcm_fe_dai_do_trigger(substream, cmd); +} + +int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_soc_dpcm *dpcm; + int ret = 0; + + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *be_substream = + snd_soc_dpcm_get_substream(be, stream); + + /* is this op for this BE ? */ + if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + continue; + + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + continue; + + dev_dbg(be->dev, "ASoC: prepare BE %s\n", + be->dai_link->name); + + ret = soc_pcm_prepare(be_substream); + if (ret < 0) { + dev_err(be->dev, "ASoC: backend prepare failed %d\n", + ret); + break; + } + + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; + } + return ret; +} + +static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = substream->private_data; + int stream = substream->stream, ret = 0; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + + dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name); + + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); + + /* there is no point preparing this FE if there are no BEs */ + if (list_empty(&fe->dpcm[stream].be_clients)) { + dev_err(fe->dev, "ASoC: no backend DAIs enabled for %s\n", + fe->dai_link->name); + ret = -EINVAL; + goto out; + } + + ret = dpcm_be_dai_prepare(fe, substream->stream); + if (ret < 0) + goto out; + + /* call prepare on the frontend */ + ret = soc_pcm_prepare(substream); + if (ret < 0) { + dev_err(fe->dev,"ASoC: prepare FE %s failed\n", + fe->dai_link->name); + goto out; + } + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START); + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; + +out: + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + mutex_unlock(&fe->card->mutex); + + return ret; +} + +static int soc_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->ioctl) + continue; + + /* FIXME: use 1st ioctl */ + return component->driver->ops->ioctl(substream, cmd, arg); + } + + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *substream = + snd_soc_dpcm_get_substream(fe, stream); + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + int err; + + dev_dbg(fe->dev, "ASoC: runtime %s close on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { + /* call bespoke trigger - FE takes care of all BE triggers */ + dev_dbg(fe->dev, "ASoC: bespoke trigger FE %s cmd stop\n", + fe->dai_link->name); + + err = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_STOP); + if (err < 0) + dev_err(fe->dev,"ASoC: trigger FE failed %d\n", err); + } else { + dev_dbg(fe->dev, "ASoC: trigger FE %s cmd stop\n", + fe->dai_link->name); + + err = dpcm_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); + if (err < 0) + dev_err(fe->dev,"ASoC: trigger FE failed %d\n", err); + } + + err = dpcm_be_dai_hw_free(fe, stream); + if (err < 0) + dev_err(fe->dev,"ASoC: hw_free FE failed %d\n", err); + + err = dpcm_be_dai_shutdown(fe, stream); + if (err < 0) + dev_err(fe->dev,"ASoC: shutdown FE failed %d\n", err); + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); + + return 0; +} + +static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *substream = + snd_soc_dpcm_get_substream(fe, stream); + struct snd_soc_dpcm *dpcm; + enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + int ret; + unsigned long flags; + + dev_dbg(fe->dev, "ASoC: runtime %s open on FE %s\n", + stream ? "capture" : "playback", fe->dai_link->name); + + /* Only start the BE if the FE is ready */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_FREE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_CLOSE) + return -EINVAL; + + /* startup must always be called for new BEs */ + ret = dpcm_be_dai_startup(fe, stream); + if (ret < 0) + goto disconnect; + + /* keep going if FE state is > open */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_OPEN) + return 0; + + ret = dpcm_be_dai_hw_params(fe, stream); + if (ret < 0) + goto close; + + /* keep going if FE state is > hw_params */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_PARAMS) + return 0; + + + ret = dpcm_be_dai_prepare(fe, stream); + if (ret < 0) + goto hw_free; + + /* run the stream event for each BE */ + dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); + + /* keep going if FE state is > prepare */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_PREPARE || + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_STOP) + return 0; + + if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { + /* call trigger on the frontend - FE takes care of all BE triggers */ + dev_dbg(fe->dev, "ASoC: bespoke trigger FE %s cmd start\n", + fe->dai_link->name); + + ret = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(fe->dev,"ASoC: bespoke trigger FE failed %d\n", ret); + goto hw_free; + } + } else { + dev_dbg(fe->dev, "ASoC: trigger FE %s cmd start\n", + fe->dai_link->name); + + ret = dpcm_be_dai_trigger(fe, stream, + SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret); + goto hw_free; + } + } + + return 0; + +hw_free: + dpcm_be_dai_hw_free(fe, stream); +close: + dpcm_be_dai_shutdown(fe, stream); +disconnect: + /* disconnect any non started BEs */ + spin_lock_irqsave(&fe->card->dpcm_lock, flags); + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + } + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); + + return ret; +} + +static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + int ret; + + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); + ret = dpcm_run_update_startup(fe, stream); + if (ret < 0) + dev_err(fe->dev, "ASoC: failed to startup some BEs\n"); + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + + return ret; +} + +static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + int ret; + + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); + ret = dpcm_run_update_shutdown(fe, stream); + if (ret < 0) + dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n"); + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + + return ret; +} + +static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, int new) +{ + struct snd_soc_dapm_widget_list *list; + int count, paths; + + if (!fe->dai_link->dynamic) + return 0; + + /* only check active links */ + if (!fe->cpu_dai->active) + return 0; + + /* DAPM sync will call this to update DSP paths */ + dev_dbg(fe->dev, "ASoC: DPCM %s runtime update for FE %s\n", + new ? "new" : "old", fe->dai_link->name); + + /* skip if FE doesn't have playback capability */ + if (!fe->cpu_dai->driver->playback.channels_min || + !fe->codec_dai->driver->playback.channels_min) + goto capture; + + /* skip if FE isn't currently playing */ + if (!fe->cpu_dai->playback_active || !fe->codec_dai->playback_active) + goto capture; + + paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list); + if (paths < 0) { + dev_warn(fe->dev, "ASoC: %s no valid %s path\n", + fe->dai_link->name, "playback"); + return paths; + } + + /* update any playback paths */ + count = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, new); + if (count) { + if (new) + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + else + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_PLAYBACK); + + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); + } + + dpcm_path_put(&list); + +capture: + /* skip if FE doesn't have capture capability */ + if (!fe->cpu_dai->driver->capture.channels_min || + !fe->codec_dai->driver->capture.channels_min) + return 0; + + /* skip if FE isn't currently capturing */ + if (!fe->cpu_dai->capture_active || !fe->codec_dai->capture_active) + return 0; + + paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list); + if (paths < 0) { + dev_warn(fe->dev, "ASoC: %s no valid %s path\n", + fe->dai_link->name, "capture"); + return paths; + } + + /* update any old capture paths */ + count = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, new); + if (count) { + if (new) + dpcm_run_new_update(fe, SNDRV_PCM_STREAM_CAPTURE); + else + dpcm_run_old_update(fe, SNDRV_PCM_STREAM_CAPTURE); + + dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); + dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); + } + + dpcm_path_put(&list); + + return 0; +} + +/* Called by DAPM mixer/mux changes to update audio routing between PCMs and + * any DAI links. + */ +int soc_dpcm_runtime_update(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *fe; + int ret = 0; + + mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + /* shutdown all old paths first */ + list_for_each_entry(fe, &card->rtd_list, list) { + ret = soc_dpcm_fe_runtime_update(fe, 0); + if (ret) + goto out; + } + + /* bring new paths up */ + list_for_each_entry(fe, &card->rtd_list, list) { + ret = soc_dpcm_fe_runtime_update(fe, 1); + if (ret) + goto out; + } + +out: + mutex_unlock(&card->mutex); + return ret; +} +int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) +{ + struct snd_soc_dpcm *dpcm; + struct list_head *clients = + &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients; + + list_for_each_entry(dpcm, clients, list_be) { + + struct snd_soc_pcm_runtime *be = dpcm->be; + int i; + + if (be->dai_link->ignore_suspend) + continue; + + for (i = 0; i < be->num_codecs; i++) { + struct snd_soc_dai *dai = be->codec_dais[i]; + struct snd_soc_dai_driver *drv = dai->driver; + + dev_dbg(be->dev, "ASoC: BE digital mute %s\n", + be->dai_link->name); + + if (drv->ops && drv->ops->digital_mute && + dai->playback_active) + drv->ops->digital_mute(dai, mute); + } + } + + return 0; +} + +static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_soc_dpcm *dpcm; + struct snd_soc_dapm_widget_list *list; + int ret; + int stream = fe_substream->stream; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + fe->dpcm[stream].runtime = fe_substream->runtime; + + ret = dpcm_path_get(fe, stream, &list); + if (ret < 0) { + mutex_unlock(&fe->card->mutex); + return ret; + } else if (ret == 0) { + dev_dbg(fe->dev, "ASoC: %s no valid %s route\n", + fe->dai_link->name, stream ? "capture" : "playback"); + } + + /* calculate valid and active FE <-> BE dpcms */ + dpcm_process_paths(fe, stream, &list, 1); + + ret = dpcm_fe_dai_startup(fe_substream); + if (ret < 0) { + /* clean up all links */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + + dpcm_be_disconnect(fe, stream); + fe->dpcm[stream].runtime = NULL; + } + + dpcm_clear_pending_state(fe, stream); + dpcm_path_put(&list); + mutex_unlock(&fe->card->mutex); + return ret; +} + +static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = fe_substream->private_data; + struct snd_soc_dpcm *dpcm; + int stream = fe_substream->stream, ret; + + mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + ret = dpcm_fe_dai_shutdown(fe_substream); + + /* mark FE's links ready to prune */ + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + + dpcm_be_disconnect(fe, stream); + + fe->dpcm[stream].runtime = NULL; + mutex_unlock(&fe->card->mutex); + return ret; +} + +static void soc_pcm_private_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + /* need to sync the delayed work before releasing resources */ + flush_delayed_work(&rtd->delayed_work); + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (component->driver->pcm_free) + component->driver->pcm_free(pcm); + } +} + +static int soc_rtdcom_ack(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->ack) + continue; + + /* FIXME. it returns 1st ask now */ + return component->driver->ops->ack(substream); + } + + return -EINVAL; +} + +static int soc_rtdcom_copy_user(struct snd_pcm_substream *substream, int channel, + unsigned long pos, void __user *buf, + unsigned long bytes) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->copy_user) + continue; + + /* FIXME. it returns 1st copy now */ + return component->driver->ops->copy_user(substream, channel, + pos, buf, bytes); + } + + return -EINVAL; +} + +static int soc_rtdcom_copy_kernel(struct snd_pcm_substream *substream, int channel, + unsigned long pos, void *buf, unsigned long bytes) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->copy_kernel) + continue; + + /* FIXME. it returns 1st copy now */ + return component->driver->ops->copy_kernel(substream, channel, + pos, buf, bytes); + } + + return -EINVAL; +} + +static int soc_rtdcom_fill_silence(struct snd_pcm_substream *substream, int channel, + unsigned long pos, unsigned long bytes) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->fill_silence) + continue; + + /* FIXME. it returns 1st silence now */ + return component->driver->ops->fill_silence(substream, channel, + pos, bytes); + } + + return -EINVAL; +} + +static struct page *soc_rtdcom_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + struct page *page; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->page) + continue; + + /* FIXME. it returns 1st page now */ + page = component->driver->ops->page(substream, offset); + if (page) + return page; + } + + return NULL; +} + +static int soc_rtdcom_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_component *component; + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->ops || + !component->driver->ops->mmap) + continue; + + /* FIXME. it returns 1st mmap now */ + return component->driver->ops->mmap(substream, vma); + } + + return -EINVAL; +} + +/* create a new pcm */ +int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) +{ + struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_component *component; + struct snd_soc_rtdcom_list *rtdcom; + struct snd_pcm *pcm; + char new_name[64]; + int ret = 0, playback = 0, capture = 0; + int i; + + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { + playback = rtd->dai_link->dpcm_playback; + capture = rtd->dai_link->dpcm_capture; + } else { + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + if (codec_dai->driver->playback.channels_min) + playback = 1; + if (codec_dai->driver->capture.channels_min) + capture = 1; + } + + capture = capture && cpu_dai->driver->capture.channels_min; + playback = playback && cpu_dai->driver->playback.channels_min; + } + + if (rtd->dai_link->playback_only) { + playback = 1; + capture = 0; + } + + if (rtd->dai_link->capture_only) { + playback = 0; + capture = 1; + } + + /* create the PCM */ + if (rtd->dai_link->no_pcm) { + snprintf(new_name, sizeof(new_name), "(%s)", + rtd->dai_link->stream_name); + + ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, + playback, capture, &pcm); + } else { + if (rtd->dai_link->dynamic) + snprintf(new_name, sizeof(new_name), "%s (*)", + rtd->dai_link->stream_name); + else + snprintf(new_name, sizeof(new_name), "%s %s-%d", + rtd->dai_link->stream_name, + (rtd->num_codecs > 1) ? + "multicodec" : rtd->codec_dai->name, num); + + ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, + capture, &pcm); + } + if (ret < 0) { + dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n", + rtd->dai_link->name); + return ret; + } + dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name); + + /* DAPM dai link stream work */ + INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); + + pcm->nonatomic = rtd->dai_link->nonatomic; + rtd->pcm = pcm; + pcm->private_data = rtd; + + if (rtd->dai_link->no_pcm) { + if (playback) + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; + if (capture) + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd; + goto out; + } + + /* ASoC PCM operations */ + if (rtd->dai_link->dynamic) { + rtd->ops.open = dpcm_fe_dai_open; + rtd->ops.hw_params = dpcm_fe_dai_hw_params; + rtd->ops.prepare = dpcm_fe_dai_prepare; + rtd->ops.trigger = dpcm_fe_dai_trigger; + rtd->ops.hw_free = dpcm_fe_dai_hw_free; + rtd->ops.close = dpcm_fe_dai_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } else { + rtd->ops.open = soc_pcm_open; + rtd->ops.hw_params = soc_pcm_hw_params; + rtd->ops.prepare = soc_pcm_prepare; + rtd->ops.trigger = soc_pcm_trigger; + rtd->ops.hw_free = soc_pcm_hw_free; + rtd->ops.close = soc_pcm_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } + + for_each_rtdcom(rtd, rtdcom) { + const struct snd_pcm_ops *ops = rtdcom->component->driver->ops; + + if (!ops) + continue; + + if (ops->ack) + rtd->ops.ack = soc_rtdcom_ack; + if (ops->copy_user) + rtd->ops.copy_user = soc_rtdcom_copy_user; + if (ops->copy_kernel) + rtd->ops.copy_kernel = soc_rtdcom_copy_kernel; + if (ops->fill_silence) + rtd->ops.fill_silence = soc_rtdcom_fill_silence; + if (ops->page) + rtd->ops.page = soc_rtdcom_page; + if (ops->mmap) + rtd->ops.mmap = soc_rtdcom_mmap; + } + + if (playback) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops); + + if (capture) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops); + + for_each_rtdcom(rtd, rtdcom) { + component = rtdcom->component; + + if (!component->driver->pcm_new) + continue; + + ret = component->driver->pcm_new(rtd); + if (ret < 0) { + dev_err(component->dev, + "ASoC: pcm constructor failed: %d\n", + ret); + return ret; + } + } + + pcm->private_free = soc_pcm_private_free; +out: + dev_info(rtd->card->dev, "%s <-> %s mapping ok\n", + (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name, + cpu_dai->name); + return ret; +} + +/* is the current PCM operation for this FE ? */ +int snd_soc_dpcm_fe_can_update(struct snd_soc_pcm_runtime *fe, int stream) +{ + if (fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) + return 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_fe_can_update); + +/* is the current PCM operation for this BE ? */ +int snd_soc_dpcm_be_can_update(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + if ((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) || + ((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_BE) && + be->dpcm[stream].runtime_update)) + return 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_can_update); + +/* get the substream for this BE */ +struct snd_pcm_substream * + snd_soc_dpcm_get_substream(struct snd_soc_pcm_runtime *be, int stream) +{ + return be->pcm->streams[stream].substream; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_get_substream); + +/* get the BE runtime state */ +enum snd_soc_dpcm_state + snd_soc_dpcm_be_get_state(struct snd_soc_pcm_runtime *be, int stream) +{ + return be->dpcm[stream].state; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_get_state); + +/* set the BE runtime state */ +void snd_soc_dpcm_be_set_state(struct snd_soc_pcm_runtime *be, + int stream, enum snd_soc_dpcm_state state) +{ + be->dpcm[stream].state = state; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_set_state); + +/* + * We can only hw_free, stop, pause or suspend a BE DAI if any of it's FE + * are not running, paused or suspended for the specified stream direction. + */ +int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + int state; + int ret = 1; + unsigned long flags; + + spin_lock_irqsave(&fe->card->dpcm_lock, flags); + list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + + if (dpcm->fe == fe) + continue; + + state = dpcm->fe->dpcm[stream].state; + if (state == SND_SOC_DPCM_STATE_START || + state == SND_SOC_DPCM_STATE_PAUSED || + state == SND_SOC_DPCM_STATE_SUSPEND) { + ret = 0; + break; + } + } + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); + + /* it's safe to free/stop this BE DAI */ + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop); + +/* + * We can only change hw params a BE DAI if any of it's FE are not prepared, + * running, paused or suspended for the specified stream direction. + */ +int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dpcm *dpcm; + int state; + int ret = 1; + unsigned long flags; + + spin_lock_irqsave(&fe->card->dpcm_lock, flags); + list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + + if (dpcm->fe == fe) + continue; + + state = dpcm->fe->dpcm[stream].state; + if (state == SND_SOC_DPCM_STATE_START || + state == SND_SOC_DPCM_STATE_PAUSED || + state == SND_SOC_DPCM_STATE_SUSPEND || + state == SND_SOC_DPCM_STATE_PREPARE) { + ret = 0; + break; + } + } + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); + + /* it's safe to change hw_params */ + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_params); + +#ifdef CONFIG_DEBUG_FS +static const char *dpcm_state_string(enum snd_soc_dpcm_state state) +{ + switch (state) { + case SND_SOC_DPCM_STATE_NEW: + return "new"; + case SND_SOC_DPCM_STATE_OPEN: + return "open"; + case SND_SOC_DPCM_STATE_HW_PARAMS: + return "hw_params"; + case SND_SOC_DPCM_STATE_PREPARE: + return "prepare"; + case SND_SOC_DPCM_STATE_START: + return "start"; + case SND_SOC_DPCM_STATE_STOP: + return "stop"; + case SND_SOC_DPCM_STATE_SUSPEND: + return "suspend"; + case SND_SOC_DPCM_STATE_PAUSED: + return "paused"; + case SND_SOC_DPCM_STATE_HW_FREE: + return "hw_free"; + case SND_SOC_DPCM_STATE_CLOSE: + return "close"; + } + + return "unknown"; +} + +static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, + int stream, char *buf, size_t size) +{ + struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params; + struct snd_soc_dpcm *dpcm; + ssize_t offset = 0; + unsigned long flags; + + /* FE state */ + offset += scnprintf(buf + offset, size - offset, + "[%s - %s]\n", fe->dai_link->name, + stream ? "Capture" : "Playback"); + + offset += scnprintf(buf + offset, size - offset, "State: %s\n", + dpcm_state_string(fe->dpcm[stream].state)); + + if ((fe->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (fe->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += scnprintf(buf + offset, size - offset, + "Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + + /* BEs state */ + offset += scnprintf(buf + offset, size - offset, "Backends:\n"); + + if (list_empty(&fe->dpcm[stream].be_clients)) { + offset += scnprintf(buf + offset, size - offset, + " No active DSP links\n"); + goto out; + } + + spin_lock_irqsave(&fe->card->dpcm_lock, flags); + list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + params = &dpcm->hw_params; + + offset += scnprintf(buf + offset, size - offset, + "- %s\n", be->dai_link->name); + + offset += scnprintf(buf + offset, size - offset, + " State: %s\n", + dpcm_state_string(be->dpcm[stream].state)); + + if ((be->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += scnprintf(buf + offset, size - offset, + " Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + } + spin_unlock_irqrestore(&fe->card->dpcm_lock, flags); +out: + return offset; +} + +static ssize_t dpcm_state_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_pcm_runtime *fe = file->private_data; + ssize_t out_count = PAGE_SIZE, offset = 0, ret = 0; + char *buf; + + buf = kmalloc(out_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (fe->cpu_dai->driver->playback.channels_min) + offset += dpcm_show_state(fe, SNDRV_PCM_STREAM_PLAYBACK, + buf + offset, out_count - offset); + + if (fe->cpu_dai->driver->capture.channels_min) + offset += dpcm_show_state(fe, SNDRV_PCM_STREAM_CAPTURE, + buf + offset, out_count - offset); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, offset); + + kfree(buf); + return ret; +} + +static const struct file_operations dpcm_state_fops = { + .open = simple_open, + .read = dpcm_state_read_file, + .llseek = default_llseek, +}; + +void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) +{ + if (!rtd->dai_link) + return; + + if (!rtd->card->debugfs_card_root) + return; + + rtd->debugfs_dpcm_root = debugfs_create_dir(rtd->dai_link->name, + rtd->card->debugfs_card_root); + if (!rtd->debugfs_dpcm_root) { + dev_dbg(rtd->dev, + "ASoC: Failed to create dpcm debugfs directory %s\n", + rtd->dai_link->name); + return; + } + + debugfs_create_file("state", 0444, rtd->debugfs_dpcm_root, + rtd, &dpcm_state_fops); +} +#endif diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c new file mode 100644 index 000000000..776250feb --- /dev/null +++ b/sound/soc/soc-topology.c @@ -0,0 +1,2618 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-topology.c -- ALSA SoC Topology +// +// Copyright (C) 2012 Texas Instruments Inc. +// Copyright (C) 2015 Intel Corporation. +// +// Authors: Liam Girdwood +// K, Mythri P +// Prusty, Subhransu S +// B, Jayachandran +// Abdullah, Omair M +// Jin, Yao +// Lin, Mengdong +// +// Add support to read audio firmware topology alongside firmware text. The +// topology data can contain kcontrols, DAPM graphs, widgets, DAIs, DAI links, +// equalizers, firmware, coefficients etc. +// +// This file only manages the core ALSA and ASoC components, all other bespoke +// firmware topology data is passed to component drivers for bespoke handling. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * We make several passes over the data (since it wont necessarily be ordered) + * and process objects in the following order. This guarantees the component + * drivers will be ready with any vendor data before the mixers and DAPM objects + * are loaded (that may make use of the vendor data). + */ +#define SOC_TPLG_PASS_MANIFEST 0 +#define SOC_TPLG_PASS_VENDOR 1 +#define SOC_TPLG_PASS_MIXER 2 +#define SOC_TPLG_PASS_WIDGET 3 +#define SOC_TPLG_PASS_PCM_DAI 4 +#define SOC_TPLG_PASS_GRAPH 5 +#define SOC_TPLG_PASS_PINS 6 +#define SOC_TPLG_PASS_BE_DAI 7 +#define SOC_TPLG_PASS_LINK 8 + +#define SOC_TPLG_PASS_START SOC_TPLG_PASS_MANIFEST +#define SOC_TPLG_PASS_END SOC_TPLG_PASS_LINK + +/* topology context */ +struct soc_tplg { + const struct firmware *fw; + + /* runtime FW parsing */ + const u8 *pos; /* read postion */ + const u8 *hdr_pos; /* header position */ + unsigned int pass; /* pass number */ + + /* component caller */ + struct device *dev; + struct snd_soc_component *comp; + u32 index; /* current block index */ + u32 req_index; /* required index, only loaded/free matching blocks */ + + /* vendor specific kcontrol operations */ + const struct snd_soc_tplg_kcontrol_ops *io_ops; + int io_ops_count; + + /* vendor specific bytes ext handlers, for TLV bytes controls */ + const struct snd_soc_tplg_bytes_ext_ops *bytes_ext_ops; + int bytes_ext_ops_count; + + /* optional fw loading callbacks to component drivers */ + struct snd_soc_tplg_ops *ops; +}; + +static int soc_tplg_process_headers(struct soc_tplg *tplg); +static void soc_tplg_complete(struct soc_tplg *tplg); +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget); +struct snd_soc_dapm_widget * +snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, + const struct snd_soc_dapm_widget *widget); + +/* check we dont overflow the data for this control chunk */ +static int soc_tplg_check_elem_count(struct soc_tplg *tplg, size_t elem_size, + unsigned int count, size_t bytes, const char *elem_type) +{ + const u8 *end = tplg->pos + elem_size * count; + + if (end > tplg->fw->data + tplg->fw->size) { + dev_err(tplg->dev, "ASoC: %s overflow end of data\n", + elem_type); + return -EINVAL; + } + + /* check there is enough room in chunk for control. + extra bytes at the end of control are for vendor data here */ + if (elem_size * count > bytes) { + dev_err(tplg->dev, + "ASoC: %s count %d of size %zu is bigger than chunk %zu\n", + elem_type, count, elem_size, bytes); + return -EINVAL; + } + + return 0; +} + +static inline int soc_tplg_is_eof(struct soc_tplg *tplg) +{ + const u8 *end = tplg->hdr_pos; + + if (end >= tplg->fw->data + tplg->fw->size) + return 1; + return 0; +} + +static inline unsigned long soc_tplg_get_hdr_offset(struct soc_tplg *tplg) +{ + return (unsigned long)(tplg->hdr_pos - tplg->fw->data); +} + +static inline unsigned long soc_tplg_get_offset(struct soc_tplg *tplg) +{ + return (unsigned long)(tplg->pos - tplg->fw->data); +} + +/* mapping of Kcontrol types and associated operations. */ +static const struct snd_soc_tplg_kcontrol_ops io_ops[] = { + {SND_SOC_TPLG_CTL_VOLSW, snd_soc_get_volsw, + snd_soc_put_volsw, snd_soc_info_volsw}, + {SND_SOC_TPLG_CTL_VOLSW_SX, snd_soc_get_volsw_sx, + snd_soc_put_volsw_sx, NULL}, + {SND_SOC_TPLG_CTL_ENUM, snd_soc_get_enum_double, + snd_soc_put_enum_double, snd_soc_info_enum_double}, + {SND_SOC_TPLG_CTL_ENUM_VALUE, snd_soc_get_enum_double, + snd_soc_put_enum_double, NULL}, + {SND_SOC_TPLG_CTL_BYTES, snd_soc_bytes_get, + snd_soc_bytes_put, snd_soc_bytes_info}, + {SND_SOC_TPLG_CTL_RANGE, snd_soc_get_volsw_range, + snd_soc_put_volsw_range, snd_soc_info_volsw_range}, + {SND_SOC_TPLG_CTL_VOLSW_XR_SX, snd_soc_get_xr_sx, + snd_soc_put_xr_sx, snd_soc_info_xr_sx}, + {SND_SOC_TPLG_CTL_STROBE, snd_soc_get_strobe, + snd_soc_put_strobe, NULL}, + {SND_SOC_TPLG_DAPM_CTL_VOLSW, snd_soc_dapm_get_volsw, + snd_soc_dapm_put_volsw, snd_soc_info_volsw}, + {SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE, snd_soc_dapm_get_enum_double, + snd_soc_dapm_put_enum_double, snd_soc_info_enum_double}, + {SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT, snd_soc_dapm_get_enum_double, + snd_soc_dapm_put_enum_double, NULL}, + {SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE, snd_soc_dapm_get_enum_double, + snd_soc_dapm_put_enum_double, NULL}, + {SND_SOC_TPLG_DAPM_CTL_PIN, snd_soc_dapm_get_pin_switch, + snd_soc_dapm_put_pin_switch, snd_soc_dapm_info_pin_switch}, +}; + +struct soc_tplg_map { + int uid; + int kid; +}; + +/* mapping of widget types from UAPI IDs to kernel IDs */ +static const struct soc_tplg_map dapm_map[] = { + {SND_SOC_TPLG_DAPM_INPUT, snd_soc_dapm_input}, + {SND_SOC_TPLG_DAPM_OUTPUT, snd_soc_dapm_output}, + {SND_SOC_TPLG_DAPM_MUX, snd_soc_dapm_mux}, + {SND_SOC_TPLG_DAPM_MIXER, snd_soc_dapm_mixer}, + {SND_SOC_TPLG_DAPM_PGA, snd_soc_dapm_pga}, + {SND_SOC_TPLG_DAPM_OUT_DRV, snd_soc_dapm_out_drv}, + {SND_SOC_TPLG_DAPM_ADC, snd_soc_dapm_adc}, + {SND_SOC_TPLG_DAPM_DAC, snd_soc_dapm_dac}, + {SND_SOC_TPLG_DAPM_SWITCH, snd_soc_dapm_switch}, + {SND_SOC_TPLG_DAPM_PRE, snd_soc_dapm_pre}, + {SND_SOC_TPLG_DAPM_POST, snd_soc_dapm_post}, + {SND_SOC_TPLG_DAPM_AIF_IN, snd_soc_dapm_aif_in}, + {SND_SOC_TPLG_DAPM_AIF_OUT, snd_soc_dapm_aif_out}, + {SND_SOC_TPLG_DAPM_DAI_IN, snd_soc_dapm_dai_in}, + {SND_SOC_TPLG_DAPM_DAI_OUT, snd_soc_dapm_dai_out}, + {SND_SOC_TPLG_DAPM_DAI_LINK, snd_soc_dapm_dai_link}, + {SND_SOC_TPLG_DAPM_BUFFER, snd_soc_dapm_buffer}, + {SND_SOC_TPLG_DAPM_SCHEDULER, snd_soc_dapm_scheduler}, + {SND_SOC_TPLG_DAPM_EFFECT, snd_soc_dapm_effect}, + {SND_SOC_TPLG_DAPM_SIGGEN, snd_soc_dapm_siggen}, + {SND_SOC_TPLG_DAPM_SRC, snd_soc_dapm_src}, + {SND_SOC_TPLG_DAPM_ASRC, snd_soc_dapm_asrc}, + {SND_SOC_TPLG_DAPM_ENCODER, snd_soc_dapm_encoder}, + {SND_SOC_TPLG_DAPM_DECODER, snd_soc_dapm_decoder}, +}; + +static int tplc_chan_get_reg(struct soc_tplg *tplg, + struct snd_soc_tplg_channel *chan, int map) +{ + int i; + + for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { + if (chan[i].id == map) + return chan[i].reg; + } + + return -EINVAL; +} + +static int tplc_chan_get_shift(struct soc_tplg *tplg, + struct snd_soc_tplg_channel *chan, int map) +{ + int i; + + for (i = 0; i < SND_SOC_TPLG_MAX_CHAN; i++) { + if (chan[i].id == map) + return chan[i].shift; + } + + return -EINVAL; +} + +static int get_widget_id(int tplg_type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dapm_map); i++) { + if (tplg_type == dapm_map[i].uid) + return dapm_map[i].kid; + } + + return -EINVAL; +} + +static inline void soc_bind_err(struct soc_tplg *tplg, + struct snd_soc_tplg_ctl_hdr *hdr, int index) +{ + dev_err(tplg->dev, + "ASoC: invalid control type (g,p,i) %d:%d:%d index %d at 0x%lx\n", + hdr->ops.get, hdr->ops.put, hdr->ops.info, index, + soc_tplg_get_offset(tplg)); +} + +static inline void soc_control_err(struct soc_tplg *tplg, + struct snd_soc_tplg_ctl_hdr *hdr, const char *name) +{ + dev_err(tplg->dev, + "ASoC: no complete mixer IO handler for %s type (g,p,i) %d:%d:%d at 0x%lx\n", + name, hdr->ops.get, hdr->ops.put, hdr->ops.info, + soc_tplg_get_offset(tplg)); +} + +/* pass vendor data to component driver for processing */ +static int soc_tplg_vendor_load_(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + int ret = 0; + + if (tplg->comp && tplg->ops && tplg->ops->vendor_load) + ret = tplg->ops->vendor_load(tplg->comp, tplg->index, hdr); + else { + dev_err(tplg->dev, "ASoC: no vendor load callback for ID %d\n", + hdr->vendor_type); + return -EINVAL; + } + + if (ret < 0) + dev_err(tplg->dev, + "ASoC: vendor load failed at hdr offset %ld/0x%lx for type %d:%d\n", + soc_tplg_get_hdr_offset(tplg), + soc_tplg_get_hdr_offset(tplg), + hdr->type, hdr->vendor_type); + return ret; +} + +/* pass vendor data to component driver for processing */ +static int soc_tplg_vendor_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + if (tplg->pass != SOC_TPLG_PASS_VENDOR) + return 0; + + return soc_tplg_vendor_load_(tplg, hdr); +} + +/* optionally pass new dynamic widget to component driver. This is mainly for + * external widgets where we can assign private data/ops */ +static int soc_tplg_widget_load(struct soc_tplg *tplg, + struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w) +{ + if (tplg->comp && tplg->ops && tplg->ops->widget_load) + return tplg->ops->widget_load(tplg->comp, tplg->index, w, + tplg_w); + + return 0; +} + +/* optionally pass new dynamic widget to component driver. This is mainly for + * external widgets where we can assign private data/ops */ +static int soc_tplg_widget_ready(struct soc_tplg *tplg, + struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w) +{ + if (tplg->comp && tplg->ops && tplg->ops->widget_ready) + return tplg->ops->widget_ready(tplg->comp, tplg->index, w, + tplg_w); + + return 0; +} + +/* pass DAI configurations to component driver for extra initialization */ +static int soc_tplg_dai_load(struct soc_tplg *tplg, + struct snd_soc_dai_driver *dai_drv, + struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai) +{ + if (tplg->comp && tplg->ops && tplg->ops->dai_load) + return tplg->ops->dai_load(tplg->comp, tplg->index, dai_drv, + pcm, dai); + + return 0; +} + +/* pass link configurations to component driver for extra initialization */ +static int soc_tplg_dai_link_load(struct soc_tplg *tplg, + struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg) +{ + if (tplg->comp && tplg->ops && tplg->ops->link_load) + return tplg->ops->link_load(tplg->comp, tplg->index, link, cfg); + + return 0; +} + +/* tell the component driver that all firmware has been loaded in this request */ +static void soc_tplg_complete(struct soc_tplg *tplg) +{ + if (tplg->comp && tplg->ops && tplg->ops->complete) + tplg->ops->complete(tplg->comp); +} + +/* add a dynamic kcontrol */ +static int soc_tplg_add_dcontrol(struct snd_card *card, struct device *dev, + const struct snd_kcontrol_new *control_new, const char *prefix, + void *data, struct snd_kcontrol **kcontrol) +{ + int err; + + *kcontrol = snd_soc_cnew(control_new, data, control_new->name, prefix); + if (*kcontrol == NULL) { + dev_err(dev, "ASoC: Failed to create new kcontrol %s\n", + control_new->name); + return -ENOMEM; + } + + err = snd_ctl_add(card, *kcontrol); + if (err < 0) { + dev_err(dev, "ASoC: Failed to add %s: %d\n", + control_new->name, err); + return err; + } + + return 0; +} + +/* add a dynamic kcontrol for component driver */ +static int soc_tplg_add_kcontrol(struct soc_tplg *tplg, + struct snd_kcontrol_new *k, struct snd_kcontrol **kcontrol) +{ + struct snd_soc_component *comp = tplg->comp; + + return soc_tplg_add_dcontrol(comp->card->snd_card, + comp->dev, k, comp->name_prefix, comp, kcontrol); +} + +/* remove a mixer kcontrol */ +static void remove_mixer(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct soc_mixer_control *sm = + container_of(dobj, struct soc_mixer_control, dobj); + const unsigned int *p = NULL; + + if (pass != SOC_TPLG_PASS_MIXER) + return; + + if (dobj->ops && dobj->ops->control_unload) + dobj->ops->control_unload(comp, dobj); + + if (sm->dobj.control.kcontrol->tlv.p) + p = sm->dobj.control.kcontrol->tlv.p; + snd_ctl_remove(card, sm->dobj.control.kcontrol); + list_del(&sm->dobj.list); + kfree(sm); + kfree(p); +} + +/* remove an enum kcontrol */ +static void remove_enum(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct soc_enum *se = container_of(dobj, struct soc_enum, dobj); + int i; + + if (pass != SOC_TPLG_PASS_MIXER) + return; + + if (dobj->ops && dobj->ops->control_unload) + dobj->ops->control_unload(comp, dobj); + + snd_ctl_remove(card, se->dobj.control.kcontrol); + list_del(&se->dobj.list); + + kfree(se->dobj.control.dvalues); + for (i = 0; i < se->items; i++) + kfree(se->dobj.control.dtexts[i]); + kfree(se); +} + +/* remove a byte kcontrol */ +static void remove_bytes(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct soc_bytes_ext *sb = + container_of(dobj, struct soc_bytes_ext, dobj); + + if (pass != SOC_TPLG_PASS_MIXER) + return; + + if (dobj->ops && dobj->ops->control_unload) + dobj->ops->control_unload(comp, dobj); + + snd_ctl_remove(card, sb->dobj.control.kcontrol); + list_del(&sb->dobj.list); + kfree(sb); +} + +/* remove a widget and it's kcontrols - routes must be removed first */ +static void remove_widget(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_card *card = comp->card->snd_card; + struct snd_soc_dapm_widget *w = + container_of(dobj, struct snd_soc_dapm_widget, dobj); + int i; + + if (pass != SOC_TPLG_PASS_WIDGET) + return; + + if (dobj->ops && dobj->ops->widget_unload) + dobj->ops->widget_unload(comp, dobj); + + if (!w->kcontrols) + goto free_news; + + /* + * Dynamic Widgets either have 1..N enum kcontrols or mixers. + * The enum may either have an array of values or strings. + */ + if (dobj->widget.kcontrol_type == SND_SOC_TPLG_TYPE_ENUM) { + /* enumerated widget mixer */ + for (i = 0; w->kcontrols != NULL && i < w->num_kcontrols; i++) { + struct snd_kcontrol *kcontrol = w->kcontrols[i]; + struct soc_enum *se = + (struct soc_enum *)kcontrol->private_value; + int j; + + snd_ctl_remove(card, kcontrol); + + kfree(se->dobj.control.dvalues); + for (j = 0; j < se->items; j++) + kfree(se->dobj.control.dtexts[j]); + + kfree(se); + kfree(w->kcontrol_news[i].name); + } + } else { + /* volume mixer or bytes controls */ + for (i = 0; w->kcontrols != NULL && i < w->num_kcontrols; i++) { + struct snd_kcontrol *kcontrol = w->kcontrols[i]; + + if (dobj->widget.kcontrol_type + == SND_SOC_TPLG_TYPE_MIXER) + kfree(kcontrol->tlv.p); + + /* Private value is used as struct soc_mixer_control + * for volume mixers or soc_bytes_ext for bytes + * controls. + */ + kfree((void *)kcontrol->private_value); + snd_ctl_remove(card, kcontrol); + kfree(w->kcontrol_news[i].name); + } + } + +free_news: + kfree(w->kcontrol_news); + + /* widget w is freed by soc-dapm.c */ +} + +/* remove DAI configurations */ +static void remove_dai(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_soc_dai_driver *dai_drv = + container_of(dobj, struct snd_soc_dai_driver, dobj); + + if (pass != SOC_TPLG_PASS_PCM_DAI) + return; + + if (dobj->ops && dobj->ops->dai_unload) + dobj->ops->dai_unload(comp, dobj); + + kfree(dai_drv->name); + list_del(&dobj->list); + kfree(dai_drv); +} + +/* remove link configurations */ +static void remove_link(struct snd_soc_component *comp, + struct snd_soc_dobj *dobj, int pass) +{ + struct snd_soc_dai_link *link = + container_of(dobj, struct snd_soc_dai_link, dobj); + + if (pass != SOC_TPLG_PASS_PCM_DAI) + return; + + if (dobj->ops && dobj->ops->link_unload) + dobj->ops->link_unload(comp, dobj); + + kfree(link->name); + kfree(link->stream_name); + kfree(link->cpu_dai_name); + + list_del(&dobj->list); + snd_soc_remove_dai_link(comp->card, link); + kfree(link); +} + +/* bind a kcontrol to it's IO handlers */ +static int soc_tplg_kcontrol_bind_io(struct snd_soc_tplg_ctl_hdr *hdr, + struct snd_kcontrol_new *k, + const struct soc_tplg *tplg) +{ + const struct snd_soc_tplg_kcontrol_ops *ops; + const struct snd_soc_tplg_bytes_ext_ops *ext_ops; + int num_ops, i; + + if (hdr->ops.info == SND_SOC_TPLG_CTL_BYTES + && k->iface & SNDRV_CTL_ELEM_IFACE_MIXER + && (k->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ + || k->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) + && k->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + struct soc_bytes_ext *sbe; + struct snd_soc_tplg_bytes_control *be; + + sbe = (struct soc_bytes_ext *)k->private_value; + be = container_of(hdr, struct snd_soc_tplg_bytes_control, hdr); + + /* TLV bytes controls need standard kcontrol info handler, + * TLV callback and extended put/get handlers. + */ + k->info = snd_soc_bytes_info_ext; + k->tlv.c = snd_soc_bytes_tlv_callback; + + ext_ops = tplg->bytes_ext_ops; + num_ops = tplg->bytes_ext_ops_count; + for (i = 0; i < num_ops; i++) { + if (!sbe->put && ext_ops[i].id == be->ext_ops.put) + sbe->put = ext_ops[i].put; + if (!sbe->get && ext_ops[i].id == be->ext_ops.get) + sbe->get = ext_ops[i].get; + } + + if (sbe->put && sbe->get) + return 0; + else + return -EINVAL; + } + + /* try and map vendor specific kcontrol handlers first */ + ops = tplg->io_ops; + num_ops = tplg->io_ops_count; + for (i = 0; i < num_ops; i++) { + + if (k->put == NULL && ops[i].id == hdr->ops.put) + k->put = ops[i].put; + if (k->get == NULL && ops[i].id == hdr->ops.get) + k->get = ops[i].get; + if (k->info == NULL && ops[i].id == hdr->ops.info) + k->info = ops[i].info; + } + + /* vendor specific handlers found ? */ + if (k->put && k->get && k->info) + return 0; + + /* none found so try standard kcontrol handlers */ + ops = io_ops; + num_ops = ARRAY_SIZE(io_ops); + for (i = 0; i < num_ops; i++) { + + if (k->put == NULL && ops[i].id == hdr->ops.put) + k->put = ops[i].put; + if (k->get == NULL && ops[i].id == hdr->ops.get) + k->get = ops[i].get; + if (k->info == NULL && ops[i].id == hdr->ops.info) + k->info = ops[i].info; + } + + /* standard handlers found ? */ + if (k->put && k->get && k->info) + return 0; + + /* nothing to bind */ + return -EINVAL; +} + +/* bind a widgets to it's evnt handlers */ +int snd_soc_tplg_widget_bind_event(struct snd_soc_dapm_widget *w, + const struct snd_soc_tplg_widget_events *events, + int num_events, u16 event_type) +{ + int i; + + w->event = NULL; + + for (i = 0; i < num_events; i++) { + if (event_type == events[i].type) { + + /* found - so assign event */ + w->event = events[i].event_handler; + return 0; + } + } + + /* not found */ + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_bind_event); + +/* optionally pass new dynamic kcontrol to component driver. */ +static int soc_tplg_init_kcontrol(struct soc_tplg *tplg, + struct snd_kcontrol_new *k, struct snd_soc_tplg_ctl_hdr *hdr) +{ + if (tplg->comp && tplg->ops && tplg->ops->control_load) + return tplg->ops->control_load(tplg->comp, tplg->index, k, + hdr); + + return 0; +} + + +static int soc_tplg_create_tlv_db_scale(struct soc_tplg *tplg, + struct snd_kcontrol_new *kc, struct snd_soc_tplg_tlv_dbscale *scale) +{ + unsigned int item_len = 2 * sizeof(unsigned int); + unsigned int *p; + + p = kzalloc(item_len + 2 * sizeof(unsigned int), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p[0] = SNDRV_CTL_TLVT_DB_SCALE; + p[1] = item_len; + p[2] = scale->min; + p[3] = (scale->step & TLV_DB_SCALE_MASK) + | (scale->mute ? TLV_DB_SCALE_MUTE : 0); + + kc->tlv.p = (void *)p; + return 0; +} + +static int soc_tplg_create_tlv(struct soc_tplg *tplg, + struct snd_kcontrol_new *kc, struct snd_soc_tplg_ctl_hdr *tc) +{ + struct snd_soc_tplg_ctl_tlv *tplg_tlv; + + if (!(tc->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)) + return 0; + + if (!(tc->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)) { + tplg_tlv = &tc->tlv; + switch (tplg_tlv->type) { + case SNDRV_CTL_TLVT_DB_SCALE: + return soc_tplg_create_tlv_db_scale(tplg, kc, + &tplg_tlv->scale); + + /* TODO: add support for other TLV types */ + default: + dev_dbg(tplg->dev, "Unsupported TLV type %d\n", + tplg_tlv->type); + return -EINVAL; + } + } + + return 0; +} + +static inline void soc_tplg_free_tlv(struct soc_tplg *tplg, + struct snd_kcontrol_new *kc) +{ + kfree(kc->tlv.p); +} + +static int soc_tplg_dbytes_create(struct soc_tplg *tplg, unsigned int count, + size_t size) +{ + struct snd_soc_tplg_bytes_control *be; + struct soc_bytes_ext *sbe; + struct snd_kcontrol_new kc; + int i, err; + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_bytes_control), count, + size, "mixer bytes")) { + dev_err(tplg->dev, "ASoC: Invalid count %d for byte control\n", + count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + be = (struct snd_soc_tplg_bytes_control *)tplg->pos; + + /* validate kcontrol */ + if (strnlen(be->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + sbe = kzalloc(sizeof(*sbe), GFP_KERNEL); + if (sbe == NULL) + return -ENOMEM; + + tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + + be->priv.size); + + dev_dbg(tplg->dev, + "ASoC: adding bytes kcontrol %s with access 0x%x\n", + be->hdr.name, be->hdr.access); + + memset(&kc, 0, sizeof(kc)); + kc.name = be->hdr.name; + kc.private_value = (long)sbe; + kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc.access = be->hdr.access; + + sbe->max = be->max; + sbe->dobj.type = SND_SOC_DOBJ_BYTES; + sbe->dobj.ops = tplg->ops; + INIT_LIST_HEAD(&sbe->dobj.list); + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&be->hdr, &kc, tplg); + if (err) { + soc_control_err(tplg, &be->hdr, be->hdr.name); + kfree(sbe); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc, + (struct snd_soc_tplg_ctl_hdr *)be); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + be->hdr.name); + kfree(sbe); + continue; + } + + /* register control here */ + err = soc_tplg_add_kcontrol(tplg, &kc, + &sbe->dobj.control.kcontrol); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to add %s\n", + be->hdr.name); + kfree(sbe); + continue; + } + + list_add(&sbe->dobj.list, &tplg->comp->dobj_list); + } + return 0; + +} + +static int soc_tplg_dmixer_create(struct soc_tplg *tplg, unsigned int count, + size_t size) +{ + struct snd_soc_tplg_mixer_control *mc; + struct soc_mixer_control *sm; + struct snd_kcontrol_new kc; + int i, err; + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_mixer_control), + count, size, "mixers")) { + + dev_err(tplg->dev, "ASoC: invalid count %d for controls\n", + count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + mc = (struct snd_soc_tplg_mixer_control *)tplg->pos; + + /* validate kcontrol */ + if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + sm = kzalloc(sizeof(*sm), GFP_KERNEL); + if (sm == NULL) + return -ENOMEM; + tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + + mc->priv.size); + + dev_dbg(tplg->dev, + "ASoC: adding mixer kcontrol %s with access 0x%x\n", + mc->hdr.name, mc->hdr.access); + + memset(&kc, 0, sizeof(kc)); + kc.name = mc->hdr.name; + kc.private_value = (long)sm; + kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc.access = mc->hdr.access; + + /* we only support FL/FR channel mapping atm */ + sm->reg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rreg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FR); + sm->shift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rshift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FR); + + sm->max = mc->max; + sm->min = mc->min; + sm->invert = mc->invert; + sm->platform_max = mc->platform_max; + sm->dobj.index = tplg->index; + sm->dobj.ops = tplg->ops; + sm->dobj.type = SND_SOC_DOBJ_MIXER; + INIT_LIST_HEAD(&sm->dobj.list); + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&mc->hdr, &kc, tplg); + if (err) { + soc_control_err(tplg, &mc->hdr, mc->hdr.name); + kfree(sm); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc, + (struct snd_soc_tplg_ctl_hdr *) mc); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + mc->hdr.name); + kfree(sm); + continue; + } + + /* create any TLV data */ + soc_tplg_create_tlv(tplg, &kc, &mc->hdr); + + /* register control here */ + err = soc_tplg_add_kcontrol(tplg, &kc, + &sm->dobj.control.kcontrol); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to add %s\n", + mc->hdr.name); + soc_tplg_free_tlv(tplg, &kc); + kfree(sm); + continue; + } + + list_add(&sm->dobj.list, &tplg->comp->dobj_list); + } + + return 0; +} + +static int soc_tplg_denum_create_texts(struct soc_enum *se, + struct snd_soc_tplg_enum_control *ec) +{ + int i, ret; + + se->dobj.control.dtexts = + kcalloc(ec->items, sizeof(char *), GFP_KERNEL); + if (se->dobj.control.dtexts == NULL) + return -ENOMEM; + + for (i = 0; i < ec->items; i++) { + + if (strnlen(ec->texts[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) { + ret = -EINVAL; + goto err; + } + + se->dobj.control.dtexts[i] = kstrdup(ec->texts[i], GFP_KERNEL); + if (!se->dobj.control.dtexts[i]) { + ret = -ENOMEM; + goto err; + } + } + + se->texts = (const char * const *)se->dobj.control.dtexts; + return 0; + +err: + for (--i; i >= 0; i--) + kfree(se->dobj.control.dtexts[i]); + kfree(se->dobj.control.dtexts); + return ret; +} + +static int soc_tplg_denum_create_values(struct soc_enum *se, + struct snd_soc_tplg_enum_control *ec) +{ + if (ec->items > sizeof(*ec->values)) + return -EINVAL; + + se->dobj.control.dvalues = kmemdup(ec->values, + ec->items * sizeof(u32), + GFP_KERNEL); + if (!se->dobj.control.dvalues) + return -ENOMEM; + + return 0; +} + +static int soc_tplg_denum_create(struct soc_tplg *tplg, unsigned int count, + size_t size) +{ + struct snd_soc_tplg_enum_control *ec; + struct soc_enum *se; + struct snd_kcontrol_new kc; + int i, ret, err; + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_enum_control), + count, size, "enums")) { + + dev_err(tplg->dev, "ASoC: invalid count %d for enum controls\n", + count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + ec = (struct snd_soc_tplg_enum_control *)tplg->pos; + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); + + /* validate kcontrol */ + if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + se = kzalloc((sizeof(*se)), GFP_KERNEL); + if (se == NULL) + return -ENOMEM; + + dev_dbg(tplg->dev, "ASoC: adding enum kcontrol %s size %d\n", + ec->hdr.name, ec->items); + + memset(&kc, 0, sizeof(kc)); + kc.name = ec->hdr.name; + kc.private_value = (long)se; + kc.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc.access = ec->hdr.access; + + se->reg = tplc_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); + se->shift_l = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FL); + se->shift_r = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FL); + + se->items = ec->items; + se->mask = ec->mask; + se->dobj.index = tplg->index; + se->dobj.type = SND_SOC_DOBJ_ENUM; + se->dobj.ops = tplg->ops; + INIT_LIST_HEAD(&se->dobj.list); + + switch (ec->hdr.ops.info) { + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + err = soc_tplg_denum_create_values(se, ec); + if (err < 0) { + dev_err(tplg->dev, + "ASoC: could not create values for %s\n", + ec->hdr.name); + kfree(se); + continue; + } + /* fall through and create texts */ + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + err = soc_tplg_denum_create_texts(se, ec); + if (err < 0) { + dev_err(tplg->dev, + "ASoC: could not create texts for %s\n", + ec->hdr.name); + kfree(se); + continue; + } + break; + default: + dev_err(tplg->dev, + "ASoC: invalid enum control type %d for %s\n", + ec->hdr.ops.info, ec->hdr.name); + kfree(se); + continue; + } + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&ec->hdr, &kc, tplg); + if (err) { + soc_control_err(tplg, &ec->hdr, ec->hdr.name); + kfree(se); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc, + (struct snd_soc_tplg_ctl_hdr *) ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + ec->hdr.name); + kfree(se); + continue; + } + + /* register control here */ + ret = soc_tplg_add_kcontrol(tplg, + &kc, &se->dobj.control.kcontrol); + if (ret < 0) { + dev_err(tplg->dev, "ASoC: could not add kcontrol %s\n", + ec->hdr.name); + kfree(se); + continue; + } + + list_add(&se->dobj.list, &tplg->comp->dobj_list); + } + + return 0; +} + +static int soc_tplg_kcontrol_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_ctl_hdr *control_hdr; + int i; + + if (tplg->pass != SOC_TPLG_PASS_MIXER) { + tplg->pos += hdr->size + hdr->payload_size; + return 0; + } + + dev_dbg(tplg->dev, "ASoC: adding %d kcontrols at 0x%lx\n", hdr->count, + soc_tplg_get_offset(tplg)); + + for (i = 0; i < hdr->count; i++) { + + control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; + + if (control_hdr->size != sizeof(*control_hdr)) { + dev_err(tplg->dev, "ASoC: invalid control size\n"); + return -EINVAL; + } + + switch (control_hdr->ops.info) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_STROBE: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_DAPM_CTL_VOLSW: + case SND_SOC_TPLG_DAPM_CTL_PIN: + soc_tplg_dmixer_create(tplg, 1, hdr->payload_size); + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + soc_tplg_denum_create(tplg, 1, hdr->payload_size); + break; + case SND_SOC_TPLG_CTL_BYTES: + soc_tplg_dbytes_create(tplg, 1, hdr->payload_size); + break; + default: + soc_bind_err(tplg, control_hdr, i); + return -EINVAL; + } + } + + return 0; +} + +/* optionally pass new dynamic kcontrol to component driver. */ +static int soc_tplg_add_route(struct soc_tplg *tplg, + struct snd_soc_dapm_route *route) +{ + if (tplg->comp && tplg->ops && tplg->ops->dapm_route_load) + return tplg->ops->dapm_route_load(tplg->comp, tplg->index, + route); + + return 0; +} + +static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_dapm_context *dapm = &tplg->comp->dapm; + struct snd_soc_dapm_route route; + struct snd_soc_tplg_dapm_graph_elem *elem; + int count = hdr->count, i; + + if (tplg->pass != SOC_TPLG_PASS_GRAPH) { + tplg->pos += hdr->size + hdr->payload_size; + return 0; + } + + if (soc_tplg_check_elem_count(tplg, + sizeof(struct snd_soc_tplg_dapm_graph_elem), + count, hdr->payload_size, "graph")) { + + dev_err(tplg->dev, "ASoC: invalid count %d for DAPM routes\n", + count); + return -EINVAL; + } + + dev_dbg(tplg->dev, "ASoC: adding %d DAPM routes for index %d\n", count, + hdr->index); + + for (i = 0; i < count; i++) { + elem = (struct snd_soc_tplg_dapm_graph_elem *)tplg->pos; + tplg->pos += sizeof(struct snd_soc_tplg_dapm_graph_elem); + + /* validate routes */ + if (strnlen(elem->source, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + if (strnlen(elem->sink, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + if (strnlen(elem->control, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + route.source = elem->source; + route.sink = elem->sink; + route.connected = NULL; /* set to NULL atm for tplg users */ + if (strnlen(elem->control, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == 0) + route.control = NULL; + else + route.control = elem->control; + + soc_tplg_add_route(tplg, &route); + + /* add route, but keep going if some fail */ + snd_soc_dapm_add_routes(dapm, &route, 1); + } + + return 0; +} + +static struct snd_kcontrol_new *soc_tplg_dapm_widget_dmixer_create( + struct soc_tplg *tplg, int num_kcontrols) +{ + struct snd_kcontrol_new *kc; + struct soc_mixer_control *sm; + struct snd_soc_tplg_mixer_control *mc; + int i, err; + + kc = kcalloc(num_kcontrols, sizeof(*kc), GFP_KERNEL); + if (kc == NULL) + return NULL; + + for (i = 0; i < num_kcontrols; i++) { + mc = (struct snd_soc_tplg_mixer_control *)tplg->pos; + sm = kzalloc(sizeof(*sm), GFP_KERNEL); + if (sm == NULL) + goto err; + + tplg->pos += (sizeof(struct snd_soc_tplg_mixer_control) + + mc->priv.size); + + /* validate kcontrol */ + if (strnlen(mc->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + goto err_str; + + dev_dbg(tplg->dev, " adding DAPM widget mixer control %s at %d\n", + mc->hdr.name, i); + + kc[i].name = kstrdup(mc->hdr.name, GFP_KERNEL); + if (kc[i].name == NULL) + goto err_str; + kc[i].private_value = (long)sm; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].access = mc->hdr.access; + + /* we only support FL/FR channel mapping atm */ + sm->reg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rreg = tplc_chan_get_reg(tplg, mc->channel, + SNDRV_CHMAP_FR); + sm->shift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FL); + sm->rshift = tplc_chan_get_shift(tplg, mc->channel, + SNDRV_CHMAP_FR); + + sm->max = mc->max; + sm->min = mc->min; + sm->invert = mc->invert; + sm->platform_max = mc->platform_max; + sm->dobj.index = tplg->index; + INIT_LIST_HEAD(&sm->dobj.list); + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&mc->hdr, &kc[i], tplg); + if (err) { + soc_control_err(tplg, &mc->hdr, mc->hdr.name); + kfree(sm); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc[i], + (struct snd_soc_tplg_ctl_hdr *)mc); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + mc->hdr.name); + kfree(sm); + continue; + } + + /* create any TLV data */ + soc_tplg_create_tlv(tplg, &kc[i], &mc->hdr); + } + return kc; + +err_str: + kfree(sm); +err: + for (--i; i >= 0; i--) { + kfree((void *)kc[i].private_value); + kfree(kc[i].name); + } + kfree(kc); + return NULL; +} + +static struct snd_kcontrol_new *soc_tplg_dapm_widget_denum_create( + struct soc_tplg *tplg, int num_kcontrols) +{ + struct snd_kcontrol_new *kc; + struct snd_soc_tplg_enum_control *ec; + struct soc_enum *se; + int i, j, err; + + kc = kcalloc(num_kcontrols, sizeof(*kc), GFP_KERNEL); + if (kc == NULL) + return NULL; + + for (i = 0; i < num_kcontrols; i++) { + ec = (struct snd_soc_tplg_enum_control *)tplg->pos; + /* validate kcontrol */ + if (strnlen(ec->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + goto err; + + se = kzalloc(sizeof(*se), GFP_KERNEL); + if (se == NULL) + goto err; + + dev_dbg(tplg->dev, " adding DAPM widget enum control %s\n", + ec->hdr.name); + + kc[i].name = kstrdup(ec->hdr.name, GFP_KERNEL); + if (kc[i].name == NULL) { + kfree(se); + goto err_se; + } + kc[i].private_value = (long)se; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].access = ec->hdr.access; + + /* we only support FL/FR channel mapping atm */ + se->reg = tplc_chan_get_reg(tplg, ec->channel, SNDRV_CHMAP_FL); + se->shift_l = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FL); + se->shift_r = tplc_chan_get_shift(tplg, ec->channel, + SNDRV_CHMAP_FR); + + se->items = ec->items; + se->mask = ec->mask; + se->dobj.index = tplg->index; + + switch (ec->hdr.ops.info) { + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + err = soc_tplg_denum_create_values(se, ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: could not create values for %s\n", + ec->hdr.name); + goto err_se; + } + /* fall through to create texts */ + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + err = soc_tplg_denum_create_texts(se, ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: could not create texts for %s\n", + ec->hdr.name); + goto err_se; + } + break; + default: + dev_err(tplg->dev, "ASoC: invalid enum control type %d for %s\n", + ec->hdr.ops.info, ec->hdr.name); + goto err_se; + } + + /* map io handlers */ + err = soc_tplg_kcontrol_bind_io(&ec->hdr, &kc[i], tplg); + if (err) { + soc_control_err(tplg, &ec->hdr, ec->hdr.name); + goto err_se; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc[i], + (struct snd_soc_tplg_ctl_hdr *)ec); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + ec->hdr.name); + goto err_se; + } + + tplg->pos += (sizeof(struct snd_soc_tplg_enum_control) + + ec->priv.size); + } + + return kc; + +err_se: + for (; i >= 0; i--) { + /* free values and texts */ + se = (struct soc_enum *)kc[i].private_value; + if (!se) + continue; + + kfree(se->dobj.control.dvalues); + for (j = 0; j < ec->items; j++) + kfree(se->dobj.control.dtexts[j]); + + kfree(se); + kfree(kc[i].name); + } +err: + kfree(kc); + + return NULL; +} + +static struct snd_kcontrol_new *soc_tplg_dapm_widget_dbytes_create( + struct soc_tplg *tplg, int count) +{ + struct snd_soc_tplg_bytes_control *be; + struct soc_bytes_ext *sbe; + struct snd_kcontrol_new *kc; + int i, err; + + kc = kcalloc(count, sizeof(*kc), GFP_KERNEL); + if (!kc) + return NULL; + + for (i = 0; i < count; i++) { + be = (struct snd_soc_tplg_bytes_control *)tplg->pos; + + /* validate kcontrol */ + if (strnlen(be->hdr.name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + goto err; + + sbe = kzalloc(sizeof(*sbe), GFP_KERNEL); + if (sbe == NULL) + goto err; + + tplg->pos += (sizeof(struct snd_soc_tplg_bytes_control) + + be->priv.size); + + dev_dbg(tplg->dev, + "ASoC: adding bytes kcontrol %s with access 0x%x\n", + be->hdr.name, be->hdr.access); + + kc[i].name = kstrdup(be->hdr.name, GFP_KERNEL); + if (kc[i].name == NULL) { + kfree(sbe); + goto err; + } + kc[i].private_value = (long)sbe; + kc[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kc[i].access = be->hdr.access; + + sbe->max = be->max; + INIT_LIST_HEAD(&sbe->dobj.list); + + /* map standard io handlers and check for external handlers */ + err = soc_tplg_kcontrol_bind_io(&be->hdr, &kc[i], tplg); + if (err) { + soc_control_err(tplg, &be->hdr, be->hdr.name); + kfree(sbe); + continue; + } + + /* pass control to driver for optional further init */ + err = soc_tplg_init_kcontrol(tplg, &kc[i], + (struct snd_soc_tplg_ctl_hdr *)be); + if (err < 0) { + dev_err(tplg->dev, "ASoC: failed to init %s\n", + be->hdr.name); + kfree(sbe); + continue; + } + } + + return kc; + +err: + for (--i; i >= 0; i--) { + kfree((void *)kc[i].private_value); + kfree(kc[i].name); + } + + kfree(kc); + return NULL; +} + +static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, + struct snd_soc_tplg_dapm_widget *w) +{ + struct snd_soc_dapm_context *dapm = &tplg->comp->dapm; + struct snd_soc_dapm_widget template, *widget; + struct snd_soc_tplg_ctl_hdr *control_hdr; + struct snd_soc_card *card = tplg->comp->card; + unsigned int kcontrol_type; + int ret = 0; + + if (strnlen(w->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + if (strnlen(w->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) == + SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + + dev_dbg(tplg->dev, "ASoC: creating DAPM widget %s id %d\n", + w->name, w->id); + + memset(&template, 0, sizeof(template)); + + /* map user to kernel widget ID */ + template.id = get_widget_id(w->id); + if (template.id < 0) + return template.id; + + /* strings are allocated here, but used and freed by the widget */ + template.name = kstrdup(w->name, GFP_KERNEL); + if (!template.name) + return -ENOMEM; + template.sname = kstrdup(w->sname, GFP_KERNEL); + if (!template.sname) { + ret = -ENOMEM; + goto err; + } + template.reg = w->reg; + template.shift = w->shift; + template.mask = w->mask; + template.subseq = w->subseq; + template.on_val = w->invert ? 0 : 1; + template.off_val = w->invert ? 1 : 0; + template.ignore_suspend = w->ignore_suspend; + template.event_flags = w->event_flags; + template.dobj.index = tplg->index; + + tplg->pos += + (sizeof(struct snd_soc_tplg_dapm_widget) + w->priv.size); + if (w->num_kcontrols == 0) { + kcontrol_type = 0; + template.num_kcontrols = 0; + goto widget; + } + + control_hdr = (struct snd_soc_tplg_ctl_hdr *)tplg->pos; + dev_dbg(tplg->dev, "ASoC: template %s has %d controls of type %x\n", + w->name, w->num_kcontrols, control_hdr->type); + + switch (control_hdr->ops.info) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_STROBE: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + case SND_SOC_TPLG_CTL_RANGE: + case SND_SOC_TPLG_DAPM_CTL_VOLSW: + kcontrol_type = SND_SOC_TPLG_TYPE_MIXER; /* volume mixer */ + template.num_kcontrols = w->num_kcontrols; + template.kcontrol_news = + soc_tplg_dapm_widget_dmixer_create(tplg, + template.num_kcontrols); + if (!template.kcontrol_news) { + ret = -ENOMEM; + goto hdr_err; + } + break; + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: + case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: + kcontrol_type = SND_SOC_TPLG_TYPE_ENUM; /* enumerated mixer */ + template.num_kcontrols = w->num_kcontrols; + template.kcontrol_news = + soc_tplg_dapm_widget_denum_create(tplg, + template.num_kcontrols); + if (!template.kcontrol_news) { + ret = -ENOMEM; + goto hdr_err; + } + break; + case SND_SOC_TPLG_CTL_BYTES: + kcontrol_type = SND_SOC_TPLG_TYPE_BYTES; /* bytes control */ + template.num_kcontrols = w->num_kcontrols; + template.kcontrol_news = + soc_tplg_dapm_widget_dbytes_create(tplg, + template.num_kcontrols); + if (!template.kcontrol_news) { + ret = -ENOMEM; + goto hdr_err; + } + break; + default: + dev_err(tplg->dev, "ASoC: invalid widget control type %d:%d:%d\n", + control_hdr->ops.get, control_hdr->ops.put, + control_hdr->ops.info); + ret = -EINVAL; + goto hdr_err; + } + +widget: + ret = soc_tplg_widget_load(tplg, &template, w); + if (ret < 0) + goto hdr_err; + + /* card dapm mutex is held by the core if we are loading topology + * data during sound card init. */ + if (card->instantiated) + widget = snd_soc_dapm_new_control(dapm, &template); + else + widget = snd_soc_dapm_new_control_unlocked(dapm, &template); + if (IS_ERR(widget)) { + ret = PTR_ERR(widget); + /* Do not nag about probe deferrals */ + if (ret != -EPROBE_DEFER) + dev_err(tplg->dev, + "ASoC: failed to create widget %s controls (%d)\n", + w->name, ret); + goto hdr_err; + } + if (widget == NULL) { + dev_err(tplg->dev, "ASoC: failed to create widget %s controls\n", + w->name); + ret = -ENOMEM; + goto hdr_err; + } + + widget->dobj.type = SND_SOC_DOBJ_WIDGET; + widget->dobj.widget.kcontrol_type = kcontrol_type; + widget->dobj.ops = tplg->ops; + widget->dobj.index = tplg->index; + list_add(&widget->dobj.list, &tplg->comp->dobj_list); + + ret = soc_tplg_widget_ready(tplg, widget, w); + if (ret < 0) + goto ready_err; + + return 0; + +ready_err: + snd_soc_tplg_widget_remove(widget); + snd_soc_dapm_free_widget(widget); +hdr_err: + kfree(template.sname); +err: + kfree(template.name); + return ret; +} + +static int soc_tplg_dapm_widget_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_dapm_widget *widget; + int ret, count = hdr->count, i; + + if (tplg->pass != SOC_TPLG_PASS_WIDGET) + return 0; + + dev_dbg(tplg->dev, "ASoC: adding %d DAPM widgets\n", count); + + for (i = 0; i < count; i++) { + widget = (struct snd_soc_tplg_dapm_widget *) tplg->pos; + if (widget->size != sizeof(*widget)) { + dev_err(tplg->dev, "ASoC: invalid widget size\n"); + return -EINVAL; + } + + ret = soc_tplg_dapm_widget_create(tplg, widget); + if (ret < 0) { + dev_err(tplg->dev, "ASoC: failed to load widget %s\n", + widget->name); + return ret; + } + } + + return 0; +} + +static int soc_tplg_dapm_complete(struct soc_tplg *tplg) +{ + struct snd_soc_card *card = tplg->comp->card; + int ret; + + /* Card might not have been registered at this point. + * If so, just return success. + */ + if (!card || !card->instantiated) { + dev_warn(tplg->dev, "ASoC: Parent card not yet available," + " widget card binding deferred\n"); + return 0; + } + + ret = snd_soc_dapm_new_widgets(card); + if (ret < 0) + dev_err(tplg->dev, "ASoC: failed to create new widgets %d\n", + ret); + + return 0; +} + +static void set_stream_info(struct snd_soc_pcm_stream *stream, + struct snd_soc_tplg_stream_caps *caps) +{ + stream->stream_name = kstrdup(caps->name, GFP_KERNEL); + stream->channels_min = caps->channels_min; + stream->channels_max = caps->channels_max; + stream->rates = caps->rates; + stream->rate_min = caps->rate_min; + stream->rate_max = caps->rate_max; + stream->formats = caps->formats; + stream->sig_bits = caps->sig_bits; +} + +static void set_dai_flags(struct snd_soc_dai_driver *dai_drv, + unsigned int flag_mask, unsigned int flags) +{ + if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES) + dai_drv->symmetric_rates = + flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES ? 1 : 0; + + if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS) + dai_drv->symmetric_channels = + flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS ? + 1 : 0; + + if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS) + dai_drv->symmetric_samplebits = + flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS ? + 1 : 0; +} + +static int soc_tplg_dai_create(struct soc_tplg *tplg, + struct snd_soc_tplg_pcm *pcm) +{ + struct snd_soc_dai_driver *dai_drv; + struct snd_soc_pcm_stream *stream; + struct snd_soc_tplg_stream_caps *caps; + int ret; + + dai_drv = kzalloc(sizeof(struct snd_soc_dai_driver), GFP_KERNEL); + if (dai_drv == NULL) + return -ENOMEM; + + if (strlen(pcm->dai_name)) + dai_drv->name = kstrdup(pcm->dai_name, GFP_KERNEL); + dai_drv->id = pcm->dai_id; + + if (pcm->playback) { + stream = &dai_drv->playback; + caps = &pcm->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; + set_stream_info(stream, caps); + } + + if (pcm->capture) { + stream = &dai_drv->capture; + caps = &pcm->caps[SND_SOC_TPLG_STREAM_CAPTURE]; + set_stream_info(stream, caps); + } + + if (pcm->compress) + dai_drv->compress_new = snd_soc_new_compress; + + /* pass control to component driver for optional further init */ + ret = soc_tplg_dai_load(tplg, dai_drv, pcm, NULL); + if (ret < 0) { + dev_err(tplg->comp->dev, "ASoC: DAI loading failed\n"); + kfree(dai_drv); + return ret; + } + + dai_drv->dobj.index = tplg->index; + dai_drv->dobj.ops = tplg->ops; + dai_drv->dobj.type = SND_SOC_DOBJ_PCM; + list_add(&dai_drv->dobj.list, &tplg->comp->dobj_list); + + /* register the DAI to the component */ + return snd_soc_register_dai(tplg->comp, dai_drv); +} + +static void set_link_flags(struct snd_soc_dai_link *link, + unsigned int flag_mask, unsigned int flags) +{ + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES) + link->symmetric_rates = + flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_RATES ? 1 : 0; + + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS) + link->symmetric_channels = + flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_CHANNELS ? + 1 : 0; + + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS) + link->symmetric_samplebits = + flags & SND_SOC_TPLG_LNK_FLGBIT_SYMMETRIC_SAMPLEBITS ? + 1 : 0; + + if (flag_mask & SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP) + link->ignore_suspend = + flags & SND_SOC_TPLG_LNK_FLGBIT_VOICE_WAKEUP ? + 1 : 0; +} + +/* create the FE DAI link */ +static int soc_tplg_fe_link_create(struct soc_tplg *tplg, + struct snd_soc_tplg_pcm *pcm) +{ + struct snd_soc_dai_link *link; + int ret; + + link = kzalloc(sizeof(struct snd_soc_dai_link), GFP_KERNEL); + if (link == NULL) + return -ENOMEM; + + if (strlen(pcm->pcm_name)) { + link->name = kstrdup(pcm->pcm_name, GFP_KERNEL); + link->stream_name = kstrdup(pcm->pcm_name, GFP_KERNEL); + } + link->id = pcm->pcm_id; + + if (strlen(pcm->dai_name)) + link->cpu_dai_name = kstrdup(pcm->dai_name, GFP_KERNEL); + + link->codec_name = "snd-soc-dummy"; + link->codec_dai_name = "snd-soc-dummy-dai"; + + /* enable DPCM */ + link->dynamic = 1; + link->dpcm_playback = pcm->playback; + link->dpcm_capture = pcm->capture; + if (pcm->flag_mask) + set_link_flags(link, pcm->flag_mask, pcm->flags); + + /* pass control to component driver for optional further init */ + ret = soc_tplg_dai_link_load(tplg, link, NULL); + if (ret < 0) { + dev_err(tplg->comp->dev, "ASoC: FE link loading failed\n"); + kfree(link); + return ret; + } + + link->dobj.index = tplg->index; + link->dobj.ops = tplg->ops; + link->dobj.type = SND_SOC_DOBJ_DAI_LINK; + list_add(&link->dobj.list, &tplg->comp->dobj_list); + + snd_soc_add_dai_link(tplg->comp->card, link); + return 0; +} + +/* create a FE DAI and DAI link from the PCM object */ +static int soc_tplg_pcm_create(struct soc_tplg *tplg, + struct snd_soc_tplg_pcm *pcm) +{ + int ret; + + ret = soc_tplg_dai_create(tplg, pcm); + if (ret < 0) + return ret; + + return soc_tplg_fe_link_create(tplg, pcm); +} + +/* copy stream caps from the old version 4 of source */ +static void stream_caps_new_ver(struct snd_soc_tplg_stream_caps *dest, + struct snd_soc_tplg_stream_caps_v4 *src) +{ + dest->size = sizeof(*dest); + memcpy(dest->name, src->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + dest->formats = src->formats; + dest->rates = src->rates; + dest->rate_min = src->rate_min; + dest->rate_max = src->rate_max; + dest->channels_min = src->channels_min; + dest->channels_max = src->channels_max; + dest->periods_min = src->periods_min; + dest->periods_max = src->periods_max; + dest->period_size_min = src->period_size_min; + dest->period_size_max = src->period_size_max; + dest->buffer_size_min = src->buffer_size_min; + dest->buffer_size_max = src->buffer_size_max; +} + +/** + * pcm_new_ver - Create the new version of PCM from the old version. + * @tplg: topology context + * @src: older version of pcm as a source + * @pcm: latest version of pcm created from the source + * + * Support from vesion 4. User should free the returned pcm manually. + */ +static int pcm_new_ver(struct soc_tplg *tplg, + struct snd_soc_tplg_pcm *src, + struct snd_soc_tplg_pcm **pcm) +{ + struct snd_soc_tplg_pcm *dest; + struct snd_soc_tplg_pcm_v4 *src_v4; + int i; + + *pcm = NULL; + + if (src->size != sizeof(*src_v4)) { + dev_err(tplg->dev, "ASoC: invalid PCM size\n"); + return -EINVAL; + } + + dev_warn(tplg->dev, "ASoC: old version of PCM\n"); + src_v4 = (struct snd_soc_tplg_pcm_v4 *)src; + dest = kzalloc(sizeof(*dest), GFP_KERNEL); + if (!dest) + return -ENOMEM; + + dest->size = sizeof(*dest); /* size of latest abi version */ + memcpy(dest->pcm_name, src_v4->pcm_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + memcpy(dest->dai_name, src_v4->dai_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + dest->pcm_id = src_v4->pcm_id; + dest->dai_id = src_v4->dai_id; + dest->playback = src_v4->playback; + dest->capture = src_v4->capture; + dest->compress = src_v4->compress; + dest->num_streams = src_v4->num_streams; + for (i = 0; i < dest->num_streams; i++) + memcpy(&dest->stream[i], &src_v4->stream[i], + sizeof(struct snd_soc_tplg_stream)); + + for (i = 0; i < 2; i++) + stream_caps_new_ver(&dest->caps[i], &src_v4->caps[i]); + + *pcm = dest; + return 0; +} + +static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_pcm *pcm, *_pcm; + int count = hdr->count; + int i; + bool abi_match; + int ret; + + if (tplg->pass != SOC_TPLG_PASS_PCM_DAI) + return 0; + + /* check the element size and count */ + pcm = (struct snd_soc_tplg_pcm *)tplg->pos; + if (pcm->size > sizeof(struct snd_soc_tplg_pcm) + || pcm->size < sizeof(struct snd_soc_tplg_pcm_v4)) { + dev_err(tplg->dev, "ASoC: invalid size %d for PCM elems\n", + pcm->size); + return -EINVAL; + } + + if (soc_tplg_check_elem_count(tplg, + pcm->size, count, + hdr->payload_size, "PCM DAI")) { + dev_err(tplg->dev, "ASoC: invalid count %d for PCM DAI elems\n", + count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + pcm = (struct snd_soc_tplg_pcm *)tplg->pos; + + /* check ABI version by size, create a new version of pcm + * if abi not match. + */ + if (pcm->size == sizeof(*pcm)) { + abi_match = true; + _pcm = pcm; + } else { + abi_match = false; + ret = pcm_new_ver(tplg, pcm, &_pcm); + if (ret < 0) + return ret; + } + + /* create the FE DAIs and DAI links */ + ret = soc_tplg_pcm_create(tplg, _pcm); + if (ret < 0) { + if (!abi_match) + kfree(_pcm); + return ret; + } + + /* offset by version-specific struct size and + * real priv data size + */ + tplg->pos += pcm->size + _pcm->priv.size; + + if (!abi_match) + kfree(_pcm); /* free the duplicated one */ + } + + dev_dbg(tplg->dev, "ASoC: adding %d PCM DAIs\n", count); + + return 0; +} + +/** + * set_link_hw_format - Set the HW audio format of the physical DAI link. + * @link: &snd_soc_dai_link which should be updated + * @cfg: physical link configs. + * + * Topology context contains a list of supported HW formats (configs) and + * a default format ID for the physical link. This function will use this + * default ID to choose the HW format to set the link's DAI format for init. + */ +static void set_link_hw_format(struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + struct snd_soc_tplg_hw_config *hw_config; + unsigned char bclk_master, fsync_master; + unsigned char invert_bclk, invert_fsync; + int i; + + for (i = 0; i < cfg->num_hw_configs; i++) { + hw_config = &cfg->hw_config[i]; + if (hw_config->id != cfg->default_hw_config_id) + continue; + + link->dai_fmt = hw_config->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + /* clock gating */ + switch (hw_config->clock_gated) { + case SND_SOC_TPLG_DAI_CLK_GATE_GATED: + link->dai_fmt |= SND_SOC_DAIFMT_GATED; + break; + + case SND_SOC_TPLG_DAI_CLK_GATE_CONT: + link->dai_fmt |= SND_SOC_DAIFMT_CONT; + break; + + default: + /* ignore the value */ + break; + } + + /* clock signal polarity */ + invert_bclk = hw_config->invert_bclk; + invert_fsync = hw_config->invert_fsync; + if (!invert_bclk && !invert_fsync) + link->dai_fmt |= SND_SOC_DAIFMT_NB_NF; + else if (!invert_bclk && invert_fsync) + link->dai_fmt |= SND_SOC_DAIFMT_NB_IF; + else if (invert_bclk && !invert_fsync) + link->dai_fmt |= SND_SOC_DAIFMT_IB_NF; + else + link->dai_fmt |= SND_SOC_DAIFMT_IB_IF; + + /* clock masters */ + bclk_master = (hw_config->bclk_master == + SND_SOC_TPLG_BCLK_CM); + fsync_master = (hw_config->fsync_master == + SND_SOC_TPLG_FSYNC_CM); + if (bclk_master && fsync_master) + link->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + else if (!bclk_master && fsync_master) + link->dai_fmt |= SND_SOC_DAIFMT_CBS_CFM; + else if (bclk_master && !fsync_master) + link->dai_fmt |= SND_SOC_DAIFMT_CBM_CFS; + else + link->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + } +} + +/** + * link_new_ver - Create a new physical link config from the old + * version of source. + * @tplg: topology context + * @src: old version of phyical link config as a source + * @link: latest version of physical link config created from the source + * + * Support from vesion 4. User need free the returned link config manually. + */ +static int link_new_ver(struct soc_tplg *tplg, + struct snd_soc_tplg_link_config *src, + struct snd_soc_tplg_link_config **link) +{ + struct snd_soc_tplg_link_config *dest; + struct snd_soc_tplg_link_config_v4 *src_v4; + int i; + + *link = NULL; + + if (src->size != sizeof(struct snd_soc_tplg_link_config_v4)) { + dev_err(tplg->dev, "ASoC: invalid physical link config size\n"); + return -EINVAL; + } + + dev_warn(tplg->dev, "ASoC: old version of physical link config\n"); + + src_v4 = (struct snd_soc_tplg_link_config_v4 *)src; + dest = kzalloc(sizeof(*dest), GFP_KERNEL); + if (!dest) + return -ENOMEM; + + dest->size = sizeof(*dest); + dest->id = src_v4->id; + dest->num_streams = src_v4->num_streams; + for (i = 0; i < dest->num_streams; i++) + memcpy(&dest->stream[i], &src_v4->stream[i], + sizeof(struct snd_soc_tplg_stream)); + + *link = dest; + return 0; +} + +/* Find and configure an existing physical DAI link */ +static int soc_tplg_link_config(struct soc_tplg *tplg, + struct snd_soc_tplg_link_config *cfg) +{ + struct snd_soc_dai_link *link; + const char *name, *stream_name; + size_t len; + int ret; + + len = strnlen(cfg->name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + else if (len) + name = cfg->name; + else + name = NULL; + + len = strnlen(cfg->stream_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN); + if (len == SNDRV_CTL_ELEM_ID_NAME_MAXLEN) + return -EINVAL; + else if (len) + stream_name = cfg->stream_name; + else + stream_name = NULL; + + link = snd_soc_find_dai_link(tplg->comp->card, cfg->id, + name, stream_name); + if (!link) { + dev_err(tplg->dev, "ASoC: physical link %s (id %d) not exist\n", + name, cfg->id); + return -EINVAL; + } + + /* hw format */ + if (cfg->num_hw_configs) + set_link_hw_format(link, cfg); + + /* flags */ + if (cfg->flag_mask) + set_link_flags(link, cfg->flag_mask, cfg->flags); + + /* pass control to component driver for optional further init */ + ret = soc_tplg_dai_link_load(tplg, link, cfg); + if (ret < 0) { + dev_err(tplg->dev, "ASoC: physical link loading failed\n"); + return ret; + } + + return 0; +} + + +/* Load physical link config elements from the topology context */ +static int soc_tplg_link_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_link_config *link, *_link; + int count = hdr->count; + int i, ret; + bool abi_match; + + if (tplg->pass != SOC_TPLG_PASS_LINK) { + tplg->pos += hdr->size + hdr->payload_size; + return 0; + }; + + /* check the element size and count */ + link = (struct snd_soc_tplg_link_config *)tplg->pos; + if (link->size > sizeof(struct snd_soc_tplg_link_config) + || link->size < sizeof(struct snd_soc_tplg_link_config_v4)) { + dev_err(tplg->dev, "ASoC: invalid size %d for physical link elems\n", + link->size); + return -EINVAL; + } + + if (soc_tplg_check_elem_count(tplg, + link->size, count, + hdr->payload_size, "physical link config")) { + dev_err(tplg->dev, "ASoC: invalid count %d for physical link elems\n", + count); + return -EINVAL; + } + + /* config physical DAI links */ + for (i = 0; i < count; i++) { + link = (struct snd_soc_tplg_link_config *)tplg->pos; + if (link->size == sizeof(*link)) { + abi_match = true; + _link = link; + } else { + abi_match = false; + ret = link_new_ver(tplg, link, &_link); + if (ret < 0) + return ret; + } + + ret = soc_tplg_link_config(tplg, _link); + if (ret < 0) { + if (!abi_match) + kfree(_link); + return ret; + } + + /* offset by version-specific struct size and + * real priv data size + */ + tplg->pos += link->size + _link->priv.size; + + if (!abi_match) + kfree(_link); /* free the duplicated one */ + } + + return 0; +} + +/** + * soc_tplg_dai_config - Find and configure an existing physical DAI. + * @tplg: topology context + * @d: physical DAI configs. + * + * The physical dai should already be registered by the platform driver. + * The platform driver should specify the DAI name and ID for matching. + */ +static int soc_tplg_dai_config(struct soc_tplg *tplg, + struct snd_soc_tplg_dai *d) +{ + struct snd_soc_dai_link_component dai_component = {0}; + struct snd_soc_dai *dai; + struct snd_soc_dai_driver *dai_drv; + struct snd_soc_pcm_stream *stream; + struct snd_soc_tplg_stream_caps *caps; + int ret; + + dai_component.dai_name = d->dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(tplg->dev, "ASoC: physical DAI %s not registered\n", + d->dai_name); + return -EINVAL; + } + + if (d->dai_id != dai->id) { + dev_err(tplg->dev, "ASoC: physical DAI %s id mismatch\n", + d->dai_name); + return -EINVAL; + } + + dai_drv = dai->driver; + if (!dai_drv) + return -EINVAL; + + if (d->playback) { + stream = &dai_drv->playback; + caps = &d->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; + set_stream_info(stream, caps); + } + + if (d->capture) { + stream = &dai_drv->capture; + caps = &d->caps[SND_SOC_TPLG_STREAM_CAPTURE]; + set_stream_info(stream, caps); + } + + if (d->flag_mask) + set_dai_flags(dai_drv, d->flag_mask, d->flags); + + /* pass control to component driver for optional further init */ + ret = soc_tplg_dai_load(tplg, dai_drv, NULL, dai); + if (ret < 0) { + dev_err(tplg->comp->dev, "ASoC: DAI loading failed\n"); + return ret; + } + + return 0; +} + +/* load physical DAI elements */ +static int soc_tplg_dai_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_dai *dai; + int count = hdr->count; + int i; + + if (tplg->pass != SOC_TPLG_PASS_BE_DAI) + return 0; + + /* config the existing BE DAIs */ + for (i = 0; i < count; i++) { + dai = (struct snd_soc_tplg_dai *)tplg->pos; + if (dai->size != sizeof(*dai)) { + dev_err(tplg->dev, "ASoC: invalid physical DAI size\n"); + return -EINVAL; + } + + soc_tplg_dai_config(tplg, dai); + tplg->pos += (sizeof(*dai) + dai->priv.size); + } + + dev_dbg(tplg->dev, "ASoC: Configure %d BE DAIs\n", count); + return 0; +} + +/** + * manifest_new_ver - Create a new version of manifest from the old version + * of source. + * @tplg: topology context + * @src: old version of manifest as a source + * @manifest: latest version of manifest created from the source + * + * Support from vesion 4. Users need free the returned manifest manually. + */ +static int manifest_new_ver(struct soc_tplg *tplg, + struct snd_soc_tplg_manifest *src, + struct snd_soc_tplg_manifest **manifest) +{ + struct snd_soc_tplg_manifest *dest; + struct snd_soc_tplg_manifest_v4 *src_v4; + + *manifest = NULL; + + if (src->size != sizeof(*src_v4)) { + dev_warn(tplg->dev, "ASoC: invalid manifest size %d\n", + src->size); + if (src->size) + return -EINVAL; + src->size = sizeof(*src_v4); + } + + dev_warn(tplg->dev, "ASoC: old version of manifest\n"); + + src_v4 = (struct snd_soc_tplg_manifest_v4 *)src; + dest = kzalloc(sizeof(*dest) + src_v4->priv.size, GFP_KERNEL); + if (!dest) + return -ENOMEM; + + dest->size = sizeof(*dest); /* size of latest abi version */ + dest->control_elems = src_v4->control_elems; + dest->widget_elems = src_v4->widget_elems; + dest->graph_elems = src_v4->graph_elems; + dest->pcm_elems = src_v4->pcm_elems; + dest->dai_link_elems = src_v4->dai_link_elems; + dest->priv.size = src_v4->priv.size; + if (dest->priv.size) + memcpy(dest->priv.data, src_v4->priv.data, + src_v4->priv.size); + + *manifest = dest; + return 0; +} + +static int soc_tplg_manifest_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_manifest *manifest, *_manifest; + bool abi_match; + int ret = 0; + + if (tplg->pass != SOC_TPLG_PASS_MANIFEST) + return 0; + + manifest = (struct snd_soc_tplg_manifest *)tplg->pos; + + /* check ABI version by size, create a new manifest if abi not match */ + if (manifest->size == sizeof(*manifest)) { + abi_match = true; + _manifest = manifest; + } else { + abi_match = false; + ret = manifest_new_ver(tplg, manifest, &_manifest); + if (ret < 0) + return ret; + } + + /* pass control to component driver for optional further init */ + if (tplg->comp && tplg->ops && tplg->ops->manifest) + ret = tplg->ops->manifest(tplg->comp, tplg->index, _manifest); + + if (!abi_match) /* free the duplicated one */ + kfree(_manifest); + + return ret; +} + +/* validate header magic, size and type */ +static int soc_valid_header(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + if (soc_tplg_get_hdr_offset(tplg) >= tplg->fw->size) + return 0; + + if (hdr->size != sizeof(*hdr)) { + dev_err(tplg->dev, + "ASoC: invalid header size for type %d at offset 0x%lx size 0x%zx.\n", + hdr->type, soc_tplg_get_hdr_offset(tplg), + tplg->fw->size); + return -EINVAL; + } + + /* big endian firmware objects not supported atm */ + if (hdr->magic == cpu_to_be32(SND_SOC_TPLG_MAGIC)) { + dev_err(tplg->dev, + "ASoC: pass %d big endian not supported header got %x at offset 0x%lx size 0x%zx.\n", + tplg->pass, hdr->magic, + soc_tplg_get_hdr_offset(tplg), tplg->fw->size); + return -EINVAL; + } + + if (hdr->magic != SND_SOC_TPLG_MAGIC) { + dev_err(tplg->dev, + "ASoC: pass %d does not have a valid header got %x at offset 0x%lx size 0x%zx.\n", + tplg->pass, hdr->magic, + soc_tplg_get_hdr_offset(tplg), tplg->fw->size); + return -EINVAL; + } + + /* Support ABI from version 4 */ + if (hdr->abi > SND_SOC_TPLG_ABI_VERSION + || hdr->abi < SND_SOC_TPLG_ABI_VERSION_MIN) { + dev_err(tplg->dev, + "ASoC: pass %d invalid ABI version got 0x%x need 0x%x at offset 0x%lx size 0x%zx.\n", + tplg->pass, hdr->abi, + SND_SOC_TPLG_ABI_VERSION, soc_tplg_get_hdr_offset(tplg), + tplg->fw->size); + return -EINVAL; + } + + if (hdr->payload_size == 0) { + dev_err(tplg->dev, "ASoC: header has 0 size at offset 0x%lx.\n", + soc_tplg_get_hdr_offset(tplg)); + return -EINVAL; + } + + if (tplg->pass == hdr->type) + dev_dbg(tplg->dev, + "ASoC: Got 0x%x bytes of type %d version %d vendor %d at pass %d\n", + hdr->payload_size, hdr->type, hdr->version, + hdr->vendor_type, tplg->pass); + + return 1; +} + +/* check header type and call appropriate handler */ +static int soc_tplg_load_header(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + tplg->pos = tplg->hdr_pos + sizeof(struct snd_soc_tplg_hdr); + + /* check for matching ID */ + if (hdr->index != tplg->req_index && + tplg->req_index != SND_SOC_TPLG_INDEX_ALL) + return 0; + + tplg->index = hdr->index; + + switch (hdr->type) { + case SND_SOC_TPLG_TYPE_MIXER: + case SND_SOC_TPLG_TYPE_ENUM: + case SND_SOC_TPLG_TYPE_BYTES: + return soc_tplg_kcontrol_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAPM_GRAPH: + return soc_tplg_dapm_graph_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAPM_WIDGET: + return soc_tplg_dapm_widget_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_PCM: + return soc_tplg_pcm_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAI: + return soc_tplg_dai_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_DAI_LINK: + case SND_SOC_TPLG_TYPE_BACKEND_LINK: + /* physical link configurations */ + return soc_tplg_link_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_MANIFEST: + return soc_tplg_manifest_load(tplg, hdr); + default: + /* bespoke vendor data object */ + return soc_tplg_vendor_load(tplg, hdr); + } + + return 0; +} + +/* process the topology file headers */ +static int soc_tplg_process_headers(struct soc_tplg *tplg) +{ + struct snd_soc_tplg_hdr *hdr; + int ret; + + tplg->pass = SOC_TPLG_PASS_START; + + /* process the header types from start to end */ + while (tplg->pass <= SOC_TPLG_PASS_END) { + + tplg->hdr_pos = tplg->fw->data; + hdr = (struct snd_soc_tplg_hdr *)tplg->hdr_pos; + + while (!soc_tplg_is_eof(tplg)) { + + /* make sure header is valid before loading */ + ret = soc_valid_header(tplg, hdr); + if (ret < 0) + return ret; + else if (ret == 0) + break; + + /* load the header object */ + ret = soc_tplg_load_header(tplg, hdr); + if (ret < 0) + return ret; + + /* goto next header */ + tplg->hdr_pos += hdr->payload_size + + sizeof(struct snd_soc_tplg_hdr); + hdr = (struct snd_soc_tplg_hdr *)tplg->hdr_pos; + } + + /* next data type pass */ + tplg->pass++; + } + + /* signal DAPM we are complete */ + ret = soc_tplg_dapm_complete(tplg); + if (ret < 0) + dev_err(tplg->dev, + "ASoC: failed to initialise DAPM from Firmware\n"); + + return ret; +} + +static int soc_tplg_load(struct soc_tplg *tplg) +{ + int ret; + + ret = soc_tplg_process_headers(tplg); + if (ret == 0) + soc_tplg_complete(tplg); + + return ret; +} + +/* load audio component topology from "firmware" file */ +int snd_soc_tplg_component_load(struct snd_soc_component *comp, + struct snd_soc_tplg_ops *ops, const struct firmware *fw, u32 id) +{ + struct soc_tplg tplg; + int ret; + + /* setup parsing context */ + memset(&tplg, 0, sizeof(tplg)); + tplg.fw = fw; + tplg.dev = comp->dev; + tplg.comp = comp; + tplg.ops = ops; + tplg.req_index = id; + tplg.io_ops = ops->io_ops; + tplg.io_ops_count = ops->io_ops_count; + tplg.bytes_ext_ops = ops->bytes_ext_ops; + tplg.bytes_ext_ops_count = ops->bytes_ext_ops_count; + + ret = soc_tplg_load(&tplg); + /* free the created components if fail to load topology */ + if (ret) + snd_soc_tplg_component_remove(comp, SND_SOC_TPLG_INDEX_ALL); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_component_load); + +/* remove this dynamic widget */ +void snd_soc_tplg_widget_remove(struct snd_soc_dapm_widget *w) +{ + /* make sure we are a widget */ + if (w->dobj.type != SND_SOC_DOBJ_WIDGET) + return; + + remove_widget(w->dapm->component, &w->dobj, SOC_TPLG_PASS_WIDGET); +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_remove); + +/* remove all dynamic widgets from this DAPM context */ +void snd_soc_tplg_widget_remove_all(struct snd_soc_dapm_context *dapm, + u32 index) +{ + struct snd_soc_dapm_widget *w, *next_w; + + list_for_each_entry_safe(w, next_w, &dapm->card->widgets, list) { + + /* make sure we are a widget with correct context */ + if (w->dobj.type != SND_SOC_DOBJ_WIDGET || w->dapm != dapm) + continue; + + /* match ID */ + if (w->dobj.index != index && + w->dobj.index != SND_SOC_TPLG_INDEX_ALL) + continue; + /* check and free and dynamic widget kcontrols */ + snd_soc_tplg_widget_remove(w); + snd_soc_dapm_free_widget(w); + } + snd_soc_dapm_reset_cache(dapm); +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_remove_all); + +/* remove dynamic controls from the component driver */ +int snd_soc_tplg_component_remove(struct snd_soc_component *comp, u32 index) +{ + struct snd_card *card = comp->card->snd_card; + struct snd_soc_dobj *dobj, *next_dobj; + int pass = SOC_TPLG_PASS_END; + + /* process the header types from end to start */ + while (pass >= SOC_TPLG_PASS_START) { + + /* remove mixer controls */ + down_write(&card->controls_rwsem); + list_for_each_entry_safe(dobj, next_dobj, &comp->dobj_list, + list) { + + /* match index */ + if (dobj->index != index && + index != SND_SOC_TPLG_INDEX_ALL) + continue; + + switch (dobj->type) { + case SND_SOC_DOBJ_MIXER: + remove_mixer(comp, dobj, pass); + break; + case SND_SOC_DOBJ_ENUM: + remove_enum(comp, dobj, pass); + break; + case SND_SOC_DOBJ_BYTES: + remove_bytes(comp, dobj, pass); + break; + case SND_SOC_DOBJ_WIDGET: + remove_widget(comp, dobj, pass); + break; + case SND_SOC_DOBJ_PCM: + remove_dai(comp, dobj, pass); + break; + case SND_SOC_DOBJ_DAI_LINK: + remove_link(comp, dobj, pass); + break; + default: + dev_err(comp->dev, "ASoC: invalid component type %d for removal\n", + dobj->type); + break; + } + } + up_write(&card->controls_rwsem); + pass--; + } + + /* let caller know if FW can be freed when no objects are left */ + return !list_empty(&comp->dobj_list); +} +EXPORT_SYMBOL_GPL(snd_soc_tplg_component_remove); diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c new file mode 100644 index 000000000..e0c93496c --- /dev/null +++ b/sound/soc/soc-utils.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-util.c -- ALSA SoC Audio Layer utility functions +// +// Copyright 2009 Wolfson Microelectronics PLC. +// +// Author: Mark Brown +// Liam Girdwood + +#include +#include +#include +#include +#include +#include + +int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots) +{ + return sample_size * channels * tdm_slots; +} +EXPORT_SYMBOL_GPL(snd_soc_calc_frame_size); + +int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params) +{ + int sample_size; + + sample_size = snd_pcm_format_width(params_format(params)); + if (sample_size < 0) + return sample_size; + + return snd_soc_calc_frame_size(sample_size, params_channels(params), + 1); +} +EXPORT_SYMBOL_GPL(snd_soc_params_to_frame_size); + +int snd_soc_calc_bclk(int fs, int sample_size, int channels, int tdm_slots) +{ + return fs * snd_soc_calc_frame_size(sample_size, channels, tdm_slots); +} +EXPORT_SYMBOL_GPL(snd_soc_calc_bclk); + +int snd_soc_params_to_bclk(struct snd_pcm_hw_params *params) +{ + int ret; + + ret = snd_soc_params_to_frame_size(params); + + if (ret > 0) + return ret * params_rate(params); + else + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_params_to_bclk); + +int snd_soc_component_enable_pin(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_enable_pin(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_enable_pin(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_enable_pin); + +int snd_soc_component_enable_pin_unlocked(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_enable_pin_unlocked(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_enable_pin_unlocked(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_enable_pin_unlocked); + +int snd_soc_component_disable_pin(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_disable_pin(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_disable_pin(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_disable_pin); + +int snd_soc_component_disable_pin_unlocked(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_disable_pin_unlocked(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_disable_pin_unlocked(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_disable_pin_unlocked); + +int snd_soc_component_nc_pin(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_nc_pin(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_nc_pin(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_nc_pin); + +int snd_soc_component_nc_pin_unlocked(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_nc_pin_unlocked(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_nc_pin_unlocked(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_nc_pin_unlocked); + +int snd_soc_component_get_pin_status(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_get_pin_status(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_get_pin_status(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_get_pin_status); + +int snd_soc_component_force_enable_pin(struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_force_enable_pin(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_force_enable_pin(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_force_enable_pin); + +int snd_soc_component_force_enable_pin_unlocked( + struct snd_soc_component *component, + const char *pin) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(component); + char *full_name; + int ret; + + if (!component->name_prefix) + return snd_soc_dapm_force_enable_pin_unlocked(dapm, pin); + + full_name = kasprintf(GFP_KERNEL, "%s %s", component->name_prefix, pin); + if (!full_name) + return -ENOMEM; + + ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, full_name); + kfree(full_name); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_component_force_enable_pin_unlocked); + +static const struct snd_pcm_hardware dummy_dma_hardware = { + /* Random values to keep userspace happy when checking constraints */ + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .buffer_bytes_max = 128*1024, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = PAGE_SIZE*2, + .periods_min = 2, + .periods_max = 128, +}; + +static int dummy_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* BE's dont need dummy params */ + if (!rtd->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &dummy_dma_hardware); + + return 0; +} + +static const struct snd_pcm_ops dummy_dma_ops = { + .open = dummy_dma_open, + .ioctl = snd_pcm_lib_ioctl, +}; + +static const struct snd_soc_component_driver dummy_platform = { + .ops = &dummy_dma_ops, +}; + +static const struct snd_soc_component_driver dummy_codec = { + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) +/* + * The dummy CODEC is only meant to be used in situations where there is no + * actual hardware. + * + * If there is actual hardware even if it does not have a control bus + * the hardware will still have constraints like supported samplerates, etc. + * which should be modelled. And the data flow graph also should be modelled + * using DAPM. + */ +static struct snd_soc_dai_driver dummy_dai = { + .name = "snd-soc-dummy-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +int snd_soc_dai_is_dummy(struct snd_soc_dai *dai) +{ + if (dai->driver == &dummy_dai) + return 1; + return 0; +} + +static int snd_soc_dummy_probe(struct platform_device *pdev) +{ + int ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &dummy_codec, &dummy_dai, 1); + if (ret < 0) + return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, &dummy_platform, + NULL, 0); + + return ret; +} + +static struct platform_driver soc_dummy_driver = { + .driver = { + .name = "snd-soc-dummy", + }, + .probe = snd_soc_dummy_probe, +}; + +static struct platform_device *soc_dummy_dev; + +int __init snd_soc_util_init(void) +{ + int ret; + + soc_dummy_dev = + platform_device_register_simple("snd-soc-dummy", -1, NULL, 0); + if (IS_ERR(soc_dummy_dev)) + return PTR_ERR(soc_dummy_dev); + + ret = platform_driver_register(&soc_dummy_driver); + if (ret != 0) + platform_device_unregister(soc_dummy_dev); + + return ret; +} + +void __exit snd_soc_util_exit(void) +{ + platform_driver_unregister(&soc_dummy_driver); + platform_device_unregister(soc_dummy_dev); +} diff --git a/sound/soc/spear/Kconfig b/sound/soc/spear/Kconfig new file mode 100644 index 000000000..4fb91412e --- /dev/null +++ b/sound/soc/spear/Kconfig @@ -0,0 +1,9 @@ +config SND_SPEAR_SOC + tristate + select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_SPEAR_SPDIF_OUT + tristate + +config SND_SPEAR_SPDIF_IN + tristate diff --git a/sound/soc/spear/Makefile b/sound/soc/spear/Makefile new file mode 100644 index 000000000..31d9dae28 --- /dev/null +++ b/sound/soc/spear/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# SPEAR Platform Support +snd-soc-spear-pcm-objs := spear_pcm.o +snd-soc-spear-spdif-in-objs := spdif_in.o +snd-soc-spear-spdif-out-objs := spdif_out.o + +obj-$(CONFIG_SND_SPEAR_SOC) += snd-soc-spear-pcm.o +obj-$(CONFIG_SND_SPEAR_SPDIF_IN) += snd-soc-spear-spdif-in.o +obj-$(CONFIG_SND_SPEAR_SPDIF_OUT) += snd-soc-spear-spdif-out.o diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c new file mode 100644 index 000000000..78a6a360b --- /dev/null +++ b/sound/soc/spear/spdif_in.c @@ -0,0 +1,275 @@ +/* + * ALSA SoC SPDIF In Audio Layer for spear processors + * + * Copyright (C) 2012 ST Microelectronics + * Vipin Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spdif_in_regs.h" +#include "spear_pcm.h" + +struct spdif_in_params { + u32 format; +}; + +struct spdif_in_dev { + struct clk *clk; + struct spear_dma_data dma_params; + struct spdif_in_params saved_params; + void *io_base; + struct device *dev; + void (*reset_perip)(void); + int irq; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_pcm_config config; +}; + +static void spdif_in_configure(struct spdif_in_dev *host) +{ + u32 ctrl = SPDIF_IN_PRTYEN | SPDIF_IN_STATEN | SPDIF_IN_USREN | + SPDIF_IN_VALEN | SPDIF_IN_BLKEN; + ctrl |= SPDIF_MODE_16BIT | SPDIF_FIFO_THRES_16; + + writel(ctrl, host->io_base + SPDIF_IN_CTRL); + writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK); +} + +static int spdif_in_dai_probe(struct snd_soc_dai *dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + host->dma_params_rx.filter_data = &host->dma_params; + dai->capture_dma_data = &host->dma_params_rx; + + return 0; +} + +static void spdif_in_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return; + + writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK); +} + +static void spdif_in_format(struct spdif_in_dev *host, u32 format) +{ + u32 ctrl = readl(host->io_base + SPDIF_IN_CTRL); + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + ctrl |= SPDIF_XTRACT_16BIT; + break; + + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + ctrl &= ~SPDIF_XTRACT_16BIT; + break; + } + + writel(ctrl, host->io_base + SPDIF_IN_CTRL); +} + +static int spdif_in_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + u32 format; + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + format = params_format(params); + host->saved_params.format = format; + + return 0; +} + +static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + u32 ctrl; + int ret = 0; + + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + clk_enable(host->clk); + spdif_in_configure(host); + spdif_in_format(host, host->saved_params.format); + + ctrl = readl(host->io_base + SPDIF_IN_CTRL); + ctrl |= SPDIF_IN_SAMPLE | SPDIF_IN_ENB; + writel(ctrl, host->io_base + SPDIF_IN_CTRL); + writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl = readl(host->io_base + SPDIF_IN_CTRL); + ctrl &= ~(SPDIF_IN_SAMPLE | SPDIF_IN_ENB); + writel(ctrl, host->io_base + SPDIF_IN_CTRL); + writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK); + + if (host->reset_perip) + host->reset_perip(); + clk_disable(host->clk); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static const struct snd_soc_dai_ops spdif_in_dai_ops = { + .shutdown = spdif_in_shutdown, + .trigger = spdif_in_trigger, + .hw_params = spdif_in_hw_params, +}; + +static struct snd_soc_dai_driver spdif_in_dai = { + .probe = spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &spdif_in_dai_ops, +}; + +static const struct snd_soc_component_driver spdif_in_component = { + .name = "spdif-in", +}; + +static irqreturn_t spdif_in_irq(int irq, void *arg) +{ + struct spdif_in_dev *host = (struct spdif_in_dev *)arg; + + u32 irq_status = readl(host->io_base + SPDIF_IN_IRQ); + + if (!irq_status) + return IRQ_NONE; + + if (irq_status & SPDIF_IRQ_FIFOWRITE) + dev_err(host->dev, "spdif in: fifo write error"); + if (irq_status & SPDIF_IRQ_EMPTYFIFOREAD) + dev_err(host->dev, "spdif in: empty fifo read error"); + if (irq_status & SPDIF_IRQ_FIFOFULL) + dev_err(host->dev, "spdif in: fifo full error"); + if (irq_status & SPDIF_IRQ_OUTOFRANGE) + dev_err(host->dev, "spdif in: out of range error"); + + writel(0, host->io_base + SPDIF_IN_IRQ); + + return IRQ_HANDLED; +} + +static int spdif_in_probe(struct platform_device *pdev) +{ + struct spdif_in_dev *host; + struct spear_spdif_platform_data *pdata; + struct resource *res, *res_fifo; + void __iomem *io_base; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + res_fifo = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res_fifo) + return -EINVAL; + + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->io_base = io_base; + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq); + return host->irq; + } + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + pdata = dev_get_platdata(&pdev->dev); + + if (!pdata) + return -EINVAL; + + host->dma_params.data = pdata->dma_params; + host->dma_params.addr = res_fifo->start; + host->dma_params.max_burst = 16; + host->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->reset_perip = pdata->reset_perip; + + host->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, host); + + ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, + "spdif-in", host); + if (ret) { + dev_warn(&pdev->dev, "request_irq failed\n"); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component, + &spdif_in_dai, 1); + if (ret) + return ret; + + return devm_spear_pcm_platform_register(&pdev->dev, &host->config, + pdata->filter); +} + +static struct platform_driver spdif_in_driver = { + .probe = spdif_in_probe, + .driver = { + .name = "spdif-in", + }, +}; + +module_platform_driver(spdif_in_driver); + +MODULE_AUTHOR("Vipin Kumar "); +MODULE_DESCRIPTION("SPEAr SPDIF IN SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spdif_in"); diff --git a/sound/soc/spear/spdif_in_regs.h b/sound/soc/spear/spdif_in_regs.h new file mode 100644 index 000000000..37af7bc66 --- /dev/null +++ b/sound/soc/spear/spdif_in_regs.h @@ -0,0 +1,60 @@ +/* + * SPEAr SPDIF IN controller header file + * + * Copyright (ST) 2011 Vipin Kumar (vipin.kumar@st.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPDIF_IN_REGS_H +#define SPDIF_IN_REGS_H + +#define SPDIF_IN_CTRL 0x00 + #define SPDIF_IN_PRTYEN (1 << 20) + #define SPDIF_IN_STATEN (1 << 19) + #define SPDIF_IN_USREN (1 << 18) + #define SPDIF_IN_VALEN (1 << 17) + #define SPDIF_IN_BLKEN (1 << 16) + + #define SPDIF_MODE_24BIT (8 << 12) + #define SPDIF_MODE_23BIT (7 << 12) + #define SPDIF_MODE_22BIT (6 << 12) + #define SPDIF_MODE_21BIT (5 << 12) + #define SPDIF_MODE_20BIT (4 << 12) + #define SPDIF_MODE_19BIT (3 << 12) + #define SPDIF_MODE_18BIT (2 << 12) + #define SPDIF_MODE_17BIT (1 << 12) + #define SPDIF_MODE_16BIT (0 << 12) + #define SPDIF_MODE_MASK (0x0F << 12) + + #define SPDIF_IN_VALID (1 << 11) + #define SPDIF_IN_SAMPLE (1 << 10) + #define SPDIF_DATA_SWAP (1 << 9) + #define SPDIF_IN_ENB (1 << 8) + #define SPDIF_DATA_REVERT (1 << 7) + #define SPDIF_XTRACT_16BIT (1 << 6) + #define SPDIF_FIFO_THRES_16 (16 << 0) + +#define SPDIF_IN_IRQ_MASK 0x04 +#define SPDIF_IN_IRQ 0x08 + #define SPDIF_IRQ_FIFOWRITE (1 << 0) + #define SPDIF_IRQ_EMPTYFIFOREAD (1 << 1) + #define SPDIF_IRQ_FIFOFULL (1 << 2) + #define SPDIF_IRQ_OUTOFRANGE (1 << 3) + +#define SPDIF_IN_STA 0x0C + #define SPDIF_IN_LOCK (0x1 << 0) + +#endif /* SPDIF_IN_REGS_H */ diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c new file mode 100644 index 000000000..58d584381 --- /dev/null +++ b/sound/soc/spear/spdif_out.c @@ -0,0 +1,364 @@ +/* + * ALSA SoC SPDIF Out Audio Layer for spear processors + * + * Copyright (C) 2012 ST Microelectronics + * Vipin Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spdif_out_regs.h" +#include "spear_pcm.h" + +struct spdif_out_params { + u32 rate; + u32 core_freq; + u32 mute; +}; + +struct spdif_out_dev { + struct clk *clk; + struct spear_dma_data dma_params; + struct spdif_out_params saved_params; + u32 running; + void __iomem *io_base; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_pcm_config config; +}; + +static void spdif_out_configure(struct spdif_out_dev *host) +{ + writel(SPDIF_OUT_RESET, host->io_base + SPDIF_OUT_SOFT_RST); + mdelay(1); + writel(readl(host->io_base + SPDIF_OUT_SOFT_RST) & ~SPDIF_OUT_RESET, + host->io_base + SPDIF_OUT_SOFT_RST); + + writel(SPDIF_OUT_FDMA_TRIG_16 | SPDIF_OUT_MEMFMT_16_16 | + SPDIF_OUT_VALID_HW | SPDIF_OUT_USER_HW | + SPDIF_OUT_CHNLSTA_HW | SPDIF_OUT_PARITY_HW, + host->io_base + SPDIF_OUT_CFG); + + writel(0x7F, host->io_base + SPDIF_OUT_INT_STA_CLR); + writel(0x7F, host->io_base + SPDIF_OUT_INT_EN_CLR); +} + +static int spdif_out_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + ret = clk_enable(host->clk); + if (ret) + return ret; + + host->running = true; + spdif_out_configure(host); + + return 0; +} + +static void spdif_out_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return; + + clk_disable(host->clk); + host->running = false; +} + +static void spdif_out_clock(struct spdif_out_dev *host, u32 core_freq, + u32 rate) +{ + u32 divider, ctrl; + + clk_set_rate(host->clk, core_freq); + divider = DIV_ROUND_CLOSEST(clk_get_rate(host->clk), (rate * 128)); + + ctrl = readl(host->io_base + SPDIF_OUT_CTRL); + ctrl &= ~SPDIF_DIVIDER_MASK; + ctrl |= (divider << SPDIF_DIVIDER_SHIFT) & SPDIF_DIVIDER_MASK; + writel(ctrl, host->io_base + SPDIF_OUT_CTRL); +} + +static int spdif_out_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + u32 rate, core_freq; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + rate = params_rate(params); + + switch (rate) { + case 8000: + case 16000: + case 32000: + case 64000: + /* + * The clock is multiplied by 10 to bring it to feasible range + * of frequencies for sscg + */ + core_freq = 64000 * 128 * 10; /* 81.92 MHz */ + break; + case 5512: + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + core_freq = 176400 * 128; /* 22.5792 MHz */ + break; + case 48000: + case 96000: + case 192000: + default: + core_freq = 192000 * 128; /* 24.576 MHz */ + break; + } + + spdif_out_clock(host, core_freq, rate); + host->saved_params.core_freq = core_freq; + host->saved_params.rate = rate; + + return 0; +} + +static int spdif_out_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + u32 ctrl; + int ret = 0; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ctrl = readl(host->io_base + SPDIF_OUT_CTRL); + ctrl &= ~SPDIF_OPMODE_MASK; + if (!host->saved_params.mute) + ctrl |= SPDIF_OPMODE_AUD_DATA | + SPDIF_STATE_NORMAL; + else + ctrl |= SPDIF_OPMODE_MUTE_PCM; + writel(ctrl, host->io_base + SPDIF_OUT_CTRL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ctrl = readl(host->io_base + SPDIF_OUT_CTRL); + ctrl &= ~SPDIF_OPMODE_MASK; + ctrl |= SPDIF_OPMODE_OFF; + writel(ctrl, host->io_base + SPDIF_OUT_CTRL); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int spdif_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + u32 val; + + host->saved_params.mute = mute; + val = readl(host->io_base + SPDIF_OUT_CTRL); + val &= ~SPDIF_OPMODE_MASK; + + if (mute) + val |= SPDIF_OPMODE_MUTE_PCM; + else { + if (host->running) + val |= SPDIF_OPMODE_AUD_DATA | SPDIF_STATE_NORMAL; + else + val |= SPDIF_OPMODE_OFF; + } + + writel(val, host->io_base + SPDIF_OUT_CTRL); + return 0; +} + +static int spdif_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(cpu_dai); + + ucontrol->value.integer.value[0] = host->saved_params.mute; + return 0; +} + +static int spdif_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(cpu_dai); + + if (host->saved_params.mute == ucontrol->value.integer.value[0]) + return 0; + + spdif_digital_mute(cpu_dai, ucontrol->value.integer.value[0]); + + return 1; +} +static const struct snd_kcontrol_new spdif_out_controls[] = { + SOC_SINGLE_BOOL_EXT("IEC958 Playback Switch", 0, + spdif_mute_get, spdif_mute_put), +}; + +static int spdif_soc_dai_probe(struct snd_soc_dai *dai) +{ + struct spdif_out_dev *host = snd_soc_dai_get_drvdata(dai); + + host->dma_params_tx.filter_data = &host->dma_params; + dai->playback_dma_data = &host->dma_params_tx; + + return snd_soc_add_dai_controls(dai, spdif_out_controls, + ARRAY_SIZE(spdif_out_controls)); +} + +static const struct snd_soc_dai_ops spdif_out_dai_ops = { + .digital_mute = spdif_digital_mute, + .startup = spdif_out_startup, + .shutdown = spdif_out_shutdown, + .trigger = spdif_out_trigger, + .hw_params = spdif_out_hw_params, +}; + +static struct snd_soc_dai_driver spdif_out_dai = { + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .probe = spdif_soc_dai_probe, + .ops = &spdif_out_dai_ops, +}; + +static const struct snd_soc_component_driver spdif_out_component = { + .name = "spdif-out", +}; + +static int spdif_out_probe(struct platform_device *pdev) +{ + struct spdif_out_dev *host; + struct spear_spdif_platform_data *pdata; + struct resource *res; + int ret; + + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->io_base)) + return PTR_ERR(host->io_base); + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + pdata = dev_get_platdata(&pdev->dev); + + host->dma_params.data = pdata->dma_params; + host->dma_params.addr = res->start + SPDIF_OUT_FIFO_DATA; + host->dma_params.max_burst = 16; + host->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + dev_set_drvdata(&pdev->dev, host); + + ret = devm_snd_soc_register_component(&pdev->dev, &spdif_out_component, + &spdif_out_dai, 1); + if (ret) + return ret; + + return devm_spear_pcm_platform_register(&pdev->dev, &host->config, + pdata->filter); +} + +#ifdef CONFIG_PM +static int spdif_out_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spdif_out_dev *host = dev_get_drvdata(&pdev->dev); + + if (host->running) + clk_disable(host->clk); + + return 0; +} + +static int spdif_out_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spdif_out_dev *host = dev_get_drvdata(&pdev->dev); + + if (host->running) { + clk_enable(host->clk); + spdif_out_configure(host); + spdif_out_clock(host, host->saved_params.core_freq, + host->saved_params.rate); + } + return 0; +} + +static SIMPLE_DEV_PM_OPS(spdif_out_dev_pm_ops, spdif_out_suspend, \ + spdif_out_resume); + +#define SPDIF_OUT_DEV_PM_OPS (&spdif_out_dev_pm_ops) + +#else +#define SPDIF_OUT_DEV_PM_OPS NULL + +#endif + +static struct platform_driver spdif_out_driver = { + .probe = spdif_out_probe, + .driver = { + .name = "spdif-out", + .pm = SPDIF_OUT_DEV_PM_OPS, + }, +}; + +module_platform_driver(spdif_out_driver); + +MODULE_AUTHOR("Vipin Kumar "); +MODULE_DESCRIPTION("SPEAr SPDIF OUT SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:spdif_out"); diff --git a/sound/soc/spear/spdif_out_regs.h b/sound/soc/spear/spdif_out_regs.h new file mode 100644 index 000000000..a5e53324b --- /dev/null +++ b/sound/soc/spear/spdif_out_regs.h @@ -0,0 +1,79 @@ +/* + * SPEAr SPDIF OUT controller header file + * + * Copyright (ST) 2011 Vipin Kumar (vipin.kumar@st.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SPDIF_OUT_REGS_H +#define SPDIF_OUT_REGS_H + +#define SPDIF_OUT_SOFT_RST 0x00 + #define SPDIF_OUT_RESET (1 << 0) +#define SPDIF_OUT_FIFO_DATA 0x04 +#define SPDIF_OUT_INT_STA 0x08 +#define SPDIF_OUT_INT_STA_CLR 0x0C + #define SPDIF_INT_UNDERFLOW (1 << 0) + #define SPDIF_INT_EODATA (1 << 1) + #define SPDIF_INT_EOBLOCK (1 << 2) + #define SPDIF_INT_EOLATENCY (1 << 3) + #define SPDIF_INT_EOPD_DATA (1 << 4) + #define SPDIF_INT_MEMFULLREAD (1 << 5) + #define SPDIF_INT_EOPD_PAUSE (1 << 6) + +#define SPDIF_OUT_INT_EN 0x10 +#define SPDIF_OUT_INT_EN_SET 0x14 +#define SPDIF_OUT_INT_EN_CLR 0x18 +#define SPDIF_OUT_CTRL 0x1C + #define SPDIF_OPMODE_MASK (7 << 0) + #define SPDIF_OPMODE_OFF (0 << 0) + #define SPDIF_OPMODE_MUTE_PCM (1 << 0) + #define SPDIF_OPMODE_MUTE_PAUSE (2 << 0) + #define SPDIF_OPMODE_AUD_DATA (3 << 0) + #define SPDIF_OPMODE_ENCODE (4 << 0) + #define SPDIF_STATE_NORMAL (1 << 3) + #define SPDIF_DIVIDER_MASK (0xff << 5) + #define SPDIF_DIVIDER_SHIFT (5) + #define SPDIF_SAMPLEREAD_MASK (0x1ffff << 15) + #define SPDIF_SAMPLEREAD_SHIFT (15) +#define SPDIF_OUT_STA 0x20 +#define SPDIF_OUT_PA_PB 0x24 +#define SPDIF_OUT_PC_PD 0x28 +#define SPDIF_OUT_CL1 0x2C +#define SPDIF_OUT_CR1 0x30 +#define SPDIF_OUT_CL2_CR2_UV 0x34 +#define SPDIF_OUT_PAUSE_LAT 0x38 +#define SPDIF_OUT_FRMLEN_BRST 0x3C +#define SPDIF_OUT_CFG 0x40 + #define SPDIF_OUT_MEMFMT_16_0 (0 << 5) + #define SPDIF_OUT_MEMFMT_16_16 (1 << 5) + #define SPDIF_OUT_VALID_DMA (0 << 3) + #define SPDIF_OUT_VALID_HW (1 << 3) + #define SPDIF_OUT_USER_DMA (0 << 2) + #define SPDIF_OUT_USER_HW (1 << 2) + #define SPDIF_OUT_CHNLSTA_DMA (0 << 1) + #define SPDIF_OUT_CHNLSTA_HW (1 << 1) + #define SPDIF_OUT_PARITY_HW (0 << 0) + #define SPDIF_OUT_PARITY_DMA (1 << 0) + #define SPDIF_OUT_FDMA_TRIG_2 (2 << 8) + #define SPDIF_OUT_FDMA_TRIG_6 (6 << 8) + #define SPDIF_OUT_FDMA_TRIG_8 (8 << 8) + #define SPDIF_OUT_FDMA_TRIG_10 (10 << 8) + #define SPDIF_OUT_FDMA_TRIG_12 (12 << 8) + #define SPDIF_OUT_FDMA_TRIG_16 (16 << 8) + #define SPDIF_OUT_FDMA_TRIG_18 (18 << 8) + +#endif /* SPDIF_OUT_REGS_H */ diff --git a/sound/soc/spear/spear_pcm.c b/sound/soc/spear/spear_pcm.c new file mode 100644 index 000000000..e8476da15 --- /dev/null +++ b/sound/soc/spear/spear_pcm.c @@ -0,0 +1,55 @@ +/* + * ALSA PCM interface for ST SPEAr Processors + * + * sound/soc/spear/spear_pcm.c + * + * Copyright (C) 2012 ST Microelectronics + * Rajeev Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "spear_pcm.h" + +static const struct snd_pcm_hardware spear_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .buffer_bytes_max = 16 * 1024, /* max buffer size */ + .period_bytes_min = 2 * 1024, /* 1 msec data minimum period size */ + .period_bytes_max = 2 * 1024, /* maximum period size */ + .periods_min = 1, /* min # periods */ + .periods_max = 8, /* max # of periods */ + .fifo_size = 0, /* fifo size in bytes */ +}; + +static const struct snd_dmaengine_pcm_config spear_dmaengine_pcm_config = { + .pcm_hardware = &spear_pcm_hardware, + .prealloc_buffer_size = 16 * 1024, +}; + +int devm_spear_pcm_platform_register(struct device *dev, + struct snd_dmaengine_pcm_config *config, + bool (*filter)(struct dma_chan *chan, void *slave)) +{ + *config = spear_dmaengine_pcm_config; + config->compat_filter_fn = filter; + + return devm_snd_dmaengine_pcm_register(dev, config, + SND_DMAENGINE_PCM_FLAG_NO_DT | + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(devm_spear_pcm_platform_register); + +MODULE_AUTHOR("Rajeev Kumar "); +MODULE_DESCRIPTION("SPEAr PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/spear/spear_pcm.h b/sound/soc/spear/spear_pcm.h new file mode 100644 index 000000000..9b0ca62d6 --- /dev/null +++ b/sound/soc/spear/spear_pcm.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#ifndef __SPEAR_PCM_H__ +#define __SPEAR_PCM_H__ + +int devm_spear_pcm_platform_register(struct device *dev, + struct snd_dmaengine_pcm_config *config, + bool (*filter)(struct dma_chan *chan, void *slave)); + +#endif diff --git a/sound/soc/sti/Kconfig b/sound/soc/sti/Kconfig new file mode 100644 index 000000000..64a690077 --- /dev/null +++ b/sound/soc/sti/Kconfig @@ -0,0 +1,11 @@ +# +# STM SoC audio configuration +# +menuconfig SND_SOC_STI + tristate "SoC Audio support for STI System-On-Chip" + depends on SND_SOC + depends on ARCH_STI || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to enable ASoC-support for + any of the STI platforms (e.g. STIH416). diff --git a/sound/soc/sti/Makefile b/sound/soc/sti/Makefile new file mode 100644 index 000000000..4b188d2d7 --- /dev/null +++ b/sound/soc/sti/Makefile @@ -0,0 +1,4 @@ +# STI platform support +snd-soc-sti-objs := sti_uniperif.o uniperif_player.o uniperif_reader.o + +obj-$(CONFIG_SND_SOC_STI) += snd-soc-sti.o diff --git a/sound/soc/sti/sti_uniperif.c b/sound/soc/sti/sti_uniperif.c new file mode 100644 index 000000000..98eb205a0 --- /dev/null +++ b/sound/soc/sti/sti_uniperif.c @@ -0,0 +1,513 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include + +#include "uniperif.h" + +/* + * User frame size shall be 2, 4, 6 or 8 32-bits words length + * (i.e. 8, 16, 24 or 32 bytes) + * This constraint comes from allowed values for + * UNIPERIF_I2S_FMT_NUM_CH register + */ +#define UNIPERIF_MAX_FRAME_SZ 0x20 +#define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ) + +struct sti_uniperiph_dev_data { + unsigned int id; /* Nb available player instances */ + unsigned int version; /* player IP version */ + unsigned int stream; + const char *dai_names; + enum uniperif_type type; +}; + +static const struct sti_uniperiph_dev_data sti_uniplayer_hdmi = { + .id = 0, + .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, + .stream = SNDRV_PCM_STREAM_PLAYBACK, + .dai_names = "Uni Player #0 (HDMI)", + .type = SND_ST_UNIPERIF_TYPE_HDMI +}; + +static const struct sti_uniperiph_dev_data sti_uniplayer_pcm_out = { + .id = 1, + .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, + .stream = SNDRV_PCM_STREAM_PLAYBACK, + .dai_names = "Uni Player #1 (PCM OUT)", + .type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM, +}; + +static const struct sti_uniperiph_dev_data sti_uniplayer_dac = { + .id = 2, + .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, + .stream = SNDRV_PCM_STREAM_PLAYBACK, + .dai_names = "Uni Player #2 (DAC)", + .type = SND_ST_UNIPERIF_TYPE_PCM, +}; + +static const struct sti_uniperiph_dev_data sti_uniplayer_spdif = { + .id = 3, + .version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0, + .stream = SNDRV_PCM_STREAM_PLAYBACK, + .dai_names = "Uni Player #3 (SPDIF)", + .type = SND_ST_UNIPERIF_TYPE_SPDIF +}; + +static const struct sti_uniperiph_dev_data sti_unireader_pcm_in = { + .id = 0, + .version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, + .stream = SNDRV_PCM_STREAM_CAPTURE, + .dai_names = "Uni Reader #0 (PCM IN)", + .type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM, +}; + +static const struct sti_uniperiph_dev_data sti_unireader_hdmi_in = { + .id = 1, + .version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, + .stream = SNDRV_PCM_STREAM_CAPTURE, + .dai_names = "Uni Reader #1 (HDMI IN)", + .type = SND_ST_UNIPERIF_TYPE_PCM, +}; + +static const struct of_device_id snd_soc_sti_match[] = { + { .compatible = "st,stih407-uni-player-hdmi", + .data = &sti_uniplayer_hdmi + }, + { .compatible = "st,stih407-uni-player-pcm-out", + .data = &sti_uniplayer_pcm_out + }, + { .compatible = "st,stih407-uni-player-dac", + .data = &sti_uniplayer_dac + }, + { .compatible = "st,stih407-uni-player-spdif", + .data = &sti_uniplayer_spdif + }, + { .compatible = "st,stih407-uni-reader-pcm_in", + .data = &sti_unireader_pcm_in + }, + { .compatible = "st,stih407-uni-reader-hdmi", + .data = &sti_unireader_hdmi_in + }, + {}, +}; + +int sti_uniperiph_reset(struct uniperif *uni) +{ + int count = 10; + + /* Reset uniperipheral uni */ + SET_UNIPERIF_SOFT_RST_SOFT_RST(uni); + + if (uni->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { + while (GET_UNIPERIF_SOFT_RST_SOFT_RST(uni) && count) { + udelay(5); + count--; + } + } + + if (!count) { + dev_err(uni->dev, "Failed to reset uniperif\n"); + return -EIO; + } + + return 0; +} + +int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; + int i, frame_size, avail_slots; + + if (!UNIPERIF_TYPE_IS_TDM(uni)) { + dev_err(uni->dev, "cpu dai not in tdm mode\n"); + return -EINVAL; + } + + /* store info in unip context */ + uni->tdm_slot.slots = slots; + uni->tdm_slot.slot_width = slot_width; + /* unip is unidirectionnal */ + uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask; + + /* number of available timeslots */ + for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) { + if ((uni->tdm_slot.mask >> i) & 0x01) + avail_slots++; + } + uni->tdm_slot.avail_slots = avail_slots; + + /* frame size in bytes */ + frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8; + + /* check frame size is allowed */ + if ((frame_size > UNIPERIF_MAX_FRAME_SZ) || + (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) { + dev_err(uni->dev, "frame size not allowed: %d bytes\n", + frame_size); + return -EINVAL; + } + + return 0; +} + +int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct uniperif *uni = rule->private; + struct snd_interval t; + + t.min = uni->tdm_slot.avail_slots; + t.max = uni->tdm_slot.avail_slots; + t.openmin = 0; + t.openmax = 0; + t.integer = 0; + + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct uniperif *uni = rule->private; + struct snd_mask *maskp = hw_param_mask(params, rule->var); + u64 format; + + switch (uni->tdm_slot.slot_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; + break; + case 32: + format = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + dev_err(uni->dev, "format not supported: %d bits\n", + uni->tdm_slot.slot_width); + return -EINVAL; + } + + maskp->bits[0] &= (u_int32_t)format; + maskp->bits[1] &= (u_int32_t)(format >> 32); + /* clear remaining indexes */ + memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8); + + if (!maskp->bits[0] && !maskp->bits[1]) + return -EINVAL; + + return 0; +} + +int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, + unsigned int *word_pos) +{ + int slot_width = uni->tdm_slot.slot_width / 8; + int slots_num = uni->tdm_slot.slots; + unsigned int slots_mask = uni->tdm_slot.mask; + int i, j, k; + unsigned int word16_pos[4]; + + /* word16_pos: + * word16_pos[0] = WORDX_LSB + * word16_pos[1] = WORDX_MSB, + * word16_pos[2] = WORDX+1_LSB + * word16_pos[3] = WORDX+1_MSB + */ + + /* set unip word position */ + for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) { + if ((slots_mask >> i) & 0x01) { + word16_pos[j] = i * slot_width; + + if (slot_width == 4) { + word16_pos[j + 1] = word16_pos[j] + 2; + j++; + } + j++; + + if (j > 3) { + word_pos[k] = word16_pos[1] | + (word16_pos[0] << 8) | + (word16_pos[3] << 16) | + (word16_pos[2] << 24); + j = 0; + k++; + } + } + } + + return 0; +} + +/* + * sti_uniperiph_dai_create_ctrl + * This function is used to create Ctrl associated to DAI but also pcm device. + * Request is done by front end to associate ctrl with pcm device id + */ +static int sti_uniperiph_dai_create_ctrl(struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; + struct snd_kcontrol_new *ctrl; + int i; + + if (!uni->num_ctrls) + return 0; + + for (i = 0; i < uni->num_ctrls; i++) { + /* + * Several Control can have same name. Controls are indexed on + * Uniperipheral instance ID + */ + ctrl = &uni->snd_ctrls[i]; + ctrl->index = uni->id; + ctrl->device = uni->id; + } + + return snd_soc_add_dai_controls(dai, uni->snd_ctrls, uni->num_ctrls); +} + +/* + * DAI + */ +int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; + struct snd_dmaengine_dai_dma_data *dma_data; + int transfer_size; + + if (uni->type == SND_ST_UNIPERIF_TYPE_TDM) + /* transfer size = user frame size (in 32-bits FIFO cell) */ + transfer_size = snd_soc_params_to_frame_size(params) / 32; + else + transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES; + + dma_data = snd_soc_dai_get_dma_data(dai, substream); + dma_data->maxburst = transfer_size; + + return 0; +} + +int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + + priv->dai_data.uni->daifmt = fmt; + + return 0; +} + +static int sti_uniperiph_dai_suspend(struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; + int ret; + + /* The uniperipheral should be in stopped state */ + if (uni->state != UNIPERIF_STATE_STOPPED) { + dev_err(uni->dev, "%s: invalid uni state( %d)\n", + __func__, (int)uni->state); + return -EBUSY; + } + + /* Pinctrl: switch pinstate to sleep */ + ret = pinctrl_pm_select_sleep_state(uni->dev); + if (ret) + dev_err(uni->dev, "%s: failed to select pinctrl state\n", + __func__); + + return ret; +} + +static int sti_uniperiph_dai_resume(struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *uni = priv->dai_data.uni; + int ret; + + if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = uni_player_resume(uni); + if (ret) + return ret; + } + + /* pinctrl: switch pinstate to default */ + ret = pinctrl_pm_select_default_state(uni->dev); + if (ret) + dev_err(uni->dev, "%s: failed to select pinctrl state\n", + __func__); + + return ret; +} + +static int sti_uniperiph_dai_probe(struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct sti_uniperiph_dai *dai_data = &priv->dai_data; + + /* DMA settings*/ + if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_init_dma_data(dai, &dai_data->dma_data, NULL); + else + snd_soc_dai_init_dma_data(dai, NULL, &dai_data->dma_data); + + dai_data->dma_data.addr = dai_data->uni->fifo_phys_address; + dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + return sti_uniperiph_dai_create_ctrl(dai); +} + +static const struct snd_soc_dai_driver sti_uniperiph_dai_template = { + .probe = sti_uniperiph_dai_probe, + .suspend = sti_uniperiph_dai_suspend, + .resume = sti_uniperiph_dai_resume +}; + +static const struct snd_soc_component_driver sti_uniperiph_dai_component = { + .name = "sti_cpu_dai", +}; + +static int sti_uniperiph_cpu_dai_of(struct device_node *node, + struct sti_uniperiph_data *priv) +{ + struct device *dev = &priv->pdev->dev; + struct sti_uniperiph_dai *dai_data = &priv->dai_data; + struct snd_soc_dai_driver *dai = priv->dai; + struct snd_soc_pcm_stream *stream; + struct uniperif *uni; + const struct of_device_id *of_id; + const struct sti_uniperiph_dev_data *dev_data; + const char *mode; + int ret; + + /* Populate data structure depending on compatibility */ + of_id = of_match_node(snd_soc_sti_match, node); + if (!of_id->data) { + dev_err(dev, "data associated to device is missing\n"); + return -EINVAL; + } + dev_data = (struct sti_uniperiph_dev_data *)of_id->data; + + uni = devm_kzalloc(dev, sizeof(*uni), GFP_KERNEL); + if (!uni) + return -ENOMEM; + + uni->id = dev_data->id; + uni->ver = dev_data->version; + + *dai = sti_uniperiph_dai_template; + dai->name = dev_data->dai_names; + + /* Get resources */ + uni->mem_region = platform_get_resource(priv->pdev, IORESOURCE_MEM, 0); + + if (!uni->mem_region) { + dev_err(dev, "Failed to get memory resource\n"); + return -ENODEV; + } + + uni->base = devm_ioremap_resource(dev, uni->mem_region); + + if (IS_ERR(uni->base)) + return PTR_ERR(uni->base); + + uni->fifo_phys_address = uni->mem_region->start + + UNIPERIF_FIFO_DATA_OFFSET(uni); + + uni->irq = platform_get_irq(priv->pdev, 0); + if (uni->irq < 0) { + dev_err(dev, "Failed to get IRQ resource\n"); + return -ENXIO; + } + + uni->type = dev_data->type; + + /* check if player should be configured for tdm */ + if (dev_data->type & SND_ST_UNIPERIF_TYPE_TDM) { + if (!of_property_read_string(node, "st,tdm-mode", &mode)) + uni->type = SND_ST_UNIPERIF_TYPE_TDM; + else + uni->type = SND_ST_UNIPERIF_TYPE_PCM; + } + + dai_data->uni = uni; + dai_data->stream = dev_data->stream; + + if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = uni_player_init(priv->pdev, uni); + stream = &dai->playback; + } else { + ret = uni_reader_init(priv->pdev, uni); + stream = &dai->capture; + } + if (ret < 0) + return ret; + + dai->ops = uni->dai_ops; + + stream->stream_name = dai->name; + stream->channels_min = uni->hw->channels_min; + stream->channels_max = uni->hw->channels_max; + stream->rates = uni->hw->rates; + stream->formats = uni->hw->formats; + + return 0; +} + +static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = { + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static int sti_uniperiph_probe(struct platform_device *pdev) +{ + struct sti_uniperiph_data *priv; + struct device_node *node = pdev->dev.of_node; + int ret; + + /* Allocate the private data and the CPU_DAI array */ + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai), GFP_KERNEL); + if (!priv->dai) + return -ENOMEM; + + priv->pdev = pdev; + + ret = sti_uniperiph_cpu_dai_of(node, priv); + + dev_set_drvdata(&pdev->dev, priv); + + ret = devm_snd_soc_register_component(&pdev->dev, + &sti_uniperiph_dai_component, + priv->dai, 1); + if (ret < 0) + return ret; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, + &dmaengine_pcm_config, 0); +} + +static struct platform_driver sti_uniperiph_driver = { + .driver = { + .name = "sti-uniperiph-dai", + .of_match_table = snd_soc_sti_match, + }, + .probe = sti_uniperiph_probe, +}; +module_platform_driver(sti_uniperiph_driver); + +MODULE_DESCRIPTION("uniperipheral DAI driver"); +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sti/uniperif.h b/sound/soc/sti/uniperif.h new file mode 100644 index 000000000..cfcb0ea9d --- /dev/null +++ b/sound/soc/sti/uniperif.h @@ -0,0 +1,1416 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef __SND_ST_AUD_UNIPERIF_H +#define __SND_ST_AUD_UNIPERIF_H + +#include + +#include + +/* + * Register access macros + */ + +#define GET_UNIPERIF_REG(ip, offset, shift, mask) \ + ((readl_relaxed(ip->base + offset) >> shift) & mask) +#define SET_UNIPERIF_REG(ip, offset, shift, mask, value) \ + writel_relaxed(((readl_relaxed(ip->base + offset) & \ + ~(mask << shift)) | (((value) & mask) << shift)), ip->base + offset) +#define SET_UNIPERIF_BIT_REG(ip, offset, shift, mask, value) \ + writel_relaxed((((value) & mask) << shift), ip->base + offset) + +/* + * UNIPERIF_SOFT_RST reg + */ + +#define UNIPERIF_SOFT_RST_OFFSET(ip) 0x0000 +#define GET_UNIPERIF_SOFT_RST(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + readl_relaxed(ip->base + UNIPERIF_SOFT_RST_OFFSET(ip)) : 0) +#define SET_UNIPERIF_SOFT_RST(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_SOFT_RST_OFFSET(ip)) + +/* SOFT_RST */ +#define UNIPERIF_SOFT_RST_SOFT_RST_SHIFT(ip) 0x0 +#define UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip) 0x1 +#define SET_UNIPERIF_SOFT_RST_SOFT_RST(ip) \ + SET_UNIPERIF_BIT_REG(ip, \ + UNIPERIF_SOFT_RST_OFFSET(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_SHIFT(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip), 1) +#define GET_UNIPERIF_SOFT_RST_SOFT_RST(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_SOFT_RST_OFFSET(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_SHIFT(ip), \ + UNIPERIF_SOFT_RST_SOFT_RST_MASK(ip)) + +/* + * UNIPERIF_FIFO_DATA reg + */ + +#define UNIPERIF_FIFO_DATA_OFFSET(ip) 0x0004 +#define SET_UNIPERIF_DATA(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_FIFO_DATA_OFFSET(ip)) + +/* + * UNIPERIF_CHANNEL_STA_REGN reg + */ + +#define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) +#define GET_UNIPERIF_CHANNEL_STA_REGN(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REGN(ip, n)) +#define SET_UNIPERIF_CHANNEL_STA_REGN(ip, n, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_CHANNEL_STA_REGN(ip, n)) + +#define UNIPERIF_CHANNEL_STA_REG0_OFFSET(ip) 0x0060 +#define GET_UNIPERIF_CHANNEL_STA_REG0(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG0_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG0(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG0_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG1_OFFSET(ip) 0x0064 +#define GET_UNIPERIF_CHANNEL_STA_REG1(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG1_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG1(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG1_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG2_OFFSET(ip) 0x0068 +#define GET_UNIPERIF_CHANNEL_STA_REG2(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG2_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG2(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG2_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG3_OFFSET(ip) 0x006C +#define GET_UNIPERIF_CHANNEL_STA_REG3(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG3_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG3(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG3_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG4_OFFSET(ip) 0x0070 +#define GET_UNIPERIF_CHANNEL_STA_REG4(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG4_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG4(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG4_OFFSET(ip)) + +#define UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip) 0x0074 +#define GET_UNIPERIF_CHANNEL_STA_REG5(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip)) +#define SET_UNIPERIF_CHANNEL_STA_REG5(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CHANNEL_STA_REG5_OFFSET(ip)) + +/* + * UNIPERIF_ITS reg + */ + +#define UNIPERIF_ITS_OFFSET(ip) 0x000C +#define GET_UNIPERIF_ITS(ip) \ + readl_relaxed(ip->base + UNIPERIF_ITS_OFFSET(ip)) + +/* MEM_BLK_READ */ +#define UNIPERIF_ITS_MEM_BLK_READ_SHIFT(ip) 5 +#define UNIPERIF_ITS_MEM_BLK_READ_MASK(ip) \ + (BIT(UNIPERIF_ITS_MEM_BLK_READ_SHIFT(ip))) + +/* FIFO_ERROR */ +#define UNIPERIF_ITS_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITS_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITS_FIFO_ERROR_SHIFT(ip))) + +/* DMA_ERROR */ +#define UNIPERIF_ITS_DMA_ERROR_SHIFT(ip) 9 +#define UNIPERIF_ITS_DMA_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITS_DMA_ERROR_SHIFT(ip))) + +/* UNDERFLOW_REC_DONE */ +#define UNIPERIF_ITS_UNDERFLOW_REC_DONE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 12) +#define UNIPERIF_ITS_UNDERFLOW_REC_DONE_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITS_UNDERFLOW_REC_DONE_SHIFT(ip)))) + +/* UNDERFLOW_REC_FAILED */ +#define UNIPERIF_ITS_UNDERFLOW_REC_FAILED_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 13) +#define UNIPERIF_ITS_UNDERFLOW_REC_FAILED_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITS_UNDERFLOW_REC_FAILED_SHIFT(ip)))) + +/* + * UNIPERIF_ITS_BCLR reg + */ + +/* FIFO_ERROR */ +#define UNIPERIF_ITS_BCLR_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITS_BCLR_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITS_BCLR_FIFO_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITS_BCLR_FIFO_ERROR(ip) \ + SET_UNIPERIF_ITS_BCLR(ip, \ + UNIPERIF_ITS_BCLR_FIFO_ERROR_MASK(ip)) + +#define UNIPERIF_ITS_BCLR_OFFSET(ip) 0x0010 +#define SET_UNIPERIF_ITS_BCLR(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_ITS_BCLR_OFFSET(ip)) + +/* + * UNIPERIF_ITM reg + */ + +#define UNIPERIF_ITM_OFFSET(ip) 0x0018 +#define GET_UNIPERIF_ITM(ip) \ + readl_relaxed(ip->base + UNIPERIF_ITM_OFFSET(ip)) + +/* FIFO_ERROR */ +#define UNIPERIF_ITM_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITM_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_FIFO_ERROR_SHIFT(ip))) + +/* UNDERFLOW_REC_DONE */ +#define UNIPERIF_ITM_UNDERFLOW_REC_DONE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 12) +#define UNIPERIF_ITM_UNDERFLOW_REC_DONE_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_UNDERFLOW_REC_DONE_SHIFT(ip)))) + +/* UNDERFLOW_REC_FAILED */ +#define UNIPERIF_ITM_UNDERFLOW_REC_FAILED_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 13) +#define UNIPERIF_ITM_UNDERFLOW_REC_FAILED_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_UNDERFLOW_REC_FAILED_SHIFT(ip)))) + +/* + * UNIPERIF_ITM_BCLR reg + */ + +#define UNIPERIF_ITM_BCLR_OFFSET(ip) 0x001c +#define SET_UNIPERIF_ITM_BCLR(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_ITM_BCLR_OFFSET(ip)) + +/* FIFO_ERROR */ +#define UNIPERIF_ITM_BCLR_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITM_BCLR_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BCLR_FIFO_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BCLR_FIFO_ERROR(ip) \ + SET_UNIPERIF_ITM_BCLR(ip, \ + UNIPERIF_ITM_BCLR_FIFO_ERROR_MASK(ip)) + +/* DMA_ERROR */ +#define UNIPERIF_ITM_BCLR_DMA_ERROR_SHIFT(ip) 9 +#define UNIPERIF_ITM_BCLR_DMA_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BCLR_DMA_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BCLR_DMA_ERROR(ip) \ + SET_UNIPERIF_ITM_BCLR(ip, \ + UNIPERIF_ITM_BCLR_DMA_ERROR_MASK(ip)) + +/* + * UNIPERIF_ITM_BSET reg + */ + +#define UNIPERIF_ITM_BSET_OFFSET(ip) 0x0020 +#define SET_UNIPERIF_ITM_BSET(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_ITM_BSET_OFFSET(ip)) + +/* FIFO_ERROR */ +#define UNIPERIF_ITM_BSET_FIFO_ERROR_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 8) +#define UNIPERIF_ITM_BSET_FIFO_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BSET_FIFO_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BSET_FIFO_ERROR(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_FIFO_ERROR_MASK(ip)) + +/* MEM_BLK_READ */ +#define UNIPERIF_ITM_BSET_MEM_BLK_READ_SHIFT(ip) 5 +#define UNIPERIF_ITM_BSET_MEM_BLK_READ_MASK(ip) \ + (BIT(UNIPERIF_ITM_BSET_MEM_BLK_READ_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BSET_MEM_BLK_READ(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_MEM_BLK_READ_MASK(ip)) + +/* DMA_ERROR */ +#define UNIPERIF_ITM_BSET_DMA_ERROR_SHIFT(ip) 9 +#define UNIPERIF_ITM_BSET_DMA_ERROR_MASK(ip) \ + (BIT(UNIPERIF_ITM_BSET_DMA_ERROR_SHIFT(ip))) +#define SET_UNIPERIF_ITM_BSET_DMA_ERROR(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_DMA_ERROR_MASK(ip)) + +/* UNDERFLOW_REC_DONE */ +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 12) +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_SHIFT(ip)))) +#define SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE_MASK(ip)) + +/* UNDERFLOW_REC_FAILED */ +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 13) +#define UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? \ + 0 : (BIT(UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_SHIFT(ip)))) +#define SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED(ip) \ + SET_UNIPERIF_ITM_BSET(ip, \ + UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED_MASK(ip)) + +/* + * UNIPERIF_CONFIG reg + */ + +#define UNIPERIF_CONFIG_OFFSET(ip) 0x0040 +#define GET_UNIPERIF_CONFIG(ip) \ + readl_relaxed(ip->base + UNIPERIF_CONFIG_OFFSET(ip)) +#define SET_UNIPERIF_CONFIG(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CONFIG_OFFSET(ip)) + +/* PARITY_CNTR */ +#define UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip) 0 +#define UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_PARITY_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_PARITY_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_PARITY_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_PARITY_CNTR_MASK(ip), 1) + +/* CHANNEL_STA_CNTR */ +#define UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip) 1 +#define UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_CHANNEL_STA_CNTR_MASK(ip), 1) + +/* USER_DAT_CNTR */ +#define UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip) 2 +#define UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_USER_DAT_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_USER_DAT_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_USER_DAT_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_USER_DAT_CNTR_MASK(ip), 0) + +/* VALIDITY_DAT_CNTR */ +#define UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip) 3 +#define UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip)) +#define SET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_BY_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_BY_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_SHIFT(ip), \ + UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_MASK(ip), 1) + +/* ONE_BIT_AUD_SUPPORT */ +#define UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip) 4 +#define UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_ONE_BIT_AUD(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip)) +#define SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_ONE_BIT_AUD_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_SHIFT(ip), \ + UNIPERIF_CONFIG_ONE_BIT_AUD_MASK(ip), 1) + +/* MEMORY_FMT */ +#define UNIPERIF_CONFIG_MEM_FMT_SHIFT(ip) 5 +#define UNIPERIF_CONFIG_MEM_FMT_MASK(ip) 0x1 +#define VALUE_UNIPERIF_CONFIG_MEM_FMT_16_0(ip) 0 +#define VALUE_UNIPERIF_CONFIG_MEM_FMT_16_16(ip) 1 +#define GET_UNIPERIF_CONFIG_MEM_FMT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MEM_FMT_SHIFT(ip), \ + UNIPERIF_CONFIG_MEM_FMT_MASK(ip)) +#define SET_UNIPERIF_CONFIG_MEM_FMT(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MEM_FMT_SHIFT(ip), \ + UNIPERIF_CONFIG_MEM_FMT_MASK(ip), value) +#define SET_UNIPERIF_CONFIG_MEM_FMT_16_0(ip) \ + SET_UNIPERIF_CONFIG_MEM_FMT(ip, \ + VALUE_UNIPERIF_CONFIG_MEM_FMT_16_0(ip)) +#define SET_UNIPERIF_CONFIG_MEM_FMT_16_16(ip) \ + SET_UNIPERIF_CONFIG_MEM_FMT(ip, \ + VALUE_UNIPERIF_CONFIG_MEM_FMT_16_16(ip)) + +/* REPEAT_CHL_STS */ +#define UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip) 6 +#define UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_REPEAT_CHL_STS(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip)) +#define SET_UNIPERIF_CONFIG_REPEAT_CHL_STS_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_REPEAT_CHL_STS_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_SHIFT(ip), \ + UNIPERIF_CONFIG_REPEAT_CHL_STS_MASK(ip), 1) + +/* BACK_STALL_REQ */ +#define UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 7 : -1) +#define UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_BACK_STALL_REQ(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip)) +#define SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_BACK_STALL_REQ_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_SHIFT(ip), \ + UNIPERIF_CONFIG_BACK_STALL_REQ_MASK(ip), 1) + +/* FDMA_TRIGGER_LIMIT */ +#define UNIPERIF_CONFIG_DMA_TRIG_LIMIT_SHIFT(ip) 8 +#define UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(ip) 0x7F +#define GET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_SHIFT(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(ip)) +#define SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_SHIFT(ip), \ + UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(ip), value) + +/* CHL_STS_UPDATE */ +#define UNIPERIF_CONFIG_CHL_STS_UPDATE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 16 : -1) +#define UNIPERIF_CONFIG_CHL_STS_UPDATE_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_CHL_STS_UPDATE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_MASK(ip)) +#define SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_CONFIG_CHL_STS_UPDATE_MASK(ip), 1) + +/* IDLE_MOD */ +#define UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip) 18 +#define UNIPERIF_CONFIG_IDLE_MOD_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_IDLE_MOD(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_MASK(ip)) +#define SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_MASK(ip), 0) +#define SET_UNIPERIF_CONFIG_IDLE_MOD_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_SHIFT(ip), \ + UNIPERIF_CONFIG_IDLE_MOD_MASK(ip), 1) + +/* SUBFRAME_SELECTION */ +#define UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip) 19 +#define UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_SUBFRAME_SEL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip)) +#define SET_UNIPERIF_CONFIG_SUBFRAME_SEL_SUBF1_SUBF0(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_SUBFRAME_SEL_SUBF0_SUBF1(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_SHIFT(ip), \ + UNIPERIF_CONFIG_SUBFRAME_SEL_MASK(ip), 0) + +/* FULL_SW_CONTROL */ +#define UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip) 20 +#define UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_SPDIF_SW_CTRL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip)) +#define SET_UNIPERIF_CONFIG_SPDIF_SW_CTRL_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_SPDIF_SW_CTRL_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_SHIFT(ip), \ + UNIPERIF_CONFIG_SPDIF_SW_CTRL_MASK(ip), 0) + +/* MASTER_CLKEDGE */ +#define UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 24 : -1) +#define UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip) 0x1 +#define GET_UNIPERIF_CONFIG_MSTR_CLKEDGE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip)) +#define SET_UNIPERIF_CONFIG_MSTR_CLKEDGE_FALLING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip), 1) +#define SET_UNIPERIF_CONFIG_MSTR_CLKEDGE_RISING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CONFIG_OFFSET(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_SHIFT(ip), \ + UNIPERIF_CONFIG_MSTR_CLKEDGE_MASK(ip), 0) + +/* + * UNIPERIF_CTRL reg + */ + +#define UNIPERIF_CTRL_OFFSET(ip) 0x0044 +#define GET_UNIPERIF_CTRL(ip) \ + readl_relaxed(ip->base + UNIPERIF_CTRL_OFFSET(ip)) +#define SET_UNIPERIF_CTRL(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_CTRL_OFFSET(ip)) + +/* OPERATION */ +#define UNIPERIF_CTRL_OPERATION_SHIFT(ip) 0 +#define UNIPERIF_CTRL_OPERATION_MASK(ip) 0x7 +#define GET_UNIPERIF_CTRL_OPERATION(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_OFF(ip) 0 +#define SET_UNIPERIF_CTRL_OPERATION_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_OFF(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PCM_NULL(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 1 : -1) +#define SET_UNIPERIF_CTRL_OPERATION_MUTE_PCM_NULL(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PCM_NULL(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PAUSE_BURST(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 2 : -1) +#define SET_UNIPERIF_CTRL_OPERATION_MUTE_PAUSE_BURST(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_MUTE_PAUSE_BURST(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_PCM_DATA(ip) 3 +#define SET_UNIPERIF_CTRL_OPERATION_PCM_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_PCM_DATA(ip)) +/* This is the same as above! */ +#define VALUE_UNIPERIF_CTRL_OPERATION_AUDIO_DATA(ip) 3 +#define SET_UNIPERIF_CTRL_OPERATION_AUDIO_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_AUDIO_DATA(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_ENC_DATA(ip) 4 +#define SET_UNIPERIF_CTRL_OPERATION_ENC_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_ENC_DATA(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_CD_DATA(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 5 : -1) +#define SET_UNIPERIF_CTRL_OPERATION_CD_DATA(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_CD_DATA(ip)) +#define VALUE_UNIPERIF_CTRL_OPERATION_STANDBY(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 7) +#define SET_UNIPERIF_CTRL_OPERATION_STANDBY(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_OPERATION_SHIFT(ip), \ + UNIPERIF_CTRL_OPERATION_MASK(ip), \ + VALUE_UNIPERIF_CTRL_OPERATION_STANDBY(ip)) + +/* EXIT_STBY_ON_EOBLOCK */ +#define UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 3) +#define UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip)) +#define SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_SHIFT(ip), \ + UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_MASK(ip), 1) + +/* ROUNDING */ +#define UNIPERIF_CTRL_ROUNDING_SHIFT(ip) 4 +#define UNIPERIF_CTRL_ROUNDING_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_ROUNDING(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ROUNDING_SHIFT(ip), \ + UNIPERIF_CTRL_ROUNDING_MASK(ip)) +#define SET_UNIPERIF_CTRL_ROUNDING_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ROUNDING_SHIFT(ip), \ + UNIPERIF_CTRL_ROUNDING_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_ROUNDING_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ROUNDING_SHIFT(ip), \ + UNIPERIF_CTRL_ROUNDING_MASK(ip), 1) + +/* DIVIDER */ +#define UNIPERIF_CTRL_DIVIDER_SHIFT(ip) 5 +#define UNIPERIF_CTRL_DIVIDER_MASK(ip) 0xff +#define GET_UNIPERIF_CTRL_DIVIDER(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_DIVIDER_SHIFT(ip), \ + UNIPERIF_CTRL_DIVIDER_MASK(ip)) +#define SET_UNIPERIF_CTRL_DIVIDER(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_DIVIDER_SHIFT(ip), \ + UNIPERIF_CTRL_DIVIDER_MASK(ip), value) + +/* BYTE_SWAP */ +#define UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 13 : -1) +#define UNIPERIF_CTRL_BYTE_SWP_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_BYTE_SWP(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip), \ + UNIPERIF_CTRL_BYTE_SWP_MASK(ip)) +#define SET_UNIPERIF_CTRL_BYTE_SWP_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip), \ + UNIPERIF_CTRL_BYTE_SWP_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_BYTE_SWP_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_BYTE_SWP_SHIFT(ip), \ + UNIPERIF_CTRL_BYTE_SWP_MASK(ip), 1) + +/* ZERO_STUFFING_HW_SW */ +#define UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 14 : -1) +#define UNIPERIF_CTRL_ZERO_STUFF_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_ZERO_STUFF(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_MASK(ip)) +#define SET_UNIPERIF_CTRL_ZERO_STUFF_HW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_MASK(ip), 1) +#define SET_UNIPERIF_CTRL_ZERO_STUFF_SW(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_SHIFT(ip), \ + UNIPERIF_CTRL_ZERO_STUFF_MASK(ip), 0) + +/* SPDIF_LAT */ +#define UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 16 : -1) +#define UNIPERIF_CTRL_SPDIF_LAT_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_SPDIF_LAT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_MASK(ip)) +#define SET_UNIPERIF_CTRL_SPDIF_LAT_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_MASK(ip), 1) +#define SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_LAT_MASK(ip), 0) + +/* EN_SPDIF_FORMATTING */ +#define UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip) 17 +#define UNIPERIF_CTRL_SPDIF_FMT_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_SPDIF_FMT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_MASK(ip)) +#define SET_UNIPERIF_CTRL_SPDIF_FMT_ON(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_MASK(ip), 1) +#define SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_SHIFT(ip), \ + UNIPERIF_CTRL_SPDIF_FMT_MASK(ip), 0) + +/* READER_OUT_SELECT */ +#define UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 18 : -1) +#define UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip) 0x1 +#define GET_UNIPERIF_CTRL_READER_OUT_SEL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip)) +#define SET_UNIPERIF_CTRL_READER_OUT_SEL_IN_MEM(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 0) +#define SET_UNIPERIF_CTRL_READER_OUT_SEL_ON_I2S_LINE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_SHIFT(ip), \ + UNIPERIF_CTRL_READER_OUT_SEL_MASK(ip), 1) + +/* UNDERFLOW_REC_WINDOW */ +#define UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip) 20 +#define UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_MASK(ip) 0xff +#define GET_UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_MASK(ip)) +#define SET_UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_CTRL_OFFSET(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_SHIFT(ip), \ + UNIPERIF_CTRL_UNDERFLOW_REC_WINDOW_MASK(ip), value) + +/* + * UNIPERIF_I2S_FMT a.k.a UNIPERIF_FORMAT reg + */ + +#define UNIPERIF_I2S_FMT_OFFSET(ip) 0x0048 +#define GET_UNIPERIF_I2S_FMT(ip) \ + readl_relaxed(ip->base + UNIPERIF_I2S_FMT_OFFSET(ip)) +#define SET_UNIPERIF_I2S_FMT(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_I2S_FMT_OFFSET(ip)) + +/* NBIT */ +#define UNIPERIF_I2S_FMT_NBIT_SHIFT(ip) 0 +#define UNIPERIF_I2S_FMT_NBIT_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_NBIT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NBIT_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NBIT_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_NBIT_32(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NBIT_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NBIT_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_NBIT_16(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NBIT_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NBIT_MASK(ip), 1) + +/* DATA_SIZE */ +#define UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip) 1 +#define UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip) 0x7 +#define GET_UNIPERIF_I2S_FMT_DATA_SIZE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_18(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 1) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_20(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 2) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_24(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 3) +#define SET_UNIPERIF_I2S_FMTL_DATA_SIZE_28(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 4) +#define SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_DATA_SIZE_MASK(ip), 5) + +/* LR_POL */ +#define UNIPERIF_I2S_FMT_LR_POL_SHIFT(ip) 4 +#define UNIPERIF_I2S_FMT_LR_POL_MASK(ip) 0x1 +#define VALUE_UNIPERIF_I2S_FMT_LR_POL_LOW(ip) 0x0 +#define VALUE_UNIPERIF_I2S_FMT_LR_POL_HIG(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_LR_POL(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_LR_POL_SHIFT(ip), \ + UNIPERIF_I2S_FMT_LR_POL_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_LR_POL(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_LR_POL_SHIFT(ip), \ + UNIPERIF_I2S_FMT_LR_POL_MASK(ip), value) +#define SET_UNIPERIF_I2S_FMT_LR_POL_LOW(ip) \ + SET_UNIPERIF_I2S_FMT_LR_POL(ip, \ + VALUE_UNIPERIF_I2S_FMT_LR_POL_LOW(ip)) +#define SET_UNIPERIF_I2S_FMT_LR_POL_HIG(ip) \ + SET_UNIPERIF_I2S_FMT_LR_POL(ip, \ + VALUE_UNIPERIF_I2S_FMT_LR_POL_HIG(ip)) + +/* SCLK_EDGE */ +#define UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip) 5 +#define UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_SCLK_EDGE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_SHIFT(ip), \ + UNIPERIF_I2S_FMT_SCLK_EDGE_MASK(ip), 1) + +/* PADDING */ +#define UNIPERIF_I2S_FMT_PADDING_SHIFT(ip) 6 +#define UNIPERIF_I2S_FMT_PADDING_MASK(ip) 0x1 +#define UNIPERIF_I2S_FMT_PADDING_MASK(ip) 0x1 +#define VALUE_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(ip) 0x0 +#define VALUE_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_PADDING(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_PADDING_SHIFT(ip), \ + UNIPERIF_I2S_FMT_PADDING_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_PADDING(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_PADDING_SHIFT(ip), \ + UNIPERIF_I2S_FMT_PADDING_MASK(ip), value) +#define SET_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(ip) \ + SET_UNIPERIF_I2S_FMT_PADDING(ip, \ + VALUE_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(ip)) +#define SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(ip) \ + SET_UNIPERIF_I2S_FMT_PADDING(ip, \ + VALUE_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(ip)) + +/* ALIGN */ +#define UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip) 7 +#define UNIPERIF_I2S_FMT_ALIGN_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_ALIGN(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ALIGN_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ALIGN_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_ALIGN_RIGHT(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ALIGN_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ALIGN_MASK(ip), 1) + +/* ORDER */ +#define UNIPERIF_I2S_FMT_ORDER_SHIFT(ip) 8 +#define UNIPERIF_I2S_FMT_ORDER_MASK(ip) 0x1 +#define GET_UNIPERIF_I2S_FMT_ORDER(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ORDER_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ORDER_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_ORDER_LSB(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ORDER_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ORDER_MASK(ip), 0) +#define SET_UNIPERIF_I2S_FMT_ORDER_MSB(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_ORDER_SHIFT(ip), \ + UNIPERIF_I2S_FMT_ORDER_MASK(ip), 1) + +/* NUM_CH */ +#define UNIPERIF_I2S_FMT_NUM_CH_SHIFT(ip) 9 +#define UNIPERIF_I2S_FMT_NUM_CH_MASK(ip) 0x7 +#define GET_UNIPERIF_I2S_FMT_NUM_CH(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_NUM_CH(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NUM_CH_MASK(ip), value) + +/* NO_OF_SAMPLES_TO_READ */ +#define UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_SHIFT(ip) 12 +#define UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_MASK(ip) 0xfffff +#define GET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_MASK(ip)) +#define SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_I2S_FMT_OFFSET(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_SHIFT(ip), \ + UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ_MASK(ip), value) + +/* + * UNIPERIF_BIT_CONTROL reg + */ + +#define UNIPERIF_BIT_CONTROL_OFFSET(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 0x004c) +#define GET_UNIPERIF_BIT_CONTROL(ip) \ + readl_relaxed(ip->base + UNIPERIF_BIT_CONTROL_OFFSET(ip)) +#define SET_UNIPERIF_BIT_CONTROL(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_BIT_CONTROL_OFFSET(ip)) + +/* CLR_UNDERFLOW_DURATION */ +#define UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_SHIFT(ip) 0 +#define UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_MASK(ip) 0x1 +#define GET_UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_MASK(ip)) +#define SET_UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION_MASK(ip), 1) + +/* CHL_STS_UPDATE */ +#define UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_SHIFT(ip) 1 +#define UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_MASK(ip) 0x1 +#define GET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_MASK(ip)) +#define SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(ip) \ + SET_UNIPERIF_BIT_REG(ip, \ + UNIPERIF_BIT_CONTROL_OFFSET(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_SHIFT(ip), \ + UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE_MASK(ip), 1) + +/* + * UNIPERIF_STATUS_1 reg + */ + +#define UNIPERIF_STATUS_1_OFFSET(ip) 0x0050 +#define GET_UNIPERIF_STATUS_1(ip) \ + readl_relaxed(ip->base + UNIPERIF_STATUS_1_OFFSET(ip)) +#define SET_UNIPERIF_STATUS_1(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_STATUS_1_OFFSET(ip)) + +/* UNDERFLOW_DURATION */ +#define UNIPERIF_STATUS_1_UNDERFLOW_DURATION_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 0) +#define UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip) 0xff +#define GET_UNIPERIF_STATUS_1_UNDERFLOW_DURATION(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_STATUS_1_OFFSET(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip)) +#define SET_UNIPERIF_STATUS_1_UNDERFLOW_DURATION(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_STATUS_1_OFFSET(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_SHIFT(ip), \ + UNIPERIF_STATUS_1_UNDERFLOW_DURATION_MASK(ip), value) + +/* + * UNIPERIF_CHANNEL_STA_REGN reg + */ + +#define UNIPERIF_CHANNEL_STA_REGN(ip, n) (0x0060 + (4 * n)) +#define GET_UNIPERIF_CHANNEL_STA_REGN(ip) \ + readl_relaxed(ip->base + UNIPERIF_CHANNEL_STA_REGN(ip, n)) +#define SET_UNIPERIF_CHANNEL_STA_REGN(ip, n, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_CHANNEL_STA_REGN(ip, n)) + +/* + * UNIPERIF_USER_VALIDITY reg + */ + +#define UNIPERIF_USER_VALIDITY_OFFSET(ip) 0x0090 +#define GET_UNIPERIF_USER_VALIDITY(ip) \ + readl_relaxed(ip->base + UNIPERIF_USER_VALIDITY_OFFSET(ip)) +#define SET_UNIPERIF_USER_VALIDITY(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_USER_VALIDITY_OFFSET(ip)) + +/* VALIDITY_LEFT_AND_RIGHT */ +#define UNIPERIF_USER_VALIDITY_VALIDITY_LR_SHIFT(ip) 0 +#define UNIPERIF_USER_VALIDITY_VALIDITY_LR_MASK(ip) 0x3 +#define GET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_USER_VALIDITY_OFFSET(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_SHIFT(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_MASK(ip)) +#define SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_USER_VALIDITY_OFFSET(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_SHIFT(ip), \ + UNIPERIF_USER_VALIDITY_VALIDITY_LR_MASK(ip), \ + value ? 0x3 : 0) + +/* + * UNIPERIF_DBG_STANDBY_LEFT_SP reg + */ +#define UNIPERIF_DBG_STANDBY_LEFT_SP_OFFSET(ip) 0x0150 +#define UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? -1 : 0) +#define UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip) \ + ((ip)->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 ? 0 : 0xFFFFFF) +#define GET_UNIPERIF_DBG_STANDBY_LEFT_SP(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_DBG_STANDBY_LEFT_SP_OFFSET(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip)) +#define SET_UNIPERIF_DBG_STANDBY_LEFT_SP(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_DBG_STANDBY_LEFT_SP_OFFSET(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_SHIFT(ip), \ + UNIPERIF_DBG_STANDBY_LEFT_SP_MASK(ip), value) + +/* + * UNIPERIF_TDM_ENABLE + */ +#define UNIPERIF_TDM_ENABLE_OFFSET(ip) 0x0118 +#define GET_UNIPERIF_TDM_ENABLE(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip)) +#define SET_UNIPERIF_TDM_ENABLE(ip, value) \ + writel_relaxed(value, ip->base + UNIPERIF_TDM_ENABLE_OFFSET(ip)) + +/* TDM_ENABLE */ +#define UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip) 0x0 +#define UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip) 0x1 +#define GET_UNIPERIF_TDM_ENABLE_EN_TDM(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip)) +#define SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 1) +#define SET_UNIPERIF_TDM_ENABLE_TDM_DISABLE(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_ENABLE_OFFSET(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_SHIFT(ip), \ + UNIPERIF_TDM_ENABLE_EN_TDM_MASK(ip), 0) + +/* + * UNIPERIF_TDM_FS_REF_FREQ + */ +#define UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip) 0x011c +#define GET_UNIPERIF_TDM_FS_REF_FREQ(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ(ip, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip)) + +/* REF_FREQ */ +#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip) 0x0 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) 0 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) 1 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) 2 +#define VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) 3 +#define UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip) 0x3 +#define GET_UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_8KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_16KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_32KHZ(ip)) +#define SET_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_FREQ_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_FREQ_REF_FREQ_MASK(ip), \ + VALUE_UNIPERIF_TDM_FS_REF_FREQ_48KHZ(ip)) + +/* + * UNIPERIF_TDM_FS_REF_DIV + */ +#define UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip) 0x0120 +#define GET_UNIPERIF_TDM_FS_REF_DIV(ip) \ + readl_relaxed(ip->base + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip)) +#define SET_UNIPERIF_TDM_FS_REF_DIV(ip, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip)) + +/* NUM_TIMESLOT */ +#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip) 0x0 +#define UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip) 0xff +#define GET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip) \ + GET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip)) +#define SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(ip, value) \ + SET_UNIPERIF_REG(ip, \ + UNIPERIF_TDM_FS_REF_DIV_OFFSET(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_SHIFT(ip), \ + UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT_MASK(ip), value) + +/* + * UNIPERIF_TDM_WORD_POS_X_Y + * 32 bits of UNIPERIF_TDM_WORD_POS_X_Y register shall be set in 1 shot + */ +#define UNIPERIF_TDM_WORD_POS_1_2_OFFSET(ip) 0x013c +#define UNIPERIF_TDM_WORD_POS_3_4_OFFSET(ip) 0x0140 +#define UNIPERIF_TDM_WORD_POS_5_6_OFFSET(ip) 0x0144 +#define UNIPERIF_TDM_WORD_POS_7_8_OFFSET(ip) 0x0148 +#define GET_UNIPERIF_TDM_WORD_POS(ip, words) \ + readl_relaxed(ip->base + UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip)) +#define SET_UNIPERIF_TDM_WORD_POS(ip, words, value) \ + writel_relaxed(value, ip->base + \ + UNIPERIF_TDM_WORD_POS_##words##_OFFSET(ip)) +/* + * uniperipheral IP capabilities + */ + +#define UNIPERIF_FIFO_SIZE 70 /* FIFO is 70 cells deep */ +#define UNIPERIF_FIFO_FRAMES 4 /* FDMA trigger limit in frames */ + +#define UNIPERIF_TYPE_IS_HDMI(p) \ + ((p)->type == SND_ST_UNIPERIF_TYPE_HDMI) +#define UNIPERIF_TYPE_IS_PCM(p) \ + ((p)->type == SND_ST_UNIPERIF_TYPE_PCM) +#define UNIPERIF_TYPE_IS_SPDIF(p) \ + ((p)->type == SND_ST_UNIPERIF_TYPE_SPDIF) +#define UNIPERIF_TYPE_IS_IEC958(p) \ + (UNIPERIF_TYPE_IS_HDMI(p) || \ + UNIPERIF_TYPE_IS_SPDIF(p)) +#define UNIPERIF_TYPE_IS_TDM(p) \ + ((p)->type == SND_ST_UNIPERIF_TYPE_TDM) + +/* + * Uniperipheral IP revisions + */ +enum uniperif_version { + SND_ST_UNIPERIF_VERSION_UNKNOWN, + /* SASG1 (Orly), Newman */ + SND_ST_UNIPERIF_VERSION_C6AUD0_UNI_1_0, + /* SASC1, SASG2 (Orly2) */ + SND_ST_UNIPERIF_VERSION_UNI_PLR_1_0, + /* SASC1, SASG2 (Orly2), TELSS, Cannes */ + SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0, + /* TELSS (SASC1) */ + SND_ST_UNIPERIF_VERSION_TDM_PLR_1_0, + /* Cannes/Monaco */ + SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 +}; + +enum uniperif_type { + SND_ST_UNIPERIF_TYPE_NONE = 0x00, + SND_ST_UNIPERIF_TYPE_HDMI = 0x01, + SND_ST_UNIPERIF_TYPE_PCM = 0x02, + SND_ST_UNIPERIF_TYPE_SPDIF = 0x04, + SND_ST_UNIPERIF_TYPE_TDM = 0x08 +}; + +enum uniperif_state { + UNIPERIF_STATE_STOPPED, + UNIPERIF_STATE_STARTED, + UNIPERIF_STATE_STANDBY, + UNIPERIF_STATE_UNDERFLOW, + UNIPERIF_STATE_OVERFLOW = UNIPERIF_STATE_UNDERFLOW, + UNIPERIF_STATE_XRUN +}; + +enum uniperif_iec958_encoding_mode { + UNIPERIF_IEC958_ENCODING_MODE_PCM, + UNIPERIF_IEC958_ENCODING_MODE_ENCODED +}; + +enum uniperif_word_pos { + WORD_1_2, + WORD_3_4, + WORD_5_6, + WORD_7_8, + WORD_MAX +}; + +struct uniperif_iec958_settings { + enum uniperif_iec958_encoding_mode encoding_mode; + struct snd_aes_iec958 iec958; +}; + +struct dai_tdm_slot { + unsigned int mask; + int slots; + int slot_width; + unsigned int avail_slots; +}; + +struct uniperif { + /* System information */ + enum uniperif_type type; + int underflow_enabled; /* Underflow recovery mode */ + struct device *dev; + int id; /* instance value of the uniperipheral IP */ + int ver; /* IP version, used by register access macros */ + struct regmap_field *clk_sel; + struct regmap_field *valid_sel; + spinlock_t irq_lock; /* use to prevent race condition with IRQ */ + + /* capabilities */ + const struct snd_pcm_hardware *hw; + + /* Resources */ + struct resource *mem_region; + void __iomem *base; + unsigned long fifo_phys_address; + int irq; + + /* Clocks */ + struct clk *clk; + int mclk; + int clk_adj; + + /* Runtime data */ + enum uniperif_state state; + + struct snd_pcm_substream *substream; + + /* Specific to IEC958 player */ + struct uniperif_iec958_settings stream_settings; + struct mutex ctrl_lock; /* For resource updated by stream and controls*/ + + /*alsa ctrl*/ + struct snd_kcontrol_new *snd_ctrls; + int num_ctrls; + + /* dai properties */ + unsigned int daifmt; + struct dai_tdm_slot tdm_slot; + + /* DAI callbacks */ + const struct snd_soc_dai_ops *dai_ops; +}; + +struct sti_uniperiph_dai { + int stream; + struct uniperif *uni; + struct snd_dmaengine_dai_dma_data dma_data; +}; + +struct sti_uniperiph_data { + struct platform_device *pdev; + struct snd_soc_dai_driver *dai; + struct sti_uniperiph_dai dai_data; +}; + +static const struct snd_pcm_hardware uni_tdm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + + .channels_min = 1, + .channels_max = 32, + + .periods_min = 2, + .periods_max = 10, + + .period_bytes_min = 128, + .period_bytes_max = 64 * PAGE_SIZE, + .buffer_bytes_max = 256 * PAGE_SIZE +}; + +/* uniperiph player*/ +int uni_player_init(struct platform_device *pdev, + struct uniperif *uni_player); +int uni_player_resume(struct uniperif *player); + +/* uniperiph reader */ +int uni_reader_init(struct platform_device *pdev, + struct uniperif *uni_reader); + +/* common */ +int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt); + +int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); + +static inline int sti_uniperiph_get_user_frame_size( + struct snd_pcm_runtime *runtime) +{ + return (runtime->channels * snd_pcm_format_width(runtime->format) / 8); +} + +static inline int sti_uniperiph_get_unip_tdm_frame_size(struct uniperif *uni) +{ + return (uni->tdm_slot.slots * uni->tdm_slot.slot_width / 8); +} + +int sti_uniperiph_reset(struct uniperif *uni); + +int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width); + +int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni, + unsigned int *word_pos); + +int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule); + +int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule); + +#endif diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c new file mode 100644 index 000000000..908f13623 --- /dev/null +++ b/sound/soc/sti/uniperif_player.c @@ -0,0 +1,1148 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include + +#include +#include + +#include "uniperif.h" + +/* + * Some hardware-related definitions + */ + +/* sys config registers definitions */ +#define SYS_CFG_AUDIO_GLUE 0xA4 + +/* + * Driver specific types. + */ + +#define UNIPERIF_PLAYER_CLK_ADJ_MIN -999999 +#define UNIPERIF_PLAYER_CLK_ADJ_MAX 1000000 +#define UNIPERIF_PLAYER_I2S_OUT 1 /* player id connected to I2S/TDM TX bus */ + +/* + * Note: snd_pcm_hardware is linked to DMA controller but is declared here to + * integrate DAI_CPU capability in term of rate and supported channels + */ +static const struct snd_pcm_hardware uni_player_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 192000, + + .channels_min = 2, + .channels_max = 8, + + .periods_min = 2, + .periods_max = 48, + + .period_bytes_min = 128, + .period_bytes_max = 64 * PAGE_SIZE, + .buffer_bytes_max = 256 * PAGE_SIZE +}; + +/* + * uni_player_irq_handler + * In case of error audio stream is stopped; stop action is protected via PCM + * stream lock to avoid race condition with trigger callback. + */ +static irqreturn_t uni_player_irq_handler(int irq, void *dev_id) +{ + irqreturn_t ret = IRQ_NONE; + struct uniperif *player = dev_id; + unsigned int status; + unsigned int tmp; + + spin_lock(&player->irq_lock); + if (!player->substream) + goto irq_spin_unlock; + + snd_pcm_stream_lock(player->substream); + if (player->state == UNIPERIF_STATE_STOPPED) + goto stream_unlock; + + /* Get interrupt status & clear them immediately */ + status = GET_UNIPERIF_ITS(player); + SET_UNIPERIF_ITS_BCLR(player, status); + + /* Check for fifo error (underrun) */ + if (unlikely(status & UNIPERIF_ITS_FIFO_ERROR_MASK(player))) { + dev_err(player->dev, "FIFO underflow error detected\n"); + + /* Interrupt is just for information when underflow recovery */ + if (player->underflow_enabled) { + /* Update state to underflow */ + player->state = UNIPERIF_STATE_UNDERFLOW; + + } else { + /* Disable interrupt so doesn't continually fire */ + SET_UNIPERIF_ITM_BCLR_FIFO_ERROR(player); + + /* Stop the player */ + snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN); + } + + ret = IRQ_HANDLED; + } + + /* Check for dma error (overrun) */ + if (unlikely(status & UNIPERIF_ITS_DMA_ERROR_MASK(player))) { + dev_err(player->dev, "DMA error detected\n"); + + /* Disable interrupt so doesn't continually fire */ + SET_UNIPERIF_ITM_BCLR_DMA_ERROR(player); + + /* Stop the player */ + snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN); + + ret = IRQ_HANDLED; + } + + /* Check for underflow recovery done */ + if (unlikely(status & UNIPERIF_ITM_UNDERFLOW_REC_DONE_MASK(player))) { + if (!player->underflow_enabled) { + dev_err(player->dev, + "unexpected Underflow recovering\n"); + ret = -EPERM; + goto stream_unlock; + } + /* Read the underflow recovery duration */ + tmp = GET_UNIPERIF_STATUS_1_UNDERFLOW_DURATION(player); + dev_dbg(player->dev, "Underflow recovered (%d LR clocks max)\n", + tmp); + + /* Clear the underflow recovery duration */ + SET_UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION(player); + + /* Update state to started */ + player->state = UNIPERIF_STATE_STARTED; + + ret = IRQ_HANDLED; + } + + /* Check if underflow recovery failed */ + if (unlikely(status & + UNIPERIF_ITM_UNDERFLOW_REC_FAILED_MASK(player))) { + dev_err(player->dev, "Underflow recovery failed\n"); + + /* Stop the player */ + snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN); + + ret = IRQ_HANDLED; + } + +stream_unlock: + snd_pcm_stream_unlock(player->substream); +irq_spin_unlock: + spin_unlock(&player->irq_lock); + + return ret; +} + +static int uni_player_clk_set_rate(struct uniperif *player, unsigned long rate) +{ + int rate_adjusted, rate_achieved, delta, ret; + int adjustment = player->clk_adj; + + /* + * a + * F = f + --------- * f = f + d + * 1000000 + * + * a + * d = --------- * f + * 1000000 + * + * where: + * f - nominal rate + * a - adjustment in ppm (parts per milion) + * F - rate to be set in synthesizer + * d - delta (difference) between f and F + */ + if (adjustment < 0) { + /* div64_64 operates on unsigned values... */ + delta = -1; + adjustment = -adjustment; + } else { + delta = 1; + } + /* 500000 ppm is 0.5, which is used to round up values */ + delta *= (int)div64_u64((uint64_t)rate * + (uint64_t)adjustment + 500000, 1000000); + rate_adjusted = rate + delta; + + /* Adjusted rate should never be == 0 */ + if (!rate_adjusted) + return -EINVAL; + + ret = clk_set_rate(player->clk, rate_adjusted); + if (ret < 0) + return ret; + + rate_achieved = clk_get_rate(player->clk); + if (!rate_achieved) + /* If value is 0 means that clock or parent not valid */ + return -EINVAL; + + /* + * Using ALSA's adjustment control, we can modify the rate to be up + * to twice as much as requested, but no more + */ + delta = rate_achieved - rate; + if (delta < 0) { + /* div64_64 operates on unsigned values... */ + delta = -delta; + adjustment = -1; + } else { + adjustment = 1; + } + /* Frequency/2 is added to round up result */ + adjustment *= (int)div64_u64((uint64_t)delta * 1000000 + rate / 2, + rate); + player->clk_adj = adjustment; + return 0; +} + +static void uni_player_set_channel_status(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int n; + unsigned int status; + + /* + * Some AVRs and TVs require the channel status to contain a correct + * sampling frequency. If no sample rate is already specified, then + * set one. + */ + if (runtime) { + switch (runtime->rate) { + case 22050: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_22050; + break; + case 44100: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_44100; + break; + case 88200: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_88200; + break; + case 176400: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_176400; + break; + case 24000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_24000; + break; + case 48000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_48000; + break; + case 96000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_96000; + break; + case 192000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_192000; + break; + case 32000: + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_32000; + break; + default: + /* Mark as sampling frequency not indicated */ + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_NOTID; + break; + } + } + + /* Audio mode: + * Use audio mode status to select PCM or encoded mode + */ + if (player->stream_settings.iec958.status[0] & IEC958_AES0_NONAUDIO) + player->stream_settings.encoding_mode = + UNIPERIF_IEC958_ENCODING_MODE_ENCODED; + else + player->stream_settings.encoding_mode = + UNIPERIF_IEC958_ENCODING_MODE_PCM; + + if (player->stream_settings.encoding_mode == + UNIPERIF_IEC958_ENCODING_MODE_PCM) + /* Clear user validity bits */ + SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 0); + else + /* Set user validity bits */ + SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 1); + + /* Program the new channel status */ + for (n = 0; n < 6; ++n) { + status = + player->stream_settings.iec958.status[0 + (n * 4)] & 0xf; + status |= + player->stream_settings.iec958.status[1 + (n * 4)] << 8; + status |= + player->stream_settings.iec958.status[2 + (n * 4)] << 16; + status |= + player->stream_settings.iec958.status[3 + (n * 4)] << 24; + SET_UNIPERIF_CHANNEL_STA_REGN(player, n, status); + } + + /* Update the channel status */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(player); + else + SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(player); +} + +static int uni_player_prepare_iec958(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int clk_div; + + clk_div = player->mclk / runtime->rate; + + /* Oversampling must be multiple of 128 as iec958 frame is 32-bits */ + if ((clk_div % 128) || (clk_div <= 0)) { + dev_err(player->dev, "%s: invalid clk_div %d\n", + __func__, clk_div); + return -EINVAL; + } + + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* 16/16 memory format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_16(player); + /* 16-bits per sub-frame */ + SET_UNIPERIF_I2S_FMT_NBIT_32(player); + /* Set 16-bit sample precision */ + SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(player); + break; + case SNDRV_PCM_FORMAT_S32_LE: + /* 16/0 memory format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player); + /* 32-bits per sub-frame */ + SET_UNIPERIF_I2S_FMT_NBIT_32(player); + /* Set 24-bit sample precision */ + SET_UNIPERIF_I2S_FMT_DATA_SIZE_24(player); + break; + default: + dev_err(player->dev, "format not supported\n"); + return -EINVAL; + } + + /* Set parity to be calculated by the hardware */ + SET_UNIPERIF_CONFIG_PARITY_CNTR_BY_HW(player); + + /* Set channel status bits to be inserted by the hardware */ + SET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR_BY_HW(player); + + /* Set user data bits to be inserted by the hardware */ + SET_UNIPERIF_CONFIG_USER_DAT_CNTR_BY_HW(player); + + /* Set validity bits to be inserted by the hardware */ + SET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_BY_HW(player); + + /* Set full software control to disabled */ + SET_UNIPERIF_CONFIG_SPDIF_SW_CTRL_DISABLE(player); + + SET_UNIPERIF_CTRL_ZERO_STUFF_HW(player); + + mutex_lock(&player->ctrl_lock); + /* Update the channel status */ + uni_player_set_channel_status(player, runtime); + mutex_unlock(&player->ctrl_lock); + + /* Clear the user validity user bits */ + SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 0); + + /* Disable one-bit audio mode */ + SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player); + + /* Enable consecutive frames repetition of Z preamble (not for HBRA) */ + SET_UNIPERIF_CONFIG_REPEAT_CHL_STS_ENABLE(player); + + /* Change to SUF0_SUBF1 and left/right channels swap! */ + SET_UNIPERIF_CONFIG_SUBFRAME_SEL_SUBF1_SUBF0(player); + + /* Set data output as MSB first */ + SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); + + if (player->stream_settings.encoding_mode == + UNIPERIF_IEC958_ENCODING_MODE_ENCODED) + SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_ON(player); + else + SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_OFF(player); + + SET_UNIPERIF_I2S_FMT_NUM_CH(player, runtime->channels / 2); + + /* Set rounding to off */ + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + + /* Set clock divisor */ + SET_UNIPERIF_CTRL_DIVIDER(player, clk_div / 128); + + /* Set the spdif latency to not wait before starting player */ + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); + + /* + * Ensure iec958 formatting is off. It will be enabled in function + * uni_player_start() at the same time as the operation + * mode is set to work around a silicon issue. + */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player); + else + SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); + + return 0; +} + +static int uni_player_prepare_pcm(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int output_frame_size, slot_width, clk_div; + + /* Force slot width to 32 in I2S mode (HW constraint) */ + if ((player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_I2S) + slot_width = 32; + else + slot_width = snd_pcm_format_width(runtime->format); + + output_frame_size = slot_width * runtime->channels; + + clk_div = player->mclk / runtime->rate; + /* + * For 32 bits subframe clk_div must be a multiple of 128, + * for 16 bits must be a multiple of 64 + */ + if ((slot_width == 32) && (clk_div % 128)) { + dev_err(player->dev, "%s: invalid clk_div\n", __func__); + return -EINVAL; + } + + if ((slot_width == 16) && (clk_div % 64)) { + dev_err(player->dev, "%s: invalid clk_div\n", __func__); + return -EINVAL; + } + + /* + * Number of bits per subframe (which is one channel sample) + * on output - Transfer 16 or 32 bits from FIFO + */ + switch (slot_width) { + case 32: + SET_UNIPERIF_I2S_FMT_NBIT_32(player); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player); + break; + case 16: + SET_UNIPERIF_I2S_FMT_NBIT_16(player); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(player); + break; + default: + dev_err(player->dev, "subframe format not supported\n"); + return -EINVAL; + } + + /* Configure data memory format */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* One data word contains two samples */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_16(player); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + /* + * Actually "16 bits/0 bits" means "32/28/24/20/18/16 bits + * on the left than zeros (if less than 32 bytes)"... ;-) + */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player); + break; + + default: + dev_err(player->dev, "format not supported\n"); + return -EINVAL; + } + + /* Set rounding to off */ + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + + /* Set clock divisor */ + SET_UNIPERIF_CTRL_DIVIDER(player, clk_div / (2 * output_frame_size)); + + /* Number of channelsmust be even*/ + if ((runtime->channels % 2) || (runtime->channels < 2) || + (runtime->channels > 10)) { + dev_err(player->dev, "%s: invalid nb of channels\n", __func__); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NUM_CH(player, runtime->channels / 2); + + /* Set 1-bit audio format to disabled */ + SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); + + /* No iec958 formatting as outputting to DAC */ + SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player); + + return 0; +} + +static int uni_player_prepare_tdm(struct uniperif *player, + struct snd_pcm_runtime *runtime) +{ + int tdm_frame_size; /* unip tdm frame size in bytes */ + int user_frame_size; /* user tdm frame size in bytes */ + /* default unip TDM_WORD_POS_X_Y */ + unsigned int word_pos[4] = { + 0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A}; + int freq, ret; + + tdm_frame_size = + sti_uniperiph_get_unip_tdm_frame_size(player); + user_frame_size = + sti_uniperiph_get_user_frame_size(runtime); + + /* fix 16/0 format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player); + + /* number of words inserted on the TDM line */ + SET_UNIPERIF_I2S_FMT_NUM_CH(player, user_frame_size / 4 / 2); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(player); + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player); + + /* Enable the tdm functionality */ + SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(player); + + /* number of 8 bits timeslots avail in unip tdm frame */ + SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(player, tdm_frame_size); + + /* set the timeslot allocation for words in FIFO */ + sti_uniperiph_get_tdm_word_pos(player, word_pos); + SET_UNIPERIF_TDM_WORD_POS(player, 1_2, word_pos[WORD_1_2]); + SET_UNIPERIF_TDM_WORD_POS(player, 3_4, word_pos[WORD_3_4]); + SET_UNIPERIF_TDM_WORD_POS(player, 5_6, word_pos[WORD_5_6]); + SET_UNIPERIF_TDM_WORD_POS(player, 7_8, word_pos[WORD_7_8]); + + /* set unip clk rate (not done vai set_sysclk ops) */ + freq = runtime->rate * tdm_frame_size * 8; + mutex_lock(&player->ctrl_lock); + ret = uni_player_clk_set_rate(player, freq); + if (!ret) + player->mclk = freq; + mutex_unlock(&player->ctrl_lock); + + return 0; +} + +/* + * ALSA uniperipheral iec958 controls + */ +static int uni_player_ctl_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 uni_player_ctl_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + struct snd_aes_iec958 *iec958 = &player->stream_settings.iec958; + + mutex_lock(&player->ctrl_lock); + ucontrol->value.iec958.status[0] = iec958->status[0]; + ucontrol->value.iec958.status[1] = iec958->status[1]; + ucontrol->value.iec958.status[2] = iec958->status[2]; + ucontrol->value.iec958.status[3] = iec958->status[3]; + mutex_unlock(&player->ctrl_lock); + return 0; +} + +static int uni_player_ctl_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + struct snd_aes_iec958 *iec958 = &player->stream_settings.iec958; + unsigned long flags; + + mutex_lock(&player->ctrl_lock); + iec958->status[0] = ucontrol->value.iec958.status[0]; + iec958->status[1] = ucontrol->value.iec958.status[1]; + iec958->status[2] = ucontrol->value.iec958.status[2]; + iec958->status[3] = ucontrol->value.iec958.status[3]; + + spin_lock_irqsave(&player->irq_lock, flags); + if (player->substream && player->substream->runtime) + uni_player_set_channel_status(player, + player->substream->runtime); + else + uni_player_set_channel_status(player, NULL); + + spin_unlock_irqrestore(&player->irq_lock, flags); + mutex_unlock(&player->ctrl_lock); + + return 0; +} + +static struct snd_kcontrol_new uni_player_iec958_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = uni_player_ctl_iec958_info, + .get = uni_player_ctl_iec958_get, + .put = uni_player_ctl_iec958_put, +}; + +/* + * uniperif rate adjustement control + */ +static int snd_sti_clk_adjustment_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 = UNIPERIF_PLAYER_CLK_ADJ_MIN; + uinfo->value.integer.max = UNIPERIF_PLAYER_CLK_ADJ_MAX; + uinfo->value.integer.step = 1; + + return 0; +} + +static int snd_sti_clk_adjustment_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + + mutex_lock(&player->ctrl_lock); + ucontrol->value.integer.value[0] = player->clk_adj; + mutex_unlock(&player->ctrl_lock); + + return 0; +} + +static int snd_sti_clk_adjustment_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + int ret = 0; + + if ((ucontrol->value.integer.value[0] < UNIPERIF_PLAYER_CLK_ADJ_MIN) || + (ucontrol->value.integer.value[0] > UNIPERIF_PLAYER_CLK_ADJ_MAX)) + return -EINVAL; + + mutex_lock(&player->ctrl_lock); + player->clk_adj = ucontrol->value.integer.value[0]; + + if (player->mclk) + ret = uni_player_clk_set_rate(player, player->mclk); + mutex_unlock(&player->ctrl_lock); + + return ret; +} + +static struct snd_kcontrol_new uni_player_clk_adj_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Playback Oversampling Freq. Adjustment", + .info = snd_sti_clk_adjustment_info, + .get = snd_sti_clk_adjustment_get, + .put = snd_sti_clk_adjustment_put, +}; + +static struct snd_kcontrol_new *snd_sti_pcm_ctl[] = { + &uni_player_clk_adj_ctl, +}; + +static struct snd_kcontrol_new *snd_sti_iec_ctl[] = { + &uni_player_iec958_ctl, + &uni_player_clk_adj_ctl, +}; + +static int uni_player_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + unsigned long flags; + int ret; + + spin_lock_irqsave(&player->irq_lock, flags); + player->substream = substream; + spin_unlock_irqrestore(&player->irq_lock, flags); + + player->clk_adj = 0; + + if (!UNIPERIF_TYPE_IS_TDM(player)) + return 0; + + /* refine hw constraint in tdm mode */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + sti_uniperiph_fix_tdm_chan, + player, SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + sti_uniperiph_fix_tdm_format, + player, SNDRV_PCM_HW_PARAM_FORMAT, + -1); +} + +static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + int ret; + + if (UNIPERIF_TYPE_IS_TDM(player) || (dir == SND_SOC_CLOCK_IN)) + return 0; + + if (clk_id != 0) + return -EINVAL; + + mutex_lock(&player->ctrl_lock); + ret = uni_player_clk_set_rate(player, freq); + if (!ret) + player->mclk = freq; + mutex_unlock(&player->ctrl_lock); + + return ret; +} + +static int uni_player_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + struct snd_pcm_runtime *runtime = substream->runtime; + int transfer_size, trigger_limit; + int ret; + + /* The player should be stopped */ + if (player->state != UNIPERIF_STATE_STOPPED) { + dev_err(player->dev, "%s: invalid player state %d\n", __func__, + player->state); + return -EINVAL; + } + + /* Calculate transfer size (in fifo cells and bytes) for frame count */ + if (player->type == SND_ST_UNIPERIF_TYPE_TDM) { + /* transfer size = user frame size (in 32 bits FIFO cell) */ + transfer_size = + sti_uniperiph_get_user_frame_size(runtime) / 4; + } else { + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + } + + /* Calculate number of empty cells available before asserting DREQ */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) { + trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; + } else { + /* + * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 + * FDMA_TRIGGER_LIMIT also controls when the state switches + * from OFF or STANDBY to AUDIO DATA. + */ + trigger_limit = transfer_size; + } + + /* Trigger limit must be an even number */ + if ((!trigger_limit % 2) || (trigger_limit != 1 && transfer_size % 2) || + (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(player))) { + dev_err(player->dev, "invalid trigger limit %d\n", + trigger_limit); + return -EINVAL; + } + + SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(player, trigger_limit); + + /* Uniperipheral setup depends on player type */ + switch (player->type) { + case SND_ST_UNIPERIF_TYPE_HDMI: + ret = uni_player_prepare_iec958(player, runtime); + break; + case SND_ST_UNIPERIF_TYPE_PCM: + ret = uni_player_prepare_pcm(player, runtime); + break; + case SND_ST_UNIPERIF_TYPE_SPDIF: + ret = uni_player_prepare_iec958(player, runtime); + break; + case SND_ST_UNIPERIF_TYPE_TDM: + ret = uni_player_prepare_tdm(player, runtime); + break; + default: + dev_err(player->dev, "invalid player type\n"); + return -EINVAL; + } + + if (ret) + return ret; + + switch (player->daifmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(player); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(player); + break; + case SND_SOC_DAIFMT_NB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(player); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(player); + break; + case SND_SOC_DAIFMT_IB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(player); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player); + break; + case SND_SOC_DAIFMT_IB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(player); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player); + break; + } + + switch (player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player); + SET_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(player); + break; + case SND_SOC_DAIFMT_LEFT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(player); + break; + case SND_SOC_DAIFMT_RIGHT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_RIGHT(player); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(player); + break; + default: + dev_err(player->dev, "format not supported\n"); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(player, 0); + + + return sti_uniperiph_reset(player); +} + +static int uni_player_start(struct uniperif *player) +{ + int ret; + + /* The player should be stopped */ + if (player->state != UNIPERIF_STATE_STOPPED) { + dev_err(player->dev, "%s: invalid player state\n", __func__); + return -EINVAL; + } + + ret = clk_prepare_enable(player->clk); + if (ret) { + dev_err(player->dev, "%s: Failed to enable clock\n", __func__); + return ret; + } + + /* Clear any pending interrupts */ + SET_UNIPERIF_ITS_BCLR(player, GET_UNIPERIF_ITS(player)); + + /* Set the interrupt mask */ + SET_UNIPERIF_ITM_BSET_DMA_ERROR(player); + SET_UNIPERIF_ITM_BSET_FIFO_ERROR(player); + + /* Enable underflow recovery interrupts */ + if (player->underflow_enabled) { + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE(player); + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED(player); + } + + ret = sti_uniperiph_reset(player); + if (ret < 0) { + clk_disable_unprepare(player->clk); + return ret; + } + + /* + * Does not use IEC61937 features of the uniperipheral hardware. + * Instead it performs IEC61937 in software and inserts it directly + * into the audio data stream. As such, when encoded mode is selected, + * linear pcm mode is still used, but with the differences of the + * channel status bits set for encoded mode and the validity bits set. + */ + SET_UNIPERIF_CTRL_OPERATION_PCM_DATA(player); + + /* + * If iec958 formatting is required for hdmi or spdif, then it must be + * enabled after the operation mode is set. If set prior to this, it + * will not take affect and hang the player. + */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + if (UNIPERIF_TYPE_IS_IEC958(player)) + SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player); + + /* Force channel status update (no update if clk disable) */ + if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(player); + else + SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(player); + + /* Update state to started */ + player->state = UNIPERIF_STATE_STARTED; + + return 0; +} + +static int uni_player_stop(struct uniperif *player) +{ + int ret; + + /* The player should not be in stopped state */ + if (player->state == UNIPERIF_STATE_STOPPED) { + dev_err(player->dev, "%s: invalid player state\n", __func__); + return -EINVAL; + } + + /* Turn the player off */ + SET_UNIPERIF_CTRL_OPERATION_OFF(player); + + ret = sti_uniperiph_reset(player); + if (ret < 0) + return ret; + + /* Disable interrupts */ + SET_UNIPERIF_ITM_BCLR(player, GET_UNIPERIF_ITM(player)); + + /* Disable clock */ + clk_disable_unprepare(player->clk); + + /* Update state to stopped and return */ + player->state = UNIPERIF_STATE_STOPPED; + + return 0; +} + +int uni_player_resume(struct uniperif *player) +{ + int ret; + + /* Select the frequency synthesizer clock */ + if (player->clk_sel) { + ret = regmap_field_write(player->clk_sel, 1); + if (ret) { + dev_err(player->dev, + "%s: Failed to select freq synth clock\n", + __func__); + return ret; + } + } + + SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(player); + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); + SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player); + + return 0; +} +EXPORT_SYMBOL_GPL(uni_player_resume); + +static int uni_player_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + return uni_player_start(player); + case SNDRV_PCM_TRIGGER_STOP: + return uni_player_stop(player); + case SNDRV_PCM_TRIGGER_RESUME: + return uni_player_resume(player); + default: + return -EINVAL; + } +} + +static void uni_player_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *player = priv->dai_data.uni; + unsigned long flags; + + spin_lock_irqsave(&player->irq_lock, flags); + if (player->state != UNIPERIF_STATE_STOPPED) + /* Stop the player */ + uni_player_stop(player); + + player->substream = NULL; + spin_unlock_irqrestore(&player->irq_lock, flags); +} + +static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, + struct uniperif *player) +{ + struct device_node *node = pdev->dev.of_node; + struct regmap *regmap; + struct reg_field regfield[2] = { + /* PCM_CLK_SEL */ + REG_FIELD(SYS_CFG_AUDIO_GLUE, + 8 + player->id, + 8 + player->id), + /* PCMP_VALID_SEL */ + REG_FIELD(SYS_CFG_AUDIO_GLUE, 0, 1) + }; + + regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); + + if (IS_ERR(regmap)) { + dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n"); + return PTR_ERR(regmap); + } + + player->clk_sel = regmap_field_alloc(regmap, regfield[0]); + player->valid_sel = regmap_field_alloc(regmap, regfield[1]); + + return 0; +} + +static const struct snd_soc_dai_ops uni_player_dai_ops = { + .startup = uni_player_startup, + .shutdown = uni_player_shutdown, + .prepare = uni_player_prepare, + .trigger = uni_player_trigger, + .hw_params = sti_uniperiph_dai_hw_params, + .set_fmt = sti_uniperiph_dai_set_fmt, + .set_sysclk = uni_player_set_sysclk, + .set_tdm_slot = sti_uniperiph_set_tdm_slot +}; + +int uni_player_init(struct platform_device *pdev, + struct uniperif *player) +{ + int ret = 0; + + player->dev = &pdev->dev; + player->state = UNIPERIF_STATE_STOPPED; + player->dai_ops = &uni_player_dai_ops; + + /* Get PCM_CLK_SEL & PCMP_VALID_SEL from audio-glue-ctrl SoC reg */ + ret = uni_player_parse_dt_audio_glue(pdev, player); + + if (ret < 0) { + dev_err(player->dev, "Failed to parse DeviceTree\n"); + return ret; + } + + /* Underflow recovery is only supported on later ip revisions */ + if (player->ver >= SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + player->underflow_enabled = 1; + + if (UNIPERIF_TYPE_IS_TDM(player)) + player->hw = &uni_tdm_hw; + else + player->hw = &uni_player_pcm_hw; + + /* Get uniperif resource */ + player->clk = of_clk_get(pdev->dev.of_node, 0); + if (IS_ERR(player->clk)) { + dev_err(player->dev, "Failed to get clock\n"); + return PTR_ERR(player->clk); + } + + /* Select the frequency synthesizer clock */ + if (player->clk_sel) { + ret = regmap_field_write(player->clk_sel, 1); + if (ret) { + dev_err(player->dev, + "%s: Failed to select freq synth clock\n", + __func__); + return ret; + } + } + + /* connect to I2S/TDM TX bus */ + if (player->valid_sel && + (player->id == UNIPERIF_PLAYER_I2S_OUT)) { + ret = regmap_field_write(player->valid_sel, player->id); + if (ret) { + dev_err(player->dev, + "%s: unable to connect to tdm bus\n", __func__); + return ret; + } + } + + ret = devm_request_irq(&pdev->dev, player->irq, + uni_player_irq_handler, IRQF_SHARED, + dev_name(&pdev->dev), player); + if (ret < 0) { + dev_err(player->dev, "unable to request IRQ %d\n", player->irq); + return ret; + } + + mutex_init(&player->ctrl_lock); + spin_lock_init(&player->irq_lock); + + /* Ensure that disabled by default */ + SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(player); + SET_UNIPERIF_CTRL_ROUNDING_OFF(player); + SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player); + SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player); + + if (UNIPERIF_TYPE_IS_IEC958(player)) { + /* Set default iec958 status bits */ + + /* Consumer, PCM, copyright, 2ch, mode 0 */ + player->stream_settings.iec958.status[0] = 0x00; + /* Broadcast reception category */ + player->stream_settings.iec958.status[1] = + IEC958_AES1_CON_GENERAL; + /* Do not take into account source or channel number */ + player->stream_settings.iec958.status[2] = + IEC958_AES2_CON_SOURCE_UNSPEC; + /* Sampling frequency not indicated */ + player->stream_settings.iec958.status[3] = + IEC958_AES3_CON_FS_NOTID; + /* Max sample word 24-bit, sample word length not indicated */ + player->stream_settings.iec958.status[4] = + IEC958_AES4_CON_MAX_WORDLEN_24 | + IEC958_AES4_CON_WORDLEN_24_20; + + player->num_ctrls = ARRAY_SIZE(snd_sti_iec_ctl); + player->snd_ctrls = snd_sti_iec_ctl[0]; + } else { + player->num_ctrls = ARRAY_SIZE(snd_sti_pcm_ctl); + player->snd_ctrls = snd_sti_pcm_ctl[0]; + } + + return 0; +} +EXPORT_SYMBOL_GPL(uni_player_init); diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c new file mode 100644 index 000000000..ee0055e60 --- /dev/null +++ b/sound/soc/sti/uniperif_reader.c @@ -0,0 +1,436 @@ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include + +#include "uniperif.h" + +#define UNIPERIF_READER_I2S_IN 0 /* reader id connected to I2S/TDM TX bus */ +/* + * Note: snd_pcm_hardware is linked to DMA controller but is declared here to + * integrate unireader capability in term of rate and supported channels + */ +static const struct snd_pcm_hardware uni_reader_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 96000, + + .channels_min = 2, + .channels_max = 8, + + .periods_min = 2, + .periods_max = 48, + + .period_bytes_min = 128, + .period_bytes_max = 64 * PAGE_SIZE, + .buffer_bytes_max = 256 * PAGE_SIZE +}; + +/* + * uni_reader_irq_handler + * In case of error audio stream is stopped; stop action is protected via PCM + * stream lock to avoid race condition with trigger callback. + */ +static irqreturn_t uni_reader_irq_handler(int irq, void *dev_id) +{ + irqreturn_t ret = IRQ_NONE; + struct uniperif *reader = dev_id; + unsigned int status; + + spin_lock(&reader->irq_lock); + if (!reader->substream) + goto irq_spin_unlock; + + snd_pcm_stream_lock(reader->substream); + if (reader->state == UNIPERIF_STATE_STOPPED) { + /* Unexpected IRQ: do nothing */ + dev_warn(reader->dev, "unexpected IRQ\n"); + goto stream_unlock; + } + + /* Get interrupt status & clear them immediately */ + status = GET_UNIPERIF_ITS(reader); + SET_UNIPERIF_ITS_BCLR(reader, status); + + /* Check for fifo overflow error */ + if (unlikely(status & UNIPERIF_ITS_FIFO_ERROR_MASK(reader))) { + dev_err(reader->dev, "FIFO error detected\n"); + + snd_pcm_stop(reader->substream, SNDRV_PCM_STATE_XRUN); + + ret = IRQ_HANDLED; + } + +stream_unlock: + snd_pcm_stream_unlock(reader->substream); +irq_spin_unlock: + spin_unlock(&reader->irq_lock); + + return ret; +} + +static int uni_reader_prepare_pcm(struct snd_pcm_runtime *runtime, + struct uniperif *reader) +{ + int slot_width; + + /* Force slot width to 32 in I2S mode */ + if ((reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) + == SND_SOC_DAIFMT_I2S) { + slot_width = 32; + } else { + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + slot_width = 16; + break; + default: + slot_width = 32; + break; + } + } + + /* Number of bits per subframe (i.e one channel sample) on input. */ + switch (slot_width) { + case 32: + SET_UNIPERIF_I2S_FMT_NBIT_32(reader); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(reader); + break; + case 16: + SET_UNIPERIF_I2S_FMT_NBIT_16(reader); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(reader); + break; + default: + dev_err(reader->dev, "subframe format not supported\n"); + return -EINVAL; + } + + /* Configure data memory format */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + /* One data word contains two samples */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_16(reader); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + /* + * Actually "16 bits/0 bits" means "32/28/24/20/18/16 bits + * on the MSB then zeros (if less than 32 bytes)"... + */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(reader); + break; + + default: + dev_err(reader->dev, "format not supported\n"); + return -EINVAL; + } + + /* Number of channels must be even */ + if ((runtime->channels % 2) || (runtime->channels < 2) || + (runtime->channels > 10)) { + dev_err(reader->dev, "%s: invalid nb of channels\n", __func__); + return -EINVAL; + } + + SET_UNIPERIF_I2S_FMT_NUM_CH(reader, runtime->channels / 2); + SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); + + return 0; +} + +static int uni_reader_prepare_tdm(struct snd_pcm_runtime *runtime, + struct uniperif *reader) +{ + int frame_size; /* user tdm frame size in bytes */ + /* default unip TDM_WORD_POS_X_Y */ + unsigned int word_pos[4] = { + 0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A}; + + frame_size = sti_uniperiph_get_user_frame_size(runtime); + + /* fix 16/0 format */ + SET_UNIPERIF_CONFIG_MEM_FMT_16_0(reader); + SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(reader); + + /* number of words inserted on the TDM line */ + SET_UNIPERIF_I2S_FMT_NUM_CH(reader, frame_size / 4 / 2); + + SET_UNIPERIF_I2S_FMT_ORDER_MSB(reader); + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); + SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(reader); + + /* + * set the timeslots allocation for words in FIFO + * + * HW bug: (LSB word < MSB word) => this config is not possible + * So if we want (LSB word < MSB) word, then it shall be + * handled by user + */ + sti_uniperiph_get_tdm_word_pos(reader, word_pos); + SET_UNIPERIF_TDM_WORD_POS(reader, 1_2, word_pos[WORD_1_2]); + SET_UNIPERIF_TDM_WORD_POS(reader, 3_4, word_pos[WORD_3_4]); + SET_UNIPERIF_TDM_WORD_POS(reader, 5_6, word_pos[WORD_5_6]); + SET_UNIPERIF_TDM_WORD_POS(reader, 7_8, word_pos[WORD_7_8]); + + return 0; +} + +static int uni_reader_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + struct snd_pcm_runtime *runtime = substream->runtime; + int transfer_size, trigger_limit, ret; + + /* The reader should be stopped */ + if (reader->state != UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state %d\n", __func__, + reader->state); + return -EINVAL; + } + + /* Calculate transfer size (in fifo cells and bytes) for frame count */ + if (reader->type == SND_ST_UNIPERIF_TYPE_TDM) { + /* transfer size = unip frame size (in 32 bits FIFO cell) */ + transfer_size = + sti_uniperiph_get_user_frame_size(runtime) / 4; + } else { + transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES; + } + + /* Calculate number of empty cells available before asserting DREQ */ + if (reader->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) + trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size; + else + /* + * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0 + * FDMA_TRIGGER_LIMIT also controls when the state switches + * from OFF or STANDBY to AUDIO DATA. + */ + trigger_limit = transfer_size; + + /* Trigger limit must be an even number */ + if ((!trigger_limit % 2) || + (trigger_limit != 1 && transfer_size % 2) || + (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(reader))) { + dev_err(reader->dev, "invalid trigger limit %d\n", + trigger_limit); + return -EINVAL; + } + + SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(reader, trigger_limit); + + if (UNIPERIF_TYPE_IS_TDM(reader)) + ret = uni_reader_prepare_tdm(runtime, reader); + else + ret = uni_reader_prepare_pcm(runtime, reader); + if (ret) + return ret; + + switch (reader->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); + SET_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(reader); + break; + case SND_SOC_DAIFMT_LEFT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(reader); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(reader); + break; + case SND_SOC_DAIFMT_RIGHT_J: + SET_UNIPERIF_I2S_FMT_ALIGN_RIGHT(reader); + SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(reader); + break; + default: + dev_err(reader->dev, "format not supported\n"); + return -EINVAL; + } + + /* Data clocking (changing) on the rising/falling edge */ + switch (reader->daifmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); + break; + case SND_SOC_DAIFMT_NB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(reader); + break; + case SND_SOC_DAIFMT_IB_NF: + SET_UNIPERIF_I2S_FMT_LR_POL_LOW(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader); + break; + case SND_SOC_DAIFMT_IB_IF: + SET_UNIPERIF_I2S_FMT_LR_POL_HIG(reader); + SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(reader); + break; + } + + /* Clear any pending interrupts */ + SET_UNIPERIF_ITS_BCLR(reader, GET_UNIPERIF_ITS(reader)); + + SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(reader, 0); + + /* Set the interrupt mask */ + SET_UNIPERIF_ITM_BSET_DMA_ERROR(reader); + SET_UNIPERIF_ITM_BSET_FIFO_ERROR(reader); + SET_UNIPERIF_ITM_BSET_MEM_BLK_READ(reader); + + /* Enable underflow recovery interrupts */ + if (reader->underflow_enabled) { + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE(reader); + SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED(reader); + } + + /* Reset uniperipheral reader */ + return sti_uniperiph_reset(reader); +} + +static int uni_reader_start(struct uniperif *reader) +{ + /* The reader should be stopped */ + if (reader->state != UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state\n", __func__); + return -EINVAL; + } + + /* Enable reader interrupts (and clear possible stalled ones) */ + SET_UNIPERIF_ITS_BCLR_FIFO_ERROR(reader); + SET_UNIPERIF_ITM_BSET_FIFO_ERROR(reader); + + /* Launch the reader */ + SET_UNIPERIF_CTRL_OPERATION_PCM_DATA(reader); + + /* Update state to started */ + reader->state = UNIPERIF_STATE_STARTED; + return 0; +} + +static int uni_reader_stop(struct uniperif *reader) +{ + /* The reader should not be in stopped state */ + if (reader->state == UNIPERIF_STATE_STOPPED) { + dev_err(reader->dev, "%s: invalid reader state\n", __func__); + return -EINVAL; + } + + /* Turn the reader off */ + SET_UNIPERIF_CTRL_OPERATION_OFF(reader); + + /* Disable interrupts */ + SET_UNIPERIF_ITM_BCLR(reader, GET_UNIPERIF_ITM(reader)); + + /* Update state to stopped and return */ + reader->state = UNIPERIF_STATE_STOPPED; + + return 0; +} + +static int uni_reader_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + return uni_reader_start(reader); + case SNDRV_PCM_TRIGGER_STOP: + return uni_reader_stop(reader); + default: + return -EINVAL; + } +} + +static int uni_reader_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + unsigned long flags; + int ret; + + spin_lock_irqsave(&reader->irq_lock, flags); + reader->substream = substream; + spin_unlock_irqrestore(&reader->irq_lock, flags); + + if (!UNIPERIF_TYPE_IS_TDM(reader)) + return 0; + + /* refine hw constraint in tdm mode */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + sti_uniperiph_fix_tdm_chan, + reader, SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + if (ret < 0) + return ret; + + return snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + sti_uniperiph_fix_tdm_format, + reader, SNDRV_PCM_HW_PARAM_FORMAT, + -1); +} + +static void uni_reader_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai); + struct uniperif *reader = priv->dai_data.uni; + unsigned long flags; + + spin_lock_irqsave(&reader->irq_lock, flags); + if (reader->state != UNIPERIF_STATE_STOPPED) { + /* Stop the reader */ + uni_reader_stop(reader); + } + reader->substream = NULL; + spin_unlock_irqrestore(&reader->irq_lock, flags); +} + +static const struct snd_soc_dai_ops uni_reader_dai_ops = { + .startup = uni_reader_startup, + .shutdown = uni_reader_shutdown, + .prepare = uni_reader_prepare, + .trigger = uni_reader_trigger, + .hw_params = sti_uniperiph_dai_hw_params, + .set_fmt = sti_uniperiph_dai_set_fmt, + .set_tdm_slot = sti_uniperiph_set_tdm_slot +}; + +int uni_reader_init(struct platform_device *pdev, + struct uniperif *reader) +{ + int ret = 0; + + reader->dev = &pdev->dev; + reader->state = UNIPERIF_STATE_STOPPED; + reader->dai_ops = &uni_reader_dai_ops; + + if (UNIPERIF_TYPE_IS_TDM(reader)) + reader->hw = &uni_tdm_hw; + else + reader->hw = &uni_reader_pcm_hw; + + ret = devm_request_irq(&pdev->dev, reader->irq, + uni_reader_irq_handler, IRQF_SHARED, + dev_name(&pdev->dev), reader); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request IRQ\n"); + return -EBUSY; + } + + spin_lock_init(&reader->irq_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(uni_reader_init); diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig new file mode 100644 index 000000000..9b2681397 --- /dev/null +++ b/sound/soc/stm/Kconfig @@ -0,0 +1,44 @@ +menu "STMicroelectronics STM32 SOC audio support" + +config SND_SOC_STM32_SAI + tristate "STM32 SAI interface (Serial Audio Interface) support" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + select SND_PCM_IEC958 + help + Say Y if you want to enable SAI for STM32 + +config SND_SOC_STM32_I2S + tristate "STM32 I2S interface (SPI/I2S block) support" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to enable I2S for STM32 + +config SND_SOC_STM32_SPDIFRX + tristate "STM32 S/PDIF receiver (SPDIFRX) support" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + select SND_SOC_SPDIF + help + Say Y if you want to enable S/PDIF capture for STM32 + +config SND_SOC_STM32_DFSDM + tristate "SoC Audio support for STM32 DFSDM" + depends on ARCH_STM32 || COMPILE_TEST + depends on SND_SOC + depends on STM32_DFSDM_ADC + select SND_SOC_GENERIC_DMAENGINE_PCM + select SND_SOC_DMIC + select IIO_BUFFER_CB + help + Select this option to enable the STM32 Digital Filter + for Sigma Delta Modulators (DFSDM) driver used + in various STM32 series for digital microphone capture. +endmenu diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile new file mode 100644 index 000000000..3143c0b47 --- /dev/null +++ b/sound/soc/stm/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# SAI +snd-soc-stm32-sai-sub-objs := stm32_sai_sub.o +obj-$(CONFIG_SND_SOC_STM32_SAI) += snd-soc-stm32-sai-sub.o + +snd-soc-stm32-sai-objs := stm32_sai.o +obj-$(CONFIG_SND_SOC_STM32_SAI) += snd-soc-stm32-sai.o + +# I2S +snd-soc-stm32-i2s-objs := stm32_i2s.o +obj-$(CONFIG_SND_SOC_STM32_I2S) += snd-soc-stm32-i2s.o + +# SPDIFRX +snd-soc-stm32-spdifrx-objs := stm32_spdifrx.o +obj-$(CONFIG_SND_SOC_STM32_SPDIFRX) += snd-soc-stm32-spdifrx.o + +#DFSDM +obj-$(CONFIG_SND_SOC_STM32_DFSDM) += stm32_adfsdm.o diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c new file mode 100644 index 000000000..24948b95e --- /dev/null +++ b/sound/soc/stm/stm32_adfsdm.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part of STM32 DFSDM ASoC DAI driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Authors: Arnaud Pouliquen + * Olivier Moysan + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define STM32_ADFSDM_DRV_NAME "stm32-adfsdm" + +#define DFSDM_MAX_PERIOD_SIZE (PAGE_SIZE / 2) +#define DFSDM_MAX_PERIODS 6 + +struct stm32_adfsdm_priv { + struct snd_soc_dai_driver dai_drv; + struct snd_pcm_substream *substream; + struct device *dev; + + /* IIO */ + struct iio_channel *iio_ch; + struct iio_cb_buffer *iio_cb; + bool iio_active; + + /* PCM buffer */ + unsigned char *pcm_buff; + unsigned int pos; + + struct mutex lock; /* protect against race condition on iio state */ +}; + +static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + + .rate_min = 8000, + .rate_max = 32000, + + .channels_min = 1, + .channels_max = 1, + + .periods_min = 2, + .periods_max = DFSDM_MAX_PERIODS, + + .period_bytes_max = DFSDM_MAX_PERIOD_SIZE, + .buffer_bytes_max = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE +}; + +static void stm32_adfsdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + + mutex_lock(&priv->lock); + if (priv->iio_active) { + iio_channel_stop_all_cb(priv->iio_cb); + priv->iio_active = false; + } + mutex_unlock(&priv->lock); +} + +static int stm32_adfsdm_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + int ret; + + mutex_lock(&priv->lock); + if (priv->iio_active) { + iio_channel_stop_all_cb(priv->iio_cb); + priv->iio_active = false; + } + + ret = iio_write_channel_attribute(priv->iio_ch, + substream->runtime->rate, 0, + IIO_CHAN_INFO_SAMP_FREQ); + if (ret < 0) { + dev_err(dai->dev, "%s: Failed to set %d sampling rate\n", + __func__, substream->runtime->rate); + goto out; + } + + if (!priv->iio_active) { + ret = iio_channel_start_all_cb(priv->iio_cb); + if (!ret) + priv->iio_active = true; + else + dev_err(dai->dev, "%s: IIO channel start failed (%d)\n", + __func__, ret); + } + +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int stm32_adfsdm_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(dai); + ssize_t size; + char str_freq[10]; + + dev_dbg(dai->dev, "%s: Enter for freq %d\n", __func__, freq); + + /* Set IIO frequency if CODEC is master as clock comes from SPI_IN */ + + snprintf(str_freq, sizeof(str_freq), "%d\n", freq); + size = iio_write_channel_ext_info(priv->iio_ch, "spi_clk_freq", + str_freq, sizeof(str_freq)); + if (size != sizeof(str_freq)) { + dev_err(dai->dev, "%s: Failed to set SPI clock\n", + __func__); + return -EINVAL; + } + return 0; +} + +static const struct snd_soc_dai_ops stm32_adfsdm_dai_ops = { + .shutdown = stm32_adfsdm_shutdown, + .prepare = stm32_adfsdm_dai_prepare, + .set_sysclk = stm32_adfsdm_set_sysclk, +}; + +static const struct snd_soc_dai_driver stm32_adfsdm_dai = { + .capture = { + .channels_min = 1, + .channels_max = 1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000), + }, + .ops = &stm32_adfsdm_dai_ops, +}; + +static const struct snd_soc_component_driver stm32_adfsdm_dai_component = { + .name = "stm32_dfsdm_audio", +}; + +static int stm32_afsdm_pcm_cb(const void *data, size_t size, void *private) +{ + struct stm32_adfsdm_priv *priv = private; + struct snd_soc_pcm_runtime *rtd = priv->substream->private_data; + u8 *pcm_buff = priv->pcm_buff; + u8 *src_buff = (u8 *)data; + unsigned int buff_size = snd_pcm_lib_buffer_bytes(priv->substream); + unsigned int period_size = snd_pcm_lib_period_bytes(priv->substream); + unsigned int old_pos = priv->pos; + unsigned int cur_size = size; + + dev_dbg(rtd->dev, "%s: buff_add :%pK, pos = %d, size = %zu\n", + __func__, &pcm_buff[priv->pos], priv->pos, size); + + if ((priv->pos + size) > buff_size) { + memcpy(&pcm_buff[priv->pos], src_buff, buff_size - priv->pos); + cur_size -= buff_size - priv->pos; + priv->pos = 0; + } + + memcpy(&pcm_buff[priv->pos], &src_buff[size - cur_size], cur_size); + priv->pos = (priv->pos + cur_size) % buff_size; + + if (cur_size != size || (old_pos && (old_pos % period_size < size))) + snd_pcm_period_elapsed(priv->substream); + + return 0; +} + +static int stm32_adfsdm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stm32_adfsdm_priv *priv = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + priv->pos = 0; + return stm32_dfsdm_get_buff_cb(priv->iio_ch->indio_dev, + stm32_afsdm_pcm_cb, priv); + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + return stm32_dfsdm_release_buff_cb(priv->iio_ch->indio_dev); + } + + return -EINVAL; +} + +static int stm32_adfsdm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stm32_adfsdm_priv *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &stm32_adfsdm_pcm_hw); + if (!ret) + priv->substream = substream; + + return ret; +} + +static int stm32_adfsdm_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stm32_adfsdm_priv *priv = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + + snd_pcm_lib_free_pages(substream); + priv->substream = NULL; + + return 0; +} + +static snd_pcm_uframes_t stm32_adfsdm_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stm32_adfsdm_priv *priv = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + + return bytes_to_frames(substream->runtime, priv->pos); +} + +static int stm32_adfsdm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct stm32_adfsdm_priv *priv = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + priv->pcm_buff = substream->runtime->dma_area; + + return iio_channel_cb_set_buffer_watermark(priv->iio_cb, + params_period_size(params)); +} + +static int stm32_adfsdm_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static struct snd_pcm_ops stm32_adfsdm_pcm_ops = { + .open = stm32_adfsdm_pcm_open, + .close = stm32_adfsdm_pcm_close, + .hw_params = stm32_adfsdm_pcm_hw_params, + .hw_free = stm32_adfsdm_pcm_hw_free, + .trigger = stm32_adfsdm_trigger, + .pointer = stm32_adfsdm_pcm_pointer, +}; + +static int stm32_adfsdm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct stm32_adfsdm_priv *priv = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + unsigned int size = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE; + + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + priv->dev, size, size); +} + +static void stm32_adfsdm_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static struct snd_soc_component_driver stm32_adfsdm_soc_platform = { + .ops = &stm32_adfsdm_pcm_ops, + .pcm_new = stm32_adfsdm_pcm_new, + .pcm_free = stm32_adfsdm_pcm_free, +}; + +static const struct of_device_id stm32_adfsdm_of_match[] = { + {.compatible = "st,stm32h7-dfsdm-dai"}, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_adfsdm_of_match); + +static int stm32_adfsdm_probe(struct platform_device *pdev) +{ + struct stm32_adfsdm_priv *priv; + struct snd_soc_component *component; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->dai_drv = stm32_adfsdm_dai; + mutex_init(&priv->lock); + + dev_set_drvdata(&pdev->dev, priv); + + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_dai_component, + &priv->dai_drv, 1); + if (ret < 0) + return ret; + + /* Associate iio channel */ + priv->iio_ch = devm_iio_channel_get_all(&pdev->dev); + if (IS_ERR(priv->iio_ch)) + return PTR_ERR(priv->iio_ch); + + priv->iio_cb = iio_channel_get_all_cb(&pdev->dev, NULL, NULL); + if (IS_ERR(priv->iio_cb)) + return PTR_ERR(priv->iio_cb); + + component = devm_kzalloc(&pdev->dev, sizeof(*component), GFP_KERNEL); + if (!component) + return -ENOMEM; +#ifdef CONFIG_DEBUG_FS + component->debugfs_prefix = "pcm"; +#endif + + ret = snd_soc_add_component(&pdev->dev, component, + &stm32_adfsdm_soc_platform, NULL, 0); + if (ret < 0) + dev_err(&pdev->dev, "%s: Failed to register PCM platform\n", + __func__); + + return ret; +} + +static int stm32_adfsdm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static struct platform_driver stm32_adfsdm_driver = { + .driver = { + .name = STM32_ADFSDM_DRV_NAME, + .of_match_table = stm32_adfsdm_of_match, + }, + .probe = stm32_adfsdm_probe, + .remove = stm32_adfsdm_remove, +}; + +module_platform_driver(stm32_adfsdm_driver); + +MODULE_DESCRIPTION("stm32 DFSDM DAI driver"); +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" STM32_ADFSDM_DRV_NAME); diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c new file mode 100644 index 000000000..aa2b11961 --- /dev/null +++ b/sound/soc/stm/stm32_i2s.c @@ -0,0 +1,945 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (I2S) driver. + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define STM32_I2S_CR1_REG 0x0 +#define STM32_I2S_CFG1_REG 0x08 +#define STM32_I2S_CFG2_REG 0x0C +#define STM32_I2S_IER_REG 0x10 +#define STM32_I2S_SR_REG 0x14 +#define STM32_I2S_IFCR_REG 0x18 +#define STM32_I2S_TXDR_REG 0X20 +#define STM32_I2S_RXDR_REG 0x30 +#define STM32_I2S_CGFR_REG 0X50 + +/* Bit definition for SPI2S_CR1 register */ +#define I2S_CR1_SPE BIT(0) +#define I2S_CR1_CSTART BIT(9) +#define I2S_CR1_CSUSP BIT(10) +#define I2S_CR1_HDDIR BIT(11) +#define I2S_CR1_SSI BIT(12) +#define I2S_CR1_CRC33_17 BIT(13) +#define I2S_CR1_RCRCI BIT(14) +#define I2S_CR1_TCRCI BIT(15) + +/* Bit definition for SPI_CFG2 register */ +#define I2S_CFG2_IOSWP_SHIFT 15 +#define I2S_CFG2_IOSWP BIT(I2S_CFG2_IOSWP_SHIFT) +#define I2S_CFG2_LSBFRST BIT(23) +#define I2S_CFG2_AFCNTR BIT(31) + +/* Bit definition for SPI_CFG1 register */ +#define I2S_CFG1_FTHVL_SHIFT 5 +#define I2S_CFG1_FTHVL_MASK GENMASK(8, I2S_CFG1_FTHVL_SHIFT) +#define I2S_CFG1_FTHVL_SET(x) ((x) << I2S_CFG1_FTHVL_SHIFT) + +#define I2S_CFG1_TXDMAEN BIT(15) +#define I2S_CFG1_RXDMAEN BIT(14) + +/* Bit definition for SPI2S_IER register */ +#define I2S_IER_RXPIE BIT(0) +#define I2S_IER_TXPIE BIT(1) +#define I2S_IER_DPXPIE BIT(2) +#define I2S_IER_EOTIE BIT(3) +#define I2S_IER_TXTFIE BIT(4) +#define I2S_IER_UDRIE BIT(5) +#define I2S_IER_OVRIE BIT(6) +#define I2S_IER_CRCEIE BIT(7) +#define I2S_IER_TIFREIE BIT(8) +#define I2S_IER_MODFIE BIT(9) +#define I2S_IER_TSERFIE BIT(10) + +/* Bit definition for SPI2S_SR register */ +#define I2S_SR_RXP BIT(0) +#define I2S_SR_TXP BIT(1) +#define I2S_SR_DPXP BIT(2) +#define I2S_SR_EOT BIT(3) +#define I2S_SR_TXTF BIT(4) +#define I2S_SR_UDR BIT(5) +#define I2S_SR_OVR BIT(6) +#define I2S_SR_CRCERR BIT(7) +#define I2S_SR_TIFRE BIT(8) +#define I2S_SR_MODF BIT(9) +#define I2S_SR_TSERF BIT(10) +#define I2S_SR_SUSP BIT(11) +#define I2S_SR_TXC BIT(12) +#define I2S_SR_RXPLVL GENMASK(14, 13) +#define I2S_SR_RXWNE BIT(15) + +#define I2S_SR_MASK GENMASK(15, 0) + +/* Bit definition for SPI_IFCR register */ +#define I2S_IFCR_EOTC BIT(3) +#define I2S_IFCR_TXTFC BIT(4) +#define I2S_IFCR_UDRC BIT(5) +#define I2S_IFCR_OVRC BIT(6) +#define I2S_IFCR_CRCEC BIT(7) +#define I2S_IFCR_TIFREC BIT(8) +#define I2S_IFCR_MODFC BIT(9) +#define I2S_IFCR_TSERFC BIT(10) +#define I2S_IFCR_SUSPC BIT(11) + +#define I2S_IFCR_MASK GENMASK(11, 3) + +/* Bit definition for SPI_I2SCGFR register */ +#define I2S_CGFR_I2SMOD BIT(0) + +#define I2S_CGFR_I2SCFG_SHIFT 1 +#define I2S_CGFR_I2SCFG_MASK GENMASK(3, I2S_CGFR_I2SCFG_SHIFT) +#define I2S_CGFR_I2SCFG_SET(x) ((x) << I2S_CGFR_I2SCFG_SHIFT) + +#define I2S_CGFR_I2SSTD_SHIFT 4 +#define I2S_CGFR_I2SSTD_MASK GENMASK(5, I2S_CGFR_I2SSTD_SHIFT) +#define I2S_CGFR_I2SSTD_SET(x) ((x) << I2S_CGFR_I2SSTD_SHIFT) + +#define I2S_CGFR_PCMSYNC BIT(7) + +#define I2S_CGFR_DATLEN_SHIFT 8 +#define I2S_CGFR_DATLEN_MASK GENMASK(9, I2S_CGFR_DATLEN_SHIFT) +#define I2S_CGFR_DATLEN_SET(x) ((x) << I2S_CGFR_DATLEN_SHIFT) + +#define I2S_CGFR_CHLEN_SHIFT 10 +#define I2S_CGFR_CHLEN BIT(I2S_CGFR_CHLEN_SHIFT) +#define I2S_CGFR_CKPOL BIT(11) +#define I2S_CGFR_FIXCH BIT(12) +#define I2S_CGFR_WSINV BIT(13) +#define I2S_CGFR_DATFMT BIT(14) + +#define I2S_CGFR_I2SDIV_SHIFT 16 +#define I2S_CGFR_I2SDIV_BIT_H 23 +#define I2S_CGFR_I2SDIV_MASK GENMASK(I2S_CGFR_I2SDIV_BIT_H,\ + I2S_CGFR_I2SDIV_SHIFT) +#define I2S_CGFR_I2SDIV_SET(x) ((x) << I2S_CGFR_I2SDIV_SHIFT) +#define I2S_CGFR_I2SDIV_MAX ((1 << (I2S_CGFR_I2SDIV_BIT_H -\ + I2S_CGFR_I2SDIV_SHIFT)) - 1) + +#define I2S_CGFR_ODD_SHIFT 24 +#define I2S_CGFR_ODD BIT(I2S_CGFR_ODD_SHIFT) +#define I2S_CGFR_MCKOE BIT(25) + +enum i2s_master_mode { + I2S_MS_NOT_SET, + I2S_MS_MASTER, + I2S_MS_SLAVE, +}; + +enum i2s_mode { + I2S_I2SMOD_TX_SLAVE, + I2S_I2SMOD_RX_SLAVE, + I2S_I2SMOD_TX_MASTER, + I2S_I2SMOD_RX_MASTER, + I2S_I2SMOD_FD_SLAVE, + I2S_I2SMOD_FD_MASTER, +}; + +enum i2s_fifo_th { + I2S_FIFO_TH_NONE, + I2S_FIFO_TH_ONE_QUARTER, + I2S_FIFO_TH_HALF, + I2S_FIFO_TH_THREE_QUARTER, + I2S_FIFO_TH_FULL, +}; + +enum i2s_std { + I2S_STD_I2S, + I2S_STD_LEFT_J, + I2S_STD_RIGHT_J, + I2S_STD_DSP, +}; + +enum i2s_datlen { + I2S_I2SMOD_DATLEN_16, + I2S_I2SMOD_DATLEN_24, + I2S_I2SMOD_DATLEN_32, +}; + +#define STM32_I2S_DAI_NAME_SIZE 20 +#define STM32_I2S_FIFO_SIZE 16 + +#define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER) +#define STM32_I2S_IS_SLAVE(x) ((x)->ms_flg == I2S_MS_SLAVE) + +/** + * @regmap_conf: I2S register map configuration pointer + * @egmap: I2S register map pointer + * @pdev: device data pointer + * @dai_drv: DAI driver pointer + * @dma_data_tx: dma configuration data for tx channel + * @dma_data_rx: dma configuration data for tx channel + * @substream: PCM substream data pointer + * @i2sclk: kernel clock feeding the I2S clock generator + * @pclk: peripheral clock driving bus interface + * @x8kclk: I2S parent clock for sampling frequencies multiple of 8kHz + * @x11kclk: I2S parent clock for sampling frequencies multiple of 11kHz + * @base: mmio register base virtual address + * @phys_addr: I2S registers physical base address + * @lock_fd: lock to manage race conditions in full duplex mode + * @dais_name: DAI name + * @mclk_rate: master clock frequency (Hz) + * @fmt: DAI protocol + * @refcount: keep count of opened streams on I2S + * @ms_flg: master mode flag. + */ +struct stm32_i2s_data { + const struct regmap_config *regmap_conf; + struct regmap *regmap; + struct platform_device *pdev; + struct snd_soc_dai_driver *dai_drv; + struct snd_dmaengine_dai_dma_data dma_data_tx; + struct snd_dmaengine_dai_dma_data dma_data_rx; + struct snd_pcm_substream *substream; + struct clk *i2sclk; + struct clk *pclk; + struct clk *x8kclk; + struct clk *x11kclk; + void __iomem *base; + dma_addr_t phys_addr; + spinlock_t lock_fd; /* Manage race conditions for full duplex */ + char dais_name[STM32_I2S_DAI_NAME_SIZE]; + unsigned int mclk_rate; + unsigned int fmt; + int refcount; + int ms_flg; +}; + +static irqreturn_t stm32_i2s_isr(int irq, void *devid) +{ + struct stm32_i2s_data *i2s = (struct stm32_i2s_data *)devid; + struct platform_device *pdev = i2s->pdev; + u32 sr, ier; + unsigned long flags; + int err = 0; + + regmap_read(i2s->regmap, STM32_I2S_SR_REG, &sr); + regmap_read(i2s->regmap, STM32_I2S_IER_REG, &ier); + + flags = sr & ier; + if (!flags) { + dev_dbg(&pdev->dev, "Spurious IRQ sr=0x%08x, ier=0x%08x\n", + sr, ier); + return IRQ_NONE; + } + + regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG, + I2S_IFCR_MASK, flags); + + if (flags & I2S_SR_OVR) { + dev_dbg(&pdev->dev, "Overrun\n"); + err = 1; + } + + if (flags & I2S_SR_UDR) { + dev_dbg(&pdev->dev, "Underrun\n"); + err = 1; + } + + if (flags & I2S_SR_TIFRE) + dev_dbg(&pdev->dev, "Frame error\n"); + + if (err) + snd_pcm_stop_xrun(i2s->substream); + + return IRQ_HANDLED; +} + +static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_I2S_CR1_REG: + case STM32_I2S_CFG1_REG: + case STM32_I2S_CFG2_REG: + case STM32_I2S_IER_REG: + case STM32_I2S_SR_REG: + case STM32_I2S_TXDR_REG: + case STM32_I2S_RXDR_REG: + case STM32_I2S_CGFR_REG: + return true; + default: + return false; + } +} + +static bool stm32_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_I2S_TXDR_REG: + case STM32_I2S_RXDR_REG: + return true; + default: + return false; + } +} + +static bool stm32_i2s_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_I2S_CR1_REG: + case STM32_I2S_CFG1_REG: + case STM32_I2S_CFG2_REG: + case STM32_I2S_IER_REG: + case STM32_I2S_IFCR_REG: + case STM32_I2S_TXDR_REG: + case STM32_I2S_CGFR_REG: + return true; + default: + return false; + } +} + +static int stm32_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + u32 cgfr; + u32 cgfr_mask = I2S_CGFR_I2SSTD_MASK | I2S_CGFR_CKPOL | + I2S_CGFR_WSINV | I2S_CGFR_I2SCFG_MASK; + + dev_dbg(cpu_dai->dev, "fmt %x\n", fmt); + + /* + * winv = 0 : default behavior (high/low) for all standards + * ckpol = 0 for all standards. + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_I2S); + break; + case SND_SOC_DAIFMT_MSB: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_LEFT_J); + break; + case SND_SOC_DAIFMT_LSB: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_RIGHT_J); + break; + case SND_SOC_DAIFMT_DSP_A: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_DSP); + break; + /* DSP_B not mapped on I2S PCM long format. 1 bit offset does not fit */ + default: + dev_err(cpu_dai->dev, "Unsupported protocol %#x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + /* DAI clock strobing */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + cgfr |= I2S_CGFR_CKPOL; + break; + case SND_SOC_DAIFMT_NB_IF: + cgfr |= I2S_CGFR_WSINV; + break; + case SND_SOC_DAIFMT_IB_IF: + cgfr |= I2S_CGFR_CKPOL; + cgfr |= I2S_CGFR_WSINV; + break; + default: + dev_err(cpu_dai->dev, "Unsupported strobing %#x\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->ms_flg = I2S_MS_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->ms_flg = I2S_MS_MASTER; + break; + default: + dev_err(cpu_dai->dev, "Unsupported mode %#x\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + i2s->fmt = fmt; + return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + cgfr_mask, cgfr); +} + +static int stm32_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + dev_dbg(cpu_dai->dev, "I2S MCLK frequency is %uHz\n", freq); + + if ((dir == SND_SOC_CLOCK_OUT) && STM32_I2S_IS_MASTER(i2s)) { + i2s->mclk_rate = freq; + + /* Enable master clock if master mode and mclk-fs are set */ + return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + I2S_CGFR_MCKOE, I2S_CGFR_MCKOE); + } + + return 0; +} + +static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *params) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long i2s_clock_rate; + unsigned int tmp, div, real_div, nb_bits, frame_len; + unsigned int rate = params_rate(params); + int ret; + u32 cgfr, cgfr_mask; + bool odd; + + if (!(rate % 11025)) + clk_set_parent(i2s->i2sclk, i2s->x11kclk); + else + clk_set_parent(i2s->i2sclk, i2s->x8kclk); + i2s_clock_rate = clk_get_rate(i2s->i2sclk); + + /* + * mckl = mclk_ratio x ws + * i2s mode : mclk_ratio = 256 + * dsp mode : mclk_ratio = 128 + * + * mclk on + * i2s mode : div = i2s_clk / (mclk_ratio * ws) + * dsp mode : div = i2s_clk / (mclk_ratio * ws) + * mclk off + * i2s mode : div = i2s_clk / (nb_bits x ws) + * dsp mode : div = i2s_clk / (nb_bits x ws) + */ + if (i2s->mclk_rate) { + tmp = DIV_ROUND_CLOSEST(i2s_clock_rate, i2s->mclk_rate); + } else { + frame_len = 32; + if ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_DSP_A) + frame_len = 16; + + /* master clock not enabled */ + ret = regmap_read(i2s->regmap, STM32_I2S_CGFR_REG, &cgfr); + if (ret < 0) + return ret; + + nb_bits = frame_len * ((cgfr & I2S_CGFR_CHLEN) + 1); + tmp = DIV_ROUND_CLOSEST(i2s_clock_rate, (nb_bits * rate)); + } + + /* Check the parity of the divider */ + odd = tmp & 0x1; + + /* Compute the div prescaler */ + div = tmp >> 1; + + cgfr = I2S_CGFR_I2SDIV_SET(div) | (odd << I2S_CGFR_ODD_SHIFT); + cgfr_mask = I2S_CGFR_I2SDIV_MASK | I2S_CGFR_ODD; + + real_div = ((2 * div) + odd); + dev_dbg(cpu_dai->dev, "I2S clk: %ld, SCLK: %d\n", + i2s_clock_rate, rate); + dev_dbg(cpu_dai->dev, "Divider: 2*%d(div)+%d(odd) = %d\n", + div, odd, real_div); + + if (((div == 1) && odd) || (div > I2S_CGFR_I2SDIV_MAX)) { + dev_err(cpu_dai->dev, "Wrong divider setting\n"); + return -EINVAL; + } + + if (!div && !odd) + dev_warn(cpu_dai->dev, "real divider forced to 1\n"); + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + cgfr_mask, cgfr); + if (ret < 0) + return ret; + + /* Set bitclock and frameclock to their inactive state */ + return regmap_update_bits(i2s->regmap, STM32_I2S_CFG2_REG, + I2S_CFG2_AFCNTR, I2S_CFG2_AFCNTR); +} + +static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *params, + struct snd_pcm_substream *substream) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + int format = params_width(params); + u32 cfgr, cfgr_mask, cfg1; + unsigned int fthlv; + int ret; + + if ((params_channels(params) == 1) && + ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A)) { + dev_err(cpu_dai->dev, "Mono mode supported only by DSP_A\n"); + return -EINVAL; + } + + switch (format) { + case 16: + cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_16); + cfgr_mask = I2S_CGFR_DATLEN_MASK | I2S_CGFR_CHLEN; + break; + case 32: + cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_32) | + I2S_CGFR_CHLEN; + cfgr_mask = I2S_CGFR_DATLEN_MASK | I2S_CGFR_CHLEN; + break; + default: + dev_err(cpu_dai->dev, "Unexpected format %d", format); + return -EINVAL; + } + + if (STM32_I2S_IS_SLAVE(i2s)) { + cfgr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_FD_SLAVE); + + /* As data length is either 16 or 32 bits, fixch always set */ + cfgr |= I2S_CGFR_FIXCH; + cfgr_mask |= I2S_CGFR_FIXCH; + } else { + cfgr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_FD_MASTER); + } + cfgr_mask |= I2S_CGFR_I2SCFG_MASK; + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + cfgr_mask, cfgr); + if (ret < 0) + return ret; + + fthlv = STM32_I2S_FIFO_SIZE * I2S_FIFO_TH_ONE_QUARTER / 4; + cfg1 = I2S_CFG1_FTHVL_SET(fthlv - 1); + + return regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, + I2S_CFG1_FTHVL_MASK, cfg1); +} + +static int stm32_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + i2s->substream = substream; + + spin_lock(&i2s->lock_fd); + i2s->refcount++; + spin_unlock(&i2s->lock_fd); + + return regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG, + I2S_IFCR_MASK, I2S_IFCR_MASK); +} + +static int stm32_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + ret = stm32_i2s_configure(cpu_dai, params, substream); + if (ret < 0) { + dev_err(cpu_dai->dev, "Configuration returned error %d\n", ret); + return ret; + } + + if (STM32_I2S_IS_MASTER(i2s)) + ret = stm32_i2s_configure_clock(cpu_dai, params); + + return ret; +} + +static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + bool playback_flg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u32 cfg1_mask, ier; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* Enable i2s */ + dev_dbg(cpu_dai->dev, "start I2S\n"); + + cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; + regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, + cfg1_mask, cfg1_mask); + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, + I2S_CR1_SPE, I2S_CR1_SPE); + if (ret < 0) { + dev_err(cpu_dai->dev, "Error %d enabling I2S\n", ret); + return ret; + } + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, + I2S_CR1_CSTART, I2S_CR1_CSTART); + if (ret < 0) { + dev_err(cpu_dai->dev, "Error %d starting I2S\n", ret); + return ret; + } + + regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG, + I2S_IFCR_MASK, I2S_IFCR_MASK); + + if (playback_flg) { + ier = I2S_IER_UDRIE; + } else { + ier = I2S_IER_OVRIE; + + spin_lock(&i2s->lock_fd); + if (i2s->refcount == 1) + /* dummy write to trigger capture */ + regmap_write(i2s->regmap, + STM32_I2S_TXDR_REG, 0); + spin_unlock(&i2s->lock_fd); + } + + if (STM32_I2S_IS_SLAVE(i2s)) + ier |= I2S_IER_TIFREIE; + + regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, ier, ier); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (playback_flg) + regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, + I2S_IER_UDRIE, + (unsigned int)~I2S_IER_UDRIE); + else + regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, + I2S_IER_OVRIE, + (unsigned int)~I2S_IER_OVRIE); + + spin_lock(&i2s->lock_fd); + i2s->refcount--; + if (i2s->refcount) { + spin_unlock(&i2s->lock_fd); + break; + } + spin_unlock(&i2s->lock_fd); + + dev_dbg(cpu_dai->dev, "stop I2S\n"); + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, + I2S_CR1_SPE, 0); + if (ret < 0) { + dev_err(cpu_dai->dev, "Error %d disabling I2S\n", ret); + return ret; + } + + cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; + regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, + cfg1_mask, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void stm32_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + i2s->substream = NULL; + + regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + I2S_CGFR_MCKOE, (unsigned int)~I2S_CGFR_MCKOE); +} + +static int stm32_i2s_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = dev_get_drvdata(cpu_dai->dev); + struct snd_dmaengine_dai_dma_data *dma_data_tx = &i2s->dma_data_tx; + struct snd_dmaengine_dai_dma_data *dma_data_rx = &i2s->dma_data_rx; + + /* Buswidth will be set by framework */ + dma_data_tx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + dma_data_tx->addr = (dma_addr_t)(i2s->phys_addr) + STM32_I2S_TXDR_REG; + dma_data_tx->maxburst = 1; + dma_data_rx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + dma_data_rx->addr = (dma_addr_t)(i2s->phys_addr) + STM32_I2S_RXDR_REG; + dma_data_rx->maxburst = 1; + + snd_soc_dai_init_dma_data(cpu_dai, dma_data_tx, dma_data_rx); + + return 0; +} + +static const struct regmap_config stm32_h7_i2s_regmap_conf = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM32_I2S_CGFR_REG, + .readable_reg = stm32_i2s_readable_reg, + .volatile_reg = stm32_i2s_volatile_reg, + .writeable_reg = stm32_i2s_writeable_reg, + .fast_io = true, +}; + +static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { + .set_sysclk = stm32_i2s_set_sysclk, + .set_fmt = stm32_i2s_set_dai_fmt, + .startup = stm32_i2s_startup, + .hw_params = stm32_i2s_hw_params, + .trigger = stm32_i2s_trigger, + .shutdown = stm32_i2s_shutdown, +}; + +static const struct snd_pcm_hardware stm32_i2s_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_max = 2048, + .periods_min = 2, + .periods_max = 8, +}; + +static const struct snd_dmaengine_pcm_config stm32_i2s_pcm_config = { + .pcm_hardware = &stm32_i2s_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prealloc_buffer_size = PAGE_SIZE * 8, +}; + +static const struct snd_soc_component_driver stm32_i2s_component = { + .name = "stm32-i2s", +}; + +static void stm32_i2s_dai_init(struct snd_soc_pcm_stream *stream, + char *stream_name) +{ + stream->stream_name = stream_name; + stream->channels_min = 1; + stream->channels_max = 2; + stream->rates = SNDRV_PCM_RATE_8000_192000; + stream->formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE; +} + +static int stm32_i2s_dais_init(struct platform_device *pdev, + struct stm32_i2s_data *i2s) +{ + struct snd_soc_dai_driver *dai_ptr; + + dai_ptr = devm_kzalloc(&pdev->dev, sizeof(struct snd_soc_dai_driver), + GFP_KERNEL); + if (!dai_ptr) + return -ENOMEM; + + snprintf(i2s->dais_name, STM32_I2S_DAI_NAME_SIZE, + "%s", dev_name(&pdev->dev)); + + dai_ptr->probe = stm32_i2s_dai_probe; + dai_ptr->ops = &stm32_i2s_pcm_dai_ops; + dai_ptr->name = i2s->dais_name; + dai_ptr->id = 1; + stm32_i2s_dai_init(&dai_ptr->playback, "playback"); + stm32_i2s_dai_init(&dai_ptr->capture, "capture"); + i2s->dai_drv = dai_ptr; + + return 0; +} + +static const struct of_device_id stm32_i2s_ids[] = { + { + .compatible = "st,stm32h7-i2s", + .data = &stm32_h7_i2s_regmap_conf + }, + {}, +}; + +static int stm32_i2s_parse_dt(struct platform_device *pdev, + struct stm32_i2s_data *i2s) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; + struct reset_control *rst; + struct resource *res; + int irq, ret; + + if (!np) + return -ENODEV; + + of_id = of_match_device(stm32_i2s_ids, &pdev->dev); + if (of_id) + i2s->regmap_conf = (const struct regmap_config *)of_id->data; + else + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(i2s->base)) + return PTR_ERR(i2s->base); + + i2s->phys_addr = res->start; + + /* Get clocks */ + i2s->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(i2s->pclk)) { + dev_err(&pdev->dev, "Could not get pclk\n"); + return PTR_ERR(i2s->pclk); + } + + i2s->i2sclk = devm_clk_get(&pdev->dev, "i2sclk"); + if (IS_ERR(i2s->i2sclk)) { + dev_err(&pdev->dev, "Could not get i2sclk\n"); + return PTR_ERR(i2s->i2sclk); + } + + i2s->x8kclk = devm_clk_get(&pdev->dev, "x8k"); + if (IS_ERR(i2s->x8kclk)) { + dev_err(&pdev->dev, "missing x8k parent clock\n"); + return PTR_ERR(i2s->x8kclk); + } + + i2s->x11kclk = devm_clk_get(&pdev->dev, "x11k"); + if (IS_ERR(i2s->x11kclk)) { + dev_err(&pdev->dev, "missing x11k parent clock\n"); + return PTR_ERR(i2s->x11kclk); + } + + /* Get irqs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return -ENOENT; + } + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, IRQF_ONESHOT, + dev_name(&pdev->dev), i2s); + if (ret) { + dev_err(&pdev->dev, "irq request returned %d\n", ret); + return ret; + } + + /* Reset */ + rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + return 0; +} + +static int stm32_i2s_probe(struct platform_device *pdev) +{ + struct stm32_i2s_data *i2s; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + ret = stm32_i2s_parse_dt(pdev, i2s); + if (ret) + return ret; + + i2s->pdev = pdev; + i2s->ms_flg = I2S_MS_NOT_SET; + spin_lock_init(&i2s->lock_fd); + platform_set_drvdata(pdev, i2s); + + ret = stm32_i2s_dais_init(pdev, i2s); + if (ret) + return ret; + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->base, + i2s->regmap_conf); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(i2s->regmap); + } + + ret = clk_prepare_enable(i2s->pclk); + if (ret) { + dev_err(&pdev->dev, "Enable pclk failed: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(i2s->i2sclk); + if (ret) { + dev_err(&pdev->dev, "Enable i2sclk failed: %d\n", ret); + goto err_pclk_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &stm32_i2s_component, + i2s->dai_drv, 1); + if (ret) + goto err_clocks_disable; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &stm32_i2s_pcm_config, 0); + if (ret) + goto err_clocks_disable; + + /* Set SPI/I2S in i2s mode */ + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + I2S_CGFR_I2SMOD, I2S_CGFR_I2SMOD); + if (ret) + goto err_clocks_disable; + + return ret; + +err_clocks_disable: + clk_disable_unprepare(i2s->i2sclk); +err_pclk_disable: + clk_disable_unprepare(i2s->pclk); + + return ret; +} + +static int stm32_i2s_remove(struct platform_device *pdev) +{ + struct stm32_i2s_data *i2s = platform_get_drvdata(pdev); + + clk_disable_unprepare(i2s->i2sclk); + clk_disable_unprepare(i2s->pclk); + + return 0; +} + +MODULE_DEVICE_TABLE(of, stm32_i2s_ids); + +static struct platform_driver stm32_i2s_driver = { + .driver = { + .name = "st,stm32-i2s", + .of_match_table = stm32_i2s_ids, + }, + .probe = stm32_i2s_probe, + .remove = stm32_i2s_remove, +}; + +module_platform_driver(stm32_i2s_driver); + +MODULE_DESCRIPTION("STM32 Soc i2s Interface"); +MODULE_AUTHOR("Olivier Moysan, "); +MODULE_ALIAS("platform:stm32-i2s"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c new file mode 100644 index 000000000..540c4a004 --- /dev/null +++ b/sound/soc/stm/stm32_sai.c @@ -0,0 +1,211 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "stm32_sai.h" + +static const struct stm32_sai_conf stm32_sai_conf_f4 = { + .version = SAI_STM32F4, + .has_spdif = false, +}; + +static const struct stm32_sai_conf stm32_sai_conf_h7 = { + .version = SAI_STM32H7, + .has_spdif = true, +}; + +static const struct of_device_id stm32_sai_ids[] = { + { .compatible = "st,stm32f4-sai", .data = (void *)&stm32_sai_conf_f4 }, + { .compatible = "st,stm32h7-sai", .data = (void *)&stm32_sai_conf_h7 }, + {} +}; + +static int stm32_sai_sync_conf_client(struct stm32_sai_data *sai, int synci) +{ + int ret; + + /* Enable peripheral clock to allow GCR register access */ + ret = clk_prepare_enable(sai->pclk); + if (ret) { + dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + writel_relaxed(FIELD_PREP(SAI_GCR_SYNCIN_MASK, (synci - 1)), sai->base); + + clk_disable_unprepare(sai->pclk); + + return 0; +} + +static int stm32_sai_sync_conf_provider(struct stm32_sai_data *sai, int synco) +{ + u32 prev_synco; + int ret; + + /* Enable peripheral clock to allow GCR register access */ + ret = clk_prepare_enable(sai->pclk); + if (ret) { + dev_err(&sai->pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + dev_dbg(&sai->pdev->dev, "Set %s%s as synchro provider\n", + sai->pdev->dev.of_node->name, + synco == STM_SAI_SYNC_OUT_A ? "A" : "B"); + + prev_synco = FIELD_GET(SAI_GCR_SYNCOUT_MASK, readl_relaxed(sai->base)); + if (prev_synco != STM_SAI_SYNC_OUT_NONE && synco != prev_synco) { + dev_err(&sai->pdev->dev, "%s%s already set as sync provider\n", + sai->pdev->dev.of_node->name, + prev_synco == STM_SAI_SYNC_OUT_A ? "A" : "B"); + clk_disable_unprepare(sai->pclk); + return -EINVAL; + } + + writel_relaxed(FIELD_PREP(SAI_GCR_SYNCOUT_MASK, synco), sai->base); + + clk_disable_unprepare(sai->pclk); + + return 0; +} + +static int stm32_sai_set_sync(struct stm32_sai_data *sai_client, + struct device_node *np_provider, + int synco, int synci) +{ + struct platform_device *pdev = of_find_device_by_node(np_provider); + struct stm32_sai_data *sai_provider; + int ret; + + if (!pdev) { + dev_err(&sai_client->pdev->dev, + "Device not found for node %s\n", np_provider->name); + return -ENODEV; + } + + sai_provider = platform_get_drvdata(pdev); + if (!sai_provider) { + dev_err(&sai_client->pdev->dev, + "SAI sync provider data not found\n"); + ret = -EINVAL; + goto out_put_dev; + } + + /* Configure sync client */ + ret = stm32_sai_sync_conf_client(sai_client, synci); + if (ret < 0) + goto out_put_dev; + + /* Configure sync provider */ + ret = stm32_sai_sync_conf_provider(sai_provider, synco); + +out_put_dev: + put_device(&pdev->dev); + return ret; +} + +static int stm32_sai_probe(struct platform_device *pdev) +{ + struct stm32_sai_data *sai; + struct reset_control *rst; + struct resource *res; + const struct of_device_id *of_id; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sai->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(sai->base)) + return PTR_ERR(sai->base); + + of_id = of_match_device(stm32_sai_ids, &pdev->dev); + if (of_id) + sai->conf = (struct stm32_sai_conf *)of_id->data; + else + return -EINVAL; + + if (!STM_SAI_IS_F4(sai)) { + sai->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(sai->pclk)) { + dev_err(&pdev->dev, "missing bus clock pclk\n"); + return PTR_ERR(sai->pclk); + } + } + + sai->clk_x8k = devm_clk_get(&pdev->dev, "x8k"); + if (IS_ERR(sai->clk_x8k)) { + dev_err(&pdev->dev, "missing x8k parent clock\n"); + return PTR_ERR(sai->clk_x8k); + } + + sai->clk_x11k = devm_clk_get(&pdev->dev, "x11k"); + if (IS_ERR(sai->clk_x11k)) { + dev_err(&pdev->dev, "missing x11k parent clock\n"); + return PTR_ERR(sai->clk_x11k); + } + + /* init irqs */ + sai->irq = platform_get_irq(pdev, 0); + if (sai->irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return sai->irq; + } + + /* reset */ + rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + sai->pdev = pdev; + sai->set_sync = &stm32_sai_set_sync; + platform_set_drvdata(pdev, sai); + + return devm_of_platform_populate(&pdev->dev); +} + +MODULE_DEVICE_TABLE(of, stm32_sai_ids); + +static struct platform_driver stm32_sai_driver = { + .driver = { + .name = "st,stm32-sai", + .of_match_table = stm32_sai_ids, + }, + .probe = stm32_sai_probe, +}; + +module_platform_driver(stm32_sai_driver); + +MODULE_DESCRIPTION("STM32 Soc SAI Interface"); +MODULE_AUTHOR("Olivier Moysan "); +MODULE_ALIAS("platform:st,stm32-sai"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h new file mode 100644 index 000000000..f25422174 --- /dev/null +++ b/sound/soc/stm/stm32_sai.h @@ -0,0 +1,279 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include + +/******************** SAI Register Map **************************************/ + +/* Global configuration register */ +#define STM_SAI_GCR 0x00 + +/* Sub-block A&B registers offsets, relative to A&B sub-block addresses */ +#define STM_SAI_CR1_REGX 0x00 /* A offset: 0x04. B offset: 0x24 */ +#define STM_SAI_CR2_REGX 0x04 +#define STM_SAI_FRCR_REGX 0x08 +#define STM_SAI_SLOTR_REGX 0x0C +#define STM_SAI_IMR_REGX 0x10 +#define STM_SAI_SR_REGX 0x14 +#define STM_SAI_CLRFR_REGX 0x18 +#define STM_SAI_DR_REGX 0x1C + +/* Sub-block A registers, relative to sub-block A address */ +#define STM_SAI_PDMCR_REGX 0x40 +#define STM_SAI_PDMLY_REGX 0x44 + +/******************** Bit definition for SAI_GCR register *******************/ +#define SAI_GCR_SYNCIN_SHIFT 0 +#define SAI_GCR_SYNCIN_WDTH 2 +#define SAI_GCR_SYNCIN_MASK GENMASK(1, SAI_GCR_SYNCIN_SHIFT) +#define SAI_GCR_SYNCIN_MAX FIELD_GET(SAI_GCR_SYNCIN_MASK,\ + SAI_GCR_SYNCIN_MASK) + +#define SAI_GCR_SYNCOUT_SHIFT 4 +#define SAI_GCR_SYNCOUT_MASK GENMASK(5, SAI_GCR_SYNCOUT_SHIFT) + +/******************* Bit definition for SAI_XCR1 register *******************/ +#define SAI_XCR1_RX_TX_SHIFT 0 +#define SAI_XCR1_RX_TX BIT(SAI_XCR1_RX_TX_SHIFT) +#define SAI_XCR1_SLAVE_SHIFT 1 +#define SAI_XCR1_SLAVE BIT(SAI_XCR1_SLAVE_SHIFT) + +#define SAI_XCR1_PRTCFG_SHIFT 2 +#define SAI_XCR1_PRTCFG_MASK GENMASK(3, SAI_XCR1_PRTCFG_SHIFT) +#define SAI_XCR1_PRTCFG_SET(x) ((x) << SAI_XCR1_PRTCFG_SHIFT) + +#define SAI_XCR1_DS_SHIFT 5 +#define SAI_XCR1_DS_MASK GENMASK(7, SAI_XCR1_DS_SHIFT) +#define SAI_XCR1_DS_SET(x) ((x) << SAI_XCR1_DS_SHIFT) + +#define SAI_XCR1_LSBFIRST_SHIFT 8 +#define SAI_XCR1_LSBFIRST BIT(SAI_XCR1_LSBFIRST_SHIFT) +#define SAI_XCR1_CKSTR_SHIFT 9 +#define SAI_XCR1_CKSTR BIT(SAI_XCR1_CKSTR_SHIFT) + +#define SAI_XCR1_SYNCEN_SHIFT 10 +#define SAI_XCR1_SYNCEN_MASK GENMASK(11, SAI_XCR1_SYNCEN_SHIFT) +#define SAI_XCR1_SYNCEN_SET(x) ((x) << SAI_XCR1_SYNCEN_SHIFT) + +#define SAI_XCR1_MONO_SHIFT 12 +#define SAI_XCR1_MONO BIT(SAI_XCR1_MONO_SHIFT) +#define SAI_XCR1_OUTDRIV_SHIFT 13 +#define SAI_XCR1_OUTDRIV BIT(SAI_XCR1_OUTDRIV_SHIFT) +#define SAI_XCR1_SAIEN_SHIFT 16 +#define SAI_XCR1_SAIEN BIT(SAI_XCR1_SAIEN_SHIFT) +#define SAI_XCR1_DMAEN_SHIFT 17 +#define SAI_XCR1_DMAEN BIT(SAI_XCR1_DMAEN_SHIFT) +#define SAI_XCR1_NODIV_SHIFT 19 +#define SAI_XCR1_NODIV BIT(SAI_XCR1_NODIV_SHIFT) + +#define SAI_XCR1_MCKDIV_SHIFT 20 +#define SAI_XCR1_MCKDIV_WIDTH(x) (((x) == SAI_STM32F4) ? 4 : 6) +#define SAI_XCR1_MCKDIV_MASK(x) GENMASK((SAI_XCR1_MCKDIV_SHIFT + (x) - 1),\ + SAI_XCR1_MCKDIV_SHIFT) +#define SAI_XCR1_MCKDIV_SET(x) ((x) << SAI_XCR1_MCKDIV_SHIFT) +#define SAI_XCR1_MCKDIV_MAX(x) ((1 << SAI_XCR1_MCKDIV_WIDTH(x)) - 1) + +#define SAI_XCR1_OSR_SHIFT 26 +#define SAI_XCR1_OSR BIT(SAI_XCR1_OSR_SHIFT) + +/******************* Bit definition for SAI_XCR2 register *******************/ +#define SAI_XCR2_FTH_SHIFT 0 +#define SAI_XCR2_FTH_MASK GENMASK(2, SAI_XCR2_FTH_SHIFT) +#define SAI_XCR2_FTH_SET(x) ((x) << SAI_XCR2_FTH_SHIFT) + +#define SAI_XCR2_FFLUSH_SHIFT 3 +#define SAI_XCR2_FFLUSH BIT(SAI_XCR2_FFLUSH_SHIFT) +#define SAI_XCR2_TRIS_SHIFT 4 +#define SAI_XCR2_TRIS BIT(SAI_XCR2_TRIS_SHIFT) +#define SAI_XCR2_MUTE_SHIFT 5 +#define SAI_XCR2_MUTE BIT(SAI_XCR2_MUTE_SHIFT) +#define SAI_XCR2_MUTEVAL_SHIFT 6 +#define SAI_XCR2_MUTEVAL BIT(SAI_XCR2_MUTEVAL_SHIFT) + +#define SAI_XCR2_MUTECNT_SHIFT 7 +#define SAI_XCR2_MUTECNT_MASK GENMASK(12, SAI_XCR2_MUTECNT_SHIFT) +#define SAI_XCR2_MUTECNT_SET(x) ((x) << SAI_XCR2_MUTECNT_SHIFT) + +#define SAI_XCR2_CPL_SHIFT 13 +#define SAI_XCR2_CPL BIT(SAI_XCR2_CPL_SHIFT) + +#define SAI_XCR2_COMP_SHIFT 14 +#define SAI_XCR2_COMP_MASK GENMASK(15, SAI_XCR2_COMP_SHIFT) +#define SAI_XCR2_COMP_SET(x) ((x) << SAI_XCR2_COMP_SHIFT) + +/****************** Bit definition for SAI_XFRCR register *******************/ +#define SAI_XFRCR_FRL_SHIFT 0 +#define SAI_XFRCR_FRL_MASK GENMASK(7, SAI_XFRCR_FRL_SHIFT) +#define SAI_XFRCR_FRL_SET(x) ((x) << SAI_XFRCR_FRL_SHIFT) + +#define SAI_XFRCR_FSALL_SHIFT 8 +#define SAI_XFRCR_FSALL_MASK GENMASK(14, SAI_XFRCR_FSALL_SHIFT) +#define SAI_XFRCR_FSALL_SET(x) ((x) << SAI_XFRCR_FSALL_SHIFT) + +#define SAI_XFRCR_FSDEF_SHIFT 16 +#define SAI_XFRCR_FSDEF BIT(SAI_XFRCR_FSDEF_SHIFT) +#define SAI_XFRCR_FSPOL_SHIFT 17 +#define SAI_XFRCR_FSPOL BIT(SAI_XFRCR_FSPOL_SHIFT) +#define SAI_XFRCR_FSOFF_SHIFT 18 +#define SAI_XFRCR_FSOFF BIT(SAI_XFRCR_FSOFF_SHIFT) + +/****************** Bit definition for SAI_XSLOTR register ******************/ +#define SAI_XSLOTR_FBOFF_SHIFT 0 +#define SAI_XSLOTR_FBOFF_MASK GENMASK(4, SAI_XSLOTR_FBOFF_SHIFT) +#define SAI_XSLOTR_FBOFF_SET(x) ((x) << SAI_XSLOTR_FBOFF_SHIFT) + +#define SAI_XSLOTR_SLOTSZ_SHIFT 6 +#define SAI_XSLOTR_SLOTSZ_MASK GENMASK(7, SAI_XSLOTR_SLOTSZ_SHIFT) +#define SAI_XSLOTR_SLOTSZ_SET(x) ((x) << SAI_XSLOTR_SLOTSZ_SHIFT) + +#define SAI_XSLOTR_NBSLOT_SHIFT 8 +#define SAI_XSLOTR_NBSLOT_MASK GENMASK(11, SAI_XSLOTR_NBSLOT_SHIFT) +#define SAI_XSLOTR_NBSLOT_SET(x) ((x) << SAI_XSLOTR_NBSLOT_SHIFT) + +#define SAI_XSLOTR_SLOTEN_SHIFT 16 +#define SAI_XSLOTR_SLOTEN_WIDTH 16 +#define SAI_XSLOTR_SLOTEN_MASK GENMASK(31, SAI_XSLOTR_SLOTEN_SHIFT) +#define SAI_XSLOTR_SLOTEN_SET(x) ((x) << SAI_XSLOTR_SLOTEN_SHIFT) + +/******************* Bit definition for SAI_XIMR register *******************/ +#define SAI_XIMR_OVRUDRIE BIT(0) +#define SAI_XIMR_MUTEDETIE BIT(1) +#define SAI_XIMR_WCKCFGIE BIT(2) +#define SAI_XIMR_FREQIE BIT(3) +#define SAI_XIMR_CNRDYIE BIT(4) +#define SAI_XIMR_AFSDETIE BIT(5) +#define SAI_XIMR_LFSDETIE BIT(6) + +#define SAI_XIMR_SHIFT 0 +#define SAI_XIMR_MASK GENMASK(6, SAI_XIMR_SHIFT) + +/******************** Bit definition for SAI_XSR register *******************/ +#define SAI_XSR_OVRUDR BIT(0) +#define SAI_XSR_MUTEDET BIT(1) +#define SAI_XSR_WCKCFG BIT(2) +#define SAI_XSR_FREQ BIT(3) +#define SAI_XSR_CNRDY BIT(4) +#define SAI_XSR_AFSDET BIT(5) +#define SAI_XSR_LFSDET BIT(6) + +#define SAI_XSR_SHIFT 0 +#define SAI_XSR_MASK GENMASK(6, SAI_XSR_SHIFT) + +/****************** Bit definition for SAI_XCLRFR register ******************/ +#define SAI_XCLRFR_COVRUDR BIT(0) +#define SAI_XCLRFR_CMUTEDET BIT(1) +#define SAI_XCLRFR_CWCKCFG BIT(2) +#define SAI_XCLRFR_CFREQ BIT(3) +#define SAI_XCLRFR_CCNRDY BIT(4) +#define SAI_XCLRFR_CAFSDET BIT(5) +#define SAI_XCLRFR_CLFSDET BIT(6) + +#define SAI_XCLRFR_SHIFT 0 +#define SAI_XCLRFR_MASK GENMASK(6, SAI_XCLRFR_SHIFT) + +/****************** Bit definition for SAI_PDMCR register ******************/ +#define SAI_PDMCR_PDMEN BIT(0) + +#define SAI_PDMCR_MICNBR_SHIFT 4 +#define SAI_PDMCR_MICNBR_MASK GENMASK(5, SAI_PDMCR_MICNBR_SHIFT) +#define SAI_PDMCR_MICNBR_SET(x) ((x) << SAI_PDMCR_MICNBR_SHIFT) + +#define SAI_PDMCR_CKEN1 BIT(8) +#define SAI_PDMCR_CKEN2 BIT(9) +#define SAI_PDMCR_CKEN3 BIT(10) +#define SAI_PDMCR_CKEN4 BIT(11) + +/****************** Bit definition for (SAI_PDMDLY register ****************/ +#define SAI_PDMDLY_1L_SHIFT 0 +#define SAI_PDMDLY_1L_MASK GENMASK(2, SAI_PDMDLY_1L_SHIFT) +#define SAI_PDMDLY_1L_WIDTH 3 + +#define SAI_PDMDLY_1R_SHIFT 4 +#define SAI_PDMDLY_1R_MASK GENMASK(6, SAI_PDMDLY_1R_SHIFT) +#define SAI_PDMDLY_1R_WIDTH 3 + +#define SAI_PDMDLY_2L_SHIFT 8 +#define SAI_PDMDLY_2L_MASK GENMASK(10, SAI_PDMDLY_2L_SHIFT) +#define SAI_PDMDLY_2L_WIDTH 3 + +#define SAI_PDMDLY_2R_SHIFT 12 +#define SAI_PDMDLY_2R_MASK GENMASK(14, SAI_PDMDLY_2R_SHIFT) +#define SAI_PDMDLY_2R_WIDTH 3 + +#define SAI_PDMDLY_3L_SHIFT 16 +#define SAI_PDMDLY_3L_MASK GENMASK(18, SAI_PDMDLY_3L_SHIFT) +#define SAI_PDMDLY_3L_WIDTH 3 + +#define SAI_PDMDLY_3R_SHIFT 20 +#define SAI_PDMDLY_3R_MASK GENMASK(22, SAI_PDMDLY_3R_SHIFT) +#define SAI_PDMDLY_3R_WIDTH 3 + +#define SAI_PDMDLY_4L_SHIFT 24 +#define SAI_PDMDLY_4L_MASK GENMASK(26, SAI_PDMDLY_4L_SHIFT) +#define SAI_PDMDLY_4L_WIDTH 3 + +#define SAI_PDMDLY_4R_SHIFT 28 +#define SAI_PDMDLY_4R_MASK GENMASK(30, SAI_PDMDLY_4R_SHIFT) +#define SAI_PDMDLY_4R_WIDTH 3 + +#define STM_SAI_IS_F4(ip) ((ip)->conf->version == SAI_STM32F4) +#define STM_SAI_IS_H7(ip) ((ip)->conf->version == SAI_STM32H7) + +enum stm32_sai_syncout { + STM_SAI_SYNC_OUT_NONE, + STM_SAI_SYNC_OUT_A, + STM_SAI_SYNC_OUT_B, +}; + +enum stm32_sai_version { + SAI_STM32F4, + SAI_STM32H7 +}; + +/** + * struct stm32_sai_conf - SAI configuration + * @version: SAI version + * @has_spdif: SAI S/PDIF support flag + */ +struct stm32_sai_conf { + int version; + bool has_spdif; +}; + +/** + * struct stm32_sai_data - private data of SAI instance driver + * @pdev: device data pointer + * @base: common register bank virtual base address + * @pclk: SAI bus clock + * @clk_x8k: SAI parent clock for sampling frequencies multiple of 8kHz + * @clk_x11k: SAI parent clock for sampling frequencies multiple of 11kHz + * @version: SOC version + * @irq: SAI interrupt line + * @set_sync: pointer to synchro mode configuration callback + */ +struct stm32_sai_data { + struct platform_device *pdev; + void __iomem *base; + struct clk *pclk; + struct clk *clk_x8k; + struct clk *clk_x11k; + struct stm32_sai_conf *conf; + int irq; + int (*set_sync)(struct stm32_sai_data *sai, + struct device_node *np_provider, int synco, int synci); +}; diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c new file mode 100644 index 000000000..6c2e69e32 --- /dev/null +++ b/sound/soc/stm/stm32_sai_sub.c @@ -0,0 +1,1301 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "stm32_sai.h" + +#define SAI_FREE_PROTOCOL 0x0 +#define SAI_SPDIF_PROTOCOL 0x1 + +#define SAI_SLOT_SIZE_AUTO 0x0 +#define SAI_SLOT_SIZE_16 0x1 +#define SAI_SLOT_SIZE_32 0x2 + +#define SAI_DATASIZE_8 0x2 +#define SAI_DATASIZE_10 0x3 +#define SAI_DATASIZE_16 0x4 +#define SAI_DATASIZE_20 0x5 +#define SAI_DATASIZE_24 0x6 +#define SAI_DATASIZE_32 0x7 + +#define STM_SAI_FIFO_SIZE 8 +#define STM_SAI_DAI_NAME_SIZE 15 + +#define STM_SAI_IS_PLAYBACK(ip) ((ip)->dir == SNDRV_PCM_STREAM_PLAYBACK) +#define STM_SAI_IS_CAPTURE(ip) ((ip)->dir == SNDRV_PCM_STREAM_CAPTURE) + +#define STM_SAI_A_ID 0x0 +#define STM_SAI_B_ID 0x1 + +#define STM_SAI_IS_SUB_A(x) ((x)->id == STM_SAI_A_ID) +#define STM_SAI_IS_SUB_B(x) ((x)->id == STM_SAI_B_ID) +#define STM_SAI_BLOCK_NAME(x) (((x)->id == STM_SAI_A_ID) ? "A" : "B") + +#define SAI_SYNC_NONE 0x0 +#define SAI_SYNC_INTERNAL 0x1 +#define SAI_SYNC_EXTERNAL 0x2 + +#define STM_SAI_PROTOCOL_IS_SPDIF(ip) ((ip)->spdif) +#define STM_SAI_HAS_SPDIF(x) ((x)->pdata->conf->has_spdif) +#define STM_SAI_HAS_EXT_SYNC(x) (!STM_SAI_IS_F4(sai->pdata)) + +#define SAI_IEC60958_BLOCK_FRAMES 192 +#define SAI_IEC60958_STATUS_BYTES 24 + +/** + * struct stm32_sai_sub_data - private data of SAI sub block (block A or B) + * @pdev: device data pointer + * @regmap: SAI register map pointer + * @regmap_config: SAI sub block register map configuration pointer + * @dma_params: dma configuration data for rx or tx channel + * @cpu_dai_drv: DAI driver data pointer + * @cpu_dai: DAI runtime data pointer + * @substream: PCM substream data pointer + * @pdata: SAI block parent data pointer + * @np_sync_provider: synchronization provider node + * @sai_ck: kernel clock feeding the SAI clock generator + * @phys_addr: SAI registers physical base address + * @mclk_rate: SAI block master clock frequency (Hz). set at init + * @id: SAI sub block id corresponding to sub-block A or B + * @dir: SAI block direction (playback or capture). set at init + * @master: SAI block mode flag. (true=master, false=slave) set at init + * @spdif: SAI S/PDIF iec60958 mode flag. set at init + * @fmt: SAI block format. relevant only for custom protocols. set at init + * @sync: SAI block synchronization mode. (none, internal or external) + * @synco: SAI block ext sync source (provider setting). (none, sub-block A/B) + * @synci: SAI block ext sync source (client setting). (SAI sync provider index) + * @fs_length: frame synchronization length. depends on protocol settings + * @slots: rx or tx slot number + * @slot_width: rx or tx slot width in bits + * @slot_mask: rx or tx active slots mask. set at init or at runtime + * @data_size: PCM data width. corresponds to PCM substream width. + * @spdif_frm_cnt: S/PDIF playback frame counter + * @iec958: iec958 data + * @ctrl_lock: control lock + */ +struct stm32_sai_sub_data { + struct platform_device *pdev; + struct regmap *regmap; + const struct regmap_config *regmap_config; + struct snd_dmaengine_dai_dma_data dma_params; + struct snd_soc_dai_driver *cpu_dai_drv; + struct snd_soc_dai *cpu_dai; + struct snd_pcm_substream *substream; + struct stm32_sai_data *pdata; + struct device_node *np_sync_provider; + struct clk *sai_ck; + dma_addr_t phys_addr; + unsigned int mclk_rate; + unsigned int id; + int dir; + bool master; + bool spdif; + int fmt; + int sync; + int synco; + int synci; + int fs_length; + int slots; + int slot_width; + int slot_mask; + int data_size; + unsigned int spdif_frm_cnt; + struct snd_aes_iec958 iec958; + struct mutex ctrl_lock; /* protect resources accessed by controls */ +}; + +enum stm32_sai_fifo_th { + STM_SAI_FIFO_TH_EMPTY, + STM_SAI_FIFO_TH_QUARTER, + STM_SAI_FIFO_TH_HALF, + STM_SAI_FIFO_TH_3_QUARTER, + STM_SAI_FIFO_TH_FULL, +}; + +static bool stm32_sai_sub_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_CR1_REGX: + case STM_SAI_CR2_REGX: + case STM_SAI_FRCR_REGX: + case STM_SAI_SLOTR_REGX: + case STM_SAI_IMR_REGX: + case STM_SAI_SR_REGX: + case STM_SAI_CLRFR_REGX: + case STM_SAI_DR_REGX: + case STM_SAI_PDMCR_REGX: + case STM_SAI_PDMLY_REGX: + return true; + default: + return false; + } +} + +static bool stm32_sai_sub_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_DR_REGX: + return true; + default: + return false; + } +} + +static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM_SAI_CR1_REGX: + case STM_SAI_CR2_REGX: + case STM_SAI_FRCR_REGX: + case STM_SAI_SLOTR_REGX: + case STM_SAI_IMR_REGX: + case STM_SAI_SR_REGX: + case STM_SAI_CLRFR_REGX: + case STM_SAI_DR_REGX: + case STM_SAI_PDMCR_REGX: + case STM_SAI_PDMLY_REGX: + return true; + default: + return false; + } +} + +static const struct regmap_config stm32_sai_sub_regmap_config_f4 = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM_SAI_DR_REGX, + .readable_reg = stm32_sai_sub_readable_reg, + .volatile_reg = stm32_sai_sub_volatile_reg, + .writeable_reg = stm32_sai_sub_writeable_reg, + .fast_io = true, +}; + +static const struct regmap_config stm32_sai_sub_regmap_config_h7 = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM_SAI_PDMLY_REGX, + .readable_reg = stm32_sai_sub_readable_reg, + .volatile_reg = stm32_sai_sub_volatile_reg, + .writeable_reg = stm32_sai_sub_writeable_reg, + .fast_io = true, +}; + +static int snd_pcm_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 snd_pcm_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uctl) +{ + struct stm32_sai_sub_data *sai = snd_kcontrol_chip(kcontrol); + + mutex_lock(&sai->ctrl_lock); + memcpy(uctl->value.iec958.status, sai->iec958.status, 4); + mutex_unlock(&sai->ctrl_lock); + + return 0; +} + +static int snd_pcm_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uctl) +{ + struct stm32_sai_sub_data *sai = snd_kcontrol_chip(kcontrol); + + mutex_lock(&sai->ctrl_lock); + memcpy(sai->iec958.status, uctl->value.iec958.status, 4); + mutex_unlock(&sai->ctrl_lock); + + return 0; +} + +static const struct snd_kcontrol_new iec958_ctls = { + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_pcm_iec958_info, + .get = snd_pcm_iec958_get, + .put = snd_pcm_iec958_put, +}; + +static irqreturn_t stm32_sai_isr(int irq, void *devid) +{ + struct stm32_sai_sub_data *sai = (struct stm32_sai_sub_data *)devid; + struct platform_device *pdev = sai->pdev; + unsigned int sr, imr, flags; + snd_pcm_state_t status = SNDRV_PCM_STATE_RUNNING; + + regmap_read(sai->regmap, STM_SAI_IMR_REGX, &imr); + regmap_read(sai->regmap, STM_SAI_SR_REGX, &sr); + + flags = sr & imr; + if (!flags) + return IRQ_NONE; + + regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, SAI_XCLRFR_MASK, + SAI_XCLRFR_MASK); + + if (!sai->substream) { + dev_err(&pdev->dev, "Device stopped. Spurious IRQ 0x%x\n", sr); + return IRQ_NONE; + } + + if (flags & SAI_XIMR_OVRUDRIE) { + dev_err(&pdev->dev, "IRQ %s\n", + STM_SAI_IS_PLAYBACK(sai) ? "underrun" : "overrun"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (flags & SAI_XIMR_MUTEDETIE) + dev_dbg(&pdev->dev, "IRQ mute detected\n"); + + if (flags & SAI_XIMR_WCKCFGIE) { + dev_err(&pdev->dev, "IRQ wrong clock configuration\n"); + status = SNDRV_PCM_STATE_DISCONNECTED; + } + + if (flags & SAI_XIMR_CNRDYIE) + dev_err(&pdev->dev, "IRQ Codec not ready\n"); + + if (flags & SAI_XIMR_AFSDETIE) { + dev_err(&pdev->dev, "IRQ Anticipated frame synchro\n"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (flags & SAI_XIMR_LFSDETIE) { + dev_err(&pdev->dev, "IRQ Late frame synchro\n"); + status = SNDRV_PCM_STATE_XRUN; + } + + if (status != SNDRV_PCM_STATE_RUNNING) + snd_pcm_stop_xrun(sai->substream); + + return IRQ_HANDLED; +} + +static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + if ((dir == SND_SOC_CLOCK_OUT) && sai->master) { + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_NODIV, + (unsigned int)~SAI_XCR1_NODIV); + if (ret < 0) + return ret; + + sai->mclk_rate = freq; + dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq); + } + + return 0; +} + +static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int slotr, slotr_mask, slot_size; + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + dev_warn(cpu_dai->dev, "Slot setting relevant only for TDM\n"); + return 0; + } + + dev_dbg(cpu_dai->dev, "Masks tx/rx:%#x/%#x, slots:%d, width:%d\n", + tx_mask, rx_mask, slots, slot_width); + + switch (slot_width) { + case 16: + slot_size = SAI_SLOT_SIZE_16; + break; + case 32: + slot_size = SAI_SLOT_SIZE_32; + break; + default: + slot_size = SAI_SLOT_SIZE_AUTO; + break; + } + + slotr = SAI_XSLOTR_SLOTSZ_SET(slot_size) | + SAI_XSLOTR_NBSLOT_SET(slots - 1); + slotr_mask = SAI_XSLOTR_SLOTSZ_MASK | SAI_XSLOTR_NBSLOT_MASK; + + /* tx/rx mask set in machine init, if slot number defined in DT */ + if (STM_SAI_IS_PLAYBACK(sai)) { + sai->slot_mask = tx_mask; + slotr |= SAI_XSLOTR_SLOTEN_SET(tx_mask); + } + + if (STM_SAI_IS_CAPTURE(sai)) { + sai->slot_mask = rx_mask; + slotr |= SAI_XSLOTR_SLOTEN_SET(rx_mask); + } + + slotr_mask |= SAI_XSLOTR_SLOTEN_MASK; + + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, slotr_mask, slotr); + + sai->slot_width = slot_width; + sai->slots = slots; + + return 0; +} + +static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1, frcr = 0; + int cr1_mask, frcr_mask = 0; + int ret; + + dev_dbg(cpu_dai->dev, "fmt %x\n", fmt); + + /* Do not generate master by default */ + cr1 = SAI_XCR1_NODIV; + cr1_mask = SAI_XCR1_NODIV; + + cr1_mask |= SAI_XCR1_PRTCFG_MASK; + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + cr1 |= SAI_XCR1_PRTCFG_SET(SAI_SPDIF_PROTOCOL); + goto conf_update; + } + + cr1 |= SAI_XCR1_PRTCFG_SET(SAI_FREE_PROTOCOL); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + /* SCK active high for all protocols */ + case SND_SOC_DAIFMT_I2S: + cr1 |= SAI_XCR1_CKSTR; + frcr |= SAI_XFRCR_FSOFF | SAI_XFRCR_FSDEF; + break; + /* Left justified */ + case SND_SOC_DAIFMT_MSB: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF; + break; + /* Right justified */ + case SND_SOC_DAIFMT_LSB: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSDEF; + break; + case SND_SOC_DAIFMT_DSP_A: + frcr |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF; + break; + case SND_SOC_DAIFMT_DSP_B: + frcr |= SAI_XFRCR_FSPOL; + break; + default: + dev_err(cpu_dai->dev, "Unsupported protocol %#x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + cr1_mask |= SAI_XCR1_CKSTR; + frcr_mask |= SAI_XFRCR_FSPOL | SAI_XFRCR_FSOFF | + SAI_XFRCR_FSDEF; + + /* DAI clock strobing. Invert setting previously set */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + cr1 ^= SAI_XCR1_CKSTR; + break; + case SND_SOC_DAIFMT_NB_IF: + frcr ^= SAI_XFRCR_FSPOL; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert fs & sck */ + cr1 ^= SAI_XCR1_CKSTR; + frcr ^= SAI_XFRCR_FSPOL; + break; + default: + dev_err(cpu_dai->dev, "Unsupported strobing %#x\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + cr1_mask |= SAI_XCR1_CKSTR; + frcr_mask |= SAI_XFRCR_FSPOL; + + regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr); + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master */ + cr1 |= SAI_XCR1_SLAVE; + sai->master = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + sai->master = true; + break; + default: + dev_err(cpu_dai->dev, "Unsupported mode %#x\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + /* Set slave mode if sub-block is synchronized with another SAI */ + if (sai->sync) { + dev_dbg(cpu_dai->dev, "Synchronized SAI configured as slave\n"); + cr1 |= SAI_XCR1_SLAVE; + sai->master = false; + } + + cr1_mask |= SAI_XCR1_SLAVE; + +conf_update: + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + sai->fmt = fmt; + + return 0; +} + +static int stm32_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int imr, cr2, ret; + + sai->substream = substream; + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + snd_pcm_hw_constraint_mask64(substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_FMTBIT_S32_LE); + snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + } + + ret = clk_prepare_enable(sai->sai_ck); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to enable clock: %d\n", ret); + return ret; + } + + /* Enable ITs */ + + regmap_update_bits(sai->regmap, STM_SAI_CLRFR_REGX, + SAI_XCLRFR_MASK, SAI_XCLRFR_MASK); + + imr = SAI_XIMR_OVRUDRIE; + if (STM_SAI_IS_CAPTURE(sai)) { + regmap_read(sai->regmap, STM_SAI_CR2_REGX, &cr2); + if (cr2 & SAI_XCR2_MUTECNT_MASK) + imr |= SAI_XIMR_MUTEDETIE; + } + + if (sai->master) + imr |= SAI_XIMR_WCKCFGIE; + else + imr |= SAI_XIMR_AFSDETIE | SAI_XIMR_LFSDETIE; + + regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, + SAI_XIMR_MASK, imr); + + return 0; +} + +static int stm32_sai_set_config(struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1, cr1_mask, ret; + + /* + * DMA bursts increment is set to 4 words. + * SAI fifo threshold is set to half fifo, to keep enough space + * for DMA incoming bursts. + */ + regmap_update_bits(sai->regmap, STM_SAI_CR2_REGX, + SAI_XCR2_FFLUSH | SAI_XCR2_FTH_MASK, + SAI_XCR2_FFLUSH | + SAI_XCR2_FTH_SET(STM_SAI_FIFO_TH_HALF)); + + /* DS bits in CR1 not set for SPDIF (size forced to 24 bits).*/ + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + sai->spdif_frm_cnt = 0; + return 0; + } + + /* Mode, data format and channel config */ + cr1_mask = SAI_XCR1_DS_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + cr1 = SAI_XCR1_DS_SET(SAI_DATASIZE_8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + cr1 = SAI_XCR1_DS_SET(SAI_DATASIZE_16); + break; + case SNDRV_PCM_FORMAT_S32_LE: + cr1 = SAI_XCR1_DS_SET(SAI_DATASIZE_32); + break; + default: + dev_err(cpu_dai->dev, "Data format not supported"); + return -EINVAL; + } + + cr1_mask |= SAI_XCR1_MONO; + if ((sai->slots == 2) && (params_channels(params) == 1)) + cr1 |= SAI_XCR1_MONO; + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + return 0; +} + +static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int slotr, slot_sz; + + regmap_read(sai->regmap, STM_SAI_SLOTR_REGX, &slotr); + + /* + * If SLOTSZ is set to auto in SLOTR, align slot width on data size + * By default slot width = data size, if not forced from DT + */ + slot_sz = slotr & SAI_XSLOTR_SLOTSZ_MASK; + if (slot_sz == SAI_XSLOTR_SLOTSZ_SET(SAI_SLOT_SIZE_AUTO)) + sai->slot_width = sai->data_size; + + if (sai->slot_width < sai->data_size) { + dev_err(cpu_dai->dev, + "Data size %d larger than slot width\n", + sai->data_size); + return -EINVAL; + } + + /* Slot number is set to 2, if not specified in DT */ + if (!sai->slots) + sai->slots = 2; + + /* The number of slots in the audio frame is equal to NBSLOT[3:0] + 1*/ + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, + SAI_XSLOTR_NBSLOT_MASK, + SAI_XSLOTR_NBSLOT_SET((sai->slots - 1))); + + /* Set default slots mask if not already set from DT */ + if (!(slotr & SAI_XSLOTR_SLOTEN_MASK)) { + sai->slot_mask = (1 << sai->slots) - 1; + regmap_update_bits(sai->regmap, + STM_SAI_SLOTR_REGX, SAI_XSLOTR_SLOTEN_MASK, + SAI_XSLOTR_SLOTEN_SET(sai->slot_mask)); + } + + dev_dbg(cpu_dai->dev, "Slots %d, slot width %d\n", + sai->slots, sai->slot_width); + + return 0; +} + +static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int fs_active, offset, format; + int frcr, frcr_mask; + + format = sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + sai->fs_length = sai->slot_width * sai->slots; + + fs_active = sai->fs_length / 2; + if ((format == SND_SOC_DAIFMT_DSP_A) || + (format == SND_SOC_DAIFMT_DSP_B)) + fs_active = 1; + + frcr = SAI_XFRCR_FRL_SET((sai->fs_length - 1)); + frcr |= SAI_XFRCR_FSALL_SET((fs_active - 1)); + frcr_mask = SAI_XFRCR_FRL_MASK | SAI_XFRCR_FSALL_MASK; + + dev_dbg(cpu_dai->dev, "Frame length %d, frame active %d\n", + sai->fs_length, fs_active); + + regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr); + + if ((sai->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_LSB) { + offset = sai->slot_width - sai->data_size; + + regmap_update_bits(sai->regmap, STM_SAI_SLOTR_REGX, + SAI_XSLOTR_FBOFF_MASK, + SAI_XSLOTR_FBOFF_SET(offset)); + } +} + +static void stm32_sai_init_iec958_status(struct stm32_sai_sub_data *sai) +{ + unsigned char *cs = sai->iec958.status; + + cs[0] = IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS_NONE; + cs[1] = IEC958_AES1_CON_GENERAL; + cs[2] = IEC958_AES2_CON_SOURCE_UNSPEC | IEC958_AES2_CON_CHANNEL_UNSPEC; + cs[3] = IEC958_AES3_CON_CLOCK_1000PPM | IEC958_AES3_CON_FS_NOTID; +} + +static void stm32_sai_set_iec958_status(struct stm32_sai_sub_data *sai, + struct snd_pcm_runtime *runtime) +{ + if (!runtime) + return; + + /* Force the sample rate according to runtime rate */ + mutex_lock(&sai->ctrl_lock); + switch (runtime->rate) { + case 22050: + sai->iec958.status[3] = IEC958_AES3_CON_FS_22050; + break; + case 44100: + sai->iec958.status[3] = IEC958_AES3_CON_FS_44100; + break; + case 88200: + sai->iec958.status[3] = IEC958_AES3_CON_FS_88200; + break; + case 176400: + sai->iec958.status[3] = IEC958_AES3_CON_FS_176400; + break; + case 24000: + sai->iec958.status[3] = IEC958_AES3_CON_FS_24000; + break; + case 48000: + sai->iec958.status[3] = IEC958_AES3_CON_FS_48000; + break; + case 96000: + sai->iec958.status[3] = IEC958_AES3_CON_FS_96000; + break; + case 192000: + sai->iec958.status[3] = IEC958_AES3_CON_FS_192000; + break; + case 32000: + sai->iec958.status[3] = IEC958_AES3_CON_FS_32000; + break; + default: + sai->iec958.status[3] = IEC958_AES3_CON_FS_NOTID; + break; + } + mutex_unlock(&sai->ctrl_lock); +} + +static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *params) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int cr1, mask, div = 0; + int sai_clk_rate, mclk_ratio, den, ret; + int version = sai->pdata->conf->version; + unsigned int rate = params_rate(params); + + if (!sai->mclk_rate) { + dev_err(cpu_dai->dev, "Mclk rate is null\n"); + return -EINVAL; + } + + if (!(rate % 11025)) + clk_set_parent(sai->sai_ck, sai->pdata->clk_x11k); + else + clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k); + sai_clk_rate = clk_get_rate(sai->sai_ck); + + if (STM_SAI_IS_F4(sai->pdata)) { + /* + * mclk_rate = 256 * fs + * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate + * MCKDIV = sai_ck / (2 * mclk_rate) otherwise + */ + if (2 * sai_clk_rate >= 3 * sai->mclk_rate) + div = DIV_ROUND_CLOSEST(sai_clk_rate, + 2 * sai->mclk_rate); + } else { + /* + * TDM mode : + * mclk on + * MCKDIV = sai_ck / (ws x 256) (NOMCK=0. OSR=0) + * MCKDIV = sai_ck / (ws x 512) (NOMCK=0. OSR=1) + * mclk off + * MCKDIV = sai_ck / (frl x ws) (NOMCK=1) + * Note: NOMCK/NODIV correspond to same bit. + */ + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + div = DIV_ROUND_CLOSEST(sai_clk_rate, + (params_rate(params) * 128)); + } else { + if (sai->mclk_rate) { + mclk_ratio = sai->mclk_rate / rate; + if (mclk_ratio == 512) { + mask = SAI_XCR1_OSR; + cr1 = SAI_XCR1_OSR; + } else if (mclk_ratio != 256) { + dev_err(cpu_dai->dev, + "Wrong mclk ratio %d\n", + mclk_ratio); + return -EINVAL; + } + div = DIV_ROUND_CLOSEST(sai_clk_rate, + sai->mclk_rate); + } else { + /* mclk-fs not set, master clock not active */ + den = sai->fs_length * params_rate(params); + div = DIV_ROUND_CLOSEST(sai_clk_rate, den); + } + } + } + + if (div > SAI_XCR1_MCKDIV_MAX(version)) { + dev_err(cpu_dai->dev, "Divider %d out of range\n", div); + return -EINVAL; + } + dev_dbg(cpu_dai->dev, "SAI clock %d, divider %d\n", sai_clk_rate, div); + + mask = SAI_XCR1_MCKDIV_MASK(SAI_XCR1_MCKDIV_WIDTH(version)); + cr1 = SAI_XCR1_MCKDIV_SET(div); + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1); + if (ret < 0) { + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + return ret; + } + + return 0; +} + +static int stm32_sai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + sai->data_size = params_width(params); + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + /* Rate not already set in runtime structure */ + substream->runtime->rate = params_rate(params); + stm32_sai_set_iec958_status(sai, substream->runtime); + } else { + ret = stm32_sai_set_slots(cpu_dai); + if (ret < 0) + return ret; + stm32_sai_set_frame(cpu_dai); + } + + ret = stm32_sai_set_config(cpu_dai, substream, params); + if (ret) + return ret; + + if (sai->master) + ret = stm32_sai_configure_clock(cpu_dai, params); + + return ret; +} + +static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev_dbg(cpu_dai->dev, "Enable DMA and SAI\n"); + + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_DMAEN, SAI_XCR1_DMAEN); + + /* Enable SAI */ + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_SAIEN, SAI_XCR1_SAIEN); + if (ret < 0) + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n"); + + regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, + SAI_XIMR_MASK, 0); + + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_SAIEN, + (unsigned int)~SAI_XCR1_SAIEN); + + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_DMAEN, + (unsigned int)~SAI_XCR1_DMAEN); + if (ret < 0) + dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + sai->spdif_frm_cnt = 0; + break; + default: + return -EINVAL; + } + + return ret; +} + +static void stm32_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + + regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0); + + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, SAI_XCR1_NODIV, + SAI_XCR1_NODIV); + + clk_disable_unprepare(sai->sai_ck); + sai->substream = NULL; +} + +static int stm32_sai_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + struct snd_kcontrol_new knew = iec958_ctls; + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) { + dev_dbg(&sai->pdev->dev, "%s: register iec controls", __func__); + knew.device = rtd->pcm->device; + return snd_ctl_add(rtd->pcm->card, snd_ctl_new1(&knew, sai)); + } + + return 0; +} + +static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + int cr1 = 0, cr1_mask; + + sai->dma_params.addr = (dma_addr_t)(sai->phys_addr + STM_SAI_DR_REGX); + /* + * DMA supports 4, 8 or 16 burst sizes. Burst size 4 is the best choice, + * as it allows bytes, half-word and words transfers. (See DMA fifos + * constraints). + */ + sai->dma_params.maxburst = 4; + /* Buswidth will be set by framework at runtime */ + sai->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + + if (STM_SAI_IS_PLAYBACK(sai)) + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params, NULL); + else + snd_soc_dai_init_dma_data(cpu_dai, NULL, &sai->dma_params); + + /* Next settings are not relevant for spdif mode */ + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + return 0; + + cr1_mask = SAI_XCR1_RX_TX; + if (STM_SAI_IS_CAPTURE(sai)) + cr1 |= SAI_XCR1_RX_TX; + + /* Configure synchronization */ + if (sai->sync == SAI_SYNC_EXTERNAL) { + /* Configure synchro client and provider */ + sai->pdata->set_sync(sai->pdata, sai->np_sync_provider, + sai->synco, sai->synci); + } + + cr1_mask |= SAI_XCR1_SYNCEN_MASK; + cr1 |= SAI_XCR1_SYNCEN_SET(sai->sync); + + return regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); +} + +static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { + .set_sysclk = stm32_sai_set_sysclk, + .set_fmt = stm32_sai_set_dai_fmt, + .set_tdm_slot = stm32_sai_set_dai_tdm_slot, + .startup = stm32_sai_startup, + .hw_params = stm32_sai_hw_params, + .trigger = stm32_sai_trigger, + .shutdown = stm32_sai_shutdown, +}; + +static int stm32_sai_pcm_process_spdif(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + void *buf, unsigned long bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct stm32_sai_sub_data *sai = dev_get_drvdata(cpu_dai->dev); + int *ptr = (int *)(runtime->dma_area + hwoff + + channel * (runtime->dma_bytes / runtime->channels)); + ssize_t cnt = bytes_to_samples(runtime, bytes); + unsigned int frm_cnt = sai->spdif_frm_cnt; + unsigned int byte; + unsigned int mask; + + do { + *ptr = ((*ptr >> 8) & 0x00ffffff); + + /* Set channel status bit */ + byte = frm_cnt >> 3; + mask = 1 << (frm_cnt - (byte << 3)); + if (sai->iec958.status[byte] & mask) + *ptr |= 0x04000000; + ptr++; + + if (!(cnt % 2)) + frm_cnt++; + + if (frm_cnt == SAI_IEC60958_BLOCK_FRAMES) + frm_cnt = 0; + } while (--cnt); + sai->spdif_frm_cnt = frm_cnt; + + return 0; +} + +/* No support of mmap in S/PDIF mode */ +static const struct snd_pcm_hardware stm32_sai_pcm_hw_spdif = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_min = 1024, + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, +}; + +static const struct snd_pcm_hardware stm32_sai_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_min = 1024, /* 5ms at 48kHz */ + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, +}; + +static struct snd_soc_dai_driver stm32_sai_playback_dai[] = { +{ + .probe = stm32_sai_dai_probe, + .pcm_new = stm32_sai_pcm_new, + .id = 1, /* avoid call to fmt_single_name() */ + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + /* DMA does not support 24 bits transfers */ + .formats = + SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &stm32_sai_pcm_dai_ops, + } +}; + +static struct snd_soc_dai_driver stm32_sai_capture_dai[] = { +{ + .probe = stm32_sai_dai_probe, + .id = 1, /* avoid call to fmt_single_name() */ + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + /* DMA does not support 24 bits transfers */ + .formats = + SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &stm32_sai_pcm_dai_ops, + } +}; + +static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config = { + .pcm_hardware = &stm32_sai_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static const struct snd_dmaengine_pcm_config stm32_sai_pcm_config_spdif = { + .pcm_hardware = &stm32_sai_pcm_hw_spdif, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .process = stm32_sai_pcm_process_spdif, +}; + +static const struct snd_soc_component_driver stm32_component = { + .name = "stm32-sai", +}; + +static const struct of_device_id stm32_sai_sub_ids[] = { + { .compatible = "st,stm32-sai-sub-a", + .data = (void *)STM_SAI_A_ID}, + { .compatible = "st,stm32-sai-sub-b", + .data = (void *)STM_SAI_B_ID}, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_sai_sub_ids); + +static int stm32_sai_sub_parse_of(struct platform_device *pdev, + struct stm32_sai_sub_data *sai) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *res; + void __iomem *base; + struct of_phandle_args args; + int ret; + + if (!np) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->phys_addr = res->start; + + sai->regmap_config = &stm32_sai_sub_regmap_config_f4; + /* Note: PDM registers not available for H7 sub-block B */ + if (STM_SAI_IS_H7(sai->pdata) && STM_SAI_IS_SUB_A(sai)) + sai->regmap_config = &stm32_sai_sub_regmap_config_h7; + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "sai_ck", + base, sai->regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "Failed to initialize MMIO\n"); + return PTR_ERR(sai->regmap); + } + + /* Get direction property */ + if (of_property_match_string(np, "dma-names", "tx") >= 0) { + sai->dir = SNDRV_PCM_STREAM_PLAYBACK; + } else if (of_property_match_string(np, "dma-names", "rx") >= 0) { + sai->dir = SNDRV_PCM_STREAM_CAPTURE; + } else { + dev_err(&pdev->dev, "Unsupported direction\n"); + return -EINVAL; + } + + /* Get spdif iec60958 property */ + sai->spdif = false; + if (of_get_property(np, "st,iec60958", NULL)) { + if (!STM_SAI_HAS_SPDIF(sai) || + sai->dir == SNDRV_PCM_STREAM_CAPTURE) { + dev_err(&pdev->dev, "S/PDIF IEC60958 not supported\n"); + return -EINVAL; + } + stm32_sai_init_iec958_status(sai); + sai->spdif = true; + sai->master = true; + } + + /* Get synchronization property */ + args.np = NULL; + ret = of_parse_phandle_with_fixed_args(np, "st,sync", 1, 0, &args); + if (ret < 0 && ret != -ENOENT) { + dev_err(&pdev->dev, "Failed to get st,sync property\n"); + return ret; + } + + sai->sync = SAI_SYNC_NONE; + if (args.np) { + if (args.np == np) { + dev_err(&pdev->dev, "%s sync own reference\n", + np->name); + of_node_put(args.np); + return -EINVAL; + } + + sai->np_sync_provider = of_get_parent(args.np); + if (!sai->np_sync_provider) { + dev_err(&pdev->dev, "%s parent node not found\n", + np->name); + of_node_put(args.np); + return -ENODEV; + } + + sai->sync = SAI_SYNC_INTERNAL; + if (sai->np_sync_provider != sai->pdata->pdev->dev.of_node) { + if (!STM_SAI_HAS_EXT_SYNC(sai)) { + dev_err(&pdev->dev, + "External synchro not supported\n"); + of_node_put(args.np); + return -EINVAL; + } + sai->sync = SAI_SYNC_EXTERNAL; + + sai->synci = args.args[0]; + if (sai->synci < 1 || + (sai->synci > (SAI_GCR_SYNCIN_MAX + 1))) { + dev_err(&pdev->dev, "Wrong SAI index\n"); + of_node_put(args.np); + return -EINVAL; + } + + if (of_property_match_string(args.np, "compatible", + "st,stm32-sai-sub-a") >= 0) + sai->synco = STM_SAI_SYNC_OUT_A; + + if (of_property_match_string(args.np, "compatible", + "st,stm32-sai-sub-b") >= 0) + sai->synco = STM_SAI_SYNC_OUT_B; + + if (!sai->synco) { + dev_err(&pdev->dev, "Unknown SAI sub-block\n"); + of_node_put(args.np); + return -EINVAL; + } + } + + dev_dbg(&pdev->dev, "%s synchronized with %s\n", + pdev->name, args.np->full_name); + } + + of_node_put(args.np); + sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck"); + if (IS_ERR(sai->sai_ck)) { + dev_err(&pdev->dev, "Missing kernel clock sai_ck\n"); + return PTR_ERR(sai->sai_ck); + } + + return 0; +} + +static int stm32_sai_sub_dais_init(struct platform_device *pdev, + struct stm32_sai_sub_data *sai) +{ + sai->cpu_dai_drv = devm_kzalloc(&pdev->dev, + sizeof(struct snd_soc_dai_driver), + GFP_KERNEL); + if (!sai->cpu_dai_drv) + return -ENOMEM; + + if (STM_SAI_IS_PLAYBACK(sai)) { + memcpy(sai->cpu_dai_drv, &stm32_sai_playback_dai, + sizeof(stm32_sai_playback_dai)); + sai->cpu_dai_drv->playback.stream_name = sai->cpu_dai_drv->name; + } else { + memcpy(sai->cpu_dai_drv, &stm32_sai_capture_dai, + sizeof(stm32_sai_capture_dai)); + sai->cpu_dai_drv->capture.stream_name = sai->cpu_dai_drv->name; + } + sai->cpu_dai_drv->name = dev_name(&pdev->dev); + + return 0; +} + +static int stm32_sai_sub_probe(struct platform_device *pdev) +{ + struct stm32_sai_sub_data *sai; + const struct of_device_id *of_id; + const struct snd_dmaengine_pcm_config *conf = &stm32_sai_pcm_config; + int ret; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + of_id = of_match_device(stm32_sai_sub_ids, &pdev->dev); + if (!of_id) + return -EINVAL; + sai->id = (uintptr_t)of_id->data; + + sai->pdev = pdev; + mutex_init(&sai->ctrl_lock); + platform_set_drvdata(pdev, sai); + + sai->pdata = dev_get_drvdata(pdev->dev.parent); + if (!sai->pdata) { + dev_err(&pdev->dev, "Parent device data not available\n"); + return -EINVAL; + } + + ret = stm32_sai_sub_parse_of(pdev, sai); + if (ret) + return ret; + + ret = stm32_sai_sub_dais_init(pdev, sai); + if (ret) + return ret; + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); + if (ret) { + dev_err(&pdev->dev, "IRQ request returned %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &stm32_component, + sai->cpu_dai_drv, 1); + if (ret) + return ret; + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, conf, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register pcm dma\n"); + return ret; + } + + return 0; +} + +static struct platform_driver stm32_sai_sub_driver = { + .driver = { + .name = "st,stm32-sai-sub", + .of_match_table = stm32_sai_sub_ids, + }, + .probe = stm32_sai_sub_probe, +}; + +module_platform_driver(stm32_sai_sub_driver); + +MODULE_DESCRIPTION("STM32 Soc SAI sub-block Interface"); +MODULE_AUTHOR("Olivier Moysan "); +MODULE_ALIAS("platform:st,stm32-sai-sub"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c new file mode 100644 index 000000000..07082f7ef --- /dev/null +++ b/sound/soc/stm/stm32_spdifrx.c @@ -0,0 +1,1016 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SPDIF-rx) driver. + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* SPDIF-rx Register Map */ +#define STM32_SPDIFRX_CR 0x00 +#define STM32_SPDIFRX_IMR 0x04 +#define STM32_SPDIFRX_SR 0x08 +#define STM32_SPDIFRX_IFCR 0x0C +#define STM32_SPDIFRX_DR 0x10 +#define STM32_SPDIFRX_CSR 0x14 +#define STM32_SPDIFRX_DIR 0x18 + +/* Bit definition for SPDIF_CR register */ +#define SPDIFRX_CR_SPDIFEN_SHIFT 0 +#define SPDIFRX_CR_SPDIFEN_MASK GENMASK(1, SPDIFRX_CR_SPDIFEN_SHIFT) +#define SPDIFRX_CR_SPDIFENSET(x) ((x) << SPDIFRX_CR_SPDIFEN_SHIFT) + +#define SPDIFRX_CR_RXDMAEN BIT(2) +#define SPDIFRX_CR_RXSTEO BIT(3) + +#define SPDIFRX_CR_DRFMT_SHIFT 4 +#define SPDIFRX_CR_DRFMT_MASK GENMASK(5, SPDIFRX_CR_DRFMT_SHIFT) +#define SPDIFRX_CR_DRFMTSET(x) ((x) << SPDIFRX_CR_DRFMT_SHIFT) + +#define SPDIFRX_CR_PMSK BIT(6) +#define SPDIFRX_CR_VMSK BIT(7) +#define SPDIFRX_CR_CUMSK BIT(8) +#define SPDIFRX_CR_PTMSK BIT(9) +#define SPDIFRX_CR_CBDMAEN BIT(10) +#define SPDIFRX_CR_CHSEL_SHIFT 11 +#define SPDIFRX_CR_CHSEL BIT(SPDIFRX_CR_CHSEL_SHIFT) + +#define SPDIFRX_CR_NBTR_SHIFT 12 +#define SPDIFRX_CR_NBTR_MASK GENMASK(13, SPDIFRX_CR_NBTR_SHIFT) +#define SPDIFRX_CR_NBTRSET(x) ((x) << SPDIFRX_CR_NBTR_SHIFT) + +#define SPDIFRX_CR_WFA BIT(14) + +#define SPDIFRX_CR_INSEL_SHIFT 16 +#define SPDIFRX_CR_INSEL_MASK GENMASK(18, PDIFRX_CR_INSEL_SHIFT) +#define SPDIFRX_CR_INSELSET(x) ((x) << SPDIFRX_CR_INSEL_SHIFT) + +#define SPDIFRX_CR_CKSEN_SHIFT 20 +#define SPDIFRX_CR_CKSEN BIT(20) +#define SPDIFRX_CR_CKSBKPEN BIT(21) + +/* Bit definition for SPDIFRX_IMR register */ +#define SPDIFRX_IMR_RXNEI BIT(0) +#define SPDIFRX_IMR_CSRNEIE BIT(1) +#define SPDIFRX_IMR_PERRIE BIT(2) +#define SPDIFRX_IMR_OVRIE BIT(3) +#define SPDIFRX_IMR_SBLKIE BIT(4) +#define SPDIFRX_IMR_SYNCDIE BIT(5) +#define SPDIFRX_IMR_IFEIE BIT(6) + +#define SPDIFRX_XIMR_MASK GENMASK(6, 0) + +/* Bit definition for SPDIFRX_SR register */ +#define SPDIFRX_SR_RXNE BIT(0) +#define SPDIFRX_SR_CSRNE BIT(1) +#define SPDIFRX_SR_PERR BIT(2) +#define SPDIFRX_SR_OVR BIT(3) +#define SPDIFRX_SR_SBD BIT(4) +#define SPDIFRX_SR_SYNCD BIT(5) +#define SPDIFRX_SR_FERR BIT(6) +#define SPDIFRX_SR_SERR BIT(7) +#define SPDIFRX_SR_TERR BIT(8) + +#define SPDIFRX_SR_WIDTH5_SHIFT 16 +#define SPDIFRX_SR_WIDTH5_MASK GENMASK(30, PDIFRX_SR_WIDTH5_SHIFT) +#define SPDIFRX_SR_WIDTH5SET(x) ((x) << SPDIFRX_SR_WIDTH5_SHIFT) + +/* Bit definition for SPDIFRX_IFCR register */ +#define SPDIFRX_IFCR_PERRCF BIT(2) +#define SPDIFRX_IFCR_OVRCF BIT(3) +#define SPDIFRX_IFCR_SBDCF BIT(4) +#define SPDIFRX_IFCR_SYNCDCF BIT(5) + +#define SPDIFRX_XIFCR_MASK GENMASK(5, 2) + +/* Bit definition for SPDIFRX_DR register (DRFMT = 0b00) */ +#define SPDIFRX_DR0_DR_SHIFT 0 +#define SPDIFRX_DR0_DR_MASK GENMASK(23, SPDIFRX_DR0_DR_SHIFT) +#define SPDIFRX_DR0_DRSET(x) ((x) << SPDIFRX_DR0_DR_SHIFT) + +#define SPDIFRX_DR0_PE BIT(24) + +#define SPDIFRX_DR0_V BIT(25) +#define SPDIFRX_DR0_U BIT(26) +#define SPDIFRX_DR0_C BIT(27) + +#define SPDIFRX_DR0_PT_SHIFT 28 +#define SPDIFRX_DR0_PT_MASK GENMASK(29, SPDIFRX_DR0_PT_SHIFT) +#define SPDIFRX_DR0_PTSET(x) ((x) << SPDIFRX_DR0_PT_SHIFT) + +/* Bit definition for SPDIFRX_DR register (DRFMT = 0b01) */ +#define SPDIFRX_DR1_PE BIT(0) +#define SPDIFRX_DR1_V BIT(1) +#define SPDIFRX_DR1_U BIT(2) +#define SPDIFRX_DR1_C BIT(3) + +#define SPDIFRX_DR1_PT_SHIFT 4 +#define SPDIFRX_DR1_PT_MASK GENMASK(5, SPDIFRX_DR1_PT_SHIFT) +#define SPDIFRX_DR1_PTSET(x) ((x) << SPDIFRX_DR1_PT_SHIFT) + +#define SPDIFRX_DR1_DR_SHIFT 8 +#define SPDIFRX_DR1_DR_MASK GENMASK(31, SPDIFRX_DR1_DR_SHIFT) +#define SPDIFRX_DR1_DRSET(x) ((x) << SPDIFRX_DR1_DR_SHIFT) + +/* Bit definition for SPDIFRX_DR register (DRFMT = 0b10) */ +#define SPDIFRX_DR1_DRNL1_SHIFT 0 +#define SPDIFRX_DR1_DRNL1_MASK GENMASK(15, SPDIFRX_DR1_DRNL1_SHIFT) +#define SPDIFRX_DR1_DRNL1SET(x) ((x) << SPDIFRX_DR1_DRNL1_SHIFT) + +#define SPDIFRX_DR1_DRNL2_SHIFT 16 +#define SPDIFRX_DR1_DRNL2_MASK GENMASK(31, SPDIFRX_DR1_DRNL2_SHIFT) +#define SPDIFRX_DR1_DRNL2SET(x) ((x) << SPDIFRX_DR1_DRNL2_SHIFT) + +/* Bit definition for SPDIFRX_CSR register */ +#define SPDIFRX_CSR_USR_SHIFT 0 +#define SPDIFRX_CSR_USR_MASK GENMASK(15, SPDIFRX_CSR_USR_SHIFT) +#define SPDIFRX_CSR_USRGET(x) (((x) & SPDIFRX_CSR_USR_MASK)\ + >> SPDIFRX_CSR_USR_SHIFT) + +#define SPDIFRX_CSR_CS_SHIFT 16 +#define SPDIFRX_CSR_CS_MASK GENMASK(23, SPDIFRX_CSR_CS_SHIFT) +#define SPDIFRX_CSR_CSGET(x) (((x) & SPDIFRX_CSR_CS_MASK)\ + >> SPDIFRX_CSR_CS_SHIFT) + +#define SPDIFRX_CSR_SOB BIT(24) + +/* Bit definition for SPDIFRX_DIR register */ +#define SPDIFRX_DIR_THI_SHIFT 0 +#define SPDIFRX_DIR_THI_MASK GENMASK(12, SPDIFRX_DIR_THI_SHIFT) +#define SPDIFRX_DIR_THI_SET(x) ((x) << SPDIFRX_DIR_THI_SHIFT) + +#define SPDIFRX_DIR_TLO_SHIFT 16 +#define SPDIFRX_DIR_TLO_MASK GENMASK(28, SPDIFRX_DIR_TLO_SHIFT) +#define SPDIFRX_DIR_TLO_SET(x) ((x) << SPDIFRX_DIR_TLO_SHIFT) + +#define SPDIFRX_SPDIFEN_DISABLE 0x0 +#define SPDIFRX_SPDIFEN_SYNC 0x1 +#define SPDIFRX_SPDIFEN_ENABLE 0x3 + +#define SPDIFRX_IN1 0x1 +#define SPDIFRX_IN2 0x2 +#define SPDIFRX_IN3 0x3 +#define SPDIFRX_IN4 0x4 +#define SPDIFRX_IN5 0x5 +#define SPDIFRX_IN6 0x6 +#define SPDIFRX_IN7 0x7 +#define SPDIFRX_IN8 0x8 + +#define SPDIFRX_NBTR_NONE 0x0 +#define SPDIFRX_NBTR_3 0x1 +#define SPDIFRX_NBTR_15 0x2 +#define SPDIFRX_NBTR_63 0x3 + +#define SPDIFRX_DRFMT_RIGHT 0x0 +#define SPDIFRX_DRFMT_LEFT 0x1 +#define SPDIFRX_DRFMT_PACKED 0x2 + +/* 192 CS bits in S/PDIF frame. i.e 24 CS bytes */ +#define SPDIFRX_CS_BYTES_NB 24 +#define SPDIFRX_UB_BYTES_NB 48 + +/* + * CSR register is retrieved as a 32 bits word + * It contains 1 channel status byte and 2 user data bytes + * 2 S/PDIF frames are acquired to get all CS/UB bits + */ +#define SPDIFRX_CSR_BUF_LENGTH (SPDIFRX_CS_BYTES_NB * 4 * 2) + +/** + * struct stm32_spdifrx_data - private data of SPDIFRX + * @pdev: device data pointer + * @base: mmio register base virtual address + * @regmap: SPDIFRX register map pointer + * @regmap_conf: SPDIFRX register map configuration pointer + * @cs_completion: channel status retrieving completion + * @kclk: kernel clock feeding the SPDIFRX clock generator + * @dma_params: dma configuration data for rx channel + * @substream: PCM substream data pointer + * @dmab: dma buffer info pointer + * @ctrl_chan: dma channel for S/PDIF control bits + * @desc:dma async transaction descriptor + * @slave_config: dma slave channel runtime config pointer + * @phys_addr: SPDIFRX registers physical base address + * @lock: synchronization enabling lock + * @irq_lock: prevent race condition with IRQ on stream state + * @cs: channel status buffer + * @ub: user data buffer + * @irq: SPDIFRX interrupt line + * @refcount: keep count of opened DMA channels + */ +struct stm32_spdifrx_data { + struct platform_device *pdev; + void __iomem *base; + struct regmap *regmap; + const struct regmap_config *regmap_conf; + struct completion cs_completion; + struct clk *kclk; + struct snd_dmaengine_dai_dma_data dma_params; + struct snd_pcm_substream *substream; + struct snd_dma_buffer *dmab; + struct dma_chan *ctrl_chan; + struct dma_async_tx_descriptor *desc; + struct dma_slave_config slave_config; + dma_addr_t phys_addr; + spinlock_t lock; /* Sync enabling lock */ + spinlock_t irq_lock; /* Prevent race condition on stream state */ + unsigned char cs[SPDIFRX_CS_BYTES_NB]; + unsigned char ub[SPDIFRX_UB_BYTES_NB]; + int irq; + int refcount; +}; + +static void stm32_spdifrx_dma_complete(void *data) +{ + struct stm32_spdifrx_data *spdifrx = (struct stm32_spdifrx_data *)data; + struct platform_device *pdev = spdifrx->pdev; + u32 *p_start = (u32 *)spdifrx->dmab->area; + u32 *p_end = p_start + (2 * SPDIFRX_CS_BYTES_NB) - 1; + u32 *ptr = p_start; + u16 *ub_ptr = (short *)spdifrx->ub; + int i = 0; + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_CBDMAEN, + (unsigned int)~SPDIFRX_CR_CBDMAEN); + + if (!spdifrx->dmab->area) + return; + + while (ptr <= p_end) { + if (*ptr & SPDIFRX_CSR_SOB) + break; + ptr++; + } + + if (ptr > p_end) { + dev_err(&pdev->dev, "Start of S/PDIF block not found\n"); + return; + } + + while (i < SPDIFRX_CS_BYTES_NB) { + spdifrx->cs[i] = (unsigned char)SPDIFRX_CSR_CSGET(*ptr); + *ub_ptr++ = SPDIFRX_CSR_USRGET(*ptr++); + if (ptr > p_end) { + dev_err(&pdev->dev, "Failed to get channel status\n"); + return; + } + i++; + } + + complete(&spdifrx->cs_completion); +} + +static int stm32_spdifrx_dma_ctrl_start(struct stm32_spdifrx_data *spdifrx) +{ + dma_cookie_t cookie; + int err; + + spdifrx->desc = dmaengine_prep_slave_single(spdifrx->ctrl_chan, + spdifrx->dmab->addr, + SPDIFRX_CSR_BUF_LENGTH, + DMA_DEV_TO_MEM, + DMA_CTRL_ACK); + if (!spdifrx->desc) + return -EINVAL; + + spdifrx->desc->callback = stm32_spdifrx_dma_complete; + spdifrx->desc->callback_param = spdifrx; + cookie = dmaengine_submit(spdifrx->desc); + err = dma_submit_error(cookie); + if (err) + return -EINVAL; + + dma_async_issue_pending(spdifrx->ctrl_chan); + + return 0; +} + +static void stm32_spdifrx_dma_ctrl_stop(struct stm32_spdifrx_data *spdifrx) +{ + dmaengine_terminate_async(spdifrx->ctrl_chan); +} + +static int stm32_spdifrx_start_sync(struct stm32_spdifrx_data *spdifrx) +{ + int cr, cr_mask, imr, ret; + unsigned long flags; + + /* Enable IRQs */ + imr = SPDIFRX_IMR_IFEIE | SPDIFRX_IMR_SYNCDIE | SPDIFRX_IMR_PERRIE; + ret = regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IMR, imr, imr); + if (ret) + return ret; + + spin_lock_irqsave(&spdifrx->lock, flags); + + spdifrx->refcount++; + + regmap_read(spdifrx->regmap, STM32_SPDIFRX_CR, &cr); + + if (!(cr & SPDIFRX_CR_SPDIFEN_MASK)) { + /* + * Start sync if SPDIFRX is still in idle state. + * SPDIFRX reception enabled when sync done + */ + dev_dbg(&spdifrx->pdev->dev, "start synchronization\n"); + + /* + * SPDIFRX configuration: + * Wait for activity before starting sync process. This avoid + * to issue sync errors when spdif signal is missing on input. + * Preamble, CS, user, validity and parity error bits not copied + * to DR register. + */ + cr = SPDIFRX_CR_WFA | SPDIFRX_CR_PMSK | SPDIFRX_CR_VMSK | + SPDIFRX_CR_CUMSK | SPDIFRX_CR_PTMSK | SPDIFRX_CR_RXSTEO; + cr_mask = cr; + + cr |= SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_SYNC); + cr_mask |= SPDIFRX_CR_SPDIFEN_MASK; + ret = regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + cr_mask, cr); + if (ret < 0) + dev_err(&spdifrx->pdev->dev, + "Failed to start synchronization\n"); + } + + spin_unlock_irqrestore(&spdifrx->lock, flags); + + return ret; +} + +static void stm32_spdifrx_stop(struct stm32_spdifrx_data *spdifrx) +{ + int cr, cr_mask, reg; + unsigned long flags; + + spin_lock_irqsave(&spdifrx->lock, flags); + + if (--spdifrx->refcount) { + spin_unlock_irqrestore(&spdifrx->lock, flags); + return; + } + + cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_DISABLE); + cr_mask = SPDIFRX_CR_SPDIFEN_MASK | SPDIFRX_CR_RXDMAEN; + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, cr_mask, cr); + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IMR, + SPDIFRX_XIMR_MASK, 0); + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IFCR, + SPDIFRX_XIFCR_MASK, SPDIFRX_XIFCR_MASK); + + /* dummy read to clear CSRNE and RXNE in status register */ + regmap_read(spdifrx->regmap, STM32_SPDIFRX_DR, ®); + regmap_read(spdifrx->regmap, STM32_SPDIFRX_CSR, ®); + + spin_unlock_irqrestore(&spdifrx->lock, flags); +} + +static int stm32_spdifrx_dma_ctrl_register(struct device *dev, + struct stm32_spdifrx_data *spdifrx) +{ + int ret; + + spdifrx->ctrl_chan = dma_request_chan(dev, "rx-ctrl"); + if (IS_ERR(spdifrx->ctrl_chan)) { + dev_err(dev, "dma_request_slave_channel failed\n"); + return PTR_ERR(spdifrx->ctrl_chan); + } + + spdifrx->dmab = devm_kzalloc(dev, sizeof(struct snd_dma_buffer), + GFP_KERNEL); + if (!spdifrx->dmab) + return -ENOMEM; + + spdifrx->dmab->dev.type = SNDRV_DMA_TYPE_DEV_IRAM; + spdifrx->dmab->dev.dev = dev; + ret = snd_dma_alloc_pages(spdifrx->dmab->dev.type, dev, + SPDIFRX_CSR_BUF_LENGTH, spdifrx->dmab); + if (ret < 0) { + dev_err(dev, "snd_dma_alloc_pages returned error %d\n", ret); + return ret; + } + + spdifrx->slave_config.direction = DMA_DEV_TO_MEM; + spdifrx->slave_config.src_addr = (dma_addr_t)(spdifrx->phys_addr + + STM32_SPDIFRX_CSR); + spdifrx->slave_config.dst_addr = spdifrx->dmab->addr; + spdifrx->slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdifrx->slave_config.src_maxburst = 1; + + ret = dmaengine_slave_config(spdifrx->ctrl_chan, + &spdifrx->slave_config); + if (ret < 0) { + dev_err(dev, "dmaengine_slave_config returned error %d\n", ret); + spdifrx->ctrl_chan = NULL; + } + + return ret; +}; + +static const char * const spdifrx_enum_input[] = { + "in0", "in1", "in2", "in3" +}; + +/* By default CS bits are retrieved from channel A */ +static const char * const spdifrx_enum_cs_channel[] = { + "A", "B" +}; + +static SOC_ENUM_SINGLE_DECL(ctrl_enum_input, + STM32_SPDIFRX_CR, SPDIFRX_CR_INSEL_SHIFT, + spdifrx_enum_input); + +static SOC_ENUM_SINGLE_DECL(ctrl_enum_cs_channel, + STM32_SPDIFRX_CR, SPDIFRX_CR_CHSEL_SHIFT, + spdifrx_enum_cs_channel); + +static int stm32_spdifrx_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 stm32_spdifrx_ub_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 stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx) +{ + int ret = 0; + + memset(spdifrx->cs, 0, SPDIFRX_CS_BYTES_NB); + memset(spdifrx->ub, 0, SPDIFRX_UB_BYTES_NB); + + ret = stm32_spdifrx_dma_ctrl_start(spdifrx); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(spdifrx->kclk); + if (ret) { + dev_err(&spdifrx->pdev->dev, "Enable kclk failed: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_CBDMAEN, SPDIFRX_CR_CBDMAEN); + if (ret < 0) + goto end; + + ret = stm32_spdifrx_start_sync(spdifrx); + if (ret < 0) + goto end; + + if (wait_for_completion_interruptible_timeout(&spdifrx->cs_completion, + msecs_to_jiffies(100)) + <= 0) { + dev_err(&spdifrx->pdev->dev, "Failed to get control data\n"); + ret = -EAGAIN; + } + + stm32_spdifrx_stop(spdifrx); + stm32_spdifrx_dma_ctrl_stop(spdifrx); + +end: + clk_disable_unprepare(spdifrx->kclk); + + return ret; +} + +static int stm32_spdifrx_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + + stm32_spdifrx_get_ctrl_data(spdifrx); + + ucontrol->value.iec958.status[0] = spdifrx->cs[0]; + ucontrol->value.iec958.status[1] = spdifrx->cs[1]; + ucontrol->value.iec958.status[2] = spdifrx->cs[2]; + ucontrol->value.iec958.status[3] = spdifrx->cs[3]; + ucontrol->value.iec958.status[4] = spdifrx->cs[4]; + + return 0; +} + +static int stm32_spdif_user_bits_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + + stm32_spdifrx_get_ctrl_data(spdifrx); + + ucontrol->value.iec958.status[0] = spdifrx->ub[0]; + ucontrol->value.iec958.status[1] = spdifrx->ub[1]; + ucontrol->value.iec958.status[2] = spdifrx->ub[2]; + ucontrol->value.iec958.status[3] = spdifrx->ub[3]; + ucontrol->value.iec958.status[4] = spdifrx->ub[4]; + + return 0; +} + +static struct snd_kcontrol_new stm32_spdifrx_iec_ctrls[] = { + /* Channel status control */ + { + .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 = stm32_spdifrx_info, + .get = stm32_spdifrx_capture_get, + }, + /* User bits control */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 User Bit Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = stm32_spdifrx_ub_info, + .get = stm32_spdif_user_bits_get, + }, +}; + +static struct snd_kcontrol_new stm32_spdifrx_ctrls[] = { + SOC_ENUM("SPDIFRX input", ctrl_enum_input), + SOC_ENUM("SPDIFRX CS channel", ctrl_enum_cs_channel), +}; + +static int stm32_spdifrx_dai_register_ctrls(struct snd_soc_dai *cpu_dai) +{ + int ret; + + ret = snd_soc_add_dai_controls(cpu_dai, stm32_spdifrx_iec_ctrls, + ARRAY_SIZE(stm32_spdifrx_iec_ctrls)); + if (ret < 0) + return ret; + + return snd_soc_add_component_controls(cpu_dai->component, + stm32_spdifrx_ctrls, + ARRAY_SIZE(stm32_spdifrx_ctrls)); +} + +static int stm32_spdifrx_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(cpu_dai->dev); + + spdifrx->dma_params.addr = (dma_addr_t)(spdifrx->phys_addr + + STM32_SPDIFRX_DR); + spdifrx->dma_params.maxburst = 1; + + snd_soc_dai_init_dma_data(cpu_dai, NULL, &spdifrx->dma_params); + + return stm32_spdifrx_dai_register_ctrls(cpu_dai); +} + +static bool stm32_spdifrx_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_SPDIFRX_CR: + case STM32_SPDIFRX_IMR: + case STM32_SPDIFRX_SR: + case STM32_SPDIFRX_IFCR: + case STM32_SPDIFRX_DR: + case STM32_SPDIFRX_CSR: + case STM32_SPDIFRX_DIR: + return true; + default: + return false; + } +} + +static bool stm32_spdifrx_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg == STM32_SPDIFRX_DR) + return true; + + return false; +} + +static bool stm32_spdifrx_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_SPDIFRX_CR: + case STM32_SPDIFRX_IMR: + case STM32_SPDIFRX_IFCR: + return true; + default: + return false; + } +} + +static const struct regmap_config stm32_h7_spdifrx_regmap_conf = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM32_SPDIFRX_DIR, + .readable_reg = stm32_spdifrx_readable_reg, + .volatile_reg = stm32_spdifrx_volatile_reg, + .writeable_reg = stm32_spdifrx_writeable_reg, + .fast_io = true, +}; + +static irqreturn_t stm32_spdifrx_isr(int irq, void *devid) +{ + struct stm32_spdifrx_data *spdifrx = (struct stm32_spdifrx_data *)devid; + struct platform_device *pdev = spdifrx->pdev; + unsigned int cr, mask, sr, imr; + unsigned int flags; + int err = 0, err_xrun = 0; + + regmap_read(spdifrx->regmap, STM32_SPDIFRX_SR, &sr); + regmap_read(spdifrx->regmap, STM32_SPDIFRX_IMR, &imr); + + mask = imr & SPDIFRX_XIMR_MASK; + /* SERR, TERR, FERR IRQs are generated if IFEIE is set */ + if (mask & SPDIFRX_IMR_IFEIE) + mask |= (SPDIFRX_IMR_IFEIE << 1) | (SPDIFRX_IMR_IFEIE << 2); + + flags = sr & mask; + if (!flags) { + dev_err(&pdev->dev, "Unexpected IRQ. rflags=%#x, imr=%#x\n", + sr, imr); + return IRQ_NONE; + } + + /* Clear IRQs */ + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IFCR, + SPDIFRX_XIFCR_MASK, flags); + + if (flags & SPDIFRX_SR_PERR) { + dev_dbg(&pdev->dev, "Parity error\n"); + err_xrun = 1; + } + + if (flags & SPDIFRX_SR_OVR) { + dev_dbg(&pdev->dev, "Overrun error\n"); + err_xrun = 1; + } + + if (flags & SPDIFRX_SR_SBD) + dev_dbg(&pdev->dev, "Synchronization block detected\n"); + + if (flags & SPDIFRX_SR_SYNCD) { + dev_dbg(&pdev->dev, "Synchronization done\n"); + + /* Enable spdifrx */ + cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_ENABLE); + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_SPDIFEN_MASK, cr); + } + + if (flags & SPDIFRX_SR_FERR) { + dev_dbg(&pdev->dev, "Frame error\n"); + err = 1; + } + + if (flags & SPDIFRX_SR_SERR) { + dev_dbg(&pdev->dev, "Synchronization error\n"); + err = 1; + } + + if (flags & SPDIFRX_SR_TERR) { + dev_dbg(&pdev->dev, "Timeout error\n"); + err = 1; + } + + if (err) { + /* SPDIFRX in STATE_STOP. Disable SPDIFRX to clear errors */ + cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_DISABLE); + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_SPDIFEN_MASK, cr); + + spin_lock(&spdifrx->irq_lock); + if (spdifrx->substream) + snd_pcm_stop(spdifrx->substream, + SNDRV_PCM_STATE_DISCONNECTED); + spin_unlock(&spdifrx->irq_lock); + + return IRQ_HANDLED; + } + + spin_lock(&spdifrx->irq_lock); + if (err_xrun && spdifrx->substream) + snd_pcm_stop_xrun(spdifrx->substream); + spin_unlock(&spdifrx->irq_lock); + + return IRQ_HANDLED; +} + +static int stm32_spdifrx_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + int ret; + + spin_lock_irqsave(&spdifrx->irq_lock, flags); + spdifrx->substream = substream; + spin_unlock_irqrestore(&spdifrx->irq_lock, flags); + + ret = clk_prepare_enable(spdifrx->kclk); + if (ret) + dev_err(&spdifrx->pdev->dev, "Enable kclk failed: %d\n", ret); + + return ret; +} + +static int stm32_spdifrx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + int data_size = params_width(params); + int fmt; + + switch (data_size) { + case 16: + fmt = SPDIFRX_DRFMT_PACKED; + break; + case 32: + fmt = SPDIFRX_DRFMT_LEFT; + break; + default: + dev_err(&spdifrx->pdev->dev, "Unexpected data format\n"); + return -EINVAL; + } + + /* + * Set buswidth to 4 bytes for all data formats. + * Packed format: transfer 2 x 2 bytes samples + * Left format: transfer 1 x 3 bytes samples + 1 dummy byte + */ + spdifrx->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + snd_soc_dai_init_dma_data(cpu_dai, NULL, &spdifrx->dma_params); + + return regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_DRFMT_MASK, + SPDIFRX_CR_DRFMTSET(fmt)); +} + +static int stm32_spdifrx_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IMR, + SPDIFRX_IMR_OVRIE, SPDIFRX_IMR_OVRIE); + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_RXDMAEN, SPDIFRX_CR_RXDMAEN); + + ret = stm32_spdifrx_start_sync(spdifrx); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + stm32_spdifrx_stop(spdifrx); + break; + default: + return -EINVAL; + } + + return ret; +} + +static void stm32_spdifrx_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long flags; + + spin_lock_irqsave(&spdifrx->irq_lock, flags); + spdifrx->substream = NULL; + spin_unlock_irqrestore(&spdifrx->irq_lock, flags); + + clk_disable_unprepare(spdifrx->kclk); +} + +static const struct snd_soc_dai_ops stm32_spdifrx_pcm_dai_ops = { + .startup = stm32_spdifrx_startup, + .hw_params = stm32_spdifrx_hw_params, + .trigger = stm32_spdifrx_trigger, + .shutdown = stm32_spdifrx_shutdown, +}; + +static struct snd_soc_dai_driver stm32_spdifrx_dai[] = { + { + .probe = stm32_spdifrx_dai_probe, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &stm32_spdifrx_pcm_dai_ops, + } +}; + +static const struct snd_pcm_hardware stm32_spdifrx_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_max = 2048, /* MDMA constraint */ + .periods_min = 2, + .periods_max = 8, +}; + +static const struct snd_soc_component_driver stm32_spdifrx_component = { + .name = "stm32-spdifrx", +}; + +static const struct snd_dmaengine_pcm_config stm32_spdifrx_pcm_config = { + .pcm_hardware = &stm32_spdifrx_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static const struct of_device_id stm32_spdifrx_ids[] = { + { + .compatible = "st,stm32h7-spdifrx", + .data = &stm32_h7_spdifrx_regmap_conf + }, + {} +}; + +static int stm32_spdifrx_parse_of(struct platform_device *pdev, + struct stm32_spdifrx_data *spdifrx) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; + struct resource *res; + + if (!np) + return -ENODEV; + + of_id = of_match_device(stm32_spdifrx_ids, &pdev->dev); + if (of_id) + spdifrx->regmap_conf = + (const struct regmap_config *)of_id->data; + else + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spdifrx->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spdifrx->base)) + return PTR_ERR(spdifrx->base); + + spdifrx->phys_addr = res->start; + + spdifrx->kclk = devm_clk_get(&pdev->dev, "kclk"); + if (IS_ERR(spdifrx->kclk)) { + dev_err(&pdev->dev, "Could not get kclk\n"); + return PTR_ERR(spdifrx->kclk); + } + + spdifrx->irq = platform_get_irq(pdev, 0); + if (spdifrx->irq < 0) { + dev_err(&pdev->dev, "No irq for node %s\n", pdev->name); + return spdifrx->irq; + } + + return 0; +} + +static int stm32_spdifrx_probe(struct platform_device *pdev) +{ + struct stm32_spdifrx_data *spdifrx; + struct reset_control *rst; + const struct snd_dmaengine_pcm_config *pcm_config = NULL; + int ret; + + spdifrx = devm_kzalloc(&pdev->dev, sizeof(*spdifrx), GFP_KERNEL); + if (!spdifrx) + return -ENOMEM; + + spdifrx->pdev = pdev; + init_completion(&spdifrx->cs_completion); + spin_lock_init(&spdifrx->lock); + spin_lock_init(&spdifrx->irq_lock); + + platform_set_drvdata(pdev, spdifrx); + + ret = stm32_spdifrx_parse_of(pdev, spdifrx); + if (ret) + return ret; + + spdifrx->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "kclk", + spdifrx->base, + spdifrx->regmap_conf); + if (IS_ERR(spdifrx->regmap)) { + dev_err(&pdev->dev, "Regmap init failed\n"); + return PTR_ERR(spdifrx->regmap); + } + + ret = devm_request_irq(&pdev->dev, spdifrx->irq, stm32_spdifrx_isr, 0, + dev_name(&pdev->dev), spdifrx); + if (ret) { + dev_err(&pdev->dev, "IRQ request returned %d\n", ret); + return ret; + } + + rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_spdifrx_component, + stm32_spdifrx_dai, + ARRAY_SIZE(stm32_spdifrx_dai)); + if (ret) + return ret; + + ret = stm32_spdifrx_dma_ctrl_register(&pdev->dev, spdifrx); + if (ret) + goto error; + + pcm_config = &stm32_spdifrx_pcm_config; + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0); + if (ret) { + dev_err(&pdev->dev, "PCM DMA register returned %d\n", ret); + goto error; + } + + return 0; + +error: + if (!IS_ERR(spdifrx->ctrl_chan)) + dma_release_channel(spdifrx->ctrl_chan); + if (spdifrx->dmab) + snd_dma_free_pages(spdifrx->dmab); + + return ret; +} + +static int stm32_spdifrx_remove(struct platform_device *pdev) +{ + struct stm32_spdifrx_data *spdifrx = platform_get_drvdata(pdev); + + if (spdifrx->ctrl_chan) + dma_release_channel(spdifrx->ctrl_chan); + + if (spdifrx->dmab) + snd_dma_free_pages(spdifrx->dmab); + + return 0; +} + +MODULE_DEVICE_TABLE(of, stm32_spdifrx_ids); + +static struct platform_driver stm32_spdifrx_driver = { + .driver = { + .name = "st,stm32-spdifrx", + .of_match_table = stm32_spdifrx_ids, + }, + .probe = stm32_spdifrx_probe, + .remove = stm32_spdifrx_remove, +}; + +module_platform_driver(stm32_spdifrx_driver); + +MODULE_DESCRIPTION("STM32 Soc spdifrx Interface"); +MODULE_AUTHOR("Olivier Moysan, "); +MODULE_ALIAS("platform:stm32-spdifrx"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig new file mode 100644 index 000000000..22408bc2d --- /dev/null +++ b/sound/soc/sunxi/Kconfig @@ -0,0 +1,48 @@ +menu "Allwinner SoC Audio support" + depends on ARCH_SUNXI || COMPILE_TEST + +config SND_SUN4I_CODEC + tristate "Allwinner A10 Codec Support" + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Select Y or M to add support for the Codec embedded in the Allwinner + A10 and affiliated SoCs. + +config SND_SUN8I_CODEC + tristate "Allwinner SUN8I audio codec" + depends on OF + depends on MACH_SUN8I || COMPILE_TEST + select REGMAP_MMIO + help + This option enables the digital part of the internal audio codec for + Allwinner sun8i SoC (and particularly A33). + + Say Y or M if you want to add sun8i digital audio codec support. + +config SND_SUN8I_CODEC_ANALOG + tristate "Allwinner sun8i Codec Analog Controls Support" + depends on MACH_SUN8I || (ARM64 && ARCH_SUNXI) || COMPILE_TEST + select REGMAP + help + Say Y or M if you want to add support for the analog controls for + the codec embedded in newer Allwinner SoCs. + +config SND_SUN4I_I2S + tristate "Allwinner A10 I2S Support" + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to + the Allwinner A10 I2S. You will also need to select the + individual machine drivers to support below. + +config SND_SUN4I_SPDIF + tristate "Allwinner A10 SPDIF Support" + depends on OF + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M to add support for the S/PDIF audio block in the Allwinner + A10 and affiliated SoCs. +endmenu diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile new file mode 100644 index 000000000..4a9ef6738 --- /dev/null +++ b/sound/soc/sunxi/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o +obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o +obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o +obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o +obj-$(CONFIG_SND_SUN8I_CODEC) += sun8i-codec.o diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c new file mode 100644 index 000000000..16e8f7428 --- /dev/null +++ b/sound/soc/sunxi/sun4i-codec.c @@ -0,0 +1,1745 @@ +/* + * Copyright 2014 Emilio López + * Copyright 2014 Jon Smirl + * Copyright 2015 Maxime Ripard + * Copyright 2015 Adam Sampson + * Copyright 2016 Chen-Yu Tsai + * + * Based on the Allwinner SDK driver, released under the GPL. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Codec DAC digital controls and FIFO registers */ +#define SUN4I_CODEC_DAC_DPC (0x00) +#define SUN4I_CODEC_DAC_DPC_EN_DA (31) +#define SUN4I_CODEC_DAC_DPC_DVOL (12) +#define SUN4I_CODEC_DAC_FIFOC (0x04) +#define SUN4I_CODEC_DAC_FIFOC_DAC_FS (29) +#define SUN4I_CODEC_DAC_FIFOC_FIR_VERSION (28) +#define SUN4I_CODEC_DAC_FIFOC_SEND_LASAT (26) +#define SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE (24) +#define SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT (21) +#define SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL (8) +#define SUN4I_CODEC_DAC_FIFOC_MONO_EN (6) +#define SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS (5) +#define SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN (4) +#define SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH (0) +#define SUN4I_CODEC_DAC_FIFOS (0x08) +#define SUN4I_CODEC_DAC_TXDATA (0x0c) + +/* Codec DAC side analog signal controls */ +#define SUN4I_CODEC_DAC_ACTL (0x10) +#define SUN4I_CODEC_DAC_ACTL_DACAENR (31) +#define SUN4I_CODEC_DAC_ACTL_DACAENL (30) +#define SUN4I_CODEC_DAC_ACTL_MIXEN (29) +#define SUN4I_CODEC_DAC_ACTL_LDACLMIXS (15) +#define SUN4I_CODEC_DAC_ACTL_RDACRMIXS (14) +#define SUN4I_CODEC_DAC_ACTL_LDACRMIXS (13) +#define SUN4I_CODEC_DAC_ACTL_DACPAS (8) +#define SUN4I_CODEC_DAC_ACTL_MIXPAS (7) +#define SUN4I_CODEC_DAC_ACTL_PA_MUTE (6) +#define SUN4I_CODEC_DAC_ACTL_PA_VOL (0) +#define SUN4I_CODEC_DAC_TUNE (0x14) +#define SUN4I_CODEC_DAC_DEBUG (0x18) + +/* Codec ADC digital controls and FIFO registers */ +#define SUN4I_CODEC_ADC_FIFOC (0x1c) +#define SUN4I_CODEC_ADC_FIFOC_ADC_FS (29) +#define SUN4I_CODEC_ADC_FIFOC_EN_AD (28) +#define SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE (24) +#define SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL (8) +#define SUN4I_CODEC_ADC_FIFOC_MONO_EN (7) +#define SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS (6) +#define SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN (4) +#define SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH (0) +#define SUN4I_CODEC_ADC_FIFOS (0x20) +#define SUN4I_CODEC_ADC_RXDATA (0x24) + +/* Codec ADC side analog signal controls */ +#define SUN4I_CODEC_ADC_ACTL (0x28) +#define SUN4I_CODEC_ADC_ACTL_ADC_R_EN (31) +#define SUN4I_CODEC_ADC_ACTL_ADC_L_EN (30) +#define SUN4I_CODEC_ADC_ACTL_PREG1EN (29) +#define SUN4I_CODEC_ADC_ACTL_PREG2EN (28) +#define SUN4I_CODEC_ADC_ACTL_VMICEN (27) +#define SUN4I_CODEC_ADC_ACTL_VADCG (20) +#define SUN4I_CODEC_ADC_ACTL_ADCIS (17) +#define SUN4I_CODEC_ADC_ACTL_PA_EN (4) +#define SUN4I_CODEC_ADC_ACTL_DDE (3) +#define SUN4I_CODEC_ADC_DEBUG (0x2c) + +/* FIFO counters */ +#define SUN4I_CODEC_DAC_TXCNT (0x30) +#define SUN4I_CODEC_ADC_RXCNT (0x34) + +/* Calibration register (sun7i only) */ +#define SUN7I_CODEC_AC_DAC_CAL (0x38) + +/* Microphone controls (sun7i only) */ +#define SUN7I_CODEC_AC_MIC_PHONE_CAL (0x3c) + +/* + * sun6i specific registers + * + * sun6i shares the same digital control and FIFO registers as sun4i, + * but only the DAC digital controls are at the same offset. The others + * have been moved around to accommodate extra analog controls. + */ + +/* Codec DAC digital controls and FIFO registers */ +#define SUN6I_CODEC_ADC_FIFOC (0x10) +#define SUN6I_CODEC_ADC_FIFOC_EN_AD (28) +#define SUN6I_CODEC_ADC_FIFOS (0x14) +#define SUN6I_CODEC_ADC_RXDATA (0x18) + +/* Output mixer and gain controls */ +#define SUN6I_CODEC_OM_DACA_CTRL (0x20) +#define SUN6I_CODEC_OM_DACA_CTRL_DACAREN (31) +#define SUN6I_CODEC_OM_DACA_CTRL_DACALEN (30) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIXEN (29) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIXEN (28) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC1 (23) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2 (22) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONE (21) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONEP (20) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR (19) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR (18) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL (17) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC1 (16) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC2 (15) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONE (14) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONEN (13) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL (12) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL (11) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR (10) +#define SUN6I_CODEC_OM_DACA_CTRL_RHPIS (9) +#define SUN6I_CODEC_OM_DACA_CTRL_LHPIS (8) +#define SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE (7) +#define SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE (6) +#define SUN6I_CODEC_OM_DACA_CTRL_HPVOL (0) +#define SUN6I_CODEC_OM_PA_CTRL (0x24) +#define SUN6I_CODEC_OM_PA_CTRL_HPPAEN (31) +#define SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL (29) +#define SUN6I_CODEC_OM_PA_CTRL_COMPTEN (28) +#define SUN6I_CODEC_OM_PA_CTRL_MIC1G (15) +#define SUN6I_CODEC_OM_PA_CTRL_MIC2G (12) +#define SUN6I_CODEC_OM_PA_CTRL_LINEING (9) +#define SUN6I_CODEC_OM_PA_CTRL_PHONEG (6) +#define SUN6I_CODEC_OM_PA_CTRL_PHONEPG (3) +#define SUN6I_CODEC_OM_PA_CTRL_PHONENG (0) + +/* Microphone, line out and phone out controls */ +#define SUN6I_CODEC_MIC_CTRL (0x28) +#define SUN6I_CODEC_MIC_CTRL_HBIASEN (31) +#define SUN6I_CODEC_MIC_CTRL_MBIASEN (30) +#define SUN6I_CODEC_MIC_CTRL_MIC1AMPEN (28) +#define SUN6I_CODEC_MIC_CTRL_MIC1BOOST (25) +#define SUN6I_CODEC_MIC_CTRL_MIC2AMPEN (24) +#define SUN6I_CODEC_MIC_CTRL_MIC2BOOST (21) +#define SUN6I_CODEC_MIC_CTRL_MIC2SLT (20) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTLEN (19) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTREN (18) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTLSRC (17) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTRSRC (16) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTVC (11) +#define SUN6I_CODEC_MIC_CTRL_PHONEPREG (8) + +/* ADC mixer controls */ +#define SUN6I_CODEC_ADC_ACTL (0x2c) +#define SUN6I_CODEC_ADC_ACTL_ADCREN (31) +#define SUN6I_CODEC_ADC_ACTL_ADCLEN (30) +#define SUN6I_CODEC_ADC_ACTL_ADCRG (27) +#define SUN6I_CODEC_ADC_ACTL_ADCLG (24) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC1 (13) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC2 (12) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONE (11) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONEP (10) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_LINEINR (9) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXR (8) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXL (7) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC1 (6) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC2 (5) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONE (4) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONEN (3) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_LINEINL (2) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXL (1) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXR (0) + +/* Analog performance tuning controls */ +#define SUN6I_CODEC_ADDA_TUNE (0x30) + +/* Calibration controls */ +#define SUN6I_CODEC_CALIBRATION (0x34) + +/* FIFO counters */ +#define SUN6I_CODEC_DAC_TXCNT (0x40) +#define SUN6I_CODEC_ADC_RXCNT (0x44) + +/* headset jack detection and button support registers */ +#define SUN6I_CODEC_HMIC_CTL (0x50) +#define SUN6I_CODEC_HMIC_DATA (0x54) + +/* TODO sun6i DAP (Digital Audio Processing) bits */ + +/* FIFO counters moved on A23 */ +#define SUN8I_A23_CODEC_DAC_TXCNT (0x1c) +#define SUN8I_A23_CODEC_ADC_RXCNT (0x20) + +/* TX FIFO moved on H3 */ +#define SUN8I_H3_CODEC_DAC_TXDATA (0x20) +#define SUN8I_H3_CODEC_DAC_DBG (0x48) +#define SUN8I_H3_CODEC_ADC_DBG (0x4c) + +/* TODO H3 DAP (Digital Audio Processing) bits */ + +struct sun4i_codec { + struct device *dev; + struct regmap *regmap; + struct clk *clk_apb; + struct clk *clk_module; + struct reset_control *rst; + struct gpio_desc *gpio_pa; + + /* ADC_FIFOC register is at different offset on different SoCs */ + struct regmap_field *reg_adc_fifoc; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; +}; + +static void sun4i_codec_start_playback(struct sun4i_codec *scodec) +{ + /* Flush TX FIFO */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH), + BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); + + /* Enable DAC DRQ */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN), + BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN)); +} + +static void sun4i_codec_stop_playback(struct sun4i_codec *scodec) +{ + /* Disable DAC DRQ */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN), + 0); +} + +static void sun4i_codec_start_capture(struct sun4i_codec *scodec) +{ + /* Enable ADC DRQ */ + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN)); +} + +static void sun4i_codec_stop_capture(struct sun4i_codec *scodec) +{ + /* Disable ADC DRQ */ + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0); +} + +static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_codec_start_playback(scodec); + else + sun4i_codec_start_capture(scodec); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_codec_stop_playback(scodec); + else + sun4i_codec_stop_capture(scodec); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + + /* Flush RX FIFO */ + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH), + BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH)); + + + /* Set RX FIFO trigger level */ + regmap_field_update_bits(scodec->reg_adc_fifoc, + 0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL, + 0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL); + + /* + * FIXME: Undocumented in the datasheet, but + * Allwinner's code mentions that it is related + * related to microphone gain + */ + if (of_device_is_compatible(scodec->dev->of_node, + "allwinner,sun4i-a10-codec") || + of_device_is_compatible(scodec->dev->of_node, + "allwinner,sun7i-a20-codec")) { + regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_ACTL, + 0x3 << 25, + 0x1 << 25); + } + + if (of_device_is_compatible(scodec->dev->of_node, + "allwinner,sun7i-a20-codec")) + /* FIXME: Undocumented bits */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_TUNE, + 0x3 << 8, + 0x1 << 8); + + return 0; +} + +static int sun4i_codec_prepare_playback(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + u32 val; + + /* Flush the TX FIFO */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH), + BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); + + /* Set TX FIFO Empty Trigger Level */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + 0x3f << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL, + 0xf << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL); + + if (substream->runtime->rate > 32000) + /* Use 64 bits FIR filter */ + val = 0; + else + /* Use 32 bits FIR filter */ + val = BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION); + + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION), + val); + + /* Send zeros when we have an underrun */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_SEND_LASAT), + 0); + + return 0; +}; + +static int sun4i_codec_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return sun4i_codec_prepare_playback(substream, dai); + + return sun4i_codec_prepare_capture(substream, dai); +} + +static unsigned long sun4i_codec_get_mod_freq(struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + + switch (rate) { + case 176400: + case 88200: + case 44100: + case 33075: + case 22050: + case 14700: + case 11025: + case 7350: + return 22579200; + + case 192000: + case 96000: + case 48000: + case 32000: + case 24000: + case 16000: + case 12000: + case 8000: + return 24576000; + + default: + return 0; + } +} + +static int sun4i_codec_get_hw_rate(struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + + switch (rate) { + case 192000: + case 176400: + return 6; + + case 96000: + case 88200: + return 7; + + case 48000: + case 44100: + return 0; + + case 32000: + case 33075: + return 1; + + case 24000: + case 22050: + return 2; + + case 16000: + case 14700: + return 3; + + case 12000: + case 11025: + return 4; + + case 8000: + case 7350: + return 5; + + default: + return -EINVAL; + } +} + +static int sun4i_codec_hw_params_capture(struct sun4i_codec *scodec, + struct snd_pcm_hw_params *params, + unsigned int hwrate) +{ + /* Set ADC sample rate */ + regmap_field_update_bits(scodec->reg_adc_fifoc, + 7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS, + hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS); + + /* Set the number of channels we want to use */ + if (params_channels(params) == 1) + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), + BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN)); + else + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), + 0); + + /* Set the number of sample bits to either 16 or 24 bits */ + if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) { + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS), + BIT(SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS)); + + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE), + 0); + + scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + } else { + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS), + 0); + + /* Fill most significant bits with valid data MSB */ + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE), + BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE)); + + scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + } + + return 0; +} + +static int sun4i_codec_hw_params_playback(struct sun4i_codec *scodec, + struct snd_pcm_hw_params *params, + unsigned int hwrate) +{ + u32 val; + + /* Set DAC sample rate */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + 7 << SUN4I_CODEC_DAC_FIFOC_DAC_FS, + hwrate << SUN4I_CODEC_DAC_FIFOC_DAC_FS); + + /* Set the number of channels we want to use */ + if (params_channels(params) == 1) + val = BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN); + else + val = 0; + + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN), + val); + + /* Set the number of sample bits to either 16 or 24 bits */ + if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) { + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS), + BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS)); + + /* Set TX FIFO mode to padding the LSBs with 0 */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE), + 0); + + scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + } else { + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS), + 0); + + /* Set TX FIFO mode to repeat the MSB */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE), + BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE)); + + scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + } + + return 0; +} + +static int sun4i_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + unsigned long clk_freq; + int ret, hwrate; + + clk_freq = sun4i_codec_get_mod_freq(params); + if (!clk_freq) + return -EINVAL; + + ret = clk_set_rate(scodec->clk_module, clk_freq); + if (ret) + return ret; + + hwrate = sun4i_codec_get_hw_rate(params); + if (hwrate < 0) + return hwrate; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return sun4i_codec_hw_params_playback(scodec, params, + hwrate); + + return sun4i_codec_hw_params_capture(scodec, params, + hwrate); +} + + +static unsigned int sun4i_codec_src_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, + 44100, 48000, 96000, 192000 +}; + + +static struct snd_pcm_hw_constraint_list sun4i_codec_constraints = { + .count = ARRAY_SIZE(sun4i_codec_src_rates), + .list = sun4i_codec_src_rates, +}; + + +static int sun4i_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &sun4i_codec_constraints); + + /* + * Stop issuing DRQ when we have room for less than 16 samples + * in our TX FIFO + */ + regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, + 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT, + 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT); + + return clk_prepare_enable(scodec->clk_module); +} + +static void sun4i_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); + + clk_disable_unprepare(scodec->clk_module); +} + +static const struct snd_soc_dai_ops sun4i_codec_dai_ops = { + .startup = sun4i_codec_startup, + .shutdown = sun4i_codec_shutdown, + .trigger = sun4i_codec_trigger, + .hw_params = sun4i_codec_hw_params, + .prepare = sun4i_codec_prepare, +}; + +static struct snd_soc_dai_driver sun4i_codec_dai = { + .name = "Codec", + .ops = &sun4i_codec_dai_ops, + .playback = { + .stream_name = "Codec Playback", + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 24, + }, + .capture = { + .stream_name = "Codec Capture", + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 24, + }, +}; + +/*** sun4i Codec ***/ +static const struct snd_kcontrol_new sun4i_codec_pa_mute = + SOC_DAPM_SINGLE("Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_PA_MUTE, 1, 0); + +static DECLARE_TLV_DB_SCALE(sun4i_codec_pa_volume_scale, -6300, 100, 1); + +static const struct snd_kcontrol_new sun4i_codec_controls[] = { + SOC_SINGLE_TLV("Power Amplifier Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_PA_VOL, 0x3F, 0, + sun4i_codec_pa_volume_scale), +}; + +static const struct snd_kcontrol_new sun4i_codec_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_LDACLMIXS, 1, 0), +}; + +static const struct snd_kcontrol_new sun4i_codec_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Right DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_RDACRMIXS, 1, 0), + SOC_DAPM_SINGLE("Left DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_LDACRMIXS, 1, 0), +}; + +static const struct snd_kcontrol_new sun4i_codec_pa_mixer_controls[] = { + SOC_DAPM_SINGLE("DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_DACPAS, 1, 0), + SOC_DAPM_SINGLE("Mixer Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_MIXPAS, 1, 0), +}; + +static const struct snd_soc_dapm_widget sun4i_codec_codec_dapm_widgets[] = { + /* Digital parts of the ADCs */ + SND_SOC_DAPM_SUPPLY("ADC", SUN4I_CODEC_ADC_FIFOC, + SUN4I_CODEC_ADC_FIFOC_EN_AD, 0, + NULL, 0), + + /* Digital parts of the DACs */ + SND_SOC_DAPM_SUPPLY("DAC", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_EN_DA, 0, + NULL, 0), + + /* Analog parts of the ADCs */ + SND_SOC_DAPM_ADC("Left ADC", "Codec Capture", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_ADC_L_EN, 0), + SND_SOC_DAPM_ADC("Right ADC", "Codec Capture", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_ADC_R_EN, 0), + + /* Analog parts of the DACs */ + SND_SOC_DAPM_DAC("Left DAC", "Codec Playback", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_DACAENL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Codec Playback", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_DACAENR, 0), + + /* Mixers */ + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + sun4i_codec_left_mixer_controls, + ARRAY_SIZE(sun4i_codec_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + sun4i_codec_right_mixer_controls, + ARRAY_SIZE(sun4i_codec_right_mixer_controls)), + + /* Global Mixer Enable */ + SND_SOC_DAPM_SUPPLY("Mixer Enable", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_MIXEN, 0, NULL, 0), + + /* VMIC */ + SND_SOC_DAPM_SUPPLY("VMIC", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_VMICEN, 0, NULL, 0), + + /* Mic Pre-Amplifiers */ + SND_SOC_DAPM_PGA("MIC1 Pre-Amplifier", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_PREG1EN, 0, NULL, 0), + + /* Power Amplifier */ + SND_SOC_DAPM_MIXER("Power Amplifier", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_PA_EN, 0, + sun4i_codec_pa_mixer_controls, + ARRAY_SIZE(sun4i_codec_pa_mixer_controls)), + SND_SOC_DAPM_SWITCH("Power Amplifier Mute", SND_SOC_NOPM, 0, 0, + &sun4i_codec_pa_mute), + + SND_SOC_DAPM_INPUT("Mic1"), + + SND_SOC_DAPM_OUTPUT("HP Right"), + SND_SOC_DAPM_OUTPUT("HP Left"), +}; + +static const struct snd_soc_dapm_route sun4i_codec_codec_dapm_routes[] = { + /* Left ADC / DAC Routes */ + { "Left ADC", NULL, "ADC" }, + { "Left DAC", NULL, "DAC" }, + + /* Right ADC / DAC Routes */ + { "Right ADC", NULL, "ADC" }, + { "Right DAC", NULL, "DAC" }, + + /* Right Mixer Routes */ + { "Right Mixer", NULL, "Mixer Enable" }, + { "Right Mixer", "Left DAC Playback Switch", "Left DAC" }, + { "Right Mixer", "Right DAC Playback Switch", "Right DAC" }, + + /* Left Mixer Routes */ + { "Left Mixer", NULL, "Mixer Enable" }, + { "Left Mixer", "Left DAC Playback Switch", "Left DAC" }, + + /* Power Amplifier Routes */ + { "Power Amplifier", "Mixer Playback Switch", "Left Mixer" }, + { "Power Amplifier", "Mixer Playback Switch", "Right Mixer" }, + { "Power Amplifier", "DAC Playback Switch", "Left DAC" }, + { "Power Amplifier", "DAC Playback Switch", "Right DAC" }, + + /* Headphone Output Routes */ + { "Power Amplifier Mute", "Switch", "Power Amplifier" }, + { "HP Right", NULL, "Power Amplifier Mute" }, + { "HP Left", NULL, "Power Amplifier Mute" }, + + /* Mic1 Routes */ + { "Left ADC", NULL, "MIC1 Pre-Amplifier" }, + { "Right ADC", NULL, "MIC1 Pre-Amplifier" }, + { "MIC1 Pre-Amplifier", NULL, "Mic1"}, + { "Mic1", NULL, "VMIC" }, +}; + +static const struct snd_soc_component_driver sun4i_codec_codec = { + .controls = sun4i_codec_controls, + .num_controls = ARRAY_SIZE(sun4i_codec_controls), + .dapm_widgets = sun4i_codec_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun4i_codec_codec_dapm_widgets), + .dapm_routes = sun4i_codec_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sun4i_codec_codec_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/*** sun6i Codec ***/ + +/* mixer controls */ +static const struct snd_kcontrol_new sun6i_codec_mixer_controls[] = { + SOC_DAPM_DOUBLE("DAC Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR, 1, 0), + SOC_DAPM_DOUBLE("DAC Reversed Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL, 1, 0), + SOC_DAPM_DOUBLE("Line In Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR, 1, 0), + SOC_DAPM_DOUBLE("Mic1 Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC1, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC1, 1, 0), + SOC_DAPM_DOUBLE("Mic2 Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC2, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2, 1, 0), +}; + +/* ADC mixer controls */ +static const struct snd_kcontrol_new sun6i_codec_adc_mixer_controls[] = { + SOC_DAPM_DOUBLE("Mixer Capture Switch", + SUN6I_CODEC_ADC_ACTL, + SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXL, + SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXR, 1, 0), + SOC_DAPM_DOUBLE("Mixer Reversed Capture Switch", + SUN6I_CODEC_ADC_ACTL, + SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXR, + SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXL, 1, 0), + SOC_DAPM_DOUBLE("Line In Capture Switch", + SUN6I_CODEC_ADC_ACTL, + SUN6I_CODEC_ADC_ACTL_LADCMIX_LINEINL, + SUN6I_CODEC_ADC_ACTL_RADCMIX_LINEINR, 1, 0), + SOC_DAPM_DOUBLE("Mic1 Capture Switch", + SUN6I_CODEC_ADC_ACTL, + SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC1, + SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC1, 1, 0), + SOC_DAPM_DOUBLE("Mic2 Capture Switch", + SUN6I_CODEC_ADC_ACTL, + SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC2, + SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC2, 1, 0), +}; + +/* headphone controls */ +static const char * const sun6i_codec_hp_src_enum_text[] = { + "DAC", "Mixer", +}; + +static SOC_ENUM_DOUBLE_DECL(sun6i_codec_hp_src_enum, + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LHPIS, + SUN6I_CODEC_OM_DACA_CTRL_RHPIS, + sun6i_codec_hp_src_enum_text); + +static const struct snd_kcontrol_new sun6i_codec_hp_src[] = { + SOC_DAPM_ENUM("Headphone Source Playback Route", + sun6i_codec_hp_src_enum), +}; + +/* microphone controls */ +static const char * const sun6i_codec_mic2_src_enum_text[] = { + "Mic2", "Mic3", +}; + +static SOC_ENUM_SINGLE_DECL(sun6i_codec_mic2_src_enum, + SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_MIC2SLT, + sun6i_codec_mic2_src_enum_text); + +static const struct snd_kcontrol_new sun6i_codec_mic2_src[] = { + SOC_DAPM_ENUM("Mic2 Amplifier Source Route", + sun6i_codec_mic2_src_enum), +}; + +/* line out controls */ +static const char * const sun6i_codec_lineout_src_enum_text[] = { + "Stereo", "Mono Differential", +}; + +static SOC_ENUM_DOUBLE_DECL(sun6i_codec_lineout_src_enum, + SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_LINEOUTLSRC, + SUN6I_CODEC_MIC_CTRL_LINEOUTRSRC, + sun6i_codec_lineout_src_enum_text); + +static const struct snd_kcontrol_new sun6i_codec_lineout_src[] = { + SOC_DAPM_ENUM("Line Out Source Playback Route", + sun6i_codec_lineout_src_enum), +}; + +/* volume / mute controls */ +static const DECLARE_TLV_DB_SCALE(sun6i_codec_dvol_scale, -7308, 116, 0); +static const DECLARE_TLV_DB_SCALE(sun6i_codec_hp_vol_scale, -6300, 100, 1); +static const DECLARE_TLV_DB_SCALE(sun6i_codec_out_mixer_pregain_scale, + -450, 150, 0); +static const DECLARE_TLV_DB_RANGE(sun6i_codec_lineout_vol_scale, + 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), +); +static const DECLARE_TLV_DB_RANGE(sun6i_codec_mic_gain_scale, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), +); + +static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = { + SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_DVOL, 0x3f, 1, + sun6i_codec_dvol_scale), + SOC_SINGLE_TLV("Headphone Playback Volume", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_HPVOL, 0x3f, 0, + sun6i_codec_hp_vol_scale), + SOC_SINGLE_TLV("Line Out Playback Volume", + SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_LINEOUTVC, 0x1f, 0, + sun6i_codec_lineout_vol_scale), + SOC_DOUBLE("Headphone Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE, + SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE, 1, 0), + SOC_DOUBLE("Line Out Playback Switch", + SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_LINEOUTLEN, + SUN6I_CODEC_MIC_CTRL_LINEOUTREN, 1, 0), + /* Mixer pre-gains */ + SOC_SINGLE_TLV("Line In Playback Volume", + SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_LINEING, + 0x7, 0, sun6i_codec_out_mixer_pregain_scale), + SOC_SINGLE_TLV("Mic1 Playback Volume", + SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_MIC1G, + 0x7, 0, sun6i_codec_out_mixer_pregain_scale), + SOC_SINGLE_TLV("Mic2 Playback Volume", + SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_MIC2G, + 0x7, 0, sun6i_codec_out_mixer_pregain_scale), + + /* Microphone Amp boost gains */ + SOC_SINGLE_TLV("Mic1 Boost Volume", SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_MIC1BOOST, 0x7, 0, + sun6i_codec_mic_gain_scale), + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_MIC2BOOST, 0x7, 0, + sun6i_codec_mic_gain_scale), + SOC_DOUBLE_TLV("ADC Capture Volume", + SUN6I_CODEC_ADC_ACTL, SUN6I_CODEC_ADC_ACTL_ADCLG, + SUN6I_CODEC_ADC_ACTL_ADCRG, 0x7, 0, + sun6i_codec_out_mixer_pregain_scale), +}; + +static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = { + /* Microphone inputs */ + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("MIC3"), + + /* Microphone Bias */ + SND_SOC_DAPM_SUPPLY("HBIAS", SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_HBIASEN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MBIAS", SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_MBIASEN, 0, NULL, 0), + + /* Mic input path */ + SND_SOC_DAPM_MUX("Mic2 Amplifier Source Route", + SND_SOC_NOPM, 0, 0, sun6i_codec_mic2_src), + SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_MIC1AMPEN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN6I_CODEC_MIC_CTRL, + SUN6I_CODEC_MIC_CTRL_MIC2AMPEN, 0, NULL, 0), + + /* Line In */ + SND_SOC_DAPM_INPUT("LINEIN"), + + /* Digital parts of the ADCs */ + SND_SOC_DAPM_SUPPLY("ADC Enable", SUN6I_CODEC_ADC_FIFOC, + SUN6I_CODEC_ADC_FIFOC_EN_AD, 0, + NULL, 0), + + /* Analog parts of the ADCs */ + SND_SOC_DAPM_ADC("Left ADC", "Codec Capture", SUN6I_CODEC_ADC_ACTL, + SUN6I_CODEC_ADC_ACTL_ADCLEN, 0), + SND_SOC_DAPM_ADC("Right ADC", "Codec Capture", SUN6I_CODEC_ADC_ACTL, + SUN6I_CODEC_ADC_ACTL_ADCREN, 0), + + /* ADC Mixers */ + SOC_MIXER_ARRAY("Left ADC Mixer", SND_SOC_NOPM, 0, 0, + sun6i_codec_adc_mixer_controls), + SOC_MIXER_ARRAY("Right ADC Mixer", SND_SOC_NOPM, 0, 0, + sun6i_codec_adc_mixer_controls), + + /* Digital parts of the DACs */ + SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_EN_DA, 0, + NULL, 0), + + /* Analog parts of the DACs */ + SND_SOC_DAPM_DAC("Left DAC", "Codec Playback", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_DACALEN, 0), + SND_SOC_DAPM_DAC("Right DAC", "Codec Playback", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_DACAREN, 0), + + /* Mixers */ + SOC_MIXER_ARRAY("Left Mixer", SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIXEN, 0, + sun6i_codec_mixer_controls), + SOC_MIXER_ARRAY("Right Mixer", SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_RMIXEN, 0, + sun6i_codec_mixer_controls), + + /* Headphone output path */ + SND_SOC_DAPM_MUX("Headphone Source Playback Route", + SND_SOC_NOPM, 0, 0, sun6i_codec_hp_src), + SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN6I_CODEC_OM_PA_CTRL, + SUN6I_CODEC_OM_PA_CTRL_HPPAEN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN6I_CODEC_OM_PA_CTRL, + SUN6I_CODEC_OM_PA_CTRL_COMPTEN, 0, NULL, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN6I_CODEC_OM_PA_CTRL, + SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL, 0x3, 0x3, 0), + SND_SOC_DAPM_OUTPUT("HP"), + + /* Line Out path */ + SND_SOC_DAPM_MUX("Line Out Source Playback Route", + SND_SOC_NOPM, 0, 0, sun6i_codec_lineout_src), + SND_SOC_DAPM_OUTPUT("LINEOUT"), +}; + +static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = { + /* DAC Routes */ + { "Left DAC", NULL, "DAC Enable" }, + { "Right DAC", NULL, "DAC Enable" }, + + /* Microphone Routes */ + { "Mic1 Amplifier", NULL, "MIC1"}, + { "Mic2 Amplifier Source Route", "Mic2", "MIC2" }, + { "Mic2 Amplifier Source Route", "Mic3", "MIC3" }, + { "Mic2 Amplifier", NULL, "Mic2 Amplifier Source Route"}, + + /* Left Mixer Routes */ + { "Left Mixer", "DAC Playback Switch", "Left DAC" }, + { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, + { "Left Mixer", "Line In Playback Switch", "LINEIN" }, + { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, + { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + /* Right Mixer Routes */ + { "Right Mixer", "DAC Playback Switch", "Right DAC" }, + { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, + { "Right Mixer", "Line In Playback Switch", "LINEIN" }, + { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, + { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + /* Left ADC Mixer Routes */ + { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" }, + { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" }, + { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, + { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, + { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, + + /* Right ADC Mixer Routes */ + { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" }, + { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" }, + { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, + { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, + { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, + + /* Headphone Routes */ + { "Headphone Source Playback Route", "DAC", "Left DAC" }, + { "Headphone Source Playback Route", "DAC", "Right DAC" }, + { "Headphone Source Playback Route", "Mixer", "Left Mixer" }, + { "Headphone Source Playback Route", "Mixer", "Right Mixer" }, + { "Headphone Amp", NULL, "Headphone Source Playback Route" }, + { "HP", NULL, "Headphone Amp" }, + { "HPCOM", NULL, "HPCOM Protection" }, + + /* Line Out Routes */ + { "Line Out Source Playback Route", "Stereo", "Left Mixer" }, + { "Line Out Source Playback Route", "Stereo", "Right Mixer" }, + { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" }, + { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, + { "LINEOUT", NULL, "Line Out Source Playback Route" }, + + /* ADC Routes */ + { "Left ADC", NULL, "ADC Enable" }, + { "Right ADC", NULL, "ADC Enable" }, + { "Left ADC", NULL, "Left ADC Mixer" }, + { "Right ADC", NULL, "Right ADC Mixer" }, +}; + +static const struct snd_soc_component_driver sun6i_codec_codec = { + .controls = sun6i_codec_codec_widgets, + .num_controls = ARRAY_SIZE(sun6i_codec_codec_widgets), + .dapm_widgets = sun6i_codec_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun6i_codec_codec_dapm_widgets), + .dapm_routes = sun6i_codec_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sun6i_codec_codec_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +/* sun8i A23 codec */ +static const struct snd_kcontrol_new sun8i_a23_codec_codec_controls[] = { + SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_DVOL, 0x3f, 1, + sun6i_codec_dvol_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_a23_codec_codec_widgets[] = { + /* Digital parts of the ADCs */ + SND_SOC_DAPM_SUPPLY("ADC Enable", SUN6I_CODEC_ADC_FIFOC, + SUN6I_CODEC_ADC_FIFOC_EN_AD, 0, NULL, 0), + /* Digital parts of the DACs */ + SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_EN_DA, 0, NULL, 0), + +}; + +static const struct snd_soc_component_driver sun8i_a23_codec_codec = { + .controls = sun8i_a23_codec_codec_controls, + .num_controls = ARRAY_SIZE(sun8i_a23_codec_codec_controls), + .dapm_widgets = sun8i_a23_codec_codec_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun8i_a23_codec_codec_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct snd_soc_component_driver sun4i_codec_component = { + .name = "sun4i-codec", +}; + +#define SUN4I_CODEC_RATES SNDRV_PCM_RATE_CONTINUOUS +#define SUN4I_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int sun4i_codec_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); + + snd_soc_dai_init_dma_data(dai, &scodec->playback_dma_data, + &scodec->capture_dma_data); + + return 0; +} + +static struct snd_soc_dai_driver dummy_cpu_dai = { + .name = "sun4i-codec-cpu-dai", + .probe = sun4i_codec_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SUN4I_CODEC_RATES, + .formats = SUN4I_CODEC_FORMATS, + .sig_bits = 24, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SUN4I_CODEC_RATES, + .formats = SUN4I_CODEC_FORMATS, + .sig_bits = 24, + }, +}; + +static struct snd_soc_dai_link *sun4i_codec_create_link(struct device *dev, + int *num_links) +{ + struct snd_soc_dai_link *link = devm_kzalloc(dev, sizeof(*link), + GFP_KERNEL); + if (!link) + return NULL; + + link->name = "cdc"; + link->stream_name = "CDC PCM"; + link->codec_dai_name = "Codec"; + link->cpu_dai_name = dev_name(dev); + link->codec_name = dev_name(dev); + link->platform_name = dev_name(dev); + link->dai_fmt = SND_SOC_DAIFMT_I2S; + + *num_links = 1; + + return link; +}; + +static int sun4i_codec_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(w->dapm->card); + + gpiod_set_value_cansleep(scodec->gpio_pa, + !!SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget sun4i_codec_card_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", sun4i_codec_spk_event), +}; + +static const struct snd_soc_dapm_route sun4i_codec_card_dapm_routes[] = { + { "Speaker", NULL, "HP Right" }, + { "Speaker", NULL, "HP Left" }, +}; + +static struct snd_soc_card *sun4i_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = "sun4i-codec"; + card->dapm_widgets = sun4i_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun4i_codec_card_dapm_widgets); + card->dapm_routes = sun4i_codec_card_dapm_routes; + card->num_dapm_routes = ARRAY_SIZE(sun4i_codec_card_dapm_routes); + + return card; +}; + +static const struct snd_soc_dapm_widget sun6i_codec_card_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", sun4i_codec_spk_event), +}; + +static struct snd_soc_card *sun6i_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = "A31 Audio Codec"; + card->dapm_widgets = sun6i_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + +/* Connect digital side enables to analog side widgets */ +static const struct snd_soc_dapm_route sun8i_codec_card_routes[] = { + /* ADC Routes */ + { "Left ADC", NULL, "ADC Enable" }, + { "Right ADC", NULL, "ADC Enable" }, + { "Codec Capture", NULL, "Left ADC" }, + { "Codec Capture", NULL, "Right ADC" }, + + /* DAC Routes */ + { "Left DAC", NULL, "DAC Enable" }, + { "Right DAC", NULL, "DAC Enable" }, + { "Left DAC", NULL, "Codec Playback" }, + { "Right DAC", NULL, "Codec Playback" }, +}; + +static struct snd_soc_aux_dev aux_dev = { + .name = "Codec Analog Controls", +}; + +static struct snd_soc_card *sun8i_a23_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + aux_dev.codec_of_node = of_parse_phandle(dev->of_node, + "allwinner,codec-analog-controls", + 0); + if (!aux_dev.codec_of_node) { + dev_err(dev, "Can't find analog controls for codec.\n"); + return ERR_PTR(-EINVAL); + }; + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = "A23 Audio Codec"; + card->dapm_widgets = sun6i_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); + card->dapm_routes = sun8i_codec_card_routes; + card->num_dapm_routes = ARRAY_SIZE(sun8i_codec_card_routes); + card->aux_dev = &aux_dev; + card->num_aux_devs = 1; + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + +static struct snd_soc_card *sun8i_h3_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + aux_dev.codec_of_node = of_parse_phandle(dev->of_node, + "allwinner,codec-analog-controls", + 0); + if (!aux_dev.codec_of_node) { + dev_err(dev, "Can't find analog controls for codec.\n"); + return ERR_PTR(-EINVAL); + }; + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = "H3 Audio Codec"; + card->dapm_widgets = sun6i_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); + card->dapm_routes = sun8i_codec_card_routes; + card->num_dapm_routes = ARRAY_SIZE(sun8i_codec_card_routes); + card->aux_dev = &aux_dev; + card->num_aux_devs = 1; + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + +static struct snd_soc_card *sun8i_v3s_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + aux_dev.codec_of_node = of_parse_phandle(dev->of_node, + "allwinner,codec-analog-controls", + 0); + if (!aux_dev.codec_of_node) { + dev_err(dev, "Can't find analog controls for codec.\n"); + return ERR_PTR(-EINVAL); + }; + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = "V3s Audio Codec"; + card->dapm_widgets = sun6i_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); + card->dapm_routes = sun8i_codec_card_routes; + card->num_dapm_routes = ARRAY_SIZE(sun8i_codec_card_routes); + card->aux_dev = &aux_dev; + card->num_aux_devs = 1; + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + +static const struct regmap_config sun4i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN4I_CODEC_ADC_RXCNT, +}; + +static const struct regmap_config sun6i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN6I_CODEC_HMIC_DATA, +}; + +static const struct regmap_config sun7i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN7I_CODEC_AC_MIC_PHONE_CAL, +}; + +static const struct regmap_config sun8i_a23_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN8I_A23_CODEC_ADC_RXCNT, +}; + +static const struct regmap_config sun8i_h3_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN8I_H3_CODEC_ADC_DBG, +}; + +static const struct regmap_config sun8i_v3s_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN8I_H3_CODEC_ADC_DBG, +}; + +struct sun4i_codec_quirks { + const struct regmap_config *regmap_config; + const struct snd_soc_component_driver *codec; + struct snd_soc_card * (*create_card)(struct device *dev); + struct reg_field reg_adc_fifoc; /* used for regmap_field */ + unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */ + unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */ + bool has_reset; +}; + +static const struct sun4i_codec_quirks sun4i_codec_quirks = { + .regmap_config = &sun4i_codec_regmap_config, + .codec = &sun4i_codec_codec, + .create_card = sun4i_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA, +}; + +static const struct sun4i_codec_quirks sun6i_a31_codec_quirks = { + .regmap_config = &sun6i_codec_regmap_config, + .codec = &sun6i_codec_codec, + .create_card = sun6i_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, + .has_reset = true, +}; + +static const struct sun4i_codec_quirks sun7i_codec_quirks = { + .regmap_config = &sun7i_codec_regmap_config, + .codec = &sun4i_codec_codec, + .create_card = sun4i_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA, +}; + +static const struct sun4i_codec_quirks sun8i_a23_codec_quirks = { + .regmap_config = &sun8i_a23_codec_regmap_config, + .codec = &sun8i_a23_codec_codec, + .create_card = sun8i_a23_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, + .has_reset = true, +}; + +static const struct sun4i_codec_quirks sun8i_h3_codec_quirks = { + .regmap_config = &sun8i_h3_codec_regmap_config, + /* + * TODO Share the codec structure with A23 for now. + * This should be split out when adding digital audio + * processing support for the H3. + */ + .codec = &sun8i_a23_codec_codec, + .create_card = sun8i_h3_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, + .has_reset = true, +}; + +static const struct sun4i_codec_quirks sun8i_v3s_codec_quirks = { + .regmap_config = &sun8i_v3s_codec_regmap_config, + /* + * TODO The codec structure should be split out, like + * H3, when adding digital audio processing support. + */ + .codec = &sun8i_a23_codec_codec, + .create_card = sun8i_v3s_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, + .has_reset = true, +}; + +static const struct of_device_id sun4i_codec_of_match[] = { + { + .compatible = "allwinner,sun4i-a10-codec", + .data = &sun4i_codec_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-codec", + .data = &sun6i_a31_codec_quirks, + }, + { + .compatible = "allwinner,sun7i-a20-codec", + .data = &sun7i_codec_quirks, + }, + { + .compatible = "allwinner,sun8i-a23-codec", + .data = &sun8i_a23_codec_quirks, + }, + { + .compatible = "allwinner,sun8i-h3-codec", + .data = &sun8i_h3_codec_quirks, + }, + { + .compatible = "allwinner,sun8i-v3s-codec", + .data = &sun8i_v3s_codec_quirks, + }, + {} +}; +MODULE_DEVICE_TABLE(of, sun4i_codec_of_match); + +static int sun4i_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct sun4i_codec *scodec; + const struct sun4i_codec_quirks *quirks; + struct resource *res; + void __iomem *base; + int ret; + + scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL); + if (!scodec) + return -ENOMEM; + + scodec->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed to map the registers\n"); + return PTR_ERR(base); + } + + quirks = of_device_get_match_data(&pdev->dev); + if (quirks == NULL) { + dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); + return -ENODEV; + } + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + quirks->regmap_config); + if (IS_ERR(scodec->regmap)) { + dev_err(&pdev->dev, "Failed to create our regmap\n"); + return PTR_ERR(scodec->regmap); + } + + /* Get the clocks from the DT */ + scodec->clk_apb = devm_clk_get(&pdev->dev, "apb"); + if (IS_ERR(scodec->clk_apb)) { + dev_err(&pdev->dev, "Failed to get the APB clock\n"); + return PTR_ERR(scodec->clk_apb); + } + + scodec->clk_module = devm_clk_get(&pdev->dev, "codec"); + if (IS_ERR(scodec->clk_module)) { + dev_err(&pdev->dev, "Failed to get the module clock\n"); + return PTR_ERR(scodec->clk_module); + } + + if (quirks->has_reset) { + scodec->rst = devm_reset_control_get_exclusive(&pdev->dev, + NULL); + if (IS_ERR(scodec->rst)) { + dev_err(&pdev->dev, "Failed to get reset control\n"); + return PTR_ERR(scodec->rst); + } + } + + scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", + GPIOD_OUT_LOW); + if (IS_ERR(scodec->gpio_pa)) { + ret = PTR_ERR(scodec->gpio_pa); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get pa gpio: %d\n", ret); + return ret; + } + + /* reg_field setup */ + scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_adc_fifoc); + if (IS_ERR(scodec->reg_adc_fifoc)) { + ret = PTR_ERR(scodec->reg_adc_fifoc); + dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", + ret); + return ret; + } + + /* Enable the bus clock */ + if (clk_prepare_enable(scodec->clk_apb)) { + dev_err(&pdev->dev, "Failed to enable the APB clock\n"); + return -EINVAL; + } + + /* Deassert the reset control */ + if (scodec->rst) { + ret = reset_control_deassert(scodec->rst); + if (ret) { + dev_err(&pdev->dev, + "Failed to deassert the reset control\n"); + goto err_clk_disable; + } + } + + /* DMA configuration for TX FIFO */ + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; + scodec->playback_dma_data.maxburst = 8; + scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + /* DMA configuration for RX FIFO */ + scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata; + scodec->capture_dma_data.maxburst = 8; + scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, + &sun4i_codec_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Failed to register our codec\n"); + goto err_assert_reset; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_codec_component, + &dummy_cpu_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Failed to register our DAI\n"); + goto err_assert_reset; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); + goto err_assert_reset; + } + + card = quirks->create_card(&pdev->dev); + if (IS_ERR(card)) { + ret = PTR_ERR(card); + dev_err(&pdev->dev, "Failed to create our card\n"); + goto err_assert_reset; + } + + snd_soc_card_set_drvdata(card, scodec); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "Failed to register our card\n"); + goto err_assert_reset; + } + + return 0; + +err_assert_reset: + if (scodec->rst) + reset_control_assert(scodec->rst); +err_clk_disable: + clk_disable_unprepare(scodec->clk_apb); + return ret; +} + +static int sun4i_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + if (scodec->rst) + reset_control_assert(scodec->rst); + clk_disable_unprepare(scodec->clk_apb); + + return 0; +} + +static struct platform_driver sun4i_codec_driver = { + .driver = { + .name = "sun4i-codec", + .of_match_table = sun4i_codec_of_match, + }, + .probe = sun4i_codec_probe, + .remove = sun4i_codec_remove, +}; +module_platform_driver(sun4i_codec_driver); + +MODULE_DESCRIPTION("Allwinner A10 codec driver"); +MODULE_AUTHOR("Emilio López "); +MODULE_AUTHOR("Jon Smirl "); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_AUTHOR("Chen-Yu Tsai "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c new file mode 100644 index 000000000..f248e5639 --- /dev/null +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -0,0 +1,1200 @@ +/* + * Copyright (C) 2015 Andrea Venturi + * Andrea Venturi + * + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define SUN4I_I2S_CTRL_REG 0x00 +#define SUN4I_I2S_CTRL_SDO_EN_MASK GENMASK(11, 8) +#define SUN4I_I2S_CTRL_SDO_EN(sdo) BIT(8 + (sdo)) +#define SUN4I_I2S_CTRL_MODE_MASK BIT(5) +#define SUN4I_I2S_CTRL_MODE_SLAVE (1 << 5) +#define SUN4I_I2S_CTRL_MODE_MASTER (0 << 5) +#define SUN4I_I2S_CTRL_TX_EN BIT(2) +#define SUN4I_I2S_CTRL_RX_EN BIT(1) +#define SUN4I_I2S_CTRL_GL_EN BIT(0) + +#define SUN4I_I2S_FMT0_REG 0x04 +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_MASK BIT(7) +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED (1 << 7) +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL (0 << 7) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_MASK BIT(6) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED (1 << 6) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL (0 << 6) +#define SUN4I_I2S_FMT0_SR_MASK GENMASK(5, 4) +#define SUN4I_I2S_FMT0_SR(sr) ((sr) << 4) +#define SUN4I_I2S_FMT0_WSS_MASK GENMASK(3, 2) +#define SUN4I_I2S_FMT0_WSS(wss) ((wss) << 2) +#define SUN4I_I2S_FMT0_FMT_MASK GENMASK(1, 0) +#define SUN4I_I2S_FMT0_FMT_RIGHT_J (2 << 0) +#define SUN4I_I2S_FMT0_FMT_LEFT_J (1 << 0) +#define SUN4I_I2S_FMT0_FMT_I2S (0 << 0) +#define SUN4I_I2S_FMT0_POLARITY_INVERTED (1) +#define SUN4I_I2S_FMT0_POLARITY_NORMAL (0) + +#define SUN4I_I2S_FMT1_REG 0x08 +#define SUN4I_I2S_FIFO_TX_REG 0x0c +#define SUN4I_I2S_FIFO_RX_REG 0x10 + +#define SUN4I_I2S_FIFO_CTRL_REG 0x14 +#define SUN4I_I2S_FIFO_CTRL_FLUSH_TX BIT(25) +#define SUN4I_I2S_FIFO_CTRL_FLUSH_RX BIT(24) +#define SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK BIT(2) +#define SUN4I_I2S_FIFO_CTRL_TX_MODE(mode) ((mode) << 2) +#define SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK GENMASK(1, 0) +#define SUN4I_I2S_FIFO_CTRL_RX_MODE(mode) (mode) + +#define SUN4I_I2S_FIFO_STA_REG 0x18 + +#define SUN4I_I2S_DMA_INT_CTRL_REG 0x1c +#define SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN BIT(7) +#define SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN BIT(3) + +#define SUN4I_I2S_INT_STA_REG 0x20 + +#define SUN4I_I2S_CLK_DIV_REG 0x24 +#define SUN4I_I2S_CLK_DIV_MCLK_EN BIT(7) +#define SUN4I_I2S_CLK_DIV_BCLK_MASK GENMASK(6, 4) +#define SUN4I_I2S_CLK_DIV_BCLK(bclk) ((bclk) << 4) +#define SUN4I_I2S_CLK_DIV_MCLK_MASK GENMASK(3, 0) +#define SUN4I_I2S_CLK_DIV_MCLK(mclk) ((mclk) << 0) + +#define SUN4I_I2S_TX_CNT_REG 0x28 +#define SUN4I_I2S_RX_CNT_REG 0x2c + +#define SUN4I_I2S_TX_CHAN_SEL_REG 0x30 +#define SUN4I_I2S_CHAN_SEL(num_chan) (((num_chan) - 1) << 0) + +#define SUN4I_I2S_TX_CHAN_MAP_REG 0x34 +#define SUN4I_I2S_TX_CHAN_MAP(chan, sample) ((sample) << (chan << 2)) + +#define SUN4I_I2S_RX_CHAN_SEL_REG 0x38 +#define SUN4I_I2S_RX_CHAN_MAP_REG 0x3c + +/* Defines required for sun8i-h3 support */ +#define SUN8I_I2S_CTRL_BCLK_OUT BIT(18) +#define SUN8I_I2S_CTRL_LRCK_OUT BIT(17) + +#define SUN8I_I2S_FMT0_LRCK_PERIOD_MASK GENMASK(17, 8) +#define SUN8I_I2S_FMT0_LRCK_PERIOD(period) ((period - 1) << 8) + +#define SUN8I_I2S_INT_STA_REG 0x0c +#define SUN8I_I2S_FIFO_TX_REG 0x20 + +#define SUN8I_I2S_CHAN_CFG_REG 0x30 +#define SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM_MASK GENMASK(6, 4) +#define SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM(chan) ((chan - 1) << 4) +#define SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM_MASK GENMASK(2, 0) +#define SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM(chan) (chan - 1) + +#define SUN8I_I2S_TX_CHAN_MAP_REG 0x44 +#define SUN8I_I2S_TX_CHAN_SEL_REG 0x34 +#define SUN8I_I2S_TX_CHAN_OFFSET_MASK GENMASK(13, 12) +#define SUN8I_I2S_TX_CHAN_OFFSET(offset) (offset << 12) +#define SUN8I_I2S_TX_CHAN_EN_MASK GENMASK(11, 4) +#define SUN8I_I2S_TX_CHAN_EN(num_chan) (((1 << num_chan) - 1) << 4) + +#define SUN8I_I2S_RX_CHAN_SEL_REG 0x54 +#define SUN8I_I2S_RX_CHAN_MAP_REG 0x58 + +/** + * struct sun4i_i2s_quirks - Differences between SoC variants. + * + * @has_reset: SoC needs reset deasserted. + * @has_slave_select_bit: SoC has a bit to enable slave mode. + * @has_fmt_set_lrck_period: SoC requires lrclk period to be set. + * @has_chcfg: tx and rx slot number need to be set. + * @has_chsel_tx_chen: SoC requires that the tx channels are enabled. + * @has_chsel_offset: SoC uses offset for selecting dai operational mode. + * @reg_offset_txdata: offset of the tx fifo. + * @sun4i_i2s_regmap: regmap config to use. + * @mclk_offset: Value by which mclkdiv needs to be adjusted. + * @bclk_offset: Value by which bclkdiv needs to be adjusted. + * @fmt_offset: Value by which wss and sr needs to be adjusted. + * @field_clkdiv_mclk_en: regmap field to enable mclk output. + * @field_fmt_wss: regmap field to set word select size. + * @field_fmt_sr: regmap field to set sample resolution. + * @field_fmt_bclk: regmap field to set clk polarity. + * @field_fmt_lrclk: regmap field to set frame polarity. + * @field_fmt_mode: regmap field to set the operational mode. + * @field_txchanmap: location of the tx channel mapping register. + * @field_rxchanmap: location of the rx channel mapping register. + * @field_txchansel: location of the tx channel select bit fields. + * @field_rxchansel: location of the rx channel select bit fields. + */ +struct sun4i_i2s_quirks { + bool has_reset; + bool has_slave_select_bit; + bool has_fmt_set_lrck_period; + bool has_chcfg; + bool has_chsel_tx_chen; + bool has_chsel_offset; + unsigned int reg_offset_txdata; /* TX FIFO */ + const struct regmap_config *sun4i_i2s_regmap; + unsigned int mclk_offset; + unsigned int bclk_offset; + unsigned int fmt_offset; + + /* Register fields for i2s */ + struct reg_field field_clkdiv_mclk_en; + struct reg_field field_fmt_wss; + struct reg_field field_fmt_sr; + struct reg_field field_fmt_bclk; + struct reg_field field_fmt_lrclk; + struct reg_field field_fmt_mode; + struct reg_field field_txchanmap; + struct reg_field field_rxchanmap; + struct reg_field field_txchansel; + struct reg_field field_rxchansel; +}; + +struct sun4i_i2s { + struct clk *bus_clk; + struct clk *mod_clk; + struct regmap *regmap; + struct reset_control *rst; + + unsigned int mclk_freq; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + /* Register fields for i2s */ + struct regmap_field *field_clkdiv_mclk_en; + struct regmap_field *field_fmt_wss; + struct regmap_field *field_fmt_sr; + struct regmap_field *field_fmt_bclk; + struct regmap_field *field_fmt_lrclk; + struct regmap_field *field_fmt_mode; + struct regmap_field *field_txchanmap; + struct regmap_field *field_rxchanmap; + struct regmap_field *field_txchansel; + struct regmap_field *field_rxchansel; + + const struct sun4i_i2s_quirks *variant; +}; + +struct sun4i_i2s_clk_div { + u8 div; + u8 val; +}; + +static const struct sun4i_i2s_clk_div sun4i_i2s_bclk_div[] = { + { .div = 2, .val = 0 }, + { .div = 4, .val = 1 }, + { .div = 6, .val = 2 }, + { .div = 8, .val = 3 }, + { .div = 12, .val = 4 }, + { .div = 16, .val = 5 }, + /* TODO - extend divide ratio supported by newer SoCs */ +}; + +static const struct sun4i_i2s_clk_div sun4i_i2s_mclk_div[] = { + { .div = 1, .val = 0 }, + { .div = 2, .val = 1 }, + { .div = 4, .val = 2 }, + { .div = 6, .val = 3 }, + { .div = 8, .val = 4 }, + { .div = 12, .val = 5 }, + { .div = 16, .val = 6 }, + { .div = 24, .val = 7 }, + /* TODO - extend divide ratio supported by newer SoCs */ +}; + +static int sun4i_i2s_get_bclk_div(struct sun4i_i2s *i2s, + unsigned long parent_rate, + unsigned int sampling_rate, + unsigned int word_size) +{ + int div = parent_rate / sampling_rate / word_size / 2; + int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_i2s_bclk_div); i++) { + const struct sun4i_i2s_clk_div *bdiv = &sun4i_i2s_bclk_div[i]; + + if (bdiv->div == div) + return bdiv->val; + } + + return -EINVAL; +} + +static int sun4i_i2s_get_mclk_div(struct sun4i_i2s *i2s, + unsigned int oversample_rate, + unsigned int module_rate, + unsigned int sampling_rate) +{ + int div = module_rate / sampling_rate / oversample_rate; + int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_i2s_mclk_div); i++) { + const struct sun4i_i2s_clk_div *mdiv = &sun4i_i2s_mclk_div[i]; + + if (mdiv->div == div) + return mdiv->val; + } + + return -EINVAL; +} + +static int sun4i_i2s_oversample_rates[] = { 128, 192, 256, 384, 512, 768 }; +static bool sun4i_i2s_oversample_is_valid(unsigned int oversample) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_i2s_oversample_rates); i++) + if (sun4i_i2s_oversample_rates[i] == oversample) + return true; + + return false; +} + +static int sun4i_i2s_set_clk_rate(struct snd_soc_dai *dai, + unsigned int rate, + unsigned int word_size) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int oversample_rate, clk_rate; + int bclk_div, mclk_div; + int ret; + + switch (rate) { + case 176400: + case 88200: + case 44100: + case 22050: + case 11025: + clk_rate = 22579200; + break; + + case 192000: + case 128000: + case 96000: + case 64000: + case 48000: + case 32000: + case 24000: + case 16000: + case 12000: + case 8000: + clk_rate = 24576000; + break; + + default: + dev_err(dai->dev, "Unsupported sample rate: %u\n", rate); + return -EINVAL; + } + + ret = clk_set_rate(i2s->mod_clk, clk_rate); + if (ret) + return ret; + + oversample_rate = i2s->mclk_freq / rate; + if (!sun4i_i2s_oversample_is_valid(oversample_rate)) { + dev_err(dai->dev, "Unsupported oversample rate: %d\n", + oversample_rate); + return -EINVAL; + } + + bclk_div = sun4i_i2s_get_bclk_div(i2s, i2s->mclk_freq, + rate, word_size); + if (bclk_div < 0) { + dev_err(dai->dev, "Unsupported BCLK divider: %d\n", bclk_div); + return -EINVAL; + } + + mclk_div = sun4i_i2s_get_mclk_div(i2s, oversample_rate, + clk_rate, rate); + if (mclk_div < 0) { + dev_err(dai->dev, "Unsupported MCLK divider: %d\n", mclk_div); + return -EINVAL; + } + + /* Adjust the clock division values if needed */ + bclk_div += i2s->variant->bclk_offset; + mclk_div += i2s->variant->mclk_offset; + + regmap_write(i2s->regmap, SUN4I_I2S_CLK_DIV_REG, + SUN4I_I2S_CLK_DIV_BCLK(bclk_div) | + SUN4I_I2S_CLK_DIV_MCLK(mclk_div)); + + regmap_field_write(i2s->field_clkdiv_mclk_en, 1); + + /* Set sync period */ + if (i2s->variant->has_fmt_set_lrck_period) + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, + SUN8I_I2S_FMT0_LRCK_PERIOD_MASK, + SUN8I_I2S_FMT0_LRCK_PERIOD(32)); + + return 0; +} + +static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + int sr, wss, channels; + u32 width; + + channels = params_channels(params); + if (channels != 2) { + dev_err(dai->dev, "Unsupported number of channels: %d\n", + channels); + return -EINVAL; + } + + if (i2s->variant->has_chcfg) { + regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG, + SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM_MASK, + SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM(channels)); + regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG, + SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM_MASK, + SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM(channels)); + } + + /* Map the channels for playback and capture */ + regmap_field_write(i2s->field_txchanmap, 0x76543210); + regmap_field_write(i2s->field_rxchanmap, 0x00003210); + + /* Configure the channels */ + regmap_field_write(i2s->field_txchansel, + SUN4I_I2S_CHAN_SEL(params_channels(params))); + + regmap_field_write(i2s->field_rxchansel, + SUN4I_I2S_CHAN_SEL(params_channels(params))); + + if (i2s->variant->has_chsel_tx_chen) + regmap_update_bits(i2s->regmap, SUN8I_I2S_TX_CHAN_SEL_REG, + SUN8I_I2S_TX_CHAN_EN_MASK, + SUN8I_I2S_TX_CHAN_EN(channels)); + + switch (params_physical_width(params)) { + case 16: + width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + default: + dev_err(dai->dev, "Unsupported physical sample width: %d\n", + params_physical_width(params)); + return -EINVAL; + } + i2s->playback_dma_data.addr_width = width; + + switch (params_width(params)) { + case 16: + sr = 0; + wss = 0; + break; + + default: + dev_err(dai->dev, "Unsupported sample width: %d\n", + params_width(params)); + return -EINVAL; + } + + regmap_field_write(i2s->field_fmt_wss, + wss + i2s->variant->fmt_offset); + regmap_field_write(i2s->field_fmt_sr, + sr + i2s->variant->fmt_offset); + + return sun4i_i2s_set_clk_rate(dai, params_rate(params), + params_width(params)); +} + +static int sun4i_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 val; + u32 offset = 0; + u32 bclk_polarity = SUN4I_I2S_FMT0_POLARITY_NORMAL; + u32 lrclk_polarity = SUN4I_I2S_FMT0_POLARITY_NORMAL; + + /* DAI Mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val = SUN4I_I2S_FMT0_FMT_I2S; + offset = 1; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = SUN4I_I2S_FMT0_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val = SUN4I_I2S_FMT0_FMT_RIGHT_J; + break; + default: + dev_err(dai->dev, "Unsupported format: %d\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + if (i2s->variant->has_chsel_offset) { + /* + * offset being set indicates that we're connected to an i2s + * device, however offset is only used on the sun8i block and + * i2s shares the same setting with the LJ format. Increment + * val so that the bit to value to write is correct. + */ + if (offset > 0) + val++; + /* blck offset determines whether i2s or LJ */ + regmap_update_bits(i2s->regmap, SUN8I_I2S_TX_CHAN_SEL_REG, + SUN8I_I2S_TX_CHAN_OFFSET_MASK, + SUN8I_I2S_TX_CHAN_OFFSET(offset)); + + regmap_update_bits(i2s->regmap, SUN8I_I2S_RX_CHAN_SEL_REG, + SUN8I_I2S_TX_CHAN_OFFSET_MASK, + SUN8I_I2S_TX_CHAN_OFFSET(offset)); + } + + regmap_field_write(i2s->field_fmt_mode, val); + + /* DAI clock polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + bclk_polarity = SUN4I_I2S_FMT0_POLARITY_INVERTED; + lrclk_polarity = SUN4I_I2S_FMT0_POLARITY_INVERTED; + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + bclk_polarity = SUN4I_I2S_FMT0_POLARITY_INVERTED; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + lrclk_polarity = SUN4I_I2S_FMT0_POLARITY_INVERTED; + break; + case SND_SOC_DAIFMT_NB_NF: + break; + default: + dev_err(dai->dev, "Unsupported clock polarity: %d\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + + regmap_field_write(i2s->field_fmt_bclk, bclk_polarity); + regmap_field_write(i2s->field_fmt_lrclk, lrclk_polarity); + + if (i2s->variant->has_slave_select_bit) { + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* BCLK and LRCLK master */ + val = SUN4I_I2S_CTRL_MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* BCLK and LRCLK slave */ + val = SUN4I_I2S_CTRL_MODE_SLAVE; + break; + default: + dev_err(dai->dev, "Unsupported slave setting: %d\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_MODE_MASK, + val); + } else { + /* + * The newer i2s block does not have a slave select bit, + * instead the clk pins are configured as inputs. + */ + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* BCLK and LRCLK master */ + val = SUN8I_I2S_CTRL_BCLK_OUT | + SUN8I_I2S_CTRL_LRCK_OUT; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* BCLK and LRCLK slave */ + val = 0; + break; + default: + dev_err(dai->dev, "Unsupported slave setting: %d\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN8I_I2S_CTRL_BCLK_OUT | + SUN8I_I2S_CTRL_LRCK_OUT, + val); + } + + /* Set significant bits in our FIFOs */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK | + SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK, + SUN4I_I2S_FIFO_CTRL_TX_MODE(1) | + SUN4I_I2S_FIFO_CTRL_RX_MODE(1)); + return 0; +} + +static void sun4i_i2s_start_capture(struct sun4i_i2s *i2s) +{ + /* Flush RX FIFO */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_FLUSH_RX, + SUN4I_I2S_FIFO_CTRL_FLUSH_RX); + + /* Clear RX counter */ + regmap_write(i2s->regmap, SUN4I_I2S_RX_CNT_REG, 0); + + /* Enable RX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_RX_EN, + SUN4I_I2S_CTRL_RX_EN); + + /* Enable RX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN, + SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN); +} + +static void sun4i_i2s_start_playback(struct sun4i_i2s *i2s) +{ + /* Flush TX FIFO */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_FLUSH_TX, + SUN4I_I2S_FIFO_CTRL_FLUSH_TX); + + /* Clear TX counter */ + regmap_write(i2s->regmap, SUN4I_I2S_TX_CNT_REG, 0); + + /* Enable TX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_TX_EN, + SUN4I_I2S_CTRL_TX_EN); + + /* Enable TX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN); +} + +static void sun4i_i2s_stop_capture(struct sun4i_i2s *i2s) +{ + /* Disable RX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_RX_EN, + 0); + + /* Disable RX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN, + 0); +} + +static void sun4i_i2s_stop_playback(struct sun4i_i2s *i2s) +{ + /* Disable TX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_TX_EN, + 0); + + /* Disable TX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN, + 0); +} + +static int sun4i_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_i2s_start_playback(i2s); + else + sun4i_i2s_start_capture(i2s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_i2s_stop_playback(i2s); + else + sun4i_i2s_stop_capture(i2s); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int sun4i_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + /* Enable the whole hardware block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_GL_EN, SUN4I_I2S_CTRL_GL_EN); + + /* Enable the first output line */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_SDO_EN_MASK, + SUN4I_I2S_CTRL_SDO_EN(0)); + + + return clk_prepare_enable(i2s->mod_clk); +} + +static void sun4i_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(i2s->mod_clk); + + /* Disable our output lines */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_SDO_EN_MASK, 0); + + /* Disable the whole hardware block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_GL_EN, 0); +} + +static int sun4i_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + if (clk_id != 0) + return -EINVAL; + + i2s->mclk_freq = freq; + + return 0; +} + +static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = { + .hw_params = sun4i_i2s_hw_params, + .set_fmt = sun4i_i2s_set_fmt, + .set_sysclk = sun4i_i2s_set_sysclk, + .shutdown = sun4i_i2s_shutdown, + .startup = sun4i_i2s_startup, + .trigger = sun4i_i2s_trigger, +}; + +static int sun4i_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, + &i2s->playback_dma_data, + &i2s->capture_dma_data); + + snd_soc_dai_set_drvdata(dai, i2s); + + return 0; +} + +static struct snd_soc_dai_driver sun4i_i2s_dai = { + .probe = sun4i_i2s_dai_probe, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sun4i_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver sun4i_i2s_component = { + .name = "sun4i-dai", +}; + +static bool sun4i_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_TX_REG: + return false; + + default: + return true; + } +} + +static bool sun4i_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_RX_REG: + case SUN4I_I2S_FIFO_STA_REG: + return false; + + default: + return true; + } +} + +static bool sun4i_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_RX_REG: + case SUN4I_I2S_INT_STA_REG: + case SUN4I_I2S_RX_CNT_REG: + case SUN4I_I2S_TX_CNT_REG: + return true; + + default: + return false; + } +} + +static bool sun8i_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN8I_I2S_FIFO_TX_REG: + return false; + + default: + return true; + } +} + +static bool sun8i_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg == SUN8I_I2S_INT_STA_REG) + return true; + if (reg == SUN8I_I2S_FIFO_TX_REG) + return false; + + return sun4i_i2s_volatile_reg(dev, reg); +} + +static const struct reg_default sun4i_i2s_reg_defaults[] = { + { SUN4I_I2S_CTRL_REG, 0x00000000 }, + { SUN4I_I2S_FMT0_REG, 0x0000000c }, + { SUN4I_I2S_FMT1_REG, 0x00004020 }, + { SUN4I_I2S_FIFO_CTRL_REG, 0x000400f0 }, + { SUN4I_I2S_DMA_INT_CTRL_REG, 0x00000000 }, + { SUN4I_I2S_CLK_DIV_REG, 0x00000000 }, + { SUN4I_I2S_TX_CHAN_SEL_REG, 0x00000001 }, + { SUN4I_I2S_TX_CHAN_MAP_REG, 0x76543210 }, + { SUN4I_I2S_RX_CHAN_SEL_REG, 0x00000001 }, + { SUN4I_I2S_RX_CHAN_MAP_REG, 0x00003210 }, +}; + +static const struct reg_default sun8i_i2s_reg_defaults[] = { + { SUN4I_I2S_CTRL_REG, 0x00060000 }, + { SUN4I_I2S_FMT0_REG, 0x00000033 }, + { SUN4I_I2S_FMT1_REG, 0x00000030 }, + { SUN4I_I2S_FIFO_CTRL_REG, 0x000400f0 }, + { SUN4I_I2S_DMA_INT_CTRL_REG, 0x00000000 }, + { SUN4I_I2S_CLK_DIV_REG, 0x00000000 }, + { SUN8I_I2S_CHAN_CFG_REG, 0x00000000 }, + { SUN8I_I2S_TX_CHAN_SEL_REG, 0x00000000 }, + { SUN8I_I2S_TX_CHAN_MAP_REG, 0x00000000 }, + { SUN8I_I2S_RX_CHAN_SEL_REG, 0x00000000 }, + { SUN8I_I2S_RX_CHAN_MAP_REG, 0x00000000 }, +}; + +static const struct regmap_config sun4i_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN4I_I2S_RX_CHAN_MAP_REG, + + .cache_type = REGCACHE_FLAT, + .reg_defaults = sun4i_i2s_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(sun4i_i2s_reg_defaults), + .writeable_reg = sun4i_i2s_wr_reg, + .readable_reg = sun4i_i2s_rd_reg, + .volatile_reg = sun4i_i2s_volatile_reg, +}; + +static const struct regmap_config sun8i_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN8I_I2S_RX_CHAN_MAP_REG, + .cache_type = REGCACHE_FLAT, + .reg_defaults = sun8i_i2s_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(sun8i_i2s_reg_defaults), + .writeable_reg = sun4i_i2s_wr_reg, + .readable_reg = sun8i_i2s_rd_reg, + .volatile_reg = sun8i_i2s_volatile_reg, +}; + +static int sun4i_i2s_runtime_resume(struct device *dev) +{ + struct sun4i_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->bus_clk); + if (ret) { + dev_err(dev, "Failed to enable bus clock\n"); + return ret; + } + + regcache_cache_only(i2s->regmap, false); + regcache_mark_dirty(i2s->regmap); + + ret = regcache_sync(i2s->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap cache\n"); + goto err_disable_clk; + } + + return 0; + +err_disable_clk: + clk_disable_unprepare(i2s->bus_clk); + return ret; +} + +static int sun4i_i2s_runtime_suspend(struct device *dev) +{ + struct sun4i_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + clk_disable_unprepare(i2s->bus_clk); + + return 0; +} + +static const struct sun4i_i2s_quirks sun4i_a10_i2s_quirks = { + .has_reset = false, + .reg_offset_txdata = SUN4I_I2S_FIFO_TX_REG, + .sun4i_i2s_regmap = &sun4i_i2s_regmap_config, + .field_clkdiv_mclk_en = REG_FIELD(SUN4I_I2S_CLK_DIV_REG, 7, 7), + .field_fmt_wss = REG_FIELD(SUN4I_I2S_FMT0_REG, 2, 3), + .field_fmt_sr = REG_FIELD(SUN4I_I2S_FMT0_REG, 4, 5), + .field_fmt_bclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 6, 6), + .field_fmt_lrclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 7, 7), + .has_slave_select_bit = true, + .field_fmt_mode = REG_FIELD(SUN4I_I2S_FMT0_REG, 0, 1), + .field_txchanmap = REG_FIELD(SUN4I_I2S_TX_CHAN_MAP_REG, 0, 31), + .field_rxchanmap = REG_FIELD(SUN4I_I2S_RX_CHAN_MAP_REG, 0, 31), + .field_txchansel = REG_FIELD(SUN4I_I2S_TX_CHAN_SEL_REG, 0, 2), + .field_rxchansel = REG_FIELD(SUN4I_I2S_RX_CHAN_SEL_REG, 0, 2), +}; + +static const struct sun4i_i2s_quirks sun6i_a31_i2s_quirks = { + .has_reset = true, + .reg_offset_txdata = SUN4I_I2S_FIFO_TX_REG, + .sun4i_i2s_regmap = &sun4i_i2s_regmap_config, + .field_clkdiv_mclk_en = REG_FIELD(SUN4I_I2S_CLK_DIV_REG, 7, 7), + .field_fmt_wss = REG_FIELD(SUN4I_I2S_FMT0_REG, 2, 3), + .field_fmt_sr = REG_FIELD(SUN4I_I2S_FMT0_REG, 4, 5), + .field_fmt_bclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 6, 6), + .field_fmt_lrclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 7, 7), + .has_slave_select_bit = true, + .field_fmt_mode = REG_FIELD(SUN4I_I2S_FMT0_REG, 0, 1), + .field_txchanmap = REG_FIELD(SUN4I_I2S_TX_CHAN_MAP_REG, 0, 31), + .field_rxchanmap = REG_FIELD(SUN4I_I2S_RX_CHAN_MAP_REG, 0, 31), + .field_txchansel = REG_FIELD(SUN4I_I2S_TX_CHAN_SEL_REG, 0, 2), + .field_rxchansel = REG_FIELD(SUN4I_I2S_RX_CHAN_SEL_REG, 0, 2), +}; + +static const struct sun4i_i2s_quirks sun8i_a83t_i2s_quirks = { + .has_reset = true, + .reg_offset_txdata = SUN8I_I2S_FIFO_TX_REG, + .sun4i_i2s_regmap = &sun4i_i2s_regmap_config, + .field_clkdiv_mclk_en = REG_FIELD(SUN4I_I2S_CLK_DIV_REG, 7, 7), + .field_fmt_wss = REG_FIELD(SUN4I_I2S_FMT0_REG, 2, 3), + .field_fmt_sr = REG_FIELD(SUN4I_I2S_FMT0_REG, 4, 5), + .field_fmt_bclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 6, 6), + .field_fmt_lrclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 7, 7), + .has_slave_select_bit = true, + .field_fmt_mode = REG_FIELD(SUN4I_I2S_FMT0_REG, 0, 1), + .field_txchanmap = REG_FIELD(SUN4I_I2S_TX_CHAN_MAP_REG, 0, 31), + .field_rxchanmap = REG_FIELD(SUN4I_I2S_RX_CHAN_MAP_REG, 0, 31), + .field_txchansel = REG_FIELD(SUN4I_I2S_TX_CHAN_SEL_REG, 0, 2), + .field_rxchansel = REG_FIELD(SUN4I_I2S_RX_CHAN_SEL_REG, 0, 2), +}; + +static const struct sun4i_i2s_quirks sun8i_h3_i2s_quirks = { + .has_reset = true, + .reg_offset_txdata = SUN8I_I2S_FIFO_TX_REG, + .sun4i_i2s_regmap = &sun8i_i2s_regmap_config, + .mclk_offset = 1, + .bclk_offset = 2, + .fmt_offset = 3, + .has_fmt_set_lrck_period = true, + .has_chcfg = true, + .has_chsel_tx_chen = true, + .has_chsel_offset = true, + .field_clkdiv_mclk_en = REG_FIELD(SUN4I_I2S_CLK_DIV_REG, 8, 8), + .field_fmt_wss = REG_FIELD(SUN4I_I2S_FMT0_REG, 0, 2), + .field_fmt_sr = REG_FIELD(SUN4I_I2S_FMT0_REG, 4, 6), + .field_fmt_bclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 7, 7), + .field_fmt_lrclk = REG_FIELD(SUN4I_I2S_FMT0_REG, 19, 19), + .field_fmt_mode = REG_FIELD(SUN4I_I2S_CTRL_REG, 4, 5), + .field_txchanmap = REG_FIELD(SUN8I_I2S_TX_CHAN_MAP_REG, 0, 31), + .field_rxchanmap = REG_FIELD(SUN8I_I2S_RX_CHAN_MAP_REG, 0, 31), + .field_txchansel = REG_FIELD(SUN8I_I2S_TX_CHAN_SEL_REG, 0, 2), + .field_rxchansel = REG_FIELD(SUN8I_I2S_RX_CHAN_SEL_REG, 0, 2), +}; + +static int sun4i_i2s_init_regmap_fields(struct device *dev, + struct sun4i_i2s *i2s) +{ + i2s->field_clkdiv_mclk_en = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_clkdiv_mclk_en); + if (IS_ERR(i2s->field_clkdiv_mclk_en)) + return PTR_ERR(i2s->field_clkdiv_mclk_en); + + i2s->field_fmt_wss = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_fmt_wss); + if (IS_ERR(i2s->field_fmt_wss)) + return PTR_ERR(i2s->field_fmt_wss); + + i2s->field_fmt_sr = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_fmt_sr); + if (IS_ERR(i2s->field_fmt_sr)) + return PTR_ERR(i2s->field_fmt_sr); + + i2s->field_fmt_bclk = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_fmt_bclk); + if (IS_ERR(i2s->field_fmt_bclk)) + return PTR_ERR(i2s->field_fmt_bclk); + + i2s->field_fmt_lrclk = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_fmt_lrclk); + if (IS_ERR(i2s->field_fmt_lrclk)) + return PTR_ERR(i2s->field_fmt_lrclk); + + i2s->field_fmt_mode = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_fmt_mode); + if (IS_ERR(i2s->field_fmt_mode)) + return PTR_ERR(i2s->field_fmt_mode); + + i2s->field_txchanmap = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_txchanmap); + if (IS_ERR(i2s->field_txchanmap)) + return PTR_ERR(i2s->field_txchanmap); + + i2s->field_rxchanmap = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_rxchanmap); + if (IS_ERR(i2s->field_rxchanmap)) + return PTR_ERR(i2s->field_rxchanmap); + + i2s->field_txchansel = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_txchansel); + if (IS_ERR(i2s->field_txchansel)) + return PTR_ERR(i2s->field_txchansel); + + i2s->field_rxchansel = + devm_regmap_field_alloc(dev, i2s->regmap, + i2s->variant->field_rxchansel); + return PTR_ERR_OR_ZERO(i2s->field_rxchansel); +} + +static int sun4i_i2s_probe(struct platform_device *pdev) +{ + struct sun4i_i2s *i2s; + struct resource *res; + void __iomem *regs; + int irq, ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + platform_set_drvdata(pdev, i2s); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Can't retrieve our interrupt\n"); + return irq; + } + + i2s->variant = of_device_get_match_data(&pdev->dev); + if (!i2s->variant) { + dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); + return -ENODEV; + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); + if (IS_ERR(i2s->bus_clk)) { + dev_err(&pdev->dev, "Can't get our bus clock\n"); + return PTR_ERR(i2s->bus_clk); + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + i2s->variant->sun4i_i2s_regmap); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "Regmap initialisation failed\n"); + return PTR_ERR(i2s->regmap); + } + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); + if (IS_ERR(i2s->mod_clk)) { + dev_err(&pdev->dev, "Can't get our mod clock\n"); + return PTR_ERR(i2s->mod_clk); + } + + if (i2s->variant->has_reset) { + i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s->rst)) { + dev_err(&pdev->dev, "Failed to get reset control\n"); + return PTR_ERR(i2s->rst); + } + } + + if (!IS_ERR(i2s->rst)) { + ret = reset_control_deassert(i2s->rst); + if (ret) { + dev_err(&pdev->dev, + "Failed to deassert the reset control\n"); + return -EINVAL; + } + } + + i2s->playback_dma_data.addr = res->start + + i2s->variant->reg_offset_txdata; + i2s->playback_dma_data.maxburst = 8; + + i2s->capture_dma_data.addr = res->start + SUN4I_I2S_FIFO_RX_REG; + i2s->capture_dma_data.maxburst = 8; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = sun4i_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_i2s_component, + &sun4i_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI\n"); + goto err_suspend; + } + + ret = snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM\n"); + goto err_suspend; + } + + ret = sun4i_i2s_init_regmap_fields(&pdev->dev, i2s); + if (ret) { + dev_err(&pdev->dev, "Could not initialise regmap fields\n"); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + if (!IS_ERR(i2s->rst)) + reset_control_assert(i2s->rst); + + return ret; +} + +static int sun4i_i2s_remove(struct platform_device *pdev) +{ + struct sun4i_i2s *i2s = dev_get_drvdata(&pdev->dev); + + snd_dmaengine_pcm_unregister(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_i2s_runtime_suspend(&pdev->dev); + + if (!IS_ERR(i2s->rst)) + reset_control_assert(i2s->rst); + + return 0; +} + +static const struct of_device_id sun4i_i2s_match[] = { + { + .compatible = "allwinner,sun4i-a10-i2s", + .data = &sun4i_a10_i2s_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-i2s", + .data = &sun6i_a31_i2s_quirks, + }, + { + .compatible = "allwinner,sun8i-a83t-i2s", + .data = &sun8i_a83t_i2s_quirks, + }, + { + .compatible = "allwinner,sun8i-h3-i2s", + .data = &sun8i_h3_i2s_quirks, + }, + {} +}; +MODULE_DEVICE_TABLE(of, sun4i_i2s_match); + +static const struct dev_pm_ops sun4i_i2s_pm_ops = { + .runtime_resume = sun4i_i2s_runtime_resume, + .runtime_suspend = sun4i_i2s_runtime_suspend, +}; + +static struct platform_driver sun4i_i2s_driver = { + .probe = sun4i_i2s_probe, + .remove = sun4i_i2s_remove, + .driver = { + .name = "sun4i-i2s", + .of_match_table = sun4i_i2s_match, + .pm = &sun4i_i2s_pm_ops, + }, +}; +module_platform_driver(sun4i_i2s_driver); + +MODULE_AUTHOR("Andrea Venturi "); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_DESCRIPTION("Allwinner A10 I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c new file mode 100644 index 000000000..b4af4aabe --- /dev/null +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -0,0 +1,593 @@ +/* + * ALSA SoC SPDIF Audio Layer + * + * Copyright 2015 Andrea Venturi + * Copyright 2015 Marcus Cooper + * + * Based on the Allwinner SDK driver, released under the GPL. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SUN4I_SPDIF_CTL (0x00) + #define SUN4I_SPDIF_CTL_MCLKDIV(v) ((v) << 4) /* v even */ + #define SUN4I_SPDIF_CTL_MCLKOUTEN BIT(2) + #define SUN4I_SPDIF_CTL_GEN BIT(1) + #define SUN4I_SPDIF_CTL_RESET BIT(0) + +#define SUN4I_SPDIF_TXCFG (0x04) + #define SUN4I_SPDIF_TXCFG_SINGLEMOD BIT(31) + #define SUN4I_SPDIF_TXCFG_ASS BIT(17) + #define SUN4I_SPDIF_TXCFG_NONAUDIO BIT(16) + #define SUN4I_SPDIF_TXCFG_TXRATIO(v) ((v) << 4) + #define SUN4I_SPDIF_TXCFG_TXRATIO_MASK GENMASK(8, 4) + #define SUN4I_SPDIF_TXCFG_FMTRVD GENMASK(3, 2) + #define SUN4I_SPDIF_TXCFG_FMT16BIT (0 << 2) + #define SUN4I_SPDIF_TXCFG_FMT20BIT (1 << 2) + #define SUN4I_SPDIF_TXCFG_FMT24BIT (2 << 2) + #define SUN4I_SPDIF_TXCFG_CHSTMODE BIT(1) + #define SUN4I_SPDIF_TXCFG_TXEN BIT(0) + +#define SUN4I_SPDIF_RXCFG (0x08) + #define SUN4I_SPDIF_RXCFG_LOCKFLAG BIT(4) + #define SUN4I_SPDIF_RXCFG_CHSTSRC BIT(3) + #define SUN4I_SPDIF_RXCFG_CHSTCP BIT(1) + #define SUN4I_SPDIF_RXCFG_RXEN BIT(0) + +#define SUN4I_SPDIF_TXFIFO (0x0C) + +#define SUN4I_SPDIF_RXFIFO (0x10) + +#define SUN4I_SPDIF_FCTL (0x14) + #define SUN4I_SPDIF_FCTL_FIFOSRC BIT(31) + #define SUN4I_SPDIF_FCTL_FTX BIT(17) + #define SUN4I_SPDIF_FCTL_FRX BIT(16) + #define SUN4I_SPDIF_FCTL_TXTL(v) ((v) << 8) + #define SUN4I_SPDIF_FCTL_TXTL_MASK GENMASK(12, 8) + #define SUN4I_SPDIF_FCTL_RXTL(v) ((v) << 3) + #define SUN4I_SPDIF_FCTL_RXTL_MASK GENMASK(7, 3) + #define SUN4I_SPDIF_FCTL_TXIM BIT(2) + #define SUN4I_SPDIF_FCTL_RXOM(v) ((v) << 0) + #define SUN4I_SPDIF_FCTL_RXOM_MASK GENMASK(1, 0) + +#define SUN4I_SPDIF_FSTA (0x18) + #define SUN4I_SPDIF_FSTA_TXE BIT(14) + #define SUN4I_SPDIF_FSTA_TXECNTSHT (8) + #define SUN4I_SPDIF_FSTA_RXA BIT(6) + #define SUN4I_SPDIF_FSTA_RXACNTSHT (0) + +#define SUN4I_SPDIF_INT (0x1C) + #define SUN4I_SPDIF_INT_RXLOCKEN BIT(18) + #define SUN4I_SPDIF_INT_RXUNLOCKEN BIT(17) + #define SUN4I_SPDIF_INT_RXPARERREN BIT(16) + #define SUN4I_SPDIF_INT_TXDRQEN BIT(7) + #define SUN4I_SPDIF_INT_TXUIEN BIT(6) + #define SUN4I_SPDIF_INT_TXOIEN BIT(5) + #define SUN4I_SPDIF_INT_TXEIEN BIT(4) + #define SUN4I_SPDIF_INT_RXDRQEN BIT(2) + #define SUN4I_SPDIF_INT_RXOIEN BIT(1) + #define SUN4I_SPDIF_INT_RXAIEN BIT(0) + +#define SUN4I_SPDIF_ISTA (0x20) + #define SUN4I_SPDIF_ISTA_RXLOCKSTA BIT(18) + #define SUN4I_SPDIF_ISTA_RXUNLOCKSTA BIT(17) + #define SUN4I_SPDIF_ISTA_RXPARERRSTA BIT(16) + #define SUN4I_SPDIF_ISTA_TXUSTA BIT(6) + #define SUN4I_SPDIF_ISTA_TXOSTA BIT(5) + #define SUN4I_SPDIF_ISTA_TXESTA BIT(4) + #define SUN4I_SPDIF_ISTA_RXOSTA BIT(1) + #define SUN4I_SPDIF_ISTA_RXASTA BIT(0) + +#define SUN8I_SPDIF_TXFIFO (0x20) + +#define SUN4I_SPDIF_TXCNT (0x24) + +#define SUN4I_SPDIF_RXCNT (0x28) + +#define SUN4I_SPDIF_TXCHSTA0 (0x2C) + #define SUN4I_SPDIF_TXCHSTA0_CLK(v) ((v) << 28) + #define SUN4I_SPDIF_TXCHSTA0_SAMFREQ(v) ((v) << 24) + #define SUN4I_SPDIF_TXCHSTA0_SAMFREQ_MASK GENMASK(27, 24) + #define SUN4I_SPDIF_TXCHSTA0_CHNUM(v) ((v) << 20) + #define SUN4I_SPDIF_TXCHSTA0_CHNUM_MASK GENMASK(23, 20) + #define SUN4I_SPDIF_TXCHSTA0_SRCNUM(v) ((v) << 16) + #define SUN4I_SPDIF_TXCHSTA0_CATACOD(v) ((v) << 8) + #define SUN4I_SPDIF_TXCHSTA0_MODE(v) ((v) << 6) + #define SUN4I_SPDIF_TXCHSTA0_EMPHASIS(v) ((v) << 3) + #define SUN4I_SPDIF_TXCHSTA0_CP BIT(2) + #define SUN4I_SPDIF_TXCHSTA0_AUDIO BIT(1) + #define SUN4I_SPDIF_TXCHSTA0_PRO BIT(0) + +#define SUN4I_SPDIF_TXCHSTA1 (0x30) + #define SUN4I_SPDIF_TXCHSTA1_CGMSA(v) ((v) << 8) + #define SUN4I_SPDIF_TXCHSTA1_ORISAMFREQ(v) ((v) << 4) + #define SUN4I_SPDIF_TXCHSTA1_ORISAMFREQ_MASK GENMASK(7, 4) + #define SUN4I_SPDIF_TXCHSTA1_SAMWORDLEN(v) ((v) << 1) + #define SUN4I_SPDIF_TXCHSTA1_MAXWORDLEN BIT(0) + +#define SUN4I_SPDIF_RXCHSTA0 (0x34) + #define SUN4I_SPDIF_RXCHSTA0_CLK(v) ((v) << 28) + #define SUN4I_SPDIF_RXCHSTA0_SAMFREQ(v) ((v) << 24) + #define SUN4I_SPDIF_RXCHSTA0_CHNUM(v) ((v) << 20) + #define SUN4I_SPDIF_RXCHSTA0_SRCNUM(v) ((v) << 16) + #define SUN4I_SPDIF_RXCHSTA0_CATACOD(v) ((v) << 8) + #define SUN4I_SPDIF_RXCHSTA0_MODE(v) ((v) << 6) + #define SUN4I_SPDIF_RXCHSTA0_EMPHASIS(v) ((v) << 3) + #define SUN4I_SPDIF_RXCHSTA0_CP BIT(2) + #define SUN4I_SPDIF_RXCHSTA0_AUDIO BIT(1) + #define SUN4I_SPDIF_RXCHSTA0_PRO BIT(0) + +#define SUN4I_SPDIF_RXCHSTA1 (0x38) + #define SUN4I_SPDIF_RXCHSTA1_CGMSA(v) ((v) << 8) + #define SUN4I_SPDIF_RXCHSTA1_ORISAMFREQ(v) ((v) << 4) + #define SUN4I_SPDIF_RXCHSTA1_SAMWORDLEN(v) ((v) << 1) + #define SUN4I_SPDIF_RXCHSTA1_MAXWORDLEN BIT(0) + +/* Defines for Sampling Frequency */ +#define SUN4I_SPDIF_SAMFREQ_44_1KHZ 0x0 +#define SUN4I_SPDIF_SAMFREQ_NOT_INDICATED 0x1 +#define SUN4I_SPDIF_SAMFREQ_48KHZ 0x2 +#define SUN4I_SPDIF_SAMFREQ_32KHZ 0x3 +#define SUN4I_SPDIF_SAMFREQ_22_05KHZ 0x4 +#define SUN4I_SPDIF_SAMFREQ_24KHZ 0x6 +#define SUN4I_SPDIF_SAMFREQ_88_2KHZ 0x8 +#define SUN4I_SPDIF_SAMFREQ_76_8KHZ 0x9 +#define SUN4I_SPDIF_SAMFREQ_96KHZ 0xa +#define SUN4I_SPDIF_SAMFREQ_176_4KHZ 0xc +#define SUN4I_SPDIF_SAMFREQ_192KHZ 0xe + +struct sun4i_spdif_dev { + struct platform_device *pdev; + struct clk *spdif_clk; + struct clk *apb_clk; + struct reset_control *rst; + struct snd_soc_dai_driver cpu_dai_drv; + struct regmap *regmap; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +static void sun4i_spdif_configure(struct sun4i_spdif_dev *host) +{ + /* soft reset SPDIF */ + regmap_write(host->regmap, SUN4I_SPDIF_CTL, SUN4I_SPDIF_CTL_RESET); + + /* flush TX FIFO */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, + SUN4I_SPDIF_FCTL_FTX, SUN4I_SPDIF_FCTL_FTX); + + /* clear TX counter */ + regmap_write(host->regmap, SUN4I_SPDIF_TXCNT, 0); +} + +static void sun4i_snd_txctrl_on(struct snd_pcm_substream *substream, + struct sun4i_spdif_dev *host) +{ + if (substream->runtime->channels == 1) + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_SINGLEMOD, + SUN4I_SPDIF_TXCFG_SINGLEMOD); + + /* SPDIF TX ENABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_TXEN, SUN4I_SPDIF_TXCFG_TXEN); + + /* DRQ ENABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_INT, + SUN4I_SPDIF_INT_TXDRQEN, SUN4I_SPDIF_INT_TXDRQEN); + + /* Global enable */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_CTL, + SUN4I_SPDIF_CTL_GEN, SUN4I_SPDIF_CTL_GEN); +} + +static void sun4i_snd_txctrl_off(struct snd_pcm_substream *substream, + struct sun4i_spdif_dev *host) +{ + /* SPDIF TX DISABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_TXEN, 0); + + /* DRQ DISABLE */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_INT, + SUN4I_SPDIF_INT_TXDRQEN, 0); + + /* Global disable */ + regmap_update_bits(host->regmap, SUN4I_SPDIF_CTL, + SUN4I_SPDIF_CTL_GEN, 0); +} + +static int sun4i_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + sun4i_spdif_configure(host); + + return 0; +} + +static int sun4i_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + int ret = 0; + int fmt; + unsigned long rate = params_rate(params); + u32 mclk_div = 0; + unsigned int mclk = 0; + u32 reg_val; + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); + struct platform_device *pdev = host->pdev; + + /* Add the PCM and raw data select interface */ + switch (params_channels(params)) { + case 1: /* PCM mode */ + case 2: + fmt = 0; + break; + case 4: /* raw data mode */ + fmt = SUN4I_SPDIF_TXCFG_NONAUDIO; + break; + default: + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + fmt |= SUN4I_SPDIF_TXCFG_FMT16BIT; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + fmt |= SUN4I_SPDIF_TXCFG_FMT20BIT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + fmt |= SUN4I_SPDIF_TXCFG_FMT24BIT; + break; + default: + return -EINVAL; + } + + switch (rate) { + case 22050: + case 44100: + case 88200: + case 176400: + mclk = 22579200; + break; + case 24000: + case 32000: + case 48000: + case 96000: + case 192000: + mclk = 24576000; + break; + default: + return -EINVAL; + } + + ret = clk_set_rate(host->spdif_clk, mclk); + if (ret < 0) { + dev_err(&pdev->dev, + "Setting SPDIF clock rate for %d Hz failed!\n", mclk); + return ret; + } + + regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, + SUN4I_SPDIF_FCTL_TXIM, SUN4I_SPDIF_FCTL_TXIM); + + switch (rate) { + case 22050: + case 24000: + mclk_div = 8; + break; + case 32000: + mclk_div = 6; + break; + case 44100: + case 48000: + mclk_div = 4; + break; + case 88200: + case 96000: + mclk_div = 2; + break; + case 176400: + case 192000: + mclk_div = 1; + break; + default: + return -EINVAL; + } + + reg_val = 0; + reg_val |= SUN4I_SPDIF_TXCFG_ASS; + reg_val |= fmt; /* set non audio and bit depth */ + reg_val |= SUN4I_SPDIF_TXCFG_CHSTMODE; + reg_val |= SUN4I_SPDIF_TXCFG_TXRATIO(mclk_div - 1); + regmap_write(host->regmap, SUN4I_SPDIF_TXCFG, reg_val); + + return 0; +} + +static int sun4i_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(dai); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + sun4i_snd_txctrl_on(substream, host); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sun4i_snd_txctrl_off(substream, host); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int sun4i_spdif_soc_dai_probe(struct snd_soc_dai *dai) +{ + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &host->dma_params_tx, NULL); + return 0; +} + +static const struct snd_soc_dai_ops sun4i_spdif_dai_ops = { + .startup = sun4i_spdif_startup, + .trigger = sun4i_spdif_trigger, + .hw_params = sun4i_spdif_hw_params, +}; + +static const struct regmap_config sun4i_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN4I_SPDIF_RXCHSTA1, +}; + +#define SUN4I_RATES SNDRV_PCM_RATE_8000_192000 + +#define SUN4I_FORMATS (SNDRV_PCM_FORMAT_S16_LE | \ + SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +static struct snd_soc_dai_driver sun4i_spdif_dai = { + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SUN4I_RATES, + .formats = SUN4I_FORMATS, + }, + .probe = sun4i_spdif_soc_dai_probe, + .ops = &sun4i_spdif_dai_ops, + .name = "spdif", +}; + +struct sun4i_spdif_quirks { + unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */ + bool has_reset; +}; + +static const struct sun4i_spdif_quirks sun4i_a10_spdif_quirks = { + .reg_dac_txdata = SUN4I_SPDIF_TXFIFO, +}; + +static const struct sun4i_spdif_quirks sun6i_a31_spdif_quirks = { + .reg_dac_txdata = SUN4I_SPDIF_TXFIFO, + .has_reset = true, +}; + +static const struct sun4i_spdif_quirks sun8i_h3_spdif_quirks = { + .reg_dac_txdata = SUN8I_SPDIF_TXFIFO, + .has_reset = true, +}; + +static const struct of_device_id sun4i_spdif_of_match[] = { + { + .compatible = "allwinner,sun4i-a10-spdif", + .data = &sun4i_a10_spdif_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-spdif", + .data = &sun6i_a31_spdif_quirks, + }, + { + .compatible = "allwinner,sun8i-h3-spdif", + .data = &sun8i_h3_spdif_quirks, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match); + +static const struct snd_soc_component_driver sun4i_spdif_component = { + .name = "sun4i-spdif", +}; + +static int sun4i_spdif_runtime_suspend(struct device *dev) +{ + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); + + clk_disable_unprepare(host->spdif_clk); + clk_disable_unprepare(host->apb_clk); + + return 0; +} + +static int sun4i_spdif_runtime_resume(struct device *dev) +{ + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(host->spdif_clk); + if (ret) + return ret; + ret = clk_prepare_enable(host->apb_clk); + if (ret) + clk_disable_unprepare(host->spdif_clk); + + return ret; +} + +static int sun4i_spdif_probe(struct platform_device *pdev) +{ + struct sun4i_spdif_dev *host; + struct resource *res; + const struct sun4i_spdif_quirks *quirks; + int ret; + void __iomem *base; + + dev_dbg(&pdev->dev, "Entered %s\n", __func__); + + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->pdev = pdev; + + /* Initialize this copy of the CPU DAI driver structure */ + memcpy(&host->cpu_dai_drv, &sun4i_spdif_dai, sizeof(sun4i_spdif_dai)); + host->cpu_dai_drv.name = dev_name(&pdev->dev); + + /* Get the addresses */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + quirks = of_device_get_match_data(&pdev->dev); + if (quirks == NULL) { + dev_err(&pdev->dev, "Failed to determine the quirks to use\n"); + return -ENODEV; + } + + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_spdif_regmap_config); + + /* Clocks */ + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); + if (IS_ERR(host->apb_clk)) { + dev_err(&pdev->dev, "failed to get a apb clock.\n"); + return PTR_ERR(host->apb_clk); + } + + host->spdif_clk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(host->spdif_clk)) { + dev_err(&pdev->dev, "failed to get a spdif clock.\n"); + return PTR_ERR(host->spdif_clk); + } + + host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; + host->dma_params_tx.maxburst = 8; + host->dma_params_tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + platform_set_drvdata(pdev, host); + + if (quirks->has_reset) { + host->rst = devm_reset_control_get_optional_exclusive(&pdev->dev, + NULL); + if (IS_ERR(host->rst) && PTR_ERR(host->rst) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + dev_err(&pdev->dev, "Failed to get reset: %d\n", ret); + return ret; + } + if (!IS_ERR(host->rst)) + reset_control_deassert(host->rst); + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_spdif_component, &sun4i_spdif_dai, 1); + if (ret) + return ret; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = sun4i_spdif_runtime_resume(&pdev->dev); + if (ret) + goto err_unregister; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_suspend; + return 0; +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_spdif_runtime_suspend(&pdev->dev); +err_unregister: + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int sun4i_spdif_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_spdif_runtime_suspend(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops sun4i_spdif_pm = { + SET_RUNTIME_PM_OPS(sun4i_spdif_runtime_suspend, + sun4i_spdif_runtime_resume, NULL) +}; + +static struct platform_driver sun4i_spdif_driver = { + .driver = { + .name = "sun4i-spdif", + .of_match_table = of_match_ptr(sun4i_spdif_of_match), + .pm = &sun4i_spdif_pm, + }, + .probe = sun4i_spdif_probe, + .remove = sun4i_spdif_remove, +}; + +module_platform_driver(sun4i_spdif_driver); + +MODULE_AUTHOR("Marcus Cooper "); +MODULE_AUTHOR("Andrea Venturi "); +MODULE_DESCRIPTION("Allwinner sun4i SPDIF SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun4i-spdif"); diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c new file mode 100644 index 000000000..485e79f29 --- /dev/null +++ b/sound/soc/sunxi/sun8i-codec-analog.c @@ -0,0 +1,938 @@ +/* + * This driver supports the analog controls for the internal codec + * found in Allwinner's A31s, A23, A33 and H3 SoCs. + * + * Copyright 2016 Chen-Yu Tsai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Codec analog control register offsets and bit fields */ +#define SUN8I_ADDA_HP_VOLC 0x00 +#define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 7 +#define SUN8I_ADDA_HP_VOLC_HP_VOL 0 +#define SUN8I_ADDA_LOMIXSC 0x01 +#define SUN8I_ADDA_LOMIXSC_MIC1 6 +#define SUN8I_ADDA_LOMIXSC_MIC2 5 +#define SUN8I_ADDA_LOMIXSC_PHONE 4 +#define SUN8I_ADDA_LOMIXSC_PHONEN 3 +#define SUN8I_ADDA_LOMIXSC_LINEINL 2 +#define SUN8I_ADDA_LOMIXSC_DACL 1 +#define SUN8I_ADDA_LOMIXSC_DACR 0 +#define SUN8I_ADDA_ROMIXSC 0x02 +#define SUN8I_ADDA_ROMIXSC_MIC1 6 +#define SUN8I_ADDA_ROMIXSC_MIC2 5 +#define SUN8I_ADDA_ROMIXSC_PHONE 4 +#define SUN8I_ADDA_ROMIXSC_PHONEP 3 +#define SUN8I_ADDA_ROMIXSC_LINEINR 2 +#define SUN8I_ADDA_ROMIXSC_DACR 1 +#define SUN8I_ADDA_ROMIXSC_DACL 0 +#define SUN8I_ADDA_DAC_PA_SRC 0x03 +#define SUN8I_ADDA_DAC_PA_SRC_DACAREN 7 +#define SUN8I_ADDA_DAC_PA_SRC_DACALEN 6 +#define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 5 +#define SUN8I_ADDA_DAC_PA_SRC_LMIXEN 4 +#define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE 3 +#define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE 2 +#define SUN8I_ADDA_DAC_PA_SRC_RHPIS 1 +#define SUN8I_ADDA_DAC_PA_SRC_LHPIS 0 +#define SUN8I_ADDA_PHONEIN_GCTRL 0x04 +#define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG 4 +#define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG 0 +#define SUN8I_ADDA_LINEIN_GCTRL 0x05 +#define SUN8I_ADDA_LINEIN_GCTRL_LINEING 4 +#define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 0 +#define SUN8I_ADDA_MICIN_GCTRL 0x06 +#define SUN8I_ADDA_MICIN_GCTRL_MIC1G 4 +#define SUN8I_ADDA_MICIN_GCTRL_MIC2G 0 +#define SUN8I_ADDA_PAEN_HP_CTRL 0x07 +#define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 7 +#define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */ +#define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC 5 +#define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN 4 +#define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL 2 +#define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE 1 +#define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE 0 +#define SUN8I_ADDA_PHONEOUT_CTRL 0x08 +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG 5 +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN 4 +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1 3 +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2 2 +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX 1 +#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX 0 +#define SUN8I_ADDA_PHONE_GAIN_CTRL 0x09 +#define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL 3 +#define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG 0 +#define SUN8I_ADDA_MIC2G_CTRL 0x0a +#define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN 7 +#define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST 4 +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN 3 +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN 2 +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC 1 +#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC 0 +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL 0x0b +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN 7 +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN 6 +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE 5 +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN 3 +#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST 0 +#define SUN8I_ADDA_LADCMIXSC 0x0c +#define SUN8I_ADDA_LADCMIXSC_MIC1 6 +#define SUN8I_ADDA_LADCMIXSC_MIC2 5 +#define SUN8I_ADDA_LADCMIXSC_PHONE 4 +#define SUN8I_ADDA_LADCMIXSC_PHONEN 3 +#define SUN8I_ADDA_LADCMIXSC_LINEINL 2 +#define SUN8I_ADDA_LADCMIXSC_OMIXRL 1 +#define SUN8I_ADDA_LADCMIXSC_OMIXRR 0 +#define SUN8I_ADDA_RADCMIXSC 0x0d +#define SUN8I_ADDA_RADCMIXSC_MIC1 6 +#define SUN8I_ADDA_RADCMIXSC_MIC2 5 +#define SUN8I_ADDA_RADCMIXSC_PHONE 4 +#define SUN8I_ADDA_RADCMIXSC_PHONEP 3 +#define SUN8I_ADDA_RADCMIXSC_LINEINR 2 +#define SUN8I_ADDA_RADCMIXSC_OMIXR 1 +#define SUN8I_ADDA_RADCMIXSC_OMIXL 0 +#define SUN8I_ADDA_RES 0x0e +#define SUN8I_ADDA_RES_MMICBIAS_SEL 4 +#define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL 0 +#define SUN8I_ADDA_ADC_AP_EN 0x0f +#define SUN8I_ADDA_ADC_AP_EN_ADCREN 7 +#define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6 +#define SUN8I_ADDA_ADC_AP_EN_ADCG 0 + +/* Analog control register access bits */ +#define ADDA_PR 0x0 /* PRCM base + 0x1c0 */ +#define ADDA_PR_RESET BIT(28) +#define ADDA_PR_WRITE BIT(24) +#define ADDA_PR_ADDR_SHIFT 16 +#define ADDA_PR_ADDR_MASK GENMASK(4, 0) +#define ADDA_PR_DATA_IN_SHIFT 8 +#define ADDA_PR_DATA_IN_MASK GENMASK(7, 0) +#define ADDA_PR_DATA_OUT_SHIFT 0 +#define ADDA_PR_DATA_OUT_MASK GENMASK(7, 0) + +/* regmap access bits */ +static int adda_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + void __iomem *base = (void __iomem *)context; + u32 tmp; + + /* De-assert reset */ + writel(readl(base) | ADDA_PR_RESET, base); + + /* Clear write bit */ + writel(readl(base) & ~ADDA_PR_WRITE, base); + + /* Set register address */ + tmp = readl(base); + tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT); + tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT; + writel(tmp, base); + + /* Read back value */ + *val = readl(base) & ADDA_PR_DATA_OUT_MASK; + + return 0; +} + +static int adda_reg_write(void *context, unsigned int reg, unsigned int val) +{ + void __iomem *base = (void __iomem *)context; + u32 tmp; + + /* De-assert reset */ + writel(readl(base) | ADDA_PR_RESET, base); + + /* Set register address */ + tmp = readl(base); + tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT); + tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT; + writel(tmp, base); + + /* Set data to write */ + tmp = readl(base); + tmp &= ~(ADDA_PR_DATA_IN_MASK << ADDA_PR_DATA_IN_SHIFT); + tmp |= (val & ADDA_PR_DATA_IN_MASK) << ADDA_PR_DATA_IN_SHIFT; + writel(tmp, base); + + /* Set write bit to signal a write */ + writel(readl(base) | ADDA_PR_WRITE, base); + + /* Clear write bit */ + writel(readl(base) & ~ADDA_PR_WRITE, base); + + return 0; +} + +static const struct regmap_config adda_pr_regmap_cfg = { + .name = "adda-pr", + .reg_bits = 5, + .reg_stride = 1, + .val_bits = 8, + .reg_read = adda_reg_read, + .reg_write = adda_reg_write, + .fast_io = true, + .max_register = 24, +}; + +/* mixer controls */ +static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = { + SOC_DAPM_DOUBLE_R("DAC Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_DACL, 1, 0), + SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_DACR, 1, 0), + SOC_DAPM_DOUBLE_R("Line In Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_LINEINL, 1, 0), + SOC_DAPM_DOUBLE_R("Mic1 Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), + SOC_DAPM_DOUBLE_R("Mic2 Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_MIC2, 1, 0), +}; + +/* mixer controls */ +static const struct snd_kcontrol_new sun8i_v3s_codec_mixer_controls[] = { + SOC_DAPM_DOUBLE_R("DAC Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_DACL, 1, 0), + SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_DACR, 1, 0), + SOC_DAPM_DOUBLE_R("Mic1 Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), +}; + +/* ADC mixer controls */ +static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = { + SOC_DAPM_DOUBLE_R("Mixer Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), + SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), + SOC_DAPM_DOUBLE_R("Line In Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0), + SOC_DAPM_DOUBLE_R("Mic1 Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), + SOC_DAPM_DOUBLE_R("Mic2 Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0), +}; + +/* ADC mixer controls */ +static const struct snd_kcontrol_new sun8i_v3s_codec_adc_mixer_controls[] = { + SOC_DAPM_DOUBLE_R("Mixer Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), + SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), + SOC_DAPM_DOUBLE_R("Mic1 Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), +}; + +/* volume / mute controls */ +static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale, + -450, 150, 0); +static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), +); + +static const struct snd_kcontrol_new sun8i_codec_common_controls[] = { + /* Mixer pre-gain */ + SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL, + SUN8I_ADDA_MICIN_GCTRL_MIC1G, + 0x7, 0, sun8i_codec_out_mixer_pregain_scale), + + /* Microphone Amp boost gain */ + SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0, + sun8i_codec_mic_gain_scale), + + /* ADC */ + SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0, + sun8i_codec_out_mixer_pregain_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { + /* ADC */ + SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0), + SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0), + + /* DAC */ + SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0), + SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0), + /* + * Due to this component and the codec belonging to separate DAPM + * contexts, we need to manually link the above widgets to their + * stream widgets at the card level. + */ + + /* Microphone input */ + SND_SOC_DAPM_INPUT("MIC1"), + + /* Mic input path */ + SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_mixer_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, + sun8i_codec_mixer_controls, + ARRAY_SIZE(sun8i_codec_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, + sun8i_codec_mixer_controls, + ARRAY_SIZE(sun8i_codec_mixer_controls)), + SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, + sun8i_codec_adc_mixer_controls, + ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), + SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, + sun8i_codec_adc_mixer_controls, + ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), +}; + +static const struct snd_soc_dapm_widget sun8i_v3s_codec_mixer_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, + sun8i_v3s_codec_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, + sun8i_v3s_codec_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), + SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, + sun8i_v3s_codec_adc_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), + SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, + sun8i_v3s_codec_adc_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), +}; + +static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { + /* Microphone Routes */ + { "Mic1 Amplifier", NULL, "MIC1"}, +}; + +static const struct snd_soc_dapm_route sun8i_codec_mixer_routes[] = { + /* Left Mixer Routes */ + { "Left Mixer", "DAC Playback Switch", "Left DAC" }, + { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, + { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, + + /* Right Mixer Routes */ + { "Right Mixer", "DAC Playback Switch", "Right DAC" }, + { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, + { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, + + /* Left ADC Mixer Routes */ + { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" }, + { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" }, + { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, + + /* Right ADC Mixer Routes */ + { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" }, + { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" }, + { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, + + /* ADC Routes */ + { "Left ADC", NULL, "Left ADC Mixer" }, + { "Right ADC", NULL, "Right ADC Mixer" }, +}; + +/* headphone specific controls, widgets, and routes */ +static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1); +static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = { + SOC_SINGLE_TLV("Headphone Playback Volume", + SUN8I_ADDA_HP_VOLC, + SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0, + sun8i_codec_hp_vol_scale), + SOC_DOUBLE("Headphone Playback Switch", + SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE, + SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0), +}; + +static const char * const sun8i_codec_hp_src_enum_text[] = { + "DAC", "Mixer", +}; + +static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum, + SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_LHPIS, + SUN8I_ADDA_DAC_PA_SRC_RHPIS, + sun8i_codec_hp_src_enum_text); + +static const struct snd_kcontrol_new sun8i_codec_hp_src[] = { + SOC_DAPM_ENUM("Headphone Source Playback Route", + sun8i_codec_hp_src_enum), +}; + +static int sun8i_headphone_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, + BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), + BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN)); + /* + * Need a delay to have the amplifier up. 700ms seems the best + * compromise between the time to let the amplifier up and the + * time not to feel this delay while playing a sound. + */ + msleep(700); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, + BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), + 0x0); + } + + return 0; +} + +static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = { + SND_SOC_DAPM_MUX("Headphone Source Playback Route", + SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src), + SND_SOC_DAPM_OUT_DRV_E("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL, + SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0, + sun8i_headphone_amp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL, + SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL, + SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0), + SND_SOC_DAPM_OUTPUT("HP"), +}; + +static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = { + { "Headphone Source Playback Route", "DAC", "Left DAC" }, + { "Headphone Source Playback Route", "DAC", "Right DAC" }, + { "Headphone Source Playback Route", "Mixer", "Left Mixer" }, + { "Headphone Source Playback Route", "Mixer", "Right Mixer" }, + { "Headphone Amp", NULL, "Headphone Source Playback Route" }, + { "HPCOM", NULL, "HPCOM Protection" }, + { "HP", NULL, "Headphone Amp" }, +}; + +static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_headphone_controls, + ARRAY_SIZE(sun8i_codec_headphone_controls)); + if (ret) { + dev_err(dev, "Failed to add Headphone controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets, + ARRAY_SIZE(sun8i_codec_headphone_widgets)); + if (ret) { + dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes, + ARRAY_SIZE(sun8i_codec_headphone_routes)); + if (ret) { + dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + +/* mbias specific widget */ +static const struct snd_soc_dapm_widget sun8i_codec_mbias_widgets[] = { + SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN, + 0, NULL, 0), +}; + +static int sun8i_codec_add_mbias(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mbias_widgets, + ARRAY_SIZE(sun8i_codec_mbias_widgets)); + if (ret) + dev_err(dev, "Failed to add MBIAS DAPM widgets: %d\n", ret); + + return ret; +} + +/* hmic specific widget */ +static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = { + SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN, + 0, NULL, 0), +}; + +static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets, + ARRAY_SIZE(sun8i_codec_hmic_widgets)); + if (ret) + dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret); + + return ret; +} + +/* line in specific controls, widgets and rines */ +static const struct snd_kcontrol_new sun8i_codec_linein_controls[] = { + /* Mixer pre-gain */ + SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, + SUN8I_ADDA_LINEIN_GCTRL_LINEING, + 0x7, 0, sun8i_codec_out_mixer_pregain_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = { + /* Line input */ + SND_SOC_DAPM_INPUT("LINEIN"), +}; + +static const struct snd_soc_dapm_route sun8i_codec_linein_routes[] = { + { "Left Mixer", "Line In Playback Switch", "LINEIN" }, + + { "Right Mixer", "Line In Playback Switch", "LINEIN" }, + + { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, + + { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, +}; + +static int sun8i_codec_add_linein(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_linein_controls, + ARRAY_SIZE(sun8i_codec_linein_controls)); + if (ret) { + dev_err(dev, "Failed to add Line In controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_linein_widgets, + ARRAY_SIZE(sun8i_codec_linein_widgets)); + if (ret) { + dev_err(dev, "Failed to add Line In DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_linein_routes, + ARRAY_SIZE(sun8i_codec_linein_routes)); + if (ret) { + dev_err(dev, "Failed to add Line In DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + + +/* line out specific controls, widgets and routes */ +static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale, + 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), +); +static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = { + SOC_SINGLE_TLV("Line Out Playback Volume", + SUN8I_ADDA_PHONE_GAIN_CTRL, + SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0, + sun8i_codec_lineout_vol_scale), + SOC_DOUBLE("Line Out Playback Switch", + SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN, + SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0), +}; + +static const char * const sun8i_codec_lineout_src_enum_text[] = { + "Stereo", "Mono Differential", +}; + +static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum, + SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC, + SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC, + sun8i_codec_lineout_src_enum_text); + +static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = { + SOC_DAPM_ENUM("Line Out Source Playback Route", + sun8i_codec_lineout_src_enum), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = { + SND_SOC_DAPM_MUX("Line Out Source Playback Route", + SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src), + /* It is unclear if this is a buffer or gate, model it as a supply */ + SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL, + SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("LINEOUT"), +}; + +static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = { + { "Line Out Source Playback Route", "Stereo", "Left Mixer" }, + { "Line Out Source Playback Route", "Stereo", "Right Mixer" }, + { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" }, + { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, + { "LINEOUT", NULL, "Line Out Source Playback Route" }, + { "LINEOUT", NULL, "Line Out Enable", }, +}; + +static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_lineout_controls, + ARRAY_SIZE(sun8i_codec_lineout_controls)); + if (ret) { + dev_err(dev, "Failed to add Line Out controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets, + ARRAY_SIZE(sun8i_codec_lineout_widgets)); + if (ret) { + dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes, + ARRAY_SIZE(sun8i_codec_lineout_routes)); + if (ret) { + dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + +/* mic2 specific controls, widgets and routes */ +static const struct snd_kcontrol_new sun8i_codec_mic2_controls[] = { + /* Mixer pre-gain */ + SOC_SINGLE_TLV("Mic2 Playback Volume", + SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, + 0x7, 0, sun8i_codec_out_mixer_pregain_scale), + + /* Microphone Amp boost gain */ + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, + sun8i_codec_mic_gain_scale), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_mic2_widgets[] = { + /* Microphone input */ + SND_SOC_DAPM_INPUT("MIC2"), + + /* Mic input path */ + SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, + SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route sun8i_codec_mic2_routes[] = { + { "Mic2 Amplifier", NULL, "MIC2"}, + + { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, + + { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, + + { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, +}; + +static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_add_component_controls(cmpnt, + sun8i_codec_mic2_controls, + ARRAY_SIZE(sun8i_codec_mic2_controls)); + if (ret) { + dev_err(dev, "Failed to add MIC2 controls: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mic2_widgets, + ARRAY_SIZE(sun8i_codec_mic2_widgets)); + if (ret) { + dev_err(dev, "Failed to add MIC2 DAPM widgets: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mic2_routes, + ARRAY_SIZE(sun8i_codec_mic2_routes)); + if (ret) { + dev_err(dev, "Failed to add MIC2 DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + +struct sun8i_codec_analog_quirks { + bool has_headphone; + bool has_hmic; + bool has_linein; + bool has_lineout; + bool has_mbias; + bool has_mic2; +}; + +static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { + .has_headphone = true, + .has_hmic = true, + .has_linein = true, + .has_mbias = true, + .has_mic2 = true, +}; + +static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { + .has_linein = true, + .has_lineout = true, + .has_mbias = true, + .has_mic2 = true, +}; + +static int sun8i_codec_analog_add_mixer(struct snd_soc_component *cmpnt, + const struct sun8i_codec_analog_quirks *quirks) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + if (!quirks->has_mic2 && !quirks->has_linein) { + /* + * Apply the special widget set which has uses a control + * without MIC2 and Line In, for SoCs without these. + * TODO: not all special cases are supported now, this case + * is present because it's the case of V3s. + */ + ret = snd_soc_dapm_new_controls(dapm, + sun8i_v3s_codec_mixer_widgets, + ARRAY_SIZE(sun8i_v3s_codec_mixer_widgets)); + if (ret) { + dev_err(dev, "Failed to add V3s Mixer DAPM widgets: %d\n", ret); + return ret; + } + } else { + /* Apply the generic mixer widget set. */ + ret = snd_soc_dapm_new_controls(dapm, + sun8i_codec_mixer_widgets, + ARRAY_SIZE(sun8i_codec_mixer_widgets)); + if (ret) { + dev_err(dev, "Failed to add Mixer DAPM widgets: %d\n", ret); + return ret; + } + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mixer_routes, + ARRAY_SIZE(sun8i_codec_mixer_routes)); + if (ret) { + dev_err(dev, "Failed to add Mixer DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = { + .has_headphone = true, + .has_hmic = true, +}; + +static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) +{ + struct device *dev = cmpnt->dev; + const struct sun8i_codec_analog_quirks *quirks; + int ret; + + /* + * This would never return NULL unless someone directly registers a + * platform device matching this driver's name, without specifying a + * device tree node. + */ + quirks = of_device_get_match_data(dev); + + /* Add controls, widgets, and routes for individual features */ + ret = sun8i_codec_analog_add_mixer(cmpnt, quirks); + if (ret) + return ret; + + if (quirks->has_headphone) { + ret = sun8i_codec_add_headphone(cmpnt); + if (ret) + return ret; + } + + if (quirks->has_hmic) { + ret = sun8i_codec_add_hmic(cmpnt); + if (ret) + return ret; + } + + if (quirks->has_linein) { + ret = sun8i_codec_add_linein(cmpnt); + if (ret) + return ret; + } + + if (quirks->has_lineout) { + ret = sun8i_codec_add_lineout(cmpnt); + if (ret) + return ret; + } + + if (quirks->has_mbias) { + ret = sun8i_codec_add_mbias(cmpnt); + if (ret) + return ret; + } + + if (quirks->has_mic2) { + ret = sun8i_codec_add_mic2(cmpnt); + if (ret) + return ret; + } + + return 0; +} + +static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = { + .controls = sun8i_codec_common_controls, + .num_controls = ARRAY_SIZE(sun8i_codec_common_controls), + .dapm_widgets = sun8i_codec_common_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets), + .dapm_routes = sun8i_codec_common_routes, + .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes), + .probe = sun8i_codec_analog_cmpnt_probe, +}; + +static const struct of_device_id sun8i_codec_analog_of_match[] = { + { + .compatible = "allwinner,sun8i-a23-codec-analog", + .data = &sun8i_a23_quirks, + }, + { + .compatible = "allwinner,sun8i-h3-codec-analog", + .data = &sun8i_h3_quirks, + }, + { + .compatible = "allwinner,sun8i-v3s-codec-analog", + .data = &sun8i_v3s_quirks, + }, + {} +}; +MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match); + +static int sun8i_codec_analog_probe(struct platform_device *pdev) +{ + struct resource *res; + struct regmap *regmap; + void __iomem *base; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed to map the registers\n"); + return PTR_ERR(base); + } + + regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg); + if (IS_ERR(regmap)) { + dev_err(&pdev->dev, "Failed to create regmap\n"); + return PTR_ERR(regmap); + } + + return devm_snd_soc_register_component(&pdev->dev, + &sun8i_codec_analog_cmpnt_drv, + NULL, 0); +} + +static struct platform_driver sun8i_codec_analog_driver = { + .driver = { + .name = "sun8i-codec-analog", + .of_match_table = sun8i_codec_analog_of_match, + }, + .probe = sun8i_codec_analog_probe, +}; +module_platform_driver(sun8i_codec_analog_driver); + +MODULE_DESCRIPTION("Allwinner internal codec analog controls driver"); +MODULE_AUTHOR("Chen-Yu Tsai "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun8i-codec-analog"); diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c new file mode 100644 index 000000000..8bcdeb281 --- /dev/null +++ b/sound/soc/sunxi/sun8i-codec.c @@ -0,0 +1,629 @@ +/* + * This driver supports the digital controls for the internal codec + * found in Allwinner's A33 SoCs. + * + * (C) Copyright 2010-2016 + * Reuuimlla Technology Co., Ltd. + * huangxin + * Mylène Josserand + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SUN8I_SYSCLK_CTL 0x00c +#define SUN8I_SYSCLK_CTL_AIF1CLK_ENA 11 +#define SUN8I_SYSCLK_CTL_AIF1CLK_SRC_PLL 9 +#define SUN8I_SYSCLK_CTL_AIF1CLK_SRC 8 +#define SUN8I_SYSCLK_CTL_SYSCLK_ENA 3 +#define SUN8I_SYSCLK_CTL_SYSCLK_SRC 0 +#define SUN8I_MOD_CLK_ENA 0x010 +#define SUN8I_MOD_CLK_ENA_AIF1 15 +#define SUN8I_MOD_CLK_ENA_ADC 3 +#define SUN8I_MOD_CLK_ENA_DAC 2 +#define SUN8I_MOD_RST_CTL 0x014 +#define SUN8I_MOD_RST_CTL_AIF1 15 +#define SUN8I_MOD_RST_CTL_ADC 3 +#define SUN8I_MOD_RST_CTL_DAC 2 +#define SUN8I_SYS_SR_CTRL 0x018 +#define SUN8I_SYS_SR_CTRL_AIF1_FS 12 +#define SUN8I_SYS_SR_CTRL_AIF2_FS 8 +#define SUN8I_AIF1CLK_CTRL 0x040 +#define SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD 15 +#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV 14 +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV 13 +#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV 9 +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV 6 +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_16 (1 << 6) +#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ 4 +#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16 (1 << 4) +#define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT 2 +#define SUN8I_AIF1_ADCDAT_CTRL 0x044 +#define SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0L_ENA 15 +#define SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0R_ENA 14 +#define SUN8I_AIF1_DACDAT_CTRL 0x048 +#define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA 15 +#define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA 14 +#define SUN8I_AIF1_MXR_SRC 0x04c +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF1DA0L 15 +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACL 14 +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_ADCL 13 +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACR 12 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF1DA0R 11 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACR 10 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_ADCR 9 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACL 8 +#define SUN8I_ADC_DIG_CTRL 0x100 +#define SUN8I_ADC_DIG_CTRL_ENDA 15 +#define SUN8I_ADC_DIG_CTRL_ADOUT_DTS 2 +#define SUN8I_ADC_DIG_CTRL_ADOUT_DLY 1 +#define SUN8I_DAC_DIG_CTRL 0x120 +#define SUN8I_DAC_DIG_CTRL_ENDA 15 +#define SUN8I_DAC_MXR_SRC 0x130 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA0L 15 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA1L 14 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF2DACL 13 +#define SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_ADCL 12 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA0R 11 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA1R 10 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF2DACR 9 +#define SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_ADCR 8 + +#define SUN8I_SYS_SR_CTRL_AIF1_FS_MASK GENMASK(15, 12) +#define SUN8I_SYS_SR_CTRL_AIF2_FS_MASK GENMASK(11, 8) +#define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT_MASK GENMASK(3, 2) +#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK GENMASK(5, 4) +#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK GENMASK(8, 6) +#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV_MASK GENMASK(12, 9) + +struct sun8i_codec { + struct device *dev; + struct regmap *regmap; + struct clk *clk_module; + struct clk *clk_bus; +}; + +static int sun8i_codec_runtime_resume(struct device *dev) +{ + struct sun8i_codec *scodec = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(scodec->clk_module); + if (ret) { + dev_err(dev, "Failed to enable the module clock\n"); + return ret; + } + + ret = clk_prepare_enable(scodec->clk_bus); + if (ret) { + dev_err(dev, "Failed to enable the bus clock\n"); + goto err_disable_modclk; + } + + regcache_cache_only(scodec->regmap, false); + + ret = regcache_sync(scodec->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap cache\n"); + goto err_disable_clk; + } + + return 0; + +err_disable_clk: + clk_disable_unprepare(scodec->clk_bus); + +err_disable_modclk: + clk_disable_unprepare(scodec->clk_module); + + return ret; +} + +static int sun8i_codec_runtime_suspend(struct device *dev) +{ + struct sun8i_codec *scodec = dev_get_drvdata(dev); + + regcache_cache_only(scodec->regmap, true); + regcache_mark_dirty(scodec->regmap); + + clk_disable_unprepare(scodec->clk_module); + clk_disable_unprepare(scodec->clk_bus); + + return 0; +} + +static int sun8i_codec_get_hw_rate(struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + + switch (rate) { + case 8000: + case 7350: + return 0x0; + case 11025: + return 0x1; + case 12000: + return 0x2; + case 16000: + return 0x3; + case 22050: + return 0x4; + case 24000: + return 0x5; + case 32000: + return 0x6; + case 44100: + return 0x7; + case 48000: + return 0x8; + case 96000: + return 0x9; + case 192000: + return 0xa; + default: + return -EINVAL; + } +} + +static int sun8i_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct sun8i_codec *scodec = snd_soc_component_get_drvdata(dai->component); + u32 value; + + /* clock masters */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: /* Codec slave, DAI master */ + value = 0x1; + break; + case SND_SOC_DAIFMT_CBM_CFM: /* Codec Master, DAI slave */ + value = 0x0; + break; + default: + return -EINVAL; + } + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + BIT(SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD), + value << SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD); + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* Normal */ + value = 0x0; + break; + case SND_SOC_DAIFMT_IB_IF: /* Inversion */ + value = 0x1; + break; + default: + return -EINVAL; + } + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + BIT(SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV), + value << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV); + + /* + * It appears that the DAI and the codec don't share the same + * polarity for the LRCK signal when they mean 'normal' and + * 'inverted' in the datasheet. + * + * Since the DAI here is our regular i2s driver that have been + * tested with way more codecs than just this one, it means + * that the codec probably gets it backward, and we have to + * invert the value here. + */ + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + BIT(SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV), + !value << SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV); + + /* DAI format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + value = 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + value = 0x1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + value = 0x2; + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + value = 0x3; + break; + default: + return -EINVAL; + } + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT_MASK, + value << SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT); + + return 0; +} + +struct sun8i_codec_clk_div { + u8 div; + u8 val; +}; + +static const struct sun8i_codec_clk_div sun8i_codec_bclk_div[] = { + { .div = 1, .val = 0 }, + { .div = 2, .val = 1 }, + { .div = 4, .val = 2 }, + { .div = 6, .val = 3 }, + { .div = 8, .val = 4 }, + { .div = 12, .val = 5 }, + { .div = 16, .val = 6 }, + { .div = 24, .val = 7 }, + { .div = 32, .val = 8 }, + { .div = 48, .val = 9 }, + { .div = 64, .val = 10 }, + { .div = 96, .val = 11 }, + { .div = 128, .val = 12 }, + { .div = 192, .val = 13 }, +}; + +static u8 sun8i_codec_get_bclk_div(struct sun8i_codec *scodec, + unsigned int rate, + unsigned int word_size) +{ + unsigned long clk_rate = clk_get_rate(scodec->clk_module); + unsigned int div = clk_rate / rate / word_size / 2; + unsigned int best_val = 0, best_diff = ~0; + int i; + + for (i = 0; i < ARRAY_SIZE(sun8i_codec_bclk_div); i++) { + const struct sun8i_codec_clk_div *bdiv = &sun8i_codec_bclk_div[i]; + unsigned int diff = abs(bdiv->div - div); + + if (diff < best_diff) { + best_diff = diff; + best_val = bdiv->val; + } + } + + return best_val; +} + +static int sun8i_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sun8i_codec *scodec = snd_soc_component_get_drvdata(dai->component); + int sample_rate; + u8 bclk_div; + + /* + * The CPU DAI handles only a sample of 16 bits. Configure the + * codec to handle this type of sample resolution. + */ + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK, + SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16); + + bclk_div = sun8i_codec_get_bclk_div(scodec, params_rate(params), 16); + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV_MASK, + bclk_div << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV); + + regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, + SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK, + SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_16); + + sample_rate = sun8i_codec_get_hw_rate(params); + if (sample_rate < 0) + return sample_rate; + + regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL, + SUN8I_SYS_SR_CTRL_AIF1_FS_MASK, + sample_rate << SUN8I_SYS_SR_CTRL_AIF1_FS); + regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL, + SUN8I_SYS_SR_CTRL_AIF2_FS_MASK, + sample_rate << SUN8I_SYS_SR_CTRL_AIF2_FS); + + return 0; +} + +static const struct snd_kcontrol_new sun8i_dac_mixer_controls[] = { + SOC_DAPM_DOUBLE("AIF1 Slot 0 Digital DAC Playback Switch", + SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA0L, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA0R, 1, 0), + SOC_DAPM_DOUBLE("AIF1 Slot 1 Digital DAC Playback Switch", + SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF1DA1L, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF1DA1R, 1, 0), + SOC_DAPM_DOUBLE("AIF2 Digital DAC Playback Switch", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_AIF2DACL, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_AIF2DACR, 1, 0), + SOC_DAPM_DOUBLE("ADC Digital DAC Playback Switch", SUN8I_DAC_MXR_SRC, + SUN8I_DAC_MXR_SRC_DACL_MXR_SRC_ADCL, + SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_ADCR, 1, 0), +}; + +static const struct snd_kcontrol_new sun8i_input_mixer_controls[] = { + SOC_DAPM_DOUBLE("AIF1 Slot 0 Digital ADC Capture Switch", + SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF1DA0L, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF1DA0R, 1, 0), + SOC_DAPM_DOUBLE("AIF2 Digital ADC Capture Switch", SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACL, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACR, 1, 0), + SOC_DAPM_DOUBLE("AIF1 Data Digital ADC Capture Switch", + SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_ADCL, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_ADCR, 1, 0), + SOC_DAPM_DOUBLE("AIF2 Inv Digital ADC Capture Switch", + SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACR, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACL, 1, 0), +}; + +static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { + /* Digital parts of the DACs and ADC */ + SND_SOC_DAPM_SUPPLY("DAC", SUN8I_DAC_DIG_CTRL, SUN8I_DAC_DIG_CTRL_ENDA, + 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC", SUN8I_ADC_DIG_CTRL, SUN8I_ADC_DIG_CTRL_ENDA, + 0, NULL, 0), + + /* Analog DAC AIF */ + SND_SOC_DAPM_AIF_IN("AIF1 Slot 0 Left", "Playback", 0, + SUN8I_AIF1_DACDAT_CTRL, + SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA, 0), + SND_SOC_DAPM_AIF_IN("AIF1 Slot 0 Right", "Playback", 0, + SUN8I_AIF1_DACDAT_CTRL, + SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0), + + /* Analog ADC AIF */ + SND_SOC_DAPM_AIF_IN("AIF1 Slot 0 Left ADC", "Capture", 0, + SUN8I_AIF1_ADCDAT_CTRL, + SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0L_ENA, 0), + SND_SOC_DAPM_AIF_IN("AIF1 Slot 0 Right ADC", "Capture", 0, + SUN8I_AIF1_ADCDAT_CTRL, + SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0R_ENA, 0), + + /* DAC and ADC Mixers */ + SOC_MIXER_ARRAY("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_dac_mixer_controls), + SOC_MIXER_ARRAY("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_dac_mixer_controls), + SOC_MIXER_ARRAY("Left Digital ADC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_input_mixer_controls), + SOC_MIXER_ARRAY("Right Digital ADC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_input_mixer_controls), + + /* Clocks */ + SND_SOC_DAPM_SUPPLY("MODCLK AFI1", SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_AIF1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MODCLK DAC", SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_DAC, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MODCLK ADC", SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_ADC, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF1", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_AIF1CLK_ENA, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("SYSCLK", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_SYSCLK_ENA, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("AIF1 PLL", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_AIF1CLK_SRC_PLL, 0, NULL, 0), + /* Inversion as 0=AIF1, 1=AIF2 */ + SND_SOC_DAPM_SUPPLY("SYSCLK AIF1", SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_SYSCLK_SRC, 1, NULL, 0), + + /* Module reset */ + SND_SOC_DAPM_SUPPLY("RST AIF1", SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_AIF1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RST DAC", SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_DAC, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RST ADC", SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_ADC, 0, NULL, 0), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + +}; + +static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { + /* Clock Routes */ + { "AIF1", NULL, "SYSCLK AIF1" }, + { "AIF1 PLL", NULL, "AIF1" }, + { "RST AIF1", NULL, "AIF1 PLL" }, + { "MODCLK AFI1", NULL, "RST AIF1" }, + { "DAC", NULL, "MODCLK AFI1" }, + { "ADC", NULL, "MODCLK AFI1" }, + + { "RST DAC", NULL, "SYSCLK" }, + { "MODCLK DAC", NULL, "RST DAC" }, + { "DAC", NULL, "MODCLK DAC" }, + + { "RST ADC", NULL, "SYSCLK" }, + { "MODCLK ADC", NULL, "RST ADC" }, + { "ADC", NULL, "MODCLK ADC" }, + + /* DAC Routes */ + { "AIF1 Slot 0 Right", NULL, "DAC" }, + { "AIF1 Slot 0 Left", NULL, "DAC" }, + + /* DAC Mixer Routes */ + { "Left Digital DAC Mixer", "AIF1 Slot 0 Digital DAC Playback Switch", + "AIF1 Slot 0 Left"}, + { "Right Digital DAC Mixer", "AIF1 Slot 0 Digital DAC Playback Switch", + "AIF1 Slot 0 Right"}, + + /* ADC Routes */ + { "AIF1 Slot 0 Right ADC", NULL, "ADC" }, + { "AIF1 Slot 0 Left ADC", NULL, "ADC" }, + + /* ADC Mixer Routes */ + { "Left Digital ADC Mixer", "AIF1 Data Digital ADC Capture Switch", + "AIF1 Slot 0 Left ADC" }, + { "Right Digital ADC Mixer", "AIF1 Data Digital ADC Capture Switch", + "AIF1 Slot 0 Right ADC" }, +}; + +static const struct snd_soc_dai_ops sun8i_codec_dai_ops = { + .hw_params = sun8i_codec_hw_params, + .set_fmt = sun8i_set_fmt, +}; + +static struct snd_soc_dai_driver sun8i_codec_dai = { + .name = "sun8i", + /* playback capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + /* capture capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .sig_bits = 24, + }, + /* pcm operations */ + .ops = &sun8i_codec_dai_ops, +}; + +static const struct snd_soc_component_driver sun8i_soc_component = { + .dapm_widgets = sun8i_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_dapm_widgets), + .dapm_routes = sun8i_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sun8i_codec_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct regmap_config sun8i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN8I_DAC_MXR_SRC, + + .cache_type = REGCACHE_FLAT, +}; + +static int sun8i_codec_probe(struct platform_device *pdev) +{ + struct resource *res_base; + struct sun8i_codec *scodec; + void __iomem *base; + int ret; + + scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL); + if (!scodec) + return -ENOMEM; + + scodec->dev = &pdev->dev; + + scodec->clk_module = devm_clk_get(&pdev->dev, "mod"); + if (IS_ERR(scodec->clk_module)) { + dev_err(&pdev->dev, "Failed to get the module clock\n"); + return PTR_ERR(scodec->clk_module); + } + + scodec->clk_bus = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(scodec->clk_bus)) { + dev_err(&pdev->dev, "Failed to get the bus clock\n"); + return PTR_ERR(scodec->clk_bus); + } + + res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res_base); + if (IS_ERR(base)) { + dev_err(&pdev->dev, "Failed to map the registers\n"); + return PTR_ERR(base); + } + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun8i_codec_regmap_config); + if (IS_ERR(scodec->regmap)) { + dev_err(&pdev->dev, "Failed to create our regmap\n"); + return PTR_ERR(scodec->regmap); + } + + platform_set_drvdata(pdev, scodec); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = sun8i_codec_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &sun8i_soc_component, + &sun8i_codec_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec\n"); + goto err_suspend; + } + + return ret; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + sun8i_codec_runtime_suspend(&pdev->dev); + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int sun8i_codec_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + sun8i_codec_runtime_suspend(&pdev->dev); + + return 0; +} + +static const struct of_device_id sun8i_codec_of_match[] = { + { .compatible = "allwinner,sun8i-a33-codec" }, + {} +}; +MODULE_DEVICE_TABLE(of, sun8i_codec_of_match); + +static const struct dev_pm_ops sun8i_codec_pm_ops = { + SET_RUNTIME_PM_OPS(sun8i_codec_runtime_suspend, + sun8i_codec_runtime_resume, NULL) +}; + +static struct platform_driver sun8i_codec_driver = { + .driver = { + .name = "sun8i-codec", + .of_match_table = sun8i_codec_of_match, + .pm = &sun8i_codec_pm_ops, + }, + .probe = sun8i_codec_probe, + .remove = sun8i_codec_remove, +}; +module_platform_driver(sun8i_codec_driver); + +MODULE_DESCRIPTION("Allwinner A33 (sun8i) codec driver"); +MODULE_AUTHOR("Mylène Josserand "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sun8i-codec"); diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig new file mode 100644 index 000000000..6875fc39a --- /dev/null +++ b/sound/soc/tegra/Kconfig @@ -0,0 +1,137 @@ +config SND_SOC_TEGRA + tristate "SoC Audio for the Tegra System-on-Chip" + depends on (ARCH_TEGRA && TEGRA20_APB_DMA) || COMPILE_TEST + depends on COMMON_CLK + depends on RESET_CONTROLLER + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M here if you want support for SoC audio on Tegra. + +config SND_SOC_TEGRA20_AC97 + tristate "Tegra20 AC97 interface" + depends on SND_SOC_TEGRA + select SND_SOC_AC97_BUS + select SND_SOC_TEGRA20_DAS + help + Say Y or M if you want to add support for codecs attached to the + Tegra20 AC97 interface. You will also need to select the individual + machine drivers to support below. + +config SND_SOC_TEGRA20_DAS + tristate "Tegra20 DAS module" + depends on SND_SOC_TEGRA + help + Say Y or M if you want to add support for the Tegra20 DAS module. + You will also need to select the individual machine drivers to + support below. + +config SND_SOC_TEGRA20_I2S + tristate "Tegra20 I2S interface" + depends on SND_SOC_TEGRA + select SND_SOC_TEGRA20_DAS + help + Say Y or M if you want to add support for codecs attached to the + Tegra20 I2S interface. You will also need to select the individual + machine drivers to support below. + +config SND_SOC_TEGRA20_SPDIF + tristate "Tegra20 SPDIF interface" + depends on SND_SOC_TEGRA + default m + help + Say Y or M if you want to add support for the Tegra20 SPDIF interface. + You will also need to select the individual machine drivers to support + below. + +config SND_SOC_TEGRA30_AHUB + tristate "Tegra30 AHUB module" + depends on SND_SOC_TEGRA + help + Say Y or M if you want to add support for the Tegra30 AHUB module. + You will also need to select the individual machine drivers to + support below. + +config SND_SOC_TEGRA30_I2S + tristate "Tegra30 I2S interface" + depends on SND_SOC_TEGRA + select SND_SOC_TEGRA30_AHUB + help + Say Y or M if you want to add support for codecs attached to the + Tegra30 I2S interface. You will also need to select the individual + machine drivers to support below. + +config SND_SOC_TEGRA_RT5640 + tristate "SoC Audio support for Tegra boards using an RT5640 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_RT5640 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the RT5640 codec, such as Dalmore. + +config SND_SOC_TEGRA_WM8753 + tristate "SoC Audio support for Tegra boards using a WM8753 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_WM8753 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8753 codec, such as Whistler. + +config SND_SOC_TEGRA_WM8903 + tristate "SoC Audio support for Tegra boards using a WM8903 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_WM8903 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8093 codec. Currently, the supported boards are + Harmony, Ventana, Seaboard, Kaen, and Aebl. + +config SND_SOC_TEGRA_WM9712 + tristate "SoC Audio support for Tegra boards using a WM9712 codec" + depends on SND_SOC_TEGRA && GPIOLIB + select SND_SOC_TEGRA20_AC97 + select SND_SOC_WM9712 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM9712 (or compatible) codec. + +config SND_SOC_TEGRA_TRIMSLICE + tristate "SoC Audio support for TrimSlice board" + depends on SND_SOC_TEGRA && I2C + select SND_SOC_TLV320AIC23_I2C + help + Say Y or M here if you want to add support for SoC audio on the + TrimSlice platform. + +config SND_SOC_TEGRA_ALC5632 + tristate "SoC Audio support for Tegra boards using an ALC5632 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_ALC5632 + help + Say Y or M here if you want to add support for SoC audio on the + Toshiba AC100 netbook. + +config SND_SOC_TEGRA_MAX98090 + tristate "SoC Audio support for Tegra boards using a MAX98090 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_MAX98090 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the MAX98090 codec, such as Venice2. + +config SND_SOC_TEGRA_RT5677 + tristate "SoC Audio support for Tegra boards using a RT5677 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_RT5677 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the RT5677 codec, such as Ryu. + +config SND_SOC_TEGRA_SGTL5000 + tristate "SoC Audio support for Tegra boards using a SGTL5000 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_SGTL5000 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the SGTL5000 codec, such as Apalis T30, Apalis TK1 or + Colibri T30. diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile new file mode 100644 index 000000000..2329b72c9 --- /dev/null +++ b/sound/soc/tegra/Makefile @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 +# Tegra platform Support +snd-soc-tegra-pcm-objs := tegra_pcm.o +snd-soc-tegra-utils-objs += tegra_asoc_utils.o +snd-soc-tegra20-ac97-objs := tegra20_ac97.o +snd-soc-tegra20-das-objs := tegra20_das.o +snd-soc-tegra20-i2s-objs := tegra20_i2s.o +snd-soc-tegra20-spdif-objs := tegra20_spdif.o +snd-soc-tegra30-ahub-objs := tegra30_ahub.o +snd-soc-tegra30-i2s-objs := tegra30_i2s.o + +obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o +obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o +obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o +obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o +obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o +obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o +obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o +obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o + +# Tegra machine Support +snd-soc-tegra-rt5640-objs := tegra_rt5640.o +snd-soc-tegra-rt5677-objs := tegra_rt5677.o +snd-soc-tegra-wm8753-objs := tegra_wm8753.o +snd-soc-tegra-wm8903-objs := tegra_wm8903.o +snd-soc-tegra-wm9712-objs := tegra_wm9712.o +snd-soc-tegra-trimslice-objs := trimslice.o +snd-soc-tegra-alc5632-objs := tegra_alc5632.o +snd-soc-tegra-max98090-objs := tegra_max98090.o +snd-soc-tegra-sgtl5000-objs := tegra_sgtl5000.o + +obj-$(CONFIG_SND_SOC_TEGRA_RT5640) += snd-soc-tegra-rt5640.o +obj-$(CONFIG_SND_SOC_TEGRA_RT5677) += snd-soc-tegra-rt5677.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8753) += snd-soc-tegra-wm8753.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o +obj-$(CONFIG_SND_SOC_TEGRA_WM9712) += snd-soc-tegra-wm9712.o +obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o +obj-$(CONFIG_SND_SOC_TEGRA_ALC5632) += snd-soc-tegra-alc5632.o +obj-$(CONFIG_SND_SOC_TEGRA_MAX98090) += snd-soc-tegra-max98090.o +obj-$(CONFIG_SND_SOC_TEGRA_SGTL5000) += snd-soc-tegra-sgtl5000.o \ No newline at end of file diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c new file mode 100644 index 000000000..682ef33af --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.c @@ -0,0 +1,451 @@ +/* + * tegra20_ac97.c - Tegra20 AC97 platform driver + * + * Copyright (c) 2012 Lucas Stach + * + * Partly based on code copyright/by: + * + * Copyright (c) 2011,2012 Toradex Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tegra20_ac97.h" + +#define DRV_NAME "tegra20-ac97" + +static struct tegra20_ac97 *workdata; + +static void tegra20_ac97_codec_reset(struct snd_ac97 *ac97) +{ + u32 readback; + unsigned long timeout; + + /* reset line is not driven by DAC pad group, have to toggle GPIO */ + gpio_set_value(workdata->reset_gpio, 0); + udelay(2); + + gpio_set_value(workdata->reset_gpio, 1); + udelay(2); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97) +{ + u32 readback; + unsigned long timeout; + + /* + * although sync line is driven by the DAC pad group warm reset using + * the controller cmd is not working, have to toggle sync line + * manually. + */ + gpio_request(workdata->sync_gpio, "codec-sync"); + + gpio_direction_output(workdata->sync_gpio, 1); + + udelay(2); + gpio_set_value(workdata->sync_gpio, 0); + udelay(2); + gpio_free(workdata->sync_gpio); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd, + unsigned short reg) +{ + u32 readback; + unsigned long timeout; + + regmap_write(workdata->regmap, TEGRA20_AC97_CMD, + (((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) & + TEGRA20_AC97_CMD_CMD_ADDR_MASK) | + TEGRA20_AC97_CMD_BUSY); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback); + if (readback & TEGRA20_AC97_STATUS1_STA_VALID1) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); + + return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >> + TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT); +} + +static void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd, + unsigned short reg, unsigned short val) +{ + u32 readback; + unsigned long timeout; + + regmap_write(workdata->regmap, TEGRA20_AC97_CMD, + ((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) & + TEGRA20_AC97_CMD_CMD_ADDR_MASK) | + ((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) & + TEGRA20_AC97_CMD_CMD_DATA_MASK) | + TEGRA20_AC97_CMD_BUSY); + + timeout = jiffies + msecs_to_jiffies(100); + + do { + regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback); + if (!(readback & TEGRA20_AC97_CMD_BUSY)) + break; + usleep_range(1000, 2000); + } while (!time_after(jiffies, timeout)); +} + +static struct snd_ac97_bus_ops tegra20_ac97_ops = { + .read = tegra20_ac97_codec_read, + .write = tegra20_ac97_codec_write, + .reset = tegra20_ac97_codec_reset, + .warm_reset = tegra20_ac97_codec_warm_reset, +}; + +static inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN); + + regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL, + TEGRA20_AC97_CTRL_PCM_DAC_EN | + TEGRA20_AC97_CTRL_STM_EN, + TEGRA20_AC97_CTRL_PCM_DAC_EN | + TEGRA20_AC97_CTRL_STM_EN); +} + +static inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0); + + regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL, + TEGRA20_AC97_CTRL_PCM_DAC_EN, 0); +} + +static inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN); +} + +static inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97) +{ + regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR, + TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0); +} + +static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_start_playback(ac97); + else + tegra20_ac97_start_capture(ac97); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_stop_playback(ac97); + else + tegra20_ac97_stop_capture(ac97); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = { + .trigger = tegra20_ac97_trigger, +}; + +static int tegra20_ac97_probe(struct snd_soc_dai *dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &ac97->capture_dma_data; + dai->playback_dma_data = &ac97->playback_dma_data; + + return 0; +} + +static struct snd_soc_dai_driver tegra20_ac97_dai = { + .name = "tegra-ac97-pcm", + .bus_control = true, + .probe = tegra20_ac97_probe, + .playback = { + .stream_name = "PCM Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "PCM Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra20_ac97_dai_ops, +}; + +static const struct snd_soc_component_driver tegra20_ac97_component = { + .name = DRV_NAME, +}; + +static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_CTRL: + case TEGRA20_AC97_CMD: + case TEGRA20_AC97_STATUS1: + case TEGRA20_AC97_FIFO1_SCR: + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_STATUS1: + case TEGRA20_AC97_FIFO1_SCR: + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_AC97_FIFO_TX1: + case TEGRA20_AC97_FIFO_RX1: + return true; + default: + break; + } + + return false; +} + +static const struct regmap_config tegra20_ac97_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA20_AC97_FIFO_RX1, + .writeable_reg = tegra20_ac97_wr_rd_reg, + .readable_reg = tegra20_ac97_wr_rd_reg, + .volatile_reg = tegra20_ac97_volatile_reg, + .precious_reg = tegra20_ac97_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int tegra20_ac97_platform_probe(struct platform_device *pdev) +{ + struct tegra20_ac97 *ac97; + struct resource *mem; + void __iomem *regs; + int ret = 0; + + ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97), + GFP_KERNEL); + if (!ac97) { + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, ac97); + + ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ac97->clk_ac97)) { + dev_err(&pdev->dev, "Can't retrieve ac97 clock\n"); + ret = PTR_ERR(ac97->clk_ac97); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err_clk_put; + } + + ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_ac97_regmap_config); + if (IS_ERR(ac97->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(ac97->regmap); + goto err_clk_put; + } + + ac97->reset_gpio = of_get_named_gpio(pdev->dev.of_node, + "nvidia,codec-reset-gpio", 0); + if (gpio_is_valid(ac97->reset_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, ac97->reset_gpio, + GPIOF_OUT_INIT_HIGH, "codec-reset"); + if (ret) { + dev_err(&pdev->dev, "could not get codec-reset GPIO\n"); + goto err_clk_put; + } + } else { + dev_err(&pdev->dev, "no codec-reset GPIO supplied\n"); + goto err_clk_put; + } + + ac97->sync_gpio = of_get_named_gpio(pdev->dev.of_node, + "nvidia,codec-sync-gpio", 0); + if (!gpio_is_valid(ac97->sync_gpio)) { + dev_err(&pdev->dev, "no codec-sync GPIO supplied\n"); + goto err_clk_put; + } + + ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1; + ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ac97->capture_dma_data.maxburst = 4; + + ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1; + ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ac97->playback_dma_data.maxburst = 4; + + ret = clk_prepare_enable(ac97->clk_ac97); + if (ret) { + dev_err(&pdev->dev, "clk_enable failed: %d\n", ret); + goto err_clk_put; + } + + ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops); + if (ret) { + dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret); + goto err_clk_disable_unprepare; + } + + ret = snd_soc_register_component(&pdev->dev, &tegra20_ac97_component, + &tegra20_ac97_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_clk_disable_unprepare; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_component; + } + + /* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */ + workdata = ac97; + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_clk_disable_unprepare: + clk_disable_unprepare(ac97->clk_ac97); +err_clk_put: +err: + snd_soc_set_ac97_ops(NULL); + return ret; +} + +static int tegra20_ac97_platform_remove(struct platform_device *pdev) +{ + struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + clk_disable_unprepare(ac97->clk_ac97); + + snd_soc_set_ac97_ops(NULL); + + return 0; +} + +static const struct of_device_id tegra20_ac97_of_match[] = { + { .compatible = "nvidia,tegra20-ac97", }, + {}, +}; + +static struct platform_driver tegra20_ac97_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = tegra20_ac97_of_match, + }, + .probe = tegra20_ac97_platform_probe, + .remove = tegra20_ac97_platform_remove, +}; +module_platform_driver(tegra20_ac97_driver); + +MODULE_AUTHOR("Lucas Stach"); +MODULE_DESCRIPTION("Tegra20 AC97 ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_ac97_of_match); diff --git a/sound/soc/tegra/tegra20_ac97.h b/sound/soc/tegra/tegra20_ac97.h new file mode 100644 index 000000000..0a39d823e --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.h @@ -0,0 +1,94 @@ +/* + * tegra20_ac97.h - Definitions for the Tegra20 AC97 controller driver + * + * Copyright (c) 2012 Lucas Stach + * + * Partly based on code copyright/by: + * + * Copyright (c) 2011,2012 Toradex Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#ifndef __TEGRA20_AC97_H__ +#define __TEGRA20_AC97_H__ + +#include "tegra_pcm.h" + +#define TEGRA20_AC97_CTRL 0x00 +#define TEGRA20_AC97_CMD 0x04 +#define TEGRA20_AC97_STATUS1 0x08 +/* ... */ +#define TEGRA20_AC97_FIFO1_SCR 0x1c +/* ... */ +#define TEGRA20_AC97_FIFO_TX1 0x40 +#define TEGRA20_AC97_FIFO_RX1 0x80 + +/* TEGRA20_AC97_CTRL */ +#define TEGRA20_AC97_CTRL_STM2_EN (1 << 16) +#define TEGRA20_AC97_CTRL_DOUBLE_SAMPLING_EN (1 << 11) +#define TEGRA20_AC97_CTRL_IO_CNTRL_EN (1 << 10) +#define TEGRA20_AC97_CTRL_HSET_DAC_EN (1 << 9) +#define TEGRA20_AC97_CTRL_LINE2_DAC_EN (1 << 8) +#define TEGRA20_AC97_CTRL_PCM_LFE_EN (1 << 7) +#define TEGRA20_AC97_CTRL_PCM_SUR_EN (1 << 6) +#define TEGRA20_AC97_CTRL_PCM_CEN_DAC_EN (1 << 5) +#define TEGRA20_AC97_CTRL_LINE1_DAC_EN (1 << 4) +#define TEGRA20_AC97_CTRL_PCM_DAC_EN (1 << 3) +#define TEGRA20_AC97_CTRL_COLD_RESET (1 << 2) +#define TEGRA20_AC97_CTRL_WARM_RESET (1 << 1) +#define TEGRA20_AC97_CTRL_STM_EN (1 << 0) + +/* TEGRA20_AC97_CMD */ +#define TEGRA20_AC97_CMD_CMD_ADDR_SHIFT 24 +#define TEGRA20_AC97_CMD_CMD_ADDR_MASK (0xff << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) +#define TEGRA20_AC97_CMD_CMD_DATA_SHIFT 8 +#define TEGRA20_AC97_CMD_CMD_DATA_MASK (0xffff << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) +#define TEGRA20_AC97_CMD_CMD_ID_SHIFT 2 +#define TEGRA20_AC97_CMD_CMD_ID_MASK (0x3 << TEGRA20_AC97_CMD_CMD_ID_SHIFT) +#define TEGRA20_AC97_CMD_BUSY (1 << 0) + +/* TEGRA20_AC97_STATUS1 */ +#define TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT 24 +#define TEGRA20_AC97_STATUS1_STA_ADDR1_MASK (0xff << TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT) +#define TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT 8 +#define TEGRA20_AC97_STATUS1_STA_DATA1_MASK (0xffff << TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT) +#define TEGRA20_AC97_STATUS1_STA_VALID1 (1 << 2) +#define TEGRA20_AC97_STATUS1_STANDBY1 (1 << 1) +#define TEGRA20_AC97_STATUS1_CODEC1_RDY (1 << 0) + +/* TEGRA20_AC97_FIFO1_SCR */ +#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT 27 +#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT) +#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT 22 +#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT) +#define TEGRA20_AC97_FIFO_SCR_REC_OVERRUN_INT_STA (1 << 19) +#define TEGRA20_AC97_FIFO_SCR_PB_UNDERRUN_INT_STA (1 << 18) +#define TEGRA20_AC97_FIFO_SCR_REC_FORCE_MT (1 << 17) +#define TEGRA20_AC97_FIFO_SCR_PB_FORCE_MT (1 << 16) +#define TEGRA20_AC97_FIFO_SCR_REC_FULL_EN (1 << 15) +#define TEGRA20_AC97_FIFO_SCR_REC_3QRT_FULL_EN (1 << 14) +#define TEGRA20_AC97_FIFO_SCR_REC_QRT_FULL_EN (1 << 13) +#define TEGRA20_AC97_FIFO_SCR_REC_EMPTY_EN (1 << 12) +#define TEGRA20_AC97_FIFO_SCR_PB_NOT_FULL_EN (1 << 11) +#define TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN (1 << 10) +#define TEGRA20_AC97_FIFO_SCR_PB_3QRT_MT_EN (1 << 9) +#define TEGRA20_AC97_FIFO_SCR_PB_EMPTY_MT_EN (1 << 8) + +struct tegra20_ac97 { + struct clk *clk_ac97; + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; + int reset_gpio; + int sync_gpio; +}; +#endif /* __TEGRA20_AC97_H__ */ diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c new file mode 100644 index 000000000..4024e3abb --- /dev/null +++ b/sound/soc/tegra/tegra20_das.c @@ -0,0 +1,230 @@ +/* + * tegra20_das.c - Tegra20 DAS driver + * + * Author: Stephen Warren + * Copyright (C) 2010 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "tegra20_das.h" + +#define DRV_NAME "tegra20-das" + +static struct tegra20_das *das; + +static inline void tegra20_das_write(u32 reg, u32 val) +{ + regmap_write(das->regmap, reg, val); +} + +static inline u32 tegra20_das_read(u32 reg) +{ + u32 val; + + regmap_read(das->regmap, reg, &val); + return val; +} + +int tegra20_das_connect_dap_to_dac(int dap, int dac) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA20_DAS_DAP_CTRL_SEL + + (dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); + reg = dac << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; + + tegra20_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dac); + +int tegra20_das_connect_dap_to_dap(int dap, int otherdap, int master, + int sdata1rx, int sdata2rx) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA20_DAS_DAP_CTRL_SEL + + (dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); + reg = otherdap << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P | + !!sdata2rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P | + !!sdata1rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P | + !!master << TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P; + + tegra20_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dap); + +int tegra20_das_connect_dac_to_dap(int dac, int dap) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL + + (dac * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); + reg = dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | + dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | + dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; + + tegra20_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dac_to_dap); + +#define LAST_REG(name) \ + (TEGRA20_DAS_##name + \ + (TEGRA20_DAS_##name##_STRIDE * (TEGRA20_DAS_##name##_COUNT - 1))) + +static bool tegra20_das_wr_rd_reg(struct device *dev, unsigned int reg) +{ + if ((reg >= TEGRA20_DAS_DAP_CTRL_SEL) && + (reg <= LAST_REG(DAP_CTRL_SEL))) + return true; + if ((reg >= TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL) && + (reg <= LAST_REG(DAC_INPUT_DATA_CLK_SEL))) + return true; + + return false; +} + +static const struct regmap_config tegra20_das_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = LAST_REG(DAC_INPUT_DATA_CLK_SEL), + .writeable_reg = tegra20_das_wr_rd_reg, + .readable_reg = tegra20_das_wr_rd_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int tegra20_das_probe(struct platform_device *pdev) +{ + struct resource *res; + void __iomem *regs; + int ret = 0; + + if (das) + return -ENODEV; + + das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL); + if (!das) { + ret = -ENOMEM; + goto err; + } + das->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err; + } + + das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_das_regmap_config); + if (IS_ERR(das->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(das->regmap); + goto err; + } + + ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAS DAP connection\n"); + goto err; + } + ret = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAC_ID_1, + TEGRA20_DAS_DAC_SEL_DAP1); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAS DAC connection\n"); + goto err; + } + + ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_3, + TEGRA20_DAS_DAP_SEL_DAC3); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAS DAP connection\n"); + goto err; + } + ret = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAC_ID_3, + TEGRA20_DAS_DAC_SEL_DAP3); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAS DAC connection\n"); + goto err; + } + + platform_set_drvdata(pdev, das); + + return 0; + +err: + das = NULL; + return ret; +} + +static int tegra20_das_remove(struct platform_device *pdev) +{ + if (!das) + return -ENODEV; + + das = NULL; + + return 0; +} + +static const struct of_device_id tegra20_das_of_match[] = { + { .compatible = "nvidia,tegra20-das", }, + {}, +}; + +static struct platform_driver tegra20_das_driver = { + .probe = tegra20_das_probe, + .remove = tegra20_das_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = tegra20_das_of_match, + }, +}; +module_platform_driver(tegra20_das_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra20 DAS driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_das_of_match); diff --git a/sound/soc/tegra/tegra20_das.h b/sound/soc/tegra/tegra20_das.h new file mode 100644 index 000000000..be217f3d3 --- /dev/null +++ b/sound/soc/tegra/tegra20_das.h @@ -0,0 +1,134 @@ +/* + * tegra20_das.h - Definitions for Tegra20 DAS driver + * + * Author: Stephen Warren + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA20_DAS_H__ +#define __TEGRA20_DAS_H__ + +/* Register TEGRA20_DAS_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_CTRL_SEL 0x00 +#define TEGRA20_DAS_DAP_CTRL_SEL_COUNT 5 +#define TEGRA20_DAS_DAP_CTRL_SEL_STRIDE 4 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 + +/* Values for field TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_SEL_DAC1 0 +#define TEGRA20_DAS_DAP_SEL_DAC2 1 +#define TEGRA20_DAS_DAP_SEL_DAC3 2 +#define TEGRA20_DAS_DAP_SEL_DAP1 16 +#define TEGRA20_DAS_DAP_SEL_DAP2 17 +#define TEGRA20_DAS_DAP_SEL_DAP3 18 +#define TEGRA20_DAS_DAP_SEL_DAP4 19 +#define TEGRA20_DAS_DAP_SEL_DAP5 20 + +/* Register TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL */ +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 + +/* + * Values for: + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL + */ +#define TEGRA20_DAS_DAC_SEL_DAP1 0 +#define TEGRA20_DAS_DAC_SEL_DAP2 1 +#define TEGRA20_DAS_DAC_SEL_DAP3 2 +#define TEGRA20_DAS_DAC_SEL_DAP4 3 +#define TEGRA20_DAS_DAC_SEL_DAP5 4 + +/* + * Names/IDs of the DACs/DAPs. + */ + +#define TEGRA20_DAS_DAP_ID_1 0 +#define TEGRA20_DAS_DAP_ID_2 1 +#define TEGRA20_DAS_DAP_ID_3 2 +#define TEGRA20_DAS_DAP_ID_4 3 +#define TEGRA20_DAS_DAP_ID_5 4 + +#define TEGRA20_DAS_DAC_ID_1 0 +#define TEGRA20_DAS_DAC_ID_2 1 +#define TEGRA20_DAS_DAC_ID_3 2 + +struct tegra20_das { + struct device *dev; + struct regmap *regmap; +}; + +/* + * Terminology: + * DAS: Digital audio switch (HW module controlled by this driver) + * DAP: Digital audio port (port/pins on Tegra device) + * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) + * + * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific + * DAC, or another DAP. When DAPs are connected, one must be the master and + * one the slave. Each DAC allows selection of a specific DAP for input, to + * cater for the case where N DAPs are connected to 1 DAC for broadcast + * output. + * + * This driver is dumb; no attempt is made to ensure that a valid routing + * configuration is programmed. + */ + +/* + * Connect a DAP to to a DAC + * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* + * dac_sel: DAC to connect to: TEGRA20_DAS_DAP_SEL_DAC* + */ +extern int tegra20_das_connect_dap_to_dac(int dap_id, int dac_sel); + +/* + * Connect a DAP to to another DAP + * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* + * other_dap_sel: DAP to connect to: TEGRA20_DAS_DAP_SEL_DAP* + * master: Is this DAP the master (1) or slave (0) + * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) + * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) + */ +extern int tegra20_das_connect_dap_to_dap(int dap_id, int other_dap_sel, + int master, int sdata1rx, + int sdata2rx); + +/* + * Connect a DAC's input to a DAP + * (DAC outputs are selected by the DAP) + * dac_id: DAC ID to connect: TEGRA20_DAS_DAC_ID_* + * dap_sel: DAP to receive input from: TEGRA20_DAS_DAC_SEL_DAP* + */ +extern int tegra20_das_connect_dac_to_dap(int dac_id, int dap_sel); + +#endif diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c new file mode 100644 index 000000000..26253c284 --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.c @@ -0,0 +1,463 @@ +/* + * tegra20_i2s.c - Tegra20 I2S driver + * + * Author: Stephen Warren + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tegra20_i2s.h" + +#define DRV_NAME "tegra20-i2s" + +static int tegra20_i2s_runtime_suspend(struct device *dev) +{ + struct tegra20_i2s *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(i2s->clk_i2s); + + return 0; +} + +static int tegra20_i2s_runtime_resume(struct device *dev) +{ + struct tegra20_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->clk_i2s); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask = 0, val = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + mask |= TEGRA20_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val |= TEGRA20_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + mask |= TEGRA20_I2S_CTRL_BIT_FORMAT_MASK | + TEGRA20_I2S_CTRL_LRCK_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; + val |= TEGRA20_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_I2S; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_RJM; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= TEGRA20_I2S_CTRL_BIT_FORMAT_LJM; + val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val); + + return 0; +} + +static int tegra20_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + int ret, sample_size, srate, i2sclock, bitcnt; + + mask = TEGRA20_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = TEGRA20_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = TEGRA20_I2S_CTRL_BIT_SIZE_24; + sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + val = TEGRA20_I2S_CTRL_BIT_SIZE_32; + sample_size = 32; + break; + default: + return -EINVAL; + } + + mask |= TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK; + val |= TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED; + + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val); + + srate = params_rate(params); + + /* Final "* 2" required by Tegra hardware */ + i2sclock = srate * params_channels(params) * sample_size * 2; + + ret = clk_set_rate(i2s->clk_i2s, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + bitcnt = (i2sclock / (2 * srate)) - 1; + if (bitcnt < 0 || bitcnt > TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) + return -EINVAL; + val = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + val |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE; + + regmap_write(i2s->regmap, TEGRA20_I2S_TIMING, val); + + regmap_write(i2s->regmap, TEGRA20_I2S_FIFO_SCR, + TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | + TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); + + return 0; +} + +static void tegra20_i2s_start_playback(struct tegra20_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO1_ENABLE, + TEGRA20_I2S_CTRL_FIFO1_ENABLE); +} + +static void tegra20_i2s_stop_playback(struct tegra20_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO1_ENABLE, 0); +} + +static void tegra20_i2s_start_capture(struct tegra20_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO2_ENABLE, + TEGRA20_I2S_CTRL_FIFO2_ENABLE); +} + +static void tegra20_i2s_stop_capture(struct tegra20_i2s *i2s) +{ + regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, + TEGRA20_I2S_CTRL_FIFO2_ENABLE, 0); +} + +static int tegra20_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_i2s_start_playback(i2s); + else + tegra20_i2s_start_capture(i2s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_i2s_stop_playback(i2s); + else + tegra20_i2s_stop_capture(i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra20_i2s_probe(struct snd_soc_dai *dai) +{ + struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = { + .set_fmt = tegra20_i2s_set_fmt, + .hw_params = tegra20_i2s_hw_params, + .trigger = tegra20_i2s_trigger, +}; + +static const struct snd_soc_dai_driver tegra20_i2s_dai_template = { + .probe = tegra20_i2s_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra20_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver tegra20_i2s_component = { + .name = DRV_NAME, +}; + +static bool tegra20_i2s_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_I2S_CTRL: + case TEGRA20_I2S_STATUS: + case TEGRA20_I2S_TIMING: + case TEGRA20_I2S_FIFO_SCR: + case TEGRA20_I2S_PCM_CTRL: + case TEGRA20_I2S_NW_CTRL: + case TEGRA20_I2S_TDM_CTRL: + case TEGRA20_I2S_TDM_TX_RX_CTRL: + case TEGRA20_I2S_FIFO1: + case TEGRA20_I2S_FIFO2: + return true; + default: + return false; + } +} + +static bool tegra20_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_I2S_STATUS: + case TEGRA20_I2S_FIFO_SCR: + case TEGRA20_I2S_FIFO1: + case TEGRA20_I2S_FIFO2: + return true; + default: + return false; + } +} + +static bool tegra20_i2s_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_I2S_FIFO1: + case TEGRA20_I2S_FIFO2: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra20_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA20_I2S_FIFO2, + .writeable_reg = tegra20_i2s_wr_rd_reg, + .readable_reg = tegra20_i2s_wr_rd_reg, + .volatile_reg = tegra20_i2s_volatile_reg, + .precious_reg = tegra20_i2s_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int tegra20_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra20_i2s *i2s; + struct resource *mem; + void __iomem *regs; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL); + if (!i2s) { + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, i2s); + + i2s->dai = tegra20_i2s_dai_template; + i2s->dai.name = dev_name(&pdev->dev); + + i2s->clk_i2s = clk_get(&pdev->dev, NULL); + if (IS_ERR(i2s->clk_i2s)) { + dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk_i2s); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err_clk_put; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(i2s->regmap); + goto err_clk_put; + } + + i2s->capture_dma_data.addr = mem->start + TEGRA20_I2S_FIFO2; + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma_data.maxburst = 4; + + i2s->playback_dma_data.addr = mem->start + TEGRA20_I2S_FIFO1; + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma_data.maxburst = 4; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra20_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_component(&pdev->dev, &tegra20_i2s_component, + &i2s->dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_suspend; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_component; + } + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + clk_put(i2s->clk_i2s); +err: + return ret; +} + +static int tegra20_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra20_i2s *i2s = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_i2s_runtime_suspend(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + clk_put(i2s->clk_i2s); + + return 0; +} + +static const struct of_device_id tegra20_i2s_of_match[] = { + { .compatible = "nvidia,tegra20-i2s", }, + {}, +}; + +static const struct dev_pm_ops tegra20_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend, + tegra20_i2s_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_i2s_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = tegra20_i2s_of_match, + .pm = &tegra20_i2s_pm_ops, + }, + .probe = tegra20_i2s_platform_probe, + .remove = tegra20_i2s_platform_remove, +}; +module_platform_driver(tegra20_i2s_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra20 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_i2s_of_match); diff --git a/sound/soc/tegra/tegra20_i2s.h b/sound/soc/tegra/tegra20_i2s.h new file mode 100644 index 000000000..fa6c29cc1 --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.h @@ -0,0 +1,163 @@ +/* + * tegra20_i2s.h - Definitions for Tegra20 I2S driver + * + * Author: Stephen Warren + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA20_I2S_H__ +#define __TEGRA20_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA20_I2S1_BASE and TEGRA20_I2S2_BASE */ + +#define TEGRA20_I2S_CTRL 0x00 +#define TEGRA20_I2S_STATUS 0x04 +#define TEGRA20_I2S_TIMING 0x08 +#define TEGRA20_I2S_FIFO_SCR 0x0c +#define TEGRA20_I2S_PCM_CTRL 0x10 +#define TEGRA20_I2S_NW_CTRL 0x14 +#define TEGRA20_I2S_TDM_CTRL 0x20 +#define TEGRA20_I2S_TDM_TX_RX_CTRL 0x24 +#define TEGRA20_I2S_FIFO1 0x40 +#define TEGRA20_I2S_FIFO2 0x80 + +/* Fields in TEGRA20_I2S_CTRL */ + +#define TEGRA20_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30) +#define TEGRA20_I2S_CTRL_FIFO1_ENABLE (1 << 29) +#define TEGRA20_I2S_CTRL_FIFO2_ENABLE (1 << 28) +#define TEGRA20_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27) +#define TEGRA20_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26) +#define TEGRA20_I2S_CTRL_MASTER_ENABLE (1 << 25) + +#define TEGRA20_I2S_LRCK_LEFT_LOW 0 +#define TEGRA20_I2S_LRCK_RIGHT_LOW 1 + +#define TEGRA20_I2S_CTRL_LRCK_SHIFT 24 +#define TEGRA20_I2S_CTRL_LRCK_MASK (1 << TEGRA20_I2S_CTRL_LRCK_SHIFT) +#define TEGRA20_I2S_CTRL_LRCK_L_LOW (TEGRA20_I2S_LRCK_LEFT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT) +#define TEGRA20_I2S_CTRL_LRCK_R_LOW (TEGRA20_I2S_LRCK_RIGHT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA20_I2S_BIT_FORMAT_I2S 0 +#define TEGRA20_I2S_BIT_FORMAT_RJM 1 +#define TEGRA20_I2S_BIT_FORMAT_LJM 2 +#define TEGRA20_I2S_BIT_FORMAT_DSP 3 + +#define TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT 10 +#define TEGRA20_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_I2S (TEGRA20_I2S_BIT_FORMAT_I2S << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_RJM (TEGRA20_I2S_BIT_FORMAT_RJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_LJM (TEGRA20_I2S_BIT_FORMAT_LJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_DSP (TEGRA20_I2S_BIT_FORMAT_DSP << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) + +#define TEGRA20_I2S_BIT_SIZE_16 0 +#define TEGRA20_I2S_BIT_SIZE_20 1 +#define TEGRA20_I2S_BIT_SIZE_24 2 +#define TEGRA20_I2S_BIT_SIZE_32 3 + +#define TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT 8 +#define TEGRA20_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_16 (TEGRA20_I2S_BIT_SIZE_16 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_20 (TEGRA20_I2S_BIT_SIZE_20 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_24 (TEGRA20_I2S_BIT_SIZE_24 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_32 (TEGRA20_I2S_BIT_SIZE_32 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) + +#define TEGRA20_I2S_FIFO_16_LSB 0 +#define TEGRA20_I2S_FIFO_20_LSB 1 +#define TEGRA20_I2S_FIFO_24_LSB 2 +#define TEGRA20_I2S_FIFO_32 3 +#define TEGRA20_I2S_FIFO_PACKED 7 + +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT 4 +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA20_I2S_FIFO_16_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA20_I2S_FIFO_20_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA20_I2S_FIFO_24_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_32 (TEGRA20_I2S_FIFO_32 << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA20_I2S_FIFO_PACKED << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) + +#define TEGRA20_I2S_CTRL_IE_FIFO1_ERR (1 << 3) +#define TEGRA20_I2S_CTRL_IE_FIFO2_ERR (1 << 2) +#define TEGRA20_I2S_CTRL_QE_FIFO1 (1 << 1) +#define TEGRA20_I2S_CTRL_QE_FIFO2 (1 << 0) + +/* Fields in TEGRA20_I2S_STATUS */ + +#define TEGRA20_I2S_STATUS_FIFO1_RDY (1 << 31) +#define TEGRA20_I2S_STATUS_FIFO2_RDY (1 << 30) +#define TEGRA20_I2S_STATUS_FIFO1_BSY (1 << 29) +#define TEGRA20_I2S_STATUS_FIFO2_BSY (1 << 28) +#define TEGRA20_I2S_STATUS_FIFO1_ERR (1 << 3) +#define TEGRA20_I2S_STATUS_FIFO2_ERR (1 << 2) +#define TEGRA20_I2S_STATUS_QS_FIFO1 (1 << 1) +#define TEGRA20_I2S_STATUS_QS_FIFO2 (1 << 0) + +/* Fields in TEGRA20_I2S_TIMING */ + +#define TEGRA20_I2S_TIMING_NON_SYM_ENABLE (1 << 12) +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7ff +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA20_I2S_FIFO_SCR */ + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24 +#define TEGRA20_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16 +#define TEGRA20_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_CLR (1 << 12) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_CLR (1 << 8) + +#define TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT 0 +#define TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1 +#define TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2 +#define TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3 + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4 +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) + +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0 +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) + +struct tegra20_i2s { + struct snd_soc_dai_driver dai; + struct clk *clk_i2s; + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c new file mode 100644 index 000000000..767c0491e --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.c @@ -0,0 +1,375 @@ +/* + * tegra20_spdif.c - Tegra20 SPDIF driver + * + * Author: Stephen Warren + * Copyright (C) 2011-2012 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tegra20_spdif.h" + +#define DRV_NAME "tegra20-spdif" + +static int tegra20_spdif_runtime_suspend(struct device *dev) +{ + struct tegra20_spdif *spdif = dev_get_drvdata(dev); + + clk_disable_unprepare(spdif->clk_spdif_out); + + return 0; +} + +static int tegra20_spdif_runtime_resume(struct device *dev) +{ + struct tegra20_spdif *spdif = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(spdif->clk_spdif_out); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int mask = 0, val = 0; + int ret, spdifclock; + + mask |= TEGRA20_SPDIF_CTRL_PACK | + TEGRA20_SPDIF_CTRL_BIT_MODE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val |= TEGRA20_SPDIF_CTRL_PACK | + TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT; + break; + default: + return -EINVAL; + } + + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val); + + switch (params_rate(params)) { + case 32000: + spdifclock = 4096000; + break; + case 44100: + spdifclock = 5644800; + break; + case 48000: + spdifclock = 6144000; + break; + case 88200: + spdifclock = 11289600; + break; + case 96000: + spdifclock = 12288000; + break; + case 176400: + spdifclock = 22579200; + break; + case 192000: + spdifclock = 24576000; + break; + default: + return -EINVAL; + } + + ret = clk_set_rate(spdif->clk_spdif_out, spdifclock); + if (ret) { + dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret); + return ret; + } + + return 0; +} + +static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif) +{ + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, + TEGRA20_SPDIF_CTRL_TX_EN, + TEGRA20_SPDIF_CTRL_TX_EN); +} + +static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif) +{ + regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, + TEGRA20_SPDIF_CTRL_TX_EN, 0); +} + +static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + tegra20_spdif_start_playback(spdif); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + tegra20_spdif_stop_playback(spdif); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra20_spdif_probe(struct snd_soc_dai *dai) +{ + struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = NULL; + dai->playback_dma_data = &spdif->playback_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = { + .hw_params = tegra20_spdif_hw_params, + .trigger = tegra20_spdif_trigger, +}; + +static struct snd_soc_dai_driver tegra20_spdif_dai = { + .name = DRV_NAME, + .probe = tegra20_spdif_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra20_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver tegra20_spdif_component = { + .name = DRV_NAME, +}; + +static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_SPDIF_CTRL: + case TEGRA20_SPDIF_STATUS: + case TEGRA20_SPDIF_STROBE_CTRL: + case TEGRA20_SPDIF_DATA_FIFO_CSR: + case TEGRA20_SPDIF_DATA_OUT: + case TEGRA20_SPDIF_DATA_IN: + case TEGRA20_SPDIF_CH_STA_RX_A: + case TEGRA20_SPDIF_CH_STA_RX_B: + case TEGRA20_SPDIF_CH_STA_RX_C: + case TEGRA20_SPDIF_CH_STA_RX_D: + case TEGRA20_SPDIF_CH_STA_RX_E: + case TEGRA20_SPDIF_CH_STA_RX_F: + case TEGRA20_SPDIF_CH_STA_TX_A: + case TEGRA20_SPDIF_CH_STA_TX_B: + case TEGRA20_SPDIF_CH_STA_TX_C: + case TEGRA20_SPDIF_CH_STA_TX_D: + case TEGRA20_SPDIF_CH_STA_TX_E: + case TEGRA20_SPDIF_CH_STA_TX_F: + case TEGRA20_SPDIF_USR_STA_RX_A: + case TEGRA20_SPDIF_USR_DAT_TX_A: + return true; + default: + return false; + } +} + +static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_SPDIF_STATUS: + case TEGRA20_SPDIF_DATA_FIFO_CSR: + case TEGRA20_SPDIF_DATA_OUT: + case TEGRA20_SPDIF_DATA_IN: + case TEGRA20_SPDIF_CH_STA_RX_A: + case TEGRA20_SPDIF_CH_STA_RX_B: + case TEGRA20_SPDIF_CH_STA_RX_C: + case TEGRA20_SPDIF_CH_STA_RX_D: + case TEGRA20_SPDIF_CH_STA_RX_E: + case TEGRA20_SPDIF_CH_STA_RX_F: + case TEGRA20_SPDIF_USR_STA_RX_A: + case TEGRA20_SPDIF_USR_DAT_TX_A: + return true; + default: + return false; + } +} + +static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA20_SPDIF_DATA_OUT: + case TEGRA20_SPDIF_DATA_IN: + case TEGRA20_SPDIF_USR_STA_RX_A: + case TEGRA20_SPDIF_USR_DAT_TX_A: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra20_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA20_SPDIF_USR_DAT_TX_A, + .writeable_reg = tegra20_spdif_wr_rd_reg, + .readable_reg = tegra20_spdif_wr_rd_reg, + .volatile_reg = tegra20_spdif_volatile_reg, + .precious_reg = tegra20_spdif_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int tegra20_spdif_platform_probe(struct platform_device *pdev) +{ + struct tegra20_spdif *spdif; + struct resource *mem, *dmareq; + void __iomem *regs; + int ret; + + spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif), + GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, spdif); + + spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "spdif_out"); + if (IS_ERR(spdif->clk_spdif_out)) { + pr_err("Can't retrieve spdif clock\n"); + ret = PTR_ERR(spdif->clk_spdif_out); + return ret; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmareq) { + dev_err(&pdev->dev, "No DMA resource\n"); + return -ENODEV; + } + + spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(spdif->regmap); + return ret; + } + + spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; + spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->playback_dma_data.maxburst = 4; + spdif->playback_dma_data.slave_id = dmareq->start; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra20_spdif_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = snd_soc_register_component(&pdev->dev, &tegra20_spdif_component, + &tegra20_spdif_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_suspend; + } + + ret = tegra_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_component; + } + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_spdif_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int tegra20_spdif_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra20_spdif_runtime_suspend(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct dev_pm_ops tegra20_spdif_pm_ops = { + SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend, + tegra20_spdif_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_spdif_driver = { + .driver = { + .name = DRV_NAME, + .pm = &tegra20_spdif_pm_ops, + }, + .probe = tegra20_spdif_platform_probe, + .remove = tegra20_spdif_platform_remove, +}; + +module_platform_driver(tegra20_spdif_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra20_spdif.h b/sound/soc/tegra/tegra20_spdif.h new file mode 100644 index 000000000..85a9aefcc --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.h @@ -0,0 +1,470 @@ +/* + * tegra20_spdif.h - Definitions for Tegra20 SPDIF driver + * + * Author: Stephen Warren + * Copyright (C) 2011 - NVIDIA, Inc. + * + * Based on code copyright/by: + * Copyright (c) 2008-2009, NVIDIA Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA20_SPDIF_H__ +#define __TEGRA20_SPDIF_H__ + +#include "tegra_pcm.h" + +/* Offsets from TEGRA20_SPDIF_BASE */ + +#define TEGRA20_SPDIF_CTRL 0x0 +#define TEGRA20_SPDIF_STATUS 0x4 +#define TEGRA20_SPDIF_STROBE_CTRL 0x8 +#define TEGRA20_SPDIF_DATA_FIFO_CSR 0x0C +#define TEGRA20_SPDIF_DATA_OUT 0x40 +#define TEGRA20_SPDIF_DATA_IN 0x80 +#define TEGRA20_SPDIF_CH_STA_RX_A 0x100 +#define TEGRA20_SPDIF_CH_STA_RX_B 0x104 +#define TEGRA20_SPDIF_CH_STA_RX_C 0x108 +#define TEGRA20_SPDIF_CH_STA_RX_D 0x10C +#define TEGRA20_SPDIF_CH_STA_RX_E 0x110 +#define TEGRA20_SPDIF_CH_STA_RX_F 0x114 +#define TEGRA20_SPDIF_CH_STA_TX_A 0x140 +#define TEGRA20_SPDIF_CH_STA_TX_B 0x144 +#define TEGRA20_SPDIF_CH_STA_TX_C 0x148 +#define TEGRA20_SPDIF_CH_STA_TX_D 0x14C +#define TEGRA20_SPDIF_CH_STA_TX_E 0x150 +#define TEGRA20_SPDIF_CH_STA_TX_F 0x154 +#define TEGRA20_SPDIF_USR_STA_RX_A 0x180 +#define TEGRA20_SPDIF_USR_DAT_TX_A 0x1C0 + +/* Fields in TEGRA20_SPDIF_CTRL */ + +/* Start capturing from 0=right, 1=left channel */ +#define TEGRA20_SPDIF_CTRL_CAP_LC (1 << 30) + +/* SPDIF receiver(RX) enable */ +#define TEGRA20_SPDIF_CTRL_RX_EN (1 << 29) + +/* SPDIF Transmitter(TX) enable */ +#define TEGRA20_SPDIF_CTRL_TX_EN (1 << 28) + +/* Transmit Channel status */ +#define TEGRA20_SPDIF_CTRL_TC_EN (1 << 27) + +/* Transmit user Data */ +#define TEGRA20_SPDIF_CTRL_TU_EN (1 << 26) + +/* Interrupt on transmit error */ +#define TEGRA20_SPDIF_CTRL_IE_TXE (1 << 25) + +/* Interrupt on receive error */ +#define TEGRA20_SPDIF_CTRL_IE_RXE (1 << 24) + +/* Interrupt on invalid preamble */ +#define TEGRA20_SPDIF_CTRL_IE_P (1 << 23) + +/* Interrupt on "B" preamble */ +#define TEGRA20_SPDIF_CTRL_IE_B (1 << 22) + +/* Interrupt when block of channel status received */ +#define TEGRA20_SPDIF_CTRL_IE_C (1 << 21) + +/* Interrupt when a valid information unit (IU) is received */ +#define TEGRA20_SPDIF_CTRL_IE_U (1 << 20) + +/* Interrupt when RX user FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_RU (1 << 19) + +/* Interrupt when TX user FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_TU (1 << 18) + +/* Interrupt when RX data FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_RX (1 << 17) + +/* Interrupt when TX data FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_TX (1 << 16) + +/* Loopback test mode enable */ +#define TEGRA20_SPDIF_CTRL_LBK_EN (1 << 15) + +/* + * Pack data mode: + * 0 = Single data (16 bit needs to be padded to match the + * interface data bit size). + * 1 = Packeted left/right channel data into a single word. + */ +#define TEGRA20_SPDIF_CTRL_PACK (1 << 14) + +/* + * 00 = 16bit data + * 01 = 20bit data + * 10 = 24bit data + * 11 = raw data + */ +#define TEGRA20_SPDIF_BIT_MODE_16BIT 0 +#define TEGRA20_SPDIF_BIT_MODE_20BIT 1 +#define TEGRA20_SPDIF_BIT_MODE_24BIT 2 +#define TEGRA20_SPDIF_BIT_MODE_RAW 3 + +#define TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT 12 +#define TEGRA20_SPDIF_CTRL_BIT_MODE_MASK (3 << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT (TEGRA20_SPDIF_BIT_MODE_16BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_20BIT (TEGRA20_SPDIF_BIT_MODE_20BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_24BIT (TEGRA20_SPDIF_BIT_MODE_24BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_RAW (TEGRA20_SPDIF_BIT_MODE_RAW << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) + +/* Fields in TEGRA20_SPDIF_STATUS */ + +/* + * Note: IS_P, IS_B, IS_C, and IS_U are sticky bits. Software must + * write a 1 to the corresponding bit location to clear the status. + */ + +/* + * Receiver(RX) shifter is busy receiving data. + * This bit is asserted when the receiver first locked onto the + * preamble of the data stream after RX_EN is asserted. This bit is + * deasserted when either, + * (a) the end of a frame is reached after RX_EN is deeasserted, or + * (b) the SPDIF data stream becomes inactive. + */ +#define TEGRA20_SPDIF_STATUS_RX_BSY (1 << 29) + +/* + * Transmitter(TX) shifter is busy transmitting data. + * This bit is asserted when TX_EN is asserted. + * This bit is deasserted when the end of a frame is reached after + * TX_EN is deasserted. + */ +#define TEGRA20_SPDIF_STATUS_TX_BSY (1 << 28) + +/* + * TX is busy shifting out channel status. + * This bit is asserted when both TX_EN and TC_EN are asserted and + * data from CH_STA_TX_A register is loaded into the internal shifter. + * This bit is deasserted when either, + * (a) the end of a frame is reached after TX_EN is deasserted, or + * (b) CH_STA_TX_F register is loaded into the internal shifter. + */ +#define TEGRA20_SPDIF_STATUS_TC_BSY (1 << 27) + +/* + * TX User data FIFO busy. + * This bit is asserted when TX_EN and TXU_EN are asserted and + * there's data in the TX user FIFO. This bit is deassert when either, + * (a) the end of a frame is reached after TX_EN is deasserted, or + * (b) there's no data left in the TX user FIFO. + */ +#define TEGRA20_SPDIF_STATUS_TU_BSY (1 << 26) + +/* TX FIFO Underrun error status */ +#define TEGRA20_SPDIF_STATUS_TX_ERR (1 << 25) + +/* RX FIFO Overrun error status */ +#define TEGRA20_SPDIF_STATUS_RX_ERR (1 << 24) + +/* Preamble status: 0=Preamble OK, 1=bad/missing preamble */ +#define TEGRA20_SPDIF_STATUS_IS_P (1 << 23) + +/* B-preamble detection status: 0=not detected, 1=B-preamble detected */ +#define TEGRA20_SPDIF_STATUS_IS_B (1 << 22) + +/* + * RX channel block data receive status: + * 0=entire block not recieved yet. + * 1=received entire block of channel status, + */ +#define TEGRA20_SPDIF_STATUS_IS_C (1 << 21) + +/* RX User Data Valid flag: 1=valid IU detected, 0 = no IU detected. */ +#define TEGRA20_SPDIF_STATUS_IS_U (1 << 20) + +/* + * RX User FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_RU (1 << 19) + +/* + * TX User FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_TU (1 << 18) + +/* + * RX Data FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_RX (1 << 17) + +/* + * TX Data FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_TX (1 << 16) + +/* Fields in TEGRA20_SPDIF_STROBE_CTRL */ + +/* + * Indicates the approximate number of detected SPDIFIN clocks within a + * bi-phase period. + */ +#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT 16 +#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_MASK (0xff << TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT) + +/* Data strobe mode: 0=Auto-locked 1=Manual locked */ +#define TEGRA20_SPDIF_STROBE_CTRL_STROBE (1 << 15) + +/* + * Manual data strobe time within the bi-phase clock period (in terms of + * the number of over-sampling clocks). + */ +#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT 8 +#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_MASK (0x1f << TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT) + +/* + * Manual SPDIFIN bi-phase clock period (in terms of the number of + * over-sampling clocks). + */ +#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT 0 +#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_MASK (0x3f << TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT) + +/* Fields in SPDIF_DATA_FIFO_CSR */ + +/* Clear Receiver User FIFO (RX USR.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_CLR (1 << 31) + +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT 0 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS 1 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS 2 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS 3 + +/* RU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT 29 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU2_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU3_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) + +/* Number of RX USR.FIFO levels with valid data. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT 24 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT) + +/* Clear Transmitter User FIFO (TX USR.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_CLR (1 << 23) + +/* TU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT 21 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU2_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU3_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) + +/* Number of TX USR.FIFO levels that could be filled. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT 16 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT) + +/* Clear Receiver Data FIFO (RX DATA.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_CLR (1 << 15) + +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT 0 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS 1 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS 2 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS 3 + +/* RU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT 13 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU8_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU12_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) + +/* Number of RX DATA.FIFO levels with valid data. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT 8 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_MASK (0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT) + +/* Clear Transmitter Data FIFO (TX DATA.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_CLR (1 << 7) + +/* TU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT 5 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK \ + (0x3 << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU1_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU8_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU12_WORD_FULL \ + (TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) + +/* Number of TX DATA.FIFO levels that could be filled. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT 0 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_MASK (0x1f << SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT) + +/* Fields in TEGRA20_SPDIF_DATA_OUT */ + +/* + * This register has 5 different formats: + * 16-bit (BIT_MODE=00, PACK=0) + * 20-bit (BIT_MODE=01, PACK=0) + * 24-bit (BIT_MODE=10, PACK=0) + * raw (BIT_MODE=11, PACK=0) + * 16-bit packed (BIT_MODE=00, PACK=1) + */ + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_P (1 << 31) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_C (1 << 30) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_U (1 << 29) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_V (1 << 28) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT 8 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT 4 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT 16 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT 0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT) + +/* Fields in TEGRA20_SPDIF_DATA_IN */ + +/* + * This register has 5 different formats: + * 16-bit (BIT_MODE=00, PACK=0) + * 20-bit (BIT_MODE=01, PACK=0) + * 24-bit (BIT_MODE=10, PACK=0) + * raw (BIT_MODE=11, PACK=0) + * 16-bit packed (BIT_MODE=00, PACK=1) + * + * Bits 31:24 are common to all modes except 16-bit packed + */ + +#define TEGRA20_SPDIF_DATA_IN_DATA_P (1 << 31) +#define TEGRA20_SPDIF_DATA_IN_DATA_C (1 << 30) +#define TEGRA20_SPDIF_DATA_IN_DATA_U (1 << 29) +#define TEGRA20_SPDIF_DATA_IN_DATA_V (1 << 28) + +#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT 24 +#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_20_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_24_MASK (0xffffff << TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT 8 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_MASK (0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT 4 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_MASK (0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT 16 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT 0 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_MASK (0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT) + +/* Fields in TEGRA20_SPDIF_CH_STA_RX_A */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_B */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_C */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_D */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_E */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_F */ + +/* + * The 6-word receive channel data page buffer holds a block (192 frames) of + * channel status information. The order of receive is from LSB to MSB + * bit, and from CH_STA_RX_A to CH_STA_RX_F then back to CH_STA_RX_A. + */ + +/* Fields in TEGRA20_SPDIF_CH_STA_TX_A */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_B */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_C */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_D */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_E */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_F */ + +/* + * The 6-word transmit channel data page buffer holds a block (192 frames) of + * channel status information. The order of transmission is from LSB to MSB + * bit, and from CH_STA_TX_A to CH_STA_TX_F then back to CH_STA_TX_A. + */ + +/* Fields in TEGRA20_SPDIF_USR_STA_RX_A */ + +/* + * This 4-word deep FIFO receives user FIFO field information. The order of + * receive is from LSB to MSB bit. + */ + +/* Fields in TEGRA20_SPDIF_USR_DAT_TX_A */ + +/* + * This 4-word deep FIFO transmits user FIFO field information. The order of + * transmission is from LSB to MSB bit. + */ + +struct tegra20_spdif { + struct clk *clk_spdif_out; + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c new file mode 100644 index 000000000..88e838ac9 --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.c @@ -0,0 +1,755 @@ +/* + * tegra30_ahub.c - Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tegra30_ahub.h" + +#define DRV_NAME "tegra30-ahub" + +static struct tegra30_ahub *ahub; + +static inline void tegra30_apbif_write(u32 reg, u32 val) +{ + regmap_write(ahub->regmap_apbif, reg, val); +} + +static inline u32 tegra30_apbif_read(u32 reg) +{ + u32 val; + + regmap_read(ahub->regmap_apbif, reg, &val); + return val; +} + +static inline void tegra30_audio_write(u32 reg, u32 val) +{ + regmap_write(ahub->regmap_ahub, reg, val); +} + +static int tegra30_ahub_runtime_suspend(struct device *dev) +{ + regcache_cache_only(ahub->regmap_apbif, true); + regcache_cache_only(ahub->regmap_ahub, true); + + clk_disable_unprepare(ahub->clk_apbif); + clk_disable_unprepare(ahub->clk_d_audio); + + return 0; +} + +/* + * clk_apbif isn't required for an I2S<->I2S configuration where no PCM data + * is read from or sent to memory. However, that's not something the rest of + * the driver supports right now, so we'll just treat the two clocks as one + * for now. + * + * These functions should not be a plain ref-count. Instead, each active stream + * contributes some requirement to the minimum clock rate, so starting or + * stopping streams should dynamically adjust the clock as required. However, + * this is not yet implemented. + */ +static int tegra30_ahub_runtime_resume(struct device *dev) +{ + int ret; + + ret = clk_prepare_enable(ahub->clk_d_audio); + if (ret) { + dev_err(dev, "clk_enable d_audio failed: %d\n", ret); + return ret; + } + ret = clk_prepare_enable(ahub->clk_apbif); + if (ret) { + dev_err(dev, "clk_enable apbif failed: %d\n", ret); + clk_disable(ahub->clk_d_audio); + return ret; + } + + regcache_cache_only(ahub->regmap_apbif, false); + regcache_cache_only(ahub->regmap_ahub, false); + + return 0; +} + +int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg) +{ + int channel; + u32 reg, val; + struct tegra30_ahub_cif_conf cif_conf; + + channel = find_first_zero_bit(ahub->rx_usage, + TEGRA30_AHUB_CHANNEL_CTRL_COUNT); + if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT) + return -EBUSY; + + __set_bit(channel, ahub->rx_usage); + + *rxcif = TEGRA30_AHUB_RXCIF_APBIF_RX0 + channel; + snprintf(dmachan, dmachan_len, "rx%d", channel); + *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_RXFIFO + + (channel * TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE); + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK | + TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK); + val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) | + TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN | + TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16; + tegra30_apbif_write(reg, val); + + cif_conf.threshold = 0; + cif_conf.audio_channels = 2; + cif_conf.client_channels = 2; + cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + reg = TEGRA30_AHUB_CIF_RX_CTRL + + (channel * TEGRA30_AHUB_CIF_RX_CTRL_STRIDE); + ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_rx_fifo); + +int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg, val; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val |= TEGRA30_AHUB_CHANNEL_CTRL_RX_EN; + tegra30_apbif_write(reg, val); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_enable_rx_fifo); + +int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg, val; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~TEGRA30_AHUB_CHANNEL_CTRL_RX_EN; + tegra30_apbif_write(reg, val); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_disable_rx_fifo); + +int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + + __clear_bit(channel, ahub->rx_usage); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_free_rx_fifo); + +int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg) +{ + int channel; + u32 reg, val; + struct tegra30_ahub_cif_conf cif_conf; + + channel = find_first_zero_bit(ahub->tx_usage, + TEGRA30_AHUB_CHANNEL_CTRL_COUNT); + if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT) + return -EBUSY; + + __set_bit(channel, ahub->tx_usage); + + *txcif = TEGRA30_AHUB_TXCIF_APBIF_TX0 + channel; + snprintf(dmachan, dmachan_len, "tx%d", channel); + *fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_TXFIFO + + (channel * TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE); + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK | + TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK); + val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) | + TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN | + TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16; + tegra30_apbif_write(reg, val); + + cif_conf.threshold = 0; + cif_conf.audio_channels = 2; + cif_conf.client_channels = 2; + cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + reg = TEGRA30_AHUB_CIF_TX_CTRL + + (channel * TEGRA30_AHUB_CIF_TX_CTRL_STRIDE); + ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_tx_fifo); + +int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif) +{ + int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; + int reg, val; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val |= TEGRA30_AHUB_CHANNEL_CTRL_TX_EN; + tegra30_apbif_write(reg, val); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_enable_tx_fifo); + +int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif) +{ + int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; + int reg, val; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_CHANNEL_CTRL + + (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); + val = tegra30_apbif_read(reg); + val &= ~TEGRA30_AHUB_CHANNEL_CTRL_TX_EN; + tegra30_apbif_write(reg, val); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_disable_tx_fifo); + +int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif) +{ + int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; + + __clear_bit(channel, ahub->tx_usage); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_free_tx_fifo); + +int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif, + enum tegra30_ahub_txcif txcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_AUDIO_RX + + (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); + tegra30_audio_write(reg, 1 << txcif); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_set_rx_cif_source); + +int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif) +{ + int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + int reg; + + pm_runtime_get_sync(ahub->dev); + + reg = TEGRA30_AHUB_AUDIO_RX + + (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); + tegra30_audio_write(reg, 0); + + pm_runtime_put(ahub->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_unset_rx_cif_source); + +#define MOD_LIST_MASK_TEGRA30 BIT(0) +#define MOD_LIST_MASK_TEGRA114 BIT(1) +#define MOD_LIST_MASK_TEGRA124 BIT(2) + +#define MOD_LIST_MASK_TEGRA30_OR_LATER \ + (MOD_LIST_MASK_TEGRA30 | MOD_LIST_MASK_TEGRA114 | \ + MOD_LIST_MASK_TEGRA124) +#define MOD_LIST_MASK_TEGRA114_OR_LATER \ + (MOD_LIST_MASK_TEGRA114 | MOD_LIST_MASK_TEGRA124) + +static const struct { + const char *rst_name; + u32 mod_list_mask; +} configlink_mods[] = { + { "i2s0", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s1", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s2", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s3", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "i2s4", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "dam0", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "dam1", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "dam2", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "spdif", MOD_LIST_MASK_TEGRA30_OR_LATER }, + { "amx", MOD_LIST_MASK_TEGRA114_OR_LATER }, + { "adx", MOD_LIST_MASK_TEGRA114_OR_LATER }, + { "amx1", MOD_LIST_MASK_TEGRA124 }, + { "adx1", MOD_LIST_MASK_TEGRA124 }, + { "afc0", MOD_LIST_MASK_TEGRA124 }, + { "afc1", MOD_LIST_MASK_TEGRA124 }, + { "afc2", MOD_LIST_MASK_TEGRA124 }, + { "afc3", MOD_LIST_MASK_TEGRA124 }, + { "afc4", MOD_LIST_MASK_TEGRA124 }, + { "afc5", MOD_LIST_MASK_TEGRA124 }, +}; + +#define LAST_REG(name) \ + (TEGRA30_AHUB_##name + \ + (TEGRA30_AHUB_##name##_STRIDE * TEGRA30_AHUB_##name##_COUNT) - 4) + +#define REG_IN_ARRAY(reg, name) \ + ((reg >= TEGRA30_AHUB_##name) && \ + (reg <= LAST_REG(name) && \ + (!((reg - TEGRA30_AHUB_##name) % TEGRA30_AHUB_##name##_STRIDE)))) + +static bool tegra30_ahub_apbif_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_AHUB_CONFIG_LINK_CTRL: + case TEGRA30_AHUB_MISC_CTRL: + case TEGRA30_AHUB_APBDMA_LIVE_STATUS: + case TEGRA30_AHUB_I2S_LIVE_STATUS: + case TEGRA30_AHUB_SPDIF_LIVE_STATUS: + case TEGRA30_AHUB_I2S_INT_MASK: + case TEGRA30_AHUB_DAM_INT_MASK: + case TEGRA30_AHUB_SPDIF_INT_MASK: + case TEGRA30_AHUB_APBIF_INT_MASK: + case TEGRA30_AHUB_I2S_INT_STATUS: + case TEGRA30_AHUB_DAM_INT_STATUS: + case TEGRA30_AHUB_SPDIF_INT_STATUS: + case TEGRA30_AHUB_APBIF_INT_STATUS: + case TEGRA30_AHUB_I2S_INT_SOURCE: + case TEGRA30_AHUB_DAM_INT_SOURCE: + case TEGRA30_AHUB_SPDIF_INT_SOURCE: + case TEGRA30_AHUB_APBIF_INT_SOURCE: + case TEGRA30_AHUB_I2S_INT_SET: + case TEGRA30_AHUB_DAM_INT_SET: + case TEGRA30_AHUB_SPDIF_INT_SET: + case TEGRA30_AHUB_APBIF_INT_SET: + return true; + default: + break; + } + + if (REG_IN_ARRAY(reg, CHANNEL_CTRL) || + REG_IN_ARRAY(reg, CHANNEL_CLEAR) || + REG_IN_ARRAY(reg, CHANNEL_STATUS) || + REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || + REG_IN_ARRAY(reg, CIF_TX_CTRL) || + REG_IN_ARRAY(reg, CIF_RX_CTRL) || + REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) + return true; + + return false; +} + +static bool tegra30_ahub_apbif_volatile_reg(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case TEGRA30_AHUB_CONFIG_LINK_CTRL: + case TEGRA30_AHUB_MISC_CTRL: + case TEGRA30_AHUB_APBDMA_LIVE_STATUS: + case TEGRA30_AHUB_I2S_LIVE_STATUS: + case TEGRA30_AHUB_SPDIF_LIVE_STATUS: + case TEGRA30_AHUB_I2S_INT_STATUS: + case TEGRA30_AHUB_DAM_INT_STATUS: + case TEGRA30_AHUB_SPDIF_INT_STATUS: + case TEGRA30_AHUB_APBIF_INT_STATUS: + case TEGRA30_AHUB_I2S_INT_SET: + case TEGRA30_AHUB_DAM_INT_SET: + case TEGRA30_AHUB_SPDIF_INT_SET: + case TEGRA30_AHUB_APBIF_INT_SET: + return true; + default: + break; + } + + if (REG_IN_ARRAY(reg, CHANNEL_CLEAR) || + REG_IN_ARRAY(reg, CHANNEL_STATUS) || + REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || + REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) + return true; + + return false; +} + +static bool tegra30_ahub_apbif_precious_reg(struct device *dev, + unsigned int reg) +{ + if (REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || + REG_IN_ARRAY(reg, CHANNEL_RXFIFO)) + return true; + + return false; +} + +static const struct regmap_config tegra30_ahub_apbif_regmap_config = { + .name = "apbif", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = TEGRA30_AHUB_APBIF_INT_SET, + .writeable_reg = tegra30_ahub_apbif_wr_rd_reg, + .readable_reg = tegra30_ahub_apbif_wr_rd_reg, + .volatile_reg = tegra30_ahub_apbif_volatile_reg, + .precious_reg = tegra30_ahub_apbif_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static bool tegra30_ahub_ahub_wr_rd_reg(struct device *dev, unsigned int reg) +{ + if (REG_IN_ARRAY(reg, AUDIO_RX)) + return true; + + return false; +} + +static const struct regmap_config tegra30_ahub_ahub_regmap_config = { + .name = "ahub", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = LAST_REG(AUDIO_RX), + .writeable_reg = tegra30_ahub_ahub_wr_rd_reg, + .readable_reg = tegra30_ahub_ahub_wr_rd_reg, + .cache_type = REGCACHE_FLAT, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra30 = { + .mod_list_mask = MOD_LIST_MASK_TEGRA30, + .set_audio_cif = tegra30_ahub_set_cif, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra114 = { + .mod_list_mask = MOD_LIST_MASK_TEGRA114, + .set_audio_cif = tegra30_ahub_set_cif, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra124 = { + .mod_list_mask = MOD_LIST_MASK_TEGRA124, + .set_audio_cif = tegra124_ahub_set_cif, +}; + +static const struct of_device_id tegra30_ahub_of_match[] = { + { .compatible = "nvidia,tegra124-ahub", .data = &soc_data_tegra124 }, + { .compatible = "nvidia,tegra114-ahub", .data = &soc_data_tegra114 }, + { .compatible = "nvidia,tegra30-ahub", .data = &soc_data_tegra30 }, + {}, +}; + +static int tegra30_ahub_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct tegra30_ahub_soc_data *soc_data; + struct reset_control *rst; + int i; + struct resource *res0, *res1; + void __iomem *regs_apbif, *regs_ahub; + int ret = 0; + + if (ahub) + return -ENODEV; + + match = of_match_device(tegra30_ahub_of_match, &pdev->dev); + if (!match) + return -EINVAL; + soc_data = match->data; + + /* + * The AHUB hosts a register bus: the "configlink". For this to + * operate correctly, all devices on this bus must be out of reset. + * Ensure that here. + */ + for (i = 0; i < ARRAY_SIZE(configlink_mods); i++) { + if (!(configlink_mods[i].mod_list_mask & + soc_data->mod_list_mask)) + continue; + + rst = reset_control_get_exclusive(&pdev->dev, + configlink_mods[i].rst_name); + if (IS_ERR(rst)) { + dev_err(&pdev->dev, "Can't get reset %s\n", + configlink_mods[i].rst_name); + ret = PTR_ERR(rst); + return ret; + } + + ret = reset_control_deassert(rst); + reset_control_put(rst); + if (ret) + return ret; + } + + ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub), + GFP_KERNEL); + if (!ahub) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, ahub); + + ahub->soc_data = soc_data; + ahub->dev = &pdev->dev; + + ahub->clk_d_audio = devm_clk_get(&pdev->dev, "d_audio"); + if (IS_ERR(ahub->clk_d_audio)) { + dev_err(&pdev->dev, "Can't retrieve ahub d_audio clock\n"); + ret = PTR_ERR(ahub->clk_d_audio); + return ret; + } + + ahub->clk_apbif = devm_clk_get(&pdev->dev, "apbif"); + if (IS_ERR(ahub->clk_apbif)) { + dev_err(&pdev->dev, "Can't retrieve ahub apbif clock\n"); + ret = PTR_ERR(ahub->clk_apbif); + return ret; + } + + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs_apbif = devm_ioremap_resource(&pdev->dev, res0); + if (IS_ERR(regs_apbif)) + return PTR_ERR(regs_apbif); + + ahub->apbif_addr = res0->start; + + ahub->regmap_apbif = devm_regmap_init_mmio(&pdev->dev, regs_apbif, + &tegra30_ahub_apbif_regmap_config); + if (IS_ERR(ahub->regmap_apbif)) { + dev_err(&pdev->dev, "apbif regmap init failed\n"); + ret = PTR_ERR(ahub->regmap_apbif); + return ret; + } + regcache_cache_only(ahub->regmap_apbif, true); + + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + regs_ahub = devm_ioremap_resource(&pdev->dev, res1); + if (IS_ERR(regs_ahub)) + return PTR_ERR(regs_ahub); + + ahub->regmap_ahub = devm_regmap_init_mmio(&pdev->dev, regs_ahub, + &tegra30_ahub_ahub_regmap_config); + if (IS_ERR(ahub->regmap_ahub)) { + dev_err(&pdev->dev, "ahub regmap init failed\n"); + ret = PTR_ERR(ahub->regmap_ahub); + return ret; + } + regcache_cache_only(ahub->regmap_ahub, true); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra30_ahub_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + + return 0; + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int tegra30_ahub_remove(struct platform_device *pdev) +{ + if (!ahub) + return -ENODEV; + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_ahub_runtime_suspend(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra30_ahub_suspend(struct device *dev) +{ + regcache_mark_dirty(ahub->regmap_ahub); + regcache_mark_dirty(ahub->regmap_apbif); + + return 0; +} + +static int tegra30_ahub_resume(struct device *dev) +{ + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put(dev); + return ret; + } + ret = regcache_sync(ahub->regmap_ahub); + ret |= regcache_sync(ahub->regmap_apbif); + pm_runtime_put(dev); + + return ret; +} +#endif + +static const struct dev_pm_ops tegra30_ahub_pm_ops = { + SET_RUNTIME_PM_OPS(tegra30_ahub_runtime_suspend, + tegra30_ahub_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra30_ahub_suspend, tegra30_ahub_resume) +}; + +static struct platform_driver tegra30_ahub_driver = { + .probe = tegra30_ahub_probe, + .remove = tegra30_ahub_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = tegra30_ahub_of_match, + .pm = &tegra30_ahub_pm_ops, + }, +}; +module_platform_driver(tegra30_ahub_driver); + +void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << + TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + ((conf->audio_channels - 1) << + TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + ((conf->client_channels - 1) << + TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + (conf->audio_bits << + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << + TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << + TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << + TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | + (conf->direction << + TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | + (conf->truncate << + TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << + TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + + regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra30_ahub_set_cif); + +void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf) +{ + unsigned int value; + + value = (conf->threshold << + TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | + ((conf->audio_channels - 1) << + TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | + ((conf->client_channels - 1) << + TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | + (conf->audio_bits << + TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | + (conf->client_bits << + TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | + (conf->expand << + TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | + (conf->stereo_conv << + TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | + (conf->replicate << + TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | + (conf->direction << + TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | + (conf->truncate << + TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | + (conf->mono_conv << + TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + + regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra124_ahub_set_cif); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra30 AHUB driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra30_ahub_of_match); diff --git a/sound/soc/tegra/tegra30_ahub.h b/sound/soc/tegra/tegra30_ahub.h new file mode 100644 index 000000000..fd7ba75ed --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.h @@ -0,0 +1,534 @@ +/* + * tegra30_ahub.h - Definitions for Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#ifndef __TEGRA30_AHUB_H__ +#define __TEGRA30_AHUB_H__ + +/* Fields in *_CIF_RX/TX_CTRL; used by AHUB FIFOs, and all other audio modules */ + +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 28 +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0xf +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) + +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT 24 +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US 0x3f +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK (TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 24 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 7 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT 20 +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US 0xf +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK (TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 7 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT 16 +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US 0xf +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK (TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) + +#define TEGRA30_AUDIOCIF_BITS_4 0 +#define TEGRA30_AUDIOCIF_BITS_8 1 +#define TEGRA30_AUDIOCIF_BITS_12 2 +#define TEGRA30_AUDIOCIF_BITS_16 3 +#define TEGRA30_AUDIOCIF_BITS_20 4 +#define TEGRA30_AUDIOCIF_BITS_24 5 +#define TEGRA30_AUDIOCIF_BITS_28 6 +#define TEGRA30_AUDIOCIF_BITS_32 7 + +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT 12 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT 8 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_MASK (7 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_4 (TEGRA30_AUDIOCIF_BITS_4 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_8 (TEGRA30_AUDIOCIF_BITS_8 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_12 (TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16 (TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_20 (TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_24 (TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_28 (TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_32 (TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_EXPAND_ZERO 0 +#define TEGRA30_AUDIOCIF_EXPAND_ONE 1 +#define TEGRA30_AUDIOCIF_EXPAND_LFSR 2 + +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT 6 +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_MASK (3 << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ZERO (TEGRA30_AUDIOCIF_EXPAND_ZERO << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ONE (TEGRA30_AUDIOCIF_EXPAND_ONE << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_LFSR (TEGRA30_AUDIOCIF_EXPAND_LFSR << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) + +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH0 0 +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH1 1 +#define TEGRA30_AUDIOCIF_STEREO_CONV_AVG 2 + +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT 4 +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_MASK (3 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH0 (TEGRA30_AUDIOCIF_STEREO_CONV_CH0 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH1 (TEGRA30_AUDIOCIF_STEREO_CONV_CH1 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_AVG (TEGRA30_AUDIOCIF_STEREO_CONV_AVG << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT 3 + +#define TEGRA30_AUDIOCIF_DIRECTION_TX 0 +#define TEGRA30_AUDIOCIF_DIRECTION_RX 1 + +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT 2 +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_MASK (1 << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX (TEGRA30_AUDIOCIF_DIRECTION_TX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX (TEGRA30_AUDIOCIF_DIRECTION_RX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) + +#define TEGRA30_AUDIOCIF_TRUNCATE_ROUND 0 +#define TEGRA30_AUDIOCIF_TRUNCATE_CHOP 1 + +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT 1 +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_MASK (1 << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_ROUND (TEGRA30_AUDIOCIF_TRUNCATE_ROUND << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_CHOP (TEGRA30_AUDIOCIF_TRUNCATE_CHOP << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) + +#define TEGRA30_AUDIOCIF_MONO_CONV_ZERO 0 +#define TEGRA30_AUDIOCIF_MONO_CONV_COPY 1 + +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT 0 +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_MASK (1 << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_ZERO (TEGRA30_AUDIOCIF_MONO_CONV_ZERO << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_COPY (TEGRA30_AUDIOCIF_MONO_CONV_COPY << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) + +/* Registers within TEGRA30_AUDIO_CLUSTER_BASE */ + +/* TEGRA30_AHUB_CHANNEL_CTRL */ + +#define TEGRA30_AHUB_CHANNEL_CTRL 0x0 +#define TEGRA30_AHUB_CHANNEL_CTRL_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_CTRL_COUNT 4 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_EN (1 << 31) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_EN (1 << 30) +#define TEGRA30_AHUB_CHANNEL_CTRL_LOOPBACK (1 << 29) + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT 16 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT 8 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN (1 << 6) + +#define TEGRA30_PACK_8_4 2 +#define TEGRA30_PACK_16 3 + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT 4 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US 3 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN (1 << 2) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT 0 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US 3 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK (TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_8_4 (TEGRA30_PACK_8_4 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16 (TEGRA30_PACK_16 << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) + +/* TEGRA30_AHUB_CHANNEL_CLEAR */ + +#define TEGRA30_AHUB_CHANNEL_CLEAR 0x4 +#define TEGRA30_AHUB_CHANNEL_CLEAR_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_CLEAR_COUNT 4 +#define TEGRA30_AHUB_CHANNEL_CLEAR_TX_SOFT_RESET (1 << 31) +#define TEGRA30_AHUB_CHANNEL_CLEAR_RX_SOFT_RESET (1 << 30) + +/* TEGRA30_AHUB_CHANNEL_STATUS */ + +#define TEGRA30_AHUB_CHANNEL_STATUS 0x8 +#define TEGRA30_AHUB_CHANNEL_STATUS_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_STATUS_COUNT 4 +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT 24 +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT) +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT 16 +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US 0xff +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK (TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT) +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_TRIG (1 << 1) +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_TRIG (1 << 0) + +/* TEGRA30_AHUB_CHANNEL_TXFIFO */ + +#define TEGRA30_AHUB_CHANNEL_TXFIFO 0xc +#define TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_TXFIFO_COUNT 4 + +/* TEGRA30_AHUB_CHANNEL_RXFIFO */ + +#define TEGRA30_AHUB_CHANNEL_RXFIFO 0x10 +#define TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE 0x20 +#define TEGRA30_AHUB_CHANNEL_RXFIFO_COUNT 4 + +/* TEGRA30_AHUB_CIF_TX_CTRL */ + +#define TEGRA30_AHUB_CIF_TX_CTRL 0x14 +#define TEGRA30_AHUB_CIF_TX_CTRL_STRIDE 0x20 +#define TEGRA30_AHUB_CIF_TX_CTRL_COUNT 4 +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */ + +/* TEGRA30_AHUB_CIF_RX_CTRL */ + +#define TEGRA30_AHUB_CIF_RX_CTRL 0x18 +#define TEGRA30_AHUB_CIF_RX_CTRL_STRIDE 0x20 +#define TEGRA30_AHUB_CIF_RX_CTRL_COUNT 4 +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */ + +/* TEGRA30_AHUB_CONFIG_LINK_CTRL */ + +#define TEGRA30_AHUB_CONFIG_LINK_CTRL 0x80 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT 28 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US 0xf +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT 16 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US 0xfff +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT 4 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US 0xfff +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK (TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CG_EN (1 << 2) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CLEAR_TIMEOUT_CNTR (1 << 1) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_SOFT_RESET (1 << 0) + +/* TEGRA30_AHUB_MISC_CTRL */ + +#define TEGRA30_AHUB_MISC_CTRL 0x84 +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_ACTIVE (1 << 31) +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_CG_EN (1 << 8) +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT 0 +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_MASK (0x1f << TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT) + +/* TEGRA30_AHUB_APBDMA_LIVE_STATUS */ + +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS 0x88 +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_FULL (1 << 31) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_FULL (1 << 30) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_FULL (1 << 29) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_FULL (1 << 28) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_FULL (1 << 27) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_FULL (1 << 26) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_FULL (1 << 25) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL (1 << 24) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_EMPTY (1 << 23) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_EMPTY (1 << 22) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_EMPTY (1 << 21) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_EMPTY (1 << 20) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_EMPTY (1 << 19) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_EMPTY (1 << 18) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_EMPTY (1 << 17) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_EMPTY (1 << 16) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_FULL (1 << 15) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_FULL (1 << 14) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_FULL (1 << 13) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_FULL (1 << 12) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_FULL (1 << 11) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_FULL (1 << 10) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_FULL (1 << 9) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_FULL (1 << 8) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_EMPTY (1 << 7) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_EMPTY (1 << 6) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_EMPTY (1 << 5) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_EMPTY (1 << 4) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_EMPTY (1 << 3) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_EMPTY (1 << 2) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_I2S_LIVE_STATUS */ + +#define TEGRA30_AHUB_I2S_LIVE_STATUS 0x8c +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_FULL (1 << 29) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_FULL (1 << 28) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_FULL (1 << 27) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_FULL (1 << 26) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_FULL (1 << 25) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_FULL (1 << 24) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_FULL (1 << 23) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_FULL (1 << 22) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_FULL (1 << 21) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_FULL (1 << 20) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_ENABLED (1 << 19) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_ENABLED (1 << 18) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_ENABLED (1 << 17) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_ENABLED (1 << 16) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_ENABLED (1 << 15) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_ENABLED (1 << 14) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_ENABLED (1 << 13) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_ENABLED (1 << 12) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_ENABLED (1 << 11) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_ENABLED (1 << 10) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_EMPTY (1 << 9) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_EMPTY (1 << 8) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_EMPTY (1 << 7) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_EMPTY (1 << 6) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_EMPTY (1 << 5) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_EMPTY (1 << 4) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_EMPTY (1 << 3) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_EMPTY (1 << 2) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_DAM0_LIVE_STATUS */ + +#define TEGRA30_AHUB_DAM_LIVE_STATUS 0x90 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_STRIDE 0x8 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_COUNT 3 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TX_ENABLED (1 << 26) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1_ENABLED (1 << 25) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0_ENABLED (1 << 24) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_FULL (1 << 15) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_FULL (1 << 9) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_FULL (1 << 8) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_EMPTY (1 << 7) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_SPDIF_LIVE_STATUS */ + +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS 0xa8 +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TX_ENABLED (1 << 11) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RX_ENABLED (1 << 10) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TX_ENABLED (1 << 9) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RX_ENABLED (1 << 8) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_FULL (1 << 7) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_FULL (1 << 6) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_FULL (1 << 5) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_FULL (1 << 4) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_EMPTY (1 << 3) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_EMPTY (1 << 2) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_EMPTY (1 << 1) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_EMPTY (1 << 0) + +/* TEGRA30_AHUB_I2S_INT_MASK */ + +#define TEGRA30_AHUB_I2S_INT_MASK 0xb0 + +/* TEGRA30_AHUB_DAM_INT_MASK */ + +#define TEGRA30_AHUB_DAM_INT_MASK 0xb4 + +/* TEGRA30_AHUB_SPDIF_INT_MASK */ + +#define TEGRA30_AHUB_SPDIF_INT_MASK 0xbc + +/* TEGRA30_AHUB_APBIF_INT_MASK */ + +#define TEGRA30_AHUB_APBIF_INT_MASK 0xc0 + +/* TEGRA30_AHUB_I2S_INT_STATUS */ + +#define TEGRA30_AHUB_I2S_INT_STATUS 0xc8 + +/* TEGRA30_AHUB_DAM_INT_STATUS */ + +#define TEGRA30_AHUB_DAM_INT_STATUS 0xcc + +/* TEGRA30_AHUB_SPDIF_INT_STATUS */ + +#define TEGRA30_AHUB_SPDIF_INT_STATUS 0xd4 + +/* TEGRA30_AHUB_APBIF_INT_STATUS */ + +#define TEGRA30_AHUB_APBIF_INT_STATUS 0xd8 + +/* TEGRA30_AHUB_I2S_INT_SOURCE */ + +#define TEGRA30_AHUB_I2S_INT_SOURCE 0xe0 + +/* TEGRA30_AHUB_DAM_INT_SOURCE */ + +#define TEGRA30_AHUB_DAM_INT_SOURCE 0xe4 + +/* TEGRA30_AHUB_SPDIF_INT_SOURCE */ + +#define TEGRA30_AHUB_SPDIF_INT_SOURCE 0xec + +/* TEGRA30_AHUB_APBIF_INT_SOURCE */ + +#define TEGRA30_AHUB_APBIF_INT_SOURCE 0xf0 + +/* TEGRA30_AHUB_I2S_INT_SET */ + +#define TEGRA30_AHUB_I2S_INT_SET 0xf8 + +/* TEGRA30_AHUB_DAM_INT_SET */ + +#define TEGRA30_AHUB_DAM_INT_SET 0xfc + +/* TEGRA30_AHUB_SPDIF_INT_SET */ + +#define TEGRA30_AHUB_SPDIF_INT_SET 0x100 + +/* TEGRA30_AHUB_APBIF_INT_SET */ + +#define TEGRA30_AHUB_APBIF_INT_SET 0x104 + +/* Registers within TEGRA30_AHUB_BASE */ + +#define TEGRA30_AHUB_AUDIO_RX 0x0 +#define TEGRA30_AHUB_AUDIO_RX_STRIDE 0x4 +#define TEGRA30_AHUB_AUDIO_RX_COUNT 17 +/* This register repeats once for each entry in enum tegra30_ahub_rxcif */ +/* The fields in this register are 1 bit per entry in tegra30_ahub_txcif */ + +/* + * Terminology: + * AHUB: Audio Hub; a cross-bar switch between the audio devices: DMA FIFOs, + * I2S controllers, SPDIF controllers, and DAMs. + * XBAR: The core cross-bar component of the AHUB. + * CIF: Client Interface; the HW module connecting an audio device to the + * XBAR. + * DAM: Digital Audio Mixer: A HW module that mixes multiple audio streams, + * possibly including sample-rate conversion. + * + * Each TX CIF transmits data into the XBAR. Each RX CIF can receive audio + * transmitted by a particular TX CIF. + * + * This driver is currently very simplistic; many HW features are not + * exposed; DAMs are not supported, only 16-bit stereo audio is supported, + * etc. + */ + +enum tegra30_ahub_txcif { + TEGRA30_AHUB_TXCIF_APBIF_TX0, + TEGRA30_AHUB_TXCIF_APBIF_TX1, + TEGRA30_AHUB_TXCIF_APBIF_TX2, + TEGRA30_AHUB_TXCIF_APBIF_TX3, + TEGRA30_AHUB_TXCIF_I2S0_TX0, + TEGRA30_AHUB_TXCIF_I2S1_TX0, + TEGRA30_AHUB_TXCIF_I2S2_TX0, + TEGRA30_AHUB_TXCIF_I2S3_TX0, + TEGRA30_AHUB_TXCIF_I2S4_TX0, + TEGRA30_AHUB_TXCIF_DAM0_TX0, + TEGRA30_AHUB_TXCIF_DAM1_TX0, + TEGRA30_AHUB_TXCIF_DAM2_TX0, + TEGRA30_AHUB_TXCIF_SPDIF_TX0, + TEGRA30_AHUB_TXCIF_SPDIF_TX1, +}; + +enum tegra30_ahub_rxcif { + TEGRA30_AHUB_RXCIF_APBIF_RX0, + TEGRA30_AHUB_RXCIF_APBIF_RX1, + TEGRA30_AHUB_RXcIF_APBIF_RX2, + TEGRA30_AHUB_RXCIF_APBIF_RX3, + TEGRA30_AHUB_RXCIF_I2S0_RX0, + TEGRA30_AHUB_RXCIF_I2S1_RX0, + TEGRA30_AHUB_RXCIF_I2S2_RX0, + TEGRA30_AHUB_RXCIF_I2S3_RX0, + TEGRA30_AHUB_RXCIF_I2S4_RX0, + TEGRA30_AHUB_RXCIF_DAM0_RX0, + TEGRA30_AHUB_RXCIF_DAM0_RX1, + TEGRA30_AHUB_RXCIF_DAM1_RX0, + TEGRA30_AHUB_RXCIF_DAM2_RX1, + TEGRA30_AHUB_RXCIF_DAM3_RX0, + TEGRA30_AHUB_RXCIF_DAM3_RX1, + TEGRA30_AHUB_RXCIF_SPDIF_RX0, + TEGRA30_AHUB_RXCIF_SPDIF_RX1, +}; + +extern int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg); +extern int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif); +extern int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif); +extern int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif); + +extern int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif, + char *dmachan, int dmachan_len, + dma_addr_t *fiforeg); +extern int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif); + +extern int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif, + enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif); + +struct tegra30_ahub_cif_conf { + unsigned int threshold; + unsigned int audio_channels; + unsigned int client_channels; + unsigned int audio_bits; + unsigned int client_bits; + unsigned int expand; + unsigned int stereo_conv; + unsigned int replicate; + unsigned int direction; + unsigned int truncate; + unsigned int mono_conv; +}; + +void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf); +void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg, + struct tegra30_ahub_cif_conf *conf); + +struct tegra30_ahub_soc_data { + u32 mod_list_mask; + void (*set_audio_cif)(struct regmap *regmap, + unsigned int reg, + struct tegra30_ahub_cif_conf *conf); + /* + * FIXME: There are many more differences in HW, such as: + * - More APBIF channels. + * - Extra separate chunks of register address space to represent + * the extra APBIF channels. + * - More units connected to the AHUB, so that tegra30_ahub_[rt]xcif + * need expansion, coupled with there being more defined bits in + * the AHUB routing registers. + * However, the driver doesn't support those new features yet, so we + * don't represent them here yet. + */ +}; + +struct tegra30_ahub { + const struct tegra30_ahub_soc_data *soc_data; + struct device *dev; + struct clk *clk_d_audio; + struct clk *clk_apbif; + resource_size_t apbif_addr; + struct regmap *regmap_apbif; + struct regmap *regmap_ahub; + DECLARE_BITMAP(rx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT); + DECLARE_BITMAP(tx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT); +}; + +#endif diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c new file mode 100644 index 000000000..bf155c509 --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.c @@ -0,0 +1,586 @@ +/* + * tegra30_i2s.c - Tegra30 I2S driver + * + * Author: Stephen Warren + * Copyright (c) 2010-2012, NVIDIA CORPORATION. All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tegra30_ahub.h" +#include "tegra30_i2s.h" + +#define DRV_NAME "tegra30-i2s" + +static int tegra30_i2s_runtime_suspend(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + clk_disable_unprepare(i2s->clk_i2s); + + return 0; +} + +static int tegra30_i2s_runtime_resume(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->clk_i2s); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); + return ret; + } + + regcache_cache_only(i2s->regmap, false); + + return 0; +} + +static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask = 0, val = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + mask |= TEGRA30_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val |= TEGRA30_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + mask |= TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK | + TEGRA30_I2S_CTRL_LRCK_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; + val |= TEGRA30_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; + val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dai->dev); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); + pm_runtime_put(dai->dev); + + return 0; +} + +static int tegra30_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = dai->dev; + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val, reg; + int ret, sample_size, srate, i2sclock, bitcnt; + struct tegra30_ahub_cif_conf cif_conf; + + if (params_channels(params) != 2) + return -EINVAL; + + mask = TEGRA30_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val = TEGRA30_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); + + srate = params_rate(params); + + /* Final "* 2" required by Tegra hardware */ + i2sclock = srate * params_channels(params) * sample_size * 2; + + bitcnt = (i2sclock / (2 * srate)) - 1; + if (bitcnt < 0 || bitcnt > TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) + return -EINVAL; + + ret = clk_set_rate(i2s->clk_i2s, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + val = bitcnt << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + val |= TEGRA30_I2S_TIMING_NON_SYM_ENABLE; + + regmap_write(i2s->regmap, TEGRA30_I2S_TIMING, val); + + cif_conf.threshold = 0; + cif_conf.audio_channels = 2; + cif_conf.client_channels = 2; + cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; + cif_conf.expand = 0; + cif_conf.stereo_conv = 0; + cif_conf.replicate = 0; + cif_conf.truncate = 0; + cif_conf.mono_conv = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; + reg = TEGRA30_I2S_CIF_RX_CTRL; + } else { + cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; + reg = TEGRA30_I2S_CIF_TX_CTRL; + } + + i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf); + + val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) | + (1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT); + regmap_write(i2s->regmap, TEGRA30_I2S_OFFSET, val); + + return 0; +} + +static void tegra30_i2s_start_playback(struct tegra30_i2s *i2s) +{ + tegra30_ahub_enable_tx_fifo(i2s->playback_fifo_cif); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_TX, + TEGRA30_I2S_CTRL_XFER_EN_TX); +} + +static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s) +{ + tegra30_ahub_disable_tx_fifo(i2s->playback_fifo_cif); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_TX, 0); +} + +static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s) +{ + tegra30_ahub_enable_rx_fifo(i2s->capture_fifo_cif); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_RX, + TEGRA30_I2S_CTRL_XFER_EN_RX); +} + +static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s) +{ + tegra30_ahub_disable_rx_fifo(i2s->capture_fifo_cif); + regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, + TEGRA30_I2S_CTRL_XFER_EN_RX, 0); +} + +static int tegra30_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra30_i2s_start_playback(i2s); + else + tegra30_i2s_start_capture(i2s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra30_i2s_stop_playback(i2s); + else + tegra30_i2s_stop_capture(i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra30_i2s_probe(struct snd_soc_dai *dai) +{ + struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static const struct snd_soc_dai_ops tegra30_i2s_dai_ops = { + .set_fmt = tegra30_i2s_set_fmt, + .hw_params = tegra30_i2s_hw_params, + .trigger = tegra30_i2s_trigger, +}; + +static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { + .probe = tegra30_i2s_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra30_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver tegra30_i2s_component = { + .name = DRV_NAME, +}; + +static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_I2S_CTRL: + case TEGRA30_I2S_TIMING: + case TEGRA30_I2S_OFFSET: + case TEGRA30_I2S_CH_CTRL: + case TEGRA30_I2S_SLOT_CTRL: + case TEGRA30_I2S_CIF_RX_CTRL: + case TEGRA30_I2S_CIF_TX_CTRL: + case TEGRA30_I2S_FLOWCTL: + case TEGRA30_I2S_TX_STEP: + case TEGRA30_I2S_FLOW_STATUS: + case TEGRA30_I2S_FLOW_TOTAL: + case TEGRA30_I2S_FLOW_OVER: + case TEGRA30_I2S_FLOW_UNDER: + case TEGRA30_I2S_LCOEF_1_4_0: + case TEGRA30_I2S_LCOEF_1_4_1: + case TEGRA30_I2S_LCOEF_1_4_2: + case TEGRA30_I2S_LCOEF_1_4_3: + case TEGRA30_I2S_LCOEF_1_4_4: + case TEGRA30_I2S_LCOEF_1_4_5: + case TEGRA30_I2S_LCOEF_2_4_0: + case TEGRA30_I2S_LCOEF_2_4_1: + case TEGRA30_I2S_LCOEF_2_4_2: + return true; + default: + return false; + } +} + +static bool tegra30_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TEGRA30_I2S_FLOW_STATUS: + case TEGRA30_I2S_FLOW_TOTAL: + case TEGRA30_I2S_FLOW_OVER: + case TEGRA30_I2S_FLOW_UNDER: + return true; + default: + return false; + } +} + +static const struct regmap_config tegra30_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = TEGRA30_I2S_LCOEF_2_4_2, + .writeable_reg = tegra30_i2s_wr_rd_reg, + .readable_reg = tegra30_i2s_wr_rd_reg, + .volatile_reg = tegra30_i2s_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static const struct tegra30_i2s_soc_data tegra30_i2s_config = { + .set_audio_cif = tegra30_ahub_set_cif, +}; + +static const struct tegra30_i2s_soc_data tegra124_i2s_config = { + .set_audio_cif = tegra124_ahub_set_cif, +}; + +static const struct of_device_id tegra30_i2s_of_match[] = { + { .compatible = "nvidia,tegra124-i2s", .data = &tegra124_i2s_config }, + { .compatible = "nvidia,tegra30-i2s", .data = &tegra30_i2s_config }, + {}, +}; + +static int tegra30_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra30_i2s *i2s; + const struct of_device_id *match; + u32 cif_ids[2]; + struct resource *mem; + void __iomem *regs; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL); + if (!i2s) { + ret = -ENOMEM; + goto err; + } + dev_set_drvdata(&pdev->dev, i2s); + + match = of_match_device(tegra30_i2s_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Error: No device match found\n"); + ret = -ENODEV; + goto err; + } + i2s->soc_data = (struct tegra30_i2s_soc_data *)match->data; + + i2s->dai = tegra30_i2s_dai_template; + i2s->dai.name = dev_name(&pdev->dev); + + ret = of_property_read_u32_array(pdev->dev.of_node, + "nvidia,ahub-cif-ids", cif_ids, + ARRAY_SIZE(cif_ids)); + if (ret < 0) + goto err; + + i2s->playback_i2s_cif = cif_ids[0]; + i2s->capture_i2s_cif = cif_ids[1]; + + i2s->clk_i2s = clk_get(&pdev->dev, NULL); + if (IS_ERR(i2s->clk_i2s)) { + dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk_i2s); + goto err; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err_clk_put; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra30_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + ret = PTR_ERR(i2s->regmap); + goto err_clk_put; + } + regcache_cache_only(i2s->regmap, true); + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = tegra30_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->playback_dma_data.maxburst = 4; + ret = tegra30_ahub_allocate_tx_fifo(&i2s->playback_fifo_cif, + i2s->playback_dma_chan, + sizeof(i2s->playback_dma_chan), + &i2s->playback_dma_data.addr); + if (ret) { + dev_err(&pdev->dev, "Could not alloc TX FIFO: %d\n", ret); + goto err_suspend; + } + ret = tegra30_ahub_set_rx_cif_source(i2s->playback_i2s_cif, + i2s->playback_fifo_cif); + if (ret) { + dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret); + goto err_free_tx_fifo; + } + + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s->capture_dma_data.maxburst = 4; + ret = tegra30_ahub_allocate_rx_fifo(&i2s->capture_fifo_cif, + i2s->capture_dma_chan, + sizeof(i2s->capture_dma_chan), + &i2s->capture_dma_data.addr); + if (ret) { + dev_err(&pdev->dev, "Could not alloc RX FIFO: %d\n", ret); + goto err_unroute_tx_fifo; + } + ret = tegra30_ahub_set_rx_cif_source(i2s->capture_fifo_cif, + i2s->capture_i2s_cif); + if (ret) { + dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret); + goto err_free_rx_fifo; + } + + ret = snd_soc_register_component(&pdev->dev, &tegra30_i2s_component, + &i2s->dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_unroute_rx_fifo; + } + + ret = tegra_pcm_platform_register_with_chan_names(&pdev->dev, + &i2s->dma_config, i2s->playback_dma_chan, + i2s->capture_dma_chan); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); + goto err_unregister_component; + } + + return 0; + +err_unregister_component: + snd_soc_unregister_component(&pdev->dev); +err_unroute_rx_fifo: + tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif); +err_free_rx_fifo: + tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif); +err_unroute_tx_fifo: + tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); +err_free_tx_fifo: + tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err_clk_put: + clk_put(i2s->clk_i2s); +err: + return ret; +} + +static int tegra30_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra30_i2s_runtime_suspend(&pdev->dev); + + tegra_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif); + tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif); + + tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); + tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); + + clk_put(i2s->clk_i2s); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra30_i2s_suspend(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + + regcache_mark_dirty(i2s->regmap); + + return 0; +} + +static int tegra30_i2s_resume(struct device *dev) +{ + struct tegra30_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put(dev); + return ret; + } + ret = regcache_sync(i2s->regmap); + pm_runtime_put(dev); + + return ret; +} +#endif + +static const struct dev_pm_ops tegra30_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend, + tegra30_i2s_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra30_i2s_suspend, tegra30_i2s_resume) +}; + +static struct platform_driver tegra30_i2s_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = tegra30_i2s_of_match, + .pm = &tegra30_i2s_pm_ops, + }, + .probe = tegra30_i2s_platform_probe, + .remove = tegra30_i2s_platform_remove, +}; +module_platform_driver(tegra30_i2s_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra30 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra30_i2s_of_match); diff --git a/sound/soc/tegra/tegra30_i2s.h b/sound/soc/tegra/tegra30_i2s.h new file mode 100644 index 000000000..2e561e946 --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.h @@ -0,0 +1,251 @@ +/* + * tegra30_i2s.h - Definitions for Tegra30 I2S driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#ifndef __TEGRA30_I2S_H__ +#define __TEGRA30_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA30_I2S*_BASE */ + +#define TEGRA30_I2S_CTRL 0x0 +#define TEGRA30_I2S_TIMING 0x4 +#define TEGRA30_I2S_OFFSET 0x08 +#define TEGRA30_I2S_CH_CTRL 0x0c +#define TEGRA30_I2S_SLOT_CTRL 0x10 +#define TEGRA30_I2S_CIF_RX_CTRL 0x14 +#define TEGRA30_I2S_CIF_TX_CTRL 0x18 +#define TEGRA30_I2S_FLOWCTL 0x1c +#define TEGRA30_I2S_TX_STEP 0x20 +#define TEGRA30_I2S_FLOW_STATUS 0x24 +#define TEGRA30_I2S_FLOW_TOTAL 0x28 +#define TEGRA30_I2S_FLOW_OVER 0x2c +#define TEGRA30_I2S_FLOW_UNDER 0x30 +#define TEGRA30_I2S_LCOEF_1_4_0 0x34 +#define TEGRA30_I2S_LCOEF_1_4_1 0x38 +#define TEGRA30_I2S_LCOEF_1_4_2 0x3c +#define TEGRA30_I2S_LCOEF_1_4_3 0x40 +#define TEGRA30_I2S_LCOEF_1_4_4 0x44 +#define TEGRA30_I2S_LCOEF_1_4_5 0x48 +#define TEGRA30_I2S_LCOEF_2_4_0 0x4c +#define TEGRA30_I2S_LCOEF_2_4_1 0x50 +#define TEGRA30_I2S_LCOEF_2_4_2 0x54 + +/* Fields in TEGRA30_I2S_CTRL */ + +#define TEGRA30_I2S_CTRL_XFER_EN_TX (1 << 31) +#define TEGRA30_I2S_CTRL_XFER_EN_RX (1 << 30) +#define TEGRA30_I2S_CTRL_CG_EN (1 << 29) +#define TEGRA30_I2S_CTRL_SOFT_RESET (1 << 28) +#define TEGRA30_I2S_CTRL_TX_FLOWCTL_EN (1 << 27) + +#define TEGRA30_I2S_CTRL_OBS_SEL_SHIFT 24 +#define TEGRA30_I2S_CTRL_OBS_SEL_MASK (7 << TEGRA30_I2S_CTRL_OBS_SEL_SHIFT) + +#define TEGRA30_I2S_FRAME_FORMAT_LRCK 0 +#define TEGRA30_I2S_FRAME_FORMAT_FSYNC 1 + +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT 12 +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK (7 << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK (TEGRA30_I2S_FRAME_FORMAT_LRCK << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC (TEGRA30_I2S_FRAME_FORMAT_FSYNC << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) + +#define TEGRA30_I2S_CTRL_MASTER_ENABLE (1 << 10) + +#define TEGRA30_I2S_LRCK_LEFT_LOW 0 +#define TEGRA30_I2S_LRCK_RIGHT_LOW 1 + +#define TEGRA30_I2S_CTRL_LRCK_SHIFT 9 +#define TEGRA30_I2S_CTRL_LRCK_MASK (1 << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_L_LOW (TEGRA30_I2S_LRCK_LEFT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_R_LOW (TEGRA30_I2S_LRCK_RIGHT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA30_I2S_CTRL_LPBK_ENABLE (1 << 8) + +#define TEGRA30_I2S_BIT_CODE_LINEAR 0 +#define TEGRA30_I2S_BIT_CODE_ULAW 1 +#define TEGRA30_I2S_BIT_CODE_ALAW 2 + +#define TEGRA30_I2S_CTRL_BIT_CODE_SHIFT 4 +#define TEGRA30_I2S_CTRL_BIT_CODE_MASK (3 << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_LINEAR (TEGRA30_I2S_BIT_CODE_LINEAR << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ULAW (TEGRA30_I2S_BIT_CODE_ULAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ALAW (TEGRA30_I2S_BIT_CODE_ALAW << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) + +#define TEGRA30_I2S_BITS_8 1 +#define TEGRA30_I2S_BITS_12 2 +#define TEGRA30_I2S_BITS_16 3 +#define TEGRA30_I2S_BITS_20 4 +#define TEGRA30_I2S_BITS_24 5 +#define TEGRA30_I2S_BITS_28 6 +#define TEGRA30_I2S_BITS_32 7 + +/* Sample container size; see {RX,TX}_MASK field in CH_CTRL below */ +#define TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT 0 +#define TEGRA30_I2S_CTRL_BIT_SIZE_MASK (7 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_8 (TEGRA30_I2S_BITS_8 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_12 (TEGRA30_I2S_BITS_12 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_16 (TEGRA30_I2S_BITS_16 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_20 (TEGRA30_I2S_BITS_20 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_24 (TEGRA30_I2S_BITS_24 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_28 (TEGRA30_I2S_BITS_28 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_32 (TEGRA30_I2S_BITS_32 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) + +/* Fields in TEGRA30_I2S_TIMING */ + +#define TEGRA30_I2S_TIMING_NON_SYM_ENABLE (1 << 12) +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7ff +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA30_I2S_OFFSET */ + +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT 16 +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US 0x7ff +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT 0 +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US 0x7ff +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK (TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT) + +/* Fields in TEGRA30_I2S_CH_CTRL */ + +/* (FSYNC width - 1) in bit clocks */ +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT 24 +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US 0xff +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK (TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US << TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT) + +#define TEGRA30_I2S_HIGHZ_NO 0 +#define TEGRA30_I2S_HIGHZ_YES 1 +#define TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK 2 + +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT 12 +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_MASK (3 << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_NO (TEGRA30_I2S_HIGHZ_NO << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_YES (TEGRA30_I2S_HIGHZ_YES << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_ON_HALF_BIT_CLK (TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) + +#define TEGRA30_I2S_MSB_FIRST 0 +#define TEGRA30_I2S_LSB_FIRST 1 + +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT 10 +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT 9 +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MASK (1 << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MSB_FIRST (TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_LSB_FIRST (TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) + +#define TEGRA30_I2S_POS_EDGE 0 +#define TEGRA30_I2S_NEG_EDGE 1 + +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT 8 +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_MASK (1 << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_POS_EDGE (TEGRA30_I2S_POS_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE (TEGRA30_I2S_NEG_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) + +/* Sample size is # bits from BIT_SIZE minus this field */ +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT 4 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US 7 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT) + +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT 0 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US 7 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK (TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT) + +/* Fields in TEGRA30_I2S_SLOT_CTRL */ + +/* Number of slots in frame, minus 1 */ +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT 16 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US 7 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK (TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT) + +/* TDM mode slot enable bitmask */ +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT 8 +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) + +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT 0 +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK (0xff << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) + +/* Fields in TEGRA30_I2S_CIF_RX_CTRL */ +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */ + +/* Fields in TEGRA30_I2S_CIF_TX_CTRL */ +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */ + +/* Fields in TEGRA30_I2S_FLOWCTL */ + +#define TEGRA30_I2S_FILTER_LINEAR 0 +#define TEGRA30_I2S_FILTER_QUAD 1 + +#define TEGRA30_I2S_FLOWCTL_FILTER_SHIFT 31 +#define TEGRA30_I2S_FLOWCTL_FILTER_MASK (1 << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_LINEAR (TEGRA30_I2S_FILTER_LINEAR << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_QUAD (TEGRA30_I2S_FILTER_QUAD << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) + +/* Fields in TEGRA30_I2S_TX_STEP */ + +#define TEGRA30_I2S_TX_STEP_SHIFT 0 +#define TEGRA30_I2S_TX_STEP_MASK_US 0xffff +#define TEGRA30_I2S_TX_STEP_MASK (TEGRA30_I2S_TX_STEP_MASK_US << TEGRA30_I2S_TX_STEP_SHIFT) + +/* Fields in TEGRA30_I2S_FLOW_STATUS */ + +#define TEGRA30_I2S_FLOW_STATUS_UNDERFLOW (1 << 31) +#define TEGRA30_I2S_FLOW_STATUS_OVERFLOW (1 << 30) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_INT_EN (1 << 4) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_CLR (1 << 3) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_CLR (1 << 2) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_EN (1 << 1) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_EN (1 << 0) + +/* + * There are no fields in TEGRA30_I2S_FLOW_TOTAL, TEGRA30_I2S_FLOW_OVER, + * TEGRA30_I2S_FLOW_UNDER; they are counters taking the whole register. + */ + +/* Fields in TEGRA30_I2S_LCOEF_* */ + +#define TEGRA30_I2S_LCOEF_COEF_SHIFT 0 +#define TEGRA30_I2S_LCOEF_COEF_MASK_US 0xffff +#define TEGRA30_I2S_LCOEF_COEF_MASK (TEGRA30_I2S_LCOEF_COEF_MASK_US << TEGRA30_I2S_LCOEF_COEF_SHIFT) + +struct tegra30_i2s_soc_data { + void (*set_audio_cif)(struct regmap *regmap, + unsigned int reg, + struct tegra30_ahub_cif_conf *conf); +}; + +struct tegra30_i2s { + const struct tegra30_i2s_soc_data *soc_data; + struct snd_soc_dai_driver dai; + int cif_id; + struct clk *clk_i2s; + enum tegra30_ahub_txcif capture_i2s_cif; + enum tegra30_ahub_rxcif capture_fifo_cif; + char capture_dma_chan[8]; + struct snd_dmaengine_dai_dma_data capture_dma_data; + enum tegra30_ahub_rxcif playback_i2s_cif; + enum tegra30_ahub_txcif playback_fifo_cif; + char playback_dma_chan[8]; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct regmap *regmap; + struct snd_dmaengine_pcm_config dma_config; +}; + +#endif diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c new file mode 100644 index 000000000..6fea74173 --- /dev/null +++ b/sound/soc/tegra/tegra_alc5632.c @@ -0,0 +1,263 @@ +/* +* tegra_alc5632.c -- Toshiba AC100(PAZ00) machine ASoC driver + * + * Copyright (C) 2011 The AC100 Kernel Team + * Copyright (C) 2012 - NVIDIA, Inc. + * + * Authors: Leon Romanovsky + * Andrey Danin + * Marc Dietrich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/alc5632.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-alc5632" + +struct tegra_alc5632 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; +}; + +static int tegra_alc5632_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_alc5632 *alc5632 = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + mclk = 512 * srate; + + err = tegra_asoc_utils_set_rate(&alc5632->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_alc5632_asoc_ops = { + .hw_params = tegra_alc5632_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_alc5632_hs_jack; + +static struct snd_soc_jack_pin tegra_alc5632_hs_jack_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headset Stereophone", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_alc5632_hp_jack_gpio = { + .name = "Headset detection", + .report = SND_JACK_HEADSET, + .debounce_time = 150, +}; + +static const struct snd_soc_dapm_widget tegra_alc5632_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Int Spk", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic", NULL), +}; + +static const struct snd_kcontrol_new tegra_alc5632_controls[] = { + SOC_DAPM_PIN_SWITCH("Int Spk"), +}; + +static int tegra_alc5632_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(rtd->card); + + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET, + &tegra_alc5632_hs_jack, + tegra_alc5632_hs_jack_pins, + ARRAY_SIZE(tegra_alc5632_hs_jack_pins)); + if (ret) + return ret; + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_alc5632_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_jack_add_gpios(&tegra_alc5632_hs_jack, + 1, + &tegra_alc5632_hp_jack_gpio); + } + + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "MICBIAS1"); + + return 0; +} + +static struct snd_soc_dai_link tegra_alc5632_dai = { + .name = "ALC5632", + .stream_name = "ALC5632 PCM", + .codec_dai_name = "alc5632-hifi", + .init = tegra_alc5632_asoc_init, + .ops = &tegra_alc5632_asoc_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_alc5632 = { + .name = "tegra-alc5632", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_alc5632_dai, + .num_links = 1, + .controls = tegra_alc5632_controls, + .num_controls = ARRAY_SIZE(tegra_alc5632_controls), + .dapm_widgets = tegra_alc5632_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_alc5632_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_alc5632_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_alc5632; + struct tegra_alc5632 *alc5632; + int ret; + + alc5632 = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_alc5632), GFP_KERNEL); + if (!alc5632) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, alc5632); + + alc5632->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (alc5632->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_alc5632_dai.codec_of_node = of_parse_phandle( + pdev->dev.of_node, "nvidia,audio-codec", 0); + + if (!tegra_alc5632_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_alc5632_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_alc5632_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err_put_codec_of_node; + } + + tegra_alc5632_dai.platform_of_node = tegra_alc5632_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&alc5632->util_data, &pdev->dev); + if (ret) + goto err_put_cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&alc5632->util_data); +err_put_cpu_of_node: + of_node_put(tegra_alc5632_dai.cpu_of_node); + tegra_alc5632_dai.cpu_of_node = NULL; + tegra_alc5632_dai.platform_of_node = NULL; +err_put_codec_of_node: + of_node_put(tegra_alc5632_dai.codec_of_node); + tegra_alc5632_dai.codec_of_node = NULL; +err: + return ret; +} + +static int tegra_alc5632_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + of_node_put(tegra_alc5632_dai.cpu_of_node); + tegra_alc5632_dai.cpu_of_node = NULL; + tegra_alc5632_dai.platform_of_node = NULL; + of_node_put(tegra_alc5632_dai.codec_of_node); + tegra_alc5632_dai.codec_of_node = NULL; + + return 0; +} + +static const struct of_device_id tegra_alc5632_of_match[] = { + { .compatible = "nvidia,tegra-audio-alc5632", }, + {}, +}; + +static struct platform_driver tegra_alc5632_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_alc5632_of_match, + }, + .probe = tegra_alc5632_probe, + .remove = tegra_alc5632_remove, +}; +module_platform_driver(tegra_alc5632_driver); + +MODULE_AUTHOR("Leon Romanovsky "); +MODULE_DESCRIPTION("Tegra+ALC5632 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_alc5632_of_match); diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c new file mode 100644 index 000000000..1be311c51 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.c @@ -0,0 +1,240 @@ +/* + * tegra_asoc_utils.c - Harmony machine ASoC driver + * + * Author: Stephen Warren + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "tegra_asoc_utils.h" + +int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, + int mclk) +{ + int new_baseclock; + bool clk_change; + int err; + + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 56448000; + else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) + new_baseclock = 564480000; + else + new_baseclock = 282240000; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) + new_baseclock = 73728000; + else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) + new_baseclock = 552960000; + else + new_baseclock = 368640000; + break; + default: + return -EINVAL; + } + + clk_change = ((new_baseclock != data->set_baseclock) || + (mclk != data->set_mclk)); + if (!clk_change) + return 0; + + data->set_baseclock = 0; + data->set_mclk = 0; + + clk_disable_unprepare(data->clk_cdev1); + clk_disable_unprepare(data->clk_pll_a_out0); + clk_disable_unprepare(data->clk_pll_a); + + err = clk_set_rate(data->clk_pll_a, new_baseclock); + if (err) { + dev_err(data->dev, "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(data->clk_pll_a_out0, mclk); + if (err) { + dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err); + return err; + } + + /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */ + + err = clk_prepare_enable(data->clk_pll_a); + if (err) { + dev_err(data->dev, "Can't enable pll_a: %d\n", err); + return err; + } + + err = clk_prepare_enable(data->clk_pll_a_out0); + if (err) { + dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err); + return err; + } + + err = clk_prepare_enable(data->clk_cdev1); + if (err) { + dev_err(data->dev, "Can't enable cdev1: %d\n", err); + return err; + } + + data->set_baseclock = new_baseclock; + data->set_mclk = mclk; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate); + +int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data) +{ + const int pll_rate = 73728000; + const int ac97_rate = 24576000; + int err; + + clk_disable_unprepare(data->clk_cdev1); + clk_disable_unprepare(data->clk_pll_a_out0); + clk_disable_unprepare(data->clk_pll_a); + + /* + * AC97 rate is fixed at 24.576MHz and is used for both the host + * controller and the external codec + */ + err = clk_set_rate(data->clk_pll_a, pll_rate); + if (err) { + dev_err(data->dev, "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(data->clk_pll_a_out0, ac97_rate); + if (err) { + dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err); + return err; + } + + /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */ + + err = clk_prepare_enable(data->clk_pll_a); + if (err) { + dev_err(data->dev, "Can't enable pll_a: %d\n", err); + return err; + } + + err = clk_prepare_enable(data->clk_pll_a_out0); + if (err) { + dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err); + return err; + } + + err = clk_prepare_enable(data->clk_cdev1); + if (err) { + dev_err(data->dev, "Can't enable cdev1: %d\n", err); + return err; + } + + data->set_baseclock = pll_rate; + data->set_mclk = ac97_rate; + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_ac97_rate); + +int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, + struct device *dev) +{ + int ret; + + data->dev = dev; + + if (of_machine_is_compatible("nvidia,tegra20")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20; + else if (of_machine_is_compatible("nvidia,tegra30")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA30; + else if (of_machine_is_compatible("nvidia,tegra114")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA114; + else if (of_machine_is_compatible("nvidia,tegra124")) + data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA124; + else { + dev_err(data->dev, "SoC unknown to Tegra ASoC utils\n"); + return -EINVAL; + } + + data->clk_pll_a = clk_get(dev, "pll_a"); + if (IS_ERR(data->clk_pll_a)) { + dev_err(data->dev, "Can't retrieve clk pll_a\n"); + ret = PTR_ERR(data->clk_pll_a); + goto err; + } + + data->clk_pll_a_out0 = clk_get(dev, "pll_a_out0"); + if (IS_ERR(data->clk_pll_a_out0)) { + dev_err(data->dev, "Can't retrieve clk pll_a_out0\n"); + ret = PTR_ERR(data->clk_pll_a_out0); + goto err_put_pll_a; + } + + data->clk_cdev1 = clk_get(dev, "mclk"); + if (IS_ERR(data->clk_cdev1)) { + dev_err(data->dev, "Can't retrieve clk cdev1\n"); + ret = PTR_ERR(data->clk_cdev1); + goto err_put_pll_a_out0; + } + + ret = tegra_asoc_utils_set_rate(data, 44100, 256 * 44100); + if (ret) + goto err_put_cdev1; + + return 0; + +err_put_cdev1: + clk_put(data->clk_cdev1); +err_put_pll_a_out0: + clk_put(data->clk_pll_a_out0); +err_put_pll_a: + clk_put(data->clk_pll_a); +err: + return ret; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_init); + +void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data) +{ + clk_put(data->clk_cdev1); + clk_put(data->clk_pll_a_out0); + clk_put(data->clk_pll_a); +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_fini); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra ASoC utility code"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h new file mode 100644 index 000000000..ca8037634 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.h @@ -0,0 +1,53 @@ +/* + * tegra_asoc_utils.h - Definitions for Tegra DAS driver + * + * Author: Stephen Warren + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_ASOC_UTILS_H__ +#define __TEGRA_ASOC_UTILS_H__ + +struct clk; +struct device; + +enum tegra_asoc_utils_soc { + TEGRA_ASOC_UTILS_SOC_TEGRA20, + TEGRA_ASOC_UTILS_SOC_TEGRA30, + TEGRA_ASOC_UTILS_SOC_TEGRA114, + TEGRA_ASOC_UTILS_SOC_TEGRA124, +}; + +struct tegra_asoc_utils_data { + struct device *dev; + enum tegra_asoc_utils_soc soc; + struct clk *clk_pll_a; + struct clk *clk_pll_a_out0; + struct clk *clk_cdev1; + int set_baseclock; + int set_mclk; +}; + +int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, + int mclk); +int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data); +int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data, + struct device *dev); +void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data); + +#endif diff --git a/sound/soc/tegra/tegra_max98090.c b/sound/soc/tegra/tegra_max98090.c new file mode 100644 index 000000000..10998d703 --- /dev/null +++ b/sound/soc/tegra/tegra_max98090.c @@ -0,0 +1,305 @@ +/* + * Tegra machine ASoC driver for boards using a MAX90809 CODEC. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-max98090" + +struct tegra_max98090 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; + int gpio_mic_det; +}; + +static int tegra_max98090_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_max98090 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12000000; + break; + } + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_max98090_ops = { + .hw_params = tegra_max98090_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_max98090_hp_jack; + +static struct snd_soc_jack_pin tegra_max98090_hp_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_max98090_hp_jack_gpio = { + .name = "Headphone detection", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static struct snd_soc_jack tegra_max98090_mic_jack; + +static struct snd_soc_jack_pin tegra_max98090_mic_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_max98090_mic_jack_gpio = { + .name = "Mic detection", + .report = SND_JACK_MICROPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static const struct snd_soc_dapm_widget tegra_max98090_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static const struct snd_kcontrol_new tegra_max98090_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static int tegra_max98090_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_max98090 *machine = snd_soc_card_get_drvdata(rtd->card); + + if (gpio_is_valid(machine->gpio_hp_det)) { + snd_soc_card_jack_new(rtd->card, "Headphones", + SND_JACK_HEADPHONE, + &tegra_max98090_hp_jack, + tegra_max98090_hp_jack_pins, + ARRAY_SIZE(tegra_max98090_hp_jack_pins)); + + tegra_max98090_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_jack_add_gpios(&tegra_max98090_hp_jack, + 1, + &tegra_max98090_hp_jack_gpio); + } + + if (gpio_is_valid(machine->gpio_mic_det)) { + snd_soc_card_jack_new(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, + &tegra_max98090_mic_jack, + tegra_max98090_mic_jack_pins, + ARRAY_SIZE(tegra_max98090_mic_jack_pins)); + + tegra_max98090_mic_jack_gpio.gpio = machine->gpio_mic_det; + snd_soc_jack_add_gpios(&tegra_max98090_mic_jack, + 1, + &tegra_max98090_mic_jack_gpio); + } + + return 0; +} + +static struct snd_soc_dai_link tegra_max98090_dai = { + .name = "max98090", + .stream_name = "max98090 PCM", + .codec_dai_name = "HiFi", + .init = tegra_max98090_asoc_init, + .ops = &tegra_max98090_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_max98090 = { + .name = "tegra-max98090", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_max98090_dai, + .num_links = 1, + .controls = tegra_max98090_controls, + .num_controls = ARRAY_SIZE(tegra_max98090_controls), + .dapm_widgets = tegra_max98090_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_max98090_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_max98090_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_max98090; + struct tegra_max98090 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_max98090), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_mic_det = + of_get_named_gpio(np, "nvidia,mic-det-gpios", 0); + if (machine->gpio_mic_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_max98090_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_max98090_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_max98090_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_max98090_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_max98090_dai.platform_of_node = tegra_max98090_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err: + return ret; +} + +static int tegra_max98090_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_max98090 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + return 0; +} + +static const struct of_device_id tegra_max98090_of_match[] = { + { .compatible = "nvidia,tegra-audio-max98090", }, + {}, +}; + +static struct platform_driver tegra_max98090_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_max98090_of_match, + }, + .probe = tegra_max98090_probe, + .remove = tegra_max98090_remove, +}; +module_platform_driver(tegra_max98090_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra max98090 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_max98090_of_match); diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 000000000..93caed500 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c @@ -0,0 +1,86 @@ +/* + * tegra_pcm.c - Tegra PCM driver + * + * Author: Stephen Warren + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson + * Vijay Mali + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "tegra_pcm.h" + +static const struct snd_pcm_hardware tegra_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .period_bytes_min = 1024, + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, + .buffer_bytes_max = PAGE_SIZE * 8, + .fifo_size = 4, +}; + +static const struct snd_dmaengine_pcm_config tegra_dmaengine_pcm_config = { + .pcm_hardware = &tegra_pcm_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prealloc_buffer_size = PAGE_SIZE * 8, +}; + +int tegra_pcm_platform_register(struct device *dev) +{ + return snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_register); + +int tegra_pcm_platform_register_with_chan_names(struct device *dev, + struct snd_dmaengine_pcm_config *config, + char *txdmachan, char *rxdmachan) +{ + *config = tegra_dmaengine_pcm_config; + config->dma_dev = dev->parent; + config->chan_names[0] = txdmachan; + config->chan_names[1] = rxdmachan; + + return snd_dmaengine_pcm_register(dev, config, 0); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_register_with_chan_names); + +void tegra_pcm_platform_unregister(struct device *dev) +{ + return snd_dmaengine_pcm_unregister(dev); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra PCM ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h new file mode 100644 index 000000000..7883dec74 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.h @@ -0,0 +1,42 @@ +/* + * tegra_pcm.h - Definitions for Tegra PCM driver + * + * Author: Stephen Warren + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TEGRA_PCM_H__ +#define __TEGRA_PCM_H__ + +struct snd_dmaengine_pcm_config; + +int tegra_pcm_platform_register(struct device *dev); +int tegra_pcm_platform_register_with_chan_names(struct device *dev, + struct snd_dmaengine_pcm_config *config, + char *txdmachan, char *rxdmachan); +void tegra_pcm_platform_unregister(struct device *dev); + +#endif diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c new file mode 100644 index 000000000..e0cbe85b6 --- /dev/null +++ b/sound/soc/tegra/tegra_rt5640.c @@ -0,0 +1,251 @@ +/* +* tegra_rt5640.c - Tegra machine ASoC driver for boards using RT5640 codec. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/rt5640.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-rt5640" + +struct tegra_rt5640 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; + enum of_gpio_flags gpio_hp_det_flags; +}; + +static int tegra_rt5640_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + mclk = 256 * srate; + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_rt5640_ops = { + .hw_params = tegra_rt5640_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_rt5640_hp_jack; + +static struct snd_soc_jack_pin tegra_rt5640_hp_jack_pins[] = { + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_rt5640_hp_jack_gpio = { + .name = "Headphone detection", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static const struct snd_soc_dapm_widget tegra_rt5640_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_SPK("Speakers", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_kcontrol_new tegra_rt5640_controls[] = { + SOC_DAPM_PIN_SWITCH("Speakers"), +}; + +static int tegra_rt5640_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(rtd->card); + + snd_soc_card_jack_new(rtd->card, "Headphones", SND_JACK_HEADPHONE, + &tegra_rt5640_hp_jack, tegra_rt5640_hp_jack_pins, + ARRAY_SIZE(tegra_rt5640_hp_jack_pins)); + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_rt5640_hp_jack_gpio.gpio = machine->gpio_hp_det; + tegra_rt5640_hp_jack_gpio.invert = + !!(machine->gpio_hp_det_flags & OF_GPIO_ACTIVE_LOW); + snd_soc_jack_add_gpios(&tegra_rt5640_hp_jack, + 1, + &tegra_rt5640_hp_jack_gpio); + } + + return 0; +} + +static struct snd_soc_dai_link tegra_rt5640_dai = { + .name = "RT5640", + .stream_name = "RT5640 PCM", + .codec_dai_name = "rt5640-aif1", + .init = tegra_rt5640_asoc_init, + .ops = &tegra_rt5640_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_rt5640 = { + .name = "tegra-rt5640", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_rt5640_dai, + .num_links = 1, + .controls = tegra_rt5640_controls, + .num_controls = ARRAY_SIZE(tegra_rt5640_controls), + .dapm_widgets = tegra_rt5640_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_rt5640_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_rt5640_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_rt5640; + struct tegra_rt5640 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_rt5640), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_hp_det = of_get_named_gpio_flags( + np, "nvidia,hp-det-gpios", 0, &machine->gpio_hp_det_flags); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_rt5640_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_rt5640_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_rt5640_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_rt5640_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_rt5640_dai.platform_of_node = tegra_rt5640_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err: + return ret; +} + +static int tegra_rt5640_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + return 0; +} + +static const struct of_device_id tegra_rt5640_of_match[] = { + { .compatible = "nvidia,tegra-audio-rt5640", }, + {}, +}; + +static struct platform_driver tegra_rt5640_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_rt5640_of_match, + }, + .probe = tegra_rt5640_probe, + .remove = tegra_rt5640_remove, +}; +module_platform_driver(tegra_rt5640_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra+RT5640 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_rt5640_of_match); diff --git a/sound/soc/tegra/tegra_rt5677.c b/sound/soc/tegra/tegra_rt5677.c new file mode 100644 index 000000000..e285793d2 --- /dev/null +++ b/sound/soc/tegra/tegra_rt5677.c @@ -0,0 +1,336 @@ +/* +* tegra_rt5677.c - Tegra machine ASoC driver for boards using RT5677 codec. + * + * Copyright (c) 2014, The Chromium OS Authors. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/rt5677.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-rt5677" + +struct tegra_rt5677 { + struct tegra_asoc_utils_data util_data; + int gpio_hp_det; + int gpio_hp_en; + int gpio_mic_present; + int gpio_dmic_clk_en; +}; + +static int tegra_rt5677_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk, err; + + srate = params_rate(params); + mclk = 256 * srate; + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_MCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static int tegra_rt5677_event_hp(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(card); + + if (!gpio_is_valid(machine->gpio_hp_en)) + return 0; + + gpio_set_value_cansleep(machine->gpio_hp_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_ops tegra_rt5677_ops = { + .hw_params = tegra_rt5677_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_rt5677_hp_jack; + +static struct snd_soc_jack_pin tegra_rt5677_hp_jack_pins = { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, +}; +static struct snd_soc_jack_gpio tegra_rt5677_hp_jack_gpio = { + .name = "Headphone detection", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, +}; + +static struct snd_soc_jack tegra_rt5677_mic_jack; + +static struct snd_soc_jack_pin tegra_rt5677_mic_jack_pins = { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, +}; + +static struct snd_soc_jack_gpio tegra_rt5677_mic_jack_gpio = { + .name = "Headset Mic detection", + .report = SND_JACK_MICROPHONE, + .debounce_time = 150, + .invert = 1 +}; + +static const struct snd_soc_dapm_widget tegra_rt5677_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_HP("Headphone", tegra_rt5677_event_hp), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Internal Mic 1", NULL), + SND_SOC_DAPM_MIC("Internal Mic 2", NULL), +}; + +static const struct snd_kcontrol_new tegra_rt5677_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic 1"), + SOC_DAPM_PIN_SWITCH("Internal Mic 2"), +}; + +static int tegra_rt5677_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(rtd->card); + + snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE, + &tegra_rt5677_hp_jack, + &tegra_rt5677_hp_jack_pins, 1); + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_rt5677_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_jack_add_gpios(&tegra_rt5677_hp_jack, 1, + &tegra_rt5677_hp_jack_gpio); + } + + + snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE, + &tegra_rt5677_mic_jack, + &tegra_rt5677_mic_jack_pins, 1); + + if (gpio_is_valid(machine->gpio_mic_present)) { + tegra_rt5677_mic_jack_gpio.gpio = machine->gpio_mic_present; + snd_soc_jack_add_gpios(&tegra_rt5677_mic_jack, 1, + &tegra_rt5677_mic_jack_gpio); + } + + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "MICBIAS1"); + + return 0; +} + +static struct snd_soc_dai_link tegra_rt5677_dai = { + .name = "RT5677", + .stream_name = "RT5677 PCM", + .codec_dai_name = "rt5677-aif1", + .init = tegra_rt5677_asoc_init, + .ops = &tegra_rt5677_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_rt5677 = { + .name = "tegra-rt5677", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_rt5677_dai, + .num_links = 1, + .controls = tegra_rt5677_controls, + .num_controls = ARRAY_SIZE(tegra_rt5677_controls), + .dapm_widgets = tegra_rt5677_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_rt5677_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_rt5677_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_rt5677; + struct tegra_rt5677 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_rt5677), GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_mic_present = of_get_named_gpio(np, + "nvidia,mic-present-gpios", 0); + if (machine->gpio_mic_present == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_hp_en = of_get_named_gpio(np, "nvidia,hp-en-gpios", 0); + if (machine->gpio_hp_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_hp_en)) { + ret = devm_gpio_request_one(&pdev->dev, machine->gpio_hp_en, + GPIOF_OUT_INIT_LOW, "hp_en"); + if (ret) { + dev_err(card->dev, "cannot get hp_en gpio\n"); + return ret; + } + } + + machine->gpio_dmic_clk_en = of_get_named_gpio(np, + "nvidia,dmic-clk-en-gpios", 0); + if (machine->gpio_dmic_clk_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_dmic_clk_en)) { + ret = devm_gpio_request_one(&pdev->dev, + machine->gpio_dmic_clk_en, + GPIOF_OUT_INIT_HIGH, "dmic_clk_en"); + if (ret) { + dev_err(card->dev, "cannot get dmic_clk_en gpio\n"); + return ret; + } + } + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_rt5677_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_rt5677_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_rt5677_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_rt5677_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err_put_codec_of_node; + } + tegra_rt5677_dai.platform_of_node = tegra_rt5677_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err_put_cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err_put_cpu_of_node: + of_node_put(tegra_rt5677_dai.cpu_of_node); + tegra_rt5677_dai.cpu_of_node = NULL; + tegra_rt5677_dai.platform_of_node = NULL; +err_put_codec_of_node: + of_node_put(tegra_rt5677_dai.codec_of_node); + tegra_rt5677_dai.codec_of_node = NULL; +err: + return ret; +} + +static int tegra_rt5677_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_rt5677 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + tegra_rt5677_dai.platform_of_node = NULL; + of_node_put(tegra_rt5677_dai.codec_of_node); + tegra_rt5677_dai.codec_of_node = NULL; + of_node_put(tegra_rt5677_dai.cpu_of_node); + tegra_rt5677_dai.cpu_of_node = NULL; + + return 0; +} + +static const struct of_device_id tegra_rt5677_of_match[] = { + { .compatible = "nvidia,tegra-audio-rt5677", }, + {}, +}; + +static struct platform_driver tegra_rt5677_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_rt5677_of_match, + }, + .probe = tegra_rt5677_probe, + .remove = tegra_rt5677_remove, +}; +module_platform_driver(tegra_rt5677_driver); + +MODULE_AUTHOR("Anatol Pomozov "); +MODULE_DESCRIPTION("Tegra+RT5677 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_rt5677_of_match); diff --git a/sound/soc/tegra/tegra_sgtl5000.c b/sound/soc/tegra/tegra_sgtl5000.c new file mode 100644 index 000000000..e6cbc89ea --- /dev/null +++ b/sound/soc/tegra/tegra_sgtl5000.c @@ -0,0 +1,223 @@ +/* + * tegra_sgtl5000.c - Tegra machine ASoC driver for boards using SGTL5000 codec + * + * Author: Marcel Ziswiler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/sgtl5000.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-sgtl5000" + +struct tegra_sgtl5000 { + struct tegra_asoc_utils_data util_data; +}; + +static int tegra_sgtl5000_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_sgtl5000 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12288000; + break; + } + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_sgtl5000_ops = { + .hw_params = tegra_sgtl5000_hw_params, +}; + +static const struct snd_soc_dapm_widget tegra_sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static struct snd_soc_dai_link tegra_sgtl5000_dai = { + .name = "sgtl5000", + .stream_name = "HiFi", + .codec_dai_name = "sgtl5000", + .ops = &tegra_sgtl5000_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_sgtl5000 = { + .name = "tegra-sgtl5000", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_sgtl5000_dai, + .num_links = 1, + .dapm_widgets = tegra_sgtl5000_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_sgtl5000_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_sgtl5000_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_sgtl5000; + struct tegra_sgtl5000 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_sgtl5000), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_sgtl5000_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_sgtl5000_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_sgtl5000_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_sgtl5000_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing/invalid\n"); + ret = -EINVAL; + goto err_put_codec_of_node; + } + + tegra_sgtl5000_dai.platform_of_node = tegra_sgtl5000_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err_put_cpu_of_node; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err_put_cpu_of_node: + of_node_put(tegra_sgtl5000_dai.cpu_of_node); + tegra_sgtl5000_dai.cpu_of_node = NULL; + tegra_sgtl5000_dai.platform_of_node = NULL; +err_put_codec_of_node: + of_node_put(tegra_sgtl5000_dai.codec_of_node); + tegra_sgtl5000_dai.codec_of_node = NULL; +err: + return ret; +} + +static int tegra_sgtl5000_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_sgtl5000 *machine = snd_soc_card_get_drvdata(card); + int ret; + + ret = snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + of_node_put(tegra_sgtl5000_dai.cpu_of_node); + tegra_sgtl5000_dai.cpu_of_node = NULL; + tegra_sgtl5000_dai.platform_of_node = NULL; + of_node_put(tegra_sgtl5000_dai.codec_of_node); + tegra_sgtl5000_dai.codec_of_node = NULL; + + return ret; +} + +static const struct of_device_id tegra_sgtl5000_of_match[] = { + { .compatible = "nvidia,tegra-audio-sgtl5000", }, + { /* sentinel */ }, +}; + +static struct platform_driver tegra_sgtl5000_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_sgtl5000_of_match, + }, + .probe = tegra_sgtl5000_driver_probe, + .remove = tegra_sgtl5000_driver_remove, +}; +module_platform_driver(tegra_sgtl5000_driver); + +MODULE_AUTHOR("Marcel Ziswiler "); +MODULE_DESCRIPTION("Tegra SGTL5000 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_sgtl5000_of_match); diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c new file mode 100644 index 000000000..3fa0e9913 --- /dev/null +++ b/sound/soc/tegra/tegra_wm8753.c @@ -0,0 +1,217 @@ +/* + * tegra_wm8753.c - Tegra machine ASoC driver for boards using WM8753 codec. + * + * Author: Stephen Warren + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/wm8753.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm8753" + +struct tegra_wm8753 { + struct tegra_asoc_utils_data util_data; +}; + +static int tegra_wm8753_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12288000; + break; + } + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_wm8753_ops = { + .hw_params = tegra_wm8753_hw_params, +}; + +static const struct snd_soc_dapm_widget tegra_wm8753_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static struct snd_soc_dai_link tegra_wm8753_dai = { + .name = "WM8753", + .stream_name = "WM8753 PCM", + .codec_dai_name = "wm8753-hifi", + .ops = &tegra_wm8753_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_wm8753 = { + .name = "tegra-wm8753", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8753_dai, + .num_links = 1, + + .dapm_widgets = tegra_wm8753_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm8753_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_wm8753_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_wm8753; + struct tegra_wm8753 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8753), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_wm8753_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_wm8753_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_wm8753_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_wm8753_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_wm8753_dai.platform_of_node = tegra_wm8753_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err: + return ret; +} + +static int tegra_wm8753_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + return 0; +} + +static const struct of_device_id tegra_wm8753_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8753", }, + {}, +}; + +static struct platform_driver tegra_wm8753_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm8753_of_match, + }, + .probe = tegra_wm8753_driver_probe, + .remove = tegra_wm8753_driver_remove, +}; +module_platform_driver(tegra_wm8753_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra+WM8753 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm8753_of_match); diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c new file mode 100644 index 000000000..8edcd8203 --- /dev/null +++ b/sound/soc/tegra/tegra_wm8903.c @@ -0,0 +1,395 @@ +/* + * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec. + * + * Author: Stephen Warren + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/wm8903.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm8903" + +struct tegra_wm8903 { + int gpio_spkr_en; + int gpio_hp_det; + int gpio_hp_mute; + int gpio_int_mic_en; + int gpio_ext_mic_en; + struct tegra_asoc_utils_data util_data; +}; + +static int tegra_wm8903_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 64000: + case 88200: + case 96000: + mclk = 128 * srate; + break; + default: + mclk = 256 * srate; + break; + } + /* FIXME: Codec only requires >= 3MHz if OSR==0 */ + while (mclk < 6000000) + mclk *= 2; + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops tegra_wm8903_ops = { + .hw_params = tegra_wm8903_hw_params, +}; + +static struct snd_soc_jack tegra_wm8903_hp_jack; + +static struct snd_soc_jack_pin tegra_wm8903_hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio tegra_wm8903_hp_jack_gpio = { + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, + .invert = 1, +}; + +static struct snd_soc_jack tegra_wm8903_mic_jack; + +static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int tegra_wm8903_event_int_spk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + + if (!gpio_is_valid(machine->gpio_spkr_en)) + return 0; + + gpio_set_value_cansleep(machine->gpio_spkr_en, + SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static int tegra_wm8903_event_hp(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + + if (!gpio_is_valid(machine->gpio_hp_mute)) + return 0; + + gpio_set_value_cansleep(machine->gpio_hp_mute, + !SND_SOC_DAPM_EVENT_ON(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget tegra_wm8903_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Int Spk", tegra_wm8903_event_int_spk), + SND_SOC_DAPM_HP("Headphone Jack", tegra_wm8903_event_hp), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_kcontrol_new tegra_wm8903_controls[] = { + SOC_DAPM_PIN_SWITCH("Int Spk"), +}; + +static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_card *card = rtd->card; + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + int shrt = 0; + + if (gpio_is_valid(machine->gpio_hp_det)) { + tegra_wm8903_hp_jack_gpio.gpio = machine->gpio_hp_det; + snd_soc_card_jack_new(rtd->card, "Headphone Jack", + SND_JACK_HEADPHONE, &tegra_wm8903_hp_jack, + tegra_wm8903_hp_jack_pins, + ARRAY_SIZE(tegra_wm8903_hp_jack_pins)); + snd_soc_jack_add_gpios(&tegra_wm8903_hp_jack, + 1, + &tegra_wm8903_hp_jack_gpio); + } + + if (of_property_read_bool(card->dev->of_node, "nvidia,headset")) + shrt = SND_JACK_MICROPHONE; + + snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE, + &tegra_wm8903_mic_jack, + tegra_wm8903_mic_jack_pins, + ARRAY_SIZE(tegra_wm8903_mic_jack_pins)); + wm8903_mic_detect(component, &tegra_wm8903_mic_jack, SND_JACK_MICROPHONE, + shrt); + + snd_soc_dapm_force_enable_pin(&card->dapm, "MICBIAS"); + + return 0; +} + +static int tegra_wm8903_remove(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = + snd_soc_get_pcm_runtime(card, card->dai_link[0].name); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_component *component = codec_dai->component; + + wm8903_mic_detect(component, NULL, 0, 0); + + return 0; +} + +static struct snd_soc_dai_link tegra_wm8903_dai = { + .name = "WM8903", + .stream_name = "WM8903 PCM", + .codec_dai_name = "wm8903-hifi", + .init = tegra_wm8903_init, + .ops = &tegra_wm8903_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_wm8903 = { + .name = "tegra-wm8903", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8903_dai, + .num_links = 1, + .remove = tegra_wm8903_remove, + .controls = tegra_wm8903_controls, + .num_controls = ARRAY_SIZE(tegra_wm8903_controls), + .dapm_widgets = tegra_wm8903_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm8903_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_wm8903_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_wm8903; + struct tegra_wm8903 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8903), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->gpio_spkr_en = of_get_named_gpio(np, "nvidia,spkr-en-gpios", + 0); + if (machine->gpio_spkr_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_spkr_en)) { + ret = devm_gpio_request_one(&pdev->dev, machine->gpio_spkr_en, + GPIOF_OUT_INIT_LOW, "spkr_en"); + if (ret) { + dev_err(card->dev, "cannot get spkr_en gpio\n"); + return ret; + } + } + + machine->gpio_hp_mute = of_get_named_gpio(np, "nvidia,hp-mute-gpios", + 0); + if (machine->gpio_hp_mute == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_hp_mute)) { + ret = devm_gpio_request_one(&pdev->dev, machine->gpio_hp_mute, + GPIOF_OUT_INIT_HIGH, "hp_mute"); + if (ret) { + dev_err(card->dev, "cannot get hp_mute gpio\n"); + return ret; + } + } + + machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); + if (machine->gpio_hp_det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + machine->gpio_int_mic_en = of_get_named_gpio(np, + "nvidia,int-mic-en-gpios", 0); + if (machine->gpio_int_mic_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_int_mic_en)) { + /* Disable int mic; enable signal is active-high */ + ret = devm_gpio_request_one(&pdev->dev, + machine->gpio_int_mic_en, + GPIOF_OUT_INIT_LOW, "int_mic_en"); + if (ret) { + dev_err(card->dev, "cannot get int_mic_en gpio\n"); + return ret; + } + } + + machine->gpio_ext_mic_en = of_get_named_gpio(np, + "nvidia,ext-mic-en-gpios", 0); + if (machine->gpio_ext_mic_en == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (gpio_is_valid(machine->gpio_ext_mic_en)) { + /* Enable ext mic; enable signal is active-low */ + ret = devm_gpio_request_one(&pdev->dev, + machine->gpio_ext_mic_en, + GPIOF_OUT_INIT_LOW, "ext_mic_en"); + if (ret) { + dev_err(card->dev, "cannot get ext_mic_en gpio\n"); + return ret; + } + } + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_wm8903_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_wm8903_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_wm8903_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_wm8903_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_wm8903_dai.platform_of_node = tegra_wm8903_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err: + return ret; +} + +static int tegra_wm8903_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + return 0; +} + +static const struct of_device_id tegra_wm8903_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8903", }, + {}, +}; + +static struct platform_driver tegra_wm8903_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm8903_of_match, + }, + .probe = tegra_wm8903_driver_probe, + .remove = tegra_wm8903_driver_remove, +}; +module_platform_driver(tegra_wm8903_driver); + +MODULE_AUTHOR("Stephen Warren "); +MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm8903_of_match); diff --git a/sound/soc/tegra/tegra_wm9712.c b/sound/soc/tegra/tegra_wm9712.c new file mode 100644 index 000000000..7175e6eea --- /dev/null +++ b/sound/soc/tegra/tegra_wm9712.c @@ -0,0 +1,176 @@ +/* + * tegra20_wm9712.c - Tegra machine ASoC driver for boards using WM9712 codec. + * + * Copyright 2012 Lucas Stach + * + * Partly based on code copyright/by: + * Copyright 2011,2012 Toradex Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm9712" + +struct tegra_wm9712 { + struct platform_device *codec; + struct tegra_asoc_utils_data util_data; +}; + +static const struct snd_soc_dapm_widget tegra_wm9712_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), +}; + +static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd) +{ + return snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); +} + +static struct snd_soc_dai_link tegra_wm9712_dai = { + .name = "AC97 HiFi", + .stream_name = "AC97 HiFi", + .codec_dai_name = "wm9712-hifi", + .codec_name = "wm9712-codec", + .init = tegra_wm9712_init, +}; + +static struct snd_soc_card snd_soc_tegra_wm9712 = { + .name = "tegra-wm9712", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &tegra_wm9712_dai, + .num_links = 1, + + .dapm_widgets = tegra_wm9712_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_wm9712_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_wm9712_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_wm9712; + struct tegra_wm9712 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm9712), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, machine); + + machine->codec = platform_device_alloc("wm9712-codec", -1); + if (!machine->codec) { + dev_err(&pdev->dev, "Can't allocate wm9712 platform device\n"); + return -ENOMEM; + } + + ret = platform_device_add(machine->codec); + if (ret) + goto codec_put; + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto codec_unregister; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto codec_unregister; + + tegra_wm9712_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,ac97-controller", 0); + if (!tegra_wm9712_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,ac97-controller' missing or invalid\n"); + ret = -EINVAL; + goto codec_unregister; + } + + tegra_wm9712_dai.platform_of_node = tegra_wm9712_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto codec_unregister; + + ret = tegra_asoc_utils_set_ac97_rate(&machine->util_data); + if (ret) + goto asoc_utils_fini; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto asoc_utils_fini; + } + + return 0; + +asoc_utils_fini: + tegra_asoc_utils_fini(&machine->util_data); +codec_unregister: + platform_device_del(machine->codec); +codec_put: + platform_device_put(machine->codec); + return ret; +} + +static int tegra_wm9712_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_wm9712 *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + platform_device_unregister(machine->codec); + + return 0; +} + +static const struct of_device_id tegra_wm9712_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm9712", }, + {}, +}; + +static struct platform_driver tegra_wm9712_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_wm9712_of_match, + }, + .probe = tegra_wm9712_driver_probe, + .remove = tegra_wm9712_driver_remove, +}; +module_platform_driver(tegra_wm9712_driver); + +MODULE_AUTHOR("Lucas Stach"); +MODULE_DESCRIPTION("Tegra+WM9712 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm9712_of_match); diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c new file mode 100644 index 000000000..47ef6d6f4 --- /dev/null +++ b/sound/soc/tegra/trimslice.c @@ -0,0 +1,204 @@ +/* + * trimslice.c - TrimSlice machine ASoC driver + * + * Copyright (C) 2011 - CompuLab, Ltd. + * Author: Mike Rapoport + * + * Based on code copyright/by: + * Author: Stephen Warren + * Copyright (C) 2010-2011 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/tlv320aic23.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-trimslice" + +struct tegra_trimslice { + struct tegra_asoc_utils_data util_data; +}; + +static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + mclk = 128 * srate; + + err = tegra_asoc_utils_set_rate(&trimslice->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static const struct snd_soc_ops trimslice_asoc_ops = { + .hw_params = trimslice_asoc_hw_params, +}; + +static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { + SND_SOC_DAPM_HP("Line Out", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route trimslice_audio_map[] = { + {"Line Out", NULL, "LOUT"}, + {"Line Out", NULL, "ROUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, +}; + +static struct snd_soc_dai_link trimslice_tlv320aic23_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .codec_dai_name = "tlv320aic23-hifi", + .ops = &trimslice_asoc_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_trimslice = { + .name = "tegra-trimslice", + .driver_name = "tegra", + .owner = THIS_MODULE, + .dai_link = &trimslice_tlv320aic23_dai, + .num_links = 1, + + .dapm_widgets = trimslice_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), + .dapm_routes = trimslice_audio_map, + .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), + .fully_routed = true, +}; + +static int tegra_snd_trimslice_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_trimslice; + struct tegra_trimslice *trimslice; + int ret; + + trimslice = devm_kzalloc(&pdev->dev, sizeof(struct tegra_trimslice), + GFP_KERNEL); + if (!trimslice) + return -ENOMEM; + + card->dev = &pdev->dev; + snd_soc_card_set_drvdata(card, trimslice); + + trimslice_tlv320aic23_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!trimslice_tlv320aic23_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + trimslice_tlv320aic23_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!trimslice_tlv320aic23_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + trimslice_tlv320aic23_dai.platform_of_node = + trimslice_tlv320aic23_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev); + if (ret) + goto err; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&trimslice->util_data); +err: + return ret; +} + +static int tegra_snd_trimslice_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&trimslice->util_data); + + return 0; +} + +static const struct of_device_id trimslice_of_match[] = { + { .compatible = "nvidia,tegra-audio-trimslice", }, + {}, +}; +MODULE_DEVICE_TABLE(of, trimslice_of_match); + +static struct platform_driver tegra_snd_trimslice_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = trimslice_of_match, + }, + .probe = tegra_snd_trimslice_probe, + .remove = tegra_snd_trimslice_remove, +}; +module_platform_driver(tegra_snd_trimslice_driver); + +MODULE_AUTHOR("Mike Rapoport "); +MODULE_DESCRIPTION("Trimslice machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/txx9/Kconfig b/sound/soc/txx9/Kconfig new file mode 100644 index 000000000..ebc9327ea --- /dev/null +++ b/sound/soc/txx9/Kconfig @@ -0,0 +1,29 @@ +## +## TXx9 ACLC +## +config SND_SOC_TXX9ACLC + tristate "SoC Audio for TXx9" + depends on HAS_TXX9_ACLC && TXX9_DMAC + help + This option enables support for the AC Link Controllers in TXx9 SoC. + +config HAS_TXX9_ACLC + bool + +config SND_SOC_TXX9ACLC_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + + +## +## Boards +## +config SND_SOC_TXX9ACLC_GENERIC + tristate "Generic TXx9 ACLC sound machine" + depends on SND_SOC_TXX9ACLC + select SND_SOC_TXX9ACLC_AC97 + select SND_SOC_AC97_CODEC + help + This is a generic AC97 sound machine for use in TXx9 based systems. diff --git a/sound/soc/txx9/Makefile b/sound/soc/txx9/Makefile new file mode 100644 index 000000000..37ad833eb --- /dev/null +++ b/sound/soc/txx9/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# Platform +snd-soc-txx9aclc-objs := txx9aclc.o +snd-soc-txx9aclc-ac97-objs := txx9aclc-ac97.o + +obj-$(CONFIG_SND_SOC_TXX9ACLC) += snd-soc-txx9aclc.o +obj-$(CONFIG_SND_SOC_TXX9ACLC_AC97) += snd-soc-txx9aclc-ac97.o + +# Machine +snd-soc-txx9aclc-generic-objs := txx9aclc-generic.o + +obj-$(CONFIG_SND_SOC_TXX9ACLC_GENERIC) += snd-soc-txx9aclc-generic.o diff --git a/sound/soc/txx9/txx9aclc-ac97.c b/sound/soc/txx9/txx9aclc-ac97.c new file mode 100644 index 000000000..e2ad00e3c --- /dev/null +++ b/sound/soc/txx9/txx9aclc-ac97.c @@ -0,0 +1,235 @@ +/* + * TXx9 ACLC AC97 driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "txx9aclc.h" + +#define AC97_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_48000 + +#ifdef __BIG_ENDIAN +#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_BE +#else +#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_LE +#endif + +static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq); + +/* REVISIT: How to find txx9aclc_drvdata from snd_ac97? */ +static struct txx9aclc_plat_drvdata *txx9aclc_drvdata; + +static int txx9aclc_regready(struct txx9aclc_plat_drvdata *drvdata) +{ + return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY; +} + +/* AC97 controller reads codec register */ +static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; + void __iomem *base = drvdata->base; + u32 dat; + + if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num))) + return 0xffff; + reg |= ac97->num << 7; + dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ; + __raw_writel(dat, base + ACREGACC); + __raw_writel(ACINT_REGACCRDY, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) { + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); + printk(KERN_ERR "ac97 read timeout (reg %#x)\n", reg); + dat = 0xffff; + goto done; + } + dat = __raw_readl(base + ACREGACC); + if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) { + printk(KERN_ERR "reg mismatch %x with %x\n", + dat, reg); + dat = 0xffff; + goto done; + } + dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff; +done: + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); + return dat; +} + +/* AC97 controller writes to codec register */ +static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; + void __iomem *base = drvdata->base; + + __raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) | + (val << ACREGACC_DAT_SHIFT), + base + ACREGACC); + __raw_writel(ACINT_REGACCRDY, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(txx9aclc_drvdata), HZ)) { + printk(KERN_ERR + "ac97 write timeout (reg %#x)\n", reg); + } + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); +} + +static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; + void __iomem *base = drvdata->base; + u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY; + + __raw_writel(ACCTL_ENLINK, base + ACCTLDIS); + mmiowb(); + udelay(1); + __raw_writel(ACCTL_ENLINK, base + ACCTLEN); + /* wait for primary codec ready status */ + __raw_writel(ready, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, + (__raw_readl(base + ACINTSTS) & ready) == ready, + HZ)) { + dev_err(&ac97->dev, "primary codec is not ready " + "(status %#x)\n", + __raw_readl(base + ACINTSTS)); + } + __raw_writel(ACINT_REGACCRDY, base + ACINTSTS); + __raw_writel(ready, base + ACINTDIS); +} + +/* AC97 controller operations */ +static struct snd_ac97_bus_ops txx9aclc_ac97_ops = { + .read = txx9aclc_ac97_read, + .write = txx9aclc_ac97_write, + .reset = txx9aclc_ac97_cold_reset, +}; + +static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id) +{ + struct txx9aclc_plat_drvdata *drvdata = dev_id; + void __iomem *base = drvdata->base; + + __raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS); + wake_up(&ac97_waitq); + return IRQ_HANDLED; +} + +static int txx9aclc_ac97_probe(struct snd_soc_dai *dai) +{ + txx9aclc_drvdata = snd_soc_dai_get_drvdata(dai); + return 0; +} + +static int txx9aclc_ac97_remove(struct snd_soc_dai *dai) +{ + struct txx9aclc_plat_drvdata *drvdata = snd_soc_dai_get_drvdata(dai); + + /* disable AC-link */ + __raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS); + txx9aclc_drvdata = NULL; + return 0; +} + +static struct snd_soc_dai_driver txx9aclc_ac97_dai = { + .bus_control = true, + .probe = txx9aclc_ac97_probe, + .remove = txx9aclc_ac97_remove, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, +}; + +static const struct snd_soc_component_driver txx9aclc_ac97_component = { + .name = "txx9aclc-ac97", +}; + +static int txx9aclc_ac97_dev_probe(struct platform_device *pdev) +{ + struct txx9aclc_plat_drvdata *drvdata; + struct resource *r; + int err; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drvdata->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(drvdata->base)) + return PTR_ERR(drvdata->base); + + platform_set_drvdata(pdev, drvdata); + drvdata->physbase = r->start; + if (sizeof(drvdata->physbase) > sizeof(r->start) && + r->start >= TXX9_DIRECTMAP_BASE && + r->start < TXX9_DIRECTMAP_BASE + 0x400000) + drvdata->physbase |= 0xf00000000ull; + err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq, + 0, dev_name(&pdev->dev), drvdata); + if (err < 0) + return err; + + err = snd_soc_set_ac97_ops(&txx9aclc_ac97_ops); + if (err < 0) + return err; + + return snd_soc_register_component(&pdev->dev, &txx9aclc_ac97_component, + &txx9aclc_ac97_dai, 1); +} + +static int txx9aclc_ac97_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + snd_soc_set_ac97_ops(NULL); + return 0; +} + +static struct platform_driver txx9aclc_ac97_driver = { + .probe = txx9aclc_ac97_dev_probe, + .remove = txx9aclc_ac97_dev_remove, + .driver = { + .name = "txx9aclc-ac97", + }, +}; + +module_platform_driver(txx9aclc_ac97_driver); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("TXx9 ACLC AC97 driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:txx9aclc-ac97"); diff --git a/sound/soc/txx9/txx9aclc-generic.c b/sound/soc/txx9/txx9aclc-generic.c new file mode 100644 index 000000000..d0b1e7759 --- /dev/null +++ b/sound/soc/txx9/txx9aclc-generic.c @@ -0,0 +1,89 @@ +/* + * Generic TXx9 ACLC machine driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This is a very generic AC97 sound machine driver for boards which + * have (AC97) audio at ACLC (e.g. RBTX49XX boards). + */ + +#include +#include +#include +#include +#include +#include "txx9aclc.h" + +static struct snd_soc_dai_link txx9aclc_generic_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "txx9aclc-ac97", + .codec_dai_name = "ac97-hifi", + .platform_name = "txx9aclc-pcm-audio", + .codec_name = "ac97-codec", +}; + +static struct snd_soc_card txx9aclc_generic_card = { + .name = "Generic TXx9 ACLC Audio", + .owner = THIS_MODULE, + .dai_link = &txx9aclc_generic_dai, + .num_links = 1, +}; + +static struct platform_device *soc_pdev; + +static int __init txx9aclc_generic_probe(struct platform_device *pdev) +{ + int ret; + + soc_pdev = platform_device_alloc("soc-audio", -1); + if (!soc_pdev) + return -ENOMEM; + platform_set_drvdata(soc_pdev, &txx9aclc_generic_card); + ret = platform_device_add(soc_pdev); + if (ret) { + platform_device_put(soc_pdev); + return ret; + } + + return 0; +} + +static int __exit txx9aclc_generic_remove(struct platform_device *pdev) +{ + platform_device_unregister(soc_pdev); + return 0; +} + +static struct platform_driver txx9aclc_generic_driver = { + .remove = __exit_p(txx9aclc_generic_remove), + .driver = { + .name = "txx9aclc-generic", + }, +}; + +static int __init txx9aclc_generic_init(void) +{ + return platform_driver_probe(&txx9aclc_generic_driver, + txx9aclc_generic_probe); +} + +static void __exit txx9aclc_generic_exit(void) +{ + platform_driver_unregister(&txx9aclc_generic_driver); +} + +module_init(txx9aclc_generic_init); +module_exit(txx9aclc_generic_exit); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("Generic TXx9 ACLC ALSA SoC audio driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:txx9aclc-generic"); diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c new file mode 100644 index 000000000..8d31fe628 --- /dev/null +++ b/sound/soc/txx9/txx9aclc.c @@ -0,0 +1,435 @@ +/* + * Generic TXx9 ACLC platform driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "txx9aclc.h" + +#define DRV_NAME "txx9aclc" + +static struct txx9aclc_soc_device { + struct txx9aclc_dmadata dmadata[2]; +} txx9aclc_soc_device; + +/* REVISIT: How to find txx9aclc_drvdata from snd_ac97? */ +static struct txx9aclc_plat_drvdata *txx9aclc_drvdata; + +static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev, + struct txx9aclc_dmadata *dmadata); + +static const struct snd_pcm_hardware txx9aclc_pcm_hardware = { + /* + * REVISIT: SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID + * needs more works for noncoherent MIPS. + */ + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE, + .period_bytes_min = 1024, + .period_bytes_max = 8 * 1024, + .periods_min = 2, + .periods_max = 4096, + .buffer_bytes_max = 32 * 1024, +}; + +static int txx9aclc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct txx9aclc_dmadata *dmadata = runtime->private_data; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + dev_dbg(component->dev, + "runtime->dma_area = %#lx dma_addr = %#lx dma_bytes = %zd " + "runtime->min_align %ld\n", + (unsigned long)runtime->dma_area, + (unsigned long)runtime->dma_addr, runtime->dma_bytes, + runtime->min_align); + dev_dbg(component->dev, + "periods %d period_bytes %d stream %d\n", + params_periods(params), params_period_bytes(params), + substream->stream); + + dmadata->substream = substream; + dmadata->pos = 0; + return 0; +} + +static int txx9aclc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int txx9aclc_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct txx9aclc_dmadata *dmadata = runtime->private_data; + + dmadata->dma_addr = runtime->dma_addr; + dmadata->buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + dmadata->period_bytes = snd_pcm_lib_period_bytes(substream); + + if (dmadata->buffer_bytes == dmadata->period_bytes) { + dmadata->frag_bytes = dmadata->period_bytes >> 1; + dmadata->frags = 2; + } else { + dmadata->frag_bytes = dmadata->period_bytes; + dmadata->frags = dmadata->buffer_bytes / dmadata->period_bytes; + } + dmadata->frag_count = 0; + dmadata->pos = 0; + return 0; +} + +static void txx9aclc_dma_complete(void *arg) +{ + struct txx9aclc_dmadata *dmadata = arg; + unsigned long flags; + + /* dma completion handler cannot submit new operations */ + spin_lock_irqsave(&dmadata->dma_lock, flags); + if (dmadata->frag_count >= 0) { + dmadata->dmacount--; + if (!WARN_ON(dmadata->dmacount < 0)) + tasklet_schedule(&dmadata->tasklet); + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); +} + +static struct dma_async_tx_descriptor * +txx9aclc_dma_submit(struct txx9aclc_dmadata *dmadata, dma_addr_t buf_dma_addr) +{ + struct dma_chan *chan = dmadata->dma_chan; + struct dma_async_tx_descriptor *desc; + struct scatterlist sg; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buf_dma_addr)), + dmadata->frag_bytes, buf_dma_addr & (PAGE_SIZE - 1)); + sg_dma_address(&sg) = buf_dma_addr; + desc = dmaengine_prep_slave_sg(chan, &sg, 1, + dmadata->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(&chan->dev->device, "cannot prepare slave dma\n"); + return NULL; + } + desc->callback = txx9aclc_dma_complete; + desc->callback_param = dmadata; + dmaengine_submit(desc); + return desc; +} + +#define NR_DMA_CHAIN 2 + +static void txx9aclc_dma_tasklet(unsigned long data) +{ + struct txx9aclc_dmadata *dmadata = (struct txx9aclc_dmadata *)data; + struct dma_chan *chan = dmadata->dma_chan; + struct dma_async_tx_descriptor *desc; + struct snd_pcm_substream *substream = dmadata->substream; + u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + ACCTL_AUDODMA : ACCTL_AUDIDMA; + int i; + unsigned long flags; + + spin_lock_irqsave(&dmadata->dma_lock, flags); + if (dmadata->frag_count < 0) { + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; + void __iomem *base = drvdata->base; + + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + dmaengine_terminate_all(chan); + /* first time */ + for (i = 0; i < NR_DMA_CHAIN; i++) { + desc = txx9aclc_dma_submit(dmadata, + dmadata->dma_addr + i * dmadata->frag_bytes); + if (!desc) + return; + } + dmadata->dmacount = NR_DMA_CHAIN; + dma_async_issue_pending(chan); + spin_lock_irqsave(&dmadata->dma_lock, flags); + __raw_writel(ctlbit, base + ACCTLEN); + dmadata->frag_count = NR_DMA_CHAIN % dmadata->frags; + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + return; + } + if (WARN_ON(dmadata->dmacount >= NR_DMA_CHAIN)) { + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + return; + } + while (dmadata->dmacount < NR_DMA_CHAIN) { + dmadata->dmacount++; + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + desc = txx9aclc_dma_submit(dmadata, + dmadata->dma_addr + + dmadata->frag_count * dmadata->frag_bytes); + if (!desc) + return; + dma_async_issue_pending(chan); + + spin_lock_irqsave(&dmadata->dma_lock, flags); + dmadata->frag_count++; + dmadata->frag_count %= dmadata->frags; + dmadata->pos += dmadata->frag_bytes; + dmadata->pos %= dmadata->buffer_bytes; + if ((dmadata->frag_count * dmadata->frag_bytes) % + dmadata->period_bytes == 0) + snd_pcm_period_elapsed(substream); + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); +} + +static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; + void __iomem *base = drvdata->base; + unsigned long flags; + int ret = 0; + u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + ACCTL_AUDODMA : ACCTL_AUDIDMA; + + spin_lock_irqsave(&dmadata->dma_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dmadata->frag_count = -1; + tasklet_schedule(&dmadata->tasklet); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + __raw_writel(ctlbit, base + ACCTLDIS); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + __raw_writel(ctlbit, base + ACCTLEN); + break; + default: + ret = -EINVAL; + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + return ret; +} + +static snd_pcm_uframes_t +txx9aclc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, dmadata->pos); +} + +static int txx9aclc_pcm_open(struct snd_pcm_substream *substream) +{ + struct txx9aclc_soc_device *dev = &txx9aclc_soc_device; + struct txx9aclc_dmadata *dmadata = &dev->dmadata[substream->stream]; + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &txx9aclc_pcm_hardware); + if (ret) + return ret; + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + substream->runtime->private_data = dmadata; + return 0; +} + +static int txx9aclc_pcm_close(struct snd_pcm_substream *substream) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + struct dma_chan *chan = dmadata->dma_chan; + + dmadata->frag_count = -1; + dmaengine_terminate_all(chan); + return 0; +} + +static const struct snd_pcm_ops txx9aclc_pcm_ops = { + .open = txx9aclc_pcm_open, + .close = txx9aclc_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = txx9aclc_pcm_hw_params, + .hw_free = txx9aclc_pcm_hw_free, + .prepare = txx9aclc_pcm_prepare, + .trigger = txx9aclc_pcm_trigger, + .pointer = txx9aclc_pcm_pointer, +}; + +static int txx9aclc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME); + struct platform_device *pdev = to_platform_device(component->dev); + struct txx9aclc_soc_device *dev; + struct resource *r; + int i; + int ret; + + /* at this point onwards the AC97 component has probed and this will be valid */ + dev = snd_soc_dai_get_drvdata(dai); + + dev->dmadata[0].stream = SNDRV_PCM_STREAM_PLAYBACK; + dev->dmadata[1].stream = SNDRV_PCM_STREAM_CAPTURE; + for (i = 0; i < 2; i++) { + r = platform_get_resource(pdev, IORESOURCE_DMA, i); + if (!r) { + ret = -EBUSY; + goto exit; + } + dev->dmadata[i].dma_res = r; + ret = txx9aclc_dma_init(dev, &dev->dmadata[i]); + if (ret) + goto exit; + } + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, 64 * 1024, 4 * 1024 * 1024); + +exit: + for (i = 0; i < 2; i++) { + if (dev->dmadata[i].dma_chan) + dma_release_channel(dev->dmadata[i].dma_chan); + dev->dmadata[i].dma_chan = NULL; + } + return ret; +} + +static bool filter(struct dma_chan *chan, void *param) +{ + struct txx9aclc_dmadata *dmadata = param; + char *devname; + bool found = false; + + devname = kasprintf(GFP_KERNEL, "%s.%d", dmadata->dma_res->name, + (int)dmadata->dma_res->start); + if (strcmp(dev_name(chan->device->dev), devname) == 0) { + chan->private = &dmadata->dma_slave; + found = true; + } + kfree(devname); + return found; +} + +static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev, + struct txx9aclc_dmadata *dmadata) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; + struct txx9dmac_slave *ds = &dmadata->dma_slave; + dma_cap_mask_t mask; + + spin_lock_init(&dmadata->dma_lock); + + ds->reg_width = sizeof(u32); + if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ds->tx_reg = drvdata->physbase + ACAUDODAT; + ds->rx_reg = 0; + } else { + ds->tx_reg = 0; + ds->rx_reg = drvdata->physbase + ACAUDIDAT; + } + + /* Try to grab a DMA channel */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dmadata->dma_chan = dma_request_channel(mask, filter, dmadata); + if (!dmadata->dma_chan) { + printk(KERN_ERR + "DMA channel for %s is not available\n", + dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "playback" : "capture"); + return -EBUSY; + } + tasklet_init(&dmadata->tasklet, txx9aclc_dma_tasklet, + (unsigned long)dmadata); + return 0; +} + +static int txx9aclc_pcm_probe(struct snd_soc_component *component) +{ + snd_soc_component_set_drvdata(component, &txx9aclc_soc_device); + return 0; +} + +static void txx9aclc_pcm_remove(struct snd_soc_component *component) +{ + struct txx9aclc_soc_device *dev = snd_soc_component_get_drvdata(component); + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_drvdata; + void __iomem *base = drvdata->base; + int i; + + /* disable all FIFO DMAs */ + __raw_writel(ACCTL_AUDODMA | ACCTL_AUDIDMA, base + ACCTLDIS); + /* dummy R/W to clear pending DMAREQ if any */ + __raw_writel(__raw_readl(base + ACAUDIDAT), base + ACAUDODAT); + + for (i = 0; i < 2; i++) { + struct txx9aclc_dmadata *dmadata = &dev->dmadata[i]; + struct dma_chan *chan = dmadata->dma_chan; + + if (chan) { + dmadata->frag_count = -1; + dmaengine_terminate_all(chan); + dma_release_channel(chan); + } + dev->dmadata[i].dma_chan = NULL; + } +} + +static const struct snd_soc_component_driver txx9aclc_soc_component = { + .name = DRV_NAME, + .probe = txx9aclc_pcm_probe, + .remove = txx9aclc_pcm_remove, + .ops = &txx9aclc_pcm_ops, + .pcm_new = txx9aclc_pcm_new, +}; + +static int txx9aclc_soc_platform_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &txx9aclc_soc_component, NULL, 0); +} + +static struct platform_driver txx9aclc_pcm_driver = { + .driver = { + .name = "txx9aclc-pcm-audio", + }, + + .probe = txx9aclc_soc_platform_probe, +}; + +module_platform_driver(txx9aclc_pcm_driver); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("TXx9 ACLC Audio DMA driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/txx9/txx9aclc.h b/sound/soc/txx9/txx9aclc.h new file mode 100644 index 000000000..9c2de84fe --- /dev/null +++ b/sound/soc/txx9/txx9aclc.h @@ -0,0 +1,74 @@ +/* + * TXx9 SoC AC Link Controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __TXX9ACLC_H +#define __TXX9ACLC_H + +#include +#include + +#define ACCTLEN 0x00 /* control enable */ +#define ACCTLDIS 0x04 /* control disable */ +#define ACCTL_ENLINK 0x00000001 /* enable/disable AC-link */ +#define ACCTL_AUDODMA 0x00000100 /* AUDODMA enable/disable */ +#define ACCTL_AUDIDMA 0x00001000 /* AUDIDMA enable/disable */ +#define ACCTL_AUDOEHLT 0x00010000 /* AUDO error halt + enable/disable */ +#define ACCTL_AUDIEHLT 0x00100000 /* AUDI error halt + enable/disable */ +#define ACREGACC 0x08 /* codec register access */ +#define ACREGACC_DAT_SHIFT 0 /* data field */ +#define ACREGACC_REG_SHIFT 16 /* address field */ +#define ACREGACC_CODECID_SHIFT 24 /* CODEC ID field */ +#define ACREGACC_READ 0x80000000 /* CODEC read */ +#define ACREGACC_WRITE 0x00000000 /* CODEC write */ +#define ACINTSTS 0x10 /* interrupt status */ +#define ACINTMSTS 0x14 /* interrupt masked status */ +#define ACINTEN 0x18 /* interrupt enable */ +#define ACINTDIS 0x1c /* interrupt disable */ +#define ACINT_CODECRDY(n) (0x00000001 << (n)) /* CODECn ready */ +#define ACINT_REGACCRDY 0x00000010 /* ACREGACC ready */ +#define ACINT_AUDOERR 0x00000100 /* AUDO underrun error */ +#define ACINT_AUDIERR 0x00001000 /* AUDI overrun error */ +#define ACDMASTS 0x80 /* DMA request status */ +#define ACDMA_AUDO 0x00000001 /* AUDODMA pending */ +#define ACDMA_AUDI 0x00000010 /* AUDIDMA pending */ +#define ACAUDODAT 0xa0 /* audio out data */ +#define ACAUDIDAT 0xb0 /* audio in data */ +#define ACREVID 0xfc /* revision ID */ + +struct txx9aclc_dmadata { + struct resource *dma_res; + struct txx9dmac_slave dma_slave; + struct dma_chan *dma_chan; + struct tasklet_struct tasklet; + spinlock_t dma_lock; + int stream; /* SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE */ + struct snd_pcm_substream *substream; + unsigned long pos; + dma_addr_t dma_addr; + unsigned long buffer_bytes; + unsigned long period_bytes; + unsigned long frag_bytes; + int frags; + int frag_count; + int dmacount; +}; + +struct txx9aclc_plat_drvdata { + void __iomem *base; + u64 physbase; +}; + +static inline struct txx9aclc_plat_drvdata *txx9aclc_get_plat_drvdata( + struct snd_soc_dai *dai) +{ + return dev_get_drvdata(dai->dev); +} + +#endif /* __TXX9ACLC_H */ diff --git a/sound/soc/uniphier/Kconfig b/sound/soc/uniphier/Kconfig new file mode 100644 index 000000000..ddfa6424c --- /dev/null +++ b/sound/soc/uniphier/Kconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0 +config SND_SOC_UNIPHIER + tristate "ASoC support for UniPhier" + depends on (ARCH_UNIPHIER || COMPILE_TEST) + help + Say Y or M if you want to add support for the Socionext + UniPhier SoC audio interfaces. You will also need to select the + audio interfaces to support below. + If unsure select "N". + +config SND_SOC_UNIPHIER_AIO + tristate "UniPhier AIO DAI Driver" + select REGMAP_MMIO + select SND_SOC_COMPRESS + depends on SND_SOC_UNIPHIER + help + This adds ASoC driver support for Socionext UniPhier + 'AIO' Audio Input/Output subsystem. + Select Y if you use such device. + If unsure select "N". + +config SND_SOC_UNIPHIER_LD11 + tristate "UniPhier LD11/LD20 Device Driver" + depends on SND_SOC_UNIPHIER + select SND_SOC_UNIPHIER_AIO + help + This adds ASoC driver for Socionext UniPhier LD11/LD20 + input and output that can be used with other codecs. + Select Y if you use such device. + If unsure select "N". + +config SND_SOC_UNIPHIER_PXS2 + tristate "UniPhier PXs2 Device Driver" + depends on SND_SOC_UNIPHIER + select SND_SOC_UNIPHIER_AIO + help + This adds ASoC driver for Socionext UniPhier PXs2 + input and output that can be used with other codecs. + Select Y if you use such device. + If unsure select "N". + +config SND_SOC_UNIPHIER_EVEA_CODEC + tristate "UniPhier SoC internal audio codec" + depends on SND_SOC_UNIPHIER + select REGMAP_MMIO + help + This adds Codec driver for Socionext UniPhier LD11/20 SoC + internal DAC. This driver supports Line In / Out and HeadPhone. + Select Y if you use such device. + If unsure select "N". diff --git a/sound/soc/uniphier/Makefile b/sound/soc/uniphier/Makefile new file mode 100644 index 000000000..88169395f --- /dev/null +++ b/sound/soc/uniphier/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-uniphier-aio-cpu-objs := aio-core.o aio-dma.o aio-cpu.o aio-compress.o +snd-soc-uniphier-aio-ld11-objs := aio-ld11.o +snd-soc-uniphier-aio-pxs2-objs := aio-pxs2.o + +obj-$(CONFIG_SND_SOC_UNIPHIER_AIO) += snd-soc-uniphier-aio-cpu.o +obj-$(CONFIG_SND_SOC_UNIPHIER_LD11) += snd-soc-uniphier-aio-ld11.o +obj-$(CONFIG_SND_SOC_UNIPHIER_PXS2) += snd-soc-uniphier-aio-pxs2.o + +snd-soc-uniphier-evea-objs := evea.o +obj-$(CONFIG_SND_SOC_UNIPHIER_EVEA_CODEC) += snd-soc-uniphier-evea.o diff --git a/sound/soc/uniphier/aio-compress.c b/sound/soc/uniphier/aio-compress.c new file mode 100644 index 000000000..17f773ac5 --- /dev/null +++ b/sound/soc/uniphier/aio-compress.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO Compress Audio driver. +// +// Copyright (c) 2017-2018 Socionext Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aio.h" + +static int uniphier_aio_compr_prepare(struct snd_compr_stream *cstream); +static int uniphier_aio_compr_hw_free(struct snd_compr_stream *cstream); + +static int uniphier_aio_comprdma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_compr *compr = rtd->compr; + struct device *dev = compr->card->dev; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[compr->direction]; + size_t size = AUD_RING_SIZE; + int dma_dir = DMA_FROM_DEVICE, ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(33)); + if (ret) + return ret; + + sub->compr_area = kzalloc(size, GFP_KERNEL); + if (!sub->compr_area) + return -ENOMEM; + + if (sub->swm->dir == PORT_DIR_OUTPUT) + dma_dir = DMA_TO_DEVICE; + + sub->compr_addr = dma_map_single(dev, sub->compr_area, size, dma_dir); + if (dma_mapping_error(dev, sub->compr_addr)) { + kfree(sub->compr_area); + sub->compr_area = NULL; + + return -ENOMEM; + } + + sub->compr_bytes = size; + + return 0; +} + +static int uniphier_aio_comprdma_free(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_compr *compr = rtd->compr; + struct device *dev = compr->card->dev; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[compr->direction]; + int dma_dir = DMA_FROM_DEVICE; + + if (sub->swm->dir == PORT_DIR_OUTPUT) + dma_dir = DMA_TO_DEVICE; + + dma_unmap_single(dev, sub->compr_addr, sub->compr_bytes, dma_dir); + kfree(sub->compr_area); + sub->compr_area = NULL; + + return 0; +} + +static int uniphier_aio_compr_open(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int ret; + + if (sub->cstream) + return -EBUSY; + + sub->cstream = cstream; + sub->pass_through = 1; + sub->use_mmap = false; + + ret = uniphier_aio_comprdma_new(rtd); + if (ret) + return ret; + + ret = aio_init(sub); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aio_compr_free(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int ret; + + ret = uniphier_aio_compr_hw_free(cstream); + if (ret) + return ret; + ret = uniphier_aio_comprdma_free(rtd); + if (ret) + return ret; + + sub->cstream = NULL; + + return 0; +} + +static int uniphier_aio_compr_get_params(struct snd_compr_stream *cstream, + struct snd_codec *params) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + + *params = sub->cparams.codec; + + return 0; +} + +static int uniphier_aio_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + struct device *dev = &aio->chip->pdev->dev; + int ret; + + if (params->codec.id != SND_AUDIOCODEC_IEC61937) { + dev_err(dev, "Codec ID is not supported(%d)\n", + params->codec.id); + return -EINVAL; + } + if (params->codec.profile != SND_AUDIOPROFILE_IEC61937_SPDIF) { + dev_err(dev, "Codec profile is not supported(%d)\n", + params->codec.profile); + return -EINVAL; + } + + /* IEC frame type will be changed after received valid data */ + sub->iec_pc = IEC61937_PC_AAC; + + sub->cparams = *params; + sub->setting = 1; + + aio_port_reset(sub); + aio_src_reset(sub); + + ret = uniphier_aio_compr_prepare(cstream); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aio_compr_hw_free(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + + sub->setting = 0; + + return 0; +} + +static int uniphier_aio_compr_prepare(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int bytes = runtime->fragment_size; + unsigned long flags; + int ret; + + ret = aiodma_ch_set_param(sub); + if (ret) + return ret; + + spin_lock_irqsave(&sub->lock, flags); + ret = aiodma_rb_set_buffer(sub, sub->compr_addr, + sub->compr_addr + sub->compr_bytes, + bytes); + spin_unlock_irqrestore(&sub->lock, flags); + if (ret) + return ret; + + ret = aio_port_set_param(sub, sub->pass_through, &sub->params); + if (ret) + return ret; + ret = aio_oport_set_stream_type(sub, sub->iec_pc); + if (ret) + return ret; + aio_port_set_enable(sub, 1); + + ret = aio_if_set_param(sub, sub->pass_through); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aio_compr_trigger(struct snd_compr_stream *cstream, + int cmd) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + struct device *dev = &aio->chip->pdev->dev; + int bytes = runtime->fragment_size, ret = 0; + unsigned long flags; + + spin_lock_irqsave(&sub->lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + aiodma_ch_set_enable(sub, 1); + sub->running = 1; + + break; + case SNDRV_PCM_TRIGGER_STOP: + sub->running = 0; + aiodma_ch_set_enable(sub, 0); + + break; + default: + dev_warn(dev, "Unknown trigger(%d)\n", cmd); + ret = -EINVAL; + } + spin_unlock_irqrestore(&sub->lock, flags); + + return ret; +} + +static int uniphier_aio_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int bytes = runtime->fragment_size; + unsigned long flags; + u32 pos; + + spin_lock_irqsave(&sub->lock, flags); + + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + pos = sub->rd_offs; + /* Size of AIO output format is double of IEC61937 */ + tstamp->copied_total = sub->rd_total / 2; + } else { + pos = sub->wr_offs; + tstamp->copied_total = sub->rd_total; + } + tstamp->byte_offset = pos; + + spin_unlock_irqrestore(&sub->lock, flags); + + return 0; +} + +static int aio_compr_send_to_hw(struct uniphier_aio_sub *sub, + char __user *buf, size_t dstsize) +{ + u32 __user *srcbuf = (u32 __user *)buf; + u32 *dstbuf = (u32 *)(sub->compr_area + sub->wr_offs); + int src = 0, dst = 0, ret; + u32 frm, frm_a, frm_b; + + while (dstsize > 0) { + ret = get_user(frm, srcbuf + src); + if (ret) + return ret; + src++; + + frm_a = frm & 0xffff; + frm_b = (frm >> 16) & 0xffff; + + if (frm == IEC61937_HEADER_SIGN) { + frm_a |= 0x01000000; + + /* Next data is Pc and Pd */ + sub->iec_header = true; + } else { + u16 pc = be16_to_cpu((__be16)frm_a); + + if (sub->iec_header && sub->iec_pc != pc) { + /* Force overwrite IEC frame type */ + sub->iec_pc = pc; + ret = aio_oport_set_stream_type(sub, pc); + if (ret) + return ret; + } + sub->iec_header = false; + } + dstbuf[dst++] = frm_a; + dstbuf[dst++] = frm_b; + + dstsize -= sizeof(u32) * 2; + } + + return 0; +} + +static int uniphier_aio_compr_copy(struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct device *carddev = rtd->compr->card->dev; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + size_t cnt = min_t(size_t, count, aio_rb_space_to_end(sub) / 2); + int bytes = runtime->fragment_size; + unsigned long flags; + size_t s; + int ret; + + if (cnt < sizeof(u32)) + return 0; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + dma_addr_t dmapos = sub->compr_addr + sub->wr_offs; + + /* Size of AIO output format is double of IEC61937 */ + s = cnt * 2; + + dma_sync_single_for_cpu(carddev, dmapos, s, DMA_TO_DEVICE); + ret = aio_compr_send_to_hw(sub, buf, s); + dma_sync_single_for_device(carddev, dmapos, s, DMA_TO_DEVICE); + } else { + dma_addr_t dmapos = sub->compr_addr + sub->rd_offs; + + s = cnt; + + dma_sync_single_for_cpu(carddev, dmapos, s, DMA_FROM_DEVICE); + ret = copy_to_user(buf, sub->compr_area + sub->rd_offs, s); + dma_sync_single_for_device(carddev, dmapos, s, DMA_FROM_DEVICE); + } + if (ret) + return -EFAULT; + + spin_lock_irqsave(&sub->lock, flags); + + sub->threshold = 2 * bytes; + aiodma_rb_set_threshold(sub, sub->compr_bytes, 2 * bytes); + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + sub->wr_offs += s; + if (sub->wr_offs >= sub->compr_bytes) + sub->wr_offs -= sub->compr_bytes; + } else { + sub->rd_offs += s; + if (sub->rd_offs >= sub->compr_bytes) + sub->rd_offs -= sub->compr_bytes; + } + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + + spin_unlock_irqrestore(&sub->lock, flags); + + return cnt; +} + +static int uniphier_aio_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->num_codecs = 1; + caps->min_fragment_size = AUD_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = AUD_MAX_FRAGMENT_SIZE; + caps->min_fragments = AUD_MIN_FRAGMENT; + caps->max_fragments = AUD_MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_IEC61937; + + return 0; +} + +static const struct snd_compr_codec_caps caps_iec = { + .num_descriptors = 1, + .descriptor[0].max_ch = 8, + .descriptor[0].num_sample_rates = 0, + .descriptor[0].num_bitrates = 0, + .descriptor[0].profiles = SND_AUDIOPROFILE_IEC61937_SPDIF, + .descriptor[0].modes = SND_AUDIOMODE_IEC_AC3 | + SND_AUDIOMODE_IEC_MPEG1 | + SND_AUDIOMODE_IEC_MP3 | + SND_AUDIOMODE_IEC_DTS, + .descriptor[0].formats = 0, +}; + +static int uniphier_aio_compr_get_codec_caps(struct snd_compr_stream *stream, + struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_IEC61937) + *codec = caps_iec; + else + return -EINVAL; + + return 0; +} + +const struct snd_compr_ops uniphier_aio_compr_ops = { + .open = uniphier_aio_compr_open, + .free = uniphier_aio_compr_free, + .get_params = uniphier_aio_compr_get_params, + .set_params = uniphier_aio_compr_set_params, + .trigger = uniphier_aio_compr_trigger, + .pointer = uniphier_aio_compr_pointer, + .copy = uniphier_aio_compr_copy, + .get_caps = uniphier_aio_compr_get_caps, + .get_codec_caps = uniphier_aio_compr_get_codec_caps, +}; diff --git a/sound/soc/uniphier/aio-core.c b/sound/soc/uniphier/aio-core.c new file mode 100644 index 000000000..9bcba06ba --- /dev/null +++ b/sound/soc/uniphier/aio-core.c @@ -0,0 +1,1251 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA common driver. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aio.h" +#include "aio-reg.h" + +static u64 rb_cnt(u64 wr, u64 rd, u64 len) +{ + if (rd <= wr) + return wr - rd; + else + return len - (rd - wr); +} + +static u64 rb_cnt_to_end(u64 wr, u64 rd, u64 len) +{ + if (rd <= wr) + return wr - rd; + else + return len - rd; +} + +static u64 rb_space(u64 wr, u64 rd, u64 len) +{ + if (rd <= wr) + return len - (wr - rd) - 8; + else + return rd - wr - 8; +} + +static u64 rb_space_to_end(u64 wr, u64 rd, u64 len) +{ + if (rd > wr) + return rd - wr - 8; + else if (rd > 0) + return len - wr; + else + return len - wr - 8; +} + +u64 aio_rb_cnt(struct uniphier_aio_sub *sub) +{ + return rb_cnt(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +u64 aio_rbt_cnt_to_end(struct uniphier_aio_sub *sub) +{ + return rb_cnt_to_end(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +u64 aio_rb_space(struct uniphier_aio_sub *sub) +{ + return rb_space(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +u64 aio_rb_space_to_end(struct uniphier_aio_sub *sub) +{ + return rb_space_to_end(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +/** + * aio_iecout_set_enable - setup IEC output via SoC glue + * @chip: the AIO chip pointer + * @enable: false to stop the output, true to start + * + * Set enabled or disabled S/PDIF signal output to out of SoC via AOnIEC pins. + * This function need to call at driver startup. + * + * The regmap of SoC glue is specified by 'socionext,syscon' optional property + * of DT. This function has no effect if no property. + */ +void aio_iecout_set_enable(struct uniphier_aio_chip *chip, bool enable) +{ + struct regmap *r = chip->regmap_sg; + + if (!r) + return; + + regmap_write(r, SG_AOUTEN, (enable) ? ~0 : 0); +} + +/** + * aio_chip_set_pll - set frequency to audio PLL + * @chip : the AIO chip pointer + * @source: PLL + * @freq : frequency in Hz, 0 is ignored + * + * Sets frequency of audio PLL. This function can be called anytime, + * but it takes time till PLL is locked. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_chip_set_pll(struct uniphier_aio_chip *chip, int pll_id, + unsigned int freq) +{ + struct device *dev = &chip->pdev->dev; + struct regmap *r = chip->regmap; + int shift; + u32 v; + + /* Not change */ + if (freq == 0) + return 0; + + switch (pll_id) { + case AUD_PLL_A1: + shift = 0; + break; + case AUD_PLL_F1: + shift = 1; + break; + case AUD_PLL_A2: + shift = 2; + break; + case AUD_PLL_F2: + shift = 3; + break; + default: + dev_err(dev, "PLL(%d) not supported\n", pll_id); + return -EINVAL; + } + + switch (freq) { + case 36864000: + v = A2APLLCTR1_APLLX_36MHZ; + break; + case 33868800: + v = A2APLLCTR1_APLLX_33MHZ; + break; + default: + dev_err(dev, "PLL frequency not supported(%d)\n", freq); + return -EINVAL; + } + chip->plls[pll_id].freq = freq; + + regmap_update_bits(r, A2APLLCTR1, A2APLLCTR1_APLLX_MASK << shift, + v << shift); + + return 0; +} + +/** + * aio_chip_init - initialize AIO whole settings + * @chip: the AIO chip pointer + * + * Sets AIO fixed and whole device settings to AIO. + * This function need to call once at driver startup. + * + * The register area that is changed by this function is shared by all + * modules of AIO. But there is not race condition since this function + * has always set the same initialize values. + */ +void aio_chip_init(struct uniphier_aio_chip *chip) +{ + struct regmap *r = chip->regmap; + + regmap_update_bits(r, A2APLLCTR0, + A2APLLCTR0_APLLXPOW_MASK, + A2APLLCTR0_APLLXPOW_PWON); + + regmap_update_bits(r, A2EXMCLKSEL0, + A2EXMCLKSEL0_EXMCLK_MASK, + A2EXMCLKSEL0_EXMCLK_OUTPUT); + + regmap_update_bits(r, A2AIOINPUTSEL, A2AIOINPUTSEL_RXSEL_MASK, + A2AIOINPUTSEL_RXSEL_PCMI1_HDMIRX1 | + A2AIOINPUTSEL_RXSEL_PCMI2_SIF | + A2AIOINPUTSEL_RXSEL_PCMI3_EVEA | + A2AIOINPUTSEL_RXSEL_IECI1_HDMIRX1); + + if (chip->chip_spec->addr_ext) + regmap_update_bits(r, CDA2D_TEST, CDA2D_TEST_DDR_MODE_MASK, + CDA2D_TEST_DDR_MODE_EXTON0); + else + regmap_update_bits(r, CDA2D_TEST, CDA2D_TEST_DDR_MODE_MASK, + CDA2D_TEST_DDR_MODE_EXTOFF1); +} + +/** + * aio_init - initialize AIO substream + * @sub: the AIO substream pointer + * + * Sets fixed settings of each AIO substreams. + * This function need to call once at substream startup. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_init(struct uniphier_aio_sub *sub) +{ + struct device *dev = &sub->aio->chip->pdev->dev; + struct regmap *r = sub->aio->chip->regmap; + + regmap_write(r, A2RBNMAPCTR0(sub->swm->rb.hw), + MAPCTR0_EN | sub->swm->rb.map); + regmap_write(r, A2CHNMAPCTR0(sub->swm->ch.hw), + MAPCTR0_EN | sub->swm->ch.map); + + switch (sub->swm->type) { + case PORT_TYPE_I2S: + case PORT_TYPE_SPDIF: + case PORT_TYPE_EVE: + if (sub->swm->dir == PORT_DIR_INPUT) { + regmap_write(r, A2IIFNMAPCTR0(sub->swm->iif.hw), + MAPCTR0_EN | sub->swm->iif.map); + regmap_write(r, A2IPORTNMAPCTR0(sub->swm->iport.hw), + MAPCTR0_EN | sub->swm->iport.map); + } else { + regmap_write(r, A2OIFNMAPCTR0(sub->swm->oif.hw), + MAPCTR0_EN | sub->swm->oif.map); + regmap_write(r, A2OPORTNMAPCTR0(sub->swm->oport.hw), + MAPCTR0_EN | sub->swm->oport.map); + } + break; + case PORT_TYPE_CONV: + regmap_write(r, A2OIFNMAPCTR0(sub->swm->oif.hw), + MAPCTR0_EN | sub->swm->oif.map); + regmap_write(r, A2OPORTNMAPCTR0(sub->swm->oport.hw), + MAPCTR0_EN | sub->swm->oport.map); + regmap_write(r, A2CHNMAPCTR0(sub->swm->och.hw), + MAPCTR0_EN | sub->swm->och.map); + regmap_write(r, A2IIFNMAPCTR0(sub->swm->iif.hw), + MAPCTR0_EN | sub->swm->iif.map); + break; + default: + dev_err(dev, "Unknown port type %d.\n", sub->swm->type); + return -EINVAL; + } + + return 0; +} + +/** + * aio_port_reset - reset AIO port block + * @sub: the AIO substream pointer + * + * Resets the digital signal input/output port block of AIO. + */ +void aio_port_reset(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + regmap_write(r, AOUTRSTCTR0, BIT(sub->swm->oport.map)); + regmap_write(r, AOUTRSTCTR1, BIT(sub->swm->oport.map)); + } else { + regmap_update_bits(r, IPORTMXRSTCTR(sub->swm->iport.map), + IPORTMXRSTCTR_RSTPI_MASK, + IPORTMXRSTCTR_RSTPI_RESET); + regmap_update_bits(r, IPORTMXRSTCTR(sub->swm->iport.map), + IPORTMXRSTCTR_RSTPI_MASK, + IPORTMXRSTCTR_RSTPI_RELEASE); + } +} + +/** + * aio_port_set_ch - set channels of LPCM + * @sub: the AIO substream pointer, PCM substream only + * @ch : count of channels + * + * Set suitable slot selecting to input/output port block of AIO. + * + * This function may return error if non-PCM substream. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_ch(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 slotsel_2ch[] = { + 0, 0, 0, 0, 0, + }; + u32 slotsel_multi[] = { + OPORTMXTYSLOTCTR_SLOTSEL_SLOT0, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT1, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT2, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT3, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT4, + }; + u32 mode, *slotsel; + int i; + + switch (params_channels(&sub->params)) { + case 8: + case 6: + mode = OPORTMXTYSLOTCTR_MODE; + slotsel = slotsel_multi; + break; + case 2: + mode = 0; + slotsel = slotsel_2ch; + break; + default: + return -EINVAL; + } + + for (i = 0; i < AUD_MAX_SLOTSEL; i++) { + regmap_update_bits(r, OPORTMXTYSLOTCTR(sub->swm->oport.map, i), + OPORTMXTYSLOTCTR_MODE, mode); + regmap_update_bits(r, OPORTMXTYSLOTCTR(sub->swm->oport.map, i), + OPORTMXTYSLOTCTR_SLOTSEL_MASK, slotsel[i]); + } + + return 0; +} + +/** + * aio_port_set_rate - set sampling rate of LPCM + * @sub: the AIO substream pointer, PCM substream only + * @rate: Sampling rate in Hz. + * + * Set suitable I2S format settings to input/output port block of AIO. + * Parameter is specified by hw_params(). + * + * This function may return error if non-PCM substream. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_rate(struct uniphier_aio_sub *sub, int rate) +{ + struct regmap *r = sub->aio->chip->regmap; + struct device *dev = &sub->aio->chip->pdev->dev; + u32 v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + switch (rate) { + case 8000: + v = OPORTMXCTR1_FSSEL_8; + break; + case 11025: + v = OPORTMXCTR1_FSSEL_11_025; + break; + case 12000: + v = OPORTMXCTR1_FSSEL_12; + break; + case 16000: + v = OPORTMXCTR1_FSSEL_16; + break; + case 22050: + v = OPORTMXCTR1_FSSEL_22_05; + break; + case 24000: + v = OPORTMXCTR1_FSSEL_24; + break; + case 32000: + v = OPORTMXCTR1_FSSEL_32; + break; + case 44100: + v = OPORTMXCTR1_FSSEL_44_1; + break; + case 48000: + v = OPORTMXCTR1_FSSEL_48; + break; + case 88200: + v = OPORTMXCTR1_FSSEL_88_2; + break; + case 96000: + v = OPORTMXCTR1_FSSEL_96; + break; + case 176400: + v = OPORTMXCTR1_FSSEL_176_4; + break; + case 192000: + v = OPORTMXCTR1_FSSEL_192; + break; + default: + dev_err(dev, "Rate not supported(%d)\n", rate); + return -EINVAL; + } + + regmap_update_bits(r, OPORTMXCTR1(sub->swm->oport.map), + OPORTMXCTR1_FSSEL_MASK, v); + } else { + switch (rate) { + case 8000: + v = IPORTMXCTR1_FSSEL_8; + break; + case 11025: + v = IPORTMXCTR1_FSSEL_11_025; + break; + case 12000: + v = IPORTMXCTR1_FSSEL_12; + break; + case 16000: + v = IPORTMXCTR1_FSSEL_16; + break; + case 22050: + v = IPORTMXCTR1_FSSEL_22_05; + break; + case 24000: + v = IPORTMXCTR1_FSSEL_24; + break; + case 32000: + v = IPORTMXCTR1_FSSEL_32; + break; + case 44100: + v = IPORTMXCTR1_FSSEL_44_1; + break; + case 48000: + v = IPORTMXCTR1_FSSEL_48; + break; + case 88200: + v = IPORTMXCTR1_FSSEL_88_2; + break; + case 96000: + v = IPORTMXCTR1_FSSEL_96; + break; + case 176400: + v = IPORTMXCTR1_FSSEL_176_4; + break; + case 192000: + v = IPORTMXCTR1_FSSEL_192; + break; + default: + dev_err(dev, "Rate not supported(%d)\n", rate); + return -EINVAL; + } + + regmap_update_bits(r, IPORTMXCTR1(sub->swm->iport.map), + IPORTMXCTR1_FSSEL_MASK, v); + } + + return 0; +} + +/** + * aio_port_set_fmt - set format of I2S data + * @sub: the AIO substream pointer, PCM substream only + * This parameter has no effect if substream is I2S or PCM. + * + * Set suitable I2S format settings to input/output port block of AIO. + * Parameter is specified by set_fmt(). + * + * This function may return error if non-PCM substream. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_fmt(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + struct device *dev = &sub->aio->chip->pdev->dev; + u32 v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + switch (sub->aio->fmt) { + case SND_SOC_DAIFMT_LEFT_J: + v = OPORTMXCTR1_I2SLRSEL_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + v = OPORTMXCTR1_I2SLRSEL_RIGHT; + break; + case SND_SOC_DAIFMT_I2S: + v = OPORTMXCTR1_I2SLRSEL_I2S; + break; + default: + dev_err(dev, "Format is not supported(%d)\n", + sub->aio->fmt); + return -EINVAL; + } + + v |= OPORTMXCTR1_OUTBITSEL_24; + regmap_update_bits(r, OPORTMXCTR1(sub->swm->oport.map), + OPORTMXCTR1_I2SLRSEL_MASK | + OPORTMXCTR1_OUTBITSEL_MASK, v); + } else { + switch (sub->aio->fmt) { + case SND_SOC_DAIFMT_LEFT_J: + v = IPORTMXCTR1_LRSEL_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + v = IPORTMXCTR1_LRSEL_RIGHT; + break; + case SND_SOC_DAIFMT_I2S: + v = IPORTMXCTR1_LRSEL_I2S; + break; + default: + dev_err(dev, "Format is not supported(%d)\n", + sub->aio->fmt); + return -EINVAL; + } + + v |= IPORTMXCTR1_OUTBITSEL_24 | + IPORTMXCTR1_CHSEL_ALL; + regmap_update_bits(r, IPORTMXCTR1(sub->swm->iport.map), + IPORTMXCTR1_LRSEL_MASK | + IPORTMXCTR1_OUTBITSEL_MASK | + IPORTMXCTR1_CHSEL_MASK, v); + } + + return 0; +} + +/** + * aio_port_set_clk - set clock and divider of AIO port block + * @sub: the AIO substream pointer + * + * Set suitable PLL clock divider and relational settings to + * input/output port block of AIO. Parameters are specified by + * set_sysclk() and set_pll(). + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_clk(struct uniphier_aio_sub *sub) +{ + struct uniphier_aio_chip *chip = sub->aio->chip; + struct device *dev = &sub->aio->chip->pdev->dev; + struct regmap *r = sub->aio->chip->regmap; + u32 v_pll[] = { + OPORTMXCTR2_ACLKSEL_A1, OPORTMXCTR2_ACLKSEL_F1, + OPORTMXCTR2_ACLKSEL_A2, OPORTMXCTR2_ACLKSEL_F2, + OPORTMXCTR2_ACLKSEL_A2PLL, + OPORTMXCTR2_ACLKSEL_RX1, + }; + u32 v_div[] = { + OPORTMXCTR2_DACCKSEL_1_2, OPORTMXCTR2_DACCKSEL_1_3, + OPORTMXCTR2_DACCKSEL_1_1, OPORTMXCTR2_DACCKSEL_2_3, + }; + u32 v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + if (sub->swm->type == PORT_TYPE_I2S) { + if (sub->aio->pll_out >= ARRAY_SIZE(v_pll)) { + dev_err(dev, "PLL(%d) is invalid\n", + sub->aio->pll_out); + return -EINVAL; + } + if (sub->aio->plldiv >= ARRAY_SIZE(v_div)) { + dev_err(dev, "PLL divider(%d) is invalid\n", + sub->aio->plldiv); + return -EINVAL; + } + + v = v_pll[sub->aio->pll_out] | + OPORTMXCTR2_MSSEL_MASTER | + v_div[sub->aio->plldiv]; + + switch (chip->plls[sub->aio->pll_out].freq) { + case 0: + case 36864000: + case 33868800: + v |= OPORTMXCTR2_EXTLSIFSSEL_36; + break; + default: + v |= OPORTMXCTR2_EXTLSIFSSEL_24; + break; + } + } else if (sub->swm->type == PORT_TYPE_EVE) { + v = OPORTMXCTR2_ACLKSEL_A2PLL | + OPORTMXCTR2_MSSEL_MASTER | + OPORTMXCTR2_EXTLSIFSSEL_36 | + OPORTMXCTR2_DACCKSEL_1_2; + } else if (sub->swm->type == PORT_TYPE_SPDIF) { + if (sub->aio->pll_out >= ARRAY_SIZE(v_pll)) { + dev_err(dev, "PLL(%d) is invalid\n", + sub->aio->pll_out); + return -EINVAL; + } + v = v_pll[sub->aio->pll_out] | + OPORTMXCTR2_MSSEL_MASTER | + OPORTMXCTR2_DACCKSEL_1_2; + + switch (chip->plls[sub->aio->pll_out].freq) { + case 0: + case 36864000: + case 33868800: + v |= OPORTMXCTR2_EXTLSIFSSEL_36; + break; + default: + v |= OPORTMXCTR2_EXTLSIFSSEL_24; + break; + } + } else { + v = OPORTMXCTR2_ACLKSEL_A1 | + OPORTMXCTR2_MSSEL_MASTER | + OPORTMXCTR2_EXTLSIFSSEL_36 | + OPORTMXCTR2_DACCKSEL_1_2; + } + regmap_write(r, OPORTMXCTR2(sub->swm->oport.map), v); + } else { + v = IPORTMXCTR2_ACLKSEL_A1 | + IPORTMXCTR2_MSSEL_SLAVE | + IPORTMXCTR2_EXTLSIFSSEL_36 | + IPORTMXCTR2_DACCKSEL_1_2; + regmap_write(r, IPORTMXCTR2(sub->swm->iport.map), v); + } + + return 0; +} + +/** + * aio_port_set_param - set parameters of AIO port block + * @sub: the AIO substream pointer + * @pass_through: Zero if sound data is LPCM, otherwise if data is not LPCM. + * This parameter has no effect if substream is I2S or PCM. + * @params: hardware parameters of ALSA + * + * Set suitable setting to input/output port block of AIO to process the + * specified in params. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_port_set_param(struct uniphier_aio_sub *sub, int pass_through, + const struct snd_pcm_hw_params *params) +{ + struct regmap *r = sub->aio->chip->regmap; + unsigned int rate; + u32 v; + int ret; + + if (!pass_through) { + if (sub->swm->type == PORT_TYPE_EVE || + sub->swm->type == PORT_TYPE_CONV) { + rate = 48000; + } else { + rate = params_rate(params); + } + + ret = aio_port_set_ch(sub); + if (ret) + return ret; + + ret = aio_port_set_rate(sub, rate); + if (ret) + return ret; + + ret = aio_port_set_fmt(sub); + if (ret) + return ret; + } + + ret = aio_port_set_clk(sub); + if (ret) + return ret; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + if (pass_through) + v = OPORTMXCTR3_SRCSEL_STREAM | + OPORTMXCTR3_VALID_STREAM; + else + v = OPORTMXCTR3_SRCSEL_PCM | + OPORTMXCTR3_VALID_PCM; + + v |= OPORTMXCTR3_IECTHUR_IECOUT | + OPORTMXCTR3_PMSEL_PAUSE | + OPORTMXCTR3_PMSW_MUTE_OFF; + regmap_write(r, OPORTMXCTR3(sub->swm->oport.map), v); + } else { + regmap_write(r, IPORTMXACLKSEL0EX(sub->swm->iport.map), + IPORTMXACLKSEL0EX_ACLKSEL0EX_INTERNAL); + regmap_write(r, IPORTMXEXNOE(sub->swm->iport.map), + IPORTMXEXNOE_PCMINOE_INPUT); + } + + return 0; +} + +/** + * aio_port_set_enable - start or stop of AIO port block + * @sub: the AIO substream pointer + * @enable: zero to stop the block, otherwise to start + * + * Start or stop the signal input/output port block of AIO. + */ +void aio_port_set_enable(struct uniphier_aio_sub *sub, int enable) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + regmap_write(r, OPORTMXPATH(sub->swm->oport.map), + sub->swm->oif.map); + + regmap_update_bits(r, OPORTMXMASK(sub->swm->oport.map), + OPORTMXMASK_IUDXMSK_MASK | + OPORTMXMASK_IUXCKMSK_MASK | + OPORTMXMASK_DXMSK_MASK | + OPORTMXMASK_XCKMSK_MASK, + OPORTMXMASK_IUDXMSK_OFF | + OPORTMXMASK_IUXCKMSK_OFF | + OPORTMXMASK_DXMSK_OFF | + OPORTMXMASK_XCKMSK_OFF); + + if (enable) + regmap_write(r, AOUTENCTR0, BIT(sub->swm->oport.map)); + else + regmap_write(r, AOUTENCTR1, BIT(sub->swm->oport.map)); + } else { + regmap_update_bits(r, IPORTMXMASK(sub->swm->iport.map), + IPORTMXMASK_IUXCKMSK_MASK | + IPORTMXMASK_XCKMSK_MASK, + IPORTMXMASK_IUXCKMSK_OFF | + IPORTMXMASK_XCKMSK_OFF); + + if (enable) + regmap_update_bits(r, + IPORTMXCTR2(sub->swm->iport.map), + IPORTMXCTR2_REQEN_MASK, + IPORTMXCTR2_REQEN_ENABLE); + else + regmap_update_bits(r, + IPORTMXCTR2(sub->swm->iport.map), + IPORTMXCTR2_REQEN_MASK, + IPORTMXCTR2_REQEN_DISABLE); + } +} + +/** + * aio_port_get_volume - get volume of AIO port block + * @sub: the AIO substream pointer + * + * Return: current volume, range is 0x0000 - 0xffff + */ +int aio_port_get_volume(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + regmap_read(r, OPORTMXTYVOLGAINSTATUS(sub->swm->oport.map, 0), &v); + + return FIELD_GET(OPORTMXTYVOLGAINSTATUS_CUR_MASK, v); +} + +/** + * aio_port_set_volume - set volume of AIO port block + * @sub: the AIO substream pointer + * @vol: target volume, range is 0x0000 - 0xffff. + * + * Change digital volume and perfome fade-out/fade-in effect for specified + * output slot of port. Gained PCM value can calculate as the following: + * Gained = Original * vol / 0x4000 + */ +void aio_port_set_volume(struct uniphier_aio_sub *sub, int vol) +{ + struct regmap *r = sub->aio->chip->regmap; + int oport_map = sub->swm->oport.map; + int cur, diff, slope = 0, fs; + + if (sub->swm->dir == PORT_DIR_INPUT) + return; + + cur = aio_port_get_volume(sub); + diff = abs(vol - cur); + fs = params_rate(&sub->params); + if (fs) + slope = diff / AUD_VOL_FADE_TIME * 1000 / fs; + slope = max(1, slope); + + regmap_update_bits(r, OPORTMXTYVOLPARA1(oport_map, 0), + OPORTMXTYVOLPARA1_SLOPEU_MASK, slope << 16); + regmap_update_bits(r, OPORTMXTYVOLPARA2(oport_map, 0), + OPORTMXTYVOLPARA2_TARGET_MASK, vol); + + if (cur < vol) + regmap_update_bits(r, OPORTMXTYVOLPARA2(oport_map, 0), + OPORTMXTYVOLPARA2_FADE_MASK, + OPORTMXTYVOLPARA2_FADE_FADEIN); + else + regmap_update_bits(r, OPORTMXTYVOLPARA2(oport_map, 0), + OPORTMXTYVOLPARA2_FADE_MASK, + OPORTMXTYVOLPARA2_FADE_FADEOUT); + + regmap_write(r, AOUTFADECTR0, BIT(oport_map)); +} + +/** + * aio_if_set_param - set parameters of AIO DMA I/F block + * @sub: the AIO substream pointer + * @pass_through: Zero if sound data is LPCM, otherwise if data is not LPCM. + * This parameter has no effect if substream is I2S or PCM. + * + * Set suitable setting to DMA interface block of AIO to process the + * specified in settings. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_if_set_param(struct uniphier_aio_sub *sub, int pass_through) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 memfmt, v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + if (pass_through) { + v = PBOUTMXCTR0_ENDIAN_0123 | + PBOUTMXCTR0_MEMFMT_STREAM; + } else { + switch (params_channels(&sub->params)) { + case 2: + memfmt = PBOUTMXCTR0_MEMFMT_2CH; + break; + case 6: + memfmt = PBOUTMXCTR0_MEMFMT_6CH; + break; + case 8: + memfmt = PBOUTMXCTR0_MEMFMT_8CH; + break; + default: + return -EINVAL; + } + v = PBOUTMXCTR0_ENDIAN_3210 | memfmt; + } + + regmap_write(r, PBOUTMXCTR0(sub->swm->oif.map), v); + regmap_write(r, PBOUTMXCTR1(sub->swm->oif.map), 0); + } else { + regmap_write(r, PBINMXCTR(sub->swm->iif.map), + PBINMXCTR_NCONNECT_CONNECT | + PBINMXCTR_INOUTSEL_IN | + (sub->swm->iport.map << PBINMXCTR_PBINSEL_SHIFT) | + PBINMXCTR_ENDIAN_3210 | + PBINMXCTR_MEMFMT_D0); + } + + return 0; +} + +/** + * aio_oport_set_stream_type - set parameters of AIO playback port block + * @sub: the AIO substream pointer + * @pc: Pc type of IEC61937 + * + * Set special setting to output port block of AIO to output the stream + * via S/PDIF. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_oport_set_stream_type(struct uniphier_aio_sub *sub, + enum IEC61937_PC pc) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 repet = 0, pause = OPORTMXPAUDAT_PAUSEPC_CMN; + + switch (pc) { + case IEC61937_PC_AC3: + repet = OPORTMXREPET_STRLENGTH_AC3 | + OPORTMXREPET_PMLENGTH_AC3; + pause |= OPORTMXPAUDAT_PAUSEPD_AC3; + break; + case IEC61937_PC_MPA: + repet = OPORTMXREPET_STRLENGTH_MPA | + OPORTMXREPET_PMLENGTH_MPA; + pause |= OPORTMXPAUDAT_PAUSEPD_MPA; + break; + case IEC61937_PC_MP3: + repet = OPORTMXREPET_STRLENGTH_MP3 | + OPORTMXREPET_PMLENGTH_MP3; + pause |= OPORTMXPAUDAT_PAUSEPD_MP3; + break; + case IEC61937_PC_DTS1: + repet = OPORTMXREPET_STRLENGTH_DTS1 | + OPORTMXREPET_PMLENGTH_DTS1; + pause |= OPORTMXPAUDAT_PAUSEPD_DTS1; + break; + case IEC61937_PC_DTS2: + repet = OPORTMXREPET_STRLENGTH_DTS2 | + OPORTMXREPET_PMLENGTH_DTS2; + pause |= OPORTMXPAUDAT_PAUSEPD_DTS2; + break; + case IEC61937_PC_DTS3: + repet = OPORTMXREPET_STRLENGTH_DTS3 | + OPORTMXREPET_PMLENGTH_DTS3; + pause |= OPORTMXPAUDAT_PAUSEPD_DTS3; + break; + case IEC61937_PC_AAC: + repet = OPORTMXREPET_STRLENGTH_AAC | + OPORTMXREPET_PMLENGTH_AAC; + pause |= OPORTMXPAUDAT_PAUSEPD_AAC; + break; + case IEC61937_PC_PAUSE: + /* Do nothing */ + break; + } + + regmap_write(r, OPORTMXREPET(sub->swm->oport.map), repet); + regmap_write(r, OPORTMXPAUDAT(sub->swm->oport.map), pause); + + return 0; +} + +/** + * aio_src_reset - reset AIO SRC block + * @sub: the AIO substream pointer + * + * Resets the digital signal input/output port with sampling rate converter + * block of AIO. + * This function has no effect if substream is not supported rate converter. + */ +void aio_src_reset(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir != PORT_DIR_OUTPUT) + return; + + regmap_write(r, AOUTSRCRSTCTR0, BIT(sub->swm->oport.map)); + regmap_write(r, AOUTSRCRSTCTR1, BIT(sub->swm->oport.map)); +} + +/** + * aio_src_set_param - set parameters of AIO SRC block + * @sub: the AIO substream pointer + * @params: hardware parameters of ALSA + * + * Set suitable setting to input/output port with sampling rate converter + * block of AIO to process the specified in params. + * This function has no effect if substream is not supported rate converter. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_src_set_param(struct uniphier_aio_sub *sub, + const struct snd_pcm_hw_params *params) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + if (sub->swm->dir != PORT_DIR_OUTPUT) + return 0; + + regmap_write(r, OPORTMXSRC1CTR(sub->swm->oport.map), + OPORTMXSRC1CTR_THMODE_SRC | + OPORTMXSRC1CTR_SRCPATH_CALC | + OPORTMXSRC1CTR_SYNC_ASYNC | + OPORTMXSRC1CTR_FSIIPSEL_INNER | + OPORTMXSRC1CTR_FSISEL_ACLK); + + switch (params_rate(params)) { + default: + case 48000: + v = OPORTMXRATE_I_ACLKSEL_APLLA1 | + OPORTMXRATE_I_MCKSEL_36 | + OPORTMXRATE_I_FSSEL_48; + break; + case 44100: + v = OPORTMXRATE_I_ACLKSEL_APLLA2 | + OPORTMXRATE_I_MCKSEL_33 | + OPORTMXRATE_I_FSSEL_44_1; + break; + case 32000: + v = OPORTMXRATE_I_ACLKSEL_APLLA1 | + OPORTMXRATE_I_MCKSEL_36 | + OPORTMXRATE_I_FSSEL_32; + break; + } + + regmap_write(r, OPORTMXRATE_I(sub->swm->oport.map), + v | OPORTMXRATE_I_ACLKSRC_APLL | + OPORTMXRATE_I_LRCKSTP_STOP); + regmap_update_bits(r, OPORTMXRATE_I(sub->swm->oport.map), + OPORTMXRATE_I_LRCKSTP_MASK, + OPORTMXRATE_I_LRCKSTP_START); + + return 0; +} + +int aio_srcif_set_param(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + regmap_write(r, PBINMXCTR(sub->swm->iif.map), + PBINMXCTR_NCONNECT_CONNECT | + PBINMXCTR_INOUTSEL_OUT | + (sub->swm->oport.map << PBINMXCTR_PBINSEL_SHIFT) | + PBINMXCTR_ENDIAN_3210 | + PBINMXCTR_MEMFMT_D0); + + return 0; +} + +int aio_srcch_set_param(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + regmap_write(r, CDA2D_CHMXCTRL1(sub->swm->och.map), + CDA2D_CHMXCTRL1_INDSIZE_INFINITE); + + regmap_write(r, CDA2D_CHMXSRCAMODE(sub->swm->och.map), + CDA2D_CHMXAMODE_ENDIAN_3210 | + CDA2D_CHMXAMODE_AUPDT_FIX | + CDA2D_CHMXAMODE_TYPE_NORMAL); + + regmap_write(r, CDA2D_CHMXDSTAMODE(sub->swm->och.map), + CDA2D_CHMXAMODE_ENDIAN_3210 | + CDA2D_CHMXAMODE_AUPDT_INC | + CDA2D_CHMXAMODE_TYPE_RING | + (sub->swm->och.map << CDA2D_CHMXAMODE_RSSEL_SHIFT)); + + return 0; +} + +void aio_srcch_set_enable(struct uniphier_aio_sub *sub, int enable) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + if (enable) + v = CDA2D_STRT0_STOP_START; + else + v = CDA2D_STRT0_STOP_STOP; + + regmap_write(r, CDA2D_STRT0, + v | BIT(sub->swm->och.map)); +} + +int aiodma_ch_set_param(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + regmap_write(r, CDA2D_CHMXCTRL1(sub->swm->ch.map), + CDA2D_CHMXCTRL1_INDSIZE_INFINITE); + + v = CDA2D_CHMXAMODE_ENDIAN_3210 | + CDA2D_CHMXAMODE_AUPDT_INC | + CDA2D_CHMXAMODE_TYPE_NORMAL | + (sub->swm->rb.map << CDA2D_CHMXAMODE_RSSEL_SHIFT); + if (sub->swm->dir == PORT_DIR_OUTPUT) + regmap_write(r, CDA2D_CHMXSRCAMODE(sub->swm->ch.map), v); + else + regmap_write(r, CDA2D_CHMXDSTAMODE(sub->swm->ch.map), v); + + return 0; +} + +void aiodma_ch_set_enable(struct uniphier_aio_sub *sub, int enable) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (enable) { + regmap_write(r, CDA2D_STRT0, + CDA2D_STRT0_STOP_START | BIT(sub->swm->ch.map)); + + regmap_update_bits(r, INTRBIM(0), + BIT(sub->swm->rb.map), + BIT(sub->swm->rb.map)); + } else { + regmap_write(r, CDA2D_STRT0, + CDA2D_STRT0_STOP_STOP | BIT(sub->swm->ch.map)); + + regmap_update_bits(r, INTRBIM(0), + BIT(sub->swm->rb.map), + 0); + } +} + +static u64 aiodma_rb_get_rp(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 pos_u, pos_l; + int i; + + regmap_write(r, CDA2D_RDPTRLOAD, + CDA2D_RDPTRLOAD_LSFLAG_STORE | BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), &pos_l); + + regmap_read(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), &pos_l); + regmap_read(r, CDA2D_RBMXRDPTRU(sub->swm->rb.map), &pos_u); + pos_u = FIELD_GET(CDA2D_RBMXPTRU_PTRU_MASK, pos_u); + + return ((u64)pos_u << 32) | pos_l; +} + +static void aiodma_rb_set_rp(struct uniphier_aio_sub *sub, u64 pos) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 tmp; + int i; + + regmap_write(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), (u32)pos); + regmap_write(r, CDA2D_RBMXRDPTRU(sub->swm->rb.map), (u32)(pos >> 32)); + regmap_write(r, CDA2D_RDPTRLOAD, BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), &tmp); +} + +static u64 aiodma_rb_get_wp(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 pos_u, pos_l; + int i; + + regmap_write(r, CDA2D_WRPTRLOAD, + CDA2D_WRPTRLOAD_LSFLAG_STORE | BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), &pos_l); + + regmap_read(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), &pos_l); + regmap_read(r, CDA2D_RBMXWRPTRU(sub->swm->rb.map), &pos_u); + pos_u = FIELD_GET(CDA2D_RBMXPTRU_PTRU_MASK, pos_u); + + return ((u64)pos_u << 32) | pos_l; +} + +static void aiodma_rb_set_wp(struct uniphier_aio_sub *sub, u64 pos) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 tmp; + int i; + + regmap_write(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), + lower_32_bits(pos)); + regmap_write(r, CDA2D_RBMXWRPTRU(sub->swm->rb.map), + upper_32_bits(pos)); + regmap_write(r, CDA2D_WRPTRLOAD, BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), &tmp); +} + +int aiodma_rb_set_threshold(struct uniphier_aio_sub *sub, u64 size, u32 th) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (size <= th) + return -EINVAL; + + regmap_write(r, CDA2D_RBMXBTH(sub->swm->rb.map), th); + regmap_write(r, CDA2D_RBMXRTH(sub->swm->rb.map), th); + + return 0; +} + +int aiodma_rb_set_buffer(struct uniphier_aio_sub *sub, u64 start, u64 end, + int period) +{ + struct regmap *r = sub->aio->chip->regmap; + u64 size = end - start; + int ret; + + if (end < start || period < 0) + return -EINVAL; + + regmap_write(r, CDA2D_RBMXCNFG(sub->swm->rb.map), 0); + regmap_write(r, CDA2D_RBMXBGNADRS(sub->swm->rb.map), + lower_32_bits(start)); + regmap_write(r, CDA2D_RBMXBGNADRSU(sub->swm->rb.map), + upper_32_bits(start)); + regmap_write(r, CDA2D_RBMXENDADRS(sub->swm->rb.map), + lower_32_bits(end)); + regmap_write(r, CDA2D_RBMXENDADRSU(sub->swm->rb.map), + upper_32_bits(end)); + + regmap_write(r, CDA2D_RBADRSLOAD, BIT(sub->swm->rb.map)); + + ret = aiodma_rb_set_threshold(sub, size, 2 * period); + if (ret) + return ret; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + aiodma_rb_set_rp(sub, start); + aiodma_rb_set_wp(sub, end - period); + + regmap_update_bits(r, CDA2D_RBMXIE(sub->swm->rb.map), + CDA2D_RBMXIX_SPACE, + CDA2D_RBMXIX_SPACE); + } else { + aiodma_rb_set_rp(sub, end - period); + aiodma_rb_set_wp(sub, start); + + regmap_update_bits(r, CDA2D_RBMXIE(sub->swm->rb.map), + CDA2D_RBMXIX_REMAIN, + CDA2D_RBMXIX_REMAIN); + } + + sub->threshold = 2 * period; + sub->rd_offs = 0; + sub->wr_offs = 0; + sub->rd_org = 0; + sub->wr_org = 0; + sub->rd_total = 0; + sub->wr_total = 0; + + return 0; +} + +void aiodma_rb_sync(struct uniphier_aio_sub *sub, u64 start, u64 size, + int period) +{ + if (sub->swm->dir == PORT_DIR_OUTPUT) { + sub->rd_offs = aiodma_rb_get_rp(sub) - start; + + if (sub->use_mmap) { + sub->threshold = 2 * period; + aiodma_rb_set_threshold(sub, size, 2 * period); + + sub->wr_offs = sub->rd_offs - period; + if (sub->rd_offs < period) + sub->wr_offs += size; + } + aiodma_rb_set_wp(sub, sub->wr_offs + start); + } else { + sub->wr_offs = aiodma_rb_get_wp(sub) - start; + + if (sub->use_mmap) { + sub->threshold = 2 * period; + aiodma_rb_set_threshold(sub, size, 2 * period); + + sub->rd_offs = sub->wr_offs - period; + if (sub->wr_offs < period) + sub->rd_offs += size; + } + aiodma_rb_set_rp(sub, sub->rd_offs + start); + } + + sub->rd_total += sub->rd_offs - sub->rd_org; + if (sub->rd_offs < sub->rd_org) + sub->rd_total += size; + sub->wr_total += sub->wr_offs - sub->wr_org; + if (sub->wr_offs < sub->wr_org) + sub->wr_total += size; + + sub->rd_org = sub->rd_offs; + sub->wr_org = sub->wr_offs; +} + +bool aiodma_rb_is_irq(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 ir; + + regmap_read(r, CDA2D_RBMXIR(sub->swm->rb.map), &ir); + + if (sub->swm->dir == PORT_DIR_OUTPUT) + return !!(ir & CDA2D_RBMXIX_SPACE); + else + return !!(ir & CDA2D_RBMXIX_REMAIN); +} + +void aiodma_rb_clear_irq(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir == PORT_DIR_OUTPUT) + regmap_write(r, CDA2D_RBMXIR(sub->swm->rb.map), + CDA2D_RBMXIX_SPACE); + else + regmap_write(r, CDA2D_RBMXIR(sub->swm->rb.map), + CDA2D_RBMXIX_REMAIN); +} diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c new file mode 100644 index 000000000..2ae582a99 --- /dev/null +++ b/sound/soc/uniphier/aio-cpu.c @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA CPU DAI driver. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aio.h" + +static bool is_valid_pll(struct uniphier_aio_chip *chip, int pll_id) +{ + struct device *dev = &chip->pdev->dev; + + if (pll_id < 0 || chip->num_plls <= pll_id) { + dev_err(dev, "PLL(%d) is not supported\n", pll_id); + return false; + } + + return chip->plls[pll_id].enable; +} + +/** + * find_volume - find volume supported HW port by HW port number + * @chip: the AIO chip pointer + * @oport_hw: HW port number, one of AUD_HW_XXXX + * + * Find AIO device from device list by HW port number. Volume feature is + * available only in Output and PCM ports, this limitation comes from HW + * specifications. + * + * Return: The pointer of AIO substream if successful, otherwise NULL on error. + */ +static struct uniphier_aio_sub *find_volume(struct uniphier_aio_chip *chip, + int oport_hw) +{ + int i; + + for (i = 0; i < chip->num_aios; i++) { + struct uniphier_aio_sub *sub = &chip->aios[i].sub[0]; + + if (!sub->swm) + continue; + + if (sub->swm->oport.hw == oport_hw) + return sub; + } + + return NULL; +} + +static bool match_spec(const struct uniphier_aio_spec *spec, + const char *name, int dir) +{ + if (dir == SNDRV_PCM_STREAM_PLAYBACK && + spec->swm.dir != PORT_DIR_OUTPUT) { + return false; + } + + if (dir == SNDRV_PCM_STREAM_CAPTURE && + spec->swm.dir != PORT_DIR_INPUT) { + return false; + } + + if (spec->name && strcmp(spec->name, name) == 0) + return true; + + if (spec->gname && strcmp(spec->gname, name) == 0) + return true; + + return false; +} + +/** + * find_spec - find HW specification info by name + * @aio: the AIO device pointer + * @name: name of device + * @direction: the direction of substream, SNDRV_PCM_STREAM_* + * + * Find hardware specification information from list by device name. This + * information is used for telling the difference of SoCs to driver. + * + * Specification list is array of 'struct uniphier_aio_spec' which is defined + * in each drivers (see: aio-i2s.c). + * + * Return: The pointer of hardware specification of AIO if successful, + * otherwise NULL on error. + */ +static const struct uniphier_aio_spec *find_spec(struct uniphier_aio *aio, + const char *name, + int direction) +{ + const struct uniphier_aio_chip_spec *chip_spec = aio->chip->chip_spec; + int i; + + for (i = 0; i < chip_spec->num_specs; i++) { + const struct uniphier_aio_spec *spec = &chip_spec->specs[i]; + + if (match_spec(spec, name, direction)) + return spec; + } + + return NULL; +} + +/** + * find_divider - find clock divider by frequency + * @aio: the AIO device pointer + * @pll_id: PLL ID, should be AUD_PLL_XX + * @freq: required frequency + * + * Find suitable clock divider by frequency. + * + * Return: The ID of PLL if successful, otherwise negative error value. + */ +static int find_divider(struct uniphier_aio *aio, int pll_id, unsigned int freq) +{ + struct uniphier_aio_pll *pll; + int mul[] = { 1, 1, 1, 2, }; + int div[] = { 2, 3, 1, 3, }; + int i; + + if (!is_valid_pll(aio->chip, pll_id)) + return -EINVAL; + + pll = &aio->chip->plls[pll_id]; + for (i = 0; i < ARRAY_SIZE(mul); i++) + if (pll->freq * mul[i] / div[i] == freq) + return i; + + return -ENOTSUPP; +} + +static int uniphier_aio_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct device *dev = &aio->chip->pdev->dev; + bool pll_auto = false; + int pll_id, div_id; + + switch (clk_id) { + case AUD_CLK_IO: + return -ENOTSUPP; + case AUD_CLK_A1: + pll_id = AUD_PLL_A1; + break; + case AUD_CLK_F1: + pll_id = AUD_PLL_F1; + break; + case AUD_CLK_A2: + pll_id = AUD_PLL_A2; + break; + case AUD_CLK_F2: + pll_id = AUD_PLL_F2; + break; + case AUD_CLK_A: + pll_id = AUD_PLL_A1; + pll_auto = true; + break; + case AUD_CLK_F: + pll_id = AUD_PLL_F1; + pll_auto = true; + break; + case AUD_CLK_APLL: + pll_id = AUD_PLL_APLL; + break; + case AUD_CLK_RX0: + pll_id = AUD_PLL_RX0; + break; + case AUD_CLK_USB0: + pll_id = AUD_PLL_USB0; + break; + case AUD_CLK_HSC0: + pll_id = AUD_PLL_HSC0; + break; + default: + dev_err(dev, "Sysclk(%d) is not supported\n", clk_id); + return -EINVAL; + } + + if (pll_auto) { + for (pll_id = 0; pll_id < aio->chip->num_plls; pll_id++) { + div_id = find_divider(aio, pll_id, freq); + if (div_id >= 0) { + aio->plldiv = div_id; + break; + } + } + if (pll_id == aio->chip->num_plls) { + dev_err(dev, "Sysclk frequency is not supported(%d)\n", + freq); + return -EINVAL; + } + } + + if (dir == SND_SOC_CLOCK_OUT) + aio->pll_out = pll_id; + else + aio->pll_in = pll_id; + + return 0; +} + +static int uniphier_aio_set_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + int ret; + + if (!is_valid_pll(aio->chip, pll_id)) + return -EINVAL; + + ret = aio_chip_set_pll(aio->chip, pll_id, freq_out); + if (ret < 0) + return ret; + + return 0; +} + +static int uniphier_aio_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct device *dev = &aio->chip->pdev->dev; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_I2S: + aio->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(dev, "Format is not supported(%d)\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + return 0; +} + +static int uniphier_aio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int ret; + + sub->substream = substream; + sub->pass_through = 0; + sub->use_mmap = true; + + ret = aio_init(sub); + if (ret) + return ret; + + return 0; +} + +static void uniphier_aio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + + sub->substream = NULL; +} + +static int uniphier_aio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + struct device *dev = &aio->chip->pdev->dev; + int freq, ret; + + switch (params_rate(params)) { + case 48000: + case 32000: + case 24000: + freq = 12288000; + break; + case 44100: + case 22050: + freq = 11289600; + break; + default: + dev_err(dev, "Rate is not supported(%d)\n", + params_rate(params)); + return -EINVAL; + } + ret = snd_soc_dai_set_sysclk(dai, AUD_CLK_A, + freq, SND_SOC_CLOCK_OUT); + if (ret) + return ret; + + sub->params = *params; + sub->setting = 1; + + aio_port_reset(sub); + aio_port_set_volume(sub, sub->vol); + aio_src_reset(sub); + + return 0; +} + +static int uniphier_aio_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + + sub->setting = 0; + + return 0; +} + +static int uniphier_aio_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int ret; + + ret = aio_port_set_param(sub, sub->pass_through, &sub->params); + if (ret) + return ret; + ret = aio_src_set_param(sub, &sub->params); + if (ret) + return ret; + aio_port_set_enable(sub, 1); + + ret = aio_if_set_param(sub, sub->pass_through); + if (ret) + return ret; + + if (sub->swm->type == PORT_TYPE_CONV) { + ret = aio_srcif_set_param(sub); + if (ret) + return ret; + ret = aio_srcch_set_param(sub); + if (ret) + return ret; + aio_srcch_set_enable(sub, 1); + } + + return 0; +} + +const struct snd_soc_dai_ops uniphier_aio_i2s_ops = { + .set_sysclk = uniphier_aio_set_sysclk, + .set_pll = uniphier_aio_set_pll, + .set_fmt = uniphier_aio_set_fmt, + .startup = uniphier_aio_startup, + .shutdown = uniphier_aio_shutdown, + .hw_params = uniphier_aio_hw_params, + .hw_free = uniphier_aio_hw_free, + .prepare = uniphier_aio_prepare, +}; +EXPORT_SYMBOL_GPL(uniphier_aio_i2s_ops); + +const struct snd_soc_dai_ops uniphier_aio_spdif_ops = { + .set_sysclk = uniphier_aio_set_sysclk, + .set_pll = uniphier_aio_set_pll, + .startup = uniphier_aio_startup, + .shutdown = uniphier_aio_shutdown, + .hw_params = uniphier_aio_hw_params, + .hw_free = uniphier_aio_hw_free, + .prepare = uniphier_aio_prepare, +}; +EXPORT_SYMBOL_GPL(uniphier_aio_spdif_ops); + +int uniphier_aio_dai_probe(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + int i; + + for (i = 0; i < ARRAY_SIZE(aio->sub); i++) { + struct uniphier_aio_sub *sub = &aio->sub[i]; + const struct uniphier_aio_spec *spec; + + spec = find_spec(aio, dai->name, i); + if (!spec) + continue; + + sub->swm = &spec->swm; + sub->spec = spec; + + sub->vol = AUD_VOL_INIT; + } + + aio_iecout_set_enable(aio->chip, true); + aio_chip_init(aio->chip); + aio->chip->active = 1; + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_probe); + +int uniphier_aio_dai_remove(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + + aio->chip->active = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_remove); + +int uniphier_aio_dai_suspend(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + + aio->chip->num_wup_aios--; + if (!aio->chip->num_wup_aios) { + reset_control_assert(aio->chip->rst); + clk_disable_unprepare(aio->chip->clk); + } + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_suspend); + +int uniphier_aio_dai_resume(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + int ret, i; + + if (!aio->chip->active) + return 0; + + if (!aio->chip->num_wup_aios) { + ret = clk_prepare_enable(aio->chip->clk); + if (ret) + return ret; + + ret = reset_control_deassert(aio->chip->rst); + if (ret) + goto err_out_clock; + } + + aio_iecout_set_enable(aio->chip, true); + aio_chip_init(aio->chip); + + for (i = 0; i < ARRAY_SIZE(aio->sub); i++) { + struct uniphier_aio_sub *sub = &aio->sub[i]; + + if (!sub->spec || !sub->substream) + continue; + + ret = aio_init(sub); + if (ret) + goto err_out_reset; + + if (!sub->setting) + continue; + + aio_port_reset(sub); + aio_src_reset(sub); + } + aio->chip->num_wup_aios++; + + return 0; + +err_out_reset: + if (!aio->chip->num_wup_aios) + reset_control_assert(aio->chip->rst); +err_out_clock: + if (!aio->chip->num_wup_aios) + clk_disable_unprepare(aio->chip->clk); + + return ret; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_resume); + +static int uniphier_aio_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 = AUD_VOL_MAX; + + return 0; +} + +static int uniphier_aio_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct uniphier_aio_chip *chip = snd_soc_component_get_drvdata(comp); + struct uniphier_aio_sub *sub; + int oport_hw = kcontrol->private_value; + + sub = find_volume(chip, oport_hw); + if (!sub) + return 0; + + ucontrol->value.integer.value[0] = sub->vol; + + return 0; +} + +static int uniphier_aio_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct uniphier_aio_chip *chip = snd_soc_component_get_drvdata(comp); + struct uniphier_aio_sub *sub; + int oport_hw = kcontrol->private_value; + + sub = find_volume(chip, oport_hw); + if (!sub) + return 0; + + if (sub->vol == ucontrol->value.integer.value[0]) + return 0; + sub->vol = ucontrol->value.integer.value[0]; + + aio_port_set_volume(sub, sub->vol); + + return 0; +} + +static const struct snd_kcontrol_new uniphier_aio_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "HPCMOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_HPCMOUT1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "PCMOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_PCMOUT1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "PCMOUT2 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_PCMOUT2, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "PCMOUT3 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_PCMOUT3, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "HIECOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_HIECOUT1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "IECOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_IECOUT1, + }, +}; + +static const struct snd_soc_component_driver uniphier_aio_component = { + .name = "uniphier-aio", + .controls = uniphier_aio_controls, + .num_controls = ARRAY_SIZE(uniphier_aio_controls), +}; + +int uniphier_aio_probe(struct platform_device *pdev) +{ + struct uniphier_aio_chip *chip; + struct device *dev = &pdev->dev; + int ret, i, j; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->chip_spec = of_device_get_match_data(dev); + if (!chip->chip_spec) + return -EINVAL; + + chip->regmap_sg = syscon_regmap_lookup_by_phandle(dev->of_node, + "socionext,syscon"); + if (IS_ERR(chip->regmap_sg)) { + if (PTR_ERR(chip->regmap_sg) == -EPROBE_DEFER) + return -EPROBE_DEFER; + chip->regmap_sg = NULL; + } + + chip->clk = devm_clk_get(dev, "aio"); + if (IS_ERR(chip->clk)) + return PTR_ERR(chip->clk); + + chip->rst = devm_reset_control_get_shared(dev, "aio"); + if (IS_ERR(chip->rst)) + return PTR_ERR(chip->rst); + + chip->num_aios = chip->chip_spec->num_dais; + chip->num_wup_aios = chip->num_aios; + chip->aios = devm_kcalloc(dev, + chip->num_aios, sizeof(struct uniphier_aio), + GFP_KERNEL); + if (!chip->aios) + return -ENOMEM; + + chip->num_plls = chip->chip_spec->num_plls; + chip->plls = devm_kcalloc(dev, + chip->num_plls, + sizeof(struct uniphier_aio_pll), + GFP_KERNEL); + if (!chip->plls) + return -ENOMEM; + memcpy(chip->plls, chip->chip_spec->plls, + sizeof(struct uniphier_aio_pll) * chip->num_plls); + + for (i = 0; i < chip->num_aios; i++) { + struct uniphier_aio *aio = &chip->aios[i]; + + aio->chip = chip; + aio->fmt = SND_SOC_DAIFMT_I2S; + + for (j = 0; j < ARRAY_SIZE(aio->sub); j++) { + struct uniphier_aio_sub *sub = &aio->sub[j]; + + sub->aio = aio; + spin_lock_init(&sub->lock); + } + } + + chip->pdev = pdev; + platform_set_drvdata(pdev, chip); + + ret = clk_prepare_enable(chip->clk); + if (ret) + return ret; + + ret = reset_control_deassert(chip->rst); + if (ret) + goto err_out_clock; + + ret = devm_snd_soc_register_component(dev, &uniphier_aio_component, + chip->chip_spec->dais, + chip->chip_spec->num_dais); + if (ret) { + dev_err(dev, "Register component failed.\n"); + goto err_out_reset; + } + + ret = uniphier_aiodma_soc_register_platform(pdev); + if (ret) { + dev_err(dev, "Register platform failed.\n"); + goto err_out_reset; + } + + return 0; + +err_out_reset: + reset_control_assert(chip->rst); + +err_out_clock: + clk_disable_unprepare(chip->clk); + + return ret; +} +EXPORT_SYMBOL_GPL(uniphier_aio_probe); + +int uniphier_aio_remove(struct platform_device *pdev) +{ + struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + + reset_control_assert(chip->rst); + clk_disable_unprepare(chip->clk); + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_remove); + +MODULE_AUTHOR("Katsuhiro Suzuki "); +MODULE_DESCRIPTION("UniPhier AIO CPU DAI driver."); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/uniphier/aio-dma.c b/sound/soc/uniphier/aio-dma.c new file mode 100644 index 000000000..4ec6b65bf --- /dev/null +++ b/sound/soc/uniphier/aio-dma.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO DMA driver. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include +#include +#include +#include +#include +#include +#include + +#include "aio.h" + +static struct snd_pcm_hardware uniphier_aiodma_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .period_bytes_min = 256, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 1024, + .buffer_bytes_max = 128 * 1024, +}; + +static void aiodma_pcm_irq(struct uniphier_aio_sub *sub) +{ + struct snd_pcm_runtime *runtime = sub->substream->runtime; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + int ret; + + spin_lock(&sub->lock); + ret = aiodma_rb_set_threshold(sub, runtime->dma_bytes, + sub->threshold + bytes); + if (!ret) + sub->threshold += bytes; + + aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, bytes); + aiodma_rb_clear_irq(sub); + spin_unlock(&sub->lock); + + snd_pcm_period_elapsed(sub->substream); +} + +static void aiodma_compr_irq(struct uniphier_aio_sub *sub) +{ + struct snd_compr_runtime *runtime = sub->cstream->runtime; + int bytes = runtime->fragment_size; + int ret; + + spin_lock(&sub->lock); + ret = aiodma_rb_set_threshold(sub, sub->compr_bytes, + sub->threshold + bytes); + if (!ret) + sub->threshold += bytes; + + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + aiodma_rb_clear_irq(sub); + spin_unlock(&sub->lock); + + snd_compr_fragment_elapsed(sub->cstream); +} + +static irqreturn_t aiodma_irq(int irq, void *p) +{ + struct platform_device *pdev = p; + struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + irqreturn_t ret = IRQ_NONE; + int i, j; + + for (i = 0; i < chip->num_aios; i++) { + struct uniphier_aio *aio = &chip->aios[i]; + + for (j = 0; j < ARRAY_SIZE(aio->sub); j++) { + struct uniphier_aio_sub *sub = &aio->sub[j]; + + /* Skip channel that does not trigger */ + if (!sub->running || !aiodma_rb_is_irq(sub)) + continue; + + if (sub->substream) + aiodma_pcm_irq(sub); + if (sub->cstream) + aiodma_compr_irq(sub); + + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static int uniphier_aiodma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_set_runtime_hwparams(substream, &uniphier_aiodma_hw); + + return snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256); +} + +static int uniphier_aiodma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + substream->runtime->dma_bytes = params_buffer_bytes(params); + + return 0; +} + +static int uniphier_aiodma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + substream->runtime->dma_bytes = 0; + + return 0; +} + +static int uniphier_aiodma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + unsigned long flags; + int ret; + + ret = aiodma_ch_set_param(sub); + if (ret) + return ret; + + spin_lock_irqsave(&sub->lock, flags); + ret = aiodma_rb_set_buffer(sub, runtime->dma_addr, + runtime->dma_addr + runtime->dma_bytes, + bytes); + spin_unlock_irqrestore(&sub->lock, flags); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aiodma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + struct device *dev = &aio->chip->pdev->dev; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + unsigned long flags; + + spin_lock_irqsave(&sub->lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, + bytes); + aiodma_ch_set_enable(sub, 1); + sub->running = 1; + + break; + case SNDRV_PCM_TRIGGER_STOP: + sub->running = 0; + aiodma_ch_set_enable(sub, 0); + + break; + default: + dev_warn(dev, "Unknown trigger(%d) ignored\n", cmd); + break; + } + spin_unlock_irqrestore(&sub->lock, flags); + + return 0; +} + +static snd_pcm_uframes_t uniphier_aiodma_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + unsigned long flags; + snd_pcm_uframes_t pos; + + spin_lock_irqsave(&sub->lock, flags); + aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, bytes); + + if (sub->swm->dir == PORT_DIR_OUTPUT) + pos = bytes_to_frames(runtime, sub->rd_offs); + else + pos = bytes_to_frames(runtime, sub->wr_offs); + spin_unlock_irqrestore(&sub->lock, flags); + + return pos; +} + +static int uniphier_aiodma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static const struct snd_pcm_ops uniphier_aiodma_ops = { + .open = uniphier_aiodma_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = uniphier_aiodma_hw_params, + .hw_free = uniphier_aiodma_hw_free, + .prepare = uniphier_aiodma_prepare, + .trigger = uniphier_aiodma_trigger, + .pointer = uniphier_aiodma_pointer, + .mmap = uniphier_aiodma_mmap, +}; + +static int uniphier_aiodma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct device *dev = rtd->card->snd_card->dev; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(33)); + if (ret) + return ret; + + return snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, dev, + uniphier_aiodma_hw.buffer_bytes_max, + uniphier_aiodma_hw.buffer_bytes_max); +} + +static void uniphier_aiodma_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static const struct snd_soc_component_driver uniphier_soc_platform = { + .pcm_new = uniphier_aiodma_new, + .pcm_free = uniphier_aiodma_free, + .ops = &uniphier_aiodma_ops, + .compr_ops = &uniphier_aio_compr_ops, +}; + +static const struct regmap_config aiodma_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x7fffc, + .cache_type = REGCACHE_NONE, +}; + +/** + * uniphier_aiodma_soc_register_platform - register the AIO DMA + * @pdev: the platform device + * + * Register and setup the DMA of AIO to transfer the sound data to device. + * This function need to call once at driver startup and need NOT to call + * unregister function. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int uniphier_aiodma_soc_register_platform(struct platform_device *pdev) +{ + struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *preg; + int irq, ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + preg = devm_ioremap_resource(dev, res); + if (IS_ERR(preg)) + return PTR_ERR(preg); + + chip->regmap = devm_regmap_init_mmio(dev, preg, + &aiodma_regmap_config); + if (IS_ERR(chip->regmap)) + return PTR_ERR(chip->regmap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "Could not get irq.\n"); + return irq; + } + + ret = devm_request_irq(dev, irq, aiodma_irq, + IRQF_SHARED, dev_name(dev), pdev); + if (ret) + return ret; + + return devm_snd_soc_register_component(dev, &uniphier_soc_platform, + NULL, 0); +} +EXPORT_SYMBOL_GPL(uniphier_aiodma_soc_register_platform); diff --git a/sound/soc/uniphier/aio-ld11.c b/sound/soc/uniphier/aio-ld11.c new file mode 100644 index 000000000..de962df24 --- /dev/null +++ b/sound/soc/uniphier/aio-ld11.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA driver for LD11/LD20. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include + +#include "aio.h" + +static const struct uniphier_aio_spec uniphier_aio_ld11[] = { + /* for HDMI PCM In, Pin:AI1Dx */ + { + .name = AUD_NAME_PCMIN1, + .gname = AUD_GNAME_HDMI, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 21, 14, }, + .ch = { 21, 14, }, + .iif = { 5, 3, }, + .iport = { 0, AUD_HW_PCMIN1, }, + }, + }, + + /* for SIF In, Pin:AI2Dx */ + { + .name = AUD_NAME_PCMIN2, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 22, 15, }, + .ch = { 22, 15, }, + .iif = { 6, 4, }, + .iport = { 1, AUD_HW_PCMIN2, }, + }, + }, + + /* for Line In, Pin:AI3Dx */ + { + .name = AUD_NAME_PCMIN3, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_EVE, + .dir = PORT_DIR_INPUT, + .rb = { 23, 16, }, + .ch = { 23, 16, }, + .iif = { 7, 5, }, + .iport = { 2, AUD_HW_PCMIN3, }, + }, + }, + + /* for S/PDIF In, Pin:AI1IEC */ + { + .name = AUD_NAME_IECIN1, + .gname = AUD_GNAME_IEC, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_INPUT, + .rb = { 26, 17, }, + .ch = { 26, 17, }, + .iif = { 10, 6, }, + .iport = { 3, AUD_HW_IECIN1, }, + }, + }, + + /* for Speaker, Pin:AO1Dx */ + { + .name = AUD_NAME_HPCMOUT1, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 0, 0, }, + .ch = { 0, 0, }, + .oif = { 0, 0, }, + .oport = { 0, AUD_HW_HPCMOUT1, }, + }, + }, + + /* for HDMI PCM, Pin:AO2Dx */ + { + .name = AUD_NAME_PCMOUT1, + .gname = AUD_GNAME_HDMI, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 0, 0, }, + .ch = { 0, 0, }, + .oif = { 0, 0, }, + .oport = { 3, AUD_HW_PCMOUT1, }, + }, + }, + + /* for Line Out, Pin:LO2_x */ + { + .name = AUD_NAME_PCMOUT2, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_EVE, + .dir = PORT_DIR_OUTPUT, + .rb = { 2, 2, }, + .ch = { 2, 2, }, + .oif = { 2, 2, }, + .oport = { 1, AUD_HW_PCMOUT2, }, + }, + }, + + /* for Headphone, Pin:HP1_x */ + { + .name = AUD_NAME_PCMOUT3, + .swm = { + .type = PORT_TYPE_EVE, + .dir = PORT_DIR_OUTPUT, + .rb = { 3, 3, }, + .ch = { 3, 3, }, + .oif = { 3, 3, }, + .oport = { 2, AUD_HW_PCMOUT3, }, + }, + }, + + /* for HW Sampling Rate Converter */ + { + .name = AUD_NAME_EPCMOUT2, + .swm = { + .type = PORT_TYPE_CONV, + .dir = PORT_DIR_OUTPUT, + .rb = { 7, 5, }, + .ch = { 7, 5, }, + .oif = { 7, 5, }, + .oport = { 6, AUD_HW_EPCMOUT2, }, + .och = { 17, 12, }, + .iif = { 1, 1, }, + }, + }, + + /* for HW Sampling Rate Converter 2 */ + { + .name = AUD_NAME_EPCMOUT3, + .swm = { + .type = PORT_TYPE_CONV, + .dir = PORT_DIR_OUTPUT, + .rb = { 8, 6, }, + .ch = { 8, 6, }, + .oif = { 8, 6, }, + .oport = { 7, AUD_HW_EPCMOUT3, }, + .och = { 18, 13, }, + .iif = { 2, 2, }, + }, + }, + + /* for S/PDIF Out, Pin:AO1IEC */ + { + .name = AUD_NAME_HIECOUT1, + .gname = AUD_GNAME_IEC, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 1, 1, }, + .ch = { 1, 1, }, + .oif = { 1, 1, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, + + /* for S/PDIF Out, Pin:AO1IEC, Compress */ + { + .name = AUD_NAME_HIECCOMPOUT1, + .gname = AUD_GNAME_IEC, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 1, 1, }, + .ch = { 1, 1, }, + .oif = { 1, 1, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, +}; + +static const struct uniphier_aio_pll uniphier_aio_pll_ld11[] = { + [AUD_PLL_A1] = { .enable = true, }, + [AUD_PLL_F1] = { .enable = true, }, + [AUD_PLL_A2] = { .enable = true, }, + [AUD_PLL_F2] = { .enable = true, }, + [AUD_PLL_APLL] = { .enable = true, }, + [AUD_PLL_RX0] = { .enable = true, }, + [AUD_PLL_USB0] = { .enable = true, }, + [AUD_PLL_HSC0] = { .enable = true, }, +}; + +static int uniphier_aio_ld11_probe(struct snd_soc_dai *dai) +{ + int ret; + + ret = uniphier_aio_dai_probe(dai); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A1, 0, 0, 36864000); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F1, 0, 0, 36864000); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A2, 0, 0, 33868800); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F2, 0, 0, 33868800); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_dai_driver uniphier_aio_dai_ld11[] = { + { + .name = AUD_GNAME_HDMI, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_PCMIN2, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .capture = { + .stream_name = AUD_NAME_PCMIN2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_GNAME_LINE, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN3, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HPCMOUT1, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HPCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 8, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_PCMOUT3, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT3, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HIECOUT1, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HIECOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_EPCMOUT2, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_EPCMOUT2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_EPCMOUT3, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_EPCMOUT3, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HIECCOMPOUT1, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .compress_new = snd_soc_new_compress, + .playback = { + .stream_name = AUD_NAME_HIECCOMPOUT1, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &uniphier_aio_spdif_ops, + }, +}; + +static const struct uniphier_aio_chip_spec uniphier_aio_ld11_spec = { + .specs = uniphier_aio_ld11, + .num_specs = ARRAY_SIZE(uniphier_aio_ld11), + .dais = uniphier_aio_dai_ld11, + .num_dais = ARRAY_SIZE(uniphier_aio_dai_ld11), + .plls = uniphier_aio_pll_ld11, + .num_plls = ARRAY_SIZE(uniphier_aio_pll_ld11), + .addr_ext = 0, +}; + +static const struct uniphier_aio_chip_spec uniphier_aio_ld20_spec = { + .specs = uniphier_aio_ld11, + .num_specs = ARRAY_SIZE(uniphier_aio_ld11), + .dais = uniphier_aio_dai_ld11, + .num_dais = ARRAY_SIZE(uniphier_aio_dai_ld11), + .plls = uniphier_aio_pll_ld11, + .num_plls = ARRAY_SIZE(uniphier_aio_pll_ld11), + .addr_ext = 1, +}; + +static const struct of_device_id uniphier_aio_of_match[] = { + { + .compatible = "socionext,uniphier-ld11-aio", + .data = &uniphier_aio_ld11_spec, + }, + { + .compatible = "socionext,uniphier-ld20-aio", + .data = &uniphier_aio_ld20_spec, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, uniphier_aio_of_match); + +static struct platform_driver uniphier_aio_driver = { + .driver = { + .name = "snd-uniphier-aio-ld11", + .of_match_table = of_match_ptr(uniphier_aio_of_match), + }, + .probe = uniphier_aio_probe, + .remove = uniphier_aio_remove, +}; +module_platform_driver(uniphier_aio_driver); + +MODULE_AUTHOR("Katsuhiro Suzuki "); +MODULE_DESCRIPTION("UniPhier LD11/LD20 AIO driver."); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/uniphier/aio-pxs2.c b/sound/soc/uniphier/aio-pxs2.c new file mode 100644 index 000000000..69cd5b0af --- /dev/null +++ b/sound/soc/uniphier/aio-pxs2.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA driver for PXs2. +// +// Copyright (c) 2018 Socionext Inc. + +#include + +#include "aio.h" + +static const struct uniphier_aio_spec uniphier_aio_pxs2[] = { + /* for Line PCM In, Pin:AI1Dx */ + { + .name = AUD_NAME_PCMIN1, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 16, 11, }, + .ch = { 16, 11, }, + .iif = { 0, 0, }, + .iport = { 0, AUD_HW_PCMIN1, }, + }, + }, + + /* for Speaker/Headphone/Mic PCM In, Pin:AI2Dx */ + { + .name = AUD_NAME_PCMIN2, + .gname = AUD_GNAME_AUX, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 17, 12, }, + .ch = { 17, 12, }, + .iif = { 1, 1, }, + .iport = { 1, AUD_HW_PCMIN2, }, + }, + }, + + /* for HDMI PCM Out, Pin:AO1Dx (inner) */ + { + .name = AUD_NAME_HPCMOUT1, + .gname = AUD_GNAME_HDMI, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 0, 0, }, + .ch = { 0, 0, }, + .oif = { 0, 0, }, + .oport = { 3, AUD_HW_HPCMOUT1, }, + }, + }, + + /* for Line PCM Out, Pin:AO2Dx */ + { + .name = AUD_NAME_PCMOUT1, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 1, 1, }, + .ch = { 1, 1, }, + .oif = { 1, 1, }, + .oport = { 0, AUD_HW_PCMOUT1, }, + }, + }, + + /* for Speaker/Headphone/Mic PCM Out, Pin:AO3Dx */ + { + .name = AUD_NAME_PCMOUT2, + .gname = AUD_GNAME_AUX, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 2, 2, }, + .ch = { 2, 2, }, + .oif = { 2, 2, }, + .oport = { 1, AUD_HW_PCMOUT2, }, + }, + }, + + /* for HDMI Out, Pin:AO1IEC */ + { + .name = AUD_NAME_HIECOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 6, 4, }, + .ch = { 6, 4, }, + .oif = { 6, 4, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, + + /* for HDMI Out, Pin:AO1IEC, Compress */ + { + .name = AUD_NAME_HIECCOMPOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 6, 4, }, + .ch = { 6, 4, }, + .oif = { 6, 4, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, + + /* for S/PDIF Out, Pin:AO2IEC */ + { + .name = AUD_NAME_IECOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 7, 5, }, + .ch = { 7, 5, }, + .oif = { 7, 5, }, + .oport = { 13, AUD_HW_IECOUT1, }, + }, + }, + + /* for S/PDIF Out, Pin:AO2IEC */ + { + .name = AUD_NAME_IECCOMPOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 7, 5, }, + .ch = { 7, 5, }, + .oif = { 7, 5, }, + .oport = { 13, AUD_HW_IECOUT1, }, + }, + }, +}; + +static const struct uniphier_aio_pll uniphier_aio_pll_pxs2[] = { + [AUD_PLL_A1] = { .enable = true, }, + [AUD_PLL_F1] = { .enable = true, }, + [AUD_PLL_A2] = { .enable = true, }, + [AUD_PLL_F2] = { .enable = true, }, + [AUD_PLL_APLL] = { .enable = true, }, + [AUD_PLL_HSC0] = { .enable = true, }, +}; + +static int uniphier_aio_pxs2_probe(struct snd_soc_dai *dai) +{ + int ret; + + ret = uniphier_aio_dai_probe(dai); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A1, 0, 0, 36864000); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F1, 0, 0, 36864000); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A2, 0, 0, 33868800); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F2, 0, 0, 33868800); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_dai_driver uniphier_aio_dai_pxs2[] = { + { + .name = AUD_GNAME_HDMI, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HPCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_GNAME_LINE, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_GNAME_AUX, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HIECOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HIECOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_IECOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_IECOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_HIECCOMPOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .compress_new = snd_soc_new_compress, + .playback = { + .stream_name = AUD_NAME_HIECCOMPOUT1, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_IECCOMPOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .compress_new = snd_soc_new_compress, + .playback = { + .stream_name = AUD_NAME_IECCOMPOUT1, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &uniphier_aio_spdif_ops, + }, +}; + +static const struct uniphier_aio_chip_spec uniphier_aio_pxs2_spec = { + .specs = uniphier_aio_pxs2, + .num_specs = ARRAY_SIZE(uniphier_aio_pxs2), + .dais = uniphier_aio_dai_pxs2, + .num_dais = ARRAY_SIZE(uniphier_aio_dai_pxs2), + .plls = uniphier_aio_pll_pxs2, + .num_plls = ARRAY_SIZE(uniphier_aio_pll_pxs2), + .addr_ext = 0, +}; + +static const struct of_device_id uniphier_aio_of_match[] = { + { + .compatible = "socionext,uniphier-pxs2-aio", + .data = &uniphier_aio_pxs2_spec, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, uniphier_aio_of_match); + +static struct platform_driver uniphier_aio_driver = { + .driver = { + .name = "snd-uniphier-aio-pxs2", + .of_match_table = of_match_ptr(uniphier_aio_of_match), + }, + .probe = uniphier_aio_probe, + .remove = uniphier_aio_remove, +}; +module_platform_driver(uniphier_aio_driver); + +MODULE_AUTHOR("Katsuhiro Suzuki "); +MODULE_DESCRIPTION("UniPhier PXs2 AIO driver."); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/uniphier/aio-reg.h b/sound/soc/uniphier/aio-reg.h new file mode 100644 index 000000000..734395dbc --- /dev/null +++ b/sound/soc/uniphier/aio-reg.h @@ -0,0 +1,476 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Socionext UniPhier AIO ALSA driver. + * + * Copyright (c) 2016-2018 Socionext Inc. + */ + +#ifndef SND_UNIPHIER_AIO_REG_H__ +#define SND_UNIPHIER_AIO_REG_H__ + +#include + +/* soc-glue */ +#define SG_AOUTEN 0x1c04 + +/* SW view */ +#define A2CHNMAPCTR0(n) (0x00000 + 0x40 * (n)) +#define A2RBNMAPCTR0(n) (0x01000 + 0x40 * (n)) +#define A2IPORTNMAPCTR0(n) (0x02000 + 0x40 * (n)) +#define A2IPORTNMAPCTR1(n) (0x02004 + 0x40 * (n)) +#define A2IIFNMAPCTR0(n) (0x03000 + 0x40 * (n)) +#define A2OPORTNMAPCTR0(n) (0x04000 + 0x40 * (n)) +#define A2OPORTNMAPCTR1(n) (0x04004 + 0x40 * (n)) +#define A2OPORTNMAPCTR2(n) (0x04008 + 0x40 * (n)) +#define A2OIFNMAPCTR0(n) (0x05000 + 0x40 * (n)) +#define A2ATNMAPCTR0(n) (0x06000 + 0x40 * (n)) + +#define MAPCTR0_EN 0x80000000 + +/* CTL */ +#define A2APLLCTR0 0x07000 +#define A2APLLCTR0_APLLXPOW_MASK GENMASK(3, 0) +#define A2APLLCTR0_APLLXPOW_PWOFF (0x0 << 0) +#define A2APLLCTR0_APLLXPOW_PWON (0xf << 0) +#define A2APLLCTR1 0x07004 +#define A2APLLCTR1_APLLX_MASK 0x00010101 +#define A2APLLCTR1_APLLX_36MHZ 0x00000000 +#define A2APLLCTR1_APLLX_33MHZ 0x00000001 +#define A2EXMCLKSEL0 0x07030 +#define A2EXMCLKSEL0_EXMCLK_MASK GENMASK(2, 0) +#define A2EXMCLKSEL0_EXMCLK_OUTPUT (0x0 << 0) +#define A2EXMCLKSEL0_EXMCLK_INPUT (0x7 << 0) +#define A2SSIFSW 0x07050 +#define A2CH22_2CTR 0x07054 +#define A2AIOINPUTSEL 0x070e0 +#define A2AIOINPUTSEL_RXSEL_PCMI1_MASK GENMASK(2, 0) +#define A2AIOINPUTSEL_RXSEL_PCMI1_HDMIRX1 (0x2 << 0) +#define A2AIOINPUTSEL_RXSEL_PCMI2_MASK GENMASK(6, 4) +#define A2AIOINPUTSEL_RXSEL_PCMI2_SIF (0x7 << 4) +#define A2AIOINPUTSEL_RXSEL_PCMI3_MASK GENMASK(10, 8) +#define A2AIOINPUTSEL_RXSEL_PCMI3_EVEA (0x1 << 8) +#define A2AIOINPUTSEL_RXSEL_IECI1_MASK GENMASK(14, 12) +#define A2AIOINPUTSEL_RXSEL_IECI1_HDMIRX1 (0x2 << 12) +#define A2AIOINPUTSEL_RXSEL_MASK (A2AIOINPUTSEL_RXSEL_PCMI1_MASK | \ + A2AIOINPUTSEL_RXSEL_PCMI2_MASK | \ + A2AIOINPUTSEL_RXSEL_PCMI3_MASK | \ + A2AIOINPUTSEL_RXSEL_IECI1_HDMIRX1) + +/* INTC */ +#define INTCHIM(m) (0x9028 + 0x80 * (m)) +#define INTRBIM(m) (0x9030 + 0x80 * (m)) +#define INTCHID(m) (0xa028 + 0x80 * (m)) +#define INTRBID(m) (0xa030 + 0x80 * (m)) + +/* AIN(PCMINN) */ +#define IPORTMXCTR1(n) (0x22000 + 0x400 * (n)) +#define IPORTMXCTR1_LRSEL_MASK GENMASK(11, 10) +#define IPORTMXCTR1_LRSEL_RIGHT (0x0 << 10) +#define IPORTMXCTR1_LRSEL_LEFT (0x1 << 10) +#define IPORTMXCTR1_LRSEL_I2S (0x2 << 10) +#define IPORTMXCTR1_OUTBITSEL_MASK (0x800003U << 8) +#define IPORTMXCTR1_OUTBITSEL_32 (0x800000U << 8) +#define IPORTMXCTR1_OUTBITSEL_24 (0x000000U << 8) +#define IPORTMXCTR1_OUTBITSEL_20 (0x000001U << 8) +#define IPORTMXCTR1_OUTBITSEL_16 (0x000002U << 8) +#define IPORTMXCTR1_CHSEL_MASK GENMASK(6, 4) +#define IPORTMXCTR1_CHSEL_ALL (0x0 << 4) +#define IPORTMXCTR1_CHSEL_D0_D2 (0x1 << 4) +#define IPORTMXCTR1_CHSEL_D0 (0x2 << 4) +#define IPORTMXCTR1_CHSEL_D1 (0x3 << 4) +#define IPORTMXCTR1_CHSEL_D2 (0x4 << 4) +#define IPORTMXCTR1_CHSEL_DMIX (0x5 << 4) +#define IPORTMXCTR1_FSSEL_MASK GENMASK(3, 0) +#define IPORTMXCTR1_FSSEL_48 (0x0 << 0) +#define IPORTMXCTR1_FSSEL_96 (0x1 << 0) +#define IPORTMXCTR1_FSSEL_192 (0x2 << 0) +#define IPORTMXCTR1_FSSEL_32 (0x3 << 0) +#define IPORTMXCTR1_FSSEL_44_1 (0x4 << 0) +#define IPORTMXCTR1_FSSEL_88_2 (0x5 << 0) +#define IPORTMXCTR1_FSSEL_176_4 (0x6 << 0) +#define IPORTMXCTR1_FSSEL_16 (0x8 << 0) +#define IPORTMXCTR1_FSSEL_22_05 (0x9 << 0) +#define IPORTMXCTR1_FSSEL_24 (0xa << 0) +#define IPORTMXCTR1_FSSEL_8 (0xb << 0) +#define IPORTMXCTR1_FSSEL_11_025 (0xc << 0) +#define IPORTMXCTR1_FSSEL_12 (0xd << 0) +#define IPORTMXCTR2(n) (0x22004 + 0x400 * (n)) +#define IPORTMXCTR2_ACLKSEL_MASK GENMASK(19, 16) +#define IPORTMXCTR2_ACLKSEL_A1 (0x0 << 16) +#define IPORTMXCTR2_ACLKSEL_F1 (0x1 << 16) +#define IPORTMXCTR2_ACLKSEL_A2 (0x2 << 16) +#define IPORTMXCTR2_ACLKSEL_F2 (0x3 << 16) +#define IPORTMXCTR2_ACLKSEL_A2PLL (0x4 << 16) +#define IPORTMXCTR2_ACLKSEL_RX1 (0x5 << 16) +#define IPORTMXCTR2_ACLKSEL_RX2 (0x6 << 16) +#define IPORTMXCTR2_MSSEL_MASK BIT(15) +#define IPORTMXCTR2_MSSEL_SLAVE (0x0 << 15) +#define IPORTMXCTR2_MSSEL_MASTER (0x1 << 15) +#define IPORTMXCTR2_EXTLSIFSSEL_MASK BIT(14) +#define IPORTMXCTR2_EXTLSIFSSEL_36 (0x0 << 14) +#define IPORTMXCTR2_EXTLSIFSSEL_24 (0x1 << 14) +#define IPORTMXCTR2_DACCKSEL_MASK GENMASK(9, 8) +#define IPORTMXCTR2_DACCKSEL_1_2 (0x0 << 8) +#define IPORTMXCTR2_DACCKSEL_1_3 (0x1 << 8) +#define IPORTMXCTR2_DACCKSEL_1_1 (0x2 << 8) +#define IPORTMXCTR2_DACCKSEL_2_3 (0x3 << 8) +#define IPORTMXCTR2_REQEN_MASK BIT(0) +#define IPORTMXCTR2_REQEN_DISABLE (0x0 << 0) +#define IPORTMXCTR2_REQEN_ENABLE (0x1 << 0) +#define IPORTMXCNTCTR(n) (0x22010 + 0x400 * (n)) +#define IPORTMXCOUNTER(n) (0x22014 + 0x400 * (n)) +#define IPORTMXCNTMONI(n) (0x22018 + 0x400 * (n)) +#define IPORTMXACLKSEL0EX(n) (0x22020 + 0x400 * (n)) +#define IPORTMXACLKSEL0EX_ACLKSEL0EX_MASK GENMASK(3, 0) +#define IPORTMXACLKSEL0EX_ACLKSEL0EX_INTERNAL (0x0 << 0) +#define IPORTMXACLKSEL0EX_ACLKSEL0EX_EXTERNAL (0xf << 0) +#define IPORTMXEXNOE(n) (0x22070 + 0x400 * (n)) +#define IPORTMXEXNOE_PCMINOE_MASK BIT(0) +#define IPORTMXEXNOE_PCMINOE_OUTPUT (0x0 << 0) +#define IPORTMXEXNOE_PCMINOE_INPUT (0x1 << 0) +#define IPORTMXMASK(n) (0x22078 + 0x400 * (n)) +#define IPORTMXMASK_IUXCKMSK_MASK GENMASK(18, 16) +#define IPORTMXMASK_IUXCKMSK_ON (0x0 << 16) +#define IPORTMXMASK_IUXCKMSK_OFF (0x7 << 16) +#define IPORTMXMASK_XCKMSK_MASK GENMASK(2, 0) +#define IPORTMXMASK_XCKMSK_ON (0x0 << 0) +#define IPORTMXMASK_XCKMSK_OFF (0x7 << 0) +#define IPORTMXRSTCTR(n) (0x2207c + 0x400 * (n)) +#define IPORTMXRSTCTR_RSTPI_MASK BIT(7) +#define IPORTMXRSTCTR_RSTPI_RELEASE (0x0 << 7) +#define IPORTMXRSTCTR_RSTPI_RESET (0x1 << 7) + +/* AIN(PBinMX) */ +#define PBINMXCTR(n) (0x20200 + 0x40 * (n)) +#define PBINMXCTR_NCONNECT_MASK BIT(15) +#define PBINMXCTR_NCONNECT_CONNECT (0x0 << 15) +#define PBINMXCTR_NCONNECT_DISCONNECT (0x1 << 15) +#define PBINMXCTR_INOUTSEL_MASK BIT(14) +#define PBINMXCTR_INOUTSEL_IN (0x0 << 14) +#define PBINMXCTR_INOUTSEL_OUT (0x1 << 14) +#define PBINMXCTR_PBINSEL_SHIFT (8) +#define PBINMXCTR_ENDIAN_MASK GENMASK(5, 4) +#define PBINMXCTR_ENDIAN_3210 (0x0 << 4) +#define PBINMXCTR_ENDIAN_0123 (0x1 << 4) +#define PBINMXCTR_ENDIAN_1032 (0x2 << 4) +#define PBINMXCTR_ENDIAN_2301 (0x3 << 4) +#define PBINMXCTR_MEMFMT_MASK GENMASK(3, 0) +#define PBINMXCTR_MEMFMT_D0 (0x0 << 0) +#define PBINMXCTR_MEMFMT_5_1CH_DMIX (0x1 << 0) +#define PBINMXCTR_MEMFMT_6CH (0x2 << 0) +#define PBINMXCTR_MEMFMT_4CH (0x3 << 0) +#define PBINMXCTR_MEMFMT_DMIX (0x4 << 0) +#define PBINMXCTR_MEMFMT_1CH (0x5 << 0) +#define PBINMXCTR_MEMFMT_16LR (0x6 << 0) +#define PBINMXCTR_MEMFMT_7_1CH (0x7 << 0) +#define PBINMXCTR_MEMFMT_7_1CH_DMIX (0x8 << 0) +#define PBINMXCTR_MEMFMT_STREAM (0xf << 0) +#define PBINMXPAUSECTR0(n) (0x20204 + 0x40 * (n)) +#define PBINMXPAUSECTR1(n) (0x20208 + 0x40 * (n)) + +/* AOUT */ +#define AOUTFADECTR0 0x40020 +#define AOUTENCTR0 0x40040 +#define AOUTENCTR1 0x40044 +#define AOUTENCTR2 0x40048 +#define AOUTRSTCTR0 0x40060 +#define AOUTRSTCTR1 0x40064 +#define AOUTRSTCTR2 0x40068 +#define AOUTSRCRSTCTR0 0x400c0 +#define AOUTSRCRSTCTR1 0x400c4 +#define AOUTSRCRSTCTR2 0x400c8 + +/* AOUT PCMOUT has 5 slots, slot0-3: D0-3, slot4: DMIX */ +#define OPORT_SLOT_MAX 5 + +/* AOUT(PCMOUTN) */ +#define OPORTMXCTR1(n) (0x42000 + 0x400 * (n)) +#define OPORTMXCTR1_I2SLRSEL_MASK (0x11 << 10) +#define OPORTMXCTR1_I2SLRSEL_RIGHT (0x00 << 10) +#define OPORTMXCTR1_I2SLRSEL_LEFT (0x01 << 10) +#define OPORTMXCTR1_I2SLRSEL_I2S (0x11 << 10) +#define OPORTMXCTR1_OUTBITSEL_MASK (0x800003U << 8) +#define OPORTMXCTR1_OUTBITSEL_32 (0x800000U << 8) +#define OPORTMXCTR1_OUTBITSEL_24 (0x000000U << 8) +#define OPORTMXCTR1_OUTBITSEL_20 (0x000001U << 8) +#define OPORTMXCTR1_OUTBITSEL_16 (0x000002U << 8) +#define OPORTMXCTR1_FSSEL_MASK GENMASK(3, 0) +#define OPORTMXCTR1_FSSEL_48 (0x0 << 0) +#define OPORTMXCTR1_FSSEL_96 (0x1 << 0) +#define OPORTMXCTR1_FSSEL_192 (0x2 << 0) +#define OPORTMXCTR1_FSSEL_32 (0x3 << 0) +#define OPORTMXCTR1_FSSEL_44_1 (0x4 << 0) +#define OPORTMXCTR1_FSSEL_88_2 (0x5 << 0) +#define OPORTMXCTR1_FSSEL_176_4 (0x6 << 0) +#define OPORTMXCTR1_FSSEL_16 (0x8 << 0) +#define OPORTMXCTR1_FSSEL_22_05 (0x9 << 0) +#define OPORTMXCTR1_FSSEL_24 (0xa << 0) +#define OPORTMXCTR1_FSSEL_8 (0xb << 0) +#define OPORTMXCTR1_FSSEL_11_025 (0xc << 0) +#define OPORTMXCTR1_FSSEL_12 (0xd << 0) +#define OPORTMXCTR2(n) (0x42004 + 0x400 * (n)) +#define OPORTMXCTR2_ACLKSEL_MASK GENMASK(19, 16) +#define OPORTMXCTR2_ACLKSEL_A1 (0x0 << 16) +#define OPORTMXCTR2_ACLKSEL_F1 (0x1 << 16) +#define OPORTMXCTR2_ACLKSEL_A2 (0x2 << 16) +#define OPORTMXCTR2_ACLKSEL_F2 (0x3 << 16) +#define OPORTMXCTR2_ACLKSEL_A2PLL (0x4 << 16) +#define OPORTMXCTR2_ACLKSEL_RX1 (0x5 << 16) +#define OPORTMXCTR2_ACLKSEL_RX2 (0x6 << 16) +#define OPORTMXCTR2_MSSEL_MASK BIT(15) +#define OPORTMXCTR2_MSSEL_SLAVE (0x0 << 15) +#define OPORTMXCTR2_MSSEL_MASTER (0x1 << 15) +#define OPORTMXCTR2_EXTLSIFSSEL_MASK BIT(14) +#define OPORTMXCTR2_EXTLSIFSSEL_36 (0x0 << 14) +#define OPORTMXCTR2_EXTLSIFSSEL_24 (0x1 << 14) +#define OPORTMXCTR2_DACCKSEL_MASK GENMASK(9, 8) +#define OPORTMXCTR2_DACCKSEL_1_2 (0x0 << 8) +#define OPORTMXCTR2_DACCKSEL_1_3 (0x1 << 8) +#define OPORTMXCTR2_DACCKSEL_1_1 (0x2 << 8) +#define OPORTMXCTR2_DACCKSEL_2_3 (0x3 << 8) +#define OPORTMXCTR3(n) (0x42008 + 0x400 * (n)) +#define OPORTMXCTR3_IECTHUR_MASK BIT(19) +#define OPORTMXCTR3_IECTHUR_IECOUT (0x0 << 19) +#define OPORTMXCTR3_IECTHUR_IECIN (0x1 << 19) +#define OPORTMXCTR3_SRCSEL_MASK GENMASK(18, 16) +#define OPORTMXCTR3_SRCSEL_PCM (0x0 << 16) +#define OPORTMXCTR3_SRCSEL_STREAM (0x1 << 16) +#define OPORTMXCTR3_SRCSEL_CDDTS (0x2 << 16) +#define OPORTMXCTR3_VALID_MASK BIT(12) +#define OPORTMXCTR3_VALID_PCM (0x0 << 12) +#define OPORTMXCTR3_VALID_STREAM (0x1 << 12) +#define OPORTMXCTR3_PMSEL_MASK BIT(3) +#define OPORTMXCTR3_PMSEL_MUTE (0x0 << 3) +#define OPORTMXCTR3_PMSEL_PAUSE (0x1 << 3) +#define OPORTMXCTR3_PMSW_MASK BIT(2) +#define OPORTMXCTR3_PMSW_MUTE_OFF (0x0 << 2) +#define OPORTMXCTR3_PMSW_MUTE_ON (0x1 << 2) +#define OPORTMXSRC1CTR(n) (0x4200c + 0x400 * (n)) +#define OPORTMXSRC1CTR_FSIIPNUM_SHIFT (24) +#define OPORTMXSRC1CTR_THMODE_MASK BIT(23) +#define OPORTMXSRC1CTR_THMODE_SRC (0x0 << 23) +#define OPORTMXSRC1CTR_THMODE_BYPASS (0x1 << 23) +#define OPORTMXSRC1CTR_LOCK_MASK BIT(16) +#define OPORTMXSRC1CTR_LOCK_UNLOCK (0x0 << 16) +#define OPORTMXSRC1CTR_LOCK_LOCK (0x1 << 16) +#define OPORTMXSRC1CTR_SRCPATH_MASK BIT(15) +#define OPORTMXSRC1CTR_SRCPATH_BYPASS (0x0 << 15) +#define OPORTMXSRC1CTR_SRCPATH_CALC (0x1 << 15) +#define OPORTMXSRC1CTR_SYNC_MASK BIT(14) +#define OPORTMXSRC1CTR_SYNC_ASYNC (0x0 << 14) +#define OPORTMXSRC1CTR_SYNC_SYNC (0x1 << 14) +#define OPORTMXSRC1CTR_FSOCK_MASK GENMASK(11, 10) +#define OPORTMXSRC1CTR_FSOCK_44_1 (0x0 << 10) +#define OPORTMXSRC1CTR_FSOCK_48 (0x1 << 10) +#define OPORTMXSRC1CTR_FSOCK_32 (0x2 << 10) +#define OPORTMXSRC1CTR_FSICK_MASK GENMASK(9, 8) +#define OPORTMXSRC1CTR_FSICK_44_1 (0x0 << 8) +#define OPORTMXSRC1CTR_FSICK_48 (0x1 << 8) +#define OPORTMXSRC1CTR_FSICK_32 (0x2 << 8) +#define OPORTMXSRC1CTR_FSIIPSEL_MASK GENMASK(5, 4) +#define OPORTMXSRC1CTR_FSIIPSEL_INNER (0x0 << 4) +#define OPORTMXSRC1CTR_FSIIPSEL_OUTER (0x1 << 4) +#define OPORTMXSRC1CTR_FSISEL_MASK GENMASK(3, 0) +#define OPORTMXSRC1CTR_FSISEL_ACLK (0x0 << 0) +#define OPORTMXSRC1CTR_FSISEL_DD (0x1 << 0) +#define OPORTMXDSDMUTEDAT(n) (0x42020 + 0x400 * (n)) +#define OPORTMXDXDFREQMODE(n) (0x42024 + 0x400 * (n)) +#define OPORTMXDSDSEL(n) (0x42028 + 0x400 * (n)) +#define OPORTMXDSDPORT(n) (0x4202c + 0x400 * (n)) +#define OPORTMXACLKSEL0EX(n) (0x42030 + 0x400 * (n)) +#define OPORTMXPATH(n) (0x42040 + 0x400 * (n)) +#define OPORTMXSYNC(n) (0x42044 + 0x400 * (n)) +#define OPORTMXREPET(n) (0x42050 + 0x400 * (n)) +#define OPORTMXREPET_STRLENGTH_AC3 SBF_(IEC61937_FRM_STR_AC3, 16) +#define OPORTMXREPET_STRLENGTH_MPA SBF_(IEC61937_FRM_STR_MPA, 16) +#define OPORTMXREPET_STRLENGTH_MP3 SBF_(IEC61937_FRM_STR_MP3, 16) +#define OPORTMXREPET_STRLENGTH_DTS1 SBF_(IEC61937_FRM_STR_DTS1, 16) +#define OPORTMXREPET_STRLENGTH_DTS2 SBF_(IEC61937_FRM_STR_DTS2, 16) +#define OPORTMXREPET_STRLENGTH_DTS3 SBF_(IEC61937_FRM_STR_DTS3, 16) +#define OPORTMXREPET_STRLENGTH_AAC SBF_(IEC61937_FRM_STR_AAC, 16) +#define OPORTMXREPET_PMLENGTH_AC3 SBF_(IEC61937_FRM_PAU_AC3, 0) +#define OPORTMXREPET_PMLENGTH_MPA SBF_(IEC61937_FRM_PAU_MPA, 0) +#define OPORTMXREPET_PMLENGTH_MP3 SBF_(IEC61937_FRM_PAU_MP3, 0) +#define OPORTMXREPET_PMLENGTH_DTS1 SBF_(IEC61937_FRM_PAU_DTS1, 0) +#define OPORTMXREPET_PMLENGTH_DTS2 SBF_(IEC61937_FRM_PAU_DTS2, 0) +#define OPORTMXREPET_PMLENGTH_DTS3 SBF_(IEC61937_FRM_PAU_DTS3, 0) +#define OPORTMXREPET_PMLENGTH_AAC SBF_(IEC61937_FRM_PAU_AAC, 0) +#define OPORTMXPAUDAT(n) (0x42054 + 0x400 * (n)) +#define OPORTMXPAUDAT_PAUSEPC_CMN (IEC61937_PC_PAUSE << 16) +#define OPORTMXPAUDAT_PAUSEPD_AC3 (IEC61937_FRM_PAU_AC3 * 4) +#define OPORTMXPAUDAT_PAUSEPD_MPA (IEC61937_FRM_PAU_MPA * 4) +#define OPORTMXPAUDAT_PAUSEPD_MP3 (IEC61937_FRM_PAU_MP3 * 4) +#define OPORTMXPAUDAT_PAUSEPD_DTS1 (IEC61937_FRM_PAU_DTS1 * 4) +#define OPORTMXPAUDAT_PAUSEPD_DTS2 (IEC61937_FRM_PAU_DTS2 * 4) +#define OPORTMXPAUDAT_PAUSEPD_DTS3 (IEC61937_FRM_PAU_DTS3 * 4) +#define OPORTMXPAUDAT_PAUSEPD_AAC (IEC61937_FRM_PAU_AAC * 4) +#define OPORTMXRATE_I(n) (0x420e4 + 0x400 * (n)) +#define OPORTMXRATE_I_EQU_MASK BIT(31) +#define OPORTMXRATE_I_EQU_NOTEQUAL (0x0 << 31) +#define OPORTMXRATE_I_EQU_EQUAL (0x1 << 31) +#define OPORTMXRATE_I_SRCBPMD_MASK BIT(29) +#define OPORTMXRATE_I_SRCBPMD_BYPASS (0x0 << 29) +#define OPORTMXRATE_I_SRCBPMD_SRC (0x1 << 29) +#define OPORTMXRATE_I_LRCKSTP_MASK BIT(24) +#define OPORTMXRATE_I_LRCKSTP_START (0x0 << 24) +#define OPORTMXRATE_I_LRCKSTP_STOP (0x1 << 24) +#define OPORTMXRATE_I_ACLKSRC_MASK GENMASK(15, 12) +#define OPORTMXRATE_I_ACLKSRC_APLL (0x0 << 12) +#define OPORTMXRATE_I_ACLKSRC_USB (0x1 << 12) +#define OPORTMXRATE_I_ACLKSRC_HSC (0x3 << 12) +/* if OPORTMXRATE_I_ACLKSRC_APLL */ +#define OPORTMXRATE_I_ACLKSEL_MASK GENMASK(11, 8) +#define OPORTMXRATE_I_ACLKSEL_APLLA1 (0x0 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLLF1 (0x1 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLLA2 (0x2 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLLF2 (0x3 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLL (0x4 << 8) +#define OPORTMXRATE_I_ACLKSEL_HDMI1 (0x5 << 8) +#define OPORTMXRATE_I_ACLKSEL_HDMI2 (0x6 << 8) +#define OPORTMXRATE_I_ACLKSEL_AI1ADCCK (0xc << 8) +#define OPORTMXRATE_I_ACLKSEL_AI2ADCCK (0xd << 8) +#define OPORTMXRATE_I_ACLKSEL_AI3ADCCK (0xe << 8) +#define OPORTMXRATE_I_MCKSEL_MASK GENMASK(7, 4) +#define OPORTMXRATE_I_MCKSEL_36 (0x0 << 4) +#define OPORTMXRATE_I_MCKSEL_33 (0x1 << 4) +#define OPORTMXRATE_I_MCKSEL_HSC27 (0xb << 4) +#define OPORTMXRATE_I_FSSEL_MASK GENMASK(3, 0) +#define OPORTMXRATE_I_FSSEL_48 (0x0 << 0) +#define OPORTMXRATE_I_FSSEL_96 (0x1 << 0) +#define OPORTMXRATE_I_FSSEL_192 (0x2 << 0) +#define OPORTMXRATE_I_FSSEL_32 (0x3 << 0) +#define OPORTMXRATE_I_FSSEL_44_1 (0x4 << 0) +#define OPORTMXRATE_I_FSSEL_88_2 (0x5 << 0) +#define OPORTMXRATE_I_FSSEL_176_4 (0x6 << 0) +#define OPORTMXRATE_I_FSSEL_16 (0x8 << 0) +#define OPORTMXRATE_I_FSSEL_22_05 (0x9 << 0) +#define OPORTMXRATE_I_FSSEL_24 (0xa << 0) +#define OPORTMXRATE_I_FSSEL_8 (0xb << 0) +#define OPORTMXRATE_I_FSSEL_11_025 (0xc << 0) +#define OPORTMXRATE_I_FSSEL_12 (0xd << 0) +#define OPORTMXEXNOE(n) (0x420f0 + 0x400 * (n)) +#define OPORTMXMASK(n) (0x420f8 + 0x400 * (n)) +#define OPORTMXMASK_IUDXMSK_MASK GENMASK(28, 24) +#define OPORTMXMASK_IUDXMSK_ON (0x00 << 24) +#define OPORTMXMASK_IUDXMSK_OFF (0x1f << 24) +#define OPORTMXMASK_IUXCKMSK_MASK GENMASK(18, 16) +#define OPORTMXMASK_IUXCKMSK_ON (0x0 << 16) +#define OPORTMXMASK_IUXCKMSK_OFF (0x7 << 16) +#define OPORTMXMASK_DXMSK_MASK GENMASK(12, 8) +#define OPORTMXMASK_DXMSK_ON (0x00 << 8) +#define OPORTMXMASK_DXMSK_OFF (0x1f << 8) +#define OPORTMXMASK_XCKMSK_MASK GENMASK(2, 0) +#define OPORTMXMASK_XCKMSK_ON (0x0 << 0) +#define OPORTMXMASK_XCKMSK_OFF (0x7 << 0) +#define OPORTMXDEBUG(n) (0x420fc + 0x400 * (n)) +#define OPORTMXTYVOLPARA1(n, m) (0x42100 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYVOLPARA1_SLOPEU_MASK GENMASK(31, 16) +#define OPORTMXTYVOLPARA2(n, m) (0x42104 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYVOLPARA2_FADE_MASK GENMASK(17, 16) +#define OPORTMXTYVOLPARA2_FADE_NOOP (0x0 << 16) +#define OPORTMXTYVOLPARA2_FADE_FADEOUT (0x1 << 16) +#define OPORTMXTYVOLPARA2_FADE_FADEIN (0x2 << 16) +#define OPORTMXTYVOLPARA2_TARGET_MASK GENMASK(15, 0) +#define OPORTMXTYVOLGAINSTATUS(n, m) (0x42108 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYVOLGAINSTATUS_CUR_MASK GENMASK(15, 0) +#define OPORTMXTYSLOTCTR(n, m) (0x42114 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYSLOTCTR_MODE BIT(15) +#define OPORTMXTYSLOTCTR_SLOTSEL_MASK GENMASK(11, 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT0 (0x8 << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT1 (0x9 << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT2 (0xa << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT3 (0xb << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT4 (0xc << 8) +#define OPORTMXT0SLOTCTR_MUTEOFF_MASK BIT(1) +#define OPORTMXT0SLOTCTR_MUTEOFF_MUTE (0x0 << 1) +#define OPORTMXT0SLOTCTR_MUTEOFF_UNMUTE (0x1 << 1) +#define OPORTMXTYRSTCTR(n, m) (0x4211c + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXT0RSTCTR_RST_MASK BIT(1) +#define OPORTMXT0RSTCTR_RST_OFF (0x0 << 1) +#define OPORTMXT0RSTCTR_RST_ON (0x1 << 1) + +#define SBF_(frame, shift) (((frame) * 2 - 1) << shift) + +/* AOUT(PBoutMX) */ +#define PBOUTMXCTR0(n) (0x40200 + 0x40 * (n)) +#define PBOUTMXCTR0_ENDIAN_MASK GENMASK(5, 4) +#define PBOUTMXCTR0_ENDIAN_3210 (0x0 << 4) +#define PBOUTMXCTR0_ENDIAN_0123 (0x1 << 4) +#define PBOUTMXCTR0_ENDIAN_1032 (0x2 << 4) +#define PBOUTMXCTR0_ENDIAN_2301 (0x3 << 4) +#define PBOUTMXCTR0_MEMFMT_MASK GENMASK(3, 0) +#define PBOUTMXCTR0_MEMFMT_10CH (0x0 << 0) +#define PBOUTMXCTR0_MEMFMT_8CH (0x1 << 0) +#define PBOUTMXCTR0_MEMFMT_6CH (0x2 << 0) +#define PBOUTMXCTR0_MEMFMT_4CH (0x3 << 0) +#define PBOUTMXCTR0_MEMFMT_2CH (0x4 << 0) +#define PBOUTMXCTR0_MEMFMT_STREAM (0x5 << 0) +#define PBOUTMXCTR0_MEMFMT_1CH (0x6 << 0) +#define PBOUTMXCTR1(n) (0x40204 + 0x40 * (n)) +#define PBOUTMXINTCTR(n) (0x40208 + 0x40 * (n)) + +/* A2D(subsystem) */ +#define CDA2D_STRT0 0x10000 +#define CDA2D_STRT0_STOP_MASK BIT(31) +#define CDA2D_STRT0_STOP_START (0x0 << 31) +#define CDA2D_STRT0_STOP_STOP (0x1 << 31) +#define CDA2D_STAT0 0x10020 +#define CDA2D_TEST 0x100a0 +#define CDA2D_TEST_DDR_MODE_MASK GENMASK(3, 2) +#define CDA2D_TEST_DDR_MODE_EXTON0 (0x0 << 2) +#define CDA2D_TEST_DDR_MODE_EXTOFF1 (0x3 << 2) +#define CDA2D_STRTADRSLOAD 0x100b0 + +#define CDA2D_CHMXCTRL1(n) (0x12000 + 0x80 * (n)) +#define CDA2D_CHMXCTRL1_INDSIZE_MASK BIT(0) +#define CDA2D_CHMXCTRL1_INDSIZE_FINITE (0x0 << 0) +#define CDA2D_CHMXCTRL1_INDSIZE_INFINITE (0x1 << 0) +#define CDA2D_CHMXCTRL2(n) (0x12004 + 0x80 * (n)) +#define CDA2D_CHMXSRCAMODE(n) (0x12020 + 0x80 * (n)) +#define CDA2D_CHMXDSTAMODE(n) (0x12024 + 0x80 * (n)) +#define CDA2D_CHMXAMODE_ENDIAN_MASK GENMASK(17, 16) +#define CDA2D_CHMXAMODE_ENDIAN_3210 (0x0 << 16) +#define CDA2D_CHMXAMODE_ENDIAN_0123 (0x1 << 16) +#define CDA2D_CHMXAMODE_ENDIAN_1032 (0x2 << 16) +#define CDA2D_CHMXAMODE_ENDIAN_2301 (0x3 << 16) +#define CDA2D_CHMXAMODE_RSSEL_SHIFT (8) +#define CDA2D_CHMXAMODE_AUPDT_MASK GENMASK(5, 4) +#define CDA2D_CHMXAMODE_AUPDT_INC (0x0 << 4) +#define CDA2D_CHMXAMODE_AUPDT_FIX (0x2 << 4) +#define CDA2D_CHMXAMODE_TYPE_MASK GENMASK(3, 2) +#define CDA2D_CHMXAMODE_TYPE_NORMAL (0x0 << 2) +#define CDA2D_CHMXAMODE_TYPE_RING (0x1 << 2) +#define CDA2D_CHMXSRCSTRTADRS(n) (0x12030 + 0x80 * (n)) +#define CDA2D_CHMXSRCSTRTADRSU(n) (0x12034 + 0x80 * (n)) +#define CDA2D_CHMXDSTSTRTADRS(n) (0x12038 + 0x80 * (n)) +#define CDA2D_CHMXDSTSTRTADRSU(n) (0x1203c + 0x80 * (n)) + +/* A2D(ring buffer) */ +#define CDA2D_RBFLUSH0 0x10040 +#define CDA2D_RBADRSLOAD 0x100b4 +#define CDA2D_RDPTRLOAD 0x100b8 +#define CDA2D_RDPTRLOAD_LSFLAG_LOAD (0x0 << 31) +#define CDA2D_RDPTRLOAD_LSFLAG_STORE (0x1 << 31) +#define CDA2D_WRPTRLOAD 0x100bc +#define CDA2D_WRPTRLOAD_LSFLAG_LOAD (0x0 << 31) +#define CDA2D_WRPTRLOAD_LSFLAG_STORE (0x1 << 31) + +#define CDA2D_RBMXBGNADRS(n) (0x14000 + 0x80 * (n)) +#define CDA2D_RBMXBGNADRSU(n) (0x14004 + 0x80 * (n)) +#define CDA2D_RBMXENDADRS(n) (0x14008 + 0x80 * (n)) +#define CDA2D_RBMXENDADRSU(n) (0x1400c + 0x80 * (n)) +#define CDA2D_RBMXBTH(n) (0x14038 + 0x80 * (n)) +#define CDA2D_RBMXRTH(n) (0x1403c + 0x80 * (n)) +#define CDA2D_RBMXRDPTR(n) (0x14020 + 0x80 * (n)) +#define CDA2D_RBMXRDPTRU(n) (0x14024 + 0x80 * (n)) +#define CDA2D_RBMXWRPTR(n) (0x14028 + 0x80 * (n)) +#define CDA2D_RBMXWRPTRU(n) (0x1402c + 0x80 * (n)) +#define CDA2D_RBMXPTRU_PTRU_MASK GENMASK(1, 0) +#define CDA2D_RBMXCNFG(n) (0x14030 + 0x80 * (n)) +#define CDA2D_RBMXIR(n) (0x14014 + 0x80 * (n)) +#define CDA2D_RBMXIE(n) (0x14018 + 0x80 * (n)) +#define CDA2D_RBMXID(n) (0x1401c + 0x80 * (n)) +#define CDA2D_RBMXIX_SPACE BIT(3) +#define CDA2D_RBMXIX_REMAIN BIT(4) + +#endif /* SND_UNIPHIER_AIO_REG_H__ */ diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h new file mode 100644 index 000000000..a7ff7e556 --- /dev/null +++ b/sound/soc/uniphier/aio.h @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Socionext UniPhier AIO ALSA driver. + * + * Copyright (c) 2016-2018 Socionext Inc. + */ + +#ifndef SND_UNIPHIER_AIO_H__ +#define SND_UNIPHIER_AIO_H__ + +#include +#include +#include +#include +#include + +struct platform_device; + +enum ID_PORT_TYPE { + PORT_TYPE_UNKNOWN, + PORT_TYPE_I2S, + PORT_TYPE_SPDIF, + PORT_TYPE_EVE, + PORT_TYPE_CONV, +}; + +enum ID_PORT_DIR { + PORT_DIR_OUTPUT, + PORT_DIR_INPUT, +}; + +enum IEC61937_PC { + IEC61937_PC_AC3 = 0x0001, + IEC61937_PC_PAUSE = 0x0003, + IEC61937_PC_MPA = 0x0004, + IEC61937_PC_MP3 = 0x0005, + IEC61937_PC_DTS1 = 0x000b, + IEC61937_PC_DTS2 = 0x000c, + IEC61937_PC_DTS3 = 0x000d, + IEC61937_PC_AAC = 0x0007, +}; + +/* IEC61937 Repetition period of data-burst in IEC60958 frames */ +#define IEC61937_FRM_STR_AC3 1536 +#define IEC61937_FRM_STR_MPA 1152 +#define IEC61937_FRM_STR_MP3 1152 +#define IEC61937_FRM_STR_DTS1 512 +#define IEC61937_FRM_STR_DTS2 1024 +#define IEC61937_FRM_STR_DTS3 2048 +#define IEC61937_FRM_STR_AAC 1024 + +/* IEC61937 Repetition period of Pause data-burst in IEC60958 frames */ +#define IEC61937_FRM_PAU_AC3 3 +#define IEC61937_FRM_PAU_MPA 32 +#define IEC61937_FRM_PAU_MP3 32 +#define IEC61937_FRM_PAU_DTS1 3 +#define IEC61937_FRM_PAU_DTS2 3 +#define IEC61937_FRM_PAU_DTS3 3 +#define IEC61937_FRM_PAU_AAC 32 + +/* IEC61937 Pa and Pb */ +#define IEC61937_HEADER_SIGN 0x1f4e72f8 + +#define AUD_HW_PCMIN1 0 +#define AUD_HW_PCMIN2 1 +#define AUD_HW_PCMIN3 2 +#define AUD_HW_IECIN1 3 +#define AUD_HW_DIECIN1 4 + +#define AUD_NAME_PCMIN1 "aio-pcmin1" +#define AUD_NAME_PCMIN2 "aio-pcmin2" +#define AUD_NAME_PCMIN3 "aio-pcmin3" +#define AUD_NAME_IECIN1 "aio-iecin1" +#define AUD_NAME_DIECIN1 "aio-diecin1" + +#define AUD_HW_HPCMOUT1 0 +#define AUD_HW_PCMOUT1 1 +#define AUD_HW_PCMOUT2 2 +#define AUD_HW_PCMOUT3 3 +#define AUD_HW_EPCMOUT1 4 +#define AUD_HW_EPCMOUT2 5 +#define AUD_HW_EPCMOUT3 6 +#define AUD_HW_EPCMOUT6 9 +#define AUD_HW_HIECOUT1 10 +#define AUD_HW_IECOUT1 11 +#define AUD_HW_CMASTER 31 + +#define AUD_NAME_HPCMOUT1 "aio-hpcmout1" +#define AUD_NAME_PCMOUT1 "aio-pcmout1" +#define AUD_NAME_PCMOUT2 "aio-pcmout2" +#define AUD_NAME_PCMOUT3 "aio-pcmout3" +#define AUD_NAME_EPCMOUT1 "aio-epcmout1" +#define AUD_NAME_EPCMOUT2 "aio-epcmout2" +#define AUD_NAME_EPCMOUT3 "aio-epcmout3" +#define AUD_NAME_EPCMOUT6 "aio-epcmout6" +#define AUD_NAME_HIECOUT1 "aio-hiecout1" +#define AUD_NAME_IECOUT1 "aio-iecout1" +#define AUD_NAME_CMASTER "aio-cmaster" +#define AUD_NAME_HIECCOMPOUT1 "aio-hieccompout1" +#define AUD_NAME_IECCOMPOUT1 "aio-ieccompout1" + +#define AUD_GNAME_HDMI "aio-hdmi" +#define AUD_GNAME_LINE "aio-line" +#define AUD_GNAME_AUX "aio-aux" +#define AUD_GNAME_IEC "aio-iec" + +#define AUD_CLK_IO 0 +#define AUD_CLK_A1 1 +#define AUD_CLK_F1 2 +#define AUD_CLK_A2 3 +#define AUD_CLK_F2 4 +#define AUD_CLK_A 5 +#define AUD_CLK_F 6 +#define AUD_CLK_APLL 7 +#define AUD_CLK_RX0 8 +#define AUD_CLK_USB0 9 +#define AUD_CLK_HSC0 10 + +#define AUD_PLL_A1 0 +#define AUD_PLL_F1 1 +#define AUD_PLL_A2 2 +#define AUD_PLL_F2 3 +#define AUD_PLL_APLL 4 +#define AUD_PLL_RX0 5 +#define AUD_PLL_USB0 6 +#define AUD_PLL_HSC0 7 + +#define AUD_PLLDIV_1_2 0 +#define AUD_PLLDIV_1_3 1 +#define AUD_PLLDIV_1_1 2 +#define AUD_PLLDIV_2_3 3 + +#define AUD_VOL_INIT 0x4000 /* +0dB */ +#define AUD_VOL_MAX 0xffff /* +6dB */ +#define AUD_VOL_FADE_TIME 20 /* 20ms */ + +#define AUD_RING_SIZE (128 * 1024) + +#define AUD_MIN_FRAGMENT 4 +#define AUD_MAX_FRAGMENT 8 +#define AUD_MIN_FRAGMENT_SIZE (4 * 1024) +#define AUD_MAX_FRAGMENT_SIZE (16 * 1024) + +/* max 5 slots, 10 channels, 2 channel in 1 slot */ +#define AUD_MAX_SLOTSEL 5 + +/* + * This is a selector for virtual register map of AIO. + * + * map: Specify the index of virtual register map. + * hw : Specify the ID of real register map, selector uses this value. + * A meaning of this value depends specification of SoC. + */ +struct uniphier_aio_selector { + int map; + int hw; +}; + +/** + * 'SoftWare MAPping' setting of UniPhier AIO registers. + * + * We have to setup 'virtual' register maps to access 'real' registers of AIO. + * This feature is legacy and meaningless but AIO needs this to work. + * + * Each hardware blocks have own virtual register maps as following: + * + * Address Virtual Real + * ------- --------- --------------- + * 0x12000 DMAC map0 --> [selector] --> DMAC hardware 3 + * 0x12080 DMAC map1 --> [selector] --> DMAC hardware 1 + * ... + * 0x42000 Port map0 --> [selector] --> Port hardware 1 + * 0x42400 Port map1 --> [selector] --> Port hardware 2 + * ... + * + * ch : Input or output channel of DMAC + * rb : Ring buffer + * iport: PCM input port + * iif : Input interface + * oport: PCM output port + * oif : Output interface + * och : Output channel of DMAC for sampling rate converter + * + * These are examples for sound data paths: + * + * For caputure device: + * (outer of AIO) -> iport -> iif -> ch -> rb -> (CPU) + * For playback device: + * (CPU) -> rb -> ch -> oif -> oport -> (outer of AIO) + * For sampling rate converter device: + * (CPU) -> rb -> ch -> oif -> (HW SRC) -> iif -> och -> orb -> (CPU) + */ +struct uniphier_aio_swmap { + int type; + int dir; + + struct uniphier_aio_selector ch; + struct uniphier_aio_selector rb; + struct uniphier_aio_selector iport; + struct uniphier_aio_selector iif; + struct uniphier_aio_selector oport; + struct uniphier_aio_selector oif; + struct uniphier_aio_selector och; +}; + +struct uniphier_aio_spec { + const char *name; + const char *gname; + struct uniphier_aio_swmap swm; +}; + +struct uniphier_aio_pll { + bool enable; + unsigned int freq; +}; + +struct uniphier_aio_chip_spec { + const struct uniphier_aio_spec *specs; + int num_specs; + const struct uniphier_aio_pll *plls; + int num_plls; + struct snd_soc_dai_driver *dais; + int num_dais; + + /* DMA access mode, this is workaround for DMA hungup */ + int addr_ext; +}; + +struct uniphier_aio_sub { + struct uniphier_aio *aio; + + /* Guard sub->rd_offs and wr_offs from IRQ handler. */ + spinlock_t lock; + + const struct uniphier_aio_swmap *swm; + const struct uniphier_aio_spec *spec; + + /* For PCM audio */ + struct snd_pcm_substream *substream; + struct snd_pcm_hw_params params; + int vol; + + /* For compress audio */ + struct snd_compr_stream *cstream; + struct snd_compr_params cparams; + unsigned char *compr_area; + dma_addr_t compr_addr; + size_t compr_bytes; + int pass_through; + enum IEC61937_PC iec_pc; + bool iec_header; + + /* Both PCM and compress audio */ + bool use_mmap; + int setting; + int running; + u64 rd_offs; + u64 wr_offs; + u32 threshold; + u64 rd_org; + u64 wr_org; + u64 rd_total; + u64 wr_total; +}; + +struct uniphier_aio { + struct uniphier_aio_chip *chip; + + struct uniphier_aio_sub sub[2]; + + unsigned int fmt; + /* Set one of AUD_CLK_X */ + int clk_in; + int clk_out; + /* Set one of AUD_PLL_X */ + int pll_in; + int pll_out; + /* Set one of AUD_PLLDIV_X */ + int plldiv; +}; + +struct uniphier_aio_chip { + struct platform_device *pdev; + const struct uniphier_aio_chip_spec *chip_spec; + + struct uniphier_aio *aios; + int num_aios; + int num_wup_aios; + struct uniphier_aio_pll *plls; + int num_plls; + + struct clk *clk; + struct reset_control *rst; + struct regmap *regmap; + struct regmap *regmap_sg; + int active; +}; + +static inline struct uniphier_aio *uniphier_priv(struct snd_soc_dai *dai) +{ + struct uniphier_aio_chip *chip = snd_soc_dai_get_drvdata(dai); + + return &chip->aios[dai->id]; +} + +int uniphier_aiodma_soc_register_platform(struct platform_device *pdev); +extern const struct snd_compr_ops uniphier_aio_compr_ops; + +int uniphier_aio_dai_probe(struct snd_soc_dai *dai); +int uniphier_aio_dai_remove(struct snd_soc_dai *dai); +int uniphier_aio_dai_suspend(struct snd_soc_dai *dai); +int uniphier_aio_dai_resume(struct snd_soc_dai *dai); +int uniphier_aio_probe(struct platform_device *pdev); +int uniphier_aio_remove(struct platform_device *pdev); +extern const struct snd_soc_dai_ops uniphier_aio_i2s_ops; +extern const struct snd_soc_dai_ops uniphier_aio_spdif_ops; + +u64 aio_rb_cnt(struct uniphier_aio_sub *sub); +u64 aio_rbt_cnt_to_end(struct uniphier_aio_sub *sub); +u64 aio_rb_space(struct uniphier_aio_sub *sub); +u64 aio_rb_space_to_end(struct uniphier_aio_sub *sub); + +void aio_iecout_set_enable(struct uniphier_aio_chip *chip, bool enable); +int aio_chip_set_pll(struct uniphier_aio_chip *chip, int pll_id, + unsigned int freq); +void aio_chip_init(struct uniphier_aio_chip *chip); +int aio_init(struct uniphier_aio_sub *sub); +void aio_port_reset(struct uniphier_aio_sub *sub); +int aio_port_set_param(struct uniphier_aio_sub *sub, int pass_through, + const struct snd_pcm_hw_params *params); +void aio_port_set_enable(struct uniphier_aio_sub *sub, int enable); +int aio_port_get_volume(struct uniphier_aio_sub *sub); +void aio_port_set_volume(struct uniphier_aio_sub *sub, int vol); +int aio_if_set_param(struct uniphier_aio_sub *sub, int pass_through); +int aio_oport_set_stream_type(struct uniphier_aio_sub *sub, + enum IEC61937_PC pc); +void aio_src_reset(struct uniphier_aio_sub *sub); +int aio_src_set_param(struct uniphier_aio_sub *sub, + const struct snd_pcm_hw_params *params); +int aio_srcif_set_param(struct uniphier_aio_sub *sub); +int aio_srcch_set_param(struct uniphier_aio_sub *sub); +void aio_srcch_set_enable(struct uniphier_aio_sub *sub, int enable); + +int aiodma_ch_set_param(struct uniphier_aio_sub *sub); +void aiodma_ch_set_enable(struct uniphier_aio_sub *sub, int enable); +int aiodma_rb_set_threshold(struct uniphier_aio_sub *sub, u64 size, u32 th); +int aiodma_rb_set_buffer(struct uniphier_aio_sub *sub, u64 start, u64 end, + int period); +void aiodma_rb_sync(struct uniphier_aio_sub *sub, u64 start, u64 size, + int period); +bool aiodma_rb_is_irq(struct uniphier_aio_sub *sub); +void aiodma_rb_clear_irq(struct uniphier_aio_sub *sub); + +#endif /* SND_UNIPHIER_AIO_H__ */ diff --git a/sound/soc/uniphier/evea.c b/sound/soc/uniphier/evea.c new file mode 100644 index 000000000..f9c10165f --- /dev/null +++ b/sound/soc/uniphier/evea.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier EVEA ADC/DAC codec driver. +// +// Copyright (c) 2016-2017 Socionext Inc. + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "evea" +#define EVEA_RATES SNDRV_PCM_RATE_48000 +#define EVEA_FORMATS SNDRV_PCM_FMTBIT_S32_LE + +#define AADCPOW(n) (0x0078 + 0x04 * (n)) +#define AADCPOW_AADC_POWD BIT(0) +#define ALINSW1 0x0088 +#define ALINSW1_SEL1_SHIFT 3 +#define AHPOUTPOW 0x0098 +#define AHPOUTPOW_HP_ON BIT(4) +#define ALINEPOW 0x009c +#define ALINEPOW_LIN2_POWD BIT(3) +#define ALINEPOW_LIN1_POWD BIT(4) +#define ALO1OUTPOW 0x00a8 +#define ALO1OUTPOW_LO1_ON BIT(4) +#define ALO2OUTPOW 0x00ac +#define ALO2OUTPOW_ADAC2_MUTE BIT(0) +#define ALO2OUTPOW_LO2_ON BIT(4) +#define AANAPOW 0x00b8 +#define AANAPOW_A_POWD BIT(4) +#define ADACSEQ1(n) (0x0144 + 0x40 * (n)) +#define ADACSEQ1_MMUTE BIT(1) +#define ADACSEQ2(n) (0x0160 + 0x40 * (n)) +#define ADACSEQ2_ADACIN_FIX BIT(0) +#define ADAC1ODC 0x0200 +#define ADAC1ODC_HP_DIS_RES_MASK GENMASK(2, 1) +#define ADAC1ODC_HP_DIS_RES_OFF (0x0 << 1) +#define ADAC1ODC_HP_DIS_RES_ON (0x3 << 1) +#define ADAC1ODC_ADAC_RAMPCLT_MASK GENMASK(8, 7) +#define ADAC1ODC_ADAC_RAMPCLT_NORMAL (0x0 << 7) +#define ADAC1ODC_ADAC_RAMPCLT_REDUCE (0x1 << 7) + +struct evea_priv { + struct clk *clk, *clk_exiv; + struct reset_control *rst, *rst_exiv, *rst_adamv; + struct regmap *regmap; + + int switch_lin; + int switch_lo; + int switch_hp; +}; + +static const char * const linsw1_sel1_text[] = { + "LIN1", "LIN2", "LIN3" +}; + +static SOC_ENUM_SINGLE_DECL(linsw1_sel1_enum, + ALINSW1, ALINSW1_SEL1_SHIFT, + linsw1_sel1_text); + +static const struct snd_kcontrol_new linesw1_mux[] = { + SOC_DAPM_ENUM("Line In 1 Source", linsw1_sel1_enum), +}; + +static const struct snd_soc_dapm_widget evea_widgets[] = { + SND_SOC_DAPM_ADC("ADC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("Line In 1 Mux", SND_SOC_NOPM, 0, 0, linesw1_mux), + SND_SOC_DAPM_INPUT("LIN1_LP"), + SND_SOC_DAPM_INPUT("LIN1_RP"), + SND_SOC_DAPM_INPUT("LIN2_LP"), + SND_SOC_DAPM_INPUT("LIN2_RP"), + SND_SOC_DAPM_INPUT("LIN3_LP"), + SND_SOC_DAPM_INPUT("LIN3_RP"), + + SND_SOC_DAPM_DAC("DAC HP", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC LO1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC LO2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("HP1_L"), + SND_SOC_DAPM_OUTPUT("HP1_R"), + SND_SOC_DAPM_OUTPUT("LO2_L"), + SND_SOC_DAPM_OUTPUT("LO2_R"), +}; + +static const struct snd_soc_dapm_route evea_routes[] = { + { "Line In 1", NULL, "ADC" }, + { "ADC", NULL, "Line In 1 Mux" }, + { "Line In 1 Mux", "LIN1", "LIN1_LP" }, + { "Line In 1 Mux", "LIN1", "LIN1_RP" }, + { "Line In 1 Mux", "LIN2", "LIN2_LP" }, + { "Line In 1 Mux", "LIN2", "LIN2_RP" }, + { "Line In 1 Mux", "LIN3", "LIN3_LP" }, + { "Line In 1 Mux", "LIN3", "LIN3_RP" }, + + { "DAC HP", NULL, "Headphone 1" }, + { "DAC LO1", NULL, "Line Out 1" }, + { "DAC LO2", NULL, "Line Out 2" }, + { "HP1_L", NULL, "DAC HP" }, + { "HP1_R", NULL, "DAC HP" }, + { "LO2_L", NULL, "DAC LO2" }, + { "LO2_R", NULL, "DAC LO2" }, +}; + +static void evea_set_power_state_on(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + regmap_update_bits(map, AANAPOW, AANAPOW_A_POWD, + AANAPOW_A_POWD); + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_ON); + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_ADAC_RAMPCLT_MASK, + ADAC1ODC_ADAC_RAMPCLT_REDUCE); + + regmap_update_bits(map, ADACSEQ2(0), ADACSEQ2_ADACIN_FIX, 0); + regmap_update_bits(map, ADACSEQ2(1), ADACSEQ2_ADACIN_FIX, 0); + regmap_update_bits(map, ADACSEQ2(2), ADACSEQ2_ADACIN_FIX, 0); +} + +static void evea_set_power_state_off(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_ON); + + regmap_update_bits(map, ADACSEQ1(0), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + regmap_update_bits(map, ADACSEQ1(1), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + regmap_update_bits(map, ADACSEQ1(2), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + + regmap_update_bits(map, ALO1OUTPOW, ALO1OUTPOW_LO1_ON, 0); + regmap_update_bits(map, ALO2OUTPOW, ALO2OUTPOW_LO2_ON, 0); + regmap_update_bits(map, AHPOUTPOW, AHPOUTPOW_HP_ON, 0); +} + +static int evea_update_switch_lin(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + if (evea->switch_lin) { + regmap_update_bits(map, ALINEPOW, + ALINEPOW_LIN2_POWD | ALINEPOW_LIN1_POWD, + ALINEPOW_LIN2_POWD | ALINEPOW_LIN1_POWD); + + regmap_update_bits(map, AADCPOW(0), AADCPOW_AADC_POWD, + AADCPOW_AADC_POWD); + regmap_update_bits(map, AADCPOW(1), AADCPOW_AADC_POWD, + AADCPOW_AADC_POWD); + } else { + regmap_update_bits(map, AADCPOW(0), AADCPOW_AADC_POWD, 0); + regmap_update_bits(map, AADCPOW(1), AADCPOW_AADC_POWD, 0); + + regmap_update_bits(map, ALINEPOW, + ALINEPOW_LIN2_POWD | ALINEPOW_LIN1_POWD, 0); + } + + return 0; +} + +static int evea_update_switch_lo(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + if (evea->switch_lo) { + regmap_update_bits(map, ADACSEQ1(0), ADACSEQ1_MMUTE, 0); + regmap_update_bits(map, ADACSEQ1(2), ADACSEQ1_MMUTE, 0); + + regmap_update_bits(map, ALO1OUTPOW, ALO1OUTPOW_LO1_ON, + ALO1OUTPOW_LO1_ON); + regmap_update_bits(map, ALO2OUTPOW, + ALO2OUTPOW_ADAC2_MUTE | ALO2OUTPOW_LO2_ON, + ALO2OUTPOW_ADAC2_MUTE | ALO2OUTPOW_LO2_ON); + } else { + regmap_update_bits(map, ADACSEQ1(0), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + regmap_update_bits(map, ADACSEQ1(2), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + + regmap_update_bits(map, ALO1OUTPOW, ALO1OUTPOW_LO1_ON, 0); + regmap_update_bits(map, ALO2OUTPOW, + ALO2OUTPOW_ADAC2_MUTE | ALO2OUTPOW_LO2_ON, + 0); + } + + return 0; +} + +static int evea_update_switch_hp(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + if (evea->switch_hp) { + regmap_update_bits(map, ADACSEQ1(1), ADACSEQ1_MMUTE, 0); + + regmap_update_bits(map, AHPOUTPOW, AHPOUTPOW_HP_ON, + AHPOUTPOW_HP_ON); + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_OFF); + } else { + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_ON); + + regmap_update_bits(map, ADACSEQ1(1), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + + regmap_update_bits(map, AHPOUTPOW, AHPOUTPOW_HP_ON, 0); + } + + return 0; +} + +static void evea_update_switch_all(struct evea_priv *evea) +{ + evea_update_switch_lin(evea); + evea_update_switch_lo(evea); + evea_update_switch_hp(evea); +} + +static int evea_get_switch_lin(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = evea->switch_lin; + + return 0; +} + +static int evea_set_switch_lin(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + if (evea->switch_lin == ucontrol->value.integer.value[0]) + return 0; + + evea->switch_lin = ucontrol->value.integer.value[0]; + + return evea_update_switch_lin(evea); +} + +static int evea_get_switch_lo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = evea->switch_lo; + + return 0; +} + +static int evea_set_switch_lo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + if (evea->switch_lo == ucontrol->value.integer.value[0]) + return 0; + + evea->switch_lo = ucontrol->value.integer.value[0]; + + return evea_update_switch_lo(evea); +} + +static int evea_get_switch_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = evea->switch_hp; + + return 0; +} + +static int evea_set_switch_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + if (evea->switch_hp == ucontrol->value.integer.value[0]) + return 0; + + evea->switch_hp = ucontrol->value.integer.value[0]; + + return evea_update_switch_hp(evea); +} + +static const struct snd_kcontrol_new evea_controls[] = { + SOC_SINGLE_BOOL_EXT("Line Capture Switch", 0, + evea_get_switch_lin, evea_set_switch_lin), + SOC_SINGLE_BOOL_EXT("Line Playback Switch", 0, + evea_get_switch_lo, evea_set_switch_lo), + SOC_SINGLE_BOOL_EXT("Headphone Playback Switch", 0, + evea_get_switch_hp, evea_set_switch_hp), +}; + +static int evea_codec_probe(struct snd_soc_component *component) +{ + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + evea->switch_lin = 1; + evea->switch_lo = 1; + evea->switch_hp = 1; + + evea_set_power_state_on(evea); + evea_update_switch_all(evea); + + return 0; +} + +static int evea_codec_suspend(struct snd_soc_component *component) +{ + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + evea_set_power_state_off(evea); + + reset_control_assert(evea->rst_adamv); + reset_control_assert(evea->rst_exiv); + reset_control_assert(evea->rst); + + clk_disable_unprepare(evea->clk_exiv); + clk_disable_unprepare(evea->clk); + + return 0; +} + +static int evea_codec_resume(struct snd_soc_component *component) +{ + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + int ret; + + ret = clk_prepare_enable(evea->clk); + if (ret) + return ret; + + ret = clk_prepare_enable(evea->clk_exiv); + if (ret) + goto err_out_clock; + + ret = reset_control_deassert(evea->rst); + if (ret) + goto err_out_clock_exiv; + + ret = reset_control_deassert(evea->rst_exiv); + if (ret) + goto err_out_reset; + + ret = reset_control_deassert(evea->rst_adamv); + if (ret) + goto err_out_reset_exiv; + + evea_set_power_state_on(evea); + evea_update_switch_all(evea); + + return 0; + +err_out_reset_exiv: + reset_control_assert(evea->rst_exiv); + +err_out_reset: + reset_control_assert(evea->rst); + +err_out_clock_exiv: + clk_disable_unprepare(evea->clk_exiv); + +err_out_clock: + clk_disable_unprepare(evea->clk); + + return ret; +} + +static struct snd_soc_component_driver soc_codec_evea = { + .probe = evea_codec_probe, + .suspend = evea_codec_suspend, + .resume = evea_codec_resume, + .dapm_widgets = evea_widgets, + .num_dapm_widgets = ARRAY_SIZE(evea_widgets), + .dapm_routes = evea_routes, + .num_dapm_routes = ARRAY_SIZE(evea_routes), + .controls = evea_controls, + .num_controls = ARRAY_SIZE(evea_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver soc_dai_evea[] = { + { + .name = DRV_NAME "-line1", + .playback = { + .stream_name = "Line Out 1", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = "Line In 1", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + }, + { + .name = DRV_NAME "-hp1", + .playback = { + .stream_name = "Headphone 1", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + }, + { + .name = DRV_NAME "-lo2", + .playback = { + .stream_name = "Line Out 2", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + }, +}; + +static const struct regmap_config evea_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xffc, + .cache_type = REGCACHE_NONE, +}; + +static int evea_probe(struct platform_device *pdev) +{ + struct evea_priv *evea; + struct resource *res; + void __iomem *preg; + int ret; + + evea = devm_kzalloc(&pdev->dev, sizeof(struct evea_priv), GFP_KERNEL); + if (!evea) + return -ENOMEM; + + evea->clk = devm_clk_get(&pdev->dev, "evea"); + if (IS_ERR(evea->clk)) + return PTR_ERR(evea->clk); + + evea->clk_exiv = devm_clk_get(&pdev->dev, "exiv"); + if (IS_ERR(evea->clk_exiv)) + return PTR_ERR(evea->clk_exiv); + + evea->rst = devm_reset_control_get_shared(&pdev->dev, "evea"); + if (IS_ERR(evea->rst)) + return PTR_ERR(evea->rst); + + evea->rst_exiv = devm_reset_control_get_shared(&pdev->dev, "exiv"); + if (IS_ERR(evea->rst_exiv)) + return PTR_ERR(evea->rst_exiv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + preg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(preg)) + return PTR_ERR(preg); + + evea->regmap = devm_regmap_init_mmio(&pdev->dev, preg, + &evea_regmap_config); + if (IS_ERR(evea->regmap)) + return PTR_ERR(evea->regmap); + + ret = clk_prepare_enable(evea->clk); + if (ret) + return ret; + + ret = clk_prepare_enable(evea->clk_exiv); + if (ret) + goto err_out_clock; + + ret = reset_control_deassert(evea->rst); + if (ret) + goto err_out_clock_exiv; + + ret = reset_control_deassert(evea->rst_exiv); + if (ret) + goto err_out_reset; + + /* ADAMV will hangup if EXIV reset is asserted */ + evea->rst_adamv = devm_reset_control_get_shared(&pdev->dev, "adamv"); + if (IS_ERR(evea->rst_adamv)) { + ret = PTR_ERR(evea->rst_adamv); + goto err_out_reset_exiv; + } + + ret = reset_control_deassert(evea->rst_adamv); + if (ret) + goto err_out_reset_exiv; + + platform_set_drvdata(pdev, evea); + + ret = devm_snd_soc_register_component(&pdev->dev, &soc_codec_evea, + soc_dai_evea, ARRAY_SIZE(soc_dai_evea)); + if (ret) + goto err_out_reset_adamv; + + return 0; + +err_out_reset_adamv: + reset_control_assert(evea->rst_adamv); + +err_out_reset_exiv: + reset_control_assert(evea->rst_exiv); + +err_out_reset: + reset_control_assert(evea->rst); + +err_out_clock_exiv: + clk_disable_unprepare(evea->clk_exiv); + +err_out_clock: + clk_disable_unprepare(evea->clk); + + return ret; +} + +static int evea_remove(struct platform_device *pdev) +{ + struct evea_priv *evea = platform_get_drvdata(pdev); + + reset_control_assert(evea->rst_adamv); + reset_control_assert(evea->rst_exiv); + reset_control_assert(evea->rst); + + clk_disable_unprepare(evea->clk_exiv); + clk_disable_unprepare(evea->clk); + + return 0; +} + +static const struct of_device_id evea_of_match[] = { + { .compatible = "socionext,uniphier-evea", }, + {} +}; +MODULE_DEVICE_TABLE(of, evea_of_match); + +static struct platform_driver evea_codec_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(evea_of_match), + }, + .probe = evea_probe, + .remove = evea_remove, +}; +module_platform_driver(evea_codec_driver); + +MODULE_AUTHOR("Katsuhiro Suzuki "); +MODULE_DESCRIPTION("UniPhier EVEA codec driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig new file mode 100644 index 000000000..aa5011894 --- /dev/null +++ b/sound/soc/ux500/Kconfig @@ -0,0 +1,32 @@ +# +# Ux500 SoC audio configuration +# +menuconfig SND_SOC_UX500 + tristate "SoC Audio support for Ux500 platform" + depends on SND_SOC + depends on MFD_DB8500_PRCMU + help + Say Y if you want to enable ASoC-support for + any of the Ux500 platforms (e.g. U8500). + +config SND_SOC_UX500_PLAT_MSP_I2S + tristate + depends on SND_SOC_UX500 + +config SND_SOC_UX500_PLAT_DMA + tristate "Platform - DB8500 (DMA)" + depends on SND_SOC_UX500 + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to enable the Ux500 platform-driver. + +config SND_SOC_UX500_MACH_MOP500 + tristate "Machine - MOP500 (Ux500 + AB8500)" + depends on AB8500_CORE && AB8500_GPADC && SND_SOC_UX500 + select SND_SOC_AB8500_CODEC + select SND_SOC_UX500_PLAT_MSP_I2S + select SND_SOC_UX500_PLAT_DMA + help + Select this to enable the MOP500 machine-driver. + This will enable platform-drivers for: Ux500 + This will enable codec-drivers for: AB8500 diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile new file mode 100644 index 000000000..e7d6de51b --- /dev/null +++ b/sound/soc/ux500/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# Ux500 Platform Support + +snd-soc-ux500-plat-msp-i2s-objs := ux500_msp_dai.o ux500_msp_i2s.o +obj-$(CONFIG_SND_SOC_UX500_PLAT_MSP_I2S) += snd-soc-ux500-plat-msp-i2s.o + +snd-soc-ux500-plat-dma-objs := ux500_pcm.o +obj-$(CONFIG_SND_SOC_UX500_PLAT_DMA) += snd-soc-ux500-plat-dma.o + +snd-soc-ux500-mach-mop500-objs := mop500.o mop500_ab8500.o +obj-$(CONFIG_SND_SOC_UX500_MACH_MOP500) += snd-soc-ux500-mach-mop500.o diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c new file mode 100644 index 000000000..c60a57797 --- /dev/null +++ b/sound/soc/ux500/mop500.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja (ola.o.lilja@stericsson.com) + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" + +#include "mop500_ab8500.h" + +/* Define the whole MOP500 soundcard, linking platform to the codec-drivers */ +static struct snd_soc_dai_link mop500_dai_links[] = { + { + .name = "ab8500_0", + .stream_name = "ab8500_0", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "ab8500-codec-dai.0", + .platform_name = "ux500-msp-i2s.1", + .codec_name = "ab8500-codec.0", + .init = mop500_ab8500_machine_init, + .ops = mop500_ab8500_ops, + }, + { + .name = "ab8500_1", + .stream_name = "ab8500_1", + .cpu_dai_name = "ux500-msp-i2s.3", + .codec_dai_name = "ab8500-codec-dai.1", + .platform_name = "ux500-msp-i2s.3", + .codec_name = "ab8500-codec.0", + .init = NULL, + .ops = mop500_ab8500_ops, + }, +}; + +static struct snd_soc_card mop500_card = { + .name = "MOP500-card", + .owner = THIS_MODULE, + .probe = NULL, + .dai_link = mop500_dai_links, + .num_links = ARRAY_SIZE(mop500_dai_links), +}; + +static void mop500_of_node_put(void) +{ + int i; + + for (i = 0; i < 2; i++) { + of_node_put(mop500_dai_links[i].cpu_of_node); + of_node_put(mop500_dai_links[i].codec_of_node); + } +} + +static int mop500_of_probe(struct platform_device *pdev, + struct device_node *np) +{ + struct device_node *codec_np, *msp_np[2]; + int i; + + msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); + msp_np[1] = of_parse_phandle(np, "stericsson,cpu-dai", 1); + codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); + + if (!(msp_np[0] && msp_np[1] && codec_np)) { + dev_err(&pdev->dev, "Phandle missing or invalid\n"); + mop500_of_node_put(); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + mop500_dai_links[i].cpu_of_node = msp_np[i]; + mop500_dai_links[i].cpu_dai_name = NULL; + mop500_dai_links[i].platform_of_node = msp_np[i]; + mop500_dai_links[i].platform_name = NULL; + mop500_dai_links[i].codec_of_node = codec_np; + mop500_dai_links[i].codec_name = NULL; + } + + snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name"); + + return 0; +} + +static int mop500_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + int ret; + + dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); + + mop500_card.dev = &pdev->dev; + + if (np) { + ret = mop500_of_probe(pdev, np); + if (ret) + return ret; + } + + dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n", + __func__, mop500_card.name); + + snd_soc_card_set_drvdata(&mop500_card, NULL); + + dev_dbg(&pdev->dev, "%s: Card %s: num_links = %d\n", + __func__, mop500_card.name, mop500_card.num_links); + dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: name = %s\n", + __func__, mop500_card.name, mop500_card.dai_link[0].name); + dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: stream_name = %s\n", + __func__, mop500_card.name, + mop500_card.dai_link[0].stream_name); + + ret = snd_soc_register_card(&mop500_card); + if (ret) + dev_err(&pdev->dev, + "Error: snd_soc_register_card failed (%d)!\n", ret); + + return ret; +} + +static int mop500_remove(struct platform_device *pdev) +{ + struct snd_soc_card *mop500_card = platform_get_drvdata(pdev); + + pr_debug("%s: Enter.\n", __func__); + + snd_soc_unregister_card(mop500_card); + mop500_ab8500_remove(mop500_card); + mop500_of_node_put(); + + return 0; +} + +static const struct of_device_id snd_soc_mop500_match[] = { + { .compatible = "stericsson,snd-soc-mop500", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_soc_mop500_match); + +static struct platform_driver snd_soc_mop500_driver = { + .driver = { + .name = "snd-soc-mop500", + .of_match_table = snd_soc_mop500_match, + }, + .probe = mop500_probe, + .remove = mop500_remove, +}; + +module_platform_driver(snd_soc_mop500_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC MOP500 board driver"); +MODULE_AUTHOR("Ola Lilja"); diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c new file mode 100644 index 000000000..85d810d76 --- /dev/null +++ b/sound/soc/ux500/mop500_ab8500.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Kristoffer Karlsson + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" +#include "mop500_ab8500.h" +#include "../codecs/ab8500-codec.h" + +#define TX_SLOT_MONO 0x0008 +#define TX_SLOT_STEREO 0x000a +#define RX_SLOT_MONO 0x0001 +#define RX_SLOT_STEREO 0x0003 +#define TX_SLOT_8CH 0x00FF +#define RX_SLOT_8CH 0x00FF + +#define DEF_TX_SLOTS TX_SLOT_STEREO +#define DEF_RX_SLOTS RX_SLOT_MONO + +#define DRIVERMODE_NORMAL 0 +#define DRIVERMODE_CODEC_ONLY 1 + +/* Slot configuration */ +static unsigned int tx_slots = DEF_TX_SLOTS; +static unsigned int rx_slots = DEF_RX_SLOTS; + +/* Configuration consistency parameters */ +static DEFINE_MUTEX(mop500_ab8500_params_lock); +static unsigned long mop500_ab8500_usage; +static int mop500_ab8500_rate; +static int mop500_ab8500_channels; + +/* Clocks */ +static const char * const enum_mclk[] = { + "SYSCLK", + "ULPCLK" +}; +enum mclk { + MCLK_SYSCLK, + MCLK_ULPCLK, +}; + +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); + +/* Private data for machine-part MOP500<->AB8500 */ +struct mop500_ab8500_drvdata { + /* Clocks */ + enum mclk mclk_sel; + struct clk *clk_ptr_intclk; + struct clk *clk_ptr_sysclk; + struct clk *clk_ptr_ulpclk; +}; + +static inline const char *get_mclk_str(enum mclk mclk_sel) +{ + switch (mclk_sel) { + case MCLK_SYSCLK: + return "SYSCLK"; + case MCLK_ULPCLK: + return "ULPCLK"; + default: + return "Unknown"; + } +} + +static int mop500_ab8500_set_mclk(struct device *dev, + struct mop500_ab8500_drvdata *drvdata) +{ + int status; + struct clk *clk_ptr; + + if (IS_ERR(drvdata->clk_ptr_intclk)) { + dev_err(dev, + "%s: ERROR: intclk not initialized!\n", __func__); + return -EIO; + } + + switch (drvdata->mclk_sel) { + case MCLK_SYSCLK: + clk_ptr = drvdata->clk_ptr_sysclk; + break; + case MCLK_ULPCLK: + clk_ptr = drvdata->clk_ptr_ulpclk; + break; + default: + return -EINVAL; + } + + if (IS_ERR(clk_ptr)) { + dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__, + get_mclk_str(drvdata->mclk_sel)); + return -EIO; + } + + status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr); + if (status) + dev_err(dev, + "%s: ERROR: Setting intclk parent to %s failed (ret = %d)!", + __func__, get_mclk_str(drvdata->mclk_sel), status); + else + dev_dbg(dev, + "%s: intclk parent changed to %s.\n", + __func__, get_mclk_str(drvdata->mclk_sel)); + + return status; +} + +/* + * Control-events + */ + +static int mclk_input_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mop500_ab8500_drvdata *drvdata = + snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = drvdata->mclk_sel; + + return 0; +} + +static int mclk_input_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mop500_ab8500_drvdata *drvdata = + snd_soc_card_get_drvdata(card); + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val > (unsigned int)MCLK_ULPCLK) + return -EINVAL; + if (drvdata->mclk_sel == val) + return 0; + + drvdata->mclk_sel = val; + + return 1; +} + +/* + * Controls + */ + +static struct snd_kcontrol_new mop500_ab8500_ctrls[] = { + SOC_ENUM_EXT("Master Clock Select", + soc_enum_mclk, + mclk_input_control_get, mclk_input_control_put), + SOC_DAPM_PIN_SWITCH("Headset Left"), + SOC_DAPM_PIN_SWITCH("Headset Right"), + SOC_DAPM_PIN_SWITCH("Earpiece"), + SOC_DAPM_PIN_SWITCH("Speaker Left"), + SOC_DAPM_PIN_SWITCH("Speaker Right"), + SOC_DAPM_PIN_SWITCH("LineOut Left"), + SOC_DAPM_PIN_SWITCH("LineOut Right"), + SOC_DAPM_PIN_SWITCH("Vibra 1"), + SOC_DAPM_PIN_SWITCH("Vibra 2"), + SOC_DAPM_PIN_SWITCH("Mic 1"), + SOC_DAPM_PIN_SWITCH("Mic 2"), + SOC_DAPM_PIN_SWITCH("LineIn Left"), + SOC_DAPM_PIN_SWITCH("LineIn Right"), + SOC_DAPM_PIN_SWITCH("DMic 1"), + SOC_DAPM_PIN_SWITCH("DMic 2"), + SOC_DAPM_PIN_SWITCH("DMic 3"), + SOC_DAPM_PIN_SWITCH("DMic 4"), + SOC_DAPM_PIN_SWITCH("DMic 5"), + SOC_DAPM_PIN_SWITCH("DMic 6"), +}; + +/* ASoC */ + +static int mop500_ab8500_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* Set audio-clock source */ + return mop500_ab8500_set_mclk(rtd->card->dev, + snd_soc_card_get_drvdata(rtd->card)); +} + +static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + + dev_dbg(dev, "%s: Enter\n", __func__); + + /* Reset slots configuration to default(s) */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tx_slots = DEF_TX_SLOTS; + else + rx_slots = DEF_RX_SLOTS; +} + +static int mop500_ab8500_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct device *dev = rtd->card->dev; + unsigned int fmt; + int channels, ret = 0, driver_mode, slots; + unsigned int sw_codec, sw_cpu; + bool is_playback; + + dev_dbg(dev, "%s: Enter\n", __func__); + + dev_dbg(dev, "%s: substream->pcm->name = %s\n" + "substream->pcm->id = %s.\n" + "substream->name = %s.\n" + "substream->number = %d.\n", + __func__, + substream->pcm->name, + substream->pcm->id, + substream->name, + substream->number); + + /* Ensure configuration consistency between DAIs */ + mutex_lock(&mop500_ab8500_params_lock); + if (mop500_ab8500_usage) { + if (mop500_ab8500_rate != params_rate(params) || + mop500_ab8500_channels != params_channels(params)) { + mutex_unlock(&mop500_ab8500_params_lock); + return -EBUSY; + } + } else { + mop500_ab8500_rate = params_rate(params); + mop500_ab8500_channels = params_channels(params); + } + __set_bit(cpu_dai->id, &mop500_ab8500_usage); + mutex_unlock(&mop500_ab8500_params_lock); + + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S32_LE: + sw_cpu = 32; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + sw_cpu = 16; + break; + + default: + return -EINVAL; + } + + /* Setup codec depending on driver-mode */ + if (channels == 8) + driver_mode = DRIVERMODE_CODEC_ONLY; + else + driver_mode = DRIVERMODE_NORMAL; + dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__, + (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); + + /* Setup format */ + + if (driver_mode == DRIVERMODE_NORMAL) { + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CONT; + } else { + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_GATED; + } + + ret = snd_soc_runtime_set_dai_fmt(rtd, fmt); + if (ret) + return ret; + + /* Setup TDM-slots */ + + is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + switch (channels) { + case 1: + slots = 16; + tx_slots = (is_playback) ? TX_SLOT_MONO : 0; + rx_slots = (is_playback) ? 0 : RX_SLOT_MONO; + break; + case 2: + slots = 16; + tx_slots = (is_playback) ? TX_SLOT_STEREO : 0; + rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO; + break; + case 8: + slots = 16; + tx_slots = (is_playback) ? TX_SLOT_8CH : 0; + rx_slots = (is_playback) ? 0 : RX_SLOT_8CH; + break; + default: + return -EINVAL; + } + + if (driver_mode == DRIVERMODE_NORMAL) + sw_codec = sw_cpu; + else + sw_codec = 20; + + dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, + tx_slots, rx_slots); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, + sw_cpu); + if (ret) + return ret; + + dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, + tx_slots, rx_slots); + ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, + sw_codec); + if (ret) + return ret; + + return 0; +} + +static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + mutex_lock(&mop500_ab8500_params_lock); + __clear_bit(cpu_dai->id, &mop500_ab8500_usage); + mutex_unlock(&mop500_ab8500_params_lock); + + return 0; +} + +struct snd_soc_ops mop500_ab8500_ops[] = { + { + .hw_params = mop500_ab8500_hw_params, + .hw_free = mop500_ab8500_hw_free, + .startup = mop500_ab8500_startup, + .shutdown = mop500_ab8500_shutdown, + } +}; + +int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + struct device *dev = rtd->card->dev; + struct mop500_ab8500_drvdata *drvdata; + int ret; + + dev_dbg(dev, "%s Enter.\n", __func__); + + /* Create driver private-data struct */ + drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata), + GFP_KERNEL); + + if (!drvdata) + return -ENOMEM; + + snd_soc_card_set_drvdata(rtd->card, drvdata); + + /* Setup clocks */ + + drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk"); + if (IS_ERR(drvdata->clk_ptr_sysclk)) + dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n", + __func__); + drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk"); + if (IS_ERR(drvdata->clk_ptr_ulpclk)) + dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n", + __func__); + drvdata->clk_ptr_intclk = clk_get(dev, "intclk"); + if (IS_ERR(drvdata->clk_ptr_intclk)) + dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n", + __func__); + + /* Set intclk default parent to ulpclk */ + drvdata->mclk_sel = MCLK_ULPCLK; + ret = mop500_ab8500_set_mclk(dev, drvdata); + if (ret < 0) + dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n", + __func__); + + drvdata->mclk_sel = MCLK_ULPCLK; + + /* Add controls */ + ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls, + ARRAY_SIZE(mop500_ab8500_ctrls)); + if (ret < 0) { + pr_err("%s: Failed to add machine-controls (%d)!\n", + __func__, ret); + return ret; + } + + ret = snd_soc_dapm_disable_pin(dapm, "Earpiece"); + ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "Mic 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "Mic 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 3"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 4"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 5"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 6"); + + return ret; +} + +void mop500_ab8500_remove(struct snd_soc_card *card) +{ + struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card); + + if (drvdata->clk_ptr_sysclk != NULL) + clk_put(drvdata->clk_ptr_sysclk); + if (drvdata->clk_ptr_ulpclk != NULL) + clk_put(drvdata->clk_ptr_ulpclk); + if (drvdata->clk_ptr_intclk != NULL) + clk_put(drvdata->clk_ptr_intclk); + + snd_soc_card_set_drvdata(card, drvdata); +} diff --git a/sound/soc/ux500/mop500_ab8500.h b/sound/soc/ux500/mop500_ab8500.h new file mode 100644 index 000000000..cca5b3396 --- /dev/null +++ b/sound/soc/ux500/mop500_ab8500.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef MOP500_AB8500_H +#define MOP500_AB8500_H + +extern struct snd_soc_ops mop500_ab8500_ops[]; + +int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *runtime); +void mop500_ab8500_remove(struct snd_soc_card *card); + +#endif diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c new file mode 100644 index 000000000..625b72a5f --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -0,0 +1,860 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Roger Nilsson + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ux500_msp_i2s.h" +#include "ux500_msp_dai.h" +#include "ux500_pcm.h" + +static int setup_pcm_multichan(struct snd_soc_dai *dai, + struct ux500_msp_config *msp_config) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct msp_multichannel_config *multi = + &msp_config->multichannel_config; + + if (drvdata->slots > 1) { + msp_config->multichannel_configured = 1; + + multi->tx_multichannel_enable = true; + multi->rx_multichannel_enable = true; + multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED; + + multi->tx_channel_0_enable = drvdata->tx_mask; + multi->tx_channel_1_enable = 0; + multi->tx_channel_2_enable = 0; + multi->tx_channel_3_enable = 0; + + multi->rx_channel_0_enable = drvdata->rx_mask; + multi->rx_channel_1_enable = 0; + multi->rx_channel_2_enable = 0; + multi->rx_channel_3_enable = 0; + + dev_dbg(dai->dev, + "%s: Multichannel enabled. Slots: %d, TX: %u, RX: %u\n", + __func__, drvdata->slots, multi->tx_channel_0_enable, + multi->rx_channel_0_enable); + } + + return 0; +} + +static int setup_frameper(struct snd_soc_dai *dai, unsigned int rate, + struct msp_protdesc *prot_desc) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + switch (drvdata->slots) { + case 1: + switch (rate) { + case 8000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_8_KHZ; + break; + + case 16000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_16_KHZ; + break; + + case 44100: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_44_1_KHZ; + break; + + case 48000: + prot_desc->frame_period = + FRAME_PER_SINGLE_SLOT_48_KHZ; + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported sample-rate (freq = %d)!\n", + __func__, rate); + return -EINVAL; + } + break; + + case 2: + prot_desc->frame_period = FRAME_PER_2_SLOTS; + break; + + case 8: + prot_desc->frame_period = FRAME_PER_8_SLOTS; + break; + + case 16: + prot_desc->frame_period = FRAME_PER_16_SLOTS; + break; + default: + dev_err(dai->dev, + "%s: Error: Unsupported slot-count (slots = %d)!\n", + __func__, drvdata->slots); + return -EINVAL; + } + + prot_desc->clocks_per_frame = + prot_desc->frame_period+1; + + dev_dbg(dai->dev, "%s: Clocks per frame: %u\n", + __func__, + prot_desc->clocks_per_frame); + + return 0; +} + +static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, + struct msp_protdesc *prot_desc) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + u32 frame_length = MSP_FRAME_LEN_1; + + prot_desc->frame_width = 0; + + switch (drvdata->slots) { + case 1: + frame_length = MSP_FRAME_LEN_1; + break; + + case 2: + frame_length = MSP_FRAME_LEN_2; + break; + + case 8: + frame_length = MSP_FRAME_LEN_8; + break; + + case 16: + frame_length = MSP_FRAME_LEN_16; + break; + default: + dev_err(dai->dev, + "%s: Error: Unsupported slot-count (slots = %d)!\n", + __func__, drvdata->slots); + return -EINVAL; + } + + prot_desc->tx_frame_len_1 = frame_length; + prot_desc->rx_frame_len_1 = frame_length; + prot_desc->tx_frame_len_2 = frame_length; + prot_desc->rx_frame_len_2 = frame_length; + + prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16; + prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16; + + return setup_frameper(dai, rate, prot_desc); +} + +static int setup_clocking(struct snd_soc_dai *dai, + unsigned int fmt, + struct ux500_msp_config *msp_config) +{ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_NB_IF: + msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT; + msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT; + + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported inversion (fmt = 0x%x)!\n", + __func__, fmt); + + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + dev_dbg(dai->dev, "%s: Codec is master.\n", __func__); + + msp_config->iodelay = 0x20; + msp_config->rx_fsync_sel = 0; + msp_config->tx_fsync_sel = 1 << TFSSEL_SHIFT; + msp_config->tx_clk_sel = 0; + msp_config->rx_clk_sel = 0; + msp_config->srg_clk_sel = 0x2 << SCKSEL_SHIFT; + + break; + + case SND_SOC_DAIFMT_CBS_CFS: + dev_dbg(dai->dev, "%s: Codec is slave.\n", __func__); + + msp_config->tx_clk_sel = TX_CLK_SEL_SRG; + msp_config->tx_fsync_sel = TX_SYNC_SRG_PROG; + msp_config->rx_clk_sel = RX_CLK_SEL_SRG; + msp_config->rx_fsync_sel = RX_SYNC_SRG; + msp_config->srg_clk_sel = 1 << SCKSEL_SHIFT; + + break; + + default: + dev_err(dai->dev, "%s: Error: Unsupported master (fmt = 0x%x)!\n", + __func__, fmt); + + return -EINVAL; + } + + return 0; +} + +static int setup_pcm_protdesc(struct snd_soc_dai *dai, + unsigned int fmt, + struct msp_protdesc *prot_desc) +{ + prot_desc->rx_phase_mode = MSP_SINGLE_PHASE; + prot_desc->tx_phase_mode = MSP_SINGLE_PHASE; + prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE; + prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE; + prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_HI); + prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_HI << RFSPOL_SHIFT; + + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) { + dev_dbg(dai->dev, "%s: DSP_A.\n", __func__); + prot_desc->rx_clk_pol = MSP_RISING_EDGE; + prot_desc->tx_clk_pol = MSP_FALLING_EDGE; + + prot_desc->rx_data_delay = MSP_DELAY_1; + prot_desc->tx_data_delay = MSP_DELAY_1; + } else { + dev_dbg(dai->dev, "%s: DSP_B.\n", __func__); + prot_desc->rx_clk_pol = MSP_FALLING_EDGE; + prot_desc->tx_clk_pol = MSP_RISING_EDGE; + + prot_desc->rx_data_delay = MSP_DELAY_0; + prot_desc->tx_data_delay = MSP_DELAY_0; + } + + prot_desc->rx_half_word_swap = MSP_SWAP_NONE; + prot_desc->tx_half_word_swap = MSP_SWAP_NONE; + prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR; + prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR; + prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE; + + return 0; +} + +static int setup_i2s_protdesc(struct msp_protdesc *prot_desc) +{ + prot_desc->rx_phase_mode = MSP_DUAL_PHASE; + prot_desc->tx_phase_mode = MSP_DUAL_PHASE; + prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC; + prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC; + prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST; + prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_LO); + prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_LO << RFSPOL_SHIFT; + + prot_desc->rx_frame_len_1 = MSP_FRAME_LEN_1; + prot_desc->rx_frame_len_2 = MSP_FRAME_LEN_1; + prot_desc->tx_frame_len_1 = MSP_FRAME_LEN_1; + prot_desc->tx_frame_len_2 = MSP_FRAME_LEN_1; + prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16; + prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16; + prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16; + + prot_desc->rx_clk_pol = MSP_RISING_EDGE; + prot_desc->tx_clk_pol = MSP_FALLING_EDGE; + + prot_desc->rx_data_delay = MSP_DELAY_0; + prot_desc->tx_data_delay = MSP_DELAY_0; + + prot_desc->tx_half_word_swap = MSP_SWAP_NONE; + prot_desc->rx_half_word_swap = MSP_SWAP_NONE; + prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR; + prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR; + prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE; + + return 0; +} + +static int setup_msp_config(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + struct ux500_msp_config *msp_config) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct msp_protdesc *prot_desc = &msp_config->protdesc; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int fmt = drvdata->fmt; + int ret; + + memset(msp_config, 0, sizeof(*msp_config)); + + msp_config->f_inputclk = drvdata->master_clk; + + msp_config->tx_fifo_config = TX_FIFO_ENABLE; + msp_config->rx_fifo_config = RX_FIFO_ENABLE; + msp_config->def_elem_len = 1; + msp_config->direction = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + MSP_DIR_TX : MSP_DIR_RX; + msp_config->data_size = MSP_DATA_BITS_32; + msp_config->frame_freq = runtime->rate; + + dev_dbg(dai->dev, "%s: f_inputclk = %u, frame_freq = %u.\n", + __func__, msp_config->f_inputclk, msp_config->frame_freq); + /* To avoid division by zero */ + prot_desc->clocks_per_frame = 1; + + dev_dbg(dai->dev, "%s: rate: %u, channels: %d.\n", __func__, + runtime->rate, runtime->channels); + switch (fmt & + (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) { + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__); + + msp_config->default_protdesc = 1; + msp_config->protocol = MSP_I2S_PROTOCOL; + break; + + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__); + + msp_config->data_size = MSP_DATA_BITS_16; + msp_config->protocol = MSP_I2S_PROTOCOL; + + ret = setup_i2s_protdesc(prot_desc); + if (ret < 0) + return ret; + + break; + + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM: + dev_dbg(dai->dev, "%s: PCM format.\n", __func__); + + msp_config->data_size = MSP_DATA_BITS_16; + msp_config->protocol = MSP_PCM_PROTOCOL; + + ret = setup_pcm_protdesc(dai, fmt, prot_desc); + if (ret < 0) + return ret; + + ret = setup_pcm_multichan(dai, msp_config); + if (ret < 0) + return ret; + + ret = setup_pcm_framing(dai, runtime->rate, prot_desc); + if (ret < 0) + return ret; + + break; + + default: + dev_err(dai->dev, "%s: Error: Unsupported format (%d)!\n", + __func__, fmt); + return -EINVAL; + } + + return setup_clocking(dai, fmt, msp_config); +} + +static int ux500_msp_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, + snd_pcm_stream_str(substream)); + + /* Enable regulator */ + ret = regulator_enable(drvdata->reg_vape); + if (ret != 0) { + dev_err(drvdata->msp->dev, + "%s: Failed to enable regulator!\n", __func__); + return ret; + } + + /* Prepare and enable clocks */ + dev_dbg(dai->dev, "%s: Enabling MSP-clocks.\n", __func__); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) { + dev_err(drvdata->msp->dev, + "%s: Failed to prepare/enable pclk!\n", __func__); + goto err_pclk; + } + + ret = clk_prepare_enable(drvdata->clk); + if (ret) { + dev_err(drvdata->msp->dev, + "%s: Failed to prepare/enable clk!\n", __func__); + goto err_clk; + } + + return ret; +err_clk: + clk_disable_unprepare(drvdata->pclk); +err_pclk: + regulator_disable(drvdata->reg_vape); + return ret; +} + +static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, + snd_pcm_stream_str(substream)); + + if (drvdata->vape_opp_constraint == 1) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "ux500_msp_i2s", 50); + drvdata->vape_opp_constraint = 0; + } + + if (ux500_msp_i2s_close(drvdata->msp, + is_playback ? MSP_DIR_TX : MSP_DIR_RX)) { + dev_err(dai->dev, + "%s: Error: MSP %d (%s): Unable to close i2s.\n", + __func__, dai->id, snd_pcm_stream_str(substream)); + } + + /* Disable and unprepare clocks */ + clk_disable_unprepare(drvdata->clk); + clk_disable_unprepare(drvdata->pclk); + + /* Disable regulator */ + ret = regulator_disable(drvdata->reg_vape); + if (ret < 0) + dev_err(dai->dev, + "%s: ERROR: Failed to disable regulator (%d)!\n", + __func__, ret); +} + +static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_msp_config msp_config; + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__, + dai->id, snd_pcm_stream_str(substream), runtime->rate); + + setup_msp_config(substream, dai, &msp_config); + + ret = ux500_msp_i2s_open(drvdata->msp, &msp_config); + if (ret < 0) { + dev_err(dai->dev, "%s: Error: msp_setup failed (ret = %d)!\n", + __func__, ret); + return ret; + } + + /* Set OPP-level */ + if ((drvdata->fmt & SND_SOC_DAIFMT_MASTER_MASK) && + (drvdata->msp->f_bitclk > 19200000)) { + /* If the bit-clock is higher than 19.2MHz, Vape should be + * run in 100% OPP. Only when bit-clock is used (MSP master) + */ + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "ux500-msp-i2s", 100); + drvdata->vape_opp_constraint = 1; + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, + "ux500-msp-i2s", 50); + drvdata->vape_opp_constraint = 0; + } + + return ret; +} + +static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int mask, slots_active; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", + __func__, dai->id, snd_pcm_stream_str(substream)); + + switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, 2); + break; + + case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_DSP_A: + mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + drvdata->tx_mask : + drvdata->rx_mask; + + slots_active = hweight32(mask); + dev_dbg(dai->dev, "TDM-slots active: %d", slots_active); + + snd_pcm_hw_constraint_single(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + slots_active); + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported protocol (fmt = 0x%x)!\n", + __func__, drvdata->fmt); + return -EINVAL; + } + + return 0; +} + +static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d: Enter.\n", __func__, dai->id); + + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_MASTER_MASK)) { + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported protocol/master (fmt = 0x%x)!\n", + __func__, drvdata->fmt); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + break; + + default: + dev_err(dai->dev, + "%s: Error: Unsupported inversion (fmt = 0x%x)!\n", + __func__, drvdata->fmt); + return -EINVAL; + } + + drvdata->fmt = fmt; + return 0; +} + +static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + unsigned int cap; + + switch (slots) { + case 1: + cap = 0x01; + break; + case 2: + cap = 0x03; + break; + case 8: + cap = 0xFF; + break; + case 16: + cap = 0xFFFF; + break; + default: + dev_err(dai->dev, "%s: Error: Unsupported slot-count (%d)!\n", + __func__, slots); + return -EINVAL; + } + drvdata->slots = slots; + + if (!(slot_width == 16)) { + dev_err(dai->dev, "%s: Error: Unsupported slot-width (%d)!\n", + __func__, slot_width); + return -EINVAL; + } + drvdata->slot_width = slot_width; + + drvdata->tx_mask = tx_mask & cap; + drvdata->rx_mask = rx_mask & cap; + + return 0; +} + +static int ux500_msp_dai_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d: Enter. clk-id: %d, freq: %u.\n", + __func__, dai->id, clk_id, freq); + + switch (clk_id) { + case UX500_MSP_MASTER_CLOCK: + drvdata->master_clk = freq; + break; + + default: + dev_err(dai->dev, "%s: MSP %d: Invalid clk-id (%d)!\n", + __func__, dai->id, clk_id); + return -EINVAL; + } + + return 0; +} + +static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n", + __func__, dai->id, snd_pcm_stream_str(substream), + (int)drvdata->msp->id, cmd); + + ret = ux500_msp_i2s_trigger(drvdata->msp, cmd, substream->stream); + + return ret; +} + +static int ux500_msp_dai_of_probe(struct snd_soc_dai *dai) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct snd_dmaengine_dai_dma_data *playback_dma_data; + struct snd_dmaengine_dai_dma_data *capture_dma_data; + + playback_dma_data = devm_kzalloc(dai->dev, + sizeof(*playback_dma_data), + GFP_KERNEL); + if (!playback_dma_data) + return -ENOMEM; + + capture_dma_data = devm_kzalloc(dai->dev, + sizeof(*capture_dma_data), + GFP_KERNEL); + if (!capture_dma_data) + return -ENOMEM; + + playback_dma_data->addr = drvdata->msp->playback_dma_data.tx_rx_addr; + capture_dma_data->addr = drvdata->msp->capture_dma_data.tx_rx_addr; + + playback_dma_data->maxburst = 4; + capture_dma_data->maxburst = 4; + + snd_soc_dai_init_dma_data(dai, playback_dma_data, capture_dma_data); + + return 0; +} + +static int ux500_msp_dai_probe(struct snd_soc_dai *dai) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); + struct msp_i2s_platform_data *pdata = dai->dev->platform_data; + int ret; + + if (!pdata) { + ret = ux500_msp_dai_of_probe(dai); + return ret; + } + + drvdata->msp->playback_dma_data.data_size = drvdata->slot_width; + drvdata->msp->capture_dma_data.data_size = drvdata->slot_width; + + snd_soc_dai_init_dma_data(dai, + &drvdata->msp->playback_dma_data, + &drvdata->msp->capture_dma_data); + return 0; +} + +static const struct snd_soc_dai_ops ux500_msp_dai_ops[] = { + { + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + } +}; + +static struct snd_soc_dai_driver ux500_msp_dai_drv = { + .probe = ux500_msp_dai_probe, + .suspend = NULL, + .resume = NULL, + .playback.channels_min = UX500_MSP_MIN_CHANNELS, + .playback.channels_max = UX500_MSP_MAX_CHANNELS, + .playback.rates = UX500_I2S_RATES, + .playback.formats = UX500_I2S_FORMATS, + .capture.channels_min = UX500_MSP_MIN_CHANNELS, + .capture.channels_max = UX500_MSP_MAX_CHANNELS, + .capture.rates = UX500_I2S_RATES, + .capture.formats = UX500_I2S_FORMATS, + .ops = ux500_msp_dai_ops, +}; + +static const struct snd_soc_component_driver ux500_msp_component = { + .name = "ux500-msp", +}; + + +static int ux500_msp_drv_probe(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *drvdata; + struct msp_i2s_platform_data *pdata = pdev->dev.platform_data; + struct device_node *np = pdev->dev.of_node; + int ret = 0; + + if (!pdata && !np) { + dev_err(&pdev->dev, "No platform data or Device Tree found\n"); + return -ENODEV; + } + + drvdata = devm_kzalloc(&pdev->dev, + sizeof(struct ux500_msp_i2s_drvdata), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->fmt = 0; + drvdata->slots = 1; + drvdata->tx_mask = 0x01; + drvdata->rx_mask = 0x01; + drvdata->slot_width = 16; + drvdata->master_clk = MSP_INPUT_FREQ_APB; + + drvdata->reg_vape = devm_regulator_get(&pdev->dev, "v-ape"); + if (IS_ERR(drvdata->reg_vape)) { + ret = (int)PTR_ERR(drvdata->reg_vape); + dev_err(&pdev->dev, + "%s: ERROR: Failed to get Vape supply (%d)!\n", + __func__, ret); + return ret; + } + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50); + + drvdata->pclk = devm_clk_get(&pdev->dev, "apb_pclk"); + if (IS_ERR(drvdata->pclk)) { + ret = (int)PTR_ERR(drvdata->pclk); + dev_err(&pdev->dev, + "%s: ERROR: devm_clk_get of pclk failed (%d)!\n", + __func__, ret); + return ret; + } + + drvdata->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(drvdata->clk)) { + ret = (int)PTR_ERR(drvdata->clk); + dev_err(&pdev->dev, + "%s: ERROR: devm_clk_get failed (%d)!\n", + __func__, ret); + return ret; + } + + ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp, + pdev->dev.platform_data); + if (!drvdata->msp) { + dev_err(&pdev->dev, + "%s: ERROR: Failed to init MSP-struct (%d)!", + __func__, ret); + return ret; + } + dev_set_drvdata(&pdev->dev, drvdata); + + ret = snd_soc_register_component(&pdev->dev, &ux500_msp_component, + &ux500_msp_dai_drv, 1); + if (ret < 0) { + dev_err(&pdev->dev, "Error: %s: Failed to register MSP%d!\n", + __func__, drvdata->msp->id); + return ret; + } + + ret = ux500_pcm_register_platform(pdev); + if (ret < 0) { + dev_err(&pdev->dev, + "Error: %s: Failed to register PCM platform device!\n", + __func__); + goto err_reg_plat; + } + + return 0; + +err_reg_plat: + snd_soc_unregister_component(&pdev->dev); + return ret; +} + +static int ux500_msp_drv_remove(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(&pdev->dev); + + ux500_pcm_unregister_platform(pdev); + + snd_soc_unregister_component(&pdev->dev); + + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s"); + + ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp); + + return 0; +} + +static const struct of_device_id ux500_msp_i2s_match[] = { + { .compatible = "stericsson,ux500-msp-i2s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ux500_msp_i2s_match); + +static struct platform_driver msp_i2s_driver = { + .driver = { + .name = "ux500-msp-i2s", + .of_match_table = ux500_msp_i2s_match, + }, + .probe = ux500_msp_drv_probe, + .remove = ux500_msp_drv_remove, +}; +module_platform_driver(msp_i2s_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h new file mode 100644 index 000000000..312ae535e --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Roger Nilsson + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef UX500_msp_dai_H +#define UX500_msp_dai_H + +#include +#include + +#include "ux500_msp_i2s.h" + +#define UX500_NBR_OF_DAI 4 + +#define UX500_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) + +#define FRAME_PER_SINGLE_SLOT_8_KHZ 31 +#define FRAME_PER_SINGLE_SLOT_16_KHZ 124 +#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63 +#define FRAME_PER_SINGLE_SLOT_48_KHZ 49 +#define FRAME_PER_2_SLOTS 31 +#define FRAME_PER_8_SLOTS 138 +#define FRAME_PER_16_SLOTS 277 + +#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000 +#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ + +#define UX500_MSP_MIN_CHANNELS 1 +#define UX500_MSP_MAX_CHANNELS 8 + +#define PLAYBACK_CONFIGURED 1 +#define CAPTURE_CONFIGURED 2 + +enum ux500_msp_clock_id { + UX500_MSP_MASTER_CLOCK, +}; + +struct ux500_msp_i2s_drvdata { + struct ux500_msp *msp; + struct regulator *reg_vape; + unsigned int fmt; + unsigned int tx_mask; + unsigned int rx_mask; + int slots; + int slot_width; + + /* Clocks */ + unsigned int master_clk; + struct clk *clk; + struct clk *pclk; + + /* Regulators */ + int vape_opp_constraint; +}; + +int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay); + +#endif diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c new file mode 100644 index 000000000..bd5266aca --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -0,0 +1,735 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Roger Nilsson , + * Sandeep Kaushik + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ux500_msp_i2s.h" + + /* Protocol desciptors */ +static const struct msp_protdesc prot_descs[] = { + { /* I2S */ + MSP_SINGLE_PHASE, + MSP_SINGLE_PHASE, + MSP_PHASE2_START_MODE_IMEDIATE, + MSP_PHASE2_START_MODE_IMEDIATE, + MSP_BTF_MS_BIT_FIRST, + MSP_BTF_MS_BIT_FIRST, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_ELEM_LEN_32, + MSP_ELEM_LEN_32, + MSP_ELEM_LEN_32, + MSP_ELEM_LEN_32, + MSP_DELAY_1, + MSP_DELAY_1, + MSP_RISING_EDGE, + MSP_FALLING_EDGE, + MSP_FSYNC_POL_ACT_LO, + MSP_FSYNC_POL_ACT_LO, + MSP_SWAP_NONE, + MSP_SWAP_NONE, + MSP_COMPRESS_MODE_LINEAR, + MSP_EXPAND_MODE_LINEAR, + MSP_FSYNC_IGNORE, + 31, + 15, + 32, + }, { /* PCM */ + MSP_DUAL_PHASE, + MSP_DUAL_PHASE, + MSP_PHASE2_START_MODE_FSYNC, + MSP_PHASE2_START_MODE_FSYNC, + MSP_BTF_MS_BIT_FIRST, + MSP_BTF_MS_BIT_FIRST, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_ELEM_LEN_16, + MSP_ELEM_LEN_16, + MSP_ELEM_LEN_16, + MSP_ELEM_LEN_16, + MSP_DELAY_0, + MSP_DELAY_0, + MSP_RISING_EDGE, + MSP_FALLING_EDGE, + MSP_FSYNC_POL_ACT_HI, + MSP_FSYNC_POL_ACT_HI, + MSP_SWAP_NONE, + MSP_SWAP_NONE, + MSP_COMPRESS_MODE_LINEAR, + MSP_EXPAND_MODE_LINEAR, + MSP_FSYNC_IGNORE, + 255, + 0, + 256, + }, { /* Companded PCM */ + MSP_SINGLE_PHASE, + MSP_SINGLE_PHASE, + MSP_PHASE2_START_MODE_FSYNC, + MSP_PHASE2_START_MODE_FSYNC, + MSP_BTF_MS_BIT_FIRST, + MSP_BTF_MS_BIT_FIRST, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_FRAME_LEN_1, + MSP_ELEM_LEN_8, + MSP_ELEM_LEN_8, + MSP_ELEM_LEN_8, + MSP_ELEM_LEN_8, + MSP_DELAY_0, + MSP_DELAY_0, + MSP_RISING_EDGE, + MSP_RISING_EDGE, + MSP_FSYNC_POL_ACT_HI, + MSP_FSYNC_POL_ACT_HI, + MSP_SWAP_NONE, + MSP_SWAP_NONE, + MSP_COMPRESS_MODE_LINEAR, + MSP_EXPAND_MODE_LINEAR, + MSP_FSYNC_IGNORE, + 255, + 0, + 256, + }, +}; + +static void set_prot_desc_tx(struct ux500_msp *msp, + struct msp_protdesc *protdesc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protdesc->tx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protdesc->tx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protdesc->tx_frame_len_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protdesc->tx_frame_len_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protdesc->tx_elem_len_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protdesc->tx_elem_len_2); + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + } + temp_reg |= MSP_DATA_DELAY_BITS(protdesc->tx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protdesc->tx_byte_order); + temp_reg |= MSP_FSYNC_POL(protdesc->tx_fsync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protdesc->tx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protdesc->compression_mode); + temp_reg |= MSP_SET_FSYNC_IGNORE(protdesc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_TCF); +} + +static void set_prot_desc_rx(struct ux500_msp *msp, + struct msp_protdesc *protdesc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protdesc->rx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protdesc->rx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protdesc->rx_frame_len_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protdesc->rx_frame_len_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protdesc->rx_elem_len_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protdesc->rx_elem_len_2); + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + } + + temp_reg |= MSP_DATA_DELAY_BITS(protdesc->rx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protdesc->rx_byte_order); + temp_reg |= MSP_FSYNC_POL(protdesc->rx_fsync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protdesc->rx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protdesc->expansion_mode); + temp_reg |= MSP_SET_FSYNC_IGNORE(protdesc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_RCF); +} + +static int configure_protocol(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + struct msp_protdesc *protdesc; + enum msp_data_size data_size; + u32 temp_reg = 0; + + data_size = config->data_size; + msp->def_elem_len = config->def_elem_len; + if (config->default_protdesc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + dev_err(msp->dev, "%s: ERROR: Invalid protocol!\n", + __func__); + return -EINVAL; + } + protdesc = + (struct msp_protdesc *)&prot_descs[config->protocol]; + } else { + protdesc = (struct msp_protdesc *)&config->protdesc; + } + + if (data_size < MSP_DATA_BITS_DEFAULT || data_size > MSP_DATA_BITS_32) { + dev_err(msp->dev, + "%s: ERROR: Invalid data-size requested (data_size = %d)!\n", + __func__, data_size); + return -EINVAL; + } + + if (config->direction & MSP_DIR_TX) + set_prot_desc_tx(msp, protdesc, data_size); + if (config->direction & MSP_DIR_RX) + set_prot_desc_rx(msp, protdesc, data_size); + + /* The code below should not be separated. */ + temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; + temp_reg |= MSP_TX_CLKPOL_BIT(~protdesc->tx_clk_pol); + writel(temp_reg, msp->registers + MSP_GCR); + temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; + temp_reg |= MSP_RX_CLKPOL_BIT(protdesc->rx_clk_pol); + writel(temp_reg, msp->registers + MSP_GCR); + + return 0; +} + +static int setup_bitclk(struct ux500_msp *msp, struct ux500_msp_config *config) +{ + u32 reg_val_GCR; + u32 frame_per = 0; + u32 sck_div = 0; + u32 frame_width = 0; + u32 temp_reg = 0; + struct msp_protdesc *protdesc = NULL; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR); + + if (config->default_protdesc) + protdesc = + (struct msp_protdesc *)&prot_descs[config->protocol]; + else + protdesc = (struct msp_protdesc *)&config->protdesc; + + switch (config->protocol) { + case MSP_PCM_PROTOCOL: + case MSP_PCM_COMPAND_PROTOCOL: + frame_width = protdesc->frame_width; + sck_div = config->f_inputclk / (config->frame_freq * + (protdesc->clocks_per_frame)); + frame_per = protdesc->frame_period; + break; + case MSP_I2S_PROTOCOL: + frame_width = protdesc->frame_width; + sck_div = config->f_inputclk / (config->frame_freq * + (protdesc->clocks_per_frame)); + frame_per = protdesc->frame_period; + break; + default: + dev_err(msp->dev, "%s: ERROR: Unknown protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + + temp_reg = (sck_div - 1) & SCK_DIV_MASK; + temp_reg |= FRAME_WIDTH_BITS(frame_width); + temp_reg |= FRAME_PERIOD_BITS(frame_per); + writel(temp_reg, msp->registers + MSP_SRG); + + msp->f_bitclk = (config->f_inputclk)/(sck_div + 1); + + /* Enable bit-clock */ + udelay(100); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | SRG_ENABLE, msp->registers + MSP_GCR); + udelay(100); + + return 0; +} + +static int configure_multichannel(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + struct msp_protdesc *protdesc; + struct msp_multichannel_config *mcfg; + u32 reg_val_MCR; + + if (config->default_protdesc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + dev_err(msp->dev, + "%s: ERROR: Invalid protocol (%d)!\n", + __func__, config->protocol); + return -EINVAL; + } + protdesc = (struct msp_protdesc *) + &prot_descs[config->protocol]; + } else { + protdesc = (struct msp_protdesc *)&config->protdesc; + } + + mcfg = &config->multichannel_config; + if (mcfg->tx_multichannel_enable) { + if (protdesc->tx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | (mcfg->tx_multichannel_enable ? + 1 << TMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->tx_channel_0_enable, + msp->registers + MSP_TCE0); + writel(mcfg->tx_channel_1_enable, + msp->registers + MSP_TCE1); + writel(mcfg->tx_channel_2_enable, + msp->registers + MSP_TCE2); + writel(mcfg->tx_channel_3_enable, + msp->registers + MSP_TCE3); + } else { + dev_err(msp->dev, + "%s: ERROR: Only single-phase supported (TX-mode: %d)!\n", + __func__, protdesc->tx_phase_mode); + return -EINVAL; + } + } + if (mcfg->rx_multichannel_enable) { + if (protdesc->rx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | (mcfg->rx_multichannel_enable ? + 1 << RMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->rx_channel_0_enable, + msp->registers + MSP_RCE0); + writel(mcfg->rx_channel_1_enable, + msp->registers + MSP_RCE1); + writel(mcfg->rx_channel_2_enable, + msp->registers + MSP_RCE2); + writel(mcfg->rx_channel_3_enable, + msp->registers + MSP_RCE3); + } else { + dev_err(msp->dev, + "%s: ERROR: Only single-phase supported (RX-mode: %d)!\n", + __func__, protdesc->rx_phase_mode); + return -EINVAL; + } + if (mcfg->rx_comparison_enable_mode) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_comparison_enable_mode << RCMPM_BIT), + msp->registers + MSP_MCR); + + writel(mcfg->comparison_mask, + msp->registers + MSP_RCM); + writel(mcfg->comparison_value, + msp->registers + MSP_RCV); + + } + } + + return 0; +} + +static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config) +{ + int status = 0; + u32 reg_val_DMACR, reg_val_GCR; + + /* Configure msp with protocol dependent settings */ + configure_protocol(msp, config); + setup_bitclk(msp, config); + if (config->multichannel_configured == 1) { + status = configure_multichannel(msp, config); + if (status) + dev_warn(msp->dev, + "%s: WARN: configure_multichannel failed (%d)!\n", + __func__, status); + } + + /* Make sure the correct DMA-directions are configured */ + if ((config->direction & MSP_DIR_RX) && + !msp->capture_dma_data.dma_cfg) { + dev_err(msp->dev, "%s: ERROR: MSP RX-mode is not configured!", + __func__); + return -EINVAL; + } + if ((config->direction == MSP_DIR_TX) && + !msp->playback_dma_data.dma_cfg) { + dev_err(msp->dev, "%s: ERROR: MSP TX-mode is not configured!", + __func__); + return -EINVAL; + } + + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + if (config->direction & MSP_DIR_RX) + reg_val_DMACR |= RX_DMA_ENABLE; + if (config->direction & MSP_DIR_TX) + reg_val_DMACR |= TX_DMA_ENABLE; + writel(reg_val_DMACR, msp->registers + MSP_DMACR); + + writel(config->iodelay, msp->registers + MSP_IODLY); + + /* Enable frame generation logic */ + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR); + + return status; +} + +static void flush_fifo_rx(struct ux500_msp *msp) +{ + u32 reg_val_DR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | RX_ENABLE, msp->registers + MSP_GCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & RX_FIFO_EMPTY) && limit--) { + reg_val_DR = readl(msp->registers + MSP_DR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +static void flush_fifo_tx(struct ux500_msp *msp) +{ + u32 reg_val_TSTDR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | TX_ENABLE, msp->registers + MSP_GCR); + writel(MSP_ITCR_ITEN | MSP_ITCR_TESTFIFO, msp->registers + MSP_ITCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & TX_FIFO_EMPTY) && limit--) { + reg_val_TSTDR = readl(msp->registers + MSP_TSTDR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + writel(0x0, msp->registers + MSP_ITCR); + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +int ux500_msp_i2s_open(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + u32 old_reg, new_reg, mask; + int res; + unsigned int tx_sel, rx_sel, tx_busy, rx_busy; + + if (in_interrupt()) { + dev_err(msp->dev, + "%s: ERROR: Open called in interrupt context!\n", + __func__); + return -1; + } + + tx_sel = (config->direction & MSP_DIR_TX) > 0; + rx_sel = (config->direction & MSP_DIR_RX) > 0; + if (!tx_sel && !rx_sel) { + dev_err(msp->dev, "%s: Error: No direction selected!\n", + __func__); + return -EINVAL; + } + + tx_busy = (msp->dir_busy & MSP_DIR_TX) > 0; + rx_busy = (msp->dir_busy & MSP_DIR_RX) > 0; + if (tx_busy && tx_sel) { + dev_err(msp->dev, "%s: Error: TX is in use!\n", __func__); + return -EBUSY; + } + if (rx_busy && rx_sel) { + dev_err(msp->dev, "%s: Error: RX is in use!\n", __func__); + return -EBUSY; + } + + msp->dir_busy |= (tx_sel ? MSP_DIR_TX : 0) | (rx_sel ? MSP_DIR_RX : 0); + + /* First do the global config register */ + mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK | + TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | + LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + + new_reg = (config->tx_clk_sel | config->rx_clk_sel | + config->rx_fsync_pol | config->tx_fsync_pol | + config->rx_fsync_sel | config->tx_fsync_sel | + config->rx_fifo_config | config->tx_fifo_config | + config->srg_clk_sel | config->loopback_enable | + config->tx_data_enable); + + old_reg = readl(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + writel(new_reg, msp->registers + MSP_GCR); + + res = enable_msp(msp, config); + if (res < 0) { + dev_err(msp->dev, "%s: ERROR: enable_msp failed (%d)!\n", + __func__, res); + return -EBUSY; + } + if (config->loopback_enable & 0x80) + msp->loopback_enable = 1; + + /* Flush FIFOs */ + flush_fifo_tx(msp); + flush_fifo_rx(msp); + + msp->msp_state = MSP_STATE_CONFIGURED; + return 0; +} + +static void disable_msp_rx(struct ux500_msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~RX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~RX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(RX_SERVICE_INT | RX_OVERRUN_ERROR_INT), + msp->registers + MSP_IMSC); + + msp->dir_busy &= ~MSP_DIR_RX; +} + +static void disable_msp_tx(struct ux500_msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~TX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~TX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(TX_SERVICE_INT | TX_UNDERRUN_ERR_INT), + msp->registers + MSP_IMSC); + + msp->dir_busy &= ~MSP_DIR_TX; +} + +static int disable_msp(struct ux500_msp *msp, unsigned int dir) +{ + u32 reg_val_GCR; + int status = 0; + unsigned int disable_tx, disable_rx; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + disable_tx = dir & MSP_DIR_TX; + disable_rx = dir & MSP_DIR_TX; + if (disable_tx && disable_rx) { + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | LOOPBACK_MASK, + msp->registers + MSP_GCR); + + /* Flush TX-FIFO */ + flush_fifo_tx(msp); + + /* Disable TX-channel */ + writel((readl(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + + /* Flush RX-FIFO */ + flush_fifo_rx(msp); + + /* Disable Loopback and Receive channel */ + writel((readl(msp->registers + MSP_GCR) & + (~(RX_ENABLE | LOOPBACK_MASK))), + msp->registers + MSP_GCR); + + disable_msp_tx(msp); + disable_msp_rx(msp); + } else if (disable_tx) + disable_msp_tx(msp); + else if (disable_rx) + disable_msp_rx(msp); + + return status; +} + +int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) +{ + u32 reg_val_GCR, enable_bit; + + if (msp->msp_state == MSP_STATE_IDLE) { + dev_err(msp->dev, "%s: ERROR: MSP is not configured!\n", + __func__); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = TX_ENABLE; + else + enable_bit = RX_ENABLE; + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + disable_msp_tx(msp); + else + disable_msp_rx(msp); + break; + default: + return -EINVAL; + } + + return 0; +} + +int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) +{ + int status = 0; + + dev_dbg(msp->dev, "%s: Enter (dir = 0x%01x).\n", __func__, dir); + + status = disable_msp(msp, dir); + if (msp->dir_busy == 0) { + /* disable sample rate and frame generators */ + msp->msp_state = MSP_STATE_IDLE; + writel((readl(msp->registers + MSP_GCR) & + (~(FRAME_GEN_ENABLE | SRG_ENABLE))), + msp->registers + MSP_GCR); + + writel(0, msp->registers + MSP_GCR); + writel(0, msp->registers + MSP_TCF); + writel(0, msp->registers + MSP_RCF); + writel(0, msp->registers + MSP_DMACR); + writel(0, msp->registers + MSP_SRG); + writel(0, msp->registers + MSP_MCR); + writel(0, msp->registers + MSP_RCM); + writel(0, msp->registers + MSP_RCV); + writel(0, msp->registers + MSP_TCE0); + writel(0, msp->registers + MSP_TCE1); + writel(0, msp->registers + MSP_TCE2); + writel(0, msp->registers + MSP_TCE3); + writel(0, msp->registers + MSP_RCE0); + writel(0, msp->registers + MSP_RCE1); + writel(0, msp->registers + MSP_RCE2); + writel(0, msp->registers + MSP_RCE3); + } + + return status; + +} + +static int ux500_msp_i2s_of_init_msp(struct platform_device *pdev, + struct ux500_msp *msp, + struct msp_i2s_platform_data **platform_data) +{ + struct msp_i2s_platform_data *pdata; + + *platform_data = devm_kzalloc(&pdev->dev, + sizeof(struct msp_i2s_platform_data), + GFP_KERNEL); + pdata = *platform_data; + if (!pdata) + return -ENOMEM; + + msp->playback_dma_data.dma_cfg = devm_kzalloc(&pdev->dev, + sizeof(struct stedma40_chan_cfg), + GFP_KERNEL); + if (!msp->playback_dma_data.dma_cfg) + return -ENOMEM; + + msp->capture_dma_data.dma_cfg = devm_kzalloc(&pdev->dev, + sizeof(struct stedma40_chan_cfg), + GFP_KERNEL); + if (!msp->capture_dma_data.dma_cfg) + return -ENOMEM; + + return 0; +} + +int ux500_msp_i2s_init_msp(struct platform_device *pdev, + struct ux500_msp **msp_p, + struct msp_i2s_platform_data *platform_data) +{ + struct resource *res = NULL; + struct device_node *np = pdev->dev.of_node; + struct ux500_msp *msp; + int ret; + + *msp_p = devm_kzalloc(&pdev->dev, sizeof(struct ux500_msp), GFP_KERNEL); + msp = *msp_p; + if (!msp) + return -ENOMEM; + + if (!platform_data) { + if (np) { + ret = ux500_msp_i2s_of_init_msp(pdev, msp, + &platform_data); + if (ret) + return ret; + } else + return -EINVAL; + } else { + msp->playback_dma_data.dma_cfg = platform_data->msp_i2s_dma_tx; + msp->capture_dma_data.dma_cfg = platform_data->msp_i2s_dma_rx; + msp->id = platform_data->id; + } + + msp->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "%s: ERROR: Unable to get resource!\n", + __func__); + return -ENOMEM; + } + + msp->playback_dma_data.tx_rx_addr = res->start + MSP_DR; + msp->capture_dma_data.tx_rx_addr = res->start + MSP_DR; + + msp->registers = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (msp->registers == NULL) { + dev_err(&pdev->dev, "%s: ERROR: ioremap failed!\n", __func__); + return -ENOMEM; + } + + msp->msp_state = MSP_STATE_IDLE; + msp->loopback_enable = 0; + + return 0; +} + +void ux500_msp_i2s_cleanup_msp(struct platform_device *pdev, + struct ux500_msp *msp) +{ + dev_dbg(msp->dev, "%s: Enter (id = %d).\n", __func__, msp->id); +} + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h new file mode 100644 index 000000000..875de0f68 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -0,0 +1,502 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + + +#ifndef UX500_MSP_I2S_H +#define UX500_MSP_I2S_H + +#include +#include + +#define MSP_INPUT_FREQ_APB 48000000 + +/*** Stereo mode. Used for APB data accesses as 16 bits accesses (mono), + * 32 bits accesses (stereo). + ***/ +enum msp_stereo_mode { + MSP_MONO, + MSP_STEREO +}; + +/* Direction (Transmit/Receive mode) */ +enum msp_direction { + MSP_TX = 1, + MSP_RX = 2 +}; + +/* Transmit and receive configuration register */ +#define MSP_BIG_ENDIAN 0x00000000 +#define MSP_LITTLE_ENDIAN 0x00001000 +#define MSP_UNEXPECTED_FS_ABORT 0x00000000 +#define MSP_UNEXPECTED_FS_IGNORE 0x00008000 +#define MSP_NON_MODE_BIT_MASK 0x00009000 + +/* Global configuration register */ +#define RX_ENABLE 0x00000001 +#define RX_FIFO_ENABLE 0x00000002 +#define RX_SYNC_SRG 0x00000010 +#define RX_CLK_POL_RISING 0x00000020 +#define RX_CLK_SEL_SRG 0x00000040 +#define TX_ENABLE 0x00000100 +#define TX_FIFO_ENABLE 0x00000200 +#define TX_SYNC_SRG_PROG 0x00001800 +#define TX_SYNC_SRG_AUTO 0x00001000 +#define TX_CLK_POL_RISING 0x00002000 +#define TX_CLK_SEL_SRG 0x00004000 +#define TX_EXTRA_DELAY_ENABLE 0x00008000 +#define SRG_ENABLE 0x00010000 +#define FRAME_GEN_ENABLE 0x00100000 +#define SRG_CLK_SEL_APB 0x00000000 +#define RX_FIFO_SYNC_HI 0x00000000 +#define TX_FIFO_SYNC_HI 0x00000000 +#define SPI_CLK_MODE_NORMAL 0x00000000 + +#define MSP_FRAME_SIZE_AUTO -1 + +#define MSP_DR 0x00 +#define MSP_GCR 0x04 +#define MSP_TCF 0x08 +#define MSP_RCF 0x0c +#define MSP_SRG 0x10 +#define MSP_FLR 0x14 +#define MSP_DMACR 0x18 + +#define MSP_IMSC 0x20 +#define MSP_RIS 0x24 +#define MSP_MIS 0x28 +#define MSP_ICR 0x2c +#define MSP_MCR 0x30 +#define MSP_RCV 0x34 +#define MSP_RCM 0x38 + +#define MSP_TCE0 0x40 +#define MSP_TCE1 0x44 +#define MSP_TCE2 0x48 +#define MSP_TCE3 0x4c + +#define MSP_RCE0 0x60 +#define MSP_RCE1 0x64 +#define MSP_RCE2 0x68 +#define MSP_RCE3 0x6c +#define MSP_IODLY 0x70 + +#define MSP_ITCR 0x80 +#define MSP_ITIP 0x84 +#define MSP_ITOP 0x88 +#define MSP_TSTDR 0x8c + +#define MSP_PID0 0xfe0 +#define MSP_PID1 0xfe4 +#define MSP_PID2 0xfe8 +#define MSP_PID3 0xfec + +#define MSP_CID0 0xff0 +#define MSP_CID1 0xff4 +#define MSP_CID2 0xff8 +#define MSP_CID3 0xffc + +/* Protocol dependant parameters list */ +#define RX_ENABLE_MASK BIT(0) +#define RX_FIFO_ENABLE_MASK BIT(1) +#define RX_FSYNC_MASK BIT(2) +#define DIRECT_COMPANDING_MASK BIT(3) +#define RX_SYNC_SEL_MASK BIT(4) +#define RX_CLK_POL_MASK BIT(5) +#define RX_CLK_SEL_MASK BIT(6) +#define LOOPBACK_MASK BIT(7) +#define TX_ENABLE_MASK BIT(8) +#define TX_FIFO_ENABLE_MASK BIT(9) +#define TX_FSYNC_MASK BIT(10) +#define TX_MSP_TDR_TSR BIT(11) +#define TX_SYNC_SEL_MASK (BIT(12) | BIT(11)) +#define TX_CLK_POL_MASK BIT(13) +#define TX_CLK_SEL_MASK BIT(14) +#define TX_EXTRA_DELAY_MASK BIT(15) +#define SRG_ENABLE_MASK BIT(16) +#define SRG_CLK_POL_MASK BIT(17) +#define SRG_CLK_SEL_MASK (BIT(19) | BIT(18)) +#define FRAME_GEN_EN_MASK BIT(20) +#define SPI_CLK_MODE_MASK (BIT(22) | BIT(21)) +#define SPI_BURST_MODE_MASK BIT(23) + +#define RXEN_SHIFT 0 +#define RFFEN_SHIFT 1 +#define RFSPOL_SHIFT 2 +#define DCM_SHIFT 3 +#define RFSSEL_SHIFT 4 +#define RCKPOL_SHIFT 5 +#define RCKSEL_SHIFT 6 +#define LBM_SHIFT 7 +#define TXEN_SHIFT 8 +#define TFFEN_SHIFT 9 +#define TFSPOL_SHIFT 10 +#define TFSSEL_SHIFT 11 +#define TCKPOL_SHIFT 13 +#define TCKSEL_SHIFT 14 +#define TXDDL_SHIFT 15 +#define SGEN_SHIFT 16 +#define SCKPOL_SHIFT 17 +#define SCKSEL_SHIFT 18 +#define FGEN_SHIFT 20 +#define SPICKM_SHIFT 21 +#define TBSWAP_SHIFT 28 + +#define RCKPOL_MASK BIT(0) +#define TCKPOL_MASK BIT(0) +#define SPICKM_MASK (BIT(1) | BIT(0)) +#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT) +#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) + +#define P1ELEN_SHIFT 0 +#define P1FLEN_SHIFT 3 +#define DTYP_SHIFT 10 +#define ENDN_SHIFT 12 +#define DDLY_SHIFT 13 +#define FSIG_SHIFT 15 +#define P2ELEN_SHIFT 16 +#define P2FLEN_SHIFT 19 +#define P2SM_SHIFT 26 +#define P2EN_SHIFT 27 +#define FSYNC_SHIFT 15 + +#define P1ELEN_MASK 0x00000007 +#define P2ELEN_MASK 0x00070000 +#define P1FLEN_MASK 0x00000378 +#define P2FLEN_MASK 0x03780000 +#define DDLY_MASK 0x00003000 +#define DTYP_MASK 0x00000600 +#define P2SM_MASK 0x04000000 +#define P2EN_MASK 0x08000000 +#define ENDN_MASK 0x00001000 +#define TFSPOL_MASK 0x00000400 +#define TBSWAP_MASK 0x30000000 +#define COMPANDING_MODE_MASK 0x00000c00 +#define FSYNC_MASK 0x00008000 + +#define MSP_P1_ELEM_LEN_BITS(n) (n & P1ELEN_MASK) +#define MSP_P2_ELEM_LEN_BITS(n) (((n) << P2ELEN_SHIFT) & P2ELEN_MASK) +#define MSP_P1_FRAME_LEN_BITS(n) (((n) << P1FLEN_SHIFT) & P1FLEN_MASK) +#define MSP_P2_FRAME_LEN_BITS(n) (((n) << P2FLEN_SHIFT) & P2FLEN_MASK) +#define MSP_DATA_DELAY_BITS(n) (((n) << DDLY_SHIFT) & DDLY_MASK) +#define MSP_DATA_TYPE_BITS(n) (((n) << DTYP_SHIFT) & DTYP_MASK) +#define MSP_P2_START_MODE_BIT(n) ((n << P2SM_SHIFT) & P2SM_MASK) +#define MSP_P2_ENABLE_BIT(n) ((n << P2EN_SHIFT) & P2EN_MASK) +#define MSP_SET_ENDIANNES_BIT(n) ((n << ENDN_SHIFT) & ENDN_MASK) +#define MSP_FSYNC_POL(n) ((n << TFSPOL_SHIFT) & TFSPOL_MASK) +#define MSP_DATA_WORD_SWAP(n) ((n << TBSWAP_SHIFT) & TBSWAP_MASK) +#define MSP_SET_COMPANDING_MODE(n) ((n << DTYP_SHIFT) & \ + COMPANDING_MODE_MASK) +#define MSP_SET_FSYNC_IGNORE(n) ((n << FSYNC_SHIFT) & FSYNC_MASK) + +/* Flag register */ +#define RX_BUSY BIT(0) +#define RX_FIFO_EMPTY BIT(1) +#define RX_FIFO_FULL BIT(2) +#define TX_BUSY BIT(3) +#define TX_FIFO_EMPTY BIT(4) +#define TX_FIFO_FULL BIT(5) + +#define RBUSY_SHIFT 0 +#define RFE_SHIFT 1 +#define RFU_SHIFT 2 +#define TBUSY_SHIFT 3 +#define TFE_SHIFT 4 +#define TFU_SHIFT 5 + +/* Multichannel control register */ +#define RMCEN_SHIFT 0 +#define RMCSF_SHIFT 1 +#define RCMPM_SHIFT 3 +#define TMCEN_SHIFT 5 +#define TNCSF_SHIFT 6 + +/* Sample rate generator register */ +#define SCKDIV_SHIFT 0 +#define FRWID_SHIFT 10 +#define FRPER_SHIFT 16 + +#define SCK_DIV_MASK 0x0000003FF +#define FRAME_WIDTH_BITS(n) (((n) << FRWID_SHIFT) & 0x0000FC00) +#define FRAME_PERIOD_BITS(n) (((n) << FRPER_SHIFT) & 0x1FFF0000) + +/* DMA controller register */ +#define RX_DMA_ENABLE BIT(0) +#define TX_DMA_ENABLE BIT(1) + +#define RDMAE_SHIFT 0 +#define TDMAE_SHIFT 1 + +/* Interrupt Register */ +#define RX_SERVICE_INT BIT(0) +#define RX_OVERRUN_ERROR_INT BIT(1) +#define RX_FSYNC_ERR_INT BIT(2) +#define RX_FSYNC_INT BIT(3) +#define TX_SERVICE_INT BIT(4) +#define TX_UNDERRUN_ERR_INT BIT(5) +#define TX_FSYNC_ERR_INT BIT(6) +#define TX_FSYNC_INT BIT(7) +#define ALL_INT 0x000000ff + +/* MSP test control register */ +#define MSP_ITCR_ITEN BIT(0) +#define MSP_ITCR_TESTFIFO BIT(1) + +#define RMCEN_BIT 0 +#define RMCSF_BIT 1 +#define RCMPM_BIT 3 +#define TMCEN_BIT 5 +#define TNCSF_BIT 6 + +/* Single or dual phase mode */ +enum msp_phase_mode { + MSP_SINGLE_PHASE, + MSP_DUAL_PHASE +}; + +/* Frame length */ +enum msp_frame_length { + MSP_FRAME_LEN_1 = 0, + MSP_FRAME_LEN_2 = 1, + MSP_FRAME_LEN_4 = 3, + MSP_FRAME_LEN_8 = 7, + MSP_FRAME_LEN_12 = 11, + MSP_FRAME_LEN_16 = 15, + MSP_FRAME_LEN_20 = 19, + MSP_FRAME_LEN_32 = 31, + MSP_FRAME_LEN_48 = 47, + MSP_FRAME_LEN_64 = 63 +}; + +/* Element length */ +enum msp_elem_length { + MSP_ELEM_LEN_8 = 0, + MSP_ELEM_LEN_10 = 1, + MSP_ELEM_LEN_12 = 2, + MSP_ELEM_LEN_14 = 3, + MSP_ELEM_LEN_16 = 4, + MSP_ELEM_LEN_20 = 5, + MSP_ELEM_LEN_24 = 6, + MSP_ELEM_LEN_32 = 7 +}; + +enum msp_data_xfer_width { + MSP_DATA_TRANSFER_WIDTH_BYTE, + MSP_DATA_TRANSFER_WIDTH_HALFWORD, + MSP_DATA_TRANSFER_WIDTH_WORD +}; + +enum msp_frame_sync { + MSP_FSYNC_UNIGNORE = 0, + MSP_FSYNC_IGNORE = 1, +}; + +enum msp_phase2_start_mode { + MSP_PHASE2_START_MODE_IMEDIATE, + MSP_PHASE2_START_MODE_FSYNC +}; + +enum msp_btf { + MSP_BTF_MS_BIT_FIRST = 0, + MSP_BTF_LS_BIT_FIRST = 1 +}; + +enum msp_fsync_pol { + MSP_FSYNC_POL_ACT_HI = 0, + MSP_FSYNC_POL_ACT_LO = 1 +}; + +/* Data delay (in bit clock cycles) */ +enum msp_delay { + MSP_DELAY_0 = 0, + MSP_DELAY_1 = 1, + MSP_DELAY_2 = 2, + MSP_DELAY_3 = 3 +}; + +/* Configurations of clocks (transmit, receive or sample rate generator) */ +enum msp_edge { + MSP_FALLING_EDGE = 0, + MSP_RISING_EDGE = 1, +}; + +enum msp_hws { + MSP_SWAP_NONE = 0, + MSP_SWAP_BYTE_PER_WORD = 1, + MSP_SWAP_BYTE_PER_HALF_WORD = 2, + MSP_SWAP_HALF_WORD_PER_WORD = 3 +}; + +enum msp_compress_mode { + MSP_COMPRESS_MODE_LINEAR = 0, + MSP_COMPRESS_MODE_MU_LAW = 2, + MSP_COMPRESS_MODE_A_LAW = 3 +}; + +enum msp_expand_mode { + MSP_EXPAND_MODE_LINEAR = 0, + MSP_EXPAND_MODE_LINEAR_SIGNED = 1, + MSP_EXPAND_MODE_MU_LAW = 2, + MSP_EXPAND_MODE_A_LAW = 3 +}; + +#define MSP_FRAME_PERIOD_IN_MONO_MODE 256 +#define MSP_FRAME_PERIOD_IN_STEREO_MODE 32 +#define MSP_FRAME_WIDTH_IN_STEREO_MODE 16 + +enum msp_protocol { + MSP_I2S_PROTOCOL, + MSP_PCM_PROTOCOL, + MSP_PCM_COMPAND_PROTOCOL, + MSP_INVALID_PROTOCOL +}; + +/* + * No of registers to backup during + * suspend resume + */ +#define MAX_MSP_BACKUP_REGS 36 + +enum i2s_direction_t { + MSP_DIR_TX = 0x01, + MSP_DIR_RX = 0x02, +}; + +enum msp_data_size { + MSP_DATA_BITS_DEFAULT = -1, + MSP_DATA_BITS_8 = 0x00, + MSP_DATA_BITS_10, + MSP_DATA_BITS_12, + MSP_DATA_BITS_14, + MSP_DATA_BITS_16, + MSP_DATA_BITS_20, + MSP_DATA_BITS_24, + MSP_DATA_BITS_32, +}; + +enum msp_state { + MSP_STATE_IDLE = 0, + MSP_STATE_CONFIGURED = 1, + MSP_STATE_RUNNING = 2, +}; + +enum msp_rx_comparison_enable_mode { + MSP_COMPARISON_DISABLED = 0, + MSP_COMPARISON_NONEQUAL_ENABLED = 2, + MSP_COMPARISON_EQUAL_ENABLED = 3 +}; + +struct msp_multichannel_config { + bool rx_multichannel_enable; + bool tx_multichannel_enable; + enum msp_rx_comparison_enable_mode rx_comparison_enable_mode; + u8 padding; + u32 comparison_value; + u32 comparison_mask; + u32 rx_channel_0_enable; + u32 rx_channel_1_enable; + u32 rx_channel_2_enable; + u32 rx_channel_3_enable; + u32 tx_channel_0_enable; + u32 tx_channel_1_enable; + u32 tx_channel_2_enable; + u32 tx_channel_3_enable; +}; + +struct msp_protdesc { + u32 rx_phase_mode; + u32 tx_phase_mode; + u32 rx_phase2_start_mode; + u32 tx_phase2_start_mode; + u32 rx_byte_order; + u32 tx_byte_order; + u32 rx_frame_len_1; + u32 rx_frame_len_2; + u32 tx_frame_len_1; + u32 tx_frame_len_2; + u32 rx_elem_len_1; + u32 rx_elem_len_2; + u32 tx_elem_len_1; + u32 tx_elem_len_2; + u32 rx_data_delay; + u32 tx_data_delay; + u32 rx_clk_pol; + u32 tx_clk_pol; + u32 rx_fsync_pol; + u32 tx_fsync_pol; + u32 rx_half_word_swap; + u32 tx_half_word_swap; + u32 compression_mode; + u32 expansion_mode; + u32 frame_sync_ignore; + u32 frame_period; + u32 frame_width; + u32 clocks_per_frame; +}; + +struct ux500_msp_config { + unsigned int f_inputclk; + unsigned int rx_clk_sel; + unsigned int tx_clk_sel; + unsigned int srg_clk_sel; + unsigned int rx_fsync_pol; + unsigned int tx_fsync_pol; + unsigned int rx_fsync_sel; + unsigned int tx_fsync_sel; + unsigned int rx_fifo_config; + unsigned int tx_fifo_config; + unsigned int loopback_enable; + unsigned int tx_data_enable; + unsigned int default_protdesc; + struct msp_protdesc protdesc; + int multichannel_configured; + struct msp_multichannel_config multichannel_config; + unsigned int direction; + unsigned int protocol; + unsigned int frame_freq; + enum msp_data_size data_size; + unsigned int def_elem_len; + unsigned int iodelay; +}; + +struct ux500_msp_dma_params { + unsigned int data_size; + dma_addr_t tx_rx_addr; + struct stedma40_chan_cfg *dma_cfg; +}; + +struct ux500_msp { + int id; + void __iomem *registers; + struct device *dev; + struct ux500_msp_dma_params playback_dma_data; + struct ux500_msp_dma_params capture_dma_data; + enum msp_state msp_state; + int def_elem_len; + unsigned int dir_busy; + int loopback_enable; + unsigned int f_bitclk; +}; + +struct msp_i2s_platform_data; +int ux500_msp_i2s_init_msp(struct platform_device *pdev, + struct ux500_msp **msp_p, + struct msp_i2s_platform_data *platform_data); +void ux500_msp_i2s_cleanup_msp(struct platform_device *pdev, + struct ux500_msp *msp); +int ux500_msp_i2s_open(struct ux500_msp *msp, struct ux500_msp_config *config); +int ux500_msp_i2s_close(struct ux500_msp *msp, + unsigned int dir); +int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, + int direction); + +#endif diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c new file mode 100644 index 000000000..d35ba7700 --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Roger Nilsson + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ux500_msp_i2s.h" +#include "ux500_pcm.h" + +#define UX500_PLATFORM_PERIODS_BYTES_MIN 128 +#define UX500_PLATFORM_PERIODS_BYTES_MAX (64 * PAGE_SIZE) +#define UX500_PLATFORM_PERIODS_MIN 2 +#define UX500_PLATFORM_PERIODS_MAX 48 +#define UX500_PLATFORM_BUFFER_BYTES_MAX (2048 * PAGE_SIZE) + +static const struct snd_pcm_hardware ux500_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN, + .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX, + .periods_min = UX500_PLATFORM_PERIODS_MIN, + .periods_max = UX500_PLATFORM_PERIODS_MAX, +}; + +static struct dma_chan *ux500_pcm_request_chan(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + u16 per_data_width, mem_data_width; + struct stedma40_chan_cfg *dma_cfg; + struct ux500_msp_dma_params *dma_params; + + dma_params = snd_soc_dai_get_dma_data(dai, substream); + dma_cfg = dma_params->dma_cfg; + + mem_data_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + switch (dma_params->data_size) { + case 32: + per_data_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + case 16: + per_data_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 8: + per_data_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + break; + default: + per_data_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_cfg->src_info.data_width = mem_data_width; + dma_cfg->dst_info.data_width = per_data_width; + } else { + dma_cfg->src_info.data_width = per_data_width; + dma_cfg->dst_info.data_width = mem_data_width; + } + + return snd_dmaengine_pcm_request_channel(stedma40_filter, dma_cfg); +} + +static int ux500_pcm_prepare_slave_config(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct msp_i2s_platform_data *pdata = rtd->cpu_dai->dev->platform_data; + struct snd_dmaengine_dai_dma_data *snd_dma_params; + struct ux500_msp_dma_params *ste_dma_params; + dma_addr_t dma_addr; + int ret; + + if (pdata) { + ste_dma_params = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_addr = ste_dma_params->tx_rx_addr; + } else { + snd_dma_params = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_addr = snd_dma_params->addr; + } + + ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config); + if (ret) + return ret; + + slave_config->dst_maxburst = 4; + slave_config->src_maxburst = 4; + + slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + slave_config->dst_addr = dma_addr; + else + slave_config->src_addr = dma_addr; + + return 0; +} + +static const struct snd_dmaengine_pcm_config ux500_dmaengine_pcm_config = { + .pcm_hardware = &ux500_pcm_hw, + .compat_request_channel = ux500_pcm_request_chan, + .prealloc_buffer_size = 128 * 1024, + .prepare_slave_config = ux500_pcm_prepare_slave_config, +}; + +static const struct snd_dmaengine_pcm_config ux500_dmaengine_of_pcm_config = { + .compat_request_channel = ux500_pcm_request_chan, + .prepare_slave_config = ux500_pcm_prepare_slave_config, +}; + +int ux500_pcm_register_platform(struct platform_device *pdev) +{ + const struct snd_dmaengine_pcm_config *pcm_config; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (np) + pcm_config = &ux500_dmaengine_of_pcm_config; + else + pcm_config = &ux500_dmaengine_pcm_config; + + ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); + if (ret < 0) { + dev_err(&pdev->dev, + "%s: ERROR: Failed to register platform '%s' (%d)!\n", + __func__, pdev->name, ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ux500_pcm_register_platform); + +int ux500_pcm_unregister_platform(struct platform_device *pdev) +{ + snd_dmaengine_pcm_unregister(&pdev->dev); + return 0; +} +EXPORT_SYMBOL_GPL(ux500_pcm_unregister_platform); + +MODULE_AUTHOR("Ola Lilja"); +MODULE_AUTHOR("Roger Nilsson"); +MODULE_DESCRIPTION("ASoC UX500 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/ux500/ux500_pcm.h b/sound/soc/ux500/ux500_pcm.h new file mode 100644 index 000000000..d76e1aff6 --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja , + * Roger Nilsson + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ +#ifndef UX500_PCM_H +#define UX500_PCM_H + +#include + +#include + +int ux500_pcm_register_platform(struct platform_device *pdev); +int ux500_pcm_unregister_platform(struct platform_device *pdev); + +#endif diff --git a/sound/soc/xtensa/Kconfig b/sound/soc/xtensa/Kconfig new file mode 100644 index 000000000..c201beb36 --- /dev/null +++ b/sound/soc/xtensa/Kconfig @@ -0,0 +1,7 @@ +config SND_SOC_XTFPGA_I2S + tristate "XTFPGA I2S master" + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to the + I2S interface on XTFPGA daughter board. You will also need to select + the drivers for the rest of XTFPGA audio subsystem. diff --git a/sound/soc/xtensa/Makefile b/sound/soc/xtensa/Makefile new file mode 100644 index 000000000..15efbf914 --- /dev/null +++ b/sound/soc/xtensa/Makefile @@ -0,0 +1,3 @@ +snd-soc-xtfpga-i2s-objs := xtfpga-i2s.o + +obj-$(CONFIG_SND_SOC_XTFPGA_I2S) += snd-soc-xtfpga-i2s.o diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c new file mode 100644 index 000000000..503560916 --- /dev/null +++ b/sound/soc/xtensa/xtfpga-i2s.c @@ -0,0 +1,658 @@ +/* + * Xtfpga I2S controller driver + * + * Copyright (c) 2014 Cadence Design Systems Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "xtfpga-i2s" + +#define XTFPGA_I2S_VERSION 0x00 +#define XTFPGA_I2S_CONFIG 0x04 +#define XTFPGA_I2S_INT_MASK 0x08 +#define XTFPGA_I2S_INT_STATUS 0x0c +#define XTFPGA_I2S_CHAN0_DATA 0x10 +#define XTFPGA_I2S_CHAN1_DATA 0x14 +#define XTFPGA_I2S_CHAN2_DATA 0x18 +#define XTFPGA_I2S_CHAN3_DATA 0x1c + +#define XTFPGA_I2S_CONFIG_TX_ENABLE 0x1 +#define XTFPGA_I2S_CONFIG_INT_ENABLE 0x2 +#define XTFPGA_I2S_CONFIG_LEFT 0x4 +#define XTFPGA_I2S_CONFIG_RATIO_BASE 8 +#define XTFPGA_I2S_CONFIG_RATIO_MASK 0x0000ff00 +#define XTFPGA_I2S_CONFIG_RES_BASE 16 +#define XTFPGA_I2S_CONFIG_RES_MASK 0x003f0000 +#define XTFPGA_I2S_CONFIG_LEVEL_BASE 24 +#define XTFPGA_I2S_CONFIG_LEVEL_MASK 0x0f000000 +#define XTFPGA_I2S_CONFIG_CHANNEL_BASE 28 + +#define XTFPGA_I2S_INT_UNDERRUN 0x1 +#define XTFPGA_I2S_INT_LEVEL 0x2 +#define XTFPGA_I2S_INT_VALID 0x3 + +#define XTFPGA_I2S_FIFO_SIZE 8192 + +/* + * I2S controller operation: + * + * Enabling TX: output 1 period of zeros (starting with left channel) + * and then queued data. + * + * Level status and interrupt: whenever FIFO level is below FIFO trigger, + * level status is 1 and an IRQ is asserted (if enabled). + * + * Underrun status and interrupt: whenever FIFO is empty, underrun status + * is 1 and an IRQ is asserted (if enabled). + */ +struct xtfpga_i2s { + struct device *dev; + struct clk *clk; + struct regmap *regmap; + void __iomem *regs; + + /* current playback substream. NULL if not playing. + * + * Access to that field is synchronized between the interrupt handler + * and userspace through RCU. + * + * Interrupt handler (threaded part) does PIO on substream data in RCU + * read-side critical section. Trigger callback sets and clears the + * pointer when the playback is started and stopped with + * rcu_assign_pointer. When userspace is about to free the playback + * stream in the pcm_close callback it synchronizes with the interrupt + * handler by means of synchronize_rcu call. + */ + struct snd_pcm_substream __rcu *tx_substream; + unsigned (*tx_fn)(struct xtfpga_i2s *i2s, + struct snd_pcm_runtime *runtime, + unsigned tx_ptr); + unsigned tx_ptr; /* next frame index in the sample buffer */ + + /* current fifo level estimate. + * Doesn't have to be perfectly accurate, but must be not less than + * the actual FIFO level in order to avoid stall on push attempt. + */ + unsigned tx_fifo_level; + + /* FIFO level at which level interrupt occurs */ + unsigned tx_fifo_low; + + /* maximal FIFO level */ + unsigned tx_fifo_high; +}; + +static bool xtfpga_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + return reg >= XTFPGA_I2S_CONFIG; +} + +static bool xtfpga_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + return reg < XTFPGA_I2S_CHAN0_DATA; +} + +static bool xtfpga_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == XTFPGA_I2S_INT_STATUS; +} + +static const struct regmap_config xtfpga_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = XTFPGA_I2S_CHAN3_DATA, + .writeable_reg = xtfpga_i2s_wr_reg, + .readable_reg = xtfpga_i2s_rd_reg, + .volatile_reg = xtfpga_i2s_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +/* Generate functions that do PIO from TX DMA area to FIFO for all supported + * stream formats. + * Functions will be called xtfpga_pcm_tx_x, e.g. + * xtfpga_pcm_tx_2x16 for 16-bit stereo. + * + * FIFO consists of 32-bit words, one word per channel, always 2 channels. + * If I2S interface is configured with smaller sample resolution, only + * the LSB of each word is used. + */ +#define xtfpga_pcm_tx_fn(channels, sample_bits) \ +static unsigned xtfpga_pcm_tx_##channels##x##sample_bits( \ + struct xtfpga_i2s *i2s, struct snd_pcm_runtime *runtime, \ + unsigned tx_ptr) \ +{ \ + const u##sample_bits (*p)[channels] = \ + (void *)runtime->dma_area; \ +\ + for (; i2s->tx_fifo_level < i2s->tx_fifo_high; \ + i2s->tx_fifo_level += 2) { \ + iowrite32(p[tx_ptr][0], \ + i2s->regs + XTFPGA_I2S_CHAN0_DATA); \ + iowrite32(p[tx_ptr][channels - 1], \ + i2s->regs + XTFPGA_I2S_CHAN0_DATA); \ + if (++tx_ptr >= runtime->buffer_size) \ + tx_ptr = 0; \ + } \ + return tx_ptr; \ +} + +xtfpga_pcm_tx_fn(1, 16) +xtfpga_pcm_tx_fn(2, 16) +xtfpga_pcm_tx_fn(1, 32) +xtfpga_pcm_tx_fn(2, 32) + +#undef xtfpga_pcm_tx_fn + +static bool xtfpga_pcm_push_tx(struct xtfpga_i2s *i2s) +{ + struct snd_pcm_substream *tx_substream; + bool tx_active; + + rcu_read_lock(); + tx_substream = rcu_dereference(i2s->tx_substream); + tx_active = tx_substream && snd_pcm_running(tx_substream); + if (tx_active) { + unsigned tx_ptr = READ_ONCE(i2s->tx_ptr); + unsigned new_tx_ptr = i2s->tx_fn(i2s, tx_substream->runtime, + tx_ptr); + + cmpxchg(&i2s->tx_ptr, tx_ptr, new_tx_ptr); + } + rcu_read_unlock(); + + return tx_active; +} + +static void xtfpga_pcm_refill_fifo(struct xtfpga_i2s *i2s) +{ + unsigned int_status; + unsigned i; + + regmap_read(i2s->regmap, XTFPGA_I2S_INT_STATUS, + &int_status); + + for (i = 0; i < 2; ++i) { + bool tx_active = xtfpga_pcm_push_tx(i2s); + + regmap_write(i2s->regmap, XTFPGA_I2S_INT_STATUS, + XTFPGA_I2S_INT_VALID); + if (tx_active) + regmap_read(i2s->regmap, XTFPGA_I2S_INT_STATUS, + &int_status); + + if (!tx_active || + !(int_status & XTFPGA_I2S_INT_LEVEL)) + break; + + /* After the push the level IRQ is still asserted, + * means FIFO level is below tx_fifo_low. Estimate + * it as tx_fifo_low. + */ + i2s->tx_fifo_level = i2s->tx_fifo_low; + } + + if (!(int_status & XTFPGA_I2S_INT_LEVEL)) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, + XTFPGA_I2S_INT_VALID); + else if (!(int_status & XTFPGA_I2S_INT_UNDERRUN)) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, + XTFPGA_I2S_INT_UNDERRUN); + + if (!(int_status & XTFPGA_I2S_INT_UNDERRUN)) + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_INT_ENABLE | + XTFPGA_I2S_CONFIG_TX_ENABLE, + XTFPGA_I2S_CONFIG_INT_ENABLE | + XTFPGA_I2S_CONFIG_TX_ENABLE); + else + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_INT_ENABLE | + XTFPGA_I2S_CONFIG_TX_ENABLE, 0); +} + +static irqreturn_t xtfpga_i2s_threaded_irq_handler(int irq, void *dev_id) +{ + struct xtfpga_i2s *i2s = dev_id; + struct snd_pcm_substream *tx_substream; + unsigned config, int_status, int_mask; + + regmap_read(i2s->regmap, XTFPGA_I2S_CONFIG, &config); + regmap_read(i2s->regmap, XTFPGA_I2S_INT_MASK, &int_mask); + regmap_read(i2s->regmap, XTFPGA_I2S_INT_STATUS, &int_status); + + if (!(config & XTFPGA_I2S_CONFIG_INT_ENABLE) || + !(int_status & int_mask & XTFPGA_I2S_INT_VALID)) + return IRQ_NONE; + + /* Update FIFO level estimate in accordance with interrupt status + * register. + */ + if (int_status & XTFPGA_I2S_INT_UNDERRUN) { + i2s->tx_fifo_level = 0; + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_TX_ENABLE, 0); + } else { + /* The FIFO isn't empty, but is below tx_fifo_low. Estimate + * it as tx_fifo_low. + */ + i2s->tx_fifo_level = i2s->tx_fifo_low; + } + + rcu_read_lock(); + tx_substream = rcu_dereference(i2s->tx_substream); + + if (tx_substream && snd_pcm_running(tx_substream)) { + snd_pcm_period_elapsed(tx_substream); + if (int_status & XTFPGA_I2S_INT_UNDERRUN) + dev_dbg_ratelimited(i2s->dev, "%s: underrun\n", + __func__); + } + rcu_read_unlock(); + + /* Refill FIFO, update allowed IRQ reasons, enable IRQ if FIFO is + * not empty. + */ + xtfpga_pcm_refill_fifo(i2s); + + return IRQ_HANDLED; +} + +static int xtfpga_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct xtfpga_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_set_dma_data(dai, substream, i2s); + return 0; +} + +static int xtfpga_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct xtfpga_i2s *i2s = snd_soc_dai_get_drvdata(dai); + unsigned srate = params_rate(params); + unsigned channels = params_channels(params); + unsigned period_size = params_period_size(params); + unsigned sample_size = snd_pcm_format_width(params_format(params)); + unsigned freq, ratio, level; + int err; + + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_RES_MASK, + sample_size << XTFPGA_I2S_CONFIG_RES_BASE); + + freq = 256 * srate; + err = clk_set_rate(i2s->clk, freq); + if (err < 0) + return err; + + /* ratio field of the config register controls MCLK->I2S clock + * derivation: I2S clock = MCLK / (2 * (ratio + 2)). + * + * So with MCLK = 256 * sample rate ratio is 0 for 32 bit stereo + * and 2 for 16 bit stereo. + */ + ratio = (freq - (srate * sample_size * 8)) / + (srate * sample_size * 4); + + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_RATIO_MASK, + ratio << XTFPGA_I2S_CONFIG_RATIO_BASE); + + i2s->tx_fifo_low = XTFPGA_I2S_FIFO_SIZE / 2; + + /* period_size * 2: FIFO always gets 2 samples per frame */ + for (level = 1; + i2s->tx_fifo_low / 2 >= period_size * 2 && + level < (XTFPGA_I2S_CONFIG_LEVEL_MASK >> + XTFPGA_I2S_CONFIG_LEVEL_BASE); ++level) + i2s->tx_fifo_low /= 2; + + i2s->tx_fifo_high = 2 * i2s->tx_fifo_low; + + regmap_update_bits(i2s->regmap, XTFPGA_I2S_CONFIG, + XTFPGA_I2S_CONFIG_LEVEL_MASK, + level << XTFPGA_I2S_CONFIG_LEVEL_BASE); + + dev_dbg(i2s->dev, + "%s srate: %u, channels: %u, sample_size: %u, period_size: %u\n", + __func__, srate, channels, sample_size, period_size); + dev_dbg(i2s->dev, "%s freq: %u, ratio: %u, level: %u\n", + __func__, freq, ratio, level); + + return 0; +} + +static int xtfpga_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) + return -EINVAL; + + return 0; +} + +/* PCM */ + +static const struct snd_pcm_hardware xtfpga_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .period_bytes_min = 2, + .period_bytes_max = XTFPGA_I2S_FIFO_SIZE / 2 * 8, + .periods_min = 2, + .periods_max = XTFPGA_I2S_FIFO_SIZE * 8 / 2, + .buffer_bytes_max = XTFPGA_I2S_FIFO_SIZE * 8, + .fifo_size = 16, +}; + +static int xtfpga_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + void *p; + + snd_soc_set_runtime_hwparams(substream, &xtfpga_pcm_hardware); + p = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + runtime->private_data = p; + + return 0; +} + +static int xtfpga_pcm_close(struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int xtfpga_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct xtfpga_i2s *i2s = runtime->private_data; + unsigned channels = params_channels(hw_params); + + switch (channels) { + case 1: + case 2: + break; + + default: + return -EINVAL; + + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s->tx_fn = (channels == 1) ? + xtfpga_pcm_tx_1x16 : + xtfpga_pcm_tx_2x16; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + i2s->tx_fn = (channels == 1) ? + xtfpga_pcm_tx_1x32 : + xtfpga_pcm_tx_2x32; + break; + + default: + return -EINVAL; + } + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + return ret; +} + +static int xtfpga_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct xtfpga_i2s *i2s = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + WRITE_ONCE(i2s->tx_ptr, 0); + rcu_assign_pointer(i2s->tx_substream, substream); + xtfpga_pcm_refill_fifo(i2s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + rcu_assign_pointer(i2s->tx_substream, NULL); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} + +static snd_pcm_uframes_t xtfpga_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct xtfpga_i2s *i2s = runtime->private_data; + snd_pcm_uframes_t pos = READ_ONCE(i2s->tx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int xtfpga_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + size_t size = xtfpga_pcm_hardware.buffer_bytes_max; + + return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_DEV, + card->dev, size, size); +} + +static const struct snd_pcm_ops xtfpga_pcm_ops = { + .open = xtfpga_pcm_open, + .close = xtfpga_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = xtfpga_pcm_hw_params, + .trigger = xtfpga_pcm_trigger, + .pointer = xtfpga_pcm_pointer, +}; + +static const struct snd_soc_component_driver xtfpga_i2s_component = { + .name = DRV_NAME, + .pcm_new = xtfpga_pcm_new, + .ops = &xtfpga_pcm_ops, +}; + +static const struct snd_soc_dai_ops xtfpga_i2s_dai_ops = { + .startup = xtfpga_i2s_startup, + .hw_params = xtfpga_i2s_hw_params, + .set_fmt = xtfpga_i2s_set_fmt, +}; + +static struct snd_soc_dai_driver xtfpga_i2s_dai[] = { + { + .name = "xtfpga-i2s", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &xtfpga_i2s_dai_ops, + }, +}; + +static int xtfpga_i2s_runtime_suspend(struct device *dev) +{ + struct xtfpga_i2s *i2s = dev_get_drvdata(dev); + + clk_disable_unprepare(i2s->clk); + return 0; +} + +static int xtfpga_i2s_runtime_resume(struct device *dev) +{ + struct xtfpga_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->clk); + if (ret) { + dev_err(dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + return 0; +} + +static int xtfpga_i2s_probe(struct platform_device *pdev) +{ + struct xtfpga_i2s *i2s; + struct resource *mem; + int err, irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) { + err = -ENOMEM; + goto err; + } + platform_set_drvdata(pdev, i2s); + i2s->dev = &pdev->dev; + dev_dbg(&pdev->dev, "dev: %p, i2s: %p\n", &pdev->dev, i2s); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(i2s->regs)) { + err = PTR_ERR(i2s->regs); + goto err; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->regs, + &xtfpga_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + err = PTR_ERR(i2s->regmap); + goto err; + } + + i2s->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(i2s->clk)) { + dev_err(&pdev->dev, "couldn't get clock\n"); + err = PTR_ERR(i2s->clk); + goto err; + } + + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, + (0x1 << XTFPGA_I2S_CONFIG_CHANNEL_BASE)); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_STATUS, XTFPGA_I2S_INT_VALID); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, XTFPGA_I2S_INT_UNDERRUN); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No IRQ resource\n"); + err = irq; + goto err; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xtfpga_i2s_threaded_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + pdev->name, i2s); + if (err < 0) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto err; + } + + err = devm_snd_soc_register_component(&pdev->dev, + &xtfpga_i2s_component, + xtfpga_i2s_dai, + ARRAY_SIZE(xtfpga_i2s_dai)); + if (err < 0) { + dev_err(&pdev->dev, "couldn't register component\n"); + goto err; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = xtfpga_i2s_runtime_resume(&pdev->dev); + if (err) + goto err_pm_disable; + } + return 0; + +err_pm_disable: + pm_runtime_disable(&pdev->dev); +err: + dev_err(&pdev->dev, "%s: err = %d\n", __func__, err); + return err; +} + +static int xtfpga_i2s_remove(struct platform_device *pdev) +{ + struct xtfpga_i2s *i2s = dev_get_drvdata(&pdev->dev); + + if (i2s->regmap && !IS_ERR(i2s->regmap)) { + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, 0); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, 0); + regmap_write(i2s->regmap, XTFPGA_I2S_INT_STATUS, + XTFPGA_I2S_INT_VALID); + } + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + xtfpga_i2s_runtime_suspend(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id xtfpga_i2s_of_match[] = { + { .compatible = "cdns,xtfpga-i2s", }, + {}, +}; +MODULE_DEVICE_TABLE(of, xtfpga_i2s_of_match); +#endif + +static const struct dev_pm_ops xtfpga_i2s_pm_ops = { + SET_RUNTIME_PM_OPS(xtfpga_i2s_runtime_suspend, + xtfpga_i2s_runtime_resume, NULL) +}; + +static struct platform_driver xtfpga_i2s_driver = { + .probe = xtfpga_i2s_probe, + .remove = xtfpga_i2s_remove, + .driver = { + .name = "xtfpga-i2s", + .of_match_table = of_match_ptr(xtfpga_i2s_of_match), + .pm = &xtfpga_i2s_pm_ops, + }, +}; + +module_platform_driver(xtfpga_i2s_driver); + +MODULE_AUTHOR("Max Filippov "); +MODULE_DESCRIPTION("xtfpga I2S controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/zte/Kconfig b/sound/soc/zte/Kconfig new file mode 100644 index 000000000..75f67a5d2 --- /dev/null +++ b/sound/soc/zte/Kconfig @@ -0,0 +1,25 @@ +config ZX_SPDIF + tristate "ZTE ZX SPDIF Driver Support" + depends on ARCH_ZX || COMPILE_TEST + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to the + ZTE ZX SPDIF interface + +config ZX_I2S + tristate "ZTE ZX I2S Driver Support" + depends on ARCH_ZX || COMPILE_TEST + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to the + ZTE ZX I2S interface + +config ZX_TDM + tristate "ZTE ZX TDM Driver Support" + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to the + ZTE ZX TDM interface diff --git a/sound/soc/zte/Makefile b/sound/soc/zte/Makefile new file mode 100644 index 000000000..1fc841acd --- /dev/null +++ b/sound/soc/zte/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_ZX_SPDIF) += zx-spdif.o +obj-$(CONFIG_ZX_I2S) += zx-i2s.o +obj-$(CONFIG_ZX_TDM) += zx-tdm.o diff --git a/sound/soc/zte/zx-i2s.c b/sound/soc/zte/zx-i2s.c new file mode 100644 index 000000000..0d36630aa --- /dev/null +++ b/sound/soc/zte/zx-i2s.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2015 Linaro + * + * Author: Jun Nie + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define ZX_I2S_PROCESS_CTRL 0x04 +#define ZX_I2S_TIMING_CTRL 0x08 +#define ZX_I2S_FIFO_CTRL 0x0C +#define ZX_I2S_FIFO_STATUS 0x10 +#define ZX_I2S_INT_EN 0x14 +#define ZX_I2S_INT_STATUS 0x18 +#define ZX_I2S_DATA 0x1C +#define ZX_I2S_FRAME_CNTR 0x20 + +#define I2S_DEAGULT_FIFO_THRES (0x10) +#define I2S_MAX_FIFO_THRES (0x20) + +#define ZX_I2S_PROCESS_TX_EN (1 << 0) +#define ZX_I2S_PROCESS_TX_DIS (0 << 0) +#define ZX_I2S_PROCESS_RX_EN (1 << 1) +#define ZX_I2S_PROCESS_RX_DIS (0 << 1) +#define ZX_I2S_PROCESS_I2S_EN (1 << 2) +#define ZX_I2S_PROCESS_I2S_DIS (0 << 2) + +#define ZX_I2S_TIMING_MAST (1 << 0) +#define ZX_I2S_TIMING_SLAVE (0 << 0) +#define ZX_I2S_TIMING_MS_MASK (1 << 0) +#define ZX_I2S_TIMING_LOOP (1 << 1) +#define ZX_I2S_TIMING_NOR (0 << 1) +#define ZX_I2S_TIMING_LOOP_MASK (1 << 1) +#define ZX_I2S_TIMING_PTNR (1 << 2) +#define ZX_I2S_TIMING_NTPR (0 << 2) +#define ZX_I2S_TIMING_PHASE_MASK (1 << 2) +#define ZX_I2S_TIMING_TDM (1 << 3) +#define ZX_I2S_TIMING_I2S (0 << 3) +#define ZX_I2S_TIMING_TIMING_MASK (1 << 3) +#define ZX_I2S_TIMING_LONG_SYNC (1 << 4) +#define ZX_I2S_TIMING_SHORT_SYNC (0 << 4) +#define ZX_I2S_TIMING_SYNC_MASK (1 << 4) +#define ZX_I2S_TIMING_TEAK_EN (1 << 5) +#define ZX_I2S_TIMING_TEAK_DIS (0 << 5) +#define ZX_I2S_TIMING_TEAK_MASK (1 << 5) +#define ZX_I2S_TIMING_STD_I2S (0 << 6) +#define ZX_I2S_TIMING_MSB_JUSTIF (1 << 6) +#define ZX_I2S_TIMING_LSB_JUSTIF (2 << 6) +#define ZX_I2S_TIMING_ALIGN_MASK (3 << 6) +#define ZX_I2S_TIMING_CHN_MASK (7 << 8) +#define ZX_I2S_TIMING_CHN(x) ((x - 1) << 8) +#define ZX_I2S_TIMING_LANE_MASK (3 << 11) +#define ZX_I2S_TIMING_LANE(x) ((x - 1) << 11) +#define ZX_I2S_TIMING_TSCFG_MASK (7 << 13) +#define ZX_I2S_TIMING_TSCFG(x) (x << 13) +#define ZX_I2S_TIMING_TS_WIDTH_MASK (0x1f << 16) +#define ZX_I2S_TIMING_TS_WIDTH(x) ((x - 1) << 16) +#define ZX_I2S_TIMING_DATA_SIZE_MASK (0x1f << 21) +#define ZX_I2S_TIMING_DATA_SIZE(x) ((x - 1) << 21) +#define ZX_I2S_TIMING_CFG_ERR_MASK (1 << 31) + +#define ZX_I2S_FIFO_CTRL_TX_RST (1 << 0) +#define ZX_I2S_FIFO_CTRL_TX_RST_MASK (1 << 0) +#define ZX_I2S_FIFO_CTRL_RX_RST (1 << 1) +#define ZX_I2S_FIFO_CTRL_RX_RST_MASK (1 << 1) +#define ZX_I2S_FIFO_CTRL_TX_DMA_EN (1 << 4) +#define ZX_I2S_FIFO_CTRL_TX_DMA_DIS (0 << 4) +#define ZX_I2S_FIFO_CTRL_TX_DMA_MASK (1 << 4) +#define ZX_I2S_FIFO_CTRL_RX_DMA_EN (1 << 5) +#define ZX_I2S_FIFO_CTRL_RX_DMA_DIS (0 << 5) +#define ZX_I2S_FIFO_CTRL_RX_DMA_MASK (1 << 5) +#define ZX_I2S_FIFO_CTRL_TX_THRES_MASK (0x1F << 8) +#define ZX_I2S_FIFO_CTRL_RX_THRES_MASK (0x1F << 16) + +#define CLK_RAT (32 * 4) + +struct zx_i2s_info { + struct snd_dmaengine_dai_dma_data dma_playback; + struct snd_dmaengine_dai_dma_data dma_capture; + struct clk *dai_wclk; + struct clk *dai_pclk; + void __iomem *reg_base; + int master; + resource_size_t mapbase; +}; + +static void zx_i2s_tx_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_PROCESS_CTRL); + if (on) + val |= ZX_I2S_PROCESS_TX_EN | ZX_I2S_PROCESS_I2S_EN; + else + val &= ~(ZX_I2S_PROCESS_TX_EN | ZX_I2S_PROCESS_I2S_EN); + writel_relaxed(val, base + ZX_I2S_PROCESS_CTRL); +} + +static void zx_i2s_rx_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_PROCESS_CTRL); + if (on) + val |= ZX_I2S_PROCESS_RX_EN | ZX_I2S_PROCESS_I2S_EN; + else + val &= ~(ZX_I2S_PROCESS_RX_EN | ZX_I2S_PROCESS_I2S_EN); + writel_relaxed(val, base + ZX_I2S_PROCESS_CTRL); +} + +static void zx_i2s_tx_dma_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_FIFO_CTRL); + val |= ZX_I2S_FIFO_CTRL_TX_RST | (I2S_DEAGULT_FIFO_THRES << 8); + if (on) + val |= ZX_I2S_FIFO_CTRL_TX_DMA_EN; + else + val &= ~ZX_I2S_FIFO_CTRL_TX_DMA_EN; + writel_relaxed(val, base + ZX_I2S_FIFO_CTRL); +} + +static void zx_i2s_rx_dma_en(void __iomem *base, bool on) +{ + unsigned long val; + + val = readl_relaxed(base + ZX_I2S_FIFO_CTRL); + val |= ZX_I2S_FIFO_CTRL_RX_RST | (I2S_DEAGULT_FIFO_THRES << 16); + if (on) + val |= ZX_I2S_FIFO_CTRL_RX_DMA_EN; + else + val &= ~ZX_I2S_FIFO_CTRL_RX_DMA_EN; + writel_relaxed(val, base + ZX_I2S_FIFO_CTRL); +} + +#define ZX_I2S_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | 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 ZX_I2S_FMTBIT \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int zx_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, zx_i2s); + zx_i2s->dma_playback.addr = zx_i2s->mapbase + ZX_I2S_DATA; + zx_i2s->dma_playback.maxburst = 16; + zx_i2s->dma_capture.addr = zx_i2s->mapbase + ZX_I2S_DATA; + zx_i2s->dma_capture.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &zx_i2s->dma_playback, + &zx_i2s->dma_capture); + return 0; +} + +static int zx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct zx_i2s_info *i2s = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long val; + + val = readl_relaxed(i2s->reg_base + ZX_I2S_TIMING_CTRL); + val &= ~(ZX_I2S_TIMING_TIMING_MASK | ZX_I2S_TIMING_ALIGN_MASK | + ZX_I2S_TIMING_TEAK_MASK | ZX_I2S_TIMING_SYNC_MASK | + ZX_I2S_TIMING_MS_MASK); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= (ZX_I2S_TIMING_I2S | ZX_I2S_TIMING_STD_I2S); + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= (ZX_I2S_TIMING_I2S | ZX_I2S_TIMING_MSB_JUSTIF); + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= (ZX_I2S_TIMING_I2S | ZX_I2S_TIMING_LSB_JUSTIF); + break; + default: + dev_err(cpu_dai->dev, "Unknown i2s timing\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* Codec is master, and I2S is slave. */ + i2s->master = 0; + val |= ZX_I2S_TIMING_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* Codec is slave, and I2S is master. */ + i2s->master = 1; + val |= ZX_I2S_TIMING_MAST; + break; + default: + dev_err(cpu_dai->dev, "Unknown master/slave format\n"); + return -EINVAL; + } + + writel_relaxed(val, i2s->reg_base + ZX_I2S_TIMING_CTRL); + return 0; +} + +static int zx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct zx_i2s_info *i2s = snd_soc_dai_get_drvdata(socdai); + struct snd_dmaengine_dai_dma_data *dma_data; + unsigned int lane, ch_num, len, ret = 0; + unsigned int ts_width = 32; + unsigned long val; + unsigned long chn_cfg; + + dma_data = snd_soc_dai_get_dma_data(socdai, substream); + dma_data->addr_width = ts_width >> 3; + + val = readl_relaxed(i2s->reg_base + ZX_I2S_TIMING_CTRL); + val &= ~(ZX_I2S_TIMING_TS_WIDTH_MASK | ZX_I2S_TIMING_DATA_SIZE_MASK | + ZX_I2S_TIMING_LANE_MASK | ZX_I2S_TIMING_CHN_MASK | + ZX_I2S_TIMING_TSCFG_MASK); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + len = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + len = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + len = 32; + break; + default: + dev_err(socdai->dev, "Unknown data format\n"); + return -EINVAL; + } + val |= ZX_I2S_TIMING_TS_WIDTH(ts_width) | ZX_I2S_TIMING_DATA_SIZE(len); + + ch_num = params_channels(params); + switch (ch_num) { + case 1: + lane = 1; + chn_cfg = 2; + break; + case 2: + case 4: + case 6: + case 8: + lane = ch_num / 2; + chn_cfg = 3; + break; + default: + dev_err(socdai->dev, "Not support channel num %d\n", ch_num); + return -EINVAL; + } + val |= ZX_I2S_TIMING_LANE(lane); + val |= ZX_I2S_TIMING_TSCFG(chn_cfg); + val |= ZX_I2S_TIMING_CHN(ch_num); + writel_relaxed(val, i2s->reg_base + ZX_I2S_TIMING_CTRL); + + if (i2s->master) + ret = clk_set_rate(i2s->dai_wclk, + params_rate(params) * ch_num * CLK_RAT); + + return ret; +} + +static int zx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (capture) + zx_i2s_rx_dma_en(zx_i2s->reg_base, true); + else + zx_i2s_tx_dma_en(zx_i2s->reg_base, true); + /* fall thru */ + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (capture) + zx_i2s_rx_en(zx_i2s->reg_base, true); + else + zx_i2s_tx_en(zx_i2s->reg_base, true); + break; + + case SNDRV_PCM_TRIGGER_STOP: + if (capture) + zx_i2s_rx_dma_en(zx_i2s->reg_base, false); + else + zx_i2s_tx_dma_en(zx_i2s->reg_base, false); + /* fall thru */ + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (capture) + zx_i2s_rx_en(zx_i2s->reg_base, false); + else + zx_i2s_tx_en(zx_i2s->reg_base, false); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int zx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + int ret; + + ret = clk_prepare_enable(zx_i2s->dai_wclk); + if (ret) + return ret; + + ret = clk_prepare_enable(zx_i2s->dai_pclk); + if (ret) { + clk_disable_unprepare(zx_i2s->dai_wclk); + return ret; + } + + return ret; +} + +static void zx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_i2s_info *zx_i2s = dev_get_drvdata(dai->dev); + + clk_disable_unprepare(zx_i2s->dai_wclk); + clk_disable_unprepare(zx_i2s->dai_pclk); +} + +static const struct snd_soc_dai_ops zx_i2s_dai_ops = { + .trigger = zx_i2s_trigger, + .hw_params = zx_i2s_hw_params, + .set_fmt = zx_i2s_set_fmt, + .startup = zx_i2s_startup, + .shutdown = zx_i2s_shutdown, +}; + +static const struct snd_soc_component_driver zx_i2s_component = { + .name = "zx-i2s", +}; + +static struct snd_soc_dai_driver zx_i2s_dai = { + .name = "zx-i2s-dai", + .id = 0, + .probe = zx_i2s_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 8, + .rates = ZX_I2S_RATES, + .formats = ZX_I2S_FMTBIT, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = ZX_I2S_RATES, + .formats = ZX_I2S_FMTBIT, + }, + .ops = &zx_i2s_dai_ops, +}; + +static int zx_i2s_probe(struct platform_device *pdev) +{ + struct resource *res; + struct zx_i2s_info *zx_i2s; + int ret; + + zx_i2s = devm_kzalloc(&pdev->dev, sizeof(*zx_i2s), GFP_KERNEL); + if (!zx_i2s) + return -ENOMEM; + + zx_i2s->dai_wclk = devm_clk_get(&pdev->dev, "wclk"); + if (IS_ERR(zx_i2s->dai_wclk)) { + dev_err(&pdev->dev, "Fail to get wclk\n"); + return PTR_ERR(zx_i2s->dai_wclk); + } + + zx_i2s->dai_pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(zx_i2s->dai_pclk)) { + dev_err(&pdev->dev, "Fail to get pclk\n"); + return PTR_ERR(zx_i2s->dai_pclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zx_i2s->mapbase = res->start; + zx_i2s->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(zx_i2s->reg_base)) { + dev_err(&pdev->dev, "ioremap failed!\n"); + return PTR_ERR(zx_i2s->reg_base); + } + + writel_relaxed(0, zx_i2s->reg_base + ZX_I2S_FIFO_CTRL); + platform_set_drvdata(pdev, zx_i2s); + + ret = devm_snd_soc_register_component(&pdev->dev, &zx_i2s_component, + &zx_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register DAI failed: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id zx_i2s_dt_ids[] = { + { .compatible = "zte,zx296702-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, zx_i2s_dt_ids); + +static struct platform_driver i2s_driver = { + .probe = zx_i2s_probe, + .driver = { + .name = "zx-i2s", + .of_match_table = zx_i2s_dt_ids, + }, +}; + +module_platform_driver(i2s_driver); + +MODULE_AUTHOR("Jun Nie "); +MODULE_DESCRIPTION("ZTE I2S SoC DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/zte/zx-spdif.c b/sound/soc/zte/zx-spdif.c new file mode 100644 index 000000000..17b6ce350 --- /dev/null +++ b/sound/soc/zte/zx-spdif.c @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2015 Linaro + * + * Author: Jun Nie + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ZX_CTRL 0x04 +#define ZX_FIFOCTRL 0x08 +#define ZX_INT_STATUS 0x10 +#define ZX_INT_MASK 0x14 +#define ZX_DATA 0x18 +#define ZX_VALID_BIT 0x1c +#define ZX_CH_STA_1 0x20 +#define ZX_CH_STA_2 0x24 +#define ZX_CH_STA_3 0x28 +#define ZX_CH_STA_4 0x2c +#define ZX_CH_STA_5 0x30 +#define ZX_CH_STA_6 0x34 + +#define ZX_CTRL_MODA_16 (0 << 6) +#define ZX_CTRL_MODA_18 BIT(6) +#define ZX_CTRL_MODA_20 (2 << 6) +#define ZX_CTRL_MODA_24 (3 << 6) +#define ZX_CTRL_MODA_MASK (3 << 6) + +#define ZX_CTRL_ENB BIT(4) +#define ZX_CTRL_DNB (0 << 4) +#define ZX_CTRL_ENB_MASK BIT(4) + +#define ZX_CTRL_TX_OPEN BIT(0) +#define ZX_CTRL_TX_CLOSE (0 << 0) +#define ZX_CTRL_TX_MASK BIT(0) + +#define ZX_CTRL_OPEN (ZX_CTRL_TX_OPEN | ZX_CTRL_ENB) +#define ZX_CTRL_CLOSE (ZX_CTRL_TX_CLOSE | ZX_CTRL_DNB) + +#define ZX_CTRL_DOUBLE_TRACK (0 << 8) +#define ZX_CTRL_LEFT_TRACK BIT(8) +#define ZX_CTRL_RIGHT_TRACK (2 << 8) +#define ZX_CTRL_TRACK_MASK (3 << 8) + +#define ZX_FIFOCTRL_TXTH_MASK (0x1f << 8) +#define ZX_FIFOCTRL_TXTH(x) (x << 8) +#define ZX_FIFOCTRL_TX_DMA_EN BIT(2) +#define ZX_FIFOCTRL_TX_DMA_DIS (0 << 2) +#define ZX_FIFOCTRL_TX_DMA_EN_MASK BIT(2) +#define ZX_FIFOCTRL_TX_FIFO_RST BIT(0) +#define ZX_FIFOCTRL_TX_FIFO_RST_MASK BIT(0) + +#define ZX_VALID_DOUBLE_TRACK (0 << 0) +#define ZX_VALID_LEFT_TRACK BIT(1) +#define ZX_VALID_RIGHT_TRACK (2 << 0) +#define ZX_VALID_TRACK_MASK (3 << 0) + +#define ZX_SPDIF_CLK_RAT (2 * 32) + +struct zx_spdif_info { + struct snd_dmaengine_dai_dma_data dma_data; + struct clk *dai_clk; + void __iomem *reg_base; + resource_size_t mapbase; +}; + +static int zx_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, zx_spdif); + zx_spdif->dma_data.addr = zx_spdif->mapbase + ZX_DATA; + zx_spdif->dma_data.maxburst = 8; + snd_soc_dai_init_dma_data(dai, &zx_spdif->dma_data, NULL); + return 0; +} + +static int zx_spdif_chanstats(void __iomem *base, unsigned int rate) +{ + u32 cstas1; + + switch (rate) { + case 22050: + cstas1 = IEC958_AES3_CON_FS_22050; + break; + case 24000: + cstas1 = IEC958_AES3_CON_FS_24000; + break; + case 32000: + cstas1 = IEC958_AES3_CON_FS_32000; + break; + case 44100: + cstas1 = IEC958_AES3_CON_FS_44100; + break; + case 48000: + cstas1 = IEC958_AES3_CON_FS_48000; + break; + case 88200: + cstas1 = IEC958_AES3_CON_FS_88200; + break; + case 96000: + cstas1 = IEC958_AES3_CON_FS_96000; + break; + case 176400: + cstas1 = IEC958_AES3_CON_FS_176400; + break; + case 192000: + cstas1 = IEC958_AES3_CON_FS_192000; + break; + default: + return -EINVAL; + } + cstas1 = cstas1 << 24; + cstas1 |= IEC958_AES0_CON_NOT_COPYRIGHT; + + writel_relaxed(cstas1, base + ZX_CH_STA_1); + return 0; +} + +static int zx_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(socdai->dev); + struct zx_spdif_info *spdif = snd_soc_dai_get_drvdata(socdai); + struct snd_dmaengine_dai_dma_data *dma_data = + snd_soc_dai_get_dma_data(socdai, substream); + u32 val, ch_num, rate; + int ret; + + dma_data->addr_width = params_width(params) >> 3; + + val = readl_relaxed(zx_spdif->reg_base + ZX_CTRL); + val &= ~ZX_CTRL_MODA_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + val |= ZX_CTRL_MODA_16; + break; + + case SNDRV_PCM_FORMAT_S18_3LE: + val |= ZX_CTRL_MODA_18; + break; + + case SNDRV_PCM_FORMAT_S20_3LE: + val |= ZX_CTRL_MODA_20; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + val |= ZX_CTRL_MODA_24; + break; + default: + dev_err(socdai->dev, "Format not support!\n"); + return -EINVAL; + } + + ch_num = params_channels(params); + if (ch_num == 2) + val |= ZX_CTRL_DOUBLE_TRACK; + else + val |= ZX_CTRL_LEFT_TRACK; + writel_relaxed(val, zx_spdif->reg_base + ZX_CTRL); + + val = readl_relaxed(zx_spdif->reg_base + ZX_VALID_BIT); + val &= ~ZX_VALID_TRACK_MASK; + if (ch_num == 2) + val |= ZX_VALID_DOUBLE_TRACK; + else + val |= ZX_VALID_RIGHT_TRACK; + writel_relaxed(val, zx_spdif->reg_base + ZX_VALID_BIT); + + rate = params_rate(params); + ret = zx_spdif_chanstats(zx_spdif->reg_base, rate); + if (ret) + return ret; + return clk_set_rate(spdif->dai_clk, rate * ch_num * ZX_SPDIF_CLK_RAT); +} + +static void zx_spdif_cfg_tx(void __iomem *base, int on) +{ + u32 val; + + val = readl_relaxed(base + ZX_CTRL); + val &= ~(ZX_CTRL_ENB_MASK | ZX_CTRL_TX_MASK); + val |= on ? ZX_CTRL_OPEN : ZX_CTRL_CLOSE; + writel_relaxed(val, base + ZX_CTRL); + + val = readl_relaxed(base + ZX_FIFOCTRL); + val &= ~ZX_FIFOCTRL_TX_DMA_EN_MASK; + if (on) + val |= ZX_FIFOCTRL_TX_DMA_EN; + writel_relaxed(val, base + ZX_FIFOCTRL); +} + +static int zx_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + u32 val; + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + val = readl_relaxed(zx_spdif->reg_base + ZX_FIFOCTRL); + val |= ZX_FIFOCTRL_TX_FIFO_RST; + writel_relaxed(val, zx_spdif->reg_base + ZX_FIFOCTRL); + /* fall thru */ + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + zx_spdif_cfg_tx(zx_spdif->reg_base, true); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + zx_spdif_cfg_tx(zx_spdif->reg_base, false); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int zx_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + + return clk_prepare_enable(zx_spdif->dai_clk); +} + +static void zx_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_spdif_info *zx_spdif = dev_get_drvdata(dai->dev); + + clk_disable_unprepare(zx_spdif->dai_clk); +} + +#define ZX_RATES \ + (SNDRV_PCM_RATE_22050 | 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 ZX_FORMAT \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE \ + | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops zx_spdif_dai_ops = { + .trigger = zx_spdif_trigger, + .startup = zx_spdif_startup, + .shutdown = zx_spdif_shutdown, + .hw_params = zx_spdif_hw_params, +}; + +static struct snd_soc_dai_driver zx_spdif_dai = { + .name = "spdif", + .id = 0, + .probe = zx_spdif_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ZX_RATES, + .formats = ZX_FORMAT, + }, + .ops = &zx_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver zx_spdif_component = { + .name = "spdif", +}; + +static void zx_spdif_dev_init(void __iomem *base) +{ + u32 val; + + writel_relaxed(0, base + ZX_CTRL); + writel_relaxed(0, base + ZX_INT_MASK); + writel_relaxed(0xf, base + ZX_INT_STATUS); + writel_relaxed(0x1, base + ZX_FIFOCTRL); + + val = readl_relaxed(base + ZX_FIFOCTRL); + val &= ~(ZX_FIFOCTRL_TXTH_MASK | ZX_FIFOCTRL_TX_FIFO_RST_MASK); + val |= ZX_FIFOCTRL_TXTH(8); + writel_relaxed(val, base + ZX_FIFOCTRL); +} + +static int zx_spdif_probe(struct platform_device *pdev) +{ + struct resource *res; + struct zx_spdif_info *zx_spdif; + int ret; + + zx_spdif = devm_kzalloc(&pdev->dev, sizeof(*zx_spdif), GFP_KERNEL); + if (!zx_spdif) + return -ENOMEM; + + zx_spdif->dai_clk = devm_clk_get(&pdev->dev, "tx"); + if (IS_ERR(zx_spdif->dai_clk)) { + dev_err(&pdev->dev, "Fail to get clk\n"); + return PTR_ERR(zx_spdif->dai_clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zx_spdif->mapbase = res->start; + zx_spdif->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(zx_spdif->reg_base)) { + dev_err(&pdev->dev, "ioremap failed!\n"); + return PTR_ERR(zx_spdif->reg_base); + } + + zx_spdif_dev_init(zx_spdif->reg_base); + platform_set_drvdata(pdev, zx_spdif); + + ret = devm_snd_soc_register_component(&pdev->dev, &zx_spdif_component, + &zx_spdif_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register DAI failed: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id zx_spdif_dt_ids[] = { + { .compatible = "zte,zx296702-spdif", }, + {} +}; +MODULE_DEVICE_TABLE(of, zx_spdif_dt_ids); + +static struct platform_driver spdif_driver = { + .probe = zx_spdif_probe, + .driver = { + .name = "zx-spdif", + .of_match_table = zx_spdif_dt_ids, + }, +}; + +module_platform_driver(spdif_driver); + +MODULE_AUTHOR("Jun Nie "); +MODULE_DESCRIPTION("ZTE SPDIF SoC DAI"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/zte/zx-tdm.c b/sound/soc/zte/zx-tdm.c new file mode 100644 index 000000000..389272eeb --- /dev/null +++ b/sound/soc/zte/zx-tdm.c @@ -0,0 +1,461 @@ +/* + * ZTE's TDM driver + * + * Copyright (C) 2017 ZTE Ltd + * + * Author: Baoyou Xie + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_TIMING_CTRL 0x04 +#define REG_TX_FIFO_CTRL 0x0C +#define REG_RX_FIFO_CTRL 0x10 +#define REG_INT_EN 0x1C +#define REG_INT_STATUS 0x20 +#define REG_DATABUF 0x24 +#define REG_TS_MASK0 0x44 +#define REG_PROCESS_CTRL 0x54 + +#define FIFO_CTRL_TX_RST BIT(0) +#define FIFO_CTRL_RX_RST BIT(0) +#define DEAGULT_FIFO_THRES GENMASK(4, 2) + +#define FIFO_CTRL_TX_DMA_EN BIT(1) +#define FIFO_CTRL_RX_DMA_EN BIT(1) + +#define TX_FIFO_RST_MASK BIT(0) +#define RX_FIFO_RST_MASK BIT(0) + +#define FIFOCTRL_TX_FIFO_RST BIT(0) +#define FIFOCTRL_RX_FIFO_RST BIT(0) + +#define TXTH_MASK GENMASK(5, 2) +#define RXTH_MASK GENMASK(5, 2) + +#define FIFOCTRL_THRESHOLD(x) ((x) << 2) + +#define TIMING_MS_MASK BIT(1) +/* + * 00: 8 clk cycles every timeslot + * 01: 16 clk cycles every timeslot + * 10: 32 clk cycles every timeslot + */ +#define TIMING_SYNC_WIDTH_MASK GENMASK(6, 5) +#define TIMING_WIDTH_SHIFT 5 +#define TIMING_DEFAULT_WIDTH 0 +#define TIMING_TS_WIDTH(x) ((x) << TIMING_WIDTH_SHIFT) +#define TIMING_WIDTH_FACTOR 8 + +#define TIMING_MASTER_MODE BIT(21) +#define TIMING_LSB_FIRST BIT(20) +#define TIMING_TS_NUM(x) (((x) - 1) << 7) +#define TIMING_CLK_SEL_MASK GENMASK(2, 0) +#define TIMING_CLK_SEL_DEF BIT(2) + +#define PROCESS_TX_EN BIT(0) +#define PROCESS_RX_EN BIT(1) +#define PROCESS_TDM_EN BIT(2) +#define PROCESS_DISABLE_ALL 0 + +#define INT_DISABLE_ALL 0 +#define INT_STATUS_MASK GENMASK(6, 0) + +struct zx_tdm_info { + struct snd_dmaengine_dai_dma_data dma_playback; + struct snd_dmaengine_dai_dma_data dma_capture; + resource_size_t phy_addr; + void __iomem *regbase; + struct clk *dai_wclk; + struct clk *dai_pclk; + int master; + struct device *dev; +}; + +static inline u32 zx_tdm_readl(struct zx_tdm_info *tdm, u16 reg) +{ + return readl_relaxed(tdm->regbase + reg); +} + +static inline void zx_tdm_writel(struct zx_tdm_info *tdm, u16 reg, u32 val) +{ + writel_relaxed(val, tdm->regbase + reg); +} + +static void zx_tdm_tx_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_PROCESS_CTRL); + if (on) + val |= PROCESS_TX_EN | PROCESS_TDM_EN; + else + val &= ~(PROCESS_TX_EN | PROCESS_TDM_EN); + zx_tdm_writel(tdm, REG_PROCESS_CTRL, val); +} + +static void zx_tdm_rx_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_PROCESS_CTRL); + if (on) + val |= PROCESS_RX_EN | PROCESS_TDM_EN; + else + val &= ~(PROCESS_RX_EN | PROCESS_TDM_EN); + zx_tdm_writel(tdm, REG_PROCESS_CTRL, val); +} + +static void zx_tdm_tx_dma_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL); + val |= FIFO_CTRL_TX_RST | DEAGULT_FIFO_THRES; + if (on) + val |= FIFO_CTRL_TX_DMA_EN; + else + val &= ~FIFO_CTRL_TX_DMA_EN; + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val); +} + +static void zx_tdm_rx_dma_en(struct zx_tdm_info *tdm, bool on) +{ + unsigned long val; + + val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL); + val |= FIFO_CTRL_RX_RST | DEAGULT_FIFO_THRES; + if (on) + val |= FIFO_CTRL_RX_DMA_EN; + else + val &= ~FIFO_CTRL_RX_DMA_EN; + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val); +} + +#define ZX_TDM_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define ZX_TDM_FMTBIT \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_MU_LAW | \ + SNDRV_PCM_FMTBIT_A_LAW) + +static int zx_tdm_dai_probe(struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, zx_tdm); + zx_tdm->dma_playback.addr = zx_tdm->phy_addr + REG_DATABUF; + zx_tdm->dma_playback.maxburst = 16; + zx_tdm->dma_capture.addr = zx_tdm->phy_addr + REG_DATABUF; + zx_tdm->dma_capture.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &zx_tdm->dma_playback, + &zx_tdm->dma_capture); + return 0; +} + +static int zx_tdm_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long val; + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val &= ~(TIMING_SYNC_WIDTH_MASK | TIMING_MS_MASK); + val |= TIMING_DEFAULT_WIDTH << TIMING_WIDTH_SHIFT; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tdm->master = 1; + val |= TIMING_MASTER_MODE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + tdm->master = 0; + val &= ~TIMING_MASTER_MODE; + break; + default: + dev_err(cpu_dai->dev, "Unknown master/slave format\n"); + return -EINVAL; + } + + + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + + return 0; +} + +static int zx_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(socdai); + struct snd_dmaengine_dai_dma_data *dma_data; + unsigned int ts_width = TIMING_DEFAULT_WIDTH; + unsigned int ch_num = 32; + unsigned int mask = 0; + unsigned int ret = 0; + unsigned long val; + + dma_data = snd_soc_dai_get_dma_data(socdai, substream); + dma_data->addr_width = ch_num >> 3; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_MU_LAW: + case SNDRV_PCM_FORMAT_A_LAW: + case SNDRV_PCM_FORMAT_S16_LE: + ts_width = 1; + break; + default: + ts_width = 0; + dev_err(socdai->dev, "Unknown data format\n"); + return -EINVAL; + } + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val |= TIMING_TS_WIDTH(ts_width) | TIMING_TS_NUM(1); + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + zx_tdm_writel(tdm, REG_TS_MASK0, mask); + + if (tdm->master) + ret = clk_set_rate(tdm->dai_wclk, + params_rate(params) * TIMING_WIDTH_FACTOR * ch_num); + + return ret; +} + +static int zx_tdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + unsigned int val; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (capture) { + val = zx_tdm_readl(zx_tdm, REG_RX_FIFO_CTRL); + val |= FIFOCTRL_RX_FIFO_RST; + zx_tdm_writel(zx_tdm, REG_RX_FIFO_CTRL, val); + + zx_tdm_rx_dma_en(zx_tdm, true); + } else { + val = zx_tdm_readl(zx_tdm, REG_TX_FIFO_CTRL); + val |= FIFOCTRL_TX_FIFO_RST; + zx_tdm_writel(zx_tdm, REG_TX_FIFO_CTRL, val); + + zx_tdm_tx_dma_en(zx_tdm, true); + } + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (capture) + zx_tdm_rx_en(zx_tdm, true); + else + zx_tdm_tx_en(zx_tdm, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + if (capture) + zx_tdm_rx_dma_en(zx_tdm, false); + else + zx_tdm_tx_dma_en(zx_tdm, false); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (capture) + zx_tdm_rx_en(zx_tdm, false); + else + zx_tdm_tx_en(zx_tdm, false); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int zx_tdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + int ret; + + ret = clk_prepare_enable(zx_tdm->dai_wclk); + if (ret) + return ret; + + ret = clk_prepare_enable(zx_tdm->dai_pclk); + if (ret) { + clk_disable_unprepare(zx_tdm->dai_wclk); + return ret; + } + + return 0; +} + +static void zx_tdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev); + + clk_disable_unprepare(zx_tdm->dai_pclk); + clk_disable_unprepare(zx_tdm->dai_wclk); +} + +static const struct snd_soc_dai_ops zx_tdm_dai_ops = { + .trigger = zx_tdm_trigger, + .hw_params = zx_tdm_hw_params, + .set_fmt = zx_tdm_set_fmt, + .startup = zx_tdm_startup, + .shutdown = zx_tdm_shutdown, +}; + +static const struct snd_soc_component_driver zx_tdm_component = { + .name = "zx-tdm", +}; + +static void zx_tdm_init_state(struct zx_tdm_info *tdm) +{ + unsigned int val; + + zx_tdm_writel(tdm, REG_PROCESS_CTRL, PROCESS_DISABLE_ALL); + + val = zx_tdm_readl(tdm, REG_TIMING_CTRL); + val |= TIMING_LSB_FIRST; + val &= ~TIMING_CLK_SEL_MASK; + val |= TIMING_CLK_SEL_DEF; + zx_tdm_writel(tdm, REG_TIMING_CTRL, val); + + zx_tdm_writel(tdm, REG_INT_EN, INT_DISABLE_ALL); + /* + * write INT_STATUS register to clear it. + */ + zx_tdm_writel(tdm, REG_INT_STATUS, INT_STATUS_MASK); + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, FIFOCTRL_RX_FIFO_RST); + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, FIFOCTRL_TX_FIFO_RST); + + val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL); + val &= ~(RXTH_MASK | RX_FIFO_RST_MASK); + val |= FIFOCTRL_THRESHOLD(8); + zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val); + + val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL); + val &= ~(TXTH_MASK | TX_FIFO_RST_MASK); + val |= FIFOCTRL_THRESHOLD(8); + zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val); +} + +static struct snd_soc_dai_driver zx_tdm_dai = { + .name = "zx-tdm-dai", + .id = 0, + .probe = zx_tdm_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 4, + .rates = ZX_TDM_RATES, + .formats = ZX_TDM_FMTBIT, + }, + .capture = { + .channels_min = 1, + .channels_max = 4, + .rates = ZX_TDM_RATES, + .formats = ZX_TDM_FMTBIT, + }, + .ops = &zx_tdm_dai_ops, +}; + +static int zx_tdm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct of_phandle_args out_args; + unsigned int dma_reg_offset; + struct zx_tdm_info *zx_tdm; + unsigned int dma_mask; + struct resource *res; + struct regmap *regmap_sysctrl; + int ret; + + zx_tdm = devm_kzalloc(&pdev->dev, sizeof(*zx_tdm), GFP_KERNEL); + if (!zx_tdm) + return -ENOMEM; + + zx_tdm->dev = dev; + + zx_tdm->dai_wclk = devm_clk_get(&pdev->dev, "wclk"); + if (IS_ERR(zx_tdm->dai_wclk)) { + dev_err(&pdev->dev, "Fail to get wclk\n"); + return PTR_ERR(zx_tdm->dai_wclk); + } + + zx_tdm->dai_pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(zx_tdm->dai_pclk)) { + dev_err(&pdev->dev, "Fail to get pclk\n"); + return PTR_ERR(zx_tdm->dai_pclk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + zx_tdm->phy_addr = res->start; + zx_tdm->regbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(zx_tdm->regbase)) + return PTR_ERR(zx_tdm->regbase); + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "zte,tdm-dma-sysctrl", 2, 0, &out_args); + if (ret) { + dev_err(&pdev->dev, "Fail to get zte,tdm-dma-sysctrl\n"); + return ret; + } + + dma_reg_offset = out_args.args[0]; + dma_mask = out_args.args[1]; + regmap_sysctrl = syscon_node_to_regmap(out_args.np); + if (IS_ERR(regmap_sysctrl)) { + of_node_put(out_args.np); + return PTR_ERR(regmap_sysctrl); + } + + regmap_update_bits(regmap_sysctrl, dma_reg_offset, dma_mask, dma_mask); + of_node_put(out_args.np); + + zx_tdm_init_state(zx_tdm); + platform_set_drvdata(pdev, zx_tdm); + + ret = devm_snd_soc_register_component(&pdev->dev, &zx_tdm_component, + &zx_tdm_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Register DAI failed: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id zx_tdm_dt_ids[] = { + { .compatible = "zte,zx296718-tdm", }, + {} +}; +MODULE_DEVICE_TABLE(of, zx_tdm_dt_ids); + +static struct platform_driver tdm_driver = { + .probe = zx_tdm_probe, + .driver = { + .name = "zx-tdm", + .of_match_table = zx_tdm_dt_ids, + }, +}; +module_platform_driver(tdm_driver); + +MODULE_AUTHOR("Baoyou Xie "); +MODULE_DESCRIPTION("ZTE TDM DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/sound_core.c b/sound/sound_core.c new file mode 100644 index 000000000..dd64c4b19 --- /dev/null +++ b/sound/sound_core.c @@ -0,0 +1,625 @@ +/* + * Sound core. This file is composed of two parts. sound_class + * which is common to both OSS and ALSA and OSS sound core which + * is used OSS or emulation of it. + */ + +/* + * First, the common part. + */ +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SOUND_OSS_CORE +static int __init init_oss_soundcore(void); +static void cleanup_oss_soundcore(void); +#else +static inline int init_oss_soundcore(void) { return 0; } +static inline void cleanup_oss_soundcore(void) { } +#endif + +struct class *sound_class; +EXPORT_SYMBOL(sound_class); + +MODULE_DESCRIPTION("Core sound module"); +MODULE_AUTHOR("Alan Cox"); +MODULE_LICENSE("GPL"); + +static char *sound_devnode(struct device *dev, umode_t *mode) +{ + if (MAJOR(dev->devt) == SOUND_MAJOR) + return NULL; + return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev)); +} + +static int __init init_soundcore(void) +{ + int rc; + + rc = init_oss_soundcore(); + if (rc) + return rc; + + sound_class = class_create(THIS_MODULE, "sound"); + if (IS_ERR(sound_class)) { + cleanup_oss_soundcore(); + return PTR_ERR(sound_class); + } + + sound_class->devnode = sound_devnode; + + return 0; +} + +static void __exit cleanup_soundcore(void) +{ + cleanup_oss_soundcore(); + class_destroy(sound_class); +} + +subsys_initcall(init_soundcore); +module_exit(cleanup_soundcore); + + +#ifdef CONFIG_SOUND_OSS_CORE +/* + * OSS sound core handling. Breaks out sound functions to submodules + * + * Author: Alan Cox + * + * Fixes: + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * -------------------- + * + * Top level handler for the sound subsystem. Various devices can + * plug into this. The fact they don't all go via OSS doesn't mean + * they don't have to implement the OSS API. There is a lot of logic + * to keeping much of the OSS weight out of the code in a compatibility + * module, but it's up to the driver to rember to load it... + * + * The code provides a set of functions for registration of devices + * by type. This is done rather than providing a single call so that + * we can hide any future changes in the internals (eg when we go to + * 32bit dev_t) from the modules and their interface. + * + * Secondly we need to allocate the dsp, dsp16 and audio devices as + * one. Thus we misuse the chains a bit to simplify this. + * + * Thirdly to make it more fun and for 2.3.x and above we do all + * of this using fine grained locking. + * + * FIXME: we have to resolve modules and fine grained load/unload + * locking at some point in 2.3.x. + */ + +#include +#include +#include +#include +#include +#include + +#define SOUND_STEP 16 + +struct sound_unit +{ + int unit_minor; + const struct file_operations *unit_fops; + struct sound_unit *next; + char name[32]; +}; + +/* + * By default, OSS sound_core claims full legacy minor range (0-255) + * of SOUND_MAJOR to trap open attempts to any sound minor and + * requests modules using custom sound-slot/service-* module aliases. + * The only benefit of doing this is allowing use of custom module + * aliases instead of the standard char-major-* ones. This behavior + * prevents alternative OSS implementation and is scheduled to be + * removed. + * + * CONFIG_SOUND_OSS_CORE_PRECLAIM and soundcore.preclaim_oss kernel + * parameter are added to allow distros and developers to try and + * switch to alternative implementations without needing to rebuild + * the kernel in the meantime. If preclaim_oss is non-zero, the + * kernel will behave the same as before. All SOUND_MAJOR minors are + * preclaimed and the custom module aliases along with standard chrdev + * ones are emitted if a missing device is opened. If preclaim_oss is + * zero, sound_core only grabs what's actually in use and for missing + * devices only the standard chrdev aliases are requested. + * + * All these clutters are scheduled to be removed along with + * sound-slot/service-* module aliases. + */ +#ifdef CONFIG_SOUND_OSS_CORE_PRECLAIM +static int preclaim_oss = 1; +#else +static int preclaim_oss = 0; +#endif + +module_param(preclaim_oss, int, 0444); + +static int soundcore_open(struct inode *, struct file *); + +static const struct file_operations soundcore_fops = +{ + /* We must have an owner or the module locking fails */ + .owner = THIS_MODULE, + .open = soundcore_open, + .llseek = noop_llseek, +}; + +/* + * Low level list operator. Scan the ordered list, find a hole and + * join into it. Called with the lock asserted + */ + +static int __sound_insert_unit(struct sound_unit * s, struct sound_unit **list, const struct file_operations *fops, int index, int low, int top) +{ + int n=low; + + if (index < 0) { /* first free */ + + while (*list && (*list)->unit_minornext); + + while(nunit_minor>n) + break; + list=&((*list)->next); + n+=SOUND_STEP; + } + + if(n>=top) + return -ENOENT; + } else { + n = low+(index*16); + while (*list) { + if ((*list)->unit_minor==n) + return -EBUSY; + if ((*list)->unit_minor>n) + break; + list=&((*list)->next); + } + } + + /* + * Fill it in + */ + + s->unit_minor=n; + s->unit_fops=fops; + + /* + * Link it + */ + + s->next=*list; + *list=s; + + + return n; +} + +/* + * Remove a node from the chain. Called with the lock asserted + */ + +static struct sound_unit *__sound_remove_unit(struct sound_unit **list, int unit) +{ + while(*list) + { + struct sound_unit *p=*list; + if(p->unit_minor==unit) + { + *list=p->next; + return p; + } + list=&(p->next); + } + printk(KERN_ERR "Sound device %d went missing!\n", unit); + return NULL; +} + +/* + * This lock guards the sound loader list. + */ + +static DEFINE_SPINLOCK(sound_loader_lock); + +/* + * Allocate the controlling structure and add it to the sound driver + * list. Acquires locks as needed + */ + +static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, const char *name, umode_t mode, struct device *dev) +{ + struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL); + int r; + + if (!s) + return -ENOMEM; + + spin_lock(&sound_loader_lock); +retry: + r = __sound_insert_unit(s, list, fops, index, low, top); + spin_unlock(&sound_loader_lock); + + if (r < 0) + goto fail; + else if (r < SOUND_STEP) + sprintf(s->name, "sound/%s", name); + else + sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP); + + if (!preclaim_oss) { + /* + * Something else might have grabbed the minor. If + * first free slot is requested, rescan with @low set + * to the next unit; otherwise, -EBUSY. + */ + r = __register_chrdev(SOUND_MAJOR, s->unit_minor, 1, s->name, + &soundcore_fops); + if (r < 0) { + spin_lock(&sound_loader_lock); + __sound_remove_unit(list, s->unit_minor); + if (index < 0) { + low = s->unit_minor + SOUND_STEP; + goto retry; + } + spin_unlock(&sound_loader_lock); + r = -EBUSY; + goto fail; + } + } + + device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), + NULL, "%s", s->name+6); + return s->unit_minor; + +fail: + kfree(s); + return r; +} + +/* + * Remove a unit. Acquires locks as needed. The drivers MUST have + * completed the removal before their file operations become + * invalid. + */ + +static void sound_remove_unit(struct sound_unit **list, int unit) +{ + struct sound_unit *p; + + spin_lock(&sound_loader_lock); + p = __sound_remove_unit(list, unit); + spin_unlock(&sound_loader_lock); + if (p) { + if (!preclaim_oss) + __unregister_chrdev(SOUND_MAJOR, p->unit_minor, 1, + p->name); + device_destroy(sound_class, MKDEV(SOUND_MAJOR, p->unit_minor)); + kfree(p); + } +} + +/* + * Allocations + * + * 0 *16 Mixers + * 1 *8 Sequencers + * 2 *16 Midi + * 3 *16 DSP + * 4 *16 SunDSP + * 5 *16 DSP16 + * 6 -- sndstat (obsolete) + * 7 *16 unused + * 8 -- alternate sequencer (see above) + * 9 *16 raw synthesizer access + * 10 *16 unused + * 11 *16 unused + * 12 *16 unused + * 13 *16 unused + * 14 *16 unused + * 15 *16 unused + */ + +static struct sound_unit *chains[SOUND_STEP]; + +/** + * register_sound_special_device - register a special sound node + * @fops: File operations for the driver + * @unit: Unit number to allocate + * @dev: device pointer + * + * Allocate a special sound device by minor number from the sound + * subsystem. + * + * Return: The allocated number is returned on success. On failure, + * a negative error code is returned. + */ + +int register_sound_special_device(const struct file_operations *fops, int unit, + struct device *dev) +{ + const int chain = unit % SOUND_STEP; + int max_unit = 256; + const char *name; + char _name[16]; + + switch (chain) { + case 0: + name = "mixer"; + break; + case 1: + name = "sequencer"; + if (unit >= SOUND_STEP) + goto __unknown; + max_unit = unit + 1; + break; + case 2: + name = "midi"; + break; + case 3: + name = "dsp"; + break; + case 4: + name = "audio"; + break; + case 5: + name = "dspW"; + break; + case 8: + name = "sequencer2"; + if (unit >= SOUND_STEP) + goto __unknown; + max_unit = unit + 1; + break; + case 9: + name = "dmmidi"; + break; + case 10: + name = "dmfm"; + break; + case 12: + name = "adsp"; + break; + case 13: + name = "amidi"; + break; + case 14: + name = "admmidi"; + break; + default: + { + __unknown: + sprintf(_name, "unknown%d", chain); + if (unit >= SOUND_STEP) + strcat(_name, "-"); + name = _name; + } + break; + } + return sound_insert_unit(&chains[chain], fops, -1, unit, max_unit, + name, 0600, dev); +} + +EXPORT_SYMBOL(register_sound_special_device); + +int register_sound_special(const struct file_operations *fops, int unit) +{ + return register_sound_special_device(fops, unit, NULL); +} + +EXPORT_SYMBOL(register_sound_special); + +/** + * register_sound_mixer - register a mixer device + * @fops: File operations for the driver + * @dev: Unit number to allocate + * + * Allocate a mixer device. Unit is the number of the mixer requested. + * Pass -1 to request the next free mixer unit. + * + * Return: On success, the allocated number is returned. On failure, + * a negative error code is returned. + */ + +int register_sound_mixer(const struct file_operations *fops, int dev) +{ + return sound_insert_unit(&chains[0], fops, dev, 0, 128, + "mixer", 0600, NULL); +} + +EXPORT_SYMBOL(register_sound_mixer); + +/* + * DSP's are registered as a triple. Register only one and cheat + * in open - see below. + */ + +/** + * register_sound_dsp - register a DSP device + * @fops: File operations for the driver + * @dev: Unit number to allocate + * + * Allocate a DSP device. Unit is the number of the DSP requested. + * Pass -1 to request the next free DSP unit. + * + * This function allocates both the audio and dsp device entries together + * and will always allocate them as a matching pair - eg dsp3/audio3 + * + * Return: On success, the allocated number is returned. On failure, + * a negative error code is returned. + */ + +int register_sound_dsp(const struct file_operations *fops, int dev) +{ + return sound_insert_unit(&chains[3], fops, dev, 3, 131, + "dsp", 0600, NULL); +} + +EXPORT_SYMBOL(register_sound_dsp); + +/** + * unregister_sound_special - unregister a special sound device + * @unit: unit number to allocate + * + * Release a sound device that was allocated with + * register_sound_special(). The unit passed is the return value from + * the register function. + */ + + +void unregister_sound_special(int unit) +{ + sound_remove_unit(&chains[unit % SOUND_STEP], unit); +} + +EXPORT_SYMBOL(unregister_sound_special); + +/** + * unregister_sound_mixer - unregister a mixer + * @unit: unit number to allocate + * + * Release a sound device that was allocated with register_sound_mixer(). + * The unit passed is the return value from the register function. + */ + +void unregister_sound_mixer(int unit) +{ + sound_remove_unit(&chains[0], unit); +} + +EXPORT_SYMBOL(unregister_sound_mixer); + +/** + * unregister_sound_dsp - unregister a DSP device + * @unit: unit number to allocate + * + * Release a sound device that was allocated with register_sound_dsp(). + * The unit passed is the return value from the register function. + * + * Both of the allocated units are released together automatically. + */ + +void unregister_sound_dsp(int unit) +{ + sound_remove_unit(&chains[3], unit); +} + + +EXPORT_SYMBOL(unregister_sound_dsp); + +static struct sound_unit *__look_for_unit(int chain, int unit) +{ + struct sound_unit *s; + + s=chains[chain]; + while(s && s->unit_minor <= unit) + { + if(s->unit_minor==unit) + return s; + s=s->next; + } + return NULL; +} + +static int soundcore_open(struct inode *inode, struct file *file) +{ + int chain; + int unit = iminor(inode); + struct sound_unit *s; + const struct file_operations *new_fops = NULL; + + chain=unit&0x0F; + if(chain==4 || chain==5) /* dsp/audio/dsp16 */ + { + unit&=0xF0; + unit|=3; + chain=3; + } + + spin_lock(&sound_loader_lock); + s = __look_for_unit(chain, unit); + if (s) + new_fops = fops_get(s->unit_fops); + if (preclaim_oss && !new_fops) { + spin_unlock(&sound_loader_lock); + + /* + * Please, don't change this order or code. + * For ALSA slot means soundcard and OSS emulation code + * comes as add-on modules which aren't depend on + * ALSA toplevel modules for soundcards, thus we need + * load them at first. [Jaroslav Kysela ] + */ + request_module("sound-slot-%i", unit>>4); + request_module("sound-service-%i-%i", unit>>4, chain); + + /* + * sound-slot/service-* module aliases are scheduled + * for removal in favor of the standard char-major-* + * module aliases. For the time being, generate both + * the legacy and standard module aliases to ease + * transition. + */ + if (request_module("char-major-%d-%d", SOUND_MAJOR, unit) > 0) + request_module("char-major-%d", SOUND_MAJOR); + + spin_lock(&sound_loader_lock); + s = __look_for_unit(chain, unit); + if (s) + new_fops = fops_get(s->unit_fops); + } + spin_unlock(&sound_loader_lock); + if (new_fops) { + /* + * We rely upon the fact that we can't be unloaded while the + * subdriver is there. + */ + int err = 0; + replace_fops(file, new_fops); + + if (file->f_op->open) + err = file->f_op->open(inode,file); + + return err; + } + return -ENODEV; +} + +MODULE_ALIAS_CHARDEV_MAJOR(SOUND_MAJOR); + +static void cleanup_oss_soundcore(void) +{ + /* We have nothing to really do here - we know the lists must be + empty */ + unregister_chrdev(SOUND_MAJOR, "sound"); +} + +static int __init init_oss_soundcore(void) +{ + if (preclaim_oss && + register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops) < 0) { + printk(KERN_ERR "soundcore: sound device already in use.\n"); + return -EBUSY; + } + + return 0; +} + +#endif /* CONFIG_SOUND_OSS_CORE */ diff --git a/sound/sparc/Kconfig b/sound/sparc/Kconfig new file mode 100644 index 000000000..dfcd38647 --- /dev/null +++ b/sound/sparc/Kconfig @@ -0,0 +1,42 @@ +# ALSA Sparc drivers + +menuconfig SND_SPARC + bool "Sparc sound devices" + depends on SPARC + default y + help + Support for sound devices specific to Sun SPARC architectures. + +if SND_SPARC + +config SND_SUN_AMD7930 + tristate "Sun AMD7930" + depends on SBUS + select SND_PCM + help + Say Y here to include support for AMD7930 sound device on Sun. + + To compile this driver as a module, choose M here: the module + will be called snd-sun-amd7930. + +config SND_SUN_CS4231 + tristate "Sun CS4231" + select SND_PCM + select SND_TIMER + help + Say Y here to include support for CS4231 sound device on Sun. + + To compile this driver as a module, choose M here: the module + will be called snd-sun-cs4231. + +config SND_SUN_DBRI + tristate "Sun DBRI" + depends on SBUS + select SND_PCM + help + Say Y here to include support for DBRI sound device on Sun. + + To compile this driver as a module, choose M here: the module + will be called snd-sun-dbri. + +endif # SND_SPARC diff --git a/sound/sparc/Makefile b/sound/sparc/Makefile new file mode 100644 index 000000000..e1f596571 --- /dev/null +++ b/sound/sparc/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2002 by David S. Miller +# + +snd-sun-amd7930-objs := amd7930.o +snd-sun-cs4231-objs := cs4231.o +snd-sun-dbri-objs := dbri.o + +obj-$(CONFIG_SND_SUN_AMD7930) += snd-sun-amd7930.o +obj-$(CONFIG_SND_SUN_CS4231) += snd-sun-cs4231.o +obj-$(CONFIG_SND_SUN_DBRI) += snd-sun-dbri.o diff --git a/sound/sparc/amd7930.c b/sound/sparc/amd7930.c new file mode 100644 index 000000000..56f17410f --- /dev/null +++ b/sound/sparc/amd7930.c @@ -0,0 +1,1100 @@ +/* + * Driver for AMD7930 sound chips found on Sparcs. + * Copyright (C) 2002, 2008 David S. Miller + * + * Based entirely upon drivers/sbus/audio/amd7930.c which is: + * Copyright (C) 1996,1997 Thomas K. Dyas (tdyas@eden.rutgers.edu) + * + * --- Notes from Thomas's original driver --- + * This is the lowlevel driver for the AMD7930 audio chip found on all + * sun4c machines and some sun4m machines. + * + * The amd7930 is actually an ISDN chip which has a very simple + * integrated audio encoder/decoder. When Sun decided on what chip to + * use for audio, they had the brilliant idea of using the amd7930 and + * only connecting the audio encoder/decoder pins. + * + * Thanks to the AMD engineer who was able to get us the AMD79C30 + * databook which has all the programming information and gain tables. + * + * Advanced Micro Devices' Am79C30A is an ISDN/audio chip used in the + * SparcStation 1+. The chip provides microphone and speaker interfaces + * which provide mono-channel audio at 8K samples per second via either + * 8-bit A-law or 8-bit mu-law encoding. Also, the chip features an + * ISDN BRI Line Interface Unit (LIU), I.430 S/T physical interface, + * which performs basic D channel LAPD processing and provides raw + * B channel data. The digital audio channel, the two ISDN B channels, + * and two 64 Kbps channels to the microprocessor are all interconnected + * via a multiplexer. + * --- End of notes from Thoamas's original driver --- + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Sun AMD7930 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Sun AMD7930 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Sun AMD7930 soundcard."); +MODULE_AUTHOR("Thomas K. Dyas and David S. Miller"); +MODULE_DESCRIPTION("Sun AMD7930"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Sun,AMD7930}}"); + +/* Device register layout. */ + +/* Register interface presented to the CPU by the amd7930. */ +#define AMD7930_CR 0x00UL /* Command Register (W) */ +#define AMD7930_IR AMD7930_CR /* Interrupt Register (R) */ +#define AMD7930_DR 0x01UL /* Data Register (R/W) */ +#define AMD7930_DSR1 0x02UL /* D-channel Status Register 1 (R) */ +#define AMD7930_DER 0x03UL /* D-channel Error Register (R) */ +#define AMD7930_DCTB 0x04UL /* D-channel Transmit Buffer (W) */ +#define AMD7930_DCRB AMD7930_DCTB /* D-channel Receive Buffer (R) */ +#define AMD7930_BBTB 0x05UL /* Bb-channel Transmit Buffer (W) */ +#define AMD7930_BBRB AMD7930_BBTB /* Bb-channel Receive Buffer (R) */ +#define AMD7930_BCTB 0x06UL /* Bc-channel Transmit Buffer (W) */ +#define AMD7930_BCRB AMD7930_BCTB /* Bc-channel Receive Buffer (R) */ +#define AMD7930_DSR2 0x07UL /* D-channel Status Register 2 (R) */ + +/* Indirect registers in the Main Audio Processor. */ +struct amd7930_map { + __u16 x[8]; + __u16 r[8]; + __u16 gx; + __u16 gr; + __u16 ger; + __u16 stgr; + __u16 ftgr; + __u16 atgr; + __u8 mmr1; + __u8 mmr2; +}; + +/* After an amd7930 interrupt, reading the Interrupt Register (ir) + * clears the interrupt and returns a bitmask indicating which + * interrupt source(s) require service. + */ + +#define AMR_IR_DTTHRSH 0x01 /* D-channel xmit threshold */ +#define AMR_IR_DRTHRSH 0x02 /* D-channel recv threshold */ +#define AMR_IR_DSRI 0x04 /* D-channel packet status */ +#define AMR_IR_DERI 0x08 /* D-channel error */ +#define AMR_IR_BBUF 0x10 /* B-channel data xfer */ +#define AMR_IR_LSRI 0x20 /* LIU status */ +#define AMR_IR_DSR2I 0x40 /* D-channel buffer status */ +#define AMR_IR_MLTFRMI 0x80 /* multiframe or PP */ + +/* The amd7930 has "indirect registers" which are accessed by writing + * the register number into the Command Register and then reading or + * writing values from the Data Register as appropriate. We define the + * AMR_* macros to be the indirect register numbers and AM_* macros to + * be bits in whatever register is referred to. + */ + +/* Initialization */ +#define AMR_INIT 0x21 +#define AM_INIT_ACTIVE 0x01 +#define AM_INIT_DATAONLY 0x02 +#define AM_INIT_POWERDOWN 0x03 +#define AM_INIT_DISABLE_INTS 0x04 +#define AMR_INIT2 0x20 +#define AM_INIT2_ENABLE_POWERDOWN 0x20 +#define AM_INIT2_ENABLE_MULTIFRAME 0x10 + +/* Line Interface Unit */ +#define AMR_LIU_LSR 0xA1 +#define AM_LIU_LSR_STATE 0x07 +#define AM_LIU_LSR_F3 0x08 +#define AM_LIU_LSR_F7 0x10 +#define AM_LIU_LSR_F8 0x20 +#define AM_LIU_LSR_HSW 0x40 +#define AM_LIU_LSR_HSW_CHG 0x80 +#define AMR_LIU_LPR 0xA2 +#define AMR_LIU_LMR1 0xA3 +#define AM_LIU_LMR1_B1_ENABL 0x01 +#define AM_LIU_LMR1_B2_ENABL 0x02 +#define AM_LIU_LMR1_F_DISABL 0x04 +#define AM_LIU_LMR1_FA_DISABL 0x08 +#define AM_LIU_LMR1_REQ_ACTIV 0x10 +#define AM_LIU_LMR1_F8_F3 0x20 +#define AM_LIU_LMR1_LIU_ENABL 0x40 +#define AMR_LIU_LMR2 0xA4 +#define AM_LIU_LMR2_DECHO 0x01 +#define AM_LIU_LMR2_DLOOP 0x02 +#define AM_LIU_LMR2_DBACKOFF 0x04 +#define AM_LIU_LMR2_EN_F3_INT 0x08 +#define AM_LIU_LMR2_EN_F8_INT 0x10 +#define AM_LIU_LMR2_EN_HSW_INT 0x20 +#define AM_LIU_LMR2_EN_F7_INT 0x40 +#define AMR_LIU_2_4 0xA5 +#define AMR_LIU_MF 0xA6 +#define AMR_LIU_MFSB 0xA7 +#define AMR_LIU_MFQB 0xA8 + +/* Multiplexor */ +#define AMR_MUX_MCR1 0x41 +#define AMR_MUX_MCR2 0x42 +#define AMR_MUX_MCR3 0x43 +#define AM_MUX_CHANNEL_B1 0x01 +#define AM_MUX_CHANNEL_B2 0x02 +#define AM_MUX_CHANNEL_Ba 0x03 +#define AM_MUX_CHANNEL_Bb 0x04 +#define AM_MUX_CHANNEL_Bc 0x05 +#define AM_MUX_CHANNEL_Bd 0x06 +#define AM_MUX_CHANNEL_Be 0x07 +#define AM_MUX_CHANNEL_Bf 0x08 +#define AMR_MUX_MCR4 0x44 +#define AM_MUX_MCR4_ENABLE_INTS 0x08 +#define AM_MUX_MCR4_REVERSE_Bb 0x10 +#define AM_MUX_MCR4_REVERSE_Bc 0x20 +#define AMR_MUX_1_4 0x45 + +/* Main Audio Processor */ +#define AMR_MAP_X 0x61 +#define AMR_MAP_R 0x62 +#define AMR_MAP_GX 0x63 +#define AMR_MAP_GR 0x64 +#define AMR_MAP_GER 0x65 +#define AMR_MAP_STGR 0x66 +#define AMR_MAP_FTGR_1_2 0x67 +#define AMR_MAP_ATGR_1_2 0x68 +#define AMR_MAP_MMR1 0x69 +#define AM_MAP_MMR1_ALAW 0x01 +#define AM_MAP_MMR1_GX 0x02 +#define AM_MAP_MMR1_GR 0x04 +#define AM_MAP_MMR1_GER 0x08 +#define AM_MAP_MMR1_X 0x10 +#define AM_MAP_MMR1_R 0x20 +#define AM_MAP_MMR1_STG 0x40 +#define AM_MAP_MMR1_LOOPBACK 0x80 +#define AMR_MAP_MMR2 0x6A +#define AM_MAP_MMR2_AINB 0x01 +#define AM_MAP_MMR2_LS 0x02 +#define AM_MAP_MMR2_ENABLE_DTMF 0x04 +#define AM_MAP_MMR2_ENABLE_TONEGEN 0x08 +#define AM_MAP_MMR2_ENABLE_TONERING 0x10 +#define AM_MAP_MMR2_DISABLE_HIGHPASS 0x20 +#define AM_MAP_MMR2_DISABLE_AUTOZERO 0x40 +#define AMR_MAP_1_10 0x6B +#define AMR_MAP_MMR3 0x6C +#define AMR_MAP_STRA 0x6D +#define AMR_MAP_STRF 0x6E +#define AMR_MAP_PEAKX 0x70 +#define AMR_MAP_PEAKR 0x71 +#define AMR_MAP_15_16 0x72 + +/* Data Link Controller */ +#define AMR_DLC_FRAR_1_2_3 0x81 +#define AMR_DLC_SRAR_1_2_3 0x82 +#define AMR_DLC_TAR 0x83 +#define AMR_DLC_DRLR 0x84 +#define AMR_DLC_DTCR 0x85 +#define AMR_DLC_DMR1 0x86 +#define AMR_DLC_DMR1_DTTHRSH_INT 0x01 +#define AMR_DLC_DMR1_DRTHRSH_INT 0x02 +#define AMR_DLC_DMR1_TAR_ENABL 0x04 +#define AMR_DLC_DMR1_EORP_INT 0x08 +#define AMR_DLC_DMR1_EN_ADDR1 0x10 +#define AMR_DLC_DMR1_EN_ADDR2 0x20 +#define AMR_DLC_DMR1_EN_ADDR3 0x40 +#define AMR_DLC_DMR1_EN_ADDR4 0x80 +#define AMR_DLC_DMR1_EN_ADDRS 0xf0 +#define AMR_DLC_DMR2 0x87 +#define AMR_DLC_DMR2_RABRT_INT 0x01 +#define AMR_DLC_DMR2_RESID_INT 0x02 +#define AMR_DLC_DMR2_COLL_INT 0x04 +#define AMR_DLC_DMR2_FCS_INT 0x08 +#define AMR_DLC_DMR2_OVFL_INT 0x10 +#define AMR_DLC_DMR2_UNFL_INT 0x20 +#define AMR_DLC_DMR2_OVRN_INT 0x40 +#define AMR_DLC_DMR2_UNRN_INT 0x80 +#define AMR_DLC_1_7 0x88 +#define AMR_DLC_DRCR 0x89 +#define AMR_DLC_RNGR1 0x8A +#define AMR_DLC_RNGR2 0x8B +#define AMR_DLC_FRAR4 0x8C +#define AMR_DLC_SRAR4 0x8D +#define AMR_DLC_DMR3 0x8E +#define AMR_DLC_DMR3_VA_INT 0x01 +#define AMR_DLC_DMR3_EOTP_INT 0x02 +#define AMR_DLC_DMR3_LBRP_INT 0x04 +#define AMR_DLC_DMR3_RBA_INT 0x08 +#define AMR_DLC_DMR3_LBT_INT 0x10 +#define AMR_DLC_DMR3_TBE_INT 0x20 +#define AMR_DLC_DMR3_RPLOST_INT 0x40 +#define AMR_DLC_DMR3_KEEP_FCS 0x80 +#define AMR_DLC_DMR4 0x8F +#define AMR_DLC_DMR4_RCV_1 0x00 +#define AMR_DLC_DMR4_RCV_2 0x01 +#define AMR_DLC_DMR4_RCV_4 0x02 +#define AMR_DLC_DMR4_RCV_8 0x03 +#define AMR_DLC_DMR4_RCV_16 0x01 +#define AMR_DLC_DMR4_RCV_24 0x02 +#define AMR_DLC_DMR4_RCV_30 0x03 +#define AMR_DLC_DMR4_XMT_1 0x00 +#define AMR_DLC_DMR4_XMT_2 0x04 +#define AMR_DLC_DMR4_XMT_4 0x08 +#define AMR_DLC_DMR4_XMT_8 0x0c +#define AMR_DLC_DMR4_XMT_10 0x08 +#define AMR_DLC_DMR4_XMT_14 0x0c +#define AMR_DLC_DMR4_IDLE_MARK 0x00 +#define AMR_DLC_DMR4_IDLE_FLAG 0x10 +#define AMR_DLC_DMR4_ADDR_BOTH 0x00 +#define AMR_DLC_DMR4_ADDR_1ST 0x20 +#define AMR_DLC_DMR4_ADDR_2ND 0xa0 +#define AMR_DLC_DMR4_CR_ENABLE 0x40 +#define AMR_DLC_12_15 0x90 +#define AMR_DLC_ASR 0x91 +#define AMR_DLC_EFCR 0x92 +#define AMR_DLC_EFCR_EXTEND_FIFO 0x01 +#define AMR_DLC_EFCR_SEC_PKT_INT 0x02 + +#define AMR_DSR1_VADDR 0x01 +#define AMR_DSR1_EORP 0x02 +#define AMR_DSR1_PKT_IP 0x04 +#define AMR_DSR1_DECHO_ON 0x08 +#define AMR_DSR1_DLOOP_ON 0x10 +#define AMR_DSR1_DBACK_OFF 0x20 +#define AMR_DSR1_EOTP 0x40 +#define AMR_DSR1_CXMT_ABRT 0x80 + +#define AMR_DSR2_LBRP 0x01 +#define AMR_DSR2_RBA 0x02 +#define AMR_DSR2_RPLOST 0x04 +#define AMR_DSR2_LAST_BYTE 0x08 +#define AMR_DSR2_TBE 0x10 +#define AMR_DSR2_MARK_IDLE 0x20 +#define AMR_DSR2_FLAG_IDLE 0x40 +#define AMR_DSR2_SECOND_PKT 0x80 + +#define AMR_DER_RABRT 0x01 +#define AMR_DER_RFRAME 0x02 +#define AMR_DER_COLLISION 0x04 +#define AMR_DER_FCS 0x08 +#define AMR_DER_OVFL 0x10 +#define AMR_DER_UNFL 0x20 +#define AMR_DER_OVRN 0x40 +#define AMR_DER_UNRN 0x80 + +/* Peripheral Port */ +#define AMR_PP_PPCR1 0xC0 +#define AMR_PP_PPSR 0xC1 +#define AMR_PP_PPIER 0xC2 +#define AMR_PP_MTDR 0xC3 +#define AMR_PP_MRDR 0xC3 +#define AMR_PP_CITDR0 0xC4 +#define AMR_PP_CIRDR0 0xC4 +#define AMR_PP_CITDR1 0xC5 +#define AMR_PP_CIRDR1 0xC5 +#define AMR_PP_PPCR2 0xC8 +#define AMR_PP_PPCR3 0xC9 + +struct snd_amd7930 { + spinlock_t lock; + void __iomem *regs; + u32 flags; +#define AMD7930_FLAG_PLAYBACK 0x00000001 +#define AMD7930_FLAG_CAPTURE 0x00000002 + + struct amd7930_map map; + + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + /* Playback/Capture buffer state. */ + unsigned char *p_orig, *p_cur; + int p_left; + unsigned char *c_orig, *c_cur; + int c_left; + + int rgain; + int pgain; + int mgain; + + struct platform_device *op; + unsigned int irq; + struct snd_amd7930 *next; +}; + +static struct snd_amd7930 *amd7930_list; + +/* Idle the AMD7930 chip. The amd->lock is not held. */ +static __inline__ void amd7930_idle(struct snd_amd7930 *amd) +{ + unsigned long flags; + + spin_lock_irqsave(&amd->lock, flags); + sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR); + sbus_writeb(0, amd->regs + AMD7930_DR); + spin_unlock_irqrestore(&amd->lock, flags); +} + +/* Enable chip interrupts. The amd->lock is not held. */ +static __inline__ void amd7930_enable_ints(struct snd_amd7930 *amd) +{ + unsigned long flags; + + spin_lock_irqsave(&amd->lock, flags); + sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR); + sbus_writeb(AM_INIT_ACTIVE, amd->regs + AMD7930_DR); + spin_unlock_irqrestore(&amd->lock, flags); +} + +/* Disable chip interrupts. The amd->lock is not held. */ +static __inline__ void amd7930_disable_ints(struct snd_amd7930 *amd) +{ + unsigned long flags; + + spin_lock_irqsave(&amd->lock, flags); + sbus_writeb(AMR_INIT, amd->regs + AMD7930_CR); + sbus_writeb(AM_INIT_ACTIVE | AM_INIT_DISABLE_INTS, amd->regs + AMD7930_DR); + spin_unlock_irqrestore(&amd->lock, flags); +} + +/* Commit amd7930_map settings to the hardware. + * The amd->lock is held and local interrupts are disabled. + */ +static void __amd7930_write_map(struct snd_amd7930 *amd) +{ + struct amd7930_map *map = &amd->map; + + sbus_writeb(AMR_MAP_GX, amd->regs + AMD7930_CR); + sbus_writeb(((map->gx >> 0) & 0xff), amd->regs + AMD7930_DR); + sbus_writeb(((map->gx >> 8) & 0xff), amd->regs + AMD7930_DR); + + sbus_writeb(AMR_MAP_GR, amd->regs + AMD7930_CR); + sbus_writeb(((map->gr >> 0) & 0xff), amd->regs + AMD7930_DR); + sbus_writeb(((map->gr >> 8) & 0xff), amd->regs + AMD7930_DR); + + sbus_writeb(AMR_MAP_STGR, amd->regs + AMD7930_CR); + sbus_writeb(((map->stgr >> 0) & 0xff), amd->regs + AMD7930_DR); + sbus_writeb(((map->stgr >> 8) & 0xff), amd->regs + AMD7930_DR); + + sbus_writeb(AMR_MAP_GER, amd->regs + AMD7930_CR); + sbus_writeb(((map->ger >> 0) & 0xff), amd->regs + AMD7930_DR); + sbus_writeb(((map->ger >> 8) & 0xff), amd->regs + AMD7930_DR); + + sbus_writeb(AMR_MAP_MMR1, amd->regs + AMD7930_CR); + sbus_writeb(map->mmr1, amd->regs + AMD7930_DR); + + sbus_writeb(AMR_MAP_MMR2, amd->regs + AMD7930_CR); + sbus_writeb(map->mmr2, amd->regs + AMD7930_DR); +} + +/* gx, gr & stg gains. this table must contain 256 elements with + * the 0th being "infinity" (the magic value 9008). The remaining + * elements match sun's gain curve (but with higher resolution): + * -18 to 0dB in .16dB steps then 0 to 12dB in .08dB steps. + */ +static __const__ __u16 gx_coeff[256] = { + 0x9008, 0x8b7c, 0x8b51, 0x8b45, 0x8b42, 0x8b3b, 0x8b36, 0x8b33, + 0x8b32, 0x8b2a, 0x8b2b, 0x8b2c, 0x8b25, 0x8b23, 0x8b22, 0x8b22, + 0x9122, 0x8b1a, 0x8aa3, 0x8aa3, 0x8b1c, 0x8aa6, 0x912d, 0x912b, + 0x8aab, 0x8b12, 0x8aaa, 0x8ab2, 0x9132, 0x8ab4, 0x913c, 0x8abb, + 0x9142, 0x9144, 0x9151, 0x8ad5, 0x8aeb, 0x8a79, 0x8a5a, 0x8a4a, + 0x8b03, 0x91c2, 0x91bb, 0x8a3f, 0x8a33, 0x91b2, 0x9212, 0x9213, + 0x8a2c, 0x921d, 0x8a23, 0x921a, 0x9222, 0x9223, 0x922d, 0x9231, + 0x9234, 0x9242, 0x925b, 0x92dd, 0x92c1, 0x92b3, 0x92ab, 0x92a4, + 0x92a2, 0x932b, 0x9341, 0x93d3, 0x93b2, 0x93a2, 0x943c, 0x94b2, + 0x953a, 0x9653, 0x9782, 0x9e21, 0x9d23, 0x9cd2, 0x9c23, 0x9baa, + 0x9bde, 0x9b33, 0x9b22, 0x9b1d, 0x9ab2, 0xa142, 0xa1e5, 0x9a3b, + 0xa213, 0xa1a2, 0xa231, 0xa2eb, 0xa313, 0xa334, 0xa421, 0xa54b, + 0xada4, 0xac23, 0xab3b, 0xaaab, 0xaa5c, 0xb1a3, 0xb2ca, 0xb3bd, + 0xbe24, 0xbb2b, 0xba33, 0xc32b, 0xcb5a, 0xd2a2, 0xe31d, 0x0808, + 0x72ba, 0x62c2, 0x5c32, 0x52db, 0x513e, 0x4cce, 0x43b2, 0x4243, + 0x41b4, 0x3b12, 0x3bc3, 0x3df2, 0x34bd, 0x3334, 0x32c2, 0x3224, + 0x31aa, 0x2a7b, 0x2aaa, 0x2b23, 0x2bba, 0x2c42, 0x2e23, 0x25bb, + 0x242b, 0x240f, 0x231a, 0x22bb, 0x2241, 0x2223, 0x221f, 0x1a33, + 0x1a4a, 0x1acd, 0x2132, 0x1b1b, 0x1b2c, 0x1b62, 0x1c12, 0x1c32, + 0x1d1b, 0x1e71, 0x16b1, 0x1522, 0x1434, 0x1412, 0x1352, 0x1323, + 0x1315, 0x12bc, 0x127a, 0x1235, 0x1226, 0x11a2, 0x1216, 0x0a2a, + 0x11bc, 0x11d1, 0x1163, 0x0ac2, 0x0ab2, 0x0aab, 0x0b1b, 0x0b23, + 0x0b33, 0x0c0f, 0x0bb3, 0x0c1b, 0x0c3e, 0x0cb1, 0x0d4c, 0x0ec1, + 0x079a, 0x0614, 0x0521, 0x047c, 0x0422, 0x03b1, 0x03e3, 0x0333, + 0x0322, 0x031c, 0x02aa, 0x02ba, 0x02f2, 0x0242, 0x0232, 0x0227, + 0x0222, 0x021b, 0x01ad, 0x0212, 0x01b2, 0x01bb, 0x01cb, 0x01f6, + 0x0152, 0x013a, 0x0133, 0x0131, 0x012c, 0x0123, 0x0122, 0x00a2, + 0x011b, 0x011e, 0x0114, 0x00b1, 0x00aa, 0x00b3, 0x00bd, 0x00ba, + 0x00c5, 0x00d3, 0x00f3, 0x0062, 0x0051, 0x0042, 0x003b, 0x0033, + 0x0032, 0x002a, 0x002c, 0x0025, 0x0023, 0x0022, 0x001a, 0x0021, + 0x001b, 0x001b, 0x001d, 0x0015, 0x0013, 0x0013, 0x0012, 0x0012, + 0x000a, 0x000a, 0x0011, 0x0011, 0x000b, 0x000b, 0x000c, 0x000e, +}; + +static __const__ __u16 ger_coeff[] = { + 0x431f, /* 5. dB */ + 0x331f, /* 5.5 dB */ + 0x40dd, /* 6. dB */ + 0x11dd, /* 6.5 dB */ + 0x440f, /* 7. dB */ + 0x411f, /* 7.5 dB */ + 0x311f, /* 8. dB */ + 0x5520, /* 8.5 dB */ + 0x10dd, /* 9. dB */ + 0x4211, /* 9.5 dB */ + 0x410f, /* 10. dB */ + 0x111f, /* 10.5 dB */ + 0x600b, /* 11. dB */ + 0x00dd, /* 11.5 dB */ + 0x4210, /* 12. dB */ + 0x110f, /* 13. dB */ + 0x7200, /* 14. dB */ + 0x2110, /* 15. dB */ + 0x2200, /* 15.9 dB */ + 0x000b, /* 16.9 dB */ + 0x000f /* 18. dB */ +}; + +/* Update amd7930_map settings and program them into the hardware. + * The amd->lock is held and local interrupts are disabled. + */ +static void __amd7930_update_map(struct snd_amd7930 *amd) +{ + struct amd7930_map *map = &amd->map; + int level; + + map->gx = gx_coeff[amd->rgain]; + map->stgr = gx_coeff[amd->mgain]; + level = (amd->pgain * (256 + ARRAY_SIZE(ger_coeff))) >> 8; + if (level >= 256) { + map->ger = ger_coeff[level - 256]; + map->gr = gx_coeff[255]; + } else { + map->ger = ger_coeff[0]; + map->gr = gx_coeff[level]; + } + __amd7930_write_map(amd); +} + +static irqreturn_t snd_amd7930_interrupt(int irq, void *dev_id) +{ + struct snd_amd7930 *amd = dev_id; + unsigned int elapsed; + u8 ir; + + spin_lock(&amd->lock); + + elapsed = 0; + + ir = sbus_readb(amd->regs + AMD7930_IR); + if (ir & AMR_IR_BBUF) { + u8 byte; + + if (amd->flags & AMD7930_FLAG_PLAYBACK) { + if (amd->p_left > 0) { + byte = *(amd->p_cur++); + amd->p_left--; + sbus_writeb(byte, amd->regs + AMD7930_BBTB); + if (amd->p_left == 0) + elapsed |= AMD7930_FLAG_PLAYBACK; + } else + sbus_writeb(0, amd->regs + AMD7930_BBTB); + } else if (amd->flags & AMD7930_FLAG_CAPTURE) { + byte = sbus_readb(amd->regs + AMD7930_BBRB); + if (amd->c_left > 0) { + *(amd->c_cur++) = byte; + amd->c_left--; + if (amd->c_left == 0) + elapsed |= AMD7930_FLAG_CAPTURE; + } + } + } + spin_unlock(&amd->lock); + + if (elapsed & AMD7930_FLAG_PLAYBACK) + snd_pcm_period_elapsed(amd->playback_substream); + else + snd_pcm_period_elapsed(amd->capture_substream); + + return IRQ_HANDLED; +} + +static int snd_amd7930_trigger(struct snd_amd7930 *amd, unsigned int flag, int cmd) +{ + unsigned long flags; + int result = 0; + + spin_lock_irqsave(&amd->lock, flags); + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (!(amd->flags & flag)) { + amd->flags |= flag; + + /* Enable B channel interrupts. */ + sbus_writeb(AMR_MUX_MCR4, amd->regs + AMD7930_CR); + sbus_writeb(AM_MUX_MCR4_ENABLE_INTS, amd->regs + AMD7930_DR); + } + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + if (amd->flags & flag) { + amd->flags &= ~flag; + + /* Disable B channel interrupts. */ + sbus_writeb(AMR_MUX_MCR4, amd->regs + AMD7930_CR); + sbus_writeb(0, amd->regs + AMD7930_DR); + } + } else { + result = -EINVAL; + } + spin_unlock_irqrestore(&amd->lock, flags); + + return result; +} + +static int snd_amd7930_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + return snd_amd7930_trigger(amd, AMD7930_FLAG_PLAYBACK, cmd); +} + +static int snd_amd7930_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + return snd_amd7930_trigger(amd, AMD7930_FLAG_CAPTURE, cmd); +} + +static int snd_amd7930_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned long flags; + u8 new_mmr1; + + spin_lock_irqsave(&amd->lock, flags); + + amd->flags |= AMD7930_FLAG_PLAYBACK; + + /* Setup the pseudo-dma transfer pointers. */ + amd->p_orig = amd->p_cur = runtime->dma_area; + amd->p_left = size; + + /* Put the chip into the correct encoding format. */ + new_mmr1 = amd->map.mmr1; + if (runtime->format == SNDRV_PCM_FORMAT_A_LAW) + new_mmr1 |= AM_MAP_MMR1_ALAW; + else + new_mmr1 &= ~AM_MAP_MMR1_ALAW; + if (new_mmr1 != amd->map.mmr1) { + amd->map.mmr1 = new_mmr1; + __amd7930_update_map(amd); + } + + spin_unlock_irqrestore(&amd->lock, flags); + + return 0; +} + +static int snd_amd7930_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned long flags; + u8 new_mmr1; + + spin_lock_irqsave(&amd->lock, flags); + + amd->flags |= AMD7930_FLAG_CAPTURE; + + /* Setup the pseudo-dma transfer pointers. */ + amd->c_orig = amd->c_cur = runtime->dma_area; + amd->c_left = size; + + /* Put the chip into the correct encoding format. */ + new_mmr1 = amd->map.mmr1; + if (runtime->format == SNDRV_PCM_FORMAT_A_LAW) + new_mmr1 |= AM_MAP_MMR1_ALAW; + else + new_mmr1 &= ~AM_MAP_MMR1_ALAW; + if (new_mmr1 != amd->map.mmr1) { + amd->map.mmr1 = new_mmr1; + __amd7930_update_map(amd); + } + + spin_unlock_irqrestore(&amd->lock, flags); + + return 0; +} + +static snd_pcm_uframes_t snd_amd7930_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(amd->flags & AMD7930_FLAG_PLAYBACK)) + return 0; + ptr = amd->p_cur - amd->p_orig; + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_amd7930_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(amd->flags & AMD7930_FLAG_CAPTURE)) + return 0; + + ptr = amd->c_cur - amd->c_orig; + return bytes_to_frames(substream->runtime, ptr); +} + +/* Playback and capture have identical properties. */ +static const struct snd_pcm_hardware snd_amd7930_pcm_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_HALF_DUPLEX), + .formats = SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = (64*1024), + .period_bytes_min = 1, + .period_bytes_max = (64*1024), + .periods_min = 1, + .periods_max = 1024, +}; + +static int snd_amd7930_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + amd->playback_substream = substream; + runtime->hw = snd_amd7930_pcm_hw; + return 0; +} + +static int snd_amd7930_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + amd->capture_substream = substream; + runtime->hw = snd_amd7930_pcm_hw; + return 0; +} + +static int snd_amd7930_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + + amd->playback_substream = NULL; + return 0; +} + +static int snd_amd7930_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_amd7930 *amd = snd_pcm_substream_chip(substream); + + amd->capture_substream = NULL; + return 0; +} + +static int snd_amd7930_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_amd7930_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static const struct snd_pcm_ops snd_amd7930_playback_ops = { + .open = snd_amd7930_playback_open, + .close = snd_amd7930_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_amd7930_hw_params, + .hw_free = snd_amd7930_hw_free, + .prepare = snd_amd7930_playback_prepare, + .trigger = snd_amd7930_playback_trigger, + .pointer = snd_amd7930_playback_pointer, +}; + +static const struct snd_pcm_ops snd_amd7930_capture_ops = { + .open = snd_amd7930_capture_open, + .close = snd_amd7930_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_amd7930_hw_params, + .hw_free = snd_amd7930_hw_free, + .prepare = snd_amd7930_capture_prepare, + .trigger = snd_amd7930_capture_trigger, + .pointer = snd_amd7930_capture_pointer, +}; + +static int snd_amd7930_pcm(struct snd_amd7930 *amd) +{ + struct snd_pcm *pcm; + int err; + + if ((err = snd_pcm_new(amd->card, + /* ID */ "sun_amd7930", + /* device */ 0, + /* playback count */ 1, + /* capture count */ 1, &pcm)) < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_amd7930_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_amd7930_capture_ops); + + pcm->private_data = amd; + pcm->info_flags = 0; + strcpy(pcm->name, amd->card->shortname); + amd->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64*1024, 64*1024); + + return 0; +} + +#define VOLUME_MONITOR 0 +#define VOLUME_CAPTURE 1 +#define VOLUME_PLAYBACK 2 + +static int snd_amd7930_info_volume(struct snd_kcontrol *kctl, 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_amd7930_get_volume(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_amd7930 *amd = snd_kcontrol_chip(kctl); + int type = kctl->private_value; + int *swval; + + switch (type) { + case VOLUME_MONITOR: + swval = &amd->mgain; + break; + case VOLUME_CAPTURE: + swval = &amd->rgain; + break; + case VOLUME_PLAYBACK: + default: + swval = &amd->pgain; + break; + } + + ucontrol->value.integer.value[0] = *swval; + + return 0; +} + +static int snd_amd7930_put_volume(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_amd7930 *amd = snd_kcontrol_chip(kctl); + unsigned long flags; + int type = kctl->private_value; + int *swval, change; + + switch (type) { + case VOLUME_MONITOR: + swval = &amd->mgain; + break; + case VOLUME_CAPTURE: + swval = &amd->rgain; + break; + case VOLUME_PLAYBACK: + default: + swval = &amd->pgain; + break; + } + + spin_lock_irqsave(&amd->lock, flags); + + if (*swval != ucontrol->value.integer.value[0]) { + *swval = ucontrol->value.integer.value[0] & 0xff; + __amd7930_update_map(amd); + change = 1; + } else + change = 0; + + spin_unlock_irqrestore(&amd->lock, flags); + + return change; +} + +static struct snd_kcontrol_new amd7930_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Volume", + .index = 0, + .info = snd_amd7930_info_volume, + .get = snd_amd7930_get_volume, + .put = snd_amd7930_put_volume, + .private_value = VOLUME_MONITOR, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .index = 0, + .info = snd_amd7930_info_volume, + .get = snd_amd7930_get_volume, + .put = snd_amd7930_put_volume, + .private_value = VOLUME_CAPTURE, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Playback Volume", + .index = 0, + .info = snd_amd7930_info_volume, + .get = snd_amd7930_get_volume, + .put = snd_amd7930_put_volume, + .private_value = VOLUME_PLAYBACK, + }, +}; + +static int snd_amd7930_mixer(struct snd_amd7930 *amd) +{ + struct snd_card *card; + int idx, err; + + if (snd_BUG_ON(!amd || !amd->card)) + return -EINVAL; + + card = amd->card; + strcpy(card->mixername, card->shortname); + + for (idx = 0; idx < ARRAY_SIZE(amd7930_controls); idx++) { + if ((err = snd_ctl_add(card, + snd_ctl_new1(&amd7930_controls[idx], amd))) < 0) + return err; + } + + return 0; +} + +static int snd_amd7930_free(struct snd_amd7930 *amd) +{ + struct platform_device *op = amd->op; + + amd7930_idle(amd); + + if (amd->irq) + free_irq(amd->irq, amd); + + if (amd->regs) + of_iounmap(&op->resource[0], amd->regs, + resource_size(&op->resource[0])); + + kfree(amd); + + return 0; +} + +static int snd_amd7930_dev_free(struct snd_device *device) +{ + struct snd_amd7930 *amd = device->device_data; + + return snd_amd7930_free(amd); +} + +static struct snd_device_ops snd_amd7930_dev_ops = { + .dev_free = snd_amd7930_dev_free, +}; + +static int snd_amd7930_create(struct snd_card *card, + struct platform_device *op, + int irq, int dev, + struct snd_amd7930 **ramd) +{ + struct snd_amd7930 *amd; + unsigned long flags; + int err; + + *ramd = NULL; + amd = kzalloc(sizeof(*amd), GFP_KERNEL); + if (amd == NULL) + return -ENOMEM; + + spin_lock_init(&amd->lock); + amd->card = card; + amd->op = op; + + amd->regs = of_ioremap(&op->resource[0], 0, + resource_size(&op->resource[0]), "amd7930"); + if (!amd->regs) { + snd_printk(KERN_ERR + "amd7930-%d: Unable to map chip registers.\n", dev); + kfree(amd); + return -EIO; + } + + amd7930_idle(amd); + + if (request_irq(irq, snd_amd7930_interrupt, + IRQF_SHARED, "amd7930", amd)) { + snd_printk(KERN_ERR "amd7930-%d: Unable to grab IRQ %d\n", + dev, irq); + snd_amd7930_free(amd); + return -EBUSY; + } + amd->irq = irq; + + amd7930_enable_ints(amd); + + spin_lock_irqsave(&amd->lock, flags); + + amd->rgain = 128; + amd->pgain = 200; + amd->mgain = 0; + + memset(&amd->map, 0, sizeof(amd->map)); + amd->map.mmr1 = (AM_MAP_MMR1_GX | AM_MAP_MMR1_GER | + AM_MAP_MMR1_GR | AM_MAP_MMR1_STG); + amd->map.mmr2 = (AM_MAP_MMR2_LS | AM_MAP_MMR2_AINB); + + __amd7930_update_map(amd); + + /* Always MUX audio (Ba) to channel Bb. */ + sbus_writeb(AMR_MUX_MCR1, amd->regs + AMD7930_CR); + sbus_writeb(AM_MUX_CHANNEL_Ba | (AM_MUX_CHANNEL_Bb << 4), + amd->regs + AMD7930_DR); + + spin_unlock_irqrestore(&amd->lock, flags); + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, + amd, &snd_amd7930_dev_ops)) < 0) { + snd_amd7930_free(amd); + return err; + } + + *ramd = amd; + return 0; +} + +static int amd7930_sbus_probe(struct platform_device *op) +{ + struct resource *rp = &op->resource[0]; + static int dev_num; + struct snd_card *card; + struct snd_amd7930 *amd; + int err, irq; + + irq = op->archdata.irqs[0]; + + if (dev_num >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev_num]) { + dev_num++; + return -ENOENT; + } + + err = snd_card_new(&op->dev, index[dev_num], id[dev_num], + THIS_MODULE, 0, &card); + if (err < 0) + return err; + + strcpy(card->driver, "AMD7930"); + strcpy(card->shortname, "Sun AMD7930"); + sprintf(card->longname, "%s at 0x%02lx:0x%08Lx, irq %d", + card->shortname, + rp->flags & 0xffL, + (unsigned long long)rp->start, + irq); + + if ((err = snd_amd7930_create(card, op, + irq, dev_num, &amd)) < 0) + goto out_err; + + if ((err = snd_amd7930_pcm(amd)) < 0) + goto out_err; + + if ((err = snd_amd7930_mixer(amd)) < 0) + goto out_err; + + if ((err = snd_card_register(card)) < 0) + goto out_err; + + amd->next = amd7930_list; + amd7930_list = amd; + + dev_num++; + + return 0; + +out_err: + snd_card_free(card); + return err; +} + +static const struct of_device_id amd7930_match[] = { + { + .name = "audio", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, amd7930_match); + +static struct platform_driver amd7930_sbus_driver = { + .driver = { + .name = "audio", + .of_match_table = amd7930_match, + }, + .probe = amd7930_sbus_probe, +}; + +static int __init amd7930_init(void) +{ + return platform_driver_register(&amd7930_sbus_driver); +} + +static void __exit amd7930_exit(void) +{ + struct snd_amd7930 *p = amd7930_list; + + while (p != NULL) { + struct snd_amd7930 *next = p->next; + + snd_card_free(p->card); + + p = next; + } + + amd7930_list = NULL; + + platform_driver_unregister(&amd7930_sbus_driver); +} + +module_init(amd7930_init); +module_exit(amd7930_exit); diff --git a/sound/sparc/cs4231.c b/sound/sparc/cs4231.c new file mode 100644 index 000000000..079063d80 --- /dev/null +++ b/sound/sparc/cs4231.c @@ -0,0 +1,2116 @@ +/* + * Driver for CS4231 sound chips found on Sparcs. + * Copyright (C) 2002, 2008 David S. Miller + * + * Based entirely upon drivers/sbus/audio/cs4231.c which is: + * Copyright (C) 1996, 1997, 1998 Derrick J Brashear (shadow@andrew.cmu.edu) + * and also sound/isa/cs423x/cs4231_lib.c which is: + * Copyright (c) by Jaroslav Kysela + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SBUS +#define SBUS_SUPPORT +#endif + +#if defined(CONFIG_PCI) && defined(CONFIG_SPARC64) +#define EBUS_SUPPORT +#include +#include +#endif + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +/* Enable this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Sun CS4231 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Sun CS4231 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Sun CS4231 soundcard."); +MODULE_AUTHOR("Jaroslav Kysela, Derrick J. Brashear and David S. Miller"); +MODULE_DESCRIPTION("Sun CS4231"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Sun,CS4231}}"); + +#ifdef SBUS_SUPPORT +struct sbus_dma_info { + spinlock_t lock; /* DMA access lock */ + int dir; + void __iomem *regs; +}; +#endif + +struct snd_cs4231; +struct cs4231_dma_control { + void (*prepare)(struct cs4231_dma_control *dma_cont, + int dir); + void (*enable)(struct cs4231_dma_control *dma_cont, int on); + int (*request)(struct cs4231_dma_control *dma_cont, + dma_addr_t bus_addr, size_t len); + unsigned int (*address)(struct cs4231_dma_control *dma_cont); +#ifdef EBUS_SUPPORT + struct ebus_dma_info ebus_info; +#endif +#ifdef SBUS_SUPPORT + struct sbus_dma_info sbus_info; +#endif +}; + +struct snd_cs4231 { + spinlock_t lock; /* registers access lock */ + void __iomem *port; + + struct cs4231_dma_control p_dma; + struct cs4231_dma_control c_dma; + + u32 flags; +#define CS4231_FLAG_EBUS 0x00000001 +#define CS4231_FLAG_PLAYBACK 0x00000002 +#define CS4231_FLAG_CAPTURE 0x00000004 + + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + unsigned int p_periods_sent; + struct snd_pcm_substream *capture_substream; + unsigned int c_periods_sent; + struct snd_timer *timer; + + unsigned short mode; +#define CS4231_MODE_NONE 0x0000 +#define CS4231_MODE_PLAY 0x0001 +#define CS4231_MODE_RECORD 0x0002 +#define CS4231_MODE_TIMER 0x0004 +#define CS4231_MODE_OPEN (CS4231_MODE_PLAY | CS4231_MODE_RECORD | \ + CS4231_MODE_TIMER) + + unsigned char image[32]; /* registers image */ + int mce_bit; + int calibrate_mute; + struct mutex mce_mutex; /* mutex for mce register */ + struct mutex open_mutex; /* mutex for ALSA open/close */ + + struct platform_device *op; + unsigned int irq[2]; + unsigned int regs_size; + struct snd_cs4231 *next; +}; + +/* Eventually we can use sound/isa/cs423x/cs4231_lib.c directly, but for + * now.... -DaveM + */ + +/* IO ports */ +#include + +/* XXX offsets are different than PC ISA chips... */ +#define CS4231U(chip, x) ((chip)->port + ((c_d_c_CS4231##x) << 2)) + +/* SBUS DMA register defines. */ + +#define APCCSR 0x10UL /* APC DMA CSR */ +#define APCCVA 0x20UL /* APC Capture DMA Address */ +#define APCCC 0x24UL /* APC Capture Count */ +#define APCCNVA 0x28UL /* APC Capture DMA Next Address */ +#define APCCNC 0x2cUL /* APC Capture Next Count */ +#define APCPVA 0x30UL /* APC Play DMA Address */ +#define APCPC 0x34UL /* APC Play Count */ +#define APCPNVA 0x38UL /* APC Play DMA Next Address */ +#define APCPNC 0x3cUL /* APC Play Next Count */ + +/* Defines for SBUS DMA-routines */ + +#define APCVA 0x0UL /* APC DMA Address */ +#define APCC 0x4UL /* APC Count */ +#define APCNVA 0x8UL /* APC DMA Next Address */ +#define APCNC 0xcUL /* APC Next Count */ +#define APC_PLAY 0x30UL /* Play registers start at 0x30 */ +#define APC_RECORD 0x20UL /* Record registers start at 0x20 */ + +/* APCCSR bits */ + +#define APC_INT_PENDING 0x800000 /* Interrupt Pending */ +#define APC_PLAY_INT 0x400000 /* Playback interrupt */ +#define APC_CAPT_INT 0x200000 /* Capture interrupt */ +#define APC_GENL_INT 0x100000 /* General interrupt */ +#define APC_XINT_ENA 0x80000 /* General ext int. enable */ +#define APC_XINT_PLAY 0x40000 /* Playback ext intr */ +#define APC_XINT_CAPT 0x20000 /* Capture ext intr */ +#define APC_XINT_GENL 0x10000 /* Error ext intr */ +#define APC_XINT_EMPT 0x8000 /* Pipe empty interrupt (0 write to pva) */ +#define APC_XINT_PEMP 0x4000 /* Play pipe empty (pva and pnva not set) */ +#define APC_XINT_PNVA 0x2000 /* Playback NVA dirty */ +#define APC_XINT_PENA 0x1000 /* play pipe empty Int enable */ +#define APC_XINT_COVF 0x800 /* Cap data dropped on floor */ +#define APC_XINT_CNVA 0x400 /* Capture NVA dirty */ +#define APC_XINT_CEMP 0x200 /* Capture pipe empty (cva and cnva not set) */ +#define APC_XINT_CENA 0x100 /* Cap. pipe empty int enable */ +#define APC_PPAUSE 0x80 /* Pause the play DMA */ +#define APC_CPAUSE 0x40 /* Pause the capture DMA */ +#define APC_CDC_RESET 0x20 /* CODEC RESET */ +#define APC_PDMA_READY 0x08 /* Play DMA Go */ +#define APC_CDMA_READY 0x04 /* Capture DMA Go */ +#define APC_CHIP_RESET 0x01 /* Reset the chip */ + +/* EBUS DMA register offsets */ + +#define EBDMA_CSR 0x00UL /* Control/Status */ +#define EBDMA_ADDR 0x04UL /* DMA Address */ +#define EBDMA_COUNT 0x08UL /* DMA Count */ + +/* + * Some variables + */ + +static unsigned char freq_bits[14] = { + /* 5510 */ 0x00 | CS4231_XTAL2, + /* 6620 */ 0x0E | CS4231_XTAL2, + /* 8000 */ 0x00 | CS4231_XTAL1, + /* 9600 */ 0x0E | CS4231_XTAL1, + /* 11025 */ 0x02 | CS4231_XTAL2, + /* 16000 */ 0x02 | CS4231_XTAL1, + /* 18900 */ 0x04 | CS4231_XTAL2, + /* 22050 */ 0x06 | CS4231_XTAL2, + /* 27042 */ 0x04 | CS4231_XTAL1, + /* 32000 */ 0x06 | CS4231_XTAL1, + /* 33075 */ 0x0C | CS4231_XTAL2, + /* 37800 */ 0x08 | CS4231_XTAL2, + /* 44100 */ 0x0A | CS4231_XTAL2, + /* 48000 */ 0x0C | CS4231_XTAL1 +}; + +static const unsigned int rates[14] = { + 5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050, + 27042, 32000, 33075, 37800, 44100, 48000 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, +}; + +static int snd_cs4231_xrate(struct snd_pcm_runtime *runtime) +{ + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); +} + +static unsigned char snd_cs4231_original_image[32] = +{ + 0x00, /* 00/00 - lic */ + 0x00, /* 01/01 - ric */ + 0x9f, /* 02/02 - la1ic */ + 0x9f, /* 03/03 - ra1ic */ + 0x9f, /* 04/04 - la2ic */ + 0x9f, /* 05/05 - ra2ic */ + 0xbf, /* 06/06 - loc */ + 0xbf, /* 07/07 - roc */ + 0x20, /* 08/08 - pdfr */ + CS4231_AUTOCALIB, /* 09/09 - ic */ + 0x00, /* 0a/10 - pc */ + 0x00, /* 0b/11 - ti */ + CS4231_MODE2, /* 0c/12 - mi */ + 0x00, /* 0d/13 - lbc */ + 0x00, /* 0e/14 - pbru */ + 0x00, /* 0f/15 - pbrl */ + 0x80, /* 10/16 - afei */ + 0x01, /* 11/17 - afeii */ + 0x9f, /* 12/18 - llic */ + 0x9f, /* 13/19 - rlic */ + 0x00, /* 14/20 - tlb */ + 0x00, /* 15/21 - thb */ + 0x00, /* 16/22 - la3mic/reserved */ + 0x00, /* 17/23 - ra3mic/reserved */ + 0x00, /* 18/24 - afs */ + 0x00, /* 19/25 - lamoc/version */ + 0x00, /* 1a/26 - mioc */ + 0x00, /* 1b/27 - ramoc/reserved */ + 0x20, /* 1c/28 - cdfr */ + 0x00, /* 1d/29 - res4 */ + 0x00, /* 1e/30 - cbru */ + 0x00, /* 1f/31 - cbrl */ +}; + +static u8 __cs4231_readb(struct snd_cs4231 *cp, void __iomem *reg_addr) +{ + if (cp->flags & CS4231_FLAG_EBUS) + return readb(reg_addr); + else + return sbus_readb(reg_addr); +} + +static void __cs4231_writeb(struct snd_cs4231 *cp, u8 val, + void __iomem *reg_addr) +{ + if (cp->flags & CS4231_FLAG_EBUS) + return writeb(val, reg_addr); + else + return sbus_writeb(val, reg_addr); +} + +/* + * Basic I/O functions + */ + +static void snd_cs4231_ready(struct snd_cs4231 *chip) +{ + int timeout; + + for (timeout = 250; timeout > 0; timeout--) { + int val = __cs4231_readb(chip, CS4231U(chip, REGSEL)); + if ((val & CS4231_INIT) == 0) + break; + udelay(100); + } +} + +static void snd_cs4231_dout(struct snd_cs4231 *chip, unsigned char reg, + unsigned char value) +{ + snd_cs4231_ready(chip); +#ifdef CONFIG_SND_DEBUG + if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) + snd_printdd("out: auto calibration time out - reg = 0x%x, " + "value = 0x%x\n", + reg, value); +#endif + __cs4231_writeb(chip, chip->mce_bit | reg, CS4231U(chip, REGSEL)); + wmb(); + __cs4231_writeb(chip, value, CS4231U(chip, REG)); + mb(); +} + +static inline void snd_cs4231_outm(struct snd_cs4231 *chip, unsigned char reg, + unsigned char mask, unsigned char value) +{ + unsigned char tmp = (chip->image[reg] & mask) | value; + + chip->image[reg] = tmp; + if (!chip->calibrate_mute) + snd_cs4231_dout(chip, reg, tmp); +} + +static void snd_cs4231_out(struct snd_cs4231 *chip, unsigned char reg, + unsigned char value) +{ + snd_cs4231_dout(chip, reg, value); + chip->image[reg] = value; + mb(); +} + +static unsigned char snd_cs4231_in(struct snd_cs4231 *chip, unsigned char reg) +{ + snd_cs4231_ready(chip); +#ifdef CONFIG_SND_DEBUG + if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) + snd_printdd("in: auto calibration time out - reg = 0x%x\n", + reg); +#endif + __cs4231_writeb(chip, chip->mce_bit | reg, CS4231U(chip, REGSEL)); + mb(); + return __cs4231_readb(chip, CS4231U(chip, REG)); +} + +/* + * CS4231 detection / MCE routines + */ + +static void snd_cs4231_busy_wait(struct snd_cs4231 *chip) +{ + int timeout; + + /* looks like this sequence is proper for CS4231A chip (GUS MAX) */ + for (timeout = 5; timeout > 0; timeout--) + __cs4231_readb(chip, CS4231U(chip, REGSEL)); + + /* end of cleanup sequence */ + for (timeout = 500; timeout > 0; timeout--) { + int val = __cs4231_readb(chip, CS4231U(chip, REGSEL)); + if ((val & CS4231_INIT) == 0) + break; + msleep(1); + } +} + +static void snd_cs4231_mce_up(struct snd_cs4231 *chip) +{ + unsigned long flags; + int timeout; + + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_ready(chip); +#ifdef CONFIG_SND_DEBUG + if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) + snd_printdd("mce_up - auto calibration time out (0)\n"); +#endif + chip->mce_bit |= CS4231_MCE; + timeout = __cs4231_readb(chip, CS4231U(chip, REGSEL)); + if (timeout == 0x80) + snd_printdd("mce_up [%p]: serious init problem - " + "codec still busy\n", + chip->port); + if (!(timeout & CS4231_MCE)) + __cs4231_writeb(chip, chip->mce_bit | (timeout & 0x1f), + CS4231U(chip, REGSEL)); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static void snd_cs4231_mce_down(struct snd_cs4231 *chip) +{ + unsigned long flags, timeout; + int reg; + + snd_cs4231_busy_wait(chip); + spin_lock_irqsave(&chip->lock, flags); +#ifdef CONFIG_SND_DEBUG + if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) + snd_printdd("mce_down [%p] - auto calibration time out (0)\n", + CS4231U(chip, REGSEL)); +#endif + chip->mce_bit &= ~CS4231_MCE; + reg = __cs4231_readb(chip, CS4231U(chip, REGSEL)); + __cs4231_writeb(chip, chip->mce_bit | (reg & 0x1f), + CS4231U(chip, REGSEL)); + if (reg == 0x80) + snd_printdd("mce_down [%p]: serious init problem " + "- codec still busy\n", chip->port); + if ((reg & CS4231_MCE) == 0) { + spin_unlock_irqrestore(&chip->lock, flags); + return; + } + + /* + * Wait for auto-calibration (AC) process to finish, i.e. ACI to go low. + */ + timeout = jiffies + msecs_to_jiffies(250); + do { + spin_unlock_irqrestore(&chip->lock, flags); + msleep(1); + spin_lock_irqsave(&chip->lock, flags); + reg = snd_cs4231_in(chip, CS4231_TEST_INIT); + reg &= CS4231_CALIB_IN_PROGRESS; + } while (reg && time_before(jiffies, timeout)); + spin_unlock_irqrestore(&chip->lock, flags); + + if (reg) + snd_printk(KERN_ERR + "mce_down - auto calibration time out (2)\n"); +} + +static void snd_cs4231_advance_dma(struct cs4231_dma_control *dma_cont, + struct snd_pcm_substream *substream, + unsigned int *periods_sent) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + while (1) { + unsigned int period_size = snd_pcm_lib_period_bytes(substream); + unsigned int offset = period_size * (*periods_sent); + + if (WARN_ON(period_size >= (1 << 24))) + return; + + if (dma_cont->request(dma_cont, + runtime->dma_addr + offset, period_size)) + return; + (*periods_sent) = ((*periods_sent) + 1) % runtime->periods; + } +} + +static void cs4231_dma_trigger(struct snd_pcm_substream *substream, + unsigned int what, int on) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + struct cs4231_dma_control *dma_cont; + + if (what & CS4231_PLAYBACK_ENABLE) { + dma_cont = &chip->p_dma; + if (on) { + dma_cont->prepare(dma_cont, 0); + dma_cont->enable(dma_cont, 1); + snd_cs4231_advance_dma(dma_cont, + chip->playback_substream, + &chip->p_periods_sent); + } else { + dma_cont->enable(dma_cont, 0); + } + } + if (what & CS4231_RECORD_ENABLE) { + dma_cont = &chip->c_dma; + if (on) { + dma_cont->prepare(dma_cont, 1); + dma_cont->enable(dma_cont, 1); + snd_cs4231_advance_dma(dma_cont, + chip->capture_substream, + &chip->c_periods_sent); + } else { + dma_cont->enable(dma_cont, 0); + } + } +} + +static int snd_cs4231_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + int result = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + { + unsigned int what = 0; + struct snd_pcm_substream *s; + unsigned long flags; + + snd_pcm_group_for_each_entry(s, substream) { + if (s == chip->playback_substream) { + what |= CS4231_PLAYBACK_ENABLE; + snd_pcm_trigger_done(s, substream); + } else if (s == chip->capture_substream) { + what |= CS4231_RECORD_ENABLE; + snd_pcm_trigger_done(s, substream); + } + } + + spin_lock_irqsave(&chip->lock, flags); + if (cmd == SNDRV_PCM_TRIGGER_START) { + cs4231_dma_trigger(substream, what, 1); + chip->image[CS4231_IFACE_CTRL] |= what; + } else { + cs4231_dma_trigger(substream, what, 0); + chip->image[CS4231_IFACE_CTRL] &= ~what; + } + snd_cs4231_out(chip, CS4231_IFACE_CTRL, + chip->image[CS4231_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->lock, flags); + break; + } + default: + result = -EINVAL; + break; + } + + return result; +} + +/* + * CODEC I/O + */ + +static unsigned char snd_cs4231_get_rate(unsigned int rate) +{ + int i; + + for (i = 0; i < 14; i++) + if (rate == rates[i]) + return freq_bits[i]; + + return freq_bits[13]; +} + +static unsigned char snd_cs4231_get_format(struct snd_cs4231 *chip, int format, + int channels) +{ + unsigned char rformat; + + rformat = CS4231_LINEAR_8; + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + rformat = CS4231_ULAW_8; + break; + case SNDRV_PCM_FORMAT_A_LAW: + rformat = CS4231_ALAW_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + rformat = CS4231_LINEAR_16; + break; + case SNDRV_PCM_FORMAT_S16_BE: + rformat = CS4231_LINEAR_16_BIG; + break; + case SNDRV_PCM_FORMAT_IMA_ADPCM: + rformat = CS4231_ADPCM_16; + break; + } + if (channels > 1) + rformat |= CS4231_STEREO; + return rformat; +} + +static void snd_cs4231_calibrate_mute(struct snd_cs4231 *chip, int mute) +{ + unsigned long flags; + + mute = mute ? 1 : 0; + spin_lock_irqsave(&chip->lock, flags); + if (chip->calibrate_mute == mute) { + spin_unlock_irqrestore(&chip->lock, flags); + return; + } + if (!mute) { + snd_cs4231_dout(chip, CS4231_LEFT_INPUT, + chip->image[CS4231_LEFT_INPUT]); + snd_cs4231_dout(chip, CS4231_RIGHT_INPUT, + chip->image[CS4231_RIGHT_INPUT]); + snd_cs4231_dout(chip, CS4231_LOOPBACK, + chip->image[CS4231_LOOPBACK]); + } + snd_cs4231_dout(chip, CS4231_AUX1_LEFT_INPUT, + mute ? 0x80 : chip->image[CS4231_AUX1_LEFT_INPUT]); + snd_cs4231_dout(chip, CS4231_AUX1_RIGHT_INPUT, + mute ? 0x80 : chip->image[CS4231_AUX1_RIGHT_INPUT]); + snd_cs4231_dout(chip, CS4231_AUX2_LEFT_INPUT, + mute ? 0x80 : chip->image[CS4231_AUX2_LEFT_INPUT]); + snd_cs4231_dout(chip, CS4231_AUX2_RIGHT_INPUT, + mute ? 0x80 : chip->image[CS4231_AUX2_RIGHT_INPUT]); + snd_cs4231_dout(chip, CS4231_LEFT_OUTPUT, + mute ? 0x80 : chip->image[CS4231_LEFT_OUTPUT]); + snd_cs4231_dout(chip, CS4231_RIGHT_OUTPUT, + mute ? 0x80 : chip->image[CS4231_RIGHT_OUTPUT]); + snd_cs4231_dout(chip, CS4231_LEFT_LINE_IN, + mute ? 0x80 : chip->image[CS4231_LEFT_LINE_IN]); + snd_cs4231_dout(chip, CS4231_RIGHT_LINE_IN, + mute ? 0x80 : chip->image[CS4231_RIGHT_LINE_IN]); + snd_cs4231_dout(chip, CS4231_MONO_CTRL, + mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]); + chip->calibrate_mute = mute; + spin_unlock_irqrestore(&chip->lock, flags); +} + +static void snd_cs4231_playback_format(struct snd_cs4231 *chip, + struct snd_pcm_hw_params *params, + unsigned char pdfr) +{ + unsigned long flags; + + mutex_lock(&chip->mce_mutex); + snd_cs4231_calibrate_mute(chip, 1); + + snd_cs4231_mce_up(chip); + + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, + (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) ? + (pdfr & 0xf0) | (chip->image[CS4231_REC_FORMAT] & 0x0f) : + pdfr); + spin_unlock_irqrestore(&chip->lock, flags); + + snd_cs4231_mce_down(chip); + + snd_cs4231_calibrate_mute(chip, 0); + mutex_unlock(&chip->mce_mutex); +} + +static void snd_cs4231_capture_format(struct snd_cs4231 *chip, + struct snd_pcm_hw_params *params, + unsigned char cdfr) +{ + unsigned long flags; + + mutex_lock(&chip->mce_mutex); + snd_cs4231_calibrate_mute(chip, 1); + + snd_cs4231_mce_up(chip); + + spin_lock_irqsave(&chip->lock, flags); + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) { + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, + ((chip->image[CS4231_PLAYBK_FORMAT]) & 0xf0) | + (cdfr & 0x0f)); + spin_unlock_irqrestore(&chip->lock, flags); + snd_cs4231_mce_down(chip); + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->lock, flags); + } + snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr); + spin_unlock_irqrestore(&chip->lock, flags); + + snd_cs4231_mce_down(chip); + + snd_cs4231_calibrate_mute(chip, 0); + mutex_unlock(&chip->mce_mutex); +} + +/* + * Timer interface + */ + +static unsigned long snd_cs4231_timer_resolution(struct snd_timer *timer) +{ + struct snd_cs4231 *chip = snd_timer_chip(timer); + + return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920; +} + +static int snd_cs4231_timer_start(struct snd_timer *timer) +{ + unsigned long flags; + unsigned int ticks; + struct snd_cs4231 *chip = snd_timer_chip(timer); + + spin_lock_irqsave(&chip->lock, flags); + ticks = timer->sticks; + if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 || + (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] || + (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) { + snd_cs4231_out(chip, CS4231_TIMER_HIGH, + chip->image[CS4231_TIMER_HIGH] = + (unsigned char) (ticks >> 8)); + snd_cs4231_out(chip, CS4231_TIMER_LOW, + chip->image[CS4231_TIMER_LOW] = + (unsigned char) ticks); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1] | + CS4231_TIMER_ENABLE); + } + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int snd_cs4231_timer_stop(struct snd_timer *timer) +{ + unsigned long flags; + struct snd_cs4231 *chip = snd_timer_chip(timer); + + spin_lock_irqsave(&chip->lock, flags); + chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE; + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1]); + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static void snd_cs4231_init(struct snd_cs4231 *chip) +{ + unsigned long flags; + + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printdd("init: (1)\n"); +#endif + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->lock, flags); + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | + CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | + CS4231_RECORD_PIO | + CS4231_CALIB_MODE); + chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB; + snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printdd("init: (2)\n"); +#endif + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, + chip->image[CS4231_ALT_FEATURE_1]); + spin_unlock_irqrestore(&chip->lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printdd("init: (3) - afei = 0x%x\n", + chip->image[CS4231_ALT_FEATURE_1]); +#endif + + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_2, + chip->image[CS4231_ALT_FEATURE_2]); + spin_unlock_irqrestore(&chip->lock, flags); + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, + chip->image[CS4231_PLAYBK_FORMAT]); + spin_unlock_irqrestore(&chip->lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printdd("init: (4)\n"); +#endif + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT]); + spin_unlock_irqrestore(&chip->lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printdd("init: (5)\n"); +#endif +} + +static int snd_cs4231_open(struct snd_cs4231 *chip, unsigned int mode) +{ + unsigned long flags; + + mutex_lock(&chip->open_mutex); + if ((chip->mode & mode)) { + mutex_unlock(&chip->open_mutex); + return -EAGAIN; + } + if (chip->mode & CS4231_MODE_OPEN) { + chip->mode |= mode; + mutex_unlock(&chip->open_mutex); + return 0; + } + /* ok. now enable and ack CODEC IRQ */ + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ | + CS4231_RECORD_IRQ | + CS4231_TIMER_IRQ); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + __cs4231_writeb(chip, 0, CS4231U(chip, STATUS)); /* clear IRQ */ + __cs4231_writeb(chip, 0, CS4231U(chip, STATUS)); /* clear IRQ */ + + snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ | + CS4231_RECORD_IRQ | + CS4231_TIMER_IRQ); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + + spin_unlock_irqrestore(&chip->lock, flags); + + chip->mode = mode; + mutex_unlock(&chip->open_mutex); + return 0; +} + +static void snd_cs4231_close(struct snd_cs4231 *chip, unsigned int mode) +{ + unsigned long flags; + + mutex_lock(&chip->open_mutex); + chip->mode &= ~mode; + if (chip->mode & CS4231_MODE_OPEN) { + mutex_unlock(&chip->open_mutex); + return; + } + snd_cs4231_calibrate_mute(chip, 1); + + /* disable IRQ */ + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + __cs4231_writeb(chip, 0, CS4231U(chip, STATUS)); /* clear IRQ */ + __cs4231_writeb(chip, 0, CS4231U(chip, STATUS)); /* clear IRQ */ + + /* now disable record & playback */ + + if (chip->image[CS4231_IFACE_CTRL] & + (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) { + spin_unlock_irqrestore(&chip->lock, flags); + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->lock, flags); + chip->image[CS4231_IFACE_CTRL] &= + ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | CS4231_RECORD_PIO); + snd_cs4231_out(chip, CS4231_IFACE_CTRL, + chip->image[CS4231_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->lock, flags); + snd_cs4231_mce_down(chip); + spin_lock_irqsave(&chip->lock, flags); + } + + /* clear IRQ again */ + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + __cs4231_writeb(chip, 0, CS4231U(chip, STATUS)); /* clear IRQ */ + __cs4231_writeb(chip, 0, CS4231U(chip, STATUS)); /* clear IRQ */ + spin_unlock_irqrestore(&chip->lock, flags); + + snd_cs4231_calibrate_mute(chip, 0); + + chip->mode = 0; + mutex_unlock(&chip->open_mutex); +} + +/* + * timer open/close + */ + +static int snd_cs4231_timer_open(struct snd_timer *timer) +{ + struct snd_cs4231 *chip = snd_timer_chip(timer); + snd_cs4231_open(chip, CS4231_MODE_TIMER); + return 0; +} + +static int snd_cs4231_timer_close(struct snd_timer *timer) +{ + struct snd_cs4231 *chip = snd_timer_chip(timer); + snd_cs4231_close(chip, CS4231_MODE_TIMER); + return 0; +} + +static struct snd_timer_hardware snd_cs4231_timer_table = { + .flags = SNDRV_TIMER_HW_AUTO, + .resolution = 9945, + .ticks = 65535, + .open = snd_cs4231_timer_open, + .close = snd_cs4231_timer_close, + .c_resolution = snd_cs4231_timer_resolution, + .start = snd_cs4231_timer_start, + .stop = snd_cs4231_timer_stop, +}; + +/* + * ok.. exported functions.. + */ + +static int snd_cs4231_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + unsigned char new_pdfr; + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + new_pdfr = snd_cs4231_get_format(chip, params_format(hw_params), + params_channels(hw_params)) | + snd_cs4231_get_rate(params_rate(hw_params)); + snd_cs4231_playback_format(chip, hw_params, new_pdfr); + + return 0; +} + +static int snd_cs4231_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&chip->lock, flags); + + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | + CS4231_PLAYBACK_PIO); + + if (WARN_ON(runtime->period_size > 0xffff + 1)) { + ret = -EINVAL; + goto out; + } + + chip->p_periods_sent = 0; + +out: + spin_unlock_irqrestore(&chip->lock, flags); + + return ret; +} + +static int snd_cs4231_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + unsigned char new_cdfr; + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + new_cdfr = snd_cs4231_get_format(chip, params_format(hw_params), + params_channels(hw_params)) | + snd_cs4231_get_rate(params_rate(hw_params)); + snd_cs4231_capture_format(chip, hw_params, new_cdfr); + + return 0; +} + +static int snd_cs4231_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | + CS4231_RECORD_PIO); + + + chip->c_periods_sent = 0; + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static void snd_cs4231_overrange(struct snd_cs4231 *chip) +{ + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&chip->lock, flags); + res = snd_cs4231_in(chip, CS4231_TEST_INIT); + spin_unlock_irqrestore(&chip->lock, flags); + + /* detect overrange only above 0dB; may be user selectable? */ + if (res & (0x08 | 0x02)) + chip->capture_substream->runtime->overrange++; +} + +static void snd_cs4231_play_callback(struct snd_cs4231 *chip) +{ + if (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE) { + snd_pcm_period_elapsed(chip->playback_substream); + snd_cs4231_advance_dma(&chip->p_dma, chip->playback_substream, + &chip->p_periods_sent); + } +} + +static void snd_cs4231_capture_callback(struct snd_cs4231 *chip) +{ + if (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) { + snd_pcm_period_elapsed(chip->capture_substream); + snd_cs4231_advance_dma(&chip->c_dma, chip->capture_substream, + &chip->c_periods_sent); + } +} + +static snd_pcm_uframes_t snd_cs4231_playback_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + struct cs4231_dma_control *dma_cont = &chip->p_dma; + size_t ptr; + + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) + return 0; + ptr = dma_cont->address(dma_cont); + if (ptr != 0) + ptr -= substream->runtime->dma_addr; + + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_cs4231_capture_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + struct cs4231_dma_control *dma_cont = &chip->c_dma; + size_t ptr; + + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)) + return 0; + ptr = dma_cont->address(dma_cont); + if (ptr != 0) + ptr -= substream->runtime->dma_addr; + + return bytes_to_frames(substream->runtime, ptr); +} + +static int snd_cs4231_probe(struct snd_cs4231 *chip) +{ + unsigned long flags; + int i; + int id = 0; + int vers = 0; + unsigned char *ptr; + + for (i = 0; i < 50; i++) { + mb(); + if (__cs4231_readb(chip, CS4231U(chip, REGSEL)) & CS4231_INIT) + msleep(2); + else { + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_out(chip, CS4231_MISC_INFO, CS4231_MODE2); + id = snd_cs4231_in(chip, CS4231_MISC_INFO) & 0x0f; + vers = snd_cs4231_in(chip, CS4231_VERSION); + spin_unlock_irqrestore(&chip->lock, flags); + if (id == 0x0a) + break; /* this is valid value */ + } + } + snd_printdd("cs4231: port = %p, id = 0x%x\n", chip->port, id); + if (id != 0x0a) + return -ENODEV; /* no valid device found */ + + spin_lock_irqsave(&chip->lock, flags); + + /* clear any pendings IRQ */ + __cs4231_readb(chip, CS4231U(chip, STATUS)); + __cs4231_writeb(chip, 0, CS4231U(chip, STATUS)); + mb(); + + spin_unlock_irqrestore(&chip->lock, flags); + + chip->image[CS4231_MISC_INFO] = CS4231_MODE2; + chip->image[CS4231_IFACE_CTRL] = + chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA; + chip->image[CS4231_ALT_FEATURE_1] = 0x80; + chip->image[CS4231_ALT_FEATURE_2] = 0x01; + if (vers & 0x20) + chip->image[CS4231_ALT_FEATURE_2] |= 0x02; + + ptr = (unsigned char *) &chip->image; + + snd_cs4231_mce_down(chip); + + spin_lock_irqsave(&chip->lock, flags); + + for (i = 0; i < 32; i++) /* ok.. fill all CS4231 registers */ + snd_cs4231_out(chip, i, *ptr++); + + spin_unlock_irqrestore(&chip->lock, flags); + + snd_cs4231_mce_up(chip); + + snd_cs4231_mce_down(chip); + + mdelay(2); + + return 0; /* all things are ok.. */ +} + +static const struct snd_pcm_hardware snd_cs4231_playback = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_KNOT | + SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 32 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 1, + .periods_max = 1024, +}; + +static const struct snd_pcm_hardware snd_cs4231_capture = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_KNOT | + SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 32 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 32 * 1024, + .periods_min = 1, + .periods_max = 1024, +}; + +static int snd_cs4231_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + runtime->hw = snd_cs4231_playback; + + err = snd_cs4231_open(chip, CS4231_MODE_PLAY); + if (err < 0) + return err; + chip->playback_substream = substream; + chip->p_periods_sent = 0; + snd_pcm_set_sync(substream); + snd_cs4231_xrate(runtime); + + return 0; +} + +static int snd_cs4231_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + runtime->hw = snd_cs4231_capture; + + err = snd_cs4231_open(chip, CS4231_MODE_RECORD); + if (err < 0) + return err; + chip->capture_substream = substream; + chip->c_periods_sent = 0; + snd_pcm_set_sync(substream); + snd_cs4231_xrate(runtime); + + return 0; +} + +static int snd_cs4231_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + + snd_cs4231_close(chip, CS4231_MODE_PLAY); + chip->playback_substream = NULL; + + return 0; +} + +static int snd_cs4231_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_cs4231 *chip = snd_pcm_substream_chip(substream); + + snd_cs4231_close(chip, CS4231_MODE_RECORD); + chip->capture_substream = NULL; + + return 0; +} + +/* XXX We can do some power-management, in particular on EBUS using + * XXX the audio AUXIO register... + */ + +static const struct snd_pcm_ops snd_cs4231_playback_ops = { + .open = snd_cs4231_playback_open, + .close = snd_cs4231_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cs4231_playback_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .prepare = snd_cs4231_playback_prepare, + .trigger = snd_cs4231_trigger, + .pointer = snd_cs4231_playback_pointer, +}; + +static const struct snd_pcm_ops snd_cs4231_capture_ops = { + .open = snd_cs4231_capture_open, + .close = snd_cs4231_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cs4231_capture_hw_params, + .hw_free = snd_pcm_lib_free_pages, + .prepare = snd_cs4231_capture_prepare, + .trigger = snd_cs4231_trigger, + .pointer = snd_cs4231_capture_pointer, +}; + +static int snd_cs4231_pcm(struct snd_card *card) +{ + struct snd_cs4231 *chip = card->private_data; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(card, "CS4231", 0, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_cs4231_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_cs4231_capture_ops); + + /* global setup */ + pcm->private_data = chip; + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + strcpy(pcm->name, "CS4231"); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + &chip->op->dev, + 64 * 1024, 128 * 1024); + + chip->pcm = pcm; + + return 0; +} + +static int snd_cs4231_timer(struct snd_card *card) +{ + struct snd_cs4231 *chip = card->private_data; + struct snd_timer *timer; + struct snd_timer_id tid; + int err; + + /* Timer initialization */ + tid.dev_class = SNDRV_TIMER_CLASS_CARD; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = card->number; + tid.device = 0; + tid.subdevice = 0; + err = snd_timer_new(card, "CS4231", &tid, &timer); + if (err < 0) + return err; + strcpy(timer->name, "CS4231"); + timer->private_data = chip; + timer->hw = snd_cs4231_timer_table; + chip->timer = timer; + + return 0; +} + +/* + * MIXER part + */ + +static int snd_cs4231_info_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[4] = { + "Line", "CD", "Mic", "Mix" + }; + + return snd_ctl_enum_info(uinfo, 2, 4, texts); +} + +static int snd_cs4231_get_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + ucontrol->value.enumerated.item[0] = + (chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6; + ucontrol->value.enumerated.item[1] = + (chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6; + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int snd_cs4231_put_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned short left, right; + int change; + + if (ucontrol->value.enumerated.item[0] > 3 || + ucontrol->value.enumerated.item[1] > 3) + return -EINVAL; + left = ucontrol->value.enumerated.item[0] << 6; + right = ucontrol->value.enumerated.item[1] << 6; + + spin_lock_irqsave(&chip->lock, flags); + + left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left; + right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right; + change = left != chip->image[CS4231_LEFT_INPUT] || + right != chip->image[CS4231_RIGHT_INPUT]; + snd_cs4231_out(chip, CS4231_LEFT_INPUT, left); + snd_cs4231_out(chip, CS4231_RIGHT_INPUT, right); + + spin_unlock_irqrestore(&chip->lock, flags); + + return change; +} + +static int snd_cs4231_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_cs4231_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->lock, flags); + + ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask; + + spin_unlock_irqrestore(&chip->lock, flags); + + if (invert) + ucontrol->value.integer.value[0] = + (mask - ucontrol->value.integer.value[0]); + + return 0; +} + +static int snd_cs4231_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + + spin_lock_irqsave(&chip->lock, flags); + + val = (chip->image[reg] & ~(mask << shift)) | val; + change = val != chip->image[reg]; + snd_cs4231_out(chip, reg, val); + + spin_unlock_irqrestore(&chip->lock, flags); + + return change; +} + +static int snd_cs4231_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_cs4231_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->lock, flags); + + ucontrol->value.integer.value[0] = + (chip->image[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = + (chip->image[right_reg] >> shift_right) & mask; + + spin_unlock_irqrestore(&chip->lock, flags); + + if (invert) { + ucontrol->value.integer.value[0] = + (mask - ucontrol->value.integer.value[0]); + ucontrol->value.integer.value[1] = + (mask - ucontrol->value.integer.value[1]); + } + + return 0; +} + +static int snd_cs4231_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_cs4231 *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + + spin_lock_irqsave(&chip->lock, flags); + + val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1; + val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2; + change = val1 != chip->image[left_reg]; + change |= val2 != chip->image[right_reg]; + snd_cs4231_out(chip, left_reg, val1); + snd_cs4231_out(chip, right_reg, val2); + + spin_unlock_irqrestore(&chip->lock, flags); + + return change; +} + +#define CS4231_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), .index = (xindex), \ + .info = snd_cs4231_info_single, \ + .get = snd_cs4231_get_single, .put = snd_cs4231_put_single, \ + .private_value = (reg) | ((shift) << 8) | ((mask) << 16) | ((invert) << 24) } + +#define CS4231_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_cs4231_info_double, \ + .get = snd_cs4231_get_double, .put = snd_cs4231_put_double, \ + .private_value = (left_reg) | ((right_reg) << 8) | ((shift_left) << 16) | \ + ((shift_right) << 19) | ((mask) << 24) | ((invert) << 22) } + +static struct snd_kcontrol_new snd_cs4231_controls[] = { +CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, + CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT, + CS4231_RIGHT_OUTPUT, 0, 0, 63, 1), +CS4231_DOUBLE("Line Playback Switch", 0, CS4231_LEFT_LINE_IN, + CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +CS4231_DOUBLE("Line Playback Volume", 0, CS4231_LEFT_LINE_IN, + CS4231_RIGHT_LINE_IN, 0, 0, 31, 1), +CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, + CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Aux Playback Volume", 0, CS4231_AUX1_LEFT_INPUT, + CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1), +CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT, + CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Aux Playback Volume", 1, CS4231_AUX2_LEFT_INPUT, + CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1), +CS4231_SINGLE("Mono Playback Switch", 0, CS4231_MONO_CTRL, 7, 1, 1), +CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1), +CS4231_SINGLE("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, 6, 1, 1), +CS4231_SINGLE("Mono Output Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0), +CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0, + 15, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_cs4231_info_mux, + .get = snd_cs4231_get_mux, + .put = snd_cs4231_put_mux, +}, +CS4231_DOUBLE("Mic Boost", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, + 1, 0), +CS4231_SINGLE("Loopback Capture Switch", 0, CS4231_LOOPBACK, 0, 1, 0), +CS4231_SINGLE("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1), +/* SPARC specific uses of XCTL{0,1} general purpose outputs. */ +CS4231_SINGLE("Line Out Switch", 0, CS4231_PIN_CTRL, 6, 1, 1), +CS4231_SINGLE("Headphone Out Switch", 0, CS4231_PIN_CTRL, 7, 1, 1) +}; + +static int snd_cs4231_mixer(struct snd_card *card) +{ + struct snd_cs4231 *chip = card->private_data; + int err, idx; + + if (snd_BUG_ON(!chip || !chip->pcm)) + return -EINVAL; + + strcpy(card->mixername, chip->pcm->name); + + for (idx = 0; idx < ARRAY_SIZE(snd_cs4231_controls); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_cs4231_controls[idx], chip)); + if (err < 0) + return err; + } + return 0; +} + +static int dev; + +static int cs4231_attach_begin(struct platform_device *op, + struct snd_card **rcard) +{ + struct snd_card *card; + struct snd_cs4231 *chip; + int err; + + *rcard = NULL; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_cs4231), &card); + if (err < 0) + return err; + + strcpy(card->driver, "CS4231"); + strcpy(card->shortname, "Sun CS4231"); + + chip = card->private_data; + chip->card = card; + + *rcard = card; + return 0; +} + +static int cs4231_attach_finish(struct snd_card *card) +{ + struct snd_cs4231 *chip = card->private_data; + int err; + + err = snd_cs4231_pcm(card); + if (err < 0) + goto out_err; + + err = snd_cs4231_mixer(card); + if (err < 0) + goto out_err; + + err = snd_cs4231_timer(card); + if (err < 0) + goto out_err; + + err = snd_card_register(card); + if (err < 0) + goto out_err; + + dev_set_drvdata(&chip->op->dev, chip); + + dev++; + return 0; + +out_err: + snd_card_free(card); + return err; +} + +#ifdef SBUS_SUPPORT + +static irqreturn_t snd_cs4231_sbus_interrupt(int irq, void *dev_id) +{ + unsigned long flags; + unsigned char status; + u32 csr; + struct snd_cs4231 *chip = dev_id; + + /*This is IRQ is not raised by the cs4231*/ + if (!(__cs4231_readb(chip, CS4231U(chip, STATUS)) & CS4231_GLOBALIRQ)) + return IRQ_NONE; + + /* ACK the APC interrupt. */ + csr = sbus_readl(chip->port + APCCSR); + + sbus_writel(csr, chip->port + APCCSR); + + if ((csr & APC_PDMA_READY) && + (csr & APC_PLAY_INT) && + (csr & APC_XINT_PNVA) && + !(csr & APC_XINT_EMPT)) + snd_cs4231_play_callback(chip); + + if ((csr & APC_CDMA_READY) && + (csr & APC_CAPT_INT) && + (csr & APC_XINT_CNVA) && + !(csr & APC_XINT_EMPT)) + snd_cs4231_capture_callback(chip); + + status = snd_cs4231_in(chip, CS4231_IRQ_STATUS); + + if (status & CS4231_TIMER_IRQ) { + if (chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + } + + if ((status & CS4231_RECORD_IRQ) && (csr & APC_CDMA_READY)) + snd_cs4231_overrange(chip); + + /* ACK the CS4231 interrupt. */ + spin_lock_irqsave(&chip->lock, flags); + snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0); + spin_unlock_irqrestore(&chip->lock, flags); + + return IRQ_HANDLED; +} + +/* + * SBUS DMA routines + */ + +static int sbus_dma_request(struct cs4231_dma_control *dma_cont, + dma_addr_t bus_addr, size_t len) +{ + unsigned long flags; + u32 test, csr; + int err; + struct sbus_dma_info *base = &dma_cont->sbus_info; + + if (len >= (1 << 24)) + return -EINVAL; + spin_lock_irqsave(&base->lock, flags); + csr = sbus_readl(base->regs + APCCSR); + err = -EINVAL; + test = APC_CDMA_READY; + if (base->dir == APC_PLAY) + test = APC_PDMA_READY; + if (!(csr & test)) + goto out; + err = -EBUSY; + test = APC_XINT_CNVA; + if (base->dir == APC_PLAY) + test = APC_XINT_PNVA; + if (!(csr & test)) + goto out; + err = 0; + sbus_writel(bus_addr, base->regs + base->dir + APCNVA); + sbus_writel(len, base->regs + base->dir + APCNC); +out: + spin_unlock_irqrestore(&base->lock, flags); + return err; +} + +static void sbus_dma_prepare(struct cs4231_dma_control *dma_cont, int d) +{ + unsigned long flags; + u32 csr, test; + struct sbus_dma_info *base = &dma_cont->sbus_info; + + spin_lock_irqsave(&base->lock, flags); + csr = sbus_readl(base->regs + APCCSR); + test = APC_GENL_INT | APC_PLAY_INT | APC_XINT_ENA | + APC_XINT_PLAY | APC_XINT_PEMP | APC_XINT_GENL | + APC_XINT_PENA; + if (base->dir == APC_RECORD) + test = APC_GENL_INT | APC_CAPT_INT | APC_XINT_ENA | + APC_XINT_CAPT | APC_XINT_CEMP | APC_XINT_GENL; + csr |= test; + sbus_writel(csr, base->regs + APCCSR); + spin_unlock_irqrestore(&base->lock, flags); +} + +static void sbus_dma_enable(struct cs4231_dma_control *dma_cont, int on) +{ + unsigned long flags; + u32 csr, shift; + struct sbus_dma_info *base = &dma_cont->sbus_info; + + spin_lock_irqsave(&base->lock, flags); + if (!on) { + sbus_writel(0, base->regs + base->dir + APCNC); + sbus_writel(0, base->regs + base->dir + APCNVA); + if (base->dir == APC_PLAY) { + sbus_writel(0, base->regs + base->dir + APCC); + sbus_writel(0, base->regs + base->dir + APCVA); + } + + udelay(1200); + } + csr = sbus_readl(base->regs + APCCSR); + shift = 0; + if (base->dir == APC_PLAY) + shift = 1; + if (on) + csr &= ~(APC_CPAUSE << shift); + else + csr |= (APC_CPAUSE << shift); + sbus_writel(csr, base->regs + APCCSR); + if (on) + csr |= (APC_CDMA_READY << shift); + else + csr &= ~(APC_CDMA_READY << shift); + sbus_writel(csr, base->regs + APCCSR); + + spin_unlock_irqrestore(&base->lock, flags); +} + +static unsigned int sbus_dma_addr(struct cs4231_dma_control *dma_cont) +{ + struct sbus_dma_info *base = &dma_cont->sbus_info; + + return sbus_readl(base->regs + base->dir + APCVA); +} + +/* + * Init and exit routines + */ + +static int snd_cs4231_sbus_free(struct snd_cs4231 *chip) +{ + struct platform_device *op = chip->op; + + if (chip->irq[0]) + free_irq(chip->irq[0], chip); + + if (chip->port) + of_iounmap(&op->resource[0], chip->port, chip->regs_size); + + return 0; +} + +static int snd_cs4231_sbus_dev_free(struct snd_device *device) +{ + struct snd_cs4231 *cp = device->device_data; + + return snd_cs4231_sbus_free(cp); +} + +static struct snd_device_ops snd_cs4231_sbus_dev_ops = { + .dev_free = snd_cs4231_sbus_dev_free, +}; + +static int snd_cs4231_sbus_create(struct snd_card *card, + struct platform_device *op, + int dev) +{ + struct snd_cs4231 *chip = card->private_data; + int err; + + spin_lock_init(&chip->lock); + spin_lock_init(&chip->c_dma.sbus_info.lock); + spin_lock_init(&chip->p_dma.sbus_info.lock); + mutex_init(&chip->mce_mutex); + mutex_init(&chip->open_mutex); + chip->op = op; + chip->regs_size = resource_size(&op->resource[0]); + memcpy(&chip->image, &snd_cs4231_original_image, + sizeof(snd_cs4231_original_image)); + + chip->port = of_ioremap(&op->resource[0], 0, + chip->regs_size, "cs4231"); + if (!chip->port) { + snd_printdd("cs4231-%d: Unable to map chip registers.\n", dev); + return -EIO; + } + + chip->c_dma.sbus_info.regs = chip->port; + chip->p_dma.sbus_info.regs = chip->port; + chip->c_dma.sbus_info.dir = APC_RECORD; + chip->p_dma.sbus_info.dir = APC_PLAY; + + chip->p_dma.prepare = sbus_dma_prepare; + chip->p_dma.enable = sbus_dma_enable; + chip->p_dma.request = sbus_dma_request; + chip->p_dma.address = sbus_dma_addr; + + chip->c_dma.prepare = sbus_dma_prepare; + chip->c_dma.enable = sbus_dma_enable; + chip->c_dma.request = sbus_dma_request; + chip->c_dma.address = sbus_dma_addr; + + if (request_irq(op->archdata.irqs[0], snd_cs4231_sbus_interrupt, + IRQF_SHARED, "cs4231", chip)) { + snd_printdd("cs4231-%d: Unable to grab SBUS IRQ %d\n", + dev, op->archdata.irqs[0]); + snd_cs4231_sbus_free(chip); + return -EBUSY; + } + chip->irq[0] = op->archdata.irqs[0]; + + if (snd_cs4231_probe(chip) < 0) { + snd_cs4231_sbus_free(chip); + return -ENODEV; + } + snd_cs4231_init(chip); + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, + chip, &snd_cs4231_sbus_dev_ops)) < 0) { + snd_cs4231_sbus_free(chip); + return err; + } + + return 0; +} + +static int cs4231_sbus_probe(struct platform_device *op) +{ + struct resource *rp = &op->resource[0]; + struct snd_card *card; + int err; + + err = cs4231_attach_begin(op, &card); + if (err) + return err; + + sprintf(card->longname, "%s at 0x%02lx:0x%016Lx, irq %d", + card->shortname, + rp->flags & 0xffL, + (unsigned long long)rp->start, + op->archdata.irqs[0]); + + err = snd_cs4231_sbus_create(card, op, dev); + if (err < 0) { + snd_card_free(card); + return err; + } + + return cs4231_attach_finish(card); +} +#endif + +#ifdef EBUS_SUPPORT + +static void snd_cs4231_ebus_play_callback(struct ebus_dma_info *p, int event, + void *cookie) +{ + struct snd_cs4231 *chip = cookie; + + snd_cs4231_play_callback(chip); +} + +static void snd_cs4231_ebus_capture_callback(struct ebus_dma_info *p, + int event, void *cookie) +{ + struct snd_cs4231 *chip = cookie; + + snd_cs4231_capture_callback(chip); +} + +/* + * EBUS DMA wrappers + */ + +static int _ebus_dma_request(struct cs4231_dma_control *dma_cont, + dma_addr_t bus_addr, size_t len) +{ + return ebus_dma_request(&dma_cont->ebus_info, bus_addr, len); +} + +static void _ebus_dma_enable(struct cs4231_dma_control *dma_cont, int on) +{ + ebus_dma_enable(&dma_cont->ebus_info, on); +} + +static void _ebus_dma_prepare(struct cs4231_dma_control *dma_cont, int dir) +{ + ebus_dma_prepare(&dma_cont->ebus_info, dir); +} + +static unsigned int _ebus_dma_addr(struct cs4231_dma_control *dma_cont) +{ + return ebus_dma_addr(&dma_cont->ebus_info); +} + +/* + * Init and exit routines + */ + +static int snd_cs4231_ebus_free(struct snd_cs4231 *chip) +{ + struct platform_device *op = chip->op; + + if (chip->c_dma.ebus_info.regs) { + ebus_dma_unregister(&chip->c_dma.ebus_info); + of_iounmap(&op->resource[2], chip->c_dma.ebus_info.regs, 0x10); + } + if (chip->p_dma.ebus_info.regs) { + ebus_dma_unregister(&chip->p_dma.ebus_info); + of_iounmap(&op->resource[1], chip->p_dma.ebus_info.regs, 0x10); + } + + if (chip->port) + of_iounmap(&op->resource[0], chip->port, 0x10); + + return 0; +} + +static int snd_cs4231_ebus_dev_free(struct snd_device *device) +{ + struct snd_cs4231 *cp = device->device_data; + + return snd_cs4231_ebus_free(cp); +} + +static struct snd_device_ops snd_cs4231_ebus_dev_ops = { + .dev_free = snd_cs4231_ebus_dev_free, +}; + +static int snd_cs4231_ebus_create(struct snd_card *card, + struct platform_device *op, + int dev) +{ + struct snd_cs4231 *chip = card->private_data; + int err; + + spin_lock_init(&chip->lock); + spin_lock_init(&chip->c_dma.ebus_info.lock); + spin_lock_init(&chip->p_dma.ebus_info.lock); + mutex_init(&chip->mce_mutex); + mutex_init(&chip->open_mutex); + chip->flags |= CS4231_FLAG_EBUS; + chip->op = op; + memcpy(&chip->image, &snd_cs4231_original_image, + sizeof(snd_cs4231_original_image)); + strcpy(chip->c_dma.ebus_info.name, "cs4231(capture)"); + chip->c_dma.ebus_info.flags = EBUS_DMA_FLAG_USE_EBDMA_HANDLER; + chip->c_dma.ebus_info.callback = snd_cs4231_ebus_capture_callback; + chip->c_dma.ebus_info.client_cookie = chip; + chip->c_dma.ebus_info.irq = op->archdata.irqs[0]; + strcpy(chip->p_dma.ebus_info.name, "cs4231(play)"); + chip->p_dma.ebus_info.flags = EBUS_DMA_FLAG_USE_EBDMA_HANDLER; + chip->p_dma.ebus_info.callback = snd_cs4231_ebus_play_callback; + chip->p_dma.ebus_info.client_cookie = chip; + chip->p_dma.ebus_info.irq = op->archdata.irqs[1]; + + chip->p_dma.prepare = _ebus_dma_prepare; + chip->p_dma.enable = _ebus_dma_enable; + chip->p_dma.request = _ebus_dma_request; + chip->p_dma.address = _ebus_dma_addr; + + chip->c_dma.prepare = _ebus_dma_prepare; + chip->c_dma.enable = _ebus_dma_enable; + chip->c_dma.request = _ebus_dma_request; + chip->c_dma.address = _ebus_dma_addr; + + chip->port = of_ioremap(&op->resource[0], 0, 0x10, "cs4231"); + chip->p_dma.ebus_info.regs = + of_ioremap(&op->resource[1], 0, 0x10, "cs4231_pdma"); + chip->c_dma.ebus_info.regs = + of_ioremap(&op->resource[2], 0, 0x10, "cs4231_cdma"); + if (!chip->port || !chip->p_dma.ebus_info.regs || + !chip->c_dma.ebus_info.regs) { + snd_cs4231_ebus_free(chip); + snd_printdd("cs4231-%d: Unable to map chip registers.\n", dev); + return -EIO; + } + + if (ebus_dma_register(&chip->c_dma.ebus_info)) { + snd_cs4231_ebus_free(chip); + snd_printdd("cs4231-%d: Unable to register EBUS capture DMA\n", + dev); + return -EBUSY; + } + if (ebus_dma_irq_enable(&chip->c_dma.ebus_info, 1)) { + snd_cs4231_ebus_free(chip); + snd_printdd("cs4231-%d: Unable to enable EBUS capture IRQ\n", + dev); + return -EBUSY; + } + + if (ebus_dma_register(&chip->p_dma.ebus_info)) { + snd_cs4231_ebus_free(chip); + snd_printdd("cs4231-%d: Unable to register EBUS play DMA\n", + dev); + return -EBUSY; + } + if (ebus_dma_irq_enable(&chip->p_dma.ebus_info, 1)) { + snd_cs4231_ebus_free(chip); + snd_printdd("cs4231-%d: Unable to enable EBUS play IRQ\n", dev); + return -EBUSY; + } + + if (snd_cs4231_probe(chip) < 0) { + snd_cs4231_ebus_free(chip); + return -ENODEV; + } + snd_cs4231_init(chip); + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, + chip, &snd_cs4231_ebus_dev_ops)) < 0) { + snd_cs4231_ebus_free(chip); + return err; + } + + return 0; +} + +static int cs4231_ebus_probe(struct platform_device *op) +{ + struct snd_card *card; + int err; + + err = cs4231_attach_begin(op, &card); + if (err) + return err; + + sprintf(card->longname, "%s at 0x%llx, irq %d", + card->shortname, + op->resource[0].start, + op->archdata.irqs[0]); + + err = snd_cs4231_ebus_create(card, op, dev); + if (err < 0) { + snd_card_free(card); + return err; + } + + return cs4231_attach_finish(card); +} +#endif + +static int cs4231_probe(struct platform_device *op) +{ +#ifdef EBUS_SUPPORT + if (!strcmp(op->dev.of_node->parent->name, "ebus")) + return cs4231_ebus_probe(op); +#endif +#ifdef SBUS_SUPPORT + if (!strcmp(op->dev.of_node->parent->name, "sbus") || + !strcmp(op->dev.of_node->parent->name, "sbi")) + return cs4231_sbus_probe(op); +#endif + return -ENODEV; +} + +static int cs4231_remove(struct platform_device *op) +{ + struct snd_cs4231 *chip = dev_get_drvdata(&op->dev); + + snd_card_free(chip->card); + + return 0; +} + +static const struct of_device_id cs4231_match[] = { + { + .name = "SUNW,CS4231", + }, + { + .name = "audio", + .compatible = "SUNW,CS4231", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cs4231_match); + +static struct platform_driver cs4231_driver = { + .driver = { + .name = "audio", + .of_match_table = cs4231_match, + }, + .probe = cs4231_probe, + .remove = cs4231_remove, +}; + +module_platform_driver(cs4231_driver); diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c new file mode 100644 index 000000000..7609eceba --- /dev/null +++ b/sound/sparc/dbri.c @@ -0,0 +1,2702 @@ +/* + * Driver for DBRI sound chip found on Sparcs. + * Copyright (C) 2004, 2005 Martin Habets (mhabets@users.sourceforge.net) + * + * Converted to ring buffered version by Krzysztof Helt (krzysztof.h1@wp.pl) + * + * Based entirely upon drivers/sbus/audio/dbri.c which is: + * Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de) + * Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org) + * + * This is the low level driver for the DBRI & MMCODEC duo used for ISDN & AUDIO + * on Sun SPARCStation 10, 20, LX and Voyager models. + * + * - DBRI: AT&T T5900FX Dual Basic Rates ISDN Interface. It is a 32 channel + * data time multiplexer with ISDN support (aka T7259) + * Interfaces: SBus,ISDN NT & TE, CHI, 4 bits parallel. + * CHI: (spelled ki) Concentration Highway Interface (AT&T or Intel bus ?). + * Documentation: + * - "STP 4000SBus Dual Basic Rate ISDN (DBRI) Transceiver" from + * Sparc Technology Business (courtesy of Sun Support) + * - Data sheet of the T7903, a newer but very similar ISA bus equivalent + * available from the Lucent (formerly AT&T microelectronics) home + * page. + * - http://www.freesoft.org/Linux/DBRI/ + * - MMCODEC: Crystal Semiconductor CS4215 16 bit Multimedia Audio Codec + * Interfaces: CHI, Audio In & Out, 2 bits parallel + * Documentation: from the Crystal Semiconductor home page. + * + * The DBRI is a 32 pipe machine, each pipe can transfer some bits between + * memory and a serial device (long pipes, no. 0-15) or between two serial + * devices (short pipes, no. 16-31), or simply send a fixed data to a serial + * device (short pipes). + * A timeslot defines the bit-offset and no. of bits read from a serial device. + * The timeslots are linked to 6 circular lists, one for each direction for + * each serial device (NT,TE,CHI). A timeslot is associated to 1 or 2 pipes + * (the second one is a monitor/tee pipe, valid only for serial input). + * + * The mmcodec is connected via the CHI bus and needs the data & some + * parameters (volume, output selection) time multiplexed in 8 byte + * chunks. It also has a control mode, which serves for audio format setting. + * + * Looking at the CS4215 data sheet it is easy to set up 2 or 4 codecs on + * the same CHI bus, so I thought perhaps it is possible to use the on-board + * & the speakerbox codec simultaneously, giving 2 (not very independent :-) + * audio devices. But the SUN HW group decided against it, at least on my + * LX the speakerbox connector has at least 1 pin missing and 1 wrongly + * connected. + * + * I've tried to stick to the following function naming conventions: + * snd_* ALSA stuff + * cs4215_* CS4215 codec specific stuff + * dbri_* DBRI high-level stuff + * other DBRI low-level stuff + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +MODULE_AUTHOR("Rudolf Koenig, Brent Baccala and Martin Habets"); +MODULE_DESCRIPTION("Sun DBRI"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Sun,DBRI}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +/* Enable this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Sun DBRI soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Sun DBRI soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Sun DBRI soundcard."); + +#undef DBRI_DEBUG + +#define D_INT (1<<0) +#define D_GEN (1<<1) +#define D_CMD (1<<2) +#define D_MM (1<<3) +#define D_USR (1<<4) +#define D_DESC (1<<5) + +static int dbri_debug; +module_param(dbri_debug, int, 0644); +MODULE_PARM_DESC(dbri_debug, "Debug value for Sun DBRI soundcard."); + +#ifdef DBRI_DEBUG +static char *cmds[] = { + "WAIT", "PAUSE", "JUMP", "IIQ", "REX", "SDP", "CDP", "DTS", + "SSP", "CHI", "NT", "TE", "CDEC", "TEST", "CDM", "RESRV" +}; + +#define dprintk(a, x...) if (dbri_debug & a) printk(KERN_DEBUG x) + +#else +#define dprintk(a, x...) do { } while (0) + +#endif /* DBRI_DEBUG */ + +#define DBRI_CMD(cmd, intr, value) ((cmd << 28) | \ + (intr << 27) | \ + value) + +/*************************************************************************** + CS4215 specific definitions and structures +****************************************************************************/ + +struct cs4215 { + __u8 data[4]; /* Data mode: Time slots 5-8 */ + __u8 ctrl[4]; /* Ctrl mode: Time slots 1-4 */ + __u8 onboard; + __u8 offset; /* Bit offset from frame sync to time slot 1 */ + volatile __u32 status; + volatile __u32 version; + __u8 precision; /* In bits, either 8 or 16 */ + __u8 channels; /* 1 or 2 */ +}; + +/* + * Control mode first + */ + +/* Time Slot 1, Status register */ +#define CS4215_CLB (1<<2) /* Control Latch Bit */ +#define CS4215_OLB (1<<3) /* 1: line: 2.0V, speaker 4V */ + /* 0: line: 2.8V, speaker 8V */ +#define CS4215_MLB (1<<4) /* 1: Microphone: 20dB gain disabled */ +#define CS4215_RSRVD_1 (1<<5) + +/* Time Slot 2, Data Format Register */ +#define CS4215_DFR_LINEAR16 0 +#define CS4215_DFR_ULAW 1 +#define CS4215_DFR_ALAW 2 +#define CS4215_DFR_LINEAR8 3 +#define CS4215_DFR_STEREO (1<<2) +static struct { + unsigned short freq; + unsigned char xtal; + unsigned char csval; +} CS4215_FREQ[] = { + { 8000, (1 << 4), (0 << 3) }, + { 16000, (1 << 4), (1 << 3) }, + { 27429, (1 << 4), (2 << 3) }, /* Actually 24428.57 */ + { 32000, (1 << 4), (3 << 3) }, + /* { NA, (1 << 4), (4 << 3) }, */ + /* { NA, (1 << 4), (5 << 3) }, */ + { 48000, (1 << 4), (6 << 3) }, + { 9600, (1 << 4), (7 << 3) }, + { 5512, (2 << 4), (0 << 3) }, /* Actually 5512.5 */ + { 11025, (2 << 4), (1 << 3) }, + { 18900, (2 << 4), (2 << 3) }, + { 22050, (2 << 4), (3 << 3) }, + { 37800, (2 << 4), (4 << 3) }, + { 44100, (2 << 4), (5 << 3) }, + { 33075, (2 << 4), (6 << 3) }, + { 6615, (2 << 4), (7 << 3) }, + { 0, 0, 0} +}; + +#define CS4215_HPF (1<<7) /* High Pass Filter, 1: Enabled */ + +#define CS4215_12_MASK 0xfcbf /* Mask off reserved bits in slot 1 & 2 */ + +/* Time Slot 3, Serial Port Control register */ +#define CS4215_XEN (1<<0) /* 0: Enable serial output */ +#define CS4215_XCLK (1<<1) /* 1: Master mode: Generate SCLK */ +#define CS4215_BSEL_64 (0<<2) /* Bitrate: 64 bits per frame */ +#define CS4215_BSEL_128 (1<<2) +#define CS4215_BSEL_256 (2<<2) +#define CS4215_MCK_MAST (0<<4) /* Master clock */ +#define CS4215_MCK_XTL1 (1<<4) /* 24.576 MHz clock source */ +#define CS4215_MCK_XTL2 (2<<4) /* 16.9344 MHz clock source */ +#define CS4215_MCK_CLK1 (3<<4) /* Clockin, 256 x Fs */ +#define CS4215_MCK_CLK2 (4<<4) /* Clockin, see DFR */ + +/* Time Slot 4, Test Register */ +#define CS4215_DAD (1<<0) /* 0:Digital-Dig loop, 1:Dig-Analog-Dig loop */ +#define CS4215_ENL (1<<1) /* Enable Loopback Testing */ + +/* Time Slot 5, Parallel Port Register */ +/* Read only here and the same as the in data mode */ + +/* Time Slot 6, Reserved */ + +/* Time Slot 7, Version Register */ +#define CS4215_VERSION_MASK 0xf /* Known versions 0/C, 1/D, 2/E */ + +/* Time Slot 8, Reserved */ + +/* + * Data mode + */ +/* Time Slot 1-2: Left Channel Data, 2-3: Right Channel Data */ + +/* Time Slot 5, Output Setting */ +#define CS4215_LO(v) v /* Left Output Attenuation 0x3f: -94.5 dB */ +#define CS4215_LE (1<<6) /* Line Out Enable */ +#define CS4215_HE (1<<7) /* Headphone Enable */ + +/* Time Slot 6, Output Setting */ +#define CS4215_RO(v) v /* Right Output Attenuation 0x3f: -94.5 dB */ +#define CS4215_SE (1<<6) /* Speaker Enable */ +#define CS4215_ADI (1<<7) /* A/D Data Invalid: Busy in calibration */ + +/* Time Slot 7, Input Setting */ +#define CS4215_LG(v) v /* Left Gain Setting 0xf: 22.5 dB */ +#define CS4215_IS (1<<4) /* Input Select: 1=Microphone, 0=Line */ +#define CS4215_OVR (1<<5) /* 1: Over range condition occurred */ +#define CS4215_PIO0 (1<<6) /* Parallel I/O 0 */ +#define CS4215_PIO1 (1<<7) + +/* Time Slot 8, Input Setting */ +#define CS4215_RG(v) v /* Right Gain Setting 0xf: 22.5 dB */ +#define CS4215_MA(v) (v<<4) /* Monitor Path Attenuation 0xf: mute */ + +/*************************************************************************** + DBRI specific definitions and structures +****************************************************************************/ + +/* DBRI main registers */ +#define REG0 0x00 /* Status and Control */ +#define REG1 0x04 /* Mode and Interrupt */ +#define REG2 0x08 /* Parallel IO */ +#define REG3 0x0c /* Test */ +#define REG8 0x20 /* Command Queue Pointer */ +#define REG9 0x24 /* Interrupt Queue Pointer */ + +#define DBRI_NO_CMDS 64 +#define DBRI_INT_BLK 64 +#define DBRI_NO_DESCS 64 +#define DBRI_NO_PIPES 32 +#define DBRI_MAX_PIPE (DBRI_NO_PIPES - 1) + +#define DBRI_REC 0 +#define DBRI_PLAY 1 +#define DBRI_NO_STREAMS 2 + +/* One transmit/receive descriptor */ +/* When ba != 0 descriptor is used */ +struct dbri_mem { + volatile __u32 word1; + __u32 ba; /* Transmit/Receive Buffer Address */ + __u32 nda; /* Next Descriptor Address */ + volatile __u32 word4; +}; + +/* This structure is in a DMA region where it can accessed by both + * the CPU and the DBRI + */ +struct dbri_dma { + s32 cmd[DBRI_NO_CMDS]; /* Place for commands */ + volatile s32 intr[DBRI_INT_BLK]; /* Interrupt field */ + struct dbri_mem desc[DBRI_NO_DESCS]; /* Xmit/receive descriptors */ +}; + +#define dbri_dma_off(member, elem) \ + ((u32)(unsigned long) \ + (&(((struct dbri_dma *)0)->member[elem]))) + +enum in_or_out { PIPEinput, PIPEoutput }; + +struct dbri_pipe { + u32 sdp; /* SDP command word */ + int nextpipe; /* Next pipe in linked list */ + int length; /* Length of timeslot (bits) */ + int first_desc; /* Index of first descriptor */ + int desc; /* Index of active descriptor */ + volatile __u32 *recv_fixed_ptr; /* Ptr to receive fixed data */ +}; + +/* Per stream (playback or record) information */ +struct dbri_streaminfo { + struct snd_pcm_substream *substream; + u32 dvma_buffer; /* Device view of ALSA DMA buffer */ + int size; /* Size of DMA buffer */ + size_t offset; /* offset in user buffer */ + int pipe; /* Data pipe used */ + int left_gain; /* mixer elements */ + int right_gain; +}; + +/* This structure holds the information for both chips (DBRI & CS4215) */ +struct snd_dbri { + int regs_size, irq; /* Needed for unload */ + struct platform_device *op; /* OF device info */ + spinlock_t lock; + + struct dbri_dma *dma; /* Pointer to our DMA block */ + dma_addr_t dma_dvma; /* DBRI visible DMA address */ + + void __iomem *regs; /* dbri HW regs */ + int dbri_irqp; /* intr queue pointer */ + + struct dbri_pipe pipes[DBRI_NO_PIPES]; /* DBRI's 32 data pipes */ + int next_desc[DBRI_NO_DESCS]; /* Index of next desc, or -1 */ + spinlock_t cmdlock; /* Protects cmd queue accesses */ + s32 *cmdptr; /* Pointer to the last queued cmd */ + + int chi_bpf; + + struct cs4215 mm; /* mmcodec special info */ + /* per stream (playback/record) info */ + struct dbri_streaminfo stream_info[DBRI_NO_STREAMS]; +}; + +#define DBRI_MAX_VOLUME 63 /* Output volume */ +#define DBRI_MAX_GAIN 15 /* Input gain */ + +/* DBRI Reg0 - Status Control Register - defines. (Page 17) */ +#define D_P (1<<15) /* Program command & queue pointer valid */ +#define D_G (1<<14) /* Allow 4-Word SBus Burst */ +#define D_S (1<<13) /* Allow 16-Word SBus Burst */ +#define D_E (1<<12) /* Allow 8-Word SBus Burst */ +#define D_X (1<<7) /* Sanity Timer Disable */ +#define D_T (1<<6) /* Permit activation of the TE interface */ +#define D_N (1<<5) /* Permit activation of the NT interface */ +#define D_C (1<<4) /* Permit activation of the CHI interface */ +#define D_F (1<<3) /* Force Sanity Timer Time-Out */ +#define D_D (1<<2) /* Disable Master Mode */ +#define D_H (1<<1) /* Halt for Analysis */ +#define D_R (1<<0) /* Soft Reset */ + +/* DBRI Reg1 - Mode and Interrupt Register - defines. (Page 18) */ +#define D_LITTLE_END (1<<8) /* Byte Order */ +#define D_BIG_END (0<<8) /* Byte Order */ +#define D_MRR (1<<4) /* Multiple Error Ack on SBus (read only) */ +#define D_MLE (1<<3) /* Multiple Late Error on SBus (read only) */ +#define D_LBG (1<<2) /* Lost Bus Grant on SBus (read only) */ +#define D_MBE (1<<1) /* Burst Error on SBus (read only) */ +#define D_IR (1<<0) /* Interrupt Indicator (read only) */ + +/* DBRI Reg2 - Parallel IO Register - defines. (Page 18) */ +#define D_ENPIO3 (1<<7) /* Enable Pin 3 */ +#define D_ENPIO2 (1<<6) /* Enable Pin 2 */ +#define D_ENPIO1 (1<<5) /* Enable Pin 1 */ +#define D_ENPIO0 (1<<4) /* Enable Pin 0 */ +#define D_ENPIO (0xf0) /* Enable all the pins */ +#define D_PIO3 (1<<3) /* Pin 3: 1: Data mode, 0: Ctrl mode */ +#define D_PIO2 (1<<2) /* Pin 2: 1: Onboard PDN */ +#define D_PIO1 (1<<1) /* Pin 1: 0: Reset */ +#define D_PIO0 (1<<0) /* Pin 0: 1: Speakerbox PDN */ + +/* DBRI Commands (Page 20) */ +#define D_WAIT 0x0 /* Stop execution */ +#define D_PAUSE 0x1 /* Flush long pipes */ +#define D_JUMP 0x2 /* New command queue */ +#define D_IIQ 0x3 /* Initialize Interrupt Queue */ +#define D_REX 0x4 /* Report command execution via interrupt */ +#define D_SDP 0x5 /* Setup Data Pipe */ +#define D_CDP 0x6 /* Continue Data Pipe (reread NULL Pointer) */ +#define D_DTS 0x7 /* Define Time Slot */ +#define D_SSP 0x8 /* Set short Data Pipe */ +#define D_CHI 0x9 /* Set CHI Global Mode */ +#define D_NT 0xa /* NT Command */ +#define D_TE 0xb /* TE Command */ +#define D_CDEC 0xc /* Codec setup */ +#define D_TEST 0xd /* No comment */ +#define D_CDM 0xe /* CHI Data mode command */ + +/* Special bits for some commands */ +#define D_PIPE(v) ((v)<<0) /* Pipe No.: 0-15 long, 16-21 short */ + +/* Setup Data Pipe */ +/* IRM */ +#define D_SDP_2SAME (1<<18) /* Report 2nd time in a row value received */ +#define D_SDP_CHANGE (2<<18) /* Report any changes */ +#define D_SDP_EVERY (3<<18) /* Report any changes */ +#define D_SDP_EOL (1<<17) /* EOL interrupt enable */ +#define D_SDP_IDLE (1<<16) /* HDLC idle interrupt enable */ + +/* Pipe data MODE */ +#define D_SDP_MEM (0<<13) /* To/from memory */ +#define D_SDP_HDLC (2<<13) +#define D_SDP_HDLC_D (3<<13) /* D Channel (prio control) */ +#define D_SDP_SER (4<<13) /* Serial to serial */ +#define D_SDP_FIXED (6<<13) /* Short only */ +#define D_SDP_MODE(v) ((v)&(7<<13)) + +#define D_SDP_TO_SER (1<<12) /* Direction */ +#define D_SDP_FROM_SER (0<<12) /* Direction */ +#define D_SDP_MSB (1<<11) /* Bit order within Byte */ +#define D_SDP_LSB (0<<11) /* Bit order within Byte */ +#define D_SDP_P (1<<10) /* Pointer Valid */ +#define D_SDP_A (1<<8) /* Abort */ +#define D_SDP_C (1<<7) /* Clear */ + +/* Define Time Slot */ +#define D_DTS_VI (1<<17) /* Valid Input Time-Slot Descriptor */ +#define D_DTS_VO (1<<16) /* Valid Output Time-Slot Descriptor */ +#define D_DTS_INS (1<<15) /* Insert Time Slot */ +#define D_DTS_DEL (0<<15) /* Delete Time Slot */ +#define D_DTS_PRVIN(v) ((v)<<10) /* Previous In Pipe */ +#define D_DTS_PRVOUT(v) ((v)<<5) /* Previous Out Pipe */ + +/* Time Slot defines */ +#define D_TS_LEN(v) ((v)<<24) /* Number of bits in this time slot */ +#define D_TS_CYCLE(v) ((v)<<14) /* Bit Count at start of TS */ +#define D_TS_DI (1<<13) /* Data Invert */ +#define D_TS_1CHANNEL (0<<10) /* Single Channel / Normal mode */ +#define D_TS_MONITOR (2<<10) /* Monitor pipe */ +#define D_TS_NONCONTIG (3<<10) /* Non contiguous mode */ +#define D_TS_ANCHOR (7<<10) /* Starting short pipes */ +#define D_TS_MON(v) ((v)<<5) /* Monitor Pipe */ +#define D_TS_NEXT(v) ((v)<<0) /* Pipe no.: 0-15 long, 16-21 short */ + +/* Concentration Highway Interface Modes */ +#define D_CHI_CHICM(v) ((v)<<16) /* Clock mode */ +#define D_CHI_IR (1<<15) /* Immediate Interrupt Report */ +#define D_CHI_EN (1<<14) /* CHIL Interrupt enabled */ +#define D_CHI_OD (1<<13) /* Open Drain Enable */ +#define D_CHI_FE (1<<12) /* Sample CHIFS on Rising Frame Edge */ +#define D_CHI_FD (1<<11) /* Frame Drive */ +#define D_CHI_BPF(v) ((v)<<0) /* Bits per Frame */ + +/* NT: These are here for completeness */ +#define D_NT_FBIT (1<<17) /* Frame Bit */ +#define D_NT_NBF (1<<16) /* Number of bad frames to loose framing */ +#define D_NT_IRM_IMM (1<<15) /* Interrupt Report & Mask: Immediate */ +#define D_NT_IRM_EN (1<<14) /* Interrupt Report & Mask: Enable */ +#define D_NT_ISNT (1<<13) /* Configure interface as NT */ +#define D_NT_FT (1<<12) /* Fixed Timing */ +#define D_NT_EZ (1<<11) /* Echo Channel is Zeros */ +#define D_NT_IFA (1<<10) /* Inhibit Final Activation */ +#define D_NT_ACT (1<<9) /* Activate Interface */ +#define D_NT_MFE (1<<8) /* Multiframe Enable */ +#define D_NT_RLB(v) ((v)<<5) /* Remote Loopback */ +#define D_NT_LLB(v) ((v)<<2) /* Local Loopback */ +#define D_NT_FACT (1<<1) /* Force Activation */ +#define D_NT_ABV (1<<0) /* Activate Bipolar Violation */ + +/* Codec Setup */ +#define D_CDEC_CK(v) ((v)<<24) /* Clock Select */ +#define D_CDEC_FED(v) ((v)<<12) /* FSCOD Falling Edge Delay */ +#define D_CDEC_RED(v) ((v)<<0) /* FSCOD Rising Edge Delay */ + +/* Test */ +#define D_TEST_RAM(v) ((v)<<16) /* RAM Pointer */ +#define D_TEST_SIZE(v) ((v)<<11) /* */ +#define D_TEST_ROMONOFF 0x5 /* Toggle ROM opcode monitor on/off */ +#define D_TEST_PROC 0x6 /* Microprocessor test */ +#define D_TEST_SER 0x7 /* Serial-Controller test */ +#define D_TEST_RAMREAD 0x8 /* Copy from Ram to system memory */ +#define D_TEST_RAMWRITE 0x9 /* Copy into Ram from system memory */ +#define D_TEST_RAMBIST 0xa /* RAM Built-In Self Test */ +#define D_TEST_MCBIST 0xb /* Microcontroller Built-In Self Test */ +#define D_TEST_DUMP 0xe /* ROM Dump */ + +/* CHI Data Mode */ +#define D_CDM_THI (1 << 8) /* Transmit Data on CHIDR Pin */ +#define D_CDM_RHI (1 << 7) /* Receive Data on CHIDX Pin */ +#define D_CDM_RCE (1 << 6) /* Receive on Rising Edge of CHICK */ +#define D_CDM_XCE (1 << 2) /* Transmit Data on Rising Edge of CHICK */ +#define D_CDM_XEN (1 << 1) /* Transmit Highway Enable */ +#define D_CDM_REN (1 << 0) /* Receive Highway Enable */ + +/* The Interrupts */ +#define D_INTR_BRDY 1 /* Buffer Ready for processing */ +#define D_INTR_MINT 2 /* Marked Interrupt in RD/TD */ +#define D_INTR_IBEG 3 /* Flag to idle transition detected (HDLC) */ +#define D_INTR_IEND 4 /* Idle to flag transition detected (HDLC) */ +#define D_INTR_EOL 5 /* End of List */ +#define D_INTR_CMDI 6 /* Command has bean read */ +#define D_INTR_XCMP 8 /* Transmission of frame complete */ +#define D_INTR_SBRI 9 /* BRI status change info */ +#define D_INTR_FXDT 10 /* Fixed data change */ +#define D_INTR_CHIL 11 /* CHI lost frame sync (channel 36 only) */ +#define D_INTR_COLL 11 /* Unrecoverable D-Channel collision */ +#define D_INTR_DBYT 12 /* Dropped by frame slip */ +#define D_INTR_RBYT 13 /* Repeated by frame slip */ +#define D_INTR_LINT 14 /* Lost Interrupt */ +#define D_INTR_UNDR 15 /* DMA underrun */ + +#define D_INTR_TE 32 +#define D_INTR_NT 34 +#define D_INTR_CHI 36 +#define D_INTR_CMD 38 + +#define D_INTR_GETCHAN(v) (((v) >> 24) & 0x3f) +#define D_INTR_GETCODE(v) (((v) >> 20) & 0xf) +#define D_INTR_GETCMD(v) (((v) >> 16) & 0xf) +#define D_INTR_GETVAL(v) ((v) & 0xffff) +#define D_INTR_GETRVAL(v) ((v) & 0xfffff) + +#define D_P_0 0 /* TE receive anchor */ +#define D_P_1 1 /* TE transmit anchor */ +#define D_P_2 2 /* NT transmit anchor */ +#define D_P_3 3 /* NT receive anchor */ +#define D_P_4 4 /* CHI send data */ +#define D_P_5 5 /* CHI receive data */ +#define D_P_6 6 /* */ +#define D_P_7 7 /* */ +#define D_P_8 8 /* */ +#define D_P_9 9 /* */ +#define D_P_10 10 /* */ +#define D_P_11 11 /* */ +#define D_P_12 12 /* */ +#define D_P_13 13 /* */ +#define D_P_14 14 /* */ +#define D_P_15 15 /* */ +#define D_P_16 16 /* CHI anchor pipe */ +#define D_P_17 17 /* CHI send */ +#define D_P_18 18 /* CHI receive */ +#define D_P_19 19 /* CHI receive */ +#define D_P_20 20 /* CHI receive */ +#define D_P_21 21 /* */ +#define D_P_22 22 /* */ +#define D_P_23 23 /* */ +#define D_P_24 24 /* */ +#define D_P_25 25 /* */ +#define D_P_26 26 /* */ +#define D_P_27 27 /* */ +#define D_P_28 28 /* */ +#define D_P_29 29 /* */ +#define D_P_30 30 /* */ +#define D_P_31 31 /* */ + +/* Transmit descriptor defines */ +#define DBRI_TD_F (1 << 31) /* End of Frame */ +#define DBRI_TD_D (1 << 30) /* Do not append CRC */ +#define DBRI_TD_CNT(v) ((v) << 16) /* Number of valid bytes in the buffer */ +#define DBRI_TD_B (1 << 15) /* Final interrupt */ +#define DBRI_TD_M (1 << 14) /* Marker interrupt */ +#define DBRI_TD_I (1 << 13) /* Transmit Idle Characters */ +#define DBRI_TD_FCNT(v) (v) /* Flag Count */ +#define DBRI_TD_UNR (1 << 3) /* Underrun: transmitter is out of data */ +#define DBRI_TD_ABT (1 << 2) /* Abort: frame aborted */ +#define DBRI_TD_TBC (1 << 0) /* Transmit buffer Complete */ +#define DBRI_TD_STATUS(v) ((v) & 0xff) /* Transmit status */ + /* Maximum buffer size per TD: almost 8KB */ +#define DBRI_TD_MAXCNT ((1 << 13) - 4) + +/* Receive descriptor defines */ +#define DBRI_RD_F (1 << 31) /* End of Frame */ +#define DBRI_RD_C (1 << 30) /* Completed buffer */ +#define DBRI_RD_B (1 << 15) /* Final interrupt */ +#define DBRI_RD_M (1 << 14) /* Marker interrupt */ +#define DBRI_RD_BCNT(v) (v) /* Buffer size */ +#define DBRI_RD_CRC (1 << 7) /* 0: CRC is correct */ +#define DBRI_RD_BBC (1 << 6) /* 1: Bad Byte received */ +#define DBRI_RD_ABT (1 << 5) /* Abort: frame aborted */ +#define DBRI_RD_OVRN (1 << 3) /* Overrun: data lost */ +#define DBRI_RD_STATUS(v) ((v) & 0xff) /* Receive status */ +#define DBRI_RD_CNT(v) (((v) >> 16) & 0x1fff) /* Valid bytes in the buffer */ + +/* stream_info[] access */ +/* Translate the ALSA direction into the array index */ +#define DBRI_STREAMNO(substream) \ + (substream->stream == \ + SNDRV_PCM_STREAM_PLAYBACK ? DBRI_PLAY: DBRI_REC) + +/* Return a pointer to dbri_streaminfo */ +#define DBRI_STREAM(dbri, substream) \ + &dbri->stream_info[DBRI_STREAMNO(substream)] + +/* + * Short data pipes transmit LSB first. The CS4215 receives MSB first. Grrr. + * So we have to reverse the bits. Note: not all bit lengths are supported + */ +static __u32 reverse_bytes(__u32 b, int len) +{ + switch (len) { + case 32: + b = ((b & 0xffff0000) >> 16) | ((b & 0x0000ffff) << 16); + case 16: + b = ((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8); + case 8: + b = ((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4); + case 4: + b = ((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2); + case 2: + b = ((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1); + case 1: + case 0: + break; + default: + printk(KERN_ERR "DBRI reverse_bytes: unsupported length\n"); + } + + return b; +} + +/* +**************************************************************************** +************** DBRI initialization and command synchronization ************* +**************************************************************************** + +Commands are sent to the DBRI by building a list of them in memory, +then writing the address of the first list item to DBRI register 8. +The list is terminated with a WAIT command, which generates a +CPU interrupt to signal completion. + +Since the DBRI can run in parallel with the CPU, several means of +synchronization present themselves. The method implemented here uses +the dbri_cmdwait() to wait for execution of batch of sent commands. + +A circular command buffer is used here. A new command is being added +while another can be executed. The scheme works by adding two WAIT commands +after each sent batch of commands. When the next batch is prepared it is +added after the WAIT commands then the WAITs are replaced with single JUMP +command to the new batch. The the DBRI is forced to reread the last WAIT +command (replaced by the JUMP by then). If the DBRI is still executing +previous commands the request to reread the WAIT command is ignored. + +Every time a routine wants to write commands to the DBRI, it must +first call dbri_cmdlock() and get pointer to a free space in +dbri->dma->cmd buffer. After this, the commands can be written to +the buffer, and dbri_cmdsend() is called with the final pointer value +to send them to the DBRI. + +*/ + +#define MAXLOOPS 20 +/* + * Wait for the current command string to execute + */ +static void dbri_cmdwait(struct snd_dbri *dbri) +{ + int maxloops = MAXLOOPS; + unsigned long flags; + + /* Delay if previous commands are still being processed */ + spin_lock_irqsave(&dbri->lock, flags); + while ((--maxloops) > 0 && (sbus_readl(dbri->regs + REG0) & D_P)) { + spin_unlock_irqrestore(&dbri->lock, flags); + msleep_interruptible(1); + spin_lock_irqsave(&dbri->lock, flags); + } + spin_unlock_irqrestore(&dbri->lock, flags); + + if (maxloops == 0) + printk(KERN_ERR "DBRI: Chip never completed command buffer\n"); + else + dprintk(D_CMD, "Chip completed command buffer (%d)\n", + MAXLOOPS - maxloops - 1); +} +/* + * Lock the command queue and return pointer to space for len cmd words + * It locks the cmdlock spinlock. + */ +static s32 *dbri_cmdlock(struct snd_dbri *dbri, int len) +{ + u32 dvma_addr = (u32)dbri->dma_dvma; + + /* Space for 2 WAIT cmds (replaced later by 1 JUMP cmd) */ + len += 2; + spin_lock(&dbri->cmdlock); + if (dbri->cmdptr - dbri->dma->cmd + len < DBRI_NO_CMDS - 2) + return dbri->cmdptr + 2; + else if (len < sbus_readl(dbri->regs + REG8) - dvma_addr) + return dbri->dma->cmd; + else + printk(KERN_ERR "DBRI: no space for commands."); + + return NULL; +} + +/* + * Send prepared cmd string. It works by writing a JUMP cmd into + * the last WAIT cmd and force DBRI to reread the cmd. + * The JUMP cmd points to the new cmd string. + * It also releases the cmdlock spinlock. + * + * Lock must be held before calling this. + */ +static void dbri_cmdsend(struct snd_dbri *dbri, s32 *cmd, int len) +{ + u32 dvma_addr = (u32)dbri->dma_dvma; + s32 tmp, addr; + static int wait_id = 0; + + wait_id++; + wait_id &= 0xffff; /* restrict it to a 16 bit counter. */ + *(cmd) = DBRI_CMD(D_WAIT, 1, wait_id); + *(cmd+1) = DBRI_CMD(D_WAIT, 1, wait_id); + + /* Replace the last command with JUMP */ + addr = dvma_addr + (cmd - len - dbri->dma->cmd) * sizeof(s32); + *(dbri->cmdptr+1) = addr; + *(dbri->cmdptr) = DBRI_CMD(D_JUMP, 0, 0); + +#ifdef DBRI_DEBUG + if (cmd > dbri->cmdptr) { + s32 *ptr; + + for (ptr = dbri->cmdptr; ptr < cmd+2; ptr++) + dprintk(D_CMD, "cmd: %lx:%08x\n", + (unsigned long)ptr, *ptr); + } else { + s32 *ptr = dbri->cmdptr; + + dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr); + ptr++; + dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr); + for (ptr = dbri->dma->cmd; ptr < cmd+2; ptr++) + dprintk(D_CMD, "cmd: %lx:%08x\n", + (unsigned long)ptr, *ptr); + } +#endif + + /* Reread the last command */ + tmp = sbus_readl(dbri->regs + REG0); + tmp |= D_P; + sbus_writel(tmp, dbri->regs + REG0); + + dbri->cmdptr = cmd; + spin_unlock(&dbri->cmdlock); +} + +/* Lock must be held when calling this */ +static void dbri_reset(struct snd_dbri *dbri) +{ + int i; + u32 tmp; + + dprintk(D_GEN, "reset 0:%x 2:%x 8:%x 9:%x\n", + sbus_readl(dbri->regs + REG0), + sbus_readl(dbri->regs + REG2), + sbus_readl(dbri->regs + REG8), sbus_readl(dbri->regs + REG9)); + + sbus_writel(D_R, dbri->regs + REG0); /* Soft Reset */ + for (i = 0; (sbus_readl(dbri->regs + REG0) & D_R) && i < 64; i++) + udelay(10); + + /* A brute approach - DBRI falls back to working burst size by itself + * On SS20 D_S does not work, so do not try so high. */ + tmp = sbus_readl(dbri->regs + REG0); + tmp |= D_G | D_E; + tmp &= ~D_S; + sbus_writel(tmp, dbri->regs + REG0); +} + +/* Lock must not be held before calling this */ +static void dbri_initialize(struct snd_dbri *dbri) +{ + u32 dvma_addr = (u32)dbri->dma_dvma; + s32 *cmd; + u32 dma_addr; + unsigned long flags; + int n; + + spin_lock_irqsave(&dbri->lock, flags); + + dbri_reset(dbri); + + /* Initialize pipes */ + for (n = 0; n < DBRI_NO_PIPES; n++) + dbri->pipes[n].desc = dbri->pipes[n].first_desc = -1; + + spin_lock_init(&dbri->cmdlock); + /* + * Initialize the interrupt ring buffer. + */ + dma_addr = dvma_addr + dbri_dma_off(intr, 0); + dbri->dma->intr[0] = dma_addr; + dbri->dbri_irqp = 1; + /* + * Set up the interrupt queue + */ + spin_lock(&dbri->cmdlock); + cmd = dbri->cmdptr = dbri->dma->cmd; + *(cmd++) = DBRI_CMD(D_IIQ, 0, 0); + *(cmd++) = dma_addr; + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + dbri->cmdptr = cmd; + *(cmd++) = DBRI_CMD(D_WAIT, 1, 0); + *(cmd++) = DBRI_CMD(D_WAIT, 1, 0); + dma_addr = dvma_addr + dbri_dma_off(cmd, 0); + sbus_writel(dma_addr, dbri->regs + REG8); + spin_unlock(&dbri->cmdlock); + + spin_unlock_irqrestore(&dbri->lock, flags); + dbri_cmdwait(dbri); +} + +/* +**************************************************************************** +************************** DBRI data pipe management *********************** +**************************************************************************** + +While DBRI control functions use the command and interrupt buffers, the +main data path takes the form of data pipes, which can be short (command +and interrupt driven), or long (attached to DMA buffers). These functions +provide a rudimentary means of setting up and managing the DBRI's pipes, +but the calling functions have to make sure they respect the pipes' linked +list ordering, among other things. The transmit and receive functions +here interface closely with the transmit and receive interrupt code. + +*/ +static inline int pipe_active(struct snd_dbri *dbri, int pipe) +{ + return ((pipe >= 0) && (dbri->pipes[pipe].desc != -1)); +} + +/* reset_pipe(dbri, pipe) + * + * Called on an in-use pipe to clear anything being transmitted or received + * Lock must be held before calling this. + */ +static void reset_pipe(struct snd_dbri *dbri, int pipe) +{ + int sdp; + int desc; + s32 *cmd; + + if (pipe < 0 || pipe > DBRI_MAX_PIPE) { + printk(KERN_ERR "DBRI: reset_pipe called with " + "illegal pipe number\n"); + return; + } + + sdp = dbri->pipes[pipe].sdp; + if (sdp == 0) { + printk(KERN_ERR "DBRI: reset_pipe called " + "on uninitialized pipe\n"); + return; + } + + cmd = dbri_cmdlock(dbri, 3); + *(cmd++) = DBRI_CMD(D_SDP, 0, sdp | D_SDP_C | D_SDP_P); + *(cmd++) = 0; + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + dbri_cmdsend(dbri, cmd, 3); + + desc = dbri->pipes[pipe].first_desc; + if (desc >= 0) + do { + dbri->dma->desc[desc].ba = 0; + dbri->dma->desc[desc].nda = 0; + desc = dbri->next_desc[desc]; + } while (desc != -1 && desc != dbri->pipes[pipe].first_desc); + + dbri->pipes[pipe].desc = -1; + dbri->pipes[pipe].first_desc = -1; +} + +/* + * Lock must be held before calling this. + */ +static void setup_pipe(struct snd_dbri *dbri, int pipe, int sdp) +{ + if (pipe < 0 || pipe > DBRI_MAX_PIPE) { + printk(KERN_ERR "DBRI: setup_pipe called " + "with illegal pipe number\n"); + return; + } + + if ((sdp & 0xf800) != sdp) { + printk(KERN_ERR "DBRI: setup_pipe called " + "with strange SDP value\n"); + /* sdp &= 0xf800; */ + } + + /* If this is a fixed receive pipe, arrange for an interrupt + * every time its data changes + */ + if (D_SDP_MODE(sdp) == D_SDP_FIXED && !(sdp & D_SDP_TO_SER)) + sdp |= D_SDP_CHANGE; + + sdp |= D_PIPE(pipe); + dbri->pipes[pipe].sdp = sdp; + dbri->pipes[pipe].desc = -1; + dbri->pipes[pipe].first_desc = -1; + + reset_pipe(dbri, pipe); +} + +/* + * Lock must be held before calling this. + */ +static void link_time_slot(struct snd_dbri *dbri, int pipe, + int prevpipe, int nextpipe, + int length, int cycle) +{ + s32 *cmd; + int val; + + if (pipe < 0 || pipe > DBRI_MAX_PIPE + || prevpipe < 0 || prevpipe > DBRI_MAX_PIPE + || nextpipe < 0 || nextpipe > DBRI_MAX_PIPE) { + printk(KERN_ERR + "DBRI: link_time_slot called with illegal pipe number\n"); + return; + } + + if (dbri->pipes[pipe].sdp == 0 + || dbri->pipes[prevpipe].sdp == 0 + || dbri->pipes[nextpipe].sdp == 0) { + printk(KERN_ERR "DBRI: link_time_slot called " + "on uninitialized pipe\n"); + return; + } + + dbri->pipes[prevpipe].nextpipe = pipe; + dbri->pipes[pipe].nextpipe = nextpipe; + dbri->pipes[pipe].length = length; + + cmd = dbri_cmdlock(dbri, 4); + + if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) { + /* Deal with CHI special case: + * "If transmission on edges 0 or 1 is desired, then cycle n + * (where n = # of bit times per frame...) must be used." + * - DBRI data sheet, page 11 + */ + if (prevpipe == 16 && cycle == 0) + cycle = dbri->chi_bpf; + + val = D_DTS_VO | D_DTS_INS | D_DTS_PRVOUT(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = 0; + *(cmd++) = + D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe); + } else { + val = D_DTS_VI | D_DTS_INS | D_DTS_PRVIN(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = + D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe); + *(cmd++) = 0; + } + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + + dbri_cmdsend(dbri, cmd, 4); +} + +#if 0 +/* + * Lock must be held before calling this. + */ +static void unlink_time_slot(struct snd_dbri *dbri, int pipe, + enum in_or_out direction, int prevpipe, + int nextpipe) +{ + s32 *cmd; + int val; + + if (pipe < 0 || pipe > DBRI_MAX_PIPE + || prevpipe < 0 || prevpipe > DBRI_MAX_PIPE + || nextpipe < 0 || nextpipe > DBRI_MAX_PIPE) { + printk(KERN_ERR + "DBRI: unlink_time_slot called with illegal pipe number\n"); + return; + } + + cmd = dbri_cmdlock(dbri, 4); + + if (direction == PIPEinput) { + val = D_DTS_VI | D_DTS_DEL | D_DTS_PRVIN(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = D_TS_NEXT(nextpipe); + *(cmd++) = 0; + } else { + val = D_DTS_VO | D_DTS_DEL | D_DTS_PRVOUT(prevpipe) | pipe; + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = 0; + *(cmd++) = D_TS_NEXT(nextpipe); + } + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + + dbri_cmdsend(dbri, cmd, 4); +} +#endif + +/* xmit_fixed() / recv_fixed() + * + * Transmit/receive data on a "fixed" pipe - i.e, one whose contents are not + * expected to change much, and which we don't need to buffer. + * The DBRI only interrupts us when the data changes (receive pipes), + * or only changes the data when this function is called (transmit pipes). + * Only short pipes (numbers 16-31) can be used in fixed data mode. + * + * These function operate on a 32-bit field, no matter how large + * the actual time slot is. The interrupt handler takes care of bit + * ordering and alignment. An 8-bit time slot will always end up + * in the low-order 8 bits, filled either MSB-first or LSB-first, + * depending on the settings passed to setup_pipe(). + * + * Lock must not be held before calling it. + */ +static void xmit_fixed(struct snd_dbri *dbri, int pipe, unsigned int data) +{ + s32 *cmd; + unsigned long flags; + + if (pipe < 16 || pipe > DBRI_MAX_PIPE) { + printk(KERN_ERR "DBRI: xmit_fixed: Illegal pipe number\n"); + return; + } + + if (D_SDP_MODE(dbri->pipes[pipe].sdp) == 0) { + printk(KERN_ERR "DBRI: xmit_fixed: " + "Uninitialized pipe %d\n", pipe); + return; + } + + if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) { + printk(KERN_ERR "DBRI: xmit_fixed: Non-fixed pipe %d\n", pipe); + return; + } + + if (!(dbri->pipes[pipe].sdp & D_SDP_TO_SER)) { + printk(KERN_ERR "DBRI: xmit_fixed: Called on receive pipe %d\n", + pipe); + return; + } + + /* DBRI short pipes always transmit LSB first */ + + if (dbri->pipes[pipe].sdp & D_SDP_MSB) + data = reverse_bytes(data, dbri->pipes[pipe].length); + + cmd = dbri_cmdlock(dbri, 3); + + *(cmd++) = DBRI_CMD(D_SSP, 0, pipe); + *(cmd++) = data; + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + + spin_lock_irqsave(&dbri->lock, flags); + dbri_cmdsend(dbri, cmd, 3); + spin_unlock_irqrestore(&dbri->lock, flags); + dbri_cmdwait(dbri); + +} + +static void recv_fixed(struct snd_dbri *dbri, int pipe, volatile __u32 *ptr) +{ + if (pipe < 16 || pipe > DBRI_MAX_PIPE) { + printk(KERN_ERR "DBRI: recv_fixed called with " + "illegal pipe number\n"); + return; + } + + if (D_SDP_MODE(dbri->pipes[pipe].sdp) != D_SDP_FIXED) { + printk(KERN_ERR "DBRI: recv_fixed called on " + "non-fixed pipe %d\n", pipe); + return; + } + + if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) { + printk(KERN_ERR "DBRI: recv_fixed called on " + "transmit pipe %d\n", pipe); + return; + } + + dbri->pipes[pipe].recv_fixed_ptr = ptr; +} + +/* setup_descs() + * + * Setup transmit/receive data on a "long" pipe - i.e, one associated + * with a DMA buffer. + * + * Only pipe numbers 0-15 can be used in this mode. + * + * This function takes a stream number pointing to a data buffer, + * and work by building chains of descriptors which identify the + * data buffers. Buffers too large for a single descriptor will + * be spread across multiple descriptors. + * + * All descriptors create a ring buffer. + * + * Lock must be held before calling this. + */ +static int setup_descs(struct snd_dbri *dbri, int streamno, unsigned int period) +{ + struct dbri_streaminfo *info = &dbri->stream_info[streamno]; + u32 dvma_addr = (u32)dbri->dma_dvma; + __u32 dvma_buffer; + int desc; + int len; + int first_desc = -1; + int last_desc = -1; + + if (info->pipe < 0 || info->pipe > 15) { + printk(KERN_ERR "DBRI: setup_descs: Illegal pipe number\n"); + return -2; + } + + if (dbri->pipes[info->pipe].sdp == 0) { + printk(KERN_ERR "DBRI: setup_descs: Uninitialized pipe %d\n", + info->pipe); + return -2; + } + + dvma_buffer = info->dvma_buffer; + len = info->size; + + if (streamno == DBRI_PLAY) { + if (!(dbri->pipes[info->pipe].sdp & D_SDP_TO_SER)) { + printk(KERN_ERR "DBRI: setup_descs: " + "Called on receive pipe %d\n", info->pipe); + return -2; + } + } else { + if (dbri->pipes[info->pipe].sdp & D_SDP_TO_SER) { + printk(KERN_ERR + "DBRI: setup_descs: Called on transmit pipe %d\n", + info->pipe); + return -2; + } + /* Should be able to queue multiple buffers + * to receive on a pipe + */ + if (pipe_active(dbri, info->pipe)) { + printk(KERN_ERR "DBRI: recv_on_pipe: " + "Called on active pipe %d\n", info->pipe); + return -2; + } + + /* Make sure buffer size is multiple of four */ + len &= ~3; + } + + /* Free descriptors if pipe has any */ + desc = dbri->pipes[info->pipe].first_desc; + if (desc >= 0) + do { + dbri->dma->desc[desc].ba = 0; + dbri->dma->desc[desc].nda = 0; + desc = dbri->next_desc[desc]; + } while (desc != -1 && + desc != dbri->pipes[info->pipe].first_desc); + + dbri->pipes[info->pipe].desc = -1; + dbri->pipes[info->pipe].first_desc = -1; + + desc = 0; + while (len > 0) { + int mylen; + + for (; desc < DBRI_NO_DESCS; desc++) { + if (!dbri->dma->desc[desc].ba) + break; + } + + if (desc == DBRI_NO_DESCS) { + printk(KERN_ERR "DBRI: setup_descs: No descriptors\n"); + return -1; + } + + if (len > DBRI_TD_MAXCNT) + mylen = DBRI_TD_MAXCNT; /* 8KB - 4 */ + else + mylen = len; + + if (mylen > period) + mylen = period; + + dbri->next_desc[desc] = -1; + dbri->dma->desc[desc].ba = dvma_buffer; + dbri->dma->desc[desc].nda = 0; + + if (streamno == DBRI_PLAY) { + dbri->dma->desc[desc].word1 = DBRI_TD_CNT(mylen); + dbri->dma->desc[desc].word4 = 0; + dbri->dma->desc[desc].word1 |= DBRI_TD_F | DBRI_TD_B; + } else { + dbri->dma->desc[desc].word1 = 0; + dbri->dma->desc[desc].word4 = + DBRI_RD_B | DBRI_RD_BCNT(mylen); + } + + if (first_desc == -1) + first_desc = desc; + else { + dbri->next_desc[last_desc] = desc; + dbri->dma->desc[last_desc].nda = + dvma_addr + dbri_dma_off(desc, desc); + } + + last_desc = desc; + dvma_buffer += mylen; + len -= mylen; + } + + if (first_desc == -1 || last_desc == -1) { + printk(KERN_ERR "DBRI: setup_descs: " + " Not enough descriptors available\n"); + return -1; + } + + dbri->dma->desc[last_desc].nda = + dvma_addr + dbri_dma_off(desc, first_desc); + dbri->next_desc[last_desc] = first_desc; + dbri->pipes[info->pipe].first_desc = first_desc; + dbri->pipes[info->pipe].desc = first_desc; + +#ifdef DBRI_DEBUG + for (desc = first_desc; desc != -1;) { + dprintk(D_DESC, "DESC %d: %08x %08x %08x %08x\n", + desc, + dbri->dma->desc[desc].word1, + dbri->dma->desc[desc].ba, + dbri->dma->desc[desc].nda, dbri->dma->desc[desc].word4); + desc = dbri->next_desc[desc]; + if (desc == first_desc) + break; + } +#endif + return 0; +} + +/* +**************************************************************************** +************************** DBRI - CHI interface **************************** +**************************************************************************** + +The CHI is a four-wire (clock, frame sync, data in, data out) time-division +multiplexed serial interface which the DBRI can operate in either master +(give clock/frame sync) or slave (take clock/frame sync) mode. + +*/ + +enum master_or_slave { CHImaster, CHIslave }; + +/* + * Lock must not be held before calling it. + */ +static void reset_chi(struct snd_dbri *dbri, + enum master_or_slave master_or_slave, + int bits_per_frame) +{ + s32 *cmd; + int val; + + /* Set CHI Anchor: Pipe 16 */ + + cmd = dbri_cmdlock(dbri, 4); + val = D_DTS_VO | D_DTS_VI | D_DTS_INS + | D_DTS_PRVIN(16) | D_PIPE(16) | D_DTS_PRVOUT(16); + *(cmd++) = DBRI_CMD(D_DTS, 0, val); + *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16); + *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16); + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + dbri_cmdsend(dbri, cmd, 4); + + dbri->pipes[16].sdp = 1; + dbri->pipes[16].nextpipe = 16; + + cmd = dbri_cmdlock(dbri, 4); + + if (master_or_slave == CHIslave) { + /* Setup DBRI for CHI Slave - receive clock, frame sync (FS) + * + * CHICM = 0 (slave mode, 8 kHz frame rate) + * IR = give immediate CHI status interrupt + * EN = give CHI status interrupt upon change + */ + *(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(0)); + } else { + /* Setup DBRI for CHI Master - generate clock, FS + * + * BPF = bits per 8 kHz frame + * 12.288 MHz / CHICM_divisor = clock rate + * FD = 1 - drive CHIFS on rising edge of CHICK + */ + int clockrate = bits_per_frame * 8; + int divisor = 12288 / clockrate; + + if (divisor > 255 || divisor * clockrate != 12288) + printk(KERN_ERR "DBRI: illegal bits_per_frame " + "in setup_chi\n"); + + *(cmd++) = DBRI_CMD(D_CHI, 0, D_CHI_CHICM(divisor) | D_CHI_FD + | D_CHI_BPF(bits_per_frame)); + } + + dbri->chi_bpf = bits_per_frame; + + /* CHI Data Mode + * + * RCE = 0 - receive on falling edge of CHICK + * XCE = 1 - transmit on rising edge of CHICK + * XEN = 1 - enable transmitter + * REN = 1 - enable receiver + */ + + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + *(cmd++) = DBRI_CMD(D_CDM, 0, D_CDM_XCE | D_CDM_XEN | D_CDM_REN); + *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0); + + dbri_cmdsend(dbri, cmd, 4); +} + +/* +**************************************************************************** +*********************** CS4215 audio codec management ********************** +**************************************************************************** + +In the standard SPARC audio configuration, the CS4215 codec is attached +to the DBRI via the CHI interface and few of the DBRI's PIO pins. + + * Lock must not be held before calling it. + +*/ +static void cs4215_setup_pipes(struct snd_dbri *dbri) +{ + unsigned long flags; + + spin_lock_irqsave(&dbri->lock, flags); + /* + * Data mode: + * Pipe 4: Send timeslots 1-4 (audio data) + * Pipe 20: Send timeslots 5-8 (part of ctrl data) + * Pipe 6: Receive timeslots 1-4 (audio data) + * Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via + * interrupt, and the rest of the data (slot 5 and 8) is + * not relevant for us (only for doublechecking). + * + * Control mode: + * Pipe 17: Send timeslots 1-4 (slots 5-8 are read only) + * Pipe 18: Receive timeslot 1 (clb). + * Pipe 19: Receive timeslot 7 (version). + */ + + setup_pipe(dbri, 4, D_SDP_MEM | D_SDP_TO_SER | D_SDP_MSB); + setup_pipe(dbri, 20, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB); + setup_pipe(dbri, 6, D_SDP_MEM | D_SDP_FROM_SER | D_SDP_MSB); + setup_pipe(dbri, 21, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB); + + setup_pipe(dbri, 17, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB); + setup_pipe(dbri, 18, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB); + setup_pipe(dbri, 19, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB); + spin_unlock_irqrestore(&dbri->lock, flags); + + dbri_cmdwait(dbri); +} + +static int cs4215_init_data(struct cs4215 *mm) +{ + /* + * No action, memory resetting only. + * + * Data Time Slot 5-8 + * Speaker,Line and Headphone enable. Gain set to the half. + * Input is mike. + */ + mm->data[0] = CS4215_LO(0x20) | CS4215_HE | CS4215_LE; + mm->data[1] = CS4215_RO(0x20) | CS4215_SE; + mm->data[2] = CS4215_LG(0x8) | CS4215_IS | CS4215_PIO0 | CS4215_PIO1; + mm->data[3] = CS4215_RG(0x8) | CS4215_MA(0xf); + + /* + * Control Time Slot 1-4 + * 0: Default I/O voltage scale + * 1: 8 bit ulaw, 8kHz, mono, high pass filter disabled + * 2: Serial enable, CHI master, 128 bits per frame, clock 1 + * 3: Tests disabled + */ + mm->ctrl[0] = CS4215_RSRVD_1 | CS4215_MLB; + mm->ctrl[1] = CS4215_DFR_ULAW | CS4215_FREQ[0].csval; + mm->ctrl[2] = CS4215_XCLK | CS4215_BSEL_128 | CS4215_FREQ[0].xtal; + mm->ctrl[3] = 0; + + mm->status = 0; + mm->version = 0xff; + mm->precision = 8; /* For ULAW */ + mm->channels = 1; + + return 0; +} + +static void cs4215_setdata(struct snd_dbri *dbri, int muted) +{ + if (muted) { + dbri->mm.data[0] |= 63; + dbri->mm.data[1] |= 63; + dbri->mm.data[2] &= ~15; + dbri->mm.data[3] &= ~15; + } else { + /* Start by setting the playback attenuation. */ + struct dbri_streaminfo *info = &dbri->stream_info[DBRI_PLAY]; + int left_gain = info->left_gain & 0x3f; + int right_gain = info->right_gain & 0x3f; + + dbri->mm.data[0] &= ~0x3f; /* Reset the volume bits */ + dbri->mm.data[1] &= ~0x3f; + dbri->mm.data[0] |= (DBRI_MAX_VOLUME - left_gain); + dbri->mm.data[1] |= (DBRI_MAX_VOLUME - right_gain); + + /* Now set the recording gain. */ + info = &dbri->stream_info[DBRI_REC]; + left_gain = info->left_gain & 0xf; + right_gain = info->right_gain & 0xf; + dbri->mm.data[2] |= CS4215_LG(left_gain); + dbri->mm.data[3] |= CS4215_RG(right_gain); + } + + xmit_fixed(dbri, 20, *(int *)dbri->mm.data); +} + +/* + * Set the CS4215 to data mode. + */ +static void cs4215_open(struct snd_dbri *dbri) +{ + int data_width; + u32 tmp; + unsigned long flags; + + dprintk(D_MM, "cs4215_open: %d channels, %d bits\n", + dbri->mm.channels, dbri->mm.precision); + + /* Temporarily mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + + cs4215_setdata(dbri, 1); + udelay(125); + + /* + * Data mode: + * Pipe 4: Send timeslots 1-4 (audio data) + * Pipe 20: Send timeslots 5-8 (part of ctrl data) + * Pipe 6: Receive timeslots 1-4 (audio data) + * Pipe 21: Receive timeslots 6-7. We can only receive 20 bits via + * interrupt, and the rest of the data (slot 5 and 8) is + * not relevant for us (only for doublechecking). + * + * Just like in control mode, the time slots are all offset by eight + * bits. The CS4215, it seems, observes TSIN (the delayed signal) + * even if it's the CHI master. Don't ask me... + */ + spin_lock_irqsave(&dbri->lock, flags); + tmp = sbus_readl(dbri->regs + REG0); + tmp &= ~(D_C); /* Disable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + + /* Switch CS4215 to data mode - set PIO3 to 1 */ + sbus_writel(D_ENPIO | D_PIO1 | D_PIO3 | + (dbri->mm.onboard ? D_PIO0 : D_PIO2), dbri->regs + REG2); + + reset_chi(dbri, CHIslave, 128); + + /* Note: this next doesn't work for 8-bit stereo, because the two + * channels would be on timeslots 1 and 3, with 2 and 4 idle. + * (See CS4215 datasheet Fig 15) + * + * DBRI non-contiguous mode would be required to make this work. + */ + data_width = dbri->mm.channels * dbri->mm.precision; + + link_time_slot(dbri, 4, 16, 16, data_width, dbri->mm.offset); + link_time_slot(dbri, 20, 4, 16, 32, dbri->mm.offset + 32); + link_time_slot(dbri, 6, 16, 16, data_width, dbri->mm.offset); + link_time_slot(dbri, 21, 6, 16, 16, dbri->mm.offset + 40); + + /* FIXME: enable CHI after _setdata? */ + tmp = sbus_readl(dbri->regs + REG0); + tmp |= D_C; /* Enable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + spin_unlock_irqrestore(&dbri->lock, flags); + + cs4215_setdata(dbri, 0); +} + +/* + * Send the control information (i.e. audio format) + */ +static int cs4215_setctrl(struct snd_dbri *dbri) +{ + int i, val; + u32 tmp; + unsigned long flags; + + /* FIXME - let the CPU do something useful during these delays */ + + /* Temporarily mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + cs4215_setdata(dbri, 1); + udelay(125); + + /* + * Enable Control mode: Set DBRI's PIO3 (4215's D/~C) to 0, then wait + * 12 cycles <= 12/(5512.5*64) sec = 34.01 usec + */ + val = D_ENPIO | D_PIO1 | (dbri->mm.onboard ? D_PIO0 : D_PIO2); + sbus_writel(val, dbri->regs + REG2); + dprintk(D_MM, "cs4215_setctrl: reg2=0x%x\n", val); + udelay(34); + + /* In Control mode, the CS4215 is a slave device, so the DBRI must + * operate as CHI master, supplying clocking and frame synchronization. + * + * In Data mode, however, the CS4215 must be CHI master to insure + * that its data stream is synchronous with its codec. + * + * The upshot of all this? We start by putting the DBRI into master + * mode, program the CS4215 in Control mode, then switch the CS4215 + * into Data mode and put the DBRI into slave mode. Various timing + * requirements must be observed along the way. + * + * Oh, and one more thing, on a SPARCStation 20 (and maybe + * others?), the addressing of the CS4215's time slots is + * offset by eight bits, so we add eight to all the "cycle" + * values in the Define Time Slot (DTS) commands. This is + * done in hardware by a TI 248 that delays the DBRI->4215 + * frame sync signal by eight clock cycles. Anybody know why? + */ + spin_lock_irqsave(&dbri->lock, flags); + tmp = sbus_readl(dbri->regs + REG0); + tmp &= ~D_C; /* Disable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + + reset_chi(dbri, CHImaster, 128); + + /* + * Control mode: + * Pipe 17: Send timeslots 1-4 (slots 5-8 are read only) + * Pipe 18: Receive timeslot 1 (clb). + * Pipe 19: Receive timeslot 7 (version). + */ + + link_time_slot(dbri, 17, 16, 16, 32, dbri->mm.offset); + link_time_slot(dbri, 18, 16, 16, 8, dbri->mm.offset); + link_time_slot(dbri, 19, 18, 16, 8, dbri->mm.offset + 48); + spin_unlock_irqrestore(&dbri->lock, flags); + + /* Wait for the chip to echo back CLB (Control Latch Bit) as zero */ + dbri->mm.ctrl[0] &= ~CS4215_CLB; + xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl); + + spin_lock_irqsave(&dbri->lock, flags); + tmp = sbus_readl(dbri->regs + REG0); + tmp |= D_C; /* Enable CHI */ + sbus_writel(tmp, dbri->regs + REG0); + spin_unlock_irqrestore(&dbri->lock, flags); + + for (i = 10; ((dbri->mm.status & 0xe4) != 0x20); --i) + msleep_interruptible(1); + + if (i == 0) { + dprintk(D_MM, "CS4215 didn't respond to CLB (0x%02x)\n", + dbri->mm.status); + return -1; + } + + /* Disable changes to our copy of the version number, as we are about + * to leave control mode. + */ + recv_fixed(dbri, 19, NULL); + + /* Terminate CS4215 control mode - data sheet says + * "Set CLB=1 and send two more frames of valid control info" + */ + dbri->mm.ctrl[0] |= CS4215_CLB; + xmit_fixed(dbri, 17, *(int *)dbri->mm.ctrl); + + /* Two frames of control info @ 8kHz frame rate = 250 us delay */ + udelay(250); + + cs4215_setdata(dbri, 0); + + return 0; +} + +/* + * Setup the codec with the sampling rate, audio format and number of + * channels. + * As part of the process we resend the settings for the data + * timeslots as well. + */ +static int cs4215_prepare(struct snd_dbri *dbri, unsigned int rate, + snd_pcm_format_t format, unsigned int channels) +{ + int freq_idx; + int ret = 0; + + /* Lookup index for this rate */ + for (freq_idx = 0; CS4215_FREQ[freq_idx].freq != 0; freq_idx++) { + if (CS4215_FREQ[freq_idx].freq == rate) + break; + } + if (CS4215_FREQ[freq_idx].freq != rate) { + printk(KERN_WARNING "DBRI: Unsupported rate %d Hz\n", rate); + return -1; + } + + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + dbri->mm.ctrl[1] = CS4215_DFR_ULAW; + dbri->mm.precision = 8; + break; + case SNDRV_PCM_FORMAT_A_LAW: + dbri->mm.ctrl[1] = CS4215_DFR_ALAW; + dbri->mm.precision = 8; + break; + case SNDRV_PCM_FORMAT_U8: + dbri->mm.ctrl[1] = CS4215_DFR_LINEAR8; + dbri->mm.precision = 8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + dbri->mm.ctrl[1] = CS4215_DFR_LINEAR16; + dbri->mm.precision = 16; + break; + default: + printk(KERN_WARNING "DBRI: Unsupported format %d\n", format); + return -1; + } + + /* Add rate parameters */ + dbri->mm.ctrl[1] |= CS4215_FREQ[freq_idx].csval; + dbri->mm.ctrl[2] = CS4215_XCLK | + CS4215_BSEL_128 | CS4215_FREQ[freq_idx].xtal; + + dbri->mm.channels = channels; + if (channels == 2) + dbri->mm.ctrl[1] |= CS4215_DFR_STEREO; + + ret = cs4215_setctrl(dbri); + if (ret == 0) + cs4215_open(dbri); /* set codec to data mode */ + + return ret; +} + +/* + * + */ +static int cs4215_init(struct snd_dbri *dbri) +{ + u32 reg2 = sbus_readl(dbri->regs + REG2); + dprintk(D_MM, "cs4215_init: reg2=0x%x\n", reg2); + + /* Look for the cs4215 chips */ + if (reg2 & D_PIO2) { + dprintk(D_MM, "Onboard CS4215 detected\n"); + dbri->mm.onboard = 1; + } + if (reg2 & D_PIO0) { + dprintk(D_MM, "Speakerbox detected\n"); + dbri->mm.onboard = 0; + + if (reg2 & D_PIO2) { + printk(KERN_INFO "DBRI: Using speakerbox / " + "ignoring onboard mmcodec.\n"); + sbus_writel(D_ENPIO2, dbri->regs + REG2); + } + } + + if (!(reg2 & (D_PIO0 | D_PIO2))) { + printk(KERN_ERR "DBRI: no mmcodec found.\n"); + return -EIO; + } + + cs4215_setup_pipes(dbri); + cs4215_init_data(&dbri->mm); + + /* Enable capture of the status & version timeslots. */ + recv_fixed(dbri, 18, &dbri->mm.status); + recv_fixed(dbri, 19, &dbri->mm.version); + + dbri->mm.offset = dbri->mm.onboard ? 0 : 8; + if (cs4215_setctrl(dbri) == -1 || dbri->mm.version == 0xff) { + dprintk(D_MM, "CS4215 failed probe at offset %d\n", + dbri->mm.offset); + return -EIO; + } + dprintk(D_MM, "Found CS4215 at offset %d\n", dbri->mm.offset); + + return 0; +} + +/* +**************************************************************************** +*************************** DBRI interrupt handler ************************* +**************************************************************************** + +The DBRI communicates with the CPU mainly via a circular interrupt +buffer. When an interrupt is signaled, the CPU walks through the +buffer and calls dbri_process_one_interrupt() for each interrupt word. +Complicated interrupts are handled by dedicated functions (which +appear first in this file). Any pending interrupts can be serviced by +calling dbri_process_interrupt_buffer(), which works even if the CPU's +interrupts are disabled. + +*/ + +/* xmit_descs() + * + * Starts transmitting the current TD's for recording/playing. + * For playback, ALSA has filled the DMA memory with new data (we hope). + */ +static void xmit_descs(struct snd_dbri *dbri) +{ + struct dbri_streaminfo *info; + u32 dvma_addr; + s32 *cmd; + unsigned long flags; + int first_td; + + if (dbri == NULL) + return; /* Disabled */ + + dvma_addr = (u32)dbri->dma_dvma; + info = &dbri->stream_info[DBRI_REC]; + spin_lock_irqsave(&dbri->lock, flags); + + if (info->pipe >= 0) { + first_td = dbri->pipes[info->pipe].first_desc; + + dprintk(D_DESC, "xmit_descs rec @ TD %d\n", first_td); + + /* Stream could be closed by the time we run. */ + if (first_td >= 0) { + cmd = dbri_cmdlock(dbri, 2); + *(cmd++) = DBRI_CMD(D_SDP, 0, + dbri->pipes[info->pipe].sdp + | D_SDP_P | D_SDP_EVERY | D_SDP_C); + *(cmd++) = dvma_addr + + dbri_dma_off(desc, first_td); + dbri_cmdsend(dbri, cmd, 2); + + /* Reset our admin of the pipe. */ + dbri->pipes[info->pipe].desc = first_td; + } + } + + info = &dbri->stream_info[DBRI_PLAY]; + + if (info->pipe >= 0) { + first_td = dbri->pipes[info->pipe].first_desc; + + dprintk(D_DESC, "xmit_descs play @ TD %d\n", first_td); + + /* Stream could be closed by the time we run. */ + if (first_td >= 0) { + cmd = dbri_cmdlock(dbri, 2); + *(cmd++) = DBRI_CMD(D_SDP, 0, + dbri->pipes[info->pipe].sdp + | D_SDP_P | D_SDP_EVERY | D_SDP_C); + *(cmd++) = dvma_addr + + dbri_dma_off(desc, first_td); + dbri_cmdsend(dbri, cmd, 2); + + /* Reset our admin of the pipe. */ + dbri->pipes[info->pipe].desc = first_td; + } + } + + spin_unlock_irqrestore(&dbri->lock, flags); +} + +/* transmission_complete_intr() + * + * Called by main interrupt handler when DBRI signals transmission complete + * on a pipe (interrupt triggered by the B bit in a transmit descriptor). + * + * Walks through the pipe's list of transmit buffer descriptors and marks + * them as available. Stops when the first descriptor is found without + * TBC (Transmit Buffer Complete) set, or we've run through them all. + * + * The DMA buffers are not released. They form a ring buffer and + * they are filled by ALSA while others are transmitted by DMA. + * + */ + +static void transmission_complete_intr(struct snd_dbri *dbri, int pipe) +{ + struct dbri_streaminfo *info = &dbri->stream_info[DBRI_PLAY]; + int td = dbri->pipes[pipe].desc; + int status; + + while (td >= 0) { + if (td >= DBRI_NO_DESCS) { + printk(KERN_ERR "DBRI: invalid td on pipe %d\n", pipe); + return; + } + + status = DBRI_TD_STATUS(dbri->dma->desc[td].word4); + if (!(status & DBRI_TD_TBC)) + break; + + dprintk(D_INT, "TD %d, status 0x%02x\n", td, status); + + dbri->dma->desc[td].word4 = 0; /* Reset it for next time. */ + info->offset += DBRI_RD_CNT(dbri->dma->desc[td].word1); + + td = dbri->next_desc[td]; + dbri->pipes[pipe].desc = td; + } + + /* Notify ALSA */ + spin_unlock(&dbri->lock); + snd_pcm_period_elapsed(info->substream); + spin_lock(&dbri->lock); +} + +static void reception_complete_intr(struct snd_dbri *dbri, int pipe) +{ + struct dbri_streaminfo *info; + int rd = dbri->pipes[pipe].desc; + s32 status; + + if (rd < 0 || rd >= DBRI_NO_DESCS) { + printk(KERN_ERR "DBRI: invalid rd on pipe %d\n", pipe); + return; + } + + dbri->pipes[pipe].desc = dbri->next_desc[rd]; + status = dbri->dma->desc[rd].word1; + dbri->dma->desc[rd].word1 = 0; /* Reset it for next time. */ + + info = &dbri->stream_info[DBRI_REC]; + info->offset += DBRI_RD_CNT(status); + + /* FIXME: Check status */ + + dprintk(D_INT, "Recv RD %d, status 0x%02x, len %d\n", + rd, DBRI_RD_STATUS(status), DBRI_RD_CNT(status)); + + /* Notify ALSA */ + spin_unlock(&dbri->lock); + snd_pcm_period_elapsed(info->substream); + spin_lock(&dbri->lock); +} + +static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) +{ + int val = D_INTR_GETVAL(x); + int channel = D_INTR_GETCHAN(x); + int command = D_INTR_GETCMD(x); + int code = D_INTR_GETCODE(x); +#ifdef DBRI_DEBUG + int rval = D_INTR_GETRVAL(x); +#endif + + if (channel == D_INTR_CMD) { + dprintk(D_CMD, "INTR: Command: %-5s Value:%d\n", + cmds[command], val); + } else { + dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n", + channel, code, rval); + } + + switch (code) { + case D_INTR_CMDI: + if (command != D_WAIT) + printk(KERN_ERR "DBRI: Command read interrupt\n"); + break; + case D_INTR_BRDY: + reception_complete_intr(dbri, channel); + break; + case D_INTR_XCMP: + case D_INTR_MINT: + transmission_complete_intr(dbri, channel); + break; + case D_INTR_UNDR: + /* UNDR - Transmission underrun + * resend SDP command with clear pipe bit (C) set + */ + { + /* FIXME: do something useful in case of underrun */ + printk(KERN_ERR "DBRI: Underrun error\n"); +#if 0 + s32 *cmd; + int pipe = channel; + int td = dbri->pipes[pipe].desc; + + dbri->dma->desc[td].word4 = 0; + cmd = dbri_cmdlock(dbri, NoGetLock); + *(cmd++) = DBRI_CMD(D_SDP, 0, + dbri->pipes[pipe].sdp + | D_SDP_P | D_SDP_C | D_SDP_2SAME); + *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, td); + dbri_cmdsend(dbri, cmd); +#endif + } + break; + case D_INTR_FXDT: + /* FXDT - Fixed data change */ + if (dbri->pipes[channel].sdp & D_SDP_MSB) + val = reverse_bytes(val, dbri->pipes[channel].length); + + if (dbri->pipes[channel].recv_fixed_ptr) + *(dbri->pipes[channel].recv_fixed_ptr) = val; + break; + default: + if (channel != D_INTR_CMD) + printk(KERN_WARNING + "DBRI: Ignored Interrupt: %d (0x%x)\n", code, x); + } +} + +/* dbri_process_interrupt_buffer advances through the DBRI's interrupt + * buffer until it finds a zero word (indicating nothing more to do + * right now). Non-zero words require processing and are handed off + * to dbri_process_one_interrupt AFTER advancing the pointer. + */ +static void dbri_process_interrupt_buffer(struct snd_dbri *dbri) +{ + s32 x; + + while ((x = dbri->dma->intr[dbri->dbri_irqp]) != 0) { + dbri->dma->intr[dbri->dbri_irqp] = 0; + dbri->dbri_irqp++; + if (dbri->dbri_irqp == DBRI_INT_BLK) + dbri->dbri_irqp = 1; + + dbri_process_one_interrupt(dbri, x); + } +} + +static irqreturn_t snd_dbri_interrupt(int irq, void *dev_id) +{ + struct snd_dbri *dbri = dev_id; + static int errcnt = 0; + int x; + + if (dbri == NULL) + return IRQ_NONE; + spin_lock(&dbri->lock); + + /* + * Read it, so the interrupt goes away. + */ + x = sbus_readl(dbri->regs + REG1); + + if (x & (D_MRR | D_MLE | D_LBG | D_MBE)) { + u32 tmp; + + if (x & D_MRR) + printk(KERN_ERR + "DBRI: Multiple Error Ack on SBus reg1=0x%x\n", + x); + if (x & D_MLE) + printk(KERN_ERR + "DBRI: Multiple Late Error on SBus reg1=0x%x\n", + x); + if (x & D_LBG) + printk(KERN_ERR + "DBRI: Lost Bus Grant on SBus reg1=0x%x\n", x); + if (x & D_MBE) + printk(KERN_ERR + "DBRI: Burst Error on SBus reg1=0x%x\n", x); + + /* Some of these SBus errors cause the chip's SBus circuitry + * to be disabled, so just re-enable and try to keep going. + * + * The only one I've seen is MRR, which will be triggered + * if you let a transmit pipe underrun, then try to CDP it. + * + * If these things persist, we reset the chip. + */ + if ((++errcnt) % 10 == 0) { + dprintk(D_INT, "Interrupt errors exceeded.\n"); + dbri_reset(dbri); + } else { + tmp = sbus_readl(dbri->regs + REG0); + tmp &= ~(D_D); + sbus_writel(tmp, dbri->regs + REG0); + } + } + + dbri_process_interrupt_buffer(dbri); + + spin_unlock(&dbri->lock); + + return IRQ_HANDLED; +} + +/**************************************************************************** + PCM Interface +****************************************************************************/ +static const struct snd_pcm_hardware snd_dbri_pcm_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_MU_LAW | + SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_5512, + .rate_min = 5512, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 1, + .period_bytes_max = DBRI_TD_MAXCNT, + .periods_min = 1, + .periods_max = 1024, +}; + +static int snd_hw_rule_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_mask fmt; + + snd_mask_any(&fmt); + if (c->min > 1) { + fmt.bits[0] &= SNDRV_PCM_FMTBIT_S16_BE; + return snd_mask_refine(f, &fmt); + } + return 0; +} + +static int snd_hw_rule_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_interval ch; + + snd_interval_any(&ch); + if (!(f->bits[0] & SNDRV_PCM_FMTBIT_S16_BE)) { + ch.min = 1; + ch.max = 1; + ch.integer = 1; + return snd_interval_refine(c, &ch); + } + return 0; +} + +static int snd_dbri_open(struct snd_pcm_substream *substream) +{ + struct snd_dbri *dbri = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream); + unsigned long flags; + + dprintk(D_USR, "open audio output.\n"); + runtime->hw = snd_dbri_pcm_hw; + + spin_lock_irqsave(&dbri->lock, flags); + info->substream = substream; + info->offset = 0; + info->dvma_buffer = 0; + info->pipe = -1; + spin_unlock_irqrestore(&dbri->lock, flags); + + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_hw_rule_format, NULL, SNDRV_PCM_HW_PARAM_FORMAT, + -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + snd_hw_rule_channels, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, + -1); + + cs4215_open(dbri); + + return 0; +} + +static int snd_dbri_close(struct snd_pcm_substream *substream) +{ + struct snd_dbri *dbri = snd_pcm_substream_chip(substream); + struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream); + + dprintk(D_USR, "close audio output.\n"); + info->substream = NULL; + info->offset = 0; + + return 0; +} + +static int snd_dbri_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dbri *dbri = snd_pcm_substream_chip(substream); + struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream); + int direction; + int ret; + + /* set sampling rate, audio format and number of channels */ + ret = cs4215_prepare(dbri, params_rate(hw_params), + params_format(hw_params), + params_channels(hw_params)); + if (ret != 0) + return ret; + + if ((ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params))) < 0) { + printk(KERN_ERR "malloc_pages failed with %d\n", ret); + return ret; + } + + /* hw_params can get called multiple times. Only map the DMA once. + */ + if (info->dvma_buffer == 0) { + if (DBRI_STREAMNO(substream) == DBRI_PLAY) + direction = DMA_TO_DEVICE; + else + direction = DMA_FROM_DEVICE; + + info->dvma_buffer = + dma_map_single(&dbri->op->dev, + runtime->dma_area, + params_buffer_bytes(hw_params), + direction); + } + + direction = params_buffer_bytes(hw_params); + dprintk(D_USR, "hw_params: %d bytes, dvma=%x\n", + direction, info->dvma_buffer); + return 0; +} + +static int snd_dbri_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dbri *dbri = snd_pcm_substream_chip(substream); + struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream); + int direction; + + dprintk(D_USR, "hw_free.\n"); + + /* hw_free can get called multiple times. Only unmap the DMA once. + */ + if (info->dvma_buffer) { + if (DBRI_STREAMNO(substream) == DBRI_PLAY) + direction = DMA_TO_DEVICE; + else + direction = DMA_FROM_DEVICE; + + dma_unmap_single(&dbri->op->dev, info->dvma_buffer, + substream->runtime->buffer_size, direction); + info->dvma_buffer = 0; + } + if (info->pipe != -1) { + reset_pipe(dbri, info->pipe); + info->pipe = -1; + } + + return snd_pcm_lib_free_pages(substream); +} + +static int snd_dbri_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dbri *dbri = snd_pcm_substream_chip(substream); + struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream); + int ret; + + info->size = snd_pcm_lib_buffer_bytes(substream); + if (DBRI_STREAMNO(substream) == DBRI_PLAY) + info->pipe = 4; /* Send pipe */ + else + info->pipe = 6; /* Receive pipe */ + + spin_lock_irq(&dbri->lock); + info->offset = 0; + + /* Setup the all the transmit/receive descriptors to cover the + * whole DMA buffer. + */ + ret = setup_descs(dbri, DBRI_STREAMNO(substream), + snd_pcm_lib_period_bytes(substream)); + + spin_unlock_irq(&dbri->lock); + + dprintk(D_USR, "prepare audio output. %d bytes\n", info->size); + return ret; +} + +static int snd_dbri_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dbri *dbri = snd_pcm_substream_chip(substream); + struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dprintk(D_USR, "start audio, period is %d bytes\n", + (int)snd_pcm_lib_period_bytes(substream)); + /* Re-submit the TDs. */ + xmit_descs(dbri); + break; + case SNDRV_PCM_TRIGGER_STOP: + dprintk(D_USR, "stop audio.\n"); + reset_pipe(dbri, info->pipe); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t snd_dbri_pointer(struct snd_pcm_substream *substream) +{ + struct snd_dbri *dbri = snd_pcm_substream_chip(substream); + struct dbri_streaminfo *info = DBRI_STREAM(dbri, substream); + snd_pcm_uframes_t ret; + + ret = bytes_to_frames(substream->runtime, info->offset) + % substream->runtime->buffer_size; + dprintk(D_USR, "I/O pointer: %ld frames of %ld.\n", + ret, substream->runtime->buffer_size); + return ret; +} + +static const struct snd_pcm_ops snd_dbri_ops = { + .open = snd_dbri_open, + .close = snd_dbri_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_dbri_hw_params, + .hw_free = snd_dbri_hw_free, + .prepare = snd_dbri_prepare, + .trigger = snd_dbri_trigger, + .pointer = snd_dbri_pointer, +}; + +static int snd_dbri_pcm(struct snd_card *card) +{ + struct snd_pcm *pcm; + int err; + + if ((err = snd_pcm_new(card, + /* ID */ "sun_dbri", + /* device */ 0, + /* playback count */ 1, + /* capture count */ 1, &pcm)) < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_dbri_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_dbri_ops); + + pcm->private_data = card->private_data; + pcm->info_flags = 0; + strcpy(pcm->name, card->shortname); + + if ((err = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64 * 1024, 64 * 1024)) < 0) + return err; + + return 0; +} + +/***************************************************************************** + Mixer interface +*****************************************************************************/ + +static int snd_cs4215_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; + if (kcontrol->private_value == DBRI_PLAY) + uinfo->value.integer.max = DBRI_MAX_VOLUME; + else + uinfo->value.integer.max = DBRI_MAX_GAIN; + return 0; +} + +static int snd_cs4215_get_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol); + struct dbri_streaminfo *info; + + if (snd_BUG_ON(!dbri)) + return -EINVAL; + info = &dbri->stream_info[kcontrol->private_value]; + + ucontrol->value.integer.value[0] = info->left_gain; + ucontrol->value.integer.value[1] = info->right_gain; + return 0; +} + +static int snd_cs4215_put_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol); + struct dbri_streaminfo *info = + &dbri->stream_info[kcontrol->private_value]; + unsigned int vol[2]; + int changed = 0; + + vol[0] = ucontrol->value.integer.value[0]; + vol[1] = ucontrol->value.integer.value[1]; + if (kcontrol->private_value == DBRI_PLAY) { + if (vol[0] > DBRI_MAX_VOLUME || vol[1] > DBRI_MAX_VOLUME) + return -EINVAL; + } else { + if (vol[0] > DBRI_MAX_GAIN || vol[1] > DBRI_MAX_GAIN) + return -EINVAL; + } + + if (info->left_gain != vol[0]) { + info->left_gain = vol[0]; + changed = 1; + } + if (info->right_gain != vol[1]) { + info->right_gain = vol[1]; + changed = 1; + } + if (changed) { + /* First mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + cs4215_setdata(dbri, 1); + udelay(125); + cs4215_setdata(dbri, 0); + } + return changed; +} + +static int snd_cs4215_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_cs4215_get_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol); + int elem = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 1; + + if (snd_BUG_ON(!dbri)) + return -EINVAL; + + if (elem < 4) + ucontrol->value.integer.value[0] = + (dbri->mm.data[elem] >> shift) & mask; + else + ucontrol->value.integer.value[0] = + (dbri->mm.ctrl[elem - 4] >> shift) & mask; + + if (invert == 1) + ucontrol->value.integer.value[0] = + mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_cs4215_put_single(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_dbri *dbri = snd_kcontrol_chip(kcontrol); + int elem = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 1; + int changed = 0; + unsigned short val; + + if (snd_BUG_ON(!dbri)) + return -EINVAL; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert == 1) + val = mask - val; + val <<= shift; + + if (elem < 4) { + dbri->mm.data[elem] = (dbri->mm.data[elem] & + ~(mask << shift)) | val; + changed = (val != dbri->mm.data[elem]); + } else { + dbri->mm.ctrl[elem - 4] = (dbri->mm.ctrl[elem - 4] & + ~(mask << shift)) | val; + changed = (val != dbri->mm.ctrl[elem - 4]); + } + + dprintk(D_GEN, "put_single: mask=0x%x, changed=%d, " + "mixer-value=%ld, mm-value=0x%x\n", + mask, changed, ucontrol->value.integer.value[0], + dbri->mm.data[elem & 3]); + + if (changed) { + /* First mute outputs, and wait 1/8000 sec (125 us) + * to make sure this takes. This avoids clicking noises. + */ + cs4215_setdata(dbri, 1); + udelay(125); + cs4215_setdata(dbri, 0); + } + return changed; +} + +/* Entries 0-3 map to the 4 data timeslots, entries 4-7 map to the 4 control + timeslots. Shift is the bit offset in the timeslot, mask defines the + number of bits. invert is a boolean for use with attenuation. + */ +#define CS4215_SINGLE(xname, entry, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = snd_cs4215_info_single, \ + .get = snd_cs4215_get_single, .put = snd_cs4215_put_single, \ + .private_value = (entry) | ((shift) << 8) | ((mask) << 16) | \ + ((invert) << 24) }, + +static struct snd_kcontrol_new dbri_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Playback Volume", + .info = snd_cs4215_info_volume, + .get = snd_cs4215_get_volume, + .put = snd_cs4215_put_volume, + .private_value = DBRI_PLAY, + }, + CS4215_SINGLE("Headphone switch", 0, 7, 1, 0) + CS4215_SINGLE("Line out switch", 0, 6, 1, 0) + CS4215_SINGLE("Speaker switch", 1, 6, 1, 0) + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .info = snd_cs4215_info_volume, + .get = snd_cs4215_get_volume, + .put = snd_cs4215_put_volume, + .private_value = DBRI_REC, + }, + /* FIXME: mic/line switch */ + CS4215_SINGLE("Line in switch", 2, 4, 1, 0) + CS4215_SINGLE("High Pass Filter switch", 5, 7, 1, 0) + CS4215_SINGLE("Monitor Volume", 3, 4, 0xf, 1) + CS4215_SINGLE("Mic boost", 4, 4, 1, 1) +}; + +static int snd_dbri_mixer(struct snd_card *card) +{ + int idx, err; + struct snd_dbri *dbri; + + if (snd_BUG_ON(!card || !card->private_data)) + return -EINVAL; + dbri = card->private_data; + + strcpy(card->mixername, card->shortname); + + for (idx = 0; idx < ARRAY_SIZE(dbri_controls); idx++) { + err = snd_ctl_add(card, + snd_ctl_new1(&dbri_controls[idx], dbri)); + if (err < 0) + return err; + } + + for (idx = DBRI_REC; idx < DBRI_NO_STREAMS; idx++) { + dbri->stream_info[idx].left_gain = 0; + dbri->stream_info[idx].right_gain = 0; + } + + return 0; +} + +/**************************************************************************** + /proc interface +****************************************************************************/ +static void dbri_regs_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_dbri *dbri = entry->private_data; + + snd_iprintf(buffer, "REG0: 0x%x\n", sbus_readl(dbri->regs + REG0)); + snd_iprintf(buffer, "REG2: 0x%x\n", sbus_readl(dbri->regs + REG2)); + snd_iprintf(buffer, "REG8: 0x%x\n", sbus_readl(dbri->regs + REG8)); + snd_iprintf(buffer, "REG9: 0x%x\n", sbus_readl(dbri->regs + REG9)); +} + +#ifdef DBRI_DEBUG +static void dbri_debug_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_dbri *dbri = entry->private_data; + int pipe; + snd_iprintf(buffer, "debug=%d\n", dbri_debug); + + for (pipe = 0; pipe < 32; pipe++) { + if (pipe_active(dbri, pipe)) { + struct dbri_pipe *pptr = &dbri->pipes[pipe]; + snd_iprintf(buffer, + "Pipe %d: %s SDP=0x%x desc=%d, " + "len=%d next %d\n", + pipe, + (pptr->sdp & D_SDP_TO_SER) ? "output" : + "input", + pptr->sdp, pptr->desc, + pptr->length, pptr->nextpipe); + } + } +} +#endif + +static void snd_dbri_proc(struct snd_card *card) +{ + struct snd_dbri *dbri = card->private_data; + struct snd_info_entry *entry; + + if (!snd_card_proc_new(card, "regs", &entry)) + snd_info_set_text_ops(entry, dbri, dbri_regs_read); + +#ifdef DBRI_DEBUG + if (!snd_card_proc_new(card, "debug", &entry)) { + snd_info_set_text_ops(entry, dbri, dbri_debug_read); + entry->mode = S_IFREG | 0444; /* Readable only. */ + } +#endif +} + +/* +**************************************************************************** +**************************** Initialization ******************************** +**************************************************************************** +*/ +static void snd_dbri_free(struct snd_dbri *dbri); + +static int snd_dbri_create(struct snd_card *card, + struct platform_device *op, + int irq, int dev) +{ + struct snd_dbri *dbri = card->private_data; + int err; + + spin_lock_init(&dbri->lock); + dbri->op = op; + dbri->irq = irq; + + dbri->dma = dma_zalloc_coherent(&op->dev, sizeof(struct dbri_dma), + &dbri->dma_dvma, GFP_KERNEL); + if (!dbri->dma) + return -ENOMEM; + + dprintk(D_GEN, "DMA Cmd Block 0x%p (%pad)\n", + dbri->dma, dbri->dma_dvma); + + /* Map the registers into memory. */ + dbri->regs_size = resource_size(&op->resource[0]); + dbri->regs = of_ioremap(&op->resource[0], 0, + dbri->regs_size, "DBRI Registers"); + if (!dbri->regs) { + printk(KERN_ERR "DBRI: could not allocate registers\n"); + dma_free_coherent(&op->dev, sizeof(struct dbri_dma), + (void *)dbri->dma, dbri->dma_dvma); + return -EIO; + } + + err = request_irq(dbri->irq, snd_dbri_interrupt, IRQF_SHARED, + "DBRI audio", dbri); + if (err) { + printk(KERN_ERR "DBRI: Can't get irq %d\n", dbri->irq); + of_iounmap(&op->resource[0], dbri->regs, dbri->regs_size); + dma_free_coherent(&op->dev, sizeof(struct dbri_dma), + (void *)dbri->dma, dbri->dma_dvma); + return err; + } + + /* Do low level initialization of the DBRI and CS4215 chips */ + dbri_initialize(dbri); + err = cs4215_init(dbri); + if (err) { + snd_dbri_free(dbri); + return err; + } + + return 0; +} + +static void snd_dbri_free(struct snd_dbri *dbri) +{ + dprintk(D_GEN, "snd_dbri_free\n"); + dbri_reset(dbri); + + if (dbri->irq) + free_irq(dbri->irq, dbri); + + if (dbri->regs) + of_iounmap(&dbri->op->resource[0], dbri->regs, dbri->regs_size); + + if (dbri->dma) + dma_free_coherent(&dbri->op->dev, + sizeof(struct dbri_dma), + (void *)dbri->dma, dbri->dma_dvma); +} + +static int dbri_probe(struct platform_device *op) +{ + struct snd_dbri *dbri; + struct resource *rp; + struct snd_card *card; + static int dev = 0; + int irq; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + irq = op->archdata.irqs[0]; + if (irq <= 0) { + printk(KERN_ERR "DBRI-%d: No IRQ.\n", dev); + return -ENODEV; + } + + err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_dbri), &card); + if (err < 0) + return err; + + strcpy(card->driver, "DBRI"); + strcpy(card->shortname, "Sun DBRI"); + rp = &op->resource[0]; + sprintf(card->longname, "%s at 0x%02lx:0x%016Lx, irq %d", + card->shortname, + rp->flags & 0xffL, (unsigned long long)rp->start, irq); + + err = snd_dbri_create(card, op, irq, dev); + if (err < 0) { + snd_card_free(card); + return err; + } + + dbri = card->private_data; + err = snd_dbri_pcm(card); + if (err < 0) + goto _err; + + err = snd_dbri_mixer(card); + if (err < 0) + goto _err; + + /* /proc file handling */ + snd_dbri_proc(card); + dev_set_drvdata(&op->dev, card); + + err = snd_card_register(card); + if (err < 0) + goto _err; + + printk(KERN_INFO "audio%d at %p (irq %d) is DBRI(%c)+CS4215(%d)\n", + dev, dbri->regs, + dbri->irq, op->dev.of_node->name[9], dbri->mm.version); + dev++; + + return 0; + +_err: + snd_dbri_free(dbri); + snd_card_free(card); + return err; +} + +static int dbri_remove(struct platform_device *op) +{ + struct snd_card *card = dev_get_drvdata(&op->dev); + + snd_dbri_free(card->private_data); + snd_card_free(card); + + return 0; +} + +static const struct of_device_id dbri_match[] = { + { + .name = "SUNW,DBRIe", + }, + { + .name = "SUNW,DBRIf", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dbri_match); + +static struct platform_driver dbri_sbus_driver = { + .driver = { + .name = "dbri", + .of_match_table = dbri_match, + }, + .probe = dbri_probe, + .remove = dbri_remove, +}; + +module_platform_driver(dbri_sbus_driver); diff --git a/sound/spi/Kconfig b/sound/spi/Kconfig new file mode 100644 index 000000000..e6485be2e --- /dev/null +++ b/sound/spi/Kconfig @@ -0,0 +1,38 @@ +#SPI drivers + +menuconfig SND_SPI + bool "SPI sound devices" + depends on SPI + default y + help + Support for sound devices connected via the SPI bus. + +if SND_SPI + +config SND_AT73C213 + tristate "Atmel AT73C213 DAC driver" + depends on ATMEL_SSC + select SND_PCM + help + Say Y here if you want to use the Atmel AT73C213 external DAC. This + DAC can be found on Atmel development boards. + + This driver requires the Atmel SSC driver for sound sink, a + peripheral found on most AT91 and AVR32 microprocessors. + + To compile this driver as a module, choose M here: the module will be + called snd-at73c213. + +config SND_AT73C213_TARGET_BITRATE + int "Target bitrate for AT73C213" + depends on SND_AT73C213 + default "48000" + range 8000 50000 + help + Sets the target bitrate for the bitrate calculator in the driver. + Limited by hardware to be between 8000 Hz and 50000 Hz. + + Set to 48000 Hz by default. + +endif # SND_SPI + diff --git a/sound/spi/Makefile b/sound/spi/Makefile new file mode 100644 index 000000000..a3834919b --- /dev/null +++ b/sound/spi/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for SPI drivers + +snd-at73c213-objs := at73c213.o + +obj-$(CONFIG_SND_AT73C213) += snd-at73c213.o diff --git a/sound/spi/at73c213.c b/sound/spi/at73c213.c new file mode 100644 index 000000000..3763f06ed --- /dev/null +++ b/sound/spi/at73c213.c @@ -0,0 +1,1146 @@ +/* + * Driver for AT73C213 16-bit stereo DAC connected to Atmel SSC + * + * Copyright (C) 2006-2007 Atmel Norway + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/*#define DEBUG*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include "at73c213.h" + +#define BITRATE_MIN 8000 /* Hardware limit? */ +#define BITRATE_TARGET CONFIG_SND_AT73C213_TARGET_BITRATE +#define BITRATE_MAX 50000 /* Hardware limit. */ + +/* Initial (hardware reset) AT73C213 register values. */ +static u8 snd_at73c213_original_image[18] = +{ + 0x00, /* 00 - CTRL */ + 0x05, /* 01 - LLIG */ + 0x05, /* 02 - RLIG */ + 0x08, /* 03 - LPMG */ + 0x08, /* 04 - RPMG */ + 0x00, /* 05 - LLOG */ + 0x00, /* 06 - RLOG */ + 0x22, /* 07 - OLC */ + 0x09, /* 08 - MC */ + 0x00, /* 09 - CSFC */ + 0x00, /* 0A - MISC */ + 0x00, /* 0B - */ + 0x00, /* 0C - PRECH */ + 0x05, /* 0D - AUXG */ + 0x00, /* 0E - */ + 0x00, /* 0F - */ + 0x00, /* 10 - RST */ + 0x00, /* 11 - PA_CTRL */ +}; + +struct snd_at73c213 { + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + struct at73c213_board_info *board; + int irq; + int period; + unsigned long bitrate; + struct ssc_device *ssc; + struct spi_device *spi; + u8 spi_wbuffer[2]; + u8 spi_rbuffer[2]; + /* Image of the SPI registers in AT73C213. */ + u8 reg_image[18]; + /* Protect SSC registers against concurrent access. */ + spinlock_t lock; + /* Protect mixer registers against concurrent access. */ + struct mutex mixer_lock; +}; + +#define get_chip(card) ((struct snd_at73c213 *)card->private_data) + +static int +snd_at73c213_write_reg(struct snd_at73c213 *chip, u8 reg, u8 val) +{ + struct spi_message msg; + struct spi_transfer msg_xfer = { + .len = 2, + .cs_change = 0, + }; + int retval; + + spi_message_init(&msg); + + chip->spi_wbuffer[0] = reg; + chip->spi_wbuffer[1] = val; + + msg_xfer.tx_buf = chip->spi_wbuffer; + msg_xfer.rx_buf = chip->spi_rbuffer; + spi_message_add_tail(&msg_xfer, &msg); + + retval = spi_sync(chip->spi, &msg); + + if (!retval) + chip->reg_image[reg] = val; + + return retval; +} + +static struct snd_pcm_hardware snd_at73c213_playback_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, /* Replaced by chip->bitrate later. */ + .rate_max = 50000, /* Replaced by chip->bitrate later. */ + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024 - 1, + .period_bytes_min = 512, + .period_bytes_max = 64 * 1024 - 1, + .periods_min = 4, + .periods_max = 1024, +}; + +/* + * Calculate and set bitrate and divisions. + */ +static int snd_at73c213_set_bitrate(struct snd_at73c213 *chip) +{ + unsigned long ssc_rate = clk_get_rate(chip->ssc->clk); + unsigned long dac_rate_new, ssc_div; + int status; + unsigned long ssc_div_max, ssc_div_min; + int max_tries; + + /* + * We connect two clocks here, picking divisors so the I2S clocks + * out data at the same rate the DAC clocks it in ... and as close + * as practical to the desired target rate. + * + * The DAC master clock (MCLK) is programmable, and is either 256 + * or (not here) 384 times the I2S output clock (BCLK). + */ + + /* SSC clock / (bitrate * stereo * 16-bit). */ + ssc_div = ssc_rate / (BITRATE_TARGET * 2 * 16); + ssc_div_min = ssc_rate / (BITRATE_MAX * 2 * 16); + ssc_div_max = ssc_rate / (BITRATE_MIN * 2 * 16); + max_tries = (ssc_div_max - ssc_div_min) / 2; + + if (max_tries < 1) + max_tries = 1; + + /* ssc_div must be even. */ + ssc_div = (ssc_div + 1) & ~1UL; + + if ((ssc_rate / (ssc_div * 2 * 16)) < BITRATE_MIN) { + ssc_div -= 2; + if ((ssc_rate / (ssc_div * 2 * 16)) > BITRATE_MAX) + return -ENXIO; + } + + /* Search for a possible bitrate. */ + do { + /* SSC clock / (ssc divider * 16-bit * stereo). */ + if ((ssc_rate / (ssc_div * 2 * 16)) < BITRATE_MIN) + return -ENXIO; + + /* 256 / (2 * 16) = 8 */ + dac_rate_new = 8 * (ssc_rate / ssc_div); + + status = clk_round_rate(chip->board->dac_clk, dac_rate_new); + if (status <= 0) + return status; + + /* Ignore difference smaller than 256 Hz. */ + if ((status/256) == (dac_rate_new/256)) + goto set_rate; + + ssc_div += 2; + } while (--max_tries); + + /* Not able to find a valid bitrate. */ + return -ENXIO; + +set_rate: + status = clk_set_rate(chip->board->dac_clk, status); + if (status < 0) + return status; + + /* Set divider in SSC device. */ + ssc_writel(chip->ssc->regs, CMR, ssc_div/2); + + /* SSC clock / (ssc divider * 16-bit * stereo). */ + chip->bitrate = ssc_rate / (ssc_div * 16 * 2); + + dev_info(&chip->spi->dev, + "at73c213: supported bitrate is %lu (%lu divider)\n", + chip->bitrate, ssc_div); + + return 0; +} + +static int snd_at73c213_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_at73c213 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + /* ensure buffer_size is a multiple of period_size */ + err = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + snd_at73c213_playback_hw.rate_min = chip->bitrate; + snd_at73c213_playback_hw.rate_max = chip->bitrate; + runtime->hw = snd_at73c213_playback_hw; + chip->substream = substream; + + err = clk_enable(chip->ssc->clk); + if (err) + return err; + + return 0; +} + +static int snd_at73c213_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_at73c213 *chip = snd_pcm_substream_chip(substream); + chip->substream = NULL; + clk_disable(chip->ssc->clk); + return 0; +} + +static int snd_at73c213_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_at73c213 *chip = snd_pcm_substream_chip(substream); + int channels = params_channels(hw_params); + int val; + + val = ssc_readl(chip->ssc->regs, TFMR); + val = SSC_BFINS(TFMR_DATNB, channels - 1, val); + ssc_writel(chip->ssc->regs, TFMR, val); + + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_at73c213_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_at73c213_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_at73c213 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int block_size; + + block_size = frames_to_bytes(runtime, runtime->period_size); + + chip->period = 0; + + ssc_writel(chip->ssc->regs, PDC_TPR, + (long)runtime->dma_addr); + ssc_writel(chip->ssc->regs, PDC_TCR, + runtime->period_size * runtime->channels); + ssc_writel(chip->ssc->regs, PDC_TNPR, + (long)runtime->dma_addr + block_size); + ssc_writel(chip->ssc->regs, PDC_TNCR, + runtime->period_size * runtime->channels); + + return 0; +} + +static int snd_at73c213_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_at73c213 *chip = snd_pcm_substream_chip(substream); + int retval = 0; + + spin_lock(&chip->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ssc_writel(chip->ssc->regs, IER, SSC_BIT(IER_ENDTX)); + ssc_writel(chip->ssc->regs, PDC_PTCR, SSC_BIT(PDC_PTCR_TXTEN)); + break; + case SNDRV_PCM_TRIGGER_STOP: + ssc_writel(chip->ssc->regs, PDC_PTCR, SSC_BIT(PDC_PTCR_TXTDIS)); + ssc_writel(chip->ssc->regs, IDR, SSC_BIT(IDR_ENDTX)); + break; + default: + dev_dbg(&chip->spi->dev, "spurious command %x\n", cmd); + retval = -EINVAL; + break; + } + + spin_unlock(&chip->lock); + + return retval; +} + +static snd_pcm_uframes_t +snd_at73c213_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_at73c213 *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t pos; + unsigned long bytes; + + bytes = ssc_readl(chip->ssc->regs, PDC_TPR) + - (unsigned long)runtime->dma_addr; + + pos = bytes_to_frames(runtime, bytes); + if (pos >= runtime->buffer_size) + pos -= runtime->buffer_size; + + return pos; +} + +static const struct snd_pcm_ops at73c213_playback_ops = { + .open = snd_at73c213_pcm_open, + .close = snd_at73c213_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_at73c213_pcm_hw_params, + .hw_free = snd_at73c213_pcm_hw_free, + .prepare = snd_at73c213_pcm_prepare, + .trigger = snd_at73c213_pcm_trigger, + .pointer = snd_at73c213_pcm_pointer, +}; + +static int snd_at73c213_pcm_new(struct snd_at73c213 *chip, int device) +{ + struct snd_pcm *pcm; + int retval; + + retval = snd_pcm_new(chip->card, chip->card->shortname, + device, 1, 0, &pcm); + if (retval < 0) + goto out; + + pcm->private_data = chip; + pcm->info_flags = SNDRV_PCM_INFO_BLOCK_TRANSFER; + strcpy(pcm->name, "at73c213"); + chip->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &at73c213_playback_ops); + + retval = snd_pcm_lib_preallocate_pages_for_all(chip->pcm, + SNDRV_DMA_TYPE_DEV, &chip->ssc->pdev->dev, + 64 * 1024, 64 * 1024); +out: + return retval; +} + +static irqreturn_t snd_at73c213_interrupt(int irq, void *dev_id) +{ + struct snd_at73c213 *chip = dev_id; + struct snd_pcm_runtime *runtime = chip->substream->runtime; + u32 status; + int offset; + int block_size; + int next_period; + int retval = IRQ_NONE; + + spin_lock(&chip->lock); + + block_size = frames_to_bytes(runtime, runtime->period_size); + status = ssc_readl(chip->ssc->regs, IMR); + + if (status & SSC_BIT(IMR_ENDTX)) { + chip->period++; + if (chip->period == runtime->periods) + chip->period = 0; + next_period = chip->period + 1; + if (next_period == runtime->periods) + next_period = 0; + + offset = block_size * next_period; + + ssc_writel(chip->ssc->regs, PDC_TNPR, + (long)runtime->dma_addr + offset); + ssc_writel(chip->ssc->regs, PDC_TNCR, + runtime->period_size * runtime->channels); + retval = IRQ_HANDLED; + } + + ssc_readl(chip->ssc->regs, IMR); + spin_unlock(&chip->lock); + + if (status & SSC_BIT(IMR_ENDTX)) + snd_pcm_period_elapsed(chip->substream); + + return retval; +} + +/* + * Mixer functions. + */ +static int snd_at73c213_mono_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_at73c213 *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; + + mutex_lock(&chip->mixer_lock); + + ucontrol->value.integer.value[0] = + (chip->reg_image[reg] >> shift) & mask; + + if (invert) + ucontrol->value.integer.value[0] = + mask - ucontrol->value.integer.value[0]; + + mutex_unlock(&chip->mixer_lock); + + return 0; +} + +static int snd_at73c213_mono_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_at73c213 *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 change, retval; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + + mutex_lock(&chip->mixer_lock); + + val = (chip->reg_image[reg] & ~(mask << shift)) | val; + change = val != chip->reg_image[reg]; + retval = snd_at73c213_write_reg(chip, reg, val); + + mutex_unlock(&chip->mixer_lock); + + if (retval) + return retval; + + return change; +} + +static int snd_at73c213_stereo_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + if (mask == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + + return 0; +} + +static int snd_at73c213_stereo_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_at73c213 *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; + + mutex_lock(&chip->mixer_lock); + + ucontrol->value.integer.value[0] = + (chip->reg_image[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = + (chip->reg_image[right_reg] >> shift_right) & mask; + + if (invert) { + ucontrol->value.integer.value[0] = + mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = + mask - ucontrol->value.integer.value[1]; + } + + mutex_unlock(&chip->mixer_lock); + + return 0; +} + +static int snd_at73c213_stereo_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_at73c213 *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, retval; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + + mutex_lock(&chip->mixer_lock); + + val1 = (chip->reg_image[left_reg] & ~(mask << shift_left)) | val1; + val2 = (chip->reg_image[right_reg] & ~(mask << shift_right)) | val2; + change = val1 != chip->reg_image[left_reg] + || val2 != chip->reg_image[right_reg]; + retval = snd_at73c213_write_reg(chip, left_reg, val1); + if (retval) { + mutex_unlock(&chip->mixer_lock); + goto out; + } + retval = snd_at73c213_write_reg(chip, right_reg, val2); + if (retval) { + mutex_unlock(&chip->mixer_lock); + goto out; + } + + mutex_unlock(&chip->mixer_lock); + + return change; + +out: + return retval; +} + +#define snd_at73c213_mono_switch_info snd_ctl_boolean_mono_info + +static int snd_at73c213_mono_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_at73c213 *chip = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + mutex_lock(&chip->mixer_lock); + + ucontrol->value.integer.value[0] = + (chip->reg_image[reg] >> shift) & 0x01; + + if (invert) + ucontrol->value.integer.value[0] = + 0x01 - ucontrol->value.integer.value[0]; + + mutex_unlock(&chip->mixer_lock); + + return 0; +} + +static int snd_at73c213_mono_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_at73c213 *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 change, retval; + unsigned short val; + + if (ucontrol->value.integer.value[0]) + val = mask; + else + val = 0; + + if (invert) + val = mask - val; + val <<= shift; + + mutex_lock(&chip->mixer_lock); + + val |= (chip->reg_image[reg] & ~(mask << shift)); + change = val != chip->reg_image[reg]; + + retval = snd_at73c213_write_reg(chip, reg, val); + + mutex_unlock(&chip->mixer_lock); + + if (retval) + return retval; + + return change; +} + +static int snd_at73c213_pa_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = ((kcontrol->private_value >> 16) & 0xff) - 1; + + return 0; +} + +static int snd_at73c213_line_capture_volume_info( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + /* When inverted will give values 0x10001 => 0. */ + uinfo->value.integer.min = 14; + uinfo->value.integer.max = 31; + + return 0; +} + +static int snd_at73c213_aux_capture_volume_info( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + /* When inverted will give values 0x10001 => 0. */ + uinfo->value.integer.min = 14; + uinfo->value.integer.max = 31; + + return 0; +} + +#define AT73C213_MONO_SWITCH(xname, xindex, reg, shift, mask, invert) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_at73c213_mono_switch_info, \ + .get = snd_at73c213_mono_switch_get, \ + .put = snd_at73c213_mono_switch_put, \ + .private_value = (reg | (shift << 8) | (mask << 16) | (invert << 24)) \ +} + +#define AT73C213_STEREO(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = xindex, \ + .info = snd_at73c213_stereo_info, \ + .get = snd_at73c213_stereo_get, \ + .put = snd_at73c213_stereo_put, \ + .private_value = (left_reg | (right_reg << 8) \ + | (shift_left << 16) | (shift_right << 19) \ + | (mask << 24) | (invert << 22)) \ +} + +static struct snd_kcontrol_new snd_at73c213_controls[] = { +AT73C213_STEREO("Master Playback Volume", 0, DAC_LMPG, DAC_RMPG, 0, 0, 0x1f, 1), +AT73C213_STEREO("Master Playback Switch", 0, DAC_LMPG, DAC_RMPG, 5, 5, 1, 1), +AT73C213_STEREO("PCM Playback Volume", 0, DAC_LLOG, DAC_RLOG, 0, 0, 0x1f, 1), +AT73C213_STEREO("PCM Playback Switch", 0, DAC_LLOG, DAC_RLOG, 5, 5, 1, 1), +AT73C213_MONO_SWITCH("Mono PA Playback Switch", 0, DAC_CTRL, DAC_CTRL_ONPADRV, + 0x01, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PA Playback Volume", + .index = 0, + .info = snd_at73c213_pa_volume_info, + .get = snd_at73c213_mono_get, + .put = snd_at73c213_mono_put, + .private_value = PA_CTRL | (PA_CTRL_APAGAIN << 8) | \ + (0x0f << 16) | (1 << 24), +}, +AT73C213_MONO_SWITCH("PA High Gain Playback Switch", 0, PA_CTRL, PA_CTRL_APALP, + 0x01, 1), +AT73C213_MONO_SWITCH("PA Playback Switch", 0, PA_CTRL, PA_CTRL_APAON, 0x01, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Aux Capture Volume", + .index = 0, + .info = snd_at73c213_aux_capture_volume_info, + .get = snd_at73c213_mono_get, + .put = snd_at73c213_mono_put, + .private_value = DAC_AUXG | (0 << 8) | (0x1f << 16) | (1 << 24), +}, +AT73C213_MONO_SWITCH("Aux Capture Switch", 0, DAC_CTRL, DAC_CTRL_ONAUXIN, + 0x01, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Capture Volume", + .index = 0, + .info = snd_at73c213_line_capture_volume_info, + .get = snd_at73c213_stereo_get, + .put = snd_at73c213_stereo_put, + .private_value = DAC_LLIG | (DAC_RLIG << 8) | (0 << 16) | (0 << 19) + | (0x1f << 24) | (1 << 22), +}, +AT73C213_MONO_SWITCH("Line Capture Switch", 0, DAC_CTRL, 0, 0x03, 0), +}; + +static int snd_at73c213_mixer(struct snd_at73c213 *chip) +{ + struct snd_card *card; + int errval, idx; + + if (chip == NULL || chip->pcm == NULL) + return -EINVAL; + + card = chip->card; + + strcpy(card->mixername, chip->pcm->name); + + for (idx = 0; idx < ARRAY_SIZE(snd_at73c213_controls); idx++) { + errval = snd_ctl_add(card, + snd_ctl_new1(&snd_at73c213_controls[idx], + chip)); + if (errval < 0) + goto cleanup; + } + + return 0; + +cleanup: + for (idx = 1; idx < ARRAY_SIZE(snd_at73c213_controls) + 1; idx++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_find_numid(card, idx); + if (kctl) + snd_ctl_remove(card, kctl); + } + return errval; +} + +/* + * Device functions + */ +static int snd_at73c213_ssc_init(struct snd_at73c213 *chip) +{ + /* + * Continuous clock output. + * Starts on falling TF. + * Delay 1 cycle (1 bit). + * Periode is 16 bit (16 - 1). + */ + ssc_writel(chip->ssc->regs, TCMR, + SSC_BF(TCMR_CKO, 1) + | SSC_BF(TCMR_START, 4) + | SSC_BF(TCMR_STTDLY, 1) + | SSC_BF(TCMR_PERIOD, 16 - 1)); + /* + * Data length is 16 bit (16 - 1). + * Transmit MSB first. + * Transmit 2 words each transfer. + * Frame sync length is 16 bit (16 - 1). + * Frame starts on negative pulse. + */ + ssc_writel(chip->ssc->regs, TFMR, + SSC_BF(TFMR_DATLEN, 16 - 1) + | SSC_BIT(TFMR_MSBF) + | SSC_BF(TFMR_DATNB, 1) + | SSC_BF(TFMR_FSLEN, 16 - 1) + | SSC_BF(TFMR_FSOS, 1)); + + return 0; +} + +static int snd_at73c213_chip_init(struct snd_at73c213 *chip) +{ + int retval; + unsigned char dac_ctrl = 0; + + retval = snd_at73c213_set_bitrate(chip); + if (retval) + goto out; + + /* Enable DAC master clock. */ + retval = clk_enable(chip->board->dac_clk); + if (retval) + goto out; + + /* Initialize at73c213 on SPI bus. */ + retval = snd_at73c213_write_reg(chip, DAC_RST, 0x04); + if (retval) + goto out_clk; + msleep(1); + retval = snd_at73c213_write_reg(chip, DAC_RST, 0x03); + if (retval) + goto out_clk; + + /* Precharge everything. */ + retval = snd_at73c213_write_reg(chip, DAC_PRECH, 0xff); + if (retval) + goto out_clk; + retval = snd_at73c213_write_reg(chip, PA_CTRL, (1<ssc->regs, CR, SSC_BIT(CR_TXEN)); + + goto out; + +out_clk: + clk_disable(chip->board->dac_clk); +out: + return retval; +} + +static int snd_at73c213_dev_free(struct snd_device *device) +{ + struct snd_at73c213 *chip = device->device_data; + + ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS)); + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + chip->irq = -1; + } + + return 0; +} + +static int snd_at73c213_dev_init(struct snd_card *card, + struct spi_device *spi) +{ + static struct snd_device_ops ops = { + .dev_free = snd_at73c213_dev_free, + }; + struct snd_at73c213 *chip = get_chip(card); + int irq, retval; + + irq = chip->ssc->irq; + if (irq < 0) + return irq; + + spin_lock_init(&chip->lock); + mutex_init(&chip->mixer_lock); + chip->card = card; + chip->irq = -1; + + retval = clk_enable(chip->ssc->clk); + if (retval) + return retval; + + retval = request_irq(irq, snd_at73c213_interrupt, 0, "at73c213", chip); + if (retval) { + dev_dbg(&chip->spi->dev, "unable to request irq %d\n", irq); + goto out; + } + chip->irq = irq; + + memcpy(&chip->reg_image, &snd_at73c213_original_image, + sizeof(snd_at73c213_original_image)); + + retval = snd_at73c213_ssc_init(chip); + if (retval) + goto out_irq; + + retval = snd_at73c213_chip_init(chip); + if (retval) + goto out_irq; + + retval = snd_at73c213_pcm_new(chip, 0); + if (retval) + goto out_irq; + + retval = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (retval) + goto out_irq; + + retval = snd_at73c213_mixer(chip); + if (retval) + goto out_snd_dev; + + goto out; + +out_snd_dev: + snd_device_free(card, chip); +out_irq: + free_irq(chip->irq, chip); + chip->irq = -1; +out: + clk_disable(chip->ssc->clk); + + return retval; +} + +static int snd_at73c213_probe(struct spi_device *spi) +{ + struct snd_card *card; + struct snd_at73c213 *chip; + struct at73c213_board_info *board; + int retval; + char id[16]; + + board = spi->dev.platform_data; + if (!board) { + dev_dbg(&spi->dev, "no platform_data\n"); + return -ENXIO; + } + + if (!board->dac_clk) { + dev_dbg(&spi->dev, "no DAC clk\n"); + return -ENXIO; + } + + if (IS_ERR(board->dac_clk)) { + dev_dbg(&spi->dev, "no DAC clk\n"); + return PTR_ERR(board->dac_clk); + } + + /* Allocate "card" using some unused identifiers. */ + snprintf(id, sizeof id, "at73c213_%d", board->ssc_id); + retval = snd_card_new(&spi->dev, -1, id, THIS_MODULE, + sizeof(struct snd_at73c213), &card); + if (retval < 0) + goto out; + + chip = card->private_data; + chip->spi = spi; + chip->board = board; + + chip->ssc = ssc_request(board->ssc_id); + if (IS_ERR(chip->ssc)) { + dev_dbg(&spi->dev, "could not get ssc%d device\n", + board->ssc_id); + retval = PTR_ERR(chip->ssc); + goto out_card; + } + + retval = snd_at73c213_dev_init(card, spi); + if (retval) + goto out_ssc; + + strcpy(card->driver, "at73c213"); + strcpy(card->shortname, board->shortname); + sprintf(card->longname, "%s on irq %d", card->shortname, chip->irq); + + retval = snd_card_register(card); + if (retval) + goto out_ssc; + + dev_set_drvdata(&spi->dev, card); + + goto out; + +out_ssc: + ssc_free(chip->ssc); +out_card: + snd_card_free(card); +out: + return retval; +} + +static int snd_at73c213_remove(struct spi_device *spi) +{ + struct snd_card *card = dev_get_drvdata(&spi->dev); + struct snd_at73c213 *chip = card->private_data; + int retval; + + /* Stop playback. */ + retval = clk_enable(chip->ssc->clk); + if (retval) + goto out; + ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS)); + clk_disable(chip->ssc->clk); + + /* Mute sound. */ + retval = snd_at73c213_write_reg(chip, DAC_LMPG, 0x3f); + if (retval) + goto out; + retval = snd_at73c213_write_reg(chip, DAC_RMPG, 0x3f); + if (retval) + goto out; + retval = snd_at73c213_write_reg(chip, DAC_LLOG, 0x3f); + if (retval) + goto out; + retval = snd_at73c213_write_reg(chip, DAC_RLOG, 0x3f); + if (retval) + goto out; + retval = snd_at73c213_write_reg(chip, DAC_LLIG, 0x11); + if (retval) + goto out; + retval = snd_at73c213_write_reg(chip, DAC_RLIG, 0x11); + if (retval) + goto out; + retval = snd_at73c213_write_reg(chip, DAC_AUXG, 0x11); + if (retval) + goto out; + + /* Turn off PA. */ + retval = snd_at73c213_write_reg(chip, PA_CTRL, + chip->reg_image[PA_CTRL] | 0x0f); + if (retval) + goto out; + msleep(10); + retval = snd_at73c213_write_reg(chip, PA_CTRL, + (1 << PA_CTRL_APALP) | 0x0f); + if (retval) + goto out; + + /* Turn off external DAC. */ + retval = snd_at73c213_write_reg(chip, DAC_CTRL, 0x0c); + if (retval) + goto out; + msleep(2); + retval = snd_at73c213_write_reg(chip, DAC_CTRL, 0x00); + if (retval) + goto out; + + /* Turn off master power. */ + retval = snd_at73c213_write_reg(chip, DAC_PRECH, 0x00); + if (retval) + goto out; + +out: + /* Stop DAC master clock. */ + clk_disable(chip->board->dac_clk); + + ssc_free(chip->ssc); + snd_card_free(card); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int snd_at73c213_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_at73c213 *chip = card->private_data; + + ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS)); + clk_disable(chip->ssc->clk); + clk_disable(chip->board->dac_clk); + + return 0; +} + +static int snd_at73c213_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_at73c213 *chip = card->private_data; + int retval; + + retval = clk_enable(chip->board->dac_clk); + if (retval) + return retval; + retval = clk_enable(chip->ssc->clk); + if (retval) { + clk_disable(chip->board->dac_clk); + return retval; + } + ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXEN)); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(at73c213_pm_ops, snd_at73c213_suspend, + snd_at73c213_resume); +#define AT73C213_PM_OPS (&at73c213_pm_ops) + +#else +#define AT73C213_PM_OPS NULL +#endif + +static struct spi_driver at73c213_driver = { + .driver = { + .name = "at73c213", + .pm = AT73C213_PM_OPS, + }, + .probe = snd_at73c213_probe, + .remove = snd_at73c213_remove, +}; + +module_spi_driver(at73c213_driver); + +MODULE_AUTHOR("Hans-Christian Egtvedt "); +MODULE_DESCRIPTION("Sound driver for AT73C213 with Atmel SSC"); +MODULE_LICENSE("GPL"); diff --git a/sound/spi/at73c213.h b/sound/spi/at73c213.h new file mode 100644 index 000000000..fd8b372df --- /dev/null +++ b/sound/spi/at73c213.h @@ -0,0 +1,119 @@ +/* + * Driver for the AT73C213 16-bit stereo DAC on Atmel ATSTK1000 + * + * Copyright (C) 2006 - 2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * The full GNU General Public License is included in this + * distribution in the file called COPYING. + */ + +#ifndef _SND_AT73C213_H +#define _SND_AT73C213_H + +/* DAC control register */ +#define DAC_CTRL 0x00 +#define DAC_CTRL_ONPADRV 7 +#define DAC_CTRL_ONAUXIN 6 +#define DAC_CTRL_ONDACR 5 +#define DAC_CTRL_ONDACL 4 +#define DAC_CTRL_ONLNOR 3 +#define DAC_CTRL_ONLNOL 2 +#define DAC_CTRL_ONLNIR 1 +#define DAC_CTRL_ONLNIL 0 + +/* DAC left line in gain register */ +#define DAC_LLIG 0x01 +#define DAC_LLIG_LLIG 0 + +/* DAC right line in gain register */ +#define DAC_RLIG 0x02 +#define DAC_RLIG_RLIG 0 + +/* DAC Left Master Playback Gain Register */ +#define DAC_LMPG 0x03 +#define DAC_LMPG_LMPG 0 + +/* DAC Right Master Playback Gain Register */ +#define DAC_RMPG 0x04 +#define DAC_RMPG_RMPG 0 + +/* DAC Left Line Out Gain Register */ +#define DAC_LLOG 0x05 +#define DAC_LLOG_LLOG 0 + +/* DAC Right Line Out Gain Register */ +#define DAC_RLOG 0x06 +#define DAC_RLOG_RLOG 0 + +/* DAC Output Level Control Register */ +#define DAC_OLC 0x07 +#define DAC_OLC_RSHORT 7 +#define DAC_OLC_ROLC 4 +#define DAC_OLC_LSHORT 3 +#define DAC_OLC_LOLC 0 + +/* DAC Mixer Control Register */ +#define DAC_MC 0x08 +#define DAC_MC_INVR 5 +#define DAC_MC_INVL 4 +#define DAC_MC_RMSMIN2 3 +#define DAC_MC_RMSMIN1 2 +#define DAC_MC_LMSMIN2 1 +#define DAC_MC_LMSMIN1 0 + +/* DAC Clock and Sampling Frequency Control Register */ +#define DAC_CSFC 0x09 +#define DAC_CSFC_OVRSEL 4 + +/* DAC Miscellaneous Register */ +#define DAC_MISC 0x0A +#define DAC_MISC_VCMCAPSEL 7 +#define DAC_MISC_DINTSEL 4 +#define DAC_MISC_DITHEN 3 +#define DAC_MISC_DEEMPEN 2 +#define DAC_MISC_NBITS 0 + +/* DAC Precharge Control Register */ +#define DAC_PRECH 0x0C +#define DAC_PRECH_PRCHGPDRV 7 +#define DAC_PRECH_PRCHGAUX1 6 +#define DAC_PRECH_PRCHGLNOR 5 +#define DAC_PRECH_PRCHGLNOL 4 +#define DAC_PRECH_PRCHGLNIR 3 +#define DAC_PRECH_PRCHGLNIL 2 +#define DAC_PRECH_PRCHG 1 +#define DAC_PRECH_ONMSTR 0 + +/* DAC Auxiliary Input Gain Control Register */ +#define DAC_AUXG 0x0D +#define DAC_AUXG_AUXG 0 + +/* DAC Reset Register */ +#define DAC_RST 0x10 +#define DAC_RST_RESMASK 2 +#define DAC_RST_RESFILZ 1 +#define DAC_RST_RSTZ 0 + +/* Power Amplifier Control Register */ +#define PA_CTRL 0x11 +#define PA_CTRL_APAON 6 +#define PA_CTRL_APAPRECH 5 +#define PA_CTRL_APALP 4 +#define PA_CTRL_APAGAIN 0 + +#endif /* _SND_AT73C213_H */ diff --git a/sound/synth/Kconfig b/sound/synth/Kconfig new file mode 100644 index 000000000..dfe8950e0 --- /dev/null +++ b/sound/synth/Kconfig @@ -0,0 +1,2 @@ +config SND_SYNTH_EMUX + tristate diff --git a/sound/synth/Makefile b/sound/synth/Makefile new file mode 100644 index 000000000..b9f71d5db --- /dev/null +++ b/sound/synth/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-util-mem-objs := util_mem.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_EMU10K1) += snd-util-mem.o +obj-$(CONFIG_SND_TRIDENT) += snd-util-mem.o +obj-$(CONFIG_SND_SBAWE_SEQ) += snd-util-mem.o +obj-$(CONFIG_SND_SEQUENCER) += emux/ diff --git a/sound/synth/emux/Makefile b/sound/synth/emux/Makefile new file mode 100644 index 000000000..ed28c81ac --- /dev/null +++ b/sound/synth/emux/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela +# + +snd-emux-synth-objs := emux.o emux_synth.o emux_seq.o emux_nrpn.o \ + emux_effect.o emux_hwdep.o soundfont.o +snd-emux-synth-$(CONFIG_SND_PROC_FS) += emux_proc.o +ifneq ($(CONFIG_SND_SEQUENCER_OSS),) +snd-emux-synth-y += emux_oss.o +endif + +obj-$(CONFIG_SND_SYNTH_EMUX) += snd-emux-synth.o diff --git a/sound/synth/emux/emux.c b/sound/synth/emux/emux.c new file mode 100644 index 000000000..d8140ad98 --- /dev/null +++ b/sound/synth/emux/emux.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2000 Takashi Iwai + * + * Routines for control of EMU WaveTable chip + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include "emux_voice.h" + +MODULE_AUTHOR("Takashi Iwai"); +MODULE_DESCRIPTION("Routines for control of EMU WaveTable chip"); +MODULE_LICENSE("GPL"); + +/* + * create a new hardware dependent device for Emu8000/Emu10k1 + */ +int snd_emux_new(struct snd_emux **remu) +{ + struct snd_emux *emu; + + *remu = NULL; + emu = kzalloc(sizeof(*emu), GFP_KERNEL); + if (emu == NULL) + return -ENOMEM; + + spin_lock_init(&emu->voice_lock); + mutex_init(&emu->register_mutex); + + emu->client = -1; +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + emu->oss_synth = NULL; +#endif + emu->max_voices = 0; + emu->use_time = 0; + + timer_setup(&emu->tlist, snd_emux_timer_callback, 0); + emu->timer_active = 0; + + *remu = emu; + return 0; +} + +EXPORT_SYMBOL(snd_emux_new); + +/* + */ +static int sf_sample_new(void *private_data, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr, + const void __user *buf, long count) +{ + struct snd_emux *emu = private_data; + return emu->ops.sample_new(emu, sp, hdr, buf, count); + +} + +static int sf_sample_free(void *private_data, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr) +{ + struct snd_emux *emu = private_data; + return emu->ops.sample_free(emu, sp, hdr); + +} + +static void sf_sample_reset(void *private_data) +{ + struct snd_emux *emu = private_data; + emu->ops.sample_reset(emu); +} + +int snd_emux_register(struct snd_emux *emu, struct snd_card *card, int index, char *name) +{ + int err; + struct snd_sf_callback sf_cb; + + if (snd_BUG_ON(!emu->hw || emu->max_voices <= 0)) + return -EINVAL; + if (snd_BUG_ON(!card || !name)) + return -EINVAL; + + emu->card = card; + emu->name = kstrdup(name, GFP_KERNEL); + emu->voices = kcalloc(emu->max_voices, sizeof(struct snd_emux_voice), + GFP_KERNEL); + if (emu->name == NULL || emu->voices == NULL) + return -ENOMEM; + + /* create soundfont list */ + memset(&sf_cb, 0, sizeof(sf_cb)); + sf_cb.private_data = emu; + if (emu->ops.sample_new) + sf_cb.sample_new = sf_sample_new; + if (emu->ops.sample_free) + sf_cb.sample_free = sf_sample_free; + if (emu->ops.sample_reset) + sf_cb.sample_reset = sf_sample_reset; + emu->sflist = snd_sf_new(&sf_cb, emu->memhdr); + if (emu->sflist == NULL) + return -ENOMEM; + + if ((err = snd_emux_init_hwdep(emu)) < 0) + return err; + + snd_emux_init_voices(emu); + + snd_emux_init_seq(emu, card, index); +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + snd_emux_init_seq_oss(emu); +#endif + snd_emux_init_virmidi(emu, card); + + snd_emux_proc_init(emu, card, index); + return 0; +} + +EXPORT_SYMBOL(snd_emux_register); + +/* + */ +int snd_emux_free(struct snd_emux *emu) +{ + unsigned long flags; + + if (! emu) + return -EINVAL; + + spin_lock_irqsave(&emu->voice_lock, flags); + if (emu->timer_active) + del_timer(&emu->tlist); + spin_unlock_irqrestore(&emu->voice_lock, flags); + + snd_emux_proc_free(emu); + snd_emux_delete_virmidi(emu); +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + snd_emux_detach_seq_oss(emu); +#endif + snd_emux_detach_seq(emu); + snd_emux_delete_hwdep(emu); + snd_sf_free(emu->sflist); + kfree(emu->voices); + kfree(emu->name); + kfree(emu); + return 0; +} + +EXPORT_SYMBOL(snd_emux_free); diff --git a/sound/synth/emux/emux_effect.c b/sound/synth/emux/emux_effect.c new file mode 100644 index 000000000..9ac0bf531 --- /dev/null +++ b/sound/synth/emux/emux_effect.c @@ -0,0 +1,310 @@ +/* + * Midi synth routines for the Emu8k/Emu10k1 + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai + * + * Contains code based on awe_wave.c by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "emux_voice.h" +#include + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT +/* + * effects table + */ + +#define xoffsetof(type,tag) ((long)(&((type)NULL)->tag) - (long)(NULL)) + +#define parm_offset(tag) xoffsetof(struct soundfont_voice_parm *, tag) + +#define PARM_IS_BYTE (1 << 0) +#define PARM_IS_WORD (1 << 1) +#define PARM_IS_ALIGNED (3 << 2) +#define PARM_IS_ALIGN_HI (1 << 2) +#define PARM_IS_ALIGN_LO (2 << 2) +#define PARM_IS_SIGNED (1 << 4) + +#define PARM_WORD (PARM_IS_WORD) +#define PARM_BYTE_LO (PARM_IS_BYTE|PARM_IS_ALIGN_LO) +#define PARM_BYTE_HI (PARM_IS_BYTE|PARM_IS_ALIGN_HI) +#define PARM_BYTE (PARM_IS_BYTE) +#define PARM_SIGN_LO (PARM_IS_BYTE|PARM_IS_ALIGN_LO|PARM_IS_SIGNED) +#define PARM_SIGN_HI (PARM_IS_BYTE|PARM_IS_ALIGN_HI|PARM_IS_SIGNED) + +static struct emux_parm_defs { + int type; /* byte or word */ + int low, high; /* value range */ + long offset; /* offset in parameter record (-1 = not written) */ + int update; /* flgas for real-time update */ +} parm_defs[EMUX_NUM_EFFECTS] = { + {PARM_WORD, 0, 0x8000, parm_offset(moddelay), 0}, /* env1 delay */ + {PARM_BYTE_LO, 1, 0x80, parm_offset(modatkhld), 0}, /* env1 attack */ + {PARM_BYTE_HI, 0, 0x7e, parm_offset(modatkhld), 0}, /* env1 hold */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(moddcysus), 0}, /* env1 decay */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(modrelease), 0}, /* env1 release */ + {PARM_BYTE_HI, 0, 0x7f, parm_offset(moddcysus), 0}, /* env1 sustain */ + {PARM_BYTE_HI, 0, 0xff, parm_offset(pefe), 0}, /* env1 pitch */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(pefe), 0}, /* env1 fc */ + + {PARM_WORD, 0, 0x8000, parm_offset(voldelay), 0}, /* env2 delay */ + {PARM_BYTE_LO, 1, 0x80, parm_offset(volatkhld), 0}, /* env2 attack */ + {PARM_BYTE_HI, 0, 0x7e, parm_offset(volatkhld), 0}, /* env2 hold */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(voldcysus), 0}, /* env2 decay */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(volrelease), 0}, /* env2 release */ + {PARM_BYTE_HI, 0, 0x7f, parm_offset(voldcysus), 0}, /* env2 sustain */ + + {PARM_WORD, 0, 0x8000, parm_offset(lfo1delay), 0}, /* lfo1 delay */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(tremfrq), SNDRV_EMUX_UPDATE_TREMFREQ}, /* lfo1 freq */ + {PARM_SIGN_HI, -128, 127, parm_offset(tremfrq), SNDRV_EMUX_UPDATE_TREMFREQ}, /* lfo1 vol */ + {PARM_SIGN_HI, -128, 127, parm_offset(fmmod), SNDRV_EMUX_UPDATE_FMMOD}, /* lfo1 pitch */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(fmmod), SNDRV_EMUX_UPDATE_FMMOD}, /* lfo1 cutoff */ + + {PARM_WORD, 0, 0x8000, parm_offset(lfo2delay), 0}, /* lfo2 delay */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(fm2frq2), SNDRV_EMUX_UPDATE_FM2FRQ2}, /* lfo2 freq */ + {PARM_SIGN_HI, -128, 127, parm_offset(fm2frq2), SNDRV_EMUX_UPDATE_FM2FRQ2}, /* lfo2 pitch */ + + {PARM_WORD, 0, 0xffff, -1, SNDRV_EMUX_UPDATE_PITCH}, /* initial pitch */ + {PARM_BYTE, 0, 0xff, parm_offset(chorus), 0}, /* chorus */ + {PARM_BYTE, 0, 0xff, parm_offset(reverb), 0}, /* reverb */ + {PARM_BYTE, 0, 0xff, parm_offset(cutoff), SNDRV_EMUX_UPDATE_VOLUME}, /* cutoff */ + {PARM_BYTE, 0, 15, parm_offset(filterQ), SNDRV_EMUX_UPDATE_Q}, /* resonance */ + + {PARM_WORD, 0, 0xffff, -1, 0}, /* sample start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* loop start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* loop end */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* coarse sample start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* coarse loop start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* coarse loop end */ + {PARM_BYTE, 0, 0xff, -1, SNDRV_EMUX_UPDATE_VOLUME}, /* initial attenuation */ +}; + +/* set byte effect value */ +static void +effect_set_byte(unsigned char *valp, struct snd_midi_channel *chan, int type) +{ + short effect; + struct snd_emux_effect_table *fx = chan->private; + + effect = fx->val[type]; + if (fx->flag[type] == EMUX_FX_FLAG_ADD) { + if (parm_defs[type].type & PARM_IS_SIGNED) + effect += *(char*)valp; + else + effect += *valp; + } + if (effect < parm_defs[type].low) + effect = parm_defs[type].low; + else if (effect > parm_defs[type].high) + effect = parm_defs[type].high; + *valp = (unsigned char)effect; +} + +/* set word effect value */ +static void +effect_set_word(unsigned short *valp, struct snd_midi_channel *chan, int type) +{ + int effect; + struct snd_emux_effect_table *fx = chan->private; + + effect = *(unsigned short*)&fx->val[type]; + if (fx->flag[type] == EMUX_FX_FLAG_ADD) + effect += *valp; + if (effect < parm_defs[type].low) + effect = parm_defs[type].low; + else if (effect > parm_defs[type].high) + effect = parm_defs[type].high; + *valp = (unsigned short)effect; +} + +/* address offset */ +static int +effect_get_offset(struct snd_midi_channel *chan, int lo, int hi, int mode) +{ + int addr = 0; + struct snd_emux_effect_table *fx = chan->private; + + if (fx->flag[hi]) + addr = (short)fx->val[hi]; + addr = addr << 15; + if (fx->flag[lo]) + addr += (short)fx->val[lo]; + if (!(mode & SNDRV_SFNT_SAMPLE_8BITS)) + addr /= 2; + return addr; +} + +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) +/* change effects - for OSS sequencer compatibility */ +void +snd_emux_send_effect_oss(struct snd_emux_port *port, + struct snd_midi_channel *chan, int type, int val) +{ + int mode; + + if (type & 0x40) + mode = EMUX_FX_FLAG_OFF; + else if (type & 0x80) + mode = EMUX_FX_FLAG_ADD; + else + mode = EMUX_FX_FLAG_SET; + type &= 0x3f; + + snd_emux_send_effect(port, chan, type, val, mode); +} +#endif + +/* Modify the effect value. + * if update is necessary, call emu8000_control + */ +void +snd_emux_send_effect(struct snd_emux_port *port, struct snd_midi_channel *chan, + int type, int val, int mode) +{ + int i; + int offset; + unsigned char *srcp, *origp; + struct snd_emux *emu; + struct snd_emux_effect_table *fx; + unsigned long flags; + + emu = port->emu; + fx = chan->private; + if (emu == NULL || fx == NULL) + return; + if (type < 0 || type >= EMUX_NUM_EFFECTS) + return; + + fx->val[type] = val; + fx->flag[type] = mode; + + /* do we need to modify the register in realtime ? */ + if (! parm_defs[type].update || (offset = parm_defs[type].offset) < 0) + return; + +#ifdef SNDRV_LITTLE_ENDIAN + if (parm_defs[type].type & PARM_IS_ALIGN_HI) + offset++; +#else + if (parm_defs[type].type & PARM_IS_ALIGN_LO) + offset++; +#endif + /* modify the register values */ + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + struct snd_emux_voice *vp = &emu->voices[i]; + if (!STATE_IS_PLAYING(vp->state) || vp->chan != chan) + continue; + srcp = (unsigned char*)&vp->reg.parm + offset; + origp = (unsigned char*)&vp->zone->v.parm + offset; + if (parm_defs[i].type & PARM_IS_BYTE) { + *srcp = *origp; + effect_set_byte(srcp, chan, type); + } else { + *(unsigned short*)srcp = *(unsigned short*)origp; + effect_set_word((unsigned short*)srcp, chan, type); + } + } + spin_unlock_irqrestore(&emu->voice_lock, flags); + + /* activate them */ + snd_emux_update_channel(port, chan, parm_defs[type].update); +} + + +/* copy wavetable registers to voice table */ +void +snd_emux_setup_effect(struct snd_emux_voice *vp) +{ + struct snd_midi_channel *chan = vp->chan; + struct snd_emux_effect_table *fx; + unsigned char *srcp; + int i; + + if (! (fx = chan->private)) + return; + + /* modify the register values via effect table */ + for (i = 0; i < EMUX_FX_END; i++) { + int offset; + if (! fx->flag[i] || (offset = parm_defs[i].offset) < 0) + continue; +#ifdef SNDRV_LITTLE_ENDIAN + if (parm_defs[i].type & PARM_IS_ALIGN_HI) + offset++; +#else + if (parm_defs[i].type & PARM_IS_ALIGN_LO) + offset++; +#endif + srcp = (unsigned char*)&vp->reg.parm + offset; + if (parm_defs[i].type & PARM_IS_BYTE) + effect_set_byte(srcp, chan, i); + else + effect_set_word((unsigned short*)srcp, chan, i); + } + + /* correct sample and loop points */ + vp->reg.start += effect_get_offset(chan, EMUX_FX_SAMPLE_START, + EMUX_FX_COARSE_SAMPLE_START, + vp->reg.sample_mode); + + vp->reg.loopstart += effect_get_offset(chan, EMUX_FX_LOOP_START, + EMUX_FX_COARSE_LOOP_START, + vp->reg.sample_mode); + + vp->reg.loopend += effect_get_offset(chan, EMUX_FX_LOOP_END, + EMUX_FX_COARSE_LOOP_END, + vp->reg.sample_mode); +} + +/* + * effect table + */ +void +snd_emux_create_effect(struct snd_emux_port *p) +{ + int i; + p->effect = kcalloc(p->chset.max_channels, + sizeof(struct snd_emux_effect_table), GFP_KERNEL); + if (p->effect) { + for (i = 0; i < p->chset.max_channels; i++) + p->chset.channels[i].private = p->effect + i; + } else { + for (i = 0; i < p->chset.max_channels; i++) + p->chset.channels[i].private = NULL; + } +} + +void +snd_emux_delete_effect(struct snd_emux_port *p) +{ + kfree(p->effect); + p->effect = NULL; +} + +void +snd_emux_clear_effect(struct snd_emux_port *p) +{ + if (p->effect) { + memset(p->effect, 0, sizeof(struct snd_emux_effect_table) * + p->chset.max_channels); + } +} + +#endif /* SNDRV_EMUX_USE_RAW_EFFECT */ diff --git a/sound/synth/emux/emux_hwdep.c b/sound/synth/emux/emux_hwdep.c new file mode 100644 index 000000000..d9fcae071 --- /dev/null +++ b/sound/synth/emux/emux_hwdep.c @@ -0,0 +1,156 @@ +/* + * Interface for hwdep device + * + * Copyright (C) 2004 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include "emux_voice.h" + +#define TMP_CLIENT_ID 0x1001 + +/* + * load patch + */ +static int +snd_emux_hwdep_load_patch(struct snd_emux *emu, void __user *arg) +{ + int err; + struct soundfont_patch_info patch; + + if (copy_from_user(&patch, arg, sizeof(patch))) + return -EFAULT; + + if (patch.type >= SNDRV_SFNT_LOAD_INFO && + patch.type <= SNDRV_SFNT_PROBE_DATA) { + err = snd_soundfont_load(emu->sflist, arg, patch.len + sizeof(patch), TMP_CLIENT_ID); + if (err < 0) + return err; + } else { + if (emu->ops.load_fx) + return emu->ops.load_fx(emu, patch.type, patch.optarg, arg, patch.len + sizeof(patch)); + else + return -EINVAL; + } + return 0; +} + +/* + * set misc mode + */ +static int +snd_emux_hwdep_misc_mode(struct snd_emux *emu, void __user *arg) +{ + struct snd_emux_misc_mode info; + int i; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (info.mode < 0 || info.mode >= EMUX_MD_END) + return -EINVAL; + info.mode = array_index_nospec(info.mode, EMUX_MD_END); + + if (info.port < 0) { + for (i = 0; i < emu->num_ports; i++) + emu->portptrs[i]->ctrls[info.mode] = info.value; + } else { + if (info.port < emu->num_ports) { + info.port = array_index_nospec(info.port, emu->num_ports); + emu->portptrs[info.port]->ctrls[info.mode] = info.value; + } + } + return 0; +} + + +/* + * ioctl + */ +static int +snd_emux_hwdep_ioctl(struct snd_hwdep * hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_emux *emu = hw->private_data; + + switch (cmd) { + case SNDRV_EMUX_IOCTL_VERSION: + return put_user(SNDRV_EMUX_VERSION, (unsigned int __user *)arg); + case SNDRV_EMUX_IOCTL_LOAD_PATCH: + return snd_emux_hwdep_load_patch(emu, (void __user *)arg); + case SNDRV_EMUX_IOCTL_RESET_SAMPLES: + snd_soundfont_remove_samples(emu->sflist); + break; + case SNDRV_EMUX_IOCTL_REMOVE_LAST_SAMPLES: + snd_soundfont_remove_unlocked(emu->sflist); + break; + case SNDRV_EMUX_IOCTL_MEM_AVAIL: + if (emu->memhdr) { + int size = snd_util_mem_avail(emu->memhdr); + return put_user(size, (unsigned int __user *)arg); + } + break; + case SNDRV_EMUX_IOCTL_MISC_MODE: + return snd_emux_hwdep_misc_mode(emu, (void __user *)arg); + } + + return 0; +} + + +/* + * register hwdep device + */ + +int +snd_emux_init_hwdep(struct snd_emux *emu) +{ + struct snd_hwdep *hw; + int err; + + if ((err = snd_hwdep_new(emu->card, SNDRV_EMUX_HWDEP_NAME, emu->hwdep_idx, &hw)) < 0) + return err; + emu->hwdep = hw; + strcpy(hw->name, SNDRV_EMUX_HWDEP_NAME); + hw->iface = SNDRV_HWDEP_IFACE_EMUX_WAVETABLE; + hw->ops.ioctl = snd_emux_hwdep_ioctl; + /* The ioctl parameter types are compatible between 32- and + * 64-bit architectures, so use the same function. */ + hw->ops.ioctl_compat = snd_emux_hwdep_ioctl; + hw->exclusive = 1; + hw->private_data = emu; + if ((err = snd_card_register(emu->card)) < 0) + return err; + + return 0; +} + + +/* + * unregister + */ +void +snd_emux_delete_hwdep(struct snd_emux *emu) +{ + if (emu->hwdep) { + snd_device_free(emu->card, emu->hwdep); + emu->hwdep = NULL; + } +} diff --git a/sound/synth/emux/emux_nrpn.c b/sound/synth/emux/emux_nrpn.c new file mode 100644 index 000000000..9729a15b6 --- /dev/null +++ b/sound/synth/emux/emux_nrpn.c @@ -0,0 +1,397 @@ +/* + * NRPN / SYSEX callbacks for Emu8k/Emu10k1 + * + * Copyright (c) 1999-2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "emux_voice.h" +#include + +/* + * conversion from NRPN/control parameters to Emu8000 raw parameters + */ + +/* NRPN / CC -> Emu8000 parameter converter */ +struct nrpn_conv_table { + int control; + int effect; + int (*convert)(int val); +}; + +/* effect sensitivity */ + +#define FX_CUTOFF 0 +#define FX_RESONANCE 1 +#define FX_ATTACK 2 +#define FX_RELEASE 3 +#define FX_VIBRATE 4 +#define FX_VIBDEPTH 5 +#define FX_VIBDELAY 6 +#define FX_NUMS 7 + +/* + * convert NRPN/control values + */ + +static int send_converted_effect(const struct nrpn_conv_table *table, + int num_tables, + struct snd_emux_port *port, + struct snd_midi_channel *chan, + int type, int val, int mode) +{ + int i, cval; + for (i = 0; i < num_tables; i++) { + if (table[i].control == type) { + cval = table[i].convert(val); + snd_emux_send_effect(port, chan, table[i].effect, + cval, mode); + return 1; + } + } + return 0; +} + +#define DEF_FX_CUTOFF 170 +#define DEF_FX_RESONANCE 6 +#define DEF_FX_ATTACK 50 +#define DEF_FX_RELEASE 50 +#define DEF_FX_VIBRATE 30 +#define DEF_FX_VIBDEPTH 4 +#define DEF_FX_VIBDELAY 1500 + +/* effect sensitivities for GS NRPN: + * adjusted for chaos 8MB soundfonts + */ +static int gs_sense[] = +{ + DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE, + DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY +}; + +/* effect sensitivies for XG controls: + * adjusted for chaos 8MB soundfonts + */ +static int xg_sense[] = +{ + DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE, + DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY +}; + + +/* + * AWE32 NRPN effects + */ + +static int fx_delay(int val); +static int fx_attack(int val); +static int fx_hold(int val); +static int fx_decay(int val); +static int fx_the_value(int val); +static int fx_twice_value(int val); +static int fx_conv_pitch(int val); +static int fx_conv_Q(int val); + +/* function for each NRPN */ /* [range] units */ +#define fx_env1_delay fx_delay /* [0,5900] 4msec */ +#define fx_env1_attack fx_attack /* [0,5940] 1msec */ +#define fx_env1_hold fx_hold /* [0,8191] 1msec */ +#define fx_env1_decay fx_decay /* [0,5940] 4msec */ +#define fx_env1_release fx_decay /* [0,5940] 4msec */ +#define fx_env1_sustain fx_the_value /* [0,127] 0.75dB */ +#define fx_env1_pitch fx_the_value /* [-127,127] 9.375cents */ +#define fx_env1_cutoff fx_the_value /* [-127,127] 56.25cents */ + +#define fx_env2_delay fx_delay /* [0,5900] 4msec */ +#define fx_env2_attack fx_attack /* [0,5940] 1msec */ +#define fx_env2_hold fx_hold /* [0,8191] 1msec */ +#define fx_env2_decay fx_decay /* [0,5940] 4msec */ +#define fx_env2_release fx_decay /* [0,5940] 4msec */ +#define fx_env2_sustain fx_the_value /* [0,127] 0.75dB */ + +#define fx_lfo1_delay fx_delay /* [0,5900] 4msec */ +#define fx_lfo1_freq fx_twice_value /* [0,127] 84mHz */ +#define fx_lfo1_volume fx_twice_value /* [0,127] 0.1875dB */ +#define fx_lfo1_pitch fx_the_value /* [-127,127] 9.375cents */ +#define fx_lfo1_cutoff fx_twice_value /* [-64,63] 56.25cents */ + +#define fx_lfo2_delay fx_delay /* [0,5900] 4msec */ +#define fx_lfo2_freq fx_twice_value /* [0,127] 84mHz */ +#define fx_lfo2_pitch fx_the_value /* [-127,127] 9.375cents */ + +#define fx_init_pitch fx_conv_pitch /* [-8192,8192] cents */ +#define fx_chorus fx_the_value /* [0,255] -- */ +#define fx_reverb fx_the_value /* [0,255] -- */ +#define fx_cutoff fx_twice_value /* [0,127] 62Hz */ +#define fx_filterQ fx_conv_Q /* [0,127] -- */ + +static int fx_delay(int val) +{ + return (unsigned short)snd_sf_calc_parm_delay(val); +} + +static int fx_attack(int val) +{ + return (unsigned short)snd_sf_calc_parm_attack(val); +} + +static int fx_hold(int val) +{ + return (unsigned short)snd_sf_calc_parm_hold(val); +} + +static int fx_decay(int val) +{ + return (unsigned short)snd_sf_calc_parm_decay(val); +} + +static int fx_the_value(int val) +{ + return (unsigned short)(val & 0xff); +} + +static int fx_twice_value(int val) +{ + return (unsigned short)((val * 2) & 0xff); +} + +static int fx_conv_pitch(int val) +{ + return (short)(val * 4096 / 1200); +} + +static int fx_conv_Q(int val) +{ + return (unsigned short)((val / 8) & 0xff); +} + + +static const struct nrpn_conv_table awe_effects[] = +{ + { 0, EMUX_FX_LFO1_DELAY, fx_lfo1_delay}, + { 1, EMUX_FX_LFO1_FREQ, fx_lfo1_freq}, + { 2, EMUX_FX_LFO2_DELAY, fx_lfo2_delay}, + { 3, EMUX_FX_LFO2_FREQ, fx_lfo2_freq}, + + { 4, EMUX_FX_ENV1_DELAY, fx_env1_delay}, + { 5, EMUX_FX_ENV1_ATTACK,fx_env1_attack}, + { 6, EMUX_FX_ENV1_HOLD, fx_env1_hold}, + { 7, EMUX_FX_ENV1_DECAY, fx_env1_decay}, + { 8, EMUX_FX_ENV1_SUSTAIN, fx_env1_sustain}, + { 9, EMUX_FX_ENV1_RELEASE, fx_env1_release}, + + {10, EMUX_FX_ENV2_DELAY, fx_env2_delay}, + {11, EMUX_FX_ENV2_ATTACK, fx_env2_attack}, + {12, EMUX_FX_ENV2_HOLD, fx_env2_hold}, + {13, EMUX_FX_ENV2_DECAY, fx_env2_decay}, + {14, EMUX_FX_ENV2_SUSTAIN, fx_env2_sustain}, + {15, EMUX_FX_ENV2_RELEASE, fx_env2_release}, + + {16, EMUX_FX_INIT_PITCH, fx_init_pitch}, + {17, EMUX_FX_LFO1_PITCH, fx_lfo1_pitch}, + {18, EMUX_FX_LFO2_PITCH, fx_lfo2_pitch}, + {19, EMUX_FX_ENV1_PITCH, fx_env1_pitch}, + {20, EMUX_FX_LFO1_VOLUME, fx_lfo1_volume}, + {21, EMUX_FX_CUTOFF, fx_cutoff}, + {22, EMUX_FX_FILTERQ, fx_filterQ}, + {23, EMUX_FX_LFO1_CUTOFF, fx_lfo1_cutoff}, + {24, EMUX_FX_ENV1_CUTOFF, fx_env1_cutoff}, + {25, EMUX_FX_CHORUS, fx_chorus}, + {26, EMUX_FX_REVERB, fx_reverb}, +}; + + +/* + * GS(SC88) NRPN effects; still experimental + */ + +/* cutoff: quarter semitone step, max=255 */ +static int gs_cutoff(int val) +{ + return (val - 64) * gs_sense[FX_CUTOFF] / 50; +} + +/* resonance: 0 to 15(max) */ +static int gs_filterQ(int val) +{ + return (val - 64) * gs_sense[FX_RESONANCE] / 50; +} + +/* attack: */ +static int gs_attack(int val) +{ + return -(val - 64) * gs_sense[FX_ATTACK] / 50; +} + +/* decay: */ +static int gs_decay(int val) +{ + return -(val - 64) * gs_sense[FX_RELEASE] / 50; +} + +/* release: */ +static int gs_release(int val) +{ + return -(val - 64) * gs_sense[FX_RELEASE] / 50; +} + +/* vibrato freq: 0.042Hz step, max=255 */ +static int gs_vib_rate(int val) +{ + return (val - 64) * gs_sense[FX_VIBRATE] / 50; +} + +/* vibrato depth: max=127, 1 octave */ +static int gs_vib_depth(int val) +{ + return (val - 64) * gs_sense[FX_VIBDEPTH] / 50; +} + +/* vibrato delay: -0.725msec step */ +static int gs_vib_delay(int val) +{ + return -(val - 64) * gs_sense[FX_VIBDELAY] / 50; +} + +static const struct nrpn_conv_table gs_effects[] = +{ + {32, EMUX_FX_CUTOFF, gs_cutoff}, + {33, EMUX_FX_FILTERQ, gs_filterQ}, + {99, EMUX_FX_ENV2_ATTACK, gs_attack}, + {100, EMUX_FX_ENV2_DECAY, gs_decay}, + {102, EMUX_FX_ENV2_RELEASE, gs_release}, + {8, EMUX_FX_LFO1_FREQ, gs_vib_rate}, + {9, EMUX_FX_LFO1_VOLUME, gs_vib_depth}, + {10, EMUX_FX_LFO1_DELAY, gs_vib_delay}, +}; + + +/* + * NRPN events + */ +void +snd_emux_nrpn(void *p, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + struct snd_emux_port *port; + + port = p; + if (snd_BUG_ON(!port || !chan)) + return; + + if (chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] == 127 && + chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB] <= 26) { + int val; + /* Win/DOS AWE32 specific NRPNs */ + /* both MSB/LSB necessary */ + val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) | + chan->control[MIDI_CTL_LSB_DATA_ENTRY]; + val -= 8192; + send_converted_effect + (awe_effects, ARRAY_SIZE(awe_effects), + port, chan, chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB], + val, EMUX_FX_FLAG_SET); + return; + } + + if (port->chset.midi_mode == SNDRV_MIDI_MODE_GS && + chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] == 1) { + int val; + /* GS specific NRPNs */ + /* only MSB is valid */ + val = chan->control[MIDI_CTL_MSB_DATA_ENTRY]; + send_converted_effect + (gs_effects, ARRAY_SIZE(gs_effects), + port, chan, chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB], + val, EMUX_FX_FLAG_ADD); + return; + } +} + + +/* + * XG control effects; still experimental + */ + +/* cutoff: quarter semitone step, max=255 */ +static int xg_cutoff(int val) +{ + return (val - 64) * xg_sense[FX_CUTOFF] / 64; +} + +/* resonance: 0(open) to 15(most nasal) */ +static int xg_filterQ(int val) +{ + return (val - 64) * xg_sense[FX_RESONANCE] / 64; +} + +/* attack: */ +static int xg_attack(int val) +{ + return -(val - 64) * xg_sense[FX_ATTACK] / 64; +} + +/* release: */ +static int xg_release(int val) +{ + return -(val - 64) * xg_sense[FX_RELEASE] / 64; +} + +static const struct nrpn_conv_table xg_effects[] = +{ + {71, EMUX_FX_CUTOFF, xg_cutoff}, + {74, EMUX_FX_FILTERQ, xg_filterQ}, + {72, EMUX_FX_ENV2_RELEASE, xg_release}, + {73, EMUX_FX_ENV2_ATTACK, xg_attack}, +}; + +int +snd_emux_xg_control(struct snd_emux_port *port, struct snd_midi_channel *chan, + int param) +{ + return send_converted_effect(xg_effects, ARRAY_SIZE(xg_effects), + port, chan, param, + chan->control[param], + EMUX_FX_FLAG_ADD); +} + +/* + * receive sysex + */ +void +snd_emux_sysex(void *p, unsigned char *buf, int len, int parsed, + struct snd_midi_channel_set *chset) +{ + struct snd_emux_port *port; + struct snd_emux *emu; + + port = p; + if (snd_BUG_ON(!port || !chset)) + return; + emu = port->emu; + + switch (parsed) { + case SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME: + snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME); + break; + default: + if (emu->ops.sysex) + emu->ops.sysex(emu, buf, len, parsed, chset); + break; + } +} + diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c new file mode 100644 index 000000000..764ff4bc2 --- /dev/null +++ b/sound/synth/emux/emux_oss.c @@ -0,0 +1,503 @@ +/* + * Interface for OSS sequencer emulation + * + * Copyright (C) 1999 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Changes + * 19990227 Steve Ratcliffe Made separate file and merged in latest + * midi emulation. + */ + + +#include +#include +#include +#include "emux_voice.h" +#include + +static int snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure); +static int snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg); +static int snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, + unsigned long ioarg); +static int snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, + const char __user *buf, int offs, int count); +static int snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg); +static int snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, + void *private, int atomic, int hop); +static void reset_port_mode(struct snd_emux_port *port, int midi_mode); +static void emuspec_control(struct snd_emux *emu, struct snd_emux_port *port, + int cmd, unsigned char *event, int atomic, int hop); +static void gusspec_control(struct snd_emux *emu, struct snd_emux_port *port, + int cmd, unsigned char *event, int atomic, int hop); +static void fake_event(struct snd_emux *emu, struct snd_emux_port *port, + int ch, int param, int val, int atomic, int hop); + +/* operators */ +static struct snd_seq_oss_callback oss_callback = { + .owner = THIS_MODULE, + .open = snd_emux_open_seq_oss, + .close = snd_emux_close_seq_oss, + .ioctl = snd_emux_ioctl_seq_oss, + .load_patch = snd_emux_load_patch_seq_oss, + .reset = snd_emux_reset_seq_oss, +}; + + +/* + * register OSS synth + */ + +void +snd_emux_init_seq_oss(struct snd_emux *emu) +{ + struct snd_seq_oss_reg *arg; + struct snd_seq_device *dev; + + /* using device#1 here for avoiding conflicts with OPL3 */ + if (snd_seq_device_new(emu->card, 1, SNDRV_SEQ_DEV_ID_OSS, + sizeof(struct snd_seq_oss_reg), &dev) < 0) + return; + + emu->oss_synth = dev; + strcpy(dev->name, emu->name); + arg = SNDRV_SEQ_DEVICE_ARGPTR(dev); + arg->type = SYNTH_TYPE_SAMPLE; + arg->subtype = SAMPLE_TYPE_AWE32; + arg->nvoices = emu->max_voices; + arg->oper = oss_callback; + arg->private_data = emu; + + /* register to OSS synth table */ + snd_device_register(emu->card, dev); +} + + +/* + * unregister + */ +void +snd_emux_detach_seq_oss(struct snd_emux *emu) +{ + if (emu->oss_synth) { + snd_device_free(emu->card, emu->oss_synth); + emu->oss_synth = NULL; + } +} + + +/* use port number as a unique soundfont client number */ +#define SF_CLIENT_NO(p) ((p) + 0x1000) + +/* + * open port for OSS sequencer + */ +static int +snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + struct snd_seq_port_callback callback; + char tmpname[64]; + + emu = closure; + if (snd_BUG_ON(!arg || !emu)) + return -ENXIO; + + if (!snd_emux_inc_count(emu)) + return -EFAULT; + + memset(&callback, 0, sizeof(callback)); + callback.owner = THIS_MODULE; + callback.event_input = snd_emux_event_oss_input; + + sprintf(tmpname, "%s OSS Port", emu->name); + p = snd_emux_create_port(emu, tmpname, 32, + 1, &callback); + if (p == NULL) { + snd_printk(KERN_ERR "can't create port\n"); + snd_emux_dec_count(emu); + return -ENOMEM; + } + + /* fill the argument data */ + arg->private_data = p; + arg->addr.client = p->chset.client; + arg->addr.port = p->chset.port; + p->oss_arg = arg; + + reset_port_mode(p, arg->seq_mode); + + snd_emux_reset_port(p); + return 0; +} + + +#define DEFAULT_DRUM_FLAGS ((1<<9) | (1<<25)) + +/* + * reset port mode + */ +static void +reset_port_mode(struct snd_emux_port *port, int midi_mode) +{ + if (midi_mode) { + port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI; + port->drum_flags = DEFAULT_DRUM_FLAGS; + port->volume_atten = 0; + port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS; + } else { + port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH; + port->drum_flags = 0; + port->volume_atten = 32; + port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + } +} + + +/* + * close port + */ +static int +snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + + snd_emux_sounds_off_all(p); + snd_soundfont_close_check(emu->sflist, SF_CLIENT_NO(p->chset.port)); + snd_seq_event_port_detach(p->chset.client, p->chset.port); + snd_emux_dec_count(emu); + + return 0; +} + + +/* + * load patch + */ +static int +snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, + const char __user *buf, int offs, int count) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + int rc; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + + if (format == GUS_PATCH) + rc = snd_soundfont_load_guspatch(emu->sflist, buf, count, + SF_CLIENT_NO(p->chset.port)); + else if (format == SNDRV_OSS_SOUNDFONT_PATCH) { + struct soundfont_patch_info patch; + if (count < (int)sizeof(patch)) + return -EINVAL; + if (copy_from_user(&patch, buf, sizeof(patch))) + return -EFAULT; + if (patch.type >= SNDRV_SFNT_LOAD_INFO && + patch.type <= SNDRV_SFNT_PROBE_DATA) + rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port)); + else { + if (emu->ops.load_fx) + rc = emu->ops.load_fx(emu, patch.type, patch.optarg, buf, count); + else + rc = -EINVAL; + } + } else + rc = 0; + return rc; +} + + +/* + * ioctl + */ +static int +snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg) +{ + struct snd_emux_port *p; + struct snd_emux *emu; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + + switch (cmd) { + case SNDCTL_SEQ_RESETSAMPLES: + snd_soundfont_remove_samples(emu->sflist); + return 0; + + case SNDCTL_SYNTH_MEMAVL: + if (emu->memhdr) + return snd_util_mem_avail(emu->memhdr); + return 0; + } + + return 0; +} + + +/* + * reset device + */ +static int +snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg) +{ + struct snd_emux_port *p; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + snd_emux_reset_port(p); + return 0; +} + + +/* + * receive raw events: only SEQ_PRIVATE is accepted. + */ +static int +snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + unsigned char cmd, *data; + + p = private_data; + if (snd_BUG_ON(!p)) + return -EINVAL; + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -EINVAL; + if (ev->type != SNDRV_SEQ_EVENT_OSS) + return snd_emux_event_input(ev, direct, private_data, atomic, hop); + + data = ev->data.raw8.d; + /* only SEQ_PRIVATE is accepted */ + if (data[0] != 0xfe) + return 0; + cmd = data[2] & _EMUX_OSS_MODE_VALUE_MASK; + if (data[2] & _EMUX_OSS_MODE_FLAG) + emuspec_control(emu, p, cmd, data, atomic, hop); + else + gusspec_control(emu, p, cmd, data, atomic, hop); + return 0; +} + + +/* + * OSS/AWE driver specific h/w controls + */ +static void +emuspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd, + unsigned char *event, int atomic, int hop) +{ + int voice; + unsigned short p1; + short p2; + int i; + struct snd_midi_channel *chan; + + voice = event[3]; + if (voice < 0 || voice >= port->chset.max_channels) + chan = NULL; + else + chan = &port->chset.channels[voice]; + + p1 = *(unsigned short *) &event[4]; + p2 = *(short *) &event[6]; + + switch (cmd) { +#if 0 /* don't do this atomically */ + case _EMUX_OSS_REMOVE_LAST_SAMPLES: + snd_soundfont_remove_unlocked(emu->sflist); + break; +#endif + case _EMUX_OSS_SEND_EFFECT: + if (chan) + snd_emux_send_effect_oss(port, chan, p1, p2); + break; + + case _EMUX_OSS_TERMINATE_ALL: + snd_emux_terminate_all(emu); + break; + + case _EMUX_OSS_TERMINATE_CHANNEL: + /*snd_emux_mute_channel(emu, chan);*/ + break; + case _EMUX_OSS_RESET_CHANNEL: + /*snd_emux_channel_init(chset, chan);*/ + break; + + case _EMUX_OSS_RELEASE_ALL: + fake_event(emu, port, voice, MIDI_CTL_ALL_NOTES_OFF, 0, atomic, hop); + break; + case _EMUX_OSS_NOTEOFF_ALL: + fake_event(emu, port, voice, MIDI_CTL_ALL_SOUNDS_OFF, 0, atomic, hop); + break; + + case _EMUX_OSS_INITIAL_VOLUME: + if (p2) { + port->volume_atten = (short)p1; + snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME); + } + break; + + case _EMUX_OSS_CHN_PRESSURE: + if (chan) { + chan->midi_pressure = p1; + snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_FMMOD|SNDRV_EMUX_UPDATE_FM2FRQ2); + } + break; + + case _EMUX_OSS_CHANNEL_MODE: + reset_port_mode(port, p1); + snd_emux_reset_port(port); + break; + + case _EMUX_OSS_DRUM_CHANNELS: + port->drum_flags = *(unsigned int*)&event[4]; + for (i = 0; i < port->chset.max_channels; i++) { + chan = &port->chset.channels[i]; + chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0; + } + break; + + case _EMUX_OSS_MISC_MODE: + if (p1 < EMUX_MD_END) + port->ctrls[p1] = p2; + break; + case _EMUX_OSS_DEBUG_MODE: + break; + + default: + if (emu->ops.oss_ioctl) + emu->ops.oss_ioctl(emu, cmd, p1, p2); + break; + } +} + +/* + * GUS specific h/w controls + */ + +#include + +static void +gusspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd, + unsigned char *event, int atomic, int hop) +{ + int voice; + unsigned short p1; + int plong; + struct snd_midi_channel *chan; + + if (port->port_mode != SNDRV_EMUX_PORT_MODE_OSS_SYNTH) + return; + if (cmd == _GUS_NUMVOICES) + return; + voice = event[3]; + if (voice < 0 || voice >= port->chset.max_channels) + return; + + chan = &port->chset.channels[voice]; + + p1 = *(unsigned short *) &event[4]; + plong = *(int*) &event[4]; + + switch (cmd) { + case _GUS_VOICESAMPLE: + chan->midi_program = p1; + return; + + case _GUS_VOICEBALA: + /* 0 to 15 --> 0 to 127 */ + chan->control[MIDI_CTL_MSB_PAN] = (int)p1 << 3; + snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN); + return; + + case _GUS_VOICEVOL: + case _GUS_VOICEVOL2: + /* not supported yet */ + return; + + case _GUS_RAMPRANGE: + case _GUS_RAMPRATE: + case _GUS_RAMPMODE: + case _GUS_RAMPON: + case _GUS_RAMPOFF: + /* volume ramping not supported */ + return; + + case _GUS_VOLUME_SCALE: + return; + + case _GUS_VOICE_POS: +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_send_effect(port, chan, EMUX_FX_SAMPLE_START, + (short)(plong & 0x7fff), + EMUX_FX_FLAG_SET); + snd_emux_send_effect(port, chan, EMUX_FX_COARSE_SAMPLE_START, + (plong >> 15) & 0xffff, + EMUX_FX_FLAG_SET); +#endif + return; + } +} + + +/* + * send an event to midi emulation + */ +static void +fake_event(struct snd_emux *emu, struct snd_emux_port *port, int ch, int param, int val, int atomic, int hop) +{ + struct snd_seq_event ev; + memset(&ev, 0, sizeof(ev)); + ev.type = SNDRV_SEQ_EVENT_CONTROLLER; + ev.data.control.channel = ch; + ev.data.control.param = param; + ev.data.control.value = val; + snd_emux_event_input(&ev, 0, port, atomic, hop); +} diff --git a/sound/synth/emux/emux_proc.c b/sound/synth/emux/emux_proc.c new file mode 100644 index 000000000..a82b4053b --- /dev/null +++ b/sound/synth/emux/emux_proc.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2000 Takashi Iwai + * + * Proc interface for Emu8k/Emu10k1 WaveTable synth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include "emux_voice.h" + +static void +snd_emux_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buf) +{ + struct snd_emux *emu; + int i; + + emu = entry->private_data; + mutex_lock(&emu->register_mutex); + if (emu->name) + snd_iprintf(buf, "Device: %s\n", emu->name); + snd_iprintf(buf, "Ports: %d\n", emu->num_ports); + snd_iprintf(buf, "Addresses:"); + for (i = 0; i < emu->num_ports; i++) + snd_iprintf(buf, " %d:%d", emu->client, emu->ports[i]); + snd_iprintf(buf, "\n"); + snd_iprintf(buf, "Use Counter: %d\n", emu->used); + snd_iprintf(buf, "Max Voices: %d\n", emu->max_voices); + snd_iprintf(buf, "Allocated Voices: %d\n", emu->num_voices); + if (emu->memhdr) { + snd_iprintf(buf, "Memory Size: %d\n", emu->memhdr->size); + snd_iprintf(buf, "Memory Available: %d\n", snd_util_mem_avail(emu->memhdr)); + snd_iprintf(buf, "Allocated Blocks: %d\n", emu->memhdr->nblocks); + } else { + snd_iprintf(buf, "Memory Size: 0\n"); + } + if (emu->sflist) { + mutex_lock(&emu->sflist->presets_mutex); + snd_iprintf(buf, "SoundFonts: %d\n", emu->sflist->fonts_size); + snd_iprintf(buf, "Instruments: %d\n", emu->sflist->zone_counter); + snd_iprintf(buf, "Samples: %d\n", emu->sflist->sample_counter); + snd_iprintf(buf, "Locked Instruments: %d\n", emu->sflist->zone_locked); + snd_iprintf(buf, "Locked Samples: %d\n", emu->sflist->sample_locked); + mutex_unlock(&emu->sflist->presets_mutex); + } +#if 0 /* debug */ + if (emu->voices[0].state != SNDRV_EMUX_ST_OFF && emu->voices[0].ch >= 0) { + struct snd_emux_voice *vp = &emu->voices[0]; + snd_iprintf(buf, "voice 0: on\n"); + snd_iprintf(buf, "mod delay=%x, atkhld=%x, dcysus=%x, rel=%x\n", + vp->reg.parm.moddelay, + vp->reg.parm.modatkhld, + vp->reg.parm.moddcysus, + vp->reg.parm.modrelease); + snd_iprintf(buf, "vol delay=%x, atkhld=%x, dcysus=%x, rel=%x\n", + vp->reg.parm.voldelay, + vp->reg.parm.volatkhld, + vp->reg.parm.voldcysus, + vp->reg.parm.volrelease); + snd_iprintf(buf, "lfo1 delay=%x, lfo2 delay=%x, pefe=%x\n", + vp->reg.parm.lfo1delay, + vp->reg.parm.lfo2delay, + vp->reg.parm.pefe); + snd_iprintf(buf, "fmmod=%x, tremfrq=%x, fm2frq2=%x\n", + vp->reg.parm.fmmod, + vp->reg.parm.tremfrq, + vp->reg.parm.fm2frq2); + snd_iprintf(buf, "cutoff=%x, filterQ=%x, chorus=%x, reverb=%x\n", + vp->reg.parm.cutoff, + vp->reg.parm.filterQ, + vp->reg.parm.chorus, + vp->reg.parm.reverb); + snd_iprintf(buf, "avol=%x, acutoff=%x, apitch=%x\n", + vp->avol, vp->acutoff, vp->apitch); + snd_iprintf(buf, "apan=%x, aaux=%x, ptarget=%x, vtarget=%x, ftarget=%x\n", + vp->apan, vp->aaux, + vp->ptarget, + vp->vtarget, + vp->ftarget); + snd_iprintf(buf, "start=%x, end=%x, loopstart=%x, loopend=%x\n", + vp->reg.start, vp->reg.end, vp->reg.loopstart, vp->reg.loopend); + snd_iprintf(buf, "sample_mode=%x, rate=%x\n", vp->reg.sample_mode, vp->reg.rate_offset); + } +#endif + mutex_unlock(&emu->register_mutex); +} + + +void snd_emux_proc_init(struct snd_emux *emu, struct snd_card *card, int device) +{ + struct snd_info_entry *entry; + char name[64]; + + sprintf(name, "wavetableD%d", device); + entry = snd_info_create_card_entry(card, name, card->proc_root); + if (entry == NULL) + return; + + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = emu; + entry->c.text.read = snd_emux_proc_info_read; + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); + else + emu->proc = entry; +} + +void snd_emux_proc_free(struct snd_emux *emu) +{ + snd_info_free_entry(emu->proc); + emu->proc = NULL; +} diff --git a/sound/synth/emux/emux_seq.c b/sound/synth/emux/emux_seq.c new file mode 100644 index 000000000..396c406d0 --- /dev/null +++ b/sound/synth/emux/emux_seq.c @@ -0,0 +1,416 @@ +/* + * Midi Sequencer interface routines. + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "emux_voice.h" +#include +#include + +/* Prototypes for static functions */ +static void free_port(void *private); +static void snd_emux_init_port(struct snd_emux_port *p); +static int snd_emux_use(void *private_data, struct snd_seq_port_subscribe *info); +static int snd_emux_unuse(void *private_data, struct snd_seq_port_subscribe *info); + +/* + * MIDI emulation operators + */ +static struct snd_midi_op emux_ops = { + .note_on = snd_emux_note_on, + .note_off = snd_emux_note_off, + .key_press = snd_emux_key_press, + .note_terminate = snd_emux_terminate_note, + .control = snd_emux_control, + .nrpn = snd_emux_nrpn, + .sysex = snd_emux_sysex, +}; + + +/* + * number of MIDI channels + */ +#define MIDI_CHANNELS 16 + +/* + * type flags for MIDI sequencer port + */ +#define DEFAULT_MIDI_TYPE (SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |\ + SNDRV_SEQ_PORT_TYPE_MIDI_GM |\ + SNDRV_SEQ_PORT_TYPE_MIDI_GS |\ + SNDRV_SEQ_PORT_TYPE_MIDI_XG |\ + SNDRV_SEQ_PORT_TYPE_HARDWARE |\ + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) + +/* + * Initialise the EMUX Synth by creating a client and registering + * a series of ports. + * Each of the ports will contain the 16 midi channels. Applications + * can connect to these ports to play midi data. + */ +int +snd_emux_init_seq(struct snd_emux *emu, struct snd_card *card, int index) +{ + int i; + struct snd_seq_port_callback pinfo; + char tmpname[64]; + + emu->client = snd_seq_create_kernel_client(card, index, + "%s WaveTable", emu->name); + if (emu->client < 0) { + snd_printk(KERN_ERR "can't create client\n"); + return -ENODEV; + } + + if (emu->num_ports < 0) { + snd_printk(KERN_WARNING "seqports must be greater than zero\n"); + emu->num_ports = 1; + } else if (emu->num_ports >= SNDRV_EMUX_MAX_PORTS) { + snd_printk(KERN_WARNING "too many ports." + "limited max. ports %d\n", SNDRV_EMUX_MAX_PORTS); + emu->num_ports = SNDRV_EMUX_MAX_PORTS; + } + + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.owner = THIS_MODULE; + pinfo.use = snd_emux_use; + pinfo.unuse = snd_emux_unuse; + pinfo.event_input = snd_emux_event_input; + + for (i = 0; i < emu->num_ports; i++) { + struct snd_emux_port *p; + + sprintf(tmpname, "%s Port %d", emu->name, i); + p = snd_emux_create_port(emu, tmpname, MIDI_CHANNELS, + 0, &pinfo); + if (!p) { + snd_printk(KERN_ERR "can't create port\n"); + return -ENOMEM; + } + + p->port_mode = SNDRV_EMUX_PORT_MODE_MIDI; + snd_emux_init_port(p); + emu->ports[i] = p->chset.port; + emu->portptrs[i] = p; + } + + return 0; +} + + +/* + * Detach from the ports that were set up for this synthesizer and + * destroy the kernel client. + */ +void +snd_emux_detach_seq(struct snd_emux *emu) +{ + if (emu->voices) + snd_emux_terminate_all(emu); + + if (emu->client >= 0) { + snd_seq_delete_kernel_client(emu->client); + emu->client = -1; + } +} + + +/* + * create a sequencer port and channel_set + */ + +struct snd_emux_port * +snd_emux_create_port(struct snd_emux *emu, char *name, + int max_channels, int oss_port, + struct snd_seq_port_callback *callback) +{ + struct snd_emux_port *p; + int i, type, cap; + + /* Allocate structures for this channel */ + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return NULL; + + p->chset.channels = kcalloc(max_channels, sizeof(*p->chset.channels), + GFP_KERNEL); + if (!p->chset.channels) { + kfree(p); + return NULL; + } + for (i = 0; i < max_channels; i++) + p->chset.channels[i].number = i; + p->chset.private_data = p; + p->chset.max_channels = max_channels; + p->emu = emu; + p->chset.client = emu->client; +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_create_effect(p); +#endif + callback->private_free = free_port; + callback->private_data = p; + + cap = SNDRV_SEQ_PORT_CAP_WRITE; + if (oss_port) { + type = SNDRV_SEQ_PORT_TYPE_SPECIFIC; + } else { + type = DEFAULT_MIDI_TYPE; + cap |= SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + } + + p->chset.port = snd_seq_event_port_attach(emu->client, callback, + cap, type, max_channels, + emu->max_voices, name); + + return p; +} + + +/* + * release memory block for port + */ +static void +free_port(void *private_data) +{ + struct snd_emux_port *p; + + p = private_data; + if (p) { +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_delete_effect(p); +#endif + kfree(p->chset.channels); + kfree(p); + } +} + + +#define DEFAULT_DRUM_FLAGS (1<<9) + +/* + * initialize the port specific parameters + */ +static void +snd_emux_init_port(struct snd_emux_port *p) +{ + p->drum_flags = DEFAULT_DRUM_FLAGS; + p->volume_atten = 0; + + snd_emux_reset_port(p); +} + + +/* + * reset port + */ +void +snd_emux_reset_port(struct snd_emux_port *port) +{ + int i; + + /* stop all sounds */ + snd_emux_sounds_off_all(port); + + snd_midi_channel_set_clear(&port->chset); + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_clear_effect(port); +#endif + + /* set port specific control parameters */ + port->ctrls[EMUX_MD_DEF_BANK] = 0; + port->ctrls[EMUX_MD_DEF_DRUM] = 0; + port->ctrls[EMUX_MD_REALTIME_PAN] = 1; + + for (i = 0; i < port->chset.max_channels; i++) { + struct snd_midi_channel *chan = port->chset.channels + i; + chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0; + } +} + + +/* + * input sequencer event + */ +int +snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct snd_emux_port *port; + + port = private_data; + if (snd_BUG_ON(!port || !ev)) + return -EINVAL; + + snd_midi_process_event(&emux_ops, ev, &port->chset); + + return 0; +} + + +/* + * increment usage count + */ +static int +__snd_emux_inc_count(struct snd_emux *emu) +{ + emu->used++; + if (!try_module_get(emu->ops.owner)) + goto __error; + if (!try_module_get(emu->card->module)) { + module_put(emu->ops.owner); + __error: + emu->used--; + return 0; + } + return 1; +} + +int snd_emux_inc_count(struct snd_emux *emu) +{ + int ret; + + mutex_lock(&emu->register_mutex); + ret = __snd_emux_inc_count(emu); + mutex_unlock(&emu->register_mutex); + return ret; +} + +/* + * decrease usage count + */ +static void +__snd_emux_dec_count(struct snd_emux *emu) +{ + module_put(emu->card->module); + emu->used--; + if (emu->used <= 0) + snd_emux_terminate_all(emu); + module_put(emu->ops.owner); +} + +void snd_emux_dec_count(struct snd_emux *emu) +{ + mutex_lock(&emu->register_mutex); + __snd_emux_dec_count(emu); + mutex_unlock(&emu->register_mutex); +} + +/* + * Routine that is called upon a first use of a particular port + */ +static int +snd_emux_use(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct snd_emux_port *p; + struct snd_emux *emu; + + p = private_data; + if (snd_BUG_ON(!p)) + return -EINVAL; + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -EINVAL; + + mutex_lock(&emu->register_mutex); + snd_emux_init_port(p); + __snd_emux_inc_count(emu); + mutex_unlock(&emu->register_mutex); + return 0; +} + +/* + * Routine that is called upon the last unuse() of a particular port. + */ +static int +snd_emux_unuse(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct snd_emux_port *p; + struct snd_emux *emu; + + p = private_data; + if (snd_BUG_ON(!p)) + return -EINVAL; + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -EINVAL; + + mutex_lock(&emu->register_mutex); + snd_emux_sounds_off_all(p); + __snd_emux_dec_count(emu); + mutex_unlock(&emu->register_mutex); + return 0; +} + + +/* + * attach virtual rawmidi devices + */ +int snd_emux_init_virmidi(struct snd_emux *emu, struct snd_card *card) +{ + int i; + + emu->vmidi = NULL; + if (emu->midi_ports <= 0) + return 0; + + emu->vmidi = kcalloc(emu->midi_ports, sizeof(*emu->vmidi), GFP_KERNEL); + if (!emu->vmidi) + return -ENOMEM; + + for (i = 0; i < emu->midi_ports; i++) { + struct snd_rawmidi *rmidi; + struct snd_virmidi_dev *rdev; + if (snd_virmidi_new(card, emu->midi_devidx + i, &rmidi) < 0) + goto __error; + rdev = rmidi->private_data; + sprintf(rmidi->name, "%s Synth MIDI", emu->name); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_ATTACH; + rdev->client = emu->client; + rdev->port = emu->ports[i]; + if (snd_device_register(card, rmidi) < 0) { + snd_device_free(card, rmidi); + goto __error; + } + emu->vmidi[i] = rmidi; + /* snd_printk(KERN_DEBUG "virmidi %d ok\n", i); */ + } + return 0; + +__error: + /* snd_printk(KERN_DEBUG "error init..\n"); */ + snd_emux_delete_virmidi(emu); + return -ENOMEM; +} + +int snd_emux_delete_virmidi(struct snd_emux *emu) +{ + int i; + + if (!emu->vmidi) + return 0; + + for (i = 0; i < emu->midi_ports; i++) { + if (emu->vmidi[i]) + snd_device_free(emu->card, emu->vmidi[i]); + } + kfree(emu->vmidi); + emu->vmidi = NULL; + return 0; +} diff --git a/sound/synth/emux/emux_synth.c b/sound/synth/emux/emux_synth.c new file mode 100644 index 000000000..9fa696b0d --- /dev/null +++ b/sound/synth/emux/emux_synth.c @@ -0,0 +1,982 @@ +/* + * Midi synth routines for the Emu8k/Emu10k1 + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai + * + * Contains code based on awe_wave.c by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include "emux_voice.h" +#include + +/* + * Prototypes + */ + +/* + * 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) + +static int get_zone(struct snd_emux *emu, struct snd_emux_port *port, + int *notep, int vel, struct snd_midi_channel *chan, + struct snd_sf_zone **table); +static int get_bank(struct snd_emux_port *port, struct snd_midi_channel *chan); +static void terminate_note1(struct snd_emux *emu, int note, + struct snd_midi_channel *chan, int free); +static void exclusive_note_off(struct snd_emux *emu, struct snd_emux_port *port, + int exclass); +static void terminate_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int free); +static void update_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int update); +static void setup_voice(struct snd_emux_voice *vp); +static int calc_pan(struct snd_emux_voice *vp); +static int calc_volume(struct snd_emux_voice *vp); +static int calc_pitch(struct snd_emux_voice *vp); + + +/* + * Start a note. + */ +void +snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan) +{ + struct snd_emux *emu; + int i, key, nvoices; + struct snd_emux_voice *vp; + struct snd_sf_zone *table[SNDRV_EMUX_MAX_MULTI_VOICES]; + unsigned long flags; + struct snd_emux_port *port; + + port = p; + if (snd_BUG_ON(!port || !chan)) + return; + + emu = port->emu; + if (snd_BUG_ON(!emu || !emu->ops.get_voice || !emu->ops.trigger)) + return; + + key = note; /* remember the original note */ + nvoices = get_zone(emu, port, ¬e, vel, chan, table); + if (! nvoices) + return; + + /* exclusive note off */ + for (i = 0; i < nvoices; i++) { + struct snd_sf_zone *zp = table[i]; + if (zp && zp->v.exclusiveClass) + exclusive_note_off(emu, port, zp->v.exclusiveClass); + } + +#if 0 // seems not necessary + /* Turn off the same note on the same channel. */ + terminate_note1(emu, key, chan, 0); +#endif + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < nvoices; i++) { + + /* set up each voice parameter */ + /* at this stage, we don't trigger the voice yet. */ + + if (table[i] == NULL) + continue; + + vp = emu->ops.get_voice(emu, port); + if (vp == NULL || vp->ch < 0) + continue; + if (STATE_IS_PLAYING(vp->state)) + emu->ops.terminate(vp); + + vp->time = emu->use_time++; + vp->chan = chan; + vp->port = port; + vp->key = key; + vp->note = note; + vp->velocity = vel; + vp->zone = table[i]; + if (vp->zone->sample) + vp->block = vp->zone->sample->block; + else + vp->block = NULL; + + setup_voice(vp); + + vp->state = SNDRV_EMUX_ST_STANDBY; + if (emu->ops.prepare) { + vp->state = SNDRV_EMUX_ST_OFF; + if (emu->ops.prepare(vp) >= 0) + vp->state = SNDRV_EMUX_ST_STANDBY; + } + } + + /* start envelope now */ + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + if (vp->state == SNDRV_EMUX_ST_STANDBY && + vp->chan == chan) { + emu->ops.trigger(vp); + vp->state = SNDRV_EMUX_ST_ON; + vp->ontime = jiffies; /* remember the trigger timing */ + } + } + spin_unlock_irqrestore(&emu->voice_lock, flags); + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) { + /* clear voice position for the next note on this channel */ + struct snd_emux_effect_table *fx = chan->private; + if (fx) { + fx->flag[EMUX_FX_SAMPLE_START] = 0; + fx->flag[EMUX_FX_COARSE_SAMPLE_START] = 0; + } + } +#endif +} + +/* + * Release a note in response to a midi note off. + */ +void +snd_emux_note_off(void *p, int note, int vel, struct snd_midi_channel *chan) +{ + int ch; + struct snd_emux *emu; + struct snd_emux_voice *vp; + unsigned long flags; + struct snd_emux_port *port; + + port = p; + if (snd_BUG_ON(!port || !chan)) + return; + + emu = port->emu; + if (snd_BUG_ON(!emu || !emu->ops.release)) + return; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (ch = 0; ch < emu->max_voices; ch++) { + vp = &emu->voices[ch]; + if (STATE_IS_PLAYING(vp->state) && + vp->chan == chan && vp->key == note) { + vp->state = SNDRV_EMUX_ST_RELEASED; + if (vp->ontime == jiffies) { + /* if note-off is sent too shortly after + * note-on, emuX engine cannot produce the sound + * correctly. so we'll release this note + * a bit later via timer callback. + */ + vp->state = SNDRV_EMUX_ST_PENDING; + if (! emu->timer_active) { + mod_timer(&emu->tlist, jiffies + 1); + emu->timer_active = 1; + } + } else + /* ok now release the note */ + emu->ops.release(vp); + } + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +/* + * timer callback + * + * release the pending note-offs + */ +void snd_emux_timer_callback(struct timer_list *t) +{ + struct snd_emux *emu = from_timer(emu, t, tlist); + struct snd_emux_voice *vp; + unsigned long flags; + int ch, do_again = 0; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (ch = 0; ch < emu->max_voices; ch++) { + vp = &emu->voices[ch]; + if (vp->state == SNDRV_EMUX_ST_PENDING) { + if (vp->ontime == jiffies) + do_again++; /* release this at the next interrupt */ + else { + emu->ops.release(vp); + vp->state = SNDRV_EMUX_ST_RELEASED; + } + } + } + if (do_again) { + mod_timer(&emu->tlist, jiffies + 1); + emu->timer_active = 1; + } else + emu->timer_active = 0; + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +/* + * key pressure change + */ +void +snd_emux_key_press(void *p, int note, int vel, struct snd_midi_channel *chan) +{ + int ch; + struct snd_emux *emu; + struct snd_emux_voice *vp; + unsigned long flags; + struct snd_emux_port *port; + + port = p; + if (snd_BUG_ON(!port || !chan)) + return; + + emu = port->emu; + if (snd_BUG_ON(!emu || !emu->ops.update)) + return; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (ch = 0; ch < emu->max_voices; ch++) { + vp = &emu->voices[ch]; + if (vp->state == SNDRV_EMUX_ST_ON && + vp->chan == chan && vp->key == note) { + vp->velocity = vel; + update_voice(emu, vp, SNDRV_EMUX_UPDATE_VOLUME); + } + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + + +/* + * Modulate the voices which belong to the channel + */ +void +snd_emux_update_channel(struct snd_emux_port *port, struct snd_midi_channel *chan, int update) +{ + struct snd_emux *emu; + struct snd_emux_voice *vp; + int i; + unsigned long flags; + + if (! update) + return; + + emu = port->emu; + if (snd_BUG_ON(!emu || !emu->ops.update)) + return; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + if (vp->chan == chan) + update_voice(emu, vp, update); + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +/* + * Modulate all the voices which belong to the port. + */ +void +snd_emux_update_port(struct snd_emux_port *port, int update) +{ + struct snd_emux *emu; + struct snd_emux_voice *vp; + int i; + unsigned long flags; + + if (! update) + return; + + emu = port->emu; + if (snd_BUG_ON(!emu || !emu->ops.update)) + return; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + if (vp->port == port) + update_voice(emu, vp, update); + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + + +/* + * Deal with a controller type event. This includes all types of + * control events, not just the midi controllers + */ +void +snd_emux_control(void *p, int type, struct snd_midi_channel *chan) +{ + struct snd_emux_port *port; + + port = p; + if (snd_BUG_ON(!port || !chan)) + return; + + switch (type) { + case MIDI_CTL_MSB_MAIN_VOLUME: + case MIDI_CTL_MSB_EXPRESSION: + snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_VOLUME); + break; + + case MIDI_CTL_MSB_PAN: + snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN); + break; + + case MIDI_CTL_SOFT_PEDAL: +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + /* FIXME: this is an emulation */ + if (chan->control[type] >= 64) + snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, -160, + EMUX_FX_FLAG_ADD); + else + snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, 0, + EMUX_FX_FLAG_OFF); +#endif + break; + + case MIDI_CTL_PITCHBEND: + snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PITCH); + break; + + case MIDI_CTL_MSB_MODWHEEL: + case MIDI_CTL_CHAN_PRESSURE: + snd_emux_update_channel(port, chan, + SNDRV_EMUX_UPDATE_FMMOD | + SNDRV_EMUX_UPDATE_FM2FRQ2); + break; + + } + + if (port->chset.midi_mode == SNDRV_MIDI_MODE_XG) { + snd_emux_xg_control(port, chan, type); + } +} + + +/* + * terminate note - if free flag is true, free the terminated voice + */ +static void +terminate_note1(struct snd_emux *emu, int note, struct snd_midi_channel *chan, int free) +{ + int i; + struct snd_emux_voice *vp; + unsigned long flags; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + if (STATE_IS_PLAYING(vp->state) && vp->chan == chan && + vp->key == note) + terminate_voice(emu, vp, free); + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + + +/* + * terminate note - exported for midi emulation + */ +void +snd_emux_terminate_note(void *p, int note, struct snd_midi_channel *chan) +{ + struct snd_emux *emu; + struct snd_emux_port *port; + + port = p; + if (snd_BUG_ON(!port || !chan)) + return; + + emu = port->emu; + if (snd_BUG_ON(!emu || !emu->ops.terminate)) + return; + + terminate_note1(emu, note, chan, 1); +} + + +/* + * Terminate all the notes + */ +void +snd_emux_terminate_all(struct snd_emux *emu) +{ + int i; + struct snd_emux_voice *vp; + unsigned long flags; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + if (STATE_IS_PLAYING(vp->state)) + terminate_voice(emu, vp, 0); + if (vp->state == SNDRV_EMUX_ST_OFF) { + if (emu->ops.free_voice) + emu->ops.free_voice(vp); + if (emu->ops.reset) + emu->ops.reset(emu, i); + } + vp->time = 0; + } + /* initialize allocation time */ + emu->use_time = 0; + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +EXPORT_SYMBOL(snd_emux_terminate_all); + +/* + * Terminate all voices associated with the given port + */ +void +snd_emux_sounds_off_all(struct snd_emux_port *port) +{ + int i; + struct snd_emux *emu; + struct snd_emux_voice *vp; + unsigned long flags; + + if (snd_BUG_ON(!port)) + return; + emu = port->emu; + if (snd_BUG_ON(!emu || !emu->ops.terminate)) + return; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + if (STATE_IS_PLAYING(vp->state) && + vp->port == port) + terminate_voice(emu, vp, 0); + if (vp->state == SNDRV_EMUX_ST_OFF) { + if (emu->ops.free_voice) + emu->ops.free_voice(vp); + if (emu->ops.reset) + emu->ops.reset(emu, i); + } + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + + +/* + * Terminate all voices that have the same exclusive class. This + * is mainly for drums. + */ +static void +exclusive_note_off(struct snd_emux *emu, struct snd_emux_port *port, int exclass) +{ + struct snd_emux_voice *vp; + int i; + unsigned long flags; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + if (STATE_IS_PLAYING(vp->state) && vp->port == port && + vp->reg.exclusiveClass == exclass) { + terminate_voice(emu, vp, 0); + } + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +/* + * terminate a voice + * if free flag is true, call free_voice after termination + */ +static void +terminate_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int free) +{ + emu->ops.terminate(vp); + vp->time = emu->use_time++; + vp->chan = NULL; + vp->port = NULL; + vp->zone = NULL; + vp->block = NULL; + vp->state = SNDRV_EMUX_ST_OFF; + if (free && emu->ops.free_voice) + emu->ops.free_voice(vp); +} + + +/* + * Modulate the voice + */ +static void +update_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int update) +{ + if (!STATE_IS_PLAYING(vp->state)) + return; + + if (vp->chan == NULL || vp->port == NULL) + return; + if (update & SNDRV_EMUX_UPDATE_VOLUME) + calc_volume(vp); + if (update & SNDRV_EMUX_UPDATE_PITCH) + calc_pitch(vp); + if (update & SNDRV_EMUX_UPDATE_PAN) { + if (! calc_pan(vp) && (update == SNDRV_EMUX_UPDATE_PAN)) + return; + } + emu->ops.update(vp, update); +} + + +#if 0 // not used +/* table for volume target calculation */ +static unsigned short voltarget[16] = { + 0xEAC0, 0xE0C8, 0xD740, 0xCE20, 0xC560, 0xBD08, 0xB500, 0xAD58, + 0xA5F8, 0x9EF0, 0x9830, 0x91C0, 0x8B90, 0x85A8, 0x8000, 0x7A90 +}; +#endif + +#define LO_BYTE(v) ((v) & 0xff) +#define HI_BYTE(v) (((v) >> 8) & 0xff) + +/* + * Sets up the voice structure by calculating some values that + * will be needed later. + */ +static void +setup_voice(struct snd_emux_voice *vp) +{ + struct soundfont_voice_parm *parm; + int pitch; + + /* copy the original register values */ + vp->reg = vp->zone->v; + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_setup_effect(vp); +#endif + + /* reset status */ + vp->apan = -1; + vp->avol = -1; + vp->apitch = -1; + + calc_volume(vp); + calc_pitch(vp); + calc_pan(vp); + + parm = &vp->reg.parm; + + /* compute filter target and correct modulation parameters */ + if (LO_BYTE(parm->modatkhld) >= 0x80 && parm->moddelay >= 0x8000) { + parm->moddelay = 0xbfff; + pitch = (HI_BYTE(parm->pefe) << 4) + vp->apitch; + if (pitch > 0xffff) + pitch = 0xffff; + /* calculate filter target */ + vp->ftarget = parm->cutoff + LO_BYTE(parm->pefe); + LIMITVALUE(vp->ftarget, 0, 255); + vp->ftarget <<= 8; + } else { + vp->ftarget = parm->cutoff; + vp->ftarget <<= 8; + pitch = vp->apitch; + } + + /* compute pitch target */ + if (pitch != 0xffff) { + vp->ptarget = 1 << (pitch >> 12); + if (pitch & 0x800) vp->ptarget += (vp->ptarget*0x102e)/0x2710; + if (pitch & 0x400) vp->ptarget += (vp->ptarget*0x764)/0x2710; + if (pitch & 0x200) vp->ptarget += (vp->ptarget*0x389)/0x2710; + vp->ptarget += (vp->ptarget >> 1); + if (vp->ptarget > 0xffff) vp->ptarget = 0xffff; + } else + vp->ptarget = 0xffff; + + if (LO_BYTE(parm->modatkhld) >= 0x80) { + parm->modatkhld &= ~0xff; + parm->modatkhld |= 0x7f; + } + + /* compute volume target and correct volume parameters */ + vp->vtarget = 0; +#if 0 /* FIXME: this leads to some clicks.. */ + if (LO_BYTE(parm->volatkhld) >= 0x80 && parm->voldelay >= 0x8000) { + parm->voldelay = 0xbfff; + vp->vtarget = voltarget[vp->avol % 0x10] >> (vp->avol >> 4); + } +#endif + + if (LO_BYTE(parm->volatkhld) >= 0x80) { + parm->volatkhld &= ~0xff; + parm->volatkhld |= 0x7f; + } +} + +/* + * calculate pitch parameter + */ +static unsigned char pan_volumes[256] = { +0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x14,0x17,0x1a,0x1d,0x20,0x22,0x25,0x28,0x2a, +0x2d,0x30,0x32,0x35,0x37,0x3a,0x3c,0x3f,0x41,0x44,0x46,0x49,0x4b,0x4d,0x50,0x52, +0x54,0x57,0x59,0x5b,0x5d,0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6f,0x71,0x73,0x75, +0x77,0x79,0x7b,0x7c,0x7e,0x80,0x82,0x84,0x86,0x88,0x89,0x8b,0x8d,0x8f,0x90,0x92, +0x94,0x96,0x97,0x99,0x9a,0x9c,0x9e,0x9f,0xa1,0xa2,0xa4,0xa5,0xa7,0xa8,0xaa,0xab, +0xad,0xae,0xaf,0xb1,0xb2,0xb3,0xb5,0xb6,0xb7,0xb9,0xba,0xbb,0xbc,0xbe,0xbf,0xc0, +0xc1,0xc2,0xc3,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1, +0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdc,0xdd,0xde,0xdf, +0xdf,0xe0,0xe1,0xe2,0xe2,0xe3,0xe4,0xe4,0xe5,0xe6,0xe6,0xe7,0xe8,0xe8,0xe9,0xe9, +0xea,0xeb,0xeb,0xec,0xec,0xed,0xed,0xee,0xee,0xef,0xef,0xf0,0xf0,0xf1,0xf1,0xf1, +0xf2,0xf2,0xf3,0xf3,0xf3,0xf4,0xf4,0xf5,0xf5,0xf5,0xf6,0xf6,0xf6,0xf7,0xf7,0xf7, +0xf7,0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xfa,0xfa,0xfa,0xfa,0xfb,0xfb,0xfb, +0xfb,0xfb,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd, +0xfd,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe, +0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, +0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, +}; + +static int +calc_pan(struct snd_emux_voice *vp) +{ + struct snd_midi_channel *chan = vp->chan; + int pan; + + /* pan & loop start (pan 8bit, MSB, 0:right, 0xff:left) */ + if (vp->reg.fixpan > 0) /* 0-127 */ + pan = 255 - (int)vp->reg.fixpan * 2; + else { + pan = chan->control[MIDI_CTL_MSB_PAN] - 64; + if (vp->reg.pan >= 0) /* 0-127 */ + pan += vp->reg.pan - 64; + pan = 127 - (int)pan * 2; + } + LIMITVALUE(pan, 0, 255); + + if (vp->emu->linear_panning) { + /* assuming linear volume */ + if (pan != vp->apan) { + vp->apan = pan; + if (pan == 0) + vp->aaux = 0xff; + else + vp->aaux = (-pan) & 0xff; + return 1; + } else + return 0; + } else { + /* using volume table */ + if (vp->apan != (int)pan_volumes[pan]) { + vp->apan = pan_volumes[pan]; + vp->aaux = pan_volumes[255 - pan]; + return 1; + } + return 0; + } +} + + +/* + * calculate volume attenuation + * + * Voice volume is controlled by volume attenuation parameter. + * So volume becomes maximum when avol is 0 (no attenuation), and + * minimum when 255 (-96dB or silence). + */ + +/* tables for volume->attenuation calculation */ +static unsigned char voltab1[128] = { + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x2b, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, + 0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, + 0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, + 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, + 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d, + 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static unsigned char voltab2[128] = { + 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x2a, + 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x24, 0x23, 0x22, 0x21, + 0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, 0x1b, 0x1a, + 0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15, + 0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10, + 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, + 0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, + 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static unsigned char expressiontab[128] = { + 0x7f, 0x6c, 0x62, 0x5a, 0x54, 0x50, 0x4b, 0x48, 0x45, 0x42, + 0x40, 0x3d, 0x3b, 0x39, 0x38, 0x36, 0x34, 0x33, 0x31, 0x30, + 0x2f, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, + 0x24, 0x24, 0x23, 0x22, 0x21, 0x21, 0x20, 0x1f, 0x1e, 0x1e, + 0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18, + 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x15, 0x14, 0x14, 0x13, + 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x0f, 0x0f, + 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, + 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, + 0x06, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, + 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * Magic to calculate the volume (actually attenuation) from all the + * voice and channels parameters. + */ +static int +calc_volume(struct snd_emux_voice *vp) +{ + int vol; + int main_vol, expression_vol, master_vol; + struct snd_midi_channel *chan = vp->chan; + struct snd_emux_port *port = vp->port; + + expression_vol = chan->control[MIDI_CTL_MSB_EXPRESSION]; + LIMITMAX(vp->velocity, 127); + LIMITVALUE(expression_vol, 0, 127); + if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) { + /* 0 - 127 */ + main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME]; + vol = (vp->velocity * main_vol * expression_vol) / (127*127); + vol = vol * vp->reg.amplitude / 127; + + LIMITVALUE(vol, 0, 127); + + /* calc to attenuation */ + vol = snd_sf_vol_table[vol]; + + } else { + main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME] * vp->reg.amplitude / 127; + LIMITVALUE(main_vol, 0, 127); + + vol = voltab1[main_vol] + voltab2[vp->velocity]; + vol = (vol * 8) / 3; + vol += vp->reg.attenuation; + vol += ((0x100 - vol) * expressiontab[expression_vol])/128; + } + + master_vol = port->chset.gs_master_volume; + LIMITVALUE(master_vol, 0, 127); + vol += snd_sf_vol_table[master_vol]; + vol += port->volume_atten; + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + if (chan->private) { + struct snd_emux_effect_table *fx = chan->private; + vol += fx->val[EMUX_FX_ATTEN]; + } +#endif + + LIMITVALUE(vol, 0, 255); + if (vp->avol == vol) + return 0; /* value unchanged */ + + vp->avol = vol; + if (!SF_IS_DRUM_BANK(get_bank(port, chan)) + && LO_BYTE(vp->reg.parm.volatkhld) < 0x7d) { + int atten; + if (vp->velocity < 70) + atten = 70; + else + atten = vp->velocity; + vp->acutoff = (atten * vp->reg.parm.cutoff + 0xa0) >> 7; + } else { + vp->acutoff = vp->reg.parm.cutoff; + } + + return 1; /* value changed */ +} + +/* + * calculate pitch offset + * + * 0xE000 is no pitch offset at 44100Hz sample. + * Every 4096 is one octave. + */ + +static int +calc_pitch(struct snd_emux_voice *vp) +{ + struct snd_midi_channel *chan = vp->chan; + int offset; + + /* calculate offset */ + if (vp->reg.fixkey >= 0) { + offset = (vp->reg.fixkey - vp->reg.root) * 4096 / 12; + } else { + offset = (vp->note - vp->reg.root) * 4096 / 12; + } + offset = (offset * vp->reg.scaleTuning) / 100; + offset += vp->reg.tune * 4096 / 1200; + if (chan->midi_pitchbend != 0) { + /* (128 * 8192: 1 semitone) ==> (4096: 12 semitones) */ + offset += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 3072; + } + + /* tuning via RPN: + * coarse = -8192 to 8192 (100 cent per 128) + * fine = -8192 to 8192 (max=100cent) + */ + /* 4096 = 1200 cents in emu8000 parameter */ + offset += chan->gm_rpn_coarse_tuning * 4096 / (12 * 128); + offset += chan->gm_rpn_fine_tuning / 24; + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + /* add initial pitch correction */ + if (chan->private) { + struct snd_emux_effect_table *fx = chan->private; + if (fx->flag[EMUX_FX_INIT_PITCH]) + offset += fx->val[EMUX_FX_INIT_PITCH]; + } +#endif + + /* 0xe000: root pitch */ + offset += 0xe000 + vp->reg.rate_offset; + offset += vp->emu->pitch_shift; + LIMITVALUE(offset, 0, 0xffff); + if (offset == vp->apitch) + return 0; /* unchanged */ + vp->apitch = offset; + return 1; /* value changed */ +} + +/* + * Get the bank number assigned to the channel + */ +static int +get_bank(struct snd_emux_port *port, struct snd_midi_channel *chan) +{ + int val; + + switch (port->chset.midi_mode) { + case SNDRV_MIDI_MODE_XG: + val = chan->control[MIDI_CTL_MSB_BANK]; + if (val == 127) + return 128; /* return drum bank */ + return chan->control[MIDI_CTL_LSB_BANK]; + + case SNDRV_MIDI_MODE_GS: + if (chan->drum_channel) + return 128; + /* ignore LSB (bank map) */ + return chan->control[MIDI_CTL_MSB_BANK]; + + default: + if (chan->drum_channel) + return 128; + return chan->control[MIDI_CTL_MSB_BANK]; + } +} + + +/* Look for the zones matching with the given note and velocity. + * The resultant zones are stored on table. + */ +static int +get_zone(struct snd_emux *emu, struct snd_emux_port *port, + int *notep, int vel, struct snd_midi_channel *chan, + struct snd_sf_zone **table) +{ + int preset, bank, def_preset, def_bank; + + bank = get_bank(port, chan); + preset = chan->midi_program; + + if (SF_IS_DRUM_BANK(bank)) { + def_preset = port->ctrls[EMUX_MD_DEF_DRUM]; + def_bank = bank; + } else { + def_preset = preset; + def_bank = port->ctrls[EMUX_MD_DEF_BANK]; + } + + return snd_soundfont_search_zone(emu->sflist, notep, vel, preset, bank, + def_preset, def_bank, + table, SNDRV_EMUX_MAX_MULTI_VOICES); +} + +/* + */ +void +snd_emux_init_voices(struct snd_emux *emu) +{ + struct snd_emux_voice *vp; + int i; + unsigned long flags; + + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + vp = &emu->voices[i]; + vp->ch = -1; /* not used */ + vp->state = SNDRV_EMUX_ST_OFF; + vp->chan = NULL; + vp->port = NULL; + vp->time = 0; + vp->emu = emu; + vp->hw = emu->hw; + } + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +/* + */ +void snd_emux_lock_voice(struct snd_emux *emu, int voice) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->voice_lock, flags); + if (emu->voices[voice].state == SNDRV_EMUX_ST_OFF) + emu->voices[voice].state = SNDRV_EMUX_ST_LOCKED; + else + snd_printk(KERN_WARNING + "invalid voice for lock %d (state = %x)\n", + voice, emu->voices[voice].state); + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +EXPORT_SYMBOL(snd_emux_lock_voice); + +/* + */ +void snd_emux_unlock_voice(struct snd_emux *emu, int voice) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->voice_lock, flags); + if (emu->voices[voice].state == SNDRV_EMUX_ST_LOCKED) + emu->voices[voice].state = SNDRV_EMUX_ST_OFF; + else + snd_printk(KERN_WARNING + "invalid voice for unlock %d (state = %x)\n", + voice, emu->voices[voice].state); + spin_unlock_irqrestore(&emu->voice_lock, flags); +} + +EXPORT_SYMBOL(snd_emux_unlock_voice); diff --git a/sound/synth/emux/emux_voice.h b/sound/synth/emux/emux_voice.h new file mode 100644 index 000000000..326fa8993 --- /dev/null +++ b/sound/synth/emux/emux_voice.h @@ -0,0 +1,100 @@ +#ifndef __EMUX_VOICE_H +#define __EMUX_VOICE_H + +/* + * A structure to keep track of each hardware voice + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +/* Prototypes for emux_seq.c */ +int snd_emux_init_seq(struct snd_emux *emu, struct snd_card *card, int index); +void snd_emux_detach_seq(struct snd_emux *emu); +struct snd_emux_port *snd_emux_create_port(struct snd_emux *emu, char *name, + int max_channels, int type, + struct snd_seq_port_callback *callback); +void snd_emux_reset_port(struct snd_emux_port *port); +int snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private, + int atomic, int hop); +int snd_emux_inc_count(struct snd_emux *emu); +void snd_emux_dec_count(struct snd_emux *emu); +int snd_emux_init_virmidi(struct snd_emux *emu, struct snd_card *card); +int snd_emux_delete_virmidi(struct snd_emux *emu); + +/* Prototypes for emux_synth.c */ +void snd_emux_init_voices(struct snd_emux *emu); + +void snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_emux_note_off(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_emux_key_press(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_emux_terminate_note(void *p, int note, struct snd_midi_channel *chan); +void snd_emux_control(void *p, int type, struct snd_midi_channel *chan); + +void snd_emux_sounds_off_all(struct snd_emux_port *port); +void snd_emux_update_channel(struct snd_emux_port *port, + struct snd_midi_channel *chan, int update); +void snd_emux_update_port(struct snd_emux_port *port, int update); + +void snd_emux_timer_callback(struct timer_list *t); + +/* emux_effect.c */ +#ifdef SNDRV_EMUX_USE_RAW_EFFECT +void snd_emux_create_effect(struct snd_emux_port *p); +void snd_emux_delete_effect(struct snd_emux_port *p); +void snd_emux_clear_effect(struct snd_emux_port *p); +void snd_emux_setup_effect(struct snd_emux_voice *vp); +void snd_emux_send_effect_oss(struct snd_emux_port *port, + struct snd_midi_channel *chan, int type, int val); +void snd_emux_send_effect(struct snd_emux_port *port, + struct snd_midi_channel *chan, int type, int val, int mode); +#endif + +/* emux_nrpn.c */ +void snd_emux_sysex(void *private_data, unsigned char *buf, int len, + int parsed, struct snd_midi_channel_set *chset); +int snd_emux_xg_control(struct snd_emux_port *port, + struct snd_midi_channel *chan, int param); +void snd_emux_nrpn(void *private_data, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); + +/* emux_oss.c */ +void snd_emux_init_seq_oss(struct snd_emux *emu); +void snd_emux_detach_seq_oss(struct snd_emux *emu); + +/* emux_proc.c */ +#ifdef CONFIG_SND_PROC_FS +void snd_emux_proc_init(struct snd_emux *emu, struct snd_card *card, int device); +void snd_emux_proc_free(struct snd_emux *emu); +#else +static inline void snd_emux_proc_init(struct snd_emux *emu, + struct snd_card *card, int device) {} +static inline void snd_emux_proc_free(struct snd_emux *emu) {} +#endif + +#define STATE_IS_PLAYING(s) ((s) & SNDRV_EMUX_ST_ON) + +/* emux_hwdep.c */ +int snd_emux_init_hwdep(struct snd_emux *emu); +void snd_emux_delete_hwdep(struct snd_emux *emu); + +#endif diff --git a/sound/synth/emux/soundfont.c b/sound/synth/emux/soundfont.c new file mode 100644 index 000000000..31a4ea948 --- /dev/null +++ b/sound/synth/emux/soundfont.c @@ -0,0 +1,1497 @@ +/* + * Soundfont generic routines. + * It is intended that these should be used by any driver that is willing + * to accept soundfont patches. + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * Deal with reading in of a soundfont. Code follows the OSS way + * of doing things so that the old sfxload utility can be used. + * Everything may change when there is an alsa way of doing things. + */ +#include +#include +#include +#include +#include +#include + +/* Prototypes for static functions */ + +static int open_patch(struct snd_sf_list *sflist, const char __user *data, + int count, int client); +static struct snd_soundfont *newsf(struct snd_sf_list *sflist, int type, char *name); +static int is_identical_font(struct snd_soundfont *sf, int type, unsigned char *name); +static int close_patch(struct snd_sf_list *sflist); +static int probe_data(struct snd_sf_list *sflist, int sample_id); +static void set_zone_counter(struct snd_sf_list *sflist, + struct snd_soundfont *sf, struct snd_sf_zone *zp); +static struct snd_sf_zone *sf_zone_new(struct snd_sf_list *sflist, + struct snd_soundfont *sf); +static void set_sample_counter(struct snd_sf_list *sflist, + struct snd_soundfont *sf, struct snd_sf_sample *sp); +static struct snd_sf_sample *sf_sample_new(struct snd_sf_list *sflist, + struct snd_soundfont *sf); +static void sf_sample_delete(struct snd_sf_list *sflist, + struct snd_soundfont *sf, struct snd_sf_sample *sp); +static int load_map(struct snd_sf_list *sflist, const void __user *data, int count); +static int load_info(struct snd_sf_list *sflist, const void __user *data, long count); +static int remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf, + int bank, int instr); +static void init_voice_info(struct soundfont_voice_info *avp); +static void init_voice_parm(struct soundfont_voice_parm *pp); +static struct snd_sf_sample *set_sample(struct snd_soundfont *sf, + struct soundfont_voice_info *avp); +static struct snd_sf_sample *find_sample(struct snd_soundfont *sf, int sample_id); +static int load_data(struct snd_sf_list *sflist, const void __user *data, long count); +static void rebuild_presets(struct snd_sf_list *sflist); +static void add_preset(struct snd_sf_list *sflist, struct snd_sf_zone *cur); +static void delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp); +static struct snd_sf_zone *search_first_zone(struct snd_sf_list *sflist, + int bank, int preset, int key); +static int search_zones(struct snd_sf_list *sflist, int *notep, int vel, + int preset, int bank, struct snd_sf_zone **table, + int max_layers, int level); +static int get_index(int bank, int instr, int key); +static void snd_sf_init(struct snd_sf_list *sflist); +static void snd_sf_clear(struct snd_sf_list *sflist); + +/* + * lock access to sflist + */ +static void +lock_preset(struct snd_sf_list *sflist) +{ + unsigned long flags; + mutex_lock(&sflist->presets_mutex); + spin_lock_irqsave(&sflist->lock, flags); + sflist->presets_locked = 1; + spin_unlock_irqrestore(&sflist->lock, flags); +} + + +/* + * remove lock + */ +static void +unlock_preset(struct snd_sf_list *sflist) +{ + unsigned long flags; + spin_lock_irqsave(&sflist->lock, flags); + sflist->presets_locked = 0; + spin_unlock_irqrestore(&sflist->lock, flags); + mutex_unlock(&sflist->presets_mutex); +} + + +/* + * close the patch if the patch was opened by this client. + */ +int +snd_soundfont_close_check(struct snd_sf_list *sflist, int client) +{ + unsigned long flags; + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->open_client == client) { + spin_unlock_irqrestore(&sflist->lock, flags); + return close_patch(sflist); + } + spin_unlock_irqrestore(&sflist->lock, flags); + return 0; +} + + +/* + * Deal with a soundfont patch. Any driver could use these routines + * although it was designed for the AWE64. + * + * The sample_write and callargs pararameters allow a callback into + * the actual driver to write sample data to the board or whatever + * it wants to do with it. + */ +int +snd_soundfont_load(struct snd_sf_list *sflist, const void __user *data, + long count, int client) +{ + struct soundfont_patch_info patch; + unsigned long flags; + int rc; + + if (count < (long)sizeof(patch)) { + snd_printk(KERN_ERR "patch record too small %ld\n", count); + return -EINVAL; + } + if (copy_from_user(&patch, data, sizeof(patch))) + return -EFAULT; + + count -= sizeof(patch); + data += sizeof(patch); + + if (patch.key != SNDRV_OSS_SOUNDFONT_PATCH) { + snd_printk(KERN_ERR "The wrong kind of patch %x\n", patch.key); + return -EINVAL; + } + if (count < patch.len) { + snd_printk(KERN_ERR "Patch too short %ld, need %d\n", + count, patch.len); + return -EINVAL; + } + if (patch.len < 0) { + snd_printk(KERN_ERR "poor length %d\n", patch.len); + return -EINVAL; + } + + if (patch.type == SNDRV_SFNT_OPEN_PATCH) { + /* grab sflist to open */ + lock_preset(sflist); + rc = open_patch(sflist, data, count, client); + unlock_preset(sflist); + return rc; + } + + /* check if other client already opened patch */ + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->open_client != client) { + spin_unlock_irqrestore(&sflist->lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&sflist->lock, flags); + + lock_preset(sflist); + rc = -EINVAL; + switch (patch.type) { + case SNDRV_SFNT_LOAD_INFO: + rc = load_info(sflist, data, count); + break; + case SNDRV_SFNT_LOAD_DATA: + rc = load_data(sflist, data, count); + break; + case SNDRV_SFNT_CLOSE_PATCH: + rc = close_patch(sflist); + break; + case SNDRV_SFNT_REPLACE_DATA: + /*rc = replace_data(&patch, data, count);*/ + break; + case SNDRV_SFNT_MAP_PRESET: + rc = load_map(sflist, data, count); + break; + case SNDRV_SFNT_PROBE_DATA: + rc = probe_data(sflist, patch.optarg); + break; + case SNDRV_SFNT_REMOVE_INFO: + /* patch must be opened */ + if (!sflist->currsf) { + snd_printk(KERN_ERR "soundfont: remove_info: " + "patch not opened\n"); + rc = -EINVAL; + } else { + int bank, instr; + bank = ((unsigned short)patch.optarg >> 8) & 0xff; + instr = (unsigned short)patch.optarg & 0xff; + if (! remove_info(sflist, sflist->currsf, bank, instr)) + rc = -EINVAL; + else + rc = 0; + } + break; + } + unlock_preset(sflist); + + return rc; +} + + +/* check if specified type is special font (GUS or preset-alias) */ +static inline int +is_special_type(int type) +{ + type &= 0x0f; + return (type == SNDRV_SFNT_PAT_TYPE_GUS || + type == SNDRV_SFNT_PAT_TYPE_MAP); +} + + +/* open patch; create sf list */ +static int +open_patch(struct snd_sf_list *sflist, const char __user *data, + int count, int client) +{ + struct soundfont_open_parm parm; + struct snd_soundfont *sf; + unsigned long flags; + + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->open_client >= 0 || sflist->currsf) { + spin_unlock_irqrestore(&sflist->lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&sflist->lock, flags); + + if (copy_from_user(&parm, data, sizeof(parm))) + return -EFAULT; + + if (is_special_type(parm.type)) { + parm.type |= SNDRV_SFNT_PAT_SHARED; + sf = newsf(sflist, parm.type, NULL); + } else + sf = newsf(sflist, parm.type, parm.name); + if (sf == NULL) { + return -ENOMEM; + } + + spin_lock_irqsave(&sflist->lock, flags); + sflist->open_client = client; + sflist->currsf = sf; + spin_unlock_irqrestore(&sflist->lock, flags); + + return 0; +} + +/* + * Allocate a new soundfont structure. + */ +static struct snd_soundfont * +newsf(struct snd_sf_list *sflist, int type, char *name) +{ + struct snd_soundfont *sf; + + /* check the shared fonts */ + if (type & SNDRV_SFNT_PAT_SHARED) { + for (sf = sflist->fonts; sf; sf = sf->next) { + if (is_identical_font(sf, type, name)) { + return sf; + } + } + } + + /* not found -- create a new one */ + sf = kzalloc(sizeof(*sf), GFP_KERNEL); + if (sf == NULL) + return NULL; + sf->id = sflist->fonts_size; + sflist->fonts_size++; + + /* prepend this record */ + sf->next = sflist->fonts; + sflist->fonts = sf; + + sf->type = type; + sf->zones = NULL; + sf->samples = NULL; + if (name) + memcpy(sf->name, name, SNDRV_SFNT_PATCH_NAME_LEN); + + return sf; +} + +/* check if the given name matches to the existing list */ +static int +is_identical_font(struct snd_soundfont *sf, int type, unsigned char *name) +{ + return ((sf->type & SNDRV_SFNT_PAT_SHARED) && + (sf->type & 0x0f) == (type & 0x0f) && + (name == NULL || + memcmp(sf->name, name, SNDRV_SFNT_PATCH_NAME_LEN) == 0)); +} + +/* + * Close the current patch. + */ +static int +close_patch(struct snd_sf_list *sflist) +{ + unsigned long flags; + + spin_lock_irqsave(&sflist->lock, flags); + sflist->currsf = NULL; + sflist->open_client = -1; + spin_unlock_irqrestore(&sflist->lock, flags); + + rebuild_presets(sflist); + + return 0; + +} + +/* probe sample in the current list -- nothing to be loaded */ +static int +probe_data(struct snd_sf_list *sflist, int sample_id) +{ + /* patch must be opened */ + if (sflist->currsf) { + /* search the specified sample by optarg */ + if (find_sample(sflist->currsf, sample_id)) + return 0; + } + return -EINVAL; +} + +/* + * increment zone counter + */ +static void +set_zone_counter(struct snd_sf_list *sflist, struct snd_soundfont *sf, + struct snd_sf_zone *zp) +{ + zp->counter = sflist->zone_counter++; + if (sf->type & SNDRV_SFNT_PAT_LOCKED) + sflist->zone_locked = sflist->zone_counter; +} + +/* + * allocate a new zone record + */ +static struct snd_sf_zone * +sf_zone_new(struct snd_sf_list *sflist, struct snd_soundfont *sf) +{ + struct snd_sf_zone *zp; + + if ((zp = kzalloc(sizeof(*zp), GFP_KERNEL)) == NULL) + return NULL; + zp->next = sf->zones; + sf->zones = zp; + + init_voice_info(&zp->v); + + set_zone_counter(sflist, sf, zp); + return zp; +} + + +/* + * increment sample counter + */ +static void +set_sample_counter(struct snd_sf_list *sflist, struct snd_soundfont *sf, + struct snd_sf_sample *sp) +{ + sp->counter = sflist->sample_counter++; + if (sf->type & SNDRV_SFNT_PAT_LOCKED) + sflist->sample_locked = sflist->sample_counter; +} + +/* + * allocate a new sample list record + */ +static struct snd_sf_sample * +sf_sample_new(struct snd_sf_list *sflist, struct snd_soundfont *sf) +{ + struct snd_sf_sample *sp; + + if ((sp = kzalloc(sizeof(*sp), GFP_KERNEL)) == NULL) + return NULL; + + sp->next = sf->samples; + sf->samples = sp; + + set_sample_counter(sflist, sf, sp); + return sp; +} + +/* + * delete sample list -- this is an exceptional job. + * only the last allocated sample can be deleted. + */ +static void +sf_sample_delete(struct snd_sf_list *sflist, struct snd_soundfont *sf, + struct snd_sf_sample *sp) +{ + /* only last sample is accepted */ + if (sp == sf->samples) { + sf->samples = sp->next; + kfree(sp); + } +} + + +/* load voice map */ +static int +load_map(struct snd_sf_list *sflist, const void __user *data, int count) +{ + struct snd_sf_zone *zp, *prevp; + struct snd_soundfont *sf; + struct soundfont_voice_map map; + + /* get the link info */ + if (count < (int)sizeof(map)) + return -EINVAL; + if (copy_from_user(&map, data, sizeof(map))) + return -EFAULT; + + if (map.map_instr < 0 || map.map_instr >= SF_MAX_INSTRUMENTS) + return -EINVAL; + + sf = newsf(sflist, SNDRV_SFNT_PAT_TYPE_MAP|SNDRV_SFNT_PAT_SHARED, NULL); + if (sf == NULL) + return -ENOMEM; + + prevp = NULL; + for (zp = sf->zones; zp; prevp = zp, zp = zp->next) { + if (zp->mapped && + zp->instr == map.map_instr && + zp->bank == map.map_bank && + zp->v.low == map.map_key && + zp->v.start == map.src_instr && + zp->v.end == map.src_bank && + zp->v.fixkey == map.src_key) { + /* the same mapping is already present */ + /* relink this record to the link head */ + if (prevp) { + prevp->next = zp->next; + zp->next = sf->zones; + sf->zones = zp; + } + /* update the counter */ + set_zone_counter(sflist, sf, zp); + return 0; + } + } + + /* create a new zone */ + if ((zp = sf_zone_new(sflist, sf)) == NULL) + return -ENOMEM; + + zp->bank = map.map_bank; + zp->instr = map.map_instr; + zp->mapped = 1; + if (map.map_key >= 0) { + zp->v.low = map.map_key; + zp->v.high = map.map_key; + } + zp->v.start = map.src_instr; + zp->v.end = map.src_bank; + zp->v.fixkey = map.src_key; + zp->v.sf_id = sf->id; + + add_preset(sflist, zp); + + return 0; +} + + +/* remove the present instrument layers */ +static int +remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf, + int bank, int instr) +{ + struct snd_sf_zone *prev, *next, *p; + int removed = 0; + + prev = NULL; + for (p = sf->zones; p; p = next) { + next = p->next; + if (! p->mapped && + p->bank == bank && p->instr == instr) { + /* remove this layer */ + if (prev) + prev->next = next; + else + sf->zones = next; + removed++; + kfree(p); + } else + prev = p; + } + if (removed) + rebuild_presets(sflist); + return removed; +} + + +/* + * Read an info record from the user buffer and save it on the current + * open soundfont. + */ +static int +load_info(struct snd_sf_list *sflist, const void __user *data, long count) +{ + struct snd_soundfont *sf; + struct snd_sf_zone *zone; + struct soundfont_voice_rec_hdr hdr; + int i; + + /* patch must be opened */ + if ((sf = sflist->currsf) == NULL) + return -EINVAL; + + if (is_special_type(sf->type)) + return -EINVAL; + + if (count < (long)sizeof(hdr)) { + printk(KERN_ERR "Soundfont error: invalid patch zone length\n"); + return -EINVAL; + } + if (copy_from_user((char*)&hdr, data, sizeof(hdr))) + return -EFAULT; + + data += sizeof(hdr); + count -= sizeof(hdr); + + if (hdr.nvoices <= 0 || hdr.nvoices >= 100) { + printk(KERN_ERR "Soundfont error: Illegal voice number %d\n", + hdr.nvoices); + return -EINVAL; + } + + if (count < (long)sizeof(struct soundfont_voice_info) * hdr.nvoices) { + printk(KERN_ERR "Soundfont Error: " + "patch length(%ld) is smaller than nvoices(%d)\n", + count, hdr.nvoices); + return -EINVAL; + } + + switch (hdr.write_mode) { + case SNDRV_SFNT_WR_EXCLUSIVE: + /* exclusive mode - if the instrument already exists, + return error */ + for (zone = sf->zones; zone; zone = zone->next) { + if (!zone->mapped && + zone->bank == hdr.bank && + zone->instr == hdr.instr) + return -EINVAL; + } + break; + case SNDRV_SFNT_WR_REPLACE: + /* replace mode - remove the instrument if it already exists */ + remove_info(sflist, sf, hdr.bank, hdr.instr); + break; + } + + for (i = 0; i < hdr.nvoices; i++) { + struct snd_sf_zone tmpzone; + + /* copy awe_voice_info parameters */ + if (copy_from_user(&tmpzone.v, data, sizeof(tmpzone.v))) { + return -EFAULT; + } + + data += sizeof(tmpzone.v); + count -= sizeof(tmpzone.v); + + tmpzone.bank = hdr.bank; + tmpzone.instr = hdr.instr; + tmpzone.mapped = 0; + tmpzone.v.sf_id = sf->id; + if (tmpzone.v.mode & SNDRV_SFNT_MODE_INIT_PARM) + init_voice_parm(&tmpzone.v.parm); + + /* create a new zone */ + if ((zone = sf_zone_new(sflist, sf)) == NULL) { + return -ENOMEM; + } + + /* copy the temporary data */ + zone->bank = tmpzone.bank; + zone->instr = tmpzone.instr; + zone->v = tmpzone.v; + + /* look up the sample */ + zone->sample = set_sample(sf, &zone->v); + } + + return 0; +} + + +/* initialize voice_info record */ +static void +init_voice_info(struct soundfont_voice_info *avp) +{ + memset(avp, 0, sizeof(*avp)); + + avp->root = 60; + avp->high = 127; + avp->velhigh = 127; + avp->fixkey = -1; + avp->fixvel = -1; + avp->fixpan = -1; + avp->pan = -1; + avp->amplitude = 127; + avp->scaleTuning = 100; + + init_voice_parm(&avp->parm); +} + +/* initialize voice_parm record: + * Env1/2: delay=0, attack=0, hold=0, sustain=0, decay=0, release=0. + * Vibrato and Tremolo effects are zero. + * Cutoff is maximum. + * Chorus and Reverb effects are zero. + */ +static void +init_voice_parm(struct soundfont_voice_parm *pp) +{ + memset(pp, 0, sizeof(*pp)); + + pp->moddelay = 0x8000; + pp->modatkhld = 0x7f7f; + pp->moddcysus = 0x7f7f; + pp->modrelease = 0x807f; + + pp->voldelay = 0x8000; + pp->volatkhld = 0x7f7f; + pp->voldcysus = 0x7f7f; + pp->volrelease = 0x807f; + + pp->lfo1delay = 0x8000; + pp->lfo2delay = 0x8000; + + pp->cutoff = 0xff; +} + +/* search the specified sample */ +static struct snd_sf_sample * +set_sample(struct snd_soundfont *sf, struct soundfont_voice_info *avp) +{ + struct snd_sf_sample *sample; + + sample = find_sample(sf, avp->sample); + if (sample == NULL) + return NULL; + + /* add in the actual sample offsets: + * The voice_info addresses define only the relative offset + * from sample pointers. Here we calculate the actual DRAM + * offset from sample pointers. + */ + avp->start += sample->v.start; + avp->end += sample->v.end; + avp->loopstart += sample->v.loopstart; + avp->loopend += sample->v.loopend; + + /* copy mode flags */ + avp->sample_mode = sample->v.mode_flags; + + return sample; +} + +/* find the sample pointer with the given id in the soundfont */ +static struct snd_sf_sample * +find_sample(struct snd_soundfont *sf, int sample_id) +{ + struct snd_sf_sample *p; + + if (sf == NULL) + return NULL; + + for (p = sf->samples; p; p = p->next) { + if (p->v.sample == sample_id) + return p; + } + return NULL; +} + + +/* + * Load sample information, this can include data to be loaded onto + * the soundcard. It can also just be a pointer into soundcard ROM. + * If there is data it will be written to the soundcard via the callback + * routine. + */ +static int +load_data(struct snd_sf_list *sflist, const void __user *data, long count) +{ + struct snd_soundfont *sf; + struct soundfont_sample_info sample_info; + struct snd_sf_sample *sp; + long off; + + /* patch must be opened */ + if ((sf = sflist->currsf) == NULL) + return -EINVAL; + + if (is_special_type(sf->type)) + return -EINVAL; + + if (copy_from_user(&sample_info, data, sizeof(sample_info))) + return -EFAULT; + + off = sizeof(sample_info); + + if (sample_info.size != (count-off)/2) + return -EINVAL; + + /* Check for dup */ + if (find_sample(sf, sample_info.sample)) { + /* if shared sample, skip this data */ + if (sf->type & SNDRV_SFNT_PAT_SHARED) + return 0; + return -EINVAL; + } + + /* Allocate a new sample structure */ + if ((sp = sf_sample_new(sflist, sf)) == NULL) + return -ENOMEM; + + sp->v = sample_info; + sp->v.sf_id = sf->id; + sp->v.dummy = 0; + sp->v.truesize = sp->v.size; + + /* + * If there is wave data then load it. + */ + if (sp->v.size > 0) { + int rc; + rc = sflist->callback.sample_new + (sflist->callback.private_data, sp, sflist->memhdr, + data + off, count - off); + if (rc < 0) { + sf_sample_delete(sflist, sf, sp); + return rc; + } + sflist->mem_used += sp->v.truesize; + } + + return count; +} + + +/* log2_tbl[i] = log2(i+128) * 0x10000 */ +static int log_tbl[129] = { + 0x70000, 0x702df, 0x705b9, 0x7088e, 0x70b5d, 0x70e26, 0x710eb, 0x713aa, + 0x71663, 0x71918, 0x71bc8, 0x71e72, 0x72118, 0x723b9, 0x72655, 0x728ed, + 0x72b80, 0x72e0e, 0x73098, 0x7331d, 0x7359e, 0x7381b, 0x73a93, 0x73d08, + 0x73f78, 0x741e4, 0x7444c, 0x746b0, 0x74910, 0x74b6c, 0x74dc4, 0x75019, + 0x75269, 0x754b6, 0x75700, 0x75946, 0x75b88, 0x75dc7, 0x76002, 0x7623a, + 0x7646e, 0x766a0, 0x768cd, 0x76af8, 0x76d1f, 0x76f43, 0x77164, 0x77382, + 0x7759d, 0x777b4, 0x779c9, 0x77bdb, 0x77dea, 0x77ff5, 0x781fe, 0x78404, + 0x78608, 0x78808, 0x78a06, 0x78c01, 0x78df9, 0x78fef, 0x791e2, 0x793d2, + 0x795c0, 0x797ab, 0x79993, 0x79b79, 0x79d5d, 0x79f3e, 0x7a11d, 0x7a2f9, + 0x7a4d3, 0x7a6ab, 0x7a880, 0x7aa53, 0x7ac24, 0x7adf2, 0x7afbe, 0x7b188, + 0x7b350, 0x7b515, 0x7b6d8, 0x7b899, 0x7ba58, 0x7bc15, 0x7bdd0, 0x7bf89, + 0x7c140, 0x7c2f5, 0x7c4a7, 0x7c658, 0x7c807, 0x7c9b3, 0x7cb5e, 0x7cd07, + 0x7ceae, 0x7d053, 0x7d1f7, 0x7d398, 0x7d538, 0x7d6d6, 0x7d872, 0x7da0c, + 0x7dba4, 0x7dd3b, 0x7ded0, 0x7e063, 0x7e1f4, 0x7e384, 0x7e512, 0x7e69f, + 0x7e829, 0x7e9b3, 0x7eb3a, 0x7ecc0, 0x7ee44, 0x7efc7, 0x7f148, 0x7f2c8, + 0x7f446, 0x7f5c2, 0x7f73d, 0x7f8b7, 0x7fa2f, 0x7fba5, 0x7fd1a, 0x7fe8d, + 0x80000, +}; + +/* convert from linear to log value + * + * conversion: value = log2(amount / base) * ratio + * + * argument: + * amount = linear value (unsigned, 32bit max) + * offset = base offset (:= log2(base) * 0x10000) + * ratio = division ratio + * + */ +int +snd_sf_linear_to_log(unsigned int amount, int offset, int ratio) +{ + int v; + int s, low, bit; + + if (amount < 2) + return 0; + for (bit = 0; ! (amount & 0x80000000L); bit++) + amount <<= 1; + s = (amount >> 24) & 0x7f; + low = (amount >> 16) & 0xff; + /* linear approxmimation by lower 8 bit */ + v = (log_tbl[s + 1] * low + log_tbl[s] * (0x100 - low)) >> 8; + v -= offset; + v = (v * ratio) >> 16; + v += (24 - bit) * ratio; + return v; +} + +EXPORT_SYMBOL(snd_sf_linear_to_log); + + +#define OFFSET_MSEC 653117 /* base = 1000 */ +#define OFFSET_ABSCENT 851781 /* base = 8176 */ +#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ + +#define ABSCENT_RATIO 1200 +#define TIMECENT_RATIO 1200 +#define SAMPLERATE_RATIO 4096 + +/* + * mHz to abscent + * conversion: abscent = log2(MHz / 8176) * 1200 + */ +static int +freq_to_note(int mhz) +{ + return snd_sf_linear_to_log(mhz, OFFSET_ABSCENT, ABSCENT_RATIO); +} + +/* convert Hz to AWE32 rate offset: + * sample pitch offset for the specified sample rate + * rate=44100 is no offset, each 4096 is 1 octave (twice). + * eg, when rate is 22050, this offset becomes -4096. + * + * conversion: offset = log2(Hz / 44100) * 4096 + */ +static int +calc_rate_offset(int hz) +{ + return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); +} + + +/* calculate GUS envelope time */ +static int +calc_gus_envelope_time(int rate, int start, int end) +{ + int r, p, t; + r = (3 - ((rate >> 6) & 3)) * 3; + p = rate & 0x3f; + t = end - start; + if (t < 0) t = -t; + if (13 > r) + t = t << (13 - r); + else + t = t >> (r - 13); + return (t * 10) / (p * 441); +} + +/* convert envelope time parameter to soundfont parameters */ + +/* attack & decay/release time table (msec) */ +static short attack_time_tbl[128] = { +32767, 32767, 5989, 4235, 2994, 2518, 2117, 1780, 1497, 1373, 1259, 1154, 1058, 970, 890, 816, +707, 691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, +361, 345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, +180, 172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, +90, 86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, +45, 43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, +22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 15, 14, 13, 13, 12, 12, +11, 11, 10, 10, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 6, 0, +}; + +static short decay_time_tbl[128] = { +32767, 32767, 22614, 15990, 11307, 9508, 7995, 6723, 5653, 5184, 4754, 4359, 3997, 3665, 3361, 3082, +2828, 2765, 2648, 2535, 2428, 2325, 2226, 2132, 2042, 1955, 1872, 1793, 1717, 1644, 1574, 1507, +1443, 1382, 1324, 1267, 1214, 1162, 1113, 1066, 978, 936, 897, 859, 822, 787, 754, 722, +691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, 361, +345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, 180, +172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, 90, +86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, 45, +43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, 22, +}; + +/* delay time = 0x8000 - msec/92 */ +int +snd_sf_calc_parm_hold(int msec) +{ + int val = (0x7f * 92 - msec) / 92; + if (val < 1) val = 1; + if (val >= 126) val = 126; + return val; +} + +/* search an index for specified time from given time table */ +static int +calc_parm_search(int msec, short *table) +{ + int left = 1, right = 127, mid; + while (left < right) { + mid = (left + right) / 2; + if (msec < (int)table[mid]) + left = mid + 1; + else + right = mid; + } + return left; +} + +/* attack time: search from time table */ +int +snd_sf_calc_parm_attack(int msec) +{ + return calc_parm_search(msec, attack_time_tbl); +} + +/* decay/release time: search from time table */ +int +snd_sf_calc_parm_decay(int msec) +{ + return calc_parm_search(msec, decay_time_tbl); +} + +int snd_sf_vol_table[128] = { + 255,111,95,86,79,74,70,66,63,61,58,56,54,52,50,49, + 47,46,45,43,42,41,40,39,38,37,36,35,34,34,33,32, + 31,31,30,29,29,28,27,27,26,26,25,24,24,23,23,22, + 22,21,21,21,20,20,19,19,18,18,18,17,17,16,16,16, + 15,15,15,14,14,14,13,13,13,12,12,12,11,11,11,10, + 10,10,10,9,9,9,8,8,8,8,7,7,7,7,6,6, + 6,6,5,5,5,5,5,4,4,4,4,3,3,3,3,3, + 2,2,2,2,2,1,1,1,1,1,0,0,0,0,0,0, +}; + + +#define calc_gus_sustain(val) (0x7f - snd_sf_vol_table[(val)/2]) +#define calc_gus_attenuation(val) snd_sf_vol_table[(val)/2] + +/* load GUS patch */ +static int +load_guspatch(struct snd_sf_list *sflist, const char __user *data, + long count, int client) +{ + struct patch_info patch; + struct snd_soundfont *sf; + struct snd_sf_zone *zone; + struct snd_sf_sample *smp; + int note, sample_id; + int rc; + + if (count < (long)sizeof(patch)) { + snd_printk(KERN_ERR "patch record too small %ld\n", count); + return -EINVAL; + } + if (copy_from_user(&patch, data, sizeof(patch))) + return -EFAULT; + + count -= sizeof(patch); + data += sizeof(patch); + + sf = newsf(sflist, SNDRV_SFNT_PAT_TYPE_GUS|SNDRV_SFNT_PAT_SHARED, NULL); + if (sf == NULL) + return -ENOMEM; + if ((smp = sf_sample_new(sflist, sf)) == NULL) + return -ENOMEM; + sample_id = sflist->sample_counter; + smp->v.sample = sample_id; + smp->v.start = 0; + smp->v.end = patch.len; + smp->v.loopstart = patch.loop_start; + smp->v.loopend = patch.loop_end; + smp->v.size = patch.len; + + /* set up mode flags */ + smp->v.mode_flags = 0; + if (!(patch.mode & WAVE_16_BITS)) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_8BITS; + if (patch.mode & WAVE_UNSIGNED) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_UNSIGNED; + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_NO_BLANK; + if (!(patch.mode & (WAVE_LOOPING|WAVE_BIDIR_LOOP|WAVE_LOOP_BACK))) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_SINGLESHOT; + if (patch.mode & WAVE_BIDIR_LOOP) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_BIDIR_LOOP; + if (patch.mode & WAVE_LOOP_BACK) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_REVERSE_LOOP; + + if (patch.mode & WAVE_16_BITS) { + /* convert to word offsets */ + smp->v.size /= 2; + smp->v.end /= 2; + smp->v.loopstart /= 2; + smp->v.loopend /= 2; + } + /*smp->v.loopend++;*/ + + smp->v.dummy = 0; + smp->v.truesize = 0; + smp->v.sf_id = sf->id; + + /* set up voice info */ + if ((zone = sf_zone_new(sflist, sf)) == NULL) { + sf_sample_delete(sflist, sf, smp); + return -ENOMEM; + } + + /* + * load wave data + */ + if (sflist->callback.sample_new) { + rc = sflist->callback.sample_new + (sflist->callback.private_data, smp, sflist->memhdr, + data, count); + if (rc < 0) { + sf_sample_delete(sflist, sf, smp); + kfree(zone); + return rc; + } + /* memory offset is updated after */ + } + + /* update the memory offset here */ + sflist->mem_used += smp->v.truesize; + + zone->v.sample = sample_id; /* the last sample */ + zone->v.rate_offset = calc_rate_offset(patch.base_freq); + note = freq_to_note(patch.base_note); + zone->v.root = note / 100; + zone->v.tune = -(note % 100); + zone->v.low = (freq_to_note(patch.low_note) + 99) / 100; + zone->v.high = freq_to_note(patch.high_note) / 100; + /* panning position; -128 - 127 => 0-127 */ + zone->v.pan = (patch.panning + 128) / 2; +#if 0 + snd_printk(KERN_DEBUG + "gus: basefrq=%d (ofs=%d) root=%d,tune=%d, range:%d-%d\n", + (int)patch.base_freq, zone->v.rate_offset, + zone->v.root, zone->v.tune, zone->v.low, zone->v.high); +#endif + + /* detuning is ignored */ + /* 6points volume envelope */ + if (patch.mode & WAVE_ENVELOPES) { + int attack, hold, decay, release; + attack = calc_gus_envelope_time + (patch.env_rate[0], 0, patch.env_offset[0]); + hold = calc_gus_envelope_time + (patch.env_rate[1], patch.env_offset[0], + patch.env_offset[1]); + decay = calc_gus_envelope_time + (patch.env_rate[2], patch.env_offset[1], + patch.env_offset[2]); + release = calc_gus_envelope_time + (patch.env_rate[3], patch.env_offset[1], + patch.env_offset[4]); + release += calc_gus_envelope_time + (patch.env_rate[4], patch.env_offset[3], + patch.env_offset[4]); + release += calc_gus_envelope_time + (patch.env_rate[5], patch.env_offset[4], + patch.env_offset[5]); + zone->v.parm.volatkhld = + (snd_sf_calc_parm_hold(hold) << 8) | + snd_sf_calc_parm_attack(attack); + zone->v.parm.voldcysus = (calc_gus_sustain(patch.env_offset[2]) << 8) | + snd_sf_calc_parm_decay(decay); + zone->v.parm.volrelease = 0x8000 | snd_sf_calc_parm_decay(release); + zone->v.attenuation = calc_gus_attenuation(patch.env_offset[0]); +#if 0 + snd_printk(KERN_DEBUG + "gus: atkhld=%x, dcysus=%x, volrel=%x, att=%d\n", + zone->v.parm.volatkhld, + zone->v.parm.voldcysus, + zone->v.parm.volrelease, + zone->v.attenuation); +#endif + } + + /* fast release */ + if (patch.mode & WAVE_FAST_RELEASE) { + zone->v.parm.volrelease = 0x807f; + } + + /* tremolo effect */ + if (patch.mode & WAVE_TREMOLO) { + int rate = (patch.tremolo_rate * 1000 / 38) / 42; + zone->v.parm.tremfrq = ((patch.tremolo_depth / 2) << 8) | rate; + } + /* vibrato effect */ + if (patch.mode & WAVE_VIBRATO) { + int rate = (patch.vibrato_rate * 1000 / 38) / 42; + zone->v.parm.fm2frq2 = ((patch.vibrato_depth / 6) << 8) | rate; + } + + /* scale_freq, scale_factor, volume, and fractions not implemented */ + + if (!(smp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT)) + zone->v.mode = SNDRV_SFNT_MODE_LOOPING; + else + zone->v.mode = 0; + + /* append to the tail of the list */ + /*zone->bank = ctrls[AWE_MD_GUS_BANK];*/ + zone->bank = 0; + zone->instr = patch.instr_no; + zone->mapped = 0; + zone->v.sf_id = sf->id; + + zone->sample = set_sample(sf, &zone->v); + + /* rebuild preset now */ + add_preset(sflist, zone); + + return 0; +} + +/* load GUS patch */ +int +snd_soundfont_load_guspatch(struct snd_sf_list *sflist, const char __user *data, + long count, int client) +{ + int rc; + lock_preset(sflist); + rc = load_guspatch(sflist, data, count, client); + unlock_preset(sflist); + return rc; +} + + +/* + * Rebuild the preset table. This is like a hash table in that it allows + * quick access to the zone information. For each preset there are zone + * structures linked by next_instr and by next_zone. Former is the whole + * link for this preset, and latter is the link for zone (i.e. instrument/ + * bank/key combination). + */ +static void +rebuild_presets(struct snd_sf_list *sflist) +{ + struct snd_soundfont *sf; + struct snd_sf_zone *cur; + + /* clear preset table */ + memset(sflist->presets, 0, sizeof(sflist->presets)); + + /* search all fonts and insert each font */ + for (sf = sflist->fonts; sf; sf = sf->next) { + for (cur = sf->zones; cur; cur = cur->next) { + if (! cur->mapped && cur->sample == NULL) { + /* try again to search the corresponding sample */ + cur->sample = set_sample(sf, &cur->v); + if (cur->sample == NULL) + continue; + } + + add_preset(sflist, cur); + } + } +} + + +/* + * add the given zone to preset table + */ +static void +add_preset(struct snd_sf_list *sflist, struct snd_sf_zone *cur) +{ + struct snd_sf_zone *zone; + int index; + + zone = search_first_zone(sflist, cur->bank, cur->instr, cur->v.low); + if (zone && zone->v.sf_id != cur->v.sf_id) { + /* different instrument was already defined */ + struct snd_sf_zone *p; + /* compare the allocated time */ + for (p = zone; p; p = p->next_zone) { + if (p->counter > cur->counter) + /* the current is older.. skipped */ + return; + } + /* remove old zones */ + delete_preset(sflist, zone); + zone = NULL; /* do not forget to clear this! */ + } + + /* prepend this zone */ + if ((index = get_index(cur->bank, cur->instr, cur->v.low)) < 0) + return; + cur->next_zone = zone; /* zone link */ + cur->next_instr = sflist->presets[index]; /* preset table link */ + sflist->presets[index] = cur; +} + +/* + * delete the given zones from preset_table + */ +static void +delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp) +{ + int index; + struct snd_sf_zone *p; + + if ((index = get_index(zp->bank, zp->instr, zp->v.low)) < 0) + return; + for (p = sflist->presets[index]; p; p = p->next_instr) { + while (p->next_instr == zp) { + p->next_instr = zp->next_instr; + zp = zp->next_zone; + if (zp == NULL) + return; + } + } +} + + +/* + * Search matching zones from preset table. + * The note can be rewritten by preset mapping (alias). + * The found zones are stored on 'table' array. max_layers defines + * the maximum number of elements in this array. + * This function returns the number of found zones. 0 if not found. + */ +int +snd_soundfont_search_zone(struct snd_sf_list *sflist, int *notep, int vel, + int preset, int bank, + int def_preset, int def_bank, + struct snd_sf_zone **table, int max_layers) +{ + int nvoices; + unsigned long flags; + + /* this function is supposed to be called atomically, + * so we check the lock. if it's busy, just returns 0 to + * tell the caller the busy state + */ + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->presets_locked) { + spin_unlock_irqrestore(&sflist->lock, flags); + return 0; + } + nvoices = search_zones(sflist, notep, vel, preset, bank, + table, max_layers, 0); + if (! nvoices) { + if (preset != def_preset || bank != def_bank) + nvoices = search_zones(sflist, notep, vel, + def_preset, def_bank, + table, max_layers, 0); + } + spin_unlock_irqrestore(&sflist->lock, flags); + return nvoices; +} + + +/* + * search the first matching zone + */ +static struct snd_sf_zone * +search_first_zone(struct snd_sf_list *sflist, int bank, int preset, int key) +{ + int index; + struct snd_sf_zone *zp; + + if ((index = get_index(bank, preset, key)) < 0) + return NULL; + for (zp = sflist->presets[index]; zp; zp = zp->next_instr) { + if (zp->instr == preset && zp->bank == bank) + return zp; + } + return NULL; +} + + +/* + * search matching zones from sflist. can be called recursively. + */ +static int +search_zones(struct snd_sf_list *sflist, int *notep, int vel, + int preset, int bank, struct snd_sf_zone **table, + int max_layers, int level) +{ + struct snd_sf_zone *zp; + int nvoices; + + zp = search_first_zone(sflist, bank, preset, *notep); + nvoices = 0; + for (; zp; zp = zp->next_zone) { + if (*notep >= zp->v.low && *notep <= zp->v.high && + vel >= zp->v.vellow && vel <= zp->v.velhigh) { + if (zp->mapped) { + /* search preset mapping (aliasing) */ + int key = zp->v.fixkey; + preset = zp->v.start; + bank = zp->v.end; + + if (level > 5) /* too deep alias level */ + return 0; + if (key < 0) + key = *notep; + nvoices = search_zones(sflist, &key, vel, + preset, bank, table, + max_layers, level + 1); + if (nvoices > 0) + *notep = key; + break; + } + table[nvoices++] = zp; + if (nvoices >= max_layers) + break; + } + } + + return nvoices; +} + + +/* calculate the index of preset table: + * drums are mapped from 128 to 255 according to its note key. + * other instruments are mapped from 0 to 127. + * if the index is out of range, return -1. + */ +static int +get_index(int bank, int instr, int key) +{ + int index; + if (SF_IS_DRUM_BANK(bank)) + index = key + SF_MAX_INSTRUMENTS; + else + index = instr; + index = index % SF_MAX_PRESETS; + if (index < 0) + return -1; + return index; +} + +/* + * Initialise the sflist structure. + */ +static void +snd_sf_init(struct snd_sf_list *sflist) +{ + memset(sflist->presets, 0, sizeof(sflist->presets)); + + sflist->mem_used = 0; + sflist->currsf = NULL; + sflist->open_client = -1; + sflist->fonts = NULL; + sflist->fonts_size = 0; + sflist->zone_counter = 0; + sflist->sample_counter = 0; + sflist->zone_locked = 0; + sflist->sample_locked = 0; +} + +/* + * Release all list records + */ +static void +snd_sf_clear(struct snd_sf_list *sflist) +{ + struct snd_soundfont *sf, *nextsf; + struct snd_sf_zone *zp, *nextzp; + struct snd_sf_sample *sp, *nextsp; + + for (sf = sflist->fonts; sf; sf = nextsf) { + nextsf = sf->next; + for (zp = sf->zones; zp; zp = nextzp) { + nextzp = zp->next; + kfree(zp); + } + for (sp = sf->samples; sp; sp = nextsp) { + nextsp = sp->next; + if (sflist->callback.sample_free) + sflist->callback.sample_free(sflist->callback.private_data, + sp, sflist->memhdr); + kfree(sp); + } + kfree(sf); + } + + snd_sf_init(sflist); +} + + +/* + * Create a new sflist structure + */ +struct snd_sf_list * +snd_sf_new(struct snd_sf_callback *callback, struct snd_util_memhdr *hdr) +{ + struct snd_sf_list *sflist; + + if ((sflist = kzalloc(sizeof(*sflist), GFP_KERNEL)) == NULL) + return NULL; + + mutex_init(&sflist->presets_mutex); + spin_lock_init(&sflist->lock); + sflist->memhdr = hdr; + + if (callback) + sflist->callback = *callback; + + snd_sf_init(sflist); + return sflist; +} + + +/* + * Free everything allocated off the sflist structure. + */ +void +snd_sf_free(struct snd_sf_list *sflist) +{ + if (sflist == NULL) + return; + + lock_preset(sflist); + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + snd_sf_clear(sflist); + unlock_preset(sflist); + + kfree(sflist); +} + +/* + * Remove all samples + * The soundcard should be silet before calling this function. + */ +int +snd_soundfont_remove_samples(struct snd_sf_list *sflist) +{ + lock_preset(sflist); + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + snd_sf_clear(sflist); + unlock_preset(sflist); + + return 0; +} + +/* + * Remove unlocked samples. + * The soundcard should be silent before calling this function. + */ +int +snd_soundfont_remove_unlocked(struct snd_sf_list *sflist) +{ + struct snd_soundfont *sf; + struct snd_sf_zone *zp, *nextzp; + struct snd_sf_sample *sp, *nextsp; + + lock_preset(sflist); + + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + + /* to be sure */ + memset(sflist->presets, 0, sizeof(sflist->presets)); + + for (sf = sflist->fonts; sf; sf = sf->next) { + for (zp = sf->zones; zp; zp = nextzp) { + if (zp->counter < sflist->zone_locked) + break; + nextzp = zp->next; + sf->zones = nextzp; + kfree(zp); + } + + for (sp = sf->samples; sp; sp = nextsp) { + if (sp->counter < sflist->sample_locked) + break; + nextsp = sp->next; + sf->samples = nextsp; + sflist->mem_used -= sp->v.truesize; + if (sflist->callback.sample_free) + sflist->callback.sample_free(sflist->callback.private_data, + sp, sflist->memhdr); + kfree(sp); + } + } + + sflist->zone_counter = sflist->zone_locked; + sflist->sample_counter = sflist->sample_locked; + + rebuild_presets(sflist); + + unlock_preset(sflist); + return 0; +} diff --git a/sound/synth/util_mem.c b/sound/synth/util_mem.c new file mode 100644 index 000000000..4bd1e9820 --- /dev/null +++ b/sound/synth/util_mem.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2000 Takashi Iwai + * + * Generic memory management routines for soundcard memory allocation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Takashi Iwai"); +MODULE_DESCRIPTION("Generic memory management routines for soundcard memory allocation"); +MODULE_LICENSE("GPL"); + +#define get_memblk(p) list_entry(p, struct snd_util_memblk, list) + +/* + * create a new memory manager + */ +struct snd_util_memhdr * +snd_util_memhdr_new(int memsize) +{ + struct snd_util_memhdr *hdr; + + hdr = kzalloc(sizeof(*hdr), GFP_KERNEL); + if (hdr == NULL) + return NULL; + hdr->size = memsize; + mutex_init(&hdr->block_mutex); + INIT_LIST_HEAD(&hdr->block); + + return hdr; +} + +/* + * free a memory manager + */ +void snd_util_memhdr_free(struct snd_util_memhdr *hdr) +{ + struct list_head *p; + + if (!hdr) + return; + /* release all blocks */ + while ((p = hdr->block.next) != &hdr->block) { + list_del(p); + kfree(get_memblk(p)); + } + kfree(hdr); +} + +/* + * allocate a memory block (without mutex) + */ +struct snd_util_memblk * +__snd_util_mem_alloc(struct snd_util_memhdr *hdr, int size) +{ + struct snd_util_memblk *blk; + unsigned int units, prev_offset; + struct list_head *p; + + if (snd_BUG_ON(!hdr || size <= 0)) + return NULL; + + /* word alignment */ + units = size; + if (units & 1) + units++; + if (units > hdr->size) + return NULL; + + /* look for empty block */ + prev_offset = 0; + list_for_each(p, &hdr->block) { + blk = get_memblk(p); + if (blk->offset - prev_offset >= units) + goto __found; + prev_offset = blk->offset + blk->size; + } + if (hdr->size - prev_offset < units) + return NULL; + +__found: + return __snd_util_memblk_new(hdr, units, p->prev); +} + + +/* + * create a new memory block with the given size + * the block is linked next to prev + */ +struct snd_util_memblk * +__snd_util_memblk_new(struct snd_util_memhdr *hdr, unsigned int units, + struct list_head *prev) +{ + struct snd_util_memblk *blk; + + blk = kmalloc(sizeof(struct snd_util_memblk) + hdr->block_extra_size, + GFP_KERNEL); + if (blk == NULL) + return NULL; + + if (prev == &hdr->block) + blk->offset = 0; + else { + struct snd_util_memblk *p = get_memblk(prev); + blk->offset = p->offset + p->size; + } + blk->size = units; + list_add(&blk->list, prev); + hdr->nblocks++; + hdr->used += units; + return blk; +} + + +/* + * allocate a memory block (with mutex) + */ +struct snd_util_memblk * +snd_util_mem_alloc(struct snd_util_memhdr *hdr, int size) +{ + struct snd_util_memblk *blk; + mutex_lock(&hdr->block_mutex); + blk = __snd_util_mem_alloc(hdr, size); + mutex_unlock(&hdr->block_mutex); + return blk; +} + + +/* + * remove the block from linked-list and free resource + * (without mutex) + */ +void +__snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk) +{ + list_del(&blk->list); + hdr->nblocks--; + hdr->used -= blk->size; + kfree(blk); +} + +/* + * free a memory block (with mutex) + */ +int snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk) +{ + if (snd_BUG_ON(!hdr || !blk)) + return -EINVAL; + + mutex_lock(&hdr->block_mutex); + __snd_util_mem_free(hdr, blk); + mutex_unlock(&hdr->block_mutex); + return 0; +} + +/* + * return available memory size + */ +int snd_util_mem_avail(struct snd_util_memhdr *hdr) +{ + unsigned int size; + mutex_lock(&hdr->block_mutex); + size = hdr->size - hdr->used; + mutex_unlock(&hdr->block_mutex); + return size; +} + + +EXPORT_SYMBOL(snd_util_memhdr_new); +EXPORT_SYMBOL(snd_util_memhdr_free); +EXPORT_SYMBOL(snd_util_mem_alloc); +EXPORT_SYMBOL(snd_util_mem_free); +EXPORT_SYMBOL(snd_util_mem_avail); +EXPORT_SYMBOL(__snd_util_mem_alloc); +EXPORT_SYMBOL(__snd_util_mem_free); +EXPORT_SYMBOL(__snd_util_memblk_new); diff --git a/sound/usb/6fire/Makefile b/sound/usb/6fire/Makefile new file mode 100644 index 000000000..dfce6ec53 --- /dev/null +++ b/sound/usb/6fire/Makefile @@ -0,0 +1,3 @@ +snd-usb-6fire-objs += chip.o comm.o midi.o control.o firmware.o pcm.o +obj-$(CONFIG_SND_USB_6FIRE) += snd-usb-6fire.o + diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c new file mode 100644 index 000000000..17d5e3ee6 --- /dev/null +++ b/sound/usb/6fire/chip.c @@ -0,0 +1,212 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Main routines and module definitions. + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "chip.h" +#include "firmware.h" +#include "pcm.h" +#include "control.h" +#include "comm.h" +#include "midi.h" + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Torsten Schenk "); +MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver"); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{{TerraTec,DMX 6Fire USB}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable card */ +static struct sfire_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; +static struct usb_device *devices[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the 6fire sound device"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the 6fire sound device."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable the 6fire sound device."); + +static DEFINE_MUTEX(register_mutex); + +static void usb6fire_chip_abort(struct sfire_chip *chip) +{ + if (chip) { + if (chip->pcm) + usb6fire_pcm_abort(chip); + if (chip->midi) + usb6fire_midi_abort(chip); + if (chip->comm) + usb6fire_comm_abort(chip); + if (chip->control) + usb6fire_control_abort(chip); + if (chip->card) { + snd_card_disconnect(chip->card); + snd_card_free_when_closed(chip->card); + chip->card = NULL; + } + } +} + +static void usb6fire_chip_destroy(struct sfire_chip *chip) +{ + if (chip) { + if (chip->pcm) + usb6fire_pcm_destroy(chip); + if (chip->midi) + usb6fire_midi_destroy(chip); + if (chip->comm) + usb6fire_comm_destroy(chip); + if (chip->control) + usb6fire_control_destroy(chip); + if (chip->card) + snd_card_free(chip->card); + } +} + +static int usb6fire_chip_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + int ret; + int i; + struct sfire_chip *chip = NULL; + struct usb_device *device = interface_to_usbdev(intf); + int regidx = -1; /* index in module parameter array */ + struct snd_card *card = NULL; + + /* look if we already serve this card and return if so */ + mutex_lock(®ister_mutex); + for (i = 0; i < SNDRV_CARDS; i++) { + if (devices[i] == device) { + if (chips[i]) + chips[i]->intf_count++; + usb_set_intfdata(intf, chips[i]); + mutex_unlock(®ister_mutex); + return 0; + } else if (!devices[i] && regidx < 0) + regidx = i; + } + if (regidx < 0) { + mutex_unlock(®ister_mutex); + dev_err(&intf->dev, "too many cards registered.\n"); + return -ENODEV; + } + devices[regidx] = device; + mutex_unlock(®ister_mutex); + + /* check, if firmware is present on device, upload it if not */ + ret = usb6fire_fw_init(intf); + if (ret < 0) + return ret; + else if (ret == FW_NOT_READY) /* firmware update performed */ + return 0; + + /* if we are here, card can be registered in alsa. */ + if (usb_set_interface(device, 0, 0) != 0) { + dev_err(&intf->dev, "can't set first interface.\n"); + return -EIO; + } + ret = snd_card_new(&intf->dev, index[regidx], id[regidx], + THIS_MODULE, sizeof(struct sfire_chip), &card); + if (ret < 0) { + dev_err(&intf->dev, "cannot create alsa card.\n"); + return ret; + } + strcpy(card->driver, "6FireUSB"); + strcpy(card->shortname, "TerraTec DMX6FireUSB"); + sprintf(card->longname, "%s at %d:%d", card->shortname, + device->bus->busnum, device->devnum); + + chip = card->private_data; + chips[regidx] = chip; + chip->dev = device; + chip->regidx = regidx; + chip->intf_count = 1; + chip->card = card; + + ret = usb6fire_comm_init(chip); + if (ret < 0) + goto destroy_chip; + + ret = usb6fire_midi_init(chip); + if (ret < 0) + goto destroy_chip; + + ret = usb6fire_pcm_init(chip); + if (ret < 0) + goto destroy_chip; + + ret = usb6fire_control_init(chip); + if (ret < 0) + goto destroy_chip; + + ret = snd_card_register(card); + if (ret < 0) { + dev_err(&intf->dev, "cannot register card."); + goto destroy_chip; + } + usb_set_intfdata(intf, chip); + return 0; + +destroy_chip: + usb6fire_chip_destroy(chip); + return ret; +} + +static void usb6fire_chip_disconnect(struct usb_interface *intf) +{ + struct sfire_chip *chip; + + chip = usb_get_intfdata(intf); + if (chip) { /* if !chip, fw upload has been performed */ + chip->intf_count--; + if (!chip->intf_count) { + mutex_lock(®ister_mutex); + devices[chip->regidx] = NULL; + chips[chip->regidx] = NULL; + mutex_unlock(®ister_mutex); + + chip->shutdown = true; + usb6fire_chip_abort(chip); + usb6fire_chip_destroy(chip); + } + } +} + +static const struct usb_device_id device_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0ccd, + .idProduct = 0x0080 + }, + {} +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +static struct usb_driver usb_driver = { + .name = "snd-usb-6fire", + .probe = usb6fire_chip_probe, + .disconnect = usb6fire_chip_disconnect, + .id_table = device_table, +}; + +module_usb_driver(usb_driver); diff --git a/sound/usb/6fire/chip.h b/sound/usb/6fire/chip.h new file mode 100644 index 000000000..bde02d105 --- /dev/null +++ b/sound/usb/6fire/chip.h @@ -0,0 +1,31 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef USB6FIRE_CHIP_H +#define USB6FIRE_CHIP_H + +#include "common.h" + +struct sfire_chip { + struct usb_device *dev; + struct snd_card *card; + int intf_count; /* number of registered interfaces */ + int regidx; /* index in module parameter arrays */ + bool shutdown; + + struct midi_runtime *midi; + struct pcm_runtime *pcm; + struct control_runtime *control; + struct comm_runtime *comm; +}; +#endif /* USB6FIRE_CHIP_H */ + diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c new file mode 100644 index 000000000..f29c115b9 --- /dev/null +++ b/sound/usb/6fire/comm.c @@ -0,0 +1,204 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Device communications + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "comm.h" +#include "chip.h" +#include "midi.h" + +enum { + COMM_EP = 1, + COMM_FPGA_EP = 2 +}; + +static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb, + u8 *buffer, void *context, void(*handler)(struct urb *urb)) +{ + usb_init_urb(urb); + urb->transfer_buffer = buffer; + urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP); + urb->complete = handler; + urb->context = context; + urb->interval = 1; + urb->dev = rt->chip->dev; +} + +static void usb6fire_comm_receiver_handler(struct urb *urb) +{ + struct comm_runtime *rt = urb->context; + struct midi_runtime *midi_rt = rt->chip->midi; + + if (!urb->status) { + if (rt->receiver_buffer[0] == 0x10) /* midi in event */ + if (midi_rt) + midi_rt->in_received(midi_rt, + rt->receiver_buffer + 2, + rt->receiver_buffer[1]); + } + + if (!rt->chip->shutdown) { + urb->status = 0; + urb->actual_length = 0; + if (usb_submit_urb(urb, GFP_ATOMIC) < 0) + dev_warn(&urb->dev->dev, + "comm data receiver aborted.\n"); + } +} + +static void usb6fire_comm_init_buffer(u8 *buffer, u8 id, u8 request, + u8 reg, u8 vl, u8 vh) +{ + buffer[0] = 0x01; + buffer[2] = request; + buffer[3] = id; + switch (request) { + case 0x02: + buffer[1] = 0x05; /* length (starting at buffer[2]) */ + buffer[4] = reg; + buffer[5] = vl; + buffer[6] = vh; + break; + + case 0x12: + buffer[1] = 0x0b; /* length (starting at buffer[2]) */ + buffer[4] = 0x00; + buffer[5] = 0x18; + buffer[6] = 0x05; + buffer[7] = 0x00; + buffer[8] = 0x01; + buffer[9] = 0x00; + buffer[10] = 0x9e; + buffer[11] = reg; + buffer[12] = vl; + break; + + case 0x20: + case 0x21: + case 0x22: + buffer[1] = 0x04; + buffer[4] = reg; + buffer[5] = vl; + break; + } +} + +static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev) +{ + int ret; + int actual_len; + + ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP), + buffer, buffer[1] + 2, &actual_len, 1000); + if (ret < 0) + return ret; + else if (actual_len != buffer[1] + 2) + return -EIO; + return 0; +} + +static int usb6fire_comm_write8(struct comm_runtime *rt, u8 request, + u8 reg, u8 value) +{ + u8 *buffer; + int ret; + + /* 13: maximum length of message */ + buffer = kmalloc(13, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + usb6fire_comm_init_buffer(buffer, 0x00, request, reg, value, 0x00); + ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev); + + kfree(buffer); + return ret; +} + +static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request, + u8 reg, u8 vl, u8 vh) +{ + u8 *buffer; + int ret; + + /* 13: maximum length of message */ + buffer = kmalloc(13, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + usb6fire_comm_init_buffer(buffer, 0x00, request, reg, vl, vh); + ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev); + + kfree(buffer); + return ret; +} + +int usb6fire_comm_init(struct sfire_chip *chip) +{ + struct comm_runtime *rt = kzalloc(sizeof(struct comm_runtime), + GFP_KERNEL); + struct urb *urb; + int ret; + + if (!rt) + return -ENOMEM; + + rt->receiver_buffer = kzalloc(COMM_RECEIVER_BUFSIZE, GFP_KERNEL); + if (!rt->receiver_buffer) { + kfree(rt); + return -ENOMEM; + } + + urb = &rt->receiver; + rt->serial = 1; + rt->chip = chip; + usb_init_urb(urb); + rt->init_urb = usb6fire_comm_init_urb; + rt->write8 = usb6fire_comm_write8; + rt->write16 = usb6fire_comm_write16; + + /* submit an urb that receives communication data from device */ + urb->transfer_buffer = rt->receiver_buffer; + urb->transfer_buffer_length = COMM_RECEIVER_BUFSIZE; + urb->pipe = usb_rcvintpipe(chip->dev, COMM_EP); + urb->dev = chip->dev; + urb->complete = usb6fire_comm_receiver_handler; + urb->context = rt; + urb->interval = 1; + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret < 0) { + kfree(rt->receiver_buffer); + kfree(rt); + dev_err(&chip->dev->dev, "cannot create comm data receiver."); + return ret; + } + chip->comm = rt; + return 0; +} + +void usb6fire_comm_abort(struct sfire_chip *chip) +{ + struct comm_runtime *rt = chip->comm; + + if (rt) + usb_poison_urb(&rt->receiver); +} + +void usb6fire_comm_destroy(struct sfire_chip *chip) +{ + struct comm_runtime *rt = chip->comm; + + kfree(rt->receiver_buffer); + kfree(rt); + chip->comm = NULL; +} diff --git a/sound/usb/6fire/comm.h b/sound/usb/6fire/comm.h new file mode 100644 index 000000000..780d5ed8e --- /dev/null +++ b/sound/usb/6fire/comm.h @@ -0,0 +1,43 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef USB6FIRE_COMM_H +#define USB6FIRE_COMM_H + +#include "common.h" + +enum /* settings for comm */ +{ + COMM_RECEIVER_BUFSIZE = 64, +}; + +struct comm_runtime { + struct sfire_chip *chip; + + struct urb receiver; + u8 *receiver_buffer; + + u8 serial; /* urb serial */ + + void (*init_urb)(struct comm_runtime *rt, struct urb *urb, u8 *buffer, + void *context, void(*handler)(struct urb *urb)); + /* writes control data to the device */ + int (*write8)(struct comm_runtime *rt, u8 request, u8 reg, u8 value); + int (*write16)(struct comm_runtime *rt, u8 request, u8 reg, + u8 vh, u8 vl); +}; + +int usb6fire_comm_init(struct sfire_chip *chip); +void usb6fire_comm_abort(struct sfire_chip *chip); +void usb6fire_comm_destroy(struct sfire_chip *chip); +#endif /* USB6FIRE_COMM_H */ + diff --git a/sound/usb/6fire/common.h b/sound/usb/6fire/common.h new file mode 100644 index 000000000..b6eb03ed1 --- /dev/null +++ b/sound/usb/6fire/common.h @@ -0,0 +1,29 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef USB6FIRE_COMMON_H +#define USB6FIRE_COMMON_H + +#include +#include +#include + +#define PREFIX "6fire: " + +struct sfire_chip; +struct midi_runtime; +struct pcm_runtime; +struct control_runtime; +struct comm_runtime; +#endif /* USB6FIRE_COMMON_H */ + diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c new file mode 100644 index 000000000..54656eed6 --- /dev/null +++ b/sound/usb/6fire/control.c @@ -0,0 +1,621 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Mixer control + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * Thanks to: + * - Holger Ruckdeschel: he found out how to control individual channel + * volumes and introduced mute switch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include + +#include "control.h" +#include "comm.h" +#include "chip.h" + +static const char * const opt_coax_texts[2] = { "Optical", "Coax" }; +static const char * const line_phono_texts[2] = { "Line", "Phono" }; + +/* + * data that needs to be sent to device. sets up card internal stuff. + * values dumped from windows driver and filtered by trial'n'error. + */ +static const struct { + u8 type; + u8 reg; + u8 value; +} +init_data[] = { + { 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 }, + { 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 }, + { 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 }, + { 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 }, + { 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 }, + { 0x12, 0x0d, 0x38 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 }, + { 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 }, + { 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 }, + { 0 } /* TERMINATING ENTRY */ +}; + +static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 }; +/* values to write to soundcard register for all samplerates */ +static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01}; +static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00}; + +static DECLARE_TLV_DB_MINMAX(tlv_output, -9000, 0); +static DECLARE_TLV_DB_MINMAX(tlv_input, -1500, 1500); + +enum { + DIGITAL_THRU_ONLY_SAMPLERATE = 3 +}; + +static void usb6fire_control_output_vol_update(struct control_runtime *rt) +{ + struct comm_runtime *comm_rt = rt->chip->comm; + int i; + + if (comm_rt) + for (i = 0; i < 6; i++) + if (!(rt->ovol_updated & (1 << i))) { + comm_rt->write8(comm_rt, 0x12, 0x0f + i, + 180 - rt->output_vol[i]); + rt->ovol_updated |= 1 << i; + } +} + +static void usb6fire_control_output_mute_update(struct control_runtime *rt) +{ + struct comm_runtime *comm_rt = rt->chip->comm; + + if (comm_rt) + comm_rt->write8(comm_rt, 0x12, 0x0e, ~rt->output_mute); +} + +static void usb6fire_control_input_vol_update(struct control_runtime *rt) +{ + struct comm_runtime *comm_rt = rt->chip->comm; + int i; + + if (comm_rt) + for (i = 0; i < 2; i++) + if (!(rt->ivol_updated & (1 << i))) { + comm_rt->write8(comm_rt, 0x12, 0x1c + i, + rt->input_vol[i] & 0x3f); + rt->ivol_updated |= 1 << i; + } +} + +static void usb6fire_control_line_phono_update(struct control_runtime *rt) +{ + struct comm_runtime *comm_rt = rt->chip->comm; + if (comm_rt) { + comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch); + comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch); + } +} + +static void usb6fire_control_opt_coax_update(struct control_runtime *rt) +{ + struct comm_runtime *comm_rt = rt->chip->comm; + if (comm_rt) { + comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch); + comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch); + } +} + +static int usb6fire_control_set_rate(struct control_runtime *rt, int rate) +{ + int ret; + struct usb_device *device = rt->chip->dev; + struct comm_runtime *comm_rt = rt->chip->comm; + + if (rate < 0 || rate >= CONTROL_N_RATES) + return -EINVAL; + + ret = usb_set_interface(device, 1, rates_altsetting[rate]); + if (ret < 0) + return ret; + + /* set soundcard clock */ + ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rate], + rates_6fire_vh[rate]); + if (ret < 0) + return ret; + + return 0; +} + +static int usb6fire_control_set_channels( + struct control_runtime *rt, int n_analog_out, + int n_analog_in, bool spdif_out, bool spdif_in) +{ + int ret; + struct comm_runtime *comm_rt = rt->chip->comm; + + /* enable analog inputs and outputs + * (one bit per stereo-channel) */ + ret = comm_rt->write16(comm_rt, 0x02, 0x02, + (1 << (n_analog_out / 2)) - 1, + (1 << (n_analog_in / 2)) - 1); + if (ret < 0) + return ret; + + /* disable digital inputs and outputs */ + /* TODO: use spdif_x to enable/disable digital channels */ + ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); + if (ret < 0) + return ret; + + return 0; +} + +static int usb6fire_control_streaming_update(struct control_runtime *rt) +{ + struct comm_runtime *comm_rt = rt->chip->comm; + + if (comm_rt) { + if (!rt->usb_streaming && rt->digital_thru_switch) + usb6fire_control_set_rate(rt, + DIGITAL_THRU_ONLY_SAMPLERATE); + return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, + (rt->usb_streaming ? 0x01 : 0x00) | + (rt->digital_thru_switch ? 0x08 : 0x00)); + } + return -EINVAL; +} + +static int usb6fire_control_output_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 = 180; + return 0; +} + +static int usb6fire_control_output_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + unsigned int ch = kcontrol->private_value; + int changed = 0; + + if (ch > 4) { + dev_err(&rt->chip->dev->dev, + "Invalid channel in volume control."); + return -EINVAL; + } + + if (rt->output_vol[ch] != ucontrol->value.integer.value[0]) { + rt->output_vol[ch] = ucontrol->value.integer.value[0]; + rt->ovol_updated &= ~(1 << ch); + changed = 1; + } + if (rt->output_vol[ch + 1] != ucontrol->value.integer.value[1]) { + rt->output_vol[ch + 1] = ucontrol->value.integer.value[1]; + rt->ovol_updated &= ~(2 << ch); + changed = 1; + } + + if (changed) + usb6fire_control_output_vol_update(rt); + + return changed; +} + +static int usb6fire_control_output_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + unsigned int ch = kcontrol->private_value; + + if (ch > 4) { + dev_err(&rt->chip->dev->dev, + "Invalid channel in volume control."); + return -EINVAL; + } + + ucontrol->value.integer.value[0] = rt->output_vol[ch]; + ucontrol->value.integer.value[1] = rt->output_vol[ch + 1]; + return 0; +} + +static int usb6fire_control_output_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + unsigned int ch = kcontrol->private_value; + u8 old = rt->output_mute; + u8 value = 0; + + if (ch > 4) { + dev_err(&rt->chip->dev->dev, + "Invalid channel in volume control."); + return -EINVAL; + } + + rt->output_mute &= ~(3 << ch); + if (ucontrol->value.integer.value[0]) + value |= 1; + if (ucontrol->value.integer.value[1]) + value |= 2; + rt->output_mute |= value << ch; + + if (rt->output_mute != old) + usb6fire_control_output_mute_update(rt); + + return rt->output_mute != old; +} + +static int usb6fire_control_output_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + unsigned int ch = kcontrol->private_value; + u8 value = rt->output_mute >> ch; + + if (ch > 4) { + dev_err(&rt->chip->dev->dev, + "Invalid channel in volume control."); + return -EINVAL; + } + + ucontrol->value.integer.value[0] = 1 & value; + value >>= 1; + ucontrol->value.integer.value[1] = 1 & value; + + return 0; +} + +static int usb6fire_control_input_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 = 30; + return 0; +} + +static int usb6fire_control_input_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + int changed = 0; + + if (rt->input_vol[0] != ucontrol->value.integer.value[0]) { + rt->input_vol[0] = ucontrol->value.integer.value[0] - 15; + rt->ivol_updated &= ~(1 << 0); + changed = 1; + } + if (rt->input_vol[1] != ucontrol->value.integer.value[1]) { + rt->input_vol[1] = ucontrol->value.integer.value[1] - 15; + rt->ivol_updated &= ~(1 << 1); + changed = 1; + } + + if (changed) + usb6fire_control_input_vol_update(rt); + + return changed; +} + +static int usb6fire_control_input_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = rt->input_vol[0] + 15; + ucontrol->value.integer.value[1] = rt->input_vol[1] + 15; + + return 0; +} + +static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, line_phono_texts); +} + +static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + int changed = 0; + if (rt->line_phono_switch != ucontrol->value.integer.value[0]) { + rt->line_phono_switch = ucontrol->value.integer.value[0]; + usb6fire_control_line_phono_update(rt); + changed = 1; + } + return changed; +} + +static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = rt->line_phono_switch; + return 0; +} + +static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 2, opt_coax_texts); +} + +static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + int changed = 0; + + if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) { + rt->opt_coax_switch = ucontrol->value.enumerated.item[0]; + usb6fire_control_opt_coax_update(rt); + changed = 1; + } + return changed; +} + +static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = rt->opt_coax_switch; + return 0; +} + +static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + int changed = 0; + + if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) { + rt->digital_thru_switch = ucontrol->value.integer.value[0]; + usb6fire_control_streaming_update(rt); + changed = 1; + } + return changed; +} + +static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct control_runtime *rt = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = rt->digital_thru_switch; + return 0; +} + +static struct snd_kcontrol_new vol_elements[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Playback Volume", + .index = 0, + .private_value = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = usb6fire_control_output_vol_info, + .get = usb6fire_control_output_vol_get, + .put = usb6fire_control_output_vol_put, + .tlv = { .p = tlv_output } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Playback Volume", + .index = 1, + .private_value = 2, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = usb6fire_control_output_vol_info, + .get = usb6fire_control_output_vol_get, + .put = usb6fire_control_output_vol_put, + .tlv = { .p = tlv_output } + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Playback Volume", + .index = 2, + .private_value = 4, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = usb6fire_control_output_vol_info, + .get = usb6fire_control_output_vol_get, + .put = usb6fire_control_output_vol_put, + .tlv = { .p = tlv_output } + }, + {} +}; + +static struct snd_kcontrol_new mute_elements[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Playback Switch", + .index = 0, + .private_value = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_stereo_info, + .get = usb6fire_control_output_mute_get, + .put = usb6fire_control_output_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Playback Switch", + .index = 1, + .private_value = 2, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_stereo_info, + .get = usb6fire_control_output_mute_get, + .put = usb6fire_control_output_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Playback Switch", + .index = 2, + .private_value = 4, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_stereo_info, + .get = usb6fire_control_output_mute_get, + .put = usb6fire_control_output_mute_put, + }, + {} +}; + +static struct snd_kcontrol_new elements[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line/Phono Capture Route", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = usb6fire_control_line_phono_info, + .get = usb6fire_control_line_phono_get, + .put = usb6fire_control_line_phono_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Opt/Coax Capture Route", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = usb6fire_control_opt_coax_info, + .get = usb6fire_control_opt_coax_get, + .put = usb6fire_control_opt_coax_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Thru Playback Route", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = usb6fire_control_digital_thru_get, + .put = usb6fire_control_digital_thru_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Analog Capture Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = usb6fire_control_input_vol_info, + .get = usb6fire_control_input_vol_get, + .put = usb6fire_control_input_vol_put, + .tlv = { .p = tlv_input } + }, + {} +}; + +static int usb6fire_control_add_virtual( + struct control_runtime *rt, + struct snd_card *card, + char *name, + struct snd_kcontrol_new *elems) +{ + int ret; + int i; + struct snd_kcontrol *vmaster = + snd_ctl_make_virtual_master(name, tlv_output); + struct snd_kcontrol *control; + + if (!vmaster) + return -ENOMEM; + ret = snd_ctl_add(card, vmaster); + if (ret < 0) + return ret; + + i = 0; + while (elems[i].name) { + control = snd_ctl_new1(&elems[i], rt); + if (!control) + return -ENOMEM; + ret = snd_ctl_add(card, control); + if (ret < 0) + return ret; + ret = snd_ctl_add_slave(vmaster, control); + if (ret < 0) + return ret; + i++; + } + return 0; +} + +int usb6fire_control_init(struct sfire_chip *chip) +{ + int i; + int ret; + struct control_runtime *rt = kzalloc(sizeof(struct control_runtime), + GFP_KERNEL); + struct comm_runtime *comm_rt = chip->comm; + + if (!rt) + return -ENOMEM; + + rt->chip = chip; + rt->update_streaming = usb6fire_control_streaming_update; + rt->set_rate = usb6fire_control_set_rate; + rt->set_channels = usb6fire_control_set_channels; + + i = 0; + while (init_data[i].type) { + comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg, + init_data[i].value); + i++; + } + + usb6fire_control_opt_coax_update(rt); + usb6fire_control_line_phono_update(rt); + usb6fire_control_output_vol_update(rt); + usb6fire_control_output_mute_update(rt); + usb6fire_control_input_vol_update(rt); + usb6fire_control_streaming_update(rt); + + ret = usb6fire_control_add_virtual(rt, chip->card, + "Master Playback Volume", vol_elements); + if (ret) { + dev_err(&chip->dev->dev, "cannot add control.\n"); + kfree(rt); + return ret; + } + ret = usb6fire_control_add_virtual(rt, chip->card, + "Master Playback Switch", mute_elements); + if (ret) { + dev_err(&chip->dev->dev, "cannot add control.\n"); + kfree(rt); + return ret; + } + + i = 0; + while (elements[i].name) { + ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt)); + if (ret < 0) { + kfree(rt); + dev_err(&chip->dev->dev, "cannot add control.\n"); + return ret; + } + i++; + } + + chip->control = rt; + return 0; +} + +void usb6fire_control_abort(struct sfire_chip *chip) +{} + +void usb6fire_control_destroy(struct sfire_chip *chip) +{ + kfree(chip->control); + chip->control = NULL; +} diff --git a/sound/usb/6fire/control.h b/sound/usb/6fire/control.h new file mode 100644 index 000000000..5a40ba143 --- /dev/null +++ b/sound/usb/6fire/control.h @@ -0,0 +1,57 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef USB6FIRE_CONTROL_H +#define USB6FIRE_CONTROL_H + +#include "common.h" + +enum { + CONTROL_MAX_ELEMENTS = 32 +}; + +enum { + CONTROL_RATE_44KHZ, + CONTROL_RATE_48KHZ, + CONTROL_RATE_88KHZ, + CONTROL_RATE_96KHZ, + CONTROL_RATE_176KHZ, + CONTROL_RATE_192KHZ, + CONTROL_N_RATES +}; + +struct control_runtime { + int (*update_streaming)(struct control_runtime *rt); + int (*set_rate)(struct control_runtime *rt, int rate); + int (*set_channels)(struct control_runtime *rt, int n_analog_out, + int n_analog_in, bool spdif_out, bool spdif_in); + + struct sfire_chip *chip; + + struct snd_kcontrol *element[CONTROL_MAX_ELEMENTS]; + bool opt_coax_switch; + bool line_phono_switch; + bool digital_thru_switch; + bool usb_streaming; + u8 output_vol[6]; + u8 ovol_updated; + u8 output_mute; + s8 input_vol[2]; + u8 ivol_updated; +}; + +int usb6fire_control_init(struct sfire_chip *chip); +void usb6fire_control_abort(struct sfire_chip *chip); +void usb6fire_control_destroy(struct sfire_chip *chip); +#endif /* USB6FIRE_CONTROL_H */ + diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c new file mode 100644 index 000000000..7a8911104 --- /dev/null +++ b/sound/usb/6fire/firmware.c @@ -0,0 +1,421 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Firmware loader + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include + +#include "firmware.h" +#include "chip.h" + +MODULE_FIRMWARE("6fire/dmx6firel2.ihx"); +MODULE_FIRMWARE("6fire/dmx6fireap.ihx"); +MODULE_FIRMWARE("6fire/dmx6firecf.bin"); + +enum { + FPGA_BUFSIZE = 512, FPGA_EP = 2 +}; + +/* + * wMaxPacketSize of pcm endpoints. + * keep synced with rates_in_packet_size and rates_out_packet_size in pcm.c + * fpp: frames per isopacket + * + * CAUTION: keep sizeof <= buffer[] in usb6fire_fw_init + */ +static const u8 ep_w_max_packet_size[] = { + 0xe4, 0x00, 0xe4, 0x00, /* alt 1: 228 EP2 and EP6 (7 fpp) */ + 0xa4, 0x01, 0xa4, 0x01, /* alt 2: 420 EP2 and EP6 (13 fpp)*/ + 0x94, 0x01, 0x5c, 0x02 /* alt 3: 404 EP2 and 604 EP6 (25 fpp) */ +}; + +static const u8 known_fw_versions[][2] = { + { 0x03, 0x01 } +}; + +struct ihex_record { + u16 address; + u8 len; + u8 data[256]; + char error; /* true if an error occurred parsing this record */ + + u8 max_len; /* maximum record length in whole ihex */ + + /* private */ + const char *txt_data; + unsigned int txt_length; + unsigned int txt_offset; /* current position in txt_data */ +}; + +static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc) +{ + u8 val = 0; + int hval; + + hval = hex_to_bin(data[0]); + if (hval >= 0) + val |= (hval << 4); + + hval = hex_to_bin(data[1]); + if (hval >= 0) + val |= hval; + + *crc += val; + return val; +} + +/* + * returns true if record is available, false otherwise. + * iff an error occurred, false will be returned and record->error will be true. + */ +static bool usb6fire_fw_ihex_next_record(struct ihex_record *record) +{ + u8 crc = 0; + u8 type; + int i; + + record->error = false; + + /* find begin of record (marked by a colon) */ + while (record->txt_offset < record->txt_length + && record->txt_data[record->txt_offset] != ':') + record->txt_offset++; + if (record->txt_offset == record->txt_length) + return false; + + /* number of characters needed for len, addr and type entries */ + record->txt_offset++; + if (record->txt_offset + 8 > record->txt_length) { + record->error = true; + return false; + } + + record->len = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + record->address = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc) << 8; + record->txt_offset += 2; + record->address |= usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + type = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + + /* number of characters needed for data and crc entries */ + if (record->txt_offset + 2 * (record->len + 1) > record->txt_length) { + record->error = true; + return false; + } + for (i = 0; i < record->len; i++) { + record->data[i] = usb6fire_fw_ihex_hex(record->txt_data + + record->txt_offset, &crc); + record->txt_offset += 2; + } + usb6fire_fw_ihex_hex(record->txt_data + record->txt_offset, &crc); + if (crc) { + record->error = true; + return false; + } + + if (type == 1 || !record->len) /* eof */ + return false; + else if (type == 0) + return true; + else { + record->error = true; + return false; + } +} + +static int usb6fire_fw_ihex_init(const struct firmware *fw, + struct ihex_record *record) +{ + record->txt_data = fw->data; + record->txt_length = fw->size; + record->txt_offset = 0; + record->max_len = 0; + /* read all records, if loop ends, record->error indicates, + * whether ihex is valid. */ + while (usb6fire_fw_ihex_next_record(record)) + record->max_len = max(record->len, record->max_len); + if (record->error) + return -EINVAL; + record->txt_offset = 0; + return 0; +} + +static int usb6fire_fw_ezusb_write(struct usb_device *device, + int type, int value, char *data, int len) +{ + int ret; + + ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, 0, data, len, 1000); + if (ret < 0) + return ret; + else if (ret != len) + return -EIO; + return 0; +} + +static int usb6fire_fw_ezusb_read(struct usb_device *device, + int type, int value, char *data, int len) +{ + int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value, + 0, data, len, 1000); + if (ret < 0) + return ret; + else if (ret != len) + return -EIO; + return 0; +} + +static int usb6fire_fw_fpga_write(struct usb_device *device, + char *data, int len) +{ + int actual_len; + int ret; + + ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len, + &actual_len, 1000); + if (ret < 0) + return ret; + else if (actual_len != len) + return -EIO; + return 0; +} + +static int usb6fire_fw_ezusb_upload( + struct usb_interface *intf, const char *fwname, + unsigned int postaddr, u8 *postdata, unsigned int postlen) +{ + int ret; + u8 data; + struct usb_device *device = interface_to_usbdev(intf); + const struct firmware *fw = NULL; + struct ihex_record *rec = kmalloc(sizeof(struct ihex_record), + GFP_KERNEL); + + if (!rec) + return -ENOMEM; + + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + kfree(rec); + dev_err(&intf->dev, + "error requesting ezusb firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { + kfree(rec); + release_firmware(fw); + dev_err(&intf->dev, + "error validating ezusb firmware %s.\n", fwname); + return ret; + } + /* upload firmware image */ + data = 0x01; /* stop ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret < 0) { + kfree(rec); + release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: begin message.\n", + fwname); + return ret; + } + + while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, + rec->data, rec->len); + if (ret < 0) { + kfree(rec); + release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: data urb.\n", + fwname); + return ret; + } + } + + release_firmware(fw); + kfree(rec); + if (postdata) { /* write data after firmware has been uploaded */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, + postdata, postlen); + if (ret < 0) { + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: post urb.\n", + fwname); + return ret; + } + } + + data = 0x00; /* resume ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret < 0) { + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: end message.\n", + fwname); + return ret; + } + return 0; +} + +static int usb6fire_fw_fpga_upload( + struct usb_interface *intf, const char *fwname) +{ + int ret; + int i; + struct usb_device *device = interface_to_usbdev(intf); + u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); + const char *c; + const char *end; + const struct firmware *fw; + + if (!buffer) + return -ENOMEM; + + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + dev_err(&intf->dev, "unable to get fpga firmware %s.\n", + fwname); + kfree(buffer); + return -EIO; + } + + c = fw->data; + end = fw->data + fw->size; + + ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); + if (ret < 0) { + kfree(buffer); + release_firmware(fw); + dev_err(&intf->dev, + "unable to upload fpga firmware: begin urb.\n"); + return ret; + } + + while (c != end) { + for (i = 0; c != end && i < FPGA_BUFSIZE; i++, c++) + buffer[i] = bitrev8((u8)*c); + + ret = usb6fire_fw_fpga_write(device, buffer, i); + if (ret < 0) { + release_firmware(fw); + kfree(buffer); + dev_err(&intf->dev, + "unable to upload fpga firmware: fw urb.\n"); + return ret; + } + } + release_firmware(fw); + kfree(buffer); + + ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0); + if (ret < 0) { + dev_err(&intf->dev, + "unable to upload fpga firmware: end urb.\n"); + return ret; + } + return 0; +} + +/* check, if the firmware version the devices has currently loaded + * is known by this driver. 'version' needs to have 4 bytes version + * info data. */ +static int usb6fire_fw_check(struct usb_interface *intf, const u8 *version) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(known_fw_versions); i++) + if (!memcmp(version, known_fw_versions + i, 2)) + return 0; + + dev_err(&intf->dev, "invalid firmware version in device: %4ph. " + "please reconnect to power. if this failure " + "still happens, check your firmware installation.", + version); + return -EINVAL; +} + +int usb6fire_fw_init(struct usb_interface *intf) +{ + int i; + int ret; + struct usb_device *device = interface_to_usbdev(intf); + /* buffer: 8 receiving bytes from device and + * sizeof(EP_W_MAX_PACKET_SIZE) bytes for non-const copy */ + u8 buffer[12]; + + ret = usb6fire_fw_ezusb_read(device, 1, 0, buffer, 8); + if (ret < 0) { + dev_err(&intf->dev, + "unable to receive device firmware state.\n"); + return ret; + } + if (buffer[0] != 0xeb || buffer[1] != 0xaa || buffer[2] != 0x55) { + dev_err(&intf->dev, + "unknown device firmware state received from device:"); + for (i = 0; i < 8; i++) + printk(KERN_CONT "%02x ", buffer[i]); + printk(KERN_CONT "\n"); + return -EIO; + } + /* do we need fpga loader ezusb firmware? */ + if (buffer[3] == 0x01) { + ret = usb6fire_fw_ezusb_upload(intf, + "6fire/dmx6firel2.ihx", 0, NULL, 0); + if (ret < 0) + return ret; + return FW_NOT_READY; + } + /* do we need fpga firmware and application ezusb firmware? */ + else if (buffer[3] == 0x02) { + ret = usb6fire_fw_check(intf, buffer + 4); + if (ret < 0) + return ret; + ret = usb6fire_fw_fpga_upload(intf, "6fire/dmx6firecf.bin"); + if (ret < 0) + return ret; + memcpy(buffer, ep_w_max_packet_size, + sizeof(ep_w_max_packet_size)); + ret = usb6fire_fw_ezusb_upload(intf, "6fire/dmx6fireap.ihx", + 0x0003, buffer, sizeof(ep_w_max_packet_size)); + if (ret < 0) + return ret; + return FW_NOT_READY; + } + /* all fw loaded? */ + else if (buffer[3] == 0x03) + return usb6fire_fw_check(intf, buffer + 4); + /* unknown data? */ + else { + dev_err(&intf->dev, + "unknown device firmware state received from device: "); + for (i = 0; i < 8; i++) + printk(KERN_CONT "%02x ", buffer[i]); + printk(KERN_CONT "\n"); + return -EIO; + } + return 0; +} + diff --git a/sound/usb/6fire/firmware.h b/sound/usb/6fire/firmware.h new file mode 100644 index 000000000..c109c4f75 --- /dev/null +++ b/sound/usb/6fire/firmware.h @@ -0,0 +1,27 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef USB6FIRE_FIRMWARE_H +#define USB6FIRE_FIRMWARE_H + +#include "common.h" + +enum /* firmware state of device */ +{ + FW_READY = 0, + FW_NOT_READY = 1 +}; + +int usb6fire_fw_init(struct usb_interface *intf); +#endif /* USB6FIRE_FIRMWARE_H */ + diff --git a/sound/usb/6fire/midi.c b/sound/usb/6fire/midi.c new file mode 100644 index 000000000..aa5adbb6e --- /dev/null +++ b/sound/usb/6fire/midi.c @@ -0,0 +1,218 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Rawmidi driver + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include + +#include "midi.h" +#include "chip.h" +#include "comm.h" + +enum { + MIDI_BUFSIZE = 64 +}; + +static void usb6fire_midi_out_handler(struct urb *urb) +{ + struct midi_runtime *rt = urb->context; + int ret; + unsigned long flags; + + spin_lock_irqsave(&rt->out_lock, flags); + + if (rt->out) { + ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4, + MIDI_BUFSIZE - 4); + if (ret > 0) { /* more data available, send next packet */ + rt->out_buffer[1] = ret + 2; + rt->out_buffer[3] = rt->out_serial++; + urb->transfer_buffer_length = ret + 4; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&urb->dev->dev, + "midi out urb submit failed: %d\n", + ret); + } else /* no more data to transmit */ + rt->out = NULL; + } + spin_unlock_irqrestore(&rt->out_lock, flags); +} + +static void usb6fire_midi_in_received( + struct midi_runtime *rt, u8 *data, int length) +{ + unsigned long flags; + + spin_lock_irqsave(&rt->in_lock, flags); + if (rt->in) + snd_rawmidi_receive(rt->in, data, length); + spin_unlock_irqrestore(&rt->in_lock, flags); +} + +static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static int usb6fire_midi_out_close(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static void usb6fire_midi_out_trigger( + struct snd_rawmidi_substream *alsa_sub, int up) +{ + struct midi_runtime *rt = alsa_sub->rmidi->private_data; + struct urb *urb = &rt->out_urb; + __s8 ret; + unsigned long flags; + + spin_lock_irqsave(&rt->out_lock, flags); + if (up) { /* start transfer */ + if (rt->out) { /* we are already transmitting so just return */ + spin_unlock_irqrestore(&rt->out_lock, flags); + return; + } + + ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4, + MIDI_BUFSIZE - 4); + if (ret > 0) { + rt->out_buffer[1] = ret + 2; + rt->out_buffer[3] = rt->out_serial++; + urb->transfer_buffer_length = ret + 4; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&urb->dev->dev, + "midi out urb submit failed: %d\n", + ret); + else + rt->out = alsa_sub; + } + } else if (rt->out == alsa_sub) + rt->out = NULL; + spin_unlock_irqrestore(&rt->out_lock, flags); +} + +static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub) +{ + struct midi_runtime *rt = alsa_sub->rmidi->private_data; + int retry = 0; + + while (rt->out && retry++ < 100) + msleep(10); +} + +static int usb6fire_midi_in_open(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static int usb6fire_midi_in_close(struct snd_rawmidi_substream *alsa_sub) +{ + return 0; +} + +static void usb6fire_midi_in_trigger( + struct snd_rawmidi_substream *alsa_sub, int up) +{ + struct midi_runtime *rt = alsa_sub->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&rt->in_lock, flags); + if (up) + rt->in = alsa_sub; + else + rt->in = NULL; + spin_unlock_irqrestore(&rt->in_lock, flags); +} + +static const struct snd_rawmidi_ops out_ops = { + .open = usb6fire_midi_out_open, + .close = usb6fire_midi_out_close, + .trigger = usb6fire_midi_out_trigger, + .drain = usb6fire_midi_out_drain +}; + +static const struct snd_rawmidi_ops in_ops = { + .open = usb6fire_midi_in_open, + .close = usb6fire_midi_in_close, + .trigger = usb6fire_midi_in_trigger +}; + +int usb6fire_midi_init(struct sfire_chip *chip) +{ + int ret; + struct midi_runtime *rt = kzalloc(sizeof(struct midi_runtime), + GFP_KERNEL); + struct comm_runtime *comm_rt = chip->comm; + + if (!rt) + return -ENOMEM; + + rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL); + if (!rt->out_buffer) { + kfree(rt); + return -ENOMEM; + } + + rt->chip = chip; + rt->in_received = usb6fire_midi_in_received; + rt->out_buffer[0] = 0x80; /* 'send midi' command */ + rt->out_buffer[1] = 0x00; /* size of data */ + rt->out_buffer[2] = 0x00; /* always 0 */ + spin_lock_init(&rt->in_lock); + spin_lock_init(&rt->out_lock); + + comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt, + usb6fire_midi_out_handler); + + ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance); + if (ret < 0) { + kfree(rt->out_buffer); + kfree(rt); + dev_err(&chip->dev->dev, "unable to create midi.\n"); + return ret; + } + rt->instance->private_data = rt; + strcpy(rt->instance->name, "DMX6FireUSB MIDI"); + rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_OUTPUT, + &out_ops); + snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_INPUT, + &in_ops); + + chip->midi = rt; + return 0; +} + +void usb6fire_midi_abort(struct sfire_chip *chip) +{ + struct midi_runtime *rt = chip->midi; + + if (rt) + usb_poison_urb(&rt->out_urb); +} + +void usb6fire_midi_destroy(struct sfire_chip *chip) +{ + struct midi_runtime *rt = chip->midi; + + kfree(rt->out_buffer); + kfree(rt); + chip->midi = NULL; +} diff --git a/sound/usb/6fire/midi.h b/sound/usb/6fire/midi.h new file mode 100644 index 000000000..84851b9f5 --- /dev/null +++ b/sound/usb/6fire/midi.h @@ -0,0 +1,41 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef USB6FIRE_MIDI_H +#define USB6FIRE_MIDI_H + +#include "common.h" + +struct midi_runtime { + struct sfire_chip *chip; + struct snd_rawmidi *instance; + + struct snd_rawmidi_substream *in; + char in_active; + + spinlock_t in_lock; + spinlock_t out_lock; + struct snd_rawmidi_substream *out; + struct urb out_urb; + u8 out_serial; /* serial number of out packet */ + u8 *out_buffer; + int buffer_offset; + + void (*in_received)(struct midi_runtime *rt, u8 *data, int length); +}; + +int usb6fire_midi_init(struct sfire_chip *chip); +void usb6fire_midi_abort(struct sfire_chip *chip); +void usb6fire_midi_destroy(struct sfire_chip *chip); +#endif /* USB6FIRE_MIDI_H */ + diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c new file mode 100644 index 000000000..f8ef3e2a8 --- /dev/null +++ b/sound/usb/6fire/pcm.c @@ -0,0 +1,709 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * PCM driver + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "pcm.h" +#include "chip.h" +#include "comm.h" +#include "control.h" + +enum { + OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4 +}; + +/* keep next two synced with + * FW_EP_W_MAX_PACKET_SIZE[] and RATES_MAX_PACKET_SIZE + * and CONTROL_RATE_XXX in control.h */ +static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 }; +static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 }; +static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 }; +static const int rates_alsaid[] = { + SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000, + SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000, + SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000 }; + +enum { /* settings for pcm */ + OUT_EP = 6, IN_EP = 2, MAX_BUFSIZE = 128 * 1024 +}; + +enum { /* pcm streaming states */ + STREAM_DISABLED, /* no pcm streaming */ + STREAM_STARTING, /* pcm streaming requested, waiting to become ready */ + STREAM_RUNNING, /* pcm streaming running */ + STREAM_STOPPING +}; + +static const struct snd_pcm_hardware pcm_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_S24_LE | SNDRV_PCM_FMTBIT_S32_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 = 1, + .channels_max = 0, /* set in pcm_open, depending on capture/playback */ + .buffer_bytes_max = MAX_BUFSIZE, + .period_bytes_min = PCM_N_PACKETS_PER_URB * (PCM_MAX_PACKET_SIZE - 4), + .period_bytes_max = MAX_BUFSIZE, + .periods_min = 2, + .periods_max = 1024 +}; + +static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) +{ + int ret; + struct control_runtime *ctrl_rt = rt->chip->control; + + ctrl_rt->usb_streaming = false; + ret = ctrl_rt->update_streaming(ctrl_rt); + if (ret < 0) { + dev_err(&rt->chip->dev->dev, + "error stopping streaming while setting samplerate %d.\n", + rates[rt->rate]); + return ret; + } + + ret = ctrl_rt->set_rate(ctrl_rt, rt->rate); + if (ret < 0) { + dev_err(&rt->chip->dev->dev, + "error setting samplerate %d.\n", + rates[rt->rate]); + return ret; + } + + ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS, + false, false); + if (ret < 0) { + dev_err(&rt->chip->dev->dev, + "error initializing channels while setting samplerate %d.\n", + rates[rt->rate]); + return ret; + } + + ctrl_rt->usb_streaming = true; + ret = ctrl_rt->update_streaming(ctrl_rt); + if (ret < 0) { + dev_err(&rt->chip->dev->dev, + "error starting streaming while setting samplerate %d.\n", + rates[rt->rate]); + return ret; + } + + rt->in_n_analog = IN_N_CHANNELS; + rt->out_n_analog = OUT_N_CHANNELS; + rt->in_packet_size = rates_in_packet_size[rt->rate]; + rt->out_packet_size = rates_out_packet_size[rt->rate]; + return 0; +} + +static struct pcm_substream *usb6fire_pcm_get_substream( + struct snd_pcm_substream *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + + if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + return &rt->playback; + else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) + return &rt->capture; + dev_err(&rt->chip->dev->dev, "error getting pcm substream slot.\n"); + return NULL; +} + +/* call with stream_mutex locked */ +static void usb6fire_pcm_stream_stop(struct pcm_runtime *rt) +{ + int i; + struct control_runtime *ctrl_rt = rt->chip->control; + + if (rt->stream_state != STREAM_DISABLED) { + + rt->stream_state = STREAM_STOPPING; + + for (i = 0; i < PCM_N_URBS; i++) { + usb_kill_urb(&rt->in_urbs[i].instance); + usb_kill_urb(&rt->out_urbs[i].instance); + } + ctrl_rt->usb_streaming = false; + ctrl_rt->update_streaming(ctrl_rt); + rt->stream_state = STREAM_DISABLED; + } +} + +/* call with stream_mutex locked */ +static int usb6fire_pcm_stream_start(struct pcm_runtime *rt) +{ + int ret; + int i; + int k; + struct usb_iso_packet_descriptor *packet; + + if (rt->stream_state == STREAM_DISABLED) { + /* submit our in urbs */ + rt->stream_wait_cond = false; + rt->stream_state = STREAM_STARTING; + for (i = 0; i < PCM_N_URBS; i++) { + for (k = 0; k < PCM_N_PACKETS_PER_URB; k++) { + packet = &rt->in_urbs[i].packets[k]; + packet->offset = k * rt->in_packet_size; + packet->length = rt->in_packet_size; + packet->actual_length = 0; + packet->status = 0; + } + ret = usb_submit_urb(&rt->in_urbs[i].instance, + GFP_ATOMIC); + if (ret) { + usb6fire_pcm_stream_stop(rt); + return ret; + } + } + + /* wait for first out urb to return (sent in in urb handler) */ + wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond, + HZ); + if (rt->stream_wait_cond) + rt->stream_state = STREAM_RUNNING; + else { + usb6fire_pcm_stream_stop(rt); + return -EIO; + } + } + return 0; +} + +/* call with substream locked */ +static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb) +{ + int i; + int frame; + int frame_count; + unsigned int total_length = 0; + struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance); + struct snd_pcm_runtime *alsa_rt = sub->instance->runtime; + u32 *src = NULL; + u32 *dest = (u32 *) (alsa_rt->dma_area + sub->dma_off + * (alsa_rt->frame_bits >> 3)); + u32 *dest_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size + * (alsa_rt->frame_bits >> 3)); + int bytes_per_frame = alsa_rt->channels << 2; + + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + /* at least 4 header bytes for valid packet. + * after that: 32 bits per sample for analog channels */ + if (urb->packets[i].actual_length > 4) + frame_count = (urb->packets[i].actual_length - 4) + / (rt->in_n_analog << 2); + else + frame_count = 0; + + if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE) + src = (u32 *) (urb->buffer + total_length); + else if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE) + src = (u32 *) (urb->buffer - 1 + total_length); + else + return; + src++; /* skip leading 4 bytes of every packet */ + total_length += urb->packets[i].length; + for (frame = 0; frame < frame_count; frame++) { + memcpy(dest, src, bytes_per_frame); + dest += alsa_rt->channels; + src += rt->in_n_analog; + sub->dma_off++; + sub->period_off++; + if (dest == dest_end) { + sub->dma_off = 0; + dest = (u32 *) alsa_rt->dma_area; + } + } + } +} + +/* call with substream locked */ +static void usb6fire_pcm_playback(struct pcm_substream *sub, + struct pcm_urb *urb) +{ + int i; + int frame; + int frame_count; + struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance); + struct snd_pcm_runtime *alsa_rt = sub->instance->runtime; + u32 *src = (u32 *) (alsa_rt->dma_area + sub->dma_off + * (alsa_rt->frame_bits >> 3)); + u32 *src_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size + * (alsa_rt->frame_bits >> 3)); + u32 *dest; + int bytes_per_frame = alsa_rt->channels << 2; + + if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE) + dest = (u32 *) (urb->buffer - 1); + else if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE) + dest = (u32 *) (urb->buffer); + else { + dev_err(&rt->chip->dev->dev, "Unknown sample format."); + return; + } + + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + /* at least 4 header bytes for valid packet. + * after that: 32 bits per sample for analog channels */ + if (urb->packets[i].length > 4) + frame_count = (urb->packets[i].length - 4) + / (rt->out_n_analog << 2); + else + frame_count = 0; + dest++; /* skip leading 4 bytes of every frame */ + for (frame = 0; frame < frame_count; frame++) { + memcpy(dest, src, bytes_per_frame); + src += alsa_rt->channels; + dest += rt->out_n_analog; + sub->dma_off++; + sub->period_off++; + if (src == src_end) { + src = (u32 *) alsa_rt->dma_area; + sub->dma_off = 0; + } + } + } +} + +static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) +{ + struct pcm_urb *in_urb = usb_urb->context; + struct pcm_urb *out_urb = in_urb->peer; + struct pcm_runtime *rt = in_urb->chip->pcm; + struct pcm_substream *sub; + unsigned long flags; + int total_length = 0; + int frame_count; + int frame; + int channel; + int i; + u8 *dest; + + if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING) + return; + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) + if (in_urb->packets[i].status) { + rt->panic = true; + return; + } + + if (rt->stream_state == STREAM_DISABLED) { + dev_err(&rt->chip->dev->dev, + "internal error: stream disabled in in-urb handler.\n"); + return; + } + + /* receive our capture data */ + sub = &rt->capture; + spin_lock_irqsave(&sub->lock, flags); + if (sub->active) { + usb6fire_pcm_capture(sub, in_urb); + if (sub->period_off >= sub->instance->runtime->period_size) { + sub->period_off %= sub->instance->runtime->period_size; + spin_unlock_irqrestore(&sub->lock, flags); + snd_pcm_period_elapsed(sub->instance); + } else + spin_unlock_irqrestore(&sub->lock, flags); + } else + spin_unlock_irqrestore(&sub->lock, flags); + + /* setup out urb structure */ + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + out_urb->packets[i].offset = total_length; + out_urb->packets[i].length = (in_urb->packets[i].actual_length + - 4) / (rt->in_n_analog << 2) + * (rt->out_n_analog << 2) + 4; + out_urb->packets[i].status = 0; + total_length += out_urb->packets[i].length; + } + memset(out_urb->buffer, 0, total_length); + + /* now send our playback data (if a free out urb was found) */ + sub = &rt->playback; + spin_lock_irqsave(&sub->lock, flags); + if (sub->active) { + usb6fire_pcm_playback(sub, out_urb); + if (sub->period_off >= sub->instance->runtime->period_size) { + sub->period_off %= sub->instance->runtime->period_size; + spin_unlock_irqrestore(&sub->lock, flags); + snd_pcm_period_elapsed(sub->instance); + } else + spin_unlock_irqrestore(&sub->lock, flags); + } else + spin_unlock_irqrestore(&sub->lock, flags); + + /* setup the 4th byte of each sample (0x40 for analog channels) */ + dest = out_urb->buffer; + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) + if (out_urb->packets[i].length >= 4) { + frame_count = (out_urb->packets[i].length - 4) + / (rt->out_n_analog << 2); + *(dest++) = 0xaa; + *(dest++) = 0xaa; + *(dest++) = frame_count; + *(dest++) = 0x00; + for (frame = 0; frame < frame_count; frame++) + for (channel = 0; + channel < rt->out_n_analog; + channel++) { + dest += 3; /* skip sample data */ + *(dest++) = 0x40; + } + } + usb_submit_urb(&out_urb->instance, GFP_ATOMIC); + usb_submit_urb(&in_urb->instance, GFP_ATOMIC); +} + +static void usb6fire_pcm_out_urb_handler(struct urb *usb_urb) +{ + struct pcm_urb *urb = usb_urb->context; + struct pcm_runtime *rt = urb->chip->pcm; + + if (rt->stream_state == STREAM_STARTING) { + rt->stream_wait_cond = true; + wake_up(&rt->stream_wait_queue); + } +} + +static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + struct pcm_substream *sub = NULL; + struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; + + if (rt->panic) + return -EPIPE; + + mutex_lock(&rt->stream_mutex); + alsa_rt->hw = pcm_hw; + + if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (rt->rate < ARRAY_SIZE(rates)) + alsa_rt->hw.rates = rates_alsaid[rt->rate]; + alsa_rt->hw.channels_max = OUT_N_CHANNELS; + sub = &rt->playback; + } else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (rt->rate < ARRAY_SIZE(rates)) + alsa_rt->hw.rates = rates_alsaid[rt->rate]; + alsa_rt->hw.channels_max = IN_N_CHANNELS; + sub = &rt->capture; + } + + if (!sub) { + mutex_unlock(&rt->stream_mutex); + dev_err(&rt->chip->dev->dev, "invalid stream type.\n"); + return -EINVAL; + } + + sub->instance = alsa_sub; + sub->active = false; + mutex_unlock(&rt->stream_mutex); + return 0; +} + +static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub); + unsigned long flags; + + if (rt->panic) + return 0; + + mutex_lock(&rt->stream_mutex); + if (sub) { + /* deactivate substream */ + spin_lock_irqsave(&sub->lock, flags); + sub->instance = NULL; + sub->active = false; + spin_unlock_irqrestore(&sub->lock, flags); + + /* all substreams closed? if so, stop streaming */ + if (!rt->playback.instance && !rt->capture.instance) { + usb6fire_pcm_stream_stop(rt); + rt->rate = ARRAY_SIZE(rates); + } + } + mutex_unlock(&rt->stream_mutex); + return 0; +} + +static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub, + params_buffer_bytes(hw_params)); +} + +static int usb6fire_pcm_hw_free(struct snd_pcm_substream *alsa_sub) +{ + return snd_pcm_lib_free_vmalloc_buffer(alsa_sub); +} + +static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub); + struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; + int ret; + + if (rt->panic) + return -EPIPE; + if (!sub) + return -ENODEV; + + mutex_lock(&rt->stream_mutex); + sub->dma_off = 0; + sub->period_off = 0; + + if (rt->stream_state == STREAM_DISABLED) { + for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++) + if (alsa_rt->rate == rates[rt->rate]) + break; + if (rt->rate == ARRAY_SIZE(rates)) { + mutex_unlock(&rt->stream_mutex); + dev_err(&rt->chip->dev->dev, + "invalid rate %d in prepare.\n", + alsa_rt->rate); + return -EINVAL; + } + + ret = usb6fire_pcm_set_rate(rt); + if (ret) { + mutex_unlock(&rt->stream_mutex); + return ret; + } + ret = usb6fire_pcm_stream_start(rt); + if (ret) { + mutex_unlock(&rt->stream_mutex); + dev_err(&rt->chip->dev->dev, + "could not start pcm stream.\n"); + return ret; + } + } + mutex_unlock(&rt->stream_mutex); + return 0; +} + +static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd) +{ + struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub); + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + unsigned long flags; + + if (rt->panic) + return -EPIPE; + if (!sub) + return -ENODEV; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&sub->lock, flags); + sub->active = true; + spin_unlock_irqrestore(&sub->lock, flags); + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&sub->lock, flags); + sub->active = false; + spin_unlock_irqrestore(&sub->lock, flags); + return 0; + + default: + return -EINVAL; + } +} + +static snd_pcm_uframes_t usb6fire_pcm_pointer( + struct snd_pcm_substream *alsa_sub) +{ + struct pcm_substream *sub = usb6fire_pcm_get_substream(alsa_sub); + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + unsigned long flags; + snd_pcm_uframes_t ret; + + if (rt->panic || !sub) + return SNDRV_PCM_POS_XRUN; + + spin_lock_irqsave(&sub->lock, flags); + ret = sub->dma_off; + spin_unlock_irqrestore(&sub->lock, flags); + return ret; +} + +static const struct snd_pcm_ops pcm_ops = { + .open = usb6fire_pcm_open, + .close = usb6fire_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = usb6fire_pcm_hw_params, + .hw_free = usb6fire_pcm_hw_free, + .prepare = usb6fire_pcm_prepare, + .trigger = usb6fire_pcm_trigger, + .pointer = usb6fire_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static void usb6fire_pcm_init_urb(struct pcm_urb *urb, + struct sfire_chip *chip, bool in, int ep, + void (*handler)(struct urb *)) +{ + urb->chip = chip; + usb_init_urb(&urb->instance); + urb->instance.transfer_buffer = urb->buffer; + urb->instance.transfer_buffer_length = + PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE; + urb->instance.dev = chip->dev; + urb->instance.pipe = in ? usb_rcvisocpipe(chip->dev, ep) + : usb_sndisocpipe(chip->dev, ep); + urb->instance.interval = 1; + urb->instance.complete = handler; + urb->instance.context = urb; + urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB; +} + +static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt) +{ + int i; + + for (i = 0; i < PCM_N_URBS; i++) { + rt->out_urbs[i].buffer = kcalloc(PCM_MAX_PACKET_SIZE, + PCM_N_PACKETS_PER_URB, + GFP_KERNEL); + if (!rt->out_urbs[i].buffer) + return -ENOMEM; + rt->in_urbs[i].buffer = kcalloc(PCM_MAX_PACKET_SIZE, + PCM_N_PACKETS_PER_URB, + GFP_KERNEL); + if (!rt->in_urbs[i].buffer) + return -ENOMEM; + } + return 0; +} + +static void usb6fire_pcm_buffers_destroy(struct pcm_runtime *rt) +{ + int i; + + for (i = 0; i < PCM_N_URBS; i++) { + kfree(rt->out_urbs[i].buffer); + kfree(rt->in_urbs[i].buffer); + } +} + +int usb6fire_pcm_init(struct sfire_chip *chip) +{ + int i; + int ret; + struct snd_pcm *pcm; + struct pcm_runtime *rt = + kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL); + + if (!rt) + return -ENOMEM; + + ret = usb6fire_pcm_buffers_init(rt); + if (ret) { + usb6fire_pcm_buffers_destroy(rt); + kfree(rt); + return ret; + } + + rt->chip = chip; + rt->stream_state = STREAM_DISABLED; + rt->rate = ARRAY_SIZE(rates); + init_waitqueue_head(&rt->stream_wait_queue); + mutex_init(&rt->stream_mutex); + + spin_lock_init(&rt->playback.lock); + spin_lock_init(&rt->capture.lock); + + for (i = 0; i < PCM_N_URBS; i++) { + usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP, + usb6fire_pcm_in_urb_handler); + usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP, + usb6fire_pcm_out_urb_handler); + + rt->in_urbs[i].peer = &rt->out_urbs[i]; + rt->out_urbs[i].peer = &rt->in_urbs[i]; + } + + ret = snd_pcm_new(chip->card, "DMX6FireUSB", 0, 1, 1, &pcm); + if (ret < 0) { + usb6fire_pcm_buffers_destroy(rt); + kfree(rt); + dev_err(&chip->dev->dev, "cannot create pcm instance.\n"); + return ret; + } + + pcm->private_data = rt; + strcpy(pcm->name, "DMX 6Fire USB"); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops); + + if (ret) { + usb6fire_pcm_buffers_destroy(rt); + kfree(rt); + dev_err(&chip->dev->dev, + "error preallocating pcm buffers.\n"); + return ret; + } + rt->instance = pcm; + + chip->pcm = rt; + return 0; +} + +void usb6fire_pcm_abort(struct sfire_chip *chip) +{ + struct pcm_runtime *rt = chip->pcm; + int i; + + if (rt) { + rt->panic = true; + + if (rt->playback.instance) + snd_pcm_stop_xrun(rt->playback.instance); + + if (rt->capture.instance) + snd_pcm_stop_xrun(rt->capture.instance); + + for (i = 0; i < PCM_N_URBS; i++) { + usb_poison_urb(&rt->in_urbs[i].instance); + usb_poison_urb(&rt->out_urbs[i].instance); + } + + } +} + +void usb6fire_pcm_destroy(struct sfire_chip *chip) +{ + struct pcm_runtime *rt = chip->pcm; + + usb6fire_pcm_buffers_destroy(rt); + kfree(rt); + chip->pcm = NULL; +} diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h new file mode 100644 index 000000000..f5779d618 --- /dev/null +++ b/sound/usb/6fire/pcm.h @@ -0,0 +1,75 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * Created: Jan 01, 2011 + * Copyright: (C) Torsten Schenk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef USB6FIRE_PCM_H +#define USB6FIRE_PCM_H + +#include +#include + +#include "common.h" + +enum /* settings for pcm */ +{ + /* maximum of EP_W_MAX_PACKET_SIZE[] (see firmware.c) */ + PCM_N_URBS = 16, PCM_N_PACKETS_PER_URB = 8, PCM_MAX_PACKET_SIZE = 604 +}; + +struct pcm_urb { + struct sfire_chip *chip; + + /* BEGIN DO NOT SEPARATE */ + struct urb instance; + struct usb_iso_packet_descriptor packets[PCM_N_PACKETS_PER_URB]; + /* END DO NOT SEPARATE */ + u8 *buffer; + + struct pcm_urb *peer; +}; + +struct pcm_substream { + spinlock_t lock; + struct snd_pcm_substream *instance; + + bool active; + + snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */ + snd_pcm_uframes_t period_off; /* current position in current period */ +}; + +struct pcm_runtime { + struct sfire_chip *chip; + struct snd_pcm *instance; + + struct pcm_substream playback; + struct pcm_substream capture; + bool panic; /* if set driver won't do anymore pcm on device */ + + struct pcm_urb in_urbs[PCM_N_URBS]; + struct pcm_urb out_urbs[PCM_N_URBS]; + int in_packet_size; + int out_packet_size; + int in_n_analog; /* number of analog channels soundcard sends */ + int out_n_analog; /* number of analog channels soundcard receives */ + + struct mutex stream_mutex; + u8 stream_state; /* one of STREAM_XXX (pcm.c) */ + u8 rate; /* one of PCM_RATE_XXX */ + wait_queue_head_t stream_wait_queue; + bool stream_wait_cond; +}; + +int usb6fire_pcm_init(struct sfire_chip *chip); +void usb6fire_pcm_abort(struct sfire_chip *chip); +void usb6fire_pcm_destroy(struct sfire_chip *chip); +#endif /* USB6FIRE_PCM_H */ diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig new file mode 100644 index 000000000..f61b5662b --- /dev/null +++ b/sound/usb/Kconfig @@ -0,0 +1,166 @@ +# ALSA USB drivers + +menuconfig SND_USB + bool "USB sound devices" + depends on USB + default y + help + Support for sound devices connected via the USB bus. + +if SND_USB && USB + +config SND_USB_AUDIO + tristate "USB Audio/MIDI driver" + select SND_HWDEP + select SND_RAWMIDI + select SND_PCM + select BITREVERSE + help + Say Y here to include support for USB audio and USB MIDI + devices. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-audio. + +config SND_USB_UA101 + tristate "Edirol UA-101/UA-1000 driver" + select SND_PCM + select SND_RAWMIDI + help + Say Y here to include support for the Edirol UA-101 and UA-1000 + audio/MIDI interfaces. + + To compile this driver as a module, choose M here: the module + will be called snd-ua101. + +config SND_USB_USX2Y + tristate "Tascam US-122, US-224 and US-428 USB driver" + depends on X86 || PPC || ALPHA + select SND_HWDEP + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for Tascam USB Audio/MIDI + interfaces or controllers US-122, US-224 and US-428. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-usx2y. + +config SND_USB_CAIAQ + tristate "Native Instruments USB audio devices" + select SND_HWDEP + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for caiaq USB audio interfaces, + namely: + + * Native Instruments RigKontrol2 + * Native Instruments RigKontrol3 + * Native Instruments Kore Controller + * Native Instruments Kore Controller 2 + * Native Instruments Audio Kontrol 1 + * Native Instruments Audio 2 DJ + * Native Instruments Audio 4 DJ + * Native Instruments Audio 8 DJ + * Native Instruments Traktor Audio 2 + * Native Instruments Guitar Rig Session I/O + * Native Instruments Guitar Rig mobile + * Native Instruments Traktor Kontrol X1 + * Native Instruments Traktor Kontrol S4 + * Native Instruments Maschine Controller + + To compile this driver as a module, choose M here: the module + will be called snd-usb-caiaq. + +config SND_USB_CAIAQ_INPUT + bool "enable input device for controllers" + depends on SND_USB_CAIAQ + depends on INPUT=y || INPUT=SND_USB_CAIAQ + help + Say Y here to support input controllers like buttons, knobs, + alpha dials and analog pedals on the following products: + + * Native Instruments RigKontrol2 + * Native Instruments RigKontrol3 + * Native Instruments Kore Controller + * Native Instruments Kore Controller 2 + * Native Instruments Audio Kontrol 1 + * Native Instruments Traktor Kontrol S4 + * Native Instruments Maschine Controller + +config SND_USB_US122L + tristate "Tascam US-122L USB driver" + depends on X86 || COMPILE_TEST + select SND_HWDEP + select SND_RAWMIDI + help + Say Y here to include support for Tascam US-122L USB Audio/MIDI + interfaces. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-us122l. + +config SND_USB_6FIRE + tristate "TerraTec DMX 6Fire USB" + select FW_LOADER + select BITREVERSE + select SND_RAWMIDI + select SND_PCM + select SND_VMASTER + help + Say Y here to include support for TerraTec 6fire DMX USB interface. + + You will need firmware files in order to be able to use the device + after it has been coldstarted. An install script for the firmware + and further help can be found at + http://sixfireusb.sourceforge.net + +config SND_USB_HIFACE + tristate "M2Tech hiFace USB-SPDIF driver" + select SND_PCM + help + Select this option to include support for M2Tech hiFace USB-SPDIF + interface. + + This driver supports the original M2Tech hiFace and some other + compatible devices. The supported products are: + + * M2Tech Young + * M2Tech hiFace + * M2Tech North Star + * M2Tech W4S Young + * M2Tech Corrson + * M2Tech AUDIA + * M2Tech SL Audio + * M2Tech Empirical + * M2Tech Rockna + * M2Tech Pathos + * M2Tech Metronome + * M2Tech CAD + * M2Tech Audio Esclusive + * M2Tech Rotel + * M2Tech Eeaudio + * The Chord Company CHORD + * AVA Group A/S Vitus + + To compile this driver as a module, choose M here: the module + will be called snd-usb-hiface. + +config SND_BCD2000 + tristate "Behringer BCD2000 MIDI driver" + select SND_RAWMIDI + help + Say Y here to include MIDI support for the Behringer BCD2000 DJ + controller. + + Audio support is still work-in-progress at + https://github.com/anyc/snd-usb-bcd2000 + + To compile this driver as a module, choose M here: the module + will be called snd-bcd2000. + +source "sound/usb/line6/Kconfig" + +endif # SND_USB + diff --git a/sound/usb/Makefile b/sound/usb/Makefile new file mode 100644 index 000000000..a12fffcbc --- /dev/null +++ b/sound/usb/Makefile @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# + +snd-usb-audio-objs := card.o \ + clock.o \ + endpoint.o \ + format.o \ + helper.o \ + mixer.o \ + mixer_quirks.o \ + mixer_scarlett.o \ + mixer_us16x08.o \ + pcm.o \ + power.o \ + proc.o \ + quirks.o \ + stream.o \ + validate.o + +snd-usbmidi-lib-objs := midi.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o + +obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o +obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o +obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o + +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ +obj-$(CONFIG_SND_USB_LINE6) += line6/ diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile new file mode 100644 index 000000000..f09ccc0af --- /dev/null +++ b/sound/usb/bcd2000/Makefile @@ -0,0 +1,3 @@ +snd-bcd2000-y := bcd2000.o + +obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o \ No newline at end of file diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c new file mode 100644 index 000000000..d6c8b29fe --- /dev/null +++ b/sound/usb/bcd2000/bcd2000.c @@ -0,0 +1,468 @@ +/* + * Behringer BCD2000 driver + * + * Copyright (C) 2014 Mario Kicherer (dev@kicherer.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PREFIX "snd-bcd2000: " +#define BUFSIZE 64 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x1397, 0x00bd) }, + { }, +}; + +static unsigned char device_cmd_prefix[] = {0x03, 0x00}; + +static unsigned char bcd2000_init_sequence[] = { + 0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81, + 0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7, + 0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7, + 0x18, 0xfa, 0x11, 0xff +}; + +struct bcd2000 { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *intf; + int card_index; + + int midi_out_active; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_receive_substream; + struct snd_rawmidi_substream *midi_out_substream; + + unsigned char midi_in_buf[BUFSIZE]; + unsigned char midi_out_buf[BUFSIZE]; + + struct urb *midi_out_urb; + struct urb *midi_in_urb; + + struct usb_anchor anchor; +}; + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); +static struct usb_driver bcd2000_driver; + +#ifdef CONFIG_SND_DEBUG +static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) +{ + print_hex_dump(KERN_DEBUG, prefix, + DUMP_PREFIX_NONE, 16, 1, + buf, len, false); +} +#else +static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {} +#endif + +static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +/* (de)register midi substream from client */ +static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct bcd2000 *bcd2k = substream->rmidi->private_data; + bcd2k->midi_receive_substream = up ? substream : NULL; +} + +static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k, + const unsigned char *buf, unsigned int buf_len) +{ + unsigned int payload_length, tocopy; + struct snd_rawmidi_substream *midi_receive_substream; + + midi_receive_substream = READ_ONCE(bcd2k->midi_receive_substream); + if (!midi_receive_substream) + return; + + bcd2000_dump_buffer(PREFIX "received from device: ", buf, buf_len); + + if (buf_len < 2) + return; + + payload_length = buf[0]; + + /* ignore packets without payload */ + if (payload_length == 0) + return; + + tocopy = min(payload_length, buf_len-1); + + bcd2000_dump_buffer(PREFIX "sending to userspace: ", + &buf[1], tocopy); + + snd_rawmidi_receive(midi_receive_substream, + &buf[1], tocopy); +} + +static void bcd2000_midi_send(struct bcd2000 *bcd2k) +{ + int len, ret; + struct snd_rawmidi_substream *midi_out_substream; + + BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE); + + midi_out_substream = READ_ONCE(bcd2k->midi_out_substream); + if (!midi_out_substream) + return; + + /* copy command prefix bytes */ + memcpy(bcd2k->midi_out_buf, device_cmd_prefix, + sizeof(device_cmd_prefix)); + + /* + * get MIDI packet and leave space for command prefix + * and payload length + */ + len = snd_rawmidi_transmit(midi_out_substream, + bcd2k->midi_out_buf + 3, BUFSIZE - 3); + + if (len < 0) + dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n", + __func__, len); + + if (len <= 0) + return; + + /* set payload length */ + bcd2k->midi_out_buf[2] = len; + bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE; + + bcd2000_dump_buffer(PREFIX "sending to device: ", + bcd2k->midi_out_buf, len+3); + + /* send packet to the BCD2000 */ + ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n", + __func__, midi_out_substream, ret, len); + else + bcd2k->midi_out_active = 1; +} + +static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct bcd2000 *bcd2k = substream->rmidi->private_data; + + if (bcd2k->midi_out_active) { + usb_kill_urb(bcd2k->midi_out_urb); + bcd2k->midi_out_active = 0; + } + + return 0; +} + +/* (de)register midi substream from client */ +static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct bcd2000 *bcd2k = substream->rmidi->private_data; + + if (up) { + bcd2k->midi_out_substream = substream; + /* check if there is data userspace wants to send */ + if (!bcd2k->midi_out_active) + bcd2000_midi_send(bcd2k); + } else { + bcd2k->midi_out_substream = NULL; + } +} + +static void bcd2000_output_complete(struct urb *urb) +{ + struct bcd2000 *bcd2k = urb->context; + + bcd2k->midi_out_active = 0; + + if (urb->status) + dev_warn(&urb->dev->dev, + PREFIX "output urb->status: %d\n", urb->status); + + if (urb->status == -ESHUTDOWN) + return; + + /* check if there is more data userspace wants to send */ + bcd2000_midi_send(bcd2k); +} + +static void bcd2000_input_complete(struct urb *urb) +{ + int ret; + struct bcd2000 *bcd2k = urb->context; + + if (urb->status) + dev_warn(&urb->dev->dev, + PREFIX "input urb->status: %i\n", urb->status); + + if (!bcd2k || urb->status == -ESHUTDOWN) + return; + + if (urb->actual_length > 0) + bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer, + urb->actual_length); + + /* return URB to device */ + ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s: usb_submit_urb() failed, ret=%d\n", + __func__, ret); +} + +static const struct snd_rawmidi_ops bcd2000_midi_output = { + .open = bcd2000_midi_output_open, + .close = bcd2000_midi_output_close, + .trigger = bcd2000_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops bcd2000_midi_input = { + .open = bcd2000_midi_input_open, + .close = bcd2000_midi_input_close, + .trigger = bcd2000_midi_input_trigger, +}; + +static void bcd2000_init_device(struct bcd2000 *bcd2k) +{ + int ret; + + init_usb_anchor(&bcd2k->anchor); + usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor); + usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor); + + /* copy init sequence into buffer */ + memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52); + bcd2k->midi_out_urb->transfer_buffer_length = 52; + + /* submit sequence */ + ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s: usb_submit_urb() out failed, ret=%d: ", + __func__, ret); + else + bcd2k->midi_out_active = 1; + + /* pass URB to device to enable button and controller events */ + ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL); + if (ret < 0) + dev_err(&bcd2k->dev->dev, PREFIX + "%s: usb_submit_urb() in failed, ret=%d: ", + __func__, ret); + + /* ensure initialization is finished */ + usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000); +} + +static int bcd2000_init_midi(struct bcd2000 *bcd2k) +{ + int ret; + struct snd_rawmidi *rmidi; + + ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0, + 1, /* output */ + 1, /* input */ + &rmidi); + + if (ret < 0) + return ret; + + strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name)); + + rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = bcd2k; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &bcd2000_midi_output); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &bcd2000_midi_input); + + bcd2k->rmidi = rmidi; + + bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL); + bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) { + dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n"); + return -ENOMEM; + } + + usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev, + usb_rcvintpipe(bcd2k->dev, 0x81), + bcd2k->midi_in_buf, BUFSIZE, + bcd2000_input_complete, bcd2k, 1); + + usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev, + usb_sndintpipe(bcd2k->dev, 0x1), + bcd2k->midi_out_buf, BUFSIZE, + bcd2000_output_complete, bcd2k, 1); + + /* sanity checks of EPs before actually submitting */ + if (usb_urb_ep_type_check(bcd2k->midi_in_urb) || + usb_urb_ep_type_check(bcd2k->midi_out_urb)) { + dev_err(&bcd2k->dev->dev, "invalid MIDI EP\n"); + return -EINVAL; + } + + bcd2000_init_device(bcd2k); + + return 0; +} + +static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k, + struct usb_interface *interface) +{ + /* usb_kill_urb not necessary, urb is aborted automatically */ + + usb_free_urb(bcd2k->midi_out_urb); + usb_free_urb(bcd2k->midi_in_urb); + + if (bcd2k->intf) { + usb_set_intfdata(bcd2k->intf, NULL); + bcd2k->intf = NULL; + } +} + +static int bcd2000_probe(struct usb_interface *interface, + const struct usb_device_id *usb_id) +{ + struct snd_card *card; + struct bcd2000 *bcd2k; + unsigned int card_index; + char usb_path[32]; + int err; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) + if (!test_bit(card_index, devices_used)) + break; + + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return -ENOENT; + } + + err = snd_card_new(&interface->dev, index[card_index], id[card_index], + THIS_MODULE, sizeof(*bcd2k), &card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return err; + } + + bcd2k = card->private_data; + bcd2k->dev = interface_to_usbdev(interface); + bcd2k->card = card; + bcd2k->card_index = card_index; + bcd2k->intf = interface; + + snd_card_set_dev(card, &interface->dev); + + strncpy(card->driver, "snd-bcd2000", sizeof(card->driver)); + strncpy(card->shortname, "BCD2000", sizeof(card->shortname)); + usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path)); + snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname), + "Behringer BCD2000 at %s", + usb_path); + + err = bcd2000_init_midi(bcd2k); + if (err < 0) + goto probe_error; + + err = snd_card_register(card); + if (err < 0) + goto probe_error; + + usb_set_intfdata(interface, bcd2k); + set_bit(card_index, devices_used); + + mutex_unlock(&devices_mutex); + return 0; + +probe_error: + dev_info(&bcd2k->dev->dev, PREFIX "error during probing"); + bcd2000_free_usb_related_resources(bcd2k, interface); + snd_card_free(card); + mutex_unlock(&devices_mutex); + return err; +} + +static void bcd2000_disconnect(struct usb_interface *interface) +{ + struct bcd2000 *bcd2k = usb_get_intfdata(interface); + + if (!bcd2k) + return; + + mutex_lock(&devices_mutex); + + /* make sure that userspace cannot create new requests */ + snd_card_disconnect(bcd2k->card); + + bcd2000_free_usb_related_resources(bcd2k, interface); + + clear_bit(bcd2k->card_index, devices_used); + + snd_card_free_when_closed(bcd2k->card); + + mutex_unlock(&devices_mutex); +} + +static struct usb_driver bcd2000_driver = { + .name = "snd-bcd2000", + .probe = bcd2000_probe, + .disconnect = bcd2000_disconnect, + .id_table = id_table, +}; + +module_usb_driver(bcd2000_driver); + +MODULE_DEVICE_TABLE(usb, id_table); +MODULE_AUTHOR("Mario Kicherer, dev@kicherer.org"); +MODULE_DESCRIPTION("Behringer BCD2000 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile new file mode 100644 index 000000000..388999653 --- /dev/null +++ b/sound/usb/caiaq/Makefile @@ -0,0 +1,4 @@ +snd-usb-caiaq-y := device.o audio.o midi.o control.o +snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o + +obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c new file mode 100644 index 000000000..c6108a3d7 --- /dev/null +++ b/sound/usb/caiaq/audio.c @@ -0,0 +1,903 @@ +/* + * Copyright (c) 2006-2008 Daniel Mack, Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "audio.h" + +#define N_URBS 32 +#define CLOCK_DRIFT_TOLERANCE 5 +#define FRAMES_PER_URB 8 +#define BYTES_PER_FRAME 512 +#define CHANNELS_PER_STREAM 2 +#define BYTES_PER_SAMPLE 3 +#define BYTES_PER_SAMPLE_USB 4 +#define MAX_BUFFER_SIZE (128*1024) +#define MAX_ENDPOINT_SIZE 512 + +#define ENDPOINT_CAPTURE 2 +#define ENDPOINT_PLAYBACK 6 + +#define MAKE_CHECKBYTE(cdev,stream,i) \ + (stream << 1) | (~(i / (cdev->n_streams * BYTES_PER_SAMPLE_USB)) & 1) + +static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000), + .rate_min = 44100, + .rate_max = 0, /* will overwrite later */ + .channels_min = CHANNELS_PER_STREAM, + .channels_max = CHANNELS_PER_STREAM, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = 128, + .period_bytes_max = MAX_BUFFER_SIZE, + .periods_min = 1, + .periods_max = 1024, +}; + +static void +activate_substream(struct snd_usb_caiaqdev *cdev, + struct snd_pcm_substream *sub) +{ + spin_lock(&cdev->spinlock); + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdev->sub_playback[sub->number] = sub; + else + cdev->sub_capture[sub->number] = sub; + + spin_unlock(&cdev->spinlock); +} + +static void +deactivate_substream(struct snd_usb_caiaqdev *cdev, + struct snd_pcm_substream *sub) +{ + unsigned long flags; + spin_lock_irqsave(&cdev->spinlock, flags); + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdev->sub_playback[sub->number] = NULL; + else + cdev->sub_capture[sub->number] = NULL; + + spin_unlock_irqrestore(&cdev->spinlock, flags); +} + +static int +all_substreams_zero(struct snd_pcm_substream **subs) +{ + int i; + for (i = 0; i < MAX_STREAMS; i++) + if (subs[i] != NULL) + return 0; + return 1; +} + +static int stream_start(struct snd_usb_caiaqdev *cdev) +{ + int i, ret; + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + + if (cdev->streaming) + return -EINVAL; + + memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback)); + memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture)); + cdev->input_panic = 0; + cdev->output_panic = 0; + cdev->first_packet = 4; + cdev->streaming = 1; + cdev->warned = 0; + + for (i = 0; i < N_URBS; i++) { + ret = usb_submit_urb(cdev->data_urbs_in[i], GFP_ATOMIC); + if (ret) { + dev_err(dev, "unable to trigger read #%d! (ret %d)\n", + i, ret); + cdev->streaming = 0; + return -EPIPE; + } + } + + return 0; +} + +static void stream_stop(struct snd_usb_caiaqdev *cdev) +{ + int i; + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + if (!cdev->streaming) + return; + + cdev->streaming = 0; + + for (i = 0; i < N_URBS; i++) { + usb_kill_urb(cdev->data_urbs_in[i]); + + if (test_bit(i, &cdev->outurb_active_mask)) + usb_kill_urb(cdev->data_urbs_out[i]); + } + + cdev->outurb_active_mask = 0; +} + +static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream) +{ + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream); + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, substream); + substream->runtime->hw = cdev->pcm_info; + snd_pcm_limit_hw_rates(substream->runtime); + + return 0; +} + +static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream) +{ + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream); + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, substream); + if (all_substreams_zero(cdev->sub_playback) && + all_substreams_zero(cdev->sub_capture)) { + /* when the last client has stopped streaming, + * all sample rates are allowed again */ + stream_stop(cdev); + cdev->pcm_info.rates = cdev->samplerates; + } + + return 0; +} + +static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(sub, + params_buffer_bytes(hw_params)); +} + +static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub) +{ + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); + deactivate_substream(cdev, sub); + return snd_pcm_lib_free_vmalloc_buffer(sub); +} + +/* this should probably go upstream */ +#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12 +#error "Change this table" +#endif + +static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100, + 48000, 64000, 88200, 96000, 176400, 192000 }; + +static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream) +{ + int bytes_per_sample, bpp, ret, i; + int index = substream->number; + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + int out_pos; + + switch (cdev->spec.data_alignment) { + case 0: + case 2: + out_pos = BYTES_PER_SAMPLE + 1; + break; + case 3: + default: + out_pos = 0; + break; + } + + cdev->period_out_count[index] = out_pos; + cdev->audio_out_buf_pos[index] = out_pos; + } else { + int in_pos; + + switch (cdev->spec.data_alignment) { + case 0: + in_pos = BYTES_PER_SAMPLE + 2; + break; + case 2: + in_pos = BYTES_PER_SAMPLE; + break; + case 3: + default: + in_pos = 0; + break; + } + + cdev->period_in_count[index] = in_pos; + cdev->audio_in_buf_pos[index] = in_pos; + } + + if (cdev->streaming) + return 0; + + /* the first client that opens a stream defines the sample rate + * setting for all subsequent calls, until the last client closed. */ + for (i=0; i < ARRAY_SIZE(rates); i++) + if (runtime->rate == rates[i]) + cdev->pcm_info.rates = 1 << i; + + snd_pcm_limit_hw_rates(runtime); + + bytes_per_sample = BYTES_PER_SAMPLE; + if (cdev->spec.data_alignment >= 2) + bytes_per_sample++; + + bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE) + * bytes_per_sample * CHANNELS_PER_STREAM * cdev->n_streams; + + if (bpp > MAX_ENDPOINT_SIZE) + bpp = MAX_ENDPOINT_SIZE; + + ret = snd_usb_caiaq_set_audio_params(cdev, runtime->rate, + runtime->sample_bits, bpp); + if (ret) + return ret; + + ret = stream_start(cdev); + if (ret) + return ret; + + cdev->output_running = 0; + wait_event_timeout(cdev->prepare_wait_queue, cdev->output_running, HZ); + if (!cdev->output_running) { + stream_stop(cdev); + return -EPIPE; + } + + return 0; +} + +static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd) +{ + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p) cmd %d\n", __func__, sub, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + activate_substream(cdev, sub); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + deactivate_substream(cdev, sub); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +snd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub) +{ + int index = sub->number; + struct snd_usb_caiaqdev *cdev = snd_pcm_substream_chip(sub); + snd_pcm_uframes_t ptr; + + spin_lock(&cdev->spinlock); + + if (cdev->input_panic || cdev->output_panic) { + ptr = SNDRV_PCM_POS_XRUN; + goto unlock; + } + + if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + ptr = bytes_to_frames(sub->runtime, + cdev->audio_out_buf_pos[index]); + else + ptr = bytes_to_frames(sub->runtime, + cdev->audio_in_buf_pos[index]); + +unlock: + spin_unlock(&cdev->spinlock); + return ptr; +} + +/* operators for both playback and capture */ +static const struct snd_pcm_ops snd_usb_caiaq_ops = { + .open = snd_usb_caiaq_substream_open, + .close = snd_usb_caiaq_substream_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_caiaq_pcm_hw_params, + .hw_free = snd_usb_caiaq_pcm_hw_free, + .prepare = snd_usb_caiaq_pcm_prepare, + .trigger = snd_usb_caiaq_pcm_trigger, + .pointer = snd_usb_caiaq_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static void check_for_elapsed_periods(struct snd_usb_caiaqdev *cdev, + struct snd_pcm_substream **subs) +{ + int stream, pb, *cnt; + struct snd_pcm_substream *sub; + + for (stream = 0; stream < cdev->n_streams; stream++) { + sub = subs[stream]; + if (!sub) + continue; + + pb = snd_pcm_lib_period_bytes(sub); + cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + &cdev->period_out_count[stream] : + &cdev->period_in_count[stream]; + + if (*cnt >= pb) { + snd_pcm_period_elapsed(sub); + *cnt %= pb; + } + } +} + +static void read_in_urb_mode0(struct snd_usb_caiaqdev *cdev, + const struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + struct snd_pcm_substream *sub; + int stream, i; + + if (all_substreams_zero(cdev->sub_capture)) + return; + + for (i = 0; i < iso->actual_length;) { + for (stream = 0; stream < cdev->n_streams; stream++, i++) { + sub = cdev->sub_capture[stream]; + if (sub) { + struct snd_pcm_runtime *rt = sub->runtime; + char *audio_buf = rt->dma_area; + int sz = frames_to_bytes(rt, rt->buffer_size); + audio_buf[cdev->audio_in_buf_pos[stream]++] + = usb_buf[i]; + cdev->period_in_count[stream]++; + if (cdev->audio_in_buf_pos[stream] == sz) + cdev->audio_in_buf_pos[stream] = 0; + } + } + } +} + +static void read_in_urb_mode2(struct snd_usb_caiaqdev *cdev, + const struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + unsigned char check_byte; + struct snd_pcm_substream *sub; + int stream, i; + + for (i = 0; i < iso->actual_length;) { + if (i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) == 0) { + for (stream = 0; + stream < cdev->n_streams; + stream++, i++) { + if (cdev->first_packet) + continue; + + check_byte = MAKE_CHECKBYTE(cdev, stream, i); + + if ((usb_buf[i] & 0x3f) != check_byte) + cdev->input_panic = 1; + + if (usb_buf[i] & 0x80) + cdev->output_panic = 1; + } + } + cdev->first_packet = 0; + + for (stream = 0; stream < cdev->n_streams; stream++, i++) { + sub = cdev->sub_capture[stream]; + if (cdev->input_panic) + usb_buf[i] = 0; + + if (sub) { + struct snd_pcm_runtime *rt = sub->runtime; + char *audio_buf = rt->dma_area; + int sz = frames_to_bytes(rt, rt->buffer_size); + audio_buf[cdev->audio_in_buf_pos[stream]++] = + usb_buf[i]; + cdev->period_in_count[stream]++; + if (cdev->audio_in_buf_pos[stream] == sz) + cdev->audio_in_buf_pos[stream] = 0; + } + } + } +} + +static void read_in_urb_mode3(struct snd_usb_caiaqdev *cdev, + const struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + struct device *dev = caiaqdev_to_dev(cdev); + int stream, i; + + /* paranoia check */ + if (iso->actual_length % (BYTES_PER_SAMPLE_USB * CHANNELS_PER_STREAM)) + return; + + for (i = 0; i < iso->actual_length;) { + for (stream = 0; stream < cdev->n_streams; stream++) { + struct snd_pcm_substream *sub = cdev->sub_capture[stream]; + char *audio_buf = NULL; + int c, n, sz = 0; + + if (sub && !cdev->input_panic) { + struct snd_pcm_runtime *rt = sub->runtime; + audio_buf = rt->dma_area; + sz = frames_to_bytes(rt, rt->buffer_size); + } + + for (c = 0; c < CHANNELS_PER_STREAM; c++) { + /* 3 audio data bytes, followed by 1 check byte */ + if (audio_buf) { + for (n = 0; n < BYTES_PER_SAMPLE; n++) { + audio_buf[cdev->audio_in_buf_pos[stream]++] = usb_buf[i+n]; + + if (cdev->audio_in_buf_pos[stream] == sz) + cdev->audio_in_buf_pos[stream] = 0; + } + + cdev->period_in_count[stream] += BYTES_PER_SAMPLE; + } + + i += BYTES_PER_SAMPLE; + + if (usb_buf[i] != ((stream << 1) | c) && + !cdev->first_packet) { + if (!cdev->input_panic) + dev_warn(dev, " EXPECTED: %02x got %02x, c %d, stream %d, i %d\n", + ((stream << 1) | c), usb_buf[i], c, stream, i); + cdev->input_panic = 1; + } + + i++; + } + } + } + + if (cdev->first_packet > 0) + cdev->first_packet--; +} + +static void read_in_urb(struct snd_usb_caiaqdev *cdev, + const struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + if (!cdev->streaming) + return; + + if (iso->actual_length < cdev->bpp) + return; + + switch (cdev->spec.data_alignment) { + case 0: + read_in_urb_mode0(cdev, urb, iso); + break; + case 2: + read_in_urb_mode2(cdev, urb, iso); + break; + case 3: + read_in_urb_mode3(cdev, urb, iso); + break; + } + + if ((cdev->input_panic || cdev->output_panic) && !cdev->warned) { + dev_warn(dev, "streaming error detected %s %s\n", + cdev->input_panic ? "(input)" : "", + cdev->output_panic ? "(output)" : ""); + cdev->warned = 1; + } +} + +static void fill_out_urb_mode_0(struct snd_usb_caiaqdev *cdev, + struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + struct snd_pcm_substream *sub; + int stream, i; + + for (i = 0; i < iso->length;) { + for (stream = 0; stream < cdev->n_streams; stream++, i++) { + sub = cdev->sub_playback[stream]; + if (sub) { + struct snd_pcm_runtime *rt = sub->runtime; + char *audio_buf = rt->dma_area; + int sz = frames_to_bytes(rt, rt->buffer_size); + usb_buf[i] = + audio_buf[cdev->audio_out_buf_pos[stream]]; + cdev->period_out_count[stream]++; + cdev->audio_out_buf_pos[stream]++; + if (cdev->audio_out_buf_pos[stream] == sz) + cdev->audio_out_buf_pos[stream] = 0; + } else + usb_buf[i] = 0; + } + + /* fill in the check bytes */ + if (cdev->spec.data_alignment == 2 && + i % (cdev->n_streams * BYTES_PER_SAMPLE_USB) == + (cdev->n_streams * CHANNELS_PER_STREAM)) + for (stream = 0; stream < cdev->n_streams; stream++, i++) + usb_buf[i] = MAKE_CHECKBYTE(cdev, stream, i); + } +} + +static void fill_out_urb_mode_3(struct snd_usb_caiaqdev *cdev, + struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + unsigned char *usb_buf = urb->transfer_buffer + iso->offset; + int stream, i; + + for (i = 0; i < iso->length;) { + for (stream = 0; stream < cdev->n_streams; stream++) { + struct snd_pcm_substream *sub = cdev->sub_playback[stream]; + char *audio_buf = NULL; + int c, n, sz = 0; + + if (sub) { + struct snd_pcm_runtime *rt = sub->runtime; + audio_buf = rt->dma_area; + sz = frames_to_bytes(rt, rt->buffer_size); + } + + for (c = 0; c < CHANNELS_PER_STREAM; c++) { + for (n = 0; n < BYTES_PER_SAMPLE; n++) { + if (audio_buf) { + usb_buf[i+n] = audio_buf[cdev->audio_out_buf_pos[stream]++]; + + if (cdev->audio_out_buf_pos[stream] == sz) + cdev->audio_out_buf_pos[stream] = 0; + } else { + usb_buf[i+n] = 0; + } + } + + if (audio_buf) + cdev->period_out_count[stream] += BYTES_PER_SAMPLE; + + i += BYTES_PER_SAMPLE; + + /* fill in the check byte pattern */ + usb_buf[i++] = (stream << 1) | c; + } + } + } +} + +static inline void fill_out_urb(struct snd_usb_caiaqdev *cdev, + struct urb *urb, + const struct usb_iso_packet_descriptor *iso) +{ + switch (cdev->spec.data_alignment) { + case 0: + case 2: + fill_out_urb_mode_0(cdev, urb, iso); + break; + case 3: + fill_out_urb_mode_3(cdev, urb, iso); + break; + } +} + +static void read_completed(struct urb *urb) +{ + struct snd_usb_caiaq_cb_info *info = urb->context; + struct snd_usb_caiaqdev *cdev; + struct device *dev; + struct urb *out = NULL; + int i, frame, len, send_it = 0, outframe = 0; + unsigned long flags; + size_t offset = 0; + + if (urb->status || !info) + return; + + cdev = info->cdev; + dev = caiaqdev_to_dev(cdev); + + if (!cdev->streaming) + return; + + /* find an unused output urb that is unused */ + for (i = 0; i < N_URBS; i++) + if (test_and_set_bit(i, &cdev->outurb_active_mask) == 0) { + out = cdev->data_urbs_out[i]; + break; + } + + if (!out) { + dev_err(dev, "Unable to find an output urb to use\n"); + goto requeue; + } + + /* read the recently received packet and send back one which has + * the same layout */ + for (frame = 0; frame < FRAMES_PER_URB; frame++) { + if (urb->iso_frame_desc[frame].status) + continue; + + len = urb->iso_frame_desc[outframe].actual_length; + out->iso_frame_desc[outframe].length = len; + out->iso_frame_desc[outframe].actual_length = 0; + out->iso_frame_desc[outframe].offset = offset; + offset += len; + + if (len > 0) { + spin_lock_irqsave(&cdev->spinlock, flags); + fill_out_urb(cdev, out, &out->iso_frame_desc[outframe]); + read_in_urb(cdev, urb, &urb->iso_frame_desc[frame]); + spin_unlock_irqrestore(&cdev->spinlock, flags); + check_for_elapsed_periods(cdev, cdev->sub_playback); + check_for_elapsed_periods(cdev, cdev->sub_capture); + send_it = 1; + } + + outframe++; + } + + if (send_it) { + out->number_of_packets = outframe; + usb_submit_urb(out, GFP_ATOMIC); + } else { + struct snd_usb_caiaq_cb_info *oinfo = out->context; + clear_bit(oinfo->index, &cdev->outurb_active_mask); + } + +requeue: + /* re-submit inbound urb */ + for (frame = 0; frame < FRAMES_PER_URB; frame++) { + urb->iso_frame_desc[frame].offset = BYTES_PER_FRAME * frame; + urb->iso_frame_desc[frame].length = BYTES_PER_FRAME; + urb->iso_frame_desc[frame].actual_length = 0; + } + + urb->number_of_packets = FRAMES_PER_URB; + usb_submit_urb(urb, GFP_ATOMIC); +} + +static void write_completed(struct urb *urb) +{ + struct snd_usb_caiaq_cb_info *info = urb->context; + struct snd_usb_caiaqdev *cdev = info->cdev; + + if (!cdev->output_running) { + cdev->output_running = 1; + wake_up(&cdev->prepare_wait_queue); + } + + clear_bit(info->index, &cdev->outurb_active_mask); +} + +static struct urb **alloc_urbs(struct snd_usb_caiaqdev *cdev, int dir, int *ret) +{ + int i, frame; + struct urb **urbs; + struct usb_device *usb_dev = cdev->chip.dev; + unsigned int pipe; + + pipe = (dir == SNDRV_PCM_STREAM_PLAYBACK) ? + usb_sndisocpipe(usb_dev, ENDPOINT_PLAYBACK) : + usb_rcvisocpipe(usb_dev, ENDPOINT_CAPTURE); + + urbs = kmalloc_array(N_URBS, sizeof(*urbs), GFP_KERNEL); + if (!urbs) { + *ret = -ENOMEM; + return NULL; + } + + for (i = 0; i < N_URBS; i++) { + urbs[i] = usb_alloc_urb(FRAMES_PER_URB, GFP_KERNEL); + if (!urbs[i]) { + *ret = -ENOMEM; + return urbs; + } + + urbs[i]->transfer_buffer = + kmalloc_array(BYTES_PER_FRAME, FRAMES_PER_URB, + GFP_KERNEL); + if (!urbs[i]->transfer_buffer) { + *ret = -ENOMEM; + return urbs; + } + + for (frame = 0; frame < FRAMES_PER_URB; frame++) { + struct usb_iso_packet_descriptor *iso = + &urbs[i]->iso_frame_desc[frame]; + + iso->offset = BYTES_PER_FRAME * frame; + iso->length = BYTES_PER_FRAME; + } + + urbs[i]->dev = usb_dev; + urbs[i]->pipe = pipe; + urbs[i]->transfer_buffer_length = FRAMES_PER_URB + * BYTES_PER_FRAME; + urbs[i]->context = &cdev->data_cb_info[i]; + urbs[i]->interval = 1; + urbs[i]->number_of_packets = FRAMES_PER_URB; + urbs[i]->complete = (dir == SNDRV_PCM_STREAM_CAPTURE) ? + read_completed : write_completed; + } + + *ret = 0; + return urbs; +} + +static void free_urbs(struct urb **urbs) +{ + int i; + + if (!urbs) + return; + + for (i = 0; i < N_URBS; i++) { + if (!urbs[i]) + continue; + + usb_kill_urb(urbs[i]); + kfree(urbs[i]->transfer_buffer); + usb_free_urb(urbs[i]); + } + + kfree(urbs); +} + +int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) +{ + int i, ret; + struct device *dev = caiaqdev_to_dev(cdev); + + cdev->n_audio_in = max(cdev->spec.num_analog_audio_in, + cdev->spec.num_digital_audio_in) / + CHANNELS_PER_STREAM; + cdev->n_audio_out = max(cdev->spec.num_analog_audio_out, + cdev->spec.num_digital_audio_out) / + CHANNELS_PER_STREAM; + cdev->n_streams = max(cdev->n_audio_in, cdev->n_audio_out); + + dev_dbg(dev, "cdev->n_audio_in = %d\n", cdev->n_audio_in); + dev_dbg(dev, "cdev->n_audio_out = %d\n", cdev->n_audio_out); + dev_dbg(dev, "cdev->n_streams = %d\n", cdev->n_streams); + + if (cdev->n_streams > MAX_STREAMS) { + dev_err(dev, "unable to initialize device, too many streams.\n"); + return -EINVAL; + } + + if (cdev->n_streams < 1) { + dev_err(dev, "bogus number of streams: %d\n", cdev->n_streams); + return -EINVAL; + } + + ret = snd_pcm_new(cdev->chip.card, cdev->product_name, 0, + cdev->n_audio_out, cdev->n_audio_in, &cdev->pcm); + + if (ret < 0) { + dev_err(dev, "snd_pcm_new() returned %d\n", ret); + return ret; + } + + cdev->pcm->private_data = cdev; + strlcpy(cdev->pcm->name, cdev->product_name, sizeof(cdev->pcm->name)); + + memset(cdev->sub_playback, 0, sizeof(cdev->sub_playback)); + memset(cdev->sub_capture, 0, sizeof(cdev->sub_capture)); + + memcpy(&cdev->pcm_info, &snd_usb_caiaq_pcm_hardware, + sizeof(snd_usb_caiaq_pcm_hardware)); + + /* setup samplerates */ + cdev->samplerates = cdev->pcm_info.rates; + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_SESSIONIO): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_GUITARRIGMOBILE): + cdev->samplerates |= SNDRV_PCM_RATE_192000; + /* fall thru */ + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO2DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORAUDIO2): + cdev->samplerates |= SNDRV_PCM_RATE_88200; + break; + } + + snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_usb_caiaq_ops); + snd_pcm_set_ops(cdev->pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_usb_caiaq_ops); + + cdev->data_cb_info = + kmalloc_array(N_URBS, sizeof(struct snd_usb_caiaq_cb_info), + GFP_KERNEL); + + if (!cdev->data_cb_info) + return -ENOMEM; + + cdev->outurb_active_mask = 0; + BUILD_BUG_ON(N_URBS > (sizeof(cdev->outurb_active_mask) * 8)); + + for (i = 0; i < N_URBS; i++) { + cdev->data_cb_info[i].cdev = cdev; + cdev->data_cb_info[i].index = i; + } + + cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret); + if (ret < 0) { + kfree(cdev->data_cb_info); + free_urbs(cdev->data_urbs_in); + return ret; + } + + cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret); + if (ret < 0) { + kfree(cdev->data_cb_info); + free_urbs(cdev->data_urbs_in); + free_urbs(cdev->data_urbs_out); + return ret; + } + + return 0; +} + +void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + dev_dbg(dev, "%s(%p)\n", __func__, cdev); + stream_stop(cdev); + free_urbs(cdev->data_urbs_in); + free_urbs(cdev->data_urbs_out); + kfree(cdev->data_cb_info); +} + diff --git a/sound/usb/caiaq/audio.h b/sound/usb/caiaq/audio.h new file mode 100644 index 000000000..869bf6264 --- /dev/null +++ b/sound/usb/caiaq/audio.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_AUDIO_H +#define CAIAQ_AUDIO_H + +int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_AUDIO_H */ diff --git a/sound/usb/caiaq/control.c b/sound/usb/caiaq/control.c new file mode 100644 index 000000000..b7a7c805d --- /dev/null +++ b/sound/usb/caiaq/control.c @@ -0,0 +1,656 @@ +/* + * Copyright (c) 2007 Daniel Mack + * friendly supported by NI. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "control.h" + +#define CNT_INTVAL 0x10000 +#define MASCHINE_BANK_SIZE 32 + +static int control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol); + struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card); + int pos = kcontrol->private_value; + int is_intval = pos & CNT_INTVAL; + int maxval = 63; + + uinfo->count = 1; + pos &= ~CNT_INTVAL; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): + if (pos == 0) { + /* current input mode of A8DJ and A4DJ */ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + return 0; + } + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + maxval = 127; + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + maxval = 31; + break; + } + + if (is_intval) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = maxval; + } else { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + } + + return 0; +} + +static int control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol); + struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card); + int pos = kcontrol->private_value; + + if (pos & CNT_INTVAL) + ucontrol->value.integer.value[0] + = cdev->control_state[pos & ~CNT_INTVAL]; + else + ucontrol->value.integer.value[0] + = !!(cdev->control_state[pos / 8] & (1 << pos % 8)); + + return 0; +} + +static int control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_audio *chip = snd_kcontrol_chip(kcontrol); + struct snd_usb_caiaqdev *cdev = caiaqdev(chip->card); + int pos = kcontrol->private_value; + int v = ucontrol->value.integer.value[0]; + unsigned char cmd; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + cmd = EP1_CMD_DIMM_LEDS; + break; + default: + cmd = EP1_CMD_WRITE_IO; + break; + } + + if (pos & CNT_INTVAL) { + int i = pos & ~CNT_INTVAL; + + cdev->control_state[i] = v; + + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4)) { + int actual_len; + + cdev->ep8_out_buf[0] = i; + cdev->ep8_out_buf[1] = v; + + usb_bulk_msg(cdev->chip.dev, + usb_sndbulkpipe(cdev->chip.dev, 8), + cdev->ep8_out_buf, sizeof(cdev->ep8_out_buf), + &actual_len, 200); + } else if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER)) { + + int bank = 0; + int offset = 0; + + if (i >= MASCHINE_BANK_SIZE) { + bank = 0x1e; + offset = MASCHINE_BANK_SIZE; + } + + snd_usb_caiaq_send_command_bank(cdev, cmd, bank, + cdev->control_state + offset, + MASCHINE_BANK_SIZE); + } else { + snd_usb_caiaq_send_command(cdev, cmd, + cdev->control_state, sizeof(cdev->control_state)); + } + } else { + if (v) + cdev->control_state[pos / 8] |= 1 << (pos % 8); + else + cdev->control_state[pos / 8] &= ~(1 << (pos % 8)); + + snd_usb_caiaq_send_command(cdev, cmd, + cdev->control_state, sizeof(cdev->control_state)); + } + + return 1; +} + +static struct snd_kcontrol_new kcontrol_template = { + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .index = 0, + .info = control_info, + .get = control_get, + .put = control_put, + /* name and private_value filled later */ +}; + +struct caiaq_controller { + char *name; + int index; +}; + +static struct caiaq_controller ak1_controller[] = { + { "LED left", 2 }, + { "LED middle", 1 }, + { "LED right", 0 }, + { "LED ring", 3 } +}; + +static struct caiaq_controller rk2_controller[] = { + { "LED 1", 5 }, + { "LED 2", 4 }, + { "LED 3", 3 }, + { "LED 4", 2 }, + { "LED 5", 1 }, + { "LED 6", 0 }, + { "LED pedal", 6 }, + { "LED 7seg_1b", 8 }, + { "LED 7seg_1c", 9 }, + { "LED 7seg_2a", 10 }, + { "LED 7seg_2b", 11 }, + { "LED 7seg_2c", 12 }, + { "LED 7seg_2d", 13 }, + { "LED 7seg_2e", 14 }, + { "LED 7seg_2f", 15 }, + { "LED 7seg_2g", 16 }, + { "LED 7seg_3a", 17 }, + { "LED 7seg_3b", 18 }, + { "LED 7seg_3c", 19 }, + { "LED 7seg_3d", 20 }, + { "LED 7seg_3e", 21 }, + { "LED 7seg_3f", 22 }, + { "LED 7seg_3g", 23 } +}; + +static struct caiaq_controller rk3_controller[] = { + { "LED 7seg_1a", 0 + 0 }, + { "LED 7seg_1b", 0 + 1 }, + { "LED 7seg_1c", 0 + 2 }, + { "LED 7seg_1d", 0 + 3 }, + { "LED 7seg_1e", 0 + 4 }, + { "LED 7seg_1f", 0 + 5 }, + { "LED 7seg_1g", 0 + 6 }, + { "LED 7seg_1p", 0 + 7 }, + + { "LED 7seg_2a", 8 + 0 }, + { "LED 7seg_2b", 8 + 1 }, + { "LED 7seg_2c", 8 + 2 }, + { "LED 7seg_2d", 8 + 3 }, + { "LED 7seg_2e", 8 + 4 }, + { "LED 7seg_2f", 8 + 5 }, + { "LED 7seg_2g", 8 + 6 }, + { "LED 7seg_2p", 8 + 7 }, + + { "LED 7seg_3a", 16 + 0 }, + { "LED 7seg_3b", 16 + 1 }, + { "LED 7seg_3c", 16 + 2 }, + { "LED 7seg_3d", 16 + 3 }, + { "LED 7seg_3e", 16 + 4 }, + { "LED 7seg_3f", 16 + 5 }, + { "LED 7seg_3g", 16 + 6 }, + { "LED 7seg_3p", 16 + 7 }, + + { "LED 7seg_4a", 24 + 0 }, + { "LED 7seg_4b", 24 + 1 }, + { "LED 7seg_4c", 24 + 2 }, + { "LED 7seg_4d", 24 + 3 }, + { "LED 7seg_4e", 24 + 4 }, + { "LED 7seg_4f", 24 + 5 }, + { "LED 7seg_4g", 24 + 6 }, + { "LED 7seg_4p", 24 + 7 }, + + { "LED 1", 32 + 0 }, + { "LED 2", 32 + 1 }, + { "LED 3", 32 + 2 }, + { "LED 4", 32 + 3 }, + { "LED 5", 32 + 4 }, + { "LED 6", 32 + 5 }, + { "LED 7", 32 + 6 }, + { "LED 8", 32 + 7 }, + { "LED pedal", 32 + 8 } +}; + +static struct caiaq_controller kore_controller[] = { + { "LED F1", 8 | CNT_INTVAL }, + { "LED F2", 12 | CNT_INTVAL }, + { "LED F3", 0 | CNT_INTVAL }, + { "LED F4", 4 | CNT_INTVAL }, + { "LED F5", 11 | CNT_INTVAL }, + { "LED F6", 15 | CNT_INTVAL }, + { "LED F7", 3 | CNT_INTVAL }, + { "LED F8", 7 | CNT_INTVAL }, + { "LED touch1", 10 | CNT_INTVAL }, + { "LED touch2", 14 | CNT_INTVAL }, + { "LED touch3", 2 | CNT_INTVAL }, + { "LED touch4", 6 | CNT_INTVAL }, + { "LED touch5", 9 | CNT_INTVAL }, + { "LED touch6", 13 | CNT_INTVAL }, + { "LED touch7", 1 | CNT_INTVAL }, + { "LED touch8", 5 | CNT_INTVAL }, + { "LED left", 18 | CNT_INTVAL }, + { "LED right", 22 | CNT_INTVAL }, + { "LED up", 16 | CNT_INTVAL }, + { "LED down", 20 | CNT_INTVAL }, + { "LED stop", 23 | CNT_INTVAL }, + { "LED play", 21 | CNT_INTVAL }, + { "LED record", 19 | CNT_INTVAL }, + { "LED listen", 17 | CNT_INTVAL }, + { "LED lcd", 30 | CNT_INTVAL }, + { "LED menu", 28 | CNT_INTVAL }, + { "LED sound", 31 | CNT_INTVAL }, + { "LED esc", 29 | CNT_INTVAL }, + { "LED view", 27 | CNT_INTVAL }, + { "LED enter", 24 | CNT_INTVAL }, + { "LED control", 26 | CNT_INTVAL } +}; + +static struct caiaq_controller a8dj_controller[] = { + { "Current input mode", 0 | CNT_INTVAL }, + { "GND lift for TC Vinyl mode", 24 + 0 }, + { "GND lift for TC CD/Line mode", 24 + 1 }, + { "GND lift for phono mode", 24 + 2 }, + { "Software lock", 40 } +}; + +static struct caiaq_controller a4dj_controller[] = { + { "Current input mode", 0 | CNT_INTVAL } +}; + +static struct caiaq_controller kontrolx1_controller[] = { + { "LED FX A: ON", 7 | CNT_INTVAL }, + { "LED FX A: 1", 6 | CNT_INTVAL }, + { "LED FX A: 2", 5 | CNT_INTVAL }, + { "LED FX A: 3", 4 | CNT_INTVAL }, + { "LED FX B: ON", 3 | CNT_INTVAL }, + { "LED FX B: 1", 2 | CNT_INTVAL }, + { "LED FX B: 2", 1 | CNT_INTVAL }, + { "LED FX B: 3", 0 | CNT_INTVAL }, + + { "LED Hotcue", 28 | CNT_INTVAL }, + { "LED Shift (white)", 29 | CNT_INTVAL }, + { "LED Shift (green)", 30 | CNT_INTVAL }, + + { "LED Deck A: FX1", 24 | CNT_INTVAL }, + { "LED Deck A: FX2", 25 | CNT_INTVAL }, + { "LED Deck A: IN", 17 | CNT_INTVAL }, + { "LED Deck A: OUT", 16 | CNT_INTVAL }, + { "LED Deck A: < BEAT", 19 | CNT_INTVAL }, + { "LED Deck A: BEAT >", 18 | CNT_INTVAL }, + { "LED Deck A: CUE/ABS", 21 | CNT_INTVAL }, + { "LED Deck A: CUP/REL", 20 | CNT_INTVAL }, + { "LED Deck A: PLAY", 23 | CNT_INTVAL }, + { "LED Deck A: SYNC", 22 | CNT_INTVAL }, + + { "LED Deck B: FX1", 26 | CNT_INTVAL }, + { "LED Deck B: FX2", 27 | CNT_INTVAL }, + { "LED Deck B: IN", 15 | CNT_INTVAL }, + { "LED Deck B: OUT", 14 | CNT_INTVAL }, + { "LED Deck B: < BEAT", 13 | CNT_INTVAL }, + { "LED Deck B: BEAT >", 12 | CNT_INTVAL }, + { "LED Deck B: CUE/ABS", 11 | CNT_INTVAL }, + { "LED Deck B: CUP/REL", 10 | CNT_INTVAL }, + { "LED Deck B: PLAY", 9 | CNT_INTVAL }, + { "LED Deck B: SYNC", 8 | CNT_INTVAL }, +}; + +static struct caiaq_controller kontrols4_controller[] = { + { "LED: Master: Quant", 10 | CNT_INTVAL }, + { "LED: Master: Headphone", 11 | CNT_INTVAL }, + { "LED: Master: Master", 12 | CNT_INTVAL }, + { "LED: Master: Snap", 14 | CNT_INTVAL }, + { "LED: Master: Warning", 15 | CNT_INTVAL }, + { "LED: Master: Master button", 112 | CNT_INTVAL }, + { "LED: Master: Snap button", 113 | CNT_INTVAL }, + { "LED: Master: Rec", 118 | CNT_INTVAL }, + { "LED: Master: Size", 119 | CNT_INTVAL }, + { "LED: Master: Quant button", 120 | CNT_INTVAL }, + { "LED: Master: Browser button", 121 | CNT_INTVAL }, + { "LED: Master: Play button", 126 | CNT_INTVAL }, + { "LED: Master: Undo button", 127 | CNT_INTVAL }, + + { "LED: Channel A: >", 4 | CNT_INTVAL }, + { "LED: Channel A: <", 5 | CNT_INTVAL }, + { "LED: Channel A: Meter 1", 97 | CNT_INTVAL }, + { "LED: Channel A: Meter 2", 98 | CNT_INTVAL }, + { "LED: Channel A: Meter 3", 99 | CNT_INTVAL }, + { "LED: Channel A: Meter 4", 100 | CNT_INTVAL }, + { "LED: Channel A: Meter 5", 101 | CNT_INTVAL }, + { "LED: Channel A: Meter 6", 102 | CNT_INTVAL }, + { "LED: Channel A: Meter clip", 103 | CNT_INTVAL }, + { "LED: Channel A: Active", 114 | CNT_INTVAL }, + { "LED: Channel A: Cue", 116 | CNT_INTVAL }, + { "LED: Channel A: FX1", 149 | CNT_INTVAL }, + { "LED: Channel A: FX2", 148 | CNT_INTVAL }, + + { "LED: Channel B: >", 2 | CNT_INTVAL }, + { "LED: Channel B: <", 3 | CNT_INTVAL }, + { "LED: Channel B: Meter 1", 89 | CNT_INTVAL }, + { "LED: Channel B: Meter 2", 90 | CNT_INTVAL }, + { "LED: Channel B: Meter 3", 91 | CNT_INTVAL }, + { "LED: Channel B: Meter 4", 92 | CNT_INTVAL }, + { "LED: Channel B: Meter 5", 93 | CNT_INTVAL }, + { "LED: Channel B: Meter 6", 94 | CNT_INTVAL }, + { "LED: Channel B: Meter clip", 95 | CNT_INTVAL }, + { "LED: Channel B: Active", 122 | CNT_INTVAL }, + { "LED: Channel B: Cue", 125 | CNT_INTVAL }, + { "LED: Channel B: FX1", 147 | CNT_INTVAL }, + { "LED: Channel B: FX2", 146 | CNT_INTVAL }, + + { "LED: Channel C: >", 6 | CNT_INTVAL }, + { "LED: Channel C: <", 7 | CNT_INTVAL }, + { "LED: Channel C: Meter 1", 105 | CNT_INTVAL }, + { "LED: Channel C: Meter 2", 106 | CNT_INTVAL }, + { "LED: Channel C: Meter 3", 107 | CNT_INTVAL }, + { "LED: Channel C: Meter 4", 108 | CNT_INTVAL }, + { "LED: Channel C: Meter 5", 109 | CNT_INTVAL }, + { "LED: Channel C: Meter 6", 110 | CNT_INTVAL }, + { "LED: Channel C: Meter clip", 111 | CNT_INTVAL }, + { "LED: Channel C: Active", 115 | CNT_INTVAL }, + { "LED: Channel C: Cue", 117 | CNT_INTVAL }, + { "LED: Channel C: FX1", 151 | CNT_INTVAL }, + { "LED: Channel C: FX2", 150 | CNT_INTVAL }, + + { "LED: Channel D: >", 0 | CNT_INTVAL }, + { "LED: Channel D: <", 1 | CNT_INTVAL }, + { "LED: Channel D: Meter 1", 81 | CNT_INTVAL }, + { "LED: Channel D: Meter 2", 82 | CNT_INTVAL }, + { "LED: Channel D: Meter 3", 83 | CNT_INTVAL }, + { "LED: Channel D: Meter 4", 84 | CNT_INTVAL }, + { "LED: Channel D: Meter 5", 85 | CNT_INTVAL }, + { "LED: Channel D: Meter 6", 86 | CNT_INTVAL }, + { "LED: Channel D: Meter clip", 87 | CNT_INTVAL }, + { "LED: Channel D: Active", 123 | CNT_INTVAL }, + { "LED: Channel D: Cue", 124 | CNT_INTVAL }, + { "LED: Channel D: FX1", 145 | CNT_INTVAL }, + { "LED: Channel D: FX2", 144 | CNT_INTVAL }, + + { "LED: Deck A: 1 (blue)", 22 | CNT_INTVAL }, + { "LED: Deck A: 1 (green)", 23 | CNT_INTVAL }, + { "LED: Deck A: 2 (blue)", 20 | CNT_INTVAL }, + { "LED: Deck A: 2 (green)", 21 | CNT_INTVAL }, + { "LED: Deck A: 3 (blue)", 18 | CNT_INTVAL }, + { "LED: Deck A: 3 (green)", 19 | CNT_INTVAL }, + { "LED: Deck A: 4 (blue)", 16 | CNT_INTVAL }, + { "LED: Deck A: 4 (green)", 17 | CNT_INTVAL }, + { "LED: Deck A: Load", 44 | CNT_INTVAL }, + { "LED: Deck A: Deck C button", 45 | CNT_INTVAL }, + { "LED: Deck A: In", 47 | CNT_INTVAL }, + { "LED: Deck A: Out", 46 | CNT_INTVAL }, + { "LED: Deck A: Shift", 24 | CNT_INTVAL }, + { "LED: Deck A: Sync", 27 | CNT_INTVAL }, + { "LED: Deck A: Cue", 26 | CNT_INTVAL }, + { "LED: Deck A: Play", 25 | CNT_INTVAL }, + { "LED: Deck A: Tempo up", 33 | CNT_INTVAL }, + { "LED: Deck A: Tempo down", 32 | CNT_INTVAL }, + { "LED: Deck A: Master", 34 | CNT_INTVAL }, + { "LED: Deck A: Keylock", 35 | CNT_INTVAL }, + { "LED: Deck A: Deck A", 37 | CNT_INTVAL }, + { "LED: Deck A: Deck C", 36 | CNT_INTVAL }, + { "LED: Deck A: Samples", 38 | CNT_INTVAL }, + { "LED: Deck A: On Air", 39 | CNT_INTVAL }, + { "LED: Deck A: Sample 1", 31 | CNT_INTVAL }, + { "LED: Deck A: Sample 2", 30 | CNT_INTVAL }, + { "LED: Deck A: Sample 3", 29 | CNT_INTVAL }, + { "LED: Deck A: Sample 4", 28 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - A", 55 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - B", 54 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - C", 53 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - D", 52 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - E", 51 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - F", 50 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - G", 49 | CNT_INTVAL }, + { "LED: Deck A: Digit 1 - dot", 48 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - A", 63 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - B", 62 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - C", 61 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - D", 60 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - E", 59 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - F", 58 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - G", 57 | CNT_INTVAL }, + { "LED: Deck A: Digit 2 - dot", 56 | CNT_INTVAL }, + + { "LED: Deck B: 1 (blue)", 78 | CNT_INTVAL }, + { "LED: Deck B: 1 (green)", 79 | CNT_INTVAL }, + { "LED: Deck B: 2 (blue)", 76 | CNT_INTVAL }, + { "LED: Deck B: 2 (green)", 77 | CNT_INTVAL }, + { "LED: Deck B: 3 (blue)", 74 | CNT_INTVAL }, + { "LED: Deck B: 3 (green)", 75 | CNT_INTVAL }, + { "LED: Deck B: 4 (blue)", 72 | CNT_INTVAL }, + { "LED: Deck B: 4 (green)", 73 | CNT_INTVAL }, + { "LED: Deck B: Load", 180 | CNT_INTVAL }, + { "LED: Deck B: Deck D button", 181 | CNT_INTVAL }, + { "LED: Deck B: In", 183 | CNT_INTVAL }, + { "LED: Deck B: Out", 182 | CNT_INTVAL }, + { "LED: Deck B: Shift", 64 | CNT_INTVAL }, + { "LED: Deck B: Sync", 67 | CNT_INTVAL }, + { "LED: Deck B: Cue", 66 | CNT_INTVAL }, + { "LED: Deck B: Play", 65 | CNT_INTVAL }, + { "LED: Deck B: Tempo up", 185 | CNT_INTVAL }, + { "LED: Deck B: Tempo down", 184 | CNT_INTVAL }, + { "LED: Deck B: Master", 186 | CNT_INTVAL }, + { "LED: Deck B: Keylock", 187 | CNT_INTVAL }, + { "LED: Deck B: Deck B", 189 | CNT_INTVAL }, + { "LED: Deck B: Deck D", 188 | CNT_INTVAL }, + { "LED: Deck B: Samples", 190 | CNT_INTVAL }, + { "LED: Deck B: On Air", 191 | CNT_INTVAL }, + { "LED: Deck B: Sample 1", 71 | CNT_INTVAL }, + { "LED: Deck B: Sample 2", 70 | CNT_INTVAL }, + { "LED: Deck B: Sample 3", 69 | CNT_INTVAL }, + { "LED: Deck B: Sample 4", 68 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - A", 175 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - B", 174 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - C", 173 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - D", 172 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - E", 171 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - F", 170 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - G", 169 | CNT_INTVAL }, + { "LED: Deck B: Digit 1 - dot", 168 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - A", 167 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - B", 166 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - C", 165 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - D", 164 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - E", 163 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - F", 162 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - G", 161 | CNT_INTVAL }, + { "LED: Deck B: Digit 2 - dot", 160 | CNT_INTVAL }, + + { "LED: FX1: dry/wet", 153 | CNT_INTVAL }, + { "LED: FX1: 1", 154 | CNT_INTVAL }, + { "LED: FX1: 2", 155 | CNT_INTVAL }, + { "LED: FX1: 3", 156 | CNT_INTVAL }, + { "LED: FX1: Mode", 157 | CNT_INTVAL }, + { "LED: FX2: dry/wet", 129 | CNT_INTVAL }, + { "LED: FX2: 1", 130 | CNT_INTVAL }, + { "LED: FX2: 2", 131 | CNT_INTVAL }, + { "LED: FX2: 3", 132 | CNT_INTVAL }, + { "LED: FX2: Mode", 133 | CNT_INTVAL }, +}; + +static struct caiaq_controller maschine_controller[] = { + { "LED: Pad 1", 3 | CNT_INTVAL }, + { "LED: Pad 2", 2 | CNT_INTVAL }, + { "LED: Pad 3", 1 | CNT_INTVAL }, + { "LED: Pad 4", 0 | CNT_INTVAL }, + { "LED: Pad 5", 7 | CNT_INTVAL }, + { "LED: Pad 6", 6 | CNT_INTVAL }, + { "LED: Pad 7", 5 | CNT_INTVAL }, + { "LED: Pad 8", 4 | CNT_INTVAL }, + { "LED: Pad 9", 11 | CNT_INTVAL }, + { "LED: Pad 10", 10 | CNT_INTVAL }, + { "LED: Pad 11", 9 | CNT_INTVAL }, + { "LED: Pad 12", 8 | CNT_INTVAL }, + { "LED: Pad 13", 15 | CNT_INTVAL }, + { "LED: Pad 14", 14 | CNT_INTVAL }, + { "LED: Pad 15", 13 | CNT_INTVAL }, + { "LED: Pad 16", 12 | CNT_INTVAL }, + + { "LED: Mute", 16 | CNT_INTVAL }, + { "LED: Solo", 17 | CNT_INTVAL }, + { "LED: Select", 18 | CNT_INTVAL }, + { "LED: Duplicate", 19 | CNT_INTVAL }, + { "LED: Navigate", 20 | CNT_INTVAL }, + { "LED: Pad Mode", 21 | CNT_INTVAL }, + { "LED: Pattern", 22 | CNT_INTVAL }, + { "LED: Scene", 23 | CNT_INTVAL }, + + { "LED: Shift", 24 | CNT_INTVAL }, + { "LED: Erase", 25 | CNT_INTVAL }, + { "LED: Grid", 26 | CNT_INTVAL }, + { "LED: Right Bottom", 27 | CNT_INTVAL }, + { "LED: Rec", 28 | CNT_INTVAL }, + { "LED: Play", 29 | CNT_INTVAL }, + { "LED: Left Bottom", 32 | CNT_INTVAL }, + { "LED: Restart", 33 | CNT_INTVAL }, + + { "LED: Group A", 41 | CNT_INTVAL }, + { "LED: Group B", 40 | CNT_INTVAL }, + { "LED: Group C", 37 | CNT_INTVAL }, + { "LED: Group D", 36 | CNT_INTVAL }, + { "LED: Group E", 39 | CNT_INTVAL }, + { "LED: Group F", 38 | CNT_INTVAL }, + { "LED: Group G", 35 | CNT_INTVAL }, + { "LED: Group H", 34 | CNT_INTVAL }, + + { "LED: Auto Write", 42 | CNT_INTVAL }, + { "LED: Snap", 43 | CNT_INTVAL }, + { "LED: Right Top", 44 | CNT_INTVAL }, + { "LED: Left Top", 45 | CNT_INTVAL }, + { "LED: Sampling", 46 | CNT_INTVAL }, + { "LED: Browse", 47 | CNT_INTVAL }, + { "LED: Step", 48 | CNT_INTVAL }, + { "LED: Control", 49 | CNT_INTVAL }, + + { "LED: Top Button 1", 57 | CNT_INTVAL }, + { "LED: Top Button 2", 56 | CNT_INTVAL }, + { "LED: Top Button 3", 55 | CNT_INTVAL }, + { "LED: Top Button 4", 54 | CNT_INTVAL }, + { "LED: Top Button 5", 53 | CNT_INTVAL }, + { "LED: Top Button 6", 52 | CNT_INTVAL }, + { "LED: Top Button 7", 51 | CNT_INTVAL }, + { "LED: Top Button 8", 50 | CNT_INTVAL }, + + { "LED: Note Repeat", 58 | CNT_INTVAL }, + + { "Backlight Display", 59 | CNT_INTVAL } +}; + +static int add_controls(struct caiaq_controller *c, int num, + struct snd_usb_caiaqdev *cdev) +{ + int i, ret; + struct snd_kcontrol *kc; + + for (i = 0; i < num; i++, c++) { + kcontrol_template.name = c->name; + kcontrol_template.private_value = c->index; + kc = snd_ctl_new1(&kcontrol_template, cdev); + ret = snd_ctl_add(cdev->chip.card, kc); + if (ret < 0) + return ret; + } + + return 0; +} + +int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev) +{ + int ret = 0; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + ret = add_controls(ak1_controller, + ARRAY_SIZE(ak1_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + ret = add_controls(rk2_controller, + ARRAY_SIZE(rk2_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + ret = add_controls(rk3_controller, + ARRAY_SIZE(rk3_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + ret = add_controls(kore_controller, + ARRAY_SIZE(kore_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + ret = add_controls(a8dj_controller, + ARRAY_SIZE(a8dj_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO4DJ): + ret = add_controls(a4dj_controller, + ARRAY_SIZE(a4dj_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + ret = add_controls(kontrolx1_controller, + ARRAY_SIZE(kontrolx1_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + ret = add_controls(kontrols4_controller, + ARRAY_SIZE(kontrols4_controller), cdev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + ret = add_controls(maschine_controller, + ARRAY_SIZE(maschine_controller), cdev); + break; + } + + return ret; +} + diff --git a/sound/usb/caiaq/control.h b/sound/usb/caiaq/control.h new file mode 100644 index 000000000..cb204fd45 --- /dev/null +++ b/sound/usb/caiaq/control.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_CONTROL_H +#define CAIAQ_CONTROL_H + +int snd_usb_caiaq_control_init(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_CONTROL_H */ diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c new file mode 100644 index 000000000..d55ca48de --- /dev/null +++ b/sound/usb/caiaq/device.c @@ -0,0 +1,583 @@ +/* + * caiaq.c: ALSA driver for caiaq/NativeInstruments devices + * + * Copyright (c) 2007 Daniel Mack + * Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "audio.h" +#include "midi.h" +#include "control.h" +#include "input.h" + +MODULE_AUTHOR("Daniel Mack "); +MODULE_DESCRIPTION("caiaq USB audio"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Native Instruments,RigKontrol2}," + "{Native Instruments,RigKontrol3}," + "{Native Instruments,Kore Controller}," + "{Native Instruments,Kore Controller 2}," + "{Native Instruments,Audio Kontrol 1}," + "{Native Instruments,Audio 2 DJ}," + "{Native Instruments,Audio 4 DJ}," + "{Native Instruments,Audio 8 DJ}," + "{Native Instruments,Traktor Audio 2}," + "{Native Instruments,Session I/O}," + "{Native Instruments,GuitarRig mobile}," + "{Native Instruments,Traktor Kontrol X1}," + "{Native Instruments,Traktor Kontrol S4}," + "{Native Instruments,Maschine Controller}}"); + +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 the caiaq sound device"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the caiaq soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable the caiaq soundcard."); + +enum { + SAMPLERATE_44100 = 0, + SAMPLERATE_48000 = 1, + SAMPLERATE_96000 = 2, + SAMPLERATE_192000 = 3, + SAMPLERATE_88200 = 4, + SAMPLERATE_INVALID = 0xff +}; + +enum { + DEPTH_NONE = 0, + DEPTH_16 = 1, + DEPTH_24 = 2, + DEPTH_32 = 3 +}; + +static const struct usb_device_id snd_usb_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_RIGKONTROL2 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_RIGKONTROL3 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_KORECONTROLLER + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_KORECONTROLLER2 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_AK1 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_AUDIO8DJ + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_SESSIONIO + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_GUITARRIGMOBILE + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_AUDIO4DJ + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_AUDIO2DJ + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORKONTROLX1 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORKONTROLS4 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_TRAKTORAUDIO2 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = USB_VID_NATIVEINSTRUMENTS, + .idProduct = USB_PID_MASCHINECONTROLLER + }, + { /* terminator */ } +}; + +static void usb_ep1_command_reply_dispatch (struct urb* urb) +{ + int ret; + struct device *dev = &urb->dev->dev; + struct snd_usb_caiaqdev *cdev = urb->context; + unsigned char *buf = urb->transfer_buffer; + + if (urb->status || !cdev) { + dev_warn(dev, "received EP1 urb->status = %i\n", urb->status); + return; + } + + switch(buf[0]) { + case EP1_CMD_GET_DEVICE_INFO: + memcpy(&cdev->spec, buf+1, sizeof(struct caiaq_device_spec)); + cdev->spec.fw_version = le16_to_cpu(cdev->spec.fw_version); + dev_dbg(dev, "device spec (firmware %d): audio: %d in, %d out, " + "MIDI: %d in, %d out, data alignment %d\n", + cdev->spec.fw_version, + cdev->spec.num_analog_audio_in, + cdev->spec.num_analog_audio_out, + cdev->spec.num_midi_in, + cdev->spec.num_midi_out, + cdev->spec.data_alignment); + + cdev->spec_received++; + wake_up(&cdev->ep1_wait_queue); + break; + case EP1_CMD_AUDIO_PARAMS: + cdev->audio_parm_answer = buf[1]; + wake_up(&cdev->ep1_wait_queue); + break; + case EP1_CMD_MIDI_READ: + snd_usb_caiaq_midi_handle_input(cdev, buf[1], buf + 3, buf[2]); + break; + case EP1_CMD_READ_IO: + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ)) { + if (urb->actual_length > sizeof(cdev->control_state)) + urb->actual_length = sizeof(cdev->control_state); + memcpy(cdev->control_state, buf + 1, urb->actual_length); + wake_up(&cdev->ep1_wait_queue); + break; + } +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + case EP1_CMD_READ_ERP: + case EP1_CMD_READ_ANALOG: + snd_usb_caiaq_input_dispatch(cdev, buf, urb->actual_length); +#endif + break; + } + + cdev->ep1_in_urb.actual_length = 0; + ret = usb_submit_urb(&cdev->ep1_in_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(dev, "unable to submit urb. OOM!?\n"); +} + +int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, + unsigned char command, + const unsigned char *buffer, + int len) +{ + int actual_len; + struct usb_device *usb_dev = cdev->chip.dev; + + if (!usb_dev) + return -EIO; + + if (len > EP1_BUFSIZE - 1) + len = EP1_BUFSIZE - 1; + + if (buffer && len > 0) + memcpy(cdev->ep1_out_buf+1, buffer, len); + + cdev->ep1_out_buf[0] = command; + return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1), + cdev->ep1_out_buf, len+1, &actual_len, 200); +} + +int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, + unsigned char command, + unsigned char bank, + const unsigned char *buffer, + int len) +{ + int actual_len; + struct usb_device *usb_dev = cdev->chip.dev; + + if (!usb_dev) + return -EIO; + + if (len > EP1_BUFSIZE - 2) + len = EP1_BUFSIZE - 2; + + if (buffer && len > 0) + memcpy(cdev->ep1_out_buf+2, buffer, len); + + cdev->ep1_out_buf[0] = command; + cdev->ep1_out_buf[1] = bank; + + return usb_bulk_msg(usb_dev, usb_sndbulkpipe(usb_dev, 1), + cdev->ep1_out_buf, len+2, &actual_len, 200); +} + +int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev, + int rate, int depth, int bpp) +{ + int ret; + char tmp[5]; + struct device *dev = caiaqdev_to_dev(cdev); + + switch (rate) { + case 44100: tmp[0] = SAMPLERATE_44100; break; + case 48000: tmp[0] = SAMPLERATE_48000; break; + case 88200: tmp[0] = SAMPLERATE_88200; break; + case 96000: tmp[0] = SAMPLERATE_96000; break; + case 192000: tmp[0] = SAMPLERATE_192000; break; + default: return -EINVAL; + } + + switch (depth) { + case 16: tmp[1] = DEPTH_16; break; + case 24: tmp[1] = DEPTH_24; break; + default: return -EINVAL; + } + + tmp[2] = bpp & 0xff; + tmp[3] = bpp >> 8; + tmp[4] = 1; /* packets per microframe */ + + dev_dbg(dev, "setting audio params: %d Hz, %d bits, %d bpp\n", + rate, depth, bpp); + + cdev->audio_parm_answer = -1; + ret = snd_usb_caiaq_send_command(cdev, EP1_CMD_AUDIO_PARAMS, + tmp, sizeof(tmp)); + + if (ret) + return ret; + + if (!wait_event_timeout(cdev->ep1_wait_queue, + cdev->audio_parm_answer >= 0, HZ)) + return -EPIPE; + + if (cdev->audio_parm_answer != 1) + dev_dbg(dev, "unable to set the device's audio params\n"); + else + cdev->bpp = bpp; + + return cdev->audio_parm_answer == 1 ? 0 : -EINVAL; +} + +int snd_usb_caiaq_set_auto_msg(struct snd_usb_caiaqdev *cdev, + int digital, int analog, int erp) +{ + char tmp[3] = { digital, analog, erp }; + return snd_usb_caiaq_send_command(cdev, EP1_CMD_AUTO_MSG, + tmp, sizeof(tmp)); +} + +static void setup_card(struct snd_usb_caiaqdev *cdev) +{ + int ret; + char val[4]; + struct device *dev = caiaqdev_to_dev(cdev); + + /* device-specific startup specials */ + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + /* RigKontrol2 - display centered dash ('-') */ + val[0] = 0x00; + val[1] = 0x00; + val[2] = 0x01; + snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 3); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + /* RigKontrol2 - display two centered dashes ('--') */ + val[0] = 0x00; + val[1] = 0x40; + val[2] = 0x40; + val[3] = 0x00; + snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 4); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + /* Audio Kontrol 1 - make USB-LED stop blinking */ + val[0] = 0x00; + snd_usb_caiaq_send_command(cdev, EP1_CMD_WRITE_IO, val, 1); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AUDIO8DJ): + /* Audio 8 DJ - trigger read of current settings */ + cdev->control_state[0] = 0xff; + snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 0); + snd_usb_caiaq_send_command(cdev, EP1_CMD_READ_IO, NULL, 0); + + if (!wait_event_timeout(cdev->ep1_wait_queue, + cdev->control_state[0] != 0xff, HZ)) + return; + + /* fix up some defaults */ + if ((cdev->control_state[1] != 2) || + (cdev->control_state[2] != 3) || + (cdev->control_state[4] != 2)) { + cdev->control_state[1] = 2; + cdev->control_state[2] = 3; + cdev->control_state[4] = 2; + snd_usb_caiaq_send_command(cdev, + EP1_CMD_WRITE_IO, cdev->control_state, 6); + } + + break; + } + + if (cdev->spec.num_analog_audio_out + + cdev->spec.num_analog_audio_in + + cdev->spec.num_digital_audio_out + + cdev->spec.num_digital_audio_in > 0) { + ret = snd_usb_caiaq_audio_init(cdev); + if (ret < 0) + dev_err(dev, "Unable to set up audio system (ret=%d)\n", ret); + } + + if (cdev->spec.num_midi_in + + cdev->spec.num_midi_out > 0) { + ret = snd_usb_caiaq_midi_init(cdev); + if (ret < 0) + dev_err(dev, "Unable to set up MIDI system (ret=%d)\n", ret); + } + +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + ret = snd_usb_caiaq_input_init(cdev); + if (ret < 0) + dev_err(dev, "Unable to set up input system (ret=%d)\n", ret); +#endif + + /* finally, register the card and all its sub-instances */ + ret = snd_card_register(cdev->chip.card); + if (ret < 0) { + dev_err(dev, "snd_card_register() returned %d\n", ret); + snd_card_free(cdev->chip.card); + } + + ret = snd_usb_caiaq_control_init(cdev); + if (ret < 0) + dev_err(dev, "Unable to set up control system (ret=%d)\n", ret); +} + +static int create_card(struct usb_device *usb_dev, + struct usb_interface *intf, + struct snd_card **cardp) +{ + int devnum; + int err; + struct snd_card *card; + struct snd_usb_caiaqdev *cdev; + + for (devnum = 0; devnum < SNDRV_CARDS; devnum++) + if (enable[devnum]) + break; + + if (devnum >= SNDRV_CARDS) + return -ENODEV; + + err = snd_card_new(&intf->dev, + index[devnum], id[devnum], THIS_MODULE, + sizeof(struct snd_usb_caiaqdev), &card); + if (err < 0) + return err; + + cdev = caiaqdev(card); + cdev->chip.dev = usb_dev; + cdev->chip.card = card; + cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), + le16_to_cpu(usb_dev->descriptor.idProduct)); + spin_lock_init(&cdev->spinlock); + + *cardp = card; + return 0; +} + +static int init_card(struct snd_usb_caiaqdev *cdev) +{ + char *c, usbpath[32]; + struct usb_device *usb_dev = cdev->chip.dev; + struct snd_card *card = cdev->chip.card; + struct device *dev = caiaqdev_to_dev(cdev); + int err, len; + + if (usb_set_interface(usb_dev, 0, 1) != 0) { + dev_err(dev, "can't set alt interface.\n"); + return -EIO; + } + + usb_init_urb(&cdev->ep1_in_urb); + usb_init_urb(&cdev->midi_out_urb); + + usb_fill_bulk_urb(&cdev->ep1_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x1), + cdev->ep1_in_buf, EP1_BUFSIZE, + usb_ep1_command_reply_dispatch, cdev); + + usb_fill_bulk_urb(&cdev->midi_out_urb, usb_dev, + usb_sndbulkpipe(usb_dev, 0x1), + cdev->midi_out_buf, EP1_BUFSIZE, + snd_usb_caiaq_midi_output_done, cdev); + + /* sanity checks of EPs before actually submitting */ + if (usb_urb_ep_type_check(&cdev->ep1_in_urb) || + usb_urb_ep_type_check(&cdev->midi_out_urb)) { + dev_err(dev, "invalid EPs\n"); + return -EINVAL; + } + + init_waitqueue_head(&cdev->ep1_wait_queue); + init_waitqueue_head(&cdev->prepare_wait_queue); + + if (usb_submit_urb(&cdev->ep1_in_urb, GFP_KERNEL) != 0) + return -EIO; + + err = snd_usb_caiaq_send_command(cdev, EP1_CMD_GET_DEVICE_INFO, NULL, 0); + if (err) + goto err_kill_urb; + + if (!wait_event_timeout(cdev->ep1_wait_queue, cdev->spec_received, HZ)) { + err = -ENODEV; + goto err_kill_urb; + } + + usb_string(usb_dev, usb_dev->descriptor.iManufacturer, + cdev->vendor_name, CAIAQ_USB_STR_LEN); + + usb_string(usb_dev, usb_dev->descriptor.iProduct, + cdev->product_name, CAIAQ_USB_STR_LEN); + + strlcpy(card->driver, MODNAME, sizeof(card->driver)); + strlcpy(card->shortname, cdev->product_name, sizeof(card->shortname)); + strlcpy(card->mixername, cdev->product_name, sizeof(card->mixername)); + + /* if the id was not passed as module option, fill it with a shortened + * version of the product string which does not contain any + * whitespaces */ + + if (*card->id == '\0') { + char id[sizeof(card->id)]; + + memset(id, 0, sizeof(id)); + + for (c = card->shortname, len = 0; + *c && len < sizeof(card->id); c++) + if (*c != ' ') + id[len++] = *c; + + snd_card_set_id(card, id); + } + + usb_make_path(usb_dev, usbpath, sizeof(usbpath)); + snprintf(card->longname, sizeof(card->longname), "%s %s (%s)", + cdev->vendor_name, cdev->product_name, usbpath); + + setup_card(cdev); + return 0; + + err_kill_urb: + usb_kill_urb(&cdev->ep1_in_urb); + return err; +} + +static int snd_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret; + struct snd_card *card = NULL; + struct usb_device *usb_dev = interface_to_usbdev(intf); + + ret = create_card(usb_dev, intf, &card); + + if (ret < 0) + return ret; + + usb_set_intfdata(intf, card); + ret = init_card(caiaqdev(card)); + if (ret < 0) { + dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret); + snd_card_free(card); + return ret; + } + + return 0; +} + +static void snd_disconnect(struct usb_interface *intf) +{ + struct snd_card *card = usb_get_intfdata(intf); + struct device *dev = intf->usb_dev; + struct snd_usb_caiaqdev *cdev; + + if (!card) + return; + + cdev = caiaqdev(card); + dev_dbg(dev, "%s(%p)\n", __func__, intf); + + snd_card_disconnect(card); + +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + snd_usb_caiaq_input_free(cdev); +#endif + snd_usb_caiaq_audio_free(cdev); + + usb_kill_urb(&cdev->ep1_in_urb); + usb_kill_urb(&cdev->midi_out_urb); + + snd_card_free(card); + usb_reset_device(interface_to_usbdev(intf)); +} + + +MODULE_DEVICE_TABLE(usb, snd_usb_id_table); +static struct usb_driver snd_usb_driver = { + .name = MODNAME, + .probe = snd_probe, + .disconnect = snd_disconnect, + .id_table = snd_usb_id_table, +}; + +module_usb_driver(snd_usb_driver); diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h new file mode 100644 index 000000000..50fea0857 --- /dev/null +++ b/sound/usb/caiaq/device.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_DEVICE_H +#define CAIAQ_DEVICE_H + +#include "../usbaudio.h" + +#define USB_VID_NATIVEINSTRUMENTS 0x17cc + +#define USB_PID_RIGKONTROL2 0x1969 +#define USB_PID_RIGKONTROL3 0x1940 +#define USB_PID_KORECONTROLLER 0x4711 +#define USB_PID_KORECONTROLLER2 0x4712 +#define USB_PID_AK1 0x0815 +#define USB_PID_AUDIO2DJ 0x041c +#define USB_PID_AUDIO4DJ 0x0839 +#define USB_PID_AUDIO8DJ 0x1978 +#define USB_PID_SESSIONIO 0x1915 +#define USB_PID_GUITARRIGMOBILE 0x0d8d +#define USB_PID_TRAKTORKONTROLX1 0x2305 +#define USB_PID_TRAKTORKONTROLS4 0xbaff +#define USB_PID_TRAKTORAUDIO2 0x041d +#define USB_PID_MASCHINECONTROLLER 0x0808 + +#define EP1_BUFSIZE 64 +#define EP4_BUFSIZE 512 +#define CAIAQ_USB_STR_LEN 0xff +#define MAX_STREAMS 32 + +#define MODNAME "snd-usb-caiaq" + +#define EP1_CMD_GET_DEVICE_INFO 0x1 +#define EP1_CMD_READ_ERP 0x2 +#define EP1_CMD_READ_ANALOG 0x3 +#define EP1_CMD_READ_IO 0x4 +#define EP1_CMD_WRITE_IO 0x5 +#define EP1_CMD_MIDI_READ 0x6 +#define EP1_CMD_MIDI_WRITE 0x7 +#define EP1_CMD_AUDIO_PARAMS 0x9 +#define EP1_CMD_AUTO_MSG 0xb +#define EP1_CMD_DIMM_LEDS 0xc + +struct caiaq_device_spec { + unsigned short fw_version; + unsigned char hw_subtype; + unsigned char num_erp; + unsigned char num_analog_in; + unsigned char num_digital_in; + unsigned char num_digital_out; + unsigned char num_analog_audio_out; + unsigned char num_analog_audio_in; + unsigned char num_digital_audio_out; + unsigned char num_digital_audio_in; + unsigned char num_midi_out; + unsigned char num_midi_in; + unsigned char data_alignment; +} __attribute__ ((packed)); + +struct snd_usb_caiaq_cb_info; + +struct snd_usb_caiaqdev { + struct snd_usb_audio chip; + + struct urb ep1_in_urb; + struct urb midi_out_urb; + struct urb **data_urbs_in; + struct urb **data_urbs_out; + struct snd_usb_caiaq_cb_info *data_cb_info; + + unsigned char ep1_in_buf[EP1_BUFSIZE]; + unsigned char ep1_out_buf[EP1_BUFSIZE]; + unsigned char midi_out_buf[EP1_BUFSIZE]; + + struct caiaq_device_spec spec; + spinlock_t spinlock; + wait_queue_head_t ep1_wait_queue; + wait_queue_head_t prepare_wait_queue; + int spec_received, audio_parm_answer; + int midi_out_active; + + char vendor_name[CAIAQ_USB_STR_LEN]; + char product_name[CAIAQ_USB_STR_LEN]; + + int n_streams, n_audio_in, n_audio_out; + int streaming, first_packet, output_running; + int audio_in_buf_pos[MAX_STREAMS]; + int audio_out_buf_pos[MAX_STREAMS]; + int period_in_count[MAX_STREAMS]; + int period_out_count[MAX_STREAMS]; + int input_panic, output_panic, warned; + char *audio_in_buf, *audio_out_buf; + unsigned int samplerates, bpp; + unsigned long outurb_active_mask; + + struct snd_pcm_substream *sub_playback[MAX_STREAMS]; + struct snd_pcm_substream *sub_capture[MAX_STREAMS]; + + /* Controls */ + unsigned char control_state[256]; + unsigned char ep8_out_buf[2]; + + /* Linux input */ +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + struct input_dev *input_dev; + char phys[64]; /* physical device path */ + unsigned short keycode[128]; + struct urb *ep4_in_urb; + unsigned char ep4_in_buf[EP4_BUFSIZE]; +#endif + + /* ALSA */ + struct snd_pcm *pcm; + struct snd_pcm_hardware pcm_info; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_receive_substream; + struct snd_rawmidi_substream *midi_out_substream; +}; + +struct snd_usb_caiaq_cb_info { + struct snd_usb_caiaqdev *cdev; + int index; +}; + +#define caiaqdev(c) ((struct snd_usb_caiaqdev*)(c)->private_data) +#define caiaqdev_to_dev(d) (d->chip.card->dev) + +int snd_usb_caiaq_set_audio_params (struct snd_usb_caiaqdev *cdev, int rate, int depth, int bbp); +int snd_usb_caiaq_set_auto_msg (struct snd_usb_caiaqdev *cdev, int digital, int analog, int erp); +int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, + unsigned char command, + const unsigned char *buffer, + int len); +int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, + unsigned char command, + unsigned char bank, + const unsigned char *buffer, + int len); + +#endif /* CAIAQ_DEVICE_H */ diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c new file mode 100644 index 000000000..e883659ea --- /dev/null +++ b/sound/usb/caiaq/input.c @@ -0,0 +1,855 @@ +/* + * Copyright (c) 2006,2007 Daniel Mack, Tim Ruetz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "input.h" + +static unsigned short keycode_ak1[] = { KEY_C, KEY_B, KEY_A }; +static unsigned short keycode_rk2[] = { KEY_1, KEY_2, KEY_3, KEY_4, + KEY_5, KEY_6, KEY_7 }; +static unsigned short keycode_rk3[] = { KEY_1, KEY_2, KEY_3, KEY_4, + KEY_5, KEY_6, KEY_7, KEY_8, KEY_9 }; + +static unsigned short keycode_kore[] = { + KEY_FN_F1, /* "menu" */ + KEY_FN_F7, /* "lcd backlight */ + KEY_FN_F2, /* "control" */ + KEY_FN_F3, /* "enter" */ + KEY_FN_F4, /* "view" */ + KEY_FN_F5, /* "esc" */ + KEY_FN_F6, /* "sound" */ + KEY_FN_F8, /* array spacer, never triggered. */ + KEY_RIGHT, + KEY_DOWN, + KEY_UP, + KEY_LEFT, + KEY_SOUND, /* "listen" */ + KEY_RECORD, + KEY_PLAYPAUSE, + KEY_STOP, + BTN_4, /* 8 softkeys */ + BTN_3, + BTN_2, + BTN_1, + BTN_8, + BTN_7, + BTN_6, + BTN_5, + KEY_BRL_DOT4, /* touch sensitive knobs */ + KEY_BRL_DOT3, + KEY_BRL_DOT2, + KEY_BRL_DOT1, + KEY_BRL_DOT8, + KEY_BRL_DOT7, + KEY_BRL_DOT6, + KEY_BRL_DOT5 +}; + +#define MASCHINE_BUTTONS (42) +#define MASCHINE_BUTTON(X) ((X) + BTN_MISC) +#define MASCHINE_PADS (16) +#define MASCHINE_PAD(X) ((X) + ABS_PRESSURE) + +static unsigned short keycode_maschine[] = { + MASCHINE_BUTTON(40), /* mute */ + MASCHINE_BUTTON(39), /* solo */ + MASCHINE_BUTTON(38), /* select */ + MASCHINE_BUTTON(37), /* duplicate */ + MASCHINE_BUTTON(36), /* navigate */ + MASCHINE_BUTTON(35), /* pad mode */ + MASCHINE_BUTTON(34), /* pattern */ + MASCHINE_BUTTON(33), /* scene */ + KEY_RESERVED, /* spacer */ + + MASCHINE_BUTTON(30), /* rec */ + MASCHINE_BUTTON(31), /* erase */ + MASCHINE_BUTTON(32), /* shift */ + MASCHINE_BUTTON(28), /* grid */ + MASCHINE_BUTTON(27), /* > */ + MASCHINE_BUTTON(26), /* < */ + MASCHINE_BUTTON(25), /* restart */ + + MASCHINE_BUTTON(21), /* E */ + MASCHINE_BUTTON(22), /* F */ + MASCHINE_BUTTON(23), /* G */ + MASCHINE_BUTTON(24), /* H */ + MASCHINE_BUTTON(20), /* D */ + MASCHINE_BUTTON(19), /* C */ + MASCHINE_BUTTON(18), /* B */ + MASCHINE_BUTTON(17), /* A */ + + MASCHINE_BUTTON(0), /* control */ + MASCHINE_BUTTON(2), /* browse */ + MASCHINE_BUTTON(4), /* < */ + MASCHINE_BUTTON(6), /* snap */ + MASCHINE_BUTTON(7), /* autowrite */ + MASCHINE_BUTTON(5), /* > */ + MASCHINE_BUTTON(3), /* sampling */ + MASCHINE_BUTTON(1), /* step */ + + MASCHINE_BUTTON(15), /* 8 softkeys */ + MASCHINE_BUTTON(14), + MASCHINE_BUTTON(13), + MASCHINE_BUTTON(12), + MASCHINE_BUTTON(11), + MASCHINE_BUTTON(10), + MASCHINE_BUTTON(9), + MASCHINE_BUTTON(8), + + MASCHINE_BUTTON(16), /* note repeat */ + MASCHINE_BUTTON(29) /* play */ +}; + +#define KONTROLX1_INPUTS (40) +#define KONTROLS4_BUTTONS (12 * 8) +#define KONTROLS4_AXIS (46) + +#define KONTROLS4_BUTTON(X) ((X) + BTN_MISC) +#define KONTROLS4_ABS(X) ((X) + ABS_HAT0X) + +#define DEG90 (range / 2) +#define DEG180 (range) +#define DEG270 (DEG90 + DEG180) +#define DEG360 (DEG180 * 2) +#define HIGH_PEAK (268) +#define LOW_PEAK (-7) + +/* some of these devices have endless rotation potentiometers + * built in which use two tapers, 90 degrees phase shifted. + * this algorithm decodes them to one single value, ranging + * from 0 to 999 */ +static unsigned int decode_erp(unsigned char a, unsigned char b) +{ + int weight_a, weight_b; + int pos_a, pos_b; + int ret; + int range = HIGH_PEAK - LOW_PEAK; + int mid_value = (HIGH_PEAK + LOW_PEAK) / 2; + + weight_b = abs(mid_value - a) - (range / 2 - 100) / 2; + + if (weight_b < 0) + weight_b = 0; + + if (weight_b > 100) + weight_b = 100; + + weight_a = 100 - weight_b; + + if (a < mid_value) { + /* 0..90 and 270..360 degrees */ + pos_b = b - LOW_PEAK + DEG270; + if (pos_b >= DEG360) + pos_b -= DEG360; + } else + /* 90..270 degrees */ + pos_b = HIGH_PEAK - b + DEG90; + + + if (b > mid_value) + /* 0..180 degrees */ + pos_a = a - LOW_PEAK; + else + /* 180..360 degrees */ + pos_a = HIGH_PEAK - a + DEG180; + + /* interpolate both slider values, depending on weight factors */ + /* 0..99 x DEG360 */ + ret = pos_a * weight_a + pos_b * weight_b; + + /* normalize to 0..999 */ + ret *= 10; + ret /= DEG360; + + if (ret < 0) + ret += 1000; + + if (ret >= 1000) + ret -= 1000; + + return ret; +} + +#undef DEG90 +#undef DEG180 +#undef DEG270 +#undef DEG360 +#undef HIGH_PEAK +#undef LOW_PEAK + +static inline void snd_caiaq_input_report_abs(struct snd_usb_caiaqdev *cdev, + int axis, const unsigned char *buf, + int offset) +{ + input_report_abs(cdev->input_dev, axis, + (buf[offset * 2] << 8) | buf[offset * 2 + 1]); +} + +static void snd_caiaq_input_read_analog(struct snd_usb_caiaqdev *cdev, + const unsigned char *buf, + unsigned int len) +{ + struct input_dev *input_dev = cdev->input_dev; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + snd_caiaq_input_report_abs(cdev, ABS_X, buf, 2); + snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 0); + snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 1); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + snd_caiaq_input_report_abs(cdev, ABS_X, buf, 0); + snd_caiaq_input_report_abs(cdev, ABS_Y, buf, 1); + snd_caiaq_input_report_abs(cdev, ABS_Z, buf, 2); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + snd_caiaq_input_report_abs(cdev, ABS_HAT0X, buf, 4); + snd_caiaq_input_report_abs(cdev, ABS_HAT0Y, buf, 2); + snd_caiaq_input_report_abs(cdev, ABS_HAT1X, buf, 6); + snd_caiaq_input_report_abs(cdev, ABS_HAT1Y, buf, 1); + snd_caiaq_input_report_abs(cdev, ABS_HAT2X, buf, 7); + snd_caiaq_input_report_abs(cdev, ABS_HAT2Y, buf, 0); + snd_caiaq_input_report_abs(cdev, ABS_HAT3X, buf, 5); + snd_caiaq_input_report_abs(cdev, ABS_HAT3Y, buf, 3); + break; + } + + input_sync(input_dev); +} + +static void snd_caiaq_input_read_erp(struct snd_usb_caiaqdev *cdev, + const char *buf, unsigned int len) +{ + struct input_dev *input_dev = cdev->input_dev; + int i; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + i = decode_erp(buf[0], buf[1]); + input_report_abs(input_dev, ABS_X, i); + input_sync(input_dev); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + i = decode_erp(buf[7], buf[5]); + input_report_abs(input_dev, ABS_HAT0X, i); + i = decode_erp(buf[12], buf[14]); + input_report_abs(input_dev, ABS_HAT0Y, i); + i = decode_erp(buf[15], buf[13]); + input_report_abs(input_dev, ABS_HAT1X, i); + i = decode_erp(buf[0], buf[2]); + input_report_abs(input_dev, ABS_HAT1Y, i); + i = decode_erp(buf[3], buf[1]); + input_report_abs(input_dev, ABS_HAT2X, i); + i = decode_erp(buf[8], buf[10]); + input_report_abs(input_dev, ABS_HAT2Y, i); + i = decode_erp(buf[11], buf[9]); + input_report_abs(input_dev, ABS_HAT3X, i); + i = decode_erp(buf[4], buf[6]); + input_report_abs(input_dev, ABS_HAT3Y, i); + input_sync(input_dev); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + /* 4 under the left screen */ + input_report_abs(input_dev, ABS_HAT0X, decode_erp(buf[21], buf[20])); + input_report_abs(input_dev, ABS_HAT0Y, decode_erp(buf[15], buf[14])); + input_report_abs(input_dev, ABS_HAT1X, decode_erp(buf[9], buf[8])); + input_report_abs(input_dev, ABS_HAT1Y, decode_erp(buf[3], buf[2])); + + /* 4 under the right screen */ + input_report_abs(input_dev, ABS_HAT2X, decode_erp(buf[19], buf[18])); + input_report_abs(input_dev, ABS_HAT2Y, decode_erp(buf[13], buf[12])); + input_report_abs(input_dev, ABS_HAT3X, decode_erp(buf[7], buf[6])); + input_report_abs(input_dev, ABS_HAT3Y, decode_erp(buf[1], buf[0])); + + /* volume */ + input_report_abs(input_dev, ABS_RX, decode_erp(buf[17], buf[16])); + /* tempo */ + input_report_abs(input_dev, ABS_RY, decode_erp(buf[11], buf[10])); + /* swing */ + input_report_abs(input_dev, ABS_RZ, decode_erp(buf[5], buf[4])); + + input_sync(input_dev); + break; + } +} + +static void snd_caiaq_input_read_io(struct snd_usb_caiaqdev *cdev, + unsigned char *buf, unsigned int len) +{ + struct input_dev *input_dev = cdev->input_dev; + unsigned short *keycode = input_dev->keycode; + int i; + + if (!keycode) + return; + + if (input_dev->id.product == USB_PID_RIGKONTROL2) + for (i = 0; i < len; i++) + buf[i] = ~buf[i]; + + for (i = 0; i < input_dev->keycodemax && i < len * 8; i++) + input_report_key(input_dev, keycode[i], + buf[i / 8] & (1 << (i % 8))); + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + input_report_abs(cdev->input_dev, ABS_MISC, 255 - buf[4]); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + /* rotary encoders */ + input_report_abs(cdev->input_dev, ABS_X, buf[5] & 0xf); + input_report_abs(cdev->input_dev, ABS_Y, buf[5] >> 4); + input_report_abs(cdev->input_dev, ABS_Z, buf[6] & 0xf); + input_report_abs(cdev->input_dev, ABS_MISC, buf[6] >> 4); + break; + } + + input_sync(input_dev); +} + +#define TKS4_MSGBLOCK_SIZE 16 + +static void snd_usb_caiaq_tks4_dispatch(struct snd_usb_caiaqdev *cdev, + const unsigned char *buf, + unsigned int len) +{ + struct device *dev = caiaqdev_to_dev(cdev); + + while (len) { + unsigned int i, block_id = (buf[0] << 8) | buf[1]; + + switch (block_id) { + case 0: + /* buttons */ + for (i = 0; i < KONTROLS4_BUTTONS; i++) + input_report_key(cdev->input_dev, KONTROLS4_BUTTON(i), + (buf[4 + (i / 8)] >> (i % 8)) & 1); + break; + + case 1: + /* left wheel */ + input_report_abs(cdev->input_dev, KONTROLS4_ABS(36), buf[9] | ((buf[8] & 0x3) << 8)); + /* right wheel */ + input_report_abs(cdev->input_dev, KONTROLS4_ABS(37), buf[13] | ((buf[12] & 0x3) << 8)); + + /* rotary encoders */ + input_report_abs(cdev->input_dev, KONTROLS4_ABS(38), buf[3] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(39), buf[4] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(40), buf[4] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(41), buf[5] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(42), buf[5] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(43), buf[6] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(44), buf[6] & 0xf); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(45), buf[7] >> 4); + input_report_abs(cdev->input_dev, KONTROLS4_ABS(46), buf[7] & 0xf); + + break; + case 2: + /* Volume Fader Channel D */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(0), buf, 1); + /* Volume Fader Channel B */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(1), buf, 2); + /* Volume Fader Channel A */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(2), buf, 3); + /* Volume Fader Channel C */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(3), buf, 4); + /* Loop Volume */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(4), buf, 6); + /* Crossfader */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(7), buf, 7); + + break; + + case 3: + /* Tempo Fader R */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(6), buf, 3); + /* Tempo Fader L */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(5), buf, 4); + /* Mic Volume */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(8), buf, 6); + /* Cue Mix */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(9), buf, 7); + + break; + + case 4: + /* Wheel distance sensor L */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(10), buf, 1); + /* Wheel distance sensor R */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(11), buf, 2); + /* Channel D EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(12), buf, 3); + /* Channel D EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(13), buf, 4); + /* Channel D EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(14), buf, 5); + /* Channel D EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(15), buf, 6); + /* FX2 - dry/wet */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(16), buf, 7); + + break; + + case 5: + /* FX2 - 1 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(17), buf, 1); + /* FX2 - 2 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(18), buf, 2); + /* FX2 - 3 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(19), buf, 3); + /* Channel B EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(20), buf, 4); + /* Channel B EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(21), buf, 5); + /* Channel B EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(22), buf, 6); + /* Channel B EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(23), buf, 7); + + break; + + case 6: + /* Channel A EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(24), buf, 1); + /* Channel A EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(25), buf, 2); + /* Channel A EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(26), buf, 3); + /* Channel A EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(27), buf, 4); + /* Channel C EQ - Filter */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(28), buf, 5); + /* Channel C EQ - Low */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(29), buf, 6); + /* Channel C EQ - Mid */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(30), buf, 7); + + break; + + case 7: + /* Channel C EQ - Hi */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(31), buf, 1); + /* FX1 - wet/dry */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(32), buf, 2); + /* FX1 - 1 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(33), buf, 3); + /* FX1 - 2 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(34), buf, 4); + /* FX1 - 3 */ + snd_caiaq_input_report_abs(cdev, KONTROLS4_ABS(35), buf, 5); + + break; + + default: + dev_dbg(dev, "%s(): bogus block (id %d)\n", + __func__, block_id); + return; + } + + len -= TKS4_MSGBLOCK_SIZE; + buf += TKS4_MSGBLOCK_SIZE; + } + + input_sync(cdev->input_dev); +} + +#define MASCHINE_MSGBLOCK_SIZE 2 + +static void snd_usb_caiaq_maschine_dispatch(struct snd_usb_caiaqdev *cdev, + const unsigned char *buf, + unsigned int len) +{ + unsigned int i, pad_id; + __le16 *pressure = (__le16 *) buf; + + for (i = 0; i < MASCHINE_PADS; i++) { + pad_id = le16_to_cpu(*pressure) >> 12; + input_report_abs(cdev->input_dev, MASCHINE_PAD(pad_id), + le16_to_cpu(*pressure) & 0xfff); + pressure++; + } + + input_sync(cdev->input_dev); +} + +static void snd_usb_caiaq_ep4_reply_dispatch(struct urb *urb) +{ + struct snd_usb_caiaqdev *cdev = urb->context; + unsigned char *buf = urb->transfer_buffer; + struct device *dev = &urb->dev->dev; + int ret; + + if (urb->status || !cdev || urb != cdev->ep4_in_urb) + return; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + if (urb->actual_length < 24) + goto requeue; + + if (buf[0] & 0x3) + snd_caiaq_input_read_io(cdev, buf + 1, 7); + + if (buf[0] & 0x4) + snd_caiaq_input_read_analog(cdev, buf + 8, 16); + + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + snd_usb_caiaq_tks4_dispatch(cdev, buf, urb->actual_length); + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + if (urb->actual_length < (MASCHINE_PADS * MASCHINE_MSGBLOCK_SIZE)) + goto requeue; + + snd_usb_caiaq_maschine_dispatch(cdev, buf, urb->actual_length); + break; + } + +requeue: + cdev->ep4_in_urb->actual_length = 0; + ret = usb_submit_urb(cdev->ep4_in_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(dev, "unable to submit urb. OOM!?\n"); +} + +static int snd_usb_caiaq_input_open(struct input_dev *idev) +{ + struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev); + + if (!cdev) + return -EINVAL; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + if (usb_submit_urb(cdev->ep4_in_urb, GFP_KERNEL) != 0) + return -EIO; + break; + } + + return 0; +} + +static void snd_usb_caiaq_input_close(struct input_dev *idev) +{ + struct snd_usb_caiaqdev *cdev = input_get_drvdata(idev); + + if (!cdev) + return; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + usb_kill_urb(cdev->ep4_in_urb); + break; + } +} + +void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, + char *buf, + unsigned int len) +{ + if (!cdev->input_dev || len < 1) + return; + + switch (buf[0]) { + case EP1_CMD_READ_ANALOG: + snd_caiaq_input_read_analog(cdev, buf + 1, len - 1); + break; + case EP1_CMD_READ_ERP: + snd_caiaq_input_read_erp(cdev, buf + 1, len - 1); + break; + case EP1_CMD_READ_IO: + snd_caiaq_input_read_io(cdev, buf + 1, len - 1); + break; + } +} + +int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) +{ + struct usb_device *usb_dev = cdev->chip.dev; + struct input_dev *input; + int i, ret = 0; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys)); + strlcat(cdev->phys, "/input0", sizeof(cdev->phys)); + + input->name = cdev->product_name; + input->phys = cdev->phys; + usb_to_input_id(usb_dev, &input->id); + input->dev.parent = &usb_dev->dev; + + input_set_drvdata(input, cdev); + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL2): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk2)); + memcpy(cdev->keycode, keycode_rk2, sizeof(keycode_rk2)); + input->keycodemax = ARRAY_SIZE(keycode_rk2); + input_set_abs_params(input, ABS_X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10); + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_RIGKONTROL3): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_rk3)); + memcpy(cdev->keycode, keycode_rk3, sizeof(keycode_rk3)); + input->keycodemax = ARRAY_SIZE(keycode_rk3); + input_set_abs_params(input, ABS_X, 0, 1024, 0, 10); + input_set_abs_params(input, ABS_Y, 0, 1024, 0, 10); + input_set_abs_params(input, ABS_Z, 0, 1024, 0, 10); + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 0); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_AK1): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_X); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_ak1)); + memcpy(cdev->keycode, keycode_ak1, sizeof(keycode_ak1)); + input->keycodemax = ARRAY_SIZE(keycode_ak1); + input_set_abs_params(input, ABS_X, 0, 999, 0, 10); + snd_usb_caiaq_set_auto_msg(cdev, 1, 0, 5); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) | + BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) | + BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) | + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore)); + memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore)); + input->keycodemax = ARRAY_SIZE(keycode_kore); + input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_Z, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_MISC, 0, 255, 0, 1); + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + break; + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLX1): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) | + BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) | + BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) | + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) | + BIT_MASK(ABS_Z); + input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); + BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLX1_INPUTS); + for (i = 0; i < KONTROLX1_INPUTS; i++) + cdev->keycode[i] = BTN_MISC + i; + input->keycodemax = KONTROLX1_INPUTS; + + /* analog potentiometers */ + input_set_abs_params(input, ABS_HAT0X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT0Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT1X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT1Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT2X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT2Y, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT3X, 0, 4096, 0, 10); + input_set_abs_params(input, ABS_HAT3Y, 0, 4096, 0, 10); + + /* rotary encoders */ + input_set_abs_params(input, ABS_X, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_Y, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_Z, 0, 0xf, 0, 1); + input_set_abs_params(input, ABS_MISC, 0, 0xf, 0, 1); + + cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep4_in_urb) { + ret = -ENOMEM; + goto exit_free_idev; + } + + usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x4), + cdev->ep4_in_buf, EP4_BUFSIZE, + snd_usb_caiaq_ep4_reply_dispatch, cdev); + ret = usb_urb_ep_type_check(cdev->ep4_in_urb); + if (ret < 0) + goto exit_free_idev; + + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_TRAKTORKONTROLS4): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + BUILD_BUG_ON(sizeof(cdev->keycode) < KONTROLS4_BUTTONS); + for (i = 0; i < KONTROLS4_BUTTONS; i++) + cdev->keycode[i] = KONTROLS4_BUTTON(i); + input->keycodemax = KONTROLS4_BUTTONS; + + for (i = 0; i < KONTROLS4_AXIS; i++) { + int axis = KONTROLS4_ABS(i); + input->absbit[BIT_WORD(axis)] |= BIT_MASK(axis); + } + + /* 36 analog potentiometers and faders */ + for (i = 0; i < 36; i++) + input_set_abs_params(input, KONTROLS4_ABS(i), 0, 0xfff, 0, 10); + + /* 2 encoder wheels */ + input_set_abs_params(input, KONTROLS4_ABS(36), 0, 0x3ff, 0, 1); + input_set_abs_params(input, KONTROLS4_ABS(37), 0, 0x3ff, 0, 1); + + /* 9 rotary encoders */ + for (i = 0; i < 9; i++) + input_set_abs_params(input, KONTROLS4_ABS(38+i), 0, 0xf, 0, 1); + + cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep4_in_urb) { + ret = -ENOMEM; + goto exit_free_idev; + } + + usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x4), + cdev->ep4_in_buf, EP4_BUFSIZE, + snd_usb_caiaq_ep4_reply_dispatch, cdev); + ret = usb_urb_ep_type_check(cdev->ep4_in_urb); + if (ret < 0) + goto exit_free_idev; + + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + + break; + + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_MASCHINECONTROLLER): + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->absbit[0] = BIT_MASK(ABS_HAT0X) | BIT_MASK(ABS_HAT0Y) | + BIT_MASK(ABS_HAT1X) | BIT_MASK(ABS_HAT1Y) | + BIT_MASK(ABS_HAT2X) | BIT_MASK(ABS_HAT2Y) | + BIT_MASK(ABS_HAT3X) | BIT_MASK(ABS_HAT3Y) | + BIT_MASK(ABS_RX) | BIT_MASK(ABS_RY) | + BIT_MASK(ABS_RZ); + + BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_maschine)); + memcpy(cdev->keycode, keycode_maschine, sizeof(keycode_maschine)); + input->keycodemax = ARRAY_SIZE(keycode_maschine); + + for (i = 0; i < MASCHINE_PADS; i++) { + input->absbit[0] |= MASCHINE_PAD(i); + input_set_abs_params(input, MASCHINE_PAD(i), 0, 0xfff, 5, 10); + } + + input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT1Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT2Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3X, 0, 999, 0, 10); + input_set_abs_params(input, ABS_HAT3Y, 0, 999, 0, 10); + input_set_abs_params(input, ABS_RX, 0, 999, 0, 10); + input_set_abs_params(input, ABS_RY, 0, 999, 0, 10); + input_set_abs_params(input, ABS_RZ, 0, 999, 0, 10); + + cdev->ep4_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep4_in_urb) { + ret = -ENOMEM; + goto exit_free_idev; + } + + usb_fill_bulk_urb(cdev->ep4_in_urb, usb_dev, + usb_rcvbulkpipe(usb_dev, 0x4), + cdev->ep4_in_buf, EP4_BUFSIZE, + snd_usb_caiaq_ep4_reply_dispatch, cdev); + ret = usb_urb_ep_type_check(cdev->ep4_in_urb); + if (ret < 0) + goto exit_free_idev; + + snd_usb_caiaq_set_auto_msg(cdev, 1, 10, 5); + break; + + default: + /* no input methods supported on this device */ + goto exit_free_idev; + } + + input->open = snd_usb_caiaq_input_open; + input->close = snd_usb_caiaq_input_close; + input->keycode = cdev->keycode; + input->keycodesize = sizeof(unsigned short); + for (i = 0; i < input->keycodemax; i++) + __set_bit(cdev->keycode[i], input->keybit); + + cdev->input_dev = input; + + ret = input_register_device(input); + if (ret < 0) + goto exit_free_idev; + + return 0; + +exit_free_idev: + input_free_device(input); + cdev->input_dev = NULL; + return ret; +} + +void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev) +{ + if (!cdev || !cdev->input_dev) + return; + + usb_kill_urb(cdev->ep4_in_urb); + usb_free_urb(cdev->ep4_in_urb); + cdev->ep4_in_urb = NULL; + + input_unregister_device(cdev->input_dev); + cdev->input_dev = NULL; +} diff --git a/sound/usb/caiaq/input.h b/sound/usb/caiaq/input.h new file mode 100644 index 000000000..c42891e7b --- /dev/null +++ b/sound/usb/caiaq/input.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_INPUT_H +#define CAIAQ_INPUT_H + +void snd_usb_caiaq_input_dispatch(struct snd_usb_caiaqdev *cdev, char *buf, unsigned int len); +int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_input_free(struct snd_usb_caiaqdev *cdev); + +#endif diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c new file mode 100644 index 000000000..f8e5b1b57 --- /dev/null +++ b/sound/usb/caiaq/midi.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2006,2007 Daniel Mack + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "midi.h" + +static int snd_usb_caiaq_midi_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int snd_usb_caiaq_midi_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void snd_usb_caiaq_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + + if (!cdev) + return; + + cdev->midi_receive_substream = up ? substream : NULL; +} + + +static int snd_usb_caiaq_midi_output_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + if (cdev->midi_out_active) { + usb_kill_urb(&cdev->midi_out_urb); + cdev->midi_out_active = 0; + } + return 0; +} + +static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev, + struct snd_rawmidi_substream *substream) +{ + int len, ret; + struct device *dev = caiaqdev_to_dev(cdev); + + cdev->midi_out_buf[0] = EP1_CMD_MIDI_WRITE; + cdev->midi_out_buf[1] = 0; /* port */ + len = snd_rawmidi_transmit(substream, cdev->midi_out_buf + 3, + EP1_BUFSIZE - 3); + + if (len <= 0) + return; + + cdev->midi_out_buf[2] = len; + cdev->midi_out_urb.transfer_buffer_length = len+3; + + ret = usb_submit_urb(&cdev->midi_out_urb, GFP_ATOMIC); + if (ret < 0) + dev_err(dev, + "snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed," + "ret=%d, len=%d\n", substream, ret, len); + else + cdev->midi_out_active = 1; +} + +static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + + if (up) { + cdev->midi_out_substream = substream; + if (!cdev->midi_out_active) + snd_usb_caiaq_midi_send(cdev, substream); + } else { + cdev->midi_out_substream = NULL; + } +} + + +static const struct snd_rawmidi_ops snd_usb_caiaq_midi_output = +{ + .open = snd_usb_caiaq_midi_output_open, + .close = snd_usb_caiaq_midi_output_close, + .trigger = snd_usb_caiaq_midi_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_usb_caiaq_midi_input = +{ + .open = snd_usb_caiaq_midi_input_open, + .close = snd_usb_caiaq_midi_input_close, + .trigger = snd_usb_caiaq_midi_input_trigger, +}; + +void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev, + int port, const char *buf, int len) +{ + if (!cdev->midi_receive_substream) + return; + + snd_rawmidi_receive(cdev->midi_receive_substream, buf, len); +} + +int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *device) +{ + int ret; + struct snd_rawmidi *rmidi; + + ret = snd_rawmidi_new(device->chip.card, device->product_name, 0, + device->spec.num_midi_out, + device->spec.num_midi_in, + &rmidi); + + if (ret < 0) + return ret; + + strlcpy(rmidi->name, device->product_name, sizeof(rmidi->name)); + + rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = device; + + if (device->spec.num_midi_out > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_usb_caiaq_midi_output); + } + + if (device->spec.num_midi_in > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_usb_caiaq_midi_input); + } + + device->rmidi = rmidi; + + return 0; +} + +void snd_usb_caiaq_midi_output_done(struct urb* urb) +{ + struct snd_usb_caiaqdev *cdev = urb->context; + + cdev->midi_out_active = 0; + if (urb->status != 0) + return; + + if (!cdev->midi_out_substream) + return; + + snd_usb_caiaq_midi_send(cdev, cdev->midi_out_substream); +} diff --git a/sound/usb/caiaq/midi.h b/sound/usb/caiaq/midi.h new file mode 100644 index 000000000..a6ae0c224 --- /dev/null +++ b/sound/usb/caiaq/midi.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_MIDI_H +#define CAIAQ_MIDI_H + +int snd_usb_caiaq_midi_init(struct snd_usb_caiaqdev *cdev); +void snd_usb_caiaq_midi_handle_input(struct snd_usb_caiaqdev *cdev, + int port, const char *buf, int len); +void snd_usb_caiaq_midi_output_done(struct urb *urb); + +#endif /* CAIAQ_MIDI_H */ diff --git a/sound/usb/card.c b/sound/usb/card.c new file mode 100644 index 000000000..ce8925e84 --- /dev/null +++ b/sound/usb/card.c @@ -0,0 +1,920 @@ +/* + * (Tentative) USB Audio Driver for ALSA + * + * Copyright (c) 2002 by Takashi Iwai + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * Audio Class 3.0 support by Ruslan Bilovol + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * NOTES: + * + * - the linked URBs would be preferred but not used so far because of + * the instability of unlinking. + * - type II is not supported properly. there is no device which supports + * this type *correctly*. SB extigy looks as if it supports, but it's + * indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream). + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "midi.h" +#include "mixer.h" +#include "proc.h" +#include "quirks.h" +#include "endpoint.h" +#include "helper.h" +#include "debug.h" +#include "pcm.h" +#include "format.h" +#include "power.h" +#include "stream.h" + +MODULE_AUTHOR("Takashi Iwai "); +MODULE_DESCRIPTION("USB Audio"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}"); + + +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 */ +/* Vendor/product IDs for this card */ +static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; +static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; +static int device_setup[SNDRV_CARDS]; /* device parameter for this card */ +static bool ignore_ctl_error; +static bool autoclock = true; +static char *quirk_alias[SNDRV_CARDS]; + +bool snd_usb_use_vmalloc = true; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the USB audio adapter."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the USB audio adapter."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable USB audio adapter."); +module_param_array(vid, int, NULL, 0444); +MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device."); +module_param_array(pid, int, NULL, 0444); +MODULE_PARM_DESC(pid, "Product ID for the USB audio device."); +module_param_array(device_setup, int, NULL, 0444); +MODULE_PARM_DESC(device_setup, "Specific device setup (if needed)."); +module_param(ignore_ctl_error, bool, 0444); +MODULE_PARM_DESC(ignore_ctl_error, + "Ignore errors from USB controller for mixer interfaces."); +module_param(autoclock, bool, 0444); +MODULE_PARM_DESC(autoclock, "Enable auto-clock selection for UAC2 devices (default: yes)."); +module_param_array(quirk_alias, charp, NULL, 0444); +MODULE_PARM_DESC(quirk_alias, "Quirk aliases, e.g. 0123abcd:5678beef."); +module_param_named(use_vmalloc, snd_usb_use_vmalloc, bool, 0444); +MODULE_PARM_DESC(use_vmalloc, "Use vmalloc for PCM intermediate buffers (default: yes)."); + +/* + * we keep the snd_usb_audio_t instances by ourselves for merging + * the all interfaces on the same card as one sound device. + */ + +static DEFINE_MUTEX(register_mutex); +static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; +static struct usb_driver usb_audio_driver; + +/* + * disconnect streams + * called from usb_audio_disconnect() + */ +static void snd_usb_stream_disconnect(struct snd_usb_stream *as) +{ + int idx; + struct snd_usb_substream *subs; + + for (idx = 0; idx < 2; idx++) { + subs = &as->substream[idx]; + if (!subs->num_formats) + continue; + subs->interface = -1; + subs->data_endpoint = NULL; + subs->sync_endpoint = NULL; + } +} + +static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface) +{ + struct usb_device *dev = chip->dev; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct usb_interface *iface = usb_ifnum_to_if(dev, interface); + + if (!iface) { + dev_err(&dev->dev, "%u:%d : does not exist\n", + ctrlif, interface); + return -EINVAL; + } + + alts = &iface->altsetting[0]; + altsd = get_iface_desc(alts); + + /* + * Android with both accessory and audio interfaces enabled gets the + * interface numbers wrong. + */ + if ((chip->usb_id == USB_ID(0x18d1, 0x2d04) || + chip->usb_id == USB_ID(0x18d1, 0x2d05)) && + interface == 0 && + altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC && + altsd->bInterfaceSubClass == USB_SUBCLASS_VENDOR_SPEC) { + interface = 2; + iface = usb_ifnum_to_if(dev, interface); + if (!iface) + return -EINVAL; + alts = &iface->altsetting[0]; + altsd = get_iface_desc(alts); + } + + if (usb_interface_claimed(iface)) { + dev_dbg(&dev->dev, "%d:%d: skipping, already claimed\n", + ctrlif, interface); + return -EINVAL; + } + + if ((altsd->bInterfaceClass == USB_CLASS_AUDIO || + altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && + altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { + int err = __snd_usbmidi_create(chip->card, iface, + &chip->midi_list, NULL, + chip->usb_id); + if (err < 0) { + dev_err(&dev->dev, + "%u:%d: cannot create sequencer device\n", + ctrlif, interface); + return -EINVAL; + } + return usb_driver_claim_interface(&usb_audio_driver, iface, + USB_AUDIO_IFACE_UNUSED); + } + + if ((altsd->bInterfaceClass != USB_CLASS_AUDIO && + altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || + altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) { + dev_dbg(&dev->dev, + "%u:%d: skipping non-supported interface %d\n", + ctrlif, interface, altsd->bInterfaceClass); + /* skip non-supported classes */ + return -EINVAL; + } + + if (snd_usb_get_speed(dev) == USB_SPEED_LOW) { + dev_err(&dev->dev, "low speed audio streaming not supported\n"); + return -EINVAL; + } + + if (! snd_usb_parse_audio_interface(chip, interface)) { + usb_set_interface(dev, interface, 0); /* reset the current interface */ + return usb_driver_claim_interface(&usb_audio_driver, iface, + USB_AUDIO_IFACE_UNUSED); + } + + return 0; +} + +/* + * parse audio control descriptor and create pcm/midi streams + */ +static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) +{ + struct usb_device *dev = chip->dev; + struct usb_host_interface *host_iface; + struct usb_interface_descriptor *altsd; + int i, protocol; + + /* find audiocontrol interface */ + host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0]; + altsd = get_iface_desc(host_iface); + protocol = altsd->bInterfaceProtocol; + + switch (protocol) { + default: + dev_warn(&dev->dev, + "unknown interface protocol %#02x, assuming v1\n", + protocol); + /* fall through */ + + case UAC_VERSION_1: { + struct uac1_ac_header_descriptor *h1; + int rest_bytes; + + h1 = snd_usb_find_csint_desc(host_iface->extra, + host_iface->extralen, + NULL, UAC_HEADER); + if (!h1 || h1->bLength < sizeof(*h1)) { + dev_err(&dev->dev, "cannot find UAC_HEADER\n"); + return -EINVAL; + } + + rest_bytes = (void *)(host_iface->extra + + host_iface->extralen) - (void *)h1; + + /* just to be sure -- this shouldn't hit at all */ + if (rest_bytes <= 0) { + dev_err(&dev->dev, "invalid control header\n"); + return -EINVAL; + } + + if (rest_bytes < sizeof(*h1)) { + dev_err(&dev->dev, "too short v1 buffer descriptor\n"); + return -EINVAL; + } + + if (!h1->bInCollection) { + dev_info(&dev->dev, "skipping empty audio interface (v1)\n"); + return -EINVAL; + } + + if (rest_bytes < h1->bLength) { + dev_err(&dev->dev, "invalid buffer length (v1)\n"); + return -EINVAL; + } + + if (h1->bLength < sizeof(*h1) + h1->bInCollection) { + dev_err(&dev->dev, "invalid UAC_HEADER (v1)\n"); + return -EINVAL; + } + + for (i = 0; i < h1->bInCollection; i++) + snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]); + + break; + } + + case UAC_VERSION_2: + case UAC_VERSION_3: { + struct usb_interface_assoc_descriptor *assoc = + usb_ifnum_to_if(dev, ctrlif)->intf_assoc; + + if (!assoc) { + /* + * Firmware writers cannot count to three. So to find + * the IAD on the NuForce UDH-100, also check the next + * interface. + */ + struct usb_interface *iface = + usb_ifnum_to_if(dev, ctrlif + 1); + if (iface && + iface->intf_assoc && + iface->intf_assoc->bFunctionClass == USB_CLASS_AUDIO && + iface->intf_assoc->bFunctionProtocol == UAC_VERSION_2) + assoc = iface->intf_assoc; + } + + if (!assoc) { + dev_err(&dev->dev, "Audio class v2/v3 interfaces need an interface association\n"); + return -EINVAL; + } + + if (protocol == UAC_VERSION_3) { + int badd = assoc->bFunctionSubClass; + + if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 && + (badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO || + badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) { + dev_err(&dev->dev, + "Unsupported UAC3 BADD profile\n"); + return -EINVAL; + } + + chip->badd_profile = badd; + } + + for (i = 0; i < assoc->bInterfaceCount; i++) { + int intf = assoc->bFirstInterface + i; + + if (intf != ctrlif) + snd_usb_create_stream(chip, ctrlif, intf); + } + + break; + } + } + + return 0; +} + +/* + * free the chip instance + * + * here we have to do not much, since pcm and controls are already freed + * + */ + +static void snd_usb_audio_free(struct snd_card *card) +{ + struct snd_usb_audio *chip = card->private_data; + struct snd_usb_endpoint *ep, *n; + + list_for_each_entry_safe(ep, n, &chip->ep_list, list) + snd_usb_endpoint_free(ep); + + mutex_destroy(&chip->mutex); + if (!atomic_read(&chip->shutdown)) + dev_set_drvdata(&chip->dev->dev, NULL); +} + +static void usb_audio_make_shortname(struct usb_device *dev, + struct snd_usb_audio *chip, + const struct snd_usb_audio_quirk *quirk) +{ + struct snd_card *card = chip->card; + + if (quirk && quirk->product_name && *quirk->product_name) { + strlcpy(card->shortname, quirk->product_name, + sizeof(card->shortname)); + return; + } + + /* retrieve the device string as shortname */ + if (!dev->descriptor.iProduct || + usb_string(dev, dev->descriptor.iProduct, + card->shortname, sizeof(card->shortname)) <= 0) { + /* no name available from anywhere, so use ID */ + sprintf(card->shortname, "USB Device %#04x:%#04x", + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); + } + + strim(card->shortname); +} + +static void usb_audio_make_longname(struct usb_device *dev, + struct snd_usb_audio *chip, + const struct snd_usb_audio_quirk *quirk) +{ + struct snd_card *card = chip->card; + int len; + + /* shortcut - if any pre-defined string is given, use it */ + if (quirk && quirk->profile_name && *quirk->profile_name) { + strlcpy(card->longname, quirk->profile_name, + sizeof(card->longname)); + return; + } + + if (quirk && quirk->vendor_name && *quirk->vendor_name) { + len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); + } else { + /* retrieve the vendor and device strings as longname */ + if (dev->descriptor.iManufacturer) + len = usb_string(dev, dev->descriptor.iManufacturer, + card->longname, sizeof(card->longname)); + else + len = 0; + /* we don't really care if there isn't any vendor string */ + } + if (len > 0) { + strim(card->longname); + if (*card->longname) + strlcat(card->longname, " ", sizeof(card->longname)); + } + + strlcat(card->longname, card->shortname, sizeof(card->longname)); + + len = strlcat(card->longname, " at ", sizeof(card->longname)); + + if (len < sizeof(card->longname)) + usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); + + switch (snd_usb_get_speed(dev)) { + case USB_SPEED_LOW: + strlcat(card->longname, ", low speed", sizeof(card->longname)); + break; + case USB_SPEED_FULL: + strlcat(card->longname, ", full speed", sizeof(card->longname)); + break; + case USB_SPEED_HIGH: + strlcat(card->longname, ", high speed", sizeof(card->longname)); + break; + case USB_SPEED_SUPER: + strlcat(card->longname, ", super speed", sizeof(card->longname)); + break; + case USB_SPEED_SUPER_PLUS: + strlcat(card->longname, ", super speed plus", sizeof(card->longname)); + break; + default: + break; + } +} + +/* + * create a chip instance and set its names. + */ +static int snd_usb_audio_create(struct usb_interface *intf, + struct usb_device *dev, int idx, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id, + struct snd_usb_audio **rchip) +{ + struct snd_card *card; + struct snd_usb_audio *chip; + int err; + char component[14]; + + *rchip = NULL; + + switch (snd_usb_get_speed(dev)) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_WIRELESS: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + break; + default: + dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev)); + return -ENXIO; + } + + err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) { + dev_err(&dev->dev, "cannot create card instance %d\n", idx); + return err; + } + + chip = card->private_data; + mutex_init(&chip->mutex); + init_waitqueue_head(&chip->shutdown_wait); + chip->index = idx; + chip->dev = dev; + chip->card = card; + chip->setup = device_setup[idx]; + chip->autoclock = autoclock; + atomic_set(&chip->active, 1); /* avoid autopm during probing */ + atomic_set(&chip->usage_count, 0); + atomic_set(&chip->shutdown, 0); + + chip->usb_id = usb_id; + INIT_LIST_HEAD(&chip->pcm_list); + INIT_LIST_HEAD(&chip->ep_list); + INIT_LIST_HEAD(&chip->midi_list); + INIT_LIST_HEAD(&chip->mixer_list); + + card->private_free = snd_usb_audio_free; + + strcpy(card->driver, "USB-Audio"); + sprintf(component, "USB%04x:%04x", + USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); + snd_component_add(card, component); + + usb_audio_make_shortname(dev, chip, quirk); + usb_audio_make_longname(dev, chip, quirk); + + snd_usb_audio_create_proc(chip); + + *rchip = chip; + return 0; +} + +/* look for a matching quirk alias id */ +static bool get_alias_id(struct usb_device *dev, unsigned int *id) +{ + int i; + unsigned int src, dst; + + for (i = 0; i < ARRAY_SIZE(quirk_alias); i++) { + if (!quirk_alias[i] || + sscanf(quirk_alias[i], "%x:%x", &src, &dst) != 2 || + src != *id) + continue; + dev_info(&dev->dev, + "device (%04x:%04x): applying quirk alias %04x:%04x\n", + USB_ID_VENDOR(*id), USB_ID_PRODUCT(*id), + USB_ID_VENDOR(dst), USB_ID_PRODUCT(dst)); + *id = dst; + return true; + } + + return false; +} + +static const struct usb_device_id usb_audio_ids[]; /* defined below */ + +/* look for the corresponding quirk */ +static const struct snd_usb_audio_quirk * +get_alias_quirk(struct usb_device *dev, unsigned int id) +{ + const struct usb_device_id *p; + + for (p = usb_audio_ids; p->match_flags; p++) { + /* FIXME: this checks only vendor:product pair in the list */ + if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) == + USB_DEVICE_ID_MATCH_DEVICE && + p->idVendor == USB_ID_VENDOR(id) && + p->idProduct == USB_ID_PRODUCT(id)) + return (const struct snd_usb_audio_quirk *)p->driver_info; + } + + return NULL; +} + +/* + * probe the active usb device + * + * note that this can be called multiple times per a device, when it + * includes multiple audio control interfaces. + * + * thus we check the usb device pointer and creates the card instance + * only at the first time. the successive calls of this function will + * append the pcm interface to the corresponding card. + */ +static int usb_audio_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + const struct snd_usb_audio_quirk *quirk = + (const struct snd_usb_audio_quirk *)usb_id->driver_info; + struct snd_usb_audio *chip; + int i, err; + struct usb_host_interface *alts; + int ifnum; + u32 id; + + alts = &intf->altsetting[0]; + ifnum = get_iface_desc(alts)->bInterfaceNumber; + id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + if (get_alias_id(dev, &id)) + quirk = get_alias_quirk(dev, id); + if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum) + return -ENXIO; + + err = snd_usb_apply_boot_quirk(dev, intf, quirk, id); + if (err < 0) + return err; + + /* + * found a config. now register to ALSA + */ + + /* check whether it's already registered */ + chip = NULL; + mutex_lock(®ister_mutex); + for (i = 0; i < SNDRV_CARDS; i++) { + if (usb_chip[i] && usb_chip[i]->dev == dev) { + if (atomic_read(&usb_chip[i]->shutdown)) { + dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n"); + err = -EIO; + goto __error; + } + chip = usb_chip[i]; + atomic_inc(&chip->active); /* avoid autopm */ + break; + } + } + if (! chip) { + /* it's a fresh one. + * now look for an empty slot and create a new card instance + */ + for (i = 0; i < SNDRV_CARDS; i++) + if (!usb_chip[i] && + (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) && + (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) { + if (enable[i]) { + err = snd_usb_audio_create(intf, dev, i, quirk, + id, &chip); + if (err < 0) + goto __error; + chip->pm_intf = intf; + break; + } else if (vid[i] != -1 || pid[i] != -1) { + dev_info(&dev->dev, + "device (%04x:%04x) is disabled\n", + USB_ID_VENDOR(id), + USB_ID_PRODUCT(id)); + err = -ENOENT; + goto __error; + } + } + if (!chip) { + dev_err(&dev->dev, "no available usb audio device\n"); + err = -ENODEV; + goto __error; + } + } + dev_set_drvdata(&dev->dev, chip); + + /* + * For devices with more than one control interface, we assume the + * first contains the audio controls. We might need a more specific + * check here in the future. + */ + if (!chip->ctrl_intf) + chip->ctrl_intf = alts; + + chip->txfr_quirk = 0; + err = 1; /* continue */ + if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) { + /* need some special handlings */ + err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk); + if (err < 0) + goto __error; + } + + if (err > 0) { + /* create normal USB audio interfaces */ + err = snd_usb_create_streams(chip, ifnum); + if (err < 0) + goto __error; + err = snd_usb_create_mixer(chip, ifnum, ignore_ctl_error); + if (err < 0) + goto __error; + } + + /* we are allowed to call snd_card_register() many times, but first + * check to see if a device needs to skip it or do anything special + */ + if (!snd_usb_registration_quirk(chip, ifnum)) { + err = snd_card_register(chip->card); + if (err < 0) + goto __error; + } + + usb_chip[chip->index] = chip; + chip->num_interfaces++; + usb_set_intfdata(intf, chip); + atomic_dec(&chip->active); + mutex_unlock(®ister_mutex); + return 0; + + __error: + if (chip) { + /* chip->active is inside the chip->card object, + * decrement before memory is possibly returned. + */ + atomic_dec(&chip->active); + if (!chip->num_interfaces) + snd_card_free(chip->card); + } + mutex_unlock(®ister_mutex); + return err; +} + +/* + * we need to take care of counter, since disconnection can be called also + * many times as well as usb_audio_probe(). + */ +static void usb_audio_disconnect(struct usb_interface *intf) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + struct snd_card *card; + struct list_head *p; + + if (chip == USB_AUDIO_IFACE_UNUSED) + return; + + card = chip->card; + + mutex_lock(®ister_mutex); + if (atomic_inc_return(&chip->shutdown) == 1) { + struct snd_usb_stream *as; + struct snd_usb_endpoint *ep; + struct usb_mixer_interface *mixer; + + /* wait until all pending tasks done; + * they are protected by snd_usb_lock_shutdown() + */ + wait_event(chip->shutdown_wait, + !atomic_read(&chip->usage_count)); + snd_card_disconnect(card); + /* release the pcm resources */ + list_for_each_entry(as, &chip->pcm_list, list) { + snd_usb_stream_disconnect(as); + } + /* release the endpoint resources */ + list_for_each_entry(ep, &chip->ep_list, list) { + snd_usb_endpoint_release(ep); + } + /* release the midi resources */ + list_for_each(p, &chip->midi_list) { + snd_usbmidi_disconnect(p); + } + /* release mixer resources */ + list_for_each_entry(mixer, &chip->mixer_list, list) { + snd_usb_mixer_disconnect(mixer); + } + } + + chip->num_interfaces--; + if (chip->num_interfaces <= 0) { + usb_chip[chip->index] = NULL; + mutex_unlock(®ister_mutex); + snd_card_free_when_closed(card); + } else { + mutex_unlock(®ister_mutex); + } +} + +/* lock the shutdown (disconnect) task and autoresume */ +int snd_usb_lock_shutdown(struct snd_usb_audio *chip) +{ + int err; + + atomic_inc(&chip->usage_count); + if (atomic_read(&chip->shutdown)) { + err = -EIO; + goto error; + } + err = snd_usb_autoresume(chip); + if (err < 0) + goto error; + return 0; + + error: + if (atomic_dec_and_test(&chip->usage_count)) + wake_up(&chip->shutdown_wait); + return err; +} + +/* autosuspend and unlock the shutdown */ +void snd_usb_unlock_shutdown(struct snd_usb_audio *chip) +{ + snd_usb_autosuspend(chip); + if (atomic_dec_and_test(&chip->usage_count)) + wake_up(&chip->shutdown_wait); +} + +#ifdef CONFIG_PM + +int snd_usb_autoresume(struct snd_usb_audio *chip) +{ + if (atomic_read(&chip->shutdown)) + return -EIO; + if (atomic_inc_return(&chip->active) == 1) + return usb_autopm_get_interface(chip->pm_intf); + return 0; +} + +void snd_usb_autosuspend(struct snd_usb_audio *chip) +{ + if (atomic_read(&chip->shutdown)) + return; + if (atomic_dec_and_test(&chip->active)) + usb_autopm_put_interface(chip->pm_intf); +} + +static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + struct snd_usb_stream *as; + struct usb_mixer_interface *mixer; + struct list_head *p; + + if (chip == USB_AUDIO_IFACE_UNUSED) + return 0; + + if (!chip->num_suspended_intf++) { + list_for_each_entry(as, &chip->pcm_list, list) { + snd_pcm_suspend_all(as->pcm); + snd_usb_pcm_suspend(as); + as->substream[0].need_setup_ep = + as->substream[1].need_setup_ep = true; + } + list_for_each(p, &chip->midi_list) + snd_usbmidi_suspend(p); + list_for_each_entry(mixer, &chip->mixer_list, list) + snd_usb_mixer_suspend(mixer); + } + + if (!PMSG_IS_AUTO(message) && !chip->system_suspend) { + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + chip->system_suspend = chip->num_suspended_intf; + } + + return 0; +} + +static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume) +{ + struct snd_usb_audio *chip = usb_get_intfdata(intf); + struct snd_usb_stream *as; + struct usb_mixer_interface *mixer; + struct list_head *p; + int err = 0; + + if (chip == USB_AUDIO_IFACE_UNUSED) + return 0; + + atomic_inc(&chip->active); /* avoid autopm */ + if (chip->num_suspended_intf > 1) + goto out; + + list_for_each_entry(as, &chip->pcm_list, list) { + err = snd_usb_pcm_resume(as); + if (err < 0) + goto err_out; + } + + /* + * ALSA leaves material resumption to user space + * we just notify and restart the mixers + */ + list_for_each_entry(mixer, &chip->mixer_list, list) { + err = snd_usb_mixer_resume(mixer, reset_resume); + if (err < 0) + goto err_out; + } + + list_for_each(p, &chip->midi_list) { + snd_usbmidi_resume(p); + } + + out: + if (chip->num_suspended_intf == chip->system_suspend) { + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); + chip->system_suspend = 0; + } + chip->num_suspended_intf--; + +err_out: + atomic_dec(&chip->active); /* allow autopm after this point */ + return err; +} + +static int usb_audio_resume(struct usb_interface *intf) +{ + return __usb_audio_resume(intf, false); +} + +static int usb_audio_reset_resume(struct usb_interface *intf) +{ + return __usb_audio_resume(intf, true); +} +#else +#define usb_audio_suspend NULL +#define usb_audio_resume NULL +#define usb_audio_reset_resume NULL +#endif /* CONFIG_PM */ + +static const struct usb_device_id usb_audio_ids [] = { +#include "quirks-table.h" + { .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS), + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usb_audio_ids); + +/* + * entry point for linux usb interface + */ + +static struct usb_driver usb_audio_driver = { + .name = "snd-usb-audio", + .probe = usb_audio_probe, + .disconnect = usb_audio_disconnect, + .suspend = usb_audio_suspend, + .resume = usb_audio_resume, + .reset_resume = usb_audio_reset_resume, + .id_table = usb_audio_ids, + .supports_autosuspend = 1, +}; + +module_usb_driver(usb_audio_driver); diff --git a/sound/usb/card.h b/sound/usb/card.h new file mode 100644 index 000000000..f30fec68e --- /dev/null +++ b/sound/usb/card.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_CARD_H +#define __USBAUDIO_CARD_H + +#define MAX_NR_RATES 1024 +#define MAX_PACKS 6 /* per URB */ +#define MAX_PACKS_HS (MAX_PACKS * 8) /* in high speed mode */ +#define MAX_URBS 12 +#define SYNC_URBS 4 /* always four urbs for sync */ +#define MAX_QUEUE 18 /* try not to exceed this queue length, in ms */ + +struct audioformat { + struct list_head list; + u64 formats; /* ALSA format bits */ + unsigned int channels; /* # channels */ + unsigned int fmt_type; /* USB audio format type (1-3) */ + unsigned int frame_size; /* samples per frame for non-audio */ + int iface; /* interface number */ + unsigned char altsetting; /* corresponding alternate setting */ + unsigned char altset_idx; /* array index of altenate setting */ + unsigned char attributes; /* corresponding attributes of cs endpoint */ + unsigned char endpoint; /* endpoint */ + unsigned char ep_attr; /* endpoint attributes */ + unsigned char datainterval; /* log_2 of data packet interval */ + unsigned char protocol; /* UAC_VERSION_1/2/3 */ + unsigned int maxpacksize; /* max. packet size */ + unsigned int rates; /* rate bitmasks */ + unsigned int rate_min, rate_max; /* min/max rates */ + unsigned int nr_rates; /* number of rate table entries */ + unsigned int *rate_table; /* rate table */ + unsigned char clock; /* associated clock */ + struct snd_pcm_chmap_elem *chmap; /* (optional) channel map */ + bool dsd_dop; /* add DOP headers in case of DSD samples */ + bool dsd_bitrev; /* reverse the bits of each DSD sample */ + bool dsd_raw; /* altsetting is raw DSD */ +}; + +struct snd_usb_substream; +struct snd_usb_endpoint; +struct snd_usb_power_domain; + +struct snd_urb_ctx { + struct urb *urb; + unsigned int buffer_size; /* size of data buffer, if data URB */ + struct snd_usb_substream *subs; + struct snd_usb_endpoint *ep; + int index; /* index for urb array */ + int packets; /* number of packets per urb */ + int packet_size[MAX_PACKS_HS]; /* size of packets for next submission */ + struct list_head ready_list; +}; + +struct snd_usb_endpoint { + struct snd_usb_audio *chip; + + int use_count; + int ep_num; /* the referenced endpoint number */ + int type; /* SND_USB_ENDPOINT_TYPE_* */ + unsigned long flags; + + void (*prepare_data_urb) (struct snd_usb_substream *subs, + struct urb *urb); + void (*retire_data_urb) (struct snd_usb_substream *subs, + struct urb *urb); + + struct snd_usb_substream *data_subs; + struct snd_usb_endpoint *sync_master; + struct snd_usb_endpoint *sync_slave; + + struct snd_urb_ctx urb[MAX_URBS]; + + struct snd_usb_packet_info { + uint32_t packet_size[MAX_PACKS_HS]; + int packets; + } next_packet[MAX_URBS]; + int next_packet_read_pos, next_packet_write_pos; + struct list_head ready_playback_urbs; + + unsigned int nurbs; /* # urbs */ + unsigned long active_mask; /* bitmask of active urbs */ + unsigned long unlink_mask; /* bitmask of unlinked urbs */ + char *syncbuf; /* sync buffer for all sync URBs */ + dma_addr_t sync_dma; /* DMA address of syncbuf */ + + unsigned int pipe; /* the data i/o pipe */ + unsigned int freqn; /* nominal sampling rate in fs/fps in Q16.16 format */ + unsigned int freqm; /* momentary sampling rate in fs/fps in Q16.16 format */ + int freqshift; /* how much to shift the feedback value to get Q16.16 */ + unsigned int freqmax; /* maximum sampling rate, used for buffer management */ + unsigned int phase; /* phase accumulator */ + unsigned int maxpacksize; /* max packet size in bytes */ + unsigned int maxframesize; /* max packet size in frames */ + unsigned int max_urb_frames; /* max URB size in frames */ + unsigned int curpacksize; /* current packet size in bytes (for capture) */ + unsigned int curframesize; /* current packet size in frames (for capture) */ + unsigned int syncmaxsize; /* sync endpoint packet size */ + unsigned int fill_max:1; /* fill max packet size always */ + unsigned int tenor_fb_quirk:1; /* corrupted feedback data */ + unsigned int datainterval; /* log_2 of data packet interval */ + unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ + unsigned char silence_value; + unsigned int stride; + int iface, altsetting; + int skip_packets; /* quirks for devices to ignore the first n packets + in a stream */ + + spinlock_t lock; + struct list_head list; +}; + +struct snd_usb_substream { + struct snd_usb_stream *stream; + struct usb_device *dev; + struct snd_pcm_substream *pcm_substream; + int direction; /* playback or capture */ + int interface; /* current interface */ + int endpoint; /* assigned endpoint */ + struct audioformat *cur_audiofmt; /* current audioformat pointer (for hw_params callback) */ + struct snd_usb_power_domain *str_pd; /* UAC3 Power Domain for streaming path */ + snd_pcm_format_t pcm_format; /* current audio format (for hw_params callback) */ + unsigned int channels; /* current number of channels (for hw_params callback) */ + unsigned int channels_max; /* max channels in the all audiofmts */ + unsigned int cur_rate; /* current rate (for hw_params callback) */ + unsigned int period_bytes; /* current period bytes (for hw_params callback) */ + unsigned int period_frames; /* current frames per period */ + unsigned int buffer_periods; /* current periods per buffer */ + unsigned int altset_idx; /* USB data format: index of alternate setting */ + unsigned int txfr_quirk:1; /* allow sub-frame alignment */ + unsigned int tx_length_quirk:1; /* add length specifier to transfers */ + unsigned int fmt_type; /* USB audio format type (1-3) */ + unsigned int pkt_offset_adj; /* Bytes to drop from beginning of packets (for non-compliant devices) */ + unsigned int stream_offset_adj; /* Bytes to drop from beginning of stream (for non-compliant devices) */ + + unsigned int running: 1; /* running status */ + + unsigned int hwptr_done; /* processed byte position in the buffer */ + unsigned int transfer_done; /* processed frames since last period update */ + unsigned int frame_limit; /* limits number of packets in URB */ + + /* data and sync endpoints for this stream */ + unsigned int ep_num; /* the endpoint number */ + struct snd_usb_endpoint *data_endpoint; + struct snd_usb_endpoint *sync_endpoint; + unsigned long flags; + bool need_setup_ep; /* (re)configure EP at prepare? */ + bool need_setup_fmt; /* (re)configure fmt after resume? */ + unsigned int speed; /* USB_SPEED_XXX */ + + u64 formats; /* format bitmasks (all or'ed) */ + unsigned int num_formats; /* number of supported audio formats (list) */ + struct list_head fmt_list; /* format list */ + struct snd_pcm_hw_constraint_list rate_list; /* limited rates */ + spinlock_t lock; + + int last_frame_number; /* stored frame number */ + int last_delay; /* stored delay */ + + struct { + int marker; + int channel; + int byte_idx; + } dsd_dop; + + bool trigger_tstamp_pending_update; /* trigger timestamp being updated from initial estimate */ +}; + +struct snd_usb_stream { + struct snd_usb_audio *chip; + struct snd_pcm *pcm; + int pcm_index; + unsigned int fmt_type; /* USB audio format type (1-3) */ + struct snd_usb_substream substream[2]; + struct list_head list; +}; + +#endif /* __USBAUDIO_CARD_H */ diff --git a/sound/usb/clock.c b/sound/usb/clock.c new file mode 100644 index 000000000..d1455fb2c --- /dev/null +++ b/sound/usb/clock.c @@ -0,0 +1,677 @@ +/* + * Clock domain and sample rate management functions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "helper.h" +#include "clock.h" +#include "quirks.h" + +static void *find_uac_clock_desc(struct usb_host_interface *iface, int id, + bool (*validator)(void *, int), u8 type) +{ + void *cs = NULL; + + while ((cs = snd_usb_find_csint_desc(iface->extra, iface->extralen, + cs, type))) { + if (validator(cs, id)) + return cs; + } + + return NULL; +} + +static bool validate_clock_source_v2(void *p, int id) +{ + struct uac_clock_source_descriptor *cs = p; + return cs->bClockID == id; +} + +static bool validate_clock_source_v3(void *p, int id) +{ + struct uac3_clock_source_descriptor *cs = p; + return cs->bClockID == id; +} + +static bool validate_clock_selector_v2(void *p, int id) +{ + struct uac_clock_selector_descriptor *cs = p; + return cs->bClockID == id; +} + +static bool validate_clock_selector_v3(void *p, int id) +{ + struct uac3_clock_selector_descriptor *cs = p; + return cs->bClockID == id; +} + +static bool validate_clock_multiplier_v2(void *p, int id) +{ + struct uac_clock_multiplier_descriptor *cs = p; + return cs->bClockID == id; +} + +static bool validate_clock_multiplier_v3(void *p, int id) +{ + struct uac3_clock_multiplier_descriptor *cs = p; + return cs->bClockID == id; +} + +#define DEFINE_FIND_HELPER(name, obj, validator, type) \ +static obj *name(struct usb_host_interface *iface, int id) \ +{ \ + return find_uac_clock_desc(iface, id, validator, type); \ +} + +DEFINE_FIND_HELPER(snd_usb_find_clock_source, + struct uac_clock_source_descriptor, + validate_clock_source_v2, UAC2_CLOCK_SOURCE); +DEFINE_FIND_HELPER(snd_usb_find_clock_source_v3, + struct uac3_clock_source_descriptor, + validate_clock_source_v3, UAC3_CLOCK_SOURCE); + +DEFINE_FIND_HELPER(snd_usb_find_clock_selector, + struct uac_clock_selector_descriptor, + validate_clock_selector_v2, UAC2_CLOCK_SELECTOR); +DEFINE_FIND_HELPER(snd_usb_find_clock_selector_v3, + struct uac3_clock_selector_descriptor, + validate_clock_selector_v3, UAC3_CLOCK_SELECTOR); + +DEFINE_FIND_HELPER(snd_usb_find_clock_multiplier, + struct uac_clock_multiplier_descriptor, + validate_clock_multiplier_v2, UAC2_CLOCK_MULTIPLIER); +DEFINE_FIND_HELPER(snd_usb_find_clock_multiplier_v3, + struct uac3_clock_multiplier_descriptor, + validate_clock_multiplier_v3, UAC3_CLOCK_MULTIPLIER); + +static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_id) +{ + unsigned char buf; + int ret; + + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + UAC2_CX_CLOCK_SELECTOR << 8, + snd_usb_ctrl_intf(chip) | (selector_id << 8), + &buf, sizeof(buf)); + + if (ret < 0) + return ret; + + return buf; +} + +static int uac_clock_selector_set_val(struct snd_usb_audio *chip, int selector_id, + unsigned char pin) +{ + int ret; + + ret = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + UAC2_CX_CLOCK_SELECTOR << 8, + snd_usb_ctrl_intf(chip) | (selector_id << 8), + &pin, sizeof(pin)); + if (ret < 0) + return ret; + + if (ret != sizeof(pin)) { + usb_audio_err(chip, + "setting selector (id %d) unexpected length %d\n", + selector_id, ret); + return -EINVAL; + } + + ret = uac_clock_selector_get_val(chip, selector_id); + if (ret < 0) + return ret; + + if (ret != pin) { + usb_audio_err(chip, + "setting selector (id %d) to %x failed (current: %d)\n", + selector_id, pin, ret); + return -EINVAL; + } + + return ret; +} + +/* + * Assume the clock is valid if clock source supports only one single sample + * rate, the terminal is connected directly to it (there is no clock selector) + * and clock type is internal. This is to deal with some Denon DJ controllers + * that always reports that clock is invalid. + */ +static bool uac_clock_source_is_valid_quirk(struct snd_usb_audio *chip, + struct audioformat *fmt, + int source_id) +{ + if (fmt->protocol == UAC_VERSION_2) { + struct uac_clock_source_descriptor *cs_desc = + snd_usb_find_clock_source(chip->ctrl_intf, source_id); + + if (!cs_desc) + return false; + + return (fmt->nr_rates == 1 && + (fmt->clock & 0xff) == cs_desc->bClockID && + (cs_desc->bmAttributes & 0x3) != + UAC_CLOCK_SOURCE_TYPE_EXT); + } + + return false; +} + +static bool uac_clock_source_is_valid(struct snd_usb_audio *chip, + struct audioformat *fmt, + int source_id) +{ + int err; + unsigned char data; + struct usb_device *dev = chip->dev; + u32 bmControls; + + if (fmt->protocol == UAC_VERSION_3) { + struct uac3_clock_source_descriptor *cs_desc = + snd_usb_find_clock_source_v3(chip->ctrl_intf, source_id); + + if (!cs_desc) + return false; + bmControls = le32_to_cpu(cs_desc->bmControls); + } else { /* UAC_VERSION_1/2 */ + struct uac_clock_source_descriptor *cs_desc = + snd_usb_find_clock_source(chip->ctrl_intf, source_id); + + if (!cs_desc) + return false; + bmControls = cs_desc->bmControls; + } + + /* If a clock source can't tell us whether it's valid, we assume it is */ + if (!uac_v2v3_control_is_readable(bmControls, + UAC2_CS_CONTROL_CLOCK_VALID)) + return true; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_CLOCK_VALID << 8, + snd_usb_ctrl_intf(chip) | (source_id << 8), + &data, sizeof(data)); + + if (err < 0) { + dev_warn(&dev->dev, + "%s(): cannot get clock validity for id %d\n", + __func__, source_id); + return false; + } + + if (data) + return true; + else + return uac_clock_source_is_valid_quirk(chip, fmt, source_id); +} + +static int __uac_clock_find_source(struct snd_usb_audio *chip, + struct audioformat *fmt, int entity_id, + unsigned long *visited, bool validate) +{ + struct uac_clock_source_descriptor *source; + struct uac_clock_selector_descriptor *selector; + struct uac_clock_multiplier_descriptor *multiplier; + + entity_id &= 0xff; + + if (test_and_set_bit(entity_id, visited)) { + usb_audio_warn(chip, + "%s(): recursive clock topology detected, id %d.\n", + __func__, entity_id); + return -EINVAL; + } + + /* first, see if the ID we're looking for is a clock source already */ + source = snd_usb_find_clock_source(chip->ctrl_intf, entity_id); + if (source) { + entity_id = source->bClockID; + if (validate && !uac_clock_source_is_valid(chip, fmt, + entity_id)) { + usb_audio_err(chip, + "clock source %d is not valid, cannot use\n", + entity_id); + return -ENXIO; + } + return entity_id; + } + + selector = snd_usb_find_clock_selector(chip->ctrl_intf, entity_id); + if (selector) { + int ret, i, cur, err; + + /* the entity ID we are looking for is a selector. + * find out what it currently selects */ + ret = uac_clock_selector_get_val(chip, selector->bClockID); + if (ret < 0) + return ret; + + /* Selector values are one-based */ + + if (ret > selector->bNrInPins || ret < 1) { + usb_audio_err(chip, + "%s(): selector reported illegal value, id %d, ret %d\n", + __func__, selector->bClockID, ret); + + return -EINVAL; + } + + cur = ret; + ret = __uac_clock_find_source(chip, fmt, + selector->baCSourceID[ret - 1], + visited, validate); + if (ret > 0) { + /* + * For Samsung USBC Headset (AKG), setting clock selector again + * will result in incorrect default clock setting problems + */ + if (chip->usb_id == USB_ID(0x04e8, 0xa051)) + return ret; + err = uac_clock_selector_set_val(chip, entity_id, cur); + if (err < 0) + return err; + } + + if (!validate || ret > 0 || !chip->autoclock) + return ret; + + /* The current clock source is invalid, try others. */ + for (i = 1; i <= selector->bNrInPins; i++) { + if (i == cur) + continue; + + ret = __uac_clock_find_source(chip, fmt, + selector->baCSourceID[i - 1], + visited, true); + if (ret < 0) + continue; + + err = uac_clock_selector_set_val(chip, entity_id, i); + if (err < 0) + continue; + + usb_audio_info(chip, + "found and selected valid clock source %d\n", + ret); + return ret; + } + + return -ENXIO; + } + + /* FIXME: multipliers only act as pass-thru element for now */ + multiplier = snd_usb_find_clock_multiplier(chip->ctrl_intf, entity_id); + if (multiplier) + return __uac_clock_find_source(chip, fmt, + multiplier->bCSourceID, + visited, validate); + + return -EINVAL; +} + +static int __uac3_clock_find_source(struct snd_usb_audio *chip, + struct audioformat *fmt, int entity_id, + unsigned long *visited, bool validate) +{ + struct uac3_clock_source_descriptor *source; + struct uac3_clock_selector_descriptor *selector; + struct uac3_clock_multiplier_descriptor *multiplier; + + entity_id &= 0xff; + + if (test_and_set_bit(entity_id, visited)) { + usb_audio_warn(chip, + "%s(): recursive clock topology detected, id %d.\n", + __func__, entity_id); + return -EINVAL; + } + + /* first, see if the ID we're looking for is a clock source already */ + source = snd_usb_find_clock_source_v3(chip->ctrl_intf, entity_id); + if (source) { + entity_id = source->bClockID; + if (validate && !uac_clock_source_is_valid(chip, fmt, + entity_id)) { + usb_audio_err(chip, + "clock source %d is not valid, cannot use\n", + entity_id); + return -ENXIO; + } + return entity_id; + } + + selector = snd_usb_find_clock_selector_v3(chip->ctrl_intf, entity_id); + if (selector) { + int ret, i, cur, err; + + /* the entity ID we are looking for is a selector. + * find out what it currently selects */ + ret = uac_clock_selector_get_val(chip, selector->bClockID); + if (ret < 0) + return ret; + + /* Selector values are one-based */ + + if (ret > selector->bNrInPins || ret < 1) { + usb_audio_err(chip, + "%s(): selector reported illegal value, id %d, ret %d\n", + __func__, selector->bClockID, ret); + + return -EINVAL; + } + + cur = ret; + ret = __uac3_clock_find_source(chip, fmt, + selector->baCSourceID[ret - 1], + visited, validate); + if (ret > 0) { + err = uac_clock_selector_set_val(chip, entity_id, cur); + if (err < 0) + return err; + } + + if (!validate || ret > 0 || !chip->autoclock) + return ret; + + /* The current clock source is invalid, try others. */ + for (i = 1; i <= selector->bNrInPins; i++) { + int err; + + if (i == cur) + continue; + + ret = __uac3_clock_find_source(chip, fmt, + selector->baCSourceID[i - 1], + visited, true); + if (ret < 0) + continue; + + err = uac_clock_selector_set_val(chip, entity_id, i); + if (err < 0) + continue; + + usb_audio_info(chip, + "found and selected valid clock source %d\n", + ret); + return ret; + } + + return -ENXIO; + } + + /* FIXME: multipliers only act as pass-thru element for now */ + multiplier = snd_usb_find_clock_multiplier_v3(chip->ctrl_intf, + entity_id); + if (multiplier) + return __uac3_clock_find_source(chip, fmt, + multiplier->bCSourceID, + visited, validate); + + return -EINVAL; +} + +/* + * For all kinds of sample rate settings and other device queries, + * the clock source (end-leaf) must be used. However, clock selectors, + * clock multipliers and sample rate converters may be specified as + * clock source input to terminal. This functions walks the clock path + * to its end and tries to find the source. + * + * The 'visited' bitfield is used internally to detect recursive loops. + * + * Returns the clock source UnitID (>=0) on success, or an error. + */ +int snd_usb_clock_find_source(struct snd_usb_audio *chip, + struct audioformat *fmt, bool validate) +{ + DECLARE_BITMAP(visited, 256); + memset(visited, 0, sizeof(visited)); + + switch (fmt->protocol) { + case UAC_VERSION_2: + return __uac_clock_find_source(chip, fmt, fmt->clock, visited, + validate); + case UAC_VERSION_3: + return __uac3_clock_find_source(chip, fmt, fmt->clock, visited, + validate); + default: + return -EINVAL; + } +} + +static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + struct usb_device *dev = chip->dev; + unsigned int ep; + unsigned char data[3]; + int err, crate; + + if (get_iface_desc(alts)->bNumEndpoints < 1) + return -EINVAL; + ep = get_endpoint(alts, 0)->bEndpointAddress; + + /* if endpoint doesn't have sampling rate control, bail out */ + if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE)) + return 0; + + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data)); + if (err < 0) { + dev_err(&dev->dev, "%d:%d: cannot set freq %d to ep %#x\n", + iface, fmt->altsetting, rate, ep); + return err; + } + + /* Don't check the sample rate for devices which we know don't + * support reading */ + if (snd_usb_get_sample_rate_quirk(chip)) + return 0; + /* the firmware is likely buggy, don't repeat to fail too many times */ + if (chip->sample_rate_read_error > 2) + return 0; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data)); + if (err < 0) { + dev_err(&dev->dev, "%d:%d: cannot get freq at ep %#x\n", + iface, fmt->altsetting, ep); + chip->sample_rate_read_error++; + return 0; /* some devices don't support reading */ + } + + crate = data[0] | (data[1] << 8) | (data[2] << 16); + if (!crate) { + dev_info(&dev->dev, "failed to read current rate; disabling the check\n"); + chip->sample_rate_read_error = 3; /* three strikes, see above */ + return 0; + } + + if (crate != rate) { + dev_warn(&dev->dev, "current rate %d is different from the runtime rate %d\n", crate, rate); + // runtime->rate = crate; + } + + return 0; +} + +static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, + int altsetting, int clock) +{ + struct usb_device *dev = chip->dev; + __le32 data; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_SAM_FREQ << 8, + snd_usb_ctrl_intf(chip) | (clock << 8), + &data, sizeof(data)); + if (err < 0) { + dev_warn(&dev->dev, "%d:%d: cannot get freq (v2/v3): err %d\n", + iface, altsetting, err); + return 0; + } + + return le32_to_cpu(data); +} + +static int set_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + struct usb_device *dev = chip->dev; + __le32 data; + int err, cur_rate, prev_rate; + int clock; + bool writeable; + u32 bmControls; + + /* First, try to find a valid clock. This may trigger + * automatic clock selection if the current clock is not + * valid. + */ + clock = snd_usb_clock_find_source(chip, fmt, true); + if (clock < 0) { + /* We did not find a valid clock, but that might be + * because the current sample rate does not match an + * external clock source. Try again without validation + * and we will do another validation after setting the + * rate. + */ + clock = snd_usb_clock_find_source(chip, fmt, false); + if (clock < 0) + return clock; + } + + prev_rate = get_sample_rate_v2v3(chip, iface, fmt->altsetting, clock); + if (prev_rate == rate) + goto validation; + + if (fmt->protocol == UAC_VERSION_3) { + struct uac3_clock_source_descriptor *cs_desc; + + cs_desc = snd_usb_find_clock_source_v3(chip->ctrl_intf, clock); + bmControls = le32_to_cpu(cs_desc->bmControls); + } else { + struct uac_clock_source_descriptor *cs_desc; + + cs_desc = snd_usb_find_clock_source(chip->ctrl_intf, clock); + bmControls = cs_desc->bmControls; + } + + writeable = uac_v2v3_control_is_writeable(bmControls, + UAC2_CS_CONTROL_SAM_FREQ); + if (writeable) { + data = cpu_to_le32(rate); + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + UAC2_CS_CONTROL_SAM_FREQ << 8, + snd_usb_ctrl_intf(chip) | (clock << 8), + &data, sizeof(data)); + if (err < 0) { + usb_audio_err(chip, + "%d:%d: cannot set freq %d (v2/v3): err %d\n", + iface, fmt->altsetting, rate, err); + return err; + } + + cur_rate = get_sample_rate_v2v3(chip, iface, + fmt->altsetting, clock); + } else { + cur_rate = prev_rate; + } + + if (cur_rate != rate) { + if (!writeable) { + usb_audio_warn(chip, + "%d:%d: freq mismatch (RO clock): req %d, clock runs @%d\n", + iface, fmt->altsetting, rate, cur_rate); + return -ENXIO; + } + usb_audio_dbg(chip, + "current rate %d is different from the runtime rate %d\n", + cur_rate, rate); + } + + /* Some devices doesn't respond to sample rate changes while the + * interface is active. */ + if (rate != prev_rate) { + usb_set_interface(dev, iface, 0); + snd_usb_set_interface_quirk(dev); + usb_set_interface(dev, iface, fmt->altsetting); + snd_usb_set_interface_quirk(dev); + } + +validation: + /* validate clock after rate change */ + if (!uac_clock_source_is_valid(chip, fmt, clock)) + return -ENXIO; + return 0; +} + +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + switch (fmt->protocol) { + case UAC_VERSION_1: + default: + return set_sample_rate_v1(chip, iface, alts, fmt, rate); + + case UAC_VERSION_3: + if (chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + if (rate != UAC3_BADD_SAMPLING_RATE) + return -ENXIO; + else + return 0; + } + /* fall through */ + case UAC_VERSION_2: + return set_sample_rate_v2v3(chip, iface, alts, fmt, rate); + } +} + diff --git a/sound/usb/clock.h b/sound/usb/clock.h new file mode 100644 index 000000000..68df0fbe0 --- /dev/null +++ b/sound/usb/clock.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_CLOCK_H +#define __USBAUDIO_CLOCK_H + +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate); + +int snd_usb_clock_find_source(struct snd_usb_audio *chip, + struct audioformat *fmt, bool validate); + +#endif /* __USBAUDIO_CLOCK_H */ diff --git a/sound/usb/debug.h b/sound/usb/debug.h new file mode 100644 index 000000000..7dd983c35 --- /dev/null +++ b/sound/usb/debug.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_DEBUG_H +#define __USBAUDIO_DEBUG_H + +/* + * h/w constraints + */ + +#ifdef HW_CONST_DEBUG +#define hwc_debug(fmt, args...) printk(KERN_DEBUG fmt, ##args) +#else +#define hwc_debug(fmt, args...) do { } while(0) +#endif + +#endif /* __USBAUDIO_DEBUG_H */ + diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c new file mode 100644 index 000000000..727ef9889 --- /dev/null +++ b/sound/usb/endpoint.c @@ -0,0 +1,1233 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "helper.h" +#include "card.h" +#include "endpoint.h" +#include "pcm.h" +#include "quirks.h" + +#define EP_FLAG_RUNNING 1 +#define EP_FLAG_STOPPING 2 + +/* + * snd_usb_endpoint is a model that abstracts everything related to an + * USB endpoint and its streaming. + * + * There are functions to activate and deactivate the streaming URBs and + * optional callbacks to let the pcm logic handle the actual content of the + * packets for playback and record. Thus, the bus streaming and the audio + * handlers are fully decoupled. + * + * There are two different types of endpoints in audio applications. + * + * SND_USB_ENDPOINT_TYPE_DATA handles full audio data payload for both + * inbound and outbound traffic. + * + * SND_USB_ENDPOINT_TYPE_SYNC endpoints are for inbound traffic only and + * expect the payload to carry Q10.14 / Q16.16 formatted sync information + * (3 or 4 bytes). + * + * Each endpoint has to be configured prior to being used by calling + * snd_usb_endpoint_set_params(). + * + * The model incorporates a reference counting, so that multiple users + * can call snd_usb_endpoint_start() and snd_usb_endpoint_stop(), and + * only the first user will effectively start the URBs, and only the last + * one to stop it will tear the URBs down again. + */ + +/* + * convert a sampling rate into our full speed format (fs/1000 in Q16.16) + * this will overflow at approx 524 kHz + */ +static inline unsigned get_usb_full_speed_rate(unsigned int rate) +{ + return ((rate << 13) + 62) / 125; +} + +/* + * convert a sampling rate into USB high speed format (fs/8000 in Q16.16) + * this will overflow at approx 4 MHz + */ +static inline unsigned get_usb_high_speed_rate(unsigned int rate) +{ + return ((rate << 10) + 62) / 125; +} + +/* + * release a urb data + */ +static void release_urb_ctx(struct snd_urb_ctx *u) +{ + if (u->buffer_size) + usb_free_coherent(u->ep->chip->dev, u->buffer_size, + u->urb->transfer_buffer, + u->urb->transfer_dma); + usb_free_urb(u->urb); + u->urb = NULL; +} + +static const char *usb_error_string(int err) +{ + switch (err) { + case -ENODEV: + return "no device"; + case -ENOENT: + return "endpoint not enabled"; + case -EPIPE: + return "endpoint stalled"; + case -ENOSPC: + return "not enough bandwidth"; + case -ESHUTDOWN: + return "device disabled"; + case -EHOSTUNREACH: + return "device suspended"; + case -EINVAL: + case -EAGAIN: + case -EFBIG: + case -EMSGSIZE: + return "internal error"; + default: + return "unknown error"; + } +} + +/** + * snd_usb_endpoint_implicit_feedback_sink: Report endpoint usage type + * + * @ep: The snd_usb_endpoint + * + * Determine whether an endpoint is driven by an implicit feedback + * data endpoint source. + */ +int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep) +{ + return ep->sync_master && + ep->sync_master->type == SND_USB_ENDPOINT_TYPE_DATA && + ep->type == SND_USB_ENDPOINT_TYPE_DATA && + usb_pipeout(ep->pipe); +} + +/* + * For streaming based on information derived from sync endpoints, + * prepare_outbound_urb_sizes() will call next_packet_size() to + * determine the number of samples to be sent in the next packet. + * + * For implicit feedback, next_packet_size() is unused. + */ +int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep) +{ + unsigned long flags; + int ret; + + if (ep->fill_max) + return ep->maxframesize; + + spin_lock_irqsave(&ep->lock, flags); + ep->phase = (ep->phase & 0xffff) + + (ep->freqm << ep->datainterval); + ret = min(ep->phase >> 16, ep->maxframesize); + spin_unlock_irqrestore(&ep->lock, flags); + + return ret; +} + +static void retire_outbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *urb_ctx) +{ + if (ep->retire_data_urb) + ep->retire_data_urb(ep->data_subs, urb_ctx->urb); +} + +static void retire_inbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *urb_ctx) +{ + struct urb *urb = urb_ctx->urb; + + if (unlikely(ep->skip_packets > 0)) { + ep->skip_packets--; + return; + } + + if (ep->sync_slave) + snd_usb_handle_sync_urb(ep->sync_slave, ep, urb); + + if (ep->retire_data_urb) + ep->retire_data_urb(ep->data_subs, urb); +} + +static void prepare_silent_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) +{ + struct urb *urb = ctx->urb; + unsigned int offs = 0; + unsigned int extra = 0; + __le32 packet_length; + int i; + + /* For tx_length_quirk, put packet length at start of packet */ + if (ep->chip->tx_length_quirk) + extra = sizeof(packet_length); + + for (i = 0; i < ctx->packets; ++i) { + unsigned int offset; + unsigned int length; + int counts; + + if (ctx->packet_size[i]) + counts = ctx->packet_size[i]; + else + counts = snd_usb_endpoint_next_packet_size(ep); + + length = counts * ep->stride; /* number of silent bytes */ + offset = offs * ep->stride + extra * i; + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = length + extra; + if (extra) { + packet_length = cpu_to_le32(length); + memcpy(urb->transfer_buffer + offset, + &packet_length, sizeof(packet_length)); + } + memset(urb->transfer_buffer + offset + extra, + ep->silence_value, length); + offs += counts; + } + + urb->number_of_packets = ctx->packets; + urb->transfer_buffer_length = offs * ep->stride + ctx->packets * extra; +} + +/* + * Prepare a PLAYBACK urb for submission to the bus. + */ +static void prepare_outbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) +{ + struct urb *urb = ctx->urb; + unsigned char *cp = urb->transfer_buffer; + + urb->dev = ep->chip->dev; /* we need to set this at each time */ + + switch (ep->type) { + case SND_USB_ENDPOINT_TYPE_DATA: + if (ep->prepare_data_urb) { + ep->prepare_data_urb(ep->data_subs, urb); + } else { + /* no data provider, so send silence */ + prepare_silent_urb(ep, ctx); + } + break; + + case SND_USB_ENDPOINT_TYPE_SYNC: + if (snd_usb_get_speed(ep->chip->dev) >= USB_SPEED_HIGH) { + /* + * fill the length and offset of each urb descriptor. + * the fixed 12.13 frequency is passed as 16.16 through the pipe. + */ + urb->iso_frame_desc[0].length = 4; + urb->iso_frame_desc[0].offset = 0; + cp[0] = ep->freqn; + cp[1] = ep->freqn >> 8; + cp[2] = ep->freqn >> 16; + cp[3] = ep->freqn >> 24; + } else { + /* + * fill the length and offset of each urb descriptor. + * the fixed 10.14 frequency is passed through the pipe. + */ + urb->iso_frame_desc[0].length = 3; + urb->iso_frame_desc[0].offset = 0; + cp[0] = ep->freqn >> 2; + cp[1] = ep->freqn >> 10; + cp[2] = ep->freqn >> 18; + } + + break; + } +} + +/* + * Prepare a CAPTURE or SYNC urb for submission to the bus. + */ +static inline void prepare_inbound_urb(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *urb_ctx) +{ + int i, offs; + struct urb *urb = urb_ctx->urb; + + urb->dev = ep->chip->dev; /* we need to set this at each time */ + + switch (ep->type) { + case SND_USB_ENDPOINT_TYPE_DATA: + offs = 0; + for (i = 0; i < urb_ctx->packets; i++) { + urb->iso_frame_desc[i].offset = offs; + urb->iso_frame_desc[i].length = ep->curpacksize; + offs += ep->curpacksize; + } + + urb->transfer_buffer_length = offs; + urb->number_of_packets = urb_ctx->packets; + break; + + case SND_USB_ENDPOINT_TYPE_SYNC: + urb->iso_frame_desc[0].length = min(4u, ep->syncmaxsize); + urb->iso_frame_desc[0].offset = 0; + break; + } +} + +/* + * Send output urbs that have been prepared previously. URBs are dequeued + * from ep->ready_playback_urbs and in case there there aren't any available + * or there are no packets that have been prepared, this function does + * nothing. + * + * The reason why the functionality of sending and preparing URBs is separated + * is that host controllers don't guarantee the order in which they return + * inbound and outbound packets to their submitters. + * + * This function is only used for implicit feedback endpoints. For endpoints + * driven by dedicated sync endpoints, URBs are immediately re-submitted + * from their completion handler. + */ +static void queue_pending_output_urbs(struct snd_usb_endpoint *ep) +{ + while (test_bit(EP_FLAG_RUNNING, &ep->flags)) { + + unsigned long flags; + struct snd_usb_packet_info *uninitialized_var(packet); + struct snd_urb_ctx *ctx = NULL; + int err, i; + + spin_lock_irqsave(&ep->lock, flags); + if (ep->next_packet_read_pos != ep->next_packet_write_pos) { + packet = ep->next_packet + ep->next_packet_read_pos; + ep->next_packet_read_pos++; + ep->next_packet_read_pos %= MAX_URBS; + + /* take URB out of FIFO */ + if (!list_empty(&ep->ready_playback_urbs)) { + ctx = list_first_entry(&ep->ready_playback_urbs, + struct snd_urb_ctx, ready_list); + list_del_init(&ctx->ready_list); + } + } + spin_unlock_irqrestore(&ep->lock, flags); + + if (ctx == NULL) + return; + + /* copy over the length information */ + for (i = 0; i < packet->packets; i++) + ctx->packet_size[i] = packet->packet_size[i]; + + /* call the data handler to fill in playback data */ + prepare_outbound_urb(ep, ctx); + + err = usb_submit_urb(ctx->urb, GFP_ATOMIC); + if (err < 0) + usb_audio_err(ep->chip, + "Unable to submit urb #%d: %d (urb %p)\n", + ctx->index, err, ctx->urb); + else + set_bit(ctx->index, &ep->active_mask); + } +} + +/* + * complete callback for urbs + */ +static void snd_complete_urb(struct urb *urb) +{ + struct snd_urb_ctx *ctx = urb->context; + struct snd_usb_endpoint *ep = ctx->ep; + struct snd_pcm_substream *substream; + unsigned long flags; + int err; + + if (unlikely(urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN)) /* device disabled */ + goto exit_clear; + /* device disconnected */ + if (unlikely(atomic_read(&ep->chip->shutdown))) + goto exit_clear; + + if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags))) + goto exit_clear; + + if (usb_pipeout(ep->pipe)) { + retire_outbound_urb(ep, ctx); + /* can be stopped during retire callback */ + if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags))) + goto exit_clear; + + if (snd_usb_endpoint_implicit_feedback_sink(ep)) { + spin_lock_irqsave(&ep->lock, flags); + list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + spin_unlock_irqrestore(&ep->lock, flags); + queue_pending_output_urbs(ep); + + goto exit_clear; + } + + prepare_outbound_urb(ep, ctx); + /* can be stopped during prepare callback */ + if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags))) + goto exit_clear; + } else { + retire_inbound_urb(ep, ctx); + /* can be stopped during retire callback */ + if (unlikely(!test_bit(EP_FLAG_RUNNING, &ep->flags))) + goto exit_clear; + + prepare_inbound_urb(ep, ctx); + } + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err == 0) + return; + + usb_audio_err(ep->chip, "cannot submit urb (err = %d)\n", err); + if (ep->data_subs && ep->data_subs->pcm_substream) { + substream = ep->data_subs->pcm_substream; + snd_pcm_stop_xrun(substream); + } + +exit_clear: + clear_bit(ctx->index, &ep->active_mask); +} + +/** + * snd_usb_add_endpoint: Add an endpoint to an USB audio chip + * + * @chip: The chip + * @alts: The USB host interface + * @ep_num: The number of the endpoint to use + * @direction: SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE + * @type: SND_USB_ENDPOINT_TYPE_DATA or SND_USB_ENDPOINT_TYPE_SYNC + * + * If the requested endpoint has not been added to the given chip before, + * a new instance is created. Otherwise, a pointer to the previoulsy + * created instance is returned. In case of any error, NULL is returned. + * + * New endpoints will be added to chip->ep_list and must be freed by + * calling snd_usb_endpoint_free(). + * + * For SND_USB_ENDPOINT_TYPE_SYNC, the caller needs to guarantee that + * bNumEndpoints > 1 beforehand. + */ +struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int ep_num, int direction, int type) +{ + struct snd_usb_endpoint *ep; + int is_playback = direction == SNDRV_PCM_STREAM_PLAYBACK; + + if (WARN_ON(!alts)) + return NULL; + + mutex_lock(&chip->mutex); + + list_for_each_entry(ep, &chip->ep_list, list) { + if (ep->ep_num == ep_num && + ep->iface == alts->desc.bInterfaceNumber && + ep->altsetting == alts->desc.bAlternateSetting) { + usb_audio_dbg(ep->chip, + "Re-using EP %x in iface %d,%d @%p\n", + ep_num, ep->iface, ep->altsetting, ep); + goto __exit_unlock; + } + } + + usb_audio_dbg(chip, "Creating new %s %s endpoint #%x\n", + is_playback ? "playback" : "capture", + type == SND_USB_ENDPOINT_TYPE_DATA ? "data" : "sync", + ep_num); + + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + goto __exit_unlock; + + ep->chip = chip; + spin_lock_init(&ep->lock); + ep->type = type; + ep->ep_num = ep_num; + ep->iface = alts->desc.bInterfaceNumber; + ep->altsetting = alts->desc.bAlternateSetting; + INIT_LIST_HEAD(&ep->ready_playback_urbs); + ep_num &= USB_ENDPOINT_NUMBER_MASK; + + if (is_playback) + ep->pipe = usb_sndisocpipe(chip->dev, ep_num); + else + ep->pipe = usb_rcvisocpipe(chip->dev, ep_num); + + if (type == SND_USB_ENDPOINT_TYPE_SYNC) { + if (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 1)->bRefresh >= 1 && + get_endpoint(alts, 1)->bRefresh <= 9) + ep->syncinterval = get_endpoint(alts, 1)->bRefresh; + else if (snd_usb_get_speed(chip->dev) == USB_SPEED_FULL) + ep->syncinterval = 1; + else if (get_endpoint(alts, 1)->bInterval >= 1 && + get_endpoint(alts, 1)->bInterval <= 16) + ep->syncinterval = get_endpoint(alts, 1)->bInterval - 1; + else + ep->syncinterval = 3; + + ep->syncmaxsize = le16_to_cpu(get_endpoint(alts, 1)->wMaxPacketSize); + } + + list_add_tail(&ep->list, &chip->ep_list); + +__exit_unlock: + mutex_unlock(&chip->mutex); + + return ep; +} + +/* + * wait until all urbs are processed. + */ +static int wait_clear_urbs(struct snd_usb_endpoint *ep) +{ + unsigned long end_time = jiffies + msecs_to_jiffies(1000); + int alive; + + do { + alive = bitmap_weight(&ep->active_mask, ep->nurbs); + if (!alive) + break; + + schedule_timeout_uninterruptible(1); + } while (time_before(jiffies, end_time)); + + if (alive) + usb_audio_err(ep->chip, + "timeout: still %d active urbs on EP #%x\n", + alive, ep->ep_num); + clear_bit(EP_FLAG_STOPPING, &ep->flags); + + ep->data_subs = NULL; + ep->sync_slave = NULL; + ep->retire_data_urb = NULL; + ep->prepare_data_urb = NULL; + + return 0; +} + +/* sync the pending stop operation; + * this function itself doesn't trigger the stop operation + */ +void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) +{ + if (ep && test_bit(EP_FLAG_STOPPING, &ep->flags)) + wait_clear_urbs(ep); +} + +/* + * unlink active urbs. + */ +static int deactivate_urbs(struct snd_usb_endpoint *ep, bool force) +{ + unsigned int i; + + if (!force && atomic_read(&ep->chip->shutdown)) /* to be sure... */ + return -EBADFD; + + clear_bit(EP_FLAG_RUNNING, &ep->flags); + + INIT_LIST_HEAD(&ep->ready_playback_urbs); + ep->next_packet_read_pos = 0; + ep->next_packet_write_pos = 0; + + for (i = 0; i < ep->nurbs; i++) { + if (test_bit(i, &ep->active_mask)) { + if (!test_and_set_bit(i, &ep->unlink_mask)) { + struct urb *u = ep->urb[i].urb; + usb_unlink_urb(u); + } + } + } + + return 0; +} + +/* + * release an endpoint's urbs + */ +static void release_urbs(struct snd_usb_endpoint *ep, int force) +{ + int i; + + /* route incoming urbs to nirvana */ + ep->retire_data_urb = NULL; + ep->prepare_data_urb = NULL; + + /* stop urbs */ + deactivate_urbs(ep, force); + wait_clear_urbs(ep); + + for (i = 0; i < ep->nurbs; i++) + release_urb_ctx(&ep->urb[i]); + + if (ep->syncbuf) + usb_free_coherent(ep->chip->dev, SYNC_URBS * 4, + ep->syncbuf, ep->sync_dma); + + ep->syncbuf = NULL; + ep->nurbs = 0; +} + +/* + * configure a data endpoint + */ +static int data_ep_set_params(struct snd_usb_endpoint *ep, + snd_pcm_format_t pcm_format, + unsigned int channels, + unsigned int period_bytes, + unsigned int frames_per_period, + unsigned int periods_per_buffer, + struct audioformat *fmt, + struct snd_usb_endpoint *sync_ep) +{ + unsigned int maxsize, minsize, packs_per_ms, max_packs_per_urb; + unsigned int max_packs_per_period, urbs_per_period, urb_packs; + unsigned int max_urbs, i; + int frame_bits = snd_pcm_format_physical_width(pcm_format) * channels; + int tx_length_quirk = (ep->chip->tx_length_quirk && + usb_pipeout(ep->pipe)); + + if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) { + /* + * When operating in DSD DOP mode, the size of a sample frame + * in hardware differs from the actual physical format width + * because we need to make room for the DOP markers. + */ + frame_bits += channels << 3; + } + + ep->datainterval = fmt->datainterval; + ep->stride = frame_bits >> 3; + + switch (pcm_format) { + case SNDRV_PCM_FORMAT_U8: + ep->silence_value = 0x80; + break; + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + ep->silence_value = 0x69; + break; + default: + ep->silence_value = 0; + } + + /* assume max. frequency is 50% higher than nominal */ + ep->freqmax = ep->freqn + (ep->freqn >> 1); + /* Round up freqmax to nearest integer in order to calculate maximum + * packet size, which must represent a whole number of frames. + * This is accomplished by adding 0x0.ffff before converting the + * Q16.16 format into integer. + * In order to accurately calculate the maximum packet size when + * the data interval is more than 1 (i.e. ep->datainterval > 0), + * multiply by the data interval prior to rounding. For instance, + * a freqmax of 41 kHz will result in a max packet size of 6 (5.125) + * frames with a data interval of 1, but 11 (10.25) frames with a + * data interval of 2. + * (ep->freqmax << ep->datainterval overflows at 8.192 MHz for the + * maximum datainterval value of 3, at USB full speed, higher for + * USB high speed, noting that ep->freqmax is in units of + * frames per packet in Q16.16 format.) + */ + maxsize = (((ep->freqmax << ep->datainterval) + 0xffff) >> 16) * + (frame_bits >> 3); + if (tx_length_quirk) + maxsize += sizeof(__le32); /* Space for length descriptor */ + /* but wMaxPacketSize might reduce this */ + if (ep->maxpacksize && ep->maxpacksize < maxsize) { + /* whatever fits into a max. size packet */ + unsigned int data_maxsize = maxsize = ep->maxpacksize; + + if (tx_length_quirk) + /* Need to remove the length descriptor to calc freq */ + data_maxsize -= sizeof(__le32); + ep->freqmax = (data_maxsize / (frame_bits >> 3)) + << (16 - ep->datainterval); + } + + if (ep->fill_max) + ep->curpacksize = ep->maxpacksize; + else + ep->curpacksize = maxsize; + + if (snd_usb_get_speed(ep->chip->dev) != USB_SPEED_FULL) { + packs_per_ms = 8 >> ep->datainterval; + max_packs_per_urb = MAX_PACKS_HS; + } else { + packs_per_ms = 1; + max_packs_per_urb = MAX_PACKS; + } + if (sync_ep && !snd_usb_endpoint_implicit_feedback_sink(ep)) + max_packs_per_urb = min(max_packs_per_urb, + 1U << sync_ep->syncinterval); + max_packs_per_urb = max(1u, max_packs_per_urb >> ep->datainterval); + + /* + * Capture endpoints need to use small URBs because there's no way + * to tell in advance where the next period will end, and we don't + * want the next URB to complete much after the period ends. + * + * Playback endpoints with implicit sync much use the same parameters + * as their corresponding capture endpoint. + */ + if (usb_pipein(ep->pipe) || + snd_usb_endpoint_implicit_feedback_sink(ep)) { + + urb_packs = packs_per_ms; + /* + * Wireless devices can poll at a max rate of once per 4ms. + * For dataintervals less than 5, increase the packet count to + * allow the host controller to use bursting to fill in the + * gaps. + */ + if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_WIRELESS) { + int interval = ep->datainterval; + while (interval < 5) { + urb_packs <<= 1; + ++interval; + } + } + /* make capture URBs <= 1 ms and smaller than a period */ + urb_packs = min(max_packs_per_urb, urb_packs); + while (urb_packs > 1 && urb_packs * maxsize >= period_bytes) + urb_packs >>= 1; + ep->nurbs = MAX_URBS; + + /* + * Playback endpoints without implicit sync are adjusted so that + * a period fits as evenly as possible in the smallest number of + * URBs. The total number of URBs is adjusted to the size of the + * ALSA buffer, subject to the MAX_URBS and MAX_QUEUE limits. + */ + } else { + /* determine how small a packet can be */ + minsize = (ep->freqn >> (16 - ep->datainterval)) * + (frame_bits >> 3); + /* with sync from device, assume it can be 12% lower */ + if (sync_ep) + minsize -= minsize >> 3; + minsize = max(minsize, 1u); + + /* how many packets will contain an entire ALSA period? */ + max_packs_per_period = DIV_ROUND_UP(period_bytes, minsize); + + /* how many URBs will contain a period? */ + urbs_per_period = DIV_ROUND_UP(max_packs_per_period, + max_packs_per_urb); + /* how many packets are needed in each URB? */ + urb_packs = DIV_ROUND_UP(max_packs_per_period, urbs_per_period); + + /* limit the number of frames in a single URB */ + ep->max_urb_frames = DIV_ROUND_UP(frames_per_period, + urbs_per_period); + + /* try to use enough URBs to contain an entire ALSA buffer */ + max_urbs = min((unsigned) MAX_URBS, + MAX_QUEUE * packs_per_ms / urb_packs); + ep->nurbs = min(max_urbs, urbs_per_period * periods_per_buffer); + } + + /* allocate and initialize data urbs */ + for (i = 0; i < ep->nurbs; i++) { + struct snd_urb_ctx *u = &ep->urb[i]; + u->index = i; + u->ep = ep; + u->packets = urb_packs; + u->buffer_size = maxsize * u->packets; + + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) + u->packets++; /* for transfer delimiter */ + u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); + if (!u->urb) + goto out_of_memory; + + u->urb->transfer_buffer = + usb_alloc_coherent(ep->chip->dev, u->buffer_size, + GFP_KERNEL, &u->urb->transfer_dma); + if (!u->urb->transfer_buffer) + goto out_of_memory; + u->urb->pipe = ep->pipe; + u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + u->urb->interval = 1 << ep->datainterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; + INIT_LIST_HEAD(&u->ready_list); + } + + return 0; + +out_of_memory: + release_urbs(ep, 0); + return -ENOMEM; +} + +/* + * configure a sync endpoint + */ +static int sync_ep_set_params(struct snd_usb_endpoint *ep) +{ + int i; + + ep->syncbuf = usb_alloc_coherent(ep->chip->dev, SYNC_URBS * 4, + GFP_KERNEL, &ep->sync_dma); + if (!ep->syncbuf) + return -ENOMEM; + + for (i = 0; i < SYNC_URBS; i++) { + struct snd_urb_ctx *u = &ep->urb[i]; + u->index = i; + u->ep = ep; + u->packets = 1; + u->urb = usb_alloc_urb(1, GFP_KERNEL); + if (!u->urb) + goto out_of_memory; + u->urb->transfer_buffer = ep->syncbuf + i * 4; + u->urb->transfer_dma = ep->sync_dma + i * 4; + u->urb->transfer_buffer_length = 4; + u->urb->pipe = ep->pipe; + u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + u->urb->number_of_packets = 1; + u->urb->interval = 1 << ep->syncinterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; + } + + ep->nurbs = SYNC_URBS; + + return 0; + +out_of_memory: + release_urbs(ep, 0); + return -ENOMEM; +} + +/** + * snd_usb_endpoint_set_params: configure an snd_usb_endpoint + * + * @ep: the snd_usb_endpoint to configure + * @pcm_format: the audio fomat. + * @channels: the number of audio channels. + * @period_bytes: the number of bytes in one alsa period. + * @period_frames: the number of frames in one alsa period. + * @buffer_periods: the number of periods in one alsa buffer. + * @rate: the frame rate. + * @fmt: the USB audio format information + * @sync_ep: the sync endpoint to use, if any + * + * Determine the number of URBs to be used on this endpoint. + * An endpoint must be configured before it can be started. + * An endpoint that is already running can not be reconfigured. + */ +int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, + snd_pcm_format_t pcm_format, + unsigned int channels, + unsigned int period_bytes, + unsigned int period_frames, + unsigned int buffer_periods, + unsigned int rate, + struct audioformat *fmt, + struct snd_usb_endpoint *sync_ep) +{ + int err; + + if (ep->use_count != 0) { + usb_audio_warn(ep->chip, + "Unable to change format on ep #%x: already in use\n", + ep->ep_num); + return -EBUSY; + } + + /* release old buffers, if any */ + release_urbs(ep, 0); + + ep->datainterval = fmt->datainterval; + ep->maxpacksize = fmt->maxpacksize; + ep->fill_max = !!(fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX); + + if (snd_usb_get_speed(ep->chip->dev) == USB_SPEED_FULL) + ep->freqn = get_usb_full_speed_rate(rate); + else + ep->freqn = get_usb_high_speed_rate(rate); + + /* calculate the frequency in 16.16 format */ + ep->freqm = ep->freqn; + ep->freqshift = INT_MIN; + + ep->phase = 0; + + switch (ep->type) { + case SND_USB_ENDPOINT_TYPE_DATA: + err = data_ep_set_params(ep, pcm_format, channels, + period_bytes, period_frames, + buffer_periods, fmt, sync_ep); + break; + case SND_USB_ENDPOINT_TYPE_SYNC: + err = sync_ep_set_params(ep); + break; + default: + err = -EINVAL; + } + + usb_audio_dbg(ep->chip, + "Setting params for ep #%x (type %d, %d urbs), ret=%d\n", + ep->ep_num, ep->type, ep->nurbs, err); + + return err; +} + +/** + * snd_usb_endpoint_start: start an snd_usb_endpoint + * + * @ep: the endpoint to start + * + * A call to this function will increment the use count of the endpoint. + * In case it is not already running, the URBs for this endpoint will be + * submitted. Otherwise, this function does nothing. + * + * Must be balanced to calls of snd_usb_endpoint_stop(). + * + * Returns an error if the URB submission failed, 0 in all other cases. + */ +int snd_usb_endpoint_start(struct snd_usb_endpoint *ep) +{ + int err; + unsigned int i; + + if (atomic_read(&ep->chip->shutdown)) + return -EBADFD; + + /* already running? */ + if (++ep->use_count != 1) + return 0; + + /* just to be sure */ + deactivate_urbs(ep, false); + + ep->active_mask = 0; + ep->unlink_mask = 0; + ep->phase = 0; + + snd_usb_endpoint_start_quirk(ep); + + /* + * If this endpoint has a data endpoint as implicit feedback source, + * don't start the urbs here. Instead, mark them all as available, + * wait for the record urbs to return and queue the playback urbs + * from that context. + */ + + set_bit(EP_FLAG_RUNNING, &ep->flags); + + if (snd_usb_endpoint_implicit_feedback_sink(ep)) { + for (i = 0; i < ep->nurbs; i++) { + struct snd_urb_ctx *ctx = ep->urb + i; + list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + } + + return 0; + } + + for (i = 0; i < ep->nurbs; i++) { + struct urb *urb = ep->urb[i].urb; + + if (snd_BUG_ON(!urb)) + goto __error; + + if (usb_pipeout(ep->pipe)) { + prepare_outbound_urb(ep, urb->context); + } else { + prepare_inbound_urb(ep, urb->context); + } + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + usb_audio_err(ep->chip, + "cannot submit urb %d, error %d: %s\n", + i, err, usb_error_string(err)); + goto __error; + } + set_bit(i, &ep->active_mask); + } + + return 0; + +__error: + clear_bit(EP_FLAG_RUNNING, &ep->flags); + ep->use_count--; + deactivate_urbs(ep, false); + return -EPIPE; +} + +/** + * snd_usb_endpoint_stop: stop an snd_usb_endpoint + * + * @ep: the endpoint to stop (may be NULL) + * + * A call to this function will decrement the use count of the endpoint. + * In case the last user has requested the endpoint stop, the URBs will + * actually be deactivated. + * + * Must be balanced to calls of snd_usb_endpoint_start(). + * + * The caller needs to synchronize the pending stop operation via + * snd_usb_endpoint_sync_pending_stop(). + */ +void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep) +{ + if (!ep) + return; + + if (snd_BUG_ON(ep->use_count == 0)) + return; + + if (--ep->use_count == 0) { + deactivate_urbs(ep, false); + set_bit(EP_FLAG_STOPPING, &ep->flags); + } +} + +/** + * snd_usb_endpoint_deactivate: deactivate an snd_usb_endpoint + * + * @ep: the endpoint to deactivate + * + * If the endpoint is not currently in use, this functions will + * deactivate its associated URBs. + * + * In case of any active users, this functions does nothing. + */ +void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep) +{ + if (!ep) + return; + + if (ep->use_count != 0) + return; + + deactivate_urbs(ep, true); + wait_clear_urbs(ep); +} + +/** + * snd_usb_endpoint_release: Tear down an snd_usb_endpoint + * + * @ep: the endpoint to release + * + * This function does not care for the endpoint's use count but will tear + * down all the streaming URBs immediately. + */ +void snd_usb_endpoint_release(struct snd_usb_endpoint *ep) +{ + release_urbs(ep, 1); +} + +/** + * snd_usb_endpoint_free: Free the resources of an snd_usb_endpoint + * + * @ep: the endpoint to free + * + * This free all resources of the given ep. + */ +void snd_usb_endpoint_free(struct snd_usb_endpoint *ep) +{ + kfree(ep); +} + +/** + * snd_usb_handle_sync_urb: parse an USB sync packet + * + * @ep: the endpoint to handle the packet + * @sender: the sending endpoint + * @urb: the received packet + * + * This function is called from the context of an endpoint that received + * the packet and is used to let another endpoint object handle the payload. + */ +void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, + struct snd_usb_endpoint *sender, + const struct urb *urb) +{ + int shift; + unsigned int f; + unsigned long flags; + + snd_BUG_ON(ep == sender); + + /* + * In case the endpoint is operating in implicit feedback mode, prepare + * a new outbound URB that has the same layout as the received packet + * and add it to the list of pending urbs. queue_pending_output_urbs() + * will take care of them later. + */ + if (snd_usb_endpoint_implicit_feedback_sink(ep) && + ep->use_count != 0) { + + /* implicit feedback case */ + int i, bytes = 0; + struct snd_urb_ctx *in_ctx; + struct snd_usb_packet_info *out_packet; + + in_ctx = urb->context; + + /* Count overall packet size */ + for (i = 0; i < in_ctx->packets; i++) + if (urb->iso_frame_desc[i].status == 0) + bytes += urb->iso_frame_desc[i].actual_length; + + /* + * skip empty packets. At least M-Audio's Fast Track Ultra stops + * streaming once it received a 0-byte OUT URB + */ + if (bytes == 0) + return; + + spin_lock_irqsave(&ep->lock, flags); + out_packet = ep->next_packet + ep->next_packet_write_pos; + + /* + * Iterate through the inbound packet and prepare the lengths + * for the output packet. The OUT packet we are about to send + * will have the same amount of payload bytes per stride as the + * IN packet we just received. Since the actual size is scaled + * by the stride, use the sender stride to calculate the length + * in case the number of channels differ between the implicitly + * fed-back endpoint and the synchronizing endpoint. + */ + + out_packet->packets = in_ctx->packets; + for (i = 0; i < in_ctx->packets; i++) { + if (urb->iso_frame_desc[i].status == 0) + out_packet->packet_size[i] = + urb->iso_frame_desc[i].actual_length / sender->stride; + else + out_packet->packet_size[i] = 0; + } + + ep->next_packet_write_pos++; + ep->next_packet_write_pos %= MAX_URBS; + spin_unlock_irqrestore(&ep->lock, flags); + queue_pending_output_urbs(ep); + + return; + } + + /* + * process after playback sync complete + * + * Full speed devices report feedback values in 10.14 format as samples + * per frame, high speed devices in 16.16 format as samples per + * microframe. + * + * Because the Audio Class 1 spec was written before USB 2.0, many high + * speed devices use a wrong interpretation, some others use an + * entirely different format. + * + * Therefore, we cannot predict what format any particular device uses + * and must detect it automatically. + */ + + if (urb->iso_frame_desc[0].status != 0 || + urb->iso_frame_desc[0].actual_length < 3) + return; + + f = le32_to_cpup(urb->transfer_buffer); + if (urb->iso_frame_desc[0].actual_length == 3) + f &= 0x00ffffff; + else + f &= 0x0fffffff; + + if (f == 0) + return; + + if (unlikely(sender->tenor_fb_quirk)) { + /* + * Devices based on Tenor 8802 chipsets (TEAC UD-H01 + * and others) sometimes change the feedback value + * by +/- 0x1.0000. + */ + if (f < ep->freqn - 0x8000) + f += 0xf000; + else if (f > ep->freqn + 0x8000) + f -= 0xf000; + } else if (unlikely(ep->freqshift == INT_MIN)) { + /* + * The first time we see a feedback value, determine its format + * by shifting it left or right until it matches the nominal + * frequency value. This assumes that the feedback does not + * differ from the nominal value more than +50% or -25%. + */ + shift = 0; + while (f < ep->freqn - ep->freqn / 4) { + f <<= 1; + shift++; + } + while (f > ep->freqn + ep->freqn / 2) { + f >>= 1; + shift--; + } + ep->freqshift = shift; + } else if (ep->freqshift >= 0) + f <<= ep->freqshift; + else + f >>= -ep->freqshift; + + if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) { + /* + * If the frequency looks valid, set it. + * This value is referred to in prepare_playback_urb(). + */ + spin_lock_irqsave(&ep->lock, flags); + ep->freqm = f; + spin_unlock_irqrestore(&ep->lock, flags); + } else { + /* + * Out of range; maybe the shift value is wrong. + * Reset it so that we autodetect again the next time. + */ + ep->freqshift = INT_MIN; + } +} + diff --git a/sound/usb/endpoint.h b/sound/usb/endpoint.h new file mode 100644 index 000000000..63a39d4fa --- /dev/null +++ b/sound/usb/endpoint.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_ENDPOINT_H +#define __USBAUDIO_ENDPOINT_H + +#define SND_USB_ENDPOINT_TYPE_DATA 0 +#define SND_USB_ENDPOINT_TYPE_SYNC 1 + +struct snd_usb_endpoint *snd_usb_add_endpoint(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int ep_num, int direction, int type); + +int snd_usb_endpoint_set_params(struct snd_usb_endpoint *ep, + snd_pcm_format_t pcm_format, + unsigned int channels, + unsigned int period_bytes, + unsigned int period_frames, + unsigned int buffer_periods, + unsigned int rate, + struct audioformat *fmt, + struct snd_usb_endpoint *sync_ep); + +int snd_usb_endpoint_start(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_stop(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep); +int snd_usb_endpoint_activate(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_deactivate(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_release(struct snd_usb_endpoint *ep); +void snd_usb_endpoint_free(struct snd_usb_endpoint *ep); + +int snd_usb_endpoint_implicit_feedback_sink(struct snd_usb_endpoint *ep); +int snd_usb_endpoint_next_packet_size(struct snd_usb_endpoint *ep); + +void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, + struct snd_usb_endpoint *sender, + const struct urb *urb); + +#endif /* __USBAUDIO_ENDPOINT_H */ diff --git a/sound/usb/format.c b/sound/usb/format.c new file mode 100644 index 000000000..01ba7a939 --- /dev/null +++ b/sound/usb/format.c @@ -0,0 +1,640 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "quirks.h" +#include "helper.h" +#include "debug.h" +#include "clock.h" +#include "format.h" + +/* + * parse the audio format type I descriptor + * and returns the corresponding pcm format + * + * @dev: usb device + * @fp: audioformat record + * @format: the format tag (wFormatTag) + * @fmt: the format type descriptor (v1/v2) or AudioStreaming descriptor (v3) + */ +static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, + struct audioformat *fp, + u64 format, void *_fmt) +{ + int sample_width, sample_bytes; + u64 pcm_formats = 0; + + switch (fp->protocol) { + case UAC_VERSION_1: + default: { + struct uac_format_type_i_discrete_descriptor *fmt = _fmt; + if (format >= 64) + return 0; /* invalid format */ + sample_width = fmt->bBitResolution; + sample_bytes = fmt->bSubframeSize; + format = 1ULL << format; + break; + } + + case UAC_VERSION_2: { + struct uac_format_type_i_ext_descriptor *fmt = _fmt; + sample_width = fmt->bBitResolution; + sample_bytes = fmt->bSubslotSize; + + if (format & UAC2_FORMAT_TYPE_I_RAW_DATA) { + pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL; + /* flag potentially raw DSD capable altsettings */ + fp->dsd_raw = true; + } + + format <<= 1; + break; + } + case UAC_VERSION_3: { + struct uac3_as_header_descriptor *as = _fmt; + + sample_width = as->bBitResolution; + sample_bytes = as->bSubslotSize; + + if (format & UAC3_FORMAT_TYPE_I_RAW_DATA) + pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL; + + format <<= 1; + break; + } + } + + if ((pcm_formats == 0) && + (format == 0 || format == (1 << UAC_FORMAT_TYPE_I_UNDEFINED))) { + /* some devices don't define this correctly... */ + usb_audio_info(chip, "%u:%d : format type 0 is detected, processed as PCM\n", + fp->iface, fp->altsetting); + format = 1 << UAC_FORMAT_TYPE_I_PCM; + } + if (format & (1 << UAC_FORMAT_TYPE_I_PCM)) { + if (((chip->usb_id == USB_ID(0x0582, 0x0016)) || + /* Edirol SD-90 */ + (chip->usb_id == USB_ID(0x0582, 0x000c))) && + /* Roland SC-D70 */ + sample_width == 24 && sample_bytes == 2) + sample_bytes = 3; + else if (sample_width > sample_bytes * 8) { + usb_audio_info(chip, "%u:%d : sample bitwidth %d in over sample bytes %d\n", + fp->iface, fp->altsetting, + sample_width, sample_bytes); + } + /* check the format byte size */ + switch (sample_bytes) { + case 1: + pcm_formats |= SNDRV_PCM_FMTBIT_S8; + break; + case 2: + if (snd_usb_is_big_endian_format(chip, fp)) + pcm_formats |= SNDRV_PCM_FMTBIT_S16_BE; /* grrr, big endian!! */ + else + pcm_formats |= SNDRV_PCM_FMTBIT_S16_LE; + break; + case 3: + if (snd_usb_is_big_endian_format(chip, fp)) + pcm_formats |= SNDRV_PCM_FMTBIT_S24_3BE; /* grrr, big endian!! */ + else + pcm_formats |= SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + pcm_formats |= SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + usb_audio_info(chip, + "%u:%d : unsupported sample bitwidth %d in %d bytes\n", + fp->iface, fp->altsetting, + sample_width, sample_bytes); + break; + } + } + if (format & (1 << UAC_FORMAT_TYPE_I_PCM8)) { + /* Dallas DS4201 workaround: it advertises U8 format, but really + supports S8. */ + if (chip->usb_id == USB_ID(0x04fa, 0x4201)) + pcm_formats |= SNDRV_PCM_FMTBIT_S8; + else + pcm_formats |= SNDRV_PCM_FMTBIT_U8; + } + if (format & (1 << UAC_FORMAT_TYPE_I_IEEE_FLOAT)) { + pcm_formats |= SNDRV_PCM_FMTBIT_FLOAT_LE; + } + if (format & (1 << UAC_FORMAT_TYPE_I_ALAW)) { + pcm_formats |= SNDRV_PCM_FMTBIT_A_LAW; + } + if (format & (1 << UAC_FORMAT_TYPE_I_MULAW)) { + pcm_formats |= SNDRV_PCM_FMTBIT_MU_LAW; + } + if (format & ~0x3f) { + usb_audio_info(chip, + "%u:%d : unsupported format bits %#llx\n", + fp->iface, fp->altsetting, format); + } + + pcm_formats |= snd_usb_interface_dsd_format_quirks(chip, fp, sample_bytes); + + return pcm_formats; +} + + +/* + * parse the format descriptor and stores the possible sample rates + * on the audioformat table (audio class v1). + * + * @dev: usb device + * @fp: audioformat record + * @fmt: the format descriptor + * @offset: the start offset of descriptor pointing the rate type + * (7 for type I and II, 8 for type II) + */ +static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp, + unsigned char *fmt, int offset) +{ + int nr_rates = fmt[offset]; + + if (fmt[0] < offset + 1 + 3 * (nr_rates ? nr_rates : 2)) { + usb_audio_err(chip, + "%u:%d : invalid UAC_FORMAT_TYPE desc\n", + fp->iface, fp->altsetting); + return -EINVAL; + } + + if (nr_rates) { + /* + * build the rate table and bitmap flags + */ + int r, idx; + + fp->rate_table = kmalloc_array(nr_rates, sizeof(int), + GFP_KERNEL); + if (fp->rate_table == NULL) + return -ENOMEM; + + fp->nr_rates = 0; + fp->rate_min = fp->rate_max = 0; + for (r = 0, idx = offset + 1; r < nr_rates; r++, idx += 3) { + unsigned int rate = combine_triple(&fmt[idx]); + if (!rate) + continue; + /* C-Media CM6501 mislabels its 96 kHz altsetting */ + /* Terratec Aureon 7.1 USB C-Media 6206, too */ + /* Ozone Z90 USB C-Media, too */ + if (rate == 48000 && nr_rates == 1 && + (chip->usb_id == USB_ID(0x0d8c, 0x0201) || + chip->usb_id == USB_ID(0x0d8c, 0x0102) || + chip->usb_id == USB_ID(0x0d8c, 0x0078) || + chip->usb_id == USB_ID(0x0ccd, 0x00b1)) && + fp->altsetting == 5 && fp->maxpacksize == 392) + rate = 96000; + /* Creative VF0420/VF0470 Live Cams report 16 kHz instead of 8kHz */ + if (rate == 16000 && + (chip->usb_id == USB_ID(0x041e, 0x4064) || + chip->usb_id == USB_ID(0x041e, 0x4068))) + rate = 8000; + + fp->rate_table[fp->nr_rates] = rate; + if (!fp->rate_min || rate < fp->rate_min) + fp->rate_min = rate; + if (!fp->rate_max || rate > fp->rate_max) + fp->rate_max = rate; + fp->rates |= snd_pcm_rate_to_rate_bit(rate); + fp->nr_rates++; + } + if (!fp->nr_rates) { + hwc_debug("All rates were zero. Skipping format!\n"); + return -EINVAL; + } + } else { + /* continuous rates */ + fp->rates = SNDRV_PCM_RATE_CONTINUOUS; + fp->rate_min = combine_triple(&fmt[offset + 1]); + fp->rate_max = combine_triple(&fmt[offset + 4]); + } + return 0; +} + +/* + * Many Focusrite devices supports a limited set of sampling rates per + * altsetting. Maximum rate is exposed in the last 4 bytes of Format Type + * descriptor which has a non-standard bLength = 10. + */ +static bool focusrite_valid_sample_rate(struct snd_usb_audio *chip, + struct audioformat *fp, + unsigned int rate) +{ + struct usb_interface *iface; + struct usb_host_interface *alts; + unsigned char *fmt; + unsigned int max_rate; + + iface = usb_ifnum_to_if(chip->dev, fp->iface); + if (!iface) + return true; + + alts = &iface->altsetting[fp->altset_idx]; + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_FORMAT_TYPE); + if (!fmt) + return true; + + if (fmt[0] == 10) { /* bLength */ + max_rate = combine_quad(&fmt[6]); + + /* Validate max rate */ + if (max_rate != 48000 && + max_rate != 96000 && + max_rate != 192000 && + max_rate != 384000) { + + usb_audio_info(chip, + "%u:%d : unexpected max rate: %u\n", + fp->iface, fp->altsetting, max_rate); + + return true; + } + + return rate <= max_rate; + } + + return true; +} + +/* + * Helper function to walk the array of sample rate triplets reported by + * the device. The problem is that we need to parse whole array first to + * get to know how many sample rates we have to expect. + * Then fp->rate_table can be allocated and filled. + */ +static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip, + struct audioformat *fp, int nr_triplets, + const unsigned char *data) +{ + int i, nr_rates = 0; + + fp->rates = fp->rate_min = fp->rate_max = 0; + + for (i = 0; i < nr_triplets; i++) { + int min = combine_quad(&data[2 + 12 * i]); + int max = combine_quad(&data[6 + 12 * i]); + int res = combine_quad(&data[10 + 12 * i]); + unsigned int rate; + + if ((max < 0) || (min < 0) || (res < 0) || (max < min)) + continue; + + /* + * for ranges with res == 1, we announce a continuous sample + * rate range, and this function should return 0 for no further + * parsing. + */ + if (res == 1) { + fp->rate_min = min; + fp->rate_max = max; + fp->rates = SNDRV_PCM_RATE_CONTINUOUS; + return 0; + } + + for (rate = min; rate <= max; rate += res) { + /* Filter out invalid rates on Focusrite devices */ + if (USB_ID_VENDOR(chip->usb_id) == 0x1235 && + !focusrite_valid_sample_rate(chip, fp, rate)) + goto skip_rate; + + if (fp->rate_table) + fp->rate_table[nr_rates] = rate; + if (!fp->rate_min || rate < fp->rate_min) + fp->rate_min = rate; + if (!fp->rate_max || rate > fp->rate_max) + fp->rate_max = rate; + fp->rates |= snd_pcm_rate_to_rate_bit(rate); + + nr_rates++; + if (nr_rates >= MAX_NR_RATES) { + usb_audio_err(chip, "invalid uac2 rates\n"); + break; + } + +skip_rate: + /* avoid endless loop */ + if (res == 0) + break; + } + } + + return nr_rates; +} + +/* + * parse the format descriptor and stores the possible sample rates + * on the audioformat table (audio class v2 and v3). + */ +static int parse_audio_format_rates_v2v3(struct snd_usb_audio *chip, + struct audioformat *fp) +{ + struct usb_device *dev = chip->dev; + unsigned char tmp[2], *data; + int nr_triplets, data_size, ret = 0; + int clock = snd_usb_clock_find_source(chip, fp, false); + + if (clock < 0) { + dev_err(&dev->dev, + "%s(): unable to find clock source (clock %d)\n", + __func__, clock); + goto err; + } + + /* get the number of sample rates first by only fetching 2 bytes */ + ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_SAM_FREQ << 8, + snd_usb_ctrl_intf(chip) | (clock << 8), + tmp, sizeof(tmp)); + + if (ret < 0) { + dev_err(&dev->dev, + "%s(): unable to retrieve number of sample rates (clock %d)\n", + __func__, clock); + goto err; + } + + nr_triplets = (tmp[1] << 8) | tmp[0]; + data_size = 2 + 12 * nr_triplets; + data = kzalloc(data_size, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err; + } + + /* now get the full information */ + ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_SAM_FREQ << 8, + snd_usb_ctrl_intf(chip) | (clock << 8), + data, data_size); + + if (ret < 0) { + dev_err(&dev->dev, + "%s(): unable to retrieve sample rate range (clock %d)\n", + __func__, clock); + ret = -EINVAL; + goto err_free; + } + + /* Call the triplet parser, and make sure fp->rate_table is NULL. + * We just use the return value to know how many sample rates we + * will have to deal with. */ + kfree(fp->rate_table); + fp->rate_table = NULL; + fp->nr_rates = parse_uac2_sample_rate_range(chip, fp, nr_triplets, data); + + if (fp->nr_rates == 0) { + /* SNDRV_PCM_RATE_CONTINUOUS */ + ret = 0; + goto err_free; + } + + fp->rate_table = kmalloc_array(fp->nr_rates, sizeof(int), GFP_KERNEL); + if (!fp->rate_table) { + ret = -ENOMEM; + goto err_free; + } + + /* Call the triplet parser again, but this time, fp->rate_table is + * allocated, so the rates will be stored */ + parse_uac2_sample_rate_range(chip, fp, nr_triplets, data); + +err_free: + kfree(data); +err: + return ret; +} + +/* + * parse the format type I and III descriptors + */ +static int parse_audio_format_i(struct snd_usb_audio *chip, + struct audioformat *fp, u64 format, + void *_fmt) +{ + snd_pcm_format_t pcm_format; + unsigned int fmt_type; + int ret; + + switch (fp->protocol) { + default: + case UAC_VERSION_1: + case UAC_VERSION_2: { + struct uac_format_type_i_continuous_descriptor *fmt = _fmt; + + fmt_type = fmt->bFormatType; + break; + } + case UAC_VERSION_3: { + /* fp->fmt_type is already set in this case */ + fmt_type = fp->fmt_type; + break; + } + } + + if (fmt_type == UAC_FORMAT_TYPE_III) { + /* FIXME: the format type is really IECxxx + * but we give normal PCM format to get the existing + * apps working... + */ + switch (chip->usb_id) { + + case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + if (chip->setup == 0x00 && + fp->altsetting == 6) + pcm_format = SNDRV_PCM_FORMAT_S16_BE; + else + pcm_format = SNDRV_PCM_FORMAT_S16_LE; + break; + default: + pcm_format = SNDRV_PCM_FORMAT_S16_LE; + } + fp->formats = pcm_format_to_bits(pcm_format); + } else { + fp->formats = parse_audio_format_i_type(chip, fp, format, _fmt); + if (!fp->formats) + return -EINVAL; + } + + /* gather possible sample rates */ + /* audio class v1 reports possible sample rates as part of the + * proprietary class specific descriptor. + * audio class v2 uses class specific EP0 range requests for that. + */ + switch (fp->protocol) { + default: + case UAC_VERSION_1: { + struct uac_format_type_i_continuous_descriptor *fmt = _fmt; + + fp->channels = fmt->bNrChannels; + ret = parse_audio_format_rates_v1(chip, fp, (unsigned char *) fmt, 7); + break; + } + case UAC_VERSION_2: + case UAC_VERSION_3: { + /* fp->channels is already set in this case */ + ret = parse_audio_format_rates_v2v3(chip, fp); + break; + } + } + + if (fp->channels < 1) { + usb_audio_err(chip, + "%u:%d : invalid channels %d\n", + fp->iface, fp->altsetting, fp->channels); + return -EINVAL; + } + + return ret; +} + +/* + * parse the format type II descriptor + */ +static int parse_audio_format_ii(struct snd_usb_audio *chip, + struct audioformat *fp, + u64 format, void *_fmt) +{ + int brate, framesize, ret; + + switch (format) { + case UAC_FORMAT_TYPE_II_AC3: + /* FIXME: there is no AC3 format defined yet */ + // fp->formats = SNDRV_PCM_FMTBIT_AC3; + fp->formats = SNDRV_PCM_FMTBIT_U8; /* temporary hack to receive byte streams */ + break; + case UAC_FORMAT_TYPE_II_MPEG: + fp->formats = SNDRV_PCM_FMTBIT_MPEG; + break; + default: + usb_audio_info(chip, + "%u:%d : unknown format tag %#llx is detected. processed as MPEG.\n", + fp->iface, fp->altsetting, format); + fp->formats = SNDRV_PCM_FMTBIT_MPEG; + break; + } + + fp->channels = 1; + + switch (fp->protocol) { + default: + case UAC_VERSION_1: { + struct uac_format_type_ii_discrete_descriptor *fmt = _fmt; + brate = le16_to_cpu(fmt->wMaxBitRate); + framesize = le16_to_cpu(fmt->wSamplesPerFrame); + usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize); + fp->frame_size = framesize; + ret = parse_audio_format_rates_v1(chip, fp, _fmt, 8); /* fmt[8..] sample rates */ + break; + } + case UAC_VERSION_2: { + struct uac_format_type_ii_ext_descriptor *fmt = _fmt; + brate = le16_to_cpu(fmt->wMaxBitRate); + framesize = le16_to_cpu(fmt->wSamplesPerFrame); + usb_audio_info(chip, "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize); + fp->frame_size = framesize; + ret = parse_audio_format_rates_v2v3(chip, fp); + break; + } + } + + return ret; +} + +int snd_usb_parse_audio_format(struct snd_usb_audio *chip, + struct audioformat *fp, u64 format, + struct uac_format_type_i_continuous_descriptor *fmt, + int stream) +{ + int err; + + switch (fmt->bFormatType) { + case UAC_FORMAT_TYPE_I: + case UAC_FORMAT_TYPE_III: + err = parse_audio_format_i(chip, fp, format, fmt); + break; + case UAC_FORMAT_TYPE_II: + err = parse_audio_format_ii(chip, fp, format, fmt); + break; + default: + usb_audio_info(chip, + "%u:%d : format type %d is not supported yet\n", + fp->iface, fp->altsetting, + fmt->bFormatType); + return -ENOTSUPP; + } + fp->fmt_type = fmt->bFormatType; + if (err < 0) + return err; +#if 1 + /* FIXME: temporary hack for extigy/audigy 2 nx/zs */ + /* extigy apparently supports sample rates other than 48k + * but not in ordinary way. so we enable only 48k atm. + */ + if (chip->usb_id == USB_ID(0x041e, 0x3000) || + chip->usb_id == USB_ID(0x041e, 0x3020) || + chip->usb_id == USB_ID(0x041e, 0x3061)) { + if (fmt->bFormatType == UAC_FORMAT_TYPE_I && + fp->rates != SNDRV_PCM_RATE_48000 && + fp->rates != SNDRV_PCM_RATE_96000) + return -ENOTSUPP; + } +#endif + return 0; +} + +int snd_usb_parse_audio_format_v3(struct snd_usb_audio *chip, + struct audioformat *fp, + struct uac3_as_header_descriptor *as, + int stream) +{ + u64 format = le64_to_cpu(as->bmFormats); + int err; + + /* + * Type I format bits are D0..D6 + * This test works because type IV is not supported + */ + if (format & 0x7f) + fp->fmt_type = UAC_FORMAT_TYPE_I; + else + fp->fmt_type = UAC_FORMAT_TYPE_III; + + err = parse_audio_format_i(chip, fp, format, as); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/format.h b/sound/usb/format.h new file mode 100644 index 000000000..e70171892 --- /dev/null +++ b/sound/usb/format.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_FORMAT_H +#define __USBAUDIO_FORMAT_H + +int snd_usb_parse_audio_format(struct snd_usb_audio *chip, + struct audioformat *fp, u64 format, + struct uac_format_type_i_continuous_descriptor *fmt, + int stream); + +int snd_usb_parse_audio_format_v3(struct snd_usb_audio *chip, + struct audioformat *fp, + struct uac3_as_header_descriptor *as, + int stream); +#endif /* __USBAUDIO_FORMAT_H */ diff --git a/sound/usb/helper.c b/sound/usb/helper.c new file mode 100644 index 000000000..7712e2b84 --- /dev/null +++ b/sound/usb/helper.c @@ -0,0 +1,133 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#include "usbaudio.h" +#include "helper.h" +#include "quirks.h" + +/* + * combine bytes and get an integer value + */ +unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size) +{ + switch (size) { + case 1: return *bytes; + case 2: return combine_word(bytes); + case 3: return combine_triple(bytes); + case 4: return combine_quad(bytes); + default: return 0; + } +} + +/* + * parse descriptor buffer and return the pointer starting the given + * descriptor type. + */ +void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype) +{ + u8 *p, *end, *next; + + p = descstart; + end = p + desclen; + for (; p < end;) { + if (p[0] < 2) + return NULL; + next = p + p[0]; + if (next > end) + return NULL; + if (p[1] == dtype && (!after || (void *)p > after)) { + return p; + } + p = next; + } + return NULL; +} + +/* + * find a class-specified interface descriptor with the given subtype. + */ +void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype) +{ + unsigned char *p = after; + + while ((p = snd_usb_find_desc(buffer, buflen, p, + USB_DT_CS_INTERFACE)) != NULL) { + if (p[0] >= 3 && p[2] == dsubtype) + return p; + } + return NULL; +} + +/* + * Wrapper for usb_control_msg(). + * Allocates a temp buffer to prevent dmaing from/to the stack. + */ +int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request, + __u8 requesttype, __u16 value, __u16 index, void *data, + __u16 size) +{ + int err; + void *buf = NULL; + int timeout; + + if (size > 0) { + buf = kmemdup(data, size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + } + + if (requesttype & USB_DIR_IN) + timeout = USB_CTRL_GET_TIMEOUT; + else + timeout = USB_CTRL_SET_TIMEOUT; + + err = usb_control_msg(dev, pipe, request, requesttype, + value, index, buf, size, timeout); + + if (size > 0) { + memcpy(data, buf, size); + kfree(buf); + } + + snd_usb_ctl_msg_quirk(dev, pipe, request, requesttype, + value, index, data, size); + + return err; +} + +unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, + struct usb_host_interface *alts) +{ + switch (snd_usb_get_speed(chip->dev)) { + case USB_SPEED_HIGH: + case USB_SPEED_WIRELESS: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + if (get_endpoint(alts, 0)->bInterval >= 1 && + get_endpoint(alts, 0)->bInterval <= 4) + return get_endpoint(alts, 0)->bInterval - 1; + break; + default: + break; + } + return 0; +} + diff --git a/sound/usb/helper.h b/sound/usb/helper.h new file mode 100644 index 000000000..f5b4c6647 --- /dev/null +++ b/sound/usb/helper.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_HELPER_H +#define __USBAUDIO_HELPER_H + +unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size); + +void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype); +void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype); + +int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, + __u8 request, __u8 requesttype, __u16 value, __u16 index, + void *data, __u16 size); + +unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip, + struct usb_host_interface *alts); + +/* + * retrieve usb_interface descriptor from the host interface + * (conditional for compatibility with the older API) + */ +#define get_iface_desc(iface) (&(iface)->desc) +#define get_endpoint(alt,ep) (&(alt)->endpoint[ep].desc) +#define get_ep_desc(ep) (&(ep)->desc) +#define get_cfg_desc(cfg) (&(cfg)->desc) + +#define snd_usb_get_speed(dev) ((dev)->speed) + +static inline int snd_usb_ctrl_intf(struct snd_usb_audio *chip) +{ + return get_iface_desc(chip->ctrl_intf)->bInterfaceNumber; +} + +/* in validate.c */ +bool snd_usb_validate_audio_desc(void *p, int protocol); +bool snd_usb_validate_midi_desc(void *p); + +#endif /* __USBAUDIO_HELPER_H */ diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile new file mode 100644 index 000000000..463b136d1 --- /dev/null +++ b/sound/usb/hiface/Makefile @@ -0,0 +1,2 @@ +snd-usb-hiface-objs := chip.o pcm.o +obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c new file mode 100644 index 000000000..2670d646b --- /dev/null +++ b/sound/usb/hiface/chip.c @@ -0,0 +1,297 @@ +/* + * Linux driver for M2Tech hiFace compatible devices + * + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V. + * + * Authors: Michael Trimarchi + * Antonio Ospite + * + * The driver is based on the work done in TerraTec DMX 6Fire USB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include + +#include "chip.h" +#include "pcm.h" + +MODULE_AUTHOR("Michael Trimarchi "); +MODULE_AUTHOR("Antonio Ospite "); +MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver"); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{{M2Tech,Young}," + "{M2Tech,hiFace}," + "{M2Tech,North Star}," + "{M2Tech,W4S Young}," + "{M2Tech,Corrson}," + "{M2Tech,AUDIA}," + "{M2Tech,SL Audio}," + "{M2Tech,Empirical}," + "{M2Tech,Rockna}," + "{M2Tech,Pathos}," + "{M2Tech,Metronome}," + "{M2Tech,CAD}," + "{M2Tech,Audio Esclusive}," + "{M2Tech,Rotel}," + "{M2Tech,Eeaudio}," + "{The Chord Company,CHORD}," + "{AVA Group A/S,Vitus}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ + +#define DRIVER_NAME "snd-usb-hiface" +#define CARD_NAME "hiFace" + +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."); + +static DEFINE_MUTEX(register_mutex); + +struct hiface_vendor_quirk { + const char *device_name; + u8 extra_freq; +}; + +static int hiface_chip_create(struct usb_interface *intf, + struct usb_device *device, int idx, + const struct hiface_vendor_quirk *quirk, + struct hiface_chip **rchip) +{ + struct snd_card *card = NULL; + struct hiface_chip *chip; + int ret; + int len; + + *rchip = NULL; + + /* if we are here, card can be registered in alsa. */ + ret = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE, + sizeof(*chip), &card); + if (ret < 0) { + dev_err(&device->dev, "cannot create alsa card.\n"); + return ret; + } + + strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + + if (quirk && quirk->device_name) + strlcpy(card->shortname, quirk->device_name, sizeof(card->shortname)); + else + strlcpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname)); + + strlcat(card->longname, card->shortname, sizeof(card->longname)); + len = strlcat(card->longname, " at ", sizeof(card->longname)); + if (len < sizeof(card->longname)) + usb_make_path(device, card->longname + len, + sizeof(card->longname) - len); + + chip = card->private_data; + chip->dev = device; + chip->card = card; + + *rchip = chip; + return 0; +} + +static int hiface_chip_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info; + int ret; + int i; + struct hiface_chip *chip; + struct usb_device *device = interface_to_usbdev(intf); + + ret = usb_set_interface(device, 0, 0); + if (ret != 0) { + dev_err(&device->dev, "can't set first interface for " CARD_NAME " device.\n"); + return -EIO; + } + + /* check whether the card is already registered */ + chip = NULL; + mutex_lock(®ister_mutex); + + for (i = 0; i < SNDRV_CARDS; i++) + if (enable[i]) + break; + + if (i >= SNDRV_CARDS) { + dev_err(&device->dev, "no available " CARD_NAME " audio device\n"); + ret = -ENODEV; + goto err; + } + + ret = hiface_chip_create(intf, device, i, quirk, &chip); + if (ret < 0) + goto err; + + ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0); + if (ret < 0) + goto err_chip_destroy; + + ret = snd_card_register(chip->card); + if (ret < 0) { + dev_err(&device->dev, "cannot register " CARD_NAME " card\n"); + goto err_chip_destroy; + } + + mutex_unlock(®ister_mutex); + + usb_set_intfdata(intf, chip); + return 0; + +err_chip_destroy: + snd_card_free(chip->card); +err: + mutex_unlock(®ister_mutex); + return ret; +} + +static void hiface_chip_disconnect(struct usb_interface *intf) +{ + struct hiface_chip *chip; + struct snd_card *card; + + chip = usb_get_intfdata(intf); + if (!chip) + return; + + card = chip->card; + + /* Make sure that the userspace cannot create new request */ + snd_card_disconnect(card); + + hiface_pcm_abort(chip); + snd_card_free_when_closed(card); +} + +static const struct usb_device_id device_table[] = { + { + USB_DEVICE(0x04b4, 0x0384), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Young", + .extra_freq = 1, + } + }, + { + USB_DEVICE(0x04b4, 0x930b), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "hiFace", + } + }, + { + USB_DEVICE(0x04b4, 0x931b), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "North Star", + } + }, + { + USB_DEVICE(0x04b4, 0x931c), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "W4S Young", + } + }, + { + USB_DEVICE(0x04b4, 0x931d), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Corrson", + } + }, + { + USB_DEVICE(0x04b4, 0x931e), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "AUDIA", + } + }, + { + USB_DEVICE(0x04b4, 0x931f), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "SL Audio", + } + }, + { + USB_DEVICE(0x04b4, 0x9320), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Empirical", + } + }, + { + USB_DEVICE(0x04b4, 0x9321), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Rockna", + } + }, + { + USB_DEVICE(0x249c, 0x9001), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Pathos", + } + }, + { + USB_DEVICE(0x249c, 0x9002), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Metronome", + } + }, + { + USB_DEVICE(0x249c, 0x9006), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "CAD", + } + }, + { + USB_DEVICE(0x249c, 0x9008), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Audio Esclusive", + } + }, + { + USB_DEVICE(0x249c, 0x931c), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Rotel", + } + }, + { + USB_DEVICE(0x249c, 0x932c), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Eeaudio", + } + }, + { + USB_DEVICE(0x245f, 0x931c), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "CHORD", + } + }, + { + USB_DEVICE(0x25c6, 0x9002), + .driver_info = (unsigned long)&(const struct hiface_vendor_quirk) { + .device_name = "Vitus", + } + }, + {} +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +static struct usb_driver hiface_usb_driver = { + .name = DRIVER_NAME, + .probe = hiface_chip_probe, + .disconnect = hiface_chip_disconnect, + .id_table = device_table, +}; + +module_usb_driver(hiface_usb_driver); diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h new file mode 100644 index 000000000..189a1371b --- /dev/null +++ b/sound/usb/hiface/chip.h @@ -0,0 +1,30 @@ +/* + * Linux driver for M2Tech hiFace compatible devices + * + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V. + * + * Authors: Michael Trimarchi + * Antonio Ospite + * + * The driver is based on the work done in TerraTec DMX 6Fire USB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef HIFACE_CHIP_H +#define HIFACE_CHIP_H + +#include +#include + +struct pcm_runtime; + +struct hiface_chip { + struct usb_device *dev; + struct snd_card *card; + struct pcm_runtime *pcm; +}; +#endif /* HIFACE_CHIP_H */ diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c new file mode 100644 index 000000000..a197fc3b9 --- /dev/null +++ b/sound/usb/hiface/pcm.c @@ -0,0 +1,632 @@ +/* + * Linux driver for M2Tech hiFace compatible devices + * + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V. + * + * Authors: Michael Trimarchi + * Antonio Ospite + * + * The driver is based on the work done in TerraTec DMX 6Fire USB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include + +#include "pcm.h" +#include "chip.h" + +#define OUT_EP 0x2 +#define PCM_N_URBS 8 +#define PCM_PACKET_SIZE 4096 +#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE) + +struct pcm_urb { + struct hiface_chip *chip; + + struct urb instance; + struct usb_anchor submitted; + u8 *buffer; +}; + +struct pcm_substream { + spinlock_t lock; + struct snd_pcm_substream *instance; + + bool active; + snd_pcm_uframes_t dma_off; /* current position in alsa dma_area */ + snd_pcm_uframes_t period_off; /* current position in current period */ +}; + +enum { /* pcm streaming states */ + STREAM_DISABLED, /* no pcm streaming */ + STREAM_STARTING, /* pcm streaming requested, waiting to become ready */ + STREAM_RUNNING, /* pcm streaming running */ + STREAM_STOPPING +}; + +struct pcm_runtime { + struct hiface_chip *chip; + struct snd_pcm *instance; + + struct pcm_substream playback; + bool panic; /* if set driver won't do anymore pcm on device */ + + struct pcm_urb out_urbs[PCM_N_URBS]; + + struct mutex stream_mutex; + u8 stream_state; /* one of STREAM_XXX */ + u8 extra_freq; + wait_queue_head_t stream_wait_queue; + bool stream_wait_cond; +}; + +static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000, + 352800, 384000 }; +static const struct snd_pcm_hw_constraint_list constraints_extra_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static const struct snd_pcm_hardware pcm_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH, + + .formats = SNDRV_PCM_FMTBIT_S32_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, /* changes in hiface_pcm_open to support extra rates */ + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = PCM_BUFFER_SIZE, + .period_bytes_min = PCM_PACKET_SIZE, + .period_bytes_max = PCM_BUFFER_SIZE, + .periods_min = 2, + .periods_max = 1024 +}; + +/* message values used to change the sample rate */ +#define HIFACE_SET_RATE_REQUEST 0xb0 + +#define HIFACE_RATE_44100 0x43 +#define HIFACE_RATE_48000 0x4b +#define HIFACE_RATE_88200 0x42 +#define HIFACE_RATE_96000 0x4a +#define HIFACE_RATE_176400 0x40 +#define HIFACE_RATE_192000 0x48 +#define HIFACE_RATE_352800 0x58 +#define HIFACE_RATE_384000 0x68 + +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate) +{ + struct usb_device *device = rt->chip->dev; + u16 rate_value; + int ret; + + /* We are already sure that the rate is supported here thanks to + * ALSA constraints + */ + switch (rate) { + case 44100: + rate_value = HIFACE_RATE_44100; + break; + case 48000: + rate_value = HIFACE_RATE_48000; + break; + case 88200: + rate_value = HIFACE_RATE_88200; + break; + case 96000: + rate_value = HIFACE_RATE_96000; + break; + case 176400: + rate_value = HIFACE_RATE_176400; + break; + case 192000: + rate_value = HIFACE_RATE_192000; + break; + case 352800: + rate_value = HIFACE_RATE_352800; + break; + case 384000: + rate_value = HIFACE_RATE_384000; + break; + default: + dev_err(&device->dev, "Unsupported rate %d\n", rate); + return -EINVAL; + } + + /* + * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000) + * 43 b0 43 00 00 00 00 00 + * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000) + * 43 b0 4b 00 00 00 00 00 + * This control message doesn't have any ack from the + * other side + */ + ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), + HIFACE_SET_RATE_REQUEST, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + rate_value, 0, NULL, 0, 100); + if (ret < 0) { + dev_err(&device->dev, "Error setting samplerate %d.\n", rate); + return ret; + } + + return 0; +} + +static struct pcm_substream *hiface_pcm_get_substream(struct snd_pcm_substream + *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + struct device *device = &rt->chip->dev->dev; + + if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + return &rt->playback; + + dev_err(device, "Error getting pcm substream slot.\n"); + return NULL; +} + +/* call with stream_mutex locked */ +static void hiface_pcm_stream_stop(struct pcm_runtime *rt) +{ + int i, time; + + if (rt->stream_state != STREAM_DISABLED) { + rt->stream_state = STREAM_STOPPING; + + for (i = 0; i < PCM_N_URBS; i++) { + time = usb_wait_anchor_empty_timeout( + &rt->out_urbs[i].submitted, 100); + if (!time) + usb_kill_anchored_urbs( + &rt->out_urbs[i].submitted); + usb_kill_urb(&rt->out_urbs[i].instance); + } + + rt->stream_state = STREAM_DISABLED; + } +} + +/* call with stream_mutex locked */ +static int hiface_pcm_stream_start(struct pcm_runtime *rt) +{ + int ret = 0; + int i; + + if (rt->stream_state == STREAM_DISABLED) { + + /* reset panic state when starting a new stream */ + rt->panic = false; + + /* submit our out urbs zero init */ + rt->stream_state = STREAM_STARTING; + for (i = 0; i < PCM_N_URBS; i++) { + memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE); + usb_anchor_urb(&rt->out_urbs[i].instance, + &rt->out_urbs[i].submitted); + ret = usb_submit_urb(&rt->out_urbs[i].instance, + GFP_ATOMIC); + if (ret) { + hiface_pcm_stream_stop(rt); + return ret; + } + } + + /* wait for first out urb to return (sent in in urb handler) */ + wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond, + HZ); + if (rt->stream_wait_cond) { + struct device *device = &rt->chip->dev->dev; + dev_dbg(device, "%s: Stream is running wakeup event\n", + __func__); + rt->stream_state = STREAM_RUNNING; + } else { + hiface_pcm_stream_stop(rt); + return -EIO; + } + } + return ret; +} + +/* The hardware wants word-swapped 32-bit values */ +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n) +{ + unsigned int i; + + for (i = 0; i < n / 4; i++) + ((u32 *)dest)[i] = swahw32(((u32 *)src)[i]); +} + +/* call with substream locked */ +/* returns true if a period elapsed */ +static bool hiface_pcm_playback(struct pcm_substream *sub, struct pcm_urb *urb) +{ + struct snd_pcm_runtime *alsa_rt = sub->instance->runtime; + struct device *device = &urb->chip->dev->dev; + u8 *source; + unsigned int pcm_buffer_size; + + WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE); + + pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance); + + if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) { + dev_dbg(device, "%s: (1) buffer_size %#x dma_offset %#x\n", __func__, + (unsigned int) pcm_buffer_size, + (unsigned int) sub->dma_off); + + source = alsa_rt->dma_area + sub->dma_off; + memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE); + } else { + /* wrap around at end of ring buffer */ + unsigned int len; + + dev_dbg(device, "%s: (2) buffer_size %#x dma_offset %#x\n", __func__, + (unsigned int) pcm_buffer_size, + (unsigned int) sub->dma_off); + + len = pcm_buffer_size - sub->dma_off; + + source = alsa_rt->dma_area + sub->dma_off; + memcpy_swahw32(urb->buffer, source, len); + + source = alsa_rt->dma_area; + memcpy_swahw32(urb->buffer + len, source, + PCM_PACKET_SIZE - len); + } + sub->dma_off += PCM_PACKET_SIZE; + if (sub->dma_off >= pcm_buffer_size) + sub->dma_off -= pcm_buffer_size; + + sub->period_off += PCM_PACKET_SIZE; + if (sub->period_off >= alsa_rt->period_size) { + sub->period_off %= alsa_rt->period_size; + return true; + } + return false; +} + +static void hiface_pcm_out_urb_handler(struct urb *usb_urb) +{ + struct pcm_urb *out_urb = usb_urb->context; + struct pcm_runtime *rt = out_urb->chip->pcm; + struct pcm_substream *sub; + bool do_period_elapsed = false; + unsigned long flags; + int ret; + + if (rt->panic || rt->stream_state == STREAM_STOPPING) + return; + + if (unlikely(usb_urb->status == -ENOENT || /* unlinked */ + usb_urb->status == -ENODEV || /* device removed */ + usb_urb->status == -ECONNRESET || /* unlinked */ + usb_urb->status == -ESHUTDOWN)) { /* device disabled */ + goto out_fail; + } + + if (rt->stream_state == STREAM_STARTING) { + rt->stream_wait_cond = true; + wake_up(&rt->stream_wait_queue); + } + + /* now send our playback data (if a free out urb was found) */ + sub = &rt->playback; + spin_lock_irqsave(&sub->lock, flags); + if (sub->active) + do_period_elapsed = hiface_pcm_playback(sub, out_urb); + else + memset(out_urb->buffer, 0, PCM_PACKET_SIZE); + + spin_unlock_irqrestore(&sub->lock, flags); + + if (do_period_elapsed) + snd_pcm_period_elapsed(sub->instance); + + ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC); + if (ret < 0) + goto out_fail; + + return; + +out_fail: + rt->panic = true; +} + +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + struct pcm_substream *sub = NULL; + struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; + int ret; + + if (rt->panic) + return -EPIPE; + + mutex_lock(&rt->stream_mutex); + alsa_rt->hw = pcm_hw; + + if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) + sub = &rt->playback; + + if (!sub) { + struct device *device = &rt->chip->dev->dev; + mutex_unlock(&rt->stream_mutex); + dev_err(device, "Invalid stream type\n"); + return -EINVAL; + } + + if (rt->extra_freq) { + alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT; + alsa_rt->hw.rate_max = 384000; + + /* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */ + ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_extra_rates); + if (ret < 0) { + mutex_unlock(&rt->stream_mutex); + return ret; + } + } + + sub->instance = alsa_sub; + sub->active = false; + mutex_unlock(&rt->stream_mutex); + return 0; +} + +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub); + unsigned long flags; + + if (rt->panic) + return 0; + + mutex_lock(&rt->stream_mutex); + if (sub) { + hiface_pcm_stream_stop(rt); + + /* deactivate substream */ + spin_lock_irqsave(&sub->lock, flags); + sub->instance = NULL; + sub->active = false; + spin_unlock_irqrestore(&sub->lock, flags); + + } + mutex_unlock(&rt->stream_mutex); + return 0; +} + +static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub, + params_buffer_bytes(hw_params)); +} + +static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub) +{ + return snd_pcm_lib_free_vmalloc_buffer(alsa_sub); +} + +static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub) +{ + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub); + struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; + int ret; + + if (rt->panic) + return -EPIPE; + if (!sub) + return -ENODEV; + + mutex_lock(&rt->stream_mutex); + + hiface_pcm_stream_stop(rt); + + sub->dma_off = 0; + sub->period_off = 0; + + if (rt->stream_state == STREAM_DISABLED) { + + ret = hiface_pcm_set_rate(rt, alsa_rt->rate); + if (ret) { + mutex_unlock(&rt->stream_mutex); + return ret; + } + ret = hiface_pcm_stream_start(rt); + if (ret) { + mutex_unlock(&rt->stream_mutex); + return ret; + } + } + mutex_unlock(&rt->stream_mutex); + return 0; +} + +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd) +{ + struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub); + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + + if (rt->panic) + return -EPIPE; + if (!sub) + return -ENODEV; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irq(&sub->lock); + sub->active = true; + spin_unlock_irq(&sub->lock); + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irq(&sub->lock); + sub->active = false; + spin_unlock_irq(&sub->lock); + return 0; + + default: + return -EINVAL; + } +} + +static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub) +{ + struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub); + struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); + unsigned long flags; + snd_pcm_uframes_t dma_offset; + + if (rt->panic || !sub) + return SNDRV_PCM_POS_XRUN; + + spin_lock_irqsave(&sub->lock, flags); + dma_offset = sub->dma_off; + spin_unlock_irqrestore(&sub->lock, flags); + return bytes_to_frames(alsa_sub->runtime, dma_offset); +} + +static const struct snd_pcm_ops pcm_ops = { + .open = hiface_pcm_open, + .close = hiface_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = hiface_pcm_hw_params, + .hw_free = hiface_pcm_hw_free, + .prepare = hiface_pcm_prepare, + .trigger = hiface_pcm_trigger, + .pointer = hiface_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static int hiface_pcm_init_urb(struct pcm_urb *urb, + struct hiface_chip *chip, + unsigned int ep, + void (*handler)(struct urb *)) +{ + urb->chip = chip; + usb_init_urb(&urb->instance); + + urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL); + if (!urb->buffer) + return -ENOMEM; + + usb_fill_bulk_urb(&urb->instance, chip->dev, + usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer, + PCM_PACKET_SIZE, handler, urb); + if (usb_urb_ep_type_check(&urb->instance)) + return -EINVAL; + init_usb_anchor(&urb->submitted); + + return 0; +} + +void hiface_pcm_abort(struct hiface_chip *chip) +{ + struct pcm_runtime *rt = chip->pcm; + + if (rt) { + rt->panic = true; + + mutex_lock(&rt->stream_mutex); + hiface_pcm_stream_stop(rt); + mutex_unlock(&rt->stream_mutex); + } +} + +static void hiface_pcm_destroy(struct hiface_chip *chip) +{ + struct pcm_runtime *rt = chip->pcm; + int i; + + for (i = 0; i < PCM_N_URBS; i++) + kfree(rt->out_urbs[i].buffer); + + kfree(chip->pcm); + chip->pcm = NULL; +} + +static void hiface_pcm_free(struct snd_pcm *pcm) +{ + struct pcm_runtime *rt = pcm->private_data; + + if (rt) + hiface_pcm_destroy(rt->chip); +} + +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) +{ + int i; + int ret; + struct snd_pcm *pcm; + struct pcm_runtime *rt; + + rt = kzalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) + return -ENOMEM; + + rt->chip = chip; + rt->stream_state = STREAM_DISABLED; + if (extra_freq) + rt->extra_freq = 1; + + init_waitqueue_head(&rt->stream_wait_queue); + mutex_init(&rt->stream_mutex); + spin_lock_init(&rt->playback.lock); + + for (i = 0; i < PCM_N_URBS; i++) { + ret = hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP, + hiface_pcm_out_urb_handler); + if (ret < 0) + goto error; + } + + ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm); + if (ret < 0) { + dev_err(&chip->dev->dev, "Cannot create pcm instance\n"); + goto error; + } + + pcm->private_data = rt; + pcm->private_free = hiface_pcm_free; + + strlcpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name)); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops); + + rt->instance = pcm; + + chip->pcm = rt; + return 0; + +error: + for (i = 0; i < PCM_N_URBS; i++) + kfree(rt->out_urbs[i].buffer); + kfree(rt); + return ret; +} diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h new file mode 100644 index 000000000..77edd7c12 --- /dev/null +++ b/sound/usb/hiface/pcm.h @@ -0,0 +1,24 @@ +/* + * Linux driver for M2Tech hiFace compatible devices + * + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V. + * + * Authors: Michael Trimarchi + * Antonio Ospite + * + * The driver is based on the work done in TerraTec DMX 6Fire USB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef HIFACE_PCM_H +#define HIFACE_PCM_H + +struct hiface_chip; + +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq); +void hiface_pcm_abort(struct hiface_chip *chip); +#endif /* HIFACE_PCM_H */ diff --git a/sound/usb/line6/Kconfig b/sound/usb/line6/Kconfig new file mode 100644 index 000000000..39b400392 --- /dev/null +++ b/sound/usb/line6/Kconfig @@ -0,0 +1,43 @@ +config SND_USB_LINE6 + tristate + select SND_RAWMIDI + select SND_PCM + select SND_HWDEP + +config SND_USB_POD + tristate "Line 6 POD USB support" + select SND_USB_LINE6 + help + This is a driver for PODxt and other similar devices, + supporting the following features: + * Reading/writing individual parameters + * Reading/writing complete channel, effects setup, and amp + setup data + * Channel switching + * Virtual MIDI interface + * Tuner access + * Playback/capture/mixer device for any ALSA-compatible PCM + audio application + * Signal routing (record clean/processed guitar signal, + re-amping) + +config SND_USB_PODHD + tristate "Line 6 POD X3/HD300/400/500 USB support" + select SND_USB_LINE6 + help + This is a driver for POD X3, HD300, 400 and 500 devices. + +config SND_USB_TONEPORT + tristate "TonePort GX, UX1 and UX2 USB support" + select SND_USB_LINE6 + select NEW_LEDS + select LEDS_CLASS + help + This is a driver for TonePort GX, UX1 and UX2 devices. + +config SND_USB_VARIAX + tristate "Variax Workbench USB support" + select SND_USB_LINE6 + help + This is a driver for Variax Workbench device. + diff --git a/sound/usb/line6/Makefile b/sound/usb/line6/Makefile new file mode 100644 index 000000000..4ba98eb32 --- /dev/null +++ b/sound/usb/line6/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-usb-line6-y := \ + capture.o \ + driver.o \ + midi.o \ + midibuf.o \ + pcm.o \ + playback.o + +snd-usb-pod-y := pod.o +snd-usb-podhd-y := podhd.o +snd-usb-toneport-y := toneport.o +snd-usb-variax-y := variax.o + +obj-$(CONFIG_SND_USB_LINE6) += snd-usb-line6.o +obj-$(CONFIG_SND_USB_POD) += snd-usb-pod.o +obj-$(CONFIG_SND_USB_PODHD) += snd-usb-podhd.o +obj-$(CONFIG_SND_USB_TONEPORT) += snd-usb-toneport.o +obj-$(CONFIG_SND_USB_VARIAX) += snd-usb-variax.o diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c new file mode 100644 index 000000000..8efd9f00c --- /dev/null +++ b/sound/usb/line6/capture.c @@ -0,0 +1,299 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include + +#include "capture.h" +#include "driver.h" +#include "pcm.h" + +/* + Find a free URB and submit it. + must be called in line6pcm->in.lock context +*/ +static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) +{ + int index; + int i, urb_size; + int ret; + struct urb *urb_in; + + index = find_first_zero_bit(&line6pcm->in.active_urbs, + line6pcm->line6->iso_buffers); + + if (index < 0 || index >= line6pcm->line6->iso_buffers) { + dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); + return -EINVAL; + } + + urb_in = line6pcm->in.urbs[index]; + urb_size = 0; + + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + struct usb_iso_packet_descriptor *fin = + &urb_in->iso_frame_desc[i]; + fin->offset = urb_size; + fin->length = line6pcm->max_packet_size_in; + urb_size += line6pcm->max_packet_size_in; + } + + urb_in->transfer_buffer = + line6pcm->in.buffer + + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_in; + urb_in->transfer_buffer_length = urb_size; + urb_in->context = line6pcm; + + ret = usb_submit_urb(urb_in, GFP_ATOMIC); + + if (ret == 0) + set_bit(index, &line6pcm->in.active_urbs); + else + dev_err(line6pcm->line6->ifcdev, + "URB in #%d submission failed (%d)\n", index, ret); + + return 0; +} + +/* + Submit all currently available capture URBs. + must be called in line6pcm->in.lock context +*/ +int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm) +{ + int ret = 0, i; + + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { + ret = submit_audio_in_urb(line6pcm); + if (ret < 0) + break; + } + + return ret; +} + +/* + Copy data into ALSA capture buffer. +*/ +void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) +{ + struct snd_pcm_substream *substream = + get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); + struct snd_pcm_runtime *runtime = substream->runtime; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max; + int frames = fsize / bytes_per_frame; + + if (runtime == NULL) + return; + + if (line6pcm->in.pos_done + frames > runtime->buffer_size) { + /* + The transferred area goes over buffer boundary, + copy two separate chunks. + */ + int len; + + len = runtime->buffer_size - line6pcm->in.pos_done; + + if (len > 0) { + memcpy(runtime->dma_area + + line6pcm->in.pos_done * bytes_per_frame, fbuf, + len * bytes_per_frame); + memcpy(runtime->dma_area, fbuf + len * bytes_per_frame, + (frames - len) * bytes_per_frame); + } else { + /* this is somewhat paranoid */ + dev_err(line6pcm->line6->ifcdev, + "driver bug: len = %d\n", len); + } + } else { + /* copy single chunk */ + memcpy(runtime->dma_area + + line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize); + } + + line6pcm->in.pos_done += frames; + if (line6pcm->in.pos_done >= runtime->buffer_size) + line6pcm->in.pos_done -= runtime->buffer_size; +} + +void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length) +{ + struct snd_pcm_substream *substream = + get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); + + line6pcm->in.bytes += length; + if (line6pcm->in.bytes >= line6pcm->in.period) { + line6pcm->in.bytes %= line6pcm->in.period; + spin_unlock(&line6pcm->in.lock); + snd_pcm_period_elapsed(substream); + spin_lock(&line6pcm->in.lock); + } +} + +/* + * Callback for completed capture URB. + */ +static void audio_in_callback(struct urb *urb) +{ + int i, index, length = 0, shutdown = 0; + unsigned long flags; + + struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; + + line6pcm->in.last_frame = urb->start_frame; + + /* find index of URB */ + for (index = 0; index < line6pcm->line6->iso_buffers; ++index) + if (urb == line6pcm->in.urbs[index]) + break; + + spin_lock_irqsave(&line6pcm->in.lock, flags); + + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + char *fbuf; + int fsize; + struct usb_iso_packet_descriptor *fin = &urb->iso_frame_desc[i]; + + if (fin->status == -EXDEV) { + shutdown = 1; + break; + } + + fbuf = urb->transfer_buffer + fin->offset; + fsize = fin->actual_length; + + if (fsize > line6pcm->max_packet_size_in) { + dev_err(line6pcm->line6->ifcdev, + "driver and/or device bug: packet too large (%d > %d)\n", + fsize, line6pcm->max_packet_size_in); + } + + length += fsize; + + BUILD_BUG_ON_MSG(LINE6_ISO_PACKETS != 1, + "The following code assumes LINE6_ISO_PACKETS == 1"); + /* TODO: + * Also, if iso_buffers != 2, the prev frame is almost random at + * playback side. + * This needs to be redesigned. It should be "stable", but we may + * experience sync problems on such high-speed configs. + */ + + line6pcm->prev_fbuf = fbuf; + line6pcm->prev_fsize = fsize / + (line6pcm->properties->bytes_per_channel * + line6pcm->properties->capture_hw.channels_max); + + if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && + test_bit(LINE6_STREAM_PCM, &line6pcm->in.running) && + fsize > 0) + line6_capture_copy(line6pcm, fbuf, fsize); + } + + clear_bit(index, &line6pcm->in.active_urbs); + + if (test_and_clear_bit(index, &line6pcm->in.unlink_urbs)) + shutdown = 1; + + if (!shutdown) { + submit_audio_in_urb(line6pcm); + + if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && + test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) + line6_capture_check_period(line6pcm, length); + } + + spin_unlock_irqrestore(&line6pcm->in.lock, flags); +} + +/* open capture callback */ +static int snd_line6_capture_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + err = snd_pcm_hw_constraint_ratdens(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &line6pcm->properties->rates); + if (err < 0) + return err; + + line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false); + + runtime->hw = line6pcm->properties->capture_hw; + return 0; +} + +/* close capture callback */ +static int snd_line6_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + line6_pcm_release(line6pcm, LINE6_STREAM_CAPTURE_HELPER); + return 0; +} + +/* capture operators */ +const struct snd_pcm_ops snd_line6_capture_ops = { + .open = snd_line6_capture_open, + .close = snd_line6_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_line6_hw_params, + .hw_free = snd_line6_hw_free, + .prepare = snd_line6_prepare, + .trigger = snd_line6_trigger, + .pointer = snd_line6_pointer, +}; + +int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + struct usb_line6 *line6 = line6pcm->line6; + int i; + + line6pcm->in.urbs = kcalloc(line6->iso_buffers, sizeof(struct urb *), + GFP_KERNEL); + if (line6pcm->in.urbs == NULL) + return -ENOMEM; + + /* create audio URBs and fill in constant values: */ + for (i = 0; i < line6->iso_buffers; ++i) { + struct urb *urb; + + /* URB for audio in: */ + urb = line6pcm->in.urbs[i] = + usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL); + + if (urb == NULL) + return -ENOMEM; + + urb->dev = line6->usbdev; + urb->pipe = + usb_rcvisocpipe(line6->usbdev, + line6->properties->ep_audio_r & + USB_ENDPOINT_NUMBER_MASK); + urb->transfer_flags = URB_ISO_ASAP; + urb->start_frame = -1; + urb->number_of_packets = LINE6_ISO_PACKETS; + urb->interval = LINE6_ISO_INTERVAL; + urb->error_count = 0; + urb->complete = audio_in_callback; + if (usb_urb_ep_type_check(urb)) + return -EINVAL; + } + + return 0; +} diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h new file mode 100644 index 000000000..b67ccc39f --- /dev/null +++ b/sound/usb/line6/capture.h @@ -0,0 +1,29 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef CAPTURE_H +#define CAPTURE_H + +#include + +#include "driver.h" +#include "pcm.h" + +extern const struct snd_pcm_ops snd_line6_capture_ops; + +extern void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, + int fsize); +extern void line6_capture_check_period(struct snd_line6_pcm *line6pcm, + int length); +extern int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm); +extern int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm); + +#endif diff --git a/sound/usb/line6/driver.c b/sound/usb/line6/driver.c new file mode 100644 index 000000000..67d74218d --- /dev/null +++ b/sound/usb/line6/driver.c @@ -0,0 +1,907 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "capture.h" +#include "driver.h" +#include "midi.h" +#include "playback.h" + +#define DRIVER_AUTHOR "Markus Grabner " +#define DRIVER_DESC "Line 6 USB Driver" + +/* + This is Line 6's MIDI manufacturer ID. +*/ +const unsigned char line6_midi_id[3] = { + 0x00, 0x01, 0x0c +}; +EXPORT_SYMBOL_GPL(line6_midi_id); + +/* + Code to request version of POD, Variax interface + (and maybe other devices). +*/ +static const char line6_request_version[] = { + 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 +}; + +/* + Class for asynchronous messages. +*/ +struct message { + struct usb_line6 *line6; + const char *buffer; + int size; + int done; +}; + +/* + Forward declarations. +*/ +static void line6_data_received(struct urb *urb); +static int line6_send_raw_message_async_part(struct message *msg, + struct urb *urb); + +/* + Start to listen on endpoint. +*/ +static int line6_start_listen(struct usb_line6 *line6) +{ + int err; + + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(line6->urb_listen, line6->usbdev, + usb_rcvintpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6, line6->interval); + } else { + usb_fill_bulk_urb(line6->urb_listen, line6->usbdev, + usb_rcvbulkpipe(line6->usbdev, line6->properties->ep_ctrl_r), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6); + } + + /* sanity checks of EP before actually submitting */ + if (usb_urb_ep_type_check(line6->urb_listen)) { + dev_err(line6->ifcdev, "invalid control EP\n"); + return -EINVAL; + } + + line6->urb_listen->actual_length = 0; + err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC); + return err; +} + +/* + Stop listening on endpoint. +*/ +static void line6_stop_listen(struct usb_line6 *line6) +{ + usb_kill_urb(line6->urb_listen); +} + +/* + Send raw message in pieces of wMaxPacketSize bytes. +*/ +static int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, + int size) +{ + int i, done = 0; + const struct line6_properties *properties = line6->properties; + + for (i = 0; i < size; i += line6->max_packet_size) { + int partial; + const char *frag_buf = buffer + i; + int frag_size = min(line6->max_packet_size, size - i); + int retval; + + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT); + } else { + retval = usb_bulk_msg(line6->usbdev, + usb_sndbulkpipe(line6->usbdev, properties->ep_ctrl_w), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT); + } + + if (retval) { + dev_err(line6->ifcdev, + "usb_bulk_msg failed (%d)\n", retval); + break; + } + + done += frag_size; + } + + return done; +} + +/* + Notification of completion of asynchronous request transmission. +*/ +static void line6_async_request_sent(struct urb *urb) +{ + struct message *msg = (struct message *)urb->context; + + if (msg->done >= msg->size) { + usb_free_urb(urb); + kfree(msg); + } else + line6_send_raw_message_async_part(msg, urb); +} + +/* + Asynchronously send part of a raw message. +*/ +static int line6_send_raw_message_async_part(struct message *msg, + struct urb *urb) +{ + int retval; + struct usb_line6 *line6 = msg->line6; + int done = msg->done; + int bytes = min(msg->size - done, line6->max_packet_size); + + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + usb_fill_int_urb(urb, line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg, line6->interval); + } else { + usb_fill_bulk_urb(urb, line6->usbdev, + usb_sndbulkpipe(line6->usbdev, line6->properties->ep_ctrl_w), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg); + } + + msg->done += bytes; + + /* sanity checks of EP before actually submitting */ + retval = usb_urb_ep_type_check(urb); + if (retval < 0) + goto error; + + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval < 0) + goto error; + + return 0; + + error: + dev_err(line6->ifcdev, "%s: usb_submit_urb failed (%d)\n", + __func__, retval); + usb_free_urb(urb); + kfree(msg); + return retval; +} + +/* + Setup and start timer. +*/ +void line6_start_timer(struct timer_list *timer, unsigned long msecs, + void (*function)(struct timer_list *t)) +{ + timer->function = function; + mod_timer(timer, jiffies + msecs_to_jiffies(msecs)); +} +EXPORT_SYMBOL_GPL(line6_start_timer); + +/* + Asynchronously send raw message. +*/ +int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer, + int size) +{ + struct message *msg; + struct urb *urb; + + /* create message: */ + msg = kmalloc(sizeof(struct message), GFP_ATOMIC); + if (msg == NULL) + return -ENOMEM; + + /* create URB: */ + urb = usb_alloc_urb(0, GFP_ATOMIC); + + if (urb == NULL) { + kfree(msg); + return -ENOMEM; + } + + /* set message data: */ + msg->line6 = line6; + msg->buffer = buffer; + msg->size = size; + msg->done = 0; + + /* start sending: */ + return line6_send_raw_message_async_part(msg, urb); +} +EXPORT_SYMBOL_GPL(line6_send_raw_message_async); + +/* + Send asynchronous device version request. +*/ +int line6_version_request_async(struct usb_line6 *line6) +{ + char *buffer; + int retval; + + buffer = kmemdup(line6_request_version, + sizeof(line6_request_version), GFP_ATOMIC); + if (buffer == NULL) + return -ENOMEM; + + retval = line6_send_raw_message_async(line6, buffer, + sizeof(line6_request_version)); + kfree(buffer); + return retval; +} +EXPORT_SYMBOL_GPL(line6_version_request_async); + +/* + Send sysex message in pieces of wMaxPacketSize bytes. +*/ +int line6_send_sysex_message(struct usb_line6 *line6, const char *buffer, + int size) +{ + return line6_send_raw_message(line6, buffer, + size + SYSEX_EXTRA_SIZE) - + SYSEX_EXTRA_SIZE; +} +EXPORT_SYMBOL_GPL(line6_send_sysex_message); + +/* + Allocate buffer for sysex message and prepare header. + @param code sysex message code + @param size number of bytes between code and sysex end +*/ +char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, int code2, + int size) +{ + char *buffer = kmalloc(size + SYSEX_EXTRA_SIZE, GFP_ATOMIC); + + if (!buffer) + return NULL; + + buffer[0] = LINE6_SYSEX_BEGIN; + memcpy(buffer + 1, line6_midi_id, sizeof(line6_midi_id)); + buffer[sizeof(line6_midi_id) + 1] = code1; + buffer[sizeof(line6_midi_id) + 2] = code2; + buffer[sizeof(line6_midi_id) + 3 + size] = LINE6_SYSEX_END; + return buffer; +} +EXPORT_SYMBOL_GPL(line6_alloc_sysex_buffer); + +/* + Notification of data received from the Line 6 device. +*/ +static void line6_data_received(struct urb *urb) +{ + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + struct midi_buffer *mb = &line6->line6midi->midibuf_in; + int done; + + if (urb->status == -ESHUTDOWN) + return; + + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + done = + line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + + if (done < urb->actual_length) { + line6_midibuf_ignore(mb, done); + dev_dbg(line6->ifcdev, "%d %d buffer overflow - message skipped\n", + done, urb->actual_length); + } + + for (;;) { + done = + line6_midibuf_read(mb, line6->buffer_message, + LINE6_MIDI_MESSAGE_MAXLEN); + + if (done <= 0) + break; + + line6->message_length = done; + line6_midi_receive(line6, line6->buffer_message, done); + + if (line6->process_message) + line6->process_message(line6); + } + } else { + line6->buffer_message = urb->transfer_buffer; + line6->message_length = urb->actual_length; + if (line6->process_message) + line6->process_message(line6); + line6->buffer_message = NULL; + } + + line6_start_listen(line6); +} + +#define LINE6_READ_WRITE_STATUS_DELAY 2 /* milliseconds */ +#define LINE6_READ_WRITE_MAX_RETRIES 50 + +/* + Read data from device. +*/ +int line6_read_data(struct usb_line6 *line6, unsigned address, void *data, + unsigned datalen) +{ + struct usb_device *usbdev = line6->usbdev; + int ret; + unsigned char *len; + unsigned count; + + if (address > 0xffff || datalen > 0xff) + return -EINVAL; + + len = kmalloc(sizeof(*len), GFP_KERNEL); + if (!len) + return -ENOMEM; + + /* query the serial number: */ + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + (datalen << 8) | 0x21, address, + NULL, 0, LINE6_TIMEOUT); + + if (ret < 0) { + dev_err(line6->ifcdev, "read request failed (error %d)\n", ret); + goto exit; + } + + /* Wait for data length. We'll get 0xff until length arrives. */ + for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) { + mdelay(LINE6_READ_WRITE_STATUS_DELAY); + + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_IN, + 0x0012, 0x0000, len, 1, + LINE6_TIMEOUT); + if (ret < 0) { + dev_err(line6->ifcdev, + "receive length failed (error %d)\n", ret); + goto exit; + } + + if (*len != 0xff) + break; + } + + ret = -EIO; + if (*len == 0xff) { + dev_err(line6->ifcdev, "read failed after %d retries\n", + count); + goto exit; + } else if (*len != datalen) { + /* should be equal or something went wrong */ + dev_err(line6->ifcdev, + "length mismatch (expected %d, got %d)\n", + (int)datalen, (int)*len); + goto exit; + } + + /* receive the result: */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0013, 0x0000, data, datalen, + LINE6_TIMEOUT); + + if (ret < 0) + dev_err(line6->ifcdev, "read failed (error %d)\n", ret); + +exit: + kfree(len); + return ret; +} +EXPORT_SYMBOL_GPL(line6_read_data); + +/* + Write data to device. +*/ +int line6_write_data(struct usb_line6 *line6, unsigned address, void *data, + unsigned datalen) +{ + struct usb_device *usbdev = line6->usbdev; + int ret; + unsigned char *status; + int count; + + if (address > 0xffff || datalen > 0xffff) + return -EINVAL; + + status = kmalloc(sizeof(*status), GFP_KERNEL); + if (!status) + return -ENOMEM; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x0022, address, data, datalen, + LINE6_TIMEOUT); + + if (ret < 0) { + dev_err(line6->ifcdev, + "write request failed (error %d)\n", ret); + goto exit; + } + + for (count = 0; count < LINE6_READ_WRITE_MAX_RETRIES; count++) { + mdelay(LINE6_READ_WRITE_STATUS_DELAY); + + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), + 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_IN, + 0x0012, 0x0000, + status, 1, LINE6_TIMEOUT); + + if (ret < 0) { + dev_err(line6->ifcdev, + "receiving status failed (error %d)\n", ret); + goto exit; + } + + if (*status != 0xff) + break; + } + + if (*status == 0xff) { + dev_err(line6->ifcdev, "write failed after %d retries\n", + count); + ret = -EIO; + } else if (*status != 0) { + dev_err(line6->ifcdev, "write failed (error %d)\n", ret); + ret = -EIO; + } +exit: + kfree(status); + return ret; +} +EXPORT_SYMBOL_GPL(line6_write_data); + +/* + Read Line 6 device serial number. + (POD, TonePort, GuitarPort) +*/ +int line6_read_serial_number(struct usb_line6 *line6, u32 *serial_number) +{ + return line6_read_data(line6, 0x80d0, serial_number, + sizeof(*serial_number)); +} +EXPORT_SYMBOL_GPL(line6_read_serial_number); + +/* + Card destructor. +*/ +static void line6_destruct(struct snd_card *card) +{ + struct usb_line6 *line6 = card->private_data; + struct usb_device *usbdev = line6->usbdev; + + /* Free buffer memory first. We cannot depend on the existence of private + * data from the (podhd) module, it may be gone already during this call + */ + kfree(line6->buffer_message); + + kfree(line6->buffer_listen); + + /* then free URBs: */ + usb_free_urb(line6->urb_listen); + line6->urb_listen = NULL; + + /* decrement reference counters: */ + usb_put_dev(usbdev); +} + +static void line6_get_usb_properties(struct usb_line6 *line6) +{ + struct usb_device *usbdev = line6->usbdev; + const struct line6_properties *properties = line6->properties; + int pipe; + struct usb_host_endpoint *ep = NULL; + + if (properties->capabilities & LINE6_CAP_CONTROL) { + if (properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + pipe = usb_rcvintpipe(line6->usbdev, + line6->properties->ep_ctrl_r); + } else { + pipe = usb_rcvbulkpipe(line6->usbdev, + line6->properties->ep_ctrl_r); + } + ep = usbdev->ep_in[usb_pipeendpoint(pipe)]; + } + + /* Control data transfer properties */ + if (ep) { + line6->interval = ep->desc.bInterval; + line6->max_packet_size = le16_to_cpu(ep->desc.wMaxPacketSize); + } else { + if (properties->capabilities & LINE6_CAP_CONTROL) { + dev_err(line6->ifcdev, + "endpoint not available, using fallback values"); + } + line6->interval = LINE6_FALLBACK_INTERVAL; + line6->max_packet_size = LINE6_FALLBACK_MAXPACKETSIZE; + } + + /* Isochronous transfer properties */ + if (usbdev->speed == USB_SPEED_LOW) { + line6->intervals_per_second = USB_LOW_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_LOW_ISO_BUFFERS; + } else { + line6->intervals_per_second = USB_HIGH_INTERVALS_PER_SECOND; + line6->iso_buffers = USB_HIGH_ISO_BUFFERS; + } +} + +/* Enable buffering of incoming messages, flush the buffer */ +static int line6_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct usb_line6 *line6 = hw->private_data; + + /* NOTE: hwdep layer provides atomicity here */ + + line6->messages.active = 1; + + return 0; +} + +/* Stop buffering */ +static int line6_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_line6 *line6 = hw->private_data; + + line6->messages.active = 0; + + return 0; +} + +/* Read from circular buffer, return to user */ +static long +line6_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct usb_line6 *line6 = hwdep->private_data; + long rv = 0; + unsigned int out_count; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + + while (kfifo_len(&line6->messages.fifo) == 0) { + mutex_unlock(&line6->messages.read_lock); + + rv = wait_event_interruptible( + line6->messages.wait_queue, + kfifo_len(&line6->messages.fifo) != 0); + if (rv < 0) + return rv; + + if (mutex_lock_interruptible(&line6->messages.read_lock)) + return -ERESTARTSYS; + } + + if (kfifo_peek_len(&line6->messages.fifo) > count) { + /* Buffer too small; allow re-read of the current item... */ + rv = -EINVAL; + } else { + rv = kfifo_to_user(&line6->messages.fifo, buf, count, &out_count); + if (rv == 0) + rv = out_count; + } + + mutex_unlock(&line6->messages.read_lock); + return rv; +} + +/* Write directly (no buffering) to device by user*/ +static long +line6_hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, + loff_t *offset) +{ + struct usb_line6 *line6 = hwdep->private_data; + int rv; + char *data_copy; + + if (count > line6->max_packet_size * LINE6_RAW_MESSAGES_MAXCOUNT) { + /* This is an arbitrary limit - still better than nothing... */ + return -EINVAL; + } + + data_copy = memdup_user(data, count); + if (IS_ERR(data_copy)) + return PTR_ERR(data_copy); + + rv = line6_send_raw_message(line6, data_copy, count); + + kfree(data_copy); + return rv; +} + +static const struct snd_hwdep_ops hwdep_ops = { + .open = line6_hwdep_open, + .release = line6_hwdep_release, + .read = line6_hwdep_read, + .write = line6_hwdep_write, +}; + +/* Insert into circular buffer */ +static void line6_hwdep_push_message(struct usb_line6 *line6) +{ + if (!line6->messages.active) + return; + + if (kfifo_avail(&line6->messages.fifo) >= line6->message_length) { + /* No race condition here, there's only one writer */ + kfifo_in(&line6->messages.fifo, + line6->buffer_message, line6->message_length); + } /* else TODO: signal overflow */ + + wake_up_interruptible(&line6->messages.wait_queue); +} + +static int line6_hwdep_init(struct usb_line6 *line6) +{ + int err; + struct snd_hwdep *hwdep; + + /* TODO: usb_driver_claim_interface(); */ + line6->process_message = line6_hwdep_push_message; + line6->messages.active = 0; + init_waitqueue_head(&line6->messages.wait_queue); + mutex_init(&line6->messages.read_lock); + INIT_KFIFO(line6->messages.fifo); + + err = snd_hwdep_new(line6->card, "config", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "config"); + hwdep->iface = SNDRV_HWDEP_IFACE_LINE6; + hwdep->ops = hwdep_ops; + hwdep->private_data = line6; + hwdep->exclusive = true; + +end: + return err; +} + +static int line6_init_cap_control(struct usb_line6 *line6) +{ + int ret; + + /* initialize USB buffers: */ + line6->buffer_listen = kmalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL); + if (!line6->buffer_listen) + return -ENOMEM; + + line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL); + if (!line6->urb_listen) + return -ENOMEM; + + if (line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI) { + line6->buffer_message = kmalloc(LINE6_MIDI_MESSAGE_MAXLEN, GFP_KERNEL); + if (!line6->buffer_message) + return -ENOMEM; + + ret = line6_init_midi(line6); + if (ret < 0) + return ret; + } else { + ret = line6_hwdep_init(line6); + if (ret < 0) + return ret; + } + + ret = line6_start_listen(line6); + if (ret < 0) { + dev_err(line6->ifcdev, "cannot start listening: %d\n", ret); + return ret; + } + + return 0; +} + +static void line6_startup_work(struct work_struct *work) +{ + struct usb_line6 *line6 = + container_of(work, struct usb_line6, startup_work.work); + + if (line6->startup) + line6->startup(line6); +} + +/* + Probe USB device. +*/ +int line6_probe(struct usb_interface *interface, + const struct usb_device_id *id, + const char *driver_name, + const struct line6_properties *properties, + int (*private_init)(struct usb_line6 *, const struct usb_device_id *id), + size_t data_size) +{ + struct usb_device *usbdev = interface_to_usbdev(interface); + struct snd_card *card; + struct usb_line6 *line6; + int interface_number; + int ret; + + if (WARN_ON(data_size < sizeof(*line6))) + return -EINVAL; + + /* we don't handle multiple configurations */ + if (usbdev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + ret = snd_card_new(&interface->dev, + SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, data_size, &card); + if (ret < 0) + return ret; + + /* store basic data: */ + line6 = card->private_data; + line6->card = card; + line6->properties = properties; + line6->usbdev = usbdev; + line6->ifcdev = &interface->dev; + INIT_DELAYED_WORK(&line6->startup_work, line6_startup_work); + + strcpy(card->id, properties->id); + strcpy(card->driver, driver_name); + strcpy(card->shortname, properties->name); + sprintf(card->longname, "Line 6 %s at USB %s", properties->name, + dev_name(line6->ifcdev)); + card->private_free = line6_destruct; + + usb_set_intfdata(interface, line6); + + /* increment reference counters: */ + usb_get_dev(usbdev); + + /* initialize device info: */ + dev_info(&interface->dev, "Line 6 %s found\n", properties->name); + + /* query interface number */ + interface_number = interface->cur_altsetting->desc.bInterfaceNumber; + + /* TODO reserves the bus bandwidth even without actual transfer */ + ret = usb_set_interface(usbdev, interface_number, + properties->altsetting); + if (ret < 0) { + dev_err(&interface->dev, "set_interface failed\n"); + goto error; + } + + line6_get_usb_properties(line6); + + if (properties->capabilities & LINE6_CAP_CONTROL) { + ret = line6_init_cap_control(line6); + if (ret < 0) + goto error; + } + + /* initialize device data based on device: */ + ret = private_init(line6, id); + if (ret < 0) + goto error; + + /* creation of additional special files should go here */ + + dev_info(&interface->dev, "Line 6 %s now attached\n", + properties->name); + + return 0; + + error: + /* we can call disconnect callback here because no close-sync is + * needed yet at this point + */ + line6_disconnect(interface); + return ret; +} +EXPORT_SYMBOL_GPL(line6_probe); + +/* + Line 6 device disconnected. +*/ +void line6_disconnect(struct usb_interface *interface) +{ + struct usb_line6 *line6 = usb_get_intfdata(interface); + struct usb_device *usbdev = interface_to_usbdev(interface); + + if (!line6) + return; + + if (WARN_ON(usbdev != line6->usbdev)) + return; + + cancel_delayed_work_sync(&line6->startup_work); + + if (line6->urb_listen != NULL) + line6_stop_listen(line6); + + snd_card_disconnect(line6->card); + if (line6->line6pcm) + line6_pcm_disconnect(line6->line6pcm); + if (line6->disconnect) + line6->disconnect(line6); + + dev_info(&interface->dev, "Line 6 %s now disconnected\n", + line6->properties->name); + + /* make sure the device isn't destructed twice: */ + usb_set_intfdata(interface, NULL); + + snd_card_free_when_closed(line6->card); +} +EXPORT_SYMBOL_GPL(line6_disconnect); + +#ifdef CONFIG_PM + +/* + Suspend Line 6 device. +*/ +int line6_suspend(struct usb_interface *interface, pm_message_t message) +{ + struct usb_line6 *line6 = usb_get_intfdata(interface); + struct snd_line6_pcm *line6pcm = line6->line6pcm; + + snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot); + + if (line6->properties->capabilities & LINE6_CAP_CONTROL) + line6_stop_listen(line6); + + if (line6pcm != NULL) { + snd_pcm_suspend_all(line6pcm->pcm); + line6pcm->flags = 0; + } + + return 0; +} +EXPORT_SYMBOL_GPL(line6_suspend); + +/* + Resume Line 6 device. +*/ +int line6_resume(struct usb_interface *interface) +{ + struct usb_line6 *line6 = usb_get_intfdata(interface); + + if (line6->properties->capabilities & LINE6_CAP_CONTROL) + line6_start_listen(line6); + + snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); + return 0; +} +EXPORT_SYMBOL_GPL(line6_resume); + +#endif /* CONFIG_PM */ + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + diff --git a/sound/usb/line6/driver.h b/sound/usb/line6/driver.h new file mode 100644 index 000000000..d2d786ead --- /dev/null +++ b/sound/usb/line6/driver.h @@ -0,0 +1,224 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef DRIVER_H +#define DRIVER_H + +#include +#include +#include +#include + +#include "midi.h" + +/* USB 1.1 speed configuration */ +#define USB_LOW_INTERVALS_PER_SECOND 1000 +#define USB_LOW_ISO_BUFFERS 2 + +/* USB 2.0+ speed configuration */ +#define USB_HIGH_INTERVALS_PER_SECOND 8000 +#define USB_HIGH_ISO_BUFFERS 16 + +/* Fallback USB interval and max packet size values */ +#define LINE6_FALLBACK_INTERVAL 10 +#define LINE6_FALLBACK_MAXPACKETSIZE 16 + +#define LINE6_TIMEOUT 1000 +#define LINE6_BUFSIZE_LISTEN 64 +#define LINE6_MIDI_MESSAGE_MAXLEN 256 + +#define LINE6_RAW_MESSAGES_MAXCOUNT_ORDER 7 +/* 4k packets are common, BUFSIZE * MAXCOUNT should be bigger... */ +#define LINE6_RAW_MESSAGES_MAXCOUNT (1 << LINE6_RAW_MESSAGES_MAXCOUNT_ORDER) + + +#if LINE6_BUFSIZE_LISTEN > 65535 +#error "Use dynamic fifo instead" +#endif + +/* + Line 6 MIDI control commands +*/ +#define LINE6_PARAM_CHANGE 0xb0 +#define LINE6_PROGRAM_CHANGE 0xc0 +#define LINE6_SYSEX_BEGIN 0xf0 +#define LINE6_SYSEX_END 0xf7 +#define LINE6_RESET 0xff + +/* + MIDI channel for messages initiated by the host + (and eventually echoed back by the device) +*/ +#define LINE6_CHANNEL_HOST 0x00 + +/* + MIDI channel for messages initiated by the device +*/ +#define LINE6_CHANNEL_DEVICE 0x02 + +#define LINE6_CHANNEL_UNKNOWN 5 /* don't know yet what this is good for */ + +#define LINE6_CHANNEL_MASK 0x0f + +#define CHECK_STARTUP_PROGRESS(x, n) \ +do { \ + if ((x) >= (n)) \ + return; \ + x = (n); \ +} while (0) + +extern const unsigned char line6_midi_id[3]; + +static const int SYSEX_DATA_OFS = sizeof(line6_midi_id) + 3; +static const int SYSEX_EXTRA_SIZE = sizeof(line6_midi_id) + 4; + +/* + Common properties of Line 6 devices. +*/ +struct line6_properties { + /* Card id string (maximum 16 characters). + * This can be used to address the device in ALSA programs as + * "default:CARD=" + */ + const char *id; + + /* Card short name (maximum 32 characters) */ + const char *name; + + /* Bit vector defining this device's capabilities in line6usb driver */ + int capabilities; + + int altsetting; + + unsigned int ctrl_if; + unsigned int ep_ctrl_r; + unsigned int ep_ctrl_w; + unsigned int ep_audio_r; + unsigned int ep_audio_w; +}; + +/* Capability bits */ +enum { + /* device supports settings parameter via USB */ + LINE6_CAP_CONTROL = 1 << 0, + /* device supports PCM input/output via USB */ + LINE6_CAP_PCM = 1 << 1, + /* device supports hardware monitoring */ + LINE6_CAP_HWMON = 1 << 2, + /* device requires output data when input is read */ + LINE6_CAP_IN_NEEDS_OUT = 1 << 3, + /* device uses raw MIDI via USB (data endpoints) */ + LINE6_CAP_CONTROL_MIDI = 1 << 4, + /* device provides low-level information */ + LINE6_CAP_CONTROL_INFO = 1 << 5, +}; + +/* + Common data shared by all Line 6 devices. + Corresponds to a pair of USB endpoints. +*/ +struct usb_line6 { + /* USB device */ + struct usb_device *usbdev; + + /* Properties */ + const struct line6_properties *properties; + + /* Interval for data USB packets */ + int interval; + /* ...for isochronous transfers framing */ + int intervals_per_second; + + /* Number of isochronous URBs used for frame transfers */ + int iso_buffers; + + /* Maximum size of data USB packet */ + int max_packet_size; + + /* Device representing the USB interface */ + struct device *ifcdev; + + /* Line 6 sound card data structure. + * Each device has at least MIDI or PCM. + */ + struct snd_card *card; + + /* Line 6 PCM device data structure */ + struct snd_line6_pcm *line6pcm; + + /* Line 6 MIDI device data structure */ + struct snd_line6_midi *line6midi; + + /* URB for listening to POD data endpoint */ + struct urb *urb_listen; + + /* Buffer for incoming data from POD data endpoint */ + unsigned char *buffer_listen; + + /* Buffer for message to be processed, generated from MIDI layer */ + unsigned char *buffer_message; + + /* Length of message to be processed, generated from MIDI layer */ + int message_length; + + /* Circular buffer for non-MIDI control messages */ + struct { + struct mutex read_lock; + wait_queue_head_t wait_queue; + unsigned int active:1; + STRUCT_KFIFO_REC_2(LINE6_BUFSIZE_LISTEN * LINE6_RAW_MESSAGES_MAXCOUNT) + fifo; + } messages; + + /* Work for delayed PCM startup */ + struct delayed_work startup_work; + + /* If MIDI is supported, buffer_message contains the pre-processed data; + * otherwise the data is only in urb_listen (buffer_incoming). + */ + void (*process_message)(struct usb_line6 *); + void (*disconnect)(struct usb_line6 *line6); + void (*startup)(struct usb_line6 *line6); +}; + +extern char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, + int code2, int size); +extern int line6_read_data(struct usb_line6 *line6, unsigned address, + void *data, unsigned datalen); +extern int line6_read_serial_number(struct usb_line6 *line6, + u32 *serial_number); +extern int line6_send_raw_message_async(struct usb_line6 *line6, + const char *buffer, int size); +extern int line6_send_sysex_message(struct usb_line6 *line6, + const char *buffer, int size); +extern ssize_t line6_set_raw(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +extern void line6_start_timer(struct timer_list *timer, unsigned long msecs, + void (*function)(struct timer_list *t)); +extern int line6_version_request_async(struct usb_line6 *line6); +extern int line6_write_data(struct usb_line6 *line6, unsigned address, + void *data, unsigned datalen); + +int line6_probe(struct usb_interface *interface, + const struct usb_device_id *id, + const char *driver_name, + const struct line6_properties *properties, + int (*private_init)(struct usb_line6 *, const struct usb_device_id *id), + size_t data_size); + +void line6_disconnect(struct usb_interface *interface); + +#ifdef CONFIG_PM +int line6_suspend(struct usb_interface *interface, pm_message_t message); +int line6_resume(struct usb_interface *interface); +#endif + +#endif diff --git a/sound/usb/line6/midi.c b/sound/usb/line6/midi.c new file mode 100644 index 000000000..e2cf55c53 --- /dev/null +++ b/sound/usb/line6/midi.c @@ -0,0 +1,297 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include + +#include "driver.h" +#include "midi.h" + +#define line6_rawmidi_substream_midi(substream) \ + ((struct snd_line6_midi *)((substream)->rmidi->private_data)) + +static int send_midi_async(struct usb_line6 *line6, unsigned char *data, + int length); + +/* + Pass data received via USB to MIDI. +*/ +void line6_midi_receive(struct usb_line6 *line6, unsigned char *data, + int length) +{ + if (line6->line6midi->substream_receive) + snd_rawmidi_receive(line6->line6midi->substream_receive, + data, length); +} + +/* + Read data from MIDI buffer and transmit them via USB. +*/ +static void line6_midi_transmit(struct snd_rawmidi_substream *substream) +{ + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + struct snd_line6_midi *line6midi = line6->line6midi; + struct midi_buffer *mb = &line6midi->midibuf_out; + unsigned char chunk[LINE6_FALLBACK_MAXPACKETSIZE]; + int req, done; + + for (;;) { + req = min(line6_midibuf_bytes_free(mb), line6->max_packet_size); + done = snd_rawmidi_transmit_peek(substream, chunk, req); + + if (done == 0) + break; + + line6_midibuf_write(mb, chunk, done); + snd_rawmidi_transmit_ack(substream, done); + } + + for (;;) { + done = line6_midibuf_read(mb, chunk, + LINE6_FALLBACK_MAXPACKETSIZE); + + if (done == 0) + break; + + send_midi_async(line6, chunk, done); + } +} + +/* + Notification of completion of MIDI transmission. +*/ +static void midi_sent(struct urb *urb) +{ + unsigned long flags; + int status; + int num; + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + + status = urb->status; + kfree(urb->transfer_buffer); + usb_free_urb(urb); + + if (status == -ESHUTDOWN) + return; + + spin_lock_irqsave(&line6->line6midi->lock, flags); + num = --line6->line6midi->num_active_send_urbs; + + if (num == 0) { + line6_midi_transmit(line6->line6midi->substream_transmit); + num = line6->line6midi->num_active_send_urbs; + } + + if (num == 0) + wake_up(&line6->line6midi->send_wait); + + spin_unlock_irqrestore(&line6->line6midi->lock, flags); +} + +/* + Send an asynchronous MIDI message. + Assumes that line6->line6midi->lock is held + (i.e., this function is serialized). +*/ +static int send_midi_async(struct usb_line6 *line6, unsigned char *data, + int length) +{ + struct urb *urb; + int retval; + unsigned char *transfer_buffer; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + + if (urb == NULL) + return -ENOMEM; + + transfer_buffer = kmemdup(data, length, GFP_ATOMIC); + + if (transfer_buffer == NULL) { + usb_free_urb(urb); + return -ENOMEM; + } + + usb_fill_int_urb(urb, line6->usbdev, + usb_sndintpipe(line6->usbdev, + line6->properties->ep_ctrl_w), + transfer_buffer, length, midi_sent, line6, + line6->interval); + urb->actual_length = 0; + retval = usb_urb_ep_type_check(urb); + if (retval < 0) + goto error; + + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval < 0) + goto error; + + ++line6->line6midi->num_active_send_urbs; + return 0; + + error: + dev_err(line6->ifcdev, "usb_submit_urb failed\n"); + usb_free_urb(urb); + return retval; +} + +static int line6_midi_output_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int line6_midi_output_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void line6_midi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + + line6->line6midi->substream_transmit = substream; + spin_lock_irqsave(&line6->line6midi->lock, flags); + + if (line6->line6midi->num_active_send_urbs == 0) + line6_midi_transmit(substream); + + spin_unlock_irqrestore(&line6->line6midi->lock, flags); +} + +static void line6_midi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + struct snd_line6_midi *midi = line6->line6midi; + + wait_event_interruptible(midi->send_wait, + midi->num_active_send_urbs == 0); +} + +static int line6_midi_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int line6_midi_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void line6_midi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + + if (up) + line6->line6midi->substream_receive = substream; + else + line6->line6midi->substream_receive = NULL; +} + +static const struct snd_rawmidi_ops line6_midi_output_ops = { + .open = line6_midi_output_open, + .close = line6_midi_output_close, + .trigger = line6_midi_output_trigger, + .drain = line6_midi_output_drain, +}; + +static const struct snd_rawmidi_ops line6_midi_input_ops = { + .open = line6_midi_input_open, + .close = line6_midi_input_close, + .trigger = line6_midi_input_trigger, +}; + +/* Create a MIDI device */ +static int snd_line6_new_midi(struct usb_line6 *line6, + struct snd_rawmidi **rmidi_ret) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(line6->card, "Line 6 MIDI", 0, 1, 1, rmidi_ret); + if (err < 0) + return err; + + rmidi = *rmidi_ret; + strcpy(rmidi->id, line6->properties->id); + strcpy(rmidi->name, line6->properties->name); + + rmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &line6_midi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &line6_midi_input_ops); + return 0; +} + +/* MIDI device destructor */ +static void snd_line6_midi_free(struct snd_rawmidi *rmidi) +{ + struct snd_line6_midi *line6midi = rmidi->private_data; + + line6_midibuf_destroy(&line6midi->midibuf_in); + line6_midibuf_destroy(&line6midi->midibuf_out); + kfree(line6midi); +} + +/* + Initialize the Line 6 MIDI subsystem. +*/ +int line6_init_midi(struct usb_line6 *line6) +{ + int err; + struct snd_rawmidi *rmidi; + struct snd_line6_midi *line6midi; + + if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) { + /* skip MIDI initialization and report success */ + return 0; + } + + err = snd_line6_new_midi(line6, &rmidi); + if (err < 0) + return err; + + line6midi = kzalloc(sizeof(struct snd_line6_midi), GFP_KERNEL); + if (!line6midi) + return -ENOMEM; + + rmidi->private_data = line6midi; + rmidi->private_free = snd_line6_midi_free; + + init_waitqueue_head(&line6midi->send_wait); + spin_lock_init(&line6midi->lock); + line6midi->line6 = line6; + + err = line6_midibuf_init(&line6midi->midibuf_in, MIDI_BUFFER_SIZE, 0); + if (err < 0) + return err; + + err = line6_midibuf_init(&line6midi->midibuf_out, MIDI_BUFFER_SIZE, 1); + if (err < 0) + return err; + + line6->line6midi = line6midi; + return 0; +} +EXPORT_SYMBOL_GPL(line6_init_midi); diff --git a/sound/usb/line6/midi.h b/sound/usb/line6/midi.h new file mode 100644 index 000000000..cf82d69e2 --- /dev/null +++ b/sound/usb/line6/midi.h @@ -0,0 +1,51 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef MIDI_H +#define MIDI_H + +#include + +#include "midibuf.h" + +#define MIDI_BUFFER_SIZE 1024 + +struct snd_line6_midi { + /* Pointer back to the Line 6 driver data structure */ + struct usb_line6 *line6; + + /* MIDI substream for receiving (or NULL if not active) */ + struct snd_rawmidi_substream *substream_receive; + + /* MIDI substream for transmitting (or NULL if not active) */ + struct snd_rawmidi_substream *substream_transmit; + + /* Number of currently active MIDI send URBs */ + int num_active_send_urbs; + + /* Spin lock to protect MIDI buffer handling */ + spinlock_t lock; + + /* Wait queue for MIDI transmission */ + wait_queue_head_t send_wait; + + /* Buffer for incoming MIDI stream */ + struct midi_buffer midibuf_in; + + /* Buffer for outgoing MIDI stream */ + struct midi_buffer midibuf_out; +}; + +extern int line6_init_midi(struct usb_line6 *line6); +extern void line6_midi_receive(struct usb_line6 *line6, unsigned char *data, + int length); + +#endif diff --git a/sound/usb/line6/midibuf.c b/sound/usb/line6/midibuf.c new file mode 100644 index 000000000..c931d4880 --- /dev/null +++ b/sound/usb/line6/midibuf.c @@ -0,0 +1,252 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include + +#include "midibuf.h" + +static int midibuf_message_length(unsigned char code) +{ + int message_length; + + if (code < 0x80) + message_length = -1; + else if (code < 0xf0) { + static const int length[] = { 3, 3, 3, 3, 2, 2, 3 }; + + message_length = length[(code >> 4) - 8]; + } else { + /* + Note that according to the MIDI specification 0xf2 is + the "Song Position Pointer", but this is used by Line 6 + to send sysex messages to the host. + */ + static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1, + 1, 1, 1, -1, 1, 1 + }; + message_length = length[code & 0x0f]; + } + + return message_length; +} + +static int midibuf_is_empty(struct midi_buffer *this) +{ + return (this->pos_read == this->pos_write) && !this->full; +} + +static int midibuf_is_full(struct midi_buffer *this) +{ + return this->full; +} + +void line6_midibuf_reset(struct midi_buffer *this) +{ + this->pos_read = this->pos_write = this->full = 0; + this->command_prev = -1; +} + +int line6_midibuf_init(struct midi_buffer *this, int size, int split) +{ + this->buf = kmalloc(size, GFP_KERNEL); + + if (this->buf == NULL) + return -ENOMEM; + + this->size = size; + this->split = split; + line6_midibuf_reset(this); + return 0; +} + +int line6_midibuf_bytes_free(struct midi_buffer *this) +{ + return + midibuf_is_full(this) ? + 0 : + (this->pos_read - this->pos_write + this->size - 1) % this->size + + 1; +} + +int line6_midibuf_bytes_used(struct midi_buffer *this) +{ + return + midibuf_is_empty(this) ? + 0 : + (this->pos_write - this->pos_read + this->size - 1) % this->size + + 1; +} + +int line6_midibuf_write(struct midi_buffer *this, unsigned char *data, + int length) +{ + int bytes_free; + int length1, length2; + int skip_active_sense = 0; + + if (midibuf_is_full(this) || (length <= 0)) + return 0; + + /* skip trailing active sense */ + if (data[length - 1] == 0xfe) { + --length; + skip_active_sense = 1; + } + + bytes_free = line6_midibuf_bytes_free(this); + + if (length > bytes_free) + length = bytes_free; + + if (length > 0) { + length1 = this->size - this->pos_write; + + if (length < length1) { + /* no buffer wraparound */ + memcpy(this->buf + this->pos_write, data, length); + this->pos_write += length; + } else { + /* buffer wraparound */ + length2 = length - length1; + memcpy(this->buf + this->pos_write, data, length1); + memcpy(this->buf, data + length1, length2); + this->pos_write = length2; + } + + if (this->pos_write == this->pos_read) + this->full = 1; + } + + return length + skip_active_sense; +} + +int line6_midibuf_read(struct midi_buffer *this, unsigned char *data, + int length) +{ + int bytes_used; + int length1, length2; + int command; + int midi_length; + int repeat = 0; + int i; + + /* we need to be able to store at least a 3 byte MIDI message */ + if (length < 3) + return -EINVAL; + + if (midibuf_is_empty(this)) + return 0; + + bytes_used = line6_midibuf_bytes_used(this); + + if (length > bytes_used) + length = bytes_used; + + length1 = this->size - this->pos_read; + + /* check MIDI command length */ + command = this->buf[this->pos_read]; + + if (command & 0x80) { + midi_length = midibuf_message_length(command); + this->command_prev = command; + } else { + if (this->command_prev > 0) { + int midi_length_prev = + midibuf_message_length(this->command_prev); + + if (midi_length_prev > 1) { + midi_length = midi_length_prev - 1; + repeat = 1; + } else + midi_length = -1; + } else + midi_length = -1; + } + + if (midi_length < 0) { + /* search for end of message */ + if (length < length1) { + /* no buffer wraparound */ + for (i = 1; i < length; ++i) + if (this->buf[this->pos_read + i] & 0x80) + break; + + midi_length = i; + } else { + /* buffer wraparound */ + length2 = length - length1; + + for (i = 1; i < length1; ++i) + if (this->buf[this->pos_read + i] & 0x80) + break; + + if (i < length1) + midi_length = i; + else { + for (i = 0; i < length2; ++i) + if (this->buf[i] & 0x80) + break; + + midi_length = length1 + i; + } + } + + if (midi_length == length) + midi_length = -1; /* end of message not found */ + } + + if (midi_length < 0) { + if (!this->split) + return 0; /* command is not yet complete */ + } else { + if (length < midi_length) + return 0; /* command is not yet complete */ + + length = midi_length; + } + + if (length < length1) { + /* no buffer wraparound */ + memcpy(data + repeat, this->buf + this->pos_read, length); + this->pos_read += length; + } else { + /* buffer wraparound */ + length2 = length - length1; + memcpy(data + repeat, this->buf + this->pos_read, length1); + memcpy(data + repeat + length1, this->buf, length2); + this->pos_read = length2; + } + + if (repeat) + data[0] = this->command_prev; + + this->full = 0; + return length + repeat; +} + +int line6_midibuf_ignore(struct midi_buffer *this, int length) +{ + int bytes_used = line6_midibuf_bytes_used(this); + + if (length > bytes_used) + length = bytes_used; + + this->pos_read = (this->pos_read + length) % this->size; + this->full = 0; + return length; +} + +void line6_midibuf_destroy(struct midi_buffer *this) +{ + kfree(this->buf); + this->buf = NULL; +} diff --git a/sound/usb/line6/midibuf.h b/sound/usb/line6/midibuf.h new file mode 100644 index 000000000..6ea21ffb6 --- /dev/null +++ b/sound/usb/line6/midibuf.h @@ -0,0 +1,35 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef MIDIBUF_H +#define MIDIBUF_H + +struct midi_buffer { + unsigned char *buf; + int size; + int split; + int pos_read, pos_write; + int full; + int command_prev; +}; + +extern int line6_midibuf_bytes_used(struct midi_buffer *mb); +extern int line6_midibuf_bytes_free(struct midi_buffer *mb); +extern void line6_midibuf_destroy(struct midi_buffer *mb); +extern int line6_midibuf_ignore(struct midi_buffer *mb, int length); +extern int line6_midibuf_init(struct midi_buffer *mb, int size, int split); +extern int line6_midibuf_read(struct midi_buffer *mb, unsigned char *data, + int length); +extern void line6_midibuf_reset(struct midi_buffer *mb); +extern int line6_midibuf_write(struct midi_buffer *mb, unsigned char *data, + int length); + +#endif diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c new file mode 100644 index 000000000..531564269 --- /dev/null +++ b/sound/usb/line6/pcm.c @@ -0,0 +1,620 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "capture.h" +#include "driver.h" +#include "playback.h" + +/* impulse response volume controls */ +static int snd_line6_impulse_volume_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_line6_impulse_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = line6pcm->impulse_volume; + return 0; +} + +static int snd_line6_impulse_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + int value = ucontrol->value.integer.value[0]; + int err; + + if (line6pcm->impulse_volume == value) + return 0; + + line6pcm->impulse_volume = value; + if (value > 0) { + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_IMPULSE, true); + if (err < 0) { + line6pcm->impulse_volume = 0; + return err; + } + } else { + line6_pcm_release(line6pcm, LINE6_STREAM_IMPULSE); + } + return 1; +} + +/* impulse response period controls */ +static int snd_line6_impulse_period_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 = 2000; + return 0; +} + +static int snd_line6_impulse_period_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = line6pcm->impulse_period; + return 0; +} + +static int snd_line6_impulse_period_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + int value = ucontrol->value.integer.value[0]; + + if (line6pcm->impulse_period == value) + return 0; + + line6pcm->impulse_period = value; + return 1; +} + +/* + Unlink all currently active URBs. +*/ +static void line6_unlink_audio_urbs(struct snd_line6_pcm *line6pcm, + struct line6_pcm_stream *pcms) +{ + int i; + + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { + if (test_bit(i, &pcms->active_urbs)) { + if (!test_and_set_bit(i, &pcms->unlink_urbs)) + usb_unlink_urb(pcms->urbs[i]); + } + } +} + +/* + Wait until unlinking of all currently active URBs has been finished. +*/ +static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm, + struct line6_pcm_stream *pcms) +{ + int timeout = HZ; + int i; + int alive; + + do { + alive = 0; + for (i = 0; i < line6pcm->line6->iso_buffers; i++) { + if (test_bit(i, &pcms->active_urbs)) + alive++; + } + if (!alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (--timeout > 0); + if (alive) + dev_err(line6pcm->line6->ifcdev, + "timeout: still %d active urbs..\n", alive); +} + +static inline struct line6_pcm_stream * +get_stream(struct snd_line6_pcm *line6pcm, int direction) +{ + return (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + &line6pcm->out : &line6pcm->in; +} + +/* allocate a buffer if not opened yet; + * call this in line6pcm.state_mutex + */ +static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, + struct line6_pcm_stream *pstr, int direction, int type) +{ + const int pkt_size = + (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + line6pcm->max_packet_size_out : + line6pcm->max_packet_size_in; + + /* Invoked multiple times in a row so allocate once only */ + if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { + pstr->buffer = + kmalloc(array3_size(line6pcm->line6->iso_buffers, + LINE6_ISO_PACKETS, pkt_size), + GFP_KERNEL); + if (!pstr->buffer) + return -ENOMEM; + } + return 0; +} + +/* free a buffer if all streams are closed; + * call this in line6pcm.state_mutex + */ +static void line6_buffer_release(struct snd_line6_pcm *line6pcm, + struct line6_pcm_stream *pstr, int type) +{ + clear_bit(type, &pstr->opened); + if (!pstr->opened) { + line6_wait_clear_audio_urbs(line6pcm, pstr); + kfree(pstr->buffer); + pstr->buffer = NULL; + } +} + +/* start a PCM stream */ +static int line6_stream_start(struct snd_line6_pcm *line6pcm, int direction, + int type) +{ + unsigned long flags; + struct line6_pcm_stream *pstr = get_stream(line6pcm, direction); + int ret = 0; + + spin_lock_irqsave(&pstr->lock, flags); + if (!test_and_set_bit(type, &pstr->running) && + !(pstr->active_urbs || pstr->unlink_urbs)) { + pstr->count = 0; + /* Submit all currently available URBs */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + ret = line6_submit_audio_out_all_urbs(line6pcm); + else + ret = line6_submit_audio_in_all_urbs(line6pcm); + } + + if (ret < 0) + clear_bit(type, &pstr->running); + spin_unlock_irqrestore(&pstr->lock, flags); + return ret; +} + +/* stop a PCM stream; this doesn't sync with the unlinked URBs */ +static void line6_stream_stop(struct snd_line6_pcm *line6pcm, int direction, + int type) +{ + unsigned long flags; + struct line6_pcm_stream *pstr = get_stream(line6pcm, direction); + + spin_lock_irqsave(&pstr->lock, flags); + clear_bit(type, &pstr->running); + if (!pstr->running) { + spin_unlock_irqrestore(&pstr->lock, flags); + line6_unlink_audio_urbs(line6pcm, pstr); + spin_lock_irqsave(&pstr->lock, flags); + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + line6pcm->prev_fbuf = NULL; + line6pcm->prev_fsize = 0; + } + } + spin_unlock_irqrestore(&pstr->lock, flags); +} + +/* common PCM trigger callback */ +int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + int err; + + clear_bit(LINE6_FLAG_PREPARED, &line6pcm->flags); + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (s->stream == SNDRV_PCM_STREAM_CAPTURE && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT)) { + err = line6_stream_start(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + if (err < 0) + return err; + } + err = line6_stream_start(line6pcm, s->stream, + LINE6_STREAM_PCM); + if (err < 0) + return err; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (s->stream == SNDRV_PCM_STREAM_CAPTURE && + (line6pcm->line6->properties->capabilities & + LINE6_CAP_IN_NEEDS_OUT)) { + line6_stream_stop(line6pcm, SNDRV_PCM_STREAM_PLAYBACK, + LINE6_STREAM_CAPTURE_HELPER); + } + line6_stream_stop(line6pcm, s->stream, + LINE6_STREAM_PCM); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (s->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + set_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (s->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -EINVAL; + clear_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags); + break; + + default: + return -EINVAL; + } + } + + return 0; +} + +/* common PCM pointer callback */ +snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream); + + return pstr->pos_done; +} + +/* Acquire and optionally start duplex streams: + * type is either LINE6_STREAM_IMPULSE or LINE6_STREAM_MONITOR + */ +int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, bool start) +{ + struct line6_pcm_stream *pstr; + int ret = 0, dir; + + /* TODO: We should assert SNDRV_PCM_STREAM_PLAYBACK/CAPTURE == 0/1 */ + mutex_lock(&line6pcm->state_mutex); + for (dir = 0; dir < 2; dir++) { + pstr = get_stream(line6pcm, dir); + ret = line6_buffer_acquire(line6pcm, pstr, dir, type); + if (ret < 0) + goto error; + if (!pstr->running) + line6_wait_clear_audio_urbs(line6pcm, pstr); + } + if (start) { + for (dir = 0; dir < 2; dir++) { + ret = line6_stream_start(line6pcm, dir, type); + if (ret < 0) + goto error; + } + } + error: + mutex_unlock(&line6pcm->state_mutex); + if (ret < 0) + line6_pcm_release(line6pcm, type); + return ret; +} +EXPORT_SYMBOL_GPL(line6_pcm_acquire); + +/* Stop and release duplex streams */ +void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type) +{ + struct line6_pcm_stream *pstr; + int dir; + + mutex_lock(&line6pcm->state_mutex); + for (dir = 0; dir < 2; dir++) + line6_stream_stop(line6pcm, dir, type); + for (dir = 0; dir < 2; dir++) { + pstr = get_stream(line6pcm, dir); + line6_buffer_release(line6pcm, pstr, type); + } + mutex_unlock(&line6pcm->state_mutex); +} +EXPORT_SYMBOL_GPL(line6_pcm_release); + +/* common PCM hw_params callback */ +int snd_line6_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int ret; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream); + + mutex_lock(&line6pcm->state_mutex); + ret = line6_buffer_acquire(line6pcm, pstr, substream->stream, + LINE6_STREAM_PCM); + if (ret < 0) + goto error; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) { + line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM); + goto error; + } + + pstr->period = params_period_bytes(hw_params); + error: + mutex_unlock(&line6pcm->state_mutex); + return ret; +} + +/* common PCM hw_free callback */ +int snd_line6_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream); + + mutex_lock(&line6pcm->state_mutex); + line6_buffer_release(line6pcm, pstr, LINE6_STREAM_PCM); + mutex_unlock(&line6pcm->state_mutex); + return snd_pcm_lib_free_pages(substream); +} + + +/* control info callback */ +static int snd_line6_control_playback_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 = 256; + return 0; +} + +/* control get callback */ +static int snd_line6_control_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + for (i = 0; i < 2; i++) + ucontrol->value.integer.value[i] = line6pcm->volume_playback[i]; + + return 0; +} + +/* control put callback */ +static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i, changed = 0; + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + for (i = 0; i < 2; i++) + if (line6pcm->volume_playback[i] != + ucontrol->value.integer.value[i]) { + line6pcm->volume_playback[i] = + ucontrol->value.integer.value[i]; + changed = 1; + } + + return changed; +} + +/* control definition */ +static const struct snd_kcontrol_new line6_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = snd_line6_control_playback_info, + .get = snd_line6_control_playback_get, + .put = snd_line6_control_playback_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Impulse Response Volume", + .info = snd_line6_impulse_volume_info, + .get = snd_line6_impulse_volume_get, + .put = snd_line6_impulse_volume_put + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Impulse Response Period", + .info = snd_line6_impulse_period_info, + .get = snd_line6_impulse_period_get, + .put = snd_line6_impulse_period_put + }, +}; + +/* + Cleanup the PCM device. +*/ +static void cleanup_urbs(struct line6_pcm_stream *pcms, int iso_buffers) +{ + int i; + + /* Most likely impossible in current code... */ + if (pcms->urbs == NULL) + return; + + for (i = 0; i < iso_buffers; i++) { + if (pcms->urbs[i]) { + usb_kill_urb(pcms->urbs[i]); + usb_free_urb(pcms->urbs[i]); + } + } + kfree(pcms->urbs); + pcms->urbs = NULL; +} + +static void line6_cleanup_pcm(struct snd_pcm *pcm) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm); + + cleanup_urbs(&line6pcm->out, line6pcm->line6->iso_buffers); + cleanup_urbs(&line6pcm->in, line6pcm->line6->iso_buffers); + kfree(line6pcm); +} + +/* create a PCM device */ +static int snd_line6_new_pcm(struct usb_line6 *line6, struct snd_pcm **pcm_ret) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(line6->card, (char *)line6->properties->name, + 0, 1, 1, pcm_ret); + if (err < 0) + return err; + pcm = *pcm_ret; + strcpy(pcm->name, line6->properties->name); + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_line6_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_line6_capture_ops); + + /* pre-allocation of buffers */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), 64 * 1024, + 128 * 1024); + return 0; +} + +/* + Sync with PCM stream stops. +*/ +void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm) +{ + line6_unlink_audio_urbs(line6pcm, &line6pcm->out); + line6_unlink_audio_urbs(line6pcm, &line6pcm->in); + line6_wait_clear_audio_urbs(line6pcm, &line6pcm->out); + line6_wait_clear_audio_urbs(line6pcm, &line6pcm->in); +} + +/* + Create and register the PCM device and mixer entries. + Create URBs for playback and capture. +*/ +int line6_init_pcm(struct usb_line6 *line6, + struct line6_pcm_properties *properties) +{ + int i, err; + unsigned ep_read = line6->properties->ep_audio_r; + unsigned ep_write = line6->properties->ep_audio_w; + struct snd_pcm *pcm; + struct snd_line6_pcm *line6pcm; + + if (!(line6->properties->capabilities & LINE6_CAP_PCM)) + return 0; /* skip PCM initialization and report success */ + + err = snd_line6_new_pcm(line6, &pcm); + if (err < 0) + return err; + + line6pcm = kzalloc(sizeof(*line6pcm), GFP_KERNEL); + if (!line6pcm) + return -ENOMEM; + + mutex_init(&line6pcm->state_mutex); + line6pcm->pcm = pcm; + line6pcm->properties = properties; + line6pcm->volume_playback[0] = line6pcm->volume_playback[1] = 255; + line6pcm->volume_monitor = 255; + line6pcm->line6 = line6; + + spin_lock_init(&line6pcm->out.lock); + spin_lock_init(&line6pcm->in.lock); + line6pcm->impulse_period = LINE6_IMPULSE_DEFAULT_PERIOD; + + line6->line6pcm = line6pcm; + + pcm->private_data = line6pcm; + pcm->private_free = line6_cleanup_pcm; + + line6pcm->max_packet_size_in = + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read), 0); + line6pcm->max_packet_size_out = + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write), 1); + if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) { + dev_err(line6pcm->line6->ifcdev, + "cannot get proper max packet size\n"); + return -EINVAL; + } + + err = line6_create_audio_out_urbs(line6pcm); + if (err < 0) + return err; + + err = line6_create_audio_in_urbs(line6pcm); + if (err < 0) + return err; + + /* mixer: */ + for (i = 0; i < ARRAY_SIZE(line6_controls); i++) { + err = snd_ctl_add(line6->card, + snd_ctl_new1(&line6_controls[i], line6pcm)); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(line6_init_pcm); + +/* prepare pcm callback */ +int snd_line6_prepare(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + struct line6_pcm_stream *pstr = get_stream(line6pcm, substream->stream); + + mutex_lock(&line6pcm->state_mutex); + if (!pstr->running) + line6_wait_clear_audio_urbs(line6pcm, pstr); + + if (!test_and_set_bit(LINE6_FLAG_PREPARED, &line6pcm->flags)) { + line6pcm->out.count = 0; + line6pcm->out.pos = 0; + line6pcm->out.pos_done = 0; + line6pcm->out.bytes = 0; + line6pcm->in.count = 0; + line6pcm->in.pos_done = 0; + line6pcm->in.bytes = 0; + } + + mutex_unlock(&line6pcm->state_mutex); + return 0; +} diff --git a/sound/usb/line6/pcm.h b/sound/usb/line6/pcm.h new file mode 100644 index 000000000..bb0c9cbf2 --- /dev/null +++ b/sound/usb/line6/pcm.h @@ -0,0 +1,199 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +/* + PCM interface to POD series devices. +*/ + +#ifndef PCM_H +#define PCM_H + +#include + +#include "driver.h" + +/* + number of USB frames per URB + The Line 6 Windows driver always transmits two frames per packet, but + the Linux driver performs significantly better (i.e., lower latency) + with only one frame per packet. +*/ +#define LINE6_ISO_PACKETS 1 + +/* in a "full speed" device (such as the PODxt Pro) this means 1ms, + * for "high speed" it's 1/8ms + */ +#define LINE6_ISO_INTERVAL 1 + +#define LINE6_IMPULSE_DEFAULT_PERIOD 100 + +/* + Get substream from Line 6 PCM data structure +*/ +#define get_substream(line6pcm, stream) \ + (line6pcm->pcm->streams[stream].substream) + +/* + PCM mode bits. + + There are several features of the Line 6 USB driver which require PCM + data to be exchanged with the device: + *) PCM playback and capture via ALSA + *) software monitoring (for devices without hardware monitoring) + *) optional impulse response measurement + However, from the device's point of view, there is just a single + capture and playback stream, which must be shared between these + subsystems. It is therefore necessary to maintain the state of the + subsystems with respect to PCM usage. + + We define two bit flags, "opened" and "running", for each playback + or capture stream. Both can contain the bit flag corresponding to + LINE6_STREAM_* type, + LINE6_STREAM_PCM = ALSA PCM playback or capture + LINE6_STREAM_MONITOR = software monitoring + IMPULSE = optional impulse response measurement + The opened flag indicates whether the buffer is allocated while + the running flag indicates whether the stream is running. + + For monitor or impulse operations, the driver needs to call + line6_pcm_acquire() or line6_pcm_release() with the appropriate + LINE6_STREAM_* flag. +*/ + +/* stream types */ +enum { + LINE6_STREAM_PCM, + LINE6_STREAM_MONITOR, + LINE6_STREAM_IMPULSE, + LINE6_STREAM_CAPTURE_HELPER, +}; + +/* misc bit flags for PCM operation */ +enum { + LINE6_FLAG_PAUSE_PLAYBACK, + LINE6_FLAG_PREPARED, +}; + +struct line6_pcm_properties { + struct snd_pcm_hardware playback_hw, capture_hw; + struct snd_pcm_hw_constraint_ratdens rates; + int bytes_per_channel; +}; + +struct line6_pcm_stream { + /* allocated URBs */ + struct urb **urbs; + + /* Temporary buffer; + * Since the packet size is not known in advance, this buffer is + * large enough to store maximum size packets. + */ + unsigned char *buffer; + + /* Free frame position in the buffer. */ + snd_pcm_uframes_t pos; + + /* Count processed bytes; + * This is modulo period size (to determine when a period is finished). + */ + unsigned bytes; + + /* Counter to create desired sample rate */ + unsigned count; + + /* period size in bytes */ + unsigned period; + + /* Processed frame position in the buffer; + * The contents of the ring buffer have been consumed by the USB + * subsystem (i.e., sent to the USB device) up to this position. + */ + snd_pcm_uframes_t pos_done; + + /* Bit mask of active URBs */ + unsigned long active_urbs; + + /* Bit mask of URBs currently being unlinked */ + unsigned long unlink_urbs; + + /* Spin lock to protect updates of the buffer positions (not contents) + */ + spinlock_t lock; + + /* Bit flags for operational stream types */ + unsigned long opened; + + /* Bit flags for running stream types */ + unsigned long running; + + int last_frame; +}; + +struct snd_line6_pcm { + /* Pointer back to the Line 6 driver data structure */ + struct usb_line6 *line6; + + /* Properties. */ + struct line6_pcm_properties *properties; + + /* ALSA pcm stream */ + struct snd_pcm *pcm; + + /* protection to state changes of in/out streams */ + struct mutex state_mutex; + + /* Capture and playback streams */ + struct line6_pcm_stream in; + struct line6_pcm_stream out; + + /* Previously captured frame (for software monitoring) */ + unsigned char *prev_fbuf; + + /* Size of previously captured frame (for software monitoring/sync) */ + int prev_fsize; + + /* Maximum size of USB packet */ + int max_packet_size_in; + int max_packet_size_out; + + /* PCM playback volume (left and right) */ + int volume_playback[2]; + + /* PCM monitor volume */ + int volume_monitor; + + /* Volume of impulse response test signal (if zero, test is disabled) */ + int impulse_volume; + + /* Period of impulse response test signal */ + int impulse_period; + + /* Counter for impulse response test signal */ + int impulse_count; + + /* Several status bits (see LINE6_FLAG_*) */ + unsigned long flags; +}; + +extern int line6_init_pcm(struct usb_line6 *line6, + struct line6_pcm_properties *properties); +extern int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd); +extern int snd_line6_prepare(struct snd_pcm_substream *substream); +extern int snd_line6_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params); +extern int snd_line6_hw_free(struct snd_pcm_substream *substream); +extern snd_pcm_uframes_t snd_line6_pointer(struct snd_pcm_substream *substream); +extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm); +extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int type, + bool start); +extern void line6_pcm_release(struct snd_line6_pcm *line6pcm, int type); + +#endif diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c new file mode 100644 index 000000000..3fb86318f --- /dev/null +++ b/sound/usb/line6/playback.c @@ -0,0 +1,444 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include + +#include "capture.h" +#include "driver.h" +#include "pcm.h" +#include "playback.h" + +/* + Software stereo volume control. +*/ +static void change_volume(struct urb *urb_out, int volume[], + int bytes_per_frame) +{ + int chn = 0; + + if (volume[0] == 256 && volume[1] == 256) + return; /* maximum volume - no change */ + + if (bytes_per_frame == 4) { + __le16 *p, *buf_end; + + p = (__le16 *)urb_out->transfer_buffer; + buf_end = p + urb_out->transfer_buffer_length / sizeof(*p); + + for (; p < buf_end; ++p) { + short pv = le16_to_cpu(*p); + int val = (pv * volume[chn & 1]) >> 8; + pv = clamp(val, -0x8000, 0x7fff); + *p = cpu_to_le16(pv); + ++chn; + } + } else if (bytes_per_frame == 6) { + unsigned char *p, *buf_end; + + p = (unsigned char *)urb_out->transfer_buffer; + buf_end = p + urb_out->transfer_buffer_length; + + for (; p < buf_end; p += 3) { + int val; + + val = p[0] + (p[1] << 8) + ((signed char)p[2] << 16); + val = (val * volume[chn & 1]) >> 8; + val = clamp(val, -0x800000, 0x7fffff); + p[0] = val; + p[1] = val >> 8; + p[2] = val >> 16; + ++chn; + } + } +} + +/* + Create signal for impulse response test. +*/ +static void create_impulse_test_signal(struct snd_line6_pcm *line6pcm, + struct urb *urb_out, int bytes_per_frame) +{ + int frames = urb_out->transfer_buffer_length / bytes_per_frame; + + if (bytes_per_frame == 4) { + int i; + short *pi = (short *)line6pcm->prev_fbuf; + short *po = (short *)urb_out->transfer_buffer; + + for (i = 0; i < frames; ++i) { + po[0] = pi[0]; + po[1] = 0; + pi += 2; + po += 2; + } + } else if (bytes_per_frame == 6) { + int i, j; + unsigned char *pi = line6pcm->prev_fbuf; + unsigned char *po = urb_out->transfer_buffer; + + for (i = 0; i < frames; ++i) { + for (j = 0; j < bytes_per_frame / 2; ++j) + po[j] = pi[j]; + + for (; j < bytes_per_frame; ++j) + po[j] = 0; + + pi += bytes_per_frame; + po += bytes_per_frame; + } + } + if (--line6pcm->impulse_count <= 0) { + ((unsigned char *)(urb_out->transfer_buffer))[bytes_per_frame - + 1] = + line6pcm->impulse_volume; + line6pcm->impulse_count = line6pcm->impulse_period; + } +} + +/* + Add signal to buffer for software monitoring. +*/ +static void add_monitor_signal(struct urb *urb_out, unsigned char *signal, + int volume, int bytes_per_frame) +{ + if (volume == 0) + return; /* zero volume - no change */ + + if (bytes_per_frame == 4) { + __le16 *pi, *po, *buf_end; + + pi = (__le16 *)signal; + po = (__le16 *)urb_out->transfer_buffer; + buf_end = po + urb_out->transfer_buffer_length / sizeof(*po); + + for (; po < buf_end; ++pi, ++po) { + short pov = le16_to_cpu(*po); + short piv = le16_to_cpu(*pi); + int val = pov + ((piv * volume) >> 8); + pov = clamp(val, -0x8000, 0x7fff); + *po = cpu_to_le16(pov); + } + } + + /* + We don't need to handle devices with 6 bytes per frame here + since they all support hardware monitoring. + */ +} + +/* + Find a free URB, prepare audio data, and submit URB. + must be called in line6pcm->out.lock context +*/ +static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) +{ + int index; + int i, urb_size, urb_frames; + int ret; + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max; + const int frame_increment = + line6pcm->properties->rates.rats[0].num_min; + const int frame_factor = + line6pcm->properties->rates.rats[0].den * + (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); + struct urb *urb_out; + + index = find_first_zero_bit(&line6pcm->out.active_urbs, + line6pcm->line6->iso_buffers); + + if (index < 0 || index >= line6pcm->line6->iso_buffers) { + dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); + return -EINVAL; + } + + urb_out = line6pcm->out.urbs[index]; + urb_size = 0; + + /* TODO: this may not work for LINE6_ISO_PACKETS != 1 */ + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + /* compute frame size for given sampling rate */ + int fsize = 0; + struct usb_iso_packet_descriptor *fout = + &urb_out->iso_frame_desc[i]; + + fsize = line6pcm->prev_fsize; + if (fsize == 0) { + int n; + + line6pcm->out.count += frame_increment; + n = line6pcm->out.count / frame_factor; + line6pcm->out.count -= n * frame_factor; + fsize = n; + } + + fsize *= bytes_per_frame; + + fout->offset = urb_size; + fout->length = fsize; + urb_size += fsize; + } + + if (urb_size == 0) { + /* can't determine URB size */ + dev_err(line6pcm->line6->ifcdev, "driver bug: urb_size = 0\n"); + return -EINVAL; + } + + urb_frames = urb_size / bytes_per_frame; + urb_out->transfer_buffer = + line6pcm->out.buffer + + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size_out; + urb_out->transfer_buffer_length = urb_size; + urb_out->context = line6pcm; + + if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running) && + !test_bit(LINE6_FLAG_PAUSE_PLAYBACK, &line6pcm->flags)) { + struct snd_pcm_runtime *runtime = + get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK)->runtime; + + if (line6pcm->out.pos + urb_frames > runtime->buffer_size) { + /* + The transferred area goes over buffer boundary, + copy the data to the temp buffer. + */ + int len; + + len = runtime->buffer_size - line6pcm->out.pos; + + if (len > 0) { + memcpy(urb_out->transfer_buffer, + runtime->dma_area + + line6pcm->out.pos * bytes_per_frame, + len * bytes_per_frame); + memcpy(urb_out->transfer_buffer + + len * bytes_per_frame, runtime->dma_area, + (urb_frames - len) * bytes_per_frame); + } else + dev_err(line6pcm->line6->ifcdev, "driver bug: len = %d\n", + len); + } else { + memcpy(urb_out->transfer_buffer, + runtime->dma_area + + line6pcm->out.pos * bytes_per_frame, + urb_out->transfer_buffer_length); + } + + line6pcm->out.pos += urb_frames; + if (line6pcm->out.pos >= runtime->buffer_size) + line6pcm->out.pos -= runtime->buffer_size; + + change_volume(urb_out, line6pcm->volume_playback, + bytes_per_frame); + } else { + memset(urb_out->transfer_buffer, 0, + urb_out->transfer_buffer_length); + } + + spin_lock_nested(&line6pcm->in.lock, SINGLE_DEPTH_NESTING); + if (line6pcm->prev_fbuf) { + if (test_bit(LINE6_STREAM_IMPULSE, &line6pcm->out.running)) { + create_impulse_test_signal(line6pcm, urb_out, + bytes_per_frame); + if (test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) { + line6_capture_copy(line6pcm, + urb_out->transfer_buffer, + urb_out-> + transfer_buffer_length); + line6_capture_check_period(line6pcm, + urb_out->transfer_buffer_length); + } + } else { + if (!(line6pcm->line6->properties->capabilities & LINE6_CAP_HWMON) + && line6pcm->out.running && line6pcm->in.running) + add_monitor_signal(urb_out, line6pcm->prev_fbuf, + line6pcm->volume_monitor, + bytes_per_frame); + } + line6pcm->prev_fbuf = NULL; + line6pcm->prev_fsize = 0; + } + spin_unlock(&line6pcm->in.lock); + + ret = usb_submit_urb(urb_out, GFP_ATOMIC); + + if (ret == 0) + set_bit(index, &line6pcm->out.active_urbs); + else + dev_err(line6pcm->line6->ifcdev, + "URB out #%d submission failed (%d)\n", index, ret); + + return 0; +} + +/* + Submit all currently available playback URBs. + must be called in line6pcm->out.lock context + */ +int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) +{ + int ret = 0, i; + + for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { + ret = submit_audio_out_urb(line6pcm); + if (ret < 0) + break; + } + + return ret; +} + +/* + Callback for completed playback URB. +*/ +static void audio_out_callback(struct urb *urb) +{ + int i, index, length = 0, shutdown = 0; + unsigned long flags; + struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; + struct snd_pcm_substream *substream = + get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK); + const int bytes_per_frame = + line6pcm->properties->bytes_per_channel * + line6pcm->properties->playback_hw.channels_max; + +#if USE_CLEAR_BUFFER_WORKAROUND + memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); +#endif + + line6pcm->out.last_frame = urb->start_frame; + + /* find index of URB */ + for (index = 0; index < line6pcm->line6->iso_buffers; index++) + if (urb == line6pcm->out.urbs[index]) + break; + + if (index >= line6pcm->line6->iso_buffers) + return; /* URB has been unlinked asynchronously */ + + for (i = 0; i < LINE6_ISO_PACKETS; i++) + length += urb->iso_frame_desc[i].length; + + spin_lock_irqsave(&line6pcm->out.lock, flags); + + if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) { + struct snd_pcm_runtime *runtime = substream->runtime; + + line6pcm->out.pos_done += + length / bytes_per_frame; + + if (line6pcm->out.pos_done >= runtime->buffer_size) + line6pcm->out.pos_done -= runtime->buffer_size; + } + + clear_bit(index, &line6pcm->out.active_urbs); + + for (i = 0; i < LINE6_ISO_PACKETS; i++) + if (urb->iso_frame_desc[i].status == -EXDEV) { + shutdown = 1; + break; + } + + if (test_and_clear_bit(index, &line6pcm->out.unlink_urbs)) + shutdown = 1; + + if (!shutdown) { + submit_audio_out_urb(line6pcm); + + if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) { + line6pcm->out.bytes += length; + if (line6pcm->out.bytes >= line6pcm->out.period) { + line6pcm->out.bytes %= line6pcm->out.period; + spin_unlock(&line6pcm->out.lock); + snd_pcm_period_elapsed(substream); + spin_lock(&line6pcm->out.lock); + } + } + } + spin_unlock_irqrestore(&line6pcm->out.lock, flags); +} + +/* open playback callback */ +static int snd_line6_playback_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + err = snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &line6pcm->properties->rates); + if (err < 0) + return err; + + runtime->hw = line6pcm->properties->playback_hw; + return 0; +} + +/* close playback callback */ +static int snd_line6_playback_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* playback operators */ +const struct snd_pcm_ops snd_line6_playback_ops = { + .open = snd_line6_playback_open, + .close = snd_line6_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_line6_hw_params, + .hw_free = snd_line6_hw_free, + .prepare = snd_line6_prepare, + .trigger = snd_line6_trigger, + .pointer = snd_line6_pointer, +}; + +int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + struct usb_line6 *line6 = line6pcm->line6; + int i; + + line6pcm->out.urbs = kcalloc(line6->iso_buffers, sizeof(struct urb *), + GFP_KERNEL); + if (line6pcm->out.urbs == NULL) + return -ENOMEM; + + /* create audio URBs and fill in constant values: */ + for (i = 0; i < line6->iso_buffers; ++i) { + struct urb *urb; + + /* URB for audio out: */ + urb = line6pcm->out.urbs[i] = + usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL); + + if (urb == NULL) + return -ENOMEM; + + urb->dev = line6->usbdev; + urb->pipe = + usb_sndisocpipe(line6->usbdev, + line6->properties->ep_audio_w & + USB_ENDPOINT_NUMBER_MASK); + urb->transfer_flags = URB_ISO_ASAP; + urb->start_frame = -1; + urb->number_of_packets = LINE6_ISO_PACKETS; + urb->interval = LINE6_ISO_INTERVAL; + urb->error_count = 0; + urb->complete = audio_out_callback; + if (usb_urb_ep_type_check(urb)) + return -EINVAL; + } + + return 0; +} diff --git a/sound/usb/line6/playback.h b/sound/usb/line6/playback.h new file mode 100644 index 000000000..d8d3b8a07 --- /dev/null +++ b/sound/usb/line6/playback.h @@ -0,0 +1,35 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef PLAYBACK_H +#define PLAYBACK_H + +#include + +#include "driver.h" + +/* + * When the TonePort is used with jack in full duplex mode and the outputs are + * not connected, the software monitor produces an ugly noise since everything + * written to the output buffer (i.e., the input signal) will be repeated in + * the next period (sounds like a delay effect). As a workaround, the output + * buffer is cleared after the data have been read, but there must be a better + * solution. Until one is found, this workaround can be used to fix the + * problem. + */ +#define USE_CLEAR_BUFFER_WORKAROUND 1 + +extern const struct snd_pcm_ops snd_line6_playback_ops; + +extern int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm); +extern int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm); + +#endif diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c new file mode 100644 index 000000000..dff8e7d5f --- /dev/null +++ b/sound/usb/line6/pod.c @@ -0,0 +1,584 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "capture.h" +#include "driver.h" +#include "playback.h" + +/* + Locate name in binary program dump +*/ +#define POD_NAME_OFFSET 0 +#define POD_NAME_LENGTH 16 + +/* + Other constants +*/ +#define POD_CONTROL_SIZE 0x80 +#define POD_BUFSIZE_DUMPREQ 7 +#define POD_STARTUP_DELAY 1000 + +/* + Stages of POD startup procedure +*/ +enum { + POD_STARTUP_INIT = 1, + POD_STARTUP_VERSIONREQ, + POD_STARTUP_WORKQUEUE, + POD_STARTUP_SETUP, + POD_STARTUP_LAST = POD_STARTUP_SETUP - 1 +}; + +enum { + LINE6_BASSPODXT, + LINE6_BASSPODXTLIVE, + LINE6_BASSPODXTPRO, + LINE6_POCKETPOD, + LINE6_PODXT, + LINE6_PODXTLIVE_POD, + LINE6_PODXTPRO, +}; + +struct usb_line6_pod { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Instrument monitor level */ + int monitor_level; + + /* Timer for device initialization */ + struct timer_list startup_timer; + + /* Work handler for device initialization */ + struct work_struct startup_work; + + /* Current progress in startup procedure */ + int startup_progress; + + /* Serial number of device */ + u32 serial_number; + + /* Firmware version (x 100) */ + int firmware_version; + + /* Device ID */ + int device_id; +}; + +#define POD_SYSEX_CODE 3 + +/* *INDENT-OFF* */ + +enum { + POD_SYSEX_SAVE = 0x24, + POD_SYSEX_SYSTEM = 0x56, + POD_SYSEX_SYSTEMREQ = 0x57, + /* POD_SYSEX_UPDATE = 0x6c, */ /* software update! */ + POD_SYSEX_STORE = 0x71, + POD_SYSEX_FINISH = 0x72, + POD_SYSEX_DUMPMEM = 0x73, + POD_SYSEX_DUMP = 0x74, + POD_SYSEX_DUMPREQ = 0x75 + + /* dumps entire internal memory of PODxt Pro */ + /* POD_SYSEX_DUMPMEM2 = 0x76 */ +}; + +enum { + POD_MONITOR_LEVEL = 0x04, + POD_SYSTEM_INVALID = 0x10000 +}; + +/* *INDENT-ON* */ + +enum { + POD_DUMP_MEMORY = 2 +}; + +enum { + POD_BUSY_READ, + POD_BUSY_WRITE, + POD_CHANNEL_DIRTY, + POD_SAVE_PRESSED, + POD_BUSY_MIDISEND +}; + +static struct snd_ratden pod_ratden = { + .num_min = 78125, + .num_max = 78125, + .num_step = 1, + .den = 2 +}; + +static struct line6_pcm_properties pod_pcm_properties = { + .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 | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 39062, + .rate_max = 39063, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_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_S24_3LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 39062, + .rate_max = 39063, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &pod_ratden}, + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ +}; + +static const char pod_version_header[] = { + 0xf2, 0x7e, 0x7f, 0x06, 0x02 +}; + +/* forward declarations: */ +static void pod_startup2(struct timer_list *t); +static void pod_startup3(struct usb_line6_pod *pod); + +static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code, + int size) +{ + return line6_alloc_sysex_buffer(&pod->line6, POD_SYSEX_CODE, code, + size); +} + +/* + Process a completely received message. +*/ +static void line6_pod_process_message(struct usb_line6 *line6) +{ + struct usb_line6_pod *pod = (struct usb_line6_pod *) line6; + const unsigned char *buf = pod->line6.buffer_message; + + if (memcmp(buf, pod_version_header, sizeof(pod_version_header)) == 0) { + pod->firmware_version = buf[13] * 100 + buf[14] * 10 + buf[15]; + pod->device_id = ((int)buf[8] << 16) | ((int)buf[9] << 8) | + (int) buf[10]; + pod_startup3(pod); + return; + } + + /* Only look for sysex messages from this device */ + if (buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE) && + buf[0] != (LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN)) { + return; + } + if (memcmp(buf + 1, line6_midi_id, sizeof(line6_midi_id)) != 0) + return; + + if (buf[5] == POD_SYSEX_SYSTEM && buf[6] == POD_MONITOR_LEVEL) { + short value = ((int)buf[7] << 12) | ((int)buf[8] << 8) | + ((int)buf[9] << 4) | (int)buf[10]; + pod->monitor_level = value; + } +} + +/* + Send system parameter (from integer). +*/ +static int pod_set_system_param_int(struct usb_line6_pod *pod, int value, + int code) +{ + char *sysex; + static const int size = 5; + + sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size); + if (!sysex) + return -ENOMEM; + sysex[SYSEX_DATA_OFS] = code; + sysex[SYSEX_DATA_OFS + 1] = (value >> 12) & 0x0f; + sysex[SYSEX_DATA_OFS + 2] = (value >> 8) & 0x0f; + sysex[SYSEX_DATA_OFS + 3] = (value >> 4) & 0x0f; + sysex[SYSEX_DATA_OFS + 4] = (value) & 0x0f; + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + return 0; +} + +/* + "read" request on "serial_number" special file. +*/ +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_pod *pod = card->private_data; + + return sprintf(buf, "%u\n", pod->serial_number); +} + +/* + "read" request on "firmware_version" special file. +*/ +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_pod *pod = card->private_data; + + return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100, + pod->firmware_version % 100); +} + +/* + "read" request on "device_id" special file. +*/ +static ssize_t device_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_pod *pod = card->private_data; + + return sprintf(buf, "%d\n", pod->device_id); +} + +/* + POD startup procedure. + This is a sequence of functions with special requirements (e.g., must + not run immediately after initialization, must not run in interrupt + context). After the last one has finished, the device is ready to use. +*/ + +static void pod_startup1(struct usb_line6_pod *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&pod->startup_timer, POD_STARTUP_DELAY, pod_startup2); +} + +static void pod_startup2(struct timer_list *t) +{ + struct usb_line6_pod *pod = from_timer(pod, t, startup_timer); + struct usb_line6 *line6 = &pod->line6; + + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_VERSIONREQ); + + /* request firmware version: */ + line6_version_request_async(line6); +} + +static void pod_startup3(struct usb_line6_pod *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&pod->startup_work); +} + +static void pod_startup4(struct work_struct *work) +{ + struct usb_line6_pod *pod = + container_of(work, struct usb_line6_pod, startup_work); + struct usb_line6 *line6 = &pod->line6; + + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_SETUP); + + /* serial number: */ + line6_read_serial_number(&pod->line6, &pod->serial_number); + + /* ALSA audio interface: */ + snd_card_register(line6->card); +} + +/* POD special files: */ +static DEVICE_ATTR_RO(device_id); +static DEVICE_ATTR_RO(firmware_version); +static DEVICE_ATTR_RO(serial_number); + +static struct attribute *pod_dev_attrs[] = { + &dev_attr_device_id.attr, + &dev_attr_firmware_version.attr, + &dev_attr_serial_number.attr, + NULL +}; + +static const struct attribute_group pod_dev_attr_group = { + .name = "pod", + .attrs = pod_dev_attrs, +}; + +/* control info callback */ +static int snd_pod_control_monitor_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 = 65535; + return 0; +} + +/* control get callback */ +static int snd_pod_control_monitor_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; + + ucontrol->value.integer.value[0] = pod->monitor_level; + return 0; +} + +/* control put callback */ +static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; + + if (ucontrol->value.integer.value[0] == pod->monitor_level) + return 0; + + pod->monitor_level = ucontrol->value.integer.value[0]; + pod_set_system_param_int(pod, ucontrol->value.integer.value[0], + POD_MONITOR_LEVEL); + return 1; +} + +/* control definition */ +static const struct snd_kcontrol_new pod_control_monitor = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_pod_control_monitor_info, + .get = snd_pod_control_monitor_get, + .put = snd_pod_control_monitor_put +}; + +/* + POD device disconnected. +*/ +static void line6_pod_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_pod *pod = (struct usb_line6_pod *)line6; + + del_timer_sync(&pod->startup_timer); + cancel_work_sync(&pod->startup_work); +} + +/* + Try to init POD device. +*/ +static int pod_init(struct usb_line6 *line6, + const struct usb_device_id *id) +{ + int err; + struct usb_line6_pod *pod = (struct usb_line6_pod *) line6; + + line6->process_message = line6_pod_process_message; + line6->disconnect = line6_pod_disconnect; + + timer_setup(&pod->startup_timer, NULL, 0); + INIT_WORK(&pod->startup_work, pod_startup4); + + /* create sysfs entries: */ + err = snd_card_add_dev_attr(line6->card, &pod_dev_attr_group); + if (err < 0) + return err; + + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, &pod_pcm_properties); + if (err < 0) + return err; + + /* register monitor control: */ + err = snd_ctl_add(line6->card, + snd_ctl_new1(&pod_control_monitor, line6->line6pcm)); + if (err < 0) + return err; + + /* + When the sound card is registered at this point, the PODxt Live + displays "Invalid Code Error 07", so we do it later in the event + handler. + */ + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + pod->monitor_level = POD_SYSTEM_INVALID; + + /* initiate startup procedure: */ + pod_startup1(pod); + } + + return 0; +} + +#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) +#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) + +/* table of devices that work with this driver */ +static const struct usb_device_id pod_id_table[] = { + { LINE6_DEVICE(0x4250), .driver_info = LINE6_BASSPODXT }, + { LINE6_DEVICE(0x4642), .driver_info = LINE6_BASSPODXTLIVE }, + { LINE6_DEVICE(0x4252), .driver_info = LINE6_BASSPODXTPRO }, + { LINE6_IF_NUM(0x5051, 1), .driver_info = LINE6_POCKETPOD }, + { LINE6_DEVICE(0x5044), .driver_info = LINE6_PODXT }, + { LINE6_IF_NUM(0x4650, 0), .driver_info = LINE6_PODXTLIVE_POD }, + { LINE6_DEVICE(0x5050), .driver_info = LINE6_PODXTPRO }, + {} +}; + +MODULE_DEVICE_TABLE(usb, pod_id_table); + +static const struct line6_properties pod_properties_table[] = { + [LINE6_BASSPODXT] = { + .id = "BassPODxt", + .name = "BassPODxt", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI + | LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 5, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_BASSPODXTLIVE] = { + .id = "BassPODxtLive", + .name = "BassPODxt Live", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI + | LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 1, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_BASSPODXTPRO] = { + .id = "BassPODxtPro", + .name = "BassPODxt Pro", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI + | LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 5, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_POCKETPOD] = { + .id = "PocketPOD", + .name = "Pocket POD", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, + .altsetting = 0, + .ep_ctrl_r = 0x82, + .ep_ctrl_w = 0x02, + /* no audio channel */ + }, + [LINE6_PODXT] = { + .id = "PODxt", + .name = "PODxt", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI + | LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 5, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_PODXTLIVE_POD] = { + .id = "PODxtLive", + .name = "PODxt Live", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI + | LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 1, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_PODXTPRO] = { + .id = "PODxtPro", + .name = "PODxt Pro", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI + | LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 5, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, +}; + +/* + Probe USB device. +*/ +static int pod_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + return line6_probe(interface, id, "Line6-POD", + &pod_properties_table[id->driver_info], + pod_init, sizeof(struct usb_line6_pod)); +} + +static struct usb_driver pod_driver = { + .name = KBUILD_MODNAME, + .probe = pod_probe, + .disconnect = line6_disconnect, +#ifdef CONFIG_PM + .suspend = line6_suspend, + .resume = line6_resume, + .reset_resume = line6_resume, +#endif + .id_table = pod_id_table, +}; + +module_usb_driver(pod_driver); + +MODULE_DESCRIPTION("Line 6 POD USB driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c new file mode 100644 index 000000000..2806808d8 --- /dev/null +++ b/sound/usb/line6/podhd.c @@ -0,0 +1,513 @@ +/* + * Line 6 Pod HD + * + * Copyright (C) 2011 Stefan Hajnoczi + * Copyright (C) 2015 Andrej Krutak + * Copyright (C) 2017 Hans P. Moller + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include + +#include "driver.h" +#include "pcm.h" + +#define PODHD_STARTUP_DELAY 500 + +/* + * Stages of POD startup procedure + */ +enum { + PODHD_STARTUP_INIT = 1, + PODHD_STARTUP_SCHEDULE_WORKQUEUE, + PODHD_STARTUP_SETUP, + PODHD_STARTUP_LAST = PODHD_STARTUP_SETUP - 1 +}; + +enum { + LINE6_PODHD300, + LINE6_PODHD400, + LINE6_PODHD500_0, + LINE6_PODHD500_1, + LINE6_PODX3, + LINE6_PODX3LIVE, + LINE6_PODHD500X, + LINE6_PODHDDESKTOP +}; + +struct usb_line6_podhd { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Timer for device initialization */ + struct timer_list startup_timer; + + /* Work handler for device initialization */ + struct work_struct startup_work; + + /* Current progress in startup procedure */ + int startup_progress; + + /* Serial number of device */ + u32 serial_number; + + /* Firmware version */ + int firmware_version; +}; + +static struct snd_ratden podhd_ratden = { + .num_min = 48000, + .num_max = 48000, + .num_step = 1, + .den = 1, +}; + +static struct line6_pcm_properties podhd_pcm_properties = { + .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 | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_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_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &podhd_ratden}, + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ +}; + +static struct line6_pcm_properties podx3_pcm_properties = { + .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 | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_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_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + /* 1+2: Main signal (out), 3+4: Tone 1, + * 5+6: Tone 2, 7+8: raw + */ + .channels_min = 8, + .channels_max = 8, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &podhd_ratden}, + .bytes_per_channel = 3 /* SNDRV_PCM_FMTBIT_S24_3LE */ +}; +static struct usb_driver podhd_driver; + +static void podhd_startup_start_workqueue(struct timer_list *t); +static void podhd_startup_workqueue(struct work_struct *work); +static int podhd_startup_finalize(struct usb_line6_podhd *pod); + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%u\n", pod->serial_number); +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = dev_to_snd_card(dev); + struct usb_line6_podhd *pod = card->private_data; + + return sprintf(buf, "%06x\n", pod->firmware_version); +} + +static DEVICE_ATTR_RO(firmware_version); +static DEVICE_ATTR_RO(serial_number); + +static struct attribute *podhd_dev_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_serial_number.attr, + NULL +}; + +static const struct attribute_group podhd_dev_attr_group = { + .name = "podhd", + .attrs = podhd_dev_attrs, +}; + +/* + * POD X3 startup procedure. + * + * May be compatible with other POD HD's, since it's also similar to the + * previous POD setup. In any case, it doesn't seem to be required for the + * audio nor bulk interfaces to work. + */ + +static void podhd_startup(struct usb_line6_podhd *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&pod->startup_timer, PODHD_STARTUP_DELAY, + podhd_startup_start_workqueue); +} + +static void podhd_startup_start_workqueue(struct timer_list *t) +{ + struct usb_line6_podhd *pod = from_timer(pod, t, startup_timer); + + CHECK_STARTUP_PROGRESS(pod->startup_progress, + PODHD_STARTUP_SCHEDULE_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&pod->startup_work); +} + +static int podhd_dev_start(struct usb_line6_podhd *pod) +{ + int ret; + u8 *init_bytes; + int i; + struct usb_device *usbdev = pod->line6.usbdev; + + init_bytes = kmalloc(8, GFP_KERNEL); + if (!init_bytes) + return -ENOMEM; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + 0x67, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x11, 0, + NULL, 0, LINE6_TIMEOUT); + if (ret < 0) { + dev_err(pod->line6.ifcdev, "read request failed (error %d)\n", ret); + goto exit; + } + + /* NOTE: looks like some kind of ping message */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x11, 0x0, + init_bytes, 3, LINE6_TIMEOUT); + if (ret < 0) { + dev_err(pod->line6.ifcdev, + "receive length failed (error %d)\n", ret); + goto exit; + } + + pod->firmware_version = + (init_bytes[0] << 16) | (init_bytes[1] << 8) | (init_bytes[2] << 0); + + for (i = 0; i <= 16; i++) { + ret = line6_read_data(&pod->line6, 0xf000 + 0x08 * i, init_bytes, 8); + if (ret < 0) + goto exit; + } + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), + USB_REQ_SET_FEATURE, + USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_DIR_OUT, + 1, 0, + NULL, 0, LINE6_TIMEOUT); +exit: + kfree(init_bytes); + return ret; +} + +static void podhd_startup_workqueue(struct work_struct *work) +{ + struct usb_line6_podhd *pod = + container_of(work, struct usb_line6_podhd, startup_work); + + CHECK_STARTUP_PROGRESS(pod->startup_progress, PODHD_STARTUP_SETUP); + + podhd_dev_start(pod); + line6_read_serial_number(&pod->line6, &pod->serial_number); + + podhd_startup_finalize(pod); +} + +static int podhd_startup_finalize(struct usb_line6_podhd *pod) +{ + struct usb_line6 *line6 = &pod->line6; + + /* ALSA audio interface: */ + return snd_card_register(line6->card); +} + +static void podhd_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_podhd *pod = (struct usb_line6_podhd *)line6; + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) { + struct usb_interface *intf; + + del_timer_sync(&pod->startup_timer); + cancel_work_sync(&pod->startup_work); + + intf = usb_ifnum_to_if(line6->usbdev, + pod->line6.properties->ctrl_if); + if (intf) + usb_driver_release_interface(&podhd_driver, intf); + } +} + +/* + Try to init POD HD device. +*/ +static int podhd_init(struct usb_line6 *line6, + const struct usb_device_id *id) +{ + int err; + struct usb_line6_podhd *pod = (struct usb_line6_podhd *) line6; + struct usb_interface *intf; + + line6->disconnect = podhd_disconnect; + + timer_setup(&pod->startup_timer, NULL, 0); + INIT_WORK(&pod->startup_work, podhd_startup_workqueue); + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL) { + /* claim the data interface */ + intf = usb_ifnum_to_if(line6->usbdev, + pod->line6.properties->ctrl_if); + if (!intf) { + dev_err(pod->line6.ifcdev, "interface %d not found\n", + pod->line6.properties->ctrl_if); + return -ENODEV; + } + + err = usb_driver_claim_interface(&podhd_driver, intf, NULL); + if (err != 0) { + dev_err(pod->line6.ifcdev, "can't claim interface %d, error %d\n", + pod->line6.properties->ctrl_if, err); + return err; + } + } + + if (pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO) { + /* create sysfs entries: */ + err = snd_card_add_dev_attr(line6->card, &podhd_dev_attr_group); + if (err < 0) + return err; + } + + if (pod->line6.properties->capabilities & LINE6_CAP_PCM) { + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, + (id->driver_info == LINE6_PODX3 || + id->driver_info == LINE6_PODX3LIVE) ? &podx3_pcm_properties : + &podhd_pcm_properties); + if (err < 0) + return err; + } + + if (!(pod->line6.properties->capabilities & LINE6_CAP_CONTROL_INFO)) { + /* register USB audio system directly */ + return podhd_startup_finalize(pod); + } + + /* init device and delay registering */ + podhd_startup(pod); + return 0; +} + +#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) +#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) + +/* table of devices that work with this driver */ +static const struct usb_device_id podhd_id_table[] = { + /* TODO: no need to alloc data interfaces when only audio is used */ + { LINE6_DEVICE(0x5057), .driver_info = LINE6_PODHD300 }, + { LINE6_DEVICE(0x5058), .driver_info = LINE6_PODHD400 }, + { LINE6_IF_NUM(0x414D, 0), .driver_info = LINE6_PODHD500_0 }, + { LINE6_IF_NUM(0x414D, 1), .driver_info = LINE6_PODHD500_1 }, + { LINE6_IF_NUM(0x414A, 0), .driver_info = LINE6_PODX3 }, + { LINE6_IF_NUM(0x414B, 0), .driver_info = LINE6_PODX3LIVE }, + { LINE6_IF_NUM(0x4159, 0), .driver_info = LINE6_PODHD500X }, + { LINE6_IF_NUM(0x4156, 0), .driver_info = LINE6_PODHDDESKTOP }, + {} +}; + +MODULE_DEVICE_TABLE(usb, podhd_id_table); + +static const struct line6_properties podhd_properties_table[] = { + [LINE6_PODHD300] = { + .id = "PODHD300", + .name = "POD HD300", + .capabilities = LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 5, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_PODHD400] = { + .id = "PODHD400", + .name = "POD HD400", + .capabilities = LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 5, + .ep_ctrl_r = 0x84, + .ep_ctrl_w = 0x03, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_PODHD500_0] = { + .id = "PODHD500", + .name = "POD HD500", + .capabilities = LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 0, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, + [LINE6_PODHD500_1] = { + .id = "PODHD500", + .name = "POD HD500", + .capabilities = LINE6_CAP_PCM + | LINE6_CAP_HWMON, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, + [LINE6_PODX3] = { + .id = "PODX3", + .name = "POD X3", + .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ctrl_if = 1, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, + [LINE6_PODX3LIVE] = { + .id = "PODX3LIVE", + .name = "POD X3 LIVE", + .capabilities = LINE6_CAP_CONTROL | LINE6_CAP_CONTROL_INFO + | LINE6_CAP_PCM | LINE6_CAP_HWMON | LINE6_CAP_IN_NEEDS_OUT, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ctrl_if = 1, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, + [LINE6_PODHD500X] = { + .id = "PODHD500X", + .name = "POD HD500X", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ctrl_if = 1, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, + [LINE6_PODHDDESKTOP] = { + .id = "PODHDDESKTOP", + .name = "POD HDDESKTOP", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_PCM | LINE6_CAP_HWMON, + .altsetting = 1, + .ep_ctrl_r = 0x81, + .ep_ctrl_w = 0x01, + .ctrl_if = 1, + .ep_audio_r = 0x86, + .ep_audio_w = 0x02, + }, +}; + +/* + Probe USB device. +*/ +static int podhd_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + return line6_probe(interface, id, "Line6-PODHD", + &podhd_properties_table[id->driver_info], + podhd_init, sizeof(struct usb_line6_podhd)); +} + +static struct usb_driver podhd_driver = { + .name = KBUILD_MODNAME, + .probe = podhd_probe, + .disconnect = line6_disconnect, +#ifdef CONFIG_PM + .suspend = line6_suspend, + .resume = line6_resume, + .reset_resume = line6_resume, +#endif + .id_table = podhd_id_table, +}; + +module_usb_driver(podhd_driver); + +MODULE_DESCRIPTION("Line 6 PODHD USB driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/line6/toneport.c b/sound/usb/line6/toneport.c new file mode 100644 index 000000000..cbb2e66be --- /dev/null +++ b/sound/usb/line6/toneport.c @@ -0,0 +1,585 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * Emil Myhrman (emil.myhrman@gmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "capture.h" +#include "driver.h" +#include "playback.h" + +enum line6_device_type { + LINE6_GUITARPORT, + LINE6_PODSTUDIO_GX, + LINE6_PODSTUDIO_UX1, + LINE6_PODSTUDIO_UX2, + LINE6_TONEPORT_GX, + LINE6_TONEPORT_UX1, + LINE6_TONEPORT_UX2, +}; + +struct usb_line6_toneport; + +struct toneport_led { + struct led_classdev dev; + char name[64]; + struct usb_line6_toneport *toneport; + bool registered; +}; + +struct usb_line6_toneport { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Source selector */ + int source; + + /* Serial number of device */ + u32 serial_number; + + /* Firmware version (x 100) */ + u8 firmware_version; + + /* Device type */ + enum line6_device_type type; + + /* LED instances */ + struct toneport_led leds[2]; +}; + +static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2); + +#define TONEPORT_PCM_DELAY 1 + +static struct snd_ratden toneport_ratden = { + .num_min = 44100, + .num_max = 44100, + .num_step = 1, + .den = 1 +}; + +static struct line6_pcm_properties toneport_pcm_properties = { + .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 | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .capture_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, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .rates = { + .nrats = 1, + .rats = &toneport_ratden}, + .bytes_per_channel = 2 +}; + +static const struct { + const char *name; + int code; +} toneport_source_info[] = { + {"Microphone", 0x0a01}, + {"Line", 0x0801}, + {"Instrument", 0x0b01}, + {"Inst & Mic", 0x0901} +}; + +static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2) +{ + int ret; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + cmd1, cmd2, NULL, 0, LINE6_TIMEOUT); + + if (ret < 0) { + dev_err(&usbdev->dev, "send failed (error %d)\n", ret); + return ret; + } + + return 0; +} + +/* monitor info callback */ +static int snd_toneport_monitor_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 = 256; + return 0; +} + +/* monitor get callback */ +static int snd_toneport_monitor_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = line6pcm->volume_monitor; + return 0; +} + +/* monitor put callback */ +static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + int err; + + if (ucontrol->value.integer.value[0] == line6pcm->volume_monitor) + return 0; + + line6pcm->volume_monitor = ucontrol->value.integer.value[0]; + + if (line6pcm->volume_monitor > 0) { + err = line6_pcm_acquire(line6pcm, LINE6_STREAM_MONITOR, true); + if (err < 0) { + line6pcm->volume_monitor = 0; + line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR); + return err; + } + } else { + line6_pcm_release(line6pcm, LINE6_STREAM_MONITOR); + } + + return 1; +} + +/* source info callback */ +static int snd_toneport_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const int size = ARRAY_SIZE(toneport_source_info); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = size; + + if (uinfo->value.enumerated.item >= size) + uinfo->value.enumerated.item = size - 1; + + strcpy(uinfo->value.enumerated.name, + toneport_source_info[uinfo->value.enumerated.item].name); + + return 0; +} + +/* source get callback */ +static int snd_toneport_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_toneport *toneport = + (struct usb_line6_toneport *)line6pcm->line6; + ucontrol->value.enumerated.item[0] = toneport->source; + return 0; +} + +/* source put callback */ +static int snd_toneport_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_toneport *toneport = + (struct usb_line6_toneport *)line6pcm->line6; + unsigned int source; + + source = ucontrol->value.enumerated.item[0]; + if (source >= ARRAY_SIZE(toneport_source_info)) + return -EINVAL; + if (source == toneport->source) + return 0; + + toneport->source = source; + toneport_send_cmd(toneport->line6.usbdev, + toneport_source_info[source].code, 0x0000); + return 1; +} + +static void toneport_startup(struct usb_line6 *line6) +{ + line6_pcm_acquire(line6->line6pcm, LINE6_STREAM_MONITOR, true); +} + +/* control definition */ +static const struct snd_kcontrol_new toneport_control_monitor = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_toneport_monitor_info, + .get = snd_toneport_monitor_get, + .put = snd_toneport_monitor_put +}; + +/* source selector definition */ +static const struct snd_kcontrol_new toneport_control_source = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Source", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_toneport_source_info, + .get = snd_toneport_source_get, + .put = snd_toneport_source_put +}; + +/* + For the led on Guitarport. + Brightness goes from 0x00 to 0x26. Set a value above this to have led + blink. + (void cmd_0x02(byte red, byte green) +*/ + +static bool toneport_has_led(struct usb_line6_toneport *toneport) +{ + switch (toneport->type) { + case LINE6_GUITARPORT: + case LINE6_TONEPORT_GX: + /* add your device here if you are missing support for the LEDs */ + return true; + + default: + return false; + } +} + +static const char * const led_colors[2] = { "red", "green" }; +static const int led_init_vals[2] = { 0x00, 0x26 }; + +static void toneport_update_led(struct usb_line6_toneport *toneport) +{ + toneport_send_cmd(toneport->line6.usbdev, + (toneport->leds[0].dev.brightness << 8) | 0x0002, + toneport->leds[1].dev.brightness); +} + +static void toneport_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct toneport_led *leds = + container_of(led_cdev, struct toneport_led, dev); + toneport_update_led(leds->toneport); +} + +static int toneport_init_leds(struct usb_line6_toneport *toneport) +{ + struct device *dev = &toneport->line6.usbdev->dev; + int i, err; + + for (i = 0; i < 2; i++) { + struct toneport_led *led = &toneport->leds[i]; + struct led_classdev *leddev = &led->dev; + + led->toneport = toneport; + snprintf(led->name, sizeof(led->name), "%s::%s", + dev_name(dev), led_colors[i]); + leddev->name = led->name; + leddev->brightness = led_init_vals[i]; + leddev->max_brightness = 0x26; + leddev->brightness_set = toneport_led_brightness_set; + err = led_classdev_register(dev, leddev); + if (err) + return err; + led->registered = true; + } + + return 0; +} + +static void toneport_remove_leds(struct usb_line6_toneport *toneport) +{ + struct toneport_led *led; + int i; + + for (i = 0; i < 2; i++) { + led = &toneport->leds[i]; + if (!led->registered) + break; + led_classdev_unregister(&led->dev); + led->registered = false; + } +} + +static bool toneport_has_source_select(struct usb_line6_toneport *toneport) +{ + switch (toneport->type) { + case LINE6_TONEPORT_UX1: + case LINE6_TONEPORT_UX2: + case LINE6_PODSTUDIO_UX1: + case LINE6_PODSTUDIO_UX2: + return true; + + default: + return false; + } +} + +/* + Setup Toneport device. +*/ +static int toneport_setup(struct usb_line6_toneport *toneport) +{ + u32 *ticks; + struct usb_line6 *line6 = &toneport->line6; + struct usb_device *usbdev = line6->usbdev; + + ticks = kmalloc(sizeof(*ticks), GFP_KERNEL); + if (!ticks) + return -ENOMEM; + + /* sync time on device with host: */ + /* note: 32-bit timestamps overflow in year 2106 */ + *ticks = (u32)ktime_get_real_seconds(); + line6_write_data(line6, 0x80c6, ticks, 4); + kfree(ticks); + + /* enable device: */ + toneport_send_cmd(usbdev, 0x0301, 0x0000); + + /* initialize source select: */ + if (toneport_has_source_select(toneport)) + toneport_send_cmd(usbdev, + toneport_source_info[toneport->source].code, + 0x0000); + + if (toneport_has_led(toneport)) + toneport_update_led(toneport); + + schedule_delayed_work(&toneport->line6.startup_work, + msecs_to_jiffies(TONEPORT_PCM_DELAY * 1000)); + return 0; +} + +/* + Toneport device disconnected. +*/ +static void line6_toneport_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_toneport *toneport = + (struct usb_line6_toneport *)line6; + + if (toneport_has_led(toneport)) + toneport_remove_leds(toneport); +} + + +/* + Try to init Toneport device. +*/ +static int toneport_init(struct usb_line6 *line6, + const struct usb_device_id *id) +{ + int err; + struct usb_line6_toneport *toneport = (struct usb_line6_toneport *) line6; + + toneport->type = id->driver_info; + + line6->disconnect = line6_toneport_disconnect; + line6->startup = toneport_startup; + + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, &toneport_pcm_properties); + if (err < 0) + return err; + + /* register monitor control: */ + err = snd_ctl_add(line6->card, + snd_ctl_new1(&toneport_control_monitor, + line6->line6pcm)); + if (err < 0) + return err; + + /* register source select control: */ + if (toneport_has_source_select(toneport)) { + err = + snd_ctl_add(line6->card, + snd_ctl_new1(&toneport_control_source, + line6->line6pcm)); + if (err < 0) + return err; + } + + line6_read_serial_number(line6, &toneport->serial_number); + line6_read_data(line6, 0x80c2, &toneport->firmware_version, 1); + + if (toneport_has_led(toneport)) { + err = toneport_init_leds(toneport); + if (err < 0) + return err; + } + + err = toneport_setup(toneport); + if (err) + return err; + + /* register audio system: */ + return snd_card_register(line6->card); +} + +#ifdef CONFIG_PM +/* + Resume Toneport device after reset. +*/ +static int toneport_reset_resume(struct usb_interface *interface) +{ + int err; + + err = toneport_setup(usb_get_intfdata(interface)); + if (err) + return err; + return line6_resume(interface); +} +#endif + +#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) +#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) + +/* table of devices that work with this driver */ +static const struct usb_device_id toneport_id_table[] = { + { LINE6_DEVICE(0x4750), .driver_info = LINE6_GUITARPORT }, + { LINE6_DEVICE(0x4153), .driver_info = LINE6_PODSTUDIO_GX }, + { LINE6_DEVICE(0x4150), .driver_info = LINE6_PODSTUDIO_UX1 }, + { LINE6_IF_NUM(0x4151, 0), .driver_info = LINE6_PODSTUDIO_UX2 }, + { LINE6_DEVICE(0x4147), .driver_info = LINE6_TONEPORT_GX }, + { LINE6_DEVICE(0x4141), .driver_info = LINE6_TONEPORT_UX1 }, + { LINE6_IF_NUM(0x4142, 0), .driver_info = LINE6_TONEPORT_UX2 }, + {} +}; + +MODULE_DEVICE_TABLE(usb, toneport_id_table); + +static const struct line6_properties toneport_properties_table[] = { + [LINE6_GUITARPORT] = { + .id = "GuitarPort", + .name = "GuitarPort", + .capabilities = LINE6_CAP_PCM, + .altsetting = 2, /* 1..4 seem to be ok */ + /* no control channel */ + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_PODSTUDIO_GX] = { + .id = "PODStudioGX", + .name = "POD Studio GX", + .capabilities = LINE6_CAP_PCM, + .altsetting = 2, /* 1..4 seem to be ok */ + /* no control channel */ + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_PODSTUDIO_UX1] = { + .id = "PODStudioUX1", + .name = "POD Studio UX1", + .capabilities = LINE6_CAP_PCM, + .altsetting = 2, /* 1..4 seem to be ok */ + /* no control channel */ + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_PODSTUDIO_UX2] = { + .id = "PODStudioUX2", + .name = "POD Studio UX2", + .capabilities = LINE6_CAP_PCM, + .altsetting = 2, /* defaults to 44.1kHz, 16-bit */ + /* no control channel */ + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_TONEPORT_GX] = { + .id = "TonePortGX", + .name = "TonePort GX", + .capabilities = LINE6_CAP_PCM, + .altsetting = 2, /* 1..4 seem to be ok */ + /* no control channel */ + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_TONEPORT_UX1] = { + .id = "TonePortUX1", + .name = "TonePort UX1", + .capabilities = LINE6_CAP_PCM, + .altsetting = 2, /* 1..4 seem to be ok */ + /* no control channel */ + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_TONEPORT_UX2] = { + .id = "TonePortUX2", + .name = "TonePort UX2", + .capabilities = LINE6_CAP_PCM, + .altsetting = 2, /* defaults to 44.1kHz, 16-bit */ + /* no control channel */ + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, +}; + +/* + Probe USB device. +*/ +static int toneport_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + return line6_probe(interface, id, "Line6-TonePort", + &toneport_properties_table[id->driver_info], + toneport_init, sizeof(struct usb_line6_toneport)); +} + +static struct usb_driver toneport_driver = { + .name = KBUILD_MODNAME, + .probe = toneport_probe, + .disconnect = line6_disconnect, +#ifdef CONFIG_PM + .suspend = line6_suspend, + .resume = line6_resume, + .reset_resume = toneport_reset_resume, +#endif + .id_table = toneport_id_table, +}; + +module_usb_driver(toneport_driver); + +MODULE_DESCRIPTION("TonePort USB driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/line6/variax.c b/sound/usb/line6/variax.c new file mode 100644 index 000000000..163a08a82 --- /dev/null +++ b/sound/usb/line6/variax.c @@ -0,0 +1,302 @@ +/* + * Line 6 Linux USB driver + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "driver.h" + +#define VARIAX_STARTUP_DELAY1 1000 +#define VARIAX_STARTUP_DELAY3 100 +#define VARIAX_STARTUP_DELAY4 100 + +/* + Stages of Variax startup procedure +*/ +enum { + VARIAX_STARTUP_INIT = 1, + VARIAX_STARTUP_VERSIONREQ, + VARIAX_STARTUP_WAIT, + VARIAX_STARTUP_ACTIVATE, + VARIAX_STARTUP_WORKQUEUE, + VARIAX_STARTUP_SETUP, + VARIAX_STARTUP_LAST = VARIAX_STARTUP_SETUP - 1 +}; + +enum { + LINE6_PODXTLIVE_VARIAX, + LINE6_VARIAX +}; + +struct usb_line6_variax { + /* Generic Line 6 USB data */ + struct usb_line6 line6; + + /* Buffer for activation code */ + unsigned char *buffer_activate; + + /* Handler for device initialization */ + struct work_struct startup_work; + + /* Timers for device initialization */ + struct timer_list startup_timer1; + struct timer_list startup_timer2; + + /* Current progress in startup procedure */ + int startup_progress; +}; + +#define VARIAX_OFFSET_ACTIVATE 7 + +/* + This message is sent by the device during initialization and identifies + the connected guitar version. +*/ +static const char variax_init_version[] = { + 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c, + 0x07, 0x00, 0x00, 0x00 +}; + +/* + This message is the last one sent by the device during initialization. +*/ +static const char variax_init_done[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b +}; + +static const char variax_activate[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01, + 0xf7 +}; + +/* forward declarations: */ +static void variax_startup2(struct timer_list *t); +static void variax_startup4(struct timer_list *t); +static void variax_startup5(struct timer_list *t); + +static void variax_activate_async(struct usb_line6_variax *variax, int a) +{ + variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a; + line6_send_raw_message_async(&variax->line6, variax->buffer_activate, + sizeof(variax_activate)); +} + +/* + Variax startup procedure. + This is a sequence of functions with special requirements (e.g., must + not run immediately after initialization, must not run in interrupt + context). After the last one has finished, the device is ready to use. +*/ + +static void variax_startup1(struct usb_line6_variax *variax) +{ + CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, + variax_startup2); +} + +static void variax_startup2(struct timer_list *t) +{ + struct usb_line6_variax *variax = from_timer(variax, t, startup_timer1); + struct usb_line6 *line6 = &variax->line6; + + /* schedule another startup procedure until startup is complete: */ + if (variax->startup_progress >= VARIAX_STARTUP_LAST) + return; + + variax->startup_progress = VARIAX_STARTUP_VERSIONREQ; + line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, + variax_startup2); + + /* request firmware version: */ + line6_version_request_async(line6); +} + +static void variax_startup3(struct usb_line6_variax *variax) +{ + CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_WAIT); + + /* delay startup procedure: */ + line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY3, + variax_startup4); +} + +static void variax_startup4(struct timer_list *t) +{ + struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2); + + CHECK_STARTUP_PROGRESS(variax->startup_progress, + VARIAX_STARTUP_ACTIVATE); + + /* activate device: */ + variax_activate_async(variax, 1); + line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY4, + variax_startup5); +} + +static void variax_startup5(struct timer_list *t) +{ + struct usb_line6_variax *variax = from_timer(variax, t, startup_timer2); + + CHECK_STARTUP_PROGRESS(variax->startup_progress, + VARIAX_STARTUP_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&variax->startup_work); +} + +static void variax_startup6(struct work_struct *work) +{ + struct usb_line6_variax *variax = + container_of(work, struct usb_line6_variax, startup_work); + + CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_SETUP); + + /* ALSA audio interface: */ + snd_card_register(variax->line6.card); +} + +/* + Process a completely received message. +*/ +static void line6_variax_process_message(struct usb_line6 *line6) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *) line6; + const unsigned char *buf = variax->line6.buffer_message; + + switch (buf[0]) { + case LINE6_RESET: + dev_info(variax->line6.ifcdev, "VARIAX reset\n"); + break; + + case LINE6_SYSEX_BEGIN: + if (memcmp(buf + 1, variax_init_version + 1, + sizeof(variax_init_version) - 1) == 0) { + variax_startup3(variax); + } else if (memcmp(buf + 1, variax_init_done + 1, + sizeof(variax_init_done) - 1) == 0) { + /* notify of complete initialization: */ + variax_startup4(&variax->startup_timer2); + } + break; + } +} + +/* + Variax destructor. +*/ +static void line6_variax_disconnect(struct usb_line6 *line6) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *)line6; + + del_timer(&variax->startup_timer1); + del_timer(&variax->startup_timer2); + cancel_work_sync(&variax->startup_work); + + kfree(variax->buffer_activate); +} + +/* + Try to init workbench device. +*/ +static int variax_init(struct usb_line6 *line6, + const struct usb_device_id *id) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *) line6; + + line6->process_message = line6_variax_process_message; + line6->disconnect = line6_variax_disconnect; + + timer_setup(&variax->startup_timer1, NULL, 0); + timer_setup(&variax->startup_timer2, NULL, 0); + INIT_WORK(&variax->startup_work, variax_startup6); + + /* initialize USB buffers: */ + variax->buffer_activate = kmemdup(variax_activate, + sizeof(variax_activate), GFP_KERNEL); + + if (variax->buffer_activate == NULL) + return -ENOMEM; + + /* initiate startup procedure: */ + variax_startup1(variax); + return 0; +} + +#define LINE6_DEVICE(prod) USB_DEVICE(0x0e41, prod) +#define LINE6_IF_NUM(prod, n) USB_DEVICE_INTERFACE_NUMBER(0x0e41, prod, n) + +/* table of devices that work with this driver */ +static const struct usb_device_id variax_id_table[] = { + { LINE6_IF_NUM(0x4650, 1), .driver_info = LINE6_PODXTLIVE_VARIAX }, + { LINE6_DEVICE(0x534d), .driver_info = LINE6_VARIAX }, + {} +}; + +MODULE_DEVICE_TABLE(usb, variax_id_table); + +static const struct line6_properties variax_properties_table[] = { + [LINE6_PODXTLIVE_VARIAX] = { + .id = "PODxtLive", + .name = "PODxt Live", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, + .altsetting = 1, + .ep_ctrl_r = 0x86, + .ep_ctrl_w = 0x05, + .ep_audio_r = 0x82, + .ep_audio_w = 0x01, + }, + [LINE6_VARIAX] = { + .id = "Variax", + .name = "Variax Workbench", + .capabilities = LINE6_CAP_CONTROL + | LINE6_CAP_CONTROL_MIDI, + .altsetting = 1, + .ep_ctrl_r = 0x82, + .ep_ctrl_w = 0x01, + /* no audio channel */ + } +}; + +/* + Probe USB device. +*/ +static int variax_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + return line6_probe(interface, id, "Line6-Variax", + &variax_properties_table[id->driver_info], + variax_init, sizeof(struct usb_line6_variax)); +} + +static struct usb_driver variax_driver = { + .name = KBUILD_MODNAME, + .probe = variax_probe, + .disconnect = line6_disconnect, +#ifdef CONFIG_PM + .suspend = line6_suspend, + .resume = line6_resume, + .reset_resume = line6_resume, +#endif + .id_table = variax_id_table, +}; + +module_usb_driver(variax_driver); + +MODULE_DESCRIPTION("Vairax Workbench USB driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/usb/midi.c b/sound/usb/midi.c new file mode 100644 index 000000000..c9c604f0e --- /dev/null +++ b/sound/usb/midi.c @@ -0,0 +1,2527 @@ +/* + * usbmidi.c - ALSA USB MIDI driver + * + * Copyright (c) 2002-2009 Clemens Ladisch + * All rights reserved. + * + * Based on the OSS usb-midi driver by NAGANO Daisuke, + * NetBSD's umidi driver by Takuya SHIOZAKI, + * the "USB Device Class Definition for MIDI Devices" by Roland + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed and/or modified under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbaudio.h" +#include "midi.h" +#include "power.h" +#include "helper.h" + +/* + * define this to log all USB packets + */ +/* #define DUMP_PACKETS */ + +/* + * how long to wait after some USB errors, so that hub_wq can disconnect() us + * without too many spurious errors + */ +#define ERROR_DELAY_JIFFIES (HZ / 10) + +#define OUTPUT_URBS 7 +#define INPUT_URBS 7 + + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("USB Audio/MIDI helper module"); +MODULE_LICENSE("Dual BSD/GPL"); + + +struct usb_ms_header_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bcdMSC[2]; + __le16 wTotalLength; +} __attribute__ ((packed)); + +struct usb_ms_endpoint_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bNumEmbMIDIJack; + __u8 baAssocJackID[0]; +} __attribute__ ((packed)); + +struct snd_usb_midi_in_endpoint; +struct snd_usb_midi_out_endpoint; +struct snd_usb_midi_endpoint; + +struct usb_protocol_ops { + void (*input)(struct snd_usb_midi_in_endpoint*, uint8_t*, int); + void (*output)(struct snd_usb_midi_out_endpoint *ep, struct urb *urb); + void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t); + void (*init_out_endpoint)(struct snd_usb_midi_out_endpoint *); + void (*finish_out_endpoint)(struct snd_usb_midi_out_endpoint *); +}; + +struct snd_usb_midi { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *iface; + const struct snd_usb_audio_quirk *quirk; + struct snd_rawmidi *rmidi; + const struct usb_protocol_ops *usb_protocol_ops; + struct list_head list; + struct timer_list error_timer; + spinlock_t disc_lock; + struct rw_semaphore disc_rwsem; + struct mutex mutex; + u32 usb_id; + int next_midi_device; + + struct snd_usb_midi_endpoint { + struct snd_usb_midi_out_endpoint *out; + struct snd_usb_midi_in_endpoint *in; + } endpoints[MIDI_MAX_ENDPOINTS]; + unsigned long input_triggered; + unsigned int opened[2]; + unsigned char disconnected; + unsigned char input_running; + + struct snd_kcontrol *roland_load_ctl; +}; + +struct snd_usb_midi_out_endpoint { + struct snd_usb_midi *umidi; + struct out_urb_context { + struct urb *urb; + struct snd_usb_midi_out_endpoint *ep; + } urbs[OUTPUT_URBS]; + unsigned int active_urbs; + unsigned int drain_urbs; + int max_transfer; /* size of urb buffer */ + struct tasklet_struct tasklet; + unsigned int next_urb; + spinlock_t buffer_lock; + + struct usbmidi_out_port { + struct snd_usb_midi_out_endpoint *ep; + struct snd_rawmidi_substream *substream; + int active; + uint8_t cable; /* cable number << 4 */ + uint8_t state; +#define STATE_UNKNOWN 0 +#define STATE_1PARAM 1 +#define STATE_2PARAM_1 2 +#define STATE_2PARAM_2 3 +#define STATE_SYSEX_0 4 +#define STATE_SYSEX_1 5 +#define STATE_SYSEX_2 6 + uint8_t data[2]; + } ports[0x10]; + int current_port; + + wait_queue_head_t drain_wait; +}; + +struct snd_usb_midi_in_endpoint { + struct snd_usb_midi *umidi; + struct urb *urbs[INPUT_URBS]; + struct usbmidi_in_port { + struct snd_rawmidi_substream *substream; + u8 running_status_length; + } ports[0x10]; + u8 seen_f5; + bool in_sysex; + u8 last_cin; + u8 error_resubmit; + int current_port; +}; + +static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep); + +static const uint8_t snd_usbmidi_cin_length[] = { + 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 +}; + +/* + * Submits the URB, with error handling. + */ +static int snd_usbmidi_submit_urb(struct urb *urb, gfp_t flags) +{ + int err = usb_submit_urb(urb, flags); + if (err < 0 && err != -ENODEV) + dev_err(&urb->dev->dev, "usb_submit_urb: %d\n", err); + return err; +} + +/* + * Error handling for URB completion functions. + */ +static int snd_usbmidi_urb_error(const struct urb *urb) +{ + switch (urb->status) { + /* manually unlinked, or device gone */ + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + case -ENODEV: + return -ENODEV; + /* errors that might occur during unplugging */ + case -EPROTO: + case -ETIME: + case -EILSEQ: + return -EIO; + default: + dev_err(&urb->dev->dev, "urb status %d\n", urb->status); + return 0; /* continue */ + } +} + +/* + * Receives a chunk of MIDI data. + */ +static void snd_usbmidi_input_data(struct snd_usb_midi_in_endpoint *ep, + int portidx, uint8_t *data, int length) +{ + struct usbmidi_in_port *port = &ep->ports[portidx]; + + if (!port->substream) { + dev_dbg(&ep->umidi->dev->dev, "unexpected port %d!\n", portidx); + return; + } + if (!test_bit(port->substream->number, &ep->umidi->input_triggered)) + return; + snd_rawmidi_receive(port->substream, data, length); +} + +#ifdef DUMP_PACKETS +static void dump_urb(const char *type, const u8 *data, int length) +{ + snd_printk(KERN_DEBUG "%s packet: [", type); + for (; length > 0; ++data, --length) + printk(KERN_CONT " %02x", *data); + printk(KERN_CONT " ]\n"); +} +#else +#define dump_urb(type, data, length) /* nothing */ +#endif + +/* + * Processes the data read from the device. + */ +static void snd_usbmidi_in_urb_complete(struct urb *urb) +{ + struct snd_usb_midi_in_endpoint *ep = urb->context; + + if (urb->status == 0) { + dump_urb("received", urb->transfer_buffer, urb->actual_length); + ep->umidi->usb_protocol_ops->input(ep, urb->transfer_buffer, + urb->actual_length); + } else { + int err = snd_usbmidi_urb_error(urb); + if (err < 0) { + if (err != -ENODEV) { + ep->error_resubmit = 1; + mod_timer(&ep->umidi->error_timer, + jiffies + ERROR_DELAY_JIFFIES); + } + return; + } + } + + urb->dev = ep->umidi->dev; + snd_usbmidi_submit_urb(urb, GFP_ATOMIC); +} + +static void snd_usbmidi_out_urb_complete(struct urb *urb) +{ + struct out_urb_context *context = urb->context; + struct snd_usb_midi_out_endpoint *ep = context->ep; + unsigned int urb_index; + unsigned long flags; + + spin_lock_irqsave(&ep->buffer_lock, flags); + urb_index = context - ep->urbs; + ep->active_urbs &= ~(1 << urb_index); + if (unlikely(ep->drain_urbs)) { + ep->drain_urbs &= ~(1 << urb_index); + wake_up(&ep->drain_wait); + } + spin_unlock_irqrestore(&ep->buffer_lock, flags); + if (urb->status < 0) { + int err = snd_usbmidi_urb_error(urb); + if (err < 0) { + if (err != -ENODEV) + mod_timer(&ep->umidi->error_timer, + jiffies + ERROR_DELAY_JIFFIES); + return; + } + } + snd_usbmidi_do_output(ep); +} + +/* + * This is called when some data should be transferred to the device + * (from one or more substreams). + */ +static void snd_usbmidi_do_output(struct snd_usb_midi_out_endpoint *ep) +{ + unsigned int urb_index; + struct urb *urb; + unsigned long flags; + + spin_lock_irqsave(&ep->buffer_lock, flags); + if (ep->umidi->disconnected) { + spin_unlock_irqrestore(&ep->buffer_lock, flags); + return; + } + + urb_index = ep->next_urb; + for (;;) { + if (!(ep->active_urbs & (1 << urb_index))) { + urb = ep->urbs[urb_index].urb; + urb->transfer_buffer_length = 0; + ep->umidi->usb_protocol_ops->output(ep, urb); + if (urb->transfer_buffer_length == 0) + break; + + dump_urb("sending", urb->transfer_buffer, + urb->transfer_buffer_length); + urb->dev = ep->umidi->dev; + if (snd_usbmidi_submit_urb(urb, GFP_ATOMIC) < 0) + break; + ep->active_urbs |= 1 << urb_index; + } + if (++urb_index >= OUTPUT_URBS) + urb_index = 0; + if (urb_index == ep->next_urb) + break; + } + ep->next_urb = urb_index; + spin_unlock_irqrestore(&ep->buffer_lock, flags); +} + +static void snd_usbmidi_out_tasklet(unsigned long data) +{ + struct snd_usb_midi_out_endpoint *ep = + (struct snd_usb_midi_out_endpoint *) data; + + snd_usbmidi_do_output(ep); +} + +/* called after transfers had been interrupted due to some USB error */ +static void snd_usbmidi_error_timer(struct timer_list *t) +{ + struct snd_usb_midi *umidi = from_timer(umidi, t, error_timer); + unsigned int i, j; + + spin_lock(&umidi->disc_lock); + if (umidi->disconnected) { + spin_unlock(&umidi->disc_lock); + return; + } + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_in_endpoint *in = umidi->endpoints[i].in; + if (in && in->error_resubmit) { + in->error_resubmit = 0; + for (j = 0; j < INPUT_URBS; ++j) { + if (atomic_read(&in->urbs[j]->use_count)) + continue; + in->urbs[j]->dev = umidi->dev; + snd_usbmidi_submit_urb(in->urbs[j], GFP_ATOMIC); + } + } + if (umidi->endpoints[i].out) + snd_usbmidi_do_output(umidi->endpoints[i].out); + } + spin_unlock(&umidi->disc_lock); +} + +/* helper function to send static data that may not DMA-able */ +static int send_bulk_static_data(struct snd_usb_midi_out_endpoint *ep, + const void *data, int len) +{ + int err = 0; + void *buf = kmemdup(data, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + dump_urb("sending", buf, len); + if (ep->urbs[0].urb) + err = usb_bulk_msg(ep->umidi->dev, ep->urbs[0].urb->pipe, + buf, len, NULL, 250); + kfree(buf); + return err; +} + +/* + * Standard USB MIDI protocol: see the spec. + * Midiman protocol: like the standard protocol, but the control byte is the + * fourth byte in each packet, and uses length instead of CIN. + */ + +static void snd_usbmidi_standard_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + int i; + + for (i = 0; i + 3 < buffer_length; i += 4) + if (buffer[i] != 0) { + int cable = buffer[i] >> 4; + int length = snd_usbmidi_cin_length[buffer[i] & 0x0f]; + snd_usbmidi_input_data(ep, cable, &buffer[i + 1], + length); + } +} + +static void snd_usbmidi_midiman_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + int i; + + for (i = 0; i + 3 < buffer_length; i += 4) + if (buffer[i + 3] != 0) { + int port = buffer[i + 3] >> 4; + int length = buffer[i + 3] & 3; + snd_usbmidi_input_data(ep, port, &buffer[i], length); + } +} + +/* + * Buggy M-Audio device: running status on input results in a packet that has + * the data bytes but not the status byte and that is marked with CIN 4. + */ +static void snd_usbmidi_maudio_broken_running_status_input( + struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + int i; + + for (i = 0; i + 3 < buffer_length; i += 4) + if (buffer[i] != 0) { + int cable = buffer[i] >> 4; + u8 cin = buffer[i] & 0x0f; + struct usbmidi_in_port *port = &ep->ports[cable]; + int length; + + length = snd_usbmidi_cin_length[cin]; + if (cin == 0xf && buffer[i + 1] >= 0xf8) + ; /* realtime msg: no running status change */ + else if (cin >= 0x8 && cin <= 0xe) + /* channel msg */ + port->running_status_length = length - 1; + else if (cin == 0x4 && + port->running_status_length != 0 && + buffer[i + 1] < 0x80) + /* CIN 4 that is not a SysEx */ + length = port->running_status_length; + else + /* + * All other msgs cannot begin running status. + * (A channel msg sent as two or three CIN 0xF + * packets could in theory, but this device + * doesn't use this format.) + */ + port->running_status_length = 0; + snd_usbmidi_input_data(ep, cable, &buffer[i + 1], + length); + } +} + +/* + * QinHeng CH345 is buggy: every second packet inside a SysEx has not CIN 4 + * but the previously seen CIN, but still with three data bytes. + */ +static void ch345_broken_sysex_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + unsigned int i, cin, length; + + for (i = 0; i + 3 < buffer_length; i += 4) { + if (buffer[i] == 0 && i > 0) + break; + cin = buffer[i] & 0x0f; + if (ep->in_sysex && + cin == ep->last_cin && + (buffer[i + 1 + (cin == 0x6)] & 0x80) == 0) + cin = 0x4; +#if 0 + if (buffer[i + 1] == 0x90) { + /* + * Either a corrupted running status or a real note-on + * message; impossible to detect reliably. + */ + } +#endif + length = snd_usbmidi_cin_length[cin]; + snd_usbmidi_input_data(ep, 0, &buffer[i + 1], length); + ep->in_sysex = cin == 0x4; + if (!ep->in_sysex) + ep->last_cin = cin; + } +} + +/* + * CME protocol: like the standard protocol, but SysEx commands are sent as a + * single USB packet preceded by a 0x0F byte. + */ +static void snd_usbmidi_cme_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + if (buffer_length < 2 || (buffer[0] & 0x0f) != 0x0f) + snd_usbmidi_standard_input(ep, buffer, buffer_length); + else + snd_usbmidi_input_data(ep, buffer[0] >> 4, + &buffer[1], buffer_length - 1); +} + +/* + * Adds one USB MIDI packet to the output buffer. + */ +static void snd_usbmidi_output_standard_packet(struct urb *urb, uint8_t p0, + uint8_t p1, uint8_t p2, + uint8_t p3) +{ + + uint8_t *buf = + (uint8_t *)urb->transfer_buffer + urb->transfer_buffer_length; + buf[0] = p0; + buf[1] = p1; + buf[2] = p2; + buf[3] = p3; + urb->transfer_buffer_length += 4; +} + +/* + * Adds one Midiman packet to the output buffer. + */ +static void snd_usbmidi_output_midiman_packet(struct urb *urb, uint8_t p0, + uint8_t p1, uint8_t p2, + uint8_t p3) +{ + + uint8_t *buf = + (uint8_t *)urb->transfer_buffer + urb->transfer_buffer_length; + buf[0] = p1; + buf[1] = p2; + buf[2] = p3; + buf[3] = (p0 & 0xf0) | snd_usbmidi_cin_length[p0 & 0x0f]; + urb->transfer_buffer_length += 4; +} + +/* + * Converts MIDI commands to USB MIDI packets. + */ +static void snd_usbmidi_transmit_byte(struct usbmidi_out_port *port, + uint8_t b, struct urb *urb) +{ + uint8_t p0 = port->cable; + void (*output_packet)(struct urb*, uint8_t, uint8_t, uint8_t, uint8_t) = + port->ep->umidi->usb_protocol_ops->output_packet; + + if (b >= 0xf8) { + output_packet(urb, p0 | 0x0f, b, 0, 0); + } else if (b >= 0xf0) { + switch (b) { + case 0xf0: + port->data[0] = b; + port->state = STATE_SYSEX_1; + break; + case 0xf1: + case 0xf3: + port->data[0] = b; + port->state = STATE_1PARAM; + break; + case 0xf2: + port->data[0] = b; + port->state = STATE_2PARAM_1; + break; + case 0xf4: + case 0xf5: + port->state = STATE_UNKNOWN; + break; + case 0xf6: + output_packet(urb, p0 | 0x05, 0xf6, 0, 0); + port->state = STATE_UNKNOWN; + break; + case 0xf7: + switch (port->state) { + case STATE_SYSEX_0: + output_packet(urb, p0 | 0x05, 0xf7, 0, 0); + break; + case STATE_SYSEX_1: + output_packet(urb, p0 | 0x06, port->data[0], + 0xf7, 0); + break; + case STATE_SYSEX_2: + output_packet(urb, p0 | 0x07, port->data[0], + port->data[1], 0xf7); + break; + } + port->state = STATE_UNKNOWN; + break; + } + } else if (b >= 0x80) { + port->data[0] = b; + if (b >= 0xc0 && b <= 0xdf) + port->state = STATE_1PARAM; + else + port->state = STATE_2PARAM_1; + } else { /* b < 0x80 */ + switch (port->state) { + case STATE_1PARAM: + if (port->data[0] < 0xf0) { + p0 |= port->data[0] >> 4; + } else { + p0 |= 0x02; + port->state = STATE_UNKNOWN; + } + output_packet(urb, p0, port->data[0], b, 0); + break; + case STATE_2PARAM_1: + port->data[1] = b; + port->state = STATE_2PARAM_2; + break; + case STATE_2PARAM_2: + if (port->data[0] < 0xf0) { + p0 |= port->data[0] >> 4; + port->state = STATE_2PARAM_1; + } else { + p0 |= 0x03; + port->state = STATE_UNKNOWN; + } + output_packet(urb, p0, port->data[0], port->data[1], b); + break; + case STATE_SYSEX_0: + port->data[0] = b; + port->state = STATE_SYSEX_1; + break; + case STATE_SYSEX_1: + port->data[1] = b; + port->state = STATE_SYSEX_2; + break; + case STATE_SYSEX_2: + output_packet(urb, p0 | 0x04, port->data[0], + port->data[1], b); + port->state = STATE_SYSEX_0; + break; + } + } +} + +static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep, + struct urb *urb) +{ + int p; + + /* FIXME: lower-numbered ports can starve higher-numbered ports */ + for (p = 0; p < 0x10; ++p) { + struct usbmidi_out_port *port = &ep->ports[p]; + if (!port->active) + continue; + while (urb->transfer_buffer_length + 3 < ep->max_transfer) { + uint8_t b; + if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) { + port->active = 0; + break; + } + snd_usbmidi_transmit_byte(port, b, urb); + } + } +} + +static const struct usb_protocol_ops snd_usbmidi_standard_ops = { + .input = snd_usbmidi_standard_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_standard_packet, +}; + +static const struct usb_protocol_ops snd_usbmidi_midiman_ops = { + .input = snd_usbmidi_midiman_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_midiman_packet, +}; + +static const +struct usb_protocol_ops snd_usbmidi_maudio_broken_running_status_ops = { + .input = snd_usbmidi_maudio_broken_running_status_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_standard_packet, +}; + +static const struct usb_protocol_ops snd_usbmidi_cme_ops = { + .input = snd_usbmidi_cme_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_standard_packet, +}; + +static const struct usb_protocol_ops snd_usbmidi_ch345_broken_sysex_ops = { + .input = ch345_broken_sysex_input, + .output = snd_usbmidi_standard_output, + .output_packet = snd_usbmidi_output_standard_packet, +}; + +/* + * AKAI MPD16 protocol: + * + * For control port (endpoint 1): + * ============================== + * One or more chunks consisting of first byte of (0x10 | msg_len) and then a + * SysEx message (msg_len=9 bytes long). + * + * For data port (endpoint 2): + * =========================== + * One or more chunks consisting of first byte of (0x20 | msg_len) and then a + * MIDI message (msg_len bytes long) + * + * Messages sent: Active Sense, Note On, Poly Pressure, Control Change. + */ +static void snd_usbmidi_akai_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + unsigned int pos = 0; + unsigned int len = (unsigned int)buffer_length; + while (pos < len) { + unsigned int port = (buffer[pos] >> 4) - 1; + unsigned int msg_len = buffer[pos] & 0x0f; + pos++; + if (pos + msg_len <= len && port < 2) + snd_usbmidi_input_data(ep, 0, &buffer[pos], msg_len); + pos += msg_len; + } +} + +#define MAX_AKAI_SYSEX_LEN 9 + +static void snd_usbmidi_akai_output(struct snd_usb_midi_out_endpoint *ep, + struct urb *urb) +{ + uint8_t *msg; + int pos, end, count, buf_end; + uint8_t tmp[MAX_AKAI_SYSEX_LEN]; + struct snd_rawmidi_substream *substream = ep->ports[0].substream; + + if (!ep->ports[0].active) + return; + + msg = urb->transfer_buffer + urb->transfer_buffer_length; + buf_end = ep->max_transfer - MAX_AKAI_SYSEX_LEN - 1; + + /* only try adding more data when there's space for at least 1 SysEx */ + while (urb->transfer_buffer_length < buf_end) { + count = snd_rawmidi_transmit_peek(substream, + tmp, MAX_AKAI_SYSEX_LEN); + if (!count) { + ep->ports[0].active = 0; + return; + } + /* try to skip non-SysEx data */ + for (pos = 0; pos < count && tmp[pos] != 0xF0; pos++) + ; + + if (pos > 0) { + snd_rawmidi_transmit_ack(substream, pos); + continue; + } + + /* look for the start or end marker */ + for (end = 1; end < count && tmp[end] < 0xF0; end++) + ; + + /* next SysEx started before the end of current one */ + if (end < count && tmp[end] == 0xF0) { + /* it's incomplete - drop it */ + snd_rawmidi_transmit_ack(substream, end); + continue; + } + /* SysEx complete */ + if (end < count && tmp[end] == 0xF7) { + /* queue it, ack it, and get the next one */ + count = end + 1; + msg[0] = 0x10 | count; + memcpy(&msg[1], tmp, count); + snd_rawmidi_transmit_ack(substream, count); + urb->transfer_buffer_length += count + 1; + msg += count + 1; + continue; + } + /* less than 9 bytes and no end byte - wait for more */ + if (count < MAX_AKAI_SYSEX_LEN) { + ep->ports[0].active = 0; + return; + } + /* 9 bytes and no end marker in sight - malformed, skip it */ + snd_rawmidi_transmit_ack(substream, count); + } +} + +static const struct usb_protocol_ops snd_usbmidi_akai_ops = { + .input = snd_usbmidi_akai_input, + .output = snd_usbmidi_akai_output, +}; + +/* + * Novation USB MIDI protocol: number of data bytes is in the first byte + * (when receiving) (+1!) or in the second byte (when sending); data begins + * at the third byte. + */ + +static void snd_usbmidi_novation_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + if (buffer_length < 2 || !buffer[0] || buffer_length < buffer[0] + 1) + return; + snd_usbmidi_input_data(ep, 0, &buffer[2], buffer[0] - 1); +} + +static void snd_usbmidi_novation_output(struct snd_usb_midi_out_endpoint *ep, + struct urb *urb) +{ + uint8_t *transfer_buffer; + int count; + + if (!ep->ports[0].active) + return; + transfer_buffer = urb->transfer_buffer; + count = snd_rawmidi_transmit(ep->ports[0].substream, + &transfer_buffer[2], + ep->max_transfer - 2); + if (count < 1) { + ep->ports[0].active = 0; + return; + } + transfer_buffer[0] = 0; + transfer_buffer[1] = count; + urb->transfer_buffer_length = 2 + count; +} + +static const struct usb_protocol_ops snd_usbmidi_novation_ops = { + .input = snd_usbmidi_novation_input, + .output = snd_usbmidi_novation_output, +}; + +/* + * "raw" protocol: just move raw MIDI bytes from/to the endpoint + */ + +static void snd_usbmidi_raw_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + snd_usbmidi_input_data(ep, 0, buffer, buffer_length); +} + +static void snd_usbmidi_raw_output(struct snd_usb_midi_out_endpoint *ep, + struct urb *urb) +{ + int count; + + if (!ep->ports[0].active) + return; + count = snd_rawmidi_transmit(ep->ports[0].substream, + urb->transfer_buffer, + ep->max_transfer); + if (count < 1) { + ep->ports[0].active = 0; + return; + } + urb->transfer_buffer_length = count; +} + +static const struct usb_protocol_ops snd_usbmidi_raw_ops = { + .input = snd_usbmidi_raw_input, + .output = snd_usbmidi_raw_output, +}; + +/* + * FTDI protocol: raw MIDI bytes, but input packets have two modem status bytes. + */ + +static void snd_usbmidi_ftdi_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + if (buffer_length > 2) + snd_usbmidi_input_data(ep, 0, buffer + 2, buffer_length - 2); +} + +static const struct usb_protocol_ops snd_usbmidi_ftdi_ops = { + .input = snd_usbmidi_ftdi_input, + .output = snd_usbmidi_raw_output, +}; + +static void snd_usbmidi_us122l_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + if (buffer_length != 9) + return; + buffer_length = 8; + while (buffer_length && buffer[buffer_length - 1] == 0xFD) + buffer_length--; + if (buffer_length) + snd_usbmidi_input_data(ep, 0, buffer, buffer_length); +} + +static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep, + struct urb *urb) +{ + int count; + + if (!ep->ports[0].active) + return; + switch (snd_usb_get_speed(ep->umidi->dev)) { + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + count = 1; + break; + default: + count = 2; + } + count = snd_rawmidi_transmit(ep->ports[0].substream, + urb->transfer_buffer, + count); + if (count < 1) { + ep->ports[0].active = 0; + return; + } + + memset(urb->transfer_buffer + count, 0xFD, ep->max_transfer - count); + urb->transfer_buffer_length = ep->max_transfer; +} + +static const struct usb_protocol_ops snd_usbmidi_122l_ops = { + .input = snd_usbmidi_us122l_input, + .output = snd_usbmidi_us122l_output, +}; + +/* + * Emagic USB MIDI protocol: raw MIDI with "F5 xx" port switching. + */ + +static void snd_usbmidi_emagic_init_out(struct snd_usb_midi_out_endpoint *ep) +{ + static const u8 init_data[] = { + /* initialization magic: "get version" */ + 0xf0, + 0x00, 0x20, 0x31, /* Emagic */ + 0x64, /* Unitor8 */ + 0x0b, /* version number request */ + 0x00, /* command version */ + 0x00, /* EEPROM, box 0 */ + 0xf7 + }; + send_bulk_static_data(ep, init_data, sizeof(init_data)); + /* while we're at it, pour on more magic */ + send_bulk_static_data(ep, init_data, sizeof(init_data)); +} + +static void snd_usbmidi_emagic_finish_out(struct snd_usb_midi_out_endpoint *ep) +{ + static const u8 finish_data[] = { + /* switch to patch mode with last preset */ + 0xf0, + 0x00, 0x20, 0x31, /* Emagic */ + 0x64, /* Unitor8 */ + 0x10, /* patch switch command */ + 0x00, /* command version */ + 0x7f, /* to all boxes */ + 0x40, /* last preset in EEPROM */ + 0xf7 + }; + send_bulk_static_data(ep, finish_data, sizeof(finish_data)); +} + +static void snd_usbmidi_emagic_input(struct snd_usb_midi_in_endpoint *ep, + uint8_t *buffer, int buffer_length) +{ + int i; + + /* FF indicates end of valid data */ + for (i = 0; i < buffer_length; ++i) + if (buffer[i] == 0xff) { + buffer_length = i; + break; + } + + /* handle F5 at end of last buffer */ + if (ep->seen_f5) + goto switch_port; + + while (buffer_length > 0) { + /* determine size of data until next F5 */ + for (i = 0; i < buffer_length; ++i) + if (buffer[i] == 0xf5) + break; + snd_usbmidi_input_data(ep, ep->current_port, buffer, i); + buffer += i; + buffer_length -= i; + + if (buffer_length <= 0) + break; + /* assert(buffer[0] == 0xf5); */ + ep->seen_f5 = 1; + ++buffer; + --buffer_length; + + switch_port: + if (buffer_length <= 0) + break; + if (buffer[0] < 0x80) { + ep->current_port = (buffer[0] - 1) & 15; + ++buffer; + --buffer_length; + } + ep->seen_f5 = 0; + } +} + +static void snd_usbmidi_emagic_output(struct snd_usb_midi_out_endpoint *ep, + struct urb *urb) +{ + int port0 = ep->current_port; + uint8_t *buf = urb->transfer_buffer; + int buf_free = ep->max_transfer; + int length, i; + + for (i = 0; i < 0x10; ++i) { + /* round-robin, starting at the last current port */ + int portnum = (port0 + i) & 15; + struct usbmidi_out_port *port = &ep->ports[portnum]; + + if (!port->active) + continue; + if (snd_rawmidi_transmit_peek(port->substream, buf, 1) != 1) { + port->active = 0; + continue; + } + + if (portnum != ep->current_port) { + if (buf_free < 2) + break; + ep->current_port = portnum; + buf[0] = 0xf5; + buf[1] = (portnum + 1) & 15; + buf += 2; + buf_free -= 2; + } + + if (buf_free < 1) + break; + length = snd_rawmidi_transmit(port->substream, buf, buf_free); + if (length > 0) { + buf += length; + buf_free -= length; + if (buf_free < 1) + break; + } + } + if (buf_free < ep->max_transfer && buf_free > 0) { + *buf = 0xff; + --buf_free; + } + urb->transfer_buffer_length = ep->max_transfer - buf_free; +} + +static const struct usb_protocol_ops snd_usbmidi_emagic_ops = { + .input = snd_usbmidi_emagic_input, + .output = snd_usbmidi_emagic_output, + .init_out_endpoint = snd_usbmidi_emagic_init_out, + .finish_out_endpoint = snd_usbmidi_emagic_finish_out, +}; + + +static void update_roland_altsetting(struct snd_usb_midi *umidi) +{ + struct usb_interface *intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor *intfd; + int is_light_load; + + intf = umidi->iface; + is_light_load = intf->cur_altsetting != intf->altsetting; + if (umidi->roland_load_ctl->private_value == is_light_load) + return; + hostif = &intf->altsetting[umidi->roland_load_ctl->private_value]; + intfd = get_iface_desc(hostif); + snd_usbmidi_input_stop(&umidi->list); + usb_set_interface(umidi->dev, intfd->bInterfaceNumber, + intfd->bAlternateSetting); + snd_usbmidi_input_start(&umidi->list); +} + +static int substream_open(struct snd_rawmidi_substream *substream, int dir, + int open) +{ + struct snd_usb_midi *umidi = substream->rmidi->private_data; + struct snd_kcontrol *ctl; + + down_read(&umidi->disc_rwsem); + if (umidi->disconnected) { + up_read(&umidi->disc_rwsem); + return open ? -ENODEV : 0; + } + + mutex_lock(&umidi->mutex); + if (open) { + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl = umidi->roland_load_ctl; + ctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(umidi->card, + SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + update_roland_altsetting(umidi); + } + } + umidi->opened[dir]++; + if (umidi->opened[1]) + snd_usbmidi_input_start(&umidi->list); + } else { + umidi->opened[dir]--; + if (!umidi->opened[1]) + snd_usbmidi_input_stop(&umidi->list); + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl = umidi->roland_load_ctl; + ctl->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(umidi->card, + SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + } + } + } + mutex_unlock(&umidi->mutex); + up_read(&umidi->disc_rwsem); + return 0; +} + +static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi *umidi = substream->rmidi->private_data; + struct usbmidi_out_port *port = NULL; + int i, j; + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) + if (umidi->endpoints[i].out) + for (j = 0; j < 0x10; ++j) + if (umidi->endpoints[i].out->ports[j].substream == substream) { + port = &umidi->endpoints[i].out->ports[j]; + break; + } + if (!port) { + snd_BUG(); + return -ENXIO; + } + + substream->runtime->private_data = port; + port->state = STATE_UNKNOWN; + return substream_open(substream, 0, 1); +} + +static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream) +{ + return substream_open(substream, 0, 0); +} + +static void snd_usbmidi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct usbmidi_out_port *port = + (struct usbmidi_out_port *)substream->runtime->private_data; + + port->active = up; + if (up) { + if (port->ep->umidi->disconnected) { + /* gobble up remaining bytes to prevent wait in + * snd_rawmidi_drain_output */ + while (!snd_rawmidi_transmit_empty(substream)) + snd_rawmidi_transmit_ack(substream, 1); + return; + } + tasklet_schedule(&port->ep->tasklet); + } +} + +static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct usbmidi_out_port *port = substream->runtime->private_data; + struct snd_usb_midi_out_endpoint *ep = port->ep; + unsigned int drain_urbs; + DEFINE_WAIT(wait); + long timeout = msecs_to_jiffies(50); + + if (ep->umidi->disconnected) + return; + /* + * The substream buffer is empty, but some data might still be in the + * currently active URBs, so we have to wait for those to complete. + */ + spin_lock_irq(&ep->buffer_lock); + drain_urbs = ep->active_urbs; + if (drain_urbs) { + ep->drain_urbs |= drain_urbs; + do { + prepare_to_wait(&ep->drain_wait, &wait, + TASK_UNINTERRUPTIBLE); + spin_unlock_irq(&ep->buffer_lock); + timeout = schedule_timeout(timeout); + spin_lock_irq(&ep->buffer_lock); + drain_urbs &= ep->drain_urbs; + } while (drain_urbs && timeout); + finish_wait(&ep->drain_wait, &wait); + } + port->active = 0; + spin_unlock_irq(&ep->buffer_lock); +} + +static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream) +{ + return substream_open(substream, 1, 1); +} + +static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream) +{ + return substream_open(substream, 1, 0); +} + +static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_usb_midi *umidi = substream->rmidi->private_data; + + if (up) + set_bit(substream->number, &umidi->input_triggered); + else + clear_bit(substream->number, &umidi->input_triggered); +} + +static const struct snd_rawmidi_ops snd_usbmidi_output_ops = { + .open = snd_usbmidi_output_open, + .close = snd_usbmidi_output_close, + .trigger = snd_usbmidi_output_trigger, + .drain = snd_usbmidi_output_drain, +}; + +static const struct snd_rawmidi_ops snd_usbmidi_input_ops = { + .open = snd_usbmidi_input_open, + .close = snd_usbmidi_input_close, + .trigger = snd_usbmidi_input_trigger +}; + +static void free_urb_and_buffer(struct snd_usb_midi *umidi, struct urb *urb, + unsigned int buffer_length) +{ + usb_free_coherent(umidi->dev, buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); +} + +/* + * Frees an input endpoint. + * May be called when ep hasn't been initialized completely. + */ +static void snd_usbmidi_in_endpoint_delete(struct snd_usb_midi_in_endpoint *ep) +{ + unsigned int i; + + for (i = 0; i < INPUT_URBS; ++i) + if (ep->urbs[i]) + free_urb_and_buffer(ep->umidi, ep->urbs[i], + ep->urbs[i]->transfer_buffer_length); + kfree(ep); +} + +/* + * Creates an input endpoint. + */ +static int snd_usbmidi_in_endpoint_create(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *ep_info, + struct snd_usb_midi_endpoint *rep) +{ + struct snd_usb_midi_in_endpoint *ep; + void *buffer; + unsigned int pipe; + int length; + unsigned int i; + int err; + + rep->in = NULL; + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + ep->umidi = umidi; + + for (i = 0; i < INPUT_URBS; ++i) { + ep->urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!ep->urbs[i]) { + err = -ENOMEM; + goto error; + } + } + if (ep_info->in_interval) + pipe = usb_rcvintpipe(umidi->dev, ep_info->in_ep); + else + pipe = usb_rcvbulkpipe(umidi->dev, ep_info->in_ep); + length = usb_maxpacket(umidi->dev, pipe, 0); + for (i = 0; i < INPUT_URBS; ++i) { + buffer = usb_alloc_coherent(umidi->dev, length, GFP_KERNEL, + &ep->urbs[i]->transfer_dma); + if (!buffer) { + err = -ENOMEM; + goto error; + } + if (ep_info->in_interval) + usb_fill_int_urb(ep->urbs[i], umidi->dev, + pipe, buffer, length, + snd_usbmidi_in_urb_complete, + ep, ep_info->in_interval); + else + usb_fill_bulk_urb(ep->urbs[i], umidi->dev, + pipe, buffer, length, + snd_usbmidi_in_urb_complete, ep); + ep->urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + err = usb_urb_ep_type_check(ep->urbs[i]); + if (err < 0) { + dev_err(&umidi->dev->dev, "invalid MIDI in EP %x\n", + ep_info->in_ep); + goto error; + } + } + + rep->in = ep; + return 0; + + error: + snd_usbmidi_in_endpoint_delete(ep); + return err; +} + +/* + * Frees an output endpoint. + * May be called when ep hasn't been initialized completely. + */ +static void snd_usbmidi_out_endpoint_clear(struct snd_usb_midi_out_endpoint *ep) +{ + unsigned int i; + + for (i = 0; i < OUTPUT_URBS; ++i) + if (ep->urbs[i].urb) { + free_urb_and_buffer(ep->umidi, ep->urbs[i].urb, + ep->max_transfer); + ep->urbs[i].urb = NULL; + } +} + +static void snd_usbmidi_out_endpoint_delete(struct snd_usb_midi_out_endpoint *ep) +{ + snd_usbmidi_out_endpoint_clear(ep); + kfree(ep); +} + +/* + * Creates an output endpoint, and initializes output ports. + */ +static int snd_usbmidi_out_endpoint_create(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *ep_info, + struct snd_usb_midi_endpoint *rep) +{ + struct snd_usb_midi_out_endpoint *ep; + unsigned int i; + unsigned int pipe; + void *buffer; + int err; + + rep->out = NULL; + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + ep->umidi = umidi; + + for (i = 0; i < OUTPUT_URBS; ++i) { + ep->urbs[i].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ep->urbs[i].urb) { + err = -ENOMEM; + goto error; + } + ep->urbs[i].ep = ep; + } + if (ep_info->out_interval) + pipe = usb_sndintpipe(umidi->dev, ep_info->out_ep); + else + pipe = usb_sndbulkpipe(umidi->dev, ep_info->out_ep); + switch (umidi->usb_id) { + default: + ep->max_transfer = usb_maxpacket(umidi->dev, pipe, 1); + break; + /* + * Various chips declare a packet size larger than 4 bytes, but + * do not actually work with larger packets: + */ + case USB_ID(0x0a67, 0x5011): /* Medeli DD305 */ + case USB_ID(0x0a92, 0x1020): /* ESI M4U */ + case USB_ID(0x1430, 0x474b): /* RedOctane GH MIDI INTERFACE */ + case USB_ID(0x15ca, 0x0101): /* Textech USB Midi Cable */ + case USB_ID(0x15ca, 0x1806): /* Textech USB Midi Cable */ + case USB_ID(0x1a86, 0x752d): /* QinHeng CH345 "USB2.0-MIDI" */ + case USB_ID(0xfc08, 0x0101): /* Unknown vendor Cable */ + ep->max_transfer = 4; + break; + /* + * Some devices only work with 9 bytes packet size: + */ + case USB_ID(0x0644, 0x800E): /* Tascam US-122L */ + case USB_ID(0x0644, 0x800F): /* Tascam US-144 */ + ep->max_transfer = 9; + break; + } + for (i = 0; i < OUTPUT_URBS; ++i) { + buffer = usb_alloc_coherent(umidi->dev, + ep->max_transfer, GFP_KERNEL, + &ep->urbs[i].urb->transfer_dma); + if (!buffer) { + err = -ENOMEM; + goto error; + } + if (ep_info->out_interval) + usb_fill_int_urb(ep->urbs[i].urb, umidi->dev, + pipe, buffer, ep->max_transfer, + snd_usbmidi_out_urb_complete, + &ep->urbs[i], ep_info->out_interval); + else + usb_fill_bulk_urb(ep->urbs[i].urb, umidi->dev, + pipe, buffer, ep->max_transfer, + snd_usbmidi_out_urb_complete, + &ep->urbs[i]); + err = usb_urb_ep_type_check(ep->urbs[i].urb); + if (err < 0) { + dev_err(&umidi->dev->dev, "invalid MIDI out EP %x\n", + ep_info->out_ep); + goto error; + } + ep->urbs[i].urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + } + + spin_lock_init(&ep->buffer_lock); + tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep); + init_waitqueue_head(&ep->drain_wait); + + for (i = 0; i < 0x10; ++i) + if (ep_info->out_cables & (1 << i)) { + ep->ports[i].ep = ep; + ep->ports[i].cable = i << 4; + } + + if (umidi->usb_protocol_ops->init_out_endpoint) + umidi->usb_protocol_ops->init_out_endpoint(ep); + + rep->out = ep; + return 0; + + error: + snd_usbmidi_out_endpoint_delete(ep); + return err; +} + +/* + * Frees everything. + */ +static void snd_usbmidi_free(struct snd_usb_midi *umidi) +{ + int i; + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i]; + if (ep->out) + snd_usbmidi_out_endpoint_delete(ep->out); + if (ep->in) + snd_usbmidi_in_endpoint_delete(ep->in); + } + mutex_destroy(&umidi->mutex); + kfree(umidi); +} + +/* + * Unlinks all URBs (must be done before the usb_device is deleted). + */ +void snd_usbmidi_disconnect(struct list_head *p) +{ + struct snd_usb_midi *umidi; + unsigned int i, j; + + umidi = list_entry(p, struct snd_usb_midi, list); + /* + * an URB's completion handler may start the timer and + * a timer may submit an URB. To reliably break the cycle + * a flag under lock must be used + */ + down_write(&umidi->disc_rwsem); + spin_lock_irq(&umidi->disc_lock); + umidi->disconnected = 1; + spin_unlock_irq(&umidi->disc_lock); + up_write(&umidi->disc_rwsem); + + del_timer_sync(&umidi->error_timer); + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i]; + if (ep->out) + tasklet_kill(&ep->out->tasklet); + if (ep->out) { + for (j = 0; j < OUTPUT_URBS; ++j) + usb_kill_urb(ep->out->urbs[j].urb); + if (umidi->usb_protocol_ops->finish_out_endpoint) + umidi->usb_protocol_ops->finish_out_endpoint(ep->out); + ep->out->active_urbs = 0; + if (ep->out->drain_urbs) { + ep->out->drain_urbs = 0; + wake_up(&ep->out->drain_wait); + } + } + if (ep->in) + for (j = 0; j < INPUT_URBS; ++j) + usb_kill_urb(ep->in->urbs[j]); + /* free endpoints here; later call can result in Oops */ + if (ep->out) + snd_usbmidi_out_endpoint_clear(ep->out); + if (ep->in) { + snd_usbmidi_in_endpoint_delete(ep->in); + ep->in = NULL; + } + } +} +EXPORT_SYMBOL(snd_usbmidi_disconnect); + +static void snd_usbmidi_rawmidi_free(struct snd_rawmidi *rmidi) +{ + struct snd_usb_midi *umidi = rmidi->private_data; + snd_usbmidi_free(umidi); +} + +static struct snd_rawmidi_substream *snd_usbmidi_find_substream(struct snd_usb_midi *umidi, + int stream, + int number) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &umidi->rmidi->streams[stream].substreams, + list) { + if (substream->number == number) + return substream; + } + return NULL; +} + +/* + * This list specifies names for ports that do not fit into the standard + * "(product) MIDI (n)" schema because they aren't external MIDI ports, + * such as internal control or synthesizer ports. + */ +static struct port_info { + u32 id; + short int port; + short int voices; + const char *name; + unsigned int seq_flags; +} snd_usbmidi_port_info[] = { +#define PORT_INFO(vendor, product, num, name_, voices_, flags) \ + { .id = USB_ID(vendor, product), \ + .port = num, .voices = voices_, \ + .name = name_, .seq_flags = flags } +#define EXTERNAL_PORT(vendor, product, num, name) \ + PORT_INFO(vendor, product, num, name, 0, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE | \ + SNDRV_SEQ_PORT_TYPE_PORT) +#define CONTROL_PORT(vendor, product, num, name) \ + PORT_INFO(vendor, product, num, name, 0, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE) +#define GM_SYNTH_PORT(vendor, product, num, name, voices) \ + PORT_INFO(vendor, product, num, name, voices, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE | \ + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) +#define ROLAND_SYNTH_PORT(vendor, product, num, name, voices) \ + PORT_INFO(vendor, product, num, name, voices, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GS | \ + SNDRV_SEQ_PORT_TYPE_MIDI_XG | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE | \ + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) +#define SOUNDCANVAS_PORT(vendor, product, num, name, voices) \ + PORT_INFO(vendor, product, num, name, voices, \ + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GM2 | \ + SNDRV_SEQ_PORT_TYPE_MIDI_GS | \ + SNDRV_SEQ_PORT_TYPE_MIDI_XG | \ + SNDRV_SEQ_PORT_TYPE_MIDI_MT32 | \ + SNDRV_SEQ_PORT_TYPE_HARDWARE | \ + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) + /* Yamaha MOTIF XF */ + GM_SYNTH_PORT(0x0499, 0x105c, 0, "%s Tone Generator", 128), + CONTROL_PORT(0x0499, 0x105c, 1, "%s Remote Control"), + EXTERNAL_PORT(0x0499, 0x105c, 2, "%s Thru"), + CONTROL_PORT(0x0499, 0x105c, 3, "%s Editor"), + /* Roland UA-100 */ + CONTROL_PORT(0x0582, 0x0000, 2, "%s Control"), + /* Roland SC-8850 */ + SOUNDCANVAS_PORT(0x0582, 0x0003, 0, "%s Part A", 128), + SOUNDCANVAS_PORT(0x0582, 0x0003, 1, "%s Part B", 128), + SOUNDCANVAS_PORT(0x0582, 0x0003, 2, "%s Part C", 128), + SOUNDCANVAS_PORT(0x0582, 0x0003, 3, "%s Part D", 128), + EXTERNAL_PORT(0x0582, 0x0003, 4, "%s MIDI 1"), + EXTERNAL_PORT(0x0582, 0x0003, 5, "%s MIDI 2"), + /* Roland U-8 */ + EXTERNAL_PORT(0x0582, 0x0004, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x0004, 1, "%s Control"), + /* Roland SC-8820 */ + SOUNDCANVAS_PORT(0x0582, 0x0007, 0, "%s Part A", 64), + SOUNDCANVAS_PORT(0x0582, 0x0007, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x0007, 2, "%s MIDI"), + /* Roland SK-500 */ + SOUNDCANVAS_PORT(0x0582, 0x000b, 0, "%s Part A", 64), + SOUNDCANVAS_PORT(0x0582, 0x000b, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x000b, 2, "%s MIDI"), + /* Roland SC-D70 */ + SOUNDCANVAS_PORT(0x0582, 0x000c, 0, "%s Part A", 64), + SOUNDCANVAS_PORT(0x0582, 0x000c, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x000c, 2, "%s MIDI"), + /* Edirol UM-880 */ + CONTROL_PORT(0x0582, 0x0014, 8, "%s Control"), + /* Edirol SD-90 */ + ROLAND_SYNTH_PORT(0x0582, 0x0016, 0, "%s Part A", 128), + ROLAND_SYNTH_PORT(0x0582, 0x0016, 1, "%s Part B", 128), + EXTERNAL_PORT(0x0582, 0x0016, 2, "%s MIDI 1"), + EXTERNAL_PORT(0x0582, 0x0016, 3, "%s MIDI 2"), + /* Edirol UM-550 */ + CONTROL_PORT(0x0582, 0x0023, 5, "%s Control"), + /* Edirol SD-20 */ + ROLAND_SYNTH_PORT(0x0582, 0x0027, 0, "%s Part A", 64), + ROLAND_SYNTH_PORT(0x0582, 0x0027, 1, "%s Part B", 64), + EXTERNAL_PORT(0x0582, 0x0027, 2, "%s MIDI"), + /* Edirol SD-80 */ + ROLAND_SYNTH_PORT(0x0582, 0x0029, 0, "%s Part A", 128), + ROLAND_SYNTH_PORT(0x0582, 0x0029, 1, "%s Part B", 128), + EXTERNAL_PORT(0x0582, 0x0029, 2, "%s MIDI 1"), + EXTERNAL_PORT(0x0582, 0x0029, 3, "%s MIDI 2"), + /* Edirol UA-700 */ + EXTERNAL_PORT(0x0582, 0x002b, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x002b, 1, "%s Control"), + /* Roland VariOS */ + EXTERNAL_PORT(0x0582, 0x002f, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x002f, 1, "%s External MIDI"), + EXTERNAL_PORT(0x0582, 0x002f, 2, "%s Sync"), + /* Edirol PCR */ + EXTERNAL_PORT(0x0582, 0x0033, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x0033, 1, "%s 1"), + EXTERNAL_PORT(0x0582, 0x0033, 2, "%s 2"), + /* BOSS GS-10 */ + EXTERNAL_PORT(0x0582, 0x003b, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x003b, 1, "%s Control"), + /* Edirol UA-1000 */ + EXTERNAL_PORT(0x0582, 0x0044, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x0044, 1, "%s Control"), + /* Edirol UR-80 */ + EXTERNAL_PORT(0x0582, 0x0048, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x0048, 1, "%s 1"), + EXTERNAL_PORT(0x0582, 0x0048, 2, "%s 2"), + /* Edirol PCR-A */ + EXTERNAL_PORT(0x0582, 0x004d, 0, "%s MIDI"), + EXTERNAL_PORT(0x0582, 0x004d, 1, "%s 1"), + EXTERNAL_PORT(0x0582, 0x004d, 2, "%s 2"), + /* BOSS GT-PRO */ + CONTROL_PORT(0x0582, 0x0089, 0, "%s Control"), + /* Edirol UM-3EX */ + CONTROL_PORT(0x0582, 0x009a, 3, "%s Control"), + /* Roland VG-99 */ + CONTROL_PORT(0x0582, 0x00b2, 0, "%s Control"), + EXTERNAL_PORT(0x0582, 0x00b2, 1, "%s MIDI"), + /* Cakewalk Sonar V-Studio 100 */ + EXTERNAL_PORT(0x0582, 0x00eb, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x00eb, 1, "%s Control"), + /* Roland VB-99 */ + CONTROL_PORT(0x0582, 0x0102, 0, "%s Control"), + EXTERNAL_PORT(0x0582, 0x0102, 1, "%s MIDI"), + /* Roland A-PRO */ + EXTERNAL_PORT(0x0582, 0x010f, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x010f, 1, "%s 1"), + CONTROL_PORT(0x0582, 0x010f, 2, "%s 2"), + /* Roland SD-50 */ + ROLAND_SYNTH_PORT(0x0582, 0x0114, 0, "%s Synth", 128), + EXTERNAL_PORT(0x0582, 0x0114, 1, "%s MIDI"), + CONTROL_PORT(0x0582, 0x0114, 2, "%s Control"), + /* Roland OCTA-CAPTURE */ + EXTERNAL_PORT(0x0582, 0x0120, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x0120, 1, "%s Control"), + EXTERNAL_PORT(0x0582, 0x0121, 0, "%s MIDI"), + CONTROL_PORT(0x0582, 0x0121, 1, "%s Control"), + /* Roland SPD-SX */ + CONTROL_PORT(0x0582, 0x0145, 0, "%s Control"), + EXTERNAL_PORT(0x0582, 0x0145, 1, "%s MIDI"), + /* Roland A-Series */ + CONTROL_PORT(0x0582, 0x0156, 0, "%s Keyboard"), + EXTERNAL_PORT(0x0582, 0x0156, 1, "%s MIDI"), + /* Roland INTEGRA-7 */ + ROLAND_SYNTH_PORT(0x0582, 0x015b, 0, "%s Synth", 128), + CONTROL_PORT(0x0582, 0x015b, 1, "%s Control"), + /* M-Audio MidiSport 8x8 */ + CONTROL_PORT(0x0763, 0x1031, 8, "%s Control"), + CONTROL_PORT(0x0763, 0x1033, 8, "%s Control"), + /* MOTU Fastlane */ + EXTERNAL_PORT(0x07fd, 0x0001, 0, "%s MIDI A"), + EXTERNAL_PORT(0x07fd, 0x0001, 1, "%s MIDI B"), + /* Emagic Unitor8/AMT8/MT4 */ + EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"), + EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"), + EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"), + /* Akai MPD16 */ + CONTROL_PORT(0x09e8, 0x0062, 0, "%s Control"), + PORT_INFO(0x09e8, 0x0062, 1, "%s MIDI", 0, + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE), + /* Access Music Virus TI */ + EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"), + PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0, + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER), +}; + +static struct port_info *find_port_info(struct snd_usb_midi *umidi, int number) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(snd_usbmidi_port_info); ++i) { + if (snd_usbmidi_port_info[i].id == umidi->usb_id && + snd_usbmidi_port_info[i].port == number) + return &snd_usbmidi_port_info[i]; + } + return NULL; +} + +static void snd_usbmidi_get_port_info(struct snd_rawmidi *rmidi, int number, + struct snd_seq_port_info *seq_port_info) +{ + struct snd_usb_midi *umidi = rmidi->private_data; + struct port_info *port_info; + + /* TODO: read port flags from descriptors */ + port_info = find_port_info(umidi, number); + if (port_info) { + seq_port_info->type = port_info->seq_flags; + seq_port_info->midi_voices = port_info->voices; + } +} + +static void snd_usbmidi_init_substream(struct snd_usb_midi *umidi, + int stream, int number, + struct snd_rawmidi_substream **rsubstream) +{ + struct port_info *port_info; + const char *name_format; + + struct snd_rawmidi_substream *substream = + snd_usbmidi_find_substream(umidi, stream, number); + if (!substream) { + dev_err(&umidi->dev->dev, "substream %d:%d not found\n", stream, + number); + return; + } + + /* TODO: read port name from jack descriptor */ + port_info = find_port_info(umidi, number); + name_format = port_info ? port_info->name : "%s MIDI %d"; + snprintf(substream->name, sizeof(substream->name), + name_format, umidi->card->shortname, number + 1); + + *rsubstream = substream; +} + +/* + * Creates the endpoints and their ports. + */ +static int snd_usbmidi_create_endpoints(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *endpoints) +{ + int i, j, err; + int out_ports = 0, in_ports = 0; + + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + if (endpoints[i].out_cables) { + err = snd_usbmidi_out_endpoint_create(umidi, + &endpoints[i], + &umidi->endpoints[i]); + if (err < 0) + return err; + } + if (endpoints[i].in_cables) { + err = snd_usbmidi_in_endpoint_create(umidi, + &endpoints[i], + &umidi->endpoints[i]); + if (err < 0) + return err; + } + + for (j = 0; j < 0x10; ++j) { + if (endpoints[i].out_cables & (1 << j)) { + snd_usbmidi_init_substream(umidi, + SNDRV_RAWMIDI_STREAM_OUTPUT, + out_ports, + &umidi->endpoints[i].out->ports[j].substream); + ++out_ports; + } + if (endpoints[i].in_cables & (1 << j)) { + snd_usbmidi_init_substream(umidi, + SNDRV_RAWMIDI_STREAM_INPUT, + in_ports, + &umidi->endpoints[i].in->ports[j].substream); + ++in_ports; + } + } + } + dev_dbg(&umidi->dev->dev, "created %d output and %d input ports\n", + out_ports, in_ports); + return 0; +} + +static struct usb_ms_endpoint_descriptor *find_usb_ms_endpoint_descriptor( + struct usb_host_endpoint *hostep) +{ + unsigned char *extra = hostep->extra; + int extralen = hostep->extralen; + + while (extralen > 3) { + struct usb_ms_endpoint_descriptor *ms_ep = + (struct usb_ms_endpoint_descriptor *)extra; + + if (ms_ep->bLength > 3 && + ms_ep->bDescriptorType == USB_DT_CS_ENDPOINT && + ms_ep->bDescriptorSubtype == UAC_MS_GENERAL) + return ms_ep; + if (!extra[0]) + break; + extralen -= extra[0]; + extra += extra[0]; + } + return NULL; +} + +/* + * Returns MIDIStreaming device capabilities. + */ +static int snd_usbmidi_get_ms_info(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *endpoints) +{ + struct usb_interface *intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor *intfd; + struct usb_ms_header_descriptor *ms_header; + struct usb_host_endpoint *hostep; + struct usb_endpoint_descriptor *ep; + struct usb_ms_endpoint_descriptor *ms_ep; + int i, epidx; + + intf = umidi->iface; + if (!intf) + return -ENXIO; + hostif = &intf->altsetting[0]; + intfd = get_iface_desc(hostif); + ms_header = (struct usb_ms_header_descriptor *)hostif->extra; + if (hostif->extralen >= 7 && + ms_header->bLength >= 7 && + ms_header->bDescriptorType == USB_DT_CS_INTERFACE && + ms_header->bDescriptorSubtype == UAC_HEADER) + dev_dbg(&umidi->dev->dev, "MIDIStreaming version %02x.%02x\n", + ms_header->bcdMSC[1], ms_header->bcdMSC[0]); + else + dev_warn(&umidi->dev->dev, + "MIDIStreaming interface descriptor not found\n"); + + epidx = 0; + for (i = 0; i < intfd->bNumEndpoints; ++i) { + hostep = &hostif->endpoint[i]; + ep = get_ep_desc(hostep); + if (!usb_endpoint_xfer_bulk(ep) && !usb_endpoint_xfer_int(ep)) + continue; + ms_ep = find_usb_ms_endpoint_descriptor(hostep); + if (!ms_ep) + continue; + if (ms_ep->bLength <= sizeof(*ms_ep)) + continue; + if (ms_ep->bNumEmbMIDIJack > 0x10) + continue; + if (ms_ep->bLength < sizeof(*ms_ep) + ms_ep->bNumEmbMIDIJack) + continue; + if (usb_endpoint_dir_out(ep)) { + if (endpoints[epidx].out_ep) { + if (++epidx >= MIDI_MAX_ENDPOINTS) { + dev_warn(&umidi->dev->dev, + "too many endpoints\n"); + break; + } + } + endpoints[epidx].out_ep = usb_endpoint_num(ep); + if (usb_endpoint_xfer_int(ep)) + endpoints[epidx].out_interval = ep->bInterval; + else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW) + /* + * Low speed bulk transfers don't exist, so + * force interrupt transfers for devices like + * ESI MIDI Mate that try to use them anyway. + */ + endpoints[epidx].out_interval = 1; + endpoints[epidx].out_cables = + (1 << ms_ep->bNumEmbMIDIJack) - 1; + dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n", + ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); + } else { + if (endpoints[epidx].in_ep) { + if (++epidx >= MIDI_MAX_ENDPOINTS) { + dev_warn(&umidi->dev->dev, + "too many endpoints\n"); + break; + } + } + endpoints[epidx].in_ep = usb_endpoint_num(ep); + if (usb_endpoint_xfer_int(ep)) + endpoints[epidx].in_interval = ep->bInterval; + else if (snd_usb_get_speed(umidi->dev) == USB_SPEED_LOW) + endpoints[epidx].in_interval = 1; + endpoints[epidx].in_cables = + (1 << ms_ep->bNumEmbMIDIJack) - 1; + dev_dbg(&umidi->dev->dev, "EP %02X: %d jack(s)\n", + ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack); + } + } + return 0; +} + +static int roland_load_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const names[] = { "High Load", "Light Load" }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int roland_load_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + value->value.enumerated.item[0] = kcontrol->private_value; + return 0; +} + +static int roland_load_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_usb_midi *umidi = kcontrol->private_data; + int changed; + + if (value->value.enumerated.item[0] > 1) + return -EINVAL; + mutex_lock(&umidi->mutex); + changed = value->value.enumerated.item[0] != kcontrol->private_value; + if (changed) + kcontrol->private_value = value->value.enumerated.item[0]; + mutex_unlock(&umidi->mutex); + return changed; +} + +static const struct snd_kcontrol_new roland_load_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MIDI Input Mode", + .info = roland_load_info, + .get = roland_load_get, + .put = roland_load_put, + .private_value = 1, +}; + +/* + * On Roland devices, use the second alternate setting to be able to use + * the interrupt input endpoint. + */ +static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi *umidi) +{ + struct usb_interface *intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor *intfd; + + intf = umidi->iface; + if (!intf || intf->num_altsetting != 2) + return; + + hostif = &intf->altsetting[1]; + intfd = get_iface_desc(hostif); + /* If either or both of the endpoints support interrupt transfer, + * then use the alternate setting + */ + if (intfd->bNumEndpoints != 2 || + !((get_endpoint(hostif, 0)->bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT || + (get_endpoint(hostif, 1)->bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) + return; + + dev_dbg(&umidi->dev->dev, "switching to altsetting %d with int ep\n", + intfd->bAlternateSetting); + usb_set_interface(umidi->dev, intfd->bInterfaceNumber, + intfd->bAlternateSetting); + + umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi); + if (snd_ctl_add(umidi->card, umidi->roland_load_ctl) < 0) + umidi->roland_load_ctl = NULL; +} + +/* + * Try to find any usable endpoints in the interface. + */ +static int snd_usbmidi_detect_endpoints(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *endpoint, + int max_endpoints) +{ + struct usb_interface *intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor *intfd; + struct usb_endpoint_descriptor *epd; + int i, out_eps = 0, in_eps = 0; + + if (USB_ID_VENDOR(umidi->usb_id) == 0x0582) + snd_usbmidi_switch_roland_altsetting(umidi); + + if (endpoint[0].out_ep || endpoint[0].in_ep) + return 0; + + intf = umidi->iface; + if (!intf || intf->num_altsetting < 1) + return -ENOENT; + hostif = intf->cur_altsetting; + intfd = get_iface_desc(hostif); + + for (i = 0; i < intfd->bNumEndpoints; ++i) { + epd = get_endpoint(hostif, i); + if (!usb_endpoint_xfer_bulk(epd) && + !usb_endpoint_xfer_int(epd)) + continue; + if (out_eps < max_endpoints && + usb_endpoint_dir_out(epd)) { + endpoint[out_eps].out_ep = usb_endpoint_num(epd); + if (usb_endpoint_xfer_int(epd)) + endpoint[out_eps].out_interval = epd->bInterval; + ++out_eps; + } + if (in_eps < max_endpoints && + usb_endpoint_dir_in(epd)) { + endpoint[in_eps].in_ep = usb_endpoint_num(epd); + if (usb_endpoint_xfer_int(epd)) + endpoint[in_eps].in_interval = epd->bInterval; + ++in_eps; + } + } + return (out_eps || in_eps) ? 0 : -ENOENT; +} + +/* + * Detects the endpoints for one-port-per-endpoint protocols. + */ +static int snd_usbmidi_detect_per_port_endpoints(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *endpoints) +{ + int err, i; + + err = snd_usbmidi_detect_endpoints(umidi, endpoints, MIDI_MAX_ENDPOINTS); + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + if (endpoints[i].out_ep) + endpoints[i].out_cables = 0x0001; + if (endpoints[i].in_ep) + endpoints[i].in_cables = 0x0001; + } + return err; +} + +/* + * Detects the endpoints and ports of Yamaha devices. + */ +static int snd_usbmidi_detect_yamaha(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *endpoint) +{ + struct usb_interface *intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor *intfd; + uint8_t *cs_desc; + + intf = umidi->iface; + if (!intf) + return -ENOENT; + hostif = intf->altsetting; + intfd = get_iface_desc(hostif); + if (intfd->bNumEndpoints < 1) + return -ENOENT; + + /* + * For each port there is one MIDI_IN/OUT_JACK descriptor, not + * necessarily with any useful contents. So simply count 'em. + */ + for (cs_desc = hostif->extra; + cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2; + cs_desc += cs_desc[0]) { + if (cs_desc[1] == USB_DT_CS_INTERFACE) { + if (cs_desc[2] == UAC_MIDI_IN_JACK) + endpoint->in_cables = + (endpoint->in_cables << 1) | 1; + else if (cs_desc[2] == UAC_MIDI_OUT_JACK) + endpoint->out_cables = + (endpoint->out_cables << 1) | 1; + } + } + if (!endpoint->in_cables && !endpoint->out_cables) + return -ENOENT; + + return snd_usbmidi_detect_endpoints(umidi, endpoint, 1); +} + +/* + * Detects the endpoints and ports of Roland devices. + */ +static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *endpoint) +{ + struct usb_interface *intf; + struct usb_host_interface *hostif; + u8 *cs_desc; + + intf = umidi->iface; + if (!intf) + return -ENOENT; + hostif = intf->altsetting; + /* + * Some devices have a descriptor <06 24 F1 02 >, + * some have standard class descriptors, or both kinds, or neither. + */ + for (cs_desc = hostif->extra; + cs_desc < hostif->extra + hostif->extralen && cs_desc[0] >= 2; + cs_desc += cs_desc[0]) { + if (cs_desc[0] >= 6 && + cs_desc[1] == USB_DT_CS_INTERFACE && + cs_desc[2] == 0xf1 && + cs_desc[3] == 0x02) { + if (cs_desc[4] > 0x10 || cs_desc[5] > 0x10) + continue; + endpoint->in_cables = (1 << cs_desc[4]) - 1; + endpoint->out_cables = (1 << cs_desc[5]) - 1; + return snd_usbmidi_detect_endpoints(umidi, endpoint, 1); + } else if (cs_desc[0] >= 7 && + cs_desc[1] == USB_DT_CS_INTERFACE && + cs_desc[2] == UAC_HEADER) { + return snd_usbmidi_get_ms_info(umidi, endpoint); + } + } + + return -ENODEV; +} + +/* + * Creates the endpoints and their ports for Midiman devices. + */ +static int snd_usbmidi_create_endpoints_midiman(struct snd_usb_midi *umidi, + struct snd_usb_midi_endpoint_info *endpoint) +{ + struct snd_usb_midi_endpoint_info ep_info; + struct usb_interface *intf; + struct usb_host_interface *hostif; + struct usb_interface_descriptor *intfd; + struct usb_endpoint_descriptor *epd; + int cable, err; + + intf = umidi->iface; + if (!intf) + return -ENOENT; + hostif = intf->altsetting; + intfd = get_iface_desc(hostif); + /* + * The various MidiSport devices have more or less random endpoint + * numbers, so we have to identify the endpoints by their index in + * the descriptor array, like the driver for that other OS does. + * + * There is one interrupt input endpoint for all input ports, one + * bulk output endpoint for even-numbered ports, and one for odd- + * numbered ports. Both bulk output endpoints have corresponding + * input bulk endpoints (at indices 1 and 3) which aren't used. + */ + if (intfd->bNumEndpoints < (endpoint->out_cables > 0x0001 ? 5 : 3)) { + dev_dbg(&umidi->dev->dev, "not enough endpoints\n"); + return -ENOENT; + } + + epd = get_endpoint(hostif, 0); + if (!usb_endpoint_dir_in(epd) || !usb_endpoint_xfer_int(epd)) { + dev_dbg(&umidi->dev->dev, "endpoint[0] isn't interrupt\n"); + return -ENXIO; + } + epd = get_endpoint(hostif, 2); + if (!usb_endpoint_dir_out(epd) || !usb_endpoint_xfer_bulk(epd)) { + dev_dbg(&umidi->dev->dev, "endpoint[2] isn't bulk output\n"); + return -ENXIO; + } + if (endpoint->out_cables > 0x0001) { + epd = get_endpoint(hostif, 4); + if (!usb_endpoint_dir_out(epd) || + !usb_endpoint_xfer_bulk(epd)) { + dev_dbg(&umidi->dev->dev, + "endpoint[4] isn't bulk output\n"); + return -ENXIO; + } + } + + ep_info.out_ep = get_endpoint(hostif, 2)->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + ep_info.out_interval = 0; + ep_info.out_cables = endpoint->out_cables & 0x5555; + err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, + &umidi->endpoints[0]); + if (err < 0) + return err; + + ep_info.in_ep = get_endpoint(hostif, 0)->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + ep_info.in_interval = get_endpoint(hostif, 0)->bInterval; + ep_info.in_cables = endpoint->in_cables; + err = snd_usbmidi_in_endpoint_create(umidi, &ep_info, + &umidi->endpoints[0]); + if (err < 0) + return err; + + if (endpoint->out_cables > 0x0001) { + ep_info.out_ep = get_endpoint(hostif, 4)->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + ep_info.out_cables = endpoint->out_cables & 0xaaaa; + err = snd_usbmidi_out_endpoint_create(umidi, &ep_info, + &umidi->endpoints[1]); + if (err < 0) + return err; + } + + for (cable = 0; cable < 0x10; ++cable) { + if (endpoint->out_cables & (1 << cable)) + snd_usbmidi_init_substream(umidi, + SNDRV_RAWMIDI_STREAM_OUTPUT, + cable, + &umidi->endpoints[cable & 1].out->ports[cable].substream); + if (endpoint->in_cables & (1 << cable)) + snd_usbmidi_init_substream(umidi, + SNDRV_RAWMIDI_STREAM_INPUT, + cable, + &umidi->endpoints[0].in->ports[cable].substream); + } + return 0; +} + +static const struct snd_rawmidi_global_ops snd_usbmidi_ops = { + .get_port_info = snd_usbmidi_get_port_info, +}; + +static int snd_usbmidi_create_rawmidi(struct snd_usb_midi *umidi, + int out_ports, int in_ports) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(umidi->card, "USB MIDI", + umidi->next_midi_device++, + out_ports, in_ports, &rmidi); + if (err < 0) + return err; + strcpy(rmidi->name, umidi->card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->ops = &snd_usbmidi_ops; + rmidi->private_data = umidi; + rmidi->private_free = snd_usbmidi_rawmidi_free; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_usbmidi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_usbmidi_input_ops); + + umidi->rmidi = rmidi; + return 0; +} + +/* + * Temporarily stop input. + */ +void snd_usbmidi_input_stop(struct list_head *p) +{ + struct snd_usb_midi *umidi; + unsigned int i, j; + + umidi = list_entry(p, struct snd_usb_midi, list); + if (!umidi->input_running) + return; + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + struct snd_usb_midi_endpoint *ep = &umidi->endpoints[i]; + if (ep->in) + for (j = 0; j < INPUT_URBS; ++j) + usb_kill_urb(ep->in->urbs[j]); + } + umidi->input_running = 0; +} +EXPORT_SYMBOL(snd_usbmidi_input_stop); + +static void snd_usbmidi_input_start_ep(struct snd_usb_midi *umidi, + struct snd_usb_midi_in_endpoint *ep) +{ + unsigned int i; + unsigned long flags; + + if (!ep) + return; + for (i = 0; i < INPUT_URBS; ++i) { + struct urb *urb = ep->urbs[i]; + spin_lock_irqsave(&umidi->disc_lock, flags); + if (!atomic_read(&urb->use_count)) { + urb->dev = ep->umidi->dev; + snd_usbmidi_submit_urb(urb, GFP_ATOMIC); + } + spin_unlock_irqrestore(&umidi->disc_lock, flags); + } +} + +/* + * Resume input after a call to snd_usbmidi_input_stop(). + */ +void snd_usbmidi_input_start(struct list_head *p) +{ + struct snd_usb_midi *umidi; + int i; + + umidi = list_entry(p, struct snd_usb_midi, list); + if (umidi->input_running || !umidi->opened[1]) + return; + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) + snd_usbmidi_input_start_ep(umidi, umidi->endpoints[i].in); + umidi->input_running = 1; +} +EXPORT_SYMBOL(snd_usbmidi_input_start); + +/* + * Prepare for suspend. Typically called from the USB suspend callback. + */ +void snd_usbmidi_suspend(struct list_head *p) +{ + struct snd_usb_midi *umidi; + + umidi = list_entry(p, struct snd_usb_midi, list); + mutex_lock(&umidi->mutex); + snd_usbmidi_input_stop(p); + mutex_unlock(&umidi->mutex); +} +EXPORT_SYMBOL(snd_usbmidi_suspend); + +/* + * Resume. Typically called from the USB resume callback. + */ +void snd_usbmidi_resume(struct list_head *p) +{ + struct snd_usb_midi *umidi; + + umidi = list_entry(p, struct snd_usb_midi, list); + mutex_lock(&umidi->mutex); + snd_usbmidi_input_start(p); + mutex_unlock(&umidi->mutex); +} +EXPORT_SYMBOL(snd_usbmidi_resume); + +/* + * Creates and registers everything needed for a MIDI streaming interface. + */ +int __snd_usbmidi_create(struct snd_card *card, + struct usb_interface *iface, + struct list_head *midi_list, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id) +{ + struct snd_usb_midi *umidi; + struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS]; + int out_ports, in_ports; + int i, err; + + umidi = kzalloc(sizeof(*umidi), GFP_KERNEL); + if (!umidi) + return -ENOMEM; + umidi->dev = interface_to_usbdev(iface); + umidi->card = card; + umidi->iface = iface; + umidi->quirk = quirk; + umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; + spin_lock_init(&umidi->disc_lock); + init_rwsem(&umidi->disc_rwsem); + mutex_init(&umidi->mutex); + if (!usb_id) + usb_id = USB_ID(le16_to_cpu(umidi->dev->descriptor.idVendor), + le16_to_cpu(umidi->dev->descriptor.idProduct)); + umidi->usb_id = usb_id; + timer_setup(&umidi->error_timer, snd_usbmidi_error_timer, 0); + + /* detect the endpoint(s) to use */ + memset(endpoints, 0, sizeof(endpoints)); + switch (quirk ? quirk->type : QUIRK_MIDI_STANDARD_INTERFACE) { + case QUIRK_MIDI_STANDARD_INTERFACE: + err = snd_usbmidi_get_ms_info(umidi, endpoints); + if (umidi->usb_id == USB_ID(0x0763, 0x0150)) /* M-Audio Uno */ + umidi->usb_protocol_ops = + &snd_usbmidi_maudio_broken_running_status_ops; + break; + case QUIRK_MIDI_US122L: + umidi->usb_protocol_ops = &snd_usbmidi_122l_ops; + /* fall through */ + case QUIRK_MIDI_FIXED_ENDPOINT: + memcpy(&endpoints[0], quirk->data, + sizeof(struct snd_usb_midi_endpoint_info)); + err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1); + break; + case QUIRK_MIDI_YAMAHA: + err = snd_usbmidi_detect_yamaha(umidi, &endpoints[0]); + break; + case QUIRK_MIDI_ROLAND: + err = snd_usbmidi_detect_roland(umidi, &endpoints[0]); + break; + case QUIRK_MIDI_MIDIMAN: + umidi->usb_protocol_ops = &snd_usbmidi_midiman_ops; + memcpy(&endpoints[0], quirk->data, + sizeof(struct snd_usb_midi_endpoint_info)); + err = 0; + break; + case QUIRK_MIDI_NOVATION: + umidi->usb_protocol_ops = &snd_usbmidi_novation_ops; + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + case QUIRK_MIDI_RAW_BYTES: + umidi->usb_protocol_ops = &snd_usbmidi_raw_ops; + /* + * Interface 1 contains isochronous endpoints, but with the same + * numbers as in interface 0. Since it is interface 1 that the + * USB core has most recently seen, these descriptors are now + * associated with the endpoint numbers. This will foul up our + * attempts to submit bulk/interrupt URBs to the endpoints in + * interface 0, so we have to make sure that the USB core looks + * again at interface 0 by calling usb_set_interface() on it. + */ + if (umidi->usb_id == USB_ID(0x07fd, 0x0001)) /* MOTU Fastlane */ + usb_set_interface(umidi->dev, 0, 0); + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + case QUIRK_MIDI_EMAGIC: + umidi->usb_protocol_ops = &snd_usbmidi_emagic_ops; + memcpy(&endpoints[0], quirk->data, + sizeof(struct snd_usb_midi_endpoint_info)); + err = snd_usbmidi_detect_endpoints(umidi, &endpoints[0], 1); + break; + case QUIRK_MIDI_CME: + umidi->usb_protocol_ops = &snd_usbmidi_cme_ops; + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + case QUIRK_MIDI_AKAI: + umidi->usb_protocol_ops = &snd_usbmidi_akai_ops; + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + /* endpoint 1 is input-only */ + endpoints[1].out_cables = 0; + break; + case QUIRK_MIDI_FTDI: + umidi->usb_protocol_ops = &snd_usbmidi_ftdi_ops; + + /* set baud rate to 31250 (48 MHz / 16 / 96) */ + err = usb_control_msg(umidi->dev, usb_sndctrlpipe(umidi->dev, 0), + 3, 0x40, 0x60, 0, NULL, 0, 1000); + if (err < 0) + break; + + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + case QUIRK_MIDI_CH345: + umidi->usb_protocol_ops = &snd_usbmidi_ch345_broken_sysex_ops; + err = snd_usbmidi_detect_per_port_endpoints(umidi, endpoints); + break; + default: + dev_err(&umidi->dev->dev, "invalid quirk type %d\n", + quirk->type); + err = -ENXIO; + break; + } + if (err < 0) + goto free_midi; + + /* create rawmidi device */ + out_ports = 0; + in_ports = 0; + for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) { + out_ports += hweight16(endpoints[i].out_cables); + in_ports += hweight16(endpoints[i].in_cables); + } + err = snd_usbmidi_create_rawmidi(umidi, out_ports, in_ports); + if (err < 0) + goto free_midi; + + /* create endpoint/port structures */ + if (quirk && quirk->type == QUIRK_MIDI_MIDIMAN) + err = snd_usbmidi_create_endpoints_midiman(umidi, &endpoints[0]); + else + err = snd_usbmidi_create_endpoints(umidi, endpoints); + if (err < 0) + goto exit; + + usb_autopm_get_interface_no_resume(umidi->iface); + + list_add_tail(&umidi->list, midi_list); + return 0; + +free_midi: + kfree(umidi); +exit: + return err; +} +EXPORT_SYMBOL(__snd_usbmidi_create); diff --git a/sound/usb/midi.h b/sound/usb/midi.h new file mode 100644 index 000000000..8c38aec22 --- /dev/null +++ b/sound/usb/midi.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBMIDI_H +#define __USBMIDI_H + +/* maximum number of endpoints per interface */ +#define MIDI_MAX_ENDPOINTS 2 + +/* data for QUIRK_MIDI_FIXED_ENDPOINT */ +struct snd_usb_midi_endpoint_info { + int8_t out_ep; /* ep number, 0 autodetect */ + uint8_t out_interval; /* interval for interrupt endpoints */ + int8_t in_ep; + uint8_t in_interval; + uint16_t out_cables; /* bitmask */ + uint16_t in_cables; /* bitmask */ +}; + +/* for QUIRK_MIDI_YAMAHA, data is NULL */ + +/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info + * structure (out_cables and in_cables only) */ + +/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk + * structures, terminated with .ifnum = -1 */ + +/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */ + +/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */ + +/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */ + +/* for QUIRK_IGNORE_INTERFACE, data is NULL */ + +/* for QUIRK_MIDI_NOVATION and _RAW, data is NULL */ + +/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info + * structure (out_cables and in_cables only) */ + +/* for QUIRK_MIDI_CME, data is NULL */ + +/* for QUIRK_MIDI_AKAI, data is NULL */ + +int __snd_usbmidi_create(struct snd_card *card, + struct usb_interface *iface, + struct list_head *midi_list, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id); + +static inline int snd_usbmidi_create(struct snd_card *card, + struct usb_interface *iface, + struct list_head *midi_list, + const struct snd_usb_audio_quirk *quirk) +{ + return __snd_usbmidi_create(card, iface, midi_list, quirk, 0); +} + +void snd_usbmidi_input_stop(struct list_head *p); +void snd_usbmidi_input_start(struct list_head *p); +void snd_usbmidi_disconnect(struct list_head *p); +void snd_usbmidi_suspend(struct list_head *p); +void snd_usbmidi_resume(struct list_head *p); + +#endif /* __USBMIDI_H */ diff --git a/sound/usb/misc/Makefile b/sound/usb/misc/Makefile new file mode 100644 index 000000000..ccefd8158 --- /dev/null +++ b/sound/usb/misc/Makefile @@ -0,0 +1,2 @@ +snd-ua101-objs := ua101.o +obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c new file mode 100644 index 000000000..5e94b6b4b --- /dev/null +++ b/sound/usb/misc/ua101.c @@ -0,0 +1,1386 @@ +/* + * Edirol UA-101/UA-1000 driver + * Copyright (c) Clemens Ladisch + * + * This driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../usbaudio.h" +#include "../midi.h" + +MODULE_DESCRIPTION("Edirol UA-101/1000 driver"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101},{Edirol,UA-1000}}"); + +/* + * Should not be lower than the minimum scheduling delay of the host + * controller. Some Intel controllers need more than one frame; as long as + * that driver doesn't tell us about this, use 1.5 frames just to be sure. + */ +#define MIN_QUEUE_LENGTH 12 +/* Somewhat random. */ +#define MAX_QUEUE_LENGTH 30 +/* + * This magic value optimizes memory usage efficiency for the UA-101's packet + * sizes at all sample rates, taking into account the stupid cache pool sizes + * that usb_alloc_coherent() uses. + */ +#define DEFAULT_QUEUE_LENGTH 21 + +#define MAX_PACKET_SIZE 672 /* hardware specific */ +#define MAX_MEMORY_BUFFERS DIV_ROUND_UP(MAX_QUEUE_LENGTH, \ + PAGE_SIZE / MAX_PACKET_SIZE) + +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 queue_length = 21; + +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"); +module_param(queue_length, uint, 0644); +MODULE_PARM_DESC(queue_length, "USB queue length in microframes, " + __stringify(MIN_QUEUE_LENGTH)"-"__stringify(MAX_QUEUE_LENGTH)); + +enum { + INTF_PLAYBACK, + INTF_CAPTURE, + INTF_MIDI, + + INTF_COUNT +}; + +/* bits in struct ua101::states */ +enum { + USB_CAPTURE_RUNNING, + USB_PLAYBACK_RUNNING, + ALSA_CAPTURE_OPEN, + ALSA_PLAYBACK_OPEN, + ALSA_CAPTURE_RUNNING, + ALSA_PLAYBACK_RUNNING, + CAPTURE_URB_COMPLETED, + PLAYBACK_URB_COMPLETED, + DISCONNECTED, +}; + +struct ua101 { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *intf[INTF_COUNT]; + int card_index; + struct snd_pcm *pcm; + struct list_head midi_list; + u64 format_bit; + unsigned int rate; + unsigned int packets_per_second; + spinlock_t lock; + struct mutex mutex; + unsigned long states; + + /* FIFO to synchronize playback rate to capture rate */ + unsigned int rate_feedback_start; + unsigned int rate_feedback_count; + u8 rate_feedback[MAX_QUEUE_LENGTH]; + + struct list_head ready_playback_urbs; + struct tasklet_struct playback_tasklet; + wait_queue_head_t alsa_capture_wait; + wait_queue_head_t rate_feedback_wait; + wait_queue_head_t alsa_playback_wait; + struct ua101_stream { + struct snd_pcm_substream *substream; + unsigned int usb_pipe; + unsigned int channels; + unsigned int frame_bytes; + unsigned int max_packet_bytes; + unsigned int period_pos; + unsigned int buffer_pos; + unsigned int queue_length; + struct ua101_urb { + struct urb urb; + struct usb_iso_packet_descriptor iso_frame_desc[1]; + struct list_head ready_list; + } *urbs[MAX_QUEUE_LENGTH]; + struct { + unsigned int size; + void *addr; + dma_addr_t dma; + } buffers[MAX_MEMORY_BUFFERS]; + } capture, playback; +}; + +static DEFINE_MUTEX(devices_mutex); +static unsigned int devices_used; +static struct usb_driver ua101_driver; + +static void abort_alsa_playback(struct ua101 *ua); +static void abort_alsa_capture(struct ua101 *ua); + +static const char *usb_error_string(int err) +{ + switch (err) { + case -ENODEV: + return "no device"; + case -ENOENT: + return "endpoint not enabled"; + case -EPIPE: + return "endpoint stalled"; + case -ENOSPC: + return "not enough bandwidth"; + case -ESHUTDOWN: + return "device disabled"; + case -EHOSTUNREACH: + return "device suspended"; + case -EINVAL: + case -EAGAIN: + case -EFBIG: + case -EMSGSIZE: + return "internal error"; + default: + return "unknown error"; + } +} + +static void abort_usb_capture(struct ua101 *ua) +{ + if (test_and_clear_bit(USB_CAPTURE_RUNNING, &ua->states)) { + wake_up(&ua->alsa_capture_wait); + wake_up(&ua->rate_feedback_wait); + } +} + +static void abort_usb_playback(struct ua101 *ua) +{ + if (test_and_clear_bit(USB_PLAYBACK_RUNNING, &ua->states)) + wake_up(&ua->alsa_playback_wait); +} + +static void playback_urb_complete(struct urb *usb_urb) +{ + struct ua101_urb *urb = (struct ua101_urb *)usb_urb; + struct ua101 *ua = urb->urb.context; + unsigned long flags; + + if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ + urb->urb.status == -ENODEV || /* device removed */ + urb->urb.status == -ECONNRESET || /* unlinked */ + urb->urb.status == -ESHUTDOWN)) { /* device disabled */ + abort_usb_playback(ua); + abort_alsa_playback(ua); + return; + } + + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { + /* append URB to FIFO */ + spin_lock_irqsave(&ua->lock, flags); + list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); + if (ua->rate_feedback_count > 0) + tasklet_schedule(&ua->playback_tasklet); + ua->playback.substream->runtime->delay -= + urb->urb.iso_frame_desc[0].length / + ua->playback.frame_bytes; + spin_unlock_irqrestore(&ua->lock, flags); + } +} + +static void first_playback_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + + urb->complete = playback_urb_complete; + playback_urb_complete(urb); + + set_bit(PLAYBACK_URB_COMPLETED, &ua->states); + wake_up(&ua->alsa_playback_wait); +} + +/* copy data from the ALSA ring buffer into the URB buffer */ +static bool copy_playback_data(struct ua101_stream *stream, struct urb *urb, + unsigned int frames) +{ + struct snd_pcm_runtime *runtime; + unsigned int frame_bytes, frames1; + const u8 *source; + + runtime = stream->substream->runtime; + frame_bytes = stream->frame_bytes; + source = runtime->dma_area + stream->buffer_pos * frame_bytes; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(urb->transfer_buffer, source, frames * frame_bytes); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(urb->transfer_buffer, source, frames1 * frame_bytes); + memcpy(urb->transfer_buffer + frames1 * frame_bytes, + runtime->dma_area, (frames - frames1) * frame_bytes); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + return true; + } + return false; +} + +static inline void add_with_wraparound(struct ua101 *ua, + unsigned int *value, unsigned int add) +{ + *value += add; + if (*value >= ua->playback.queue_length) + *value -= ua->playback.queue_length; +} + +static void playback_tasklet(unsigned long data) +{ + struct ua101 *ua = (void *)data; + unsigned long flags; + unsigned int frames; + struct ua101_urb *urb; + bool do_period_elapsed = false; + int err; + + if (unlikely(!test_bit(USB_PLAYBACK_RUNNING, &ua->states))) + return; + + /* + * Synchronizing the playback rate to the capture rate is done by using + * the same sequence of packet sizes for both streams. + * Submitting a playback URB therefore requires both a ready URB and + * the size of the corresponding capture packet, i.e., both playback + * and capture URBs must have been completed. Since the USB core does + * not guarantee that playback and capture complete callbacks are + * called alternately, we use two FIFOs for packet sizes and read URBs; + * submitting playback URBs is possible as long as both FIFOs are + * nonempty. + */ + spin_lock_irqsave(&ua->lock, flags); + while (ua->rate_feedback_count > 0 && + !list_empty(&ua->ready_playback_urbs)) { + /* take packet size out of FIFO */ + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + + /* take URB out of FIFO */ + urb = list_first_entry(&ua->ready_playback_urbs, + struct ua101_urb, ready_list); + list_del(&urb->ready_list); + + /* fill packet with data or silence */ + urb->urb.iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, + &urb->urb, + frames); + else + memset(urb->urb.transfer_buffer, 0, + urb->urb.iso_frame_desc[0].length); + + /* and off you go ... */ + err = usb_submit_urb(&urb->urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + spin_unlock_irqrestore(&ua->lock, flags); + abort_usb_playback(ua); + abort_alsa_playback(ua); + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + return; + } + ua->playback.substream->runtime->delay += frames; + } + spin_unlock_irqrestore(&ua->lock, flags); + if (do_period_elapsed) + snd_pcm_period_elapsed(ua->playback.substream); +} + +/* copy data from the URB buffer into the ALSA ring buffer */ +static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, + unsigned int frames) +{ + struct snd_pcm_runtime *runtime; + unsigned int frame_bytes, frames1; + u8 *dest; + + runtime = stream->substream->runtime; + frame_bytes = stream->frame_bytes; + dest = runtime->dma_area + stream->buffer_pos * frame_bytes; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(dest, urb->transfer_buffer, frames * frame_bytes); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(dest, urb->transfer_buffer, frames1 * frame_bytes); + memcpy(runtime->dma_area, + urb->transfer_buffer + frames1 * frame_bytes, + (frames - frames1) * frame_bytes); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + return true; + } + return false; +} + +static void capture_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + struct ua101_stream *stream = &ua->capture; + unsigned long flags; + unsigned int frames, write_ptr; + bool do_period_elapsed; + int err; + + if (unlikely(urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN)) /* device disabled */ + goto stream_stopped; + + if (urb->status >= 0 && urb->iso_frame_desc[0].status >= 0) + frames = urb->iso_frame_desc[0].actual_length / + stream->frame_bytes; + else + frames = 0; + + spin_lock_irqsave(&ua->lock, flags); + + if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) + do_period_elapsed = copy_capture_data(stream, urb, frames); + else + do_period_elapsed = false; + + if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + spin_unlock_irqrestore(&ua->lock, flags); + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + goto stream_stopped; + } + + /* append packet size to FIFO */ + write_ptr = ua->rate_feedback_start; + add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count); + ua->rate_feedback[write_ptr] = frames; + if (ua->rate_feedback_count < ua->playback.queue_length) { + ua->rate_feedback_count++; + if (ua->rate_feedback_count == + ua->playback.queue_length) + wake_up(&ua->rate_feedback_wait); + } else { + /* + * Ring buffer overflow; this happens when the playback + * stream is not running. Throw away the oldest entry, + * so that the playback stream, when it starts, sees + * the most recent packet sizes. + */ + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + } + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) && + !list_empty(&ua->ready_playback_urbs)) + tasklet_schedule(&ua->playback_tasklet); + } + + spin_unlock_irqrestore(&ua->lock, flags); + + if (do_period_elapsed) + snd_pcm_period_elapsed(stream->substream); + + return; + +stream_stopped: + abort_usb_playback(ua); + abort_usb_capture(ua); + abort_alsa_playback(ua); + abort_alsa_capture(ua); +} + +static void first_capture_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + + urb->complete = capture_urb_complete; + capture_urb_complete(urb); + + set_bit(CAPTURE_URB_COMPLETED, &ua->states); + wake_up(&ua->alsa_capture_wait); +} + +static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { + int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + return err; + } + } + return 0; +} + +static void kill_stream_urbs(struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) + if (stream->urbs[i]) + usb_kill_urb(&stream->urbs[i]->urb); +} + +static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) +{ + struct usb_host_interface *alts; + + alts = ua->intf[intf_index]->cur_altsetting; + if (alts->desc.bAlternateSetting != 1) { + int err = usb_set_interface(ua->dev, + alts->desc.bInterfaceNumber, 1); + if (err < 0) { + dev_err(&ua->dev->dev, + "cannot initialize interface; error %d: %s\n", + err, usb_error_string(err)); + return err; + } + } + return 0; +} + +static void disable_iso_interface(struct ua101 *ua, unsigned int intf_index) +{ + struct usb_host_interface *alts; + + if (!ua->intf[intf_index]) + return; + + alts = ua->intf[intf_index]->cur_altsetting; + if (alts->desc.bAlternateSetting != 0) { + int err = usb_set_interface(ua->dev, + alts->desc.bInterfaceNumber, 0); + if (err < 0 && !test_bit(DISCONNECTED, &ua->states)) + dev_warn(&ua->dev->dev, + "interface reset failed; error %d: %s\n", + err, usb_error_string(err)); + } +} + +static void stop_usb_capture(struct ua101 *ua) +{ + clear_bit(USB_CAPTURE_RUNNING, &ua->states); + + kill_stream_urbs(&ua->capture); + + disable_iso_interface(ua, INTF_CAPTURE); +} + +static int start_usb_capture(struct ua101 *ua) +{ + int err; + + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + + if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return 0; + + kill_stream_urbs(&ua->capture); + + err = enable_iso_interface(ua, INTF_CAPTURE); + if (err < 0) + return err; + + clear_bit(CAPTURE_URB_COMPLETED, &ua->states); + ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; + ua->rate_feedback_start = 0; + ua->rate_feedback_count = 0; + + set_bit(USB_CAPTURE_RUNNING, &ua->states); + err = submit_stream_urbs(ua, &ua->capture); + if (err < 0) + stop_usb_capture(ua); + return err; +} + +static void stop_usb_playback(struct ua101 *ua) +{ + clear_bit(USB_PLAYBACK_RUNNING, &ua->states); + + kill_stream_urbs(&ua->playback); + + tasklet_kill(&ua->playback_tasklet); + + disable_iso_interface(ua, INTF_PLAYBACK); +} + +static int start_usb_playback(struct ua101 *ua) +{ + unsigned int i, frames; + struct urb *urb; + int err = 0; + + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return 0; + + kill_stream_urbs(&ua->playback); + tasklet_kill(&ua->playback_tasklet); + + err = enable_iso_interface(ua, INTF_PLAYBACK); + if (err < 0) + return err; + + clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); + ua->playback.urbs[0]->urb.complete = + first_playback_urb_complete; + spin_lock_irq(&ua->lock); + INIT_LIST_HEAD(&ua->ready_playback_urbs); + spin_unlock_irq(&ua->lock); + + /* + * We submit the initial URBs all at once, so we have to wait for the + * packet size FIFO to be full. + */ + wait_event(ua->rate_feedback_wait, + ua->rate_feedback_count >= ua->playback.queue_length || + !test_bit(USB_CAPTURE_RUNNING, &ua->states) || + test_bit(DISCONNECTED, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) { + stop_usb_playback(ua); + return -ENODEV; + } + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) { + stop_usb_playback(ua); + return -EIO; + } + + for (i = 0; i < ua->playback.queue_length; ++i) { + /* all initial URBs contain silence */ + spin_lock_irq(&ua->lock); + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + spin_unlock_irq(&ua->lock); + urb = &ua->playback.urbs[i]->urb; + urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + memset(urb->transfer_buffer, 0, + urb->iso_frame_desc[0].length); + } + + set_bit(USB_PLAYBACK_RUNNING, &ua->states); + err = submit_stream_urbs(ua, &ua->playback); + if (err < 0) + stop_usb_playback(ua); + return err; +} + +static void abort_alsa_capture(struct ua101 *ua) +{ + if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) + snd_pcm_stop_xrun(ua->capture.substream); +} + +static void abort_alsa_playback(struct ua101 *ua) +{ + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + snd_pcm_stop_xrun(ua->playback.substream); +} + +static int set_stream_hw(struct ua101 *ua, struct snd_pcm_substream *substream, + unsigned int channels) +{ + int err; + + substream->runtime->hw.info = + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_FIFO_IN_FRAMES; + substream->runtime->hw.formats = ua->format_bit; + substream->runtime->hw.rates = snd_pcm_rate_to_rate_bit(ua->rate); + substream->runtime->hw.rate_min = ua->rate; + substream->runtime->hw.rate_max = ua->rate; + substream->runtime->hw.channels_min = channels; + substream->runtime->hw.channels_max = channels; + substream->runtime->hw.buffer_bytes_max = 45000 * 1024; + substream->runtime->hw.period_bytes_min = 1; + substream->runtime->hw.period_bytes_max = UINT_MAX; + substream->runtime->hw.periods_min = 2; + substream->runtime->hw.periods_max = UINT_MAX; + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 1500000 / ua->packets_per_second, + UINT_MAX); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); + return err; +} + +static int capture_pcm_open(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + ua->capture.substream = substream; + err = set_stream_hw(ua, substream, ua->capture.channels); + if (err < 0) + return err; + substream->runtime->hw.fifo_size = + DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second); + substream->runtime->delay = substream->runtime->hw.fifo_size; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + set_bit(ALSA_CAPTURE_OPEN, &ua->states); + mutex_unlock(&ua->mutex); + return err; +} + +static int playback_pcm_open(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + ua->playback.substream = substream; + err = set_stream_hw(ua, substream, ua->playback.channels); + if (err < 0) + return err; + substream->runtime->hw.fifo_size = + DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length, + ua->packets_per_second); + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err < 0) + goto error; + err = start_usb_playback(ua); + if (err < 0) { + if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) + stop_usb_capture(ua); + goto error; + } + set_bit(ALSA_PLAYBACK_OPEN, &ua->states); +error: + mutex_unlock(&ua->mutex); + return err; +} + +static int capture_pcm_close(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + + mutex_lock(&ua->mutex); + clear_bit(ALSA_CAPTURE_OPEN, &ua->states); + if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states)) + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + return 0; +} + +static int playback_pcm_close(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + + mutex_lock(&ua->mutex); + stop_usb_playback(ua); + clear_bit(ALSA_PLAYBACK_OPEN, &ua->states); + if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + return 0; +} + +static int capture_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int playback_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + err = start_usb_playback(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int ua101_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + /* + * The EHCI driver schedules the first packet of an iso stream at 10 ms + * in the future, i.e., no data is actually captured for that long. + * Take the wait here so that the stream is known to be actually + * running when the start trigger has been called. + */ + wait_event(ua->alsa_capture_wait, + test_bit(CAPTURE_URB_COMPLETED, &ua->states) || + !test_bit(USB_CAPTURE_RUNNING, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return -EIO; + + ua->capture.period_pos = 0; + ua->capture.buffer_pos = 0; + return 0; +} + +static int playback_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + err = start_usb_playback(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + /* see the comment in capture_pcm_prepare() */ + wait_event(ua->alsa_playback_wait, + test_bit(PLAYBACK_URB_COMPLETED, &ua->states) || + !test_bit(USB_PLAYBACK_RUNNING, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return -EIO; + + substream->runtime->delay = 0; + ua->playback.period_pos = 0; + ua->playback.buffer_pos = 0; + return 0; +} + +static int capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua101 *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return -EIO; + set_bit(ALSA_CAPTURE_RUNNING, &ua->states); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + clear_bit(ALSA_CAPTURE_RUNNING, &ua->states); + return 0; + default: + return -EINVAL; + } +} + +static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua101 *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return -EIO; + set_bit(ALSA_PLAYBACK_RUNNING, &ua->states); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + clear_bit(ALSA_PLAYBACK_RUNNING, &ua->states); + return 0; + default: + return -EINVAL; + } +} + +static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua, + struct ua101_stream *stream) +{ + unsigned long flags; + unsigned int pos; + + spin_lock_irqsave(&ua->lock, flags); + pos = stream->buffer_pos; + spin_unlock_irqrestore(&ua->lock, flags); + return pos; +} + +static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct ua101 *ua = subs->private_data; + + return ua101_pcm_pointer(ua, &ua->capture); +} + +static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct ua101 *ua = subs->private_data; + + return ua101_pcm_pointer(ua, &ua->playback); +} + +static const struct snd_pcm_ops capture_pcm_ops = { + .open = capture_pcm_open, + .close = capture_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_pcm_hw_params, + .hw_free = ua101_pcm_hw_free, + .prepare = capture_pcm_prepare, + .trigger = capture_pcm_trigger, + .pointer = capture_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct snd_pcm_ops playback_pcm_ops = { + .open = playback_pcm_open, + .close = playback_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_pcm_hw_params, + .hw_free = ua101_pcm_hw_free, + .prepare = playback_pcm_prepare, + .trigger = playback_pcm_trigger, + .pointer = playback_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct uac_format_type_i_discrete_descriptor * +find_format_descriptor(struct usb_interface *interface) +{ + struct usb_host_interface *alt; + u8 *extra; + int extralen; + + if (interface->num_altsetting != 2) { + dev_err(&interface->dev, "invalid num_altsetting\n"); + return NULL; + } + + alt = &interface->altsetting[0]; + if (alt->desc.bNumEndpoints != 0) { + dev_err(&interface->dev, "invalid bNumEndpoints\n"); + return NULL; + } + + alt = &interface->altsetting[1]; + if (alt->desc.bNumEndpoints != 1) { + dev_err(&interface->dev, "invalid bNumEndpoints\n"); + return NULL; + } + + extra = alt->extra; + extralen = alt->extralen; + while (extralen >= sizeof(struct usb_descriptor_header)) { + struct uac_format_type_i_discrete_descriptor *desc; + + desc = (struct uac_format_type_i_discrete_descriptor *)extra; + if (desc->bLength > extralen) { + dev_err(&interface->dev, "descriptor overflow\n"); + return NULL; + } + if (desc->bLength == UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1) && + desc->bDescriptorType == USB_DT_CS_INTERFACE && + desc->bDescriptorSubtype == UAC_FORMAT_TYPE) { + if (desc->bFormatType != UAC_FORMAT_TYPE_I_PCM || + desc->bSamFreqType != 1) { + dev_err(&interface->dev, + "invalid format type\n"); + return NULL; + } + return desc; + } + extralen -= desc->bLength; + extra += desc->bLength; + } + dev_err(&interface->dev, "sample format descriptor not found\n"); + return NULL; +} + +static int detect_usb_format(struct ua101 *ua) +{ + const struct uac_format_type_i_discrete_descriptor *fmt_capture; + const struct uac_format_type_i_discrete_descriptor *fmt_playback; + const struct usb_endpoint_descriptor *epd; + unsigned int rate2; + + fmt_capture = find_format_descriptor(ua->intf[INTF_CAPTURE]); + fmt_playback = find_format_descriptor(ua->intf[INTF_PLAYBACK]); + if (!fmt_capture || !fmt_playback) + return -ENXIO; + + switch (fmt_capture->bSubframeSize) { + case 3: + ua->format_bit = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + ua->format_bit = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + dev_err(&ua->dev->dev, "sample width is not 24 or 32 bits\n"); + return -ENXIO; + } + if (fmt_capture->bSubframeSize != fmt_playback->bSubframeSize) { + dev_err(&ua->dev->dev, + "playback/capture sample widths do not match\n"); + return -ENXIO; + } + + if (fmt_capture->bBitResolution != 24 || + fmt_playback->bBitResolution != 24) { + dev_err(&ua->dev->dev, "sample width is not 24 bits\n"); + return -ENXIO; + } + + ua->rate = combine_triple(fmt_capture->tSamFreq[0]); + rate2 = combine_triple(fmt_playback->tSamFreq[0]); + if (ua->rate != rate2) { + dev_err(&ua->dev->dev, + "playback/capture rates do not match: %u/%u\n", + rate2, ua->rate); + return -ENXIO; + } + + switch (ua->dev->speed) { + case USB_SPEED_FULL: + ua->packets_per_second = 1000; + break; + case USB_SPEED_HIGH: + ua->packets_per_second = 8000; + break; + default: + dev_err(&ua->dev->dev, "unknown device speed\n"); + return -ENXIO; + } + + ua->capture.channels = fmt_capture->bNrChannels; + ua->playback.channels = fmt_playback->bNrChannels; + ua->capture.frame_bytes = + fmt_capture->bSubframeSize * ua->capture.channels; + ua->playback.frame_bytes = + fmt_playback->bSubframeSize * ua->playback.channels; + + epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc; + if (!usb_endpoint_is_isoc_in(epd) || usb_endpoint_maxp(epd) == 0) { + dev_err(&ua->dev->dev, "invalid capture endpoint\n"); + return -ENXIO; + } + ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->capture.max_packet_bytes = usb_endpoint_maxp(epd); + + epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc; + if (!usb_endpoint_is_isoc_out(epd) || usb_endpoint_maxp(epd) == 0) { + dev_err(&ua->dev->dev, "invalid playback endpoint\n"); + return -ENXIO; + } + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->playback.max_packet_bytes = usb_endpoint_maxp(epd); + return 0; +} + +static int alloc_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int remaining_packets, packets, packets_per_page, i; + size_t size; + + stream->queue_length = queue_length; + stream->queue_length = max(stream->queue_length, + (unsigned int)MIN_QUEUE_LENGTH); + stream->queue_length = min(stream->queue_length, + (unsigned int)MAX_QUEUE_LENGTH); + + /* + * The cache pool sizes used by usb_alloc_coherent() (128, 512, 2048) are + * quite bad when used with the packet sizes of this device (e.g. 280, + * 520, 624). Therefore, we allocate and subdivide entire pages, using + * a smaller buffer only for the last chunk. + */ + remaining_packets = stream->queue_length; + packets_per_page = PAGE_SIZE / stream->max_packet_bytes; + for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) { + packets = min(remaining_packets, packets_per_page); + size = packets * stream->max_packet_bytes; + stream->buffers[i].addr = + usb_alloc_coherent(ua->dev, size, GFP_KERNEL, + &stream->buffers[i].dma); + if (!stream->buffers[i].addr) + return -ENOMEM; + stream->buffers[i].size = size; + remaining_packets -= packets; + if (!remaining_packets) + break; + } + if (remaining_packets) { + dev_err(&ua->dev->dev, "too many packets\n"); + return -ENXIO; + } + return 0; +} + +static void free_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) + usb_free_coherent(ua->dev, + stream->buffers[i].size, + stream->buffers[i].addr, + stream->buffers[i].dma); +} + +static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + void (*urb_complete)(struct urb *)) +{ + unsigned max_packet_size = stream->max_packet_bytes; + struct ua101_urb *urb; + unsigned int b, u = 0; + + for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { + unsigned int size = stream->buffers[b].size; + u8 *addr = stream->buffers[b].addr; + dma_addr_t dma = stream->buffers[b].dma; + + while (size >= max_packet_size) { + if (u >= stream->queue_length) + goto bufsize_error; + urb = kmalloc(sizeof(*urb), GFP_KERNEL); + if (!urb) + return -ENOMEM; + usb_init_urb(&urb->urb); + urb->urb.dev = ua->dev; + urb->urb.pipe = stream->usb_pipe; + urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP; + urb->urb.transfer_buffer = addr; + urb->urb.transfer_dma = dma; + urb->urb.transfer_buffer_length = max_packet_size; + urb->urb.number_of_packets = 1; + urb->urb.interval = 1; + urb->urb.context = ua; + urb->urb.complete = urb_complete; + urb->urb.iso_frame_desc[0].offset = 0; + urb->urb.iso_frame_desc[0].length = max_packet_size; + stream->urbs[u++] = urb; + size -= max_packet_size; + addr += max_packet_size; + dma += max_packet_size; + } + } + if (u == stream->queue_length) + return 0; +bufsize_error: + dev_err(&ua->dev->dev, "internal buffer size error\n"); + return -ENXIO; +} + +static void free_stream_urbs(struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { + kfree(stream->urbs[i]); + stream->urbs[i] = NULL; + } +} + +static void free_usb_related_resources(struct ua101 *ua, + struct usb_interface *interface) +{ + unsigned int i; + struct usb_interface *intf; + + mutex_lock(&ua->mutex); + free_stream_urbs(&ua->capture); + free_stream_urbs(&ua->playback); + mutex_unlock(&ua->mutex); + free_stream_buffers(ua, &ua->capture); + free_stream_buffers(ua, &ua->playback); + + for (i = 0; i < ARRAY_SIZE(ua->intf); ++i) { + mutex_lock(&ua->mutex); + intf = ua->intf[i]; + ua->intf[i] = NULL; + mutex_unlock(&ua->mutex); + if (intf) { + usb_set_intfdata(intf, NULL); + if (intf != interface) + usb_driver_release_interface(&ua101_driver, + intf); + } + } +} + +static void ua101_card_free(struct snd_card *card) +{ + struct ua101 *ua = card->private_data; + + mutex_destroy(&ua->mutex); +} + +static int ua101_probe(struct usb_interface *interface, + const struct usb_device_id *usb_id) +{ + static const struct snd_usb_midi_endpoint_info midi_ep = { + .out_cables = 0x0001, + .in_cables = 0x0001 + }; + static const struct snd_usb_audio_quirk midi_quirk = { + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &midi_ep + }; + static const int intf_numbers[2][3] = { + { /* UA-101 */ + [INTF_PLAYBACK] = 0, + [INTF_CAPTURE] = 1, + [INTF_MIDI] = 2, + }, + { /* UA-1000 */ + [INTF_CAPTURE] = 1, + [INTF_PLAYBACK] = 2, + [INTF_MIDI] = 3, + }, + }; + struct snd_card *card; + struct ua101 *ua; + unsigned int card_index, i; + int is_ua1000; + const char *name; + char usb_path[32]; + int err; + + is_ua1000 = usb_id->idProduct == 0x0044; + + if (interface->altsetting->desc.bInterfaceNumber != + intf_numbers[is_ua1000][0]) + return -ENODEV; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) + if (enable[card_index] && !(devices_used & (1 << card_index))) + break; + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return -ENOENT; + } + err = snd_card_new(&interface->dev, + index[card_index], id[card_index], THIS_MODULE, + sizeof(*ua), &card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return err; + } + card->private_free = ua101_card_free; + ua = card->private_data; + ua->dev = interface_to_usbdev(interface); + ua->card = card; + ua->card_index = card_index; + INIT_LIST_HEAD(&ua->midi_list); + spin_lock_init(&ua->lock); + mutex_init(&ua->mutex); + INIT_LIST_HEAD(&ua->ready_playback_urbs); + tasklet_init(&ua->playback_tasklet, + playback_tasklet, (unsigned long)ua); + init_waitqueue_head(&ua->alsa_capture_wait); + init_waitqueue_head(&ua->rate_feedback_wait); + init_waitqueue_head(&ua->alsa_playback_wait); + + ua->intf[0] = interface; + for (i = 1; i < ARRAY_SIZE(ua->intf); ++i) { + ua->intf[i] = usb_ifnum_to_if(ua->dev, + intf_numbers[is_ua1000][i]); + if (!ua->intf[i]) { + dev_err(&ua->dev->dev, "interface %u not found\n", + intf_numbers[is_ua1000][i]); + err = -ENXIO; + goto probe_error; + } + err = usb_driver_claim_interface(&ua101_driver, + ua->intf[i], ua); + if (err < 0) { + ua->intf[i] = NULL; + err = -EBUSY; + goto probe_error; + } + } + + err = detect_usb_format(ua); + if (err < 0) + goto probe_error; + + name = usb_id->idProduct == 0x0044 ? "UA-1000" : "UA-101"; + strcpy(card->driver, "UA-101"); + strcpy(card->shortname, name); + usb_make_path(ua->dev, usb_path, sizeof(usb_path)); + snprintf(ua->card->longname, sizeof(ua->card->longname), + "EDIROL %s (serial %s), %u Hz at %s, %s speed", name, + ua->dev->serial ? ua->dev->serial : "?", ua->rate, usb_path, + ua->dev->speed == USB_SPEED_HIGH ? "high" : "full"); + + err = alloc_stream_buffers(ua, &ua->capture); + if (err < 0) + goto probe_error; + err = alloc_stream_buffers(ua, &ua->playback); + if (err < 0) + goto probe_error; + + err = alloc_stream_urbs(ua, &ua->capture, capture_urb_complete); + if (err < 0) + goto probe_error; + err = alloc_stream_urbs(ua, &ua->playback, playback_urb_complete); + if (err < 0) + goto probe_error; + + err = snd_pcm_new(card, name, 0, 1, 1, &ua->pcm); + if (err < 0) + goto probe_error; + ua->pcm->private_data = ua; + strcpy(ua->pcm->name, name); + snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops); + snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops); + + err = snd_usbmidi_create(card, ua->intf[INTF_MIDI], + &ua->midi_list, &midi_quirk); + if (err < 0) + goto probe_error; + + err = snd_card_register(card); + if (err < 0) + goto probe_error; + + usb_set_intfdata(interface, ua); + devices_used |= 1 << card_index; + + mutex_unlock(&devices_mutex); + return 0; + +probe_error: + free_usb_related_resources(ua, interface); + snd_card_free(card); + mutex_unlock(&devices_mutex); + return err; +} + +static void ua101_disconnect(struct usb_interface *interface) +{ + struct ua101 *ua = usb_get_intfdata(interface); + struct list_head *midi; + + if (!ua) + return; + + mutex_lock(&devices_mutex); + + set_bit(DISCONNECTED, &ua->states); + wake_up(&ua->rate_feedback_wait); + + /* make sure that userspace cannot create new requests */ + snd_card_disconnect(ua->card); + + /* make sure that there are no pending USB requests */ + list_for_each(midi, &ua->midi_list) + snd_usbmidi_disconnect(midi); + abort_alsa_playback(ua); + abort_alsa_capture(ua); + mutex_lock(&ua->mutex); + stop_usb_playback(ua); + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + + free_usb_related_resources(ua, interface); + + devices_used &= ~(1 << ua->card_index); + + snd_card_free_when_closed(ua->card); + + mutex_unlock(&devices_mutex); +} + +static const struct usb_device_id ua101_ids[] = { + { USB_DEVICE(0x0582, 0x0044) }, /* UA-1000 high speed */ + { USB_DEVICE(0x0582, 0x007d) }, /* UA-101 high speed */ + { USB_DEVICE(0x0582, 0x008d) }, /* UA-101 full speed */ + { } +}; +MODULE_DEVICE_TABLE(usb, ua101_ids); + +static struct usb_driver ua101_driver = { + .name = "snd-ua101", + .id_table = ua101_ids, + .probe = ua101_probe, + .disconnect = ua101_disconnect, +#if 0 + .suspend = ua101_suspend, + .resume = ua101_resume, +#endif +}; + +module_usb_driver(ua101_driver); diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c new file mode 100644 index 000000000..6c8cdce81 --- /dev/null +++ b/sound/usb/mixer.c @@ -0,0 +1,3677 @@ +/* + * (Tentative) USB Audio Driver for ALSA + * + * Mixer control part + * + * Copyright (c) 2002 by Takashi Iwai + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * TODOs, for both the mixer and the streaming interfaces: + * + * - support for UAC2 effect units + * - support for graphical equalizers + * - RANGE and MEM set commands (UAC2) + * - RANGE and MEM interrupt dispatchers (UAC2) + * - audio channel clustering (UAC2) + * - audio sample rate converter units (UAC2) + * - proper handling of clock multipliers (UAC2) + * - dispatch clock change notifications (UAC2) + * - stop PCM streams which use a clock that became invalid + * - stop PCM streams which use a clock selector that has changed + * - parse available sample rates again when clock sources changed + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "mixer_quirks.h" +#include "power.h" + +#define MAX_ID_ELEMS 256 + +struct usb_audio_term { + int id; + int type; + int channels; + unsigned int chconfig; + int name; +}; + +struct usbmix_name_map; + +struct mixer_build { + struct snd_usb_audio *chip; + struct usb_mixer_interface *mixer; + unsigned char *buffer; + unsigned int buflen; + DECLARE_BITMAP(unitbitmap, MAX_ID_ELEMS); + DECLARE_BITMAP(termbitmap, MAX_ID_ELEMS); + struct usb_audio_term oterm; + const struct usbmix_name_map *map; + const struct usbmix_selector_map *selector_map; +}; + +/*E-mu 0202/0404/0204 eXtension Unit(XU) control*/ +enum { + USB_XU_CLOCK_RATE = 0xe301, + USB_XU_CLOCK_SOURCE = 0xe302, + USB_XU_DIGITAL_IO_STATUS = 0xe303, + USB_XU_DEVICE_OPTIONS = 0xe304, + USB_XU_DIRECT_MONITORING = 0xe305, + USB_XU_METERING = 0xe306 +}; +enum { + USB_XU_CLOCK_SOURCE_SELECTOR = 0x02, /* clock source*/ + USB_XU_CLOCK_RATE_SELECTOR = 0x03, /* clock rate */ + USB_XU_DIGITAL_FORMAT_SELECTOR = 0x01, /* the spdif format */ + USB_XU_SOFT_LIMIT_SELECTOR = 0x03 /* soft limiter */ +}; + +/* + * manual mapping of mixer names + * if the mixer topology is too complicated and the parsed names are + * ambiguous, add the entries in usbmixer_maps.c. + */ +#include "mixer_maps.c" + +static const struct usbmix_name_map * +find_map(const struct usbmix_name_map *p, int unitid, int control) +{ + if (!p) + return NULL; + + for (; p->id; p++) { + if (p->id == unitid && + (!control || !p->control || control == p->control)) + return p; + } + return NULL; +} + +/* get the mapped name if the unit matches */ +static int +check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen) +{ + if (!p || !p->name) + return 0; + + buflen--; + return strlcpy(buf, p->name, buflen); +} + +/* ignore the error value if ignore_ctl_error flag is set */ +#define filter_error(cval, err) \ + ((cval)->head.mixer->ignore_ctl_error ? 0 : (err)) + +/* check whether the control should be ignored */ +static inline int +check_ignored_ctl(const struct usbmix_name_map *p) +{ + if (!p || p->name || p->dB) + return 0; + return 1; +} + +/* dB mapping */ +static inline void check_mapped_dB(const struct usbmix_name_map *p, + struct usb_mixer_elem_info *cval) +{ + if (p && p->dB) { + cval->dBmin = p->dB->min; + cval->dBmax = p->dB->max; + cval->initialized = 1; + } +} + +/* get the mapped selector source name */ +static int check_mapped_selector_name(struct mixer_build *state, int unitid, + int index, char *buf, int buflen) +{ + const struct usbmix_selector_map *p; + + if (!state->selector_map) + return 0; + for (p = state->selector_map; p->id; p++) { + if (p->id == unitid && index < p->count) + return strlcpy(buf, p->names[index], buflen); + } + return 0; +} + +/* + * find an audio control unit with the given unit id + */ +static void *find_audio_control_unit(struct mixer_build *state, + unsigned char unit) +{ + /* we just parse the header */ + struct uac_feature_unit_descriptor *hdr = NULL; + + while ((hdr = snd_usb_find_desc(state->buffer, state->buflen, hdr, + USB_DT_CS_INTERFACE)) != NULL) { + if (hdr->bLength >= 4 && + hdr->bDescriptorSubtype >= UAC_INPUT_TERMINAL && + hdr->bDescriptorSubtype <= UAC3_SAMPLE_RATE_CONVERTER && + hdr->bUnitID == unit) + return hdr; + } + + return NULL; +} + +/* + * copy a string with the given id + */ +static int snd_usb_copy_string_desc(struct snd_usb_audio *chip, + int index, char *buf, int maxlen) +{ + int len = usb_string(chip->dev, index, buf, maxlen - 1); + + if (len < 0) + return 0; + + buf[len] = 0; + return len; +} + +/* + * convert from the byte/word on usb descriptor to the zero-based integer + */ +static int convert_signed_value(struct usb_mixer_elem_info *cval, int val) +{ + switch (cval->val_type) { + case USB_MIXER_BOOLEAN: + return !!val; + case USB_MIXER_INV_BOOLEAN: + return !val; + case USB_MIXER_U8: + val &= 0xff; + break; + case USB_MIXER_S8: + val &= 0xff; + if (val >= 0x80) + val -= 0x100; + break; + case USB_MIXER_U16: + val &= 0xffff; + break; + case USB_MIXER_S16: + val &= 0xffff; + if (val >= 0x8000) + val -= 0x10000; + break; + } + return val; +} + +/* + * convert from the zero-based int to the byte/word for usb descriptor + */ +static int convert_bytes_value(struct usb_mixer_elem_info *cval, int val) +{ + switch (cval->val_type) { + case USB_MIXER_BOOLEAN: + return !!val; + case USB_MIXER_INV_BOOLEAN: + return !val; + case USB_MIXER_S8: + case USB_MIXER_U8: + return val & 0xff; + case USB_MIXER_S16: + case USB_MIXER_U16: + return val & 0xffff; + } + return 0; /* not reached */ +} + +static int get_relative_value(struct usb_mixer_elem_info *cval, int val) +{ + if (!cval->res) + cval->res = 1; + if (val < cval->min) + return 0; + else if (val >= cval->max) + return (cval->max - cval->min + cval->res - 1) / cval->res; + else + return (val - cval->min) / cval->res; +} + +static int get_abs_value(struct usb_mixer_elem_info *cval, int val) +{ + if (val < 0) + return cval->min; + if (!cval->res) + cval->res = 1; + val *= cval->res; + val += cval->min; + if (val > cval->max) + return cval->max; + return val; +} + +static int uac2_ctl_value_size(int val_type) +{ + switch (val_type) { + case USB_MIXER_S32: + case USB_MIXER_U32: + return 4; + case USB_MIXER_S16: + case USB_MIXER_U16: + return 2; + default: + return 1; + } + return 0; /* unreachable */ +} + + +/* + * retrieve a mixer value + */ + +static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, + int validx, int *value_ret) +{ + struct snd_usb_audio *chip = cval->head.mixer->chip; + unsigned char buf[2]; + int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; + int timeout = 10; + int idx = 0, err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return -EIO; + + while (timeout-- > 0) { + idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, buf, val_len); + if (err >= val_len) { + *value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len)); + err = 0; + goto out; + } else if (err == -ETIMEDOUT) { + goto out; + } + } + usb_audio_dbg(chip, + "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", + request, validx, idx, cval->val_type); + err = -EINVAL; + + out: + snd_usb_unlock_shutdown(chip); + return err; +} + +static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, + int validx, int *value_ret) +{ + struct snd_usb_audio *chip = cval->head.mixer->chip; + /* enough space for one range */ + unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; + unsigned char *val; + int idx = 0, ret, val_size, size; + __u8 bRequest; + + val_size = uac2_ctl_value_size(cval->val_type); + + if (request == UAC_GET_CUR) { + bRequest = UAC2_CS_CUR; + size = val_size; + } else { + bRequest = UAC2_CS_RANGE; + size = sizeof(__u16) + 3 * val_size; + } + + memset(buf, 0, sizeof(buf)); + + ret = snd_usb_lock_shutdown(chip) ? -EIO : 0; + if (ret) + goto error; + + idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, buf, size); + snd_usb_unlock_shutdown(chip); + + if (ret < 0) { +error: + usb_audio_err(chip, + "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", + request, validx, idx, cval->val_type); + return ret; + } + + /* FIXME: how should we handle multiple triplets here? */ + + switch (request) { + case UAC_GET_CUR: + val = buf; + break; + case UAC_GET_MIN: + val = buf + sizeof(__u16); + break; + case UAC_GET_MAX: + val = buf + sizeof(__u16) + val_size; + break; + case UAC_GET_RES: + val = buf + sizeof(__u16) + val_size * 2; + break; + default: + return -EINVAL; + } + + *value_ret = convert_signed_value(cval, + snd_usb_combine_bytes(val, val_size)); + + return 0; +} + +static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, + int validx, int *value_ret) +{ + validx += cval->idx_off; + + return (cval->head.mixer->protocol == UAC_VERSION_1) ? + get_ctl_value_v1(cval, request, validx, value_ret) : + get_ctl_value_v2(cval, request, validx, value_ret); +} + +static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, + int validx, int *value) +{ + return get_ctl_value(cval, UAC_GET_CUR, validx, value); +} + +/* channel = 0: master, 1 = first channel */ +static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, + int channel, int *value) +{ + return get_ctl_value(cval, UAC_GET_CUR, + (cval->control << 8) | channel, + value); +} + +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value) +{ + int err; + + if (cval->cached & (1 << channel)) { + *value = cval->cache_val[index]; + return 0; + } + err = get_cur_mix_raw(cval, channel, value); + if (err < 0) { + if (!cval->head.mixer->ignore_ctl_error) + usb_audio_dbg(cval->head.mixer->chip, + "cannot get current value for control %d ch %d: err = %d\n", + cval->control, channel, err); + return err; + } + cval->cached |= 1 << channel; + cval->cache_val[index] = *value; + return 0; +} + +/* + * set a mixer value + */ + +int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, + int request, int validx, int value_set) +{ + struct snd_usb_audio *chip = cval->head.mixer->chip; + unsigned char buf[4]; + int idx = 0, val_len, err, timeout = 10; + + validx += cval->idx_off; + + + if (cval->head.mixer->protocol == UAC_VERSION_1) { + val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1; + } else { /* UAC_VERSION_2/3 */ + val_len = uac2_ctl_value_size(cval->val_type); + + /* FIXME */ + if (request != UAC_SET_CUR) { + usb_audio_dbg(chip, "RANGE setting not yet supported\n"); + return -EINVAL; + } + + request = UAC2_CS_CUR; + } + + value_set = convert_bytes_value(cval, value_set); + buf[0] = value_set & 0xff; + buf[1] = (value_set >> 8) & 0xff; + buf[2] = (value_set >> 16) & 0xff; + buf[3] = (value_set >> 24) & 0xff; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return -EIO; + + while (timeout-- > 0) { + idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + validx, idx, buf, val_len); + if (err >= 0) { + err = 0; + goto out; + } else if (err == -ETIMEDOUT) { + goto out; + } + } + usb_audio_dbg(chip, "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d, data = %#x/%#x\n", + request, validx, idx, cval->val_type, buf[0], buf[1]); + err = -EINVAL; + + out: + snd_usb_unlock_shutdown(chip); + return err; +} + +static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, + int validx, int value) +{ + return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); +} + +int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value) +{ + int err; + unsigned int read_only = (channel == 0) ? + cval->master_readonly : + cval->ch_readonly & (1 << (channel - 1)); + + if (read_only) { + usb_audio_dbg(cval->head.mixer->chip, + "%s(): channel %d of control %d is read_only\n", + __func__, channel, cval->control); + return 0; + } + + err = snd_usb_mixer_set_ctl_value(cval, + UAC_SET_CUR, (cval->control << 8) | channel, + value); + if (err < 0) + return err; + cval->cached |= 1 << channel; + cval->cache_val[index] = value; + return 0; +} + +/* + * TLV callback for mixer volume controls + */ +int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + DECLARE_TLV_DB_MINMAX(scale, 0, 0); + + if (size < sizeof(scale)) + return -ENOMEM; + if (cval->min_mute) + scale[0] = SNDRV_CTL_TLVT_DB_MINMAX_MUTE; + scale[2] = cval->dBmin; + scale[3] = cval->dBmax; + if (copy_to_user(_tlv, scale, sizeof(scale))) + return -EFAULT; + return 0; +} + +/* + * parser routines begin here... + */ + +static int parse_audio_unit(struct mixer_build *state, int unitid); + + +/* + * check if the input/output channel routing is enabled on the given bitmap. + * used for mixer unit parser + */ +static int check_matrix_bitmap(unsigned char *bmap, + int ich, int och, int num_outs) +{ + int idx = ich * num_outs + och; + return bmap[idx >> 3] & (0x80 >> (idx & 7)); +} + +/* + * add an alsa control element + * search and increment the index until an empty slot is found. + * + * if failed, give up and free the control instance. + */ + +int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, + struct snd_kcontrol *kctl, + bool is_std_info) +{ + struct usb_mixer_interface *mixer = list->mixer; + int err; + + while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) + kctl->id.index++; + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) { + usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", + err); + return err; + } + list->kctl = kctl; + list->is_std_info = is_std_info; + list->next_id_elem = mixer->id_elems[list->id]; + mixer->id_elems[list->id] = list; + return 0; +} + +/* + * get a terminal name string + */ + +static struct iterm_name_combo { + int type; + char *name; +} iterm_names[] = { + { 0x0300, "Output" }, + { 0x0301, "Speaker" }, + { 0x0302, "Headphone" }, + { 0x0303, "HMD Audio" }, + { 0x0304, "Desktop Speaker" }, + { 0x0305, "Room Speaker" }, + { 0x0306, "Com Speaker" }, + { 0x0307, "LFE" }, + { 0x0600, "External In" }, + { 0x0601, "Analog In" }, + { 0x0602, "Digital In" }, + { 0x0603, "Line" }, + { 0x0604, "Legacy In" }, + { 0x0605, "IEC958 In" }, + { 0x0606, "1394 DA Stream" }, + { 0x0607, "1394 DV Stream" }, + { 0x0700, "Embedded" }, + { 0x0701, "Noise Source" }, + { 0x0702, "Equalization Noise" }, + { 0x0703, "CD" }, + { 0x0704, "DAT" }, + { 0x0705, "DCC" }, + { 0x0706, "MiniDisk" }, + { 0x0707, "Analog Tape" }, + { 0x0708, "Phonograph" }, + { 0x0709, "VCR Audio" }, + { 0x070a, "Video Disk Audio" }, + { 0x070b, "DVD Audio" }, + { 0x070c, "TV Tuner Audio" }, + { 0x070d, "Satellite Rec Audio" }, + { 0x070e, "Cable Tuner Audio" }, + { 0x070f, "DSS Audio" }, + { 0x0710, "Radio Receiver" }, + { 0x0711, "Radio Transmitter" }, + { 0x0712, "Multi-Track Recorder" }, + { 0x0713, "Synthesizer" }, + { 0 }, +}; + +static int get_term_name(struct snd_usb_audio *chip, struct usb_audio_term *iterm, + unsigned char *name, int maxlen, int term_only) +{ + struct iterm_name_combo *names; + int len; + + if (iterm->name) { + len = snd_usb_copy_string_desc(chip, iterm->name, + name, maxlen); + if (len) + return len; + } + + /* virtual type - not a real terminal */ + if (iterm->type >> 16) { + if (term_only) + return 0; + switch (iterm->type >> 16) { + case UAC3_SELECTOR_UNIT: + strcpy(name, "Selector"); + return 8; + case UAC3_PROCESSING_UNIT: + strcpy(name, "Process Unit"); + return 12; + case UAC3_EXTENSION_UNIT: + strcpy(name, "Ext Unit"); + return 8; + case UAC3_MIXER_UNIT: + strcpy(name, "Mixer"); + return 5; + default: + return sprintf(name, "Unit %d", iterm->id); + } + } + + switch (iterm->type & 0xff00) { + case 0x0100: + strcpy(name, "PCM"); + return 3; + case 0x0200: + strcpy(name, "Mic"); + return 3; + case 0x0400: + strcpy(name, "Headset"); + return 7; + case 0x0500: + strcpy(name, "Phone"); + return 5; + } + + for (names = iterm_names; names->type; names++) { + if (names->type == iterm->type) { + strcpy(name, names->name); + return strlen(names->name); + } + } + + return 0; +} + +/* + * Get logical cluster information for UAC3 devices. + */ +static int get_cluster_channels_v3(struct mixer_build *state, unsigned int cluster_id) +{ + struct uac3_cluster_header_descriptor c_header; + int err; + + err = snd_usb_ctl_msg(state->chip->dev, + usb_rcvctrlpipe(state->chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(state->chip), + &c_header, sizeof(c_header)); + if (err < 0) + goto error; + if (err != sizeof(c_header)) { + err = -EIO; + goto error; + } + + return c_header.bNrChannels; + +error: + usb_audio_err(state->chip, "cannot request logical cluster ID: %d (err: %d)\n", cluster_id, err); + return err; +} + +/* + * Get number of channels for a Mixer Unit. + */ +static int uac_mixer_unit_get_channels(struct mixer_build *state, + struct uac_mixer_unit_descriptor *desc) +{ + int mu_channels; + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + if (desc->bLength < sizeof(*desc) + desc->bNrInPins + 1) + return 0; /* no bmControls -> skip */ + mu_channels = uac_mixer_unit_bNrChannels(desc); + break; + case UAC_VERSION_3: + mu_channels = get_cluster_channels_v3(state, + uac3_mixer_unit_wClusterDescrID(desc)); + break; + } + + return mu_channels; +} + +/* + * Parse Input Terminal Unit + */ +static int __check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term); + +static int parse_term_uac1_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_input_terminal_descriptor *d = p1; + + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le16_to_cpu(d->wChannelConfig); + term->name = d->iTerminal; + return 0; +} + +static int parse_term_uac2_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac2_input_terminal_descriptor *d = p1; + int err; + + /* call recursively to verify the referenced clock entity */ + err = __check_input_term(state, d->bCSourceID, term); + if (err < 0) + return err; + + /* save input term properties after recursion, + * to ensure they are not overriden by the recursion calls + */ + term->id = id; + term->type = le16_to_cpu(d->wTerminalType); + term->channels = d->bNrChannels; + term->chconfig = le32_to_cpu(d->bmChannelConfig); + term->name = d->iTerminal; + return 0; +} + +static int parse_term_uac3_iterm_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac3_input_terminal_descriptor *d = p1; + int err; + + /* call recursively to verify the referenced clock entity */ + err = __check_input_term(state, d->bCSourceID, term); + if (err < 0) + return err; + + /* save input term properties after recursion, + * to ensure they are not overriden by the recursion calls + */ + term->id = id; + term->type = le16_to_cpu(d->wTerminalType); + + err = get_cluster_channels_v3(state, le16_to_cpu(d->wClusterDescrID)); + if (err < 0) + return err; + term->channels = err; + + /* REVISIT: UAC3 IT doesn't have channels cfg */ + term->chconfig = 0; + + term->name = le16_to_cpu(d->wTerminalDescrStr); + return 0; +} + +static int parse_term_mixer_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_mixer_unit_descriptor *d = p1; + int protocol = state->mixer->protocol; + int err; + + err = uac_mixer_unit_get_channels(state, d); + if (err <= 0) + return err; + + term->type = UAC3_MIXER_UNIT << 16; /* virtual type */ + term->channels = err; + if (protocol != UAC_VERSION_3) { + term->chconfig = uac_mixer_unit_wChannelConfig(d, protocol); + term->name = uac_mixer_unit_iMixer(d); + } + return 0; +} + +static int parse_term_selector_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_selector_unit_descriptor *d = p1; + int err; + + /* call recursively to retrieve the channel info */ + err = __check_input_term(state, d->baSourceID[0], term); + if (err < 0) + return err; + term->type = UAC3_SELECTOR_UNIT << 16; /* virtual type */ + term->id = id; + if (state->mixer->protocol != UAC_VERSION_3) + term->name = uac_selector_unit_iSelector(d); + return 0; +} + +static int parse_term_proc_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id, int vtype) +{ + struct uac_processing_unit_descriptor *d = p1; + int protocol = state->mixer->protocol; + int err; + + if (d->bNrInPins) { + /* call recursively to retrieve the channel info */ + err = __check_input_term(state, d->baSourceID[0], term); + if (err < 0) + return err; + } + + term->type = vtype << 16; /* virtual type */ + term->id = id; + + if (protocol == UAC_VERSION_3) + return 0; + + if (!term->channels) { + term->channels = uac_processing_unit_bNrChannels(d); + term->chconfig = uac_processing_unit_wChannelConfig(d, protocol); + } + term->name = uac_processing_unit_iProcessing(d, protocol); + return 0; +} + +static int parse_term_effect_unit(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + term->type = UAC3_EFFECT_UNIT << 16; /* virtual type */ + term->id = id; + return 0; +} + +static int parse_term_uac2_clock_source(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac_clock_source_descriptor *d = p1; + + term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ + term->id = id; + term->name = d->iClockSource; + return 0; +} + +static int parse_term_uac3_clock_source(struct mixer_build *state, + struct usb_audio_term *term, + void *p1, int id) +{ + struct uac3_clock_source_descriptor *d = p1; + + term->type = UAC3_CLOCK_SOURCE << 16; /* virtual type */ + term->id = id; + term->name = le16_to_cpu(d->wClockSourceStr); + return 0; +} + +#define PTYPE(a, b) ((a) << 8 | (b)) + +/* + * parse the source unit recursively until it reaches to a terminal + * or a branched unit. + */ +static int __check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term) +{ + int protocol = state->mixer->protocol; + void *p1; + unsigned char *hdr; + + for (;;) { + /* a loop in the terminal chain? */ + if (test_and_set_bit(id, state->termbitmap)) + return -EINVAL; + + p1 = find_audio_control_unit(state, id); + if (!p1) + break; + if (!snd_usb_validate_audio_desc(p1, protocol)) + break; /* bad descriptor */ + + hdr = p1; + term->id = id; + + switch (PTYPE(protocol, hdr[2])) { + case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): { + /* the header is the same for all versions */ + struct uac_feature_unit_descriptor *d = p1; + + id = d->bSourceID; + break; /* continue to parse */ + } + case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): + return parse_term_uac1_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): + return parse_term_uac2_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): + return parse_term_uac3_iterm_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): + return parse_term_mixer_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): + case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): + return parse_term_selector_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): + return parse_term_proc_unit(state, term, p1, id, + UAC3_PROCESSING_UNIT); + case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): + return parse_term_effect_unit(state, term, p1, id); + case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): + return parse_term_proc_unit(state, term, p1, id, + UAC3_EXTENSION_UNIT); + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): + return parse_term_uac2_clock_source(state, term, p1, id); + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): + return parse_term_uac3_clock_source(state, term, p1, id); + default: + return -ENODEV; + } + } + return -ENODEV; +} + + +static int check_input_term(struct mixer_build *state, int id, + struct usb_audio_term *term) +{ + memset(term, 0, sizeof(*term)); + memset(state->termbitmap, 0, sizeof(state->termbitmap)); + return __check_input_term(state, id, term); +} + +/* + * Feature Unit + */ + +/* feature unit control information */ +struct usb_feature_control_info { + int control; + const char *name; + int type; /* data type for uac1 */ + int type_uac2; /* data type for uac2 if different from uac1, else -1 */ +}; + +static const struct usb_feature_control_info audio_feature_info[] = { + { UAC_FU_MUTE, "Mute", USB_MIXER_INV_BOOLEAN, -1 }, + { UAC_FU_VOLUME, "Volume", USB_MIXER_S16, -1 }, + { UAC_FU_BASS, "Tone Control - Bass", USB_MIXER_S8, -1 }, + { UAC_FU_MID, "Tone Control - Mid", USB_MIXER_S8, -1 }, + { UAC_FU_TREBLE, "Tone Control - Treble", USB_MIXER_S8, -1 }, + { UAC_FU_GRAPHIC_EQUALIZER, "Graphic Equalizer", USB_MIXER_S8, -1 }, /* FIXME: not implemented yet */ + { UAC_FU_AUTOMATIC_GAIN, "Auto Gain Control", USB_MIXER_BOOLEAN, -1 }, + { UAC_FU_DELAY, "Delay Control", USB_MIXER_U16, USB_MIXER_U32 }, + { UAC_FU_BASS_BOOST, "Bass Boost", USB_MIXER_BOOLEAN, -1 }, + { UAC_FU_LOUDNESS, "Loudness", USB_MIXER_BOOLEAN, -1 }, + /* UAC2 specific */ + { UAC2_FU_INPUT_GAIN, "Input Gain Control", USB_MIXER_S16, -1 }, + { UAC2_FU_INPUT_GAIN_PAD, "Input Gain Pad Control", USB_MIXER_S16, -1 }, + { UAC2_FU_PHASE_INVERTER, "Phase Inverter Control", USB_MIXER_BOOLEAN, -1 }, +}; + +static void usb_mixer_elem_info_free(struct usb_mixer_elem_info *cval) +{ + kfree(cval); +} + +/* private_free callback */ +void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) +{ + usb_mixer_elem_info_free(kctl->private_data); + kctl->private_data = NULL; +} + +/* + * interface to ALSA control for feature/mixer units + */ + +/* volume control quirks */ +static void volume_control_quirks(struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + struct snd_usb_audio *chip = cval->head.mixer->chip; + switch (chip->usb_id) { + case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ + case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */ + if (strcmp(kctl->id.name, "Effect Duration") == 0) { + cval->min = 0x0000; + cval->max = 0xffff; + cval->res = 0x00e6; + break; + } + if (strcmp(kctl->id.name, "Effect Volume") == 0 || + strcmp(kctl->id.name, "Effect Feedback Volume") == 0) { + cval->min = 0x00; + cval->max = 0xff; + break; + } + if (strstr(kctl->id.name, "Effect Return") != NULL) { + cval->min = 0xb706; + cval->max = 0xff7b; + cval->res = 0x0073; + break; + } + if ((strstr(kctl->id.name, "Playback Volume") != NULL) || + (strstr(kctl->id.name, "Effect Send") != NULL)) { + cval->min = 0xb5fb; /* -73 dB = 0xb6ff */ + cval->max = 0xfcfe; + cval->res = 0x0073; + } + break; + + case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */ + case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */ + if (strcmp(kctl->id.name, "Effect Duration") == 0) { + usb_audio_info(chip, + "set quirk for FTU Effect Duration\n"); + cval->min = 0x0000; + cval->max = 0x7f00; + cval->res = 0x0100; + break; + } + if (strcmp(kctl->id.name, "Effect Volume") == 0 || + strcmp(kctl->id.name, "Effect Feedback Volume") == 0) { + usb_audio_info(chip, + "set quirks for FTU Effect Feedback/Volume\n"); + cval->min = 0x00; + cval->max = 0x7f; + break; + } + break; + + case USB_ID(0x0d8c, 0x0103): + if (!strcmp(kctl->id.name, "PCM Playback Volume")) { + usb_audio_info(chip, + "set volume quirk for CM102-A+/102S+\n"); + cval->min = -256; + } + break; + + case USB_ID(0x0471, 0x0101): + case USB_ID(0x0471, 0x0104): + case USB_ID(0x0471, 0x0105): + case USB_ID(0x0672, 0x1041): + /* quirk for UDA1321/N101. + * note that detection between firmware 2.1.1.7 (N101) + * and later 2.1.1.21 is not very clear from datasheets. + * I hope that the min value is -15360 for newer firmware --jk + */ + if (!strcmp(kctl->id.name, "PCM Playback Volume") && + cval->min == -15616) { + usb_audio_info(chip, + "set volume quirk for UDA1321/N101 chip\n"); + cval->max = -256; + } + break; + + case USB_ID(0x046d, 0x09a4): + if (!strcmp(kctl->id.name, "Mic Capture Volume")) { + usb_audio_info(chip, + "set volume quirk for QuickCam E3500\n"); + cval->min = 6080; + cval->max = 8768; + cval->res = 192; + } + break; + + case USB_ID(0x046d, 0x0807): /* Logitech Webcam C500 */ + case USB_ID(0x046d, 0x0808): + case USB_ID(0x046d, 0x0809): + case USB_ID(0x046d, 0x0819): /* Logitech Webcam C210 */ + case USB_ID(0x046d, 0x081b): /* HD Webcam c310 */ + case USB_ID(0x046d, 0x081d): /* HD Webcam c510 */ + case USB_ID(0x046d, 0x0825): /* HD Webcam c270 */ + case USB_ID(0x046d, 0x0826): /* HD Webcam c525 */ + case USB_ID(0x046d, 0x08ca): /* Logitech Quickcam Fusion */ + case USB_ID(0x046d, 0x0991): + case USB_ID(0x046d, 0x09a2): /* QuickCam Communicate Deluxe/S7500 */ + /* Most audio usb devices lie about volume resolution. + * Most Logitech webcams have res = 384. + * Probably there is some logitech magic behind this number --fishor + */ + if (!strcmp(kctl->id.name, "Mic Capture Volume")) { + usb_audio_info(chip, + "set resolution quirk: cval->res = 384\n"); + cval->res = 384; + } + break; + case USB_ID(0x0495, 0x3042): /* ESS Technology Asus USB DAC */ + if ((strstr(kctl->id.name, "Playback Volume") != NULL) || + strstr(kctl->id.name, "Capture Volume") != NULL) { + cval->min >>= 8; + cval->max = 0; + cval->res = 1; + } + break; + } +} + +/* + * retrieve the minimum and maximum values for the specified control + */ +static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + int default_min, struct snd_kcontrol *kctl) +{ + /* for failsafe */ + cval->min = default_min; + cval->max = cval->min + 1; + cval->res = 1; + cval->dBmin = cval->dBmax = 0; + + if (cval->val_type == USB_MIXER_BOOLEAN || + cval->val_type == USB_MIXER_INV_BOOLEAN) { + cval->initialized = 1; + } else { + int minchn = 0; + if (cval->cmask) { + int i; + for (i = 0; i < MAX_CHANNELS; i++) + if (cval->cmask & (1 << i)) { + minchn = i + 1; + break; + } + } + if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || + get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get min/max values for control %d (id %d)\n", + cval->head.id, snd_usb_ctrl_intf(cval->head.mixer->chip), + cval->control, cval->head.id); + return -EINVAL; + } + if (get_ctl_value(cval, UAC_GET_RES, + (cval->control << 8) | minchn, + &cval->res) < 0) { + cval->res = 1; + } else { + int last_valid_res = cval->res; + + while (cval->res > 1) { + if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, + (cval->control << 8) | minchn, + cval->res / 2) < 0) + break; + cval->res /= 2; + } + if (get_ctl_value(cval, UAC_GET_RES, + (cval->control << 8) | minchn, &cval->res) < 0) + cval->res = last_valid_res; + } + if (cval->res == 0) + cval->res = 1; + + /* Additional checks for the proper resolution + * + * Some devices report smaller resolutions than actually + * reacting. They don't return errors but simply clip + * to the lower aligned value. + */ + if (cval->min + cval->res < cval->max) { + int last_valid_res = cval->res; + int saved, test, check; + if (get_cur_mix_raw(cval, minchn, &saved) < 0) + goto no_res_check; + for (;;) { + test = saved; + if (test < cval->max) + test += cval->res; + else + test -= cval->res; + if (test < cval->min || test > cval->max || + snd_usb_set_cur_mix_value(cval, minchn, 0, test) || + get_cur_mix_raw(cval, minchn, &check)) { + cval->res = last_valid_res; + break; + } + if (test == check) + break; + cval->res *= 2; + } + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); + } + +no_res_check: + cval->initialized = 1; + } + + if (kctl) + volume_control_quirks(cval, kctl); + + /* USB descriptions contain the dB scale in 1/256 dB unit + * while ALSA TLV contains in 1/100 dB unit + */ + cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256; + cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256; + if (cval->dBmin > cval->dBmax) { + /* something is wrong; assume it's either from/to 0dB */ + if (cval->dBmin < 0) + cval->dBmax = 0; + else if (cval->dBmin > 0) + cval->dBmin = 0; + if (cval->dBmin > cval->dBmax) { + /* totally crap, return an error */ + return -EINVAL; + } + } + + return 0; +} + +#define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) + +/* get a feature/mixer unit info */ +static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + + if (cval->val_type == USB_MIXER_BOOLEAN || + cval->val_type == USB_MIXER_INV_BOOLEAN) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = cval->channels; + if (cval->val_type == USB_MIXER_BOOLEAN || + cval->val_type == USB_MIXER_INV_BOOLEAN) { + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + } else { + if (!cval->initialized) { + get_min_max_with_quirks(cval, 0, kcontrol); + if (cval->initialized && cval->dBmin >= cval->dBmax) { + kcontrol->vd[0].access &= + ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + snd_ctl_notify(cval->head.mixer->chip->card, + SNDRV_CTL_EVENT_MASK_INFO, + &kcontrol->id); + } + } + uinfo->value.integer.min = 0; + uinfo->value.integer.max = + (cval->max - cval->min + cval->res - 1) / cval->res; + } + return 0; +} + +/* get the current value from feature/mixer unit */ +static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int c, cnt, val, err; + + ucontrol->value.integer.value[0] = cval->min; + if (cval->cmask) { + cnt = 0; + for (c = 0; c < MAX_CHANNELS; c++) { + if (!(cval->cmask & (1 << c))) + continue; + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); + if (err < 0) + return filter_error(cval, err); + val = get_relative_value(cval, val); + ucontrol->value.integer.value[cnt] = val; + cnt++; + } + return 0; + } else { + /* master channel */ + err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); + if (err < 0) + return filter_error(cval, err); + val = get_relative_value(cval, val); + ucontrol->value.integer.value[0] = val; + } + return 0; +} + +/* put the current value to feature/mixer unit */ +static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int c, cnt, val, oval, err; + int changed = 0; + + if (cval->cmask) { + cnt = 0; + for (c = 0; c < MAX_CHANNELS; c++) { + if (!(cval->cmask & (1 << c))) + continue; + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); + if (err < 0) + return filter_error(cval, err); + val = ucontrol->value.integer.value[cnt]; + val = get_abs_value(cval, val); + if (oval != val) { + snd_usb_set_cur_mix_value(cval, c + 1, cnt, val); + changed = 1; + } + cnt++; + } + } else { + /* master channel */ + err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval); + if (err < 0) + return filter_error(cval, err); + val = ucontrol->value.integer.value[0]; + val = get_abs_value(cval, val); + if (val != oval) { + snd_usb_set_cur_mix_value(cval, 0, 0, val); + changed = 1; + } + } + return changed; +} + +/* get the boolean value from the master channel of a UAC control */ +static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int val, err; + + err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); + if (err < 0) + return filter_error(cval, err); + val = (val != 0); + ucontrol->value.integer.value[0] = val; + return 0; +} + +/* get the connectors status and report it as boolean type */ +static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct snd_usb_audio *chip = cval->head.mixer->chip; + int idx = 0, validx, ret, val; + + validx = cval->control << 8 | 0; + + ret = snd_usb_lock_shutdown(chip) ? -EIO : 0; + if (ret) + goto error; + + idx = snd_usb_ctrl_intf(chip) | (cval->head.id << 8); + if (cval->head.mixer->protocol == UAC_VERSION_2) { + struct uac2_connectors_ctl_blk uac2_conn; + + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, &uac2_conn, sizeof(uac2_conn)); + val = !!uac2_conn.bNrChannels; + } else { /* UAC_VERSION_3 */ + struct uac3_insertion_ctl_blk uac3_conn; + + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, &uac3_conn, sizeof(uac3_conn)); + val = !!uac3_conn.bmConInserted; + } + + snd_usb_unlock_shutdown(chip); + + if (ret < 0) { +error: + usb_audio_err(chip, + "cannot get connectors status: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", + UAC_GET_CUR, validx, idx, cval->val_type); + return filter_error(cval, ret); + } + + ucontrol->value.integer.value[0] = val; + return 0; +} + +static struct snd_kcontrol_new usb_feature_unit_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later manually */ + .info = mixer_ctl_feature_info, + .get = mixer_ctl_feature_get, + .put = mixer_ctl_feature_put, +}; + +/* the read-only variant */ +static const struct snd_kcontrol_new usb_feature_unit_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later manually */ + .info = mixer_ctl_feature_info, + .get = mixer_ctl_feature_get, + .put = NULL, +}; + +/* + * A control which shows the boolean value from reading a UAC control on + * the master channel. + */ +static struct snd_kcontrol_new usb_bool_master_control_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = mixer_ctl_master_bool_get, + .put = NULL, +}; + +static const struct snd_kcontrol_new usb_connector_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = mixer_ctl_connector_get, + .put = NULL, +}; + +/* + * This symbol is exported in order to allow the mixer quirks to + * hook up to the standard feature unit control mechanism + */ +struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl; + +/* + * build a feature control + */ +static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) +{ + return strlcat(kctl->id.name, str, sizeof(kctl->id.name)); +} + +/* + * A lot of headsets/headphones have a "Speaker" mixer. Make sure we + * rename it to "Headphone". We determine if something is a headphone + * similar to how udev determines form factor. + */ +static void check_no_speaker_on_headset(struct snd_kcontrol *kctl, + struct snd_card *card) +{ + const char *names_to_check[] = { + "Headset", "headset", "Headphone", "headphone", NULL}; + const char **s; + bool found = false; + + if (strcmp("Speaker", kctl->id.name)) + return; + + for (s = names_to_check; *s; s++) + if (strstr(card->shortname, *s)) { + found = true; + break; + } + + if (!found) + return; + + strlcpy(kctl->id.name, "Headphone", sizeof(kctl->id.name)); +} + +static const struct usb_feature_control_info *get_feature_control_info(int control) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(audio_feature_info); ++i) { + if (audio_feature_info[i].control == control) + return &audio_feature_info[i]; + } + return NULL; +} + +static void __build_feature_ctl(struct usb_mixer_interface *mixer, + const struct usbmix_name_map *imap, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, + struct usb_audio_term *oterm, + int unitid, int nameid, int readonly_mask) +{ + const struct usb_feature_control_info *ctl_info; + unsigned int len = 0; + int mapped_name = 0; + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *cval; + const struct usbmix_name_map *map; + unsigned int range; + + if (control == UAC_FU_GRAPHIC_EQUALIZER) { + /* FIXME: not supported yet */ + return; + } + + map = find_map(imap, unitid, control); + if (check_ignored_ctl(map)) + return; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return; + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); + cval->control = control; + cval->cmask = ctl_mask; + + ctl_info = get_feature_control_info(control); + if (!ctl_info) { + usb_mixer_elem_info_free(cval); + return; + } + if (mixer->protocol == UAC_VERSION_1) + cval->val_type = ctl_info->type; + else /* UAC_VERSION_2 */ + cval->val_type = ctl_info->type_uac2 >= 0 ? + ctl_info->type_uac2 : ctl_info->type; + + if (ctl_mask == 0) { + cval->channels = 1; /* master channel */ + cval->master_readonly = readonly_mask; + } else { + int i, c = 0; + for (i = 0; i < 16; i++) + if (ctl_mask & (1 << i)) + c++; + cval->channels = c; + cval->ch_readonly = readonly_mask; + } + + /* + * If all channels in the mask are marked read-only, make the control + * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't + * issue write commands to read-only channels. + */ + if (cval->channels == readonly_mask) + kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval); + else + kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); + + if (!kctl) { + usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); + usb_mixer_elem_info_free(cval); + return; + } + kctl->private_free = snd_usb_mixer_elem_free; + + len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); + mapped_name = len != 0; + if (!len && nameid) + len = snd_usb_copy_string_desc(mixer->chip, nameid, + kctl->id.name, sizeof(kctl->id.name)); + + switch (control) { + case UAC_FU_MUTE: + case UAC_FU_VOLUME: + /* + * determine the control name. the rule is: + * - if a name id is given in descriptor, use it. + * - if the connected input can be determined, then use the name + * of terminal type. + * - if the connected output can be determined, use it. + * - otherwise, anonymous name. + */ + if (!len) { + if (iterm) + len = get_term_name(mixer->chip, iterm, + kctl->id.name, + sizeof(kctl->id.name), 1); + if (!len && oterm) + len = get_term_name(mixer->chip, oterm, + kctl->id.name, + sizeof(kctl->id.name), 1); + if (!len) + snprintf(kctl->id.name, sizeof(kctl->id.name), + "Feature %d", unitid); + } + + if (!mapped_name) + check_no_speaker_on_headset(kctl, mixer->chip->card); + + /* + * determine the stream direction: + * if the connected output is USB stream, then it's likely a + * capture stream. otherwise it should be playback (hopefully :) + */ + if (!mapped_name && oterm && !(oterm->type >> 16)) { + if ((oterm->type & 0xff00) == 0x0100) + append_ctl_name(kctl, " Capture"); + else + append_ctl_name(kctl, " Playback"); + } + append_ctl_name(kctl, control == UAC_FU_MUTE ? + " Switch" : " Volume"); + break; + default: + if (!len) + strlcpy(kctl->id.name, audio_feature_info[control-1].name, + sizeof(kctl->id.name)); + break; + } + + /* get min/max values */ + get_min_max_with_quirks(cval, 0, kctl); + + /* skip a bogus volume range */ + if (cval->max <= cval->min) { + usb_audio_dbg(mixer->chip, + "[%d] FU [%s] skipped due to invalid volume\n", + cval->head.id, kctl->id.name); + snd_ctl_free_one(kctl); + return; + } + + + if (control == UAC_FU_VOLUME) { + check_mapped_dB(map, cval); + if (cval->dBmin < cval->dBmax || !cval->initialized) { + kctl->tlv.c = snd_usb_mixer_vol_tlv; + kctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } + } + + snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl); + + range = (cval->max - cval->min) / cval->res; + /* + * Are there devices with volume range more than 255? I use a bit more + * to be sure. 384 is a resolution magic number found on Logitech + * devices. It will definitively catch all buggy Logitech devices. + */ + if (range > 384) { + usb_audio_warn(mixer->chip, + "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", + range); + usb_audio_warn(mixer->chip, + "[%d] FU [%s] ch = %d, val = %d/%d/%d", + cval->head.id, kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); + } + + usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + cval->head.id, kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); + snd_usb_mixer_add_control(&cval->head, kctl); +} + +static void build_feature_ctl(struct mixer_build *state, void *raw_desc, + unsigned int ctl_mask, int control, + struct usb_audio_term *iterm, int unitid, + int readonly_mask) +{ + struct uac_feature_unit_descriptor *desc = raw_desc; + int nameid = uac_feature_unit_iFeature(desc); + + __build_feature_ctl(state->mixer, state->map, ctl_mask, control, + iterm, &state->oterm, unitid, nameid, readonly_mask); +} + +static void build_feature_ctl_badd(struct usb_mixer_interface *mixer, + unsigned int ctl_mask, int control, int unitid, + const struct usbmix_name_map *badd_map) +{ + __build_feature_ctl(mixer, badd_map, ctl_mask, control, + NULL, NULL, unitid, 0, 0); +} + +static void get_connector_control_name(struct usb_mixer_interface *mixer, + struct usb_audio_term *term, + bool is_input, char *name, int name_size) +{ + int name_len = get_term_name(mixer->chip, term, name, name_size, 0); + + if (name_len == 0) + strlcpy(name, "Unknown", name_size); + + /* + * sound/core/ctljack.c has a convention of naming jack controls + * by ending in " Jack". Make it slightly more useful by + * indicating Input or Output after the terminal name. + */ + if (is_input) + strlcat(name, " - Input Jack", name_size); + else + strlcat(name, " - Output Jack", name_size); +} + +/* Build a mixer control for a UAC connector control (jack-detect) */ +static void build_connector_control(struct usb_mixer_interface *mixer, + const struct usbmix_name_map *imap, + struct usb_audio_term *term, bool is_input) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *cval; + const struct usbmix_name_map *map; + + map = find_map(imap, term->id, 0); + if (check_ignored_ctl(map)) + return; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return; + snd_usb_mixer_elem_init_std(&cval->head, mixer, term->id); + /* + * UAC2: The first byte from reading the UAC2_TE_CONNECTOR control returns the + * number of channels connected. + * + * UAC3: The first byte specifies size of bitmap for the inserted controls. The + * following byte(s) specifies which connectors are inserted. + * + * This boolean ctl will simply report if any channels are connected + * or not. + */ + if (mixer->protocol == UAC_VERSION_2) + cval->control = UAC2_TE_CONNECTOR; + else /* UAC_VERSION_3 */ + cval->control = UAC3_TE_INSERTION; + + cval->val_type = USB_MIXER_BOOLEAN; + cval->channels = 1; /* report true if any channel is connected */ + cval->min = 0; + cval->max = 1; + kctl = snd_ctl_new1(&usb_connector_ctl_ro, cval); + if (!kctl) { + usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); + usb_mixer_elem_info_free(cval); + return; + } + + if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) + strlcat(kctl->id.name, " Jack", sizeof(kctl->id.name)); + else + get_connector_control_name(mixer, term, is_input, kctl->id.name, + sizeof(kctl->id.name)); + kctl->private_free = snd_usb_mixer_elem_free; + snd_usb_mixer_add_control(&cval->head, kctl); +} + +static int parse_clock_source_unit(struct mixer_build *state, int unitid, + void *_ftr) +{ + struct uac_clock_source_descriptor *hdr = _ftr; + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int ret; + + if (state->mixer->protocol != UAC_VERSION_2) + return -EINVAL; + + /* + * The only property of this unit we are interested in is the + * clock source validity. If that isn't readable, just bail out. + */ + if (!uac_v2v3_control_is_readable(hdr->bmControls, + UAC2_CS_CONTROL_CLOCK_VALID)) + return 0; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + + snd_usb_mixer_elem_init_std(&cval->head, state->mixer, hdr->bClockID); + + cval->min = 0; + cval->max = 1; + cval->channels = 1; + cval->val_type = USB_MIXER_BOOLEAN; + cval->control = UAC2_CS_CONTROL_CLOCK_VALID; + + cval->master_readonly = 1; + /* From UAC2 5.2.5.1.2 "Only the get request is supported." */ + kctl = snd_ctl_new1(&usb_bool_master_control_ctl_ro, cval); + + if (!kctl) { + usb_mixer_elem_info_free(cval); + return -ENOMEM; + } + + kctl->private_free = snd_usb_mixer_elem_free; + ret = snd_usb_copy_string_desc(state->chip, hdr->iClockSource, + name, sizeof(name)); + if (ret > 0) + snprintf(kctl->id.name, sizeof(kctl->id.name), + "%s Validity", name); + else + snprintf(kctl->id.name, sizeof(kctl->id.name), + "Clock Source %d Validity", hdr->bClockID); + + return snd_usb_mixer_add_control(&cval->head, kctl); +} + +/* + * parse a feature unit + * + * most of controls are defined here. + */ +static int parse_audio_feature_unit(struct mixer_build *state, int unitid, + void *_ftr) +{ + int channels, i, j; + struct usb_audio_term iterm; + unsigned int master_bits, first_ch_bits; + int err, csize; + struct uac_feature_unit_descriptor *hdr = _ftr; + __u8 *bmaControls; + + if (state->mixer->protocol == UAC_VERSION_1) { + csize = hdr->bControlSize; + channels = (hdr->bLength - 7) / csize - 1; + bmaControls = hdr->bmaControls; + } else if (state->mixer->protocol == UAC_VERSION_2) { + struct uac2_feature_unit_descriptor *ftr = _ftr; + csize = 4; + channels = (hdr->bLength - 6) / 4 - 1; + bmaControls = ftr->bmaControls; + } else { /* UAC_VERSION_3 */ + struct uac3_feature_unit_descriptor *ftr = _ftr; + + csize = 4; + channels = (ftr->bLength - 7) / 4 - 1; + bmaControls = ftr->bmaControls; + } + + /* parse the source unit */ + err = parse_audio_unit(state, hdr->bSourceID); + if (err < 0) + return err; + + /* determine the input source type and name */ + err = check_input_term(state, hdr->bSourceID, &iterm); + if (err < 0) + return err; + + master_bits = snd_usb_combine_bytes(bmaControls, csize); + /* master configuration quirks */ + switch (state->chip->usb_id) { + case USB_ID(0x08bb, 0x2702): + usb_audio_info(state->chip, + "usbmixer: master volume quirk for PCM2702 chip\n"); + /* disable non-functional volume control */ + master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME); + break; + case USB_ID(0x1130, 0xf211): + usb_audio_info(state->chip, + "usbmixer: volume control quirk for Tenx TP6911 Audio Headset\n"); + /* disable non-functional volume control */ + channels = 0; + break; + + } + if (channels > 0) + first_ch_bits = snd_usb_combine_bytes(bmaControls + csize, csize); + else + first_ch_bits = 0; + + if (state->mixer->protocol == UAC_VERSION_1) { + /* check all control types */ + for (i = 0; i < 10; i++) { + unsigned int ch_bits = 0; + int control = audio_feature_info[i].control; + + for (j = 0; j < channels; j++) { + unsigned int mask; + + mask = snd_usb_combine_bytes(bmaControls + + csize * (j+1), csize); + if (mask & (1 << i)) + ch_bits |= (1 << j); + } + /* audio class v1 controls are never read-only */ + + /* + * The first channel must be set + * (for ease of programming). + */ + if (ch_bits & 1) + build_feature_ctl(state, _ftr, ch_bits, control, + &iterm, unitid, 0); + if (master_bits & (1 << i)) + build_feature_ctl(state, _ftr, 0, control, + &iterm, unitid, 0); + } + } else { /* UAC_VERSION_2/3 */ + for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) { + unsigned int ch_bits = 0; + unsigned int ch_read_only = 0; + int control = audio_feature_info[i].control; + + for (j = 0; j < channels; j++) { + unsigned int mask; + + mask = snd_usb_combine_bytes(bmaControls + + csize * (j+1), csize); + if (uac_v2v3_control_is_readable(mask, control)) { + ch_bits |= (1 << j); + if (!uac_v2v3_control_is_writeable(mask, control)) + ch_read_only |= (1 << j); + } + } + + /* + * NOTE: build_feature_ctl() will mark the control + * read-only if all channels are marked read-only in + * the descriptors. Otherwise, the control will be + * reported as writeable, but the driver will not + * actually issue a write command for read-only + * channels. + */ + + /* + * The first channel must be set + * (for ease of programming). + */ + if (ch_bits & 1) + build_feature_ctl(state, _ftr, ch_bits, control, + &iterm, unitid, ch_read_only); + if (uac_v2v3_control_is_readable(master_bits, control)) + build_feature_ctl(state, _ftr, 0, control, + &iterm, unitid, + !uac_v2v3_control_is_writeable(master_bits, + control)); + } + } + + return 0; +} + +/* + * Mixer Unit + */ + +/* check whether the given in/out overflows bmMixerControls matrix */ +static bool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc, + int protocol, int num_ins, int num_outs) +{ + u8 *hdr = (u8 *)desc; + u8 *c = uac_mixer_unit_bmControls(desc, protocol); + size_t rest; /* remaining bytes after bmMixerControls */ + + switch (protocol) { + case UAC_VERSION_1: + default: + rest = 1; /* iMixer */ + break; + case UAC_VERSION_2: + rest = 2; /* bmControls + iMixer */ + break; + case UAC_VERSION_3: + rest = 6; /* bmControls + wMixerDescrStr */ + break; + } + + /* overflow? */ + return c + (num_ins * num_outs + 7) / 8 + rest > hdr + hdr[0]; +} + +/* + * build a mixer unit control + * + * the callbacks are identical with feature unit. + * input channel number (zero based) is given in control field instead. + */ +static void build_mixer_unit_ctl(struct mixer_build *state, + struct uac_mixer_unit_descriptor *desc, + int in_pin, int in_ch, int num_outs, + int unitid, struct usb_audio_term *iterm) +{ + struct usb_mixer_elem_info *cval; + unsigned int i, len; + struct snd_kcontrol *kctl; + const struct usbmix_name_map *map; + + map = find_map(state->map, unitid, 0); + if (check_ignored_ctl(map)) + return; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return; + + snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); + cval->control = in_ch + 1; /* based on 1 */ + cval->val_type = USB_MIXER_S16; + for (i = 0; i < num_outs; i++) { + __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); + + if (check_matrix_bitmap(c, in_ch, i, num_outs)) { + cval->cmask |= (1 << i); + cval->channels++; + } + } + + /* get min/max values */ + get_min_max(cval, 0); + + kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); + if (!kctl) { + usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + usb_mixer_elem_info_free(cval); + return; + } + kctl->private_free = snd_usb_mixer_elem_free; + + len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); + if (!len) + len = get_term_name(state->chip, iterm, kctl->id.name, + sizeof(kctl->id.name), 0); + if (!len) + len = sprintf(kctl->id.name, "Mixer Source %d", in_ch + 1); + append_ctl_name(kctl, " Volume"); + + usb_audio_dbg(state->chip, "[%d] MU [%s] ch = %d, val = %d/%d\n", + cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max); + snd_usb_mixer_add_control(&cval->head, kctl); +} + +static int parse_audio_input_terminal(struct mixer_build *state, int unitid, + void *raw_desc) +{ + struct usb_audio_term iterm; + unsigned int control, bmctls, term_id; + + if (state->mixer->protocol == UAC_VERSION_2) { + struct uac2_input_terminal_descriptor *d_v2 = raw_desc; + control = UAC2_TE_CONNECTOR; + term_id = d_v2->bTerminalID; + bmctls = le16_to_cpu(d_v2->bmControls); + } else if (state->mixer->protocol == UAC_VERSION_3) { + struct uac3_input_terminal_descriptor *d_v3 = raw_desc; + control = UAC3_TE_INSERTION; + term_id = d_v3->bTerminalID; + bmctls = le32_to_cpu(d_v3->bmControls); + } else { + return 0; /* UAC1. No Insertion control */ + } + + check_input_term(state, term_id, &iterm); + + /* Check for jack detection. */ + if ((iterm.type & 0xff00) != 0x0100 && + uac_v2v3_control_is_readable(bmctls, control)) + build_connector_control(state->mixer, state->map, &iterm, true); + + return 0; +} + +/* + * parse a mixer unit + */ +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + struct uac_mixer_unit_descriptor *desc = raw_desc; + struct usb_audio_term iterm; + int input_pins, num_ins, num_outs; + int pin, ich, err; + + err = uac_mixer_unit_get_channels(state, desc); + if (err < 0) { + usb_audio_err(state->chip, + "invalid MIXER UNIT descriptor %d\n", + unitid); + return err; + } + + num_outs = err; + input_pins = desc->bNrInPins; + + num_ins = 0; + ich = 0; + for (pin = 0; pin < input_pins; pin++) { + err = parse_audio_unit(state, desc->baSourceID[pin]); + if (err < 0) + continue; + /* no bmControls field (e.g. Maya44) -> ignore */ + if (!num_outs) + continue; + err = check_input_term(state, desc->baSourceID[pin], &iterm); + if (err < 0) + return err; + num_ins += iterm.channels; + if (mixer_bitmap_overflow(desc, state->mixer->protocol, + num_ins, num_outs)) + break; + for (; ich < num_ins; ich++) { + int och, ich_has_controls = 0; + + for (och = 0; och < num_outs; och++) { + __u8 *c = uac_mixer_unit_bmControls(desc, + state->mixer->protocol); + + if (check_matrix_bitmap(c, ich, och, num_outs)) { + ich_has_controls = 1; + break; + } + } + if (ich_has_controls) + build_mixer_unit_ctl(state, desc, pin, ich, num_outs, + unitid, &iterm); + } + } + return 0; +} + +/* + * Processing Unit / Extension Unit + */ + +/* get callback for processing/extension unit */ +static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int err, val; + + err = get_cur_ctl_value(cval, cval->control << 8, &val); + if (err < 0) { + ucontrol->value.integer.value[0] = cval->min; + return filter_error(cval, err); + } + val = get_relative_value(cval, val); + ucontrol->value.integer.value[0] = val; + return 0; +} + +/* put callback for processing/extension unit */ +static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int val, oval, err; + + err = get_cur_ctl_value(cval, cval->control << 8, &oval); + if (err < 0) + return filter_error(cval, err); + val = ucontrol->value.integer.value[0]; + val = get_abs_value(cval, val); + if (val != oval) { + set_cur_ctl_value(cval, cval->control << 8, val); + return 1; + } + return 0; +} + +/* alsa control interface for processing/extension unit */ +static const struct snd_kcontrol_new mixer_procunit_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = mixer_ctl_feature_info, + .get = mixer_ctl_procunit_get, + .put = mixer_ctl_procunit_put, +}; + +/* + * predefined data for processing units + */ +struct procunit_value_info { + int control; + const char *suffix; + int val_type; + int min_value; +}; + +struct procunit_info { + int type; + char *name; + const struct procunit_value_info *values; +}; + +static const struct procunit_value_info undefined_proc_info[] = { + { 0x00, "Control Undefined", 0 }, + { 0 } +}; + +static const struct procunit_value_info updown_proc_info[] = { + { UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, + { 0 } +}; +static const struct procunit_value_info prologic_proc_info[] = { + { UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, + { 0 } +}; +static const struct procunit_value_info threed_enh_proc_info[] = { + { UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 }, + { 0 } +}; +static const struct procunit_value_info reverb_proc_info[] = { + { UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 }, + { UAC_REVERB_TIME, "Time", USB_MIXER_U16 }, + { UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 }, + { 0 } +}; +static const struct procunit_value_info chorus_proc_info[] = { + { UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 }, + { UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 }, + { UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 }, + { 0 } +}; +static const struct procunit_value_info dcr_proc_info[] = { + { UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_DCR_RATE, "Ratio", USB_MIXER_U16 }, + { UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 }, + { UAC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 }, + { UAC_DCR_ATTACK_TIME, "Attack Time", USB_MIXER_U16 }, + { UAC_DCR_RELEASE_TIME, "Release Time", USB_MIXER_U16 }, + { 0 } +}; + +static const struct procunit_info procunits[] = { + { UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info }, + { UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info }, + { UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info }, + { UAC_PROCESS_REVERB, "Reverb", reverb_proc_info }, + { UAC_PROCESS_CHORUS, "Chorus", chorus_proc_info }, + { UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info }, + { 0 }, +}; + +static const struct procunit_value_info uac3_updown_proc_info[] = { + { UAC3_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, + { 0 } +}; +static const struct procunit_value_info uac3_stereo_ext_proc_info[] = { + { UAC3_EXT_WIDTH_CONTROL, "Width Control", USB_MIXER_U8 }, + { 0 } +}; + +static const struct procunit_info uac3_procunits[] = { + { UAC3_PROCESS_UP_DOWNMIX, "Up Down", uac3_updown_proc_info }, + { UAC3_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", uac3_stereo_ext_proc_info }, + { UAC3_PROCESS_MULTI_FUNCTION, "Multi-Function", undefined_proc_info }, + { 0 }, +}; + +/* + * predefined data for extension units + */ +static const struct procunit_value_info clock_rate_xu_info[] = { + { USB_XU_CLOCK_RATE_SELECTOR, "Selector", USB_MIXER_U8, 0 }, + { 0 } +}; +static const struct procunit_value_info clock_source_xu_info[] = { + { USB_XU_CLOCK_SOURCE_SELECTOR, "External", USB_MIXER_BOOLEAN }, + { 0 } +}; +static const struct procunit_value_info spdif_format_xu_info[] = { + { USB_XU_DIGITAL_FORMAT_SELECTOR, "SPDIF/AC3", USB_MIXER_BOOLEAN }, + { 0 } +}; +static const struct procunit_value_info soft_limit_xu_info[] = { + { USB_XU_SOFT_LIMIT_SELECTOR, " ", USB_MIXER_BOOLEAN }, + { 0 } +}; +static const struct procunit_info extunits[] = { + { USB_XU_CLOCK_RATE, "Clock rate", clock_rate_xu_info }, + { USB_XU_CLOCK_SOURCE, "DigitalIn CLK source", clock_source_xu_info }, + { USB_XU_DIGITAL_IO_STATUS, "DigitalOut format:", spdif_format_xu_info }, + { USB_XU_DEVICE_OPTIONS, "AnalogueIn Soft Limit", soft_limit_xu_info }, + { 0 } +}; + +/* + * build a processing/extension unit + */ +static int build_audio_procunit(struct mixer_build *state, int unitid, + void *raw_desc, const struct procunit_info *list, + bool extension_unit) +{ + struct uac_processing_unit_descriptor *desc = raw_desc; + int num_ins; + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + int i, err, nameid, type, len; + const struct procunit_info *info; + const struct procunit_value_info *valinfo; + const struct usbmix_name_map *map; + static const struct procunit_value_info default_value_info[] = { + { 0x01, "Switch", USB_MIXER_BOOLEAN }, + { 0 } + }; + static const struct procunit_info default_info = { + 0, NULL, default_value_info + }; + const char *name = extension_unit ? + "Extension Unit" : "Processing Unit"; + + num_ins = desc->bNrInPins; + for (i = 0; i < num_ins; i++) { + err = parse_audio_unit(state, desc->baSourceID[i]); + if (err < 0) + return err; + } + + type = le16_to_cpu(desc->wProcessType); + for (info = list; info && info->type; info++) + if (info->type == type) + break; + if (!info || !info->type) + info = &default_info; + + for (valinfo = info->values; valinfo->control; valinfo++) { + __u8 *controls = uac_processing_unit_bmControls(desc, state->mixer->protocol); + + if (state->mixer->protocol == UAC_VERSION_1) { + if (!(controls[valinfo->control / 8] & + (1 << ((valinfo->control % 8) - 1)))) + continue; + } else { /* UAC_VERSION_2/3 */ + if (!uac_v2v3_control_is_readable(controls[valinfo->control / 8], + valinfo->control)) + continue; + } + + map = find_map(state->map, unitid, valinfo->control); + if (check_ignored_ctl(map)) + continue; + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); + cval->control = valinfo->control; + cval->val_type = valinfo->val_type; + cval->channels = 1; + + if (state->mixer->protocol > UAC_VERSION_1 && + !uac_v2v3_control_is_writeable(controls[valinfo->control / 8], + valinfo->control)) + cval->master_readonly = 1; + + /* get min/max values */ + switch (type) { + case UAC_PROCESS_UP_DOWNMIX: { + bool mode_sel = false; + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + if (cval->control == UAC_UD_MODE_SELECT) + mode_sel = true; + break; + case UAC_VERSION_3: + if (cval->control == UAC3_UD_MODE_SELECT) + mode_sel = true; + break; + } + + if (mode_sel) { + __u8 *control_spec = uac_processing_unit_specific(desc, + state->mixer->protocol); + cval->min = 1; + cval->max = control_spec[0]; + cval->res = 1; + cval->initialized = 1; + break; + } + + get_min_max(cval, valinfo->min_value); + break; + } + case USB_XU_CLOCK_RATE: + /* + * E-Mu USB 0404/0202/TrackerPre/0204 + * samplerate control quirk + */ + cval->min = 0; + cval->max = 5; + cval->res = 1; + cval->initialized = 1; + break; + default: + get_min_max(cval, valinfo->min_value); + break; + } + + kctl = snd_ctl_new1(&mixer_procunit_ctl, cval); + if (!kctl) { + usb_mixer_elem_info_free(cval); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { + /* nothing */ ; + } else if (info->name) { + strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name)); + } else { + if (extension_unit) + nameid = uac_extension_unit_iExtension(desc, state->mixer->protocol); + else + nameid = uac_processing_unit_iProcessing(desc, state->mixer->protocol); + len = 0; + if (nameid) + len = snd_usb_copy_string_desc(state->chip, + nameid, + kctl->id.name, + sizeof(kctl->id.name)); + if (!len) + strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + } + append_ctl_name(kctl, " "); + append_ctl_name(kctl, valinfo->suffix); + + usb_audio_dbg(state->chip, + "[%d] PU [%s] ch = %d, val = %d/%d\n", + cval->head.id, kctl->id.name, cval->channels, + cval->min, cval->max); + + err = snd_usb_mixer_add_control(&cval->head, kctl); + if (err < 0) + return err; + } + return 0; +} + +static int parse_audio_processing_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + switch (state->mixer->protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + return build_audio_procunit(state, unitid, raw_desc, + procunits, false); + case UAC_VERSION_3: + return build_audio_procunit(state, unitid, raw_desc, + uac3_procunits, false); + } +} + +static int parse_audio_extension_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + /* + * Note that we parse extension units with processing unit descriptors. + * That's ok as the layout is the same. + */ + return build_audio_procunit(state, unitid, raw_desc, extunits, true); +} + +/* + * Selector Unit + */ + +/* + * info callback for selector unit + * use an enumerator type for routing + */ +static int mixer_ctl_selector_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + const char **itemlist = (const char **)kcontrol->private_value; + + if (snd_BUG_ON(!itemlist)) + return -EINVAL; + return snd_ctl_enum_info(uinfo, 1, cval->max, itemlist); +} + +/* get callback for selector unit */ +static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int val, err; + + err = get_cur_ctl_value(cval, cval->control << 8, &val); + if (err < 0) { + ucontrol->value.enumerated.item[0] = 0; + return filter_error(cval, err); + } + val = get_relative_value(cval, val); + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +/* put callback for selector unit */ +static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + int val, oval, err; + + err = get_cur_ctl_value(cval, cval->control << 8, &oval); + if (err < 0) + return filter_error(cval, err); + val = ucontrol->value.enumerated.item[0]; + val = get_abs_value(cval, val); + if (val != oval) { + set_cur_ctl_value(cval, cval->control << 8, val); + return 1; + } + return 0; +} + +/* alsa control interface for selector unit */ +static const struct snd_kcontrol_new mixer_selectunit_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = mixer_ctl_selector_info, + .get = mixer_ctl_selector_get, + .put = mixer_ctl_selector_put, +}; + +/* + * private free callback. + * free both private_data and private_value + */ +static void usb_mixer_selector_elem_free(struct snd_kcontrol *kctl) +{ + int i, num_ins = 0; + + if (kctl->private_data) { + struct usb_mixer_elem_info *cval = kctl->private_data; + num_ins = cval->max; + usb_mixer_elem_info_free(cval); + kctl->private_data = NULL; + } + if (kctl->private_value) { + char **itemlist = (char **)kctl->private_value; + for (i = 0; i < num_ins; i++) + kfree(itemlist[i]); + kfree(itemlist); + kctl->private_value = 0; + } +} + +/* + * parse a selector unit + */ +static int parse_audio_selector_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + struct uac_selector_unit_descriptor *desc = raw_desc; + unsigned int i, nameid, len; + int err; + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + const struct usbmix_name_map *map; + char **namelist; + + for (i = 0; i < desc->bNrInPins; i++) { + err = parse_audio_unit(state, desc->baSourceID[i]); + if (err < 0) + return err; + } + + if (desc->bNrInPins == 1) /* only one ? nonsense! */ + return 0; + + map = find_map(state->map, unitid, 0); + if (check_ignored_ctl(map)) + return 0; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); + cval->val_type = USB_MIXER_U8; + cval->channels = 1; + cval->min = 1; + cval->max = desc->bNrInPins; + cval->res = 1; + cval->initialized = 1; + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + default: + cval->control = 0; + break; + case UAC_VERSION_2: + case UAC_VERSION_3: + if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR || + desc->bDescriptorSubtype == UAC3_CLOCK_SELECTOR) + cval->control = UAC2_CX_CLOCK_SELECTOR; + else /* UAC2/3_SELECTOR_UNIT */ + cval->control = UAC2_SU_SELECTOR; + break; + } + + namelist = kcalloc(desc->bNrInPins, sizeof(char *), GFP_KERNEL); + if (!namelist) { + err = -ENOMEM; + goto error_cval; + } +#define MAX_ITEM_NAME_LEN 64 + for (i = 0; i < desc->bNrInPins; i++) { + struct usb_audio_term iterm; + len = 0; + namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL); + if (!namelist[i]) { + err = -ENOMEM; + goto error_name; + } + len = check_mapped_selector_name(state, unitid, i, namelist[i], + MAX_ITEM_NAME_LEN); + if (! len && check_input_term(state, desc->baSourceID[i], &iterm) >= 0) + len = get_term_name(state->chip, &iterm, namelist[i], + MAX_ITEM_NAME_LEN, 0); + if (! len) + sprintf(namelist[i], "Input %u", i); + } + + kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval); + if (! kctl) { + usb_audio_err(state->chip, "cannot malloc kcontrol\n"); + err = -ENOMEM; + goto error_name; + } + kctl->private_value = (unsigned long)namelist; + kctl->private_free = usb_mixer_selector_elem_free; + + /* check the static mapping table at first */ + len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); + if (!len) { + /* no mapping ? */ + switch (state->mixer->protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + /* if iSelector is given, use it */ + nameid = uac_selector_unit_iSelector(desc); + if (nameid) + len = snd_usb_copy_string_desc(state->chip, + nameid, kctl->id.name, + sizeof(kctl->id.name)); + break; + case UAC_VERSION_3: + /* TODO: Class-Specific strings not yet supported */ + break; + } + + /* ... or pick up the terminal name at next */ + if (!len) + len = get_term_name(state->chip, &state->oterm, + kctl->id.name, sizeof(kctl->id.name), 0); + /* ... or use the fixed string "USB" as the last resort */ + if (!len) + strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name)); + + /* and add the proper suffix */ + if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR || + desc->bDescriptorSubtype == UAC3_CLOCK_SELECTOR) + append_ctl_name(kctl, " Clock Source"); + else if ((state->oterm.type & 0xff00) == 0x0100) + append_ctl_name(kctl, " Capture Source"); + else + append_ctl_name(kctl, " Playback Source"); + } + + usb_audio_dbg(state->chip, "[%d] SU [%s] items = %d\n", + cval->head.id, kctl->id.name, desc->bNrInPins); + return snd_usb_mixer_add_control(&cval->head, kctl); + + error_name: + for (i = 0; i < desc->bNrInPins; i++) + kfree(namelist[i]); + kfree(namelist); + error_cval: + usb_mixer_elem_info_free(cval); + return err; +} + +/* + * parse an audio unit recursively + */ + +static int parse_audio_unit(struct mixer_build *state, int unitid) +{ + unsigned char *p1; + int protocol = state->mixer->protocol; + + if (test_and_set_bit(unitid, state->unitbitmap)) + return 0; /* the unit already visited */ + + p1 = find_audio_control_unit(state, unitid); + if (!p1) { + usb_audio_err(state->chip, "unit %d not found!\n", unitid); + return -EINVAL; + } + + if (!snd_usb_validate_audio_desc(p1, protocol)) { + usb_audio_dbg(state->chip, "invalid unit %d\n", unitid); + return 0; /* skip invalid unit */ + } + + switch (PTYPE(protocol, p1[2])) { + case PTYPE(UAC_VERSION_1, UAC_INPUT_TERMINAL): + case PTYPE(UAC_VERSION_2, UAC_INPUT_TERMINAL): + case PTYPE(UAC_VERSION_3, UAC_INPUT_TERMINAL): + return parse_audio_input_terminal(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_2, UAC_MIXER_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_MIXER_UNIT): + return parse_audio_mixer_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SOURCE): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SOURCE): + return parse_clock_source_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_SELECTOR_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_CLOCK_SELECTOR): + case PTYPE(UAC_VERSION_3, UAC3_CLOCK_SELECTOR): + return parse_audio_selector_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_2, UAC_FEATURE_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_FEATURE_UNIT): + return parse_audio_feature_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC1_PROCESSING_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_PROCESSING_UNIT): + return parse_audio_processing_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_1, UAC1_EXTENSION_UNIT): + case PTYPE(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2): + case PTYPE(UAC_VERSION_3, UAC3_EXTENSION_UNIT): + return parse_audio_extension_unit(state, unitid, p1); + case PTYPE(UAC_VERSION_2, UAC2_EFFECT_UNIT): + case PTYPE(UAC_VERSION_3, UAC3_EFFECT_UNIT): + return 0; /* FIXME - effect units not implemented yet */ + default: + usb_audio_err(state->chip, + "unit %u: unexpected type 0x%02x\n", + unitid, p1[2]); + return -EINVAL; + } +} + +static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) +{ + /* kill pending URBs */ + snd_usb_mixer_disconnect(mixer); + + kfree(mixer->id_elems); + if (mixer->urb) { + kfree(mixer->urb->transfer_buffer); + usb_free_urb(mixer->urb); + } + usb_free_urb(mixer->rc_urb); + kfree(mixer->rc_setup_packet); + kfree(mixer); +} + +static int snd_usb_mixer_dev_free(struct snd_device *device) +{ + struct usb_mixer_interface *mixer = device->device_data; + snd_usb_mixer_free(mixer); + return 0; +} + +/* UAC3 predefined channels configuration */ +struct uac3_badd_profile { + int subclass; + const char *name; + int c_chmask; /* capture channels mask */ + int p_chmask; /* playback channels mask */ + int st_chmask; /* side tone mixing channel mask */ +}; + +static const struct uac3_badd_profile uac3_badd_profiles[] = { + { + /* + * BAIF, BAOF or combination of both + * IN: Mono or Stereo cfg, Mono alt possible + * OUT: Mono or Stereo cfg, Mono alt possible + */ + .subclass = UAC3_FUNCTION_SUBCLASS_GENERIC_IO, + .name = "GENERIC IO", + .c_chmask = -1, /* dynamic channels */ + .p_chmask = -1, /* dynamic channels */ + }, + { + /* BAOF; Stereo only cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADPHONE, + .name = "HEADPHONE", + .p_chmask = 3, + }, + { + /* BAOF; Mono or Stereo cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_SPEAKER, + .name = "SPEAKER", + .p_chmask = -1, /* dynamic channels */ + }, + { + /* BAIF; Mono or Stereo cfg, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_MICROPHONE, + .name = "MICROPHONE", + .c_chmask = -1, /* dynamic channels */ + }, + { + /* + * BAIOF topology + * IN: Mono only + * OUT: Mono or Stereo cfg, Mono alt possible + */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADSET, + .name = "HEADSET", + .c_chmask = 1, + .p_chmask = -1, /* dynamic channels */ + .st_chmask = 1, + }, + { + /* BAIOF; IN: Mono only; OUT: Stereo only, Mono alt possible */ + .subclass = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER, + .name = "HEADSET ADAPTER", + .c_chmask = 1, + .p_chmask = 3, + .st_chmask = 1, + }, + { + /* BAIF + BAOF; IN: Mono only; OUT: Mono only */ + .subclass = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE, + .name = "SPEAKERPHONE", + .c_chmask = 1, + .p_chmask = 1, + }, + { 0 } /* terminator */ +}; + +static bool uac3_badd_func_has_valid_channels(struct usb_mixer_interface *mixer, + const struct uac3_badd_profile *f, + int c_chmask, int p_chmask) +{ + /* + * If both playback/capture channels are dynamic, make sure + * at least one channel is present + */ + if (f->c_chmask < 0 && f->p_chmask < 0) { + if (!c_chmask && !p_chmask) { + usb_audio_warn(mixer->chip, "BAAD %s: no channels?", + f->name); + return false; + } + return true; + } + + if ((f->c_chmask < 0 && !c_chmask) || + (f->c_chmask >= 0 && f->c_chmask != c_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s c_chmask mismatch", + f->name); + return false; + } + if ((f->p_chmask < 0 && !p_chmask) || + (f->p_chmask >= 0 && f->p_chmask != p_chmask)) { + usb_audio_warn(mixer->chip, "BAAD %s p_chmask mismatch", + f->name); + return false; + } + return true; +} + +/* + * create mixer controls for UAC3 BADD profiles + * + * UAC3 BADD device doesn't contain CS descriptors thus we will guess everything + * + * BADD device may contain Mixer Unit, which doesn't have any controls, skip it + */ +static int snd_usb_mixer_controls_badd(struct usb_mixer_interface *mixer, + int ctrlif) +{ + struct usb_device *dev = mixer->chip->dev; + struct usb_interface_assoc_descriptor *assoc; + int badd_profile = mixer->chip->badd_profile; + const struct uac3_badd_profile *f; + const struct usbmix_ctl_map *map; + int p_chmask = 0, c_chmask = 0, st_chmask = 0; + int i; + + assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc; + + /* Detect BADD capture/playback channels from AS EP descriptors */ + for (i = 0; i < assoc->bInterfaceCount; i++) { + int intf = assoc->bFirstInterface + i; + + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + unsigned int maxpacksize; + char dir_in; + int chmask, num; + + if (intf == ctrlif) + continue; + + iface = usb_ifnum_to_if(dev, intf); + if (!iface) + continue; + + num = iface->num_altsetting; + + if (num < 2) + return -EINVAL; + + /* + * The number of Channels in an AudioStreaming interface + * and the audio sample bit resolution (16 bits or 24 + * bits) can be derived from the wMaxPacketSize field in + * the Standard AS Audio Data Endpoint descriptor in + * Alternate Setting 1 + */ + alts = &iface->altsetting[1]; + altsd = get_iface_desc(alts); + + if (altsd->bNumEndpoints < 1) + return -EINVAL; + + /* check direction */ + dir_in = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN); + maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + switch (maxpacksize) { + default: + usb_audio_err(mixer->chip, + "incorrect wMaxPacketSize 0x%x for BADD profile\n", + maxpacksize); + return -EINVAL; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + chmask = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: + chmask = 3; + break; + } + + if (dir_in) + c_chmask = chmask; + else + p_chmask = chmask; + } + + usb_audio_dbg(mixer->chip, + "UAC3 BADD profile 0x%x: detected c_chmask=%d p_chmask=%d\n", + badd_profile, c_chmask, p_chmask); + + /* check the mapping table */ + for (map = uac3_badd_usbmix_ctl_maps; map->id; map++) { + if (map->id == badd_profile) + break; + } + + if (!map->id) + return -EINVAL; + + for (f = uac3_badd_profiles; f->name; f++) { + if (badd_profile == f->subclass) + break; + } + if (!f->name) + return -EINVAL; + if (!uac3_badd_func_has_valid_channels(mixer, f, c_chmask, p_chmask)) + return -EINVAL; + st_chmask = f->st_chmask; + + /* Playback */ + if (p_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID2, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, p_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID2, map->map); + } + + /* Capture */ + if (c_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID5, map->map); + /* Mono/Stereo volume channels, always writable */ + build_feature_ctl_badd(mixer, c_chmask, UAC_FU_VOLUME, + UAC3_BADD_FU_ID5, map->map); + } + + /* Side tone-mixing */ + if (st_chmask) { + /* Master channel, always writable */ + build_feature_ctl_badd(mixer, 0, UAC_FU_MUTE, + UAC3_BADD_FU_ID7, map->map); + /* Mono volume channel, always writable */ + build_feature_ctl_badd(mixer, 1, UAC_FU_VOLUME, + UAC3_BADD_FU_ID7, map->map); + } + + /* Insertion Control */ + if (f->subclass == UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER) { + struct usb_audio_term iterm, oterm; + + /* Input Term - Insertion control */ + memset(&iterm, 0, sizeof(iterm)); + iterm.id = UAC3_BADD_IT_ID4; + iterm.type = UAC_BIDIR_TERMINAL_HEADSET; + build_connector_control(mixer, map->map, &iterm, true); + + /* Output Term - Insertion control */ + memset(&oterm, 0, sizeof(oterm)); + oterm.id = UAC3_BADD_OT_ID3; + oterm.type = UAC_BIDIR_TERMINAL_HEADSET; + build_connector_control(mixer, map->map, &oterm, false); + } + + return 0; +} + +/* + * create mixer controls + * + * walk through all UAC_OUTPUT_TERMINAL descriptors to search for mixers + */ +static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) +{ + struct mixer_build state; + int err; + const struct usbmix_ctl_map *map; + void *p; + + memset(&state, 0, sizeof(state)); + state.chip = mixer->chip; + state.mixer = mixer; + state.buffer = mixer->hostif->extra; + state.buflen = mixer->hostif->extralen; + + /* check the mapping table */ + for (map = usbmix_ctl_maps; map->id; map++) { + if (map->id == state.chip->usb_id) { + state.map = map->map; + state.selector_map = map->selector_map; + mixer->connector_map = map->connector_map; + mixer->ignore_ctl_error |= map->ignore_ctl_error; + break; + } + } + + p = NULL; + while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, + mixer->hostif->extralen, + p, UAC_OUTPUT_TERMINAL)) != NULL) { + if (!snd_usb_validate_audio_desc(p, mixer->protocol)) + continue; /* skip invalid descriptor */ + + if (mixer->protocol == UAC_VERSION_1) { + struct uac1_output_terminal_descriptor *desc = p; + + /* mark terminal ID as visited */ + set_bit(desc->bTerminalID, state.unitbitmap); + state.oterm.id = desc->bTerminalID; + state.oterm.type = le16_to_cpu(desc->wTerminalType); + state.oterm.name = desc->iTerminal; + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0 && err != -EINVAL) + return err; + } else if (mixer->protocol == UAC_VERSION_2) { + struct uac2_output_terminal_descriptor *desc = p; + + /* mark terminal ID as visited */ + set_bit(desc->bTerminalID, state.unitbitmap); + state.oterm.id = desc->bTerminalID; + state.oterm.type = le16_to_cpu(desc->wTerminalType); + state.oterm.name = desc->iTerminal; + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0 && err != -EINVAL) + return err; + + /* + * For UAC2, use the same approach to also add the + * clock selectors + */ + err = parse_audio_unit(&state, desc->bCSourceID); + if (err < 0 && err != -EINVAL) + return err; + + if ((state.oterm.type & 0xff00) != 0x0100 && + uac_v2v3_control_is_readable(le16_to_cpu(desc->bmControls), + UAC2_TE_CONNECTOR)) { + build_connector_control(state.mixer, state.map, + &state.oterm, false); + } + } else { /* UAC_VERSION_3 */ + struct uac3_output_terminal_descriptor *desc = p; + + /* mark terminal ID as visited */ + set_bit(desc->bTerminalID, state.unitbitmap); + state.oterm.id = desc->bTerminalID; + state.oterm.type = le16_to_cpu(desc->wTerminalType); + state.oterm.name = le16_to_cpu(desc->wTerminalDescrStr); + err = parse_audio_unit(&state, desc->bSourceID); + if (err < 0 && err != -EINVAL) + return err; + + /* + * For UAC3, use the same approach to also add the + * clock selectors + */ + err = parse_audio_unit(&state, desc->bCSourceID); + if (err < 0 && err != -EINVAL) + return err; + + if ((state.oterm.type & 0xff00) != 0x0100 && + uac_v2v3_control_is_readable(le32_to_cpu(desc->bmControls), + UAC3_TE_INSERTION)) { + build_connector_control(state.mixer, state.map, + &state.oterm, false); + } + } + } + + return 0; +} + +static int delegate_notify(struct usb_mixer_interface *mixer, int unitid, + u8 *control, u8 *channel) +{ + const struct usbmix_connector_map *map = mixer->connector_map; + + if (!map) + return unitid; + + for (; map->id; map++) { + if (map->id == unitid) { + if (control && map->control) + *control = map->control; + if (channel && map->channel) + *channel = map->channel; + return map->delegated_id; + } + } + return unitid; +} + +void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) +{ + struct usb_mixer_elem_list *list; + + unitid = delegate_notify(mixer, unitid, NULL, NULL); + + for_each_mixer_elem(list, mixer, unitid) { + struct usb_mixer_elem_info *info; + + if (!list->is_std_info) + continue; + info = mixer_elem_list_to_info(list); + /* invalidate cache, so the value is read from the device */ + info->cached = 0; + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &list->kctl->id); + } +} + +static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, + struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list); + static const char * const val_types[] = { + "BOOLEAN", "INV_BOOLEAN", "S8", "U8", "S16", "U16", "S32", "U32", + }; + snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " + "channels=%i, type=\"%s\"\n", cval->head.id, + cval->control, cval->cmask, cval->channels, + val_types[cval->val_type]); + snd_iprintf(buffer, " Volume: min=%i, max=%i, dBmin=%i, dBmax=%i\n", + cval->min, cval->max, cval->dBmin, cval->dBmax); +} + +static void snd_usb_mixer_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_usb_audio *chip = entry->private_data; + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_list *list; + int unitid; + + list_for_each_entry(mixer, &chip->mixer_list, list) { + snd_iprintf(buffer, + "USB Mixer: usb_id=0x%08x, ctrlif=%i, ctlerr=%i\n", + chip->usb_id, snd_usb_ctrl_intf(chip), + mixer->ignore_ctl_error); + snd_iprintf(buffer, "Card: %s\n", chip->card->longname); + for (unitid = 0; unitid < MAX_ID_ELEMS; unitid++) { + for_each_mixer_elem(list, mixer, unitid) { + snd_iprintf(buffer, " Unit: %i\n", list->id); + if (list->kctl) + snd_iprintf(buffer, + " Control: name=\"%s\", index=%i\n", + list->kctl->id.name, + list->kctl->id.index); + if (list->dump) + list->dump(buffer, list); + } + } + } +} + +static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, + int attribute, int value, int index) +{ + struct usb_mixer_elem_list *list; + __u8 unitid = (index >> 8) & 0xff; + __u8 control = (value >> 8) & 0xff; + __u8 channel = value & 0xff; + unsigned int count = 0; + + if (channel >= MAX_CHANNELS) { + usb_audio_dbg(mixer->chip, + "%s(): bogus channel number %d\n", + __func__, channel); + return; + } + + unitid = delegate_notify(mixer, unitid, &control, &channel); + + for_each_mixer_elem(list, mixer, unitid) + count++; + + if (count == 0) + return; + + for_each_mixer_elem(list, mixer, unitid) { + struct usb_mixer_elem_info *info; + + if (!list->kctl) + continue; + if (!list->is_std_info) + continue; + + info = mixer_elem_list_to_info(list); + if (count > 1 && info->control != control) + continue; + + switch (attribute) { + case UAC2_CS_CUR: + /* invalidate cache, so the value is read from the device */ + if (channel) + info->cached &= ~(1 << channel); + else /* master channel */ + info->cached = 0; + + snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &info->head.kctl->id); + break; + + case UAC2_CS_RANGE: + /* TODO */ + break; + + case UAC2_CS_MEM: + /* TODO */ + break; + + default: + usb_audio_dbg(mixer->chip, + "unknown attribute %d in interrupt\n", + attribute); + break; + } /* switch */ + } +} + +static void snd_usb_mixer_interrupt(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + int len = urb->actual_length; + int ustatus = urb->status; + + if (ustatus != 0) + goto requeue; + + if (mixer->protocol == UAC_VERSION_1) { + struct uac1_status_word *status; + + for (status = urb->transfer_buffer; + len >= sizeof(*status); + len -= sizeof(*status), status++) { + dev_dbg(&urb->dev->dev, "status interrupt: %02x %02x\n", + status->bStatusType, + status->bOriginator); + + /* ignore any notifications not from the control interface */ + if ((status->bStatusType & UAC1_STATUS_TYPE_ORIG_MASK) != + UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF) + continue; + + if (status->bStatusType & UAC1_STATUS_TYPE_MEM_CHANGED) + snd_usb_mixer_rc_memory_change(mixer, status->bOriginator); + else + snd_usb_mixer_notify_id(mixer, status->bOriginator); + } + } else { /* UAC_VERSION_2 */ + struct uac2_interrupt_data_msg *msg; + + for (msg = urb->transfer_buffer; + len >= sizeof(*msg); + len -= sizeof(*msg), msg++) { + /* drop vendor specific and endpoint requests */ + if ((msg->bInfo & UAC2_INTERRUPT_DATA_MSG_VENDOR) || + (msg->bInfo & UAC2_INTERRUPT_DATA_MSG_EP)) + continue; + + snd_usb_mixer_interrupt_v2(mixer, msg->bAttribute, + le16_to_cpu(msg->wValue), + le16_to_cpu(msg->wIndex)); + } + } + +requeue: + if (ustatus != -ENOENT && + ustatus != -ECONNRESET && + ustatus != -ESHUTDOWN) { + urb->dev = mixer->chip->dev; + usb_submit_urb(urb, GFP_ATOMIC); + } +} + +/* create the handler for the optional status interrupt endpoint */ +static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer) +{ + struct usb_endpoint_descriptor *ep; + void *transfer_buffer; + int buffer_length; + unsigned int epnum; + + /* we need one interrupt input endpoint */ + if (get_iface_desc(mixer->hostif)->bNumEndpoints < 1) + return 0; + ep = get_endpoint(mixer->hostif, 0); + if (!usb_endpoint_dir_in(ep) || !usb_endpoint_xfer_int(ep)) + return 0; + + epnum = usb_endpoint_num(ep); + buffer_length = le16_to_cpu(ep->wMaxPacketSize); + transfer_buffer = kmalloc(buffer_length, GFP_KERNEL); + if (!transfer_buffer) + return -ENOMEM; + mixer->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->urb) { + kfree(transfer_buffer); + return -ENOMEM; + } + usb_fill_int_urb(mixer->urb, mixer->chip->dev, + usb_rcvintpipe(mixer->chip->dev, epnum), + transfer_buffer, buffer_length, + snd_usb_mixer_interrupt, mixer, ep->bInterval); + usb_submit_urb(mixer->urb, GFP_KERNEL); + return 0; +} + +static int keep_iface_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = mixer->chip->keep_iface; + return 0; +} + +static int keep_iface_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol); + bool keep_iface = !!ucontrol->value.integer.value[0]; + + if (mixer->chip->keep_iface == keep_iface) + return 0; + mixer->chip->keep_iface = keep_iface; + return 1; +} + +static const struct snd_kcontrol_new keep_iface_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Keep Interface", + .info = snd_ctl_boolean_mono_info, + .get = keep_iface_ctl_get, + .put = keep_iface_ctl_put, +}; + +static int create_keep_iface_ctl(struct usb_mixer_interface *mixer) +{ + struct snd_kcontrol *kctl = snd_ctl_new1(&keep_iface_ctl, mixer); + + /* need only one control per card */ + if (snd_ctl_find_id(mixer->chip->card, &kctl->id)) { + snd_ctl_free_one(kctl); + return 0; + } + + return snd_ctl_add(mixer->chip->card, kctl); +} + +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, + int ignore_error) +{ + static struct snd_device_ops dev_ops = { + .dev_free = snd_usb_mixer_dev_free + }; + struct usb_mixer_interface *mixer; + struct snd_info_entry *entry; + int err; + + strcpy(chip->card->mixername, "USB Mixer"); + + mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + mixer->chip = chip; + mixer->ignore_ctl_error = ignore_error; + mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems), + GFP_KERNEL); + if (!mixer->id_elems) { + kfree(mixer); + return -ENOMEM; + } + + mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; + switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) { + case UAC_VERSION_1: + default: + mixer->protocol = UAC_VERSION_1; + break; + case UAC_VERSION_2: + mixer->protocol = UAC_VERSION_2; + break; + case UAC_VERSION_3: + mixer->protocol = UAC_VERSION_3; + break; + } + + if (mixer->protocol == UAC_VERSION_3 && + chip->badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + err = snd_usb_mixer_controls_badd(mixer, ctrlif); + if (err < 0) + goto _error; + } else { + err = snd_usb_mixer_controls(mixer); + if (err < 0) + goto _error; + } + + err = snd_usb_mixer_status_create(mixer); + if (err < 0) + goto _error; + + err = create_keep_iface_ctl(mixer); + if (err < 0) + goto _error; + + err = snd_usb_mixer_apply_create_quirk(mixer); + if (err < 0) + goto _error; + + err = snd_device_new(chip->card, SNDRV_DEV_CODEC, mixer, &dev_ops); + if (err < 0) + goto _error; + + if (list_empty(&chip->mixer_list) && + !snd_card_proc_new(chip->card, "usbmixer", &entry)) + snd_info_set_text_ops(entry, chip, snd_usb_mixer_proc_read); + + list_add(&mixer->list, &chip->mixer_list); + return 0; + +_error: + snd_usb_mixer_free(mixer); + return err; +} + +void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer) +{ + if (mixer->disconnected) + return; + if (mixer->urb) + usb_kill_urb(mixer->urb); + if (mixer->rc_urb) + usb_kill_urb(mixer->rc_urb); + mixer->disconnected = true; +} + +#ifdef CONFIG_PM +/* stop any bus activity of a mixer */ +static void snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer) +{ + usb_kill_urb(mixer->urb); + usb_kill_urb(mixer->rc_urb); +} + +static int snd_usb_mixer_activate(struct usb_mixer_interface *mixer) +{ + int err; + + if (mixer->urb) { + err = usb_submit_urb(mixer->urb, GFP_NOIO); + if (err < 0) + return err; + } + + return 0; +} + +int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer) +{ + snd_usb_mixer_inactivate(mixer); + return 0; +} + +static int restore_mixer_value(struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list); + int c, err, idx; + + if (cval->cmask) { + idx = 0; + for (c = 0; c < MAX_CHANNELS; c++) { + if (!(cval->cmask & (1 << c))) + continue; + if (cval->cached & (1 << (c + 1))) { + err = snd_usb_set_cur_mix_value(cval, c + 1, idx, + cval->cache_val[idx]); + if (err < 0) + return err; + } + idx++; + } + } else { + /* master */ + if (cval->cached) { + err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); + if (err < 0) + return err; + } + } + + return 0; +} + +int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume) +{ + struct usb_mixer_elem_list *list; + int id, err; + + if (reset_resume) { + /* restore cached mixer values */ + for (id = 0; id < MAX_ID_ELEMS; id++) { + for_each_mixer_elem(list, mixer, id) { + if (list->resume) { + err = list->resume(list); + if (err < 0) + return err; + } + } + } + } + + snd_usb_mixer_resume_quirk(mixer); + + return snd_usb_mixer_activate(mixer); +} +#endif + +void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, + struct usb_mixer_interface *mixer, + int unitid) +{ + list->mixer = mixer; + list->id = unitid; + list->dump = snd_usb_mixer_dump_cval; +#ifdef CONFIG_PM + list->resume = restore_mixer_value; +#endif +} diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h new file mode 100644 index 000000000..f7e6fe1a9 --- /dev/null +++ b/sound/usb/mixer.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBMIXER_H +#define __USBMIXER_H + +#include + +struct usbmix_connector_map { + u8 id; + u8 delegated_id; + u8 control; + u8 channel; +}; + +struct usb_mixer_interface { + struct snd_usb_audio *chip; + struct usb_host_interface *hostif; + struct list_head list; + unsigned int ignore_ctl_error; + struct urb *urb; + /* array[MAX_ID_ELEMS], indexed by unit id */ + struct usb_mixer_elem_list **id_elems; + + /* the usb audio specification version this interface complies to */ + int protocol; + + /* optional connector delegation map */ + const struct usbmix_connector_map *connector_map; + + /* Sound Blaster remote control stuff */ + const struct rc_config *rc_cfg; + u32 rc_code; + wait_queue_head_t rc_waitq; + struct urb *rc_urb; + struct usb_ctrlrequest *rc_setup_packet; + u8 rc_buffer[6]; + + bool disconnected; +}; + +#define MAX_CHANNELS 16 /* max logical channels */ + +enum { + USB_MIXER_BOOLEAN, + USB_MIXER_INV_BOOLEAN, + USB_MIXER_S8, + USB_MIXER_U8, + USB_MIXER_S16, + USB_MIXER_U16, + USB_MIXER_S32, + USB_MIXER_U32, +}; + +typedef void (*usb_mixer_elem_dump_func_t)(struct snd_info_buffer *buffer, + struct usb_mixer_elem_list *list); +typedef int (*usb_mixer_elem_resume_func_t)(struct usb_mixer_elem_list *elem); + +struct usb_mixer_elem_list { + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_list *next_id_elem; /* list of controls with same id */ + struct snd_kcontrol *kctl; + unsigned int id; + bool is_std_info; + usb_mixer_elem_dump_func_t dump; + usb_mixer_elem_resume_func_t resume; +}; + +/* iterate over mixer element list of the given unit id */ +#define for_each_mixer_elem(list, mixer, id) \ + for ((list) = (mixer)->id_elems[id]; (list); (list) = (list)->next_id_elem) +#define mixer_elem_list_to_info(list) \ + container_of(list, struct usb_mixer_elem_info, head) + +struct usb_mixer_elem_info { + struct usb_mixer_elem_list head; + unsigned int control; /* CS or ICN (high byte) */ + unsigned int cmask; /* channel mask bitmap: 0 = master */ + unsigned int idx_off; /* Control index offset */ + unsigned int ch_readonly; + unsigned int master_readonly; + int channels; + int val_type; + int min, max, res; + int dBmin, dBmax; + int cached; + int cache_val[MAX_CHANNELS]; + u8 initialized; + u8 min_mute; + void *private_data; +}; + +int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, + int ignore_error); +void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer); + +void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid); + +int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, + int request, int validx, int value_set); + +int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, + struct snd_kcontrol *kctl, + bool is_std_info); + +#define snd_usb_mixer_add_control(list, kctl) \ + snd_usb_mixer_add_list(list, kctl, true) + +void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, + struct usb_mixer_interface *mixer, + int unitid); + +int snd_usb_mixer_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv); + +#ifdef CONFIG_PM +int snd_usb_mixer_suspend(struct usb_mixer_interface *mixer); +int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume); +#endif + +int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value); + +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value); + +extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl); + +extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; + +#endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c new file mode 100644 index 000000000..1d4e535e2 --- /dev/null +++ b/sound/usb/mixer_maps.c @@ -0,0 +1,649 @@ +/* + * Additional mixer mapping + * + * Copyright (c) 2002 by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +struct usbmix_dB_map { + u32 min; + u32 max; +}; + +struct usbmix_name_map { + int id; + const char *name; + int control; + const struct usbmix_dB_map *dB; +}; + +struct usbmix_selector_map { + int id; + int count; + const char **names; +}; + +struct usbmix_ctl_map { + u32 id; + const struct usbmix_name_map *map; + const struct usbmix_selector_map *selector_map; + const struct usbmix_connector_map *connector_map; + int ignore_ctl_error; +}; + +/* + * USB control mappers for SB Exitigy + */ + +/* + * Topology of SB Extigy (see on the wide screen :) + +USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24] + ^ | | | | +USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+ | | | +->FU[25] > Dig_OUT[26] + ^ ^ | | | | +Dig_IN[4] -+ | | | | +->FU[28]---------------------> Spk_OUT[19] + | | | | +Lin-IN[7] -+-->FU[8]---------+ | | +----------------------------------------> Hph_OUT[20] + | | | +Mic-IN[9] --+->FU[10]----------------------------+ | + || | + || +----------------------------------------------------+ + VV V + ++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13] +*/ + +static const struct usbmix_name_map extigy_map[] = { + /* 1: IT pcm */ + { 2, "PCM Playback" }, /* FU */ + /* 3: IT pcm */ + /* 4: IT digital in */ + { 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */ + { 6, "Digital In" }, /* FU */ + /* 7: IT line */ + { 8, "Line Playback" }, /* FU */ + /* 9: IT mic */ + { 10, "Mic Playback" }, /* FU */ + { 11, "Capture Source" }, /* SU */ + { 12, "Capture" }, /* FU */ + /* 13: OT pcm capture */ + /* 14: MU (w/o controls) */ + /* 15: PU (3D enh) */ + /* 16: MU (w/o controls) */ + { 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */ + { 17, "Channel Routing", 2 }, /* PU: mode select */ + { 18, "Tone Control - Bass", UAC_FU_BASS }, /* FU */ + { 18, "Tone Control - Treble", UAC_FU_TREBLE }, /* FU */ + { 18, "Master Playback" }, /* FU; others */ + /* 19: OT speaker */ + /* 20: OT headphone */ + { 21, NULL }, /* DISABLED: EU (for what?) */ + { 22, "Digital Out Playback" }, /* FU */ + { 23, "Digital Out1 Playback" }, /* FU */ /* FIXME: corresponds to 24 */ + /* 24: OT digital out */ + { 25, "IEC958 Optical Playback" }, /* FU */ + { 26, "IEC958 Optical Playback" }, /* OT */ + { 27, NULL }, /* DISABLED: EU (for what?) */ + /* 28: FU speaker (mute) */ + { 29, NULL }, /* Digital Input Playback Source? */ + { 0 } /* terminator */ +}; + +/* Sound Blaster MP3+ controls mapping + * The default mixer channels have totally misleading names, + * e.g. no Master and fake PCM volume + * Pavel Mihaylov + */ +static const struct usbmix_dB_map mp3plus_dB_1 = {.min = -4781, .max = 0}; + /* just guess */ +static const struct usbmix_dB_map mp3plus_dB_2 = {.min = -1781, .max = 618}; + /* just guess */ + +static const struct usbmix_name_map mp3plus_map[] = { + /* 1: IT pcm */ + /* 2: IT mic */ + /* 3: IT line */ + /* 4: IT digital in */ + /* 5: OT digital out */ + /* 6: OT speaker */ + /* 7: OT pcm capture */ + { 8, "Capture Source" }, /* FU, default PCM Capture Source */ + /* (Mic, Input 1 = Line input, Input 2 = Optical input) */ + { 9, "Master Playback" }, /* FU, default Speaker 1 */ + /* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */ + { 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 }, + /* FU, Mic Capture */ + { 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */ + { 11, "Line Capture", .dB = &mp3plus_dB_2 }, + /* FU, default PCM Capture */ + { 12, "Digital In Playback" }, /* FU, default PCM 1 */ + { 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 }, + /* FU, default Mic Playback */ + { 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */ + /* 15: MU */ + { 0 } /* terminator */ +}; + +/* Topology of SB Audigy 2 NX + + +----------------------------->EU[27]--+ + | v + | +----------------------------------->SU[29]---->FU[22]-->Dig_OUT[24] + | | ^ +USB_IN[1]-+------------+ +->EU[17]->+->FU[11]-+ + | v | v | +Dig_IN[4]---+->FU[6]-->MU[16]->FU[18]-+->EU[21]->SU[31]----->FU[30]->Hph_OUT[20] + | ^ | | +Lin_IN[7]-+--->FU[8]---+ +->EU[23]->FU[28]------------->Spk_OUT[19] + | | v + +--->FU[12]------------------------------------->SU[14]--->USB_OUT[15] + | ^ + +->FU[13]--------------------------------------+ +*/ +static const struct usbmix_name_map audigy2nx_map[] = { + /* 1: IT pcm playback */ + /* 4: IT digital in */ + { 6, "Digital In Playback" }, /* FU */ + /* 7: IT line in */ + { 8, "Line Playback" }, /* FU */ + { 11, "What-U-Hear Capture" }, /* FU */ + { 12, "Line Capture" }, /* FU */ + { 13, "Digital In Capture" }, /* FU */ + { 14, "Capture Source" }, /* SU */ + /* 15: OT pcm capture */ + /* 16: MU w/o controls */ + { 17, NULL }, /* DISABLED: EU (for what?) */ + { 18, "Master Playback" }, /* FU */ + /* 19: OT speaker */ + /* 20: OT headphone */ + { 21, NULL }, /* DISABLED: EU (for what?) */ + { 22, "Digital Out Playback" }, /* FU */ + { 23, NULL }, /* DISABLED: EU (for what?) */ + /* 24: OT digital out */ + { 27, NULL }, /* DISABLED: EU (for what?) */ + { 28, "Speaker Playback" }, /* FU */ + { 29, "Digital Out Source" }, /* SU */ + { 30, "Headphone Playback" }, /* FU */ + { 31, "Headphone Source" }, /* SU */ + { 0 } /* terminator */ +}; + +static const struct usbmix_name_map mbox1_map[] = { + { 1, "Clock" }, + { 0 } /* terminator */ +}; + +static const struct usbmix_selector_map c400_selectors[] = { + { + .id = 0x80, + .count = 2, + .names = (const char*[]) {"Internal", "SPDIF"} + }, + { 0 } /* terminator */ +}; + +static const struct usbmix_selector_map audigy2nx_selectors[] = { + { + .id = 14, /* Capture Source */ + .count = 3, + .names = (const char*[]) {"Line", "Digital In", "What-U-Hear"} + }, + { + .id = 29, /* Digital Out Source */ + .count = 3, + .names = (const char*[]) {"Front", "PCM", "Digital In"} + }, + { + .id = 31, /* Headphone Source */ + .count = 2, + .names = (const char*[]) {"Front", "Side"} + }, + { 0 } /* terminator */ +}; + +/* Creative SoundBlaster Live! 24-bit External */ +static const struct usbmix_name_map live24ext_map[] = { + /* 2: PCM Playback Volume */ + { 5, "Mic Capture" }, /* FU, default PCM Capture Volume */ + { 0 } /* terminator */ +}; + +/* LineX FM Transmitter entry - needed to bypass controls bug */ +static const struct usbmix_name_map linex_map[] = { + /* 1: IT pcm */ + /* 2: OT Speaker */ + { 3, "Master" }, /* FU: master volume - left / right / mute */ + { 0 } /* terminator */ +}; + +static const struct usbmix_name_map maya44_map[] = { + /* 1: IT line */ + { 2, "Line Playback" }, /* FU */ + /* 3: IT line */ + { 4, "Line Playback" }, /* FU */ + /* 5: IT pcm playback */ + /* 6: MU */ + { 7, "Master Playback" }, /* FU */ + /* 8: OT speaker */ + /* 9: IT line */ + { 10, "Line Capture" }, /* FU */ + /* 11: MU */ + /* 12: OT pcm capture */ + { } +}; + +/* Section "justlink_map" below added by James Courtier-Dutton + * sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK + * Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.) + * The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device, + * so this map removes all unwanted sliders from alsamixer + */ + +static const struct usbmix_name_map justlink_map[] = { + /* 1: IT pcm playback */ + /* 2: Not present */ + { 3, NULL}, /* IT mic (No mic input on device) */ + /* 4: Not present */ + /* 5: OT speacker */ + /* 6: OT pcm capture */ + { 7, "Master Playback" }, /* Mute/volume for speaker */ + { 8, NULL }, /* Capture Switch (No capture inputs on device) */ + { 9, NULL }, /* Capture Mute/volume (No capture inputs on device */ + /* 0xa: Not present */ + /* 0xb: MU (w/o controls) */ + { 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */ + { 0 } /* terminator */ +}; + +/* TerraTec Aureon 5.1 MkII USB */ +static const struct usbmix_name_map aureon_51_2_map[] = { + /* 1: IT USB */ + /* 2: IT Mic */ + /* 3: IT Line */ + /* 4: IT SPDIF */ + /* 5: OT SPDIF */ + /* 6: OT Speaker */ + /* 7: OT USB */ + { 8, "Capture Source" }, /* SU */ + { 9, "Master Playback" }, /* FU */ + { 10, "Mic Capture" }, /* FU */ + { 11, "Line Capture" }, /* FU */ + { 12, "IEC958 In Capture" }, /* FU */ + { 13, "Mic Playback" }, /* FU */ + { 14, "Line Playback" }, /* FU */ + /* 15: MU */ + {} /* terminator */ +}; + +static const struct usbmix_name_map scratch_live_map[] = { + /* 1: IT Line 1 (USB streaming) */ + /* 2: OT Line 1 (Speaker) */ + /* 3: IT Line 1 (Line connector) */ + { 4, "Line 1 In" }, /* FU */ + /* 5: OT Line 1 (USB streaming) */ + /* 6: IT Line 2 (USB streaming) */ + /* 7: OT Line 2 (Speaker) */ + /* 8: IT Line 2 (Line connector) */ + { 9, "Line 2 In" }, /* FU */ + /* 10: OT Line 2 (USB streaming) */ + /* 11: IT Mic (Line connector) */ + /* 12: OT Mic (USB streaming) */ + { 0 } /* terminator */ +}; + +static const struct usbmix_name_map ebox44_map[] = { + { 4, NULL }, /* FU */ + { 6, NULL }, /* MU */ + { 7, NULL }, /* FU */ + { 10, NULL }, /* FU */ + { 11, NULL }, /* MU */ + { 0 } +}; + +/* "Gamesurround Muse Pocket LT" looks same like "Sound Blaster MP3+" + * most importand difference is SU[8], it should be set to "Capture Source" + * to make alsamixer and PA working properly. + * FIXME: or mp3plus_map should use "Capture Source" too, + * so this maps can be merget + */ +static const struct usbmix_name_map hercules_usb51_map[] = { + { 8, "Capture Source" }, /* SU, default "PCM Capture Source" */ + { 9, "Master Playback" }, /* FU, default "Speaker Playback" */ + { 10, "Mic Boost", 7 }, /* FU, default "Auto Gain Input" */ + { 11, "Line Capture" }, /* FU, default "PCM Capture" */ + { 13, "Mic Bypass Playback" }, /* FU, default "Mic Playback" */ + { 14, "Line Bypass Playback" }, /* FU, default "Line Playback" */ + { 0 } /* terminator */ +}; + +/* Plantronics Gamecom 780 has a broken volume control, better to disable it */ +static const struct usbmix_name_map gamecom780_map[] = { + { 9, NULL }, /* FU, speaker out */ + {} +}; + +/* some (all?) SCMS USB3318 devices are affected by a firmware lock up + * when anything attempts to access FU 10 (control) + */ +static const struct usbmix_name_map scms_usb3318_map[] = { + { 10, NULL }, + { 0 } +}; + +/* Bose companion 5, the dB conversion factor is 16 instead of 256 */ +static const struct usbmix_dB_map bose_companion5_dB = {-5006, -6}; +static const struct usbmix_name_map bose_companion5_map[] = { + { 3, NULL, .dB = &bose_companion5_dB }, + { 0 } /* terminator */ +}; + +/* Sennheiser Communications Headset [PC 8], the dB value is reported as -6 negative maximum */ +static const struct usbmix_dB_map sennheiser_pc8_dB = {-9500, 0}; +static const struct usbmix_name_map sennheiser_pc8_map[] = { + { 9, NULL, .dB = &sennheiser_pc8_dB }, + { 0 } /* terminator */ +}; + +/* + * Dell usb dock with ALC4020 codec had a firmware problem where it got + * screwed up when zero volume is passed; just skip it as a workaround + * + * Also the extension unit gives an access error, so skip it as well. + */ +static const struct usbmix_name_map dell_alc4020_map[] = { + { 4, NULL }, /* extension unit */ + { 16, NULL }, + { 19, NULL }, + { 0 } +}; + +/* Some mobos shipped with a dummy HD-audio show the invalid GET_MIN/GET_MAX + * response for Input Gain Pad (id=19, control=12) and the connector status + * for SPDIF terminal (id=18). Skip them. + */ +static const struct usbmix_name_map asus_rog_map[] = { + { 18, NULL }, /* OT, connector control */ + { 19, NULL, 12 }, /* FU, Input Gain Pad */ + {} +}; + +/* TRX40 mobos with Realtek ALC1220-VB */ +static const struct usbmix_name_map trx40_mobo_map[] = { + { 18, NULL }, /* OT, IEC958 - broken response, disabled */ + { 19, NULL, 12 }, /* FU, Input Gain Pad - broken response, disabled */ + { 16, "Speaker" }, /* OT */ + { 22, "Speaker Playback" }, /* FU */ + { 7, "Line" }, /* IT */ + { 19, "Line Capture" }, /* FU */ + { 17, "Front Headphone" }, /* OT */ + { 23, "Front Headphone Playback" }, /* FU */ + { 8, "Mic" }, /* IT */ + { 20, "Mic Capture" }, /* FU */ + { 9, "Front Mic" }, /* IT */ + { 21, "Front Mic Capture" }, /* FU */ + { 24, "IEC958 Playback" }, /* FU */ + {} +}; + +static const struct usbmix_connector_map trx40_mobo_connector_map[] = { + { 10, 16 }, /* (Back) Speaker */ + { 11, 17 }, /* Front Headphone */ + { 13, 7 }, /* Line */ + { 14, 8 }, /* Mic */ + { 15, 9 }, /* Front Mic */ + {} +}; + +/* Rear panel + front mic on Gigabyte TRX40 Aorus Master with ALC1220-VB */ +static const struct usbmix_name_map aorus_master_alc1220vb_map[] = { + { 17, NULL }, /* OT, IEC958?, disabled */ + { 19, NULL, 12 }, /* FU, Input Gain Pad - broken response, disabled */ + { 16, "Line Out" }, /* OT */ + { 22, "Line Out Playback" }, /* FU */ + { 7, "Line" }, /* IT */ + { 19, "Line Capture" }, /* FU */ + { 8, "Mic" }, /* IT */ + { 20, "Mic Capture" }, /* FU */ + { 9, "Front Mic" }, /* IT */ + { 21, "Front Mic Capture" }, /* FU */ + {} +}; + +/* + * Control map entries + */ + +static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + { + .id = USB_ID(0x041e, 0x3000), + .map = extigy_map, + .ignore_ctl_error = 1, + }, + { + .id = USB_ID(0x041e, 0x3010), + .map = mp3plus_map, + }, + { + .id = USB_ID(0x041e, 0x3020), + .map = audigy2nx_map, + .selector_map = audigy2nx_selectors, + }, + { + .id = USB_ID(0x041e, 0x3040), + .map = live24ext_map, + }, + { + .id = USB_ID(0x041e, 0x3048), + .map = audigy2nx_map, + .selector_map = audigy2nx_selectors, + }, + { /* Logitech, Inc. QuickCam Pro for Notebooks */ + .id = USB_ID(0x046d, 0x0991), + .ignore_ctl_error = 1, + }, + { /* Logitech, Inc. QuickCam E 3500 */ + .id = USB_ID(0x046d, 0x09a4), + .ignore_ctl_error = 1, + }, + { /* Plantronics GameCom 780 */ + .id = USB_ID(0x047f, 0xc010), + .map = gamecom780_map, + }, + { + /* Hercules DJ Console (Windows Edition) */ + .id = USB_ID(0x06f8, 0xb000), + .ignore_ctl_error = 1, + }, + { + /* Hercules DJ Console (Macintosh Edition) */ + .id = USB_ID(0x06f8, 0xd002), + .ignore_ctl_error = 1, + }, + { + /* Hercules Gamesurround Muse Pocket LT + * (USB 5.1 Channel Audio Adapter) + */ + .id = USB_ID(0x06f8, 0xc000), + .map = hercules_usb51_map, + }, + { + .id = USB_ID(0x0763, 0x2030), + .selector_map = c400_selectors, + }, + { + .id = USB_ID(0x0763, 0x2031), + .selector_map = c400_selectors, + }, + { + .id = USB_ID(0x08bb, 0x2702), + .map = linex_map, + .ignore_ctl_error = 1, + }, + { + .id = USB_ID(0x0a92, 0x0091), + .map = maya44_map, + }, + { + .id = USB_ID(0x0c45, 0x1158), + .map = justlink_map, + }, + { + .id = USB_ID(0x0ccd, 0x0028), + .map = aureon_51_2_map, + }, + { + .id = USB_ID(0x0bda, 0x4014), + .map = dell_alc4020_map, + }, + { + .id = USB_ID(0x0dba, 0x1000), + .map = mbox1_map, + }, + { + .id = USB_ID(0x13e5, 0x0001), + .map = scratch_live_map, + .ignore_ctl_error = 1, + }, + { + .id = USB_ID(0x200c, 0x1018), + .map = ebox44_map, + }, + { + /* MAYA44 USB+ */ + .id = USB_ID(0x2573, 0x0008), + .map = maya44_map, + }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), + .map = scms_usb3318_map, + }, + { + /* Arcam rPAC */ + .id = USB_ID(0x25c4, 0x0003), + .map = scms_usb3318_map, + }, + { + /* Bose Companion 5 */ + .id = USB_ID(0x05a7, 0x1020), + .map = bose_companion5_map, + }, + { /* Gigabyte TRX40 Aorus Master (rear panel + front mic) */ + .id = USB_ID(0x0414, 0xa001), + .map = aorus_master_alc1220vb_map, + }, + { /* Gigabyte TRX40 Aorus Pro WiFi */ + .id = USB_ID(0x0414, 0xa002), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* ASUS ROG Zenith II */ + .id = USB_ID(0x0b05, 0x1916), + .map = asus_rog_map, + }, + { /* ASUS ROG Strix */ + .id = USB_ID(0x0b05, 0x1917), + .map = asus_rog_map, + }, + { /* MSI TRX40 Creator */ + .id = USB_ID(0x0db0, 0x0d64), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* MSI TRX40 */ + .id = USB_ID(0x0db0, 0x543d), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { /* Asrock TRX40 Creator */ + .id = USB_ID(0x26ce, 0x0a01), + .map = trx40_mobo_map, + .connector_map = trx40_mobo_connector_map, + }, + { 0 } /* terminator */ +}; + +/* + * Control map entries for UAC3 BADD profiles + */ + +static const struct usbmix_name_map uac3_badd_generic_io_map[] = { + { UAC3_BADD_FU_ID2, "Generic Out Playback" }, + { UAC3_BADD_FU_ID5, "Generic In Capture" }, + { 0 } /* terminator */ +}; +static const struct usbmix_name_map uac3_badd_headphone_map[] = { + { UAC3_BADD_FU_ID2, "Headphone Playback" }, + { 0 } /* terminator */ +}; +static const struct usbmix_name_map uac3_badd_speaker_map[] = { + { UAC3_BADD_FU_ID2, "Speaker Playback" }, + { 0 } /* terminator */ +}; +static const struct usbmix_name_map uac3_badd_microphone_map[] = { + { UAC3_BADD_FU_ID5, "Mic Capture" }, + { 0 } /* terminator */ +}; +/* Covers also 'headset adapter' profile */ +static const struct usbmix_name_map uac3_badd_headset_map[] = { + { UAC3_BADD_FU_ID2, "Headset Playback" }, + { UAC3_BADD_FU_ID5, "Headset Capture" }, + { UAC3_BADD_FU_ID7, "Sidetone Mixing" }, + { 0 } /* terminator */ +}; +static const struct usbmix_name_map uac3_badd_speakerphone_map[] = { + { UAC3_BADD_FU_ID2, "Speaker Playback" }, + { UAC3_BADD_FU_ID5, "Mic Capture" }, + { 0 } /* terminator */ +}; + +static const struct usbmix_ctl_map uac3_badd_usbmix_ctl_maps[] = { + { + .id = UAC3_FUNCTION_SUBCLASS_GENERIC_IO, + .map = uac3_badd_generic_io_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADPHONE, + .map = uac3_badd_headphone_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_SPEAKER, + .map = uac3_badd_speaker_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_MICROPHONE, + .map = uac3_badd_microphone_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADSET, + .map = uac3_badd_headset_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_HEADSET_ADAPTER, + .map = uac3_badd_headset_map, + }, + { + .id = UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE, + .map = uac3_badd_speakerphone_map, + }, + { + /* Sennheiser Communications Headset [PC 8] */ + .id = USB_ID(0x1395, 0x0025), + .map = sennheiser_pc8_map, + }, + { 0 } /* terminator */ +}; diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c new file mode 100644 index 000000000..3bb89fcaa --- /dev/null +++ b/sound/usb/mixer_quirks.c @@ -0,0 +1,2009 @@ +/* + * USB Audio Driver for ALSA + * + * Quirks and vendor-specific extensions for mixer interfaces + * + * Copyright (c) 2002 by Takashi Iwai + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * Audio Advantage Micro II support added by: + * Przemek Rudy (prudy1@o2.pl) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "mixer_quirks.h" +#include "mixer_scarlett.h" +#include "mixer_us16x08.h" +#include "helper.h" + +struct std_mono_table { + unsigned int unitid, control, cmask; + int val_type; + const char *name; + snd_kcontrol_tlv_rw_t *tlv_callback; +}; + +/* This function allows for the creation of standard UAC controls. + * See the quirks for M-Audio FTUs or Ebox-44. + * If you don't want to set a TLV callback pass NULL. + * + * Since there doesn't seem to be a devices that needs a multichannel + * version, we keep it mono for simplicity. + */ +static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer, + unsigned int unitid, + unsigned int control, + unsigned int cmask, + int val_type, + unsigned int idx_off, + const char *name, + snd_kcontrol_tlv_rw_t *tlv_callback) +{ + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + + snd_usb_mixer_elem_init_std(&cval->head, mixer, unitid); + cval->val_type = val_type; + cval->channels = 1; + cval->control = control; + cval->cmask = cmask; + cval->idx_off = idx_off; + + /* get_min_max() is called only for integer volumes later, + * so provide a short-cut for booleans */ + cval->min = 0; + cval->max = 1; + cval->res = 0; + cval->dBmin = 0; + cval->dBmax = 0; + + /* Create control */ + kctl = snd_ctl_new1(snd_usb_feature_unit_ctl, cval); + if (!kctl) { + kfree(cval); + return -ENOMEM; + } + + /* Set name */ + snprintf(kctl->id.name, sizeof(kctl->id.name), name); + kctl->private_free = snd_usb_mixer_elem_free; + + /* set TLV */ + if (tlv_callback) { + kctl->tlv.c = tlv_callback; + kctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } + /* Add control to mixer */ + return snd_usb_mixer_add_control(&cval->head, kctl); +} + +static int snd_create_std_mono_ctl(struct usb_mixer_interface *mixer, + unsigned int unitid, + unsigned int control, + unsigned int cmask, + int val_type, + const char *name, + snd_kcontrol_tlv_rw_t *tlv_callback) +{ + return snd_create_std_mono_ctl_offset(mixer, unitid, control, cmask, + val_type, 0 /* Offset */, name, tlv_callback); +} + +/* + * Create a set of standard UAC controls from a table + */ +static int snd_create_std_mono_table(struct usb_mixer_interface *mixer, + const struct std_mono_table *t) +{ + int err; + + while (t->name != NULL) { + err = snd_create_std_mono_ctl(mixer, t->unitid, t->control, + t->cmask, t->val_type, t->name, t->tlv_callback); + if (err < 0) + return err; + t++; + } + + return 0; +} + +static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer, + int id, + usb_mixer_elem_resume_func_t resume, + const struct snd_kcontrol_new *knew, + struct usb_mixer_elem_list **listp) +{ + struct usb_mixer_elem_list *list; + struct snd_kcontrol *kctl; + + list = kzalloc(sizeof(*list), GFP_KERNEL); + if (!list) + return -ENOMEM; + if (listp) + *listp = list; + list->mixer = mixer; + list->id = id; + list->resume = resume; + kctl = snd_ctl_new1(knew, list); + if (!kctl) { + kfree(list); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + /* don't use snd_usb_mixer_add_control() here, this is a special list element */ + return snd_usb_mixer_add_list(list, kctl, false); +} + +/* + * Sound Blaster remote control configuration + * + * format of remote control data: + * Extigy: xx 00 + * Audigy 2 NX: 06 80 xx 00 00 00 + * Live! 24-bit: 06 80 xx yy 22 83 + */ +static const struct rc_config { + u32 usb_id; + u8 offset; + u8 length; + u8 packet_length; + u8 min_packet_length; /* minimum accepted length of the URB result */ + u8 mute_mixer_id; + u32 mute_code; +} rc_configs[] = { + { USB_ID(0x041e, 0x3000), 0, 1, 2, 1, 18, 0x0013 }, /* Extigy */ + { USB_ID(0x041e, 0x3020), 2, 1, 6, 6, 18, 0x0013 }, /* Audigy 2 NX */ + { USB_ID(0x041e, 0x3040), 2, 2, 6, 6, 2, 0x6e91 }, /* Live! 24-bit */ + { USB_ID(0x041e, 0x3042), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 */ + { USB_ID(0x041e, 0x30df), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ + { USB_ID(0x041e, 0x3237), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ + { USB_ID(0x041e, 0x3263), 0, 1, 1, 1, 1, 0x000d }, /* Usb X-Fi S51 Pro */ + { USB_ID(0x041e, 0x3048), 2, 2, 6, 6, 2, 0x6e91 }, /* Toshiba SB0500 */ +}; + +static void snd_usb_soundblaster_remote_complete(struct urb *urb) +{ + struct usb_mixer_interface *mixer = urb->context; + const struct rc_config *rc = mixer->rc_cfg; + u32 code; + + if (urb->status < 0 || urb->actual_length < rc->min_packet_length) + return; + + code = mixer->rc_buffer[rc->offset]; + if (rc->length == 2) + code |= mixer->rc_buffer[rc->offset + 1] << 8; + + /* the Mute button actually changes the mixer control */ + if (code == rc->mute_code) + snd_usb_mixer_notify_id(mixer, rc->mute_mixer_id); + mixer->rc_code = code; + wmb(); + wake_up(&mixer->rc_waitq); +} + +static long snd_usb_sbrc_hwdep_read(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + int err; + u32 rc_code; + + if (count != 1 && count != 4) + return -EINVAL; + err = wait_event_interruptible(mixer->rc_waitq, + (rc_code = xchg(&mixer->rc_code, 0)) != 0); + if (err == 0) { + if (count == 1) + err = put_user(rc_code, buf); + else + err = put_user(rc_code, (u32 __user *)buf); + } + return err < 0 ? err : count; +} + +static __poll_t snd_usb_sbrc_hwdep_poll(struct snd_hwdep *hw, struct file *file, + poll_table *wait) +{ + struct usb_mixer_interface *mixer = hw->private_data; + + poll_wait(file, &mixer->rc_waitq, wait); + return mixer->rc_code ? EPOLLIN | EPOLLRDNORM : 0; +} + +static int snd_usb_soundblaster_remote_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hwdep; + int err, len, i; + + for (i = 0; i < ARRAY_SIZE(rc_configs); ++i) + if (rc_configs[i].usb_id == mixer->chip->usb_id) + break; + if (i >= ARRAY_SIZE(rc_configs)) + return 0; + mixer->rc_cfg = &rc_configs[i]; + + len = mixer->rc_cfg->packet_length; + + init_waitqueue_head(&mixer->rc_waitq); + err = snd_hwdep_new(mixer->chip->card, "SB remote control", 0, &hwdep); + if (err < 0) + return err; + snprintf(hwdep->name, sizeof(hwdep->name), + "%s remote control", mixer->chip->card->shortname); + hwdep->iface = SNDRV_HWDEP_IFACE_SB_RC; + hwdep->private_data = mixer; + hwdep->ops.read = snd_usb_sbrc_hwdep_read; + hwdep->ops.poll = snd_usb_sbrc_hwdep_poll; + hwdep->exclusive = 1; + + mixer->rc_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mixer->rc_urb) + return -ENOMEM; + mixer->rc_setup_packet = kmalloc(sizeof(*mixer->rc_setup_packet), GFP_KERNEL); + if (!mixer->rc_setup_packet) { + usb_free_urb(mixer->rc_urb); + mixer->rc_urb = NULL; + return -ENOMEM; + } + mixer->rc_setup_packet->bRequestType = + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + mixer->rc_setup_packet->bRequest = UAC_GET_MEM; + mixer->rc_setup_packet->wValue = cpu_to_le16(0); + mixer->rc_setup_packet->wIndex = cpu_to_le16(0); + mixer->rc_setup_packet->wLength = cpu_to_le16(len); + usb_fill_control_urb(mixer->rc_urb, mixer->chip->dev, + usb_rcvctrlpipe(mixer->chip->dev, 0), + (u8*)mixer->rc_setup_packet, mixer->rc_buffer, len, + snd_usb_soundblaster_remote_complete, mixer); + return 0; +} + +#define snd_audigy2nx_led_info snd_ctl_boolean_mono_info + +static int snd_audigy2nx_led_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value >> 8; + return 0; +} + +static int snd_audigy2nx_led_update(struct usb_mixer_interface *mixer, + int value, int index) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + if (chip->usb_id == USB_ID(0x041e, 0x3042)) + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); + /* USB X-Fi S51 Pro */ + if (chip->usb_id == USB_ID(0x041e, 0x30df)) + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + !value, 0, NULL, 0); + else + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x24, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + value, index + 2, NULL, 0); + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_audigy2nx_led_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct usb_mixer_interface *mixer = list->mixer; + int index = kcontrol->private_value & 0xff; + unsigned int value = ucontrol->value.integer.value[0]; + int old_value = kcontrol->private_value >> 8; + int err; + + if (value > 1) + return -EINVAL; + if (value == old_value) + return 0; + kcontrol->private_value = (value << 8) | index; + err = snd_audigy2nx_led_update(mixer, value, index); + return err < 0 ? err : 1; +} + +static int snd_audigy2nx_led_resume(struct usb_mixer_elem_list *list) +{ + int priv_value = list->kctl->private_value; + + return snd_audigy2nx_led_update(list->mixer, priv_value >> 8, + priv_value & 0xff); +} + +/* name and private_value are set dynamically */ +static const struct snd_kcontrol_new snd_audigy2nx_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_audigy2nx_led_info, + .get = snd_audigy2nx_led_get, + .put = snd_audigy2nx_led_put, +}; + +static const char * const snd_audigy2nx_led_names[] = { + "CMSS LED Switch", + "Power LED Switch", + "Dolby Digital LED Switch", +}; + +static int snd_audigy2nx_controls_create(struct usb_mixer_interface *mixer) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_audigy2nx_led_names); ++i) { + struct snd_kcontrol_new knew; + + /* USB X-Fi S51 doesn't have a CMSS LED */ + if ((mixer->chip->usb_id == USB_ID(0x041e, 0x3042)) && i == 0) + continue; + /* USB X-Fi S51 Pro doesn't have one either */ + if ((mixer->chip->usb_id == USB_ID(0x041e, 0x30df)) && i == 0) + continue; + if (i > 1 && /* Live24ext has 2 LEDs only */ + (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3042) || + mixer->chip->usb_id == USB_ID(0x041e, 0x30df) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048))) + break; + + knew = snd_audigy2nx_control; + knew.name = snd_audigy2nx_led_names[i]; + knew.private_value = (1 << 8) | i; /* LED on as default */ + err = add_single_ctl_with_resume(mixer, 0, + snd_audigy2nx_led_resume, + &knew, NULL); + if (err < 0) + return err; + } + return 0; +} + +static void snd_audigy2nx_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const struct sb_jack { + int unitid; + const char *name; + } jacks_audigy2nx[] = { + {4, "dig in "}, + {7, "line in"}, + {19, "spk out"}, + {20, "hph out"}, + {-1, NULL} + }, jacks_live24ext[] = { + {4, "line in"}, /* &1=Line, &2=Mic*/ + {3, "hph out"}, /* headphones */ + {0, "RC "}, /* last command, 6 bytes see rc_config above */ + {-1, NULL} + }; + const struct sb_jack *jacks; + struct usb_mixer_interface *mixer = entry->private_data; + int i, err; + u8 buf[3]; + + snd_iprintf(buffer, "%s jacks\n\n", mixer->chip->card->shortname); + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3020)) + jacks = jacks_audigy2nx; + else if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) + jacks = jacks_live24ext; + else + return; + + for (i = 0; jacks[i].name; ++i) { + snd_iprintf(buffer, "%s: ", jacks[i].name); + err = snd_usb_lock_shutdown(mixer->chip); + if (err < 0) + return; + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_rcvctrlpipe(mixer->chip->dev, 0), + UAC_GET_MEM, USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, 0, + jacks[i].unitid << 8, buf, 3); + snd_usb_unlock_shutdown(mixer->chip); + if (err == 3 && (buf[0] == 3 || buf[0] == 6)) + snd_iprintf(buffer, "%02x %02x\n", buf[1], buf[2]); + else + snd_iprintf(buffer, "?\n"); + } +} + +/* EMU0204 */ +static int snd_emu0204_ch_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = {"1/2", "3/4"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int snd_emu0204_ch_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = kcontrol->private_value; + return 0; +} + +static int snd_emu0204_ch_switch_update(struct usb_mixer_interface *mixer, + int value) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + unsigned char buf[2]; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + buf[0] = 0x01; + buf[1] = value ? 0x02 : 0x01; + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + 0x0400, 0x0e00, buf, 2); + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_emu0204_ch_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct usb_mixer_interface *mixer = list->mixer; + unsigned int value = ucontrol->value.enumerated.item[0]; + int err; + + if (value > 1) + return -EINVAL; + + if (value == kcontrol->private_value) + return 0; + + kcontrol->private_value = value; + err = snd_emu0204_ch_switch_update(mixer, value); + return err < 0 ? err : 1; +} + +static int snd_emu0204_ch_switch_resume(struct usb_mixer_elem_list *list) +{ + return snd_emu0204_ch_switch_update(list->mixer, + list->kctl->private_value); +} + +static struct snd_kcontrol_new snd_emu0204_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Front Jack Channels", + .info = snd_emu0204_ch_switch_info, + .get = snd_emu0204_ch_switch_get, + .put = snd_emu0204_ch_switch_put, + .private_value = 0, +}; + +static int snd_emu0204_controls_create(struct usb_mixer_interface *mixer) +{ + return add_single_ctl_with_resume(mixer, 0, + snd_emu0204_ch_switch_resume, + &snd_emu0204_control, NULL); +} + +/* ASUS Xonar U1 / U3 controls */ + +static int snd_xonar_u1_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = !!(kcontrol->private_value & 0x02); + return 0; +} + +static int snd_xonar_u1_switch_update(struct usb_mixer_interface *mixer, + unsigned char status) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x08, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 50, 0, &status, 1); + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_xonar_u1_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + u8 old_status, new_status; + int err; + + old_status = kcontrol->private_value; + if (ucontrol->value.integer.value[0]) + new_status = old_status | 0x02; + else + new_status = old_status & ~0x02; + if (new_status == old_status) + return 0; + + kcontrol->private_value = new_status; + err = snd_xonar_u1_switch_update(list->mixer, new_status); + return err < 0 ? err : 1; +} + +static int snd_xonar_u1_switch_resume(struct usb_mixer_elem_list *list) +{ + return snd_xonar_u1_switch_update(list->mixer, + list->kctl->private_value); +} + +static struct snd_kcontrol_new snd_xonar_u1_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_xonar_u1_switch_get, + .put = snd_xonar_u1_switch_put, + .private_value = 0x05, +}; + +static int snd_xonar_u1_controls_create(struct usb_mixer_interface *mixer) +{ + return add_single_ctl_with_resume(mixer, 0, + snd_xonar_u1_switch_resume, + &snd_xonar_u1_output_switch, NULL); +} + +/* Digidesign Mbox 1 clock source switch (internal/spdif) */ + +static int snd_mbox1_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = kctl->private_value; + return 0; +} + +static int snd_mbox1_switch_update(struct usb_mixer_interface *mixer, int val) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + unsigned char buff[3]; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + /* Prepare for magic command to toggle clock source */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_INTERFACE, 0x00, 0x500, buff, 1); + if (err < 0) + goto err; + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + if (err < 0) + goto err; + + /* 2 possibilities: Internal -> send sample rate + * S/PDIF sync -> send zeroes + * NB: Sample rate locked to 48kHz on purpose to + * prevent user from resetting the sample rate + * while S/PDIF sync is enabled and confusing + * this configuration. + */ + if (val == 0) { + buff[0] = 0x80; + buff[1] = 0xbb; + buff[2] = 0x00; + } else { + buff[0] = buff[1] = buff[2] = 0x00; + } + + /* Send the magic command to toggle the clock source */ + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), 0x1, + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + if (err < 0) + goto err; + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x81, buff, 3); + if (err < 0) + goto err; + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), 0x81, + USB_DIR_IN | + USB_TYPE_CLASS | + USB_RECIP_ENDPOINT, 0x100, 0x2, buff, 3); + if (err < 0) + goto err; + +err: + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_mbox1_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + int err; + bool cur_val, new_val; + + cur_val = kctl->private_value; + new_val = ucontrol->value.enumerated.item[0]; + if (cur_val == new_val) + return 0; + + kctl->private_value = new_val; + err = snd_mbox1_switch_update(mixer, new_val); + return err < 0 ? err : 1; +} + +static int snd_mbox1_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[2] = { + "Internal", + "S/PDIF" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int snd_mbox1_switch_resume(struct usb_mixer_elem_list *list) +{ + return snd_mbox1_switch_update(list->mixer, list->kctl->private_value); +} + +static struct snd_kcontrol_new snd_mbox1_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Source", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_mbox1_switch_info, + .get = snd_mbox1_switch_get, + .put = snd_mbox1_switch_put, + .private_value = 0 +}; + +static int snd_mbox1_create_sync_switch(struct usb_mixer_interface *mixer) +{ + return add_single_ctl_with_resume(mixer, 0, + snd_mbox1_switch_resume, + &snd_mbox1_switch, NULL); +} + +/* Native Instruments device quirks */ + +#define _MAKE_NI_CONTROL(bRequest,wIndex) ((bRequest) << 16 | (wIndex)) + +static int snd_ni_control_init_val(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl) +{ + struct usb_device *dev = mixer->chip->dev; + unsigned int pval = kctl->private_value; + u8 value; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + (pval >> 16) & 0xff, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0, pval & 0xffff, &value, 1); + if (err < 0) { + dev_err(&dev->dev, + "unable to issue vendor read request (ret = %d)", err); + return err; + } + + kctl->private_value |= ((unsigned int)value << 24); + return 0; +} + +static int snd_nativeinstruments_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value >> 24; + return 0; +} + +static int snd_ni_update_cur_val(struct usb_mixer_elem_list *list) +{ + struct snd_usb_audio *chip = list->mixer->chip; + unsigned int pval = list->kctl->private_value; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + err = usb_control_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + (pval >> 16) & 0xff, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + pval >> 24, pval & 0xffff, NULL, 0, 1000); + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_nativeinstruments_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + u8 oldval = (kcontrol->private_value >> 24) & 0xff; + u8 newval = ucontrol->value.integer.value[0]; + int err; + + if (oldval == newval) + return 0; + + kcontrol->private_value &= ~(0xff << 24); + kcontrol->private_value |= (unsigned int)newval << 24; + err = snd_ni_update_cur_val(list); + return err < 0 ? err : 1; +} + +static struct snd_kcontrol_new snd_nativeinstruments_ta6_mixers[] = { + { + .name = "Direct Thru Channel A", + .private_value = _MAKE_NI_CONTROL(0x01, 0x03), + }, + { + .name = "Direct Thru Channel B", + .private_value = _MAKE_NI_CONTROL(0x01, 0x05), + }, + { + .name = "Phono Input Channel A", + .private_value = _MAKE_NI_CONTROL(0x02, 0x03), + }, + { + .name = "Phono Input Channel B", + .private_value = _MAKE_NI_CONTROL(0x02, 0x05), + }, +}; + +static struct snd_kcontrol_new snd_nativeinstruments_ta10_mixers[] = { + { + .name = "Direct Thru Channel A", + .private_value = _MAKE_NI_CONTROL(0x01, 0x03), + }, + { + .name = "Direct Thru Channel B", + .private_value = _MAKE_NI_CONTROL(0x01, 0x05), + }, + { + .name = "Direct Thru Channel C", + .private_value = _MAKE_NI_CONTROL(0x01, 0x07), + }, + { + .name = "Direct Thru Channel D", + .private_value = _MAKE_NI_CONTROL(0x01, 0x09), + }, + { + .name = "Phono Input Channel A", + .private_value = _MAKE_NI_CONTROL(0x02, 0x03), + }, + { + .name = "Phono Input Channel B", + .private_value = _MAKE_NI_CONTROL(0x02, 0x05), + }, + { + .name = "Phono Input Channel C", + .private_value = _MAKE_NI_CONTROL(0x02, 0x07), + }, + { + .name = "Phono Input Channel D", + .private_value = _MAKE_NI_CONTROL(0x02, 0x09), + }, +}; + +static int snd_nativeinstruments_create_mixer(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *kc, + unsigned int count) +{ + int i, err = 0; + struct snd_kcontrol_new template = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .get = snd_nativeinstruments_control_get, + .put = snd_nativeinstruments_control_put, + .info = snd_ctl_boolean_mono_info, + }; + + for (i = 0; i < count; i++) { + struct usb_mixer_elem_list *list; + + template.name = kc[i].name; + template.private_value = kc[i].private_value; + + err = add_single_ctl_with_resume(mixer, 0, + snd_ni_update_cur_val, + &template, &list); + if (err < 0) + break; + snd_ni_control_init_val(mixer, list->kctl); + } + + return err; +} + +/* M-Audio FastTrack Ultra quirks */ +/* FTU Effect switch (also used by C400/C600) */ +static int snd_ftu_eff_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[8] = { + "Room 1", "Room 2", "Room 3", "Hall 1", + "Hall 2", "Plate", "Delay", "Echo" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int snd_ftu_eff_switch_init(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl) +{ + struct usb_device *dev = mixer->chip->dev; + unsigned int pval = kctl->private_value; + int err; + unsigned char value[2]; + + value[0] = 0x00; + value[1] = 0x00; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + pval & 0xff00, + snd_usb_ctrl_intf(mixer->chip) | ((pval & 0xff) << 8), + value, 2); + if (err < 0) + return err; + + kctl->private_value |= (unsigned int)value[0] << 24; + return 0; +} + +static int snd_ftu_eff_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = kctl->private_value >> 24; + return 0; +} + +static int snd_ftu_eff_switch_update(struct usb_mixer_elem_list *list) +{ + struct snd_usb_audio *chip = list->mixer->chip; + unsigned int pval = list->kctl->private_value; + unsigned char value[2]; + int err; + + value[0] = pval >> 24; + value[1] = 0; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + pval & 0xff00, + snd_usb_ctrl_intf(chip) | ((pval & 0xff) << 8), + value, 2); + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_ftu_eff_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + unsigned int pval = list->kctl->private_value; + int cur_val, err, new_val; + + cur_val = pval >> 24; + new_val = ucontrol->value.enumerated.item[0]; + if (cur_val == new_val) + return 0; + + kctl->private_value &= ~(0xff << 24); + kctl->private_value |= new_val << 24; + err = snd_ftu_eff_switch_update(list); + return err < 0 ? err : 1; +} + +static int snd_ftu_create_effect_switch(struct usb_mixer_interface *mixer, + int validx, int bUnitID) +{ + static struct snd_kcontrol_new template = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Effect Program Switch", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ftu_eff_switch_info, + .get = snd_ftu_eff_switch_get, + .put = snd_ftu_eff_switch_put + }; + struct usb_mixer_elem_list *list; + int err; + + err = add_single_ctl_with_resume(mixer, bUnitID, + snd_ftu_eff_switch_update, + &template, &list); + if (err < 0) + return err; + list->kctl->private_value = (validx << 8) | bUnitID; + snd_ftu_eff_switch_init(mixer, list->kctl); + return 0; +} + +/* Create volume controls for FTU devices*/ +static int snd_ftu_create_volume_ctls(struct usb_mixer_interface *mixer) +{ + char name[64]; + unsigned int control, cmask; + int in, out, err; + + const unsigned int id = 5; + const int val_type = USB_MIXER_S16; + + for (out = 0; out < 8; out++) { + control = out + 1; + for (in = 0; in < 8; in++) { + cmask = 1 << in; + snprintf(name, sizeof(name), + "AIn%d - Out%d Capture Volume", + in + 1, out + 1); + err = snd_create_std_mono_ctl(mixer, id, control, + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + for (in = 8; in < 16; in++) { + cmask = 1 << in; + snprintf(name, sizeof(name), + "DIn%d - Out%d Playback Volume", + in - 7, out + 1); + err = snd_create_std_mono_ctl(mixer, id, control, + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + } + + return 0; +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_ftu_create_effect_volume_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Volume"; + const unsigned int id = 6; + const int val_type = USB_MIXER_U8; + const unsigned int control = 2; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, snd_usb_mixer_vol_tlv); +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_ftu_create_effect_duration_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Duration"; + const unsigned int id = 6; + const int val_type = USB_MIXER_S16; + const unsigned int control = 3; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, snd_usb_mixer_vol_tlv); +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_ftu_create_effect_feedback_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Feedback Volume"; + const unsigned int id = 6; + const int val_type = USB_MIXER_U8; + const unsigned int control = 4; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, NULL); +} + +static int snd_ftu_create_effect_return_ctls(struct usb_mixer_interface *mixer) +{ + unsigned int cmask; + int err, ch; + char name[48]; + + const unsigned int id = 7; + const int val_type = USB_MIXER_S16; + const unsigned int control = 7; + + for (ch = 0; ch < 4; ++ch) { + cmask = 1 << ch; + snprintf(name, sizeof(name), + "Effect Return %d Volume", ch + 1); + err = snd_create_std_mono_ctl(mixer, id, control, + cmask, val_type, name, + snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + + return 0; +} + +static int snd_ftu_create_effect_send_ctls(struct usb_mixer_interface *mixer) +{ + unsigned int cmask; + int err, ch; + char name[48]; + + const unsigned int id = 5; + const int val_type = USB_MIXER_S16; + const unsigned int control = 9; + + for (ch = 0; ch < 8; ++ch) { + cmask = 1 << ch; + snprintf(name, sizeof(name), + "Effect Send AIn%d Volume", ch + 1); + err = snd_create_std_mono_ctl(mixer, id, control, cmask, + val_type, name, + snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + for (ch = 8; ch < 16; ++ch) { + cmask = 1 << ch; + snprintf(name, sizeof(name), + "Effect Send DIn%d Volume", ch - 7); + err = snd_create_std_mono_ctl(mixer, id, control, cmask, + val_type, name, + snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + return 0; +} + +static int snd_ftu_create_mixer(struct usb_mixer_interface *mixer) +{ + int err; + + err = snd_ftu_create_volume_ctls(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_switch(mixer, 1, 6); + if (err < 0) + return err; + + err = snd_ftu_create_effect_volume_ctl(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_duration_ctl(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_feedback_ctl(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_return_ctls(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_send_ctls(mixer); + if (err < 0) + return err; + + return 0; +} + +void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, + unsigned char samplerate_id) +{ + struct usb_mixer_interface *mixer; + struct usb_mixer_elem_info *cval; + int unitid = 12; /* SampleRate ExtensionUnit ID */ + + list_for_each_entry(mixer, &chip->mixer_list, list) { + if (mixer->id_elems[unitid]) { + cval = mixer_elem_list_to_info(mixer->id_elems[unitid]); + snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, + cval->control << 8, + samplerate_id); + snd_usb_mixer_notify_id(mixer, unitid); + break; + } + } +} + +/* M-Audio Fast Track C400/C600 */ +/* C400/C600 volume controls, this control needs a volume quirk, see mixer.c */ +static int snd_c400_create_vol_ctls(struct usb_mixer_interface *mixer) +{ + char name[64]; + unsigned int cmask, offset; + int out, chan, err; + int num_outs = 0; + int num_ins = 0; + + const unsigned int id = 0x40; + const int val_type = USB_MIXER_S16; + const int control = 1; + + switch (mixer->chip->usb_id) { + case USB_ID(0x0763, 0x2030): + num_outs = 6; + num_ins = 4; + break; + case USB_ID(0x0763, 0x2031): + num_outs = 8; + num_ins = 6; + break; + } + + for (chan = 0; chan < num_outs + num_ins; chan++) { + for (out = 0; out < num_outs; out++) { + if (chan < num_outs) { + snprintf(name, sizeof(name), + "PCM%d-Out%d Playback Volume", + chan + 1, out + 1); + } else { + snprintf(name, sizeof(name), + "In%d-Out%d Playback Volume", + chan - num_outs + 1, out + 1); + } + + cmask = (out == 0) ? 0 : 1 << (out - 1); + offset = chan * num_outs; + err = snd_create_std_mono_ctl_offset(mixer, id, control, + cmask, val_type, offset, name, + &snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + } + + return 0; +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_c400_create_effect_volume_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Volume"; + const unsigned int id = 0x43; + const int val_type = USB_MIXER_U8; + const unsigned int control = 3; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, snd_usb_mixer_vol_tlv); +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_c400_create_effect_duration_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Duration"; + const unsigned int id = 0x43; + const int val_type = USB_MIXER_S16; + const unsigned int control = 4; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, snd_usb_mixer_vol_tlv); +} + +/* This control needs a volume quirk, see mixer.c */ +static int snd_c400_create_effect_feedback_ctl(struct usb_mixer_interface *mixer) +{ + static const char name[] = "Effect Feedback Volume"; + const unsigned int id = 0x43; + const int val_type = USB_MIXER_U8; + const unsigned int control = 5; + const unsigned int cmask = 0; + + return snd_create_std_mono_ctl(mixer, id, control, cmask, val_type, + name, NULL); +} + +static int snd_c400_create_effect_vol_ctls(struct usb_mixer_interface *mixer) +{ + char name[64]; + unsigned int cmask; + int chan, err; + int num_outs = 0; + int num_ins = 0; + + const unsigned int id = 0x42; + const int val_type = USB_MIXER_S16; + const int control = 1; + + switch (mixer->chip->usb_id) { + case USB_ID(0x0763, 0x2030): + num_outs = 6; + num_ins = 4; + break; + case USB_ID(0x0763, 0x2031): + num_outs = 8; + num_ins = 6; + break; + } + + for (chan = 0; chan < num_outs + num_ins; chan++) { + if (chan < num_outs) { + snprintf(name, sizeof(name), + "Effect Send DOut%d", + chan + 1); + } else { + snprintf(name, sizeof(name), + "Effect Send AIn%d", + chan - num_outs + 1); + } + + cmask = (chan == 0) ? 0 : 1 << (chan - 1); + err = snd_create_std_mono_ctl(mixer, id, control, + cmask, val_type, name, + &snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + + return 0; +} + +static int snd_c400_create_effect_ret_vol_ctls(struct usb_mixer_interface *mixer) +{ + char name[64]; + unsigned int cmask; + int chan, err; + int num_outs = 0; + int offset = 0; + + const unsigned int id = 0x40; + const int val_type = USB_MIXER_S16; + const int control = 1; + + switch (mixer->chip->usb_id) { + case USB_ID(0x0763, 0x2030): + num_outs = 6; + offset = 0x3c; + /* { 0x3c, 0x43, 0x3e, 0x45, 0x40, 0x47 } */ + break; + case USB_ID(0x0763, 0x2031): + num_outs = 8; + offset = 0x70; + /* { 0x70, 0x79, 0x72, 0x7b, 0x74, 0x7d, 0x76, 0x7f } */ + break; + } + + for (chan = 0; chan < num_outs; chan++) { + snprintf(name, sizeof(name), + "Effect Return %d", + chan + 1); + + cmask = (chan == 0) ? 0 : + 1 << (chan + (chan % 2) * num_outs - 1); + err = snd_create_std_mono_ctl_offset(mixer, id, control, + cmask, val_type, offset, name, + &snd_usb_mixer_vol_tlv); + if (err < 0) + return err; + } + + return 0; +} + +static int snd_c400_create_mixer(struct usb_mixer_interface *mixer) +{ + int err; + + err = snd_c400_create_vol_ctls(mixer); + if (err < 0) + return err; + + err = snd_c400_create_effect_vol_ctls(mixer); + if (err < 0) + return err; + + err = snd_c400_create_effect_ret_vol_ctls(mixer); + if (err < 0) + return err; + + err = snd_ftu_create_effect_switch(mixer, 2, 0x43); + if (err < 0) + return err; + + err = snd_c400_create_effect_volume_ctl(mixer); + if (err < 0) + return err; + + err = snd_c400_create_effect_duration_ctl(mixer); + if (err < 0) + return err; + + err = snd_c400_create_effect_feedback_ctl(mixer); + if (err < 0) + return err; + + return 0; +} + +/* + * The mixer units for Ebox-44 are corrupt, and even where they + * are valid they presents mono controls as L and R channels of + * stereo. So we provide a good mixer here. + */ +static const struct std_mono_table ebox44_table[] = { + { + .unitid = 4, + .control = 1, + .cmask = 0x0, + .val_type = USB_MIXER_INV_BOOLEAN, + .name = "Headphone Playback Switch" + }, + { + .unitid = 4, + .control = 2, + .cmask = 0x1, + .val_type = USB_MIXER_S16, + .name = "Headphone A Mix Playback Volume" + }, + { + .unitid = 4, + .control = 2, + .cmask = 0x2, + .val_type = USB_MIXER_S16, + .name = "Headphone B Mix Playback Volume" + }, + + { + .unitid = 7, + .control = 1, + .cmask = 0x0, + .val_type = USB_MIXER_INV_BOOLEAN, + .name = "Output Playback Switch" + }, + { + .unitid = 7, + .control = 2, + .cmask = 0x1, + .val_type = USB_MIXER_S16, + .name = "Output A Playback Volume" + }, + { + .unitid = 7, + .control = 2, + .cmask = 0x2, + .val_type = USB_MIXER_S16, + .name = "Output B Playback Volume" + }, + + { + .unitid = 10, + .control = 1, + .cmask = 0x0, + .val_type = USB_MIXER_INV_BOOLEAN, + .name = "Input Capture Switch" + }, + { + .unitid = 10, + .control = 2, + .cmask = 0x1, + .val_type = USB_MIXER_S16, + .name = "Input A Capture Volume" + }, + { + .unitid = 10, + .control = 2, + .cmask = 0x2, + .val_type = USB_MIXER_S16, + .name = "Input B Capture Volume" + }, + + {} +}; + +/* Audio Advantage Micro II findings: + * + * Mapping spdif AES bits to vendor register.bit: + * AES0: [0 0 0 0 2.3 2.2 2.1 2.0] - default 0x00 + * AES1: [3.3 3.2.3.1.3.0 2.7 2.6 2.5 2.4] - default: 0x01 + * AES2: [0 0 0 0 0 0 0 0] + * AES3: [0 0 0 0 0 0 x 0] - 'x' bit is set basing on standard usb request + * (UAC_EP_CS_ATTR_SAMPLE_RATE) for Audio Devices + * + * power on values: + * r2: 0x10 + * r3: 0x20 (b7 is zeroed just before playback (except IEC61937) and set + * just after it to 0xa0, presumably it disables/mutes some analog + * parts when there is no audio.) + * r9: 0x28 + * + * Optical transmitter on/off: + * vendor register.bit: 9.1 + * 0 - on (0x28 register value) + * 1 - off (0x2a register value) + * + */ +static int snd_microii_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_microii_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + struct snd_usb_audio *chip = list->mixer->chip; + int err; + struct usb_interface *iface; + struct usb_host_interface *alts; + unsigned int ep; + unsigned char data[3]; + int rate; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + ucontrol->value.iec958.status[0] = kcontrol->private_value & 0xff; + ucontrol->value.iec958.status[1] = (kcontrol->private_value >> 8) & 0xff; + ucontrol->value.iec958.status[2] = 0x00; + + /* use known values for that card: interface#1 altsetting#1 */ + iface = usb_ifnum_to_if(chip->dev, 1); + if (!iface || iface->num_altsetting < 2) { + err = -EINVAL; + goto end; + } + alts = &iface->altsetting[1]; + if (get_iface_desc(alts)->bNumEndpoints < 1) { + err = -EINVAL; + goto end; + } + ep = get_endpoint(alts, 0)->bEndpointAddress; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC_GET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, + ep, + data, + sizeof(data)); + if (err < 0) + goto end; + + rate = data[0] | (data[1] << 8) | (data[2] << 16); + ucontrol->value.iec958.status[3] = (rate == 48000) ? + IEC958_AES3_CON_FS_48000 : IEC958_AES3_CON_FS_44100; + + err = 0; + end: + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_microii_spdif_default_update(struct usb_mixer_elem_list *list) +{ + struct snd_usb_audio *chip = list->mixer->chip; + unsigned int pval = list->kctl->private_value; + u8 reg; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + reg = ((pval >> 4) & 0xf0) | (pval & 0x0f); + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 2, + NULL, + 0); + if (err < 0) + goto end; + + reg = (pval & IEC958_AES0_NONAUDIO) ? 0xa0 : 0x20; + reg |= (pval >> 12) & 0x0f; + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 3, + NULL, + 0); + if (err < 0) + goto end; + + end: + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + unsigned int pval, pval_old; + int err; + + pval = pval_old = kcontrol->private_value; + pval &= 0xfffff0f0; + pval |= (ucontrol->value.iec958.status[1] & 0x0f) << 8; + pval |= (ucontrol->value.iec958.status[0] & 0x0f); + + pval &= 0xffff0fff; + pval |= (ucontrol->value.iec958.status[1] & 0xf0) << 8; + + /* The frequency bits in AES3 cannot be set via register access. */ + + /* Silently ignore any bits from the request that cannot be set. */ + + if (pval == pval_old) + return 0; + + kcontrol->private_value = pval; + err = snd_microii_spdif_default_update(list); + return err < 0 ? err : 1; +} + +static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0x0f; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0x00; + ucontrol->value.iec958.status[3] = 0x00; + + return 0; +} + +static int snd_microii_spdif_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = !(kcontrol->private_value & 0x02); + + return 0; +} + +static int snd_microii_spdif_switch_update(struct usb_mixer_elem_list *list) +{ + struct snd_usb_audio *chip = list->mixer->chip; + u8 reg = list->kctl->private_value; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + reg, + 9, + NULL, + 0); + + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_microii_spdif_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + u8 reg; + int err; + + reg = ucontrol->value.integer.value[0] ? 0x28 : 0x2a; + if (reg != list->kctl->private_value) + return 0; + + kcontrol->private_value = reg; + err = snd_microii_spdif_switch_update(list); + return err < 0 ? err : 1; +} + +static struct snd_kcontrol_new snd_microii_mixer_spdif[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = snd_microii_spdif_info, + .get = snd_microii_spdif_default_get, + .put = snd_microii_spdif_default_put, + .private_value = 0x00000100UL,/* reset value */ + }, + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = snd_microii_spdif_info, + .get = snd_microii_spdif_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + .info = snd_ctl_boolean_mono_info, + .get = snd_microii_spdif_switch_get, + .put = snd_microii_spdif_switch_put, + .private_value = 0x00000028UL,/* reset value */ + } +}; + +static int snd_microii_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i; + static const usb_mixer_elem_resume_func_t resume_funcs[] = { + snd_microii_spdif_default_update, + NULL, + snd_microii_spdif_switch_update + }; + + for (i = 0; i < ARRAY_SIZE(snd_microii_mixer_spdif); ++i) { + err = add_single_ctl_with_resume(mixer, 0, + resume_funcs[i], + &snd_microii_mixer_spdif[i], + NULL); + if (err < 0) + return err; + } + + return 0; +} + +/* Creative Sound Blaster E1 */ + +static int snd_soundblaster_e1_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +static int snd_soundblaster_e1_switch_update(struct usb_mixer_interface *mixer, + unsigned char state) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + unsigned char buff[2]; + + buff[0] = 0x02; + buff[1] = state ? 0x02 : 0x00; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), HID_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x0202, 3, buff, 2); + snd_usb_unlock_shutdown(chip); + return err; +} + +static int snd_soundblaster_e1_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + unsigned char value = !!ucontrol->value.integer.value[0]; + int err; + + if (kcontrol->private_value == value) + return 0; + kcontrol->private_value = value; + err = snd_soundblaster_e1_switch_update(list->mixer, value); + return err < 0 ? err : 1; +} + +static int snd_soundblaster_e1_switch_resume(struct usb_mixer_elem_list *list) +{ + return snd_soundblaster_e1_switch_update(list->mixer, + list->kctl->private_value); +} + +static int snd_soundblaster_e1_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char *const texts[2] = { + "Mic", "Aux" + }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static struct snd_kcontrol_new snd_soundblaster_e1_input_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .info = snd_soundblaster_e1_switch_info, + .get = snd_soundblaster_e1_switch_get, + .put = snd_soundblaster_e1_switch_put, + .private_value = 0, +}; + +static int snd_soundblaster_e1_switch_create(struct usb_mixer_interface *mixer) +{ + return add_single_ctl_with_resume(mixer, 0, + snd_soundblaster_e1_switch_resume, + &snd_soundblaster_e1_input_switch, + NULL); +} + +static void dell_dock_init_vol(struct snd_usb_audio *chip, int ch, int id) +{ + u16 buf = 0; + + snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + ch, snd_usb_ctrl_intf(chip) | (id << 8), + &buf, 2); +} + +static int dell_dock_mixer_init(struct usb_mixer_interface *mixer) +{ + /* fix to 0dB playback volumes */ + dell_dock_init_vol(mixer->chip, 1, 16); + dell_dock_init_vol(mixer->chip, 2, 16); + dell_dock_init_vol(mixer->chip, 1, 19); + dell_dock_init_vol(mixer->chip, 2, 19); + return 0; +} + +int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) +{ + int err = 0; + struct snd_info_entry *entry; + + err = snd_usb_soundblaster_remote_init(mixer); + if (err < 0) + return err; + + switch (mixer->chip->usb_id) { + /* Tascam US-16x08 */ + case USB_ID(0x0644, 0x8047): + err = snd_us16x08_controls_create(mixer); + break; + case USB_ID(0x041e, 0x3020): + case USB_ID(0x041e, 0x3040): + case USB_ID(0x041e, 0x3042): + case USB_ID(0x041e, 0x30df): + case USB_ID(0x041e, 0x3048): + err = snd_audigy2nx_controls_create(mixer); + if (err < 0) + break; + if (!snd_card_proc_new(mixer->chip->card, "audigy2nx", &entry)) + snd_info_set_text_ops(entry, mixer, + snd_audigy2nx_proc_read); + break; + + /* EMU0204 */ + case USB_ID(0x041e, 0x3f19): + err = snd_emu0204_controls_create(mixer); + break; + + case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ + case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C400 */ + err = snd_c400_create_mixer(mixer); + break; + + case USB_ID(0x0763, 0x2080): /* M-Audio Fast Track Ultra */ + case USB_ID(0x0763, 0x2081): /* M-Audio Fast Track Ultra 8R */ + err = snd_ftu_create_mixer(mixer); + break; + + case USB_ID(0x0b05, 0x1739): /* ASUS Xonar U1 */ + case USB_ID(0x0b05, 0x1743): /* ASUS Xonar U1 (2) */ + case USB_ID(0x0b05, 0x17a0): /* ASUS Xonar U3 */ + err = snd_xonar_u1_controls_create(mixer); + break; + + case USB_ID(0x0d8c, 0x0103): /* Audio Advantage Micro II */ + err = snd_microii_controls_create(mixer); + break; + + case USB_ID(0x0dba, 0x1000): /* Digidesign Mbox 1 */ + err = snd_mbox1_create_sync_switch(mixer); + break; + + case USB_ID(0x17cc, 0x1011): /* Traktor Audio 6 */ + err = snd_nativeinstruments_create_mixer(mixer, + snd_nativeinstruments_ta6_mixers, + ARRAY_SIZE(snd_nativeinstruments_ta6_mixers)); + break; + + case USB_ID(0x17cc, 0x1021): /* Traktor Audio 10 */ + err = snd_nativeinstruments_create_mixer(mixer, + snd_nativeinstruments_ta10_mixers, + ARRAY_SIZE(snd_nativeinstruments_ta10_mixers)); + break; + + case USB_ID(0x200c, 0x1018): /* Electrix Ebox-44 */ + /* detection is disabled in mixer_maps.c */ + err = snd_create_std_mono_table(mixer, ebox44_table); + break; + + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + err = snd_scarlett_controls_create(mixer); + break; + + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ + err = snd_soundblaster_e1_switch_create(mixer); + break; + case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ + err = dell_dock_mixer_init(mixer); + break; + } + + return err; +} + +#ifdef CONFIG_PM +void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) +{ + switch (mixer->chip->usb_id) { + case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ + dell_dock_mixer_init(mixer); + break; + } +} +#endif + +void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, + int unitid) +{ + if (!mixer->rc_cfg) + return; + /* unit ids specific to Extigy/Audigy 2 NX: */ + switch (unitid) { + case 0: /* remote control */ + mixer->rc_urb->dev = mixer->chip->dev; + usb_submit_urb(mixer->rc_urb, GFP_ATOMIC); + break; + case 4: /* digital in jack */ + case 7: /* line in jacks */ + case 19: /* speaker out jacks */ + case 20: /* headphones out jack */ + break; + /* live24ext: 4 = line-in jack */ + case 3: /* hp-out jack (may actuate Mute) */ + if (mixer->chip->usb_id == USB_ID(0x041e, 0x3040) || + mixer->chip->usb_id == USB_ID(0x041e, 0x3048)) + snd_usb_mixer_notify_id(mixer, mixer->rc_cfg->mute_mixer_id); + break; + default: + usb_audio_dbg(mixer->chip, "memory change in unknown unit %d\n", unitid); + break; + } +} + +static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + /* Approximation using 10 ranges based on output measurement on hw v1.2. + * This seems close to the cubic mapping e.g. alsamixer uses. */ + static const DECLARE_TLV_DB_RANGE(scale, + 0, 1, TLV_DB_MINMAX_ITEM(-5300, -4970), + 2, 5, TLV_DB_MINMAX_ITEM(-4710, -4160), + 6, 7, TLV_DB_MINMAX_ITEM(-3884, -3710), + 8, 14, TLV_DB_MINMAX_ITEM(-3443, -2560), + 15, 16, TLV_DB_MINMAX_ITEM(-2475, -2324), + 17, 19, TLV_DB_MINMAX_ITEM(-2228, -2031), + 20, 26, TLV_DB_MINMAX_ITEM(-1910, -1393), + 27, 31, TLV_DB_MINMAX_ITEM(-1322, -1032), + 32, 40, TLV_DB_MINMAX_ITEM(-968, -490), + 41, 50, TLV_DB_MINMAX_ITEM(-441, 0), + ); + + if (cval->min == 0 && cval->max == 50) { + usb_audio_info(mixer->chip, "applying DragonFly dB scale quirk (0-50 variant)\n"); + kctl->tlv.p = scale; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + + } else if (cval->min == 0 && cval->max <= 1000) { + /* Some other clearly broken DragonFly variant. + * At least a 0..53 variant (hw v1.0) exists. + */ + usb_audio_info(mixer->chip, "ignoring too narrow dB range on a DragonFly device"); + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + } +} + +void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, int unitid, + struct snd_kcontrol *kctl) +{ + switch (mixer->chip->usb_id) { + case USB_ID(0x21b4, 0x0081): /* AudioQuest DragonFly */ + if (unitid == 7 && cval->control == UAC_FU_VOLUME) + snd_dragonfly_quirk_db_scale(mixer, cval, kctl); + break; + /* lowest playback value is muted on some devices */ + case USB_ID(0x0d8c, 0x000c): /* C-Media */ + case USB_ID(0x0d8c, 0x0014): /* C-Media */ + case USB_ID(0x19f7, 0x0003): /* RODE NT-USB */ + if (strstr(kctl->id.name, "Playback")) + cval->min_mute = 1; + break; + } +} + diff --git a/sound/usb/mixer_quirks.h b/sound/usb/mixer_quirks.h new file mode 100644 index 000000000..52be26db5 --- /dev/null +++ b/sound/usb/mixer_quirks.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SND_USB_MIXER_QUIRKS_H +#define SND_USB_MIXER_QUIRKS_H + +int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer); + +void snd_emuusb_set_samplerate(struct snd_usb_audio *chip, + unsigned char samplerate_id); + +void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, + int unitid); + +void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, int unitid, + struct snd_kcontrol *kctl); + +#ifdef CONFIG_PM +void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer); +#endif + +#endif /* SND_USB_MIXER_QUIRKS_H */ + diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 000000000..2e93c0b2e --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,1002 @@ +/* + * Scarlett Driver for ALSA + * + * Copyright (c) 2013 by Tobias Hoffmann + * Copyright (c) 2013 by Robin Gareus + * Copyright (c) 2002 by Takashi Iwai + * Copyright (c) 2014 by Chris J Arges + * + * Many codes borrowed from audio.c by + * Alan Cox (alan at lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer at ife.ee.ethz.ch) + * + * Code cleanup: + * David Henningsson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * Auto-detection via UAC2 is not feasible to properly discover the vast + * majority of features. It's related to both Linux/ALSA's UAC2 as well as + * Focusrite's implementation of it. Eventually quirks may be sufficient but + * right now it's a major headache to work arount these things. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - automatic re-initialization on connect if device was power-cycled + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * \--------------/ | | | | \--------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ \-->| ALSA PCM in | + * \--------------/ \--------------/ + * + * + */ + +#include +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 +#define SND_SCARLETT_MATRIX_IN_MAX 18 +#define SND_SCARLETT_CONTROLS_MAX 10 +#define SND_SCARLETT_OFFSETS_MAX 5 + +enum { + SCARLETT_OUTPUTS, + SCARLETT_SWITCH_IMPEDANCE, + SCARLETT_SWITCH_PAD, +}; + +enum { + SCARLETT_OFFSET_PCM = 0, + SCARLETT_OFFSET_ANALOG = 1, + SCARLETT_OFFSET_SPDIF = 2, + SCARLETT_OFFSET_ADAT = 3, + SCARLETT_OFFSET_MIX = 4, +}; + +struct scarlett_mixer_elem_enum_info { + int start; + int len; + int offsets[SND_SCARLETT_OFFSETS_MAX]; + char const * const *names; +}; + +struct scarlett_mixer_control { + unsigned char num; + unsigned char type; + const char *name; +}; + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + struct scarlett_mixer_elem_enum_info opt_master; + struct scarlett_mixer_elem_enum_info opt_matrix; + + /* initial values for matrix mux */ + int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX]; + + int num_controls; /* number of items in controls */ + const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX]; +}; + +/********************** Enum Strings *************************/ + +static const struct scarlett_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .offsets = {}, + .names = (char const * const []){ + "0dB", "-10dB" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .offsets = {}, + .names = (char const * const []){ + "Line", "Hi-Z" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .offsets = {}, + .names = (char const * const []){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .offsets = {}, + .names = (char const * const []){ + "No Lock", "Locked" + } +}; + +static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = !val; /* invert mute logic for mixer */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_resume(struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); + int i; + + for (i = 0; i < elem->channels; i++) + if (elem->cached & (1 << i)) + snd_usb_set_cur_mix_value(elem, i, i, + elem->cache_val[i]); + return 0; +} + +static int scarlett_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = (int)kctl->private_value + + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static void scarlett_generate_name(int i, char *dst, int offsets[]) +{ + if (i > offsets[SCARLETT_OFFSET_MIX]) + sprintf(dst, "Mix %c", + 'A'+(i - offsets[SCARLETT_OFFSET_MIX] - 1)); + else if (i > offsets[SCARLETT_OFFSET_ADAT]) + sprintf(dst, "ADAT %d", i - offsets[SCARLETT_OFFSET_ADAT]); + else if (i > offsets[SCARLETT_OFFSET_SPDIF]) + sprintf(dst, "SPDIF %d", i - offsets[SCARLETT_OFFSET_SPDIF]); + else if (i > offsets[SCARLETT_OFFSET_ANALOG]) + sprintf(dst, "Analog %d", i - offsets[SCARLETT_OFFSET_ANALOG]); + else if (i > offsets[SCARLETT_OFFSET_PCM]) + sprintf(dst, "PCM %d", i - offsets[SCARLETT_OFFSET_PCM]); + else + sprintf(dst, "Off"); +} + +static int scarlett_ctl_enum_dynamic_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + unsigned int items = opt->len; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = elem->channels; + uinfo->value.enumerated.items = items; + + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + + /* generate name dynamically based on item number and offset info */ + scarlett_generate_name(uinfo->value.enumerated.item, + uinfo->value.enumerated.name, + opt->offsets); + + return 0; +} + +static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + + return snd_ctl_enum_info(uinfo, elem->channels, opt->len, + (const char * const *)opt->names); +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &val); + if (err < 0) + return err; + + val = clamp(val - opt->start, 0, opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + opt->start; + if (val != oval) { + snd_usb_set_cur_mix_value(elem, 0, 0, val); + return 1; + } + return 0; +} + +static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list) +{ + struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); + + if (elem->cached) + snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val); + return 0; +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + unsigned char buf[2 * MAX_CHANNELS] = {0, }; + int wValue = (elem->control << 8) | elem->idx_off; + int idx = snd_usb_ctrl_intf(chip) | (elem->head.id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + return 0; +} + +static const struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static const struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static const struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static const struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static const struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_dynamic_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static const struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + usb_mixer_elem_resume_func_t resume, + int index, int offset, int num, + int val_type, int channels, const char *name, + const struct scarlett_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->head.resume = resume; + elem->control = offset; + elem->idx_off = num; + elem->head.id = index; + elem->val_type = val_type; + + elem->channels = channels; + + /* add scarlett_mixer_elem_enum_info struct */ + elem->private_data = (void *)opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + + err = snd_usb_mixer_add_control(&elem->head, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, + scarlett_ctl_resume, 0x0a, 0x01, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add volume control and initialize to 0 */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, + scarlett_ctl_resume, 0x0a, 0x02, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, + scarlett_ctl_enum_resume, 0x33, 0x00, + 2*index, USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, + scarlett_ctl_enum_resume, 0x33, 0x00, + 2*index+1, USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + + return 0; +} + +/********************** device-specific config *************************/ + +/* untested... */ +static const struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .opt_master = { + .start = -1, + .len = 27, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .num_controls = 9, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* untested... */ +static const struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .opt_master = { + .start = -1, + .len = 25, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .num_controls = 7, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static const struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .opt_master = { + .start = -1, + .len = 31, + .offsets = {0, 6, 14, 16, 24}, + .names = NULL, + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .offsets = {0, 6, 14, 16, 24}, + .names = NULL, + }, + + .num_controls = 5, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + }, + + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .opt_master = { + .start = -1, + .len = 35, + .offsets = {0, 8, 16, 18, 26}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .offsets = {0, 8, 16, 18, 26}, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .opt_master = { + .start = -1, + .len = 47, + .offsets = {0, 20, 28, 30, 38}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .offsets = {0, 20, 28, 30, 38}, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" }, + { .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" }, + { .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" }, + { .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" }, + { .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" }, + { .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" }, + /*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/ + }, + + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + + +static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + int i, err; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const struct scarlett_mixer_control *ctl; + struct usb_mixer_elem_info *elem; + + /* create master switch and playback volume */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, + scarlett_ctl_resume, 0x0a, 0x01, 0, + USB_MIXER_S16, 1, "Master Playback Switch", NULL, + &elem); + if (err < 0) + return err; + + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, + scarlett_ctl_resume, 0x0a, 0x02, 0, + USB_MIXER_S16, 1, "Master Playback Volume", NULL, + &elem); + if (err < 0) + return err; + + /* iterate through controls in info struct and create each one */ + for (i = 0; i < info->num_controls; i++) { + ctl = &info->controls[i]; + + switch (ctl->type) { + case SCARLETT_OUTPUTS: + err = add_output_ctls(mixer, ctl->num, ctl->name, info); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_IMPEDANCE: + sprintf(mx, "Input %d Impedance Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, + scarlett_ctl_enum_resume, 0x01, + 0x09, ctl->num, USB_MIXER_S16, 1, mx, + &opt_impedance, &elem); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_PAD: + sprintf(mx, "Input %d Pad Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, + scarlett_ctl_enum_resume, 0x01, + 0x0b, ctl->num, USB_MIXER_S16, 1, mx, + &opt_pad, &elem); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + /* generic function to create controls */ + err = scarlett_controls_create_generic(mixer, info); + if (err < 0) + return err; + + /* setup matrix controls */ + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, + scarlett_ctl_enum_resume, 0x32, + 0x06, i, USB_MIXER_S16, 1, mx, + &info->opt_matrix, &elem); + if (err < 0) + return err; + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + err = add_new_ctl(mixer, &usb_scarlett_ctl, + scarlett_ctl_resume, 0x3c, 0x00, + (i << 3) + (o & 0x07), USB_MIXER_S16, + 1, mx, NULL, &elem); + if (err < 0) + return err; + + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, sizeof(mx), "Input Source %02d Capture Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, + scarlett_ctl_enum_resume, 0x34, + 0x00, i, USB_MIXER_S16, 1, mx, + &info->opt_master, &elem); + if (err < 0) + return err; + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, + scarlett_ctl_enum_resume, 0x28, 0x01, 0, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, NULL, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + err = snd_usb_ctl_msg(mixer->chip->dev, + usb_sndctrlpipe(mixer->chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_OUT, 0x0100, snd_usb_ctrl_intf(mixer->chip) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return err; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 000000000..bbf063b79 --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */ diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c new file mode 100644 index 000000000..7db3032e7 --- /dev/null +++ b/sound/usb/mixer_us16x08.c @@ -0,0 +1,1424 @@ +/* + * Tascam US-16x08 ALSA driver + * + * Copyright (c) 2016 by Detlef Urban (onkel@paraair.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" + +#include "mixer_us16x08.h" + +/* USB control message templates */ +static const char route_msg[] = { + 0x61, + 0x02, + 0x03, /* input from master (0x02) or input from computer bus (0x03) */ + 0x62, + 0x02, + 0x01, /* input index (0x01/0x02 eq. left/right) or bus (0x01-0x08) */ + 0x41, + 0x01, + 0x61, + 0x02, + 0x01, + 0x62, + 0x02, + 0x01, /* output index (0x01-0x08) */ + 0x42, + 0x01, + 0x43, + 0x01, + 0x00, + 0x00 +}; + +static const char mix_init_msg1[] = { + 0x71, 0x01, 0x00, 0x00 +}; + +static const char mix_init_msg2[] = { + 0x62, 0x02, 0x00, 0x61, 0x02, 0x04, 0xb1, 0x01, 0x00, 0x00 +}; + +static const char mix_msg_in[] = { + /* default message head, equal to all mixers */ + 0x61, 0x02, 0x04, 0x62, 0x02, 0x01, + 0x81, /* 0x06: Controller ID */ + 0x02, /* 0x07: */ + 0x00, /* 0x08: Value of common mixer */ + 0x00, + 0x00 +}; + +static const char mix_msg_out[] = { + /* default message head, equal to all mixers */ + 0x61, 0x02, 0x02, 0x62, 0x02, 0x01, + 0x81, /* 0x06: Controller ID */ + 0x02, /* 0x07: */ + 0x00, /* 0x08: Value of common mixer */ + 0x00, + 0x00 +}; + +static const char bypass_msg_out[] = { + 0x45, + 0x02, + 0x01, /* on/off flag */ + 0x00, + 0x00 +}; + +static const char bus_msg_out[] = { + 0x44, + 0x02, + 0x01, /* on/off flag */ + 0x00, + 0x00 +}; + +static const char comp_msg[] = { + /* default message head, equal to all mixers */ + 0x61, 0x02, 0x04, 0x62, 0x02, 0x01, + 0x91, + 0x02, + 0xf0, /* 0x08: Threshold db (8) (e0 ... 00) (+-0dB -- -32dB) x-32 */ + 0x92, + 0x02, + 0x0a, /* 0x0b: Ratio (0a,0b,0d,0f,11,14,19,1e,23,28,32,3c,50,a0,ff) */ + 0x93, + 0x02, + 0x02, /* 0x0e: Attack (0x02 ... 0xc0) (2ms ... 200ms) */ + 0x94, + 0x02, + 0x01, /* 0x11: Release (0x01 ... 0x64) (10ms ... 1000ms) x*10 */ + 0x95, + 0x02, + 0x03, /* 0x14: gain (0 ... 20) (0dB .. 20dB) */ + 0x96, + 0x02, + 0x01, + 0x97, + 0x02, + 0x01, /* 0x1a: main Comp switch (0 ... 1) (off ... on)) */ + 0x00, + 0x00 +}; + +static const char eqs_msq[] = { + /* default message head, equal to all mixers */ + 0x61, 0x02, 0x04, 0x62, 0x02, 0x01, + 0x51, /* 0x06: Controller ID */ + 0x02, + 0x04, /* 0x08: EQ set num (0x01..0x04) (LOW, LOWMID, HIGHMID, HIGH)) */ + 0x52, + 0x02, + 0x0c, /* 0x0b: value dB (0 ... 12) (-12db .. +12db) x-6 */ + 0x53, + 0x02, + 0x0f, /* 0x0e: value freq (32-47) (1.7kHz..18kHz) */ + 0x54, + 0x02, + 0x02, /* 0x11: band width (0-6) (Q16-Q0.25) 2^x/4 (EQ xxMID only) */ + 0x55, + 0x02, + 0x01, /* 0x14: main EQ switch (0 ... 1) (off ... on)) */ + 0x00, + 0x00 +}; + +/* compressor ratio map */ +static const char ratio_map[] = { + 0x0a, 0x0b, 0x0d, 0x0f, 0x11, 0x14, 0x19, 0x1e, + 0x23, 0x28, 0x32, 0x3c, 0x50, 0xa0, 0xff +}; + +/* route enumeration names */ +static const char *const route_names[] = { + "Master Left", "Master Right", "Output 1", "Output 2", "Output 3", + "Output 4", "Output 5", "Output 6", "Output 7", "Output 8", +}; + +static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + unsigned char *buf, int size) +{ + + mutex_lock(&chip->mutex); + snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + SND_US16X08_URB_METER_REQUEST, + SND_US16X08_URB_METER_REQUESTTYPE, 0, 0, buf, size); + mutex_unlock(&chip->mutex); + return 0; +} + +/* wrapper function to send prepared URB buffer to usb device. Return an error + * code if something went wrong + */ +static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size) +{ + return snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE, + 0, 0, buf, size); +} + +static int snd_us16x08_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, 10, route_names); +} + +static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + int index = ucontrol->id.index; + + /* route has no bias */ + ucontrol->value.enumerated.item[0] = elem->cache_val[index]; + + return 0; +} + +static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + int index = ucontrol->id.index; + char buf[sizeof(route_msg)]; + int val, val_org, err; + + /* get the new value (no bias for routes) */ + val = ucontrol->value.enumerated.item[0]; + + /* sanity check */ + if (val < 0 || val > 9) + return -EINVAL; + + /* prepare the message buffer from template */ + memcpy(buf, route_msg, sizeof(route_msg)); + + if (val < 2) { + /* input comes from a master channel */ + val_org = val; + buf[2] = 0x02; + } else { + /* input comes from a computer channel */ + buf[2] = 0x03; + val_org = val - 2; + } + + /* place new route selection in URB message */ + buf[5] = (unsigned char) (val_org & 0x0f) + 1; + /* place route selector in URB message */ + buf[13] = index + 1; + + err = snd_us16x08_send_urb(chip, buf, sizeof(route_msg)); + + if (err > 0) { + elem->cached |= 1 << index; + elem->cache_val[index] = val; + } else { + usb_audio_dbg(chip, "Failed to set routing, err:%d\n", err); + } + + return err > 0 ? 1 : 0; +} + +static int snd_us16x08_master_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->count = 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.max = SND_US16X08_KCMAX(kcontrol); + uinfo->value.integer.min = SND_US16X08_KCMIN(kcontrol); + uinfo->value.integer.step = SND_US16X08_KCSTEP(kcontrol); + return 0; +} + +static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + int index = ucontrol->id.index; + + ucontrol->value.integer.value[0] = elem->cache_val[index]; + + return 0; +} + +static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + char buf[sizeof(mix_msg_out)]; + int val, err; + int index = ucontrol->id.index; + + /* new control value incl. bias*/ + val = ucontrol->value.integer.value[0]; + + /* sanity check */ + if (val < SND_US16X08_KCMIN(kcontrol) + || val > SND_US16X08_KCMAX(kcontrol)) + return -EINVAL; + + /* prepare the message buffer from template */ + memcpy(buf, mix_msg_out, sizeof(mix_msg_out)); + + buf[8] = val - SND_US16X08_KCBIAS(kcontrol); + buf[6] = elem->head.id; + + /* place channel selector in URB message */ + buf[5] = index + 1; + err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_out)); + + if (err > 0) { + elem->cached |= 1 << index; + elem->cache_val[index] = val; + } else { + usb_audio_dbg(chip, "Failed to set master, err:%d\n", err); + } + + return err > 0 ? 1 : 0; +} + +static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + char buf[sizeof(mix_msg_out)]; + int val, err = 0; + + val = ucontrol->value.integer.value[0]; + + /* prepare the message buffer from template */ + switch (elem->head.id) { + case SND_US16X08_ID_BYPASS: + memcpy(buf, bypass_msg_out, sizeof(bypass_msg_out)); + buf[2] = val; + err = snd_us16x08_send_urb(chip, buf, sizeof(bypass_msg_out)); + break; + case SND_US16X08_ID_BUSS_OUT: + memcpy(buf, bus_msg_out, sizeof(bus_msg_out)); + buf[2] = val; + err = snd_us16x08_send_urb(chip, buf, sizeof(bus_msg_out)); + break; + case SND_US16X08_ID_MUTE: + memcpy(buf, mix_msg_out, sizeof(mix_msg_out)); + buf[8] = val; + buf[6] = elem->head.id; + buf[5] = 1; + err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_out)); + break; + } + + if (err > 0) { + elem->cached |= 1; + elem->cache_val[0] = val; + } else { + usb_audio_dbg(chip, "Failed to set buss param, err:%d\n", err); + } + + return err > 0 ? 1 : 0; +} + +static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + + switch (elem->head.id) { + case SND_US16X08_ID_BUSS_OUT: + ucontrol->value.integer.value[0] = elem->cache_val[0]; + break; + case SND_US16X08_ID_BYPASS: + ucontrol->value.integer.value[0] = elem->cache_val[0]; + break; + case SND_US16X08_ID_MUTE: + ucontrol->value.integer.value[0] = elem->cache_val[0]; + break; + } + + return 0; +} + +/* gets a current mixer value from common store */ +static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + int index = ucontrol->id.index; + + ucontrol->value.integer.value[0] = elem->cache_val[index]; + + return 0; +} + +static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + char buf[sizeof(mix_msg_in)]; + int val, err; + int index = ucontrol->id.index; + + val = ucontrol->value.integer.value[0]; + + /* sanity check */ + if (val < SND_US16X08_KCMIN(kcontrol) + || val > SND_US16X08_KCMAX(kcontrol)) + return -EINVAL; + + /* prepare URB message from template */ + memcpy(buf, mix_msg_in, sizeof(mix_msg_in)); + + /* add the bias to the new value */ + buf[8] = val - SND_US16X08_KCBIAS(kcontrol); + buf[6] = elem->head.id; + buf[5] = index + 1; + + err = snd_us16x08_send_urb(chip, buf, sizeof(mix_msg_in)); + + if (err > 0) { + elem->cached |= 1 << index; + elem->cache_val[index] = val; + } else { + usb_audio_dbg(chip, "Failed to set channel, err:%d\n", err); + } + + return err > 0 ? 1 : 0; +} + +static int snd_us16x08_mix_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->count = 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.max = SND_US16X08_KCMAX(kcontrol); + uinfo->value.integer.min = SND_US16X08_KCMIN(kcontrol); + uinfo->value.integer.step = SND_US16X08_KCSTEP(kcontrol); + return 0; +} + +static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_us16x08_comp_store *store = elem->private_data; + int index = ucontrol->id.index; + int val_idx = COMP_STORE_IDX(elem->head.id); + + ucontrol->value.integer.value[0] = store->val[val_idx][index]; + + return 0; +} + +static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + struct snd_us16x08_comp_store *store = elem->private_data; + int index = ucontrol->id.index; + char buf[sizeof(comp_msg)]; + int val_idx, val; + int err; + + val = ucontrol->value.integer.value[0]; + + /* sanity check */ + if (val < SND_US16X08_KCMIN(kcontrol) + || val > SND_US16X08_KCMAX(kcontrol)) + return -EINVAL; + + /* new control value incl. bias*/ + val_idx = elem->head.id - SND_US16X08_ID_COMP_BASE; + + store->val[val_idx][index] = ucontrol->value.integer.value[0]; + + /* prepare compressor URB message from template */ + memcpy(buf, comp_msg, sizeof(comp_msg)); + + /* place comp values in message buffer watch bias! */ + buf[8] = store->val[ + COMP_STORE_IDX(SND_US16X08_ID_COMP_THRESHOLD)][index] + - SND_US16X08_COMP_THRESHOLD_BIAS; + buf[11] = ratio_map[store->val[ + COMP_STORE_IDX(SND_US16X08_ID_COMP_RATIO)][index]]; + buf[14] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_ATTACK)][index] + + SND_US16X08_COMP_ATTACK_BIAS; + buf[17] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RELEASE)][index] + + SND_US16X08_COMP_RELEASE_BIAS; + buf[20] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_GAIN)][index]; + buf[26] = store->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)][index]; + + /* place channel selector in message buffer */ + buf[5] = index + 1; + + err = snd_us16x08_send_urb(chip, buf, sizeof(comp_msg)); + + if (err > 0) { + elem->cached |= 1 << index; + elem->cache_val[index] = val; + } else { + usb_audio_dbg(chip, "Failed to set compressor, err:%d\n", err); + } + + return 1; +} + +static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val; + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_us16x08_eq_store *store = elem->private_data; + int index = ucontrol->id.index; + + /* get low switch from cache is enough, cause all bands are together */ + val = store->val[EQ_STORE_BAND_IDX(elem->head.id)] + [EQ_STORE_PARAM_IDX(elem->head.id)][index]; + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + struct snd_us16x08_eq_store *store = elem->private_data; + int index = ucontrol->id.index; + char buf[sizeof(eqs_msq)]; + int val, err = 0; + int b_idx; + + /* new control value incl. bias*/ + val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol); + + /* prepare URB message from EQ template */ + memcpy(buf, eqs_msq, sizeof(eqs_msq)); + + /* place channel index in URB message */ + buf[5] = index + 1; + for (b_idx = 0; b_idx < SND_US16X08_ID_EQ_BAND_COUNT; b_idx++) { + /* all four EQ bands have to be enabled/disabled in once */ + buf[20] = val; + buf[17] = store->val[b_idx][2][index]; + buf[14] = store->val[b_idx][1][index]; + buf[11] = store->val[b_idx][0][index]; + buf[8] = b_idx + 1; + err = snd_us16x08_send_urb(chip, buf, sizeof(eqs_msq)); + if (err < 0) + break; + store->val[b_idx][3][index] = val; + msleep(15); + } + + if (err > 0) { + elem->cached |= 1 << index; + elem->cache_val[index] = val; + } else { + usb_audio_dbg(chip, "Failed to set eq switch, err:%d\n", err); + } + + return 1; +} + +static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val; + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_us16x08_eq_store *store = elem->private_data; + int index = ucontrol->id.index; + int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; + int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + + val = store->val[b_idx][p_idx][index]; + + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + struct snd_us16x08_eq_store *store = elem->private_data; + int index = ucontrol->id.index; + char buf[sizeof(eqs_msq)]; + int val, err; + int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; + int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + + val = ucontrol->value.integer.value[0]; + + /* sanity check */ + if (val < SND_US16X08_KCMIN(kcontrol) + || val > SND_US16X08_KCMAX(kcontrol)) + return -EINVAL; + + /* copy URB buffer from EQ template */ + memcpy(buf, eqs_msq, sizeof(eqs_msq)); + + store->val[b_idx][p_idx][index] = val; + buf[20] = store->val[b_idx][3][index]; + buf[17] = store->val[b_idx][2][index]; + buf[14] = store->val[b_idx][1][index]; + buf[11] = store->val[b_idx][0][index]; + + /* place channel index in URB buffer */ + buf[5] = index + 1; + + /* place EQ band in URB buffer */ + buf[8] = b_idx + 1; + + err = snd_us16x08_send_urb(chip, buf, sizeof(eqs_msq)); + + if (err > 0) { + /* store new value in EQ band cache */ + elem->cached |= 1 << index; + elem->cache_val[index] = val; + } else { + usb_audio_dbg(chip, "Failed to set eq param, err:%d\n", err); + } + + return 1; +} + +static int snd_us16x08_meter_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->count = 34; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.max = 0x7FFF; + uinfo->value.integer.min = 0; + + return 0; +} + +/* calculate compressor index for reduction level request */ +static int snd_get_meter_comp_index(struct snd_us16x08_meter_store *store) +{ + int ret; + + /* any channel active */ + if (store->comp_active_index) { + /* check for stereo link */ + if (store->comp_active_index & 0x20) { + /* reset comp_index to left channel*/ + if (store->comp_index - + store->comp_active_index > 1) + store->comp_index = + store->comp_active_index; + + ret = store->comp_index++ & 0x1F; + } else { + /* no stereo link */ + ret = store->comp_active_index; + } + } else { + /* skip channels with no compressor active */ + while (!store->comp_store->val[ + COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)] + [store->comp_index - 1] + && store->comp_index <= SND_US16X08_MAX_CHANNELS) { + store->comp_index++; + } + ret = store->comp_index++; + if (store->comp_index > SND_US16X08_MAX_CHANNELS) + store->comp_index = 1; + } + return ret; +} + +/* retrieve the meter level values from URB message */ +static void get_meter_levels_from_urb(int s, + struct snd_us16x08_meter_store *store, + u8 *meter_urb) +{ + int val = MUC2(meter_urb, s) + (MUC3(meter_urb, s) << 8); + + if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 && + MUA2(meter_urb, s) == 0x04 && MUB0(meter_urb, s) == 0x62) { + if (MUC0(meter_urb, s) == 0x72) + store->meter_level[MUB2(meter_urb, s) - 1] = val; + if (MUC0(meter_urb, s) == 0xb2) + store->comp_level[MUB2(meter_urb, s) - 1] = val; + } + if (MUA0(meter_urb, s) == 0x61 && MUA1(meter_urb, s) == 0x02 && + MUA2(meter_urb, s) == 0x02 && MUB0(meter_urb, s) == 0x62) + store->master_level[MUB2(meter_urb, s) - 1] = val; +} + +/* Function to retrieve current meter values from the device. + * + * The device needs to be polled for meter values with an initial + * requests. It will return with a sequence of different meter value + * packages. The first request (case 0:) initiate this meter response sequence. + * After the third response, an additional request can be placed, + * to retrieve compressor reduction level value for given channel. This round + * trip channel selector will skip all inactive compressors. + * A mixer can interrupt this round-trip by selecting one ore two (stereo-link) + * specific channels. + */ +static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i, set; + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_usb_audio *chip = elem->head.mixer->chip; + struct snd_us16x08_meter_store *store = elem->private_data; + u8 meter_urb[64]; + + switch (kcontrol->private_value) { + case 0: { + char tmp[sizeof(mix_init_msg1)]; + + memcpy(tmp, mix_init_msg1, sizeof(mix_init_msg1)); + snd_us16x08_send_urb(chip, tmp, 4); + snd_us16x08_recv_urb(chip, meter_urb, + sizeof(meter_urb)); + kcontrol->private_value++; + break; + } + case 1: + snd_us16x08_recv_urb(chip, meter_urb, + sizeof(meter_urb)); + kcontrol->private_value++; + break; + case 2: + snd_us16x08_recv_urb(chip, meter_urb, + sizeof(meter_urb)); + kcontrol->private_value++; + break; + case 3: { + char tmp[sizeof(mix_init_msg2)]; + + memcpy(tmp, mix_init_msg2, sizeof(mix_init_msg2)); + tmp[2] = snd_get_meter_comp_index(store); + snd_us16x08_send_urb(chip, tmp, 10); + snd_us16x08_recv_urb(chip, meter_urb, + sizeof(meter_urb)); + kcontrol->private_value = 0; + break; + } + } + + for (set = 0; set < 6; set++) + get_meter_levels_from_urb(set, store, meter_urb); + + for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) { + ucontrol->value.integer.value[i] = + store ? store->meter_level[i] : 0; + } + + ucontrol->value.integer.value[i++] = store ? store->master_level[0] : 0; + ucontrol->value.integer.value[i++] = store ? store->master_level[1] : 0; + + for (i = 2; i < SND_US16X08_MAX_CHANNELS + 2; i++) + ucontrol->value.integer.value[i + SND_US16X08_MAX_CHANNELS] = + store ? store->comp_level[i - 2] : 0; + + return 1; +} + +static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kcontrol->private_data; + struct snd_us16x08_meter_store *store = elem->private_data; + int val; + + val = ucontrol->value.integer.value[0]; + + /* sanity check */ + if (val < 0 || val >= SND_US16X08_MAX_CHANNELS) + return -EINVAL; + + store->comp_active_index = val; + store->comp_index = val; + + return 1; +} + +static struct snd_kcontrol_new snd_us16x08_ch_boolean_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_switch_info, + .get = snd_us16x08_channel_get, + .put = snd_us16x08_channel_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) +}; + +static struct snd_kcontrol_new snd_us16x08_ch_int_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_channel_get, + .put = snd_us16x08_channel_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133) +}; + +static struct snd_kcontrol_new snd_us16x08_pan_int_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_channel_get, + .put = snd_us16x08_channel_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 255) +}; + +static struct snd_kcontrol_new snd_us16x08_master_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 1, + .info = snd_us16x08_master_info, + .get = snd_us16x08_master_get, + .put = snd_us16x08_master_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_FADER_BIAS, 1, 0, 133) +}; + +static struct snd_kcontrol_new snd_us16x08_route_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 8, + .info = snd_us16x08_route_info, + .get = snd_us16x08_route_get, + .put = snd_us16x08_route_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 9) +}; + +static struct snd_kcontrol_new snd_us16x08_bus_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 1, + .info = snd_us16x08_switch_info, + .get = snd_us16x08_bus_get, + .put = snd_us16x08_bus_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) +}; + +static struct snd_kcontrol_new snd_us16x08_compswitch_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_switch_info, + .get = snd_us16x08_comp_get, + .put = snd_us16x08_comp_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) +}; + +static struct snd_kcontrol_new snd_us16x08_comp_threshold_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_comp_get, + .put = snd_us16x08_comp_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_COMP_THRESHOLD_BIAS, 1, + 0, 0x20) +}; + +static struct snd_kcontrol_new snd_us16x08_comp_ratio_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_comp_get, + .put = snd_us16x08_comp_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, + sizeof(ratio_map) - 1), /*max*/ +}; + +static struct snd_kcontrol_new snd_us16x08_comp_gain_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_comp_get, + .put = snd_us16x08_comp_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x14) +}; + +static struct snd_kcontrol_new snd_us16x08_comp_attack_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_comp_get, + .put = snd_us16x08_comp_put, + .private_value = + SND_US16X08_KCSET(SND_US16X08_COMP_ATTACK_BIAS, 1, 0, 0xc6), +}; + +static struct snd_kcontrol_new snd_us16x08_comp_release_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_comp_get, + .put = snd_us16x08_comp_put, + .private_value = + SND_US16X08_KCSET(SND_US16X08_COMP_RELEASE_BIAS, 1, 0, 0x63), +}; + +static struct snd_kcontrol_new snd_us16x08_eq_gain_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_eq_get, + .put = snd_us16x08_eq_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 24), +}; + +static struct snd_kcontrol_new snd_us16x08_eq_low_freq_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_eq_get, + .put = snd_us16x08_eq_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x1F), +}; + +static struct snd_kcontrol_new snd_us16x08_eq_mid_freq_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_eq_get, + .put = snd_us16x08_eq_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x3F) +}; + +static struct snd_kcontrol_new snd_us16x08_eq_mid_width_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_eq_get, + .put = snd_us16x08_eq_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 0x06) +}; + +static struct snd_kcontrol_new snd_us16x08_eq_high_freq_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_mix_info, + .get = snd_us16x08_eq_get, + .put = snd_us16x08_eq_put, + .private_value = + SND_US16X08_KCSET(SND_US16X08_EQ_HIGHFREQ_BIAS, 1, 0, 0x1F) +}; + +static struct snd_kcontrol_new snd_us16x08_eq_switch_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 16, + .info = snd_us16x08_switch_info, + .get = snd_us16x08_eqswitch_get, + .put = snd_us16x08_eqswitch_put, + .private_value = SND_US16X08_KCSET(SND_US16X08_NO_BIAS, 1, 0, 1) +}; + +static struct snd_kcontrol_new snd_us16x08_meter_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .count = 1, + .info = snd_us16x08_meter_info, + .get = snd_us16x08_meter_get, + .put = snd_us16x08_meter_put +}; + +/* control store preparation */ + +/* setup compressor store and assign default value */ +static struct snd_us16x08_comp_store *snd_us16x08_create_comp_store(void) +{ + int i; + struct snd_us16x08_comp_store *tmp; + + tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return NULL; + + for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) { + tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_THRESHOLD)][i] + = 0x20; + tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RATIO)][i] = 0x00; + tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_GAIN)][i] = 0x00; + tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)][i] = 0x00; + tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_ATTACK)][i] = 0x00; + tmp->val[COMP_STORE_IDX(SND_US16X08_ID_COMP_RELEASE)][i] = 0x00; + } + return tmp; +} + +/* setup EQ store and assign default values */ +static struct snd_us16x08_eq_store *snd_us16x08_create_eq_store(void) +{ + int i, b_idx; + struct snd_us16x08_eq_store *tmp; + + tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return NULL; + + for (i = 0; i < SND_US16X08_MAX_CHANNELS; i++) { + for (b_idx = 0; b_idx < SND_US16X08_ID_EQ_BAND_COUNT; b_idx++) { + tmp->val[b_idx][0][i] = 0x0c; + tmp->val[b_idx][3][i] = 0x00; + switch (b_idx) { + case 0: /* EQ Low */ + tmp->val[b_idx][1][i] = 0x05; + tmp->val[b_idx][2][i] = 0xff; + break; + case 1: /* EQ Mid low */ + tmp->val[b_idx][1][i] = 0x0e; + tmp->val[b_idx][2][i] = 0x02; + break; + case 2: /* EQ Mid High */ + tmp->val[b_idx][1][i] = 0x1b; + tmp->val[b_idx][2][i] = 0x02; + break; + case 3: /* EQ High */ + tmp->val[b_idx][1][i] = 0x2f + - SND_US16X08_EQ_HIGHFREQ_BIAS; + tmp->val[b_idx][2][i] = 0xff; + break; + } + } + } + return tmp; +} + +static struct snd_us16x08_meter_store *snd_us16x08_create_meter_store(void) +{ + struct snd_us16x08_meter_store *tmp; + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) + return NULL; + tmp->comp_index = 1; + tmp->comp_active_index = 0; + return tmp; +} + +/* release elem->private_free as well; called only once for each *_store */ +static void elem_private_free(struct snd_kcontrol *kctl) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + + if (elem) + kfree(elem->private_data); + kfree(elem); + kctl->private_data = NULL; +} + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int val_type, int channels, + const char *name, void *opt, + bool do_private_free, + struct usb_mixer_elem_info **elem_ret) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + usb_audio_dbg(mixer->chip, "us16x08 add mixer %s\n", name); + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->head.resume = NULL; + elem->control = 0; + elem->idx_off = 0; + elem->head.id = index; + elem->val_type = val_type; + elem->channels = channels; + elem->private_data = opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + + if (do_private_free) + kctl->private_free = elem_private_free; + else + kctl->private_free = snd_usb_mixer_elem_free; + + strlcpy(kctl->id.name, name, sizeof(kctl->id.name)); + + err = snd_usb_mixer_add_control(&elem->head, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +/* table of EQ controls */ +static const struct snd_us16x08_control_params eq_controls[] = { + { /* EQ switch */ + .kcontrol_new = &snd_us16x08_eq_switch_ctl, + .control_id = SND_US16X08_ID_EQENABLE, + .type = USB_MIXER_BOOLEAN, + .num_channels = 16, + .name = "EQ Switch", + }, + { /* EQ low gain */ + .kcontrol_new = &snd_us16x08_eq_gain_ctl, + .control_id = SND_US16X08_ID_EQLOWLEVEL, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ Low Volume", + }, + { /* EQ low freq */ + .kcontrol_new = &snd_us16x08_eq_low_freq_ctl, + .control_id = SND_US16X08_ID_EQLOWFREQ, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ Low Frequence", + }, + { /* EQ mid low gain */ + .kcontrol_new = &snd_us16x08_eq_gain_ctl, + .control_id = SND_US16X08_ID_EQLOWMIDLEVEL, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ MidLow Volume", + }, + { /* EQ mid low freq */ + .kcontrol_new = &snd_us16x08_eq_mid_freq_ctl, + .control_id = SND_US16X08_ID_EQLOWMIDFREQ, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ MidLow Frequence", + }, + { /* EQ mid low Q */ + .kcontrol_new = &snd_us16x08_eq_mid_width_ctl, + .control_id = SND_US16X08_ID_EQLOWMIDWIDTH, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ MidLow Q", + }, + { /* EQ mid high gain */ + .kcontrol_new = &snd_us16x08_eq_gain_ctl, + .control_id = SND_US16X08_ID_EQHIGHMIDLEVEL, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ MidHigh Volume", + }, + { /* EQ mid high freq */ + .kcontrol_new = &snd_us16x08_eq_mid_freq_ctl, + .control_id = SND_US16X08_ID_EQHIGHMIDFREQ, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ MidHigh Frequence", + }, + { /* EQ mid high Q */ + .kcontrol_new = &snd_us16x08_eq_mid_width_ctl, + .control_id = SND_US16X08_ID_EQHIGHMIDWIDTH, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ MidHigh Q", + }, + { /* EQ high gain */ + .kcontrol_new = &snd_us16x08_eq_gain_ctl, + .control_id = SND_US16X08_ID_EQHIGHLEVEL, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ High Volume", + }, + { /* EQ low freq */ + .kcontrol_new = &snd_us16x08_eq_high_freq_ctl, + .control_id = SND_US16X08_ID_EQHIGHFREQ, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "EQ High Frequence", + }, +}; + +/* table of compressor controls */ +static const struct snd_us16x08_control_params comp_controls[] = { + { /* Comp enable */ + .kcontrol_new = &snd_us16x08_compswitch_ctl, + .control_id = SND_US16X08_ID_COMP_SWITCH, + .type = USB_MIXER_BOOLEAN, + .num_channels = 16, + .name = "Compressor Switch", + }, + { /* Comp threshold */ + .kcontrol_new = &snd_us16x08_comp_threshold_ctl, + .control_id = SND_US16X08_ID_COMP_THRESHOLD, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "Compressor Threshold Volume", + }, + { /* Comp ratio */ + .kcontrol_new = &snd_us16x08_comp_ratio_ctl, + .control_id = SND_US16X08_ID_COMP_RATIO, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "Compressor Ratio", + }, + { /* Comp attack */ + .kcontrol_new = &snd_us16x08_comp_attack_ctl, + .control_id = SND_US16X08_ID_COMP_ATTACK, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "Compressor Attack", + }, + { /* Comp release */ + .kcontrol_new = &snd_us16x08_comp_release_ctl, + .control_id = SND_US16X08_ID_COMP_RELEASE, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "Compressor Release", + }, + { /* Comp gain */ + .kcontrol_new = &snd_us16x08_comp_gain_ctl, + .control_id = SND_US16X08_ID_COMP_GAIN, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "Compressor Volume", + }, +}; + +/* table of channel controls */ +static const struct snd_us16x08_control_params channel_controls[] = { + { /* Phase */ + .kcontrol_new = &snd_us16x08_ch_boolean_ctl, + .control_id = SND_US16X08_ID_PHASE, + .type = USB_MIXER_BOOLEAN, + .num_channels = 16, + .name = "Phase Switch", + .default_val = 0 + }, + { /* Fader */ + .kcontrol_new = &snd_us16x08_ch_int_ctl, + .control_id = SND_US16X08_ID_FADER, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "Line Volume", + .default_val = 127 + }, + { /* Mute */ + .kcontrol_new = &snd_us16x08_ch_boolean_ctl, + .control_id = SND_US16X08_ID_MUTE, + .type = USB_MIXER_BOOLEAN, + .num_channels = 16, + .name = "Mute Switch", + .default_val = 0 + }, + { /* Pan */ + .kcontrol_new = &snd_us16x08_pan_int_ctl, + .control_id = SND_US16X08_ID_PAN, + .type = USB_MIXER_U16, + .num_channels = 16, + .name = "Pan Left-Right Volume", + .default_val = 127 + }, +}; + +/* table of master controls */ +static const struct snd_us16x08_control_params master_controls[] = { + { /* Master */ + .kcontrol_new = &snd_us16x08_master_ctl, + .control_id = SND_US16X08_ID_FADER, + .type = USB_MIXER_U8, + .num_channels = 16, + .name = "Master Volume", + .default_val = 127 + }, + { /* Bypass */ + .kcontrol_new = &snd_us16x08_bus_ctl, + .control_id = SND_US16X08_ID_BYPASS, + .type = USB_MIXER_BOOLEAN, + .num_channels = 16, + .name = "DSP Bypass Switch", + .default_val = 0 + }, + { /* Buss out */ + .kcontrol_new = &snd_us16x08_bus_ctl, + .control_id = SND_US16X08_ID_BUSS_OUT, + .type = USB_MIXER_BOOLEAN, + .num_channels = 16, + .name = "Buss Out Switch", + .default_val = 0 + }, + { /* Master mute */ + .kcontrol_new = &snd_us16x08_bus_ctl, + .control_id = SND_US16X08_ID_MUTE, + .type = USB_MIXER_BOOLEAN, + .num_channels = 16, + .name = "Master Mute Switch", + .default_val = 0 + }, + +}; + +int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) +{ + int i, j; + int err; + struct usb_mixer_elem_info *elem; + struct snd_us16x08_comp_store *comp_store; + struct snd_us16x08_meter_store *meter_store; + struct snd_us16x08_eq_store *eq_store; + + /* just check for non-MIDI interface */ + if (mixer->hostif->desc.bInterfaceNumber == 3) { + + /* add routing control */ + err = add_new_ctl(mixer, &snd_us16x08_route_ctl, + SND_US16X08_ID_ROUTE, USB_MIXER_U8, 8, "Line Out Route", + NULL, false, &elem); + if (err < 0) { + usb_audio_dbg(mixer->chip, + "Failed to create route control, err:%d\n", + err); + return err; + } + for (i = 0; i < 8; i++) + elem->cache_val[i] = i < 2 ? i : i + 2; + elem->cached = 0xff; + + /* create compressor mixer elements */ + comp_store = snd_us16x08_create_comp_store(); + if (!comp_store) + return -ENOMEM; + + /* add master controls */ + for (i = 0; i < ARRAY_SIZE(master_controls); i++) { + + err = add_new_ctl(mixer, + master_controls[i].kcontrol_new, + master_controls[i].control_id, + master_controls[i].type, + master_controls[i].num_channels, + master_controls[i].name, + comp_store, + i == 0, /* release comp_store only once */ + &elem); + if (err < 0) + return err; + elem->cache_val[0] = master_controls[i].default_val; + elem->cached = 1; + } + + /* add channel controls */ + for (i = 0; i < ARRAY_SIZE(channel_controls); i++) { + + err = add_new_ctl(mixer, + channel_controls[i].kcontrol_new, + channel_controls[i].control_id, + channel_controls[i].type, + channel_controls[i].num_channels, + channel_controls[i].name, + comp_store, + false, &elem); + if (err < 0) + return err; + for (j = 0; j < SND_US16X08_MAX_CHANNELS; j++) { + elem->cache_val[j] = + channel_controls[i].default_val; + } + elem->cached = 0xffff; + } + + /* create eq store */ + eq_store = snd_us16x08_create_eq_store(); + if (!eq_store) + return -ENOMEM; + + /* add EQ controls */ + for (i = 0; i < ARRAY_SIZE(eq_controls); i++) { + + err = add_new_ctl(mixer, + eq_controls[i].kcontrol_new, + eq_controls[i].control_id, + eq_controls[i].type, + eq_controls[i].num_channels, + eq_controls[i].name, + eq_store, + i == 0, /* release eq_store only once */ + NULL); + if (err < 0) + return err; + } + + /* add compressor controls */ + for (i = 0; i < ARRAY_SIZE(comp_controls); i++) { + + err = add_new_ctl(mixer, + comp_controls[i].kcontrol_new, + comp_controls[i].control_id, + comp_controls[i].type, + comp_controls[i].num_channels, + comp_controls[i].name, + comp_store, + false, NULL); + if (err < 0) + return err; + } + + /* create meters store */ + meter_store = snd_us16x08_create_meter_store(); + if (!meter_store) + return -ENOMEM; + + /* meter function 'get' must access to compressor store + * so place a reference here + */ + meter_store->comp_store = comp_store; + err = add_new_ctl(mixer, &snd_us16x08_meter_ctl, + SND_US16X08_ID_METER, USB_MIXER_U16, 0, "Level Meter", + meter_store, true, NULL); + if (err < 0) + return err; + } + + return 0; +} + diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h new file mode 100644 index 000000000..56ff16c06 --- /dev/null +++ b/sound/usb/mixer_us16x08.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_US16X08_H +#define __USB_MIXER_US16X08_H + +#define SND_US16X08_MAX_CHANNELS 16 + +/* define some bias, cause some alsa-mixers wont work with + * negative ranges or if mixer-min != 0 + */ +#define SND_US16X08_NO_BIAS 0 +#define SND_US16X08_FADER_BIAS 127 +#define SND_US16X08_EQ_HIGHFREQ_BIAS 0x20 +#define SND_US16X08_COMP_THRESHOLD_BIAS 0x20 +#define SND_US16X08_COMP_ATTACK_BIAS 2 +#define SND_US16X08_COMP_RELEASE_BIAS 1 + +/* get macro for components of kcontrol private_value */ +#define SND_US16X08_KCBIAS(x) (((x)->private_value >> 24) & 0xff) +#define SND_US16X08_KCSTEP(x) (((x)->private_value >> 16) & 0xff) +#define SND_US16X08_KCMIN(x) (((x)->private_value >> 8) & 0xff) +#define SND_US16X08_KCMAX(x) (((x)->private_value >> 0) & 0xff) +/* set macro for kcontrol private_value */ +#define SND_US16X08_KCSET(bias, step, min, max) \ + (((bias) << 24) | ((step) << 16) | ((min) << 8) | (max)) + +/* the URB request/type to control Tascam mixers */ +#define SND_US16X08_URB_REQUEST 0x1D +#define SND_US16X08_URB_REQUESTTYPE 0x40 + +/* the URB params to retrieve meter ranges */ +#define SND_US16X08_URB_METER_REQUEST 0x1e +#define SND_US16X08_URB_METER_REQUESTTYPE 0xc0 + +#define MUA0(x, y) ((x)[(y) * 10 + 4]) +#define MUA1(x, y) ((x)[(y) * 10 + 5]) +#define MUA2(x, y) ((x)[(y) * 10 + 6]) +#define MUB0(x, y) ((x)[(y) * 10 + 7]) +#define MUB1(x, y) ((x)[(y) * 10 + 8]) +#define MUB2(x, y) ((x)[(y) * 10 + 9]) +#define MUC0(x, y) ((x)[(y) * 10 + 10]) +#define MUC1(x, y) ((x)[(y) * 10 + 11]) +#define MUC2(x, y) ((x)[(y) * 10 + 12]) +#define MUC3(x, y) ((x)[(y) * 10 + 13]) + +/* Common Channel control IDs */ +#define SND_US16X08_ID_BYPASS 0x45 +#define SND_US16X08_ID_BUSS_OUT 0x44 +#define SND_US16X08_ID_PHASE 0x85 +#define SND_US16X08_ID_MUTE 0x83 +#define SND_US16X08_ID_FADER 0x81 +#define SND_US16X08_ID_PAN 0x82 +#define SND_US16X08_ID_METER 0xB1 + +#define SND_US16X08_ID_EQ_BAND_COUNT 4 +#define SND_US16X08_ID_EQ_PARAM_COUNT 4 + +/* EQ level IDs */ +#define SND_US16X08_ID_EQLOWLEVEL 0x01 +#define SND_US16X08_ID_EQLOWMIDLEVEL 0x02 +#define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03 +#define SND_US16X08_ID_EQHIGHLEVEL 0x04 + +/* EQ frequence IDs */ +#define SND_US16X08_ID_EQLOWFREQ 0x11 +#define SND_US16X08_ID_EQLOWMIDFREQ 0x12 +#define SND_US16X08_ID_EQHIGHMIDFREQ 0x13 +#define SND_US16X08_ID_EQHIGHFREQ 0x14 + +/* EQ width IDs */ +#define SND_US16X08_ID_EQLOWMIDWIDTH 0x22 +#define SND_US16X08_ID_EQHIGHMIDWIDTH 0x23 + +#define SND_US16X08_ID_EQENABLE 0x30 + +#define EQ_STORE_BAND_IDX(x) ((x) & 0xf) +#define EQ_STORE_PARAM_IDX(x) (((x) & 0xf0) >> 4) + +#define SND_US16X08_ID_ROUTE 0x00 + +/* Compressor Ids */ +#define SND_US16X08_ID_COMP_BASE 0x32 +#define SND_US16X08_ID_COMP_THRESHOLD SND_US16X08_ID_COMP_BASE +#define SND_US16X08_ID_COMP_RATIO (SND_US16X08_ID_COMP_BASE + 1) +#define SND_US16X08_ID_COMP_ATTACK (SND_US16X08_ID_COMP_BASE + 2) +#define SND_US16X08_ID_COMP_RELEASE (SND_US16X08_ID_COMP_BASE + 3) +#define SND_US16X08_ID_COMP_GAIN (SND_US16X08_ID_COMP_BASE + 4) +#define SND_US16X08_ID_COMP_SWITCH (SND_US16X08_ID_COMP_BASE + 5) +#define SND_US16X08_ID_COMP_COUNT 6 + +#define COMP_STORE_IDX(x) ((x) - SND_US16X08_ID_COMP_BASE) + +struct snd_us16x08_eq_store { + u8 val[SND_US16X08_ID_EQ_BAND_COUNT][SND_US16X08_ID_EQ_PARAM_COUNT] + [SND_US16X08_MAX_CHANNELS]; +}; + +struct snd_us16x08_comp_store { + u8 val[SND_US16X08_ID_COMP_COUNT][SND_US16X08_MAX_CHANNELS]; +}; + +struct snd_us16x08_meter_store { + int meter_level[SND_US16X08_MAX_CHANNELS]; + int master_level[2]; /* level of meter for master output */ + int comp_index; /* round trip channel selector */ + int comp_active_index; /* channel select from user space mixer */ + int comp_level[16]; /* compressor reduction level */ + struct snd_us16x08_comp_store *comp_store; +}; + +struct snd_us16x08_control_params { + struct snd_kcontrol_new *kcontrol_new; + int control_id; + int type; + int num_channels; + const char *name; + int default_val; +}; + +#define snd_us16x08_switch_info snd_ctl_boolean_mono_info + +int snd_us16x08_controls_create(struct usb_mixer_interface *mixer); +#endif /* __USB_MIXER_US16X08_H */ diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c new file mode 100644 index 000000000..4c9ab611a --- /dev/null +++ b/sound/usb/pcm.c @@ -0,0 +1,1861 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "quirks.h" +#include "debug.h" +#include "endpoint.h" +#include "helper.h" +#include "pcm.h" +#include "clock.h" +#include "power.h" + +#define SUBSTREAM_FLAG_DATA_EP_STARTED 0 +#define SUBSTREAM_FLAG_SYNC_EP_STARTED 1 + +/* return the estimated delay based on USB frame counters */ +snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, + unsigned int rate) +{ + int current_frame_number; + int frame_diff; + int est_delay; + + if (!subs->last_delay) + return 0; /* short path */ + + current_frame_number = usb_get_current_frame_number(subs->dev); + /* + * HCD implementations use different widths, use lower 8 bits. + * The delay will be managed up to 256ms, which is more than + * enough + */ + frame_diff = (current_frame_number - subs->last_frame_number) & 0xff; + + /* Approximation based on number of samples per USB frame (ms), + some truncation for 44.1 but the estimate is good enough */ + est_delay = frame_diff * rate / 1000; + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) + est_delay = subs->last_delay - est_delay; + else + est_delay = subs->last_delay + est_delay; + + if (est_delay < 0) + est_delay = 0; + return est_delay; +} + +/* + * return the current pcm pointer. just based on the hwptr_done value. + */ +static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + unsigned int hwptr_done; + + if (atomic_read(&subs->stream->chip->shutdown)) + return SNDRV_PCM_POS_XRUN; + spin_lock(&subs->lock); + hwptr_done = subs->hwptr_done; + substream->runtime->delay = snd_usb_pcm_delay(subs, + substream->runtime->rate); + spin_unlock(&subs->lock); + return hwptr_done / (substream->runtime->frame_bits >> 3); +} + +/* + * find a matching audio format + */ +static struct audioformat *find_format(struct snd_usb_substream *subs) +{ + struct audioformat *fp; + struct audioformat *found = NULL; + int cur_attr = 0, attr; + + list_for_each_entry(fp, &subs->fmt_list, list) { + if (!(fp->formats & pcm_format_to_bits(subs->pcm_format))) + continue; + if (fp->channels != subs->channels) + continue; + if (subs->cur_rate < fp->rate_min || + subs->cur_rate > fp->rate_max) + continue; + if (! (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)) { + unsigned int i; + for (i = 0; i < fp->nr_rates; i++) + if (fp->rate_table[i] == subs->cur_rate) + break; + if (i >= fp->nr_rates) + continue; + } + attr = fp->ep_attr & USB_ENDPOINT_SYNCTYPE; + if (! found) { + found = fp; + cur_attr = attr; + continue; + } + /* avoid async out and adaptive in if the other method + * supports the same format. + * this is a workaround for the case like + * M-audio audiophile USB. + */ + if (attr != cur_attr) { + if ((attr == USB_ENDPOINT_SYNC_ASYNC && + subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || + (attr == USB_ENDPOINT_SYNC_ADAPTIVE && + subs->direction == SNDRV_PCM_STREAM_CAPTURE)) + continue; + if ((cur_attr == USB_ENDPOINT_SYNC_ASYNC && + subs->direction == SNDRV_PCM_STREAM_PLAYBACK) || + (cur_attr == USB_ENDPOINT_SYNC_ADAPTIVE && + subs->direction == SNDRV_PCM_STREAM_CAPTURE)) { + found = fp; + cur_attr = attr; + continue; + } + } + /* find the format with the largest max. packet size */ + if (fp->maxpacksize > found->maxpacksize) { + found = fp; + cur_attr = attr; + } + } + return found; +} + +static int init_pitch_v1(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt) +{ + struct usb_device *dev = chip->dev; + unsigned int ep; + unsigned char data[1]; + int err; + + if (get_iface_desc(alts)->bNumEndpoints < 1) + return -EINVAL; + ep = get_endpoint(alts, 0)->bEndpointAddress; + + data[0] = 1; + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + UAC_EP_CS_ATTR_PITCH_CONTROL << 8, ep, + data, sizeof(data)); + if (err < 0) { + usb_audio_err(chip, "%d:%d: cannot set enable PITCH\n", + iface, ep); + return err; + } + + return 0; +} + +static int init_pitch_v2(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt) +{ + struct usb_device *dev = chip->dev; + unsigned char data[1]; + int err; + + data[0] = 1; + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, + UAC2_EP_CS_PITCH << 8, 0, + data, sizeof(data)); + if (err < 0) { + usb_audio_err(chip, "%d:%d: cannot set enable PITCH (v2)\n", + iface, fmt->altsetting); + return err; + } + + return 0; +} + +/* + * initialize the pitch control and sample rate + */ +int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt) +{ + /* if endpoint doesn't have pitch control, bail out */ + if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL)) + return 0; + + switch (fmt->protocol) { + case UAC_VERSION_1: + default: + return init_pitch_v1(chip, iface, alts, fmt); + + case UAC_VERSION_2: + return init_pitch_v2(chip, iface, alts, fmt); + } +} + +static int start_endpoints(struct snd_usb_substream *subs) +{ + int err; + + if (!subs->data_endpoint) + return -EINVAL; + + if (!test_and_set_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) { + struct snd_usb_endpoint *ep = subs->data_endpoint; + + dev_dbg(&subs->dev->dev, "Starting data EP @%p\n", ep); + + ep->data_subs = subs; + err = snd_usb_endpoint_start(ep); + if (err < 0) { + clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags); + return err; + } + } + + if (subs->sync_endpoint && + !test_and_set_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) { + struct snd_usb_endpoint *ep = subs->sync_endpoint; + + if (subs->data_endpoint->iface != subs->sync_endpoint->iface || + subs->data_endpoint->altsetting != subs->sync_endpoint->altsetting) { + err = usb_set_interface(subs->dev, + subs->sync_endpoint->iface, + subs->sync_endpoint->altsetting); + if (err < 0) { + clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags); + dev_err(&subs->dev->dev, + "%d:%d: cannot set interface (%d)\n", + subs->sync_endpoint->iface, + subs->sync_endpoint->altsetting, err); + return -EIO; + } + } + + dev_dbg(&subs->dev->dev, "Starting sync EP @%p\n", ep); + + ep->sync_slave = subs->data_endpoint; + err = snd_usb_endpoint_start(ep); + if (err < 0) { + clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags); + return err; + } + } + + return 0; +} + +static void stop_endpoints(struct snd_usb_substream *subs, bool wait) +{ + if (test_and_clear_bit(SUBSTREAM_FLAG_SYNC_EP_STARTED, &subs->flags)) + snd_usb_endpoint_stop(subs->sync_endpoint); + + if (test_and_clear_bit(SUBSTREAM_FLAG_DATA_EP_STARTED, &subs->flags)) + snd_usb_endpoint_stop(subs->data_endpoint); + + if (wait) { + snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint); + snd_usb_endpoint_sync_pending_stop(subs->data_endpoint); + } +} + +static int search_roland_implicit_fb(struct usb_device *dev, int ifnum, + unsigned int altsetting, + struct usb_host_interface **alts, + unsigned int *ep) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *altsd; + struct usb_endpoint_descriptor *epd; + + iface = usb_ifnum_to_if(dev, ifnum); + if (!iface || iface->num_altsetting < altsetting + 1) + return -ENOENT; + *alts = &iface->altsetting[altsetting]; + altsd = get_iface_desc(*alts); + if (altsd->bAlternateSetting != altsetting || + altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC || + (altsd->bInterfaceSubClass != 2 && + altsd->bInterfaceProtocol != 2 ) || + altsd->bNumEndpoints < 1) + return -ENOENT; + epd = get_endpoint(*alts, 0); + if (!usb_endpoint_is_isoc_in(epd) || + (epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) != + USB_ENDPOINT_USAGE_IMPLICIT_FB) + return -ENOENT; + *ep = epd->bEndpointAddress; + return 0; +} + +/* Setup an implicit feedback endpoint from a quirk. Returns 0 if no quirk + * applies. Returns 1 if a quirk was found. + */ +static int set_sync_ep_implicit_fb_quirk(struct snd_usb_substream *subs, + struct usb_device *dev, + struct usb_interface_descriptor *altsd, + unsigned int attr) +{ + struct usb_host_interface *alts; + struct usb_interface *iface; + unsigned int ep; + unsigned int ifnum; + + /* Implicit feedback sync EPs consumers are always playback EPs */ + if (subs->direction != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + switch (subs->stream->chip->usb_id) { + case USB_ID(0x0763, 0x2030): /* M-Audio Fast Track C400 */ + case USB_ID(0x0763, 0x2031): /* M-Audio Fast Track C600 */ + case USB_ID(0x22f0, 0x0006): /* Allen&Heath Qu-16 */ + ep = 0x81; + ifnum = 3; + goto add_sync_ep_from_ifnum; + case USB_ID(0x0763, 0x2080): /* M-Audio FastTrack Ultra */ + case USB_ID(0x0763, 0x2081): + ep = 0x81; + ifnum = 2; + goto add_sync_ep_from_ifnum; + case USB_ID(0x2466, 0x8003): /* Fractal Audio Axe-Fx II */ + case USB_ID(0x0499, 0x172a): /* Yamaha MODX */ + ep = 0x86; + ifnum = 2; + goto add_sync_ep_from_ifnum; + case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx III */ + ep = 0x81; + ifnum = 2; + goto add_sync_ep_from_ifnum; + case USB_ID(0x1686, 0xf029): /* Zoom UAC-2 */ + ep = 0x82; + ifnum = 2; + goto add_sync_ep_from_ifnum; + case USB_ID(0x1397, 0x0001): /* Behringer UFX1604 */ + case USB_ID(0x1397, 0x0002): /* Behringer UFX1204 */ + ep = 0x81; + ifnum = 1; + goto add_sync_ep_from_ifnum; + case USB_ID(0x0582, 0x01d8): /* BOSS Katana */ + /* BOSS Katana amplifiers do not need quirks */ + return 0; + } + + if (attr == USB_ENDPOINT_SYNC_ASYNC && + altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC && + altsd->bInterfaceProtocol == 2 && + altsd->bNumEndpoints == 1 && + USB_ID_VENDOR(subs->stream->chip->usb_id) == 0x0582 /* Roland */ && + search_roland_implicit_fb(dev, altsd->bInterfaceNumber + 1, + altsd->bAlternateSetting, + &alts, &ep) >= 0) { + goto add_sync_ep; + } + + /* No quirk */ + return 0; + +add_sync_ep_from_ifnum: + iface = usb_ifnum_to_if(dev, ifnum); + + if (!iface || iface->num_altsetting < 2) + return -EINVAL; + + alts = &iface->altsetting[1]; + +add_sync_ep: + subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, + alts, ep, !subs->direction, + SND_USB_ENDPOINT_TYPE_DATA); + if (!subs->sync_endpoint) + return -EINVAL; + + subs->data_endpoint->sync_master = subs->sync_endpoint; + + return 1; +} + +static int set_sync_endpoint(struct snd_usb_substream *subs, + struct audioformat *fmt, + struct usb_device *dev, + struct usb_host_interface *alts, + struct usb_interface_descriptor *altsd) +{ + int is_playback = subs->direction == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int ep, attr; + bool implicit_fb; + int err; + + /* we need a sync pipe in async OUT or adaptive IN mode */ + /* check the number of EP, since some devices have broken + * descriptors which fool us. if it has only one EP, + * assume it as adaptive-out or sync-in. + */ + attr = fmt->ep_attr & USB_ENDPOINT_SYNCTYPE; + + if ((is_playback && (attr != USB_ENDPOINT_SYNC_ASYNC)) || + (!is_playback && (attr != USB_ENDPOINT_SYNC_ADAPTIVE))) { + + /* + * In these modes the notion of sync_endpoint is irrelevant. + * Reset pointers to avoid using stale data from previously + * used settings, e.g. when configuration and endpoints were + * changed + */ + + subs->sync_endpoint = NULL; + subs->data_endpoint->sync_master = NULL; + } + + err = set_sync_ep_implicit_fb_quirk(subs, dev, altsd, attr); + if (err < 0) + return err; + + /* endpoint set by quirk */ + if (err > 0) + return 0; + + if (altsd->bNumEndpoints < 2) + return 0; + + if ((is_playback && (attr == USB_ENDPOINT_SYNC_SYNC || + attr == USB_ENDPOINT_SYNC_ADAPTIVE)) || + (!is_playback && attr != USB_ENDPOINT_SYNC_ADAPTIVE)) + return 0; + + /* + * In case of illegal SYNC_NONE for OUT endpoint, we keep going to see + * if we don't find a sync endpoint, as on M-Audio Transit. In case of + * error fall back to SYNC mode and don't create sync endpoint + */ + + /* check sync-pipe endpoint */ + /* ... and check descriptor size before accessing bSynchAddress + because there is a version of the SB Audigy 2 NX firmware lacking + the audio fields in the endpoint descriptors */ + if ((get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_ISOC || + (get_endpoint(alts, 1)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 1)->bSynchAddress != 0)) { + dev_err(&dev->dev, + "%d:%d : invalid sync pipe. bmAttributes %02x, bLength %d, bSynchAddress %02x\n", + fmt->iface, fmt->altsetting, + get_endpoint(alts, 1)->bmAttributes, + get_endpoint(alts, 1)->bLength, + get_endpoint(alts, 1)->bSynchAddress); + if (is_playback && attr == USB_ENDPOINT_SYNC_NONE) + return 0; + return -EINVAL; + } + ep = get_endpoint(alts, 1)->bEndpointAddress; + if (get_endpoint(alts, 0)->bLength >= USB_DT_ENDPOINT_AUDIO_SIZE && + get_endpoint(alts, 0)->bSynchAddress != 0 && + ((is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress | USB_DIR_IN)) || + (!is_playback && ep != (unsigned int)(get_endpoint(alts, 0)->bSynchAddress & ~USB_DIR_IN)))) { + dev_err(&dev->dev, + "%d:%d : invalid sync pipe. is_playback %d, ep %02x, bSynchAddress %02x\n", + fmt->iface, fmt->altsetting, + is_playback, ep, get_endpoint(alts, 0)->bSynchAddress); + if (is_playback && attr == USB_ENDPOINT_SYNC_NONE) + return 0; + return -EINVAL; + } + + implicit_fb = (get_endpoint(alts, 1)->bmAttributes & USB_ENDPOINT_USAGE_MASK) + == USB_ENDPOINT_USAGE_IMPLICIT_FB; + + subs->sync_endpoint = snd_usb_add_endpoint(subs->stream->chip, + alts, ep, !subs->direction, + implicit_fb ? + SND_USB_ENDPOINT_TYPE_DATA : + SND_USB_ENDPOINT_TYPE_SYNC); + if (!subs->sync_endpoint) { + if (is_playback && attr == USB_ENDPOINT_SYNC_NONE) + return 0; + return -EINVAL; + } + + subs->data_endpoint->sync_master = subs->sync_endpoint; + + return 0; +} + +/* + * find a matching format and set up the interface + */ +static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt) +{ + struct usb_device *dev = subs->dev; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct usb_interface *iface; + int err; + + iface = usb_ifnum_to_if(dev, fmt->iface); + if (WARN_ON(!iface)) + return -EINVAL; + alts = usb_altnum_to_altsetting(iface, fmt->altsetting); + if (WARN_ON(!alts)) + return -EINVAL; + altsd = get_iface_desc(alts); + + if (fmt == subs->cur_audiofmt && !subs->need_setup_fmt) + return 0; + + /* close the old interface */ + if (subs->interface >= 0 && (subs->interface != fmt->iface || subs->need_setup_fmt)) { + if (!subs->stream->chip->keep_iface) { + err = usb_set_interface(subs->dev, subs->interface, 0); + if (err < 0) { + dev_err(&dev->dev, + "%d:%d: return to setting 0 failed (%d)\n", + fmt->iface, fmt->altsetting, err); + return -EIO; + } + } + subs->interface = -1; + subs->altset_idx = 0; + } + + if (subs->need_setup_fmt) + subs->need_setup_fmt = false; + + /* set interface */ + if (iface->cur_altsetting != alts) { + err = snd_usb_select_mode_quirk(subs, fmt); + if (err < 0) + return -EIO; + + err = usb_set_interface(dev, fmt->iface, fmt->altsetting); + if (err < 0) { + dev_err(&dev->dev, + "%d:%d: usb_set_interface failed (%d)\n", + fmt->iface, fmt->altsetting, err); + return -EIO; + } + dev_dbg(&dev->dev, "setting usb interface %d:%d\n", + fmt->iface, fmt->altsetting); + snd_usb_set_interface_quirk(dev); + } + + subs->interface = fmt->iface; + subs->altset_idx = fmt->altset_idx; + subs->data_endpoint = snd_usb_add_endpoint(subs->stream->chip, + alts, fmt->endpoint, subs->direction, + SND_USB_ENDPOINT_TYPE_DATA); + + if (!subs->data_endpoint) + return -EINVAL; + + err = set_sync_endpoint(subs, fmt, dev, alts, altsd); + if (err < 0) + return err; + + err = snd_usb_init_pitch(subs->stream->chip, fmt->iface, alts, fmt); + if (err < 0) + return err; + + subs->cur_audiofmt = fmt; + + snd_usb_set_format_quirk(subs, fmt); + + return 0; +} + +/* + * Return the score of matching two audioformats. + * Veto the audioformat if: + * - It has no channels for some reason. + * - Requested PCM format is not supported. + * - Requested sample rate is not supported. + */ +static int match_endpoint_audioformats(struct snd_usb_substream *subs, + struct audioformat *fp, + struct audioformat *match, int rate, + snd_pcm_format_t pcm_format) +{ + int i; + int score = 0; + + if (fp->channels < 1) { + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) no channels\n", __func__, fp); + return 0; + } + + if (!(fp->formats & pcm_format_to_bits(pcm_format))) { + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) no match for format %d\n", __func__, + fp, pcm_format); + return 0; + } + + for (i = 0; i < fp->nr_rates; i++) { + if (fp->rate_table[i] == rate) { + score++; + break; + } + } + if (!score) { + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) no match for rate %d\n", __func__, + fp, rate); + return 0; + } + + if (fp->channels == match->channels) + score++; + + dev_dbg(&subs->dev->dev, + "%s: (fmt @%p) score %d\n", __func__, fp, score); + + return score; +} + +/* + * Configure the sync ep using the rate and pcm format of the data ep. + */ +static int configure_sync_endpoint(struct snd_usb_substream *subs) +{ + int ret; + struct audioformat *fp; + struct audioformat *sync_fp = NULL; + int cur_score = 0; + int sync_period_bytes = subs->period_bytes; + struct snd_usb_substream *sync_subs = + &subs->stream->substream[subs->direction ^ 1]; + + if (subs->sync_endpoint->type != SND_USB_ENDPOINT_TYPE_DATA || + !subs->stream) + return snd_usb_endpoint_set_params(subs->sync_endpoint, + subs->pcm_format, + subs->channels, + subs->period_bytes, + 0, 0, + subs->cur_rate, + subs->cur_audiofmt, + NULL); + + /* Try to find the best matching audioformat. */ + list_for_each_entry(fp, &sync_subs->fmt_list, list) { + int score = match_endpoint_audioformats(subs, + fp, subs->cur_audiofmt, + subs->cur_rate, subs->pcm_format); + + if (score > cur_score) { + sync_fp = fp; + cur_score = score; + } + } + + if (unlikely(sync_fp == NULL)) { + dev_err(&subs->dev->dev, + "%s: no valid audioformat for sync ep %x found\n", + __func__, sync_subs->ep_num); + return -EINVAL; + } + + /* + * Recalculate the period bytes if channel number differ between + * data and sync ep audioformat. + */ + if (sync_fp->channels != subs->channels) { + sync_period_bytes = (subs->period_bytes / subs->channels) * + sync_fp->channels; + dev_dbg(&subs->dev->dev, + "%s: adjusted sync ep period bytes (%d -> %d)\n", + __func__, subs->period_bytes, sync_period_bytes); + } + + ret = snd_usb_endpoint_set_params(subs->sync_endpoint, + subs->pcm_format, + sync_fp->channels, + sync_period_bytes, + 0, 0, + subs->cur_rate, + sync_fp, + NULL); + + return ret; +} + +/* + * configure endpoint params + * + * called during initial setup and upon resume + */ +static int configure_endpoint(struct snd_usb_substream *subs) +{ + int ret; + + /* format changed */ + stop_endpoints(subs, true); + ret = snd_usb_endpoint_set_params(subs->data_endpoint, + subs->pcm_format, + subs->channels, + subs->period_bytes, + subs->period_frames, + subs->buffer_periods, + subs->cur_rate, + subs->cur_audiofmt, + subs->sync_endpoint); + if (ret < 0) + return ret; + + if (subs->sync_endpoint) + ret = configure_sync_endpoint(subs); + + return ret; +} + +static int snd_usb_pcm_change_state(struct snd_usb_substream *subs, int state) +{ + int ret; + + if (!subs->str_pd) + return 0; + + ret = snd_usb_power_domain_set(subs->stream->chip, subs->str_pd, state); + if (ret < 0) { + dev_err(&subs->dev->dev, + "Cannot change Power Domain ID: %d to state: %d. Err: %d\n", + subs->str_pd->pd_id, state, ret); + return ret; + } + + return 0; +} + +int snd_usb_pcm_suspend(struct snd_usb_stream *as) +{ + int ret; + + ret = snd_usb_pcm_change_state(&as->substream[0], UAC3_PD_STATE_D2); + if (ret < 0) + return ret; + + ret = snd_usb_pcm_change_state(&as->substream[1], UAC3_PD_STATE_D2); + if (ret < 0) + return ret; + + return 0; +} + +int snd_usb_pcm_resume(struct snd_usb_stream *as) +{ + int ret; + + ret = snd_usb_pcm_change_state(&as->substream[0], UAC3_PD_STATE_D1); + if (ret < 0) + return ret; + + ret = snd_usb_pcm_change_state(&as->substream[1], UAC3_PD_STATE_D1); + if (ret < 0) + return ret; + + return 0; +} + +/* + * hw_params callback + * + * allocate a buffer and set the given audio format. + * + * so far we use a physically linear buffer although packetize transfer + * doesn't need a continuous area. + * if sg buffer is supported on the later version of alsa, we'll follow + * that. + */ +static int snd_usb_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + struct audioformat *fmt; + int ret; + + if (snd_usb_use_vmalloc) + ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + else + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + + subs->pcm_format = params_format(hw_params); + subs->period_bytes = params_period_bytes(hw_params); + subs->period_frames = params_period_size(hw_params); + subs->buffer_periods = params_periods(hw_params); + subs->channels = params_channels(hw_params); + subs->cur_rate = params_rate(hw_params); + + fmt = find_format(subs); + if (!fmt) { + dev_dbg(&subs->dev->dev, + "cannot set format: format = %#x, rate = %d, channels = %d\n", + subs->pcm_format, subs->cur_rate, subs->channels); + return -EINVAL; + } + + ret = snd_usb_lock_shutdown(subs->stream->chip); + if (ret < 0) + return ret; + + ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0); + if (ret < 0) + goto unlock; + + ret = set_format(subs, fmt); + if (ret < 0) + goto unlock; + + subs->interface = fmt->iface; + subs->altset_idx = fmt->altset_idx; + subs->need_setup_ep = true; + + unlock: + snd_usb_unlock_shutdown(subs->stream->chip); + return ret; +} + +/* + * hw_free callback + * + * reset the audio format and release the buffer + */ +static int snd_usb_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + + subs->cur_audiofmt = NULL; + subs->cur_rate = 0; + subs->period_bytes = 0; + if (!snd_usb_lock_shutdown(subs->stream->chip)) { + stop_endpoints(subs, true); + snd_usb_endpoint_deactivate(subs->sync_endpoint); + snd_usb_endpoint_deactivate(subs->data_endpoint); + snd_usb_unlock_shutdown(subs->stream->chip); + } + + if (snd_usb_use_vmalloc) + return snd_pcm_lib_free_vmalloc_buffer(substream); + else + return snd_pcm_lib_free_pages(substream); +} + +/* + * prepare callback + * + * only a few subtle things... + */ +static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usb_substream *subs = runtime->private_data; + struct usb_host_interface *alts; + struct usb_interface *iface; + int ret; + + if (! subs->cur_audiofmt) { + dev_err(&subs->dev->dev, "no format is specified!\n"); + return -ENXIO; + } + + ret = snd_usb_lock_shutdown(subs->stream->chip); + if (ret < 0) + return ret; + if (snd_BUG_ON(!subs->data_endpoint)) { + ret = -EIO; + goto unlock; + } + + snd_usb_endpoint_sync_pending_stop(subs->sync_endpoint); + snd_usb_endpoint_sync_pending_stop(subs->data_endpoint); + + ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D0); + if (ret < 0) + goto unlock; + + ret = set_format(subs, subs->cur_audiofmt); + if (ret < 0) + goto unlock; + + if (subs->need_setup_ep) { + + iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface); + alts = &iface->altsetting[subs->cur_audiofmt->altset_idx]; + ret = snd_usb_init_sample_rate(subs->stream->chip, + subs->cur_audiofmt->iface, + alts, + subs->cur_audiofmt, + subs->cur_rate); + if (ret < 0) + goto unlock; + + ret = configure_endpoint(subs); + if (ret < 0) + goto unlock; + subs->need_setup_ep = false; + } + + /* some unit conversions in runtime */ + subs->data_endpoint->maxframesize = + bytes_to_frames(runtime, subs->data_endpoint->maxpacksize); + subs->data_endpoint->curframesize = + bytes_to_frames(runtime, subs->data_endpoint->curpacksize); + + /* reset the pointer */ + subs->hwptr_done = 0; + subs->transfer_done = 0; + subs->last_delay = 0; + subs->last_frame_number = 0; + runtime->delay = 0; + + /* for playback, submit the URBs now; otherwise, the first hwptr_done + * updates for all URBs would happen at the same time when starting */ + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) + ret = start_endpoints(subs); + + unlock: + snd_usb_unlock_shutdown(subs->stream->chip); + return ret; +} + +static const struct snd_pcm_hardware snd_usb_hardware = +{ + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE, + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 512 * 1024, + .periods_min = 2, + .periods_max = 1024, +}; + +static int hw_check_valid_format(struct snd_usb_substream *subs, + struct snd_pcm_hw_params *params, + struct audioformat *fp) +{ + struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); + struct snd_mask check_fmts; + unsigned int ptime; + + /* check the format */ + snd_mask_none(&check_fmts); + check_fmts.bits[0] = (u32)fp->formats; + check_fmts.bits[1] = (u32)(fp->formats >> 32); + snd_mask_intersect(&check_fmts, fmts); + if (snd_mask_empty(&check_fmts)) { + hwc_debug(" > check: no supported format %d\n", fp->format); + return 0; + } + /* check the channels */ + if (fp->channels < ct->min || fp->channels > ct->max) { + hwc_debug(" > check: no valid channels %d (%d/%d)\n", fp->channels, ct->min, ct->max); + return 0; + } + /* check the rate is within the range */ + if (fp->rate_min > it->max || (fp->rate_min == it->max && it->openmax)) { + hwc_debug(" > check: rate_min %d > max %d\n", fp->rate_min, it->max); + return 0; + } + if (fp->rate_max < it->min || (fp->rate_max == it->min && it->openmin)) { + hwc_debug(" > check: rate_max %d < min %d\n", fp->rate_max, it->min); + return 0; + } + /* check whether the period time is >= the data packet interval */ + if (subs->speed != USB_SPEED_FULL) { + ptime = 125 * (1 << fp->datainterval); + if (ptime > pt->max || (ptime == pt->max && pt->openmax)) { + hwc_debug(" > check: ptime %u > max %u\n", ptime, pt->max); + return 0; + } + } + return 1; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct audioformat *fp; + struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + unsigned int rmin, rmax; + int changed; + + hwc_debug("hw_rule_rate: (%d,%d)\n", it->min, it->max); + changed = 0; + rmin = rmax = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (!hw_check_valid_format(subs, params, fp)) + continue; + if (changed++) { + if (rmin > fp->rate_min) + rmin = fp->rate_min; + if (rmax < fp->rate_max) + rmax = fp->rate_max; + } else { + rmin = fp->rate_min; + rmax = fp->rate_max; + } + } + + if (!changed) { + hwc_debug(" --> get empty\n"); + it->empty = 1; + return -EINVAL; + } + + changed = 0; + if (it->min < rmin) { + it->min = rmin; + it->openmin = 0; + changed = 1; + } + if (it->max > rmax) { + it->max = rmax; + it->openmax = 0; + changed = 1; + } + if (snd_interval_checkempty(it)) { + it->empty = 1; + return -EINVAL; + } + hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); + return changed; +} + + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct audioformat *fp; + struct snd_interval *it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + unsigned int rmin, rmax; + int changed; + + hwc_debug("hw_rule_channels: (%d,%d)\n", it->min, it->max); + changed = 0; + rmin = rmax = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (!hw_check_valid_format(subs, params, fp)) + continue; + if (changed++) { + if (rmin > fp->channels) + rmin = fp->channels; + if (rmax < fp->channels) + rmax = fp->channels; + } else { + rmin = fp->channels; + rmax = fp->channels; + } + } + + if (!changed) { + hwc_debug(" --> get empty\n"); + it->empty = 1; + return -EINVAL; + } + + changed = 0; + if (it->min < rmin) { + it->min = rmin; + it->openmin = 0; + changed = 1; + } + if (it->max > rmax) { + it->max = rmax; + it->openmax = 0; + changed = 1; + } + if (snd_interval_checkempty(it)) { + it->empty = 1; + return -EINVAL; + } + hwc_debug(" --> (%d, %d) (changed = %d)\n", it->min, it->max, changed); + return changed; +} + +static int hw_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct audioformat *fp; + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + u64 fbits; + u32 oldbits[2]; + int changed; + + hwc_debug("hw_rule_format: %x:%x\n", fmt->bits[0], fmt->bits[1]); + fbits = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (!hw_check_valid_format(subs, params, fp)) + continue; + fbits |= fp->formats; + } + + oldbits[0] = fmt->bits[0]; + oldbits[1] = fmt->bits[1]; + fmt->bits[0] &= (u32)fbits; + fmt->bits[1] &= (u32)(fbits >> 32); + if (!fmt->bits[0] && !fmt->bits[1]) { + hwc_debug(" --> get empty\n"); + return -EINVAL; + } + changed = (oldbits[0] != fmt->bits[0] || oldbits[1] != fmt->bits[1]); + hwc_debug(" --> %x:%x (changed = %d)\n", fmt->bits[0], fmt->bits[1], changed); + return changed; +} + +static int hw_rule_period_time(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_usb_substream *subs = rule->private; + struct audioformat *fp; + struct snd_interval *it; + unsigned char min_datainterval; + unsigned int pmin; + int changed; + + it = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME); + hwc_debug("hw_rule_period_time: (%u,%u)\n", it->min, it->max); + min_datainterval = 0xff; + list_for_each_entry(fp, &subs->fmt_list, list) { + if (!hw_check_valid_format(subs, params, fp)) + continue; + min_datainterval = min(min_datainterval, fp->datainterval); + } + if (min_datainterval == 0xff) { + hwc_debug(" --> get empty\n"); + it->empty = 1; + return -EINVAL; + } + pmin = 125 * (1 << min_datainterval); + changed = 0; + if (it->min < pmin) { + it->min = pmin; + it->openmin = 0; + changed = 1; + } + if (snd_interval_checkempty(it)) { + it->empty = 1; + return -EINVAL; + } + hwc_debug(" --> (%u,%u) (changed = %d)\n", it->min, it->max, changed); + return changed; +} + +/* + * If the device supports unusual bit rates, does the request meet these? + */ +static int snd_usb_pcm_check_knot(struct snd_pcm_runtime *runtime, + struct snd_usb_substream *subs) +{ + struct audioformat *fp; + int *rate_list; + int count = 0, needs_knot = 0; + int err; + + kfree(subs->rate_list.list); + subs->rate_list.list = NULL; + + list_for_each_entry(fp, &subs->fmt_list, list) { + if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) + return 0; + count += fp->nr_rates; + if (fp->rates & SNDRV_PCM_RATE_KNOT) + needs_knot = 1; + } + if (!needs_knot) + return 0; + + subs->rate_list.list = rate_list = + kmalloc_array(count, sizeof(int), GFP_KERNEL); + if (!subs->rate_list.list) + return -ENOMEM; + subs->rate_list.count = count; + subs->rate_list.mask = 0; + count = 0; + list_for_each_entry(fp, &subs->fmt_list, list) { + int i; + for (i = 0; i < fp->nr_rates; i++) + rate_list[count++] = fp->rate_table[i]; + } + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &subs->rate_list); + if (err < 0) + return err; + + return 0; +} + + +/* + * set up the runtime hardware information. + */ + +static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substream *subs) +{ + struct audioformat *fp; + unsigned int pt, ptmin; + int param_period_time_if_needed; + int err; + + runtime->hw.formats = subs->formats; + + runtime->hw.rate_min = 0x7fffffff; + runtime->hw.rate_max = 0; + runtime->hw.channels_min = 256; + runtime->hw.channels_max = 0; + runtime->hw.rates = 0; + ptmin = UINT_MAX; + /* check min/max rates and channels */ + list_for_each_entry(fp, &subs->fmt_list, list) { + runtime->hw.rates |= fp->rates; + if (runtime->hw.rate_min > fp->rate_min) + runtime->hw.rate_min = fp->rate_min; + if (runtime->hw.rate_max < fp->rate_max) + runtime->hw.rate_max = fp->rate_max; + if (runtime->hw.channels_min > fp->channels) + runtime->hw.channels_min = fp->channels; + if (runtime->hw.channels_max < fp->channels) + runtime->hw.channels_max = fp->channels; + if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) { + /* FIXME: there might be more than one audio formats... */ + runtime->hw.period_bytes_min = runtime->hw.period_bytes_max = + fp->frame_size; + } + pt = 125 * (1 << fp->datainterval); + ptmin = min(ptmin, pt); + } + + param_period_time_if_needed = SNDRV_PCM_HW_PARAM_PERIOD_TIME; + if (subs->speed == USB_SPEED_FULL) + /* full speed devices have fixed data packet interval */ + ptmin = 1000; + if (ptmin == 1000) + /* if period time doesn't go below 1 ms, no rules needed */ + param_period_time_if_needed = -1; + + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + ptmin, UINT_MAX); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, + param_period_time_if_needed, + -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_RATE, + param_period_time_if_needed, + -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_format, subs, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_CHANNELS, + param_period_time_if_needed, + -1); + if (err < 0) + return err; + if (param_period_time_if_needed >= 0) { + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + hw_rule_period_time, subs, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, + -1); + if (err < 0) + return err; + } + err = snd_usb_pcm_check_knot(runtime, subs); + if (err < 0) + return err; + + return snd_usb_autoresume(subs->stream->chip); +} + +static int snd_usb_pcm_open(struct snd_pcm_substream *substream) +{ + int direction = substream->stream; + struct snd_usb_stream *as = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usb_substream *subs = &as->substream[direction]; + + subs->interface = -1; + subs->altset_idx = 0; + runtime->hw = snd_usb_hardware; + runtime->private_data = subs; + subs->pcm_substream = substream; + /* runtime PM is also done there */ + + /* initialize DSD/DOP context */ + subs->dsd_dop.byte_idx = 0; + subs->dsd_dop.channel = 0; + subs->dsd_dop.marker = 1; + + return setup_hw_info(runtime, subs); +} + +static int snd_usb_pcm_close(struct snd_pcm_substream *substream) +{ + int direction = substream->stream; + struct snd_usb_stream *as = snd_pcm_substream_chip(substream); + struct snd_usb_substream *subs = &as->substream[direction]; + int ret; + + stop_endpoints(subs, true); + + if (!as->chip->keep_iface && + subs->interface >= 0 && + !snd_usb_lock_shutdown(subs->stream->chip)) { + usb_set_interface(subs->dev, subs->interface, 0); + subs->interface = -1; + ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D1); + snd_usb_unlock_shutdown(subs->stream->chip); + if (ret < 0) + return ret; + } + + subs->pcm_substream = NULL; + snd_usb_autosuspend(subs->stream->chip); + + return 0; +} + +/* Since a URB can handle only a single linear buffer, we must use double + * buffering when the data to be transferred overflows the buffer boundary. + * To avoid inconsistencies when updating hwptr_done, we use double buffering + * for all URBs. + */ +static void retire_capture_urb(struct snd_usb_substream *subs, + struct urb *urb) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + unsigned int stride, frames, bytes, oldptr; + int i, period_elapsed = 0; + unsigned long flags; + unsigned char *cp; + int current_frame_number; + + /* read frame number here, update pointer in critical section */ + current_frame_number = usb_get_current_frame_number(subs->dev); + + stride = runtime->frame_bits >> 3; + + for (i = 0; i < urb->number_of_packets; i++) { + cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset + subs->pkt_offset_adj; + if (urb->iso_frame_desc[i].status && printk_ratelimit()) { + dev_dbg(&subs->dev->dev, "frame %d active: %d\n", + i, urb->iso_frame_desc[i].status); + // continue; + } + bytes = urb->iso_frame_desc[i].actual_length; + if (subs->stream_offset_adj > 0) { + unsigned int adj = min(subs->stream_offset_adj, bytes); + cp += adj; + bytes -= adj; + subs->stream_offset_adj -= adj; + } + frames = bytes / stride; + if (!subs->txfr_quirk) + bytes = frames * stride; + if (bytes % (runtime->sample_bits >> 3) != 0) { + int oldbytes = bytes; + bytes = frames * stride; + dev_warn_ratelimited(&subs->dev->dev, + "Corrected urb data len. %d->%d\n", + oldbytes, bytes); + } + /* update the current pointer */ + spin_lock_irqsave(&subs->lock, flags); + oldptr = subs->hwptr_done; + subs->hwptr_done += bytes; + if (subs->hwptr_done >= runtime->buffer_size * stride) + subs->hwptr_done -= runtime->buffer_size * stride; + frames = (bytes + (oldptr % stride)) / stride; + subs->transfer_done += frames; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + period_elapsed = 1; + } + /* capture delay is by construction limited to one URB, + * reset delays here + */ + runtime->delay = subs->last_delay = 0; + + /* realign last_frame_number */ + subs->last_frame_number = current_frame_number; + subs->last_frame_number &= 0xFF; /* keep 8 LSBs */ + + spin_unlock_irqrestore(&subs->lock, flags); + /* copy a data chunk */ + if (oldptr + bytes > runtime->buffer_size * stride) { + unsigned int bytes1 = + runtime->buffer_size * stride - oldptr; + memcpy(runtime->dma_area + oldptr, cp, bytes1); + memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1); + } else { + memcpy(runtime->dma_area + oldptr, cp, bytes); + } + } + + if (period_elapsed) + snd_pcm_period_elapsed(subs->pcm_substream); +} + +static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs, + struct urb *urb, unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + unsigned int stride = runtime->frame_bits >> 3; + unsigned int dst_idx = 0; + unsigned int src_idx = subs->hwptr_done; + unsigned int wrap = runtime->buffer_size * stride; + u8 *dst = urb->transfer_buffer; + u8 *src = runtime->dma_area; + u8 marker[] = { 0x05, 0xfa }; + + /* + * The DSP DOP format defines a way to transport DSD samples over + * normal PCM data endpoints. It requires stuffing of marker bytes + * (0x05 and 0xfa, alternating per sample frame), and then expects + * 2 additional bytes of actual payload. The whole frame is stored + * LSB. + * + * Hence, for a stereo transport, the buffer layout looks like this, + * where L refers to left channel samples and R to right. + * + * L1 L2 0x05 R1 R2 0x05 L3 L4 0xfa R3 R4 0xfa + * L5 L6 0x05 R5 R6 0x05 L7 L8 0xfa R7 R8 0xfa + * ..... + * + */ + + while (bytes--) { + if (++subs->dsd_dop.byte_idx == 3) { + /* frame boundary? */ + dst[dst_idx++] = marker[subs->dsd_dop.marker]; + src_idx += 2; + subs->dsd_dop.byte_idx = 0; + + if (++subs->dsd_dop.channel % runtime->channels == 0) { + /* alternate the marker */ + subs->dsd_dop.marker++; + subs->dsd_dop.marker %= ARRAY_SIZE(marker); + subs->dsd_dop.channel = 0; + } + } else { + /* stuff the DSD payload */ + int idx = (src_idx + subs->dsd_dop.byte_idx - 1) % wrap; + + if (subs->cur_audiofmt->dsd_bitrev) + dst[dst_idx++] = bitrev8(src[idx]); + else + dst[dst_idx++] = src[idx]; + + subs->hwptr_done++; + } + } + if (subs->hwptr_done >= runtime->buffer_size * stride) + subs->hwptr_done -= runtime->buffer_size * stride; +} + +static void copy_to_urb(struct snd_usb_substream *subs, struct urb *urb, + int offset, int stride, unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + + if (subs->hwptr_done + bytes > runtime->buffer_size * stride) { + /* err, the transferred area goes over buffer boundary. */ + unsigned int bytes1 = + runtime->buffer_size * stride - subs->hwptr_done; + memcpy(urb->transfer_buffer + offset, + runtime->dma_area + subs->hwptr_done, bytes1); + memcpy(urb->transfer_buffer + offset + bytes1, + runtime->dma_area, bytes - bytes1); + } else { + memcpy(urb->transfer_buffer + offset, + runtime->dma_area + subs->hwptr_done, bytes); + } + subs->hwptr_done += bytes; + if (subs->hwptr_done >= runtime->buffer_size * stride) + subs->hwptr_done -= runtime->buffer_size * stride; +} + +static unsigned int copy_to_urb_quirk(struct snd_usb_substream *subs, + struct urb *urb, int stride, + unsigned int bytes) +{ + __le32 packet_length; + int i; + + /* Put __le32 length descriptor at start of each packet. */ + for (i = 0; i < urb->number_of_packets; i++) { + unsigned int length = urb->iso_frame_desc[i].length; + unsigned int offset = urb->iso_frame_desc[i].offset; + + packet_length = cpu_to_le32(length); + offset += i * sizeof(packet_length); + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length += sizeof(packet_length); + memcpy(urb->transfer_buffer + offset, + &packet_length, sizeof(packet_length)); + copy_to_urb(subs, urb, offset + sizeof(packet_length), + stride, length); + } + /* Adjust transfer size accordingly. */ + bytes += urb->number_of_packets * sizeof(packet_length); + return bytes; +} + +static void prepare_playback_urb(struct snd_usb_substream *subs, + struct urb *urb) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + struct snd_usb_endpoint *ep = subs->data_endpoint; + struct snd_urb_ctx *ctx = urb->context; + unsigned int counts, frames, bytes; + int i, stride, period_elapsed = 0; + unsigned long flags; + + stride = runtime->frame_bits >> 3; + + frames = 0; + urb->number_of_packets = 0; + spin_lock_irqsave(&subs->lock, flags); + subs->frame_limit += ep->max_urb_frames; + for (i = 0; i < ctx->packets; i++) { + if (ctx->packet_size[i]) + counts = ctx->packet_size[i]; + else + counts = snd_usb_endpoint_next_packet_size(ep); + + /* set up descriptor */ + urb->iso_frame_desc[i].offset = frames * ep->stride; + urb->iso_frame_desc[i].length = counts * ep->stride; + frames += counts; + urb->number_of_packets++; + subs->transfer_done += counts; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + subs->frame_limit = 0; + period_elapsed = 1; + if (subs->fmt_type == UAC_FORMAT_TYPE_II) { + if (subs->transfer_done > 0) { + /* FIXME: fill-max mode is not + * supported yet */ + frames -= subs->transfer_done; + counts -= subs->transfer_done; + urb->iso_frame_desc[i].length = + counts * ep->stride; + subs->transfer_done = 0; + } + i++; + if (i < ctx->packets) { + /* add a transfer delimiter */ + urb->iso_frame_desc[i].offset = + frames * ep->stride; + urb->iso_frame_desc[i].length = 0; + urb->number_of_packets++; + } + break; + } + } + /* finish at the period boundary or after enough frames */ + if ((period_elapsed || + subs->transfer_done >= subs->frame_limit) && + !snd_usb_endpoint_implicit_feedback_sink(ep)) + break; + } + bytes = frames * ep->stride; + + if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && + subs->cur_audiofmt->dsd_dop)) { + fill_playback_urb_dsd_dop(subs, urb, bytes); + } else if (unlikely(subs->pcm_format == SNDRV_PCM_FORMAT_DSD_U8 && + subs->cur_audiofmt->dsd_bitrev)) { + /* bit-reverse the bytes */ + u8 *buf = urb->transfer_buffer; + for (i = 0; i < bytes; i++) { + int idx = (subs->hwptr_done + i) + % (runtime->buffer_size * stride); + buf[i] = bitrev8(runtime->dma_area[idx]); + } + + subs->hwptr_done += bytes; + if (subs->hwptr_done >= runtime->buffer_size * stride) + subs->hwptr_done -= runtime->buffer_size * stride; + } else { + /* usual PCM */ + if (!subs->tx_length_quirk) + copy_to_urb(subs, urb, 0, stride, bytes); + else + bytes = copy_to_urb_quirk(subs, urb, stride, bytes); + /* bytes is now amount of outgoing data */ + } + + /* update delay with exact number of samples queued */ + runtime->delay = subs->last_delay; + runtime->delay += frames; + subs->last_delay = runtime->delay; + + /* realign last_frame_number */ + subs->last_frame_number = usb_get_current_frame_number(subs->dev); + subs->last_frame_number &= 0xFF; /* keep 8 LSBs */ + + if (subs->trigger_tstamp_pending_update) { + /* this is the first actual URB submitted, + * update trigger timestamp to reflect actual start time + */ + snd_pcm_gettime(runtime, &runtime->trigger_tstamp); + subs->trigger_tstamp_pending_update = false; + } + + spin_unlock_irqrestore(&subs->lock, flags); + urb->transfer_buffer_length = bytes; + if (period_elapsed) + snd_pcm_period_elapsed(subs->pcm_substream); +} + +/* + * process after playback data complete + * - decrease the delay count again + */ +static void retire_playback_urb(struct snd_usb_substream *subs, + struct urb *urb) +{ + unsigned long flags; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + struct snd_usb_endpoint *ep = subs->data_endpoint; + int processed = urb->transfer_buffer_length / ep->stride; + int est_delay; + + /* ignore the delay accounting when procssed=0 is given, i.e. + * silent payloads are procssed before handling the actual data + */ + if (!processed) + return; + + spin_lock_irqsave(&subs->lock, flags); + if (!subs->last_delay) + goto out; /* short path */ + + est_delay = snd_usb_pcm_delay(subs, runtime->rate); + /* update delay with exact number of samples played */ + if (processed > subs->last_delay) + subs->last_delay = 0; + else + subs->last_delay -= processed; + runtime->delay = subs->last_delay; + + /* + * Report when delay estimate is off by more than 2ms. + * The error should be lower than 2ms since the estimate relies + * on two reads of a counter updated every ms. + */ + if (abs(est_delay - subs->last_delay) * 1000 > runtime->rate * 2) + dev_dbg_ratelimited(&subs->dev->dev, + "delay: estimated %d, actual %d\n", + est_delay, subs->last_delay); + + if (!subs->running) { + /* update last_frame_number for delay counting here since + * prepare_playback_urb won't be called during pause + */ + subs->last_frame_number = + usb_get_current_frame_number(subs->dev) & 0xff; + } + + out: + spin_unlock_irqrestore(&subs->lock, flags); +} + +static int snd_usb_substream_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_usb_substream *subs = substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + subs->trigger_tstamp_pending_update = true; + /* fall through */ + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + subs->data_endpoint->prepare_data_urb = prepare_playback_urb; + subs->data_endpoint->retire_data_urb = retire_playback_urb; + subs->running = 1; + return 0; + case SNDRV_PCM_TRIGGER_STOP: + stop_endpoints(subs, false); + subs->running = 0; + return 0; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + subs->data_endpoint->prepare_data_urb = NULL; + /* keep retire_data_urb for delay calculation */ + subs->data_endpoint->retire_data_urb = retire_playback_urb; + subs->running = 0; + return 0; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (subs->stream->chip->setup_fmt_after_resume_quirk) { + stop_endpoints(subs, true); + subs->need_setup_fmt = true; + return 0; + } + break; + } + + return -EINVAL; +} + +static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + int err; + struct snd_usb_substream *subs = substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = start_endpoints(subs); + if (err < 0) + return err; + + subs->data_endpoint->retire_data_urb = retire_capture_urb; + subs->running = 1; + return 0; + case SNDRV_PCM_TRIGGER_STOP: + stop_endpoints(subs, false); + subs->running = 0; + return 0; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + subs->data_endpoint->retire_data_urb = NULL; + subs->running = 0; + return 0; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + subs->data_endpoint->retire_data_urb = retire_capture_urb; + subs->running = 1; + return 0; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (subs->stream->chip->setup_fmt_after_resume_quirk) { + stop_endpoints(subs, true); + subs->need_setup_fmt = true; + return 0; + } + break; + } + + return -EINVAL; +} + +static const struct snd_pcm_ops snd_usb_playback_ops = { + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_playback_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct snd_pcm_ops snd_usb_capture_ops = { + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_capture_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct snd_pcm_ops snd_usb_playback_dev_ops = { + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_playback_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +static const struct snd_pcm_ops snd_usb_capture_dev_ops = { + .open = snd_usb_pcm_open, + .close = snd_usb_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usb_hw_params, + .hw_free = snd_usb_hw_free, + .prepare = snd_usb_pcm_prepare, + .trigger = snd_usb_substream_capture_trigger, + .pointer = snd_usb_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream) +{ + const struct snd_pcm_ops *ops; + + if (snd_usb_use_vmalloc) + ops = stream == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_usb_playback_ops : &snd_usb_capture_ops; + else + ops = stream == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_usb_playback_dev_ops : &snd_usb_capture_dev_ops; + snd_pcm_set_ops(pcm, stream, ops); +} + +void snd_usb_preallocate_buffer(struct snd_usb_substream *subs) +{ + struct snd_pcm *pcm = subs->stream->pcm; + struct snd_pcm_substream *s = pcm->streams[subs->direction].substream; + struct device *dev = subs->dev->bus->sysdev; + + if (!snd_usb_use_vmalloc) + snd_pcm_lib_preallocate_pages(s, SNDRV_DMA_TYPE_DEV_SG, + dev, 64*1024, 512*1024); +} diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h new file mode 100644 index 000000000..9833627c1 --- /dev/null +++ b/sound/usb/pcm.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_PCM_H +#define __USBAUDIO_PCM_H + +snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs, + unsigned int rate); + +void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream); +int snd_usb_pcm_suspend(struct snd_usb_stream *as); +int snd_usb_pcm_resume(struct snd_usb_stream *as); + +int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt); +void snd_usb_preallocate_buffer(struct snd_usb_substream *subs); + + +#endif /* __USBAUDIO_PCM_H */ diff --git a/sound/usb/power.c b/sound/usb/power.c new file mode 100644 index 000000000..606a2cb23 --- /dev/null +++ b/sound/usb/power.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UAC3 Power Domain state management functions + */ + +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "helper.h" +#include "power.h" + +struct snd_usb_power_domain * +snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, + unsigned char id) +{ + struct snd_usb_power_domain *pd; + void *p; + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return NULL; + + p = NULL; + while ((p = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + p, UAC3_POWER_DOMAIN)) != NULL) { + struct uac3_power_domain_descriptor *pd_desc = p; + int i; + + if (!snd_usb_validate_audio_desc(p, UAC_VERSION_3)) + continue; + for (i = 0; i < pd_desc->bNrEntities; i++) { + if (pd_desc->baEntityID[i] == id) { + pd->pd_id = pd_desc->bPowerDomainID; + pd->pd_d1d0_rec = + le16_to_cpu(pd_desc->waRecoveryTime1); + pd->pd_d2d0_rec = + le16_to_cpu(pd_desc->waRecoveryTime2); + return pd; + } + } + } + + kfree(pd); + return NULL; +} + +int snd_usb_power_domain_set(struct snd_usb_audio *chip, + struct snd_usb_power_domain *pd, + unsigned char state) +{ + struct usb_device *dev = chip->dev; + unsigned char current_state; + int err, idx; + + idx = snd_usb_ctrl_intf(chip) | (pd->pd_id << 8); + + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx, + ¤t_state, sizeof(current_state)); + if (err < 0) { + dev_err(&dev->dev, "Can't get UAC3 power state for id %d\n", + pd->pd_id); + return err; + } + + if (current_state == state) { + dev_dbg(&dev->dev, "UAC3 power domain id %d already in state %d\n", + pd->pd_id, state); + return 0; + } + + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx, + &state, sizeof(state)); + if (err < 0) { + dev_err(&dev->dev, "Can't set UAC3 power state to %d for id %d\n", + state, pd->pd_id); + return err; + } + + if (state == UAC3_PD_STATE_D0) { + switch (current_state) { + case UAC3_PD_STATE_D2: + udelay(pd->pd_d2d0_rec * 50); + break; + case UAC3_PD_STATE_D1: + udelay(pd->pd_d1d0_rec * 50); + break; + default: + return -EINVAL; + } + } + + dev_dbg(&dev->dev, "UAC3 power domain id %d change to state %d\n", + pd->pd_id, state); + + return 0; +} diff --git a/sound/usb/power.h b/sound/usb/power.h new file mode 100644 index 000000000..6004231a7 --- /dev/null +++ b/sound/usb/power.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_POWER_H +#define __USBAUDIO_POWER_H + +struct snd_usb_power_domain { + int pd_id; /* UAC3 Power Domain ID */ + int pd_d1d0_rec; /* D1 to D0 recovery time */ + int pd_d2d0_rec; /* D2 to D0 recovery time */ +}; + +enum { + UAC3_PD_STATE_D0, + UAC3_PD_STATE_D1, + UAC3_PD_STATE_D2, +}; + +int snd_usb_power_domain_set(struct snd_usb_audio *chip, + struct snd_usb_power_domain *pd, + unsigned char state); +struct snd_usb_power_domain * +snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, + unsigned char id); + +#ifdef CONFIG_PM +int snd_usb_autoresume(struct snd_usb_audio *chip); +void snd_usb_autosuspend(struct snd_usb_audio *chip); +#else +static inline int snd_usb_autoresume(struct snd_usb_audio *chip) +{ + return 0; +} +static inline void snd_usb_autosuspend(struct snd_usb_audio *chip) +{ +} +#endif + +#endif /* __USBAUDIO_POWER_H */ diff --git a/sound/usb/proc.c b/sound/usb/proc.c new file mode 100644 index 000000000..28192b0b2 --- /dev/null +++ b/sound/usb/proc.c @@ -0,0 +1,178 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include + +#include +#include +#include + +#include "usbaudio.h" +#include "helper.h" +#include "card.h" +#include "endpoint.h" +#include "proc.h" + +/* convert our full speed USB rate into sampling rate in Hz */ +static inline unsigned get_full_speed_hz(unsigned int usb_rate) +{ + return (usb_rate * 125 + (1 << 12)) >> 13; +} + +/* convert our high speed USB rate into sampling rate in Hz */ +static inline unsigned get_high_speed_hz(unsigned int usb_rate) +{ + return (usb_rate * 125 + (1 << 9)) >> 10; +} + +/* + * common proc files to show the usb device info + */ +static void proc_audio_usbbus_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_usb_audio *chip = entry->private_data; + if (!atomic_read(&chip->shutdown)) + snd_iprintf(buffer, "%03d/%03d\n", chip->dev->bus->busnum, chip->dev->devnum); +} + +static void proc_audio_usbid_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_usb_audio *chip = entry->private_data; + if (!atomic_read(&chip->shutdown)) + snd_iprintf(buffer, "%04x:%04x\n", + USB_ID_VENDOR(chip->usb_id), + USB_ID_PRODUCT(chip->usb_id)); +} + +void snd_usb_audio_create_proc(struct snd_usb_audio *chip) +{ + struct snd_info_entry *entry; + if (!snd_card_proc_new(chip->card, "usbbus", &entry)) + snd_info_set_text_ops(entry, chip, proc_audio_usbbus_read); + if (!snd_card_proc_new(chip->card, "usbid", &entry)) + snd_info_set_text_ops(entry, chip, proc_audio_usbid_read); +} + +/* + * proc interface for list the supported pcm formats + */ +static void proc_dump_substream_formats(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) +{ + struct audioformat *fp; + static const char * const sync_types[4] = { + "NONE", "ASYNC", "ADAPTIVE", "SYNC" + }; + + list_for_each_entry(fp, &subs->fmt_list, list) { + snd_pcm_format_t fmt; + + snd_iprintf(buffer, " Interface %d\n", fp->iface); + snd_iprintf(buffer, " Altset %d\n", fp->altsetting); + snd_iprintf(buffer, " Format:"); + for (fmt = 0; fmt <= SNDRV_PCM_FORMAT_LAST; ++fmt) + if (fp->formats & pcm_format_to_bits(fmt)) + snd_iprintf(buffer, " %s", + snd_pcm_format_name(fmt)); + snd_iprintf(buffer, "\n"); + snd_iprintf(buffer, " Channels: %d\n", fp->channels); + snd_iprintf(buffer, " Endpoint: %d %s (%s)\n", + fp->endpoint & USB_ENDPOINT_NUMBER_MASK, + fp->endpoint & USB_DIR_IN ? "IN" : "OUT", + sync_types[(fp->ep_attr & USB_ENDPOINT_SYNCTYPE) >> 2]); + if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) { + snd_iprintf(buffer, " Rates: %d - %d (continuous)\n", + fp->rate_min, fp->rate_max); + } else { + unsigned int i; + snd_iprintf(buffer, " Rates: "); + for (i = 0; i < fp->nr_rates; i++) { + if (i > 0) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d", fp->rate_table[i]); + } + snd_iprintf(buffer, "\n"); + } + if (subs->speed != USB_SPEED_FULL) + snd_iprintf(buffer, " Data packet interval: %d us\n", + 125 * (1 << fp->datainterval)); + // snd_iprintf(buffer, " Max Packet Size = %d\n", fp->maxpacksize); + // snd_iprintf(buffer, " EP Attribute = %#x\n", fp->attributes); + } +} + +static void proc_dump_ep_status(struct snd_usb_substream *subs, + struct snd_usb_endpoint *data_ep, + struct snd_usb_endpoint *sync_ep, + struct snd_info_buffer *buffer) +{ + if (!data_ep) + return; + snd_iprintf(buffer, " Packet Size = %d\n", data_ep->curpacksize); + snd_iprintf(buffer, " Momentary freq = %u Hz (%#x.%04x)\n", + subs->speed == USB_SPEED_FULL + ? get_full_speed_hz(data_ep->freqm) + : get_high_speed_hz(data_ep->freqm), + data_ep->freqm >> 16, data_ep->freqm & 0xffff); + if (sync_ep && data_ep->freqshift != INT_MIN) { + int res = 16 - data_ep->freqshift; + snd_iprintf(buffer, " Feedback Format = %d.%d\n", + (sync_ep->syncmaxsize > 3 ? 32 : 24) - res, res); + } +} + +static void proc_dump_substream_status(struct snd_usb_substream *subs, struct snd_info_buffer *buffer) +{ + if (subs->running) { + snd_iprintf(buffer, " Status: Running\n"); + snd_iprintf(buffer, " Interface = %d\n", subs->interface); + snd_iprintf(buffer, " Altset = %d\n", subs->altset_idx); + proc_dump_ep_status(subs, subs->data_endpoint, subs->sync_endpoint, buffer); + } else { + snd_iprintf(buffer, " Status: Stop\n"); + } +} + +static void proc_pcm_format_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_usb_stream *stream = entry->private_data; + + snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name); + + if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) { + snd_iprintf(buffer, "\nPlayback:\n"); + proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); + proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer); + } + if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) { + snd_iprintf(buffer, "\nCapture:\n"); + proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); + proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer); + } +} + +void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream) +{ + struct snd_info_entry *entry; + char name[32]; + struct snd_card *card = stream->chip->card; + + sprintf(name, "stream%d", stream->pcm_index); + if (!snd_card_proc_new(card, name, &entry)) + snd_info_set_text_ops(entry, stream, proc_pcm_format_read); +} + diff --git a/sound/usb/proc.h b/sound/usb/proc.h new file mode 100644 index 000000000..72b1b2d28 --- /dev/null +++ b/sound/usb/proc.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_PROC_H +#define __USBAUDIO_PROC_H + +void snd_usb_audio_create_proc(struct snd_usb_audio *chip); +void snd_usb_proc_pcm_format_add(struct snd_usb_stream *stream); + +#endif /* __USBAUDIO_PROC_H */ + diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h new file mode 100644 index 000000000..1e0d94603 --- /dev/null +++ b/sound/usb/quirks-table.h @@ -0,0 +1,3619 @@ +/* + * ALSA USB Audio Driver + * + * Copyright (c) 2002 by Takashi Iwai , + * Clemens Ladisch + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * The contents of this file are part of the driver's id_table. + * + * In a perfect world, this file would be empty. + */ + +/* + * Use this for devices where other interfaces are standard compliant, + * to prevent the quirk being applied to those interfaces. (To work with + * hotplugging, bDeviceClass must be set to USB_CLASS_PER_INTERFACE.) + */ +#define USB_DEVICE_VENDOR_SPEC(vend, prod) \ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | \ + USB_DEVICE_ID_MATCH_PRODUCT | \ + USB_DEVICE_ID_MATCH_INT_CLASS, \ + .idVendor = vend, \ + .idProduct = prod, \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC + +/* HP Thunderbolt Dock Audio Headset */ +{ + USB_DEVICE(0x03f0, 0x0269), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "HP", + .product_name = "Thunderbolt Dock Audio Headset", + .profile_name = "HP-Thunderbolt-Dock-Audio-Headset", + .ifnum = QUIRK_NO_INTERFACE + } +}, +/* HP Thunderbolt Dock Audio Module */ +{ + USB_DEVICE(0x03f0, 0x0567), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "HP", + .product_name = "Thunderbolt Dock Audio Module", + .profile_name = "HP-Thunderbolt-Dock-Audio-Module", + .ifnum = QUIRK_NO_INTERFACE + } +}, +/* FTDI devices */ +{ + USB_DEVICE(0x0403, 0xb8d8), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "STARR LABS", */ + /* .product_name = "Starr Labs MIDI USB device", */ + .ifnum = 0, + .type = QUIRK_MIDI_FTDI + } +}, + +{ + /* Creative BT-D1 */ + USB_DEVICE(0x041e, 0x0005), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x03, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .attributes = 0, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 48000, + .rate_max = 48000, + } + } +}, + +/* Creative/E-Mu devices */ +{ + USB_DEVICE(0x041e, 0x3010), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Creative Labs", + .product_name = "Sound Blaster MP3+", + .ifnum = QUIRK_NO_INTERFACE + } +}, +/* Creative/Toshiba Multimedia Center SB-0500 */ +{ + USB_DEVICE(0x041e, 0x3048), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Toshiba", + .product_name = "SB-0500", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ + /* E-Mu 0202 USB */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x041e, + .idProduct = 0x3f02, + .bInterfaceClass = USB_CLASS_AUDIO, +}, +{ + /* E-Mu 0404 USB */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x041e, + .idProduct = 0x3f04, + .bInterfaceClass = USB_CLASS_AUDIO, +}, +{ + /* E-Mu Tracker Pre */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x041e, + .idProduct = 0x3f0a, + .bInterfaceClass = USB_CLASS_AUDIO, +}, +{ + /* E-Mu 0204 USB */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x041e, + .idProduct = 0x3f19, + .bInterfaceClass = USB_CLASS_AUDIO, +}, + +/* + * HP Wireless Audio + * When not ignored, causes instability issues for some users, forcing them to + * blacklist the entire module. + */ +{ + USB_DEVICE(0x0424, 0xb832), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Standard Microsystems Corp.", + .product_name = "HP Wireless Audio", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + /* Mixer */ + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE, + }, + /* Playback */ + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE, + }, + /* Capture */ + { + .ifnum = 2, + .type = QUIRK_IGNORE_INTERFACE, + }, + /* HID Device, .ifnum = 3 */ + { + .ifnum = -1, + } + } + } +}, + +/* + * Logitech QuickCam: bDeviceClass is vendor-specific, so generic interface + * class matches do not take effect without an explicit ID match. + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x0850, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08ae, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08c6, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08f0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08f5, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x08f6, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL +}, +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x046d, + .idProduct = 0x0990, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Logitech, Inc.", + .product_name = "QuickCam Pro 9000", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* + * Yamaha devices + */ + +#define YAMAHA_DEVICE(id, name) { \ + USB_DEVICE(0x0499, id), \ + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ + .vendor_name = "Yamaha", \ + .product_name = name, \ + .ifnum = QUIRK_ANY_INTERFACE, \ + .type = QUIRK_MIDI_YAMAHA \ + } \ +} +#define YAMAHA_INTERFACE(id, intf, name) { \ + USB_DEVICE_VENDOR_SPEC(0x0499, id), \ + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ + .vendor_name = "Yamaha", \ + .product_name = name, \ + .ifnum = intf, \ + .type = QUIRK_MIDI_YAMAHA \ + } \ +} +YAMAHA_DEVICE(0x1000, "UX256"), +YAMAHA_DEVICE(0x1001, "MU1000"), +YAMAHA_DEVICE(0x1002, "MU2000"), +YAMAHA_DEVICE(0x1003, "MU500"), +YAMAHA_INTERFACE(0x1004, 3, "UW500"), +YAMAHA_DEVICE(0x1005, "MOTIF6"), +YAMAHA_DEVICE(0x1006, "MOTIF7"), +YAMAHA_DEVICE(0x1007, "MOTIF8"), +YAMAHA_DEVICE(0x1008, "UX96"), +YAMAHA_DEVICE(0x1009, "UX16"), +YAMAHA_INTERFACE(0x100a, 3, "EOS BX"), +YAMAHA_DEVICE(0x100c, "UC-MX"), +YAMAHA_DEVICE(0x100d, "UC-KX"), +YAMAHA_DEVICE(0x100e, "S08"), +YAMAHA_DEVICE(0x100f, "CLP-150"), +YAMAHA_DEVICE(0x1010, "CLP-170"), +YAMAHA_DEVICE(0x1011, "P-250"), +YAMAHA_DEVICE(0x1012, "TYROS"), +YAMAHA_DEVICE(0x1013, "PF-500"), +YAMAHA_DEVICE(0x1014, "S90"), +YAMAHA_DEVICE(0x1015, "MOTIF-R"), +YAMAHA_DEVICE(0x1016, "MDP-5"), +YAMAHA_DEVICE(0x1017, "CVP-204"), +YAMAHA_DEVICE(0x1018, "CVP-206"), +YAMAHA_DEVICE(0x1019, "CVP-208"), +YAMAHA_DEVICE(0x101a, "CVP-210"), +YAMAHA_DEVICE(0x101b, "PSR-1100"), +YAMAHA_DEVICE(0x101c, "PSR-2100"), +YAMAHA_DEVICE(0x101d, "CLP-175"), +YAMAHA_DEVICE(0x101e, "PSR-K1"), +YAMAHA_DEVICE(0x101f, "EZ-J24"), +YAMAHA_DEVICE(0x1020, "EZ-250i"), +YAMAHA_DEVICE(0x1021, "MOTIF ES 6"), +YAMAHA_DEVICE(0x1022, "MOTIF ES 7"), +YAMAHA_DEVICE(0x1023, "MOTIF ES 8"), +YAMAHA_DEVICE(0x1024, "CVP-301"), +YAMAHA_DEVICE(0x1025, "CVP-303"), +YAMAHA_DEVICE(0x1026, "CVP-305"), +YAMAHA_DEVICE(0x1027, "CVP-307"), +YAMAHA_DEVICE(0x1028, "CVP-309"), +YAMAHA_DEVICE(0x1029, "CVP-309GP"), +YAMAHA_DEVICE(0x102a, "PSR-1500"), +YAMAHA_DEVICE(0x102b, "PSR-3000"), +YAMAHA_DEVICE(0x102e, "ELS-01/01C"), +YAMAHA_DEVICE(0x1030, "PSR-295/293"), +YAMAHA_DEVICE(0x1031, "DGX-205/203"), +YAMAHA_DEVICE(0x1032, "DGX-305"), +YAMAHA_DEVICE(0x1033, "DGX-505"), +YAMAHA_DEVICE(0x1034, NULL), +YAMAHA_DEVICE(0x1035, NULL), +YAMAHA_DEVICE(0x1036, NULL), +YAMAHA_DEVICE(0x1037, NULL), +YAMAHA_DEVICE(0x1038, NULL), +YAMAHA_DEVICE(0x1039, NULL), +YAMAHA_DEVICE(0x103a, NULL), +YAMAHA_DEVICE(0x103b, NULL), +YAMAHA_DEVICE(0x103c, NULL), +YAMAHA_DEVICE(0x103d, NULL), +YAMAHA_DEVICE(0x103e, NULL), +YAMAHA_DEVICE(0x103f, NULL), +YAMAHA_DEVICE(0x1040, NULL), +YAMAHA_DEVICE(0x1041, NULL), +YAMAHA_DEVICE(0x1042, NULL), +YAMAHA_DEVICE(0x1043, NULL), +YAMAHA_DEVICE(0x1044, NULL), +YAMAHA_DEVICE(0x1045, NULL), +YAMAHA_INTERFACE(0x104e, 0, NULL), +YAMAHA_DEVICE(0x104f, NULL), +YAMAHA_DEVICE(0x1050, NULL), +YAMAHA_DEVICE(0x1051, NULL), +YAMAHA_DEVICE(0x1052, NULL), +YAMAHA_INTERFACE(0x1053, 0, NULL), +YAMAHA_INTERFACE(0x1054, 0, NULL), +YAMAHA_DEVICE(0x1055, NULL), +YAMAHA_DEVICE(0x1056, NULL), +YAMAHA_DEVICE(0x1057, NULL), +YAMAHA_DEVICE(0x1058, NULL), +YAMAHA_DEVICE(0x1059, NULL), +YAMAHA_DEVICE(0x105a, NULL), +YAMAHA_DEVICE(0x105b, NULL), +YAMAHA_DEVICE(0x105c, NULL), +YAMAHA_DEVICE(0x105d, NULL), +{ + USB_DEVICE(0x0499, 0x1503), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Yamaha", */ + /* .product_name = "MOX6/MOX8", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_YAMAHA + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0499, 0x1507), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Yamaha", */ + /* .product_name = "THR10", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_YAMAHA + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0499, 0x1509), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Yamaha", */ + /* .product_name = "Steinberg UR22", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_YAMAHA + }, + { + .ifnum = 4, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0499, 0x150a), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Yamaha", */ + /* .product_name = "THR5A", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_YAMAHA + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0499, 0x150c), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Yamaha", */ + /* .product_name = "THR10C", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_YAMAHA + }, + { + .ifnum = -1 + } + } + } +}, +YAMAHA_DEVICE(0x2000, "DGP-7"), +YAMAHA_DEVICE(0x2001, "DGP-5"), +YAMAHA_DEVICE(0x2002, NULL), +YAMAHA_DEVICE(0x2003, NULL), +YAMAHA_DEVICE(0x5000, "CS1D"), +YAMAHA_DEVICE(0x5001, "DSP1D"), +YAMAHA_DEVICE(0x5002, "DME32"), +YAMAHA_DEVICE(0x5003, "DM2000"), +YAMAHA_DEVICE(0x5004, "02R96"), +YAMAHA_DEVICE(0x5005, "ACU16-C"), +YAMAHA_DEVICE(0x5006, "NHB32-C"), +YAMAHA_DEVICE(0x5007, "DM1000"), +YAMAHA_DEVICE(0x5008, "01V96"), +YAMAHA_DEVICE(0x5009, "SPX2000"), +YAMAHA_DEVICE(0x500a, "PM5D"), +YAMAHA_DEVICE(0x500b, "DME64N"), +YAMAHA_DEVICE(0x500c, "DME24N"), +YAMAHA_DEVICE(0x500d, NULL), +YAMAHA_DEVICE(0x500e, NULL), +YAMAHA_DEVICE(0x500f, NULL), +YAMAHA_DEVICE(0x7000, "DTX"), +YAMAHA_DEVICE(0x7010, "UB99"), +#undef YAMAHA_DEVICE +#undef YAMAHA_INTERFACE +/* this catches most recent vendor-specific Yamaha devices */ +{ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | + USB_DEVICE_ID_MATCH_INT_CLASS, + .idVendor = 0x0499, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUTODETECT + } +}, + +/* + * Roland/RolandED/Edirol/BOSS devices + */ +{ + USB_DEVICE(0x0582, 0x0000), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "UA-100", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = 0x09, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_FILL_MAX, + .endpoint = 0x81, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0007, + .in_cables = 0x0007 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-4", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0003), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SC-8850", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x003f, + .in_cables = 0x003f + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0004), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "U-8", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0005, + .in_cables = 0x0005 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Has ID 0x0099 when not in "Advanced Driver" mode. + * The UM-2EX has only one input, but we cannot detect this. */ + USB_DEVICE(0x0582, 0x0005), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-2", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0007), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SC-8820", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0013, + .in_cables = 0x0013 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0008), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "PC-300", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x009d when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0009), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-1", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x000b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SK-500", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0013, + .in_cables = 0x0013 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* thanks to Emiliano Grilli + * for helping researching this data */ + USB_DEVICE(0x0582, 0x000c), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "SC-D70", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0007, + .in_cables = 0x0007 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ /* + * This quirk is for the "Advanced Driver" mode of the Edirol UA-5. + * If the advanced mode switch at the back of the unit is off, the + * UA-5 has ID 0x0582/0x0011 and is standard compliant (no quirks), + * but offers only 16-bit PCM. + * In advanced mode, the UA-5 will output S24_3LE samples (two + * channels) at the rate indicated on the front switch, including + * the 96kHz sample rate. + */ + USB_DEVICE(0x0582, 0x0010), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-5", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0013 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0012), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "XV-5050", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0015 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0014), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-880", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x01ff, + .in_cables = 0x01ff + } + } +}, +{ + /* has ID 0x0017 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0016), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "SD-90", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x001c when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x001b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "MMP-2", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x001e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x001d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "V-SYNTH", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0024 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0023), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-550", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x003f, + .in_cables = 0x003f + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the UA-20 + * has ID 0x0026 and is standard compliant, but has only 16-bit PCM + * and no MIDI. + */ + USB_DEVICE(0x0582, 0x0025), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-20", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = 0x01, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = 0x01, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 44100, + .rate_max = 44100, + } + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0028 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0027), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "SD-20", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, +{ + /* has ID 0x002a when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0029), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "SD-80", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + } +}, +{ /* + * This quirk is for the "Advanced" modes of the Edirol UA-700. + * If the sample format switch is not in an advanced setting, the + * UA-700 has ID 0x0582/0x002c and is standard compliant (no quirks), + * but offers only 16-bit PCM and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x002b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-700", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 3, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x002e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x002d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "XV-2020", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0030 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x002f), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "VariOS", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0007, + .in_cables = 0x0007 + } + } +}, +{ + /* has ID 0x0034 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0033), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PCR", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, +{ + /* + * Has ID 0x0038 when not in "Advanced Driver" mode; + * later revisions use IDs 0x0054 and 0x00a2. + */ + USB_DEVICE(0x0582, 0x0037), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "Digital Piano", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the GS-10 + * has ID 0x003c and is standard compliant, but has only 16-bit PCM + * and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x003b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "BOSS", + .product_name = "GS-10", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0041 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0040), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "GI-20", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0043 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0042), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "RS-70", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x0049 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0047), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "EDIROL", */ + /* .product_name = "UR-80", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + /* in the 96 kHz modes, only interface 1 is there */ + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x004a when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0048), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "EDIROL", */ + /* .product_name = "UR-80", */ + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, +{ + /* has ID 0x004e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x004c), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PCR-A", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x004f when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x004d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PCR-A", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0007 + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the UA-3FX + * is standard compliant, but has only 16-bit PCM. + */ + USB_DEVICE(0x0582, 0x0050), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-3FX", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0052), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UM-1SX", + .ifnum = 0, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE(0x0582, 0x0060), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "EXR Series", + .ifnum = 0, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + /* has ID 0x0066 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0064), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "EDIROL", */ + /* .product_name = "PCR-1", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0067 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0065), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "EDIROL", */ + /* .product_name = "PCR-1", */ + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0003 + } + } +}, +{ + /* has ID 0x006e when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x006d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "FANTOM-X", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ /* + * This quirk is for the "Advanced" modes of the Edirol UA-25. + * If the switch is not in an advanced setting, the UA-25 has + * ID 0x0582/0x0073 and is standard compliant (no quirks), but + * offers only 16-bit PCM at 44.1 kHz and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x0074), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-25", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* has ID 0x0076 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0075), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "BOSS", + .product_name = "DR-880", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x007b when not in "Advanced Driver" mode */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x007a), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + /* "RD" or "RD-700SX"? */ + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + } +}, +{ + /* has ID 0x0081 when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x0080), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Roland", + .product_name = "G-70", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* has ID 0x008c when not in "Advanced Driver" mode */ + USB_DEVICE(0x0582, 0x008b), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "PC-50", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + /* + * This quirk is for the "Advanced Driver" mode. If off, the UA-4FX + * is standard compliant, but has only 16-bit PCM and no MIDI. + */ + USB_DEVICE(0x0582, 0x00a3), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-4FX", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Edirol M-16DX */ + USB_DEVICE(0x0582, 0x00c4), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Advanced modes of the Edirol UA-25EX. + * For the standard mode, UA-25EX has ID 0582:00e7, which + * offers only 16-bit PCM at 44.1 kHz and no MIDI. + */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x00e6), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "EDIROL", + .product_name = "UA-25EX", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_EDIROL_UAXX + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Edirol UM-3G */ + USB_DEVICE_VENDOR_SPEC(0x0582, 0x0108), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0007, + .in_cables = 0x0007 + } + } +}, +{ + /* BOSS ME-25 */ + USB_DEVICE(0x0582, 0x0113), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* only 44.1 kHz works at the moment */ + USB_DEVICE(0x0582, 0x0120), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Roland", */ + /* .product_name = "OCTO-CAPTURE", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 12, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* only 44.1 kHz works at the moment */ + USB_DEVICE(0x0582, 0x012f), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Roland", */ + /* .product_name = "QUAD-CAPTURE", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 6, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0582, 0x0159), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Roland", */ + /* .product_name = "UA-22", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +/* this catches most recent vendor-specific Roland devices */ +{ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | + USB_DEVICE_ID_MATCH_INT_CLASS, + .idVendor = 0x0582, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUTODETECT + } +}, + +/* Guillemot devices */ +{ + /* + * This is for the "Windows Edition" where the external MIDI ports are + * the only MIDI ports; the control data is reported through HID + * interfaces. The "Macintosh Edition" has ID 0xd002 and uses standard + * compliant USB MIDI ports for external MIDI and controls. + */ + USB_DEVICE_VENDOR_SPEC(0x06f8, 0xb000), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Hercules", + .product_name = "DJ Console (WE)", + .ifnum = 4, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, + +/* Midiman/M-Audio devices */ +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 2x2", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1011), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 1x1", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1015), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "Keystation", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1021), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 4x4", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x000f + } + } +}, +{ + /* + * For hardware revision 1.05; in the later revisions (1.10 and + * 1.21), 0x1031 is the ID for the device without firmware. + * Thanks to Olaf Giesbrecht + */ + USB_DEVICE_VER(0x0763, 0x1031, 0x0100, 0x0109), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 8x8", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x01ff, + .in_cables = 0x01ff + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1033), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 8x8", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x01ff, + .in_cables = 0x01ff + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x1041), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "MidiSport 2x4", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x000f, + .in_cables = 0x0003 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "Quattro", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + /* + * Interfaces 0-2 are "Windows-compatible", 16-bit only, + * and share endpoints with the other interfaces. + * Ignore them. The other interfaces can do 24 bits, + * but captured samples are big-endian (see usbaudio.c). + */ + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 5, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 6, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 7, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 8, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 9, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2003), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "AudioPhile", + .ifnum = 6, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2008), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "Ozone", + .ifnum = 3, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x200d), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "M-Audio", + .product_name = "OmniStudio", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 5, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 6, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 7, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 8, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 9, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x0763, 0x2019), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Ozone Academic", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_MIDIMAN, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2030), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Fast Track C400", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + /* Playback */ + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 6, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x01, + .ep_attr = 0x09, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + }, + .clock = 0x80, + } + }, + /* Capture */ + { + .ifnum = 3, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x81, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + }, + .clock = 0x80, + } + }, + /* MIDI */ + { + .ifnum = -1 /* Interface = 4 */ + } + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2031), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Fast Track C600", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + /* Playback */ + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x01, + .ep_attr = 0x09, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + }, + .clock = 0x80, + } + }, + /* Capture */ + { + .ifnum = 3, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 6, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x81, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + }, + .clock = 0x80, + } + }, + /* MIDI */ + { + .ifnum = -1 /* Interface = 4 */ + } + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2080), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Fast Track Ultra", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x01, + .ep_attr = 0x09, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + } + } + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x81, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + } + } + }, + /* interface 3 (MIDI) is standard compliant */ + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0763, 0x2081), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "M-Audio", */ + /* .product_name = "Fast Track Ultra 8R", */ + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x01, + .ep_attr = 0x09, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + } + } + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 8, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x81, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + } + } + }, + /* interface 3 (MIDI) is standard compliant */ + { + .ifnum = -1 + } + } + } +}, + +/* Casio devices */ +{ + USB_DEVICE(0x07cf, 0x6801), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Casio", + .product_name = "PL-40R", + .ifnum = 0, + .type = QUIRK_MIDI_YAMAHA + } +}, +{ + /* this ID is used by several devices without a product ID */ + USB_DEVICE(0x07cf, 0x6802), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Casio", + .product_name = "Keyboard", + .ifnum = 0, + .type = QUIRK_MIDI_YAMAHA + } +}, + +/* Mark of the Unicorn devices */ +{ + /* thanks to Robert A. Lerche */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | + USB_DEVICE_ID_MATCH_PRODUCT | + USB_DEVICE_ID_MATCH_DEV_SUBCLASS, + .idVendor = 0x07fd, + .idProduct = 0x0001, + .bDeviceSubClass = 2, + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "MOTU", + .product_name = "Fastlane", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_MIDI_RAW_BYTES + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +/* Emagic devices */ +{ + USB_DEVICE(0x086a, 0x0001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Emagic", + /* .product_name = "Unitor8", */ + .ifnum = 2, + .type = QUIRK_MIDI_EMAGIC, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x80ff, + .in_cables = 0x80ff + } + } +}, +{ + USB_DEVICE(0x086a, 0x0002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Emagic", + /* .product_name = "AMT8", */ + .ifnum = 2, + .type = QUIRK_MIDI_EMAGIC, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x80ff, + .in_cables = 0x80ff + } + } +}, +{ + USB_DEVICE(0x086a, 0x0003), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Emagic", + /* .product_name = "MT4", */ + .ifnum = 2, + .type = QUIRK_MIDI_EMAGIC, + .data = & (const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x800f, + .in_cables = 0x8003 + } + } +}, + +/* KORG devices */ +{ + USB_DEVICE_VENDOR_SPEC(0x0944, 0x0200), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "KORG, Inc.", + /* .product_name = "PANDORA PX5D", */ + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE, + } +}, + +{ + USB_DEVICE_VENDOR_SPEC(0x0944, 0x0201), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "KORG, Inc.", + /* .product_name = "ToneLab ST", */ + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE, + } +}, + +{ + USB_DEVICE_VENDOR_SPEC(0x0944, 0x0204), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "KORG, Inc.", + /* .product_name = "ToneLab EX", */ + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE, + } +}, + +/* AKAI devices */ +{ + USB_DEVICE(0x09e8, 0x0062), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "AKAI", + .product_name = "MPD16", + .ifnum = 0, + .type = QUIRK_MIDI_AKAI, + } +}, + +{ + /* Akai MPC Element */ + USB_DEVICE(0x09e8, 0x0021), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_MIDI_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +/* Steinberg devices */ +{ + /* Steinberg MI2 */ + USB_DEVICE_VENDOR_SPEC(0x0a4e, 0x2040), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &(const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Steinberg MI4 */ + USB_DEVICE_VENDOR_SPEC(0x0a4e, 0x4040), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = & (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = 3, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &(const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0001, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, + +/* TerraTec devices */ +{ + USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0012), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "PHASE 26", + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0013), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "PHASE 26", + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x0ccd, 0x0014), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "PHASE 26", + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, +{ + USB_DEVICE(0x0ccd, 0x0028), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "TerraTec", + .product_name = "Aureon5.1MkII", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ + USB_DEVICE(0x0ccd, 0x0035), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Miditech", + .product_name = "Play'n Roll", + .ifnum = 0, + .type = QUIRK_MIDI_CME + } +}, + +/* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */ +{ + USB_DEVICE(0x103d, 0x0100), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Stanton", + .product_name = "ScratchAmp", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ + USB_DEVICE(0x103d, 0x0101), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Stanton", + .product_name = "ScratchAmp", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* Novation EMS devices */ +{ + USB_DEVICE_VENDOR_SPEC(0x1235, 0x0001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Novation", + .product_name = "ReMOTE Audio/XStation", + .ifnum = 4, + .type = QUIRK_MIDI_NOVATION + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x1235, 0x0002), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Novation", + .product_name = "Speedio", + .ifnum = 3, + .type = QUIRK_MIDI_NOVATION + } +}, +{ + USB_DEVICE(0x1235, 0x000a), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Novation", */ + /* .product_name = "Nocturn", */ + .ifnum = 0, + .type = QUIRK_MIDI_RAW_BYTES + } +}, +{ + USB_DEVICE(0x1235, 0x000e), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + /* .vendor_name = "Novation", */ + /* .product_name = "Launchpad", */ + .ifnum = 0, + .type = QUIRK_MIDI_RAW_BYTES + } +}, +{ + USB_DEVICE(0x1235, 0x0010), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Focusrite", + .product_name = "Saffire 6 USB", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { + 44100, 48000 + } + } + }, + { + .ifnum = 1, + .type = QUIRK_MIDI_RAW_BYTES + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE(0x1235, 0x0018), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Novation", + .product_name = "Twitch", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = & (const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { + 44100, 48000 + } + } + }, + { + .ifnum = 1, + .type = QUIRK_MIDI_RAW_BYTES + }, + { + .ifnum = -1 + } + } + } +}, +{ + USB_DEVICE_VENDOR_SPEC(0x1235, 0x4661), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Novation", + .product_name = "ReMOTE25", + .ifnum = 0, + .type = QUIRK_MIDI_NOVATION + } +}, + +/* Access Music devices */ +{ + /* VirusTI Desktop */ + USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 3, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &(const struct snd_usb_midi_endpoint_info) { + .out_cables = 0x0003, + .in_cables = 0x0003 + } + }, + { + .ifnum = 4, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +/* */ +{ + /* aka. Serato Scratch Live DJ Box */ + USB_DEVICE(0x13e5, 0x0001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Rane", + .product_name = "SL-1", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* Native Instruments MK2 series */ +{ + /* Komplete Audio 6 */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x17cc, + .idProduct = 0x1000, +}, +{ + /* Traktor Audio 6 */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x17cc, + .idProduct = 0x1010, +}, +{ + /* Traktor Audio 10 */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x17cc, + .idProduct = 0x1020, +}, + +/* QinHeng devices */ +{ + USB_DEVICE(0x1a86, 0x752d), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "QinHeng", + .product_name = "CH345", + .ifnum = 1, + .type = QUIRK_MIDI_CH345 + } +}, + +/* KeithMcMillen Stringport */ +{ + USB_DEVICE(0x1f38, 0x0001), + .bInterfaceClass = USB_CLASS_AUDIO, +}, + +/* Miditech devices */ +{ + USB_DEVICE(0x4752, 0x0011), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Miditech", + .product_name = "Midistart-2", + .ifnum = 0, + .type = QUIRK_MIDI_CME + } +}, + +/* Central Music devices */ +{ + /* this ID used by both Miditech MidiStudio-2 and CME UF-x */ + USB_DEVICE(0x7104, 0x2202), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = 0, + .type = QUIRK_MIDI_CME + } +}, + +/* + * Auvitek au0828 devices with audio interface. + * This should be kept in sync with drivers/media/usb/au0828/au0828-cards.c + * Please notice that some drivers are DVB only, and don't need to be + * here. That's the case, for example, of DVICO_FUSIONHDTV7. + */ + +#define AU0828_DEVICE(vid, pid, vname, pname) { \ + .idVendor = vid, \ + .idProduct = pid, \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_SUBCLASS, \ + .bInterfaceClass = USB_CLASS_AUDIO, \ + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, \ + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { \ + .vendor_name = vname, \ + .product_name = pname, \ + .ifnum = QUIRK_ANY_INTERFACE, \ + .type = QUIRK_AUDIO_ALIGN_TRANSFER, \ + } \ +} + +AU0828_DEVICE(0x2040, 0x7200, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x7240, "Hauppauge", "HVR-850"), +AU0828_DEVICE(0x2040, 0x7210, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x7217, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x721b, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x721e, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x721f, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x7280, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x0fd9, 0x0008, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x7201, "Hauppauge", "HVR-950Q-MXL"), +AU0828_DEVICE(0x2040, 0x7211, "Hauppauge", "HVR-950Q-MXL"), +AU0828_DEVICE(0x2040, 0x7281, "Hauppauge", "HVR-950Q-MXL"), +AU0828_DEVICE(0x05e1, 0x0480, "Hauppauge", "Woodbury"), +AU0828_DEVICE(0x2040, 0x8200, "Hauppauge", "Woodbury"), +AU0828_DEVICE(0x2040, 0x7260, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x7213, "Hauppauge", "HVR-950Q"), +AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"), + +/* Syntek STK1160 */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x05e1, + .idProduct = 0x0408, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Syntek", + .product_name = "STK1160", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_AUDIO_ALIGN_TRANSFER + } +}, + +/* Digidesign Mbox */ +{ + /* Thanks to Clemens Ladisch */ + USB_DEVICE(0x0dba, 0x1000), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Digidesign", + .product_name = "MBox", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]){ + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0x4, + .endpoint = 0x02, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_SYNC, + .maxpacksize = 0x130, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0x4, + .endpoint = 0x81, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .maxpacksize = 0x130, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + .ifnum = -1 + } + } + } +}, + +/* DIGIDESIGN MBOX 2 */ +{ + USB_DEVICE(0x0dba, 0x3000), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Digidesign", + .product_name = "Mbox 2", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .channels = 2, + .iface = 2, + .altsetting = 2, + .altset_idx = 1, + .attributes = 0x00, + .endpoint = 0x03, + .ep_attr = USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + .ifnum = 3, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 4, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3BE, + .channels = 2, + .iface = 4, + .altsetting = 2, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x85, + .ep_attr = USB_ENDPOINT_SYNC_SYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + .ifnum = 5, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 6, + .type = QUIRK_MIDI_MIDIMAN, + .data = &(const struct snd_usb_midi_endpoint_info) { + .out_ep = 0x02, + .out_cables = 0x0001, + .in_ep = 0x81, + .in_interval = 0x01, + .in_cables = 0x0001 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* Tascam US122 MKII - playback-only support */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = 0x8021, + .bInterfaceClass = USB_CLASS_AUDIO, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "TASCAM", + .product_name = "US122 MKII", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_IGNORE_INTERFACE + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x02, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .nr_rates = 4, + .rate_table = (unsigned int[]) { + 44100, 48000, 88200, 96000 + } + } + }, + { + .ifnum = -1 + } + } + } +}, + +/* Microsoft XboxLive Headset/Xbox Communicator */ +{ + USB_DEVICE(0x045e, 0x0283), + .bInterfaceClass = USB_CLASS_PER_INTERFACE, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Microsoft", + .product_name = "XboxLive Headset/Xbox Communicator", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + /* playback */ + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 1, + .iface = 0, + .altsetting = 0, + .altset_idx = 0, + .attributes = 0, + .endpoint = 0x04, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 22050, + .rate_max = 22050 + } + }, + { + /* capture */ + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 1, + .iface = 1, + .altsetting = 0, + .altset_idx = 0, + .attributes = 0, + .endpoint = 0x85, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 16000, + .rate_max = 16000 + } + }, + { + .ifnum = -1 + } + } + } +}, + +/* Reloop Play */ +{ + USB_DEVICE(0x200c, 0x100b), + .bInterfaceClass = USB_CLASS_PER_INTERFACE, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_SYNC_ADAPTIVE, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { + 44100, 48000 + } + } + }, + { + .ifnum = -1 + } + } + } +}, + +{ + /* + * ZOOM R16/24 in audio interface mode. + * Playback requires an extra four byte LE length indicator + * at the start of each isochronous packet. This quirk is + * enabled in create_standard_audio_quirk(). + */ + USB_DEVICE(0x1686, 0x00dd), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + /* Playback */ + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE, + }, + { + /* Capture */ + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_INTERFACE, + }, + { + /* Midi */ + .ifnum = 3, + .type = QUIRK_MIDI_STANDARD_INTERFACE + }, + { + .ifnum = -1 + }, + } + } +}, + +{ + /* + * Some USB MIDI devices don't have an audio control interface, + * so we have to grab MIDI streaming interfaces here. + */ + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_MIDI_STANDARD_INTERFACE + } +}, + +{ + /* + * The original product_name is "USB Sound Device", however this name + * is also used by the CM106 based cards, so make it unique. + */ + USB_DEVICE(0x0d8c, 0x0103), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .product_name = "Audio Advantage MicroII", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* disabled due to regression for other devices; + * see https://bugzilla.kernel.org/show_bug.cgi?id=199905 + */ +#if 0 +{ + /* + * Nura's first gen headphones use Cambridge Silicon Radio's vendor + * ID, but it looks like the product ID actually is only for Nura. + * The capture interface does not work at all (even on Windows), + * and only the 48 kHz sample rate works for the playback interface. + */ + USB_DEVICE(0x0a12, 0x1243), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + /* Capture */ + { + .ifnum = 1, + .type = QUIRK_IGNORE_INTERFACE, + }, + /* Playback */ + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_FILL_MAX | + UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x03, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + .ifnum = -1 + }, + } + } +}, +#endif /* disabled */ + +{ + /* + * Bower's & Wilkins PX headphones only support the 48 kHz sample rate + * even though it advertises more. The capture interface doesn't work + * even on windows. + */ + USB_DEVICE(0x19b5, 0x0021), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + /* Playback */ + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .attributes = UAC_EP_CS_ATTR_FILL_MAX | + UAC_EP_CS_ATTR_SAMPLE_RATE, + .endpoint = 0x03, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { + 48000 + } + } + }, + { + .ifnum = -1 + }, + } + } +}, +/* Dell WD15 Dock */ +{ + USB_DEVICE(0x0bda, 0x4014), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Dell", + .product_name = "WD15 Dock", + .profile_name = "Dell-WD15-Dock", + .ifnum = QUIRK_NO_INTERFACE + } +}, +/* Dell WD19 Dock */ +{ + USB_DEVICE(0x0bda, 0x402e), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Dell", + .product_name = "WD19 Dock", + .profile_name = "Dell-WD15-Dock", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_SETUP_FMT_AFTER_RESUME + } +}, +{ + /* + * PIONEER DJ DDJ-RB + * PCM is 4 channels out, 2 dummy channels in @ 44.1 fixed + * The feedback for the output is the dummy input. + */ + USB_DEVICE_VENDOR_SPEC(0x2b73, 0x000e), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = (const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = 0, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC| + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + .ifnum = -1 + } + } + } +}, + +#define ALC1220_VB_DESKTOP(vend, prod) { \ + USB_DEVICE(vend, prod), \ + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { \ + .vendor_name = "Realtek", \ + .product_name = "ALC1220-VB-DT", \ + .profile_name = "Realtek-ALC1220-VB-Desktop", \ + .ifnum = QUIRK_NO_INTERFACE \ + } \ +} +ALC1220_VB_DESKTOP(0x0414, 0xa002), /* Gigabyte TRX40 Aorus Pro WiFi */ +ALC1220_VB_DESKTOP(0x0db0, 0x0d64), /* MSI TRX40 Creator */ +ALC1220_VB_DESKTOP(0x0db0, 0x543d), /* MSI TRX40 */ +ALC1220_VB_DESKTOP(0x26ce, 0x0a01), /* Asrock TRX40 Creator */ +#undef ALC1220_VB_DESKTOP + +/* Two entries for Gigabyte TRX40 Aorus Master: + * TRX40 Aorus Master has two USB-audio devices, one for the front headphone + * with ESS SABRE9218 DAC chip, while another for the rest I/O (the rear + * panel and the front mic) with Realtek ALC1220-VB. + * Here we provide two distinct names for making UCM profiles easier. + */ +{ + USB_DEVICE(0x0414, 0xa000), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Gigabyte", + .product_name = "Aorus Master Front Headphone", + .profile_name = "Gigabyte-Aorus-Master-Front-Headphone", + .ifnum = QUIRK_NO_INTERFACE + } +}, +{ + USB_DEVICE(0x0414, 0xa001), + .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { + .vendor_name = "Gigabyte", + .product_name = "Aorus Master Main Audio", + .profile_name = "Gigabyte-Aorus-Master-Main-Audio", + .ifnum = QUIRK_NO_INTERFACE + } +}, + +/* + * MacroSilicon MS2109 based HDMI capture cards + * + * These claim 96kHz 1ch in the descriptors, but are actually 48kHz 2ch. + * They also need QUIRK_AUDIO_ALIGN_TRANSFER, which makes one wonder if + * they pretend to be 96kHz mono as a workaround for stereo being broken + * by that... + * + * They also have an issue with initial stream alignment that causes the + * channels to be swapped and out of phase, which is dealt with in quirks.c. + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x534d, + .idProduct = 0x2109, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "MacroSilicon", + .product_name = "MS2109", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 2, + .type = QUIRK_AUDIO_ALIGN_TRANSFER, + }, + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + { + .ifnum = 3, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 48000, + .rate_max = 48000, + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* + * Sennheiser GSP670 + * Change order of interfaces loaded + */ + USB_DEVICE(0x1395, 0x0300), + .bInterfaceClass = USB_CLASS_PER_INTERFACE, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + // Communication + { + .ifnum = 3, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + // Recording + { + .ifnum = 4, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + // Main + { + .ifnum = 1, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + { + .ifnum = -1 + } + } + } +}, + +#undef USB_DEVICE_VENDOR_SPEC diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c new file mode 100644 index 000000000..b1bd63a9f --- /dev/null +++ b/sound/usb/quirks.c @@ -0,0 +1,1576 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "mixer.h" +#include "mixer_quirks.h" +#include "midi.h" +#include "quirks.h" +#include "helper.h" +#include "endpoint.h" +#include "pcm.h" +#include "clock.h" +#include "stream.h" + +/* + * handle the quirks for the contained interfaces + */ +static int create_composite_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk_comp) +{ + int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber; + const struct snd_usb_audio_quirk *quirk; + int err; + + for (quirk = quirk_comp->data; quirk->ifnum >= 0; ++quirk) { + iface = usb_ifnum_to_if(chip->dev, quirk->ifnum); + if (!iface) + continue; + if (quirk->ifnum != probed_ifnum && + usb_interface_claimed(iface)) + continue; + err = snd_usb_create_quirk(chip, iface, driver, quirk); + if (err < 0) + return err; + } + + for (quirk = quirk_comp->data; quirk->ifnum >= 0; ++quirk) { + iface = usb_ifnum_to_if(chip->dev, quirk->ifnum); + if (!iface) + continue; + if (quirk->ifnum != probed_ifnum && + !usb_interface_claimed(iface)) { + err = usb_driver_claim_interface(driver, iface, + USB_AUDIO_IFACE_UNUSED); + if (err < 0) + return err; + } + } + + return 0; +} + +static int ignore_interface_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + return 0; +} + + +/* + * Allow alignment on audio sub-slot (channel samples) rather than + * on audio slots (audio frames) + */ +static int create_align_transfer_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + chip->txfr_quirk = 1; + return 1; /* Continue with creating streams and mixer */ +} + +static int create_any_midi_quirk(struct snd_usb_audio *chip, + struct usb_interface *intf, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk); +} + +/* + * create a stream for an interface with proper descriptors + */ +static int create_standard_audio_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + int err; + + if (chip->usb_id == USB_ID(0x1686, 0x00dd)) /* Zoom R16/24 */ + chip->tx_length_quirk = 1; + + alts = &iface->altsetting[0]; + altsd = get_iface_desc(alts); + err = snd_usb_parse_audio_interface(chip, altsd->bInterfaceNumber); + if (err < 0) { + usb_audio_err(chip, "cannot setup if %d: error %d\n", + altsd->bInterfaceNumber, err); + return err; + } + /* reset the current interface */ + usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0); + return 0; +} + +/* + * create a stream for an endpoint/altsetting without proper descriptors + */ +static int create_fixed_stream_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + struct audioformat *fp; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + int stream, err; + unsigned *rate_table = NULL; + + fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL); + if (!fp) + return -ENOMEM; + + INIT_LIST_HEAD(&fp->list); + if (fp->nr_rates > MAX_NR_RATES) { + kfree(fp); + return -EINVAL; + } + if (fp->nr_rates > 0) { + rate_table = kmemdup(fp->rate_table, + sizeof(int) * fp->nr_rates, GFP_KERNEL); + if (!rate_table) { + kfree(fp); + return -ENOMEM; + } + fp->rate_table = rate_table; + } + + stream = (fp->endpoint & USB_DIR_IN) + ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + err = snd_usb_add_audio_stream(chip, stream, fp); + if (err < 0) + goto error; + if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber || + fp->altset_idx >= iface->num_altsetting) { + err = -EINVAL; + goto error; + } + alts = &iface->altsetting[fp->altset_idx]; + altsd = get_iface_desc(alts); + if (altsd->bNumEndpoints < 1) { + err = -EINVAL; + goto error; + } + + fp->protocol = altsd->bInterfaceProtocol; + + if (fp->datainterval == 0) + fp->datainterval = snd_usb_parse_datainterval(chip, alts); + if (fp->maxpacksize == 0) + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + usb_set_interface(chip->dev, fp->iface, 0); + snd_usb_init_pitch(chip, fp->iface, alts, fp); + snd_usb_init_sample_rate(chip, fp->iface, alts, fp, fp->rate_max); + return 0; + + error: + list_del(&fp->list); /* unlink for avoiding double-free */ + kfree(fp); + kfree(rate_table); + return err; +} + +static int create_auto_pcm_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver) +{ + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct usb_endpoint_descriptor *epd; + struct uac1_as_header_descriptor *ashd; + struct uac_format_type_i_discrete_descriptor *fmtd; + + /* + * Most Roland/Yamaha audio streaming interfaces have more or less + * standard descriptors, but older devices might lack descriptors, and + * future ones might change, so ensure that we fail silently if the + * interface doesn't look exactly right. + */ + + /* must have a non-zero altsetting for streaming */ + if (iface->num_altsetting < 2) + return -ENODEV; + alts = &iface->altsetting[1]; + altsd = get_iface_desc(alts); + + /* must have an isochronous endpoint for streaming */ + if (altsd->bNumEndpoints < 1) + return -ENODEV; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_xfer_isoc(epd)) + return -ENODEV; + + /* must have format descriptors */ + ashd = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, + UAC_AS_GENERAL); + fmtd = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, + UAC_FORMAT_TYPE); + if (!ashd || ashd->bLength < 7 || + !fmtd || fmtd->bLength < 8) + return -ENODEV; + + return create_standard_audio_quirk(chip, iface, driver, NULL); +} + +static int create_yamaha_midi_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + struct usb_host_interface *alts) +{ + static const struct snd_usb_audio_quirk yamaha_midi_quirk = { + .type = QUIRK_MIDI_YAMAHA + }; + struct usb_midi_in_jack_descriptor *injd; + struct usb_midi_out_jack_descriptor *outjd; + + /* must have some valid jack descriptors */ + injd = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, USB_MS_MIDI_IN_JACK); + outjd = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, USB_MS_MIDI_OUT_JACK); + if (!injd && !outjd) + return -ENODEV; + if ((injd && !snd_usb_validate_midi_desc(injd)) || + (outjd && !snd_usb_validate_midi_desc(outjd))) + return -ENODEV; + if (injd && (injd->bLength < 5 || + (injd->bJackType != USB_MS_EMBEDDED && + injd->bJackType != USB_MS_EXTERNAL))) + return -ENODEV; + if (outjd && (outjd->bLength < 6 || + (outjd->bJackType != USB_MS_EMBEDDED && + outjd->bJackType != USB_MS_EXTERNAL))) + return -ENODEV; + return create_any_midi_quirk(chip, iface, driver, &yamaha_midi_quirk); +} + +static int create_roland_midi_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + struct usb_host_interface *alts) +{ + static const struct snd_usb_audio_quirk roland_midi_quirk = { + .type = QUIRK_MIDI_ROLAND + }; + u8 *roland_desc = NULL; + + /* might have a vendor-specific descriptor <06 24 F1 02 ...> */ + for (;;) { + roland_desc = snd_usb_find_csint_desc(alts->extra, + alts->extralen, + roland_desc, 0xf1); + if (!roland_desc) + return -ENODEV; + if (roland_desc[0] < 6 || roland_desc[3] != 2) + continue; + return create_any_midi_quirk(chip, iface, driver, + &roland_midi_quirk); + } +} + +static int create_std_midi_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + struct usb_host_interface *alts) +{ + struct usb_ms_header_descriptor *mshd; + struct usb_ms_endpoint_descriptor *msepd; + + /* must have the MIDIStreaming interface header descriptor*/ + mshd = (struct usb_ms_header_descriptor *)alts->extra; + if (alts->extralen < 7 || + mshd->bLength < 7 || + mshd->bDescriptorType != USB_DT_CS_INTERFACE || + mshd->bDescriptorSubtype != USB_MS_HEADER) + return -ENODEV; + /* must have the MIDIStreaming endpoint descriptor*/ + msepd = (struct usb_ms_endpoint_descriptor *)alts->endpoint[0].extra; + if (alts->endpoint[0].extralen < 4 || + msepd->bLength < 4 || + msepd->bDescriptorType != USB_DT_CS_ENDPOINT || + msepd->bDescriptorSubtype != UAC_MS_GENERAL || + msepd->bNumEmbMIDIJack < 1 || + msepd->bNumEmbMIDIJack > 16) + return -ENODEV; + + return create_any_midi_quirk(chip, iface, driver, NULL); +} + +static int create_auto_midi_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver) +{ + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct usb_endpoint_descriptor *epd; + int err; + + alts = &iface->altsetting[0]; + altsd = get_iface_desc(alts); + + /* must have at least one bulk/interrupt endpoint for streaming */ + if (altsd->bNumEndpoints < 1) + return -ENODEV; + epd = get_endpoint(alts, 0); + if (!usb_endpoint_xfer_bulk(epd) && + !usb_endpoint_xfer_int(epd)) + return -ENODEV; + + switch (USB_ID_VENDOR(chip->usb_id)) { + case 0x0499: /* Yamaha */ + err = create_yamaha_midi_quirk(chip, iface, driver, alts); + if (err != -ENODEV) + return err; + break; + case 0x0582: /* Roland */ + err = create_roland_midi_quirk(chip, iface, driver, alts); + if (err != -ENODEV) + return err; + break; + } + + return create_std_midi_quirk(chip, iface, driver, alts); +} + +static int create_autodetect_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver) +{ + int err; + + err = create_auto_pcm_quirk(chip, iface, driver); + if (err == -ENODEV) + err = create_auto_midi_quirk(chip, iface, driver); + return err; +} + +static int create_autodetect_quirks(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber; + int ifcount, ifnum, err; + + err = create_autodetect_quirk(chip, iface, driver); + if (err < 0) + return err; + + /* + * ALSA PCM playback/capture devices cannot be registered in two steps, + * so we have to claim the other corresponding interface here. + */ + ifcount = chip->dev->actconfig->desc.bNumInterfaces; + for (ifnum = 0; ifnum < ifcount; ifnum++) { + if (ifnum == probed_ifnum || quirk->ifnum >= 0) + continue; + iface = usb_ifnum_to_if(chip->dev, ifnum); + if (!iface || + usb_interface_claimed(iface) || + get_iface_desc(iface->altsetting)->bInterfaceClass != + USB_CLASS_VENDOR_SPEC) + continue; + + err = create_autodetect_quirk(chip, iface, driver); + if (err >= 0) { + err = usb_driver_claim_interface(driver, iface, + USB_AUDIO_IFACE_UNUSED); + if (err < 0) + return err; + } + } + + return 0; +} + +/* + * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface. + * The only way to detect the sample rate is by looking at wMaxPacketSize. + */ +static int create_uaxx_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + static const struct audioformat ua_format = { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .fmt_type = UAC_FORMAT_TYPE_I, + .altsetting = 1, + .altset_idx = 1, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + }; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + struct audioformat *fp; + int stream, err; + + /* both PCM and MIDI interfaces have 2 or more altsettings */ + if (iface->num_altsetting < 2) + return -ENXIO; + alts = &iface->altsetting[1]; + altsd = get_iface_desc(alts); + + if (altsd->bNumEndpoints == 2) { + static const struct snd_usb_midi_endpoint_info ua700_ep = { + .out_cables = 0x0003, + .in_cables = 0x0003 + }; + static const struct snd_usb_audio_quirk ua700_quirk = { + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &ua700_ep + }; + static const struct snd_usb_midi_endpoint_info uaxx_ep = { + .out_cables = 0x0001, + .in_cables = 0x0001 + }; + static const struct snd_usb_audio_quirk uaxx_quirk = { + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &uaxx_ep + }; + const struct snd_usb_audio_quirk *quirk = + chip->usb_id == USB_ID(0x0582, 0x002b) + ? &ua700_quirk : &uaxx_quirk; + return __snd_usbmidi_create(chip->card, iface, + &chip->midi_list, quirk, + chip->usb_id); + } + + if (altsd->bNumEndpoints != 1) + return -ENXIO; + + fp = kmemdup(&ua_format, sizeof(*fp), GFP_KERNEL); + if (!fp) + return -ENOMEM; + + fp->iface = altsd->bInterfaceNumber; + fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; + fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; + fp->datainterval = 0; + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + INIT_LIST_HEAD(&fp->list); + + switch (fp->maxpacksize) { + case 0x120: + fp->rate_max = fp->rate_min = 44100; + break; + case 0x138: + case 0x140: + fp->rate_max = fp->rate_min = 48000; + break; + case 0x258: + case 0x260: + fp->rate_max = fp->rate_min = 96000; + break; + default: + usb_audio_err(chip, "unknown sample rate\n"); + kfree(fp); + return -ENXIO; + } + + stream = (fp->endpoint & USB_DIR_IN) + ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + err = snd_usb_add_audio_stream(chip, stream, fp); + if (err < 0) { + list_del(&fp->list); /* unlink for avoiding double-free */ + kfree(fp); + return err; + } + usb_set_interface(chip->dev, fp->iface, 0); + return 0; +} + +/* + * Create a standard mixer for the specified interface. + */ +static int create_standard_mixer_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + if (quirk->ifnum < 0) + return 0; + + return snd_usb_create_mixer(chip, quirk->ifnum, 0); +} + + +static int setup_fmt_after_resume_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + chip->setup_fmt_after_resume_quirk = 1; + return 1; /* Continue with creating streams and mixer */ +} + +/* + * audio-interface quirks + * + * returns zero if no standard audio/MIDI parsing is needed. + * returns a positive value if standard audio/midi interfaces are parsed + * after this. + * returns a negative value at error. + */ +int snd_usb_create_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk) +{ + typedef int (*quirk_func_t)(struct snd_usb_audio *, + struct usb_interface *, + struct usb_driver *, + const struct snd_usb_audio_quirk *); + static const quirk_func_t quirk_funcs[] = { + [QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk, + [QUIRK_COMPOSITE] = create_composite_quirk, + [QUIRK_AUTODETECT] = create_autodetect_quirks, + [QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk, + [QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk, + [QUIRK_MIDI_YAMAHA] = create_any_midi_quirk, + [QUIRK_MIDI_ROLAND] = create_any_midi_quirk, + [QUIRK_MIDI_MIDIMAN] = create_any_midi_quirk, + [QUIRK_MIDI_NOVATION] = create_any_midi_quirk, + [QUIRK_MIDI_RAW_BYTES] = create_any_midi_quirk, + [QUIRK_MIDI_EMAGIC] = create_any_midi_quirk, + [QUIRK_MIDI_CME] = create_any_midi_quirk, + [QUIRK_MIDI_AKAI] = create_any_midi_quirk, + [QUIRK_MIDI_FTDI] = create_any_midi_quirk, + [QUIRK_MIDI_CH345] = create_any_midi_quirk, + [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk, + [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk, + [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk, + [QUIRK_AUDIO_ALIGN_TRANSFER] = create_align_transfer_quirk, + [QUIRK_AUDIO_STANDARD_MIXER] = create_standard_mixer_quirk, + [QUIRK_SETUP_FMT_AFTER_RESUME] = setup_fmt_after_resume_quirk, + }; + + if (quirk->type < QUIRK_TYPE_COUNT) { + return quirk_funcs[quirk->type](chip, iface, driver, quirk); + } else { + usb_audio_err(chip, "invalid quirk type %d\n", quirk->type); + return -ENXIO; + } +} + +/* + * boot quirks + */ + +#define EXTIGY_FIRMWARE_SIZE_OLD 794 +#define EXTIGY_FIRMWARE_SIZE_NEW 483 + +static int snd_usb_extigy_boot_quirk(struct usb_device *dev, struct usb_interface *intf) +{ + struct usb_host_config *config = dev->actconfig; + int err; + + if (le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_OLD || + le16_to_cpu(get_cfg_desc(config)->wTotalLength) == EXTIGY_FIRMWARE_SIZE_NEW) { + dev_dbg(&dev->dev, "sending Extigy boot sequence...\n"); + /* Send message to force it to reconnect with full interface. */ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev,0), + 0x10, 0x43, 0x0001, 0x000a, NULL, 0); + if (err < 0) + dev_dbg(&dev->dev, "error sending boot message: %d\n", err); + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, + &dev->descriptor, sizeof(dev->descriptor)); + config = dev->actconfig; + if (err < 0) + dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err); + err = usb_reset_configuration(dev); + if (err < 0) + dev_dbg(&dev->dev, "error usb_reset_configuration: %d\n", err); + dev_dbg(&dev->dev, "extigy_boot: new boot length = %d\n", + le16_to_cpu(get_cfg_desc(config)->wTotalLength)); + return -ENODEV; /* quit this anyway */ + } + return 0; +} + +static int snd_usb_audigy2nx_boot_quirk(struct usb_device *dev) +{ + u8 buf = 1; + + snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0x2a, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 0, 0, &buf, 1); + if (buf == 0) { + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0x29, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + 1, 2000, NULL, 0); + return -ENODEV; + } + return 0; +} + +static int snd_usb_fasttrackpro_boot_quirk(struct usb_device *dev) +{ + int err; + + if (dev->actconfig->desc.bConfigurationValue == 1) { + dev_info(&dev->dev, + "Fast Track Pro switching to config #2\n"); + /* This function has to be available by the usb core module. + * if it is not avialable the boot quirk has to be left out + * and the configuration has to be set by udev or hotplug + * rules + */ + err = usb_driver_set_configuration(dev, 2); + if (err < 0) + dev_dbg(&dev->dev, + "error usb_driver_set_configuration: %d\n", + err); + /* Always return an error, so that we stop creating a device + that will just be destroyed and recreated with a new + configuration */ + return -ENODEV; + } else + dev_info(&dev->dev, "Fast Track Pro config OK\n"); + + return 0; +} + +/* + * C-Media CM106/CM106+ have four 16-bit internal registers that are nicely + * documented in the device's data sheet. + */ +static int snd_usb_cm106_write_int_reg(struct usb_device *dev, int reg, u16 value) +{ + u8 buf[4]; + buf[0] = 0x20; + buf[1] = value & 0xff; + buf[2] = (value >> 8) & 0xff; + buf[3] = reg; + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + 0, 0, &buf, 4); +} + +static int snd_usb_cm106_boot_quirk(struct usb_device *dev) +{ + /* + * Enable line-out driver mode, set headphone source to front + * channels, enable stereo mic. + */ + return snd_usb_cm106_write_int_reg(dev, 2, 0x8004); +} + +/* + * C-Media CM6206 is based on CM106 with two additional + * registers that are not documented in the data sheet. + * Values here are chosen based on sniffing USB traffic + * under Windows. + */ +static int snd_usb_cm6206_boot_quirk(struct usb_device *dev) +{ + int err = 0, reg; + int val[] = {0x2004, 0x3000, 0xf800, 0x143f, 0x0000, 0x3000}; + + for (reg = 0; reg < ARRAY_SIZE(val); reg++) { + err = snd_usb_cm106_write_int_reg(dev, reg, val[reg]); + if (err < 0) + return err; + } + + return err; +} + +/* quirk for Plantronics GameCom 780 with CM6302 chip */ +static int snd_usb_gamecon780_boot_quirk(struct usb_device *dev) +{ + /* set the initial volume and don't change; other values are either + * too loud or silent due to firmware bug (bko#65251) + */ + u8 buf[2] = { 0x74, 0xe3 }; + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + UAC_FU_VOLUME << 8, 9 << 8, buf, 2); +} + +/* + * Novation Twitch DJ controller + * Focusrite Novation Saffire 6 USB audio card + */ +static int snd_usb_novation_boot_quirk(struct usb_device *dev) +{ + /* preemptively set up the device because otherwise the + * raw MIDI endpoints are not active */ + usb_set_interface(dev, 0, 1); + return 0; +} + +/* + * This call will put the synth in "USB send" mode, i.e it will send MIDI + * messages through USB (this is disabled at startup). The synth will + * acknowledge by sending a sysex on endpoint 0x85 and by displaying a USB + * sign on its LCD. Values here are chosen based on sniffing USB traffic + * under Windows. + */ +static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) +{ + int err, actual_length; + + /* "midi send" enable */ + static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 }; + + void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL); + if (!buf) + return -ENOMEM; + err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf, + ARRAY_SIZE(seq), &actual_length, 1000); + kfree(buf); + if (err < 0) + return err; + + return 0; +} + +/* + * Some sound cards from Native Instruments are in fact compliant to the USB + * audio standard of version 2 and other approved USB standards, even though + * they come up as vendor-specific device when first connected. + * + * However, they can be told to come up with a new set of descriptors + * upon their next enumeration, and the interfaces announced by the new + * descriptors will then be handled by the kernel's class drivers. As the + * product ID will also change, no further checks are required. + */ + +static int snd_usb_nativeinstruments_boot_quirk(struct usb_device *dev) +{ + int ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0xaf, USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 1, 0, NULL, 0, 1000); + + if (ret < 0) + return ret; + + usb_reset_device(dev); + + /* return -EAGAIN, so the creation of an audio interface for this + * temporary device is aborted. The device will reconnect with a + * new product ID */ + return -EAGAIN; +} + +static void mbox2_setup_48_24_magic(struct usb_device *dev) +{ + u8 srate[3]; + u8 temp[12]; + + /* Choose 48000Hz permanently */ + srate[0] = 0x80; + srate[1] = 0xbb; + srate[2] = 0x00; + + /* Send the magic! */ + snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + 0x01, 0x22, 0x0100, 0x0085, &temp, 0x0003); + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 0x81, 0xa2, 0x0100, 0x0085, &srate, 0x0003); + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 0x81, 0xa2, 0x0100, 0x0086, &srate, 0x0003); + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + 0x81, 0xa2, 0x0100, 0x0003, &srate, 0x0003); + return; +} + +/* Digidesign Mbox 2 needs to load firmware onboard + * and driver must wait a few seconds for initialisation. + */ + +#define MBOX2_FIRMWARE_SIZE 646 +#define MBOX2_BOOT_LOADING 0x01 /* Hard coded into the device */ +#define MBOX2_BOOT_READY 0x02 /* Hard coded into the device */ + +static int snd_usb_mbox2_boot_quirk(struct usb_device *dev) +{ + struct usb_host_config *config = dev->actconfig; + int err; + u8 bootresponse[0x12]; + int fwsize; + int count; + + fwsize = le16_to_cpu(get_cfg_desc(config)->wTotalLength); + + if (fwsize != MBOX2_FIRMWARE_SIZE) { + dev_err(&dev->dev, "Invalid firmware size=%d.\n", fwsize); + return -ENODEV; + } + + dev_dbg(&dev->dev, "Sending Digidesign Mbox 2 boot sequence...\n"); + + count = 0; + bootresponse[0] = MBOX2_BOOT_LOADING; + while ((bootresponse[0] == MBOX2_BOOT_LOADING) && (count < 10)) { + msleep(500); /* 0.5 second delay */ + snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + /* Control magic - load onboard firmware */ + 0x85, 0xc0, 0x0001, 0x0000, &bootresponse, 0x0012); + if (bootresponse[0] == MBOX2_BOOT_READY) + break; + dev_dbg(&dev->dev, "device not ready, resending boot sequence...\n"); + count++; + } + + if (bootresponse[0] != MBOX2_BOOT_READY) { + dev_err(&dev->dev, "Unknown bootresponse=%d, or timed out, ignoring device.\n", bootresponse[0]); + return -ENODEV; + } + + dev_dbg(&dev->dev, "device initialised!\n"); + + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, + &dev->descriptor, sizeof(dev->descriptor)); + config = dev->actconfig; + if (err < 0) + dev_dbg(&dev->dev, "error usb_get_descriptor: %d\n", err); + + err = usb_reset_configuration(dev); + if (err < 0) + dev_dbg(&dev->dev, "error usb_reset_configuration: %d\n", err); + dev_dbg(&dev->dev, "mbox2_boot: new boot length = %d\n", + le16_to_cpu(get_cfg_desc(config)->wTotalLength)); + + mbox2_setup_48_24_magic(dev); + + dev_info(&dev->dev, "Digidesign Mbox 2: 24bit 48kHz"); + + return 0; /* Successful boot */ +} + +static int snd_usb_axefx3_boot_quirk(struct usb_device *dev) +{ + int err; + + dev_dbg(&dev->dev, "Waiting for Axe-Fx III to boot up...\n"); + + /* If the Axe-Fx III has not fully booted, it will timeout when trying + * to enable the audio streaming interface. A more generous timeout is + * used here to detect when the Axe-Fx III has finished booting as the + * set interface message will be acked once it has + */ + err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, + 1, 1, NULL, 0, 120000); + if (err < 0) { + dev_err(&dev->dev, + "failed waiting for Axe-Fx III to boot: %d\n", err); + return err; + } + + dev_dbg(&dev->dev, "Axe-Fx III is now ready\n"); + + err = usb_set_interface(dev, 1, 0); + if (err < 0) + dev_dbg(&dev->dev, + "error stopping Axe-Fx III interface: %d\n", err); + + return 0; +} + +/* + * Setup quirks + */ +#define MAUDIO_SET 0x01 /* parse device_setup */ +#define MAUDIO_SET_COMPATIBLE 0x80 /* use only "win-compatible" interfaces */ +#define MAUDIO_SET_DTS 0x02 /* enable DTS Digital Output */ +#define MAUDIO_SET_96K 0x04 /* 48-96KHz rate if set, 8-48KHz otherwise */ +#define MAUDIO_SET_24B 0x08 /* 24bits sample if set, 16bits otherwise */ +#define MAUDIO_SET_DI 0x10 /* enable Digital Input */ +#define MAUDIO_SET_MASK 0x1f /* bit mask for setup value */ +#define MAUDIO_SET_24B_48K_DI 0x19 /* 24bits+48KHz+Digital Input */ +#define MAUDIO_SET_24B_48K_NOTDI 0x09 /* 24bits+48KHz+No Digital Input */ +#define MAUDIO_SET_16B_48K_DI 0x11 /* 16bits+48KHz+Digital Input */ +#define MAUDIO_SET_16B_48K_NOTDI 0x01 /* 16bits+48KHz+No Digital Input */ + +static int quattro_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, int altno) +{ + /* Reset ALL ifaces to 0 altsetting. + * Call it for every possible altsetting of every interface. + */ + usb_set_interface(chip->dev, iface, 0); + if (chip->setup & MAUDIO_SET) { + if (chip->setup & MAUDIO_SET_COMPATIBLE) { + if (iface != 1 && iface != 2) + return 1; /* skip all interfaces but 1 and 2 */ + } else { + unsigned int mask; + if (iface == 1 || iface == 2) + return 1; /* skip interfaces 1 and 2 */ + if ((chip->setup & MAUDIO_SET_96K) && altno != 1) + return 1; /* skip this altsetting */ + mask = chip->setup & MAUDIO_SET_MASK; + if (mask == MAUDIO_SET_24B_48K_DI && altno != 2) + return 1; /* skip this altsetting */ + if (mask == MAUDIO_SET_24B_48K_NOTDI && altno != 3) + return 1; /* skip this altsetting */ + if (mask == MAUDIO_SET_16B_48K_NOTDI && altno != 4) + return 1; /* skip this altsetting */ + } + } + usb_audio_dbg(chip, + "using altsetting %d for interface %d config %d\n", + altno, iface, chip->setup); + return 0; /* keep this altsetting */ +} + +static int audiophile_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, + int altno) +{ + /* Reset ALL ifaces to 0 altsetting. + * Call it for every possible altsetting of every interface. + */ + usb_set_interface(chip->dev, iface, 0); + + if (chip->setup & MAUDIO_SET) { + unsigned int mask; + if ((chip->setup & MAUDIO_SET_DTS) && altno != 6) + return 1; /* skip this altsetting */ + if ((chip->setup & MAUDIO_SET_96K) && altno != 1) + return 1; /* skip this altsetting */ + mask = chip->setup & MAUDIO_SET_MASK; + if (mask == MAUDIO_SET_24B_48K_DI && altno != 2) + return 1; /* skip this altsetting */ + if (mask == MAUDIO_SET_24B_48K_NOTDI && altno != 3) + return 1; /* skip this altsetting */ + if (mask == MAUDIO_SET_16B_48K_DI && altno != 4) + return 1; /* skip this altsetting */ + if (mask == MAUDIO_SET_16B_48K_NOTDI && altno != 5) + return 1; /* skip this altsetting */ + } + + return 0; /* keep this altsetting */ +} + +static int fasttrackpro_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, int altno) +{ + /* Reset ALL ifaces to 0 altsetting. + * Call it for every possible altsetting of every interface. + */ + usb_set_interface(chip->dev, iface, 0); + + /* possible configuration where both inputs and only one output is + *used is not supported by the current setup + */ + if (chip->setup & (MAUDIO_SET | MAUDIO_SET_24B)) { + if (chip->setup & MAUDIO_SET_96K) { + if (altno != 3 && altno != 6) + return 1; + } else if (chip->setup & MAUDIO_SET_DI) { + if (iface == 4) + return 1; /* no analog input */ + if (altno != 2 && altno != 5) + return 1; /* enable only altsets 2 and 5 */ + } else { + if (iface == 5) + return 1; /* disable digialt input */ + if (altno != 2 && altno != 5) + return 1; /* enalbe only altsets 2 and 5 */ + } + } else { + /* keep only 16-Bit mode */ + if (altno != 1) + return 1; + } + + usb_audio_dbg(chip, + "using altsetting %d for interface %d config %d\n", + altno, iface, chip->setup); + return 0; /* keep this altsetting */ +} + +int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, + int iface, + int altno) +{ + /* audiophile usb: skip altsets incompatible with device_setup */ + if (chip->usb_id == USB_ID(0x0763, 0x2003)) + return audiophile_skip_setting_quirk(chip, iface, altno); + /* quattro usb: skip altsets incompatible with device_setup */ + if (chip->usb_id == USB_ID(0x0763, 0x2001)) + return quattro_skip_setting_quirk(chip, iface, altno); + /* fasttrackpro usb: skip altsets incompatible with device_setup */ + if (chip->usb_id == USB_ID(0x0763, 0x2012)) + return fasttrackpro_skip_setting_quirk(chip, iface, altno); + + return 0; +} + +int snd_usb_apply_boot_quirk(struct usb_device *dev, + struct usb_interface *intf, + const struct snd_usb_audio_quirk *quirk, + unsigned int id) +{ + switch (id) { + case USB_ID(0x041e, 0x3000): + /* SB Extigy needs special boot-up sequence */ + /* if more models come, this will go to the quirk list. */ + return snd_usb_extigy_boot_quirk(dev, intf); + + case USB_ID(0x041e, 0x3020): + /* SB Audigy 2 NX needs its own boot-up magic, too */ + return snd_usb_audigy2nx_boot_quirk(dev); + + case USB_ID(0x10f5, 0x0200): + /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */ + return snd_usb_cm106_boot_quirk(dev); + + case USB_ID(0x0d8c, 0x0102): + /* C-Media CM6206 / CM106-Like Sound Device */ + case USB_ID(0x0ccd, 0x00b1): /* Terratec Aureon 7.1 USB */ + return snd_usb_cm6206_boot_quirk(dev); + + case USB_ID(0x0dba, 0x3000): + /* Digidesign Mbox 2 */ + return snd_usb_mbox2_boot_quirk(dev); + + case USB_ID(0x1235, 0x0010): /* Focusrite Novation Saffire 6 USB */ + case USB_ID(0x1235, 0x0018): /* Focusrite Novation Twitch */ + return snd_usb_novation_boot_quirk(dev); + + case USB_ID(0x133e, 0x0815): + /* Access Music VirusTI Desktop */ + return snd_usb_accessmusic_boot_quirk(dev); + + case USB_ID(0x17cc, 0x1000): /* Komplete Audio 6 */ + case USB_ID(0x17cc, 0x1010): /* Traktor Audio 6 */ + case USB_ID(0x17cc, 0x1020): /* Traktor Audio 10 */ + return snd_usb_nativeinstruments_boot_quirk(dev); + case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro USB */ + return snd_usb_fasttrackpro_boot_quirk(dev); + case USB_ID(0x047f, 0xc010): /* Plantronics Gamecom 780 */ + return snd_usb_gamecon780_boot_quirk(dev); + case USB_ID(0x2466, 0x8010): /* Fractal Audio Axe-Fx 3 */ + return snd_usb_axefx3_boot_quirk(dev); + } + + return 0; +} + +/* + * check if the device uses big-endian samples + */ +int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *fp) +{ + /* it depends on altsetting whether the device is big-endian or not */ + switch (chip->usb_id) { + case USB_ID(0x0763, 0x2001): /* M-Audio Quattro: captured data only */ + if (fp->altsetting == 2 || fp->altsetting == 3 || + fp->altsetting == 5 || fp->altsetting == 6) + return 1; + break; + case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + if (chip->setup == 0x00 || + fp->altsetting == 1 || fp->altsetting == 2 || + fp->altsetting == 3) + return 1; + break; + case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro */ + if (fp->altsetting == 2 || fp->altsetting == 3 || + fp->altsetting == 5 || fp->altsetting == 6) + return 1; + break; + } + return 0; +} + +/* + * For E-Mu 0404USB/0202USB/TrackerPre/0204 sample rate should be set for device, + * not for interface. + */ + +enum { + EMU_QUIRK_SR_44100HZ = 0, + EMU_QUIRK_SR_48000HZ, + EMU_QUIRK_SR_88200HZ, + EMU_QUIRK_SR_96000HZ, + EMU_QUIRK_SR_176400HZ, + EMU_QUIRK_SR_192000HZ +}; + +static void set_format_emu_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt) +{ + unsigned char emu_samplerate_id = 0; + + /* When capture is active + * sample rate shouldn't be changed + * by playback substream + */ + if (subs->direction == SNDRV_PCM_STREAM_PLAYBACK) { + if (subs->stream->substream[SNDRV_PCM_STREAM_CAPTURE].interface != -1) + return; + } + + switch (fmt->rate_min) { + case 48000: + emu_samplerate_id = EMU_QUIRK_SR_48000HZ; + break; + case 88200: + emu_samplerate_id = EMU_QUIRK_SR_88200HZ; + break; + case 96000: + emu_samplerate_id = EMU_QUIRK_SR_96000HZ; + break; + case 176400: + emu_samplerate_id = EMU_QUIRK_SR_176400HZ; + break; + case 192000: + emu_samplerate_id = EMU_QUIRK_SR_192000HZ; + break; + default: + emu_samplerate_id = EMU_QUIRK_SR_44100HZ; + break; + } + snd_emuusb_set_samplerate(subs->stream->chip, emu_samplerate_id); + subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0; +} + +void snd_usb_set_format_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt) +{ + switch (subs->stream->chip->usb_id) { + case USB_ID(0x041e, 0x3f02): /* E-Mu 0202 USB */ + case USB_ID(0x041e, 0x3f04): /* E-Mu 0404 USB */ + case USB_ID(0x041e, 0x3f0a): /* E-Mu Tracker Pre */ + case USB_ID(0x041e, 0x3f19): /* E-Mu 0204 USB */ + set_format_emu_quirk(subs, fmt); + break; + case USB_ID(0x534d, 0x2109): /* MacroSilicon MS2109 */ + subs->stream_offset_adj = 2; + break; + } +} + +bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip) +{ + /* devices which do not support reading the sample rate. */ + switch (chip->usb_id) { + case USB_ID(0x041E, 0x4080): /* Creative Live Cam VF0610 */ + case USB_ID(0x04D8, 0xFEEA): /* Benchmark DAC1 Pre */ + case USB_ID(0x0556, 0x0014): /* Phoenix Audio TMX320VC */ + case USB_ID(0x05A3, 0x9420): /* ELP HD USB Camera */ + case USB_ID(0x05a7, 0x1020): /* Bose Companion 5 */ + case USB_ID(0x074D, 0x3553): /* Outlaw RR2150 (Micronas UAC3553B) */ + case USB_ID(0x1395, 0x740a): /* Sennheiser DECT */ + case USB_ID(0x1901, 0x0191): /* GE B850V3 CP2114 audio interface */ + case USB_ID(0x21B4, 0x0081): /* AudioQuest DragonFly */ + case USB_ID(0x2912, 0x30c8): /* Audioengine D1 */ + case USB_ID(0x413c, 0xa506): /* Dell AE515 sound bar */ + case USB_ID(0x046d, 0x084c): /* Logitech ConferenceCam Connect */ + return true; + } + + /* devices of these vendors don't support reading rate, either */ + switch (USB_ID_VENDOR(chip->usb_id)) { + case 0x045E: /* MS Lifecam */ + case 0x047F: /* Plantronics */ + case 0x1de7: /* Phoenix Audio */ + return true; + } + + return false; +} + +/* ITF-USB DSD based DACs need a vendor cmd to switch + * between PCM and native DSD mode + */ +static bool is_itf_usb_dsd_dac(unsigned int id) +{ + switch (id) { + case USB_ID(0x154e, 0x1002): /* Denon DCD-1500RE */ + case USB_ID(0x154e, 0x1003): /* Denon DA-300USB */ + case USB_ID(0x154e, 0x3005): /* Marantz HD-DAC1 */ + case USB_ID(0x154e, 0x3006): /* Marantz SA-14S1 */ + case USB_ID(0x1852, 0x5065): /* Luxman DA-06 */ + case USB_ID(0x0644, 0x8043): /* TEAC UD-501/UD-501V2/UD-503/NT-503 */ + case USB_ID(0x0644, 0x8044): /* Esoteric D-05X */ + case USB_ID(0x0644, 0x804a): /* TEAC UD-301 */ + return true; + } + return false; +} + +int snd_usb_select_mode_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt) +{ + struct usb_device *dev = subs->dev; + int err; + + if (is_itf_usb_dsd_dac(subs->stream->chip->usb_id)) { + /* First switch to alt set 0, otherwise the mode switch cmd + * will not be accepted by the DAC + */ + err = usb_set_interface(dev, fmt->iface, 0); + if (err < 0) + return err; + + msleep(20); /* Delay needed after setting the interface */ + + /* Vendor mode switch cmd is required. */ + if (fmt->formats & SNDRV_PCM_FMTBIT_DSD_U32_BE) { + /* DSD mode (DSD_U32) requested */ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0, + USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE, + 1, 1, NULL, 0); + if (err < 0) + return err; + + } else { + /* PCM or DOP mode (S32) requested */ + /* PCM mode (S16) requested */ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 0, + USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE, + 0, 1, NULL, 0); + if (err < 0) + return err; + + } + msleep(20); + } + return 0; +} + +void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep) +{ + /* + * "Playback Design" products send bogus feedback data at the start + * of the stream. Ignore them. + */ + if (USB_ID_VENDOR(ep->chip->usb_id) == 0x23ba && + ep->type == SND_USB_ENDPOINT_TYPE_SYNC) + ep->skip_packets = 4; + + /* + * M-Audio Fast Track C400/C600 - when packets are not skipped, real + * world latency varies by approx. +/- 50 frames (at 96KHz) each time + * the stream is (re)started. When skipping packets 16 at endpoint + * start up, the real world latency is stable within +/- 1 frame (also + * across power cycles). + */ + if ((ep->chip->usb_id == USB_ID(0x0763, 0x2030) || + ep->chip->usb_id == USB_ID(0x0763, 0x2031)) && + ep->type == SND_USB_ENDPOINT_TYPE_DATA) + ep->skip_packets = 16; + + /* Work around devices that report unreasonable feedback data */ + if ((ep->chip->usb_id == USB_ID(0x0644, 0x8038) || /* TEAC UD-H01 */ + ep->chip->usb_id == USB_ID(0x1852, 0x5034)) && /* T+A Dac8 */ + ep->syncmaxsize == 4) + ep->tenor_fb_quirk = 1; +} + +void snd_usb_set_interface_quirk(struct usb_device *dev) +{ + struct snd_usb_audio *chip = dev_get_drvdata(&dev->dev); + + if (!chip) + return; + /* + * "Playback Design" products need a 50ms delay after setting the + * USB interface. + */ + switch (USB_ID_VENDOR(chip->usb_id)) { + case 0x23ba: /* Playback Design */ + case 0x0644: /* TEAC Corp. */ + msleep(50); + break; + } +} + +/* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */ +void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe, + __u8 request, __u8 requesttype, __u16 value, + __u16 index, void *data, __u16 size) +{ + struct snd_usb_audio *chip = dev_get_drvdata(&dev->dev); + + if (!chip) + return; + /* + * "Playback Design" products need a 20ms delay after each + * class compliant request + */ + if (USB_ID_VENDOR(chip->usb_id) == 0x23ba && + (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + msleep(20); + + /* + * "TEAC Corp." products need a 20ms delay after each + * class compliant request + */ + if (USB_ID_VENDOR(chip->usb_id) == 0x0644 && + (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + msleep(20); + + /* ITF-USB DSD based DACs functionality need a delay + * after each class compliant request + */ + if (is_itf_usb_dsd_dac(chip->usb_id) + && (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + msleep(20); + + /* + * Plantronics headsets (C320, C320-M, etc) need a delay to avoid + * random microhpone failures. + */ + if (USB_ID_VENDOR(chip->usb_id) == 0x047f && + (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + msleep(20); + + /* Zoom R16/24, many Logitech(at least H650e/H570e/BCC950), + * Jabra 550a, Kingston HyperX needs a tiny delay here, + * otherwise requests like get/set frequency return + * as failed despite actually succeeding. + */ + if ((chip->usb_id == USB_ID(0x1686, 0x00dd) || + USB_ID_VENDOR(chip->usb_id) == 0x046d || /* Logitech */ + chip->usb_id == USB_ID(0x0b0e, 0x0349) || + chip->usb_id == USB_ID(0x0951, 0x16ad)) && + (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + usleep_range(1000, 2000); + + /* + * Samsung USBC Headset (AKG) need a tiny delay after each + * class compliant request. (Model number: AAM625R or AAM627R) + */ + if (chip->usb_id == USB_ID(0x04e8, 0xa051) && + (requesttype & USB_TYPE_MASK) == USB_TYPE_CLASS) + usleep_range(5000, 6000); +} + +/* + * snd_usb_interface_dsd_format_quirks() is called from format.c to + * augment the PCM format bit-field for DSD types. The UAC standards + * don't have a designated bit field to denote DSD-capable interfaces, + * hence all hardware that is known to support this format has to be + * listed here. + */ +u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, + struct audioformat *fp, + unsigned int sample_bytes) +{ + struct usb_interface *iface; + + /* Playback Designs */ + if (USB_ID_VENDOR(chip->usb_id) == 0x23ba && + USB_ID_PRODUCT(chip->usb_id) < 0x0110) { + switch (fp->altsetting) { + case 1: + fp->dsd_dop = true; + return SNDRV_PCM_FMTBIT_DSD_U16_LE; + case 2: + fp->dsd_bitrev = true; + return SNDRV_PCM_FMTBIT_DSD_U8; + case 3: + fp->dsd_bitrev = true; + return SNDRV_PCM_FMTBIT_DSD_U16_LE; + } + } + + /* XMOS based USB DACs */ + switch (chip->usb_id) { + case USB_ID(0x1511, 0x0037): /* AURALiC VEGA */ + case USB_ID(0x2522, 0x0012): /* LH Labs VI DAC Infinity */ + case USB_ID(0x2772, 0x0230): /* Pro-Ject Pre Box S2 Digital */ + if (fp->altsetting == 2) + return SNDRV_PCM_FMTBIT_DSD_U32_BE; + break; + + case USB_ID(0x0d8c, 0x0316): /* Hegel HD12 DSD */ + case USB_ID(0x10cb, 0x0103): /* The Bit Opus #3; with fp->dsd_raw */ + case USB_ID(0x16d0, 0x06b2): /* NuPrime DAC-10 */ + case USB_ID(0x16d0, 0x09dd): /* Encore mDSD */ + case USB_ID(0x16d0, 0x0733): /* Furutech ADL Stratos */ + case USB_ID(0x16d0, 0x09db): /* NuPrime Audio DAC-9 */ + case USB_ID(0x1db5, 0x0003): /* Bryston BDA3 */ + case USB_ID(0x22e1, 0xca01): /* HDTA Serenade DSD */ + case USB_ID(0x249c, 0x9326): /* M2Tech Young MkIII */ + case USB_ID(0x2616, 0x0106): /* PS Audio NuWave DAC */ + case USB_ID(0x2622, 0x0041): /* Audiolab M-DAC+ */ + case USB_ID(0x27f7, 0x3002): /* W4S DAC-2v2SE */ + case USB_ID(0x29a2, 0x0086): /* Mutec MC3+ USB */ + case USB_ID(0x6b42, 0x0042): /* MSB Technology */ + if (fp->altsetting == 3) + return SNDRV_PCM_FMTBIT_DSD_U32_BE; + break; + + /* Amanero Combo384 USB based DACs with native DSD support */ + case USB_ID(0x16d0, 0x071a): /* Amanero - Combo384 */ + case USB_ID(0x2ab6, 0x0004): /* T+A DAC8DSD-V2.0, MP1000E-V2.0, MP2000R-V2.0, MP2500R-V2.0, MP3100HV-V2.0 */ + case USB_ID(0x2ab6, 0x0005): /* T+A USB HD Audio 1 */ + case USB_ID(0x2ab6, 0x0006): /* T+A USB HD Audio 2 */ + if (fp->altsetting == 2) { + switch (le16_to_cpu(chip->dev->descriptor.bcdDevice)) { + case 0x199: + return SNDRV_PCM_FMTBIT_DSD_U32_LE; + case 0x19b: + case 0x203: + return SNDRV_PCM_FMTBIT_DSD_U32_BE; + default: + break; + } + } + break; + case USB_ID(0x16d0, 0x0a23): + if (fp->altsetting == 2) + return SNDRV_PCM_FMTBIT_DSD_U32_BE; + break; + + default: + break; + } + + /* ITF-USB DSD based DACs */ + if (is_itf_usb_dsd_dac(chip->usb_id)) { + iface = usb_ifnum_to_if(chip->dev, fp->iface); + + /* Altsetting 2 support native DSD if the num of altsets is + * three (0-2), + * Altsetting 3 support native DSD if the num of altsets is + * four (0-3). + */ + if (fp->altsetting == iface->num_altsetting - 1) + return SNDRV_PCM_FMTBIT_DSD_U32_BE; + } + + /* Mostly generic method to detect many DSD-capable implementations - + * from XMOS/Thesycon + */ + switch (USB_ID_VENDOR(chip->usb_id)) { + case 0x152a: /* Thesycon devices */ + case 0x20b1: /* XMOS based devices */ + case 0x22d9: /* Oppo */ + case 0x23ba: /* Playback Designs */ + case 0x25ce: /* Mytek devices */ + case 0x278b: /* Rotel? */ + case 0x292b: /* Gustard/Ess based devices */ + case 0x2972: /* FiiO devices */ + case 0x2ab6: /* T+A devices */ + case 0x3353: /* Khadas devices */ + case 0x3842: /* EVGA */ + case 0xc502: /* HiBy devices */ + if (fp->dsd_raw) + return SNDRV_PCM_FMTBIT_DSD_U32_BE; + break; + default: + break; + + } + + return 0; +} + +void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, + struct audioformat *fp, + int stream) +{ + switch (chip->usb_id) { + case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */ + /* Optoplay sets the sample rate attribute although + * it seems not supporting it in fact. + */ + fp->attributes &= ~UAC_EP_CS_ATTR_SAMPLE_RATE; + break; + case USB_ID(0x041e, 0x3020): /* Creative SB Audigy 2 NX */ + case USB_ID(0x0763, 0x2003): /* M-Audio Audiophile USB */ + /* doesn't set the sample rate attribute, but supports it */ + fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; + break; + case USB_ID(0x0763, 0x2001): /* M-Audio Quattro USB */ + case USB_ID(0x0763, 0x2012): /* M-Audio Fast Track Pro USB */ + case USB_ID(0x047f, 0x0ca1): /* plantronics headset */ + case USB_ID(0x077d, 0x07af): /* Griffin iMic (note that there is + an older model 77d:223) */ + /* + * plantronics headset and Griffin iMic have set adaptive-in + * although it's really not... + */ + fp->ep_attr &= ~USB_ENDPOINT_SYNCTYPE; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + fp->ep_attr |= USB_ENDPOINT_SYNC_ADAPTIVE; + else + fp->ep_attr |= USB_ENDPOINT_SYNC_SYNC; + break; + } +} + +/* + * registration quirk: + * the registration is skipped if a device matches with the given ID, + * unless the interface reaches to the defined one. This is for delaying + * the registration until the last known interface, so that the card and + * devices appear at the same time. + */ + +struct registration_quirk { + unsigned int usb_id; /* composed via USB_ID() */ + unsigned int interface; /* the interface to trigger register */ +}; + +#define REG_QUIRK_ENTRY(vendor, product, iface) \ + { .usb_id = USB_ID(vendor, product), .interface = (iface) } + +static const struct registration_quirk registration_quirks[] = { + REG_QUIRK_ENTRY(0x0951, 0x16d8, 2), /* Kingston HyperX AMP */ + REG_QUIRK_ENTRY(0x0951, 0x16ed, 2), /* Kingston HyperX Cloud Alpha S */ + REG_QUIRK_ENTRY(0x0951, 0x16ea, 2), /* Kingston HyperX Cloud Flight S */ + REG_QUIRK_ENTRY(0x0ecb, 0x1f46, 2), /* JBL Quantum 600 */ + REG_QUIRK_ENTRY(0x0ecb, 0x1f47, 2), /* JBL Quantum 800 */ + REG_QUIRK_ENTRY(0x0ecb, 0x1f4c, 2), /* JBL Quantum 400 */ + REG_QUIRK_ENTRY(0x0ecb, 0x2039, 2), /* JBL Quantum 400 */ + REG_QUIRK_ENTRY(0x0ecb, 0x203c, 2), /* JBL Quantum 600 */ + REG_QUIRK_ENTRY(0x0ecb, 0x203e, 2), /* JBL Quantum 800 */ + { 0 } /* terminator */ +}; + +/* return true if skipping registration */ +bool snd_usb_registration_quirk(struct snd_usb_audio *chip, int iface) +{ + const struct registration_quirk *q; + + for (q = registration_quirks; q->usb_id; q++) + if (chip->usb_id == q->usb_id) + return iface != q->interface; + + /* Register as normal */ + return false; +} diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h new file mode 100644 index 000000000..1efa6c968 --- /dev/null +++ b/sound/usb/quirks.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_QUIRKS_H +#define __USBAUDIO_QUIRKS_H + +struct audioformat; +struct snd_usb_endpoint; +struct snd_usb_substream; + +int snd_usb_create_quirk(struct snd_usb_audio *chip, + struct usb_interface *iface, + struct usb_driver *driver, + const struct snd_usb_audio_quirk *quirk); + +int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, + int iface, + int altno); + +int snd_usb_apply_boot_quirk(struct usb_device *dev, + struct usb_interface *intf, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id); + +void snd_usb_set_format_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt); + +bool snd_usb_get_sample_rate_quirk(struct snd_usb_audio *chip); + +int snd_usb_is_big_endian_format(struct snd_usb_audio *chip, + struct audioformat *fp); + +void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep); + +void snd_usb_set_interface_quirk(struct usb_device *dev); +void snd_usb_ctl_msg_quirk(struct usb_device *dev, unsigned int pipe, + __u8 request, __u8 requesttype, __u16 value, + __u16 index, void *data, __u16 size); + +int snd_usb_select_mode_quirk(struct snd_usb_substream *subs, + struct audioformat *fmt); + +u64 snd_usb_interface_dsd_format_quirks(struct snd_usb_audio *chip, + struct audioformat *fp, + unsigned int sample_bytes); + +void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, + struct audioformat *fp, + int stream); + +bool snd_usb_registration_quirk(struct snd_usb_audio *chip, int iface); + +#endif /* __USBAUDIO_QUIRKS_H */ diff --git a/sound/usb/stream.c b/sound/usb/stream.c new file mode 100644 index 000000000..9a950aaf5 --- /dev/null +++ b/sound/usb/stream.c @@ -0,0 +1,1214 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "usbaudio.h" +#include "card.h" +#include "proc.h" +#include "quirks.h" +#include "endpoint.h" +#include "pcm.h" +#include "helper.h" +#include "format.h" +#include "clock.h" +#include "stream.h" +#include "power.h" + +/* + * free a substream + */ +static void free_substream(struct snd_usb_substream *subs) +{ + struct audioformat *fp, *n; + + if (!subs->num_formats) + return; /* not initialized */ + list_for_each_entry_safe(fp, n, &subs->fmt_list, list) { + kfree(fp->rate_table); + kfree(fp->chmap); + kfree(fp); + } + kfree(subs->rate_list.list); + kfree(subs->str_pd); +} + + +/* + * free a usb stream instance + */ +static void snd_usb_audio_stream_free(struct snd_usb_stream *stream) +{ + free_substream(&stream->substream[0]); + free_substream(&stream->substream[1]); + list_del(&stream->list); + kfree(stream); +} + +static void snd_usb_audio_pcm_free(struct snd_pcm *pcm) +{ + struct snd_usb_stream *stream = pcm->private_data; + if (stream) { + stream->pcm = NULL; + snd_usb_audio_stream_free(stream); + } +} + +/* + * initialize the substream instance. + */ + +static void snd_usb_init_substream(struct snd_usb_stream *as, + int stream, + struct audioformat *fp, + struct snd_usb_power_domain *pd) +{ + struct snd_usb_substream *subs = &as->substream[stream]; + + INIT_LIST_HEAD(&subs->fmt_list); + spin_lock_init(&subs->lock); + + subs->stream = as; + subs->direction = stream; + subs->dev = as->chip->dev; + subs->txfr_quirk = as->chip->txfr_quirk; + subs->tx_length_quirk = as->chip->tx_length_quirk; + subs->speed = snd_usb_get_speed(subs->dev); + subs->pkt_offset_adj = 0; + subs->stream_offset_adj = 0; + + snd_usb_set_pcm_ops(as->pcm, stream); + + list_add_tail(&fp->list, &subs->fmt_list); + subs->formats |= fp->formats; + subs->num_formats++; + subs->fmt_type = fp->fmt_type; + subs->ep_num = fp->endpoint; + if (fp->channels > subs->channels_max) + subs->channels_max = fp->channels; + + if (pd) { + subs->str_pd = pd; + /* Initialize Power Domain to idle status D1 */ + snd_usb_power_domain_set(subs->stream->chip, pd, + UAC3_PD_STATE_D1); + } + + snd_usb_preallocate_buffer(subs); +} + +/* kctl callbacks for usb-audio channel maps */ +static int usb_chmap_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct snd_usb_substream *subs = info->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = subs->channels_max; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SNDRV_CHMAP_LAST; + return 0; +} + +/* check whether a duplicated entry exists in the audiofmt list */ +static bool have_dup_chmap(struct snd_usb_substream *subs, + struct audioformat *fp) +{ + struct audioformat *prev = fp; + + list_for_each_entry_continue_reverse(prev, &subs->fmt_list, list) { + if (prev->chmap && + !memcmp(prev->chmap, fp->chmap, sizeof(*fp->chmap))) + return true; + } + return false; +} + +static int usb_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct snd_usb_substream *subs = info->private_data; + struct audioformat *fp; + unsigned int __user *dst; + int count = 0; + + if (size < 8) + return -ENOMEM; + if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) + return -EFAULT; + size -= 8; + dst = tlv + 2; + list_for_each_entry(fp, &subs->fmt_list, list) { + int i, ch_bytes; + + if (!fp->chmap) + continue; + if (have_dup_chmap(subs, fp)) + continue; + /* copy the entry */ + ch_bytes = fp->chmap->channels * 4; + if (size < 8 + ch_bytes) + return -ENOMEM; + if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) || + put_user(ch_bytes, dst + 1)) + return -EFAULT; + dst += 2; + for (i = 0; i < fp->chmap->channels; i++, dst++) { + if (put_user(fp->chmap->map[i], dst)) + return -EFAULT; + } + + count += 8 + ch_bytes; + size -= 8 + ch_bytes; + } + if (put_user(count, tlv + 1)) + return -EFAULT; + return 0; +} + +static int usb_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct snd_usb_substream *subs = info->private_data; + struct snd_pcm_chmap_elem *chmap = NULL; + int i = 0; + + if (subs->cur_audiofmt) + chmap = subs->cur_audiofmt->chmap; + if (chmap) { + for (i = 0; i < chmap->channels; i++) + ucontrol->value.integer.value[i] = chmap->map[i]; + } + for (; i < subs->channels_max; i++) + ucontrol->value.integer.value[i] = 0; + return 0; +} + +/* create a chmap kctl assigned to the given USB substream */ +static int add_chmap(struct snd_pcm *pcm, int stream, + struct snd_usb_substream *subs) +{ + struct audioformat *fp; + struct snd_pcm_chmap *chmap; + struct snd_kcontrol *kctl; + int err; + + list_for_each_entry(fp, &subs->fmt_list, list) + if (fp->chmap) + goto ok; + /* no chmap is found */ + return 0; + + ok: + err = snd_pcm_add_chmap_ctls(pcm, stream, NULL, 0, 0, &chmap); + if (err < 0) + return err; + + /* override handlers */ + chmap->private_data = subs; + kctl = chmap->kctl; + kctl->info = usb_chmap_ctl_info; + kctl->get = usb_chmap_ctl_get; + kctl->tlv.c = usb_chmap_ctl_tlv; + + return 0; +} + +/* convert from USB ChannelConfig bits to ALSA chmap element */ +static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits, + int protocol) +{ + static const unsigned int uac1_maps[] = { + SNDRV_CHMAP_FL, /* left front */ + SNDRV_CHMAP_FR, /* right front */ + SNDRV_CHMAP_FC, /* center front */ + SNDRV_CHMAP_LFE, /* LFE */ + SNDRV_CHMAP_SL, /* left surround */ + SNDRV_CHMAP_SR, /* right surround */ + SNDRV_CHMAP_FLC, /* left of center */ + SNDRV_CHMAP_FRC, /* right of center */ + SNDRV_CHMAP_RC, /* surround */ + SNDRV_CHMAP_SL, /* side left */ + SNDRV_CHMAP_SR, /* side right */ + SNDRV_CHMAP_TC, /* top */ + 0 /* terminator */ + }; + static const unsigned int uac2_maps[] = { + SNDRV_CHMAP_FL, /* front left */ + SNDRV_CHMAP_FR, /* front right */ + SNDRV_CHMAP_FC, /* front center */ + SNDRV_CHMAP_LFE, /* LFE */ + SNDRV_CHMAP_RL, /* back left */ + SNDRV_CHMAP_RR, /* back right */ + SNDRV_CHMAP_FLC, /* front left of center */ + SNDRV_CHMAP_FRC, /* front right of center */ + SNDRV_CHMAP_RC, /* back center */ + SNDRV_CHMAP_SL, /* side left */ + SNDRV_CHMAP_SR, /* side right */ + SNDRV_CHMAP_TC, /* top center */ + SNDRV_CHMAP_TFL, /* top front left */ + SNDRV_CHMAP_TFC, /* top front center */ + SNDRV_CHMAP_TFR, /* top front right */ + SNDRV_CHMAP_TRL, /* top back left */ + SNDRV_CHMAP_TRC, /* top back center */ + SNDRV_CHMAP_TRR, /* top back right */ + SNDRV_CHMAP_TFLC, /* top front left of center */ + SNDRV_CHMAP_TFRC, /* top front right of center */ + SNDRV_CHMAP_LLFE, /* left LFE */ + SNDRV_CHMAP_RLFE, /* right LFE */ + SNDRV_CHMAP_TSL, /* top side left */ + SNDRV_CHMAP_TSR, /* top side right */ + SNDRV_CHMAP_BC, /* bottom center */ + SNDRV_CHMAP_RLC, /* back left of center */ + SNDRV_CHMAP_RRC, /* back right of center */ + 0 /* terminator */ + }; + struct snd_pcm_chmap_elem *chmap; + const unsigned int *maps; + int c; + + if (channels > ARRAY_SIZE(chmap->map)) + return NULL; + + chmap = kzalloc(sizeof(*chmap), GFP_KERNEL); + if (!chmap) + return NULL; + + maps = protocol == UAC_VERSION_2 ? uac2_maps : uac1_maps; + chmap->channels = channels; + c = 0; + + if (bits) { + for (; bits && *maps; maps++, bits >>= 1) + if (bits & 1) + chmap->map[c++] = *maps; + } else { + /* If we're missing wChannelConfig, then guess something + to make sure the channel map is not skipped entirely */ + if (channels == 1) + chmap->map[c++] = SNDRV_CHMAP_MONO; + else + for (; c < channels && *maps; maps++) + chmap->map[c++] = *maps; + } + + for (; c < channels; c++) + chmap->map[c] = SNDRV_CHMAP_UNKNOWN; + + return chmap; +} + +/* UAC3 device stores channels information in Cluster Descriptors */ +static struct +snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor + *cluster) +{ + unsigned int channels = cluster->bNrChannels; + struct snd_pcm_chmap_elem *chmap; + void *p = cluster; + int len, c; + + if (channels > ARRAY_SIZE(chmap->map)) + return NULL; + + chmap = kzalloc(sizeof(*chmap), GFP_KERNEL); + if (!chmap) + return NULL; + + len = le16_to_cpu(cluster->wLength); + c = 0; + p += sizeof(struct uac3_cluster_header_descriptor); + + while (((p - (void *)cluster) < len) && (c < channels)) { + struct uac3_cluster_segment_descriptor *cs_desc = p; + u16 cs_len; + u8 cs_type; + + cs_len = le16_to_cpu(cs_desc->wLength); + cs_type = cs_desc->bSegmentType; + + if (cs_type == UAC3_CHANNEL_INFORMATION) { + struct uac3_cluster_information_segment_descriptor *is = p; + unsigned char map; + + /* + * TODO: this conversion is not complete, update it + * after adding UAC3 values to asound.h + */ + switch (is->bChRelationship) { + case UAC3_CH_MONO: + map = SNDRV_CHMAP_MONO; + break; + case UAC3_CH_LEFT: + case UAC3_CH_FRONT_LEFT: + case UAC3_CH_HEADPHONE_LEFT: + map = SNDRV_CHMAP_FL; + break; + case UAC3_CH_RIGHT: + case UAC3_CH_FRONT_RIGHT: + case UAC3_CH_HEADPHONE_RIGHT: + map = SNDRV_CHMAP_FR; + break; + case UAC3_CH_FRONT_CENTER: + map = SNDRV_CHMAP_FC; + break; + case UAC3_CH_FRONT_LEFT_OF_CENTER: + map = SNDRV_CHMAP_FLC; + break; + case UAC3_CH_FRONT_RIGHT_OF_CENTER: + map = SNDRV_CHMAP_FRC; + break; + case UAC3_CH_SIDE_LEFT: + map = SNDRV_CHMAP_SL; + break; + case UAC3_CH_SIDE_RIGHT: + map = SNDRV_CHMAP_SR; + break; + case UAC3_CH_BACK_LEFT: + map = SNDRV_CHMAP_RL; + break; + case UAC3_CH_BACK_RIGHT: + map = SNDRV_CHMAP_RR; + break; + case UAC3_CH_BACK_CENTER: + map = SNDRV_CHMAP_RC; + break; + case UAC3_CH_BACK_LEFT_OF_CENTER: + map = SNDRV_CHMAP_RLC; + break; + case UAC3_CH_BACK_RIGHT_OF_CENTER: + map = SNDRV_CHMAP_RRC; + break; + case UAC3_CH_TOP_CENTER: + map = SNDRV_CHMAP_TC; + break; + case UAC3_CH_TOP_FRONT_LEFT: + map = SNDRV_CHMAP_TFL; + break; + case UAC3_CH_TOP_FRONT_RIGHT: + map = SNDRV_CHMAP_TFR; + break; + case UAC3_CH_TOP_FRONT_CENTER: + map = SNDRV_CHMAP_TFC; + break; + case UAC3_CH_TOP_FRONT_LOC: + map = SNDRV_CHMAP_TFLC; + break; + case UAC3_CH_TOP_FRONT_ROC: + map = SNDRV_CHMAP_TFRC; + break; + case UAC3_CH_TOP_SIDE_LEFT: + map = SNDRV_CHMAP_TSL; + break; + case UAC3_CH_TOP_SIDE_RIGHT: + map = SNDRV_CHMAP_TSR; + break; + case UAC3_CH_TOP_BACK_LEFT: + map = SNDRV_CHMAP_TRL; + break; + case UAC3_CH_TOP_BACK_RIGHT: + map = SNDRV_CHMAP_TRR; + break; + case UAC3_CH_TOP_BACK_CENTER: + map = SNDRV_CHMAP_TRC; + break; + case UAC3_CH_BOTTOM_CENTER: + map = SNDRV_CHMAP_BC; + break; + case UAC3_CH_LOW_FREQUENCY_EFFECTS: + map = SNDRV_CHMAP_LFE; + break; + case UAC3_CH_LFE_LEFT: + map = SNDRV_CHMAP_LLFE; + break; + case UAC3_CH_LFE_RIGHT: + map = SNDRV_CHMAP_RLFE; + break; + case UAC3_CH_RELATIONSHIP_UNDEFINED: + default: + map = SNDRV_CHMAP_UNKNOWN; + break; + } + chmap->map[c++] = map; + } + p += cs_len; + } + + if (channels < c) + pr_err("%s: channel number mismatch\n", __func__); + + chmap->channels = channels; + + for (; c < channels; c++) + chmap->map[c] = SNDRV_CHMAP_UNKNOWN; + + return chmap; +} + +/* + * add this endpoint to the chip instance. + * if a stream with the same endpoint already exists, append to it. + * if not, create a new pcm stream. note, fp is added to the substream + * fmt_list and will be freed on the chip instance release. do not free + * fp or do remove it from the substream fmt_list to avoid double-free. + */ +static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip, + int stream, + struct audioformat *fp, + struct snd_usb_power_domain *pd) + +{ + struct snd_usb_stream *as; + struct snd_usb_substream *subs; + struct snd_pcm *pcm; + int err; + + list_for_each_entry(as, &chip->pcm_list, list) { + if (as->fmt_type != fp->fmt_type) + continue; + subs = &as->substream[stream]; + if (subs->ep_num == fp->endpoint) { + list_add_tail(&fp->list, &subs->fmt_list); + subs->num_formats++; + subs->formats |= fp->formats; + return 0; + } + } + /* look for an empty stream */ + list_for_each_entry(as, &chip->pcm_list, list) { + if (as->fmt_type != fp->fmt_type) + continue; + subs = &as->substream[stream]; + if (subs->ep_num) + continue; + err = snd_pcm_new_stream(as->pcm, stream, 1); + if (err < 0) + return err; + snd_usb_init_substream(as, stream, fp, pd); + return add_chmap(as->pcm, stream, subs); + } + + /* create a new pcm */ + as = kzalloc(sizeof(*as), GFP_KERNEL); + if (!as) + return -ENOMEM; + as->pcm_index = chip->pcm_devs; + as->chip = chip; + as->fmt_type = fp->fmt_type; + err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs, + stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0, + stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1, + &pcm); + if (err < 0) { + kfree(as); + return err; + } + as->pcm = pcm; + pcm->private_data = as; + pcm->private_free = snd_usb_audio_pcm_free; + pcm->info_flags = 0; + if (chip->pcm_devs > 0) + sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs); + else + strcpy(pcm->name, "USB Audio"); + + snd_usb_init_substream(as, stream, fp, pd); + + /* + * Keep using head insertion for M-Audio Audiophile USB (tm) which has a + * fix to swap capture stream order in conf/cards/USB-audio.conf + */ + if (chip->usb_id == USB_ID(0x0763, 0x2003)) + list_add(&as->list, &chip->pcm_list); + else + list_add_tail(&as->list, &chip->pcm_list); + + chip->pcm_devs++; + + snd_usb_proc_pcm_format_add(as); + + return add_chmap(pcm, stream, &as->substream[stream]); +} + +int snd_usb_add_audio_stream(struct snd_usb_audio *chip, + int stream, + struct audioformat *fp) +{ + return __snd_usb_add_audio_stream(chip, stream, fp, NULL); +} + +static int snd_usb_add_audio_stream_v3(struct snd_usb_audio *chip, + int stream, + struct audioformat *fp, + struct snd_usb_power_domain *pd) +{ + return __snd_usb_add_audio_stream(chip, stream, fp, pd); +} + +static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int protocol, int iface_no) +{ + /* parsed with a v1 header here. that's ok as we only look at the + * header first which is the same for both versions */ + struct uac_iso_endpoint_descriptor *csep; + struct usb_interface_descriptor *altsd = get_iface_desc(alts); + int attributes = 0; + + csep = snd_usb_find_desc(alts->endpoint[0].extra, alts->endpoint[0].extralen, NULL, USB_DT_CS_ENDPOINT); + + /* Creamware Noah has this descriptor after the 2nd endpoint */ + if (!csep && altsd->bNumEndpoints >= 2) + csep = snd_usb_find_desc(alts->endpoint[1].extra, alts->endpoint[1].extralen, NULL, USB_DT_CS_ENDPOINT); + + /* + * If we can't locate the USB_DT_CS_ENDPOINT descriptor in the extra + * bytes after the first endpoint, go search the entire interface. + * Some devices have it directly *before* the standard endpoint. + */ + if (!csep) + csep = snd_usb_find_desc(alts->extra, alts->extralen, NULL, USB_DT_CS_ENDPOINT); + + if (!csep || csep->bLength < 7 || + csep->bDescriptorSubtype != UAC_EP_GENERAL) + goto error; + + if (protocol == UAC_VERSION_1) { + attributes = csep->bmAttributes; + } else if (protocol == UAC_VERSION_2) { + struct uac2_iso_endpoint_descriptor *csep2 = + (struct uac2_iso_endpoint_descriptor *) csep; + + if (csep2->bLength < sizeof(*csep2)) + goto error; + attributes = csep->bmAttributes & UAC_EP_CS_ATTR_FILL_MAX; + + /* emulate the endpoint attributes of a v1 device */ + if (csep2->bmControls & UAC2_CONTROL_PITCH) + attributes |= UAC_EP_CS_ATTR_PITCH_CONTROL; + } else { /* UAC_VERSION_3 */ + struct uac3_iso_endpoint_descriptor *csep3 = + (struct uac3_iso_endpoint_descriptor *) csep; + + if (csep3->bLength < sizeof(*csep3)) + goto error; + /* emulate the endpoint attributes of a v1 device */ + if (le32_to_cpu(csep3->bmControls) & UAC2_CONTROL_PITCH) + attributes |= UAC_EP_CS_ATTR_PITCH_CONTROL; + } + + return attributes; + + error: + usb_audio_warn(chip, + "%u:%d : no or invalid class specific endpoint descriptor\n", + iface_no, altsd->bAlternateSetting); + return 0; +} + +/* find an input terminal descriptor (either UAC1 or UAC2) with the given + * terminal id + */ +static void * +snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface, + int terminal_id, int protocol) +{ + struct uac2_input_terminal_descriptor *term = NULL; + + while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + term, UAC_INPUT_TERMINAL))) { + if (!snd_usb_validate_audio_desc(term, protocol)) + continue; + if (term->bTerminalID == terminal_id) + return term; + } + + return NULL; +} + +static void * +snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface, + int terminal_id, int protocol) +{ + /* OK to use with both UAC2 and UAC3 */ + struct uac2_output_terminal_descriptor *term = NULL; + + while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + term, UAC_OUTPUT_TERMINAL))) { + if (!snd_usb_validate_audio_desc(term, protocol)) + continue; + if (term->bTerminalID == terminal_id) + return term; + } + + return NULL; +} + +static struct audioformat * +audio_format_alloc_init(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int protocol, int iface_no, int altset_idx, + int altno, int num_channels, int clock) +{ + struct audioformat *fp; + + fp = kzalloc(sizeof(*fp), GFP_KERNEL); + if (!fp) + return NULL; + + fp->iface = iface_no; + fp->altsetting = altno; + fp->altset_idx = altset_idx; + fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; + fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; + fp->datainterval = snd_usb_parse_datainterval(chip, alts); + fp->protocol = protocol; + fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + fp->channels = num_channels; + if (snd_usb_get_speed(chip->dev) == USB_SPEED_HIGH) + fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) + * (fp->maxpacksize & 0x7ff); + fp->clock = clock; + INIT_LIST_HEAD(&fp->list); + + return fp; +} + +static struct audioformat * +snd_usb_get_audioformat_uac12(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + int protocol, int iface_no, int altset_idx, + int altno, int stream, int bm_quirk) +{ + struct usb_device *dev = chip->dev; + struct uac_format_type_i_continuous_descriptor *fmt; + unsigned int num_channels = 0, chconfig = 0; + struct audioformat *fp; + int clock = 0; + u64 format; + + /* get audio formats */ + if (protocol == UAC_VERSION_1) { + struct uac1_as_header_descriptor *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + struct uac_input_terminal_descriptor *iterm; + + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + format = le16_to_cpu(as->wFormatTag); /* remember the format value */ + + iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink, + protocol); + if (iterm) { + num_channels = iterm->bNrChannels; + chconfig = le16_to_cpu(iterm->wChannelConfig); + } + } else { /* UAC_VERSION_2 */ + struct uac2_input_terminal_descriptor *input_term; + struct uac2_output_terminal_descriptor *output_term; + struct uac2_as_header_descriptor *as = + snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + num_channels = as->bNrChannels; + format = le32_to_cpu(as->bmFormats); + chconfig = le32_to_cpu(as->bmChannelConfig); + + /* + * lookup the terminal associated to this interface + * to extract the clock + */ + input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink, + protocol); + if (input_term) { + clock = input_term->bCSourceID; + if (!chconfig && (num_channels == input_term->bNrChannels)) + chconfig = le32_to_cpu(input_term->bmChannelConfig); + goto found_clock; + } + + output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink, + protocol); + if (output_term) { + clock = output_term->bCSourceID; + goto found_clock; + } + + dev_err(&dev->dev, + "%u:%d : bogus bTerminalLink %d\n", + iface_no, altno, as->bTerminalLink); + return NULL; + } + +found_clock: + /* get format type */ + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_FORMAT_TYPE); + if (!fmt) { + dev_err(&dev->dev, + "%u:%d : no UAC_FORMAT_TYPE desc\n", + iface_no, altno); + return NULL; + } + if (((protocol == UAC_VERSION_1) && (fmt->bLength < 8)) + || ((protocol == UAC_VERSION_2) && + (fmt->bLength < 6))) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_FORMAT_TYPE desc\n", + iface_no, altno); + return NULL; + } + + /* + * Blue Microphones workaround: The last altsetting is + * identical with the previous one, except for a larger + * packet size, but is actually a mislabeled two-channel + * setting; ignore it. + * + * Part 2: analyze quirk flag and format + */ + if (bm_quirk && fmt->bNrChannels == 1 && fmt->bSubframeSize == 2) + return NULL; + + fp = audio_format_alloc_init(chip, alts, protocol, iface_no, + altset_idx, altno, num_channels, clock); + if (!fp) + return ERR_PTR(-ENOMEM); + + fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, + iface_no); + + /* some quirks for attributes here */ + snd_usb_audioformat_attributes_quirk(chip, fp, stream); + + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format(chip, fp, format, + fmt, stream) < 0) { + kfree(fp->rate_table); + kfree(fp); + return NULL; + } + + /* Create chmap */ + if (fp->channels != num_channels) + chconfig = 0; + + fp->chmap = convert_chmap(fp->channels, chconfig, protocol); + + return fp; +} + +static struct audioformat * +snd_usb_get_audioformat_uac3(struct snd_usb_audio *chip, + struct usb_host_interface *alts, + struct snd_usb_power_domain **pd_out, + int iface_no, int altset_idx, + int altno, int stream) +{ + struct usb_device *dev = chip->dev; + struct uac3_input_terminal_descriptor *input_term; + struct uac3_output_terminal_descriptor *output_term; + struct uac3_cluster_header_descriptor *cluster; + struct uac3_as_header_descriptor *as = NULL; + struct uac3_hc_descriptor_header hc_header; + struct snd_pcm_chmap_elem *chmap; + struct snd_usb_power_domain *pd; + unsigned char badd_profile; + u64 badd_formats = 0; + unsigned int num_channels; + struct audioformat *fp; + u16 cluster_id, wLength; + int clock = 0; + int err; + + badd_profile = chip->badd_profile; + + if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + unsigned int maxpacksize = + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); + + switch (maxpacksize) { + default: + dev_err(&dev->dev, + "%u:%d : incorrect wMaxPacketSize for BADD profile\n", + iface_no, altno); + return NULL; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: + badd_formats = SNDRV_PCM_FMTBIT_S16_LE; + num_channels = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: + badd_formats = SNDRV_PCM_FMTBIT_S24_3LE; + num_channels = 1; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: + badd_formats = SNDRV_PCM_FMTBIT_S16_LE; + num_channels = 2; + break; + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: + case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: + badd_formats = SNDRV_PCM_FMTBIT_S24_3LE; + num_channels = 2; + break; + } + + chmap = kzalloc(sizeof(*chmap), GFP_KERNEL); + if (!chmap) + return ERR_PTR(-ENOMEM); + + if (num_channels == 1) { + chmap->map[0] = SNDRV_CHMAP_MONO; + } else { + chmap->map[0] = SNDRV_CHMAP_FL; + chmap->map[1] = SNDRV_CHMAP_FR; + } + + chmap->channels = num_channels; + clock = UAC3_BADD_CS_ID9; + goto found_clock; + } + + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, + NULL, UAC_AS_GENERAL); + if (!as) { + dev_err(&dev->dev, + "%u:%d : UAC_AS_GENERAL descriptor not found\n", + iface_no, altno); + return NULL; + } + + if (as->bLength < sizeof(*as)) { + dev_err(&dev->dev, + "%u:%d : invalid UAC_AS_GENERAL desc\n", + iface_no, altno); + return NULL; + } + + cluster_id = le16_to_cpu(as->wClusterDescrID); + if (!cluster_id) { + dev_err(&dev->dev, + "%u:%d : no cluster descriptor\n", + iface_no, altno); + return NULL; + } + + /* + * Get number of channels and channel map through + * High Capability Cluster Descriptor + * + * First step: get High Capability header and + * read size of Cluster Descriptor + */ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(chip), + &hc_header, sizeof(hc_header)); + if (err < 0) + return ERR_PTR(err); + else if (err != sizeof(hc_header)) { + dev_err(&dev->dev, + "%u:%d : can't get High Capability descriptor\n", + iface_no, altno); + return ERR_PTR(-EIO); + } + + /* + * Second step: allocate needed amount of memory + * and request Cluster Descriptor + */ + wLength = le16_to_cpu(hc_header.wLength); + cluster = kzalloc(wLength, GFP_KERNEL); + if (!cluster) + return ERR_PTR(-ENOMEM); + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC3_CS_REQ_HIGH_CAPABILITY_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + cluster_id, + snd_usb_ctrl_intf(chip), + cluster, wLength); + if (err < 0) { + kfree(cluster); + return ERR_PTR(err); + } else if (err != wLength) { + dev_err(&dev->dev, + "%u:%d : can't get Cluster Descriptor\n", + iface_no, altno); + kfree(cluster); + return ERR_PTR(-EIO); + } + + num_channels = cluster->bNrChannels; + chmap = convert_chmap_v3(cluster); + kfree(cluster); + + /* + * lookup the terminal associated to this interface + * to extract the clock + */ + input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink, + UAC_VERSION_3); + if (input_term) { + clock = input_term->bCSourceID; + goto found_clock; + } + + output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink, + UAC_VERSION_3); + if (output_term) { + clock = output_term->bCSourceID; + goto found_clock; + } + + dev_err(&dev->dev, "%u:%d : bogus bTerminalLink %d\n", + iface_no, altno, as->bTerminalLink); + kfree(chmap); + return NULL; + +found_clock: + fp = audio_format_alloc_init(chip, alts, UAC_VERSION_3, iface_no, + altset_idx, altno, num_channels, clock); + if (!fp) { + kfree(chmap); + return ERR_PTR(-ENOMEM); + } + + fp->chmap = chmap; + + if (badd_profile >= UAC3_FUNCTION_SUBCLASS_GENERIC_IO) { + fp->attributes = 0; /* No attributes */ + + fp->fmt_type = UAC_FORMAT_TYPE_I; + fp->formats = badd_formats; + + fp->nr_rates = 0; /* SNDRV_PCM_RATE_CONTINUOUS */ + fp->rate_min = UAC3_BADD_SAMPLING_RATE; + fp->rate_max = UAC3_BADD_SAMPLING_RATE; + fp->rates = SNDRV_PCM_RATE_CONTINUOUS; + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) { + kfree(fp->chmap); + kfree(fp->rate_table); + kfree(fp); + return NULL; + } + pd->pd_id = (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + UAC3_BADD_PD_ID10 : UAC3_BADD_PD_ID11; + pd->pd_d1d0_rec = UAC3_BADD_PD_RECOVER_D1D0; + pd->pd_d2d0_rec = UAC3_BADD_PD_RECOVER_D2D0; + + } else { + fp->attributes = parse_uac_endpoint_attributes(chip, alts, + UAC_VERSION_3, + iface_no); + + pd = snd_usb_find_power_domain(chip->ctrl_intf, + as->bTerminalLink); + + /* ok, let's parse further... */ + if (snd_usb_parse_audio_format_v3(chip, fp, as, stream) < 0) { + kfree(pd); + kfree(fp->chmap); + kfree(fp->rate_table); + kfree(fp); + return NULL; + } + } + + if (pd) + *pd_out = pd; + + return fp; +} + +int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) +{ + struct usb_device *dev; + struct usb_interface *iface; + struct usb_host_interface *alts; + struct usb_interface_descriptor *altsd; + int i, altno, err, stream; + struct audioformat *fp = NULL; + struct snd_usb_power_domain *pd = NULL; + int num, protocol; + + dev = chip->dev; + + /* parse the interface's altsettings */ + iface = usb_ifnum_to_if(dev, iface_no); + + num = iface->num_altsetting; + + /* + * Dallas DS4201 workaround: It presents 5 altsettings, but the last + * one misses syncpipe, and does not produce any sound. + */ + if (chip->usb_id == USB_ID(0x04fa, 0x4201)) + num = 4; + + for (i = 0; i < num; i++) { + alts = &iface->altsetting[i]; + altsd = get_iface_desc(alts); + protocol = altsd->bInterfaceProtocol; + /* skip invalid one */ + if (((altsd->bInterfaceClass != USB_CLASS_AUDIO || + (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING && + altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC)) && + altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) || + altsd->bNumEndpoints < 1 || + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0) + continue; + /* must be isochronous */ + if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != + USB_ENDPOINT_XFER_ISOC) + continue; + /* check direction */ + stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + altno = altsd->bAlternateSetting; + + if (snd_usb_apply_interface_quirk(chip, iface_no, altno)) + continue; + + /* + * Roland audio streaming interfaces are marked with protocols + * 0/1/2, but are UAC 1 compatible. + */ + if (USB_ID_VENDOR(chip->usb_id) == 0x0582 && + altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC && + protocol <= 2) + protocol = UAC_VERSION_1; + + switch (protocol) { + default: + dev_dbg(&dev->dev, "%u:%d: unknown interface protocol %#02x, assuming v1\n", + iface_no, altno, protocol); + protocol = UAC_VERSION_1; + /* fall through */ + case UAC_VERSION_1: + /* fall through */ + case UAC_VERSION_2: { + int bm_quirk = 0; + + /* + * Blue Microphones workaround: The last altsetting is + * identical with the previous one, except for a larger + * packet size, but is actually a mislabeled two-channel + * setting; ignore it. + * + * Part 1: prepare quirk flag + */ + if (altno == 2 && num == 3 && + fp && fp->altsetting == 1 && fp->channels == 1 && + fp->formats == SNDRV_PCM_FMTBIT_S16_LE && + protocol == UAC_VERSION_1 && + le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == + fp->maxpacksize * 2) + bm_quirk = 1; + + fp = snd_usb_get_audioformat_uac12(chip, alts, protocol, + iface_no, i, altno, + stream, bm_quirk); + break; + } + case UAC_VERSION_3: + fp = snd_usb_get_audioformat_uac3(chip, alts, &pd, + iface_no, i, altno, stream); + break; + } + + if (!fp) + continue; + else if (IS_ERR(fp)) + return PTR_ERR(fp); + + dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint); + if (protocol == UAC_VERSION_3) + err = snd_usb_add_audio_stream_v3(chip, stream, fp, pd); + else + err = snd_usb_add_audio_stream(chip, stream, fp); + + if (err < 0) { + list_del(&fp->list); /* unlink for avoiding double-free */ + kfree(pd); + kfree(fp->rate_table); + kfree(fp->chmap); + kfree(fp); + return err; + } + /* try to set the interface... */ + usb_set_interface(chip->dev, iface_no, altno); + snd_usb_init_pitch(chip, iface_no, alts, fp); + snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max); + } + return 0; +} + diff --git a/sound/usb/stream.h b/sound/usb/stream.h new file mode 100644 index 000000000..d92e18d58 --- /dev/null +++ b/sound/usb/stream.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USBAUDIO_STREAM_H +#define __USBAUDIO_STREAM_H + +int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, + int iface_no); + +int snd_usb_add_audio_stream(struct snd_usb_audio *chip, + int stream, + struct audioformat *fp); + +#endif /* __USBAUDIO_STREAM_H */ + diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h new file mode 100644 index 000000000..0206fecfd --- /dev/null +++ b/sound/usb/usbaudio.h @@ -0,0 +1,135 @@ +#ifndef __USBAUDIO_H +#define __USBAUDIO_H +/* + * (Tentative) USB Audio Driver for ALSA + * + * Copyright (c) 2002 by Takashi Iwai + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* handling of USB vendor/product ID pairs as 32-bit numbers */ +#define USB_ID(vendor, product) (((unsigned int)(vendor) << 16) | (product)) +#define USB_ID_VENDOR(id) ((id) >> 16) +#define USB_ID_PRODUCT(id) ((u16)(id)) + +/* + * + */ + +struct snd_usb_audio { + int index; + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *pm_intf; + u32 usb_id; + struct mutex mutex; + unsigned int system_suspend; + atomic_t active; + atomic_t shutdown; + atomic_t usage_count; + wait_queue_head_t shutdown_wait; + unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */ + unsigned int tx_length_quirk:1; /* Put length specifier in transfers */ + unsigned int setup_fmt_after_resume_quirk:1; /* setup the format to interface after resume */ + int num_interfaces; + int num_suspended_intf; + int sample_rate_read_error; + + int badd_profile; /* UAC3 BADD profile */ + + struct list_head pcm_list; /* list of pcm streams */ + struct list_head ep_list; /* list of audio-related endpoints */ + int pcm_devs; + + struct list_head midi_list; /* list of midi interfaces */ + + struct list_head mixer_list; /* list of mixer interfaces */ + + int setup; /* from the 'device_setup' module param */ + bool autoclock; /* from the 'autoclock' module param */ + bool keep_iface; /* keep interface/altset after closing + * or parameter change + */ + + struct usb_host_interface *ctrl_intf; /* the audio control interface */ +}; + +#define USB_AUDIO_IFACE_UNUSED ((void *)-1L) + +#define usb_audio_err(chip, fmt, args...) \ + dev_err(&(chip)->dev->dev, fmt, ##args) +#define usb_audio_warn(chip, fmt, args...) \ + dev_warn(&(chip)->dev->dev, fmt, ##args) +#define usb_audio_info(chip, fmt, args...) \ + dev_info(&(chip)->dev->dev, fmt, ##args) +#define usb_audio_dbg(chip, fmt, args...) \ + dev_dbg(&(chip)->dev->dev, fmt, ##args) + +/* + * Information about devices with broken descriptors + */ + +/* special values for .ifnum */ +#define QUIRK_NO_INTERFACE -2 +#define QUIRK_ANY_INTERFACE -1 + +enum quirk_type { + QUIRK_IGNORE_INTERFACE, + QUIRK_COMPOSITE, + QUIRK_AUTODETECT, + QUIRK_MIDI_STANDARD_INTERFACE, + QUIRK_MIDI_FIXED_ENDPOINT, + QUIRK_MIDI_YAMAHA, + QUIRK_MIDI_ROLAND, + QUIRK_MIDI_MIDIMAN, + QUIRK_MIDI_NOVATION, + QUIRK_MIDI_RAW_BYTES, + QUIRK_MIDI_EMAGIC, + QUIRK_MIDI_CME, + QUIRK_MIDI_AKAI, + QUIRK_MIDI_US122L, + QUIRK_MIDI_FTDI, + QUIRK_MIDI_CH345, + QUIRK_AUDIO_STANDARD_INTERFACE, + QUIRK_AUDIO_FIXED_ENDPOINT, + QUIRK_AUDIO_EDIROL_UAXX, + QUIRK_AUDIO_ALIGN_TRANSFER, + QUIRK_AUDIO_STANDARD_MIXER, + QUIRK_SETUP_FMT_AFTER_RESUME, + + QUIRK_TYPE_COUNT +}; + +struct snd_usb_audio_quirk { + const char *vendor_name; + const char *product_name; + const char *profile_name; /* override the card->longname */ + int16_t ifnum; + uint16_t type; + const void *data; +}; + +#define combine_word(s) ((*(s)) | ((unsigned int)(s)[1] << 8)) +#define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16)) +#define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24)) + +int snd_usb_lock_shutdown(struct snd_usb_audio *chip); +void snd_usb_unlock_shutdown(struct snd_usb_audio *chip); + +extern bool snd_usb_use_vmalloc; + +#endif /* __USBAUDIO_H */ diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile new file mode 100644 index 000000000..cc4c2f1ef --- /dev/null +++ b/sound/usb/usx2y/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o +snd-usb-us122l-objs := us122l.o + +obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o +obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c new file mode 100644 index 000000000..8082f7b07 --- /dev/null +++ b/sound/usb/usx2y/us122l.c @@ -0,0 +1,772 @@ +/* + * Copyright (C) 2007, 2008 Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define MODNAME "US122L" +#include "usb_stream.c" +#include "../usbaudio.h" +#include "../midi.h" +#include "us122l.h" + +MODULE_AUTHOR("Karsten Wiese "); +MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.5"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ + /* Enable this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS"."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS"."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS"."); + +/* driver_info flags */ +#define US122L_FLAG_US144 BIT(0) + +static int snd_us122l_card_used[SNDRV_CARDS]; + +static int us122l_create_usbmidi(struct snd_card *card) +{ + static struct snd_usb_midi_endpoint_info quirk_data = { + .out_ep = 4, + .in_ep = 3, + .out_cables = 0x001, + .in_cables = 0x001 + }; + static struct snd_usb_audio_quirk quirk = { + .vendor_name = "US122L", + .product_name = NAME_ALLCAPS, + .ifnum = 1, + .type = QUIRK_MIDI_US122L, + .data = &quirk_data + }; + struct usb_device *dev = US122L(card)->dev; + struct usb_interface *iface = usb_ifnum_to_if(dev, 1); + + return snd_usbmidi_create(card, iface, + &US122L(card)->midi_list, &quirk); +} + +static int us144_create_usbmidi(struct snd_card *card) +{ + static struct snd_usb_midi_endpoint_info quirk_data = { + .out_ep = 4, + .in_ep = 3, + .out_cables = 0x001, + .in_cables = 0x001 + }; + static struct snd_usb_audio_quirk quirk = { + .vendor_name = "US144", + .product_name = NAME_ALLCAPS, + .ifnum = 0, + .type = QUIRK_MIDI_US122L, + .data = &quirk_data + }; + struct usb_device *dev = US122L(card)->dev; + struct usb_interface *iface = usb_ifnum_to_if(dev, 0); + + return snd_usbmidi_create(card, iface, + &US122L(card)->midi_list, &quirk); +} + +/* + * Wrapper for usb_control_msg(). + * Allocates a temp buffer to prevent dmaing from/to the stack. + */ +static int us122l_ctl_msg(struct usb_device *dev, unsigned int pipe, + __u8 request, __u8 requesttype, + __u16 value, __u16 index, void *data, + __u16 size, int timeout) +{ + int err; + void *buf = NULL; + + if (size > 0) { + buf = kmemdup(data, size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + } + err = usb_control_msg(dev, pipe, request, requesttype, + value, index, buf, size, timeout); + if (size > 0) { + memcpy(data, buf, size); + kfree(buf); + } + return err; +} + +static void pt_info_set(struct usb_device *dev, u8 v) +{ + int ret; + + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 'I', + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + v, 0, NULL, 0, 1000); + snd_printdd(KERN_DEBUG "%i\n", ret); +} + +static void usb_stream_hwdep_vm_open(struct vm_area_struct *area) +{ + struct us122l *us122l = area->vm_private_data; + atomic_inc(&us122l->mmap_count); + snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count)); +} + +static vm_fault_t usb_stream_hwdep_vm_fault(struct vm_fault *vmf) +{ + unsigned long offset; + struct page *page; + void *vaddr; + struct us122l *us122l = vmf->vma->vm_private_data; + struct usb_stream *s; + + mutex_lock(&us122l->mutex); + s = us122l->sk.s; + if (!s) + goto unlock; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset < PAGE_ALIGN(s->read_size)) + vaddr = (char *)s + offset; + else { + offset -= PAGE_ALIGN(s->read_size); + if (offset >= PAGE_ALIGN(s->write_size)) + goto unlock; + + vaddr = us122l->sk.write_page + offset; + } + page = virt_to_page(vaddr); + + get_page(page); + mutex_unlock(&us122l->mutex); + + vmf->page = page; + + return 0; +unlock: + mutex_unlock(&us122l->mutex); + return VM_FAULT_SIGBUS; +} + +static void usb_stream_hwdep_vm_close(struct vm_area_struct *area) +{ + struct us122l *us122l = area->vm_private_data; + atomic_dec(&us122l->mmap_count); + snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count)); +} + +static const struct vm_operations_struct usb_stream_hwdep_vm_ops = { + .open = usb_stream_hwdep_vm_open, + .fault = usb_stream_hwdep_vm_fault, + .close = usb_stream_hwdep_vm_close, +}; + + +static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct us122l *us122l = hw->private_data; + struct usb_interface *iface; + snd_printdd(KERN_DEBUG "%p %p\n", hw, file); + if (hw->used >= 2) + return -EBUSY; + + if (!us122l->first) + us122l->first = file; + + if (us122l->is_us144) { + iface = usb_ifnum_to_if(us122l->dev, 0); + usb_autopm_get_interface(iface); + } + iface = usb_ifnum_to_if(us122l->dev, 1); + usb_autopm_get_interface(iface); + return 0; +} + +static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct us122l *us122l = hw->private_data; + struct usb_interface *iface; + snd_printdd(KERN_DEBUG "%p %p\n", hw, file); + + if (us122l->is_us144) { + iface = usb_ifnum_to_if(us122l->dev, 0); + usb_autopm_put_interface(iface); + } + iface = usb_ifnum_to_if(us122l->dev, 1); + usb_autopm_put_interface(iface); + if (us122l->first == file) + us122l->first = NULL; + mutex_lock(&us122l->mutex); + if (us122l->master == file) + us122l->master = us122l->slave; + + us122l->slave = NULL; + mutex_unlock(&us122l->mutex); + return 0; +} + +static int usb_stream_hwdep_mmap(struct snd_hwdep *hw, + struct file *filp, struct vm_area_struct *area) +{ + unsigned long size = area->vm_end - area->vm_start; + struct us122l *us122l = hw->private_data; + unsigned long offset; + struct usb_stream *s; + int err = 0; + bool read; + + offset = area->vm_pgoff << PAGE_SHIFT; + mutex_lock(&us122l->mutex); + s = us122l->sk.s; + read = offset < s->read_size; + if (read && area->vm_flags & VM_WRITE) { + err = -EPERM; + goto out; + } + snd_printdd(KERN_DEBUG "%lu %u\n", size, + read ? s->read_size : s->write_size); + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) { + snd_printk(KERN_WARNING "%lu > %u\n", size, + read ? s->read_size : s->write_size); + err = -EINVAL; + goto out; + } + + area->vm_ops = &usb_stream_hwdep_vm_ops; + area->vm_flags |= VM_DONTDUMP; + if (!read) + area->vm_flags |= VM_DONTEXPAND; + area->vm_private_data = us122l; + atomic_inc(&us122l->mmap_count); +out: + mutex_unlock(&us122l->mutex); + return err; +} + +static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw, + struct file *file, poll_table *wait) +{ + struct us122l *us122l = hw->private_data; + unsigned *polled; + __poll_t mask; + + poll_wait(file, &us122l->sk.sleep, wait); + + mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM | EPOLLERR; + if (mutex_trylock(&us122l->mutex)) { + struct usb_stream *s = us122l->sk.s; + if (s && s->state == usb_stream_ready) { + if (us122l->first == file) + polled = &s->periods_polled; + else + polled = &us122l->second_periods_polled; + if (*polled != s->periods_done) { + *polled = s->periods_done; + mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM; + } else + mask = 0; + } + mutex_unlock(&us122l->mutex); + } + return mask; +} + +static void us122l_stop(struct us122l *us122l) +{ + struct list_head *p; + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_stop(p); + + usb_stream_stop(&us122l->sk); + usb_stream_free(&us122l->sk); +} + +static int us122l_set_sample_rate(struct usb_device *dev, int rate) +{ + unsigned int ep = 0x81; + unsigned char data[3]; + int err; + + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + err = us122l_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, 1000); + if (err < 0) + snd_printk(KERN_ERR "%d: cannot set freq %d to ep 0x%x\n", + dev->devnum, rate, ep); + return err; +} + +static bool us122l_start(struct us122l *us122l, + unsigned rate, unsigned period_frames) +{ + struct list_head *p; + int err; + unsigned use_packsize = 0; + bool success = false; + + if (us122l->dev->speed == USB_SPEED_HIGH) { + /* The us-122l's descriptor defaults to iso max_packsize 78, + which isn't needed for samplerates <= 48000. + Lets save some memory: + */ + switch (rate) { + case 44100: + use_packsize = 36; + break; + case 48000: + use_packsize = 42; + break; + case 88200: + use_packsize = 72; + break; + } + } + if (!usb_stream_new(&us122l->sk, us122l->dev, 1, 2, + rate, use_packsize, period_frames, 6)) + goto out; + + err = us122l_set_sample_rate(us122l->dev, rate); + if (err < 0) { + us122l_stop(us122l); + snd_printk(KERN_ERR "us122l_set_sample_rate error \n"); + goto out; + } + err = usb_stream_start(&us122l->sk); + if (err < 0) { + us122l_stop(us122l); + snd_printk(KERN_ERR "us122l_start error %i \n", err); + goto out; + } + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_start(p); + success = true; +out: + return success; +} + +static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned cmd, unsigned long arg) +{ + struct usb_stream_config cfg; + struct us122l *us122l = hw->private_data; + struct usb_stream *s; + unsigned min_period_frames; + int err = 0; + bool high_speed; + + if (cmd != SNDRV_USB_STREAM_IOCTL_SET_PARAMS) + return -ENOTTY; + + if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg))) + return -EFAULT; + + if (cfg.version != USB_STREAM_INTERFACE_VERSION) + return -ENXIO; + + high_speed = us122l->dev->speed == USB_SPEED_HIGH; + if ((cfg.sample_rate != 44100 && cfg.sample_rate != 48000 && + (!high_speed || + (cfg.sample_rate != 88200 && cfg.sample_rate != 96000))) || + cfg.frame_size != 6 || + cfg.period_frames > 0x3000) + return -EINVAL; + + switch (cfg.sample_rate) { + case 44100: + min_period_frames = 48; + break; + case 48000: + min_period_frames = 52; + break; + default: + min_period_frames = 104; + break; + } + if (!high_speed) + min_period_frames <<= 1; + if (cfg.period_frames < min_period_frames) + return -EINVAL; + + snd_power_wait(hw->card, SNDRV_CTL_POWER_D0); + + mutex_lock(&us122l->mutex); + s = us122l->sk.s; + if (!us122l->master) + us122l->master = file; + else if (us122l->master != file) { + if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg))) { + err = -EIO; + goto unlock; + } + us122l->slave = file; + } + if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg)) || + s->state == usb_stream_xrun) { + us122l_stop(us122l); + if (!us122l_start(us122l, cfg.sample_rate, cfg.period_frames)) + err = -EIO; + else + err = 1; + } +unlock: + mutex_unlock(&us122l->mutex); + wake_up_all(&us122l->sk.sleep); + return err; +} + +#define SND_USB_STREAM_ID "USB STREAM" +static int usb_stream_hwdep_new(struct snd_card *card) +{ + int err; + struct snd_hwdep *hw; + struct usb_device *dev = US122L(card)->dev; + + err = snd_hwdep_new(card, SND_USB_STREAM_ID, 0, &hw); + if (err < 0) + return err; + + hw->iface = SNDRV_HWDEP_IFACE_USB_STREAM; + hw->private_data = US122L(card); + hw->ops.open = usb_stream_hwdep_open; + hw->ops.release = usb_stream_hwdep_release; + hw->ops.ioctl = usb_stream_hwdep_ioctl; + hw->ops.ioctl_compat = usb_stream_hwdep_ioctl; + hw->ops.mmap = usb_stream_hwdep_mmap; + hw->ops.poll = usb_stream_hwdep_poll; + + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", + dev->bus->busnum, dev->devnum); + return 0; +} + + +static bool us122l_create_card(struct snd_card *card) +{ + int err; + struct us122l *us122l = US122L(card); + + if (us122l->is_us144) { + err = usb_set_interface(us122l->dev, 0, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error \n"); + return false; + } + } + err = usb_set_interface(us122l->dev, 1, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error \n"); + return false; + } + + pt_info_set(us122l->dev, 0x11); + pt_info_set(us122l->dev, 0x10); + + if (!us122l_start(us122l, 44100, 256)) + return false; + + if (us122l->is_us144) + err = us144_create_usbmidi(card); + else + err = us122l_create_usbmidi(card); + if (err < 0) { + snd_printk(KERN_ERR "us122l_create_usbmidi error %i \n", err); + goto stop; + } + err = usb_stream_hwdep_new(card); + if (err < 0) { +/* release the midi resources */ + struct list_head *p; + list_for_each(p, &us122l->midi_list) + snd_usbmidi_disconnect(p); + + goto stop; + } + return true; + +stop: + us122l_stop(us122l); + return false; +} + +static void snd_us122l_free(struct snd_card *card) +{ + struct us122l *us122l = US122L(card); + int index = us122l->card_index; + if (index >= 0 && index < SNDRV_CARDS) + snd_us122l_card_used[index] = 0; +} + +static int usx2y_create_card(struct usb_device *device, + struct usb_interface *intf, + struct snd_card **cardp, + unsigned long flags) +{ + int dev; + struct snd_card *card; + int err; + + for (dev = 0; dev < SNDRV_CARDS; ++dev) + if (enable[dev] && !snd_us122l_card_used[dev]) + break; + if (dev >= SNDRV_CARDS) + return -ENODEV; + err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct us122l), &card); + if (err < 0) + return err; + snd_us122l_card_used[US122L(card)->card_index = dev] = 1; + card->private_free = snd_us122l_free; + US122L(card)->dev = device; + mutex_init(&US122L(card)->mutex); + init_waitqueue_head(&US122L(card)->sk.sleep); + US122L(card)->is_us144 = flags & US122L_FLAG_US144; + INIT_LIST_HEAD(&US122L(card)->midi_list); + strcpy(card->driver, "USB "NAME_ALLCAPS""); + sprintf(card->shortname, "TASCAM "NAME_ALLCAPS""); + sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)", + card->shortname, + le16_to_cpu(device->descriptor.idVendor), + le16_to_cpu(device->descriptor.idProduct), + 0, + US122L(card)->dev->bus->busnum, + US122L(card)->dev->devnum + ); + *cardp = card; + return 0; +} + +static int us122l_usb_probe(struct usb_interface *intf, + const struct usb_device_id *device_id, + struct snd_card **cardp) +{ + struct usb_device *device = interface_to_usbdev(intf); + struct snd_card *card; + int err; + + err = usx2y_create_card(device, intf, &card, device_id->driver_info); + if (err < 0) + return err; + + if (!us122l_create_card(card)) { + snd_card_free(card); + return -EINVAL; + } + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + + usb_get_intf(usb_ifnum_to_if(device, 0)); + usb_get_dev(device); + *cardp = card; + return 0; +} + +static int snd_us122l_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *device = interface_to_usbdev(intf); + struct snd_card *card; + int err; + + if (id->driver_info & US122L_FLAG_US144 && + device->speed == USB_SPEED_HIGH) { + snd_printk(KERN_ERR "disable ehci-hcd to run US-144 \n"); + return -ENODEV; + } + + snd_printdd(KERN_DEBUG"%p:%i\n", + intf, intf->cur_altsetting->desc.bInterfaceNumber); + if (intf->cur_altsetting->desc.bInterfaceNumber != 1) + return 0; + + err = us122l_usb_probe(usb_get_intf(intf), id, &card); + if (err < 0) { + usb_put_intf(intf); + return err; + } + + usb_set_intfdata(intf, card); + return 0; +} + +static void snd_us122l_disconnect(struct usb_interface *intf) +{ + struct snd_card *card; + struct us122l *us122l; + struct list_head *p; + + card = usb_get_intfdata(intf); + if (!card) + return; + + snd_card_disconnect(card); + + us122l = US122L(card); + mutex_lock(&us122l->mutex); + us122l_stop(us122l); + mutex_unlock(&us122l->mutex); + +/* release the midi resources */ + list_for_each(p, &us122l->midi_list) { + snd_usbmidi_disconnect(p); + } + + usb_put_intf(usb_ifnum_to_if(us122l->dev, 0)); + usb_put_intf(usb_ifnum_to_if(us122l->dev, 1)); + usb_put_dev(us122l->dev); + + while (atomic_read(&us122l->mmap_count)) + msleep(500); + + snd_card_free(card); +} + +static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct snd_card *card; + struct us122l *us122l; + struct list_head *p; + + card = usb_get_intfdata(intf); + if (!card) + return 0; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + us122l = US122L(card); + if (!us122l) + return 0; + + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_stop(p); + + mutex_lock(&us122l->mutex); + usb_stream_stop(&us122l->sk); + mutex_unlock(&us122l->mutex); + + return 0; +} + +static int snd_us122l_resume(struct usb_interface *intf) +{ + struct snd_card *card; + struct us122l *us122l; + struct list_head *p; + int err; + + card = usb_get_intfdata(intf); + if (!card) + return 0; + + us122l = US122L(card); + if (!us122l) + return 0; + + mutex_lock(&us122l->mutex); + /* needed, doesn't restart without: */ + if (us122l->is_us144) { + err = usb_set_interface(us122l->dev, 0, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error \n"); + goto unlock; + } + } + err = usb_set_interface(us122l->dev, 1, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error \n"); + goto unlock; + } + + pt_info_set(us122l->dev, 0x11); + pt_info_set(us122l->dev, 0x10); + + err = us122l_set_sample_rate(us122l->dev, + us122l->sk.s->cfg.sample_rate); + if (err < 0) { + snd_printk(KERN_ERR "us122l_set_sample_rate error \n"); + goto unlock; + } + err = usb_stream_start(&us122l->sk); + if (err) + goto unlock; + + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_start(p); +unlock: + mutex_unlock(&us122l->mutex); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return err; +} + +static const struct usb_device_id snd_us122l_usb_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US122L + }, + { /* US-144 only works at USB1.1! Disable module ehci-hcd. */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US144, + .driver_info = US122L_FLAG_US144 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US122MKII + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US144MKII, + .driver_info = US122L_FLAG_US144 + }, + { /* terminator */ } +}; + +MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table); +static struct usb_driver snd_us122l_usb_driver = { + .name = "snd-usb-us122l", + .probe = snd_us122l_probe, + .disconnect = snd_us122l_disconnect, + .suspend = snd_us122l_suspend, + .resume = snd_us122l_resume, + .reset_resume = snd_us122l_resume, + .id_table = snd_us122l_usb_id_table, + .supports_autosuspend = 1 +}; + +module_usb_driver(snd_us122l_usb_driver); diff --git a/sound/usb/usx2y/us122l.h b/sound/usb/usx2y/us122l.h new file mode 100644 index 000000000..34bea99d3 --- /dev/null +++ b/sound/usb/usx2y/us122l.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef US122L_H +#define US122L_H + + +struct us122l { + struct usb_device *dev; + int card_index; + int stride; + struct usb_stream_kernel sk; + + struct mutex mutex; + struct file *first; + unsigned second_periods_polled; + struct file *master; + struct file *slave; + struct list_head midi_list; + + atomic_t mmap_count; + + bool is_us144; +}; + + +#define US122L(c) ((struct us122l *)(c)->private_data) + +#define NAME_ALLCAPS "US-122L" + +#define USB_ID_US122L 0x800E +#define USB_ID_US144 0x800F +#define USB_ID_US122MKII 0x8021 +#define USB_ID_US144MKII 0x8020 + +#endif diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c new file mode 100644 index 000000000..36b345970 --- /dev/null +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -0,0 +1,262 @@ +/* + * Driver for Tascam US-X2Y USB soundcards + * + * FPGA Loader + ALSA Startup + * + * Copyright (c) 2003 by Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include "usx2y.h" +#include "usbusx2y.h" +#include "usX2Yhwdep.h" + +static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf) +{ + unsigned long offset; + struct page * page; + void *vaddr; + + snd_printdd("ENTER, start %lXh, pgoff %ld\n", + vmf->vma->vm_start, + vmf->pgoff); + + offset = vmf->pgoff << PAGE_SHIFT; + vaddr = (char *)((struct usX2Ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset; + page = virt_to_page(vaddr); + get_page(page); + vmf->page = page; + + snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n", + vaddr, page); + + return 0; +} + +static const struct vm_operations_struct us428ctls_vm_ops = { + .fault = snd_us428ctls_vm_fault, +}; + +static int snd_us428ctls_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area) +{ + unsigned long size = (unsigned long)(area->vm_end - area->vm_start); + struct usX2Ydev *us428 = hw->private_data; + + // FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs? + // so as long as the device isn't fully initialised yet we return -EBUSY here. + if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT)) + return -EBUSY; + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_ALIGN(sizeof(struct us428ctls_sharedmem))) { + snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(struct us428ctls_sharedmem)); + return -EINVAL; + } + + if (!us428->us428ctls_sharedmem) { + init_waitqueue_head(&us428->us428ctls_wait_queue_head); + if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(struct us428ctls_sharedmem), GFP_KERNEL))) + return -ENOMEM; + memset(us428->us428ctls_sharedmem, -1, sizeof(struct us428ctls_sharedmem)); + us428->us428ctls_sharedmem->CtlSnapShotLast = -2; + } + area->vm_ops = &us428ctls_vm_ops; + area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + area->vm_private_data = hw->private_data; + return 0; +} + +static __poll_t snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait) +{ + __poll_t mask = 0; + struct usX2Ydev *us428 = hw->private_data; + struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem; + if (us428->chip_status & USX2Y_STAT_CHIP_HUP) + return EPOLLHUP; + + poll_wait(file, &us428->us428ctls_wait_queue_head, wait); + + if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed) + mask |= EPOLLIN; + + return mask; +} + + +static int snd_usX2Y_hwdep_dsp_status(struct snd_hwdep *hw, + struct snd_hwdep_dsp_status *info) +{ + static char *type_ids[USX2Y_TYPE_NUMS] = { + [USX2Y_TYPE_122] = "us122", + [USX2Y_TYPE_224] = "us224", + [USX2Y_TYPE_428] = "us428", + }; + struct usX2Ydev *us428 = hw->private_data; + int id = -1; + + switch (le16_to_cpu(us428->dev->descriptor.idProduct)) { + case USB_ID_US122: + id = USX2Y_TYPE_122; + break; + case USB_ID_US224: + id = USX2Y_TYPE_224; + break; + case USB_ID_US428: + id = USX2Y_TYPE_428; + break; + } + if (0 > id) + return -ENODEV; + strcpy(info->id, type_ids[id]); + info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code + if (us428->chip_status & USX2Y_STAT_CHIP_INIT) + info->chip_ready = 1; + info->version = USX2Y_DRIVER_VERSION; + return 0; +} + + +static int usX2Y_create_usbmidi(struct snd_card *card) +{ + static struct snd_usb_midi_endpoint_info quirk_data_1 = { + .out_ep = 0x06, + .in_ep = 0x06, + .out_cables = 0x001, + .in_cables = 0x001 + }; + static struct snd_usb_audio_quirk quirk_1 = { + .vendor_name = "TASCAM", + .product_name = NAME_ALLCAPS, + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &quirk_data_1 + }; + static struct snd_usb_midi_endpoint_info quirk_data_2 = { + .out_ep = 0x06, + .in_ep = 0x06, + .out_cables = 0x003, + .in_cables = 0x003 + }; + static struct snd_usb_audio_quirk quirk_2 = { + .vendor_name = "TASCAM", + .product_name = "US428", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &quirk_data_2 + }; + struct usb_device *dev = usX2Y(card)->dev; + struct usb_interface *iface = usb_ifnum_to_if(dev, 0); + struct snd_usb_audio_quirk *quirk = + le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? + &quirk_2 : &quirk_1; + + snd_printdd("usX2Y_create_usbmidi \n"); + return snd_usbmidi_create(card, iface, &usX2Y(card)->midi_list, quirk); +} + +static int usX2Y_create_alsa_devices(struct snd_card *card) +{ + int err; + + do { + if ((err = usX2Y_create_usbmidi(card)) < 0) { + snd_printk(KERN_ERR "usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err); + break; + } + if ((err = usX2Y_audio_create(card)) < 0) + break; + if ((err = usX2Y_hwdep_pcm_new(card)) < 0) + break; + if ((err = snd_card_register(card)) < 0) + break; + } while (0); + + return err; +} + +static int snd_usX2Y_hwdep_dsp_load(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image *dsp) +{ + struct usX2Ydev *priv = hw->private_data; + struct usb_device* dev = priv->dev; + int lret, err; + char *buf; + + snd_printdd( "dsp_load %s\n", dsp->name); + + buf = memdup_user(dsp->image, dsp->length); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + err = usb_set_interface(dev, 0, 1); + if (err) + snd_printk(KERN_ERR "usb_set_interface error \n"); + else + err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000); + kfree(buf); + if (err) + return err; + if (dsp->index == 1) { + msleep(250); // give the device some time + err = usX2Y_AsyncSeq04_init(priv); + if (err) { + snd_printk(KERN_ERR "usX2Y_AsyncSeq04_init error \n"); + return err; + } + err = usX2Y_In04_init(priv); + if (err) { + snd_printk(KERN_ERR "usX2Y_In04_init error \n"); + return err; + } + err = usX2Y_create_alsa_devices(hw->card); + if (err) { + snd_printk(KERN_ERR "usX2Y_create_alsa_devices error %i \n", err); + snd_card_free(hw->card); + return err; + } + priv->chip_status |= USX2Y_STAT_CHIP_INIT; + snd_printdd("%s: alsa all started\n", hw->name); + } + return err; +} + + +int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device) +{ + int err; + struct snd_hwdep *hw; + + if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0) + return err; + + hw->iface = SNDRV_HWDEP_IFACE_USX2Y; + hw->private_data = usX2Y(card); + hw->ops.dsp_status = snd_usX2Y_hwdep_dsp_status; + hw->ops.dsp_load = snd_usX2Y_hwdep_dsp_load; + hw->ops.mmap = snd_us428ctls_mmap; + hw->ops.poll = snd_us428ctls_poll; + hw->exclusive = 1; + sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); + return 0; +} + diff --git a/sound/usb/usx2y/usX2Yhwdep.h b/sound/usb/usx2y/usX2Yhwdep.h new file mode 100644 index 000000000..457199b5e --- /dev/null +++ b/sound/usb/usx2y/usX2Yhwdep.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USX2YHWDEP_H +#define USX2YHWDEP_H + +int usX2Y_hwdep_new(struct snd_card *card, struct usb_device* device); + +#endif diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c new file mode 100644 index 000000000..b0f8979ff --- /dev/null +++ b/sound/usb/usx2y/usb_stream.c @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2007, 2008 Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include "usb_stream.h" + + +/* setup */ + +static unsigned usb_stream_next_packet_size(struct usb_stream_kernel *sk) +{ + struct usb_stream *s = sk->s; + sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn; + return (sk->out_phase_peeked >> 16) * s->cfg.frame_size; +} + +static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb) +{ + struct usb_stream *s = sk->s; + int pack, lb = 0; + + for (pack = 0; pack < sk->n_o_ps; pack++) { + int l = usb_stream_next_packet_size(sk); + if (s->idle_outsize + lb + l > s->period_size) + goto check; + + sk->out_phase = sk->out_phase_peeked; + urb->iso_frame_desc[pack].offset = lb; + urb->iso_frame_desc[pack].length = l; + lb += l; + } + snd_printdd(KERN_DEBUG "%i\n", lb); + +check: + urb->number_of_packets = pack; + urb->transfer_buffer_length = lb; + s->idle_outsize += lb - s->period_size; + snd_printdd(KERN_DEBUG "idle=%i ul=%i ps=%i\n", s->idle_outsize, + lb, s->period_size); +} + +static int init_pipe_urbs(struct usb_stream_kernel *sk, unsigned use_packsize, + struct urb **urbs, char *transfer, + struct usb_device *dev, int pipe) +{ + int u, p; + int maxpacket = use_packsize ? + use_packsize : usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + int transfer_length = maxpacket * sk->n_o_ps; + + for (u = 0; u < USB_STREAM_NURBS; + ++u, transfer += transfer_length) { + struct urb *urb = urbs[u]; + struct usb_iso_packet_descriptor *desc; + urb->transfer_buffer = transfer; + urb->dev = dev; + urb->pipe = pipe; + urb->number_of_packets = sk->n_o_ps; + urb->context = sk; + urb->interval = 1; + if (usb_pipeout(pipe)) + continue; + if (usb_urb_ep_type_check(urb)) + return -EINVAL; + + urb->transfer_buffer_length = transfer_length; + desc = urb->iso_frame_desc; + desc->offset = 0; + desc->length = maxpacket; + for (p = 1; p < sk->n_o_ps; ++p) { + desc[p].offset = desc[p - 1].offset + maxpacket; + desc[p].length = maxpacket; + } + } + + return 0; +} + +static int init_urbs(struct usb_stream_kernel *sk, unsigned use_packsize, + struct usb_device *dev, int in_pipe, int out_pipe) +{ + struct usb_stream *s = sk->s; + char *indata = (char *)s + sizeof(*s) + + sizeof(struct usb_stream_packet) * + s->inpackets; + int u; + + for (u = 0; u < USB_STREAM_NURBS; ++u) { + sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL); + sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL); + } + + if (init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe) || + init_pipe_urbs(sk, use_packsize, sk->outurb, sk->write_page, dev, + out_pipe)) + return -EINVAL; + + return 0; +} + + +/* + * convert a sampling rate into our full speed format (fs/1000 in Q16.16) + * this will overflow at approx 524 kHz + */ +static inline unsigned get_usb_full_speed_rate(unsigned rate) +{ + return ((rate << 13) + 62) / 125; +} + +/* + * convert a sampling rate into USB high speed format (fs/8000 in Q16.16) + * this will overflow at approx 4 MHz + */ +static inline unsigned get_usb_high_speed_rate(unsigned rate) +{ + return ((rate << 10) + 62) / 125; +} + +void usb_stream_free(struct usb_stream_kernel *sk) +{ + struct usb_stream *s; + unsigned u; + + for (u = 0; u < USB_STREAM_NURBS; ++u) { + usb_free_urb(sk->inurb[u]); + sk->inurb[u] = NULL; + usb_free_urb(sk->outurb[u]); + sk->outurb[u] = NULL; + } + + s = sk->s; + if (!s) + return; + + free_pages((unsigned long)sk->write_page, get_order(s->write_size)); + sk->write_page = NULL; + free_pages((unsigned long)s, get_order(s->read_size)); + sk->s = NULL; +} + +struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, + struct usb_device *dev, + unsigned in_endpoint, unsigned out_endpoint, + unsigned sample_rate, unsigned use_packsize, + unsigned period_frames, unsigned frame_size) +{ + int packets, max_packsize; + int in_pipe, out_pipe; + int read_size = sizeof(struct usb_stream); + int write_size; + int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000; + int pg; + + in_pipe = usb_rcvisocpipe(dev, in_endpoint); + out_pipe = usb_sndisocpipe(dev, out_endpoint); + + max_packsize = use_packsize ? + use_packsize : usb_maxpacket(dev, in_pipe, 0); + + /* + t_period = period_frames / sample_rate + iso_packs = t_period / t_iso_frame + = (period_frames / sample_rate) * (1 / t_iso_frame) + */ + + packets = period_frames * usb_frames / sample_rate + 1; + + if (dev->speed == USB_SPEED_HIGH) + packets = (packets + 7) & ~7; + + read_size += packets * USB_STREAM_URBDEPTH * + (max_packsize + sizeof(struct usb_stream_packet)); + + max_packsize = usb_maxpacket(dev, out_pipe, 1); + write_size = max_packsize * packets * USB_STREAM_URBDEPTH; + + if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) { + snd_printk(KERN_WARNING "a size exceeds 128*PAGE_SIZE\n"); + goto out; + } + + pg = get_order(read_size); + sk->s = (void *) __get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO| + __GFP_NOWARN, pg); + if (!sk->s) { + snd_printk(KERN_WARNING "couldn't __get_free_pages()\n"); + goto out; + } + sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION; + + sk->s->read_size = read_size; + + sk->s->cfg.sample_rate = sample_rate; + sk->s->cfg.frame_size = frame_size; + sk->n_o_ps = packets; + sk->s->inpackets = packets * USB_STREAM_URBDEPTH; + sk->s->cfg.period_frames = period_frames; + sk->s->period_size = frame_size * period_frames; + + sk->s->write_size = write_size; + pg = get_order(write_size); + + sk->write_page = + (void *)__get_free_pages(GFP_KERNEL|__GFP_COMP|__GFP_ZERO| + __GFP_NOWARN, pg); + if (!sk->write_page) { + snd_printk(KERN_WARNING "couldn't __get_free_pages()\n"); + usb_stream_free(sk); + return NULL; + } + + /* calculate the frequency in 16.16 format */ + if (dev->speed == USB_SPEED_FULL) + sk->freqn = get_usb_full_speed_rate(sample_rate); + else + sk->freqn = get_usb_high_speed_rate(sample_rate); + + if (init_urbs(sk, use_packsize, dev, in_pipe, out_pipe) < 0) { + usb_stream_free(sk); + return NULL; + } + + sk->s->state = usb_stream_stopped; +out: + return sk->s; +} + + +/* start */ + +static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb) +{ + bool r; + if (unlikely(urb->status)) { + if (urb->status != -ESHUTDOWN && urb->status != -ENOENT) + snd_printk(KERN_WARNING "status=%i\n", urb->status); + sk->iso_frame_balance = 0x7FFFFFFF; + return false; + } + r = sk->iso_frame_balance == 0; + if (!r) + sk->i_urb = urb; + return r; +} + +static bool balance_playback(struct usb_stream_kernel *sk, struct urb *urb) +{ + sk->iso_frame_balance += urb->number_of_packets; + return balance_check(sk, urb); +} + +static bool balance_capture(struct usb_stream_kernel *sk, struct urb *urb) +{ + sk->iso_frame_balance -= urb->number_of_packets; + return balance_check(sk, urb); +} + +static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *)) +{ + int u; + + for (u = 0; u < USB_STREAM_NURBS; u++) { + struct urb *urb = urbs[u]; + urb->complete = complete; + } +} + +static int usb_stream_prepare_playback(struct usb_stream_kernel *sk, + struct urb *inurb) +{ + struct usb_stream *s = sk->s; + struct urb *io; + struct usb_iso_packet_descriptor *id, *od; + int p = 0, lb = 0, l = 0; + + io = sk->idle_outurb; + od = io->iso_frame_desc; + + for (; s->sync_packet < 0; ++p, ++s->sync_packet) { + struct urb *ii = sk->completed_inurb; + id = ii->iso_frame_desc + + ii->number_of_packets + s->sync_packet; + l = id->actual_length; + + od[p].length = l; + od[p].offset = lb; + lb += l; + } + + for (; + s->sync_packet < inurb->number_of_packets && p < sk->n_o_ps; + ++p, ++s->sync_packet) { + l = inurb->iso_frame_desc[s->sync_packet].actual_length; + + if (s->idle_outsize + lb + l > s->period_size) + goto check_ok; + + od[p].length = l; + od[p].offset = lb; + lb += l; + } + +check_ok: + s->sync_packet -= inurb->number_of_packets; + if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) { + snd_printk(KERN_WARNING "invalid sync_packet = %i;" + " p=%i nop=%i %i %x %x %x > %x\n", + s->sync_packet, p, inurb->number_of_packets, + s->idle_outsize + lb + l, + s->idle_outsize, lb, l, + s->period_size); + return -1; + } + if (unlikely(lb % s->cfg.frame_size)) { + snd_printk(KERN_WARNING"invalid outsize = %i\n", + lb); + return -1; + } + s->idle_outsize += lb - s->period_size; + io->number_of_packets = p; + io->transfer_buffer_length = lb; + if (s->idle_outsize <= 0) + return 0; + + snd_printk(KERN_WARNING "idle=%i\n", s->idle_outsize); + return -1; +} + +static void prepare_inurb(int number_of_packets, struct urb *iu) +{ + struct usb_iso_packet_descriptor *id; + int p; + + iu->number_of_packets = number_of_packets; + id = iu->iso_frame_desc; + id->offset = 0; + for (p = 0; p < iu->number_of_packets - 1; ++p) + id[p + 1].offset = id[p].offset + id[p].length; + + iu->transfer_buffer_length = + id[0].length * iu->number_of_packets; +} + +static int submit_urbs(struct usb_stream_kernel *sk, + struct urb *inurb, struct urb *outurb) +{ + int err; + prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb); + err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC); + if (err < 0) + goto report_failure; + + sk->idle_inurb = sk->completed_inurb; + sk->completed_inurb = inurb; + err = usb_submit_urb(sk->idle_outurb, GFP_ATOMIC); + if (err < 0) + goto report_failure; + + sk->idle_outurb = sk->completed_outurb; + sk->completed_outurb = outurb; + return 0; + +report_failure: + snd_printk(KERN_ERR "%i\n", err); + return err; +} + +#ifdef DEBUG_LOOP_BACK +/* + This loop_back() shows how to read/write the period data. + */ +static void loop_back(struct usb_stream *s) +{ + char *i, *o; + int il, ol, l, p; + struct urb *iu; + struct usb_iso_packet_descriptor *id; + + o = s->playback1st_to; + ol = s->playback1st_size; + l = 0; + + if (s->insplit_pack >= 0) { + iu = sk->idle_inurb; + id = iu->iso_frame_desc; + p = s->insplit_pack; + } else + goto second; +loop: + for (; p < iu->number_of_packets && l < s->period_size; ++p) { + i = iu->transfer_buffer + id[p].offset; + il = id[p].actual_length; + if (l + il > s->period_size) + il = s->period_size - l; + if (il <= ol) { + memcpy(o, i, il); + o += il; + ol -= il; + } else { + memcpy(o, i, ol); + singen_6pack(o, ol); + o = s->playback_to; + memcpy(o, i + ol, il - ol); + o += il - ol; + ol = s->period_size - s->playback1st_size; + } + l += il; + } + if (iu == sk->completed_inurb) { + if (l != s->period_size) + printk(KERN_DEBUG"%s:%i %i\n", __func__, __LINE__, + l/(int)s->cfg.frame_size); + + return; + } +second: + iu = sk->completed_inurb; + id = iu->iso_frame_desc; + p = 0; + goto loop; + +} +#else +static void loop_back(struct usb_stream *s) +{ +} +#endif + +static void stream_idle(struct usb_stream_kernel *sk, + struct urb *inurb, struct urb *outurb) +{ + struct usb_stream *s = sk->s; + int l, p; + int insize = s->idle_insize; + int urb_size = 0; + + s->inpacket_split = s->next_inpacket_split; + s->inpacket_split_at = s->next_inpacket_split_at; + s->next_inpacket_split = -1; + s->next_inpacket_split_at = 0; + + for (p = 0; p < inurb->number_of_packets; ++p) { + struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc; + l = id[p].actual_length; + if (unlikely(l == 0 || id[p].status)) { + snd_printk(KERN_WARNING "underrun, status=%u\n", + id[p].status); + goto err_out; + } + s->inpacket_head++; + s->inpacket_head %= s->inpackets; + if (s->inpacket_split == -1) + s->inpacket_split = s->inpacket_head; + + s->inpacket[s->inpacket_head].offset = + id[p].offset + (inurb->transfer_buffer - (void *)s); + s->inpacket[s->inpacket_head].length = l; + if (insize + l > s->period_size && + s->next_inpacket_split == -1) { + s->next_inpacket_split = s->inpacket_head; + s->next_inpacket_split_at = s->period_size - insize; + } + insize += l; + urb_size += l; + } + s->idle_insize += urb_size - s->period_size; + if (s->idle_insize < 0) { + snd_printk(KERN_WARNING "%i\n", + (s->idle_insize)/(int)s->cfg.frame_size); + goto err_out; + } + s->insize_done += urb_size; + + l = s->idle_outsize; + s->outpacket[0].offset = (sk->idle_outurb->transfer_buffer - + sk->write_page) - l; + + if (usb_stream_prepare_playback(sk, inurb) < 0) + goto err_out; + + s->outpacket[0].length = sk->idle_outurb->transfer_buffer_length + l; + s->outpacket[1].offset = sk->completed_outurb->transfer_buffer - + sk->write_page; + + if (submit_urbs(sk, inurb, outurb) < 0) + goto err_out; + + loop_back(s); + s->periods_done++; + wake_up_all(&sk->sleep); + return; +err_out: + s->state = usb_stream_xrun; + wake_up_all(&sk->sleep); +} + +static void i_capture_idle(struct urb *urb) +{ + struct usb_stream_kernel *sk = urb->context; + if (balance_capture(sk, urb)) + stream_idle(sk, urb, sk->i_urb); +} + +static void i_playback_idle(struct urb *urb) +{ + struct usb_stream_kernel *sk = urb->context; + if (balance_playback(sk, urb)) + stream_idle(sk, sk->i_urb, urb); +} + +static void stream_start(struct usb_stream_kernel *sk, + struct urb *inurb, struct urb *outurb) +{ + struct usb_stream *s = sk->s; + if (s->state >= usb_stream_sync1) { + int l, p, max_diff, max_diff_0; + int urb_size = 0; + unsigned frames_per_packet, min_frames = 0; + frames_per_packet = (s->period_size - s->idle_insize); + frames_per_packet <<= 8; + frames_per_packet /= + s->cfg.frame_size * inurb->number_of_packets; + frames_per_packet++; + + max_diff_0 = s->cfg.frame_size; + if (s->cfg.period_frames >= 256) + max_diff_0 <<= 1; + if (s->cfg.period_frames >= 1024) + max_diff_0 <<= 1; + max_diff = max_diff_0; + for (p = 0; p < inurb->number_of_packets; ++p) { + int diff; + l = inurb->iso_frame_desc[p].actual_length; + urb_size += l; + + min_frames += frames_per_packet; + diff = urb_size - + (min_frames >> 8) * s->cfg.frame_size; + if (diff < max_diff) { + snd_printdd(KERN_DEBUG "%i %i %i %i\n", + s->insize_done, + urb_size / (int)s->cfg.frame_size, + inurb->number_of_packets, diff); + max_diff = diff; + } + } + s->idle_insize -= max_diff - max_diff_0; + s->idle_insize += urb_size - s->period_size; + if (s->idle_insize < 0) { + snd_printk(KERN_WARNING "%i %i %i\n", + s->idle_insize, urb_size, s->period_size); + return; + } else if (s->idle_insize == 0) { + s->next_inpacket_split = + (s->inpacket_head + 1) % s->inpackets; + s->next_inpacket_split_at = 0; + } else { + unsigned split = s->inpacket_head; + l = s->idle_insize; + while (l > s->inpacket[split].length) { + l -= s->inpacket[split].length; + if (split == 0) + split = s->inpackets - 1; + else + split--; + } + s->next_inpacket_split = split; + s->next_inpacket_split_at = + s->inpacket[split].length - l; + } + + s->insize_done += urb_size; + + if (usb_stream_prepare_playback(sk, inurb) < 0) + return; + + } else + playback_prep_freqn(sk, sk->idle_outurb); + + if (submit_urbs(sk, inurb, outurb) < 0) + return; + + if (s->state == usb_stream_sync1 && s->insize_done > 360000) { + /* just guesswork ^^^^^^ */ + s->state = usb_stream_ready; + subs_set_complete(sk->inurb, i_capture_idle); + subs_set_complete(sk->outurb, i_playback_idle); + } +} + +static void i_capture_start(struct urb *urb) +{ + struct usb_iso_packet_descriptor *id = urb->iso_frame_desc; + struct usb_stream_kernel *sk = urb->context; + struct usb_stream *s = sk->s; + int p; + int empty = 0; + + if (urb->status) { + snd_printk(KERN_WARNING "status=%i\n", urb->status); + return; + } + + for (p = 0; p < urb->number_of_packets; ++p) { + int l = id[p].actual_length; + if (l < s->cfg.frame_size) { + ++empty; + if (s->state >= usb_stream_sync0) { + snd_printk(KERN_WARNING "%i\n", l); + return; + } + } + s->inpacket_head++; + s->inpacket_head %= s->inpackets; + s->inpacket[s->inpacket_head].offset = + id[p].offset + (urb->transfer_buffer - (void *)s); + s->inpacket[s->inpacket_head].length = l; + } +#ifdef SHOW_EMPTY + if (empty) { + printk(KERN_DEBUG"%s:%i: %i", __func__, __LINE__, + urb->iso_frame_desc[0].actual_length); + for (pack = 1; pack < urb->number_of_packets; ++pack) { + int l = urb->iso_frame_desc[pack].actual_length; + printk(KERN_CONT " %i", l); + } + printk(KERN_CONT "\n"); + } +#endif + if (!empty && s->state < usb_stream_sync1) + ++s->state; + + if (balance_capture(sk, urb)) + stream_start(sk, urb, sk->i_urb); +} + +static void i_playback_start(struct urb *urb) +{ + struct usb_stream_kernel *sk = urb->context; + if (balance_playback(sk, urb)) + stream_start(sk, sk->i_urb, urb); +} + +int usb_stream_start(struct usb_stream_kernel *sk) +{ + struct usb_stream *s = sk->s; + int frame = 0, iters = 0; + int u, err; + int try = 0; + + if (s->state != usb_stream_stopped) + return -EAGAIN; + + subs_set_complete(sk->inurb, i_capture_start); + subs_set_complete(sk->outurb, i_playback_start); + memset(sk->write_page, 0, s->write_size); +dotry: + s->insize_done = 0; + s->idle_insize = 0; + s->idle_outsize = 0; + s->sync_packet = -1; + s->inpacket_head = -1; + sk->iso_frame_balance = 0; + ++try; + for (u = 0; u < 2; u++) { + struct urb *inurb = sk->inurb[u]; + struct urb *outurb = sk->outurb[u]; + playback_prep_freqn(sk, outurb); + inurb->number_of_packets = outurb->number_of_packets; + inurb->transfer_buffer_length = + inurb->number_of_packets * + inurb->iso_frame_desc[0].length; + + if (u == 0) { + int now; + struct usb_device *dev = inurb->dev; + frame = usb_get_current_frame_number(dev); + do { + now = usb_get_current_frame_number(dev); + ++iters; + } while (now > -1 && now == frame); + } + err = usb_submit_urb(inurb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR"usb_submit_urb(sk->inurb[%i])" + " returned %i\n", u, err); + return err; + } + err = usb_submit_urb(outurb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR"usb_submit_urb(sk->outurb[%i])" + " returned %i\n", u, err); + return err; + } + + if (inurb->start_frame != outurb->start_frame) { + snd_printd(KERN_DEBUG + "u[%i] start_frames differ in:%u out:%u\n", + u, inurb->start_frame, outurb->start_frame); + goto check_retry; + } + } + snd_printdd(KERN_DEBUG "%i %i\n", frame, iters); + try = 0; +check_retry: + if (try) { + usb_stream_stop(sk); + if (try < 5) { + msleep(1500); + snd_printd(KERN_DEBUG "goto dotry;\n"); + goto dotry; + } + snd_printk(KERN_WARNING"couldn't start" + " all urbs on the same start_frame.\n"); + return -EFAULT; + } + + sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2]; + sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2]; + sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1]; + sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1]; + +/* wait, check */ + { + int wait_ms = 3000; + while (s->state != usb_stream_ready && wait_ms > 0) { + snd_printdd(KERN_DEBUG "%i\n", s->state); + msleep(200); + wait_ms -= 200; + } + } + + return s->state == usb_stream_ready ? 0 : -EFAULT; +} + + +/* stop */ + +void usb_stream_stop(struct usb_stream_kernel *sk) +{ + int u; + if (!sk->s) + return; + for (u = 0; u < USB_STREAM_NURBS; ++u) { + usb_kill_urb(sk->inurb[u]); + usb_kill_urb(sk->outurb[u]); + } + sk->s->state = usb_stream_stopped; + msleep(400); +} diff --git a/sound/usb/usx2y/usb_stream.h b/sound/usb/usx2y/usb_stream.h new file mode 100644 index 000000000..851358a8d --- /dev/null +++ b/sound/usb/usx2y/usb_stream.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_STREAM_H +#define __USB_STREAM_H + +#include + +#define USB_STREAM_NURBS 4 +#define USB_STREAM_URBDEPTH 4 + +struct usb_stream_kernel { + struct usb_stream *s; + + void *write_page; + + unsigned n_o_ps; + + struct urb *inurb[USB_STREAM_NURBS]; + struct urb *idle_inurb; + struct urb *completed_inurb; + struct urb *outurb[USB_STREAM_NURBS]; + struct urb *idle_outurb; + struct urb *completed_outurb; + struct urb *i_urb; + + int iso_frame_balance; + + wait_queue_head_t sleep; + + unsigned out_phase; + unsigned out_phase_peeked; + unsigned freqn; +}; + +struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, + struct usb_device *dev, + unsigned in_endpoint, unsigned out_endpoint, + unsigned sample_rate, unsigned use_packsize, + unsigned period_frames, unsigned frame_size); +void usb_stream_free(struct usb_stream_kernel *); +int usb_stream_start(struct usb_stream_kernel *); +void usb_stream_stop(struct usb_stream_kernel *); + +#endif /* __USB_STREAM_H */ diff --git a/sound/usb/usx2y/usbus428ctldefs.h b/sound/usb/usx2y/usbus428ctldefs.h new file mode 100644 index 000000000..b864e7e26 --- /dev/null +++ b/sound/usb/usx2y/usbus428ctldefs.h @@ -0,0 +1,104 @@ +/* + * + * Copyright (c) 2003 by Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +enum E_In84{ + eFader0 = 0, + eFader1, + eFader2, + eFader3, + eFader4, + eFader5, + eFader6, + eFader7, + eFaderM, + eTransport, + eModifier = 10, + eFilterSelect, + eSelect, + eMute, + + eSwitch = 15, + eWheelGain, + eWheelFreq, + eWheelQ, + eWheelPan, + eWheel = 20 +}; + +#define T_RECORD 1 +#define T_PLAY 2 +#define T_STOP 4 +#define T_F_FWD 8 +#define T_REW 0x10 +#define T_SOLO 0x20 +#define T_REC 0x40 +#define T_NULL 0x80 + + +struct us428_ctls { + unsigned char Fader[9]; + unsigned char Transport; + unsigned char Modifier; + unsigned char FilterSelect; + unsigned char Select; + unsigned char Mute; + unsigned char UNKNOWN; + unsigned char Switch; + unsigned char Wheel[5]; +}; + +struct us428_setByte { + unsigned char Offset, + Value; +}; + +enum { + eLT_Volume = 0, + eLT_Light +}; + +struct usX2Y_volume { + unsigned char Channel, + LH, + LL, + RH, + RL; +}; + +struct us428_lights { + struct us428_setByte Light[7]; +}; + +struct us428_p4out { + char type; + union { + struct usX2Y_volume vol; + struct us428_lights lights; + } val; +}; + +#define N_us428_ctl_BUFS 16 +#define N_us428_p4out_BUFS 16 +struct us428ctls_sharedmem{ + struct us428_ctls CtlSnapShot[N_us428_ctl_BUFS]; + int CtlSnapShotDiffersAt[N_us428_ctl_BUFS]; + int CtlSnapShotLast, CtlSnapShotRed; + struct us428_p4out p4out[N_us428_p4out_BUFS]; + int p4outLast, p4outSent; +}; diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c new file mode 100644 index 000000000..da4a5a541 --- /dev/null +++ b/sound/usb/usx2y/usbusx2y.c @@ -0,0 +1,468 @@ +/* + * usbusy2y.c - ALSA USB US-428 Driver + * +2005-04-14 Karsten Wiese + Version 0.8.7.2: + Call snd_card_free() instead of snd_card_free_in_thread() to prevent oops with dead keyboard symptom. + Tested ok with kernel 2.6.12-rc2. + +2004-12-14 Karsten Wiese + Version 0.8.7.1: + snd_pcm_open for rawusb pcm-devices now returns -EBUSY if called without rawusb's hwdep device being open. + +2004-12-02 Karsten Wiese + Version 0.8.7: + Use macro usb_maxpacket() for portability. + +2004-10-26 Karsten Wiese + Version 0.8.6: + wake_up() process waiting in usX2Y_urbs_start() on error. + +2004-10-21 Karsten Wiese + Version 0.8.5: + nrpacks is runtime or compiletime configurable now with tested values from 1 to 4. + +2004-10-03 Karsten Wiese + Version 0.8.2: + Avoid any possible racing while in prepare callback. + +2004-09-30 Karsten Wiese + Version 0.8.0: + Simplified things and made ohci work again. + +2004-09-20 Karsten Wiese + Version 0.7.3: + Use usb_kill_urb() instead of deprecated (kernel 2.6.9) usb_unlink_urb(). + +2004-07-13 Karsten Wiese + Version 0.7.1: + Don't sleep in START/STOP callbacks anymore. + us428 channels C/D not handled just for this version, sorry. + +2004-06-21 Karsten Wiese + Version 0.6.4: + Temporarely suspend midi input + to sanely call usb_set_interface() when setting format. + +2004-06-12 Karsten Wiese + Version 0.6.3: + Made it thus the following rule is enforced: + "All pcm substreams of one usX2Y have to operate at the same rate & format." + +2004-04-06 Karsten Wiese + Version 0.6.0: + Runs on 2.6.5 kernel without any "--with-debug=" things. + us224 reported running. + +2004-01-14 Karsten Wiese + Version 0.5.1: + Runs with 2.6.1 kernel. + +2003-12-30 Karsten Wiese + Version 0.4.1: + Fix 24Bit 4Channel capturing for the us428. + +2003-11-27 Karsten Wiese, Martin Langer + Version 0.4: + us122 support. + us224 could be tested by uncommenting the sections containing USB_ID_US224 + +2003-11-03 Karsten Wiese + Version 0.3: + 24Bit support. + "arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works. + +2003-08-22 Karsten Wiese + Version 0.0.8: + Removed EZUSB Firmware. First Stage Firmwaredownload is now done by tascam-firmware downloader. + See: + http://usb-midi-fw.sourceforge.net/tascam-firmware.tar.gz + +2003-06-18 Karsten Wiese + Version 0.0.5: + changed to compile with kernel 2.4.21 and alsa 0.9.4 + +2002-10-16 Karsten Wiese + Version 0.0.4: + compiles again with alsa-current. + USB_ISO_ASAP not used anymore (most of the time), instead + urb->start_frame is calculated here now, some calls inside usb-driver don't need to happen anymore. + + To get the best out of this: + Disable APM-support in the kernel as APM-BIOS calls (once each second) hard disable interrupt for many precious milliseconds. + This helped me much on my slowish PII 400 & PIII 500. + ACPI yet untested but might cause the same bad behaviour. + Use a kernel with lowlatency and preemptiv patches applied. + To autoload snd-usb-midi append a line + post-install snd-usb-us428 modprobe snd-usb-midi + to /etc/modules.conf. + + known problems: + sliders, knobs, lights not yet handled except MASTER Volume slider. + "pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does. + KDE3: "Enable full duplex operation" deadlocks. + + +2002-08-31 Karsten Wiese + Version 0.0.3: audio also simplex; + simplifying: iso urbs only 1 packet, melted structs. + ASYNC_UNLINK not used anymore: no more crashes so far..... + for alsa 0.9 rc3. + +2002-08-09 Karsten Wiese + Version 0.0.2: midi works with snd-usb-midi, audio (only fullduplex now) with i.e. bristol. + The firmware has been sniffed from win2k us-428 driver 3.09. + + * Copyright (c) 2002 - 2004 Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "usx2y.h" +#include "usbusx2y.h" +#include "usX2Yhwdep.h" + + + +MODULE_AUTHOR("Karsten Wiese "); +MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604),"NAME_ALLCAPS"(0x8001)(0x8005)(0x8007)}}"); + +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 "NAME_ALLCAPS"."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS"."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS"."); + + +static int snd_usX2Y_card_used[SNDRV_CARDS]; + +static void usX2Y_usb_disconnect(struct usb_device* usb_device, void* ptr); +static void snd_usX2Y_card_private_free(struct snd_card *card); + +/* + * pipe 4 is used for switching the lamps, setting samplerate, volumes .... + */ +static void i_usX2Y_Out04Int(struct urb *urb) +{ +#ifdef CONFIG_SND_DEBUG + if (urb->status) { + int i; + struct usX2Ydev *usX2Y = urb->context; + for (i = 0; i < 10 && usX2Y->AS04.urb[i] != urb; i++); + snd_printdd("i_usX2Y_Out04Int() urb %i status=%i\n", i, urb->status); + } +#endif +} + +static void i_usX2Y_In04Int(struct urb *urb) +{ + int err = 0; + struct usX2Ydev *usX2Y = urb->context; + struct us428ctls_sharedmem *us428ctls = usX2Y->us428ctls_sharedmem; + + usX2Y->In04IntCalls++; + + if (urb->status) { + snd_printdd("Interrupt Pipe 4 came back with status=%i\n", urb->status); + return; + } + + // printk("%i:0x%02X ", 8, (int)((unsigned char*)usX2Y->In04Buf)[8]); Master volume shows 0 here if fader is at max during boot ?!? + if (us428ctls) { + int diff = -1; + if (-2 == us428ctls->CtlSnapShotLast) { + diff = 0; + memcpy(usX2Y->In04Last, usX2Y->In04Buf, sizeof(usX2Y->In04Last)); + us428ctls->CtlSnapShotLast = -1; + } else { + int i; + for (i = 0; i < 21; i++) { + if (usX2Y->In04Last[i] != ((char*)usX2Y->In04Buf)[i]) { + if (diff < 0) + diff = i; + usX2Y->In04Last[i] = ((char*)usX2Y->In04Buf)[i]; + } + } + } + if (0 <= diff) { + int n = us428ctls->CtlSnapShotLast + 1; + if (n >= N_us428_ctl_BUFS || n < 0) + n = 0; + memcpy(us428ctls->CtlSnapShot + n, usX2Y->In04Buf, sizeof(us428ctls->CtlSnapShot[0])); + us428ctls->CtlSnapShotDiffersAt[n] = diff; + us428ctls->CtlSnapShotLast = n; + wake_up(&usX2Y->us428ctls_wait_queue_head); + } + } + + + if (usX2Y->US04) { + if (0 == usX2Y->US04->submitted) + do { + err = usb_submit_urb(usX2Y->US04->urb[usX2Y->US04->submitted++], GFP_ATOMIC); + } while (!err && usX2Y->US04->submitted < usX2Y->US04->len); + } else + if (us428ctls && us428ctls->p4outLast >= 0 && us428ctls->p4outLast < N_us428_p4out_BUFS) { + if (us428ctls->p4outLast != us428ctls->p4outSent) { + int j, send = us428ctls->p4outSent + 1; + if (send >= N_us428_p4out_BUFS) + send = 0; + for (j = 0; j < URBS_AsyncSeq && !err; ++j) + if (0 == usX2Y->AS04.urb[j]->status) { + struct us428_p4out *p4out = us428ctls->p4out + send; // FIXME if more than 1 p4out is new, 1 gets lost. + usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->dev, + usb_sndbulkpipe(usX2Y->dev, 0x04), &p4out->val.vol, + p4out->type == eLT_Light ? sizeof(struct us428_lights) : 5, + i_usX2Y_Out04Int, usX2Y); + err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC); + us428ctls->p4outSent = send; + break; + } + } + } + + if (err) + snd_printk(KERN_ERR "In04Int() usb_submit_urb err=%i\n", err); + + urb->dev = usX2Y->dev; + usb_submit_urb(urb, GFP_ATOMIC); +} + +/* + * Prepare some urbs + */ +int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y) +{ + int err = 0, + i; + + usX2Y->AS04.buffer = kmalloc_array(URBS_AsyncSeq, + URB_DataLen_AsyncSeq, GFP_KERNEL); + if (NULL == usX2Y->AS04.buffer) { + err = -ENOMEM; + } else + for (i = 0; i < URBS_AsyncSeq; ++i) { + if (NULL == (usX2Y->AS04.urb[i] = usb_alloc_urb(0, GFP_KERNEL))) { + err = -ENOMEM; + break; + } + usb_fill_bulk_urb( usX2Y->AS04.urb[i], usX2Y->dev, + usb_sndbulkpipe(usX2Y->dev, 0x04), + usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0, + i_usX2Y_Out04Int, usX2Y + ); + err = usb_urb_ep_type_check(usX2Y->AS04.urb[i]); + if (err < 0) + break; + } + return err; +} + +int usX2Y_In04_init(struct usX2Ydev *usX2Y) +{ + if (! (usX2Y->In04urb = usb_alloc_urb(0, GFP_KERNEL))) + return -ENOMEM; + + if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL))) { + usb_free_urb(usX2Y->In04urb); + return -ENOMEM; + } + + init_waitqueue_head(&usX2Y->In04WaitQueue); + usb_fill_int_urb(usX2Y->In04urb, usX2Y->dev, usb_rcvintpipe(usX2Y->dev, 0x4), + usX2Y->In04Buf, 21, + i_usX2Y_In04Int, usX2Y, + 10); + if (usb_urb_ep_type_check(usX2Y->In04urb)) + return -EINVAL; + return usb_submit_urb(usX2Y->In04urb, GFP_KERNEL); +} + +static void usX2Y_unlinkSeq(struct snd_usX2Y_AsyncSeq *S) +{ + int i; + for (i = 0; i < URBS_AsyncSeq; ++i) { + usb_kill_urb(S->urb[i]); + usb_free_urb(S->urb[i]); + S->urb[i] = NULL; + } + kfree(S->buffer); +} + + +static const struct usb_device_id snd_usX2Y_usb_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1604, + .idProduct = USB_ID_US428 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1604, + .idProduct = USB_ID_US122 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1604, + .idProduct = USB_ID_US224 + }, + { /* terminator */ } +}; + +static int usX2Y_create_card(struct usb_device *device, + struct usb_interface *intf, + struct snd_card **cardp) +{ + int dev; + struct snd_card * card; + int err; + + for (dev = 0; dev < SNDRV_CARDS; ++dev) + if (enable[dev] && !snd_usX2Y_card_used[dev]) + break; + if (dev >= SNDRV_CARDS) + return -ENODEV; + err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct usX2Ydev), &card); + if (err < 0) + return err; + snd_usX2Y_card_used[usX2Y(card)->card_index = dev] = 1; + card->private_free = snd_usX2Y_card_private_free; + usX2Y(card)->dev = device; + init_waitqueue_head(&usX2Y(card)->prepare_wait_queue); + mutex_init(&usX2Y(card)->pcm_mutex); + INIT_LIST_HEAD(&usX2Y(card)->midi_list); + strcpy(card->driver, "USB "NAME_ALLCAPS""); + sprintf(card->shortname, "TASCAM "NAME_ALLCAPS""); + sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)", + card->shortname, + le16_to_cpu(device->descriptor.idVendor), + le16_to_cpu(device->descriptor.idProduct), + 0,//us428(card)->usbmidi.ifnum, + usX2Y(card)->dev->bus->busnum, usX2Y(card)->dev->devnum + ); + *cardp = card; + return 0; +} + + +static int usX2Y_usb_probe(struct usb_device *device, + struct usb_interface *intf, + const struct usb_device_id *device_id, + struct snd_card **cardp) +{ + int err; + struct snd_card * card; + + *cardp = NULL; + if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 || + (le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 && + le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 && + le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428)) + return -EINVAL; + + err = usX2Y_create_card(device, intf, &card); + if (err < 0) + return err; + if ((err = usX2Y_hwdep_new(card, device)) < 0 || + (err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + *cardp = card; + return 0; +} + +/* + * new 2.5 USB kernel API + */ +static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct snd_card *card; + int err; + + err = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id, &card); + if (err < 0) + return err; + dev_set_drvdata(&intf->dev, card); + return 0; +} + +static void snd_usX2Y_disconnect(struct usb_interface *intf) +{ + usX2Y_usb_disconnect(interface_to_usbdev(intf), + usb_get_intfdata(intf)); +} + +MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table); +static struct usb_driver snd_usX2Y_usb_driver = { + .name = "snd-usb-usx2y", + .probe = snd_usX2Y_probe, + .disconnect = snd_usX2Y_disconnect, + .id_table = snd_usX2Y_usb_id_table, +}; + +static void snd_usX2Y_card_private_free(struct snd_card *card) +{ + kfree(usX2Y(card)->In04Buf); + usb_free_urb(usX2Y(card)->In04urb); + if (usX2Y(card)->us428ctls_sharedmem) + snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem)); + if (usX2Y(card)->card_index >= 0 && usX2Y(card)->card_index < SNDRV_CARDS) + snd_usX2Y_card_used[usX2Y(card)->card_index] = 0; +} + +/* + * Frees the device. + */ +static void usX2Y_usb_disconnect(struct usb_device *device, void* ptr) +{ + if (ptr) { + struct snd_card *card = ptr; + struct usX2Ydev *usX2Y = usX2Y(card); + struct list_head *p; + usX2Y->chip_status = USX2Y_STAT_CHIP_HUP; + usX2Y_unlinkSeq(&usX2Y->AS04); + usb_kill_urb(usX2Y->In04urb); + snd_card_disconnect(card); + /* release the midi resources */ + list_for_each(p, &usX2Y->midi_list) { + snd_usbmidi_disconnect(p); + } + if (usX2Y->us428ctls_sharedmem) + wake_up(&usX2Y->us428ctls_wait_queue_head); + snd_card_free(card); + } +} + +module_usb_driver(snd_usX2Y_usb_driver); diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h new file mode 100644 index 000000000..e0f77172c --- /dev/null +++ b/sound/usb/usx2y/usbusx2y.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USBUSX2Y_H +#define USBUSX2Y_H +#include "../usbaudio.h" +#include "../midi.h" +#include "usbus428ctldefs.h" + +#define NRURBS 2 + + +#define URBS_AsyncSeq 10 +#define URB_DataLen_AsyncSeq 32 +struct snd_usX2Y_AsyncSeq { + struct urb *urb[URBS_AsyncSeq]; + char *buffer; +}; + +struct snd_usX2Y_urbSeq { + int submitted; + int len; + struct urb *urb[0]; +}; + +#include "usx2yhwdeppcm.h" + +struct usX2Ydev { + struct usb_device *dev; + int card_index; + int stride; + struct urb *In04urb; + void *In04Buf; + char In04Last[24]; + unsigned In04IntCalls; + struct snd_usX2Y_urbSeq *US04; + wait_queue_head_t In04WaitQueue; + struct snd_usX2Y_AsyncSeq AS04; + unsigned int rate, + format; + int chip_status; + struct mutex pcm_mutex; + struct us428ctls_sharedmem *us428ctls_sharedmem; + int wait_iso_frame; + wait_queue_head_t us428ctls_wait_queue_head; + struct snd_usX2Y_hwdep_pcm_shm *hwdep_pcm_shm; + struct snd_usX2Y_substream *subs[4]; + struct snd_usX2Y_substream * volatile prepare_subs; + wait_queue_head_t prepare_wait_queue; + struct list_head midi_list; + struct list_head pcm_list; + int pcm_devs; +}; + + +struct snd_usX2Y_substream { + struct usX2Ydev *usX2Y; + struct snd_pcm_substream *pcm_substream; + + int endpoint; + unsigned int maxpacksize; /* max packet size in bytes */ + + atomic_t state; +#define state_STOPPED 0 +#define state_STARTING1 1 +#define state_STARTING2 2 +#define state_STARTING3 3 +#define state_PREPARED 4 +#define state_PRERUNNING 6 +#define state_RUNNING 8 + + int hwptr; /* free frame position in the buffer (only for playback) */ + int hwptr_done; /* processed frame position in the buffer */ + int transfer_done; /* processed frames since last period update */ + + struct urb *urb[NRURBS]; /* data urb table */ + struct urb *completed_urb; + char *tmpbuf; /* temporary buffer for playback */ +}; + + +#define usX2Y(c) ((struct usX2Ydev *)(c)->private_data) + +int usX2Y_audio_create(struct snd_card *card); + +int usX2Y_AsyncSeq04_init(struct usX2Ydev *usX2Y); +int usX2Y_In04_init(struct usX2Ydev *usX2Y); + +#define NAME_ALLCAPS "US-X2Y" + +#endif diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c new file mode 100644 index 000000000..bdb28e022 --- /dev/null +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -0,0 +1,1020 @@ +/* + * US-X2Y AUDIO + * Copyright (c) 2002-2004 by Karsten Wiese + * + * based on + * + * (Tentative) USB Audio Driver for ALSA + * + * Main and PCM part + * + * Copyright (c) 2002 by Takashi Iwai + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "usx2y.h" +#include "usbusx2y.h" + +#define USX2Y_NRPACKS 4 /* Default value used for nr of packs per urb. + 1 to 4 have been tested ok on uhci. + To use 3 on ohci, you'd need a patch: + look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on + "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425" + . + 1, 2 and 4 work out of the box on ohci, if I recall correctly. + Bigger is safer operation, + smaller gives lower latencies. + */ +#define USX2Y_NRPACKS_VARIABLE y /* If your system works ok with this module's parameter + nrpacks set to 1, you might as well comment + this #define out, and thereby produce smaller, faster code. + You'd also set USX2Y_NRPACKS to 1 then. + */ + +#ifdef USX2Y_NRPACKS_VARIABLE + static int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */ + #define nr_of_packs() nrpacks + module_param(nrpacks, int, 0444); + MODULE_PARM_DESC(nrpacks, "Number of packets per URB."); +#else + #define nr_of_packs() USX2Y_NRPACKS +#endif + + +static int usX2Y_urb_capt_retire(struct snd_usX2Y_substream *subs) +{ + struct urb *urb = subs->completed_urb; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + unsigned char *cp; + int i, len, lens = 0, hwptr_done = subs->hwptr_done; + struct usX2Ydev *usX2Y = subs->usX2Y; + + for (i = 0; i < nr_of_packs(); i++) { + cp = (unsigned char*)urb->transfer_buffer + urb->iso_frame_desc[i].offset; + if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */ + snd_printk(KERN_ERR "active frame status %i. " + "Most probably some hardware problem.\n", + urb->iso_frame_desc[i].status); + return urb->iso_frame_desc[i].status; + } + len = urb->iso_frame_desc[i].actual_length / usX2Y->stride; + if (! len) { + snd_printd("0 == len ERROR!\n"); + continue; + } + + /* copy a data chunk */ + if ((hwptr_done + len) > runtime->buffer_size) { + int cnt = runtime->buffer_size - hwptr_done; + int blen = cnt * usX2Y->stride; + memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, blen); + memcpy(runtime->dma_area, cp + blen, len * usX2Y->stride - blen); + } else { + memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, + len * usX2Y->stride); + } + lens += len; + if ((hwptr_done += len) >= runtime->buffer_size) + hwptr_done -= runtime->buffer_size; + } + + subs->hwptr_done = hwptr_done; + subs->transfer_done += lens; + /* update the pointer, call callback if necessary */ + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + snd_pcm_period_elapsed(subs->pcm_substream); + } + return 0; +} +/* + * prepare urb for playback data pipe + * + * we copy the data directly from the pcm buffer. + * the current position to be copied is held in hwptr field. + * since a urb can handle only a single linear buffer, if the total + * transferred area overflows the buffer boundary, we cannot send + * it directly from the buffer. thus the data is once copied to + * a temporary buffer and urb points to that. + */ +static int usX2Y_urb_play_prepare(struct snd_usX2Y_substream *subs, + struct urb *cap_urb, + struct urb *urb) +{ + int count, counts, pack; + struct usX2Ydev *usX2Y = subs->usX2Y; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + + count = 0; + for (pack = 0; pack < nr_of_packs(); pack++) { + /* calculate the size of a packet */ + counts = cap_urb->iso_frame_desc[pack].actual_length / usX2Y->stride; + count += counts; + if (counts < 43 || counts > 50) { + snd_printk(KERN_ERR "should not be here with counts=%i\n", counts); + return -EPIPE; + } + /* set up descriptor */ + urb->iso_frame_desc[pack].offset = pack ? + urb->iso_frame_desc[pack - 1].offset + + urb->iso_frame_desc[pack - 1].length : + 0; + urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length; + } + if (atomic_read(&subs->state) >= state_PRERUNNING) + if (subs->hwptr + count > runtime->buffer_size) { + /* err, the transferred area goes over buffer boundary. + * copy the data to the temp buffer. + */ + int len; + len = runtime->buffer_size - subs->hwptr; + urb->transfer_buffer = subs->tmpbuf; + memcpy(subs->tmpbuf, runtime->dma_area + + subs->hwptr * usX2Y->stride, len * usX2Y->stride); + memcpy(subs->tmpbuf + len * usX2Y->stride, + runtime->dma_area, (count - len) * usX2Y->stride); + subs->hwptr += count; + subs->hwptr -= runtime->buffer_size; + } else { + /* set the buffer pointer */ + urb->transfer_buffer = runtime->dma_area + subs->hwptr * usX2Y->stride; + if ((subs->hwptr += count) >= runtime->buffer_size) + subs->hwptr -= runtime->buffer_size; + } + else + urb->transfer_buffer = subs->tmpbuf; + urb->transfer_buffer_length = count * usX2Y->stride; + return 0; +} + +/* + * process after playback data complete + * + * update the current position and call callback if a period is processed. + */ +static void usX2Y_urb_play_retire(struct snd_usX2Y_substream *subs, struct urb *urb) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + int len = urb->actual_length / subs->usX2Y->stride; + + subs->transfer_done += len; + subs->hwptr_done += len; + if (subs->hwptr_done >= runtime->buffer_size) + subs->hwptr_done -= runtime->buffer_size; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + snd_pcm_period_elapsed(subs->pcm_substream); + } +} + +static int usX2Y_urb_submit(struct snd_usX2Y_substream *subs, struct urb *urb, int frame) +{ + int err; + if (!urb) + return -ENODEV; + urb->start_frame = (frame + NRURBS * nr_of_packs()); // let hcd do rollover sanity checks + urb->hcpriv = NULL; + urb->dev = subs->usX2Y->dev; /* we need to set this at each time */ + if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + snd_printk(KERN_ERR "usb_submit_urb() returned %i\n", err); + return err; + } + return 0; +} + +static inline int usX2Y_usbframe_complete(struct snd_usX2Y_substream *capsubs, + struct snd_usX2Y_substream *playbacksubs, + int frame) +{ + int err, state; + struct urb *urb = playbacksubs->completed_urb; + + state = atomic_read(&playbacksubs->state); + if (NULL != urb) { + if (state == state_RUNNING) + usX2Y_urb_play_retire(playbacksubs, urb); + else if (state >= state_PRERUNNING) + atomic_inc(&playbacksubs->state); + } else { + switch (state) { + case state_STARTING1: + urb = playbacksubs->urb[0]; + atomic_inc(&playbacksubs->state); + break; + case state_STARTING2: + urb = playbacksubs->urb[1]; + atomic_inc(&playbacksubs->state); + break; + } + } + if (urb) { + if ((err = usX2Y_urb_play_prepare(playbacksubs, capsubs->completed_urb, urb)) || + (err = usX2Y_urb_submit(playbacksubs, urb, frame))) { + return err; + } + } + + playbacksubs->completed_urb = NULL; + + state = atomic_read(&capsubs->state); + if (state >= state_PREPARED) { + if (state == state_RUNNING) { + if ((err = usX2Y_urb_capt_retire(capsubs))) + return err; + } else if (state >= state_PRERUNNING) + atomic_inc(&capsubs->state); + if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame))) + return err; + } + capsubs->completed_urb = NULL; + return 0; +} + + +static void usX2Y_clients_stop(struct usX2Ydev *usX2Y) +{ + int s, u; + + for (s = 0; s < 4; s++) { + struct snd_usX2Y_substream *subs = usX2Y->subs[s]; + if (subs) { + snd_printdd("%i %p state=%i\n", s, subs, atomic_read(&subs->state)); + atomic_set(&subs->state, state_STOPPED); + } + } + for (s = 0; s < 4; s++) { + struct snd_usX2Y_substream *subs = usX2Y->subs[s]; + if (subs) { + if (atomic_read(&subs->state) >= state_PRERUNNING) + snd_pcm_stop_xrun(subs->pcm_substream); + for (u = 0; u < NRURBS; u++) { + struct urb *urb = subs->urb[u]; + if (NULL != urb) + snd_printdd("%i status=%i start_frame=%i\n", + u, urb->status, urb->start_frame); + } + } + } + usX2Y->prepare_subs = NULL; + wake_up(&usX2Y->prepare_wait_queue); +} + +static void usX2Y_error_urb_status(struct usX2Ydev *usX2Y, + struct snd_usX2Y_substream *subs, struct urb *urb) +{ + snd_printk(KERN_ERR "ep=%i stalled with status=%i\n", subs->endpoint, urb->status); + urb->status = 0; + usX2Y_clients_stop(usX2Y); +} + +static void i_usX2Y_urb_complete(struct urb *urb) +{ + struct snd_usX2Y_substream *subs = urb->context; + struct usX2Ydev *usX2Y = subs->usX2Y; + + if (unlikely(atomic_read(&subs->state) < state_PREPARED)) { + snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", + usb_get_current_frame_number(usX2Y->dev), + subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", + urb->status, urb->start_frame); + return; + } + if (unlikely(urb->status)) { + usX2Y_error_urb_status(usX2Y, subs, urb); + return; + } + + subs->completed_urb = urb; + + { + struct snd_usX2Y_substream *capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE], + *playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (capsubs->completed_urb && + atomic_read(&capsubs->state) >= state_PREPARED && + (playbacksubs->completed_urb || + atomic_read(&playbacksubs->state) < state_PREPARED)) { + if (!usX2Y_usbframe_complete(capsubs, playbacksubs, urb->start_frame)) + usX2Y->wait_iso_frame += nr_of_packs(); + else { + snd_printdd("\n"); + usX2Y_clients_stop(usX2Y); + } + } + } +} + +static void usX2Y_urbs_set_complete(struct usX2Ydev * usX2Y, + void (*complete)(struct urb *)) +{ + int s, u; + for (s = 0; s < 4; s++) { + struct snd_usX2Y_substream *subs = usX2Y->subs[s]; + if (NULL != subs) + for (u = 0; u < NRURBS; u++) { + struct urb * urb = subs->urb[u]; + if (NULL != urb) + urb->complete = complete; + } + } +} + +static void usX2Y_subs_startup_finish(struct usX2Ydev * usX2Y) +{ + usX2Y_urbs_set_complete(usX2Y, i_usX2Y_urb_complete); + usX2Y->prepare_subs = NULL; +} + +static void i_usX2Y_subs_startup(struct urb *urb) +{ + struct snd_usX2Y_substream *subs = urb->context; + struct usX2Ydev *usX2Y = subs->usX2Y; + struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs; + if (NULL != prepare_subs) + if (urb->start_frame == prepare_subs->urb[0]->start_frame) { + usX2Y_subs_startup_finish(usX2Y); + atomic_inc(&prepare_subs->state); + wake_up(&usX2Y->prepare_wait_queue); + } + + i_usX2Y_urb_complete(urb); +} + +static void usX2Y_subs_prepare(struct snd_usX2Y_substream *subs) +{ + snd_printdd("usX2Y_substream_prepare(%p) ep=%i urb0=%p urb1=%p\n", + subs, subs->endpoint, subs->urb[0], subs->urb[1]); + /* reset the pointer */ + subs->hwptr = 0; + subs->hwptr_done = 0; + subs->transfer_done = 0; +} + + +static void usX2Y_urb_release(struct urb **urb, int free_tb) +{ + if (*urb) { + usb_kill_urb(*urb); + if (free_tb) + kfree((*urb)->transfer_buffer); + usb_free_urb(*urb); + *urb = NULL; + } +} +/* + * release a substreams urbs + */ +static void usX2Y_urbs_release(struct snd_usX2Y_substream *subs) +{ + int i; + snd_printdd("usX2Y_urbs_release() %i\n", subs->endpoint); + for (i = 0; i < NRURBS; i++) + usX2Y_urb_release(subs->urb + i, + subs != subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]); + + kfree(subs->tmpbuf); + subs->tmpbuf = NULL; +} +/* + * initialize a substream's urbs + */ +static int usX2Y_urbs_allocate(struct snd_usX2Y_substream *subs) +{ + int i; + unsigned int pipe; + int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + struct usb_device *dev = subs->usX2Y->dev; + + pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : + usb_rcvisocpipe(dev, subs->endpoint); + subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback); + if (!subs->maxpacksize) + return -EINVAL; + + if (is_playback && NULL == subs->tmpbuf) { /* allocate a temporary buffer for playback */ + subs->tmpbuf = kcalloc(nr_of_packs(), subs->maxpacksize, GFP_KERNEL); + if (!subs->tmpbuf) + return -ENOMEM; + } + /* allocate and initialize data urbs */ + for (i = 0; i < NRURBS; i++) { + struct urb **purb = subs->urb + i; + if (*purb) { + usb_kill_urb(*purb); + continue; + } + *purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL); + if (NULL == *purb) { + usX2Y_urbs_release(subs); + return -ENOMEM; + } + if (!is_playback && !(*purb)->transfer_buffer) { + /* allocate a capture buffer per urb */ + (*purb)->transfer_buffer = + kmalloc_array(subs->maxpacksize, + nr_of_packs(), GFP_KERNEL); + if (NULL == (*purb)->transfer_buffer) { + usX2Y_urbs_release(subs); + return -ENOMEM; + } + } + (*purb)->dev = dev; + (*purb)->pipe = pipe; + (*purb)->number_of_packets = nr_of_packs(); + (*purb)->context = subs; + (*purb)->interval = 1; + (*purb)->complete = i_usX2Y_subs_startup; + } + return 0; +} + +static void usX2Y_subs_startup(struct snd_usX2Y_substream *subs) +{ + struct usX2Ydev *usX2Y = subs->usX2Y; + usX2Y->prepare_subs = subs; + subs->urb[0]->start_frame = -1; + wmb(); + usX2Y_urbs_set_complete(usX2Y, i_usX2Y_subs_startup); +} + +static int usX2Y_urbs_start(struct snd_usX2Y_substream *subs) +{ + int i, err; + struct usX2Ydev *usX2Y = subs->usX2Y; + + if ((err = usX2Y_urbs_allocate(subs)) < 0) + return err; + subs->completed_urb = NULL; + for (i = 0; i < 4; i++) { + struct snd_usX2Y_substream *subs = usX2Y->subs[i]; + if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED) + goto start; + } + + start: + usX2Y_subs_startup(subs); + for (i = 0; i < NRURBS; i++) { + struct urb *urb = subs->urb[i]; + if (usb_pipein(urb->pipe)) { + unsigned long pack; + if (0 == i) + atomic_set(&subs->state, state_STARTING3); + urb->dev = usX2Y->dev; + for (pack = 0; pack < nr_of_packs(); pack++) { + urb->iso_frame_desc[pack].offset = subs->maxpacksize * pack; + urb->iso_frame_desc[pack].length = subs->maxpacksize; + } + urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); + if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + snd_printk (KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err); + err = -EPIPE; + goto cleanup; + } else + if (i == 0) + usX2Y->wait_iso_frame = urb->start_frame; + urb->transfer_flags = 0; + } else { + atomic_set(&subs->state, state_STARTING1); + break; + } + } + err = 0; + wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs); + if (atomic_read(&subs->state) != state_PREPARED) + err = -EPIPE; + + cleanup: + if (err) { + usX2Y_subs_startup_finish(usX2Y); + usX2Y_clients_stop(usX2Y); // something is completely wroong > stop evrything + } + return err; +} + +/* + * return the current pcm pointer. just return the hwptr_done value. + */ +static snd_pcm_uframes_t snd_usX2Y_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_usX2Y_substream *subs = substream->runtime->private_data; + return subs->hwptr_done; +} +/* + * start/stop substream + */ +static int snd_usX2Y_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_usX2Y_substream *subs = substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_printdd("snd_usX2Y_pcm_trigger(START)\n"); + if (atomic_read(&subs->state) == state_PREPARED && + atomic_read(&subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]->state) >= state_PREPARED) { + atomic_set(&subs->state, state_PRERUNNING); + } else { + snd_printdd("\n"); + return -EPIPE; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_printdd("snd_usX2Y_pcm_trigger(STOP)\n"); + if (atomic_read(&subs->state) >= state_PRERUNNING) + atomic_set(&subs->state, state_PREPARED); + break; + default: + return -EINVAL; + } + return 0; +} + + +/* + * allocate a buffer, setup samplerate + * + * so far we use a physically linear buffer although packetize transfer + * doesn't need a continuous area. + * if sg buffer is supported on the later version of alsa, we'll follow + * that. + */ +static struct s_c2 +{ + char c1, c2; +} + SetRate44100[] = +{ + { 0x14, 0x08}, // this line sets 44100, well actually a little less + { 0x18, 0x40}, // only tascam / frontier design knows the further lines ....... + { 0x18, 0x42}, + { 0x18, 0x45}, + { 0x18, 0x46}, + { 0x18, 0x48}, + { 0x18, 0x4A}, + { 0x18, 0x4C}, + { 0x18, 0x4E}, + { 0x18, 0x50}, + { 0x18, 0x52}, + { 0x18, 0x54}, + { 0x18, 0x56}, + { 0x18, 0x58}, + { 0x18, 0x5A}, + { 0x18, 0x5C}, + { 0x18, 0x5E}, + { 0x18, 0x60}, + { 0x18, 0x62}, + { 0x18, 0x64}, + { 0x18, 0x66}, + { 0x18, 0x68}, + { 0x18, 0x6A}, + { 0x18, 0x6C}, + { 0x18, 0x6E}, + { 0x18, 0x70}, + { 0x18, 0x72}, + { 0x18, 0x74}, + { 0x18, 0x76}, + { 0x18, 0x78}, + { 0x18, 0x7A}, + { 0x18, 0x7C}, + { 0x18, 0x7E} +}; +static struct s_c2 SetRate48000[] = +{ + { 0x14, 0x09}, // this line sets 48000, well actually a little less + { 0x18, 0x40}, // only tascam / frontier design knows the further lines ....... + { 0x18, 0x42}, + { 0x18, 0x45}, + { 0x18, 0x46}, + { 0x18, 0x48}, + { 0x18, 0x4A}, + { 0x18, 0x4C}, + { 0x18, 0x4E}, + { 0x18, 0x50}, + { 0x18, 0x52}, + { 0x18, 0x54}, + { 0x18, 0x56}, + { 0x18, 0x58}, + { 0x18, 0x5A}, + { 0x18, 0x5C}, + { 0x18, 0x5E}, + { 0x18, 0x60}, + { 0x18, 0x62}, + { 0x18, 0x64}, + { 0x18, 0x66}, + { 0x18, 0x68}, + { 0x18, 0x6A}, + { 0x18, 0x6C}, + { 0x18, 0x6E}, + { 0x18, 0x70}, + { 0x18, 0x73}, + { 0x18, 0x74}, + { 0x18, 0x76}, + { 0x18, 0x78}, + { 0x18, 0x7A}, + { 0x18, 0x7C}, + { 0x18, 0x7E} +}; +#define NOOF_SETRATE_URBS ARRAY_SIZE(SetRate48000) + +static void i_usX2Y_04Int(struct urb *urb) +{ + struct usX2Ydev *usX2Y = urb->context; + + if (urb->status) + snd_printk(KERN_ERR "snd_usX2Y_04Int() urb->status=%i\n", urb->status); + if (0 == --usX2Y->US04->len) + wake_up(&usX2Y->In04WaitQueue); +} + +static int usX2Y_rate_set(struct usX2Ydev *usX2Y, int rate) +{ + int err = 0, i; + struct snd_usX2Y_urbSeq *us = NULL; + int *usbdata = NULL; + struct s_c2 *ra = rate == 48000 ? SetRate48000 : SetRate44100; + + if (usX2Y->rate != rate) { + us = kzalloc(sizeof(*us) + sizeof(struct urb*) * NOOF_SETRATE_URBS, GFP_KERNEL); + if (NULL == us) { + err = -ENOMEM; + goto cleanup; + } + usbdata = kmalloc_array(NOOF_SETRATE_URBS, sizeof(int), + GFP_KERNEL); + if (NULL == usbdata) { + err = -ENOMEM; + goto cleanup; + } + for (i = 0; i < NOOF_SETRATE_URBS; ++i) { + if (NULL == (us->urb[i] = usb_alloc_urb(0, GFP_KERNEL))) { + err = -ENOMEM; + goto cleanup; + } + ((char*)(usbdata + i))[0] = ra[i].c1; + ((char*)(usbdata + i))[1] = ra[i].c2; + usb_fill_bulk_urb(us->urb[i], usX2Y->dev, usb_sndbulkpipe(usX2Y->dev, 4), + usbdata + i, 2, i_usX2Y_04Int, usX2Y); + } + err = usb_urb_ep_type_check(us->urb[0]); + if (err < 0) + goto cleanup; + us->submitted = 0; + us->len = NOOF_SETRATE_URBS; + usX2Y->US04 = us; + wait_event_timeout(usX2Y->In04WaitQueue, 0 == us->len, HZ); + usX2Y->US04 = NULL; + if (us->len) + err = -ENODEV; + cleanup: + if (us) { + us->submitted = 2*NOOF_SETRATE_URBS; + for (i = 0; i < NOOF_SETRATE_URBS; ++i) { + struct urb *urb = us->urb[i]; + if (!urb) + continue; + if (urb->status) { + if (!err) + err = -ENODEV; + usb_kill_urb(urb); + } + usb_free_urb(urb); + } + usX2Y->US04 = NULL; + kfree(usbdata); + kfree(us); + if (!err) + usX2Y->rate = rate; + } + } + + return err; +} + + +static int usX2Y_format_set(struct usX2Ydev *usX2Y, snd_pcm_format_t format) +{ + int alternate, err; + struct list_head* p; + if (format == SNDRV_PCM_FORMAT_S24_3LE) { + alternate = 2; + usX2Y->stride = 6; + } else { + alternate = 1; + usX2Y->stride = 4; + } + list_for_each(p, &usX2Y->midi_list) { + snd_usbmidi_input_stop(p); + } + usb_kill_urb(usX2Y->In04urb); + if ((err = usb_set_interface(usX2Y->dev, 0, alternate))) { + snd_printk(KERN_ERR "usb_set_interface error \n"); + return err; + } + usX2Y->In04urb->dev = usX2Y->dev; + err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL); + list_for_each(p, &usX2Y->midi_list) { + snd_usbmidi_input_start(p); + } + usX2Y->format = format; + usX2Y->rate = 0; + return err; +} + + +static int snd_usX2Y_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int err = 0; + unsigned int rate = params_rate(hw_params); + snd_pcm_format_t format = params_format(hw_params); + struct snd_card *card = substream->pstr->pcm->card; + struct usX2Ydev *dev = usX2Y(card); + int i; + + mutex_lock(&usX2Y(card)->pcm_mutex); + snd_printdd("snd_usX2Y_hw_params(%p, %p)\n", substream, hw_params); + /* all pcm substreams off one usX2Y have to operate at the same + * rate & format + */ + for (i = 0; i < dev->pcm_devs * 2; i++) { + struct snd_usX2Y_substream *subs = dev->subs[i]; + struct snd_pcm_substream *test_substream; + + if (!subs) + continue; + test_substream = subs->pcm_substream; + if (!test_substream || test_substream == substream || + !test_substream->runtime) + continue; + if ((test_substream->runtime->format && + test_substream->runtime->format != format) || + (test_substream->runtime->rate && + test_substream->runtime->rate != rate)) { + err = -EINVAL; + goto error; + } + } + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) { + snd_printk(KERN_ERR "snd_pcm_lib_malloc_pages(%p, %i) returned %i\n", + substream, params_buffer_bytes(hw_params), err); + goto error; + } + + error: + mutex_unlock(&usX2Y(card)->pcm_mutex); + return err; +} + +/* + * free the buffer + */ +static int snd_usX2Y_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usX2Y_substream *subs = runtime->private_data; + mutex_lock(&subs->usX2Y->pcm_mutex); + snd_printdd("snd_usX2Y_hw_free(%p)\n", substream); + + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; + atomic_set(&subs->state, state_STOPPED); + usX2Y_urbs_release(subs); + if (!cap_subs->pcm_substream || + !cap_subs->pcm_substream->runtime || + !cap_subs->pcm_substream->runtime->status || + cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) { + atomic_set(&cap_subs->state, state_STOPPED); + usX2Y_urbs_release(cap_subs); + } + } else { + struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (atomic_read(&playback_subs->state) < state_PREPARED) { + atomic_set(&subs->state, state_STOPPED); + usX2Y_urbs_release(subs); + } + } + mutex_unlock(&subs->usX2Y->pcm_mutex); + return snd_pcm_lib_free_pages(substream); +} +/* + * prepare callback + * + * set format and initialize urbs + */ +static int snd_usX2Y_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usX2Y_substream *subs = runtime->private_data; + struct usX2Ydev *usX2Y = subs->usX2Y; + struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; + int err = 0; + snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream); + + mutex_lock(&usX2Y->pcm_mutex); + usX2Y_subs_prepare(subs); +// Start hardware streams +// SyncStream first.... + if (atomic_read(&capsubs->state) < state_PREPARED) { + if (usX2Y->format != runtime->format) + if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0) + goto up_prepare_mutex; + if (usX2Y->rate != runtime->rate) + if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0) + goto up_prepare_mutex; + snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe"); + if (0 > (err = usX2Y_urbs_start(capsubs))) + goto up_prepare_mutex; + } + + if (subs != capsubs && atomic_read(&subs->state) < state_PREPARED) + err = usX2Y_urbs_start(subs); + + up_prepare_mutex: + mutex_unlock(&usX2Y->pcm_mutex); + return err; +} + +static struct snd_pcm_hardware snd_usX2Y_2c = +{ + .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_S24_3LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (2*128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0 +}; + + + +static int snd_usX2Y_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_usX2Y_substream *subs = ((struct snd_usX2Y_substream **) + snd_pcm_substream_chip(substream))[substream->stream]; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS) + return -EBUSY; + + runtime->hw = snd_usX2Y_2c; + runtime->private_data = subs; + subs->pcm_substream = substream; + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); + return 0; +} + + + +static int snd_usX2Y_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usX2Y_substream *subs = runtime->private_data; + + subs->pcm_substream = NULL; + + return 0; +} + + +static const struct snd_pcm_ops snd_usX2Y_pcm_ops = +{ + .open = snd_usX2Y_pcm_open, + .close = snd_usX2Y_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usX2Y_pcm_hw_params, + .hw_free = snd_usX2Y_pcm_hw_free, + .prepare = snd_usX2Y_pcm_prepare, + .trigger = snd_usX2Y_pcm_trigger, + .pointer = snd_usX2Y_pcm_pointer, +}; + + +/* + * free a usb stream instance + */ +static void usX2Y_audio_stream_free(struct snd_usX2Y_substream **usX2Y_substream) +{ + kfree(usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]); + usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK] = NULL; + + kfree(usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]); + usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE] = NULL; +} + +static void snd_usX2Y_pcm_private_free(struct snd_pcm *pcm) +{ + struct snd_usX2Y_substream **usX2Y_stream = pcm->private_data; + if (usX2Y_stream) + usX2Y_audio_stream_free(usX2Y_stream); +} + +static int usX2Y_audio_stream_new(struct snd_card *card, int playback_endpoint, int capture_endpoint) +{ + struct snd_pcm *pcm; + int err, i; + struct snd_usX2Y_substream **usX2Y_substream = + usX2Y(card)->subs + 2 * usX2Y(card)->pcm_devs; + + for (i = playback_endpoint ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; + i <= SNDRV_PCM_STREAM_CAPTURE; ++i) { + usX2Y_substream[i] = kzalloc(sizeof(struct snd_usX2Y_substream), GFP_KERNEL); + if (!usX2Y_substream[i]) + return -ENOMEM; + + usX2Y_substream[i]->usX2Y = usX2Y(card); + } + + if (playback_endpoint) + usX2Y_substream[SNDRV_PCM_STREAM_PLAYBACK]->endpoint = playback_endpoint; + usX2Y_substream[SNDRV_PCM_STREAM_CAPTURE]->endpoint = capture_endpoint; + + err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usX2Y(card)->pcm_devs, + playback_endpoint ? 1 : 0, 1, + &pcm); + if (err < 0) { + usX2Y_audio_stream_free(usX2Y_substream); + return err; + } + + if (playback_endpoint) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_pcm_ops); + + pcm->private_data = usX2Y_substream; + pcm->private_free = snd_usX2Y_pcm_private_free; + pcm->info_flags = 0; + + sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usX2Y(card)->pcm_devs); + + if ((playback_endpoint && + 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64*1024, 128*1024))) || + 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64*1024, 128*1024))) { + snd_usX2Y_pcm_private_free(pcm); + return err; + } + usX2Y(card)->pcm_devs++; + + return 0; +} + +/* + * create a chip instance and set its names. + */ +int usX2Y_audio_create(struct snd_card *card) +{ + int err = 0; + + INIT_LIST_HEAD(&usX2Y(card)->pcm_list); + + if (0 > (err = usX2Y_audio_stream_new(card, 0xA, 0x8))) + return err; + if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) == USB_ID_US428) + if (0 > (err = usX2Y_audio_stream_new(card, 0, 0xA))) + return err; + if (le16_to_cpu(usX2Y(card)->dev->descriptor.idProduct) != USB_ID_US122) + err = usX2Y_rate_set(usX2Y(card), 44100); // Lets us428 recognize output-volume settings, disturbs us122. + return err; +} diff --git a/sound/usb/usx2y/usx2y.h b/sound/usb/usx2y/usx2y.h new file mode 100644 index 000000000..7e59263dd --- /dev/null +++ b/sound/usb/usx2y/usx2y.h @@ -0,0 +1,51 @@ +/* + * Driver for Tascam US-X2Y USB soundcards + * + * Copyright (c) 2003 by Karsten Wiese + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SOUND_USX2Y_COMMON_H +#define __SOUND_USX2Y_COMMON_H + + +#define USX2Y_DRIVER_VERSION 0x0100 /* 0.1.0 */ + + +/* hwdep id string */ +#define SND_USX2Y_LOADER_ID "USX2Y Loader" +#define SND_USX2Y_USBPCM_ID "USX2Y USBPCM" + +/* hardware type */ +enum { + USX2Y_TYPE_122, + USX2Y_TYPE_224, + USX2Y_TYPE_428, + USX2Y_TYPE_NUMS +}; + +#define USB_ID_US122 0x8007 +#define USB_ID_US224 0x8005 +#define USB_ID_US428 0x8001 + +/* chip status */ +enum { + USX2Y_STAT_CHIP_INIT = (1 << 0), /* all operational */ + USX2Y_STAT_CHIP_MMAP_PCM_URBS = (1 << 1), /* pcm transport over mmaped urbs */ + USX2Y_STAT_CHIP_HUP = (1 << 31), /* all operational */ +}; + +#endif /* __SOUND_USX2Y_COMMON_H */ diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c new file mode 100644 index 000000000..4fd9276b8 --- /dev/null +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -0,0 +1,761 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* USX2Y "rawusb" aka hwdep_pcm implementation + + Its usb's unableness to atomically handle power of 2 period sized data chuncs + at standard samplerates, + what led to this part of the usx2y module: + It provides the alsa kernel half of the usx2y-alsa-jack driver pair. + The pair uses a hardware dependent alsa-device for mmaped pcm transport. + Advantage achieved: + The usb_hc moves pcm data from/into memory via DMA. + That memory is mmaped by jack's usx2y driver. + Jack's usx2y driver is the first/last to read/write pcm data. + Read/write is a combination of power of 2 period shaping and + float/int conversation. + Compared to mainline alsa/jack we leave out power of 2 period shaping inside + snd-usb-usx2y which needs memcpy() and additional buffers. + As a side effect possible unwanted pcm-data coruption resulting of + standard alsa's snd-usb-usx2y period shaping scheme falls away. + Result is sane jack operation at buffering schemes down to 128frames, + 2 periods. + plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the + cost of easier triggered i.e. aeolus xruns (128 or 256frames, + 2periods works but is useless cause of crackling). + + This is a first "proof of concept" implementation. + Later, functionalities should migrate to more appropriate places: + Userland: + - The jackd could mmap its float-pcm buffers directly from alsa-lib. + - alsa-lib could provide power of 2 period sized shaping combined with int/float + conversation. + Currently the usx2y jack driver provides above 2 services. + Kernel: + - rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio + devices can use it. + Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y. +*/ + +#include +#include +#include "usbusx2yaudio.c" + +#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1 + +#include + + +static int usX2Y_usbpcm_urb_capt_retire(struct snd_usX2Y_substream *subs) +{ + struct urb *urb = subs->completed_urb; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + int i, lens = 0, hwptr_done = subs->hwptr_done; + struct usX2Ydev *usX2Y = subs->usX2Y; + if (0 > usX2Y->hwdep_pcm_shm->capture_iso_start) { //FIXME + int head = usX2Y->hwdep_pcm_shm->captured_iso_head + 1; + if (head >= ARRAY_SIZE(usX2Y->hwdep_pcm_shm->captured_iso)) + head = 0; + usX2Y->hwdep_pcm_shm->capture_iso_start = head; + snd_printdd("cap start %i\n", head); + } + for (i = 0; i < nr_of_packs(); i++) { + if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */ + snd_printk(KERN_ERR "active frame status %i. Most probably some hardware problem.\n", urb->iso_frame_desc[i].status); + return urb->iso_frame_desc[i].status; + } + lens += urb->iso_frame_desc[i].actual_length / usX2Y->stride; + } + if ((hwptr_done += lens) >= runtime->buffer_size) + hwptr_done -= runtime->buffer_size; + subs->hwptr_done = hwptr_done; + subs->transfer_done += lens; + /* update the pointer, call callback if necessary */ + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + snd_pcm_period_elapsed(subs->pcm_substream); + } + return 0; +} + +static inline int usX2Y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime, + struct usX2Ydev * usX2Y) +{ + return (runtime->buffer_size * 1000) / usX2Y->rate + 1; //FIXME: so far only correct period_size == 2^x ? +} + +/* + * prepare urb for playback data pipe + * + * we copy the data directly from the pcm buffer. + * the current position to be copied is held in hwptr field. + * since a urb can handle only a single linear buffer, if the total + * transferred area overflows the buffer boundary, we cannot send + * it directly from the buffer. thus the data is once copied to + * a temporary buffer and urb points to that. + */ +static int usX2Y_hwdep_urb_play_prepare(struct snd_usX2Y_substream *subs, + struct urb *urb) +{ + int count, counts, pack; + struct usX2Ydev *usX2Y = subs->usX2Y; + struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + + if (0 > shm->playback_iso_start) { + shm->playback_iso_start = shm->captured_iso_head - + usX2Y_iso_frames_per_buffer(runtime, usX2Y); + if (0 > shm->playback_iso_start) + shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso); + shm->playback_iso_head = shm->playback_iso_start; + } + + count = 0; + for (pack = 0; pack < nr_of_packs(); pack++) { + /* calculate the size of a packet */ + counts = shm->captured_iso[shm->playback_iso_head].length / usX2Y->stride; + if (counts < 43 || counts > 50) { + snd_printk(KERN_ERR "should not be here with counts=%i\n", counts); + return -EPIPE; + } + /* set up descriptor */ + urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset; + urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length; + if (atomic_read(&subs->state) != state_RUNNING) + memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0, + urb->iso_frame_desc[pack].length); + if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso)) + shm->playback_iso_head = 0; + count += counts; + } + urb->transfer_buffer_length = count * usX2Y->stride; + return 0; +} + + +static inline void usX2Y_usbpcm_urb_capt_iso_advance(struct snd_usX2Y_substream *subs, + struct urb *urb) +{ + int pack; + for (pack = 0; pack < nr_of_packs(); ++pack) { + struct usb_iso_packet_descriptor *desc = urb->iso_frame_desc + pack; + if (NULL != subs) { + struct snd_usX2Y_hwdep_pcm_shm *shm = subs->usX2Y->hwdep_pcm_shm; + int head = shm->captured_iso_head + 1; + if (head >= ARRAY_SIZE(shm->captured_iso)) + head = 0; + shm->captured_iso[head].frame = urb->start_frame + pack; + shm->captured_iso[head].offset = desc->offset; + shm->captured_iso[head].length = desc->actual_length; + shm->captured_iso_head = head; + shm->captured_iso_frames++; + } + if ((desc->offset += desc->length * NRURBS*nr_of_packs()) + + desc->length >= SSS) + desc->offset -= (SSS - desc->length); + } +} + +static inline int usX2Y_usbpcm_usbframe_complete(struct snd_usX2Y_substream *capsubs, + struct snd_usX2Y_substream *capsubs2, + struct snd_usX2Y_substream *playbacksubs, + int frame) +{ + int err, state; + struct urb *urb = playbacksubs->completed_urb; + + state = atomic_read(&playbacksubs->state); + if (NULL != urb) { + if (state == state_RUNNING) + usX2Y_urb_play_retire(playbacksubs, urb); + else if (state >= state_PRERUNNING) + atomic_inc(&playbacksubs->state); + } else { + switch (state) { + case state_STARTING1: + urb = playbacksubs->urb[0]; + atomic_inc(&playbacksubs->state); + break; + case state_STARTING2: + urb = playbacksubs->urb[1]; + atomic_inc(&playbacksubs->state); + break; + } + } + if (urb) { + if ((err = usX2Y_hwdep_urb_play_prepare(playbacksubs, urb)) || + (err = usX2Y_urb_submit(playbacksubs, urb, frame))) { + return err; + } + } + + playbacksubs->completed_urb = NULL; + + state = atomic_read(&capsubs->state); + if (state >= state_PREPARED) { + if (state == state_RUNNING) { + if ((err = usX2Y_usbpcm_urb_capt_retire(capsubs))) + return err; + } else if (state >= state_PRERUNNING) + atomic_inc(&capsubs->state); + usX2Y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb); + if (NULL != capsubs2) + usX2Y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb); + if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame))) + return err; + if (NULL != capsubs2) + if ((err = usX2Y_urb_submit(capsubs2, capsubs2->completed_urb, frame))) + return err; + } + capsubs->completed_urb = NULL; + if (NULL != capsubs2) + capsubs2->completed_urb = NULL; + return 0; +} + + +static void i_usX2Y_usbpcm_urb_complete(struct urb *urb) +{ + struct snd_usX2Y_substream *subs = urb->context; + struct usX2Ydev *usX2Y = subs->usX2Y; + struct snd_usX2Y_substream *capsubs, *capsubs2, *playbacksubs; + + if (unlikely(atomic_read(&subs->state) < state_PREPARED)) { + snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", + usb_get_current_frame_number(usX2Y->dev), + subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", + urb->status, urb->start_frame); + return; + } + if (unlikely(urb->status)) { + usX2Y_error_urb_status(usX2Y, subs, urb); + return; + } + + subs->completed_urb = urb; + capsubs = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; + capsubs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + playbacksubs = usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (capsubs->completed_urb && atomic_read(&capsubs->state) >= state_PREPARED && + (NULL == capsubs2 || capsubs2->completed_urb) && + (playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < state_PREPARED)) { + if (!usX2Y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame)) + usX2Y->wait_iso_frame += nr_of_packs(); + else { + snd_printdd("\n"); + usX2Y_clients_stop(usX2Y); + } + } +} + + +static void usX2Y_hwdep_urb_release(struct urb **urb) +{ + usb_kill_urb(*urb); + usb_free_urb(*urb); + *urb = NULL; +} + +/* + * release a substream + */ +static void usX2Y_usbpcm_urbs_release(struct snd_usX2Y_substream *subs) +{ + int i; + snd_printdd("snd_usX2Y_urbs_release() %i\n", subs->endpoint); + for (i = 0; i < NRURBS; i++) + usX2Y_hwdep_urb_release(subs->urb + i); +} + +static void usX2Y_usbpcm_subs_startup_finish(struct usX2Ydev * usX2Y) +{ + usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_urb_complete); + usX2Y->prepare_subs = NULL; +} + +static void i_usX2Y_usbpcm_subs_startup(struct urb *urb) +{ + struct snd_usX2Y_substream *subs = urb->context; + struct usX2Ydev *usX2Y = subs->usX2Y; + struct snd_usX2Y_substream *prepare_subs = usX2Y->prepare_subs; + if (NULL != prepare_subs && + urb->start_frame == prepare_subs->urb[0]->start_frame) { + atomic_inc(&prepare_subs->state); + if (prepare_subs == usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]) { + struct snd_usX2Y_substream *cap_subs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + if (cap_subs2 != NULL) + atomic_inc(&cap_subs2->state); + } + usX2Y_usbpcm_subs_startup_finish(usX2Y); + wake_up(&usX2Y->prepare_wait_queue); + } + + i_usX2Y_usbpcm_urb_complete(urb); +} + +/* + * initialize a substream's urbs + */ +static int usX2Y_usbpcm_urbs_allocate(struct snd_usX2Y_substream *subs) +{ + int i; + unsigned int pipe; + int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + struct usb_device *dev = subs->usX2Y->dev; + + pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : + usb_rcvisocpipe(dev, subs->endpoint); + subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback); + if (!subs->maxpacksize) + return -EINVAL; + + /* allocate and initialize data urbs */ + for (i = 0; i < NRURBS; i++) { + struct urb **purb = subs->urb + i; + if (*purb) { + usb_kill_urb(*purb); + continue; + } + *purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL); + if (NULL == *purb) { + usX2Y_usbpcm_urbs_release(subs); + return -ENOMEM; + } + (*purb)->transfer_buffer = is_playback ? + subs->usX2Y->hwdep_pcm_shm->playback : ( + subs->endpoint == 0x8 ? + subs->usX2Y->hwdep_pcm_shm->capture0x8 : + subs->usX2Y->hwdep_pcm_shm->capture0xA); + + (*purb)->dev = dev; + (*purb)->pipe = pipe; + (*purb)->number_of_packets = nr_of_packs(); + (*purb)->context = subs; + (*purb)->interval = 1; + (*purb)->complete = i_usX2Y_usbpcm_subs_startup; + } + return 0; +} + +/* + * free the buffer + */ +static int snd_usX2Y_usbpcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usX2Y_substream *subs = runtime->private_data, + *cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + mutex_lock(&subs->usX2Y->pcm_mutex); + snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream); + + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + struct snd_usX2Y_substream *cap_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; + atomic_set(&subs->state, state_STOPPED); + usX2Y_usbpcm_urbs_release(subs); + if (!cap_subs->pcm_substream || + !cap_subs->pcm_substream->runtime || + !cap_subs->pcm_substream->runtime->status || + cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) { + atomic_set(&cap_subs->state, state_STOPPED); + if (NULL != cap_subs2) + atomic_set(&cap_subs2->state, state_STOPPED); + usX2Y_usbpcm_urbs_release(cap_subs); + if (NULL != cap_subs2) + usX2Y_usbpcm_urbs_release(cap_subs2); + } + } else { + struct snd_usX2Y_substream *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (atomic_read(&playback_subs->state) < state_PREPARED) { + atomic_set(&subs->state, state_STOPPED); + if (NULL != cap_subs2) + atomic_set(&cap_subs2->state, state_STOPPED); + usX2Y_usbpcm_urbs_release(subs); + if (NULL != cap_subs2) + usX2Y_usbpcm_urbs_release(cap_subs2); + } + } + mutex_unlock(&subs->usX2Y->pcm_mutex); + return snd_pcm_lib_free_pages(substream); +} + +static void usX2Y_usbpcm_subs_startup(struct snd_usX2Y_substream *subs) +{ + struct usX2Ydev * usX2Y = subs->usX2Y; + usX2Y->prepare_subs = subs; + subs->urb[0]->start_frame = -1; + smp_wmb(); // Make sure above modifications are seen by i_usX2Y_subs_startup() + usX2Y_urbs_set_complete(usX2Y, i_usX2Y_usbpcm_subs_startup); +} + +static int usX2Y_usbpcm_urbs_start(struct snd_usX2Y_substream *subs) +{ + int p, u, err, + stream = subs->pcm_substream->stream; + struct usX2Ydev *usX2Y = subs->usX2Y; + + if (SNDRV_PCM_STREAM_CAPTURE == stream) { + usX2Y->hwdep_pcm_shm->captured_iso_head = -1; + usX2Y->hwdep_pcm_shm->captured_iso_frames = 0; + } + + for (p = 0; 3 >= (stream + p); p += 2) { + struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p]; + if (subs != NULL) { + if ((err = usX2Y_usbpcm_urbs_allocate(subs)) < 0) + return err; + subs->completed_urb = NULL; + } + } + + for (p = 0; p < 4; p++) { + struct snd_usX2Y_substream *subs = usX2Y->subs[p]; + if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED) + goto start; + } + + start: + usX2Y_usbpcm_subs_startup(subs); + for (u = 0; u < NRURBS; u++) { + for (p = 0; 3 >= (stream + p); p += 2) { + struct snd_usX2Y_substream *subs = usX2Y->subs[stream + p]; + if (subs != NULL) { + struct urb *urb = subs->urb[u]; + if (usb_pipein(urb->pipe)) { + unsigned long pack; + if (0 == u) + atomic_set(&subs->state, state_STARTING3); + urb->dev = usX2Y->dev; + for (pack = 0; pack < nr_of_packs(); pack++) { + urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs()); + urb->iso_frame_desc[pack].length = subs->maxpacksize; + } + urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); + if ((err = usb_submit_urb(urb, GFP_KERNEL)) < 0) { + snd_printk (KERN_ERR "cannot usb_submit_urb() for urb %d, err = %d\n", u, err); + err = -EPIPE; + goto cleanup; + } else { + snd_printdd("%i\n", urb->start_frame); + if (u == 0) + usX2Y->wait_iso_frame = urb->start_frame; + } + urb->transfer_flags = 0; + } else { + atomic_set(&subs->state, state_STARTING1); + break; + } + } + } + } + err = 0; + wait_event(usX2Y->prepare_wait_queue, NULL == usX2Y->prepare_subs); + if (atomic_read(&subs->state) != state_PREPARED) + err = -EPIPE; + + cleanup: + if (err) { + usX2Y_subs_startup_finish(usX2Y); // Call it now + usX2Y_clients_stop(usX2Y); // something is completely wroong > stop evrything + } + return err; +} + +/* + * prepare callback + * + * set format and initialize urbs + */ +static int snd_usX2Y_usbpcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usX2Y_substream *subs = runtime->private_data; + struct usX2Ydev *usX2Y = subs->usX2Y; + struct snd_usX2Y_substream *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE]; + int err = 0; + snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream); + + if (NULL == usX2Y->hwdep_pcm_shm) { + if (NULL == (usX2Y->hwdep_pcm_shm = snd_malloc_pages(sizeof(struct snd_usX2Y_hwdep_pcm_shm), GFP_KERNEL))) + return -ENOMEM; + memset(usX2Y->hwdep_pcm_shm, 0, sizeof(struct snd_usX2Y_hwdep_pcm_shm)); + } + + mutex_lock(&usX2Y->pcm_mutex); + usX2Y_subs_prepare(subs); +// Start hardware streams +// SyncStream first.... + if (atomic_read(&capsubs->state) < state_PREPARED) { + if (usX2Y->format != runtime->format) + if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0) + goto up_prepare_mutex; + if (usX2Y->rate != runtime->rate) + if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0) + goto up_prepare_mutex; + snd_printdd("starting capture pipe for %s\n", subs == capsubs ? + "self" : "playpipe"); + if (0 > (err = usX2Y_usbpcm_urbs_start(capsubs))) + goto up_prepare_mutex; + } + + if (subs != capsubs) { + usX2Y->hwdep_pcm_shm->playback_iso_start = -1; + if (atomic_read(&subs->state) < state_PREPARED) { + while (usX2Y_iso_frames_per_buffer(runtime, usX2Y) > + usX2Y->hwdep_pcm_shm->captured_iso_frames) { + snd_printdd("Wait: iso_frames_per_buffer=%i," + "captured_iso_frames=%i\n", + usX2Y_iso_frames_per_buffer(runtime, usX2Y), + usX2Y->hwdep_pcm_shm->captured_iso_frames); + if (msleep_interruptible(10)) { + err = -ERESTARTSYS; + goto up_prepare_mutex; + } + } + if (0 > (err = usX2Y_usbpcm_urbs_start(subs))) + goto up_prepare_mutex; + } + snd_printdd("Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n", + usX2Y_iso_frames_per_buffer(runtime, usX2Y), + usX2Y->hwdep_pcm_shm->captured_iso_frames); + } else + usX2Y->hwdep_pcm_shm->capture_iso_start = -1; + + up_prepare_mutex: + mutex_unlock(&usX2Y->pcm_mutex); + return err; +} + +static struct snd_pcm_hardware snd_usX2Y_4c = +{ + .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_S24_3LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 4, + .buffer_bytes_max = (2*128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0 +}; + + + +static int snd_usX2Y_usbpcm_open(struct snd_pcm_substream *substream) +{ + struct snd_usX2Y_substream *subs = ((struct snd_usX2Y_substream **) + snd_pcm_substream_chip(substream))[substream->stream]; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (!(subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS)) + return -EBUSY; + + runtime->hw = SNDRV_PCM_STREAM_PLAYBACK == substream->stream ? snd_usX2Y_2c : + (subs->usX2Y->subs[3] ? snd_usX2Y_4c : snd_usX2Y_2c); + runtime->private_data = subs; + subs->pcm_substream = substream; + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); + return 0; +} + + +static int snd_usX2Y_usbpcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usX2Y_substream *subs = runtime->private_data; + + subs->pcm_substream = NULL; + return 0; +} + + +static const struct snd_pcm_ops snd_usX2Y_usbpcm_ops = +{ + .open = snd_usX2Y_usbpcm_open, + .close = snd_usX2Y_usbpcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_usX2Y_pcm_hw_params, + .hw_free = snd_usX2Y_usbpcm_hw_free, + .prepare = snd_usX2Y_usbpcm_prepare, + .trigger = snd_usX2Y_pcm_trigger, + .pointer = snd_usX2Y_pcm_pointer, +}; + + +static int usX2Y_pcms_busy_check(struct snd_card *card) +{ + struct usX2Ydev *dev = usX2Y(card); + int i; + + for (i = 0; i < dev->pcm_devs * 2; i++) { + struct snd_usX2Y_substream *subs = dev->subs[i]; + if (subs && subs->pcm_substream && + SUBSTREAM_BUSY(subs->pcm_substream)) + return -EBUSY; + } + return 0; +} + +static int snd_usX2Y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file) +{ + struct snd_card *card = hw->card; + int err; + + mutex_lock(&usX2Y(card)->pcm_mutex); + err = usX2Y_pcms_busy_check(card); + if (!err) + usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS; + mutex_unlock(&usX2Y(card)->pcm_mutex); + return err; +} + + +static int snd_usX2Y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file) +{ + struct snd_card *card = hw->card; + int err; + + mutex_lock(&usX2Y(card)->pcm_mutex); + err = usX2Y_pcms_busy_check(card); + if (!err) + usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS; + mutex_unlock(&usX2Y(card)->pcm_mutex); + return err; +} + + +static void snd_usX2Y_hwdep_pcm_vm_open(struct vm_area_struct *area) +{ +} + + +static void snd_usX2Y_hwdep_pcm_vm_close(struct vm_area_struct *area) +{ +} + + +static vm_fault_t snd_usX2Y_hwdep_pcm_vm_fault(struct vm_fault *vmf) +{ + unsigned long offset; + void *vaddr; + + offset = vmf->pgoff << PAGE_SHIFT; + vaddr = (char *)((struct usX2Ydev *)vmf->vma->vm_private_data)->hwdep_pcm_shm + offset; + vmf->page = virt_to_page(vaddr); + get_page(vmf->page); + return 0; +} + + +static const struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = { + .open = snd_usX2Y_hwdep_pcm_vm_open, + .close = snd_usX2Y_hwdep_pcm_vm_close, + .fault = snd_usX2Y_hwdep_pcm_vm_fault, +}; + + +static int snd_usX2Y_hwdep_pcm_mmap(struct snd_hwdep * hw, struct file *filp, struct vm_area_struct *area) +{ + unsigned long size = (unsigned long)(area->vm_end - area->vm_start); + struct usX2Ydev *usX2Y = hw->private_data; + + if (!(usX2Y->chip_status & USX2Y_STAT_CHIP_INIT)) + return -EBUSY; + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_ALIGN(sizeof(struct snd_usX2Y_hwdep_pcm_shm))) { + snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(struct snd_usX2Y_hwdep_pcm_shm)); + return -EINVAL; + } + + if (!usX2Y->hwdep_pcm_shm) { + return -ENODEV; + } + area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops; + area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + area->vm_private_data = hw->private_data; + return 0; +} + + +static void snd_usX2Y_hwdep_pcm_private_free(struct snd_hwdep *hwdep) +{ + struct usX2Ydev *usX2Y = hwdep->private_data; + if (NULL != usX2Y->hwdep_pcm_shm) + snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(struct snd_usX2Y_hwdep_pcm_shm)); +} + + +int usX2Y_hwdep_pcm_new(struct snd_card *card) +{ + int err; + struct snd_hwdep *hw; + struct snd_pcm *pcm; + struct usb_device *dev = usX2Y(card)->dev; + if (1 != nr_of_packs()) + return 0; + + if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0) + return err; + + hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM; + hw->private_data = usX2Y(card); + hw->private_free = snd_usX2Y_hwdep_pcm_private_free; + hw->ops.open = snd_usX2Y_hwdep_pcm_open; + hw->ops.release = snd_usX2Y_hwdep_pcm_release; + hw->ops.mmap = snd_usX2Y_hwdep_pcm_mmap; + hw->exclusive = 1; + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); + + err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm); + if (err < 0) { + return err; + } + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usX2Y_usbpcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usX2Y_usbpcm_ops); + + pcm->private_data = usX2Y(card)->subs; + pcm->info_flags = 0; + + sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio"); + if (0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64*1024, 128*1024)) || + 0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64*1024, 128*1024))) { + return err; + } + + + return 0; +} + +#else + +int usX2Y_hwdep_pcm_new(struct snd_card *card) +{ + return 0; +} + +#endif diff --git a/sound/usb/usx2y/usx2yhwdeppcm.h b/sound/usb/usx2y/usx2yhwdeppcm.h new file mode 100644 index 000000000..eb5a46466 --- /dev/null +++ b/sound/usb/usx2y/usx2yhwdeppcm.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define MAXPACK 50 +#define MAXBUFFERMS 100 +#define MAXSTRIDE 3 + +#define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096) +struct snd_usX2Y_hwdep_pcm_shm { + char playback[SSS]; + char capture0x8[SSS]; + char capture0xA[SSS]; + volatile int playback_iso_head; + int playback_iso_start; + struct { + int frame, + offset, + length; + } captured_iso[128]; + volatile int captured_iso_head; + volatile unsigned captured_iso_frames; + int capture_iso_start; +}; + +int usX2Y_hwdep_pcm_new(struct snd_card *card); diff --git a/sound/usb/validate.c b/sound/usb/validate.c new file mode 100644 index 000000000..89a48d731 --- /dev/null +++ b/sound/usb/validate.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Validation of USB-audio class descriptors +// + +#include +#include +#include +#include +#include +#include +#include "usbaudio.h" +#include "helper.h" + +struct usb_desc_validator { + unsigned char protocol; + unsigned char type; + bool (*func)(const void *p, const struct usb_desc_validator *v); + size_t size; +}; + +#define UAC_VERSION_ALL (unsigned char)(-1) + +/* UAC1 only */ +static bool validate_uac1_header(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac1_ac_header_descriptor *d = p; + + return d->bLength >= sizeof(*d) && + d->bLength >= sizeof(*d) + d->bInCollection; +} + +/* for mixer unit; covering all UACs */ +static bool validate_mixer_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_mixer_unit_descriptor *d = p; + size_t len; + + if (d->bLength < sizeof(*d) || !d->bNrInPins) + return false; + len = sizeof(*d) + d->bNrInPins; + /* We can't determine the bitmap size only from this unit descriptor, + * so just check with the remaining length. + * The actual bitmap is checked at mixer unit parser. + */ + switch (v->protocol) { + case UAC_VERSION_1: + default: + len += 2 + 1; /* wChannelConfig, iChannelNames */ + /* bmControls[n*m] */ + len += 1; /* iMixer */ + break; + case UAC_VERSION_2: + len += 4 + 1; /* bmChannelConfig, iChannelNames */ + /* bmMixerControls[n*m] */ + len += 1 + 1; /* bmControls, iMixer */ + break; + case UAC_VERSION_3: + len += 2; /* wClusterDescrID */ + /* bmMixerControls[n*m] */ + break; + } + return d->bLength >= len; +} + +/* both for processing and extension units; covering all UACs */ +static bool validate_processing_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_processing_unit_descriptor *d = p; + const unsigned char *hdr = p; + size_t len, m; + + if (d->bLength < sizeof(*d)) + return false; + len = sizeof(*d) + d->bNrInPins; + if (d->bLength < len) + return false; + switch (v->protocol) { + case UAC_VERSION_1: + default: + /* bNrChannels, wChannelConfig, iChannelNames */ + len += 1 + 2 + 1; + if (d->bLength < len + 1) /* bControlSize */ + return false; + m = hdr[len]; + len += 1 + m + 1; /* bControlSize, bmControls, iProcessing */ + break; + case UAC_VERSION_2: + /* bNrChannels, bmChannelConfig, iChannelNames */ + len += 1 + 4 + 1; + if (v->type == UAC2_PROCESSING_UNIT_V2) + len += 2; /* bmControls -- 2 bytes for PU */ + else + len += 1; /* bmControls -- 1 byte for EU */ + len += 1; /* iProcessing */ + break; + case UAC_VERSION_3: + /* wProcessingDescrStr, bmControls */ + len += 2 + 4; + break; + } + if (d->bLength < len) + return false; + + switch (v->protocol) { + case UAC_VERSION_1: + default: + if (v->type == UAC1_EXTENSION_UNIT) + return true; /* OK */ + switch (le16_to_cpu(d->wProcessType)) { + case UAC_PROCESS_UP_DOWNMIX: + case UAC_PROCESS_DOLBY_PROLOGIC: + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 2; /* bNrModes, waModes(n) */ + break; + default: + break; + } + break; + case UAC_VERSION_2: + if (v->type == UAC2_EXTENSION_UNIT_V2) + return true; /* OK */ + switch (le16_to_cpu(d->wProcessType)) { + case UAC2_PROCESS_UP_DOWNMIX: + case UAC2_PROCESS_DOLBY_PROLOCIC: /* SiC! */ + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 4; /* bNrModes, daModes(n) */ + break; + default: + break; + } + break; + case UAC_VERSION_3: + if (v->type == UAC3_EXTENSION_UNIT) { + len += 2; /* wClusterDescrID */ + break; + } + switch (le16_to_cpu(d->wProcessType)) { + case UAC3_PROCESS_UP_DOWNMIX: + if (d->bLength < len + 1) /* bNrModes */ + return false; + m = hdr[len]; + len += 1 + m * 2; /* bNrModes, waClusterDescrID(n) */ + break; + case UAC3_PROCESS_MULTI_FUNCTION: + len += 2 + 4; /* wClusterDescrID, bmAlgorighms */ + break; + default: + break; + } + break; + } + if (d->bLength < len) + return false; + + return true; +} + +/* both for selector and clock selector units; covering all UACs */ +static bool validate_selector_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_selector_unit_descriptor *d = p; + size_t len; + + if (d->bLength < sizeof(*d)) + return false; + len = sizeof(*d) + d->bNrInPins; + switch (v->protocol) { + case UAC_VERSION_1: + default: + len += 1; /* iSelector */ + break; + case UAC_VERSION_2: + len += 1 + 1; /* bmControls, iSelector */ + break; + case UAC_VERSION_3: + len += 4 + 2; /* bmControls, wSelectorDescrStr */ + break; + } + return d->bLength >= len; +} + +static bool validate_uac1_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d) || !d->bControlSize) + return false; + /* at least bmaControls(0) for master channel + iFeature */ + return d->bLength >= sizeof(*d) + d->bControlSize + 1; +} + +static bool validate_uac2_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac2_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d)) + return false; + /* at least bmaControls(0) for master channel + iFeature */ + return d->bLength >= sizeof(*d) + 4 + 1; +} + +static bool validate_uac3_feature_unit(const void *p, + const struct usb_desc_validator *v) +{ + const struct uac3_feature_unit_descriptor *d = p; + + if (d->bLength < sizeof(*d)) + return false; + /* at least bmaControls(0) for master channel + wFeatureDescrStr */ + return d->bLength >= sizeof(*d) + 4 + 2; +} + +static bool validate_midi_out_jack(const void *p, + const struct usb_desc_validator *v) +{ + const struct usb_midi_out_jack_descriptor *d = p; + + return d->bLength >= sizeof(*d) && + d->bLength >= sizeof(*d) + d->bNrInputPins * 2; +} + +#define FIXED(p, t, s) { .protocol = (p), .type = (t), .size = sizeof(s) } +#define FUNC(p, t, f) { .protocol = (p), .type = (t), .func = (f) } + +static const struct usb_desc_validator audio_validators[] = { + /* UAC1 */ + FUNC(UAC_VERSION_1, UAC_HEADER, validate_uac1_header), + FIXED(UAC_VERSION_1, UAC_INPUT_TERMINAL, + struct uac_input_terminal_descriptor), + FIXED(UAC_VERSION_1, UAC_OUTPUT_TERMINAL, + struct uac1_output_terminal_descriptor), + FUNC(UAC_VERSION_1, UAC_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_1, UAC_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_1, UAC_FEATURE_UNIT, validate_uac1_feature_unit), + FUNC(UAC_VERSION_1, UAC1_PROCESSING_UNIT, validate_processing_unit), + FUNC(UAC_VERSION_1, UAC1_EXTENSION_UNIT, validate_processing_unit), + + /* UAC2 */ + FIXED(UAC_VERSION_2, UAC_HEADER, struct uac2_ac_header_descriptor), + FIXED(UAC_VERSION_2, UAC_INPUT_TERMINAL, + struct uac2_input_terminal_descriptor), + FIXED(UAC_VERSION_2, UAC_OUTPUT_TERMINAL, + struct uac2_output_terminal_descriptor), + FUNC(UAC_VERSION_2, UAC_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_2, UAC_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_2, UAC_FEATURE_UNIT, validate_uac2_feature_unit), + /* UAC_VERSION_2, UAC2_EFFECT_UNIT: not implemented yet */ + FUNC(UAC_VERSION_2, UAC2_PROCESSING_UNIT_V2, validate_processing_unit), + FUNC(UAC_VERSION_2, UAC2_EXTENSION_UNIT_V2, validate_processing_unit), + FIXED(UAC_VERSION_2, UAC2_CLOCK_SOURCE, + struct uac_clock_source_descriptor), + FUNC(UAC_VERSION_2, UAC2_CLOCK_SELECTOR, validate_selector_unit), + FIXED(UAC_VERSION_2, UAC2_CLOCK_MULTIPLIER, + struct uac_clock_multiplier_descriptor), + /* UAC_VERSION_2, UAC2_SAMPLE_RATE_CONVERTER: not implemented yet */ + + /* UAC3 */ + FIXED(UAC_VERSION_2, UAC_HEADER, struct uac3_ac_header_descriptor), + FIXED(UAC_VERSION_3, UAC_INPUT_TERMINAL, + struct uac3_input_terminal_descriptor), + FIXED(UAC_VERSION_3, UAC_OUTPUT_TERMINAL, + struct uac3_output_terminal_descriptor), + /* UAC_VERSION_3, UAC3_EXTENDED_TERMINAL: not implemented yet */ + FUNC(UAC_VERSION_3, UAC3_MIXER_UNIT, validate_mixer_unit), + FUNC(UAC_VERSION_3, UAC3_SELECTOR_UNIT, validate_selector_unit), + FUNC(UAC_VERSION_3, UAC_FEATURE_UNIT, validate_uac3_feature_unit), + /* UAC_VERSION_3, UAC3_EFFECT_UNIT: not implemented yet */ + FUNC(UAC_VERSION_3, UAC3_PROCESSING_UNIT, validate_processing_unit), + FUNC(UAC_VERSION_3, UAC3_EXTENSION_UNIT, validate_processing_unit), + FIXED(UAC_VERSION_3, UAC3_CLOCK_SOURCE, + struct uac3_clock_source_descriptor), + FUNC(UAC_VERSION_3, UAC3_CLOCK_SELECTOR, validate_selector_unit), + FIXED(UAC_VERSION_3, UAC3_CLOCK_MULTIPLIER, + struct uac3_clock_multiplier_descriptor), + /* UAC_VERSION_3, UAC3_SAMPLE_RATE_CONVERTER: not implemented yet */ + /* UAC_VERSION_3, UAC3_CONNECTORS: not implemented yet */ + { } /* terminator */ +}; + +static const struct usb_desc_validator midi_validators[] = { + FIXED(UAC_VERSION_ALL, USB_MS_HEADER, + struct usb_ms_header_descriptor), + FIXED(UAC_VERSION_ALL, USB_MS_MIDI_IN_JACK, + struct usb_midi_in_jack_descriptor), + FUNC(UAC_VERSION_ALL, USB_MS_MIDI_OUT_JACK, + validate_midi_out_jack), + { } /* terminator */ +}; + + +/* Validate the given unit descriptor, return true if it's OK */ +static bool validate_desc(unsigned char *hdr, int protocol, + const struct usb_desc_validator *v) +{ + if (hdr[1] != USB_DT_CS_INTERFACE) + return true; /* don't care */ + + for (; v->type; v++) { + if (v->type == hdr[2] && + (v->protocol == UAC_VERSION_ALL || + v->protocol == protocol)) { + if (v->func) + return v->func(hdr, v); + /* check for the fixed size */ + return hdr[0] >= v->size; + } + } + + return true; /* not matching, skip validation */ +} + +bool snd_usb_validate_audio_desc(void *p, int protocol) +{ + return validate_desc(p, protocol, audio_validators); +} + +bool snd_usb_validate_midi_desc(void *p) +{ + return validate_desc(p, UAC_VERSION_1, midi_validators); +} + diff --git a/sound/x86/Kconfig b/sound/x86/Kconfig new file mode 100644 index 000000000..8adf4d1bd --- /dev/null +++ b/sound/x86/Kconfig @@ -0,0 +1,17 @@ +menuconfig SND_X86 + bool "X86 sound devices" + depends on X86 + default y + ---help--- + X86 sound devices that don't fall under SoC or PCI categories + +if SND_X86 + +config HDMI_LPE_AUDIO + tristate "HDMI audio without HDaudio on Intel Atom platforms" + depends on DRM_I915 + select SND_PCM + help + Choose this option to support HDMI LPE Audio mode + +endif # SND_X86 diff --git a/sound/x86/Makefile b/sound/x86/Makefile new file mode 100644 index 000000000..7ff919808 --- /dev/null +++ b/sound/x86/Makefile @@ -0,0 +1,4 @@ +snd-hdmi-lpe-audio-objs += \ + intel_hdmi_audio.o + +obj-$(CONFIG_HDMI_LPE_AUDIO) += snd-hdmi-lpe-audio.o diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c new file mode 100644 index 000000000..3841336dc --- /dev/null +++ b/sound/x86/intel_hdmi_audio.c @@ -0,0 +1,1955 @@ +/* + * intel_hdmi_audio.c - Intel HDMI audio driver + * + * Copyright (C) 2016 Intel Corp + * Authors: Sailaja Bandarupalli + * Ramesh Babu K V + * Vaibhav Agarwal + * Jerome Anand + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * ALSA driver for Intel HDMI audio + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "intel_hdmi_audio.h" + +#define for_each_pipe(card_ctx, pipe) \ + for ((pipe) = 0; (pipe) < (card_ctx)->num_pipes; (pipe)++) +#define for_each_port(card_ctx, port) \ + for ((port) = 0; (port) < (card_ctx)->num_ports; (port)++) + +/*standard module options for ALSA. This module supports only one card*/ +static int hdmi_card_index = SNDRV_DEFAULT_IDX1; +static char *hdmi_card_id = SNDRV_DEFAULT_STR1; +static bool single_port; + +module_param_named(index, hdmi_card_index, int, 0444); +MODULE_PARM_DESC(index, + "Index value for INTEL Intel HDMI Audio controller."); +module_param_named(id, hdmi_card_id, charp, 0444); +MODULE_PARM_DESC(id, + "ID string for INTEL Intel HDMI Audio controller."); +module_param(single_port, bool, 0444); +MODULE_PARM_DESC(single_port, + "Single-port mode (for compatibility)"); + +/* + * ELD SA bits in the CEA Speaker Allocation data block + */ +static const int eld_speaker_allocation_bits[] = { + [0] = FL | FR, + [1] = LFE, + [2] = FC, + [3] = RL | RR, + [4] = RC, + [5] = FLC | FRC, + [6] = RLC | RRC, + /* the following are not defined in ELD yet */ + [7] = 0, +}; + +/* + * This is an ordered list! + * + * The preceding ones have better chances to be selected by + * hdmi_channel_allocation(). + */ +static struct cea_channel_speaker_allocation channel_allocations[] = { +/* channel: 7 6 5 4 3 2 1 0 */ +{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } }, + /* 2.1 */ +{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } }, + /* Dolby Surround */ +{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } }, + /* surround40 */ +{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } }, + /* surround41 */ +{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } }, + /* surround50 */ +{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } }, + /* surround51 */ +{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } }, + /* 6.1 */ +{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } }, + /* surround71 */ +{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } }, + +{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } }, +{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } }, +{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } }, +{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } }, +{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } }, +{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } }, +{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } }, +{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } }, +{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } }, +{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } }, +{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } }, +{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } }, +{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } }, +{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } }, +}; + +static const struct channel_map_table map_tables[] = { + { SNDRV_CHMAP_FL, 0x00, FL }, + { SNDRV_CHMAP_FR, 0x01, FR }, + { SNDRV_CHMAP_RL, 0x04, RL }, + { SNDRV_CHMAP_RR, 0x05, RR }, + { SNDRV_CHMAP_LFE, 0x02, LFE }, + { SNDRV_CHMAP_FC, 0x03, FC }, + { SNDRV_CHMAP_RLC, 0x06, RLC }, + { SNDRV_CHMAP_RRC, 0x07, RRC }, + {} /* terminator */ +}; + +/* hardware capability structure */ +static const struct snd_pcm_hardware had_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + 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 = HAD_MIN_RATE, + .rate_max = HAD_MAX_RATE, + .channels_min = HAD_MIN_CHANNEL, + .channels_max = HAD_MAX_CHANNEL, + .buffer_bytes_max = HAD_MAX_BUFFER, + .period_bytes_min = HAD_MIN_PERIOD_BYTES, + .period_bytes_max = HAD_MAX_PERIOD_BYTES, + .periods_min = HAD_MIN_PERIODS, + .periods_max = HAD_MAX_PERIODS, + .fifo_size = HAD_FIFO_SIZE, +}; + +/* Get the active PCM substream; + * Call had_substream_put() for unreferecing. + * Don't call this inside had_spinlock, as it takes by itself + */ +static struct snd_pcm_substream * +had_substream_get(struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_substream *substream; + unsigned long flags; + + spin_lock_irqsave(&intelhaddata->had_spinlock, flags); + substream = intelhaddata->stream_info.substream; + if (substream) + intelhaddata->stream_info.substream_refcount++; + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); + return substream; +} + +/* Unref the active PCM substream; + * Don't call this inside had_spinlock, as it takes by itself + */ +static void had_substream_put(struct snd_intelhad *intelhaddata) +{ + unsigned long flags; + + spin_lock_irqsave(&intelhaddata->had_spinlock, flags); + intelhaddata->stream_info.substream_refcount--; + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); +} + +static u32 had_config_offset(int pipe) +{ + switch (pipe) { + default: + case 0: + return AUDIO_HDMI_CONFIG_A; + case 1: + return AUDIO_HDMI_CONFIG_B; + case 2: + return AUDIO_HDMI_CONFIG_C; + } +} + +/* Register access functions */ +static u32 had_read_register_raw(struct snd_intelhad_card *card_ctx, + int pipe, u32 reg) +{ + return ioread32(card_ctx->mmio_start + had_config_offset(pipe) + reg); +} + +static void had_write_register_raw(struct snd_intelhad_card *card_ctx, + int pipe, u32 reg, u32 val) +{ + iowrite32(val, card_ctx->mmio_start + had_config_offset(pipe) + reg); +} + +static void had_read_register(struct snd_intelhad *ctx, u32 reg, u32 *val) +{ + if (!ctx->connected) + *val = 0; + else + *val = had_read_register_raw(ctx->card_ctx, ctx->pipe, reg); +} + +static void had_write_register(struct snd_intelhad *ctx, u32 reg, u32 val) +{ + if (ctx->connected) + had_write_register_raw(ctx->card_ctx, ctx->pipe, reg, val); +} + +/* + * enable / disable audio configuration + * + * The normal read/modify should not directly be used on VLV2 for + * updating AUD_CONFIG register. + * This is because: + * Bit6 of AUD_CONFIG register is writeonly due to a silicon bug on VLV2 + * HDMI IP. As a result a read-modify of AUD_CONFIG regiter will always + * clear bit6. AUD_CONFIG[6:4] represents the "channels" field of the + * register. This field should be 1xy binary for configuration with 6 or + * more channels. Read-modify of AUD_CONFIG (Eg. for enabling audio) + * causes the "channels" field to be updated as 0xy binary resulting in + * bad audio. The fix is to always write the AUD_CONFIG[6:4] with + * appropriate value when doing read-modify of AUD_CONFIG register. + */ +static void had_enable_audio(struct snd_intelhad *intelhaddata, + bool enable) +{ + /* update the cached value */ + intelhaddata->aud_config.regx.aud_en = enable; + had_write_register(intelhaddata, AUD_CONFIG, + intelhaddata->aud_config.regval); +} + +/* forcibly ACKs to both BUFFER_DONE and BUFFER_UNDERRUN interrupts */ +static void had_ack_irqs(struct snd_intelhad *ctx) +{ + u32 status_reg; + + if (!ctx->connected) + return; + had_read_register(ctx, AUD_HDMI_STATUS, &status_reg); + status_reg |= HDMI_AUDIO_BUFFER_DONE | HDMI_AUDIO_UNDERRUN; + had_write_register(ctx, AUD_HDMI_STATUS, status_reg); + had_read_register(ctx, AUD_HDMI_STATUS, &status_reg); +} + +/* Reset buffer pointers */ +static void had_reset_audio(struct snd_intelhad *intelhaddata) +{ + had_write_register(intelhaddata, AUD_HDMI_STATUS, + AUD_HDMI_STATUSG_MASK_FUNCRST); + had_write_register(intelhaddata, AUD_HDMI_STATUS, 0); +} + +/* + * initialize audio channel status registers + * This function is called in the prepare callback + */ +static int had_prog_status_reg(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + union aud_ch_status_0 ch_stat0 = {.regval = 0}; + union aud_ch_status_1 ch_stat1 = {.regval = 0}; + + ch_stat0.regx.lpcm_id = (intelhaddata->aes_bits & + IEC958_AES0_NONAUDIO) >> 1; + ch_stat0.regx.clk_acc = (intelhaddata->aes_bits & + IEC958_AES3_CON_CLOCK) >> 4; + + switch (substream->runtime->rate) { + case AUD_SAMPLE_RATE_32: + ch_stat0.regx.samp_freq = CH_STATUS_MAP_32KHZ; + break; + + case AUD_SAMPLE_RATE_44_1: + ch_stat0.regx.samp_freq = CH_STATUS_MAP_44KHZ; + break; + case AUD_SAMPLE_RATE_48: + ch_stat0.regx.samp_freq = CH_STATUS_MAP_48KHZ; + break; + case AUD_SAMPLE_RATE_88_2: + ch_stat0.regx.samp_freq = CH_STATUS_MAP_88KHZ; + break; + case AUD_SAMPLE_RATE_96: + ch_stat0.regx.samp_freq = CH_STATUS_MAP_96KHZ; + break; + case AUD_SAMPLE_RATE_176_4: + ch_stat0.regx.samp_freq = CH_STATUS_MAP_176KHZ; + break; + case AUD_SAMPLE_RATE_192: + ch_stat0.regx.samp_freq = CH_STATUS_MAP_192KHZ; + break; + + default: + /* control should never come here */ + return -EINVAL; + } + + had_write_register(intelhaddata, + AUD_CH_STATUS_0, ch_stat0.regval); + + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + ch_stat1.regx.max_wrd_len = MAX_SMPL_WIDTH_20; + ch_stat1.regx.wrd_len = SMPL_WIDTH_16BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + ch_stat1.regx.max_wrd_len = MAX_SMPL_WIDTH_24; + ch_stat1.regx.wrd_len = SMPL_WIDTH_24BITS; + break; + default: + return -EINVAL; + } + + had_write_register(intelhaddata, + AUD_CH_STATUS_1, ch_stat1.regval); + return 0; +} + +/* + * function to initialize audio + * registers and buffer confgiuration registers + * This function is called in the prepare callback + */ +static int had_init_audio_ctrl(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + union aud_cfg cfg_val = {.regval = 0}; + union aud_buf_config buf_cfg = {.regval = 0}; + u8 channels; + + had_prog_status_reg(substream, intelhaddata); + + buf_cfg.regx.audio_fifo_watermark = FIFO_THRESHOLD; + buf_cfg.regx.dma_fifo_watermark = DMA_FIFO_THRESHOLD; + buf_cfg.regx.aud_delay = 0; + had_write_register(intelhaddata, AUD_BUF_CONFIG, buf_cfg.regval); + + channels = substream->runtime->channels; + cfg_val.regx.num_ch = channels - 2; + if (channels <= 2) + cfg_val.regx.layout = LAYOUT0; + else + cfg_val.regx.layout = LAYOUT1; + + if (substream->runtime->format == SNDRV_PCM_FORMAT_S16_LE) + cfg_val.regx.packet_mode = 1; + + if (substream->runtime->format == SNDRV_PCM_FORMAT_S32_LE) + cfg_val.regx.left_align = 1; + + cfg_val.regx.val_bit = 1; + + /* fix up the DP bits */ + if (intelhaddata->dp_output) { + cfg_val.regx.dp_modei = 1; + cfg_val.regx.set = 1; + } + + had_write_register(intelhaddata, AUD_CONFIG, cfg_val.regval); + intelhaddata->aud_config = cfg_val; + return 0; +} + +/* + * Compute derived values in channel_allocations[]. + */ +static void init_channel_allocations(void) +{ + int i, j; + struct cea_channel_speaker_allocation *p; + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + p = channel_allocations + i; + p->channels = 0; + p->spk_mask = 0; + for (j = 0; j < ARRAY_SIZE(p->speakers); j++) + if (p->speakers[j]) { + p->channels++; + p->spk_mask |= p->speakers[j]; + } + } +} + +/* + * The transformation takes two steps: + * + * eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask + * spk_mask => (channel_allocations[]) => ai->CA + * + * TODO: it could select the wrong CA from multiple candidates. + */ +static int had_channel_allocation(struct snd_intelhad *intelhaddata, + int channels) +{ + int i; + int ca = 0; + int spk_mask = 0; + + /* + * CA defaults to 0 for basic stereo audio + */ + if (channels <= 2) + return 0; + + /* + * expand ELD's speaker allocation mask + * + * ELD tells the speaker mask in a compact(paired) form, + * expand ELD's notions to match the ones used by Audio InfoFrame. + */ + + for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { + if (intelhaddata->eld[DRM_ELD_SPEAKER] & (1 << i)) + spk_mask |= eld_speaker_allocation_bits[i]; + } + + /* search for the first working match in the CA table */ + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + if (channels == channel_allocations[i].channels && + (spk_mask & channel_allocations[i].spk_mask) == + channel_allocations[i].spk_mask) { + ca = channel_allocations[i].ca_index; + break; + } + } + + dev_dbg(intelhaddata->dev, "select CA 0x%x for %d\n", ca, channels); + + return ca; +} + +/* from speaker bit mask to ALSA API channel position */ +static int spk_to_chmap(int spk) +{ + const struct channel_map_table *t = map_tables; + + for (; t->map; t++) { + if (t->spk_mask == spk) + return t->map; + } + return 0; +} + +static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) +{ + int i, c; + int spk_mask = 0; + struct snd_pcm_chmap_elem *chmap; + u8 eld_high, eld_high_mask = 0xF0; + u8 high_msb; + + kfree(intelhaddata->chmap->chmap); + intelhaddata->chmap->chmap = NULL; + + chmap = kzalloc(sizeof(*chmap), GFP_KERNEL); + if (!chmap) + return; + + dev_dbg(intelhaddata->dev, "eld speaker = %x\n", + intelhaddata->eld[DRM_ELD_SPEAKER]); + + /* WA: Fix the max channel supported to 8 */ + + /* + * Sink may support more than 8 channels, if eld_high has more than + * one bit set. SOC supports max 8 channels. + * Refer eld_speaker_allocation_bits, for sink speaker allocation + */ + + /* if 0x2F < eld < 0x4F fall back to 0x2f, else fall back to 0x4F */ + eld_high = intelhaddata->eld[DRM_ELD_SPEAKER] & eld_high_mask; + if ((eld_high & (eld_high-1)) && (eld_high > 0x1F)) { + /* eld_high & (eld_high-1): if more than 1 bit set */ + /* 0x1F: 7 channels */ + for (i = 1; i < 4; i++) { + high_msb = eld_high & (0x80 >> i); + if (high_msb) { + intelhaddata->eld[DRM_ELD_SPEAKER] &= + high_msb | 0xF; + break; + } + } + } + + for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { + if (intelhaddata->eld[DRM_ELD_SPEAKER] & (1 << i)) + spk_mask |= eld_speaker_allocation_bits[i]; + } + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + if (spk_mask == channel_allocations[i].spk_mask) { + for (c = 0; c < channel_allocations[i].channels; c++) { + chmap->map[c] = spk_to_chmap( + channel_allocations[i].speakers[ + (MAX_SPEAKERS - 1) - c]); + } + chmap->channels = channel_allocations[i].channels; + intelhaddata->chmap->chmap = chmap; + break; + } + } + if (i >= ARRAY_SIZE(channel_allocations)) + kfree(chmap); +} + +/* + * ALSA API channel-map control callbacks + */ +static int had_chmap_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = HAD_MAX_CHANNEL; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SNDRV_CHMAP_LAST; + return 0; +} + +static int had_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + struct snd_intelhad *intelhaddata = info->private_data; + int i; + const struct snd_pcm_chmap_elem *chmap; + + memset(ucontrol->value.integer.value, 0, + sizeof(long) * HAD_MAX_CHANNEL); + mutex_lock(&intelhaddata->mutex); + if (!intelhaddata->chmap->chmap) { + mutex_unlock(&intelhaddata->mutex); + return 0; + } + + chmap = intelhaddata->chmap->chmap; + for (i = 0; i < chmap->channels; i++) + ucontrol->value.integer.value[i] = chmap->map[i]; + mutex_unlock(&intelhaddata->mutex); + + return 0; +} + +static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata, + struct snd_pcm *pcm) +{ + int err; + + err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + NULL, 0, (unsigned long)intelhaddata, + &intelhaddata->chmap); + if (err < 0) + return err; + + intelhaddata->chmap->private_data = intelhaddata; + intelhaddata->chmap->kctl->info = had_chmap_ctl_info; + intelhaddata->chmap->kctl->get = had_chmap_ctl_get; + intelhaddata->chmap->chmap = NULL; + return 0; +} + +/* + * Initialize Data Island Packets registers + * This function is called in the prepare callback + */ +static void had_prog_dip(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + int i; + union aud_ctrl_st ctrl_state = {.regval = 0}; + union aud_info_frame2 frame2 = {.regval = 0}; + union aud_info_frame3 frame3 = {.regval = 0}; + u8 checksum = 0; + u32 info_frame; + int channels; + int ca; + + channels = substream->runtime->channels; + + had_write_register(intelhaddata, AUD_CNTL_ST, ctrl_state.regval); + + ca = had_channel_allocation(intelhaddata, channels); + if (intelhaddata->dp_output) { + info_frame = DP_INFO_FRAME_WORD1; + frame2.regval = (substream->runtime->channels - 1) | (ca << 24); + } else { + info_frame = HDMI_INFO_FRAME_WORD1; + frame2.regx.chnl_cnt = substream->runtime->channels - 1; + frame3.regx.chnl_alloc = ca; + + /* Calculte the byte wide checksum for all valid DIP words */ + for (i = 0; i < BYTES_PER_WORD; i++) + checksum += (info_frame >> (i * 8)) & 0xff; + for (i = 0; i < BYTES_PER_WORD; i++) + checksum += (frame2.regval >> (i * 8)) & 0xff; + for (i = 0; i < BYTES_PER_WORD; i++) + checksum += (frame3.regval >> (i * 8)) & 0xff; + + frame2.regx.chksum = -(checksum); + } + + had_write_register(intelhaddata, AUD_HDMIW_INFOFR, info_frame); + had_write_register(intelhaddata, AUD_HDMIW_INFOFR, frame2.regval); + had_write_register(intelhaddata, AUD_HDMIW_INFOFR, frame3.regval); + + /* program remaining DIP words with zero */ + for (i = 0; i < HAD_MAX_DIP_WORDS-VALID_DIP_WORDS; i++) + had_write_register(intelhaddata, AUD_HDMIW_INFOFR, 0x0); + + ctrl_state.regx.dip_freq = 1; + ctrl_state.regx.dip_en_sta = 1; + had_write_register(intelhaddata, AUD_CNTL_ST, ctrl_state.regval); +} + +static int had_calculate_maud_value(u32 aud_samp_freq, u32 link_rate) +{ + u32 maud_val; + + /* Select maud according to DP 1.2 spec */ + if (link_rate == DP_2_7_GHZ) { + switch (aud_samp_freq) { + case AUD_SAMPLE_RATE_32: + maud_val = AUD_SAMPLE_RATE_32_DP_2_7_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_44_1: + maud_val = AUD_SAMPLE_RATE_44_1_DP_2_7_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_48: + maud_val = AUD_SAMPLE_RATE_48_DP_2_7_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_88_2: + maud_val = AUD_SAMPLE_RATE_88_2_DP_2_7_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_96: + maud_val = AUD_SAMPLE_RATE_96_DP_2_7_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_176_4: + maud_val = AUD_SAMPLE_RATE_176_4_DP_2_7_MAUD_VAL; + break; + + case HAD_MAX_RATE: + maud_val = HAD_MAX_RATE_DP_2_7_MAUD_VAL; + break; + + default: + maud_val = -EINVAL; + break; + } + } else if (link_rate == DP_1_62_GHZ) { + switch (aud_samp_freq) { + case AUD_SAMPLE_RATE_32: + maud_val = AUD_SAMPLE_RATE_32_DP_1_62_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_44_1: + maud_val = AUD_SAMPLE_RATE_44_1_DP_1_62_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_48: + maud_val = AUD_SAMPLE_RATE_48_DP_1_62_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_88_2: + maud_val = AUD_SAMPLE_RATE_88_2_DP_1_62_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_96: + maud_val = AUD_SAMPLE_RATE_96_DP_1_62_MAUD_VAL; + break; + + case AUD_SAMPLE_RATE_176_4: + maud_val = AUD_SAMPLE_RATE_176_4_DP_1_62_MAUD_VAL; + break; + + case HAD_MAX_RATE: + maud_val = HAD_MAX_RATE_DP_1_62_MAUD_VAL; + break; + + default: + maud_val = -EINVAL; + break; + } + } else + maud_val = -EINVAL; + + return maud_val; +} + +/* + * Program HDMI audio CTS value + * + * @aud_samp_freq: sampling frequency of audio data + * @tmds: sampling frequency of the display data + * @link_rate: DP link rate + * @n_param: N value, depends on aud_samp_freq + * @intelhaddata: substream private data + * + * Program CTS register based on the audio and display sampling frequency + */ +static void had_prog_cts(u32 aud_samp_freq, u32 tmds, u32 link_rate, + u32 n_param, struct snd_intelhad *intelhaddata) +{ + u32 cts_val; + u64 dividend, divisor; + + if (intelhaddata->dp_output) { + /* Substitute cts_val with Maud according to DP 1.2 spec*/ + cts_val = had_calculate_maud_value(aud_samp_freq, link_rate); + } else { + /* Calculate CTS according to HDMI 1.3a spec*/ + dividend = (u64)tmds * n_param*1000; + divisor = 128 * aud_samp_freq; + cts_val = div64_u64(dividend, divisor); + } + dev_dbg(intelhaddata->dev, "TMDS value=%d, N value=%d, CTS Value=%d\n", + tmds, n_param, cts_val); + had_write_register(intelhaddata, AUD_HDMI_CTS, (BIT(24) | cts_val)); +} + +static int had_calculate_n_value(u32 aud_samp_freq) +{ + int n_val; + + /* Select N according to HDMI 1.3a spec*/ + switch (aud_samp_freq) { + case AUD_SAMPLE_RATE_32: + n_val = 4096; + break; + + case AUD_SAMPLE_RATE_44_1: + n_val = 6272; + break; + + case AUD_SAMPLE_RATE_48: + n_val = 6144; + break; + + case AUD_SAMPLE_RATE_88_2: + n_val = 12544; + break; + + case AUD_SAMPLE_RATE_96: + n_val = 12288; + break; + + case AUD_SAMPLE_RATE_176_4: + n_val = 25088; + break; + + case HAD_MAX_RATE: + n_val = 24576; + break; + + default: + n_val = -EINVAL; + break; + } + return n_val; +} + +/* + * Program HDMI audio N value + * + * @aud_samp_freq: sampling frequency of audio data + * @n_param: N value, depends on aud_samp_freq + * @intelhaddata: substream private data + * + * This function is called in the prepare callback. + * It programs based on the audio and display sampling frequency + */ +static int had_prog_n(u32 aud_samp_freq, u32 *n_param, + struct snd_intelhad *intelhaddata) +{ + int n_val; + + if (intelhaddata->dp_output) { + /* + * According to DP specs, Maud and Naud values hold + * a relationship, which is stated as: + * Maud/Naud = 512 * fs / f_LS_Clk + * where, fs is the sampling frequency of the audio stream + * and Naud is 32768 for Async clock. + */ + + n_val = DP_NAUD_VAL; + } else + n_val = had_calculate_n_value(aud_samp_freq); + + if (n_val < 0) + return n_val; + + had_write_register(intelhaddata, AUD_N_ENABLE, (BIT(24) | n_val)); + *n_param = n_val; + return 0; +} + +/* + * PCM ring buffer handling + * + * The hardware provides a ring buffer with the fixed 4 buffer descriptors + * (BDs). The driver maps these 4 BDs onto the PCM ring buffer. The mapping + * moves at each period elapsed. The below illustrates how it works: + * + * At time=0 + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 0 | 1 | 2 | 3 | + * + * At time=1 (period elapsed) + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 1 | 2 | 3 | 0 | + * + * At time=2 (second period elapsed) + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 2 | 3 | 0 | 1 | + * + * The bd_head field points to the index of the BD to be read. It's also the + * position to be filled at next. The pcm_head and the pcm_filled fields + * point to the indices of the current position and of the next position to + * be filled, respectively. For PCM buffer there are both _head and _filled + * because they may be difference when nperiods > 4. For example, in the + * example above at t=1, bd_head=1 and pcm_head=1 while pcm_filled=5: + * + * pcm_head (=1) --v v-- pcm_filled (=5) + * PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1| + * BD | 1 | 2 | 3 | 0 | + * bd_head (=1) --^ ^-- next to fill (= bd_head) + * + * For nperiods < 4, the remaining BDs out of 4 are marked as invalid, so that + * the hardware skips those BDs in the loop. + * + * An exceptional setup is the case with nperiods=1. Since we have to update + * BDs after finishing one BD processing, we'd need at least two BDs, where + * both BDs point to the same content, the same address, the same size of the + * whole PCM buffer. + */ + +#define AUD_BUF_ADDR(x) (AUD_BUF_A_ADDR + (x) * HAD_REG_WIDTH) +#define AUD_BUF_LEN(x) (AUD_BUF_A_LENGTH + (x) * HAD_REG_WIDTH) + +/* Set up a buffer descriptor at the "filled" position */ +static void had_prog_bd(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + int idx = intelhaddata->bd_head; + int ofs = intelhaddata->pcmbuf_filled * intelhaddata->period_bytes; + u32 addr = substream->runtime->dma_addr + ofs; + + addr |= AUD_BUF_VALID; + if (!substream->runtime->no_period_wakeup) + addr |= AUD_BUF_INTR_EN; + had_write_register(intelhaddata, AUD_BUF_ADDR(idx), addr); + had_write_register(intelhaddata, AUD_BUF_LEN(idx), + intelhaddata->period_bytes); + + /* advance the indices to the next */ + intelhaddata->bd_head++; + intelhaddata->bd_head %= intelhaddata->num_bds; + intelhaddata->pcmbuf_filled++; + intelhaddata->pcmbuf_filled %= substream->runtime->periods; +} + +/* invalidate a buffer descriptor with the given index */ +static void had_invalidate_bd(struct snd_intelhad *intelhaddata, + int idx) +{ + had_write_register(intelhaddata, AUD_BUF_ADDR(idx), 0); + had_write_register(intelhaddata, AUD_BUF_LEN(idx), 0); +} + +/* Initial programming of ring buffer */ +static void had_init_ringbuf(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int i, num_periods; + + num_periods = runtime->periods; + intelhaddata->num_bds = min(num_periods, HAD_NUM_OF_RING_BUFS); + /* set the minimum 2 BDs for num_periods=1 */ + intelhaddata->num_bds = max(intelhaddata->num_bds, 2U); + intelhaddata->period_bytes = + frames_to_bytes(runtime, runtime->period_size); + WARN_ON(intelhaddata->period_bytes & 0x3f); + + intelhaddata->bd_head = 0; + intelhaddata->pcmbuf_head = 0; + intelhaddata->pcmbuf_filled = 0; + + for (i = 0; i < HAD_NUM_OF_RING_BUFS; i++) { + if (i < intelhaddata->num_bds) + had_prog_bd(substream, intelhaddata); + else /* invalidate the rest */ + had_invalidate_bd(intelhaddata, i); + } + + intelhaddata->bd_head = 0; /* reset at head again before starting */ +} + +/* process a bd, advance to the next */ +static void had_advance_ringbuf(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + int num_periods = substream->runtime->periods; + + /* reprogram the next buffer */ + had_prog_bd(substream, intelhaddata); + + /* proceed to next */ + intelhaddata->pcmbuf_head++; + intelhaddata->pcmbuf_head %= num_periods; +} + +/* process the current BD(s); + * returns the current PCM buffer byte position, or -EPIPE for underrun. + */ +static int had_process_ringbuf(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) +{ + int len, processed; + unsigned long flags; + + processed = 0; + spin_lock_irqsave(&intelhaddata->had_spinlock, flags); + for (;;) { + /* get the remaining bytes on the buffer */ + had_read_register(intelhaddata, + AUD_BUF_LEN(intelhaddata->bd_head), + &len); + if (len < 0 || len > intelhaddata->period_bytes) { + dev_dbg(intelhaddata->dev, "Invalid buf length %d\n", + len); + len = -EPIPE; + goto out; + } + + if (len > 0) /* OK, this is the current buffer */ + break; + + /* len=0 => already empty, check the next buffer */ + if (++processed >= intelhaddata->num_bds) { + len = -EPIPE; /* all empty? - report underrun */ + goto out; + } + had_advance_ringbuf(substream, intelhaddata); + } + + len = intelhaddata->period_bytes - len; + len += intelhaddata->period_bytes * intelhaddata->pcmbuf_head; + out: + spin_unlock_irqrestore(&intelhaddata->had_spinlock, flags); + return len; +} + +/* called from irq handler */ +static void had_process_buffer_done(struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_substream *substream; + + substream = had_substream_get(intelhaddata); + if (!substream) + return; /* no stream? - bail out */ + + if (!intelhaddata->connected) { + snd_pcm_stop_xrun(substream); + goto out; /* disconnected? - bail out */ + } + + /* process or stop the stream */ + if (had_process_ringbuf(substream, intelhaddata) < 0) + snd_pcm_stop_xrun(substream); + else + snd_pcm_period_elapsed(substream); + + out: + had_substream_put(intelhaddata); +} + +/* + * The interrupt status 'sticky' bits might not be cleared by + * setting '1' to that bit once... + */ +static void wait_clear_underrun_bit(struct snd_intelhad *intelhaddata) +{ + int i; + u32 val; + + for (i = 0; i < 100; i++) { + /* clear bit30, 31 AUD_HDMI_STATUS */ + had_read_register(intelhaddata, AUD_HDMI_STATUS, &val); + if (!(val & AUD_HDMI_STATUS_MASK_UNDERRUN)) + return; + udelay(100); + cond_resched(); + had_write_register(intelhaddata, AUD_HDMI_STATUS, val); + } + dev_err(intelhaddata->dev, "Unable to clear UNDERRUN bits\n"); +} + +/* Perform some reset procedure but only when need_reset is set; + * this is called from prepare or hw_free callbacks once after trigger STOP + * or underrun has been processed in order to settle down the h/w state. + */ +static void had_do_reset(struct snd_intelhad *intelhaddata) +{ + if (!intelhaddata->need_reset || !intelhaddata->connected) + return; + + /* Reset buffer pointers */ + had_reset_audio(intelhaddata); + wait_clear_underrun_bit(intelhaddata); + intelhaddata->need_reset = false; +} + +/* called from irq handler */ +static void had_process_buffer_underrun(struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_substream *substream; + + /* Report UNDERRUN error to above layers */ + substream = had_substream_get(intelhaddata); + if (substream) { + snd_pcm_stop_xrun(substream); + had_substream_put(intelhaddata); + } + intelhaddata->need_reset = true; +} + +/* + * ALSA PCM open callback + */ +static int had_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_intelhad *intelhaddata; + struct snd_pcm_runtime *runtime; + int retval; + + intelhaddata = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + pm_runtime_get_sync(intelhaddata->dev); + + /* set the runtime hw parameter with local snd_pcm_hardware struct */ + runtime->hw = had_pcm_hardware; + + retval = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (retval < 0) + goto error; + + /* Make sure, that the period size is always aligned + * 64byte boundary + */ + retval = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + if (retval < 0) + goto error; + + retval = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (retval < 0) + goto error; + + /* expose PCM substream */ + spin_lock_irq(&intelhaddata->had_spinlock); + intelhaddata->stream_info.substream = substream; + intelhaddata->stream_info.substream_refcount++; + spin_unlock_irq(&intelhaddata->had_spinlock); + + return retval; + error: + pm_runtime_mark_last_busy(intelhaddata->dev); + pm_runtime_put_autosuspend(intelhaddata->dev); + return retval; +} + +/* + * ALSA PCM close callback + */ +static int had_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_intelhad *intelhaddata; + + intelhaddata = snd_pcm_substream_chip(substream); + + /* unreference and sync with the pending PCM accesses */ + spin_lock_irq(&intelhaddata->had_spinlock); + intelhaddata->stream_info.substream = NULL; + intelhaddata->stream_info.substream_refcount--; + while (intelhaddata->stream_info.substream_refcount > 0) { + spin_unlock_irq(&intelhaddata->had_spinlock); + cpu_relax(); + spin_lock_irq(&intelhaddata->had_spinlock); + } + spin_unlock_irq(&intelhaddata->had_spinlock); + + pm_runtime_mark_last_busy(intelhaddata->dev); + pm_runtime_put_autosuspend(intelhaddata->dev); + return 0; +} + +/* + * ALSA PCM hw_params callback + */ +static int had_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_intelhad *intelhaddata; + unsigned long addr; + int pages, buf_size, retval; + + intelhaddata = snd_pcm_substream_chip(substream); + buf_size = params_buffer_bytes(hw_params); + retval = snd_pcm_lib_malloc_pages(substream, buf_size); + if (retval < 0) + return retval; + dev_dbg(intelhaddata->dev, "%s:allocated memory = %d\n", + __func__, buf_size); + /* mark the pages as uncached region */ + addr = (unsigned long) substream->runtime->dma_area; + pages = (substream->runtime->dma_bytes + PAGE_SIZE - 1) / PAGE_SIZE; + retval = set_memory_uc(addr, pages); + if (retval) { + dev_err(intelhaddata->dev, "set_memory_uc failed.Error:%d\n", + retval); + return retval; + } + memset(substream->runtime->dma_area, 0, buf_size); + + return retval; +} + +/* + * ALSA PCM hw_free callback + */ +static int had_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_intelhad *intelhaddata; + unsigned long addr; + u32 pages; + + intelhaddata = snd_pcm_substream_chip(substream); + had_do_reset(intelhaddata); + + /* mark back the pages as cached/writeback region before the free */ + if (substream->runtime->dma_area != NULL) { + addr = (unsigned long) substream->runtime->dma_area; + pages = (substream->runtime->dma_bytes + PAGE_SIZE - 1) / + PAGE_SIZE; + set_memory_wb(addr, pages); + return snd_pcm_lib_free_pages(substream); + } + return 0; +} + +/* + * ALSA PCM trigger callback + */ +static int had_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int retval = 0; + struct snd_intelhad *intelhaddata; + + intelhaddata = snd_pcm_substream_chip(substream); + + spin_lock(&intelhaddata->had_spinlock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + /* Enable Audio */ + had_ack_irqs(intelhaddata); /* FIXME: do we need this? */ + had_enable_audio(intelhaddata, true); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* Disable Audio */ + had_enable_audio(intelhaddata, false); + intelhaddata->need_reset = true; + break; + + default: + retval = -EINVAL; + } + spin_unlock(&intelhaddata->had_spinlock); + return retval; +} + +/* + * ALSA PCM prepare callback + */ +static int had_pcm_prepare(struct snd_pcm_substream *substream) +{ + int retval; + u32 disp_samp_freq, n_param; + u32 link_rate = 0; + struct snd_intelhad *intelhaddata; + struct snd_pcm_runtime *runtime; + + intelhaddata = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + dev_dbg(intelhaddata->dev, "period_size=%d\n", + (int)frames_to_bytes(runtime, runtime->period_size)); + dev_dbg(intelhaddata->dev, "periods=%d\n", runtime->periods); + dev_dbg(intelhaddata->dev, "buffer_size=%d\n", + (int)snd_pcm_lib_buffer_bytes(substream)); + dev_dbg(intelhaddata->dev, "rate=%d\n", runtime->rate); + dev_dbg(intelhaddata->dev, "channels=%d\n", runtime->channels); + + had_do_reset(intelhaddata); + + /* Get N value in KHz */ + disp_samp_freq = intelhaddata->tmds_clock_speed; + + retval = had_prog_n(substream->runtime->rate, &n_param, intelhaddata); + if (retval) { + dev_err(intelhaddata->dev, + "programming N value failed %#x\n", retval); + goto prep_end; + } + + if (intelhaddata->dp_output) + link_rate = intelhaddata->link_rate; + + had_prog_cts(substream->runtime->rate, disp_samp_freq, link_rate, + n_param, intelhaddata); + + had_prog_dip(substream, intelhaddata); + + retval = had_init_audio_ctrl(substream, intelhaddata); + + /* Prog buffer address */ + had_init_ringbuf(substream, intelhaddata); + + /* + * Program channel mapping in following order: + * FL, FR, C, LFE, RL, RR + */ + + had_write_register(intelhaddata, AUD_BUF_CH_SWAP, SWAP_LFE_CENTER); + +prep_end: + return retval; +} + +/* + * ALSA PCM pointer callback + */ +static snd_pcm_uframes_t had_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_intelhad *intelhaddata; + int len; + + intelhaddata = snd_pcm_substream_chip(substream); + + if (!intelhaddata->connected) + return SNDRV_PCM_POS_XRUN; + + len = had_process_ringbuf(substream, intelhaddata); + if (len < 0) + return SNDRV_PCM_POS_XRUN; + len = bytes_to_frames(substream->runtime, len); + /* wrapping may happen when periods=1 */ + len %= substream->runtime->buffer_size; + return len; +} + +/* + * ALSA PCM mmap callback + */ +static int had_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + return remap_pfn_range(vma, vma->vm_start, + substream->runtime->dma_addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +/* + * ALSA PCM ops + */ +static const struct snd_pcm_ops had_pcm_ops = { + .open = had_pcm_open, + .close = had_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = had_pcm_hw_params, + .hw_free = had_pcm_hw_free, + .prepare = had_pcm_prepare, + .trigger = had_pcm_trigger, + .pointer = had_pcm_pointer, + .mmap = had_pcm_mmap, +}; + +/* process mode change of the running stream; called in mutex */ +static int had_process_mode_change(struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_substream *substream; + int retval = 0; + u32 disp_samp_freq, n_param; + u32 link_rate = 0; + + substream = had_substream_get(intelhaddata); + if (!substream) + return 0; + + /* Disable Audio */ + had_enable_audio(intelhaddata, false); + + /* Update CTS value */ + disp_samp_freq = intelhaddata->tmds_clock_speed; + + retval = had_prog_n(substream->runtime->rate, &n_param, intelhaddata); + if (retval) { + dev_err(intelhaddata->dev, + "programming N value failed %#x\n", retval); + goto out; + } + + if (intelhaddata->dp_output) + link_rate = intelhaddata->link_rate; + + had_prog_cts(substream->runtime->rate, disp_samp_freq, link_rate, + n_param, intelhaddata); + + /* Enable Audio */ + had_enable_audio(intelhaddata, true); + +out: + had_substream_put(intelhaddata); + return retval; +} + +/* process hot plug, called from wq with mutex locked */ +static void had_process_hot_plug(struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_substream *substream; + + spin_lock_irq(&intelhaddata->had_spinlock); + if (intelhaddata->connected) { + dev_dbg(intelhaddata->dev, "Device already connected\n"); + spin_unlock_irq(&intelhaddata->had_spinlock); + return; + } + + /* Disable Audio */ + had_enable_audio(intelhaddata, false); + + intelhaddata->connected = true; + dev_dbg(intelhaddata->dev, + "%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_CONNECTED\n", + __func__, __LINE__); + spin_unlock_irq(&intelhaddata->had_spinlock); + + had_build_channel_allocation_map(intelhaddata); + + /* Report to above ALSA layer */ + substream = had_substream_get(intelhaddata); + if (substream) { + snd_pcm_stop_xrun(substream); + had_substream_put(intelhaddata); + } + + snd_jack_report(intelhaddata->jack, SND_JACK_AVOUT); +} + +/* process hot unplug, called from wq with mutex locked */ +static void had_process_hot_unplug(struct snd_intelhad *intelhaddata) +{ + struct snd_pcm_substream *substream; + + spin_lock_irq(&intelhaddata->had_spinlock); + if (!intelhaddata->connected) { + dev_dbg(intelhaddata->dev, "Device already disconnected\n"); + spin_unlock_irq(&intelhaddata->had_spinlock); + return; + + } + + /* Disable Audio */ + had_enable_audio(intelhaddata, false); + + intelhaddata->connected = false; + dev_dbg(intelhaddata->dev, + "%s @ %d:DEBUG PLUG/UNPLUG : HAD_DRV_DISCONNECTED\n", + __func__, __LINE__); + spin_unlock_irq(&intelhaddata->had_spinlock); + + kfree(intelhaddata->chmap->chmap); + intelhaddata->chmap->chmap = NULL; + + /* Report to above ALSA layer */ + substream = had_substream_get(intelhaddata); + if (substream) { + snd_pcm_stop_xrun(substream); + had_substream_put(intelhaddata); + } + + snd_jack_report(intelhaddata->jack, 0); +} + +/* + * ALSA iec958 and ELD controls + */ + +static int had_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 had_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol); + + mutex_lock(&intelhaddata->mutex); + ucontrol->value.iec958.status[0] = (intelhaddata->aes_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (intelhaddata->aes_bits >> 8) & 0xff; + ucontrol->value.iec958.status[2] = + (intelhaddata->aes_bits >> 16) & 0xff; + ucontrol->value.iec958.status[3] = + (intelhaddata->aes_bits >> 24) & 0xff; + mutex_unlock(&intelhaddata->mutex); + return 0; +} + +static int had_iec958_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 had_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol); + int changed = 0; + + 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); + mutex_lock(&intelhaddata->mutex); + if (intelhaddata->aes_bits != val) { + intelhaddata->aes_bits = val; + changed = 1; + } + mutex_unlock(&intelhaddata->mutex); + return changed; +} + +static int had_ctl_eld_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = HDMI_MAX_ELD_BYTES; + return 0; +} + +static int had_ctl_eld_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol); + + mutex_lock(&intelhaddata->mutex); + memcpy(ucontrol->value.bytes.data, intelhaddata->eld, + HDMI_MAX_ELD_BYTES); + mutex_unlock(&intelhaddata->mutex); + return 0; +} + +static const struct snd_kcontrol_new had_controls[] = { + { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), + .info = had_iec958_info, /* shared */ + .get = had_iec958_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = had_iec958_info, + .get = had_iec958_get, + .put = had_iec958_put, + }, + { + .access = (SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE), + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "ELD", + .info = had_ctl_eld_info, + .get = had_ctl_eld_get, + }, +}; + +/* + * audio interrupt handler + */ +static irqreturn_t display_pipe_interrupt_handler(int irq, void *dev_id) +{ + struct snd_intelhad_card *card_ctx = dev_id; + u32 audio_stat[3] = {}; + int pipe, port; + + for_each_pipe(card_ctx, pipe) { + /* use raw register access to ack IRQs even while disconnected */ + audio_stat[pipe] = had_read_register_raw(card_ctx, pipe, + AUD_HDMI_STATUS) & + (HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE); + + if (audio_stat[pipe]) + had_write_register_raw(card_ctx, pipe, + AUD_HDMI_STATUS, audio_stat[pipe]); + } + + for_each_port(card_ctx, port) { + struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port]; + int pipe = ctx->pipe; + + if (pipe < 0) + continue; + + if (audio_stat[pipe] & HDMI_AUDIO_BUFFER_DONE) + had_process_buffer_done(ctx); + if (audio_stat[pipe] & HDMI_AUDIO_UNDERRUN) + had_process_buffer_underrun(ctx); + } + + return IRQ_HANDLED; +} + +/* + * monitor plug/unplug notification from i915; just kick off the work + */ +static void notify_audio_lpe(struct platform_device *pdev, int port) +{ + struct snd_intelhad_card *card_ctx = platform_get_drvdata(pdev); + struct snd_intelhad *ctx; + + ctx = &card_ctx->pcm_ctx[single_port ? 0 : port]; + if (single_port) + ctx->port = port; + + schedule_work(&ctx->hdmi_audio_wq); +} + +/* the work to handle monitor hot plug/unplug */ +static void had_audio_wq(struct work_struct *work) +{ + struct snd_intelhad *ctx = + container_of(work, struct snd_intelhad, hdmi_audio_wq); + struct intel_hdmi_lpe_audio_pdata *pdata = ctx->dev->platform_data; + struct intel_hdmi_lpe_audio_port_pdata *ppdata = &pdata->port[ctx->port]; + + pm_runtime_get_sync(ctx->dev); + mutex_lock(&ctx->mutex); + if (ppdata->pipe < 0) { + dev_dbg(ctx->dev, "%s: Event: HAD_NOTIFY_HOT_UNPLUG : port = %d\n", + __func__, ctx->port); + + memset(ctx->eld, 0, sizeof(ctx->eld)); /* clear the old ELD */ + + ctx->dp_output = false; + ctx->tmds_clock_speed = 0; + ctx->link_rate = 0; + + /* Shut down the stream */ + had_process_hot_unplug(ctx); + + ctx->pipe = -1; + } else { + dev_dbg(ctx->dev, "%s: HAD_NOTIFY_ELD : port = %d, tmds = %d\n", + __func__, ctx->port, ppdata->ls_clock); + + memcpy(ctx->eld, ppdata->eld, sizeof(ctx->eld)); + + ctx->dp_output = ppdata->dp_output; + if (ctx->dp_output) { + ctx->tmds_clock_speed = 0; + ctx->link_rate = ppdata->ls_clock; + } else { + ctx->tmds_clock_speed = ppdata->ls_clock; + ctx->link_rate = 0; + } + + /* + * Shut down the stream before we change + * the pipe assignment for this pcm device + */ + had_process_hot_plug(ctx); + + ctx->pipe = ppdata->pipe; + + /* Restart the stream if necessary */ + had_process_mode_change(ctx); + } + + mutex_unlock(&ctx->mutex); + pm_runtime_mark_last_busy(ctx->dev); + pm_runtime_put_autosuspend(ctx->dev); +} + +/* + * Jack interface + */ +static int had_create_jack(struct snd_intelhad *ctx, + struct snd_pcm *pcm) +{ + char hdmi_str[32]; + int err; + + snprintf(hdmi_str, sizeof(hdmi_str), + "HDMI/DP,pcm=%d", pcm->device); + + err = snd_jack_new(ctx->card_ctx->card, hdmi_str, + SND_JACK_AVOUT, &ctx->jack, + true, false); + if (err < 0) + return err; + ctx->jack->private_data = ctx; + return 0; +} + +/* + * PM callbacks + */ + +static int hdmi_lpe_audio_runtime_suspend(struct device *dev) +{ + struct snd_intelhad_card *card_ctx = dev_get_drvdata(dev); + int port; + + for_each_port(card_ctx, port) { + struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port]; + struct snd_pcm_substream *substream; + + substream = had_substream_get(ctx); + if (substream) { + snd_pcm_suspend(substream); + had_substream_put(ctx); + } + } + + return 0; +} + +static int __maybe_unused hdmi_lpe_audio_suspend(struct device *dev) +{ + struct snd_intelhad_card *card_ctx = dev_get_drvdata(dev); + int err; + + err = hdmi_lpe_audio_runtime_suspend(dev); + if (!err) + snd_power_change_state(card_ctx->card, SNDRV_CTL_POWER_D3hot); + return err; +} + +static int hdmi_lpe_audio_runtime_resume(struct device *dev) +{ + pm_runtime_mark_last_busy(dev); + return 0; +} + +static int __maybe_unused hdmi_lpe_audio_resume(struct device *dev) +{ + struct snd_intelhad_card *card_ctx = dev_get_drvdata(dev); + + hdmi_lpe_audio_runtime_resume(dev); + snd_power_change_state(card_ctx->card, SNDRV_CTL_POWER_D0); + return 0; +} + +/* release resources */ +static void hdmi_lpe_audio_free(struct snd_card *card) +{ + struct snd_intelhad_card *card_ctx = card->private_data; + struct intel_hdmi_lpe_audio_pdata *pdata = card_ctx->dev->platform_data; + int port; + + spin_lock_irq(&pdata->lpe_audio_slock); + pdata->notify_audio_lpe = NULL; + spin_unlock_irq(&pdata->lpe_audio_slock); + + for_each_port(card_ctx, port) { + struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port]; + + cancel_work_sync(&ctx->hdmi_audio_wq); + } + + if (card_ctx->mmio_start) + iounmap(card_ctx->mmio_start); + if (card_ctx->irq >= 0) + free_irq(card_ctx->irq, card_ctx); +} + +/* + * hdmi_lpe_audio_probe - start bridge with i915 + * + * This function is called when the i915 driver creates the + * hdmi-lpe-audio platform device. + */ +static int hdmi_lpe_audio_probe(struct platform_device *pdev) +{ + struct snd_card *card; + struct snd_intelhad_card *card_ctx; + struct snd_intelhad *ctx; + struct snd_pcm *pcm; + struct intel_hdmi_lpe_audio_pdata *pdata; + int irq; + struct resource *res_mmio; + int port, ret; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "%s: quit: pdata not allocated by i915!!\n", __func__); + return -EINVAL; + } + + /* get resources */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Could not get irq resource: %d\n", irq); + return irq; + } + + res_mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_mmio) { + dev_err(&pdev->dev, "Could not get IO_MEM resources\n"); + return -ENXIO; + } + + /* create a card instance with ALSA framework */ + ret = snd_card_new(&pdev->dev, hdmi_card_index, hdmi_card_id, + THIS_MODULE, sizeof(*card_ctx), &card); + if (ret) + return ret; + + card_ctx = card->private_data; + card_ctx->dev = &pdev->dev; + card_ctx->card = card; + strcpy(card->driver, INTEL_HAD); + strcpy(card->shortname, "Intel HDMI/DP LPE Audio"); + strcpy(card->longname, "Intel HDMI/DP LPE Audio"); + + card_ctx->irq = -1; + + card->private_free = hdmi_lpe_audio_free; + + platform_set_drvdata(pdev, card_ctx); + + card_ctx->num_pipes = pdata->num_pipes; + card_ctx->num_ports = single_port ? 1 : pdata->num_ports; + + for_each_port(card_ctx, port) { + ctx = &card_ctx->pcm_ctx[port]; + ctx->card_ctx = card_ctx; + ctx->dev = card_ctx->dev; + ctx->port = single_port ? -1 : port; + ctx->pipe = -1; + + spin_lock_init(&ctx->had_spinlock); + mutex_init(&ctx->mutex); + INIT_WORK(&ctx->hdmi_audio_wq, had_audio_wq); + } + + dev_dbg(&pdev->dev, "%s: mmio_start = 0x%x, mmio_end = 0x%x\n", + __func__, (unsigned int)res_mmio->start, + (unsigned int)res_mmio->end); + + card_ctx->mmio_start = ioremap_nocache(res_mmio->start, + (size_t)(resource_size(res_mmio))); + if (!card_ctx->mmio_start) { + dev_err(&pdev->dev, "Could not get ioremap\n"); + ret = -EACCES; + goto err; + } + + /* setup interrupt handler */ + ret = request_irq(irq, display_pipe_interrupt_handler, 0, + pdev->name, card_ctx); + if (ret < 0) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto err; + } + + card_ctx->irq = irq; + + /* only 32bit addressable */ + dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + + init_channel_allocations(); + + card_ctx->num_pipes = pdata->num_pipes; + card_ctx->num_ports = single_port ? 1 : pdata->num_ports; + + for_each_port(card_ctx, port) { + int i; + + ctx = &card_ctx->pcm_ctx[port]; + ret = snd_pcm_new(card, INTEL_HAD, port, MAX_PB_STREAMS, + MAX_CAP_STREAMS, &pcm); + if (ret) + goto err; + + /* setup private data which can be retrieved when required */ + pcm->private_data = ctx; + pcm->info_flags = 0; + strlcpy(pcm->name, card->shortname, strlen(card->shortname)); + /* setup the ops for playabck */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &had_pcm_ops); + + /* allocate dma pages; + * try to allocate 600k buffer as default which is large enough + */ + snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, NULL, + HAD_DEFAULT_BUFFER, HAD_MAX_BUFFER); + + /* create controls */ + for (i = 0; i < ARRAY_SIZE(had_controls); i++) { + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1(&had_controls[i], ctx); + if (!kctl) { + ret = -ENOMEM; + goto err; + } + + kctl->id.device = pcm->device; + + ret = snd_ctl_add(card, kctl); + if (ret < 0) + goto err; + } + + /* Register channel map controls */ + ret = had_register_chmap_ctls(ctx, pcm); + if (ret < 0) + goto err; + + ret = had_create_jack(ctx, pcm); + if (ret < 0) + goto err; + } + + ret = snd_card_register(card); + if (ret) + goto err; + + spin_lock_irq(&pdata->lpe_audio_slock); + pdata->notify_audio_lpe = notify_audio_lpe; + spin_unlock_irq(&pdata->lpe_audio_slock); + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_mark_last_busy(&pdev->dev); + + dev_dbg(&pdev->dev, "%s: handle pending notification\n", __func__); + for_each_port(card_ctx, port) { + struct snd_intelhad *ctx = &card_ctx->pcm_ctx[port]; + + schedule_work(&ctx->hdmi_audio_wq); + } + + return 0; + +err: + snd_card_free(card); + return ret; +} + +/* + * hdmi_lpe_audio_remove - stop bridge with i915 + * + * This function is called when the platform device is destroyed. + */ +static int hdmi_lpe_audio_remove(struct platform_device *pdev) +{ + struct snd_intelhad_card *card_ctx = platform_get_drvdata(pdev); + + snd_card_free(card_ctx->card); + return 0; +} + +static const struct dev_pm_ops hdmi_lpe_audio_pm = { + SET_SYSTEM_SLEEP_PM_OPS(hdmi_lpe_audio_suspend, hdmi_lpe_audio_resume) + SET_RUNTIME_PM_OPS(hdmi_lpe_audio_runtime_suspend, + hdmi_lpe_audio_runtime_resume, NULL) +}; + +static struct platform_driver hdmi_lpe_audio_driver = { + .driver = { + .name = "hdmi-lpe-audio", + .pm = &hdmi_lpe_audio_pm, + }, + .probe = hdmi_lpe_audio_probe, + .remove = hdmi_lpe_audio_remove, +}; + +module_platform_driver(hdmi_lpe_audio_driver); +MODULE_ALIAS("platform:hdmi_lpe_audio"); + +MODULE_AUTHOR("Sailaja Bandarupalli "); +MODULE_AUTHOR("Ramesh Babu K V "); +MODULE_AUTHOR("Vaibhav Agarwal "); +MODULE_AUTHOR("Jerome Anand "); +MODULE_DESCRIPTION("Intel HDMI Audio driver"); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{Intel,Intel_HAD}"); diff --git a/sound/x86/intel_hdmi_audio.h b/sound/x86/intel_hdmi_audio.h new file mode 100644 index 000000000..0d91bb5db --- /dev/null +++ b/sound/x86/intel_hdmi_audio.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 Intel Corporation + * Authors: Sailaja Bandarupalli + * Ramesh Babu K V + * Vaibhav Agarwal + * Jerome Anand + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _INTEL_HDMI_AUDIO_H_ +#define _INTEL_HDMI_AUDIO_H_ + +#include "intel_hdmi_lpe_audio.h" + +#define MAX_PB_STREAMS 1 +#define MAX_CAP_STREAMS 0 +#define BYTES_PER_WORD 0x4 +#define INTEL_HAD "HdmiLpeAudio" + +/* + * CEA speaker placement: + * + * FL FLC FC FRC FR + * + * LFE + * + * RL RLC RC RRC RR + * + * The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M + * corresponds to CEA RL/RR; The SMPTE channel _assignment_ C/LFE is + * swapped to CEA LFE/FC. + */ +enum cea_speaker_placement { + FL = (1 << 0), /* Front Left */ + FC = (1 << 1), /* Front Center */ + FR = (1 << 2), /* Front Right */ + FLC = (1 << 3), /* Front Left Center */ + FRC = (1 << 4), /* Front Right Center */ + RL = (1 << 5), /* Rear Left */ + RC = (1 << 6), /* Rear Center */ + RR = (1 << 7), /* Rear Right */ + RLC = (1 << 8), /* Rear Left Center */ + RRC = (1 << 9), /* Rear Right Center */ + LFE = (1 << 10), /* Low Frequency Effect */ +}; + +struct cea_channel_speaker_allocation { + int ca_index; + int speakers[8]; + + /* derived values, just for convenience */ + int channels; + int spk_mask; +}; + +struct channel_map_table { + unsigned char map; /* ALSA API channel map position */ + unsigned char cea_slot; /* CEA slot value */ + int spk_mask; /* speaker position bit mask */ +}; + +struct pcm_stream_info { + struct snd_pcm_substream *substream; + int substream_refcount; +}; + +/* + * struct snd_intelhad - intelhad driver structure + * + * @card: ptr to hold card details + * @connected: the monitor connection status + * @stream_info: stream information + * @eld: holds ELD info + * @curr_buf: pointer to hold current active ring buf + * @valid_buf_cnt: ring buffer count for stream + * @had_spinlock: driver lock + * @aes_bits: IEC958 status bits + * @buff_done: id of current buffer done intr + * @dev: platoform device handle + * @chmap: holds channel map info + */ +struct snd_intelhad { + struct snd_intelhad_card *card_ctx; + bool connected; + struct pcm_stream_info stream_info; + unsigned char eld[HDMI_MAX_ELD_BYTES]; + bool dp_output; + unsigned int aes_bits; + spinlock_t had_spinlock; + struct device *dev; + struct snd_pcm_chmap *chmap; + int tmds_clock_speed; + int link_rate; + int port; /* fixed */ + int pipe; /* can change dynamically */ + + /* ring buffer (BD) position index */ + unsigned int bd_head; + /* PCM buffer position indices */ + unsigned int pcmbuf_head; /* being processed */ + unsigned int pcmbuf_filled; /* to be filled */ + + unsigned int num_bds; /* number of BDs */ + unsigned int period_bytes; /* PCM period size in bytes */ + + /* internal stuff */ + union aud_cfg aud_config; /* AUD_CONFIG reg value cache */ + struct work_struct hdmi_audio_wq; + struct mutex mutex; /* for protecting chmap and eld */ + bool need_reset; + struct snd_jack *jack; +}; + +struct snd_intelhad_card { + struct snd_card *card; + struct device *dev; + + /* internal stuff */ + int irq; + void __iomem *mmio_start; + int num_pipes; + int num_ports; + struct snd_intelhad pcm_ctx[3]; /* one for each port */ +}; + +#endif /* _INTEL_HDMI_AUDIO_ */ diff --git a/sound/x86/intel_hdmi_lpe_audio.h b/sound/x86/intel_hdmi_lpe_audio.h new file mode 100644 index 000000000..477e51533 --- /dev/null +++ b/sound/x86/intel_hdmi_lpe_audio.h @@ -0,0 +1,328 @@ +/* + * intel_hdmi_lpe_audio.h - Intel HDMI LPE audio driver + * + * Copyright (C) 2016 Intel Corp + * Authors: Sailaja Bandarupalli + * Ramesh Babu K V + * Vaibhav Agarwal + * Jerome Anand + * Aravind Siddappaji + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#ifndef __INTEL_HDMI_LPE_AUDIO_H +#define __INTEL_HDMI_LPE_AUDIO_H + +#define HAD_MIN_CHANNEL 2 +#define HAD_MAX_CHANNEL 8 +#define HAD_NUM_OF_RING_BUFS 4 + +/* max 20bit address, aligned to 64 */ +#define HAD_MAX_BUFFER ((1024 * 1024 - 1) & ~0x3f) +#define HAD_DEFAULT_BUFFER (600 * 1024) /* default prealloc size */ +#define HAD_MAX_PERIODS 256 /* arbitrary, but should suffice */ +#define HAD_MIN_PERIODS 1 +#define HAD_MAX_PERIOD_BYTES ((HAD_MAX_BUFFER / HAD_MIN_PERIODS) & ~0x3f) +#define HAD_MIN_PERIOD_BYTES 1024 /* might be smaller */ +#define HAD_FIFO_SIZE 0 /* fifo not being used */ +#define MAX_SPEAKERS 8 + +#define AUD_SAMPLE_RATE_32 32000 +#define AUD_SAMPLE_RATE_44_1 44100 +#define AUD_SAMPLE_RATE_48 48000 +#define AUD_SAMPLE_RATE_88_2 88200 +#define AUD_SAMPLE_RATE_96 96000 +#define AUD_SAMPLE_RATE_176_4 176400 +#define AUD_SAMPLE_RATE_192 192000 + +#define HAD_MIN_RATE AUD_SAMPLE_RATE_32 +#define HAD_MAX_RATE AUD_SAMPLE_RATE_192 + +#define DIS_SAMPLE_RATE_25_2 25200 +#define DIS_SAMPLE_RATE_27 27000 +#define DIS_SAMPLE_RATE_54 54000 +#define DIS_SAMPLE_RATE_74_25 74250 +#define DIS_SAMPLE_RATE_148_5 148500 +#define HAD_REG_WIDTH 0x08 +#define HAD_MAX_DIP_WORDS 16 + +/* DP Link Rates */ +#define DP_2_7_GHZ 270000 +#define DP_1_62_GHZ 162000 + +/* Maud Values */ +#define AUD_SAMPLE_RATE_32_DP_2_7_MAUD_VAL 1988 +#define AUD_SAMPLE_RATE_44_1_DP_2_7_MAUD_VAL 2740 +#define AUD_SAMPLE_RATE_48_DP_2_7_MAUD_VAL 2982 +#define AUD_SAMPLE_RATE_88_2_DP_2_7_MAUD_VAL 5480 +#define AUD_SAMPLE_RATE_96_DP_2_7_MAUD_VAL 5965 +#define AUD_SAMPLE_RATE_176_4_DP_2_7_MAUD_VAL 10961 +#define HAD_MAX_RATE_DP_2_7_MAUD_VAL 11930 +#define AUD_SAMPLE_RATE_32_DP_1_62_MAUD_VAL 3314 +#define AUD_SAMPLE_RATE_44_1_DP_1_62_MAUD_VAL 4567 +#define AUD_SAMPLE_RATE_48_DP_1_62_MAUD_VAL 4971 +#define AUD_SAMPLE_RATE_88_2_DP_1_62_MAUD_VAL 9134 +#define AUD_SAMPLE_RATE_96_DP_1_62_MAUD_VAL 9942 +#define AUD_SAMPLE_RATE_176_4_DP_1_62_MAUD_VAL 18268 +#define HAD_MAX_RATE_DP_1_62_MAUD_VAL 19884 + +/* Naud Value */ +#define DP_NAUD_VAL 32768 + +/* HDMI Controller register offsets - audio domain common */ +/* Base address for below regs = 0x65000 */ +enum hdmi_ctrl_reg_offset_common { + AUDIO_HDMI_CONFIG_A = 0x000, + AUDIO_HDMI_CONFIG_B = 0x800, + AUDIO_HDMI_CONFIG_C = 0x900, +}; +/* HDMI controller register offsets */ +enum hdmi_ctrl_reg_offset { + AUD_CONFIG = 0x0, + AUD_CH_STATUS_0 = 0x08, + AUD_CH_STATUS_1 = 0x0C, + AUD_HDMI_CTS = 0x10, + AUD_N_ENABLE = 0x14, + AUD_SAMPLE_RATE = 0x18, + AUD_BUF_CONFIG = 0x20, + AUD_BUF_CH_SWAP = 0x24, + AUD_BUF_A_ADDR = 0x40, + AUD_BUF_A_LENGTH = 0x44, + AUD_BUF_B_ADDR = 0x48, + AUD_BUF_B_LENGTH = 0x4c, + AUD_BUF_C_ADDR = 0x50, + AUD_BUF_C_LENGTH = 0x54, + AUD_BUF_D_ADDR = 0x58, + AUD_BUF_D_LENGTH = 0x5c, + AUD_CNTL_ST = 0x60, + AUD_HDMI_STATUS = 0x64, /* v2 */ + AUD_HDMIW_INFOFR = 0x68, /* v2 */ +}; + +/* Audio configuration */ +union aud_cfg { + struct { + u32 aud_en:1; + u32 layout:1; /* LAYOUT[01], see below */ + u32 fmt:2; + u32 num_ch:3; + u32 set:1; + u32 flat:1; + u32 val_bit:1; + u32 user_bit:1; + u32 underrun:1; /* 0: send null packets, + * 1: send silence stream + */ + u32 packet_mode:1; /* 0: 32bit container, 1: 16bit */ + u32 left_align:1; /* 0: MSB bits 0-23, 1: bits 8-31 */ + u32 bogus_sample:1; /* bogus sample for odd channels */ + u32 dp_modei:1; /* 0: HDMI, 1: DP */ + u32 rsvd:16; + } regx; + u32 regval; +}; + +#define AUD_CONFIG_VALID_BIT (1 << 9) +#define AUD_CONFIG_DP_MODE (1 << 15) +#define AUD_CONFIG_CH_MASK 0x70 +#define LAYOUT0 0 /* interleaved stereo */ +#define LAYOUT1 1 /* for channels > 2 */ + +/* Audio Channel Status 0 Attributes */ +union aud_ch_status_0 { + struct { + u32 ch_status:1; + u32 lpcm_id:1; + u32 cp_info:1; + u32 format:3; + u32 mode:2; + u32 ctg_code:8; + u32 src_num:4; + u32 ch_num:4; + u32 samp_freq:4; /* CH_STATUS_MAP_XXX */ + u32 clk_acc:2; + u32 rsvd:2; + } regx; + u32 regval; +}; + +/* samp_freq values - Sampling rate as per IEC60958 Ver 3 */ +#define CH_STATUS_MAP_32KHZ 0x3 +#define CH_STATUS_MAP_44KHZ 0x0 +#define CH_STATUS_MAP_48KHZ 0x2 +#define CH_STATUS_MAP_88KHZ 0x8 +#define CH_STATUS_MAP_96KHZ 0xA +#define CH_STATUS_MAP_176KHZ 0xC +#define CH_STATUS_MAP_192KHZ 0xE + +/* Audio Channel Status 1 Attributes */ +union aud_ch_status_1 { + struct { + u32 max_wrd_len:1; + u32 wrd_len:3; + u32 rsvd:28; + } regx; + u32 regval; +}; + +#define MAX_SMPL_WIDTH_20 0x0 +#define MAX_SMPL_WIDTH_24 0x1 +#define SMPL_WIDTH_16BITS 0x1 +#define SMPL_WIDTH_24BITS 0x5 + +/* CTS register */ +union aud_hdmi_cts { + struct { + u32 cts_val:24; + u32 en_cts_prog:1; + u32 rsvd:7; + } regx; + u32 regval; +}; + +/* N register */ +union aud_hdmi_n_enable { + struct { + u32 n_val:24; + u32 en_n_prog:1; + u32 rsvd:7; + } regx; + u32 regval; +}; + +/* Audio Buffer configurations */ +union aud_buf_config { + struct { + u32 audio_fifo_watermark:8; + u32 dma_fifo_watermark:3; + u32 rsvd0:5; + u32 aud_delay:8; + u32 rsvd1:8; + } regx; + u32 regval; +}; + +#define FIFO_THRESHOLD 0xFE +#define DMA_FIFO_THRESHOLD 0x7 + +/* Audio Sample Swapping offset */ +union aud_buf_ch_swap { + struct { + u32 first_0:3; + u32 second_0:3; + u32 first_1:3; + u32 second_1:3; + u32 first_2:3; + u32 second_2:3; + u32 first_3:3; + u32 second_3:3; + u32 rsvd:8; + } regx; + u32 regval; +}; + +#define SWAP_LFE_CENTER 0x00fac4c8 /* octal 76543210 */ + +/* Address for Audio Buffer */ +union aud_buf_addr { + struct { + u32 valid:1; + u32 intr_en:1; + u32 rsvd:4; + u32 addr:26; + } regx; + u32 regval; +}; + +#define AUD_BUF_VALID (1U << 0) +#define AUD_BUF_INTR_EN (1U << 1) + +/* Length of Audio Buffer */ +union aud_buf_len { + struct { + u32 buf_len:20; + u32 rsvd:12; + } regx; + u32 regval; +}; + +/* Audio Control State Register offset */ +union aud_ctrl_st { + struct { + u32 ram_addr:4; + u32 eld_ack:1; + u32 eld_addr:4; + u32 eld_buf_size:5; + u32 eld_valid:1; + u32 cp_ready:1; + u32 dip_freq:2; + u32 dip_idx:3; + u32 dip_en_sta:4; + u32 rsvd:7; + } regx; + u32 regval; +}; + +/* Audio HDMI Widget Data Island Packet offset */ +union aud_info_frame1 { + struct { + u32 pkt_type:8; + u32 ver_num:8; + u32 len:5; + u32 rsvd:11; + } regx; + u32 regval; +}; + +#define HDMI_INFO_FRAME_WORD1 0x000a0184 +#define DP_INFO_FRAME_WORD1 0x00441b84 + +/* DIP frame 2 */ +union aud_info_frame2 { + struct { + u32 chksum:8; + u32 chnl_cnt:3; + u32 rsvd0:1; + u32 coding_type:4; + u32 smpl_size:2; + u32 smpl_freq:3; + u32 rsvd1:3; + u32 format:8; + } regx; + u32 regval; +}; + +/* DIP frame 3 */ +union aud_info_frame3 { + struct { + u32 chnl_alloc:8; + u32 rsvd0:3; + u32 lsv:4; + u32 dm_inh:1; + u32 rsvd1:16; + } regx; + u32 regval; +}; + +#define VALID_DIP_WORDS 3 + +/* AUD_HDMI_STATUS bits */ +#define HDMI_AUDIO_UNDERRUN (1U << 31) +#define HDMI_AUDIO_BUFFER_DONE (1U << 29) + +/* AUD_HDMI_STATUS register mask */ +#define AUD_HDMI_STATUS_MASK_UNDERRUN 0xC0000000 +#define AUD_HDMI_STATUS_MASK_SRDBG 0x00000002 +#define AUD_HDMI_STATUSG_MASK_FUNCRST 0x00000001 + +#endif diff --git a/sound/xen/Kconfig b/sound/xen/Kconfig new file mode 100644 index 000000000..4f1fceea8 --- /dev/null +++ b/sound/xen/Kconfig @@ -0,0 +1,10 @@ +# ALSA Xen drivers + +config SND_XEN_FRONTEND + tristate "Xen para-virtualized sound frontend driver" + depends on XEN + select SND_PCM + select XEN_XENBUS_FRONTEND + help + Choose this option if you want to enable a para-virtualized + frontend sound driver for Xen guest OSes. diff --git a/sound/xen/Makefile b/sound/xen/Makefile new file mode 100644 index 000000000..1e6470ecc --- /dev/null +++ b/sound/xen/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 OR MIT + +snd_xen_front-objs := xen_snd_front.o \ + xen_snd_front_cfg.o \ + xen_snd_front_evtchnl.o \ + xen_snd_front_shbuf.o \ + xen_snd_front_alsa.o + +obj-$(CONFIG_SND_XEN_FRONTEND) += snd_xen_front.o diff --git a/sound/xen/xen_snd_front.c b/sound/xen/xen_snd_front.c new file mode 100644 index 000000000..b089b13b5 --- /dev/null +++ b/sound/xen/xen_snd_front.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include +#include + +#include +#include +#include +#include + +#include + +#include "xen_snd_front.h" +#include "xen_snd_front_alsa.h" +#include "xen_snd_front_evtchnl.h" +#include "xen_snd_front_shbuf.h" + +static struct xensnd_req * +be_stream_prepare_req(struct xen_snd_front_evtchnl *evtchnl, u8 operation) +{ + struct xensnd_req *req; + + req = RING_GET_REQUEST(&evtchnl->u.req.ring, + evtchnl->u.req.ring.req_prod_pvt); + req->operation = operation; + req->id = evtchnl->evt_next_id++; + evtchnl->evt_id = req->id; + return req; +} + +static int be_stream_do_io(struct xen_snd_front_evtchnl *evtchnl) +{ + if (unlikely(evtchnl->state != EVTCHNL_STATE_CONNECTED)) + return -EIO; + + reinit_completion(&evtchnl->u.req.completion); + xen_snd_front_evtchnl_flush(evtchnl); + return 0; +} + +static int be_stream_wait_io(struct xen_snd_front_evtchnl *evtchnl) +{ + if (wait_for_completion_timeout(&evtchnl->u.req.completion, + msecs_to_jiffies(VSND_WAIT_BACK_MS)) <= 0) + return -ETIMEDOUT; + + return evtchnl->u.req.resp_status; +} + +int xen_snd_front_stream_query_hw_param(struct xen_snd_front_evtchnl *evtchnl, + struct xensnd_query_hw_param *hw_param_req, + struct xensnd_query_hw_param *hw_param_resp) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_HW_PARAM_QUERY); + req->op.hw_param = *hw_param_req; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + if (ret == 0) + *hw_param_resp = evtchnl->u.req.resp.hw_param; + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_prepare(struct xen_snd_front_evtchnl *evtchnl, + struct xen_snd_front_shbuf *sh_buf, + u8 format, unsigned int channels, + unsigned int rate, u32 buffer_sz, + u32 period_sz) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_OPEN); + req->op.open.pcm_format = format; + req->op.open.pcm_channels = channels; + req->op.open.pcm_rate = rate; + req->op.open.buffer_sz = buffer_sz; + req->op.open.period_sz = period_sz; + req->op.open.gref_directory = xen_snd_front_shbuf_get_dir_start(sh_buf); + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_close(struct xen_snd_front_evtchnl *evtchnl) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_CLOSE); + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_write(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_WRITE); + req->op.rw.length = count; + req->op.rw.offset = pos; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_read(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_READ); + req->op.rw.length = count; + req->op.rw.offset = pos; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +int xen_snd_front_stream_trigger(struct xen_snd_front_evtchnl *evtchnl, + int type) +{ + struct xensnd_req *req; + int ret; + + mutex_lock(&evtchnl->u.req.req_io_lock); + + mutex_lock(&evtchnl->ring_io_lock); + req = be_stream_prepare_req(evtchnl, XENSND_OP_TRIGGER); + req->op.trigger.type = type; + mutex_unlock(&evtchnl->ring_io_lock); + + ret = be_stream_do_io(evtchnl); + + if (ret == 0) + ret = be_stream_wait_io(evtchnl); + + mutex_unlock(&evtchnl->u.req.req_io_lock); + return ret; +} + +static void xen_snd_drv_fini(struct xen_snd_front_info *front_info) +{ + xen_snd_front_alsa_fini(front_info); + xen_snd_front_evtchnl_free_all(front_info); +} + +static int sndback_initwait(struct xen_snd_front_info *front_info) +{ + int num_streams; + int ret; + + ret = xen_snd_front_cfg_card(front_info, &num_streams); + if (ret < 0) + return ret; + + /* create event channels for all streams and publish */ + ret = xen_snd_front_evtchnl_create_all(front_info, num_streams); + if (ret < 0) + return ret; + + return xen_snd_front_evtchnl_publish_all(front_info); +} + +static int sndback_connect(struct xen_snd_front_info *front_info) +{ + return xen_snd_front_alsa_init(front_info); +} + +static void sndback_disconnect(struct xen_snd_front_info *front_info) +{ + xen_snd_drv_fini(front_info); + xenbus_switch_state(front_info->xb_dev, XenbusStateInitialising); +} + +static void sndback_changed(struct xenbus_device *xb_dev, + enum xenbus_state backend_state) +{ + struct xen_snd_front_info *front_info = dev_get_drvdata(&xb_dev->dev); + int ret; + + dev_dbg(&xb_dev->dev, "Backend state is %s, front is %s\n", + xenbus_strstate(backend_state), + xenbus_strstate(xb_dev->state)); + + switch (backend_state) { + case XenbusStateReconfiguring: + /* fall through */ + case XenbusStateReconfigured: + /* fall through */ + case XenbusStateInitialised: + /* fall through */ + break; + + case XenbusStateInitialising: + /* Recovering after backend unexpected closure. */ + sndback_disconnect(front_info); + break; + + case XenbusStateInitWait: + /* Recovering after backend unexpected closure. */ + sndback_disconnect(front_info); + + ret = sndback_initwait(front_info); + if (ret < 0) + xenbus_dev_fatal(xb_dev, ret, "initializing frontend"); + else + xenbus_switch_state(xb_dev, XenbusStateInitialised); + break; + + case XenbusStateConnected: + if (xb_dev->state != XenbusStateInitialised) + break; + + ret = sndback_connect(front_info); + if (ret < 0) + xenbus_dev_fatal(xb_dev, ret, "initializing frontend"); + else + xenbus_switch_state(xb_dev, XenbusStateConnected); + break; + + case XenbusStateClosing: + /* + * In this state backend starts freeing resources, + * so let it go into closed state first, so we can also + * remove ours. + */ + break; + + case XenbusStateUnknown: + /* fall through */ + case XenbusStateClosed: + if (xb_dev->state == XenbusStateClosed) + break; + + sndback_disconnect(front_info); + break; + } +} + +static int xen_drv_probe(struct xenbus_device *xb_dev, + const struct xenbus_device_id *id) +{ + struct xen_snd_front_info *front_info; + + front_info = devm_kzalloc(&xb_dev->dev, + sizeof(*front_info), GFP_KERNEL); + if (!front_info) + return -ENOMEM; + + front_info->xb_dev = xb_dev; + dev_set_drvdata(&xb_dev->dev, front_info); + + return xenbus_switch_state(xb_dev, XenbusStateInitialising); +} + +static int xen_drv_remove(struct xenbus_device *dev) +{ + struct xen_snd_front_info *front_info = dev_get_drvdata(&dev->dev); + int to = 100; + + xenbus_switch_state(dev, XenbusStateClosing); + + /* + * On driver removal it is disconnected from XenBus, + * so no backend state change events come via .otherend_changed + * callback. This prevents us from exiting gracefully, e.g. + * signaling the backend to free event channels, waiting for its + * state to change to XenbusStateClosed and cleaning at our end. + * Normally when front driver removed backend will finally go into + * XenbusStateInitWait state. + * + * Workaround: read backend's state manually and wait with time-out. + */ + while ((xenbus_read_unsigned(front_info->xb_dev->otherend, "state", + XenbusStateUnknown) != XenbusStateInitWait) && + --to) + msleep(10); + + if (!to) { + unsigned int state; + + state = xenbus_read_unsigned(front_info->xb_dev->otherend, + "state", XenbusStateUnknown); + pr_err("Backend state is %s while removing driver\n", + xenbus_strstate(state)); + } + + xen_snd_drv_fini(front_info); + xenbus_frontend_closed(dev); + return 0; +} + +static const struct xenbus_device_id xen_drv_ids[] = { + { XENSND_DRIVER_NAME }, + { "" } +}; + +static struct xenbus_driver xen_driver = { + .ids = xen_drv_ids, + .probe = xen_drv_probe, + .remove = xen_drv_remove, + .otherend_changed = sndback_changed, +}; + +static int __init xen_drv_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + if (!xen_has_pv_devices()) + return -ENODEV; + + /* At the moment we only support case with XEN_PAGE_SIZE == PAGE_SIZE */ + if (XEN_PAGE_SIZE != PAGE_SIZE) { + pr_err(XENSND_DRIVER_NAME ": different kernel and Xen page sizes are not supported: XEN_PAGE_SIZE (%lu) != PAGE_SIZE (%lu)\n", + XEN_PAGE_SIZE, PAGE_SIZE); + return -ENODEV; + } + + pr_info("Initialising Xen " XENSND_DRIVER_NAME " frontend driver\n"); + return xenbus_register_frontend(&xen_driver); +} + +static void __exit xen_drv_fini(void) +{ + pr_info("Unregistering Xen " XENSND_DRIVER_NAME " frontend driver\n"); + xenbus_unregister_driver(&xen_driver); +} + +module_init(xen_drv_init); +module_exit(xen_drv_fini); + +MODULE_DESCRIPTION("Xen virtual sound device frontend"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("xen:" XENSND_DRIVER_NAME); +MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual soundcard}}"); diff --git a/sound/xen/xen_snd_front.h b/sound/xen/xen_snd_front.h new file mode 100644 index 000000000..a2ea2463b --- /dev/null +++ b/sound/xen/xen_snd_front.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_H +#define __XEN_SND_FRONT_H + +#include "xen_snd_front_cfg.h" + +struct xen_snd_front_card_info; +struct xen_snd_front_evtchnl; +struct xen_snd_front_evtchnl_pair; +struct xen_snd_front_shbuf; +struct xensnd_query_hw_param; + +struct xen_snd_front_info { + struct xenbus_device *xb_dev; + + struct xen_snd_front_card_info *card_info; + + int num_evt_pairs; + struct xen_snd_front_evtchnl_pair *evt_pairs; + + struct xen_front_cfg_card cfg; +}; + +int xen_snd_front_stream_query_hw_param(struct xen_snd_front_evtchnl *evtchnl, + struct xensnd_query_hw_param *hw_param_req, + struct xensnd_query_hw_param *hw_param_resp); + +int xen_snd_front_stream_prepare(struct xen_snd_front_evtchnl *evtchnl, + struct xen_snd_front_shbuf *sh_buf, + u8 format, unsigned int channels, + unsigned int rate, u32 buffer_sz, + u32 period_sz); + +int xen_snd_front_stream_close(struct xen_snd_front_evtchnl *evtchnl); + +int xen_snd_front_stream_write(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count); + +int xen_snd_front_stream_read(struct xen_snd_front_evtchnl *evtchnl, + unsigned long pos, unsigned long count); + +int xen_snd_front_stream_trigger(struct xen_snd_front_evtchnl *evtchnl, + int type); + +#endif /* __XEN_SND_FRONT_H */ diff --git a/sound/xen/xen_snd_front_alsa.c b/sound/xen/xen_snd_front_alsa.c new file mode 100644 index 000000000..129180e17 --- /dev/null +++ b/sound/xen/xen_snd_front_alsa.c @@ -0,0 +1,822 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include + +#include +#include +#include + +#include + +#include "xen_snd_front.h" +#include "xen_snd_front_alsa.h" +#include "xen_snd_front_cfg.h" +#include "xen_snd_front_evtchnl.h" +#include "xen_snd_front_shbuf.h" + +struct xen_snd_front_pcm_stream_info { + struct xen_snd_front_info *front_info; + struct xen_snd_front_evtchnl_pair *evt_pair; + struct xen_snd_front_shbuf sh_buf; + int index; + + bool is_open; + struct snd_pcm_hardware pcm_hw; + + /* Number of processed frames as reported by the backend. */ + snd_pcm_uframes_t be_cur_frame; + /* Current HW pointer to be reported via .period callback. */ + atomic_t hw_ptr; + /* Modulo of the number of processed frames - for period detection. */ + u32 out_frames; +}; + +struct xen_snd_front_pcm_instance_info { + struct xen_snd_front_card_info *card_info; + struct snd_pcm *pcm; + struct snd_pcm_hardware pcm_hw; + int num_pcm_streams_pb; + struct xen_snd_front_pcm_stream_info *streams_pb; + int num_pcm_streams_cap; + struct xen_snd_front_pcm_stream_info *streams_cap; +}; + +struct xen_snd_front_card_info { + struct xen_snd_front_info *front_info; + struct snd_card *card; + struct snd_pcm_hardware pcm_hw; + int num_pcm_instances; + struct xen_snd_front_pcm_instance_info *pcm_instances; +}; + +struct alsa_sndif_sample_format { + u8 sndif; + snd_pcm_format_t alsa; +}; + +struct alsa_sndif_hw_param { + u8 sndif; + snd_pcm_hw_param_t alsa; +}; + +static const struct alsa_sndif_sample_format ALSA_SNDIF_FORMATS[] = { + { + .sndif = XENSND_PCM_FORMAT_U8, + .alsa = SNDRV_PCM_FORMAT_U8 + }, + { + .sndif = XENSND_PCM_FORMAT_S8, + .alsa = SNDRV_PCM_FORMAT_S8 + }, + { + .sndif = XENSND_PCM_FORMAT_U16_LE, + .alsa = SNDRV_PCM_FORMAT_U16_LE + }, + { + .sndif = XENSND_PCM_FORMAT_U16_BE, + .alsa = SNDRV_PCM_FORMAT_U16_BE + }, + { + .sndif = XENSND_PCM_FORMAT_S16_LE, + .alsa = SNDRV_PCM_FORMAT_S16_LE + }, + { + .sndif = XENSND_PCM_FORMAT_S16_BE, + .alsa = SNDRV_PCM_FORMAT_S16_BE + }, + { + .sndif = XENSND_PCM_FORMAT_U24_LE, + .alsa = SNDRV_PCM_FORMAT_U24_LE + }, + { + .sndif = XENSND_PCM_FORMAT_U24_BE, + .alsa = SNDRV_PCM_FORMAT_U24_BE + }, + { + .sndif = XENSND_PCM_FORMAT_S24_LE, + .alsa = SNDRV_PCM_FORMAT_S24_LE + }, + { + .sndif = XENSND_PCM_FORMAT_S24_BE, + .alsa = SNDRV_PCM_FORMAT_S24_BE + }, + { + .sndif = XENSND_PCM_FORMAT_U32_LE, + .alsa = SNDRV_PCM_FORMAT_U32_LE + }, + { + .sndif = XENSND_PCM_FORMAT_U32_BE, + .alsa = SNDRV_PCM_FORMAT_U32_BE + }, + { + .sndif = XENSND_PCM_FORMAT_S32_LE, + .alsa = SNDRV_PCM_FORMAT_S32_LE + }, + { + .sndif = XENSND_PCM_FORMAT_S32_BE, + .alsa = SNDRV_PCM_FORMAT_S32_BE + }, + { + .sndif = XENSND_PCM_FORMAT_A_LAW, + .alsa = SNDRV_PCM_FORMAT_A_LAW + }, + { + .sndif = XENSND_PCM_FORMAT_MU_LAW, + .alsa = SNDRV_PCM_FORMAT_MU_LAW + }, + { + .sndif = XENSND_PCM_FORMAT_F32_LE, + .alsa = SNDRV_PCM_FORMAT_FLOAT_LE + }, + { + .sndif = XENSND_PCM_FORMAT_F32_BE, + .alsa = SNDRV_PCM_FORMAT_FLOAT_BE + }, + { + .sndif = XENSND_PCM_FORMAT_F64_LE, + .alsa = SNDRV_PCM_FORMAT_FLOAT64_LE + }, + { + .sndif = XENSND_PCM_FORMAT_F64_BE, + .alsa = SNDRV_PCM_FORMAT_FLOAT64_BE + }, + { + .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE, + .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE + }, + { + .sndif = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE, + .alsa = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE + }, + { + .sndif = XENSND_PCM_FORMAT_IMA_ADPCM, + .alsa = SNDRV_PCM_FORMAT_IMA_ADPCM + }, + { + .sndif = XENSND_PCM_FORMAT_MPEG, + .alsa = SNDRV_PCM_FORMAT_MPEG + }, + { + .sndif = XENSND_PCM_FORMAT_GSM, + .alsa = SNDRV_PCM_FORMAT_GSM + }, +}; + +static int to_sndif_format(snd_pcm_format_t format) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) + if (ALSA_SNDIF_FORMATS[i].alsa == format) + return ALSA_SNDIF_FORMATS[i].sndif; + + return -EINVAL; +} + +static u64 to_sndif_formats_mask(u64 alsa_formats) +{ + u64 mask; + int i; + + mask = 0; + for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) + if (pcm_format_to_bits(ALSA_SNDIF_FORMATS[i].alsa) & alsa_formats) + mask |= 1 << ALSA_SNDIF_FORMATS[i].sndif; + + return mask; +} + +static u64 to_alsa_formats_mask(u64 sndif_formats) +{ + u64 mask; + int i; + + mask = 0; + for (i = 0; i < ARRAY_SIZE(ALSA_SNDIF_FORMATS); i++) + if (1 << ALSA_SNDIF_FORMATS[i].sndif & sndif_formats) + mask |= pcm_format_to_bits(ALSA_SNDIF_FORMATS[i].alsa); + + return mask; +} + +static void stream_clear(struct xen_snd_front_pcm_stream_info *stream) +{ + stream->is_open = false; + stream->be_cur_frame = 0; + stream->out_frames = 0; + atomic_set(&stream->hw_ptr, 0); + xen_snd_front_evtchnl_pair_clear(stream->evt_pair); + xen_snd_front_shbuf_clear(&stream->sh_buf); +} + +static void stream_free(struct xen_snd_front_pcm_stream_info *stream) +{ + xen_snd_front_shbuf_free(&stream->sh_buf); + stream_clear(stream); +} + +static struct xen_snd_front_pcm_stream_info * +stream_get(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_instance_info *pcm_instance = + snd_pcm_substream_chip(substream); + struct xen_snd_front_pcm_stream_info *stream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + stream = &pcm_instance->streams_pb[substream->number]; + else + stream = &pcm_instance->streams_cap[substream->number]; + + return stream; +} + +static int alsa_hw_rule(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct xen_snd_front_pcm_stream_info *stream = rule->private; + struct device *dev = &stream->front_info->xb_dev->dev; + struct snd_mask *formats = + hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_interval *rates = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *period = + hw_param_interval(params, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + struct snd_interval *buffer = + hw_param_interval(params, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE); + struct xensnd_query_hw_param req; + struct xensnd_query_hw_param resp; + struct snd_interval interval; + struct snd_mask mask; + u64 sndif_formats; + int changed, ret; + + /* Collect all the values we need for the query. */ + + req.formats = to_sndif_formats_mask((u64)formats->bits[0] | + (u64)(formats->bits[1]) << 32); + + req.rates.min = rates->min; + req.rates.max = rates->max; + + req.channels.min = channels->min; + req.channels.max = channels->max; + + req.buffer.min = buffer->min; + req.buffer.max = buffer->max; + + req.period.min = period->min; + req.period.max = period->max; + + ret = xen_snd_front_stream_query_hw_param(&stream->evt_pair->req, + &req, &resp); + if (ret < 0) { + /* Check if this is due to backend communication error. */ + if (ret == -EIO || ret == -ETIMEDOUT) + dev_err(dev, "Failed to query ALSA HW parameters\n"); + return ret; + } + + /* Refine HW parameters after the query. */ + changed = 0; + + sndif_formats = to_alsa_formats_mask(resp.formats); + snd_mask_none(&mask); + mask.bits[0] = (u32)sndif_formats; + mask.bits[1] = (u32)(sndif_formats >> 32); + ret = snd_mask_refine(formats, &mask); + if (ret < 0) + return ret; + changed |= ret; + + interval.openmin = 0; + interval.openmax = 0; + interval.integer = 1; + + interval.min = resp.rates.min; + interval.max = resp.rates.max; + ret = snd_interval_refine(rates, &interval); + if (ret < 0) + return ret; + changed |= ret; + + interval.min = resp.channels.min; + interval.max = resp.channels.max; + ret = snd_interval_refine(channels, &interval); + if (ret < 0) + return ret; + changed |= ret; + + interval.min = resp.buffer.min; + interval.max = resp.buffer.max; + ret = snd_interval_refine(buffer, &interval); + if (ret < 0) + return ret; + changed |= ret; + + interval.min = resp.period.min; + interval.max = resp.period.max; + ret = snd_interval_refine(period, &interval); + if (ret < 0) + return ret; + changed |= ret; + + return changed; +} + +static int alsa_open(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_instance_info *pcm_instance = + snd_pcm_substream_chip(substream); + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct xen_snd_front_info *front_info = + pcm_instance->card_info->front_info; + struct device *dev = &front_info->xb_dev->dev; + int ret; + + /* + * Return our HW properties: override defaults with those configured + * via XenStore. + */ + runtime->hw = stream->pcm_hw; + runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE); + runtime->hw.info |= SNDRV_PCM_INFO_INTERLEAVED; + + stream->evt_pair = &front_info->evt_pairs[stream->index]; + + stream->front_info = front_info; + + stream->evt_pair->evt.u.evt.substream = substream; + + stream_clear(stream); + + xen_snd_front_evtchnl_pair_set_connected(stream->evt_pair, true); + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_FORMAT\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_RATE\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_CHANNELS\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_PERIOD_SIZE\n"); + return ret; + } + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + alsa_hw_rule, stream, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1); + if (ret) { + dev_err(dev, "Failed to add HW rule for SNDRV_PCM_HW_PARAM_BUFFER_SIZE\n"); + return ret; + } + + return 0; +} + +static int alsa_close(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + xen_snd_front_evtchnl_pair_set_connected(stream->evt_pair, false); + return 0; +} + +static int alsa_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + /* + * This callback may be called multiple times, + * so free the previously allocated shared buffer if any. + */ + stream_free(stream); + + ret = xen_snd_front_shbuf_alloc(stream->front_info->xb_dev, + &stream->sh_buf, + params_buffer_bytes(params)); + if (ret < 0) { + stream_free(stream); + dev_err(&stream->front_info->xb_dev->dev, + "Failed to allocate buffers for stream with index %d\n", + stream->index); + return ret; + } + + return 0; +} + +static int alsa_hw_free(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + ret = xen_snd_front_stream_close(&stream->evt_pair->req); + stream_free(stream); + return ret; +} + +static int alsa_prepare(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (!stream->is_open) { + struct snd_pcm_runtime *runtime = substream->runtime; + u8 sndif_format; + int ret; + + ret = to_sndif_format(runtime->format); + if (ret < 0) { + dev_err(&stream->front_info->xb_dev->dev, + "Unsupported sample format: %d\n", + runtime->format); + return ret; + } + sndif_format = ret; + + ret = xen_snd_front_stream_prepare(&stream->evt_pair->req, + &stream->sh_buf, + sndif_format, + runtime->channels, + runtime->rate, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream)); + if (ret < 0) + return ret; + + stream->is_open = true; + } + + return 0; +} + +static int alsa_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int type; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + type = XENSND_OP_TRIGGER_START; + break; + + case SNDRV_PCM_TRIGGER_RESUME: + type = XENSND_OP_TRIGGER_RESUME; + break; + + case SNDRV_PCM_TRIGGER_STOP: + type = XENSND_OP_TRIGGER_STOP; + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + type = XENSND_OP_TRIGGER_PAUSE; + break; + + default: + return -EINVAL; + } + + return xen_snd_front_stream_trigger(&stream->evt_pair->req, type); +} + +void xen_snd_front_alsa_handle_cur_pos(struct xen_snd_front_evtchnl *evtchnl, + u64 pos_bytes) +{ + struct snd_pcm_substream *substream = evtchnl->u.evt.substream; + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + snd_pcm_uframes_t delta, new_hw_ptr, cur_frame; + + cur_frame = bytes_to_frames(substream->runtime, pos_bytes); + + delta = cur_frame - stream->be_cur_frame; + stream->be_cur_frame = cur_frame; + + new_hw_ptr = (snd_pcm_uframes_t)atomic_read(&stream->hw_ptr); + new_hw_ptr = (new_hw_ptr + delta) % substream->runtime->buffer_size; + atomic_set(&stream->hw_ptr, (int)new_hw_ptr); + + stream->out_frames += delta; + if (stream->out_frames > substream->runtime->period_size) { + stream->out_frames %= substream->runtime->period_size; + snd_pcm_period_elapsed(substream); + } +} + +static snd_pcm_uframes_t alsa_pointer(struct snd_pcm_substream *substream) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + return (snd_pcm_uframes_t)atomic_read(&stream->hw_ptr); +} + +static int alsa_pb_copy_user(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void __user *src, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + if (copy_from_user(stream->sh_buf.buffer + pos, src, count)) + return -EFAULT; + + return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count); +} + +static int alsa_pb_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void *src, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + memcpy(stream->sh_buf.buffer + pos, src, count); + + return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count); +} + +static int alsa_cap_copy_user(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void __user *dst, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + ret = xen_snd_front_stream_read(&stream->evt_pair->req, pos, count); + if (ret < 0) + return ret; + + return copy_to_user(dst, stream->sh_buf.buffer + pos, count) ? + -EFAULT : 0; +} + +static int alsa_cap_copy_kernel(struct snd_pcm_substream *substream, + int channel, unsigned long pos, void *dst, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + int ret; + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + ret = xen_snd_front_stream_read(&stream->evt_pair->req, pos, count); + if (ret < 0) + return ret; + + memcpy(dst, stream->sh_buf.buffer + pos, count); + + return 0; +} + +static int alsa_pb_fill_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long count) +{ + struct xen_snd_front_pcm_stream_info *stream = stream_get(substream); + + if (unlikely(pos + count > stream->sh_buf.buffer_sz)) + return -EINVAL; + + memset(stream->sh_buf.buffer + pos, 0, count); + + return xen_snd_front_stream_write(&stream->evt_pair->req, pos, count); +} + +/* + * FIXME: The mmaped data transfer is asynchronous and there is no + * ack signal from user-space when it is done. This is the + * reason it is not implemented in the PV driver as we do need + * to know when the buffer can be transferred to the backend. + */ + +static struct snd_pcm_ops snd_drv_alsa_playback_ops = { + .open = alsa_open, + .close = alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = alsa_hw_params, + .hw_free = alsa_hw_free, + .prepare = alsa_prepare, + .trigger = alsa_trigger, + .pointer = alsa_pointer, + .copy_user = alsa_pb_copy_user, + .copy_kernel = alsa_pb_copy_kernel, + .fill_silence = alsa_pb_fill_silence, +}; + +static struct snd_pcm_ops snd_drv_alsa_capture_ops = { + .open = alsa_open, + .close = alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = alsa_hw_params, + .hw_free = alsa_hw_free, + .prepare = alsa_prepare, + .trigger = alsa_trigger, + .pointer = alsa_pointer, + .copy_user = alsa_cap_copy_user, + .copy_kernel = alsa_cap_copy_kernel, +}; + +static int new_pcm_instance(struct xen_snd_front_card_info *card_info, + struct xen_front_cfg_pcm_instance *instance_cfg, + struct xen_snd_front_pcm_instance_info *pcm_instance_info) +{ + struct snd_pcm *pcm; + int ret, i; + + dev_dbg(&card_info->front_info->xb_dev->dev, + "New PCM device \"%s\" with id %d playback %d capture %d", + instance_cfg->name, + instance_cfg->device_id, + instance_cfg->num_streams_pb, + instance_cfg->num_streams_cap); + + pcm_instance_info->card_info = card_info; + + pcm_instance_info->pcm_hw = instance_cfg->pcm_hw; + + if (instance_cfg->num_streams_pb) { + pcm_instance_info->streams_pb = + devm_kcalloc(&card_info->card->card_dev, + instance_cfg->num_streams_pb, + sizeof(struct xen_snd_front_pcm_stream_info), + GFP_KERNEL); + if (!pcm_instance_info->streams_pb) + return -ENOMEM; + } + + if (instance_cfg->num_streams_cap) { + pcm_instance_info->streams_cap = + devm_kcalloc(&card_info->card->card_dev, + instance_cfg->num_streams_cap, + sizeof(struct xen_snd_front_pcm_stream_info), + GFP_KERNEL); + if (!pcm_instance_info->streams_cap) + return -ENOMEM; + } + + pcm_instance_info->num_pcm_streams_pb = + instance_cfg->num_streams_pb; + pcm_instance_info->num_pcm_streams_cap = + instance_cfg->num_streams_cap; + + for (i = 0; i < pcm_instance_info->num_pcm_streams_pb; i++) { + pcm_instance_info->streams_pb[i].pcm_hw = + instance_cfg->streams_pb[i].pcm_hw; + pcm_instance_info->streams_pb[i].index = + instance_cfg->streams_pb[i].index; + } + + for (i = 0; i < pcm_instance_info->num_pcm_streams_cap; i++) { + pcm_instance_info->streams_cap[i].pcm_hw = + instance_cfg->streams_cap[i].pcm_hw; + pcm_instance_info->streams_cap[i].index = + instance_cfg->streams_cap[i].index; + } + + ret = snd_pcm_new(card_info->card, instance_cfg->name, + instance_cfg->device_id, + instance_cfg->num_streams_pb, + instance_cfg->num_streams_cap, + &pcm); + if (ret < 0) + return ret; + + pcm->private_data = pcm_instance_info; + pcm->info_flags = 0; + /* we want to handle all PCM operations in non-atomic context */ + pcm->nonatomic = true; + strncpy(pcm->name, "Virtual card PCM", sizeof(pcm->name)); + + if (instance_cfg->num_streams_pb) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_drv_alsa_playback_ops); + + if (instance_cfg->num_streams_cap) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_drv_alsa_capture_ops); + + pcm_instance_info->pcm = pcm; + return 0; +} + +int xen_snd_front_alsa_init(struct xen_snd_front_info *front_info) +{ + struct device *dev = &front_info->xb_dev->dev; + struct xen_front_cfg_card *cfg = &front_info->cfg; + struct xen_snd_front_card_info *card_info; + struct snd_card *card; + int ret, i; + + dev_dbg(dev, "Creating virtual sound card\n"); + + ret = snd_card_new(dev, 0, XENSND_DRIVER_NAME, THIS_MODULE, + sizeof(struct xen_snd_front_card_info), &card); + if (ret < 0) + return ret; + + card_info = card->private_data; + card_info->front_info = front_info; + front_info->card_info = card_info; + card_info->card = card; + card_info->pcm_instances = + devm_kcalloc(dev, cfg->num_pcm_instances, + sizeof(struct xen_snd_front_pcm_instance_info), + GFP_KERNEL); + if (!card_info->pcm_instances) { + ret = -ENOMEM; + goto fail; + } + + card_info->num_pcm_instances = cfg->num_pcm_instances; + card_info->pcm_hw = cfg->pcm_hw; + + for (i = 0; i < cfg->num_pcm_instances; i++) { + ret = new_pcm_instance(card_info, &cfg->pcm_instances[i], + &card_info->pcm_instances[i]); + if (ret < 0) + goto fail; + } + + strncpy(card->driver, XENSND_DRIVER_NAME, sizeof(card->driver)); + strncpy(card->shortname, cfg->name_short, sizeof(card->shortname)); + strncpy(card->longname, cfg->name_long, sizeof(card->longname)); + + ret = snd_card_register(card); + if (ret < 0) + goto fail; + + return 0; + +fail: + snd_card_free(card); + return ret; +} + +void xen_snd_front_alsa_fini(struct xen_snd_front_info *front_info) +{ + struct xen_snd_front_card_info *card_info; + struct snd_card *card; + + card_info = front_info->card_info; + if (!card_info) + return; + + card = card_info->card; + if (!card) + return; + + dev_dbg(&front_info->xb_dev->dev, "Removing virtual sound card %d\n", + card->number); + snd_card_free(card); + + /* Card_info will be freed when destroying front_info->xb_dev->dev. */ + card_info->card = NULL; +} diff --git a/sound/xen/xen_snd_front_alsa.h b/sound/xen/xen_snd_front_alsa.h new file mode 100644 index 000000000..18abd9eec --- /dev/null +++ b/sound/xen/xen_snd_front_alsa.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_ALSA_H +#define __XEN_SND_FRONT_ALSA_H + +struct xen_snd_front_info; + +int xen_snd_front_alsa_init(struct xen_snd_front_info *front_info); + +void xen_snd_front_alsa_fini(struct xen_snd_front_info *front_info); + +void xen_snd_front_alsa_handle_cur_pos(struct xen_snd_front_evtchnl *evtchnl, + u64 pos_bytes); + +#endif /* __XEN_SND_FRONT_ALSA_H */ diff --git a/sound/xen/xen_snd_front_cfg.c b/sound/xen/xen_snd_front_cfg.c new file mode 100644 index 000000000..eda077c80 --- /dev/null +++ b/sound/xen/xen_snd_front_cfg.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include + +#include + +#include "xen_snd_front.h" +#include "xen_snd_front_cfg.h" + +/* Maximum number of supported streams. */ +#define VSND_MAX_STREAM 8 + +struct cfg_hw_sample_rate { + const char *name; + unsigned int mask; + unsigned int value; +}; + +static const struct cfg_hw_sample_rate CFG_HW_SUPPORTED_RATES[] = { + { .name = "5512", .mask = SNDRV_PCM_RATE_5512, .value = 5512 }, + { .name = "8000", .mask = SNDRV_PCM_RATE_8000, .value = 8000 }, + { .name = "11025", .mask = SNDRV_PCM_RATE_11025, .value = 11025 }, + { .name = "16000", .mask = SNDRV_PCM_RATE_16000, .value = 16000 }, + { .name = "22050", .mask = SNDRV_PCM_RATE_22050, .value = 22050 }, + { .name = "32000", .mask = SNDRV_PCM_RATE_32000, .value = 32000 }, + { .name = "44100", .mask = SNDRV_PCM_RATE_44100, .value = 44100 }, + { .name = "48000", .mask = SNDRV_PCM_RATE_48000, .value = 48000 }, + { .name = "64000", .mask = SNDRV_PCM_RATE_64000, .value = 64000 }, + { .name = "96000", .mask = SNDRV_PCM_RATE_96000, .value = 96000 }, + { .name = "176400", .mask = SNDRV_PCM_RATE_176400, .value = 176400 }, + { .name = "192000", .mask = SNDRV_PCM_RATE_192000, .value = 192000 }, +}; + +struct cfg_hw_sample_format { + const char *name; + u64 mask; +}; + +static const struct cfg_hw_sample_format CFG_HW_SUPPORTED_FORMATS[] = { + { + .name = XENSND_PCM_FORMAT_U8_STR, + .mask = SNDRV_PCM_FMTBIT_U8 + }, + { + .name = XENSND_PCM_FORMAT_S8_STR, + .mask = SNDRV_PCM_FMTBIT_S8 + }, + { + .name = XENSND_PCM_FORMAT_U16_LE_STR, + .mask = SNDRV_PCM_FMTBIT_U16_LE + }, + { + .name = XENSND_PCM_FORMAT_U16_BE_STR, + .mask = SNDRV_PCM_FMTBIT_U16_BE + }, + { + .name = XENSND_PCM_FORMAT_S16_LE_STR, + .mask = SNDRV_PCM_FMTBIT_S16_LE + }, + { + .name = XENSND_PCM_FORMAT_S16_BE_STR, + .mask = SNDRV_PCM_FMTBIT_S16_BE + }, + { + .name = XENSND_PCM_FORMAT_U24_LE_STR, + .mask = SNDRV_PCM_FMTBIT_U24_LE + }, + { + .name = XENSND_PCM_FORMAT_U24_BE_STR, + .mask = SNDRV_PCM_FMTBIT_U24_BE + }, + { + .name = XENSND_PCM_FORMAT_S24_LE_STR, + .mask = SNDRV_PCM_FMTBIT_S24_LE + }, + { + .name = XENSND_PCM_FORMAT_S24_BE_STR, + .mask = SNDRV_PCM_FMTBIT_S24_BE + }, + { + .name = XENSND_PCM_FORMAT_U32_LE_STR, + .mask = SNDRV_PCM_FMTBIT_U32_LE + }, + { + .name = XENSND_PCM_FORMAT_U32_BE_STR, + .mask = SNDRV_PCM_FMTBIT_U32_BE + }, + { + .name = XENSND_PCM_FORMAT_S32_LE_STR, + .mask = SNDRV_PCM_FMTBIT_S32_LE + }, + { + .name = XENSND_PCM_FORMAT_S32_BE_STR, + .mask = SNDRV_PCM_FMTBIT_S32_BE + }, + { + .name = XENSND_PCM_FORMAT_A_LAW_STR, + .mask = SNDRV_PCM_FMTBIT_A_LAW + }, + { + .name = XENSND_PCM_FORMAT_MU_LAW_STR, + .mask = SNDRV_PCM_FMTBIT_MU_LAW + }, + { + .name = XENSND_PCM_FORMAT_F32_LE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT_LE + }, + { + .name = XENSND_PCM_FORMAT_F32_BE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT_BE + }, + { + .name = XENSND_PCM_FORMAT_F64_LE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT64_LE + }, + { + .name = XENSND_PCM_FORMAT_F64_BE_STR, + .mask = SNDRV_PCM_FMTBIT_FLOAT64_BE + }, + { + .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_LE_STR, + .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE + }, + { + .name = XENSND_PCM_FORMAT_IEC958_SUBFRAME_BE_STR, + .mask = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE + }, + { + .name = XENSND_PCM_FORMAT_IMA_ADPCM_STR, + .mask = SNDRV_PCM_FMTBIT_IMA_ADPCM + }, + { + .name = XENSND_PCM_FORMAT_MPEG_STR, + .mask = SNDRV_PCM_FMTBIT_MPEG + }, + { + .name = XENSND_PCM_FORMAT_GSM_STR, + .mask = SNDRV_PCM_FMTBIT_GSM + }, +}; + +static void cfg_hw_rates(char *list, unsigned int len, + const char *path, struct snd_pcm_hardware *pcm_hw) +{ + char *cur_rate; + unsigned int cur_mask; + unsigned int cur_value; + unsigned int rates; + unsigned int rate_min; + unsigned int rate_max; + int i; + + rates = 0; + rate_min = -1; + rate_max = 0; + while ((cur_rate = strsep(&list, XENSND_LIST_SEPARATOR))) { + for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_RATES); i++) + if (!strncasecmp(cur_rate, + CFG_HW_SUPPORTED_RATES[i].name, + XENSND_SAMPLE_RATE_MAX_LEN)) { + cur_mask = CFG_HW_SUPPORTED_RATES[i].mask; + cur_value = CFG_HW_SUPPORTED_RATES[i].value; + rates |= cur_mask; + if (rate_min > cur_value) + rate_min = cur_value; + if (rate_max < cur_value) + rate_max = cur_value; + } + } + + if (rates) { + pcm_hw->rates = rates; + pcm_hw->rate_min = rate_min; + pcm_hw->rate_max = rate_max; + } +} + +static void cfg_formats(char *list, unsigned int len, + const char *path, struct snd_pcm_hardware *pcm_hw) +{ + u64 formats; + char *cur_format; + int i; + + formats = 0; + while ((cur_format = strsep(&list, XENSND_LIST_SEPARATOR))) { + for (i = 0; i < ARRAY_SIZE(CFG_HW_SUPPORTED_FORMATS); i++) + if (!strncasecmp(cur_format, + CFG_HW_SUPPORTED_FORMATS[i].name, + XENSND_SAMPLE_FORMAT_MAX_LEN)) + formats |= CFG_HW_SUPPORTED_FORMATS[i].mask; + } + + if (formats) + pcm_hw->formats = formats; +} + +#define MAX_BUFFER_SIZE (64 * 1024) +#define MIN_PERIOD_SIZE 64 +#define MAX_PERIOD_SIZE MAX_BUFFER_SIZE +#define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE) +#define USE_RATE (SNDRV_PCM_RATE_CONTINUOUS | \ + SNDRV_PCM_RATE_8000_48000) +#define USE_RATE_MIN 5512 +#define USE_RATE_MAX 48000 +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define USE_PERIODS_MIN 2 +#define USE_PERIODS_MAX (MAX_BUFFER_SIZE / MIN_PERIOD_SIZE) + +static const struct snd_pcm_hardware SND_DRV_PCM_HW_DEFAULT = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_SIZE, + .period_bytes_min = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +static void cfg_read_pcm_hw(const char *path, + struct snd_pcm_hardware *parent_pcm_hw, + struct snd_pcm_hardware *pcm_hw) +{ + char *list; + int val; + size_t buf_sz; + unsigned int len; + + /* Inherit parent's PCM HW and read overrides from XenStore. */ + if (parent_pcm_hw) + *pcm_hw = *parent_pcm_hw; + else + *pcm_hw = SND_DRV_PCM_HW_DEFAULT; + + val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MIN, 0); + if (val) + pcm_hw->channels_min = val; + + val = xenbus_read_unsigned(path, XENSND_FIELD_CHANNELS_MAX, 0); + if (val) + pcm_hw->channels_max = val; + + list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_RATES, &len); + if (!IS_ERR(list)) { + cfg_hw_rates(list, len, path, pcm_hw); + kfree(list); + } + + list = xenbus_read(XBT_NIL, path, XENSND_FIELD_SAMPLE_FORMATS, &len); + if (!IS_ERR(list)) { + cfg_formats(list, len, path, pcm_hw); + kfree(list); + } + + buf_sz = xenbus_read_unsigned(path, XENSND_FIELD_BUFFER_SIZE, 0); + if (buf_sz) + pcm_hw->buffer_bytes_max = buf_sz; + + /* Update configuration to match new values. */ + if (pcm_hw->channels_min > pcm_hw->channels_max) + pcm_hw->channels_min = pcm_hw->channels_max; + + if (pcm_hw->rate_min > pcm_hw->rate_max) + pcm_hw->rate_min = pcm_hw->rate_max; + + pcm_hw->period_bytes_max = pcm_hw->buffer_bytes_max; + + pcm_hw->periods_max = pcm_hw->period_bytes_max / + pcm_hw->period_bytes_min; +} + +static int cfg_get_stream_type(const char *path, int index, + int *num_pb, int *num_cap) +{ + char *str = NULL; + char *stream_path; + int ret; + + *num_pb = 0; + *num_cap = 0; + stream_path = kasprintf(GFP_KERNEL, "%s/%d", path, index); + if (!stream_path) { + ret = -ENOMEM; + goto fail; + } + + str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); + if (IS_ERR(str)) { + ret = PTR_ERR(str); + str = NULL; + goto fail; + } + + if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, + sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { + (*num_pb)++; + } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, + sizeof(XENSND_STREAM_TYPE_CAPTURE))) { + (*num_cap)++; + } else { + ret = -EINVAL; + goto fail; + } + ret = 0; + +fail: + kfree(stream_path); + kfree(str); + return ret; +} + +static int cfg_stream(struct xen_snd_front_info *front_info, + struct xen_front_cfg_pcm_instance *pcm_instance, + const char *path, int index, int *cur_pb, int *cur_cap, + int *stream_cnt) +{ + char *str = NULL; + char *stream_path; + struct xen_front_cfg_stream *stream; + int ret; + + stream_path = devm_kasprintf(&front_info->xb_dev->dev, + GFP_KERNEL, "%s/%d", path, index); + if (!stream_path) { + ret = -ENOMEM; + goto fail; + } + + str = xenbus_read(XBT_NIL, stream_path, XENSND_FIELD_TYPE, NULL); + if (IS_ERR(str)) { + ret = PTR_ERR(str); + str = NULL; + goto fail; + } + + if (!strncasecmp(str, XENSND_STREAM_TYPE_PLAYBACK, + sizeof(XENSND_STREAM_TYPE_PLAYBACK))) { + stream = &pcm_instance->streams_pb[(*cur_pb)++]; + } else if (!strncasecmp(str, XENSND_STREAM_TYPE_CAPTURE, + sizeof(XENSND_STREAM_TYPE_CAPTURE))) { + stream = &pcm_instance->streams_cap[(*cur_cap)++]; + } else { + ret = -EINVAL; + goto fail; + } + + /* Get next stream index. */ + stream->index = (*stream_cnt)++; + stream->xenstore_path = stream_path; + /* + * Check XenStore if PCM HW configuration exists for this stream + * and update if so, e.g. we inherit all values from device's PCM HW, + * but can still override some of the values for the stream. + */ + cfg_read_pcm_hw(stream->xenstore_path, + &pcm_instance->pcm_hw, &stream->pcm_hw); + ret = 0; + +fail: + kfree(str); + return ret; +} + +static int cfg_device(struct xen_snd_front_info *front_info, + struct xen_front_cfg_pcm_instance *pcm_instance, + struct snd_pcm_hardware *parent_pcm_hw, + const char *path, int node_index, int *stream_cnt) +{ + char *str; + char *device_path; + int ret, i, num_streams; + int num_pb, num_cap; + int cur_pb, cur_cap; + char node[3]; + + device_path = kasprintf(GFP_KERNEL, "%s/%d", path, node_index); + if (!device_path) + return -ENOMEM; + + str = xenbus_read(XBT_NIL, device_path, XENSND_FIELD_DEVICE_NAME, NULL); + if (!IS_ERR(str)) { + strlcpy(pcm_instance->name, str, sizeof(pcm_instance->name)); + kfree(str); + } + + pcm_instance->device_id = node_index; + + /* + * Check XenStore if PCM HW configuration exists for this device + * and update if so, e.g. we inherit all values from card's PCM HW, + * but can still override some of the values for the device. + */ + cfg_read_pcm_hw(device_path, parent_pcm_hw, &pcm_instance->pcm_hw); + + /* Find out how many streams were configured in Xen store. */ + num_streams = 0; + do { + snprintf(node, sizeof(node), "%d", num_streams); + if (!xenbus_exists(XBT_NIL, device_path, node)) + break; + + num_streams++; + } while (num_streams < VSND_MAX_STREAM); + + pcm_instance->num_streams_pb = 0; + pcm_instance->num_streams_cap = 0; + /* Get number of playback and capture streams. */ + for (i = 0; i < num_streams; i++) { + ret = cfg_get_stream_type(device_path, i, &num_pb, &num_cap); + if (ret < 0) + goto fail; + + pcm_instance->num_streams_pb += num_pb; + pcm_instance->num_streams_cap += num_cap; + } + + if (pcm_instance->num_streams_pb) { + pcm_instance->streams_pb = + devm_kcalloc(&front_info->xb_dev->dev, + pcm_instance->num_streams_pb, + sizeof(struct xen_front_cfg_stream), + GFP_KERNEL); + if (!pcm_instance->streams_pb) { + ret = -ENOMEM; + goto fail; + } + } + + if (pcm_instance->num_streams_cap) { + pcm_instance->streams_cap = + devm_kcalloc(&front_info->xb_dev->dev, + pcm_instance->num_streams_cap, + sizeof(struct xen_front_cfg_stream), + GFP_KERNEL); + if (!pcm_instance->streams_cap) { + ret = -ENOMEM; + goto fail; + } + } + + cur_pb = 0; + cur_cap = 0; + for (i = 0; i < num_streams; i++) { + ret = cfg_stream(front_info, pcm_instance, device_path, i, + &cur_pb, &cur_cap, stream_cnt); + if (ret < 0) + goto fail; + } + ret = 0; + +fail: + kfree(device_path); + return ret; +} + +int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info, + int *stream_cnt) +{ + struct xenbus_device *xb_dev = front_info->xb_dev; + struct xen_front_cfg_card *cfg = &front_info->cfg; + int ret, num_devices, i; + char node[3]; + + *stream_cnt = 0; + num_devices = 0; + do { + snprintf(node, sizeof(node), "%d", num_devices); + if (!xenbus_exists(XBT_NIL, xb_dev->nodename, node)) + break; + + num_devices++; + } while (num_devices < SNDRV_PCM_DEVICES); + + if (!num_devices) { + dev_warn(&xb_dev->dev, + "No devices configured for sound card at %s\n", + xb_dev->nodename); + return -ENODEV; + } + + /* Start from default PCM HW configuration for the card. */ + cfg_read_pcm_hw(xb_dev->nodename, NULL, &cfg->pcm_hw); + + cfg->pcm_instances = + devm_kcalloc(&front_info->xb_dev->dev, num_devices, + sizeof(struct xen_front_cfg_pcm_instance), + GFP_KERNEL); + if (!cfg->pcm_instances) + return -ENOMEM; + + for (i = 0; i < num_devices; i++) { + ret = cfg_device(front_info, &cfg->pcm_instances[i], + &cfg->pcm_hw, xb_dev->nodename, i, stream_cnt); + if (ret < 0) + return ret; + } + cfg->num_pcm_instances = num_devices; + return 0; +} + diff --git a/sound/xen/xen_snd_front_cfg.h b/sound/xen/xen_snd_front_cfg.h new file mode 100644 index 000000000..2353fcc74 --- /dev/null +++ b/sound/xen/xen_snd_front_cfg.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_CFG_H +#define __XEN_SND_FRONT_CFG_H + +#include +#include + +struct xen_snd_front_info; + +struct xen_front_cfg_stream { + int index; + char *xenstore_path; + struct snd_pcm_hardware pcm_hw; +}; + +struct xen_front_cfg_pcm_instance { + char name[80]; + int device_id; + struct snd_pcm_hardware pcm_hw; + int num_streams_pb; + struct xen_front_cfg_stream *streams_pb; + int num_streams_cap; + struct xen_front_cfg_stream *streams_cap; +}; + +struct xen_front_cfg_card { + char name_short[32]; + char name_long[80]; + struct snd_pcm_hardware pcm_hw; + int num_pcm_instances; + struct xen_front_cfg_pcm_instance *pcm_instances; +}; + +int xen_snd_front_cfg_card(struct xen_snd_front_info *front_info, + int *stream_cnt); + +#endif /* __XEN_SND_FRONT_CFG_H */ diff --git a/sound/xen/xen_snd_front_evtchnl.c b/sound/xen/xen_snd_front_evtchnl.c new file mode 100644 index 000000000..102d6e096 --- /dev/null +++ b/sound/xen/xen_snd_front_evtchnl.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include +#include +#include +#include + +#include "xen_snd_front.h" +#include "xen_snd_front_alsa.h" +#include "xen_snd_front_cfg.h" +#include "xen_snd_front_evtchnl.h" + +static irqreturn_t evtchnl_interrupt_req(int irq, void *dev_id) +{ + struct xen_snd_front_evtchnl *channel = dev_id; + struct xen_snd_front_info *front_info = channel->front_info; + struct xensnd_resp *resp; + RING_IDX i, rp; + + if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED)) + return IRQ_HANDLED; + + mutex_lock(&channel->ring_io_lock); + +again: + rp = channel->u.req.ring.sring->rsp_prod; + /* Ensure we see queued responses up to rp. */ + rmb(); + + /* + * Assume that the backend is trusted to always write sane values + * to the ring counters, so no overflow checks on frontend side + * are required. + */ + for (i = channel->u.req.ring.rsp_cons; i != rp; i++) { + resp = RING_GET_RESPONSE(&channel->u.req.ring, i); + if (resp->id != channel->evt_id) + continue; + switch (resp->operation) { + case XENSND_OP_OPEN: + /* fall through */ + case XENSND_OP_CLOSE: + /* fall through */ + case XENSND_OP_READ: + /* fall through */ + case XENSND_OP_WRITE: + /* fall through */ + case XENSND_OP_TRIGGER: + channel->u.req.resp_status = resp->status; + complete(&channel->u.req.completion); + break; + case XENSND_OP_HW_PARAM_QUERY: + channel->u.req.resp_status = resp->status; + channel->u.req.resp.hw_param = + resp->resp.hw_param; + complete(&channel->u.req.completion); + break; + + default: + dev_err(&front_info->xb_dev->dev, + "Operation %d is not supported\n", + resp->operation); + break; + } + } + + channel->u.req.ring.rsp_cons = i; + if (i != channel->u.req.ring.req_prod_pvt) { + int more_to_do; + + RING_FINAL_CHECK_FOR_RESPONSES(&channel->u.req.ring, + more_to_do); + if (more_to_do) + goto again; + } else { + channel->u.req.ring.sring->rsp_event = i + 1; + } + + mutex_unlock(&channel->ring_io_lock); + return IRQ_HANDLED; +} + +static irqreturn_t evtchnl_interrupt_evt(int irq, void *dev_id) +{ + struct xen_snd_front_evtchnl *channel = dev_id; + struct xensnd_event_page *page = channel->u.evt.page; + u32 cons, prod; + + if (unlikely(channel->state != EVTCHNL_STATE_CONNECTED)) + return IRQ_HANDLED; + + mutex_lock(&channel->ring_io_lock); + + prod = page->in_prod; + /* Ensure we see ring contents up to prod. */ + virt_rmb(); + if (prod == page->in_cons) + goto out; + + /* + * Assume that the backend is trusted to always write sane values + * to the ring counters, so no overflow checks on frontend side + * are required. + */ + for (cons = page->in_cons; cons != prod; cons++) { + struct xensnd_evt *event; + + event = &XENSND_IN_RING_REF(page, cons); + if (unlikely(event->id != channel->evt_id++)) + continue; + + switch (event->type) { + case XENSND_EVT_CUR_POS: + xen_snd_front_alsa_handle_cur_pos(channel, + event->op.cur_pos.position); + break; + } + } + + page->in_cons = cons; + /* Ensure ring contents. */ + virt_wmb(); + +out: + mutex_unlock(&channel->ring_io_lock); + return IRQ_HANDLED; +} + +void xen_snd_front_evtchnl_flush(struct xen_snd_front_evtchnl *channel) +{ + int notify; + + channel->u.req.ring.req_prod_pvt++; + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&channel->u.req.ring, notify); + if (notify) + notify_remote_via_irq(channel->irq); +} + +static void evtchnl_free(struct xen_snd_front_info *front_info, + struct xen_snd_front_evtchnl *channel) +{ + unsigned long page = 0; + + if (channel->type == EVTCHNL_TYPE_REQ) + page = (unsigned long)channel->u.req.ring.sring; + else if (channel->type == EVTCHNL_TYPE_EVT) + page = (unsigned long)channel->u.evt.page; + + if (!page) + return; + + channel->state = EVTCHNL_STATE_DISCONNECTED; + if (channel->type == EVTCHNL_TYPE_REQ) { + /* Release all who still waits for response if any. */ + channel->u.req.resp_status = -EIO; + complete_all(&channel->u.req.completion); + } + + if (channel->irq) + unbind_from_irqhandler(channel->irq, channel); + + if (channel->port) + xenbus_free_evtchn(front_info->xb_dev, channel->port); + + /* End access and free the page. */ + if (channel->gref != GRANT_INVALID_REF) + gnttab_end_foreign_access(channel->gref, 0, page); + else + free_page(page); + + memset(channel, 0, sizeof(*channel)); +} + +void xen_snd_front_evtchnl_free_all(struct xen_snd_front_info *front_info) +{ + int i; + + if (!front_info->evt_pairs) + return; + + for (i = 0; i < front_info->num_evt_pairs; i++) { + evtchnl_free(front_info, &front_info->evt_pairs[i].req); + evtchnl_free(front_info, &front_info->evt_pairs[i].evt); + } + + kfree(front_info->evt_pairs); + front_info->evt_pairs = NULL; +} + +static int evtchnl_alloc(struct xen_snd_front_info *front_info, int index, + struct xen_snd_front_evtchnl *channel, + enum xen_snd_front_evtchnl_type type) +{ + struct xenbus_device *xb_dev = front_info->xb_dev; + unsigned long page; + grant_ref_t gref; + irq_handler_t handler; + char *handler_name = NULL; + int ret; + + memset(channel, 0, sizeof(*channel)); + channel->type = type; + channel->index = index; + channel->front_info = front_info; + channel->state = EVTCHNL_STATE_DISCONNECTED; + channel->gref = GRANT_INVALID_REF; + page = get_zeroed_page(GFP_KERNEL); + if (!page) { + ret = -ENOMEM; + goto fail; + } + + handler_name = kasprintf(GFP_KERNEL, "%s-%s", XENSND_DRIVER_NAME, + type == EVTCHNL_TYPE_REQ ? + XENSND_FIELD_RING_REF : + XENSND_FIELD_EVT_RING_REF); + if (!handler_name) { + ret = -ENOMEM; + goto fail; + } + + mutex_init(&channel->ring_io_lock); + + if (type == EVTCHNL_TYPE_REQ) { + struct xen_sndif_sring *sring = (struct xen_sndif_sring *)page; + + init_completion(&channel->u.req.completion); + mutex_init(&channel->u.req.req_io_lock); + SHARED_RING_INIT(sring); + FRONT_RING_INIT(&channel->u.req.ring, sring, XEN_PAGE_SIZE); + + ret = xenbus_grant_ring(xb_dev, sring, 1, &gref); + if (ret < 0) { + channel->u.req.ring.sring = NULL; + goto fail; + } + + handler = evtchnl_interrupt_req; + } else { + ret = gnttab_grant_foreign_access(xb_dev->otherend_id, + virt_to_gfn((void *)page), 0); + if (ret < 0) + goto fail; + + channel->u.evt.page = (struct xensnd_event_page *)page; + gref = ret; + handler = evtchnl_interrupt_evt; + } + + channel->gref = gref; + + ret = xenbus_alloc_evtchn(xb_dev, &channel->port); + if (ret < 0) + goto fail; + + ret = bind_evtchn_to_irq(channel->port); + if (ret < 0) { + dev_err(&xb_dev->dev, + "Failed to bind IRQ for domid %d port %d: %d\n", + front_info->xb_dev->otherend_id, channel->port, ret); + goto fail; + } + + channel->irq = ret; + + ret = request_threaded_irq(channel->irq, NULL, handler, + IRQF_ONESHOT, handler_name, channel); + if (ret < 0) { + dev_err(&xb_dev->dev, "Failed to request IRQ %d: %d\n", + channel->irq, ret); + goto fail; + } + + kfree(handler_name); + return 0; + +fail: + if (page) + free_page(page); + kfree(handler_name); + dev_err(&xb_dev->dev, "Failed to allocate ring: %d\n", ret); + return ret; +} + +int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info, + int num_streams) +{ + struct xen_front_cfg_card *cfg = &front_info->cfg; + struct device *dev = &front_info->xb_dev->dev; + int d, ret = 0; + + front_info->evt_pairs = + kcalloc(num_streams, + sizeof(struct xen_snd_front_evtchnl_pair), + GFP_KERNEL); + if (!front_info->evt_pairs) + return -ENOMEM; + + /* Iterate over devices and their streams and create event channels. */ + for (d = 0; d < cfg->num_pcm_instances; d++) { + struct xen_front_cfg_pcm_instance *pcm_instance; + int s, index; + + pcm_instance = &cfg->pcm_instances[d]; + + for (s = 0; s < pcm_instance->num_streams_pb; s++) { + index = pcm_instance->streams_pb[s].index; + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].req, + EVTCHNL_TYPE_REQ); + if (ret < 0) { + dev_err(dev, "Error allocating control channel\n"); + goto fail; + } + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].evt, + EVTCHNL_TYPE_EVT); + if (ret < 0) { + dev_err(dev, "Error allocating in-event channel\n"); + goto fail; + } + } + + for (s = 0; s < pcm_instance->num_streams_cap; s++) { + index = pcm_instance->streams_cap[s].index; + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].req, + EVTCHNL_TYPE_REQ); + if (ret < 0) { + dev_err(dev, "Error allocating control channel\n"); + goto fail; + } + + ret = evtchnl_alloc(front_info, index, + &front_info->evt_pairs[index].evt, + EVTCHNL_TYPE_EVT); + if (ret < 0) { + dev_err(dev, "Error allocating in-event channel\n"); + goto fail; + } + } + } + + front_info->num_evt_pairs = num_streams; + return 0; + +fail: + xen_snd_front_evtchnl_free_all(front_info); + return ret; +} + +static int evtchnl_publish(struct xenbus_transaction xbt, + struct xen_snd_front_evtchnl *channel, + const char *path, const char *node_ring, + const char *node_chnl) +{ + struct xenbus_device *xb_dev = channel->front_info->xb_dev; + int ret; + + /* Write control channel ring reference. */ + ret = xenbus_printf(xbt, path, node_ring, "%u", channel->gref); + if (ret < 0) { + dev_err(&xb_dev->dev, "Error writing ring-ref: %d\n", ret); + return ret; + } + + /* Write event channel ring reference. */ + ret = xenbus_printf(xbt, path, node_chnl, "%u", channel->port); + if (ret < 0) { + dev_err(&xb_dev->dev, "Error writing event channel: %d\n", ret); + return ret; + } + + return 0; +} + +int xen_snd_front_evtchnl_publish_all(struct xen_snd_front_info *front_info) +{ + struct xen_front_cfg_card *cfg = &front_info->cfg; + struct xenbus_transaction xbt; + int ret, d; + +again: + ret = xenbus_transaction_start(&xbt); + if (ret < 0) { + xenbus_dev_fatal(front_info->xb_dev, ret, + "starting transaction"); + return ret; + } + + for (d = 0; d < cfg->num_pcm_instances; d++) { + struct xen_front_cfg_pcm_instance *pcm_instance; + int s, index; + + pcm_instance = &cfg->pcm_instances[d]; + + for (s = 0; s < pcm_instance->num_streams_pb; s++) { + index = pcm_instance->streams_pb[s].index; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].req, + pcm_instance->streams_pb[s].xenstore_path, + XENSND_FIELD_RING_REF, + XENSND_FIELD_EVT_CHNL); + if (ret < 0) + goto fail; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].evt, + pcm_instance->streams_pb[s].xenstore_path, + XENSND_FIELD_EVT_RING_REF, + XENSND_FIELD_EVT_EVT_CHNL); + if (ret < 0) + goto fail; + } + + for (s = 0; s < pcm_instance->num_streams_cap; s++) { + index = pcm_instance->streams_cap[s].index; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].req, + pcm_instance->streams_cap[s].xenstore_path, + XENSND_FIELD_RING_REF, + XENSND_FIELD_EVT_CHNL); + if (ret < 0) + goto fail; + + ret = evtchnl_publish(xbt, + &front_info->evt_pairs[index].evt, + pcm_instance->streams_cap[s].xenstore_path, + XENSND_FIELD_EVT_RING_REF, + XENSND_FIELD_EVT_EVT_CHNL); + if (ret < 0) + goto fail; + } + } + ret = xenbus_transaction_end(xbt, 0); + if (ret < 0) { + if (ret == -EAGAIN) + goto again; + + xenbus_dev_fatal(front_info->xb_dev, ret, + "completing transaction"); + goto fail_to_end; + } + return 0; +fail: + xenbus_transaction_end(xbt, 1); +fail_to_end: + xenbus_dev_fatal(front_info->xb_dev, ret, "writing XenStore"); + return ret; +} + +void xen_snd_front_evtchnl_pair_set_connected(struct xen_snd_front_evtchnl_pair *evt_pair, + bool is_connected) +{ + enum xen_snd_front_evtchnl_state state; + + if (is_connected) + state = EVTCHNL_STATE_CONNECTED; + else + state = EVTCHNL_STATE_DISCONNECTED; + + mutex_lock(&evt_pair->req.ring_io_lock); + evt_pair->req.state = state; + mutex_unlock(&evt_pair->req.ring_io_lock); + + mutex_lock(&evt_pair->evt.ring_io_lock); + evt_pair->evt.state = state; + mutex_unlock(&evt_pair->evt.ring_io_lock); +} + +void xen_snd_front_evtchnl_pair_clear(struct xen_snd_front_evtchnl_pair *evt_pair) +{ + mutex_lock(&evt_pair->req.ring_io_lock); + evt_pair->req.evt_next_id = 0; + mutex_unlock(&evt_pair->req.ring_io_lock); + + mutex_lock(&evt_pair->evt.ring_io_lock); + evt_pair->evt.evt_next_id = 0; + mutex_unlock(&evt_pair->evt.ring_io_lock); +} + diff --git a/sound/xen/xen_snd_front_evtchnl.h b/sound/xen/xen_snd_front_evtchnl.h new file mode 100644 index 000000000..cbe51fd1e --- /dev/null +++ b/sound/xen/xen_snd_front_evtchnl.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_EVTCHNL_H +#define __XEN_SND_FRONT_EVTCHNL_H + +#include + +struct xen_snd_front_info; + +#ifndef GRANT_INVALID_REF +/* + * FIXME: usage of grant reference 0 as invalid grant reference: + * grant reference 0 is valid, but never exposed to a PV driver, + * because of the fact it is already in use/reserved by the PV console. + */ +#define GRANT_INVALID_REF 0 +#endif + +/* Timeout in ms to wait for backend to respond. */ +#define VSND_WAIT_BACK_MS 3000 + +enum xen_snd_front_evtchnl_state { + EVTCHNL_STATE_DISCONNECTED, + EVTCHNL_STATE_CONNECTED, +}; + +enum xen_snd_front_evtchnl_type { + EVTCHNL_TYPE_REQ, + EVTCHNL_TYPE_EVT, +}; + +struct xen_snd_front_evtchnl { + struct xen_snd_front_info *front_info; + int gref; + int port; + int irq; + int index; + /* State of the event channel. */ + enum xen_snd_front_evtchnl_state state; + enum xen_snd_front_evtchnl_type type; + /* Either response id or incoming event id. */ + u16 evt_id; + /* Next request id or next expected event id. */ + u16 evt_next_id; + /* Shared ring access lock. */ + struct mutex ring_io_lock; + union { + struct { + struct xen_sndif_front_ring ring; + struct completion completion; + /* Serializer for backend IO: request/response. */ + struct mutex req_io_lock; + + /* Latest response status. */ + int resp_status; + union { + struct xensnd_query_hw_param hw_param; + } resp; + } req; + struct { + struct xensnd_event_page *page; + /* This is needed to handle XENSND_EVT_CUR_POS event. */ + struct snd_pcm_substream *substream; + } evt; + } u; +}; + +struct xen_snd_front_evtchnl_pair { + struct xen_snd_front_evtchnl req; + struct xen_snd_front_evtchnl evt; +}; + +int xen_snd_front_evtchnl_create_all(struct xen_snd_front_info *front_info, + int num_streams); + +void xen_snd_front_evtchnl_free_all(struct xen_snd_front_info *front_info); + +int xen_snd_front_evtchnl_publish_all(struct xen_snd_front_info *front_info); + +void xen_snd_front_evtchnl_flush(struct xen_snd_front_evtchnl *evtchnl); + +void xen_snd_front_evtchnl_pair_set_connected(struct xen_snd_front_evtchnl_pair *evt_pair, + bool is_connected); + +void xen_snd_front_evtchnl_pair_clear(struct xen_snd_front_evtchnl_pair *evt_pair); + +#endif /* __XEN_SND_FRONT_EVTCHNL_H */ diff --git a/sound/xen/xen_snd_front_shbuf.c b/sound/xen/xen_snd_front_shbuf.c new file mode 100644 index 000000000..07ac176a4 --- /dev/null +++ b/sound/xen/xen_snd_front_shbuf.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#include +#include +#include + +#include "xen_snd_front_shbuf.h" + +grant_ref_t xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf *buf) +{ + if (!buf->grefs) + return GRANT_INVALID_REF; + + return buf->grefs[0]; +} + +void xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf *buf) +{ + memset(buf, 0, sizeof(*buf)); +} + +void xen_snd_front_shbuf_free(struct xen_snd_front_shbuf *buf) +{ + int i; + + if (buf->grefs) { + for (i = 0; i < buf->num_grefs; i++) + if (buf->grefs[i] != GRANT_INVALID_REF) + gnttab_end_foreign_access(buf->grefs[i], + 0, 0UL); + kfree(buf->grefs); + } + kfree(buf->directory); + free_pages_exact(buf->buffer, buf->buffer_sz); + xen_snd_front_shbuf_clear(buf); +} + +/* + * number of grant references a page can hold with respect to the + * xensnd_page_directory header + */ +#define XENSND_NUM_GREFS_PER_PAGE ((XEN_PAGE_SIZE - \ + offsetof(struct xensnd_page_directory, gref)) / \ + sizeof(grant_ref_t)) + +static void fill_page_dir(struct xen_snd_front_shbuf *buf, + int num_pages_dir) +{ + struct xensnd_page_directory *page_dir; + unsigned char *ptr; + int i, cur_gref, grefs_left, to_copy; + + ptr = buf->directory; + grefs_left = buf->num_grefs - num_pages_dir; + /* + * skip grant references at the beginning, they are for pages granted + * for the page directory itself + */ + cur_gref = num_pages_dir; + for (i = 0; i < num_pages_dir; i++) { + page_dir = (struct xensnd_page_directory *)ptr; + if (grefs_left <= XENSND_NUM_GREFS_PER_PAGE) { + to_copy = grefs_left; + page_dir->gref_dir_next_page = GRANT_INVALID_REF; + } else { + to_copy = XENSND_NUM_GREFS_PER_PAGE; + page_dir->gref_dir_next_page = buf->grefs[i + 1]; + } + + memcpy(&page_dir->gref, &buf->grefs[cur_gref], + to_copy * sizeof(grant_ref_t)); + + ptr += XEN_PAGE_SIZE; + grefs_left -= to_copy; + cur_gref += to_copy; + } +} + +static int grant_references(struct xenbus_device *xb_dev, + struct xen_snd_front_shbuf *buf, + int num_pages_dir, int num_pages_buffer, + int num_grefs) +{ + grant_ref_t priv_gref_head; + unsigned long frame; + int ret, i, j, cur_ref; + int otherend_id; + + ret = gnttab_alloc_grant_references(num_grefs, &priv_gref_head); + if (ret) + return ret; + + buf->num_grefs = num_grefs; + otherend_id = xb_dev->otherend_id; + j = 0; + + for (i = 0; i < num_pages_dir; i++) { + cur_ref = gnttab_claim_grant_reference(&priv_gref_head); + if (cur_ref < 0) { + ret = cur_ref; + goto fail; + } + + frame = xen_page_to_gfn(virt_to_page(buf->directory + + XEN_PAGE_SIZE * i)); + gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0); + buf->grefs[j++] = cur_ref; + } + + for (i = 0; i < num_pages_buffer; i++) { + cur_ref = gnttab_claim_grant_reference(&priv_gref_head); + if (cur_ref < 0) { + ret = cur_ref; + goto fail; + } + + frame = xen_page_to_gfn(virt_to_page(buf->buffer + + XEN_PAGE_SIZE * i)); + gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0); + buf->grefs[j++] = cur_ref; + } + + gnttab_free_grant_references(priv_gref_head); + fill_page_dir(buf, num_pages_dir); + return 0; + +fail: + gnttab_free_grant_references(priv_gref_head); + return ret; +} + +static int alloc_int_buffers(struct xen_snd_front_shbuf *buf, + int num_pages_dir, int num_pages_buffer, + int num_grefs) +{ + buf->grefs = kcalloc(num_grefs, sizeof(*buf->grefs), GFP_KERNEL); + if (!buf->grefs) + return -ENOMEM; + + buf->directory = kcalloc(num_pages_dir, XEN_PAGE_SIZE, GFP_KERNEL); + if (!buf->directory) + goto fail; + + buf->buffer_sz = num_pages_buffer * XEN_PAGE_SIZE; + buf->buffer = alloc_pages_exact(buf->buffer_sz, GFP_KERNEL); + if (!buf->buffer) + goto fail; + + return 0; + +fail: + kfree(buf->grefs); + buf->grefs = NULL; + kfree(buf->directory); + buf->directory = NULL; + return -ENOMEM; +} + +int xen_snd_front_shbuf_alloc(struct xenbus_device *xb_dev, + struct xen_snd_front_shbuf *buf, + unsigned int buffer_sz) +{ + int num_pages_buffer, num_pages_dir, num_grefs; + int ret; + + xen_snd_front_shbuf_clear(buf); + + num_pages_buffer = DIV_ROUND_UP(buffer_sz, XEN_PAGE_SIZE); + /* number of pages the page directory consumes itself */ + num_pages_dir = DIV_ROUND_UP(num_pages_buffer, + XENSND_NUM_GREFS_PER_PAGE); + num_grefs = num_pages_buffer + num_pages_dir; + + ret = alloc_int_buffers(buf, num_pages_dir, + num_pages_buffer, num_grefs); + if (ret < 0) + return ret; + + ret = grant_references(xb_dev, buf, num_pages_dir, num_pages_buffer, + num_grefs); + if (ret < 0) + return ret; + + fill_page_dir(buf, num_pages_dir); + return 0; +} diff --git a/sound/xen/xen_snd_front_shbuf.h b/sound/xen/xen_snd_front_shbuf.h new file mode 100644 index 000000000..d28e97c47 --- /dev/null +++ b/sound/xen/xen_snd_front_shbuf.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ + +/* + * Xen para-virtual sound device + * + * Copyright (C) 2016-2018 EPAM Systems Inc. + * + * Author: Oleksandr Andrushchenko + */ + +#ifndef __XEN_SND_FRONT_SHBUF_H +#define __XEN_SND_FRONT_SHBUF_H + +#include + +#include "xen_snd_front_evtchnl.h" + +struct xen_snd_front_shbuf { + int num_grefs; + grant_ref_t *grefs; + u8 *directory; + u8 *buffer; + size_t buffer_sz; +}; + +grant_ref_t xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf *buf); + +int xen_snd_front_shbuf_alloc(struct xenbus_device *xb_dev, + struct xen_snd_front_shbuf *buf, + unsigned int buffer_sz); + +void xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf *buf); + +void xen_snd_front_shbuf_free(struct xen_snd_front_shbuf *buf); + +#endif /* __XEN_SND_FRONT_SHBUF_H */ -- cgit v1.2.3